[med-svn] [visad] 13/15: New upstream version 2.0

Andreas Tille tille at debian.org
Sat Dec 30 13:05:30 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository visad.

commit 78ecb52653a76abdff73bc524d946e7dfe4964ec
Author: Andreas Tille <tille at debian.org>
Date:   Sat Dec 30 14:04:09 2017 +0100

    New upstream version 2.0
---
 HTTPClient/0COPYRIGHT                              |   28 +
 HTTPClient/0LICENSE                                |  504 ++
 HTTPClient/0README                                 |   76 +
 HTTPClient/AuthSchemeNotImplException.java         |   66 +
 HTTPClient/AuthorizationHandler.java               |  151 +
 HTTPClient/AuthorizationInfo.java                  | 1222 ++++
 HTTPClient/AuthorizationModule.java                |  629 ++
 HTTPClient/AuthorizationPrompter.java              |   68 +
 HTTPClient/BufferedInputStream.java                |  230 +
 HTTPClient/CIHashtable.java                        |  276 +
 HTTPClient/ChunkedInputStream.java                 |  136 +
 HTTPClient/Codecs.java                             | 1566 +++++
 HTTPClient/ContentEncodingModule.java              |  219 +
 HTTPClient/ContentMD5Module.java                   |  185 +
 HTTPClient/Cookie.java                             |  574 ++
 HTTPClient/Cookie2.java                            |  575 ++
 HTTPClient/CookieModule.java                       | 1168 ++++
 HTTPClient/CookiePolicyHandler.java                |   72 +
 HTTPClient/DefaultAuthHandler.java                 | 1751 ++++++
 HTTPClient/DefaultModule.java                      |  153 +
 HTTPClient/FilenameMangler.java                    |   73 +
 HTTPClient/GlobalConstants.java                    |   63 +
 HTTPClient/HTTPClientModule.java                   |  199 +
 HTTPClient/HTTPClientModuleConstants.java          |   93 +
 HTTPClient/HTTPConnection.java                     | 3781 ++++++++++++
 HTTPClient/HTTPResponse.java                       |  960 +++
 HTTPClient/HashVerifier.java                       |   55 +
 HTTPClient/HttpHeaderElement.java                  |  203 +
 HTTPClient/HttpOutputStream.java                   |  456 ++
 HTTPClient/HttpURLConnection.java                  |  814 +++
 HTTPClient/IdempotentSequence.java                 |  405 ++
 HTTPClient/LinkedList.java                         |  239 +
 HTTPClient/Log.java                                |  318 +
 HTTPClient/MD5.java                                |  161 +
 HTTPClient/MD5InputStream.java                     |  143 +
 HTTPClient/ModuleException.java                    |   66 +
 HTTPClient/NVPair.java                             |  110 +
 HTTPClient/ParseException.java                     |   67 +
 HTTPClient/ProtocolNotSuppException.java           |   67 +
 HTTPClient/RedirectionModule.java                  |  543 ++
 HTTPClient/Request.java                            |  306 +
 HTTPClient/RespInputStream.java                    |  345 ++
 HTTPClient/Response.java                           | 1420 +++++
 HTTPClient/ResponseHandler.java                    |  138 +
 HTTPClient/RetryException.java                     |  105 +
 HTTPClient/RetryModule.java                        |  285 +
 HTTPClient/RoRequest.java                          |   81 +
 HTTPClient/RoResponse.java                         |  183 +
 HTTPClient/SocksClient.java                        |  622 ++
 HTTPClient/SocksException.java                     |   67 +
 HTTPClient/StreamDemultiplexor.java                |  968 +++
 HTTPClient/TransferEncodingModule.java             |  216 +
 HTTPClient/URI.java                                | 1939 ++++++
 HTTPClient/UncompressInputStream.java              |  470 ++
 HTTPClient/Util.java                               | 1131 ++++
 HTTPClient/package.html                            |   44 +
 Jama/CholeskyDecomposition.java                    |  198 +
 Jama/EigenvalueDecomposition.java                  |  955 +++
 Jama/LUDecomposition.java                          |  311 +
 Jama/Matrix.java                                   | 1088 ++++
 Jama/QRDecomposition.java                          |  218 +
 Jama/SingularValueDecomposition.java               |  539 ++
 Jama/doc/Jama/CholeskyDecomposition.html           |  227 +
 Jama/doc/Jama/EigenvalueDecomposition.html         |  249 +
 Jama/doc/Jama/LUDecomposition.html                 |  302 +
 Jama/doc/Jama/Matrix.html                          | 1520 +++++
 Jama/doc/Jama/QRDecomposition.html                 |  266 +
 Jama/doc/Jama/SingularValueDecomposition.html      |  301 +
 Jama/doc/Jama/examples/MagicSquareExample.html     |  196 +
 Jama/doc/Jama/package-frame.html                   |   47 +
 Jama/doc/Jama/package-summary.html                 |  116 +
 Jama/doc/Jama/test/TestMatrix.html                 |  164 +
 Jama/doc/Jama/tree.html                            |   93 +
 Jama/doc/Jama/util/Maths.html                      |  145 +
 Jama/doc/allclasses-frame.html                     |   54 +
 Jama/doc/help.html                                 |  115 +
 Jama/doc/index-all.html                            |  465 ++
 Jama/doc/index.html                                |   22 +
 Jama/doc/overview-frame.html                       |   45 +
 Jama/doc/overview-summary.html                     |   86 +
 Jama/doc/serializedform.html                       |  433 ++
 Jama/doc/stylesheet.css                            |   20 +
 Jama/doc/tree.html                                 |   92 +
 Jama/examples/MagicSquareExample.java              |  163 +
 Jama/test/TestMatrix.java                          |  939 +++
 Jama/util/Maths.java                               |   20 +
 META-INF/MANIFEST.MF                               |    4 +
 build.xml                                          |   47 +
 debian/TODO                                        |   15 -
 debian/changelog                                   |    7 -
 debian/compat                                      |    1 -
 debian/control                                     |   56 -
 debian/copyright                                   |  369 --
 debian/get-orig-source                             |   33 -
 debian/libvisad-java.jlibs                         |    1 -
 debian/patches/build.xml                           |   50 -
 debian/patches/fixopendap.patch                    |  771 ---
 debian/patches/remove_uncompressinputstream.patch  |   12 -
 debian/patches/series                              |    3 -
 debian/rules                                       |   13 -
 debian/source/format                               |    1 -
 dods/dap/Attribute.java                            |  567 ++
 dods/dap/AttributeBadValueException.java           |   45 +
 dods/dap/AttributeExistsException.java             |   45 +
 dods/dap/AttributeTable.java                       |  382 ++
 dods/dap/BadSemanticsException.java                |   45 +
 dods/dap/BaseType.java                             |  437 ++
 dods/dap/BaseTypeFactory.java                      |  250 +
 dods/dap/BaseTypePrimitiveVector.java              |  239 +
 dods/dap/BooleanPrimitiveVector.java               |  255 +
 dods/dap/BytePrimitiveVector.java                  |  268 +
 dods/dap/ClientIO.java                             |   68 +
 dods/dap/DAS.java                                  |  200 +
 dods/dap/DASException.java                         |   31 +
 dods/dap/DArray.java                               |  300 +
 dods/dap/DArrayDimension.java                      |  134 +
 dods/dap/DBoolean.java                             |  143 +
 dods/dap/DByte.java                                |  136 +
 dods/dap/DConnect.java                             |  653 ++
 dods/dap/DConstructor.java                         |   95 +
 dods/dap/DDS.java                                  |  535 ++
 dods/dap/DDSException.java                         |   31 +
 dods/dap/DFloat32.java                             |  143 +
 dods/dap/DFloat64.java                             |  124 +
 dods/dap/DGrid.java                                |  430 ++
 dods/dap/DInt16.java                               |  149 +
 dods/dap/DInt32.java                               |  123 +
 dods/dap/DList.java                                |   49 +
 dods/dap/DODSException.java                        |  231 +
 dods/dap/DONE                                      |  371 ++
 dods/dap/DSequence.java                            |  565 ++
 dods/dap/DString.java                              |  174 +
 dods/dap/DStructure.java                           |  296 +
 dods/dap/DUInt16.java                              |   93 +
 dods/dap/DUInt32.java                              |   71 +
 dods/dap/DURL.java                                 |   40 +
 dods/dap/DVector.java                              |  253 +
 dods/dap/DataDDS.java                              |  159 +
 dods/dap/DataReadException.java                    |   43 +
 dods/dap/DefaultFactory.java                       |  301 +
 dods/dap/Float32PrimitiveVector.java               |  257 +
 dods/dap/Float64PrimitiveVector.java               |  239 +
 dods/dap/HeaderInputStream.java                    |  143 +
 dods/dap/Int16PrimitiveVector.java                 |  259 +
 dods/dap/Int32PrimitiveVector.java                 |  237 +
 dods/dap/NoSuchAttributeException.java             |   44 +
 dods/dap/NoSuchFunctionException.java              |   39 +
 dods/dap/NoSuchVariableException.java              |   42 +
 dods/dap/PrimitiveVector.java                      |  236 +
 dods/dap/Server/AbstractClause.java                |   48 +
 dods/dap/Server/BTFunction.java                    |   39 +
 dods/dap/Server/BTFunctionClause.java              |   96 +
 dods/dap/Server/BoolFunction.java                  |   26 +
 dods/dap/Server/BoolFunctionClause.java            |   83 +
 dods/dap/Server/CEEvaluator.java                   |  329 +
 dods/dap/Server/Clause.java                        |   27 +
 dods/dap/Server/ClauseFactory.java                 |  128 +
 dods/dap/Server/DereferenceClause.java             |   61 +
 dods/dap/Server/FunctionLibrary.java               |  143 +
 dods/dap/Server/InvalidOperatorException.java      |   69 +
 dods/dap/Server/InvalidParameterException.java     |   66 +
 dods/dap/Server/Operator.java                      | 1788 ++++++
 dods/dap/Server/RegExpException.java               |   72 +
 dods/dap/Server/RelOpClause.java                   |  134 +
 dods/dap/Server/RelOps.java                        |   33 +
 dods/dap/Server/SBHException.java                  |   78 +
 dods/dap/Server/SDArray.java                       |  476 ++
 dods/dap/Server/SDBoolean.java                     |  303 +
 dods/dap/Server/SDByte.java                        |  302 +
 dods/dap/Server/SDFloat32.java                     |  305 +
 dods/dap/Server/SDFloat64.java                     |  303 +
 dods/dap/Server/SDGrid.java                        |  606 ++
 dods/dap/Server/SDInt16.java                       |  307 +
 dods/dap/Server/SDInt32.java                       |  305 +
 dods/dap/Server/SDList.java                        |  414 ++
 dods/dap/Server/SDODSException.java                |   62 +
 dods/dap/Server/SDSequence.java                    |  459 ++
 dods/dap/Server/SDString.java                      |  316 +
 dods/dap/Server/SDStructure.java                   |  356 ++
 dods/dap/Server/SDUInt16.java                      |  306 +
 dods/dap/Server/SDUInt32.java                      |  305 +
 dods/dap/Server/SDURL.java                         |  308 +
 dods/dap/Server/SSFunctionException.java           |   67 +
 dods/dap/Server/ServerArrayMethods.java            |   69 +
 dods/dap/Server/ServerDDS.java                     |  123 +
 dods/dap/Server/ServerMethods.java                 |  249 +
 dods/dap/Server/ServerSideFunction.java            |   37 +
 dods/dap/Server/SubClause.java                     |   50 +
 dods/dap/Server/TopLevelClause.java                |   36 +
 dods/dap/Server/ValueClause.java                   |   71 +
 dods/dap/Server/WrongTypeException.java            |   31 +
 dods/dap/Server/package.html                       |    8 +
 dods/dap/ServerVersion.java                        |  108 +
 dods/dap/StatusUI.java                             |   51 +
 dods/dap/TODO                                      |   96 +
 dods/dap/UInt16PrimitiveVector.java                |   81 +
 dods/dap/UInt32PrimitiveVector.java                |   62 +
 dods/dap/UIntTest.java                             |  113 +
 dods/dap/Util.java                                 |  171 +
 dods/dap/functions/Length.java                     |   30 +
 dods/dap/package.html                              |    8 +
 dods/dap/parser/DASParser.java                     | 1087 ++++
 dods/dap/parser/DASParser.jj                       |  438 ++
 dods/dap/parser/DASParserConstants.java            |   49 +
 dods/dap/parser/DASParserTokenManager.java         | 1017 ++++
 dods/dap/parser/DDSParser.java                     |  979 +++
 dods/dap/parser/DDSParser.jj                       |  470 ++
 dods/dap/parser/DDSParserConstants.java            |   56 +
 dods/dap/parser/DDSParserTokenManager.java         | 1132 ++++
 dods/dap/parser/ErrorParser.java                   |  233 +
 dods/dap/parser/ErrorParser.jj                     |  128 +
 dods/dap/parser/ErrorParserConstants.java          |   43 +
 dods/dap/parser/ErrorParserTokenManager.java       |  827 +++
 dods/dap/parser/ExprParser.java                    | 1079 ++++
 dods/dap/parser/ExprParser.jj                      |  646 ++
 dods/dap/parser/ExprParserConstants.java           |   72 +
 dods/dap/parser/ExprParserTokenManager.java        |  539 ++
 dods/dap/parser/ParseException.java                |  192 +
 dods/dap/parser/SimpleCharStream.java              |  401 ++
 dods/dap/parser/Token.java                         |   81 +
 dods/dap/parser/TokenMgrError.java                 |  133 +
 dods/dap/parser/package.html                       |    8 +
 dods/util/DODSiniFile.java                         |   76 +
 dods/util/Debug.java                               |   67 +
 dods/util/Getopts.java                             |  308 +
 dods/util/InvalidSwitch.java                       |   47 +
 dods/util/OptSwitch.java                           |  139 +
 dods/util/SortedTable.java                         |  126 +
 dods/util/Tools.java                               |   87 +
 dods/util/dasTools.java                            |  184 +
 dods/util/iniFile.java                             |  428 ++
 dods/util/package.html                             |    8 +
 dods/util/test_iniFile.java                        |   65 +
 edu/wisc/ssec/mcidas/ABISnav.java                  |  464 ++
 edu/wisc/ssec/mcidas/AREAnav.java                  |  742 +++
 edu/wisc/ssec/mcidas/AncillaryData.java            |  384 ++
 edu/wisc/ssec/mcidas/AreaDirectory.java            |  622 ++
 edu/wisc/ssec/mcidas/AreaDirectoryList.java        |  510 ++
 edu/wisc/ssec/mcidas/AreaFile.java                 | 1678 ++++++
 edu/wisc/ssec/mcidas/AreaFileException.java        |   51 +
 edu/wisc/ssec/mcidas/AreaFileFactory.java          |  456 ++
 edu/wisc/ssec/mcidas/Calibrator.java               |  104 +
 edu/wisc/ssec/mcidas/CalibratorDefault.java        |  168 +
 edu/wisc/ssec/mcidas/CalibratorException.java      |   51 +
 edu/wisc/ssec/mcidas/CalibratorFY2.java            |  189 +
 edu/wisc/ssec/mcidas/CalibratorFactory.java        |  149 +
 edu/wisc/ssec/mcidas/CalibratorGvar.java           |  402 ++
 edu/wisc/ssec/mcidas/CalibratorGvarG10.java        |  208 +
 edu/wisc/ssec/mcidas/CalibratorGvarG12.java        |  138 +
 edu/wisc/ssec/mcidas/CalibratorGvarG13.java        |  137 +
 edu/wisc/ssec/mcidas/CalibratorGvarG8.java         |  208 +
 edu/wisc/ssec/mcidas/CalibratorGvarG9.java         |  207 +
 edu/wisc/ssec/mcidas/CalibratorMsg.java            |  440 ++
 edu/wisc/ssec/mcidas/ConversionUtility.java        |  224 +
 edu/wisc/ssec/mcidas/EnhancementTable.java         |  252 +
 edu/wisc/ssec/mcidas/GEOSnav.java                  |  210 +
 edu/wisc/ssec/mcidas/GMSXnav.java                  | 1222 ++++
 edu/wisc/ssec/mcidas/GOESnav.java                  | 1097 ++++
 edu/wisc/ssec/mcidas/GRIDnav.java                  |  460 ++
 edu/wisc/ssec/mcidas/GVARnav.java                  | 1213 ++++
 edu/wisc/ssec/mcidas/GridDirectory.java            |  395 ++
 edu/wisc/ssec/mcidas/GridDirectoryList.java        |  266 +
 edu/wisc/ssec/mcidas/KALPnav.java                  |  438 ++
 edu/wisc/ssec/mcidas/LALOnav.java                  |  489 ++
 edu/wisc/ssec/mcidas/LAMBnav.java                  |  191 +
 edu/wisc/ssec/mcidas/MERCnav.java                  |  289 +
 edu/wisc/ssec/mcidas/MOLLnav.java                  |  560 ++
 edu/wisc/ssec/mcidas/MSATnav.java                  |  466 ++
 edu/wisc/ssec/mcidas/MSGTnav.java                  |  425 ++
 edu/wisc/ssec/mcidas/MSGnav.java                   |  415 ++
 edu/wisc/ssec/mcidas/McIDASException.java          |   50 +
 edu/wisc/ssec/mcidas/McIDASUtil.java               |  503 ++
 edu/wisc/ssec/mcidas/PSnav.java                    |  182 +
 edu/wisc/ssec/mcidas/RADRnav.java                  |  358 ++
 edu/wisc/ssec/mcidas/RECTnav.java                  |  372 ++
 edu/wisc/ssec/mcidas/SINUnav.java                  |  293 +
 edu/wisc/ssec/mcidas/TANCnav.java                  |  249 +
 edu/wisc/ssec/mcidas/adde/AddeDatasetURL.java      |  183 +
 edu/wisc/ssec/mcidas/adde/AddeException.java       |  111 +
 edu/wisc/ssec/mcidas/adde/AddeGridReader.java      |  356 ++
 edu/wisc/ssec/mcidas/adde/AddeImageURL.java        |  675 +++
 edu/wisc/ssec/mcidas/adde/AddePointDataReader.java |  538 ++
 edu/wisc/ssec/mcidas/adde/AddePointURL.java        |  321 +
 edu/wisc/ssec/mcidas/adde/AddeSatBands.java        |  171 +
 edu/wisc/ssec/mcidas/adde/AddeServerInfo.java      |  658 ++
 edu/wisc/ssec/mcidas/adde/AddeTextReader.java      |  500 ++
 edu/wisc/ssec/mcidas/adde/AddeURL.java             |  603 ++
 edu/wisc/ssec/mcidas/adde/AddeURLConnection.java   | 2333 ++++++++
 edu/wisc/ssec/mcidas/adde/AddeURLException.java    |   48 +
 .../ssec/mcidas/adde/AddeURLStreamHandler.java     |   72 +
 .../mcidas/adde/AddeURLStreamHandlerFactory.java   |   62 +
 edu/wisc/ssec/mcidas/adde/DataSetInfo.java         |  339 ++
 edu/wisc/ssec/mcidas/adde/GetAreaFile.java         |  538 ++
 edu/wisc/ssec/mcidas/adde/GetAreaGUI.java          | 1643 +++++
 edu/wisc/ssec/mcidas/adde/Handler.java             |   43 +
 edu/wisc/ssec/mcidas/adde/ReadTextFile.java        |  201 +
 edu/wisc/ssec/mcidas/adde/WxTextProduct.java       |  222 +
 examples/AspectRatio.java                          |  107 +
 examples/CollabMapTest.java                        |  233 +
 examples/DelaunayTest.java                         |  380 ++
 examples/DisplayEventTest.java                     |  150 +
 examples/DisplaySwitch.java                        |  650 ++
 examples/DisplayTest.java                          |  101 +
 examples/Earth.java                                |  149 +
 examples/FlowTest.java                             |  167 +
 examples/GeoDisplay.java                           |   76 +
 examples/HSVDisplay.java                           |  356 ++
 examples/IDeskMcIDAS.java                          |  157 +
 examples/MapProjectionDisplay.java                 |  526 ++
 examples/MiniSheet.java                            |  296 +
 examples/MultiData.java                            |  152 +
 examples/Parallel.java                             |  129 +
 examples/ReflectionTest.java                       |  121 +
 examples/Region.java                               |   96 +
 examples/Region3D.java                             |  185 +
 examples/Rivers.java                               |   97 +
 examples/SatDisplay.java                           |  233 +
 examples/ScaleTest.java                            |  401 ++
 examples/Simple.java                               |  129 +
 examples/SimpleAnimate.java                        |  125 +
 examples/SimpleImage.java                          |  125 +
 examples/SimpleMcIDAS.java                         |  110 +
 examples/SocketDataTest.java                       |  232 +
 examples/Stream2DTest.java                         |  151 +
 examples/Test00.java                               |  153 +
 examples/Test01.java                               |  132 +
 examples/Test02.java                               |  136 +
 examples/Test03.java                               |  145 +
 examples/Test04.java                               |   82 +
 examples/Test05.java                               |  148 +
 examples/Test06.java                               |  127 +
 examples/Test07.java                               |   81 +
 examples/Test08.java                               |   82 +
 examples/Test09.java                               |  135 +
 examples/Test10.java                               |  186 +
 examples/Test11.java                               |   93 +
 examples/Test12.java                               |  160 +
 examples/Test13.java                               |   94 +
 examples/Test14.java                               |  123 +
 examples/Test15.java                               |  106 +
 examples/Test16.java                               |   99 +
 examples/Test17.java                               |   81 +
 examples/Test18.java                               |  133 +
 examples/Test19.java                               |  169 +
 examples/Test20.java                               |   95 +
 examples/Test21.java                               |  114 +
 examples/Test22.java                               |   81 +
 examples/Test23.java                               |   81 +
 examples/Test24.java                               |   96 +
 examples/Test25.java                               |   79 +
 examples/Test26.java                               |  140 +
 examples/Test27.java                               |  207 +
 examples/Test28.java                               |   83 +
 examples/Test29.java                               |   81 +
 examples/Test30.java                               |  107 +
 examples/Test31.java                               |   91 +
 examples/Test32.java                               |  168 +
 examples/Test33.java                               |  107 +
 examples/Test34.java                               |  122 +
 examples/Test35.java                               |  159 +
 examples/Test36.java                               |   87 +
 examples/Test37.java                               |  198 +
 examples/Test38.java                               |   91 +
 examples/Test39.java                               |  126 +
 examples/Test40.java                               |  114 +
 examples/Test41.java                               |  139 +
 examples/Test42.java                               |  141 +
 examples/Test43.java                               |  177 +
 examples/Test44.java                               |  153 +
 examples/Test45.java                               |  146 +
 examples/Test46.java                               |  146 +
 examples/Test47.java                               |  182 +
 examples/Test48.java                               |   85 +
 examples/Test49.java                               |   85 +
 examples/Test50.java                               |  130 +
 examples/Test51.java                               |  145 +
 examples/Test52.java                               |  125 +
 examples/Test53.java                               |  185 +
 examples/Test54.java                               |  113 +
 examples/Test55.java                               |  132 +
 examples/Test56.java                               |  138 +
 examples/Test57.java                               |  136 +
 examples/Test58.java                               |  134 +
 examples/Test59.java                               |  113 +
 examples/Test60.java                               |  103 +
 examples/Test61.java                               |  208 +
 examples/Test62.java                               |  103 +
 examples/Test63.java                               |  153 +
 examples/Test64.java                               |  125 +
 examples/Test65.java                               |   98 +
 examples/Test66.java                               |  253 +
 examples/Test67.java                               |  171 +
 examples/Test68.java                               |  193 +
 examples/Test69.java                               |  157 +
 examples/Test70.java                               |  124 +
 examples/Test71.java                               |  222 +
 examples/Test72.java                               |   84 +
 examples/Test73.java                               |  181 +
 examples/TestEvents.java                           |  700 +++
 examples/TestIDesk.java                            |  148 +
 examples/TestMsg.java                              |  305 +
 examples/TestPlotDigits.java                       |  122 +
 examples/TestSkeleton.java                         |  264 +
 examples/TestStereo.java                           |  112 +
 examples/UISkeleton.java                           |   96 +
 examples/VerySimple.java                           |   62 +
 examples/VisuTraj.java                             |  153 +
 gnu/regexp/CharIndexed.java                        |   27 +
 gnu/regexp/CharIndexedCharArray.java               |   41 +
 gnu/regexp/CharIndexedInputStream.java             |  113 +
 gnu/regexp/CharIndexedString.java                  |   41 +
 gnu/regexp/CharIndexedStringBuffer.java            |   41 +
 gnu/regexp/Makefile.dist                           |   69 +
 gnu/regexp/RE.java                                 | 1128 ++++
 gnu/regexp/REException.java                        |  160 +
 gnu/regexp/REFilterInputStream.java                |  117 +
 gnu/regexp/REMatch.java                            |  170 +
 gnu/regexp/REMatchEnumeration.java                 |  107 +
 gnu/regexp/RESyntax.java                           |  406 ++
 gnu/regexp/REToken.java                            |   74 +
 gnu/regexp/RETokenAny.java                         |   53 +
 gnu/regexp/RETokenBackRef.java                     |   51 +
 gnu/regexp/RETokenChar.java                        |   68 +
 gnu/regexp/RETokenEnd.java                         |   44 +
 gnu/regexp/RETokenOneOf.java                       |  135 +
 gnu/regexp/RETokenPOSIX.java                       |  120 +
 gnu/regexp/RETokenRange.java                       |   48 +
 gnu/regexp/RETokenRepeated.java                    |  118 +
 gnu/regexp/RETokenStart.java                       |   50 +
 loci/formats/AggregateMetadataStore.java           |  430 ++
 loci/formats/AxisGuesser.java                      |  385 ++
 loci/formats/ChannelFiller.java                    |  142 +
 loci/formats/ChannelMerger.java                    |  148 +
 loci/formats/ChannelSeparator.java                 |  180 +
 loci/formats/ClassList.java                        |  143 +
 loci/formats/CoreMetadata.java                     |   72 +
 loci/formats/DataTools.java                        |  550 ++
 loci/formats/DimensionSwapper.java                 |  154 +
 loci/formats/DummyMetadata.java                    |  344 ++
 loci/formats/FilePattern.java                      |  717 +++
 loci/formats/FileStitcher.java                     | 1286 ++++
 loci/formats/FormatException.java                  |   43 +
 loci/formats/FormatHandler.java                    |  189 +
 loci/formats/FormatReader.java                     |  837 +++
 loci/formats/FormatTools.java                      |  466 ++
 loci/formats/FormatWriter.java                     |  218 +
 loci/formats/IFormatHandler.java                   |   66 +
 loci/formats/IFormatReader.java                    |  447 ++
 loci/formats/IFormatWriter.java                    |  127 +
 loci/formats/IRandomAccess.java                    |   73 +
 loci/formats/ImageReader.java                      |  811 +++
 loci/formats/ImageTools.java                       | 1894 ++++++
 loci/formats/ImageWriter.java                      |  384 ++
 loci/formats/IndexedColorModel.java                |  206 +
 loci/formats/LegacyQTTools.java                    |  269 +
 loci/formats/Location.java                         |  316 +
 loci/formats/Log.java                              |   81 +
 loci/formats/LogTools.java                         |   72 +
 loci/formats/MetadataRetrieve.java                 |  946 +++
 loci/formats/MetadataStore.java                    |  327 +
 loci/formats/MetadataTools.java                    |  337 ++
 loci/formats/MinMaxCalculator.java                 |  425 ++
 loci/formats/NumberFilter.java                     |   82 +
 loci/formats/RABytes.java                          |  326 +
 loci/formats/RAFile.java                           |  262 +
 loci/formats/RAUrl.java                            |  343 ++
 loci/formats/RandomAccessStream.java               |  732 +++
 loci/formats/ReaderWrapper.java                    |  570 ++
 loci/formats/ReflectException.java                 |   43 +
 loci/formats/ReflectedUniverse.java                |  454 ++
 loci/formats/StatusEvent.java                      |   72 +
 loci/formats/StatusListener.java                   |   39 +
 loci/formats/StatusReporter.java                   |   45 +
 loci/formats/TiffIFDEntry.java                     |   86 +
 loci/formats/TiffRational.java                     |  121 +
 loci/formats/TiffTools.java                        | 2552 ++++++++
 loci/formats/TwoChannelColorSpace.java             |   85 +
 loci/formats/UnknownTagException.java              |   36 +
 loci/formats/XMLTools.java                         |  248 +
 loci/formats/codec/AdobeDeflateCodec.java          |   94 +
 loci/formats/codec/BZip2Constants.java             |  125 +
 loci/formats/codec/Base64Codec.java                |  253 +
 loci/formats/codec/BaseCodec.java                  |  186 +
 loci/formats/codec/BitBuffer.java                  |  220 +
 loci/formats/codec/BitWriter.java                  |  178 +
 loci/formats/codec/ByteVector.java                 |   92 +
 loci/formats/codec/CBZip2InputStream.java          |  951 +++
 loci/formats/codec/CRC.java                        |  165 +
 loci/formats/codec/Codec.java                      |  122 +
 loci/formats/codec/JPEGCodec.java                  |   95 +
 loci/formats/codec/LZOCodec.java                   |  219 +
 loci/formats/codec/LZWCodec.java                   |  186 +
 loci/formats/codec/LZWTreeNode.java                |  111 +
 loci/formats/codec/LuraWaveCodec.java              |  137 +
 loci/formats/codec/MJPBCodec.java                  |  350 ++
 loci/formats/codec/MSRLECodec.java                 |  122 +
 loci/formats/codec/MSVideoCodec.java               |  251 +
 loci/formats/codec/NikonCodec.java                 |  119 +
 loci/formats/codec/PackbitsCodec.java              |   88 +
 loci/formats/codec/QTRLECodec.java                 |  170 +
 loci/formats/codec/RPZACodec.java                  |  215 +
 loci/formats/gui/ComboFileFilter.java              |  148 +
 loci/formats/gui/ExtensionFileFilter.java          |  117 +
 loci/formats/gui/FormatFileFilter.java             |  109 +
 loci/formats/gui/GUITools.java                     |  175 +
 loci/formats/gui/ImageViewer.java                  |  544 ++
 loci/formats/gui/PreviewPane.java                  |  225 +
 loci/formats/in/AVIReader.java                     |  508 ++
 loci/formats/in/AliconaReader.java                 |  176 +
 loci/formats/in/BMPReader.java                     |  258 +
 loci/formats/in/BZip2Constants.java                |  122 +
 loci/formats/in/BaseTiffReader.java                |  845 +++
 loci/formats/in/BioRadReader.java                  |  782 +++
 loci/formats/in/CBZip2InputStream.java             |  858 +++
 loci/formats/in/CRC.java                           |  162 +
 loci/formats/in/DeltavisionReader.java             |  610 ++
 loci/formats/in/DicomReader.java                   | 1191 ++++
 loci/formats/in/EPSReader.java                     |  301 +
 loci/formats/in/FitsReader.java                    |  146 +
 loci/formats/in/FlexReader.java                    |  214 +
 loci/formats/in/FluoviewReader.java                |  386 ++
 loci/formats/in/GIFReader.java                     |  527 ++
 loci/formats/in/GatanReader.java                   |  377 ++
 loci/formats/in/GelReader.java                     |  109 +
 loci/formats/in/ICSReader.java                     |  462 ++
 loci/formats/in/IPLabReader.java                   |  343 ++
 loci/formats/in/IPWReader.java                     |  451 ++
 loci/formats/in/ImageIOReader.java                 |  140 +
 loci/formats/in/ImarisReader.java                  |  191 +
 loci/formats/in/ImarisTiffReader.java              |  206 +
 loci/formats/in/ImprovisionTiffReader.java         |  205 +
 loci/formats/in/JPEGReader.java                    |   46 +
 loci/formats/in/KhorosReader.java                  |  185 +
 loci/formats/in/LIFReader.java                     |  687 +++
 loci/formats/in/LIMReader.java                     |  143 +
 loci/formats/in/LegacyPictReader.java              |  123 +
 loci/formats/in/LegacyQTReader.java                |  239 +
 loci/formats/in/LegacyZVIReader.java               |  575 ++
 loci/formats/in/LeicaReader.java                   | 1069 ++++
 loci/formats/in/MDBParser.java                     |  185 +
 loci/formats/in/MNGReader.java                     |  191 +
 loci/formats/in/MRCReader.java                     |  230 +
 loci/formats/in/MetamorphReader.java               | 1035 ++++
 loci/formats/in/MicromanagerReader.java            |  242 +
 loci/formats/in/ND2Reader.java                     |  831 +++
 loci/formats/in/NRRDReader.java                    |  219 +
 loci/formats/in/NikonReader.java                   |  403 ++
 loci/formats/in/OIBReader.java                     |  614 ++
 loci/formats/in/OIFReader.java                     |  429 ++
 loci/formats/in/OMETiffReader.java                 |  472 ++
 loci/formats/in/OMEXMLReader.java                  |  493 ++
 loci/formats/in/OpenlabRawReader.java              |  186 +
 loci/formats/in/OpenlabReader.java                 |  731 +++
 loci/formats/in/PCIReader.java                     |  291 +
 loci/formats/in/PGMReader.java                     |  150 +
 loci/formats/in/PNGReader.java                     |   45 +
 loci/formats/in/PSDReader.java                     |  260 +
 loci/formats/in/PerkinElmerReader.java             |  634 ++
 loci/formats/in/PictReader.java                    |  841 +++
 loci/formats/in/PrairieReader.java                 |  422 ++
 loci/formats/in/QTReader.java                      |  742 +++
 loci/formats/in/SDTInfo.java                       |  815 +++
 loci/formats/in/SDTReader.java                     |  227 +
 loci/formats/in/SEQReader.java                     |  146 +
 loci/formats/in/SlidebookReader.java               |  309 +
 loci/formats/in/TCSReader.java                     |  155 +
 loci/formats/in/TiffReader.java                    |  106 +
 loci/formats/in/VisitechReader.java                |  208 +
 loci/formats/in/ZeissLSMReader.java                |  697 +++
 loci/formats/in/ZeissZVIReader.java                | 1334 +++++
 loci/formats/out/AVIWriter.java                    |  539 ++
 loci/formats/out/EPSWriter.java                    |  162 +
 loci/formats/out/ImageIOWriter.java                |  103 +
 loci/formats/out/JPEGWriter.java                   |   68 +
 loci/formats/out/LegacyQTWriter.java               |  294 +
 loci/formats/out/OMETiffWriter.java                |   76 +
 loci/formats/out/PNGWriter.java                    |   42 +
 loci/formats/out/QTWriter.java                     |  618 ++
 loci/formats/out/TiffWriter.java                   |  171 +
 loci/formats/package.html                          |    4 +
 loci/formats/readers.txt                           |   94 +
 loci/formats/writers.txt                           |   33 +
 ncsa/hdf/hdf5lib/H5.java                           | 3795 ++++++++++++
 ncsa/hdf/hdf5lib/HDF5CDataTypes.java               |  161 +
 ncsa/hdf/hdf5lib/HDF5Constants.java                |  256 +
 ncsa/hdf/hdf5lib/HDF5GroupInfo.java                |  107 +
 ncsa/hdf/hdf5lib/HDFArray.java                     |  865 +++
 ncsa/hdf/hdf5lib/HDFNativeData.java                |  393 ++
 ncsa/hdf/hdf5lib/exceptions/HDF5AtomException.java |   42 +
 .../hdf5lib/exceptions/HDF5AttributeException.java |   40 +
 .../hdf/hdf5lib/exceptions/HDF5BtreeException.java |   41 +
 .../exceptions/HDF5DataFiltersException.java       |   42 +
 .../exceptions/HDF5DataStorageException.java       |   42 +
 .../exceptions/HDF5DatasetInterfaceException.java  |   42 +
 .../HDF5DataspaceInterfaceException.java           |   42 +
 .../exceptions/HDF5DatatypeInterfaceException.java |   42 +
 ncsa/hdf/hdf5lib/exceptions/HDF5Exception.java     |   68 +
 .../exceptions/HDF5ExternalFileListException.java  |   42 +
 .../exceptions/HDF5FileInterfaceException.java     |   42 +
 .../exceptions/HDF5FunctionArgumentException.java  |   41 +
 .../exceptions/HDF5FunctionEntryExitException.java |   42 +
 ncsa/hdf/hdf5lib/exceptions/HDF5HeapException.java |   42 +
 .../exceptions/HDF5InternalErrorException.java     |   42 +
 ncsa/hdf/hdf5lib/exceptions/HDF5JavaException.java |   41 +
 .../hdf5lib/exceptions/HDF5LibraryException.java   |  233 +
 .../exceptions/HDF5LowLevelIOException.java        |   42 +
 .../exceptions/HDF5MetaDataCacheException.java     |   42 +
 .../exceptions/HDF5ObjectHeaderException.java      |   42 +
 .../HDF5PropertyListInterfaceException.java        |   41 +
 .../hdf5lib/exceptions/HDF5ReferenceException.java |   34 +
 .../HDF5ResourceUnavailableException.java          |   41 +
 .../exceptions/HDF5SymbolTableException.java       |   42 +
 nom/tam/fits/A3DTableHDU.java                      |   51 +
 nom/tam/fits/AsciiTableHDU.java                    |   85 +
 nom/tam/fits/BadHeaderException.java               |   22 +
 nom/tam/fits/BasicHDU.java                         |  397 ++
 nom/tam/fits/BinaryTable.java                      |  728 +++
 nom/tam/fits/BinaryTableHDU.java                   |  520 ++
 nom/tam/fits/BinaryTableHeaderParser.java          |  455 ++
 nom/tam/fits/Column.java                           |   89 +
 nom/tam/fits/Data.java                             |  135 +
 nom/tam/fits/ExtensionHDU.java                     |   57 +
 nom/tam/fits/Fits.java                             |  650 ++
 nom/tam/fits/FitsDate.java                         |  300 +
 nom/tam/fits/FitsException.java                    |   24 +
 nom/tam/fits/HDU.java                              |  153 +
 nom/tam/fits/Header.java                           | 1455 +++++
 nom/tam/fits/HeaderCard.java                       |  280 +
 nom/tam/fits/HeaderCardException.java              |   21 +
 nom/tam/fits/ImageData.java                        |  101 +
 nom/tam/fits/ImageHDU.java                         |  127 +
 nom/tam/fits/PrimaryHDU.java                       |  137 +
 nom/tam/fits/RandomGroupsData.java                 |  145 +
 nom/tam/fits/RandomGroupsHDU.java                  |  159 +
 nom/tam/fits/TableHDU.java                         |   65 +
 nom/tam/fits/TruncatedFileException.java           |   21 +
 nom/tam/fits/package.html                          |   11 +
 nom/tam/test/FitsTester.java                       |  523 ++
 nom/tam/test/package.html                          |   10 +
 nom/tam/util/ArrayFuncs.java                       | 1050 ++++
 nom/tam/util/BufferedDataInputStream.java          |  732 +++
 nom/tam/util/BufferedDataOutputStream.java         |  393 ++
 nom/tam/util/ColumnTable.java                      |  793 +++
 nom/tam/util/DataTable.java                        |   32 +
 nom/tam/util/TableException.java                   |   19 +
 nom/tam/util/package.html                          |   10 +
 ucar/multiarray/AbstractAccessor.java              |  357 ++
 ucar/multiarray/Accessor.java                      |  340 ++
 ucar/multiarray/ArrayMultiArray.java               |  747 +++
 ucar/multiarray/ClipMap.java                       |  151 +
 ucar/multiarray/ConcreteIndexMap.java              |  461 ++
 ucar/multiarray/DecimateMap.java                   |  190 +
 ucar/multiarray/FlattenMap.java                    |  184 +
 ucar/multiarray/FlipMap.java                       |  119 +
 ucar/multiarray/IndexIterator.java                 |  234 +
 ucar/multiarray/IndexMap.java                      |  137 +
 ucar/multiarray/IntArrayAdapter.java               |   89 +
 ucar/multiarray/IntMap.java                        |   74 +
 ucar/multiarray/MultiArray.java                    |   58 +
 ucar/multiarray/MultiArrayImpl.java                |  701 +++
 ucar/multiarray/MultiArrayInfo.java                |   72 +
 ucar/multiarray/MultiArrayProxy.java               |  433 ++
 ucar/multiarray/OffsetIndexIterator.java           |   78 +
 ucar/multiarray/RemoteAccessor.java                |  138 +
 ucar/multiarray/ScalarMultiArray.java              |  515 ++
 ucar/multiarray/SliceMap.java                      |  154 +
 ucar/multiarray/StringCharAdapter.java             |  446 ++
 ucar/multiarray/TransposeMap.java                  |  147 +
 ucar/multiarray/overview.html                      |  103 +
 ucar/multiarray/package.html                       |  121 +
 ucar/netcdf/AbstractNetcdf.java                    |  425 ++
 ucar/netcdf/Attribute.java                         |  563 ++
 ucar/netcdf/AttributeDictionary.java               |  202 +
 ucar/netcdf/AttributeIterator.java                 |   41 +
 ucar/netcdf/AttributeSet.java                      |  126 +
 ucar/netcdf/Dimension.java                         |  154 +
 ucar/netcdf/DimensionDictionary.java               |  243 +
 ucar/netcdf/DimensionIterator.java                 |   41 +
 ucar/netcdf/DimensionSet.java                      |   77 +
 ucar/netcdf/HTTPRandomAccessFile.java              |  127 +
 ucar/netcdf/Named.java                             |   41 +
 ucar/netcdf/NamedDictionary.java                   |  221 +
 ucar/netcdf/Netcdf.java                            |  108 +
 ucar/netcdf/NetcdfFile.java                        | 1896 ++++++
 ucar/netcdf/NetcdfRemoteProxy.java                 |   72 +
 ucar/netcdf/NetcdfRemoteProxyImpl.java             |  141 +
 ucar/netcdf/NetcdfServer.java                      |  378 ++
 ucar/netcdf/NetcdfService.java                     |   73 +
 ucar/netcdf/NetcdfWrapper.java                     |  143 +
 ucar/netcdf/ProtoVariable.java                     |  560 ++
 ucar/netcdf/ProtoVariableIterator.java             |   41 +
 ucar/netcdf/RandomAccessFile.java                  | 1631 +++++
 ucar/netcdf/RemoteAccessorImpl.java                |  360 ++
 ucar/netcdf/RemoteNetcdf.java                      |  187 +
 ucar/netcdf/Schema.java                            |  382 ++
 ucar/netcdf/UnlimitedDimension.java                |  138 +
 ucar/netcdf/Variable.java                          |  356 ++
 ucar/netcdf/VariableIterator.java                  |   41 +
 ucar/netcdf/overview.html                          |  101 +
 ucar/netcdf/package.html                           |  100 +
 ucar/tests/TestNetcdf.java                         |  218 +
 ucar/tests/clone.nc                                |  Bin 0 -> 3880 bytes
 ucar/tests/t.nc                                    |  Bin 0 -> 972812 bytes
 ucar/tests/test.nc                                 |  Bin 0 -> 8192 bytes
 ucar/tests/test.out                                |  119 +
 ucar/util/AbstractLogger.java                      |  114 +
 ucar/util/Logger.java                              |  151 +
 ucar/util/RMILogger.java                           |  119 +
 visad/AVControl.java                               |   68 +
 visad/Action.java                                  |   86 +
 visad/ActionImpl.java                              |  801 +++
 visad/ActivityHandler.java                         |   64 +
 visad/AnimationControl.java                        |  220 +
 visad/AnimationSetControl.java                     |  241 +
 visad/AxisScale.java                               | 1756 ++++++
 visad/BadDirectManipulationException.java          |   47 +
 visad/BadMappingException.java                     |   47 +
 visad/BaseColorControl.java                        |  941 +++
 visad/BaseQuantity.java                            |  302 +
 visad/BaseUnit.java                                |  924 +++
 visad/CMYCoordinateSystem.java                     |  175 +
 visad/CachingCoordinateSystem.java                 |  238 +
 visad/CartesianProductCoordinateSystem.java        |  325 +
 visad/Cell.java                                    |   63 +
 visad/CellImpl.java                                |  146 +
 visad/ColorAlphaControl.java                       |   43 +
 visad/ColorControl.java                            |   43 +
 visad/CommonUnit.java                              |  103 +
 visad/ConstantMap.java                             |  213 +
 visad/Contour2D.java                               | 6062 +++++++++++++++++++
 visad/ContourControl.java                          | 1239 ++++
 visad/ContourLabelGeometry.java                    |  102 +
 visad/Control.java                                 |  392 ++
 visad/ControlEvent.java                            |   67 +
 visad/ControlListener.java                         |   43 +
 visad/CoordinateSystem.java                        |  961 +++
 visad/CoordinateSystemException.java               |   59 +
 visad/CylindricalCoordinateSystem.java             |  243 +
 visad/DEDICATION                                   |    1 +
 visad/Data.java                                    | 1080 ++++
 visad/DataDisplayLink.java                         |  470 ++
 visad/DataImpl.java                                | 1645 +++++
 visad/DataReference.java                           |   82 +
 visad/DataReferenceImpl.java                       |  114 +
 visad/DataRenderer.java                            | 2705 +++++++++
 visad/DataShadow.java                              |  106 +
 visad/DataSourceListener.java                      |   31 +
 visad/DateTime.java                                |  688 +++
 visad/Delaunay.java                                |  989 +++
 visad/Delaunay2D.txt                               |    8 +
 visad/Delaunay3D.txt                               |    7 +
 visad/DelaunayClarkson.java                        | 1260 ++++
 visad/DelaunayCustom.java                          | 1148 ++++
 visad/DelaunayFast.java                            |  670 +++
 visad/DelaunayOverlap.java                         | 2199 +++++++
 visad/DelaunayWatson.java                          |  367 ++
 visad/DerivedUnit.java                             | 1249 ++++
 visad/Display.java                                 |  591 ++
 visad/DisplayActivity.java                         |  245 +
 visad/DisplayEvent.java                            |  508 ++
 visad/DisplayException.java                        |   46 +
 visad/DisplayImpl.java                             | 3308 +++++++++++
 visad/DisplayInterruptException.java               |   47 +
 visad/DisplayListener.java                         |   48 +
 visad/DisplayMapEvent.java                         |   81 +
 visad/DisplayRealType.java                         |  274 +
 visad/DisplayReferenceEvent.java                   |   69 +
 visad/DisplayRenderer.java                         |  935 +++
 visad/DisplayTupleType.java                        |  198 +
 visad/DomainException.java                         |   47 +
 visad/DoubleSet.java                               |  163 +
 visad/DoubleStringTuple.java                       |  299 +
 visad/DoubleTuple.java                             |  356 ++
 visad/EarthVectorType.java                         |  144 +
 visad/EmpiricalCoordinateSystem.java               |  432 ++
 visad/ErrorEstimate.java                           |  679 +++
 visad/Factor.java                                  |  115 +
 visad/Field.java                                   |  185 +
 visad/FieldException.java                          |   38 +
 visad/FieldImpl.java                               | 3853 ++++++++++++
 visad/FlatField.java                               | 6265 ++++++++++++++++++++
 visad/FlatFieldIface.java                          |  163 +
 visad/FloatSet.java                                |  104 +
 visad/Flow1Control.java                            |   39 +
 visad/Flow2Control.java                            |   39 +
 visad/FlowControl.java                             |  708 +++
 visad/FlowSphericalCoordinateSystem.java           |  154 +
 visad/Function.java                                |  197 +
 visad/FunctionImpl.java                            |  245 +
 visad/FunctionType.java                            |  332 ++
 visad/GraphicsModeControl.java                     |  587 ++
 visad/GridCoordinateSystem.java                    |  146 +
 visad/GridVectorType.java                          |  136 +
 visad/Gridded1D.txt                                |   22 +
 visad/Gridded1DDoubleSet.java                      |  761 +++
 visad/Gridded1DSet.java                            |  581 ++
 visad/Gridded1DSetIface.java                       |   34 +
 visad/Gridded2D.txt                                |   22 +
 visad/Gridded2DDoubleSet.java                      |  936 +++
 visad/Gridded2DSet.java                            |  757 +++
 visad/Gridded3D.txt                                |   29 +
 visad/Gridded3DDoubleSet.java                      | 2048 +++++++
 visad/Gridded3DSet.java                            | 5657 ++++++++++++++++++
 visad/GriddedDoubleSet.java                        |   64 +
 visad/GriddedSet.java                              | 1043 ++++
 visad/GriddedSetIface.java                         |   82 +
 visad/HSVCoordinateSystem.java                     |  256 +
 visad/IdentityCoordinateSystem.java                |  129 +
 visad/ImageFlatField.java                          |  541 ++
 visad/Integer1DSet.java                            |   63 +
 visad/Integer2DSet.java                            |  112 +
 visad/Integer3DSet.java                            |  120 +
 visad/IntegerNDSet.java                            |  157 +
 visad/IntegerSet.java                              |   36 +
 visad/InverseCoordinateSystem.java                 |  101 +
 visad/InverseLinearScaledCS.java                   |   54 +
 visad/Irregular1DSet.java                          |  252 +
 visad/Irregular2DSet.java                          |  543 ++
 visad/Irregular3DSet.java                          | 3021 ++++++++++
 visad/IrregularSet.java                            |  286 +
 visad/KeyboardBehavior.java                        |   88 +
 visad/LICENSE                                      |   30 +
 visad/Linear1DSet.java                             |  628 ++
 visad/Linear2DSet.java                             |  492 ++
 visad/Linear3DSet.java                             | 1899 ++++++
 visad/LinearLatLonSet.java                         |  536 ++
 visad/LinearNDSet.java                             |  575 ++
 visad/LinearSet.java                               |   49 +
 visad/List1DDoubleSet.java                         |  180 +
 visad/List1DSet.java                               |  134 +
 visad/LocalDisplay.java                            |  148 +
 visad/LogCoordinateSystem.java                     |  148 +
 visad/LogarithmicUnit.java                         |  651 ++
 visad/Makefile.WinNT                               | 1065 ++++
 visad/MathType.java                                | 1450 +++++
 visad/MessageEvent.java                            |  194 +
 visad/MessageListener.java                         |   40 +
 visad/MouseBehavior.java                           |  144 +
 visad/MouseHelper.java                             |  884 +++
 visad/NOTICE                                       |  482 ++
 visad/OffsetUnit.java                              |  717 +++
 visad/PlotDigits.java                              |  380 ++
 visad/PlotText.java                                | 1404 +++++
 visad/PolarCoordinateSystem.java                   |  134 +
 visad/ProductSet.java                              |  639 ++
 visad/ProjectionControl.java                       |  507 ++
 visad/PromiscuousUnit.java                         |  203 +
 visad/QuantityDimension.java                       |  280 +
 visad/QuickSort.java                               |  237 +
 visad/README                                       |  399 ++
 visad/README.nexusrmi                              |   57 +
 visad/RangeControl.java                            |  185 +
 visad/Real.java                                    | 1172 ++++
 visad/RealIface.java                               |  150 +
 visad/RealTuple.java                               |  485 ++
 visad/RealTupleIface.java                          |   82 +
 visad/RealTupleType.java                           |  505 ++
 visad/RealType.java                                | 1213 ++++
 visad/RealVectorType.java                          |  165 +
 visad/ReferenceActionLink.java                     |  203 +
 visad/ReferenceException.java                      |   38 +
 visad/RemoteAction.java                            |   37 +
 visad/RemoteActionImpl.java                        |  109 +
 visad/RemoteCell.java                              |   37 +
 visad/RemoteCellImpl.java                          |   85 +
 visad/RemoteData.java                              |   37 +
 visad/RemoteDataImpl.java                          | 1242 ++++
 visad/RemoteDataReference.java                     |   38 +
 visad/RemoteDataReferenceImpl.java                 |  119 +
 visad/RemoteDisplay.java                           |   64 +
 visad/RemoteDisplayImpl.java                       |  419 ++
 visad/RemoteField.java                             |   37 +
 visad/RemoteFieldImpl.java                         |  289 +
 visad/RemoteFlatField.java                         |   37 +
 visad/RemoteFlatFieldImpl.java                     |  220 +
 visad/RemoteFunction.java                          |   37 +
 visad/RemoteFunctionImpl.java                      |  170 +
 visad/RemoteReferenceLink.java                     |   48 +
 visad/RemoteReferenceLinkImpl.java                 |  154 +
 visad/RemoteServer.java                            |   89 +
 visad/RemoteServerImpl.java                        |  279 +
 visad/RemoteSlaveDisplay.java                      |   44 +
 visad/RemoteSlaveDisplayImpl.java                  |  266 +
 visad/RemoteSourceListener.java                    |   42 +
 visad/RemoteThing.java                             |   44 +
 visad/RemoteThingImpl.java                         |  105 +
 visad/RemoteThingReference.java                    |   37 +
 visad/RemoteThingReferenceImpl.java                |  161 +
 visad/RemoteTupleIface.java                        |   37 +
 visad/RemoteVisADException.java                    |   39 +
 visad/RendererControl.java                         |  454 ++
 visad/RendererSourceListener.java                  |   29 +
 visad/SI.java                                      |  229 +
 visad/SampledSet.java                              |  709 +++
 visad/SampledSetIface.java                         |   50 +
 visad/Scalar.java                                  |   86 +
 visad/ScalarIface.java                             |   43 +
 visad/ScalarMap.java                               | 1425 +++++
 visad/ScalarMapControlEvent.java                   |   75 +
 visad/ScalarMapEvent.java                          |  169 +
 visad/ScalarMapListener.java                       |   61 +
 visad/ScalarType.java                              |  338 ++
 visad/ScaledUnit.java                              |  727 +++
 visad/Set.java                                     |  895 +++
 visad/Set1DIface.java                              |   47 +
 visad/SetException.java                            |   38 +
 visad/SetIface.java                                |  229 +
 visad/SetType.java                                 |  125 +
 visad/ShadowFunctionOrSetType.java                 | 4064 +++++++++++++
 visad/ShadowFunctionType.java                      |   44 +
 visad/ShadowRealTupleType.java                     |  192 +
 visad/ShadowRealType.java                          |  147 +
 visad/ShadowScalarType.java                        |  234 +
 visad/ShadowSetType.java                           |   44 +
 visad/ShadowTextType.java                          |  129 +
 visad/ShadowTupleType.java                         |  511 ++
 visad/ShadowType.java                              | 4195 +++++++++++++
 visad/ShapeControl.java                            |  373 ++
 visad/SimpleSet.java                               |  126 +
 visad/SimpleSetIface.java                          |   55 +
 visad/SingletonSet.java                            |  368 ++
 visad/SocketSlaveDisplay.java                      |  478 ++
 visad/SphericalCoordinateSystem.java               |  181 +
 visad/Stream2D.java                                |  569 ++
 visad/Text.java                                    |  131 +
 visad/TextControl.java                             |  656 ++
 visad/TextType.java                                |  141 +
 visad/Thing.java                                   |   45 +
 visad/ThingChangedEvent.java                       |   62 +
 visad/ThingChangedLink.java                        |  138 +
 visad/ThingChangedListener.java                    |   43 +
 visad/ThingImpl.java                               |  220 +
 visad/ThingReference.java                          |   71 +
 visad/ThingReferenceImpl.java                      |  314 +
 visad/ToggleControl.java                           |  146 +
 visad/TriangleStripBuilder.java                    |  550 ++
 visad/Tuple.java                                   |  561 ++
 visad/TupleIface.java                              |   66 +
 visad/TupleType.java                               |  389 ++
 visad/TypeException.java                           |   38 +
 visad/UnimplementedException.java                  |   38 +
 visad/UnionSet.java                                |  958 +++
 visad/Unit.java                                    | 1138 ++++
 visad/UnitException.java                           |   54 +
 visad/UnitExistsException.java                     |   48 +
 visad/ValueControl.java                            |   47 +
 visad/VisAD-Style.xjs                              | 4869 +++++++++++++++
 visad/VisAD.Manifest                               |    2 +
 visad/VisADAppearance.java                         |   58 +
 visad/VisADError.java                              |   41 +
 visad/VisADEvent.java                              |   65 +
 visad/VisADException.java                          |   41 +
 visad/VisADGeometryArray.java                      |  574 ++
 visad/VisADGroup.java                              |  121 +
 visad/VisADIndexedTriangleStripArray.java          |  733 +++
 visad/VisADLineArray.java                          |  396 ++
 visad/VisADLineStripArray.java                     |  566 ++
 visad/VisADPointArray.java                         |   55 +
 visad/VisADQuadArray.java                          |   42 +
 visad/VisADRay.java                                |   42 +
 visad/VisADSceneGraphObject.java                   |   49 +
 visad/VisADSwitch.java                             |   73 +
 visad/VisADTriangleArray.java                      |   59 +
 visad/VisADTriangleStripArray.java                 | 1415 +++++
 visad/aeri/Aeri.java                               | 1209 ++++
 visad/aeri/LinearVectorPointMethod.java            |  265 +
 visad/aeri/Qdiv.java                               | 1361 +++++
 visad/bom/BarbManipulationRendererJ2D.java         |  811 +++
 visad/bom/BarbManipulationRendererJ3D.java         | 1197 ++++
 visad/bom/BarbRenderer.java                        |   45 +
 visad/bom/BarbRendererJ2D.java                     |  232 +
 visad/bom/BarbRendererJ3D.java                     |  233 +
 visad/bom/CBMKeyboardBehaviorJ3D.java              |  108 +
 visad/bom/CollectiveBarbException.java             |   40 +
 visad/bom/CollectiveBarbManipulation.java          | 1843 ++++++
 visad/bom/CurveDelete.java                         |  118 +
 visad/bom/CurveManipulationRendererJ2D.java        |  896 +++
 visad/bom/CurveManipulationRendererJ3D.java        |  944 +++
 visad/bom/CutAndPasteFields.java                   |  886 +++
 visad/bom/DiscoverableZoom.java                    |  198 +
 visad/bom/FlexibleTrackManipulation.java           | 1047 ++++
 visad/bom/FrontDrawer.java                         | 1608 +++++
 visad/bom/GridEdit.java                            | 1022 ++++
 visad/bom/ImageRendererJ3D.java                    |  855 +++
 visad/bom/PickManipulationRendererJ2D.java         |  423 ++
 visad/bom/PickManipulationRendererJ3D.java         |  459 ++
 visad/bom/PointManipulationRendererJ3D.java        |  490 ++
 visad/bom/Radar2DCoordinateSystem.java             |  345 ++
 visad/bom/Radar3DCoordinateSystem.java             |  569 ++
 visad/bom/RadarAdapter.java                        |  329 +
 visad/bom/RadarDisplay.java                        |  275 +
 visad/bom/RadarFile.java                           |  403 ++
 visad/bom/RadarFileException.java                  |   37 +
 visad/bom/RubberBandBoxRendererJ3D.java            |  620 ++
 visad/bom/RubberBandLineRendererJ3D.java           |  636 ++
 visad/bom/SceneGraphRenderer.java                  | 3257 ++++++++++
 visad/bom/ScreenLockedDemo.java                    |  333 ++
 visad/bom/ScreenLockedRendererJ3D.java             |  225 +
 visad/bom/ShadowBarbFunctionTypeJ2D.java           |   57 +
 visad/bom/ShadowBarbFunctionTypeJ3D.java           |   57 +
 visad/bom/ShadowBarbRealTupleTypeJ2D.java          |  631 ++
 visad/bom/ShadowBarbRealTupleTypeJ3D.java          |  350 ++
 visad/bom/ShadowBarbRealTypeJ2D.java               |   57 +
 visad/bom/ShadowBarbRealTypeJ3D.java               |   57 +
 visad/bom/ShadowBarbSetTypeJ2D.java                |   57 +
 visad/bom/ShadowBarbSetTypeJ3D.java                |   57 +
 visad/bom/ShadowBarbTupleTypeJ2D.java              |   66 +
 visad/bom/ShadowBarbTupleTypeJ3D.java              |   66 +
 visad/bom/ShadowCurveSetTypeJ2D.java               |   94 +
 visad/bom/ShadowCurveSetTypeJ3D.java               |   94 +
 visad/bom/ShadowImageByRefFunctionTypeJ3D.java     | 2373 ++++++++
 visad/bom/ShadowImageFunctionTypeJ3D.java          | 1703 ++++++
 visad/bom/ShadowTextureFillSetTypeJ3D.java         |  419 ++
 visad/bom/SwellManipulationRendererJ3D.java        |  887 +++
 visad/bom/SwellRendererJ3D.java                    |  232 +
 visad/bom/Swells.java                              |  191 +
 visad/bom/TCData.java                              |  718 +++
 visad/bom/TCDataTest.java                          |  270 +
 visad/bom/TextureFillRendererJ3D.java              |  390 ++
 visad/bom/TrackManipulation.java                   |  450 ++
 visad/bom/WindPolarCoordinateSystem.java           |  146 +
 visad/bom/annotations/ImageJ3D.java                |  207 +
 visad/bom/annotations/JLabelJ3D.java               |  201 +
 visad/bom/annotations/LabelJ3D.java                |  402 ++
 visad/bom/annotations/LineJ3D.java                 |  217 +
 visad/bom/annotations/PointJ3D.java                |  147 +
 visad/bom/annotations/QuadrilateralJ3D.java        |  309 +
 visad/bom/annotations/ScreenAnnotation.java        |   50 +
 visad/bom/annotations/ScreenAnnotator.java         |  107 +
 visad/bom/annotations/ScreenAnnotatorJ3D.java      |  121 +
 visad/bom/annotations/ScreenAnnotatorUtils.java    |  875 +++
 visad/bom/annotations/TriangleJ3D.java             |  200 +
 visad/bom/radar.dat                                |  197 +
 visad/browser/ContourWidget.java                   |  377 ++
 visad/browser/Convert.java                         |  324 +
 visad/browser/Divider.java                         |  134 +
 visad/browser/GMCWidget.java                       |  337 ++
 visad/browser/README.browser                       |   40 +
 visad/browser/RangeSlider.java                     |  643 ++
 visad/browser/Slider.java                          |  444 ++
 visad/browser/VisADApplet.java                     |  674 +++
 visad/browser/Widget.java                          |  151 +
 visad/browser/WidgetEvent.java                     |   63 +
 visad/browser/WidgetListener.java                  |   41 +
 visad/browser/package.html                         |   13 +
 visad/browser/socket_applet.html                   |   13 +
 visad/browser/viewer_applet.html                   |   13 +
 visad/cluster/ClientDisplayRendererJ3D.java        |   61 +
 visad/cluster/ClientRendererJ3D.java               |  577 ++
 visad/cluster/ClusterException.java                |   40 +
 visad/cluster/DefaultNodeRendererAgent.java        |  241 +
 visad/cluster/NodeAgent.java                       |  132 +
 visad/cluster/NodeDisplayRendererJ3D.java          |   57 +
 visad/cluster/NodeRendererJ3D.java                 |  310 +
 visad/cluster/RemoteAgentContact.java              |   41 +
 visad/cluster/RemoteAgentContactImpl.java          |   51 +
 visad/cluster/RemoteClientAgent.java               |   41 +
 visad/cluster/RemoteClientAgentImpl.java           |  136 +
 visad/cluster/RemoteClientData.java                |   36 +
 visad/cluster/RemoteClientDataImpl.java            |   66 +
 visad/cluster/RemoteClientField.java               |   38 +
 visad/cluster/RemoteClientFieldImpl.java           |  318 +
 visad/cluster/RemoteClientPartitionedField.java    |   39 +
 .../cluster/RemoteClientPartitionedFieldImpl.java  |  311 +
 visad/cluster/RemoteClientTuple.java               |   38 +
 visad/cluster/RemoteClientTupleImpl.java           |  149 +
 visad/cluster/RemoteClusterData.java               |   48 +
 visad/cluster/RemoteClusterDataImpl.java           |  398 ++
 visad/cluster/RemoteNodeData.java                  |   41 +
 visad/cluster/RemoteNodeDataImpl.java              |   64 +
 visad/cluster/RemoteNodeField.java                 |   38 +
 visad/cluster/RemoteNodeFieldImpl.java             |  332 ++
 visad/cluster/RemoteNodePartitionedField.java      |   38 +
 visad/cluster/RemoteNodePartitionedFieldImpl.java  |  339 ++
 visad/cluster/RemoteNodeTuple.java                 |   38 +
 visad/cluster/RemoteNodeTupleImpl.java             |  139 +
 visad/cluster/RemoteProxyAgent.java                |   55 +
 visad/cluster/RemoteProxyAgentImpl.java            |  153 +
 visad/cluster/ShadowNodeFunctionTypeJ3D.java       |  330 ++
 visad/cluster/ShadowNodeRealTupleTypeJ3D.java      |   55 +
 visad/cluster/ShadowNodeRealTypeJ3D.java           |   55 +
 visad/cluster/ShadowNodeSetTypeJ3D.java            |   55 +
 visad/cluster/ShadowNodeTupleTypeJ3D.java          |   55 +
 visad/cluster/TestCluster.java                     |  264 +
 visad/cluster/TestClusterOneJVM.java               |  233 +
 visad/cluster/TestProxyCluster.java                |  728 +++
 visad/cluster/TestROMS.java                        |  288 +
 visad/cluster/TestSSCluster.java                   |  667 +++
 visad/cluster/TestVis5DCluster.java                |  368 ++
 visad/cluster/TestWRFCluster.java                  |  650 ++
 visad/cluster/UserDisplayRendererJ3D.java          |   58 +
 visad/cluster/UserDummyDataImpl.java               |  125 +
 visad/cluster/UserRendererJ3D.java                 |  537 ++
 visad/collab/CollabUtil.java                       |   35 +
 visad/collab/ControlMonitorEvent.java              |  168 +
 visad/collab/DisplayMonitor.java                   |  161 +
 visad/collab/DisplayMonitorImpl.java               |  790 +++
 visad/collab/DisplaySync.java                      |   38 +
 visad/collab/DisplaySyncImpl.java                  |  545 ++
 visad/collab/MapMonitorEvent.java                  |  208 +
 visad/collab/MessageMonitorEvent.java              |  234 +
 visad/collab/MonitorCallback.java                  |   43 +
 visad/collab/MonitorEvent.java                     |  206 +
 visad/collab/MonitorSyncer.java                    |  301 +
 visad/collab/ReferenceMonitorEvent.java            |  159 +
 visad/collab/RemoteDisplayMonitor.java             |   34 +
 visad/collab/RemoteDisplayMonitorImpl.java         |  295 +
 visad/collab/RemoteDisplaySync.java                |   35 +
 visad/collab/RemoteDisplaySyncImpl.java            |   85 +
 visad/collab/RemoteEventProvider.java              |   35 +
 visad/collab/RemoteEventProviderImpl.java          |   52 +
 visad/data/AreaImageAccessor.java                  |  182 +
 visad/data/AreaImageCacheAdapter.java              |  371 ++
 visad/data/ArrayCache.java                         |  430 ++
 visad/data/ArrayWrapper.java                       |  260 +
 visad/data/BadFormException.java                   |   38 +
 visad/data/BadRepositoryException.java             |   43 +
 visad/data/BaseDataProcessor.java                  |  672 +++
 visad/data/CacheStrategy.java                      |   66 +
 visad/data/CachedBufferedByteImage.java            |  206 +
 visad/data/CachedFlatField.java                    |  695 +++
 visad/data/DataCacheManager.java                   | 1587 +++++
 visad/data/DataNode.java                           |  226 +
 visad/data/DataProcessor.java                      |  183 +
 visad/data/DataRange.java                          |  236 +
 visad/data/DataVisitor.java                        |   81 +
 visad/data/DataWriter.java                         |   71 +
 visad/data/DefaultFamily.java                      |  296 +
 visad/data/DefaultFamilyTest.java                  |   67 +
 visad/data/DirectoryRepository.java                |  154 +
 visad/data/EmptyDataProcessor.java                 |  337 ++
 visad/data/EmptyDataWriter.java                    |   40 +
 visad/data/FileAccessor.java                       |   58 +
 visad/data/FileField.java                          |  101 +
 visad/data/FileFlatField.java                      |  622 ++
 visad/data/FlatFieldCache.java                     |  220 +
 visad/data/FlatFieldCacheAccessor.java             |    7 +
 visad/data/FlatFieldCacheError.java                |   10 +
 visad/data/FlatFieldCacheStrategy.java             |   17 +
 visad/data/Form.java                               |   63 +
 visad/data/FormBlockReader.java                    |   58 +
 visad/data/FormFamily.java                         |  181 +
 visad/data/FormFileInformer.java                   |   50 +
 visad/data/FormNode.java                           |  102 +
 visad/data/FormProgressInformer.java               |   42 +
 visad/data/FunctionFormFamily.java                 |  467 ++
 visad/data/LinkedDataSource.java                   |  113 +
 visad/data/MetadataReader.java                     |   56 +
 visad/data/Repository.java                         |  168 +
 visad/data/SocketDataServer.java                   |  192 +
 visad/data/SocketDataSource.java                   |  181 +
 visad/data/amanda/AmandaFile.java                  |  707 +++
 visad/data/amanda/BaseTrack.java                   |  424 ++
 visad/data/amanda/Event.java                       |  198 +
 visad/data/amanda/EventList.java                   |   68 +
 visad/data/amanda/EventWidget.java                 |  208 +
 visad/data/amanda/F2000Form.java                   |  131 +
 visad/data/amanda/F2000Util.java                   |  127 +
 visad/data/amanda/FitTrack.java                    |   42 +
 visad/data/amanda/HistogramWidget.java             |  104 +
 visad/data/amanda/Hit.java                         |  151 +
 visad/data/amanda/Hits.java                        |  253 +
 visad/data/amanda/MCTrack.java                     |   42 +
 visad/data/amanda/Module.java                      |   69 +
 visad/data/amanda/ModuleList.java                  |  165 +
 visad/data/amanda/NuView.java                      |  392 ++
 visad/data/amanda/NuView.py                        |  141 +
 visad/data/amanda/Point.java                       |   41 +
 visad/data/amanda/TrackWidget.java                 |  180 +
 visad/data/amanda/Tracks.java                      |   88 +
 visad/data/avi/AVIForm.java                        |   45 +
 visad/data/bio/BioRadForm.java                     |   44 +
 visad/data/bio/DeltavisionForm.java                |   44 +
 visad/data/bio/FluoviewTiffForm.java               |   45 +
 visad/data/bio/GatanForm.java                      |   44 +
 visad/data/bio/ICSForm.java                        |   44 +
 visad/data/bio/IPLabForm.java                      |   44 +
 visad/data/bio/IPWForm.java                        |   44 +
 visad/data/bio/ImageProSeqForm.java                |   45 +
 visad/data/bio/LegacyZVIForm.java                  |   44 +
 visad/data/bio/LeicaForm.java                      |   44 +
 visad/data/bio/LociForm.java                       |  446 ++
 visad/data/bio/MetamorphForm.java                  |   44 +
 visad/data/bio/OMEReader.java                      |   48 +
 visad/data/bio/OpenlabForm.java                    |   44 +
 visad/data/bio/PerkinElmerForm.java                |   44 +
 visad/data/bio/ZVIForm.java                        |   44 +
 visad/data/bio/ZeissForm.java                      |   44 +
 visad/data/bio/package.html                        |    4 +
 visad/data/biorad/BioRadForm.java                  |   34 +
 visad/data/dods/Adapter.java                       |  271 +
 visad/data/dods/ArrayVariableAdapter.java          |  238 +
 visad/data/dods/AttributeAdapter.java              |   47 +
 visad/data/dods/AttributeAdapterFactory.java       |  252 +
 visad/data/dods/BaseTypeVectorAdapter.java         |  107 +
 visad/data/dods/BooleanVariableAdapter.java        |  106 +
 visad/data/dods/BooleanVectorAdapter.java          |   78 +
 visad/data/dods/ByteAttributeAdapter.java          |   52 +
 visad/data/dods/ByteValuator.java                  |   69 +
 visad/data/dods/ByteVariableAdapter.java           |   90 +
 visad/data/dods/ByteVectorAdapter.java             |   83 +
 visad/data/dods/ContainerAttributeAdapter.java     |   97 +
 visad/data/dods/DODSForm.java                      |  248 +
 visad/data/dods/DODSFormTest.java                  |   83 +
 visad/data/dods/DODSSource.java                    |  284 +
 visad/data/dods/DataFactory.java                   |  132 +
 visad/data/dods/Float32AttributeAdapter.java       |   65 +
 visad/data/dods/Float32Valuator.java               |   84 +
 visad/data/dods/Float32VariableAdapter.java        |  111 +
 visad/data/dods/Float32VectorAdapter.java          |   83 +
 visad/data/dods/Float64AttributeAdapter.java       |  120 +
 visad/data/dods/Float64Valuator.java               |   84 +
 visad/data/dods/Float64VariableAdapter.java        |  112 +
 visad/data/dods/Float64VectorAdapter.java          |  144 +
 visad/data/dods/FloatAttributeAdapter.java         |  116 +
 visad/data/dods/FloatVectorAdapter.java            |  129 +
 visad/data/dods/FullSizeTest.java                  |   52 +
 visad/data/dods/GridVariableAdapter.java           |  360 ++
 visad/data/dods/GridVariableMapAdapter.java        |  100 +
 visad/data/dods/Int16AttributeAdapter.java         |   52 +
 visad/data/dods/Int16Valuator.java                 |   69 +
 visad/data/dods/Int16VariableAdapter.java          |  111 +
 visad/data/dods/Int16VectorAdapter.java            |   84 +
 visad/data/dods/Int32AttributeAdapter.java         |   65 +
 visad/data/dods/Int32Valuator.java                 |   69 +
 visad/data/dods/Int32VariableAdapter.java          |  111 +
 visad/data/dods/Int32VectorAdapter.java            |   85 +
 visad/data/dods/IntValuator.java                   |  119 +
 visad/data/dods/ListVariableAdapter.java           |  140 +
 visad/data/dods/NumericAttributeAdapter.java       |  126 +
 visad/data/dods/NumericVectorAdapter.java          |   95 +
 visad/data/dods/SequenceVariableAdapter.java       |  317 +
 visad/data/dods/SizeTest.java                      |   51 +
 visad/data/dods/StringAttributeAdapter.java        |   77 +
 visad/data/dods/StringVariableAdapter.java         |   99 +
 visad/data/dods/StructureVariableAdapter.java      |  186 +
 visad/data/dods/UByteValuator.java                 |   69 +
 visad/data/dods/UInt16AttributeAdapter.java        |   52 +
 visad/data/dods/UInt16Valuator.java                |   69 +
 visad/data/dods/UInt16VariableAdapter.java         |  116 +
 visad/data/dods/UInt16VectorAdapter.java           |   85 +
 visad/data/dods/UInt32AttributeAdapter.java        |   66 +
 visad/data/dods/UInt32Valuator.java                |   69 +
 visad/data/dods/UInt32VariableAdapter.java         |  116 +
 visad/data/dods/UInt32VectorAdapter.java           |   84 +
 visad/data/dods/UIntValuator.java                  |  106 +
 visad/data/dods/UnknownAttributeAdapter.java       |   68 +
 visad/data/dods/Valuator.java                      |  306 +
 visad/data/dods/VariableAdapter.java               |  472 ++
 visad/data/dods/VariableAdapterFactory.java        |  391 ++
 visad/data/dods/VectorAccessor.java                |  147 +
 visad/data/dods/VectorAdapter.java                 |  349 ++
 visad/data/dods/VectorAdapterFactory.java          |  324 +
 visad/data/dods/package.html                       |   20 +
 visad/data/fits/ConvertArray.java                  |  258 +
 visad/data/fits/ConvertDoubleArray.java            |   81 +
 visad/data/fits/DumpHeader.java                    |  358 ++
 visad/data/fits/ExceptionStack.java                |  131 +
 visad/data/fits/FitsAdapter.java                   |  567 ++
 visad/data/fits/FitsForm.java                      |  150 +
 visad/data/fits/FitsTourGuide.java                 |   77 +
 visad/data/fits/GenericArrayConverter.java         |  136 +
 visad/data/fits/Spasm.java                         |  288 +
 visad/data/fits/ToFits.java                        |   66 +
 visad/data/fits/TourGuide.java                     |   66 +
 visad/data/fits/TourInspector.java                 |   75 +
 visad/data/fits/TourWriter.java                    |  314 +
 visad/data/fits/Tourist.java                       |   61 +
 visad/data/fits/package.html                       |   10 +
 visad/data/fits/testdata/sseclogo.fits             |  Bin 0 -> 31680 bytes
 visad/data/gendcm.tcl                              |  145 +
 visad/data/gif/GIFAdapter.java                     |   81 +
 visad/data/gif/GIFForm.java                        |  106 +
 visad/data/gif/TestGIF.java                        |   77 +
 visad/data/gif/package.html                        |   10 +
 visad/data/gif/sseclogo.gif                        |  Bin 0 -> 1729 bytes
 visad/data/gis/ArcAsciiGridAdapter.java            |  744 +++
 visad/data/gis/ArcAsciiGridForm.java               |  149 +
 visad/data/gis/DemFamily.java                      |  230 +
 visad/data/gis/UsgsDemAdapter.java                 |  899 +++
 visad/data/gis/UsgsDemForm.java                    |  130 +
 visad/data/hdf5/COPYING                            |   41 +
 visad/data/hdf5/HDF5AdapterException.java          |   42 +
 visad/data/hdf5/HDF5DataAdaptable.java             |   53 +
 visad/data/hdf5/HDF5DatasetAdapted.java            |  397 ++
 visad/data/hdf5/HDF5FileAdapted.java               |  133 +
 visad/data/hdf5/HDF5Form.java                      |  421 ++
 visad/data/hdf5/HDF5GroupAdapted.java              |  205 +
 visad/data/hdf5/hdf5objects/HDF5Attribute.java     |  181 +
 visad/data/hdf5/hdf5objects/HDF5Dataset.java       |  513 ++
 visad/data/hdf5/hdf5objects/HDF5Dataspace.java     |  228 +
 visad/data/hdf5/hdf5objects/HDF5Datatype.java      |  362 ++
 visad/data/hdf5/hdf5objects/HDF5File.java          |  220 +
 visad/data/hdf5/hdf5objects/HDF5Group.java         |  216 +
 visad/data/hdf5/hdf5objects/HDF5Object.java        |  175 +
 visad/data/hdf5/hdf5objects/HDF5TreeNode.java      |  117 +
 visad/data/hdfeos/Calibration.java                 |   34 +
 visad/data/hdfeos/CalibrationDefault.java          |  155 +
 visad/data/hdfeos/DimensionSet.java                |  190 +
 visad/data/hdfeos/EosGrid.java                     |  240 +
 visad/data/hdfeos/EosStruct.java                   |  155 +
 visad/data/hdfeos/EosSwath.java                    |  307 +
 visad/data/hdfeos/GctpException.java               |   36 +
 visad/data/hdfeos/GctpFunction.java                |  592 ++
 visad/data/hdfeos/GctpMap.java                     |  175 +
 visad/data/hdfeos/GeoMap.java                      |   42 +
 visad/data/hdfeos/GeoMapSet.java                   |   93 +
 visad/data/hdfeos/Hdfeos.java                      |   73 +
 visad/data/hdfeos/HdfeosAccessor.java              |   70 +
 visad/data/hdfeos/HdfeosAdaptedForm.java           |   45 +
 visad/data/hdfeos/HdfeosData.java                  |   48 +
 visad/data/hdfeos/HdfeosDomain.java                |  471 ++
 visad/data/hdfeos/HdfeosDomainMap.java             |   63 +
 visad/data/hdfeos/HdfeosException.java             |   41 +
 visad/data/hdfeos/HdfeosField.java                 |  129 +
 visad/data/hdfeos/HdfeosFile.java                  |  145 +
 visad/data/hdfeos/HdfeosFlatField.java             |  264 +
 visad/data/hdfeos/HdfeosForm.java                  |  527 ++
 visad/data/hdfeos/HdfeosTuple.java                 |  136 +
 visad/data/hdfeos/LambertAzimuthalEqualArea.java   |  282 +
 visad/data/hdfeos/LambertConformalConic.java       |  261 +
 visad/data/hdfeos/NamedDimension.java              |  101 +
 visad/data/hdfeos/PolarStereographic.java          |  343 ++
 visad/data/hdfeos/README.hdfeos                    |   71 +
 visad/data/hdfeos/Shape.java                       |  105 +
 visad/data/hdfeos/ShapeSet.java                    |  134 +
 visad/data/hdfeos/Variable.java                    |  119 +
 visad/data/hdfeos/VariableSet.java                 |  141 +
 visad/data/hdfeos/hdfeosc/EHchkfidImp.c            |   65 +
 visad/data/hdfeos/hdfeosc/EHcloseImp.c             |   43 +
 visad/data/hdfeos/hdfeosc/EHgetcalImp.c            |   72 +
 visad/data/hdfeos/hdfeosc/GDattachImp.c            |   44 +
 visad/data/hdfeos/hdfeosc/GDfdims.c                |  230 +
 visad/data/hdfeos/hdfeosc/GDfdimsImp.c             |   54 +
 visad/data/hdfeos/hdfeosc/GDfieldinfoImp.c         |   74 +
 visad/data/hdfeos/hdfeosc/GDgridinfoImp.c          |   61 +
 visad/data/hdfeos/hdfeosc/GDinqattrsImp.c          |   61 +
 visad/data/hdfeos/hdfeosc/GDinqdimsImp.c           |   59 +
 visad/data/hdfeos/hdfeosc/GDinqfieldsImp.c         |   72 +
 visad/data/hdfeos/hdfeosc/GDinqgridImp.c           |   60 +
 visad/data/hdfeos/hdfeosc/GDnentriesImp.c          |   50 +
 visad/data/hdfeos/hdfeosc/GDopenImp.c              |   45 +
 visad/data/hdfeos/hdfeosc/GDprojinfoImp.c          |   61 +
 visad/data/hdfeos/hdfeosc/GDreadfieldImp.c         |  228 +
 visad/data/hdfeos/hdfeosc/GetNumericAttrImp.c      |  134 +
 visad/data/hdfeos/hdfeosc/HdfeosLib.java           |  120 +
 visad/data/hdfeos/hdfeosc/Makefile                 |  137 +
 visad/data/hdfeos/hdfeosc/SDattrinfoImp.c          |   78 +
 visad/data/hdfeos/hdfeosc/SWattachImp.c            |   48 +
 visad/data/hdfeos/hdfeosc/SWfdims.c                |  233 +
 visad/data/hdfeos/hdfeosc/SWfdimsImp.c             |   59 +
 visad/data/hdfeos/hdfeosc/SWfieldinfoImp.c         |   72 +
 visad/data/hdfeos/hdfeosc/SWinqdatafieldsImp.c     |   73 +
 visad/data/hdfeos/hdfeosc/SWinqdimsImp.c           |   62 +
 visad/data/hdfeos/hdfeosc/SWinqgeofieldsImp.c      |   68 +
 visad/data/hdfeos/hdfeosc/SWinqmapsImp.c           |   68 +
 visad/data/hdfeos/hdfeosc/SWinqswathImp.c          |   64 +
 visad/data/hdfeos/hdfeosc/SWnentriesImp.c          |   51 +
 visad/data/hdfeos/hdfeosc/SWopenImp.c              |   46 +
 visad/data/hdfeos/hdfeosc/SWreadfieldImp.c         |  229 +
 visad/data/hdfeos/package.html                     |   10 +
 visad/data/hrit/HRITAdapter.java                   |  722 +++
 visad/data/hrit/HRITCoordinateSystem.java          |  724 +++
 visad/data/hrit/HRITForm.java                      |  151 +
 visad/data/in/AndCondition.java                    |   83 +
 visad/data/in/ArithProg.java                       |  311 +
 visad/data/in/Condition.java                       |   60 +
 visad/data/in/Consolidator.java                    |   96 +
 visad/data/in/DataInputFilter.java                 |   67 +
 visad/data/in/DataInputSource.java                 |   50 +
 visad/data/in/DataInputStream.java                 |   50 +
 visad/data/in/DoubleValueVetter.java               |  190 +
 visad/data/in/LonArithProg.java                    |  189 +
 visad/data/in/MD5Key.java                          |  131 +
 visad/data/in/MathTypeCondition.java               |   79 +
 visad/data/in/MultipleValueVetter.java             |  196 +
 visad/data/in/NotCondition.java                    |   77 +
 visad/data/in/OffsetUnpacker.java                  |  159 +
 visad/data/in/OrCondition.java                     |   83 +
 visad/data/in/ScaleAndOffsetUnpacker.java          |  183 +
 visad/data/in/ScaleUnpacker.java                   |  169 +
 visad/data/in/Selector.java                        |   90 +
 visad/data/in/SingleValueVetter.java               |  167 +
 visad/data/in/TimeFactorer.java                    |  105 +
 visad/data/in/ValueProcessor.java                  |  101 +
 visad/data/in/ValueRanger.java                     |  249 +
 visad/data/in/ValueUnpacker.java                   |   87 +
 visad/data/in/ValueVetter.java                     |  129 +
 visad/data/in/package.html                         |   20 +
 visad/data/jai/JAIForm.java                        |  193 +
 visad/data/mcidas/AREACoordinateSystem.java        |  865 +++
 visad/data/mcidas/AddeTextAdapter.java             |   85 +
 visad/data/mcidas/AreaAdapter.java                 |  742 +++
 visad/data/mcidas/AreaForm.java                    |  155 +
 visad/data/mcidas/BaseMapAdapter.java              |  604 ++
 visad/data/mcidas/GRIDCoordinateSystem.java        |  156 +
 visad/data/mcidas/MapForm.java                     |  130 +
 visad/data/mcidas/McIDASGridDirectory.java         |  165 +
 visad/data/mcidas/McIDASGridReader.java            |  190 +
 visad/data/mcidas/OUTLAUST                         |  Bin 0 -> 722116 bytes
 visad/data/mcidas/OUTLSUPW                         |  Bin 0 -> 89956 bytes
 visad/data/mcidas/OUTLUSAM                         |  Bin 0 -> 450220 bytes
 visad/data/mcidas/PointDataAdapter.java            |  393 ++
 visad/data/mcidas/PointForm.java                   |  136 +
 visad/data/mcidas/README.mcidas                    |   56 +
 visad/data/mcidas/TestArea.java                    |  347 ++
 visad/data/mcidas/package.html                     |   11 +
 visad/data/netcdf/FileDialogPanel.java             |  239 +
 visad/data/netcdf/InputNetcdf.java                 |  195 +
 visad/data/netcdf/InputNetcdfPathnameEditor.java   |   24 +
 visad/data/netcdf/InputPathnameEditor.java         |  115 +
 visad/data/netcdf/NOTEBOOK                         |   35 +
 visad/data/netcdf/NetCDF.java                      |   97 +
 visad/data/netcdf/NetcdfInBean.java                |  101 +
 visad/data/netcdf/Plain.java                       |  387 ++
 visad/data/netcdf/Quantity.java                    |  149 +
 visad/data/netcdf/QuantityCheck.java               |   53 +
 visad/data/netcdf/QuantityDB.java                  |  218 +
 visad/data/netcdf/QuantityDBImpl.java              |  605 ++
 visad/data/netcdf/QuantityDBManager.java           |  127 +
 visad/data/netcdf/README                           |    2 +
 visad/data/netcdf/StandardQuantityDB.java          |  428 ++
 visad/data/netcdf/StandardQuantityDB.save          |    1 +
 .../data/netcdf/UnsupportedOperationException.java |   36 +
 visad/data/netcdf/VariableFilter.java              |   94 +
 visad/data/netcdf/depend                           |   16 +
 visad/data/netcdf/in/CfView.java                   | 1346 +++++
 visad/data/netcdf/in/CompositeStrategy.java        |   98 +
 visad/data/netcdf/in/Context.java                  |  129 +
 visad/data/netcdf/in/DataFactory.java              |  236 +
 visad/data/netcdf/in/DefaultView.java              |  571 ++
 visad/data/netcdf/in/FileDataFactory.java          |  165 +
 visad/data/netcdf/in/FileStrategy.java             |  165 +
 visad/data/netcdf/in/FlatMerger.java               |   93 +
 visad/data/netcdf/in/InMemoryStrategy.java         |   81 +
 visad/data/netcdf/in/InvalidContextException.java  |   48 +
 visad/data/netcdf/in/MaxFileFieldStrategy.java     |   62 +
 visad/data/netcdf/in/Merger.java                   |  260 +
 visad/data/netcdf/in/NetcdfAdapter.java            |  356 ++
 visad/data/netcdf/in/NetcdfQuantityDB.java         |  135 +
 visad/data/netcdf/in/Strategy.java                 |   98 +
 visad/data/netcdf/in/Vetter.java                   |  272 +
 visad/data/netcdf/in/View.java                     | 1275 ++++
 visad/data/netcdf/in/VirtualData.java              |  112 +
 visad/data/netcdf/in/VirtualDataIterator.java      |  113 +
 visad/data/netcdf/in/VirtualField.java             |  200 +
 visad/data/netcdf/in/VirtualFlatField.java         |   71 +
 visad/data/netcdf/in/VirtualReal.java              |  190 +
 visad/data/netcdf/in/VirtualScalar.java            |  307 +
 visad/data/netcdf/in/VirtualText.java              |  114 +
 visad/data/netcdf/in/VirtualTuple.java             |  274 +
 visad/data/netcdf/in/depend                        |   18 +
 visad/data/netcdf/in/package.html                  |   36 +
 visad/data/netcdf/index.html                       |   32 +
 visad/data/netcdf/out/CoordVar.java                |   96 +
 visad/data/netcdf/out/DataAccessor.java            |  151 +
 visad/data/netcdf/out/DependentRealVar.java        |  194 +
 visad/data/netcdf/out/DependentTextVar.java        |  103 +
 visad/data/netcdf/out/DependentVar.java            |   81 +
 visad/data/netcdf/out/ExportVar.java               |  289 +
 visad/data/netcdf/out/FieldAccessor.java           |   76 +
 visad/data/netcdf/out/IndependentVar.java          |  106 +
 visad/data/netcdf/out/RealAccessor.java            |   60 +
 visad/data/netcdf/out/TextAccessor.java            |   65 +
 visad/data/netcdf/out/TrivialAccessor.java         |   94 +
 visad/data/netcdf/out/TupleAccessor.java           |   68 +
 visad/data/netcdf/out/VisADAccessor.java           |   60 +
 visad/data/netcdf/out/VisADAdapter.java            |  507 ++
 visad/data/netcdf/out/package.html                 |   36 +
 visad/data/netcdf/package.html                     |   21 +
 visad/data/netcdf/small.nc                         |  Bin 0 -> 7400 bytes
 visad/data/netcdf/units/DefaultUnitsDB.java        |   65 +
 visad/data/netcdf/units/NoSuchUnitException.java   |   31 +
 visad/data/netcdf/units/ParseException.java        |   33 +
 visad/data/netcdf/units/Parser.java                |   43 +
 visad/data/netcdf/units/UnitParser.java            |   42 +
 visad/data/netcdf/units/UnitsDB.java               |   29 +
 visad/data/package.html                            |   42 +
 visad/data/qt/PictForm.java                        |   44 +
 visad/data/qt/QTForm.java                          |   45 +
 visad/data/text/README.text                        |  413 ++
 visad/data/text/TextAdapter.java                   | 1846 ++++++
 visad/data/text/TextForm.java                      |  118 +
 visad/data/text/example1.txt                       |   42 +
 visad/data/text/example2.csv                       |   41 +
 visad/data/text/example3.txt                       |   14 +
 visad/data/text/example4.csv                       |    9 +
 visad/data/text/testcolspan.csv                    |    9 +
 visad/data/text/testfixedvalue.csv                 |   23 +
 visad/data/tiff/LegacyBitBuffer.java               |  201 +
 visad/data/tiff/LegacyTiffForm.java                |  538 ++
 visad/data/tiff/LegacyTiffTools.java               |  374 ++
 visad/data/tiff/TiffForm.java                      |   63 +
 visad/data/units/API_users_guide.html              |   44 +
 visad/data/units/DefaultUnitsDB.java               | 1105 ++++
 visad/data/units/NoSuchUnitException.java          |   26 +
 visad/data/units/ParseException.java               |  198 +
 visad/data/units/Parser.java                       |  122 +
 visad/data/units/SimpleCharStream.java             |  472 ++
 visad/data/units/Token.java                        |  124 +
 visad/data/units/TokenMgrError.java                |  140 +
 visad/data/units/UnitParser.java                   | 1994 +++++++
 visad/data/units/UnitParser.jj                     |  749 +++
 visad/data/units/UnitParserConstants.java          |   88 +
 visad/data/units/UnitParserTokenManager.java       |  645 ++
 visad/data/units/UnitPrefix.java                   |   46 +
 visad/data/units/UnitTable.java                    |  387 ++
 visad/data/units/UnitsDB.java                      |   97 +
 visad/data/units/index.html                        |   16 +
 visad/data/vis5d/V5DStruct.java                    | 2601 ++++++++
 visad/data/vis5d/Vis5DAdaptedForm.java             |   47 +
 visad/data/vis5d/Vis5DCoordinateSystem.java        |  560 ++
 visad/data/vis5d/Vis5DFamily.java                  |  201 +
 visad/data/vis5d/Vis5DFile.java                    |   62 +
 visad/data/vis5d/Vis5DFileAccessor.java            |   77 +
 visad/data/vis5d/Vis5DForm.java                    |  819 +++
 visad/data/vis5d/Vis5DTopoForm.java                |  210 +
 visad/data/vis5d/Vis5DVerticalSystem.java          |  314 +
 visad/data/vis5d/package.html                      |   10 +
 visad/data/visad/BinaryFile.java                   |  166 +
 visad/data/visad/BinaryObjectCache.java            |  144 +
 visad/data/visad/BinaryReader.java                 |  571 ++
 visad/data/visad/BinarySizer.java                  |  451 ++
 visad/data/visad/BinaryWriter.java                 | 1002 ++++
 visad/data/visad/FakeData.java                     |  863 +++
 visad/data/visad/Saveable.java                     |   41 +
 visad/data/visad/TestBinary.java                   |  254 +
 visad/data/visad/VisADCachingForm.java             |  109 +
 visad/data/visad/VisADForm.java                    |  326 +
 visad/data/visad/VisADSerialForm.java              |   39 +
 visad/data/visad/binary_file_format.html           |  573 ++
 .../data/visad/object/BinaryCoordinateSystem.java  |  209 +
 visad/data/visad/object/BinaryDataArray.java       |  112 +
 visad/data/visad/object/BinaryDelaunay.java        |  152 +
 visad/data/visad/object/BinaryDisplayRealType.java |   59 +
 .../data/visad/object/BinaryDisplayTupleType.java  |   54 +
 visad/data/visad/object/BinaryDoubleArray.java     |  105 +
 visad/data/visad/object/BinaryDoubleMatrix.java    |  138 +
 visad/data/visad/object/BinaryErrorEstimate.java   |  243 +
 visad/data/visad/object/BinaryFieldImpl.java       |  258 +
 visad/data/visad/object/BinaryFlatField.java       |  541 ++
 visad/data/visad/object/BinaryFloatArray.java      |  105 +
 visad/data/visad/object/BinaryFloatMatrix.java     |  133 +
 visad/data/visad/object/BinaryFunctionType.java    |  112 +
 visad/data/visad/object/BinaryGeneric.java         |   69 +
 .../data/visad/object/BinaryGriddedDoubleSet.java  |  350 ++
 visad/data/visad/object/BinaryGriddedSet.java      |  357 ++
 visad/data/visad/object/BinaryIntegerArray.java    |  124 +
 visad/data/visad/object/BinaryIntegerMatrix.java   |  136 +
 visad/data/visad/object/BinaryIntegerSet.java      |  472 ++
 visad/data/visad/object/BinaryIrregularSet.java    |  350 ++
 visad/data/visad/object/BinaryLinearSet.java       |  511 ++
 visad/data/visad/object/BinaryList1DSet.java       |  225 +
 visad/data/visad/object/BinaryMathType.java        |  133 +
 visad/data/visad/object/BinaryObject.java          |   33 +
 visad/data/visad/object/BinaryProductSet.java      |  266 +
 visad/data/visad/object/BinaryQuantity.java        |  181 +
 visad/data/visad/object/BinaryReal.java            |  189 +
 visad/data/visad/object/BinaryRealTuple.java       |  306 +
 visad/data/visad/object/BinaryRealTupleType.java   |  226 +
 visad/data/visad/object/BinaryRealType.java        |  205 +
 visad/data/visad/object/BinaryRealVectorType.java  |   54 +
 visad/data/visad/object/BinarySampledSet.java      |   91 +
 visad/data/visad/object/BinaryScalarType.java      |   73 +
 .../data/visad/object/BinarySerializedObject.java  |  117 +
 visad/data/visad/object/BinarySetType.java         |  144 +
 visad/data/visad/object/BinarySimpleSet.java       |  228 +
 visad/data/visad/object/BinarySingletonSet.java    |  273 +
 visad/data/visad/object/BinarySize.java            |   47 +
 visad/data/visad/object/BinaryString.java          |   79 +
 visad/data/visad/object/BinaryText.java            |  116 +
 visad/data/visad/object/BinaryTextType.java        |  117 +
 visad/data/visad/object/BinaryTuple.java           |  171 +
 visad/data/visad/object/BinaryTupleType.java       |  136 +
 visad/data/visad/object/BinaryUnionSet.java        |  179 +
 visad/data/visad/object/BinaryUnit.java            |  234 +
 visad/data/visad/object/BinaryUnknown.java         |   45 +
 visad/data/visad/package.html                      |   13 +
 visad/data/visad/util/build_binary_file.pl         |  115 +
 visad/data/visad/util/build_html_tbls.pl           |   61 +
 visad/data/visad/util/datas                        |   41 +
 visad/data/visad/util/debugs                       |   17 +
 visad/data/visad/util/flds                         |   38 +
 visad/data/visad/util/maths                        |   12 +
 visad/data/visad/util/objs                         |    7 +
 visad/depend                                       |    0
 visad/formula/FormulaException.java                |   35 +
 visad/formula/FormulaManager.java                  |  383 ++
 visad/formula/FormulaUtil.java                     |  599 ++
 visad/formula/FormulaVar.java                      |  707 +++
 visad/formula/Postfix.java                         |  352 ++
 visad/formula/VMethod.java                         |   48 +
 visad/formula/VRealType.java                       |   66 +
 visad/formula/package.html                         |   24 +
 visad/georef/EarthLocation.java                    |   50 +
 visad/georef/EarthLocationLite.java                |  270 +
 visad/georef/EarthLocationTuple.java               |  238 +
 visad/georef/LatLonPoint.java                      |   60 +
 visad/georef/LatLonTuple.java                      |  181 +
 visad/georef/LongitudeLatitudeInterpCS.java        |  196 +
 visad/georef/MapProjection.java                    |  177 +
 visad/georef/NamedLocation.java                    |   53 +
 visad/georef/NamedLocationTuple.java               |  294 +
 visad/georef/NavigatedCoordinateSystem.java        |   97 +
 visad/georef/NavigatedField.java                   |   44 +
 visad/georef/TrivialMapProjection.java             |  197 +
 visad/georef/TrivialNavigation.java                |   99 +
 visad/georef/UTMCoordinate.java                    |  400 ++
 visad/georef/package.html                          |   19 +
 visad/gifts/Gifts.java                             |  859 +++
 visad/gifts/TextForm.java                          |  208 +
 visad/install/ChooserList.java                     |  162 +
 visad/install/Download.java                        |  190 +
 visad/install/JavaFile.java                        |  248 +
 visad/install/Main.java                            | 1051 ++++
 visad/install/Path.java                            |  156 +
 visad/install/ProgressMonitor.java                 |  237 +
 visad/install/README                               |   56 +
 visad/install/README.html                          |   73 +
 visad/install/SplashScreen.java                    |  296 +
 visad/install/TestDownload.java                    |  115 +
 visad/install/TestUtil.java                        |  188 +
 visad/install/UpdateJar.java                       |  214 +
 visad/install/Util.java                            |  393 ++
 visad/install/install_visad                        |   63 +
 visad/java2d/AVControlJ2D.java                     |  153 +
 visad/java2d/AnimationControlJ2D.java              |  636 ++
 visad/java2d/AnimationSetControlJ2D.java           |   55 +
 visad/java2d/DefaultDisplayRendererJ2D.java        |  205 +
 visad/java2d/DefaultRendererJ2D.java               |  130 +
 visad/java2d/DirectManipulationRendererJ2D.java    |  204 +
 visad/java2d/DisplayImplJ2D.java                   |  342 ++
 visad/java2d/DisplayPanelJ2D.java                  |   57 +
 visad/java2d/DisplayRendererJ2D.java               |  952 +++
 visad/java2d/GraphicsModeControlJ2D.java           |  891 +++
 visad/java2d/KeyboardBehaviorJ2D.java              |  242 +
 visad/java2d/MouseBehaviorJ2D.java                 |  411 ++
 visad/java2d/ProjectionControlJ2D.java             |  115 +
 visad/java2d/RendererJ2D.java                      |  245 +
 visad/java2d/ShadowFunctionOrSetTypeJ2D.java       |  207 +
 visad/java2d/ShadowFunctionTypeJ2D.java            |   46 +
 visad/java2d/ShadowRealTupleTypeJ2D.java           |   62 +
 visad/java2d/ShadowRealTypeJ2D.java                |   90 +
 visad/java2d/ShadowScalarTypeJ2D.java              |   67 +
 visad/java2d/ShadowSetTypeJ2D.java                 |   46 +
 visad/java2d/ShadowTextTypeJ2D.java                |   90 +
 visad/java2d/ShadowTupleTypeJ2D.java               |  131 +
 visad/java2d/ShadowTypeJ2D.java                    |  390 ++
 visad/java2d/ValueControlJ2D.java                  |  133 +
 visad/java2d/VisADCanvasJ2D.java                   | 1342 +++++
 visad/java2d/package.html                          |   10 +
 visad/java3d/AVControlJ3D.java                     |  236 +
 visad/java3d/AnimationControlJ3D.java              |  671 +++
 visad/java3d/AnimationRendererJ3D.java             |  294 +
 visad/java3d/DefaultDisplayRendererJ3D.java        |  327 +
 visad/java3d/DefaultRendererJ3D.java               |  268 +
 visad/java3d/DirectManipulationRendererJ3D.java    |  210 +
 visad/java3d/DisplayAppletJ3D.java                 |   67 +
 visad/java3d/DisplayImplJ3D.java                   |  762 +++
 visad/java3d/DisplayPanelJ3D.java                  |   85 +
 visad/java3d/DisplayRendererJ3D.java               | 1648 +++++
 visad/java3d/DownRoundingAnimationControlJ3D.java  |  165 +
 visad/java3d/GraphicsModeControlJ3D.java           | 1076 ++++
 visad/java3d/ImmersaDeskDisplayRendererJ3D.java    |  299 +
 visad/java3d/KeyboardBehaviorJ3D.java              |  442 ++
 visad/java3d/MouseBehaviorJ3D.java                 |  818 +++
 visad/java3d/ProjectionControlJ3D.java             |  288 +
 visad/java3d/RendererJ3D.java                      |  370 ++
 visad/java3d/ShadowAnimationFunctionTypeJ3D.java   |  231 +
 visad/java3d/ShadowFunctionOrSetTypeJ3D.java       | 1283 ++++
 visad/java3d/ShadowFunctionTypeJ3D.java            |   46 +
 visad/java3d/ShadowRealTupleTypeJ3D.java           |   62 +
 visad/java3d/ShadowRealTypeJ3D.java                |   87 +
 visad/java3d/ShadowScalarTypeJ3D.java              |   67 +
 visad/java3d/ShadowSetTypeJ3D.java                 |   46 +
 visad/java3d/ShadowTextTypeJ3D.java                |   87 +
 visad/java3d/ShadowTupleTypeJ3D.java               |  137 +
 visad/java3d/ShadowTypeJ3D.java                    | 1709 ++++++
 visad/java3d/TrackdAPI.c                           |  115 +
 visad/java3d/TrackdJNI.java                        |   68 +
 visad/java3d/TransformOnlyDisplayRendererJ3D.java  |  145 +
 visad/java3d/TwoDDisplayRendererJ3D.java           |  271 +
 visad/java3d/UniverseBuilderJ3D.java               |  125 +
 visad/java3d/ValueControlJ3D.java                  |  121 +
 visad/java3d/VisADBranchGroup.java                 |   47 +
 visad/java3d/VisADCanvasJ3D.java                   |  513 ++
 visad/java3d/VisADImageNode.java                   |  151 +
 visad/java3d/VisADImageTile.java                   |  127 +
 visad/java3d/WandBehaviorJ3D.java                  |  317 +
 visad/java3d/package.html                          |   10 +
 visad/java3d/rmic_script                           |    1 +
 visad/jmet/AlbersCoordinateSystem.java             |  182 +
 visad/jmet/DumpType.java                           |  545 ++
 visad/jmet/EASECoordinateSystem.java               |  170 +
 visad/jmet/GRIBCoordinateSystem.java               |  599 ++
 visad/jmet/MetGrid.java                            |  106 +
 visad/jmet/MetGridDirectory.java                   |  115 +
 visad/jmet/MetUnits.java                           |  160 +
 visad/jmet/NCEPPanel.java                          |  442 ++
 visad/jmet/NetcdfGrids.java                        |  811 +++
 visad/jmet/ShowNCEPModel.java                      |  683 +++
 visad/math/FFT.java                                |  799 +++
 visad/math/Histogram.java                          |  185 +
 visad/matrix/JamaCholeskyDecomposition.java        |  214 +
 visad/matrix/JamaEigenvalueDecomposition.java      |  266 +
 visad/matrix/JamaLUDecomposition.java              |  316 +
 visad/matrix/JamaMatrix.java                       | 1454 +++++
 visad/matrix/JamaQRDecomposition.java              |  290 +
 visad/matrix/JamaSingularValueDecomposition.java   |  335 ++
 visad/meteorology/ImageSequence.java               |   70 +
 visad/meteorology/ImageSequenceImpl.java           |  168 +
 visad/meteorology/ImageSequenceManager.java        |  211 +
 visad/meteorology/NavigatedImage.java              |  173 +
 visad/meteorology/SatelliteData.java               |   43 +
 visad/meteorology/SatelliteImage.java              |  108 +
 visad/meteorology/SingleBandedImage.java           |   73 +
 visad/meteorology/SingleBandedImageImpl.java       |  508 ++
 visad/meteorology/WeatherSymbols.java              |  579 ++
 visad/meteorology/package.html                     |   19 +
 visad/overview.html                                |   12 +
 visad/package.html                                 |   11 +
 visad/python/JPythonEditor.java                    |  281 +
 visad/python/JPythonFrame.java                     |  118 +
 visad/python/JPythonMethods.java                   | 4556 ++++++++++++++
 visad/python/README.python                         |  648 ++
 visad/python/RunJPython.java                       |  196 +
 visad/python/area_test.py                          |   15 +
 visad/python/cheatsheet.txt                        |  442 ++
 visad/python/dna.py                                |  116 +
 visad/python/dna_molecule.txt                      |  657 ++
 visad/python/fft_test.py                           |   18 +
 visad/python/field1d.py                            |   19 +
 visad/python/field2d.py                            |   27 +
 visad/python/gd.py                                 |  106 +
 visad/python/graph.py                              |  494 ++
 visad/python/hist_test.py                          |   11 +
 visad/python/image_line.py                         |   74 +
 visad/python/image_line_client.py                  |   39 +
 visad/python/image_line_server.py                  |   82 +
 visad/python/image_text_test.py                    |   46 +
 visad/python/make_data.py                          |   34 +
 visad/python/matrix_test.py                        |   19 +
 visad/python/mcidas_test.py                        |   23 +
 visad/python/mike.py                               |   39 +
 visad/python/resample_test.py                      |   23 +
 visad/python/slice.py                              |   91 +
 visad/python/slice_contour.py                      |   93 +
 visad/python/slice_rgb.py                          |   93 +
 visad/python/stroud.py                             |  154 +
 visad/python/stroud2.py                            |  161 +
 visad/python/subs.py                               | 1572 +++++
 visad/python/vis_test.py                           |   23 +
 visad/rabin/Rain.java                              | 1049 ++++
 visad/rabin/RainSheet.java                         |  318 +
 visad/rmic_script                                  |    1 +
 visad/sounder/NastiInstrument.java                 |  151 +
 visad/sounder/PCA.java                             |  102 +
 visad/sounder/SounderDisplay.java                  |   44 +
 visad/sounder/SounderInstrument.java               |  127 +
 visad/sounder/Sounding.java                        |  202 +
 visad/sounder/Spectrum.java                        |  188 +
 visad/ss/2d.gif                                    |  Bin 0 -> 340 bytes
 visad/ss/3d.gif                                    |  Bin 0 -> 357 bytes
 visad/ss/BasicSSCell.java                          | 3497 +++++++++++
 visad/ss/FancySSCell.java                          |  862 +++
 visad/ss/MappingDialog.java                        | 1278 ++++
 visad/ss/README.ss                                 |  665 +++
 visad/ss/SSCellChangeEvent.java                    |  100 +
 visad/ss/SSCellData.java                           |  333 ++
 visad/ss/SSCellImpl.java                           |  208 +
 visad/ss/SSCellListener.java                       |   39 +
 visad/ss/SSLayout.java                             |  160 +
 visad/ss/SpreadSheet.java                          | 3914 ++++++++++++
 visad/ss/add.gif                                   |  Bin 0 -> 863 bytes
 visad/ss/copy.gif                                  |  Bin 0 -> 145 bytes
 visad/ss/cut.gif                                   |  Bin 0 -> 130 bytes
 visad/ss/del.gif                                   |  Bin 0 -> 862 bytes
 visad/ss/display.gif                               |  Bin 0 -> 5847 bytes
 visad/ss/j2d.gif                                   |  Bin 0 -> 559 bytes
 visad/ss/mappings.gif                              |  Bin 0 -> 859 bytes
 visad/ss/open.gif                                  |  Bin 0 -> 140 bytes
 visad/ss/package.html                              |   12 +
 visad/ss/paste.gif                                 |  Bin 0 -> 159 bytes
 visad/ss/reset.gif                                 |  Bin 0 -> 872 bytes
 visad/ss/save.gif                                  |  Bin 0 -> 138 bytes
 visad/ss/show.gif                                  |  Bin 0 -> 872 bytes
 visad/ss/tile.gif                                  |  Bin 0 -> 160 bytes
 visad/test/ContourHardTest.java                    |   82 +
 visad/test/FlatFieldCacheTest.java                 |  172 +
 visad/test/ImageAnimationTest.java                 |  151 +
 visad/test/J3DTextureTest.java                     |  270 +
 visad/test/TriangleStripBuilderTest.java           |  179 +
 visad/util/AnimationWidget.java                    |  437 ++
 visad/util/ArrowSlider.java                        |  344 ++
 visad/util/BarGraph.java                           |  428 ++
 visad/util/BarSlider.java                          |  280 +
 visad/util/BaseRGBMap.java                         |  949 +++
 visad/util/ChosenColorWidget.java                  |  405 ++
 visad/util/ClientServer.java                       |  320 +
 visad/util/CmdlineConsumer.java                    |  129 +
 visad/util/CmdlineGenericConsumer.java             |  144 +
 visad/util/CmdlineParser.java                      |  195 +
 visad/util/CodeEditor.java                         |  180 +
 visad/util/CodeFrame.java                          |  135 +
 visad/util/ColorChangeEvent.java                   |   71 +
 visad/util/ColorChangeListener.java                |   43 +
 visad/util/ColorMap.java                           |  197 +
 visad/util/ColorMapWidget.java                     |  463 ++
 visad/util/ColorPreview.java                       |  186 +
 visad/util/ColorWidget.java                        |  225 +
 visad/util/ComboFileFilter.java                    |  144 +
 visad/util/ContourWidget.java                      |  609 ++
 visad/util/CursorUtil.java                         |  202 +
 visad/util/DataConverter.java                      |  308 +
 visad/util/DataUtility.java                        | 1693 ++++++
 visad/util/Delay.java                              |   50 +
 visad/util/DualRes.java                            |  192 +
 visad/util/ExtensionFileFilter.java                |  113 +
 visad/util/FloatTupleArray.java                    |   88 +
 visad/util/FloatTupleArrayImpl.java                |  177 +
 visad/util/FormFileFilter.java                     |   80 +
 visad/util/GMCWidget.java                          |  236 +
 visad/util/GUIFrame.java                           |  181 +
 visad/util/HersheyFont.java                        |  522 ++
 visad/util/ImageHelper.java                        |   55 +
 visad/util/LabeledColorWidget.java                 |  443 ++
 visad/util/LabeledRGBAWidget.java                  |  107 +
 visad/util/LabeledRGBWidget.java                   |  107 +
 visad/util/McIDASFileFilter.java                   |   54 +
 visad/util/PrintActionListener.java                |  132 +
 visad/util/ProjWidget.java                         |   96 +
 visad/util/RGBAMap.java                            |   68 +
 visad/util/RGBMap.java                             |   68 +
 visad/util/RangeSlider.java                        |   49 +
 visad/util/RangeWidget.java                        |  150 +
 visad/util/ReflectedUniverse.java                  |  352 ++
 visad/util/ResSwitcher.java                        |  170 +
 visad/util/SaveStringTokenizer.java                |  102 +
 visad/util/SceneGraphInspector.java                |  386 ++
 visad/util/SelectRangeWidget.java                  |  226 +
 visad/util/SimpleColorMapWidget.java               |  290 +
 visad/util/Slider.java                             |  114 +
 visad/util/SliderChangeEvent.java                  |   98 +
 visad/util/SliderChangeListener.java               |   43 +
 visad/util/SliderLabel.java                        |  250 +
 visad/util/StepWidget.java                         |  231 +
 visad/util/TextControlWidget.java                  | 1094 ++++
 visad/util/TextEditor.java                         |  318 +
 visad/util/TextFrame.java                          |  235 +
 visad/util/ThreadManager.java                      |  444 ++
 visad/util/ThreadPool.java                         |  258 +
 visad/util/Trace.java                              |  176 +
 visad/util/Util.java                               | 1097 ++++
 visad/util/VisADSlider.java                        |  515 ++
 visad/util/WeakMapValue.java                       |  108 +
 visad/util/WidgetLayout.java                       |  217 +
 visad/util/WidgetTest.java                         |   71 +
 visad/util/cursive.jhf                             |  113 +
 visad/util/futural.jhf                             |  101 +
 visad/util/futuram.jhf                             |  127 +
 visad/util/meteorology.jhf                         |   99 +
 visad/util/package.html                            |   11 +
 visad/util/rowmans.jhf                             |  100 +
 visad/util/rowmant.jhf                             |  176 +
 visad/util/timesr.jhf                              |  123 +
 visad/util/timesrb.jhf                             |  177 +
 visad/util/wmo.jhf                                 |  308 +
 1867 files changed, 550370 insertions(+), 1332 deletions(-)

diff --git a/HTTPClient/0COPYRIGHT b/HTTPClient/0COPYRIGHT
new file mode 100644
index 0000000..d8ee1d8
--- /dev/null
+++ b/HTTPClient/0COPYRIGHT
@@ -0,0 +1,28 @@
+/*
+ *  Copyright (c) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
diff --git a/HTTPClient/0LICENSE b/HTTPClient/0LICENSE
new file mode 100644
index 0000000..6b0b797
--- /dev/null
+++ b/HTTPClient/0LICENSE
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  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 Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    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 Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+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 other code 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.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  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, whereas the latter must
+be combined with the library in order to run.
+

+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser 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 combine 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) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) 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.
+
+    d) 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.
+
+    e) 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 materials to be 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 with
+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 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
+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
+

+           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 Lesser 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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/HTTPClient/0README b/HTTPClient/0README
new file mode 100644
index 0000000..c2617be
--- /dev/null
+++ b/HTTPClient/0README
@@ -0,0 +1,76 @@
+
+This is Version 0.3-3 of the HTTPClient package. The latest version should
+always be available at http://www.innovation.ch/java/HTTPClient/ .
+Copyright (C) 1996-2001  Ronald Tschal�r
+
+The HTTPClient is fairly full-featured http client library. It implements
+most of the relevant parts of HTTP/1.1, and automatically handles things
+like redirections, authorization, and cookies. The functionality can be
+easily extended through the use of modules.
+
+
+Installation:
+-------------
+
+Unpacking the .tar.Z or .zip should've created a subdirectory
+'HTTPClient'. Put this directory somewhere in your classpath. If you
+haven't done the following already I recommend setting up a main
+directory that contains a subdirectory for every java package you
+install, and then add this main directory to your classpath - that way
+for new packages all you have to do is unpack them into a subdirectory
+under that main directory and away you go. Now add a 'import
+HTTPClient.*;' statement to each of your files that use any part of the
+package and you're set to go.
+
+Alternatively you can put everything into a zip file and add that file
+to your CLASSPATH. If your JDK/development-environment supports
+compressed zip files and you downloaded the HTTPClient.zip file then
+you can just add that file to your CLASSPATH. If you downloaded the
+tar file, or if your JDK does not support compressed zip files, then
+unpack the HTTPClient and create an uncompressed zip file to put in
+the CLASSPATH with something like the following:
+
+  zip -r0 HTTPClient.zip HTTPClient -i '*.class'
+
+
+Directory Structure:
+--------------------
+
+ HTTPClient -- the source and compiled class files
+   |
+   +-- http --- the http handler (for use with URLConnection)
+   |
+   +-- shttp -- the shttp handler (for use with URLConnection)
+   |
+   +-- https -- the https handler (for use with URLConnection)
+   |
+   +-- alt ---- alternative versions of the core classes
+   |    |
+   |    +-- HotJava ---- the HotJava specific replacement classes
+   |
+   +-- doc -- all the documentation
+         |
+         +-- api ----- all the javadoc generated api docs
+         |
+         +-- images -- images for the documentation
+
+
+Use:
+----
+
+See the documentation in the doc subdirectory. The beginning is at
+HTTPClient/doc/index.html .
+
+
+Comments:
+---------
+
+Mail suggestions, comments, bugs, enhancement-requests to:
+
+ronald at innovation.ch
+
+
+  Have fun,
+
+  Ronald
+
diff --git a/HTTPClient/AuthSchemeNotImplException.java b/HTTPClient/AuthSchemeNotImplException.java
new file mode 100644
index 0000000..999d691
--- /dev/null
+++ b/HTTPClient/AuthSchemeNotImplException.java
@@ -0,0 +1,66 @@
+/*
+ * @(#)AuthSchemeNotImplException.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * Signals that the handling of a authorization scheme is not implemented.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class AuthSchemeNotImplException extends ModuleException
+{
+
+    /**
+     * Constructs an AuthSchemeNotImplException with no detail message.
+     * A detail message is a String that describes this particular exception.
+     */
+    public AuthSchemeNotImplException()
+    {
+	super();
+    }
+
+
+    /**
+     * Constructs an AuthSchemeNotImplException class with the specified
+     * detail message.  A detail message is a String that describes this
+     * particular exception.
+     *
+     * @param msg the String containing a detail message
+     */
+    public AuthSchemeNotImplException(String msg)
+    {
+	super(msg);
+    }
+}
diff --git a/HTTPClient/AuthorizationHandler.java b/HTTPClient/AuthorizationHandler.java
new file mode 100644
index 0000000..46c1ab3
--- /dev/null
+++ b/HTTPClient/AuthorizationHandler.java
@@ -0,0 +1,151 @@
+/*
+ * @(#)AuthorizationHandler.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+
+/**
+ * This is the interface that an Authorization handler must implement. You
+ * can implement your own auth handler to add support for auth schemes other
+ * than the ones handled by the default handler, to use a different UI for
+ * soliciting usernames and passwords, or for using an altogether different
+ * way of getting the necessary auth info.
+ *
+ * @see AuthorizationInfo#setAuthHandler(HTTPClient.AuthorizationHandler)
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public interface AuthorizationHandler
+{
+    /**
+     * This method is called whenever a 401 or 407 response is received and
+     * no candidate info is found in the list of known auth info. Usually
+     * this method will query the user for the necessary info.
+     *
+     * <P>If the returned info is not null it will be added to the list of
+     * known info. If the info is valid for more than one (host, port, realm,
+     * scheme) tuple then this method must add the corresponding auth infos
+     * itself.
+     *
+     * <P>This method must check <var>req.allow_ui</var> and only attempt
+     * user interaction if it's <var>true</var>.
+     *
+     * @param challenge the parsed challenge from the server; the host,
+     *                  port, scheme, realm and params are set to the
+     *                  values given by the server in the challenge.
+     * @param req       the request which provoked this response.
+     * @param resp      the full response.
+     * @return the authorization info to use when retrying the request,
+     *         or null if the request is not to be retried. The necessary
+     *         info includes the host, port, scheme and realm as given in
+     *         the <var>challenge</var> parameter, plus either the basic
+     *         cookie or any necessary params.
+     * @exception AuthSchemeNotImplException if the authorization scheme
+     *             in the challenge cannot be handled.
+     * @exception IOException if an exception occurs while processing the
+     *                        challenge
+     */
+    AuthorizationInfo getAuthorization(AuthorizationInfo challenge,
+				       RoRequest req, RoResponse resp)
+	    throws AuthSchemeNotImplException, IOException;
+
+
+    /**
+     * This method is called whenever auth info is chosen from the list of
+     * known info in the AuthorizationInfo class to be sent with a request.
+     * This happens when either auth info is being preemptively sent or if
+     * a 401 response is retrieved and a matching info is found in the list
+     * of known info. The intent of this method is to allow the handler to
+     * fix up the info being sent based on the actual request (e.g. in digest
+     * authentication the digest-uri, nonce and response-digest usually need
+     * to be recalculated).
+     *
+     * @param info      the authorization info retrieved from the list of
+     *                  known info.
+     * @param req       the request this info is targeted for.
+     * @param challenge the authorization challenge received from the server
+     *                  if this is in response to a 401, or null if we are
+     *                  preemptively sending the info.
+     * @param resp      the full 401 response received, or null if we are
+     *                  preemptively sending the info.
+     * @return the authorization info to be sent with the request, or null
+     *         if none is to be sent.
+     * @exception AuthSchemeNotImplException if the authorization scheme
+     *             in the info cannot be handled.
+     * @exception IOException if an exception occurs while fixing up the
+     *                        info
+     */
+    AuthorizationInfo fixupAuthInfo(AuthorizationInfo info, RoRequest req,
+				   AuthorizationInfo challenge, RoResponse resp)
+	    throws AuthSchemeNotImplException, IOException;
+
+
+    /**
+     * Sometimes even non-401 responses will contain headers pertaining to
+     * authorization (such as the "Authentication-Info" header). Therefore
+     * this method is invoked for each response received, even if it is not
+     * a 401 or 407 response. In case of a 401 or 407 response the methods
+     * <code>fixupAuthInfo()</code> and <code>getAuthorization()</code> are
+     * invoked <em>after</em> this method.
+     *
+     * @param resp the full Response
+     * @param req  the Request which provoked this response
+     * @param prev the previous auth info sent, or null if none was sent
+     * @param prxy the previous proxy auth info sent, or null if none was sent
+     * @exception IOException if an exception occurs during the reading of
+     *            the headers.
+     */
+    void handleAuthHeaders(Response resp, RoRequest req,
+			   AuthorizationInfo prev, AuthorizationInfo prxy)
+	    throws IOException;
+
+
+    /**
+     * This method is similar to <code>handleAuthHeaders</code> except that
+     * it is called if any headers in the trailer were sent. This also
+     * implies that it is invoked after any <code>fixupAuthInfo()</code> or
+     * <code>getAuthorization()</code> invocation.
+     *
+     * @param resp the full Response
+     * @param req  the Request which provoked this response
+     * @param prev the previous auth info sent, or null if none was sent
+     * @param prxy the previous proxy auth info sent, or null if none was sent
+     * @exception IOException if an exception occurs during the reading of
+     *            the trailers.
+     * @see #handleAuthHeaders(HTTPClient.Response, HTTPClient.RoRequest, HTTPClient.AuthorizationInfo, HTTPClient.AuthorizationInfo)
+     */
+    void handleAuthTrailers(Response resp, RoRequest req,
+			    AuthorizationInfo prev, AuthorizationInfo prxy)
+	    throws IOException;
+}
diff --git a/HTTPClient/AuthorizationInfo.java b/HTTPClient/AuthorizationInfo.java
new file mode 100644
index 0000000..208d5f4
--- /dev/null
+++ b/HTTPClient/AuthorizationInfo.java
@@ -0,0 +1,1222 @@
+/*
+ * @(#)AuthorizationInfo.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+
+/**
+ * Holds the information for an authorization response.
+ *
+ * <P>There are 7 fields which make up this class: host, port, scheme,
+ * realm, cookie, params, and extra_info. The host and port select which
+ * server the info will be sent to. The realm is server specified string
+ * which groups various URLs under a given server together and which is
+ * used to select the correct info when a server issues an auth challenge;
+ * for schemes which don't use a realm (such as "NTLM", "PEM", and
+ * "Kerberos") the realm must be the empty string (""). The scheme is the
+ * authorization scheme used (such as "Basic" or "Digest").
+ *
+ * <P>There are basically two formats used for the Authorization header,
+ * the one used by the "Basic" scheme and derivatives, and the one used by
+ * the "Digest" scheme and derivatives. The first form contains just the
+ * the scheme and a "cookie":
+ * 
+ * <PRE>    Authorization: Basic aGVsbG86d29ybGQ=</PRE>
+ * 
+ * The second form contains the scheme followed by a number of parameters
+ * in the form of name=value pairs:
+ * 
+ * <PRE>    Authorization: Digest username="hello", realm="test", nonce="42", ...</PRE>
+ * 
+ * The two fields "cookie" and "params" correspond to these two forms.
+ * <A HREF="#toString()">toString()</A> is used by the AuthorizationModule
+ * when generating the Authorization header and will format the info
+ * accordingly. Note that "cookie" and "params" are mutually exclusive: if
+ * the cookie field is non-null then toString() will generate the first
+ * form; otherwise it will generate the second form.
+ *
+ * <P>In some schemes "extra" information needs to be kept which doesn't
+ * appear directly in the Authorization header. An example of this are the
+ * A1 and A2 strings in the Digest scheme. Since all elements in the params
+ * field will appear in the Authorization header this field can't be used
+ * for storing such info. This is what the extra_info field is for. It is
+ * an arbitrary object which can be manipulated by the corresponding
+ * setExtraInfo() and getExtraInfo() methods, but which will not be printed
+ * by toString().
+ *
+ * <P>The addXXXAuthorization(), removeXXXAuthorization(), and
+ * getAuthorization() methods manipulate and query an internal list of
+ * AuthorizationInfo instances. There can be only one instance per host,
+ * port, scheme, and realm combination (see <A HREF="#equals">equals()</A>).
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.1
+ */
+public class AuthorizationInfo implements Cloneable
+{
+    // class fields
+
+    /** Holds the list of lists of authorization info structures */
+    private static Hashtable     CntxtList = new Hashtable();
+
+    /** A pointer to the handler to be called when we need authorization info */
+    private static AuthorizationHandler
+				 AuthHandler = new DefaultAuthHandler();
+
+    static
+    {
+	CntxtList.put(HTTPConnection.getDefaultContext(), new Hashtable());
+    }
+
+
+    // the instance oriented stuff
+
+    /** the host (lowercase) */
+    private String host;
+
+    /** the port */
+    private int port;
+
+    /** the scheme. (e.g. "Basic")
+     * Note: don't lowercase because some buggy servers use a case-sensitive
+     * match */
+    private String scheme;
+
+    /** the realm */
+    private String realm;
+
+    /** the string used for the "Basic", "NTLM", and other authorization
+     *  schemes which don't use parameters  */
+    private String cookie;
+
+    /** any parameters */
+    private NVPair[] auth_params = new NVPair[0];
+
+    /** additional info which won't be displayed in the toString() */
+    private Object extra_info = null;
+
+    /** a list of paths where this realm has been known to be required */
+    private String[] paths = new String[0];
+
+
+    // Constructors
+
+    /**
+     * Creates an new info structure for the specified host and port.
+     *
+     * @param host   the host
+     * @param port   the port
+     */
+    AuthorizationInfo(String host, int port)
+    {
+	this.host = host.trim().toLowerCase();
+	this.port = port;
+    }
+
+
+    /**
+     * Creates a new info structure for the specified host and port with the
+     * specified scheme, realm, params. The cookie is set to null.
+     *
+     * @param host   the host
+     * @param port   the port
+     * @param scheme the scheme
+     * @param realm  the realm
+     * @param params the parameters as an array of name/value pairs, or null
+     * @param info   arbitrary extra info, or null
+     */
+    public AuthorizationInfo(String host, int port, String scheme,
+			     String realm, NVPair params[], Object info)
+    {
+	this.scheme = scheme.trim();
+	this.host   = host.trim().toLowerCase();
+	this.port   = port;
+	this.realm  = realm;
+	this.cookie = null;
+
+	if (params != null)
+	    auth_params = Util.resizeArray(params, params.length);
+
+	this.extra_info   = info;
+    }
+
+
+    /**
+     * Creates a new info structure for the specified host and port with the
+     * specified scheme, realm and cookie. The params is set to a zero-length
+     * array, and the extra_info is set to null.
+     *
+     * @param host   the host
+     * @param port   the port
+     * @param scheme the scheme
+     * @param realm  the realm
+     * @param cookie for the "Basic" scheme this is the base64-encoded
+     *               username/password; for the "NTLM" scheme this is the
+     *               base64-encoded username/password message.
+     */
+    public AuthorizationInfo(String host, int port, String scheme,
+			     String realm, String cookie)
+    {
+	this.scheme = scheme.trim();
+	this.host   = host.trim().toLowerCase();
+	this.port   = port;
+	this.realm  = realm;
+	if (cookie != null)
+	    this.cookie = cookie.trim();
+	else
+	    this.cookie = null;
+    }
+
+
+    /**
+     * Creates a new copy of the given AuthorizationInfo.
+     *
+     * @param templ the info to copy
+     */
+    AuthorizationInfo(AuthorizationInfo templ)
+    {
+	this.scheme = templ.scheme;
+	this.host   = templ.host;
+	this.port   = templ.port;
+	this.realm  = templ.realm;
+	this.cookie = templ.cookie;
+
+	this.auth_params =
+		Util.resizeArray(templ.auth_params, templ.auth_params.length);
+
+	this.extra_info  = templ.extra_info;
+    }
+
+
+    // Class Methods
+
+    /**
+     * Set's the authorization handler. This handler is called whenever
+     * the server requests authorization and no entry for the requested
+     * scheme and realm can be found in the list. The handler must implement
+     * the AuthorizationHandler interface.
+     *
+     * <P>If no handler is set then a {@link DefaultAuthHandler default
+     * handler} is used. This handler currently only handles the "Basic" and
+     * "Digest" schemes and brings up a popup which prompts for the username
+     * and password.
+     *
+     * <P>The default handler can be disabled by setting the auth handler to
+     * <var>null</var>.
+     *
+     * @param  handler the new authorization handler
+     * @return the old authorization handler
+     * @see    AuthorizationHandler
+     */
+    public static AuthorizationHandler
+		    setAuthHandler(AuthorizationHandler handler)
+    {
+	AuthorizationHandler tmp = AuthHandler;
+	AuthHandler = handler;
+
+	return tmp;
+    }
+
+
+    /**
+     * Get's the current authorization handler.
+     *
+     * @return the current authorization handler, or null if none is set.
+     * @see    AuthorizationHandler
+     */
+    public static AuthorizationHandler getAuthHandler()
+    {
+	return AuthHandler;
+    }
+
+
+    /**
+     * Searches for the authorization info using the given host, port,
+     * scheme and realm. The context is the default context.
+     *
+     * @param  host         the host
+     * @param  port         the port
+     * @param  scheme       the scheme
+     * @param  realm        the realm
+     * @return a pointer to the authorization data or null if not found
+     */
+    public static AuthorizationInfo getAuthorization(
+						String host, int port,
+						String scheme, String realm)
+    {
+	return getAuthorization(host, port, scheme, realm,
+				HTTPConnection.getDefaultContext());
+    }
+
+
+    /**
+     * Searches for the authorization info in the given context using the
+     * given host, port, scheme and realm.
+     *
+     * @param  host         the host
+     * @param  port         the port
+     * @param  scheme       the scheme
+     * @param  realm        the realm
+     * @param  context      the context this info is associated with
+     * @return a pointer to the authorization data or null if not found
+     */
+    public static synchronized AuthorizationInfo getAuthorization(
+						String host, int port,
+						String scheme, String realm,
+						Object context)
+    {
+	Hashtable AuthList = Util.getList(CntxtList, context);
+
+	AuthorizationInfo auth_info =
+	    new AuthorizationInfo(host, port, scheme, realm, (NVPair[]) null,
+				  null);
+
+	return (AuthorizationInfo) AuthList.get(auth_info);
+    }
+
+
+    /**
+     * Queries the AuthHandler for authorization info. It also adds this
+     * info to the list.
+     *
+     * @param  auth_info  any info needed by the AuthHandler; at a minimum the
+     *                    host, scheme and realm should be set.
+     * @param  req        the request which initiated this query
+     * @param  resp       the full response
+     * @return a structure containing the requested info, or null if either
+     *	       no AuthHandler is set or the user canceled the request.
+     * @exception AuthSchemeNotImplException if this is thrown by
+     *                                            the AuthHandler.
+     */
+    static AuthorizationInfo queryAuthHandler(AuthorizationInfo auth_info,
+					      RoRequest req, RoResponse resp)
+	throws AuthSchemeNotImplException, IOException
+    {
+	if (AuthHandler == null)
+	    return null;
+
+	AuthorizationInfo new_info =
+		    AuthHandler.getAuthorization(auth_info, req, resp);
+	if (new_info != null)
+	{
+	    if (req != null)
+		addAuthorization((AuthorizationInfo) new_info.clone(),
+				 req.getConnection().getContext());
+	    else
+		addAuthorization((AuthorizationInfo) new_info.clone(),
+				 HTTPConnection.getDefaultContext());
+	}
+
+	return new_info;
+    }
+
+
+    /**
+     * Searches for the authorization info using the host, port, scheme and
+     * realm from the given info struct. If not found it queries the
+     * AuthHandler (if set).
+     *
+     * @param  auth_info    the AuthorizationInfo
+     * @param  req          the request which initiated this query
+     * @param  resp         the full response
+     * @param  query_auth_h if true, query the auth-handler if no info found.
+     * @return a pointer to the authorization data or null if not found
+     * @exception AuthSchemeNotImplException If thrown by the AuthHandler.
+     */
+    static synchronized AuthorizationInfo getAuthorization(
+				    AuthorizationInfo auth_info, RoRequest req,
+				    RoResponse resp, boolean query_auth_h)
+	throws AuthSchemeNotImplException, IOException
+    {
+	Hashtable AuthList;
+	if (req != null)
+	    AuthList = Util.getList(CntxtList, req.getConnection().getContext());
+	else
+	    AuthList = Util.getList(CntxtList, HTTPConnection.getDefaultContext());
+
+	AuthorizationInfo new_info =
+	    (AuthorizationInfo) AuthList.get(auth_info);
+
+	if (new_info == null  &&  query_auth_h)
+	    new_info = queryAuthHandler(auth_info, req, resp);
+
+	return new_info;
+    }
+
+
+    /**
+     * Searches for the authorization info given a host, port, scheme and
+     * realm. Queries the AuthHandler if not found in list.
+     *
+     * @param  host         the host
+     * @param  port         the port
+     * @param  scheme       the scheme
+     * @param  realm        the realm
+     * @param  req          the request which initiated this query
+     * @param  resp         the full response
+     * @param  query_auth_h if true, query the auth-handler if no info found.
+     * @return a pointer to the authorization data or null if not found
+     * @exception AuthSchemeNotImplException If thrown by the AuthHandler.
+     */
+    static AuthorizationInfo getAuthorization(String host, int port,
+					      String scheme, String realm,
+					      RoRequest req, RoResponse resp,
+					      boolean query_auth_h)
+	throws AuthSchemeNotImplException, IOException
+    {
+	return getAuthorization(new AuthorizationInfo(host, port, scheme,
+						realm, (NVPair[]) null, null),
+				req, resp, query_auth_h);
+    }
+
+
+    /**
+     * Adds an authorization entry to the list using the default context.
+     * If an entry for the specified scheme and realm already exists then
+     * its cookie and params are replaced with the new data.
+     *
+     * @param auth_info the AuthorizationInfo to add
+     */
+    public static void addAuthorization(AuthorizationInfo auth_info)
+    {
+	addAuthorization(auth_info, HTTPConnection.getDefaultContext());
+    }
+
+
+    /**
+     * Adds an authorization entry to the list. If an entry for the
+     * specified scheme and realm already exists then its cookie and
+     * params are replaced with the new data.
+     *
+     * @param auth_info the AuthorizationInfo to add
+     * @param context   the context to associate this info with
+     */
+    public static void addAuthorization(AuthorizationInfo auth_info,
+					Object context)
+    {
+	Hashtable AuthList = Util.getList(CntxtList, context);
+
+	// merge path list
+	AuthorizationInfo old_info =
+			    (AuthorizationInfo) AuthList.get(auth_info);
+	if (old_info != null)
+	{
+	    int ol = old_info.paths.length,
+		al = auth_info.paths.length;
+
+	    if (al == 0)
+		auth_info.paths = old_info.paths;
+	    else
+	    {
+		auth_info.paths = Util.resizeArray(auth_info.paths, al+ol);
+		System.arraycopy(old_info.paths, 0, auth_info.paths, al, ol);
+	    }
+	}
+
+	AuthList.put(auth_info, auth_info);
+    }
+
+
+    /**
+     * Adds an authorization entry to the list using the default context.
+     * If an entry for the specified scheme and realm already exists then
+     * its cookie and params are replaced with the new data.
+     *
+     * @param host   the host
+     * @param port   the port
+     * @param scheme the scheme
+     * @param realm  the realm
+     * @param cookie the cookie
+     * @param params an array of name/value pairs of parameters
+     * @param info   arbitrary extra auth info
+     */
+    public static void addAuthorization(String host, int port, String scheme,
+					String realm, String cookie,
+					NVPair params[], Object info)
+    {
+	addAuthorization(host, port, scheme, realm, cookie, params, info,
+			 HTTPConnection.getDefaultContext());
+    }
+
+
+    /**
+     * Adds an authorization entry to the list. If an entry for the
+     * specified scheme and realm already exists then its cookie and
+     * params are replaced with the new data.
+     *
+     * @param host    the host
+     * @param port    the port
+     * @param scheme  the scheme
+     * @param realm   the realm
+     * @param cookie  the cookie
+     * @param params  an array of name/value pairs of parameters
+     * @param info    arbitrary extra auth info
+     * @param context the context to associate this info with
+     */
+    public static void addAuthorization(String host, int port, String scheme,
+					String realm, String cookie,
+					NVPair params[], Object info,
+					Object context)
+    {
+	AuthorizationInfo auth =
+	    new AuthorizationInfo(host, port, scheme, realm, cookie);
+	if (params != null  &&  params.length > 0)
+	    auth.auth_params = Util.resizeArray(params, params.length);
+	auth.extra_info = info;
+
+	addAuthorization(auth, context);
+    }
+
+
+    /**
+     * Adds an authorization entry for the "Basic" authorization scheme to
+     * the list using the default context. If an entry already exists for
+     * the "Basic" scheme and the specified realm then it is overwritten.
+     *
+     * @param host   the host
+     * @param port   the port
+     * @param realm  the realm
+     * @param user   the username
+     * @param passwd the password
+     */
+    public static void addBasicAuthorization(String host, int port,
+					     String realm, String user,
+					     String passwd)
+    {
+	addAuthorization(host, port, "Basic", realm,
+			 Codecs.base64Encode(user + ":" + passwd),
+			 (NVPair[]) null, null);
+    }
+
+
+    /**
+     * Adds an authorization entry for the "Basic" authorization scheme to
+     * the list. If an entry already exists for the "Basic" scheme and the
+     * specified realm then it is overwritten.
+     *
+     * @param host    the host
+     * @param port    the port
+     * @param realm   the realm
+     * @param user    the username
+     * @param passwd  the password
+     * @param context the context to associate this info with
+     */
+    public static void addBasicAuthorization(String host, int port,
+					     String realm, String user,
+					     String passwd, Object context)
+    {
+	addAuthorization(host, port, "Basic", realm,
+			 Codecs.base64Encode(user + ":" + passwd),
+			 (NVPair[]) null, null, context);
+    }
+
+
+    /**
+     * Adds an authorization entry for the "Digest" authorization scheme to
+     * the list using the default context. If an entry already exists for the
+     * "Digest" scheme and the specified realm then it is overwritten.
+     *
+     * @param host   the host
+     * @param port   the port
+     * @param realm  the realm
+     * @param user   the username
+     * @param passwd the password
+     */
+    public static void addDigestAuthorization(String host, int port,
+					      String realm, String user,
+					      String passwd)
+    {
+	addDigestAuthorization(host, port, realm, user, passwd,
+			       HTTPConnection.getDefaultContext());
+    }
+
+
+    /**
+     * Adds an authorization entry for the "Digest" authorization scheme to
+     * the list. If an entry already exists for the "Digest" scheme and the
+     * specified realm then it is overwritten.
+     *
+     * @param host    the host
+     * @param port    the port
+     * @param realm   the realm
+     * @param user    the username
+     * @param passwd  the password
+     * @param context the context to associate this info with
+     */
+    public static void addDigestAuthorization(String host, int port,
+					      String realm, String user,
+					      String passwd, Object context)
+    {
+	AuthorizationInfo prev =
+			getAuthorization(host, port, "Digest", realm, context);
+	NVPair[] params;
+
+	if (prev == null)
+	{
+	    params = new NVPair[4];
+	    params[0] = new NVPair("username", user);
+	    params[1] = new NVPair("uri", "");
+	    params[2] = new NVPair("nonce", "");
+	    params[3] = new NVPair("response", "");
+	}
+	else
+	{
+	    params = prev.getParams();
+	    for (int idx=0; idx<params.length; idx++)
+	    {
+		if (params[idx].getName().equalsIgnoreCase("username"))
+		{
+		    params[idx] = new NVPair("username", user);
+		    break;
+		}
+	    }
+	}
+
+	String[] extra = { MD5.hexDigest(user + ":" + realm + ":" + passwd),
+			   null, null };
+
+	addAuthorization(host, port, "Digest", realm, null, params, extra,
+			 context);
+    }
+
+
+    /**
+     * Removes an authorization entry from the list using the default context.
+     * If no entry for the specified host, port, scheme and realm exists then
+     * this does nothing.
+     *
+     * @param auth_info the AuthorizationInfo to remove
+     */
+    public static void removeAuthorization(AuthorizationInfo auth_info)
+    {
+	removeAuthorization(auth_info, HTTPConnection.getDefaultContext());
+    }
+
+
+    /**
+     * Removes an authorization entry from the list. If no entry for the
+     * specified host, port, scheme and realm exists then this does nothing.
+     *
+     * @param auth_info the AuthorizationInfo to remove
+     * @param context   the context this info is associated with
+     */
+    public static void removeAuthorization(AuthorizationInfo auth_info,
+					   Object context)
+    {
+	Hashtable AuthList = Util.getList(CntxtList, context);
+	AuthList.remove(auth_info);
+    }
+
+
+    /**
+     * Removes an authorization entry from the list using the default context.
+     * If no entry for the specified host, port, scheme and realm exists then
+     * this does nothing.
+     *
+     * @param host   the host
+     * @param port   the port
+     * @param scheme the scheme
+     * @param realm  the realm
+     */
+    public static void removeAuthorization(String host, int port, String scheme,
+					   String realm)
+    {
+	removeAuthorization(
+	    new AuthorizationInfo(host, port, scheme, realm, (NVPair[]) null,
+				  null));
+    }
+
+
+    /**
+     * Removes an authorization entry from the list. If no entry for the
+     * specified host, port, scheme and realm exists then this does nothing.
+     *
+     * @param host    the host
+     * @param port    the port
+     * @param scheme  the scheme
+     * @param realm   the realm
+     * @param context the context this info is associated with
+     */
+    public static void removeAuthorization(String host, int port, String scheme,
+					   String realm, Object context)
+    {
+	removeAuthorization(
+	    new AuthorizationInfo(host, port, scheme, realm, (NVPair[]) null,
+				  null), context);
+    }
+
+
+    /**
+     * Tries to find the candidate in the current list of auth info for the
+     * given request. The paths associated with each auth info are examined,
+     * and the one with either the nearest direct parent or child is chosen.
+     * This is used for preemptively sending auth info.
+     *
+     * @param  req  the Request
+     * @return an AuthorizationInfo containing the info for the best match,
+     *         or null if none found.
+     */
+    static AuthorizationInfo findBest(RoRequest req)
+    {
+	String path = Util.getPath(req.getRequestURI());
+	String host = req.getConnection().getHost();
+	int    port = req.getConnection().getPort();
+
+
+	// First search for an exact match
+
+	Hashtable AuthList =
+		    Util.getList(CntxtList, req.getConnection().getContext());
+	Enumeration list = AuthList.elements();
+	while (list.hasMoreElements())
+	{
+	    AuthorizationInfo info = (AuthorizationInfo) list.nextElement();
+
+	    if (!info.host.equals(host)  ||  info.port != port)
+		continue;
+
+	    String[] paths = info.paths;
+	    for (int idx=0; idx<paths.length; idx++)
+	    {
+		if (path.equals(paths[idx]))
+		    return info;
+	    }
+	}
+
+
+	// Now find the closest parent or child
+
+	AuthorizationInfo best = null;
+	String base = path.substring(0, path.lastIndexOf('/')+1);
+	int    min  = Integer.MAX_VALUE;
+
+	list = AuthList.elements();
+	while (list.hasMoreElements())
+	{
+	    AuthorizationInfo info = (AuthorizationInfo) list.nextElement();
+
+	    if (!info.host.equals(host)  ||  info.port != port)
+		continue;
+
+	    String[] paths = info.paths;
+	    for (int idx=0; idx<paths.length; idx++)
+	    {
+		// strip the last path segment, leaving a trailing "/"
+		String ibase =
+			paths[idx].substring(0, paths[idx].lastIndexOf('/')+1);
+
+		if (base.equals(ibase))
+		    return info;
+
+		if (base.startsWith(ibase))		// found a parent
+		{
+		    int num_seg = 0, pos = ibase.length()-1;
+		    while ((pos = base.indexOf('/', pos+1)) != -1)  num_seg++;
+
+		    if (num_seg < min)
+		    {
+			min  = num_seg;
+			best = info;
+		    }
+		}
+		else if (ibase.startsWith(base))	// found a child
+		{
+		    int num_seg = 0, pos = base.length();
+		    while ((pos = ibase.indexOf('/', pos+1)) != -1)  num_seg++;
+
+		    if (num_seg < min)
+		    {
+			min  = num_seg;
+			best = info;
+		    }
+		}
+	    }
+	}
+
+	return best;
+    }
+
+
+    /**
+     * Adds the path from the given resource to our path list. The path
+     * list is used for deciding when to preemptively send auth info.
+     *
+     * @param resource the resource from which to extract the path
+     */
+    public synchronized void addPath(String resource)
+    {
+	String path = Util.getPath(resource);
+
+	// First check that we don't already have this one
+	for (int idx=0; idx<paths.length; idx++)
+	    if (paths[idx].equals(path)) return;
+
+	// Ok, add it
+	paths = Util.resizeArray(paths, paths.length+1);
+	paths[paths.length-1] = path;
+    }
+
+
+    /**
+     * Parses the authentication challenge(s) into an array of new info
+     * structures for the specified host and port.
+     *
+     * @param challenge a string containing authentication info. This must
+     *                  have the same format as value part of a
+     *                  WWW-authenticate response header field, and may
+     *                  contain multiple authentication challenges.
+     * @param req       the original request.
+     * @exception ProtocolException if any error during the parsing occurs.
+     */
+    static AuthorizationInfo[] parseAuthString(String challenge, RoRequest req,
+					       RoResponse resp)
+	    throws ProtocolException
+    {
+	int    beg = 0,
+	       end = 0;
+	char[] buf = challenge.toCharArray();
+	int    len = buf.length;
+	int[]  pos_ref = new int[2];
+
+	AuthorizationInfo auth_arr[] = new AuthorizationInfo[0],
+			  curr;
+
+	while (Character.isWhitespace(buf[len-1]))  len--;
+
+	while (true)			// get all challenges
+	{
+	    // get scheme
+	    beg = Util.skipSpace(buf, beg);
+	    if (beg == len)  break;
+
+	    end = Util.findSpace(buf, beg+1);
+
+	    int sts;
+	    try
+		{ sts = resp.getStatusCode(); }
+	    catch (IOException ioe)
+		{ throw new ProtocolException(ioe.toString()); }
+	    if (sts == 401)
+		curr = new AuthorizationInfo(req.getConnection().getHost(),
+					     req.getConnection().getPort());
+	    else
+		curr = new AuthorizationInfo(req.getConnection().getProxyHost(),
+					     req.getConnection().getProxyPort());
+
+	    /* Hack for schemes like NTLM which don't have any params or cookie.
+	     * Mickeysoft, hello? What were you morons thinking here? I suppose
+	     * you weren't, as usual, huh?
+	     */
+	    if (buf[end-1] == ',')
+	    {
+		curr.scheme = challenge.substring(beg, end-1);
+		beg = end;
+	    }
+	    else
+	    {
+		curr.scheme = challenge.substring(beg, end);
+
+		pos_ref[0] = beg; pos_ref[1] = end;
+		Vector params = parseParams(challenge, buf, pos_ref, len, curr);
+		beg = pos_ref[0]; end = pos_ref[1];
+
+		if (!params.isEmpty())
+		{
+		    curr.auth_params = new NVPair[params.size()];
+		    params.copyInto(curr.auth_params);
+		}
+	    }
+
+	    if (curr.realm == null)
+		/* Can't do this if we're supposed to allow for broken schemes
+		 * such as NTLM, Kerberos, and PEM.
+		 *
+		throw new ProtocolException("Bad Authentication header "
+		    + "format: " + challenge + "\nNo realm value found");
+		 */
+		curr.realm = "";
+
+	    auth_arr = Util.resizeArray(auth_arr, auth_arr.length+1);
+	    auth_arr[auth_arr.length-1] = curr;
+	}
+
+	return auth_arr;
+    }
+
+    private static final Vector parseParams(String challenge, char[] buf,
+					    int[] pos_ref, int len,
+					    AuthorizationInfo curr)
+	    throws ProtocolException
+    {
+	int beg = pos_ref[0];
+	int end = pos_ref[1];
+
+	// get auth-parameters
+	boolean first = true;
+	Vector params = new Vector();
+	while (true)
+	{
+	    beg = Util.skipSpace(buf, end);
+	    if (beg == len)  break;
+
+	    if (!first)				// expect ","
+	    {
+		if (buf[beg] != ',')
+		    throw new ProtocolException("Bad Authentication header "
+						+ "format: '" + challenge +
+						"'\nExpected \",\" at position "+
+						beg);
+
+		beg = Util.skipSpace(buf, beg+1);	// find param name
+		if (beg == len)  break;
+		if (buf[beg] == ',')	// skip empty params
+		{
+		    end = beg;
+		    continue;
+		}
+	    }
+
+	    int pstart = beg;
+
+	    // extract name
+	    end = beg + 1;
+	    while (end < len  &&  !Character.isWhitespace(buf[end]) &&
+		   buf[end] != '='  &&  buf[end] != ',')
+		end++;
+
+	    // hack to deal with schemes which use cookies in challenge
+	    if (first  &&
+		(end == len   ||  buf[end] == '='  &&
+		(end+1 == len  ||  (buf[end+1] == '='  &&  end+2 == len))))
+	    {
+		curr.cookie = challenge.substring(beg, len);
+		beg = len;
+		break;
+	    }
+
+	    String param_name = challenge.substring(beg, end),
+		   param_value;
+
+	    beg = Util.skipSpace(buf, end);	// find "=" or ","
+
+	    if (beg < len  &&  buf[beg] != '='  &&  buf[beg] != ','  ||
+		/* This deals with the M$ crap */
+		!first  &&  (beg == len   ||  buf[beg] == ','))
+	    {
+		// It's not a param, but another challenge
+		beg = pstart;
+		break;
+	    }
+
+
+	    if (beg < len  &&  buf[beg] == '=')		// we have a value
+	    {
+		beg = Util.skipSpace(buf, beg+1);
+		if (beg == len)
+		    throw new ProtocolException("Bad Authentication header "
+						+ "format: " + challenge +
+						"\nUnexpected EOL after token" +
+						" at position " + (end-1));
+		if (buf[beg] != '"')	// it's a token
+		{
+		    end = Util.skipToken(buf, beg);
+		    if (end == beg)
+			throw new ProtocolException("Bad Authentication header "
+			    + "format: " + challenge + "\nToken expected at " +
+			    "position " + beg);
+		    param_value = challenge.substring(beg, end);
+		}
+		else			// it's a quoted-string
+		{
+		    end = beg++;
+		    do
+			end = challenge.indexOf('"', end+1);
+		    while (end != -1  &&  challenge.charAt(end-1) == '\\');
+		    if (end == -1)
+			throw new ProtocolException("Bad Authentication header "
+			    + "format: " + challenge + "\nClosing <\"> for "
+			    + "quoted-string starting at position " + beg
+			    + " not found");
+		    param_value =
+			Util.dequoteString(challenge.substring(beg, end));
+		    end++;
+		}
+	    }
+	    else				// this is not strictly allowed
+		param_value = null;
+
+	    if (param_name.equalsIgnoreCase("realm"))
+		curr.realm = param_value;
+	    else
+		params.addElement(new NVPair(param_name, param_value));
+
+	    first = false;
+	}
+
+	pos_ref[0] = beg;
+	pos_ref[1] = end;
+	return params;
+    }
+
+
+    // Instance Methods
+
+    /**
+     * Get the host.
+     *
+     * @return a string containing the host name.
+     */
+    public final String getHost()
+    {
+	return host;
+    }
+
+
+    /**
+     * Get the port.
+     *
+     * @return an int containing the port number.
+     */
+    public final int getPort()
+    {
+	return port;
+    }
+
+
+    /**
+     * Get the scheme.
+     *
+     * @return a string containing the scheme.
+     */
+    public final String getScheme()
+    {
+	return scheme;
+    }
+
+
+    /**
+     * Get the realm.
+     *
+     * @return a string containing the realm.
+     */
+    public final String getRealm()
+    {
+	return realm;
+    }
+
+
+    /**
+     * Get the cookie
+     *
+     * @return the cookie String
+     * @since V0.3-1
+     */
+    public final String getCookie()
+    {
+	return cookie;
+    }
+
+
+    /**
+     * Set the cookie
+     *
+     * @param cookie the new cookie
+     * @since V0.3-1
+     */
+    public final void setCookie(String cookie)
+    {
+	this.cookie = cookie;
+    }
+
+
+    /**
+     * Get the authentication parameters.
+     *
+     * @return an array of name/value pairs.
+     */
+    public final NVPair[] getParams()
+    {
+	return Util.resizeArray(auth_params, auth_params.length);
+    }
+
+
+    /**
+     * Set the authentication parameters.
+     *
+     * @param an array of name/value pairs.
+     */
+    public final void setParams(NVPair[] params)
+    {
+	if (params != null)
+	    auth_params = Util.resizeArray(params, params.length);
+	else
+	    auth_params = new NVPair[0];
+    }
+
+
+    /**
+     * Get the extra info.
+     *
+     * @return the extra_info object
+     */
+    public final Object getExtraInfo()
+    {
+	return extra_info;
+    }
+
+
+    /**
+     * Set the extra info.
+     *
+     * @param info the extra info
+     */
+    public final void setExtraInfo(Object info)
+    {
+	extra_info = info;
+    }
+
+
+    /**
+     * Constructs a string containing the authorization info. The format
+     * is that of the http Authorization header.
+     *
+     * @return a String containing all info.
+     */
+    public String toString()
+    {
+	StringBuffer field = new StringBuffer(100);
+
+	field.append(scheme);
+	field.append(" ");
+
+	if (cookie != null)
+	{
+	    field.append(cookie);
+	}
+	else
+	{
+	    if (realm.length() > 0)
+	    {
+		field.append("realm=\"");
+		field.append(Util.quoteString(realm, "\\\""));
+		field.append('"');
+	    }
+
+	    for (int idx=0; idx<auth_params.length; idx++)
+	    {
+		field.append(',');
+		field.append(auth_params[idx].getName());
+		if (auth_params[idx].getValue() != null)
+		{
+		    field.append("=\"");
+		    field.append(
+			Util.quoteString(auth_params[idx].getValue(), "\\\""));
+		    field.append('"');
+		}
+	    }
+	}
+
+	return field.toString();
+    }
+
+
+    /**
+     * Produces a hash code based on host, scheme and realm. Port is not
+     * included for simplicity (and because it probably won't make much
+     * difference). Used in the AuthorizationInfo.AuthList hash table.
+     *
+     * @return the hash code
+     */
+    public int hashCode()
+    {
+	return (host+scheme.toLowerCase()+realm).hashCode();
+    }
+
+    /**
+     * Two AuthorizationInfos are considered equal if their host, port,
+     * scheme and realm match. Used in the AuthorizationInfo.AuthList hash
+     * table.
+     *
+     * @param obj another AuthorizationInfo against which this one is
+     *            to be compared.
+     * @return true if they match in the above mentioned fields; false
+     *              otherwise.
+     */
+    public boolean equals(Object obj)
+    {
+	if ((obj != null)  &&  (obj instanceof AuthorizationInfo))
+	{
+	    AuthorizationInfo auth = (AuthorizationInfo) obj;
+	    if (host.equals(auth.host)  &&
+		(port == auth.port)  &&
+		scheme.equalsIgnoreCase(auth.scheme)  &&
+		realm.equals(auth.realm))
+		    return true;
+	}
+	return false;
+    }
+
+
+    /**
+     * @return a clone of this AuthorizationInfo using a deep copy
+     */
+    public Object clone()
+    {
+	AuthorizationInfo ai;
+	try
+	{
+	    ai = (AuthorizationInfo) super.clone();
+	    ai.auth_params = Util.resizeArray(auth_params, auth_params.length);
+	    try
+	    {
+		// ai.extra_info  = extra_info.clone();
+		ai.extra_info = extra_info.getClass().getMethod("clone", (Class[])null).
+				invoke(extra_info, (Object)null);
+	    }
+	    catch (Throwable t)
+		{ }
+	    ai.paths = new String[paths.length];
+	    System.arraycopy(paths, 0, ai.paths, 0, paths.length);
+	}
+	catch (CloneNotSupportedException cnse)
+	    { throw new InternalError(cnse.toString()); /* shouldn't happen */ }
+
+	return ai;
+    }
+}
diff --git a/HTTPClient/AuthorizationModule.java b/HTTPClient/AuthorizationModule.java
new file mode 100644
index 0000000..977761a
--- /dev/null
+++ b/HTTPClient/AuthorizationModule.java
@@ -0,0 +1,629 @@
+/*
+ * @(#)AuthorizationModule.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.util.Hashtable;
+
+
+/**
+ * This module handles authentication requests. Authentication info is
+ * preemptively sent if any suitable candidate info is available. If a
+ * request returns with an appropriate status (401 or 407) then the
+ * necessary info is sought from the AuthenticationInfo class.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class AuthorizationModule implements HTTPClientModule
+{
+    /** This holds the current Proxy-Authorization-Info for each
+        HTTPConnection */
+    private static Hashtable proxy_cntxt_list = new Hashtable();
+
+    /** a list of deferred authorization retries (used with
+	Response.retryRequest()) */
+    private static Hashtable deferred_auth_list = new Hashtable();
+
+    /** counters for challenge and auth-info lists */
+    private int	auth_lst_idx,
+		prxy_lst_idx,
+		auth_scm_idx,
+		prxy_scm_idx;
+
+    /** the last auth info sent, if any */
+    private AuthorizationInfo auth_sent;
+    private AuthorizationInfo prxy_sent;
+
+    /** is the info in auth_sent a preemtive guess or the result of a 4xx */
+    private boolean auth_from_4xx;
+    private boolean prxy_from_4xx;
+
+    /** guard against bugs on both our side and the server side */
+    private int num_tries;
+
+    /** used for deferred authoriation retries */
+    private Request  saved_req;
+    private Response saved_resp;
+
+
+    // Constructors
+
+    /**
+     * Initialize counters for challenge and auth-info lists.
+     */
+    AuthorizationModule()
+    {
+	auth_lst_idx = 0;
+	prxy_lst_idx = 0;
+	auth_scm_idx = 0;
+	prxy_scm_idx = 0;
+
+	auth_sent = null;
+	prxy_sent = null;
+
+	auth_from_4xx = false;
+	prxy_from_4xx = false;
+
+	num_tries  = 0;
+	saved_req  = null;
+	saved_resp = null;
+    }
+
+
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+		throws IOException, AuthSchemeNotImplException
+    {
+	HTTPConnection con = req.getConnection();
+	AuthorizationHandler auth_handler = AuthorizationInfo.getAuthHandler();
+	AuthorizationInfo guess;
+	NVPair[] hdrs = req.getHeaders();
+	int rem_idx = -1;
+
+
+	// check for retries
+
+	HttpOutputStream out = req.getStream();
+	if (out != null  &&  deferred_auth_list.get(out) != null)
+	{
+	    copyFrom((AuthorizationModule) deferred_auth_list.remove(out));
+	    req.copyFrom(saved_req);
+
+	    Log.write(Log.AUTH, "AuthM: Handling deferred auth challenge");
+
+	    handle_auth_challenge(req, saved_resp);
+
+	    if (auth_sent != null)
+		Log.write(Log.AUTH, "AuthM: Sending request with " +
+				    "Authorization '" + auth_sent + "'");
+	    else
+		Log.write(Log.AUTH, "AuthM: Sending request with " +
+				    "Proxy-Authorization '" + prxy_sent +
+				    "'");
+
+	    return REQ_RESTART;
+	}
+
+
+	// Preemptively send proxy authorization info
+
+	Proxy: if (con.getProxyHost() != null  &&  !prxy_from_4xx)
+	{
+	    // first remove any Proxy-Auth header that still may be around
+
+	    for (int idx=0; idx<hdrs.length; idx++)
+	    {
+		if (hdrs[idx].getName().equalsIgnoreCase("Proxy-Authorization"))
+		{
+		    rem_idx = idx;
+		    break;
+		}
+	    }
+	    Hashtable proxy_auth_list = Util.getList(proxy_cntxt_list,
+						     con.getContext());
+	    guess = (AuthorizationInfo) proxy_auth_list.get(
+				    con.getProxyHost()+":"+con.getProxyPort());
+	    if (guess == null)  break Proxy;
+
+	    if (auth_handler != null)
+	    {
+		try
+		    { guess = auth_handler.fixupAuthInfo(guess, req, null, null); }
+		catch (AuthSchemeNotImplException asnie)
+		    { break Proxy; }
+		if (guess == null) break Proxy;
+	    }
+
+	    if (rem_idx == -1)	// add proxy-auth header
+	    {
+		rem_idx = hdrs.length;
+		hdrs = Util.resizeArray(hdrs, rem_idx+1);
+		req.setHeaders(hdrs);
+	    }
+
+	    hdrs[rem_idx] = new NVPair("Proxy-Authorization", guess.toString());
+	    rem_idx = -1;
+
+	    prxy_sent     = guess;
+	    prxy_from_4xx = false;
+
+	    Log.write(Log.AUTH, "AuthM: Preemptively sending " +
+			        "Proxy-Authorization '" + guess + "'");
+	}
+	if (rem_idx >= 0)
+	{
+	    System.arraycopy(hdrs, rem_idx+1, hdrs, rem_idx, hdrs.length-rem_idx-1);
+	    hdrs = Util.resizeArray(hdrs, hdrs.length-1);
+	    req.setHeaders(hdrs);
+	}
+
+
+	// Preemptively send authorization info
+
+	rem_idx = -1;
+	Auth: if (!auth_from_4xx)
+	{
+	    // first remove any Auth header that still may be around
+
+	    for (int idx=0; idx<hdrs.length; idx++)
+	    {
+		if (hdrs[idx].getName().equalsIgnoreCase("Authorization"))
+		{
+		    rem_idx = idx;
+		    break;
+		}
+	    }
+
+	    // now try and guess whether we need to send auth info
+
+	    guess = AuthorizationInfo.findBest(req);
+	    if (guess == null)  break Auth;
+
+	    if (auth_handler != null)
+	    {
+		try
+		    { guess = auth_handler.fixupAuthInfo(guess, req, null, null); }
+		catch (AuthSchemeNotImplException asnie)
+		    { break Auth; }
+		if (guess == null) break Auth;
+	    }
+
+	    if (rem_idx == -1)	// add auth header
+	    {
+		rem_idx = hdrs.length;
+		hdrs = Util.resizeArray(hdrs, rem_idx+1);
+		req.setHeaders(hdrs);
+	    }
+
+	    hdrs[rem_idx] = new NVPair("Authorization", guess.toString());
+	    rem_idx = -1;
+
+	    auth_sent     = guess;
+	    auth_from_4xx = false;
+
+	    Log.write(Log.AUTH, "AuthM: Preemptively sending Authorization '"
+				+ guess + "'");
+	}
+	if (rem_idx >= 0)
+	{
+	    System.arraycopy(hdrs, rem_idx+1, hdrs, rem_idx, hdrs.length-rem_idx-1);
+	    hdrs = Util.resizeArray(hdrs, hdrs.length-1);
+	    req.setHeaders(hdrs);
+	}
+
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest req)
+		throws IOException
+    {
+	/* If auth info successful update path list. Note: if we
+	 * preemptively sent auth info we don't actually know if
+	 * it was necessary. Therefore we don't update the path
+	 * list in this case; this prevents it from being
+	 * contaminated. If the info was necessary, then the next
+	 * time we access this resource we will again guess the
+	 * same info and send it.
+	 */
+	if (resp.getStatusCode() != 401  &&  resp.getStatusCode() != 407)
+	{
+	    if (auth_sent != null  &&  auth_from_4xx)
+	    {
+		try
+		{
+		    AuthorizationInfo.getAuthorization(auth_sent, req, resp,
+				    false).addPath(req.getRequestURI());
+		}
+		catch (AuthSchemeNotImplException asnie)
+		    { /* shouldn't happen */ }
+	    }
+
+	    // reset guard if not an auth challenge
+	    num_tries = 0;
+	}
+
+	auth_from_4xx = false;
+	prxy_from_4xx = false;
+
+	if (resp.getHeader("WWW-Authenticate") == null)
+	{
+	    auth_lst_idx = 0;
+	    auth_scm_idx = 0;
+	}
+
+	if (resp.getHeader("Proxy-Authenticate") == null)
+	{
+	    prxy_lst_idx = 0;
+	    prxy_scm_idx = 0;
+	}
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+		throws IOException, AuthSchemeNotImplException
+    {
+	// Let the AuthHandler handle any Authentication headers.
+
+	AuthorizationHandler h = AuthorizationInfo.getAuthHandler();
+	if (h != null)
+	    h.handleAuthHeaders(resp, req, auth_sent, prxy_sent);
+
+
+	// handle 401 and 407 response codes
+
+	int sts  = resp.getStatusCode();
+	switch(sts)
+	{
+	    case 401: // Unauthorized
+	    case 407: // Proxy Authentication Required
+
+		// guard against infinite retries due to bugs
+
+		num_tries++;
+		if (num_tries > 10)
+		    throw new ProtocolException("Bug in authorization handling: server refused the given info 10 times");
+
+
+		// defer handling if a stream was used
+
+		if (req.getStream() != null)
+		{
+		    if (!HTTPConnection.deferStreamed)
+		    {
+			Log.write(Log.AUTH, "AuthM: status " + sts +
+					    " not handled - request has " +
+					    "an output stream");
+			return RSP_CONTINUE;
+		    }
+
+		    saved_req  = (Request)  req.clone();
+		    saved_resp = (Response) resp.clone();
+		    deferred_auth_list.put(req.getStream(), this);
+
+		    req.getStream().reset();
+		    resp.setRetryRequest(true);
+
+		    Log.write(Log.AUTH, "AuthM: Handling of status " +
+					sts + " deferred because an " +
+					"output stream was used");
+
+		    return RSP_CONTINUE;
+		}
+
+
+		// handle the challenge
+
+		Log.write(Log.AUTH, "AuthM: Handling status: " + sts + " " +
+				    resp.getReasonLine());
+
+		handle_auth_challenge(req, resp);
+
+
+		// check for valid challenge
+
+		if (auth_sent != null  ||  prxy_sent != null)
+		{
+		    try { resp.getInputStream().close(); }
+		    catch (IOException ioe) { }
+
+		    if (auth_sent != null)
+			Log.write(Log.AUTH, "AuthM: Resending request " +
+					    "with Authorization '" +
+					    auth_sent + "'");
+		    else
+			Log.write(Log.AUTH, "AuthM: Resending request " +
+					    "with Proxy-Authorization '" +
+					    prxy_sent + "'");
+
+		    return RSP_REQUEST;
+		}
+
+
+		if (req.getStream() != null)
+		    Log.write(Log.AUTH, "AuthM: status " + sts + " not " +
+				        "handled - request has an output " +
+				        "stream");
+		else
+		    Log.write(Log.AUTH, "AuthM: No Auth Info found - " +
+				        "status " + sts + " not handled");
+
+		return RSP_CONTINUE;
+
+	    default:
+
+		return RSP_CONTINUE;
+	}
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)  throws IOException
+    {
+	// Let the AuthHandler handle any Authentication headers.
+
+	AuthorizationHandler h = AuthorizationInfo.getAuthHandler();
+	if (h != null)
+	    h.handleAuthTrailers(resp, req, auth_sent, prxy_sent);
+    }
+
+
+    /**
+     *
+     */
+    private void handle_auth_challenge(Request req, Response resp)
+	    throws AuthSchemeNotImplException, IOException
+    {
+	// handle WWW-Authenticate
+
+	int[] idx_arr = { auth_lst_idx,	// hack to pass by ref
+			  auth_scm_idx};
+	auth_sent = setAuthHeaders(resp.getHeader("WWW-Authenticate"),
+				   req, resp, "Authorization", idx_arr,
+				   auth_sent);
+	if (auth_sent != null)
+	{
+	    auth_from_4xx = true;
+	    auth_lst_idx = idx_arr[0];
+	    auth_scm_idx = idx_arr[1];
+	}
+	else
+	{
+	    auth_lst_idx = 0;
+	    auth_scm_idx = 0;
+	}
+
+
+	// handle Proxy-Authenticate
+
+	idx_arr[0] = prxy_lst_idx;	// hack to pass by ref
+	idx_arr[1] = prxy_scm_idx;
+	prxy_sent = setAuthHeaders(resp.getHeader("Proxy-Authenticate"),
+				   req, resp, "Proxy-Authorization",
+				   idx_arr, prxy_sent);
+	if (prxy_sent != null)
+	{
+	    prxy_from_4xx = true;
+	    prxy_lst_idx = idx_arr[0];
+	    prxy_scm_idx = idx_arr[1];
+	}
+	else
+	{
+	    prxy_lst_idx = 0;
+	    prxy_scm_idx = 0;
+	}
+
+	if (prxy_sent != null)
+	{
+	    HTTPConnection con = req.getConnection();
+	    Util.getList(proxy_cntxt_list, con.getContext())
+		.put(con.getProxyHost()+":"+con.getProxyPort(),
+		     prxy_sent);
+	}
+
+	// check for headers
+
+	if (auth_sent == null  &&  prxy_sent == null  &&
+	    resp.getHeader("WWW-Authenticate") == null  &&
+	    resp.getHeader("Proxy-Authenticate") == null)
+	{
+	    if (resp.getStatusCode() == 401)
+		throw new ProtocolException("Missing WWW-Authenticate header");
+	    else
+		throw new ProtocolException("Missing Proxy-Authenticate header");
+	}
+    }
+
+
+    /**
+     * Handles authentication requests and sets the authorization headers.
+     * It tries to retrieve the neccessary parameters from AuthorizationInfo,
+     * and failing that calls the AuthHandler. Handles multiple authentication
+     * headers.
+     *
+     * @param  auth_str the authentication header field returned by the server.
+     * @param  req      the Request used
+     * @param  resp     the full Response received
+     * @param  header   the header name to use in the new headers array.
+     * @param  idx_arr  an array of indicies holding the state of where we
+     *                  are when handling multiple authorization headers.
+     * @param  prev     the previous auth info sent, or null if none
+     * @return the new credentials, or null if none found
+     * @exception ProtocolException if <var>auth_str</var> is null.
+     * @exception AuthSchemeNotImplException if thrown by the AuthHandler.
+     * @exception IOException if thrown by the AuthHandler.
+     */
+    private AuthorizationInfo setAuthHeaders(String auth_str, Request req,
+					     RoResponse resp, String header,
+					     int[] idx_arr,
+					     AuthorizationInfo prev)
+	throws ProtocolException, AuthSchemeNotImplException, IOException
+    {
+	if (auth_str == null)  return null;
+
+	// get the list of challenges the server sent
+	AuthorizationInfo[] challenges =
+			AuthorizationInfo.parseAuthString(auth_str, req, resp);
+
+	if (Log.isEnabled(Log.AUTH))
+	{
+	    Log.write(Log.AUTH, "AuthM: parsed " + challenges.length +
+			        " challenges:");
+	    for (int idx=0; idx<challenges.length; idx++)
+		Log.write(Log.AUTH, "AuthM: Challenge " + challenges[idx]);
+	}
+
+	if (challenges.length == 0)
+	    return null;
+
+
+	/* some servers expect a 401 to invalidate sent credentials.
+	 * However, only do this for Basic scheme (because e.g. digest
+	 * "stale" handling will fail otherwise)
+	 */
+	if (prev != null  &&  prev.getScheme().equalsIgnoreCase("Basic"))
+	{
+	    for (int idx=0; idx<challenges.length; idx++)
+		if (prev.getRealm().equals(challenges[idx].getRealm())  &&
+		    prev.getScheme().equalsIgnoreCase(challenges[idx].getScheme()))
+		    AuthorizationInfo.removeAuthorization(prev,
+					      req.getConnection().getContext()); }
+
+	AuthorizationInfo    credentials  = null;
+	AuthorizationHandler auth_handler = AuthorizationInfo.getAuthHandler();
+
+	// try next auth challenge in list
+	while (credentials == null  &&  idx_arr[0] != -1  &&
+	       idx_arr[0] < challenges.length)
+	{
+	    credentials =
+		AuthorizationInfo.getAuthorization(challenges[idx_arr[0]], req,
+						   resp, false);
+	    if (auth_handler != null  &&  credentials != null)
+		credentials = auth_handler.fixupAuthInfo(credentials, req,
+						challenges[idx_arr[0]], resp);
+	    if (++idx_arr[0] == challenges.length)
+		idx_arr[0] = -1;
+	}
+
+
+	// if we don't have any credentials then prompt the user
+	if (credentials == null)
+	{
+	    for (int idx=0; idx<challenges.length; idx++)
+	    {
+		if (idx_arr[1] >= challenges.length)
+		    idx_arr[1] = 0;
+
+		try
+		{
+		    credentials = AuthorizationInfo.queryAuthHandler(
+					    challenges[idx_arr[1]], req, resp);
+		    break;
+		}
+		catch (AuthSchemeNotImplException asnie)
+		{
+		    if (idx == challenges.length-1)
+			throw asnie;
+		}
+		finally
+		    { idx_arr[1]++; }
+	    }
+	}
+
+	// if we still don't have any credentials then give up
+	if (credentials == null)
+	    return null;
+
+	// find auth info
+	int auth_idx;
+	NVPair[] hdrs = req.getHeaders();
+	for (auth_idx=0; auth_idx<hdrs.length; auth_idx++)
+	{
+	    if (hdrs[auth_idx].getName().equalsIgnoreCase(header))
+		break;
+	}
+
+	// add credentials to headers
+	if (auth_idx == hdrs.length)
+	{
+	    hdrs = Util.resizeArray(hdrs, auth_idx+1);
+	    req.setHeaders(hdrs);
+	}
+	hdrs[auth_idx] = new NVPair(header, credentials.toString());
+
+	return credentials;
+    }
+
+
+    private void copyFrom(AuthorizationModule other)
+    {
+	this.auth_lst_idx  = other.auth_lst_idx;
+	this.prxy_lst_idx  = other.prxy_lst_idx;
+	this.auth_scm_idx  = other.auth_scm_idx;
+	this.prxy_scm_idx  = other.prxy_scm_idx;
+
+	this.auth_sent     = other.auth_sent;
+	this.prxy_sent     = other.prxy_sent;
+
+	this.auth_from_4xx = other.auth_from_4xx;
+	this.prxy_from_4xx = other.prxy_from_4xx;
+
+	this.num_tries     = other.num_tries;
+
+	this.saved_req     = other.saved_req;
+	this.saved_resp    = other.saved_resp;
+    }
+}
diff --git a/HTTPClient/AuthorizationPrompter.java b/HTTPClient/AuthorizationPrompter.java
new file mode 100644
index 0000000..18e285e
--- /dev/null
+++ b/HTTPClient/AuthorizationPrompter.java
@@ -0,0 +1,68 @@
+/*
+ * @(#)AuthorizationPrompter.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001  Ronald Tschal�r
+ *
+ *  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., 59 Temple Place - Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ */
+
+package HTTPClient;
+
+/**
+ * This is the interface that a username/password prompter must implement. The
+ * {@link HTTPClient.DefaultAuthHandler DefaultAuthHandler} invokes an instance
+ * of this each time it needs a username and password to satisfy an
+ * authorization challenge (for which it doesn't already have the necessary
+ * info).
+ * 
+ * This can be used to implement a different UI from the default popup box
+ * for soliciting usernames and passwords, or for using an altogether
+ * different way of getting the necessary auth info.
+ *
+ * @see DefaultAuthHandler#setAuthorizationPrompter(HTTPClient.AuthorizationPrompter)
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3-3
+ */
+public interface AuthorizationPrompter
+{
+    /**
+     * This method is invoked whenever a username and password is required
+     * for an authentication challenge to proceed.
+     *
+     * @param challenge the parsed challenge from the server; the host,
+     *                  port, scheme, realm and params are set to the
+     *                  values given by the server in the challenge.
+     * @param forProxy  true if the info is for a proxy (i.e. this is part of
+     *                  handling a 407 response); false otherwise (i.e. the
+     *                  response code was 401).
+     * @return an NVPair containing the username and password in the name
+     *         and value fields, respectively, or null if the authorization
+     *         challenge handling is to be aborted (e.g. when the user
+     *         hits the <var>Cancel</var> button).
+     */
+    NVPair getUsernamePassword(AuthorizationInfo challenge, boolean forProxy);
+}
diff --git a/HTTPClient/BufferedInputStream.java b/HTTPClient/BufferedInputStream.java
new file mode 100644
index 0000000..457c874
--- /dev/null
+++ b/HTTPClient/BufferedInputStream.java
@@ -0,0 +1,230 @@
+/*
+ * @(#)BufferedInputStream.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.InputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+
+/**
+ * This class is similar to java.io.BufferedInputStream, except that it fixes
+ * certain bugs and provides support for finding multipart boundaries.
+ *
+ * <P>Note: none of the methods here are synchronized because we assume the
+ * caller is already taking care of that.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class BufferedInputStream extends FilterInputStream
+{
+    /** our read buffer */
+    private byte[] buffer = new byte[2000];
+    /** the next byte in the buffer at which to read */
+    private int    pos = 0;
+    /** the end of the valid data in the buffer */
+    private int    end = 0;
+    /** the current mark position, or -1 if none */
+    private int    mark_pos = -1;
+    /**
+     * the large read threashhold: reads larger than this aren't buffered if
+     * both the current buffer is empty and no mark has been set. This is just
+     * an attempt to balance copying vs. multiple reads.
+     */
+    private int    lr_thrshld = 1500;
+
+
+    /**
+     * Create a new BufferedInputStream around the given input stream.
+     *
+     * @param stream  the underlying input stream to use
+     */
+    BufferedInputStream(InputStream stream)
+    {
+	super(stream);
+    }
+
+    /**
+     * Read a single byte.
+     *
+     * @return the read byte, or -1 if the end of the stream has been reached
+     * @exception IOException if thrown by the underlying stream
+     */
+    public int read() throws IOException
+    {
+	if (pos >= end)
+	    fillBuff();
+
+	return (end > pos) ? (buffer[pos++] & 0xFF) : -1;
+    }
+
+    /**
+     * Read a buffer full.
+     *
+     * @param buf  the buffer to read into
+     * @param off  the offset within <var>buf</var> at which to start writing
+     * @param len  the number of bytes to read
+     * @return the number of bytes read
+     * @exception IOException if thrown by the underlying stream
+     */
+    public int read(byte[] buf, int off, int len) throws IOException
+    {
+	if (len <= 0)
+	    return 0;
+
+	// optimize for large reads
+	if (pos >= end  &&  len >= lr_thrshld  &&  mark_pos < 0)
+	    return in.read(buf, off, len);
+
+	if (pos >= end)
+	    fillBuff();
+
+	if (pos >= end)
+	    return -1;
+
+	int left = end - pos;
+	if (len > left)
+	    len = left;
+	System.arraycopy(buffer, pos, buf, off, len);
+	pos += len;
+
+	return len;
+    }
+
+    /**
+     * Skip the given number of bytes in the stream.
+     *
+     * @param n   the number of bytes to skip
+     * @return the actual number of bytes skipped
+     * @exception IOException if thrown by the underlying stream
+     */
+    public long skip(long n) throws IOException
+    {
+	if (n <= 0)
+	    return 0;
+
+	int left = end - pos;
+	if (n <= left)
+	{
+	    pos += n;
+	    return n;
+	}
+	else
+	{
+	    pos = end;
+	    return left + in.skip(n - left);
+	}
+    }
+
+    /**
+     * Fill buffer by reading from the underlying stream. This assumes the
+     * current buffer is empty, i.e. pos == end.
+     */
+    private final void fillBuff() throws IOException
+    {
+	if (mark_pos > 0)	// keep the marked stuff around if possible
+	{
+	    // only copy if we don't have any space left
+	    if (end >= buffer.length)
+	    {
+		System.arraycopy(buffer, mark_pos, buffer, 0, end - mark_pos);
+		pos = end - mark_pos;
+	    }
+	}
+	else if (mark_pos == 0  &&  end < buffer.length)
+	    ;			// pos == end, so we just fill what's left
+	else
+	    pos = 0;		// try to fill complete buffer
+
+	// make sure our state is consistent even if read() throws InterruptedIOException
+	end = pos;
+
+	int got = in.read(buffer, pos, buffer.length - pos);
+	if (got > 0)
+	    end = pos + got;
+    }
+
+    /**
+     * @return the number of bytes available for reading without blocking
+     * @exception IOException if the buffer is empty and the underlying stream has been
+     *                        closed
+     */
+    public int available() throws IOException
+    {
+	int avail = end - pos;  
+	if (avail == 0)
+	    return in.available();
+
+	try
+	    { avail += in.available(); }
+	catch (IOException ignored)
+	    { /* ignore this because we have something available */ }
+	return avail;
+    }
+
+    /**
+     * Mark the current read position so that we can start searching for the end boundary.
+     */
+    void markForSearch()
+    {
+	mark_pos = pos;
+    }
+
+    /**
+     * Figures out how many bytes past the end of the multipart we read. If we
+     * found the end, it then resets the read pos to just past the end of the
+     * boundary and unsets the mark; if not found, is sets the mark_pos back
+     * enough from the current position so we can always be sure to find the
+     * boundary.
+     *
+     * @param search     the search string (end boundary)
+     * @param search_cmp the compiled info of the search string
+     * @return how many bytes past the end of the boundary we went; -1 if we
+     *         haven't gone passed it yet.
+     */
+    int pastEnd(byte[] search, int[] search_cmp)
+    {
+	int idx = Util.findStr(search, search_cmp, buffer, mark_pos, pos);
+	if (idx == -1)
+	    mark_pos = (pos > search.length) ? pos - search.length : 0;
+	else
+	{
+	    int eos  = idx + search.length;
+	    idx      = pos - eos;
+	    pos      = eos;
+	    mark_pos = -1;
+	}
+
+	return idx;
+    }
+}
diff --git a/HTTPClient/CIHashtable.java b/HTTPClient/CIHashtable.java
new file mode 100644
index 0000000..a042dc4
--- /dev/null
+++ b/HTTPClient/CIHashtable.java
@@ -0,0 +1,276 @@
+/*
+ * @(#)CIHashtable.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ * This class implements a Hashtable with case-insensitive Strings as keys.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class CIHashtable extends Hashtable
+{
+    // Constructors
+
+    /**
+     * Create a new CIHashtable with the specified initial capacity and the
+     * specified load factor.
+     *
+     * @param intialCapacity the initial number of buckets
+     * @param loadFactor a number between 0.0 and 1.0
+     * @see java.util.Hashtable(int, float)
+     */
+    public CIHashtable(int initialCapacity, float loadFactor)
+    {
+	super(initialCapacity, loadFactor);
+    }
+
+
+    /**
+     * Create a new CIHashtable with the specified initial capacity.
+     *
+     * @param intialCapacity the initial number of buckets
+     * @see java.util.Hashtable(int)
+     */
+    public CIHashtable(int initialCapacity)
+    {
+	super(initialCapacity);
+    }
+
+
+    /**
+     * Create a new CIHashtable with a default initial capacity.
+     *
+     * @see java.util.Hashtable()
+     */
+    public CIHashtable()
+    {
+	super();
+    }
+
+
+    // Methods
+
+    /**
+     * Retrieves the object associated with the specified key. The key lookup
+     * is case-insensitive.
+     *
+     * @param key the key
+     * @return the object associated with the key, or null if none found.
+     * @see java.util.Hashtable.get(Object)
+     */
+    public Object get(String key)
+    {
+	return super.get(new CIString(key));
+    }
+
+
+    /**
+     * Stores the specified object with the specified key.
+     *
+     * @param key the key
+     * @param value the object to be associated with the key
+     * @return the object previously associated with the key, or null if
+     *         there was none.
+     * @see java.util.Hashtable.put(Object, Object)
+     */
+    public Object put(String key, Object value)
+    {
+	return super.put(new CIString(key), value);
+    }
+
+
+    /**
+     * Looks whether any object is associated with the specified key. The
+     * key lookup is case insensitive.
+     *
+     * @param key the key
+     * @return true is there is an object associated with key, false otherwise
+     * @see java.util.Hashtable.containsKey(Object)
+     */
+    public boolean containsKey(String key)
+    {
+	return super.containsKey(new CIString(key));
+    }
+
+
+    /**
+     * Removes the object associated with this key from the Hashtable. The
+     * key lookup is case insensitive.
+     *
+     * @param key the key
+     * @return the object associated with this key, or null if there was none.
+     * @see java.util.Hashtable.remove(Object)
+     */
+    public Object remove(String key)
+    {
+	return super.remove(new CIString(key));
+    }
+
+
+    /**
+     * Returns an enumeration of all the keys in the Hashtable.
+     *
+     * @return the requested Enumerator
+     * @see java.util.Hashtable.keys(Object)
+     */
+    public Enumeration keys()
+    {
+	return new CIHashtableEnumeration(super.keys());
+    }
+}
+
+
+/**
+ * A simple enumerator which delegates everything to the real enumerator.
+ * If a CIString element is returned, then the string it represents is
+ * returned instead.
+ */
+final class CIHashtableEnumeration implements Enumeration
+{
+    Enumeration HTEnum;
+
+    public CIHashtableEnumeration(Enumeration enumx)
+    {
+	HTEnum = enumx;
+    }
+
+    public boolean hasMoreElements()
+    {
+	return HTEnum.hasMoreElements();
+    }
+
+    public Object nextElement()
+    {
+	Object tmp = HTEnum.nextElement();
+	if (tmp instanceof CIString)
+	    return ((CIString) tmp).getString();
+
+	return tmp;
+    }
+}
+
+
+/**
+ * This class' raison d'etre is that I want to use a Hashtable using
+ * Strings as keys and I want the lookup be case insensitive, but I
+ * also want to be able retrieve the keys with original case (otherwise
+ * I could just use toLowerCase() in the get() and put()). Since the
+ * class String is final we create a new class that holds the string
+ * and overrides the methods hashCode() and equals().
+ */
+final class CIString
+{
+    /** the string */
+    private String string;
+
+    /** the hash code */
+    private int hash;
+
+
+    /** the constructor */
+    public CIString(String string)
+    {
+	this.string = string;
+	this.hash   = calcHashCode(string);
+    }
+
+    /** return the original string */
+    public final String getString()
+    {
+	return string;
+    }
+
+    /** the hash code was precomputed */
+    public int hashCode()
+    {
+	return hash;
+    }
+
+
+    /**
+     * We smash case before calculation so that the hash code is
+     * "case insensitive". This is based on code snarfed from
+     * java.lang.String.hashCode().
+     */
+    private static final int calcHashCode(String str)
+    {
+	int  hash  = 0;
+	char llc[] = lc;
+	int  len   = str.length();
+
+	for (int idx= 0; idx<len; idx++)
+	    hash = 31*hash + llc[str.charAt(idx)];
+
+	return hash;
+    }
+
+
+    /**
+     * Uses the case insensitive comparison.
+     */
+    public boolean equals(Object obj)
+    {
+	if (obj != null)
+	{
+	    if (obj instanceof CIString)
+		return string.equalsIgnoreCase(((CIString) obj).string);
+
+	    if (obj instanceof String)
+		return string.equalsIgnoreCase((String) obj);
+	}
+
+	return false;
+    }
+
+    /**
+     * Just return the internal string.
+     */
+    public String toString()
+    {
+	return string;
+    }
+
+
+    private static final char[] lc = new char[256];
+
+    static
+    {
+	// just ISO-8859-1
+	for (char idx=0; idx<256; idx++)
+	    lc[idx] = Character.toLowerCase(idx);
+    }
+}
diff --git a/HTTPClient/ChunkedInputStream.java b/HTTPClient/ChunkedInputStream.java
new file mode 100644
index 0000000..68ac888
--- /dev/null
+++ b/HTTPClient/ChunkedInputStream.java
@@ -0,0 +1,136 @@
+/*
+ * @(#)ChunkedInputStream.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.FilterInputStream;
+
+
+/**
+ * This class de-chunks an input stream.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class ChunkedInputStream extends FilterInputStream
+{
+    /**
+     * @param is the input stream to dechunk
+     */
+    ChunkedInputStream(InputStream is)
+    {
+	super(is);
+    }
+
+
+    byte[] one = new byte[1];
+    public synchronized int read() throws IOException
+    {
+	int b = read(one, 0, 1);
+	if (b == 1)
+	    return (one[0] & 0xff);
+	else
+	    return -1;
+    }
+
+
+    private long chunk_len = -1;
+    private boolean eof   = false;
+
+    public synchronized int read(byte[] buf, int off, int len)
+	    throws IOException
+    {
+	if (eof)  return -1;
+
+	if (chunk_len == -1)    // it's a new chunk
+	{
+	    try
+		{ chunk_len = Codecs.getChunkLength(in); }
+	    catch (ParseException pe)
+		{ throw new IOException(pe.toString()); }
+	}
+
+	if (chunk_len > 0)              // it's data
+	{
+	    if (len > chunk_len)  len = (int) chunk_len;
+	    int rcvd = in.read(buf, off, len);
+	    if (rcvd == -1)
+		throw new EOFException("Premature EOF encountered");
+
+	    chunk_len -= rcvd;
+	    if (chunk_len == 0) // got the whole chunk
+	    {
+		in.read();  // CR
+		in.read();  // LF
+		chunk_len = -1;
+	    }
+
+	    return rcvd;
+	}
+	else    			// the footers (trailers)
+	{
+	    // discard
+	    Request dummy =
+		    new Request(null, null, null, null, null, null, false);
+	    new Response(dummy, null).readTrailers(in);
+
+	    eof = true;
+	    return -1;
+	}
+    }
+
+
+    public synchronized long skip(long num)  throws IOException
+    {
+	byte[] tmp = new byte[(int) num];
+	int got = read(tmp, 0, (int) num);
+
+	if (got > 0)
+	    return (long) got;
+	else
+	    return 0L;
+    }
+
+
+    public synchronized int available()  throws IOException
+    {
+	if (eof)  return 0;
+
+	if (chunk_len != -1)
+	    return (int) chunk_len + in.available();
+	else
+	    return in.available();
+    }
+}
diff --git a/HTTPClient/Codecs.java b/HTTPClient/Codecs.java
new file mode 100644
index 0000000..0916215
--- /dev/null
+++ b/HTTPClient/Codecs.java
@@ -0,0 +1,1566 @@
+/*
+ * @(#)Codecs.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.util.BitSet;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import java.io.IOException;
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLConnection;
+
+
+/**
+ * This class collects various encoders and decoders.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class Codecs
+{
+    private static BitSet  BoundChar;
+    private static BitSet  EBCDICUnsafeChar;
+    private static byte[]  Base64EncMap, Base64DecMap;
+    private static char[]  UUEncMap;
+    private static byte[]  UUDecMap;
+
+
+    private final static String ContDisp = "\r\nContent-Disposition: form-data; name=\"";
+    private final static String FileName = "\"; filename=\"";
+    private final static String ContType = "\r\nContent-Type: ";
+    private final static String Boundary = "\r\n----------ieoau._._+2_8_GoodLuck8.3-dskdfJwSJKl234324jfLdsjfdAuaoei-----";
+
+
+    // Class Initializer
+
+    static
+    {
+	// rfc-2046 & rfc-2045: (bcharsnospace & token)
+	// used for multipart codings
+	BoundChar = new BitSet(256);
+	for (int ch='0'; ch <= '9'; ch++)  BoundChar.set(ch);
+	for (int ch='A'; ch <= 'Z'; ch++)  BoundChar.set(ch);
+	for (int ch='a'; ch <= 'z'; ch++)  BoundChar.set(ch);
+	BoundChar.set('+');
+	BoundChar.set('_');
+	BoundChar.set('-');
+	BoundChar.set('.');
+
+	// EBCDIC unsafe characters to be quoted in quoted-printable
+	// See first NOTE in section 6.7 of rfc-2045
+	EBCDICUnsafeChar = new BitSet(256);
+	EBCDICUnsafeChar.set('!');
+	EBCDICUnsafeChar.set('"');
+	EBCDICUnsafeChar.set('#');
+	EBCDICUnsafeChar.set('$');
+	EBCDICUnsafeChar.set('@');
+	EBCDICUnsafeChar.set('[');
+	EBCDICUnsafeChar.set('\\');
+	EBCDICUnsafeChar.set(']');
+	EBCDICUnsafeChar.set('^');
+	EBCDICUnsafeChar.set('`');
+	EBCDICUnsafeChar.set('{');
+	EBCDICUnsafeChar.set('|');
+	EBCDICUnsafeChar.set('}');
+	EBCDICUnsafeChar.set('~');
+
+	// rfc-2045: Base64 Alphabet
+	byte[] map = {
+	    (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
+	    (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L',
+	    (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q', (byte)'R',
+	    (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X',
+	    (byte)'Y', (byte)'Z',
+	    (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f',
+	    (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l',
+	    (byte)'m', (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r',
+	    (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x',
+	    (byte)'y', (byte)'z',
+	    (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
+	    (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' };
+	Base64EncMap = map;
+	Base64DecMap = new byte[128];
+	for (int idx=0; idx<Base64EncMap.length; idx++)
+	    Base64DecMap[Base64EncMap[idx]] = (byte) idx;
+
+	// uuencode'ing maps
+	UUEncMap = new char[64];
+	for (int idx=0; idx<UUEncMap.length; idx++)
+	    UUEncMap[idx] = (char) (idx + 0x20);
+	UUDecMap = new byte[128];
+	for (int idx=0; idx<UUEncMap.length; idx++)
+	    UUDecMap[UUEncMap[idx]] = (byte) idx;
+    }
+
+
+    // Constructors
+
+    /**
+     * This class isn't meant to be instantiated.
+     */
+    private Codecs() {}
+
+
+    // Methods
+
+    /**
+     * This method encodes the given string using the base64-encoding
+     * specified in RFC-2045 (Section 6.8). It's used for example in the
+     * "Basic" authorization scheme.
+     *
+     * @param  str the string
+     * @return the base64-encoded <var>str</var>
+     */
+    public final static String base64Encode(String str)
+    {
+	if (str == null)  return  null;
+
+	try
+	    { return new String(base64Encode(str.getBytes("8859_1")), "8859_1"); }
+	catch (UnsupportedEncodingException uee)
+	    { throw new Error(uee.toString()); }
+    }
+
+
+    /**
+     * This method encodes the given byte[] using the base64-encoding
+     * specified in RFC-2045 (Section 6.8).
+     *
+     * @param  data the data
+     * @return the base64-encoded <var>data</var>
+     */
+    public final static byte[] base64Encode(byte[] data)
+    {
+	if (data == null)  return  null;
+
+	int sidx, didx;
+	byte dest[] = new byte[((data.length+2)/3)*4];
+
+
+	// 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
+	for (sidx=0, didx=0; sidx < data.length-2; sidx += 3)
+	{
+	    dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
+	    dest[didx++] = Base64EncMap[(data[sidx+1] >>> 4) & 017 |
+					(data[sidx] << 4) & 077];
+	    dest[didx++] = Base64EncMap[(data[sidx+2] >>> 6) & 003 |
+					(data[sidx+1] << 2) & 077];
+	    dest[didx++] = Base64EncMap[data[sidx+2] & 077];
+	}
+	if (sidx < data.length)
+	{
+	    dest[didx++] = Base64EncMap[(data[sidx] >>> 2) & 077];
+	    if (sidx < data.length-1)
+	    {
+		dest[didx++] = Base64EncMap[(data[sidx+1] >>> 4) & 017 |
+					    (data[sidx] << 4) & 077];
+		dest[didx++] = Base64EncMap[(data[sidx+1] << 2) & 077];
+	    }
+	    else
+		dest[didx++] = Base64EncMap[(data[sidx] << 4) & 077];
+	}
+
+	// add padding
+	for ( ; didx < dest.length; didx++)
+	    dest[didx] = (byte) '=';
+
+	return dest;
+    }
+
+
+    /**
+     * This method decodes the given string using the base64-encoding
+     * specified in RFC-2045 (Section 6.8).
+     *
+     * @param  str the base64-encoded string.
+     * @return the decoded <var>str</var>.
+     */
+    public final static String base64Decode(String str)
+    {
+	if (str == null)  return  null;
+
+	try
+	    { return new String(base64Decode(str.getBytes("8859_1")), "8859_1"); }
+	catch (UnsupportedEncodingException uee)
+	    { throw new Error(uee.toString()); }
+    }
+
+
+    /**
+     * This method decodes the given byte[] using the base64-encoding
+     * specified in RFC-2045 (Section 6.8).
+     *
+     * @param  data the base64-encoded data.
+     * @return the decoded <var>data</var>.
+     */
+    public final static byte[] base64Decode(byte[] data)
+    {
+	if (data == null)  return  null;
+
+	int tail = data.length;
+	while (data[tail-1] == '=')  tail--;
+
+	byte dest[] = new byte[tail - data.length/4];
+
+
+	// ascii printable to 0-63 conversion
+	for (int idx = 0; idx <data.length; idx++)
+	    data[idx] = Base64DecMap[data[idx]];
+
+	// 4-byte to 3-byte conversion
+	int sidx, didx;
+	for (sidx = 0, didx=0; didx < dest.length-2; sidx += 4, didx += 3)
+	{
+	    dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) |
+			    ((data[sidx+1] >>> 4) & 003) );
+	    dest[didx+1] = (byte) ( ((data[sidx+1] << 4) & 255) |
+			    ((data[sidx+2] >>> 2) & 017) );
+	    dest[didx+2] = (byte) ( ((data[sidx+2] << 6) & 255) |
+			    (data[sidx+3] & 077) );
+	}
+	if (didx < dest.length)
+	    dest[didx]   = (byte) ( ((data[sidx] << 2) & 255) |
+			    ((data[sidx+1] >>> 4) & 003) );
+	if (++didx < dest.length)
+	    dest[didx]   = (byte) ( ((data[sidx+1] << 4) & 255) |
+			    ((data[sidx+2] >>> 2) & 017) );
+
+	return dest;
+    }
+
+
+    /**
+     * This method encodes the given byte[] using the unix uuencode
+     * encding. The output is split into lines starting with the encoded
+     * number of encoded octets in the line and ending with a newline.
+     * No line is longer than 45 octets (60 characters), not including
+     * length and newline.
+     *
+     * <P><em>Note:</em> just the raw data is encoded; no 'begin' and 'end'
+     * lines are added as is done by the unix <code>uuencode</code> utility.
+     *
+     * @param  data the data
+     * @return the uuencoded <var>data</var>
+     */
+    public final static char[] uuencode(byte[] data)
+    {
+	if (data == null)      return  null;
+	if (data.length == 0)  return  new char[0];
+
+	int line_len = 45;	// line length, in octets
+
+	int sidx, didx;
+	char nl[]   = System.getProperty("line.separator", "\n").toCharArray(),
+	     dest[] = new char[(data.length+2)/3*4 +
+			    ((data.length+line_len-1)/line_len)*(nl.length+1)];
+
+	// split into lines, adding line-length and line terminator
+	for (sidx=0, didx=0; sidx+line_len < data.length; )
+	{
+	    // line length
+	    dest[didx++] = UUEncMap[line_len];
+
+	    // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
+	    for (int end = sidx+line_len; sidx < end; sidx += 3)
+	    {
+		dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
+		dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
+					    (data[sidx] << 4) & 077];
+		dest[didx++] = UUEncMap[(data[sidx+2] >>> 6) & 003 |
+					    (data[sidx+1] << 2) & 077];
+		dest[didx++] = UUEncMap[data[sidx+2] & 077];
+	    }
+
+	    // line terminator
+	    for (int idx=0; idx<nl.length; idx++)  dest[didx++] = nl[idx];
+	}
+
+	// last line
+
+	// line length
+	dest[didx++] = UUEncMap[data.length-sidx];
+
+	// 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
+	for (; sidx+2 < data.length; sidx += 3)
+	{
+	    dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
+	    dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
+					(data[sidx] << 4) & 077];
+	    dest[didx++] = UUEncMap[(data[sidx+2] >>> 6) & 003 |
+					(data[sidx+1] << 2) & 077];
+	    dest[didx++] = UUEncMap[data[sidx+2] & 077];
+	}
+
+	if (sidx < data.length-1)
+	{
+	    dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
+	    dest[didx++] = UUEncMap[(data[sidx+1] >>> 4) & 017 |
+					(data[sidx] << 4) & 077];
+	    dest[didx++] = UUEncMap[(data[sidx+1] << 2) & 077];
+	    dest[didx++] = UUEncMap[0];
+	}
+	else if (sidx < data.length)
+	{
+	    dest[didx++] = UUEncMap[(data[sidx] >>> 2) & 077];
+	    dest[didx++] = UUEncMap[(data[sidx] << 4) & 077];
+	    dest[didx++] = UUEncMap[0];
+	    dest[didx++] = UUEncMap[0];
+	}
+
+	// line terminator
+	for (int idx=0; idx<nl.length; idx++)  dest[didx++] = nl[idx];
+
+	// sanity check
+	if (didx != dest.length)
+	    throw new Error("Calculated "+dest.length+" chars but wrote "+didx+" chars!");
+
+	return dest;
+    }
+
+
+    /**
+     * TBD! How to return file name and mode?
+     *
+     * @param rdr the reader from which to read and decode the data
+     * @exception ParseException if either the "begin" or "end" line are not
+     *                           found, or the "begin" is incorrect
+     * @exception IOException if the <var>rdr</var> throws an IOException
+     */
+    private final static byte[] uudecode(BufferedReader rdr)
+		throws ParseException, IOException
+    {
+	String line, file_name;
+	int    file_mode;
+
+
+	// search for beginning
+
+	while ((line = rdr.readLine()) != null  &&  !line.startsWith("begin "))
+	    ;
+	if (line == null)
+	    throw new ParseException("'begin' line not found");
+
+
+	// parse 'begin' line
+
+	StringTokenizer tok = new StringTokenizer(line);
+	tok.nextToken();		// throw away 'begin'
+	try				// extract mode
+	    { file_mode = Integer.parseInt(tok.nextToken(), 8); }
+	catch (Exception e)
+	    { throw new ParseException("Invalid mode on line: " + line); }
+	try				// extract name
+	    { file_name = tok.nextToken(); }
+	catch (java.util.NoSuchElementException e)
+	    { throw new ParseException("No file name found on line: " + line); }
+
+
+	// read and parse body
+
+	byte[] body = new byte[1000];
+	int    off  = 0;
+
+	while ((line = rdr.readLine()) != null  &&  !line.equals("end"))
+	{
+	    byte[] tmp = uudecode(line.toCharArray());
+	    if (off + tmp.length > body.length)
+		body = Util.resizeArray(body, off+1000);
+	    System.arraycopy(tmp, 0, body, off, tmp.length);
+	    off += tmp.length;
+	}
+
+	if (line == null)
+	    throw new ParseException("'end' line not found");
+
+
+	return Util.resizeArray(body, off);
+    }
+
+
+    /**
+     * This method decodes the given uuencoded char[].
+     *
+     * <P><em>Note:</em> just the actual data is decoded; any 'begin' and
+     * 'end' lines such as those generated by the unix <code>uuencode</code>
+     * utility must not be included.
+     *
+     * @param  data the uuencode-encoded data.
+     * @return the decoded <var>data</var>.
+     */
+    public final static byte[] uudecode(char[] data)
+    {
+	if (data == null)  return  null;
+
+	int sidx, didx;
+	byte dest[] = new byte[data.length/4*3];
+
+
+	for (sidx=0, didx=0; sidx < data.length; )
+	{
+	    // get line length (in number of encoded octets)
+	    int len = UUDecMap[data[sidx++]];
+
+	    // ascii printable to 0-63 and 4-byte to 3-byte conversion
+	    int end = didx+len;
+	    for (; didx < end-2; sidx += 4)
+	    {
+		byte A = UUDecMap[data[sidx]],
+		     B = UUDecMap[data[sidx+1]],
+		     C = UUDecMap[data[sidx+2]],
+		     D = UUDecMap[data[sidx+3]];
+		dest[didx++] = (byte) ( ((A << 2) & 255) | ((B >>> 4) & 003) );
+		dest[didx++] = (byte) ( ((B << 4) & 255) | ((C >>> 2) & 017) );
+		dest[didx++] = (byte) ( ((C << 6) & 255) | (D & 077) );
+	    }
+
+	    if (didx < end)
+	    {
+		byte A = UUDecMap[data[sidx]],
+		     B = UUDecMap[data[sidx+1]];
+		dest[didx++] = (byte) ( ((A << 2) & 255) | ((B >>> 4) & 003) );
+	    }
+	    if (didx < end)
+	    {
+		byte B = UUDecMap[data[sidx+1]],
+		     C = UUDecMap[data[sidx+2]];
+		dest[didx++] = (byte) ( ((B << 4) & 255) | ((C >>> 2) & 017) );
+	    }
+
+	    // skip padding
+	    while (sidx < data.length  &&
+		   data[sidx] != '\n'  &&  data[sidx] != '\r')
+		sidx++;
+
+	    // skip end of line
+	    while (sidx < data.length  &&
+		   (data[sidx] == '\n'  ||  data[sidx] == '\r'))
+		sidx++;
+	}
+
+	return Util.resizeArray(dest, didx);
+    }
+
+
+    /**
+     * This method does a quoted-printable encoding of the given string
+     * according to RFC-2045 (Section 6.7). <em>Note:</em> this assumes
+     * 8-bit characters.
+     *
+     * @param  str the string
+     * @return the quoted-printable encoded string
+     */
+    public final static String quotedPrintableEncode(String str)
+    {
+	if (str == null)  return  null;
+
+	char map[] =
+	    {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'},
+	     nl[]  = System.getProperty("line.separator", "\n").toCharArray(),
+	     res[] = new char[(int) (str.length()*1.5)],
+	     src[] = str.toCharArray();
+	char ch;
+	int  cnt  = 0,
+	     didx = 1,
+	     last = 0,
+	     slen = str.length();
+
+
+	for (int sidx=0; sidx < slen; sidx++)
+	{
+	    ch = src[sidx];
+
+	    if (ch == nl[0]   &&  match(src, sidx, nl))		// Rule #4
+	    {
+		if (res[didx-1] == ' ')			// Rule #3
+		{
+		    res[didx-1] = '=';
+		    res[didx++] = '2';
+		    res[didx++] = '0';
+		}
+		else if (res[didx-1] == '\t')		// Rule #3
+		{
+		    res[didx-1] = '=';
+		    res[didx++] = '0';
+		    res[didx++] = '9';
+		}
+
+		res[didx++] = '\r';
+		res[didx++] = '\n';
+		sidx += nl.length - 1;
+		cnt = didx;
+	    }
+	    else if (ch > 126  ||  (ch < 32  &&  ch != '\t')  ||  ch == '='  ||
+		     EBCDICUnsafeChar.get((int) ch))
+	    {							// Rule #1, #2
+		res[didx++] = '=';
+		res[didx++] = map[(ch & 0xf0) >>> 4];
+		res[didx++] = map[ch & 0x0f];
+	    }
+	    else						// Rule #1
+	    {
+		res[didx++] = ch;
+	    }
+
+	    if (didx > cnt+70)					// Rule #5
+	    {
+		res[didx++] = '=';
+		res[didx++] = '\r';
+		res[didx++] = '\n';
+		cnt = didx;
+	    }
+
+	    if (didx > res.length-5)
+		res = Util.resizeArray(res, res.length+500);
+	}
+
+	return String.valueOf(res, 1, didx-1);
+    }
+
+    private final static boolean match(char[] str, int start, char[] arr)
+    {
+	if (str.length < start + arr.length)  return false;
+
+	for (int idx=1; idx < arr.length; idx++)
+	    if (str[start+idx] != arr[idx])  return false;
+	return true;
+    }
+
+
+    /**
+     * This method does a quoted-printable decoding of the given string
+     * according to RFC-2045 (Section 6.7). <em>Note:</em> this method
+     * expects the whole message in one chunk, not line by line.
+     *
+     * @param  str the message
+     * @return the decoded message
+     * @exception ParseException If a '=' is not followed by a valid
+     *                           2-digit hex number or '\r\n'.
+     */
+    public final static String quotedPrintableDecode(String str)
+	    throws ParseException
+    {
+	if (str == null)  return  null;
+
+	char res[] = new char[(int) (str.length()*1.1)],
+	     src[] = str.toCharArray(),
+	     nl[]  = System.getProperty("line.separator", "\n").toCharArray();
+	int last   = 0,
+	    didx   = 0,
+	    slen   = str.length();
+
+
+	for (int sidx=0; sidx<slen; )
+	{
+	    char ch = src[sidx++];
+
+	    if (ch == '=')
+	    {
+		if (sidx >= slen-1)
+		    throw new ParseException("Premature end of input detected");
+
+		if (src[sidx] == '\n'  ||  src[sidx] == '\r')
+		{					// Rule #5
+		    sidx++;
+
+		    if (src[sidx-1] == '\r'  &&
+			src[sidx] == '\n')
+			sidx++;
+		}
+		else					// Rule #1
+		{
+		    char repl;
+		    int hi = Character.digit(src[sidx], 16),
+			lo = Character.digit(src[sidx+1], 16);
+
+		    if ((hi | lo) < 0)
+			throw new ParseException(new String(src, sidx-1, 3) +
+						" is an invalid code");
+		    else
+		    {
+			repl = (char) (hi << 4 | lo);
+			sidx += 2;
+		    }
+
+		    res[didx++] = repl;
+		}
+		last = didx;
+	    }
+	    else if (ch == '\n'  ||  ch == '\r')	// Rule #4
+	    {
+		if (ch == '\r'  &&  sidx < slen  &&  src[sidx] == '\n')
+		    sidx++;
+		for (int idx=0; idx<nl.length; idx++)
+		    res[last++] = nl[idx];
+		didx = last;
+	    }
+	    else					// Rule #1, #2
+	    {
+		res[didx++] = ch;
+		if (ch != ' '  &&  ch != '\t')		// Rule #3
+		    last = didx;
+	    }
+
+	    if (didx > res.length-nl.length-2)
+		res = Util.resizeArray(res, res.length+500);
+	}
+
+	return new String(res, 0, didx);
+    }
+
+
+    /**
+     * This method urlencodes the given string. This method is here for
+     * symmetry reasons and just calls java.net.URLEncoder.encode().
+     *
+     * @param  str the string
+     * @return the url-encoded string
+     */
+    public final static String URLEncode(String str)
+    {
+	if (str == null)  return  null;
+
+	return java.net.URLEncoder.encode(str);
+    }
+
+
+    /**
+     * This method decodes the given urlencoded string.
+     *
+     * @param  str the url-encoded string
+     * @return the decoded string
+     * @exception ParseException If a '%' is not followed by a valid
+     *                           2-digit hex number.
+     */
+    public final static String URLDecode(String str) throws ParseException
+    {
+	if (str == null)  return  null;
+
+	char[] res  = new char[str.length()];
+	int    didx = 0;
+
+	for (int sidx=0; sidx<str.length(); sidx++)
+	{
+	    char ch = str.charAt(sidx);
+	    if (ch == '+')
+		res[didx++] = ' ';
+	    else if (ch == '%')
+	    {
+		try
+		{
+		    res[didx++] = (char)
+			Integer.parseInt(str.substring(sidx+1,sidx+3), 16);
+		    sidx += 2;
+		}
+		catch (NumberFormatException e)
+		{
+		    throw new ParseException(str.substring(sidx,sidx+3) +
+					    " is an invalid code");
+		}
+	    }
+	    else
+		res[didx++] = ch;
+	}
+
+	return String.valueOf(res, 0, didx);
+    }
+
+
+    /**
+     * This method decodes a multipart/form-data encoded string.
+     *
+     * @param     data        the form-data to decode.
+     * @param     cont_type   the content type header (must contain the
+     *			      boundary string).
+     * @param     dir         the directory to create the files in.
+     * @return                an array of name/value pairs, one for each part;
+     *                        the name is the 'name' attribute given in the
+     *                        Content-Disposition header; the value is either
+     *                        the name of the file if a filename attribute was
+     *                        found, or the contents of the part.
+     * @exception IOException If any file operation fails.
+     * @exception ParseException If an error during parsing occurs.
+     * @see #mpFormDataDecode(byte[], java.lang.String, java.lang.String, HTTPClient.FilenameMangler)
+     */
+    public final static NVPair[] mpFormDataDecode(byte[] data, String cont_type,
+						  String dir)
+	    throws IOException, ParseException
+    {
+	return mpFormDataDecode(data, cont_type, dir, null);
+    }
+
+
+    /**
+     * This method decodes a multipart/form-data encoded string. The boundary
+     * is parsed from the <var>cont_type</var> parameter, which must be of the
+     * form 'multipart/form-data; boundary=...'. Any encoded files are created
+     * in the directory specified by <var>dir</var> using the encoded filename.
+     *
+     * <P><em>Note:</em> Does not handle nested encodings (yet).
+     *
+     * <P>Examples: If you're receiving a multipart/form-data encoded response
+     * from a server you could use something like:
+     * <PRE>
+     *     NVPair[] opts = Codecs.mpFormDataDecode(resp.getData(),
+     *                                  resp.getHeader("Content-type"), ".");
+     * </PRE>
+     * If you're using this in a Servlet to decode the body of a request from
+     * a client you could use something like:
+     * <PRE>
+     *     byte[] body = new byte[req.getContentLength()];
+     *     new DataInputStream(req.getInputStream()).readFully(body);
+     *     NVPair[] opts = Codecs.mpFormDataDecode(body, req.getContentType(),
+     *                                             ".");
+     * </PRE>
+     * (where 'req' is the HttpServletRequest).
+     *
+     * <P>Assuming the data received looked something like:
+     * <PRE>
+     * -----------------------------114975832116442893661388290519
+     * Content-Disposition: form-data; name="option"
+     *                                                          
+     * doit
+     * -----------------------------114975832116442893661388290519
+     * Content-Disposition: form-data; name="comment"; filename="comment.txt"
+     *                                                          
+     * Gnus and Gnats are not Gnomes.
+     * -----------------------------114975832116442893661388290519--
+     * </PRE>
+     * you would get one file called <VAR>comment.txt</VAR> in the current
+     * directory, and opts would contain two elements: {"option", "doit"}
+     * and {"comment", "comment.txt"}
+     *
+     * @param     data        the form-data to decode.
+     * @param     cont_type   the content type header (must contain the
+     *			      boundary string).
+     * @param     dir         the directory to create the files in.
+     * @param     mangler     the filename mangler, or null if no mangling is
+     *                        to be done. This is invoked just before each
+     *                        file is created and written, thereby allowing
+     *                        you to control the names of the files.
+     * @return                an array of name/value pairs, one for each part;
+     *                        the name is the 'name' attribute given in the
+     *                        Content-Disposition header; the value is either
+     *                        the name of the file if a filename attribute was
+     *                        found, or the contents of the part.
+     * @exception IOException If any file operation fails.
+     * @exception ParseException If an error during parsing occurs.
+     */
+    public final static NVPair[] mpFormDataDecode(byte[] data, String cont_type,
+						  String dir,
+						  FilenameMangler mangler)
+	    throws IOException, ParseException
+    {
+	// Find and extract boundary string
+
+	String bndstr = Util.getParameter("boundary", cont_type);
+	if (bndstr == null)
+	    throw new ParseException("'boundary' parameter not found in Content-type: " + cont_type);
+
+	byte[] srtbndry = (    "--" + bndstr + "\r\n").getBytes("8859_1"),
+	       boundary = ("\r\n--" + bndstr + "\r\n").getBytes("8859_1"),
+	       endbndry = ("\r\n--" + bndstr + "--"  ).getBytes("8859_1");
+
+
+	// setup search routines
+
+	int[] bs = Util.compile_search(srtbndry),
+	      bc = Util.compile_search(boundary),
+	      be = Util.compile_search(endbndry);
+
+
+	// let's start parsing the actual data
+
+	int start = Util.findStr(srtbndry, bs, data, 0, data.length);
+	if (start == -1)	// didn't even find the start
+	    throw new ParseException("Starting boundary not found: " +
+				     new String(srtbndry, "8859_1"));
+	start += srtbndry.length;
+
+	NVPair[] res  = new NVPair[10];
+	boolean  done = false;
+	int      idx;
+
+	for (idx=0; !done; idx++)
+	{
+	    // find end of this part
+
+	    int end = Util.findStr(boundary, bc, data, start, data.length);
+	    if (end == -1)		// must be the last part
+	    {
+		end = Util.findStr(endbndry, be, data, start, data.length);
+		if (end == -1)
+		    throw new ParseException("Ending boundary not found: " +
+					     new String(endbndry, "8859_1"));
+		done = true;
+	    }
+
+	    // parse header(s)
+
+	    String hdr, name=null, value, filename=null, cont_disp = null;
+
+	    while (true)
+	    {
+		int next = findEOL(data, start) + 2;
+		if (next-2 <= start)  break;	// empty line -> end of headers
+		hdr      = new String(data, start, next-2-start, "8859_1");
+		start    = next;
+
+		// handle line continuation
+		byte ch;
+		while (next < data.length-1  &&
+		       ((ch = data[next]) == ' '  ||  ch == '\t'))
+		{
+		    next   = findEOL(data, start) + 2;
+		    hdr   += new String(data, start, next-2-start, "8859_1");
+		    start  = next;
+		}
+
+		if (!hdr.regionMatches(true, 0, "Content-Disposition", 0, 19))
+		    continue;
+		Vector pcd =
+			Util.parseHeader(hdr.substring(hdr.indexOf(':')+1));
+		HttpHeaderElement elem = Util.getElement(pcd, "form-data");
+
+		if (elem == null)
+		    throw new ParseException("Expected 'Content-Disposition: form-data' in line: "+hdr);
+
+		NVPair[] params = elem.getParams();
+		name = filename = null;
+		for (int pidx=0; pidx<params.length; pidx++)
+		{
+		    if (params[pidx].getName().equalsIgnoreCase("name"))
+			name = params[pidx].getValue();
+		    if (params[pidx].getName().equalsIgnoreCase("filename"))
+			filename = params[pidx].getValue();
+		}
+		if (name == null)
+		    throw new ParseException("'name' parameter not found in header: "+hdr);
+
+		cont_disp = hdr;
+	    }
+
+	    start += 2;
+	    if (start > end)
+		throw new ParseException("End of header not found at offset "+end);
+
+	    if (cont_disp == null)
+		throw new ParseException("Missing 'Content-Disposition' header at offset "+start);
+
+	    // handle data for this part
+
+	    if (filename != null)			// It's a file
+	    {
+		if (mangler != null)
+		    filename = mangler.mangleFilename(filename, name);
+		if (filename != null  &&  filename.length() > 0)
+		{
+		    File file = new File(dir, filename);
+		    FileOutputStream out = new FileOutputStream(file);
+
+		    out.write(data, start, end-start);
+		    out.close();
+		}
+
+		value = filename;
+	    }
+	    else					// It's simple data
+	    {
+		value = new String(data, start, end-start, "8859_1");
+	    }
+
+	    if (idx >= res.length)
+		res = Util.resizeArray(res, idx+10);
+	    res[idx] = new NVPair(name, value);
+
+	    start = end + boundary.length;
+	}
+
+	return Util.resizeArray(res, idx);
+    }
+
+
+    /**
+     * Searches for the next CRLF in an array.
+     *
+     * @param  arr the byte array to search.
+     * @param  off the offset at which to start the search.
+     * @return the position of the CR or (arr.length-2) if not found
+     */
+    private final static int findEOL(byte[] arr, int off)
+    {
+	while (off < arr.length-1  &&
+	       !(arr[off++] == '\r'  &&  arr[off] == '\n'));
+	return off-1;
+    }
+
+    /**
+     * This method encodes name/value pairs and files into a byte array
+     * using the multipart/form-data encoding.
+     *
+     * @param     opts        the simple form-data to encode (may be null);
+     *                        for each NVPair the name refers to the 'name'
+     *                        attribute to be used in the header of the part,
+     *                        and the value is contents of the part.
+     * @param     files       the files to encode (may be null); for each
+     *                        NVPair the name refers to the 'name' attribute
+     *                        to be used in the header of the part, and the
+     *                        value is the actual filename (the file will be
+     *                        read and it's contents put in the body of that
+     *                        part).
+     * @param     ct_hdr      this returns a new NVPair in the 0'th element
+     *                        which contains name = "Content-Type",
+     *			      value = "multipart/form-data; boundary=..."
+     *                        (the reason this parameter is an array is
+     *                        because a) that's the only way to simulate
+     *                        pass-by-reference and b) you need an array for
+     *                        the headers parameter to the Post() or Put()
+     *                        anyway).
+     * @return                an encoded byte array containing all the opts
+     *			      and files.
+     * @exception IOException If any file operation fails.
+     * @see #mpFormDataEncode(HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.FilenameMangler)
+     */
+    public final static byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files,
+						NVPair[] ct_hdr)
+	    throws IOException
+    {
+	return mpFormDataEncode(opts, files, ct_hdr, null);
+    }
+
+
+    private static NVPair[] dummy = new NVPair[0];
+
+    /**
+     * This method encodes name/value pairs and files into a byte array
+     * using the multipart/form-data encoding. The boundary is returned
+     * as part of <var>ct_hdr</var>.
+     * <BR>Example:
+     * <PRE>
+     *     NVPair[] opts = { new NVPair("option", "doit") };
+     *     NVPair[] file = { new NVPair("comment", "comment.txt") };
+     *     NVPair[] hdrs = new NVPair[1];
+     *     byte[]   data = Codecs.mpFormDataEncode(opts, file, hdrs);
+     *     con.Post("/cgi-bin/handle-it", data, hdrs);
+     * </PRE>
+     * <VAR>data</VAR> will look something like the following:
+     * <PRE>
+     * -----------------------------114975832116442893661388290519
+     * Content-Disposition: form-data; name="option"
+     *                                                          
+     * doit
+     * -----------------------------114975832116442893661388290519
+     * Content-Disposition: form-data; name="comment"; filename="comment.txt"
+     * Content-Type: text/plain
+     *                                                          
+     * Gnus and Gnats are not Gnomes.
+     * -----------------------------114975832116442893661388290519--
+     * </PRE>
+     * where the "Gnus and Gnats ..." is the contents of the file
+     * <VAR>comment.txt</VAR> in the current directory.
+     *
+     * <P>If no elements are found in the parameters then a zero-length
+     * byte[] is returned and the content-type is set to
+     * <var>application/octet-string</var> (because a multipart must
+     * always have at least one part.
+     *
+     * <P>For files an attempt is made to discover the content-type, and if
+     * found a Content-Type header will be added to that part. The content type
+     * is retrieved using java.net.URLConnection.guessContentTypeFromName() -
+     * see java.net.URLConnection.setFileNameMap() for how to modify that map.
+     * Note that under JDK 1.1 by default the map seems to be empty. If you
+     * experience troubles getting the server to accept the data then make
+     * sure the fileNameMap is returning a content-type for each file (this
+     * may mean you'll have to set your own).
+     *
+     * @param     opts        the simple form-data to encode (may be null);
+     *                        for each NVPair the name refers to the 'name'
+     *                        attribute to be used in the header of the part,
+     *                        and the value is contents of the part.
+     *                        null elements in the array are ingored.
+     * @param     files       the files to encode (may be null); for each
+     *                        NVPair the name refers to the 'name' attribute
+     *                        to be used in the header of the part, and the
+     *                        value is the actual filename (the file will be
+     *                        read and it's contents put in the body of
+     *                        that part). null elements in the array
+     *                        are ingored.
+     * @param     ct_hdr      this returns a new NVPair in the 0'th element
+     *                        which contains name = "Content-Type",
+     *			      value = "multipart/form-data; boundary=..."
+     *                        (the reason this parameter is an array is
+     *                        because a) that's the only way to simulate
+     *                        pass-by-reference and b) you need an array for
+     *                        the headers parameter to the Post() or Put()
+     *                        anyway). The exception to this is that if no
+     *                        opts or files are given the type is set to
+     *                        "application/octet-stream" instead.
+     * @param     mangler     the filename mangler, or null if no mangling is
+     *                        to be done. This allows you to change the name
+     *                        used in the <var>filename</var> attribute of the
+     *                        Content-Disposition header. Note: the mangler
+     *                        will be invoked twice for each filename.
+     * @return                an encoded byte array containing all the opts
+     *			      and files.
+     * @exception IOException If any file operation fails.
+     */
+    public final static byte[] mpFormDataEncode(NVPair[] opts, NVPair[] files,
+						NVPair[] ct_hdr,
+						FilenameMangler mangler)
+	    throws IOException
+    {
+	byte[] boundary  = Boundary.getBytes("8859_1"),
+	       cont_disp = ContDisp.getBytes("8859_1"),
+	       cont_type = ContType.getBytes("8859_1"),
+	       filename  = FileName.getBytes("8859_1");
+	int len = 0,
+	    hdr_len = boundary.length + cont_disp.length+1 + 2 +  2;
+	    //        \r\n --  bnd      \r\n C-D: ..; n=".." \r\n \r\n
+
+	if (opts == null)   opts  = dummy;
+	if (files == null)  files = dummy;
+
+
+	// Calculate the length of the data
+
+	for (int idx=0; idx<opts.length; idx++)
+	{
+	    if (opts[idx] == null)  continue;
+
+	    len += hdr_len + opts[idx].getName().length() +
+		   opts[idx].getValue().length();
+	}
+
+	for (int idx=0; idx<files.length; idx++)
+	{
+	    if (files[idx] == null)  continue;
+
+	    File file = new File(files[idx].getValue());
+	    String fname = file.getName();
+	    if (mangler != null)
+		fname = mangler.mangleFilename(fname, files[idx].getName());
+	    if (fname != null)
+	    {
+		len += hdr_len + files[idx].getName().length() + filename.length;
+		len += fname.length() + file.length();
+
+		String ct = CT.getContentType(file.getName());
+		if (ct != null)
+		    len += cont_type.length + ct.length();
+	    }
+	}
+
+	if (len == 0)
+	{
+	    ct_hdr[0] = new NVPair("Content-Type", "application/octet-stream");
+	    return new byte[0];
+	}
+
+	len -= 2;			// first CR LF is not written
+	len += boundary.length + 2 + 2;	// \r\n -- bnd -- \r\n
+
+
+	// Now fill array
+
+	byte[] res = new byte[len];
+	int    pos = 0;
+
+	NewBound: for (int new_c=0x30303030; new_c!=0x7A7A7A7A; new_c++)
+	{
+	    pos = 0;
+
+	    // modify boundary in hopes that it will be unique
+	    while (!BoundChar.get(new_c     & 0xff)) new_c += 0x00000001;
+	    while (!BoundChar.get(new_c>>8  & 0xff)) new_c += 0x00000100;
+	    while (!BoundChar.get(new_c>>16 & 0xff)) new_c += 0x00010000;
+	    while (!BoundChar.get(new_c>>24 & 0xff)) new_c += 0x01000000;
+	    boundary[40] = (byte) (new_c     & 0xff);
+	    boundary[42] = (byte) (new_c>>8  & 0xff);
+	    boundary[44] = (byte) (new_c>>16 & 0xff);
+	    boundary[46] = (byte) (new_c>>24 & 0xff);
+
+	    int off = 2;
+	    int[] bnd_cmp = Util.compile_search(boundary);
+
+	    for (int idx=0; idx<opts.length; idx++)
+	    {
+		if (opts[idx] == null)  continue;
+
+		System.arraycopy(boundary, off, res, pos, boundary.length-off);
+		pos += boundary.length - off;
+		off  = 0;
+		int  start = pos;
+
+		System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
+		pos += cont_disp.length;
+
+		int nlen = opts[idx].getName().length();
+		System.arraycopy(opts[idx].getName().getBytes("8859_1"), 0, res, pos, nlen);
+		pos += nlen;
+
+		res[pos++] = (byte) '"';
+		res[pos++] = (byte) '\r';
+		res[pos++] = (byte) '\n';
+		res[pos++] = (byte) '\r';
+		res[pos++] = (byte) '\n';
+
+		int vlen = opts[idx].getValue().length();
+		System.arraycopy(opts[idx].getValue().getBytes("8859_1"), 0, res, pos, vlen);
+		pos += vlen;
+
+		if ((pos-start) >= boundary.length  &&
+		    Util.findStr(boundary, bnd_cmp, res, start, pos) != -1)
+		    continue NewBound;
+	    }
+
+	    for (int idx=0; idx<files.length; idx++)
+	    {
+		if (files[idx] == null)  continue;
+
+		File file = new File(files[idx].getValue());
+		String fname = file.getName();
+		if (mangler != null)
+		    fname = mangler.mangleFilename(fname, files[idx].getName());
+		if (fname == null)  continue;
+
+		System.arraycopy(boundary, off, res, pos, boundary.length-off);
+		pos += boundary.length - off;
+		off  = 0;
+		int start = pos;
+
+		System.arraycopy(cont_disp, 0, res, pos, cont_disp.length);
+		pos += cont_disp.length;
+
+		int nlen = files[idx].getName().length();
+		System.arraycopy(files[idx].getName().getBytes("8859_1"), 0, res, pos, nlen);
+		pos += nlen;
+
+		System.arraycopy(filename, 0, res, pos, filename.length);
+		pos += filename.length;
+
+		nlen = fname.length();
+		System.arraycopy(fname.getBytes("8859_1"), 0, res, pos, nlen);
+		pos += nlen;
+
+		res[pos++] = (byte) '"';
+
+		String ct = CT.getContentType(file.getName());
+		if (ct != null)
+		{
+		    System.arraycopy(cont_type, 0, res, pos, cont_type.length);
+		    pos += cont_type.length;
+		    System.arraycopy(ct.getBytes("8859_1"), 0, res, pos, ct.length());
+		    pos += ct.length();
+		}
+
+		res[pos++] = (byte) '\r';
+		res[pos++] = (byte) '\n';
+		res[pos++] = (byte) '\r';
+		res[pos++] = (byte) '\n';
+
+		nlen = (int) file.length();
+		FileInputStream fin = new FileInputStream(file);
+		while (nlen > 0)
+		{
+		    int got = fin.read(res, pos, nlen);
+		    nlen -= got;
+		    pos += got;
+		}
+		fin.close();
+
+		if ((pos-start) >= boundary.length  &&
+		    Util.findStr(boundary, bnd_cmp, res, start, pos) != -1)
+		    continue NewBound;
+	    }
+
+	    break NewBound;
+	}
+
+	System.arraycopy(boundary, 0, res, pos, boundary.length);
+	pos += boundary.length;
+	res[pos++] = (byte) '-';
+	res[pos++] = (byte) '-';
+	res[pos++] = (byte) '\r';
+	res[pos++] = (byte) '\n';
+
+	if (pos != len)
+	    throw new Error("Calculated "+len+" bytes but wrote "+pos+" bytes!");
+
+	/* the boundary parameter should be quoted (rfc-2046, section 5.1.1)
+	 * but too many script authors are not capable of reading specs...
+	 * So, I give up and don't quote it.
+	 */
+	ct_hdr[0] = new NVPair("Content-Type",
+			       "multipart/form-data; boundary=" +
+			       new String(boundary, 4, boundary.length-4, "8859_1"));
+
+	return res;
+    }
+
+    private static class CT extends URLConnection
+    {
+	protected static final String getContentType(String fname)
+	{
+	    return guessContentTypeFromName(fname);
+	}
+
+	private CT() { super(null); }
+	public void connect() { }
+    }
+
+
+    /**
+     * Turns an array of name/value pairs into the string
+     * "name1=value1&name2=value2&name3=value3". The names and values are
+     * first urlencoded. This is the form in which form-data is passed to
+     * a cgi script.
+     *
+     * @param pairs the array of name/value pairs
+     * @return a string containg the encoded name/value pairs
+     */
+    public final static String nv2query(NVPair pairs[])
+    {
+	if (pairs == null)
+	    return null;
+
+
+	int          idx;
+	StringBuffer qbuf = new StringBuffer();
+
+	for (idx = 0; idx < pairs.length; idx++)
+	{
+	    if (pairs[idx] != null)
+		qbuf.append(URLEncode(pairs[idx].getName()) + "=" +
+			    URLEncode(pairs[idx].getValue()) + "&");
+	}
+
+	if (qbuf.length() > 0)
+	    qbuf.setLength(qbuf.length()-1);	// remove trailing '&'
+
+	return qbuf.toString();
+    }
+
+
+    /**
+     * Turns a string of the form "name1=value1&name2=value2&name3=value3"
+     * into an array of name/value pairs. The names and values are
+     * urldecoded. The query string is in the form in which form-data is
+     * received in a cgi script.
+     *
+     * @param query the query string containing the encoded name/value pairs
+     * @return an array of NVPairs
+     * @exception ParseException If the '=' is missing in any field, or if
+     *				 the urldecoding of the name or value fails
+     */
+    public final static NVPair[] query2nv(String query)  throws ParseException
+    {
+	if (query == null) return null;
+
+	int idx = -1,
+	    cnt = 1;
+	while ((idx = query.indexOf('&', idx+1)) != -1)  cnt ++;
+	NVPair[] pairs = new NVPair[cnt];
+
+	for (idx=0, cnt=0; cnt<pairs.length; cnt++)
+	{
+	    int eq  = query.indexOf('=', idx);
+	    int end = query.indexOf('&', idx);
+
+	    if (end == -1)  end = query.length();
+
+	    if (eq == -1  ||  eq >= end)
+		throw new ParseException("'=' missing in " +
+					 query.substring(idx, end));
+
+	    pairs[cnt] =
+		    new  NVPair(URLDecode(query.substring(idx,eq)),
+				URLDecode(query.substring(eq+1,end)));
+
+	    idx = end + 1;
+	}
+
+	return pairs;
+    }
+
+
+    /**
+     * Encodes data used the chunked encoding. <var>last</var> signales if
+     * this is the last chunk, in which case the appropriate footer is
+     * generated.
+     *
+     * @param data  the data to be encoded; may be null.
+     * @param ftrs  optional headers to include in the footer (ignored if
+     *              not last); may be null.
+     * @param last  whether this is the last chunk.
+     * @return an array of bytes containing the chunk
+     */
+    public final static byte[] chunkedEncode(byte[] data, NVPair[] ftrs,
+					     boolean last)
+    {
+	return
+	    chunkedEncode(data, 0, data == null ? 0 : data.length, ftrs, last);
+    }
+
+
+    /**
+     * Encodes data used the chunked encoding. <var>last</var> signales if
+     * this is the last chunk, in which case the appropriate footer is
+     * generated.
+     *
+     * @param data  the data to be encoded; may be null.
+     * @param off   an offset into the <var>data</var>
+     * @param len   the number of bytes to take from <var>data</var>
+     * @param ftrs  optional headers to include in the footer (ignored if
+     *              not last); may be null.
+     * @param last  whether this is the last chunk.
+     * @return an array of bytes containing the chunk
+     */
+    public final static byte[] chunkedEncode(byte[] data, int off, int len,
+					     NVPair[] ftrs, boolean last)
+    {
+	if (data == null)
+	{
+	    data = new byte[0];
+	    len  = 0;
+	}
+	if (last  &&  ftrs == null) ftrs = new NVPair[0];
+
+	// get length of data as hex-string
+	String hex_len = Integer.toString(len, 16);
+
+
+	// calculate length of chunk
+
+	int res_len = 0;
+	if (len > 0)	// len CRLF data CRLF
+	    res_len += hex_len.length() + 2 + len + 2;
+
+	if (last)
+	{
+	    res_len += 1 + 2;	// 0 CRLF
+	    for (int idx=0; idx<ftrs.length; idx++)
+		res_len += ftrs[idx].getName().length() + 2 +	// name ": "
+			   ftrs[idx].getValue().length() + 2;	// value CRLF
+	    res_len += 2;	// CRLF
+	}
+
+	// allocate result
+
+	byte[] res = new byte[res_len];
+	int    r_off = 0;
+
+
+	// fill result
+
+	if (len > 0)
+	{
+	    int hlen = hex_len.length();
+	    try
+		{ System.arraycopy(hex_len.getBytes("8859_1"), 0, res, r_off, hlen); }
+	    catch (UnsupportedEncodingException uee)
+		{ throw new Error(uee.toString()); }
+	    r_off += hlen;
+	    res[r_off++] = (byte) '\r';
+	    res[r_off++] = (byte) '\n';
+
+	    System.arraycopy(data, off, res, r_off, len);
+	    r_off += len;
+	    res[r_off++] = (byte) '\r';
+	    res[r_off++] = (byte) '\n';
+	}
+
+	if (last)
+	{
+	    res[r_off++] = (byte) '0';
+	    res[r_off++] = (byte) '\r';
+	    res[r_off++] = (byte) '\n';
+
+	    for (int idx=0; idx<ftrs.length; idx++)
+	    {
+		int nlen = ftrs[idx].getName().length();
+		try
+		    { System.arraycopy(ftrs[idx].getName().getBytes("8859_1"),
+				       0, res, r_off, nlen); }
+		catch (UnsupportedEncodingException uee)
+		    { throw new Error(uee.toString()); }
+		r_off += nlen;
+
+		res[r_off++] = (byte) ':';
+		res[r_off++] = (byte) ' ';
+
+		int vlen = ftrs[idx].getValue().length();
+		try
+		    { System.arraycopy(ftrs[idx].getValue().getBytes("8859_1"),
+				       0, res, r_off, vlen); }
+		catch (UnsupportedEncodingException uee)
+		    { throw new Error(uee.toString()); }
+		r_off += vlen;
+
+		res[r_off++] = (byte) '\r';
+		res[r_off++] = (byte) '\n';
+	    }
+
+	    res[r_off++] = (byte) '\r';
+	    res[r_off++] = (byte) '\n';
+	}
+
+	if (r_off != res.length)
+	    throw new Error("Calculated "+res.length+" bytes but wrote "+r_off+" bytes!");
+
+	return res;
+    }
+
+
+    /**
+     * Decodes chunked data. The chunks are read from an InputStream, which
+     * is assumed to be correctly positioned. Use 'xxx instanceof byte[]'
+     * and 'xxx instanceof NVPair[]' to determine if this was data or the
+     * last chunk.
+     *
+     * @param  input  the stream from which to read the next chunk.
+     * @return If this was a data chunk then it returns a byte[]; else
+     *         it's the footer and it returns a NVPair[] containing the
+     *         footers.
+     * @exception ParseException If any exception during parsing occured.
+     * @exception IOException    If any exception during reading occured.
+     */
+    public final static Object chunkedDecode(InputStream input)
+	    throws ParseException, IOException
+    {
+	long clen = getChunkLength(input);
+
+	if (clen > Integer.MAX_VALUE)		// Huston, what the hell are you sending?
+	    throw new ParseException("Can't deal with chunk lengths greater " +
+				     "Integer.MAX_VALUE: " + clen + " > " +
+				     Integer.MAX_VALUE);
+
+	if (clen > 0)			// it's a chunk
+	{
+	    byte[] res = new byte[(int) clen];
+
+	    int off = 0, len = 0;
+	    while (len != -1  &&  off < res.length)
+	    {
+		len  = input.read(res, off, res.length-off);
+		off += len;
+	    }
+
+	    if (len == -1)
+		throw new ParseException("Premature EOF while reading chunk;" +
+					 "Expected: "+res.length+" Bytes, " +
+					 "Received: "+(off+1)+" Bytes");
+
+	    input.read();	// CR
+	    input.read();	// LF
+
+	    return res;
+	}
+	else				// it's the end
+	{
+	    NVPair[] res = new NVPair[0];
+
+	    BufferedReader reader = new BufferedReader(new InputStreamReader(input, "8859_1"));
+	    String line;
+
+	    // read and parse footer
+	    while ((line = reader.readLine()) != null  &&  line.length() > 0)
+	    {
+		int colon = line.indexOf(':');
+		if (colon == -1)
+		    throw new ParseException("Error in Footer format: no "+
+					     "':' found in '" + line + "'");
+		res = Util.resizeArray(res, res.length+1);
+		res[res.length-1] = new NVPair(line.substring(0, colon).trim(),
+					       line.substring(colon+1).trim());
+	    }
+
+	    return res;
+	}
+
+    }
+
+
+    /**
+     * Gets the length of the chunk.
+     *
+     * @param  input  the stream from which to read the next chunk.
+     * @return  the length of chunk to follow (w/o trailing CR LF).
+     * @exception ParseException If any exception during parsing occured.
+     * @exception IOException    If any exception during reading occured.
+     */
+    final static long getChunkLength(InputStream input)
+	    throws ParseException, IOException
+    {
+	byte[] hex_len = new byte[16];	// if they send more than 8EB chunks...
+	int    off     = 0,
+	       ch;
+
+
+	// read chunk length
+
+	while ((ch = input.read()) > 0  &&  (ch == ' '  ||  ch == '\t')) ;
+	if (ch < 0)
+	    throw new EOFException("Premature EOF while reading chunk length");
+	hex_len[off++] = (byte) ch;
+	while ((ch = input.read()) > 0  &&  ch != '\r'  &&  ch != '\n'  &&
+		ch != ' '  &&  ch != '\t'  &&  ch != ';'  &&
+		off < hex_len.length)
+	    hex_len[off++] = (byte) ch;
+
+	while ((ch == ' '  ||  ch == '\t')  &&  (ch = input.read()) > 0) ;
+	if (ch == ';')		// chunk-ext (ignore it)
+	    while ((ch = input.read()) > 0  &&  ch != '\r'  &&  ch != '\n') ;
+
+	if (ch < 0)
+	    throw new EOFException("Premature EOF while reading chunk length");
+	if (ch != '\n'  &&  (ch != '\r'  ||  input.read() != '\n'))
+	    throw new ParseException("Didn't find valid chunk length: " +
+				     new String(hex_len, 0, off, "8859_1"));
+
+	// parse chunk length
+
+	try
+	    { return Long.parseLong(new String(hex_len, 0, off, "8859_1").trim(),
+				    16); }
+	catch (NumberFormatException nfe)
+	    { throw new ParseException("Didn't find valid chunk length: " +
+					new String(hex_len, 0, off, "8859_1") ); }
+    }
+
+}
diff --git a/HTTPClient/ContentEncodingModule.java b/HTTPClient/ContentEncodingModule.java
new file mode 100644
index 0000000..8b24a27
--- /dev/null
+++ b/HTTPClient/ContentEncodingModule.java
@@ -0,0 +1,219 @@
+/*
+ * @(#)ContentEncodingModule.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.util.Vector;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.GZIPInputStream;
+
+
+/**
+ * This module handles the Content-Encoding response header. It currently
+ * handles the "gzip", "deflate", "compress" and "identity" tokens.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class ContentEncodingModule implements HTTPClientModule
+{
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+	    throws ModuleException
+    {
+	// parse Accept-Encoding header
+
+	int idx;
+	NVPair[] hdrs = req.getHeaders();
+	for (idx=0; idx<hdrs.length; idx++)
+	    if (hdrs[idx].getName().equalsIgnoreCase("Accept-Encoding"))
+		break;
+
+	Vector pae;
+	if (idx == hdrs.length)
+	{
+	    hdrs = Util.resizeArray(hdrs, idx+1);
+	    req.setHeaders(hdrs);
+	    pae = new Vector();
+	}
+	else
+	{
+	    try
+		{ pae = Util.parseHeader(hdrs[idx].getValue()); }
+	    catch (ParseException pe)
+		{ throw new ModuleException(pe.toString()); }
+	}
+
+
+	// done if "*;q=1.0" present
+
+	HttpHeaderElement all = Util.getElement(pae, "*");
+	if (all != null)
+	{
+	    NVPair[] params = all.getParams();
+	    for (idx=0; idx<params.length; idx++)
+		if (params[idx].getName().equalsIgnoreCase("q"))  break;
+
+	    if (idx == params.length)	// no qvalue, i.e. q=1.0
+		return REQ_CONTINUE;
+
+	    if (params[idx].getValue() == null  ||
+		params[idx].getValue().length() == 0)
+		throw new ModuleException("Invalid q value for \"*\" in " +
+					  "Accept-Encoding header: ");
+
+	    try
+	    {
+		if (Float.valueOf(params[idx].getValue()).floatValue() > 0.)
+		    return REQ_CONTINUE;
+	    }
+	    catch (NumberFormatException nfe)
+	    {
+		throw new ModuleException("Invalid q value for \"*\" in " +
+				"Accept-Encoding header: " + nfe.getMessage());
+	    }
+	}
+
+
+	// Add gzip, deflate and compress tokens to the Accept-Encoding header
+
+	if (!pae.contains(new HttpHeaderElement("deflate")))
+	    pae.addElement(new HttpHeaderElement("deflate"));
+	if (!pae.contains(new HttpHeaderElement("gzip")))
+	    pae.addElement(new HttpHeaderElement("gzip"));
+	if (!pae.contains(new HttpHeaderElement("x-gzip")))
+	    pae.addElement(new HttpHeaderElement("x-gzip"));
+	if (!pae.contains(new HttpHeaderElement("compress")))
+	    pae.addElement(new HttpHeaderElement("compress"));
+	if (!pae.contains(new HttpHeaderElement("x-compress")))
+	    pae.addElement(new HttpHeaderElement("x-compress"));
+
+	hdrs[idx] = new NVPair("Accept-Encoding", Util.assembleHeader(pae));
+
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+    {
+	return RSP_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+		throws IOException, ModuleException
+    {
+	String ce = resp.getHeader("Content-Encoding");
+	if (ce == null  ||  req.getMethod().equals("HEAD")  ||
+	    resp.getStatusCode() == 206)
+		return;
+
+	Vector pce;
+	try
+	    { pce = Util.parseHeader(ce); }
+	catch (ParseException pe)
+	    { throw new ModuleException(pe.toString()); }
+
+	if (pce.size() == 0)
+	    return;
+
+	String encoding = ((HttpHeaderElement) pce.firstElement()).getName();
+	if (encoding.equalsIgnoreCase("gzip")  ||
+	    encoding.equalsIgnoreCase("x-gzip"))
+	{
+	    Log.write(Log.MODS, "CEM:   pushing gzip-input-stream");
+
+	    resp.inp_stream = new GZIPInputStream(resp.inp_stream);
+	    pce.removeElementAt(pce.size()-1);
+	    resp.deleteHeader("Content-length");
+	}
+	else if (encoding.equalsIgnoreCase("deflate"))
+	{
+	    Log.write(Log.MODS, "CEM:   pushing inflater-input-stream");
+
+	    resp.inp_stream = new InflaterInputStream(resp.inp_stream);
+	    pce.removeElementAt(pce.size()-1);
+	    resp.deleteHeader("Content-length");
+	}
+	else if (encoding.equalsIgnoreCase("compress")  ||
+		 encoding.equalsIgnoreCase("x-compress"))
+	{
+	    Log.write(Log.MODS, "CEM:   pushing uncompress-input-stream");
+
+	    resp.inp_stream = new UncompressInputStream(resp.inp_stream);
+	    pce.removeElementAt(pce.size()-1);
+	    resp.deleteHeader("Content-length");
+	}
+	else if (encoding.equalsIgnoreCase("identity"))
+	{
+	    Log.write(Log.MODS, "CEM:   ignoring 'identity' token");
+	    pce.removeElementAt(pce.size()-1);
+	}
+	else
+	{
+	    Log.write(Log.MODS, "CEM:   Unknown content encoding '" +
+				encoding + "'");
+	}
+
+	if (pce.size() > 0)
+	    resp.setHeader("Content-Encoding", Util.assembleHeader(pce));
+	else
+	    resp.deleteHeader("Content-Encoding");
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)
+    {
+    }
+}
diff --git a/HTTPClient/ContentMD5Module.java b/HTTPClient/ContentMD5Module.java
new file mode 100644
index 0000000..7854302
--- /dev/null
+++ b/HTTPClient/ContentMD5Module.java
@@ -0,0 +1,185 @@
+/*
+ * @(#)ContentMD5Module.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+
+/**
+ * This module handles the Content-MD5 response header. If this header was
+ * sent with a response and the entity isn't encoded using an unknown
+ * transport encoding then an MD5InputStream is wrapped around the response
+ * input stream. The MD5InputStream keeps a running digest and checks this
+ * against the expected digest from the Content-MD5 header the stream is
+ * closed. An IOException is thrown at that point if the digests don't match.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class ContentMD5Module implements HTTPClientModule
+{
+    // Constructors
+
+    ContentMD5Module()
+    {
+    }
+
+
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+    {
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+    {
+	return RSP_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+		throws IOException, ModuleException
+    {
+	if (req.getMethod().equals("HEAD"))
+	    return;
+
+	String md5_digest = resp.getHeader("Content-MD5");
+	String trailer    = resp.getHeader("Trailer");
+	boolean md5_tok = false;
+	try
+	{
+	    if (trailer != null)
+		md5_tok = Util.hasToken(trailer, "Content-MD5");
+	}
+	catch (ParseException pe)
+	    { throw new ModuleException(pe.toString()); }
+
+	if ((md5_digest == null  &&  !md5_tok)  ||
+	    resp.getHeader("Transfer-Encoding") != null)
+	    return;
+
+	if (md5_digest != null)
+	    Log.write(Log.MODS, "CMD5M: Received digest: " + md5_digest +
+				" - pushing md5-check-stream");
+	else
+	    Log.write(Log.MODS, "CMD5M: Expecting digest in trailer " +
+				" - pushing md5-check-stream");
+
+	resp.inp_stream = new MD5InputStream(resp.inp_stream,
+					     new VerifyMD5(resp));
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)
+    {
+    }
+}
+
+
+class VerifyMD5 implements HashVerifier
+{
+    RoResponse resp;
+
+
+    public VerifyMD5(RoResponse resp)
+    {
+	this.resp = resp;
+    }
+
+
+    public void verifyHash(byte[] hash, long len)  throws IOException
+    {
+	String hdr;
+	try
+	{
+	    if ((hdr = resp.getHeader("Content-MD5")) == null)
+		hdr = resp.getTrailer("Content-MD5");
+	}
+	catch (IOException ioe)
+	    { return; }		// shouldn't happen
+
+	if (hdr == null)  return;
+
+	byte[] ContMD5 = Codecs.base64Decode(hdr.trim().getBytes("8859_1"));
+
+	for (int idx=0; idx<hash.length; idx++)
+	{
+	    if (hash[idx] != ContMD5[idx])
+		throw new IOException("MD5-Digest mismatch: expected " +
+				      hex(ContMD5) + " but calculated " +
+				      hex(hash));
+	}
+
+	Log.write(Log.MODS, "CMD5M: hash successfully verified");
+    }
+
+
+    /**
+     * Produce a string of the form "A5:22:F1:0B:53"
+     */
+    private static String hex(byte[] buf)
+    {
+	StringBuffer str = new StringBuffer(buf.length*3);
+	for (int idx=0; idx<buf.length; idx++)
+	{
+	    str.append(Character.forDigit((buf[idx] >>> 4) & 15, 16));
+	    str.append(Character.forDigit(buf[idx] & 15, 16));
+	    str.append(':');
+	}
+	str.setLength(str.length()-1);
+
+	return str.toString();
+    }
+}
diff --git a/HTTPClient/Cookie.java b/HTTPClient/Cookie.java
new file mode 100644
index 0000000..40d2435
--- /dev/null
+++ b/HTTPClient/Cookie.java
@@ -0,0 +1,574 @@
+/*
+ * @(#)Cookie.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.Serializable;
+import java.net.ProtocolException;
+import java.util.Date;
+
+
+/**
+ * This class represents an http cookie as specified in <a
+ * href="http://home.netscape.com/newsref/std/cookie_spec.html">Netscape's
+ * cookie spec</a>; however, because not even Netscape follows their own spec,
+ * and because very few folks out there actually read specs but instead just
+ * look whether Netscape accepts their stuff, the Set-Cookie header field
+ * parser actually tries to follow what Netscape has implemented, instead of
+ * what the spec says. Additionally, the parser it will also recognize the
+ * Max-Age parameter from <a
+ * href="http://www.ietf.org/rfc/rfc2109.txt">rfc-2109</a>, as that uses the
+ * same header field (Set-Cookie).
+ *
+ * <P>Some notes about how Netscape (4.7) parses:
+ * <ul>
+ * <LI>Quoting: only quotes around the expires value are recognized as such;
+ *     quotes around any other value are treated as part of the value.
+ * <LI>White space: white space around names and values is ignored
+ * <LI>Default path: if no path parameter is given, the path defaults to the
+ *     path in the request-uri up to, but not including, the last '/'. Note
+ *     that this is entirely different from what the spec says.
+ * <LI>Commas and other delimiters: Netscape just parses until the next ';'.
+ *     This means will allow commas etc inside values.
+ * </ul>
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public class Cookie implements Serializable
+{
+    /** Make this compatible with V0.3-2 */
+    private static final long serialVersionUID = 8599975325569296615L;
+
+    protected String  name;
+    protected String  value;
+    protected Date    expires;
+    protected String  domain;
+    protected String  path;
+    protected boolean secure;
+
+
+    /**
+     * Create a cookie.
+     *
+     * @param name    the cookie name
+     * @param value   the cookie value
+     * @param domain  the host this cookie will be sent to
+     * @param path    the path prefix for which this cookie will be sent
+     * @param epxires the Date this cookie expires, null if at end of
+     *                session
+     * @param secure  if true this cookie will only be over secure connections
+     * @exception NullPointerException if <var>name</var>, <var>value</var>,
+     *                                 <var>domain</var>, or <var>path</var>
+     *                                 is null
+     * @since V0.3-1
+     */
+    public Cookie(String name, String value, String domain, String path,
+		  Date expires, boolean secure)
+    {
+	if (name == null)   throw new NullPointerException("missing name");
+	if (value == null)  throw new NullPointerException("missing value");
+	if (domain == null) throw new NullPointerException("missing domain");
+	if (path == null)   throw new NullPointerException("missing path");
+
+	this.name    = name;
+	this.value   = value;
+	this.domain  = domain.toLowerCase();
+	this.path    = path;
+	this.expires = expires;
+	this.secure  = secure;
+
+	if (this.domain.indexOf('.') == -1)  this.domain += ".local";
+    }
+
+
+    /**
+     * Use <code>parse()</code> to create cookies.
+     *
+     * @see #parse(java.lang.String, HTTPClient.RoRequest)
+     */
+    protected Cookie(RoRequest req)
+    {
+	name    = null;
+	value   = null;
+	expires = null;
+	domain  = req.getConnection().getHost();
+	if (domain.indexOf('.') == -1)  domain += ".local";
+	path    = Util.getPath(req.getRequestURI());
+	/* This does not follow netscape's spec at all, but it's the way
+	 * netscape seems to do it, and because people rely on that we
+	 * therefore also have to do it...
+	 */
+	int slash = path.lastIndexOf('/');
+	if (slash >= 0)
+	    path = path.substring(0, slash);
+	secure = false;
+    }
+
+
+    /**
+     * Parses the Set-Cookie header into an array of Cookies.
+     *
+     * @param set_cookie the Set-Cookie header received from the server
+     * @param req the request used
+     * @return an array of Cookies as parsed from the Set-Cookie header
+     * @exception ProtocolException if an error occurs during parsing
+     */
+    protected static Cookie[] parse(String set_cookie, RoRequest req)
+		throws ProtocolException
+    {
+        int    beg = 0,
+               end = 0,
+	       start = 0;
+        char[] buf = set_cookie.toCharArray();
+        int    len = buf.length;
+
+        Cookie cookie_arr[] = new Cookie[0], curr;
+
+
+        cookies: while (true)                    // get all cookies
+        {
+            beg = Util.skipSpace(buf, beg);
+            if (beg >= len)  break;	// no more left
+	    if (buf[beg] == ',')	// empty header
+	    {
+		beg++;
+		continue;
+	    }
+
+	    curr  = new Cookie(req);
+	    start = beg;
+
+	    // get cookie name and value first
+
+	    end = set_cookie.indexOf('=', beg);
+	    if (end == -1)
+		throw new ProtocolException("Bad Set-Cookie header: " +
+					    set_cookie + "\nNo '=' found " +
+					    "for token starting at " +
+					    "position " + beg);
+	    curr.name = set_cookie.substring(beg, end).trim(); 
+
+	    beg = Util.skipSpace(buf, end+1);
+	    int comma = set_cookie.indexOf(',', beg);
+	    int semic = set_cookie.indexOf(';', beg);
+	    if (comma == -1  &&  semic == -1)  end = len;
+	    else if (comma == -1)  end = semic;
+	    else if (semic == -1)  end = comma;
+	    else
+	    {
+		if (comma > semic)
+		    end = semic;
+		else
+		{
+		    // try to handle broken servers which put commas
+		    // into cookie values
+		    int eq = set_cookie.indexOf('=', comma);
+		    if (eq > 0  &&  eq < semic)
+			end = set_cookie.lastIndexOf(',', eq);
+		    else
+			end = semic;
+		}
+	    }
+	    curr.value = set_cookie.substring(beg, end).trim();
+
+	    beg = end;
+
+	    // now parse attributes
+
+	    boolean legal = true;
+	    parts: while (true)			// parse all parts
+	    {
+		if (beg >= len  ||  buf[beg] == ',')  break;
+
+		// skip empty fields
+		if (buf[beg] == ';')
+		{
+		    beg = Util.skipSpace(buf, beg+1);
+		    continue;
+		}
+
+		// first check for secure, as this is the only one w/o a '='
+		if ((beg+6 <= len)  &&
+		    set_cookie.regionMatches(true, beg, "secure", 0, 6))
+		{
+		    curr.secure = true;
+		    beg += 6;
+
+		    beg = Util.skipSpace(buf, beg);
+		    if (beg < len  &&  buf[beg] == ';')	// consume ";"
+			beg = Util.skipSpace(buf, beg+1);
+		    else if (beg < len  &&  buf[beg] != ',')
+			throw new ProtocolException("Bad Set-Cookie header: " +
+						    set_cookie + "\nExpected " +
+						    "';' or ',' at position " +
+						    beg);
+
+		    continue;
+		}
+
+		// alright, must now be of the form x=y
+		end = set_cookie.indexOf('=', beg);
+		if (end == -1)
+		    throw new ProtocolException("Bad Set-Cookie header: " +
+						set_cookie + "\nNo '=' found " +
+						"for token starting at " +
+						"position " + beg);
+
+		String name = set_cookie.substring(beg, end).trim();
+		beg = Util.skipSpace(buf, end+1);
+
+		if (name.equalsIgnoreCase("expires"))
+		{
+		    /* Netscape ignores quotes around the date, and some twits
+		     * actually send that...
+		     */
+		    if (set_cookie.charAt(beg) == '\"')
+			beg = Util.skipSpace(buf, beg+1);
+
+		    /* cut off the weekday if it is there. This is a little
+		     * tricky because the comma is also used between cookies
+		     * themselves. To make sure we don't inadvertantly
+		     * mistake a date for a weekday we only skip letters.
+		     */
+		    int pos = beg;
+		    while (pos < len  &&
+			   (buf[pos] >= 'a'  &&  buf[pos] <= 'z'  ||
+			    buf[pos] >= 'A'  &&  buf[pos] <= 'Z'))
+			pos++;
+		    pos = Util.skipSpace(buf, pos);
+		    if (pos < len  &&  buf[pos] == ','  &&  pos > beg)
+			beg = pos+1;
+		}
+
+		comma = set_cookie.indexOf(',', beg);
+		semic = set_cookie.indexOf(';', beg);
+		if (comma == -1  &&  semic == -1)  end = len;
+		else if (comma == -1)  end = semic;
+		else if (semic == -1)  end = comma;
+		else end = Math.min(comma, semic);
+
+		String value = set_cookie.substring(beg, end).trim();
+		legal &= setAttribute(curr, name, value, set_cookie);
+
+		beg = end;
+		if (beg < len  &&  buf[beg] == ';')	// consume ";"
+		    beg = Util.skipSpace(buf, beg+1);
+	    }
+
+	    if (legal)
+	    {
+		cookie_arr = Util.resizeArray(cookie_arr, cookie_arr.length+1);
+		cookie_arr[cookie_arr.length-1] = curr;
+	    } else
+		Log.write(Log.COOKI, "Cooki: Ignoring cookie: " + curr);
+	}
+
+	return cookie_arr;
+    }
+
+    /**
+     * Set the given attribute, if valid.
+     *
+     * @param cookie     the cookie on which to set the value
+     * @param name       the name of the attribute
+     * @param value      the value of the attribute
+     * @param set_cookie the complete Set-Cookie header
+     * @return true if the attribute is legal; false otherwise
+     */
+    private static boolean setAttribute(Cookie cookie, String name,
+					String value, String set_cookie)
+	    throws ProtocolException
+    {
+	if (name.equalsIgnoreCase("expires"))
+	{
+	    if (value.charAt(value.length()-1) == '\"')
+		value = value.substring(0, value.length()-1).trim();
+	    try
+		// This is too strict...
+		// { cookie.expires = Util.parseHttpDate(value); }
+		{ cookie.expires = new Date(value); }
+	    catch (IllegalArgumentException iae)
+	    {
+		/* More broken servers to deal with... Ignore expires
+		 * if it's invalid
+		throw new ProtocolException("Bad Set-Cookie header: " +
+				    set_cookie + "\nInvalid date found at " +
+				    "position " + beg);
+		*/
+		Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
+				     "\n       Invalid date `" + value + "'");
+	    }
+	}
+	else if (name.equals("max-age"))	// from rfc-2109
+	{
+	    if (cookie.expires != null)  return true;
+	    if (value.charAt(0) == '\"'  &&  value.charAt(value.length()-1) == '\"')
+		value = value.substring(1, value.length()-1).trim();
+	    int age;
+	    try
+		{ age = Integer.parseInt(value); }
+	    catch (NumberFormatException nfe)
+	    {
+		throw new ProtocolException("Bad Set-Cookie header: " +
+				    set_cookie + "\nMax-Age '" + value +
+				    "' not a number");
+	    }
+	    cookie.expires = new Date(System.currentTimeMillis() + age*1000L);
+	}
+	else if (name.equalsIgnoreCase("domain"))
+	{
+	    // you get everything these days...
+	    if (value.length() == 0)
+	    {
+		Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
+				     "\n       domain is empty - ignoring domain");
+		return true;
+	    }
+
+	    // domains are case insensitive.
+	    value = value.toLowerCase();
+
+	    // add leading dot, if missing
+	    if (value.length() != 0 && value.charAt(0) != '.'  &&
+		!value.equals(cookie.domain))
+		value = '.' + value;
+
+	    // must be the same domain as in the url
+	    if (!cookie.domain.endsWith(value))
+	    {
+		Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
+				     "\n       Current domain " + cookie.domain +
+				     " does not match given parsed " + value);
+		return false;
+	    }
+
+
+	    /* Netscape's original 2-/3-dot rule really doesn't work because
+	     * many countries use a shallow hierarchy (similar to the special
+	     * TLDs defined in the spec). While the rules in rfc-2965 aren't
+	     * perfect either, they are better. OTOH, some sites use a domain
+	     * so that the host name minus the domain name contains a dot (e.g.
+	     * host x.x.yahoo.com and domain .yahoo.com). So, for the seven
+	     * special TLDs we use the 2-dot rule, and for all others we use
+	     * the rules in the state-man draft instead.
+	     */
+
+	    // domain must be either .local or must contain at least
+	    // two dots
+	    if (!value.equals(".local")  && value.indexOf('.', 1) == -1)
+	    {
+		Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
+				     "\n       Domain attribute " + value +
+				     "isn't .local and doesn't have at " +
+				     "least 2 dots");
+		return false;
+	    }
+
+	    // If TLD not special then host minus domain may not
+	    // contain any dots
+	    String top = null;
+	    if (value.length() > 3 )
+		top = value.substring(value.length()-4);
+	    if (top == null  ||  !(
+		top.equalsIgnoreCase(".com")  ||
+		top.equalsIgnoreCase(".edu")  ||
+		top.equalsIgnoreCase(".net")  ||
+		top.equalsIgnoreCase(".org")  ||
+		top.equalsIgnoreCase(".gov")  ||
+		top.equalsIgnoreCase(".mil")  ||
+		top.equalsIgnoreCase(".int")))
+	    {
+		int dl = cookie.domain.length(), vl = value.length();
+		if (dl > vl  &&
+		    cookie.domain.substring(0, dl-vl).indexOf('.') != -1)
+		{
+		    Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
+					 "\n       Domain attribute " + value +
+					 "is more than one level below " +
+					 "current domain " + cookie.domain);
+		    return false;
+		}
+	    }
+
+	    cookie.domain = value;
+	}
+	else if (name.equalsIgnoreCase("path"))
+	    cookie.path = value;
+	else
+	  ; // unknown attribute - ignore
+
+	return true;
+    }
+
+
+    /**
+     * Return the name of this cookie.
+     */
+    public String getName()
+    {
+	return name;
+    }
+
+
+    /**
+     * Return the value of this cookie.
+     */
+    public String getValue()
+    {
+	return value;
+    }
+
+
+    /**
+     * @return the expiry date of this cookie, or null if none set.
+     */
+    public Date expires()
+    {
+	return expires;
+    }
+
+
+    /**
+     * @return true if the cookie should be discarded at the end of the
+     *         session; false otherwise
+     */
+    public boolean discard()
+    {
+	return (expires == null);
+    }
+
+
+    /**
+     * Return the domain this cookie is valid in.
+     */
+    public String getDomain()
+    {
+	return domain;
+    }
+
+
+    /**
+     * Return the path this cookie is associated with.
+     */
+    public String getPath()
+    {
+	return path;
+    }
+
+
+    /**
+     * Return whether this cookie should only be sent over secure connections.
+     */
+    public boolean isSecure()
+    {
+	return secure;
+    }
+
+
+    /**
+     * @return true if this cookie has expired
+     */
+    public boolean hasExpired()
+    {
+	return (expires != null  &&  expires.getTime() <= System.currentTimeMillis());
+    }
+
+
+    /**
+     * @param  req  the request to be sent
+     * @return true if this cookie should be sent with the request
+     */
+    protected boolean sendWith(RoRequest req)
+    {
+	HTTPConnection con = req.getConnection();
+	String eff_host = con.getHost();
+	if (eff_host.indexOf('.') == -1)  eff_host += ".local";
+
+	return ((domain.charAt(0) == '.'  &&  eff_host.endsWith(domain)  ||
+		 domain.charAt(0) != '.'  &&  eff_host.equals(domain))  &&
+		Util.getPath(req.getRequestURI()).startsWith(path)  &&
+		(!secure || con.getProtocol().equals("https") ||
+		 con.getProtocol().equals("shttp")));
+    }
+
+
+    /**
+     * Hash up name, path and domain into new hash.
+     */
+    public int hashCode()
+    {
+	return (name.hashCode() + path.hashCode() + domain.hashCode());
+    }
+
+
+    /**
+     * Two cookies match if the name, path and domain match.
+     */
+    public boolean equals(Object obj)
+    {
+	if ((obj != null) && (obj instanceof Cookie))
+	{
+	    Cookie other = (Cookie) obj;
+	    return  (this.name.equals(other.name)  &&
+		     this.path.equals(other.path)  &&
+		     this.domain.equals(other.domain));
+	}
+	return false;
+    }
+
+
+    /**
+     * @return a string suitable for sending in a Cookie header.
+     */
+    protected String toExternalForm()
+    {
+	return name + "=" + value;
+    }
+
+
+    /**
+     * Create a string containing all the cookie fields. The format is that
+     * used in the Set-Cookie header.
+     */
+    public String toString()
+    {
+	StringBuffer res = new StringBuffer(name.length() + value.length() + 30);
+	res.append(name).append('=').append(value);
+	if (expires != null)  res.append("; expires=").append(expires);
+	if (path != null)     res.append("; path=").append(path);
+	if (domain != null)   res.append("; domain=").append(domain);
+	if (secure)           res.append("; secure");
+	return res.toString();
+    }
+}
diff --git a/HTTPClient/Cookie2.java b/HTTPClient/Cookie2.java
new file mode 100644
index 0000000..53eee21
--- /dev/null
+++ b/HTTPClient/Cookie2.java
@@ -0,0 +1,575 @@
+/*
+ * @(#)Cookie2.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.UnsupportedEncodingException;
+import java.net.ProtocolException;
+import java.util.Date;
+import java.util.Vector;
+import java.util.StringTokenizer;
+
+
+/**
+ * This class represents an http cookie as specified in the <A
+ * HREF="http://www.ietf.org/rfc/rfc2965.txt">HTTP State Management Mechanism spec</A>
+ * (also known as a version 1 cookie).
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public class Cookie2 extends Cookie
+{
+    /** Make this compatible with V0.3-2 */
+    private static final long serialVersionUID = 2208203902820875917L;
+
+    protected int     version;
+    protected boolean discard;
+    protected String  comment;
+    protected URI     comment_url;
+    protected int[]   port_list;
+    protected String  port_list_str;
+
+    protected boolean path_set;
+    protected boolean port_set;
+    protected boolean domain_set;
+
+
+    /**
+     * Create a cookie.
+     *
+     * @param name      the cookie name
+     * @param value     the cookie value
+     * @param domain    the host this cookie will be sent to
+     * @param port_list an array of allowed server ports for this cookie,
+     *                  or null if the the cookie may be sent to any port
+     * @param path      the path prefix for which this cookie will be sent
+     * @param epxires   the Date this cookie expires, or null if never
+     * @param discard   if true then the cookie will be discarded at the
+     *                  end of the session regardless of expiry
+     * @param secure    if true this cookie will only be over secure connections
+     * @param comment   the comment associated with this cookie, or null if none
+     * @param comment_url the comment URL associated with this cookie, or null
+     *                    if none
+     * @exception NullPointerException if <var>name</var>, <var>value</var>,
+     *                                 <var>domain</var>, or <var>path</var>
+     *                                 is null
+     */
+    public Cookie2(String name, String value, String domain, int[] port_list,
+		   String path, Date expires, boolean discard, boolean secure,
+		   String comment, URI comment_url)
+    {
+	super(name, value, domain, path, expires, secure);
+
+	this.discard     = discard;
+	this.port_list   = port_list;
+	this.comment     = comment;
+	this.comment_url = comment_url;
+
+	path_set   = true;
+	domain_set = true;
+
+	if (port_list != null  &&  port_list.length > 0)
+	{
+	    StringBuffer tmp = new StringBuffer();
+	    tmp.append(port_list[0]);
+	    for (int idx=1; idx<port_list.length; idx++)
+	    {
+		tmp.append(',');
+		tmp.append(port_list[idx]);
+	    }
+
+	    port_list_str = tmp.toString();
+	    port_set      = true;
+	}
+
+	version = 1;
+    }
+
+
+    /**
+     * Use <code>parse()</code> to create cookies.
+     *
+     * @see #parse(java.lang.String, HTTPClient.RoRequest)
+     */
+    protected Cookie2(RoRequest req)
+    {
+	super(req);
+
+	path = Util.getPath(req.getRequestURI());
+	int slash = path.lastIndexOf('/');
+	if (slash != -1)  path = path.substring(0, slash+1);
+	if (domain.indexOf('.') == -1)  domain += ".local";
+
+	version       = -1;
+	discard       = false;
+	comment       = null;
+	comment_url   = null;
+	port_list     = null;
+	port_list_str = null;
+
+	path_set      = false;
+	port_set      = false;
+	domain_set    = false;
+    }
+
+
+    /**
+     * Parses the Set-Cookie2 header into an array of Cookies.
+     *
+     * @param set_cookie the Set-Cookie2 header received from the server
+     * @param req the request used
+     * @return an array of Cookies as parsed from the Set-Cookie2 header
+     * @exception ProtocolException if an error occurs during parsing
+     */
+    protected static Cookie[] parse(String set_cookie, RoRequest req)
+		throws ProtocolException
+    {
+	Vector cookies;
+	try
+	    { cookies = Util.parseHeader(set_cookie); }
+	catch (ParseException pe)
+	    { throw new ProtocolException(pe.getMessage()); }
+
+        Cookie cookie_arr[] = new Cookie[cookies.size()];
+	int cidx=0;
+	for (int idx=0; idx<cookie_arr.length; idx++)
+	{
+	    HttpHeaderElement c_elem =
+			(HttpHeaderElement) cookies.elementAt(idx);
+
+
+	    // set NAME and VALUE
+
+	    if (c_elem.getValue() == null)
+		throw new ProtocolException("Bad Set-Cookie2 header: " +
+					    set_cookie + "\nMissing value " +
+					    "for cookie '" + c_elem.getName() +
+					    "'");
+	    Cookie2 curr = new Cookie2(req);
+	    curr.name    = c_elem.getName();
+	    curr.value   = c_elem.getValue();
+
+
+	    // set all params
+
+	    NVPair[] params = c_elem.getParams();
+	    boolean discard_set = false, secure_set = false;
+	    for (int idx2=0; idx2<params.length; idx2++)
+	    {
+		String name = params[idx2].getName().toLowerCase();
+
+		// check for required value parts
+		if ((name.equals("version")  ||  name.equals("max-age")  ||
+		     name.equals("domain")  ||  name.equals("path")  ||
+		     name.equals("comment")  ||  name.equals("commenturl"))  &&
+		    params[idx2].getValue() == null)
+		{
+		    throw new ProtocolException("Bad Set-Cookie2 header: " +
+						set_cookie + "\nMissing value "+
+						"for " + params[idx2].getName()+
+						" attribute in cookie '" +
+						c_elem.getName() + "'");
+		}
+
+
+		if (name.equals("version"))		// Version
+		{
+		    if (curr.version != -1)  continue;
+		    try
+		    {
+			curr.version =
+				Integer.parseInt(params[idx2].getValue());
+		    }
+		    catch (NumberFormatException nfe)
+		    {
+			throw new ProtocolException("Bad Set-Cookie2 header: " +
+						    set_cookie + "\nVersion '" +
+						    params[idx2].getValue() +
+						    "' not a number");
+		    }
+		}
+		else if (name.equals("path"))		// Path
+		{
+		    if (curr.path_set)  continue;
+		    curr.path = params[idx2].getValue();
+		    curr.path_set = true;
+		}
+		else if (name.equals("domain"))		// Domain
+		{
+		    if (curr.domain_set)  continue;
+		    String d = params[idx2].getValue().toLowerCase();
+
+		    // add leading dot if not present and if domain is
+		    // not the full host name
+		    if (d.charAt(0) != '.'  &&  !d.equals(curr.domain))
+			curr.domain = "." + d;
+		    else
+			curr.domain = d;
+		    curr.domain_set = true;
+		}
+		else if (name.equals("max-age"))	// Max-Age
+		{
+		    if (curr.expires != null)  continue;
+		    int age;
+		    try
+			{ age = Integer.parseInt(params[idx2].getValue()); }
+		    catch (NumberFormatException nfe)
+		    {
+			throw new ProtocolException("Bad Set-Cookie2 header: " +
+					    set_cookie + "\nMax-Age '" +
+					    params[idx2].getValue() +
+					    "' not a number");
+		    }
+		    curr.expires =
+			    new Date(System.currentTimeMillis() + age*1000L);
+		}
+		else if (name.equals("port"))		// Port
+		{
+		    if (curr.port_set)  continue;
+
+		    if (params[idx2].getValue() == null)
+		    {
+			curr.port_list    = new int[1];
+			curr.port_list[0] = req.getConnection().getPort();
+			curr.port_set     = true;
+			continue;
+		    }
+
+		    curr.port_list_str = params[idx2].getValue();
+		    StringTokenizer tok =
+			    new StringTokenizer(params[idx2].getValue(), ",");
+		    curr.port_list = new int[tok.countTokens()];
+		    for (int idx3=0; idx3<curr.port_list.length; idx3++)
+		    {
+			String port = tok.nextToken().trim();
+			try
+			    { curr.port_list[idx3] = Integer.parseInt(port); }
+			catch (NumberFormatException nfe)
+			{
+			    throw new ProtocolException("Bad Set-Cookie2 header: " +
+						    set_cookie + "\nPort '" +
+						    port + "' not a number");
+			}
+		    }
+		    curr.port_set = true;
+		}
+		else if (name.equals("discard"))	// Domain
+		{
+		    if (discard_set)  continue;
+		    curr.discard = true;
+		    discard_set  = true;
+		}
+		else if (name.equals("secure"))		// Secure
+		{
+		    if (secure_set)  continue;
+		    curr.secure = true;
+		    secure_set  = true;
+		}
+		else if (name.equals("comment"))	// Comment
+		{
+		    if (curr.comment != null)  continue;
+		    try
+		    {
+			curr.comment =
+			    new String(params[idx2].getValue().getBytes("8859_1"), "UTF8");
+		    }
+		    catch (UnsupportedEncodingException usee)
+			{ throw new Error(usee.toString()); /* shouldn't happen */ }
+		}
+		else if (name.equals("commenturl"))	// CommentURL
+		{
+		    if (curr.comment_url != null)  continue;
+		    try
+			{ curr.comment_url = new URI(params[idx2].getValue()); }
+		    catch (ParseException pe)
+		    {
+			throw new ProtocolException("Bad Set-Cookie2 header: " +
+						set_cookie + "\nCommentURL '" +
+						params[idx2].getValue() +
+						"' not a valid URL");
+		    }
+		}
+		// ignore unknown element
+	    }
+
+
+	    // check version
+
+	    if (curr.version == -1)  continue;
+
+
+	    // setup defaults
+
+	    if (curr.expires == null)  curr.discard = true;
+
+
+	    // check validity
+
+	    // path attribute must be a prefix of the request-URI
+	    if (!Util.getPath(req.getRequestURI()).startsWith(curr.path))
+	    {
+		Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
+				     set_cookie + "\n       path `" +
+				     curr.path + "' is not a prefix of the " +
+				     "request uri `" + req.getRequestURI() +
+				     "'");
+		continue;
+	    }
+
+	    // if host name is simple (i.e w/o a domain) then append .local
+	    String eff_host = req.getConnection().getHost();
+	    if (eff_host.indexOf('.') == -1)  eff_host += ".local";
+
+	    // domain must be either .local or must contain at least two dots
+	    if (!curr.domain.equals(".local")  &&
+		curr.domain.indexOf('.', 1) == -1)
+	    {
+		Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
+				     set_cookie + "\n       domain `" +
+				     curr.domain + "' is not `.local' and " +
+				     "doesn't contain two `.'s");
+		continue;
+	    }
+
+	    // domain must domain match host
+	    if (!eff_host.endsWith(curr.domain))
+	    {
+		Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
+				     set_cookie + "\n       domain `" +
+				     curr.domain + "' does not match current" +
+				     "host `" + eff_host + "'"); 
+		continue;
+	    }
+
+	    // host minus domain may not contain any dots
+	    if (eff_host.substring(0, eff_host.length()-curr.domain.length()).
+		indexOf('.') != -1)
+	    {
+		Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
+				     set_cookie + "\n       domain `" +
+				     curr.domain + "' is more than one `.'" +
+				     "away from host `" + eff_host + "'"); 
+		continue;
+	    }
+
+	    // if a port list is given it must include the current port
+	    if (curr.port_set)
+	    {
+		int idx2=0;
+		for (idx2=0; idx2<curr.port_list.length; idx2++)
+		    if (curr.port_list[idx2] == req.getConnection().getPort())
+			break;
+		if (idx2 == curr.port_list.length)
+		{
+		    Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
+					 set_cookie + "\n       port list " +
+					 "does include current port " +
+					 req.getConnection().getPort());
+		    continue;
+		}
+	    }
+
+
+	    // looks ok
+
+	    cookie_arr[cidx++] = curr;
+	}
+
+	if (cidx < cookie_arr.length)
+	    cookie_arr = Util.resizeArray(cookie_arr, cidx);
+
+	return cookie_arr;
+    }
+
+
+    /**
+     * @return the version as an int
+     */
+    public int getVersion()
+    {
+	return version;
+    }
+
+ 
+    /**
+     * @return the comment string, or null if none was set
+     */
+    public String getComment()
+    {
+	return comment;
+    }
+
+ 
+    /**
+     * @return the comment url
+     */
+    public URI getCommentURL()
+    {
+	return comment_url;
+    }
+
+ 
+    /**
+     * @return the array of ports
+     */
+    public int[] getPorts()
+    {
+	return port_list;
+    }
+
+ 
+    /**
+     * @return true if the cookie should be discarded at the end of the
+     *         session; false otherwise
+     */
+    public boolean discard()
+    {
+	return discard;
+    }
+ 
+
+    /**
+     * @param  req  the request to be sent
+     * @return true if this cookie should be sent with the request
+     */
+    protected boolean sendWith(RoRequest req)
+    {
+	HTTPConnection con = req.getConnection();
+
+	boolean port_match = !port_set;
+	if (port_set)
+	    for (int idx=0; idx<port_list.length; idx++)
+		if (port_list[idx] == con.getPort())
+		{
+		    port_match = true;
+		    break;
+		}
+
+	String eff_host = con.getHost();
+	if (eff_host.indexOf('.') == -1)  eff_host += ".local";
+
+	return ((domain.charAt(0) == '.'  &&  eff_host.endsWith(domain)  ||
+		 domain.charAt(0) != '.'  &&  eff_host.equals(domain))  &&
+		port_match  &&
+		Util.getPath(req.getRequestURI()).startsWith(path)  &&
+		(!secure || con.getProtocol().equals("https") ||
+		 con.getProtocol().equals("shttp")));
+    }
+ 
+
+    protected String toExternalForm()
+    {
+	StringBuffer cookie = new StringBuffer();
+
+	if (version == 1)
+	{
+	    /*
+	    cookie.append("$Version=");
+	    cookie.append(version);
+	    cookie.append("; ");
+	    */
+
+	    cookie.append(name);
+	    cookie.append("=");
+	    cookie.append(value);
+
+	    if (path_set)
+	    {
+		cookie.append("; ");
+		cookie.append("$Path=");
+		cookie.append(path);
+	    }
+
+	    if (domain_set)
+	    {
+		cookie.append("; ");
+		cookie.append("$Domain=");
+		cookie.append(domain);
+	    }
+
+	    if (port_set)
+	    {
+		cookie.append("; ");
+		cookie.append("$Port");
+		if (port_list_str != null)
+		{
+		    cookie.append("=\"");
+		    cookie.append(port_list_str);
+		    cookie.append('\"');
+		}
+	    }
+	}
+	else
+	    throw new Error("Internal Error: unknown version " + version);
+
+	return cookie.toString();
+    }
+
+
+    /**
+     * Create a string containing all the cookie fields. The format is that
+     * used in the Set-Cookie header.
+     */
+    public String toString()
+    {
+	StringBuffer res = new StringBuffer(name.length() + value.length() + 50);
+	res.append(name).append('=').append(value);
+
+	if (version == 1)
+	{
+	    res.append("; Version=").append(version);
+	    res.append("; Path=").append(path);
+	    res.append("; Domain=").append(domain);
+	    if (port_set)
+	    {
+		res.append("; Port=\"").append(port_list[0]);
+		for (int idx=1; idx<port_list.length; idx++)
+		    res.append(',').append(port_list[idx]);
+		res.append('\"');
+	    }
+	    if (expires != null)
+		res.append("; Max-Age=").append(
+		    ((expires.getTime() - System.currentTimeMillis()) / 1000L));
+	    if (discard)           res.append("; Discard");
+	    if (secure)            res.append("; Secure");
+	    if (comment != null)   res.append("; Comment=\"").append(comment).append('\"');
+	    if (comment_url != null)
+		res.append("; CommentURL=\"").append(comment_url).append('\"');
+	}
+	else
+	    throw new Error("Internal Error: unknown version " + version);
+
+	return res.toString();
+    }
+}
diff --git a/HTTPClient/CookieModule.java b/HTTPClient/CookieModule.java
new file mode 100644
index 0000000..e070d87
--- /dev/null
+++ b/HTTPClient/CookieModule.java
@@ -0,0 +1,1168 @@
+/*
+ * @(#)CookieModule.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.ProtocolException;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+import java.awt.Frame;
+import java.awt.Panel;
+import java.awt.Label;
+import java.awt.Color;
+import java.awt.Button;
+import java.awt.Graphics;
+import java.awt.Dimension;
+import java.awt.TextArea;
+import java.awt.TextField;
+import java.awt.GridLayout;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowAdapter;
+
+
+/**
+ * This module handles Netscape cookies (also called Version 0 cookies)
+ * and Version 1 cookies. Specifically is reads the <var>Set-Cookie</var>
+ * and <var>Set-Cookie2</var> response headers and sets the <var>Cookie</var>
+ * and <var>Cookie2</var> headers as neccessary.
+ *
+ * <P>The accepting and sending of cookies is controlled by a
+ * <var>CookiePolicyHandler</var>. This allows you to fine tune your privacy
+ * preferences. A cookie is only added to the cookie list if the handler
+ * allows it, and a cookie from the cookie list is only sent if the handler
+ * allows it.
+ *
+ * <P>This module expects to be the only one handling cookies. Specifically, it
+ * will remove any <var>Cookie</var> and <var>Cookie2</var> header fields found
+ * in the request, and it will remove the <var>Set-Cookie</var> and
+ * <var>Set-Cookie2</var> header fields in the response (after processing them).
+ * In order to add cookies to a request or to prevent cookies from being sent,
+ * you can use the {@link #addCookie(HTTPClient.Cookie) addCookie} and {@link
+ * #removeCookie(HTTPClient.Cookie) removeCookie} methods to manipulate the
+ * module's list of cookies.
+ *
+ * <P>A cookie jar can be used to store cookies between sessions. This file is
+ * read when this class is loaded and is written when the application exits;
+ * only cookies from the default context are saved. The name of the file is
+ * controlled by the system property <var>HTTPClient.cookies.jar</var> and
+ * defaults to a system dependent name. The reading and saving of cookies is
+ * enabled by setting the system property <var>HTTPClient.cookies.save</var>
+ * to <var>true</var>.
+ *
+ * @see <a href="http://home.netscape.com/newsref/std/cookie_spec.html">Netscape's cookie spec</a>
+ * @see <a href="http://www.ietf.org/rfc/rfc2965.txt">HTTP State Management Mechanism spec</a>
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public class CookieModule implements HTTPClientModule
+{
+    /** the list of known cookies */
+    private static Hashtable cookie_cntxt_list = new Hashtable();
+
+    /** the file to use for persistent cookie storage */
+    private static File cookie_jar = null;
+
+    /** an object, whose finalizer will save the cookies to the jar */
+    private static Object cookieSaver = null;
+
+    /** the cookie policy handler */
+    private static CookiePolicyHandler cookie_handler =
+					    new DefaultCookiePolicyHandler();
+
+
+
+    // read in cookies from disk at startup
+
+    static
+    {
+	boolean persist;
+	try
+	    { persist = Boolean.getBoolean("HTTPClient.cookies.save"); }
+	catch (Exception e)
+	    { persist = false; }
+
+	if (persist)
+	{
+	    loadCookies();
+
+	    // the nearest thing to atexit() I know of...
+
+	    cookieSaver = new Object()
+		{
+		    public void finalize() { saveCookies(); }
+		};
+	    try
+		{ System.runFinalizersOnExit(true); }
+	    catch (Throwable t)
+		{ }
+	}
+    }
+
+
+    private static void loadCookies()
+    {
+	// The isFile() etc need to be protected by the catch as signed
+	// applets may be allowed to read properties but not do IO
+	try
+	{
+	    cookie_jar = new File(getCookieJarName());
+	    if (cookie_jar.isFile()  &&  cookie_jar.canRead())
+	    {
+		ObjectInputStream ois =
+		    new ObjectInputStream(new FileInputStream(cookie_jar));
+		cookie_cntxt_list.put(HTTPConnection.getDefaultContext(),
+				      (Hashtable) ois.readObject());
+		ois.close();
+	    }
+	}
+	catch (Throwable t)
+	    { cookie_jar = null; }
+    }
+
+
+    private static void saveCookies()
+    {
+	if (cookie_jar != null  &&  (!cookie_jar.exists()  ||
+	     cookie_jar.isFile()  &&  cookie_jar.canWrite()))
+	{
+	    Hashtable cookie_list = new Hashtable();
+	    Enumeration enumx = Util.getList(cookie_cntxt_list,
+					    HTTPConnection.getDefaultContext())
+				   .elements();
+
+	    // discard cookies which are not to be kept across sessions
+
+	    while (enumx.hasMoreElements())
+	    {
+		Cookie cookie = (Cookie) enumx.nextElement();
+		if (!cookie.discard())
+		    cookie_list.put(cookie, cookie);
+	    }
+
+
+	    // save any remaining cookies in jar
+
+	    if (cookie_list.size() > 0)
+	    {
+		try
+		{
+		    ObjectOutputStream oos =
+			new ObjectOutputStream(new FileOutputStream(cookie_jar));
+		    oos.writeObject(cookie_list);
+		    oos.close();
+		}
+		catch (Throwable t)
+		    { }
+	    }
+	}
+    }
+
+
+    private static String getCookieJarName()
+    {
+	String file = null;
+
+	try
+	    { file = System.getProperty("HTTPClient.cookies.jar"); }
+	catch (Exception e)
+	    { }
+
+	if (file == null)
+	{
+	    // default to something reasonable
+
+	    String os = System.getProperty("os.name");
+	    if (os.equalsIgnoreCase("Windows 95")  ||
+		os.equalsIgnoreCase("16-bit Windows")  ||
+		os.equalsIgnoreCase("Windows"))
+	    {
+		file = System.getProperty("java.home") +
+		       File.separator + ".httpclient_cookies";
+	    }
+	    else if (os.equalsIgnoreCase("Windows NT"))
+	    {
+		file = System.getProperty("user.home") +
+		       File.separator + ".httpclient_cookies";
+	    }
+	    else if (os.equalsIgnoreCase("OS/2"))
+	    {
+		file = System.getProperty("user.home") +
+		       File.separator + ".httpclient_cookies";
+	    }
+	    else if (os.equalsIgnoreCase("Mac OS")  ||
+		     os.equalsIgnoreCase("MacOS"))
+	    {
+		file = "System Folder" + File.separator +
+		       "Preferences" + File.separator +
+		       "HTTPClientCookies";
+	    }
+	    else		// it's probably U*IX or VMS
+	    {
+		file = System.getProperty("user.home") +
+		       File.separator + ".httpclient_cookies";
+	    }
+	}
+
+	return file;
+    }
+
+
+    // Constructors
+
+    CookieModule()
+    {
+    }
+
+
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+    {
+	// First remove any Cookie headers we might have set for a previous
+	// request
+
+	NVPair[] hdrs = req.getHeaders();
+	int length = hdrs.length;
+	for (int idx=0; idx<hdrs.length; idx++)
+	{
+	    int beg = idx;
+	    while (idx < hdrs.length  &&
+		   hdrs[idx].getName().equalsIgnoreCase("Cookie"))
+		idx++;
+
+	    if (idx-beg > 0)
+	    {
+		length -= idx-beg;
+		System.arraycopy(hdrs, idx, hdrs, beg, length-beg);
+	    }
+	}
+	if (length < hdrs.length)
+	{
+	    hdrs = Util.resizeArray(hdrs, length);
+	    req.setHeaders(hdrs);
+	}
+
+
+	// Now set any new cookie headers
+
+	Hashtable cookie_list =
+	    Util.getList(cookie_cntxt_list, req.getConnection().getContext());
+	if (cookie_list.size() == 0)
+	    return REQ_CONTINUE;	// no need to create a lot of objects
+
+	Vector  names   = new Vector();
+	Vector  lens    = new Vector();
+	int     version = 0;
+
+	synchronized (cookie_list)
+	{
+	    Enumeration list = cookie_list.elements();
+	    Vector remove_list = null;
+
+	    while (list.hasMoreElements())
+	    {
+		Cookie cookie = (Cookie) list.nextElement();
+
+		if (cookie.hasExpired())
+		{
+		    Log.write(Log.COOKI, "CookM: cookie has expired and is " +
+					 "being removed: " + cookie);
+		    if (remove_list == null)  remove_list = new Vector();
+		    remove_list.addElement(cookie);
+		    continue;
+		}
+
+		if (cookie.sendWith(req)  &&  (cookie_handler == null  ||
+		    cookie_handler.sendCookie(cookie, req)))
+		{
+		    int len = cookie.getPath().length();
+		    int idx;
+
+		    // insert in correct position
+		    for (idx=0; idx<lens.size(); idx++)
+			if (((Integer) lens.elementAt(idx)).intValue() < len)
+			    break;
+
+		    names.insertElementAt(cookie.toExternalForm(), idx);
+		    lens.insertElementAt(new Integer(len), idx);
+
+		    if (cookie instanceof Cookie2)
+			version = Math.max(version, ((Cookie2) cookie).getVersion());
+		}
+	    }
+
+	    // remove any marked cookies
+	    // Note: we can't do this during the enumeration!
+	    if (remove_list != null)
+	    {
+		for (int idx=0; idx<remove_list.size(); idx++)
+		    cookie_list.remove(remove_list.elementAt(idx));
+	    }
+	}
+
+	if (!names.isEmpty())
+	{
+	    StringBuffer value = new StringBuffer();
+
+	    if (version > 0)
+		value.append("$Version=\"" + version + "\"; ");
+
+	    value.append((String) names.elementAt(0));
+	    for (int idx=1; idx<names.size(); idx++)
+	    {
+		value.append("; ");
+		value.append((String) names.elementAt(idx));
+	    }
+	    hdrs = Util.resizeArray(hdrs, hdrs.length+1);
+	    hdrs[hdrs.length-1] = new NVPair("Cookie", value.toString());
+
+	    // add Cookie2 header if necessary
+	    if (version != 1)	// we currently know about version 1 only
+	    {
+		int idx;
+		for (idx=0; idx<hdrs.length; idx++)
+		    if (hdrs[idx].getName().equalsIgnoreCase("Cookie2"))
+			break;
+		if (idx == hdrs.length)
+		{
+		    hdrs = Util.resizeArray(hdrs, hdrs.length+1);
+		    hdrs[hdrs.length-1] =
+				    new NVPair("Cookie2", "$Version=\"1\"");
+		}
+	    }
+
+	    req.setHeaders(hdrs);
+
+	    Log.write(Log.COOKI, "CookM: Sending cookies '" + value + "'");
+	}
+
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest req)
+	    throws IOException
+    {
+	String set_cookie  = resp.getHeader("Set-Cookie");
+	String set_cookie2 = resp.getHeader("Set-Cookie2");
+	if (set_cookie == null  &&  set_cookie2 == null)
+	    return;
+
+	resp.deleteHeader("Set-Cookie");
+	resp.deleteHeader("Set-Cookie2");
+
+	if (set_cookie != null)
+	    handleCookie(set_cookie, false, req, resp);
+	if (set_cookie2 != null)
+	    handleCookie(set_cookie2, true, req, resp);
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+    {
+	return RSP_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)  throws IOException
+    {
+	String set_cookie = resp.getTrailer("Set-Cookie");
+	String set_cookie2 = resp.getTrailer("Set-Cookie2");
+	if (set_cookie == null  &&  set_cookie2 == null)
+	    return;
+
+	resp.deleteTrailer("Set-Cookie");
+	resp.deleteTrailer("Set-Cookie2");
+
+	if (set_cookie != null)
+	    handleCookie(set_cookie, false, req, resp);
+	if (set_cookie2 != null)
+	    handleCookie(set_cookie2, true, req, resp);
+    }
+
+
+    private void handleCookie(String set_cookie, boolean cookie2, RoRequest req,
+			      Response resp)
+	    throws ProtocolException
+    {
+	Cookie[] cookies;
+	if (cookie2)
+	    cookies = Cookie2.parse(set_cookie, req);
+	else
+	    cookies = Cookie.parse(set_cookie, req);
+
+	if (Log.isEnabled(Log.COOKI))
+	{
+	    Log.write(Log.COOKI, "CookM: Received and parsed " + cookies.length +
+				 " cookies:");
+	    for (int idx=0; idx<cookies.length; idx++)
+		Log.write(Log.COOKI, "CookM: Cookie " + idx + ": " + cookies[idx]);
+	}
+
+	Hashtable cookie_list =
+	    Util.getList(cookie_cntxt_list, req.getConnection().getContext());
+	synchronized (cookie_list)
+	{
+	    for (int idx=0; idx<cookies.length; idx++)
+	    {
+		Cookie cookie = (Cookie) cookie_list.get(cookies[idx]);
+		if (cookie != null  &&  cookies[idx].hasExpired())
+		{
+		    Log.write(Log.COOKI, "CookM: cookie has expired and is " +
+					 "being removed: " + cookie);
+		    cookie_list.remove(cookie);		// expired, so remove
+		}
+		else if (!cookies[idx].hasExpired())	// new or replaced
+		{
+		    if (cookie_handler == null  ||
+			cookie_handler.acceptCookie(cookies[idx], req, resp))
+			cookie_list.put(cookies[idx], cookies[idx]);
+		}
+	    }
+	}
+    }
+
+
+    /**
+     * Discard all cookies for all contexts. Cookies stored in persistent
+     * storage are not affected.
+     */
+    public static void discardAllCookies()
+    {
+	cookie_cntxt_list.clear();
+    }
+
+
+    /**
+     * Discard all cookies for the given context. Cookies stored in persistent
+     * storage are not affected.
+     *
+     * @param context the context Object
+     */
+    public static void discardAllCookies(Object context)
+    {
+	if (context != null)
+	    cookie_cntxt_list.remove(context);
+    }
+
+
+    /**
+     * List all stored cookies for all contexts.
+     *
+     * @return an array of all Cookies
+     * @since V0.3-1
+     */
+    public static Cookie[] listAllCookies()
+    {
+	synchronized (cookie_cntxt_list)
+	{
+	    Cookie[] cookies = new Cookie[0];
+	    int idx = 0;
+
+	    Enumeration cntxt_list = cookie_cntxt_list.elements();
+	    while (cntxt_list.hasMoreElements())
+	    {
+		Hashtable cntxt = (Hashtable) cntxt_list.nextElement();
+		synchronized (cntxt)
+		{
+		    cookies = Util.resizeArray(cookies, idx+cntxt.size());
+		    Enumeration cookie_list = cntxt.elements();
+		    while (cookie_list.hasMoreElements())
+			cookies[idx++] = (Cookie) cookie_list.nextElement();
+		}
+	    }
+
+	    return cookies;
+	}
+    }
+
+
+    /**
+     * List all stored cookies for a given context.
+     *
+     * @param  context the context Object.
+     * @return an array of Cookies
+     * @since V0.3-1
+     */
+    public static Cookie[] listAllCookies(Object context)
+    {
+	Hashtable cookie_list = Util.getList(cookie_cntxt_list, context);
+
+	synchronized (cookie_list)
+	{
+	    Cookie[] cookies = new Cookie[cookie_list.size()];
+	    int idx = 0;
+
+	    Enumeration enumx = cookie_list.elements();
+	    while (enumx.hasMoreElements())
+		cookies[idx++] = (Cookie) enumx.nextElement();
+
+	    return cookies;
+	}
+    }
+
+
+    /**
+     * Add the specified cookie to the list of cookies in the default context.
+     * If a compatible cookie (as defined by <var>Cookie.equals()</var>)
+     * already exists in the list then it is replaced with the new cookie.
+     *
+     * @param cookie the Cookie to add
+     * @since V0.3-1
+     */
+    public static void addCookie(Cookie cookie)
+    {
+	Hashtable cookie_list =
+	    Util.getList(cookie_cntxt_list, HTTPConnection.getDefaultContext());
+	cookie_list.put(cookie, cookie);
+    }
+
+
+    /**
+     * Add the specified cookie to the list of cookies for the specified
+     * context. If a compatible cookie (as defined by
+     * <var>Cookie.equals()</var>) already exists in the list then it is
+     * replaced with the new cookie.
+     *
+     * @param cookie  the cookie to add
+     * @param context the context Object.
+     * @since V0.3-1
+     */
+    public static void addCookie(Cookie cookie, Object context)
+    {
+	Hashtable cookie_list = Util.getList(cookie_cntxt_list, context);
+	cookie_list.put(cookie, cookie);
+    }
+
+
+    /**
+     * Remove the specified cookie from the list of cookies in the default
+     * context. If the cookie is not found in the list then this method does
+     * nothing.
+     *
+     * @param cookie the Cookie to remove
+     * @since V0.3-1
+     */
+    public static void removeCookie(Cookie cookie)
+    {
+	Hashtable cookie_list =
+	    Util.getList(cookie_cntxt_list, HTTPConnection.getDefaultContext());
+	cookie_list.remove(cookie);
+    }
+
+
+    /**
+     * Remove the specified cookie from the list of cookies for the specified
+     * context. If the cookie is not found in the list then this method does
+     * nothing.
+     *
+     * @param cookie  the cookie to remove
+     * @param context the context Object
+     * @since V0.3-1
+     */
+    public static void removeCookie(Cookie cookie, Object context)
+    {
+	Hashtable cookie_list = Util.getList(cookie_cntxt_list, context);
+	cookie_list.remove(cookie);
+    }
+
+
+    /**
+     * Sets a new cookie policy handler. This handler will be called for each
+     * cookie that a server wishes to set and for each cookie that this
+     * module wishes to send with a request. In either case the handler may
+     * allow or reject the operation. If you wish to blindly accept and send
+     * all cookies then just disable the handler with
+     * <code>CookieModule.setCookiePolicyHandler(null);</code>.
+     *
+     * <P>At initialization time a default handler is installed. This
+     * handler allows all cookies to be sent. For any cookie that a server
+     * wishes to be set two lists are consulted. If the server matches any
+     * host or domain in the reject list then the cookie is rejected; if
+     * the server matches any host or domain in the accept list then the
+     * cookie is accepted (in that order). If no host or domain match is
+     * found in either of these two lists and user interaction is allowed
+     * then a dialog box is poped up to ask the user whether to accept or
+     * reject the cookie; if user interaction is not allowed the cookie is
+     * accepted.
+     *
+     * <P>The accept and reject lists in the default handler are initialized
+     * at startup from the two properties
+     * <var>HTTPClient.cookies.hosts.accept</var> and
+     * <var>HTTPClient.cookies.hosts.reject</var>. These properties must
+     * contain a "|" separated list of host and domain names. All names
+     * beginning with a "." are treated as domain names, all others as host
+     * names. An empty string will match all hosts. The two lists are
+     * further expanded if the user chooses one of the "Accept All from
+     * Domain" or "Reject All from Domain" buttons in the dialog box.
+     *
+     * <P>Note: the default handler does not implement the rules concerning
+     * unverifiable transactions (section 3.3.6, <A
+     * HREF="http://www.ietf.org/rfc/rfc2965.txt">RFC-2965</A>). The reason
+     * for this is simple: the default handler knows nothing about the
+     * application using this client, and it therefore does not have enough
+     * information to determine when a request is verifiable and when not. You
+     * are therefore encouraged to provide your own handler which implements
+     * section 3.3.6 (use the <code>CookiePolicyHandler.sendCookie</code>
+     * method for this).
+     *
+     * @param handler the new policy handler
+     * @return the previous policy handler
+     */
+    public static synchronized CookiePolicyHandler
+			    setCookiePolicyHandler(CookiePolicyHandler handler)
+    {
+	CookiePolicyHandler old = cookie_handler;
+	cookie_handler = handler;
+	return old;
+    }
+}
+
+
+/**
+ * A simple cookie policy handler.
+ */
+class DefaultCookiePolicyHandler implements CookiePolicyHandler
+{
+    /** a list of all hosts and domains from which to silently accept cookies */
+    private String[] accept_domains = new String[0];
+
+    /** a list of all hosts and domains from which to silently reject cookies */
+    private String[] reject_domains = new String[0];
+
+    /** the query popup */
+    private BasicCookieBox popup = null;
+
+
+    DefaultCookiePolicyHandler()
+    {
+	// have all cookies been accepted or rejected?
+	String list;
+
+	try
+	    { list = System.getProperty("HTTPClient.cookies.hosts.accept"); }
+	catch (Exception e)
+	    { list = null; }
+	String[] domains = Util.splitProperty(list);
+	for (int idx=0; idx<domains.length; idx++)
+	    addAcceptDomain(domains[idx].toLowerCase());
+
+	try
+	    { list = System.getProperty("HTTPClient.cookies.hosts.reject"); }
+	catch (Exception e)
+	    { list = null; }
+	domains = Util.splitProperty(list);
+	for (int idx=0; idx<domains.length; idx++)
+	    addRejectDomain(domains[idx].toLowerCase());
+    }
+
+
+    /**
+     * returns whether this cookie should be accepted. First checks the
+     * stored lists of accept and reject domains, and if it is neither
+     * accepted nor rejected by these then query the user via a popup.
+     *
+     * @param cookie   the cookie in question
+     * @param req      the request
+     * @param resp     the response
+     * @return true if we accept this cookie.
+     */
+    public boolean acceptCookie(Cookie cookie, RoRequest req, RoResponse resp)
+    {
+	String server = req.getConnection().getHost();
+	if (server.indexOf('.') == -1)  server += ".local";
+
+
+	// Check lists. Reject takes priority over accept
+
+	for (int idx=0; idx<reject_domains.length; idx++)
+	{
+	    if (reject_domains[idx].length() == 0  ||
+		reject_domains[idx].charAt(0) == '.'  &&
+		server.endsWith(reject_domains[idx])  ||
+		reject_domains[idx].charAt(0) != '.'  &&
+		server.equals(reject_domains[idx]))
+		    return false;
+	}
+
+	for (int idx=0; idx<accept_domains.length; idx++)
+	{
+	    if (accept_domains[idx].length() == 0  ||
+		accept_domains[idx].charAt(0) == '.'  &&
+		server.endsWith(accept_domains[idx])  ||
+		accept_domains[idx].charAt(0) != '.'  &&
+		server.equals(accept_domains[idx]))
+		    return true;
+	}
+
+
+	// Ok, not in any list, so ask the user (if allowed).
+
+	if (!req.allowUI())  return true;
+
+	if (popup == null)
+	    popup = new BasicCookieBox();
+
+	return popup.accept(cookie, this, server);
+    }
+
+
+    /**
+     * This handler just allows all cookies to be sent which were accepted
+     * (i.e. no further restrictions are placed on the sending of cookies).
+     *
+     * @return true
+     */
+    public boolean sendCookie(Cookie cookie, RoRequest req)
+    {
+	return true;
+    }
+
+
+    void addAcceptDomain(String domain)
+    {
+	if (domain.indexOf('.') == -1  &&  domain.length() > 0)
+	    domain += ".local";
+
+	for (int idx=0; idx<accept_domains.length; idx++)
+	{
+	    if (domain.endsWith(accept_domains[idx]))
+		return;
+	    if (accept_domains[idx].endsWith(domain))
+	    {
+		accept_domains[idx] = domain;
+		return;
+	    }
+	}
+	accept_domains =
+		    Util.resizeArray(accept_domains, accept_domains.length+1);
+	accept_domains[accept_domains.length-1] = domain;
+    }
+
+    void addRejectDomain(String domain)
+    {
+	if (domain.indexOf('.') == -1  &&  domain.length() > 0)
+	    domain += ".local";
+
+	for (int idx=0; idx<reject_domains.length; idx++)
+	{
+	    if (domain.endsWith(reject_domains[idx]))
+		return;
+	    if (reject_domains[idx].endsWith(domain))
+	    {
+		reject_domains[idx] = domain;
+		return;
+	    }
+	}
+
+	reject_domains =
+		    Util.resizeArray(reject_domains, reject_domains.length+1);
+	reject_domains[reject_domains.length-1] = domain;
+    }
+}
+
+
+/**
+ * A simple popup that asks whether the cookie should be accepted or rejected,
+ * or if cookies from whole domains should be silently accepted or rejected.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class BasicCookieBox extends Frame
+{
+    private final static String title = "Set Cookie Request";
+    private Dimension           screen;
+    private GridBagConstraints  constr;
+    private Label		name_value_label;
+    private Label		domain_value;
+    private Label		ports_label;
+    private Label		ports_value;
+    private Label		path_value;
+    private Label		expires_value;
+    private Label		discard_note;
+    private Label		secure_note;
+    private Label		c_url_note;
+    private Panel		left_panel;
+    private Panel		right_panel;
+    private Label   		comment_label;
+    private TextArea		comment_value;
+    private TextField		domain;
+    private Button		default_focus;
+    private boolean             accept;
+    private boolean             accept_domain;
+
+
+    /**
+     * Constructs the popup.
+     */
+    BasicCookieBox()
+    {
+	super(title);
+
+	screen = getToolkit().getScreenSize();
+
+	addNotify();
+	addWindowListener(new Close());
+
+	GridBagLayout layout;
+	setLayout(layout = new GridBagLayout());
+	constr = new GridBagConstraints();
+
+	constr.gridwidth = GridBagConstraints.REMAINDER;
+	constr.anchor = GridBagConstraints.WEST;
+	add(new Label("The server would like to set the following cookie:"), constr);
+
+	Panel p = new Panel();
+	left_panel = new Panel();
+	left_panel.setLayout(new GridLayout(4,1));
+	left_panel.add(new Label("Name=Value:"));
+	left_panel.add(new Label("Domain:"));
+	left_panel.add(new Label("Path:"));
+	left_panel.add(new Label("Expires:"));
+	ports_label = new Label("Ports:");
+	p.add(left_panel);
+
+	right_panel = new Panel();
+	right_panel.setLayout(new GridLayout(4,1));
+	right_panel.add(name_value_label = new Label());
+	right_panel.add(domain_value = new Label());
+	right_panel.add(path_value = new Label());
+	right_panel.add(expires_value = new Label());
+	ports_value = new Label();
+	p.add(right_panel);
+	add(p, constr);
+	secure_note = new Label("This cookie will only be sent over secure connections");
+	discard_note = new Label("This cookie will be discarded at the end of the session");
+	c_url_note = new Label("");
+	comment_label = new Label("Comment:");
+	comment_value =
+		new TextArea("", 3, 45, TextArea.SCROLLBARS_VERTICAL_ONLY);
+	comment_value.setEditable(false);
+
+	add(new Panel(), constr);
+
+	constr.gridwidth = 1;
+	constr.anchor = GridBagConstraints.CENTER;
+	constr.weightx = 1.0;
+	add(default_focus = new Button("Accept"), constr);
+	default_focus.addActionListener(new Accept());
+
+	Button b;
+	constr.gridwidth = GridBagConstraints.REMAINDER;
+	add(b= new Button("Reject"), constr);
+	b.addActionListener(new Reject());
+
+	constr.weightx = 0.0;
+	p = new Separator();
+	constr.fill = GridBagConstraints.HORIZONTAL;
+	add(p, constr);
+
+	constr.fill   = GridBagConstraints.NONE;
+	constr.anchor = GridBagConstraints.WEST;
+	add(new Label("Accept/Reject all cookies from a host or domain:"), constr);
+
+	p = new Panel();
+	p.add(new Label("Host/Domain:"));
+	p.add(domain = new TextField(30));
+	add(p, constr);
+
+	add(new Label("domains are characterized by a leading dot (`.');"), constr);
+	add(new Label("an empty string matches all hosts"), constr);
+
+	constr.anchor    = GridBagConstraints.CENTER;
+	constr.gridwidth = 1;
+	constr.weightx   = 1.0;
+	add(b = new Button("Accept All"), constr);
+	b.addActionListener(new AcceptDomain());
+
+	constr.gridwidth = GridBagConstraints.REMAINDER;
+	add(b = new Button("Reject All"), constr);
+	b.addActionListener(new RejectDomain());
+
+	pack();
+
+	constr.anchor    = GridBagConstraints.WEST;
+	constr.gridwidth = GridBagConstraints.REMAINDER;
+    }
+
+
+    public Dimension getMaximumSize()
+    {
+	return new Dimension(screen.width*3/4, screen.height*3/4);
+    }
+
+
+    /**
+     * our event handlers
+     */
+    private class Accept implements ActionListener
+    {
+        public void actionPerformed(ActionEvent ae)
+        {
+	    accept = true;
+	    accept_domain = false;
+            synchronized (BasicCookieBox.this)
+		{ BasicCookieBox.this.notifyAll(); }
+        }
+    }
+
+    private class Reject implements ActionListener
+    {
+        public void actionPerformed(ActionEvent ae)
+	{
+	    accept = false;
+	    accept_domain = false;
+            synchronized (BasicCookieBox.this)
+		{ BasicCookieBox.this.notifyAll(); }
+	}
+    }
+
+    private class AcceptDomain implements ActionListener
+    {
+        public void actionPerformed(ActionEvent ae)
+        {
+	    accept = true;
+	    accept_domain = true;
+            synchronized (BasicCookieBox.this)
+		{ BasicCookieBox.this.notifyAll(); }
+	}
+    }
+
+    private class RejectDomain implements ActionListener
+    {
+        public void actionPerformed(ActionEvent ae)
+	{
+	    accept = false;
+	    accept_domain = true;
+            synchronized (BasicCookieBox.this)
+		{ BasicCookieBox.this.notifyAll(); }
+	}
+    }
+
+
+    private class Close extends WindowAdapter
+    {
+	public void windowClosing(WindowEvent we)
+	{
+	    new Reject().actionPerformed(null);
+	}
+    }
+
+
+    /**
+     * the method called by the DefaultCookiePolicyHandler.
+     *
+     * @return true if the cookie should be accepted
+     */
+    public synchronized boolean accept(Cookie cookie,
+				       DefaultCookiePolicyHandler h,
+				       String server)
+    {
+	// set the new values
+
+	name_value_label.setText(cookie.getName() + "=" + cookie.getValue());
+	domain_value.setText(cookie.getDomain());
+	path_value.setText(cookie.getPath());
+	if (cookie.expires() == null)
+	    expires_value.setText("never");
+	else
+	    expires_value.setText(cookie.expires().toString());
+	int pos = 2;
+	if (cookie.isSecure())
+	    add(secure_note, constr, pos++);
+	if (cookie.discard())
+	    add(discard_note, constr, pos++);
+
+	if (cookie instanceof Cookie2)
+	{
+	    Cookie2 cookie2 = (Cookie2) cookie;
+
+	    // set ports list
+	    if (cookie2.getPorts() != null)
+	    {
+		((GridLayout) left_panel.getLayout()).setRows(5);
+		left_panel.add(ports_label, 2);
+		((GridLayout) right_panel.getLayout()).setRows(5);
+		int[] ports = cookie2.getPorts();
+		StringBuffer plist = new StringBuffer();
+		plist.append(ports[0]);
+		for (int idx=1; idx<ports.length; idx++)
+		{
+		    plist.append(", ");
+		    plist.append(ports[idx]);
+		}
+		ports_value.setText(plist.toString());
+		right_panel.add(ports_value, 2);
+	    }
+
+	    // set comment url
+	    if (cookie2.getCommentURL() != null)
+	    {
+		c_url_note.setText("For more info on this cookie see: " +
+				    cookie2.getCommentURL());
+		add(c_url_note, constr, pos++);
+	    }
+
+	    // set comment
+	    if (cookie2.getComment() != null)
+	    {
+		comment_value.setText(cookie2.getComment());
+		add(comment_label, constr, pos++);
+		add(comment_value, constr, pos++);
+	    }
+	}
+
+
+	// invalidate all labels, so that new values are displayed correctly
+
+	name_value_label.invalidate();
+	domain_value.invalidate();
+	ports_value.invalidate();
+	path_value.invalidate();
+	expires_value.invalidate();
+	left_panel.invalidate();
+	right_panel.invalidate();
+	secure_note.invalidate();
+	discard_note.invalidate();
+	c_url_note.invalidate();
+	comment_value.invalidate();
+	invalidate();
+
+
+	// set default domain test
+
+	domain.setText(cookie.getDomain());
+
+
+	// display
+
+	setResizable(true);
+	pack();
+	setResizable(false);
+	setLocation((screen.width-getPreferredSize().width)/2,
+		    (int) ((screen.height-getPreferredSize().height)/2*.7));
+	setVisible(true);
+	default_focus.requestFocus();
+
+
+	// wait for user input
+
+	try { wait(); } catch (InterruptedException e) { }
+
+	setVisible(false);
+
+
+	// reset popup
+
+	remove(secure_note);
+	remove(discard_note);
+	left_panel.remove(ports_label);
+	((GridLayout) left_panel.getLayout()).setRows(4);
+	right_panel.remove(ports_value);
+	((GridLayout) right_panel.getLayout()).setRows(4);
+	remove(c_url_note);
+	remove(comment_label);
+	remove(comment_value);
+
+
+	// handle accept/reject domain buttons
+
+	if (accept_domain)
+	{
+	    String dom = domain.getText().trim().toLowerCase();
+
+	    if (accept)
+		h.addAcceptDomain(dom);
+	    else
+		h.addRejectDomain(dom);
+	}
+
+	return accept;
+    }
+}
+
+
+/**
+ * A simple separator element.
+ */
+class Separator extends Panel
+{
+    public void paint(Graphics g)
+    {
+	int w = getSize().width,
+	    h = getSize().height/2;
+
+	g.setColor(Color.darkGray);
+	g.drawLine(2, h-1, w-2, h-1);
+	g.setColor(Color.white);
+	g.drawLine(2, h, w-2, h);
+    }
+
+    public Dimension getMinimumSize()
+    {
+	return new Dimension(4, 2);
+    }
+}
diff --git a/HTTPClient/CookiePolicyHandler.java b/HTTPClient/CookiePolicyHandler.java
new file mode 100644
index 0000000..4e28ea3
--- /dev/null
+++ b/HTTPClient/CookiePolicyHandler.java
@@ -0,0 +1,72 @@
+/*
+ * @(#)CookiePolicyHandler.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This is the interface that a cookie policy handler must implement. A
+ * policy handler allows you to control which cookies are accepted and
+ * which are sent.
+ *
+ * @see HTTPClient.CookieModule#setCookiePolicyHandler(HTTPClient.CookiePolicyHandler)
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public interface CookiePolicyHandler
+{
+    /**
+     * This method is called for each cookie that a server tries to set via
+     * the Set-Cookie header. This enables you to implement your own
+     * cookie acceptance policy.
+     *
+     * @param cookie the cookie in question
+     * @param req    the request sent which prompted the response
+     * @param resp   the response which is trying to set the cookie
+     * @return true if this cookie should be accepted, false if it is to
+     *         be rejected.
+     */
+    boolean acceptCookie(Cookie cookie, RoRequest req, RoResponse resp);
+
+    /**
+     * This method is called for each cookie that is eligible for sending
+     * with a request (according to the matching rules for the path, domain,
+     * protocol, etc). This enables you to control the sending of cookies.
+     *
+     * @param cookie the cookie in question
+     * @param req    the request this cookie is to be sent with
+     * @return true if this cookie should be sent, false if it is to be
+     *         ignored.
+     */
+    boolean sendCookie(Cookie cookie, RoRequest req);
+}
diff --git a/HTTPClient/DefaultAuthHandler.java b/HTTPClient/DefaultAuthHandler.java
new file mode 100644
index 0000000..872a5b1
--- /dev/null
+++ b/HTTPClient/DefaultAuthHandler.java
@@ -0,0 +1,1751 @@
+/*
+ * @(#)DefaultAuthHandler.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.DataInputStream;
+import java.io.InputStreamReader;
+import java.util.Vector;
+import java.util.StringTokenizer;
+
+import java.awt.Frame;
+import java.awt.Panel;
+import java.awt.Label;
+import java.awt.Button;
+import java.awt.Dimension;
+import java.awt.TextField;
+import java.awt.GridLayout;
+import java.awt.BorderLayout;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowAdapter;
+
+/**
+ * This class is the default authorization handler. It currently handles the
+ * authentication schemes "Basic", "Digest", and "SOCKS5" (used for the
+ * SocksClient and not part of HTTP per se).
+ *
+ * <P>By default, when a username and password is required, this handler throws
+ * up a message box requesting the desired info. However, applications can
+ * {@link #setAuthorizationPrompter(HTTPClient.AuthorizationPrompter) set their
+ * own authorization prompter} if desired.
+ *
+ * <P><strong>Note:</strong> all methods except for
+ * <var>setAuthorizationPrompter</var> are meant to be invoked by the
+ * AuthorizationModule only, i.e. should not be invoked by the application
+ * (those methods are only public because implementing the
+ * <var>AuthorizationHandler</var> interface requires them to be).
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.2
+ */
+public class DefaultAuthHandler implements AuthorizationHandler, GlobalConstants
+{
+    private static final byte[] NUL = new byte[0];
+
+    private static final int DI_A1  = 0;
+    private static final int DI_A1S = 1;
+    private static final int DI_QOP = 2;
+
+    private static byte[] digest_secret = null;
+
+    private static AuthorizationPrompter prompter    = null;
+    private static boolean               prompterSet = false;
+
+
+    /**
+     * For Digest authentication we need to set the uri, response and
+     * opaque parameters. For "Basic" and "SOCKS5" nothing is done.
+     */
+    public AuthorizationInfo fixupAuthInfo(AuthorizationInfo info,
+					   RoRequest req,
+					   AuthorizationInfo challenge,
+					   RoResponse resp)
+		    throws AuthSchemeNotImplException
+    {
+	// nothing to do for Basic and SOCKS5 schemes
+
+	if (info.getScheme().equalsIgnoreCase("Basic")  ||
+	    info.getScheme().equalsIgnoreCase("SOCKS5"))
+	    return info;
+	else if (!info.getScheme().equalsIgnoreCase("Digest"))
+	    throw new AuthSchemeNotImplException(info.getScheme());
+
+	if (Log.isEnabled(Log.AUTH))
+	    Log.write(Log.AUTH, "Auth:  fixing up Authorization for host " +
+				info.getHost()+":"+info.getPort() +
+				"; scheme: " + info.getScheme() +
+				"; realm: " + info.getRealm());
+
+	return digest_fixup(info, req, challenge, resp);
+    }
+
+
+    /**
+     * returns the requested authorization, or null if none was given.
+     *
+     * @param challenge the parsed challenge from the server.
+     * @param req the request which solicited this response
+     * @param resp the full response received
+     * @return a structure containing the necessary authorization info,
+     *         or null
+     * @exception AuthSchemeNotImplException if the authentication scheme
+     *             in the challenge cannot be handled.
+     */
+    public AuthorizationInfo getAuthorization(AuthorizationInfo challenge,
+					      RoRequest req, RoResponse resp)
+		    throws AuthSchemeNotImplException, IOException
+    {
+	AuthorizationInfo cred;
+
+
+	if (Log.isEnabled(Log.AUTH))
+	    Log.write(Log.AUTH, "Auth:  Requesting Authorization for host " +
+				challenge.getHost()+":"+challenge.getPort() +
+				"; scheme: " + challenge.getScheme() +
+				"; realm: " + challenge.getRealm());
+
+
+	// we only handle Basic, Digest and SOCKS5 authentication
+
+	if (!challenge.getScheme().equalsIgnoreCase("Basic")  &&
+	    !challenge.getScheme().equalsIgnoreCase("Digest")  &&
+	    !challenge.getScheme().equalsIgnoreCase("SOCKS5"))
+	    throw new AuthSchemeNotImplException(challenge.getScheme());
+
+
+	// For digest authentication, check if stale is set
+
+	if (challenge.getScheme().equalsIgnoreCase("Digest"))
+	{
+	    cred = digest_check_stale(challenge, req, resp);
+	    if (cred != null)
+		return cred;
+	}
+
+
+	// Ask the user for username/password
+
+	NVPair answer;
+	synchronized (getClass())
+	{
+	    if (!req.allowUI()  ||  prompterSet  &&  prompter == null)
+		return null;
+
+	    if (prompter == null)
+		setDefaultPrompter();
+
+	    answer = prompter.getUsernamePassword(challenge,
+						  resp.getStatusCode() == 407);
+	}
+
+	if (answer == null)
+	    return null;
+
+
+	// Now process the username/password
+
+	if (challenge.getScheme().equalsIgnoreCase("basic"))
+	{
+	    cred = new AuthorizationInfo(challenge.getHost(),
+					 challenge.getPort(),
+					 challenge.getScheme(),
+					 challenge.getRealm(),
+					 Codecs.base64Encode(
+						answer.getName() + ":" +
+						answer.getValue()));
+	}
+	else if (challenge.getScheme().equalsIgnoreCase("Digest"))
+	{
+	    cred = digest_gen_auth_info(challenge.getHost(),
+					challenge.getPort(),
+				        challenge.getRealm(), answer.getName(),
+					answer.getValue(),
+					req.getConnection().getContext());
+	    cred = digest_fixup(cred, req, challenge, null);
+	}
+	else	// SOCKS5
+	{
+	    NVPair[] upwd = { answer };
+	    cred = new AuthorizationInfo(challenge.getHost(),
+					 challenge.getPort(),
+					 challenge.getScheme(),
+					 challenge.getRealm(),
+					 upwd, null);
+	}
+
+
+	// try to get rid of any unencoded passwords in memory
+
+	answer = null;
+	System.gc();
+
+
+	// Done
+
+	Log.write(Log.AUTH, "Auth:  Got Authorization");
+
+	return cred;
+    }
+
+
+    /**
+     * We handle the "Authentication-Info" and "Proxy-Authentication-Info"
+     * headers here.
+     */
+    public void handleAuthHeaders(Response resp, RoRequest req,
+				  AuthorizationInfo prev,
+				  AuthorizationInfo prxy)
+	    throws IOException
+    {
+	String auth_info = resp.getHeader("Authentication-Info");
+	String prxy_info = resp.getHeader("Proxy-Authentication-Info");
+
+	if (auth_info == null  &&  prev != null  &&
+	    hasParam(prev.getParams(), "qop", "auth-int"))
+	    auth_info = "";
+
+	if (prxy_info == null  &&  prxy != null  &&
+	    hasParam(prxy.getParams(), "qop", "auth-int"))
+	    prxy_info = "";
+
+	try
+	{
+	    handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req,
+			   true);
+	    handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp,
+			   req, true);
+	}
+	catch (ParseException pe)
+	    { throw new IOException(pe.toString()); }
+    }
+
+
+    /**
+     * We handle the "Authentication-Info" and "Proxy-Authentication-Info"
+     * trailers here.
+     */
+    public void handleAuthTrailers(Response resp, RoRequest req,
+				   AuthorizationInfo prev,
+				   AuthorizationInfo prxy)
+	    throws IOException
+    {
+	String auth_info = resp.getTrailer("Authentication-Info");
+	String prxy_info = resp.getTrailer("Proxy-Authentication-Info");
+
+	try
+	{
+	    handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req,
+			   false);
+	    handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp,
+			   req, false);
+	}
+	catch (ParseException pe)
+	    { throw new IOException(pe.toString()); }
+    }
+
+
+    private static void handleAuthInfo(String auth_info, String hdr_name,
+				       AuthorizationInfo prev, Response resp,
+				       RoRequest req, boolean in_headers)
+	    throws ParseException, IOException
+    {
+	if (auth_info == null)  return;
+
+	Vector pai = Util.parseHeader(auth_info);
+	HttpHeaderElement elem;
+
+	if (handle_nextnonce(prev, req,
+			     elem = Util.getElement(pai, "nextnonce")))
+	    pai.removeElement(elem);
+	if (handle_discard(prev, req, elem = Util.getElement(pai, "discard")))
+	    pai.removeElement(elem);
+
+	if (in_headers)
+	{
+	    HttpHeaderElement qop = null;
+
+	    if (pai != null  &&
+		(qop = Util.getElement(pai, "qop")) != null  &&
+		qop.getValue() != null)
+	    {
+		handle_rspauth(prev, resp, req, pai, hdr_name);
+	    }
+	    else if (prev != null  &&
+		     (Util.hasToken(resp.getHeader("Trailer"), hdr_name)  &&
+		      hasParam(prev.getParams(), "qop", null)  ||
+		      hasParam(prev.getParams(), "qop", "auth-int")))
+	    {
+		handle_rspauth(prev, resp, req, null, hdr_name);
+	    }
+
+	    else if ((pai != null  &&  qop == null  &&
+		      pai.contains(new HttpHeaderElement("digest")))  ||
+		     (Util.hasToken(resp.getHeader("Trailer"), hdr_name)  &&
+		      prev != null  &&
+		      !hasParam(prev.getParams(), "qop", null)))
+	    {
+		handle_digest(prev, resp, req, hdr_name);
+	    }
+	}
+
+	if (pai.size() > 0)
+	    resp.setHeader(hdr_name, Util.assembleHeader(pai));
+	else
+	    resp.deleteHeader(hdr_name);
+    }
+
+
+    private static final boolean hasParam(NVPair[] params, String name,
+					  String val)
+    {
+	for (int idx=0; idx<params.length; idx++)
+	    if (params[idx].getName().equalsIgnoreCase(name)  &&
+		(val == null  ||  params[idx].getValue().equalsIgnoreCase(val)))
+		return true;
+
+	return false;
+    }
+
+
+    /*
+     * Here are all the Digest specific methods
+     */
+
+    private static AuthorizationInfo digest_gen_auth_info(String host, int port,
+							  String realm,
+							  String user,
+							  String pass,
+							  Object context)
+    {
+	String A1 = user + ":" + realm + ":" + pass;
+	String[] info = { MD5.hexDigest(A1), null, null };
+
+	AuthorizationInfo prev = AuthorizationInfo.getAuthorization(host, port,
+						    "Digest", realm, context);
+	NVPair[] params;
+	if (prev == null)
+	{
+	    params = new NVPair[4];
+	    params[0] = new NVPair("username", user);
+	    params[1] = new NVPair("uri", "");
+	    params[2] = new NVPair("nonce", "");
+	    params[3] = new NVPair("response", "");
+	}
+	else
+	{
+	    params = prev.getParams();
+	    for (int idx=0; idx<params.length; idx++)
+	    {
+		if (params[idx].getName().equalsIgnoreCase("username"))
+		{
+		    params[idx] = new NVPair("username", user);
+		    break;
+		}
+	    }
+	}
+
+	return new AuthorizationInfo(host, port, "Digest", realm, params, info);
+    }
+
+
+    /**
+     * The fixup handler
+     */
+    private static AuthorizationInfo digest_fixup(AuthorizationInfo info,
+						  RoRequest req,
+						  AuthorizationInfo challenge,
+						  RoResponse resp)
+	    throws AuthSchemeNotImplException
+    {
+	// get various parameters from challenge
+
+	int ch_domain=-1, ch_nonce=-1, ch_alg=-1, ch_opaque=-1, ch_stale=-1,
+	    ch_dreq=-1, ch_qop=-1;
+	NVPair[] ch_params = null;
+	if (challenge != null)
+	{
+	    ch_params = challenge.getParams();
+
+	    for (int idx=0; idx<ch_params.length; idx++)
+	    {
+		String name = ch_params[idx].getName().toLowerCase();
+		if (name.equals("domain"))               ch_domain = idx;
+		else if (name.equals("nonce"))           ch_nonce  = idx;
+		else if (name.equals("opaque"))          ch_opaque = idx;
+		else if (name.equals("algorithm"))       ch_alg    = idx;
+		else if (name.equals("stale"))           ch_stale  = idx;
+		else if (name.equals("digest-required")) ch_dreq   = idx;
+		else if (name.equals("qop"))             ch_qop    = idx;
+	    }
+	}
+
+
+	// get various parameters from info
+
+	int uri=-1, user=-1, alg=-1, response=-1, nonce=-1, cnonce=-1, nc=-1,
+	    opaque=-1, digest=-1, dreq=-1, qop=-1;
+	NVPair[] params;
+	String[] extra;
+
+	synchronized (info)	// we need to juggle nonce, nc, etc
+	{
+	    params = info.getParams();
+
+	    for (int idx=0; idx<params.length; idx++)
+	    {
+		String name = params[idx].getName().toLowerCase();
+		if (name.equals("uri"))                  uri      = idx;
+		else if (name.equals("username"))        user     = idx;
+		else if (name.equals("algorithm"))       alg      = idx;
+		else if (name.equals("nonce"))           nonce    = idx;
+		else if (name.equals("cnonce"))          cnonce   = idx;
+		else if (name.equals("nc"))              nc       = idx;
+		else if (name.equals("response"))        response = idx;
+		else if (name.equals("opaque"))          opaque   = idx;
+		else if (name.equals("digest"))          digest   = idx;
+		else if (name.equals("digest-required")) dreq     = idx;
+		else if (name.equals("qop"))             qop      = idx;
+	    }
+
+	    extra = (String[]) info.getExtraInfo();
+
+
+	    // currently only MD5 hash (and "MD5-sess") is supported
+
+	    if (alg != -1  &&
+		!params[alg].getValue().equalsIgnoreCase("MD5")  &&
+		!params[alg].getValue().equalsIgnoreCase("MD5-sess"))
+		throw new AuthSchemeNotImplException("Digest auth scheme: " +
+				    "Algorithm " + params[alg].getValue() +
+				    " not implemented");
+
+	    if (ch_alg != -1  &&
+		!ch_params[ch_alg].getValue().equalsIgnoreCase("MD5") &&
+		!ch_params[ch_alg].getValue().equalsIgnoreCase("MD5-sess"))
+		throw new AuthSchemeNotImplException("Digest auth scheme: " +
+				    "Algorithm " + ch_params[ch_alg].getValue()+
+				    " not implemented");
+
+
+	    // fix up uri and nonce
+
+	    params[uri] = new NVPair("uri",
+		    URI.escape(req.getRequestURI(), URI.escpdPathChar, false));
+	    String old_nonce = params[nonce].getValue();
+	    if (ch_nonce != -1  &&
+		!old_nonce.equals(ch_params[ch_nonce].getValue()))
+		params[nonce] = ch_params[ch_nonce];
+
+
+	    // update or add optional attributes (opaque, algorithm, cnonce,
+	    // nonce-count, and qop
+
+	    if (ch_opaque != -1)
+	    {
+		if (opaque == -1)
+		{
+		    params = Util.resizeArray(params, params.length+1);
+		    opaque = params.length-1;
+		}
+		params[opaque] = ch_params[ch_opaque];
+	    }
+
+	    if (ch_alg != -1)
+	    {
+		if (alg == -1)
+		{
+		    params = Util.resizeArray(params, params.length+1);
+		    alg = params.length-1;
+		}
+		params[alg] = ch_params[ch_alg];
+	    }
+
+	    if (ch_qop != -1  ||
+		(ch_alg != -1  &&
+		 ch_params[ch_alg].getValue().equalsIgnoreCase("MD5-sess")))
+	    {
+		if (cnonce == -1)
+		{
+		    params = Util.resizeArray(params, params.length+1);
+		    cnonce = params.length-1;
+		}
+
+		if (digest_secret == null)
+		    digest_secret = gen_random_bytes(20);
+
+		long l_time = System.currentTimeMillis();
+		byte[] time = new byte[8];
+		time[0] = (byte) (l_time & 0xFF);
+		time[1] = (byte) ((l_time >>  8) & 0xFF);
+		time[2] = (byte) ((l_time >> 16) & 0xFF);
+		time[3] = (byte) ((l_time >> 24) & 0xFF);
+		time[4] = (byte) ((l_time >> 32) & 0xFF);
+		time[5] = (byte) ((l_time >> 40) & 0xFF);
+		time[6] = (byte) ((l_time >> 48) & 0xFF);
+		time[7] = (byte) ((l_time >> 56) & 0xFF);
+
+		params[cnonce] =
+		    new NVPair("cnonce", MD5.hexDigest(digest_secret, time));
+	    }
+
+
+	    // select qop option
+
+	    if (ch_qop != -1)
+	    {
+		if (qop == -1)
+		{
+		    params = Util.resizeArray(params, params.length+1);
+		    qop = params.length-1;
+		}
+		extra[DI_QOP] = ch_params[ch_qop].getValue();
+
+
+		// select qop option
+
+		String[] qops = splitList(extra[DI_QOP], ",");
+		String p = null;
+		for (int idx=0; idx<qops.length; idx++)
+		{
+		    if (qops[idx].equalsIgnoreCase("auth-int")  &&
+			(req.getStream() == null  ||
+			 req.getConnection().ServProtVersKnown  &&
+			 req.getConnection().ServerProtocolVersion >= HTTP_1_1))
+		    {
+			p = "auth-int";
+			break;
+		    }
+		    if (qops[idx].equalsIgnoreCase("auth"))
+			p = "auth";
+		}
+		if (p == null)
+		{
+		    for (int idx=0; idx<qops.length; idx++)
+			if (qops[idx].equalsIgnoreCase("auth-int"))
+			    throw new AuthSchemeNotImplException(
+				"Digest auth scheme: Can't comply with qop " +
+				"option 'auth-int' because an HttpOutputStream " +
+				"is being used and the server doesn't speak " +
+				"HTTP/1.1");
+
+		    throw new AuthSchemeNotImplException("Digest auth scheme: "+
+				"None of the available qop options '" +
+				ch_params[ch_qop].getValue() + "' implemented");
+		}
+		params[qop] = new NVPair("qop", p);
+	    }
+
+
+	    // increment nonce-count.
+
+	    if (qop != -1)
+	    {
+		/* Note: we should actually be serializing all requests through
+		 *       here so that the server sees the nonce-count in a
+		 *       strictly increasing order. However, this would be a
+		 *       *major* hassle to do, so we're just winging it. Most
+		 *       of the time the requests will go over the wire in the
+		 *       same order as they pass through here, but in MT apps
+		 *       it's possible for one request to "overtake" another
+		 *       between here and the synchronized block in
+		 *       sendRequest().
+		 */
+		if (nc == -1)
+		{
+		    params = Util.resizeArray(params, params.length+1);
+		    nc = params.length-1;
+		    params[nc] = new NVPair("nc", "00000001");
+		}
+		else if (old_nonce.equals(params[nonce].getValue()))
+		{
+		    String c = Long.toHexString(
+				Long.parseLong(params[nc].getValue(), 16) + 1);
+		    params[nc] =
+			new NVPair("nc", "00000000".substring(c.length()) + c);
+		}
+		else
+		    params[nc] = new NVPair("nc", "00000001");
+	    }
+
+
+	    // calc new session key if necessary
+
+	    if (challenge != null  &&
+		(ch_stale == -1  ||
+		 !ch_params[ch_stale].getValue().equalsIgnoreCase("true"))  &&
+		alg != -1  &&
+		params[alg].getValue().equalsIgnoreCase("MD5-sess"))
+	    {
+		extra[DI_A1S] = MD5.hexDigest(extra[DI_A1] + ":" +
+					      params[nonce].getValue() + ":" +
+					      params[cnonce].getValue());
+	    }
+
+
+	    // update parameters for next auth cycle
+
+	    info.setParams(params);
+	    info.setExtraInfo(extra);
+	}
+
+
+	// calc "response" attribute
+
+	String hash = null;
+	if (qop != -1 && params[qop].getValue().equalsIgnoreCase("auth-int") &&
+	    req.getStream() == null)
+	{
+	    hash = MD5.hexDigest(req.getData() == null ? NUL : req.getData());
+	}
+
+	if (req.getStream() == null)
+	    params[response] = new NVPair("response", 
+		  calcResponseAttr(hash, extra, params, alg, uri, qop, nonce,
+				   nc, cnonce, req.getMethod()));
+
+
+	// calc digest if necessary
+
+	AuthorizationInfo new_info;
+
+	boolean ch_dreq_val = false;
+	if (ch_dreq != -1  &&
+	    (ch_params[ch_dreq].getValue() == null  ||
+	     ch_params[ch_dreq].getValue().equalsIgnoreCase("true")))
+	    ch_dreq_val = true;
+
+	if ((ch_dreq_val  ||  digest != -1)  &&  req.getStream() == null)
+	{
+	    NVPair[] d_params;
+	    if (digest == -1)
+	    {
+		d_params = Util.resizeArray(params, params.length+1);
+		digest = params.length;
+	    }
+	    else
+		d_params = params;
+	    d_params[digest] = new NVPair("digest",
+		   calc_digest(req, extra[DI_A1], params[nonce].getValue()));
+
+	    if (dreq == -1)	// if server requires digest, then so do we...
+	    {
+		dreq = d_params.length;
+		d_params = Util.resizeArray(d_params, d_params.length+1);
+		d_params[dreq] = new NVPair("digest-required", "true");
+	    }
+
+	    new_info = new AuthorizationInfo(info.getHost(), info.getPort(),
+					     info.getScheme(), info.getRealm(),
+					     d_params, extra);
+	}
+	else if (ch_dreq_val)
+	    new_info = null;
+	else
+	    new_info = new AuthorizationInfo(info.getHost(), info.getPort(),
+					     info.getScheme(), info.getRealm(),
+					     params, extra);
+
+
+	// add info for other domains, if listed
+
+	boolean from_server = (challenge != null)  &&
+	    challenge.getHost().equalsIgnoreCase(req.getConnection().getHost());
+	if (ch_domain != -1)
+	{
+	    URI base = null;
+	    try
+	    {
+		base = new URI(req.getConnection().getProtocol(),
+			       req.getConnection().getHost(),
+			       req.getConnection().getPort(),
+			       req.getRequestURI());
+	    }
+	    catch (ParseException pe)
+		{ }
+
+	    StringTokenizer tok =
+			new StringTokenizer(ch_params[ch_domain].getValue());
+	    while (tok.hasMoreTokens())
+	    {
+		URI Uri;
+		try
+		    { Uri = new URI(base, tok.nextToken()); }
+		catch (ParseException pe)
+		    { continue; }
+		if (Uri.getHost() == null)
+		    continue;
+
+		AuthorizationInfo tmp =
+		    AuthorizationInfo.getAuthorization(Uri.getHost(),
+						       Uri.getPort(),
+						       info.getScheme(),
+						       info.getRealm(),
+					     req.getConnection().getContext());
+		if (tmp == null)
+		{
+		    params[uri] = new NVPair("uri", Uri.getPathAndQuery());
+		    tmp = new AuthorizationInfo(Uri.getHost(), Uri.getPort(),
+					        info.getScheme(),
+						info.getRealm(), params,
+						extra);
+		    AuthorizationInfo.addAuthorization(tmp);
+		}
+		if (from_server)
+		    tmp.addPath(Uri.getPathAndQuery());
+	    }
+	}
+	else if (from_server  &&  challenge != null)
+	{
+	    // Spec says that if no domain attribute is present then the
+	    // whole server should be considered being in the same space
+	    AuthorizationInfo tmp =
+		AuthorizationInfo.getAuthorization(challenge.getHost(),
+						   challenge.getPort(),
+						   info.getScheme(),
+						   info.getRealm(),
+					     req.getConnection().getContext());
+	    if (tmp != null)  tmp.addPath("/");
+	}
+
+
+	// now return the one to use
+
+	return new_info;
+    }
+
+
+    /**
+     * @return the fixed info is stale=true; null otherwise
+     */
+    private static AuthorizationInfo digest_check_stale(
+					      AuthorizationInfo challenge,
+					      RoRequest req, RoResponse resp)
+	    throws AuthSchemeNotImplException, IOException
+    {
+	AuthorizationInfo cred = null;
+
+	NVPair[] params = challenge.getParams();
+	for (int idx=0; idx<params.length; idx++)
+	{
+	    String name = params[idx].getName();
+	    if (name.equalsIgnoreCase("stale")  &&
+		params[idx].getValue().equalsIgnoreCase("true"))
+	    {
+		cred = AuthorizationInfo.getAuthorization(challenge, req, resp,
+							  false);
+		if (cred != null)	// should always be the case
+		    return digest_fixup(cred, req, challenge, resp);
+		break;			// should never be reached
+	    }
+	}
+
+	return cred;
+    }
+
+
+    /**
+     * Handle nextnonce field.
+     */
+    private static boolean handle_nextnonce(AuthorizationInfo prev,
+					    RoRequest req,
+					    HttpHeaderElement nextnonce)
+	    throws IOException
+    {
+	if (prev == null  ||  nextnonce == null  ||
+	    nextnonce.getValue() == null)
+	    return false;
+
+	AuthorizationInfo ai;
+	try
+	    { ai = AuthorizationInfo.getAuthorization(prev, req, null, false); }
+	catch (AuthSchemeNotImplException asnie)
+	    { ai = prev; /* shouldn't happen */ }
+	synchronized (ai)
+	{
+	    NVPair[] params = ai.getParams();
+	    params = setValue(params, "nonce", nextnonce.getValue());
+	    params = setValue(params, "nc", "00000000");
+	    ai.setParams(params);
+	}
+
+	return true;
+    }
+
+
+    /**
+     * Handle digest field of the Authentication-Info response header.
+     */
+    private static boolean handle_digest(AuthorizationInfo prev, Response resp,
+					 RoRequest req, String hdr_name)
+	    throws IOException
+    {
+	if (prev == null)
+	    return false;
+
+	NVPair[] params = prev.getParams();
+	VerifyDigest
+	    verifier = new VerifyDigest(((String[]) prev.getExtraInfo())[0],
+					getValue(params, "nonce"),
+					req.getMethod(),
+					getValue(params, "uri"),
+					hdr_name, resp);
+
+	if (resp.hasEntity())
+	{
+	    Log.write(Log.AUTH, "Auth:  pushing md5-check-stream to verify "+
+				"digest from " + hdr_name);
+	    resp.inp_stream = new MD5InputStream(resp.inp_stream, verifier);
+	}
+	else
+	{
+	    Log.write(Log.AUTH, "Auth:  verifying digest from " + hdr_name);
+	    verifier.verifyHash(MD5.digest(NUL), 0);
+	}
+
+	return true;
+    }
+
+
+    /**
+     * Handle rspauth field of the Authentication-Info response header.
+     */
+    private static boolean handle_rspauth(AuthorizationInfo prev, Response resp,
+					  RoRequest req, Vector auth_info,
+					  String hdr_name)
+	    throws IOException
+    {
+	if (prev == null)
+	    return false;
+
+
+	// get the parameters we sent
+
+	NVPair[] params = prev.getParams();
+	int uri=-1, alg=-1, nonce=-1, cnonce=-1, nc=-1;
+	for (int idx=0; idx<params.length; idx++)
+	{
+	    String name = params[idx].getName().toLowerCase();
+	    if (name.equals("uri"))            uri    = idx;
+	    else if (name.equals("algorithm")) alg    = idx;
+	    else if (name.equals("nonce"))     nonce  = idx;
+	    else if (name.equals("cnonce"))    cnonce = idx;
+	    else if (name.equals("nc"))        nc     = idx;
+	}
+
+
+	// create hash verifier to verify rspauth
+
+	VerifyRspAuth
+	    verifier = new VerifyRspAuth(params[uri].getValue(),
+			      ((String[]) prev.getExtraInfo())[0],
+			      (alg == -1 ? null : params[alg].getValue()),
+						  params[nonce].getValue(),
+			      (cnonce == -1 ? "" : params[cnonce].getValue()),
+			      (nc == -1 ? "" : params[nc].getValue()),
+			      hdr_name, resp);
+
+
+	// if Authentication-Info in header and qop=auth then verify immediately
+
+	HttpHeaderElement qop = null;
+	if (auth_info != null  &&
+	    (qop = Util.getElement(auth_info, "qop")) != null  &&
+	    qop.getValue() != null  &&
+	    (qop.getValue().equalsIgnoreCase("auth")  ||
+	     !resp.hasEntity()  &&  qop.getValue().equalsIgnoreCase("auth-int"))
+	   )
+	{
+	    Log.write(Log.AUTH, "Auth:  verifying rspauth from " + hdr_name);
+	    verifier.verifyHash(MD5.digest(NUL), 0);
+	}
+	else
+	{
+	    // else push md5 stream and verify after body
+
+	    Log.write(Log.AUTH, "Auth:  pushing md5-check-stream to verify "+
+				"rspauth from " + hdr_name);
+	    resp.inp_stream = new MD5InputStream(resp.inp_stream, verifier);
+	}
+
+	return true;
+    }
+
+
+    /**
+     * Calc "response" attribute for a request.
+     */
+    private static String calcResponseAttr(String hash, String[] extra,
+					   NVPair[] params, int alg,
+					   int uri, int qop, int nonce,
+					   int nc, int cnonce, String method)
+    {
+	String A1, A2, resp_val;
+
+	if (alg != -1  &&
+	    params[alg].getValue().equalsIgnoreCase("MD5-sess"))
+	    A1 = extra[DI_A1S];
+	else
+	    A1 = extra[DI_A1];
+
+	A2 = method + ":" + params[uri].getValue();
+	if (qop != -1  &&
+	    params[qop].getValue().equalsIgnoreCase("auth-int"))
+	{
+	    A2 += ":" + hash;
+	}
+	A2 = MD5.hexDigest(A2);
+
+	if (qop == -1)
+	    resp_val =
+		MD5.hexDigest(A1 + ":" + params[nonce].getValue() + ":" + A2);
+	else
+	    resp_val =
+		MD5.hexDigest(A1 + ":" + params[nonce].getValue() + ":" +
+			      params[nc].getValue() + ":" +
+			      params[cnonce].getValue() + ":" +
+			      params[qop].getValue() + ":" + A2);
+
+	return resp_val;
+    }
+
+
+    /**
+     * Calculates the digest of the request body. This was in RFC-2069
+     * and draft-ietf-http-authentication-00.txt, but has subsequently
+     * been removed. Here for backwards compatibility.
+     */
+    private static String calc_digest(RoRequest req, String A1_hash,
+				      String nonce)
+    {
+	if (req.getStream() != null)
+	    return "";
+
+	int ct=-1, ce=-1, lm=-1, ex=-1, dt=-1;
+	for (int idx=0; idx<req.getHeaders().length; idx++)
+	{
+	    String name = req.getHeaders()[idx].getName();
+	    if (name.equalsIgnoreCase("Content-type"))
+		ct = idx;
+	    else if (name.equalsIgnoreCase("Content-Encoding"))
+		ce = idx;
+	    else if (name.equalsIgnoreCase("Last-Modified"))
+		lm = idx;
+	    else if (name.equalsIgnoreCase("Expires"))
+		ex = idx;
+	    else if (name.equalsIgnoreCase("Date"))
+		dt = idx;
+	}
+
+
+	NVPair[] hdrs = req.getHeaders();
+	byte[] entity_body = (req.getData() == null ? NUL : req.getData());
+	String entity_hash = MD5.hexDigest(entity_body);
+
+	String entity_info = MD5.hexDigest(req.getRequestURI() + ":" +
+	     (ct == -1 ? "" : hdrs[ct].getValue()) + ":" +
+	     entity_body.length + ":" +
+	     (ce == -1 ? "" : hdrs[ce].getValue()) + ":" +
+	     (lm == -1 ? "" : hdrs[lm].getValue()) + ":" +
+	     (ex == -1 ? "" : hdrs[ex].getValue()));
+	String entity_digest = A1_hash + ":" + nonce + ":" + req.getMethod() +
+			":" + (dt == -1 ? "" : hdrs[dt].getValue()) +
+			":" + entity_info + ":" + entity_hash;
+
+	if (Log.isEnabled(Log.AUTH))
+	{
+	    Log.write(Log.AUTH, "Auth:  Entity-Info: '" + req.getRequestURI() + ":" +
+		 (ct == -1 ? "" : hdrs[ct].getValue()) + ":" +
+		 entity_body.length + ":" +
+		 (ce == -1 ? "" : hdrs[ce].getValue()) + ":" +
+		 (lm == -1 ? "" : hdrs[lm].getValue()) + ":" +
+		 (ex == -1 ? "" : hdrs[ex].getValue()) +"'");
+	    Log.write(Log.AUTH, "Auth:  Entity-Body: '" + entity_hash + "'");
+	    Log.write(Log.AUTH, "Auth:  Entity-Digest: '" + entity_digest + "'");
+	}
+
+	return MD5.hexDigest(entity_digest);
+    }
+
+
+    /**
+     * Handle discard token
+     */
+    private static boolean handle_discard(AuthorizationInfo prev, RoRequest req,
+					  HttpHeaderElement discard)
+    {
+	if (discard != null  &&  prev != null)
+	{
+	    AuthorizationInfo.removeAuthorization(prev,
+					    req.getConnection().getContext());
+	    return true;
+	}
+
+	return false;
+    }
+
+
+    /**
+     * Generate <var>num</var> bytes of random data.
+     *
+     * @param num  the number of bytes to generate
+     * @return a byte array of random data
+     */
+    private static byte[] gen_random_bytes(int num)
+    {
+	// first try /dev/random
+	try
+	{
+	    FileInputStream rnd = new FileInputStream("/dev/random");
+	    DataInputStream din = new DataInputStream(rnd);
+	    byte[] data = new byte[num];
+	    din.readFully(data);
+	    try { din.close(); } catch (IOException ioe) { }
+	    return data;
+	}
+	catch (Throwable t)
+	    { }
+
+	/* This is probably a much better generator, but it can be awfully
+	 * slow (~ 6 secs / byte on my old LX)
+	 */
+	//return new java.security.SecureRandom().getSeed(num);
+
+	/* this is faster, but needs to be done better... */
+	byte[] data = new byte[num];
+	try
+	{
+	    long fm = Runtime.getRuntime().freeMemory();
+	    data[0] = (byte) (fm & 0xFF);
+	    data[1] = (byte) ((fm >>  8) & 0xFF);
+
+	    int h = data.hashCode();
+	    data[2] = (byte) (h & 0xFF);
+	    data[3] = (byte) ((h >>  8) & 0xFF);
+	    data[4] = (byte) ((h >> 16) & 0xFF);
+	    data[5] = (byte) ((h >> 24) & 0xFF);
+
+	    long time = System.currentTimeMillis();
+	    data[6] = (byte) (time & 0xFF);
+	    data[7] = (byte) ((time >>  8) & 0xFF);
+	}
+	catch (ArrayIndexOutOfBoundsException aioobe)
+	    { }
+
+	return data;
+    }
+
+
+    /**
+     * Return the value of the first NVPair whose name matches the key
+     * using a case-insensitive search.
+     *
+     * @param list an array of NVPair's
+     * @param key  the key to search for
+     * @return the value of the NVPair with that key, or null if not
+     *         found.
+     */
+    private final static String getValue(NVPair[] list, String key)
+    {
+	int len = list.length;
+
+	for (int idx=0; idx<len; idx++)
+	    if (list[idx].getName().equalsIgnoreCase(key))
+		return list[idx].getValue();
+
+	return null;
+    }
+
+    /**
+     * Return the index of the first NVPair whose name matches the key
+     * using a case-insensitive search.
+     *
+     * @param list an array of NVPair's
+     * @param key  the key to search for
+     * @return the index of the NVPair with that key, or -1 if not
+     *         found.
+     */
+    private final static int getIndex(NVPair[] list, String key)
+    {
+	int len = list.length;
+
+	for (int idx=0; idx<len; idx++)
+	    if (list[idx].getName().equalsIgnoreCase(key))
+		return idx;
+
+	return -1;
+    }
+
+    /**
+     * Sets the value of the NVPair with the name that matches the key
+     * (case-insensitive). If no name matches, a new entry is created.
+     *
+     * @param list an array of NVPair's
+     * @param key  the name of the NVPair
+     * @param val  the value of the new NVPair
+     * @return the (possibly) new list
+     */
+    private final static NVPair[] setValue(NVPair[] list, String key, String val)
+    {
+	int idx = getIndex(list, key);
+	if (idx == -1)
+	{
+	    idx = list.length;
+	    list = Util.resizeArray(list, list.length+1);
+	}
+
+	list[idx] = new NVPair(key, val);
+	return list;
+    }
+
+
+    /**
+     * Split a list into an array of Strings, using sep as the
+     * separator and removing whitespace around the separator.
+     */
+    private static String[] splitList(String str, String sep)
+    {
+	if (str == null)  return new String[0];
+
+	StringTokenizer tok = new StringTokenizer(str, sep);
+	String[] list = new String[tok.countTokens()];
+	for (int idx=0; idx<list.length; idx++)
+	    list[idx] = tok.nextToken().trim();
+
+	return list;
+    }
+
+
+    /**
+     * Produce a string of the form "A5:22:F1:0B:53"
+     */
+    static String hex(byte[] buf)
+    {
+	StringBuffer str = new StringBuffer(buf.length*3);
+	for (int idx=0; idx<buf.length; idx++)
+	{
+	    str.append(Character.forDigit((buf[idx] >> 4) & 15, 16));
+	    str.append(Character.forDigit(buf[idx] & 15, 16));
+	    str.append(':');
+	}
+	str.setLength(str.length()-1);
+
+	return str.toString();
+    }
+
+
+    static final byte[] unHex(String hex)
+    {
+	byte[] digest = new byte[hex.length()/2];
+
+	for (int idx=0; idx<digest.length; idx++)
+	{
+	    digest[idx] = (byte) (0xFF & Integer.parseInt(
+				  hex.substring(2*idx, 2*(idx+1)), 16));
+	}
+
+	return digest;
+    }
+
+
+    /**
+     * Set a new username/password prompter.
+     *
+     * @param prompt the AuthorizationPrompter to use whenever a username
+     *               and password are needed; if null, no querying will be
+     *               done
+     * @return the previous prompter
+     */
+    public static synchronized AuthorizationPrompter setAuthorizationPrompter(
+					    AuthorizationPrompter prompt)
+    {
+	AuthorizationPrompter prev = prompter;
+	prompter    = prompt;
+	prompterSet = true;
+	return prev;
+    }
+
+
+    /**
+     * Set the default authorization prompter. It first tries to figure out
+     * if the AWT is running, and if it is then the GUI popup prompter is used;
+     * otherwise the command line prompter is used.
+     */
+    private static void setDefaultPrompter()
+    {
+	// if the AWT is running use the popup box; else use the
+	// the command line prompter.
+	if (!SimpleAuthPrompt.canUseCLPrompt()  ||  isAWTRunning())
+	    prompter = new SimpleAuthPopup();
+	else
+	    prompter = new SimpleAuthPrompt();
+    }
+
+    /**
+     * Try and figure out if the AWT is running. This is done by searching all
+     * threads and looking for one whose name starts with "AWT-". 
+     */
+    private static final boolean isAWTRunning() {
+	// find top-level thread group
+	ThreadGroup root = Thread.currentThread().getThreadGroup();
+	while (root.getParent() != null)
+	    root = root.getParent();
+
+	// search all threads
+	Thread[] t_list = new Thread[root.activeCount() + 5];
+	int t_num = root.enumerate(t_list);
+	for (int idx=0; idx<t_num; idx++)
+	{
+	    if (t_list[idx].getName().startsWith("AWT-"))
+		return true;
+	}
+
+	return false;
+    }
+}
+
+
+/**
+ * This verifies the "rspauth" from draft-ietf-http-authentication-03
+ */
+class VerifyRspAuth implements HashVerifier, GlobalConstants
+{
+    private String     uri;
+    private String     HA1;
+    private String     alg;
+    private String     nonce;
+    private String     cnonce;
+    private String     nc;
+    private String     hdr;
+    private RoResponse resp;
+
+
+    public VerifyRspAuth(String uri, String HA1, String alg, String nonce,
+			 String cnonce, String nc, String hdr, RoResponse resp)
+    {
+	this.uri    = uri;
+	this.HA1    = HA1;
+	this.alg    = alg;
+	this.nonce  = nonce;
+	this.cnonce = cnonce;
+	this.nc     = nc;
+	this.hdr    = hdr;
+	this.resp   = resp;
+    }
+
+
+    public void verifyHash(byte[] hash, long len)  throws IOException
+    {
+	String auth_info = resp.getHeader(hdr);
+	if (auth_info == null)
+	    auth_info = resp.getTrailer(hdr);
+	if (auth_info == null)
+	    return;
+
+	Vector pai;
+	try
+	    { pai = Util.parseHeader(auth_info); }
+	catch (ParseException pe)
+	    { throw new IOException(pe.toString()); }
+
+	String qop;
+	HttpHeaderElement elem = Util.getElement(pai, "qop");
+	if (elem == null  ||  (qop = elem.getValue()) == null  ||
+	    (!qop.equalsIgnoreCase("auth")  &&
+	     !qop.equalsIgnoreCase("auth-int")))
+	    return;
+
+	elem = Util.getElement(pai, "rspauth");
+	if (elem == null  ||  elem.getValue() == null) return;
+	byte[] digest = DefaultAuthHandler.unHex(elem.getValue());
+
+	elem = Util.getElement(pai, "cnonce");
+	if (elem != null  &&  elem.getValue() != null  &&
+	    !elem.getValue().equals(cnonce))
+	    throw new IOException("Digest auth scheme: received wrong " +
+				  "client-nonce '" + elem.getValue() +
+				  "' - expected '" + cnonce + "'");
+
+	elem = Util.getElement(pai, "nc");
+	if (elem != null  &&  elem.getValue() != null  &&
+	    !elem.getValue().equals(nc))
+	    throw new IOException("Digest auth scheme: received wrong " +
+				  "nonce-count '" + elem.getValue() +
+				  "' - expected '" + nc + "'");
+
+	String A1, A2;
+	if (alg != null  &&  alg.equalsIgnoreCase("MD5-sess"))
+	    A1 = MD5.hexDigest(HA1 + ":" + nonce + ":" + cnonce);
+	else
+	    A1 = HA1;
+
+	// draft-01 was: A2 = resp.getStatusCode() + ":" + uri;
+	A2 = ":" + uri;
+	if (qop.equalsIgnoreCase("auth-int"))
+	    A2 += ":" + MD5.toHex(hash);
+	A2 = MD5.hexDigest(A2);
+
+	hash = MD5.digest(A1 + ":" + nonce + ":" +  nc + ":" + cnonce + ":" +
+			  qop + ":" + A2);
+
+	for (int idx=0; idx<hash.length; idx++)
+	{
+	    if (hash[idx] != digest[idx])
+		throw new IOException("MD5-Digest mismatch: expected " +
+				      DefaultAuthHandler.hex(digest) +
+				      " but calculated " +
+				      DefaultAuthHandler.hex(hash));
+	}
+
+	Log.write(Log.AUTH, "Auth:  rspauth from " + hdr +
+			    " successfully verified");
+    }
+}
+
+
+/**
+ * This verifies the "digest" from rfc-2069
+ */
+class VerifyDigest implements HashVerifier, GlobalConstants
+{
+    private String     HA1;
+    private String     nonce;
+    private String     method;
+    private String     uri;
+    private String     hdr;
+    private RoResponse resp;
+
+
+    public VerifyDigest(String HA1, String nonce, String method, String uri,
+			String hdr, RoResponse resp)
+    {
+	this.HA1    = HA1;
+	this.nonce  = nonce;
+	this.method = method;
+	this.uri    = uri;
+	this.hdr    = hdr;
+	this.resp   = resp;
+    }
+
+
+    public void verifyHash(byte[] hash, long len)  throws IOException
+    {
+	String auth_info = resp.getHeader(hdr);
+	if (auth_info == null)
+	    auth_info = resp.getTrailer(hdr);
+	if (auth_info == null)
+	    return;
+
+	Vector pai;
+	try
+	    { pai = Util.parseHeader(auth_info); }
+	catch (ParseException pe)
+	    { throw new IOException(pe.toString()); }
+	HttpHeaderElement elem = Util.getElement(pai, "digest");
+	if (elem == null  ||  elem.getValue() == null)
+	    return;
+
+	byte[] digest = DefaultAuthHandler.unHex(elem.getValue());
+
+	String entity_info = MD5.hexDigest(
+				uri + ":" +
+				header_val("Content-Type", resp) + ":" +
+				header_val("Content-Length", resp) + ":" +
+				header_val("Content-Encoding", resp) + ":" +
+				header_val("Last-Modified", resp) + ":" +
+				header_val("Expires", resp));
+	hash = MD5.digest(HA1 + ":" + nonce + ":" + method + ":" +
+			  header_val("Date", resp) +
+			  ":" + entity_info + ":" + MD5.toHex(hash));
+
+	for (int idx=0; idx<hash.length; idx++)
+	{
+	    if (hash[idx] != digest[idx])
+		throw new IOException("MD5-Digest mismatch: expected " +
+				      DefaultAuthHandler.hex(digest) +
+				      " but calculated " +
+				      DefaultAuthHandler.hex(hash));
+	}
+
+	Log.write(Log.AUTH, "Auth:  digest from " + hdr +
+			    " successfully verified");
+    }
+
+
+    private static final String header_val(String hdr_name, RoResponse resp)
+	    throws IOException
+    {
+	String hdr = resp.getHeader(hdr_name);
+	String tlr = resp.getTrailer(hdr_name);
+	return (hdr != null ? hdr : (tlr != null ? tlr : ""));
+    }
+}
+
+
+class SimpleAuthPopup implements AuthorizationPrompter
+{
+    private static BasicAuthBox inp = null;
+
+    /**
+     * the method called by DefaultAuthHandler.
+     *
+     * @return the username/password pair
+     */
+    public NVPair getUsernamePassword(AuthorizationInfo challenge, boolean forProxy)
+    {
+	String line1, line2, line3;
+
+	if (challenge.getScheme().equalsIgnoreCase("SOCKS5"))
+	{
+	    line1 = "Enter username and password for SOCKS server on host";
+	    line2 = challenge.getHost();
+	    line3 = "Authentication Method: username/password";
+	}
+	else
+	{
+	    line1 = "Enter username and password for realm `" +
+		    challenge.getRealm() + "'";
+	    line2 = "on host " + challenge.getHost() + ":" +
+		    challenge.getPort();
+	    line3 = "Authentication Scheme: " + challenge.getScheme();
+	}
+
+	synchronized(getClass())
+	{
+	    if (inp == null)
+		inp = new BasicAuthBox();
+	}
+
+	return inp.getInput(line1, line2, line3, challenge.getScheme());
+    }
+
+
+    /**
+     * This class implements a simple popup that request username and password
+     * used for the "basic" and "digest" authentication schemes.
+     *
+     * @version	0.3-3  06/05/2001
+     * @author	Ronald Tschal�r
+     */
+    private static class BasicAuthBox extends Frame
+    {
+	private final static String title = "Authorization Request";
+	private Dimension           screen;
+	private Label               line1, line2, line3;
+	private TextField           user, pass;
+	private int                 done;
+	private final static int    OK = 1, CANCEL = 0;
+
+
+	/**
+	 * Constructs the popup with two lines of text above the input fields
+	 */
+	BasicAuthBox()
+	{
+	    super(title);
+
+	    screen = getToolkit().getScreenSize();
+
+	    addNotify();
+	    addWindowListener(new Close());
+	    setLayout(new BorderLayout());
+
+	    Panel p = new Panel(new GridLayout(3,1));
+	    p.add(line1 = new Label());
+	    p.add(line2 = new Label());
+	    p.add(line3 = new Label());
+	    add("North", p);
+
+	    p = new Panel(new GridLayout(2,1));
+	    p.add(new Label("Username:"));
+	    p.add(new Label("Password:"));
+	    add("West", p);
+	    p = new Panel(new GridLayout(2,1));
+	    p.add(user = new TextField(30));
+	    p.add(pass = new TextField(30));
+	    pass.addActionListener(new Ok());
+	    pass.setEchoChar('*');
+	    add("East", p);
+
+	    GridBagLayout gb = new GridBagLayout();
+	    p = new Panel(gb);
+	    GridBagConstraints constr = new GridBagConstraints();
+	    Panel pp = new Panel();
+	    p.add(pp);
+	    constr.gridwidth = GridBagConstraints.REMAINDER;
+	    gb.setConstraints(pp, constr);
+	    constr.gridwidth = 1;
+	    constr.weightx = 1.0;
+	    Button b;
+	    p.add(b = new Button("  OK  "));
+	    b.addActionListener(new Ok());
+	    constr.weightx = 1.0;
+	    gb.setConstraints(b, constr);
+	    p.add(b = new Button("Clear"));
+	    b.addActionListener(new Clear());
+	    constr.weightx = 2.0;
+	    gb.setConstraints(b, constr);
+	    p.add(b = new Button("Cancel"));
+	    b.addActionListener(new Cancel());
+	    constr.weightx = 1.0;
+	    gb.setConstraints(b, constr);
+	    add("South", p);
+
+	    pack();
+	}
+
+
+	/**
+	 * our event handlers
+	 */
+	private class Ok implements ActionListener
+	{
+	    public void actionPerformed(ActionEvent ae)
+	    {
+		done = OK;
+		synchronized (BasicAuthBox.this)
+		    { BasicAuthBox.this.notifyAll(); }
+	    }
+	}
+
+	private class Clear implements ActionListener
+	{
+	    public void actionPerformed(ActionEvent ae)
+	    {
+		user.setText("");
+		pass.setText("");
+		user.requestFocus();
+	    }
+	}
+
+	private class Cancel implements ActionListener
+	{
+	    public void actionPerformed(ActionEvent ae)
+	    {
+		done = CANCEL;
+		synchronized (BasicAuthBox.this)
+		    { BasicAuthBox.this.notifyAll(); }
+	    }
+	}
+
+
+	private class Close extends WindowAdapter
+	{
+	    public void windowClosing(WindowEvent we)
+	    {
+		new Cancel().actionPerformed(null);
+	    }
+	}
+
+
+	/**
+	 * the method called by SimpleAuthPopup.
+	 *
+	 * @return the username/password pair
+	 */
+	synchronized NVPair getInput(String l1, String l2, String l3,
+				     String scheme)
+	{
+	    line1.setText(l1);
+	    line2.setText(l2);
+	    line3.setText(l3);
+
+	    line1.invalidate();
+	    line2.invalidate();
+	    line3.invalidate();
+
+	    setResizable(true);
+	    pack();
+	    setResizable(false);
+	    setLocation((screen.width-getPreferredSize().width)/2,
+			(int) ((screen.height-getPreferredSize().height)/2*.7));
+
+	    boolean user_focus = true;
+	    if (scheme.equalsIgnoreCase("NTLM"))
+	    {
+		// prefill the user field with the username
+		try
+		{
+		    user.setText(System.getProperty("user.name", ""));
+		    user_focus = false;
+		}
+		catch (SecurityException se)
+		    { }
+	    }
+
+	    setVisible(true);
+	    if (user_focus)
+		user.requestFocus();
+	    else
+		pass.requestFocus();
+
+	    try { wait(); } catch (InterruptedException e) { }
+
+	    setVisible(false);
+
+	    NVPair result = new NVPair(user.getText(), pass.getText());
+	    user.setText("");
+	    pass.setText("");
+
+	    if (done == CANCEL)
+		return null;
+	    else
+		return result;
+	}
+    }
+}
+
+
+/**
+ * This class implements a simple command line prompter that request
+ * username and password used for the "basic" and "digest" authentication
+ * schemes.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class SimpleAuthPrompt implements AuthorizationPrompter
+{
+    /**
+     * the method called by DefaultAuthHandler.
+     *
+     * @return the username/password pair
+     */
+    public NVPair getUsernamePassword(AuthorizationInfo challenge, boolean forProxy)
+    {
+	String user, pass;
+
+	if (challenge.getScheme().equalsIgnoreCase("SOCKS5"))
+	{
+	    System.out.println("Enter username and password for SOCKS " +
+			       "server on host " + challenge.getHost());
+	    System.out.println("Authentication Method: username/password");
+	}
+	else
+	{
+	    System.out.println("Enter username and password for realm `" +
+			       challenge.getRealm() + "' on host " +
+			       challenge.getHost() + ":" +
+			       challenge.getPort());
+	    System.out.println("Authentication Scheme: " +
+			       challenge.getScheme());
+	}
+
+
+	// get username
+
+	BufferedReader inp =
+		    new BufferedReader(new InputStreamReader(System.in));
+	System.out.print("Username: "); System.out.flush();
+	try
+	    { user = inp.readLine(); }
+	catch (IOException ioe)
+	    { return null; }
+	if (user == null  ||  user.length() == 0)
+	    return null;		// cancel'd
+
+
+	// get password
+
+	echo(false);
+	System.out.print("Password: "); System.out.flush();
+	try
+	    { pass = inp.readLine(); }
+	catch (IOException ioe)
+	    { return null; }
+	System.out.println();
+	echo(true);
+
+	if (pass == null)
+	    return null;		// cancel'd
+
+
+	// done
+
+	return new NVPair(user, pass);
+    }
+
+
+    /*
+     * Turn command-line echoing of typed characters on or off.
+     */
+    private static void echo(boolean on)
+    {
+	String os = System.getProperty("os.name");
+	String[] cmd = null;
+
+	if (os.equalsIgnoreCase("Windows 95")  ||
+	    os.equalsIgnoreCase("Windows NT"))
+	    // I don't think this works on M$ ...
+	    cmd = new String[] { "echo", on ? "on" : "off" };
+	else if (os.equalsIgnoreCase("Windows")  ||
+		 os.equalsIgnoreCase("16-bit Windows"))
+	    ;	// ???
+	else if (os.equalsIgnoreCase("OS/2"))
+	    ;	// ???
+	else if (os.equalsIgnoreCase("Mac OS")  ||
+		 os.equalsIgnoreCase("MacOS"))
+	    ;	// ???
+	else if (os.equalsIgnoreCase("OpenVMS") ||
+		 os.equalsIgnoreCase("VMS"))
+	    cmd = new String[] { "SET TERMINAL " + (on ? "/ECHO" : "/NOECHO") };
+	else			// probably unix
+	    cmd = new String[] { "/bin/sh", "-c",
+				 "stty " + (on ? "echo" : "-echo") + " < /dev/tty" };
+
+        if (cmd != null)
+	    try
+		{ Runtime.getRuntime().exec(cmd).waitFor(); }
+	    catch (Exception e)
+		{ }
+    }
+
+    /**
+     * @return true for Unix's and VMS
+     */
+    static boolean canUseCLPrompt() {
+	String os = System.getProperty("os.name");
+
+	return (os.indexOf("Linux") >= 0    ||  os.indexOf("SunOS") >= 0  ||
+		os.indexOf("Solaris") >= 0  ||  os.indexOf("BSD") >= 0    ||
+		os.indexOf("AIX") >= 0      ||  os.indexOf("HP-UX") >= 0  ||
+		os.indexOf("IRIX") >= 0     ||  os.indexOf("OSF") >= 0    ||
+		os.indexOf("A/UX") >= 0     ||  os.indexOf("VMS") >= 0);
+    }
+}
diff --git a/HTTPClient/DefaultModule.java b/HTTPClient/DefaultModule.java
new file mode 100644
index 0000000..3a6bc3a
--- /dev/null
+++ b/HTTPClient/DefaultModule.java
@@ -0,0 +1,153 @@
+/*
+ * @(#)DefaultModule.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.net.ProtocolException;
+
+
+/**
+ * This is the default module which gets called after all other modules
+ * have done their stuff.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class DefaultModule implements HTTPClientModule
+{
+    /** number of times the request will be retried */
+    private int req_timeout_retries;
+
+
+    // Constructors
+
+    /**
+     * Three retries upon receipt of a 408.
+     */
+    DefaultModule()
+    {
+	req_timeout_retries = 3;
+    }
+
+
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+    {
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+	    throws IOException
+    {
+	/* handle various response status codes until satisfied */
+
+	int sts  = resp.getStatusCode();
+	switch(sts)
+	{
+	    case 408: // Request Timeout
+
+		if (req_timeout_retries-- == 0  ||  req.getStream() != null)
+		{
+		    Log.write(Log.MODS, "DefM:  Status " + sts + " " +
+				    resp.getReasonLine() + " not handled - " +
+				    "maximum number of retries exceeded");
+
+		    return RSP_CONTINUE;
+		}
+		else
+		{
+		    Log.write(Log.MODS, "DefM:  Handling " + sts + " " +
+					resp.getReasonLine() + " - " +
+					"resending request");
+
+		    return RSP_REQUEST;
+		}
+
+	    case 411: // Length Required
+		if (req.getStream() != null  &&
+		    req.getStream().getLength() == -1)
+		    return RSP_CONTINUE;
+
+		try { resp.getInputStream().close(); }
+		catch (IOException ioe) { }
+		if (req.getData() != null)
+		    throw new ProtocolException("Received status code 411 even"+
+					    " though Content-Length was sent");
+
+		Log.write(Log.MODS, "DefM:  Handling " + sts + " " +
+				    resp.getReasonLine() + " - resending " +
+				    "request with 'Content-length: 0'");
+
+		req.setData(new byte[0]);	// will send Content-Length: 0
+		return RSP_REQUEST;
+
+	    case 505: // HTTP Version not supported
+		return RSP_CONTINUE;
+
+	    default:
+		return RSP_CONTINUE;
+	}
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)
+    {
+    }
+}
diff --git a/HTTPClient/FilenameMangler.java b/HTTPClient/FilenameMangler.java
new file mode 100644
index 0000000..09eb8fb
--- /dev/null
+++ b/HTTPClient/FilenameMangler.java
@@ -0,0 +1,73 @@
+/*
+ * @(#)FilenameMangler.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * {@link Codecs#mpFormDataDecode(byte[], java.lang.String, java.lang.String,
+ * HTTPClient.FilenameMangler) Codecs.mpFormDataDecode} and {@link
+ * Codecs#mpFormDataEncode(HTTPClient.NVPair[], HTTPClient.NVPair[],
+ * HTTPClient.NVPair[], HTTPClient.FilenameMangler) Codecs.mpFormDataEncode}
+ * may be handed an instance of a class which implements this interface in
+ * order to control names of the decoded files or the names sent in the encoded
+ * data.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3-1
+ */
+public interface FilenameMangler
+{
+    /**
+     * This is invoked by {@link Codecs#mpFormDataDecode(byte[],
+     * java.lang.String, java.lang.String, HTTPClient.FilenameMangler)
+     * Codecs.mpFormDataDecode} for each file found in the data, just before
+     * the file is created and written. If null is returned then the file is
+     * not created or written. This allows you to control which files are
+     * written and the names of the resulting files.
+     *
+     * <P>For {@link Codecs#mpFormDataEncode(HTTPClient.NVPair[],
+     * HTTPClient.NVPair[], HTTPClient.NVPair[], HTTPClient.FilenameMangler)
+     * Codecs.mpFormDataEncode} this is also invoked on each filename, allowing
+     * you to control the actual name used in the <var>filename</var> attribute
+     * of the Content-Disposition header. This does not change the name of the
+     * file actually read. If null is returned then the file is ignored.
+     *
+     * @param filename  the original filename in the Content-Disposition header
+     * @param fieldname the name of the this field, i.e. the value of the
+     *                  <var>name</var> attribute in Content-Disposition
+     *                  header
+     * @return the new file name, or null if the file is to be ignored.
+     */
+    public String mangleFilename(String filename, String fieldname);
+}
diff --git a/HTTPClient/GlobalConstants.java b/HTTPClient/GlobalConstants.java
new file mode 100644
index 0000000..df5f1cb
--- /dev/null
+++ b/HTTPClient/GlobalConstants.java
@@ -0,0 +1,63 @@
+/*
+ * @(#)GlobalConstants.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This interface defines various global constants.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+interface GlobalConstants
+{
+    /** possible http protocols we (might) handle */
+    int     HTTP       = 0; 	// plain http
+    int     HTTPS      = 1; 	// http on top of SSL
+    int     SHTTP      = 2; 	// secure http
+    int     HTTP_NG    = 3; 	// http next-generation
+
+    /** some known http versions */
+    int     HTTP_1_0   = (1 << 16) + 0;
+    int     HTTP_1_1   = (1 << 16) + 1;
+
+    /** Content delimiters */
+    int     CD_NONE    = 0; 	// raw read from the stream
+    int     CD_HDRS    = 1; 	// reading headers/trailers
+    int     CD_0       = 2; 	// no body
+    int     CD_CLOSE   = 3; 	// by closing connection
+    int     CD_CONTLEN = 4; 	// via the Content-Length header
+    int     CD_CHUNKED = 5; 	// via chunked transfer encoding
+    int     CD_MP_BR   = 6; 	// via multipart/byteranges
+}
diff --git a/HTTPClient/HTTPClientModule.java b/HTTPClient/HTTPClientModule.java
new file mode 100644
index 0000000..5852f24
--- /dev/null
+++ b/HTTPClient/HTTPClientModule.java
@@ -0,0 +1,199 @@
+/*
+ * @(#)HTTPClientModule.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+/**
+ * This is the interface that a module must implement. There are two parts
+ * during a request: the construction of the request, and the handling of
+ * the response. A request may cycle through these parts multiple times
+ * when a module generates additional subrequests (such as a redirection
+ * status handling module might do).
+ *
+ * <P>In the first step the request handler is invoked; here the headers,
+ * the request-uri, etc. can be modified, or a complete response can be
+ * generated. Then, if no response was generated, the request is sent over
+ * the wire. In the second step the response handlers are invoked. These
+ * may modify the response or, in phase 2, may generate a new request; the
+ * returned status from the phase 2 handler specifies how the processing of
+ * the request or response should further proceed.
+ *
+ * <P>The response handling is split into three phases. In the first phase
+ * the response handling cannot be modified; this is so that all modules
+ * get a chance to see the returned response. Modules will typically make
+ * notes of responses and do certain header processing here (for example the
+ * cookie module does it's work in this phase). In the second phase modules
+ * may generate new subrequests or otherwise control the further handling of
+ * the response. This is typically used for response status handling (such
+ * as for redirections and authentication). Finally, if no new subrequest
+ * was generated, the phase 3 response handlers are invoked so that modules
+ * can perform any necessary cleanups and final processing (no additional
+ * subrequests can be made anymore). It is recommended that any response
+ * processing which needn't be done if the request is not returned to the
+ * user is deferred until this phase. For example, the Content-MD5,
+ * Content-Encoding and Transfer-Encoding modules do their work in this
+ * phase as the response body is usually discarded if a new subrequest is
+ * generated.
+ *
+ * <P>When the user invokes any request method (such as <code>Get(...)</code>)
+ * a list of of modules to be used is built. Then, for each module in the
+ * list, an instance is created using the <code>Class.newInstance()</code>
+ * method. This means that each module must have a constructor which takes
+ * no arguments. This instance is then used to handle the request, its
+ * response, and any additional subrequests and their responses. In this way
+ * a module can easily keep state between related subrequests. For example, a
+ * redirection module might want to keep track of the number of redirections
+ * made to detect redirect loops; it could do this by defining an instance
+ * variable and incrementing it each time the request handler is invoked.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public interface HTTPClientModule extends HTTPClientModuleConstants
+{
+    /**
+     * This is invoked before the request is sent. A module will typically
+     * use this to make a note of headers, to modify headers and/or data,
+     * or even generate and return a response (e.g. for a cache module).
+     * If a response is generated the module must return the appropriate
+     * return code (<var>REQ_RESPONSE</var> or <var>REQ_RETURN</var>).
+     *
+     * <P>Return codes for phase 1 (defined in HTTPClientModuleConstants.java)
+     * <DL>
+     * <DT>REQ_CONTINUE	  <DI>continue processing
+     * <DT>REQ_RESTART    <DI>restart processing with first module
+     * <DT>REQ_SHORTCIRC  <DI>stop processing and send
+     * <DT>REQ_RESPONSE   <DI>go to phase 2
+     * <DT>REQ_RETURN     <DI>return response immediately (no processing)
+     * <DT>REQ_NEWCON_RST <DI>use a new HTTPConnection, restart processing
+     * <DT>REQ_NEWCON_SND <DI>use a new HTTPConnection, send immediately
+     * </DL>
+     *
+     * @param request  the request - may be modified as needed
+     * @param response the response if the status is REQ_RESPONSE or REQ_RETURN
+     * @return status code REQ_XXX specifying further action
+     * @exception IOException if an IOException occurs on the socket
+     * @exception ModuleException if an exception occurs during the handling
+     *                            of the request
+     */
+    public int requestHandler(Request request, Response[] response)
+	    throws IOException, ModuleException;
+
+
+    /**
+     * The phase 1 response handler. This will be invoked for every response.
+     * Modules will typically make notes of the response and do any header
+     * processing which must always be performed.
+     *
+     * @param response the response - may be modified
+     * @param request  the original request
+     * @exception IOException if an IOException occurs on the socket
+     * @exception ModuleException if an exception occurs during the handling
+     *                            of the response
+     */
+    public void responsePhase1Handler(Response response, RoRequest request)
+	    throws IOException, ModuleException;
+
+    /**
+     * The phase 2 response handler. A module may modify the response or
+     * generate a new request (e.g. for redirection). This handler will
+     * only be invoked for a given module if all previous modules returned
+     * <var>RSP_CONTINUE</var>. If the request is modified the handler must
+     * return an appropriate return code (<var>RSP_REQUEST</var>,
+     * <var>RSP_SEND</var>, <var>RSP_NEWCON_REQ</var> or
+     * <var>RSP_NEWCON_SND</var>). If any other code is return the request
+     * must not be modified.
+     *
+     * <P>Return codes for phase 2 (defined in HTTPClientModuleConstants.java)
+     * <DL>
+     * <DT>RSP_CONTINUE   <DI>continue processing
+     * <DT>RSP_RESTART    <DI>restart processing with first module (phase 1)
+     * <DT>RSP_SHORTCIRC  <DI>stop processing and return
+     * <DT>RSP_REQUEST    <DI>go to phase 1
+     * <DT>RSP_SEND       <DI>send request immediately (no processing)
+     * <DT>RSP_NEWCON_REQ <DI>go to phase 1 using a new HTTPConnection
+     * <DT>RSP_NEWCON_SND <DI>send request using a new HTTPConnection
+     * </DL>
+     *
+     * @param response the response - may be modified
+     * @param request  the request; if the status is RSP_REQUEST then this
+     *                 must contain the new request; however do not modify
+     *                 this if you don't return a RSP_REQUEST status.
+     * @return status code RSP_XXX specifying further action
+     * @exception IOException if an IOException occurs on the socket
+     * @exception ModuleException if an exception occurs during the handling
+     *                            of the response
+     */
+    public int  responsePhase2Handler(Response response, Request request)
+	    throws IOException, ModuleException;
+
+
+    /**
+     * The phase 3 response handler. This will only be invoked if no new
+     * subrequest was generated in phase 2. Modules should defer any repsonse
+     * handling which need only be done if the response is returned to the
+     * user to this phase.
+     * 
+     * @param response the response - may be modified
+     * @param request  the original request
+     * @exception IOException if an IOException occurs on the socket
+     * @exception ModuleException if an exception occurs during the handling
+     *                            of the response
+     */
+    public void responsePhase3Handler(Response response, RoRequest request)
+	    throws IOException, ModuleException;
+
+
+    /**
+     * The chunked transfer-encoding (and in future maybe others) can contain
+     * trailer fields at the end of the body. Since the
+     * <code>responsePhaseXHandler()</code>'s are invoked before the body is
+     * read and therefore do not have access to the trailers (unless they
+     * force the complete body to be read) this method will be invoked when
+     * the trailers have been read and parsed (sort of a post-response
+     * handling).
+     *
+     * <P>Note: This method <strong>must not</strong> modify any part of the
+     * response other than the trailers.
+     *
+     * @param response the response
+     * @param request  the request
+     * @exception IOException if an IOException occurs on the socket
+     * @exception ModuleException if an exception occurs during the handling
+     *                            of the trailers
+     */
+    public void trailerHandler(Response response, RoRequest request)
+	    throws IOException, ModuleException;
+}
diff --git a/HTTPClient/HTTPClientModuleConstants.java b/HTTPClient/HTTPClientModuleConstants.java
new file mode 100644
index 0000000..2d14b01
--- /dev/null
+++ b/HTTPClient/HTTPClientModuleConstants.java
@@ -0,0 +1,93 @@
+/*
+ * @(#)HTTPClientModuleConstants.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This interface defines the return codes that the handlers in modules
+ * may return.
+ *
+ * @see HTTPClientModule
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public interface HTTPClientModuleConstants
+{
+    // valid return codes for request handlers
+
+    /** continue processing the request */
+    int  REQ_CONTINUE   = 0;
+
+    /** restart request processing with first module */
+    int  REQ_RESTART    = 1;
+
+    /** stop processing and send the request */
+    int  REQ_SHORTCIRC  = 2;
+
+    /** response generated; go to phase 2 */
+    int  REQ_RESPONSE   = 3;
+
+    /** response generated; return response immediately (no processing) */
+    int  REQ_RETURN     = 4;
+
+    /** using a new HTTPConnection, restart request processing */
+    int  REQ_NEWCON_RST = 5;
+
+    /** using a new HTTPConnection, send request immediately */
+    int  REQ_NEWCON_SND = 6;
+
+
+    // valid return codes for the phase 2 response handlers
+
+    /** continue processing response */
+    int  RSP_CONTINUE   = 10;
+
+    /** restart response processing with first module */
+    int  RSP_RESTART    = 11;
+
+    /** stop processing and return response */
+    int  RSP_SHORTCIRC  = 12;
+
+    /** new request generated; go to phase 1 */
+    int  RSP_REQUEST    = 13;
+
+    /** new request generated; send request immediately (no processing) */
+    int  RSP_SEND       = 14;
+
+    /** go to phase 1 using a new HTTPConnection */
+    int  RSP_NEWCON_REQ = 15;
+
+    /** send request using a new HTTPConnection */
+    int  RSP_NEWCON_SND = 16;
+}
diff --git a/HTTPClient/HTTPConnection.java b/HTTPClient/HTTPConnection.java
new file mode 100644
index 0000000..66a8b7a
--- /dev/null
+++ b/HTTPClient/HTTPConnection.java
@@ -0,0 +1,3781 @@
+/*
+ * @(#)HTTPConnection.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.OutputStream;
+import java.io.DataOutputStream;
+import java.io.FilterOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.URL;
+import java.net.Socket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.ConnectException;
+import java.net.UnknownHostException;
+import java.net.NoRouteToHostException;
+import java.util.Vector;
+import java.applet.Applet;
+
+
+/**
+ * This class implements http protocol requests; it contains most of HTTP/1.1
+ * and ought to be unconditionally compliant.
+ * Redirections are automatically handled, and authorizations requests are
+ * recognized and dealt with via an authorization handler.
+ * Only full HTTP/1.0 and HTTP/1.1 requests are generated. HTTP/1.1, HTTP/1.0
+ * and HTTP/0.9 responses are recognized.
+ *
+ * <P>Using the HTTPClient should be quite simple. First add the import
+ * statement '<code>import HTTPClient.*;</code>' to your file(s). Request
+ * can then be sent using one of the methods <var>Head()</var>,
+ * <var>Get()</var>, <var>Post()</var>, etc in <var>HTTPConnection</var>.
+ * These methods all return an instance of <var>HTTPResponse</var> which
+ * has methods for accessing the response headers (<var>getHeader()</var>,
+ * <var>getHeaderAsInt()</var>, etc), various response info
+ * (<var>getStatusCode()</var>, <var>getReasonLine()</var>, etc) and the
+ * reponse data (<var>getData()</var>, <var>getText()</var>, and
+ * <var>getInputStream()</var>). Following are some examples.
+ *
+ * <P>If this is in an applet you can retrieve files from your server
+ * as follows:
+ *
+ * <PRE>
+ *     try
+ *     {
+ *         HTTPConnection con = new HTTPConnection(this);
+ *         HTTPResponse   rsp = con.Get("/my_file");
+ *         if (rsp.getStatusCode() >= 300)
+ *         {
+ *             System.err.println("Received Error: "+rsp.getReasonLine());
+ *             System.err.println(rsp.getText());
+ *         }
+ *         else
+ *             data = rsp.getData();
+ *
+ *         rsp = con.Get("/another_file");
+ *         if (rsp.getStatusCode() >= 300)
+ *         {
+ *             System.err.println("Received Error: "+rsp.getReasonLine());
+ *             System.err.println(rsp.getText());
+ *         }
+ *         else
+ *             other_data = rsp.getData();
+ *     }
+ *     catch (IOException ioe)
+ *     {
+ *         System.err.println(ioe.toString());
+ *     }
+ *     catch (ModuleException me)
+ *     {
+ *         System.err.println("Error handling request: " + me.getMessage());
+ *     }
+ * </PRE>
+ *
+ * This will get the files "/my_file" and "/another_file" and put their
+ * contents into byte[]'s accessible via <code>getData()</code>. Note that
+ * you need to only create a new <var>HTTPConnection</var> when sending a
+ * request to a new server (different host or port); although you may create
+ * a new <var>HTTPConnection</var> for every request to the same server this
+ * <strong>not</strong> recommended, as various information about the server
+ * is cached after the first request (to optimize subsequent requests) and
+ * persistent connections are used whenever possible.
+ *
+ * <P>To POST form data you would use something like this (assuming you
+ * have two fields called <var>name</var> and <var>e-mail</var>, whose
+ * contents are stored in the variables <var>name</var> and <var>email</var>):
+ *
+ * <PRE>
+ *     try
+ *     {
+ *         NVPair form_data[] = new NVPair[2];
+ *         form_data[0] = new NVPair("name", name);
+ *         form_data[1] = new NVPair("e-mail", email);
+ *
+ *         HTTPConnection con = new HTTPConnection(this);
+ *         HTTPResponse   rsp = con.Post("/cgi-bin/my_script", form_data);
+ *         if (rsp.getStatusCode() >= 300)
+ *         {
+ *             System.err.println("Received Error: "+rsp.getReasonLine());
+ *             System.err.println(rsp.getText());
+ *         }
+ *         else
+ *             stream = rsp.getInputStream();
+ *     }
+ *     catch (IOException ioe)
+ *     {
+ *         System.err.println(ioe.toString());
+ *     }
+ *     catch (ModuleException me)
+ *     {
+ *         System.err.println("Error handling request: " + me.getMessage());
+ *     }
+ * </PRE>
+ *
+ * Here the response data is read at leasure via an <var>InputStream</var>
+ * instead of all at once into a <var>byte[]</var>.
+ *
+ * <P>As another example, if you have a URL you're trying to send a request
+ * to you would do something like the following:
+ *
+ * <PRE>
+ *     try
+ *     {
+ *         URL url = new URL("http://www.mydomain.us/test/my_file");
+ *         HTTPConnection con = new HTTPConnection(url);
+ *         HTTPResponse   rsp = con.Put(url.getFile(), "Hello World");
+ *         if (rsp.getStatusCode() >= 300)
+ *         {
+ *             System.err.println("Received Error: "+rsp.getReasonLine());
+ *             System.err.println(rsp.getText());
+ *         }
+ *         else
+ *             text = rsp.getText();
+ *     }
+ *     catch (IOException ioe)
+ *     {
+ *         System.err.println(ioe.toString());
+ *     }
+ *     catch (ModuleException me)
+ *     {
+ *         System.err.println("Error handling request: " + me.getMessage());
+ *     }
+ * </PRE>
+ *
+ * <P>There are a whole number of methods for each request type; however the
+ * general forms are ([...] means that the enclosed is optional):
+ * <ul>
+ * <li> Head ( file [, form-data [, headers ] ] )
+ * <li> Head ( file [, query [, headers ] ] )
+ * <li> Get ( file [, form-data [, headers ] ] )
+ * <li> Get ( file [, query [, headers ] ] )
+ * <li> Post ( file [, form-data [, headers ] ] )
+ * <li> Post ( file [, data [, headers ] ] )
+ * <li> Post ( file [, stream [, headers ] ] )
+ * <li> Put ( file , data [, headers ] )
+ * <li> Put ( file , stream [, headers ] )
+ * <li> Delete ( file [, headers ] )
+ * <li> Options ( file [, headers [, data] ] )
+ * <li> Options ( file [, headers [, stream] ] )
+ * <li> Trace ( file [, headers ] )
+ * </ul>
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class HTTPConnection implements GlobalConstants, HTTPClientModuleConstants
+{
+    /** The current version of this package. */
+    public final static String   version = "RPT-HTTPClient/0.3-3";
+
+    /** The default context */
+    private final static Object  dflt_context = new Object();
+
+    /** The current context */
+    private Object               Context = null;
+
+    /** The protocol used on this connection */
+    private int                  Protocol;
+
+    /** The server's protocol version; M.m stored as (M<<16 | m) */
+            int   		 ServerProtocolVersion;
+
+    /** Have we gotten the server's protocol version yet? */
+            boolean		 ServProtVersKnown;
+
+    /** The protocol version we send in a request; this is always HTTP/1.1
+	unless we're talking to a broken server in which case it's HTTP/1.0 */
+    private String		 RequestProtocolVersion;
+
+    /** The remote host this connection is associated with */
+    private String               Host;
+
+    /** The remote port this connection is attached to */
+    private int                  Port;
+
+    /** The local address this connection is associated with */
+    private InetAddress          LocalAddr;
+
+    /** The local port this connection is attached to */
+    private int                  LocalPort;
+
+    /** The current proxy host to use (if any) */
+    private String               Proxy_Host = null;
+
+    /** The current proxy port */
+    private int                  Proxy_Port;
+
+    /** The default proxy host to use (if any) */
+    private static String        Default_Proxy_Host = null;
+
+    /** The default proxy port */
+    private static int           Default_Proxy_Port;
+
+    /** The list of hosts for which no proxy is to be used */
+    private static CIHashtable   non_proxy_host_list = new CIHashtable();
+    private static Vector        non_proxy_dom_list  = new Vector();
+    private static Vector        non_proxy_addr_list = new Vector();
+    private static Vector        non_proxy_mask_list = new Vector();
+
+    /** The socks server to use */
+    private SocksClient          Socks_client = null;
+
+    /** The default socks server to use */
+    private static SocksClient   Default_Socks_client = null;
+
+    /** the current stream demultiplexor */
+    private StreamDemultiplexor  input_demux = null;
+
+    /** a list of active stream demultiplexors */
+	    LinkedList           DemuxList = new LinkedList();
+
+    /** a list of active requests */
+    private LinkedList           RequestList = new LinkedList();
+
+    /** does the server support keep-alive's? */
+    private boolean              doesKeepAlive = false;
+
+    /** have we been able to determine the above yet? */
+    private boolean              keepAliveUnknown = true;
+
+    /** the maximum number of requests over a HTTP/1.0 keep-alive connection */
+    private int                  keepAliveReqMax = -1;
+
+    /** the number of requests over a HTTP/1.0 keep-alive connection left */
+    private int                  keepAliveReqLeft;
+
+    /** hack to force buffering of data instead of using chunked T-E */
+    private static boolean       no_chunked = false;
+
+    /** hack to force HTTP/1.0 requests */
+    private static boolean       force_1_0 = false;
+
+    /** hack to be able to disable pipelining */
+    private static boolean       neverPipeline = false;
+
+    /** hack to be able to disable keep-alives */
+    private static boolean       noKeepAlives = false;
+
+    /** hack to work around M$ bug */
+    private static boolean       haveMSLargeWritesBug = false;
+
+    /** hack to only enable defered handling of streamed requests when
+     * configured to do so. */
+            static boolean       deferStreamed = false;
+
+    /** the default timeout to use for new connections */
+    private static int	         DefaultTimeout = 0;
+
+    /** the timeout to use for reading responses */
+    private int	                 Timeout;
+
+    /** The list of default http headers */
+    private NVPair[]             DefaultHeaders = new NVPair[0];
+
+    /** The default list of modules (as a Vector of Class objects) */
+    private static Vector        DefaultModuleList;
+
+    /** The list of modules (as a Vector of Class objects) */
+    private Vector               ModuleList;
+
+    /** controls whether modules are allowed to interact with user */
+    private static boolean       defaultAllowUI = true;
+
+    /** controls whether modules are allowed to interact with user */
+    private boolean              allowUI;
+
+
+    static
+    {
+	/*
+	 * Let's try and see if we can figure out whether any proxies are
+	 * being used.
+	 */
+
+	try		// JDK 1.1 naming
+	{
+	    String host = System.getProperty("http.proxyHost");
+	    if (host == null)
+		throw new Exception();		// try JDK 1.0.x naming
+	    int port = Integer.getInteger("http.proxyPort", -1).intValue();
+
+	    Log.write(Log.CONN, "Conn:  using proxy " + host + ":" + port);
+	    setProxyServer(host, port);
+	}
+	catch (Exception e)
+	{
+	    try		// JDK 1.0.x naming
+	    {
+		if (Boolean.getBoolean("proxySet"))
+		{
+		    String host = System.getProperty("proxyHost");
+		    int    port = Integer.getInteger("proxyPort", -1).intValue();
+		    Log.write(Log.CONN, "Conn:  using proxy " + host + ":" + port);
+		    setProxyServer(host, port);
+		}
+	    }
+	    catch (Exception ee)
+		{ Default_Proxy_Host = null; }
+	}
+
+
+	/*
+	 * now check for the non-proxy list
+	 */
+	try
+	{
+	    String hosts = System.getProperty("HTTPClient.nonProxyHosts");
+	    if (hosts == null)
+		hosts = System.getProperty("http.nonProxyHosts");
+
+	    String[] list = Util.splitProperty(hosts);
+	    dontProxyFor(list);
+	}
+	catch (Exception e)
+	    { }
+
+
+	/*
+	 * we can't turn the JDK SOCKS handling off, so we don't use the
+	 * properties 'socksProxyHost' and 'socksProxyPort'. Instead we
+	 * define 'HTTPClient.socksHost', 'HTTPClient.socksPort' and
+	 * 'HTTPClient.socksVersion'.
+	 */
+	try
+	{
+	    String host = System.getProperty("HTTPClient.socksHost");
+	    if (host != null  &&  host.length() > 0)
+	    {
+		int port    = Integer.getInteger("HTTPClient.socksPort", -1).intValue();
+		int version = Integer.getInteger("HTTPClient.socksVersion", -1).intValue();
+		Log.write(Log.CONN, "Conn:  using SOCKS " + host + ":" + port);
+		if (version == -1)
+		    setSocksServer(host, port);
+		else
+		    setSocksServer(host, port, version);
+	    }
+	}
+	catch (Exception e)
+	    { Default_Socks_client = null; }
+
+
+	// Set up module list
+
+	String modules = "HTTPClient.RetryModule|" +
+			 "HTTPClient.CookieModule|" +
+			 "HTTPClient.RedirectionModule|" +
+			 "HTTPClient.AuthorizationModule|" +
+			 "HTTPClient.DefaultModule|" +
+			 "HTTPClient.TransferEncodingModule|" +
+			 "HTTPClient.ContentMD5Module|" +
+			 "HTTPClient.ContentEncodingModule";
+
+	boolean in_applet = false;
+	try
+	    { modules = System.getProperty("HTTPClient.Modules", modules); }
+	catch (SecurityException se)
+	    { in_applet = true; }
+
+	DefaultModuleList = new Vector();
+	String[] list     = Util.splitProperty(modules);
+	for (int idx=0; idx<list.length; idx++)
+	{
+	    try
+	    {
+		DefaultModuleList.addElement(Class.forName(list[idx]));
+		Log.write(Log.CONN, "Conn:  added module " + list[idx]);
+	    }
+	    catch (ClassNotFoundException cnfe)
+	    {
+		if (!in_applet)
+		    throw new NoClassDefFoundError(cnfe.getMessage());
+
+		/* Just ignore it. This allows for example applets to just
+		 * load the necessary modules - if you don't need a module
+		 * then don't provide it, and it won't be added to the
+		 * list. The disadvantage is that if you accidently misstype
+		 * a module name this will lead to a "silent" error.
+		 */
+	    }
+	}
+
+
+	/*
+	 * Hack: disable pipelining
+	 */
+	try
+	{
+	    neverPipeline = Boolean.getBoolean("HTTPClient.disable_pipelining");
+	    if (neverPipeline)
+		Log.write(Log.CONN, "Conn:  disabling pipelining");
+	}
+	catch (Exception e)
+	    { }
+
+	/*
+	 * Hack: disable keep-alives
+	 */
+	try
+	{
+	    noKeepAlives = Boolean.getBoolean("HTTPClient.disableKeepAlives");
+	    if (noKeepAlives)
+		Log.write(Log.CONN, "Conn:  disabling keep-alives");
+	}
+	catch (Exception e)
+	    { }
+
+	/*
+	 * Hack: force HTTP/1.0 requests
+	 */
+	try
+	{
+	    force_1_0 = Boolean.getBoolean("HTTPClient.forceHTTP_1.0");
+	    if (force_1_0)
+		Log.write(Log.CONN, "Conn:  forcing HTTP/1.0 requests");
+	}
+	catch (Exception e)
+	    { }
+
+	/*
+	 * Hack: prevent chunking of request data
+	 */
+	try
+	{
+	    no_chunked = Boolean.getBoolean("HTTPClient.dontChunkRequests");
+	    if (no_chunked)
+		Log.write(Log.CONN, "Conn:  never chunking requests");
+	}
+	catch (Exception e)
+	    { }
+
+	/*
+	 * M$ bug: large writes hang the stuff
+	 */
+	try
+	{
+	    if (System.getProperty("os.name").indexOf("Windows") >= 0  &&
+		System.getProperty("java.version").startsWith("1.1"))
+		    haveMSLargeWritesBug = true;
+	    if (haveMSLargeWritesBug)
+		Log.write(Log.CONN, "Conn:  splitting large writes into 20K chunks (M$ bug)");
+	}
+	catch (Exception e)
+	    { }
+
+	/*
+	 * Deferring the handling of responses to requests which used an output
+	 * stream is new in V0.3-3. Because it can cause memory leaks for apps
+	 * which aren't expecting this, we only enable this feature if
+	 * explicitly requested to do so.
+	 */
+	try
+	{
+	    deferStreamed = Boolean.getBoolean("HTTPClient.deferStreamed");
+	    if (deferStreamed)
+		Log.write(Log.CONN, "Conn:  enabling defered handling of " +
+				    "responses to streamed requests");
+	}
+	catch (Exception e)
+	    { }
+    }
+
+
+    // Constructors
+
+    /**
+     * Constructs a connection to the host from where the applet was loaded.
+     * Note that current security policies only let applets connect home.
+     *
+     * @param applet the current applet
+     */
+    public HTTPConnection(Applet applet)  throws ProtocolNotSuppException
+    {
+	this(applet.getCodeBase().getProtocol(),
+	     applet.getCodeBase().getHost(),
+	     applet.getCodeBase().getPort());
+    }
+
+    /**
+     * Constructs a connection to the specified host on port 80
+     *
+     * @param host the host
+     */
+    public HTTPConnection(String host)
+    {
+	Setup(HTTP, host, 80, null, -1);
+    }
+
+    /**
+     * Constructs a connection to the specified host on the specified port
+     *
+     * @param host the host
+     * @param port the port
+     */
+    public HTTPConnection(String host, int port)
+    {
+	Setup(HTTP, host, port, null, -1);
+    }
+
+    /**
+     * Constructs a connection to the specified host on the specified port,
+     * using the specified protocol (currently only "http" is supported).
+     *
+     * @param prot the protocol
+     * @param host the host
+     * @param port the port, or -1 for the default port
+     * @exception ProtocolNotSuppException if the protocol is not HTTP
+     */
+    public HTTPConnection(String prot, String host, int port)
+	throws ProtocolNotSuppException
+    {
+	this(prot, host, port, null, -1);
+    }
+
+    /**
+     * Constructs a connection to the specified host on the specified port,
+     * using the specified protocol (currently only "http" is supported),
+     * local address, and local port.
+     *
+     * @param prot      the protocol
+     * @param host      the host
+     * @param port      the port, or -1 for the default port
+     * @param localAddr the local address to bind to
+     * @param lcoalPort the local port to bind to
+     * @exception ProtocolNotSuppException if the protocol is not HTTP
+     */
+    public HTTPConnection(String prot, String host, int port,
+			  InetAddress localAddr, int localPort)
+	throws ProtocolNotSuppException
+    {
+	prot = prot.trim().toLowerCase();
+
+	//if (!prot.equals("http")  &&  !prot.equals("https"))
+	if (!prot.equals("http"))
+	    throw new ProtocolNotSuppException("Unsupported protocol '" + prot + "'");
+
+	if (prot.equals("http"))
+	    Setup(HTTP, host, port, localAddr, localPort);
+	else if (prot.equals("https"))
+	    Setup(HTTPS, host, port, localAddr, localPort);
+	else if (prot.equals("shttp"))
+	    Setup(SHTTP, host, port, localAddr, localPort);
+	else if (prot.equals("http-ng"))
+	    Setup(HTTP_NG, host, port, localAddr, localPort);
+    }
+
+    /**
+     * Constructs a connection to the host (port) as given in the url.
+     *
+     * @param     url the url
+     * @exception ProtocolNotSuppException if the protocol is not HTTP
+     */
+    public HTTPConnection(URL url) throws ProtocolNotSuppException
+    {
+	this(url.getProtocol(), url.getHost(), url.getPort());
+    }
+
+    /**
+     * Constructs a connection to the host (port) as given in the uri.
+     *
+     * @param     uri the uri
+     * @exception ProtocolNotSuppException if the protocol is not HTTP
+     */
+    public HTTPConnection(URI uri) throws ProtocolNotSuppException
+    {
+	this(uri.getScheme(), uri.getHost(), uri.getPort());
+    }
+
+    /**
+     * Sets the class variables. Must not be public.
+     *
+     * @param prot      the protocol
+     * @param host      the host
+     * @param port      the port
+     * @param localAddr the local address to bind to; if null, it's ignored
+     * @param localPort the local port to bind to
+     */
+    private void Setup(int prot, String host, int port, InetAddress localAddr,
+		       int localPort)
+    {
+	Protocol  = prot;
+	Host      = host.trim().toLowerCase();
+	Port      = port;
+	LocalAddr = localAddr;
+	LocalPort = localPort;
+
+	if (Port == -1)
+	    Port = URI.defaultPort(getProtocol());
+
+	if (Default_Proxy_Host != null  &&  !matchNonProxy(Host))
+	    setCurrentProxy(Default_Proxy_Host, Default_Proxy_Port);
+	else
+	    setCurrentProxy(null, 0);
+
+	Socks_client = Default_Socks_client;
+	Timeout      = DefaultTimeout;
+	ModuleList   = (Vector) DefaultModuleList.clone();
+	allowUI      = defaultAllowUI;
+	if (noKeepAlives)
+	    setDefaultHeaders(new NVPair[] { new NVPair("Connection", "close") });
+    }
+
+
+    /**
+     * Determines if the given host matches any entry in the non-proxy list.
+     *
+     * @param host the host to match - must be trim()'d and lowercase
+     * @return true if a match is found, false otherwise
+     * @see #dontProxyFor(java.lang.String)
+     */
+    private boolean matchNonProxy(String host)
+    {
+	// Check host name list
+
+	if (non_proxy_host_list.get(host) != null)
+	    return true;
+
+
+	// Check domain name list
+
+	for (int idx=0; idx<non_proxy_dom_list.size(); idx++)
+	    if (host.endsWith((String) non_proxy_dom_list.elementAt(idx)))
+		return true;
+
+
+	// Check IP-address and subnet list
+
+	if (non_proxy_addr_list.size() == 0)
+	    return false;
+
+	InetAddress[] host_addr;
+	try
+	    { host_addr = InetAddress.getAllByName(host); }
+	catch (UnknownHostException uhe)
+	    { return false; }	// maybe the proxy has better luck
+
+	for (int idx=0; idx<non_proxy_addr_list.size(); idx++)
+	{
+	    byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
+	    byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
+
+	    ip_loop: for (int idx2=0; idx2<host_addr.length; idx2++)
+	    {
+		byte[] raw_addr = host_addr[idx2].getAddress();
+		if (raw_addr.length != addr.length)  continue;
+
+		for (int idx3=0; idx3<raw_addr.length; idx3++)
+		{
+		    if ((raw_addr[idx3] & mask[idx3]) != (addr[idx3] & mask[idx3]))
+			continue ip_loop;
+		}
+		return true;
+	    }
+	}
+
+	return false;
+    }
+
+
+    // Methods
+
+    /**
+     * Sends the HEAD request. This request is just like the corresponding
+     * GET except that it only returns the headers and no data.
+     *
+     * @see #Get(java.lang.String)
+     * @param     file the absolute path of the file
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Head(String file)  throws IOException, ModuleException
+    {
+	return Head(file, (String) null, null);
+    }
+
+    /**
+     * Sends the HEAD request. This request is just like the corresponding
+     * GET except that it only returns the headers and no data.
+     *
+     * @see #Get(java.lang.String, HTTPClient.NVPair[])
+     * @param     file      the absolute path of the file
+     * @param     form_data an array of Name/Value pairs
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Head(String file, NVPair form_data[])
+		throws IOException, ModuleException
+    {
+	return Head(file, form_data, null);
+    }
+
+    /**
+     * Sends the HEAD request. This request is just like the corresponding
+     * GET except that it only returns the headers and no data.
+     *
+     * @see #Get(java.lang.String, HTTPClient.NVPair[], HTTPClient.NVPair[])
+     * @param     file      the absolute path of the file
+     * @param     form_data an array of Name/Value pairs
+     * @param     headers   additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Head(String file, NVPair[] form_data, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	String File  = stripRef(file),
+	       query = Codecs.nv2query(form_data);
+	if (query != null  &&  query.length() > 0)
+	    File += "?" + query;
+
+	return setupRequest("HEAD", File, headers, null, null);
+    }
+
+    /**
+     * Sends the HEAD request. This request is just like the corresponding
+     * GET except that it only returns the headers and no data.
+     *
+     * @see #Get(java.lang.String, java.lang.String)
+     * @param     file   the absolute path of the file
+     * @param     query   the query string; it will be urlencoded
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Head(String file, String query)
+		throws IOException, ModuleException
+    {
+	return Head(file, query, null);
+    }
+
+
+    /**
+     * Sends the HEAD request. This request is just like the corresponding
+     * GET except that it only returns the headers and no data.
+     *
+     * @see #Get(java.lang.String, java.lang.String, HTTPClient.NVPair[])
+     * @param     file    the absolute path of the file
+     * @param     query   the query string; it will be urlencoded
+     * @param     headers additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Head(String file, String query, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	String File = stripRef(file);
+	if (query != null  &&  query.length() > 0)
+	    File += "?" + Codecs.URLEncode(query);
+
+	return setupRequest("HEAD", File, headers, null, null);
+    }
+
+
+    /**
+     * GETs the file.
+     *
+     * @param     file the absolute path of the file
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Get(String file)  throws IOException, ModuleException
+    {
+	return Get(file, (String) null, null);
+    }
+
+    /**
+     * GETs the file with a query consisting of the specified form-data.
+     * The data is urlencoded, turned into a string of the form
+     * "name1=value1&name2=value2" and then sent as a query string.
+     *
+     * @param     file       the absolute path of the file
+     * @param     form_data  an array of Name/Value pairs
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Get(String file, NVPair form_data[])
+		throws IOException, ModuleException
+    {
+	return Get(file, form_data, null);
+    }
+
+    /**
+     * GETs the file with a query consisting of the specified form-data.
+     * The data is urlencoded, turned into a string of the form
+     * "name1=value1&name2=value2" and then sent as a query string.
+     *
+     * @param     file       the absolute path of the file
+     * @param     form_data  an array of Name/Value pairs
+     * @param     headers    additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Get(String file, NVPair[] form_data, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	String File  = stripRef(file),
+	       query = Codecs.nv2query(form_data);
+	if (query != null  &&  query.length() > 0)
+	    File += "?" + query;
+
+	return setupRequest("GET", File, headers, null, null);
+    }
+
+    /**
+     * GETs the file using the specified query string. The query string
+     * is first urlencoded.
+     *
+     * @param     file  the absolute path of the file
+     * @param     query the query
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Get(String file, String query)
+		throws IOException, ModuleException
+    {
+	return Get(file, query, null);
+    }
+
+    /**
+     * GETs the file using the specified query string. The query string
+     * is first urlencoded.
+     *
+     * @param     file     the absolute path of the file
+     * @param     query    the query string
+     * @param     headers  additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Get(String file, String query, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	String File = stripRef(file);
+	if (query != null  &&  query.length() > 0)
+	    File += "?" + Codecs.URLEncode(query);
+
+	return setupRequest("GET", File, headers, null, null);
+    }
+
+
+    /**
+     * POSTs to the specified file. No data is sent.
+     *
+     * @param     file the absolute path of the file
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Post(String file)  throws IOException, ModuleException
+    {
+	return Post(file, (byte []) null, null);
+    }
+
+    /**
+     * POSTs form-data to the specified file. The data is first urlencoded
+     * and then turned into a string of the form "name1=value1&name2=value2".
+     * A <var>Content-type</var> header with the value
+     * <var>application/x-www-form-urlencoded</var> is added.
+     *
+     * @param     file      the absolute path of the file
+     * @param     form_data an array of Name/Value pairs
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Post(String file, NVPair form_data[])
+		throws IOException, ModuleException
+    {
+	NVPair[] headers =
+	    { new NVPair("Content-type", "application/x-www-form-urlencoded") };
+
+	return Post(file, Codecs.nv2query(form_data), headers);
+    }
+
+    /**
+     * POST's form-data to the specified file using the specified headers.
+     * The data is first urlencoded and then turned into a string of the
+     * form "name1=value1&name2=value2". If no <var>Content-type</var> header
+     * is given then one is added with a value of
+     * <var>application/x-www-form-urlencoded</var>.
+     *
+     * @param     file      the absolute path of the file
+     * @param     form_data an array of Name/Value pairs
+     * @param     headers   additional headers
+     * @return    a HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Post(String file, NVPair form_data[], NVPair headers[])
+                throws IOException, ModuleException
+    {
+	int idx;
+	for (idx=0; idx<headers.length; idx++)
+	    if (headers[idx].getName().equalsIgnoreCase("Content-type")) break;
+	if (idx == headers.length)
+	{
+	    headers = Util.resizeArray(headers, idx+1);
+	    headers[idx] =
+		new NVPair("Content-type", "application/x-www-form-urlencoded");
+	}
+
+	return Post(file, Codecs.nv2query(form_data), headers);
+    }
+
+    /**
+     * POSTs the data to the specified file. The data is converted to an
+     * array of bytes using the default character converter.
+     * The request is sent using the content-type "application/octet-stream".
+     *
+     * @param     file the absolute path of the file
+     * @param     data the data
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     * @see java.lang.String#getBytes()
+     */
+    public HTTPResponse Post(String file, String data)
+		throws IOException, ModuleException
+    {
+	return Post(file, data, null);
+    }
+
+    /**
+     * POSTs the data to the specified file using the specified headers.
+     *
+     * @param     file     the absolute path of the file
+     * @param     data     the data
+     * @param     headers  additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     * @see java.lang.String#getBytes()
+     */
+    public HTTPResponse Post(String file, String data, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	byte tmp[] = null;
+
+	if (data != null  &&  data.length() > 0)
+	    tmp = data.getBytes();
+
+	return Post(file, tmp, headers);
+    }
+
+    /**
+     * POSTs the raw data to the specified file.
+     * The request is sent using the content-type "application/octet-stream"
+     *
+     * @param     file the absolute path of the file
+     * @param     data the data
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Post(String file, byte data[])
+		throws IOException, ModuleException
+    {
+	return Post(file, data, null);
+    }
+
+    /**
+     * POSTs the raw data to the specified file using the specified headers.
+     *
+     * @param     file     the absolute path of the file
+     * @param     data     the data
+     * @param     headers  additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Post(String file, byte data[], NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	if (data == null)  data = new byte[0];	// POST must always have a CL
+	return setupRequest("POST", stripRef(file), headers, data, null);
+    }
+
+
+    /**
+     * POSTs the data written to the output stream to the specified file.
+     * The request is sent using the content-type "application/octet-stream"
+     *
+     * @param     file   the absolute path of the file
+     * @param     stream the output stream on which the data is written
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Post(String file, HttpOutputStream stream)
+		throws IOException, ModuleException
+    {
+	return Post(file, stream, null);
+    }
+
+    /**
+     * POSTs the data written to the output stream to the specified file
+     * using the specified headers.
+     *
+     * @param     file     the absolute path of the file
+     * @param     stream   the output stream on which the data is written
+     * @param     headers  additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Post(String file, HttpOutputStream stream,
+			     NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	return setupRequest("POST", stripRef(file), headers, null, stream);
+    }
+
+
+    /**
+     * PUTs the data into the specified file. The data is converted to an
+     * array of bytes using the default character converter.
+     * The request ist sent using the content-type "application/octet-stream".
+     *
+     * @param     file the absolute path of the file
+     * @param     data the data
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     * @see java.lang.String#getBytes()
+     */
+    public HTTPResponse Put(String file, String data)
+		throws IOException, ModuleException
+    {
+	return Put(file, data, null);
+    }
+
+    /**
+     * PUTs the data into the specified file using the additional headers
+     * for the request.
+     *
+     * @param     file     the absolute path of the file
+     * @param     data     the data
+     * @param     headers  additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     * @see java.lang.String#getBytes()
+     */
+    public HTTPResponse Put(String file, String data, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	byte tmp[] = null;
+
+	if (data != null  &&  data.length() > 0)
+	    tmp = data.getBytes();
+
+	return Put(file, tmp, headers);
+    }
+
+    /**
+     * PUTs the raw data into the specified file.
+     * The request is sent using the content-type "application/octet-stream".
+     *
+     * @param     file     the absolute path of the file
+     * @param     data     the data
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Put(String file, byte data[])
+		throws IOException, ModuleException
+    {
+	return Put(file, data, null);
+    }
+
+    /**
+     * PUTs the raw data into the specified file using the additional
+     * headers.
+     *
+     * @param     file     the absolute path of the file
+     * @param     data     the data
+     * @param     headers  any additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Put(String file, byte data[], NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	if (data == null)  data = new byte[0];	// PUT must always have a CL
+	return setupRequest("PUT", stripRef(file), headers, data, null);
+    }
+
+    /**
+     * PUTs the data written to the output stream into the specified file.
+     * The request is sent using the content-type "application/octet-stream".
+     *
+     * @param     file     the absolute path of the file
+     * @param     stream   the output stream on which the data is written
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Put(String file, HttpOutputStream stream)
+		throws IOException, ModuleException
+    {
+	return Put(file, stream, null);
+    }
+
+    /**
+     * PUTs the data written to the output stream into the specified file
+     * using the additional headers.
+     *
+     * @param     file     the absolute path of the file
+     * @param     stream   the output stream on which the data is written
+     * @param     headers  any additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Put(String file, HttpOutputStream stream,
+			    NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	return setupRequest("PUT", stripRef(file), headers, null, stream);
+    }
+
+
+    /**
+     * Request OPTIONS from the server. If <var>file</var> is "*" then
+     * the request applies to the server as a whole; otherwise it applies
+     * only to that resource.
+     *
+     * @param     file     the absolute path of the resource, or "*"
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Options(String file)
+		throws IOException, ModuleException
+    {
+	return Options(file, null, (byte[]) null);
+    }
+
+
+    /**
+     * Request OPTIONS from the server. If <var>file</var> is "*" then
+     * the request applies to the server as a whole; otherwise it applies
+     * only to that resource.
+     *
+     * @param     file     the absolute path of the resource, or "*"
+     * @param     headers  the headers containing optional info.
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Options(String file, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	return Options(file, headers, (byte[]) null);
+    }
+
+
+    /**
+     * Request OPTIONS from the server. If <var>file</var> is "*" then
+     * the request applies to the server as a whole; otherwise it applies
+     * only to that resource.
+     *
+     * @param     file     the absolute path of the resource, or "*"
+     * @param     headers  the headers containing optional info.
+     * @param     data     any data to be sent in the optional body
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Options(String file, NVPair[] headers, byte[] data)
+		throws IOException, ModuleException
+    {
+	return setupRequest("OPTIONS", stripRef(file), headers, data, null);
+    }
+
+
+    /**
+     * Request OPTIONS from the server. If <var>file</var> is "*" then
+     * the request applies to the server as a whole; otherwise it applies
+     * only to that resource.
+     *
+     * @param     file     the absolute path of the resource, or "*"
+     * @param     headers  the headers containing optional info.
+     * @param     stream   an output stream for sending the optional body
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Options(String file, NVPair[] headers,
+				HttpOutputStream stream)
+		throws IOException, ModuleException
+    {
+	return setupRequest("OPTIONS", stripRef(file), headers, null, stream);
+    }
+
+
+    /**
+     * Requests that <var>file</var> be DELETEd from the server.
+     *
+     * @param     file     the absolute path of the resource
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Delete(String file)
+		throws IOException, ModuleException
+    {
+	return Delete(file, null);
+    }
+
+
+    /**
+     * Requests that <var>file</var> be DELETEd from the server.
+     *
+     * @param     file     the absolute path of the resource
+     * @param     headers  additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Delete(String file, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	return setupRequest("DELETE", stripRef(file), headers, null, null);
+    }
+
+
+    /**
+     * Requests a TRACE. Headers of particular interest here are "Via"
+     * and "Max-Forwards".
+     *
+     * @param     file     the absolute path of the resource
+     * @param     headers  additional headers
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Trace(String file, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	return setupRequest("TRACE", stripRef(file), headers, null, null);
+    }
+
+
+    /**
+     * Requests a TRACE.
+     *
+     * @param     file     the absolute path of the resource
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse Trace(String file)
+		throws IOException, ModuleException
+    {
+	return Trace(file, null);
+    }
+
+
+    /**
+     * This is here to allow an arbitrary, non-standard request to be sent.
+     * I'm assuming you know what you are doing...
+     *
+     * @param     method   the extension method
+     * @param     file     the absolute path of the resource, or null
+     * @param     data     optional data, or null
+     * @param     headers  optional headers, or null
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse ExtensionMethod(String method, String file,
+					byte[] data, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	return setupRequest(method.trim(), stripRef(file), headers, data, null);
+    }
+
+
+    /**
+     * This is here to allow an arbitrary, non-standard request to be sent.
+     * I'm assuming you know what you are doing...
+     *
+     * @param     method   the extension method
+     * @param     file     the absolute path of the resource, or null
+     * @param     stream   optional output stream, or null
+     * @param     headers  optional headers, or null
+     * @return    an HTTPResponse structure containing the response
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    public HTTPResponse ExtensionMethod(String method, String file,
+					HttpOutputStream os, NVPair[] headers)
+		throws IOException, ModuleException
+    {
+	return setupRequest(method.trim(), stripRef(file), headers, null, os);
+    }
+
+
+    /**
+     * Aborts all the requests currently in progress on this connection and
+     * closes all associated sockets. You usually do <em>not</em> need to
+     * invoke this - it only meant for when you need to abruptly stop things,
+     * such as for example the stop button in a browser.
+     *
+     * <P>Note: there is a small window where a request method such as
+     * <code>Get()</code> may have been invoked but the request has not
+     * been built and added to the list. Any request in this window will
+     * not be aborted.
+     *
+     * @since V0.2-3
+     */
+    public void stop()
+    {
+	for (Request req = (Request) RequestList.enumerate(); req != null;
+	     req = (Request) RequestList.next())
+	    req.aborted = true;
+
+	for (StreamDemultiplexor demux =
+				(StreamDemultiplexor) DemuxList.enumerate();
+	     demux != null; demux = (StreamDemultiplexor) DemuxList.next())
+	    demux.abort();
+    }
+
+
+    /**
+     * Sets the default http headers to be sent with each request. The
+     * actual headers sent are determined as follows: for each header
+     * specified in multiple places a value given as part of the request
+     * takes priority over any default values set by this method, which
+     * in turn takes priority over any built-in default values. A different
+     * way of looking at it is that we start off with a list of all headers
+     * specified with the request, then add any default headers set by this
+     * method which aren't already in our list, and finally add any built-in
+     * headers which aren't yet in the list. There is one exception to this
+     * rule: the "Content-length" header is always ignored; and when posting
+     * form-data any default "Content-type" is ignored in favor of the built-in
+     * "application/x-www-form-urlencoded" (however it will be overriden by any
+     * content-type header specified as part of the request).
+     *
+     * <P>Typical headers you might want to set here are "Accept" and its
+     * "Accept-*" relatives, "Connection", "From", "User-Agent", etc.
+     *
+     * @param headers an array of header-name/value pairs (do not give the
+     *                separating ':').
+     */
+    public void setDefaultHeaders(NVPair[] headers)
+    {
+	int length = (headers == null ? 0 : headers.length);
+	NVPair[] def_hdrs = new NVPair[length];
+
+	// weed out undesired headers
+	int sidx, didx;
+	for (sidx=0, didx=0; sidx<length; sidx++)
+	{
+	    if (headers[sidx] == null)
+		continue;
+
+	    String name = headers[sidx].getName().trim();
+	    if (name.equalsIgnoreCase("Content-length"))
+		continue;
+
+	    def_hdrs[didx++] = headers[sidx];
+	}
+
+	if (didx < length)
+	    def_hdrs = Util.resizeArray(def_hdrs, didx);
+
+	synchronized (DefaultHeaders)
+	    { DefaultHeaders = def_hdrs; }
+    }
+
+
+    /**
+     * Gets the current list of default http headers.
+     *
+     * @return an array of header/value pairs.
+     */
+    public NVPair[] getDefaultHeaders()
+    {
+	synchronized (DefaultHeaders)
+	{
+	    return (NVPair[]) DefaultHeaders.clone();
+	}
+    }
+
+
+    /**
+     * Returns the protocol this connection is talking.
+     *
+     * @return a string containing the (lowercased) protocol
+     */
+    public String getProtocol()
+    {
+	switch (Protocol)
+	{
+	    case HTTP:    return "http";
+	    case HTTPS:   return "https";
+	    case SHTTP:   return "shttp";
+	    case HTTP_NG: return "http-ng";
+	    default:
+		throw new Error("HTTPClient Internal Error: invalid protocol " +
+				Protocol);
+	}
+    }
+
+
+    /**
+     * Returns the host this connection is talking to.
+     *
+     * @return a string containing the (lowercased) host name.
+     */
+    public String getHost()
+    {
+	return Host;
+    }
+
+
+    /**
+     * Returns the port this connection connects to. This is always the
+     * actual port number, never -1.
+     *
+     * @return the port number
+     */
+    public int getPort()
+    {
+	return Port;
+    }
+
+
+    /**
+     * Returns the host of the proxy this connection is using.
+     *
+     * @return a string containing the (lowercased) host name.
+     */
+    public String getProxyHost()
+    {
+	return Proxy_Host;
+    }
+
+
+    /**
+     * Returns the port of the proxy this connection is using.
+     *
+     * @return the port number
+     */
+    public int getProxyPort()
+    {
+	return Proxy_Port;
+    }
+
+
+    /**
+     * See if the given uri is compatible with this connection. Compatible
+     * means that the given uri can be retrieved using this connection
+     * object.
+     *
+     * @param uri  the URI to check
+     * @return true if they're compatible, false otherwise
+     * @since V0.3-2
+     */
+    public boolean isCompatibleWith(URI uri)
+    {
+	if (!uri.getScheme().equals(getProtocol())  ||
+	    !uri.getHost().equalsIgnoreCase(Host))
+		return false;
+
+	int port = uri.getPort();
+	if (port == -1)
+	    port = URI.defaultPort(uri.getScheme());
+	return port == Port;
+    }
+
+
+    /**
+     * Sets/Resets raw mode. In raw mode all modules are bypassed, meaning
+     * the automatic handling of authorization requests, redirections,
+     * cookies, etc. is turned off.
+     *
+     * <P>The default is false.
+     *
+     * @deprecated This is not really needed anymore; in V0.2 request were
+     *             synchronous and therefore to do pipelining you needed
+     *             to disable the processing of responses.
+     * @see #removeModule(java.lang.Class)
+     *
+     * @param raw if true removes all modules (except for the retry module)
+     */
+    public void setRawMode(boolean raw)
+    {
+	// Don't remove the retry module
+	String[] modules = { "HTTPClient.CookieModule",
+			     "HTTPClient.RedirectionModule",
+			     "HTTPClient.AuthorizationModule",
+			     "HTTPClient.DefaultModule",
+			     "HTTPClient.TransferEncodingModule",
+			     "HTTPClient.ContentMD5Module",
+			     "HTTPClient.ContentEncodingModule"};
+
+	for (int idx=0; idx<modules.length; idx++)
+	{
+	    try
+	    {
+		if (raw)
+		    removeModule(Class.forName(modules[idx]));
+		else
+		    addModule(Class.forName(modules[idx]), -1);
+	    }
+	    catch (ClassNotFoundException cnfe) { }
+	}
+    }
+
+
+    /**
+     * Sets the default timeout value to be used for each new HTTPConnection.
+     * The default is 0.
+     *
+     * @param time the timeout in milliseconds.
+     * @see #setTimeout(int)
+     */
+    public static void setDefaultTimeout(int time)
+    {
+	DefaultTimeout = time;
+    }
+
+
+    /**
+     * Gets the default timeout value to be used for each new HTTPConnection.
+     *
+     * @return the timeout in milliseconds.
+     * @see #setTimeout(int)
+     */
+    public static int getDefaultTimeout()
+    {
+	return DefaultTimeout;
+    }
+
+
+    /**
+     * Sets the timeout to be used for creating connections and reading
+     * responses. When a timeout expires the operation will throw an
+     * InterruptedIOException. The operation may be restarted again
+     * afterwards. If the operation is not restarted and it is a read
+     * operation (i.e HTTPResponse.xxxx()) then
+     * <code>resp.getInputStream().close()</code> <strong>should</strong> be
+     * invoked.
+     *
+     * <P>When creating new sockets the timeout will limit the time spent
+     * doing the host name translation and establishing the connection with
+     * the server.
+     *
+     * <P>The timeout also influences the reading of the response headers.
+     * However, it does not specify a how long, for example, getStatusCode()
+     * may take, as might be assumed. Instead it specifies how long a read
+     * on the socket may take. If the response dribbles in slowly with
+     * packets arriving quicker than the timeout then the method will
+     * complete normally. I.e. the exception is only thrown if nothing
+     * arrives on the socket for the specified time. Furthermore, the
+     * timeout only influences the reading of the headers, not the reading
+     * of the body.
+     *
+     * <P>Read Timeouts are associated with responses, so that you may change
+     * this value before each request and it won't affect the reading of
+     * responses to previous requests.
+     *
+     * @param time the time in milliseconds. A time of 0 means wait
+     *             indefinitely.
+     * @see #stop()
+     */
+    public void setTimeout(int time)
+    {
+	Timeout = time;
+    }
+
+
+    /**
+     * Gets the timeout used for reading response data.
+     *
+     * @return the current timeout value
+     * @see #setTimeout(int)
+     */
+    public int getTimeout()
+    {
+	return Timeout;
+    }
+
+
+    /**
+     * Controls whether modules are allowed to prompt the user or pop up
+     * dialogs if neccessary.
+     *
+     * @param allow if true allows modules to interact with user.
+     */
+    public void setAllowUserInteraction(boolean allow)
+    {
+	allowUI = allow;
+    }
+
+    /**
+     * returns whether modules are allowed to prompt or popup dialogs
+     * if neccessary.
+     *
+     * @return true if modules are allowed to interact with user.
+     */
+    public boolean getAllowUserInteraction()
+    {
+	return allowUI;
+    }
+
+
+    /**
+     * Sets the default allow-user-action.
+     *
+     * @param allow if true allows modules to interact with user.
+     */
+    public static void setDefaultAllowUserInteraction(boolean allow)
+    {
+	defaultAllowUI = allow;
+    }
+
+    /**
+     * Gets the default allow-user-action.
+     *
+     * @return true if modules are allowed to interact with user.
+     */
+    public static boolean getDefaultAllowUserInteraction()
+    {
+	return defaultAllowUI;
+    }
+
+
+    /**
+     * Returns the default list of modules.
+     *
+     * @return an array of classes
+     */
+    public static Class[] getDefaultModules()
+    {
+	return getModules(DefaultModuleList);
+    }
+
+    /**
+     * Adds a module to the default list. It must implement the
+     * <var>HTTPClientModule</var> interface. If the module is already in
+     * the list then this method does nothing. This method only affects
+     * instances of HTTPConnection created after this method has been
+     * invoked; it does not affect existing instances.
+     *
+     * <P>Example:
+     * <PRE>
+     * HTTPConnection.addDefaultModule(Class.forName("HTTPClient.CookieModule"), 1);
+     * </PRE>
+     * adds the cookie module as the second module in the list.
+     *
+     * <P>The default list is created at class initialization time from the
+     * property <var>HTTPClient.Modules</var>. This must contain a "|"
+     * separated list of classes in the order they're to be invoked. If this
+     * property is not set it defaults to:
+     *
+     * "HTTPClient.RetryModule | HTTPClient.CookieModule |
+     *  HTTPClient.RedirectionModule | HTTPClient.AuthorizationModule |
+     *  HTTPClient.DefaultModule | HTTPClient.TransferEncodingModule |
+     *  HTTPClient.ContentMD5Module | HTTPClient.ContentEncodingModule"
+     *
+     * @see HTTPClientModule
+     * @param module the module's Class object
+     * @param pos    the position of this module in the list; if <var>pos</var>
+     *               >= 0 then this is the absolute position in the list (0 is
+     *               the first position); if <var>pos</var> < 0 then this is
+     *               the position relative to the end of the list (-1 means
+     *               the last element, -2 the second to last element, etc).
+     * @return       true if module was successfully added; false if the
+     *               module is already in the list.
+     * @exception    ArrayIndexOutOfBoundsException if <var>pos</var> >
+     *               list-size or if <var>pos</var> < -(list-size).
+     * @exception    ClassCastException if <var>module</var> does not
+     *               implement the <var>HTTPClientModule</var> interface.
+     * @exception    RuntimeException if <var>module</var> cannot be
+     *               instantiated.
+     */
+    public static boolean addDefaultModule(Class module, int pos)
+    {
+	return addModule(DefaultModuleList, module, pos);
+    }
+
+
+    /**
+     * Removes a module from the default list. If the module is not in the
+     * list it does nothing. This method only affects instances of
+     * HTTPConnection created after this method has been invoked; it does not
+     * affect existing instances.
+     *
+     * @param module the module's Class object
+     * @return true if module was successfully removed; false otherwise
+     */
+    public static boolean removeDefaultModule(Class module)
+    {
+	return removeModule(DefaultModuleList, module);
+    }
+
+
+    /**
+     * Returns the list of modules used currently.
+     *
+     * @return an array of classes
+     */
+    public Class[] getModules()
+    {
+	return getModules(ModuleList);
+    }
+
+
+    /**
+     * Adds a module to the current list. It must implement the
+     * <var>HTTPClientModule</var> interface. If the module is already in
+     * the list then this method does nothing.
+     *
+     * @see HTTPClientModule
+     * @param module the module's Class object
+     * @param pos    the position of this module in the list; if <var>pos</var>
+     *               >= 0 then this is the absolute position in the list (0 is
+     *               the first position); if <var>pos</var> < 0 then this is
+     *               the position relative to the end of the list (-1 means
+     *               the last element, -2 the second to last element, etc).
+     * @return       true if module was successfully added; false if the
+     *               module is already in the list.
+     * @exception    ArrayIndexOutOfBoundsException if <var>pos</var> >
+     *               list-size or if <var>pos</var> < -(list-size).
+     * @exception    ClassCastException if <var>module</var> does not
+     *               implement the <var>HTTPClientModule</var> interface.
+     * @exception    RuntimeException if <var>module</var> cannot be
+     *               instantiated.
+     */
+    public boolean addModule(Class module, int pos)
+    {
+	return addModule(ModuleList, module, pos);
+    }
+
+
+    /**
+     * Removes a module from the current list. If the module is not in the
+     * list it does nothing.
+     *
+     * @param module the module's Class object
+     * @return true if module was successfully removed; false otherwise
+     */
+    public boolean removeModule(Class module)
+    {
+	return removeModule(ModuleList, module);
+    }
+
+    private static final Class[] getModules(Vector list)
+    {
+	synchronized(list)
+	{
+	    Class[] modules = new Class[list.size()];
+	    list.copyInto(modules);
+	    return modules;
+	}
+    }
+
+    private static final boolean addModule(Vector list, Class module, int pos)
+    {
+	if (module == null)  return false;
+
+	// check if module implements HTTPClientModule
+	try
+	    { HTTPClientModule tmp = (HTTPClientModule) module.newInstance(); }
+	catch (RuntimeException re)
+	    { throw re; }
+	catch (Exception e)
+	    { throw new RuntimeException(e.toString()); }
+
+	synchronized (list)
+	{
+	    // check if module already in list
+	    if (list.contains(module))
+		return false;
+
+	    // add module to list
+	    if (pos < 0)
+		list.insertElementAt(module, DefaultModuleList.size()+pos+1);
+	    else
+		list.insertElementAt(module, pos);
+	}
+
+	Log.write(Log.CONN, "Conn:  Added module " + module.getName() +
+			    " to " +
+			    ((list == DefaultModuleList) ? "default " : "") +
+			    "list");
+
+	return true;
+    }
+
+    private static final boolean removeModule(Vector list, Class module)
+    {
+	if (module == null)  return false;
+
+	boolean removed = list.removeElement(module);
+	if (removed)
+	    Log.write(Log.CONN, "Conn:  Removed module " + module.getName() +
+				" from " +
+				((list == DefaultModuleList) ? "default " : "") +
+				"list");
+
+	return removed;
+    }
+
+
+    /**
+     * Sets the current context. The context is used by modules such as
+     * the AuthorizationModule and the CookieModule which keep lists of
+     * info that is normally shared between all instances of HTTPConnection.
+     * This is usually the desired behaviour. However, in some cases one
+     * would like to simulate multiple independent clients within the
+     * same application and hence the sharing of such info should be
+     * restricted. This is where the context comes in. Modules will only
+     * share their info between requests using the same context (i.e. they
+     * keep multiple lists, one for each context).
+     *
+     * <P>The context may be any object. Contexts are considered equal
+     * if <code>equals()</code> returns true. Examples of useful context
+     * objects are threads (e.g. if you are running multiple clients, one
+     * per thread) and sockets (e.g. if you are implementing a gateway).
+     *
+     * <P>When a new HTTPConnection is created it is initialized with a
+     * default context which is the same for all instances. This method
+     * must be invoked immediately after a new HTTPConnection is created
+     * and before any request method is invoked. Furthermore, this method
+     * may only be called once (i.e. the context is "sticky").
+     *
+     * @param context the new context; must be non-null
+     * @exception IllegalArgumentException if <var>context</var> is null
+     * @exception IllegalStateException if the context has already been set
+     */
+    public void setContext(Object context)
+    {
+	if (context == null)
+	    throw new IllegalArgumentException("Context must be non-null");
+	if (Context != null)
+	    throw new IllegalStateException("Context already set");
+
+	Context = context;
+    }
+
+
+    /**
+     * Returns the current context.
+     *
+     * @see #setContext(java.lang.Object)
+     * @return the current context, or the default context if
+     *         <code>setContext()</code> hasn't been invoked
+     */
+    public Object getContext()
+    {
+	if (Context != null)
+	    return Context;
+	else
+	    return dflt_context;
+    }
+
+
+    /**
+     * Returns the default context.
+     *
+     * @see #setContext(java.lang.Object)
+     * @return the default context
+     */
+    public static Object getDefaultContext()
+    {
+	return dflt_context;
+    }
+
+
+    /**
+     * Adds an authorization entry for the "digest" authorization scheme to
+     * the list. If an entry already exists for the "digest" scheme and the
+     * specified realm then it is overwritten.
+     *
+     * <P>This is a convenience method and just invokes the corresponding
+     * method in AuthorizationInfo.
+     *
+     * @param realm the realm
+     * @param user  the username
+     * @param passw the password
+     * @see AuthorizationInfo#addDigestAuthorization(java.lang.String, int, java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void addDigestAuthorization(String realm, String user, String passwd)
+    {
+	AuthorizationInfo.addDigestAuthorization(Host, Port, realm, user,
+						 passwd, getContext());
+    }
+
+
+    /**
+     * Adds an authorization entry for the "basic" authorization scheme to
+     * the list. If an entry already exists for the "basic" scheme and the
+     * specified realm then it is overwritten.
+     *
+     * <P>This is a convenience method and just invokes the corresponding
+     * method in AuthorizationInfo.
+     *
+     * @param realm the realm
+     * @param user  the username
+     * @param passw the password
+     * @see AuthorizationInfo#addBasicAuthorization(java.lang.String, int, java.lang.String, java.lang.String, java.lang.String)
+     */
+    public void addBasicAuthorization(String realm, String user, String passwd)
+    {
+	AuthorizationInfo.addBasicAuthorization(Host, Port, realm, user,
+						passwd, getContext());
+    }
+
+
+    /**
+     * Sets the default proxy server to use. The proxy will only be used
+     * for new <var>HTTPConnection</var>s created after this call and will
+     * not affect currrent instances of <var>HTTPConnection</var>. A null
+     * or empty string <var>host</var> parameter disables the proxy.
+     *
+     * <P>In an application or using the Appletviewer an alternative to
+     * this method is to set the following properties (either in the
+     * properties file or on the command line):
+     * <var>http.proxyHost</var> and <var>http.proxyPort</var>. Whether
+     * <var>http.proxyHost</var> is set or not determines whether a proxy
+     * server is used.
+     *
+     * <P>If the proxy server requires authorization and you wish to set
+     * this authorization information in the code, then you may use any
+     * of the <var>AuthorizationInfo.addXXXAuthorization()</var> methods to
+     * do so. Specify the same <var>host</var> and <var>port</var> as in
+     * this method. If you have not given any authorization info and the
+     * proxy server requires authorization then you will be prompted for
+     * the necessary info via a popup the first time you do a request.
+     *
+     * @see #setCurrentProxy(java.lang.String, int)
+     * @param  host    the host on which the proxy server resides.
+     * @param  port    the port the proxy server is listening on.
+     */
+    public static void setProxyServer(String host, int port)
+    {
+	if (host == null  ||  host.trim().length() == 0)
+	    Default_Proxy_Host = null;
+	else
+	{
+	    Default_Proxy_Host = host.trim().toLowerCase();
+	    Default_Proxy_Port = port;
+	}
+    }
+
+
+    /**
+     * Sets the proxy used by this instance. This can be used to override
+     * the proxy setting inherited from the default proxy setting. A null
+     * or empty string <var>host</var> parameter disables the proxy.
+     *
+     * <P>Note that if you set a proxy for the connection using this
+     * method, and a request made over this connection is redirected
+     * to a different server, then the connection used for new server
+     * will <em>not</em> pick this proxy setting, but instead will use
+     * the default proxy settings.
+     *
+     * @see #setProxyServer(java.lang.String, int)
+     * @param host the host the proxy runs on
+     * @param port the port the proxy is listening on
+     */
+    public synchronized void setCurrentProxy(String host, int port)
+    {
+	if (host == null  ||  host.trim().length() == 0)
+	    Proxy_Host = null;
+	else
+	{
+	    Proxy_Host = host.trim().toLowerCase();
+	    if (port <= 0)
+		Proxy_Port = 80;
+	    else
+		Proxy_Port = port;
+	}
+
+	// the proxy might be talking a different version, so renegotiate
+	switch(Protocol)
+	{
+	    case HTTP:
+	    case HTTPS:
+		if (force_1_0)
+		{
+		    ServerProtocolVersion  = HTTP_1_0;
+		    ServProtVersKnown      = true;
+		    RequestProtocolVersion = "HTTP/1.0";
+		}
+		else
+		{
+		    ServerProtocolVersion  = HTTP_1_1;
+		    ServProtVersKnown      = false;
+		    RequestProtocolVersion = "HTTP/1.1";
+		}
+		break;
+	    case HTTP_NG:
+		ServerProtocolVersion  = -1;		/* Unknown */
+		ServProtVersKnown      = false;
+		RequestProtocolVersion = "";
+		break;
+	    case SHTTP:
+		ServerProtocolVersion  = -1;		/* Unknown */
+		ServProtVersKnown      = false;
+		RequestProtocolVersion = "Secure-HTTP/1.3";
+		break;
+	    default:
+		throw new Error("HTTPClient Internal Error: invalid protocol " +
+				Protocol);
+	}
+
+	keepAliveUnknown = true;
+	doesKeepAlive    = false;
+
+	input_demux = null;
+	early_stall = null;
+	late_stall  = null;
+	prev_resp   = null;
+    }
+
+
+    /**
+     * Add <var>host</var> to the list of hosts which should be accessed
+     * directly, not via any proxy set by <code>setProxyServer()</code>.
+     *
+     * <P>The <var>host</var> may be any of:
+     * <UL>
+     * <LI>a complete host name (e.g. "www.disney.com")
+     * <LI>a domain name; domain names must begin with a dot (e.g.
+     *     ".disney.com")
+     * <LI>an IP-address (e.g. "12.34.56.78")
+     * <LI>an IP-subnet, specified as an IP-address and a netmask separated
+     *     by a "/" (e.g. "34.56.78/255.255.255.192"); a 0 bit in the netmask
+     *     means that that bit won't be used in the comparison (i.e. the
+     *     addresses are AND'ed with the netmask before comparison).
+     * </UL>
+     *
+     * <P>The two properties <var>HTTPClient.nonProxyHosts</var> and
+     * <var>http.nonProxyHosts</var> are used when this class is loaded to
+     * initialize the list of non-proxy hosts. The second property is only
+     * read if the first one is not set; the second property is also used
+     * the JDK's URLConnection. These properties must contain a "|"
+     * separated list of entries which conform to the above rules for the
+     * <var>host</var> parameter (e.g. "11.22.33.44|.disney.com").
+     *
+     * @param host a host name, domain name, IP-address or IP-subnet.
+     * @exception ParseException if the length of the netmask does not match
+     *                           the length of the IP-address
+     */
+    public static void dontProxyFor(String host)  throws ParseException
+    {
+	host = host.trim().toLowerCase();
+
+	// check for domain name
+
+	if (host.charAt(0) == '.')
+	{
+	    if (!non_proxy_dom_list.contains(host))
+		non_proxy_dom_list.addElement(host);
+	    return;
+	}
+
+
+	// check for host name
+
+	for (int idx=0; idx<host.length(); idx++)
+	{
+	    if (!Character.isDigit(host.charAt(idx))  &&
+		host.charAt(idx) != '.'  &&  host.charAt(idx) != '/')
+	    {
+		non_proxy_host_list.put(host, "");
+		return;
+	    }
+	}
+
+
+	// must be an IP-address
+
+	byte[] ip_addr;
+	byte[] ip_mask;
+	int slash;
+	if ((slash = host.indexOf('/')) != -1)	// IP subnet
+	{
+	    ip_addr = string2arr(host.substring(0, slash));
+	    ip_mask = string2arr(host.substring(slash+1));
+	    if (ip_addr.length != ip_mask.length)
+		throw new ParseException("length of IP-address (" +
+				ip_addr.length + ") != length of netmask (" +
+				ip_mask.length + ")");
+	}
+	else
+	{
+	    ip_addr = string2arr(host);
+	    ip_mask = new byte[ip_addr.length];
+	    for (int idx=0; idx<ip_mask.length; idx++)
+		ip_mask[idx] = (byte) 255;
+	}
+
+
+	// check if addr or subnet already exists
+
+	ip_loop: for (int idx=0; idx<non_proxy_addr_list.size(); idx++)
+	{
+	    byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
+	    byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
+	    if (addr.length != ip_addr.length)  continue;
+
+	    for (int idx2=0; idx2<addr.length; idx2++)
+	    {
+		if ((ip_addr[idx2] & mask[idx2]) != (addr[idx2] & mask[idx2]) ||
+		    (mask[idx2] != ip_mask[idx2]))
+		    continue ip_loop;
+	    }
+
+	    return;			// already exists
+	}
+	non_proxy_addr_list.addElement(ip_addr);
+	non_proxy_mask_list.addElement(ip_mask);
+    }
+
+
+    /**
+     * Convenience method to add a number of hosts at once. If any one
+     * host is null or cannot be parsed it is ignored.
+     *
+     * @param hosts The list of hosts to set
+     * @see #dontProxyFor(java.lang.String)
+     * @since V0.3-2
+     */
+    public static void dontProxyFor(String[] hosts)
+    {
+        if (hosts == null  ||  hosts.length == 0)
+	    return;
+
+        for (int idx=0; idx<hosts.length; idx++)
+        {
+            try
+            {
+                if (hosts[idx] != null)
+                    dontProxyFor(hosts[idx]);
+            }
+            catch(ParseException pe)
+            {
+		// ignore it
+            }
+        }
+    }
+
+
+    /**
+     * Remove <var>host</var> from the list of hosts for which the proxy
+     * should not be used. This modifies the same list that
+     * <code>dontProxyFor()</code> uses, i.e. this is used to undo a
+     * <code>dontProxyFor()</code> setting. The syntax for <var>host</var> is
+     * specified in <code>dontProxyFor()</code>.
+     *
+     * @param host a host name, domain name, IP-address or IP-subnet.
+     * @return true if the remove was sucessful, false otherwise
+     * @exception ParseException if the length of the netmask does not match
+     *                           the length of the IP-address
+     * @see #dontProxyFor(java.lang.String)
+     */
+    public static boolean doProxyFor(String host)  throws ParseException
+    {
+	host = host.trim().toLowerCase();
+
+	// check for domain name
+
+	if (host.charAt(0) == '.')
+	    return non_proxy_dom_list.removeElement(host);
+
+
+	// check for host name
+
+	for (int idx=0; idx<host.length(); idx++)
+	{
+	    if (!Character.isDigit(host.charAt(idx))  &&
+		host.charAt(idx) != '.'  &&  host.charAt(idx) != '/')
+		return (non_proxy_host_list.remove(host) != null);
+	}
+
+
+	// must be an IP-address
+
+	byte[] ip_addr;
+	byte[] ip_mask;
+	int slash;
+	if ((slash = host.indexOf('/')) != -1)	// IP subnet
+	{
+	    ip_addr = string2arr(host.substring(0, slash));
+	    ip_mask = string2arr(host.substring(slash+1));
+	    if (ip_addr.length != ip_mask.length)
+		throw new ParseException("length of IP-address (" +
+				ip_addr.length + ") != length of netmask (" +
+				ip_mask.length + ")");
+	}
+	else
+	{
+	    ip_addr = string2arr(host);
+	    ip_mask = new byte[ip_addr.length];
+	    for (int idx=0; idx<ip_mask.length; idx++)
+		ip_mask[idx] = (byte) 255;
+	}
+
+	ip_loop: for (int idx=0; idx<non_proxy_addr_list.size(); idx++)
+	{
+	    byte[] addr = (byte[]) non_proxy_addr_list.elementAt(idx);
+	    byte[] mask = (byte[]) non_proxy_mask_list.elementAt(idx);
+	    if (addr.length != ip_addr.length)  continue;
+
+	    for (int idx2=0; idx2<addr.length; idx2++)
+	    {
+		if ((ip_addr[idx2] & mask[idx2]) != (addr[idx2] & mask[idx2]) ||
+		    (mask[idx2] != ip_mask[idx2]))
+		    continue ip_loop;
+	    }
+
+	    non_proxy_addr_list.removeElementAt(idx);
+	    non_proxy_mask_list.removeElementAt(idx);
+	    return true;
+	}
+	return false;
+    }
+
+
+    /**
+     * Turn an IP-address string into an array (e.g. "12.34.56.78" into
+     * { 12, 34, 56, 78 }).
+     *
+     * @param ip IP-address
+     * @return IP-address in network byte order
+     */
+    private static byte[] string2arr(String ip)
+    {
+	byte[] arr;
+	char[] ip_char = new char[ip.length()];
+	ip.getChars(0, ip_char.length, ip_char, 0);
+
+	int cnt = 0;
+	for (int idx=0; idx<ip_char.length; idx++)
+	    if (ip_char[idx] == '.') cnt++;
+	arr = new byte[cnt+1];
+
+	cnt = 0;
+	int pos = 0;
+	for (int idx=0; idx<ip_char.length; idx++)
+	    if (ip_char[idx] == '.')
+	    {
+		arr[cnt] = (byte) Integer.parseInt(ip.substring(pos, idx));
+		cnt++;
+		pos = idx+1;
+	    }
+	arr[cnt] = (byte) Integer.parseInt(ip.substring(pos));
+
+	return arr;
+    }
+
+
+    /**
+     * Sets the SOCKS server to use. The server will only be used
+     * for new HTTPConnections created after this call and will not affect
+     * currrent instances of HTTPConnection. A null or empty string host
+     * parameter disables SOCKS.
+     * <P>The code will try to determine the SOCKS version to use at
+     * connection time. This might fail for a number of reasons, however,
+     * in which case you must specify the version explicitly.
+     *
+     * @see #setSocksServer(java.lang.String, int, int)
+     * @param  host    the host on which the proxy server resides. The port
+     *                 used is the default port 1080.
+     */
+    public static void setSocksServer(String host)
+    {
+	setSocksServer(host, 1080);
+    }
+
+
+    /**
+     * Sets the SOCKS server to use. The server will only be used
+     * for new HTTPConnections created after this call and will not affect
+     * currrent instances of HTTPConnection. A null or empty string host
+     * parameter disables SOCKS.
+     * <P>The code will try to determine the SOCKS version to use at
+     * connection time. This might fail for a number of reasons, however,
+     * in which case you must specify the version explicitly.
+     *
+     * @see #setSocksServer(java.lang.String, int, int)
+     * @param  host    the host on which the proxy server resides.
+     * @param  port    the port the proxy server is listening on.
+     */
+    public static void setSocksServer(String host, int port)
+    {
+	if (port <= 0)
+	    port = 1080;
+
+	if (host == null  ||  host.length() == 0)
+	    Default_Socks_client = null;
+	else
+	    Default_Socks_client = new SocksClient(host, port);
+    }
+
+
+    /**
+     * Sets the SOCKS server to use. The server will only be used
+     * for new HTTPConnections created after this call and will not affect
+     * currrent instances of HTTPConnection. A null or empty string host
+     * parameter disables SOCKS.
+     *
+     * <P>In an application or using the Appletviewer an alternative to
+     * this method is to set the following properties (either in the
+     * properties file or on the command line):
+     * <var>HTTPClient.socksHost</var>, <var>HTTPClient.socksPort</var>
+     * and <var>HTTPClient.socksVersion</var>. Whether
+     * <var>HTTPClient.socksHost</var> is set or not determines whether a
+     * SOCKS server is used; if <var>HTTPClient.socksPort</var> is not set
+     * it defaults to 1080; if <var>HTTPClient.socksVersion</var> is not
+     * set an attempt will be made to automatically determine the version
+     * used by the server.
+     *
+     * <P>Note: If you have also set a proxy server then a connection
+     * will be made to the SOCKS server, which in turn then makes a
+     * connection to the proxy server (possibly via other SOCKS servers),
+     * which in turn makes the final connection.
+     *
+     * <P>If the proxy server is running SOCKS version 5 and requires
+     * username/password authorization, and you wish to set
+     * this authorization information in the code, then you may use the
+     * <var>AuthorizationInfo.addAuthorization()</var> method to do so.
+     * Specify the same <var>host</var> and <var>port</var> as in this
+     * method, give the <var>scheme</var> "SOCKS5" and the <var>realm</var>
+     * "USER/PASS", set the <var>cookie</var> to null and the
+     * <var>params</var> to an array containing a single <var>NVPair</var>
+     * in turn containing the username and password. Example:
+     * <PRE>
+     *     NVPair[] up = { new NVPair(username, password) };
+     *     AuthorizationInfo.addAuthorization(host, port, "SOCKS5", "USER/PASS",
+     *                                        null, up);
+     * </PRE>
+     * If you have not given any authorization info and the proxy server
+     * requires authorization then you will be prompted for the necessary
+     * info via a popup the first time you do a request.
+     *
+     * @param  host    the host on which the proxy server resides.
+     * @param  port    the port the proxy server is listening on.
+     * @param  version the SOCKS version the server is running. Currently
+     *                 this must be '4' or '5'.
+     * @exception SocksException If <var>version</var> is not '4' or '5'.
+     */
+    public static void setSocksServer(String host, int port, int version)
+	    throws SocksException
+    {
+	if (port <= 0)
+	    port = 1080;
+
+	if (host == null  ||  host.length() == 0)
+	    Default_Socks_client = null;
+	else
+	    Default_Socks_client = new SocksClient(host, port, version);
+    }
+
+
+    /**
+     * Removes the #... part. Returns the stripped name, or "" if either
+     * the <var>file</var> is null or is the empty string (after stripping).
+     *
+     * @param file the name to strip
+     * @return the stripped name
+     */
+    private final String stripRef(String file)
+    {
+	if (file == null)  return "";
+
+	int hash = file.indexOf('#');
+	if (hash != -1)
+	    file = file.substring(0,hash);
+
+	return file.trim();
+    }
+
+
+    // private helper methods
+
+    /**
+     * Sets up the request, creating the list of headers to send and
+     * creating instances of the modules. This may be invoked by subclasses
+     * which add further methods (such as those from DAV and IPP).
+     *
+     * @param  method   GET, POST, etc.
+     * @param  resource the resource
+     * @param  headers  an array of headers to be used
+     * @param  entity   the entity (or null)
+     * @param  stream   the output stream (or null) - only one of stream and
+     *                  entity may be non-null
+     * @return the response.
+     * @exception java.io.IOException when an exception is returned from
+     *                                the socket.
+     * @exception ModuleException if an exception is encountered in any module.
+     */
+    protected final HTTPResponse setupRequest(String method, String resource,
+					      NVPair[] headers, byte[] entity,
+					      HttpOutputStream stream)
+		throws IOException, ModuleException
+    {
+	Request req = new Request(this, method, resource,
+				  mergedHeaders(headers), entity, stream,
+				  allowUI);
+	RequestList.addToEnd(req);
+
+	try
+	{
+	    HTTPResponse resp = new HTTPResponse(gen_mod_insts(), Timeout, req);
+	    handleRequest(req, resp, null, true);
+	    return resp;
+	}
+	finally
+	    { RequestList.remove(req); }
+    }
+
+
+    /**
+     * This merges built-in default headers, user-specified default headers,
+     * and method-specified headers. Method-specified take precedence over
+     * user defaults, which take precedence over built-in defaults.
+     *
+     * The following headers are removed if found: "Content-length".
+     *
+     * @param  spec   the headers specified in the call to the method
+     * @return an array consisting of merged headers.
+     */
+    private NVPair[] mergedHeaders(NVPair[] spec)
+    {
+	int spec_len = (spec != null ? spec.length : 0),
+	    defs_len;
+	NVPair[] merged;
+
+	synchronized (DefaultHeaders)
+	{
+	    defs_len = (DefaultHeaders != null ? DefaultHeaders.length : 0);
+	    merged   = new NVPair[spec_len + defs_len];
+
+	    // copy default headers
+	    System.arraycopy(DefaultHeaders, 0, merged, 0, defs_len);
+	}
+
+	// merge in selected headers
+	int sidx, didx = defs_len;
+	for (sidx=0; sidx<spec_len; sidx++)
+	{
+	    if (spec[sidx] == null)
+		continue;
+
+	    String s_name = spec[sidx].getName().trim();
+	    if (s_name.equalsIgnoreCase("Content-length"))
+		continue;
+
+	    int search;
+	    for (search=0; search<didx; search++)
+	    {
+		if (merged[search].getName().trim().equalsIgnoreCase(s_name))
+		    break;
+	    }
+
+	    merged[search] = spec[sidx];
+	    if (search == didx) didx++;
+	}
+
+	if (didx < merged.length)
+	    merged = Util.resizeArray(merged, didx);
+
+	return merged;
+    }
+
+
+    /**
+     * Generate an array of instances of the current modules.
+     */
+    private HTTPClientModule[] gen_mod_insts()
+    {
+	synchronized (ModuleList)
+	{
+	    HTTPClientModule[] mod_insts =
+		new HTTPClientModule[ModuleList.size()];
+
+	    for (int idx=0; idx<ModuleList.size(); idx++)
+	    {
+		Class mod = (Class) ModuleList.elementAt(idx);
+		try
+		    { mod_insts[idx] = (HTTPClientModule) mod.newInstance(); }
+		catch (Exception e)
+		{
+		    throw new Error("HTTPClient Internal Error: could not " +
+				    "create instance of " + mod.getName() +
+				    " -\n" + e);
+		}
+	    }
+
+	    return mod_insts;
+	}
+    }
+
+
+    /**
+     * handles the Request. First the request handler for each module is
+     * is invoked, and then if no response was generated the request is
+     * sent.
+     *
+     * @param  req        the Request
+     * @param  http_resp  the HTTPResponse
+     * @param  resp       the Response
+     * @param  usemodules if false then skip module loop
+     * @exception IOException if any module or sendRequest throws it
+     * @exception ModuleException if any module throws it
+     */
+    void handleRequest(Request req, HTTPResponse http_resp, Response resp,
+		       boolean usemodules)
+		throws IOException, ModuleException
+    {
+	Response[]         rsp_arr = { resp };
+	HTTPClientModule[] modules = http_resp.getModules();
+
+
+	// invoke requestHandler for each module
+
+	if (usemodules)
+	doModules: for (int idx=0; idx<modules.length; idx++)
+	{
+	    int sts = modules[idx].requestHandler(req, rsp_arr);
+	    switch (sts)
+	    {
+		case REQ_CONTINUE:	// continue processing
+		    break;
+
+		case REQ_RESTART:	// restart processing with first module
+		    idx = -1;
+		    continue doModules;
+
+		case REQ_SHORTCIRC:	// stop processing and send
+		    break doModules;
+
+		case REQ_RESPONSE:	// go to phase 2
+		case REQ_RETURN:		// return response immediately
+		    if (rsp_arr[0] == null)
+			throw new Error("HTTPClient Internal Error: no " +
+					"response returned by module " +
+					modules[idx].getClass().getName());
+		    http_resp.set(req, rsp_arr[0]);
+		    if (req.getStream() != null)
+			req.getStream().ignoreData(req);
+		    if (req.internal_subrequest)  return;
+		    if (sts == REQ_RESPONSE)
+			http_resp.handleResponse();
+		    else
+			http_resp.init(rsp_arr[0]);
+		    return;
+
+		case REQ_NEWCON_RST:	// new connection
+		    if (req.internal_subrequest)  return;
+		    req.getConnection().
+			    handleRequest(req, http_resp, rsp_arr[0], true);
+		    return;
+
+		case REQ_NEWCON_SND:	// new connection, send immediately
+		    if (req.internal_subrequest)  return;
+		    req.getConnection().
+			    handleRequest(req, http_resp, rsp_arr[0], false);
+		    return;
+
+		default:		// not valid
+		    throw new Error("HTTPClient Internal Error: invalid status"+
+				    " " + sts + " returned by module " +
+				    modules[idx].getClass().getName());
+	    }
+	}
+
+	if (req.internal_subrequest)  return;
+
+
+	// Send the request across the wire
+
+	if (req.getStream() != null  &&  req.getStream().getLength() == -1)
+	{
+	    if (!ServProtVersKnown  ||  ServerProtocolVersion < HTTP_1_1  ||
+		no_chunked)
+	    {
+		req.getStream().goAhead(req, null, http_resp.getTimeout());
+		http_resp.set(req, req.getStream());
+	    }
+	    else
+	    {
+		// add Transfer-Encoding header if necessary
+		int idx;
+		NVPair[] hdrs = req.getHeaders();
+		for (idx=0; idx<hdrs.length; idx++)
+		    if (hdrs[idx].getName().equalsIgnoreCase("Transfer-Encoding"))
+			break;
+
+		if (idx == hdrs.length)
+		{
+		    hdrs = Util.resizeArray(hdrs, idx+1);
+		    hdrs[idx] = new NVPair("Transfer-Encoding", "chunked");
+		    req.setHeaders(hdrs);
+		}
+		else
+		{
+		    String v = hdrs[idx].getValue();
+		    try
+		    {
+			if (!Util.hasToken(v, "chunked"))
+			    hdrs[idx] = new NVPair("Transfer-Encoding",
+						   v + ", chunked");
+		    }
+		    catch (ParseException pe)
+			{ throw new IOException(pe.toString()); }
+		}
+
+		http_resp.set(req, sendRequest(req, http_resp.getTimeout()));
+	    }
+	}
+	else
+	    http_resp.set(req, sendRequest(req, http_resp.getTimeout()));
+
+	if (req.aborted)  throw new IOException("Request aborted by user");
+    }
+
+
+    /** These mark the response to stall the next request on, if any */
+    private volatile Response early_stall = null;
+    private volatile Response late_stall  = null;
+    private volatile Response prev_resp   = null;
+    /** This marks the socket output stream as still being used */
+    private boolean  output_finished = true;
+
+    /**
+     * sends the request over the line.
+     *
+     * @param  req         the request
+     * @param  con_timeout the timeout to use when establishing a socket
+     *                     connection; an InterruptedIOException is thrown
+     *                     if the procedure times out.
+     * @param  http_resp   the HTTPResponse to add the new response to
+     * @exception IOException     if thrown by the socket
+     * @exception InterruptedIOException if the connection is not established
+     *                                   within the specified timeout
+     * @exception ModuleException if any module throws it during the SSL-
+     *                            tunneling handshake
+     */
+    Response sendRequest(Request req, int con_timeout)
+		throws IOException, ModuleException
+    {
+	ByteArrayOutputStream hdr_buf = new ByteArrayOutputStream(600);
+	Response              resp = null;
+	boolean		      keep_alive;
+
+
+	// The very first request is special in that we need its response
+	// before any further requests may be made. This is to set things
+	// like the server version.
+
+	if (early_stall != null)
+	{
+	    try
+	    {
+		Log.write(Log.CONN, "Conn:  Early-stalling Request: " +
+				    req.getMethod() + " " +
+				    req.getRequestURI());
+
+		synchronized (early_stall)
+		{
+		    // wait till the response is received
+		    try
+			{ early_stall.getVersion(); }
+		    catch (IOException ioe)
+			{ }
+		    early_stall = null;
+		}
+	    }
+	    catch (NullPointerException npe)
+		{ }
+	}
+
+
+	String[] con_hdrs = assembleHeaders(req, hdr_buf);
+
+
+	// determine if the connection should be kept alive after this
+	// request
+
+	try
+	{
+	    if (ServerProtocolVersion >= HTTP_1_1  &&
+		 !Util.hasToken(con_hdrs[0], "close")
+		||
+		ServerProtocolVersion == HTTP_1_0  &&
+		 Util.hasToken(con_hdrs[0], "keep-alive")
+		)
+		keep_alive = true;
+	    else
+		keep_alive = false;
+	}
+	catch (ParseException pe)
+	    { throw new IOException(pe.toString()); }
+
+
+	synchronized (this)
+	{
+	// Sometimes we must stall the pipeline until the previous request
+	// has been answered. However, if we are going to open up a new
+	// connection anyway we don't really need to stall.
+
+	if (late_stall != null)
+	{
+	    if (input_demux != null  ||  keepAliveUnknown)
+	    {
+		Log.write(Log.CONN, "Conn:  Stalling Request: " +
+				    req.getMethod() + " " + req.getRequestURI());
+
+		try			// wait till the response is received
+		{
+		    late_stall.getVersion();
+		    if (keepAliveUnknown)
+			determineKeepAlive(late_stall);
+		}
+		catch (IOException ioe)
+		    { }
+	    }
+
+	    late_stall = null;
+	}
+
+
+	/* POSTs must not be pipelined because of problems if the connection
+	 * is aborted. Since it is generally impossible to know what urls
+	 * POST will influence it is impossible to determine if a sequence
+	 * of requests containing a POST is idempotent.
+	 * Also, for retried requests we don't want to pipeline either.
+	 */
+	if ((req.getMethod().equals("POST")  ||  req.dont_pipeline)  &&
+	    prev_resp != null  &&  input_demux != null)
+	{
+	    Log.write(Log.CONN, "Conn:  Stalling Request: " +
+				req.getMethod() + " " + req.getRequestURI());
+
+	    try				// wait till the response is received
+		{ prev_resp.getVersion(); }
+	    catch (IOException ioe)
+		{ }
+	}
+
+
+	// If the previous request used an output stream, then wait till
+	// all the data has been written
+
+	if (!output_finished)
+	{
+	    try
+		{ wait(); }
+	    catch (InterruptedException ie)
+		{ throw new IOException(ie.toString()); }
+	}
+
+
+	if (req.aborted)  throw new IOException("Request aborted by user");
+
+	int try_count = 3;
+	/* what a hack! This is to handle the case where the server closes
+	 * the connection but we don't realize it until we try to send
+	 * something. The problem is that we only get IOException, but
+	 * we need a finer specification (i.e. whether it's an EPIPE or
+	 * something else); I don't trust relying on the message part
+	 * of IOException (which on SunOS/Solaris gives 'Broken pipe',
+	 * but what on Windoze/Mac?).
+	 */
+
+	while (try_count-- > 0)
+	{
+	    try
+	    {
+		// get a client socket
+
+		Socket sock;
+		if (input_demux == null  ||
+		    (sock = input_demux.getSocket()) == null)
+		{
+		    sock = getSocket(con_timeout);
+
+		    if (Protocol == HTTPS)
+		    {
+			if (Proxy_Host != null)
+			{
+			    Socket[] sarr = { sock };
+			    resp = enableSSLTunneling(sarr, req, con_timeout);
+			    if (resp != null)
+			    {
+				resp.final_resp = true;
+				return resp;
+			    }
+			    sock = sarr[0];
+			}
+
+			sock.setSoTimeout(con_timeout);
+			//sock = new SSLSocket(sock);
+		    }
+
+		    input_demux = new StreamDemultiplexor(Protocol, sock, this);
+		    DemuxList.addToEnd(input_demux);
+		    keepAliveReqLeft = keepAliveReqMax;
+		}
+
+		if (req.aborted)
+		    throw new IOException("Request aborted by user");
+
+		Log.write(Log.CONN, "Conn:  Sending Request: ", hdr_buf);
+
+
+		// Send headers
+
+		OutputStream sock_out = sock.getOutputStream();
+		if (haveMSLargeWritesBug)
+		    sock_out = new MSLargeWritesBugStream(sock_out);
+
+		hdr_buf.writeTo(sock_out);
+
+
+		// Wait for "100 Continue" status if necessary
+
+		try
+		{
+		    if (ServProtVersKnown  &&
+			ServerProtocolVersion >= HTTP_1_1  &&
+			Util.hasToken(con_hdrs[1], "100-continue"))
+		    {
+			resp = new Response(req, (Proxy_Host != null && Protocol != HTTPS), input_demux);
+			resp.timeout = 60;
+			if (resp.getContinue() != 100)
+			    break;
+		    }
+		}
+		catch (ParseException pe)
+		    { throw new IOException(pe.toString()); }
+		catch (InterruptedIOException iioe)
+		    { }
+		finally
+		    { if (resp != null)  resp.timeout = 0; }
+
+
+		// POST/PUT data
+
+		if (req.getData() != null  &&  req.getData().length > 0)
+		{
+		    if (req.delay_entity > 0)
+		    {
+                        // wait for something on the network; check available()
+                        // roughly every 100 ms
+
+			long num_units = req.delay_entity / 100;
+			long one_unit  = req.delay_entity / num_units;
+
+                        for (int idx=0; idx<num_units; idx++)
+                        {
+                            if (input_demux.available(null) != 0)
+                                break;
+                            try { Thread.sleep(one_unit); }
+                            catch (InterruptedException ie) { }
+                        }
+
+                        if (input_demux.available(null) == 0)
+			    sock_out.write(req.getData()); // he's still waiting
+			else
+			    keep_alive = false;		// Uh oh!
+		    }
+		    else
+			sock_out.write(req.getData());
+		}
+
+		if (req.getStream() != null)
+		    req.getStream().goAhead(req, sock_out, 0);
+		else
+		    sock_out.flush();
+
+
+		// get a new response.
+		// Note: this does not do a read on the socket.
+
+		if (resp == null)
+		    resp = new Response(req, (Proxy_Host != null &&
+					     Protocol != HTTPS),
+					input_demux);
+	    }
+	    catch (IOException ioe)
+	    {
+		Log.write(Log.CONN, "Conn:  ", ioe);
+
+		closeDemux(ioe, true);
+
+		if (try_count == 0  ||  ioe instanceof UnknownHostException  ||
+		    ioe instanceof ConnectException  ||  
+		    ioe instanceof NoRouteToHostException  ||  
+		    ioe instanceof InterruptedIOException  ||  req.aborted)
+		    throw ioe;
+
+		Log.write(Log.CONN, "Conn:  Retrying request");
+		continue;
+	    }
+
+	    break;
+	}
+
+	prev_resp = resp;
+
+
+	// close the stream after this response if necessary
+
+	if ((!keepAliveUnknown && !doesKeepAlive)  ||  !keep_alive  ||
+	    (keepAliveReqMax != -1  &&  keepAliveReqLeft-- == 0))
+	{
+	    input_demux.markForClose(resp);
+	    input_demux = null;
+	}
+	else
+	    input_demux.restartTimer();
+
+	if (keepAliveReqMax != -1)
+	    Log.write(Log.CONN, "Conn:  Number of requests left: "+
+				keepAliveReqLeft);
+
+
+	/* We don't pipeline the first request, as we need some info
+	 * about the server (such as which http version it complies with)
+	 */
+	if (!ServProtVersKnown)
+	    { early_stall = resp; resp.markAsFirstResponse(req); }
+
+	/* Also don't pipeline until we know if the server supports
+	 * keep-alive's or not.
+	 * Note: strictly speaking, HTTP/1.0 keep-alives don't mean we can
+	 *       pipeline requests. I seem to remember some (beta?) version
+	 *       of Netscape's Enterprise server which barfed if you tried
+	 *       push requests down it's throat w/o waiting for the previous
+	 *       response first. However, I've not been able to find such a
+	 *       server lately, and so I'm taking the risk and assuming we
+	 *       can in fact pipeline requests to HTTP/1.0 servers.
+	 */
+	if (keepAliveUnknown  ||
+		// We don't pipeline POST's ...
+	    !IdempotentSequence.methodIsIdempotent(req.getMethod())  ||
+	    req.dont_pipeline  ||	// Retries disable pipelining too
+	    neverPipeline)	// Emergency measure: prevent all pipelining
+	    { late_stall = resp; }
+
+
+	/* If there is an output stream then just tell the other threads to
+	 * wait; the stream will notify() when it's done. If there isn't any
+	 * stream then wake up a waiting thread (if any).
+	 */
+	if (req.getStream() != null)
+	    output_finished = false;
+	else
+	{
+	    output_finished = true;
+	    notify();
+	}
+
+
+	// Looks like were finally done
+
+	Log.write(Log.CONN, "Conn:  Request sent");
+	}
+
+	return resp;
+    }
+
+
+    /**
+     * Gets a socket. Creates a socket to the proxy if set, or else to the
+     * actual destination.
+     *
+     * @param con_timeout if not 0 then start a new thread to establish the
+     *                    the connection and join(con_timeout) it. If the
+     *                    join() times out an InteruptedIOException is thrown.
+     */
+    private Socket getSocket(int con_timeout)  throws IOException
+    {
+	Socket sock = null;
+
+	String actual_host;
+	int    actual_port;
+
+	if (Proxy_Host != null)
+	{
+	    actual_host = Proxy_Host;
+	    actual_port = Proxy_Port;
+	}
+	else
+	{
+	    actual_host = Host;
+	    actual_port = Port;
+	}
+
+	Log.write(Log.CONN, "Conn:  Creating Socket: " + actual_host + ":" +
+			    actual_port);
+
+	if (con_timeout == 0)		// normal connection establishment
+	{
+	    if (Socks_client != null)
+		sock = Socks_client.getSocket(actual_host, actual_port);
+	    else
+	    {
+		// try all A records
+		InetAddress[] addr_list = InetAddress.getAllByName(actual_host);
+		for (int idx=0; idx<addr_list.length; idx++)
+		{
+		    try
+		    {
+			if (LocalAddr == null)
+			    sock = new Socket(addr_list[idx], actual_port);
+			else
+			    sock = new Socket(addr_list[idx], actual_port,
+					      LocalAddr, LocalPort);
+			break;		// success
+		    }
+		    catch (SocketException se)
+		    {
+			if (idx == addr_list.length-1)
+			    throw se;	// we tried them all
+		    }
+		}
+	    }
+	}
+	else
+	{
+	    EstablishConnection con =
+		new EstablishConnection(actual_host, actual_port, Socks_client);
+	    con.start();
+	    try
+		{ con.join((long) con_timeout); }
+	    catch (InterruptedException ie)
+		{ }
+
+	    if (con.getException() != null)
+		throw con.getException();
+	    if ((sock = con.getSocket()) == null)
+	    {
+		con.forget();
+		if ((sock = con.getSocket()) == null)
+		    throw new InterruptedIOException("Connection establishment timed out");
+	    }
+	}
+
+	return sock;
+    }
+
+
+    /**
+     * Enable SSL Tunneling if we're talking to a proxy. See ietf draft
+     * draft-luotonen-ssl-tunneling-03 for more info.
+     *
+     * @param sock    the socket
+     * @param req     the request initiating this connection
+     * @param timeout the timeout
+     * @return the proxy's last response if unsuccessful, or null if
+     *         tunnel successfuly established
+     * @exception IOException
+     * @exception ModuleException
+     */
+    private Response enableSSLTunneling(Socket[] sock, Request req, int timeout)
+		throws IOException, ModuleException
+    {
+	// copy User-Agent and Proxy-Auth headers from request
+
+	Vector hdrs = new Vector();
+	for (int idx=0; idx<req.getHeaders().length; idx++)
+	{
+	    String name = req.getHeaders()[idx].getName();
+	    if (name.equalsIgnoreCase("User-Agent")  ||
+		name.equalsIgnoreCase("Proxy-Authorization"))
+		    hdrs.addElement(req.getHeaders()[idx]);
+	}
+
+
+	// create initial CONNECT subrequest
+
+	NVPair[] h = new NVPair[hdrs.size()];
+	hdrs.copyInto(h);
+	Request connect = new Request(this, "CONNECT", Host+":"+Port, h,
+				      null, null, req.allowUI());
+	connect.internal_subrequest = true;
+
+	ByteArrayOutputStream hdr_buf = new ByteArrayOutputStream(600);
+	HTTPResponse r = new HTTPResponse(gen_mod_insts(), timeout, connect);
+
+
+	// send and handle CONNECT request until successful or tired
+
+	Response resp = null;
+
+	while (true)
+	{
+	    handleRequest(connect, r, resp, true);
+
+	    hdr_buf.reset();
+	    assembleHeaders(connect, hdr_buf);
+
+	    Log.write(Log.CONN, "Conn:  Sending SSL-Tunneling Subrequest: ",
+		      hdr_buf);
+
+
+	    // send CONNECT
+
+	    hdr_buf.writeTo(sock[0].getOutputStream());
+
+
+	    // return if successful
+
+	    resp = new Response(connect, sock[0].getInputStream());
+	    if (resp.getStatusCode() == 200)  return null;
+
+
+	    // failed!
+
+	    // make life easy: read data and close socket
+
+	    try
+		{ resp.getData(); }
+	    catch (IOException ioe)
+		{ }
+	    try
+		{ sock[0].close(); }
+	    catch (IOException ioe)
+		{ }
+
+
+	    // handle response
+
+	    r.set(connect, resp);
+	    if (!r.handleResponse())  return resp;
+
+	    sock[0] = getSocket(timeout);
+	}
+    }
+
+
+    /**
+     * This writes out the headers on the <var>hdr_buf</var>. It takes
+     * special precautions for the following headers:
+     * <DL>
+     * <DT>Content-type<DI>This is only written if the request has an entity.
+     *                     If the request has an entity and no content-type
+     *                     header was given for the request it defaults to
+     *                     "application/octet-stream"
+     * <DT>Content-length<DI>This header is generated if the request has an
+     *                     entity and the entity isn't being sent with the
+     *                     Transfer-Encoding "chunked".
+     * <DT>User-Agent  <DI>If not present it will be generated with the current
+     *                     HTTPClient version strings. Otherwise the version
+     *                     string is appended to the given User-Agent string.
+     * <DT>Connection  <DI>This header is only written if no proxy is used.
+     *                     If no connection header is specified and the server
+     *                     is not known to understand HTTP/1.1 or later then
+     *                     a "Connection: keep-alive" header is generated.
+     * <DT>Proxy-Connection<DI>This header is only written if a proxy is used.
+     *                     If no connection header is specified and the proxy
+     *                     is not known to understand HTTP/1.1 or later then
+     *                     a "Proxy-Connection: keep-alive" header is generated.
+     * <DT>Keep-Alive  <DI>This header is only written if the Connection or
+     *                     Proxy-Connection header contains the Keep-Alive
+     *                     token.
+     * <DT>Expect      <DI>If there is no entity and this header contains the
+     *                     "100-continue" token then this token is removed.
+     *                     before writing the header.
+     * <DT>TE          <DI>If this header does not exist, it is created; else
+     *                     if the "trailers" token is not specified this token
+     *                     is added; else the header is not touched.
+     * </DL>
+     *
+     * Furthermore, it escapes various characters in request-URI.
+     *
+     * @param req     the Request
+     * @param hdr_buf the buffer onto which to write the headers
+     * @return        an array of headers; the first element contains the
+     *                the value of the Connection or Proxy-Connectin header,
+     *                the second element the value of the Expect header.
+     * @exception IOException if writing on <var>hdr_buf</var> generates an
+     *                        an IOException, or if an error occurs during
+     *                        parsing of a header
+     */
+    private String[] assembleHeaders(Request req,
+				     ByteArrayOutputStream hdr_buf)
+		throws IOException
+    {
+	DataOutputStream dataout  = new DataOutputStream(hdr_buf);
+	String[]         con_hdrs = { "", "" };
+	NVPair[]         hdrs     = req.getHeaders();
+
+
+
+	// remember various headers
+
+	int ho_idx = -1,
+	    ct_idx = -1,
+	    ua_idx = -1,
+	    co_idx = -1,
+	    pc_idx = -1,
+	    ka_idx = -1,
+	    ex_idx = -1,
+	    te_idx = -1,
+	    tc_idx = -1,
+	    ug_idx = -1;
+	for (int idx=0; idx<hdrs.length; idx++)
+	{
+	    String name = hdrs[idx].getName().trim().toLowerCase();
+	    if (name.equals("host"))                   ho_idx = idx;
+	    else if (name.equals("content-type"))      ct_idx = idx;
+	    else if (name.equals("user-agent"))        ua_idx = idx;
+	    else if (name.equals("connection"))        co_idx = idx;
+	    else if (name.equals("proxy-connection"))  pc_idx = idx;
+	    else if (name.equals("keep-alive"))        ka_idx = idx;
+	    else if (name.equals("expect"))            ex_idx = idx;
+	    else if (name.equals("te"))                te_idx = idx;
+	    else if (name.equals("transfer-encoding")) tc_idx = idx;
+	    else if (name.equals("upgrade"))           ug_idx = idx;
+	}
+
+
+	// Generate request line and Host header
+
+	String file = Util.escapeUnsafeChars(req.getRequestURI());
+	if (Proxy_Host != null  &&  Protocol != HTTPS  &&  !file.equals("*"))
+	    dataout.writeBytes(req.getMethod() + " http://" + Host + ":" + Port+
+			       file + " " + RequestProtocolVersion + "\r\n");
+	else
+	    dataout.writeBytes(req.getMethod() + " " + file + " " +
+			       RequestProtocolVersion + "\r\n");
+
+	String h_hdr = (ho_idx >= 0) ? hdrs[ho_idx].getValue().trim() : Host;
+	if (Port != URI.defaultPort(getProtocol()))
+	    dataout.writeBytes("Host: " + h_hdr + ":" + Port + "\r\n");
+	else    // Netscape-Enterprise has some bugs...
+	    dataout.writeBytes("Host: " + h_hdr + "\r\n");
+
+
+	/*
+	 * What follows is the setup for persistent connections. We default
+	 * to doing persistent connections for both HTTP/1.0 and HTTP/1.1,
+	 * unless we're using a proxy server and HTTP/1.0 in which case we
+	 * must make sure we don't do persistence (because of the problem of
+	 * 1.0 proxies blindly passing the Connection header on).
+	 *
+	 * Note: there is a "Proxy-Connection" header for use with proxies.
+	 * This however is only understood by Netscape and Netapp caches.
+	 * Furthermore, it suffers from the same problem as the Connection
+	 * header in HTTP/1.0 except that at least two proxies must be
+	 * involved. But I've taken the risk now and decided to send the
+	 * Proxy-Connection header. If I get complaints I'll remove it again.
+	 *
+	 * In any case, with this header we can now modify the above to send
+	 * the Proxy-Connection header whenever we wouldn't send the normal
+	 * Connection header.
+	 */
+
+	String co_hdr = null;
+	if (!(ServProtVersKnown  &&  ServerProtocolVersion >= HTTP_1_1  &&
+	      co_idx == -1))
+	{
+	    if (co_idx == -1)
+	    {			// no connection header given by user
+		co_hdr = "Keep-Alive";
+		con_hdrs[0] = "Keep-Alive";
+	    }
+	    else
+	    {
+		con_hdrs[0] = hdrs[co_idx].getValue().trim();
+		co_hdr = con_hdrs[0];
+	    }
+
+	    try
+	    {
+		if (ka_idx != -1  &&
+		    Util.hasToken(con_hdrs[0], "keep-alive"))
+		    dataout.writeBytes("Keep-Alive: " +
+					hdrs[ka_idx].getValue().trim() + "\r\n");
+	    }
+	    catch (ParseException pe)
+	    {
+		throw new IOException(pe.toString());
+	    }
+	}
+
+	if ((Proxy_Host != null  &&  Protocol != HTTPS)  &&
+	    !(ServProtVersKnown  &&  ServerProtocolVersion >= HTTP_1_1))
+	{
+	    if (co_hdr != null)
+	    {
+		dataout.writeBytes("Proxy-Connection: ");
+		dataout.writeBytes(co_hdr);
+		dataout.writeBytes("\r\n");
+		co_hdr = null;
+	    }
+	}
+
+	if (co_hdr != null)
+	{
+	    try
+	    {
+		if (!Util.hasToken(co_hdr, "TE"))
+		    co_hdr += ", TE";
+	    }
+	    catch (ParseException pe)
+		{ throw new IOException(pe.toString()); }
+	}
+	else
+	    co_hdr = "TE";
+
+	if (ug_idx != -1)
+	    co_hdr += ", Upgrade";
+
+	if (co_hdr != null)
+	{
+	    dataout.writeBytes("Connection: ");
+	    dataout.writeBytes(co_hdr);
+	    dataout.writeBytes("\r\n");
+	}
+
+
+
+	// handle TE header
+
+	if (te_idx != -1)
+	{
+	    dataout.writeBytes("TE: ");
+	    Vector pte;
+	    try
+		{ pte = Util.parseHeader(hdrs[te_idx].getValue()); }
+	    catch (ParseException pe)
+		{ throw new IOException(pe.toString()); }
+
+	    if (!pte.contains(new HttpHeaderElement("trailers")))
+		dataout.writeBytes("trailers, ");
+
+	    dataout.writeBytes(hdrs[te_idx].getValue().trim() + "\r\n");
+	}
+	else
+	    dataout.writeBytes("TE: trailers\r\n");
+
+
+	// User-Agent
+
+	if (ua_idx != -1)
+	    dataout.writeBytes("User-Agent: " + hdrs[ua_idx].getValue().trim() + " "
+			       + version + "\r\n");
+	else
+	    dataout.writeBytes("User-Agent: " + version + "\r\n");
+
+
+	// Write out any headers left
+
+	for (int idx=0; idx<hdrs.length; idx++)
+	{
+	    if (idx != ct_idx  &&  idx != ua_idx  &&  idx != co_idx  &&
+		idx != pc_idx  &&  idx != ka_idx  &&  idx != ex_idx  &&
+		idx != te_idx  &&  idx != ho_idx)
+		dataout.writeBytes(hdrs[idx].getName().trim() + ": " +
+				   hdrs[idx].getValue().trim() + "\r\n");
+	}
+
+
+	// Handle Content-type, Content-length and Expect headers
+
+	if (req.getData() != null  ||  req.getStream() != null)
+	{
+	    dataout.writeBytes("Content-type: ");
+	    if (ct_idx != -1)
+		dataout.writeBytes(hdrs[ct_idx].getValue().trim());
+	    else
+		dataout.writeBytes("application/octet-stream");
+	    dataout.writeBytes("\r\n");
+
+	    if (req.getData() != null)
+		dataout.writeBytes("Content-length: " +req.getData().length +
+				   "\r\n");
+	    else if (req.getStream().getLength() != -1  &&  tc_idx == -1)
+		dataout.writeBytes("Content-length: " +
+				   req.getStream().getLength() + "\r\n");
+
+	    if (ex_idx != -1)
+	    {
+		con_hdrs[1] = hdrs[ex_idx].getValue().trim();
+		dataout.writeBytes("Expect: " + con_hdrs[1] + "\r\n");
+	    }
+	}
+	else if (ex_idx != -1)
+	{
+	    Vector expect_tokens;
+	    try
+		{ expect_tokens = Util.parseHeader(hdrs[ex_idx].getValue()); }
+	    catch (ParseException pe)
+		{ throw new IOException(pe.toString()); }
+
+
+	    // remove any 100-continue tokens
+
+	    HttpHeaderElement cont = new HttpHeaderElement("100-continue");
+	    while (expect_tokens.removeElement(cont)) ;
+
+
+	    // write out header if any tokens left
+
+	    if (!expect_tokens.isEmpty())
+	    {
+		con_hdrs[1] = Util.assembleHeader(expect_tokens);
+		dataout.writeBytes("Expect: " + con_hdrs[1] + "\r\n");
+	    }
+	}
+
+	dataout.writeBytes("\r\n");		// end of header
+
+	return con_hdrs;
+    }
+
+
+    /**
+     * The very first request is special in that we use it to figure out the
+     * protocol version the server (or proxy) is compliant with.
+     *
+     * @return true if all went fine, false if the request needs to be resent
+     * @exception IOException if any exception is thrown by the response
+     */
+    boolean handleFirstRequest(Request req, Response resp)  throws IOException
+    {
+	// read response headers to get protocol version used by
+	// the server.
+
+	ServerProtocolVersion = String2ProtVers(resp.getVersion());
+	ServProtVersKnown = true;
+
+	/* We need to treat connections through proxies specially, because
+	 * many HTTP/1.0 proxies do not downgrade an HTTP/1.1 response
+	 * version to HTTP/1.0 (i.e. when we are talking to an HTTP/1.1
+	 * server through an HTTP/1.0 proxy we are mislead to thinking we're
+	 * talking to an HTTP/1.1 proxy). We use the absence of the Via
+	 * header to detect whether we're talking to an HTTP/1.0 proxy,
+	 * unless the status code indicates an error from the proxy
+	 * itself. However, this only works when the chain contains
+	 * only HTTP/1.0 proxies; if you have <client - 1.0 proxy - 1.1
+	 * proxy - server> then this will fail too. Unfortunately there
+	 * seems to be no way to reliably detect broken HTTP/1.0
+	 * proxies...
+	 */
+	int sts = resp.getStatusCode();
+	if ((Proxy_Host != null  &&  Protocol != HTTPS)  &&
+	    resp.getHeader("Via") == null  &&
+	    sts != 407  &&  sts != 502  &&  sts != 504)
+	    ServerProtocolVersion = HTTP_1_0;
+
+	Log.write(Log.CONN, "Conn:  Protocol Version established: " +
+			    ProtVers2String(ServerProtocolVersion));
+
+
+	// some (buggy) servers return an error status if they get a
+	// version they don't comprehend
+
+	if (ServerProtocolVersion == HTTP_1_0  &&
+	    (resp.getStatusCode() == 400  ||  resp.getStatusCode() == 500))
+	{
+	    if (input_demux != null)
+		input_demux.markForClose(resp);
+	    input_demux = null;
+	    RequestProtocolVersion = "HTTP/1.0";
+	    return false;
+	}
+
+	return true;
+    }
+
+
+    private void determineKeepAlive(Response resp)  throws IOException
+    {
+	// try and determine if this server does keep-alives
+
+	String con;
+
+	try
+	{
+	    if (ServerProtocolVersion >= HTTP_1_1  ||
+		(
+		 (
+		  ((Proxy_Host == null  ||  Protocol == HTTPS)  &&
+		   (con = resp.getHeader("Connection")) != null)
+		  ||
+		  ((Proxy_Host != null  &&  Protocol != HTTPS)  &&
+		   (con = resp.getHeader("Proxy-Connection")) != null)
+		 )  &&
+		 Util.hasToken(con, "keep-alive")
+		)
+	       )
+	    {
+		doesKeepAlive    = true;
+		keepAliveUnknown = false;
+
+		Log.write(Log.CONN, "Conn:  Keep-Alive enabled");
+	    }
+	    else if (resp.getStatusCode() < 400)
+		keepAliveUnknown = false;
+
+
+	    // get maximum number of requests
+
+	    if (doesKeepAlive  &&  ServerProtocolVersion == HTTP_1_0  &&
+		(con = resp.getHeader("Keep-Alive")) != null)
+	    {
+		HttpHeaderElement max =
+				Util.getElement(Util.parseHeader(con), "max");
+		if (max != null  &&  max.getValue() != null)
+		{
+		    keepAliveReqMax  = Integer.parseInt(max.getValue());
+		    keepAliveReqLeft = keepAliveReqMax;
+
+		    Log.write(Log.CONN, "Conn:  Max Keep-Alive requests: " +
+					keepAliveReqMax);
+		}
+	    }
+	}
+	catch (ParseException pe) { }
+	catch (NumberFormatException nfe) { }
+	catch (ClassCastException cce) { }
+    }
+
+
+    synchronized void outputFinished()
+    {
+	output_finished = true;
+	notify();
+    }
+
+
+    synchronized void closeDemux(IOException ioe, boolean was_reset)
+    {
+	if (input_demux != null)  input_demux.close(ioe, was_reset);
+
+	early_stall = null;
+	late_stall  = null;
+	prev_resp   = null;
+    }
+
+
+    final static String ProtVers2String(int prot_vers)
+    {
+	return "HTTP/" + (prot_vers >>> 16) + "." + (prot_vers & 0xFFFF);
+    }
+
+    final static int String2ProtVers(String prot_vers)
+    {
+	String vers = prot_vers.substring(5);
+	int    dot  = vers.indexOf('.');
+	return  Integer.parseInt(vers.substring(0, dot)) << 16 |
+		Integer.parseInt(vers.substring(dot+1));
+    }
+
+
+    /**
+     * Generates a string of the form protocol://host.domain:port .
+     *
+     * @return the string
+     */
+    public String toString()
+    {
+	return getProtocol() + "://" + getHost() +
+	    (getPort() != URI.defaultPort(getProtocol()) ? ":" + getPort() : "");
+    }
+
+
+    private class EstablishConnection extends Thread
+    {
+	String      actual_host;
+	int         actual_port;
+	IOException exception;
+	Socket      sock;
+	SocksClient Socks_client;
+	boolean     close;
+
+
+	EstablishConnection(String host, int port, SocksClient socks)
+	{
+	    super("EstablishConnection (" + host + ":" + port + ")");
+	    try { setDaemon(true); }
+	    catch (SecurityException se) { }        // Oh well...
+
+	    actual_host  = host;
+	    actual_port  = port;
+	    Socks_client = socks;
+
+	    exception = null;
+	    sock      = null;
+	    close     = false;
+	}
+
+
+	public void run()
+	{
+	    try
+	    {
+		if (Socks_client != null)
+		    sock = Socks_client.getSocket(actual_host, actual_port);
+		else
+		{
+		    // try all A records
+		    InetAddress[] addr_list = InetAddress.getAllByName(actual_host);
+		    for (int idx=0; idx<addr_list.length; idx++)
+		    {
+			try
+			{
+			    if (LocalAddr == null)
+				sock = new Socket(addr_list[idx], actual_port);
+			    else
+				sock = new Socket(addr_list[idx], actual_port,
+						  LocalAddr, LocalPort);
+			    break;		// success
+			}
+			catch (SocketException se)
+			{
+			    if (idx == addr_list.length-1  ||  close)
+				throw se;	// we tried them all
+			}
+		    }
+		}
+	    }
+	    catch (IOException ioe)
+	    {
+		exception = ioe;
+	    }
+
+	    if (close  &&  sock != null)
+	    {
+		try
+		    { sock.close(); }
+		catch (IOException ioe)
+		    { }
+		sock = null;
+	    }
+	}
+
+
+	IOException getException()
+	{
+	    return exception;
+	}
+
+
+	Socket getSocket()
+	{
+	    return sock;
+	}
+
+
+	void forget()
+	{
+	    close = true;
+	}
+    }
+
+
+    /**
+     * M$ has yet another bug in their WinSock: if you try to write too much
+     * data at once it'll hang itself. This filter therefore splits big writes
+     * up into multiple writes of at most 20K.
+     */
+    private class MSLargeWritesBugStream extends FilterOutputStream
+    {
+	private final int CHUNK_SIZE = 20000;
+
+	MSLargeWritesBugStream(OutputStream os)
+	{
+	    super(os);
+	}
+
+	public void write(byte[] b, int off, int len)  throws IOException
+	{
+	    while (len > CHUNK_SIZE)
+	    {
+		out.write(b, off, CHUNK_SIZE);
+		off += CHUNK_SIZE;
+		len -= CHUNK_SIZE;
+	    }
+	    out.write(b, off, len);
+	}
+    }
+}
diff --git a/HTTPClient/HTTPResponse.java b/HTTPClient/HTTPResponse.java
new file mode 100644
index 0000000..f77eafa
--- /dev/null
+++ b/HTTPClient/HTTPResponse.java
@@ -0,0 +1,960 @@
+/*
+ * @(#)HTTPResponse.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+import java.net.URL;
+import java.util.Date;
+import java.util.Enumeration;
+
+
+/**
+ * This defines the http-response class returned by the requests. It's
+ * basically a wrapper around the Response class which first lets all
+ * the modules handle the response before finally giving the info to
+ * the user.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	0.3
+ */
+public class HTTPResponse implements HTTPClientModuleConstants
+{
+    /** the list of modules */
+    private HTTPClientModule[]  modules;
+
+    /** the timeout for reads */
+    private int          timeout;
+
+    /** the request */
+    private Request      request = null;
+
+    /** the current response */
+            Response     response = null;
+
+    /** the HttpOutputStream to synchronize on */
+    private HttpOutputStream out_stream = null;
+
+    /** our input stream from the stream demux */
+    private InputStream  inp_stream;
+
+    /** the status code returned. */
+    private int          StatusCode;
+
+    /** the reason line associated with the status code. */
+    private String       ReasonLine;
+
+    /** the HTTP version of the response. */
+    private String       Version;
+
+    /** the original URI used. */
+    private URI          OriginalURI = null;
+
+    /** the final URI of the document. */
+    private URI          EffectiveURI = null;
+
+    /** any headers which were received and do not fit in the above list. */
+    private CIHashtable  Headers = null;
+
+    /** any trailers which were received and do not fit in the above list. */
+    private CIHashtable  Trailers = null;
+
+    /** the ContentLength of the data. */
+    private int          ContentLength = -1;
+
+    /** the data (body) returned. */
+    private byte[]       Data = null;
+
+    /** signals if we have got and parsed the headers yet? */
+    private boolean      initialized = false;
+
+    /** signals if we have got the trailers yet? */
+    private boolean      got_trailers = false;
+
+    /** marks this response as aborted (stop() in HTTPConnection) */
+    private boolean      aborted = false;
+
+    /** should the request be retried by the application? */
+    private boolean      retry = false;
+
+    /** the method used in the request */
+    private String       method = null;
+
+
+    // Constructors
+
+    /**
+     * Creates a new HTTPResponse.
+     *
+     * @param modules the list of modules handling this response
+     * @param timeout the timeout to be used on stream read()'s
+     */
+    HTTPResponse(HTTPClientModule[] modules, int timeout, Request orig)
+    {
+	this.modules = modules;
+	this.timeout = timeout;
+	try
+	{
+	    int qp = orig.getRequestURI().indexOf('?');
+	    this.OriginalURI = new URI(orig.getConnection().getProtocol(),
+				       null,
+				       orig.getConnection().getHost(),
+				       orig.getConnection().getPort(),
+				       qp < 0 ? orig.getRequestURI() :
+					 orig.getRequestURI().substring(0, qp),
+				       qp < 0 ? null :
+					 orig.getRequestURI().substring(qp+1),
+				       null);
+	}
+	catch (ParseException pe)
+	    { }
+	this.method = orig.getMethod();
+    }
+
+
+    /**
+     * @param req the request
+     * @param resp the response
+     */
+    void set(Request req, Response resp)
+    {
+	this.request   = req;
+	this.response  = resp;
+	resp.http_resp = this;
+	resp.timeout   = timeout;
+	this.aborted   = resp.final_resp;
+    }
+
+
+    /**
+     * @param req the request
+     * @param resp the response
+     */
+    void set(Request req, HttpOutputStream out_stream)
+    {
+	this.request    = req;
+	this.out_stream = out_stream;
+    }
+
+
+    // Methods
+
+    /**
+     * Give the status code for this request. These are grouped as follows:
+     * <UL>
+     *   <LI> 1xx - Informational (new in HTTP/1.1)
+     *   <LI> 2xx - Success
+     *   <LI> 3xx - Redirection
+     *   <LI> 4xx - Client Error
+     *   <LI> 5xx - Server Error
+     * </UL>
+     *
+     * @exception IOException if any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public final int getStatusCode()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	return StatusCode;
+    }
+
+    /**
+     * Give the reason line associated with the status code.
+     *
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public final String getReasonLine()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	return ReasonLine;
+    }
+
+    /**
+     * Get the HTTP version used for the response.
+     *
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public final String getVersion()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	return Version;
+    }
+
+    /**
+     * Get the name and type of server.
+     *
+     * @deprecated This method is a remnant of V0.1; use
+     *             <code>getHeader("Server")</code> instead.
+     * @see #getHeader(java.lang.String)
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public final String getServer()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	return getHeader("Server");
+    }
+
+
+    /**
+     * Get the original URI used in the request.
+     *
+     * @return the URI used in primary request
+     */
+    public final URI getOriginalURI()
+    {
+	return OriginalURI;
+    }
+
+
+    /**
+     * Get the final URL of the document. This is set if the original
+     * request was deferred via the "moved" (301, 302, or 303) return
+     * status.
+     *
+     * @return the effective URL, or null if no redirection occured
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     * @deprecated use getEffectiveURI() instead
+     * @see #getEffectiveURI
+     */
+    public final URL getEffectiveURL()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	if (EffectiveURI != null)
+	    return EffectiveURI.toURL();
+	return null;
+    }
+
+
+    /**
+     * Get the final URI of the document. If the request was redirected
+     * via the "moved" (301, 302, 303, or 307) return status this returns
+     * the URI used in the last redirection; otherwise it returns the
+     * original URI.
+     *
+     * @return the effective URI
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public final URI getEffectiveURI()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	if (EffectiveURI != null)
+	    return EffectiveURI;
+	return OriginalURI;
+    }
+
+
+    /**
+     * Retrieves the value for a given header.
+     *
+     * @param  hdr the header name.
+     * @return the value for the header, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public String getHeader(String hdr)  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	return (String) Headers.get(hdr.trim());
+    }
+
+    /**
+     * Retrieves the value for a given header. The value is parsed as an
+     * int.
+     *
+     * @param  hdr the header name.
+     * @return the value for the header if the header exists
+     * @exception NumberFormatException if the header's value is not a number
+     *                                  or if the header does not exist.
+     * @exception IOException if any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public int getHeaderAsInt(String hdr)
+		throws IOException, ModuleException, NumberFormatException
+    {
+	String val = getHeader(hdr);
+	if (val == null)
+	    throw new NumberFormatException("null");
+	return Integer.parseInt(val);
+    }
+
+    /**
+     * Retrieves the value for a given header. The value is parsed as a
+     * date; if this fails it is parsed as a long representing the number
+     * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
+     * exception is thrown.
+     * <br>Note: When sending dates use Util.httpDate().
+     *
+     * @param  hdr the header name.
+     * @return the value for the header, or null if non-existent.
+     * @exception IllegalArgumentException if the header's value is neither a
+     *            legal date nor a number.
+     * @exception IOException if any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public Date getHeaderAsDate(String hdr)
+		throws IOException, IllegalArgumentException, ModuleException
+    {
+	String raw_date = getHeader(hdr);
+	if (raw_date == null) return null;
+
+	// asctime() format is missing an explicit GMT specifier
+	if (raw_date.toUpperCase().indexOf("GMT") == -1  &&
+	    raw_date.indexOf(' ') > 0)
+	    raw_date += " GMT";
+
+	Date   date;
+
+	try
+	    { date = Util.parseHttpDate(raw_date); }
+	catch (IllegalArgumentException iae)
+	{
+	    // some servers erroneously send a number, so let's try that
+	    long time;
+	    try
+		{ time = Long.parseLong(raw_date); }
+	    catch (NumberFormatException nfe)
+		{ throw iae; }	// give up
+	    if (time < 0)  time = 0;
+	    date = new Date(time * 1000L);
+	}
+
+	return date;
+    }
+
+    /**
+     * Returns an enumeration of all the headers available via getHeader().
+     *
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public Enumeration listHeaders()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+	return Headers.keys();
+    }
+
+
+    /**
+     * Retrieves the value for a given trailer. This should not be invoked
+     * until all response data has been read. If invoked before it will
+     * call <code>getData()</code> to force the data to be read.
+     *
+     * @param  trailer the trailer name.
+     * @return the value for the trailer, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     * @see #getData()
+     */
+    public String getTrailer(String trailer) throws IOException, ModuleException
+    {
+	if (!got_trailers)  getTrailers();
+	return (String) Trailers.get(trailer.trim());
+    }
+
+    /**
+     * Retrieves the value for a given tailer. The value is parsed as an
+     * int.
+     *
+     * @param  trailer the tailer name.
+     * @return the value for the trailer if the trailer exists
+     * @exception NumberFormatException if the trailer's value is not a number
+     *                                  or if the trailer does not exist.
+     * @exception IOException if any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public int getTrailerAsInt(String trailer)
+		throws IOException, ModuleException, NumberFormatException
+    {
+	String val = getTrailer(trailer);
+	if (val == null)
+	    throw new NumberFormatException("null");
+	return Integer.parseInt(val);
+    }
+
+    /**
+     * Retrieves the value for a given trailer. The value is parsed as a
+     * date; if this fails it is parsed as a long representing the number
+     * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
+     * IllegalArgumentException is thrown.
+     * <br>Note: When sending dates use Util.httpDate().
+     *
+     * @param  trailer the trailer name.
+     * @return the value for the trailer, or null if non-existent.
+     * @exception IllegalArgumentException if the trailer's value is neither a
+     *            legal date nor a number.
+     * @exception IOException if any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public Date getTrailerAsDate(String trailer)
+		throws IOException, IllegalArgumentException, ModuleException
+    {
+	String raw_date = getTrailer(trailer);
+	if (raw_date == null) return null;
+
+	// asctime() format is missing an explicit GMT specifier
+	if (raw_date.toUpperCase().indexOf("GMT") == -1  &&
+	    raw_date.indexOf(' ') > 0)
+	    raw_date += " GMT";
+
+	Date   date;
+
+	try
+	    { date = Util.parseHttpDate(raw_date); }
+	catch (IllegalArgumentException iae)
+	{
+	    // some servers erroneously send a number, so let's try that
+	    long time;
+	    try
+		{ time = Long.parseLong(raw_date); }
+	    catch (NumberFormatException nfe)
+		{ throw iae; }	// give up
+	    if (time < 0)  time = 0;
+	    date = new Date(time * 1000L);
+	}
+
+	return date;
+    }
+
+    /**
+     * Returns an enumeration of all the trailers available via getTrailer().
+     *
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public Enumeration listTrailers()  throws IOException, ModuleException
+    {
+	if (!got_trailers)  getTrailers();
+	return Trailers.keys();
+    }
+
+
+    /**
+     * Reads all the response data into a byte array. Note that this method
+     * won't return until <em>all</em> the data has been received (so for
+     * instance don't invoke this method if the server is doing a server
+     * push). If <code>getInputStream()</code> had been previously invoked
+     * then this method only returns any unread data remaining on the stream
+     * and then closes it.
+     *
+     * <P>Note to the unwary: code like
+     *<PRE>
+     *     System.out.println("The data: " + resp.getData())
+     *</PRE>
+     * will probably not do what you want - use
+     *<PRE>
+     *     System.out.println("The data: " + resp.getText())
+     *</PRE>
+     * instead.
+     *
+     * @see #getInputStream()
+     * @return an array containing the data (body) returned. If no data
+     *         was returned then it's set to a zero-length array.
+     * @exception IOException If any io exception occured while reading
+     *			      the data
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public synchronized byte[] getData()  throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+
+	if (Data == null)
+	{
+	    try
+		{ readResponseData(inp_stream); }
+	    catch (InterruptedIOException ie)		// don't intercept
+		{ throw ie; }
+	    catch (IOException ioe)
+	    {
+		Log.write(Log.RESP, "HResp: (\"" + method + " " +
+				    OriginalURI.getPathAndQuery() + "\")");
+		Log.write(Log.RESP, "       ", ioe);
+
+		try { inp_stream.close(); } catch (Exception e) { }
+		throw ioe;
+	    }
+
+	    inp_stream.close();
+	}
+
+	return Data;
+    }
+
+    /**
+     * Reads all the response data into a buffer and turns it into a string
+     * using the appropriate character converter. Since this uses {@link
+     * #getData() getData()}, the caveats of that method apply here as well.
+     *
+     * @see #getData()
+     * @return the body as a String. If no data was returned then an empty
+     *         string is returned.
+     * @exception IOException If any io exception occured while reading
+     *			      the data, or if the content is not text
+     * @exception ModuleException if any module encounters an exception.
+     * @exception ParseException if an error occured trying to parse the
+     *                           content-type header field
+     */
+    public synchronized String getText()
+	throws IOException, ModuleException, ParseException
+    {
+	String ct = getHeader("Content-Type");
+	if (ct == null  ||  !ct.toLowerCase().startsWith("text/"))
+	    throw new IOException("Content-Type `" + ct + "' is not a text type");
+
+	String charset = Util.getParameter("charset", ct);
+	if (charset == null)
+	    charset = "ISO-8859-1";
+
+	return new String(getData(), charset);
+    }
+
+    /**
+     * Gets an input stream from which the returned data can be read. Note
+     * that if <code>getData()</code> had been previously invoked it will
+     * actually return a ByteArrayInputStream created from that data.
+     *
+     * @see #getData()
+     * @return the InputStream.
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public synchronized InputStream getInputStream()
+	    throws IOException, ModuleException
+    {
+	if (!initialized)  handleResponse();
+
+	if (Data == null)
+	    return inp_stream;
+	else
+	{
+	    getData();		// ensure complete data is read
+	    return new ByteArrayInputStream(Data);
+	}
+    }
+
+
+    /**
+     * Should the request be retried by the application? If the application
+     * used an <var>HttpOutputStream</var> in the request then various
+     * modules (such as the redirection and authorization modules) are not
+     * able to resend the request themselves. Instead, it becomes the
+     * application's responsibility. The application can check this flag, and
+     * if it's set, resend the exact same request. The modules such as the
+     * RedirectionModule or AuthorizationModule will then recognize the resend
+     * and fix up or redirect the request as required (i.e. they defer their
+     * normal action until the resend).
+     *
+     * <P>If the application resends the request then it <strong>must</strong>
+     * use the same <var>HttpOutputStream</var> instance. This is because the
+     * modules use this to recognize the retried request and to perform the
+     * necessary work on the request before it's sent.
+     *
+     * <P>Here is a skeleton example of usage:
+     * <PRE>
+     *     OutputStream out = new HttpOutputStream(1234);
+     *     do
+     *     {
+     *         rsp = con.Post("/cgi-bin/my_cgi", out);
+     *         out.write(...);
+     *         out.close();
+     *     } while (rsp.retryRequest());
+     *
+     *     if (rsp.getStatusCode() >= 300)
+     *         ...
+     * </PRE>
+     *
+     * <P>Note that for this to ever return true, the java system property
+     * <var>HTTPClient.deferStreamed</var> must be set to true at the beginning
+     * of the application (before the HTTPConnection class is loaded). This
+     * prevents unwary applications from causing inadvertent memory leaks. If
+     * an application does set this, then it <em>must</em> resend any request
+     * whose response returns true here in order to prevent memory leaks (a
+     * switch to JDK 1.2 will allow us to use weak references and eliminate
+     * this problem).
+     *
+     * @return true if the request should be retried.
+     * @exception IOException If any exception occurs on the socket.
+     * @exception ModuleException if any module encounters an exception.
+     */
+    public boolean retryRequest()  throws IOException, ModuleException
+    {
+	if (!initialized)
+	{
+	    try
+		{ handleResponse(); }
+	    catch (RetryException re)
+		{ this.retry = response.retry; }
+	}
+	return retry;
+    }
+
+
+    /**
+     * produces a full list of headers and their values, one per line.
+     *
+     * @return a string containing the headers
+     */
+    public String toString()
+    {
+	if (!initialized)
+	{
+	    try
+		{ handleResponse(); }
+	    catch (Exception e)
+	    {
+		if (!(e instanceof InterruptedIOException))
+		{
+		    Log.write(Log.RESP, "HResp: (\"" + method + " " +
+				        OriginalURI.getPathAndQuery() + "\")");
+		    Log.write(Log.RESP, "       ", e);
+		}
+		return "Failed to read headers: " + e;
+	    }
+	}
+
+	String nl = System.getProperty("line.separator", "\n");
+
+	StringBuffer str = new StringBuffer(Version);
+	str.append(' ');
+	str.append(StatusCode);
+	str.append(' ');
+	str.append(ReasonLine);
+	str.append(nl);
+
+	if (EffectiveURI != null)
+	{
+	    str.append("Effective-URI: ");
+	    str.append(EffectiveURI);
+	    str.append(nl);
+	}
+
+	Enumeration hdr_list = Headers.keys();
+	while (hdr_list.hasMoreElements())
+	{
+	    String hdr = (String) hdr_list.nextElement();
+	    str.append(hdr);
+	    str.append(": ");
+	    str.append(Headers.get(hdr));
+	    str.append(nl);
+	}
+
+	return str.toString();
+    }
+
+
+    // Helper Methods
+
+
+    HTTPClientModule[] getModules()
+    {
+	return modules;
+    }
+
+
+    /**
+     * Processes a Response. This is done by calling the response handler
+     * in each module. When all is done, the various fields of this instance
+     * are intialized from the last Response.
+     *
+     * @exception IOException if any handler throws an IOException.
+     * @exception ModuleException if any module encounters an exception.
+     * @return true if a new request was generated. This is used for internal
+     *         subrequests only
+     */
+    synchronized boolean handleResponse()  throws IOException, ModuleException
+    {
+	if (initialized)  return false;
+
+
+	/* first get the response if necessary */
+
+	if (out_stream != null)
+	{
+	    response           = out_stream.getResponse();
+	    response.http_resp = this;
+	    out_stream         = null;
+	}
+
+
+	/* go through modules and handle them */
+
+	doModules: while (true)
+	{
+
+	Phase1: for (int idx=0; idx<modules.length && !aborted; idx++)
+	{
+	    try
+		{ modules[idx].responsePhase1Handler(response, request); }
+	    catch (RetryException re)
+	    {
+		if (re.restart)
+		    continue doModules;
+		else
+		    throw re;
+	    }
+	}
+
+	Phase2: for (int idx=0; idx<modules.length && !aborted; idx++)
+	{
+            int sts = modules[idx].responsePhase2Handler(response, request);
+            switch (sts)
+            {
+                case RSP_CONTINUE:	// continue processing
+                    break;
+
+                case RSP_RESTART:	// restart response processing
+                    idx = -1;
+		    continue doModules;
+
+                case RSP_SHORTCIRC:	// stop processing and return
+                    break doModules;
+
+                case RSP_REQUEST:	// go to phase 1
+                case RSP_NEWCON_REQ:	// process the request using a new con
+		    response.getInputStream().close();
+		    if (handle_trailers) invokeTrailerHandlers(true);
+		    if (request.internal_subrequest)  return true;
+		    request.getConnection().
+				handleRequest(request, this, response, true);
+		    if (initialized)  break doModules;
+
+                    idx = -1;
+		    continue doModules;
+
+                case RSP_SEND:		// send the request immediately
+                case RSP_NEWCON_SND:	// send the request using a new con
+		    response.getInputStream().close();
+		    if (handle_trailers) invokeTrailerHandlers(true);
+		    if (request.internal_subrequest)  return true;
+		    request.getConnection().
+				handleRequest(request, this, response, false);
+                    idx = -1;
+		    continue doModules;
+
+                default:                // not valid
+                    throw new Error("HTTPClient Internal Error: invalid status"+
+                                    " " + sts + " returned by module " +
+                                    modules[idx].getClass().getName());
+	    }
+	}
+
+	Phase3: for (int idx=0; idx<modules.length && !aborted; idx++)
+	{
+            modules[idx].responsePhase3Handler(response, request);
+	}
+
+	break doModules;
+	}
+
+	/* force a read on the response in case none of the modules did */
+	response.getStatusCode();
+
+	/* all done, so copy data */
+	if (!request.internal_subrequest)
+	    init(response);
+
+	if (handle_trailers)
+	    invokeTrailerHandlers(false);
+
+	return false;
+    }
+
+
+    /**
+     * Copies the relevant fields from Response and marks this as initialized.
+     *
+     * @param resp the Response class to copy from
+     */
+    void init(Response resp)
+    {
+	if (initialized)  return;
+
+	this.StatusCode    = resp.StatusCode;
+	this.ReasonLine    = resp.ReasonLine;
+	this.Version       = resp.Version;
+	this.EffectiveURI  = resp.EffectiveURI;
+	this.ContentLength = resp.ContentLength;
+	this.Headers       = resp.Headers;
+	this.inp_stream    = resp.inp_stream;
+	this.Data          = resp.Data;
+	this.retry         = resp.retry;
+	initialized        = true;
+    }
+
+
+    private boolean handle_trailers  = false;
+    private boolean trailers_handled = false;
+
+    /**
+     * This is invoked by the RespInputStream when it is close()'d. It
+     * just invokes the trailer handler in each module.
+     *
+     * @param force invoke the handlers even if not initialized yet?
+     * @exception IOException     if thrown by any module
+     * @exception ModuleException if thrown by any module
+     */
+    void invokeTrailerHandlers(boolean force)
+	    throws IOException, ModuleException
+    {
+	if (trailers_handled)  return;
+
+	if (!force  &&  !initialized)
+	{
+	    handle_trailers = true;
+	    return;
+	}
+
+	for (int idx=0; idx<modules.length && !aborted; idx++)
+	{
+            modules[idx].trailerHandler(response, request);
+	}
+
+	trailers_handled = true;
+    }
+
+
+    /**
+     * Mark this request as having been aborted. It's invoked by
+     * HTTPConnection.stop().
+     */
+    void markAborted()
+    {
+	aborted = true;
+    }
+
+
+    /**
+     * Gets any trailers from the response if we haven't already done so.
+     */
+    private synchronized void getTrailers()  throws IOException, ModuleException
+    {
+	if (got_trailers)  return;
+	if (!initialized)  handleResponse();
+
+	response.getTrailer("Any");
+	Trailers = response.Trailers;
+	got_trailers = true;
+
+	invokeTrailerHandlers(false);
+    }
+
+
+    /**
+     * Reads the response data received. Does not return until either
+     * Content-Length bytes have been read or EOF is reached.
+     *
+     * @inp       the input stream from which to read the data
+     * @exception IOException if any read on the input stream fails
+     */
+    private void readResponseData(InputStream inp)
+	    throws IOException, ModuleException
+    {
+	if (ContentLength == 0)
+	    return;
+
+	if (Data == null)
+	    Data = new byte[0];
+
+
+	// read response data
+
+	int off = Data.length;
+
+	try
+	{
+	    // check Content-length header in case CE-Module removed it
+	    if (getHeader("Content-Length") != null)
+	    {
+		int rcvd = 0;
+		Data = new byte[ContentLength];
+
+		do
+		{
+		    off  += rcvd;
+		    rcvd  = inp.read(Data, off, ContentLength-off);
+		} while (rcvd != -1  &&  off+rcvd < ContentLength);
+
+                /* Don't do this!
+		 * If we do, then getData() won't work after a getInputStream()
+		 * because we'll never get all the expected data. Instead, let
+		 * the underlying RespInputStream throw the EOF.
+		if (rcvd == -1)	// premature EOF
+		{
+		    throw new EOFException("Encountered premature EOF while " +
+					    "reading headers: received " + off +
+					    " bytes instead of the expected " +
+					    ContentLength + " bytes");
+		}
+		*/
+	    }
+	    else
+	    {
+		int inc  = 1000,
+		    rcvd = 0;
+
+		do
+		{
+		    off  += rcvd;
+		    Data  = Util.resizeArray(Data, off+inc);
+		} while ((rcvd = inp.read(Data, off, inc)) != -1);
+
+		Data = Util.resizeArray(Data, off);
+	    }
+	}
+	catch (IOException ioe)
+	{
+	    Data = Util.resizeArray(Data, off);
+	    throw ioe;
+	}
+	finally
+	{
+	    try
+		{ inp.close(); }
+	    catch (IOException ioe)
+		{ }
+	}
+    }
+
+
+    int getTimeout()
+    {
+	return timeout;
+    }
+}
diff --git a/HTTPClient/HashVerifier.java b/HTTPClient/HashVerifier.java
new file mode 100644
index 0000000..027ade0
--- /dev/null
+++ b/HTTPClient/HashVerifier.java
@@ -0,0 +1,55 @@
+/*
+ * @(#)HashVerifier.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+/**
+ * This interface defines a hash verifier.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+interface HashVerifier
+{
+    /**
+     * This method is invoked when a digest of a stream has been calculated.
+     * It must verify that the hash (or some function of it) is correct and
+     * throw an IOException if it is not.
+     *
+     * @param hash the calculated hash
+     * @param len  the number of bytes read from the stream
+     * @exception IOException if the verification fails.
+     */
+    public void verifyHash(byte[] hash, long len)  throws IOException;
+}
diff --git a/HTTPClient/HttpHeaderElement.java b/HTTPClient/HttpHeaderElement.java
new file mode 100644
index 0000000..f4ab1d8
--- /dev/null
+++ b/HTTPClient/HttpHeaderElement.java
@@ -0,0 +1,203 @@
+/*
+ * @(#)HttpHeaderElement.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This class holds a description of an http header element. It is used
+ * by {@link Util#parseHeader(java.lang.String) Util.parseHeader}.
+ *
+ * @see Util#parseHeader(java.lang.String)
+ * @see Util#getElement(java.util.Vector, java.lang.String)
+ * @see Util#assembleHeader(java.util.Vector)
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class HttpHeaderElement
+{
+    /** element name */
+    private String name;
+
+    /** element value */
+    private String value;
+
+    /** element parameters */
+    private NVPair[] parameters;
+
+
+    // Constructors
+
+    /**
+     * Construct an element with the given name. The value and parameters
+     * are set to null. This can be used when a dummy element is constructed
+     * for comparison or retrieval purposes.
+     *
+     * @param name   the name of the element
+     */
+    public HttpHeaderElement(String name)
+    {
+	this.name  = name;
+	this.value = null;
+	parameters = new NVPair[0];
+    }
+
+
+    /**
+     * @param name   the first token in the element
+     * @param value  the value part, or null
+     * @param params the parameters
+     */
+    public HttpHeaderElement(String name, String value, NVPair[] params)
+    {
+	this.name  = name;
+	this.value = value;
+	if (params != null)
+	{
+	    parameters = new NVPair[params.length];
+	    System.arraycopy(params, 0, parameters, 0, params.length);
+	}
+	else
+	    parameters = new NVPair[0];
+    }
+
+
+    // Methods
+
+    /**
+     * @return the name
+     */
+    public String getName()
+    {
+	return name;
+    }
+
+
+    /**
+     * @return the value
+     */
+    public String getValue()
+    {
+	return value;
+    }
+
+
+    /**
+     * @return the parameters
+     */
+    public NVPair[] getParams()
+    {
+	return parameters;
+    }
+
+
+    /**
+     * Two elements are equal if they have the same name. The comparison is
+     * <em>case-insensitive</em>.
+     *
+     * @param obj the object to compare with
+     * @return true if <var>obj</var> is an HttpHeaderElement with the same
+     *         name as this element.
+     */
+    public boolean equals(Object obj)
+    {
+	if ((obj != null) && (obj instanceof HttpHeaderElement))
+	{
+	    String other = ((HttpHeaderElement) obj).name;
+	    return name.equalsIgnoreCase(other);
+	}
+
+	return false;
+    }
+
+
+    /**
+     * @return a string containing the HttpHeaderElement formatted as it
+     *         would appear in a header
+     */
+    public String toString()
+    {
+	StringBuffer buf = new StringBuffer();
+	appendTo(buf);
+	return buf.toString();
+    }
+
+
+    /**
+     * Append this header element to the given buffer. This is basically a
+     * more efficient version of <code>toString()</code> for assembling
+     * multiple elements.
+     *
+     * @param buf the StringBuffer to append this header to
+     * @see #toString()
+     */
+    public void appendTo(StringBuffer buf)
+    {
+	buf.append(name);
+
+	if (value != null)
+	{
+	    if (Util.needsQuoting(value))
+	    {
+		buf.append("=\"");
+		buf.append(Util.quoteString(value, "\\\""));
+		buf.append('"');
+	    }
+	    else
+	    {
+		buf.append('=');
+		buf.append(value);
+	    }
+	}
+
+	for (int idx=0; idx<parameters.length; idx++)
+	{
+	    buf.append(";");
+	    buf.append(parameters[idx].getName());
+	    String pval = parameters[idx].getValue();
+	    if (pval != null)
+	    {
+		if (Util.needsQuoting(pval))
+		{
+		    buf.append("=\"");
+		    buf.append(Util.quoteString(pval, "\\\""));
+		    buf.append('"');
+		}
+		else
+		{
+		    buf.append('=');
+		    buf.append(pval);
+		}
+	    }
+	}
+    }
+}
diff --git a/HTTPClient/HttpOutputStream.java b/HTTPClient/HttpOutputStream.java
new file mode 100644
index 0000000..104537c
--- /dev/null
+++ b/HTTPClient/HttpOutputStream.java
@@ -0,0 +1,456 @@
+/*
+ * @(#)HttpOutputStream.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * This class provides an output stream for requests. The stream must first
+ * be associated with a request before it may be used; this is done by
+ * passing it to one of the request methods in HTTPConnection. Example:
+ * <PRE>
+ *    OutputStream out = new HttpOutputStream(12345);
+ *    rsp = con.Post("/cgi-bin/my_cgi", out);
+ *    out.write(...);
+ *    out.close();
+ *    if (rsp.getStatusCode() >= 300)
+ *        ...
+ * </PRE>
+ *
+ * <P>There are two constructors for this class, one taking a length parameter,
+ * and one without any parameters. If the stream is created with a length
+ * then the request will be sent with the corresponding Content-length header
+ * and anything written to the stream will be written on the socket immediately.
+ * This is the preferred way. If the stream is created without a length then
+ * one of two things will happen: if, at the time of the request, the server
+ * is known to understand HTTP/1.1 then each write() will send the data
+ * immediately using the chunked encoding. If, however, either the server
+ * version is unknown (because this is first request to that server) or the
+ * server only understands HTTP/1.0 then all data will be written to a buffer
+ * first, and only when the stream is closed will the request be sent.
+ *
+ * <P>Another reason that using the <var>HttpOutputStream(length)</var>
+ * constructor is recommended over the <var>HttpOutputStream()</var> one is
+ * that some HTTP/1.1 servers do not allow the chunked transfer encoding to
+ * be used when POSTing to a cgi script. This is because the way the cgi API
+ * is defined the cgi script expects a Content-length environment variable.
+ * If the data is sent using the chunked transfer encoding however, then the
+ * server would have to buffer all the data before invoking the cgi so that
+ * this variable could be set correctly. Not all servers are willing to do
+ * this.
+ *
+ * <P>If you cannot use the <var>HttpOutputStream(length)</var> constructor and
+ * are having problems sending requests (usually a 411 response) then you can
+ * try setting the system property <var>HTTPClient.dontChunkRequests</var> to
+ * <var>true</var> (this needs to be done either on the command line or
+ * somewhere in the code before the HTTPConnection is first accessed). This
+ * will prevent the client from using the chunked encoding in this case and
+ * will cause the HttpOutputStream to buffer all the data instead, sending it
+ * only when close() is invoked.
+ *
+ * <P>The behaviour of a request sent with an output stream may differ from
+ * that of a request sent with a data parameter. The reason for this is that
+ * the various modules cannot resend a request which used an output stream.
+ * Therefore such things as authorization and retrying of requests won't be
+ * done by the HTTPClient for such requests. But see {@link
+ * HTTPResponse#retryRequest() HTTPResponse.retryRequest} for a partial
+ * solution.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public class HttpOutputStream extends OutputStream
+{
+    /** null trailers */
+    private static final NVPair[] empty = new NVPair[0];
+
+    /** the length of the data to be sent */
+    private int length;
+
+    /** the length of the data received so far */
+    private int rcvd = 0;
+
+    /** the request this stream is associated with */
+    private Request req = null;
+
+    /** the response from sendRequest if we stalled the request */
+    private Response resp = null;
+
+    /** the socket output stream */
+    private OutputStream os = null;
+
+    /** the buffer to be used if needed */
+    private ByteArrayOutputStream bos = null;
+
+    /** the trailers to send if using chunked encoding. */
+    private NVPair[] trailers = empty;
+
+    /** the timeout to pass to SendRequest() */
+    private int con_to = 0;
+
+    /** just ignore all the data if told to do so */
+    private boolean ignore = false;
+
+
+    // Constructors
+
+    /**
+     * Creates an output stream of unspecified length. Note that it is
+     * <strong>highly</strong> recommended that this constructor be avoided
+     * where possible and <code>HttpOutputStream(int)</code> used instead.
+     *
+     * @see HttpOutputStream#HttpOutputStream(int)
+     */
+    public HttpOutputStream()
+    {
+	length = -1;
+    }
+
+
+    /**
+     * This creates an output stream which will take <var>length</var> bytes
+     * of data.
+     *
+     * @param length the number of bytes which will be sent over this stream
+     */
+    public HttpOutputStream(int length)
+    {
+	if (length < 0)
+	   throw new IllegalArgumentException("Length must be greater equal 0");
+	this.length = length;
+    }
+
+
+    // Methods
+
+    /**
+     * Associates this stream with a request and the actual output stream.
+     * No other methods in this class may be invoked until this method has
+     * been invoked by the HTTPConnection.
+     *
+     * @param req    the request this stream is to be associated with
+     * @param os     the underlying output stream to write our data to, or null
+     *               if we should write to a ByteArrayOutputStream instead.
+     * @param con_to connection timeout to use in sendRequest()
+     */
+    void goAhead(Request req, OutputStream os, int con_to)
+    {
+	this.req    = req;
+	this.os     = os;
+	this.con_to = con_to;
+
+	if (os == null)
+	    bos = new ByteArrayOutputStream();
+
+	Log.write(Log.CONN, "OutS:  Stream ready for writing");
+	if (bos != null)
+	    Log.write(Log.CONN, "OutS:  Buffering all data before sending " +
+			        "request");
+    }
+
+
+    /**
+     * Setup this stream to dump the data to the great bit-bucket in the sky.
+     * This is needed for when a module handles the request directly.
+     *
+     * @param req the request this stream is to be associated with
+     */
+    void ignoreData(Request req)
+    {
+	this.req = req;
+	ignore = true;
+    }
+
+
+    /**
+     * Return the response we got from sendRequest(). This waits until
+     * the request has actually been sent.
+     *
+     * @return the response returned by sendRequest()
+     */
+    synchronized Response getResponse()
+    {
+	while (resp == null)
+	    try { wait(); } catch (InterruptedException ie) { }
+
+	return resp;
+    }
+
+
+    /**
+     * Returns the number of bytes this stream is willing to accept, or -1
+     * if it is unbounded.
+     *
+     * @return the number of bytes
+     */
+    public int getLength()
+    {
+	return length;
+    }
+
+
+    /**
+     * Gets the trailers which were set with <code>setTrailers()</code>.
+     *
+     * @return an array of header fields
+     * @see #setTrailers(HTTPClient.NVPair[])
+     */
+    public NVPair[] getTrailers()
+    {
+	return trailers;
+    }
+
+
+    /**
+     * Sets the trailers to be sent if the output is sent with the
+     * chunked transfer encoding. These must be set before the output
+     * stream is closed for them to be sent.
+     *
+     * <P>Any trailers set here <strong>should</strong> be mentioned
+     * in a <var>Trailer</var> header in the request (see section 14.40
+     * of draft-ietf-http-v11-spec-rev-06.txt).
+     *
+     * <P>This method (and its related <code>getTrailers()</code>)) are
+     * in this class and not in <var>Request</var> because setting
+     * trailers is something an application may want to do, not only
+     * modules.
+     *
+     * @param trailers an array of header fields
+     */
+    public void setTrailers(NVPair[] trailers)
+    {
+	if (trailers != null)
+	    this.trailers = trailers;
+	else
+	    this.trailers = empty;
+    }
+
+
+    /**
+     * Reset this output stream, so it may be reused in a retried request.
+     * This method may only be invoked by modules, and <strong>must</strong>
+     * never be invoked by an application.
+     */
+    public void reset()
+    {
+	rcvd     = 0;
+	req      = null;
+	resp     = null;
+	os       = null;
+	bos      = null;
+	con_to   = 0;
+	ignore   = false;
+    }
+
+
+    /**
+     * Writes a single byte on the stream. It is subject to the same rules
+     * as <code>write(byte[], int, int)</code>.
+     *
+     * @param b the byte to write
+     * @exception IOException if any exception is thrown by the socket
+     * @see #write(byte[], int, int)
+     */
+    public void write(int b)  throws IOException, IllegalAccessError
+    {
+	byte[] tmp = { (byte) b };
+	write(tmp, 0, 1);
+    }
+
+
+    /**
+     * Writes an array of bytes on the stream. This method may not be used
+     * until this stream has been passed to one of the methods in
+     * HTTPConnection (i.e. until it has been associated with a request).
+     *
+     * @param buf an array containing the data to write
+     * @param off the offset of the data whithin the buffer
+     * @param len the number bytes (starting at <var>off</var>) to write
+     * @exception IOException if any exception is thrown by the socket, or
+     *            if writing <var>len</var> bytes would cause more bytes to
+     *            be written than this stream is willing to accept.
+     * @exception IllegalAccessError if this stream has not been associated
+     *            with a request yet
+     */
+    public synchronized void write(byte[] buf, int off, int len)
+	    throws IOException, IllegalAccessError
+    {
+	if (req == null)
+	    throw new IllegalAccessError("Stream not associated with a request");
+
+	if (ignore) return;
+
+	if (length != -1  &&  rcvd+len > length)
+	{
+	    IOException ioe =
+		new IOException("Tried to write too many bytes (" + (rcvd+len) +
+				" > " + length + ")");
+	    req.getConnection().closeDemux(ioe, false);
+	    req.getConnection().outputFinished();
+	    throw ioe;
+	}
+
+	try
+	{
+	    if (bos != null)
+		bos.write(buf, off, len);
+	    else if (length != -1)
+		os.write(buf, off, len);
+	    else
+		os.write(Codecs.chunkedEncode(buf, off, len, null, false));
+	}
+	catch (IOException ioe)
+	{
+	    req.getConnection().closeDemux(ioe, true);
+	    req.getConnection().outputFinished();
+	    throw ioe;
+	}
+
+	rcvd += len;
+    }
+
+
+    /**
+     * Closes the stream and causes the data to be sent if it has not already
+     * been done so. This method <strong>must</strong> be invoked when all
+     * data has been written.
+     *
+     * @exception IOException if any exception is thrown by the underlying
+     *            socket, or if too few bytes were written.
+     * @exception IllegalAccessError if this stream has not been associated
+     *            with a request yet.
+     */
+    public synchronized void close()  throws IOException, IllegalAccessError
+    {
+	if (req == null)
+	    throw new IllegalAccessError("Stream not associated with a request");
+
+	if (ignore) return;
+
+	if (bos != null)
+	{
+	    req.setData(bos.toByteArray());
+	    req.setStream(null);
+
+	    if (trailers.length > 0)
+	    {
+		NVPair[] hdrs = req.getHeaders();
+
+		// remove any Trailer header field
+
+		int len = hdrs.length;
+		for (int idx=0; idx<len; idx++)
+		{
+		    if (hdrs[idx].getName().equalsIgnoreCase("Trailer"))
+		    {
+			System.arraycopy(hdrs, idx+1, hdrs, idx, len-idx-1);
+			len--;
+		    }
+		}
+
+
+		// add the trailers to the headers
+
+		hdrs = Util.resizeArray(hdrs, len+trailers.length);
+		System.arraycopy(trailers, 0, hdrs, len, trailers.length);
+
+		req.setHeaders(hdrs);
+	    }
+
+	    Log.write(Log.CONN, "OutS:  Sending request");
+
+	    try
+		{ resp = req.getConnection().sendRequest(req, con_to); }
+	    catch (ModuleException me)
+		{ throw new IOException(me.toString()); }
+	    notify();
+	}
+	else
+	{
+	    if (rcvd < length)
+	    {
+		IOException ioe =
+		    new IOException("Premature close: only " + rcvd +
+				    " bytes written instead of the " +
+				    "expected " + length);
+		req.getConnection().closeDemux(ioe, false);
+		req.getConnection().outputFinished();
+		throw ioe;
+	    }
+
+	    try
+	    {
+		if (length == -1)
+		{
+		    if (Log.isEnabled(Log.CONN)  &&  trailers.length > 0)
+		    {
+			Log.write(Log.CONN, "OutS:  Sending trailers:");
+			for (int idx=0; idx<trailers.length; idx++)
+			    Log.write(Log.CONN, "       " +
+						trailers[idx].getName() + ": " +
+						trailers[idx].getValue());
+		    }
+
+		    os.write(Codecs.chunkedEncode(null, 0, 0, trailers, true));
+		}
+
+		os.flush();
+
+		Log.write(Log.CONN, "OutS:  All data sent");
+	    }
+	    catch (IOException ioe)
+	    {
+		req.getConnection().closeDemux(ioe, true);
+		throw ioe;
+	    }
+	    finally
+	    {
+		req.getConnection().outputFinished();
+	    }
+	}
+    }
+
+
+    /**
+     * produces a string describing this stream.
+     *
+     * @return a string containing the name and the length
+     */
+    public String toString()
+    {
+	return getClass().getName() + "[length=" + length + "]";
+    }
+}
diff --git a/HTTPClient/HttpURLConnection.java b/HTTPClient/HttpURLConnection.java
new file mode 100644
index 0000000..c6024b2
--- /dev/null
+++ b/HTTPClient/HttpURLConnection.java
@@ -0,0 +1,814 @@
+/*
+ * @(#)HttpURLConnection.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.net.URL;
+import java.net.ProtocolException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+
+/**
+ * This class is a wrapper around HTTPConnection providing the interface
+ * defined by java.net.URLConnection and java.net.HttpURLConnection.
+ *
+ * <P>This class can be used to replace the HttpClient in the JDK with this
+ * HTTPClient by defining the property
+ * <code>java.protocol.handler.pkgs=HTTPClient</code>.
+ *
+ * <P>One difference between Sun's HttpClient and this one is that this
+ * one will provide you with a real output stream if possible. This leads
+ * to two changes: you should set the request property "Content-Length",
+ * if possible, before invoking getOutputStream(); and in many cases
+ * getOutputStream() implies connect(). This should be transparent, though,
+ * apart from the fact that you can't change any headers or other settings
+ * anymore once you've gotten the output stream.
+ * So, for large data do:
+ * <PRE>
+ *   HttpURLConnection con = (HttpURLConnection) url.openConnection();
+ *
+ *   con.setDoOutput(true);
+ *   con.setRequestProperty("Content-Length", ...);
+ *   OutputStream out = con.getOutputStream();
+ *
+ *   out.write(...);
+ *   out.close();
+ *
+ *   if (con.getResponseCode() != 200)
+ *       ...
+ * </PRE>
+ *
+ * <P>The HTTPClient will send the request data using the chunked transfer
+ * encoding when no Content-Length is specified and the server is HTTP/1.1
+ * compatible. Because cgi-scripts can't usually handle this, you may
+ * experience problems trying to POST data. For this reason, whenever
+ * the Content-Type is application/x-www-form-urlencoded getOutputStream()
+ * will buffer the data before sending it so as prevent chunking. If you
+ * are sending requests with a different Content-Type and are experiencing
+ * problems then you may want to try setting the system property
+ * <var>HTTPClient.dontChunkRequests</var> to <var>true</var> (this needs
+ * to be done either on the command line or somewhere in the code before
+ * the first URLConnection.openConnection() is invoked).
+ *
+ * <P>A second potential incompatibility is that the HTTPClient aggresively
+ * resuses connections, and can do so more often that Sun's client. This
+ * can cause problems if you send multiple requests, and the first one has
+ * a long response. In this case (assuming the server allows the connection
+ * to be used for multiple requests) the responses to second, third, etc
+ * request won't be received until the first response has been completely
+ * read. With Sun's client on the other hand you may not experience this,
+ * as it may not be able to keep the connection open and there may create
+ * multiple connections for the requests. This allows the responses to the
+ * second, third, etc requests to be read before the first response has
+ * completed. <strong>Note:</strong> whether this will happen depends on
+ * details of the resource being requested and the server. In many cases
+ * the HTTPClient and Sun's client will exhibit the same behaviour. Also,
+ * applications which depend on being able to read the second response
+ * before the first one has completed must be considered broken, because
+ * A) this behaviour cannot be relied upon even in Sun's current client,
+ * and B) Sun's implementation will exhibit the same problem if they ever
+ * switch to HTTP/1.1.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public class HttpURLConnection extends java.net.HttpURLConnection
+{
+    /** the cache of HTTPConnections */
+    protected static Hashtable  connections = new Hashtable();
+
+    /** the current connection */
+    protected HTTPConnection    con;
+
+    /** the cached url.toString() */
+    private String            urlString;
+
+    /** the resource */
+    private String            resource;
+
+    /** the current method */
+    private String            method;
+
+    /** has the current method been set via setRequestMethod()? */
+    private boolean           method_set;
+
+    /** the default request headers */
+    private static NVPair[]   default_headers = new NVPair[0];
+
+    /** the request headers */
+    private NVPair[]          headers;
+
+    /** the response */
+    protected HTTPResponse      resp;
+
+    /** is the redirection module activated for this instance? */
+    private boolean           do_redir;
+
+    /** the RedirectionModule class */
+    private static Class      redir_mod;
+
+    /** the output stream used for POST and PUT */
+    private OutputStream      output_stream;
+
+
+    static
+    {
+	// The default allowUserAction in java.net.URLConnection is
+	// false.
+	try
+	{
+	    if (Boolean.getBoolean("HTTPClient.HttpURLConnection.AllowUI"))
+		setDefaultAllowUserInteraction(true);
+	}
+	catch (SecurityException se)
+	    { }
+
+	// get the RedirectionModule class
+	try
+	    { redir_mod = Class.forName("HTTPClient.RedirectionModule"); }
+	catch (ClassNotFoundException cnfe)
+	    { throw new NoClassDefFoundError(cnfe.getMessage()); }
+
+	// Set the User-Agent if the http.agent property is set
+	try
+	{
+	    String agent = System.getProperty("http.agent");
+	    if (agent != null)
+		setDefaultRequestProperty("User-Agent", agent);
+	}
+	catch (SecurityException se)
+	    { }
+    }
+
+
+    // Constructors
+
+    private static String non_proxy_hosts = "";
+    private static String proxy_host = "";
+    private static int    proxy_port = -1;
+
+    /**
+     * Construct a connection to the specified url. A cache of
+     * HTTPConnections is used to maximize the reuse of these across
+     * multiple HttpURLConnections.
+     *
+     * <BR>The default method is "GET".
+     *
+     * @param url the url of the request
+     * @exception ProtocolNotSuppException if the protocol is not supported
+     */
+    public HttpURLConnection(URL url)
+	    throws ProtocolNotSuppException, IOException
+    {
+	super(url);
+
+	// first read proxy properties and set
+        try
+        {
+            String hosts = System.getProperty("http.nonProxyHosts", "");
+	    if (!hosts.equalsIgnoreCase(non_proxy_hosts))
+	    {
+		connections.clear();
+		non_proxy_hosts = hosts;
+		String[] list = Util.splitProperty(hosts);
+		for (int idx=0; idx<list.length; idx++)
+		    HTTPConnection.dontProxyFor(list[idx]);
+	    }
+        }
+        catch (ParseException pe)
+	    { throw new IOException(pe.toString()); }
+        catch (SecurityException se)
+            { }
+
+	try
+	{
+	    String host = System.getProperty("http.proxyHost", "");
+	    int port = Integer.getInteger("http.proxyPort", -1).intValue();
+	    if (!host.equalsIgnoreCase(proxy_host)  ||  port != proxy_port)
+	    {
+		connections.clear();
+		proxy_host = host;
+		proxy_port = port;
+		HTTPConnection.setProxyServer(host, port);
+	    }
+	}
+	catch (SecurityException se)
+	    { }
+
+	// now setup stuff
+	con           = getConnection(url);
+	method        = "GET";
+	method_set    = false;
+	resource      = url.getFile();
+	headers       = default_headers;
+	do_redir      = getFollowRedirects();
+	output_stream = null;
+
+	urlString     = url.toString();
+    }
+
+
+    /**
+     * Returns an HTTPConnection. A cache of connections is kept and first
+     * consulted; only when the cache lookup fails is a new one created
+     * and added to the cache.
+     *
+     * @param url the url
+     * @return an HTTPConnection
+     * @exception ProtocolNotSuppException if the protocol is not supported
+     */
+    protected HTTPConnection getConnection(URL url)
+	    throws ProtocolNotSuppException
+    {
+	// try the cache, using the host name
+
+	String php = url.getProtocol() + ":" + url.getHost() + ":" +
+		     ((url.getPort() != -1) ? url.getPort() :
+					URI.defaultPort(url.getProtocol()));
+	php = php.toLowerCase();
+
+	HTTPConnection con = (HTTPConnection) connections.get(php);
+	if (con != null)  return con;
+
+
+	// Not in cache, so create new one and cache it
+
+	con = new HTTPConnection(url);
+	connections.put(php, con);
+
+	return con;
+    }
+
+
+    // Methods
+
+    /**
+     * Sets the request method (e.g. "PUT" or "HEAD"). Can only be set
+     * before connect() is called.
+     *
+     * @param method the http method.
+     * @exception ProtocolException if already connected.
+     */
+    public void setRequestMethod(String method)  throws ProtocolException
+    {
+	if (connected)
+	    throw new ProtocolException("Already connected!");
+
+	Log.write(Log.URLC, "URLC:  (" + urlString + ") Setting request method: " +
+			    method);
+
+	this.method = method.trim().toUpperCase();
+	method_set  = true;
+    }
+
+
+    /**
+     * Return the request method used.
+     *
+     * @return the http method.
+     */
+    public String getRequestMethod()
+    {
+	return method;
+    }
+
+
+    /**
+     * Get the response code. Calls connect() if not connected.
+     *
+     * @return the http response code returned.
+     */
+    public int getResponseCode()  throws IOException
+    {
+	if (!connected)  connect();
+
+	try
+	    { return resp.getStatusCode(); }
+	catch (ModuleException me)
+	    { throw new IOException(me.toString()); }
+    }
+
+
+    /**
+     * Get the response message describing the response code. Calls connect()
+     * if not connected.
+     *
+     * @return the http response message returned with the response code.
+     */
+    public String getResponseMessage()  throws IOException
+    {
+	if (!connected)  connect();
+
+	try
+	    { return resp.getReasonLine(); }
+	catch (ModuleException me)
+	    { throw new IOException(me.toString()); }
+    }
+
+
+    /**
+     * Get the value part of a header. Calls connect() if not connected.
+     *
+     * @param  name the of the header.
+     * @return the value of the header, or null if no such header was returned.
+     */
+    public String getHeaderField(String name)
+    {
+	try
+	{
+	    if (!connected)  connect();
+	    return resp.getHeader(name);
+	}
+	catch (Exception e)
+	    { return null; }
+    }
+
+
+    /**
+     * Get the value part of a header and converts it to an int. If the
+     * header does not exist or if its value could not be converted to an
+     * int then the default is returned. Calls connect() if not connected.
+     *
+     * @param  name the of the header.
+     * @param  def  the default value to return in case of an error.
+     * @return the value of the header, or null if no such header was returned.
+     */
+    public int getHeaderFieldInt(String name, int def)
+    {
+	try
+	{
+	    if (!connected)  connect();
+	    return resp.getHeaderAsInt(name);
+	}
+	catch (Exception e)
+	    { return def; }
+    }
+
+
+    /**
+     * Get the value part of a header, interprets it as a date and converts
+     * it to a long representing the number of milliseconds since 1970. If
+     * the header does not exist or if its value could not be converted to a
+     * date then the default is returned. Calls connect() if not connected.
+     *
+     * @param  name the of the header.
+     * @param  def  the default value to return in case of an error.
+     * @return the value of the header, or def in case of an error.
+     */
+    public long getHeaderFieldDate(String name, long def)
+    {
+	try
+	{
+	    if (!connected)  connect();
+	    return resp.getHeaderAsDate(name).getTime();
+	}
+	catch (Exception e)
+	    { return def; }
+    }
+
+
+    private String[] hdr_keys, hdr_values;
+
+    /**
+     * Gets header name of the n-th header. Calls connect() if not connected.
+     * The name of the 0-th header is <var>null</var>, even though it the
+     * 0-th header has a value.
+     *
+     * @param n which header to return.
+     * @return the header name, or null if not that many headers.
+     */
+    public String getHeaderFieldKey(int n)
+    {
+	if (hdr_keys == null)
+	    fill_hdr_arrays();
+
+	if (n >= 0  &&  n < hdr_keys.length)
+	    return hdr_keys[n];
+	else
+	    return null;
+    }
+
+
+    /**
+     * Gets header value of the n-th header. Calls connect() if not connected.
+     * The value of 0-th header is the Status-Line (e.g. "HTTP/1.1 200 Ok").
+     *
+     * @param n which header to return.
+     * @return the header value, or null if not that many headers.
+     */
+    public String getHeaderField(int n)
+    {
+	if (hdr_values == null)
+	    fill_hdr_arrays();
+
+	if (n >= 0  &&  n < hdr_values.length)
+	    return hdr_values[n];
+	else
+	    return null;
+    }
+
+
+    /**
+     * Cache the list of headers.
+     */
+    private void fill_hdr_arrays()
+    {
+	try
+	{
+	    if (!connected)  connect();
+
+	    // count number of headers
+	    int num = 1;
+	    Enumeration enumx = resp.listHeaders();
+	    while (enumx.hasMoreElements())
+	    {
+		num++;
+		enumx.nextElement();
+	    }
+
+	    // allocate arrays
+	    hdr_keys   = new String[num];
+	    hdr_values = new String[num];
+
+	    // fill arrays
+	    enumx = resp.listHeaders();
+	    for (int idx=1; idx<num; idx++)
+	    {
+		hdr_keys[idx]   = (String) enumx.nextElement();
+		hdr_values[idx] = resp.getHeader(hdr_keys[idx]);
+	    }
+
+	    // the 0'th field is special
+	    hdr_values[0] = resp.getVersion() + " " + resp.getStatusCode() +
+			    " " + resp.getReasonLine();
+	}
+	catch (Exception e)
+	    { hdr_keys = hdr_values = new String[0]; }
+    }
+
+
+    /**
+     * Gets an input stream from which the data in the response may be read.
+     * Calls connect() if not connected.
+     *
+     * @return an InputStream
+     * @exception ProtocolException if input not enabled.
+     * @see java.net.URLConnection#setDoInput(boolean)
+     */
+    public InputStream getInputStream()  throws IOException
+    {
+	if (!doInput)
+	    throw new ProtocolException("Input not enabled! (use setDoInput(true))");
+
+	if (!connected)  connect();
+
+	InputStream stream;
+	try
+	    { stream = resp.getInputStream(); }
+	catch (ModuleException e)
+	    { throw new IOException(e.toString()); }
+
+	return stream;
+    }
+
+
+    /**
+     * Returns the error stream if the connection failed
+     * but the server sent useful data nonetheless.
+     *
+     * <P>This method will not cause a connection to be initiated.
+     *
+     * @return an InputStream, or null if either the connection hasn't
+     *         been established yet or no error occured
+     * @see java.net.HttpURLConnection#getErrorStream()
+     * @since V0.3-1
+     */
+    public InputStream getErrorStream()
+    {
+	try
+	{
+	    if (!doInput  ||  !connected  ||  resp.getStatusCode() < 300  ||
+		resp.getHeaderAsInt("Content-length") <= 0)
+		return null;
+
+	    return resp.getInputStream();
+	}
+	catch (Exception e)
+	    { return null; }
+    }
+
+
+    /**
+     * Gets an output stream which can be used send an entity with the
+     * request. Can be called multiple times, in which case always the
+     * same stream is returned.
+     *
+     * <P>The default request method changes to "POST" when this method is
+     * called. Cannot be called after connect().
+     *
+     * <P>If no Content-type has been set it defaults to
+     * <var>application/x-www-form-urlencoded</var>. Furthermore, if the
+     * Content-type is <var>application/x-www-form-urlencoded</var> then all
+     * output will be collected in a buffer before sending it to the server;
+     * otherwise an HttpOutputStream is used.
+     *
+     * @return an OutputStream
+     * @exception ProtocolException if already connect()'ed, if output is not
+     *                              enabled or if the request method does not
+     *                              support output.
+     * @see java.net.URLConnection#setDoOutput(boolean)
+     * @see HTTPClient.HttpOutputStream
+     */
+    public synchronized OutputStream getOutputStream()  throws IOException
+    {
+	if (connected)
+	    throw new ProtocolException("Already connected!");
+
+	if (!doOutput)
+	    throw new ProtocolException("Output not enabled! (use setDoOutput(true))");
+	if (!method_set)
+	    method = "POST";
+	else if (method.equals("HEAD")  ||  method.equals("GET")  ||
+		 method.equals("TRACE"))
+	    throw new ProtocolException("Method "+method+" does not support output!");
+
+	if (getRequestProperty("Content-type") == null)
+	    setRequestProperty("Content-type", "application/x-www-form-urlencoded");
+
+	if (output_stream == null)
+	{
+	    Log.write(Log.URLC, "URLC:  (" + urlString + ") creating output stream");
+
+	    String cl = getRequestProperty("Content-Length");
+	    if (cl != null)
+		output_stream = new HttpOutputStream(Integer.parseInt(cl.trim()));
+	    else
+	    {
+		// Hack: because of restrictions when using true output streams
+		// and because form-data is usually quite limited in size, we
+		// first collect all data before sending it if this is
+		// form-data.
+		if (getRequestProperty("Content-type").equals(
+			"application/x-www-form-urlencoded"))
+		    output_stream = new ByteArrayOutputStream(300);
+		else
+		    output_stream = new HttpOutputStream();
+	    }
+
+	    if (output_stream instanceof HttpOutputStream)
+		connect();
+	}
+
+	return output_stream;
+    }
+
+
+    /**
+     * Gets the url for this connection. If we're connect()'d and the request
+     * was redirected then the url returned is that of the final request.
+     *
+     * @return the final url, or null if any exception occured.
+     */
+    public URL getURL()
+    {
+	if (connected)
+	{
+	    try
+		{ return resp.getEffectiveURI().toURL(); }
+	    catch (Exception e)
+		{ return null; }
+	}
+
+	return url;
+    }
+
+
+    /**
+     * Sets the <var>If-Modified-Since</var> header.
+     *
+     * @param time the number of milliseconds since 1970.
+     */
+    public void setIfModifiedSince(long time)
+    {
+	super.setIfModifiedSince(time);
+	setRequestProperty("If-Modified-Since", Util.httpDate(new Date(time)));
+    }
+
+
+    /**
+     * Sets an arbitrary request header.
+     *
+     * @param name  the name of the header.
+     * @param value the value for the header.
+     */
+    public void setRequestProperty(String name, String value)
+    {
+	Log.write(Log.URLC, "URLC:  (" + urlString + ") Setting request property: " +
+			    name + " : " + value);
+
+	int idx;
+	for (idx=0; idx<headers.length; idx++)
+	{
+	    if (headers[idx].getName().equalsIgnoreCase(name))
+		break;
+	}
+
+	if (idx == headers.length)
+	    headers = Util.resizeArray(headers, idx+1);
+
+	headers[idx] = new NVPair(name, value);
+    }
+
+
+    /**
+     * Gets the value of a given request header.
+     *
+     * @param name  the name of the header.
+     * @return the value part of the header, or null if no such header.
+     */
+    public String getRequestProperty(String name)
+    {
+	for (int idx=0; idx<headers.length; idx++)
+	{
+	    if (headers[idx].getName().equalsIgnoreCase(name))
+		return headers[idx].getValue();
+	}
+
+	return null;
+    }
+
+
+    /**
+     * Sets an arbitrary default request header. All headers set here are
+     * automatically sent with each request.
+     *
+     * @param name  the name of the header.
+     * @param value the value for the header.
+     */
+    public static void setDefaultRequestProperty(String name, String value)
+    {
+	Log.write(Log.URLC, "URLC:  Setting default request property: " +
+			    name + " : " + value);
+
+	int idx;
+	for (idx=0; idx<default_headers.length; idx++)
+	{
+	    if (default_headers[idx].getName().equalsIgnoreCase(name))
+		break;
+	}
+
+	if (idx == default_headers.length)
+	    default_headers = Util.resizeArray(default_headers, idx+1);
+
+	default_headers[idx] = new NVPair(name, value);
+    }
+
+
+    /**
+     * Gets the value for a given default request header.
+     *
+     * @param name  the name of the header.
+     * @return the value part of the header, or null if no such header.
+     */
+    public static String getDefaultRequestProperty(String name)
+    {
+	for (int idx=0; idx<default_headers.length; idx++)
+	{
+	    if (default_headers[idx].getName().equalsIgnoreCase(name))
+		return default_headers[idx].getValue();
+	}
+
+	return null;
+    }
+
+
+    /**
+     * Enables or disables the automatic handling of redirection responses
+     * for this instance only. Cannot be called after <code>connect()</code>.
+     *
+     * @param set enables automatic redirection handling if true.
+     */
+    public void setInstanceFollowRedirects(boolean set)
+    {
+	if (connected)
+	    throw new IllegalStateException("Already connected!");
+
+	do_redir = set;
+    }
+
+
+    /**
+     * @return true if automatic redirection handling for this instance is
+     *              enabled.
+     */
+    public boolean getInstanceFollowRedirects()
+    {
+	return do_redir;
+    }
+
+
+    /**
+     * Connects to the server (if connection not still kept alive) and
+     * issues the request.
+     */
+    public synchronized void connect()  throws IOException
+    {
+	if (connected)  return;
+
+	Log.write(Log.URLC, "URLC:  (" + urlString + ") Connecting ...");
+
+	// useCaches TBD!!!
+
+	synchronized (con)
+	{
+	    con.setAllowUserInteraction(allowUserInteraction);
+	    if (do_redir)
+		con.addModule(redir_mod, 2);
+	    else
+		con.removeModule(redir_mod);
+
+	    try
+	    {
+		if (output_stream instanceof ByteArrayOutputStream)
+		    resp = con.ExtensionMethod(method, resource,
+			((ByteArrayOutputStream) output_stream).toByteArray(),
+					     headers);
+		else
+		    resp = con.ExtensionMethod(method, resource,
+				    (HttpOutputStream) output_stream, headers);
+	    }
+	    catch (ModuleException e)
+		{ throw new IOException(e.toString()); }
+	}
+
+	connected = true;
+    }
+
+
+    /**
+     * Closes all the connections to this server.
+     */
+    public void disconnect()
+    {
+	Log.write(Log.URLC, "URLC:  (" + urlString + ") Disconnecting ...");
+
+	con.stop();
+    }
+
+
+    /**
+     * Shows if request are being made through an http proxy or directly.
+     *
+     * @return true if an http proxy is being used.
+     */
+    public boolean usingProxy()
+    {
+	return (con.getProxyHost() != null);
+    }
+
+
+    /**
+     * produces a string.
+     * @return a string containing the HttpURLConnection
+     */
+    public String toString()
+    {
+	return getClass().getName() + "[" + url + "]";
+    }
+}
diff --git a/HTTPClient/IdempotentSequence.java b/HTTPClient/IdempotentSequence.java
new file mode 100644
index 0000000..0ef7649
--- /dev/null
+++ b/HTTPClient/IdempotentSequence.java
@@ -0,0 +1,405 @@
+/*
+ * @(#)IdempotentSequence.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ * <P>This class checks whether a sequence of requests is idempotent. This
+ * is used to determine which requests may be automatically retried. This
+ * class also serves as a central place to record which methods have side
+ * effects and which methods are idempotent.
+ *
+ * <P>Note: unknown methods (i.e. a method which is not HEAD, GET, POST, PUT,
+ * DELETE, OPTIONS, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, or
+ * UNLOCK) are treated conservatively, meaning they are assumed to have side
+ * effects and are not idempotent.
+ *
+ * <P>Usage:
+ * <PRE>
+ *     IdempotentSequence seq = new IdempotentSequence();
+ *     seq.add(r1);
+ *     ...
+ *     if (seq.isIdempotent(r1)) ...
+ *     ...
+ * </PRE>
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class IdempotentSequence
+{
+    /** method number definitions */
+    private static final int UNKNOWN = 0,
+			     HEAD    = 1,
+			     GET     = 2,
+			     POST    = 3,
+			     PUT     = 4,
+			     DELETE  = 5,
+			     OPTIONS = 6,
+			     TRACE   = 7,
+
+			     // DAV methods
+			     PROPFIND  = 8,
+			     PROPPATCH = 9,
+			     MKCOL     = 10,
+			     COPY      = 11,
+			     MOVE      = 12,
+			     LOCK      = 13,
+			     UNLOCK    = 14;
+
+    /** these are the history of previous requests */
+    private int[]    m_history;
+    private String[] r_history;
+    private int      m_len, r_len;
+
+    /** trigger analysis of threads */
+    private boolean   analysis_done = false;
+    private Hashtable threads = new Hashtable();
+
+
+    // Constructors
+
+    /**
+     * Start a new sequence of requests.
+     */
+    public IdempotentSequence()
+    {
+	m_history = new int[10];
+	r_history = new String[10];
+	m_len = 0;
+	r_len = 0;
+    }
+
+
+    // Methods
+
+    /**
+     * Add the request to the end of the list of requests. This is used
+     * to build the complete sequence of requests before determining
+     * whether the sequence is idempotent.
+     *
+     * @param req the next request
+     */
+    public void add(Request req)
+    {
+	if (m_len >= m_history.length)
+	    m_history = Util.resizeArray(m_history, m_history.length+10);
+	m_history[m_len++] = methodNum(req.getMethod());
+
+	if (r_len >= r_history.length)
+	    r_history = Util.resizeArray(r_history, r_history.length+10);
+	r_history[r_len++] = req.getRequestURI();
+    }
+
+
+    /**
+     * Is this request part of an idempotent sequence? This method <em>must
+     * not</em> be called before all requests have been added to this
+     * sequence; similarly, <var>add()</var> <em>must not</em> be called
+     * after this method was invoked.
+     *
+     * <P>We split up the sequence of requests into individual sub-sequences,
+     * or threads, with all requests in a thread having the same request-URI
+     * and no two threads having the same request-URI. Each thread is then
+     * marked as idempotent or not according to the following rules:
+     *
+     * <OL>
+     * <LI>If any method is UNKNOWN then the thread is not idempotent;
+     * <LI>else, if no method has side effects then the thread is idempotent;
+     * <LI>else, if the first method has side effects and is complete then
+     *     the thread is idempotent;
+     * <LI>else, if the first method has side effects, is not complete,
+     *     and no other method has side effects then the thread is idempotent;
+     * <LI>else the thread is not idempotent.
+     * </OL>
+     *
+     * <P>The major assumption here is that the side effects of any method
+     * only apply to resource specified. E.g. a <tt>"PUT /barbara.html"</tt>
+     * will only affect the resource "/barbara.html" and nothing else.
+     * This assumption is violated by POST of course; however, POSTs are
+     * not pipelined and will therefore never show up here.
+     *
+     * @param req the request
+     */
+    public boolean isIdempotent(Request req)
+    {
+	if (!analysis_done)
+	    do_analysis();
+
+	return ((Boolean) threads.get(req.getRequestURI())).booleanValue();
+    }
+
+
+    private static final Object INDET = new Object();
+
+    private void do_analysis()
+    {
+	for (int idx=0; idx<r_len; idx++)
+	{
+	    Object t_state = threads.get(r_history[idx]);
+
+	    if (m_history[idx] == UNKNOWN)
+		threads.put(r_history[idx], Boolean.FALSE);
+	    else if (t_state == null)		// new thread
+	    {
+		if (methodHasSideEffects(m_history[idx]))
+		{
+		    if (methodIsComplete(m_history[idx]))	// is idempotent
+			threads.put(r_history[idx], Boolean.TRUE);
+		    else
+			threads.put(r_history[idx], Boolean.FALSE);
+		}
+		else					// indeterminate
+		    threads.put(r_history[idx], INDET);
+	    }
+	    else				// update thread
+	    {
+		if (t_state == INDET  && methodHasSideEffects(m_history[idx]))
+		    threads.put(r_history[idx], Boolean.FALSE);
+	    }
+	}
+
+	// any thread still indeterminate must be idempotent
+	Enumeration te = threads.keys();
+	while (te.hasMoreElements())
+	{
+	    String res = (String) te.nextElement();
+	    if (threads.get(res) == INDET)
+		threads.put(res, Boolean.TRUE);
+	}
+    }
+
+
+    /**
+     * A method is idempotent if the side effects of N identical
+     * requests is the same as for a single request (Section 9.1.2
+     * of RFC-????).
+     *
+     * @return true if method is idempotent
+     */
+    public static boolean methodIsIdempotent(String method)
+    {
+	return methodIsIdempotent(methodNum(method));
+    }
+
+
+    private static boolean methodIsIdempotent(int method)
+    {
+	switch (method)
+	{
+	    case HEAD:
+	    case GET:
+	    case PUT:
+	    case DELETE:
+	    case OPTIONS:
+	    case TRACE:
+	    case PROPFIND:
+	    case PROPPATCH:
+	    case COPY:
+	    case MOVE:
+		return true;
+	    case UNKNOWN:
+	    case POST:
+	    case MKCOL:
+	    case LOCK:
+	    case UNLOCK:
+	    default:
+		return false;
+	}
+    }
+
+
+    /**
+     * A method is complete if any side effects of the request affect
+     * the complete resource. For example, a PUT is complete but a
+     * PUT with byte-ranges wouldn't be. In essence, if a request uses
+     * a method which has side effects and is complete then the state
+     * of the resource after the request is independent of the state of
+     * the resource before the request.
+     *
+     * @return true if method is complete
+     */
+    public static boolean methodIsComplete(String method)
+    {
+	return methodIsComplete(methodNum(method));
+    }
+
+
+    private static boolean methodIsComplete(int method)
+    {
+	switch (method)
+	{
+	    case HEAD:
+	    case GET:
+	    case PUT:
+	    case DELETE:
+	    case OPTIONS:
+	    case TRACE:
+	    case PROPFIND:
+	    case COPY:
+	    case MOVE:
+	    case LOCK:
+	    case UNLOCK:
+		return true;
+	    case UNKNOWN:
+	    case POST:
+	    case PROPPATCH:
+	    case MKCOL:
+	    default:
+		return false;
+	}
+    }
+
+
+    public static boolean methodHasSideEffects(String method)
+    {
+	return methodHasSideEffects(methodNum(method));
+    }
+
+
+    private static boolean methodHasSideEffects(int method)
+    {
+	switch (method)
+	{
+	    case HEAD:
+	    case GET:
+	    case OPTIONS:
+	    case TRACE:
+	    case PROPFIND:
+	    case LOCK:
+	    case UNLOCK:
+		return false;
+	    case UNKNOWN:
+	    case POST:
+	    case PUT:
+	    case DELETE:
+	    case PROPPATCH:
+	    case MKCOL:
+	    case COPY:
+	    case MOVE:
+	    default:
+		return true;
+	}
+    }
+
+
+    private static int methodNum(String method)
+    {
+	if (method.equals("GET"))
+	    return GET;
+	if (method.equals("POST"))
+	    return POST;
+	if (method.equals("HEAD"))
+	    return HEAD;
+	if (method.equals("PUT"))
+	    return PUT;
+	if (method.equals("DELETE"))
+	    return DELETE;
+	if (method.equals("OPTIONS"))
+	    return OPTIONS;
+	if (method.equals("TRACE"))
+	    return TRACE;
+	if (method.equals("PROPFIND"))
+	    return PROPFIND;
+	if (method.equals("PROPPATCH"))
+	    return PROPPATCH;
+	if (method.equals("MKCOL"))
+	    return MKCOL;
+	if (method.equals("COPY"))
+	    return COPY;
+	if (method.equals("MOVE"))
+	    return MOVE;
+	if (method.equals("LOCK"))
+	    return LOCK;
+	if (method.equals("UNLOCK"))
+	    return UNLOCK;
+
+	return UNKNOWN;
+    }
+
+
+    /**
+     * Test code.
+     */
+    public static void main(String args[])
+    {
+	IdempotentSequence seq = new IdempotentSequence();
+
+	seq.add(new Request(null, "GET", "/b1", null, null, null, false));
+	seq.add(new Request(null, "PUT", "/b2", null, null, null, false));
+	seq.add(new Request(null, "GET", "/b1", null, null, null, false));
+	seq.add(new Request(null, "PUT", "/b3", null, null, null, false));
+	seq.add(new Request(null, "GET", "/b2", null, null, null, false));
+	seq.add(new Request(null, "POST", "/b8", null, null, null, false));
+	seq.add(new Request(null, "PUT", "/b3", null, null, null, false));
+	seq.add(new Request(null, "GET", "/b1", null, null, null, false));
+	seq.add(new Request(null, "TRACE", "/b4", null, null, null, false));
+	seq.add(new Request(null, "GET", "/b9", null, null, null, false));
+	seq.add(new Request(null, "LINK", "/b4", null, null, null, false));
+	seq.add(new Request(null, "GET", "/b4", null, null, null, false));
+	seq.add(new Request(null, "PUT", "/b5", null, null, null, false));
+	seq.add(new Request(null, "HEAD", "/b5", null, null, null, false));
+	seq.add(new Request(null, "PUT", "/b5", null, null, null, false));
+	seq.add(new Request(null, "POST", "/b9", null, null, null, false));
+	seq.add(new Request(null, "GET", "/b6", null, null, null, false));
+	seq.add(new Request(null, "DELETE", "/b6", null, null, null, false));
+	seq.add(new Request(null, "HEAD", "/b6", null, null, null, false));
+	seq.add(new Request(null, "OPTIONS", "/b7", null, null, null, false));
+	seq.add(new Request(null, "TRACE", "/b7", null, null, null, false));
+	seq.add(new Request(null, "GET", "/b7", null, null, null, false));
+	seq.add(new Request(null, "PUT", "/b7", null, null, null, false));
+
+	if (!seq.isIdempotent(new Request(null, null, "/b1", null, null, null, false)))
+	    System.err.println("Sequence b1 failed");
+	if (!seq.isIdempotent(new Request(null, null, "/b2", null, null, null, false)))
+	    System.err.println("Sequence b2 failed");
+	if (!seq.isIdempotent(new Request(null, null, "/b3", null, null, null, false)))
+	    System.err.println("Sequence b3 failed");
+	if (seq.isIdempotent(new Request(null, null, "/b4", null, null, null, false)))
+	    System.err.println("Sequence b4 failed");
+	if (!seq.isIdempotent(new Request(null, null, "/b5", null, null, null, false)))
+	    System.err.println("Sequence b5 failed");
+	if (seq.isIdempotent(new Request(null, null, "/b6", null, null, null, false)))
+	    System.err.println("Sequence b6 failed");
+	if (seq.isIdempotent(new Request(null, null, "/b7", null, null, null, false)))
+	    System.err.println("Sequence b7 failed");
+	if (seq.isIdempotent(new Request(null, null, "/b8", null, null, null, false)))
+	    System.err.println("Sequence b8 failed");
+	if (seq.isIdempotent(new Request(null, null, "/b9", null, null, null, false)))
+	    System.err.println("Sequence b9 failed");
+
+	System.out.println("Tests finished");
+    }
+}
diff --git a/HTTPClient/LinkedList.java b/HTTPClient/LinkedList.java
new file mode 100644
index 0000000..cdb2227
--- /dev/null
+++ b/HTTPClient/LinkedList.java
@@ -0,0 +1,239 @@
+/*
+ * @(#)LinkedList.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This class implements a singly linked list.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class LinkedList
+{
+    /** head of list */
+    private LinkElement head = null;
+
+    /** tail of list (for faster adding) */
+    private LinkElement tail = null;
+
+
+    /**
+     * Add the specified element to the head of the list.
+     *
+     * @param elem the object to add to the list.
+     */
+    public synchronized void addToHead(Object elem)
+    {
+	head = new LinkElement(elem, head);
+
+	if (head.next == null)
+	    tail = head;
+    }
+
+
+    /**
+     * Add the specified element to the end of the list.
+     *
+     * @param elem the object to add to the list.
+     */
+    public synchronized void addToEnd(Object elem)
+    {
+	if (head == null)
+	    head = tail = new LinkElement(elem, null);
+	else
+	    tail = (tail.next = new LinkElement(elem, null));
+    }
+
+
+    /**
+     * Remove the specified element from the list. Does nothing if the element
+     * is not in the list.
+     *
+     * @param elem the object to remove from the list.
+     */
+    public synchronized void remove(Object elem)
+    {
+	if (head == null)  return;
+
+	if (head.element == elem)
+	{
+	    head = head.next;
+	    return;
+	}
+
+	LinkElement curr = head;
+	while (curr.next != null)
+	{
+	    if (curr.next.element == elem)
+	    {
+		if (curr.next == tail)  tail = curr;
+		curr.next = curr.next.next;
+		return;
+	    }
+	    curr = curr.next;
+	}
+    }
+
+
+    /**
+     * Return the first element in the list. The list is not modified in any
+     * way.
+     *
+     * @return the first element
+     */
+    public synchronized Object getFirst()
+    {
+	if (head == null)  return null;
+	return head.element;
+    }
+
+
+    private LinkElement next_enum = null;
+
+    /**
+     * Starts an enumeration of all the elements in this list. Note that only
+     * one enumeration can be active at any time.
+     *
+     * @return the first element, or null if the list is empty
+     */
+    public synchronized Object enumerate()
+    {
+	if (head == null)  return null;
+
+	next_enum = head.next;
+	return head.element;
+    }
+
+
+    /**
+     * Gets the next element in the enumeration. The enumeration must have
+     * been first initalized with a call to <code>enumerate()</code>.
+     *
+     * @return the next element, or null if none left
+     * @see #enumerate()
+     */
+    public synchronized Object next()
+    {
+	if (next_enum == null)  return null;
+
+	Object elem = next_enum.element;
+	next_enum = next_enum.next;
+
+	return elem;
+    }
+
+
+    public static void main(String args[])  throws Exception
+    {
+	// LinkedList Test Suite
+
+	System.err.println("\n*** Linked List Tests ...");
+
+	LinkedList list = new LinkedList();
+	list.addToHead("One");
+	list.addToEnd("Last");
+	if (!list.getFirst().equals("One"))
+	    throw new Exception("First element wrong");
+	if (!list.enumerate().equals("One"))
+	    throw new Exception("First element wrong");
+	if (!list.next().equals("Last"))
+	    throw new Exception("Last element wrong");
+	if (list.next() != null)
+	    throw new Exception("End of list wrong");
+	list.remove("One");
+	if (!list.getFirst().equals("Last"))
+	    throw new Exception("First element wrong");
+	list.remove("Last");
+	if (list.getFirst() != null)
+	    throw new Exception("End of list wrong");
+
+	list = new LinkedList();
+	list.addToEnd("Last");
+	list.addToHead("One");
+	if (!list.getFirst().equals("One"))
+	    throw new Exception("First element wrong");
+	if (!list.enumerate().equals("One"))
+	    throw new Exception("First element wrong");
+	if (!list.next().equals("Last"))
+	    throw new Exception("Last element wrong");
+	if (list.next() != null)
+	    throw new Exception("End of list wrong");
+	if (!list.enumerate().equals("One"))
+	    throw new Exception("First element wrong");
+	list.remove("One");
+	if (!list.next().equals("Last"))
+	    throw new Exception("Last element wrong");
+	list.remove("Last");
+	if (list.next() != null)
+	    throw new Exception("End of list wrong");
+
+	list = new LinkedList();
+	list.addToEnd("Last");
+	list.addToHead("Two");
+	list.addToHead("One");
+	if (!list.getFirst().equals("One"))
+	    throw new Exception("First element wrong");
+	if (!list.enumerate().equals("One"))
+	    throw new Exception("First element wrong");
+	if (!list.next().equals("Two"))
+	    throw new Exception("Second element wrong");
+	if (!list.next().equals("Last"))
+	    throw new Exception("Last element wrong");
+	if (list.next() != null)
+	    throw new Exception("End of list wrong");
+	list.remove("Last");
+	list.remove("Two");
+	list.remove("One");
+	if (list.getFirst() != null)
+	    throw new Exception("Empty list wrong");
+
+	System.err.println("\n*** Tests finished successfuly");
+    }
+}
+
+
+/**
+ * The represents a single element in the linked list.
+ */
+class LinkElement
+{
+    Object      element;
+    LinkElement next;
+
+    LinkElement(Object elem, LinkElement next)
+    {
+	this.element = elem;
+	this.next    = next;
+    }
+}
diff --git a/HTTPClient/Log.java b/HTTPClient/Log.java
new file mode 100644
index 0000000..cebca08
--- /dev/null
+++ b/HTTPClient/Log.java
@@ -0,0 +1,318 @@
+/*
+ * @(#)Log.java						0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.io.OutputStreamWriter;
+import java.io.ByteArrayOutputStream;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * This is a simple logger for the HTTPClient. It defines a number of
+ * "facilities", each representing one or more logically connected classes.
+ * Logging can be enabled or disabled on a per facility basis. Furthermore, the
+ * logging output can be redirected to any desired place, such as a file or a
+ * buffer; by default all logging goes to <var>System.err</var>.
+ *
+ * <P>All log entries are preceded by the name of the currently executing
+ * thread, enclosed in {}'s, and the current time in hours, minutes, seconds,
+ * and milliseconds, enclosed in []'s. Example:
+ * <tt>{Thread-5} [20:14:03.244] Conn:  Sending Request</tt>
+ *
+ * <P>When the class is loaded, two java system properties are read:
+ * <var>HTTPClient.log.file</var> and <var>HTTPClient.log.mask</var>. The first
+ * one, if set, causes all logging to be redirected to the file with the given
+ * name. The second one, if set, is used for setting which facilities are
+ * enabled; the value must be the bitwise OR ('|') of the values of the desired
+ * enabled facilities. E.g. a value of 3 would enable logging for the
+ * HTTPConnection and HTTPResponse, a value of 16 would enable cookie related
+ * logging, and a value of 8 would enable authorization related logging; a
+ * value of -1 would enable logging for all facilities. By default logging is
+ * disabled.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3-3
+ */
+public class Log
+{
+    /** The HTTPConnection facility (1) */
+    public static final int CONN  = 1 << 0;
+    /** The HTTPResponse facility (2) */
+    public static final int RESP  = 1 << 1;
+    /** The StreamDemultiplexor facility (4) */
+    public static final int DEMUX = 1 << 2;
+    /** The Authorization facility (8) */
+    public static final int AUTH  = 1 << 3;
+    /** The Cookies facility (16) */
+    public static final int COOKI = 1 << 4;
+    /** The Modules facility (32) */
+    public static final int MODS  = 1 << 5;
+    /** The Socks facility (64) */
+    public static final int SOCKS = 1 << 6;
+    /** The ULRConnection facility (128) */
+    public static final int URLC  = 1 << 7;
+    /** All the facilities - for use in <code>setLogging</code> (-1) */
+    public static final int ALL   = ~0;
+
+    private static final String NL     = System.getProperty("line.separator");
+    private static final long   TZ_OFF;
+
+    private static int     facMask     = 0;
+    private static Writer  logWriter   = new OutputStreamWriter(System.err);
+    private static boolean closeWriter = false;
+
+
+    static
+    {
+	Calendar now = Calendar.getInstance();
+	TZ_OFF = TimeZone.getDefault().getOffset(now.get(Calendar.ERA),
+						 now.get(Calendar.YEAR),
+						 now.get(Calendar.MONTH),
+						 now.get(Calendar.DAY_OF_MONTH),
+						 now.get(Calendar.DAY_OF_WEEK),
+						 now.get(Calendar.MILLISECOND));
+
+	try
+	{
+	    String file = System.getProperty("HTTPClient.log.file");
+	    if (file != null)
+	    {
+		try 
+		    { setLogWriter(new FileWriter(file), true); }
+		catch (IOException ioe)
+		{
+		    System.err.println("failed to open file log stream `" +
+				       file + "': " + ioe);
+		}
+	    }
+	}
+	catch (Exception e)
+	    { }
+
+	try
+	{
+	    facMask = Integer.getInteger("HTTPClient.log.mask", 0).intValue();
+	}
+	catch (Exception e)
+	    { }
+    }
+
+
+    // Constructors
+
+    /**
+     * Not meant to be instantiated
+     */
+    private Log()
+    {
+    }
+
+
+    // Methods
+
+    /**
+     * Write the given message to the current log if logging for the given facility is
+     * enabled.
+     *
+     * @param facility  the facility which is logging the message
+     * @param msg       the message to log
+     */
+    public static void write(int facility, String msg)
+    {
+	if ((facMask & facility) == 0)
+	    return;
+
+	try
+	{
+	    writePrefix();
+	    logWriter.write(msg);
+	    logWriter.write(NL);
+	    logWriter.flush();
+	}
+	catch (IOException ioe)
+	{
+	    System.err.println("Failed to write to log: " + ioe);
+	    System.err.println("Failed log Entry was: " + msg);
+	}
+    }
+
+    /**
+     * Write the stack trace of the given exception to the current log if logging for the
+     * given facility is enabled.
+     *
+     * @param facility  the facility which is logging the message
+     * @param prefix    the string with which to prefix the stack trace; may be null
+     * @param t         the exception to log
+     */
+    public static void write(int facility, String prefix, Throwable t)
+    {
+	if ((facMask & facility) == 0)
+	    return;
+
+	synchronized (Log.class)
+	{
+	    if (!(logWriter instanceof PrintWriter))
+		logWriter = new PrintWriter(logWriter);
+	}
+
+	try
+	{
+	    writePrefix();
+	    if (prefix != null)
+		logWriter.write(prefix);
+	    t.printStackTrace((PrintWriter) logWriter);
+	    logWriter.flush();
+	}
+	catch (IOException ioe)
+	{
+	    System.err.println("Failed to write to log: " + ioe);
+	    System.err.print("Failed log Entry was: " + prefix);
+	    t.printStackTrace(System.err);
+	}
+    }
+
+    /**
+     * Write the contents of the given buffer to the current log if logging for
+     * the given facility is enabled.
+     *
+     * @param facility  the facility which is logging the message
+     * @param prefix    the string with which to prefix the buffer contents;
+     *                  may be null
+     * @param buf       the buffer to dump
+     */
+    public static void write(int facility, String prefix, ByteArrayOutputStream buf)
+    {
+	if ((facMask & facility) == 0)
+	    return;
+
+	try
+	{
+	    writePrefix();
+	    if (prefix != null)
+		logWriter.write(prefix);
+	    logWriter.write(NL);
+	    logWriter.write(new String(buf.toByteArray(), "ISO_8859-1"));
+	    logWriter.flush();
+	}
+	catch (IOException ioe)
+	{
+	    System.err.println("Failed to write to log: " + ioe);
+	    System.err.println("Failed log Entry was: " + prefix);
+	    System.err.println(new String(buf.toByteArray()));
+	}
+    }
+
+    /**
+     * Write a log line prefix of the form 
+     * <PRE>
+     *  {thread-name} [time]
+     * </PRE>
+     */
+    private static final void writePrefix() throws IOException {
+	logWriter.write("{" + Thread.currentThread().getName() + "} ");
+
+	int mill  = (int) ((System.currentTimeMillis() + TZ_OFF) % (24 * 3600000));
+	int secs  = mill / 1000;
+	int mins  = secs / 60;
+	int hours = mins / 60;
+	logWriter.write("[" + fill2(hours) + ':' + fill2(mins - hours*60) +
+			':' + fill2(secs - mins * 60) + '.' +
+			fill3(mill - secs * 1000) + "] ");
+    }
+
+    private static final String fill2(int num) {
+	return ((num < 10) ? "0" : "") + num;
+    }
+
+    private static final String fill3(int num) {
+	return ((num < 10) ? "00" : (num < 100) ? "0" : "") + num;
+    }
+
+    /**
+     * Check whether logging for the given facility is enabled or not.
+     *
+     * @param facility  the facility to check
+     * @return true if logging for the given facility is enable; false otherwise
+     */
+    public static boolean isEnabled(int facility)
+    {
+	return ((facMask & facility) != 0);
+    }
+
+    /**
+     * Enable or disable logging for the given facilities.
+     *
+     * @param facilities the facilities for which to enable or disable logging.
+     *                   This is bitwise OR ('|') of all the desired
+     *                   facilities; use {@link #ALL ALL} to affect all facilities
+     * @param enable     if true, enable logging for the chosen facilities; if
+     *                   false, disable logging for them.
+     */
+    public static void setLogging(int facilities, boolean enable)
+    {
+	if (enable)
+	    facMask |= facilities;
+	else
+	    facMask &= ~facilities;
+    }
+
+    /**
+     * Set the writer to which to log. By default, things are logged to
+     * <var>System.err</var>.
+     *
+     * @param log           the writer to log to; if null, nothing is changed
+     * @param closeWhenDone if true, close this stream when a new stream is set
+     *                      again
+     */
+    public static void setLogWriter(Writer log, boolean closeWhenDone)
+    {
+	if (log == null)
+	    return;
+
+	if (closeWriter)
+	{
+	  try
+	      { logWriter.close(); }
+	  catch (IOException ioe)
+	      { System.err.println("Error closing log stream: " + ioe); }
+	}
+
+	logWriter   = log;
+	closeWriter = closeWhenDone;
+    }
+}
diff --git a/HTTPClient/MD5.java b/HTTPClient/MD5.java
new file mode 100644
index 0000000..8d8cedb
--- /dev/null
+++ b/HTTPClient/MD5.java
@@ -0,0 +1,161 @@
+/*
+ * @(#)MD5.java						0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+
+/**
+ * Some utility methods for digesting info using MD5.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3-3
+ */
+class MD5
+{
+    private static final char[] hex = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+    };
+
+    /**
+     * Turns array of bytes into string representing each byte as
+     * unsigned hex number.
+     *
+     * @param hash	array of bytes to convert to hex-string
+     * @return	generated hex string
+     */
+    public static final String toHex(byte hash[])
+    {
+	StringBuffer buf = new StringBuffer(hash.length * 2);
+
+	for (int idx=0; idx<hash.length; idx++)
+	    buf.append(hex[(hash[idx] >> 4) & 0x0f]).append(hex[hash[idx] & 0x0f]);
+
+	return buf.toString();
+    }
+
+    /**
+     * Digest the input.
+     *
+     * @param input the data to be digested.
+     * @return the md5-digested input
+     */
+    public static final byte[] digest(byte[] input)
+    {
+	try 
+	{
+	    MessageDigest md5 = MessageDigest.getInstance("MD5");
+	    return md5.digest(input);
+	}
+	catch (NoSuchAlgorithmException nsae)
+	{
+	    throw new Error(nsae.toString());
+	}
+    }
+
+    /**
+     * Digest the input.
+     *
+     * @param input1 the first part of the data to be digested.
+     * @param input2 the second part of the data to be digested.
+     * @return the md5-digested input
+     */
+    public static final byte[] digest(byte[] input1, byte[] input2)
+    {
+	try 
+	{
+	    MessageDigest md5 = MessageDigest.getInstance("MD5");
+	    md5.update(input1);
+	    return md5.digest(input2);
+	}
+	catch (NoSuchAlgorithmException nsae)
+	{
+	    throw new Error(nsae.toString());
+	}
+    }
+
+    /**
+     * Digest the input.
+     *
+     * @param input the data to be digested.
+     * @return the md5-digested input as a hex string
+     */
+    public static final String hexDigest(byte[] input)
+    {
+	return toHex(digest(input));
+    }
+
+    /**
+     * Digest the input.
+     *
+     * @param input1 the first part of the data to be digested.
+     * @param input2 the second part of the data to be digested.
+     * @return the md5-digested input as a hex string
+     */
+    public static final String hexDigest(byte[] input1, byte[] input2)
+    {
+	return toHex(digest(input1, input2));
+    }
+
+    /**
+     * Digest the input.
+     *
+     * @param input the data to be digested.
+     * @return the md5-digested input as a hex string
+     */
+    public static final byte[] digest(String input)
+    {
+	try
+	    { return digest(input.getBytes("8859_1")); }
+	catch (UnsupportedEncodingException uee)
+	    { throw new Error(uee.toString()); }
+    }
+
+    /**
+     * Digest the input.
+     *
+     * @param input the data to be digested.
+     * @return the md5-digested input as a hex string
+     */
+    public static final String hexDigest(String input)
+    {
+	try
+	    { return toHex(digest(input.getBytes("8859_1"))); }
+	catch (UnsupportedEncodingException uee)
+	    { throw new Error(uee.toString()); }
+    }
+}
diff --git a/HTTPClient/MD5InputStream.java b/HTTPClient/MD5InputStream.java
new file mode 100644
index 0000000..07d20ea
--- /dev/null
+++ b/HTTPClient/MD5InputStream.java
@@ -0,0 +1,143 @@
+/*
+ * @(#)MD5InputStream.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FilterInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+
+/**
+ * This class calculates a running md5 digest of the data read. When the
+ * stream is closed the calculated digest is passed to a HashVerifier which
+ * is expected to verify this digest and to throw an Exception if it fails.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class MD5InputStream extends FilterInputStream
+{
+    private HashVerifier verifier;
+    private MessageDigest md5;
+    private long rcvd = 0;
+    private boolean closed = false;
+
+
+    /**
+     * @param is the input stream over which the md5 hash is to be calculated
+     * @param verifier the HashVerifier to invoke when the stream is closed
+     */
+    public MD5InputStream(InputStream is, HashVerifier verifier)
+    {
+	super(is);
+	this.verifier = verifier;
+	try
+	    { md5 = MessageDigest.getInstance("MD5"); }
+	catch (NoSuchAlgorithmException nsae)
+	    { throw new Error(nsae.toString()); }
+    }
+
+
+    public synchronized int read() throws IOException
+    {
+	int b = in.read();
+	if (b != -1)
+	    md5.update((byte) b);
+	else
+	    real_close();
+
+	rcvd++;
+	return b;
+    }
+
+
+    public synchronized int read(byte[] buf, int off, int len)
+	    throws IOException
+    {
+	int num = in.read(buf, off, len);
+	if (num > 0)
+	    md5.update(buf, off, num);
+	else
+	    real_close();
+
+	rcvd += num;
+	return num;
+    }
+
+
+    public synchronized long skip(long num)  throws IOException
+    {
+	byte[] tmp = new byte[(int) num];
+	int got = read(tmp, 0, (int) num);
+
+	if (got > 0)
+	    return (long) got;
+	else
+	    return 0L;
+    }
+
+
+    /**
+     * Close the stream and check the digest. If the stream has not been
+     * fully read then the rest of the data will first be read (and discarded)
+     * to complete the digest calculation.
+     *
+     * @exception IOException if the close()'ing the underlying stream throws
+     *                        an IOException, or if the expected digest and
+     *                        the calculated digest don't match.
+     */
+    public synchronized void close()  throws IOException
+    {
+	while (skip(10000) > 0) ;
+	real_close();
+    }
+
+
+    /**
+     * Close the stream and check the digest.
+     *
+     * @exception IOException if the close()'ing the underlying stream throws
+     *                        an IOException, or if the expected digest and
+     *                        the calculated digest don't match.
+     */
+    private void real_close()  throws IOException
+    {
+	if (closed)  return;
+	closed = true;
+
+	in.close();
+	verifier.verifyHash(md5.digest(), rcvd);
+    }
+}
diff --git a/HTTPClient/ModuleException.java b/HTTPClient/ModuleException.java
new file mode 100644
index 0000000..7663ab8
--- /dev/null
+++ b/HTTPClient/ModuleException.java
@@ -0,0 +1,66 @@
+/*
+ * @(#)ModuleException.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * Signals that an exception occured in a module.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+public class ModuleException extends Exception
+{
+
+    /**
+     * Constructs an ModuleException with no detail message. A detail
+     * message is a String that describes this particular exception.
+     */
+    public ModuleException()
+    {
+	super();
+    }
+
+
+    /**
+     * Constructs an ModuleException class with the specified detail message.
+     * A detail message is a String that describes this particular exception.
+     *
+     * @param msg the String containing a detail message
+     */
+    public ModuleException(String msg)
+    {
+	super(msg);
+    }
+}
diff --git a/HTTPClient/NVPair.java b/HTTPClient/NVPair.java
new file mode 100644
index 0000000..0badd38
--- /dev/null
+++ b/HTTPClient/NVPair.java
@@ -0,0 +1,110 @@
+/*
+ * @(#)NVPair.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This class holds a Name/Value pair of strings. It's used for headers,
+ * form-data, attribute-lists, etc. This class is immutable.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public final class NVPair
+{
+    /** the name */
+    private String name;
+
+    /** the value */
+    private String value;
+
+
+    // Constructors
+
+    /**
+     * Creates a new name/value pair and initializes it to the
+     * specified name and value.
+     *
+     * @param name  the name
+     * @param value the value
+     */
+    public NVPair(String name, String value)
+    {
+	this.name  = name;
+	this.value = value;
+    }
+
+    /**
+     * Creates a copy of a given name/value pair.
+     *
+     * @param p the name/value pair to copy
+     */
+    public NVPair(NVPair p)
+    {
+	this(p.name, p.value);
+    }
+
+
+    // Methods
+
+    /**
+     * Get the name.
+     *
+     * @return the name
+     */
+    public final String getName()
+    {
+	return name;
+    }
+
+    /**
+     * Get the value.
+     *
+     * @return the value
+     */
+    public final String getValue()
+    {
+	return value;
+    }
+
+
+    /**
+     * Produces a string containing the name and value of this instance.
+     *
+     * @return a string containing the class name and the name and value
+     */
+    public String toString()
+    {
+	return getClass().getName() + "[name=" + name + ",value=" + value + "]";
+    }
+}
diff --git a/HTTPClient/ParseException.java b/HTTPClient/ParseException.java
new file mode 100644
index 0000000..d4bceb0
--- /dev/null
+++ b/HTTPClient/ParseException.java
@@ -0,0 +1,67 @@
+/*
+ * @(#)ParseException.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * Signals that something went wrong while parsing data. Usually means the
+ * input data was invalid.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class ParseException extends Exception
+{
+
+    /**
+     * Constructs an ParseException with no detail message.
+     * A detail message is a String that describes this particular exception.
+     */
+    public ParseException()
+    {
+	super();
+    }
+
+
+    /**
+     * Constructs an ParseException class with the specified detail message.
+     * A detail message is a String that describes this particular exception.
+     *
+     * @param s the String containing a detail message
+     */
+    public ParseException(String s)
+    {
+	super(s);
+    }
+
+}
diff --git a/HTTPClient/ProtocolNotSuppException.java b/HTTPClient/ProtocolNotSuppException.java
new file mode 100644
index 0000000..cca45fe
--- /dev/null
+++ b/HTTPClient/ProtocolNotSuppException.java
@@ -0,0 +1,67 @@
+/*
+ * @(#)ProtocolNotSuppException.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+/**
+ * Signals that the protocol is not supported.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class ProtocolNotSuppException extends IOException
+{
+
+    /**
+     * Constructs an ProtocolNotSuppException with no detail message.
+     * A detail message is a String that describes this particular exception.
+     */
+    public ProtocolNotSuppException()
+    {
+	super();
+    }
+
+
+    /**
+     * Constructs an ProtocolNotSuppException class with the specified
+     * detail message.  A detail message is a String that describes this
+     * particular exception.
+     * @param s the String containing a detail message
+     */
+    public ProtocolNotSuppException(String s)
+    {
+	super(s);
+    }
+
+}
diff --git a/HTTPClient/RedirectionModule.java b/HTTPClient/RedirectionModule.java
new file mode 100644
index 0000000..5d12d40
--- /dev/null
+++ b/HTTPClient/RedirectionModule.java
@@ -0,0 +1,543 @@
+/*
+ * @(#)RedirectionModule.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.net.ProtocolException;
+import java.io.IOException;
+import java.util.Hashtable;
+
+
+/**
+ * This module handles the redirection status codes 301, 302, 303, 305, 306
+ * and 307.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class RedirectionModule implements HTTPClientModule
+{
+    /** a list of permanent redirections (301) */
+    private static Hashtable perm_redir_cntxt_list = new Hashtable();
+
+    /** a list of deferred redirections (used with Response.retryRequest()) */
+    private static Hashtable deferred_redir_list = new Hashtable();
+
+    /** the level of redirection */
+    private int level;
+
+    /** the url used in the last redirection */
+    private URI lastURI;
+
+    /** used for deferred redirection retries */
+    private boolean new_con;
+
+    /** used for deferred redirection retries */
+    private Request saved_req;
+
+
+    // Constructors
+
+    /**
+     * Start with level 0.
+     */
+    RedirectionModule()
+    {
+	level     = 0;
+	lastURI   = null;
+	saved_req = null;
+    }
+
+
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+    {
+	HTTPConnection con = req.getConnection();
+	URI new_loc,
+	    cur_loc;
+
+
+	// check for retries
+
+	HttpOutputStream out = req.getStream();
+	if (out != null  &&  deferred_redir_list.get(out) != null)
+	{
+	    copyFrom((RedirectionModule) deferred_redir_list.remove(out));
+	    req.copyFrom(saved_req);
+
+	    if (new_con)
+		return REQ_NEWCON_RST;
+	    else
+		return REQ_RESTART;
+	}
+
+
+	// handle permanent redirections
+
+	try
+	{
+	    cur_loc = new URI(new URI(con.getProtocol(), con.getHost(), con.getPort(), null),
+			      req.getRequestURI());
+	}
+	catch (ParseException pe)
+	{
+	    throw new Error("HTTPClient Internal Error: unexpected exception '"
+			    + pe + "'");
+	}
+
+
+	// handle permanent redirections
+
+	Hashtable perm_redir_list = Util.getList(perm_redir_cntxt_list,
+					    req.getConnection().getContext());
+	if ((new_loc = (URI) perm_redir_list.get(cur_loc)) != null)
+	{
+	    /* copy query if present in old url but not in new url. This
+	     * isn't strictly conforming, but some scripts fail to properly
+	     * propagate the query string to the Location header.
+	     *
+	     * Unfortunately it looks like we're fucked either way: some
+	     * scripts fail if you don't propagate the query string, some
+	     * fail if you do... God, don't you just love it when people
+	     * can't read a spec? Anway, since we can't get it right for
+	     * all scripts we opt to follow the spec.
+	    String nres    = new_loc.getPathAndQuery(),
+		   oquery  = Util.getQuery(req.getRequestURI()),
+		   nquery  = Util.getQuery(nres);
+	    if (nquery == null  &&  oquery != null)
+		nres += "?" + oquery;
+	     */
+	    String nres = new_loc.getPathAndQuery();
+	    req.setRequestURI(nres);
+
+	    try
+		{ lastURI = new URI(new_loc, nres); }
+	    catch (ParseException pe)
+		{ }
+
+	    Log.write(Log.MODS, "RdirM: matched request in permanent " +
+				"redirection list - redoing request to " +
+				lastURI.toExternalForm());
+
+	    if (!con.isCompatibleWith(new_loc))
+	    {
+		try
+		    { con = new HTTPConnection(new_loc); }
+		catch (Exception e)
+		{
+		    throw new Error("HTTPClient Internal Error: unexpected " +
+				    "exception '" + e + "'");
+		}
+
+		con.setContext(req.getConnection().getContext());
+		req.setConnection(con);
+		return REQ_NEWCON_RST;
+	    }
+	    else
+	    {
+		return REQ_RESTART;
+	    }
+	}
+
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest req)
+	    throws IOException
+    {
+	int sts  = resp.getStatusCode();
+	if (sts < 301  ||  sts > 307  ||  sts == 304)
+	{
+	    if (lastURI != null)		// it's been redirected
+		resp.setEffectiveURI(lastURI);
+	}
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+	    throws IOException
+    {
+	/* handle various response status codes until satisfied */
+
+	int sts  = resp.getStatusCode();
+	switch(sts)
+	{
+	    case 302: // General (temporary) Redirection (handle like 303)
+
+		/* Note we only do this munging for POST and PUT. For GET it's
+		 * not necessary; for HEAD we probably want to do another HEAD.
+		 * For all others (i.e. methods from WebDAV, IPP, etc) it's
+		 * somewhat unclear - servers supporting those should really
+		 * return a 307 or 303, but some don't (guess who...), so we
+		 * just don't touch those.
+		 */
+		if (req.getMethod().equals("POST")  ||
+		    req.getMethod().equals("PUT"))
+		{
+		    Log.write(Log.MODS, "RdirM: Received status: " + sts +
+					" " + resp.getReasonLine() +
+					" - treating as 303");
+
+		    sts = 303;
+		}
+
+	    case 301: // Moved Permanently
+	    case 303: // See Other (use GET)
+	    case 307: // Moved Temporarily (we mean it!)
+
+		Log.write(Log.MODS, "RdirM: Handling status: " + sts +
+				    " " + resp.getReasonLine());
+
+		// the spec says automatic redirection may only be done if
+		// the second request is a HEAD or GET.
+		if (!req.getMethod().equals("GET")  &&
+		    !req.getMethod().equals("HEAD")  &&
+		    sts != 303)
+		{
+		    Log.write(Log.MODS, "RdirM: not redirected because " +
+					"method is neither HEAD nor GET");
+
+		    if (sts == 301  &&  resp.getHeader("Location") != null)
+			update_perm_redir_list(req,
+				    resLocHdr(resp.getHeader("Location"), req));
+
+		    resp.setEffectiveURI(lastURI);
+		    return RSP_CONTINUE;
+		}
+
+	    case 305: // Use Proxy
+	    case 306: // Switch Proxy
+
+		if (sts == 305  ||  sts == 306)
+		    Log.write(Log.MODS, "RdirM: Handling status: " + sts +
+				        " " + resp.getReasonLine());
+
+		// Don't accept 305 from a proxy
+		if (sts == 305  &&  req.getConnection().getProxyHost() != null)
+		{
+		    Log.write(Log.MODS, "RdirM: 305 ignored because " +
+					"a proxy is already in use");
+
+		    resp.setEffectiveURI(lastURI);
+		    return RSP_CONTINUE;
+		}
+
+
+		/* the level is a primitive way of preventing infinite
+		 * redirections. RFC-2068 set the max to 5, but RFC-2616
+		 * has loosened this. Since some sites (notably M$) need
+		 * more levels, this is now set to the (arbitrary) value
+		 * of 15 (god only knows why they need to do even 5
+		 * redirections...).
+		 */
+		if (level >= 15  ||  resp.getHeader("Location") == null)
+		{
+		    if (level >= 15)
+			Log.write(Log.MODS, "RdirM: not redirected because "+
+					    "of too many levels of redirection");
+		    else
+			Log.write(Log.MODS, "RdirM: not redirected because "+
+					    "no Location header was present");
+
+		    resp.setEffectiveURI(lastURI);
+		    return RSP_CONTINUE;
+		}
+		level++;
+
+		URI loc = resLocHdr(resp.getHeader("Location"), req);
+
+		HTTPConnection mvd;
+		String nres;
+		new_con = false;
+
+		if (sts == 305)
+		{
+		    mvd = new HTTPConnection(req.getConnection().getProtocol(),
+					     req.getConnection().getHost(),
+					     req.getConnection().getPort());
+		    mvd.setCurrentProxy(loc.getHost(), loc.getPort());
+		    mvd.setContext(req.getConnection().getContext());
+		    new_con = true;
+
+		    nres = req.getRequestURI();
+
+		    /* There was some discussion about this, and especially
+		     * Foteos Macrides (Lynx) said a 305 should also imply
+		     * a change to GET (for security reasons) - see the thread
+		     * starting at
+		     * http://www.ics.uci.edu/pub/ietf/http/hypermail/1997q4/0351.html
+		     * However, this is not in the latest draft, but since I
+		     * agree with Foteos we do it anyway...
+		     */
+		    req.setMethod("GET");
+		    req.setData(null);
+		    req.setStream(null);
+		}
+		else if (sts == 306)
+		{
+		    // We'll have to wait for Josh to create a new spec here.
+		    return RSP_CONTINUE;
+		}
+		else
+		{
+		    if (req.getConnection().isCompatibleWith(loc))
+		    {
+			mvd  = req.getConnection();
+			nres = loc.getPathAndQuery();
+		    }
+		    else
+		    {
+			try
+			{
+			    mvd  = new HTTPConnection(loc);
+			    nres = loc.getPathAndQuery();
+			}
+			catch (Exception e)
+			{
+			    if (req.getConnection().getProxyHost() == null  ||
+				!loc.getScheme().equalsIgnoreCase("ftp"))
+				return RSP_CONTINUE;
+
+			    // We're using a proxy and the protocol is ftp -
+			    // maybe the proxy will also proxy ftp...
+			    mvd  = new HTTPConnection("http",
+					    req.getConnection().getProxyHost(),
+					    req.getConnection().getProxyPort());
+			    mvd.setCurrentProxy(null, 0);
+			    nres = loc.toExternalForm();
+			}
+
+			mvd.setContext(req.getConnection().getContext());
+			new_con = true;
+		    }
+
+		    /* copy query if present in old url but not in new url.
+		     * This isn't strictly conforming, but some scripts fail
+		     * to propagate the query properly to the Location
+		     * header.
+		     *
+		     * See comment on line 126.
+		    String oquery  = Util.getQuery(req.getRequestURI()),
+			   nquery  = Util.getQuery(nres);
+		    if (nquery == null  &&  oquery != null)
+			nres += "?" + oquery;
+		     */
+
+		    if (sts == 303)
+		    {
+			// 303 means "use GET"
+
+			if (!req.getMethod().equals("HEAD"))
+			    req.setMethod("GET");
+			req.setData(null);
+			req.setStream(null);
+		    }
+		    else
+		    {
+			// If they used an output stream then they'll have
+			// to do the resend themselves
+			if (req.getStream() != null)
+			{
+			    if (!HTTPConnection.deferStreamed)
+			    {
+				Log.write(Log.MODS, "RdirM: status " + sts +
+						    " not handled - request " +
+						    "has an output stream");
+				return RSP_CONTINUE;
+			    }
+
+			    saved_req = (Request) req.clone();
+			    deferred_redir_list.put(req.getStream(), this);
+			    req.getStream().reset();
+			    resp.setRetryRequest(true);
+			}
+
+			if (sts == 301)
+			{
+			    // update permanent redirection list
+			    try
+			    {
+				update_perm_redir_list(req, new URI(loc, nres));
+			    }
+			    catch (ParseException pe)
+			    {
+				throw new Error("HTTPClient Internal Error: " +
+						"unexpected exception '" + pe +
+						"'");
+			    }
+			}
+		    }
+
+		    // Adjust Referer, if present
+		    NVPair[] hdrs = req.getHeaders();
+		    for (int idx=0; idx<hdrs.length; idx++)
+			if (hdrs[idx].getName().equalsIgnoreCase("Referer"))
+			{
+			    HTTPConnection con = req.getConnection();
+			    hdrs[idx] =
+				new NVPair("Referer", con+req.getRequestURI());
+			    break;
+			}
+		}
+
+		req.setConnection(mvd);
+		req.setRequestURI(nres);
+
+		try { resp.getInputStream().close(); }
+		catch (IOException ioe) { }
+
+		if (sts != 305  &&  sts != 306)
+		{
+		    try
+			{ lastURI = new URI(loc, nres); }
+		    catch (ParseException pe)
+			{ /* ??? */ }
+
+		    Log.write(Log.MODS, "RdirM: request redirected to " +
+					lastURI.toExternalForm() +
+					" using method " + req.getMethod());
+		}
+		else
+		{
+		    Log.write(Log.MODS, "RdirM: resending request using " +
+					"proxy " + mvd.getProxyHost() +
+					":" + mvd.getProxyPort());
+		}
+
+		if (req.getStream() != null)
+		    return RSP_CONTINUE;
+		else if (new_con)
+		    return RSP_NEWCON_REQ;
+		else
+		    return RSP_REQUEST;
+
+	    default:
+
+		return RSP_CONTINUE;
+	}
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Update the permanent redirection list.
+     *
+     * @param the original request
+     * @param the new location
+     */
+    private static void update_perm_redir_list(RoRequest req, URI new_loc)
+    {
+	HTTPConnection con = req.getConnection();
+	URI cur_loc = null;
+	try
+	{
+	    cur_loc = new URI(new URI(con.getProtocol(), con.getHost(), con.getPort(), null),
+			      req.getRequestURI());
+	}
+	catch (ParseException pe)
+	    { }
+
+	if (!cur_loc.equals(new_loc))
+	{
+	    Hashtable perm_redir_list =
+			Util.getList(perm_redir_cntxt_list, con.getContext());
+	    perm_redir_list.put(cur_loc, new_loc);
+	}
+    }
+
+
+    /**
+     * The Location header field must be an absolute URI, but too many broken
+     * servers use relative URIs. So, we always resolve relative to the
+     * full request URI.
+     *
+     * @param  loc the Location header field
+     * @param  req the Request to resolve relative URI's relative to
+     * @return an absolute URI corresponding to the Location header field
+     * @exception ProtocolException if the Location header field is completely
+     *                            unparseable
+     */
+    private URI resLocHdr(String loc, RoRequest req)  throws ProtocolException
+    {
+	try
+	{
+	    URI base = new URI(req.getConnection().getProtocol(),
+			       req.getConnection().getHost(),
+			       req.getConnection().getPort(), null);
+	    base = new URI(base, req.getRequestURI());
+	    URI res = new URI(base, loc);
+	    if (res.getHost() == null)
+		throw new ProtocolException("Malformed URL in Location header: `" + loc +
+					    "' - missing host field");
+	    return res;
+	}
+	catch (ParseException pe)
+	{
+	    throw new ProtocolException("Malformed URL in Location header: `" + loc +
+					"' - exception was: " + pe.getMessage());
+	}
+    }
+
+
+    private void copyFrom(RedirectionModule other)
+    {
+	this.level     = other.level;
+	this.lastURI   = other.lastURI;
+	this.saved_req = other.saved_req;
+    }
+}
diff --git a/HTTPClient/Request.java b/HTTPClient/Request.java
new file mode 100644
index 0000000..d000212
--- /dev/null
+++ b/HTTPClient/Request.java
@@ -0,0 +1,306 @@
+/*
+ * @(#)Request.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This class represents an http request. It's used by classes which
+ * implement the HTTPClientModule interface.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public final class Request implements RoRequest, Cloneable
+{
+    /** null headers */
+    private static final NVPair[] empty = new NVPair[0];
+
+    /** the current HTTPConnection */
+    private HTTPConnection connection;
+
+    /** the request method to be used (e.g. GET, POST, etc) */
+    private String         method;
+
+    /** the request-uri */
+    private String         req_uri;
+
+    /** the headers to be used */
+    private NVPair[]       headers;
+
+    /** the entity (if any) */
+    private byte[]         data;
+
+    /** or an output stream on which the entity will be written */
+    private HttpOutputStream stream;
+
+    /** are modules allowed to popup windows or otherwise prompt user? */
+    private boolean        allow_ui;
+
+    /** number of millisecs to wait for an error from the server before sending
+	the entity (used when retrying requests) */
+            long           delay_entity = 0;
+
+    /** number of retries so far */
+            int            num_retries = 0;
+
+    /** disable pipelining of following request */
+            boolean        dont_pipeline = false;
+
+    /** was this request aborted by the user? */
+            boolean        aborted = false;
+
+    /** is this an internally generated subrequest? */
+            boolean        internal_subrequest = false;
+
+
+    // Constructors
+
+    /**
+     * Creates a new request structure.
+     *
+     * @param con      the current HTTPConnection
+     * @param method   the request method
+     * @param req_uri  the request-uri
+     * @param headers  the request headers
+     * @param data     the entity as a byte[]
+     * @param stream   the entity as a stream
+     * @param allow_ui allow user interaction
+     */
+    public Request(HTTPConnection con, String method, String req_uri,
+		   NVPair[] headers, byte[] data, HttpOutputStream stream,
+		   boolean allow_ui)
+    {
+	this.connection = con;
+	this.method     = method;
+	setRequestURI(req_uri);
+	setHeaders(headers);
+	this.data       = data;
+	this.stream     = stream;
+	this.allow_ui   = allow_ui;
+    }
+
+
+    // Methods
+
+    /**
+     * @return the HTTPConnection this request is associated with
+     */
+    public HTTPConnection getConnection()
+    {
+	return connection;
+    }
+
+    /**
+     * @param con the HTTPConnection this request is associated with
+     */
+    public void setConnection(HTTPConnection  con)
+    {
+	this.connection = con;
+    }
+
+
+    /**
+     * @return the request method
+     */
+    public String getMethod()
+    {
+	return method;
+    }
+
+    /**
+     * @param method the request method (e.g. GET, POST, etc)
+     */
+    public void setMethod(String method)
+    {
+	this.method = method;
+    }
+
+
+    /**
+     * @return the request-uri
+     */
+    public String getRequestURI()
+    {
+	return req_uri;
+    }
+
+    /**
+     * @param req_uri the request-uri
+     */
+    public void setRequestURI(String req_uri)
+    {
+	if (req_uri != null  &&  req_uri.trim().length() > 0)
+	{
+	    req_uri = req_uri.trim();
+	    if (req_uri.charAt(0) != '/'  &&  !req_uri.equals("*")  &&
+		!method.equals("CONNECT")  &&  !isAbsolute(req_uri))
+		req_uri = "/" + req_uri;
+	    this.req_uri = req_uri;
+	}
+	else
+	    this.req_uri = "/";
+    }
+
+    private static final boolean isAbsolute(String uri)
+    {
+	char ch = '\0';
+	int  pos = 0, len = uri.length();
+	while (pos < len  &&  (ch = uri.charAt(pos)) != ':'  &&
+	       ch != '/'  &&  ch != '?'  &&  ch != '#')
+	  pos++;
+
+	return (ch == ':');
+    }
+
+
+    /**
+     * @return the headers making up this request
+     */
+    public NVPair[] getHeaders()
+    {
+	return headers;
+    }
+
+    /**
+     * @param headers the headers for this request
+     */
+    public void setHeaders(NVPair[] headers)
+    {
+	if (headers != null)
+	    this.headers = headers;
+	else
+	    this.headers = empty;
+    }
+
+
+    /**
+     * @return the body of this request
+     */
+    public byte[] getData()
+    {
+	return data;
+    }
+
+    /**
+     * @param data the entity for this request
+     */
+    public void setData(byte[] data)
+    {
+	this.data = data;
+    }
+
+
+    /**
+     * @return the output stream on which the body is written
+     */
+    public HttpOutputStream getStream()
+    {
+	return stream;
+    }
+
+    /**
+     * @param stream an output stream on which the entity is written
+     */
+    public void setStream(HttpOutputStream stream)
+    {
+	this.stream = stream;
+    }
+
+
+    /**
+     * @return true if the modules or handlers for this request may popup
+     *         windows or otherwise interact with the user
+     */
+    public boolean allowUI()
+    {
+	return allow_ui;
+    }
+
+    /**
+     * @param allow_ui are modules and handlers allowed to popup windows or
+     *                otherwise interact with the user?
+     */
+    public void setAllowUI(boolean allow_ui)
+    {
+	this.allow_ui = allow_ui;
+    }
+
+
+    /**
+     * @return a clone of this request object
+     */
+    public Object clone()
+    {
+	Request cl;
+	try
+	    { cl = (Request) super.clone(); }
+	catch (CloneNotSupportedException cnse)
+	    { throw new InternalError(cnse.toString()); /* shouldn't happen */ }
+
+	cl.headers = new NVPair[headers.length];
+	System.arraycopy(headers, 0, cl.headers, 0, headers.length);
+
+	return cl;
+    }
+
+
+    /**
+     * Copy all the fields from <var>other</var> to this request.
+     *
+     * @param other the Request to copy from
+     */
+    public void copyFrom(Request other)
+    {
+	this.connection          = other.connection;
+	this.method              = other.method;
+	this.req_uri             = other.req_uri;
+	this.headers             = other.headers;
+	this.data                = other.data;
+	this.stream              = other.stream;
+	this.allow_ui            = other.allow_ui;
+	this.delay_entity        = other.delay_entity;
+	this.num_retries         = other.num_retries;
+	this.dont_pipeline       = other.dont_pipeline;
+	this.aborted             = other.aborted;
+	this.internal_subrequest = other.internal_subrequest;
+    }
+
+
+    /**
+     * @return a string containing the method and request-uri
+     */
+    public String toString()
+    {
+	return getClass().getName() + ": " + method + " " + req_uri;
+    }
+}
diff --git a/HTTPClient/RespInputStream.java b/HTTPClient/RespInputStream.java
new file mode 100644
index 0000000..8dbb759
--- /dev/null
+++ b/HTTPClient/RespInputStream.java
@@ -0,0 +1,345 @@
+/*
+ * @(#)RespInputStream.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+/**
+ * This is the InputStream that gets returned to the user. The extensions
+ * consist of the capability to have the data pushed into a buffer if the
+ * stream demux needs to.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.2
+ */
+final class RespInputStream extends InputStream implements GlobalConstants
+{
+    /** Use old behaviour: don't set a timeout when reading the response body */
+    private static boolean dontTimeoutBody = false;
+
+    /** the stream demultiplexor */
+    private StreamDemultiplexor demux = null;
+
+    /** our response handler */
+    private ResponseHandler     resph;
+
+    /** signals that the user has closed the stream and will therefore
+	not read any further data */
+	    boolean             closed = false;
+
+    /** signals that the connection may not be closed prematurely */
+    private boolean             dont_truncate = false;
+
+    /** this buffer is used to buffer data that the demux has to get rid of */
+    private byte[]              buffer = null;
+
+    /** signals that we were interrupted and that the buffer is not complete */
+    private boolean             interrupted = false;
+
+    /** the offset at which the unread data starts in the buffer */
+    private int                 offset = 0;
+
+    /** the end of the data in the buffer */
+    private int                 end = 0;
+
+    /** the total number of bytes of entity data read from the demux so far */
+            int                 count = 0;
+
+    static
+    {
+	try
+	{
+	    dontTimeoutBody = Boolean.getBoolean("HTTPClient.dontTimeoutRespBody");
+	    if (dontTimeoutBody)
+		Log.write(Log.DEMUX, "RspIS: disabling timeouts when " +
+				     "reading response body");
+	}
+	catch (Exception e)
+	    { }
+    }
+
+
+    // Constructors
+
+    RespInputStream(StreamDemultiplexor demux, ResponseHandler resph)
+    {
+	this.demux = demux;
+	this.resph = resph;
+    }
+
+
+    // public Methods
+
+    private byte[] ch = new byte[1];
+    /**
+     * Reads a single byte.
+     *
+     * @return the byte read, or -1 if EOF.
+     * @exception IOException if any exception occured on the connection.
+     */
+    public synchronized int read() throws IOException
+    {
+	int rcvd = read(ch, 0, 1);
+	if (rcvd == 1)
+	    return ch[0] & 0xff;
+	else
+	    return -1;
+    }
+
+
+    /**
+     * Reads <var>len</var> bytes into <var>b</var>, starting at offset
+     * <var>off</var>.
+     *
+     * @return the number of bytes actually read, or -1 if EOF.
+     * @exception IOException if any exception occured on the connection.
+     */
+    public synchronized int read(byte[] b, int off, int len) throws IOException
+    {
+	if (closed)
+	    return -1;
+
+	int left = end - offset;
+	if (buffer != null  &&  !(left == 0  &&  interrupted))
+	{
+	    if (left == 0)  return -1;
+
+	    len = (len > left ? left : len);
+	    System.arraycopy(buffer, offset, b, off, len);
+	    offset += len;
+
+	    return len;
+	}
+	else
+	{
+	    if (resph.resp.cd_type != CD_HDRS)
+		Log.write(Log.DEMUX, "RspIS: Reading stream " + this.hashCode());
+
+	    int rcvd;
+	    if (dontTimeoutBody  &&  resph.resp.cd_type != CD_HDRS)
+		rcvd = demux.read(b, off, len, resph, 0);
+	    else
+		rcvd = demux.read(b, off, len, resph, resph.resp.timeout);
+	    if (rcvd != -1  &&  resph.resp.got_headers)
+		count += rcvd;
+
+	    return rcvd;
+	}
+    }
+
+
+    /**
+     * skips <var>num</var> bytes.
+     *
+     * @return the number of bytes actually skipped.
+     * @exception IOException if any exception occured on the connection.
+     */
+    public synchronized long skip(long num) throws IOException
+    {
+	if (closed)
+	    return 0;
+
+	int left = end - offset;
+	if (buffer != null  &&  !(left == 0  &&  interrupted))
+	{
+	    num = (num > left ? left : num);
+	    offset  += num;
+	    return num;
+	}
+	else
+	{
+	    long skpd = demux.skip(num, resph);
+	    if (resph.resp.got_headers)
+		count += skpd;
+	    return skpd;
+	}
+    }
+
+
+    /**
+     * gets the number of bytes available for reading without blocking.
+     *
+     * @return the number of bytes available.
+     * @exception IOException if any exception occured on the connection.
+     */
+    public synchronized int available() throws IOException
+    {
+	if (closed)
+	    return 0;
+
+	if (buffer != null  &&  !(end-offset == 0  &&  interrupted))
+	    return end-offset;
+	else
+	    return demux.available(resph);
+    }
+
+
+    /**
+     * closes the stream.
+     *
+     * @exception if any exception occured on the connection before or
+     *            during close.
+     */
+    public synchronized void close()  throws IOException
+    {
+	if (!closed)
+	{
+	    closed = true;
+
+	    if (dont_truncate  &&  (buffer == null  ||  interrupted))
+		readAll(resph.resp.timeout);
+
+	    Log.write(Log.DEMUX, "RspIS: User closed stream " + hashCode());
+
+	    demux.closeSocketIfAllStreamsClosed();
+
+	    if (dont_truncate)
+	    {
+		try
+		    { resph.resp.http_resp.invokeTrailerHandlers(false); }
+		catch (ModuleException me)
+		    { throw new IOException(me.toString()); }
+	    }
+	}
+    }
+
+
+    /**
+     * A safety net to clean up.
+     */
+    protected void finalize()  throws Throwable
+    {
+	try
+	    { close(); }
+	finally
+	    { super.finalize(); }
+    }
+
+
+    // local Methods
+
+    /**
+     * Reads all remainings data into buffer. This is used to force a read
+     * of upstream responses.
+     *
+     * <P>This is probably the most tricky and buggy method around. It's the
+     * only one that really violates the strict top-down method invocation
+     * from the Response through the ResponseStream to the StreamDemultiplexor.
+     * This means we need to be awfully careful about what is synchronized
+     * and what parameters are passed to whom.
+     *
+     * @param timeout the timeout to use for reading from the demux
+     * @exception IOException If any exception occurs while reading stream.
+     */
+    void readAll(int timeout)  throws IOException
+    {
+	Log.write(Log.DEMUX, "RspIS: Read-all on stream " + this.hashCode());
+
+	synchronized (resph.resp)
+	{
+	    if (!resph.resp.got_headers)	// force headers to be read
+	    {
+		int sav_to = resph.resp.timeout;
+		resph.resp.timeout = timeout;
+		resph.resp.getStatusCode();
+		resph.resp.timeout = sav_to;
+	    }
+	}
+
+	synchronized (this)
+	{
+	    if (buffer != null  &&  !interrupted)  return;
+
+	    int rcvd = 0;
+	    try
+	    {
+		if (closed)			// throw away
+		{
+		    buffer = new byte[10000];
+		    do
+		    {
+			count += rcvd;
+			rcvd   = demux.read(buffer, 0, buffer.length, resph,
+					    timeout);
+		    } while (rcvd != -1);
+		    buffer = null;
+		}
+		else
+		{
+		    if (buffer == null)
+		    {
+			buffer = new byte[10000];
+			offset = 0;
+			end    = 0;
+		    }
+
+		    do
+		    {
+			rcvd = demux.read(buffer, end, buffer.length-end, resph,
+					  timeout);
+			if (rcvd < 0)  break;
+
+			count  += rcvd;
+			end    += rcvd;
+			buffer  = Util.resizeArray(buffer, end+10000);
+		    } while (true);
+		}
+	    }
+	    catch (InterruptedIOException iioe)
+	    {
+		interrupted = true;
+		throw iioe;
+	    }
+	    catch (IOException ioe)
+	    {
+		buffer = null;	// force a read on demux for exception
+	    }
+
+	    interrupted = false;
+	}
+    }
+
+
+    /**
+     * Sometime the full response body must be read, i.e. the connection may
+     * not be closed prematurely (by us). Currently this is needed when the
+     * chunked encoding with trailers is used in a response.
+     */
+    synchronized void dontTruncate()
+    {
+	dont_truncate = true;
+    }
+}
diff --git a/HTTPClient/Response.java b/HTTPClient/Response.java
new file mode 100644
index 0000000..aa5b483
--- /dev/null
+++ b/HTTPClient/Response.java
@@ -0,0 +1,1420 @@
+/*
+ * @(#)Response.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001  Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ */
+
+package HTTPClient;
+
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.EOFException;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.ProtocolException;
+import java.util.Date;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.util.NoSuchElementException;
+
+
+/**
+ * This class represents an intermediate response. It's used internally by the
+ * modules. When all modules have handled the response then the HTTPResponse
+ * fills in its fields with the data from this class.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public final class Response implements RoResponse, GlobalConstants, Cloneable
+{
+    /** This contains a list of headers which may only have a single value */
+    private static final Hashtable singleValueHeaders;
+
+    /** our http connection */
+    private HTTPConnection connection;
+
+    /** our stream demux */
+    private StreamDemultiplexor stream_handler;
+
+    /** the HTTPResponse we're coupled with */
+            HTTPResponse http_resp;
+
+    /** the timeout for read operations */
+            int          timeout = 0;
+
+    /** our input stream (usually from the stream demux). Push input streams
+     *  onto this if necessary. */
+    public  InputStream  inp_stream;
+
+    /** our response input stream from the stream demux */
+    private RespInputStream  resp_inp_stream = null;
+
+    /** the method used in the request */
+    private String       method;
+
+    /** the resource in the request (for debugging purposes) */
+            String       resource;
+
+    /** was a proxy used for the request? */
+    private boolean      used_proxy;
+
+    /** did the request contain an entity? */
+    private boolean      sent_entity;
+
+    /** the status code returned. */
+            int          StatusCode = 0;
+
+    /** the reason line associated with the status code. */
+            String       ReasonLine;
+
+    /** the HTTP version of the response. */
+            String       Version;
+
+    /** the final URI of the document. */
+            URI          EffectiveURI = null;
+
+    /** any headers which were received and do not fit in the above list. */
+            CIHashtable  Headers = new CIHashtable();
+
+    /** any trailers which were received and do not fit in the above list. */
+            CIHashtable  Trailers = new CIHashtable();
+
+    /** the message length of the response if either there is no data (in which
+     *  case ContentLength=0) or if the message length is controlled by a
+     *  Content-Length header. If neither of these, then it's -1  */
+            int          ContentLength = -1;
+
+    /** this indicates how the length of the entity body is determined */
+            int          cd_type = CD_HDRS;
+
+    /** the data (body) returned. */
+            byte[]       Data = null;
+
+    /** signals if we in the process of reading the headers */
+            boolean      reading_headers = false;
+
+    /** signals if we have got and parsed the headers yet */
+            boolean      got_headers = false;
+
+    /** signals if we have got and parsed the trailers yet */
+            boolean      got_trailers = false;
+
+    /** remembers any exception received while reading/parsing headers */
+    private IOException  exception = null;
+
+    /** should this response be handled further? */
+            boolean      final_resp = false;
+
+    /** should the request be retried by the application? */
+            boolean      retry = false;
+
+
+    static
+    {
+	/* This static initializer creates a hashtable of header names that
+	 * should only have at most a single value in a server response. Other
+	 * headers that may have multiple values (ie Set-Cookie) will have
+	 * their values combined into one header, with individual values being
+	 * separated by commas.
+	 */
+	String[] singleValueHeaderNames = {
+	    "age", "location", "content-base", "content-length",
+	    "content-location", "content-md5", "content-range", "content-type",
+	    "date", "etag", "expires", "proxy-authenticate", "retry-after",
+	};
+
+	singleValueHeaders = new Hashtable(singleValueHeaderNames.length);
+	for (int idx=0; idx<singleValueHeaderNames.length; idx++)
+	  singleValueHeaders.put(singleValueHeaderNames[idx],
+				 singleValueHeaderNames[idx]);
+    }
+
+
+    // Constructors
+
+    /**
+     * Creates a new Response and registers it with the stream-demultiplexor.
+     */
+    Response(Request request, boolean used_proxy,
+	     StreamDemultiplexor stream_handler)
+	    throws IOException
+    {
+	this.connection     = request.getConnection();
+	this.method         = request.getMethod();
+	this.resource       = request.getRequestURI();
+	this.used_proxy     = used_proxy;
+	this.stream_handler = stream_handler;
+	sent_entity         = (request.getData() != null) ? true : false;
+
+	stream_handler.register(this, request);
+	resp_inp_stream     = stream_handler.getStream(this);
+	inp_stream          = resp_inp_stream;
+    }
+
+
+    /**
+     * Creates a new Response that reads from the given stream. This is
+     * used for the CONNECT subrequest which is used in establishing an
+     * SSL tunnel through a proxy.
+     *
+     * @param request the subrequest
+     * @param is      the input stream from which to read the headers and
+     *                data.
+     */
+    Response(Request request, InputStream is) throws IOException
+    {
+	this.connection = request.getConnection();
+	this.method     = request.getMethod();
+	this.resource   = request.getRequestURI();
+	used_proxy      = false;
+	stream_handler  = null;
+	sent_entity     = (request.getData() != null) ? true : false;
+	inp_stream      = is;
+    }
+
+
+    /**
+     * Create a new response with the given info. This is used when
+     * creating a response in a requestHandler().
+     *
+     * <P>If <var>data</var> is not null then that is used; else if the
+     * <var>is</var> is not null that is used; else the entity is empty.
+     * If the input stream is used then <var>cont_len</var> specifies
+     * the length of the data that can be read from it, or -1 if unknown.
+     *
+     * @param version  the response version (such as "HTTP/1.1")
+     * @param status   the status code
+     * @param reason   the reason line
+     * @param headers  the response headers
+     * @param data     the response entity
+     * @param is       the response entity as an InputStream
+     * @param cont_len the length of the data in the InputStream
+     */
+    public Response(String version, int status, String reason, NVPair[] headers,
+		    byte[] data, InputStream is, int cont_len)
+    {
+	this.Version    = version;
+	this.StatusCode = status;
+	this.ReasonLine = reason;
+	if (headers != null)
+	    for (int idx=0; idx<headers.length; idx++)
+		setHeader(headers[idx].getName(), headers[idx].getValue());
+	if (data != null)
+	    this.Data   = data;
+	else if (is == null)
+	    this.Data   = new byte[0];
+	else
+	{
+	    this.inp_stream = is;
+	    ContentLength   = cont_len;
+	}
+
+	got_headers  = true;
+	got_trailers = true;
+    }
+
+
+    // Methods
+
+    /**
+     * give the status code for this request. These are grouped as follows:
+     * <UL>
+     *   <LI> 1xx - Informational (new in HTTP/1.1)
+     *   <LI> 2xx - Success
+     *   <LI> 3xx - Redirection
+     *   <LI> 4xx - Client Error
+     *   <LI> 5xx - Server Error
+     * </UL>
+     *
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public final int getStatusCode()  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+	return StatusCode;
+    }
+
+    /**
+     * give the reason line associated with the status code.
+     *
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public final String getReasonLine()  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+	return ReasonLine;
+    }
+
+    /**
+     * get the HTTP version used for the response.
+     *
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public final String getVersion()  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+	return Version;
+    }
+
+    /**
+     * Wait for either a '100 Continue' or an error.
+     *
+     * @return the return status.
+     */
+    int getContinue()  throws IOException
+    {
+	getHeaders(false);
+	return StatusCode;
+    }
+
+    /**
+     * get the final URI of the document. This is set if the original
+     * request was deferred via the "moved" (301, 302, or 303) return
+     * status.
+     *
+     * @return the new URI, or null if not redirected
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public final URI getEffectiveURI()  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+	return EffectiveURI;
+    }
+
+    /**
+     * set the final URI of the document. This is only for internal use.
+     */
+    public void setEffectiveURI(URI final_uri)
+    {
+	EffectiveURI = final_uri;
+    }
+
+    /**
+     * get the final URL of the document. This is set if the original
+     * request was deferred via the "moved" (301, 302, or 303) return
+     * status.
+     *
+     * @exception IOException If any exception occurs on the socket.
+     * @deprecated use getEffectiveURI() instead
+     * @see #getEffectiveURI
+     */
+    public final URL getEffectiveURL()  throws IOException
+    {
+	return getEffectiveURI().toURL();
+    }
+
+    /**
+     * set the final URL of the document. This is only for internal use.
+     *
+     * @deprecated use setEffectiveURI() instead
+     * @see #setEffectiveURI
+     */
+    public void setEffectiveURL(URL final_url)
+    {
+	try
+	    { setEffectiveURI(new URI(final_url)); }
+	catch (ParseException pe)
+	    { throw new Error(pe.toString()); }		// shouldn't happen
+    }
+
+    /**
+     * retrieves the field for a given header.
+     *
+     * @param  hdr the header name.
+     * @return the value for the header, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public String getHeader(String hdr)  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+	return (String) Headers.get(hdr.trim());
+    }
+
+    /**
+     * retrieves the field for a given header. The value is parsed as an
+     * int.
+     *
+     * @param  hdr the header name.
+     * @return the value for the header if the header exists
+     * @exception NumberFormatException if the header's value is not a number
+     *                                  or if the header does not exist.
+     * @exception IOException if any exception occurs on the socket.
+     */
+    public int getHeaderAsInt(String hdr)
+		throws IOException, NumberFormatException
+    {
+	String val = getHeader(hdr);
+	if (val == null)
+	    throw new NumberFormatException("null");
+	return Integer.parseInt(val);
+    }
+
+    /**
+     * retrieves the field for a given header. The value is parsed as a
+     * date; if this fails it is parsed as a long representing the number
+     * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
+     * IllegalArgumentException is thrown.
+     * 
+     * <P>Note: When sending dates use Util.httpDate().
+     *
+     * @param  hdr the header name.
+     * @return the value for the header, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     * @exception IllegalArgumentException If the header cannot be parsed
+     *            as a date or time.
+     */
+    public Date getHeaderAsDate(String hdr)
+		throws IOException, IllegalArgumentException
+    {
+	String raw_date = getHeader(hdr);
+	if (raw_date == null)  return null;
+
+	// asctime() format is missing an explicit GMT specifier
+	if (raw_date.toUpperCase().indexOf("GMT") == -1  &&
+	    raw_date.indexOf(' ') > 0)
+	    raw_date += " GMT";
+
+	Date date;
+
+	try
+	    { date = Util.parseHttpDate(raw_date); }
+	catch (IllegalArgumentException iae)
+	{
+	    long time;
+	    try
+		{ time = Long.parseLong(raw_date); }
+	    catch (NumberFormatException nfe)
+		{ throw iae; }
+	    if (time < 0)  time = 0;
+	    date = new Date(time * 1000L);
+	}
+
+	return date;
+    }
+
+
+    /**
+     * Set a header field in the list of headers. If the header already
+     * exists it will be overwritten; otherwise the header will be added
+     * to the list. This is used by some modules when they process the
+     * header so that higher level stuff doesn't get confused when the
+     * headers and data don't match.
+     *
+     * @param header The name of header field to set.
+     * @param value  The value to set the field to.
+     */
+    public void setHeader(String header, String value)
+    {
+	Headers.put(header.trim(), value.trim());
+    }
+
+
+    /**
+     * Removes a header field from the list of headers. This is used by
+     * some modules when they process the header so that higher level stuff
+     * doesn't get confused when the headers and data don't match.
+     *
+     * @param header The name of header field to remove.
+     */
+    public void deleteHeader(String header)
+    {
+	Headers.remove(header.trim());
+    }
+
+
+    /**
+     * Retrieves the field for a given trailer. Note that this should not
+     * be invoked until all the response data has been read. If invoked
+     * before, it will force the data to be read via <code>getData()</code>.
+     *
+     * @param  trailer the trailer name.
+     * @return the value for the trailer, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public String getTrailer(String trailer)  throws IOException
+    {
+	if (!got_trailers)  getTrailers();
+	return (String) Trailers.get(trailer.trim());
+    }
+
+
+    /**
+     * Retrieves the field for a given tailer. The value is parsed as an
+     * int.
+     *
+     * @param  trailer the tailer name.
+     * @return the value for the trailer if the trailer exists
+     * @exception NumberFormatException if the trailer's value is not a number
+     *                                  or if the trailer does not exist.
+     * @exception IOException if any exception occurs on the socket.
+     */
+    public int getTrailerAsInt(String trailer)
+		throws IOException, NumberFormatException
+    {
+	String val = getTrailer(trailer);
+	if (val == null)
+	    throw new NumberFormatException("null");
+	return Integer.parseInt(val);
+    }
+
+
+    /**
+     * Retrieves the field for a given trailer. The value is parsed as a
+     * date; if this fails it is parsed as a long representing the number
+     * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
+     * IllegalArgumentException is thrown.
+     *
+     * <P>Note: When sending dates use Util.httpDate().
+     *
+     * @param  trailer the trailer name.
+     * @return the value for the trailer, or null if non-existent.
+     * @exception IllegalArgumentException if the trailer's value is neither a
+     *            legal date nor a number.
+     * @exception IOException if any exception occurs on the socket.
+     * @exception IllegalArgumentException If the header cannot be parsed
+     *            as a date or time.
+     */
+    public Date getTrailerAsDate(String trailer)
+		throws IOException, IllegalArgumentException
+    {
+	String raw_date = getTrailer(trailer);
+	if (raw_date == null) return null;
+
+	// asctime() format is missing an explicit GMT specifier
+	if (raw_date.toUpperCase().indexOf("GMT") == -1  &&
+	    raw_date.indexOf(' ') > 0)
+	    raw_date += " GMT";
+
+	Date   date;
+
+	try
+	    { date = Util.parseHttpDate(raw_date); }
+	catch (IllegalArgumentException iae)
+	{
+	    // some servers erroneously send a number, so let's try that
+	    long time;
+	    try
+		{ time = Long.parseLong(raw_date); }
+	    catch (NumberFormatException nfe)
+		{ throw iae; }	// give up
+	    if (time < 0)  time = 0;
+	    date = new Date(time * 1000L);
+	}
+
+	return date;
+    }
+
+
+    /**
+     * Set a trailer field in the list of trailers. If the trailer already
+     * exists it will be overwritten; otherwise the trailer will be added
+     * to the list. This is used by some modules when they process the
+     * trailer so that higher level stuff doesn't get confused when the
+     * trailer and data don't match.
+     *
+     * @param trailer The name of trailer field to set.
+     * @param value   The value to set the field to.
+     */
+    public void setTrailer(String trailer, String value)
+    {
+	Trailers.put(trailer.trim(), value.trim());
+    }
+
+
+    /**
+     * Removes a trailer field from the list of trailers. This is used by
+     * some modules when they process the trailer so that higher level stuff
+     * doesn't get confused when the trailers and data don't match.
+     *
+     * @param trailer The name of trailer field to remove.
+     */
+    public void deleteTrailer(String trailer)
+    {
+	Trailers.remove(trailer.trim());
+    }
+
+
+    /**
+     * Reads all the response data into a byte array. Note that this method
+     * won't return until <em>all</em> the data has been received (so for
+     * instance don't invoke this method if the server is doing a server
+     * push). If getInputStream() had been previously called then this method
+     * only returns any unread data remaining on the stream and then closes
+     * it.
+     *
+     * @see #getInputStream()
+     * @return an array containing the data (body) returned. If no data
+     *         was returned then it's set to a zero-length array.
+     * @exception IOException If any io exception occured while reading
+     *			      the data
+     */
+    public synchronized byte[] getData()  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+
+	if (Data == null)
+	{
+	    try
+		{ readResponseData(inp_stream); }
+	    catch (InterruptedIOException ie)		// don't intercept
+		{ throw ie; }
+	    catch (IOException ioe)
+	    {
+		Log.write(Log.RESP, "Resp:  (" + inp_stream.hashCode() + ")",
+			  ioe);
+
+		try { inp_stream.close(); } catch (Exception e) { }
+		throw ioe;
+	    }
+
+	    inp_stream.close();
+	}
+
+	return Data;
+    }
+
+    /**
+     * Gets an input stream from which the returned data can be read. Note
+     * that if getData() had been previously called it will actually return
+     * a ByteArrayInputStream created from that data.
+     *
+     * @see #getData()
+     * @return the InputStream.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public synchronized InputStream getInputStream()  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+
+	if (Data == null)
+	    return inp_stream;
+	else
+	    return new ByteArrayInputStream(Data);
+    }
+
+    /**
+     * Some responses such as those from a HEAD or with certain status
+     * codes don't have an entity. This is detected by the client and
+     * can be queried here. Note that this won't try to do a read() on
+     * the input stream (it will however cause the headers to be read
+     * and parsed if not already done).
+     *
+     * @return true if the response has an entity, false otherwise
+     * @since V0.3-1
+     */
+    public synchronized boolean hasEntity()  throws IOException
+    {
+	if (!got_headers)  getHeaders(true);
+
+	return (cd_type != CD_0);
+    }
+
+    /**
+     * Should the request be retried by the application? This can be used
+     * by modules to signal to the application that it should retry the
+     * request. It's used when the request used an <var>HttpOutputStream</var>
+     * and the module is therefore not able to retry the request itself.
+     * This flag is <var>false</var> by default.
+     *
+     * <P>If a module sets this flag then it must also reset() the
+     * the <var>HttpOutputStream</var> so it may be reused by the application.
+     * It should then also use this <var>HttpOutputStream</var> to recognize
+     * the retried request in the requestHandler().
+     *
+     * @param flag indicates whether the application should retry the request.
+     */
+    public void setRetryRequest(boolean flag)
+    {
+	retry = flag;
+    }
+
+    /**
+     * @return true if the request should be retried.
+     */
+    public boolean retryRequest()
+    {
+	return retry;
+    }
+
+
+    // Helper Methods
+
+    /**
+     * Gets and parses the headers. Sets up Data if no data will be received.
+     *
+     * @param skip_cont  if true skips over '100 Continue' status codes.
+     * @exception IOException If any exception occurs while reading the headers.
+     */
+    private synchronized void getHeaders(boolean skip_cont)  throws IOException
+    {
+	if (got_headers)  return;
+	if (exception != null)
+	{
+	    exception.fillInStackTrace();
+	    throw exception;
+	}
+
+	reading_headers = true;
+	try
+	{
+	    do
+	    {
+		Headers.clear();	// clear any headers from 100 Continue
+		String headers = readResponseHeaders(inp_stream);
+		parseResponseHeaders(headers);
+	    } while ((StatusCode == 100  &&  skip_cont)  ||	// Continue
+		     (StatusCode > 101  &&  StatusCode < 200));	// Unknown
+	}
+	catch (IOException ioe)
+	{
+	    if (!(ioe instanceof InterruptedIOException))
+		exception = ioe;
+	    if (ioe instanceof ProtocolException)	// thrown internally
+	    {
+		cd_type = CD_CLOSE;
+		if (stream_handler != null)
+		    stream_handler.markForClose(this);
+	    }
+	    throw ioe;
+	}
+	finally
+	    { reading_headers = false; }
+	if (StatusCode == 100) return;
+
+
+	// parse the Content-Length header
+
+	int cont_len = -1;
+	String cl_hdr = (String) Headers.get("Content-Length");
+	if (cl_hdr != null)
+	{
+	    try
+	    {
+		cont_len = Integer.parseInt(cl_hdr);
+		if (cont_len < 0)
+		    throw new NumberFormatException();
+	    }
+	    catch (NumberFormatException nfe)
+	    {
+		throw new ProtocolException("Invalid Content-length header"+
+					    " received: "+cl_hdr);
+	    }
+	}
+
+
+	// parse the Transfer-Encoding header
+
+	boolean te_chunked = false, te_is_identity = true, ct_mpbr = false;
+	Vector  te_hdr = null;
+	try
+	    { te_hdr = Util.parseHeader((String) Headers.get("Transfer-Encoding")); }
+	catch (ParseException pe)
+	    { }
+	if (te_hdr != null)
+	{
+	    te_chunked = ((HttpHeaderElement) te_hdr.lastElement()).getName().
+			 equalsIgnoreCase("chunked");
+	    for (int idx=0; idx<te_hdr.size(); idx++)
+		if (((HttpHeaderElement) te_hdr.elementAt(idx)).getName().
+		    equalsIgnoreCase("identity"))
+		    te_hdr.removeElementAt(idx--);
+		else
+		    te_is_identity = false;
+	}
+
+
+	// parse Content-Type header
+
+	try
+	{
+	    String hdr;
+	    if ((hdr = (String) Headers.get("Content-Type")) != null)
+	    {
+		Vector phdr = Util.parseHeader(hdr);
+		ct_mpbr = phdr.contains(new HttpHeaderElement("multipart/byteranges"))  ||
+			  phdr.contains(new HttpHeaderElement("multipart/x-byteranges"));
+	    }
+	}
+	catch (ParseException pe)
+	    { }
+
+
+	// now determine content-delimiter
+
+	if (StatusCode < 200  ||  StatusCode == 204  ||  StatusCode == 205  ||
+	    StatusCode == 304)
+	{
+	    cd_type = CD_0;
+	}
+	else if (te_chunked)
+	{
+	    cd_type = CD_CHUNKED;
+
+	    te_hdr.removeElementAt(te_hdr.size()-1);
+	    if (te_hdr.size() > 0)
+		setHeader("Transfer-Encoding", Util.assembleHeader(te_hdr));
+	    else
+		deleteHeader("Transfer-Encoding");
+	}
+	else if (cont_len != -1  &&  te_is_identity)
+	    cd_type = CD_CONTLEN;
+	else if (ct_mpbr  &&  te_is_identity)
+	    cd_type = CD_MP_BR;
+	else if (!method.equals("HEAD"))
+	{
+	    cd_type = CD_CLOSE;
+	    if (stream_handler != null)
+		stream_handler.markForClose(this);
+
+	    if (Version.equals("HTTP/0.9"))
+	    {
+		inp_stream =
+			new SequenceInputStream(new ByteArrayInputStream(Data),
+						inp_stream);
+		Data = null;
+	    }
+	}
+
+	if (cd_type == CD_CONTLEN)
+	    ContentLength = cont_len;
+	else
+	    deleteHeader("Content-Length");	// Content-Length is not valid in this case
+
+	/* We treat HEAD specially down here because the above code needs
+	 * to know whether to remove the Content-length header or not.
+	 */
+	if (method.equals("HEAD"))
+	    cd_type = CD_0;
+
+	if (cd_type == CD_0)
+	{
+	    ContentLength = 0;
+	    Data = new byte[0];
+	    inp_stream.close();		// we will not receive any more data
+	}
+
+	Log.write(Log.RESP, "Resp:  Response entity delimiter: " +
+	    (cd_type == CD_0       ? "No Entity"      :
+	     cd_type == CD_CLOSE   ? "Close"          :
+	     cd_type == CD_CONTLEN ? "Content-Length" :
+	     cd_type == CD_CHUNKED ? "Chunked"        :
+	     cd_type == CD_MP_BR   ? "Multipart"      :
+	     "???" ) + " (" + inp_stream.hashCode() + ")");
+
+
+	// remove erroneous connection tokens
+
+	if (connection.ServerProtocolVersion >= HTTP_1_1)
+	    deleteHeader("Proxy-Connection");
+	else					// HTTP/1.0
+	{
+	    if (connection.getProxyHost() != null)
+		deleteHeader("Connection");
+	    else
+		deleteHeader("Proxy-Connection");
+
+	    Vector pco;
+	    try
+		{ pco = Util.parseHeader((String) Headers.get("Connection")); }
+	    catch (ParseException pe)
+		{ pco = null; }
+
+	    if (pco != null)
+	    {
+		for (int idx=0; idx<pco.size(); idx++)
+		{
+		    String name =
+			    ((HttpHeaderElement) pco.elementAt(idx)).getName();
+		    if (!name.equalsIgnoreCase("keep-alive"))
+		    {
+			pco.removeElementAt(idx);
+			deleteHeader(name);
+			idx--;
+		    }
+		}
+
+		if (pco.size() > 0)
+		    setHeader("Connection", Util.assembleHeader(pco));
+		else
+		    deleteHeader("Connection");
+	    }
+
+	    try
+		{ pco = Util.parseHeader((String) Headers.get("Proxy-Connection")); }
+	    catch (ParseException pe)
+		{ pco = null; }
+
+	    if (pco != null)
+	    {
+		for (int idx=0; idx<pco.size(); idx++)
+		{
+		    String name =
+			    ((HttpHeaderElement) pco.elementAt(idx)).getName();
+		    if (!name.equalsIgnoreCase("keep-alive"))
+		    {
+			pco.removeElementAt(idx);
+			deleteHeader(name);
+			idx--;
+		    }
+		}
+
+		if (pco.size() > 0)
+		    setHeader("Proxy-Connection", Util.assembleHeader(pco));
+		else
+		    deleteHeader("Proxy-Connection");
+	    }
+	}
+
+
+	// this must be set before we invoke handleFirstRequest()
+	got_headers = true;
+
+	// special handling if this is the first response received
+	if (isFirstResponse)
+	{
+	    if (!connection.handleFirstRequest(req, this))
+	    {
+		// got a buggy server - need to redo the request
+		Response resp;
+		try
+		    { resp = connection.sendRequest(req, timeout); }
+		catch (ModuleException me)
+		    { throw new IOException(me.toString()); }
+		resp.getVersion();
+
+		this.StatusCode    = resp.StatusCode;
+		this.ReasonLine    = resp.ReasonLine;
+		this.Version       = resp.Version;
+		this.EffectiveURI  = resp.EffectiveURI;
+		this.ContentLength = resp.ContentLength;
+		this.Headers       = resp.Headers;
+		this.inp_stream    = resp.inp_stream;
+		this.Data          = resp.Data;
+
+		req = null;
+	    }
+	}
+    }
+
+
+    /* these are external to readResponseHeaders() because we need to be
+     * able to restart after an InterruptedIOException
+     */
+    private byte[]       buf     = new byte[7];
+    private int          buf_pos = 0;
+    private StringBuffer hdrs    = new StringBuffer(400);
+    private boolean      reading_lines = false;
+    private boolean      bol     = true;
+    private boolean      got_cr  = false;
+
+    /**
+     * Reads the response headers received, folding continued lines.
+     *
+     * <P>Some of the code is a bit convoluted because we have to be able
+     * restart after an InterruptedIOException.
+     *
+     * @inp    the input stream from which to read the response
+     * @return a (newline separated) list of headers
+     * @exception IOException if any read on the input stream fails
+     */
+    private String readResponseHeaders(InputStream inp)  throws IOException
+    {
+	if (buf_pos == 0)
+	    Log.write(Log.RESP, "Resp:  Reading Response headers " +
+				inp_stream.hashCode());
+	else
+	    Log.write(Log.RESP, "Resp:  Resuming reading Response headers " +
+				inp_stream.hashCode());
+
+
+	// read 7 bytes to see type of response
+	if (!reading_lines)
+	{
+	    try
+	    {
+		// Skip any leading white space to accomodate buggy responses
+		if (buf_pos == 0)
+		{
+		    int c;
+		    do
+		    {
+			if ((c = inp.read()) == -1)
+			    throw new EOFException("Encountered premature EOF "
+						   + "while reading Version");
+		    } while (Character.isWhitespace((char) c)) ;
+		    buf[0] = (byte) c;
+		    buf_pos = 1;
+		}
+
+		// Now read first seven bytes (the version string)
+		while (buf_pos < buf.length)
+		{
+		    int got = inp.read(buf, buf_pos, buf.length-buf_pos);
+		    if (got == -1)
+			throw new EOFException("Encountered premature EOF " +
+						"while reading Version");
+		    buf_pos += got;
+		}
+	    }
+	    catch (EOFException eof)
+	    {
+		Log.write(Log.RESP, "Resp:  (" + inp_stream.hashCode() + ")",
+			  eof);
+
+		throw eof;
+	    }
+	    for (int idx=0; idx<buf.length; idx++)
+		hdrs.append((char) buf[idx]);
+
+	    reading_lines = true;
+	}
+
+	if (hdrs.toString().startsWith("HTTP/")  ||		// It's x.x
+	    hdrs.toString().startsWith("HTTP "))		// NCSA bug
+	    readLines(inp);
+
+	// reset variables for next round
+	buf_pos = 0;
+	reading_lines = false;
+	bol     = true;
+	got_cr  = false;
+
+	String tmp = hdrs.toString();
+	hdrs.setLength(0);
+	return tmp;
+    }
+
+
+    boolean trailers_read = false;
+
+    /**
+     * This is called by the StreamDemultiplexor to read all the trailers
+     * of a chunked encoded entity.
+     *
+     * @param inp the raw input stream to read from
+     * @exception IOException if any IOException is thrown by the stream
+     */
+    void readTrailers(InputStream inp)  throws IOException
+    {
+	try
+	{
+	    readLines(inp);
+	    trailers_read = true;
+	}
+	catch (IOException ioe)
+	{
+	    if (!(ioe instanceof InterruptedIOException))
+		exception = ioe;
+	    throw ioe;
+	}
+    }
+
+
+    /**
+     * This reads a set of lines up to and including the first empty line.
+     * A line is terminated by either a <CR><LF> or <LF>. The lines are
+     * stored in the <var>hdrs</var> buffers. Continued lines are merged
+     * and stored as one line.
+     *
+     * <P>This method is restartable after an InterruptedIOException.
+     *
+     * @param inp the input stream to read from
+     * @exception IOException if any IOException is thrown by the stream
+     */
+    private void readLines(InputStream inp)  throws IOException
+    {
+	/* This loop is a merge of readLine() from DataInputStream and
+	 * the necessary header logic to merge continued lines and terminate
+	 * after an empty line. The reason this is explicit is because of
+	 * the need to handle InterruptedIOExceptions.
+	 */
+	loop: while (true)
+	{
+	    int b = inp.read();
+	    switch (b)
+	    {
+		case -1:
+		    throw new EOFException("Encountered premature EOF while reading headers:\n" + hdrs);
+		case '\r':
+		    got_cr = true;
+		    break;
+		case '\n':
+		    if (bol)  break loop;	// all headers read
+		    hdrs.append('\n');
+		    bol    = true;
+		    got_cr = false;
+		    break;
+		case ' ':
+		case '\t':
+		    if (bol)		// a continued line
+		    {
+			// replace previous \n with SP
+			hdrs.setCharAt(hdrs.length()-1, ' ');
+			bol = false;
+			break;
+		    }
+		default:
+		    if (got_cr)
+		    {
+			hdrs.append('\r');
+			got_cr = false;
+		    }
+		    hdrs.append((char) (b & 0xFF));
+		    bol = false;
+		    break;
+	    }
+	}
+    }
+
+
+    /**
+     * Parses the headers received into a new Response structure.
+     *
+     * @param  headers a (newline separated) list of headers
+     * @exception ProtocolException if any part of the headers do not
+     *            conform
+     */
+    private void parseResponseHeaders(String headers) throws ProtocolException
+    {
+	String          sts_line = null;
+	StringTokenizer lines = new StringTokenizer(headers, "\r\n"),
+			elem;
+
+	if (Log.isEnabled(Log.RESP))
+	    Log.write(Log.RESP, "Resp:  Parsing Response headers from Request "+
+				"\"" + method + " " + resource + "\":  (" +
+				inp_stream.hashCode() + ")\n\n" + headers);
+
+
+	// Detect and handle HTTP/0.9 responses
+
+	if (!headers.regionMatches(true, 0, "HTTP/", 0, 5)  &&
+	    !headers.regionMatches(true, 0, "HTTP ", 0, 5))	// NCSA bug
+	{
+	    Version    = "HTTP/0.9";
+	    StatusCode = 200;
+	    ReasonLine = "OK";
+
+	    try
+		{ Data = headers.getBytes("8859_1"); }
+	    catch (UnsupportedEncodingException uee)
+		{ throw new Error(uee.toString()); }
+
+	    return;
+	}
+
+
+	// get the status line
+
+	try
+	{
+	    sts_line = lines.nextToken();
+	    elem     = new StringTokenizer(sts_line, " \t");
+
+	    Version    = elem.nextToken();
+	    StatusCode = Integer.valueOf(elem.nextToken()).intValue();
+
+	    if (Version.equalsIgnoreCase("HTTP"))	// NCSA bug
+		Version = "HTTP/1.0";
+	}
+	catch (NoSuchElementException e)
+	{
+	    throw new ProtocolException("Invalid HTTP status line received: " +
+					sts_line);
+	}
+	try
+	    { ReasonLine = elem.nextToken("").trim(); }
+	catch (NoSuchElementException e)
+	    { ReasonLine = ""; }
+
+
+	/* If the status code shows an error and we're sending (or have sent)
+	 * an entity and it's length is delimited by a Content-length header,
+	 * then we must close the the connection (if indeed it hasn't already
+	 * been done) - RFC-2616, Section 8.2.2 .
+	 */
+	if (StatusCode >= 300  &&  sent_entity)
+	{
+	    if (stream_handler != null)
+		stream_handler.markForClose(this);
+	}
+
+
+	// get the rest of the headers
+
+	parseHeaderFields(lines, Headers);
+
+
+	/* make sure the connection isn't closed prematurely if we have
+	 * trailer fields
+	 */
+	if (Headers.get("Trailer") != null  &&  resp_inp_stream != null)
+	    resp_inp_stream.dontTruncate();
+
+	// Mark the end of the connection if it's not to be kept alive
+
+	int vers;
+	if (Version.equalsIgnoreCase("HTTP/0.9")  ||
+	    Version.equalsIgnoreCase("HTTP/1.0"))
+	    vers = 0;
+	else
+	    vers = 1;
+
+	try
+	{
+	    String con = (String) Headers.get("Connection"),
+		  pcon = (String) Headers.get("Proxy-Connection");
+
+	    // parse connection header
+	    if ((vers == 1  &&  con != null  &&  Util.hasToken(con, "close"))
+		||
+		(vers == 0  &&
+		 !((!used_proxy && con != null &&
+					Util.hasToken(con, "keep-alive"))  ||
+		   (used_proxy && pcon != null &&
+					Util.hasToken(pcon, "keep-alive")))
+		)
+	       )
+		if (stream_handler != null)
+		    stream_handler.markForClose(this);
+	}
+	catch (ParseException pe) { }
+    }
+
+
+    /**
+     * If the trailers have not been read it calls <code>getData()</code>
+     * to first force all data and trailers to be read. Then the trailers
+     * parsed into the <var>Trailers</var> hashtable.
+     *
+     * @exception IOException if any exception occured during reading of the
+     *                        response
+     */
+    private synchronized void getTrailers()  throws IOException
+    {
+	if (got_trailers)  return;
+	if (exception != null)
+	{
+	    exception.fillInStackTrace();
+	    throw exception;
+	}
+
+	Log.write(Log.RESP, "Resp:  Reading Response trailers " +
+			    inp_stream.hashCode());
+
+	try
+	{
+	    if (!trailers_read)
+	    {
+		if (resp_inp_stream != null)
+		    resp_inp_stream.readAll(timeout);
+	    }
+
+	    if (trailers_read)
+	    {
+		Log.write(Log.RESP, "Resp:  Parsing Response trailers from "+
+				    "Request \"" + method + " " + resource +
+				    "\":  (" + inp_stream.hashCode() +
+				    ")\n\n" + hdrs);
+
+		parseHeaderFields(new StringTokenizer(hdrs.toString(), "\r\n"),
+				  Trailers);
+	    }
+	}
+	finally
+	{
+	    got_trailers = true;
+	}
+    }
+
+
+    /**
+     * Parses the given lines as header fields of the form "<name>: <value>"
+     * into the given list.
+     *
+     * @param lines the header or trailer lines, one header field per line
+     * @param list  the Hashtable to store the parsed fields in
+     * @exception ProtocolException if any part of the headers do not
+     *                              conform
+     */
+    private void parseHeaderFields(StringTokenizer lines, CIHashtable list)
+	    throws ProtocolException
+    {
+	while (lines.hasMoreTokens())
+	{
+	    String hdr = lines.nextToken();
+	    int    sep = hdr.indexOf(':');
+
+	    /* Once again we have to deal with broken servers and try
+	     * to wing it here. If no ':' is found, try using the first
+	     * space:
+	     */
+	    if (sep == -1)
+		sep = hdr.indexOf(' ');
+	    if (sep == -1)
+	    {
+		throw new ProtocolException("Invalid HTTP header received: " +
+					    hdr);
+	    }
+
+	    String hdr_name  = hdr.substring(0, sep).trim();
+	    String hdr_value = hdr.substring(sep+1).trim();
+
+	    // Can header have multiple values?
+	    if (!singleValueHeaders.containsKey(hdr_name.toLowerCase()))
+	    {
+		String old_value  = (String) list.get(hdr_name);
+		if (old_value == null)
+		    list.put(hdr_name, hdr_value);
+		else
+		    list.put(hdr_name, old_value + ", " + hdr_value);
+	    }
+	    else
+		// No multiple values--just replace/put latest header value
+		list.put(hdr_name, hdr_value);
+	}
+    }
+
+
+    /**
+     * Reads the response data received. Does not return until either
+     * Content-Length bytes have been read or EOF is reached.
+     *
+     * @inp       the input stream from which to read the data
+     * @exception IOException if any read on the input stream fails
+     */
+    private void readResponseData(InputStream inp) throws IOException
+    {
+	if (ContentLength == 0)
+	    return;
+
+	if (Data == null)
+	    Data = new byte[0];
+
+
+	// read response data
+
+	int off = Data.length;
+
+	try
+	{
+	    // check Content-length header in case CE-Module removed it
+	    if (getHeader("Content-Length") != null)
+	    {
+		int rcvd = 0;
+		Data = new byte[ContentLength];
+
+		do
+		{
+		    off  += rcvd;
+		    rcvd  = inp.read(Data, off, ContentLength-off);
+		} while (rcvd != -1  &&  off+rcvd < ContentLength);
+
+		/* Don't do this!
+		 * If we do, then getData() won't work after a getInputStream()
+		 * because we'll never get all the expected data. Instead, let
+		 * the underlying RespInputStream throw the EOF.
+		if (rcvd == -1)	// premature EOF
+		{
+		    throw new EOFException("Encountered premature EOF while " +
+					    "reading headers: received " + off +
+					    " bytes instead of the expected " +
+					    ContentLength + " bytes");
+		}
+		*/
+	    }
+	    else
+	    {
+		int inc  = 1000,
+		    rcvd = 0;
+
+		do
+		{
+		    off  += rcvd;
+		    Data  = Util.resizeArray(Data, off+inc);
+		} while ((rcvd = inp.read(Data, off, inc)) != -1);
+
+		Data = Util.resizeArray(Data, off);
+	    }
+	}
+	catch (IOException ioe)
+	{
+	    Data = Util.resizeArray(Data, off);
+	    throw ioe;
+	}
+	finally
+	{
+	    try
+		{ inp.close(); }
+	    catch (IOException ioe)
+		{ }
+	}
+    }
+
+
+    Request        req = null;
+    boolean isFirstResponse = false;
+    /**
+     * This marks this response as belonging to the first request made
+     * over an HTTPConnection. The <var>con</var> and <var>req</var>
+     * parameters are needed in case we have to do a resend of the request -
+     * this is to handle buggy servers which barf upon receiving a request
+     * marked as HTTP/1.1 .
+     *
+     * @param con The HTTPConnection used
+     * @param req The Request sent
+     */
+    void markAsFirstResponse(Request req)
+    {
+	this.req = req;
+	isFirstResponse = true;
+    }
+
+
+    /**
+     * @return a clone of this request object
+     */
+    public Object clone()
+    {
+	Response cl;
+	try
+	    { cl = (Response) super.clone(); }
+	catch (CloneNotSupportedException cnse)
+	    { throw new InternalError(cnse.toString()); /* shouldn't happen */ }
+
+	cl.Headers  = (CIHashtable) Headers.clone();
+	cl.Trailers = (CIHashtable) Trailers.clone();
+
+	return cl;
+    }
+}
diff --git a/HTTPClient/ResponseHandler.java b/HTTPClient/ResponseHandler.java
new file mode 100644
index 0000000..482d2c0
--- /dev/null
+++ b/HTTPClient/ResponseHandler.java
@@ -0,0 +1,138 @@
+/*
+ * @(#)ResponseHandler.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+/**
+ * This holds various information about an active response. Used by the
+ * StreamDemultiplexor and RespInputStream.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.2
+ */
+final class ResponseHandler
+{
+    /** the response stream */
+    RespInputStream     stream;
+
+    /** the response class */
+    Response            resp;
+
+    /** the response class */
+    Request             request;
+
+    /** signals that the demux has closed the response stream, and that
+	therefore no more data can be read */
+    boolean             eof = false;
+
+    /** this is non-null if the stream has an exception pending */
+    IOException         exception = null;
+
+
+    /**
+     * Creates a new handler. This also allocates the response input
+     * stream.
+     *
+     * @param resp     the reponse
+     * @param request  the request
+     * @param demux    our stream demultiplexor.
+     */
+    ResponseHandler(Response resp, Request request, StreamDemultiplexor demux)
+    {
+	this.resp     = resp;
+	this.request  = request;
+	this.stream   = new RespInputStream(demux, this);
+
+	Log.write(Log.DEMUX, "Demux: Opening stream " + this.stream.hashCode() +
+			     " for demux (" + demux.hashCode() + ")");
+    }
+
+
+    /** holds the string that marks the end of this stream; used for
+	multipart delimited responses. */
+    private byte[] endbndry = null;
+
+    /** holds the compilation of the above string */
+    private int[]  end_cmp  = null;
+
+    /**
+     * return the boundary string for this response. Set's up the
+     * InputStream buffer if neccessary.
+     *
+     * @param  MasterStream the input stream from which the stream demux
+     *                      is reading.
+     * @return the boundary string.
+     */
+    byte[] getEndBoundary(BufferedInputStream MasterStream)
+		throws IOException, ParseException
+    {
+	if (endbndry == null)
+	    setupBoundary(MasterStream);
+
+	return endbndry;
+    }
+
+    /**
+     * return the compilation of the boundary string for this response.
+     * Set's up the InputStream buffer if neccessary.
+     *
+     * @param  MasterStream the input stream from which the stream demux
+     *                      is reading.
+     * @return the compiled boundary string.
+     */
+    int[] getEndCompiled(BufferedInputStream MasterStream)
+		throws IOException, ParseException
+    {
+	if (end_cmp == null)
+	    setupBoundary(MasterStream);
+
+	return end_cmp;
+    }
+
+    /**
+     * Gets the boundary string, compiles it for searching, and initializes
+     * the buffered input stream.
+     */
+    void setupBoundary(BufferedInputStream MasterStream)
+		throws IOException, ParseException
+    {
+	String endstr = "--" + Util.getParameter("boundary",
+			    resp.getHeader("Content-Type")) +
+			"--\r\n";
+	endbndry = endstr.getBytes("8859_1");
+	end_cmp = Util.compile_search(endbndry);
+	MasterStream.markForSearch();
+    }
+}
diff --git a/HTTPClient/RetryException.java b/HTTPClient/RetryException.java
new file mode 100644
index 0000000..128642a
--- /dev/null
+++ b/HTTPClient/RetryException.java
@@ -0,0 +1,105 @@
+/*
+ * @(#)RetryException.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+/**
+ * Signals that an exception was thrown and caught, and the request was
+ * retried.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class RetryException extends IOException
+{
+    /** the request to retry */
+    Request     request  = null;
+
+    /** the response associated with the above request */
+    Response    response = null;
+
+    /** the start of the liked list */
+    RetryException first = null;
+
+    /** the next exception in the list */
+    RetryException next  = null;
+
+    /** the original exception which caused the connection to be closed. */
+    IOException exception = null;
+
+    /** was this exception generated because of an abnormal connection reset? */
+    boolean conn_reset = true;
+
+    /** restart processing? */
+    boolean restart = false;
+
+
+    /**
+     * Constructs an RetryException with no detail message.
+     * A detail message is a String that describes this particular exception.
+     */
+    public RetryException()
+    {
+	super();
+    }
+
+
+    /**
+     * Constructs an RetryException class with the specified detail message.
+     * A detail message is a String that describes this particular exception.
+     *
+     * @param s the String containing a detail message
+     */
+    public RetryException(String s)
+    {
+	super(s);
+    }
+
+
+    // Methods
+
+    /**
+     * Inserts this exception into the list.
+     *
+     * @param re the retry exception after which to add this one
+     */
+    void addToListAfter(RetryException re)
+    {
+	if (re == null)  return;
+
+	if (re.next != null)
+	    this.next = re.next;
+	re.next = this;
+    }
+}
diff --git a/HTTPClient/RetryModule.java b/HTTPClient/RetryModule.java
new file mode 100644
index 0000000..4528237
--- /dev/null
+++ b/HTTPClient/RetryModule.java
@@ -0,0 +1,285 @@
+/*
+ * @(#)RetryModule.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+
+/**
+ * This module handles request retries when a connection closes prematurely.
+ * It is triggered by the RetryException thrown by the StreamDemultiplexor.
+ *
+ * <P>This module is somewhat unique in that it doesn't strictly limit itself
+ * to the HTTPClientModule interface and its return values. That is, it
+ * sends request directly using the HTTPConnection.sendRequest() method. This
+ * is necessary because this module will not only resend its request but it
+ * also resend all other requests in the chain. Also, it rethrows the
+ * RetryException in Phase1 to restart the processing of the modules.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3
+ */
+class RetryModule implements HTTPClientModule, GlobalConstants
+{
+    // Constructors
+
+    /**
+     */
+    RetryModule()
+    {
+    }
+
+
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+    {
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest roreq)
+	    throws IOException, ModuleException
+    {
+	try
+	{
+	    resp.getStatusCode();
+	}
+	catch (RetryException re)
+	{
+	    Log.write(Log.MODS, "RtryM: Caught RetryException");
+
+	    boolean got_lock = false;
+
+	    try
+	    {
+	    synchronized (re.first)
+	    {
+		got_lock = true;
+
+		// initialize idempotent sequence checking
+		IdempotentSequence seq = new IdempotentSequence();
+		for (RetryException e=re.first; e!=null; e=e.next)
+		    seq.add(e.request);
+
+		for (RetryException e=re.first; e!=null; e=e.next)
+		{
+		    Log.write(Log.MODS, "RtryM: handling exception ", e);
+
+		    Request req = e.request;
+		    HTTPConnection con = req.getConnection();
+
+		    /* Don't retry if either the sequence is not idempotent
+		     * (Sec 8.1.4 and 9.1.2), or we've already retried enough
+		     * times, or the headers have been read and parsed
+		     * already
+		     */
+		    if (!seq.isIdempotent(req)  ||
+			(con.ServProtVersKnown  &&
+			 con.ServerProtocolVersion >= HTTP_1_1  &&
+			 req.num_retries > 0)  ||
+			((!con.ServProtVersKnown  ||
+			  con.ServerProtocolVersion <= HTTP_1_0)  &&
+			 req.num_retries > 4)  ||
+			e.response.got_headers)
+		    {
+			e.first = null;
+			continue;
+		    }
+
+
+		    /**
+		     * if an output stream was used (i.e. we don't have the
+		     * data to resend) then delegate the responsibility for
+		     * resending to the application.
+		     */
+		    if (req.getStream() != null)
+		    {
+			if (HTTPConnection.deferStreamed)
+			{
+			    req.getStream().reset();
+			    e.response.setRetryRequest(true);
+			}
+			e.first = null;
+			continue;
+		    }
+
+
+		    /* If we have an entity then setup either the entity-delay
+		     * or the Expect header
+		     */
+		    if (req.getData() != null  &&  e.conn_reset)
+		    {
+			if (con.ServProtVersKnown  &&
+			    con.ServerProtocolVersion >= HTTP_1_1)
+			    addToken(req, "Expect", "100-continue");
+			else
+			    req.delay_entity = 5000L << req.num_retries;
+		    }
+
+		    /* If the next request in line has an entity and we're
+		     * talking to an HTTP/1.0 server then close the socket
+		     * after this request. This is so that the available()
+		     * call (to watch for an error response from the server)
+		     * will work correctly.
+		     */
+		    if (e.next != null  &&  e.next.request.getData() != null  &&
+			(!con.ServProtVersKnown  ||
+			 con.ServerProtocolVersion < HTTP_1_1)  &&
+			 e.conn_reset)
+		    {
+			addToken(req, "Connection", "close");
+		    }
+
+
+		    /* If this an HTTP/1.1 server then don't pipeline retries.
+		     * The problem is that if the server for some reason
+		     * decides not to use persistent connections and it does
+		     * not do a correct shutdown of the connection, then the
+		     * response will be ReSeT. If we did pipeline then we
+		     * would keep falling into this trap indefinitely.
+		     *
+		     * Note that for HTTP/1.0 servers, if they don't support
+		     * keep-alives then the normal code will already handle
+		     * this accordingly and won't pipe over the same
+		     * connection.
+		     */
+		    if (con.ServProtVersKnown  &&
+			con.ServerProtocolVersion >= HTTP_1_1  &&
+			e.conn_reset)
+		    {
+			req.dont_pipeline = true;
+		    }
+		    // The above is too risky - for moment let's be safe
+		    // and never pipeline retried request at all.
+		    req.dont_pipeline = true;
+
+
+		    // now resend the request
+
+		    Log.write(Log.MODS, "RtryM: Retrying request '" +
+					req.getMethod() + " " +
+					req.getRequestURI() + "'");
+
+		    if (e.conn_reset)
+			req.num_retries++;
+		    e.response.http_resp.set(req,
+				    con.sendRequest(req, e.response.timeout));
+		    e.exception = null;
+		    e.first = null;
+		}
+	    }
+	    }
+	    catch (NullPointerException npe)
+		{ if (got_lock)  throw npe; }
+	    catch (ParseException pe)
+		{ throw new IOException(pe.getMessage()); }
+
+	    if (re.exception != null)  throw re.exception;
+
+	    re.restart = true;
+	    throw re;
+	}
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+    {
+	// reset any stuff we might have set previously
+	req.delay_entity  = 0;
+	req.dont_pipeline = false;
+	req.num_retries   = 0;
+
+	return RSP_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Add a token to the given header. If the header does not exist then
+     * create it with the given token.
+     *
+     * @param req the request who's headers are to be modified
+     * @param hdr the name of the header to add the token to (or to create)
+     * @param tok the token to add
+     * @exception ParseException if parsing the header fails
+     */
+    private void addToken(Request req, String hdr, String tok)
+	    throws ParseException
+    {
+	int idx;
+	NVPair[] hdrs = req.getHeaders();
+	for (idx=0; idx<hdrs.length; idx++)
+	{
+	    if (hdrs[idx].getName().equalsIgnoreCase(hdr))
+		break;
+	}
+
+	if (idx == hdrs.length)		// no such header, so add one
+	{
+	    hdrs = Util.resizeArray(hdrs, idx+1);
+	    hdrs[idx] = new NVPair(hdr, tok);
+	    req.setHeaders(hdrs);
+	}
+	else					// header exists, so add token
+	{
+	    if (!Util.hasToken(hdrs[idx].getValue(), tok))
+		hdrs[idx] = new NVPair(hdr, hdrs[idx].getValue() + ", " + tok);
+	}
+    }
+}
diff --git a/HTTPClient/RoRequest.java b/HTTPClient/RoRequest.java
new file mode 100644
index 0000000..1c665cc
--- /dev/null
+++ b/HTTPClient/RoRequest.java
@@ -0,0 +1,81 @@
+/*
+ * @(#)RoRequest.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+
+/**
+ * This interface represents the read-only interface of an http request.
+ * It is the compile-time type passed to various handlers which might
+ * need the request info but musn't modify the request.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public interface RoRequest
+{
+    /**
+     * @return the HTTPConnection this request is associated with
+     */
+    public HTTPConnection getConnection();
+
+    /**
+     * @return the request method
+     */
+    public String getMethod();
+
+    /**
+     * @return the request-uri
+     */
+    public String getRequestURI();
+
+    /**
+     * @return the headers making up this request
+     */
+    public NVPair[] getHeaders();
+
+    /**
+     * @return the body of this request
+     */
+    public byte[] getData();
+
+    /**
+     * @return the output stream on which the body is written
+     */
+    public HttpOutputStream getStream();
+
+    /**
+     * @return true if the modules or handlers for this request may popup
+     *         windows or otherwise interact with the user
+     */
+    public boolean allowUI();
+}
diff --git a/HTTPClient/RoResponse.java b/HTTPClient/RoResponse.java
new file mode 100644
index 0000000..a54eee2
--- /dev/null
+++ b/HTTPClient/RoResponse.java
@@ -0,0 +1,183 @@
+/*
+ * @(#)RoResponse.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Date;
+
+
+/**
+ * This interface represents read-only interface of an intermediate http
+ * response. It is the compile-time type passed to various handlers which
+ * might the response info but musn't modify the response.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public interface RoResponse
+{
+    /**
+     * give the status code for this request. These are grouped as follows:
+     * <UL>
+     *   <LI> 1xx - Informational (new in HTTP/1.1)
+     *   <LI> 2xx - Success
+     *   <LI> 3xx - Redirection
+     *   <LI> 4xx - Client Error
+     *   <LI> 5xx - Server Error
+     * </UL>
+     *
+     * @return the status code
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public int getStatusCode()  throws IOException;
+
+    /**
+     * @return the reason line associated with the status code.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public String getReasonLine()  throws IOException;
+
+    /**
+     * @return the HTTP version returned by the server.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public String getVersion()  throws IOException;
+
+    /**
+     * retrieves the field for a given header.
+     *
+     * @param  hdr the header name.
+     * @return the value for the header, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public String getHeader(String hdr)  throws IOException;
+
+    /**
+     * retrieves the field for a given header. The value is parsed as an
+     * int.
+     *
+     * @param  hdr the header name.
+     * @return the value for the header if the header exists
+     * @exception NumberFormatException if the header's value is not a number
+     *                                  or if the header does not exist.
+     * @exception IOException if any exception occurs on the socket.
+     */
+    public int getHeaderAsInt(String hdr)
+		throws IOException, NumberFormatException;
+
+    /**
+     * retrieves the field for a given header. The value is parsed as a
+     * date; if this fails it is parsed as a long representing the number
+     * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
+     * IllegalArgumentException is thrown.
+     *
+     * @param  hdr the header name.
+     * @return the value for the header, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     * @exception IllegalArgumentException If the header cannot be parsed
+     *            as a date or time.
+     */
+    public Date getHeaderAsDate(String hdr)
+	    throws IOException, IllegalArgumentException;
+
+    /**
+     * Retrieves the field for a given trailer. Note that this should not
+     * be invoked until all the response data has been read. If invoked
+     * before, it will force the data to be read via <code>getData()</code>.
+     *
+     * @param  trailer the trailer name.
+     * @return the value for the trailer, or null if non-existent.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public String getTrailer(String trailer)  throws IOException;
+
+    /**
+     * Retrieves the field for a given tailer. The value is parsed as an
+     * int.
+     *
+     * @param  trailer the tailer name.
+     * @return the value for the trailer if the trailer exists
+     * @exception NumberFormatException if the trailer's value is not a number
+     *                                  or if the trailer does not exist.
+     * @exception IOException if any exception occurs on the socket.
+     */
+    public int getTrailerAsInt(String trailer)
+		throws IOException, NumberFormatException;
+
+
+    /**
+     * Retrieves the field for a given trailer. The value is parsed as a
+     * date; if this fails it is parsed as a long representing the number
+     * of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
+     * IllegalArgumentException is thrown.
+     * <br>Note: When sending dates use Util.httpDate().
+     *
+     * @param  trailer the trailer name.
+     * @return the value for the trailer, or null if non-existent.
+     * @exception IllegalArgumentException if the trailer's value is neither a
+     *            legal date nor a number.
+     * @exception IOException if any exception occurs on the socket.
+     * @exception IllegalArgumentException If the header cannot be parsed
+     *            as a date or time.
+     */
+    public Date getTrailerAsDate(String trailer)
+		throws IOException, IllegalArgumentException;
+
+    /**
+     * Reads all the response data into a byte array. Note that this method
+     * won't return until <em>all</em> the data has been received (so for
+     * instance don't invoke this method if the server is doing a server
+     * push). If getInputStream() had been previously called then this method
+     * only returns any unread data remaining on the stream and then closes
+     * it.
+     *
+     * @see #getInputStream()
+     * @return an array containing the data (body) returned. If no data
+     *         was returned then it's set to a zero-length array.
+     * @exception IOException If any io exception occured while reading
+     *			      the data
+     */
+    public byte[] getData()  throws IOException;
+
+    /**
+     * Gets an input stream from which the returned data can be read. Note
+     * that if getData() had been previously called it will actually return
+     * a ByteArrayInputStream created from that data.
+     *
+     * @see #getData()
+     * @return the InputStream.
+     * @exception IOException If any exception occurs on the socket.
+     */
+    public InputStream getInputStream()  throws IOException;
+}
diff --git a/HTTPClient/SocksClient.java b/HTTPClient/SocksClient.java
new file mode 100644
index 0000000..acae4f9
--- /dev/null
+++ b/HTTPClient/SocksClient.java
@@ -0,0 +1,622 @@
+/*
+ * @(#)SocksClient.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+import java.net.Socket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+/**
+ * This class implements a SOCKS Client. Supports both versions 4 and 5.
+ * GSSAPI however is not yet implemented.
+ * <P>Usage is as follows: somewhere in the initialization code (and before
+ * the first socket creation call) create a SocksClient instance. Then replace
+ * each socket creation call
+ *
+ *     <code>sock = new Socket(host, port);</code>
+ *
+ * with
+ *
+ *     <code>sock = socks_client.getSocket(host, port);</code>
+ *
+ * (where <var>socks_client</var> is the above created SocksClient instance).
+ * That's all.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class SocksClient
+{
+    /** the host the socks server sits on */
+    private String socks_host;
+
+    /** the port the socks server listens on */
+    private int    socks_port;
+
+    /** the version of socks that the server handles */
+    private int    socks_version;
+
+    /** socks commands */
+    private final static byte CONNECT = 1,
+			      BIND    = 2,
+			      UDP_ASS = 3;
+
+    /** socks version 5 authentication methods */
+    private final static byte NO_AUTH = 0,
+			      GSSAPI  = 1,
+			      USERPWD = 2,
+			      NO_ACC  = (byte) 0xFF;
+
+    /** socks version 5 address types */
+    private final static byte IP_V4   = 1,
+			      DMNAME  = 3,
+			      IP_V6   = 4;
+
+
+    // Constructors
+
+    /**
+     * Creates a new SOCKS Client using the specified host and port for
+     * the server. Will try to establish the SOCKS version used when
+     * establishing the first connection.
+     *
+     * @param host  the host the SOCKS server is sitting on.
+     * @param port  the port the SOCKS server is listening on.
+     */
+    SocksClient(String host, int port)
+    {
+	this.socks_host    = host;
+	this.socks_port    = port;
+	this.socks_version = -1;	// as yet unknown
+    }
+
+    /**
+     * Creates a new SOCKS Client using the specified host and port for
+     * the server.
+     *
+     * @param host     the host the SOCKS server is sitting on.
+     * @param port     the port the SOCKS server is listening on.
+     * @param version  the version the SOCKS server is using.
+     * @exception SocksException if the version is invalid (Currently allowed
+     *                           are: 4 and 5).
+     */
+    SocksClient(String host, int port, int version)  throws SocksException
+    {
+	this.socks_host    = host;
+	this.socks_port    = port;
+
+	if (version != 4  &&  version != 5)
+	    throw new SocksException("SOCKS Version not supported: "+version);
+	this.socks_version = version;
+    }
+
+
+    // Methods
+
+    /**
+     * Initiates a connection to the socks server, does the startup
+     * protocol and returns a socket ready for talking.
+     *
+     * @param host  the host you wish to connect to
+     * @param port  the port you wish to connect to
+     * @return a Socket with a connection via socks to the desired host/port
+     * @exception IOException if any socket operation fails
+     */
+    Socket getSocket(String host, int port)  throws IOException
+    {
+	return getSocket(host, port, null, -1);
+    }
+
+    /**
+     * Initiates a connection to the socks server, does the startup
+     * protocol and returns a socket ready for talking.
+     *
+     * @param host      the host you wish to connect to
+     * @param port      the port you wish to connect to
+     * @param localAddr the local address to bind to
+     * @param localPort the local port to bind to
+     * @return a Socket with a connection via socks to the desired host/port
+     * @exception IOException if any socket operation fails
+     */
+    Socket getSocket(String host, int port, InetAddress localAddr,
+		     int localPort)  throws IOException
+    {
+	Socket sock = null;
+
+	try
+	{
+	    Log.write(Log.SOCKS, "Socks: contacting server on " +
+				 socks_host + ":" + socks_port);
+
+
+	    // create socket and streams
+
+	    sock = connect(socks_host, socks_port, localAddr, localPort);
+	    InputStream  inp = sock.getInputStream();
+	    OutputStream out = sock.getOutputStream();
+
+
+	    // setup connection depending on socks version
+
+	    switch (socks_version)
+	    {
+		case 4:
+		    v4ProtExchg(inp, out, host, port);
+		    break;
+		case 5:
+		    v5ProtExchg(inp, out, host, port);
+		    break;
+		case -1:
+		    // Ok, let's try and figure it out
+		    try
+		    {
+			v4ProtExchg(inp, out, host, port);
+			socks_version = 4;
+		    }
+		    catch (SocksException se)
+		    {
+			Log.write(Log.SOCKS, "Socks: V4 request failed: " +
+					     se.getMessage());
+
+			sock.close();
+			sock = connect(socks_host, socks_port, localAddr,
+				       localPort);
+			inp = sock.getInputStream();
+			out = sock.getOutputStream();
+
+			v5ProtExchg(inp, out, host, port);
+			socks_version = 5;
+		    }
+		    break;
+		default:
+		    throw new Error("SocksClient internal error: unknown " +
+				    "version "+socks_version);
+	    }
+
+	    Log.write(Log.SOCKS, "Socks: connection established.");
+
+	    return sock;
+	}
+	catch (IOException ioe)
+	{
+	    if (sock != null)
+	    {
+		try { sock.close(); }
+		catch (IOException ee) {}
+	    }
+
+	    throw ioe;
+	}
+    }
+
+
+    /**
+     * Connect to the host/port, trying all addresses assciated with that
+     * host.
+     *
+     * @param host      the host you wish to connect to
+     * @param port      the port you wish to connect to
+     * @param localAddr the local address to bind to
+     * @param localPort the local port to bind to
+     * @return the Socket
+     * @exception IOException if the connection could not be established
+     */
+    private static final Socket connect(String host, int port,
+					InetAddress localAddr, int localPort)
+	    throws IOException
+    {
+	InetAddress[] addr_list = InetAddress.getAllByName(host);
+	for (int idx=0; idx<addr_list.length; idx++)
+	{
+	    try
+	    {
+		if (localAddr == null)
+		    return new Socket(addr_list[idx], port);
+		else
+		    return new Socket(addr_list[idx], port, localAddr, localPort);
+	    }
+	    catch (SocketException se)
+	    {
+		if (idx < addr_list.length-1)
+		    continue;	// try next IP address
+		else
+		    throw se;	// none of them worked
+	    }
+	}
+
+	return null;	// never reached - just here to shut up the compiler
+    }
+
+
+    private boolean v4A  = false;	// SOCKS version 4A
+    private byte[]  user = null;
+
+    /**
+     * Does the protocol exchange for a version 4 SOCKS connection.
+     */
+    private void v4ProtExchg(InputStream inp, OutputStream out, String host,
+			     int port)
+	throws SocksException, IOException
+    {
+	ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
+
+	Log.write(Log.SOCKS, "Socks: Beginning V4 Protocol Exchange for host "
+			     + host + ":" + port);
+
+	// get ip addr and user name
+
+	byte[] addr = { 0, 0, 0, 42 };
+	if (!v4A)
+	{
+	    try
+		{ addr = InetAddress.getByName(host).getAddress(); }
+	    // if we can't translate, let's try the server
+	    catch (UnknownHostException uhe)
+		{ v4A = true; }
+	    catch (SecurityException se)
+		{ v4A = true; }
+	    if (v4A)
+		Log.write(Log.SOCKS, "Socks: Switching to version 4A");
+	}
+
+	if (user == null)	// I see no reason not to cache this
+	{
+	    String user_str;
+	    try
+		{ user_str = System.getProperty("user.name", ""); }
+	    catch (SecurityException se)
+		{ user_str = "";	/* try it anyway */ }
+	    byte[] tmp = user_str.getBytes();
+	    user = new byte[tmp.length+1];
+	    System.arraycopy(tmp, 0, user, 0, tmp.length);
+	    user[user_str.length()] = 0;	// 0-terminated string
+	}
+
+
+	// send version 4 request
+
+	Log.write(Log.SOCKS, "Socks: Sending connect request for user " +
+			     new String(user, 0, user.length-1));
+
+	buffer.reset();
+	buffer.write(4);				// version
+	buffer.write(CONNECT);				// command
+	buffer.write((port >> 8) & 0xff);		// port
+	buffer.write(port & 0xff);
+	buffer.write(addr);				// address
+	buffer.write(user);				// user
+	if (v4A)
+	{
+	    buffer.write(host.getBytes("8859_1"));	// host name
+	    buffer.write(0);				// terminating 0
+	}
+	buffer.writeTo(out);
+
+
+	// read response
+
+	int version = inp.read();
+	if (version == -1)
+	    throw new SocksException("Connection refused by server");
+	else if (version == 4)	// not all socks4 servers are correct...
+	    Log.write(Log.SOCKS, "Socks: Warning: received version 4 " +
+				 "instead of 0");
+	else if (version != 0)
+	    throw new SocksException("Received invalid version: " + version +
+				     "; expected: 0");
+
+	int sts = inp.read();
+
+	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
+			     "; status: " + sts);
+
+	switch (sts)
+	{
+	    case 90:	// request granted
+		break;
+	    case 91:	// request rejected
+		throw new SocksException("Connection request rejected");
+	    case 92:	// request rejected: can't connect to identd
+		throw new SocksException("Connection request rejected: " +
+					 "can't connect to identd");
+	    case 93:	// request rejected: identd reports diff uid
+		throw new SocksException("Connection request rejected: " +
+					 "identd reports different user-id " +
+					 "from "+
+					 new String(user, 0, user.length-1));
+	    default:	// unknown status
+		throw new SocksException("Connection request rejected: " +
+					 "unknown error " + sts);
+	}
+
+	byte[] skip = new byte[2+4];		// skip port + address
+	int rcvd = 0,
+	    tot  = 0;
+	while (tot < skip.length  &&
+		(rcvd = inp.read(skip, 0, skip.length-tot)) != -1)
+	    tot += rcvd;
+    }
+
+
+    /**
+     * Does the protocol exchange for a version 5 SOCKS connection.
+     * (rfc-1928)
+     */
+    private void v5ProtExchg(InputStream inp, OutputStream out, String host,
+			     int port)
+	throws SocksException, IOException
+    {
+	int                   version;
+	ByteArrayOutputStream buffer = new ByteArrayOutputStream(100);
+
+	Log.write(Log.SOCKS, "Socks: Beginning V5 Protocol Exchange for host "
+			     + host + ":" + port);
+
+	// send version 5 verification methods
+
+	Log.write(Log.SOCKS, "Socks: Sending authentication request; methods"
+			     + " No-Authentication, Username/Password");
+
+	buffer.reset();
+	buffer.write(5);		// version
+	buffer.write(2);		// number of verification methods
+	buffer.write(NO_AUTH);		// method: no authentication
+	buffer.write(USERPWD);		// method: username/password
+	//buffer.write(GSSAPI);		// method: gssapi
+	buffer.writeTo(out);
+
+
+	// receive servers repsonse
+
+	version = inp.read();
+	if (version == -1)
+	    throw new SocksException("Connection refused by server");
+	else if (version != 5)
+	    throw new SocksException("Received invalid version: " + version +
+				     "; expected: 5");
+
+	int method = inp.read();
+
+	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
+			     "; method: " + method);
+
+
+	// enter sub-negotiation for authentication
+
+	switch(method)
+	{
+	    case NO_AUTH:
+		break;
+	    case GSSAPI:
+		negotiate_gssapi(inp, out);
+		break;
+	    case USERPWD:
+		negotiate_userpwd(inp, out);
+		break;
+	    case NO_ACC:
+		throw new SocksException("Server unwilling to accept any " +
+					 "standard authentication methods");
+	    default:
+		throw new SocksException("Cannot handle authentication method "
+					 + method);
+	}
+
+
+	// send version 5 request
+
+	Log.write(Log.SOCKS, "Socks: Sending connect request");
+
+	buffer.reset();
+	buffer.write(5);				// version
+	buffer.write(CONNECT);				// command
+	buffer.write(0);				// reserved - must be 0
+	buffer.write(DMNAME);				// address type
+	buffer.write(host.length() & 0xff);		// address length
+	buffer.write(host.getBytes("8859_1"));		// address
+	buffer.write((port >> 8) & 0xff);		// port
+	buffer.write(port & 0xff);
+	buffer.writeTo(out);
+
+
+	// read response
+
+	version = inp.read();
+	if (version != 5)
+	    throw new SocksException("Received invalid version: " + version +
+				     "; expected: 5");
+
+	int sts = inp.read();
+
+	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
+			     "; status: " + sts);
+
+	switch (sts)
+	{
+	    case 0:	// succeeded
+		break;
+	    case 1:
+		throw new SocksException("General SOCKS server failure");
+	    case 2:
+		throw new SocksException("Connection not allowed");
+	    case 3:
+		throw new SocksException("Network unreachable");
+	    case 4:
+		throw new SocksException("Host unreachable");
+	    case 5:
+		throw new SocksException("Connection refused");
+	    case 6:
+		throw new SocksException("TTL expired");
+	    case 7:
+		throw new SocksException("Command not supported");
+	    case 8:
+		throw new SocksException("Address type not supported");
+	    default:
+		throw new SocksException("Unknown reply received from server: "
+					 + sts);
+	}
+
+	inp.read();			// Reserved
+	int atype = inp.read(),		// address type
+	    alen;			// address length
+	switch(atype)
+	{
+	    case IP_V6:
+		alen = 16;
+		break;
+	    case IP_V4:
+		alen = 4;
+		break;
+	    case DMNAME:
+		alen = inp.read();
+		break;
+	    default:
+		throw new SocksException("Invalid address type received from" +
+					 " server: "+atype);
+	}
+
+	byte[] skip = new byte[alen+2];		// skip address + port
+	int rcvd = 0,
+	    tot  = 0;
+	while (tot < skip.length  &&
+		(rcvd = inp.read(skip, 0, skip.length-tot)) != -1)
+	    tot += rcvd;
+    }
+
+
+    /**
+     * Negotiates authentication using the gssapi protocol
+     * (draft-ietf-aft-gssapi-02).
+     *
+     * NOTE: this is not implemented currently. Will have to wait till
+     *       Java provides the necessary access to the system routines.
+     */
+    private void negotiate_gssapi(InputStream inp, OutputStream out)
+	throws SocksException, IOException
+    {
+	throw new
+	    SocksException("GSSAPI authentication protocol not implemented");
+    }
+
+
+    /**
+     * Negotiates authentication using the username/password protocol
+     * (rfc-1929). The username and password should previously have been
+     * stored using the scheme "SOCKS5" and realm "USER/PASS"; e.g.
+     * AuthorizationInfo.addAuthorization(socks_host, socks_port, "SOCKS5",
+     *					  "USER/PASS", null,
+     *					  { new NVPair(username, password) });
+     *
+     */
+    private void negotiate_userpwd(InputStream inp, OutputStream out)
+	throws SocksException, IOException
+    {
+	byte[] buffer;
+
+
+	Log.write(Log.SOCKS, "Socks: Entering authorization subnegotiation" +
+			     "; method: Username/Password");
+
+	// get username/password
+
+	AuthorizationInfo auth_info;
+	try
+	{
+	    auth_info =
+		AuthorizationInfo.getAuthorization(socks_host, socks_port,
+						   "SOCKS5", "USER/PASS",
+						   null, null, true);
+	}
+	catch (AuthSchemeNotImplException atnie)
+	    { auth_info = null; }
+
+	if (auth_info == null)
+	    throw new SocksException("No Authorization info for SOCKS found " +
+				     "(server requested username/password).");
+
+	NVPair[] unpw = auth_info.getParams();
+	if (unpw == null  ||  unpw.length == 0)
+	    throw new SocksException("No Username/Password found in " +
+				     "authorization info for SOCKS.");
+
+	String user_str = unpw[0].getName();
+	String pass_str = unpw[0].getValue();
+
+
+	// send them to server
+
+	Log.write(Log.SOCKS, "Socks: Sending authorization request for user "+
+			     user_str);
+
+	byte[] utmp = user_str.getBytes();
+	byte[] ptmp = pass_str.getBytes();
+	buffer = new byte[1+1+utmp.length+1+ptmp.length];
+	buffer[0] = 1;				// version 1 (subnegotiation)
+	buffer[1] = (byte) utmp.length;				// Username length
+	System.arraycopy(utmp, 0, buffer, 2, utmp.length);	// Username
+	buffer[2+buffer[1]] = (byte) ptmp.length;		// Password length
+	System.arraycopy(ptmp, 0, buffer, 2+buffer[1]+1, ptmp.length);	// Password
+	out.write(buffer);
+
+
+	// get reply
+
+	int version = inp.read();
+	if (version != 1)
+	    throw new SocksException("Wrong version received in username/" +
+				     "password subnegotiation response: " +
+				     version + "; expected: 1");
+
+	int sts = inp.read();
+	if (sts != 0)
+	    throw new SocksException("Username/Password authentication " +
+				     "failed; status: "+sts);
+
+	Log.write(Log.SOCKS, "Socks: Received response; version: " + version +
+			     "; status: " + sts);
+    }
+
+
+    /**
+     * produces a string.
+     * @return a string containing the host and port of the socks server
+     */
+    public String toString()
+    {
+	return getClass().getName() + "[" + socks_host + ":" + socks_port + "]";
+    }
+}
diff --git a/HTTPClient/SocksException.java b/HTTPClient/SocksException.java
new file mode 100644
index 0000000..61af36e
--- /dev/null
+++ b/HTTPClient/SocksException.java
@@ -0,0 +1,67 @@
+/*
+ * @(#)SocksException.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+
+/**
+ * Signals that an error was received while trying to set up a connection
+ * with the Socks server.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class SocksException extends IOException
+{
+    /**
+     * Constructs a SocksException with no detail message.
+     * A detail message is a String that describes this particular exception.
+     */
+    public SocksException()
+    {
+	super();
+    }
+
+
+    /**
+     * Constructs a SocksException with the specified detail message.
+     * A detail message is a String that describes this particular exception.
+     *
+     * @param s the String containing a detail message
+     */
+    public SocksException(String s)
+    {
+	super(s);
+    }
+
+}
diff --git a/HTTPClient/StreamDemultiplexor.java b/HTTPClient/StreamDemultiplexor.java
new file mode 100644
index 0000000..28fd6d7
--- /dev/null
+++ b/HTTPClient/StreamDemultiplexor.java
@@ -0,0 +1,968 @@
+/*
+ * @(#)StreamDemultiplexor.java				0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.EOFException;
+import java.io.InterruptedIOException;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * This class handles the demultiplexing of input stream. This is needed
+ * for things like keep-alive in HTTP/1.0, persist in HTTP/1.1 and in HTTP-NG.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class StreamDemultiplexor implements GlobalConstants
+{
+    /** the protocol were handling request for */
+    private int                    Protocol;
+
+    /** the connection we're working for */
+    private HTTPConnection         Connection;
+
+    /** the input stream to demultiplex */
+    private BufferedInputStream    Stream;
+
+    /** the socket this hangs off */
+    private Socket                 Sock = null;
+
+    /** signals after the closing of which stream to close the socket */
+    private ResponseHandler        MarkedForClose;
+
+    /** timer used to close the socket if unused for a given time */
+    private SocketTimeout.TimeoutEntry Timer = null;
+
+    /** timer thread which implements the timers */
+    private static SocketTimeout   TimerThread = null;
+
+    /** cleanup object to stop timer thread when we're gc'd */
+    private static Object          cleanup;
+
+    /** a Vector to hold the list of response handlers were serving */
+    private LinkedList             RespHandlerList;
+
+    /** number of unread bytes in current chunk (if transf-enc == chunked) */
+    private long                   chunk_len;
+
+    /** the currently set timeout for the socket */
+    private int                    cur_timeout = 0;
+
+
+    static
+    {
+	TimerThread = new SocketTimeout(60);
+	TimerThread.start();
+
+	/* This is here to clean up the timer thread should the
+	 * StreamDemultiplexor class be gc'd. This will not usually happen,
+	 * unless the stuff is being run in an Applet or similar environment
+	 * where multiple classloaders are used to load the same class
+	 * multiple times. However, even in those environments it's not clear
+	 * that this here will do us any good, because classes aren't usually
+	 * gc'd unless their classloader is, but the timer thread keeps a
+	 * reference to the classloader, and hence ought to prevent the
+	 * classloader from being gc'd.
+	 */
+	cleanup = new Object() {
+	    private final SocketTimeout timer = StreamDemultiplexor.TimerThread;
+
+	    protected void finalize()
+	    {
+		timer.kill();
+	    }
+	};
+    }
+
+
+    // Constructors
+
+    /**
+     * a simple contructor.
+     *
+     * @param protocol   the protocol used on this stream.
+     * @param sock       the socket which we're to demux.
+     * @param connection the http-connection this socket belongs to.
+     */
+    StreamDemultiplexor(int protocol, Socket sock, HTTPConnection connection)
+	    throws IOException
+    {
+	this.Protocol   = protocol;
+	this.Connection = connection;
+	RespHandlerList = new LinkedList();
+	init(sock);
+    }
+
+
+    /**
+     * Initializes the demultiplexor with a new socket.
+     *
+     * @param stream   the stream to demultiplex
+     */
+    private void init(Socket sock)  throws IOException
+    {
+	Log.write(Log.DEMUX, "Demux: Initializing Stream Demultiplexor (" +
+			     this.hashCode() + ")");
+
+	this.Sock       = sock;
+	this.Stream     = new BufferedInputStream(sock.getInputStream());
+	MarkedForClose  = null;
+	chunk_len       = -1;
+
+	// create a timer to close the socket after 60 seconds, but don't
+	// start it yet
+	Timer = TimerThread.setTimeout(this);
+	Timer.hyber();
+    }
+
+
+    // Methods
+
+    /**
+     * Each Response must register with us.
+     */
+    void register(Response resp_handler, Request req)  throws RetryException
+    {
+	synchronized (RespHandlerList)
+	{
+	    if (Sock == null)
+		throw new RetryException();
+
+	    RespHandlerList.addToEnd(
+				new ResponseHandler(resp_handler, req, this));
+	}
+    }
+
+    /**
+     * creates an input stream for the response.
+     *
+     * @param resp the response structure requesting the stream
+     * @return an InputStream
+     */
+    RespInputStream getStream(Response resp)
+    {
+	ResponseHandler resph;
+	synchronized (RespHandlerList)
+	{
+	    for (resph = (ResponseHandler) RespHandlerList.enumerate();
+		 resph != null;
+		 resph = (ResponseHandler) RespHandlerList.next())
+	    {
+		if (resph.resp == resp)  break;
+	    }
+	}
+
+	if (resph != null)
+	    return resph.stream;
+	else
+	    return null;
+    }
+
+
+    /**
+     * Restarts the timer thread that will close an unused socket after
+     * 60 seconds.
+     */
+    void restartTimer()
+    {
+	if (Timer != null)  Timer.reset();
+    }
+
+
+    /**
+     * reads an array of bytes from the master stream.
+     */
+    int read(byte[] b, int off, int len, ResponseHandler resph, int timeout)
+	    throws IOException
+    {
+	if (resph.exception != null)
+	{
+	    resph.exception.fillInStackTrace();
+	    throw resph.exception;
+	}
+
+	if (resph.eof)
+	    return -1;
+
+
+	// read the headers and data for all responses preceding us.
+
+	ResponseHandler head;
+	while ((head = (ResponseHandler) RespHandlerList.getFirst()) != null  &&
+		head != resph)
+	{
+	    try
+		{ head.stream.readAll(timeout); }
+	    catch (IOException ioe)
+	    {
+		if (ioe instanceof InterruptedIOException)
+		    throw ioe;
+		else
+		{
+		    resph.exception.fillInStackTrace();
+		    throw resph.exception;
+		}
+	    }
+	}
+
+
+	// Now we can read from the stream.
+
+	synchronized (this)
+	{
+	    if (resph.exception != null)
+	    {
+		resph.exception.fillInStackTrace();
+		throw resph.exception;
+	    }
+
+	    if (resph.resp.cd_type != CD_HDRS)
+		Log.write(Log.DEMUX, "Demux: Reading for stream " +
+				     resph.stream.hashCode());
+
+	    if (Timer != null)  Timer.hyber();
+
+	    try
+	    {
+		int rcvd = -1;
+
+		if (timeout != cur_timeout)
+		{
+		    Log.write(Log.DEMUX, "Demux: Setting timeout to " +
+					 timeout + " ms");
+
+		    Sock.setSoTimeout(timeout);
+		    cur_timeout = timeout;
+		}
+
+		switch (resph.resp.cd_type)
+		{
+		    case CD_HDRS:
+			rcvd = Stream.read(b, off, len);
+			if (rcvd == -1)
+			    throw new EOFException("Premature EOF encountered");
+			break;
+
+		    case CD_0:
+			rcvd = -1;
+			close(resph);
+			break;
+
+		    case CD_CLOSE:
+			rcvd = Stream.read(b, off, len);
+			if (rcvd == -1)
+			    close(resph);
+			break;
+
+		    case CD_CONTLEN:
+			int cl = resph.resp.ContentLength;
+			if (len > cl - resph.stream.count)
+			    len = cl - resph.stream.count;
+
+			rcvd = Stream.read(b, off, len);
+			if (rcvd == -1)
+			    throw new EOFException("Premature EOF encountered");
+
+			if (resph.stream.count+rcvd == cl)
+			    close(resph);
+
+			break;
+
+		    case CD_CHUNKED:
+			if (chunk_len == -1)	// it's a new chunk
+			    chunk_len = Codecs.getChunkLength(Stream);
+
+			if (chunk_len > 0)		// it's data
+			{
+			    if (len > chunk_len)  len = (int) chunk_len;
+			    rcvd = Stream.read(b, off, len);
+			    if (rcvd == -1)
+				throw new EOFException("Premature EOF encountered");
+			    chunk_len -= rcvd;
+			    if (chunk_len == 0)	// got the whole chunk
+			    {
+				Stream.read();	// CR
+				Stream.read();	// LF
+				chunk_len = -1;
+			    }
+			}
+			else	// the footers (trailers)
+			{
+			    resph.resp.readTrailers(Stream);
+			    rcvd = -1;
+			    close(resph);
+			    chunk_len = -1;
+			}
+			break;
+
+		    case CD_MP_BR:
+			byte[] endbndry = resph.getEndBoundary(Stream);
+			int[]  end_cmp  = resph.getEndCompiled(Stream);
+
+			rcvd = Stream.read(b, off, len);
+			if (rcvd == -1)
+			    throw new EOFException("Premature EOF encountered");
+
+			int ovf = Stream.pastEnd(endbndry, end_cmp);
+			if (ovf != -1)
+			{
+			    rcvd -= ovf;
+			    close(resph);
+			}
+
+			break;
+
+		    default:
+			throw new Error("Internal Error in StreamDemultiplexor: " +
+					"Invalid cd_type " + resph.resp.cd_type);
+		}
+
+		restartTimer();
+		return rcvd;
+
+	    }
+	    catch (InterruptedIOException ie)	// don't intercept this one
+	    {
+		restartTimer();
+		throw ie;
+	    }
+	    catch (IOException ioe)
+	    {
+		Log.write(Log.DEMUX, "Demux: ", ioe);
+
+		close(ioe, true);
+		throw resph.exception;		// set by retry_requests
+	    }
+	    catch (ParseException pe)
+	    {
+		Log.write(Log.DEMUX, "Demux: ", pe);
+
+		close(new IOException(pe.toString()), true);
+		throw resph.exception;		// set by retry_requests
+	    }
+	}
+    }
+
+    /**
+     * skips a number of bytes in the master stream. This is done via a
+     * dummy read, as the socket input stream doesn't like skip()'s.
+     */
+    synchronized long skip(long num, ResponseHandler resph) throws IOException
+    {
+	if (resph.exception != null)
+	{
+	    resph.exception.fillInStackTrace();
+	    throw resph.exception;
+	}
+
+	if (resph.eof)
+	    return 0;
+
+	byte[] dummy = new byte[(int) num];
+	int rcvd = read(dummy, 0, (int) num, resph, 0);
+	if (rcvd == -1)
+	    return 0;
+	else
+	    return rcvd;
+    }
+
+    /**
+     * Determines the number of available bytes. If <var>resph</var> is null, return
+     * available bytes on the socket stream itself (used by HTTPConnection).
+     */
+    synchronized int available(ResponseHandler resph) throws IOException
+    {
+	if (resph != null  &&  resph.exception != null)
+	{
+	    resph.exception.fillInStackTrace();
+	    throw resph.exception;
+	}
+
+	if (resph != null  &&  resph.eof)
+	    return 0;
+
+	int avail = Stream.available();
+	if (resph == null)
+	  return avail;
+
+	switch (resph.resp.cd_type)
+	{
+	    case CD_0:
+		return 0;
+	    case CD_HDRS:
+		// this is something of a hack; I could return 0, but then
+		// if you were waiting for something on a response that
+		// wasn't first in line (and you didn't try to read the
+		// other response) you'd wait forever. On the other hand,
+		// we might be making a false promise here...
+		return (avail > 0 ? 1 : 0);
+	    case CD_CLOSE:
+		return avail;
+	    case CD_CONTLEN:
+		int cl = resph.resp.ContentLength;
+		cl -= resph.stream.count;
+		return (avail < cl ? avail : cl);
+	    case CD_CHUNKED:
+		return avail;	// not perfect...
+	    case CD_MP_BR:
+		return avail;	// not perfect...
+	    default:
+		throw new Error("Internal Error in StreamDemultiplexor: " +
+				"Invalid cd_type " + resph.resp.cd_type);
+	}
+
+    }
+
+
+    /**
+     * Closes the socket and all associated streams. If <var>exception</var>
+     * is not null then all active requests are retried.
+     *
+     * <P>There are five ways this method may be activated. 1) if an exception
+     * occurs during read or write. 2) if the stream is marked for close but
+     * no responses are outstanding (e.g. due to a timeout). 3) when the
+     * markedForClose response is closed. 4) if all response streams up until
+     * and including the markedForClose response have been closed. 5) if this
+     * demux is finalized.
+     *
+     * @param exception the IOException to be sent to the streams.
+     * @param was_reset if true then the exception is due to a connection
+     *                  reset; otherwise it means we generated the exception
+     *                  ourselves and this is a "normal" close.
+     */
+    synchronized void close(IOException exception, boolean was_reset)
+    {
+	if (Sock == null)	// already cleaned up
+	    return;
+
+	Log.write(Log.DEMUX, "Demux: Closing all streams and socket (" +
+			     this.hashCode() + ")");
+
+	try
+	    { Stream.close(); }
+	catch (IOException ioe) { }
+	try
+	    { Sock.close(); }
+	catch (IOException ioe) { }
+	Sock = null;
+
+	if (Timer != null)
+	{
+	    Timer.kill();
+	    Timer = null;
+	}
+
+	Connection.DemuxList.remove(this);
+
+
+	// Here comes the tricky part: redo outstanding requests!
+
+	if (exception != null)
+	    synchronized (RespHandlerList)
+		{ retry_requests(exception, was_reset); }
+    }
+
+
+    /**
+     * Retries outstanding requests. Well, actually the RetryModule does
+     * that. Here we just throw a RetryException for each request so that
+     * the RetryModule can catch and handle them.
+     *
+     * @param exception the exception that led to this call.
+     * @param was_reset this flag is passed to the RetryException and is
+     *                  used by the RetryModule to distinguish abnormal closes
+     *                  from expected closes.
+     */
+    private void retry_requests(IOException exception, boolean was_reset)
+    {
+	RetryException  first = null,
+			prev  = null;
+	ResponseHandler resph = (ResponseHandler) RespHandlerList.enumerate();
+
+	while (resph != null)
+	{
+	    /* if the application is already reading the data then the
+	     * response has already been handled. In this case we must
+	     * throw the real exception.
+	     */
+	    if (resph.resp.got_headers)
+	    {
+		resph.exception = exception;
+	    }
+	    else
+	    {
+		RetryException tmp = new RetryException(exception.getMessage());
+		if (first == null)  first = tmp;
+
+		tmp.request    = resph.request;
+		tmp.response   = resph.resp;
+		tmp.exception  = exception;
+		tmp.conn_reset = was_reset;
+		tmp.first      = first;
+		tmp.addToListAfter(prev);
+
+		prev = tmp;
+		resph.exception = tmp;
+	    }
+
+	    RespHandlerList.remove(resph);
+	    resph = (ResponseHandler) RespHandlerList.next();
+	}
+    }
+
+
+    /**
+     * Closes the associated stream. If this one has been markedForClose then
+     * the socket is closed; else closeSocketIfAllStreamsClosed is invoked.
+     */
+    private void close(ResponseHandler resph)
+    {
+	synchronized (RespHandlerList)
+	{
+	    if (resph != (ResponseHandler) RespHandlerList.getFirst())
+		return;
+
+	    Log.write(Log.DEMUX, "Demux: Closing stream " +
+				 resph.stream.hashCode());
+
+	    resph.eof = true;
+	    RespHandlerList.remove(resph);
+	}
+
+	if (resph == MarkedForClose)
+	    close(new IOException("Premature end of Keep-Alive"), false);
+	else
+	    closeSocketIfAllStreamsClosed();
+    }
+
+
+    /**
+     * Close the socket if all the streams have been closed.
+     *
+     * <P>When a stream reaches eof it is removed from the response handler
+     * list, but when somebody close()'s the response stream it is just
+     * marked as such. This means that all responses in the list have either
+     * not been read at all or only partially read, but they might have been
+     * close()'d meaning that nobody is interested in the data. So If all the
+     * response streams up till and including the one markedForClose have
+     * been close()'d then we can remove them from our list and close the
+     * socket.
+     *
+     * <P>Note: if the response list is emtpy or if no response is
+     * markedForClose then this method does nothing. Specifically it does
+     * not close the socket. We only want to close the socket if we've been
+     * told to do so.
+     *
+     * <P>Also note that there might still be responses in the list after
+     * the markedForClose one. These are due to us having pipelined more
+     * requests to the server than it's willing to serve on a single
+     * connection. These requests will be retried if possible.
+     */
+    synchronized void closeSocketIfAllStreamsClosed()
+    {
+	synchronized (RespHandlerList)
+	{
+	    ResponseHandler resph = (ResponseHandler) RespHandlerList.enumerate();
+
+	    while (resph != null  &&  resph.stream.closed)
+	    {
+		if (resph == MarkedForClose)
+		{
+		    // remove all response handlers first
+		    ResponseHandler tmp;
+		    do
+		    {
+			tmp = (ResponseHandler) RespHandlerList.getFirst();
+			RespHandlerList.remove(tmp);
+		    }
+		    while (tmp != resph);
+
+		    // close the socket
+		    close(new IOException("Premature end of Keep-Alive"), false);
+		    return;
+		}
+
+		resph = (ResponseHandler) RespHandlerList.next();
+	    }
+	}
+    }
+
+
+    /**
+     * returns the socket associated with this demux
+     */
+    synchronized Socket getSocket()
+    {
+	if (MarkedForClose != null)
+	    return null;
+
+	if (Timer != null)  Timer.hyber();
+	return Sock;
+    }
+
+
+    /**
+     * Mark this demux to not accept any more request and to close the
+     * stream after this <var>resp</var>onse or all requests have been
+     * processed, or close immediately if no requests are registered.
+     *
+     * @param response the Response after which the connection should
+     *                 be closed.
+     */
+    synchronized void markForClose(Response resp)
+    {
+	synchronized (RespHandlerList)
+	{
+	    if (RespHandlerList.getFirst() == null)	// no active request,
+	    {						// so close the socket
+		close(new IOException("Premature end of Keep-Alive"), false);
+		return;
+	    }
+
+	    if (Timer != null)
+	    {
+		Timer.kill();
+		Timer = null;
+	    }
+
+	    ResponseHandler resph, lasth = null;
+	    for (resph = (ResponseHandler) RespHandlerList.enumerate();
+		 resph != null;
+		 resph = (ResponseHandler) RespHandlerList.next())
+	    {
+		if (resph.resp == resp)	// new resp precedes any others
+		{
+		    MarkedForClose = resph;
+
+		    Log.write(Log.DEMUX, "Demux: stream " +
+					 resp.inp_stream.hashCode() +
+					 " marked for close");
+
+		    closeSocketIfAllStreamsClosed();
+		    return;
+		}
+
+		if (MarkedForClose == resph)
+		    return;	// already marked for closing after an earlier resp
+
+		lasth = resph;
+	    }
+
+	    if (lasth == null)
+		return;
+
+	    MarkedForClose = lasth;		// resp == null, so use last resph
+	    closeSocketIfAllStreamsClosed();
+
+	    Log.write(Log.DEMUX, "Demux: stream " + lasth.stream.hashCode() +
+				 " marked for close");
+	}
+    }
+
+
+    /**
+     * Emergency stop. Closes the socket and notifies the responses that
+     * the requests are aborted.
+     *
+     * @since V0.3
+     */
+    void abort()
+    {
+	Log.write(Log.DEMUX, "Demux: Aborting socket (" + this.hashCode() + ")");
+
+
+	// notify all responses of abort
+
+	synchronized (RespHandlerList)
+	{
+	    for (ResponseHandler resph =
+				(ResponseHandler) RespHandlerList.enumerate();
+		 resph != null;
+		 resph = (ResponseHandler) RespHandlerList.next())
+	    {
+		if (resph.resp.http_resp != null)
+		    resph.resp.http_resp.markAborted();
+		if (resph.exception == null)
+		    resph.exception = new IOException("Request aborted by user");
+	    }
+
+
+	    /* Close the socket.
+	     * Note: this duplicates most of close(IOException, boolean). We
+	     * do *not* call close() because that is synchronized, but we want
+	     * abort() to be asynch.
+	     */
+	    if (Sock != null)
+	    {
+		try
+		{
+		    try
+			{ Sock.setSoLinger(false, 0); }
+		    catch (SocketException se)
+			{ }
+
+		    try
+			{ Stream.close(); }
+		    catch (IOException ioe) { }
+		    try
+			{ Sock.close(); }
+		    catch (IOException ioe) { }
+		    Sock = null;
+
+		    if (Timer != null)
+		    {
+			Timer.kill();
+			Timer = null;
+		    }
+		}
+		catch (NullPointerException npe)
+		    { }
+
+		Connection.DemuxList.remove(this);
+	    }
+	}
+    }
+
+
+    /**
+     * A safety net to close the connection.
+     */
+    protected void finalize() throws Throwable
+    {
+	close((IOException) null, false);
+	super.finalize();
+    }
+
+
+    /**
+     * produces a string.
+     * @return a string containing the class name and protocol number
+     */
+    public String toString()
+    {
+	String prot;
+
+	switch (Protocol)
+	{
+	    case HTTP:
+		prot = "HTTP"; break;
+	    case HTTPS:
+		prot = "HTTPS"; break;
+	    case SHTTP:
+		prot = "SHTTP"; break;
+	    case HTTP_NG:
+		prot = "HTTP_NG"; break;
+	    default:
+		throw new Error("HTTPClient Internal Error: invalid protocol " +
+				Protocol);
+	}
+
+	return getClass().getName() + "[Protocol=" + prot + "]";
+    }
+}
+
+
+/**
+ * This thread is used to reap idle connections. It is NOT used to timeout
+ * reads or writes on a socket. It keeps a list of timer entries and expires
+ * them after a given time.
+ */
+class SocketTimeout extends Thread
+{
+    private boolean alive = true;
+
+    /**
+     * This class represents a timer entry. It is used to close an
+     * inactive socket after n seconds. Once running, the timer may be
+     * suspended (hyber()), restarted (reset()), or aborted (kill()).
+     * When the timer expires it invokes markForClose() on the
+     * associated stream demultipexer.
+     */
+    class TimeoutEntry
+    {
+	boolean restart = false,
+		hyber   = false,
+		alive   = true;
+	StreamDemultiplexor demux;
+	TimeoutEntry next = null,
+		     prev = null;
+
+	TimeoutEntry(StreamDemultiplexor demux)
+	{
+	    this.demux = demux;
+	}
+
+	void reset()
+	{
+	    hyber = false;
+	    if (restart)  return;
+	    restart = true;
+
+	    synchronized (time_list)
+	    {
+		if (!alive)  return;
+
+		// remove from current position
+		next.prev = prev;
+		prev.next = next;
+
+		// and add to end of timeout list
+		next = time_list[current];
+		prev = time_list[current].prev;
+		prev.next = this;
+		next.prev = this; 
+	    }
+	}
+
+	void hyber()
+	{
+	    if (alive)  hyber = true;
+	}
+
+	void kill()
+	{
+	    alive   = false;
+	    restart = false;
+	    hyber   = false;
+
+	    synchronized (time_list)
+	    {
+		if (prev == null)  return;
+		next.prev = prev;
+		prev.next = next;
+		prev = null;
+	    }
+	}
+    }
+
+    TimeoutEntry[] time_list;	// jdk 1.1.x javac bug: these must not
+    int		   current;	//   be private!
+
+
+    SocketTimeout(int secs)
+    {
+	super("SocketTimeout");
+
+	try { setDaemon(true); }
+	catch (SecurityException se) { }	// Oh well...
+	setPriority(MAX_PRIORITY);
+
+	time_list = new TimeoutEntry[secs];
+	for (int idx=0; idx<secs; idx++)
+	{
+	    time_list[idx] = new TimeoutEntry(null);
+	    time_list[idx].next = time_list[idx].prev = time_list[idx];
+	}
+	current = 0;
+    }
+
+
+    public TimeoutEntry setTimeout(StreamDemultiplexor demux)
+    {
+	TimeoutEntry entry = new TimeoutEntry(demux);
+	synchronized (time_list)
+	{
+	    entry.next = time_list[current];
+	    entry.prev = time_list[current].prev;
+	    entry.prev.next = entry;
+	    entry.next.prev = entry; 
+	}
+
+	return entry;
+    }
+
+
+    /**
+     * This timer is implemented by sleeping for 1 second and then
+     * checking the timer list.
+     */
+    public void run()
+    {
+	TimeoutEntry marked = null;
+
+	while (alive)
+	{
+	    try { sleep(1000L); } catch (InterruptedException ie) { }
+
+	    synchronized (time_list)
+	    {
+		// reset all restart flags
+		for (TimeoutEntry entry = time_list[current].next;
+		     entry != time_list[current];
+		     entry = entry.next)
+		{
+		    entry.restart = false;
+		}
+
+		current++;
+		if (current >= time_list.length)
+		    current = 0;
+
+		// remove all expired timers 
+		for (TimeoutEntry entry = time_list[current].next;
+		     entry != time_list[current];
+		     entry = entry.next)
+		{
+		    if (entry.alive  &&  !entry.hyber)
+		    {
+			TimeoutEntry prev = entry.prev;
+			entry.kill();
+			/* put on death row. Note: we must not invoke
+			 * markForClose() here because it is synch'd
+			 * and can therefore lead to a deadlock if that
+			 * thread is trying to do a reset() or kill()
+			 */
+			entry.next = marked;
+			marked = entry;
+			entry = prev;
+		    }
+		}
+	    }
+
+	    while (marked != null)
+	    {
+		marked.demux.markForClose(null);
+		marked = marked.next;
+	    }
+	}
+    }
+
+    /**
+     * Stop the timer thread.
+     */
+    public void kill() {
+	alive = false;
+    }
+}
diff --git a/HTTPClient/TransferEncodingModule.java b/HTTPClient/TransferEncodingModule.java
new file mode 100644
index 0000000..cb0ded0
--- /dev/null
+++ b/HTTPClient/TransferEncodingModule.java
@@ -0,0 +1,216 @@
+/*
+ * @(#)TransferEncodingModule.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.util.Vector;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.GZIPInputStream;
+
+
+/**
+ * This module handles the TransferEncoding response header. It currently
+ * handles the "gzip", "deflate", "compress", "chunked" and "identity"
+ * tokens.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+class TransferEncodingModule implements HTTPClientModule
+{
+    // Methods
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int requestHandler(Request req, Response[] resp)
+	    throws ModuleException
+    {
+	// Parse TE header
+
+	int idx;
+	NVPair[] hdrs = req.getHeaders();
+	for (idx=0; idx<hdrs.length; idx++)
+	    if (hdrs[idx].getName().equalsIgnoreCase("TE"))
+		break;
+
+	Vector pte;
+	if (idx == hdrs.length)
+	{
+	    hdrs = Util.resizeArray(hdrs, idx+1);
+	    req.setHeaders(hdrs);
+	    pte = new Vector();
+	}
+	else
+	{
+	    try
+		{ pte = Util.parseHeader(hdrs[idx].getValue()); }
+	    catch (ParseException pe)
+		{ throw new ModuleException(pe.toString()); }
+	}
+
+
+        // done if "*;q=1.0" present
+ 
+        HttpHeaderElement all = Util.getElement(pte, "*");
+        if (all != null)
+	{
+	    NVPair[] params = all.getParams();
+	    for (idx=0; idx<params.length; idx++)
+		if (params[idx].getName().equalsIgnoreCase("q"))  break;
+
+	    if (idx == params.length)   // no qvalue, i.e. q=1.0
+		return REQ_CONTINUE;
+
+	    if (params[idx].getValue() == null  ||
+		params[idx].getValue().length() == 0)
+		throw new ModuleException("Invalid q value for \"*\" in TE " +
+					  "header: ");
+
+	    try
+	    {
+		if (Float.valueOf(params[idx].getValue()).floatValue() > 0.)
+		    return REQ_CONTINUE;
+	    }
+	    catch (NumberFormatException nfe)
+	    {
+		throw new ModuleException("Invalid q value for \"*\" in TE " +
+					  "header: " + nfe.getMessage());
+	    }
+	}
+ 
+
+	// Add gzip, deflate, and compress tokens to the TE header
+
+	if (!pte.contains(new HttpHeaderElement("deflate")))
+	    pte.addElement(new HttpHeaderElement("deflate"));
+	if (!pte.contains(new HttpHeaderElement("gzip")))
+	    pte.addElement(new HttpHeaderElement("gzip"));
+	if (!pte.contains(new HttpHeaderElement("compress")))
+	    pte.addElement(new HttpHeaderElement("compress"));
+
+	hdrs[idx] = new NVPair("TE", Util.assembleHeader(pte));
+
+	return REQ_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase1Handler(Response resp, RoRequest req)
+    {
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public int responsePhase2Handler(Response resp, Request req)
+    {
+	return RSP_CONTINUE;
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void responsePhase3Handler(Response resp, RoRequest req)
+		throws IOException, ModuleException
+    {
+	String te = resp.getHeader("Transfer-Encoding");
+	if (te == null  ||  req.getMethod().equals("HEAD"))
+	    return;
+
+	Vector pte;
+	try
+	    { pte = Util.parseHeader(te); }
+	catch (ParseException pe)
+	    { throw new ModuleException(pe.toString()); }
+
+	while (pte.size() > 0)
+	{
+	    String encoding = ((HttpHeaderElement) pte.lastElement()).getName();
+	    if (encoding.equalsIgnoreCase("gzip"))
+	    {
+		Log.write(Log.MODS, "TEM:   pushing gzip-input-stream");
+
+		resp.inp_stream = new GZIPInputStream(resp.inp_stream);
+	    }
+	    else if (encoding.equalsIgnoreCase("deflate"))
+	    {
+		Log.write(Log.MODS, "TEM:   pushing inflater-input-stream");
+
+		resp.inp_stream = new InflaterInputStream(resp.inp_stream);
+	    }
+	    else if (encoding.equalsIgnoreCase("compress"))
+	    {
+		Log.write(Log.MODS, "TEM:   pushing uncompress-input-stream");
+
+		resp.inp_stream = new UncompressInputStream(resp.inp_stream);
+	    }
+	    else if (encoding.equalsIgnoreCase("chunked"))
+	    {
+		Log.write(Log.MODS, "TEM:   pushing chunked-input-stream");
+
+		resp.inp_stream = new ChunkedInputStream(resp.inp_stream);
+	    }
+	    else if (encoding.equalsIgnoreCase("identity"))
+	    {
+		Log.write(Log.MODS, "TEM:   ignoring 'identity' token");
+	    }
+	    else
+	    {
+		Log.write(Log.MODS, "TEM:   Unknown transfer encoding '" +
+				    encoding + "'");
+
+		break;
+	    }
+
+	    pte.removeElementAt(pte.size()-1);
+	}
+
+	if (pte.size() > 0)
+	    resp.setHeader("Transfer-Encoding", Util.assembleHeader(pte));
+	else
+	    resp.deleteHeader("Transfer-Encoding");
+    }
+
+
+    /**
+     * Invoked by the HTTPClient.
+     */
+    public void trailerHandler(Response resp, RoRequest req)
+    {
+    }
+}
diff --git a/HTTPClient/URI.java b/HTTPClient/URI.java
new file mode 100644
index 0000000..1596298
--- /dev/null
+++ b/HTTPClient/URI.java
@@ -0,0 +1,1939 @@
+/*
+ * @(#)URI.java						0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.util.BitSet;
+import java.util.Hashtable;
+
+/**
+ * This class represents a generic URI, as defined in RFC-2396.
+ * This is similar to java.net.URL, with the following enhancements:
+ * <UL>
+ * <LI>it doesn't require a URLStreamhandler to exist for the scheme; this
+ *     allows this class to be used to hold any URI, construct absolute
+ *     URIs from relative ones, etc.
+ * <LI>it handles escapes correctly
+ * <LI>equals() works correctly
+ * <LI>relative URIs are correctly constructed
+ * <LI>it has methods for accessing various fields such as userinfo,
+ *     fragment, params, etc.
+ * <LI>it handles less common forms of resources such as the "*" used in
+ *     http URLs.
+ * </UL>
+ *
+ * <P>The elements are always stored in escaped form.
+ *
+ * <P>While RFC-2396 distinguishes between just two forms of URI's, those that
+ * follow the generic syntax and those that don't, this class knows about a
+ * third form, named semi-generic, used by quite a few popular schemes.
+ * Semi-generic syntax treats the path part as opaque, i.e. has the form
+ * <scheme>://<authority>/<opaque> . Relative URI's of this
+ * type are only resolved as far as absolute paths - relative paths do not
+ * exist.
+ *
+ * <P>Ideally, java.net.URL should subclass URI.
+ *
+ * @see		<A HREF="http://www.ics.uci.edu/pub/ietf/uri/rfc2396.txt">rfc-2396</A>
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @since	V0.3-1
+ */
+public class URI
+{
+    /**
+     * If true, then the parser will resolve certain URI's in backwards
+     * compatible (but technically incorrect) manner. Example:
+     *
+     *<PRE>
+     * base   = http://a/b/c/d;p?q
+     * rel    = http:g
+     * result = http:g		(correct)
+     * result = http://a/b/c/g	(backwards compatible)
+     *</PRE>
+     *
+     * See rfc-2396, section 5.2, step 3, second paragraph.
+     */
+    public static final boolean ENABLE_BACKWARDS_COMPATIBILITY = true;
+
+    protected static final Hashtable defaultPorts          = new Hashtable();
+    protected static final Hashtable usesGenericSyntax     = new Hashtable();
+    protected static final Hashtable usesSemiGenericSyntax = new Hashtable();
+
+    /* various character classes as defined in the draft */
+    protected static final BitSet alphanumChar;
+    protected static final BitSet markChar;
+    protected static final BitSet reservedChar;
+    protected static final BitSet unreservedChar;
+    protected static final BitSet uricChar;
+    protected static final BitSet pcharChar;
+    protected static final BitSet userinfoChar;
+    protected static final BitSet schemeChar;
+    protected static final BitSet hostChar;
+    protected static final BitSet opaqueChar;
+    protected static final BitSet reg_nameChar;
+
+    /* These are not directly in the spec, but used for escaping and
+     * unescaping parts
+     */
+
+    /** list of characters which must not be unescaped when unescaping a scheme */
+    public static final BitSet resvdSchemeChar;
+    /** list of characters which must not be unescaped when unescaping a userinfo */
+    public static final BitSet resvdUIChar;
+    /** list of characters which must not be unescaped when unescaping a host */
+    public static final BitSet resvdHostChar;
+    /** list of characters which must not be unescaped when unescaping a path */
+    public static final BitSet resvdPathChar;
+    /** list of characters which must not be unescaped when unescaping a query string */
+    public static final BitSet resvdQueryChar;
+    /** list of characters which must not be escaped when escaping a path */
+    public static final BitSet escpdPathChar;
+    /** list of characters which must not be escaped when escaping a query string */
+    public static final BitSet escpdQueryChar;
+    /** list of characters which must not be escaped when escaping a fragment identifier */
+    public static final BitSet escpdFragChar;
+
+    static
+    {
+	defaultPorts.put("http",      new Integer(80));
+	defaultPorts.put("shttp",     new Integer(80));
+	defaultPorts.put("http-ng",   new Integer(80));
+	defaultPorts.put("coffee",    new Integer(80));
+	defaultPorts.put("https",     new Integer(443));
+	defaultPorts.put("ftp",       new Integer(21));
+	defaultPorts.put("telnet",    new Integer(23));
+	defaultPorts.put("nntp",      new Integer(119));
+	defaultPorts.put("news",      new Integer(119));
+	defaultPorts.put("snews",     new Integer(563));
+	defaultPorts.put("hnews",     new Integer(80));
+	defaultPorts.put("smtp",      new Integer(25));
+	defaultPorts.put("gopher",    new Integer(70));
+	defaultPorts.put("wais",      new Integer(210));
+	defaultPorts.put("whois",     new Integer(43));
+	defaultPorts.put("whois++",   new Integer(63));
+	defaultPorts.put("rwhois",    new Integer(4321));
+	defaultPorts.put("imap",      new Integer(143));
+	defaultPorts.put("pop",       new Integer(110));
+	defaultPorts.put("prospero",  new Integer(1525));
+	defaultPorts.put("irc",       new Integer(194));
+	defaultPorts.put("ldap",      new Integer(389));
+	defaultPorts.put("nfs",       new Integer(2049));
+	defaultPorts.put("z39.50r",   new Integer(210));
+	defaultPorts.put("z39.50s",   new Integer(210));
+	defaultPorts.put("vemmi",     new Integer(575));
+	defaultPorts.put("videotex",  new Integer(516));
+	defaultPorts.put("cmp",       new Integer(829));
+
+	usesGenericSyntax.put("http", Boolean.TRUE);
+	usesGenericSyntax.put("https", Boolean.TRUE);
+	usesGenericSyntax.put("shttp", Boolean.TRUE);
+	usesGenericSyntax.put("coffee", Boolean.TRUE);
+	usesGenericSyntax.put("ftp", Boolean.TRUE);
+	usesGenericSyntax.put("file", Boolean.TRUE);
+	usesGenericSyntax.put("nntp", Boolean.TRUE);
+	usesGenericSyntax.put("news", Boolean.TRUE);
+	usesGenericSyntax.put("snews", Boolean.TRUE);
+	usesGenericSyntax.put("hnews", Boolean.TRUE);
+	usesGenericSyntax.put("imap", Boolean.TRUE);
+	usesGenericSyntax.put("wais", Boolean.TRUE);
+	usesGenericSyntax.put("nfs", Boolean.TRUE);
+	usesGenericSyntax.put("sip", Boolean.TRUE);
+	usesGenericSyntax.put("sips", Boolean.TRUE);
+	usesGenericSyntax.put("sipt", Boolean.TRUE);
+	usesGenericSyntax.put("sipu", Boolean.TRUE);
+	/* Note: schemes which definitely don't use the generic-URI syntax
+	 * and must therefore never appear in the above list:
+	 * "urn", "mailto", "sdp", "service", "tv", "gsm-sms", "tel", "fax",
+	 * "modem", "eid", "cid", "mid", "data", "ldap"
+	 */
+
+	usesSemiGenericSyntax.put("ldap", Boolean.TRUE);
+	usesSemiGenericSyntax.put("irc", Boolean.TRUE);
+	usesSemiGenericSyntax.put("gopher", Boolean.TRUE);
+	usesSemiGenericSyntax.put("videotex", Boolean.TRUE);
+	usesSemiGenericSyntax.put("rwhois", Boolean.TRUE);
+	usesSemiGenericSyntax.put("whois++", Boolean.TRUE);
+	usesSemiGenericSyntax.put("smtp", Boolean.TRUE);
+	usesSemiGenericSyntax.put("telnet", Boolean.TRUE);
+	usesSemiGenericSyntax.put("prospero", Boolean.TRUE);
+	usesSemiGenericSyntax.put("pop", Boolean.TRUE);
+	usesSemiGenericSyntax.put("vemmi", Boolean.TRUE);
+	usesSemiGenericSyntax.put("z39.50r", Boolean.TRUE);
+	usesSemiGenericSyntax.put("z39.50s", Boolean.TRUE);
+	usesSemiGenericSyntax.put("stream", Boolean.TRUE);
+	usesSemiGenericSyntax.put("cmp", Boolean.TRUE);
+
+	alphanumChar = new BitSet(128);
+	for (int ch='0'; ch<='9'; ch++)  alphanumChar.set(ch);
+	for (int ch='A'; ch<='Z'; ch++)  alphanumChar.set(ch);
+	for (int ch='a'; ch<='z'; ch++)  alphanumChar.set(ch);
+
+	markChar = new BitSet(128);
+	markChar.set('-');
+	markChar.set('_');
+	markChar.set('.');
+	markChar.set('!');
+	markChar.set('~');
+	markChar.set('*');
+	markChar.set('\'');
+	markChar.set('(');
+	markChar.set(')');
+
+	reservedChar = new BitSet(128);
+	reservedChar.set(';');
+	reservedChar.set('/');
+	reservedChar.set('?');
+	reservedChar.set(':');
+	reservedChar.set('@');
+	reservedChar.set('&');
+	reservedChar.set('=');
+	reservedChar.set('+');
+	reservedChar.set('$');
+	reservedChar.set(',');
+
+	unreservedChar = new BitSet(128);
+	unreservedChar.or(alphanumChar);
+	unreservedChar.or(markChar);
+
+	uricChar = new BitSet(128);
+	uricChar.or(unreservedChar);
+	uricChar.or(reservedChar);
+	uricChar.set('%');
+
+	pcharChar = new BitSet(128);
+	pcharChar.or(unreservedChar);
+	pcharChar.set('%');
+	pcharChar.set(':');
+	pcharChar.set('@');
+	pcharChar.set('&');
+	pcharChar.set('=');
+	pcharChar.set('+');
+	pcharChar.set('$');
+	pcharChar.set(',');
+
+	userinfoChar = new BitSet(128);
+	userinfoChar.or(unreservedChar);
+	userinfoChar.set('%');
+	userinfoChar.set(';');
+	userinfoChar.set(':');
+	userinfoChar.set('&');
+	userinfoChar.set('=');
+	userinfoChar.set('+');
+	userinfoChar.set('$');
+	userinfoChar.set(',');
+
+	// this actually shouldn't contain uppercase letters...
+	schemeChar = new BitSet(128);
+	schemeChar.or(alphanumChar);
+	schemeChar.set('+');
+	schemeChar.set('-');
+	schemeChar.set('.');
+
+	opaqueChar = new BitSet(128);
+	opaqueChar.or(uricChar);
+
+	hostChar = new BitSet(128);
+	hostChar.or(alphanumChar);
+	hostChar.set('-');
+	hostChar.set('.');
+
+	reg_nameChar = new BitSet(128);
+	reg_nameChar.or(unreservedChar);
+	reg_nameChar.set('$');
+	reg_nameChar.set(',');
+	reg_nameChar.set(';');
+	reg_nameChar.set(':');
+	reg_nameChar.set('@');
+	reg_nameChar.set('&');
+	reg_nameChar.set('=');
+	reg_nameChar.set('+');
+
+	resvdSchemeChar = new BitSet(128);
+	resvdSchemeChar.set(':');
+
+	resvdUIChar = new BitSet(128);
+	resvdUIChar.set('@');
+
+	resvdHostChar = new BitSet(128);
+	resvdHostChar.set(':');
+	resvdHostChar.set('/');
+	resvdHostChar.set('?');
+	resvdHostChar.set('#');
+
+	resvdPathChar = new BitSet(128);
+	resvdPathChar.set('/');
+	resvdPathChar.set(';');
+	resvdPathChar.set('?');
+	resvdPathChar.set('#');
+
+	resvdQueryChar = new BitSet(128);
+	resvdQueryChar.set('#');
+
+	escpdPathChar = new BitSet(128);
+	escpdPathChar.or(pcharChar);
+	escpdPathChar.set('%');
+	escpdPathChar.set('/');
+	escpdPathChar.set(';');
+
+	escpdQueryChar = new BitSet(128);
+	escpdQueryChar.or(uricChar);
+	escpdQueryChar.clear('#');
+
+	escpdFragChar = new BitSet(128);
+	escpdFragChar.or(uricChar);
+    }
+
+
+    /* our uri in pieces */
+
+    protected static final int OPAQUE       = 0;
+    protected static final int SEMI_GENERIC = 1;
+    protected static final int GENERIC      = 2;
+
+    protected int     type;
+    protected String  scheme;
+    protected String  opaque;
+    protected String  userinfo;
+    protected String  host;
+    protected int     port = -1;
+    protected String  path;
+    protected String  query;
+    protected String  fragment;
+
+
+    /* cache the java.net.URL */
+
+    protected URL     url = null;
+
+
+    // Constructors
+
+    /**
+     * Constructs a URI from the given string representation. The string
+     * must be an absolute URI.
+     *
+     * @param uri a String containing an absolute URI
+     * @exception ParseException if no scheme can be found or a specified
+     *                           port cannot be parsed as a number
+     */
+    public URI(String uri)  throws ParseException
+    {
+	this((URI) null, uri);
+    }
+
+
+    /**
+     * Constructs a URI from the given string representation, relative to
+     * the given base URI.
+     *
+     * @param base    the base URI, relative to which <var>rel_uri</var>
+     *                is to be parsed
+     * @param rel_uri a String containing a relative or absolute URI
+     * @exception ParseException if <var>base</var> is null and
+     *                           <var>rel_uri</var> is not an absolute URI, or
+     *                           if <var>base</var> is not null and the scheme
+     *                           is not known to use the generic syntax, or
+     *                           if a given port cannot be parsed as a number
+     */
+    public URI(URI base, String rel_uri)  throws ParseException
+    {
+	/* Parsing is done according to the following RE:
+	 *
+	 *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+	 *   12            3  4          5       6  7        8 9
+	 *
+	 * 2: scheme
+	 * 4: authority
+	 * 5: path
+	 * 7: query
+	 * 9: fragment
+	 */
+
+	char[] uri = rel_uri.toCharArray();
+	int pos = 0, idx, len = uri.length;
+
+
+	// trim()
+
+	while (pos < len  &&  Character.isWhitespace(uri[pos]))    pos++;
+	while (len > 0    &&  Character.isWhitespace(uri[len-1]))  len--;
+
+
+	// strip the special "url" or "uri" scheme
+
+	if (pos < len-3  &&  uri[pos+3] == ':'  &&
+	    (uri[pos+0] == 'u'  ||  uri[pos+0] == 'U')  &&
+	    (uri[pos+1] == 'r'  ||  uri[pos+1] == 'R')  &&
+	    (uri[pos+2] == 'i'  ||  uri[pos+2] == 'I'  ||
+	     uri[pos+2] == 'l'  ||  uri[pos+2] == 'L'))
+	    pos += 4;
+
+
+	// get scheme: (([^:/?#]+):)?
+
+	idx = pos;
+	while (idx < len  &&  uri[idx] != ':'  &&  uri[idx] != '/'  &&
+	       uri[idx] != '?'  &&  uri[idx] != '#')
+	    idx++;
+	if (idx < len  &&  uri[idx] == ':')
+	{
+	    scheme = rel_uri.substring(pos, idx).trim().toLowerCase();
+	    pos = idx + 1;
+	}
+
+
+	// check and resolve scheme
+
+	String final_scheme = scheme;
+	if (scheme == null)
+	{
+	    if (base == null)
+		throw new ParseException("No scheme found");
+	    final_scheme = base.scheme;
+	}
+
+
+	// check for generic vs. opaque
+
+	type = usesGenericSyntax(final_scheme) ? GENERIC :
+	       usesSemiGenericSyntax(final_scheme) ? SEMI_GENERIC : OPAQUE;
+	if (type == OPAQUE)
+	{
+	    if (base != null  &&  scheme == null)
+		throw new ParseException("Can't resolve relative URI for " +
+					 "scheme " + final_scheme);
+
+	    opaque = escape(rel_uri.substring(pos), opaqueChar, true);
+	    if (opaque.length() > 0  &&  opaque.charAt(0) == '/')
+		opaque = "%2F" + opaque.substring(1);
+	    return;
+	}
+
+
+	// get authority: (//([^/?#]*))?
+
+	if (pos+1 < len  &&  uri[pos] == '/'  &&  uri[pos+1] == '/')
+	{
+	    pos += 2;
+	    idx = pos;
+	    while (idx < len  &&  uri[idx] != '/'  &&  uri[idx] != '?'  &&
+		   uri[idx] != '#')
+		idx++;
+
+	    parse_authority(rel_uri.substring(pos, idx), final_scheme);
+	    pos = idx;
+	}
+
+
+	// handle semi-generic and generic uri's
+	
+	if (type == SEMI_GENERIC)
+	{
+	    path = escape(rel_uri.substring(pos), uricChar, true);
+	    if (path.length() > 0  &&  path.charAt(0) != '/')
+		path = '/' + path;
+	}
+	else
+	{
+	    // get path: ([^?#]*)
+
+	    idx = pos;
+	    while (idx < len  &&  uri[idx] != '?'  &&  uri[idx] != '#')
+		idx++;
+	    path = escape(rel_uri.substring(pos, idx), escpdPathChar, true);
+	    pos = idx;
+
+
+	    // get query: (\?([^#]*))?
+
+	    if (pos < len  &&  uri[pos] == '?')
+	    {
+		pos += 1;
+		idx = pos;
+		while (idx < len  &&  uri[idx] != '#')
+		    idx++;
+		this.query = escape(rel_uri.substring(pos, idx), escpdQueryChar, true);
+		pos = idx;
+	    }
+
+
+	    // get fragment: (#(.*))?
+
+	    if (pos < len  &&  uri[pos] == '#')
+		this.fragment = escape(rel_uri.substring(pos+1, len), escpdFragChar, true);
+	}
+
+
+	// now resolve the parts relative to the base
+
+	if (base != null)
+	{
+	    if (scheme != null  &&			// resolve scheme
+		!(scheme.equals(base.scheme)  &&  ENABLE_BACKWARDS_COMPATIBILITY))
+	      return;
+	    scheme = base.scheme;
+
+	    if (host != null)				// resolve authority
+		return;
+	    userinfo = base.userinfo;
+	    host     = base.host;
+	    port     = base.port;
+
+	    if (type == SEMI_GENERIC)			// can't resolve relative paths
+		return;
+
+	    if (path.length() == 0  &&  query == null)	// current doc
+	    {
+		path  = base.path;
+		query = base.query;
+		return;
+	    }
+
+	    if (path.length() == 0  ||  path.charAt(0) != '/')	// relative path
+	    {
+		idx = (base.path != null) ? base.path.lastIndexOf('/') : -1;
+		if (idx < 0)
+		    path = '/' + path;
+		else
+		    path = base.path.substring(0, idx+1) + path;
+
+		path = canonicalizePath(path);
+	    }
+	}
+    }
+
+    /**
+     * Remove all "/../" and "/./" from path, where possible. Leading "/../"'s
+     * are not removed.
+     *
+     * @param path the path to canonicalize
+     * @return the canonicalized path
+     */
+    public static String canonicalizePath(String path)
+    {
+	int idx, len = path.length();
+	if (!((idx = path.indexOf("/.")) != -1  &&
+	      (idx == len-2  ||  path.charAt(idx+2) == '/'  ||
+	       (path.charAt(idx+2) == '.'  &&
+		(idx == len-3  ||  path.charAt(idx+3) == '/')) )))
+	    return path;
+
+	char[] p = new char[path.length()];		// clean path
+	path.getChars(0, p.length, p, 0);
+
+	int beg = 0;
+	for (idx=1; idx<len; idx++)
+	{
+	    if (p[idx] == '.'  &&  p[idx-1] == '/')
+	    {
+		int end;
+		if (idx == len-1)		// trailing "/."
+		{
+		    end  = idx;
+		    idx += 1;
+		}
+		else if (p[idx+1] == '/')	// "/./"
+		{
+		    end  = idx - 1;
+		    idx += 1;
+		}
+		else if (p[idx+1] == '.'  &&
+			 (idx == len-2  ||  p[idx+2] == '/')) // "/../"
+		{
+		    if (idx < beg + 2)	// keep from backing up too much
+		    {
+			beg = idx + 2;
+			continue;
+		    }
+
+		    end  = idx - 2;
+		    while (end > beg  &&  p[end] != '/')  end--;
+		    if (p[end] != '/')  continue;
+		    if (idx == len-2) end++;
+		    idx += 2;
+		}
+		else
+		    continue;
+		System.arraycopy(p, idx, p, end, len-idx);
+		len -= idx - end;
+		idx = end;
+	    }
+	}
+
+	return new String(p, 0, len);
+    }
+
+    /**
+     * Parse the authority specific part
+     */
+    private void parse_authority(String authority, String scheme)
+	    throws ParseException
+    {
+	/* The authority is further parsed according to:
+	 *
+	 *  ^(([^@]*)@?)(\[[^]]*\]|[^:]*)?(:(.*))?
+	 *   12         3       4 5
+	 *
+	 * 2: userinfo
+	 * 3: host
+	 * 5: port
+	 */
+
+	char[] uri = authority.toCharArray();
+	int pos = 0, idx, len = uri.length;
+
+
+	// get userinfo: (([^@]*)@?)
+
+	idx = pos;
+	while (idx < len  &&  uri[idx] != '@')
+	    idx++;
+	if (idx < len  &&  uri[idx] == '@')
+	{
+	    this.userinfo = escape(authority.substring(pos, idx), userinfoChar, true);
+	    pos = idx + 1;
+	}
+
+
+	// get host: (\[[^]]*\]|[^:]*)?
+
+	idx = pos;
+	if (idx < len  &&  uri[idx] == '[')	// IPv6
+	{
+	    while (idx < len  &&  uri[idx] != ']')
+		idx++;
+	    if (idx == len)
+		throw new ParseException("No closing ']' found for opening '['"+
+					 " at position " + pos +
+					 " in authority `" + authority + "'");
+	    this.host = authority.substring(pos+1, idx);
+	    idx++;
+	}
+	else
+	{
+	    while (idx < len  &&  uri[idx] != ':')
+		idx++;
+	    this.host = escape(authority.substring(pos, idx), uricChar, true);
+	}
+	pos = idx;
+
+
+	// get port: (:(.*))?
+
+	if (pos < (len-1)  &&  uri[pos] == ':')
+	{
+	    int p;
+	    try
+	    {
+		p = Integer.parseInt(
+			    unescape(authority.substring(pos+1, len), null));
+		if (p < 0)  throw new NumberFormatException();
+	    }
+	    catch (NumberFormatException e)
+	    {
+		throw new ParseException(authority.substring(pos+1, len) +
+					 " is an invalid port number");
+	    }
+	    if (p == defaultPort(scheme))
+		this.port = -1;
+	    else
+		this.port = p;
+	}
+    }
+
+
+    /**
+     * Construct a URI from the given URL.
+     *
+     * @param url the URL
+     * @exception ParseException if <code>url.toExternalForm()</code> generates
+     *                           an invalid string representation
+     */
+    public URI(URL url)  throws ParseException
+    {
+	this((URI) null, url.toExternalForm());
+    }
+
+
+    /**
+     * Constructs a URI from the given parts, using the default port for
+     * this scheme (if known). The parts must be in unescaped form.
+     *
+     * @param scheme the scheme (sometimes known as protocol)
+     * @param host   the host
+     * @param path   the path part
+     * @exception ParseException if <var>scheme</var> is null
+     */
+    public URI(String scheme, String host, String path)  throws ParseException
+    {
+	this(scheme, null, host, -1, path, null, null);
+    }
+
+
+    /**
+     * Constructs a URI from the given parts. The parts must be in unescaped
+     * form.
+     *
+     * @param scheme the scheme (sometimes known as protocol)
+     * @param host   the host
+     * @param port   the port
+     * @param path   the path part
+     * @exception ParseException if <var>scheme</var> is null
+     */
+    public URI(String scheme, String host, int port, String path)
+	    throws ParseException
+    {
+	this(scheme, null, host, port, path, null, null);
+    }
+
+
+    /**
+     * Constructs a URI from the given parts. Any part except for the
+     * the scheme may be null. The parts must be in unescaped form.
+     *
+     * @param scheme   the scheme (sometimes known as protocol)
+     * @param userinfo the userinfo
+     * @param host     the host
+     * @param port     the port
+     * @param path     the path part
+     * @param query    the query string
+     * @param fragment the fragment identifier
+     * @exception ParseException if <var>scheme</var> is null
+     */
+    public URI(String scheme, String userinfo, String host, int port,
+	       String path, String query, String fragment)
+	    throws ParseException
+    {
+	if (scheme == null)
+	    throw new ParseException("missing scheme");
+	this.scheme = escape(scheme.trim().toLowerCase(), schemeChar, true);
+	if (userinfo != null)
+	    this.userinfo = escape(userinfo.trim(), userinfoChar, true);
+	if (host != null)
+	{
+	    host = host.trim();
+	    this.host = isIPV6Addr(host) ? host : escape(host, hostChar, true);
+	}
+	if (port != defaultPort(scheme))
+	    this.port     = port;
+	if (path != null)
+	    this.path     = escape(path.trim(), escpdPathChar, true);	// ???
+	if (query != null)
+	    this.query    = escape(query.trim(), escpdQueryChar, true);
+	if (fragment != null)
+	    this.fragment = escape(fragment.trim(), escpdFragChar, true);
+
+	type = usesGenericSyntax(scheme) ? GENERIC : SEMI_GENERIC;
+    }
+
+    private static final boolean isIPV6Addr(String host)
+    {
+	if (host.indexOf(':') < 0)
+	    return false;
+
+	for (int idx=0; idx<host.length(); idx++)
+	{
+	    char ch = host.charAt(idx);
+	    if ((ch < '0'  ||  ch > '9')  &&  ch != ':')
+		return false;
+	}
+
+	return true;
+    }
+
+
+    /**
+     * Constructs an opaque URI from the given parts.
+     *
+     * @param scheme the scheme (sometimes known as protocol)
+     * @param opaque the opaque part
+     * @exception ParseException if <var>scheme</var> is null
+     */
+    public URI(String scheme, String opaque)
+	    throws ParseException
+    {
+	if (scheme == null)
+	    throw new ParseException("missing scheme");
+	this.scheme = escape(scheme.trim().toLowerCase(), schemeChar, true);
+	this.opaque = escape(opaque, opaqueChar, true);
+
+	type = OPAQUE;
+    }
+
+
+    // Class Methods
+
+    /**
+     * @return true if the scheme should be parsed according to the
+     *         generic-URI syntax
+     */
+    public static boolean usesGenericSyntax(String scheme)
+    {
+	return usesGenericSyntax.containsKey(scheme.trim().toLowerCase());
+    }
+
+
+    /**
+     * @return true if the scheme should be parsed according to a
+     *         semi-generic-URI syntax <scheme&tgt;://<hostport>/<opaque>
+     */
+    public static boolean usesSemiGenericSyntax(String scheme)
+    {
+	return usesSemiGenericSyntax.containsKey(scheme.trim().toLowerCase());
+    }
+
+
+    /**
+     * Return the default port used by a given protocol.
+     *
+     * @param protocol the protocol
+     * @return the port number, or 0 if unknown
+     */
+    public final static int defaultPort(String protocol)
+    {
+	Integer port = (Integer) defaultPorts.get(protocol.trim().toLowerCase());
+	return (port != null) ? port.intValue() : 0;
+    }
+
+
+    // Instance Methods
+
+    /**
+     * @return the scheme (often also referred to as protocol)
+     */
+    public String getScheme()
+    {
+	return scheme;
+    }
+
+
+    /**
+     * @return the opaque part, or null if this URI is generic
+     */
+    public String getOpaque()
+    {
+	return opaque;
+    }
+
+
+    /**
+     * @return the host
+     */
+    public String getHost()
+    {
+	return host;
+    }
+
+
+    /**
+     * @return the port, or -1 if it's the default port, or 0 if unknown
+     */
+    public int getPort()
+    {
+	return port;
+    }
+
+
+    /**
+     * @return the user info
+     */
+    public String getUserinfo()
+    {
+	return userinfo;
+    }
+
+
+    /**
+     * @return the path
+     */
+    public String getPath()
+    {
+	return path;
+    }
+
+
+    /**
+     * @return the query string
+     */
+    public String getQueryString()
+    {
+	return query;
+    }
+
+
+    /**
+     * @return the path and query
+     */
+    public String getPathAndQuery()
+    {
+	if (query == null)
+	    return path;
+	if (path == null)
+	    return "?" + query;
+	return path + "?" + query;
+    }
+
+
+    /**
+     * @return the fragment
+     */
+    public String getFragment()
+    {
+	return fragment;
+    }
+
+
+    /**
+     * Does the scheme specific part of this URI use the generic-URI syntax?
+     *
+     * <P>In general URI are split into two categories: opaque-URI and
+     * generic-URI. The generic-URI syntax is the syntax most are familiar
+     * with from URLs such as ftp- and http-URLs, which is roughly:
+     * <PRE>
+     * generic-URI = scheme ":" [ "//" server ] [ "/" ] [ path_segments ] [ "?" query ]
+     * </PRE>
+     * (see RFC-2396 for exact syntax). Only URLs using the generic-URI syntax
+     * can be used to create and resolve relative URIs.
+     *
+     * <P>Whether a given scheme is parsed according to the generic-URI
+     * syntax or wether it is treated as opaque is determined by an internal
+     * table of URI schemes.
+     *
+     * @see <A HREF="http://www.ics.uci.edu/pub/ietf/uri/rfc2396.txt">rfc-2396</A>
+     */
+    public boolean isGenericURI()
+    {
+	return (type == GENERIC);
+    }
+
+    /**
+     * Does the scheme specific part of this URI use the semi-generic-URI syntax?
+     *
+     * <P>Many schemes which don't follow the full generic syntax actually
+     * follow a reduced form where the path part is treated is opaque. This
+     * is used for example by ldap, smtp, pop, etc, and is roughly
+     * <PRE>
+     * generic-URI = scheme ":" [ "//" server ] [ "/" [ opaque_path ] ]
+     * </PRE>
+     * I.e. parsing is identical to the generic-syntax, except that the path
+     * part is not further parsed. URLs using the semi-generic-URI syntax can
+     * be used to create and resolve relative URIs with the restriction that
+     * all paths are treated as absolute.
+     *
+     * <P>Whether a given scheme is parsed according to the semi-generic-URI
+     * syntax is determined by an internal table of URI schemes.
+     *
+     * @see #isGenericURI()
+     */
+    public boolean isSemiGenericURI()
+    {
+	return (type == SEMI_GENERIC);
+    }
+
+
+    /**
+     * Will try to create a java.net.URL object from this URI.
+     *
+     * @return the URL
+     * @exception MalformedURLException if no handler is available for the
+     *            scheme
+     */
+    public URL toURL()  throws MalformedURLException
+    {
+	if (url != null)  return url;
+
+	if (opaque != null)
+	    return (url = new URL(scheme + ":" + opaque));
+
+	String hostinfo;
+	if (userinfo != null  &&  host != null)
+	    hostinfo = userinfo + "@" + host;
+	else if (userinfo != null)
+	    hostinfo = userinfo + "@";
+	else
+	    hostinfo = host;
+
+	StringBuffer file = new StringBuffer(100);
+	assemblePath(file, true, true, false);
+
+	url = new URL(scheme, hostinfo, port, file.toString());
+	return url;
+    }
+
+
+    private final void assemblePath(StringBuffer buf, boolean printEmpty,
+				    boolean incFragment, boolean unescape)
+    {
+	if ((path == null  ||  path.length() == 0)  &&  printEmpty)
+	    buf.append('/');
+
+	if (path != null)
+	    buf.append(unescape ? unescapeNoPE(path, resvdPathChar) : path);
+
+	if (query != null)
+	{
+	    buf.append('?');
+	    buf.append(unescape ? unescapeNoPE(query, resvdQueryChar) : query);
+	}
+
+	if (fragment != null  &&  incFragment)
+	{
+	    buf.append('#');
+	    buf.append(unescape ? unescapeNoPE(fragment, null) : fragment);
+	}
+    }
+
+
+    private final String stringify(boolean unescape)
+    {
+	StringBuffer uri = new StringBuffer(100);
+
+	if (scheme != null)
+	{
+	    uri.append(unescape ? unescapeNoPE(scheme, resvdSchemeChar) : scheme);
+	    uri.append(':');
+	}
+
+	if (opaque != null)		// it's an opaque-uri
+	{
+	    uri.append(unescape ? unescapeNoPE(opaque, null) : opaque);
+	    return uri.toString();
+	}
+
+	if (userinfo != null  ||  host != null  ||  port != -1)
+	    uri.append("//");
+
+	if (userinfo != null)
+	{
+	    uri.append(unescape ? unescapeNoPE(userinfo, resvdUIChar) : userinfo);
+	    uri.append('@');
+	}
+
+	if (host != null)
+	{
+	    if (host.indexOf(':') < 0)
+		uri.append(unescape ? unescapeNoPE(host, resvdHostChar) : host);
+	    else
+		uri.append('[').append(host).append(']');
+	}
+
+	if (port != -1)
+	{
+	    uri.append(':');
+	    uri.append(port);
+	}
+
+	assemblePath(uri, false, true, unescape);
+
+	return uri.toString();
+    }
+
+
+    /**
+     * @return a string representation of this URI suitable for use in
+     *         links, headers, etc.
+     */
+    public String toExternalForm()
+    {
+	return stringify(false);
+    }
+
+
+    /**
+     * Return the URI as string. This differs from toExternalForm() in that
+     * all elements are unescaped before assembly. This is <em>not suitable</em>
+     * for passing to other apps or in header fields and such, and is usually
+     * not what you want.
+     *
+     * @return the URI as a string
+     * @see #toExternalForm()
+     */
+    public String toString()
+    {
+	return stringify(true);
+    }
+
+
+    /**
+     * @return true if <var>other</var> is either a URI or URL and it
+     *         matches the current URI
+     */
+    public boolean equals(Object other)
+    {
+	if (other instanceof URI)
+	{
+	    URI o = (URI) other;
+	    return (scheme.equals(o.scheme)  &&
+		    (
+		     type == OPAQUE  &&  areEqual(opaque, o.opaque)  ||
+
+		     type == SEMI_GENERIC  &&
+		      areEqual(userinfo, o.userinfo)  &&
+		      areEqualIC(host, o.host)  &&
+		      port == o.port  &&
+		      areEqual(path, o.path)  ||
+
+		     type == GENERIC  &&
+		      areEqual(userinfo, o.userinfo)  &&
+		      areEqualIC(host, o.host)  &&
+		      port == o.port  &&
+		      pathsEqual(path, o.path)  &&
+		      areEqual(query, o.query)  &&
+		      areEqual(fragment, o.fragment)
+		    ));
+	}
+
+	if (other instanceof URL)
+	{
+	    URL o = (URL) other;
+	    String h, f;
+
+	    if (userinfo != null)
+		h = userinfo + "@" + host;
+	    else
+		h = host;
+
+	    f = getPathAndQuery();
+
+	    return (scheme.equalsIgnoreCase(o.getProtocol())  &&
+		    (type == OPAQUE  &&  opaque.equals(o.getFile())  ||
+
+		     type == SEMI_GENERIC  &&
+		       areEqualIC(h, o.getHost())  &&
+		       (port == o.getPort()  ||
+			o.getPort() == defaultPort(scheme))  &&
+		       areEqual(f, o.getFile())  ||
+
+		     type == GENERIC  &&
+		       areEqualIC(h, o.getHost())  &&
+		       (port == o.getPort()  ||
+			o.getPort() == defaultPort(scheme))  &&
+		       pathsEqual(f, o.getFile())  &&
+		       areEqual(fragment, o.getRef())
+		    )
+		   );
+	}
+
+	return false;
+    }
+
+    private static final boolean areEqual(String s1, String s2)
+    {
+	return (s1 == null  &&  s2 == null  ||
+		s1 != null  &&  s2 != null  &&
+		  (s1.equals(s2)  ||
+		   unescapeNoPE(s1, null).equals(unescapeNoPE(s2, null)))
+	       );
+    }
+
+    private static final boolean areEqualIC(String s1, String s2)
+    {
+	return (s1 == null  &&  s2 == null  ||
+		s1 != null  &&  s2 != null  &&
+		  (s1.equalsIgnoreCase(s2)  ||
+		   unescapeNoPE(s1, null).equalsIgnoreCase(unescapeNoPE(s2, null)))
+	       );
+    }
+
+    private static final boolean pathsEqual(String p1, String p2)
+    {
+	if (p1 == null  &&  p2 == null)
+	    return true;
+	if (p1 == null  ||  p2 == null)
+	    return false;
+	if (p1.equals(p2))
+	    return true;
+
+	// ok, so it wasn't that simple. Let's split into parts and compare
+	// unescaped.
+	int pos1 = 0, end1 = p1.length(), pos2 = 0, end2 = p2.length();
+	while (pos1 < end1  &&  pos2 < end2)
+	{
+	    int start1 = pos1, start2 = pos2;
+
+	    char ch;
+	    while (pos1 < end1  &&  (ch = p1.charAt(pos1)) != '/'  &&  ch != ';')
+		pos1++;
+	    while (pos2 < end2  &&  (ch = p2.charAt(pos2)) != '/'  &&  ch != ';')
+		pos2++;
+
+	    if (pos1 == end1  &&  pos2 < end2  ||
+		pos2 == end2  &&  pos1 < end1  ||
+		pos1 < end1  &&  pos2 < end2  &&  p1.charAt(pos1) != p2.charAt(pos2))
+		return false;
+
+	    if ((!p1.regionMatches(start1, p2, start2, pos1-start1)  ||  (pos1-start1) != (pos2-start2))  &&
+		!unescapeNoPE(p1.substring(start1, pos1), null).equals(unescapeNoPE(p2.substring(start2, pos2), null)))
+		return false;
+
+	    pos1++;
+	    pos2++;
+	}
+
+	return (pos1 == end1  &&  pos2 == end2);
+    }
+
+    private int hashCode = -1;
+
+    /**
+     * The hash code is calculated over scheme, host, path, and query.
+     *
+     * @return the hash code
+     */
+    public int hashCode()
+    {
+	if (hashCode == -1)
+	    hashCode = (scheme != null ? unescapeNoPE(scheme, null).hashCode() : 0) + 
+		       (type == OPAQUE ?
+			  (opaque != null ? unescapeNoPE(opaque, null).hashCode() : 0) * 7
+			: (host != null ? unescapeNoPE(host, null).toLowerCase().hashCode() : 0) * 7 +
+			  (path != null ? unescapeNoPE(path, null).hashCode() : 0) * 13 +
+			  (query != null ? unescapeNoPE(query, null).hashCode() : 0) * 17);
+
+	return hashCode;
+    }
+
+
+    /**
+     * Escape any character not in the given character class. Characters
+     * greater 255 are always escaped according to ??? .
+     *
+     * @param elem         the string to escape
+     * @param allowed_char the BitSet of all allowed characters
+     * @param utf8         if true, will first UTF-8 encode unallowed characters
+     * @return the string with all characters not in allowed_char
+     *         escaped
+     */
+    public static String escape(String elem, BitSet allowed_char, boolean utf8)
+    {
+	return new String(escape(elem.toCharArray(), allowed_char, utf8));
+    }
+
+    /**
+     * Escape any character not in the given character class. Characters
+     * greater 255 are always escaped according to ??? .
+     *
+     * @param elem         the array of characters to escape
+     * @param allowed_char the BitSet of all allowed characters
+     * @param utf8         if true, will first UTF-8 encode unallowed characters
+     * @return the elem array with all characters not in allowed_char
+     *         escaped
+     */
+    public static char[] escape(char[] elem, BitSet allowed_char, boolean utf8)
+    {
+	int cnt=0;
+	for (int idx=0; idx<elem.length; idx++)
+	{
+	    if (!allowed_char.get(elem[idx]))
+	    {
+		cnt += 2;
+		if (utf8)
+		{
+		    if (elem[idx] >= 0x0080)
+			cnt += 3;
+		    if (elem[idx] >= 0x00800)
+			cnt += 3;
+		    if ((elem[idx] & 0xFC00) == 0xD800  &&  idx+1 < elem.length  &&
+			(elem[idx+1] & 0xFC00) == 0xDC00)
+		      cnt -= 6;
+		}
+	    }
+	}
+
+	if (cnt == 0)  return elem;
+
+	char[] tmp = new char[elem.length + cnt];
+	for (int idx=0, pos=0; idx<elem.length; idx++)
+	{
+	    char c = elem[idx];
+	    if (allowed_char.get(c))
+		tmp[pos++] = c;
+	    else if (utf8)
+	    {
+		/* We're UTF-8 encoding the chars first, as recommended in
+		 * the HTML 4.0 specification:
+		 * http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.2.1
+		 * Note that this doesn't change things for ASCII chars
+		 */
+		if (c <= 0x007F)
+		{
+		    pos = enc(tmp, pos, c);
+		}
+		else if (c <= 0x07FF)
+		{
+		    pos = enc(tmp, pos, 0xC0 | ((c >>  6) & 0x1F));
+		    pos = enc(tmp, pos, 0x80 | ((c >>  0) & 0x3F));
+		}
+		else if (!((c & 0xFC00) == 0xD800  &&  idx+1 < elem.length  &&
+			     (elem[idx+1] & 0xFC00) == 0xDC00))
+		{
+		    pos = enc(tmp, pos, 0xE0 | ((c >> 12) & 0x0F));
+		    pos = enc(tmp, pos, 0x80 | ((c >>  6) & 0x3F));
+		    pos = enc(tmp, pos, 0x80 | ((c >>  0) & 0x3F));
+		}
+		else
+		{
+		    int ch = ((c & 0x03FF) << 10) | (elem[++idx] & 0x03FF);
+		    ch += 0x10000;
+		    pos = enc(tmp, pos, 0xF0 | ((ch >> 18) & 0x07));
+		    pos = enc(tmp, pos, 0x80 | ((ch >> 12) & 0x3F));
+		    pos = enc(tmp, pos, 0x80 | ((ch >>  6) & 0x3F));
+		    pos = enc(tmp, pos, 0x80 | ((ch >>  0) & 0x3F));
+		}
+	    }
+	    else
+		pos = enc(tmp, pos, c);
+	}
+
+	return tmp;
+    }
+
+    private static final char[] hex =
+	    {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+
+    private static final int enc(char[] out, int pos, int c)
+    {
+	out[pos++] = '%';
+	out[pos++] = hex[(c >> 4) & 0xf];
+	out[pos++] = hex[c & 0xf];
+	return pos;
+    }
+
+    /**
+     * Unescape escaped characters (i.e. %xx) except reserved ones.
+     *
+     * @param str      the string to unescape
+     * @param reserved the characters which may not be unescaped, or null
+     * @return the unescaped string
+     * @exception ParseException if the two digits following a `%' are
+     *            not a valid hex number
+     */
+    public static final String unescape(String str, BitSet reserved)
+	    throws ParseException
+    {
+	if (str == null  ||  str.indexOf('%') == -1)
+	    return str;  				// an optimization
+
+	char[] buf = str.toCharArray();
+	char[] res = new char[buf.length];
+
+	char[] utf = new char[4];
+	int utf_idx = 0, utf_len = -1;
+	int didx = 0;
+	for (int sidx=0; sidx<buf.length; sidx++)
+	{
+	    if (buf[sidx] == '%')
+	    {
+		int ch;
+                try
+                {
+		    if (sidx + 3 > buf.length)
+			throw new NumberFormatException();
+		    ch = Integer.parseInt(str.substring(sidx+1,sidx+3), 16);
+		    if (ch < 0)
+			throw new NumberFormatException();
+		    sidx += 2;
+                }
+                catch (NumberFormatException e)
+                {
+		    /* Hmm, people not reading specs again, so we just
+		     * ignore it...
+                    throw new ParseException(str.substring(sidx,sidx+3) +
+                                            " is an invalid code");
+		    */
+		    ch = buf[sidx];
+                }
+
+		// check if we're working on a utf-char
+		if (utf_len > 0)
+		{
+		    if ((ch & 0xC0) != 0x80)	// oops, we misinterpreted
+		    {
+			didx = copyBuf(utf, utf_idx, ch, res, didx, reserved, false);
+			utf_len = -1;
+		    }
+		    else if (utf_idx == utf_len - 1)	// end-of-char
+		    {
+			if ((utf[0] & 0xE0) == 0xC0)
+			    ch = (utf[0] & 0x1F) <<  6 |
+				 (ch & 0x3F);
+			else if ((utf[0] & 0xF0) == 0xE0)
+			    ch = (utf[0] & 0x0F) << 12 |
+				 (utf[1] & 0x3F) <<  6 |
+				 (ch & 0x3F);
+			else
+			    ch = (utf[0] & 0x07) << 18 |
+				 (utf[1] & 0x3F) << 12 |
+				 (utf[2] & 0x3F) <<  6 |
+				 (ch & 0x3F);
+			if (reserved != null  &&  reserved.get(ch))
+			    didx = copyBuf(utf, utf_idx, ch, res, didx, null, true);
+			else if (utf_len < 4)
+			    res[didx++] = (char) ch;
+			else
+			{
+			    ch -= 0x10000;
+			    res[didx++] = (char) ((ch >> 10)    | 0xD800);
+			    res[didx++] = (char) ((ch & 0x03FF) | 0xDC00);
+			}
+			utf_len = -1;
+		    }
+		    else				// continue
+			utf[utf_idx++] = (char) ch;
+		}
+		// check if this is the start of a utf-char
+		else if ((ch & 0xE0) == 0xC0  ||  (ch & 0xF0) == 0xE0  ||
+			 (ch & 0xF8) == 0xF0)
+		{
+		    if ((ch & 0xE0) == 0xC0)
+			utf_len = 2;
+		    else if ((ch & 0xF0) == 0xE0)
+			utf_len = 3;
+		    else
+			utf_len = 4;
+		    utf[0] = (char) ch;
+		    utf_idx = 1;
+		}
+		// leave reserved alone
+		else if (reserved != null  &&  reserved.get(ch))
+		{
+		    res[didx++] = buf[sidx];
+		    sidx -= 2;
+		}
+		// just use the decoded version
+		else
+		    res[didx++] = (char) ch;
+	    }
+	    else if (utf_len > 0)	// oops, we misinterpreted
+	    {
+		didx = copyBuf(utf, utf_idx, buf[sidx], res, didx, reserved, false);
+		utf_len = -1;
+	    }
+	    else
+		res[didx++] = buf[sidx];
+	}
+	if (utf_len > 0)	// oops, we misinterpreted
+	    didx = copyBuf(utf, utf_idx, -1, res, didx, reserved, false);
+
+	return new String(res, 0, didx);
+    }
+
+    private static final int copyBuf(char[] utf, int utf_idx, int ch,
+				     char[] res, int didx, BitSet reserved,
+				     boolean escapeAll)
+    {
+	if (ch >= 0)
+	    utf[utf_idx++] = (char) ch;
+
+	for (int idx=0; idx<utf_idx; idx++)
+	{
+	    if (reserved != null  &&  reserved.get(utf[idx])  ||  escapeAll)
+		didx = enc(res, didx, utf[idx]);
+	    else
+		res[didx++] = utf[idx];
+	}
+
+	return didx;
+    }
+
+    /**
+     * Unescape escaped characters (i.e. %xx). If a ParseException would
+     * be thrown then just return the original string.
+     *
+     * @param str      the string to unescape
+     * @param reserved the characters which may not be unescaped, or null
+     * @return the unescaped string, or the original string if unescaping
+     *         would throw a ParseException
+     * @see #unescape(java.lang.String, java.util.BitSet)
+     */
+    private static final String unescapeNoPE(String str, BitSet reserved)
+    {
+	try
+	    { return unescape(str, reserved); }
+	catch (ParseException pe)
+	    { return str; }
+    }
+
+
+    /**
+     * Run test set.
+     *
+     * @exception Exception if any test fails
+     */
+    public static void main(String args[])  throws Exception
+    {
+	System.err.println();
+	System.err.println("*** URI Tests ...");
+
+
+	/* Relative URI test set, taken from Section C of rfc-2396 and
+	 * Roy's test1. All Roy's URI parser tests can be found at
+	 * http://www.ics.uci.edu/~fielding/url/
+	 * The tests have been augmented by a few for the IPv6 syntax
+	 */
+
+	URI base = new URI("http://a/b/c/d;p?q");
+
+	// normal examples
+	testParser(base, "g:h",        "g:h");
+	testParser(base, "g",          "http://a/b/c/g");
+	testParser(base, "./g",        "http://a/b/c/g");
+	testParser(base, "g/",         "http://a/b/c/g/");
+	testParser(base, "/g",         "http://a/g");
+	testParser(base, "//g",        "http://g");
+	testParser(base, "//[23:54]",  "http://[23:54]");
+	testParser(base, "?y",         "http://a/b/c/?y");
+	testParser(base, "g?y",        "http://a/b/c/g?y");
+	testParser(base, "#s",         "http://a/b/c/d;p?q#s");
+	testParser(base, "g#s",        "http://a/b/c/g#s");
+	testParser(base, "g?y#s",      "http://a/b/c/g?y#s");
+	testParser(base, ";x",         "http://a/b/c/;x");
+	testParser(base, "g;x",        "http://a/b/c/g;x");
+	testParser(base, "g;x?y#s",    "http://a/b/c/g;x?y#s");
+	testParser(base, ".",          "http://a/b/c/");
+	testParser(base, "./",         "http://a/b/c/");
+	testParser(base, "..",         "http://a/b/");
+	testParser(base, "../",        "http://a/b/");
+	testParser(base, "../g",       "http://a/b/g");
+	testParser(base, "../..",      "http://a/");
+	testParser(base, "../../",     "http://a/");
+	testParser(base, "../../g",    "http://a/g");
+
+	// abnormal examples
+	testParser(base, "",              "http://a/b/c/d;p?q");
+	testParser(base, "/./g",          "http://a/./g");
+	testParser(base, "/../g",         "http://a/../g");
+	testParser(base, "../../../g",    "http://a/../g");
+	testParser(base, "../../../../g", "http://a/../../g");
+	testParser(base, "g.",            "http://a/b/c/g.");
+	testParser(base, ".g",            "http://a/b/c/.g");
+	testParser(base, "g..",           "http://a/b/c/g..");
+	testParser(base, "..g",           "http://a/b/c/..g");
+	testParser(base, "./../g",        "http://a/b/g");
+	testParser(base, "./g/.",         "http://a/b/c/g/");
+	testParser(base, "g/./h",         "http://a/b/c/g/h");
+	testParser(base, "g/../h",        "http://a/b/c/h");
+	testParser(base, "g;x=1/./y",     "http://a/b/c/g;x=1/y");
+	testParser(base, "g;x=1/../y",    "http://a/b/c/y");
+	testParser(base, "g?y/./x",       "http://a/b/c/g?y/./x");
+	testParser(base, "g?y/../x",      "http://a/b/c/g?y/../x");
+	testParser(base, "g#s/./x",       "http://a/b/c/g#s/./x");
+	testParser(base, "g#s/../x",      "http://a/b/c/g#s/../x");
+	if (ENABLE_BACKWARDS_COMPATIBILITY)
+	    testParser(base, "http:g",        "http://a/b/c/g");
+	else
+	    testParser(base, "http:g",        "http:g");
+	if (ENABLE_BACKWARDS_COMPATIBILITY)
+	    testParser(base, "http:",         "http://a/b/c/d;p?q");
+	else
+	    testParser(base, "http:",         "http:");
+	testParser(base, "./g:h",         "http://a/b/c/g:h");
+
+
+	/* Roy's test2
+	 */
+	base = new URI("http://a/b/c/d;p?q=1/2");
+
+	testParser(base, "g",        "http://a/b/c/g");
+	testParser(base, "./g",      "http://a/b/c/g");
+	testParser(base, "g/",       "http://a/b/c/g/");
+	testParser(base, "/g",       "http://a/g");
+	testParser(base, "//g",      "http://g");
+	testParser(base, "//[23:54]","http://[23:54]");
+	testParser(base, "?y",       "http://a/b/c/?y");
+	testParser(base, "g?y",      "http://a/b/c/g?y");
+	testParser(base, "g?y/./x",  "http://a/b/c/g?y/./x");
+	testParser(base, "g?y/../x", "http://a/b/c/g?y/../x");
+	testParser(base, "g#s",      "http://a/b/c/g#s");
+	testParser(base, "g#s/./x",  "http://a/b/c/g#s/./x");
+	testParser(base, "g#s/../x", "http://a/b/c/g#s/../x");
+	testParser(base, "./",       "http://a/b/c/");
+	testParser(base, "../",      "http://a/b/");
+	testParser(base, "../g",     "http://a/b/g");
+	testParser(base, "../../",   "http://a/");
+	testParser(base, "../../g",  "http://a/g");
+
+
+	/* Roy's test3
+	 */
+	base = new URI("http://a/b/c/d;p=1/2?q");
+
+	testParser(base, "g",          "http://a/b/c/d;p=1/g");
+	testParser(base, "./g",        "http://a/b/c/d;p=1/g");
+	testParser(base, "g/",         "http://a/b/c/d;p=1/g/");
+	testParser(base, "g?y",        "http://a/b/c/d;p=1/g?y");
+	testParser(base, ";x",         "http://a/b/c/d;p=1/;x");
+	testParser(base, "g;x",        "http://a/b/c/d;p=1/g;x");
+	testParser(base, "g;x=1/./y",  "http://a/b/c/d;p=1/g;x=1/y");
+	testParser(base, "g;x=1/../y", "http://a/b/c/d;p=1/y");
+	testParser(base, "./",         "http://a/b/c/d;p=1/");
+	testParser(base, "../",        "http://a/b/c/");
+	testParser(base, "../g",       "http://a/b/c/g");
+	testParser(base, "../../",     "http://a/b/");
+	testParser(base, "../../g",    "http://a/b/g");
+
+
+	/* Roy's test4
+	 */
+	base = new URI("fred:///s//a/b/c");
+
+	testParser(base, "g:h",           "g:h");
+	/* we have to skip these, as usesGeneraicSyntax("fred") returns false
+	 * and we therefore don't parse relative URI's here. But test5 is
+	 * the same except that the http scheme is used.
+	testParser(base, "g",             "fred:///s//a/b/g");
+	testParser(base, "./g",           "fred:///s//a/b/g");
+	testParser(base, "g/",            "fred:///s//a/b/g/");
+	testParser(base, "/g",            "fred:///g");
+	testParser(base, "//g",           "fred://g");
+	testParser(base, "//g/x",         "fred://g/x");
+	testParser(base, "///g",          "fred:///g");
+	testParser(base, "./",            "fred:///s//a/b/");
+	testParser(base, "../",           "fred:///s//a/");
+	testParser(base, "../g",          "fred:///s//a/g");
+	testParser(base, "../../",        "fred:///s//");
+	testParser(base, "../../g",       "fred:///s//g");
+	testParser(base, "../../../g",    "fred:///s/g");
+	testParser(base, "../../../../g", "fred:///g");
+	 */
+	testPE(base, "g");
+
+
+	/* Roy's test5
+	 */
+	base = new URI("http:///s//a/b/c");
+
+	testParser(base, "g:h",           "g:h");
+	testParser(base, "g",             "http:///s//a/b/g");
+	testParser(base, "./g",           "http:///s//a/b/g");
+	testParser(base, "g/",            "http:///s//a/b/g/");
+	testParser(base, "/g",            "http:///g");
+	testParser(base, "//g",           "http://g");
+	testParser(base, "//[23:54]",     "http://[23:54]");
+	testParser(base, "//g/x",         "http://g/x");
+	testParser(base, "///g",          "http:///g");
+	testParser(base, "./",            "http:///s//a/b/");
+	testParser(base, "../",           "http:///s//a/");
+	testParser(base, "../g",          "http:///s//a/g");
+	testParser(base, "../../",        "http:///s//");
+	testParser(base, "../../g",       "http:///s//g");
+	testParser(base, "../../../g",    "http:///s/g");
+	testParser(base, "../../../../g", "http:///g");
+
+
+	/* Some additional parser tests
+	 */
+	base = new URI("http://s");
+
+	testParser(base, "ftp:h",         "ftp:h");
+	testParser(base, "ftp://h",       "ftp://h");
+	testParser(base, "//g",           "http://g");
+	testParser(base, "//g?h",         "http://g?h");
+	testParser(base, "g",             "http://s/g");
+	testParser(base, "./g",           "http://s/g");
+	testParser(base, "?g",            "http://s/?g");
+	testParser(base, "#g",            "http://s#g");
+
+	base = new URI("http:");
+
+	testParser(base, "ftp:h",         "ftp:h");
+	testParser(base, "ftp://h",       "ftp://h");
+	testParser(base, "//g",           "http://g");
+	testParser(base, "g",             "http:/g");
+	testParser(base, "?g",            "http:/?g");
+	testParser(base, "#g",            "http:#g");
+
+	base = new URI("http://s/t");
+
+	testParser(base, "ftp:/h",        "ftp:/h");
+	if (ENABLE_BACKWARDS_COMPATIBILITY)
+	    testParser(base, "http:/h",       "http://s/h");
+	else
+	    testParser(base, "http:/h",       "http:/h");
+
+	base = new URI("http://s/g?h/j");
+	testParser(base, "k",             "http://s/k");
+	testParser(base, "k?l",           "http://s/k?l");
+
+
+	/* Parser tests for semi-generic syntax
+	 */
+	base = new URI("ldap:");
+
+	testParser(base, "ldap:",         "ldap:");
+	testParser(base, "ldap://a",      "ldap://a");
+	testParser(base, "ldap://a/b",    "ldap://a/b");
+	testParser(base, "ldap:/b",       "ldap:/b");
+
+	testParser(base, "ftp:h",         "ftp:h");
+	testParser(base, "ftp://h",       "ftp://h");
+	testParser(base, "//g",           "ldap://g");
+	testParser(base, "//g?h",         "ldap://g/?h");
+	testParser(base, "g",             "ldap:/g");
+	testParser(base, "./g",           "ldap:/./g");
+	testParser(base, "?g",            "ldap:/?g");
+	testParser(base, "#g",            "ldap:/%23g");
+
+	base = new URI("ldap://s");
+
+	if (ENABLE_BACKWARDS_COMPATIBILITY)
+	    testParser(base, "ldap:",         "ldap://s");
+	else
+	    testParser(base, "ldap:",         "ldap:");
+	testParser(base, "ldap://a",      "ldap://a");
+	testParser(base, "ldap://a/b",    "ldap://a/b");
+	if (ENABLE_BACKWARDS_COMPATIBILITY)
+	    testParser(base, "ldap:/b",       "ldap://s/b");
+	else
+	    testParser(base, "ldap:/b",       "ldap:/b");
+
+	testParser(base, "ftp:h",         "ftp:h");
+	testParser(base, "ftp://h",       "ftp://h");
+	testParser(base, "//g",           "ldap://g");
+	testParser(base, "//g?h",         "ldap://g/?h");
+	testParser(base, "g",             "ldap://s/g");
+	testParser(base, "./g",           "ldap://s/./g");
+	testParser(base, "?g",            "ldap://s/?g");
+	testParser(base, "#g",            "ldap://s/%23g");
+
+	base = new URI("ldap://s/t");
+
+	testParser(base, "ftp:/h",        "ftp:/h");
+	if (ENABLE_BACKWARDS_COMPATIBILITY)
+	    testParser(base, "ldap:/h",       "ldap://s/h");
+	else
+	    testParser(base, "ldap:/h",       "ldap:/h");
+
+	if (ENABLE_BACKWARDS_COMPATIBILITY)
+	    testParser(base, "ldap:",         "ldap://s");
+	else
+	    testParser(base, "ldap:",         "ldap:");
+	testParser(base, "ldap://a",      "ldap://a");
+	testParser(base, "ldap://a/b",    "ldap://a/b");
+
+	testParser(base, "ftp:h",         "ftp:h");
+	testParser(base, "ftp://h",       "ftp://h");
+	testParser(base, "//g",           "ldap://g");
+	testParser(base, "//g?h",         "ldap://g/?h");
+	testParser(base, "g",             "ldap://s/g");
+	testParser(base, "./g",           "ldap://s/./g");
+	testParser(base, "?g",            "ldap://s/?g");
+	testParser(base, "#g",            "ldap://s/%23g");
+
+
+	/* equality tests */
+
+	// protocol
+	testNotEqual("http://a/", "nntp://a/");
+	testNotEqual("http://a/", "https://a/");
+	testNotEqual("http://a/", "shttp://a/");
+	testEqual("http://a/", "Http://a/");
+	testEqual("http://a/", "hTTP://a/");
+	testEqual("url:http://a/", "hTTP://a/");
+	testEqual("urI:http://a/", "hTTP://a/");
+
+	// host
+	testEqual("http://a/", "Http://A/");
+	testEqual("http://a.b.c/", "Http://A.b.C/");
+	testEqual("http:///", "Http:///");
+	testEqual("http://[]/", "Http:///");
+	testNotEqual("http:///", "Http://a/");
+	testNotEqual("http://[]/", "Http://a/");
+	testPE(null, "ftp://[23::43:1/");
+	testPE(null, "ftp://[/");
+
+	// port
+	testEqual("http://a.b.c/", "Http://A.b.C:80/");
+	testEqual("http://a.b.c:/", "Http://A.b.C:80/");
+	testEqual("http://[23::45:::5:]/", "Http://[23::45:::5:]:80/");
+	testEqual("http://[23::45:::5:]:/", "Http://[23::45:::5:]:80/");
+	testEqual("nntp://a", "nntp://a:119");
+	testEqual("nntp://a:", "nntp://a:119");
+	testEqual("nntp://a/", "nntp://a:119/");
+	testNotEqual("nntp://a", "nntp://a:118");
+	testNotEqual("nntp://a", "nntp://a:0");
+	testNotEqual("nntp://a:", "nntp://a:0");
+	testEqual("telnet://:23/", "telnet:///");
+	testPE(null, "ftp://:a/");
+	testPE(null, "ftp://:-1/");
+	testPE(null, "ftp://::1/");
+
+	// userinfo
+	testNotEqual("ftp://me@a", "ftp://a");
+	testNotEqual("ftp://me@a", "ftp://Me@a");
+	testEqual("ftp://Me@a", "ftp://Me@a");
+	testEqual("ftp://Me:My@a:21", "ftp://Me:My@a");
+	testEqual("ftp://Me:My@a:", "ftp://Me:My@a");
+	testNotEqual("ftp://Me:My@a:21", "ftp://Me:my@a");
+	testNotEqual("ftp://Me:My@a:", "ftp://Me:my@a");
+
+	// path
+	testEqual("ftp://a/b%2b/", "ftp://a/b+/");
+	testEqual("ftp://a/b%2b/", "ftp://a/b+/");
+	testEqual("ftp://a/b%5E/", "ftp://a/b^/");
+	testEqual("ftp://a/b%4C/", "ftp://a/bL/");
+	testNotEqual("ftp://a/b/", "ftp://a//b/");
+	testNotEqual("ftp://a/b/", "ftp://a/b//");
+	testNotEqual("ftp://a/b%4C/", "ftp://a/bl/");
+	testNotEqual("ftp://a/b%3f/", "ftp://a/b?/");
+	testNotEqual("ftp://a/b%2f/", "ftp://a/b//");
+	testNotEqual("ftp://a/b%2fc/", "ftp://a/b/c/");
+	testNotEqual("ftp://a/bc/", "ftp://a/b//");
+	testNotEqual("ftp://a/bc/", "ftp://a/b/");
+	testNotEqual("ftp://a/bc//", "ftp://a/b/");
+	testNotEqual("ftp://a/b/", "ftp://a/bc//");
+	testNotEqual("ftp://a/b/", "ftp://a/bc/");
+	testNotEqual("ftp://a/b//", "ftp://a/bc/");
+
+	testNotEqual("ftp://a/b;fc/", "ftp://a/bf;c/");
+	testNotEqual("ftp://a/b%3bfc/", "ftp://a/b;fc/");
+	testEqual("ftp://a/b;/;/", "ftp://a/b;/;/");
+	testNotEqual("ftp://a/b;/", "ftp://a/b//");
+	testNotEqual("ftp://a/b//", "ftp://a/b;/");
+	testNotEqual("ftp://a/b/;", "ftp://a/b//");
+	testNotEqual("ftp://a/b//", "ftp://a/b/;");
+	testNotEqual("ftp://a/b;/", "ftp://a/b;//");
+	testNotEqual("ftp://a/b;//", "ftp://a/b;/");
+
+	// escaping/unescaping
+	testEscape("hello\u1212there", "hello%E1%88%92there");
+	testEscape("hello\u0232there", "hello%C8%B2there");
+	testEscape("hello\uDA42\uDD42there", "hello%F2%A0%A5%82there");
+	testEscape("hello\uDA42", "hello%ED%A9%82");
+	testEscape("hello\uDA42there", "hello%ED%A9%82there");
+	testUnescape("hello%F2%A0%A5%82there", "hello\uDA42\uDD42there");
+	testUnescape("hello%F2%A0%A5there", "hello\u00F2\u00A0\u00A5there");
+	testUnescape("hello%F2%A0there", "hello\u00F2\u00A0there");
+	testUnescape("hello%F2there", "hello\u00F2there");
+	testUnescape("hello%F2%A0%A5%82", "hello\uDA42\uDD42");
+	testUnescape("hello%F2%A0%A5", "hello\u00F2\u00A0\u00A5");
+	testUnescape("hello%F2%A0", "hello\u00F2\u00A0");
+	testUnescape("hello%F2", "hello\u00F2");
+	testUnescape("hello%E1%88%92there", "hello\u1212there");
+	testUnescape("hello%E1%88there", "hello\u00E1\u0088there");
+	testUnescape("hello%E1there", "hello\u00E1there");
+	testUnescape("hello%E1%71there", "hello\u00E1qthere");
+	testUnescape("hello%E1%88", "hello\u00E1\u0088");
+	testUnescape("hello%E1%71", "hello\u00E1q");
+	testUnescape("hello%E1", "hello\u00E1");
+	testUnescape("hello%C8%B2there", "hello\u0232there");
+	testUnescape("hello%C8there", "hello\u00C8there");
+	testUnescape("hello%C8%71there", "hello\u00C8qthere");
+	testUnescape("hello%C8%71", "hello\u00C8q");
+	testUnescape("hello%C8", "hello\u00C8");
+	testUnescape("%71there", "qthere");
+	testUnescape("%B1there", "\u00B1there");
+
+	System.err.println("*** Tests finished successfuly");
+    }
+
+    private static final String nl = System.getProperty("line.separator");
+
+    private static void testParser(URI base, String relURI, String result)
+	    throws Exception
+    {
+	if (!(new URI(base, relURI).toExternalForm().equals(result)))
+	{
+	    throw new Exception("Test failed: " + nl +
+				"  base-URI = <" + base + ">" + nl +
+				"  rel-URI  = <" + relURI + ">" + nl+
+				"  expected   <" + result + ">" + nl+
+				"  but got    <" + new URI(base, relURI) + ">");
+	}
+    }
+
+    private static void testEqual(String one, String two)  throws Exception
+    {
+	URI u1 = new URI(one);
+	URI u2 = new URI(two);
+
+	if (!u1.equals(u2))
+	{
+	    throw new Exception("Test failed: " + nl +
+				"  <" + one + "> != <" + two + ">");
+	}
+	if (u1.hashCode() != u2.hashCode())
+	{
+	    throw new Exception("Test failed: " + nl +
+				"  hashCode <" + one + "> != hashCode <" + two + ">");
+	}
+    }
+
+    private static void testNotEqual(String one, String two)  throws Exception
+    {
+	URI u1 = new URI(one);
+	URI u2 = new URI(two);
+
+	if (u1.equals(u2))
+	{
+	    throw new Exception("Test failed: " + nl +
+				"  <" + one + "> == <" + two + ">");
+	}
+    }
+
+    private static void testPE(URI base, String uri)  throws Exception
+    {
+	boolean got_pe = false;
+	try
+	    { new URI(base, uri); }
+	catch (ParseException pe)
+	    { got_pe = true; }
+	if (!got_pe)
+	{
+	    throw new Exception("Test failed: " + nl +
+				"  <" + uri + "> should be invalid");
+	}
+    }
+
+    private static void testEscape(String raw, String escaped)  throws Exception
+    {
+	String test = new String(escape(raw.toCharArray(), uricChar, true));
+	if (!test.equals(escaped))
+	    throw new Exception("Test failed: " + nl +
+				"  raw-string: " + raw + nl +
+				"  escaped:    " + test + nl +
+				"  expected:   " + escaped);
+    }
+
+    private static void testUnescape(String escaped, String raw)
+	throws Exception
+    {
+	if (!unescape(escaped, null).equals(raw))
+	    throw new Exception("Test failed: " + nl +
+				"  escaped-string: " + escaped + nl +
+				"  unescaped:      " + unescape(escaped, null) + nl +
+				"  expected:       " + raw);
+    }
+}
diff --git a/HTTPClient/UncompressInputStream.java b/HTTPClient/UncompressInputStream.java
new file mode 100644
index 0000000..01b002c
--- /dev/null
+++ b/HTTPClient/UncompressInputStream.java
@@ -0,0 +1,470 @@
+/*
+ * @(#)UncompressInputStream.java			0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ *  This version has been modified from the original 0.3-3 version by the 
+ *  Unidata Program Center (support at unidata.ucar.edu) to make the constructor 
+ *  public and to fix a couple of bugs.
+ */
+
+package HTTPClient;
+
+import java.io.IOException;
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.FilterInputStream;
+
+
+/**
+ * This class decompresses an input stream containing data compressed with
+ * the unix "compress" utility (LZC, a LZW variant). This code is based
+ * heavily on the <var>unlzw.c</var> code in <var>gzip-1.2.4</var> (written
+ * by Peter Jannesen) and the original compress code.  
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ * @author	Unidata Program Center 
+ */
+public class UncompressInputStream extends FilterInputStream
+{
+    /**
+     * @param is the input stream to decompress
+     * @exception IOException if the header is malformed
+     */
+    public UncompressInputStream(InputStream is)  throws IOException
+    {
+	super(is);
+	parse_header();
+    }
+
+
+    byte[] one = new byte[1];
+    public synchronized int read() throws IOException
+    {
+	int b = read(one, 0, 1);
+	if (b == 1)
+	    return (one[0] & 0xff);
+	else
+	    return -1;
+    }
+
+
+    // string table stuff
+    private static final int TBL_CLEAR = 0x100;
+    private static final int TBL_FIRST = TBL_CLEAR + 1;
+
+    private int[]  tab_prefix;
+    private byte[] tab_suffix;
+    private int[]  zeros = new int[256];
+    private byte[] stack;
+
+    // various state
+    private boolean block_mode;
+    private int  n_bits;
+    private int  maxbits;
+    private int  maxmaxcode;
+    private int  maxcode;
+    private int  bitmask;
+    private int  oldcode;
+    private byte finchar;
+    private int  stackp;
+    private int  free_ent;
+
+    // input buffer
+    private byte[]  data = new byte[10000];
+    private int     bit_pos = 0, end = 0, got = 0;
+    private boolean eof  = false;
+    private static final int EXTRA = 64;
+
+
+    public synchronized int read(byte[] buf, int off, int len)
+	    throws IOException
+    {
+	if (eof)  return -1;
+	int start = off;
+
+	/* Using local copies of various variables speeds things up by as
+	 * much as 30% !
+	 */
+	int[]  l_tab_prefix = tab_prefix;
+	byte[] l_tab_suffix = tab_suffix;
+	byte[] l_stack      = stack;
+	int    l_n_bits     = n_bits;
+	int    l_maxcode    = maxcode;
+	int    l_maxmaxcode = maxmaxcode;
+	int    l_bitmask    = bitmask;
+	int    l_oldcode    = oldcode;
+	byte   l_finchar    = finchar;
+	int    l_stackp     = stackp;
+	int    l_free_ent   = free_ent;
+	byte[] l_data       = data;
+	int    l_bit_pos    = bit_pos;
+
+
+	// empty stack if stuff still left
+
+	int s_size = l_stack.length - l_stackp;
+	if (s_size > 0)
+	{
+	    int num = (s_size >= len) ? len : s_size ;
+	    System.arraycopy(l_stack, l_stackp, buf, off, num);
+	    off += num;
+	    len -= num;
+	    l_stackp += num;
+	}
+
+	if (len == 0)
+	{
+	    stackp = l_stackp;
+	    return off-start;
+	}
+
+
+	// loop, filling local buffer until enough data has been decompressed
+
+	main_loop: do
+	{
+	    if (end < EXTRA)  fill();
+
+	    int bit_in = (got > 0) ? (end - end%l_n_bits)<<3 :
+				     (end<<3)-(l_n_bits-1);
+
+	    while (l_bit_pos < bit_in)
+	    {
+		// handle 1-byte reads correctly
+		if (len == 0) {
+		    n_bits     = l_n_bits;
+		    maxcode    = l_maxcode;
+		    maxmaxcode = l_maxmaxcode;
+		    bitmask    = l_bitmask;
+		    oldcode    = l_oldcode;
+		    finchar    = l_finchar;
+		    stackp     = l_stackp;
+		    free_ent   = l_free_ent;
+		    bit_pos    = l_bit_pos;
+
+		    return off-start;
+		}		    
+
+		// check for code-width expansion
+
+		if (l_free_ent > l_maxcode)
+		{
+		    int n_bytes = l_n_bits << 3;
+		    l_bit_pos = (l_bit_pos-1) +
+				n_bytes - (l_bit_pos-1+n_bytes) % n_bytes;
+
+		    l_n_bits++;
+		    l_maxcode = (l_n_bits==maxbits) ? l_maxmaxcode :
+						      (1<<l_n_bits) - 1;
+
+		    if (debug)
+			System.err.println("Code-width expanded to " + l_n_bits);
+
+		    l_bitmask = (1<<l_n_bits)-1;
+		    l_bit_pos = resetbuf(l_bit_pos);
+		    continue main_loop;
+		}
+
+
+		// read next code
+
+		int pos = l_bit_pos>>3;
+		int code = (((l_data[pos]&0xFF) | ((l_data[pos+1]&0xFF)<<8) |
+			    ((l_data[pos+2]&0xFF)<<16))
+			    >> (l_bit_pos & 0x7)) & l_bitmask;
+		l_bit_pos += l_n_bits;
+
+
+		// handle first iteration
+
+		if (l_oldcode == -1)
+		{
+		    if (code >= 256)
+			throw new IOException("corrupt input: " + code +
+					      " > 255");
+		    l_finchar = (byte) (l_oldcode = code);
+		    buf[off++] = l_finchar;
+		    len--;
+		    continue;
+		}
+
+
+		// handle CLEAR code
+
+		if (code == TBL_CLEAR && block_mode)
+		{
+		    System.arraycopy(zeros, 0, l_tab_prefix, 0, zeros.length);
+		    l_free_ent = TBL_FIRST - 1;
+
+		    int n_bytes = l_n_bits << 3;
+		    l_bit_pos = (l_bit_pos-1) +
+				n_bytes - (l_bit_pos-1+n_bytes) % n_bytes;
+		    l_n_bits  = INIT_BITS;
+		    l_maxcode = (1 << l_n_bits) - 1;
+		    l_bitmask = l_maxcode;
+
+		    if (debug)  System.err.println("Code tables reset");
+
+		    l_bit_pos = resetbuf(l_bit_pos);
+		    continue main_loop;
+		}
+
+
+		// setup
+
+		int incode = code;
+		l_stackp = l_stack.length;
+
+
+		// Handle KwK case
+
+		if (code >= l_free_ent)
+		{
+		    if (code > l_free_ent)
+			throw new IOException("corrupt input: code=" + code +
+					      ", free_ent=" + l_free_ent);
+		    
+		    l_stack[--l_stackp] = l_finchar;
+		    code = l_oldcode;
+		}
+
+
+		// Generate output characters in reverse order
+
+		while (code >= 256)
+		{
+		    l_stack[--l_stackp] = l_tab_suffix[code];
+		    code = l_tab_prefix[code];
+		}
+		l_finchar  = l_tab_suffix[code];
+		buf[off++] = l_finchar;
+		len--;
+
+
+		// And put them out in forward order
+
+		s_size = l_stack.length - l_stackp;
+		int num = (s_size >= len) ? len : s_size ;
+		System.arraycopy(l_stack, l_stackp, buf, off, num);
+		off += num;
+		len -= num;
+		l_stackp += num;
+
+
+		// generate new entry in table
+
+		if (l_free_ent < l_maxmaxcode)
+		{
+		    l_tab_prefix[l_free_ent] = l_oldcode;
+		    l_tab_suffix[l_free_ent] = l_finchar;
+		    l_free_ent++;
+		}
+
+
+		// Remember previous code
+
+		l_oldcode = incode;
+
+
+		// if output buffer full, then return
+
+		if (len == 0)
+		{
+		    n_bits   = l_n_bits;
+		    maxcode  = l_maxcode;
+		    bitmask  = l_bitmask;
+		    oldcode  = l_oldcode;
+		    finchar  = l_finchar;
+		    stackp   = l_stackp;
+		    free_ent = l_free_ent;
+		    bit_pos  = l_bit_pos;
+
+		    return off-start;
+		}
+	    }
+
+	    l_bit_pos = resetbuf(l_bit_pos);
+	} while (got > 0);
+
+	n_bits   = l_n_bits;
+	maxcode  = l_maxcode;
+	bitmask  = l_bitmask;
+	oldcode  = l_oldcode;
+	finchar  = l_finchar;
+	stackp   = l_stackp;
+	free_ent = l_free_ent;
+	bit_pos  = l_bit_pos;
+
+	eof = true;
+	return off-start;
+    }
+
+
+    /**
+     * Moves the unread data in the buffer to the beginning and resets
+     * the pointers.
+     */
+    private final int resetbuf(int bit_pos)
+    {
+	int pos = bit_pos >> 3;
+	System.arraycopy(data, pos, data, 0, end-pos);
+	end -= pos;
+	return 0;
+    }
+
+
+    private final void fill()  throws IOException
+    {
+	got = in.read(data, end, data.length-1-end);
+	if (got > 0)  end += got;
+    }
+
+
+    public synchronized long skip(long num)  throws IOException
+    {
+	byte[] tmp = new byte[(int) num];
+	int got = read(tmp, 0, (int) num);
+
+	if (got > 0)
+	    return (long) got;
+	else
+	    return 0L;
+    }
+
+
+    public synchronized int available()  throws IOException
+    {
+	if (eof)  return 0;
+
+	return in.available();
+    }
+
+
+    private static final int LZW_MAGIC		= 0x1f9d;
+    private static final int MAX_BITS		= 16;
+    private static final int INIT_BITS		= 9;
+    private static final int HDR_MAXBITS 	= 0x1f;
+    private static final int HDR_EXTENDED	= 0x20;
+    private static final int HDR_FREE     	= 0x40;
+    private static final int HDR_BLOCK_MODE	= 0x80;
+
+    private void parse_header()  throws IOException
+    {
+	// read in and check magic number 
+
+	int t = in.read();
+	if (t < 0)  throw new EOFException("Failed to read magic number");
+	int magic = (t & 0xff) << 8;
+	t = in.read();
+	if (t < 0)  throw new EOFException("Failed to read magic number");
+	magic += t & 0xff;
+	if (magic != LZW_MAGIC)
+	    throw new IOException("Input not in compress format (read " +
+				  "magic number 0x" +
+				  Integer.toHexString(magic) + ")");
+
+
+	// read in header byte
+
+	int header = in.read();
+	if (header < 0)  throw new EOFException("Failed to read header");
+
+	block_mode = (header & HDR_BLOCK_MODE) > 0;
+	maxbits    = header & HDR_MAXBITS;
+
+	if (maxbits > MAX_BITS)
+	    throw new IOException("Stream compressed with " + maxbits +
+				  " bits, but can only handle " + MAX_BITS +
+				  " bits");
+
+	if ((header & HDR_EXTENDED) > 0)
+	    throw new IOException("Header extension bit set");
+
+	if ((header & HDR_FREE) > 0)
+	    throw new IOException("Header bit 6 set");
+
+	if (debug)
+	{
+	    System.err.println("block mode: " + block_mode);
+	    System.err.println("max bits:   " + maxbits);
+	}
+
+
+	// initialize stuff
+
+	maxmaxcode = 1 << maxbits;
+	n_bits     = INIT_BITS;
+	maxcode    = (1 << n_bits) - 1;
+	bitmask    = maxcode;
+	oldcode    = -1;
+	finchar    = 0;
+	free_ent   = block_mode ? TBL_FIRST : 256;
+
+	tab_prefix = new int[1 << maxbits];
+	tab_suffix = new byte[1 << maxbits];
+	stack      = new byte[1 << maxbits];
+	stackp     = stack.length;
+
+	for (int idx=255; idx>=0; idx--)
+	    tab_suffix[idx] = (byte) idx;
+    }
+
+
+    private static final boolean debug = false;
+
+    public static void main (String args[])  throws Exception
+    {
+	if (args.length != 1)
+	{
+	    System.err.println("Usage: UncompressInputStream <file>");
+	    System.exit(1);
+	}
+
+	InputStream in =
+		    new UncompressInputStream(new FileInputStream(args[0]));
+
+	byte[] buf = new byte[100000];
+	int tot = 0;
+	long beg = System.currentTimeMillis();
+
+	while (true)
+	{
+	    int got = in.read(buf);
+	    if (got < 0)  break;
+	    System.out.write(buf, 0, got);
+	    tot += got;
+	}
+
+	long end = System.currentTimeMillis();
+	System.err.println("Decompressed " + tot + " bytes");
+	System.err.println("Time: " + (end-beg)/1000. + " seconds");
+    }
+}
diff --git a/HTTPClient/Util.java b/HTTPClient/Util.java
new file mode 100644
index 0000000..16f25c5
--- /dev/null
+++ b/HTTPClient/Util.java
@@ -0,0 +1,1131 @@
+/*
+ * @(#)Util.java					0.3-3 06/05/2001
+ *
+ *  This file is part of the HTTPClient package
+ *  Copyright (C) 1996-2001 Ronald Tschal�r
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free
+ *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ *  MA 02111-1307, USA
+ *
+ *  For questions, suggestions, bug-reports, enhancement-requests etc.
+ *  I may be contacted at:
+ *
+ *  ronald at innovation.ch
+ *
+ *  The HTTPClient's home page is located at:
+ *
+ *  http://www.innovation.ch/java/HTTPClient/ 
+ *
+ */
+
+package HTTPClient;
+
+import java.lang.reflect.Array;
+import java.net.URL;
+import java.util.Date;
+import java.util.BitSet;
+import java.util.Locale;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.SimpleTimeZone;
+import java.util.StringTokenizer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+
+/**
+ * This class holds various utility methods.
+ *
+ * @version	0.3-3  06/05/2001
+ * @author	Ronald Tschal�r
+ */
+public class Util
+{
+    private static final BitSet Separators = new BitSet(128);
+    private static final BitSet TokenChar = new BitSet(128);
+    private static final BitSet UnsafeChar = new BitSet(128);
+    private static DateFormat http_format;
+    private static DateFormat parse_1123;
+    private static DateFormat parse_850;
+    private static DateFormat parse_asctime;
+    private static final Object http_format_lock = new Object();
+    private static final Object http_parse_lock  = new Object();
+
+    static
+    {
+	// rfc-2616 tspecial
+	Separators.set('(');
+	Separators.set(')');
+	Separators.set('<');
+	Separators.set('>');
+	Separators.set('@');
+	Separators.set(',');
+	Separators.set(';');
+	Separators.set(':');
+	Separators.set('\\');
+	Separators.set('"');
+	Separators.set('/');
+	Separators.set('[');
+	Separators.set(']');
+	Separators.set('?');
+	Separators.set('=');
+	Separators.set('{');
+	Separators.set('}');
+	Separators.set(' ');
+	Separators.set('\t');
+
+	// rfc-2616 token
+	for (int ch=32; ch<127; ch++)  TokenChar.set(ch);
+	TokenChar.xor(Separators);
+
+	// rfc-1738 unsafe characters, including CTL and SP, and excluding
+	// "#" and "%"
+	for (int ch=0; ch<32; ch++)  UnsafeChar.set(ch);
+	UnsafeChar.set(' ');
+	UnsafeChar.set('<');
+	UnsafeChar.set('>');
+	UnsafeChar.set('"');
+	UnsafeChar.set('{');
+	UnsafeChar.set('}');
+	UnsafeChar.set('|');
+	UnsafeChar.set('\\');
+	UnsafeChar.set('^');
+	UnsafeChar.set('~');
+	UnsafeChar.set('[');
+	UnsafeChar.set(']');
+	UnsafeChar.set('`');
+	UnsafeChar.set(127);
+
+	// rfc-1123 date format (restricted to GMT, as per rfc-2616)
+	/* This initialization has been moved to httpDate() because it
+	 * takes an awfully long time and is often not needed
+	 *
+	http_format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
+					   Locale.US);
+	http_format.setTimeZone(new SimpleTimeZone(0, "GMT"));
+	*/
+    }
+
+
+    // Constructors
+
+    /**
+     * This class isn't meant to be instantiated.
+     */
+    private Util() {}
+
+
+    // Methods
+
+    final static Object[] resizeArray(Object[] src, int new_size)
+    {
+	Class compClass = src.getClass().getComponentType();
+	Object tmp[] = (Object[]) Array.newInstance(compClass, new_size);
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static NVPair[] resizeArray(NVPair[] src, int new_size)
+    {
+	NVPair tmp[] = new NVPair[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static AuthorizationInfo[] resizeArray(AuthorizationInfo[] src,
+						 int new_size)
+    {
+	AuthorizationInfo tmp[] = new AuthorizationInfo[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static Cookie[] resizeArray(Cookie[] src, int new_size)
+    {
+	Cookie tmp[] = new Cookie[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static String[] resizeArray(String[] src, int new_size)
+    {
+	String tmp[] = new String[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static boolean[] resizeArray(boolean[] src, int new_size)
+    {
+	boolean tmp[] = new boolean[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static byte[] resizeArray(byte[] src, int new_size)
+    {
+	byte tmp[] = new byte[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static char[] resizeArray(char[] src, int new_size)
+    {
+	char tmp[] = new char[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+    final static int[] resizeArray(int[] src, int new_size)
+    {
+	int tmp[] = new int[new_size];
+	System.arraycopy(src, 0, tmp, 0,
+			(src.length < new_size ? src.length : new_size));
+	return tmp;
+    }
+
+
+    /**
+     * Split a property into an array of Strings, using "|" as the
+     * separator.
+     */
+    static String[] splitProperty(String prop)
+    {
+	if (prop == null)  return new String[0];
+
+	StringTokenizer tok = new StringTokenizer(prop, "|");
+	String[] list = new String[tok.countTokens()];
+	for (int idx=0; idx<list.length; idx++)
+	    list[idx] = tok.nextToken().trim();
+
+	return list;
+    }
+
+
+    /**
+     * Helper method for context lists used by modules. Returns the
+     * list associated with the context if it exists; otherwise it creates
+     * a new list and adds it to the context list.
+     *
+     * @param cntxt_list the list of lists indexed by context
+     * @param cntxt the context
+     */
+    final static Hashtable getList(Hashtable cntxt_list, Object cntxt)
+    {
+	synchronized (cntxt_list)
+	{
+	    Hashtable list = (Hashtable) cntxt_list.get(cntxt);
+	    if (list == null)
+	    {
+		list = new Hashtable();
+		cntxt_list.put(cntxt, list);
+	    }
+
+	    return list;
+	}
+    }
+
+
+    /**
+     * Creates an array of distances to speed up the search in findStr().
+     * The returned array should be passed as the second argument to
+     * findStr().
+     *
+     * @param search the search string (same as the first argument to
+     *               findStr()).
+     * @return an array of distances (to be passed as the second argument to
+     *         findStr()).
+     */
+    final static int[] compile_search(byte[] search)
+    {
+	int[] cmp = {0, 1, 0, 1, 0, 1};
+	int   end;
+
+	for (int idx=0; idx<search.length; idx++)
+	{
+	    for (end=idx+1; end<search.length; end++)
+	    {
+		if (search[idx] == search[end])  break;
+	    }
+	    if (end < search.length)
+	    {
+		if ((end-idx) > cmp[1])
+		{
+		    cmp[4] = cmp[2];
+		    cmp[5] = cmp[3];
+		    cmp[2] = cmp[0];
+		    cmp[3] = cmp[1];
+		    cmp[0] = idx;
+		    cmp[1] = end - idx;
+		}
+		else if ((end-idx) > cmp[3])
+		{
+		    cmp[4] = cmp[2];
+		    cmp[5] = cmp[3];
+		    cmp[2] = idx;
+		    cmp[3] = end - idx;
+		}
+		else if ((end-idx) > cmp[3])
+		{
+		    cmp[4] = idx;
+		    cmp[5] = end - idx;
+		}
+	    }
+	}
+
+	cmp[1] += cmp[0];
+	cmp[3] += cmp[2];
+	cmp[5] += cmp[4];
+	return cmp;
+    }
+
+    /**
+     * Search for a string. Use compile_search() to first generate the second
+     * argument. This uses a Knuth-Morris-Pratt like algorithm.
+     *
+     * @param search  the string to search for.
+     * @param cmp     the the array returned by compile_search.
+     * @param str     the string in which to look for <var>search</var>.
+     * @param beg     the position at which to start the search in
+     *                <var>str</var>.
+     * @param end     the position at which to end the search in <var>str</var>,
+     *                noninclusive.
+     * @return the position in <var>str</var> where <var>search</var> was
+     *         found, or -1 if not found.
+     */
+    final static int findStr(byte[] search, int[] cmp, byte[] str,
+				     int beg, int end)
+    {
+	int c1f  = cmp[0],
+	    c1l  = cmp[1],
+	    d1   = c1l - c1f,
+	    c2f  = cmp[2],
+	    c2l  = cmp[3],
+	    d2   = c2l - c2f,
+	    c3f  = cmp[4],
+	    c3l  = cmp[5],
+	    d3   = c3l - c3f;
+
+	Find: while (beg+search.length <= end)
+	{
+	    if (search[c1l] == str[beg+c1l])
+	    {
+		/* This is correct, but Visual J++ can't cope with it...
+		Comp: if (search[c1f] == str[beg+c1f])
+		{
+		    for (int idx=0; idx<search.length; idx++)
+			if (search[idx] != str[beg+idx])  break Comp;
+
+		    break Find;		// we found it
+		}
+		*  so here is the replacement: */
+		if (search[c1f] == str[beg+c1f])
+		{
+		    boolean same = true;
+
+		    for (int idx=0; idx<search.length; idx++)
+			if (search[idx] != str[beg+idx])
+			{
+				same = false;
+				break;
+			}
+
+		    if (same)
+			break Find;         // we found it
+		}
+
+		beg += d1;
+	    }
+	    else if (search[c2l] == str[beg+c2l])
+		beg += d2;
+	    else if (search[c3l] == str[beg+c3l])
+		beg += d3;
+	    else
+		beg++;
+	}
+
+	if (beg+search.length > end)
+	    return -1;
+	else
+	    return beg;
+    }
+
+
+    /**
+     * Replace quoted characters by their unquoted version. Quoted characters
+     * are characters preceded by a slash. E.g. "\c" would be replaced by "c".
+     * This is used in parsing http headers where quoted-characters are
+     * allowed in quoted-strings and often used to quote the quote character
+     * <">.
+     *
+     * @param str the string do dequote
+     * @return the string do with all quoted characters replaced by their
+     *         true value.
+     */
+    public final static String dequoteString(String str)
+    {
+	if (str.indexOf('\\') == -1)  return str;
+
+	char[] buf = str.toCharArray();
+	int pos = 0, num_deq = 0;
+	while (pos < buf.length)
+	{
+	    if (buf[pos] == '\\'  &&  pos+1 < buf.length)
+	    {
+		System.arraycopy(buf, pos+1, buf, pos, buf.length-pos-1);
+		num_deq++;
+	    }
+	    pos++;
+	}
+
+	return new String(buf, 0, buf.length-num_deq);
+    }
+
+
+    /**
+     * Replace given characters by their quoted version. Quoted characters
+     * are characters preceded by a slash. E.g. "c" would be replaced by "\c".
+     * This is used in generating http headers where certain characters need
+     * to be quoted, such as the quote character <">.
+     *
+     * @param str   the string do quote
+     * @param qlist the list of characters to quote
+     * @return the string do with all characters replaced by their
+     *         quoted version.
+     */
+    public final static String quoteString(String str, String qlist)
+    {
+	char[] list = qlist.toCharArray();
+	int idx;
+	for (idx=0; idx<list.length; idx++)
+	    if (str.indexOf(list[idx]) != -1)  break;
+	if (idx == list.length)  return str;
+
+	int len = str.length();
+	char[] buf = new char[len*2];
+	str.getChars(0, len, buf, 0);
+	int pos = 0;
+	while (pos < len)
+	{
+	    if (qlist.indexOf(buf[pos], 0) != -1)
+	    {
+		if (len == buf.length)
+		    buf = Util.resizeArray(buf, len+str.length());
+
+		System.arraycopy(buf, pos, buf, pos+1, len-pos);
+		len++;
+		buf[pos++] = '\\';
+	    }
+	    pos++;
+	}
+
+	return new String(buf, 0, len);
+    }
+
+
+    /**
+     * This parses the value part of a header. All quoted strings are
+     * dequoted.
+     *
+     * @see #parseHeader(java.lang.String, boolean)
+     * @param header  the value part of the header.
+     * @return a Vector containing all the elements; each entry is an
+     *         instance of <var>HttpHeaderElement</var>.
+     * @exception ParseException if the syntax rules are violated.
+     */
+    public final static Vector parseHeader(String header)  throws ParseException
+    {
+	return parseHeader(header, true);
+    }
+
+
+    /**
+     * This parses the value part of a header. The result is a Vector of
+     * HttpHeaderElement's. The syntax the header must conform to is:
+     *
+     * <PRE>
+     * header  = [ element ] *( "," [ element ] )
+     * element = name [ "=" [ value ] ] *( ";" [ param ] )
+     * param   = name [ "=" [ value ] ]
+     * 
+     * name    = token
+     * value   = ( token | quoted-string )
+     * 
+     * token         = 1*<any char except "=", ",", ";", <"> and
+     *                       white space>
+     * quoted-string = <"> *( text | quoted-char ) <">
+     * text          = any char except <">
+     * quoted-char   = "\" char
+     * </PRE>
+     *
+     * Any amount of white space is allowed between any part of the header,
+     * element or param and is ignored. A missing value in any element or
+     * param will be stored as the empty string; if the "=" is also missing
+     * <var>null</var> will be stored instead.
+     *
+     * @param header  the value part of the header.
+     * @param dequote if true all quoted strings are dequoted.
+     * @return a Vector containing all the elements; each entry is an
+     *         instance of <var>HttpHeaderElement</var>.
+     * @exception ParseException if the above syntax rules are violated.
+     * @see HTTPClient.HttpHeaderElement
+     */
+    public final static Vector parseHeader(String header, boolean dequote)
+	    throws ParseException
+    {
+	if (header == null)  return null;
+	char[]  buf    = header.toCharArray();
+	Vector  elems  = new Vector();
+	boolean first  = true;
+	int     beg = -1, end = 0, len = buf.length, abeg[] = new int[1];
+	String  elem_name, elem_value;
+
+
+	elements: while (true)
+	{
+	    if (!first)				// find required ","
+	    {
+		beg = skipSpace(buf, end);
+		if (beg == len)  break;
+		if (buf[beg] != ',')
+		    throw new ParseException("Bad header format: '" + header +
+					     "'\nExpected \",\" at position " +
+					     beg);
+	    }
+	    first = false;
+
+	    beg = skipSpace(buf, beg+1);
+	    if (beg == len)  break elements;
+	    if (buf[beg] == ',')		// skip empty elements
+	    {
+		end = beg;
+		continue elements;
+	    }
+
+	    if (buf[beg] == '='  ||  buf[beg] == ';'  ||  buf[beg] == '"')
+		throw new ParseException("Bad header format: '" + header +
+					 "'\nEmpty element name at position " +
+					 beg);
+
+	    end = beg+1;			// extract element name
+	    while (end < len  &&  !Character.isWhitespace(buf[end])  &&
+		   buf[end] != '='  &&  buf[end] != ','  &&  buf[end] != ';')
+		end++;
+	    elem_name = new String(buf, beg, end-beg);
+
+	    beg = skipSpace(buf, end);
+	    if (beg < len  &&  buf[beg] == '=')	// element value
+	    {
+		abeg[0] = beg+1;
+		elem_value = parseValue(buf, abeg, header, dequote);
+		end = abeg[0];
+	    }
+	    else
+	    {
+		elem_value = null;
+		end = beg;
+	    }
+
+	    NVPair[] params = new NVPair[0];
+	    params: while (true)
+	    {
+		String param_name, param_value;
+
+		beg = skipSpace(buf, end);	// expect ";"
+		if (beg == len  ||  buf[beg] != ';')
+		    break params;
+
+		beg = skipSpace(buf, beg+1);
+		if (beg == len  ||  buf[beg] == ',')
+		{
+		    end = beg;
+		    break params;
+		}
+		if (buf[beg] == ';')		// skip empty parameters
+		{
+		    end = beg;
+		    continue params;
+		}
+
+		if (buf[beg] == '='  ||  buf[beg] == '"')
+		    throw new ParseException("Bad header format: '" + header +
+					 "'\nEmpty parameter name at position "+
+					 beg);
+
+		end = beg+1;			// extract param name
+		while (end < len  &&  !Character.isWhitespace(buf[end])  &&
+		       buf[end] != '='  &&  buf[end] != ','  && buf[end] != ';')
+		    end++;
+		param_name = new String(buf, beg, end-beg);
+
+		beg = skipSpace(buf, end);
+		if (beg < len  &&  buf[beg] == '=')	// element value
+		{
+		    abeg[0] = beg+1;
+		    param_value = parseValue(buf, abeg, header, dequote);
+		    end = abeg[0];
+		}
+		else
+		{
+		    param_value = null;
+		    end = beg;
+		}
+
+		params = Util.resizeArray(params, params.length+1);
+		params[params.length-1] = new NVPair(param_name, param_value);
+	    }
+
+	    elems.addElement(
+		      new HttpHeaderElement(elem_name, elem_value, params));
+	}
+
+	return elems;
+    }
+
+
+    /**
+     * Parse the value part. Accepts either token or quoted string.
+     */
+    private static String parseValue(char[] buf, int[] abeg, String header,
+				     boolean dequote)
+		throws ParseException
+    {
+	int beg = abeg[0], end = beg, len = buf.length;
+	String value;
+
+
+	beg = skipSpace(buf, beg);
+
+	if (beg < len  &&  buf[beg] == '"')	// it's a quoted-string
+	{
+	    beg++;
+	    end = beg;
+	    char[] deq_buf = null;
+	    int    deq_pos = 0, lst_pos = beg;
+
+	    while (end < len  &&  buf[end] != '"')
+	    {
+		if (buf[end] == '\\')
+		{
+		    if (dequote)	// dequote char
+		    {
+			if (deq_buf == null)
+			    deq_buf = new char[buf.length];
+			System.arraycopy(buf, lst_pos, deq_buf, deq_pos,
+					 end-lst_pos);
+			deq_pos += end-lst_pos;
+			lst_pos = ++end;
+		    }
+		    else
+			end++;		// skip quoted char
+		}
+
+		end++;
+	    }
+	    if (end == len)
+		throw new ParseException("Bad header format: '" + header +
+					 "'\nClosing <\"> for quoted-string"+
+					 " starting at position " +
+					 (beg-1) + " not found");
+	    if (deq_buf != null)
+	    {
+		System.arraycopy(buf, lst_pos, deq_buf, deq_pos, end-lst_pos);
+		deq_pos += end-lst_pos;
+		value = new String(deq_buf, 0, deq_pos);
+	    }
+	    else
+		value = new String(buf, beg, end-beg);
+	    end++;
+	}
+	else					// it's a simple token value
+	{
+	    end = beg;
+	    while (end < len  &&  !Character.isWhitespace(buf[end])  &&
+		   buf[end] != ','  &&  buf[end] != ';')
+		end++;
+
+	    value = new String(buf, beg, end-beg);
+	}
+
+	abeg[0] = end;
+	return value;
+    }
+
+
+    /**
+     * Determines if the given header contains a certain token. The header
+     * must conform to the rules outlined in parseHeader().
+     *
+     * @see #parseHeader(java.lang.String)
+     * @param header the header value.
+     * @param token  the token to find; the match is case-insensitive.
+     * @return true if the token is present, false otherwise.
+     * @exception ParseException if this is thrown parseHeader().
+     */
+    public final static boolean hasToken(String header, String token)
+	    throws ParseException
+    {
+	if (header == null)
+	    return false;
+	else
+	    return parseHeader(header).contains(new HttpHeaderElement(token));
+    }
+
+
+    /**
+     * Get the HttpHeaderElement with the name <var>name</var>.
+     *
+     * @param header a vector of HttpHeaderElement's, such as is returned
+     *               from <code>parseHeader()</code>
+     * @param name   the name of element to retrieve; matching is
+     *               case-insensitive
+     * @return the request element, or null if none found.
+     * @see #parseHeader(java.lang.String)
+     */
+    public final static HttpHeaderElement getElement(Vector header, String name)
+    {
+	int idx = header.indexOf(new HttpHeaderElement(name));
+	if (idx == -1)
+	    return null;
+	else
+	    return (HttpHeaderElement) header.elementAt(idx);
+    }
+
+
+    /**
+     * retrieves the value associated with the parameter <var>param</var> in
+     * a given header string. It parses the header using
+     * <code>parseHeader()</code> and then searches the first element for the
+     * given parameter. This is used especially in headers like
+     * 'Content-type' and 'Content-Disposition'.
+     *
+     * <P>quoted characters ("\x") in a quoted string are dequoted.
+     *
+     * @see #parseHeader(java.lang.String)
+     * @param  param  the parameter name
+     * @param  hdr    the header value
+     * @return the value for this parameter, or null if not found.
+     * @exception ParseException if the above syntax rules are violated.
+     */
+    public final static String getParameter(String param, String hdr)
+	    throws ParseException
+    {
+	NVPair[] params = ((HttpHeaderElement) parseHeader(hdr).firstElement()).
+			    getParams();
+
+	for (int idx=0; idx<params.length; idx++)
+	{
+	    if (params[idx].getName().equalsIgnoreCase(param))
+		return params[idx].getValue();
+	}
+
+	return null;
+    }
+
+
+    /**
+     * Assembles a Vector of HttpHeaderElements into a full header string.
+     * The individual header elements are seperated by a ", ".
+     *
+     * @param the parsed header
+     * @return a string containing the assembled header
+     */
+    public final static String assembleHeader(Vector pheader)
+    {
+	StringBuffer hdr = new StringBuffer(200);
+	int len = pheader.size();
+
+	for (int idx=0; idx<len; idx++)
+	{
+	    ((HttpHeaderElement) pheader.elementAt(idx)).appendTo(hdr);
+	    hdr.append(", ");
+	}
+	hdr.setLength(hdr.length()-2);
+
+	return hdr.toString();
+    }
+
+
+    /**
+     * returns the position of the first non-space character in a char array
+     * starting a position pos.
+     *
+     * @param str the char array
+     * @param pos the position to start looking
+     * @return the position of the first non-space character
+     */
+    final static int skipSpace(char[] str, int pos)
+    {
+	int len = str.length;
+	while (pos < len  &&  Character.isWhitespace(str[pos]))  pos++;
+	return pos;
+    }
+
+    /**
+     * returns the position of the first space character in a char array
+     * starting a position pos.
+     *
+     * @param str the char array
+     * @param pos the position to start looking
+     * @return the position of the first space character, or the length of
+     *         the string if not found
+     */
+    final static int findSpace(char[] str, int pos)
+    {
+	int len = str.length;
+	while (pos < len  &&  !Character.isWhitespace(str[pos]))  pos++;
+	return pos;
+    }
+
+    /**
+     * returns the position of the first non-token character in a char array
+     * starting a position pos.
+     *
+     * @param str the char array
+     * @param pos the position to start looking
+     * @return the position of the first non-token character, or the length
+     *         of the string if not found
+     */
+    final static int skipToken(char[] str, int pos)
+    {
+	int len = str.length;
+	while (pos < len  &&  TokenChar.get(str[pos]))  pos++;
+	return pos;
+    }
+
+
+    /**
+     * Does the string need to be quoted when sent in a header? I.e. does
+     * it contain non-token characters?
+     *
+     * @param str the string
+     * @return true if it needs quoting (i.e. it contains non-token chars)
+     */
+    final static boolean needsQuoting(String str)
+    {
+	int len = str.length(), pos = 0;
+
+	while (pos < len  &&  TokenChar.get(str.charAt(pos)))  pos++;
+	return (pos < len);
+    }
+
+
+    /**
+     * Compares two http urls for equality. This exists because the method
+     * <code>java.net.URL.sameFile()</code> is broken (an explicit port 80
+     * doesn't compare equal to an implicit port, and it doesn't take
+     * escapes into account).
+     *
+     * <P>Two http urls are considered equal if they have the same protocol
+     * (case-insensitive match), the same host (case-insensitive), the
+     * same port and the same file (after decoding escaped characters).
+     *
+     * @param url1 the first url
+     * @param url1 the second url
+     * @return true if <var>url1</var> and <var>url2</var> compare equal
+     */
+    public final static boolean sameHttpURL(URL url1, URL url2)
+    {
+	if (!url1.getProtocol().equalsIgnoreCase(url2.getProtocol()))
+	    return false;
+
+	if (!url1.getHost().equalsIgnoreCase(url2.getHost()))
+	    return false;
+
+	int port1 = url1.getPort(), port2 = url2.getPort();
+	if (port1 == -1)  port1 = URI.defaultPort(url1.getProtocol());
+	if (port2 == -1)  port2 = URI.defaultPort(url1.getProtocol());
+	if (port1 != port2)
+	    return false;
+
+	try
+	    { return URI.unescape(url1.getFile(), null).equals(URI.unescape(url2.getFile(), null)); }
+	catch (ParseException pe)
+	    { return url1.getFile().equals(url2.getFile());}
+    }
+
+
+    /**
+     * Return the default port used by a given protocol.
+     *
+     * @param protocol the protocol
+     * @return the port number, or 0 if unknown
+     * @deprecated use URI.defaultPort() instead
+     * @see HTTPClient.URI#defaultPort(java.lang.String)
+     */
+    public final static int defaultPort(String protocol)
+    {
+	return URI.defaultPort(protocol);
+    }
+
+
+    /**
+     * Parse the http date string. java.util.Date will do this fine, but
+     * is deprecated, so we use SimpleDateFormat instead.
+     *
+     * @param dstr  the date string to parse
+     * @return the Date object
+     */
+    final static Date parseHttpDate(String dstr)
+    {
+	synchronized (http_parse_lock)
+	{
+	    if (parse_1123 == null)
+		setupParsers();
+	}
+
+	try
+	    { return parse_1123.parse(dstr); }
+	catch (java.text.ParseException pe)
+	    { }
+	try
+	    { return parse_850.parse(dstr); }
+	catch (java.text.ParseException pe)
+	    { }
+	try
+	    { return parse_asctime.parse(dstr); }
+	catch (java.text.ParseException pe)
+	    { throw new IllegalArgumentException(pe.toString()); }
+    }
+
+    private static final void setupParsers()
+    {
+	parse_1123 =
+	    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+	parse_850 =
+	    new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss 'GMT'", Locale.US);
+	parse_asctime =
+	    new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", Locale.US);
+
+	parse_1123.setTimeZone(new SimpleTimeZone(0, "GMT"));
+	parse_850.setTimeZone(new SimpleTimeZone(0, "GMT"));
+	parse_asctime.setTimeZone(new SimpleTimeZone(0, "GMT"));
+
+	parse_1123.setLenient(true);
+	parse_850.setLenient(true);
+	parse_asctime.setLenient(true);
+    }
+
+    /**
+     * This returns a string containing the date and time in <var>date</var>
+     * formatted according to a subset of RFC-1123. The format is defined in
+     * the HTTP/1.0 spec (RFC-1945), section 3.3, and the HTTP/1.1 spec
+     * (RFC-2616), section 3.3.1. Note that Date.toGMTString() is close, but
+     * is missing the weekday and supresses the leading zero if the day is
+     * less than the 10th. Instead we use the SimpleDateFormat class.
+     *
+     * <P>Some versions of JDK 1.1.x are bugged in that their GMT uses
+     * daylight savings time... Therefore we use our own timezone
+     * definitions.
+     *
+     * @param date the date and time to be converted
+     * @return a string containg the date and time as used in http
+     */
+    public static final String httpDate(Date date)
+    {
+	synchronized (http_format_lock)
+	{
+	    if (http_format == null)
+		setupFormatter();
+	}
+
+	return http_format.format(date);
+    }
+
+    private static final void setupFormatter()
+    {
+	http_format =
+	    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+	http_format.setTimeZone(new SimpleTimeZone(0, "GMT"));
+    }
+
+
+    /**
+     * Escape unsafe characters in a path.
+     *
+     * @param path the original path
+     * @return the path with all unsafe characters escaped
+     */
+    final static String escapeUnsafeChars(String path)
+    {
+	int len = path.length();
+	char[] buf = new char[3*len];
+
+	int dst = 0;
+	for (int src=0; src<len; src++)
+	{
+	    char ch = path.charAt(src);
+	    if (ch >= 128  ||  UnsafeChar.get(ch))
+	    {
+		buf[dst++] = '%';
+		buf[dst++] = hex_map[(ch & 0xf0) >>> 4];
+		buf[dst++] = hex_map[ch & 0x0f];
+	    }
+	    else
+		buf[dst++] = ch;
+	}
+
+	if (dst > len)
+	    return new String(buf, 0, dst);
+	else
+	    return path;
+    }
+
+    static final char[] hex_map =
+	    {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+
+
+    /**
+     * Extract the path from an http resource.
+     *
+     * <P>The "resource" part of an HTTP URI can contain a number of parts,
+     * some of which are not always of interest. These methods here will
+     * extract the various parts, assuming the following syntanx (taken from
+     * RFC-2616):
+     *
+     * <PRE>
+     * resource = [ "/" ] [ path ] [ ";" params ] [ "?" query ] [ "#" fragment ]
+     * </PRE>
+     *
+     * @param the resource to split
+     * @return the path, including any leading "/"
+     * @see #getParams
+     * @see #getQuery
+     * @see #getFragment
+     */
+    public final static String getPath(String resource)
+    {
+	int p, end = resource.length();
+	if ((p = resource.indexOf('#')) != -1)			// find fragment
+	    end = p;
+	if ((p = resource.indexOf('?')) != -1  &&  p < end)	// find query
+	    end = p;
+	if ((p = resource.indexOf(';')) != -1  &&  p < end)	// find params
+	    end = p;
+	return resource.substring(0, end);
+    }
+
+
+    /**
+     * Extract the params part from an http resource.
+     *
+     * @param the resource to split
+     * @return the params, or null if there are none
+     * @see #getPath
+     */
+    public final static String getParams(String resource)
+    {
+	int beg, f, q;
+	if ((beg = resource.indexOf(';')) == -1)		// find params
+	    return null;
+	if ((f = resource.indexOf('#')) != -1  &&  f < beg)	// find fragment
+	    return null;
+	if ((q = resource.indexOf('?')) != -1  &&  q < beg)	// find query
+	    return null;
+	if (q == -1  &&  f == -1)
+	    return resource.substring(beg+1);
+	if (f == -1  ||  (q != -1  &&  q < f))
+	    return resource.substring(beg+1, q);
+	else
+	    return resource.substring(beg+1, f);
+    }
+
+
+    /**
+     * Extract the query string from an http resource.
+     *
+     * @param the resource to split
+     * @return the query, or null if there was none
+     * @see #getPath
+     */
+    public final static String getQuery(String resource)
+    {
+	int beg, f;
+	if ((beg = resource.indexOf('?')) == -1)		// find query
+	    return null;
+	if ((f = resource.indexOf('#')) != -1  &&  f < beg)	// find fragment
+	    return null;				// '?' is in fragment
+	if (f == -1)
+	    return resource.substring(beg+1);		// no fragment
+	else
+	    return resource.substring(beg+1, f);	// strip fragment
+    }
+
+
+    /**
+     * Extract the fragment part from an http resource.
+     *
+     * @param the resource to split
+     * @return the fragment, or null if there was none
+     * @see #getPath
+     */
+    public final static String getFragment(String resource)
+    {
+	int beg;
+	if ((beg = resource.indexOf('#')) == -1)	// find fragment
+	    return null;
+	else
+	    return resource.substring(beg+1);
+    }
+
+
+    /**
+     * Match <var>pattern</var> against <var>name</var>, where
+     * <var>pattern</var> may contain wildcards ('*').
+     *
+     * @param pattern the pattern to match; may contain '*' which match
+     *                any number (0 or more) of any character (think file
+     *                globbing)
+     * @param name    the name to match against the pattern
+     * @return true if the name matches the pattern; false otherwise
+     */
+    public static final boolean wildcardMatch(String pattern, String name)
+    {
+	return
+	    wildcardMatch(pattern, name, 0, 0, pattern.length(), name.length());
+    }
+
+    private static final boolean wildcardMatch(String pattern, String name,
+					       int ppos, int npos, int plen,
+					       int nlen)
+    {
+	// find wildcard
+	int star = pattern.indexOf('*', ppos);
+	if (star < 0)
+	{
+	    return ((plen-ppos) == (nlen-npos)  &&
+		    pattern.regionMatches(ppos, name, npos, plen-ppos));
+	}
+
+	// match prefix
+	if (!pattern.regionMatches(ppos, name, npos, star-ppos))
+	    return false;
+
+	// match suffix
+	if (star == plen-1)
+	    return true;
+	while(!wildcardMatch(pattern, name, star+1, npos, plen, nlen)  &&
+	      npos < nlen)
+	    npos++;
+	return (npos < nlen);
+    }
+}
diff --git a/HTTPClient/package.html b/HTTPClient/package.html
new file mode 100644
index 0000000..64d33f1
--- /dev/null
+++ b/HTTPClient/package.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<title>ignored</title>
+<!--
+
+  This file is part of the HTTPClient package
+  Copyright (C) 1996-2001 Ronald Tschal�r
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser 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
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free
+  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+  MA 02111-1307, USA
+
+  For questions, suggestions, bug-reports, enhancement-requests etc.
+  I may be contacted at:
+
+  ronald at innovation.ch
+
+  The HTTPClient's home page is located at:
+
+  http://www.innovation.ch/java/HTTPClient/ 
+
+-->
+</head>
+<body bgcolor="white">
+
+<P>This package provides a complete http client library. For details see
+the accompanying documentation and the {@link HTTPClient.HTTPConnection
+HTTPConnection} class.
+
+</body>
+</html>
+
diff --git a/Jama/CholeskyDecomposition.java b/Jama/CholeskyDecomposition.java
new file mode 100644
index 0000000..1877088
--- /dev/null
+++ b/Jama/CholeskyDecomposition.java
@@ -0,0 +1,198 @@
+package Jama;
+
+   /** Cholesky Decomposition.
+   <P>
+   For a symmetric, positive definite matrix A, the Cholesky decomposition
+   is an lower triangular matrix L so that A = L*L'.
+   <P>
+   If the matrix is not symmetric or positive definite, the constructor
+   returns a partial decomposition and sets an internal flag that may
+   be queried by the isSPD() method.
+   */
+
+public class CholeskyDecomposition implements java.io.Serializable {
+
+/* ------------------------
+   Class variables
+ * ------------------------ */
+
+   /** Array for internal storage of decomposition.
+   @serial internal array storage.
+   */
+   private double[][] L;
+
+   /** Row and column dimension (square matrix).
+   @serial matrix dimension.
+   */
+   private int n;
+
+   /** Symmetric and positive definite flag.
+   @serial is symmetric and positive definite flag.
+   */
+   private boolean isspd;
+
+/* ------------------------
+   Constructor
+ * ------------------------ */
+
+   /** Cholesky algorithm for symmetric and positive definite matrix.
+   @param  A   Square, symmetric matrix.
+   @return     Structure to access L and isspd flag.
+   */
+
+   public CholeskyDecomposition (Matrix Arg) {
+      // Initialize.
+      double[][] A = Arg.getArray();
+      n = Arg.getRowDimension();
+      L = new double[n][n];
+      isspd = (Arg.getColumnDimension() == n);
+      // Main loop.
+      for (int j = 0; j < n; j++) {
+         double[] Lrowj = L[j];
+         double d = 0.0;
+         for (int k = 0; k < j; k++) {
+            double[] Lrowk = L[k];
+            double s = 0.0;
+            for (int i = 0; i < k; i++) {
+               s += Lrowk[i]*Lrowj[i];
+            }
+            Lrowj[k] = s = (A[j][k] - s)/L[k][k];
+            d = d + s*s;
+            isspd = isspd & (A[k][j] == A[j][k]); 
+         }
+         d = A[j][j] - d;
+         isspd = isspd & (d > 0.0);
+         L[j][j] = Math.sqrt(Math.max(d,0.0));
+         for (int k = j+1; k < n; k++) {
+            L[j][k] = 0.0;
+         }
+      }
+   }
+
+/* ------------------------
+   Temporary, experimental code.
+ * ------------------------ *\
+
+   \** Right Triangular Cholesky Decomposition.
+   <P>
+   For a symmetric, positive definite matrix A, the Right Cholesky
+   decomposition is an upper triangular matrix R so that A = R'*R.
+   This constructor computes R with the Fortran inspired column oriented
+   algorithm used in LINPACK and MATLAB.  In Java, we suspect a row oriented,
+   lower triangular decomposition is faster.  We have temporarily included
+   this constructor here until timing experiments confirm this suspicion.
+   *\
+
+   \** Array for internal storage of right triangular decomposition. **\
+   private transient double[][] R;
+
+   \** Cholesky algorithm for symmetric and positive definite matrix.
+   @param  A           Square, symmetric matrix.
+   @param  rightflag   Actual value ignored.
+   @return             Structure to access R and isspd flag.
+   *\
+
+   public CholeskyDecomposition (Matrix Arg, int rightflag) {
+      // Initialize.
+      double[][] A = Arg.getArray();
+      n = Arg.getColumnDimension();
+      R = new double[n][n];
+      isspd = (Arg.getColumnDimension() == n);
+      // Main loop.
+      for (int j = 0; j < n; j++) {
+         double d = 0.0;
+         for (int k = 0; k < j; k++) {
+            double s = A[k][j];
+            for (int i = 0; i < k; i++) {
+               s = s - R[i][k]*R[i][j];
+            }
+            R[k][j] = s = s/R[k][k];
+            d = d + s*s;
+            isspd = isspd & (A[k][j] == A[j][k]); 
+         }
+         d = A[j][j] - d;
+         isspd = isspd & (d > 0.0);
+         R[j][j] = Math.sqrt(Math.max(d,0.0));
+         for (int k = j+1; k < n; k++) {
+            R[k][j] = 0.0;
+         }
+      }
+   }
+
+   \** Return upper triangular factor.
+   @return     R
+   *\
+
+   public Matrix getR () {
+      return new Matrix(R,n,n);
+   }
+
+\* ------------------------
+   End of temporary code.
+ * ------------------------ */
+
+/* ------------------------
+   Public Methods
+ * ------------------------ */
+
+   /** Is the matrix symmetric and positive definite?
+   @return     true if A is symmetric and positive definite.
+   */
+
+   public boolean isSPD () {
+      return isspd;
+   }
+
+   /** Return triangular factor.
+   @return     L
+   */
+
+   public Matrix getL () {
+      return new Matrix(L,n,n);
+   }
+
+   /** Solve A*X = B
+   @param  B   A Matrix with as many rows as A and any number of columns.
+   @return     X so that L*L'*X = B
+   @exception  IllegalArgumentException  Matrix row dimensions must agree.
+   @exception  RuntimeException  Matrix is not symmetric positive definite.
+   */
+
+   public Matrix solve (Matrix B) {
+      if (B.getRowDimension() != n) {
+         throw new IllegalArgumentException("Matrix row dimensions must agree.");
+      }
+      if (!isspd) {
+         throw new RuntimeException("Matrix is not symmetric positive definite.");
+      }
+
+      // Copy right hand side.
+      double[][] X = B.getArrayCopy();
+      int nx = B.getColumnDimension();
+
+      // Solve L*Y = B;
+      for (int k = 0; k < n; k++) {
+         for (int i = k+1; i < n; i++) {
+            for (int j = 0; j < nx; j++) {
+               X[i][j] -= X[k][j]*L[i][k];
+            }
+         }
+         for (int j = 0; j < nx; j++) {
+            X[k][j] /= L[k][k];
+         }
+      }
+
+      // Solve L'*X = Y;
+      for (int k = n-1; k >= 0; k--) {
+         for (int j = 0; j < nx; j++) {
+            X[k][j] /= L[k][k];
+         }
+         for (int i = 0; i < k; i++) {
+            for (int j = 0; j < nx; j++) {
+               X[i][j] -= X[k][j]*L[k][i];
+            }
+         }
+      }
+      return new Matrix(X,n,nx);
+   }
+}
diff --git a/Jama/EigenvalueDecomposition.java b/Jama/EigenvalueDecomposition.java
new file mode 100644
index 0000000..6c30d38
--- /dev/null
+++ b/Jama/EigenvalueDecomposition.java
@@ -0,0 +1,955 @@
+package Jama;
+import Jama.util.*;
+
+/** Eigenvalues and eigenvectors of a real matrix. 
+<P>
+    If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is
+    diagonal and the eigenvector matrix V is orthogonal.
+    I.e. A = V.times(D.times(V.transpose())) and 
+    V.times(V.transpose()) equals the identity matrix.
+<P>
+    If A is not symmetric, then the eigenvalue matrix D is block diagonal
+    with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues,
+    lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda].  The
+    columns of V represent the eigenvectors in the sense that A*V = V*D,
+    i.e. A.times(V) equals V.times(D).  The matrix V may be badly
+    conditioned, or even singular, so the validity of the equation
+    A = V*D*inverse(V) depends upon V.cond().
+**/
+
+public class EigenvalueDecomposition implements java.io.Serializable {
+
+/* ------------------------
+   Class variables
+ * ------------------------ */
+
+   /** Row and column dimension (square matrix).
+   @serial matrix dimension.
+   */
+   private int n;
+
+   /** Symmetry flag.
+   @serial internal symmetry flag.
+   */
+   private boolean issymmetric;
+
+   /** Arrays for internal storage of eigenvalues.
+   @serial internal storage of eigenvalues.
+   */
+   private double[] d, e;
+
+   /** Array for internal storage of eigenvectors.
+   @serial internal storage of eigenvectors.
+   */
+   private double[][] V;
+
+   /** Array for internal storage of nonsymmetric Hessenberg form.
+   @serial internal storage of nonsymmetric Hessenberg form.
+   */
+   private double[][] H;
+
+   /** Working storage for nonsymmetric algorithm.
+   @serial working storage for nonsymmetric algorithm.
+   */
+   private double[] ort;
+
+/* ------------------------
+   Private Methods
+ * ------------------------ */
+
+   // Symmetric Householder reduction to tridiagonal form.
+
+   private void tred2 () {
+
+   //  This is derived from the Algol procedures tred2 by
+   //  Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
+   //  Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+   //  Fortran subroutine in EISPACK.
+
+      for (int j = 0; j < n; j++) {
+         d[j] = V[n-1][j];
+      }
+
+      // Householder reduction to tridiagonal form.
+   
+      for (int i = n-1; i > 0; i--) {
+   
+         // Scale to avoid under/overflow.
+   
+         double scale = 0.0;
+         double h = 0.0;
+         for (int k = 0; k < i; k++) {
+            scale = scale + Math.abs(d[k]);
+         }
+         if (scale == 0.0) {
+            e[i] = d[i-1];
+            for (int j = 0; j < i; j++) {
+               d[j] = V[i-1][j];
+               V[i][j] = 0.0;
+               V[j][i] = 0.0;
+            }
+         } else {
+   
+            // Generate Householder vector.
+   
+            for (int k = 0; k < i; k++) {
+               d[k] /= scale;
+               h += d[k] * d[k];
+            }
+            double f = d[i-1];
+            double g = Math.sqrt(h);
+            if (f > 0) {
+               g = -g;
+            }
+            e[i] = scale * g;
+            h = h - f * g;
+            d[i-1] = f - g;
+            for (int j = 0; j < i; j++) {
+               e[j] = 0.0;
+            }
+   
+            // Apply similarity transformation to remaining columns.
+   
+            for (int j = 0; j < i; j++) {
+               f = d[j];
+               V[j][i] = f;
+               g = e[j] + V[j][j] * f;
+               for (int k = j+1; k <= i-1; k++) {
+                  g += V[k][j] * d[k];
+                  e[k] += V[k][j] * f;
+               }
+               e[j] = g;
+            }
+            f = 0.0;
+            for (int j = 0; j < i; j++) {
+               e[j] /= h;
+               f += e[j] * d[j];
+            }
+            double hh = f / (h + h);
+            for (int j = 0; j < i; j++) {
+               e[j] -= hh * d[j];
+            }
+            for (int j = 0; j < i; j++) {
+               f = d[j];
+               g = e[j];
+               for (int k = j; k <= i-1; k++) {
+                  V[k][j] -= (f * e[k] + g * d[k]);
+               }
+               d[j] = V[i-1][j];
+               V[i][j] = 0.0;
+            }
+         }
+         d[i] = h;
+      }
+   
+      // Accumulate transformations.
+   
+      for (int i = 0; i < n-1; i++) {
+         V[n-1][i] = V[i][i];
+         V[i][i] = 1.0;
+         double h = d[i+1];
+         if (h != 0.0) {
+            for (int k = 0; k <= i; k++) {
+               d[k] = V[k][i+1] / h;
+            }
+            for (int j = 0; j <= i; j++) {
+               double g = 0.0;
+               for (int k = 0; k <= i; k++) {
+                  g += V[k][i+1] * V[k][j];
+               }
+               for (int k = 0; k <= i; k++) {
+                  V[k][j] -= g * d[k];
+               }
+            }
+         }
+         for (int k = 0; k <= i; k++) {
+            V[k][i+1] = 0.0;
+         }
+      }
+      for (int j = 0; j < n; j++) {
+         d[j] = V[n-1][j];
+         V[n-1][j] = 0.0;
+      }
+      V[n-1][n-1] = 1.0;
+      e[0] = 0.0;
+   } 
+
+   // Symmetric tridiagonal QL algorithm.
+   
+   private void tql2 () {
+
+   //  This is derived from the Algol procedures tql2, by
+   //  Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
+   //  Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+   //  Fortran subroutine in EISPACK.
+   
+      for (int i = 1; i < n; i++) {
+         e[i-1] = e[i];
+      }
+      e[n-1] = 0.0;
+   
+      double f = 0.0;
+      double tst1 = 0.0;
+      double eps = Math.pow(2.0,-52.0);
+      for (int l = 0; l < n; l++) {
+
+         // Find small subdiagonal element
+   
+         tst1 = Math.max(tst1,Math.abs(d[l]) + Math.abs(e[l]));
+         int m = l;
+         while (m < n) {
+            if (Math.abs(e[m]) <= eps*tst1) {
+               break;
+            }
+            m++;
+         }
+   
+         // If m == l, d[l] is an eigenvalue,
+         // otherwise, iterate.
+   
+         if (m > l) {
+            int iter = 0;
+            do {
+               iter = iter + 1;  // (Could check iteration count here.)
+   
+               // Compute implicit shift
+   
+               double g = d[l];
+               double p = (d[l+1] - g) / (2.0 * e[l]);
+               double r = Maths.hypot(p,1.0);
+               if (p < 0) {
+                  r = -r;
+               }
+               d[l] = e[l] / (p + r);
+               d[l+1] = e[l] * (p + r);
+               double dl1 = d[l+1];
+               double h = g - d[l];
+               for (int i = l+2; i < n; i++) {
+                  d[i] -= h;
+               }
+               f = f + h;
+   
+               // Implicit QL transformation.
+   
+               p = d[m];
+               double c = 1.0;
+               double c2 = c;
+               double c3 = c;
+               double el1 = e[l+1];
+               double s = 0.0;
+               double s2 = 0.0;
+               for (int i = m-1; i >= l; i--) {
+                  c3 = c2;
+                  c2 = c;
+                  s2 = s;
+                  g = c * e[i];
+                  h = c * p;
+                  r = Maths.hypot(p,e[i]);
+                  e[i+1] = s * r;
+                  s = e[i] / r;
+                  c = p / r;
+                  p = c * d[i] - s * g;
+                  d[i+1] = h + s * (c * g + s * d[i]);
+   
+                  // Accumulate transformation.
+   
+                  for (int k = 0; k < n; k++) {
+                     h = V[k][i+1];
+                     V[k][i+1] = s * V[k][i] + c * h;
+                     V[k][i] = c * V[k][i] - s * h;
+                  }
+               }
+               p = -s * s2 * c3 * el1 * e[l] / dl1;
+               e[l] = s * p;
+               d[l] = c * p;
+   
+               // Check for convergence.
+   
+            } while (Math.abs(e[l]) > eps*tst1);
+         }
+         d[l] = d[l] + f;
+         e[l] = 0.0;
+      }
+     
+      // Sort eigenvalues and corresponding vectors.
+   
+      for (int i = 0; i < n-1; i++) {
+         int k = i;
+         double p = d[i];
+         for (int j = i+1; j < n; j++) {
+            if (d[j] < p) {
+               k = j;
+               p = d[j];
+            }
+         }
+         if (k != i) {
+            d[k] = d[i];
+            d[i] = p;
+            for (int j = 0; j < n; j++) {
+               p = V[j][i];
+               V[j][i] = V[j][k];
+               V[j][k] = p;
+            }
+         }
+      }
+   }
+
+   // Nonsymmetric reduction to Hessenberg form.
+
+   private void orthes () {
+   
+      //  This is derived from the Algol procedures orthes and ortran,
+      //  by Martin and Wilkinson, Handbook for Auto. Comp.,
+      //  Vol.ii-Linear Algebra, and the corresponding
+      //  Fortran subroutines in EISPACK.
+   
+      int low = 0;
+      int high = n-1;
+   
+      for (int m = low+1; m <= high-1; m++) {
+   
+         // Scale column.
+   
+         double scale = 0.0;
+         for (int i = m; i <= high; i++) {
+            scale = scale + Math.abs(H[i][m-1]);
+         }
+         if (scale != 0.0) {
+   
+            // Compute Householder transformation.
+   
+            double h = 0.0;
+            for (int i = high; i >= m; i--) {
+               ort[i] = H[i][m-1]/scale;
+               h += ort[i] * ort[i];
+            }
+            double g = Math.sqrt(h);
+            if (ort[m] > 0) {
+               g = -g;
+            }
+            h = h - ort[m] * g;
+            ort[m] = ort[m] - g;
+   
+            // Apply Householder similarity transformation
+            // H = (I-u*u'/h)*H*(I-u*u')/h)
+   
+            for (int j = m; j < n; j++) {
+               double f = 0.0;
+               for (int i = high; i >= m; i--) {
+                  f += ort[i]*H[i][j];
+               }
+               f = f/h;
+               for (int i = m; i <= high; i++) {
+                  H[i][j] -= f*ort[i];
+               }
+           }
+   
+           for (int i = 0; i <= high; i++) {
+               double f = 0.0;
+               for (int j = high; j >= m; j--) {
+                  f += ort[j]*H[i][j];
+               }
+               f = f/h;
+               for (int j = m; j <= high; j++) {
+                  H[i][j] -= f*ort[j];
+               }
+            }
+            ort[m] = scale*ort[m];
+            H[m][m-1] = scale*g;
+         }
+      }
+   
+      // Accumulate transformations (Algol's ortran).
+
+      for (int i = 0; i < n; i++) {
+         for (int j = 0; j < n; j++) {
+            V[i][j] = (i == j ? 1.0 : 0.0);
+         }
+      }
+
+      for (int m = high-1; m >= low+1; m--) {
+         if (H[m][m-1] != 0.0) {
+            for (int i = m+1; i <= high; i++) {
+               ort[i] = H[i][m-1];
+            }
+            for (int j = m; j <= high; j++) {
+               double g = 0.0;
+               for (int i = m; i <= high; i++) {
+                  g += ort[i] * V[i][j];
+               }
+               // Double division avoids possible underflow
+               g = (g / ort[m]) / H[m][m-1];
+               for (int i = m; i <= high; i++) {
+                  V[i][j] += g * ort[i];
+               }
+            }
+         }
+      }
+   }
+
+
+   // Complex scalar division.
+
+   private transient double cdivr, cdivi;
+   private void cdiv(double xr, double xi, double yr, double yi) {
+      double r,d;
+      if (Math.abs(yr) > Math.abs(yi)) {
+         r = yi/yr;
+         d = yr + r*yi;
+         cdivr = (xr + r*xi)/d;
+         cdivi = (xi - r*xr)/d;
+      } else {
+         r = yr/yi;
+         d = yi + r*yr;
+         cdivr = (r*xr + xi)/d;
+         cdivi = (r*xi - xr)/d;
+      }
+   }
+
+
+   // Nonsymmetric reduction from Hessenberg to real Schur form.
+
+   private void hqr2 () {
+   
+      //  This is derived from the Algol procedure hqr2,
+      //  by Martin and Wilkinson, Handbook for Auto. Comp.,
+      //  Vol.ii-Linear Algebra, and the corresponding
+      //  Fortran subroutine in EISPACK.
+   
+      // Initialize
+   
+      int nn = this.n;
+      int n = nn-1;
+      int low = 0;
+      int high = nn-1;
+      double eps = Math.pow(2.0,-52.0);
+      double exshift = 0.0;
+      double p=0,q=0,r=0,s=0,z=0,t,w,x,y;
+   
+      // Store roots isolated by balanc and compute matrix norm
+   
+      double norm = 0.0;
+      for (int i = 0; i < nn; i++) {
+         if (i < low | i > high) {
+            d[i] = H[i][i];
+            e[i] = 0.0;
+         }
+         for (int j = Math.max(i-1,0); j < nn; j++) {
+            norm = norm + Math.abs(H[i][j]);
+         }
+      }
+   
+      // Outer loop over eigenvalue index
+   
+      int iter = 0;
+      while (n >= low) {
+   
+         // Look for single small sub-diagonal element
+   
+         int l = n;
+         while (l > low) {
+            s = Math.abs(H[l-1][l-1]) + Math.abs(H[l][l]);
+            if (s == 0.0) {
+               s = norm;
+            }
+            if (Math.abs(H[l][l-1]) < eps * s) {
+               break;
+            }
+            l--;
+         }
+       
+         // Check for convergence
+         // One root found
+   
+         if (l == n) {
+            H[n][n] = H[n][n] + exshift;
+            d[n] = H[n][n];
+            e[n] = 0.0;
+            n--;
+            iter = 0;
+   
+         // Two roots found
+   
+         } else if (l == n-1) {
+            w = H[n][n-1] * H[n-1][n];
+            p = (H[n-1][n-1] - H[n][n]) / 2.0;
+            q = p * p + w;
+            z = Math.sqrt(Math.abs(q));
+            H[n][n] = H[n][n] + exshift;
+            H[n-1][n-1] = H[n-1][n-1] + exshift;
+            x = H[n][n];
+   
+            // Real pair
+   
+            if (q >= 0) {
+               if (p >= 0) {
+                  z = p + z;
+               } else {
+                  z = p - z;
+               }
+               d[n-1] = x + z;
+               d[n] = d[n-1];
+               if (z != 0.0) {
+                  d[n] = x - w / z;
+               }
+               e[n-1] = 0.0;
+               e[n] = 0.0;
+               x = H[n][n-1];
+               s = Math.abs(x) + Math.abs(z);
+               p = x / s;
+               q = z / s;
+               r = Math.sqrt(p * p+q * q);
+               p = p / r;
+               q = q / r;
+   
+               // Row modification
+   
+               for (int j = n-1; j < nn; j++) {
+                  z = H[n-1][j];
+                  H[n-1][j] = q * z + p * H[n][j];
+                  H[n][j] = q * H[n][j] - p * z;
+               }
+   
+               // Column modification
+   
+               for (int i = 0; i <= n; i++) {
+                  z = H[i][n-1];
+                  H[i][n-1] = q * z + p * H[i][n];
+                  H[i][n] = q * H[i][n] - p * z;
+               }
+   
+               // Accumulate transformations
+   
+               for (int i = low; i <= high; i++) {
+                  z = V[i][n-1];
+                  V[i][n-1] = q * z + p * V[i][n];
+                  V[i][n] = q * V[i][n] - p * z;
+               }
+   
+            // Complex pair
+   
+            } else {
+               d[n-1] = x + p;
+               d[n] = x + p;
+               e[n-1] = z;
+               e[n] = -z;
+            }
+            n = n - 2;
+            iter = 0;
+   
+         // No convergence yet
+   
+         } else {
+   
+            // Form shift
+   
+            x = H[n][n];
+            y = 0.0;
+            w = 0.0;
+            if (l < n) {
+               y = H[n-1][n-1];
+               w = H[n][n-1] * H[n-1][n];
+            }
+   
+            // Wilkinson's original ad hoc shift
+   
+            if (iter == 10) {
+               exshift += x;
+               for (int i = low; i <= n; i++) {
+                  H[i][i] -= x;
+               }
+               s = Math.abs(H[n][n-1]) + Math.abs(H[n-1][n-2]);
+               x = y = 0.75 * s;
+               w = -0.4375 * s * s;
+            }
+
+            // MATLAB's new ad hoc shift
+
+            if (iter == 30) {
+                s = (y - x) / 2.0;
+                s = s * s + w;
+                if (s > 0) {
+                    s = Math.sqrt(s);
+                    if (y < x) {
+                       s = -s;
+                    }
+                    s = x - w / ((y - x) / 2.0 + s);
+                    for (int i = low; i <= n; i++) {
+                       H[i][i] -= s;
+                    }
+                    exshift += s;
+                    x = y = w = 0.964;
+                }
+            }
+   
+            iter = iter + 1;   // (Could check iteration count here.)
+   
+            // Look for two consecutive small sub-diagonal elements
+   
+            int m = n-2;
+            while (m >= l) {
+               z = H[m][m];
+               r = x - z;
+               s = y - z;
+               p = (r * s - w) / H[m+1][m] + H[m][m+1];
+               q = H[m+1][m+1] - z - r - s;
+               r = H[m+2][m+1];
+               s = Math.abs(p) + Math.abs(q) + Math.abs(r);
+               p = p / s;
+               q = q / s;
+               r = r / s;
+               if (m == l) {
+                  break;
+               }
+               if (Math.abs(H[m][m-1]) * (Math.abs(q) + Math.abs(r)) <
+                  eps * (Math.abs(p) * (Math.abs(H[m-1][m-1]) + Math.abs(z) +
+                  Math.abs(H[m+1][m+1])))) {
+                     break;
+               }
+               m--;
+            }
+   
+            for (int i = m+2; i <= n; i++) {
+               H[i][i-2] = 0.0;
+               if (i > m+2) {
+                  H[i][i-3] = 0.0;
+               }
+            }
+   
+            // Double QR step involving rows l:n and columns m:n
+   
+            for (int k = m; k <= n-1; k++) {
+               boolean notlast = (k != n-1);
+               if (k != m) {
+                  p = H[k][k-1];
+                  q = H[k+1][k-1];
+                  r = (notlast ? H[k+2][k-1] : 0.0);
+                  x = Math.abs(p) + Math.abs(q) + Math.abs(r);
+                  if (x != 0.0) {
+                     p = p / x;
+                     q = q / x;
+                     r = r / x;
+                  }
+               }
+               if (x == 0.0) {
+                  break;
+               }
+               s = Math.sqrt(p * p + q * q + r * r);
+               if (p < 0) {
+                  s = -s;
+               }
+               if (s != 0) {
+                  if (k != m) {
+                     H[k][k-1] = -s * x;
+                  } else if (l != m) {
+                     H[k][k-1] = -H[k][k-1];
+                  }
+                  p = p + s;
+                  x = p / s;
+                  y = q / s;
+                  z = r / s;
+                  q = q / p;
+                  r = r / p;
+   
+                  // Row modification
+   
+                  for (int j = k; j < nn; j++) {
+                     p = H[k][j] + q * H[k+1][j];
+                     if (notlast) {
+                        p = p + r * H[k+2][j];
+                        H[k+2][j] = H[k+2][j] - p * z;
+                     }
+                     H[k][j] = H[k][j] - p * x;
+                     H[k+1][j] = H[k+1][j] - p * y;
+                  }
+   
+                  // Column modification
+   
+                  for (int i = 0; i <= Math.min(n,k+3); i++) {
+                     p = x * H[i][k] + y * H[i][k+1];
+                     if (notlast) {
+                        p = p + z * H[i][k+2];
+                        H[i][k+2] = H[i][k+2] - p * r;
+                     }
+                     H[i][k] = H[i][k] - p;
+                     H[i][k+1] = H[i][k+1] - p * q;
+                  }
+   
+                  // Accumulate transformations
+   
+                  for (int i = low; i <= high; i++) {
+                     p = x * V[i][k] + y * V[i][k+1];
+                     if (notlast) {
+                        p = p + z * V[i][k+2];
+                        V[i][k+2] = V[i][k+2] - p * r;
+                     }
+                     V[i][k] = V[i][k] - p;
+                     V[i][k+1] = V[i][k+1] - p * q;
+                  }
+               }  // (s != 0)
+            }  // k loop
+         }  // check convergence
+      }  // while (n >= low)
+      
+      // Backsubstitute to find vectors of upper triangular form
+
+      if (norm == 0.0) {
+         return;
+      }
+   
+      for (n = nn-1; n >= 0; n--) {
+         p = d[n];
+         q = e[n];
+   
+         // Real vector
+   
+         if (q == 0) {
+            int l = n;
+            H[n][n] = 1.0;
+            for (int i = n-1; i >= 0; i--) {
+               w = H[i][i] - p;
+               r = 0.0;
+               for (int j = l; j <= n; j++) {
+                  r = r + H[i][j] * H[j][n];
+               }
+               if (e[i] < 0.0) {
+                  z = w;
+                  s = r;
+               } else {
+                  l = i;
+                  if (e[i] == 0.0) {
+                     if (w != 0.0) {
+                        H[i][n] = -r / w;
+                     } else {
+                        H[i][n] = -r / (eps * norm);
+                     }
+   
+                  // Solve real equations
+   
+                  } else {
+                     x = H[i][i+1];
+                     y = H[i+1][i];
+                     q = (d[i] - p) * (d[i] - p) + e[i] * e[i];
+                     t = (x * s - z * r) / q;
+                     H[i][n] = t;
+                     if (Math.abs(x) > Math.abs(z)) {
+                        H[i+1][n] = (-r - w * t) / x;
+                     } else {
+                        H[i+1][n] = (-s - y * t) / z;
+                     }
+                  }
+   
+                  // Overflow control
+   
+                  t = Math.abs(H[i][n]);
+                  if ((eps * t) * t > 1) {
+                     for (int j = i; j <= n; j++) {
+                        H[j][n] = H[j][n] / t;
+                     }
+                  }
+               }
+            }
+   
+         // Complex vector
+   
+         } else if (q < 0) {
+            int l = n-1;
+
+            // Last vector component imaginary so matrix is triangular
+   
+            if (Math.abs(H[n][n-1]) > Math.abs(H[n-1][n])) {
+               H[n-1][n-1] = q / H[n][n-1];
+               H[n-1][n] = -(H[n][n] - p) / H[n][n-1];
+            } else {
+               cdiv(0.0,-H[n-1][n],H[n-1][n-1]-p,q);
+               H[n-1][n-1] = cdivr;
+               H[n-1][n] = cdivi;
+            }
+            H[n][n-1] = 0.0;
+            H[n][n] = 1.0;
+            for (int i = n-2; i >= 0; i--) {
+               double ra,sa,vr,vi;
+               ra = 0.0;
+               sa = 0.0;
+               for (int j = l; j <= n; j++) {
+                  ra = ra + H[i][j] * H[j][n-1];
+                  sa = sa + H[i][j] * H[j][n];
+               }
+               w = H[i][i] - p;
+   
+               if (e[i] < 0.0) {
+                  z = w;
+                  r = ra;
+                  s = sa;
+               } else {
+                  l = i;
+                  if (e[i] == 0) {
+                     cdiv(-ra,-sa,w,q);
+                     H[i][n-1] = cdivr;
+                     H[i][n] = cdivi;
+                  } else {
+   
+                     // Solve complex equations
+   
+                     x = H[i][i+1];
+                     y = H[i+1][i];
+                     vr = (d[i] - p) * (d[i] - p) + e[i] * e[i] - q * q;
+                     vi = (d[i] - p) * 2.0 * q;
+                     if (vr == 0.0 & vi == 0.0) {
+                        vr = eps * norm * (Math.abs(w) + Math.abs(q) +
+                        Math.abs(x) + Math.abs(y) + Math.abs(z));
+                     }
+                     cdiv(x*r-z*ra+q*sa,x*s-z*sa-q*ra,vr,vi);
+                     H[i][n-1] = cdivr;
+                     H[i][n] = cdivi;
+                     if (Math.abs(x) > (Math.abs(z) + Math.abs(q))) {
+                        H[i+1][n-1] = (-ra - w * H[i][n-1] + q * H[i][n]) / x;
+                        H[i+1][n] = (-sa - w * H[i][n] - q * H[i][n-1]) / x;
+                     } else {
+                        cdiv(-r-y*H[i][n-1],-s-y*H[i][n],z,q);
+                        H[i+1][n-1] = cdivr;
+                        H[i+1][n] = cdivi;
+                     }
+                  }
+   
+                  // Overflow control
+
+                  t = Math.max(Math.abs(H[i][n-1]),Math.abs(H[i][n]));
+                  if ((eps * t) * t > 1) {
+                     for (int j = i; j <= n; j++) {
+                        H[j][n-1] = H[j][n-1] / t;
+                        H[j][n] = H[j][n] / t;
+                     }
+                  }
+               }
+            }
+         }
+      }
+   
+      // Vectors of isolated roots
+   
+      for (int i = 0; i < nn; i++) {
+         if (i < low | i > high) {
+            for (int j = i; j < nn; j++) {
+               V[i][j] = H[i][j];
+            }
+         }
+      }
+   
+      // Back transformation to get eigenvectors of original matrix
+   
+      for (int j = nn-1; j >= low; j--) {
+         for (int i = low; i <= high; i++) {
+            z = 0.0;
+            for (int k = low; k <= Math.min(j,high); k++) {
+               z = z + V[i][k] * H[k][j];
+            }
+            V[i][j] = z;
+         }
+      }
+   }
+
+
+/* ------------------------
+   Constructor
+ * ------------------------ */
+
+   /** Check for symmetry, then construct the eigenvalue decomposition
+   @param A    Square matrix
+   @return     Structure to access D and V.
+   */
+
+   public EigenvalueDecomposition (Matrix Arg) {
+      double[][] A = Arg.getArray();
+      n = Arg.getColumnDimension();
+      V = new double[n][n];
+      d = new double[n];
+      e = new double[n];
+
+      issymmetric = true;
+      for (int j = 0; (j < n) & issymmetric; j++) {
+         for (int i = 0; (i < n) & issymmetric; i++) {
+            issymmetric = (A[i][j] == A[j][i]);
+         }
+      }
+
+      if (issymmetric) {
+         for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+               V[i][j] = A[i][j];
+            }
+         }
+   
+         // Tridiagonalize.
+         tred2();
+   
+         // Diagonalize.
+         tql2();
+
+      } else {
+         H = new double[n][n];
+         ort = new double[n];
+         
+         for (int j = 0; j < n; j++) {
+            for (int i = 0; i < n; i++) {
+               H[i][j] = A[i][j];
+            }
+         }
+   
+         // Reduce to Hessenberg form.
+         orthes();
+   
+         // Reduce Hessenberg to real Schur form.
+         hqr2();
+      }
+   }
+
+/* ------------------------
+   Public Methods
+ * ------------------------ */
+
+   /** Return the eigenvector matrix
+   @return     V
+   */
+
+   public Matrix getV () {
+      return new Matrix(V,n,n);
+   }
+
+   /** Return the real parts of the eigenvalues
+   @return     real(diag(D))
+   */
+
+   public double[] getRealEigenvalues () {
+      return d;
+   }
+
+   /** Return the imaginary parts of the eigenvalues
+   @return     imag(diag(D))
+   */
+
+   public double[] getImagEigenvalues () {
+      return e;
+   }
+
+   /** Return the block diagonal eigenvalue matrix
+   @return     D
+   */
+
+   public Matrix getD () {
+      Matrix X = new Matrix(n,n);
+      double[][] D = X.getArray();
+      for (int i = 0; i < n; i++) {
+         for (int j = 0; j < n; j++) {
+            D[i][j] = 0.0;
+         }
+         D[i][i] = d[i];
+         if (e[i] > 0) {
+            D[i][i+1] = e[i];
+         } else if (e[i] < 0) {
+            D[i][i-1] = e[i];
+         }
+      }
+      return X;
+   }
+}
diff --git a/Jama/LUDecomposition.java b/Jama/LUDecomposition.java
new file mode 100644
index 0000000..a670a92
--- /dev/null
+++ b/Jama/LUDecomposition.java
@@ -0,0 +1,311 @@
+package Jama;
+
+   /** LU Decomposition.
+   <P>
+   For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n
+   unit lower triangular matrix L, an n-by-n upper triangular matrix U,
+   and a permutation vector piv of length m so that A(piv,:) = L*U.
+   If m < n, then L is m-by-m and U is m-by-n.
+   <P>
+   The LU decompostion with pivoting always exists, even if the matrix is
+   singular, so the constructor will never fail.  The primary use of the
+   LU decomposition is in the solution of square systems of simultaneous
+   linear equations.  This will fail if isNonsingular() returns false.
+   */
+
+public class LUDecomposition implements java.io.Serializable {
+
+/* ------------------------
+   Class variables
+ * ------------------------ */
+
+   /** Array for internal storage of decomposition.
+   @serial internal array storage.
+   */
+   private double[][] LU;
+
+   /** Row and column dimensions, and pivot sign.
+   @serial column dimension.
+   @serial row dimension.
+   @serial pivot sign.
+   */
+   private int m, n, pivsign; 
+
+   /** Internal storage of pivot vector.
+   @serial pivot vector.
+   */
+   private int[] piv;
+
+/* ------------------------
+   Constructor
+ * ------------------------ */
+
+   /** LU Decomposition
+   @param  A   Rectangular matrix
+   @return     Structure to access L, U and piv.
+   */
+
+   public LUDecomposition (Matrix A) {
+
+   // Use a "left-looking", dot-product, Crout/Doolittle algorithm.
+
+      LU = A.getArrayCopy();
+      m = A.getRowDimension();
+      n = A.getColumnDimension();
+      piv = new int[m];
+      for (int i = 0; i < m; i++) {
+         piv[i] = i;
+      }
+      pivsign = 1;
+      double[] LUrowi;
+      double[] LUcolj = new double[m];
+
+      // Outer loop.
+
+      for (int j = 0; j < n; j++) {
+
+         // Make a copy of the j-th column to localize references.
+
+         for (int i = 0; i < m; i++) {
+            LUcolj[i] = LU[i][j];
+         }
+
+         // Apply previous transformations.
+
+         for (int i = 0; i < m; i++) {
+            LUrowi = LU[i];
+
+            // Most of the time is spent in the following dot product.
+
+            int kmax = Math.min(i,j);
+            double s = 0.0;
+            for (int k = 0; k < kmax; k++) {
+               s += LUrowi[k]*LUcolj[k];
+            }
+
+            LUrowi[j] = LUcolj[i] -= s;
+         }
+   
+         // Find pivot and exchange if necessary.
+
+         int p = j;
+         for (int i = j+1; i < m; i++) {
+            if (Math.abs(LUcolj[i]) > Math.abs(LUcolj[p])) {
+               p = i;
+            }
+         }
+         if (p != j) {
+            for (int k = 0; k < n; k++) {
+               double t = LU[p][k]; LU[p][k] = LU[j][k]; LU[j][k] = t;
+            }
+            int k = piv[p]; piv[p] = piv[j]; piv[j] = k;
+            pivsign = -pivsign;
+         }
+
+         // Compute multipliers.
+         
+         if (j < m & LU[j][j] != 0.0) {
+            for (int i = j+1; i < m; i++) {
+               LU[i][j] /= LU[j][j];
+            }
+         }
+      }
+   }
+
+/* ------------------------
+   Temporary, experimental code.
+   ------------------------ *\
+
+   \** LU Decomposition, computed by Gaussian elimination.
+   <P>
+   This constructor computes L and U with the "daxpy"-based elimination
+   algorithm used in LINPACK and MATLAB.  In Java, we suspect the dot-product,
+   Crout algorithm will be faster.  We have temporarily included this
+   constructor until timing experiments confirm this suspicion.
+   <P>
+   @param  A             Rectangular matrix
+   @param  linpackflag   Use Gaussian elimination.  Actual value ignored.
+   @return               Structure to access L, U and piv.
+   *\
+
+   public LUDecomposition (Matrix A, int linpackflag) {
+      // Initialize.
+      LU = A.getArrayCopy();
+      m = A.getRowDimension();
+      n = A.getColumnDimension();
+      piv = new int[m];
+      for (int i = 0; i < m; i++) {
+         piv[i] = i;
+      }
+      pivsign = 1;
+      // Main loop.
+      for (int k = 0; k < n; k++) {
+         // Find pivot.
+         int p = k;
+         for (int i = k+1; i < m; i++) {
+            if (Math.abs(LU[i][k]) > Math.abs(LU[p][k])) {
+               p = i;
+            }
+         }
+         // Exchange if necessary.
+         if (p != k) {
+            for (int j = 0; j < n; j++) {
+               double t = LU[p][j]; LU[p][j] = LU[k][j]; LU[k][j] = t;
+            }
+            int t = piv[p]; piv[p] = piv[k]; piv[k] = t;
+            pivsign = -pivsign;
+         }
+         // Compute multipliers and eliminate k-th column.
+         if (LU[k][k] != 0.0) {
+            for (int i = k+1; i < m; i++) {
+               LU[i][k] /= LU[k][k];
+               for (int j = k+1; j < n; j++) {
+                  LU[i][j] -= LU[i][k]*LU[k][j];
+               }
+            }
+         }
+      }
+   }
+
+\* ------------------------
+   End of temporary code.
+ * ------------------------ */
+
+/* ------------------------
+   Public Methods
+ * ------------------------ */
+
+   /** Is the matrix nonsingular?
+   @return     true if U, and hence A, is nonsingular.
+   */
+
+   public boolean isNonsingular () {
+      for (int j = 0; j < n; j++) {
+         if (LU[j][j] == 0)
+            return false;
+      }
+      return true;
+   }
+
+   /** Return lower triangular factor
+   @return     L
+   */
+
+   public Matrix getL () {
+      Matrix X = new Matrix(m,n);
+      double[][] L = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            if (i > j) {
+               L[i][j] = LU[i][j];
+            } else if (i == j) {
+               L[i][j] = 1.0;
+            } else {
+               L[i][j] = 0.0;
+            }
+         }
+      }
+      return X;
+   }
+
+   /** Return upper triangular factor
+   @return     U
+   */
+
+   public Matrix getU () {
+      Matrix X = new Matrix(n,n);
+      double[][] U = X.getArray();
+      for (int i = 0; i < n; i++) {
+         for (int j = 0; j < n; j++) {
+            if (i <= j) {
+               U[i][j] = LU[i][j];
+            } else {
+               U[i][j] = 0.0;
+            }
+         }
+      }
+      return X;
+   }
+
+   /** Return pivot permutation vector
+   @return     piv
+   */
+
+   public int[] getPivot () {
+      int[] p = new int[m];
+      for (int i = 0; i < m; i++) {
+         p[i] = piv[i];
+      }
+      return p;
+   }
+
+   /** Return pivot permutation vector as a one-dimensional double array
+   @return     (double) piv
+   */
+
+   public double[] getDoublePivot () {
+      double[] vals = new double[m];
+      for (int i = 0; i < m; i++) {
+         vals[i] = (double) piv[i];
+      }
+      return vals;
+   }
+
+   /** Determinant
+   @return     det(A)
+   @exception  IllegalArgumentException  Matrix must be square
+   */
+
+   public double det () {
+      if (m != n) {
+         throw new IllegalArgumentException("Matrix must be square.");
+      }
+      double d = (double) pivsign;
+      for (int j = 0; j < n; j++) {
+         d *= LU[j][j];
+      }
+      return d;
+   }
+
+   /** Solve A*X = B
+   @param  B   A Matrix with as many rows as A and any number of columns.
+   @return     X so that L*U*X = B(piv,:)
+   @exception  IllegalArgumentException Matrix row dimensions must agree.
+   @exception  RuntimeException  Matrix is singular.
+   */
+
+   public Matrix solve (Matrix B) {
+      if (B.getRowDimension() != m) {
+         throw new IllegalArgumentException("Matrix row dimensions must agree.");
+      }
+      if (!this.isNonsingular()) {
+         throw new RuntimeException("Matrix is singular.");
+      }
+
+      // Copy right hand side with pivoting
+      int nx = B.getColumnDimension();
+      Matrix Xmat = B.getMatrix(piv,0,nx-1);
+      double[][] X = Xmat.getArray();
+
+      // Solve L*Y = B(piv,:)
+      for (int k = 0; k < n; k++) {
+         for (int i = k+1; i < n; i++) {
+            for (int j = 0; j < nx; j++) {
+               X[i][j] -= X[k][j]*LU[i][k];
+            }
+         }
+      }
+      // Solve U*X = Y;
+      for (int k = n-1; k >= 0; k--) {
+         for (int j = 0; j < nx; j++) {
+            X[k][j] /= LU[k][k];
+         }
+         for (int i = 0; i < k; i++) {
+            for (int j = 0; j < nx; j++) {
+               X[i][j] -= X[k][j]*LU[i][k];
+            }
+         }
+      }
+      return Xmat;
+   }
+}
diff --git a/Jama/Matrix.java b/Jama/Matrix.java
new file mode 100644
index 0000000..48bb472
--- /dev/null
+++ b/Jama/Matrix.java
@@ -0,0 +1,1088 @@
+package Jama;
+
+import java.text.NumberFormat;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.io.PrintWriter;
+import java.io.BufferedReader;
+import java.io.StreamTokenizer;
+import Jama.util.*;
+
+/**
+   Jama = Java Matrix class.
+<P>
+   The Java Matrix Class provides the fundamental operations of numerical
+   linear algebra.  Various constructors create Matrices from two dimensional
+   arrays of double precision floating point numbers.  Various "gets" and
+   "sets" provide access to submatrices and matrix elements.  Several methods 
+   implement basic matrix arithmetic, including matrix addition and
+   multiplication, matrix norms, and element-by-element array operations.
+   Methods for reading and printing matrices are also included.  All the
+   operations in this version of the Matrix Class involve real matrices.
+   Complex matrices may be handled in a future version.
+<P>
+   Five fundamental matrix decompositions, which consist of pairs or triples
+   of matrices, permutation vectors, and the like, produce results in five
+   decomposition classes.  These decompositions are accessed by the Matrix
+   class to compute solutions of simultaneous linear equations, determinants,
+   inverses and other matrix functions.  The five decompositions are:
+<P><UL>
+   <LI>Cholesky Decomposition of symmetric, positive definite matrices.
+   <LI>LU Decomposition of rectangular matrices.
+   <LI>QR Decomposition of rectangular matrices.
+   <LI>Singular Value Decomposition of rectangular matrices.
+   <LI>Eigenvalue Decomposition of both symmetric and nonsymmetric square matrices.
+</UL>
+<DL>
+<DT><B>Example of use:</B></DT>
+<P>
+<DD>Solve a linear system A x = b and compute the residual norm, ||b - A x||.
+<P><PRE>
+      double[][] vals = {{1.,2.,3},{4.,5.,6.},{7.,8.,10.}};
+      Matrix A = new Matrix(vals);
+      Matrix b = Matrix.random(3,1);
+      Matrix x = A.solve(b);
+      Matrix r = A.times(x).minus(b);
+      double rnorm = r.normInf();
+</PRE></DD>
+</DL>
+
+ at author The MathWorks, Inc. and the National Institute of Standards and Technology.
+ at version 5 August 1998
+*/
+
+public class Matrix implements Cloneable, java.io.Serializable {
+
+/* ------------------------
+   Class variables
+ * ------------------------ */
+
+   /** Array for internal storage of elements.
+   @serial internal array storage.
+   */
+   private double[][] A;
+
+   /** Row and column dimensions.
+   @serial row dimension.
+   @serial column dimension.
+   */
+   private int m, n;
+
+/* ------------------------
+   Constructors
+ * ------------------------ */
+
+   /** Construct an m-by-n matrix of zeros. 
+   @param m    Number of rows.
+   @param n    Number of colums.
+   */
+
+   public Matrix (int m, int n) {
+      this.m = m;
+      this.n = n;
+      A = new double[m][n];
+   }
+
+   /** Construct an m-by-n constant matrix.
+   @param m    Number of rows.
+   @param n    Number of colums.
+   @param s    Fill the matrix with this scalar value.
+   */
+
+   public Matrix (int m, int n, double s) {
+      this.m = m;
+      this.n = n;
+      A = new double[m][n];
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = s;
+         }
+      }
+   }
+
+   /** Construct a matrix from a 2-D array.
+   @param A    Two-dimensional array of doubles.
+   @exception  IllegalArgumentException All rows must have the same length
+   @see        #constructWithCopy
+   */
+
+   public Matrix (double[][] A) {
+      m = A.length;
+      n = A[0].length;
+      for (int i = 0; i < m; i++) {
+         if (A[i].length != n) {
+            throw new IllegalArgumentException("All rows must have the same length.");
+         }
+      }
+      this.A = A;
+   }
+
+   /** Construct a matrix quickly without checking arguments.
+   @param A    Two-dimensional array of doubles.
+   @param m    Number of rows.
+   @param n    Number of colums.
+   */
+
+   public Matrix (double[][] A, int m, int n) {
+      this.A = A;
+      this.m = m;
+      this.n = n;
+   }
+
+   /** Construct a matrix from a one-dimensional packed array
+   @param vals One-dimensional array of doubles, packed by columns (ala Fortran).
+   @param m    Number of rows.
+   @exception  IllegalArgumentException Array length must be a multiple of m.
+   */
+
+   public Matrix (double vals[], int m) {
+      this.m = m;
+      n = (m != 0 ? vals.length/m : 0);
+      if (m*n != vals.length) {
+         throw new IllegalArgumentException("Array length must be a multiple of m.");
+      }
+      A = new double[m][n];
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = vals[i+j*m];
+         }
+      }
+   }
+
+/* ------------------------
+   Public Methods
+ * ------------------------ */
+
+   /** Construct a matrix from a copy of a 2-D array.
+   @param A    Two-dimensional array of doubles.
+   @exception  IllegalArgumentException All rows must have the same length
+   */
+
+   public static Matrix constructWithCopy(double[][] A) {
+      int m = A.length;
+      int n = A[0].length;
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         if (A[i].length != n) {
+            throw new IllegalArgumentException
+               ("All rows must have the same length.");
+         }
+         for (int j = 0; j < n; j++) {
+            C[i][j] = A[i][j];
+         }
+      }
+      return X;
+   }
+
+   /** Make a deep copy of a matrix
+   */
+
+   public Matrix copy () {
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = A[i][j];
+         }
+      }
+      return X;
+   }
+
+   /** Clone the Matrix object.
+   */
+
+   public Object clone () {
+      return this.copy();
+   }
+
+   /** Access the internal two-dimensional array.
+   @return     Pointer to the two-dimensional array of matrix elements.
+   */
+
+   public double[][] getArray () {
+      return A;
+   }
+
+   /** Copy the internal two-dimensional array.
+   @return     Two-dimensional array copy of matrix elements.
+   */
+
+   public double[][] getArrayCopy () {
+      double[][] C = new double[m][n];
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = A[i][j];
+         }
+      }
+      return C;
+   }
+
+   /** Make a one-dimensional column packed copy of the internal array.
+   @return     Matrix elements packed in a one-dimensional array by columns.
+   */
+
+   public double[] getColumnPackedCopy () {
+      double[] vals = new double[m*n];
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            vals[i+j*m] = A[i][j];
+         }
+      }
+      return vals;
+   }
+
+   /** Make a one-dimensional row packed copy of the internal array.
+   @return     Matrix elements packed in a one-dimensional array by rows.
+   */
+
+   public double[] getRowPackedCopy () {
+      double[] vals = new double[m*n];
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            vals[i*n+j] = A[i][j];
+         }
+      }
+      return vals;
+   }
+
+   /** Get row dimension.
+   @return     m, the number of rows.
+   */
+
+   public int getRowDimension () {
+      return m;
+   }
+
+   /** Get column dimension.
+   @return     n, the number of columns.
+   */
+
+   public int getColumnDimension () {
+      return n;
+   }
+
+   /** Get a single element.
+   @param i    Row index.
+   @param j    Column index.
+   @return     A(i,j)
+   @exception  ArrayIndexOutOfBoundsException
+   */
+
+   public double get (int i, int j) {
+      return A[i][j];
+   }
+
+   /** Get a submatrix.
+   @param i0   Initial row index
+   @param i1   Final row index
+   @param j0   Initial column index
+   @param j1   Final column index
+   @return     A(i0:i1,j0:j1)
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public Matrix getMatrix (int i0, int i1, int j0, int j1) {
+      Matrix X = new Matrix(i1-i0+1,j1-j0+1);
+      double[][] B = X.getArray();
+      try {
+         for (int i = i0; i <= i1; i++) {
+            for (int j = j0; j <= j1; j++) {
+               B[i-i0][j-j0] = A[i][j];
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+      return X;
+   }
+
+   /** Get a submatrix.
+   @param r    Array of row indices.
+   @param c    Array of column indices.
+   @return     A(r(:),c(:))
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public Matrix getMatrix (int[] r, int[] c) {
+      Matrix X = new Matrix(r.length,c.length);
+      double[][] B = X.getArray();
+      try {
+         for (int i = 0; i < r.length; i++) {
+            for (int j = 0; j < c.length; j++) {
+               B[i][j] = A[r[i]][c[j]];
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+      return X;
+   }
+
+   /** Get a submatrix.
+   @param i0   Initial row index
+   @param i1   Final row index
+   @param c    Array of column indices.
+   @return     A(i0:i1,c(:))
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public Matrix getMatrix (int i0, int i1, int[] c) {
+      Matrix X = new Matrix(i1-i0+1,c.length);
+      double[][] B = X.getArray();
+      try {
+         for (int i = i0; i <= i1; i++) {
+            for (int j = 0; j < c.length; j++) {
+               B[i-i0][j] = A[i][c[j]];
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+      return X;
+   }
+
+   /** Get a submatrix.
+   @param r    Array of row indices.
+   @param i0   Initial column index
+   @param i1   Final column index
+   @return     A(r(:),j0:j1)
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public Matrix getMatrix (int[] r, int j0, int j1) {
+      Matrix X = new Matrix(r.length,j1-j0+1);
+      double[][] B = X.getArray();
+      try {
+         for (int i = 0; i < r.length; i++) {
+            for (int j = j0; j <= j1; j++) {
+               B[i][j-j0] = A[r[i]][j];
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+      return X;
+   }
+
+   /** Set a single element.
+   @param i    Row index.
+   @param j    Column index.
+   @param s    A(i,j).
+   @exception  ArrayIndexOutOfBoundsException
+   */
+
+   public void set (int i, int j, double s) {
+      A[i][j] = s;
+   }
+
+   /** Set a submatrix.
+   @param i0   Initial row index
+   @param i1   Final row index
+   @param j0   Initial column index
+   @param j1   Final column index
+   @param X    A(i0:i1,j0:j1)
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public void setMatrix (int i0, int i1, int j0, int j1, Matrix X) {
+      try {
+         for (int i = i0; i <= i1; i++) {
+            for (int j = j0; j <= j1; j++) {
+               A[i][j] = X.get(i-i0,j-j0);
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+   }
+
+   /** Set a submatrix.
+   @param r    Array of row indices.
+   @param c    Array of column indices.
+   @param X    A(r(:),c(:))
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public void setMatrix (int[] r, int[] c, Matrix X) {
+      try {
+         for (int i = 0; i < r.length; i++) {
+            for (int j = 0; j < c.length; j++) {
+               A[r[i]][c[j]] = X.get(i,j);
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+   }
+
+   /** Set a submatrix.
+   @param r    Array of row indices.
+   @param j0   Initial column index
+   @param j1   Final column index
+   @param X    A(r(:),j0:j1)
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public void setMatrix (int[] r, int j0, int j1, Matrix X) {
+      try {
+         for (int i = 0; i < r.length; i++) {
+            for (int j = j0; j <= j1; j++) {
+               A[r[i]][j] = X.get(i,j-j0);
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+   }
+
+   /** Set a submatrix.
+   @param i0   Initial row index
+   @param i1   Final row index
+   @param c    Array of column indices.
+   @param X    A(i0:i1,c(:))
+   @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+
+   public void setMatrix (int i0, int i1, int[] c, Matrix X) {
+      try {
+         for (int i = i0; i <= i1; i++) {
+            for (int j = 0; j < c.length; j++) {
+               A[i][c[j]] = X.get(i-i0,j);
+            }
+         }
+      } catch(ArrayIndexOutOfBoundsException e) {
+         throw new ArrayIndexOutOfBoundsException("Submatrix indices");
+      }
+   }
+
+   /** Matrix transpose.
+   @return    A'
+   */
+
+   public Matrix transpose () {
+      Matrix X = new Matrix(n,m);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[j][i] = A[i][j];
+         }
+      }
+      return X;
+   }
+
+   /** One norm
+   @return    maximum column sum.
+   */
+
+   public double norm1 () {
+      double f = 0;
+      for (int j = 0; j < n; j++) {
+         double s = 0;
+         for (int i = 0; i < m; i++) {
+            s += Math.abs(A[i][j]);
+         }
+         f = Math.max(f,s);
+      }
+      return f;
+   }
+
+   /** Two norm
+   @return    maximum singular value.
+   */
+
+   public double norm2 () {
+      return (new SingularValueDecomposition(this).norm2());
+   }
+
+   /** Infinity norm
+   @return    maximum row sum.
+   */
+
+   public double normInf () {
+      double f = 0;
+      for (int i = 0; i < m; i++) {
+         double s = 0;
+         for (int j = 0; j < n; j++) {
+            s += Math.abs(A[i][j]);
+         }
+         f = Math.max(f,s);
+      }
+      return f;
+   }
+
+   /** Frobenius norm
+   @return    sqrt of sum of squares of all elements.
+   */
+
+   public double normF () {
+      double f = 0;
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            f = Maths.hypot(f,A[i][j]);
+         }
+      }
+      return f;
+   }
+
+   /**  Unary minus
+   @return    -A
+   */
+
+   public Matrix uminus () {
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = -A[i][j];
+         }
+      }
+      return X;
+   }
+
+   /** C = A + B
+   @param B    another matrix
+   @return     A + B
+   */
+
+   public Matrix plus (Matrix B) {
+      checkMatrixDimensions(B);
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = A[i][j] + B.A[i][j];
+         }
+      }
+      return X;
+   }
+   public Matrix __add__(Matrix B) {
+     return plus(B);
+   }
+
+   public Matrix __radd__(Matrix B) {
+     return plus(B);
+   }
+
+   /** A = A + B
+   @param B    another matrix
+   @return     A + B
+   */
+
+   public Matrix plusEquals (Matrix B) {
+      checkMatrixDimensions(B);
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = A[i][j] + B.A[i][j];
+         }
+      }
+      return this;
+   }
+
+   /** C = A - B
+   @param B    another matrix
+   @return     A - B
+   */
+
+   public Matrix minus (Matrix B) {
+      checkMatrixDimensions(B);
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = A[i][j] - B.A[i][j];
+         }
+      }
+      return X;
+   }
+   public Matrix __sub__(Matrix B) {
+     return minus(B);
+   }
+   public Matrix __rsub__(Matrix B) {
+     return B.minus(this);
+   }
+
+
+   /** A = A - B
+   @param B    another matrix
+   @return     A - B
+   */
+
+   public Matrix minusEquals (Matrix B) {
+      checkMatrixDimensions(B);
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = A[i][j] - B.A[i][j];
+         }
+      }
+      return this;
+   }
+
+   /** Element-by-element multiplication, C = A.*B
+   @param B    another matrix
+   @return     A.*B
+   */
+
+   public Matrix arrayTimes (Matrix B) {
+      checkMatrixDimensions(B);
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = A[i][j] * B.A[i][j];
+         }
+      }
+      return X;
+   }
+   public Matrix __mul__(Matrix B) {
+     return arrayTimes(B);
+   }
+   public Matrix __rmul__(Matrix B) {
+     return arrayTimes(B);
+   }
+
+
+   /** Element-by-element multiplication in place, A = A.*B
+   @param B    another matrix
+   @return     A.*B
+   */
+
+   public Matrix arrayTimesEquals (Matrix B) {
+      checkMatrixDimensions(B);
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = A[i][j] * B.A[i][j];
+         }
+      }
+      return this;
+   }
+
+   /** Element-by-element right division, C = A./B
+   @param B    another matrix
+   @return     A./B
+   */
+
+   public Matrix arrayRightDivide (Matrix B) {
+      checkMatrixDimensions(B);
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = A[i][j] / B.A[i][j];
+         }
+      }
+      return X;
+   }
+   public Matrix __div__(Matrix B) {
+     return arrayRightDivide(B);
+   }
+
+
+   /** Element-by-element right division in place, A = A./B
+   @param B    another matrix
+   @return     A./B
+   */
+
+   public Matrix arrayRightDivideEquals (Matrix B) {
+      checkMatrixDimensions(B);
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = A[i][j] / B.A[i][j];
+         }
+      }
+      return this;
+   }
+
+   /** Element-by-element left division, C = A.\B
+   @param B    another matrix
+   @return     A.\B
+   */
+
+   public Matrix arrayLeftDivide (Matrix B) {
+      checkMatrixDimensions(B);
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = B.A[i][j] / A[i][j];
+         }
+      }
+      return X;
+   }
+   public Matrix __rdiv__(Matrix B) {
+     return arrayLeftDivide(B);
+   }
+
+
+   /** Element-by-element left division in place, A = A.\B
+   @param B    another matrix
+   @return     A.\B
+   */
+
+   public Matrix arrayLeftDivideEquals (Matrix B) {
+      checkMatrixDimensions(B);
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = B.A[i][j] / A[i][j];
+         }
+      }
+      return this;
+   }
+
+   /** Multiply a matrix by a scalar, C = s*A
+   @param s    scalar
+   @return     s*A
+   */
+
+   public Matrix times (double s) {
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = s*A[i][j];
+         }
+      }
+      return X;
+   }
+
+   public Matrix __mul__(double s) {
+     return times(s);
+   }
+
+   /** Multiply a matrix by a scalar in place, A = s*A
+   @param s    scalar
+   @return     replace A by s*A
+   */
+
+   public Matrix timesEquals (double s) {
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            A[i][j] = s*A[i][j];
+         }
+      }
+      return this;
+   }
+
+   /** Linear algebraic matrix multiplication, A * B
+   @param B    another matrix
+   @return     Matrix product, A * B
+   @exception  IllegalArgumentException Matrix inner dimensions must agree.
+   */
+
+   public Matrix times (Matrix B) {
+      if (B.m != n) {
+         throw new IllegalArgumentException("Matrix inner dimensions must agree.");
+      }
+      Matrix X = new Matrix(m,B.n);
+      double[][] C = X.getArray();
+      double[] Bcolj = new double[n];
+      for (int j = 0; j < B.n; j++) {
+         for (int k = 0; k < n; k++) {
+            Bcolj[k] = B.A[k][j];
+         }
+         for (int i = 0; i < m; i++) {
+            double[] Arowi = A[i];
+            double s = 0;
+            for (int k = 0; k < n; k++) {
+               s += Arowi[k]*Bcolj[k];
+            }
+            C[i][j] = s;
+         }
+      }
+      return X;
+   }
+
+   /** Raise matrix elements to a power, C = A^s
+   @param s    scalar
+   @return     A ^ s 
+   */
+
+   public Matrix power (double s) {
+      Matrix X = new Matrix(m,n);
+      double[][] C = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            C[i][j] = Math.pow(A[i][j],s);
+         }
+      }
+      return X;
+   }
+
+   public Matrix __pow__(double s) {
+     return power(s);
+   }
+
+   /** LU Decomposition
+   @return     LUDecomposition
+   @see LUDecomposition
+   */
+
+   public LUDecomposition lu () {
+      return new LUDecomposition(this);
+   }
+
+   /** QR Decomposition
+   @return     QRDecomposition
+   @see QRDecomposition
+   */
+
+   public QRDecomposition qr () {
+      return new QRDecomposition(this);
+   }
+
+   /** Cholesky Decomposition
+   @return     CholeskyDecomposition
+   @see CholeskyDecomposition
+   */
+
+   public CholeskyDecomposition chol () {
+      return new CholeskyDecomposition(this);
+   }
+
+   /** Singular Value Decomposition
+   @return     SingularValueDecomposition
+   @see SingularValueDecomposition
+   */
+
+   public SingularValueDecomposition svd () {
+      return new SingularValueDecomposition(this);
+   }
+
+   /** Eigenvalue Decomposition
+   @return     EigenvalueDecomposition
+   @see EigenvalueDecomposition
+   */
+
+   public EigenvalueDecomposition eig () {
+      return new EigenvalueDecomposition(this);
+   }
+
+   /** Solve A*X = B
+   @param B    right hand side
+   @return     solution if A is square, least squares solution otherwise
+   */
+
+   public Matrix solve (Matrix B) {
+      return (m == n ? (new LUDecomposition(this)).solve(B) :
+                       (new QRDecomposition(this)).solve(B));
+   }
+
+   /** Solve X*A = B, which is also A'*X' = B'
+   @param B    right hand side
+   @return     solution if A is square, least squares solution otherwise.
+   */
+
+   public Matrix solveTranspose (Matrix B) {
+      return transpose().solve(B.transpose());
+   }
+
+   /** Matrix inverse or pseudoinverse
+   @return     inverse(A) if A is square, pseudoinverse otherwise.
+   */
+
+   public Matrix inverse () {
+      return solve(identity(m,m));
+   }
+
+   /** Matrix determinant
+   @return     determinant
+   */
+
+   public double det () {
+      return new LUDecomposition(this).det();
+   }
+
+   /** Matrix rank
+   @return     effective numerical rank, obtained from SVD.
+   */
+
+   public int rank () {
+      return new SingularValueDecomposition(this).rank();
+   }
+
+   /** Matrix condition (2 norm)
+   @return     ratio of largest to smallest singular value.
+   */
+
+   public double cond () {
+      return new SingularValueDecomposition(this).cond();
+   }
+
+   /** Matrix trace.
+   @return     sum of the diagonal elements.
+   */
+
+   public double trace () {
+      double t = 0;
+      for (int i = 0; i < Math.min(m,n); i++) {
+         t += A[i][i];
+      }
+      return t;
+   }
+
+   /** Generate matrix with random elements
+   @param m    Number of rows.
+   @param n    Number of colums.
+   @return     An m-by-n matrix with uniformly distributed random elements.
+   */
+
+   public static Matrix random (int m, int n) {
+      Matrix A = new Matrix(m,n);
+      double[][] X = A.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            X[i][j] = Math.random();
+         }
+      }
+      return A;
+   }
+
+   /** Generate identity matrix
+   @param m    Number of rows.
+   @param n    Number of colums.
+   @return     An m-by-n matrix with ones on the diagonal and zeros elsewhere.
+   */
+
+   public static Matrix identity (int m, int n) {
+      Matrix A = new Matrix(m,n);
+      double[][] X = A.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            X[i][j] = (i == j ? 1.0 : 0.0);
+         }
+      }
+      return A;
+   }
+
+
+   /** Print the matrix to stdout.   Line the elements up in columns
+     * with a Fortran-like 'Fw.d' style format.
+   @param w    Column width.
+   @param d    Number of digits after the decimal.
+   */
+
+   public void print (int w, int d) {
+      print(new PrintWriter(System.out,true),w,d); }
+
+   /** Print the matrix to the output stream.   Line the elements up in
+     * columns with a Fortran-like 'Fw.d' style format.
+   @param output Output stream.
+   @param w      Column width.
+   @param d      Number of digits after the decimal.
+   */
+
+   public void print (PrintWriter output, int w, int d) {
+      DecimalFormat format = new DecimalFormat();
+      format.setMinimumIntegerDigits(1);
+      format.setMaximumFractionDigits(d);
+      format.setMinimumFractionDigits(d);
+      format.setGroupingUsed(false);
+      print(output,format,w+2);
+   }
+
+   /** Print the matrix to stdout.  Line the elements up in columns.
+     * Use the format object, and right justify within columns of width
+     * characters.
+   @param format A  Formatting object for individual elements.
+   @param width     Field width for each column.
+   */
+
+   public void print (NumberFormat format, int width) {
+      print(new PrintWriter(System.out,true),format,width); }
+
+   // DecimalFormat is a little disappointing coming from Fortran or C's printf.
+   // Since it doesn't pad on the left, the elements will come out different
+   // widths.  Consequently, we'll pass the desired column width in as an
+   // argument and do the extra padding ourselves.
+
+   /** Print the matrix to the output stream.  Line the elements up in columns.
+     * Use the format object, and right justify within columns of width
+     * characters.
+   @param output the output stream.
+   @param format A formatting object to format the matrix elements 
+   @param width  Column width.
+   */
+
+   public void print (PrintWriter output, NumberFormat format, int width) {
+      output.println();  // start on new line.
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            String s = format.format(A[i][j]); // format the number
+            int padding = Math.max(1,width-s.length()); // At _least_ 1 space
+            for (int k = 0; k < padding; k++)
+               output.print(' ');
+            output.print(s);
+         }
+         output.println();
+      }
+      output.println();   // end with blank line.
+   }
+
+   /** Read a matrix from a stream.  The format is the same the print method,
+     * so printed matrices can be read back in.  Elements are separated by
+     * whitespace, all the elements for each row appear on a single line,
+     * the last row is followed by a blank line.
+   @param input the input stream.
+   */
+
+   public static Matrix read (BufferedReader input) throws java.io.IOException {
+      StreamTokenizer tokenizer= new StreamTokenizer(input);
+
+      // Although StreamTokenizer will parse numbers, it doesn't recognize
+      // scientific notation (E or D); however, Double.valueOf does.
+      // The strategy here is to disable StreamTokenizer's number parsing.
+      // We'll only get whitespace delimited words, EOL's and EOF's.
+      // These words should all be numbers, for Double.valueOf to parse.
+
+      tokenizer.resetSyntax();
+      tokenizer.wordChars(0,255);
+      tokenizer.whitespaceChars(0, ' ');
+      tokenizer.eolIsSignificant(true);
+      java.util.Vector v = new java.util.Vector();
+
+      // Ignore initial empty lines
+      while (tokenizer.nextToken() == StreamTokenizer.TT_EOL);
+      if (tokenizer.ttype == StreamTokenizer.TT_EOF)
+	throw new java.io.IOException("Unexpected EOF on matrix read.");
+      do {
+         v.addElement(Double.valueOf(tokenizer.sval)); // Read & store 1st row.
+      } while (tokenizer.nextToken() == StreamTokenizer.TT_WORD);
+
+      int n = v.size();  // Now we've got the number of columns!
+      double row[] = new double[n];
+      for (int j=0; j<n; j++)  // extract the elements of the 1st row.
+         row[j]=((Double)v.elementAt(j)).doubleValue();
+      v.removeAllElements();
+      v.addElement(row);  // Start storing rows instead of columns.
+      while (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
+         // While non-empty lines
+         v.addElement(row = new double[n]);
+         int j = 0;
+         do {
+            if (j >= n) throw new java.io.IOException
+               ("Row " + v.size() + " is too long.");
+            row[j++] = Double.valueOf(tokenizer.sval).doubleValue();
+         } while (tokenizer.nextToken() == StreamTokenizer.TT_WORD);
+         if (j < n) throw new java.io.IOException
+            ("Row " + v.size() + " is too short.");
+      }
+      int m = v.size();  // Now we've got the number of rows.
+      double[][] A = new double[m][];
+      v.copyInto(A);  // copy the rows out of the vector
+      return new Matrix(A);
+   }
+
+
+/* ------------------------
+   Private Methods
+ * ------------------------ */
+
+   /** Check if size(A) == size(B) **/
+
+   private void checkMatrixDimensions (Matrix B) {
+      if (B.m != m || B.n != n) {
+         throw new IllegalArgumentException("Matrix dimensions must agree.");
+      }
+   }
+
+}
diff --git a/Jama/QRDecomposition.java b/Jama/QRDecomposition.java
new file mode 100644
index 0000000..f963c5b
--- /dev/null
+++ b/Jama/QRDecomposition.java
@@ -0,0 +1,218 @@
+package Jama;
+import Jama.util.*;
+
+/** QR Decomposition.
+<P>
+   For an m-by-n matrix A with m >= n, the QR decomposition is an m-by-n
+   orthogonal matrix Q and an n-by-n upper triangular matrix R so that
+   A = Q*R.
+<P>
+   The QR decompostion always exists, even if the matrix does not have
+   full rank, so the constructor will never fail.  The primary use of the
+   QR decomposition is in the least squares solution of nonsquare systems
+   of simultaneous linear equations.  This will fail if isFullRank()
+   returns false.
+*/
+
+public class QRDecomposition implements java.io.Serializable {
+
+/* ------------------------
+   Class variables
+ * ------------------------ */
+
+   /** Array for internal storage of decomposition.
+   @serial internal array storage.
+   */
+   private double[][] QR;
+
+   /** Row and column dimensions.
+   @serial column dimension.
+   @serial row dimension.
+   */
+   private int m, n;
+
+   /** Array for internal storage of diagonal of R.
+   @serial diagonal of R.
+   */
+   private double[] Rdiag;
+
+/* ------------------------
+   Constructor
+ * ------------------------ */
+
+   /** QR Decomposition, computed by Householder reflections.
+   @param A    Rectangular matrix
+   @return     Structure to access R and the Householder vectors and compute Q.
+   */
+
+   public QRDecomposition (Matrix A) {
+      // Initialize.
+      QR = A.getArrayCopy();
+      m = A.getRowDimension();
+      n = A.getColumnDimension();
+      Rdiag = new double[n];
+
+      // Main loop.
+      for (int k = 0; k < n; k++) {
+         // Compute 2-norm of k-th column without under/overflow.
+         double nrm = 0;
+         for (int i = k; i < m; i++) {
+            nrm = Maths.hypot(nrm,QR[i][k]);
+         }
+
+         if (nrm != 0.0) {
+            // Form k-th Householder vector.
+            if (QR[k][k] < 0) {
+               nrm = -nrm;
+            }
+            for (int i = k; i < m; i++) {
+               QR[i][k] /= nrm;
+            }
+            QR[k][k] += 1.0;
+
+            // Apply transformation to remaining columns.
+            for (int j = k+1; j < n; j++) {
+               double s = 0.0; 
+               for (int i = k; i < m; i++) {
+                  s += QR[i][k]*QR[i][j];
+               }
+               s = -s/QR[k][k];
+               for (int i = k; i < m; i++) {
+                  QR[i][j] += s*QR[i][k];
+               }
+            }
+         }
+         Rdiag[k] = -nrm;
+      }
+   }
+
+/* ------------------------
+   Public Methods
+ * ------------------------ */
+
+   /** Is the matrix full rank?
+   @return     true if R, and hence A, has full rank.
+   */
+
+   public boolean isFullRank () {
+      for (int j = 0; j < n; j++) {
+         if (Rdiag[j] == 0)
+            return false;
+      }
+      return true;
+   }
+
+   /** Return the Householder vectors
+   @return     Lower trapezoidal matrix whose columns define the reflections
+   */
+
+   public Matrix getH () {
+      Matrix X = new Matrix(m,n);
+      double[][] H = X.getArray();
+      for (int i = 0; i < m; i++) {
+         for (int j = 0; j < n; j++) {
+            if (i >= j) {
+               H[i][j] = QR[i][j];
+            } else {
+               H[i][j] = 0.0;
+            }
+         }
+      }
+      return X;
+   }
+
+   /** Return the upper triangular factor
+   @return     R
+   */
+
+   public Matrix getR () {
+      Matrix X = new Matrix(n,n);
+      double[][] R = X.getArray();
+      for (int i = 0; i < n; i++) {
+         for (int j = 0; j < n; j++) {
+            if (i < j) {
+               R[i][j] = QR[i][j];
+            } else if (i == j) {
+               R[i][j] = Rdiag[i];
+            } else {
+               R[i][j] = 0.0;
+            }
+         }
+      }
+      return X;
+   }
+
+   /** Generate and return the (economy-sized) orthogonal factor
+   @return     Q
+   */
+
+   public Matrix getQ () {
+      Matrix X = new Matrix(m,n);
+      double[][] Q = X.getArray();
+      for (int k = n-1; k >= 0; k--) {
+         for (int i = 0; i < m; i++) {
+            Q[i][k] = 0.0;
+         }
+         Q[k][k] = 1.0;
+         for (int j = k; j < n; j++) {
+            if (QR[k][k] != 0) {
+               double s = 0.0;
+               for (int i = k; i < m; i++) {
+                  s += QR[i][k]*Q[i][j];
+               }
+               s = -s/QR[k][k];
+               for (int i = k; i < m; i++) {
+                  Q[i][j] += s*QR[i][k];
+               }
+            }
+         }
+      }
+      return X;
+   }
+
+   /** Least squares solution of A*X = B
+   @param B    A Matrix with as many rows as A and any number of columns.
+   @return     X that minimizes the two norm of Q*R*X-B.
+   @exception  IllegalArgumentException  Matrix row dimensions must agree.
+   @exception  RuntimeException  Matrix is rank deficient.
+   */
+
+   public Matrix solve (Matrix B) {
+      if (B.getRowDimension() != m) {
+         throw new IllegalArgumentException("Matrix row dimensions must agree.");
+      }
+      if (!this.isFullRank()) {
+         throw new RuntimeException("Matrix is rank deficient.");
+      }
+      
+      // Copy right hand side
+      int nx = B.getColumnDimension();
+      double[][] X = B.getArrayCopy();
+
+      // Compute Y = transpose(Q)*B
+      for (int k = 0; k < n; k++) {
+         for (int j = 0; j < nx; j++) {
+            double s = 0.0; 
+            for (int i = k; i < m; i++) {
+               s += QR[i][k]*X[i][j];
+            }
+            s = -s/QR[k][k];
+            for (int i = k; i < m; i++) {
+               X[i][j] += s*QR[i][k];
+            }
+         }
+      }
+      // Solve R*X = Y;
+      for (int k = n-1; k >= 0; k--) {
+         for (int j = 0; j < nx; j++) {
+            X[k][j] /= Rdiag[k];
+         }
+         for (int i = 0; i < k; i++) {
+            for (int j = 0; j < nx; j++) {
+               X[i][j] -= X[k][j]*QR[i][k];
+            }
+         }
+      }
+      return (new Matrix(X,n,nx).getMatrix(0,n-1,0,nx-1));
+   }
+}
diff --git a/Jama/SingularValueDecomposition.java b/Jama/SingularValueDecomposition.java
new file mode 100644
index 0000000..7d2245b
--- /dev/null
+++ b/Jama/SingularValueDecomposition.java
@@ -0,0 +1,539 @@
+package Jama;
+import Jama.util.*;
+
+   /** Singular Value Decomposition.
+   <P>
+   For an m-by-n matrix A with m >= n, the singular value decomposition is
+   an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
+   an n-by-n orthogonal matrix V so that A = U*S*V'.
+   <P>
+   The singular values, sigma[k] = S[k][k], are ordered so that
+   sigma[0] >= sigma[1] >= ... >= sigma[n-1].
+   <P>
+   The singular value decompostion always exists, so the constructor will
+   never fail.  The matrix condition number and the effective numerical
+   rank can be computed from this decomposition.
+   */
+
+public class SingularValueDecomposition implements java.io.Serializable {
+
+/* ------------------------
+   Class variables
+ * ------------------------ */
+
+   /** Arrays for internal storage of U and V.
+   @serial internal storage of U.
+   @serial internal storage of V.
+   */
+   private double[][] U, V;
+
+   /** Array for internal storage of singular values.
+   @serial internal storage of singular values.
+   */
+   private double[] s;
+
+   /** Row and column dimensions.
+   @serial row dimension.
+   @serial column dimension.
+   */
+   private int m, n;
+
+/* ------------------------
+   Constructor
+ * ------------------------ */
+
+   /** Construct the singular value decomposition
+   @param A    Rectangular matrix
+   @return     Structure to access U, S and V.
+   */
+
+   public SingularValueDecomposition (Matrix Arg) {
+
+      // Derived from LINPACK code.
+      // Initialize.
+      double[][] A = Arg.getArrayCopy();
+      m = Arg.getRowDimension();
+      n = Arg.getColumnDimension();
+      int nu = Math.min(m,n);
+      s = new double [Math.min(m+1,n)];
+      U = new double [m][nu];
+      V = new double [n][n];
+      double[] e = new double [n];
+      double[] work = new double [m];
+      boolean wantu = true;
+      boolean wantv = true;
+
+      // Reduce A to bidiagonal form, storing the diagonal elements
+      // in s and the super-diagonal elements in e.
+
+      int nct = Math.min(m-1,n);
+      int nrt = Math.max(0,Math.min(n-2,m));
+      for (int k = 0; k < Math.max(nct,nrt); k++) {
+         if (k < nct) {
+
+            // Compute the transformation for the k-th column and
+            // place the k-th diagonal in s[k].
+            // Compute 2-norm of k-th column without under/overflow.
+            s[k] = 0;
+            for (int i = k; i < m; i++) {
+               s[k] = Maths.hypot(s[k],A[i][k]);
+            }
+            if (s[k] != 0.0) {
+               if (A[k][k] < 0.0) {
+                  s[k] = -s[k];
+               }
+               for (int i = k; i < m; i++) {
+                  A[i][k] /= s[k];
+               }
+               A[k][k] += 1.0;
+            }
+            s[k] = -s[k];
+         }
+         for (int j = k+1; j < n; j++) {
+            if ((k < nct) & (s[k] != 0.0))  {
+
+            // Apply the transformation.
+
+               double t = 0;
+               for (int i = k; i < m; i++) {
+                  t += A[i][k]*A[i][j];
+               }
+               t = -t/A[k][k];
+               for (int i = k; i < m; i++) {
+                  A[i][j] += t*A[i][k];
+               }
+            }
+
+            // Place the k-th row of A into e for the
+            // subsequent calculation of the row transformation.
+
+            e[j] = A[k][j];
+         }
+         if (wantu & (k < nct)) {
+
+            // Place the transformation in U for subsequent back
+            // multiplication.
+
+            for (int i = k; i < m; i++) {
+               U[i][k] = A[i][k];
+            }
+         }
+         if (k < nrt) {
+
+            // Compute the k-th row transformation and place the
+            // k-th super-diagonal in e[k].
+            // Compute 2-norm without under/overflow.
+            e[k] = 0;
+            for (int i = k+1; i < n; i++) {
+               e[k] = Maths.hypot(e[k],e[i]);
+            }
+            if (e[k] != 0.0) {
+               if (e[k+1] < 0.0) {
+                  e[k] = -e[k];
+               }
+               for (int i = k+1; i < n; i++) {
+                  e[i] /= e[k];
+               }
+               e[k+1] += 1.0;
+            }
+            e[k] = -e[k];
+            if ((k+1 < m) & (e[k] != 0.0)) {
+
+            // Apply the transformation.
+
+               for (int i = k+1; i < m; i++) {
+                  work[i] = 0.0;
+               }
+               for (int j = k+1; j < n; j++) {
+                  for (int i = k+1; i < m; i++) {
+                     work[i] += e[j]*A[i][j];
+                  }
+               }
+               for (int j = k+1; j < n; j++) {
+                  double t = -e[j]/e[k+1];
+                  for (int i = k+1; i < m; i++) {
+                     A[i][j] += t*work[i];
+                  }
+               }
+            }
+            if (wantv) {
+
+            // Place the transformation in V for subsequent
+            // back multiplication.
+
+               for (int i = k+1; i < n; i++) {
+                  V[i][k] = e[i];
+               }
+            }
+         }
+      }
+
+      // Set up the final bidiagonal matrix or order p.
+
+      int p = Math.min(n,m+1);
+      if (nct < n) {
+         s[nct] = A[nct][nct];
+      }
+      if (m < p) {
+         s[p-1] = 0.0;
+      }
+      if (nrt+1 < p) {
+         e[nrt] = A[nrt][p-1];
+      }
+      e[p-1] = 0.0;
+
+      // If required, generate U.
+
+      if (wantu) {
+         for (int j = nct; j < nu; j++) {
+            for (int i = 0; i < m; i++) {
+               U[i][j] = 0.0;
+            }
+            U[j][j] = 1.0;
+         }
+         for (int k = nct-1; k >= 0; k--) {
+            if (s[k] != 0.0) {
+               for (int j = k+1; j < nu; j++) {
+                  double t = 0;
+                  for (int i = k; i < m; i++) {
+                     t += U[i][k]*U[i][j];
+                  }
+                  t = -t/U[k][k];
+                  for (int i = k; i < m; i++) {
+                     U[i][j] += t*U[i][k];
+                  }
+               }
+               for (int i = k; i < m; i++ ) {
+                  U[i][k] = -U[i][k];
+               }
+               U[k][k] = 1.0 + U[k][k];
+               for (int i = 0; i < k-1; i++) {
+                  U[i][k] = 0.0;
+               }
+            } else {
+               for (int i = 0; i < m; i++) {
+                  U[i][k] = 0.0;
+               }
+               U[k][k] = 1.0;
+            }
+         }
+      }
+
+      // If required, generate V.
+
+      if (wantv) {
+         for (int k = n-1; k >= 0; k--) {
+            if ((k < nrt) & (e[k] != 0.0)) {
+               for (int j = k+1; j < nu; j++) {
+                  double t = 0;
+                  for (int i = k+1; i < n; i++) {
+                     t += V[i][k]*V[i][j];
+                  }
+                  t = -t/V[k+1][k];
+                  for (int i = k+1; i < n; i++) {
+                     V[i][j] += t*V[i][k];
+                  }
+               }
+            }
+            for (int i = 0; i < n; i++) {
+               V[i][k] = 0.0;
+            }
+            V[k][k] = 1.0;
+         }
+      }
+
+      // Main iteration loop for the singular values.
+
+      int pp = p-1;
+      int iter = 0;
+      double eps = Math.pow(2.0,-52.0);
+      while (p > 0) {
+         int k,kase;
+
+         // Here is where a test for too many iterations would go.
+
+         // This section of the program inspects for
+         // negligible elements in the s and e arrays.  On
+         // completion the variables kase and k are set as follows.
+
+         // kase = 1     if s(p) and e[k-1] are negligible and k<p
+         // kase = 2     if s(k) is negligible and k<p
+         // kase = 3     if e[k-1] is negligible, k<p, and
+         //              s(k), ..., s(p) are not negligible (qr step).
+         // kase = 4     if e(p-1) is negligible (convergence).
+
+         for (k = p-2; k >= -1; k--) {
+            if (k == -1) {
+               break;
+            }
+            if (Math.abs(e[k]) <= eps*(Math.abs(s[k]) + Math.abs(s[k+1]))) {
+               e[k] = 0.0;
+               break;
+            }
+         }
+         if (k == p-2) {
+            kase = 4;
+         } else {
+            int ks;
+            for (ks = p-1; ks >= k; ks--) {
+               if (ks == k) {
+                  break;
+               }
+               double t = (ks != p ? Math.abs(e[ks]) : 0.) + 
+                          (ks != k+1 ? Math.abs(e[ks-1]) : 0.);
+               if (Math.abs(s[ks]) <= eps*t)  {
+                  s[ks] = 0.0;
+                  break;
+               }
+            }
+            if (ks == k) {
+               kase = 3;
+            } else if (ks == p-1) {
+               kase = 1;
+            } else {
+               kase = 2;
+               k = ks;
+            }
+         }
+         k++;
+
+         // Perform the task indicated by kase.
+
+         switch (kase) {
+
+            // Deflate negligible s(p).
+
+            case 1: {
+               double f = e[p-2];
+               e[p-2] = 0.0;
+               for (int j = p-2; j >= k; j--) {
+                  double t = Maths.hypot(s[j],f);
+                  double cs = s[j]/t;
+                  double sn = f/t;
+                  s[j] = t;
+                  if (j != k) {
+                     f = -sn*e[j-1];
+                     e[j-1] = cs*e[j-1];
+                  }
+                  if (wantv) {
+                     for (int i = 0; i < n; i++) {
+                        t = cs*V[i][j] + sn*V[i][p-1];
+                        V[i][p-1] = -sn*V[i][j] + cs*V[i][p-1];
+                        V[i][j] = t;
+                     }
+                  }
+               }
+            }
+            break;
+
+            // Split at negligible s(k).
+
+            case 2: {
+               double f = e[k-1];
+               e[k-1] = 0.0;
+               for (int j = k; j < p; j++) {
+                  double t = Maths.hypot(s[j],f);
+                  double cs = s[j]/t;
+                  double sn = f/t;
+                  s[j] = t;
+                  f = -sn*e[j];
+                  e[j] = cs*e[j];
+                  if (wantu) {
+                     for (int i = 0; i < m; i++) {
+                        t = cs*U[i][j] + sn*U[i][k-1];
+                        U[i][k-1] = -sn*U[i][j] + cs*U[i][k-1];
+                        U[i][j] = t;
+                     }
+                  }
+               }
+            }
+            break;
+
+            // Perform one qr step.
+
+            case 3: {
+
+               // Calculate the shift.
+   
+               double scale = Math.max(Math.max(Math.max(Math.max(
+                       Math.abs(s[p-1]),Math.abs(s[p-2])),Math.abs(e[p-2])), 
+                       Math.abs(s[k])),Math.abs(e[k]));
+               double sp = s[p-1]/scale;
+               double spm1 = s[p-2]/scale;
+               double epm1 = e[p-2]/scale;
+               double sk = s[k]/scale;
+               double ek = e[k]/scale;
+               double b = ((spm1 + sp)*(spm1 - sp) + epm1*epm1)/2.0;
+               double c = (sp*epm1)*(sp*epm1);
+               double shift = 0.0;
+               if ((b != 0.0) | (c != 0.0)) {
+                  shift = Math.sqrt(b*b + c);
+                  if (b < 0.0) {
+                     shift = -shift;
+                  }
+                  shift = c/(b + shift);
+               }
+               double f = (sk + sp)*(sk - sp) + shift;
+               double g = sk*ek;
+   
+               // Chase zeros.
+   
+               for (int j = k; j < p-1; j++) {
+                  double t = Maths.hypot(f,g);
+                  double cs = f/t;
+                  double sn = g/t;
+                  if (j != k) {
+                     e[j-1] = t;
+                  }
+                  f = cs*s[j] + sn*e[j];
+                  e[j] = cs*e[j] - sn*s[j];
+                  g = sn*s[j+1];
+                  s[j+1] = cs*s[j+1];
+                  if (wantv) {
+                     for (int i = 0; i < n; i++) {
+                        t = cs*V[i][j] + sn*V[i][j+1];
+                        V[i][j+1] = -sn*V[i][j] + cs*V[i][j+1];
+                        V[i][j] = t;
+                     }
+                  }
+                  t = Maths.hypot(f,g);
+                  cs = f/t;
+                  sn = g/t;
+                  s[j] = t;
+                  f = cs*e[j] + sn*s[j+1];
+                  s[j+1] = -sn*e[j] + cs*s[j+1];
+                  g = sn*e[j+1];
+                  e[j+1] = cs*e[j+1];
+                  if (wantu && (j < m-1)) {
+                     for (int i = 0; i < m; i++) {
+                        t = cs*U[i][j] + sn*U[i][j+1];
+                        U[i][j+1] = -sn*U[i][j] + cs*U[i][j+1];
+                        U[i][j] = t;
+                     }
+                  }
+               }
+               e[p-2] = f;
+               iter = iter + 1;
+            }
+            break;
+
+            // Convergence.
+
+            case 4: {
+
+               // Make the singular values positive.
+   
+               if (s[k] <= 0.0) {
+                  s[k] = (s[k] < 0.0 ? -s[k] : 0.0);
+                  if (wantv) {
+                     for (int i = 0; i <= pp; i++) {
+                        V[i][k] = -V[i][k];
+                     }
+                  }
+               }
+   
+               // Order the singular values.
+   
+               while (k < pp) {
+                  if (s[k] >= s[k+1]) {
+                     break;
+                  }
+                  double t = s[k];
+                  s[k] = s[k+1];
+                  s[k+1] = t;
+                  if (wantv && (k < n-1)) {
+                     for (int i = 0; i < n; i++) {
+                        t = V[i][k+1]; V[i][k+1] = V[i][k]; V[i][k] = t;
+                     }
+                  }
+                  if (wantu && (k < m-1)) {
+                     for (int i = 0; i < m; i++) {
+                        t = U[i][k+1]; U[i][k+1] = U[i][k]; U[i][k] = t;
+                     }
+                  }
+                  k++;
+               }
+               iter = 0;
+               p--;
+            }
+            break;
+         }
+      }
+   }
+
+/* ------------------------
+   Public Methods
+ * ------------------------ */
+
+   /** Return the left singular vectors
+   @return     U
+   */
+
+   public Matrix getU () {
+      return new Matrix(U,m,Math.min(m+1,n));
+   }
+
+   /** Return the right singular vectors
+   @return     V
+   */
+
+   public Matrix getV () {
+      return new Matrix(V,n,n);
+   }
+
+   /** Return the one-dimensional array of singular values
+   @return     diagonal of S.
+   */
+
+   public double[] getSingularValues () {
+      return s;
+   }
+
+   /** Return the diagonal matrix of singular values
+   @return     S
+   */
+
+   public Matrix getS () {
+      Matrix X = new Matrix(n,n);
+      double[][] S = X.getArray();
+      for (int i = 0; i < n; i++) {
+         for (int j = 0; j < n; j++) {
+            S[i][j] = 0.0;
+         }
+         S[i][i] = this.s[i];
+      }
+      return X;
+   }
+
+   /** Two norm
+   @return     max(S)
+   */
+
+   public double norm2 () {
+      return s[0];
+   }
+
+   /** Two norm condition number
+   @return     max(S)/min(S)
+   */
+
+   public double cond () {
+      return s[0]/s[Math.min(m,n)-1];
+   }
+
+   /** Effective numerical matrix rank
+   @return     Number of nonnegligible singular values.
+   */
+
+   public int rank () {
+      double eps = Math.pow(2.0,-52.0);
+      double tol = Math.max(m,n)*s[0]*eps;
+      int r = 0;
+      for (int i = 0; i < s.length; i++) {
+         if (s[i] > tol) {
+            r++;
+         }
+      }
+      return r;
+   }
+}
diff --git a/Jama/doc/Jama/CholeskyDecomposition.html b/Jama/doc/Jama/CholeskyDecomposition.html
new file mode 100644
index 0000000..9622039
--- /dev/null
+++ b/Jama/doc/Jama/CholeskyDecomposition.html
@@ -0,0 +1,227 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Class Jama.CholeskyDecomposition
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV CLASS | <a href="EigenvalueDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="CholeskyDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.CholeskyDecomposition
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.CholeskyDecomposition</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>CholeskyDecomposition</b><dt>extends java.lang.Object<dt>implements java.io.Serializable</dl>
+Cholesky Decomposition.
+<P>
+For a symmetric, positive definite matrix A, the Cholesky decomposition
+is an lower triangular matrix L so that A = L*L'.
+<P>
+If the matrix is not symmetric or positive definite, the constructor
+returns a partial decomposition and sets an internal flag that may
+be queried by the isSPD() method.
+<p>
+<dl>
+<dt><b>See Also:</b><dd><a href="../serializedform.html#Jama.CholeskyDecomposition">Serialized Form</a></dl>
+<hr>
+
+<p>
+<a name="constructor_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Constructor Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/CholeskyDecomposition.html#CholeskyDecomposition(Jama.Matrix)">CholeskyDecomposition</a></b>(<a href="../Jama/Matrix.html">Matrix</a> Arg)
+<br>
+          Cholesky algorithm for symmetric and positive definite matrix.</td>
+</tr>
+</table>
+ <a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/CholeskyDecomposition.html#getL()">getL</a></b>()
+<br>
+          Return triangular factor.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+boolean</font>
+</td>
+<td><b><a href="../Jama/CholeskyDecomposition.html#isSPD()">isSPD</a></b>()
+<br>
+          Is the matrix symmetric and positive definite?</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/CholeskyDecomposition.html#solve(Jama.Matrix)">solve</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Solve A*X = B</td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="constructor_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Constructor Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="CholeskyDecomposition(Jama.Matrix)"><!-- --></a><h3>
+CholeskyDecomposition</h3>
+<pre>
+public <b>CholeskyDecomposition</b>(<a href="../Jama/Matrix.html">Matrix</a> Arg)</pre>
+<dl>
+<dd>Cholesky algorithm for symmetric and positive definite matrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Square, symmetric matrix.</dl>
+</dd>
+</dl>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="isSPD()"><!-- --></a><h3>
+isSPD</h3>
+<pre>
+public boolean <b>isSPD</b>()</pre>
+<dl>
+<dd>Is the matrix symmetric and positive definite?<dd><dl>
+<dt><b>Returns:</b><dd>true if A is symmetric and positive definite.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getL()"><!-- --></a><h3>
+getL</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getL</b>()</pre>
+<dl>
+<dd>Return triangular factor.<dd><dl>
+<dt><b>Returns:</b><dd>L</dl>
+</dd>
+</dl>
+<hr>
+<a name="solve(Jama.Matrix)"><!-- --></a><h3>
+solve</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>solve</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Solve A*X = B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - A Matrix with as many rows as A and any number of columns.<dt><b>Returns:</b><dd>X so that L*L'*X = B<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - Matrix row dimensions must agree.<dd>java.lang.RuntimeException - Matrix is not symmetric positive definite.</dl>
+</dd>
+</dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV CLASS | <a href="EigenvalueDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="CholeskyDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/EigenvalueDecomposition.html b/Jama/doc/Jama/EigenvalueDecomposition.html
new file mode 100644
index 0000000..bfbdfbb
--- /dev/null
+++ b/Jama/doc/Jama/EigenvalueDecomposition.html
@@ -0,0 +1,249 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Class Jama.EigenvalueDecomposition
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="CholeskyDecomposition.html">PREV CLASS</a> | <a href="LUDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="EigenvalueDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.EigenvalueDecomposition
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.EigenvalueDecomposition</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>EigenvalueDecomposition</b><dt>extends java.lang.Object<dt>implements java.io.Serializable</dl>
+Eigenvalues and eigenvectors of a real matrix. 
+<P>
+If A is symmetric, then A = V*D*V' where the eigenvalue matrix D is
+diagonal and the eigenvector matrix V is orthogonal.
+I.e. A = V.times(D.times(V.transpose())) and 
+V.times(V.transpose()) equals the identity matrix.
+<P>
+If A is not symmetric, then the eigenvalue matrix D is block diagonal
+with the real eigenvalues in 1-by-1 blocks and any complex eigenvalues,
+lambda + i*mu, in 2-by-2 blocks, [lambda, mu; -mu, lambda].  The
+columns of V represent the eigenvectors in the sense that A*V = V*D,
+i.e. A.times(V) equals V.times(D).  The matrix V may be badly
+conditioned, or even singular, so the validity of the equation
+A = V*D*inverse(V) depends upon V.cond().
+<p>
+<dl>
+<dt><b>See Also:</b><dd><a href="../serializedform.html#Jama.EigenvalueDecomposition">Serialized Form</a></dl>
+<hr>
+
+<p>
+<a name="constructor_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Constructor Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/EigenvalueDecomposition.html#EigenvalueDecomposition(Jama.Matrix)">EigenvalueDecomposition</a></b>(<a href="../Jama/Matrix.html">Matrix</a> Arg)
+<br>
+          Check for symmetry, then construct the eigenvalue decomposition</td>
+</tr>
+</table>
+ <a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/EigenvalueDecomposition.html#getD()">getD</a></b>()
+<br>
+          Return the block diagonal eigenvalue matrix</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[]</font>
+</td>
+<td><b><a href="../Jama/EigenvalueDecomposition.html#getImagEigenvalues()">getImagEigenvalues</a></b>()
+<br>
+          Return the imaginary parts of the eigenvalues</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[]</font>
+</td>
+<td><b><a href="../Jama/EigenvalueDecomposition.html#getRealEigenvalues()">getRealEigenvalues</a></b>()
+<br>
+          Return the real parts of the eigenvalues</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/EigenvalueDecomposition.html#getV()">getV</a></b>()
+<br>
+          Return the eigenvector matrix</td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="constructor_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Constructor Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="EigenvalueDecomposition(Jama.Matrix)"><!-- --></a><h3>
+EigenvalueDecomposition</h3>
+<pre>
+public <b>EigenvalueDecomposition</b>(<a href="../Jama/Matrix.html">Matrix</a> Arg)</pre>
+<dl>
+<dd>Check for symmetry, then construct the eigenvalue decomposition<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Square matrix</dl>
+</dd>
+</dl>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="getV()"><!-- --></a><h3>
+getV</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getV</b>()</pre>
+<dl>
+<dd>Return the eigenvector matrix<dd><dl>
+<dt><b>Returns:</b><dd>V</dl>
+</dd>
+</dl>
+<hr>
+<a name="getRealEigenvalues()"><!-- --></a><h3>
+getRealEigenvalues</h3>
+<pre>
+public double[] <b>getRealEigenvalues</b>()</pre>
+<dl>
+<dd>Return the real parts of the eigenvalues<dd><dl>
+<dt><b>Returns:</b><dd>real(diag(D))</dl>
+</dd>
+</dl>
+<hr>
+<a name="getImagEigenvalues()"><!-- --></a><h3>
+getImagEigenvalues</h3>
+<pre>
+public double[] <b>getImagEigenvalues</b>()</pre>
+<dl>
+<dd>Return the imaginary parts of the eigenvalues<dd><dl>
+<dt><b>Returns:</b><dd>imag(diag(D))</dl>
+</dd>
+</dl>
+<hr>
+<a name="getD()"><!-- --></a><h3>
+getD</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getD</b>()</pre>
+<dl>
+<dd>Return the block diagonal eigenvalue matrix<dd><dl>
+<dt><b>Returns:</b><dd>D</dl>
+</dd>
+</dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="CholeskyDecomposition.html">PREV CLASS</a> | <a href="LUDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="EigenvalueDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/LUDecomposition.html b/Jama/doc/Jama/LUDecomposition.html
new file mode 100644
index 0000000..0dba868
--- /dev/null
+++ b/Jama/doc/Jama/LUDecomposition.html
@@ -0,0 +1,302 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Class Jama.LUDecomposition
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="EigenvalueDecomposition.html">PREV CLASS</a> | <a href="Matrix.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="LUDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.LUDecomposition
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.LUDecomposition</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>LUDecomposition</b><dt>extends java.lang.Object<dt>implements java.io.Serializable</dl>
+LU Decomposition.
+<P>
+For an m-by-n matrix A with m >= n, the LU decomposition is an m-by-n
+unit lower triangular matrix L, an n-by-n upper triangular matrix U,
+and a permutation vector piv of length m so that A(piv,:) = L*U.
+If m < n, then L is m-by-m and U is m-by-n.
+<P>
+The LU decompostion with pivoting always exists, even if the matrix is
+singular, so the constructor will never fail.  The primary use of the
+LU decomposition is in the solution of square systems of simultaneous
+linear equations.  This will fail if isNonsingular() returns false.
+<p>
+<dl>
+<dt><b>See Also:</b><dd><a href="../serializedform.html#Jama.LUDecomposition">Serialized Form</a></dl>
+<hr>
+
+<p>
+<a name="constructor_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Constructor Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/LUDecomposition.html#LUDecomposition(Jama.Matrix)">LUDecomposition</a></b>(<a href="../Jama/Matrix.html">Matrix</a> A)
+<br>
+          LU Decomposition</td>
+</tr>
+</table>
+ <a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/LUDecomposition.html#det()">det</a></b>()
+<br>
+          Determinant</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[]</font>
+</td>
+<td><b><a href="../Jama/LUDecomposition.html#getDoublePivot()">getDoublePivot</a></b>()
+<br>
+          Return pivot permutation vector as a one-dimensional double array</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/LUDecomposition.html#getL()">getL</a></b>()
+<br>
+          Return lower triangular factor</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+int[]</font>
+</td>
+<td><b><a href="../Jama/LUDecomposition.html#getPivot()">getPivot</a></b>()
+<br>
+          Return pivot permutation vector</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/LUDecomposition.html#getU()">getU</a></b>()
+<br>
+          Return upper triangular factor</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+boolean</font>
+</td>
+<td><b><a href="../Jama/LUDecomposition.html#isNonsingular()">isNonsingular</a></b>()
+<br>
+          Is the matrix nonsingular?</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/LUDecomposition.html#solve(Jama.Matrix)">solve</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Solve A*X = B</td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="constructor_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Constructor Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="LUDecomposition(Jama.Matrix)"><!-- --></a><h3>
+LUDecomposition</h3>
+<pre>
+public <b>LUDecomposition</b>(<a href="../Jama/Matrix.html">Matrix</a> A)</pre>
+<dl>
+<dd>LU Decomposition<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Rectangular matrix</dl>
+</dd>
+</dl>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="isNonsingular()"><!-- --></a><h3>
+isNonsingular</h3>
+<pre>
+public boolean <b>isNonsingular</b>()</pre>
+<dl>
+<dd>Is the matrix nonsingular?<dd><dl>
+<dt><b>Returns:</b><dd>true if U, and hence A, is nonsingular.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getL()"><!-- --></a><h3>
+getL</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getL</b>()</pre>
+<dl>
+<dd>Return lower triangular factor<dd><dl>
+<dt><b>Returns:</b><dd>L</dl>
+</dd>
+</dl>
+<hr>
+<a name="getU()"><!-- --></a><h3>
+getU</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getU</b>()</pre>
+<dl>
+<dd>Return upper triangular factor<dd><dl>
+<dt><b>Returns:</b><dd>U</dl>
+</dd>
+</dl>
+<hr>
+<a name="getPivot()"><!-- --></a><h3>
+getPivot</h3>
+<pre>
+public int[] <b>getPivot</b>()</pre>
+<dl>
+<dd>Return pivot permutation vector<dd><dl>
+<dt><b>Returns:</b><dd>piv</dl>
+</dd>
+</dl>
+<hr>
+<a name="getDoublePivot()"><!-- --></a><h3>
+getDoublePivot</h3>
+<pre>
+public double[] <b>getDoublePivot</b>()</pre>
+<dl>
+<dd>Return pivot permutation vector as a one-dimensional double array<dd><dl>
+<dt><b>Returns:</b><dd>(double) piv</dl>
+</dd>
+</dl>
+<hr>
+<a name="det()"><!-- --></a><h3>
+det</h3>
+<pre>
+public double <b>det</b>()</pre>
+<dl>
+<dd>Determinant<dd><dl>
+<dt><b>Returns:</b><dd>det(A)<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - Matrix must be square</dl>
+</dd>
+</dl>
+<hr>
+<a name="solve(Jama.Matrix)"><!-- --></a><h3>
+solve</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>solve</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Solve A*X = B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - A Matrix with as many rows as A and any number of columns.<dt><b>Returns:</b><dd>X so that L*U*X = B(piv,:)<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - Matrix row dimensions must agree.<dd>java.lang.RuntimeException - Matrix is singular.</dl>
+</dd>
+</dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="EigenvalueDecomposition.html">PREV CLASS</a> | <a href="Matrix.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="LUDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/Matrix.html b/Jama/doc/Jama/Matrix.html
new file mode 100644
index 0000000..e2bf246
--- /dev/null
+++ b/Jama/doc/Jama/Matrix.html
@@ -0,0 +1,1520 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Class Jama.Matrix
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="LUDecomposition.html">PREV CLASS</a> | <a href="QRDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="Matrix.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.Matrix
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.Matrix</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>Matrix</b><dt>extends java.lang.Object<dt>implements java.lang.Cloneable, java.io.Serializable</dl>
+Jama = Java Matrix class.
+<P>
+The Java Matrix Class provides the fundamental operations of numerical
+linear algebra.  Various constructors create Matrices from two dimensional
+arrays of double precision floating point numbers.  Various "gets" and
+"sets" provide access to submatrices and matrix elements.  Several methods 
+implement basic matrix arithmetic, including matrix addition and
+multiplication, matrix norms, and element-by-element array operations.
+Methods for reading and printing matrices are also included.  All the
+operations in this version of the Matrix Class involve real matrices.
+Complex matrices may be handled in a future version.
+<P>
+Five fundamental matrix decompositions, which consist of pairs or triples
+of matrices, permutation vectors, and the like, produce results in five
+decomposition classes.  These decompositions are accessed by the Matrix
+class to compute solutions of simultaneous linear equations, determinants,
+inverses and other matrix functions.  The five decompositions are:
+<P><UL>
+<LI>Cholesky Decomposition of symmetric, positive definite matrices.
+<LI>LU Decomposition of rectangular matrices.
+<LI>QR Decomposition of rectangular matrices.
+<LI>Singular Value Decomposition of rectangular matrices.
+<LI>Eigenvalue Decomposition of both symmetric and nonsymmetric square matrices.
+</UL>
+<DL>
+<DT><B>Example of use:</B></DT>
+<P>
+<DD>Solve a linear system A x = b and compute the residual norm, ||b - A x||.
+<P><PRE>
+double[][] vals = {{1.,2.,3},{4.,5.,6.},{7.,8.,10.}};
+Matrix A = new Matrix(vals);
+Matrix b = Matrix.random(3,1);
+Matrix x = A.solve(b);
+Matrix r = A.times(x).minus(b);
+double rnorm = r.normInf();
+</PRE></DD>
+</DL>
+<p>
+<dl>
+<dt><b>Version:</b> <dd>5 August 1998</dd>
+<dt><b>Author:</b> <dd>The MathWorks, Inc. and the National Institute of Standards and Technology.</dd>
+<dt><b>See Also:</b><dd><a href="../serializedform.html#Jama.Matrix">Serialized Form</a></dl>
+<hr>
+
+<p>
+<a name="constructor_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Constructor Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/Matrix.html#Matrix(int, int)">Matrix</a></b>(int m,
+       int n)
+<br>
+          Construct an m-by-n matrix of zeros.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/Matrix.html#Matrix(int, int, double)">Matrix</a></b>(int m,
+       int n,
+       double s)
+<br>
+          Construct an m-by-n constant matrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/Matrix.html#Matrix(double[][])">Matrix</a></b>(double[][] A)
+<br>
+          Construct a matrix from a 2-D array.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/Matrix.html#Matrix(double[][], int, int)">Matrix</a></b>(double[][] A,
+       int m,
+       int n)
+<br>
+          Construct a matrix quickly without checking arguments.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/Matrix.html#Matrix(double[], int)">Matrix</a></b>(double[] vals,
+       int m)
+<br>
+          Construct a matrix from a one-dimensional packed array</td>
+</tr>
+</table>
+ <a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#arrayLeftDivideEquals(Jama.Matrix)">arrayLeftDivideEquals</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Element-by-element left division in place, A = A.\B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#arrayLeftDivide(Jama.Matrix)">arrayLeftDivide</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Element-by-element left division, C = A.\B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#arrayRightDivideEquals(Jama.Matrix)">arrayRightDivideEquals</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Element-by-element right division in place, A = A./B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#arrayRightDivide(Jama.Matrix)">arrayRightDivide</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Element-by-element right division, C = A./B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#arrayTimesEquals(Jama.Matrix)">arrayTimesEquals</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Element-by-element multiplication in place, A = A.*B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#arrayTimes(Jama.Matrix)">arrayTimes</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Element-by-element multiplication, C = A.*B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/CholeskyDecomposition.html">CholeskyDecomposition</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#chol()">chol</a></b>()
+<br>
+          Cholesky Decomposition</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+java.lang.Object</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#clone()">clone</a></b>()
+<br>
+          Clone the Matrix object.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#cond()">cond</a></b>()
+<br>
+          Matrix condition (2 norm)</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static <a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#constructWithCopy(double[][])">constructWithCopy</a></b>(double[][] A)
+<br>
+          Construct a matrix from a copy of a 2-D array.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#copy()">copy</a></b>()
+<br>
+          Make a deep copy of a matrix</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#det()">det</a></b>()
+<br>
+          Matrix determinant</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#eig()">eig</a></b>()
+<br>
+          Eigenvalue Decomposition</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[][]</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getArrayCopy()">getArrayCopy</a></b>()
+<br>
+          Copy the internal two-dimensional array.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[][]</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getArray()">getArray</a></b>()
+<br>
+          Access the internal two-dimensional array.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+int</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getColumnDimension()">getColumnDimension</a></b>()
+<br>
+          Get column dimension.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[]</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getColumnPackedCopy()">getColumnPackedCopy</a></b>()
+<br>
+          Make a one-dimensional column packed copy of the internal array.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#get(int, int)">get</a></b>(int i,
+    int j)
+<br>
+          Get a single element.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getMatrix(int, int, int, int)">getMatrix</a></b>(int i0,
+          int i1,
+          int j0,
+          int j1)
+<br>
+          Get a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getMatrix(int[], int[])">getMatrix</a></b>(int[] r,
+          int[] c)
+<br>
+          Get a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getMatrix(int, int, int[])">getMatrix</a></b>(int i0,
+          int i1,
+          int[] c)
+<br>
+          Get a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getMatrix(int[], int, int)">getMatrix</a></b>(int[] r,
+          int j0,
+          int j1)
+<br>
+          Get a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+int</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getRowDimension()">getRowDimension</a></b>()
+<br>
+          Get row dimension.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[]</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#getRowPackedCopy()">getRowPackedCopy</a></b>()
+<br>
+          Make a one-dimensional row packed copy of the internal array.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static <a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#identity(int, int)">identity</a></b>(int m,
+         int n)
+<br>
+          Generate identity matrix</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#inverse()">inverse</a></b>()
+<br>
+          Matrix inverse or pseudoinverse</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/LUDecomposition.html">LUDecomposition</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#lu()">lu</a></b>()
+<br>
+          LU Decomposition</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#minusEquals(Jama.Matrix)">minusEquals</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          A = A - B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#minus(Jama.Matrix)">minus</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          C = A - B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#norm1()">norm1</a></b>()
+<br>
+          One norm</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#norm2()">norm2</a></b>()
+<br>
+          Two norm</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#normF()">normF</a></b>()
+<br>
+          Frobenius norm</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#normInf()">normInf</a></b>()
+<br>
+          Infinity norm</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#plusEquals(Jama.Matrix)">plusEquals</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          A = A + B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#plus(Jama.Matrix)">plus</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          C = A + B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#print(int, int)">print</a></b>(int w,
+      int d)
+<br>
+          Print the matrix to stdout.   </td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#print(java.io.PrintWriter, int, int)">print</a></b>(java.io.PrintWriter output,
+      int w,
+      int d)
+<br>
+          Print the matrix to the output stream.   </td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#print(java.text.NumberFormat, int)">print</a></b>(java.text.NumberFormat format,
+      int width)
+<br>
+          Print the matrix to stdout.  </td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#print(java.io.PrintWriter, java.text.NumberFormat, int)">print</a></b>(java.io.PrintWriter output,
+      java.text.NumberFormat format,
+      int width)
+<br>
+          Print the matrix to the output stream.  </td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/QRDecomposition.html">QRDecomposition</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#qr()">qr</a></b>()
+<br>
+          QR Decomposition</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static <a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#random(int, int)">random</a></b>(int m,
+       int n)
+<br>
+          Generate matrix with random elements</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+int</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#rank()">rank</a></b>()
+<br>
+          Matrix rank</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static <a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#read(java.io.BufferedReader)">read</a></b>(java.io.BufferedReader input)
+<br>
+          Read a matrix from a stream.  </td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#set(int, int, double)">set</a></b>(int i,
+    int j,
+    double s)
+<br>
+          Set a single element.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#setMatrix(int, int, int, int, Jama.Matrix)">setMatrix</a></b>(int i0,
+          int i1,
+          int j0,
+          int j1,
+          <a href="../Jama/Matrix.html">Matrix</a> X)
+<br>
+          Set a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#setMatrix(int[], int[], Jama.Matrix)">setMatrix</a></b>(int[] r,
+          int[] c,
+          <a href="../Jama/Matrix.html">Matrix</a> X)
+<br>
+          Set a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#setMatrix(int[], int, int, Jama.Matrix)">setMatrix</a></b>(int[] r,
+          int j0,
+          int j1,
+          <a href="../Jama/Matrix.html">Matrix</a> X)
+<br>
+          Set a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+void</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#setMatrix(int, int, int[], Jama.Matrix)">setMatrix</a></b>(int i0,
+          int i1,
+          int[] c,
+          <a href="../Jama/Matrix.html">Matrix</a> X)
+<br>
+          Set a submatrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#solve(Jama.Matrix)">solve</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Solve A*X = B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#solveTranspose(Jama.Matrix)">solveTranspose</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Solve X*A = B, which is also A'</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/SingularValueDecomposition.html">SingularValueDecomposition</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#svd()">svd</a></b>()
+<br>
+          Singular Value Decomposition</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#timesEquals(double)">timesEquals</a></b>(double s)
+<br>
+          Multiply a matrix by a scalar in place, A = s*A</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#times(double)">times</a></b>(double s)
+<br>
+          Multiply a matrix by a scalar, C = s*A</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#times(Jama.Matrix)">times</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Linear algebraic matrix multiplication, A * B</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/Matrix.html#trace()">trace</a></b>()
+<br>
+          Matrix trace.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#transpose()">transpose</a></b>()
+<br>
+          Matrix transpose.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/Matrix.html#uminus()">uminus</a></b>()
+<br>
+          Unary minus</td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="constructor_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Constructor Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="Matrix(int, int)"><!-- --></a><h3>
+Matrix</h3>
+<pre>
+public <b>Matrix</b>(int m,
+              int n)</pre>
+<dl>
+<dd>Construct an m-by-n matrix of zeros.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+m</code>
+ - Number of rows.<dd><code>
+n</code>
+ - Number of colums.</dl>
+</dd>
+</dl>
+<hr>
+<a name="Matrix(int, int, double)"><!-- --></a><h3>
+Matrix</h3>
+<pre>
+public <b>Matrix</b>(int m,
+              int n,
+              double s)</pre>
+<dl>
+<dd>Construct an m-by-n constant matrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+m</code>
+ - Number of rows.<dd><code>
+n</code>
+ - Number of colums.<dd><code>
+s</code>
+ - Fill the matrix with this scalar value.</dl>
+</dd>
+</dl>
+<hr>
+<a name="Matrix(double[][])"><!-- --></a><h3>
+Matrix</h3>
+<pre>
+public <b>Matrix</b>(double[][] A)</pre>
+<dl>
+<dd>Construct a matrix from a 2-D array.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Two-dimensional array of doubles.<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - All rows must have the same length<dt><b>See Also:</b><dd><a href="../Jama/Matrix.html#constructWithCopy(double[][])">constructWithCopy</a></dl>
+</dd>
+</dl>
+<hr>
+<a name="Matrix(double[][], int, int)"><!-- --></a><h3>
+Matrix</h3>
+<pre>
+public <b>Matrix</b>(double[][] A,
+              int m,
+              int n)</pre>
+<dl>
+<dd>Construct a matrix quickly without checking arguments.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Two-dimensional array of doubles.<dd><code>
+m</code>
+ - Number of rows.<dd><code>
+n</code>
+ - Number of colums.</dl>
+</dd>
+</dl>
+<hr>
+<a name="Matrix(double[], int)"><!-- --></a><h3>
+Matrix</h3>
+<pre>
+public <b>Matrix</b>(double[] vals,
+              int m)</pre>
+<dl>
+<dd>Construct a matrix from a one-dimensional packed array<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+vals</code>
+ - One-dimensional array of doubles, packed by columns (ala Fortran).<dd><code>
+m</code>
+ - Number of rows.<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - Array length must be a multiple of m.</dl>
+</dd>
+</dl>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="constructWithCopy(double[][])"><!-- --></a><h3>
+constructWithCopy</h3>
+<pre>
+public static <a href="../Jama/Matrix.html">Matrix</a> <b>constructWithCopy</b>(double[][] A)</pre>
+<dl>
+<dd>Construct a matrix from a copy of a 2-D array.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Two-dimensional array of doubles.<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - All rows must have the same length</dl>
+</dd>
+</dl>
+<hr>
+<a name="copy()"><!-- --></a><h3>
+copy</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>copy</b>()</pre>
+<dl>
+<dd>Make a deep copy of a matrix<dd><dl>
+</dl>
+</dd>
+</dl>
+<hr>
+<a name="clone()"><!-- --></a><h3>
+clone</h3>
+<pre>
+public java.lang.Object <b>clone</b>()</pre>
+<dl>
+<dd>Clone the Matrix object.<dd><dl>
+<dt><b>Overrides:</b><dd>clone in class java.lang.Object</dl>
+</dd>
+</dl>
+<hr>
+<a name="getArray()"><!-- --></a><h3>
+getArray</h3>
+<pre>
+public double[][] <b>getArray</b>()</pre>
+<dl>
+<dd>Access the internal two-dimensional array.<dd><dl>
+<dt><b>Returns:</b><dd>Pointer to the two-dimensional array of matrix elements.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getArrayCopy()"><!-- --></a><h3>
+getArrayCopy</h3>
+<pre>
+public double[][] <b>getArrayCopy</b>()</pre>
+<dl>
+<dd>Copy the internal two-dimensional array.<dd><dl>
+<dt><b>Returns:</b><dd>Two-dimensional array copy of matrix elements.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getColumnPackedCopy()"><!-- --></a><h3>
+getColumnPackedCopy</h3>
+<pre>
+public double[] <b>getColumnPackedCopy</b>()</pre>
+<dl>
+<dd>Make a one-dimensional column packed copy of the internal array.<dd><dl>
+<dt><b>Returns:</b><dd>Matrix elements packed in a one-dimensional array by columns.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getRowPackedCopy()"><!-- --></a><h3>
+getRowPackedCopy</h3>
+<pre>
+public double[] <b>getRowPackedCopy</b>()</pre>
+<dl>
+<dd>Make a one-dimensional row packed copy of the internal array.<dd><dl>
+<dt><b>Returns:</b><dd>Matrix elements packed in a one-dimensional array by rows.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getRowDimension()"><!-- --></a><h3>
+getRowDimension</h3>
+<pre>
+public int <b>getRowDimension</b>()</pre>
+<dl>
+<dd>Get row dimension.<dd><dl>
+<dt><b>Returns:</b><dd>m, the number of rows.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getColumnDimension()"><!-- --></a><h3>
+getColumnDimension</h3>
+<pre>
+public int <b>getColumnDimension</b>()</pre>
+<dl>
+<dd>Get column dimension.<dd><dl>
+<dt><b>Returns:</b><dd>n, the number of columns.</dl>
+</dd>
+</dl>
+<hr>
+<a name="get(int, int)"><!-- --></a><h3>
+get</h3>
+<pre>
+public double <b>get</b>(int i,
+                  int j)</pre>
+<dl>
+<dd>Get a single element.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+i</code>
+ - Row index.<dd><code>
+j</code>
+ - Column index.<dt><b>Returns:</b><dd>A(i,j)<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException -  </dl>
+</dd>
+</dl>
+<hr>
+<a name="getMatrix(int, int, int, int)"><!-- --></a><h3>
+getMatrix</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getMatrix</b>(int i0,
+                        int i1,
+                        int j0,
+                        int j1)</pre>
+<dl>
+<dd>Get a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+i0</code>
+ - Initial row index<dd><code>
+i1</code>
+ - Final row index<dd><code>
+j0</code>
+ - Initial column index<dd><code>
+j1</code>
+ - Final column index<dt><b>Returns:</b><dd>A(i0:i1,j0:j1)<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="getMatrix(int[], int[])"><!-- --></a><h3>
+getMatrix</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getMatrix</b>(int[] r,
+                        int[] c)</pre>
+<dl>
+<dd>Get a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+r</code>
+ - Array of row indices.<dd><code>
+c</code>
+ - Array of column indices.<dt><b>Returns:</b><dd>A(r(:),c(:))<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="getMatrix(int, int, int[])"><!-- --></a><h3>
+getMatrix</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getMatrix</b>(int i0,
+                        int i1,
+                        int[] c)</pre>
+<dl>
+<dd>Get a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+i0</code>
+ - Initial row index<dd><code>
+i1</code>
+ - Final row index<dd><code>
+c</code>
+ - Array of column indices.<dt><b>Returns:</b><dd>A(i0:i1,c(:))<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="getMatrix(int[], int, int)"><!-- --></a><h3>
+getMatrix</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getMatrix</b>(int[] r,
+                        int j0,
+                        int j1)</pre>
+<dl>
+<dd>Get a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+r</code>
+ - Array of row indices.<dd><code>
+i0</code>
+ - Initial column index<dd><code>
+i1</code>
+ - Final column index<dt><b>Returns:</b><dd>A(r(:),j0:j1)<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="set(int, int, double)"><!-- --></a><h3>
+set</h3>
+<pre>
+public void <b>set</b>(int i,
+                int j,
+                double s)</pre>
+<dl>
+<dd>Set a single element.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+i</code>
+ - Row index.<dd><code>
+j</code>
+ - Column index.<dd><code>
+s</code>
+ - A(i,j).<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException -  </dl>
+</dd>
+</dl>
+<hr>
+<a name="setMatrix(int, int, int, int, Jama.Matrix)"><!-- --></a><h3>
+setMatrix</h3>
+<pre>
+public void <b>setMatrix</b>(int i0,
+                      int i1,
+                      int j0,
+                      int j1,
+                      <a href="../Jama/Matrix.html">Matrix</a> X)</pre>
+<dl>
+<dd>Set a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+i0</code>
+ - Initial row index<dd><code>
+i1</code>
+ - Final row index<dd><code>
+j0</code>
+ - Initial column index<dd><code>
+j1</code>
+ - Final column index<dd><code>
+X</code>
+ - A(i0:i1,j0:j1)<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="setMatrix(int[], int[], Jama.Matrix)"><!-- --></a><h3>
+setMatrix</h3>
+<pre>
+public void <b>setMatrix</b>(int[] r,
+                      int[] c,
+                      <a href="../Jama/Matrix.html">Matrix</a> X)</pre>
+<dl>
+<dd>Set a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+r</code>
+ - Array of row indices.<dd><code>
+c</code>
+ - Array of column indices.<dd><code>
+X</code>
+ - A(r(:),c(:))<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="setMatrix(int[], int, int, Jama.Matrix)"><!-- --></a><h3>
+setMatrix</h3>
+<pre>
+public void <b>setMatrix</b>(int[] r,
+                      int j0,
+                      int j1,
+                      <a href="../Jama/Matrix.html">Matrix</a> X)</pre>
+<dl>
+<dd>Set a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+r</code>
+ - Array of row indices.<dd><code>
+j0</code>
+ - Initial column index<dd><code>
+j1</code>
+ - Final column index<dd><code>
+X</code>
+ - A(r(:),j0:j1)<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="setMatrix(int, int, int[], Jama.Matrix)"><!-- --></a><h3>
+setMatrix</h3>
+<pre>
+public void <b>setMatrix</b>(int i0,
+                      int i1,
+                      int[] c,
+                      <a href="../Jama/Matrix.html">Matrix</a> X)</pre>
+<dl>
+<dd>Set a submatrix.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+i0</code>
+ - Initial row index<dd><code>
+i1</code>
+ - Final row index<dd><code>
+c</code>
+ - Array of column indices.<dd><code>
+X</code>
+ - A(i0:i1,c(:))<dt><b>Throws:</b><dd>ArrayIndexOutOfBoundsException - Submatrix indices</dl>
+</dd>
+</dl>
+<hr>
+<a name="transpose()"><!-- --></a><h3>
+transpose</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>transpose</b>()</pre>
+<dl>
+<dd>Matrix transpose.<dd><dl>
+<dt><b>Returns:</b><dd>A'</dl>
+</dd>
+</dl>
+<hr>
+<a name="norm1()"><!-- --></a><h3>
+norm1</h3>
+<pre>
+public double <b>norm1</b>()</pre>
+<dl>
+<dd>One norm<dd><dl>
+<dt><b>Returns:</b><dd>maximum column sum.</dl>
+</dd>
+</dl>
+<hr>
+<a name="norm2()"><!-- --></a><h3>
+norm2</h3>
+<pre>
+public double <b>norm2</b>()</pre>
+<dl>
+<dd>Two norm<dd><dl>
+<dt><b>Returns:</b><dd>maximum singular value.</dl>
+</dd>
+</dl>
+<hr>
+<a name="normInf()"><!-- --></a><h3>
+normInf</h3>
+<pre>
+public double <b>normInf</b>()</pre>
+<dl>
+<dd>Infinity norm<dd><dl>
+<dt><b>Returns:</b><dd>maximum row sum.</dl>
+</dd>
+</dl>
+<hr>
+<a name="normF()"><!-- --></a><h3>
+normF</h3>
+<pre>
+public double <b>normF</b>()</pre>
+<dl>
+<dd>Frobenius norm<dd><dl>
+<dt><b>Returns:</b><dd>sqrt of sum of squares of all elements.</dl>
+</dd>
+</dl>
+<hr>
+<a name="uminus()"><!-- --></a><h3>
+uminus</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>uminus</b>()</pre>
+<dl>
+<dd>Unary minus<dd><dl>
+<dt><b>Returns:</b><dd>-A</dl>
+</dd>
+</dl>
+<hr>
+<a name="plus(Jama.Matrix)"><!-- --></a><h3>
+plus</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>plus</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>C = A + B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A + B</dl>
+</dd>
+</dl>
+<hr>
+<a name="plusEquals(Jama.Matrix)"><!-- --></a><h3>
+plusEquals</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>plusEquals</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>A = A + B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A + B</dl>
+</dd>
+</dl>
+<hr>
+<a name="minus(Jama.Matrix)"><!-- --></a><h3>
+minus</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>minus</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>C = A - B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A - B</dl>
+</dd>
+</dl>
+<hr>
+<a name="minusEquals(Jama.Matrix)"><!-- --></a><h3>
+minusEquals</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>minusEquals</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>A = A - B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A - B</dl>
+</dd>
+</dl>
+<hr>
+<a name="arrayTimes(Jama.Matrix)"><!-- --></a><h3>
+arrayTimes</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>arrayTimes</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Element-by-element multiplication, C = A.*B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A.*B</dl>
+</dd>
+</dl>
+<hr>
+<a name="arrayTimesEquals(Jama.Matrix)"><!-- --></a><h3>
+arrayTimesEquals</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>arrayTimesEquals</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Element-by-element multiplication in place, A = A.*B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A.*B</dl>
+</dd>
+</dl>
+<hr>
+<a name="arrayRightDivide(Jama.Matrix)"><!-- --></a><h3>
+arrayRightDivide</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>arrayRightDivide</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Element-by-element right division, C = A./B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A./B</dl>
+</dd>
+</dl>
+<hr>
+<a name="arrayRightDivideEquals(Jama.Matrix)"><!-- --></a><h3>
+arrayRightDivideEquals</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>arrayRightDivideEquals</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Element-by-element right division in place, A = A./B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A./B</dl>
+</dd>
+</dl>
+<hr>
+<a name="arrayLeftDivide(Jama.Matrix)"><!-- --></a><h3>
+arrayLeftDivide</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>arrayLeftDivide</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Element-by-element left division, C = A.\B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A.\B</dl>
+</dd>
+</dl>
+<hr>
+<a name="arrayLeftDivideEquals(Jama.Matrix)"><!-- --></a><h3>
+arrayLeftDivideEquals</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>arrayLeftDivideEquals</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Element-by-element left division in place, A = A.\B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>A.\B</dl>
+</dd>
+</dl>
+<hr>
+<a name="times(double)"><!-- --></a><h3>
+times</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>times</b>(double s)</pre>
+<dl>
+<dd>Multiply a matrix by a scalar, C = s*A<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+s</code>
+ - scalar<dt><b>Returns:</b><dd>s*A</dl>
+</dd>
+</dl>
+<hr>
+<a name="timesEquals(double)"><!-- --></a><h3>
+timesEquals</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>timesEquals</b>(double s)</pre>
+<dl>
+<dd>Multiply a matrix by a scalar in place, A = s*A<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+s</code>
+ - scalar<dt><b>Returns:</b><dd>replace A by s*A</dl>
+</dd>
+</dl>
+<hr>
+<a name="times(Jama.Matrix)"><!-- --></a><h3>
+times</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>times</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Linear algebraic matrix multiplication, A * B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - another matrix<dt><b>Returns:</b><dd>Matrix product, A * B<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - Matrix inner dimensions must agree.</dl>
+</dd>
+</dl>
+<hr>
+<a name="lu()"><!-- --></a><h3>
+lu</h3>
+<pre>
+public <a href="../Jama/LUDecomposition.html">LUDecomposition</a> <b>lu</b>()</pre>
+<dl>
+<dd>LU Decomposition<dd><dl>
+<dt><b>Returns:</b><dd>LUDecomposition<dt><b>See Also:</b><dd><a href="../Jama/LUDecomposition.html">LUDecomposition</a></dl>
+</dd>
+</dl>
+<hr>
+<a name="qr()"><!-- --></a><h3>
+qr</h3>
+<pre>
+public <a href="../Jama/QRDecomposition.html">QRDecomposition</a> <b>qr</b>()</pre>
+<dl>
+<dd>QR Decomposition<dd><dl>
+<dt><b>Returns:</b><dd>QRDecomposition<dt><b>See Also:</b><dd><a href="../Jama/QRDecomposition.html">QRDecomposition</a></dl>
+</dd>
+</dl>
+<hr>
+<a name="chol()"><!-- --></a><h3>
+chol</h3>
+<pre>
+public <a href="../Jama/CholeskyDecomposition.html">CholeskyDecomposition</a> <b>chol</b>()</pre>
+<dl>
+<dd>Cholesky Decomposition<dd><dl>
+<dt><b>Returns:</b><dd>CholeskyDecomposition<dt><b>See Also:</b><dd><a href="../Jama/CholeskyDecomposition.html">CholeskyDecomposition</a></dl>
+</dd>
+</dl>
+<hr>
+<a name="svd()"><!-- --></a><h3>
+svd</h3>
+<pre>
+public <a href="../Jama/SingularValueDecomposition.html">SingularValueDecomposition</a> <b>svd</b>()</pre>
+<dl>
+<dd>Singular Value Decomposition<dd><dl>
+<dt><b>Returns:</b><dd>SingularValueDecomposition<dt><b>See Also:</b><dd><a href="../Jama/SingularValueDecomposition.html">SingularValueDecomposition</a></dl>
+</dd>
+</dl>
+<hr>
+<a name="eig()"><!-- --></a><h3>
+eig</h3>
+<pre>
+public <a href="../Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a> <b>eig</b>()</pre>
+<dl>
+<dd>Eigenvalue Decomposition<dd><dl>
+<dt><b>Returns:</b><dd>EigenvalueDecomposition<dt><b>See Also:</b><dd><a href="../Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a></dl>
+</dd>
+</dl>
+<hr>
+<a name="solve(Jama.Matrix)"><!-- --></a><h3>
+solve</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>solve</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Solve A*X = B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - right hand side<dt><b>Returns:</b><dd>solution if A is square, least squares solution otherwise</dl>
+</dd>
+</dl>
+<hr>
+<a name="solveTranspose(Jama.Matrix)"><!-- --></a><h3>
+solveTranspose</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>solveTranspose</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Solve X*A = B, which is also A'*X' = B'<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - right hand side<dt><b>Returns:</b><dd>solution if A is square, least squares solution otherwise.</dl>
+</dd>
+</dl>
+<hr>
+<a name="inverse()"><!-- --></a><h3>
+inverse</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>inverse</b>()</pre>
+<dl>
+<dd>Matrix inverse or pseudoinverse<dd><dl>
+<dt><b>Returns:</b><dd>inverse(A) if A is square, pseudoinverse otherwise.</dl>
+</dd>
+</dl>
+<hr>
+<a name="det()"><!-- --></a><h3>
+det</h3>
+<pre>
+public double <b>det</b>()</pre>
+<dl>
+<dd>Matrix determinant<dd><dl>
+<dt><b>Returns:</b><dd>determinant</dl>
+</dd>
+</dl>
+<hr>
+<a name="rank()"><!-- --></a><h3>
+rank</h3>
+<pre>
+public int <b>rank</b>()</pre>
+<dl>
+<dd>Matrix rank<dd><dl>
+<dt><b>Returns:</b><dd>effective numerical rank, obtained from SVD.</dl>
+</dd>
+</dl>
+<hr>
+<a name="cond()"><!-- --></a><h3>
+cond</h3>
+<pre>
+public double <b>cond</b>()</pre>
+<dl>
+<dd>Matrix condition (2 norm)<dd><dl>
+<dt><b>Returns:</b><dd>ratio of largest to smallest singular value.</dl>
+</dd>
+</dl>
+<hr>
+<a name="trace()"><!-- --></a><h3>
+trace</h3>
+<pre>
+public double <b>trace</b>()</pre>
+<dl>
+<dd>Matrix trace.<dd><dl>
+<dt><b>Returns:</b><dd>sum of the diagonal elements.</dl>
+</dd>
+</dl>
+<hr>
+<a name="random(int, int)"><!-- --></a><h3>
+random</h3>
+<pre>
+public static <a href="../Jama/Matrix.html">Matrix</a> <b>random</b>(int m,
+                            int n)</pre>
+<dl>
+<dd>Generate matrix with random elements<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+m</code>
+ - Number of rows.<dd><code>
+n</code>
+ - Number of colums.<dt><b>Returns:</b><dd>An m-by-n matrix with uniformly distributed random elements.</dl>
+</dd>
+</dl>
+<hr>
+<a name="identity(int, int)"><!-- --></a><h3>
+identity</h3>
+<pre>
+public static <a href="../Jama/Matrix.html">Matrix</a> <b>identity</b>(int m,
+                              int n)</pre>
+<dl>
+<dd>Generate identity matrix<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+m</code>
+ - Number of rows.<dd><code>
+n</code>
+ - Number of colums.<dt><b>Returns:</b><dd>An m-by-n matrix with ones on the diagonal and zeros elsewhere.</dl>
+</dd>
+</dl>
+<hr>
+<a name="print(int, int)"><!-- --></a><h3>
+print</h3>
+<pre>
+public void <b>print</b>(int w,
+                  int d)</pre>
+<dl>
+<dd>Print the matrix to stdout.   Line the elements up in columns
+ with a Fortran-like 'Fw.d' style format.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+w</code>
+ - Column width.<dd><code>
+d</code>
+ - Number of digits after the decimal.</dl>
+</dd>
+</dl>
+<hr>
+<a name="print(java.io.PrintWriter, int, int)"><!-- --></a><h3>
+print</h3>
+<pre>
+public void <b>print</b>(java.io.PrintWriter output,
+                  int w,
+                  int d)</pre>
+<dl>
+<dd>Print the matrix to the output stream.   Line the elements up in
+ columns with a Fortran-like 'Fw.d' style format.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+output</code>
+ - Output stream.<dd><code>
+w</code>
+ - Column width.<dd><code>
+d</code>
+ - Number of digits after the decimal.</dl>
+</dd>
+</dl>
+<hr>
+<a name="print(java.text.NumberFormat, int)"><!-- --></a><h3>
+print</h3>
+<pre>
+public void <b>print</b>(java.text.NumberFormat format,
+                  int width)</pre>
+<dl>
+<dd>Print the matrix to stdout.  Line the elements up in columns.
+ Use the format object, and right justify within columns of width
+ characters.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+format</code>
+ - A  Formatting object for individual elements.<dd><code>
+width</code>
+ - Field width for each column.</dl>
+</dd>
+</dl>
+<hr>
+<a name="print(java.io.PrintWriter, java.text.NumberFormat, int)"><!-- --></a><h3>
+print</h3>
+<pre>
+public void <b>print</b>(java.io.PrintWriter output,
+                  java.text.NumberFormat format,
+                  int width)</pre>
+<dl>
+<dd>Print the matrix to the output stream.  Line the elements up in columns.
+ Use the format object, and right justify within columns of width
+ characters.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+output</code>
+ - the output stream.<dd><code>
+format</code>
+ - A formatting object to format the matrix elements<dd><code>
+width</code>
+ - Column width.</dl>
+</dd>
+</dl>
+<hr>
+<a name="read(java.io.BufferedReader)"><!-- --></a><h3>
+read</h3>
+<pre>
+public static <a href="../Jama/Matrix.html">Matrix</a> <b>read</b>(java.io.BufferedReader input)
+                       throws java.io.IOException</pre>
+<dl>
+<dd>Read a matrix from a stream.  The format is the same the print method,
+ so printed matrices can be read back in.  Elements are separated by
+ whitespace, all the elements for each row appear on a single line,
+ the last row is followed by a blank line.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+input</code>
+ - the input stream.</dl>
+</dd>
+</dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="LUDecomposition.html">PREV CLASS</a> | <a href="QRDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="Matrix.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/QRDecomposition.html b/Jama/doc/Jama/QRDecomposition.html
new file mode 100644
index 0000000..87c2739
--- /dev/null
+++ b/Jama/doc/Jama/QRDecomposition.html
@@ -0,0 +1,266 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:42 EDT 1998
+-->
+<title>
+Class Jama.QRDecomposition
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="Matrix.html">PREV CLASS</a> | <a href="SingularValueDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="QRDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.QRDecomposition
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.QRDecomposition</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>QRDecomposition</b><dt>extends java.lang.Object<dt>implements java.io.Serializable</dl>
+QR Decomposition.
+<P>
+For an m-by-n matrix A with m >= n, the QR decomposition is an m-by-n
+orthogonal matrix Q and an n-by-n upper triangular matrix R so that
+A = Q*R.
+<P>
+The QR decompostion always exists, even if the matrix does not have
+full rank, so the constructor will never fail.  The primary use of the
+QR decomposition is in the least squares solution of nonsquare systems
+of simultaneous linear equations.  This will fail if isFullRank()
+returns false.
+<p>
+<dl>
+<dt><b>See Also:</b><dd><a href="../serializedform.html#Jama.QRDecomposition">Serialized Form</a></dl>
+<hr>
+
+<p>
+<a name="constructor_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Constructor Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/QRDecomposition.html#QRDecomposition(Jama.Matrix)">QRDecomposition</a></b>(<a href="../Jama/Matrix.html">Matrix</a> A)
+<br>
+          QR Decomposition, computed by Householder reflections.</td>
+</tr>
+</table>
+ <a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/QRDecomposition.html#getH()">getH</a></b>()
+<br>
+          Return the Householder vectors</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/QRDecomposition.html#getQ()">getQ</a></b>()
+<br>
+          Generate and return the (economy-sized) orthogonal factor</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/QRDecomposition.html#getR()">getR</a></b>()
+<br>
+          Return the upper triangular factor</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+boolean</font>
+</td>
+<td><b><a href="../Jama/QRDecomposition.html#isFullRank()">isFullRank</a></b>()
+<br>
+          Is the matrix full rank?</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/QRDecomposition.html#solve(Jama.Matrix)">solve</a></b>(<a href="../Jama/Matrix.html">Matrix</a> B)
+<br>
+          Least squares solution of A*X = B</td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="constructor_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Constructor Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="QRDecomposition(Jama.Matrix)"><!-- --></a><h3>
+QRDecomposition</h3>
+<pre>
+public <b>QRDecomposition</b>(<a href="../Jama/Matrix.html">Matrix</a> A)</pre>
+<dl>
+<dd>QR Decomposition, computed by Householder reflections.<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Rectangular matrix</dl>
+</dd>
+</dl>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="isFullRank()"><!-- --></a><h3>
+isFullRank</h3>
+<pre>
+public boolean <b>isFullRank</b>()</pre>
+<dl>
+<dd>Is the matrix full rank?<dd><dl>
+<dt><b>Returns:</b><dd>true if R, and hence A, has full rank.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getH()"><!-- --></a><h3>
+getH</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getH</b>()</pre>
+<dl>
+<dd>Return the Householder vectors<dd><dl>
+<dt><b>Returns:</b><dd>Lower trapezoidal matrix whose columns define the reflections</dl>
+</dd>
+</dl>
+<hr>
+<a name="getR()"><!-- --></a><h3>
+getR</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getR</b>()</pre>
+<dl>
+<dd>Return the upper triangular factor<dd><dl>
+<dt><b>Returns:</b><dd>R</dl>
+</dd>
+</dl>
+<hr>
+<a name="getQ()"><!-- --></a><h3>
+getQ</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getQ</b>()</pre>
+<dl>
+<dd>Generate and return the (economy-sized) orthogonal factor<dd><dl>
+<dt><b>Returns:</b><dd>Q</dl>
+</dd>
+</dl>
+<hr>
+<a name="solve(Jama.Matrix)"><!-- --></a><h3>
+solve</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>solve</b>(<a href="../Jama/Matrix.html">Matrix</a> B)</pre>
+<dl>
+<dd>Least squares solution of A*X = B<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+B</code>
+ - A Matrix with as many rows as A and any number of columns.<dt><b>Returns:</b><dd>X that minimizes the two norm of Q*R*X-B.<dt><b>Throws:</b><dd>java.lang.IllegalArgumentException - Matrix row dimensions must agree.<dd>java.lang.RuntimeException - Matrix is rank deficient.</dl>
+</dd>
+</dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="Matrix.html">PREV CLASS</a> | <a href="SingularValueDecomposition.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="QRDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/SingularValueDecomposition.html b/Jama/doc/Jama/SingularValueDecomposition.html
new file mode 100644
index 0000000..279fee0
--- /dev/null
+++ b/Jama/doc/Jama/SingularValueDecomposition.html
@@ -0,0 +1,301 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:42 EDT 1998
+-->
+<title>
+Class Jama.SingularValueDecomposition
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="QRDecomposition.html">PREV CLASS</a> | NEXT CLASS</font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="SingularValueDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.SingularValueDecomposition
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.SingularValueDecomposition</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>SingularValueDecomposition</b><dt>extends java.lang.Object<dt>implements java.io.Serializable</dl>
+Singular Value Decomposition.
+<P>
+For an m-by-n matrix A with m >= n, the singular value decomposition is
+an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
+an n-by-n orthogonal matrix V so that A = U*S*V'.
+<P>
+The singular values, sigma[k] = S[k][k], are ordered so that
+sigma[0] >= sigma[1] >= ... >= sigma[n-1].
+<P>
+The singular value decompostion always exists, so the constructor will
+never fail.  The matrix condition number and the effective numerical
+rank can be computed from this decomposition.
+<p>
+<dl>
+<dt><b>See Also:</b><dd><a href="../serializedform.html#Jama.SingularValueDecomposition">Serialized Form</a></dl>
+<hr>
+
+<p>
+<a name="constructor_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Constructor Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td><b><a href="../Jama/SingularValueDecomposition.html#SingularValueDecomposition(Jama.Matrix)">SingularValueDecomposition</a></b>(<a href="../Jama/Matrix.html">Matrix</a> Arg)
+<br>
+          Construct the singular value decomposition</td>
+</tr>
+</table>
+ <a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/SingularValueDecomposition.html#cond()">cond</a></b>()
+<br>
+          Two norm condition number</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double[]</font>
+</td>
+<td><b><a href="../Jama/SingularValueDecomposition.html#getSingularValues()">getSingularValues</a></b>()
+<br>
+          Return the one-dimensional array of singular values</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/SingularValueDecomposition.html#getS()">getS</a></b>()
+<br>
+          Return the diagonal matrix of singular values</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/SingularValueDecomposition.html#getU()">getU</a></b>()
+<br>
+          Return the left singular vectors</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+<a href="../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../Jama/SingularValueDecomposition.html#getV()">getV</a></b>()
+<br>
+          Return the right singular vectors</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+double</font>
+</td>
+<td><b><a href="../Jama/SingularValueDecomposition.html#norm2()">norm2</a></b>()
+<br>
+          Two norm</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+int</font>
+</td>
+<td><b><a href="../Jama/SingularValueDecomposition.html#rank()">rank</a></b>()
+<br>
+          Effective numerical matrix rank</td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="constructor_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Constructor Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="SingularValueDecomposition(Jama.Matrix)"><!-- --></a><h3>
+SingularValueDecomposition</h3>
+<pre>
+public <b>SingularValueDecomposition</b>(<a href="../Jama/Matrix.html">Matrix</a> Arg)</pre>
+<dl>
+<dd>Construct the singular value decomposition<dd><dl>
+<dt><b>Parameters:</b><dd><code>
+A</code>
+ - Rectangular matrix</dl>
+</dd>
+</dl>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="getU()"><!-- --></a><h3>
+getU</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getU</b>()</pre>
+<dl>
+<dd>Return the left singular vectors<dd><dl>
+<dt><b>Returns:</b><dd>U</dl>
+</dd>
+</dl>
+<hr>
+<a name="getV()"><!-- --></a><h3>
+getV</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getV</b>()</pre>
+<dl>
+<dd>Return the right singular vectors<dd><dl>
+<dt><b>Returns:</b><dd>V</dl>
+</dd>
+</dl>
+<hr>
+<a name="getSingularValues()"><!-- --></a><h3>
+getSingularValues</h3>
+<pre>
+public double[] <b>getSingularValues</b>()</pre>
+<dl>
+<dd>Return the one-dimensional array of singular values<dd><dl>
+<dt><b>Returns:</b><dd>diagonal of S.</dl>
+</dd>
+</dl>
+<hr>
+<a name="getS()"><!-- --></a><h3>
+getS</h3>
+<pre>
+public <a href="../Jama/Matrix.html">Matrix</a> <b>getS</b>()</pre>
+<dl>
+<dd>Return the diagonal matrix of singular values<dd><dl>
+<dt><b>Returns:</b><dd>S</dl>
+</dd>
+</dl>
+<hr>
+<a name="norm2()"><!-- --></a><h3>
+norm2</h3>
+<pre>
+public double <b>norm2</b>()</pre>
+<dl>
+<dd>Two norm<dd><dl>
+<dt><b>Returns:</b><dd>max(S)</dl>
+</dd>
+</dl>
+<hr>
+<a name="cond()"><!-- --></a><h3>
+cond</h3>
+<pre>
+public double <b>cond</b>()</pre>
+<dl>
+<dd>Two norm condition number<dd><dl>
+<dt><b>Returns:</b><dd>max(S)/min(S)</dl>
+</dd>
+</dl>
+<hr>
+<a name="rank()"><!-- --></a><h3>
+rank</h3>
+<pre>
+public int <b>rank</b>()</pre>
+<dl>
+<dd>Effective numerical matrix rank<dd><dl>
+<dt><b>Returns:</b><dd>Number of nonnegligible singular values.</dl>
+</dd>
+</dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="QRDecomposition.html">PREV CLASS</a> | NEXT CLASS</font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="SingularValueDecomposition.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | <a href="#constructor_summary">CONSTR</a> | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | <a href="#constructor_detail">CONSTR</a> | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/examples/MagicSquareExample.html b/Jama/doc/Jama/examples/MagicSquareExample.html
new file mode 100644
index 0000000..9b55eac
--- /dev/null
+++ b/Jama/doc/Jama/examples/MagicSquareExample.html
@@ -0,0 +1,196 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Class Jama.examples.MagicSquareExample
+</title>
+<link rel ="stylesheet" type="text/css" href="../../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="../../tree.html">Tree</a> | <a href="../../index-all.html">Index</a> | <a href="../../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV CLASS | <a href="Maths.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../../index.html" target="_top">FRAMES</a>
+ | <a href="MagicSquareExample.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | CONSTR | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | CONSTR | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.examples.MagicSquareExample
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.examples.MagicSquareExample</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>MagicSquareExample</b><dt>extends java.lang.Object</dl>
+Example of use of Matrix Class, featuring magic squares.
+<p>
+<hr>
+
+<p>
+<a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static java.lang.String</font>
+</td>
+<td><b><a href="../../Jama/examples/MagicSquareExample.html#fixedWidthDoubletoString(double, int, int)">fixedWidthDoubletoString</a></b>(double x,
+                         int w,
+                         int d)
+<br>
+          Format double with Fw.d.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static java.lang.String</font>
+</td>
+<td><b><a href="../../Jama/examples/MagicSquareExample.html#fixedWidthIntegertoString(int, int)">fixedWidthIntegertoString</a></b>(int n,
+                          int w)
+<br>
+          Format integer with Iw.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static <a href="../../Jama/Matrix.html">Matrix</a></font>
+</td>
+<td><b><a href="../../Jama/examples/MagicSquareExample.html#magic(int)">magic</a></b>(int n)
+<br>
+          Generate magic square test matrix.</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static void</font>
+</td>
+<td><b><a href="../../Jama/examples/MagicSquareExample.html#main(java.lang.String[])">main</a></b>(java.lang.String[] argv)
+<br>
+           </td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="magic(int)"><!-- --></a><h3>
+magic</h3>
+<pre>
+public static <a href="../../Jama/Matrix.html">Matrix</a> <b>magic</b>(int n)</pre>
+<dl>
+<dd>Generate magic square test matrix.</dl>
+<hr>
+<a name="fixedWidthDoubletoString(double, int, int)"><!-- --></a><h3>
+fixedWidthDoubletoString</h3>
+<pre>
+public static java.lang.String <b>fixedWidthDoubletoString</b>(double x,
+                                              int w,
+                                              int d)</pre>
+<dl>
+<dd>Format double with Fw.d.</dl>
+<hr>
+<a name="fixedWidthIntegertoString(int, int)"><!-- --></a><h3>
+fixedWidthIntegertoString</h3>
+<pre>
+public static java.lang.String <b>fixedWidthIntegertoString</b>(int n,
+                                               int w)</pre>
+<dl>
+<dd>Format integer with Iw.</dl>
+<hr>
+<a name="main(java.lang.String[])"><!-- --></a><h3>
+main</h3>
+<pre>
+public static void <b>main</b>(java.lang.String[] argv)</pre>
+<dl>
+<dd></dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="../../tree.html">Tree</a> | <a href="../../index-all.html">Index</a> | <a href="../../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV CLASS | <a href="Maths.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../../index.html" target="_top">FRAMES</a>
+ | <a href="MagicSquareExample.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | CONSTR | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | CONSTR | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/package-frame.html b/Jama/doc/Jama/package-frame.html
new file mode 100644
index 0000000..3bef46e
--- /dev/null
+++ b/Jama/doc/Jama/package-frame.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Package Jama
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<font size="+1" class="FrameTitleFont">
+Jama</font>
+
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td nowrap><font size="+1" class="FrameHeadingFont">
+Classes</font>
+ 
+<font class="FrameItemFont">
+
+<br>
+<a href="CholeskyDecomposition.html" target="classFrame">CholeskyDecomposition</a>
+
+<br>
+<a href="EigenvalueDecomposition.html" target="classFrame">EigenvalueDecomposition</a>
+
+<br>
+<a href="LUDecomposition.html" target="classFrame">LUDecomposition</a>
+
+<br>
+<a href="Matrix.html" target="classFrame">Matrix</a>
+
+<br>
+<a href="QRDecomposition.html" target="classFrame">QRDecomposition</a>
+
+<br>
+<a href="SingularValueDecomposition.html" target="classFrame">SingularValueDecomposition</a>
+</font>
+</td>
+</tr>
+</table>
+
+</body>
+</html>
diff --git a/Jama/doc/Jama/package-summary.html b/Jama/doc/Jama/package-summary.html
new file mode 100644
index 0000000..5cf4845
--- /dev/null
+++ b/Jama/doc/Jama/package-summary.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Package Jama
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <b>Package</b> | Class | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV PACKAGE | NEXT PACKAGE</font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="package-summary.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+<h2>
+Package Jama
+</h2>
+
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Class Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td width="15%"><b><a href="CholeskyDecomposition.html">CholeskyDecomposition</a></b></td>
+<td>Cholesky Decomposition.
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td width="15%"><b><a href="EigenvalueDecomposition.html">EigenvalueDecomposition</a></b></td>
+<td>Eigenvalues and eigenvectors of a real matrix. 
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td width="15%"><b><a href="LUDecomposition.html">LUDecomposition</a></b></td>
+<td>LU Decomposition.
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td width="15%"><b><a href="Matrix.html">Matrix</a></b></td>
+<td>Jama = Java Matrix class.
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td width="15%"><b><a href="QRDecomposition.html">QRDecomposition</a></b></td>
+<td>QR Decomposition.
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td width="15%"><b><a href="SingularValueDecomposition.html">SingularValueDecomposition</a></b></td>
+<td>Singular Value Decomposition.
+</td>
+</tr>
+</table>
+ 
+
+<p>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <b>Package</b> | Class | <a href="tree.html">Tree</a> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV PACKAGE | NEXT PACKAGE</font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="package-summary.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/test/TestMatrix.html b/Jama/doc/Jama/test/TestMatrix.html
new file mode 100644
index 0000000..c2cd159
--- /dev/null
+++ b/Jama/doc/Jama/test/TestMatrix.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Class Jama.test.TestMatrix
+</title>
+<link rel ="stylesheet" type="text/css" href="../../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="../../tree.html">Tree</a> | <a href="../../index-all.html">Index</a> | <a href="../../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="Maths.html">PREV CLASS</a> | NEXT CLASS</font>
+</td>
+<td><font size="-2">
+<a href="../../index.html" target="_top">FRAMES</a>
+ | <a href="TestMatrix.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | CONSTR | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | CONSTR | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.test.TestMatrix
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.test.TestMatrix</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>TestMatrix</b><dt>extends java.lang.Object</dl>
+TestMatrix tests the functionality of the Jama Matrix class and associated decompositions.
+<P>
+Run the test from the command line using
+<BLOCKQUOTE><PRE><CODE>
+java Jama.test.TestMatrix 
+</CODE></PRE></BLOCKQUOTE>
+Detailed output is provided indicating the functionality being tested
+and whether the functionality is correctly implemented.   Exception handling
+is also tested.  
+<P>
+The test is designed to run to completion and give a summary of any implementation errors
+encountered. The final output should be:
+<BLOCKQUOTE><PRE><CODE>
+TestMatrix completed.
+Total errors reported: n1
+Total warning reported: n2
+</CODE></PRE></BLOCKQUOTE>
+If the test does not run to completion, this indicates that there is a 
+substantial problem within the implementation that was not anticipated in the test design.  
+The stopping point should give an indication of where the problem exists.
+<p>
+<hr>
+
+<p>
+<a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static void</font>
+</td>
+<td><b><a href="../../Jama/test/TestMatrix.html#main(java.lang.String[])">main</a></b>(java.lang.String[] argv)
+<br>
+           </td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="main(java.lang.String[])"><!-- --></a><h3>
+main</h3>
+<pre>
+public static void <b>main</b>(java.lang.String[] argv)</pre>
+<dl>
+<dd></dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="../../tree.html">Tree</a> | <a href="../../index-all.html">Index</a> | <a href="../../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="Maths.html">PREV CLASS</a> | NEXT CLASS</font>
+</td>
+<td><font size="-2">
+<a href="../../index.html" target="_top">FRAMES</a>
+ | <a href="TestMatrix.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | CONSTR | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | CONSTR | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/tree.html b/Jama/doc/Jama/tree.html
new file mode 100644
index 0000000..6cc12fb
--- /dev/null
+++ b/Jama/doc/Jama/tree.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Jama Class Hierarchy
+</title>
+<link rel ="stylesheet" type="text/css" href="../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | Class | <b>Tree</b> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="tree.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+<center>
+<h2>
+Hierarchy For Package Jama
+</h2>
+</center>
+<dl>
+<dt><b>Package Hierarchies:</b><dd><a href="../tree.html">AllPackages</a></dl>
+<hr>
+<h2>
+Class Hierarchy
+</h2>
+<ul>
+<li type="circle">class java.lang.Object<ul>
+<li type="circle">class Jama.<a href="../Jama/CholeskyDecomposition.html"><b>CholeskyDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.<a href="../Jama/EigenvalueDecomposition.html"><b>EigenvalueDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.<a href="../Jama/LUDecomposition.html"><b>LUDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.<a href="../Jama/Matrix.html"><b>Matrix</b></a>(implements java.lang.Cloneable, java.io.Serializable)
+<li type="circle">class Jama.<a href="../Jama/QRDecomposition.html"><b>QRDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.<a href="../Jama/SingularValueDecomposition.html"><b>SingularValueDecomposition</b></a>(implements java.io.Serializable)
+</ul>
+</ul>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | Class | <b>Tree</b> | <a href="../index-all.html">Index</a> | <a href="../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="../index.html" target="_top">FRAMES</a>
+ | <a href="tree.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/Jama/util/Maths.html b/Jama/doc/Jama/util/Maths.html
new file mode 100644
index 0000000..338fe42
--- /dev/null
+++ b/Jama/doc/Jama/util/Maths.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Class Jama.util.Maths
+</title>
+<link rel ="stylesheet" type="text/css" href="../../stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="../../tree.html">Tree</a> | <a href="../../index-all.html">Index</a> | <a href="../../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="MagicSquareExample.html">PREV CLASS</a> | <a href="TestMatrix.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../../index.html" target="_top">FRAMES</a>
+ | <a href="Maths.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | CONSTR | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | CONSTR | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+<h2>
+Class Jama.util.Maths
+</h2>
+<pre>
+java.lang.Object
+  |
+  +--<b>Jama.util.Maths</b>
+</pre>
+<hr>
+<dl>
+<dt>public class <b>Maths</b><dt>extends java.lang.Object</dl>
+<hr>
+
+<p>
+<a name="method_summary"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Method Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td align=right valign=top width=1%><font size="-1">
+static double</font>
+</td>
+<td><b><a href="../../Jama/util/Maths.html#hypot(double, double)">hypot</a></b>(double a,
+      double b)
+<br>
+          sqrt(a^2 + b^2) without under/overflow.</td>
+</tr>
+</table>
+ <a name="methods_inherited_from_class_java.lang.Object"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#EEEEFF" class="TableInheritedHeadingColor">
+<td colspan=2><b>Methods inherited from class java.lang.Object</b></td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td>clone, equals, finalize, getClass, hashCode, notifyAll, notify, toString, wait, wait, wait</td>
+</tr>
+</table>
+ 
+<p>
+<a name="method_detail"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Method Detail</b></font>
+</td>
+</tr>
+</table>
+<a name="hypot(double, double)"><!-- --></a><h3>
+hypot</h3>
+<pre>
+public static double <b>hypot</b>(double a,
+                           double b)</pre>
+<dl>
+<dd>sqrt(a^2 + b^2) without under/overflow.</dl>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="../../overview-summary.html">Overview</a> | <a href="package-summary.html">Package</a> | <b>Class</b> | <a href="../../tree.html">Tree</a> | <a href="../../index-all.html">Index</a> | <a href="../../help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+<a href="MagicSquareExample.html">PREV CLASS</a> | <a href="TestMatrix.html">NEXT CLASS</a></font>
+</td>
+<td><font size="-2">
+<a href="../../index.html" target="_top">FRAMES</a>
+ | <a href="Maths.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td valign=top><font size="-2">
+SUMMARY:  INNER | FIELD | CONSTR | <a href="#method_summary">METHOD</a></font>
+</td>
+<td valign=top><font size="-2">
+DETAIL:  FIELD | CONSTR | <a href="#method_detail">METHOD</a></font>
+</td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/allclasses-frame.html b/Jama/doc/allclasses-frame.html
new file mode 100644
index 0000000..13fff00
--- /dev/null
+++ b/Jama/doc/allclasses-frame.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+All Classes
+</title>
+<link rel ="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<font size="+1" class="FrameHeadingFont">
+<b>All Classes</b></font>
+
+<br>
+
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td nowrap><font class="FrameItemFont">
+<a href="Jama/CholeskyDecomposition.html" target="classFrame">CholeskyDecomposition</a>
+
+<br>
+<a href="Jama/EigenvalueDecomposition.html" target="classFrame">EigenvalueDecomposition</a>
+
+<br>
+<a href="Jama/LUDecomposition.html" target="classFrame">LUDecomposition</a>
+
+<br>
+<a href="Jama/examples/MagicSquareExample.html" target="classFrame">MagicSquareExample</a>
+
+<br>
+<a href="Jama/util/Maths.html" target="classFrame">Maths</a>
+
+<br>
+<a href="Jama/Matrix.html" target="classFrame">Matrix</a>
+
+<br>
+<a href="Jama/QRDecomposition.html" target="classFrame">QRDecomposition</a>
+
+<br>
+<a href="Jama/SingularValueDecomposition.html" target="classFrame">SingularValueDecomposition</a>
+
+<br>
+<a href="Jama/test/TestMatrix.html" target="classFrame">TestMatrix</a>
+
+<br>
+</font>
+</td>
+</tr>
+</table>
+</body>
+</html>
diff --git a/Jama/doc/help.html b/Jama/doc/help.html
new file mode 100644
index 0000000..a419d89
--- /dev/null
+++ b/Jama/doc/help.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:42 EDT 1998
+-->
+<title>
+API Help
+</title>
+<link rel ="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <a href="tree.html">Tree</a> | <a href="index-all.html">Index</a> | <b>Help</b></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="help.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+<center>
+<h1>
+How This API Document Is Organized</h1>
+</center>
+This API document has an Overview page, a page for each Package, a page for each Class and Interface, plus a set of class hierarchies, a deprecated API list, and an index of API.<h3>
+Overview</h3>
+<blockquote>
+
+<p>
+The Overview page is the front page of this API document and provides a list of all packages. To the right of each package name is the first sentence from its package description. This page can also contain an overall description of the set of packages.</blockquote>
+<h3>
+Package</h3>
+<blockquote>
+
+<p>
+Each package has a web page that contains a links to its classes and interfaces. In addition, each class and interface contains the first sentence from the class or interface description. This page can contain four categories:<ul>
+<li>Interfaces<li>Classes<li>Exceptions<li>Errors</ul>
+</blockquote>
+<h3>
+Class (or Interface)</h3>
+<blockquote>
+
+<p>
+Each class and interface has a separate "class" web page. Note that each inner class has its own page. Each page contains a class description plus detailed descriptions of each of its inner classes, fields, constructors and methods, as follows:<ul>
+<li>Class inheritance diagram - This starts with the topmost class (<code>java.lang.Object</code>) and ends with the class or interface.<li>Subclasses - A list of all direct subclasses. (This may change to all direct and indirect subclasses in a later version of javadoc.)  Of course, any new subclasses generated outside of this document that someone creates will not appear in this list.<li>Implementing Classes - For an interface, a list of all classes that implement it.<li>Class declarat [...]
+<p>
+<li>Inner Class Summary<li>Field Summary<li>Constructor Summary<li>Method Summary
+<p>
+<li>Field Detail<li>Constructor Detail<li>Method Detail</ul>
+Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.</blockquote>
+<h3>
+Tree (Class Hierarchy)</h3>
+<blockquote>
+The <a href="tree.html">Class Hierarchy</a> contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with <code>java.lang.Object</code>. The interfaces are organized the same, but do not inherit from <code>java.lang.Object</code>.<ul>
+<li>If you are viewing the contents page, clicking on "Tree" displays the hierarchy for all packages.<li>If you are viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.</ul>
+</blockquote>
+<h3>
+Deprecated API</h3>
+<blockquote>
+The <a href="deprecatedlist.html">Deprecated API</a> page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. While deprecated APIs are currently still implemented, they may be removed in future implementations.</blockquote>
+<blockquote>
+The Index contains a list of all classes, interfaces, constructors, methods, and fields.</blockquote>
+<hr>
+<font size="-1">
+This help file applies to API documentation generated using the standard doclet. </font>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <a href="tree.html">Tree</a> | <a href="index-all.html">Index</a> | <b>Help</b></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="help.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+</body>
+</html>
diff --git a/Jama/doc/index-all.html b/Jama/doc/index-all.html
new file mode 100644
index 0000000..eed519c
--- /dev/null
+++ b/Jama/doc/index-all.html
@@ -0,0 +1,465 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Index
+</title>
+<link rel ="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <a href="tree.html">Tree</a> | <b>Index</b> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="index-all.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<a href="#_A_">A</a> <a href="#_C_">C</a> <a href="#_D_">D</a> <a href="#_E_">E</a> <a href="#_F_">F</a> <a href="#_G_">G</a> <a href="#_H_">H</a> <a href="#_I_">I</a> <a href="#_J_">J</a> <a href="#_L_">L</a> <a href="#_M_">M</a> <a href="#_N_">N</a> <a href="#_P_">P</a> <a href="#_Q_">Q</a> <a href="#_R_">R</a> <a href="#_S_">S</a> <a href="#_T_">T</a> <a href="#_U_">U</a> <hr>
+<a name="_A_"><!-- --></a><h2>
+<b>A</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#arrayLeftDivideEquals(Jama.Matrix)"><b>arrayLeftDivideEquals(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Element-by-element left division in place, A = A.\B
+<dt><a href="Jama/Matrix.html#arrayLeftDivide(Jama.Matrix)"><b>arrayLeftDivide(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Element-by-element left division, C = A.\B
+<dt><a href="Jama/Matrix.html#arrayRightDivideEquals(Jama.Matrix)"><b>arrayRightDivideEquals(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Element-by-element right division in place, A = A./B
+<dt><a href="Jama/Matrix.html#arrayRightDivide(Jama.Matrix)"><b>arrayRightDivide(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Element-by-element right division, C = A./B
+<dt><a href="Jama/Matrix.html#arrayTimesEquals(Jama.Matrix)"><b>arrayTimesEquals(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Element-by-element multiplication in place, A = A.*B
+<dt><a href="Jama/Matrix.html#arrayTimes(Jama.Matrix)"><b>arrayTimes(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Element-by-element multiplication, C = A.*B
+</dl>
+<hr>
+<a name="_C_"><!-- --></a><h2>
+<b>C</b></h2>
+<dl>
+<dt><a href="Jama/CholeskyDecomposition.html#CholeskyDecomposition(Jama.Matrix)"><b>CholeskyDecomposition(Matrix)</b></a> - 
+Constructor for class Jama.<a href="Jama/CholeskyDecomposition.html">CholeskyDecomposition</a>
+<dd>Cholesky algorithm for symmetric and positive definite matrix.
+<dt><a href="Jama/CholeskyDecomposition.html"><b>CholeskyDecomposition</b></a> - class Jama.<a href="Jama/CholeskyDecomposition.html">CholeskyDecomposition</a>.<dd>Cholesky Decomposition.
+<dt><a href="Jama/Matrix.html#chol()"><b>chol()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Cholesky Decomposition
+<dt><a href="Jama/Matrix.html#clone()"><b>clone()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Clone the Matrix object.
+<dt><a href="Jama/Matrix.html#cond()"><b>cond()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Matrix condition (2 norm)
+<dt><a href="Jama/SingularValueDecomposition.html#cond()"><b>cond()</b></a> - 
+Method in class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Two norm condition number
+<dt><a href="Jama/Matrix.html#constructWithCopy(double[][])"><b>constructWithCopy(double[][])</b></a> - 
+Static method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Construct a matrix from a copy of a 2-D array.
+<dt><a href="Jama/Matrix.html#copy()"><b>copy()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Make a deep copy of a matrix
+</dl>
+<hr>
+<a name="_D_"><!-- --></a><h2>
+<b>D</b></h2>
+<dl>
+<dt><a href="Jama/LUDecomposition.html#det()"><b>det()</b></a> - 
+Method in class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>Determinant
+<dt><a href="Jama/Matrix.html#det()"><b>det()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Matrix determinant
+</dl>
+<hr>
+<a name="_E_"><!-- --></a><h2>
+<b>E</b></h2>
+<dl>
+<dt><a href="Jama/EigenvalueDecomposition.html#EigenvalueDecomposition(Jama.Matrix)"><b>EigenvalueDecomposition(Matrix)</b></a> - 
+Constructor for class Jama.<a href="Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a>
+<dd>Check for symmetry, then construct the eigenvalue decomposition
+<dt><a href="Jama/EigenvalueDecomposition.html"><b>EigenvalueDecomposition</b></a> - class Jama.<a href="Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a>.<dd>Eigenvalues and eigenvectors of a real matrix. 
+<dt><a href="Jama/Matrix.html#eig()"><b>eig()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Eigenvalue Decomposition
+</dl>
+<hr>
+<a name="_F_"><!-- --></a><h2>
+<b>F</b></h2>
+<dl>
+<dt><a href="Jama/examples/MagicSquareExample.html#fixedWidthDoubletoString(double, int, int)"><b>fixedWidthDoubletoString(double, int, int)</b></a> - 
+Static method in class Jama.examples.<a href="Jama/examples/MagicSquareExample.html">MagicSquareExample</a>
+<dd>Format double with Fw.d.
+<dt><a href="Jama/examples/MagicSquareExample.html#fixedWidthIntegertoString(int, int)"><b>fixedWidthIntegertoString(int, int)</b></a> - 
+Static method in class Jama.examples.<a href="Jama/examples/MagicSquareExample.html">MagicSquareExample</a>
+<dd>Format integer with Iw.
+</dl>
+<hr>
+<a name="_G_"><!-- --></a><h2>
+<b>G</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#getArrayCopy()"><b>getArrayCopy()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Copy the internal two-dimensional array.
+<dt><a href="Jama/Matrix.html#getArray()"><b>getArray()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Access the internal two-dimensional array.
+<dt><a href="Jama/Matrix.html#getColumnDimension()"><b>getColumnDimension()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Get column dimension.
+<dt><a href="Jama/Matrix.html#getColumnPackedCopy()"><b>getColumnPackedCopy()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Make a one-dimensional column packed copy of the internal array.
+<dt><a href="Jama/EigenvalueDecomposition.html#getD()"><b>getD()</b></a> - 
+Method in class Jama.<a href="Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a>
+<dd>Return the block diagonal eigenvalue matrix
+<dt><a href="Jama/LUDecomposition.html#getDoublePivot()"><b>getDoublePivot()</b></a> - 
+Method in class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>Return pivot permutation vector as a one-dimensional double array
+<dt><a href="Jama/QRDecomposition.html#getH()"><b>getH()</b></a> - 
+Method in class Jama.<a href="Jama/QRDecomposition.html">QRDecomposition</a>
+<dd>Return the Householder vectors
+<dt><a href="Jama/EigenvalueDecomposition.html#getImagEigenvalues()"><b>getImagEigenvalues()</b></a> - 
+Method in class Jama.<a href="Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a>
+<dd>Return the imaginary parts of the eigenvalues
+<dt><a href="Jama/CholeskyDecomposition.html#getL()"><b>getL()</b></a> - 
+Method in class Jama.<a href="Jama/CholeskyDecomposition.html">CholeskyDecomposition</a>
+<dd>Return triangular factor.
+<dt><a href="Jama/LUDecomposition.html#getL()"><b>getL()</b></a> - 
+Method in class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>Return lower triangular factor
+<dt><a href="Jama/Matrix.html#get(int, int)"><b>get(int, int)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Get a single element.
+<dt><a href="Jama/Matrix.html#getMatrix(int, int, int, int)"><b>getMatrix(int, int, int, int)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Get a submatrix.
+<dt><a href="Jama/Matrix.html#getMatrix(int[], int[])"><b>getMatrix(int[], int[])</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Get a submatrix.
+<dt><a href="Jama/Matrix.html#getMatrix(int, int, int[])"><b>getMatrix(int, int, int[])</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Get a submatrix.
+<dt><a href="Jama/Matrix.html#getMatrix(int[], int, int)"><b>getMatrix(int[], int, int)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Get a submatrix.
+<dt><a href="Jama/LUDecomposition.html#getPivot()"><b>getPivot()</b></a> - 
+Method in class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>Return pivot permutation vector
+<dt><a href="Jama/QRDecomposition.html#getQ()"><b>getQ()</b></a> - 
+Method in class Jama.<a href="Jama/QRDecomposition.html">QRDecomposition</a>
+<dd>Generate and return the (economy-sized) orthogonal factor
+<dt><a href="Jama/EigenvalueDecomposition.html#getRealEigenvalues()"><b>getRealEigenvalues()</b></a> - 
+Method in class Jama.<a href="Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a>
+<dd>Return the real parts of the eigenvalues
+<dt><a href="Jama/Matrix.html#getRowDimension()"><b>getRowDimension()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Get row dimension.
+<dt><a href="Jama/Matrix.html#getRowPackedCopy()"><b>getRowPackedCopy()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Make a one-dimensional row packed copy of the internal array.
+<dt><a href="Jama/QRDecomposition.html#getR()"><b>getR()</b></a> - 
+Method in class Jama.<a href="Jama/QRDecomposition.html">QRDecomposition</a>
+<dd>Return the upper triangular factor
+<dt><a href="Jama/SingularValueDecomposition.html#getSingularValues()"><b>getSingularValues()</b></a> - 
+Method in class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Return the one-dimensional array of singular values
+<dt><a href="Jama/SingularValueDecomposition.html#getS()"><b>getS()</b></a> - 
+Method in class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Return the diagonal matrix of singular values
+<dt><a href="Jama/LUDecomposition.html#getU()"><b>getU()</b></a> - 
+Method in class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>Return upper triangular factor
+<dt><a href="Jama/SingularValueDecomposition.html#getU()"><b>getU()</b></a> - 
+Method in class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Return the left singular vectors
+<dt><a href="Jama/EigenvalueDecomposition.html#getV()"><b>getV()</b></a> - 
+Method in class Jama.<a href="Jama/EigenvalueDecomposition.html">EigenvalueDecomposition</a>
+<dd>Return the eigenvector matrix
+<dt><a href="Jama/SingularValueDecomposition.html#getV()"><b>getV()</b></a> - 
+Method in class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Return the right singular vectors
+</dl>
+<hr>
+<a name="_H_"><!-- --></a><h2>
+<b>H</b></h2>
+<dl>
+<dt><a href="Jama/util/Maths.html#hypot(double, double)"><b>hypot(double, double)</b></a> - 
+Static method in class Jama.util.<a href="Jama/util/Maths.html">Maths</a>
+<dd>sqrt(a^2 + b^2) without under/overflow.
+</dl>
+<hr>
+<a name="_I_"><!-- --></a><h2>
+<b>I</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#identity(int, int)"><b>identity(int, int)</b></a> - 
+Static method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Generate identity matrix
+<dt><a href="Jama/Matrix.html#inverse()"><b>inverse()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Matrix inverse or pseudoinverse
+<dt><a href="Jama/QRDecomposition.html#isFullRank()"><b>isFullRank()</b></a> - 
+Method in class Jama.<a href="Jama/QRDecomposition.html">QRDecomposition</a>
+<dd>Is the matrix full rank?
+<dt><a href="Jama/LUDecomposition.html#isNonsingular()"><b>isNonsingular()</b></a> - 
+Method in class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>Is the matrix nonsingular?
+<dt><a href="Jama/CholeskyDecomposition.html#isSPD()"><b>isSPD()</b></a> - 
+Method in class Jama.<a href="Jama/CholeskyDecomposition.html">CholeskyDecomposition</a>
+<dd>Is the matrix symmetric and positive definite?
+</dl>
+<hr>
+<a name="_J_"><!-- --></a><h2>
+<b>J</b></h2>
+<dl>
+<dt><a href="Jama/package-summary.html">Jama</a> - package Jama<dd> </dl>
+<hr>
+<a name="_L_"><!-- --></a><h2>
+<b>L</b></h2>
+<dl>
+<dt><a href="Jama/LUDecomposition.html#LUDecomposition(Jama.Matrix)"><b>LUDecomposition(Matrix)</b></a> - 
+Constructor for class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>LU Decomposition
+<dt><a href="Jama/LUDecomposition.html"><b>LUDecomposition</b></a> - class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>.<dd>LU Decomposition.
+<dt><a href="Jama/Matrix.html#lu()"><b>lu()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>LU Decomposition
+</dl>
+<hr>
+<a name="_M_"><!-- --></a><h2>
+<b>M</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#Matrix(int, int)"><b>Matrix(int, int)</b></a> - 
+Constructor for class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Construct an m-by-n matrix of zeros.
+<dt><a href="Jama/Matrix.html#Matrix(int, int, double)"><b>Matrix(int, int, double)</b></a> - 
+Constructor for class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Construct an m-by-n constant matrix.
+<dt><a href="Jama/Matrix.html#Matrix(double[][])"><b>Matrix(double[][])</b></a> - 
+Constructor for class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Construct a matrix from a 2-D array.
+<dt><a href="Jama/Matrix.html#Matrix(double[][], int, int)"><b>Matrix(double[][], int, int)</b></a> - 
+Constructor for class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Construct a matrix quickly without checking arguments.
+<dt><a href="Jama/Matrix.html#Matrix(double[], int)"><b>Matrix(double[], int)</b></a> - 
+Constructor for class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Construct a matrix from a one-dimensional packed array
+<dt><a href="Jama/examples/MagicSquareExample.html#magic(int)"><b>magic(int)</b></a> - 
+Static method in class Jama.examples.<a href="Jama/examples/MagicSquareExample.html">MagicSquareExample</a>
+<dd>Generate magic square test matrix.
+<dt><a href="Jama/examples/MagicSquareExample.html"><b>MagicSquareExample</b></a> - class Jama.examples.<a href="Jama/examples/MagicSquareExample.html">MagicSquareExample</a>.<dd>Example of use of Matrix Class, featuring magic squares.<dt><a href="Jama/examples/MagicSquareExample.html#main(java.lang.String[])"><b>main(String[])</b></a> - 
+Static method in class Jama.examples.<a href="Jama/examples/MagicSquareExample.html">MagicSquareExample</a>
+<dd> 
+<dt><a href="Jama/test/TestMatrix.html#main(java.lang.String[])"><b>main(String[])</b></a> - 
+Static method in class Jama.test.<a href="Jama/test/TestMatrix.html">TestMatrix</a>
+<dd> 
+<dt><a href="Jama/util/Maths.html"><b>Maths</b></a> - class Jama.util.<a href="Jama/util/Maths.html">Maths</a>.<dd> <dt><a href="Jama/Matrix.html"><b>Matrix</b></a> - class Jama.<a href="Jama/Matrix.html">Matrix</a>.<dd>Jama = Java Matrix class.
+<dt><a href="Jama/Matrix.html#minusEquals(Jama.Matrix)"><b>minusEquals(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>A = A - B
+<dt><a href="Jama/Matrix.html#minus(Jama.Matrix)"><b>minus(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>C = A - B
+</dl>
+<hr>
+<a name="_N_"><!-- --></a><h2>
+<b>N</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#norm1()"><b>norm1()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>One norm
+<dt><a href="Jama/Matrix.html#norm2()"><b>norm2()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Two norm
+<dt><a href="Jama/SingularValueDecomposition.html#norm2()"><b>norm2()</b></a> - 
+Method in class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Two norm
+<dt><a href="Jama/Matrix.html#normF()"><b>normF()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Frobenius norm
+<dt><a href="Jama/Matrix.html#normInf()"><b>normInf()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Infinity norm
+</dl>
+<hr>
+<a name="_P_"><!-- --></a><h2>
+<b>P</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#plusEquals(Jama.Matrix)"><b>plusEquals(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>A = A + B
+<dt><a href="Jama/Matrix.html#plus(Jama.Matrix)"><b>plus(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>C = A + B
+<dt><a href="Jama/Matrix.html#print(int, int)"><b>print(int, int)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Print the matrix to stdout.   
+<dt><a href="Jama/Matrix.html#print(java.io.PrintWriter, int, int)"><b>print(PrintWriter, int, int)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Print the matrix to the output stream.   
+<dt><a href="Jama/Matrix.html#print(java.text.NumberFormat, int)"><b>print(NumberFormat, int)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Print the matrix to stdout.  
+<dt><a href="Jama/Matrix.html#print(java.io.PrintWriter, java.text.NumberFormat, int)"><b>print(PrintWriter, NumberFormat, int)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Print the matrix to the output stream.  
+</dl>
+<hr>
+<a name="_Q_"><!-- --></a><h2>
+<b>Q</b></h2>
+<dl>
+<dt><a href="Jama/QRDecomposition.html#QRDecomposition(Jama.Matrix)"><b>QRDecomposition(Matrix)</b></a> - 
+Constructor for class Jama.<a href="Jama/QRDecomposition.html">QRDecomposition</a>
+<dd>QR Decomposition, computed by Householder reflections.
+<dt><a href="Jama/QRDecomposition.html"><b>QRDecomposition</b></a> - class Jama.<a href="Jama/QRDecomposition.html">QRDecomposition</a>.<dd>QR Decomposition.
+<dt><a href="Jama/Matrix.html#qr()"><b>qr()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>QR Decomposition
+</dl>
+<hr>
+<a name="_R_"><!-- --></a><h2>
+<b>R</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#random(int, int)"><b>random(int, int)</b></a> - 
+Static method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Generate matrix with random elements
+<dt><a href="Jama/Matrix.html#rank()"><b>rank()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Matrix rank
+<dt><a href="Jama/SingularValueDecomposition.html#rank()"><b>rank()</b></a> - 
+Method in class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Effective numerical matrix rank
+<dt><a href="Jama/Matrix.html#read(java.io.BufferedReader)"><b>read(BufferedReader)</b></a> - 
+Static method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Read a matrix from a stream.  
+</dl>
+<hr>
+<a name="_S_"><!-- --></a><h2>
+<b>S</b></h2>
+<dl>
+<dt><a href="Jama/SingularValueDecomposition.html#SingularValueDecomposition(Jama.Matrix)"><b>SingularValueDecomposition(Matrix)</b></a> - 
+Constructor for class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>
+<dd>Construct the singular value decomposition
+<dt><a href="Jama/Matrix.html#set(int, int, double)"><b>set(int, int, double)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Set a single element.
+<dt><a href="Jama/Matrix.html#setMatrix(int, int, int, int, Jama.Matrix)"><b>setMatrix(int, int, int, int, Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Set a submatrix.
+<dt><a href="Jama/Matrix.html#setMatrix(int[], int[], Jama.Matrix)"><b>setMatrix(int[], int[], Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Set a submatrix.
+<dt><a href="Jama/Matrix.html#setMatrix(int[], int, int, Jama.Matrix)"><b>setMatrix(int[], int, int, Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Set a submatrix.
+<dt><a href="Jama/Matrix.html#setMatrix(int, int, int[], Jama.Matrix)"><b>setMatrix(int, int, int[], Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Set a submatrix.
+<dt><a href="Jama/SingularValueDecomposition.html"><b>SingularValueDecomposition</b></a> - class Jama.<a href="Jama/SingularValueDecomposition.html">SingularValueDecomposition</a>.<dd>Singular Value Decomposition.
+<dt><a href="Jama/CholeskyDecomposition.html#solve(Jama.Matrix)"><b>solve(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/CholeskyDecomposition.html">CholeskyDecomposition</a>
+<dd>Solve A*X = B
+<dt><a href="Jama/LUDecomposition.html#solve(Jama.Matrix)"><b>solve(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/LUDecomposition.html">LUDecomposition</a>
+<dd>Solve A*X = B
+<dt><a href="Jama/Matrix.html#solve(Jama.Matrix)"><b>solve(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Solve A*X = B
+<dt><a href="Jama/QRDecomposition.html#solve(Jama.Matrix)"><b>solve(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/QRDecomposition.html">QRDecomposition</a>
+<dd>Least squares solution of A*X = B
+<dt><a href="Jama/Matrix.html#solveTranspose(Jama.Matrix)"><b>solveTranspose(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Solve X*A = B, which is also A'
+<dt><a href="Jama/Matrix.html#svd()"><b>svd()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Singular Value Decomposition
+</dl>
+<hr>
+<a name="_T_"><!-- --></a><h2>
+<b>T</b></h2>
+<dl>
+<dt><a href="Jama/test/TestMatrix.html"><b>TestMatrix</b></a> - class Jama.test.<a href="Jama/test/TestMatrix.html">TestMatrix</a>.<dd>TestMatrix tests the functionality of the Jama Matrix class and associated decompositions.
+<dt><a href="Jama/Matrix.html#timesEquals(double)"><b>timesEquals(double)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Multiply a matrix by a scalar in place, A = s*A
+<dt><a href="Jama/Matrix.html#times(double)"><b>times(double)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Multiply a matrix by a scalar, C = s*A
+<dt><a href="Jama/Matrix.html#times(Jama.Matrix)"><b>times(Matrix)</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Linear algebraic matrix multiplication, A * B
+<dt><a href="Jama/Matrix.html#trace()"><b>trace()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Matrix trace.
+<dt><a href="Jama/Matrix.html#transpose()"><b>transpose()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Matrix transpose.
+</dl>
+<hr>
+<a name="_U_"><!-- --></a><h2>
+<b>U</b></h2>
+<dl>
+<dt><a href="Jama/Matrix.html#uminus()"><b>uminus()</b></a> - 
+Method in class Jama.<a href="Jama/Matrix.html">Matrix</a>
+<dd>Unary minus
+</dl>
+<hr>
+<a href="#_A_">A</a> <a href="#_C_">C</a> <a href="#_D_">D</a> <a href="#_E_">E</a> <a href="#_F_">F</a> <a href="#_G_">G</a> <a href="#_H_">H</a> <a href="#_I_">I</a> <a href="#_J_">J</a> <a href="#_L_">L</a> <a href="#_M_">M</a> <a href="#_N_">N</a> <a href="#_P_">P</a> <a href="#_Q_">Q</a> <a href="#_R_">R</a> <a href="#_S_">S</a> <a href="#_T_">T</a> <a href="#_U_">U</a> <a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <a href="tree.html">Tree</a> | <b>Index</b> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="index-all.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/index.html b/Jama/doc/index.html
new file mode 100644
index 0000000..f80a4e9
--- /dev/null
+++ b/Jama/doc/index.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Untitled
+</title>
+</head>
+<frameset cols="20%,80%">
+<frame src="allclasses-frame.html" name="packageFrame">
+<frame src="Jama/CholeskyDecomposition.html" name="classFrame">
+</frameset>
+<h2>
+doclet.Frame_Alert
+</h2>
+
+<p>
+This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client.
+<br>
+Link to <a href="overview-summary.html">Non-frame version.</a></html>
diff --git a/Jama/doc/overview-frame.html b/Jama/doc/overview-frame.html
new file mode 100644
index 0000000..acb9c2d
--- /dev/null
+++ b/Jama/doc/overview-frame.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Package Summary
+</title>
+<link rel ="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td nowrap><font size="+1" class="FrameTitleFont">
+<b></b></font>
+</td>
+</tr>
+</table>
+
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td nowrap><font class="FrameItemFont">
+<a href="allclasses-frame.html" target="packageFrame">All Classes</a>
+</font>
+
+<p>
+<font size="+1" class="FrameHeadingFont">
+Packages</font>
+
+<br>
+<font class="FrameItemFont">
+<a href="Jama/package-frame.html" target="packageFrame">Jama</a>
+</font>
+
+<br>
+</td>
+</tr>
+</table>
+
+<p>
+ </body>
+</html>
diff --git a/Jama/doc/overview-summary.html b/Jama/doc/overview-summary.html
new file mode 100644
index 0000000..d297b7b
--- /dev/null
+++ b/Jama/doc/overview-summary.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:41 EDT 1998
+-->
+<title>
+Package Summary
+</title>
+<link rel ="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><b>Overview</b> | Package | Class | <a href="tree.html">Tree</a> | <a href="index-all.html">Index</a> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="overview-summary.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=2><font size="+2">
+<b>Package Summary</b></font>
+</td>
+</tr>
+<tr BGCOLOR="white" class="TableRowColor">
+<td width="20%"><b><a href="Jama/package-summary.html">Jama</a></b></td>
+<td> </td>
+</tr>
+</table>
+
+<p>
+ <hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><b>Overview</b> | Package | Class | <a href="tree.html">Tree</a> | <a href="index-all.html">Index</a> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="overview-summary.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/serializedform.html b/Jama/doc/serializedform.html
new file mode 100644
index 0000000..6a16c80
--- /dev/null
+++ b/Jama/doc/serializedform.html
@@ -0,0 +1,433 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:42 EDT 1998
+-->
+<title>
+Serialized Form
+</title>
+<link rel ="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <a href="tree.html">Tree</a> | <a href="index-all.html">Index</a> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="serializedform.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+<center>
+<h1>
+Serialized Form</h1>
+</center>
+<hr size="4" noshade>
+<center>
+<h2>
+<b>Package</b> <b>Jama</b></h2>
+</center>
+
+<p>
+<a name="Jama.CholeskyDecomposition"><!-- --></a><h2>
+Class <a href="Jama/CholeskyDecomposition.html">Jama.CholeskyDecomposition</a></h2>
+ 
+This class implements Serializable.
+<p>
+<a name="serialized_methods"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Methods</b></font>
+</td>
+</tr>
+</table>
+
+<p>
+No readObject or writeObject method declared.
+<p>
+<a name="serializedForm"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Fields</b></font>
+</td>
+</tr>
+</table>
+<h3>
+isspd</h3>
+<pre>
+boolean <b>isspd</b></pre>
+<dl>
+<dd>is symmetric and positive definite flag.</dl>
+<hr>
+<h3>
+L</h3>
+<pre>
+double[][] <b>L</b></pre>
+<dl>
+<dd>internal array storage.</dl>
+<hr>
+<h3>
+n</h3>
+<pre>
+int <b>n</b></pre>
+<dl>
+<dd>matrix dimension.</dl>
+
+<p>
+<hr size="4" noshade>
+<a name="Jama.EigenvalueDecomposition"><!-- --></a><h2>
+Class <a href="Jama/EigenvalueDecomposition.html">Jama.EigenvalueDecomposition</a></h2>
+ 
+This class implements Serializable.
+<p>
+<a name="serialized_methods"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Methods</b></font>
+</td>
+</tr>
+</table>
+
+<p>
+No readObject or writeObject method declared.
+<p>
+<a name="serializedForm"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Fields</b></font>
+</td>
+</tr>
+</table>
+<h3>
+d</h3>
+<pre>
+double[] <b>d</b></pre>
+<dl>
+<dd>internal storage of eigenvalues.</dl>
+<hr>
+<h3>
+e</h3>
+<pre>
+double[] <b>e</b></pre>
+<dl>
+<dd>internal storage of eigenvalues.</dl>
+<hr>
+<h3>
+H</h3>
+<pre>
+double[][] <b>H</b></pre>
+<dl>
+<dd>internal storage of nonsymmetric Hessenberg form.</dl>
+<hr>
+<h3>
+issymmetric</h3>
+<pre>
+boolean <b>issymmetric</b></pre>
+<dl>
+<dd>internal symmetry flag.</dl>
+<hr>
+<h3>
+n</h3>
+<pre>
+int <b>n</b></pre>
+<dl>
+<dd>matrix dimension.</dl>
+<hr>
+<h3>
+ort</h3>
+<pre>
+double[] <b>ort</b></pre>
+<dl>
+<dd>working storage for nonsymmetric algorithm.</dl>
+<hr>
+<h3>
+V</h3>
+<pre>
+double[][] <b>V</b></pre>
+<dl>
+<dd>internal storage of eigenvectors.</dl>
+
+<p>
+<hr size="4" noshade>
+<a name="Jama.LUDecomposition"><!-- --></a><h2>
+Class <a href="Jama/LUDecomposition.html">Jama.LUDecomposition</a></h2>
+ 
+This class implements Serializable.
+<p>
+<a name="serialized_methods"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Methods</b></font>
+</td>
+</tr>
+</table>
+
+<p>
+No readObject or writeObject method declared.
+<p>
+<a name="serializedForm"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Fields</b></font>
+</td>
+</tr>
+</table>
+<h3>
+LU</h3>
+<pre>
+double[][] <b>LU</b></pre>
+<dl>
+<dd>internal array storage.</dl>
+<hr>
+<h3>
+m</h3>
+<pre>
+int <b>m</b></pre>
+<dl>
+<dd>column dimension.</dl>
+<hr>
+<h3>
+n</h3>
+<pre>
+int <b>n</b></pre>
+<dl>
+<dd>column dimension.</dl>
+<hr>
+<h3>
+piv</h3>
+<pre>
+int[] <b>piv</b></pre>
+<dl>
+<dd>pivot vector.</dl>
+<hr>
+<h3>
+pivsign</h3>
+<pre>
+int <b>pivsign</b></pre>
+<dl>
+<dd>column dimension.</dl>
+
+<p>
+<hr size="4" noshade>
+<a name="Jama.Matrix"><!-- --></a><h2>
+Class <a href="Jama/Matrix.html">Jama.Matrix</a></h2>
+ 
+This class implements Serializable.
+<p>
+<a name="serialized_methods"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Methods</b></font>
+</td>
+</tr>
+</table>
+
+<p>
+No readObject or writeObject method declared.
+<p>
+<a name="serializedForm"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Fields</b></font>
+</td>
+</tr>
+</table>
+<h3>
+A</h3>
+<pre>
+double[][] <b>A</b></pre>
+<dl>
+<dd>internal array storage.</dl>
+<hr>
+<h3>
+m</h3>
+<pre>
+int <b>m</b></pre>
+<dl>
+<dd>row dimension.</dl>
+<hr>
+<h3>
+n</h3>
+<pre>
+int <b>n</b></pre>
+<dl>
+<dd>row dimension.</dl>
+
+<p>
+<hr size="4" noshade>
+<a name="Jama.QRDecomposition"><!-- --></a><h2>
+Class <a href="Jama/QRDecomposition.html">Jama.QRDecomposition</a></h2>
+ 
+This class implements Serializable.
+<p>
+<a name="serialized_methods"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Methods</b></font>
+</td>
+</tr>
+</table>
+
+<p>
+No readObject or writeObject method declared.
+<p>
+<a name="serializedForm"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Fields</b></font>
+</td>
+</tr>
+</table>
+<h3>
+m</h3>
+<pre>
+int <b>m</b></pre>
+<dl>
+<dd>column dimension.</dl>
+<hr>
+<h3>
+n</h3>
+<pre>
+int <b>n</b></pre>
+<dl>
+<dd>column dimension.</dl>
+<hr>
+<h3>
+QR</h3>
+<pre>
+double[][] <b>QR</b></pre>
+<dl>
+<dd>internal array storage.</dl>
+<hr>
+<h3>
+Rdiag</h3>
+<pre>
+double[] <b>Rdiag</b></pre>
+<dl>
+<dd>diagonal of R.</dl>
+
+<p>
+<hr size="4" noshade>
+<a name="Jama.SingularValueDecomposition"><!-- --></a><h2>
+Class <a href="Jama/SingularValueDecomposition.html">Jama.SingularValueDecomposition</a></h2>
+ 
+This class implements Serializable.
+<p>
+<a name="serialized_methods"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Methods</b></font>
+</td>
+</tr>
+</table>
+
+<p>
+No readObject or writeObject method declared.
+<p>
+<a name="serializedForm"><!-- --></a>
+<table border="1" cellpadding="3" cellspacing="0" width=100%>
+<tr BGCOLOR="#CCCCFF" class="TableSummaryHeadingColor">
+<td colspan=1><font size="+2">
+<b>Serialized Form - Fields</b></font>
+</td>
+</tr>
+</table>
+<h3>
+m</h3>
+<pre>
+int <b>m</b></pre>
+<dl>
+<dd>row dimension.</dl>
+<hr>
+<h3>
+n</h3>
+<pre>
+int <b>n</b></pre>
+<dl>
+<dd>row dimension.</dl>
+<hr>
+<h3>
+s</h3>
+<pre>
+double[] <b>s</b></pre>
+<dl>
+<dd>internal storage of singular values.</dl>
+<hr>
+<h3>
+U</h3>
+<pre>
+double[][] <b>U</b></pre>
+<dl>
+<dd>internal storage of U.</dl>
+<hr>
+<h3>
+V</h3>
+<pre>
+double[][] <b>V</b></pre>
+<dl>
+<dd>internal storage of U.</dl>
+
+<p>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <a href="tree.html">Tree</a> | <a href="index-all.html">Index</a> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="serializedform.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/doc/stylesheet.css b/Jama/doc/stylesheet.css
new file mode 100644
index 0000000..6f519ff
--- /dev/null
+++ b/Jama/doc/stylesheet.css
@@ -0,0 +1,20 @@
+/* Javadoc style sheet */
+
+/* Define colors, fonts and other style attributes here to override the defaults  */
+
+/* Page background color */
+body { background-color: #FFFFF }
+
+/* Table colors */
+.TableSummaryHeadingColor    { background: #CCCCFF } /* Dark blue */
+.TableInheritedHeadingColor  { background: #EEEEFF } /* Light blue */
+.TableRowColor               { background: #FFFFFF } /* White */
+
+/* Font used in left-hand frame lists */
+
+.FrameTitleFont  { font-size: normal; font-family: normal }
+.FrameHeadingFont { font-size: normal; font-family: normal }
+.FrameItemFont  { font-size: normal; font-family: normal }
+
+/* Example */
+/* .FrameItemFont  { font-size: 10pt; font-family: helvetica arial sans-serif } */
diff --git a/Jama/doc/tree.html b/Jama/doc/tree.html
new file mode 100644
index 0000000..c83a736
--- /dev/null
+++ b/Jama/doc/tree.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<!--NewPage-->
+<html>
+<head>
+<!-- Generated by javadoc on Tue Aug 04 21:42:40 EDT 1998
+-->
+<title>
+Class Hierarchy
+</title>
+<link rel ="stylesheet" type="text/css" href="stylesheet.css" title="Style">
+</head>
+<body bgcolor="white">
+
+<a name="navbar_top"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <b>Tree</b> | <a href="index-all.html">Index</a> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="tree.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+<center>
+<h2>
+Hierarchy For All Packages</h2>
+</center>
+<dl>
+<dt><b>Package Hierarchies:</b><dd><a href="Jama/tree.html">Jama</a></dl>
+<hr>
+<h2>
+Class Hierarchy
+</h2>
+<ul>
+<li type="circle">class java.lang.Object<ul>
+<li type="circle">class Jama.<a href="Jama/CholeskyDecomposition.html"><b>CholeskyDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.<a href="Jama/EigenvalueDecomposition.html"><b>EigenvalueDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.<a href="Jama/LUDecomposition.html"><b>LUDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.examples.<a href="Jama/examples/MagicSquareExample.html"><b>MagicSquareExample</b></a><li type="circle">class Jama.util.<a href="Jama/util/Maths.html"><b>Maths</b></a><li type="circle">class Jama.<a href="Jama/Matrix.html"><b>Matrix</b></a>(implements java.lang.Cloneable, java.io.Serializable)
+<li type="circle">class Jama.<a href="Jama/QRDecomposition.html"><b>QRDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.<a href="Jama/SingularValueDecomposition.html"><b>SingularValueDecomposition</b></a>(implements java.io.Serializable)
+<li type="circle">class Jama.test.<a href="Jama/test/TestMatrix.html"><b>TestMatrix</b></a></ul>
+</ul>
+<hr>
+<a name="navbar_bottom"><!-- --></a>
+<table BORDER="0" WIDTH="100%">
+<tr>
+<td></td>
+<td></td>
+<td></td>
+<td></td>
+</tr>
+<tr>
+<td colspan=3><a href="overview-summary.html">Overview</a> | Package | Class | <b>Tree</b> | <a href="index-all.html">Index</a> | <a href="help.html">Help</a></td>
+<td align=right valign=top rowspan=2><em>
+</em>
+</td>
+</tr>
+<tr>
+<td><font size="-2">
+PREV | NEXT</font>
+</td>
+<td><font size="-2">
+<a href="index.html" target="_top">FRAMES</a>
+ | <a href="tree.html" target="_top">NO FRAMES</a>
+</font>
+</td>
+<td></td>
+<td></td>
+</tr>
+</table>
+<hr>
+</body>
+</html>
diff --git a/Jama/examples/MagicSquareExample.java b/Jama/examples/MagicSquareExample.java
new file mode 100644
index 0000000..7e6a7cd
--- /dev/null
+++ b/Jama/examples/MagicSquareExample.java
@@ -0,0 +1,163 @@
+package Jama.examples;
+import Jama.*; 
+import java.util.Date;
+
+/** Example of use of Matrix Class, featuring magic squares. **/
+
+public class MagicSquareExample {
+
+   /** Generate magic square test matrix. **/
+
+   public static Matrix magic (int n) {
+
+      double[][] M = new double[n][n];
+
+      // Odd order
+
+      if ((n % 2) == 1) {
+         int a = (n+1)/2;
+         int b = (n+1);
+         for (int j = 0; j < n; j++) {
+            for (int i = 0; i < n; i++) {
+               M[i][j] = n*((i+j+a) % n) + ((i+2*j+b) % n) + 1;
+            }
+         }
+
+      // Doubly Even Order
+
+      } else if ((n % 4) == 0) {
+         for (int j = 0; j < n; j++) {
+            for (int i = 0; i < n; i++) {
+               if (((i+1)/2)%2 == ((j+1)/2)%2) {
+                  M[i][j] = n*n-n*i-j;
+               } else {
+                  M[i][j] = n*i+j+1;
+               }
+            }
+         }
+
+      // Singly Even Order
+
+      } else {
+         int p = n/2;
+         int k = (n-2)/4;
+         Matrix A = magic(p);
+         for (int j = 0; j < p; j++) {
+            for (int i = 0; i < p; i++) {
+               double aij = A.get(i,j);
+               M[i][j] = aij;
+               M[i][j+p] = aij + 2*p*p;
+               M[i+p][j] = aij + 3*p*p;
+               M[i+p][j+p] = aij + p*p;
+            }
+         }
+         for (int i = 0; i < p; i++) {
+            for (int j = 0; j < k; j++) {
+               double t = M[i][j]; M[i][j] = M[i+p][j]; M[i+p][j] = t;
+            }
+            for (int j = n-k+1; j < n; j++) {
+               double t = M[i][j]; M[i][j] = M[i+p][j]; M[i+p][j] = t;
+            }
+         }
+         double t = M[k][0]; M[k][0] = M[k+p][0]; M[k+p][0] = t;
+         t = M[k][k]; M[k][k] = M[k+p][k]; M[k+p][k] = t;
+      }
+      return new Matrix(M);
+   }
+
+   /** Shorten spelling of print. **/
+
+   private static void print (String s) {
+      System.out.print(s);
+   }
+   
+   /** Format double with Fw.d. **/
+
+   public static String fixedWidthDoubletoString (double x, int w, int d) {
+      java.text.DecimalFormat fmt = new java.text.DecimalFormat();
+      fmt.setMaximumFractionDigits(d);
+      fmt.setMinimumFractionDigits(d);
+      fmt.setGroupingUsed(false);
+      String s = fmt.format(x);
+      while (s.length() < w) {
+         s = " " + s;
+      }
+      return s;
+   }
+
+   /** Format integer with Iw. **/
+
+   public static String fixedWidthIntegertoString (int n, int w) {
+      String s = Integer.toString(n);
+      while (s.length() < w) {
+         s = " " + s;
+      }
+      return s;
+   }
+
+
+   public static void main (String argv[]) {
+
+   /* 
+    | Tests LU, QR, SVD and symmetric Eig decompositions.
+    |
+    |   n       = order of magic square.
+    |   trace   = diagonal sum, should be the magic sum, (n^3 + n)/2.
+    |   max_eig = maximum eigenvalue of (A + A')/2, should equal trace.
+    |   rank    = linear algebraic rank,
+    |             should equal n if n is odd, be less than n if n is even.
+    |   cond    = L_2 condition number, ratio of singular values.
+    |   lu_res  = test of LU factorization, norm1(L*U-A(p,:))/(n*eps).
+    |   qr_res  = test of QR factorization, norm1(Q*R-A)/(n*eps).
+    */
+
+      print("\n    Test of Matrix Class, using magic squares.\n");
+      print("    See MagicSquareExample.main() for an explanation.\n");
+      print("\n      n     trace       max_eig   rank        cond      lu_res      qr_res\n\n");
+ 
+      Date start_time = new Date();
+      double eps = Math.pow(2.0,-52.0);
+      for (int n = 3; n <= 32; n++) {
+         print(fixedWidthIntegertoString(n,7));
+
+         Matrix M = magic(n);
+
+         int t = (int) M.trace();
+         print(fixedWidthIntegertoString(t,10));
+
+         EigenvalueDecomposition E =
+            new EigenvalueDecomposition(M.plus(M.transpose()).times(0.5));
+         double[] d = E.getRealEigenvalues();
+         print(fixedWidthDoubletoString(d[n-1],14,3));
+
+         int r = M.rank();
+         print(fixedWidthIntegertoString(r,7));
+
+         double c = M.cond();
+         print(c < 1/eps ? fixedWidthDoubletoString(c,12,3) :
+            "         Inf");
+
+         LUDecomposition LU = new LUDecomposition(M);
+         Matrix L = LU.getL();
+         Matrix U = LU.getU();
+         int[] p = LU.getPivot();
+         Matrix R = L.times(U).minus(M.getMatrix(p,0,n-1));
+         double res = R.norm1()/(n*eps);
+         print(fixedWidthDoubletoString(res,12,3));
+
+         QRDecomposition QR = new QRDecomposition(M);
+         Matrix Q = QR.getQ();
+         R = QR.getR();
+         R = Q.times(R).minus(M);
+         res = R.norm1()/(n*eps);
+         print(fixedWidthDoubletoString(res,12,3));
+
+         print("\n");
+      }
+      Date stop_time = new Date();
+      double etime = (stop_time.getTime() - start_time.getTime())/1000.;
+      print("\nElapsed Time = " + 
+         fixedWidthDoubletoString(etime,12,3) + " seconds\n");
+      print("Adios\n");
+   }
+}
diff --git a/Jama/test/TestMatrix.java b/Jama/test/TestMatrix.java
new file mode 100644
index 0000000..f340ae9
--- /dev/null
+++ b/Jama/test/TestMatrix.java
@@ -0,0 +1,939 @@
+package Jama.test;
+import Jama.*;
+import java.io.*;
+import java.util.zip.GZIPInputStream;
+import java.text.DecimalFormat;
+
+
+/** TestMatrix tests the functionality of the Jama Matrix class and associated decompositions.
+<P>
+Run the test from the command line using
+<BLOCKQUOTE><PRE><CODE>
+ java Jama.test.TestMatrix 
+</CODE></PRE></BLOCKQUOTE>
+Detailed output is provided indicating the functionality being tested
+and whether the functionality is correctly implemented.   Exception handling
+is also tested.  
+<P>
+The test is designed to run to completion and give a summary of any implementation errors
+encountered. The final output should be:
+<BLOCKQUOTE><PRE><CODE>
+      TestMatrix completed.
+      Total errors reported: n1
+      Total warning reported: n2
+</CODE></PRE></BLOCKQUOTE>
+If the test does not run to completion, this indicates that there is a 
+substantial problem within the implementation that was not anticipated in the test design.  
+The stopping point should give an indication of where the problem exists.
+**/
+public class TestMatrix {
+   public static void main (String argv[]) {
+      Matrix A,B,C,Z,O,I,R,S,X,SUB,M,T,SQ,DEF,SOL;
+      int errorCount=0;
+      int warningCount=0;
+      double tmp, s;
+      double[] columnwise = {1.,2.,3.,4.,5.,6.,7.,8.,9.,10.,11.,12.};
+      double[] rowwise = {1.,4.,7.,10.,2.,5.,8.,11.,3.,6.,9.,12.};
+      double[][] avals = {{1.,4.,7.,10.},{2.,5.,8.,11.},{3.,6.,9.,12.}};
+      double[][] rankdef = avals;
+      double[][] tvals =  {{1.,2.,3.},{4.,5.,6.},{7.,8.,9.},{10.,11.,12.}};
+      double[][] subavals = {{5.,8.,11.},{6.,9.,12.}};
+      double[][] rvals = {{1.,4.,7.},{2.,5.,8.,11.},{3.,6.,9.,12.}};
+      double[][] pvals = {{1.,1.,1.},{1.,2.,3.},{1.,3.,6.}};
+      double[][] ivals = {{1.,0.,0.,0.},{0.,1.,0.,0.},{0.,0.,1.,0.}};
+      double[][] evals = 
+         {{0.,1.,0.,0.},{1.,0.,2.e-7,0.},{0.,-2.e-7,0.,1.},{0.,0.,1.,0.}};
+      double[][] square = {{166.,188.,210.},{188.,214.,240.},{210.,240.,270.}};
+      double[][] sqSolution = {{13.},{15.}};
+      double[][] condmat = {{1.,3.},{7.,9.}};
+      int rows=3,cols=4;
+      int invalidld=5;/* should trigger bad shape for construction with val */
+      int raggedr=0; /* (raggedr,raggedc) should be out of bounds in ragged array */
+      int raggedc=4; 
+      int validld=3; /* leading dimension of intended test Matrices */
+      int nonconformld=4; /* leading dimension which is valid, but nonconforming */
+      int ib=1,ie=2,jb=1,je=3; /* index ranges for sub Matrix */
+      int[] rowindexset = {1,2}; 
+      int[] badrowindexset = {1,3}; 
+      int[] columnindexset = {1,2,3};
+      int[] badcolumnindexset = {1,2,4};
+      double columnsummax = 33.;
+      double rowsummax = 30.;
+      double sumofdiagonals = 15;
+      double sumofsquares = 650;
+
+/** 
+      Constructors and constructor-like methods:
+         double[], int
+         double[][]  
+         int, int
+         int, int, double
+         int, int, double[][]
+         constructWithCopy(double[][])
+         random(int,int)
+         identity(int)
+**/
+
+      print("\nTesting constructors and constructor-like methods...\n");
+      try{  
+         /** check that exception is thrown in packed constructor with invalid length **/
+         A = new Matrix(columnwise,invalidld);
+         errorCount = try_failure(errorCount,"Catch invalid length in packed constructor... ",
+                     "exception not thrown for invalid input");
+      } catch ( IllegalArgumentException e ) {
+         try_success("Catch invalid length in packed constructor... ",
+                     e.getMessage());
+      }
+      try{ 
+         /** check that exception is thrown in default constructor
+             if input array is 'ragged' **/
+         A = new Matrix(rvals);
+         tmp = A.get(raggedr,raggedc);
+      } catch ( IllegalArgumentException e ) {
+         try_success("Catch ragged input to default constructor... ",
+                      e.getMessage());
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         errorCount = try_failure(errorCount,"Catch ragged input to constructor... ",
+                     "exception not thrown in construction...ArrayIndexOutOfBoundsException thrown later");
+      }
+      try{ 
+         /** check that exception is thrown in constructWithCopy
+             if input array is 'ragged' **/
+         A = Matrix.constructWithCopy(rvals);
+         tmp = A.get(raggedr,raggedc);
+      } catch ( IllegalArgumentException e ) {
+         try_success("Catch ragged input to constructWithCopy... ",e.getMessage());
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         errorCount = try_failure(errorCount,"Catch ragged input to constructWithCopy... ","exception not thrown in construction...ArrayIndexOutOfBoundsException thrown later");
+      }
+
+      A = new Matrix(columnwise,validld);
+      B = new Matrix(avals);
+      tmp = B.get(0,0);
+      avals[0][0] = 0.0;
+      C = B.minus(A);
+      avals[0][0] = tmp;
+      B = Matrix.constructWithCopy(avals);
+      tmp = B.get(0,0);
+      avals[0][0] = 0.0;
+      if ( ( tmp - B.get(0,0) ) != 0.0 ) {
+        /** check that constructWithCopy behaves properly **/
+        errorCount = try_failure(errorCount,"constructWithCopy... ","copy not effected... data visible outside");
+      } else {
+        try_success("constructWithCopy... ","");
+      }
+      avals[0][0] = columnwise[0]; 
+      I = new Matrix(ivals);
+      try {
+        check(I,Matrix.identity(3,4));
+        try_success("identity... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"identity... ","identity Matrix not successfully created");
+      }   
+
+/**   
+      Access Methods:
+         getColumnDimension()
+         getRowDimension()
+         getArray()
+         getArrayCopy()
+         getColumnPackedCopy()
+         getRowPackedCopy()
+         get(int,int)
+         getMatrix(int,int,int,int)
+         getMatrix(int,int,int[])
+         getMatrix(int[],int,int)
+         getMatrix(int[],int[])
+         set(int,int,double)
+         setMatrix(int,int,int,int,Matrix)
+         setMatrix(int,int,int[],Matrix)
+         setMatrix(int[],int,int,Matrix)
+         setMatrix(int[],int[],Matrix)
+**/
+
+      print("\nTesting access methods...\n");
+
+/**
+      Various get methods:
+**/
+
+      B = new Matrix(avals);
+      if (B.getRowDimension() != rows) {
+         errorCount = try_failure(errorCount,"getRowDimension... ","");
+      } else {
+         try_success("getRowDimension... ","");
+      }
+      if (B.getColumnDimension() != cols) {
+         errorCount = try_failure(errorCount,"getColumnDimension... ","");
+      } else {
+         try_success("getColumnDimension... ","");
+      }
+      B = new Matrix(avals);
+      double[][] barray = B.getArray();
+      if ( barray != avals ) {
+         errorCount = try_failure(errorCount,"getArray... ","");
+      } else {
+         try_success("getArray... ","");
+      }
+      barray = B.getArrayCopy();
+      if ( barray == avals ) {
+         errorCount = try_failure(errorCount,"getArrayCopy... ","data not (deep) copied");
+      }
+      try {
+         check(barray,avals);
+         try_success("getArrayCopy... ","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"getArrayCopy... ","data not successfully (deep) copied");
+      }
+      double[] bpacked = B.getColumnPackedCopy();
+      try {
+         check(bpacked,columnwise);
+         try_success("getColumnPackedCopy... ","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"getColumnPackedCopy... ","data not successfully (deep) copied by columns");
+      }
+      bpacked = B.getRowPackedCopy();
+      try {
+         check(bpacked,rowwise);
+         try_success("getRowPackedCopy... ","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"getRowPackedCopy... ","data not successfully (deep) copied by rows");
+      }
+      try {
+         tmp = B.get(B.getRowDimension(),B.getColumnDimension()-1);
+         errorCount = try_failure(errorCount,"get(int,int)... ","OutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            tmp = B.get(B.getRowDimension()-1,B.getColumnDimension());
+            errorCount = try_failure(errorCount,"get(int,int)... ","OutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("get(int,int)... OutofBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"get(int,int)... ","OutOfBoundsException expected but not thrown");
+      }
+      try {
+         if (B.get(B.getRowDimension()-1,B.getColumnDimension()-1) != 
+             avals[B.getRowDimension()-1][B.getColumnDimension()-1] ) {
+            errorCount = try_failure(errorCount,"get(int,int)... ","Matrix entry (i,j) not successfully retreived");
+         } else {
+            try_success("get(int,int)... ","");
+         }
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         errorCount = try_failure(errorCount,"get(int,int)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      SUB = new Matrix(subavals);
+      try {
+         M = B.getMatrix(ib,ie+B.getRowDimension()+1,jb,je);
+         errorCount = try_failure(errorCount,"getMatrix(int,int,int,int)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            M = B.getMatrix(ib,ie,jb,je+B.getColumnDimension()+1);
+            errorCount = try_failure(errorCount,"getMatrix(int,int,int,int)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("getMatrix(int,int,int,int)... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"getMatrix(int,int,int,int)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      }
+      try {
+         M = B.getMatrix(ib,ie,jb,je);
+         try {
+            check(SUB,M);
+            try_success("getMatrix(int,int,int,int)... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"getMatrix(int,int,int,int)... ","submatrix not successfully retreived");
+         }
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         errorCount = try_failure(errorCount,"getMatrix(int,int,int,int)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      
+      try {
+         M = B.getMatrix(ib,ie,badcolumnindexset);
+         errorCount = try_failure(errorCount,"getMatrix(int,int,int[])... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            M = B.getMatrix(ib,ie+B.getRowDimension()+1,columnindexset);
+            errorCount = try_failure(errorCount,"getMatrix(int,int,int[])... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("getMatrix(int,int,int[])... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"getMatrix(int,int,int[])... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } 
+      try {
+         M = B.getMatrix(ib,ie,columnindexset);
+         try {
+            check(SUB,M);
+            try_success("getMatrix(int,int,int[])... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"getMatrix(int,int,int[])... ","submatrix not successfully retreived");
+         }
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         errorCount = try_failure(errorCount,"getMatrix(int,int,int[])... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      try {
+         M = B.getMatrix(badrowindexset,jb,je);
+         errorCount = try_failure(errorCount,"getMatrix(int[],int,int)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            M = B.getMatrix(rowindexset,jb,je+B.getColumnDimension()+1);
+            errorCount = try_failure(errorCount,"getMatrix(int[],int,int)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("getMatrix(int[],int,int)... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"getMatrix(int[],int,int)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } 
+      try {
+         M = B.getMatrix(rowindexset,jb,je);
+         try {
+            check(SUB,M);
+            try_success("getMatrix(int[],int,int)... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"getMatrix(int[],int,int)... ","submatrix not successfully retreived");
+         }
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         errorCount = try_failure(errorCount,"getMatrix(int[],int,int)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      try {
+         M = B.getMatrix(badrowindexset,columnindexset);
+         errorCount = try_failure(errorCount,"getMatrix(int[],int[])... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            M = B.getMatrix(rowindexset,badcolumnindexset);
+            errorCount = try_failure(errorCount,"getMatrix(int[],int[])... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("getMatrix(int[],int[])... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"getMatrix(int[],int[])... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } 
+      try {
+         M = B.getMatrix(rowindexset,columnindexset);
+         try {
+            check(SUB,M);
+            try_success("getMatrix(int[],int[])... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"getMatrix(int[],int[])... ","submatrix not successfully retreived");
+         }
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         errorCount = try_failure(errorCount,"getMatrix(int[],int[])... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+
+/**
+      Various set methods:
+**/
+
+      try {
+         B.set(B.getRowDimension(),B.getColumnDimension()-1,0.);
+         errorCount = try_failure(errorCount,"set(int,int,double)... ","OutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            B.set(B.getRowDimension()-1,B.getColumnDimension(),0.);
+            errorCount = try_failure(errorCount,"set(int,int,double)... ","OutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("set(int,int,double)... OutofBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"set(int,int,double)... ","OutOfBoundsException expected but not thrown");
+      }
+      try {
+         B.set(ib,jb,0.);
+         tmp = B.get(ib,jb);
+         try {
+            check(tmp,0.);
+            try_success("set(int,int,double)... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"set(int,int,double)... ","Matrix element not successfully set");
+         }
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e1) {
+         errorCount = try_failure(errorCount,"set(int,int,double)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      M = new Matrix(2,3,0.);
+      try {
+         B.setMatrix(ib,ie+B.getRowDimension()+1,jb,je,M);
+         errorCount = try_failure(errorCount,"setMatrix(int,int,int,int,Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            B.setMatrix(ib,ie,jb,je+B.getColumnDimension()+1,M);
+            errorCount = try_failure(errorCount,"setMatrix(int,int,int,int,Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("setMatrix(int,int,int,int,Matrix)... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int,int,int,int,Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      }
+      try {
+         B.setMatrix(ib,ie,jb,je,M);
+         try {
+            check(M.minus(B.getMatrix(ib,ie,jb,je)),M);
+            try_success("setMatrix(int,int,int,int,Matrix)... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"setMatrix(int,int,int,int,Matrix)... ","submatrix not successfully set");
+         }
+         B.setMatrix(ib,ie,jb,je,SUB);
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int,int,int,int,Matrix)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      try {
+         B.setMatrix(ib,ie+B.getRowDimension()+1,columnindexset,M);
+         errorCount = try_failure(errorCount,"setMatrix(int,int,int[],Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            B.setMatrix(ib,ie,badcolumnindexset,M);
+            errorCount = try_failure(errorCount,"setMatrix(int,int,int[],Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("setMatrix(int,int,int[],Matrix)... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int,int,int[],Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      }
+      try {
+         B.setMatrix(ib,ie,columnindexset,M);
+         try {
+            check(M.minus(B.getMatrix(ib,ie,columnindexset)),M);
+            try_success("setMatrix(int,int,int[],Matrix)... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"setMatrix(int,int,int[],Matrix)... ","submatrix not successfully set");
+         }
+         B.setMatrix(ib,ie,jb,je,SUB);
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int,int,int[],Matrix)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      try {
+         B.setMatrix(rowindexset,jb,je+B.getColumnDimension()+1,M);
+         errorCount = try_failure(errorCount,"setMatrix(int[],int,int,Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            B.setMatrix(badrowindexset,jb,je,M);
+            errorCount = try_failure(errorCount,"setMatrix(int[],int,int,Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("setMatrix(int[],int,int,Matrix)... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int[],int,int,Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      }
+      try {
+         B.setMatrix(rowindexset,jb,je,M);
+         try {
+            check(M.minus(B.getMatrix(rowindexset,jb,je)),M);
+            try_success("setMatrix(int[],int,int,Matrix)... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"setMatrix(int[],int,int,Matrix)... ","submatrix not successfully set");
+         }
+         B.setMatrix(ib,ie,jb,je,SUB);
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int[],int,int,Matrix)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+      try {
+         B.setMatrix(rowindexset,badcolumnindexset,M);
+         errorCount = try_failure(errorCount,"setMatrix(int[],int[],Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e ) {
+         try {
+            B.setMatrix(badrowindexset,columnindexset,M);
+            errorCount = try_failure(errorCount,"setMatrix(int[],int[],Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+         } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+            try_success("setMatrix(int[],int[],Matrix)... ArrayIndexOutOfBoundsException... ","");
+         }
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int[],int[],Matrix)... ","ArrayIndexOutOfBoundsException expected but not thrown");
+      }
+      try {
+         B.setMatrix(rowindexset,columnindexset,M);
+         try {
+            check(M.minus(B.getMatrix(rowindexset,columnindexset)),M);
+            try_success("setMatrix(int[],int[],Matrix)... ","");
+         } catch ( java.lang.RuntimeException e ) {
+            errorCount = try_failure(errorCount,"setMatrix(int[],int[],Matrix)... ","submatrix not successfully set");
+         }
+      } catch ( java.lang.ArrayIndexOutOfBoundsException e1 ) {
+         errorCount = try_failure(errorCount,"setMatrix(int[],int[],Matrix)... ","Unexpected ArrayIndexOutOfBoundsException");
+      }
+
+/** 
+      Array-like methods:
+         minus
+         minusEquals
+         plus
+         plusEquals
+         arrayLeftDivide
+         arrayLeftDivideEquals
+         arrayRightDivide
+         arrayRightDivideEquals
+         arrayTimes
+         arrayTimesEquals
+         uminus
+**/
+
+      print("\nTesting array-like methods...\n");
+      S = new Matrix(columnwise,nonconformld);
+      R = Matrix.random(A.getRowDimension(),A.getColumnDimension());
+      A = R;
+      try {
+        S = A.minus(S);
+        errorCount = try_failure(errorCount,"minus conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("minus conformance check... ","");
+      }
+      if (A.minus(R).norm1() != 0.) {
+        errorCount = try_failure(errorCount,"minus... ","(difference of identical Matrices is nonzero,\nSubsequent use of minus should be suspect)");
+      } else {
+        try_success("minus... ","");
+      }
+      A = R.copy();
+      A.minusEquals(R);
+      Z = new Matrix(A.getRowDimension(),A.getColumnDimension());
+      try {
+        A.minusEquals(S);
+        errorCount = try_failure(errorCount,"minusEquals conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("minusEquals conformance check... ","");
+      }
+      if (A.minus(Z).norm1() != 0.) {
+        errorCount = try_failure(errorCount,"minusEquals... ","(difference of identical Matrices is nonzero,\nSubsequent use of minus should be suspect)");
+      } else {
+        try_success("minusEquals... ","");
+      }
+
+      A = R.copy();
+      B = Matrix.random(A.getRowDimension(),A.getColumnDimension());
+      C = A.minus(B); 
+      try {
+        S = A.plus(S);
+        errorCount = try_failure(errorCount,"plus conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("plus conformance check... ","");
+      }
+      try {
+        check(C.plus(B),A);
+        try_success("plus... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"plus... ","(C = A - B, but C + B != A)");
+      }
+      C = A.minus(B);
+      C.plusEquals(B);
+      try {
+        A.plusEquals(S);
+        errorCount = try_failure(errorCount,"plusEquals conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("plusEquals conformance check... ","");
+      }
+      try {
+        check(C,A);
+        try_success("plusEquals... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"plusEquals... ","(C = A - B, but C = C + B != A)");
+      }
+      A = R.uminus();
+      try {
+        check(A.plus(R),Z);
+        try_success("uminus... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"uminus... ","(-A + A != zeros)");
+      }
+      A = R.copy();
+      O = new Matrix(A.getRowDimension(),A.getColumnDimension(),1.0);
+      C = A.arrayLeftDivide(R);
+      try {
+        S = A.arrayLeftDivide(S);
+        errorCount = try_failure(errorCount,"arrayLeftDivide conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("arrayLeftDivide conformance check... ","");
+      }
+      try {
+        check(C,O);
+        try_success("arrayLeftDivide... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"arrayLeftDivide... ","(M.\\M != ones)");
+      }
+      try {
+        A.arrayLeftDivideEquals(S);
+        errorCount = try_failure(errorCount,"arrayLeftDivideEquals conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("arrayLeftDivideEquals conformance check... ","");
+      }
+      A.arrayLeftDivideEquals(R);
+      try {
+        check(A,O);
+        try_success("arrayLeftDivideEquals... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"arrayLeftDivideEquals... ","(M.\\M != ones)");
+      }
+      A = R.copy();
+      try {
+        A.arrayRightDivide(S);
+        errorCount = try_failure(errorCount,"arrayRightDivide conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("arrayRightDivide conformance check... ","");
+      }
+      C = A.arrayRightDivide(R);
+      try {
+        check(C,O);
+        try_success("arrayRightDivide... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"arrayRightDivide... ","(M./M != ones)");
+      }
+      try {
+        A.arrayRightDivideEquals(S);
+        errorCount = try_failure(errorCount,"arrayRightDivideEquals conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("arrayRightDivideEquals conformance check... ","");
+      }
+      A.arrayRightDivideEquals(R);
+      try {
+        check(A,O);
+        try_success("arrayRightDivideEquals... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"arrayRightDivideEquals... ","(M./M != ones)");
+      }
+      A = R.copy();
+      B = Matrix.random(A.getRowDimension(),A.getColumnDimension());
+      try {
+        S = A.arrayTimes(S);
+        errorCount = try_failure(errorCount,"arrayTimes conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("arrayTimes conformance check... ","");
+      }
+      C = A.arrayTimes(B);
+      try {
+        check(C.arrayRightDivideEquals(B),A);
+        try_success("arrayTimes... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"arrayTimes... ","(A = R, C = A.*B, but C./B != A)");
+      }
+      try {
+        A.arrayTimesEquals(S);
+        errorCount = try_failure(errorCount,"arrayTimesEquals conformance check... ","nonconformance not raised");
+      } catch ( IllegalArgumentException e ) {
+        try_success("arrayTimesEquals conformance check... ","");
+      }
+      A.arrayTimesEquals(B);
+      try {
+        check(A.arrayRightDivideEquals(B),R);
+        try_success("arrayTimesEquals... ","");
+      } catch ( java.lang.RuntimeException e ) {
+        errorCount = try_failure(errorCount,"arrayTimesEquals... ","(A = R, A = A.*B, but A./B != R)");
+      }
+
+/**   
+      I/O methods:
+         read
+         print
+         serializable:
+           writeObject
+           readObject
+**/
+      print("\nTesting I/O methods...\n");
+         try {
+            DecimalFormat fmt = new DecimalFormat("0.0000E00");
+            PrintWriter FILE = new PrintWriter(new FileOutputStream("JamaTestMatrix.out"));
+            A.print(FILE,fmt,10);
+            FILE.close();
+            R = Matrix.read(new BufferedReader(new FileReader("JamaTestMatrix.out")));
+            if (A.minus(R).norm1() < .001 ) {
+               try_success("print()/read()...","");
+            } else {
+               errorCount = try_failure(errorCount,"print()/read()...","Matrix read from file does not match Matrix printed to file");
+            }
+         } catch ( java.io.IOException ioe ) {
+           warningCount = try_warning(warningCount,"print()/read()...","unexpected I/O error, unable to run print/read test;  check write permission in current directory and retry");
+         } catch(Exception e) {
+            try {
+               e.printStackTrace(System.out);
+               warningCount = try_warning(warningCount,"print()/read()...","Formatting error... will try JDK1.1 reformulation...");
+               DecimalFormat fmt = new DecimalFormat("0.0000");
+               PrintWriter FILE = new PrintWriter(new FileOutputStream("JamaTestMatrix.out"));
+               A.print(FILE,fmt,10);
+               FILE.close();
+               R = Matrix.read(new BufferedReader(new FileReader("JamaTestMatrix.out")));
+               if (A.minus(R).norm1() < .001 ) {
+                  try_success("print()/read()...","");
+               } else {
+                  errorCount = try_failure(errorCount,"print()/read() (2nd attempt) ...","Matrix read from file does not match Matrix printed to file");
+               }
+            } catch ( java.io.IOException ioe ) {
+              warningCount = try_warning(warningCount,"print()/read()...","unexpected I/O error, unable to run print/read test;  check write permission in current directory and retry");
+         }
+      }
+
+      R = Matrix.random(A.getRowDimension(),A.getColumnDimension());
+      String tmpname = "TMPMATRIX.serial";
+      try {
+         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(tmpname));
+         out.writeObject(R);
+         ObjectInputStream sin = new ObjectInputStream(new FileInputStream(tmpname));
+         A = (Matrix) sin.readObject();
+ 
+         try {
+            check(A,R);
+            try_success("writeObject(Matrix)/readObject(Matrix)...","");
+         } catch ( java.lang.RuntimeException e ) {
+           errorCount = try_failure(errorCount,"writeObject(Matrix)/readObject(Matrix)...","Matrix not serialized correctly");
+         }
+      } catch ( java.io.IOException ioe ) {
+         warningCount = try_warning(warningCount,"writeObject()/readObject()...","unexpected I/O error, unable to run serialization test;  check write permission in current directory and retry");
+      } catch(Exception e) {
+         errorCount = try_failure(errorCount,"writeObject(Matrix)/readObject(Matrix)...","unexpected error in serialization test");
+      }
+
+/**
+      LA methods:
+         transpose
+         times
+         cond
+         rank
+         det
+         trace
+         norm1
+         norm2
+         normF
+         normInf
+         solve
+         solveTranspose
+         inverse
+         chol
+         eig
+         lu
+         qr
+         svd 
+**/
+
+      print("\nTesting linear algebra methods...\n");
+      A = new Matrix(columnwise,3);
+      T = new Matrix(tvals);
+      T = A.transpose();
+      try {
+         check(A.transpose(),T);
+         try_success("transpose...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"transpose()...","transpose unsuccessful");
+      }
+      A.transpose();
+      try {
+         check(A.norm1(),columnsummax);
+         try_success("norm1...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"norm1()...","incorrect norm calculation");
+      }
+      try {
+         check(A.normInf(),rowsummax);
+         try_success("normInf()...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"normInf()...","incorrect norm calculation");
+      }
+      try {
+         check(A.normF(),Math.sqrt(sumofsquares));
+         try_success("normF...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"normF()...","incorrect norm calculation");
+      }
+      try {
+         check(A.trace(),sumofdiagonals);
+         try_success("trace()...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"trace()...","incorrect trace calculation");
+      }
+      try {
+         check(A.getMatrix(0,A.getRowDimension()-1,0,A.getRowDimension()-1).det(),0.);
+         try_success("det()...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"det()...","incorrect determinant calculation");
+      }
+      SQ = new Matrix(square);
+      try {
+         check(A.times(A.transpose()),SQ);
+         try_success("times(Matrix)...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"times(Matrix)...","incorrect Matrix-Matrix product calculation");
+      }
+      try {
+         check(A.times(0.),Z);
+         try_success("times(double)...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"times(double)...","incorrect Matrix-scalar product calculation");
+      }
+
+      A = new Matrix(columnwise,4);
+      QRDecomposition QR = A.qr();
+      R = QR.getR();
+      try {
+         check(A,QR.getQ().times(R));
+         try_success("QRDecomposition...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"QRDecomposition...","incorrect QR decomposition calculation");
+      }
+      SingularValueDecomposition SVD = A.svd();
+      try {
+         check(A,SVD.getU().times(SVD.getS().times(SVD.getV().transpose())));
+         try_success("SingularValueDecomposition...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"SingularValueDecomposition...","incorrect singular value decomposition calculation");
+      }
+      DEF = new Matrix(rankdef);
+      try {
+         check(DEF.rank(),Math.min(DEF.getRowDimension(),DEF.getColumnDimension())-1);
+         try_success("rank()...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"rank()...","incorrect rank calculation");
+      }
+      B = new Matrix(condmat);
+      SVD = B.svd(); 
+      double [] singularvalues = SVD.getSingularValues();
+      try {
+         check(B.cond(),singularvalues[0]/singularvalues[Math.min(B.getRowDimension(),B.getColumnDimension())-1]);
+         try_success("cond()...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"cond()...","incorrect condition number calculation");
+      }
+      int n = A.getColumnDimension();
+      A = A.getMatrix(0,n-1,0,n-1);
+      A.set(0,0,0.);
+      LUDecomposition LU = A.lu();
+      try {
+         check(A.getMatrix(LU.getPivot(),0,n-1),LU.getL().times(LU.getU()));
+         try_success("LUDecomposition...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"LUDecomposition...","incorrect LU decomposition calculation");
+      }
+      X = A.inverse();
+      try {
+         check(A.times(X),Matrix.identity(3,3));
+         try_success("inverse()...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"inverse()...","incorrect inverse calculation");
+      }
+      O = new Matrix(SUB.getRowDimension(),1,1.0);
+      SOL = new Matrix(sqSolution);
+      SQ = SUB.getMatrix(0,SUB.getRowDimension()-1,0,SUB.getRowDimension()-1);
+      try {
+         check(SQ.solve(SOL),O); 
+         try_success("solve()...","");
+      } catch ( java.lang.IllegalArgumentException e1 ) {
+         errorCount = try_failure(errorCount,"solve()...",e1.getMessage());
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"solve()...",e.getMessage());
+      }
+      A = new Matrix(pvals);
+      CholeskyDecomposition Chol = A.chol(); 
+      Matrix L = Chol.getL();
+      try {
+         check(A,L.times(L.transpose()));
+         try_success("CholeskyDecomposition...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"CholeskyDecomposition...","incorrect Cholesky decomposition calculation");
+      }
+      X = Chol.solve(Matrix.identity(3,3));
+      try {
+         check(A.times(X),Matrix.identity(3,3));
+         try_success("CholeskyDecomposition solve()...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"CholeskyDecomposition solve()...","incorrect Choleskydecomposition solve calculation");
+      }
+      EigenvalueDecomposition Eig = A.eig();
+      Matrix D = Eig.getD();
+      Matrix V = Eig.getV();
+      try {
+         check(A.times(V),V.times(D));
+         try_success("EigenvalueDecomposition (symmetric)...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"EigenvalueDecomposition (symmetric)...","incorrect symmetric Eigenvalue decomposition calculation");
+      }
+      A = new Matrix(evals);
+      Eig = A.eig();
+      D = Eig.getD();
+      V = Eig.getV();
+      try {
+         check(A.times(V),V.times(D));
+         try_success("EigenvalueDecomposition (nonsymmetric)...","");
+      } catch ( java.lang.RuntimeException e ) {
+         errorCount = try_failure(errorCount,"EigenvalueDecomposition (nonsymmetric)...","incorrect nonsymmetric Eigenvalue decomposition calculation");
+      }
+
+      print("\nTestMatrix completed.\n");
+      print("Total errors reported: " + Integer.toString(errorCount) + "\n");
+      print("Total warnings reported: " + Integer.toString(warningCount) + "\n");
+   }
+
+   /** private utility routines **/
+
+   /** Check magnitude of difference of scalars. **/
+
+   private static void check(double x, double y) {
+      double eps = Math.pow(2.0,-52.0);
+      if (x == 0 & Math.abs(y) < 10*eps) return;
+      if (y == 0 & Math.abs(x) < 10*eps) return;
+      if (Math.abs(x-y) > 10*eps*Math.max(Math.abs(x),Math.abs(y))) {
+         throw new RuntimeException("The difference x-y is too large: x = " + Double.toString(x) + "  y = " + Double.toString(y));
+      }
+   }
+
+   /** Check norm of difference of "vectors". **/
+
+   private static void check(double[] x, double[] y) {
+      if (x.length == y.length ) {
+         for (int i=0;i<x.length;i++) {
+            check(x[i],y[i]);
+         } 
+      } else {
+         throw new RuntimeException("Attempt to compare vectors of different lengths");
+      }
+   }
+
+   /** Check norm of difference of arrays. **/
+
+   private static void check(double[][] x, double[][] y) {
+      Matrix A = new Matrix(x);
+      Matrix B = new Matrix(y);
+      check(A,B);
+   }
+
+   /** Check norm of difference of Matrices. **/
+
+   private static void check(Matrix X, Matrix Y) {
+      double eps = Math.pow(2.0,-52.0);
+      if (X.norm1() == 0. & Y.norm1() < 10*eps) return;
+      if (Y.norm1() == 0. & X.norm1() < 10*eps) return;
+      if (X.minus(Y).norm1() > 1000*eps*Math.max(X.norm1(),Y.norm1())) {
+         throw new RuntimeException("The norm of (X-Y) is too large: " +  Double.toString(X.minus(Y).norm1()));
+      }
+   }
+
+   /** Shorten spelling of print. **/
+
+   private static void print (String s) {
+      System.out.print(s);
+   }
+
+  /** Print appropriate messages for successful outcome try **/
+
+   private static void try_success (String s,String e) {
+      print(">    " + s + "success\n");
+      if ( e != "" ) {
+        print(">      Message: " + e + "\n");
+      }
+   }
+  /** Print appropriate messages for unsuccessful outcome try **/
+
+   private static int try_failure (int count, String s,String e) {
+      print(">    " + s + "*** failure ***\n>      Message: " + e + "\n");
+      return ++count;
+   }
+
+  /** Print appropriate messages for unsuccessful outcome try **/
+
+   private static int try_warning (int count, String s,String e) {
+      print(">    " + s + "*** warning ***\n>      Message: " + e + "\n");
+      return ++count;
+   }
+
+   /** Print a row vector. **/
+
+   private static void print(double[] x, int w, int d) {
+      // Use format Fw.d for all elements.
+      System.out.print("\n");
+      new Matrix(x,1).print(w,d);
+      print("\n");
+   }
+
+}
diff --git a/Jama/util/Maths.java b/Jama/util/Maths.java
new file mode 100644
index 0000000..26886f6
--- /dev/null
+++ b/Jama/util/Maths.java
@@ -0,0 +1,20 @@
+package Jama.util;
+
+public class Maths {
+
+   /** sqrt(a^2 + b^2) without under/overflow. **/
+
+   public static double hypot(double a, double b) {
+      double r;
+      if (Math.abs(a) > Math.abs(b)) {
+         r = b/a;
+         r = Math.abs(a)*Math.sqrt(1+r*r);
+      } else if (b != 0) {
+         r = a/b;
+         r = Math.abs(b)*Math.sqrt(1+r*r);
+      } else {
+         r = 0.0;
+      }
+      return r;
+   }
+}
diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..8f5e250
--- /dev/null
+++ b/META-INF/MANIFEST.MF
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Ant-Version: Apache Ant 1.8.1
+Created-By: 1.6.0_29-b11 (Sun Microsystems Inc.)
+
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..9a61613
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,47 @@
+
+    		
+<project name="VisAD Source" default="help">
+
+    <target name="build" description="compile VisAD, deps, and example files in place">
+		<javac srcdir="." memoryMaximumSize="512M" fork="true" debug="on"/>
+        <rmic classpath="." base=".">
+            <include name="visad/**/*Remote*Impl.class" />
+        </rmic>
+    	<javac srcdir="./examples" classpath="." debug="on"/>
+    </target>
+
+   	<target name="jar" description="create jar of core and dependencies">
+    	<jar destfile="visad.jar">
+    		<fileset dir=".">
+    			<exclude name="examples/**/*"/>
+    			<include name="**/*.class"/>
+    			<include name="**/*.jhf"/>
+    			<include name="**/*.gif"/>
+    			<include name="loci/**/*.txt"/>
+    		</fileset>
+    	</jar>
+    </target>
+
+    <target name="clean" description="remove class files">
+        <delete>
+            <fileset dir=".">
+                <include name="**/*.class"/>
+                <include name="visad.jar"/>
+            </fileset>
+        </delete>
+    </target>
+    
+    <target name="help" description="print help">
+    	<echo>
+Examples are in ./examples
+
+Build and jar VisAD, VisAD RMI, and dependencies:
+    ant build jar
+    		
+Run Examples:
+    java -cp visad.jar:examples DisplayTest
+    	</echo>
+    </target>
+</project>
+    		
+    	
\ No newline at end of file
diff --git a/debian/TODO b/debian/TODO
deleted file mode 100644
index 9ebf4ba..0000000
--- a/debian/TODO
+++ /dev/null
@@ -1,15 +0,0 @@
-hdf5lib is a convinient copy from:
-
-http://www.hdfgroup.org/ftp/HDF5/hdf-java/src/hdf-java/ncsa/hdf/hdf5lib/
-
-Apparently the copyright changed around 2006 (same license AFAIK)
-
-ftp://ftp.hdfgroup.org/HDF5/current/src/unpacked/COPYING
-
-----
-
-What is Jama where is this coming from ? What license ?
-
----
-
-Convinient copy of netcdf ??
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 056d798..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,7 +0,0 @@
-visad (2.0-1) UNRELEASED; urgency=low
-
-  * Initial Debian Upload (Closes: #641399)
-    This package was never uploaded and is not needed as dependency for any
-    Debian Med package
-
- -- Mathieu Malaterre <mathieu.malaterre at gmail.com>  Mon, 12 Sep 2011 09:56:16 +0200
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 249733e..0000000
--- a/debian/control
+++ /dev/null
@@ -1,56 +0,0 @@
-Source: visad
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Mathieu Malaterre <mathieu.malaterre at gmail.com>
-Section: java
-Priority: optional
-Build-Depends: debhelper (>= 10),
-               javahelper
-Build-Depends-Indep: default-jdk,
-                     default-jdk-doc,
-                     ant,
-                     libjava3d-java,
-                     libvecmath-java,
-                     libopendap-java
-Standards-Version: 3.9.8
-Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/visad/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/visad/trunk/
-Homepage: http://www.ssec.wisc.edu/~billh/visad.html
-
-Package: libvisad-java
-Architecture: all
-Depends: ${java:Depends},
-         ${misc:Depends}
-Recommends: ${java:Recommends}
-Description: Visualization for Algorithm Development
- VisAD is a Java component library for interactive and collaborative
- visualization and analysis of numerical data. The name VisAD is an acronym for
- "Visualization for Algorithm Development". The system combines:
- .
- The use of pure Java for platform independence and to support data sharing and
- real-time collaboration among geographically distributed users. Support for
- distributed computing is integrated at the lowest levels of the system using
- Java RMI distributed objects.
- .
- A general mathematical data model that can be adapted to virtually any
- numerical data, that supports data sharing among different users, different
- data sources and different scientific disciplines, and that provides
- transparent access to data independent of storage format and location (i.e.,
- memory, disk or remote). The data model has been adapted to netCDF, HDF-5,
- FITS, HDF-EOS, McIDAS, Vis5D, GIF, JPEG, TIFF, QuickTime, ASCII and many other
- file formats.
- .
- A general display model that supports interactive 3-D, data fusion, multiple
- data views, direct manipulation, collaboration, and virtual reality. The
- display model has been adapted to Java3D and Java2D and used in an ImmersaDesk
- virtual reality display.
- .
- Data analysis and computation integrated with visualization to support
- computational steering and other complex interaction modes.
- .
- Support for two distinct communities: developers who create domain- specific
- systems based on VisAD, and users of those domain-specific systems. VisAD is
- designed to support a wide variety of user interfaces, ranging from simple data
- browser applets to complex applications that allow groups of scientists to
- collaboratively develop data analysis algorithms.
- .
- Developer extensibility in as many ways as possible.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index ad8e856..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,369 +0,0 @@
-Format: http://dep.debian.net/deps/dep5/
-Upstream-Name: VisAD
-Source: http://www.ssec.wisc.edu/~billh/visad.html
-
-TODO: Jama/**.java ??
-
-File: ucar/**.java
-Copyright: © 1997-2000 Unidata Program Center/University Corporation for
- Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
- support at unidata.ucar.edu.
-License: GPL 2.1
- This library is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 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 Lesser
- General Public License for more details.
- 
- You should have received a copy of the GNU Lesser General Public License
- along with this library; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
-
-File: nom/**.java
-Copyright: © Thomas McGlynn 1997-1998.
-License:
- This code may be used for any purpose, non-commercial
- or commercial so long as this copyright notice is retained
- in the source code or included in or referred to in any
- derived software.
-
-File: ncsa/**.java
-Copyright: © 2006-2011 by The HDF Group.
- © 1998-2006 by the Board of Trustees of the University of Illinois.
-License:
- All rights reserved.
- 
- Redistribution and use in source and binary forms, with or without 
- modification, are permitted for any purpose (including commercial purposes) 
- 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 materials provided with the distribution.
- 
- 3. In addition, redistributions of modified forms of the source or binary 
-    code must carry prominent notices stating that the original code was 
-    changed and the date of the change.
- 
- 4. All publications or advertising materials mentioning features or use of 
-    this software are asked, but not required, to acknowledge that it was 
-    developed by The HDF Group and by the National Center for Supercomputing 
-    Applications at the University of Illinois at Urbana-Champaign and 
-    credit the contributors.
- 
- 5. Neither the name of The HDF Group, the name of the University, nor the 
-    name of any Contributor may be used to endorse or promote products derived 
-    from this software without specific prior written permission from 
-    The HDF Group, the University, or the Contributor, respectively.
- 
- DISCLAIMER: 
- THIS SOFTWARE IS PROVIDED BY THE HDF GROUP AND THE CONTRIBUTORS 
- "AS IS" WITH NO WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED.  In no 
- event shall The HDF Group or the Contributors be liable for any damages 
- suffered by the users arising out of the use of this software, even if 
- advised of the possibility of such damage. 
- 
- -----------------------------------------------------------------------------
- -----------------------------------------------------------------------------
- 
- Contributors:   National Center for Supercomputing Applications (NCSA) at 
- the University of Illinois, Fortner Software, Unidata Program Center (netCDF), 
- The Independent JPEG Group (JPEG), Jean-loup Gailly and Mark Adler (gzip), 
- and Digital Equipment Corporation (DEC).
- 
- -----------------------------------------------------------------------------
- 
- Portions of HDF5 were developed with support from the Lawrence Berkeley 
- National Laboratory (LBNL) and the United States Department of Energy 
- under Prime Contract No. DE-AC02-05CH11231.
- 
- -----------------------------------------------------------------------------
- 
- Portions of HDF5 were developed with support from the University of 
- California, Lawrence Livermore National Laboratory (UC LLNL).  
- The following statement applies to those portions of the product and must 
- be retained in any redistribution of source code, binaries, documentation, 
- and/or accompanying materials:
- 
-    This work was partially produced at the University of California, 
-    Lawrence Livermore National Laboratory (UC LLNL) under contract 
-    no. W-7405-ENG-48 (Contract 48) between the U.S. Department of Energy 
-    (DOE) and The Regents of the University of California (University) 
-    for the operation of UC LLNL.
- 
-    DISCLAIMER: 
-    This work was prepared as an account of work sponsored by an agency of 
-    the United States Government. Neither the United States Government nor 
-    the University of California nor any of their employees, makes any 
-    warranty, express or implied, or assumes any liability or responsibility 
-    for the accuracy, completeness, or usefulness of any information, 
-    apparatus, product, or process disclosed, or represents that its use 
-    would not infringe privately- owned rights. Reference herein to any 
-    specific commercial products, process, or service by trade name, 
-    trademark, manufacturer, or otherwise, does not necessarily constitute 
-    or imply its endorsement, recommendation, or favoring by the United 
-    States Government or the University of California. The views and 
-    opinions of authors expressed herein do not necessarily state or reflect 
-    those of the United States Government or the University of California, 
-    and shall not be used for advertising or product endorsement purposes.
- -----------------------------------------------------------------------------
-
-
-Files: loci/formats/in/CRC.java,
- loci/formats/in/CBZip2InputStream.java,
- loci/formats/in/BZip2Constants.java,
- loci/formats/codec/CRC.java,
- loci/formats/codec/BZip2Constants.java
-Copyright: © 2001,2004 The Apache Software Foundation
-License:
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-Comment:
- On Debian systems, you can read the full text of the Apache 2.0
- License in ‘/usr/share/common-licenses/Apache-2.0’.
-
-Files: loci/**.java
-Copyright: © 2005-2011 Melissa Linkert, Curtis Rueden, Chris Allan,
- Eric Kjellman and Brian Loranger.
-License:
- This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-Files: gnu/regexp/*.java
-Copyright: © 1998 Wes Biggs
-License:
- 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 program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-Files: examples/*.java
- visad/**.java
-Copyright: © 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
- Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
- Tommy Jasmin.
-Licence:
- 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., 59 Temple Place - Suite 330, Boston,
- MA 02111-1307, USA
-
-Files: ./HTTPClient/*.java
-Copyright: © 1996-2001  Ronald Tschalär
-License: GPL-2
- 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., 59 Temple Place - Suite 330, Boston,
- MA 02111-1307, USA
-Comment:
- On Debian systems, you can read the full text of the GNU General Public
- License in ‘/usr/share/common-licenses/GPL-2’.
-
-Files: visad/data/hrit/*.java
-Copyright: © 2011 Tommy Jasmin
-License: GPL
- 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 1, or (at your option)
- any later version.
- 
- This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
-Comment:
- On Debian systems, you can read the full text of the GNU General Public
- License in ‘/usr/share/common-licenses/GPL’.
-
-Files: visad/rabin/*.java
-Copyright: © 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom Rink and Dave Glowacki.
-License: GPL
- 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 1, or (at your option)
- any later version.
- 
- This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
-Comment:
- On Debian systems, you can read the full text of the GNU General Public
- License in ‘/usr/share/common-licenses/GPL’.
-
-
-Files: visad/jmet/*.java,
- visad/data/mcidas/*.java
-Copyright: © 2011 Tom Whittaker
-License: GPL
- 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 1, or (at your option)
- any later version.
- 
- This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
-Comment:
- On Debian systems, you can read the full text of the GNU General Public
- License in ‘/usr/share/common-licenses/GPL’.
-
-Files: visad/util/*.java
-Copyright: © 2011 Nick Rasmussen
-License: GPL
- 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 1, or (at your option)
- any later version.
- 
- This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
-Comment:
- On Debian systems, you can read the full text of the GNU General Public
- License in ‘/usr/share/common-licenses/GPL’.
-
-Files: edu/wisc/ssec/mcidas/GEOSnav.java
-Copyright: © 2006, Space Science and Engineering Center, UW-Madison
-C Refer to "McIDAS Software Acquisition and Distribution Policies"
-C in the file  mcidas/data/license.txt
-
-C****************************************************************
-C AUTHOR      : Oscar Alonso Lasheras
-C COPYRIGHT GMV S.A. 2006
-C PROPERTY OF GMV S.A.; ALL RIGHTS RESERVED
-C
-C PROJECT     : S3GICast
-C FILE NAME   : nvxgeos.dlm
-C LANGUAGE    : FORTRAN-77
-C TYPE        : Funcion auxliar para creacion de navegacion GEOS
-C DESCRIPTION : Navega los productos MPEF y OSIS, ademas de los productos
-C               de los servidores FSDX y SAFN-HDF5 en McIDAS
-
-Files: edu/**.java
-Copyright: © 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
- Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
- and others.
-License:
- 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., 59 Temple Place - Suite 330, Boston,
- MA 02111-1307, USA
-
-Files: dods/util/InvalidSwitch.java,
- dods/util/Getopts.java,
- dods/util/OptSwitch.java
-Copyright: © 1997 by Arieh Markel <arieh at selectjobs.com>.  
-Licence: BSD
- 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.
-
- THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
-
-
-Files: debian/*
-Copyright: © 2011 Mathieu Malaterre <mathieu.malaterre at gmail.com>
-License: PD
- The packaging work is in the public domain unless stated otherwise.
diff --git a/debian/get-orig-source b/debian/get-orig-source
deleted file mode 100755
index fcf0a73..0000000
--- a/debian/get-orig-source
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh
-set -ex
-
-PACKAGE=visad
-
-VERSION=${VERSION:-$2}
-TARFILE=${TARFILE:-$3}
-
-if [ -z ${VERSION}]; then
-VERSION=`dpkg-parsechangelog | sed -n -e 's/^Version: \(.*\)-[^-]*$/\1/p'`
-fi
-
-REVISION=`echo $VERSION | sed -e 's/^.*svn//g'`
-
-
-if [ -z ${TARFILE}]; then
-TARFILE=${PACKAGE}_${VERSION}.orig.tar.gz
-fi
-
-FOLDER=${PACKAGE}-${VERSION}
-
-wget -c ftp://ftp.ssec.wisc.edu/pub/visad-2.0/visad_src.jar
-mkdir ${FOLDER}
-cd ${FOLDER}
-jar xvf ../visad_src.jar
-rm -rf dods
-rm -rf gnu
-#rm -rf HTTPClient
-rm -rf ucar
-cd ..
-
-GZIP="--best --no-name" tar czf ${TARFILE} ${FOLDER}
-rm -rf ${FOLDER}
diff --git a/debian/libvisad-java.jlibs b/debian/libvisad-java.jlibs
deleted file mode 100644
index 67dd895..0000000
--- a/debian/libvisad-java.jlibs
+++ /dev/null
@@ -1 +0,0 @@
-visad.jar
diff --git a/debian/patches/build.xml b/debian/patches/build.xml
deleted file mode 100644
index 593a3e5..0000000
--- a/debian/patches/build.xml
+++ /dev/null
@@ -1,50 +0,0 @@
---- /dev/null
-+++ b/build.xml
-@@ -0,0 +1,47 @@
-+
-+    		
-+<project name="VisAD Source" default="help">
-+
-+    <target name="build" description="compile VisAD, deps, and example files in place">
-+		<javac srcdir="." memoryMaximumSize="512M" fork="true" debug="on"/>
-+        <rmic classpath="." base=".">
-+            <include name="visad/**/*Remote*Impl.class" />
-+        </rmic>
-+    	<javac srcdir="./examples" classpath="." debug="on"/>
-+    </target>
-+
-+   	<target name="jar" description="create jar of core and dependencies">
-+    	<jar destfile="visad.jar">
-+    		<fileset dir=".">
-+    			<exclude name="examples/**/*"/>
-+    			<include name="**/*.class"/>
-+    			<include name="**/*.jhf"/>
-+    			<include name="**/*.gif"/>
-+    			<include name="loci/**/*.txt"/>
-+    		</fileset>
-+    	</jar>
-+    </target>
-+
-+    <target name="clean" description="remove class files">
-+        <delete>
-+            <fileset dir=".">
-+                <include name="**/*.class"/>
-+                <include name="visad.jar"/>
-+            </fileset>
-+        </delete>
-+    </target>
-+    
-+    <target name="help" description="print help">
-+    	<echo>
-+Examples are in ./examples
-+
-+Build and jar VisAD, VisAD RMI, and dependencies:
-+    ant build jar
-+    		
-+Run Examples:
-+    java -cp visad.jar:examples DisplayTest
-+    	</echo>
-+    </target>
-+</project>
-+    		
-+    	
diff --git a/debian/patches/fixopendap.patch b/debian/patches/fixopendap.patch
deleted file mode 100644
index 43022fe..0000000
--- a/debian/patches/fixopendap.patch
+++ /dev/null
@@ -1,771 +0,0 @@
-Index: visad-2.0/visad/data/dods/Adapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Adapter.java	2011-03-11 01:47:36.000000000 +0100
-+++ visad-2.0/visad/data/dods/Adapter.java	2011-09-19 21:56:07.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- import visad.data.*;
- import visad.data.units.Parser;
-Index: visad-2.0/visad/data/dods/VectorAdapterFactory.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/VectorAdapterFactory.java	2011-09-19 21:53:19.000000000 +0200
-+++ visad-2.0/visad/data/dods/VectorAdapterFactory.java	2011-09-19 21:55:26.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/ArrayVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/ArrayVariableAdapter.java	2011-09-19 21:56:40.000000000 +0200
-+++ visad-2.0/visad/data/dods/ArrayVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,8 +22,8 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
--import dods.dap.Server.InvalidParameterException;
-+import opendap.dap.*;
-+import opendap.dap.Server.InvalidParameterException;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.*;
-Index: visad-2.0/visad/data/dods/AttributeAdapterFactory.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/AttributeAdapterFactory.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/AttributeAdapterFactory.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/BaseTypeVectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/BaseTypeVectorAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/BaseTypeVectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/BooleanVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/BooleanVariableAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/BooleanVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/BooleanVectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/BooleanVectorAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/BooleanVectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/ByteAttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/ByteAttributeAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/ByteAttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/ByteValuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/ByteValuator.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/ByteValuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/ByteVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/ByteVariableAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/ByteVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/ByteVectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/ByteVectorAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/ByteVectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/ContainerAttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/ContainerAttributeAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/ContainerAttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import java.util.*;
- import visad.*;
-Index: visad-2.0/visad/data/dods/DODSSource.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/DODSSource.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/DODSSource.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,8 +22,8 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
--import dods.dap.parser.ParseException;
-+import opendap.dap.*;
-+import opendap.dap.parser.ParseException;
- import java.io.*;
- import java.net.*;
- import java.rmi.RemoteException;
-Index: visad-2.0/visad/data/dods/DataFactory.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/DataFactory.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/DataFactory.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.*;
-Index: visad-2.0/visad/data/dods/Float32AttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float32AttributeAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float32AttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/Float32Valuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float32Valuator.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float32Valuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.*;
-Index: visad-2.0/visad/data/dods/Float32VariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float32VariableAdapter.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float32VariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/Float32VectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float32VectorAdapter.java	2011-09-19 21:58:32.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float32VectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/Float64AttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float64AttributeAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float64AttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.util.*;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/Float64Valuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float64Valuator.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float64Valuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.*;
-Index: visad-2.0/visad/data/dods/Float64VariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float64VariableAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float64VariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/Float64VectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Float64VectorAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/Float64VectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.in.ArithProg;
-Index: visad-2.0/visad/data/dods/FloatAttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/FloatAttributeAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/FloatAttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.util.*;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/FloatVectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/FloatVectorAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/FloatVectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.BadFormException;
-Index: visad-2.0/visad/data/dods/GridVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/GridVariableAdapter.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/GridVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import java.util.ArrayList;
- import visad.*;
-Index: visad-2.0/visad/data/dods/GridVariableMapAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/GridVariableMapAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/GridVariableMapAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.lang.ref.WeakReference;
- import java.util.*;
- import java.rmi.RemoteException;
-Index: visad-2.0/visad/data/dods/Int16AttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int16AttributeAdapter.java	2011-09-19 21:58:32.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int16AttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/Int16Valuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int16Valuator.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int16Valuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/Int16VariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int16VariableAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int16VariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/Int16VectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int16VectorAdapter.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int16VectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/Int32AttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int32AttributeAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int32AttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/Int32Valuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int32Valuator.java	2011-09-19 21:58:32.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int32Valuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/Int32VariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int32VariableAdapter.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int32VariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/Int32VectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Int32VectorAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/Int32VectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.BadFormException;
-Index: visad-2.0/visad/data/dods/IntValuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/IntValuator.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/IntValuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.*;
-Index: visad-2.0/visad/data/dods/ListVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/ListVariableAdapter.java	2011-09-19 21:58:32.000000000 +0200
-+++ visad-2.0/visad/data/dods/ListVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.*;
-Index: visad-2.0/visad/data/dods/NumericAttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/NumericAttributeAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/NumericAttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.util.*;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/NumericVectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/NumericVectorAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/NumericVectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.*;
-Index: visad-2.0/visad/data/dods/SequenceVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/SequenceVariableAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/SequenceVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import java.util.ArrayList;
- import java.util.Vector;
-Index: visad-2.0/visad/data/dods/SizeTest.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/SizeTest.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/SizeTest.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- 
- public class SizeTest
- {
-Index: visad-2.0/visad/data/dods/StringAttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/StringAttributeAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/StringAttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import java.util.*;
- import visad.*;
-Index: visad-2.0/visad/data/dods/StringVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/StringVariableAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/StringVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/StructureVariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/StructureVariableAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/StructureVariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.util.ArrayList;
- import java.rmi.RemoteException;
- import visad.*;
-Index: visad-2.0/visad/data/dods/UByteValuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UByteValuator.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/UByteValuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/UInt16AttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt16AttributeAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt16AttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/UInt16Valuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt16Valuator.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt16Valuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/UInt16VariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt16VariableAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt16VariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/UInt16VectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt16VectorAdapter.java	2011-09-19 21:58:32.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt16VectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.BadFormException;
-Index: visad-2.0/visad/data/dods/UInt32AttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt32AttributeAdapter.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt32AttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/UInt32Valuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt32Valuator.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt32Valuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/UInt32VariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt32VariableAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt32VariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/UInt32VectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UInt32VectorAdapter.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/UInt32VectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- 
-Index: visad-2.0/visad/data/dods/UIntValuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UIntValuator.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/UIntValuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/UnknownAttributeAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/UnknownAttributeAdapter.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/UnknownAttributeAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import visad.*;
- 
- /**
-Index: visad-2.0/visad/data/dods/Valuator.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/Valuator.java	2011-09-19 21:58:30.000000000 +0200
-+++ visad-2.0/visad/data/dods/Valuator.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.data.in.*;
-Index: visad-2.0/visad/data/dods/VariableAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/VariableAdapter.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/VariableAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.BadFormException;
-Index: visad-2.0/visad/data/dods/VariableAdapterFactory.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/VariableAdapterFactory.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/VariableAdapterFactory.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.VisADException;
-Index: visad-2.0/visad/data/dods/VectorAccessor.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/VectorAccessor.java	2011-09-19 21:58:31.000000000 +0200
-+++ visad-2.0/visad/data/dods/VectorAccessor.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.*;
- import visad.data.*;
-Index: visad-2.0/visad/data/dods/VectorAdapter.java
-===================================================================
---- visad-2.0.orig/visad/data/dods/VectorAdapter.java	2011-09-19 21:58:29.000000000 +0200
-+++ visad-2.0/visad/data/dods/VectorAdapter.java	2011-09-19 21:58:19.000000000 +0200
-@@ -22,7 +22,7 @@
- 
- package visad.data.dods;
- 
--import dods.dap.*;
-+import opendap.dap.*;
- import java.rmi.RemoteException;
- import visad.data.BadFormException;
- import visad.*;
diff --git a/debian/patches/remove_uncompressinputstream.patch b/debian/patches/remove_uncompressinputstream.patch
deleted file mode 100644
index a40d6d8..0000000
--- a/debian/patches/remove_uncompressinputstream.patch
+++ /dev/null
@@ -1,12 +0,0 @@
-Index: visad-2.0/edu/wisc/ssec/mcidas/adde/AddeURLConnection.java
-===================================================================
---- visad-2.0.orig/edu/wisc/ssec/mcidas/adde/AddeURLConnection.java	2011-09-19 17:36:01.000000000 +0200
-+++ visad-2.0/edu/wisc/ssec/mcidas/adde/AddeURLConnection.java	2011-09-19 17:36:06.000000000 +0200
-@@ -40,7 +40,6 @@
- import java.util.StringTokenizer;
- import java.util.zip.GZIPInputStream;
- import edu.wisc.ssec.mcidas.McIDASUtil;
--import HTTPClient.UncompressInputStream;
- 
- /**
-  * This class extends URLConnection, providing the guts of the
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index 7eb3019..0000000
--- a/debian/patches/series
+++ /dev/null
@@ -1,3 +0,0 @@
-build.xml
-remove_uncompressinputstream.patch
-fixopendap.patch
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 9db3445..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/make -f
-
-JAVA_HOME=/usr/lib/jvm/default-java
-export CLASSPATH=/usr/share/java/j3dcore.jar:/usr/share/java/j3dutils.jar:/usr/share/java/vecmath.jar:/usr/share/java/opendap.jar
-
-%:
-	dh $@ --with javahelper
-
-override_dh_auto_build:
-	ant build jar
-
-get-orig-source:
-	./debian/get-orig-source
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/dods/dap/Attribute.java b/dods/dap/Attribute.java
new file mode 100644
index 0000000..7d88032
--- /dev/null
+++ b/dods/dap/Attribute.java
@@ -0,0 +1,567 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+//
+// -- 7/14/99 Modified by: Nathan Potter (ndp at oce.orst.edu)
+// Added Support For DInt16, DUInt16, DFloat32.
+// Added (and commented out) support for DBoolean.
+// -- 7/14/99 ndp 
+//  
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * An <code>Attribute</code> holds information about a single attribute in an
+ * <code>AttributeTable</code>.  It has a type, and contains either a
+ * <code>Vector</code> of <code>String</code>s containing the attribute's
+ * values, or a reference to an <code>AttributeTable</code>, if the
+ * <code>Attribute</code> is a container.  An <code>Attribute</code> may also
+ * be created as an alias pointing to another <code>Attribute</code> of any
+ * type, including container.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see AttributeTable
+ */
+public class Attribute implements Cloneable {
+
+  /** Unknown attribute type.  This is currently unused. */
+  public static final int UNKNOWN = 1;
+
+  /** Container attribute type.  This Attribute holds an AttributeTable. */
+  public static final int CONTAINER = 2;
+
+  /** Byte attribute type. Holds an unsigned Byte.  */
+  public static final int BYTE = 3;
+
+  /** Int16 attribute type.  Holds a signed Short. */
+  public static final int INT16 = 4;
+
+  /** UInt16 attribute type.  Holds an unsigned Short. */
+  public static final int UINT16 = 5;
+
+  /** Int32 attribute type.  Holds a signed Integer. */
+  public static final int INT32 = 6;
+
+  /** UInt32 attribute type.  Holds an unsigned Integer. */
+  public static final int UINT32 = 7;
+
+  /** Float32 attribute type.  Holds a Float. */
+  public static final int FLOAT32 = 8;
+
+  /** Float64 attribute type.  Holds a Double. */
+   public static final int FLOAT64 = 9;
+
+  /** String attribute type.  Holds a String. */
+  public static final int STRING = 10;
+
+  /** URL attribute type.  Holds a String representing a URL. */
+  public static final int URL = 11;
+
+  /** The type of the attribute. */
+  private int type;
+
+  /** The name of the attribute. */
+  private String name;
+
+  /** True if this <code>Attribute</code> is an alias. */
+  private boolean is_alias;
+
+  /**
+   * If <code>is_alias</code> is true, the name of the <code>Attribute</code>
+   * we are aliased to.
+   */
+  private String aliased_to;
+
+  /** Either an AttributeTable or a Vector of String. */
+  private Object attr;
+
+    /**
+     * Construct a container attribute.
+     *
+     * @param container the <code>AttributeTable</code> container.
+     * @deprecated Use the ctor with the name.
+     */
+    public Attribute(AttributeTable container) {
+	type = CONTAINER;
+	is_alias = false;
+	attr = container;
+    }
+
+    /**
+     * Construct an <code>Attribute</code> with the given type and initial
+     * value.
+     *
+     * @param type the type of attribute to create.  Use one of the type
+     *    constants defined by this class.
+     * @param name the name of the attribute.
+     * @param value the initial value of this attribute.  Use the
+     *    <code>appendValue</code> method to create a vector of values.
+     * @param check if true, check the value and throw
+     * AttributeBadValueException if it's not valid; if false do not check its
+     * validity. 
+     * @exception AttributeBadValueException thrown if the value is not a legal
+     * member of type */
+    public Attribute(int type, String name, String value, boolean check) 
+	throws AttributeBadValueException {
+
+	if (check)
+	    dispatchCheckValue(type, value);
+
+	this.type = type;
+	this.name = name;
+	is_alias = false;
+	attr = new Vector();
+	((Vector)attr).addElement(value);
+    }
+
+    /**
+     * Construct an <code>Attribute</code> with the given type and initial
+     * value. Checks the value of the attribute and throws an exception if
+     * it's not valid.
+     *
+     * @param type the type of attribute to create.  Use one of the type
+     *    constants defined by this class.
+     * @param name the name of the attribute.
+     * @param value the initial value of this attribute.  Use the
+     *    <code>appendValue</code> method to create a vector of values.
+     * @exception AttributeBadValueException thrown if the value is not a legal
+     * member of type */
+    public Attribute(int type, String name, String value) 
+	throws AttributeBadValueException {
+
+  	dispatchCheckValue(type, value);
+
+	this.type = type;
+	this.name = name;
+	is_alias = false;
+	attr = new Vector();
+	((Vector)attr).addElement(value);
+    }
+
+  /**
+   * Construct a container attribute.
+   *
+   * @param container the <code>AttributeTable</code> container.
+   */
+  public Attribute(String name, AttributeTable container) {
+    type = CONTAINER;
+    this.name = name;
+    is_alias = false;
+    attr = container;
+  }
+
+  /**
+   * Construct an attribute aliased to the given name and contents.
+   *
+   * @param aliasedTo the name of the target <code>Attribute</code>.  This is
+   *   necessary because <code>Attribute</code> doesn't know its own name
+   *   (this is handled by the <code>DAS</code> or
+   *   <code>AttributeTable</code> holding it.
+   * @param attr the <code>Attribute</code> to point to.
+   */
+  public Attribute(String aliasedTo, Attribute attr) {
+    this.type = attr.type;
+    this.is_alias = true;
+    this.aliased_to = aliasedTo;
+    this.attr = attr.attr;  // share a reference to the other attribute's data
+  }
+
+  /**
+   * Construct an empty attribute with the given type.
+   *
+   * @param type the type of attribute to create.  Use one of the type
+   *    constants defined by this class, other than <code>CONTAINER</code>.
+   * @exception IllegalArgumentException thrown if 
+   *    <code>type</code> is <code>CONTAINER</code>. To construct an empty
+   *    container attribute, first construct and empty AttributeTable and then
+   *    use that to construct the Attribute.
+   */
+  public Attribute(String name, int type) throws IllegalArgumentException {
+    this.type = type;
+    this.name = name;
+    if(type == CONTAINER)
+      throw new IllegalArgumentException("can't construct Attribute(CONTAINER)");
+    else
+      attr = new Vector();
+  }
+
+  /**
+   * Returns a clone of this <code>Attribute</code>.  A deep copy is performed
+   * on all attribute values.
+   *
+   * @return a clone of this <code>Attribute</code>.
+   */
+  public Object clone() {
+    try {
+      Attribute a = (Attribute)super.clone();
+      // assume type, is_alias, and aliased_to have been cloned already
+      if(type == CONTAINER)
+	a.attr = ((AttributeTable)attr).clone();
+      else
+	a.attr = ((Vector)attr).clone();
+      return a;
+    } catch (CloneNotSupportedException e) {
+      // this shouldn't happen, since we are Cloneable
+      throw new InternalError();
+    }
+  }
+
+  /**
+   * Returns the attribute type as a <code>String</code>.
+   *
+   * @return the attribute type <code>String</code>.
+   */
+  public final String getTypeString() {
+    switch(type) {
+    case CONTAINER: return "Container";
+    case BYTE: return "Byte";
+    case INT16: return "Int16";
+    case UINT16: return "UInt16";
+    case INT32: return "Int32";
+    case UINT32: return "UInt32";
+    case FLOAT32: return "Float32";
+    case FLOAT64: return "Float64";
+    case STRING: return "String";
+    case URL: return "Url";
+//    case BOOLEAN: return "Boolean";
+    default: return "";
+    }
+  }
+
+  /**
+   * Returns the attribute type constant.
+   *
+   * @return the attribute type constant.
+   */
+  public final int getType() {
+    return type;
+  }
+  
+  /**
+   * Returns the attribute's name.
+   *
+   * @return the attribute name.
+   */
+  public final String getName() {
+    return name;
+  }
+
+  /**
+   * Returns true if the attribute is a container.
+   * @return true if the attribute is a container.
+   */
+  public final boolean isContainer() {
+    return (type == CONTAINER);
+  }
+
+  /**
+   * Returns true if the attribute is an alias.
+   * @return true if the attribute is an alias.
+   */
+  public final boolean isAlias() {
+    return is_alias;
+  }
+
+  /**
+   * Returns the name of the attribute aliased to.
+   * @return the name of the attribute aliased to.
+   */
+  public final String getAliasedTo() {
+    return aliased_to;
+  }
+
+  /**
+   * Returns the <code>AttributeTable</code> container.
+   * @return the <code>AttributeTable</code> container.
+   */
+  public final AttributeTable getContainer() {
+    return (AttributeTable)attr;
+  }
+
+  /**
+   * Returns the values of this attribute as an <code>Enumeration</code>
+   * of <code>String</code>.
+   *
+   * @return an <code>Enumeration</code> of <code>String</code>.
+   */
+  public final Enumeration getValues() {
+    return ((Vector)attr).elements();
+  }
+
+  /**
+   * Returns the attribute value at <code>index</code>.
+   *
+   * @param index the index of the attribute value to return.
+   * @return the attribute <code>String</code> at <code>index</code>.
+   */
+  public final String getValueAt(int index) {
+    return (String)((Vector)attr).elementAt(index);
+  }
+
+  /**
+   * Append a value to this attribute. Always checks the validity of the
+   * attribute's value.
+   *
+   * @param value the attribute <code>String</code> to add.
+   * @exception AttributeBadValueException thrown if the value is not a legal
+   *     member of type
+   */
+  public final void appendValue(String value) 
+      throws AttributeBadValueException {
+      appendValue(value, true);
+  }
+
+  /**
+   * Append a value to this attribute.
+   *
+   * @param value the attribute <code>String</code> to add.
+   * @param check if true, check the validity of he attribute's value, if
+   * false don't.
+   * @exception AttributeBadValueException thrown if the value is not a legal
+   *     member of type
+   */
+  public final void appendValue(String value, boolean check) 
+      throws AttributeBadValueException {
+
+      if (check)
+	  dispatchCheckValue(type, value);
+
+      ((Vector)attr).addElement(value);
+  }
+
+  /**
+   * Remove the <code>i</code>'th <code>String</code> from this attribute.
+   *
+   * @param index the index of the value to remove.
+   */
+  public final void deleteValueAt(int index) {
+    ((Vector)attr).removeElementAt(index);
+  }
+
+    /**
+     * Check if the value is legal for a given type.
+     *
+     * @param type the type of the value.
+     * @param value the value <code>String</code>.
+     * @exception AttributeBadValueException if the value is not a legal
+     *    member of type
+     */
+    private static void dispatchCheckValue(int type, String value)
+	throws AttributeBadValueException {
+ 
+	switch (type) {
+
+	  case BYTE:
+	    if(!checkByte(value))
+		throw new AttributeBadValueException("`" + value + "' is not a Byte value.");
+	    break;
+
+	  case INT16:
+	    if(!checkShort(value))
+		throw new AttributeBadValueException("`" + value + "' is not an Int16 value.");
+	    break;
+
+	  case UINT16:
+	    if(!checkUShort(value))
+		throw new AttributeBadValueException("`" + value + "' is not an UInt16 value.");
+	    break;
+
+	  case INT32:
+	    if(!checkInt(value))
+		throw new AttributeBadValueException("`" + value + "' is not an Int32 value.");
+	    break;
+
+	  case UINT32:
+	    if(!checkUInt(value))
+		throw new AttributeBadValueException("`" + value + "' is not an UInt32 value.");
+	    break;
+
+	  case FLOAT32:
+	    if(!checkFloat(value))
+		throw new AttributeBadValueException("`" + value + "' is not a Float32 value.");
+	    break;
+
+	  case FLOAT64:
+	    if(!checkDouble(value))
+		throw new AttributeBadValueException("`" + value + "' is not a Float64 value.");
+	    break;
+
+	    //    case BOOLEAN:
+	    //      if(!checkBoolean(value))
+	    //	throw new AttributeBadValueException("`" + value + "' is not a Boolean value.");
+	    //      break;
+
+	  default:
+	    // Assume UNKNOWN, CONTAINER, STRING, and URL are okay.
+	}
+    }
+
+
+    /**
+     * Check if string is a valid Byte.
+     * @param s the <code>String</code> to check.
+     * @return true if the value is legal.
+     */
+    private static final boolean checkByte(String s) 
+	throws AttributeBadValueException {
+	try {
+	    // Byte.parseByte() can't be used because values > 127 are allowed
+	    short val = Short.parseShort(s);
+	    if (val > 0xFF)
+		return false;
+	    else
+		return true;
+	}
+	catch (NumberFormatException e) {
+	    throw new AttributeBadValueException("`" + s + "' is not a Byte value.");
+	}
+    }
+
+  /**
+   * Check if string is a valid Int16.
+   * @param s the <code>String</code> to check.
+   * @return true if the value is legal.
+   */
+  private static final boolean checkShort(String s) {
+    try {
+	Short.parseShort(s);
+	return true;
+    }
+    catch (NumberFormatException e) {
+	return false;
+    }
+  }
+
+  /**
+   * Check if string is a valid UInt16.
+   * @param s the <code>String</code> to check.
+   * @return true if the value is legal.
+   */
+  private static final boolean checkUShort(String s) {
+    // Note: Because there is no Unsigned class in Java, use Long instead.
+    try {
+	long val = Long.parseLong(s);
+	if (val > 0xFFFFL)
+	  return false;
+	else
+	  return true;
+    }
+    catch (NumberFormatException e) {
+	return false;
+    }
+  }
+
+  /**
+   * Check if string is a valid Int32.
+   * @param s the <code>String</code> to check.
+   * @return true if the value is legal.
+   */
+  private static final boolean checkInt(String s) {
+    try {
+	Integer.parseInt(s);
+	return true;
+    }
+    catch (NumberFormatException e) {
+	return false;
+    }
+  }
+
+  /**
+   * Check if string is a valid UInt32.
+   * @param s the <code>String</code> to check.
+   * @return true if the value is legal.
+   */
+  private static final boolean checkUInt(String s) {
+    // Note: Because there is no Unsigned class in Java, use Long instead.
+    try {
+	long val = Long.parseLong(s);
+	if (val > 0xFFFFFFFFL)
+	  return false;
+	else
+	  return true;
+    }
+    catch (NumberFormatException e) {
+	return false;
+    }
+  }
+
+  /**
+   * Check if string is a valid Float32.
+   * @param s the <code>String</code> to check.
+   * @return true if the value is legal.
+   */
+  private static final boolean checkFloat(String s) {
+    try {
+	Float.valueOf(s);
+	return true;
+    }
+    catch (NumberFormatException e) {
+	if (s.equalsIgnoreCase("nan") || s.equalsIgnoreCase("inf"))
+	    return true;
+
+	return false;
+    }
+  }
+
+  /**
+   * Check if string is a valid Float64.
+   * @param s the <code>String</code> to check.
+   * @return true if the value is legal.
+   */
+  private static final boolean checkDouble(String s) {
+    try {
+	Double.valueOf(s);
+	return true;
+    }
+    catch (NumberFormatException e) {
+	if (s.equalsIgnoreCase("nan") || s.equalsIgnoreCase("inf"))
+	    return true;
+
+	return false;
+    }
+  }
+
+/* NOTE: THis method is flawed think of a better way to do this 
+	BEFORE you uncomment it!! The valueOf() method for 
+	Boolean returns true if and only if the string passed 
+	is equal to (ignoring case) to "true"
+	Otherwise it returns false... A lousy test., UNLESS
+	the JAVA implementation of a Boolean string works for
+	you.
+
+   * Check if string is a valid Boolean.
+   * @param s the <code>String</code> to check.
+   * @return true if the value is legal.
+   
+  private static final boolean checkBoolean(String s) {
+    try {
+	Boolean.valueOf(s);
+	return true;
+    }
+    catch (NumberFormatException e) {
+	return false;
+    }
+  }
+
+*/
+}
+
+// $Log: not supported by cvs2svn $
+// Revision 1.5  2002/05/30 23:24:58  jimg
+// I added methods that provide a way to add attribute values without checking
+// their type/value validity. This provides a way to add bad values in a
+// *_dods_error attribute container so that attributes that are screwed up won't
+// be lost (because they might make sense to a person, for example) or cause the
+// whole DAS to break. The DAS parser (DASParser.jj) uses this new code.
+//
diff --git a/dods/dap/AttributeBadValueException.java b/dods/dap/AttributeBadValueException.java
new file mode 100644
index 0000000..6817e88
--- /dev/null
+++ b/dods/dap/AttributeBadValueException.java
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * Thrown by <code>Attribute</code> when a bad value
+ * (not one of the supported types) is stored in an Attribute.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see Attribute
+ */
+public class AttributeBadValueException extends DASException {
+
+  /**
+   * Construct a <code>AttributeBadValueException</code> with the specified
+   * message.
+   *
+   * @param s the detail message.
+   */
+  public AttributeBadValueException(String s) {
+    super(DODSException.MALFORMED_EXPR,s);
+  }
+
+
+  /**
+   * Construct a <code>AttributeBadValueException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public AttributeBadValueException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/AttributeExistsException.java b/dods/dap/AttributeExistsException.java
new file mode 100644
index 0000000..a98635f
--- /dev/null
+++ b/dods/dap/AttributeExistsException.java
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * Thrown by <code>AttributeTable</code> when an attempt is made to create an
+ * attribute that already exists. Creating an alias with the name of an
+ * existing attribute will also trigger this exception.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see AttributeTable
+ */
+public class AttributeExistsException extends DASException {
+  /**
+   * Construct a <code>AttributeExistsException</code> with the specified
+   * message.
+   *
+   * @param s the detail message.
+   */
+  public AttributeExistsException(String s) {
+    super(DODSException.MALFORMED_EXPR,s);
+  }
+
+
+  /**
+   * Construct a <code>AttributeExistsException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public AttributeExistsException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/AttributeTable.java b/dods/dap/AttributeTable.java
new file mode 100644
index 0000000..0591c2c
--- /dev/null
+++ b/dods/dap/AttributeTable.java
@@ -0,0 +1,382 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.util.Enumeration;
+import dods.util.SortedTable;
+import java.io.*;
+
+/**
+ * An <code>AttributeTable</code> stores a set of names and, for each name,
+ * an <code>Attribute</code> object.  For more information on the types of
+ * data which can be stored in an attribute, including aliases and other
+ * <code>AttributeTable</code> objects, see the documentation for
+ * <code>Attribute</code>.
+ * <p>
+ * The attribute tables have a standard printed representation.  There is a
+ * <code>print</code> method for writing this form and a <code>parse</code>
+ * method for reading the printed form.
+ * <p>
+ * An <code>AttributeTable</code>'s print representation might look like:
+ * <pre>
+ *   String long_name "Weekly Means of Sea Surface Temperature";
+ * </pre>
+ * or
+ * <pre>
+ *   actual_range {
+ *       Float64 min -1.8;
+ *       Float64 max 35.09;
+ *   }
+ * </pre>
+ * or
+ * <pre>
+ *   String Investigators "Cornillon", "Fleirl", "Watts";
+ * </pre>
+ * or
+ * <pre>
+ *   Alias New_Attribute Old_Attribute;
+ * </pre>
+ * Here, <em>long_name</em> and <em>Investigators</em> are
+ * simple attributes, <em>actual_range</em> is a container attribute, and
+ * <em>New_Attribute</em> is an alias pointing to <em>Old_Attribute</em>.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see DAS
+ * @see Attribute
+ */
+public class AttributeTable implements Cloneable {
+    private static final boolean _Debug = false;
+
+    /** A table of Attributes with their names as a key */
+    private SortedTable attr;
+
+    /** What's the name of this table? */
+    private String name;
+
+    /** Create a new empty <code>AttributeTable</code>. 
+	@deprecated */
+    public AttributeTable() {
+	attr = new SortedTable();
+    }
+
+    /** Create a new empty <code>AttributeTable</code>. */
+    public AttributeTable(String name) {
+	this.name = name;
+	attr = new SortedTable();
+    }
+
+    /**
+     * Returns a clone of this <code>AttributeTable</code>.  A deep copy is
+     * performed on all <code>Attribute</code> and <code>AttributeTable</code>
+     * objects inside the <code>AttributeTable</code>.
+     *
+     * @return a clone of this <code>AttributeTable</code>.
+     */
+    public Object clone() {
+	try {
+	    AttributeTable at = (AttributeTable)super.clone();
+	    at.name = name;
+	    at.attr = new SortedTable();
+	    for(int i=0; i<attr.size(); i++) {
+		String key = (String)attr.getKey(i);
+		Attribute element = (Attribute)attr.elementAt(i);
+		// clone element (don't clone key because it's a read-only String)
+		at.attr.put(key, element.clone());
+	    }
+	    return at;
+	} catch (CloneNotSupportedException e) {
+	    // this shouldn't happen, since we are Cloneable
+	    throw new InternalError();
+	}
+    }
+
+  /** Returns the name of this AttributeTable. */
+  public final String getName() {
+    return name;
+  }
+
+  /**
+   * Returns an <code>Enumeration</code> of the attribute names in this
+   * <code>AttributeTable</code>.
+   * Use the <code>getAttribute</code> method to get the
+   * <code>Attribute</code> for a given name.
+   *
+   * @return an <code>Enumeration</code> of <code>String</code>.
+   * @see AttributeTable#getAttribute(String)
+   */
+  public final Enumeration getNames() {
+    return attr.keys();
+  }
+
+  /**
+   * Returns the <code>Attribute</code> which matches name.
+   *
+   * @param name the name of the <code>Attribute</code> to return.
+   * @return the <code>Attribute</code> with the specified name, or null
+   * if there is no matching <code>Attribute</code>.
+   * @see Attribute
+   */
+  public final Attribute getAttribute(String name) {
+    return (Attribute)attr.get(name);
+  }
+
+    /**
+    * Adds an attribute to the table.  If the given name already
+    * refers to an attribute, and the attribute has a vector value,
+    * the given value is appended to the attribute vector.  Calling
+    * this function repeatedly is the way to create an attribute
+    * vector.
+    * <p>
+    * The function throws an exception if the attribute is a
+    * container, or if the type of the input value does not match the
+    * existing attribute's type and the <code>check</code> parameter
+    * is true.  Use the <code>appendContainer</code> method to add container
+    * attributes. 
+    *
+    * @param name The name of the attribute to add or modify.
+    * @param type The type code of the attribute to add or modify.
+    * @param value The value to add to the attribute table.
+    * @param check Check the validity of the attribute's value? 
+    * @exception AttributeExistsException thrown if an Attribute with the same
+    *    name, but a different type was previously defined.
+    * @exception AttributeBadValueException thrown if the value is not a legal
+    *    member of type
+    * @see AttributeTable#appendContainer(String)
+    */
+    public final void appendAttribute(String name, int type, String value,
+				      boolean check)
+	throws AttributeExistsException, AttributeBadValueException {
+
+        Attribute a = (Attribute)attr.get(name);
+
+        if (a != null && (type != a.getType())) {
+
+            // type mismatch error
+            throw new AttributeExistsException("`" + name 
+			   + "' previously defined with a different type.");
+
+        } else if (a != null) {
+
+            a.appendValue(value, check);
+
+        } else {
+
+            a = new Attribute(type, name, value, check);
+            attr.put(name, a);
+        }
+    }
+
+    /**
+    * Adds an attribute to the table.  If the given name already
+    * refers to an attribute, and the attribute has a vector value,
+    * the given value is appended to the attribute vector.  Calling
+    * this function repeatedly is the way to create an attribute
+    * vector.
+    * <p>
+    * The function throws an exception if the attribute is a
+    * container, or if the type of the input value does not match the
+    * existing attribute's type.  Use the <code>appendContainer</code>
+    * method to add container attributes.
+    *
+    * @param name The name of the attribute to add or modify.
+    * @param type The type code of the attribute to add or modify.
+    * @param value The value to add to the attribute table.
+    * @exception AttributeExistsException thrown if an Attribute with the same
+    *    name, but a different type was previously defined.
+    * @exception AttributeBadValueException thrown if the value is not a legal
+    *    member of type
+    * @see AttributeTable#appendContainer(String)
+    */
+    public final void appendAttribute(String name, int type, String value)
+	throws AttributeExistsException, AttributeBadValueException {
+	appendAttribute(name, type, value, true);
+    }
+
+    /**
+    * Create and append an attribute container to the table.
+    * A container is another <code>AttributeTable</code> object.
+    *
+    * @param name the name of the container to add.
+    * @return A pointer to the new <code>AttributeTable</code> object, or null
+    *    if a container by that name already exists.
+    */
+    public final AttributeTable appendContainer(String name) {
+        // return null if name already exists
+        if (attr.get(name) != null)
+            return null;
+
+        AttributeTable at = new AttributeTable(name);
+        Attribute a = new Attribute(name, at);
+        attr.put(name, a);
+        return at;
+    }
+
+    /**
+    * Add an alias to the current table.
+    *
+    * @param alias The alias to insert into the attribute table.
+    * @param name The name of the already-existing attribute to which
+    *    the alias will refer.
+    * @exception NoSuchAttributeException thrown if the existing attribute
+    *    could not be found.
+    * @exception AttributeExistsException thrown if the new alias has the same
+    *    name as an existing attribute.
+    */
+    public final void addAlias(String alias, String name)
+       throws NoSuchAttributeException, AttributeExistsException {
+        // return false if alias already exists.
+        if (attr.get(alias) != null)
+            throw new AttributeExistsException("Could not alias `" + name +
+                                                 "' and `" + alias + "'.");
+
+        // Make sure name exists.
+        Attribute a = (Attribute)attr.get(name);
+        if (a == null)
+            throw new NoSuchAttributeException("Could not alias `" + name +
+					       "' and `" + alias + "'.");
+
+        Attribute newAttr = new Attribute(name, a);
+        attr.put(alias, newAttr);
+    }
+
+    /**
+    * Delete the attribute named <code>name</code>.
+    *
+    * @param name The name of the attribute to delete.  This can be an
+    *    attribute of any type, including containers.
+    */
+    public final void delAttribute(String name) {
+        attr.remove(name);
+    }
+
+    /**
+    * Delete the attribute named <code>name</code>.  If the attribute has a
+    * vector value, delete the <code>i</code>'th element of the vector.
+    *
+    * @param name The name of the attribute to delete.  This can be an
+    *    attribute of any type, including containers.
+    * @param i If the named attribute is a vector, and <code>i</code> is
+    *   non-negative, the <code>i</code>'th entry in the vector is deleted.
+    *   If <code>i</code> equals -1, the entire attribute is deleted.
+    * @see AttributeTable#delAttribute(String)
+    */
+    public final void delAttribute(String name, int i) {
+
+        if (i == -1) {  // delete the whole attribute
+
+            attr.remove(name);
+
+        }
+        else {
+
+            Attribute a = (Attribute)attr.get(name);
+
+            if (a != null) {
+
+                if (a.isContainer()) {
+
+                    attr.remove(name);  // delete the entire container
+
+                }
+                else {
+
+                    a.deleteValueAt(i);
+
+                }
+            }
+        }
+    }
+
+    /**
+    * Print the attribute table on the given <code>PrintWriter</code>.
+    *
+    * @param os the <code>PrintWriter</code> to use for output.
+    * @param pad the number of spaces to indent each line.
+    */
+    public void print(PrintWriter os, String pad) {
+
+        if (_Debug) System.out.println("Entered AttributeTable.print()");
+
+        for (Enumeration e = getNames(); e.hasMoreElements() ;) {
+
+            String name = (String)e.nextElement();
+            Attribute a = getAttribute(name);
+
+            if (a.isAlias()) {
+                if (_Debug) System.out.println("  Attribute \"" + name + "\" is an Alias.");
+
+                os.println(pad + "Alias " + name + " " + a.getAliasedTo() + ";");
+
+            }
+            else {
+
+                if (a.isContainer()) {
+                    if (_Debug) System.out.println("  Attribute \"" + name + "\" is a Container.");
+                    os.println(pad + name + " {");
+                    ((AttributeTable)a.getContainer()).print(os, pad + "    ");
+                    os.println(pad + "}");
+                }
+                else {
+                    if (_Debug) System.out.println("    Printing Attribute \"" + name + "\".");
+
+                    os.print(pad + a.getTypeString() + " " + name + " ");
+                    Enumeration es = a.getValues();
+                    String val = (String)es.nextElement();  // get first element
+
+                    while(es.hasMoreElements()) {  // lookahead one element
+                        os.print(val + ", ");
+                        val = (String)es.nextElement();
+                    }
+                    os.println(val + ";");  // print last element
+                }
+            }
+        }
+        os.flush();
+        if (_Debug) System.out.println("Leaving AttributeTable.print()");
+    }
+
+    /**
+    * Print the attribute table on the given <code>OutputStream</code>.
+    *
+    * @param os the <code>OutputStream</code> to use for output.
+    * @param pad the number of spaces to indent each line.
+    */
+    public final void print(OutputStream os, String pad) {
+        print(new PrintWriter(new BufferedWriter(new OutputStreamWriter(os))), pad);
+    }
+
+    /**
+    * Print the attribute table on the given <code>PrintWriter</code> with
+    * four spaces of indentation.
+    *
+    * @param os the <code>PrintWriter</code> to use for output.
+    */
+    public final void print(PrintWriter os) {
+        print(os, "    ");
+    }
+
+    /**
+    * Print the attribute table on the given <code>OutputStream</code> with
+    * four spaces of indentation.
+    *
+    * @param os the <code>OutputStream</code> to use for output.
+    */
+    public final void print(OutputStream os) {
+        print(os, "    ");
+    }
+}
+
+// $Log: not supported by cvs2svn $
+// Revision 1.7  2002/05/30 23:25:57  jimg
+// I added methods that provide a way to add attribues without the usual
+// type/value checking. See today's log in Attribute.java for more info.
+//
diff --git a/dods/dap/BadSemanticsException.java b/dods/dap/BadSemanticsException.java
new file mode 100644
index 0000000..9863064
--- /dev/null
+++ b/dods/dap/BadSemanticsException.java
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * Thrown by <code>BaseType</code> when the <code>checkSemantics</code>
+ * method is called and the variable is not valid (the name is null or some
+ * other semantic violation).
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ */
+public class BadSemanticsException extends DDSException {
+  /**
+   * Construct a <code>BadSemanticsException</code> with the specified detail
+   * message.
+   *
+   * @param s the detail message.
+   */
+  public BadSemanticsException(String s) {
+    super(DODSException.MALFORMED_EXPR,s);
+  }
+
+
+  /**
+   * Construct a <code>BadSemanticsException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public BadSemanticsException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/BaseType.java b/dods/dap/BaseType.java
new file mode 100644
index 0000000..74d14ee
--- /dev/null
+++ b/dods/dap/BaseType.java
@@ -0,0 +1,437 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * This abstract class defines the basic data type features for the DODS data
+ * access protocol (DAP) data types. All of the DAP type classes
+ * (<code>DFloat64</code>, <code>DArray</code>, etc.) subclass it or one of
+ * its two abstract descendents, <code>DVector</code> or
+ * <code>DConstructor</code>.
+ * <p>
+ * These classes and their methods give a user the capacity to set up
+ * sophisticated data types. They do <em>not</em> provide sophisticated ways to
+ * access and use this data. On the server side, in many cases, the class
+ * instances will have no data in them at all until the <code>serialize</code>
+ * method is called to send data to the client. On the client side, most DODS
+ * application programs will unpack the data promptly into whatever local
+ * data structure the programmer deems the most useful.
+ * <p>
+ * Descendents of this class should implement the <code>ClientIO</code>
+ * interface.  That interface defines a <code>deserialize</code> method used
+ * by a DODS client to retrieve the variable's declaration and value(s) from
+ * a DODS server.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see DDS
+ * @see ClientIO
+ */
+public abstract class BaseType implements Cloneable {
+  /** The name of this variable. */
+  private String _name;
+
+  /** The parent (container class) of this object, if one exists */
+  private BaseType _myParent;
+
+  /** Constructs a new <code>BaseType</code> with no name. */
+  public BaseType() {
+    this("");
+  }
+
+  /**
+   * Constructs a new <code>BaseType</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public BaseType(String n) {
+    _name = n;
+    _myParent = null;
+  }
+
+  /**
+   * Returns a clone of this <code>BaseType</code>.  A deep copy is performed
+   * on all data inside the variable.
+   *
+   * @return a clone of this <code>BaseType</code>.
+   */
+  public Object clone() {
+    try {
+      BaseType bt = (BaseType)super.clone();
+      return bt;
+    } catch (CloneNotSupportedException e) {
+      // this shouldn't happen, since we are Cloneable
+      throw new InternalError();
+    }
+  }
+
+  /**
+   * Returns the name of the class instance.
+   * @return the name of the class instance.
+   */
+  public final String getName() {
+    return _name;
+  }
+
+  /**
+   * Sets the name of the class instance.
+   * @param n the name of the class instance.
+   */
+  public final void setName(String n) {
+    _name = n;
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  abstract public String getTypeName();
+
+  /**
+   * Returns the number of variables contained in this object. For simple and
+   * vector type variables, it always returns 1. To count the number
+   * of simple-type variable in the variable tree rooted at this variable, set
+   * <code>leaves</code> to <code>true</code>.
+   *
+   * @param leaves If true, count all the simple types in the `tree' of
+   *    variables rooted at this variable.
+   * @return the number of contained variables.
+   */
+  public int elementCount(boolean leaves) {
+    return 1;
+  }
+
+  /**
+   * Returns the number of variables contained in this object. For simple and
+   * vector type variables, it always returns 1.
+   *
+   * @return the number of contained variables.
+   */
+  public final int elementCount() {
+    return elementCount(false);
+  }
+
+    /**
+    * Returns a string representation of the variables value. This
+    * is really foreshadowing functionality for Server types, but
+    * as it may come in useful for clients it is added here. Simple 
+    * types (example: DFloat32) will return a single value. DConstuctor
+    * and DVector types will be flattened. DStrings and DURL's will 
+    * have double quotes around them.
+    */
+/*    abstract public void toASCII(PrintWriter pw, boolean addName, String rootName, boolean newLine);
+
+    String toASCIIAddRootName(PrintWriter pw, boolean addName, String rootName){
+    
+        if(addName){
+            rootName = toASCIIFlatName(rootName);
+            pw.print(rootName);
+        }	
+	return(rootName);
+
+    }
+
+    String toASCIIFlatName(String rootName){
+        String s;
+        if(rootName != null){
+            s = rootName +  "." + getName();
+        }
+        else {
+            s = getName();
+        }
+	return(s);
+    }
+*/
+
+  /**
+   * Write the variable's declaration in a C-style syntax. This
+   * function is used to create textual representation of the Data
+   * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+   * information about this structure.
+   *
+   * @param os The <code>PrintWriter</code> on which to print the
+   *    declaration.
+   * @param space Each line of the declaration will begin with the
+   *    characters in this string.  Usually used for leading spaces.
+   * @param print_semi a boolean value indicating whether to print a
+   *    semicolon at the end of the declaration.
+   * @param constrained a boolean value indicating whether to print
+   *    the declartion dependent on the projection information. <b>This
+   *    is only used by Server side code.</b>
+   * @see DDS
+   */
+  public void printDecl(PrintWriter os, String space,
+			boolean print_semi, boolean constrained) {
+			
+    //System.out.println("BaseType.printDecl()...");
+    os.print(space + getTypeName() + " " + getName());
+    if (print_semi)
+      os.println(";");
+  }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                        boolean print_semi) {
+
+        printDecl(os,space,print_semi,false);
+
+    }
+
+  /**
+   * Print the variable's declaration.  Same as
+   * <code>printDecl(os, space, true)</code>.
+   *
+   * @param os The <code>PrintWriter</code> on which to print the
+   *    declaration.
+   * @param space Each line of the declaration will begin with the
+   *    characters in this string.  Usually used for leading spaces.
+   * @see DDS#print(PrintWriter)
+   */
+  public final void printDecl(PrintWriter os, String space) {
+    printDecl(os, space, true, false);
+  }
+
+  /**
+   * Print the variable's declaration.  Same as
+   * <code>printDecl(os, "    ", true)</code>.
+   *
+   * @param os The <code>PrintWriter</code> on which to print the
+   *    declaration.
+   * @see DDS#print(PrintWriter)
+   */
+  public final void printDecl(PrintWriter os) {
+    printDecl(os, "    ", true, false);
+  }
+
+  /**
+   * Print the variable's declaration using <code>OutputStream</code>.
+   *
+   * @param os The <code>OutputStream</code> on which to print the
+   *    declaration.
+   * @param space Each line of the declaration will begin with the
+   *    characters in this string.  Usually used for leading spaces.
+   * @param print_semi a boolean value indicating whether to print a
+   *    semicolon at the end of the declaration.
+   * @see DDS#print(PrintWriter)
+   */
+  public final void printDecl(OutputStream os, String space,
+			      boolean print_semi, boolean constrained) {
+    PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+    printDecl(pw, space, print_semi,constrained);
+    pw.flush();
+  }
+
+  /**
+   * Print the variable's declaration using <code>OutputStream</code>.
+   *
+   * @param os The <code>OutputStream</code> on which to print the
+   *    declaration.
+   * @param space Each line of the declaration will begin with the
+   *    characters in this string.  Usually used for leading spaces.
+   * @param print_semi a boolean value indicating whether to print a
+   *    semicolon at the end of the declaration.
+   * @see DDS#print(PrintWriter)
+   */
+  public final void printDecl(OutputStream os, String space,
+			      boolean print_semi) {
+    printDecl(os, space, print_semi,false);
+  }
+
+  /**
+   * Print the variable's declaration.  Same as
+   * <code>printDecl(os, space, true)</code>.
+   *
+   * @param os The <code>OutputStream</code> on which to print the
+   *    declaration.
+   * @param space Each line of the declaration will begin with the
+   *    characters in this string.  Usually used for leading spaces.
+   * @see DDS#print(PrintWriter)
+   */
+  public final void printDecl(OutputStream os, String space) {
+    PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+    printDecl(pw, space);
+    pw.flush();
+  }
+
+  /**
+   * Print the variable's declaration.  Same as
+   * <code>printDecl(os, "    ", true)</code>.
+   *
+   * @param os The <code>OutputStream</code> on which to print the
+   *    declaration.
+   * @see DDS#print(PrintWriter)
+   */
+  public final void printDecl(OutputStream os) {
+    PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+    printDecl(pw);
+    pw.flush();
+  }
+
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   */
+  abstract public void printVal(PrintWriter os, String space,
+				boolean print_decl_p);
+
+  /**
+   * Print the variable's value.  Same as
+   * <code>printVal(os, space, true)</code>.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see DataDDS#printVal(PrintWriter)
+   */
+  public final void printVal(PrintWriter os, String space) {
+    printVal(os, space, true);
+  }
+
+  /**
+   * Print the variable's value using <code>OutputStream</code>.
+   *
+   * @param os the <code>OutputStream</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see DataDDS#printVal(PrintWriter)
+   */
+  public final void printVal(OutputStream os, String space,
+			     boolean print_decl_p) {
+    PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+    printVal(pw, space, print_decl_p);
+    pw.flush();
+  }
+
+  /**
+   * Print the variable's value using <code>OutputStream</code>.
+   *
+   * @param os the <code>OutputStream</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see DataDDS#printVal(PrintWriter)
+   */
+  public final void printVal(OutputStream os, String space) {
+    PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+    printVal(pw, space);
+    pw.flush();
+  }
+
+  /**
+   * Checks for internal consistency.  This is important to check for complex
+   * constructor classes.
+   * For example, an <code>DInt32</code> instance would return false if it had
+   * no name defined.  A <code>DGrid</code> instance might return false for
+   * more complex reasons, such as having Map arrays of the wrong
+   * size or shape.
+   * <p>
+   * This method is used by the <code>DDS</code> class, and will rarely, if
+   * ever, be explicitly called by a DODS application program.  A
+   * variable must pass this test before it is sent, but there may be
+   * many other stages in a retrieve operation where it would fail.
+   *
+   * @param all For complex constructor types (
+   *    <code>DGrid</code>, <code>DSequence</code>, <code>DStructure</code>),
+   *    this flag indicates whether to check the
+   *    semantics of the member variables, too.
+   * @exception BadSemanticsException if semantics are bad, explains why.
+   * @see DDS#checkSemantics(boolean)
+   */
+  public void checkSemantics(boolean all)
+                             throws BadSemanticsException {
+    if (_name == null)
+      throw new BadSemanticsException("BaseType.checkSemantics(): Every variable must have a name");
+  }
+
+  /**
+   * Check semantics.  Same as <code>checkSemantics(false)</code>.
+   * @exception BadSemanticsException if semantics are bad, explains why.
+   * @see BaseType#checkSemantics(boolean)
+   */
+  public final void checkSemantics() throws BadSemanticsException {
+    checkSemantics(false);
+  }
+
+    /**
+    * Constructs a new <code>PrimitiveVector</code> object optimized for the
+    * particular data type of this <code>BaseType</code>.  For example, a
+    * <code>DByte</code> class would create a new
+    * <code>BytePrimitiveVector</code> in this call.  This allows for a very
+    * optimized, yet type-safe, implementation of <code>DVector</code>
+    * functionality.  For non-primitive types, such as
+    * <code>DArray</code>, <code>DGrid</code>, <code>DSequence</code>, and
+    * <code>DStructure</code>, the default implementation returns a
+    * <code>BaseTypePrimitiveVector</code> object which can
+    * deserialize an array of complex types.
+    *
+    * @return a new <code>PrimitiveVector</code> object for the variable type.
+    */
+    public PrimitiveVector newPrimitiveVector() {
+        return new BaseTypePrimitiveVector(this);
+    }
+
+
+    public void setParent(BaseType bt) {
+        _myParent = bt;
+    }
+    
+    public BaseType getParent() {
+        return(_myParent);
+    }
+    
+    public String getLongName(){
+    
+        boolean done = false;
+	
+        BaseType parent = _myParent;
+	
+	String longName = _name;
+	
+	
+        while(parent != null){
+            longName = parent.getName() + "." + longName;
+            parent = parent.getParent();
+        }
+	return(longName);
+    }
+
+
+  
+  
+  
+  
+  
+  
+  
+}
diff --git a/dods/dap/BaseTypeFactory.java b/dods/dap/BaseTypeFactory.java
new file mode 100644
index 0000000..84b5418
--- /dev/null
+++ b/dods/dap/BaseTypeFactory.java
@@ -0,0 +1,250 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+//
+// -- 7/14/99 Modified by: Nathan Potter (ndp at oce.orst.edu)
+// Added Support For DInt16, DUInt16, DFloat32.
+// Added (and commented out) support for DBoolean.
+// -- 7/14/99 ndp 
+//  
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * A Factory for BaseType objects. The DDS parser must implement this
+ * interface so that specific instances of <code>DByte</code>, and other
+ * <code>BaseType</code>s, can be created.
+ * <p>
+ * DDS and its parser will use this interface to create specific instances of
+ * the various data type classes in DODS. There is an implementation of this
+ * interface which creates instances of the <code>DByte</code>, etc. classes.
+ * If a new set of classes are defined (e.g. <code>NC_Byte</code>, etc.) this
+ * interface should be implemented for those classes such that the methods
+ * return instances of the appropriate types.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see DefaultFactory
+ * @see DDS
+ */
+public interface BaseTypeFactory {
+  //------------------------------------
+  /** 
+   * Construct a new DBoolean.
+   * @return the new DBoolean
+   */
+//  public DBoolean newDBoolean();
+
+  /**
+   * Construct a new DBoolean with name n.
+   * @param n the variable name
+   * @return the new DBoolean
+   */
+//  public DBoolean newDBoolean(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DByte.
+   * @return the new DByte
+   */
+  public DByte newDByte();
+
+  /**
+   * Construct a new DByte with name n.
+   * @param n the variable name
+   * @return the new DByte
+   */
+  public DByte newDByte(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DInt16.
+   * @return the new DInt16
+   */
+  public DInt16 newDInt16();
+
+  /**
+   * Construct a new DInt16 with name n.
+   * @param n the variable name
+   * @return the new DInt16
+   */
+  public DInt16 newDInt16(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DUInt16.
+   * @return the new DUInt16
+   */
+  public DUInt16 newDUInt16();
+
+  /**
+   * Construct a new DUInt16 with name n.
+   * @param n the variable name
+   * @return the new DUInt16
+   */
+  public DUInt16 newDUInt16(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DInt32.
+   * @return the new DInt32
+   */
+  public DInt32 newDInt32();
+
+  /**
+   * Construct a new DInt32 with name n.
+   * @param n the variable name
+   * @return the new DInt32
+   */
+  public DInt32 newDInt32(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DUInt32.
+   * @return the new DUInt32
+   */
+  public DUInt32 newDUInt32();
+
+  /**
+   * Construct a new DUInt32 with name n.
+   * @param n the variable name
+   * @return the new DUInt32
+   */
+  public DUInt32 newDUInt32(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DFloat32.
+   * @return the new DFloat32
+   */
+  public DFloat32 newDFloat32();
+
+  /**
+   * Construct a new DFloat32 with name n.
+   * @param n the variable name
+   * @return the new DFloat32
+   */
+  public DFloat32 newDFloat32(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DFloat64.
+   * @return the new DFloat64
+   */
+  public DFloat64 newDFloat64();
+
+  /**
+   * Construct a new DFloat64 with name n.
+   * @param n the variable name
+   * @return the new DFloat64
+   */
+  public DFloat64 newDFloat64(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DString.
+   * @return the new DString
+   */
+  public DString newDString();
+
+  /**
+   * Construct a new DString with name n.
+   * @param n the variable name
+   * @return the new DString
+   */
+  public DString newDString(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DURL.
+   * @return the new DURL
+   */
+  public DURL newDURL();
+
+  /**
+   * Construct a new DURL with name n.
+   * @param n the variable name
+   * @return the new DURL
+   */
+  public DURL newDURL(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DArray.
+   * @return the new DArray
+   */
+  public DArray newDArray();
+
+  /**
+   * Construct a new DArray with name n.
+   * @param n the variable name
+   * @return the new DArray
+   */
+  public DArray newDArray(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DList.
+   * @return the new DList
+   */
+  public DList newDList();
+
+  /**
+   * Construct a new DList with name n.
+   * @param n the variable name
+   * @return the new DList
+   */
+  public DList newDList(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DGrid.
+   * @return the new DGrid
+   */
+  public DGrid newDGrid();
+
+  /**
+   * Construct a new DGrid with name n.
+   * @param n the variable name
+   * @return the new DGrid
+   */
+  public DGrid newDGrid(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DStructure.
+   * @return the new DStructure
+   */
+  public DStructure newDStructure();
+
+  /**
+   * Construct a new DStructure with name n.
+   * @param n the variable name
+   * @return the new DStructure
+   */
+  public DStructure newDStructure(String n);
+
+  //------------------------------------
+  /** 
+   * Construct a new DSequence.
+   * @return the new DSequence
+   */
+  public DSequence newDSequence();
+
+  /**
+   * Construct a new DSequence with name n.
+   * @param n the variable name
+   * @return the new DSequence
+   */
+  public DSequence newDSequence(String n);
+
+ }
diff --git a/dods/dap/BaseTypePrimitiveVector.java b/dods/dap/BaseTypePrimitiveVector.java
new file mode 100644
index 0000000..a7b8267
--- /dev/null
+++ b/dods/dap/BaseTypePrimitiveVector.java
@@ -0,0 +1,239 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of <code>BaseType</code>.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see PrimitiveVector
+ */
+public class BaseTypePrimitiveVector extends PrimitiveVector implements Cloneable {
+    /** the array of <code>BaseType</code> values. */
+    private BaseType vals[];
+
+    /**
+    * Constructs a new <code>BaseTypePrimitiveVector</code>.
+    * @param var the template <code>BaseType</code> to use.
+    */
+    public BaseTypePrimitiveVector(BaseType var) {
+        super(var);
+    }
+
+    /**
+    * Returns a clone of this <code>BaseTypePrimitiveVector</code>.  A deep
+    * copy is performed on all data inside the variable.
+    *
+    * @return a clone of this <code>BaseTypePrimitiveVector</code>.
+    */
+    public Object clone() {
+        BaseTypePrimitiveVector v = (BaseTypePrimitiveVector)super.clone();
+        if (vals != null) {
+            v.vals = new BaseType[vals.length];
+            System.arraycopy(vals, 0, v.vals, 0, vals.length);
+        }
+        return v;
+    }
+
+
+    /**
+    * Returns the number of elements in the array.
+    * @return the number of elements in the array.
+    */
+    public int getLength() {
+        return vals.length;
+    }
+
+    /**
+    * Sets the number of elements in the array.  Allocates a new primitive
+    * array of the desired size.  Note that if this is called multiple times,
+    * the old array and its contents will be lost.
+    * <p>
+    * Only called inside of <code>deserialize</code> method or in derived
+    * classes on server.
+    *
+    * @param len the number of elements in the array.
+    */
+    public void setLength(int len) {
+        vals = new BaseType[len];
+    }
+
+    /**
+    * Return the i'th value as a <code>BaseType</code>.
+    * @param i the index of the value to return.
+    * @return the i'th value.
+    */
+    public final BaseType getValue(int i) {
+        return vals[i];
+    }
+
+    /**
+    * Set the i'th value of the array.
+    * @param i the index of the value to set.
+    * @param newVal the new value.
+    */
+    public final void setValue(int i, BaseType newVal) {
+        vals[i] = newVal;
+    }
+
+    /**
+    * Prints the value of all variables in this vector.  This
+    * method is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    */
+    public void printVal(PrintWriter os, String space) {
+        int len = vals.length;
+        for(int i=0; i<len-1; i++) {
+            vals[i].printVal(os, "", false);
+            os.print(", ");
+        }
+        // print last value, if any, without trailing comma
+        if(len > 0) {
+            vals[len-1].printVal(os, "", false);
+        }
+    }
+
+    /**
+    * Prints the value of a single variable in this vector.
+    * method is used by <code>DArray</code>'s <code>printVal</code> method.
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param index the index of the variable to print.
+    * @see DArray#printVal(PrintWriter, String, boolean)
+    */
+    public void printSingleVal(PrintWriter os, int index) {
+        vals[index].printVal(os, "", false);
+    }
+
+    /**
+    * Reads data from a <code>DataInputStream</code>. This method is only used
+    * on the client side of the DODS client/server connection.
+    *
+    * @param source a <code>DataInputStream</code> to read from.
+    * @param sv The <code>ServerVersion</code> returned by the server.
+    *    (used by <code>DSequence</code> to determine which protocol version was
+    *    used).
+    * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+    *    and user cancellation notification (may be null).
+    * @exception DataReadException when invalid data is read, or if the user
+    *     cancels the download.
+    * @exception EOFException if EOF is found before the variable is completely
+    *     deserialized.
+    * @exception IOException thrown on any other InputStream exception.
+    * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+    */
+    public synchronized void deserialize(DataInputStream source,
+                                         ServerVersion sv,
+                                         StatusUI statusUI)
+                                         throws IOException, EOFException, DataReadException {
+        for(int i=0; i<vals.length; i++) {
+            // create new variable from template
+            vals[i] = (BaseType)getTemplate().clone();
+            ((ClientIO)vals[i]).deserialize(source, sv, statusUI);
+            if (statusUI != null && statusUI.userCancelled())
+                throw new DataReadException("User cancelled");
+        }
+    }
+
+    /**
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * primarily by GUI clients which need to download DODS data, manipulate
+    * it, and then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code>
+    * exception.
+    */
+    public void externalize(DataOutputStream sink) throws IOException {
+        for(int i=0; i<vals.length; i++) {
+            // System.out.println("\t\t\tI AM THE WALRUS!");
+            ((ClientIO)vals[i]).externalize(sink);
+        }
+    }
+
+  /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
+    for (int i=start; i<=stop; i+=stride)
+      ((ClientIO)vals[i]).externalize(sink);
+  }
+
+    /**
+    * Returns (a reference to) the internal storage for this PrimitiveVector
+    * object.
+    * <h2>WARNING:</h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    *
+    * @return The internal array of BaseTypes.
+    */
+    public Object getInternalStorage() {
+        return(vals);
+    }
+
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    public void setInternalStorage(Object o) {
+      vals = (BaseType []) o;
+    }
+
+      /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector, of type BaseTypePrimitiveVector.
+   */
+  public PrimitiveVector subset( int start, int stop, int stride) {
+    BaseTypePrimitiveVector n = new BaseTypePrimitiveVector(getTemplate());
+    stride = Math.max( stride, 1);
+    stop = Math.max( start, stop);
+    int length = 1 + (stop - start) / stride;
+    n.setLength( length);
+
+    int count=0;
+    for (int i=start; i<=stop; i+=stride) {
+      n.setValue(count, vals[i]);
+      count++;
+    }
+    return n;
+  }
+
+}
diff --git a/dods/dap/BooleanPrimitiveVector.java b/dods/dap/BooleanPrimitiveVector.java
new file mode 100644
index 0000000..32c2a26
--- /dev/null
+++ b/dods/dap/BooleanPrimitiveVector.java
@@ -0,0 +1,255 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of booleans.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see PrimitiveVector
+ */
+public class BooleanPrimitiveVector extends PrimitiveVector implements Cloneable {
+  /** the array of <code>float</code> values. */
+  private boolean vals[];
+
+  /**
+   * Constructs a new <code>BooleanPrimitiveVector</code>.
+   * @param var the template <code>BaseType</code> to use.
+   */
+  public BooleanPrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Returns a clone of this <code>BooleanPrimitiveVector</code>.  A deep
+   * copy is performed on all data inside the variable.
+   *
+   * @return a clone of this <code>BooleanPrimitiveVector</code>.
+   */
+  public Object clone() {
+    BooleanPrimitiveVector v = (BooleanPrimitiveVector)super.clone();
+    if (vals != null) {
+      v.vals = new boolean[vals.length];
+      System.arraycopy(vals, 0, v.vals, 0, vals.length);
+    }
+    return v;
+  }
+
+  /**
+   * Returns the number of elements in the array.
+   * @return the number of elements in the array.
+   */
+  public int getLength() {
+    return vals.length;
+  }
+
+  /**
+   * Sets the number of elements in the array.  Allocates a new primitive
+   * array of the desired size.  Note that if this is called multiple times,
+   * the old array and its contents will be lost.
+   * <p>
+   * Only called inside of <code>deserialize</code> method or in derived
+   * classes on server.
+   *
+   * @param len the number of elements in the array.
+   */
+  public void setLength(int len) {
+    vals = new boolean[len];
+  }
+
+  /**
+   * Return the i'th value as a <code>double</code>.
+   * @param i the index of the value to return.
+   * @return the i'th value.
+   */
+  public final boolean getValue(int i) {
+    return vals[i];
+  }
+
+  /**
+   * Set the i'th value of the array.
+   * @param i the index of the value to set.
+   * @param newVal the new value.
+   */
+  public final void setValue(int i, boolean newVal) {
+    vals[i] = newVal;
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = vals.length;
+    for(int i=0; i<len-1; i++) {
+      os.print(vals[i]);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(vals[len-1]);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+  public void printSingleVal(PrintWriter os, int index) {
+    os.print(vals[index]);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+                                       ServerVersion sv,
+                                       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException {
+    for(int i=0; i<vals.length; i++) {
+      vals[i] = source.readBoolean();
+      if (statusUI != null) {
+        statusUI.incrementByteCount(1);
+        if (statusUI.userCancelled())
+          throw new DataReadException("User cancelled");
+      }
+    }
+  }
+
+  /**
+   * Writes data to a <code>DataOutputStream</code>. This method is used
+   * on the server side of the DODS client/server connection, and possibly
+   * by GUI clients which need to download DODS data, manipulate it, and
+   * then resave it as a binary file.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink) throws IOException {
+    for(int i=0; i<vals.length; i++) {
+      sink.writeBoolean(vals[i]);
+    }
+  }
+
+  /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
+    for (int i=start; i<=stop; i+=stride)
+      sink.writeBoolean(vals[i]);
+  }
+
+    /**
+    * Returns (a reference to) the internal storage for this PrimitiveVector
+    * object.
+    * <h2>WARNING:</h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    *
+    * @return The internal array of boolean values.
+    */
+    public Object getInternalStorage() {
+        return(vals);
+    }
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    public void setInternalStorage(Object o) {
+      vals = (boolean []) o;
+    }
+
+  /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector, of type BooleanPrimitiveVector.
+   */
+  public PrimitiveVector subset( int start, int stop, int stride) {
+    BooleanPrimitiveVector n = new BooleanPrimitiveVector(getTemplate());
+    stride = Math.max( stride, 1);
+    stop = Math.max( start, stop);
+    int length = 1 + (stop - start) / stride;
+    n.setLength( length);
+
+    int count=0;
+    for (int i=start; i<=stop; i+=stride) {
+      n.setValue(count, vals[i]);
+      count++;
+    }
+    return n;
+  }
+
+
+}
diff --git a/dods/dap/BytePrimitiveVector.java b/dods/dap/BytePrimitiveVector.java
new file mode 100644
index 0000000..5ba9d76
--- /dev/null
+++ b/dods/dap/BytePrimitiveVector.java
@@ -0,0 +1,268 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of bytes.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see PrimitiveVector
+ */
+public class BytePrimitiveVector extends PrimitiveVector implements Cloneable {
+  /** the array of <code>byte</code> values. */
+  private byte vals[];
+
+  /**
+   * Constructs a new <code>BytePrimitiveVector</code>.
+   * @param var the template <code>BaseType</code> to use.
+   */
+  public BytePrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Returns a clone of this <code>BytePrimitiveVector</code>.  A deep
+   * copy is performed on all data inside the variable.
+   *
+   * @return a clone of this <code>BytePrimitiveVector</code>.
+   */
+  public Object clone() {
+    BytePrimitiveVector v = (BytePrimitiveVector)super.clone();
+    if (vals != null) {
+      v.vals = new byte[vals.length];
+      System.arraycopy(vals, 0, v.vals, 0, vals.length);
+    }
+    return v;
+  }
+
+  /**
+   * Returns the number of elements in the array.
+   * @return the number of elements in the array.
+   */
+  public int getLength() {
+    return vals.length;
+  }
+
+  /**
+   * Sets the number of elements in the array.  Allocates a new primitive
+   * array of the desired size.  Note that if this is called multiple times,
+   * the old array and its contents will be lost.
+   * <p>
+   * Only called inside of <code>deserialize</code> method or in derived
+   * classes on server.
+   *
+   * @param len the number of elements in the array.
+   */
+  public void setLength(int len) {
+    vals = new byte[len];
+  }
+
+
+  /**
+   * Return the i'th value as a <code>byte</code>.
+   * @param i the index of the value to return.
+   * @return the i'th value.
+   */
+  public final byte getValue(int i) {
+    return vals[i];
+  }
+
+  /**
+   * Set the i'th value of the array.
+   * @param i the index of the value to set.
+   * @param newVal the new value.
+   */
+  public final void setValue(int i, byte newVal) {
+    vals[i] = newVal;
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = vals.length;
+    for(int i=0; i<len-1; i++) {
+      // convert to unsigned and print
+      os.print(((int)vals[i]) & 0xFF);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(((int)vals[len-1]) & 0xFF);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+  public void printSingleVal(PrintWriter os, int index) {
+    // convert to unsigned and print
+    os.print(((int)vals[index]) & 0xFF);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+                                       ServerVersion sv,
+                                       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException {
+
+    int modFour = vals.length%4;
+    // number of bytes to pad
+    int pad = (modFour != 0) ? (4-modFour) : 0;
+
+    for(int i=0; i<vals.length; i++) {
+      vals[i] = source.readByte();
+      if (statusUI != null) {
+        statusUI.incrementByteCount(1);
+        if (statusUI.userCancelled())
+          throw new DataReadException("User cancelled");
+      }
+    }
+    // pad out to a multiple of four bytes
+    byte unused;
+    for(int i=0; i<pad; i++)
+      unused = source.readByte();
+    if (statusUI != null)
+      statusUI.incrementByteCount(pad);
+  }
+
+  /**
+   * Writes data to a <code>DataOutputStream</code>. This method is used
+   * primarily by GUI clients which need to download DODS data, manipulate
+   * it, and then re-save it as a binary file.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @exception IOException thrown on any <code>OutputStream</code>
+   * exception.
+   */
+  public void externalize(DataOutputStream sink) throws IOException {
+    int modFour = vals.length%4;
+    // number of bytes to pad
+    int pad = (modFour != 0) ? (4-modFour) : 0;
+
+    for(int i=0; i<vals.length; i++) {
+      sink.writeByte(vals[i]);
+    }
+    // pad out to a multiple of four bytes
+    for(int i=0; i<pad; i++)
+      sink.writeByte(0);
+  }
+
+  /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
+    int count = 0;
+    for (int i=start; i<=stop; i+=stride) {
+      sink.writeByte(vals[i]);
+      count++;
+    }
+
+    // pad out to a multiple of four bytes
+    int modFour = count%4;
+    int pad = (modFour != 0) ? (4-modFour) : 0;
+    for(int i=0; i<pad; i++)
+      sink.writeByte(0);
+  }
+
+  /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector, of type BytePrimitiveVector.
+   */
+  public PrimitiveVector subset( int start, int stop, int stride) {
+    BytePrimitiveVector n = new BytePrimitiveVector(getTemplate());
+    stride = Math.max( stride, 1);
+    stop = Math.max( start, stop);
+    int length = 1 + (stop - start) / stride;
+    n.setLength( length);
+
+    int count=0;
+    for (int i=start; i<=stop; i+=stride) {
+      n.setValue(count, vals[i]);
+      count++;
+    }
+    return n;
+  }
+
+
+    /**
+    * Returns (a reference to) the internal storage for this PrimitiveVector
+    * object.
+    * <h2>WARNING:</h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    *
+    * @return The internal array of bytes.
+    */
+    public Object getInternalStorage() {
+        return(vals);
+    }
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    public void setInternalStorage(Object o) {
+      vals = (byte []) o;
+    }
+
+
+}
diff --git a/dods/dap/ClientIO.java b/dods/dap/ClientIO.java
new file mode 100644
index 0000000..93281df
--- /dev/null
+++ b/dods/dap/ClientIO.java
@@ -0,0 +1,68 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.EOFException;
+
+/** 
+ * Client-side serialization for DODS variables (sub-classes of
+ * <code>BaseType</code>).
+ * This does not send the entire class as the Java <code>Serializable</code>
+ * interface does, rather it sends only the binary data values. Other software
+ * is responsible for sending variable type information (see <code>DDS</code>).
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see DDS
+ */
+public interface ClientIO {
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see DataDDS
+   */
+  public void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException;
+
+
+
+    /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+    public void externalize(DataOutputStream sink) throws IOException;
+
+
+}
diff --git a/dods/dap/DAS.java b/dods/dap/DAS.java
new file mode 100644
index 0000000..ff88564
--- /dev/null
+++ b/dods/dap/DAS.java
@@ -0,0 +1,200 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.util.Enumeration;
+import dods.util.SortedTable;
+import dods.dap.parser.DASParser;
+import dods.dap.parser.ParseException;
+import java.io.*;
+
+/**
+ * The Data Attribute Structure is a set of name-value pairs used to
+ * describe the data in a particular dataset.  The name-value pairs
+ * are called the "attributes."  The values may be of any of the
+ * DODS simple data types (DByte, DInt32, DUInt32, DFloat64, DString and
+ * DURL), and may be scalar or vector.  (Note that all values are
+ * actually stored as string data.)
+ * <p>
+ * A value may also consist of a set of other name-value pairs.  This
+ * makes it possible to nest collections of attributes, giving rise
+ * to a hierarchy of attributes.  DODS uses this structure to provide
+ * information about variables in a dataset.
+ * <p>
+ * In the following example of a DAS, several of the attribute
+ * collections have names corresponding to the names of variables in
+ * a hypothetical dataset.  The attributes in that collection are said to
+ * belong to that variable.  For example, the <code>lat</code> variable has an
+ * attribute <code>units</code> of <code>degrees_north</code>.
+ *
+ * <blockquote><pre>
+ *  Attributes {
+ *      GLOBAL {
+ *          String title "Reynolds Optimum Interpolation (OI) SST";
+ *      }
+ *      lat {
+ *          String units "degrees_north";
+ *          String long_name "Latitude";
+ *          Float64 actual_range 89.5, -89.5;
+ *      }
+ *      lon {
+ *          String units "degrees_east";
+ *          String long_name "Longitude";
+ *          Float64 actual_range 0.5, 359.5;
+ *      }
+ *      time {
+ *          String units "days since 1-1-1 00:00:00";
+ *          String long_name "Time";
+ *          Float64 actual_range 726468., 729289.;
+ *          String delta_t "0000-00-07 00:00:00";
+ *      }
+ *      sst {
+ *          String long_name "Weekly Means of Sea Surface Temperature";
+ *          Float64 actual_range -1.8, 35.09;
+ *          String units "degC";
+ *          Float64 add_offset 0.;
+ *          Float64 scale_factor 0.0099999998;
+ *          Int32 missing_value 32767;
+ *      }
+ *  }
+ * </pre></blockquote>
+ *
+ * Attributes may have arbitrary names, although in most datasets it
+ * is important to choose these names so a reader will know what they
+ * describe.  In the above example, the <code>GLOBAL</code> attribute provides
+ * information about the entire dataset.
+ * <p>
+ * Data attribute information is an important part of the the data
+ * provided to a DODS client by a server, and the DAS is how this
+ * data is packaged for sending (and how it is received). 
+ *
+ * @version $Revision: 1.2 $
+ * @author jehamby
+ * @see DDS 
+ * @see AttributeTable
+ * @see Attribute
+ */
+public class DAS implements Cloneable {
+
+  /** A table containing AttributeTables with their names as a key */
+  private SortedTable attr;
+
+  /** Create a new empty <code>DAS</code>.  */
+  public DAS() {
+    attr = new SortedTable();
+  }
+
+  /**
+   * Returns a clone of this <code>DAS</code>.  A deep copy is performed on
+   * all <code>AttributeTable</code>s inside the <code>DAS</code>.
+   *
+   * @return a clone of this <code>DAS</code>.
+   */
+  public Object clone() {
+    try {
+      DAS d = (DAS)super.clone();
+      d.attr = new SortedTable();
+      for(int i=0; i<attr.size(); i++) {
+	String key = (String)attr.getKey(i);
+	AttributeTable element = (AttributeTable)attr.elementAt(i);
+	// clone element (don't clone key because it's a read-only String)
+	d.attr.put(key, element.clone());
+      }
+      return d;
+    } catch (CloneNotSupportedException e) {
+      // this shouldn't happen, since we are Cloneable
+      throw new InternalError();
+    }
+  }
+
+  /**
+   * Returns an <code>Enumeration</code> of the attribute names in this
+   * <code>DAS</code>.
+   * Use the <code>getAttributeTable</code> method to get the
+   * <code>AttributeTable</code> for a given name.
+   *
+   * @return an <code>Enumeration</code> of <code>String</code>.
+   * @see DAS#getAttributeTable(String)
+   */
+  public final Enumeration getNames() {
+    return attr.keys();
+  }
+
+  /**
+   * Returns the <code>AttributeTable</code> with the given name.
+   *
+   * @param name the name of the <code>AttributeTable</code> to return.
+   * @return the <code>AttributeTable</code> with the specified name, or null
+   * if there is no matching <code>AttributeTable</code>.
+   * @see AttributeTable
+   */
+  public final AttributeTable getAttributeTable(String name) {
+    return (AttributeTable)attr.get(name);
+  }
+
+  /**
+   * Adds an <code>AttributeTable</code> to the DAS.
+   *
+   * @param name the name of the <code>AttributeTable</code> to add.
+   * @param a the <code>AttributeTable</code> to add.
+   * @see AttributeTable
+   */
+  public void addAttributeTable(String name, AttributeTable a) {
+    attr.put(name, a);
+  }
+
+  /**
+   * Reads a <code>DAS</code> from the named <code>InputStream</code>.  This
+   * method calls a generated parser to interpret an ASCII representation of a
+   * <code>DAS</code>, and regenerate that <code>DAS</code> in memory.
+   *
+   * @param is the <code>InputStream</code> containing the <code>DAS</code> to
+   *    parse.
+   * @exception ParseException error in parser.
+   * @exception DASException error in constructing <code>DAS</code>.
+   * @exception dods.dap.parser.TokenMgrError error in token manager
+   *    (unterminated quote).
+   * @see dods.dap.parser.DASParser
+   */
+  public void parse(InputStream is) throws ParseException, DASException {
+    DASParser dp = new DASParser(is);
+    dp.Attributes(this);
+  }
+
+  /**
+   * Print the <code>DAS</code> on the given <code>PrintWriter</code>.
+   *
+   * @param os the <code>PrintWriter</code> to use for output.
+   */
+  public void print(PrintWriter os) {
+    os.println("Attributes {");
+    for (Enumeration e = getNames(); e.hasMoreElements() ;) {
+      String name = (String)e.nextElement();
+      os.println("    " + name + " {");
+      getAttributeTable(name).print(os, "        ");
+      os.println("    }");
+    }
+    os.println("}");
+    os.flush();
+  }
+
+  /**
+   * Print the <code>DAS</code> on the given <code>OutputStream</code>.
+   *
+   * @param os the <code>OutputStream</code> to use for output.
+   * @see DAS#print(PrintWriter)
+   */
+  public final void print(OutputStream os) {
+    print(new PrintWriter(new BufferedWriter(new OutputStreamWriter(os))));
+  }
+
+  
+}
diff --git a/dods/dap/DASException.java b/dods/dap/DASException.java
new file mode 100644
index 0000000..33e8959
--- /dev/null
+++ b/dods/dap/DASException.java
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * DAS exception. This is the root of all the DAS exception classes.
+ *
+ * @version $Revision: 1.2 $
+ * @author jehamby
+ * @see DODSException
+ */
+public class DASException extends DODSException {
+  /**
+   * Construct a <code>DASException</code> with the specified detail
+   * message and DODS error code.
+   *
+   * @param s the detail message.
+   */
+  public DASException(int error, String s) {
+    super(error,s);
+  }
+}
diff --git a/dods/dap/DArray.java b/dods/dap/DArray.java
new file mode 100644
index 0000000..b0c21ea
--- /dev/null
+++ b/dods/dap/DArray.java
@@ -0,0 +1,300 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.DataInputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+import dods.dap.Server.InvalidParameterException;
+
+/**
+ * This class is used to hold arrays of other DODS data. The elements of the
+ * array can be simple or compound data types. There is no limit on the
+ * number of dimensions an array can have, or on the size of each dimension.
+ * <p>
+ * If desired, the user can give each dimension of an array a name. You can,
+ * for example, have a 360x180 array of temperatures, covering the whole
+ * globe with one-degree squares. In this case, you could name the first
+ * dimension "Longitude" and the second dimension "Latitude". This can
+ * help prevent a great deal of confusion.
+ * <p>
+ * The <code>DArray</code> is used as part of the <code>DGrid</code> class,
+ * where the dimension names are crucial to its structure. The dimension names
+ * correspond to "Map" vectors, holding the actual values for that column of
+ * the array.
+ * <p>
+ * Each array dimension carries with it its own projection information. The
+ * projection inforamtion takes the form of three integers: the start, stop,
+ * and stride values. This is clearest with an example. Consider a
+ * one-dimensional array 10 elements long. If the start value of the
+ * dimension constraint is 3, then the constrained array appears to be seven
+ * elements long. If the stop value is changed to 7, then the array appears
+ * to be five elements long. If the stride is changed to two, the array will
+ * appear to be 3 elements long. Array constraints are written as
+ * <code>[start:stride:stop]</code>.
+ *
+ * <code><pre>
+ * A = [1 2 3 4 5 6 7 8 9 10]
+ * A[3::] = [4 5 6 7 8 9 10]
+ * A[3::7] = [4 5 6 7 8]
+ * A[3:2:7] = [4 6 8]
+ * A[0:3:9] = [1 4 7 10]
+ * </pre></code>
+ *
+ * NB: DODS uses zero-based indexing.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see DGrid
+ * @see DVector
+ * @see BaseType
+ */
+public class DArray extends DVector implements Cloneable {
+    /** A Vector of DArrayDimension information (i.e. the shape) */
+    private Vector dimVector;
+
+    /** Constructs a new <code>DArray</code>. */
+    public DArray() {
+        this(null);
+    }
+
+    /**
+    * Constructs a new <code>DArray</code> with name <code>n</code>.
+    * @param n the name of the variable.
+    */
+    public DArray(String n) {
+        super(n);
+        dimVector = new Vector();
+    }
+
+    /**
+    * Returns a clone of this <code>DArray</code>.  A deep copy is performed
+    * on all data inside the variable.
+    *
+    * @return a clone of this <code>DArray</code>.
+    */
+    public Object clone() {
+        DArray a = (DArray)super.clone();
+        a.dimVector = new Vector();
+        for(int i=0; i<dimVector.size(); i++) {
+              DArrayDimension d = (DArrayDimension)dimVector.elementAt(i);
+              a.dimVector.addElement(d.clone());
+        }
+        return a;
+    }
+
+    /**
+    * Returns the DODS type name of the class instance as a <code>String</code>.
+    * @return the DODS type name of the class instance as a <code>String</code>.
+    */
+    public String getTypeName() {
+        return "Array";
+    }
+
+    /**
+    * Checks for internal consistency.  For <code>DArray</code>, verify that
+    * the dimension vector is not empty.
+    *
+    * @param all for complex constructor types, this flag indicates whether to
+    *    check the semantics of the member variables, too.
+    * @exception BadSemanticsException if semantics are bad, explains why.
+    * @see BaseType#checkSemantics(boolean)
+    */
+    public void checkSemantics(boolean all)
+        throws BadSemanticsException {
+        super.checkSemantics(all);
+
+        if (dimVector.isEmpty())
+            throw new BadSemanticsException("An array variable must have dimensions");
+    }
+
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean)
+    */
+    public void printDecl(PrintWriter os, String space, boolean print_semi,
+			  boolean constrained) {
+		
+        // BEWARE! Since printDecl()is (multiplely) overloaded in BaseType and
+        // all of the different signatures of printDecl() in BaseType lead to
+        // one signature, we must be careful to override that SAME signature
+        // here. That way all calls to printDecl() for this object lead to
+        // this implementation.
+
+
+
+	    //os.println("DArray.printDecl()");
+
+        getPrimitiveVector().printDecl(os, space, false, constrained);
+        for(Enumeration e = dimVector.elements(); e.hasMoreElements(); ) {
+            DArrayDimension d = (DArrayDimension)e.nextElement();
+            os.print("[");
+            if(d.getName() != null)
+                os.print(d.getName() + " = ");
+            os.print(d.getSize() + "]");
+        }
+        if(print_semi)
+            os.println(";");
+    }
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        // print the declaration if print decl is true.
+        // for each dimension,
+        //   for each element, 
+        //     print the array given its shape, number of dimensions.
+        // Add the `;'
+
+        if (print_decl_p) {
+            printDecl(os, space, false);
+            os.print(" = ");
+        }
+
+        int dims = numDimensions();
+        int shape[] = new int[dims];
+        int i = 0;
+        for (Enumeration e = dimVector.elements(); e.hasMoreElements(); ) {
+            DArrayDimension d = (DArrayDimension)e.nextElement();
+            shape[i++] = d.getSize();
+        }
+
+        printArray(os, 0, dims, shape, 0);
+
+        if (print_decl_p)
+            os.println(";");
+    }
+
+    /**
+    * Print an array. This is a private member function.
+    * @param os is the stream used for writing
+    * @param index is the index of VEC to start printing
+    * @param dims is the number of dimensions in the array
+    * @param shape holds the size of the dimensions of the array.
+    * @param offset holds the current offset into the dimension array.
+    * @return the number of elements written.
+    */
+    private int printArray(PrintWriter os, int index, int dims, int shape[],
+			   int offset) { 
+        if (dims == 1) {
+            os.print("{");
+            for(int i=0; i<shape[offset]-1; i++) {
+                getPrimitiveVector().printSingleVal(os, index++);
+                os.print(", ");
+            }
+            getPrimitiveVector().printSingleVal(os, index++);
+            os.print("}");
+            return index;
+        }
+        else {
+            os.print("{");
+            for(int i=0; i<shape[offset]-1; i++) {
+                index = printArray(os, index, dims-1, shape, offset+1);
+                os.print(",");
+            }
+            index = printArray(os, index, dims-1, shape, offset+1);
+            os.print("}");
+            return index;
+        }
+    }
+
+    /**
+    * Given a size and a name, this function adds a dimension to the
+    * array.  For example, if the <code>DArray</code> is already 10 elements
+    * long, calling <code>appendDim</code> with a size of 5 will transform the
+    * array into a 10x5 matrix.  Calling it again with a size of 2 will
+    * create a 10x5x2 array, and so on.
+    *
+    * @param size the size of the desired new dimension.
+    * @param name the name of the new dimension.
+    */
+    public void appendDim(int size, String name) {
+        DArrayDimension newDim = new DArrayDimension(size, name);
+        dimVector.addElement(newDim);
+    }
+
+    /**
+    * Add a dimension to the array.  Same as <code>appendDim(size, null)</code>.
+    * @param size the size of the desired new dimension.
+    * @see DArray#appendDim(int, String)
+    */
+    public void appendDim(int size) {
+        appendDim(size, null);
+    }
+
+    /**
+    * Returns an <code>Enumeration</code> of <code>DArrayDimension</code>s
+    *   in this array.
+    * @return an <code>Enumeration</code> of <code>DArrayDimension</code>s
+    *   in this array.
+    */
+    public final Enumeration getDimensions() {
+        return dimVector.elements();
+    }
+
+    /**
+    * Returns the number of dimensions in this array.
+    * @return the number of dimensions in this array.
+    */
+    public final int numDimensions() {
+        return dimVector.size();
+    }
+  
+  
+  
+    /** Returns the <code>DArrayDimension</code> object for 
+    * the dimension requested. It makes sure that the dimension requested
+    * exists. 
+    */
+    public DArrayDimension getDimension(int dimension) throws InvalidParameterException {
+
+	
+		// QC the passed dimension
+        if (dimension < dimVector.size() )
+            return((DArrayDimension)dimVector.get(dimension));
+        else
+            throw new InvalidParameterException("DArray.getDimension(): Bad dimension request: dimension > # of dimensions");    
+    }
+    
+    /** Returns the <code>DArrayDimension</code> object for 
+    * the first dimension. 
+    */
+    public DArrayDimension getFirstDimension(){
+        return((DArrayDimension)dimVector.get(0));
+    }
+    
+
+}
diff --git a/dods/dap/DArrayDimension.java b/dods/dap/DArrayDimension.java
new file mode 100644
index 0000000..c253eb8
--- /dev/null
+++ b/dods/dap/DArrayDimension.java
@@ -0,0 +1,134 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import dods.dap.Server.InvalidParameterException;
+
+/**
+ * This class holds information about each dimension in a <code>DArray</code>.
+ * Each array dimension carries with it its own projection information, as
+ * well as it's name and size.
+ * <p> The projection information takes the form of three integers: the start,
+ * stop, and stride values. This is clearest with an example. Consider a
+ * one-dimensional array 10 elements long. If the start value of the
+ * dimension constraint is 3, then the constrained array appears to be seven
+ * elements long. If the stop value is changed to 7, then the array appears
+ * to be five elements long. If the stride is changed to two, the array will
+ * appear to be 3 elements long. Array constraints are written as
+ * <code>[start:stride:stop]</code>.
+ * @see DArray
+*/
+public final class DArrayDimension implements Cloneable {
+    private String  name;
+    private int     size;
+    private int     start;
+    private int     stride;
+    private int     stop;
+
+
+    /** Construct a new DArrayDimension.
+    @param size The size of the dimension.
+    @param name The dimension's name, or null if no name.
+    */
+    public DArrayDimension(int size, String name) {
+        this.size   = size;
+        this.name   = name;
+            this.start  = 0;
+            this.stride = 1;
+            this.stop   = size - 1;
+    }
+
+    /** Clone this object */
+    public Object clone() {
+        try {
+            DArrayDimension d = (DArrayDimension)super.clone();
+            return d;
+        }
+            catch (CloneNotSupportedException e) {
+            // this shouldn't happen, since we are Cloneable
+            throw new InternalError();
+        }
+    }
+
+    /** Get the dimension name. */
+    public String getName() {
+        return name;
+    }
+
+    /** Set the dimension name. */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /** Get the dimension size. */
+    public int getSize() {
+        return size;
+    }
+
+    /** Set the dimension size. */
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    /** Set the projection information for this dimension.
+      * The parameters <code>start</code> <code>stride</code> and <code>stop</code>
+      * are checked to verify that they make sense relative to each other and to
+      * the size of this dimension. If not an Invalid ParameterException is thrown.
+      * The general rule is: 0<=start<size, 0<stride, 0<=stop<size, start<=stop.
+      *
+      * @param start The starting point for the projection of this <code>DArrayDimension</code>.
+      * @param stride The size of the stride for the projection of this <code>DArrayDimension</code>.
+      * @param stop The stopping point for the projection of this <code>DArrayDimension</code>.
+      */
+    public void setProjection(int start, int stride, int stop) throws InvalidParameterException  {
+        String msg = "DArrayDimension.setProjection: Bad Projection Request: ";
+
+        if( start >= size)
+            throw new InvalidParameterException(msg + "start ("+start+") >= size ("+size+") for "+name);
+
+        if( start < 0)
+            throw new InvalidParameterException(msg + "start < 0");
+
+        if( stride <= 0)
+            throw new InvalidParameterException(msg + "stride <= 0");
+
+        if( stop >= size)
+            throw new InvalidParameterException(msg + "stop >= size");
+
+        if( stop < 0)
+            throw new InvalidParameterException(msg + "stop < 0");
+
+        if( stop < start)
+            throw new InvalidParameterException(msg + "stop < start");
+
+        this.start  = start;
+        this.stride = stride;
+        this.stop   = stop;
+        this.size   = 1 + (stop - start) / stride;
+    }
+
+    /** Get the projection start point for this dimension. */
+    public int getStart() {
+        return start;
+    }
+
+    /** Get the projection stride size for this dimension. */
+    public int getStride() {
+        return stride;
+    }
+
+    /** Get the projection stop point for this dimension. */
+    public int getStop() {
+        return stop;
+    }
+
+
+}
diff --git a/dods/dap/DBoolean.java b/dods/dap/DBoolean.java
new file mode 100644
index 0000000..da75915
--- /dev/null
+++ b/dods/dap/DBoolean.java
@@ -0,0 +1,143 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+
+package dods.dap;
+import java.io.*;
+import dods.dap.*;
+
+/**
+ * Holds a DODS <code>Boolean</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author npotter
+ * @see BaseType
+ */
+public class DBoolean extends BaseType implements ClientIO {
+  /** Constructs a new <code>DBoolean</code>. */
+  public DBoolean() { super(); }
+
+  /**
+   * Constructs a new <code>DBoolean</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DBoolean(String n) { super(n); }
+
+  /** The value of this <code>DBoolean</code>. */
+  private boolean val;
+
+  /**
+   * Get the current value as a short (16bit int).
+   * @return the current value.
+   */
+  public final boolean getValue() {
+    return val;
+  }
+
+  /**
+   * Set the current value.
+   * @param newVal the new value.
+   */
+  public final void setValue(boolean newVal) {
+    val = newVal;
+  }
+
+  /**
+   * Constructs a new <code>BooelanPrimitiveVector</code>.
+   * @return a new <code>BooelanPrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new BooleanPrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Boolean";
+  }
+
+
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + val + ";");
+    } else
+      os.print(val);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException {
+    val = source.readBoolean();
+    if(statusUI != null)
+      statusUI.incrementByteCount(1);
+  }
+
+    /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    sink.writeBoolean(val);
+  }
+}
diff --git a/dods/dap/DByte.java b/dods/dap/DByte.java
new file mode 100644
index 0000000..e547390
--- /dev/null
+++ b/dods/dap/DByte.java
@@ -0,0 +1,136 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>Byte</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ */
+public class DByte extends BaseType implements ClientIO {
+  /** Constructs a new <code>DByte</code>. */
+  public DByte() { super(); }
+
+  /**
+   * Constructs a new <code>DByte</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DByte(String n) { super(n); }
+
+  /** The value of this <code>DByte</code>. */
+  private byte val;
+
+  /**
+   * Get the current value as a byte.
+   * @return the current value.
+   */
+  public final byte getValue() {
+    return val;
+  }
+
+  /**
+   * Set the current value.
+   * @param newVal the new value.
+   */
+  public final void setValue(byte newVal) {
+    val = newVal;
+  }
+
+  /**
+   * Constructs a new <code>BytePrimitiveVector</code>.
+   * @return a new <code>BytePrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new BytePrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Byte";
+  }
+
+
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + (((int)val) & 0xFF) + ";");
+    } else
+      // convert to unsigned and print
+      os.print(((int)val) & 0xFF);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException {
+    // throw away first three bytes (padding)
+    byte unused;
+    for (int i=0; i<3; i++) {
+      unused = source.readByte();
+    }
+    // read fourth byte
+    val = source.readByte();
+
+    if (statusUI != null)
+      statusUI.incrementByteCount(4);
+  }
+
+
+    /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    // print three null bytes (padding)
+    for (int i=0; i<3; i++) {
+      sink.writeByte(0);
+    }
+    sink.writeByte(val);
+  }
+}
diff --git a/dods/dap/DConnect.java b/dods/dap/DConnect.java
new file mode 100644
index 0000000..6f28283
--- /dev/null
+++ b/dods/dap/DConnect.java
@@ -0,0 +1,653 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.net.*;
+import java.io.*;
+import dods.dap.parser.ParseException;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * This class provides support for common DODS client-side operations such as
+ * dereferencing a DODS URL, communicating network activity status
+ * to the user and reading local DODS objects.
+ * <p>
+ * Unlike its C++ counterpart, this class does not store instances of the DAS,
+ * DDS, etc. objects. Rather, the methods <code>getDAS</code>, etc. return
+ * instances of those objects.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ */
+public class DConnect {
+  private boolean dumpStream = false, dumpDAS = false;
+
+  /** InputStream to use for connection to a file instead of a remote host. */
+  private InputStream fileStream;
+
+  /** The last URLConnection used to communicate with the DODS server.
+   * Note: This is not an HttpURLConnection because file: URL's are allowed.
+   * Theoretically, one could    also run DODS over FTP, NFS, or any other
+   * protocol, although this is probably not very useful.
+   */
+  private URLConnection connection;
+
+  /**
+   * The current DODS URL, as a String (will be converted to URL inside of
+   * getDAS(), getDDS(), and getData()), without Constraint Expression.
+   */
+  private String urlString;
+
+  /** The projection portion of the current DODS CE (including leading "?"). */
+  private String projString;
+
+  /** The selection portion of the current DODS CE (including leading "&"). */
+  private String selString;
+
+  /** Whether to accept compressed documents. */
+  private boolean acceptDeflate;
+
+  /** The DODS server version. */
+  private ServerVersion ver;
+
+  /**
+   * Creates an instance bound to url which accepts compressed documents.
+   * @param urlString connect to this URL.
+   * @exception FileNotFoundException thrown if <code>urlString</code> is not
+   *     a valid URL, or a filename which exists on the system.
+   * @see DConnect#DConnect(String, boolean)
+   */
+  public DConnect(String urlString) throws FileNotFoundException {
+    this(urlString, true);
+  }
+
+  /**
+   * Creates an instance bound to url. If <code>acceptDeflate</code> is true
+   * then HTTP Request headers will indicate to servers that this client can
+   * accept compressed documents.
+   *
+   * @param urlString Connect to this URL.  If urlString is not a valid URL,
+   *   it is assumed to be a filename, which is opened.
+   * @param acceptDeflate true if this client can accept responses encoded
+   *   with deflate.
+   * @exception FileNotFoundException thrown if <code>urlString</code> is not
+   *   a valid URL, or a filename which exists on the system.
+   */
+  public DConnect(String urlString, boolean acceptDeflate) throws FileNotFoundException {
+    int ceIndex = urlString.indexOf('?');
+    if(ceIndex != -1) {
+      this.urlString = urlString.substring(0, ceIndex);
+      String expr = urlString.substring(ceIndex);
+      int selIndex = expr.indexOf('&');
+      if(selIndex != -1) {
+        this.projString = expr.substring(0, selIndex);
+        this.selString = expr.substring(selIndex);
+      } else {
+        this.projString = expr;
+        this.selString = "";
+      }
+    } else {
+      this.urlString = urlString;
+      this.projString = this.selString = "";
+    }
+    this.acceptDeflate = acceptDeflate;
+
+    // Test if the URL is really a filename, and if so, open the file
+    try {
+      URL testURL = new URL(urlString);
+    }
+    catch (MalformedURLException e) {
+      fileStream = new FileInputStream(urlString);
+    }
+  }
+
+  /**
+   * Creates an instance bound to an already open <code>InputStream</code>.
+   * @param is the <code>InputStream</code> to open.
+   */
+  public DConnect(InputStream is) {
+    this.fileStream = is;
+  }
+
+  /**
+   * Returns whether a file name or <code>InputStream</code> is being used
+   *     instead of a URL.
+   * @return true if a file name or <code>InputStream</code> is being used.
+   */
+  public final boolean isLocal() {
+    return (fileStream != null);
+  }
+
+  /**
+   * Returns the constraint expression supplied with the URL given to the
+   * constructor. If no CE was given this returns an empty <code>String</code>.
+   * <p>
+   * Note that the CE supplied to one of this object's constructors is
+   * "sticky"; it will be used with every data request made with this object.
+   * The CE passed to <code>getData</code>, however, is not sticky; it is used
+   * only for that specific request. This method returns the sticky CE.
+   *
+   * @return the constraint expression associated with this connection.
+   */
+  public final String CE() {
+    return projString + selString;
+  }
+
+  /**
+   * Returns the URL supplied to the constructor. If the URL contained a
+   * constraint expression that is not returned.
+   *
+   * @return the URL of this connection.
+   */
+  public final String URL() {
+    return urlString;
+  }
+
+  /**
+   * Open a connection to the DODS server.
+   * @param url the URL to open.
+   * @return the opened <code>InputStream</code>.
+   * @exception IOException if an IO exception occurred.
+   * @exception DODSException if the DODS server returned an error.
+   */
+  private InputStream openConnection(URL url) throws IOException, DODSException {
+    connection = url.openConnection();
+    if (acceptDeflate)
+      connection.setRequestProperty("Accept-Encoding", "deflate");
+    connection.connect();
+
+    // theory is that some errors happen "naturally" (under heavy loads i think)
+    // so try it 3 times
+    InputStream is = null;
+    int retry = 1;
+    long backoff = 100L;
+    while (true) {
+      try {
+        is = connection.getInputStream(); // get the HTTP InputStream
+        break;
+        /* if (is.available() > 0)
+          break;
+        System.out.println("DConnect available==0; retry open ("+retry+") "+url);
+        try { Thread.currentThread().sleep(backoff); }
+        catch (InterruptedException ie) {} */
+
+      } catch (NullPointerException e) {
+        System.out.println("DConnect NullPointer; retry open ("+retry+") "+url);
+        try { Thread.currentThread().sleep(backoff); }
+        catch (InterruptedException ie) {}
+
+      } catch (FileNotFoundException e) {
+        System.out.println("DConnect FileNotFound; retry open ("+retry+") "+url);
+        try { Thread.currentThread().sleep(backoff); }
+        catch (InterruptedException ie) {}
+      }
+
+      if (retry == 3)
+        throw new DODSException("Connection cannot be opened");
+      retry++;
+      backoff *= 2;
+    }
+
+    // check headers
+    String type = connection.getHeaderField("content-description");
+    // System.err.println("Content Description: " + type);
+    handleContentDesc(is, type);
+
+    ver = new ServerVersion(connection.getHeaderField("xdods-server"));
+    //System.err.println("Server: " + ver + ": " + ver.getMajor() + "," +
+    //	       ver.getMinor());
+
+    String encoding = connection.getContentEncoding();
+    //System.err.println("Content Encoding: " + encoding);
+    return handleContentEncoding(is, encoding);
+  }
+
+  /**
+   * Returns the DAS object from the dataset referenced by this object's URL.
+   * The DAS object is referred to by appending `.das' to the end of a DODS
+   * URL.
+   *
+   * @return the DAS associated with the referenced dataset.
+   * @exception MalformedURLException if the URL given to the
+   *   constructor has an error
+   * @exception IOException if an error connecting to the remote server
+   * @exception ParseException if the DAS parser returned an error
+   * @exception DASException on an error constructing the DAS
+   * @exception DODSException if an error returned by the remote server
+   */
+  public DAS getDAS() throws MalformedURLException, IOException,
+                             ParseException, DASException, DODSException {
+    InputStream is;
+    if (fileStream != null)
+      is = parseMime(fileStream);
+    else {
+      URL url = new URL(urlString + ".das" + projString + selString);
+      if (dumpDAS) {
+        System.out.println("--DConnect.getDAS to "+url);
+        copy( url.openStream(), System.out);
+        System.out.println("\n--DConnect.getDAS END1");
+        dumpBytes( url.openStream(), 100);
+        System.out.println("\n-DConnect.getDAS END2");
+      }
+      is = openConnection(url);
+    }
+    DAS das = new DAS();
+    try {
+      das.parse(is);
+    } finally {
+      is.close();  // stream is always closed even if parse() throws exception
+      if (connection instanceof HttpURLConnection)
+        ((HttpURLConnection)connection).disconnect();
+    }
+    return das;
+  }
+
+  /**
+   * Returns the DDS object from the dataset referenced by this object's URL.
+   * The DDS object is referred to by appending `.dds' to the end of a DODS
+   * URL.
+   *
+   * @return the DDS associated with the referenced dataset.
+   * @exception MalformedURLException if the URL given to the constructor
+   *    has an error
+   * @exception IOException if an error connecting to the remote server
+   * @exception ParseException if the DDS parser returned an error
+   * @exception DDSException on an error constructing the DDS
+   * @exception DODSException if an error returned by the remote server
+   */
+  public DDS getDDS() throws MalformedURLException, IOException,
+                             ParseException, DDSException, DODSException {
+    InputStream is;
+    if (fileStream != null)
+      is = parseMime(fileStream);
+    else {
+      URL url = new URL(urlString + ".dds" + projString + selString);
+      is = openConnection(url);
+    }
+    DDS dds = new DDS();
+    try {
+      dds.parse(is);
+    } finally {
+      is.close();  // stream is always closed even if parse() throws exception
+      if (connection instanceof HttpURLConnection)
+        ((HttpURLConnection)connection).disconnect();
+    }
+    return dds;
+  }
+
+  /**
+   * Returns the `Data object' from the dataset referenced by this object's
+   * URL given the constraint expression CE. Note that the Data object is
+   * really just a DDS object with data bound to the variables. The DDS will
+   * probably contain fewer variables (and those might have different
+   * types) than in the DDS returned by getDDS() because that method returns
+   * the entire DDS (but without any data) while this method returns
+   * only those variables listed in the projection part of the constraint
+   * expression.
+   * <p>
+   * Note that if CE is an empty String then the entire dataset will be
+   * returned, unless a "sticky" CE has been specified in the constructor.
+   *
+   * @param CE The constraint expression to be applied to this request by the
+   *    server.  This is combined with any CE given in the constructor.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @return The <code>DataDDS</code> object that results from applying the
+   *    given CE, combined with this object's sticky CE, on the referenced
+   *    dataset.
+   * @exception MalformedURLException if the URL given to the constructor
+        has an error
+   * @exception IOException if any error connecting to the remote server
+   * @exception ParseException if the DDS parser returned an error
+   * @exception DDSException on an error constructing the DDS
+   * @exception DODSException if any error returned by the remote server
+   */
+  public DataDDS getData(String CE, StatusUI statusUI, BaseTypeFactory btf) throws MalformedURLException, IOException,
+                             ParseException, DDSException, DODSException {
+
+
+    if (fileStream != null)
+      return getDataFromFileStream( fileStream, statusUI, btf);
+
+    String localProjString, localSelString;
+    int selIndex = CE.indexOf('&');
+    if(selIndex != -1) {
+      localProjString = CE.substring(0, selIndex);
+      localSelString = CE.substring(selIndex);
+    } else {
+      localProjString = CE;
+      localSelString = "";
+    }
+    URL url = new URL(urlString + ".dods" + projString + localProjString +
+                      selString + localSelString);
+
+
+    String errorMsg = "DConnect getData failed "+url;
+    int errorCode = DODSException.UNKNOWN_ERROR;
+    int retry = 1;
+    long backoff = 100L;
+    while (true) {
+      try {
+        return getDataFromUrl( url, statusUI, btf);
+      } 
+      catch (DODSException e) {
+        System.out.println("DConnect getData failed; retry ("+retry+","+backoff+") "+url);
+        errorMsg = e.getErrorMessage();
+        errorCode = e.getErrorCode();
+	
+        try { 
+	    Thread.currentThread().sleep(backoff);
+	}
+        catch (InterruptedException ie) {}
+      }
+
+      if (retry == 5)
+        throw new DODSException(errorCode,errorMsg);
+      retry++;
+      backoff *= 2;
+    }
+  }
+
+
+  private DataDDS getDataFromFileStream(InputStream fileStream, StatusUI statusUI, BaseTypeFactory btf) throws IOException,
+                             ParseException, DDSException, DODSException {
+
+    InputStream is = parseMime(fileStream);
+    DataDDS dds = new DataDDS(ver, btf);
+
+    try {
+      dds.parse(new HeaderInputStream(is));    // read the DDS header
+      // NOTE: the HeaderInputStream will have skipped over "Data:" line
+      dds.readData(is, statusUI); // read the data!
+
+    } finally {
+      is.close();  // stream is always closed even if parse() throws exception
+    }
+    return dds;
+  }
+
+  public DataDDS getDataFromUrl(URL url, StatusUI statusUI, BaseTypeFactory btf) throws MalformedURLException, IOException,
+                             ParseException, DDSException, DODSException {
+
+    InputStream is = openConnection(url);
+    DataDDS dds = new DataDDS(ver, btf);
+
+    // DEBUG
+    ByteArrayInputStream bis = null;
+    if (dumpStream) {
+      System.out.println("DConnect to "+url);
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      copy(is, bos);
+      bis = new ByteArrayInputStream(bos.toByteArray());
+      is = bis;
+    }
+
+    try {
+
+      if (dumpStream) {
+        bis.mark( 1000);
+        System.out.println("DConnect parse header: ");
+        dump( bis);
+        bis.reset();
+      }
+
+      dds.parse(new HeaderInputStream(is));    // read the DDS header
+      // NOTE: the HeaderInputStream will have skipped over "Data:" line
+
+      if (dumpStream) {
+        bis.mark( 20);
+        System.out.println("DConnect done with header, next bytes are: ");
+        dumpBytes( bis, 20);
+        bis.reset();
+      }
+
+      dds.readData(is, statusUI); // read the data!
+
+    } catch (Exception e) {
+      System.out.println("DConnect dds.parse: "+url+"\n "+e);
+      e.printStackTrace();
+      /* DEBUG
+      if (dumpStream) {
+        System.out.println("DConnect dump "+url);
+        bis.reset();
+        dump(bis);
+        bis.reset();
+        File saveFile = null;
+        try {
+          saveFile = File.createTempFile("debug","tmp", new File("."));
+          System.out.println("try Save file = "+ saveFile.getAbsolutePath());
+          FileOutputStream saveFileOS = new FileOutputStream(saveFile);
+          copy(bis, saveFileOS);
+          saveFileOS.close();
+          System.out.println("wrote Save file = "+ saveFile.getAbsolutePath());
+        } catch (java.io.IOException ioe) {
+          System.out.println("failed Save file = "+ saveFile.getAbsolutePath());
+          ioe.printStackTrace();
+        }
+      } */
+
+      throw new DODSException("Connection cannot be read "+url);
+
+    } finally {
+      is.close();  // stream is always closed even if parse() throws exception
+      if (connection instanceof HttpURLConnection)
+        ((HttpURLConnection)connection).disconnect();
+    }
+
+    return dds;
+  }
+
+  // DEBUG JC
+  private void copy(InputStream in, OutputStream out) {
+    try {
+      byte[] buffer = new byte[256];
+      while (true) {
+        int bytesRead = in.read(buffer);
+        if (bytesRead == -1) break;
+        out.write(buffer, 0, bytesRead);
+      }
+    } catch (IOException e) {}
+  }
+
+
+  // DEBUG JC
+  private void dump( InputStream is) throws IOException {
+    DataInputStream d = new DataInputStream(is);
+
+    try {
+      System.out.println( "dump lines avail="+is.available());
+      while ( true ) {
+        String line = d.readLine();
+        System.out.println( line);
+        if (null == line) return;
+        if (line.equals("Data:")) break;
+      }
+      System.out.println( "dump bytes avail="+is.available());
+      dumpBytes( is, 20);
+
+    } catch (java.io.EOFException e) {
+    }
+  }
+
+  private void dumpBytes( InputStream is, int n) {
+    try{
+      DataInputStream d = new DataInputStream(is);
+      int count = 0;
+      while ( (count < n) && (d.available() > 0)) {
+        System.out.println( count +" "+d.readByte());
+        count++;
+      }
+    } catch (java.io.IOException e) {
+    }
+  }
+
+  /**
+   * Returns the `Data object' from the dataset referenced by this object's
+   * URL given the constraint expression CE. Note that the Data object is
+   * really just a DDS object with data bound to the variables. The DDS will
+   * probably contain fewer variables (and those might have different
+   * types) than in the DDS returned by getDDS() because that method returns
+   * the entire DDS (but without any data) while this method returns
+   * only those variables listed in the projection part of the constraint
+   * expression.
+   * <p>
+   * Note that if CE is an empty String then the entire dataset will be
+   * returned, unless a "sticky" CE has been specified in the constructor.
+   *
+   * @param CE The constraint expression to be applied to this request by the
+   *    server.  This is combined with any CE given in the constructor.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @return The <code>DataDDS</code> object that results from applying the
+   *    given CE, combined with this object's sticky CE, on the referenced
+   *    dataset.
+   * @exception MalformedURLException if the URL given to the constructor
+        has an error
+   * @exception IOException if any error connecting to the remote server
+   * @exception ParseException if the DDS parser returned an error
+   * @exception DDSException on an error constructing the DDS
+   * @exception DODSException if any error returned by the remote server
+   */
+  public DataDDS getData(String CE, StatusUI statusUI) throws MalformedURLException, IOException,
+                             ParseException, DDSException, DODSException {
+
+    return getData(CE, statusUI, new DefaultFactory());
+  }
+
+
+
+
+  /**
+   * Return the data object with no local constraint expression.  Same as
+   * <code>getData("", statusUI)</code>.
+   *
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @return The <code>DataDDS</code> object that results from applying
+   *    this object's sticky CE, if any, on the referenced dataset.
+   * @exception MalformedURLException if the URL given to the constructor
+        has an error
+   * @exception IOException if any error connecting to the remote server
+   * @exception ParseException if the DDS parser returned an error
+   * @exception DDSException on an error constructing the DDS
+   * @exception DODSException if any error returned by the remote server
+   * @see DConnect#getData(String, StatusUI)
+   */
+  public final DataDDS getData(StatusUI statusUI) throws MalformedURLException, IOException,
+                             ParseException, DDSException, DODSException {
+    return getData("", statusUI, new DefaultFactory());
+  }
+
+  /**
+   * Returns the <code>ServerVersion</code> of the last connection.
+   * @return the <code>ServerVersion</code> of the last connection.
+   */
+  public final ServerVersion getServerVersion() {
+    return ver;
+  }
+
+  /**
+   * A primitive parser for the MIME headers used by DODS.  This is used when
+   * reading from local sources of DODS Data objects. It is called by
+   * <code>readData</code> to simulate the important actions of the
+   * <code>URLConnection</code> MIME header parsing performed in
+   * <code>openConnection</code> for HTTP URL's.
+   * <p>
+   * <b><i>NOTE:</b></i> Because BufferedReader seeks ahead, and therefore
+   * removescharacters from the InputStream which are needed later, and
+   * because there is no way to construct an InputStream from a
+   * BufferedReader, we have to use DataInputStream to read the header
+   * lines, which triggers an unavoidable deprecated warning from the
+   * Java compiler.
+   *
+   * @param is the InputStream to read.
+   * @return the InputStream to read data from (after attaching any
+   *    necessary decompression filters).
+   * @exception IOException if any IO error.
+   * @exception DODSException if the server returned an Error.
+   */
+  private InputStream parseMime(InputStream is)
+       throws IOException, DODSException {
+
+    // NOTE: because BufferedReader seeks ahead, and therefore removes
+    // characters from the InputStream which are needed later, and
+    // because there is no way to construct an InputStream from a
+    // BufferedReader, we have to use DataInputStream to read the header
+    // lines, which triggers an unavoidable deprecated warning from the
+    // Java compiler.
+
+    DataInputStream d = new DataInputStream(is);
+
+    String description = null;
+    String encoding = null;
+
+    // while there are more header (non-blank) lines
+    String line;
+    while (!(line = d.readLine()).equals("")) {
+        int spaceIndex = line.indexOf(' ');
+        // all header lines should have a space in them, but if not, skip ahead
+        if (spaceIndex == -1)
+            continue;
+        String header = line.substring(0, spaceIndex);
+        String value = line.substring(spaceIndex+1);
+
+        if (header.equals("Server:")) {
+            ver = new ServerVersion(value);
+        }
+        else if (header.equals("Content-Description:")) {
+            description = value;
+        }
+        else if (header.equals("Content-Encoding:")) {
+            encoding = value;
+        }
+    }
+    handleContentDesc(is, description);
+    return handleContentEncoding(is, encoding);
+  }
+
+  /**
+   * This code handles the Content-Description: header for
+   * <code>openConnection</code> and <code>parseMime</code>.
+   * Throws a <code>DODSException</code> if the type is
+   * <code>dods_error</code>.
+   *
+   * @param is the InputStream to read.
+   * @param type the Content-Description header, or null.
+   * @exception IOException if any error reading from the server.
+   * @exception DODSException if the server returned an error.
+   */
+  private void handleContentDesc(InputStream is, String type)
+       throws IOException, DODSException {
+    if (type != null && type.equals("dods_error")) {
+      // create server exception object
+      DODSException ds = new DODSException();
+      // parse the Error object from stream and throw it
+      ds.parse(is);
+      throw ds;
+    }
+  }
+
+  /**
+   * This code handles the Content-type: header for
+   * <code>openConnection</code> and <code>parseMime</code>
+   * @param is the InputStream to read.
+   * @param encoding the Content-type header, or null.
+   * @return the new InputStream, after applying an
+   *    <code>InflaterInputStream</code> filter if necessary.
+   */
+  private InputStream handleContentEncoding(InputStream is, String encoding) {
+    if (encoding != null && encoding.equals("deflate")) {
+      return new InflaterInputStream(is);
+    } else {
+      return is;
+    }
+  }
+}
diff --git a/dods/dap/DConstructor.java b/dods/dap/DConstructor.java
new file mode 100644
index 0000000..7273d7b
--- /dev/null
+++ b/dods/dap/DConstructor.java
@@ -0,0 +1,95 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.io.PrintWriter;
+
+/**
+ * Contains methods used only by the DODS constructor classes
+ * (<code>DStructure</code>, <code>DSequence</code>, <code>DGrid</code>, and
+ * <code>DList</code>).
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see DStructure
+ * @see DSequence
+ * @see DGrid
+ * @see DList
+ */
+abstract public class DConstructor extends BaseType {
+
+//    /** A <code>Vector</code> of DODS BaseTypes to be used by my children */
+//    protected Vector vars;
+
+    /** Constructs a new <code>DConstructor</code>. */
+    public DConstructor() {}
+
+    /**
+    * Constructs a new <code>DConstructor</code> with the given name.
+    * @param n The name of the variable.
+    */
+    public DConstructor(String n) { super(n); }
+
+    /**
+    * Adds a variable to the container.
+    * @param v the variable to add.
+    * @param part The part of the constructor data to be modified.
+    */
+    abstract public void addVariable(BaseType v, int part);
+
+    /**
+    * Adds a variable to the container.  Same as <code>addVariable(v, 0)</code>.
+    * @param v the variable to add.
+    */
+    public final void addVariable(BaseType v) {
+        addVariable(v, 0);
+    }
+  
+    /**
+    * Gets the named variable.
+    * @param name the name of the variable.
+    * @return the named variable.
+    * @exception NoSuchVariableException if the named variable does not
+    *     exist in this container.
+    */
+    abstract public BaseType getVariable(String name) 
+       throws NoSuchVariableException;
+
+    /**
+    * Gets the indexed variable. For a DGrid the index 0 returns the <code>DArray</code> and 
+    * indexes 1 and higher return the associated map <code>Vector</code>s. 
+    * @param index the index of the variable in the <code>Vector</code> Vars.
+    * @return the named variable.
+    * @exception NoSuchVariableException if the named variable does not
+    *     exist in this container.
+    */
+    abstract public BaseType getVar(int index) 
+        throws NoSuchVariableException;
+
+    /** Return an Enumeration that can be used to iterate over all of the
+     * members of the class. Each implementation must define what this means.
+     * The intent of this method is to support operations on all members of a
+     * Structure, Seqeunce or Grid that can be performed equally. So it is
+     * not necessary that this methods be usable, for example, when the
+     * caller needs to know that it s dealing with the Array part of a grid.
+     @return An Enumeration object. */
+    abstract public Enumeration getVariables();
+    
+    
+    
+
+    
+    
+    
+}
+
diff --git a/dods/dap/DDS.java b/dods/dap/DDS.java
new file mode 100644
index 0000000..e93917b
--- /dev/null
+++ b/dods/dap/DDS.java
@@ -0,0 +1,535 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+import java.util.Enumeration;
+import java.util.Vector;
+import java.util.Stack;
+import java.io.*;
+
+import dods.dap.parser.DDSParser;
+import dods.dap.parser.ParseException;
+
+/**
+ * The DODS Data Descriptor Object (DDS) is a data structure used by
+ * the DODS software to describe datasets and subsets of those
+ * datasets.  The DDS may be thought of as the declarations for the
+ * data structures that will hold data requested by some DODS client.
+ * Part of the job of a DODS server is to build a suitable DDS for a
+ * specific dataset and to send it to the client.  Depending on the
+ * data access API in use, this may involve reading part of the
+ * dataset and inferring the DDS.  Other APIs may require the server
+ * simply to read some ancillary data file with the DDS in it.
+ * <p>
+ * On the server side, in addition to the data declarations, the DDS
+ * holds the clauses of any constraint expression that may have
+ * accompanied the data request from the DODS client.  The DDS object
+ * includes methods for modifying the DDS according to the given
+ * constraint expression.  It also has methods for directly modifying
+ * a DDS, and for transmitting it from a server to a client.
+ * <p>
+ * For the client, the DDS object includes methods for reading the
+ * persistent form of the object sent from a server. This includes parsing
+ * the ASCII representation of the object and, possibly, reading data
+ * received from a server into a data object.
+ * <p>
+ * Note that the class DDS is used to instantiate both DDS and DataDDS
+ * objects. A DDS that is empty (contains no actual data) is used by servers
+ * to send structural information to the client. The same DDS can becomes a
+ * DataDDS when data values are bound to the variables it defines.
+ * <p>
+ * For a complete description of the DDS layout and protocol, please
+ * refer to <em>The DODS User Guide</em>.
+ * <p>
+ * The DDS has an ASCII representation, which is what is transmitted
+ * from a DODS server to a client.  Here is the DDS representation of
+ * an entire dataset containing a time series of worldwide grids of
+ * sea surface temperatures:
+ *
+ * <blockquote><pre>
+ *  Dataset {
+ *      Float64 lat[lat = 180];
+ *      Float64 lon[lon = 360];
+ *      Float64 time[time = 404];
+ *      Grid {
+ *       ARRAY:
+ *          Int32 sst[time = 404][lat = 180][lon = 360];
+ *       MAPS:
+ *          Float64 time[time = 404];
+ *          Float64 lat[lat = 180];
+ *          Float64 lon[lon = 360];
+ *      } sst;
+ *  } weekly;
+ * </pre></blockquote>
+ *
+ * If the data request to this dataset includes a constraint
+ * expression, the corresponding DDS might be different.  For
+ * example, if the request was only for northern hemisphere data
+ * at a specific time, the above DDS might be modified to appear like
+ * this:
+ *
+ * <blockquote><pre>
+ *  Dataset {
+ *      Grid {
+ *       ARRAY:
+ *          Int32 sst[time = 1][lat = 90][lon = 360];
+ *       MAPS:
+ *          Float64 time[time = 1];
+ *          Float64 lat[lat = 90];
+ *          Float64 lon[lon = 360];
+ *      } sst;
+ *  } weekly;
+ * </pre></blockquote>
+ *
+ * Since the constraint has narrowed the area of interest, the range
+ * of latitude values has been halved, and there is only one time
+ * value in the returned array.  Note that the simple arrays (<em>lat</em>,
+ * <em>lon</em>, and <em>time</em>) described in the dataset are also part of
+ * the <em>sst</em> Grid object.  They can be requested by themselves or as
+ * part of that larger object.
+ * <p>
+ * See <em>The DODS User Guide</em>, or the documentation of the
+ * BaseType class for descriptions of the DODS data types.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see BaseTypeFactory
+ * @see DAS
+ */
+public class DDS implements Cloneable {
+    /** The dataset name. */
+    protected String name;
+
+    /** Variables at the top level. */
+    protected Vector vars;
+
+    /** Factory for new DAP variables. */
+    private BaseTypeFactory factory;
+
+    /** Creates an empty <code>DDS</code>. */
+    public DDS() {
+        this(null, new DefaultFactory());
+    }
+
+    /**
+    * Creates an empty <code>DDS</code> with the given dataset name.
+    * @param n the dataset name
+    */
+    public DDS(String n) {
+        this(n, new DefaultFactory());
+    }
+
+    /**
+    * Creates an empty <code>DDS</code> with the given
+    * <code>BaseTypeFactory</code>.  This will be used for DODS servers which
+    * need to construct subclasses of the various <code>BaseType</code> objects
+    * to hold additional server-side information.
+    *
+    * @param factory the server <code>BaseTypeFactory</code> object.
+    */
+    public DDS(BaseTypeFactory factory) {
+        this(null, factory);
+    }
+
+    /**
+    * Creates an empty <code>DDS</code> with the given dataset name and
+    * <code>BaseTypeFactory</code>.  This will be used for DODS servers which
+    * need to construct subclasses of the various <code>BaseType</code> objects
+    * to hold additional server-side information.
+    *
+    * @param n the dataset name
+    * @param factory the server <code>BaseTypeFactory</code> object.
+    */
+    public DDS(String n, BaseTypeFactory factory) {
+        name = n;
+        vars = new Vector();
+        this.factory = factory;
+    }
+
+    /**
+    * Returns a clone of this <code>DDS</code>.  A deep copy is performed on
+    * all variables inside the <code>DDS</code>.
+    *
+    * @return a clone of this <code>DDS</code>.
+    */
+    public Object clone() {
+        try {
+            DDS d = (DDS)super.clone();
+            d.vars = new Vector();
+            for(int i=0; i<vars.size(); i++) {
+                BaseType element = (BaseType)vars.elementAt(i);
+                d.vars.addElement(element.clone());
+            }
+            d.name = new String(this.name);
+
+            // Question:
+	    // What about copying the BaseTypeFactory? 
+            // Do we want a reference to the same one? Or another instance?
+            // Is there a difference? Should we be building the clone
+            // using "new DDS(getFactory())"??
+	    
+            // Answer:
+	    // Yes. Use the same type factory!
+	    
+	    d.factory = this.factory;
+
+
+            return d;
+        } 
+	catch (CloneNotSupportedException e) {
+            // this shouldn't happen, since we are Cloneable
+            throw new InternalError();
+        }
+    }
+
+    /**
+    * Get the Class factory.  This is the machine that builds classes
+    * for the internal representation of the data set.
+    *
+    * @return the BaseTypeFactory.
+    */
+    public final BaseTypeFactory getFactory() {
+        return factory;
+    }
+
+    /**
+    * Get the dataset's name.  This is the name of the dataset
+    * itself, and is not to be confused with the name of the file or
+    * disk on which it is stored.
+    *
+    * @return the name of the dataset.
+    */
+    public final String getName() {
+        return name;
+    }
+
+    /**
+    * Set the dataset's name.  This is the name of the dataset
+    * itself, and is not to be confused with the name of the file or
+    * disk on which it is stored.
+    *
+    * @param name the name of the dataset.
+    */
+    public final void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+    * Adds a variable to the <code>DDS</code>.
+    *
+    * @param bt the new variable to add.
+    */
+    public final void addVariable(BaseType bt) {
+        vars.addElement(bt);
+    }
+
+    /**
+    * Removes a variable from the <code>DDS</code>.
+    * Does nothing if the variable can't be found.
+    * If there are multiple variables with the same name, only the first
+    * will be removed.  To detect this, call the <code>checkSemantics</code>
+    * method to verify that each variable has a unique name.
+    *
+    * @param name the name of the variable to remove.
+    * @see DDS#checkSemantics(boolean)
+    */
+    public final void delVariable(String name) {
+        try {
+            BaseType bt = getVariable(name);
+            vars.removeElement(bt);
+        }
+        catch (NoSuchVariableException e) {}
+    }
+
+    /** Is the variable <code>var</code> a vector of DConstructors? Return
+    *   true if it is, false otherwise. This mess will recurse into a
+    *   DVector's template BaseType (which is a BaseTypePrimivitiveVector) and
+    *   look to see if that is either a DConstructor or <em>contains</em> a
+    *   DConstructor. So the <code>List Strucutre { ... } g[10];</code> should
+    *   be handled correctly. <p>
+    *
+    * Note that the List type modifier may only appear once. */
+    private DConstructor isVectorOfDConstructor(BaseType var) {
+	if (!(var instanceof DVector))
+	    return null;
+	if (!(((DVector)var).getPrimitiveVector() 
+	      instanceof BaseTypePrimitiveVector))
+	    return null;
+	// OK. We have a DVector whose template is a BaseTypePrimitiveVector.
+	BaseTypePrimitiveVector btpv = (BaseTypePrimitiveVector)
+	    ((DVector)var).getPrimitiveVector();
+	// After that nasty cast, is the template a DConstructor?
+	if (btpv.getTemplate() instanceof DConstructor)
+	    return (DConstructor)btpv.getTemplate();
+	else
+	    return isVectorOfDConstructor(btpv.getTemplate());
+    }
+
+    /**
+    * Returns a reference to the named variable. 
+    *
+    * @param name the name of the variable to return.
+    * @return the variable named <code>name</code>.
+    * @exception NoSuchVariableException if the variable isn't found.
+    */
+    public BaseType getVariable(String name) throws NoSuchVariableException {
+        Stack s = new Stack();
+        s = search(name, s);
+        return (BaseType)s.pop();
+    }
+
+    /** Look for <code>name</code> in the DDS. Start the search using the
+     * ctor variable (or array/list of ctors) found on the top of the Stack
+     * <code>compStack</code> (for component stack). When the named variable
+     * is found, return the stack compStack modified so that it now contains
+     * each ctor-type variable that on the path to the named variable. If the
+     * variable is not found after exhausting all possibilities, throw
+     * NoSuchVariable.<p>
+     *
+     * Note: This method takes the stack as a parameter so that it can be
+     * used by a parser that is working through a list of identifiers that
+     * represents the path to a variable <em>as well as</em> a shorthand
+     * notation for the identifier that is the equivalent to the leaf node
+     * name alone. In the form case the caller helps build the stack by
+     * repeatedly calling <code>search</code>, in the latter case this method
+     * must build the stack itself. This method is over kill for the first
+     * case.
+     *
+     @param name Search for the named variable.
+     @param compStack The component stack. This holds the BaseType variables
+     * that match the each component of a specific variable's name. This
+     * method starts its search using the element at the top of the stack and
+     * adds to the stack. The order of BaseType variables on the stack is the
+     * reverse of the tree-traverse order. That is, the top most element on
+     * the stack is the BaseType for the named variable, <em>under</em> that
+     * is the named variable's parent and so on.
+     @return A stack of BaseType variables which hold the path from the top
+     * of the DDS to the named variable.
+     @exception NoSuchvariableException. */
+    public Stack search(String name, Stack compStack)
+	throws NoSuchVariableException
+    {
+	DDSSearch ddsSearch = new DDSSearch(compStack);
+
+	if (ddsSearch.deepSearch(name))
+	    return ddsSearch.components;
+	else
+	    throw new NoSuchVariableException("The variable `" + name 
+				      + "' was not found in the dataset.");
+    }
+
+    /** Find variables in the DDS when users name them with either fully- or
+     * partially-qualified names. */
+    private final class DDSSearch {
+	Stack components;
+
+	DDSSearch(Stack c) {
+	    components = c;
+	}
+
+	BaseType simpleSearch(String name, BaseType start) {
+	    Enumeration e = null;
+	    DConstructor dcv;
+	    if (start == null)
+		e = getVariables(); // Start with the whole DDS
+	    else if (start instanceof DConstructor)
+		e = ((DConstructor)start).getVariables();
+	    else if ((dcv = isVectorOfDConstructor(start)) != null)
+		e = dcv.getVariables();
+	    else 
+		return null;
+
+	    while (e.hasMoreElements()) {
+		BaseType v = (BaseType)e.nextElement();
+		if (v.getName().equals(name)) {
+		    return v;
+		}
+	    }
+
+	    return null;	// Not found
+	}
+	    
+	/** Look for the variable named <code>name</code>. First perform the
+	 * shallow search (see simpleSearch) and then look through all the
+	 * ctor variables. If there are no more ctors to check and the
+	 * variable has not been found, return false.
+	 *
+	 * Note that this method uses the return value to indicate whether a
+	 * particular invocation found <code>name</code>. */
+	boolean deepSearch(String name) throws NoSuchVariableException {
+	
+	    BaseType start = components.empty() ? null 
+		: (BaseType)components.peek();
+		
+	    BaseType found;
+	    
+	    if ((found = simpleSearch(name, start)) != null) {
+		components.push(found);
+		return true;
+	    }
+	
+	    Enumeration e;
+	    DConstructor dcv;
+	    if (start == null)
+		e = getVariables(); // Start with the whole DDS
+	    else if (start instanceof DConstructor)
+		e = ((DConstructor)start).getVariables();
+	    else if ((dcv = isVectorOfDConstructor(start)) != null)
+		e = dcv.getVariables();
+	    else 
+		return false;
+
+	    while (e.hasMoreElements()) {
+		BaseType v = (BaseType)e.nextElement();
+		components.push(v);
+		if (deepSearch(name))
+		    return true;
+		else
+		    components.pop();
+	    }
+	
+	    // This second return takes care of the case where a dataset
+	    // lists a bunch of ctor variable, one after another. Once the
+	    // first ctor (say a Grid) has been searched returning false to
+	    // the superior invocation of deepSearch pops it off the stack
+	    // and the while loop will search starting with the next variable
+	    // in the DDS. 
+	    return false;
+	}
+    }	    
+
+    /**
+    * Returns an <code>Enumeration</code> of the dataset variables.
+    *
+    * @return an <code>Enumeration</code> of <code>BaseType</code>.
+    */
+    public final Enumeration getVariables() {
+        return vars.elements();
+    }
+
+    /**
+    * Returns the number of variables in the dataset.
+    *
+    * @return the number of variables in the dataset.
+    */
+    public final int numVariables() {
+        return vars.size();
+    }
+
+  /**
+   * Reads a <code>DDS</code> from the named <code>InputStream</code>. This
+   * method calls a generated parser to interpret an ASCII representation of a
+   * <code>DDS</code>, and regenerate that <code>DDS</code> in memory.
+   *
+   * @param is the InputStream containing the <code>DDS</code> to parse.
+   * @exception ParseException thrown on a parser error.
+   * @exception DDSException thrown on an error constructing the
+   *    <code>DDS</code>.
+   * @see dods.dap.parser.DDSParser
+   */
+  public void parse(InputStream is) throws ParseException, DDSException {
+    DDSParser dp = new DDSParser(is);
+    dp.Dataset(this, factory);
+  }
+
+  /**
+   * Check the semantics of the <code>DDS</code>. If
+   * <code>all</code> is true, check not only the semantics of the
+   * <code>DDS</code> itself, but also recursively check all variables
+   * in the dataset.
+   *
+   * @param all this flag indicates whether to check the semantics of the
+   *      member variables, too.
+   * @exception BadSemanticsException if semantics are bad
+   */
+  public void checkSemantics(boolean all)
+                             throws BadSemanticsException {
+    if (name == null) {
+      System.err.println("A dataset must have a name");
+      throw new BadSemanticsException("DDS.checkSemantics(): A dataset must have a name");
+    }
+    Util.uniqueNames(vars, name, "Dataset");
+
+    if (all) {
+      for (Enumeration e = vars.elements(); e.hasMoreElements() ;) {
+	BaseType bt = (BaseType)e.nextElement();
+	bt.checkSemantics(true);
+      }
+    }
+  }
+
+  /**
+   * Check the semantics of the <code>DDS</code>.  Same as
+   * <code>checkSemantics(false)</code>.
+   *
+   * @exception BadSemanticsException if semantics are bad
+   * @see DDS#checkSemantics(boolean)
+   */
+  public final void checkSemantics() throws BadSemanticsException {
+    checkSemantics(false);
+  }
+
+    /**
+    * Print the <code>DDS</code> on the given <code>PrintWriter</code>.
+    *
+    * @param os the <code>PrintWriter</code> to use for output.
+    */
+    public void print(PrintWriter os) {
+        os.println("Dataset {");
+        for(Enumeration e = vars.elements(); e.hasMoreElements(); ) {	
+            BaseType bt = (BaseType)e.nextElement();
+
+	    
+            bt.printDecl(os);
+        }
+        os.print("} ");
+        if(name != null)
+            os.print(name);
+        os.println(";");
+    }
+
+  /**
+   * Print the <code>DDS</code> on the given <code>OutputStream</code>.
+   *
+   * @param os the <code>OutputStream</code> to use for output.
+   * @see DDS#print(PrintWriter)
+   */
+  public final void print(OutputStream os) {
+    PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+    print(pw);
+    pw.flush();
+  }
+  
+  
+/*
+    public void toASCII(PrintWriter pw, boolean addName){  
+        
+	String s = "";
+	Enumeration e = getVariables();
+	
+	//System.out.println("DDS.toASCII("+addName+")  getName(): "+getName());
+	while(e.hasMoreElements()){
+	    BaseType bt = (BaseType)e.nextElement();
+	    //System.out.println("Type: "+bt.getClass().getName());
+	    //bt.toASCII(pw,addName,getNAme(),true);
+	    bt.toASCII(pw,addName,null,true);
+	}
+
+
+    }
+*/
+
+
+
+  
+}
diff --git a/dods/dap/DDSException.java b/dods/dap/DDSException.java
new file mode 100644
index 0000000..e34b142
--- /dev/null
+++ b/dods/dap/DDSException.java
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * DDS exception. This is the root of all the DDS exception classes.
+ *
+ * @version $Revision: 1.2 $
+ * @author jehamby
+ * @see DODSException
+ */
+public class DDSException extends DODSException {
+  /**
+   * Construct a <code>DDSException</code> with the specified detail
+   * message and DODS error code.
+   *
+   * @param s the detail message.
+   */
+  public DDSException(int error, String s) {
+    super(error, s);
+  }
+}
diff --git a/dods/dap/DFloat32.java b/dods/dap/DFloat32.java
new file mode 100644
index 0000000..4973246
--- /dev/null
+++ b/dods/dap/DFloat32.java
@@ -0,0 +1,143 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>FLoat32</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public class DFloat32 extends BaseType implements ClientIO {
+  /** Constructs a new <code>DFloat32</code>. */
+  public DFloat32() { super(); }
+
+  /**
+   * Constructs a new <code>DFloat32</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DFloat32(String n) { super(n); }
+
+  /** The value of this <code>DFloat32</code>. */
+  private float val;
+
+  /**
+   * Get the current value as a float.
+   * @return the current value.
+   */
+  public final float getValue() {
+    return val;
+  }
+
+  /**
+   * Set the current value.
+   * @param newVal the new value.
+   */
+  public final void setValue(float newVal) {
+    val = newVal;
+  }
+
+  /**
+   * Constructs a new <code>Float32PrimitiveVector</code>.
+   * @return a new <code>Float32PrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new Float32PrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Float32";
+  }
+
+
+
+  
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + val + ";");
+    } else
+      os.print(val);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException {
+    val = source.readFloat();
+    if(statusUI != null)
+      statusUI.incrementByteCount(4);
+  }
+
+
+    /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    sink.writeFloat(val);
+  }
+}
diff --git a/dods/dap/DFloat64.java b/dods/dap/DFloat64.java
new file mode 100644
index 0000000..251dd9a
--- /dev/null
+++ b/dods/dap/DFloat64.java
@@ -0,0 +1,124 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>Float64</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ */
+public class DFloat64 extends BaseType implements ClientIO {
+  /** Constructs a new <code>DFloat64</code>. */
+  public DFloat64() { super(); }
+
+  /**
+   * Constructs a new <code>DFloat64</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DFloat64(String n) { super(n); }
+
+  /** The value of this <code>DFloat64</code>. */
+  private double val;
+
+  /**
+   * Get the current value as a double.
+   * @return the current value.
+   */
+  public final double getValue() {
+    return val;
+  }
+
+  /**
+   * Set the current value.
+   * @param newVal the new value.
+   */
+  public final void setValue(double newVal) {
+    val = newVal;
+  }
+
+  /**
+   * Constructs a new <code>Float64PrimitiveVector</code>.
+   * @return a new <code>Float64PrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new Float64PrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Float64";
+  }
+
+
+
+ 
+   /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + val + ";");
+    } else
+      os.print(val);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException {
+    val = source.readDouble();
+    if(statusUI != null)
+      statusUI.incrementByteCount(8);
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    sink.writeDouble(val);
+  }
+}
diff --git a/dods/dap/DGrid.java b/dods/dap/DGrid.java
new file mode 100644
index 0000000..64cc00a
--- /dev/null
+++ b/dods/dap/DGrid.java
@@ -0,0 +1,430 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+import java.util.Vector;
+import java.util.Enumeration;
+
+/**
+ * This class holds a <code>DArray</code> and a set of "Map"
+ * vectors.  The Map vectors are one-dimensional arrays corresponding
+ * to each dimension of the central <code>Array</code>.  Using this scheme, a
+ * <code>Grid</code> can represent, in a rectilinear array, data which is not
+ * in reality rectilinear.  An example will help make this clear.
+ * <p>
+ * Assume that the following array contains measurements of some real
+ * quantity, conducted at nine different points in space:
+ *
+ * <code><pre>
+ * A = [ 1  2  3  4 ]
+ *     [ 2  4  6  8 ]
+ *     [ 3  6  9  12]
+ * </pre></code>
+ *
+ * To locate this <code>Array</code> in the real world, we could note the
+ * location of one corner of the grid, and the grid spacing.  This would allow
+ * us to calculate the location of any of the other points of the
+ * <code>Array</code>.
+ * <p>
+ * This approach will not work, however, unless the grid spacing is
+ * precisely regular.  If the distance between Row 1 and Row 2 is not
+ * the same as the distance between Row 2 and Row 3, the scheme will
+ * break down.  The solution is to equip the <code>Array</code> with two
+ * <code>Map</code> vectors that define the location of each row or column of
+ * the array:
+ *
+ * <code><pre>
+ *       A = [ 1  2  3  4 ] Row = [ 0 ]
+ *           [ 2  4  6  8 ]       [ 3 ]
+ *           [ 3  6  9  12]       [ 8 ]
+ *
+ *  Column = [ 0  2  8  27]
+ * </pre></code>
+ *
+ * The real location of the point in the first row and column of the
+ * array is now exactly fixed at (0,0), and the point in the last row
+ * and last column is at (8,27). 
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see DArray
+ */
+public class DGrid extends DConstructor implements ClientIO {
+  /** The <code>Array</code> part of the <code>DGrid</code> */
+  public static final int ARRAY = 1;
+
+  /** The Map part of the <code>DGrid</code> */
+  public static final int MAPS = 2;
+
+  /** The Array component of this <code>DGrid</code>. */
+  protected DArray arrayVar;
+
+  /** The Map component of this <code>DGrid</code>. */
+  protected Vector mapVars;
+
+  /** Constructs a new <code>DGrid</code>. */
+  public DGrid() { this(null); }
+
+  /**
+   * Constructs a new <code>DGrid</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DGrid(String n) {
+    super(n);
+    mapVars = new Vector();
+  }
+
+    /**
+    * Returns a clone of this <code>DGrid</code>.  A deep copy is performed
+    * on all data inside the variable.
+    *
+    * @return a clone of this <code>DGrid</code>.
+    */
+    public Object clone() {
+        DGrid g = (DGrid)super.clone();
+        g.arrayVar = (DArray)arrayVar.clone();
+        g.mapVars = new Vector();
+        for(int i=0; i<mapVars.size(); i++) {
+            BaseType bt = (BaseType)mapVars.elementAt(i);
+            g.mapVars.addElement(bt.clone());
+        }
+        return g;
+    }
+
+    /**
+    * Returns the DODS type name of the class instance as a <code>String</code>.
+    * @return the DODS type name of the class instance as a <code>String</code>.
+    */
+    public String getTypeName() {
+        return "Grid";
+    }
+
+    /**
+    * Returns the number of variables contained in this object. For simple and
+    * vector type variables, it always returns 1. To count the number
+    * of simple-type variable in the variable tree rooted at this variable, set
+    * <code>leaves</code> to <code>true</code>.
+    *
+    * @param leaves If true, count all the simple types in the `tree' of
+    *    variables rooted at this variable.
+    * @return the number of contained variables.
+    */
+    public int elementCount(boolean leaves) {
+        if (!leaves)
+            return mapVars.size() + 1; // Number of Maps plus 1 Array component
+        else {
+            int count = 0;
+            for (Enumeration e = mapVars.elements() ; e.hasMoreElements() ;) {
+                BaseType bt = (BaseType)e.nextElement();
+                count += bt.elementCount(leaves);
+            }
+            count += arrayVar.elementCount(leaves);
+            return count;
+        }
+    }
+
+  /**
+   * Adds a variable to the container.
+   * @param v the variable to add.
+   * @param part the part of the <code>DGrid</code> to be modified.  Allowed
+   *    values are <code>ARRAY</code> or <code>MAPS</code>.
+   * @exception IllegalArgumentException if an invalid part was given.
+   */
+    public void addVariable(BaseType v, int part) {
+
+        if (!(v instanceof DArray)) throw new IllegalArgumentException(
+                                            "Grid `" + getName() +
+                                            "'s' member `" + arrayVar.getName() + 
+                                            "' must be an array");
+	        
+	v.setParent(this);
+	
+        switch (part) {
+	
+            case ARRAY:
+                    arrayVar = (DArray)v;
+                    return;
+		    
+            case MAPS:
+                mapVars.addElement(v);
+                return;
+		
+            default:
+                throw new IllegalArgumentException("addVariable(): Unknown Grid part");
+        }
+    }
+
+    /**
+    * Returns the named variable.
+    * @param name the name of the variable.
+    * @return the named variable.
+    * @exception NoSuchVariableException if the named variable does not
+    *     exist in this container.
+    */
+    public BaseType getVariable(String name) throws NoSuchVariableException {
+    
+        int dotIndex = name.indexOf('.');
+	
+        if (dotIndex != -1) {  // name contains "."
+	
+            String aggregate = name.substring(0, dotIndex);
+            String field = name.substring(dotIndex+1);
+
+            BaseType aggRef = getVariable(aggregate);
+            if (aggRef instanceof DConstructor)
+                return ((DConstructor)aggRef).getVariable(field);  // recurse
+            else
+                ; // fall through to throw statement
+        } 
+	else {
+            if (arrayVar.getName().equals(name))
+                return arrayVar;
+
+            for (Enumeration e = mapVars.elements() ; e.hasMoreElements() ;) {
+                BaseType v = (BaseType)e.nextElement();
+                if (v.getName().equals(name))
+                    return v;
+            }
+        }
+        throw new NoSuchVariableException("DGrid.getVariable() No such variable: '" + name + "'");
+    }
+    
+    /**
+    * Gets the indexed variable. For a DGrid the index 0 returns the <code>DArray</code> and 
+    * indexes 1 and higher return the associated map <code>Vector</code>s. 
+    * @param index the index of the variable in the <code>Vector</code> Vars.
+    * @return the indexed variable.
+    * @exception NoSuchVariableException if the named variable does not
+    *     exist in this container.
+    */
+    public BaseType getVar(int index) 
+                                    throws NoSuchVariableException {
+        if(index == 0){
+            return(arrayVar);
+        }
+        else {
+            int i = index - 1;
+            if(i < mapVars.size())
+                return((BaseType)mapVars.elementAt(i));
+            else
+                throw new NoSuchVariableException("DGrid.getVariable() No Such variable: " + index + " - 1)");
+        }	
+    }
+
+
+    /** Private class for implemantation of the Enumeration. Because
+    * DStructure and DSequence are simpler classes and use a single Vector,
+    * their implementations of getVariables aren't as fancy. */
+    class EnumerateDGrid implements Enumeration {
+        boolean array;
+        Enumeration e;
+        EnumerateDGrid() {
+            array = false;	// true when the array is/has being/been
+                            // visited
+            e = mapVars.elements();
+        }
+        public boolean hasMoreElements() {
+            return (array == false) || e.hasMoreElements();
+        }
+        public Object nextElement() {
+            if (!array) {
+                array = true;
+                return arrayVar;
+            }
+            else {
+                return e.nextElement();
+            }
+        }
+    }
+
+    /** Return an Enumeration that can be used to iterate over the members of
+    * a Structure. This implementation provides access to the elements of
+    * the Structure. Each Object returned by the Enumeration can be cast to
+    * a BaseType. 
+    @return An Enumeration */
+    public Enumeration getVariables() {
+        return new EnumerateDGrid();
+    }
+
+    /**
+    * Checks for internal consistency.  For <code>DGrid</code>, verify that
+    * the map variables have unique names and match the number of dimensions
+    * of the array variable.
+    *
+    * @param all for complex constructor types, this flag indicates whether to
+    *    check the semantics of the member variables, too.
+    * @exception BadSemanticsException if semantics are bad, explains why.
+    * @see BaseType#checkSemantics(boolean)
+    */
+    public void checkSemantics(boolean all) throws BadSemanticsException {
+        super.checkSemantics(all);
+
+        Util.uniqueNames(mapVars, getName(), getTypeName());
+
+        if (arrayVar == null)
+            throw new BadSemanticsException("DGrid.checkSemantics(): Null grid base array in `"
+				      + getName() + "'");
+				      
+        // check semantics of array variable
+        arrayVar.checkSemantics(all);
+	
+        // enough maps?
+        if (mapVars.size() != arrayVar.numDimensions())
+            throw new BadSemanticsException("DGrid.checkSemantics(): The number of map variables for grid `"
+                      + getName() + "' does not match the number of dimensions of `"
+                      + arrayVar.getName() + "'");
+
+        //----- I added this next test 12/3/99. As soon as I did I questioned whether or not
+        //----- it adds any value. ie: Can it ever happen that this test fails? I don't think
+        //----- so now that I have written it...  ndp 12/3/99
+
+        // Is the size of the maps equal to the size of the cooresponding dimensions?
+        Enumeration emap = mapVars.elements();
+
+        Enumeration edims = arrayVar.getDimensions();
+        int dim = 0;
+        while(emap.hasMoreElements() && edims.hasMoreElements()){
+
+            DArray thisMapArray = (DArray) emap.nextElement();
+            Enumeration ema = thisMapArray.getDimensions();
+            DArrayDimension thisMapDim = (DArrayDimension) ema.nextElement();
+    
+            DArrayDimension thisArrayDim = (DArrayDimension) edims.nextElement();
+
+            if(thisMapDim.getSize() != thisArrayDim.getSize() ){
+
+                throw new BadSemanticsException("In grid '" + 
+                        getName() +
+                        " The size of dimension " +
+                        dim + " in the array component '" +
+                        arrayVar.getName() +
+                        "is not equal to the size of the coresponding map vector '" +
+                        thisMapArray.getName() + ".");
+            }
+            dim++;
+        }
+	//----- end  ndp 12/3/99
+		      
+    }
+
+
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean)
+    */
+    public void printDecl(PrintWriter os, String space,boolean print_semi, boolean constrained) {
+        os.println(space + getTypeName() + " {");
+        os.println(space + " ARRAY:");
+        arrayVar.printDecl(os, space + "    ", true);
+        os.println(space + " MAPS:");
+        for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
+            BaseType bt = (BaseType)e.nextElement();
+            bt.printDecl(os, space + "    ", true);
+        }
+        os.print(space + "} " + getName());
+        if (print_semi)
+            os.println(";");
+    }
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+
+        if (print_decl_p) {
+            printDecl(os, space, false);
+            os.print(" = ");
+        }
+
+        os.print("{ ARRAY: ");
+        arrayVar.printVal(os, "", false);
+	
+        os.print(" MAPS: ");
+        for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
+            BaseType bt = (BaseType)e.nextElement();
+            bt.printVal(os, "", false);
+            if(e.hasMoreElements())
+                os.print(", ");
+        }
+        os.print(" }");
+
+        if (print_decl_p)
+            os.println(";");
+    }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @exception DataReadException if an unexpected value was read.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, DataReadException {
+    arrayVar.deserialize(source, sv, statusUI);
+    for (Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
+      if(statusUI != null && statusUI.userCancelled())
+	throw new DataReadException("DGrid.deserialize(): User cancelled");
+      ClientIO bt = (ClientIO)e.nextElement();
+      bt.deserialize(source, sv, statusUI);
+     }
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    arrayVar.externalize(sink);
+    for (Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
+      ClientIO bt = (ClientIO)e.nextElement();
+      bt.externalize(sink);
+    }
+  }
+}
diff --git a/dods/dap/DInt16.java b/dods/dap/DInt16.java
new file mode 100644
index 0000000..885c14d
--- /dev/null
+++ b/dods/dap/DInt16.java
@@ -0,0 +1,149 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>Int16</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public class DInt16 extends BaseType implements ClientIO {
+
+  /** The value of this <code>DInt16</code>. */
+  private short val;
+
+  /** Constructs a new <code>DInt16</code>. */
+  public DInt16() { super(); }
+
+  /**
+   * Constructs a new <code>DInt16</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DInt16(String n) { super(n); }
+
+  /**
+   * Get the current value as a short (16bit int).
+   * @return the current value.
+   */
+  public final short getValue() {
+    return val;
+  }
+
+  /**
+   * Set the current value.
+   * @param newVal the new value.
+   */
+  public final void setValue(short newVal) {
+    val = newVal;
+  }
+
+  /**
+   * Constructs a new <code>Int16PrimitiveVector</code>.
+   * @return a new <code>Int16PrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new Int16PrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Int16";
+  }
+
+
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + val + ";");
+    } else
+      os.print(val);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException {
+       
+  	// Read this value in as a 32bit int, since the smallest thing
+	// we write to the network is 32bits. Cast it to short (16bit int)!
+    val = (short)source.readInt();
+    if(statusUI != null)
+      statusUI.incrementByteCount(4);
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+  	// Write this value out as a 32bit int, since the smallest thing
+	// we write to the network is 32bits. The cast automagically
+	// pads it out with zero's
+    sink.writeInt((int)val);
+  }
+}
diff --git a/dods/dap/DInt32.java b/dods/dap/DInt32.java
new file mode 100644
index 0000000..9977b48
--- /dev/null
+++ b/dods/dap/DInt32.java
@@ -0,0 +1,123 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>Int32</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ */
+public class DInt32 extends BaseType implements ClientIO {
+  /** Constructs a new <code>DInt32</code>. */
+  public DInt32() { super(); }
+
+  /**
+   * Constructs a new <code>DInt32</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DInt32(String n) { super(n); }
+
+  /** The value of this <code>DInt32</code>. */
+  private int val;
+
+  /**
+   * Get the current value as an int.
+   * @return the current value.
+   */
+  public final int getValue() {
+    return val;
+  }
+
+  /**
+   * Set the current value.
+   * @param newVal the new value.
+   */
+  public final void setValue(int newVal) {
+    val = newVal;
+  }
+
+  /**
+   * Constructs a new <code>Int32PrimitiveVector</code>.
+   * @return a new <code>Int32PrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new Int32PrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Int32";
+  }
+
+
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + val + ";");
+    } else
+      os.print(val);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException {
+    val = source.readInt();
+    if(statusUI != null)
+      statusUI.incrementByteCount(4);
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    sink.writeInt(val);
+  }
+}
diff --git a/dods/dap/DList.java b/dods/dap/DList.java
new file mode 100644
index 0000000..25af2a3
--- /dev/null
+++ b/dods/dap/DList.java
@@ -0,0 +1,49 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.DataInputStream;
+import java.io.PrintWriter;
+
+/**
+ * This class implements a simple list of DODS data
+ * types. A list is a simple sequence of data items, without the
+ * sophisticated subsetting and array indexing features of an Array.
+ * <p>
+ * DODS does not support Lists of Lists. This restriction is enforced by the
+ * DDS parser.
+ *
+ * @version $Revision: 1.2 $
+ * @author jehamby
+ * @see BaseType
+ * @see DVector
+ */
+public class DList extends DVector {
+  /** Constructs a new <code>DList</code>. */
+  public DList() { super(); }
+
+  /**
+   * Constructs a new <code>DList</code> with the given name.
+   *
+   * @param n the name of the variable.
+   */
+  public DList(String n) { super(n); }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "List";
+  }
+
+  
+}
diff --git a/dods/dap/DODSException.java b/dods/dap/DODSException.java
new file mode 100644
index 0000000..952e826
--- /dev/null
+++ b/dods/dap/DODSException.java
@@ -0,0 +1,231 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+import java.io.*;
+
+import dods.dap.parser.ErrorParser;
+import dods.dap.parser.ParseException;
+
+/**
+ * Holds an exception thrown by DODS server to a client.
+ * <p>
+ * Unlike the other DODS exceptions, this one contains extra methods to
+ * get the various fields sent by the server, and a <code>parse</code> method
+ * to parse the <code>Error</code> sent from the server.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see DODSException
+ */
+public class DODSException extends Exception {
+
+    /** Undefined error. */
+    public static final int UNDEFINED_ERROR   = -1;
+    /** Unknown error. */
+    public static final int UNKNOWN_ERROR     = 0;
+    /** The file specified by the DODS URL does not exist. */
+    public static final int NO_SUCH_FILE      = 1;
+    /** The variable specified in the DODS URL does not exist. */
+    public static final int NO_SUCH_VARIABLE  = 2;
+    /** The expression specified in the DODS URL is not valid. */
+    public static final int MALFORMED_EXPR    = 3;
+    /** The user has no authorization to read the DODS URL. */
+    public static final int NO_AUTHORIZATION  = 4;
+    /** The file specified by the DODS URL can not be read. */
+    public static final int CANNOT_READ_FILE = 5;
+
+    /*
+    * Some Error objects may contain programs which can be used to
+    * correct the reported error. These programs are run using a public
+    * member function of the Error class. If an Error object does not
+    * have an associated correction program, the program type is NO_PROGRAM.
+    */
+
+    /** Undefined program type. */
+    public static final int UNDEFINED_PROG_TYPE = -1;
+    /** This Error does not contain a program. */
+    public static final int NO_PROGRAM   = 0;
+    /** This Error contains Java bytecode. */
+    public static final int JAVA_PROGRAM = 1;
+    /** This Error contains TCL code. */
+    public static final int TCL_PROGRAM  = 2;
+
+    /** The error code. 
+    * @serial
+    */
+    private int errorCode;
+    /** The error message. 
+    * @serial
+    */
+    private String errorMessage;
+    /** The program type. 
+    * @serial
+    */
+    private int programType;
+
+    /**
+    * The program source.  if programType is TCL_PROGRAM, then this is ASCII
+    * text.  Otherwise, undefined (this will need to become a byte[] array if
+    * the server sends Java bytecodes, for example).
+    * @serial
+    */
+    private String programSource;
+
+    /** Construct an empty <code>DODSException</code>. */
+    public DODSException() {
+        // this should never be seen, since this class overrides getMessage()
+	// to display its own error message.
+        super("DODSException");
+    }
+
+    /** Construct a <code>DODSException</code>. */
+    public DODSException(String msg) {
+        this();
+        errorCode = UNKNOWN_ERROR;
+        errorMessage = msg;
+    }
+
+
+    /**
+    * Construct a <code>DODSException</code> with the given message.
+    * @param code the error core
+    * @param msg the error message
+    */
+    public DODSException(int code, String msg) {
+        this();
+        errorCode = code;
+        errorMessage = msg;
+    }
+
+    /**
+    * Returns the error code.
+    * @return the error code.
+    */
+    public final int getErrorCode() {
+        return errorCode;
+    }
+
+    /**
+    * Returns the error message.
+    * @return the error message.
+    */
+    public final String getErrorMessage() {
+        return errorMessage;
+    }
+
+    /**
+    * Returns the program type.
+    * @return the program type.
+    */
+    public final int getProgramType() {
+        return programType;
+    }
+
+    /**
+    * Returns the program source.
+    * @return the program source.
+    */
+    public final String getProgramSource() {
+        return programSource;
+    }
+
+    /**
+    * Returns the detail message of this throwable object.
+    * @return the detail message of this throwable object.
+    */
+    public String getMessage() {
+        return errorMessage;
+    }
+
+    /**
+    * Sets the error code.
+    * @param code the error code.
+    */
+    public final void setErrorCode(int code) {
+        errorCode = code;
+    }
+
+    /**
+    * Sets the error message.
+    * @param msg the error message.
+    */
+    public final void setErrorMessage(String msg) {
+        errorMessage = msg;
+    }
+
+    /**
+    * Sets the program type.
+    * @param type the program type.
+    */
+    public final void setProgramType(int type) {
+        programType = type;
+    }
+
+    /**
+    * Sets the program source.
+    * @param source the program source.
+    */
+    public final void setProgramSource(String source) {
+        programSource = source;
+    }
+
+    /**
+    * Reads an Error description from the named InputStream.  This
+    * method calls a generated parser to interpret an ASCII representation of an
+    * <code>Error</code>, and regenerate it as a <code>DODSException</code>.
+    *
+    * @param is the InputStream containing the <code>Error</code> to parse.
+    * @see dods.dap.parser.ErrorParser
+    */
+    public final void parse(InputStream is) {
+        ErrorParser ep = new ErrorParser(is);
+        try {
+            ep.ErrorObject(this);
+        } catch (ParseException e) {
+	    String msg = e.getMessage();
+	    if (msg != null)
+		msg.replace('\"', '\'');
+            errorMessage = "Error parsing server Error object!\n" + msg;
+        }
+    }
+
+    /**
+    * Print the Error message on the given <code>PrintWriter</code>.
+    * This code can be used by servlets to throw DODSException to client.
+    *
+    * @param os the <code>PrintWriter</code> to use for output.
+    */
+    public void print(PrintWriter os) {
+        os.println("Error {");
+        os.println("    code = " + errorCode + ";");
+        // If the error message is wrapped in double quotes, print it, else,
+        // add wrapping double quotes.
+        if (errorMessage.charAt(0)=='"')
+            os.println("    message = " + errorMessage + ";");
+        else
+            os.println("    message = \"" + errorMessage + "\";");
+        os.println("};");
+    }
+
+    /**
+    * Print the Error message on the given <code>OutputStream</code>.
+    *
+    * @param os the <code>OutputStream</code> to use for output.
+    * @see DODSException#print(PrintWriter)
+    */
+    public final void print(OutputStream os) {
+        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+        print(pw);
+        pw.flush();
+    }
+}
diff --git a/dods/dap/DONE b/dods/dap/DONE
new file mode 100644
index 0000000..cd25ccb
--- /dev/null
+++ b/dods/dap/DONE
@@ -0,0 +1,371 @@
+/** This the (albiet primitive) bug tracking system for DODS */
+
+
+*3*(****DONE ***) Fix SDArray types (SDGrid, maybe more) to use the ServerArrayMethods 
+
+
+		       
+			       	       
+*4*    ****DONE**** DGrid.checkSemantics() does not check for map vector lengths vs size of
+        Grid array. (Duh!)10/25/99
+	    **** OK, I added this check, but when I finished I decided that it could 
+	    probably never happen that it would fail this test in the first place,
+	    the parser should catch this problem...
+	
+*5*    DGrid.addVariable() doesn't QC shit. (Duh!)10/25/99
+        **** Fixed this so it checks to make sure a DArray is passed in...
+	
+
+*6*    DDS.send() needs support for compression and MIME stuff (AT LEAST!)  10/25/99 				
+		*** MIME Done!*** 12/10/99
+		*** Copression Done! *** 12/14/99
+		*** Both of these pieces moved into the servlet code...
+		
+
+
+*8*(****DONE ***) CEEvaluator: Fix calls to DataInputStream.readLine 10/25/99
+
+	 
+*11*   **** DONE **** Have jgal check the bounds handling for projection information in 
+        DArrayDimension and the getDArrayDimension function in DArray 10/26/99
+
+        *** I did it and it seems to work in testing....
+	
+			 		 
+			 	 
+*13*(****DONE ***) Eliminate setStart, setStride, and setStop methods from DArrayDimension, SDArray, 
+        and SDGrid if possible. 10/26/99
+        *** Started: setStart setStride and setStop commented out from all of the above.
+        *** Finished. Comments removed. Code Checked in and compiled. 11/2/99
+
+
+*14*   (****DONE****)Add the serialize() method to ServerMethods (hmmm fun) 10/26/99
+       *** Stubbed into everything. 10/28/99
+       *** Status:
+                    SDBoolean:     Done 11/3/99
+                    SDByte:        Done 11/3/99
+                    SDFloat32:     Done 11/3/99
+                    SDFloat64:     Done 11/3/99
+                    SDInt16:       Done 11/3/99
+                    SDUInt16:      Done 11/3/99
+                    SDInt32:       Done 11/3/99
+                    SDUInt32:      Done 11/3/99
+                    SDURL:         Done 11/3/99
+                    SDString:      Done 11/3/99
+                    SDArray:       Done 11/3/99 (see 24)
+                    SDList:        Done 11/3/99 (see 24)
+		            SDGrid:        Done 11/4/99 (see 27)
+			        SDSequence:    Done 11/4/99 (see 28)
+                    SDStructure:   Done 11/3/99
+	
+
+*15*   **** DONE **** IF the constraint expression is empty nothing gets sent, and that's not right. 
+       (ExprParser problem) 10/28/99
+
+
+
+*16*(****DONE ***) Debug problem with the selection part of the parser (ExprParser.rel_op()) 10/28/99
+        **** Found problem. The current grammer for rel_op produces java code that is broken. 
+	    I sent mail to jgal with details. 10/29/99
+    	**** FIxed IT! 10/29/99
+
+*17*(****DONE ***) CEEvaluator._cv Clause vector is not initialized after parseing the constraint expression. 
+        It should be, even if there was no selection! 10/28/99
+	    **** Fixed. Problem with CEEvaluator() Ctor.
+
+
+*18*(****DONE ***) How deep should CEEval.markAll() go?? Should it tweak start, stride, and stop 
+        for Arrays and Grids? 10/29/99
+        **** Changed markAll() to go "deep" and modify the array projection stuff.
+
+*19*(**** DONE ****) The way we handle Clauses is flawed. DreferenceClause and FunctionClause both
+        have evalBoolean() methods. RelOpClause does not. By what method do we intend to eval Clauses
+	    at runtime????  The subtext to this is that I need to write CEEval.evalClauses()... 10/29/99
+        **** Fixed - retrograde version of Clause removed from dods.dap.Server. Correct version installed.
+
+
+*20*(****DONE ***) In serialize() I check to see if the current variable has been read. 
+        if not I read it. then I evaluate all of the clauses against it to see if I 
+	    should "externalize" it (????) then externalize it...
+        What does it mean to evaluate clauses??
+        Where do the loperand and roperand get filled (for RelOpClause) Why would these 
+	    change from one var to the next duriong serialize?
+        **** Implemented as described, evalClauses() calls evalBoolean() of each Clause 
+	    in the CEEval Clause vector and returns true if NONE of the Clauses evaluate to 
+	    false. The loperands get filled (if they haven't been) in evalBoolean().
+	
+
+*21*(****DONE ***) What I see is that there seems to be a syncronization problem with 
+        reading variables during the evaluation of Clauses. How do we handle reading 
+	    stuff that appears in the Clauses but is not the "next thing to read" from the 
+	    DDS?? Do we assume that the read() methods are smart enough to cope? What about 
+	    variables with the same name in the DDS? Does this affect things? How does a 
+	    particular member of the DDS know from what to read?
+        **** Answer Read had better be smart enough to cope with this situation. In
+	    reality, (22) is more likely the situation.
+
+
+*22*(****DONE ***) Since evaluating Clauses causes all of the pertinent data to be read 
+        anyway, why do we need to eval all of the Clauses each time we serialize another 
+	    member of the DDS?? It seems like we could cache the combined result (T/F) of 
+	    evalClauses and just check it. OR (and this gets at another question that is 
+	    nagging me) is it possible values of the loperands in the CEEvaluator Clause vector 
+	    are supposed to change during the send() process (It was certainly my understanding 
+	    that they didn't change once read, but it bothers me that they can't)...
+	    
+        **** The answer to this is that Sequences are really the only situation where the 
+	    evaluation of Clauses means anything anyway. If you were to build a Clause that 
+	    references a simple variable at the highest level in the DDS then if the Clause evals
+	    to False then no data gets sent. In a Sequence, the DDS contains the description of
+	    one row, not all the rows. As each row gets read the variables in the DDS 
+	    representation of the row get overwritten. Thus re-evaluating the Clauses for each 
+	    row in a Sequence makes sense and will produce the desired (filtered) results.
+	    **** It will be important to reset the isRead() flag for each variable in a Sequence
+	    prior to reading the next row.
+	
+
+*23*(**** DONE *****) Fix bad syntax on Revision field in comments. 11/2/99
+
+
+
+
+*24*(**** DONE *****) DVector contains the implementation of deserialize() and externalize() for both DList 
+        and DArray. The implementation for SDList.serialize() can just use the implementation  
+	    of externalize() in DVector, as the entire list goes if any of it goes (ie it does not 
+	    implement the ServerArrayMethods aka ArrayProjection interface). However, this breaks 
+	    for SDArray as the more complicated projection information requires control of the 
+	    "externalization" of the array in the child class (SDArray).
+        This may require changes to DVector to repair. 11/2/99
+	
+        **** Actually in SDArray serialize works the same way as in SDList. The part of SDArray
+	    that actually handles the "projection" stuff is the read() method. SDArray.read() uses
+	    the projection information to get only the stuff from the data archive that is indicated
+	    in the constrained DDS. This information is placed in the SDArray's vector of DArrayDimension
+	    objects (each one of which has storage for the prjection information for that dimension
+	    of the array) by the ExprParser. Thus, since read() is a method of SDArray it has access
+	    to SDArray's stuff. The idea is that when the externalize() method (of DVector) gets called,
+	    the only stuff in the DVector (parent class and underlying storage of SDArray) is the stuff
+	    indicated by the projection information. i.e. it all gets sent.
+	    *!*!*!*!*! This may require some monkeying with DArray and SDArray to mak it work...
+
+
+*25*    (****DONE****) I remain unconvinced that the solution for SDArray in (24) is right for SDArrays 
+        of complextypes... We will need a test DDS with an Array of Structures or some such to check
+	    it. 11/3/99
+	     **** I got over it... ndp
+	
+*26*    DArray.addVariable() does not check the passed parameter v for type if the parameter part
+        indicates that v is a map vector. 11/3/99
+	    **** Fixed see #4
+
+
+*27*    (**** DONE *****) Does (24) hold for SDGrid? i.e. does the application of the projection information  
+        happen in SDGrid.read()? If that's true, then can we just call DGrid.externalize() since the contents
+	    of the array and the map vectors contain ONLY data which is going to be sent? If that's not 
+	    true, then what do we do???  
+	    
+	    **** In fact it is supposed to work the same as SDArray. The projection information is used 
+	    by the read() method to get only the requested part of the grid. Thus serialize(), after 
+	    insuring that the data has been read (and checking clauses) can just call DGrid,
+	    externalize() to send all of the data contained in the object.
+
+
+*28*    (****DONE****) When I went to implement SDSequence.serialize() I realized that it was going 
+        to be identical to all of others. The functionality to read each line (over and over  
+	    until it's done) actually is in CEEvaluator.send(). At least that's how it looks right now...
+	    
+
+*29* (****Done ***) DDSParser.NonListDecl() doesn't contain support for DList
+     or DArray. OOPS! 11/4/99 *** Corollary: NonListDecl() gets called for Lists
+     and Arrays. OOPS! *** And Structures, and Grids This needs fixed ASAP!!!!!!
+
+     I claim this is done. 11/15/99 jhrg
+
+
+*30* (****DONE****)In SDArray.read() (and SDList.read() and SDGrid.read()) there are
+     serious issues wrt to placing data in to the underlying data
+     representation (in memory). In particular the DVector that is the parent
+     class of DArray and DList stores data in a PrimitiveVector. The methods
+     of PrimitiveVector that are available do NOT allow data to be assigned
+     at run time. You must determine exactly WHAT kind of PrimitiveVector (ie
+     BasTypePrimitiveVector, BytePrimitiveVector, etc.) you have and then put
+     data into it using the correct method via casting the PrimitiveVector to
+     the correct type. I am unclear if this represents a problem with the
+     architecture, my understanding of what is available in the read()
+     methods, or if Servers can make assumptions (ie I know what I am serving
+     so I should know what data type a particular variable is going to
+     be....) *** UGH! You can skirt around this by checking all possible
+     cases of instanceof on the PrimitiveVector in the Array... Ick...
+     
+     **** The situation exists because the test classes that we are writing need 
+     to respond to all possible DDS's
+     
+	    
+*31* (****DONE****) I Can't seem to pass array projection information to the parser and get
+     any kind of result. Need to know the constraint expression syntax for
+     that...
+     **** Fixed 11/16/99 jhrg
+     
+     
+
+*32*    ****DONE **** Implement read() for test types:
+        *** Status:
+                    SDBoolean:     Done 
+                    SDByte:        Done
+                    SDFloat32:     Done
+                    SDFloat64:     Done
+                    SDInt16:       Done
+                    SDUInt16:      Done
+                    SDInt32:       Done
+                    SDUInt32:      Done
+                    SDURL:         Done
+                    SDString:      Done
+                    SDArray:       Done 11/8/99 (see 30)
+                    SDList:        Done 11/8/99 (Assuming you can arbitrarily set the length of the List)
+		            SDGrid:        Done 11/15/99
+			        SDSequence:    Done 11/16/99
+                    SDStructure:   Done 11/16/99
+
+
+*33* **** DONE ****Bug: Can't specify the componets of a grid in a constraint expression. 
+     For example: In a grid with internal array g and map vectors, i,j, and 
+     k a constraint expression of "g" will cause a NoSuchVariableException 
+     to be thrown.
+     **** James fixed this (YEAH!!)
+     
+
+	
+*34*  ****DONE**** Read() methods in test classes need to be made to look more like the setProject() methods.
+     ie read() should check projection flags!
+   ->*** Is this really true? What happens when you need to read a variable that isn't projected but
+     appears in the selection component of the constraint expression?
+     *** True for Dconstructors and fixed already! 12/14/99
+	
+  
+          
+
+*35* **** DONE **** Instrumentation should only print out the variables requested after (and maybe before) they are read.
+     This might be something for the testclasses OR something for the Server Abstract types...
+   ->*** What about a printVal(sink,boolean) where we use the boolean flag to indicated wether or
+     not we want the entire object
+
+    **** Made printVal() only print projected stuff. See #39
+
+     
+*36*  **** DONE **** DDS parser pukes on test14. DDS follows: 
+
+            # test problems with named dimensions.
+
+            dataset {
+                int32 i;
+                float64 f[ longitude 10 ];
+            } data5;
+	    
+	James??
+	**** This DDS is supposed to choke the Parser.
+	
+
+
+*38* ****** DONE **** Need to fix printDecl() to work with constrained server types (preserving client functionality)
+
+*39* ****** DONE **** Need to fix printVal() in server types (utilize projection information)
+
+
+*40* **** DONE **** Need to make a smarter read() for the children of DConstructor
+
+    *** Reads only the members of the DConstructor that are marked projected....
+    
+
+*41* ****DONE**** Is there a nasty bug wrt the ReadMe flag in the SDtypes? Does it get set and checked correctly?
+     Especially in the serialization of SDSequence???
+		*** YES! There was a nasty bug and I squished it!
+
+		
+		
+*43*	***DONE***	Fix Exception messages so they do NOT contain double quotes
+		**** Found problem in DGrid.getVariable().
+
+*44*	***DONE*** Add DDS.getFactory()
+
+*45*	***DONE***	Use 44 in CEEval.parseConstraint() and remove BasetypeFactory from 
+		parseConstraint()'s type sig.
+
+*47*	***DONE***	push server implementations from jdods into DODSServelet
+		add abstract method getDDS() to DODSServlet that returns a parsed and 
+		populated DDS (getDDS to be implmented by DODS human clients)
+		
+*50 *	***DONE***	in DODSServlet return a DODSERROR obj for:
+			doGetDAS()
+			doGetINFO()
+			doGetHTML()		
+
+*52*	***DONE***	Nathan makes a drawing of new directory structure. James approves and alters
+		CVS to reflect it.
+		
+*54*	***DONE***		What about printing a constrained DDS if there is a 
+		constraint expression sent along with the request for the DDS??? Fix in DODSServlet!
+
+
+*55*	 	***DONE *** Nasty new bug. Lists of Arrays that I send to geturl break something... 
+		Is it me or is it geturl?
+		*** This problem has 2 parts: 
+			(1) Projection flags are not getting set in arrays of BaseTypes 
+				(fundamentally a parser problem)
+			*2* DONE!  The length of the array is sent twice because of behaviour in the
+				C++ client. The C++ client is not consistant, as it only consumes
+				2 length variables if the arrays & lists are of simple types. Arrays
+				and lists of BaseTypes only get one length value consumed when they arrive
+				at a client. BUG! ICK! 
+				Do we fix the C++ stuff? Or do we make the java stuff compliant? Don't
+				forget that the java code needs to be ammended for both sending and
+				recieving these types (server and client code!!)
+		**** (2) Is fixed. Java code accomodates funky C++ core *sigh*
+		
+				
+*56*		****DONE**** There is a failure to call the read() methods of members of structures 
+		in a list of structures.
+		Possibly the same error as 55.1 ?
+		
+		*** The members of a list do not get their Projection 
+			flags set at all (let alone correctly!)
+		*** Patching SDList.setProject() to detect BaseTypePrimitiveVectors 
+			and to recursively set the proiject flag for the BaseType Template 
+			variable causes data to be sent (which it wasn't before) BUT, geturl 
+			doesn't like the output (the binary looks good on inspection)...
+			
+		*** SDArray has the SAME bug!! (Arrays of Structures, etc. could be bad)
+		
+		*** This bug ALSO affectes arrays and lists of Strings (which implies URLs too)			
+			
+
+*57*	****DONE**** The Parser geneterates error message text that contains double quotes. How
+		Might we fix that? (For now I detect it in the servlet when I recieve a
+		parser exception and I replace all of the  double quotes with single quotes)
+		Fix when we fix 42!
+		
+*58*    ***DONE*** geturl fails (segmentation fault) on test.03 Lots of arrays! What's the problem?
+		*** Arrays of strings are broken! It looks like the right stuff is getting sent but 
+		geturl is barfing on the transmission. Why? Jake padings with nonzero values??
+		**** Same as 55! It's barfing because of something todo with the length headers, etc...
+		
+		*** Same bug as 55.2. Java code accomodates funky C++ core *sigh*
+
+
+*59* 		****DONE****When serializing Int16 and UInt16 I've got to send each 16bit value as a 32 bit value
+		for geturl to understand it. Is that correct? 
+		****YES
+
+
+*63*      Parser (ExprParser) does not allow use to select members of a 
+		Grid by simple name...	
+		Fixed 1/28/2000 jhrg					
+				
+67		When making a selection in the constraint expression if the variable used in the selection
+		is just named (say: http://balhblah/test01.dods?sst&year>1986) then the parser throughs an
+		error. The parser accepts the constraint expression if the selection variable(s) are fully
+		qualifed (ie  http://balhblah/test01.dods?sst&project.cruise.year>1986)
+		Fixed 2/10/2000 jhrg
+
+68		Given 67, if the parser accepts this fully qualified variable name, then the Clause objects
+		dod not get properly populated (the loperand is empty!)
+		Fixed 2/11/2000 jhrg		
diff --git a/dods/dap/DSequence.java b/dods/dap/DSequence.java
new file mode 100644
index 0000000..130376a
--- /dev/null
+++ b/dods/dap/DSequence.java
@@ -0,0 +1,565 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A <code>DSequence</code> in DODS can hold <em>N</em> sequentially accessed
+ * instances of a set of variables. In relation to the <code>DStructure</code>
+ * datatype, a <code>DSequence</code> is a table
+ * of N instances of a <code>DStructure</code>. Data in a
+ * <code>DSequence</code> is accessed row by row.
+ * <p>
+ * Unlike its C++ counterpart, this class reads all of its rows on a
+ * <code>deserialize</code>, which gives <code>DSequence</code> the same
+ * semantics as the other <code>BaseType</code> classes, eliminating the need
+ * to worry about <code>DSequence</code> as a special case.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see DConstructor
+ */
+public class DSequence extends DConstructor implements ClientIO {
+    /** The start of instance byte marker */
+    static protected byte START_OF_INSTANCE = 0x5A;
+
+    /** The end of sequence byte marker */
+    static protected byte END_OF_SEQUENCE = (byte)0xA5;
+
+    /**
+    * The variables in this <code>DSequence</code>, stored in a
+    * <code>Vector</code> of <code>BaseType</code> objects
+    * and used as a template for <code>deserialize</code>.
+    */
+    protected Vector varTemplate;
+
+    /**
+    * The values in this <code>DSequence</code>, stored as a
+    * <code>Vector</code> of <code>Vector</code> of <code>BaseType</code>
+    * objects. */
+    protected Vector allValues;
+
+    /** Level number in a multilevel sequence. */
+    private int level;
+
+    /** Constructs a new <code>DSequence</code>. */
+    public DSequence() {
+        this(null);
+    }
+
+    /**
+    * Constructs a new <code>DSequence</code> with name <code>n</code>.
+    * @param n the name of the variable.
+    */
+    public DSequence(String n) {
+        super(n);
+        varTemplate = new Vector();
+        allValues = new Vector();
+        level = 0;
+    }
+
+    /**
+    * Returns a clone of this <code>DSequence</code>.  A deep copy is performed
+    * on all data inside the variable.
+    *
+    * @return a clone of this <code>DSequence</code>.
+    */
+    public Object clone() {
+    
+        DSequence s = (DSequence)super.clone();	
+	
+        s.varTemplate = new Vector();
+	
+        for(int i=0; i<varTemplate.size(); i++) {
+            BaseType bt = (BaseType)varTemplate.elementAt(i);
+            s.varTemplate.addElement(bt.clone());
+        }
+	
+        s.allValues = new Vector();
+	
+        for(int i=0; i<allValues.size(); i++) {
+            Vector rowVec = (Vector)allValues.elementAt(i);
+            Vector newVec = new Vector();
+            for(int j=0; j<rowVec.size(); j++) {
+                BaseType bt = (BaseType)rowVec.elementAt(j);
+                newVec.addElement(bt.clone());
+            }
+            s.allValues.addElement(newVec);
+        }
+        return s;
+    }
+
+    /**
+    * Returns the DODS type name of the class instance as a <code>String</code>.
+    * @return the DODS type name of the class instance as a <code>String</code>.
+    */
+    public String getTypeName() {
+        return "Sequence";
+    }
+
+    /**
+    * Sets the level of this sequence.
+    * @param level the new level.
+    */
+    protected final void setLevel(int level) {
+        this.level = level;
+    }
+
+    /**
+    * Returns the level of this sequence.
+    * @return the level of this sequence.
+    */
+    protected final int getLevel() {
+        return level;
+    }
+
+    /**
+    * Returns the number of variables contained in this object. For simple and
+    * vector type variables, it always returns 1. To count the number
+    * of simple-type variable in the variable tree rooted at this variable, set
+    * <code>leaves</code> to <code>true</code>.
+    *
+    * @param leaves If true, count all the simple types in the `tree' of
+    *    variables rooted at this variable.
+    * @return the number of contained variables.
+    */
+    public int elementCount(boolean leaves) {
+        if (!leaves)
+            return varTemplate.size();
+        else {
+            int count = 0;
+            for (Enumeration e = varTemplate.elements() ; e.hasMoreElements() ;) {
+                BaseType bt = (BaseType)e.nextElement();
+                count += bt.elementCount(leaves);
+            }
+            return count;
+        }
+    }
+
+    /**
+    * Adds a variable to the container.
+    * @param v the variable to add.
+    * @param part ignored for <code>DSequence</code>.
+    */
+    public void addVariable(BaseType v, int part) {
+        v.setParent(this);
+        varTemplate.addElement(v);
+        if (v instanceof DSequence)
+          ((DSequence)v).setLevel(getLevel()+1);
+    }
+
+    /**
+    * Adds a row to the container.  This is assumed to contain a
+    * <code>Vector</code> of variables of the same type and in the same order
+    * as the variable template added with the <code>addVariable</code> method.
+    *
+    * @param row the <code>Vector</code> to add.
+    */
+    public final void addRow(Vector row) {
+        allValues.addElement(row);
+    }
+
+    /**
+    * Gets a row from the container.  This returns a <code>Vector</code> of
+    * variables of the same type and in the same order as the variable template
+    * added with the <code>addVariable</code> method.
+    *
+    * @param row the row number to retrieve.
+    * @return the <code>Vector</code> of <code>BaseType</code> variables.
+    */
+    public final Vector getRow(int row) {
+        return (Vector)allValues.elementAt(row);
+    }
+
+    /**
+    * Deletes a row from the container.
+    *
+    * @param row the row number to delete.
+    * @exception ArrayIndexOutOfBoundsException if the index was invalid.
+    */
+    public final void delRow(int row) {
+        allValues.removeElementAt(row);
+    }
+
+    /**
+    * Returns the number of rows in this <code>Sequence</code>.
+    * @return the number of rows currently in this <code>Sequence</code>.
+    */
+    public int getRowCount() {
+        return allValues.size();
+    }
+
+    /**
+    * Returns the named variable.  <b>Note:</b> In <code>DSequence</code>,
+    * this method returns the template variable, which holds no data.  If you
+    * need to get a variable containing data, use <code>getRow</code> or the
+    * <code>getVariable</code> method which takes a row number parameter.
+    *
+    * @param name the name of the variable.
+    * @return the named variable.
+    * @exception NoSuchVariableException if the named variable does not
+    *     exist in this container.
+    * @see DSequence#getVariable(int, String)
+    */
+    public BaseType getVariable(String name) throws NoSuchVariableException {
+        
+	int dotIndex = name.indexOf('.');
+	
+        if (dotIndex != -1) {  // name contains "."
+            String aggregate = name.substring(0, dotIndex);
+            String field = name.substring(dotIndex+1);
+
+            BaseType aggRef = getVariable(aggregate);
+            if (aggRef instanceof DConstructor)
+                return ((DConstructor)aggRef).getVariable(field);  // recurse
+            else
+                ; // fall through to throw statement
+        } 
+	else {
+            for (Enumeration e = varTemplate.elements(); e.hasMoreElements();) {
+                BaseType v = (BaseType)e.nextElement();
+                if (v.getName().equals(name))
+                  return v;
+            }
+        }
+        throw new NoSuchVariableException("DSequence: getVariable()");
+    }
+
+    /**
+    * Gets the indexed variable. For a DSrquence this returns the <code>BaseType</code>  
+    * from the <code>index</code>th column from the internal map <code>Vector</code>. 
+    * @param index the index of the variable in the <code>Vector</code> Vars.
+    * @return the indexed variable.
+    * @exception NoSuchVariableException if the named variable does not
+    *     exist in this container.
+    */
+    public BaseType getVar(int index) 
+                                    throws NoSuchVariableException {
+				    
+		if(index < varTemplate.size())
+		    return((BaseType)varTemplate.elementAt(index));
+		else
+			throw new NoSuchVariableException("DSequence.getVariable(" + index + " - 1)");
+   
+	}
+
+
+    /**
+    * Returns the named variable in the given row of the sequence.
+    *
+    * @param row the row number to retrieve.
+    * @param name the name of the variable.
+    * @return the named variable.
+    * @exception NoSuchVariableException if the named variable does not
+    *     exist in this container.
+    */
+    public BaseType getVariable(int row, String name) throws NoSuchVariableException {
+    
+        int dotIndex = name.indexOf('.');
+	
+        if (dotIndex != -1) {  // name contains "."
+            String aggregate = name.substring(0, dotIndex);
+            String field = name.substring(dotIndex+1);
+
+            BaseType aggRef = getVariable(aggregate);
+            if (aggRef instanceof DConstructor)
+                return ((DConstructor)aggRef).getVariable(field);  // recurse
+            else
+                ; // fall through to throw statement
+        } 
+	    else {
+            Vector selectedRow = (Vector)allValues.elementAt(row);
+            for (Enumeration e = selectedRow.elements() ; e.hasMoreElements() ;) {
+                BaseType v = (BaseType)e.nextElement();
+                if (v.getName().equals(name))
+                    return v;
+            }
+        }
+        throw new NoSuchVariableException("DSequence: getVariable()");
+    }
+
+    /** Return an Enumeration that can be used to iterate over the members of
+     * a Sequence. This implementation provides access to the template
+     * elements of the Sequence, not the entire sequence. Each Object
+     * returned by the Enumeration can be cast to a BaseType.
+     @return An Enumeration */
+    public Enumeration getVariables() {
+	return varTemplate.elements();
+    }
+
+  /**
+   * Checks for internal consistency.  For <code>DSequence</code>, verify that
+   * the variables have unique names.
+   *
+   * @param all for complex constructor types, this flag indicates whether to
+   *    check the semantics of the member variables, too.
+   * @exception BadSemanticsException if semantics are bad, explains why.
+   * @see BaseType#checkSemantics(boolean)
+   */
+  public void checkSemantics(boolean all)
+       throws BadSemanticsException {
+    super.checkSemantics(all);
+
+    Util.uniqueNames(varTemplate, getName(), getTypeName());
+
+    if (all) {
+      for(Enumeration e = varTemplate.elements(); e.hasMoreElements(); ) {
+	BaseType bt = (BaseType)e.nextElement();
+	bt.checkSemantics(true);
+      }
+    }
+  }
+
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean)
+    */
+    public void printDecl(PrintWriter os, String space,
+	                  boolean print_semi, boolean constrained) {
+			  				
+        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to one signature, we must be careful to override that
+        // SAME signature here. That way all calls to printDecl() for 
+        // this object lead to this implementation.
+	
+        os.println(space + getTypeName() + " {");
+        for(Enumeration e = varTemplate.elements(); e.hasMoreElements(); ) {
+            BaseType bt = (BaseType)e.nextElement();
+	    //os.println("Printing declaration for \""+bt.getName()+"\"   constrained: "+constrained);
+            bt.printDecl(os, space + "    ", true, constrained);
+        }
+        os.print(space + "} " + getName());
+        if (print_semi)
+            os.println(";");
+    }
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.print(" = ");
+    }
+
+    os.print("{ ");
+    for(Enumeration e1 = allValues.elements(); e1.hasMoreElements(); ) {
+      // get next instance vector
+      os.print("{ ");
+      Vector v = (Vector)e1.nextElement();
+      for(Enumeration e2 = v.elements(); e2.hasMoreElements(); ) {
+	// get next instance variable
+	BaseType bt = (BaseType)e2.nextElement();
+	bt.printVal(os, "", false);
+	if (e2.hasMoreElements())
+	  os.print(", ");
+      }
+      os.print(" }");
+      if(e1.hasMoreElements())
+	os.print(", ");
+    }
+    os.print(" }");
+
+    if(print_decl_p)
+      os.println(";");
+  }
+
+    /**
+    * Reads data from a <code>DataInputStream</code>. This method is only used
+    * on the client side of the DODS client/server connection.
+    *
+    * @param source a <code>DataInputStream</code> to read from.
+    * @param sv the <code>ServerVersion</code> returned by the server.
+    * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+    *    and user cancellation notification (may be null).
+    * @exception EOFException if EOF is found before the variable is completely
+    *     deserialized.
+    * @exception IOException thrown on any other InputStream exception.
+    * @exception DataReadException if an unexpected value was read.
+    * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+    */
+    public synchronized void deserialize(DataInputStream source,
+	                                           ServerVersion sv,
+	                                           StatusUI statusUI)
+                                throws IOException, EOFException, DataReadException {
+
+        // check for old servers
+        if (sv.getMajor() < 2 || (sv.getMajor() == 2 && sv.getMinor() < 15)) {
+            oldDeserialize(source, sv, statusUI);
+        } 
+	else {
+// ************* Pulled out the getLevel() check in order to support the "new" 
+// and "improved" serialization of dods sequences. 8/31/01 ndp
+//            // top level of sequence handles start and end markers
+//            if (getLevel() == 0) {
+                // loop until end of sequence
+                for(;;) {
+		
+                    byte marker = readMarker(source);
+		    
+                    if(statusUI != null)
+                        statusUI.incrementByteCount(4);
+			
+                    if (marker == START_OF_INSTANCE)
+                        deserializeSingle(source, sv, statusUI);
+                    else if (marker == END_OF_SEQUENCE)
+                        break;
+                    else
+                        throw new DataReadException("Sequence start marker not found");
+                }
+// ************* Pulled out the getLevel() check in order to support the "new" 
+// and "improved" serialization of dods sequences. 8/31/01 ndp
+//            } 
+//	    else {
+//                // lower levels only deserialize a single instance at a time
+//                deserializeSingle(source, sv, statusUI);
+//            }
+        }
+    }
+
+    /**
+    * The old deserialize protocol has a number of limitations stemming from
+    * its inability to tell when the sequence is finished.  It's really only
+    * good for a Dataset containing a single sequence, or where the sequence is
+    * the last thing in the dataset.  To handle this, we just read single
+    * instances until we get an IOException, then stop.
+    *
+    * @param source a <code>DataInputStream</code> to read from.
+    * @param sv the <code>ServerVersion</code> returned by the server.
+    * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+    *    and user cancellation notification (may be null).
+    * @exception IOException thrown on any InputStream exception other than EOF
+    *    (which is trapped here).
+    * @exception DataReadException if an unexpected value was read.
+    */
+    private void oldDeserialize(DataInputStream source, ServerVersion sv,
+		                                          StatusUI statusUI)
+                                        throws IOException, DataReadException {
+        try {
+            for(;;) {
+                deserializeSingle(source, sv, statusUI);
+            }
+        }
+        catch (EOFException e) {}
+    }
+
+    /**
+    * Deserialize a single row of the <code>DSequence</code>.
+    *
+    * @param source a <code>DataInputStream</code> to read from.
+    * @param sv the <code>ServerVersion</code> returned by the server.
+    * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+    *    and user cancellation notification (may be null).
+    * @exception EOFException if EOF is found before the variable is completely
+    *     deserialized.
+    * @exception IOException thrown on any other InputStream exception.
+    * @exception DataReadException if an unexpected value was read.
+    */
+    private void deserializeSingle(DataInputStream source, ServerVersion sv,
+	                                                     StatusUI statusUI)
+                                throws IOException, EOFException, DataReadException {
+        // create a new instance from the variable template Vector
+        Vector newInstance = new Vector();
+        for(int i=0; i<varTemplate.size(); i++) {
+            BaseType bt = (BaseType)varTemplate.elementAt(i);
+            newInstance.addElement(bt.clone());
+        }
+        // deserialize the new instance
+        for (Enumeration e = newInstance.elements(); e.hasMoreElements(); ) {
+            if(statusUI != null && statusUI.userCancelled())
+                throw new DataReadException("User cancelled");
+            ClientIO bt = (ClientIO)e.nextElement();
+            bt.deserialize(source, sv, statusUI);
+        }
+        // add the new instance to the allValues vector
+        allValues.addElement(newInstance);
+    }
+
+  /** Reads a marker byte from the input stream. */
+  private byte readMarker(DataInputStream source) throws IOException {
+    byte marker = source.readByte();
+    // pad out to a multiple of four bytes
+    byte unused;
+    for (int i=0; i<3; i++)
+      unused = source.readByte();
+
+    return marker;
+  }
+
+  /** Writes a marker byte to the output stream. */
+  protected void writeMarker(DataOutputStream sink, byte marker) throws IOException {
+    //for(int i=0; i<4; i++)
+     sink.writeByte(marker);
+     sink.writeByte((byte)0);
+     sink.writeByte((byte)0);
+     sink.writeByte((byte)0);
+  }
+
+    /**
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * primarily by GUI clients which need to download DODS data, manipulate 
+    * it, and then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code>
+    * exception. 
+    */
+    public void externalize(DataOutputStream sink) throws IOException {
+    
+        // loop until end of sequence
+        for(int i=0; i<allValues.size(); i++) {
+	
+// ************* Pulled out the getLevel() check in order to support the "new" 
+// and "improved" serialization of dods sequences. 8/31/01 ndp
+//            if (getLevel() == 0)
+                writeMarker(sink, START_OF_INSTANCE);
+		
+            Vector rowVec = (Vector)allValues.elementAt(i);
+	    
+            for(int j=0; j<rowVec.size(); j++) {
+                ClientIO bt = (ClientIO)rowVec.elementAt(j);
+                bt.externalize(sink);
+            }
+	    
+        }
+// ************* Pulled out the getLevel() check in order to support the "new" 
+// and "improved" serialization of dods sequences. 8/31/01 ndp
+//        if (getLevel() == 0)
+            writeMarker(sink, END_OF_SEQUENCE);
+    }
+}
diff --git a/dods/dap/DString.java b/dods/dap/DString.java
new file mode 100644
index 0000000..57d78a8
--- /dev/null
+++ b/dods/dap/DString.java
@@ -0,0 +1,174 @@
+////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>String</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ */
+public class DString extends BaseType implements ClientIO {
+  /** Constructs a new <code>DString</code>. */
+  public DString() { 
+    super();
+    val = "String value has not been set.";
+  }
+
+  /**
+   * Constructs a new <code>DString</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DString(String n) { 
+    super(n); 
+    val = "String value has not been set.";
+  }
+
+  /** The value of this <code>DString</code>. */
+  private String val;
+
+  /**
+   * Get the current value as a <code>String</code>.
+   * @return the current value.
+   */
+  public final String getValue() {
+    return val;
+  }
+
+  /**
+   * Set the current value.
+   * @param newVal the new value.
+   */
+  public final void setValue(String newVal) {
+    val = newVal;
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "String";
+  }
+
+
+
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = \"" + Util.escattr(val) + "\";");
+    } else
+      os.print("\"" + Util.escattr(val) + "\"");
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv the <code>ServerVersion</code> returned by the server.
+   * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @exception DataReadException if a negative string length was read.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+				       ServerVersion sv,
+				       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException {
+    int len = source.readInt();
+    if (len < 0)
+      throw new DataReadException("Negative string length read.");
+    int modFour = len%4;
+    // number of bytes to pad
+    int pad = (modFour != 0) ? (4-modFour) : 0;
+
+    byte byteArray[] = new byte[len];
+    
+    // With blackdown JDK1.1.8v3 (comes with matlab 6) read() didn't always
+    // finish reading a string.  readFully() insures that it gets all <len>
+    // characters it requested.  rph 08/20/01.
+    
+    //source.read(byteArray, 0, len);
+    source.readFully(byteArray, 0, len);
+
+    // pad out to a multiple of four bytes
+    byte unused;
+    for(int i=0; i<pad; i++)
+      unused = source.readByte();
+
+    if(statusUI != null)
+      statusUI.incrementByteCount(4 + len + pad);
+
+    // convert bytes to a new String using ISO8859_1 (Latin 1) encoding.
+    // This was chosen because it converts each byte to its Unicode value
+    // with no translation (the first 256 glyphs in Unicode are ISO8859_1)
+    try {
+      val = new String(byteArray, 0, len, "ISO8859_1");
+    }
+    catch (UnsupportedEncodingException e) {
+      // this should never happen
+      System.err.println("ISO8859_1 encoding not supported by this VM!");
+      System.exit(1);
+    }
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+    public void externalize(DataOutputStream sink) throws IOException {
+	// convert String to a byte array using ISO8859_1 (Latin 1) encoding.
+	// This was chosen because it converts each byte to its Unicode value
+	// with no translation (the first 256 glyphs in Unicode are ISO8859_1)
+
+	try {
+	    byte byteArray[] = val.getBytes("ISO8859_1");
+	    sink.writeInt(byteArray.length);
+	    int modFour = byteArray.length%4;
+	    // number of bytes to pad
+	    int pad = (modFour != 0) ? (4-modFour) : 0;
+	    sink.write(byteArray, 0, byteArray.length);
+	    for(int i=0; i<pad; i++) {
+		sink.writeByte(pad);
+	    }
+	}
+	catch (UnsupportedEncodingException e) {
+	    // this should never happen
+	    System.err.println("ISO8859_1 encoding not supported by this VM!");
+	    System.exit(1);
+	}
+    }
+}
diff --git a/dods/dap/DStructure.java b/dods/dap/DStructure.java
new file mode 100644
index 0000000..0c3e2d6
--- /dev/null
+++ b/dods/dap/DStructure.java
@@ -0,0 +1,296 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * A <code>DStructure</code> in DODS can hold <em>N</em> instances of any of
+ * the other datatypes, including other structures.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see DConstructor
+ */
+public class DStructure extends DConstructor implements ClientIO {
+  /**
+   * The variables in this <code>DStructure</code>, stored in a
+   * <code>Vector</code> of <code>BaseType</code> objects.
+   */
+  protected Vector vars;
+
+  /** Constructs a new <code>DStructure</code>. */
+  public DStructure() {
+    this(null);
+  }
+
+  /**
+   * Constructs a new <code>DStructure</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DStructure(String n) {
+    super(n);
+    vars = new Vector();
+  }
+
+  /**
+   * Returns a clone of this <code>DSequence</code>.  A deep copy is performed
+   * on all data inside the variable.
+   *
+   * @return a clone of this <code>DSequence</code>.
+   */
+  public Object clone() {
+    DStructure s = (DStructure)super.clone();
+    s.vars = new Vector();
+    for(int i=0; i<vars.size(); i++) {
+      BaseType bt = (BaseType)vars.elementAt(i);
+      s.vars.addElement(bt.clone());
+    }
+    return s;
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Structure";
+  }
+
+  /**
+   * Returns the number of variables contained in this object. For simple and
+   * vector type variables, it always returns 1. To count the number
+   * of simple-type variable in the variable tree rooted at this variable, set
+   * <code>leaves</code> to <code>true</code>.
+   *
+   * @param leaves If true, count all the simple types in the `tree' of
+   *    variables rooted at this variable.
+   * @return the number of contained variables.
+   */
+  public int elementCount(boolean leaves) {
+    if (!leaves)
+      return vars.size();
+    else {
+      int count = 0;
+      for (Enumeration e = vars.elements() ; e.hasMoreElements() ;) {
+        BaseType bt = (BaseType)e.nextElement();
+        count += bt.elementCount(leaves);
+      }
+      return count;
+    }
+  }
+
+  /**
+   * Adds a variable to the container.
+   * @param v the variable to add.
+   * @param part ignored for <code>DSequence</code>.
+   */
+  public void addVariable(BaseType v, int part) {
+    v.setParent(this);
+    vars.addElement(v);
+  }
+
+    /**
+     * Returns the named variable.
+     * @param name the name of the variable.
+     * @return the named variable.
+     * @exception NoSuchVariableException if the named variable does not
+     *     exist in this container.
+     */
+    public BaseType getVariable(String name) throws NoSuchVariableException {
+	int dotIndex = name.indexOf('.');
+	if (dotIndex != -1) {  // name contains "."
+	    String aggregate = name.substring(0, dotIndex);
+	    String field = name.substring(dotIndex+1);
+
+	    BaseType aggRef = getVariable(aggregate);
+	    if (aggRef instanceof DConstructor)
+		return ((DConstructor)aggRef).getVariable(field);  // recurse
+	    else
+		; // fall through to throw statement
+	} else {
+	    for (Enumeration e = vars.elements() ; e.hasMoreElements() ;) {
+		BaseType v = (BaseType)e.nextElement();
+		if (v.getName().equals(name))
+		    return v;
+	    }
+	}
+	throw new NoSuchVariableException("DStructure: getVariable()");
+    }
+
+
+    /**
+     * Gets the indexed variable. For a DStructure this returns the
+     * <code>BaseType</code> from the <code>index</code>th column from the
+     * internal storage <code>Vector</code>.
+     * @param index the index of the variable in the <code>Vector</code> Vars.
+     * @return the indexed variable.
+     * @exception NoSuchVariableException if the named variable does not
+     * exist in this container. */
+    public BaseType getVar(int index) 
+	throws NoSuchVariableException {
+				    
+	if(index < vars.size())
+	    return((BaseType)vars.elementAt(index));
+	else
+	    throw new NoSuchVariableException(
+               "DStructure.getVariable(" + index + " - 1)");
+    }
+
+    /** Return an Enumeration that can be used to iterate over the members of
+     * a Structure. This implementation provides access to the elements of
+     * the Structure. Each Object returned by the Enumeration can be cast to
+     * a BaseType. 
+     @return An Enumeration */
+    public Enumeration getVariables() {
+	return vars.elements();
+    }
+
+    /**
+     * Checks for internal consistency. For <code>DStructure</code>, verify
+     * that the variables have unique names.
+     *
+     * @param all for complex constructor types, this flag indicates whether to
+     *    check the semantics of the member variables, too.
+     * @exception BadSemanticsException if semantics are bad, explains why.
+     * @see BaseType#checkSemantics(boolean) */
+    public void checkSemantics(boolean all)
+	throws BadSemanticsException {
+	super.checkSemantics(all);
+
+	Util.uniqueNames(vars, getName(), getTypeName());
+
+	if (all) {
+	    for(Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+		BaseType bt = (BaseType)e.nextElement();
+		bt.checkSemantics(true);
+	    }
+	}
+    }
+    
+    
+    
+
+
+
+  /**
+   * Write the variable's declaration in a C-style syntax. This
+   * function is used to create textual representation of the Data
+   * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+   * information about this structure.
+   *
+   * @param os The <code>PrintWriter</code> on which to print the
+   *    declaration.
+   * @param space Each line of the declaration will begin with the
+   *    characters in this string.  Usually used for leading spaces.
+   * @param print_semi a boolean value indicating whether to print a
+   *    semicolon at the end of the declaration.
+   *
+   * @see BaseType#printDecl(PrintWriter, String, boolean)
+   */
+    public void printDecl(PrintWriter os, String space,
+                          boolean print_semi, boolean constrained) {
+			  
+	// BEWARE! Since printDecl()is (multiple) overloaded in BaseType and
+	// all of the different signatures of printDecl() in BaseType lead to
+	// one signature, we must be careful to override that SAME signature
+	// here. That way all calls to printDecl() for this object lead to
+	// this implementation.
+	
+	
+        os.println(space + getTypeName() + " {");
+        for(Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+            BaseType bt = (BaseType)e.nextElement();
+            bt.printDecl(os, space + "    ", true, constrained);
+        }
+        os.print(space + "} " + getName());
+
+        if (print_semi)
+            os.println(";");
+
+    }
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.print(" = ");
+    }
+
+    os.print("{ ");
+    for(Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+      BaseType bt = (BaseType)e.nextElement();
+      bt.printVal(os, "", false);
+      if(e.hasMoreElements())
+	os.print(", ");
+    }
+    os.print(" }");
+
+    if (print_decl_p)
+      os.println(";");
+  }
+
+    /**
+    * Reads data from a <code>DataInputStream</code>. This method is only used
+    * on the client side of the DODS client/server connection.
+    *
+    * @param source a <code>DataInputStream</code> to read from.
+    * @param sv the <code>ServerVersion</code> returned by the server.
+    * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+    *    and user cancellation notification (may be null).
+    * @exception EOFException if EOF is found before the variable is completely
+    *     deserialized.
+    * @exception IOException thrown on any other InputStream exception.
+    * @exception DataReadException if an unexpected value was read.
+    * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+    */
+    public synchronized void deserialize(DataInputStream source,
+                                         ServerVersion sv,
+                                         StatusUI statusUI)
+                                         throws IOException, EOFException, DataReadException {
+        for (Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+            if (statusUI != null && statusUI.userCancelled())
+                throw new DataReadException("User cancelled");
+            ClientIO bt = (ClientIO)e.nextElement();
+            bt.deserialize(source, sv, statusUI);
+        }
+    }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate 
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception. 
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    for (Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+      ClientIO bt = (ClientIO)e.nextElement();
+      bt.externalize(sink);
+    }
+  }
+}
diff --git a/dods/dap/DUInt16.java b/dods/dap/DUInt16.java
new file mode 100644
index 0000000..3c1d6ff
--- /dev/null
+++ b/dods/dap/DUInt16.java
@@ -0,0 +1,93 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>UInt16</code> value.
+ *
+ * @version $Revision: 1.2 $
+ * @author ndp
+ * @see BaseType
+ */
+public class DUInt16 extends DInt16 {
+  /** Constructs a new <code>DUInt16</code>. */
+  public DUInt16() { super(); }
+
+  /**
+   * Constructs a new <code>DUInt16</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DUInt16(String n) { super(n); }
+
+  /**
+   * Constructs a new <code>UInt16PrimitiveVector</code>.
+   * @return a new <code>UInt16PrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new UInt16PrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "UInt16";
+  }
+
+
+
+
+
+   /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    // to print properly, cast to long and convert unsigned to signed
+    long tempVal = ((long)getValue()) & 0xFFFFL;
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + tempVal + ";");
+    } else
+      os.print(tempVal);
+  }
+}
diff --git a/dods/dap/DUInt32.java b/dods/dap/DUInt32.java
new file mode 100644
index 0000000..a8a015c
--- /dev/null
+++ b/dods/dap/DUInt32.java
@@ -0,0 +1,71 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * Holds a DODS <code>UInt32</code> value.
+ *
+ * @version $Revision: 1.2 $
+ * @author jehamby
+ * @see BaseType
+ */
+public class DUInt32 extends DInt32 {
+  /** Constructs a new <code>DUInt32</code>. */
+  public DUInt32() { super(); }
+
+  /**
+   * Constructs a new <code>DUInt32</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DUInt32(String n) { super(n); }
+
+  /**
+   * Constructs a new <code>UInt32PrimitiveVector</code>.
+   * @return a new <code>UInt32PrimitiveVector</code>.
+   */
+  public PrimitiveVector newPrimitiveVector() {
+    return new UInt32PrimitiveVector(this);
+  }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "UInt32";
+  }
+
+
+
+  /**
+   * Prints the value of the variable, with its declaration.  This
+   * function is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @param print_decl_p a boolean value controlling whether the
+   *    variable declaration is printed as well as the value.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    // to print properly, cast to long and convert unsigned to signed
+    long tempVal = ((long)getValue()) & 0xFFFFFFFFL;
+    if (print_decl_p) {
+      printDecl(os, space, false);
+      os.println(" = " + tempVal + ";");
+    } else
+      os.print(tempVal);
+  }
+}
diff --git a/dods/dap/DURL.java b/dods/dap/DURL.java
new file mode 100644
index 0000000..7ffd492
--- /dev/null
+++ b/dods/dap/DURL.java
@@ -0,0 +1,40 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+/**
+ * Holds a DODS <code>URL</code> value.
+ *
+ * @version $Revision: 1.1.1.1 $
+ * @author jehamby
+ * @see BaseType
+ */
+public class DURL extends DString {
+  /** Constructs a new <code>DURL</code>. */
+  public DURL() { super(); }
+
+  /**
+   * Constructs a new <code>DURL</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public DURL(String n) { super(n); }
+
+  /**
+   * Returns the DODS type name of the class instance as a <code>String</code>.
+   * @return the DODS type name of the class instance as a <code>String</code>.
+   */
+  public String getTypeName() {
+    return "Url";
+  }
+}
diff --git a/dods/dap/DVector.java b/dods/dap/DVector.java
new file mode 100644
index 0000000..0768228
--- /dev/null
+++ b/dods/dap/DVector.java
@@ -0,0 +1,253 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * This class holds a one-dimensional array of DODS data types.
+ * It is the parent of both <code>DList</code> and <code>DArray</code>.
+ * This class uses a <code>PrimitiveVector</code> to hold the data and
+ * deserialize it, thus allowing more efficient storage to be used for the
+ * primitive types.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see DArray
+ * @see DList
+ * @see PrimitiveVector
+ */
+abstract public class DVector extends BaseType implements ClientIO {
+    /**
+    * The values in this <code>DVector</code>, stored in a
+    * <code>PrimitiveVector</code>.
+    */
+    private PrimitiveVector vals;
+
+    /** Constructs a new <code>DVector</code>. */
+    public DVector() {
+        super();
+        }
+
+    /**
+    * Returns the DODS type name of the class instance as a <code>String</code>.
+    * @return the DODS type name of the class instance as a <code>String</code>.
+    */
+    public String getTypeName() {
+        return "Vector";
+    }
+    /**
+    * Constructs a new <code>DVector</code> with name <code>n</code>.
+    * @param n the name of the variable.
+    */
+    public DVector(String n) { super(n); }
+
+    /**
+    * Returns a clone of this <code>DVector</code>.  A deep copy is performed on
+    * all variables inside the <code>DVector</code>.
+    *
+    * @return a clone of this <code>DVector</code>.
+    */
+    public Object clone() {
+        DVector v = (DVector)super.clone();
+        v.vals = (PrimitiveVector)vals.clone();
+        return v;
+    }
+
+    /**
+    * Returns the number of elements in the vector.
+    * @return the number of elements in the vector.
+    */
+    public int getLength() {
+        if (vals == null)
+            return 0;
+        else
+            return vals.getLength();
+    }
+
+    /**
+    * Sets the number of elements in the vector.  Allocates a new
+    * array of the desired size.  Note that if this is called multiple times,
+    * the old array and its contents will be lost!
+    * <p>
+    * Only called inside of <code>deserialize</code> method or in derived
+    * classes on server.
+    *
+    * @param len the number of elements in the array.
+    */
+    public void setLength(int len) {
+        vals.setLength(len);
+    }
+
+    /**
+    * Adds a variable to the container.
+    * @param v the variable to add.
+    */
+    public void addVariable(BaseType v) {
+        vals = v.newPrimitiveVector();
+        setName(v.getName());
+    }
+
+    /**
+    * Returns the <code>PrimitiveVector</code> for this vector.  This can be
+    * cast to the appropriate type and used by a DODS client to read or set
+    * individual values in the vector.
+    *
+    * @return the attached <code>PrimitiveVector</code>.
+    */
+    public PrimitiveVector getPrimitiveVector() {
+        return vals;
+    }
+
+
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean)
+    */
+    public void printDecl(PrintWriter os, String space,
+                                     boolean print_semi, boolean constrained) {
+
+        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to one signature, we must be careful to override that
+        // SAME signature here. That way all calls to printDecl() for
+        // this object lead to this implementation.
+
+
+        //os.println("DVector.printDecl()");
+        os.print(space + getTypeName());
+        vals.printDecl(os, " ", print_semi,constrained);
+    }
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+
+        if (print_decl_p) {
+            printDecl(os, space, false);
+            os.print(" = ");
+        }
+
+        os.print("{ ");
+        vals.printVal(os, "");
+
+        if (print_decl_p)
+            os.println("};");
+        else
+            os.print("}");
+    }
+
+    /**
+    * Reads data from a <code>DataInputStream</code>. This method is only used
+    * on the client side of the DODS client/server connection.
+    *
+    * @param source a <code>DataInputStream</code> to read from.
+    * @param sv the <code>ServerVersion</code> returned by the server.
+    * @param statusUI the <code>StatusUI</code> object to use for GUI updates
+    *    and user cancellation notification (may be null).
+    * @exception EOFException if EOF is found before the variable is completely
+    *     deserialized.
+    * @exception IOException thrown on any other InputStream exception.
+    * @exception DataReadException if an unexpected value was read.
+    * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+    */
+    public synchronized void deserialize(DataInputStream source,
+                                         ServerVersion sv,
+                                         StatusUI statusUI)
+                                         throws IOException,
+                                         EOFException,
+                                         DataReadException {
+
+        // Because arrays of primitive types (ie int32, float32, byte, etc) are
+        // handled in the C++ core using the XDR package we must read the
+        // length twice for those types. For BaseType vectors, we should read
+        // it only once. This is in effect a work around for a bug in the C++
+        // core as the C++ core does not consume 2 length values for the
+        // BaseType vectors. Bummer...
+
+        int length = source.readInt();
+
+        if(!(vals instanceof BaseTypePrimitiveVector)){
+            // because both XDR and DODS write the length, we must read it twice
+            int length2 = source.readInt();
+            //System.out.println("array1 length read: "+getName()+" "+length+ " -- "+length2);
+            //System.out.println("  array type = : "+vals.getClass().getName());
+
+            // QC the second length
+            if(length != length2) {
+              throw new DataReadException("Inconsistent array length read: "+length+ " != "+length2);
+            }
+        } /* else {
+          System.out.println("array2 length read: "+getName()+" "+length);
+          System.out.println("  array type = : "+vals.getClass().getName());
+        } */
+
+        if(length < 0)
+            throw new DataReadException("Negative array length read.");
+        if(statusUI != null)
+            statusUI.incrementByteCount(8);
+        vals.setLength(length);
+        vals.deserialize(source, sv, statusUI);
+    }
+
+    /**
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * primarily by GUI clients which need to download DODS data, manipulate
+    * it, and then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code>
+    * exception.
+    */
+    public void externalize(DataOutputStream sink) throws IOException {
+
+        // Because arrays of primitive types (ie int32, float32, byte, etc) are
+        // handled in the C++ core using the XDR package we must write the
+        // length twice for those types. For BaseType vectors, we should write
+        // it only once. This is in effect a work around for a bug in the C++
+        // core as the C++ core does not consume 2 length values for thge
+        // BaseType vectors. Bummer...
+        int length = vals.getLength();
+        sink.writeInt(length);
+
+        if(!(vals instanceof BaseTypePrimitiveVector)){
+            // because both XDR and DODS write the length, we must write it twice
+            sink.writeInt(length);
+        }
+
+        vals.externalize(sink);
+    }
+
+}
diff --git a/dods/dap/DataDDS.java b/dods/dap/DataDDS.java
new file mode 100644
index 0000000..f97094b
--- /dev/null
+++ b/dods/dap/DataDDS.java
@@ -0,0 +1,159 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+import java.util.Enumeration;
+import java.util.zip.DeflaterOutputStream;
+
+/**
+ * The DataDDS class extends DDS to add new methods for retrieving data from
+ * the server, and printing out the contents of the data.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see DDS
+ */
+public class DataDDS extends DDS {
+  /** The ServerVersion returned from the open DODS connection. */
+  private ServerVersion ver;
+
+  /**
+   * Construct the DataDDS with the given server version.
+   * @param ver the ServerVersion returned from the open DODS connection.
+   */
+  public DataDDS(ServerVersion ver) {
+    super();
+    this.ver = ver;
+  }
+  
+  public DataDDS(ServerVersion ver, BaseTypeFactory btf) {
+    super(btf);
+    this.ver = ver;
+  }
+
+  /**
+   * Returns the <code>ServerVersion</code> given in the constructor.
+   * @return the <code>ServerVersion</code> given in the constructor.
+   */
+  public final ServerVersion getServerVersion() {
+    return ver;
+  }
+
+  /**
+   * Read the data stream from the given InputStream.  In the C++ version,
+   * this code was in Connect.
+   *
+   * @param is the InputStream to read from
+   * @param statusUI the StatusUI object to use, or null
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception DODSException if the DODS server returned an error.
+   */
+  public void readData(InputStream is, StatusUI statusUI)
+       throws IOException, EOFException, DODSException {
+    // Buffer the input stream for better performance
+    BufferedInputStream bufferedIS = new BufferedInputStream(is);
+    // Use a DataInputStream for deserialize
+    DataInputStream dataIS = new DataInputStream(bufferedIS);
+
+    for(Enumeration e = getVariables(); e.hasMoreElements(); ) {
+      if (statusUI != null && statusUI.userCancelled())
+	throw new DataReadException("User cancelled");
+      ClientIO bt = (ClientIO)e.nextElement();
+      bt.deserialize(dataIS, ver, statusUI);
+    }
+    // notify GUI of finished download
+    if (statusUI != null)
+      statusUI.finished();
+  }
+
+  /**
+   * Print the dataset just read.  In the C++ version, this code was in
+   * <code>geturl</code>.
+   *
+   * @param os the <code>PrintWriter</code> to use.
+   */
+  public void printVal(PrintWriter os) {
+    for (Enumeration e = getVariables(); e.hasMoreElements(); ) {
+      BaseType bt = (BaseType)e.nextElement();
+      bt.printVal(os, "", true);
+    }
+    os.println();
+  }
+
+  /**
+   * Print the dataset using OutputStream.
+   * @param os the <code>OutputStream</code> to use.
+   */
+  public final void printVal(OutputStream os) {
+    PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+    printVal(pw);
+    pw.flush();
+  }
+
+  /**
+   * Dump the dataset using externalize methods. This should create
+   * a multipart Mime document with the binary representation of the
+   * DDS that is currently in memory.
+   *
+   * @param os the <code>OutputStream</code> to use.
+   * @param compress <code>true</code> if we should compress the output.
+   * @param headers <code>true</code> if we should print HTTP headers.
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public final void externalize(OutputStream os, boolean compress, boolean headers)
+       throws IOException {
+    // First, print headers
+    if (headers) {
+      PrintWriter pw = new PrintWriter(new OutputStreamWriter(os));
+      pw.println("HTTP/1.0 200 OK");
+      pw.println("Server: " + ServerVersion.getCurrentVersion());
+      pw.println("Content-type: application/octet-stream");
+      pw.println("Content-Description: dods_data");
+      if (compress) {
+	pw.println("Content-Encoding: deflate");
+      }
+      pw.println();
+      pw.flush();
+    }
+
+    // Buffer the output stream for better performance
+    OutputStream bufferedOS;
+    if (compress) {
+      // deflate has its own buffering
+      bufferedOS = new DeflaterOutputStream(os);
+    } else {
+      bufferedOS = new BufferedOutputStream(os);
+    }
+
+    // Redefine PrintWriter here, so the DDS is also compressed if necessary
+    PrintWriter pw = new PrintWriter(new OutputStreamWriter(bufferedOS));
+    print(pw);
+    // pw.println("Data:");  // JCARON CHANGED
+    pw.flush();
+    bufferedOS.write("\nData:\n".getBytes()); // JCARON CHANGED
+    bufferedOS.flush();
+
+    // Use a DataOutputStream for serialize
+    DataOutputStream dataOS = new DataOutputStream(bufferedOS);
+    for(Enumeration e = getVariables(); e.hasMoreElements(); ) {
+      ClientIO bt = (ClientIO)e.nextElement();
+      bt.externalize(dataOS);
+    }
+    // Note: for DeflaterOutputStream, flush() is not sufficient to flush
+    // all buffered data
+    dataOS.close();
+  }
+}
diff --git a/dods/dap/DataReadException.java b/dods/dap/DataReadException.java
new file mode 100644
index 0000000..6a7b409
--- /dev/null
+++ b/dods/dap/DataReadException.java
@@ -0,0 +1,43 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * Thrown when DODS encounters an exception while reading from a data set.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+ */
+public class DataReadException extends DDSException {
+  /**
+   * Construct a <code>DataReadException</code> with the specified detail
+   * message.
+   *
+   * @param s the detail message.
+   */
+  public DataReadException(String s) {
+    super(DODSException.CANNOT_READ_FILE,s);
+  }
+
+
+  /**
+   * Construct a <code>DataReadException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public DataReadException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/DefaultFactory.java b/dods/dap/DefaultFactory.java
new file mode 100644
index 0000000..93c1626
--- /dev/null
+++ b/dods/dap/DefaultFactory.java
@@ -0,0 +1,301 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+//
+// -- 7/14/99 Modified by: Nathan Potter (ndp at oce.orst.edu)
+// Added Support For DInt16, DUInt16, DFloat32.
+// Added (and commented out) support for DBoolean.
+// -- 7/14/99 ndp 
+//  
+/////////////////////////////////////////////////////////////////////////////
+
+
+package dods.dap;
+
+/**
+ * The default client-side Factory for BaseType objects.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseTypeFactory
+ */
+
+public class DefaultFactory implements BaseTypeFactory {
+  //..................................
+  /** 
+   * Construct a new DBoolean.
+   * @return the new DBoolean
+   */
+//  public DBoolean newDBoolean() {
+//    return new DBoolean();
+//  }
+
+  /**
+   * Construct a new DBoolean with name n.
+   * @param n the variable name
+   * @return the new DBoolean
+   */
+//  public DBoolean newDBoolean(String n) {
+//    return new DBoolean(n);
+//  }
+
+  //..................................
+  /** 
+   * Construct a new DByte.
+   * @return the new DByte
+   */
+  public DByte newDByte() {
+    return new DByte();
+  }
+
+  /**
+   * Construct a new DByte with name n.
+   * @param n the variable name
+   * @return the new DByte
+   */
+  public DByte newDByte(String n) {
+    return new DByte(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DInt16.
+   * @return the new DInt16
+   */
+  public DInt16 newDInt16() {
+    return new DInt16();
+  }
+
+  /**
+   * Construct a new DInt16 with name n.
+   * @param n the variable name
+   * @return the new DInt16
+   */
+  public DInt16 newDInt16(String n) {
+    return new DInt16(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DUInt16.
+   * @return the new DUInt16
+   */
+  public DUInt16 newDUInt16() {
+    return new DUInt16();
+  }
+
+  /**
+   * Construct a new DUInt16 with name n.
+   * @param n the variable name
+   * @return the new DUInt16
+   */
+  public DUInt16 newDUInt16(String n) {
+    return new DUInt16(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DInt32.
+   * @return the new DInt32
+   */
+  public DInt32 newDInt32() {
+    return new DInt32();
+  }
+
+  /**
+   * Construct a new DInt32 with name n.
+   * @param n the variable name
+   * @return the new DInt32
+   */
+  public DInt32 newDInt32(String n) {
+    return new DInt32(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DUInt32.
+   * @return the new DUInt32
+   */
+  public DUInt32 newDUInt32() {
+    return new DUInt32();
+  }
+
+  /**
+   * Construct a new DUInt32 with name n.
+   * @param n the variable name
+   * @return the new DUInt32
+   */
+  public DUInt32 newDUInt32(String n) {
+    return new DUInt32(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DFloat32.
+   * @return the new DFloat32
+   */
+  public DFloat32 newDFloat32() {
+    return new DFloat32();
+  }
+
+  /**
+   * Construct a new DFloat32 with name n.
+   * @param n the variable name
+   * @return the new DFloat32
+   */
+  public DFloat32 newDFloat32(String n) {
+    return new DFloat32(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DFloat64.
+   * @return the new DFloat64
+   */
+  public DFloat64 newDFloat64() {
+    return new DFloat64();
+  }
+
+  /**
+   * Construct a new DFloat64 with name n.
+   * @param n the variable name
+   * @return the new DFloat64
+   */
+  public DFloat64 newDFloat64(String n) {
+    return new DFloat64(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DString.
+   * @return the new DString
+   */
+  public DString newDString() {
+    return new DString();
+  }
+
+  /**
+   * Construct a new DString with name n.
+   * @param n the variable name
+   * @return the new DString
+   */
+  public DString newDString(String n) {
+    return new DString(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DURL.
+   * @return the new DURL
+   */
+  public DURL newDURL() {
+    return new DURL();
+  }
+
+  /**
+   * Construct a new DURL with name n.
+   * @param n the variable name
+   * @return the new DURL
+   */
+  public DURL newDURL(String n) {
+    return new DURL(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DArray.
+   * @return the new DArray
+   */
+  public DArray newDArray() {
+    return new DArray();
+  }
+
+  /**
+   * Construct a new DArray with name n.
+   * @param n the variable name
+   * @return the new DArray
+   */
+  public DArray newDArray(String n) {
+    return new DArray(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DList.
+   * @return the new DList
+   */
+  public DList newDList() {
+    return new DList();
+  }
+
+  /**
+   * Construct a new DList with name n.
+   * @param n the variable name
+   * @return the new DList
+   */
+  public DList newDList(String n) {
+    return new DList(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DGrid.
+   * @return the new DGrid
+   */
+  public DGrid newDGrid() {
+    return new DGrid();
+  }
+
+  /**
+   * Construct a new DGrid with name n.
+   * @param n the variable name
+   * @return the new DGrid
+   */
+  public DGrid newDGrid(String n) {
+    return new DGrid(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DStructure.
+   * @return the new DStructure
+   */
+  public DStructure newDStructure() {
+    return new DStructure();
+  }
+
+  /**
+   * Construct a new DStructure with name n.
+   * @param n the variable name
+   * @return the new DStructure
+   */
+  public DStructure newDStructure(String n) {
+    return new DStructure(n);
+  }
+
+  //..................................
+  /** 
+   * Construct a new DSequence.
+   * @return the new DSequence
+   */
+  public DSequence newDSequence() {
+    return new DSequence();
+  }
+
+  /**
+   * Construct a new DSequence with name n.
+   * @param n the variable name
+   * @return the new DSequence
+   */
+  public DSequence newDSequence(String n) {
+    return new DSequence(n);
+  }
+
+}
diff --git a/dods/dap/Float32PrimitiveVector.java b/dods/dap/Float32PrimitiveVector.java
new file mode 100644
index 0000000..9123dea
--- /dev/null
+++ b/dods/dap/Float32PrimitiveVector.java
@@ -0,0 +1,257 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of doubles.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see PrimitiveVector
+ */
+public class Float32PrimitiveVector extends PrimitiveVector implements Cloneable {
+  /** the array of <code>float</code> values. */
+  private float vals[];
+
+  /**
+   * Constructs a new <code>Float32PrimitiveVector</code>.
+   * @param var the template <code>BaseType</code> to use.
+   */
+  public Float32PrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Returns a clone of this <code>Float32PrimitiveVector</code>.  A deep
+   * copy is performed on all data inside the variable.
+   *
+   * @return a clone of this <code>Float32PrimitiveVector</code>.
+   */
+  public Object clone() {
+    Float32PrimitiveVector v = (Float32PrimitiveVector)super.clone();
+    if (vals != null) {
+      v.vals = new float[vals.length];
+      System.arraycopy(vals, 0, v.vals, 0, vals.length);
+    }
+    return v;
+  }
+
+  /**
+   * Returns the number of elements in the array.
+   * @return the number of elements in the array.
+   */
+  public int getLength() {
+    return vals.length;
+  }
+
+  /**
+   * Sets the number of elements in the array.  Allocates a new primitive
+   * array of the desired size.  Note that if this is called multiple times,
+   * the old array and its contents will be lost.
+   * <p>
+   * Only called inside of <code>deserialize</code> method or in derived
+   * classes on server.
+   *
+   * @param len the number of elements in the array.
+   */
+  public void setLength(int len) {
+    vals = new float[len];
+  }
+
+  /**
+   * Return the i'th value as a <code>double</code>.
+   * @param i the index of the value to return.
+   * @return the i'th value.
+   */
+  public final float getValue(int i) {
+    return vals[i];
+  }
+
+  /**
+   * Set the i'th value of the array.
+   * @param i the index of the value to set.
+   * @param newVal the new value.
+   */
+  public final void setValue(int i, float newVal) {
+    vals[i] = newVal;
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = vals.length;
+    for(int i=0; i<len-1; i++) {
+      os.print(vals[i]);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(vals[len-1]);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+  public void printSingleVal(PrintWriter os, int index) {
+    os.print(vals[index]);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+                                       ServerVersion sv,
+                                       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException {
+    for(int i=0; i<vals.length; i++) {
+      vals[i] = source.readFloat();
+      if (statusUI != null) {
+        statusUI.incrementByteCount(8);
+        if (statusUI.userCancelled())
+          throw new DataReadException("User cancelled");
+      }
+    }
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception.
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    for(int i=0; i<vals.length; i++) {
+      sink.writeFloat(vals[i]);
+    }
+  }
+
+  /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
+    for (int i=start; i<=stop; i+=stride)
+      sink.writeFloat(vals[i]);
+  }
+
+
+    /**
+    * Returns (a reference to) the internal storage for this PrimitiveVector
+    * object.
+    * <h2>WARNING:</h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    *
+    * @return The internal array of floats.
+    */
+    public Object getInternalStorage() {
+        return(vals);
+    }
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    public void setInternalStorage(Object o) {
+      vals = (float []) o;
+    }
+
+  /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector, of type Float32PrimitiveVector.
+   */
+  public PrimitiveVector subset( int start, int stop, int stride) {
+    Float32PrimitiveVector n = new Float32PrimitiveVector(getTemplate());
+    stride = Math.max( stride, 1);
+    stop = Math.max( start, stop);
+    int length = 1 + (stop - start) / stride;
+    n.setLength( length);
+
+    int count=0;
+    for (int i=start; i<=stop; i+=stride) {
+      n.setValue(count, vals[i]);
+      count++;
+    }
+    return n;
+  }
+
+
+
+}
diff --git a/dods/dap/Float64PrimitiveVector.java b/dods/dap/Float64PrimitiveVector.java
new file mode 100644
index 0000000..b6b20bd
--- /dev/null
+++ b/dods/dap/Float64PrimitiveVector.java
@@ -0,0 +1,239 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of doubles.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see PrimitiveVector
+ */
+public class Float64PrimitiveVector extends PrimitiveVector implements Cloneable {
+  /** the array of <code>double</code> values. */
+  private double vals[];
+
+  /**
+   * Constructs a new <code>Float64PrimitiveVector</code>.
+   * @param var the template <code>BaseType</code> to use.
+   */
+  public Float64PrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Returns a clone of this <code>Float64PrimitiveVector</code>.  A deep
+   * copy is performed on all data inside the variable.
+   *
+   * @return a clone of this <code>Float64PrimitiveVector</code>.
+   */
+  public Object clone() {
+    Float64PrimitiveVector v = (Float64PrimitiveVector)super.clone();
+    if (vals != null) {
+      v.vals = new double[vals.length];
+      System.arraycopy(vals, 0, v.vals, 0, vals.length);
+    }
+    return v;
+  }
+
+  /**
+   * Returns the number of elements in the array.
+   * @return the number of elements in the array.
+   */
+  public int getLength() {
+    return vals.length;
+  }
+
+  /**
+   * Sets the number of elements in the array.  Allocates a new primitive
+   * array of the desired size.  Note that if this is called multiple times,
+   * the old array and its contents will be lost.
+   * <p>
+   * Only called inside of <code>deserialize</code> method or in derived
+   * classes on server.
+   *
+   * @param len the number of elements in the array.
+   */
+  public void setLength(int len) {
+    vals = new double[len];
+  }
+
+  /**
+   * Return the i'th value as a <code>double</code>.
+   * @param i the index of the value to return.
+   * @return the i'th value.
+   */
+  public final double getValue(int i) {
+    return vals[i];
+  }
+
+  /**
+   * Set the i'th value of the array.
+   * @param i the index of the value to set.
+   * @param newVal the new value.
+   */
+  public final void setValue(int i, double newVal) {
+    vals[i] = newVal;
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = vals.length;
+    for(int i=0; i<len-1; i++) {
+      os.print(vals[i]);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(vals[len-1]);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+  public void printSingleVal(PrintWriter os, int index) {
+    os.print(vals[index]);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+                                       ServerVersion sv,
+                                       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException {
+    for(int i=0; i<vals.length; i++) {
+      vals[i] = source.readDouble();
+      if (statusUI != null) {
+        statusUI.incrementByteCount(8);
+        if (statusUI.userCancelled())
+          throw new DataReadException("User cancelled");
+      }
+    }
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception.
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    for(int i=0; i<vals.length; i++) {
+      sink.writeDouble(vals[i]);
+    }
+  }
+
+  /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
+    for (int i=start; i<=stop; i+=stride)
+      sink.writeDouble(vals[i]);
+  }
+
+    /**
+    * Returns (a reference to) the internal storage for this PrimitiveVector
+    * object.
+    * <h2>WARNING:</h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    *
+    * @return The internal array of doubles.
+    */
+    public Object getInternalStorage() {
+        return(vals);
+    }
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    public void setInternalStorage(Object o) {
+      vals = (double []) o;
+    }
+
+  /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector, of type Float64PrimitiveVector.
+   */
+  public PrimitiveVector subset( int start, int stop, int stride) {
+    Float64PrimitiveVector n = new Float64PrimitiveVector(getTemplate());
+    stride = Math.max( stride, 1);
+    stop = Math.max( start, stop);
+    int length = 1 + (stop - start) / stride;
+    n.setLength( length);
+
+    int count=0;
+    for (int i=start; i<=stop; i+=stride) {
+      n.setValue(count, vals[i]);
+      count++;
+    }
+    return n;
+  }
+
+
+
+
+}
diff --git a/dods/dap/HeaderInputStream.java b/dods/dap/HeaderInputStream.java
new file mode 100644
index 0000000..cafa60a
--- /dev/null
+++ b/dods/dap/HeaderInputStream.java
@@ -0,0 +1,143 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * The HeaderInputStream filters the input to only read lines of text until
+ * the "Data:" line.  This is required because overzealous buffering in the
+ * DDSParser will read the data as well as the DDS otherwise.
+ *
+ * @version $Revision: 1.2 $
+ * @author jehamby
+ * @see DConnect
+ */
+class HeaderInputStream extends FilterInputStream {
+  /** Each line is buffered here. */
+  private byte lineBuf[];
+
+  /** Number of bytes remaining in buffer. */
+  private int bytesRemaining;
+
+  /** Current buffer offset. */
+  private int currentOffset;
+
+  /** End sequence to look for: "\nData:\n" */
+  private byte[] endSequence = {(byte)'\n', (byte)'D', (byte)'a', (byte)'t',
+				(byte)'a', (byte)':', (byte)'\n'};
+
+  /** Flag when end sequence has been found */
+  private boolean endFound;
+
+  /** Construct a new HeaderInputStream. */
+  public HeaderInputStream(InputStream in) {
+    super(in);
+    lineBuf = new byte[4096];
+    bytesRemaining = currentOffset = 0;
+    endFound = false;
+  }
+
+  /** Return the number of bytes in the buffer. */
+  public int available() {
+    return bytesRemaining;
+  }
+
+  /** Returns that we don't support the mark() and reset() methods. */
+  public boolean markSupported() {
+    return false;
+  }
+
+  /** Reads a single byte of data */
+  public int read() throws IOException {
+    // if the buffer is empty, get more bytes
+    if (bytesRemaining == 0 && !endFound)
+      getMoreBytes();
+    // if the buffer is still empty, return EOF
+    if (bytesRemaining == 0)
+      return -1;
+    else {
+      bytesRemaining--;
+      return lineBuf[currentOffset++];
+    }
+  }
+
+
+
+   /** Get more bytes into buffer.  Stop when endSequence is found. */
+   private void getMoreBytes() throws IOException {
+     currentOffset = 0;   // reset current array offset to 0
+     int bytesRead = 0;   // bytes read so far
+     int lookingFor = 0;  // character in endSequence to look for
+     for(; bytesRead < lineBuf.length; bytesRead++) {
+       int c = in.read();
+       if (c == -1)
+         break;  // break on EOL and return what we have so far
+
+       lineBuf[bytesRead] = (byte)c;
+       if (lineBuf[bytesRead] == endSequence[lookingFor]) {
+         lookingFor++;
+         if (lookingFor == endSequence.length) {
+           endFound = true;
+           break;
+         }
+       } else if (lineBuf[bytesRead] == endSequence[0]) { // CHANGED JC
+         lookingFor = 1;
+       } else {
+         lookingFor = 0;
+       }
+     }
+     bytesRemaining = bytesRead;  // number of bytes we've read
+   }
+
+
+
+  /**
+   * Reads up to len bytes of data from this input stream into an array of
+   * bytes. This method blocks until some input is available.
+   */
+  public int read(byte b[], int off, int len) throws IOException {
+    if (len <= 0) {
+      return 0;
+    }
+
+    int c = read();
+    if (c == -1)
+      return -1;
+    b[off] = (byte)c;
+    
+    // We've read one byte successfully, let's try for more
+    int i = 1;
+    try {
+      for (; i < len ; i++) {
+	c = read();
+	if (c == -1) {
+	  break;
+	}
+	b[off + i] = (byte)c;
+      }
+    } catch (IOException e) {
+    }
+    return i;
+  }
+
+  /** Skips over and discards n bytes of data from the input stream. */
+  public long skip(long n) {
+    if (bytesRemaining >= n) {
+      bytesRemaining -= n;
+      return n;
+    } else {
+      int oldBytesRemaining = bytesRemaining;
+      bytesRemaining = 0;
+      return oldBytesRemaining;
+    }
+  }
+}
diff --git a/dods/dap/Int16PrimitiveVector.java b/dods/dap/Int16PrimitiveVector.java
new file mode 100644
index 0000000..fd27249
--- /dev/null
+++ b/dods/dap/Int16PrimitiveVector.java
@@ -0,0 +1,259 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of shorts. (as in 16bit ints :)
+ *
+ * @version $Revision: 1.3 $
+ * @author npotter
+ * @see PrimitiveVector
+ */
+public class Int16PrimitiveVector extends PrimitiveVector implements Cloneable {
+  /** the array of <code>short</code> values. */
+  private short vals[];
+
+  /**
+   * Constructs a new <code>Int16PrimitiveVector</code>.
+   * @param var the template <code>BaseType</code> to use.
+   */
+  public Int16PrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Returns a clone of this <code>Int16PrimitiveVector</code>.  A deep
+   * copy is performed on all data inside the variable.
+   *
+   * @return a clone of this <code>Int16PrimitiveVector</code>.
+   */
+  public Object clone() {
+    Int16PrimitiveVector v = (Int16PrimitiveVector)super.clone();
+    if (vals != null) {
+      v.vals = new short[vals.length];
+      System.arraycopy(vals, 0, v.vals, 0, vals.length);
+    }
+    return v;
+  }
+
+  /**
+   * Returns the number of elements in the array.
+   * @return the number of elements in the array.
+   */
+  public int getLength() {
+    return vals.length;
+  }
+
+  /**
+   * Sets the number of elements in the array.  Allocates a new primitive
+   * array of the desired size.  Note that if this is called multiple times,
+   * the old array and its contents will be lost.
+   * <p>
+   * Only called inside of <code>deserialize</code> method or in derived
+   * classes on server.
+   *
+   * @param len the number of elements in the array.
+   */
+  public void setLength(int len) {
+    vals = new short[len];
+  }
+
+  /**
+   * Return the i'th value as a <code>short</code>.
+   * @param i the index of the value to return.
+   * @return the i'th value.
+   */
+  public final short getValue(int i) {
+    return vals[i];
+  }
+
+  /**
+   * Set the i'th value of the array.
+   * @param i the index of the value to set.
+   * @param newVal the new value.
+   */
+  public final void setValue(int i, short newVal) {
+    vals[i] = newVal;
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = vals.length;
+    for(int i=0; i<len-1; i++) {
+      os.print(vals[i]);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(vals[len-1]);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+  public void printSingleVal(PrintWriter os, int index) {
+    os.print(vals[index]);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+                                       ServerVersion sv,
+                                       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException {
+    for(int i=0; i<vals.length; i++) {
+      vals[i] = (short)source.readInt();
+      if (statusUI != null) {
+        statusUI.incrementByteCount(4);
+        if (statusUI.userCancelled())
+          throw new DataReadException("User cancelled");
+      }
+    }
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception.
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    for(int i=0; i<vals.length; i++) {
+      sink.writeInt((int)(vals[i]));
+    }
+  }
+
+  /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
+    for (int i=start; i<=stop; i+=stride)
+      sink.writeInt((int)vals[i]);
+  }
+
+    /**
+    * Returns (a reference to) the internal storage for this PrimitiveVector
+    * object.
+    * <h2>WARNING:</h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    *
+    * @return The internal array of shorts.
+    */
+    public Object getInternalStorage() {
+        return(vals);
+    }
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    public void setInternalStorage(Object o) {
+      vals = (short []) o;
+    }
+
+  /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector, of type Int16PrimitiveVector.
+   */
+  public PrimitiveVector subset( int start, int stop, int stride) {
+    Int16PrimitiveVector n = new Int16PrimitiveVector(getTemplate());
+    stride = Math.max( stride, 1);
+    stop = Math.max( start, stop);
+    int length = 1 + (stop - start) / stride;
+    n.setLength( length);
+
+    int count=0;
+    for (int i=start; i<=stop; i+=stride) {
+      n.setValue(count, vals[i]);
+      count++;
+    }
+    return n;
+  }
+
+
+
+
+}
diff --git a/dods/dap/Int32PrimitiveVector.java b/dods/dap/Int32PrimitiveVector.java
new file mode 100644
index 0000000..813619b
--- /dev/null
+++ b/dods/dap/Int32PrimitiveVector.java
@@ -0,0 +1,237 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of ints.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see PrimitiveVector
+ */
+public class Int32PrimitiveVector extends PrimitiveVector implements Cloneable {
+  /** the array of <code>int</code> values. */
+  private int vals[];
+
+  /**
+   * Constructs a new <code>Int32PrimitiveVector</code>.
+   * @param var the template <code>BaseType</code> to use.
+   */
+  public Int32PrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Returns a clone of this <code>Int32PrimitiveVector</code>.  A deep
+   * copy is performed on all data inside the variable.
+   *
+   * @return a clone of this <code>BytePrimitiveVector</code>.
+   */
+  public Object clone() {
+    Int32PrimitiveVector v = (Int32PrimitiveVector)super.clone();
+    if (vals != null) {
+      v.vals = new int[vals.length];
+      System.arraycopy(vals, 0, v.vals, 0, vals.length);
+    }
+    return v;
+  }
+
+  /**
+   * Returns the number of elements in the array.
+   * @return the number of elements in the array.
+   */
+  public int getLength() {
+    return vals.length;
+  }
+
+  /**
+   * Sets the number of elements in the array.  Allocates a new primitive
+   * array of the desired size.  Note that if this is called multiple times,
+   * the old array and its contents will be lost.
+   * <p>
+   * Only called inside of <code>deserialize</code> method or in derived
+   * classes on server.
+   *
+   * @param len the number of elements in the array.
+   */
+  public void setLength(int len) {
+    vals = new int[len];
+  }
+
+  /**
+   * Return the i'th value as an <code>int</code>.
+   * @param i the index of the value to return.
+   * @return the i'th value.
+   */
+  public final int getValue(int i) {
+    return vals[i];
+  }
+
+  /**
+   * Set the i'th value of the array.
+   * @param i the index of the value to set.
+   * @param newVal the new value.
+   */
+  public final void setValue(int i, int newVal) {
+    vals[i] = newVal;
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = vals.length;
+    for(int i=0; i<len-1; i++) {
+      os.print(vals[i]);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(vals[len-1]);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+  public void printSingleVal(PrintWriter os, int index) {
+    os.print(vals[index]);
+  }
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public synchronized void deserialize(DataInputStream source,
+                                       ServerVersion sv,
+                                       StatusUI statusUI)
+       throws IOException, EOFException, DataReadException {
+    for(int i=0; i<vals.length; i++) {
+      vals[i] = source.readInt();
+      if (statusUI != null) {
+        statusUI.incrementByteCount(4);
+        if (statusUI.userCancelled())
+          throw new DataReadException("User cancelled");
+      }
+    }
+  }
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception.
+     */
+  public void externalize(DataOutputStream sink) throws IOException {
+    for(int i=0; i<vals.length; i++) {
+      sink.writeInt(vals[i]);
+    }
+  }
+
+  /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
+    for (int i=start; i<=stop; i+=stride)
+      sink.writeInt(vals[i]);
+  }
+
+   /**
+    * Returns (a reference to) the internal storage for this PrimitiveVector
+    * object.
+    * <h2>WARNING:</h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    *
+    * @return The internal array of ints.
+    */
+    public Object getInternalStorage() {
+        return(vals);
+    }
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    public void setInternalStorage(Object o) {
+      vals = (int []) o;
+    }
+
+  /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector, of type Int32PrimitiveVector.
+   */
+  public PrimitiveVector subset( int start, int stop, int stride) {
+    Int32PrimitiveVector n = new Int32PrimitiveVector(getTemplate());
+    stride = Math.max( stride, 1);
+    stop = Math.max( start, stop);
+    int length = 1 + (stop - start) / stride;
+    n.setLength( length);
+
+    int count=0;
+    for (int i=start; i<=stop; i+=stride) {
+      n.setValue(count, vals[i]);
+      count++;
+    }
+    return n;
+  }
+
+
+}
diff --git a/dods/dap/NoSuchAttributeException.java b/dods/dap/NoSuchAttributeException.java
new file mode 100644
index 0000000..87c5a69
--- /dev/null
+++ b/dods/dap/NoSuchAttributeException.java
@@ -0,0 +1,44 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * Thrown by <code>AttributeTable</code> when an attempt is made to alias to
+ * a non-existent attribute.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see AttributeTable#addAlias(String, String)
+ */
+public class NoSuchAttributeException extends DASException {
+  /**
+   * Construct a <code>NoSuchAttributeException</code> with the specified
+   * message.
+   *
+   * @param s the detail message.
+   */
+  public NoSuchAttributeException(String s) {
+    super(DODSException.MALFORMED_EXPR, s);
+  }
+
+
+  /**
+   * Construct a <code>NoSuchAttributeException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public NoSuchAttributeException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/NoSuchFunctionException.java b/dods/dap/NoSuchFunctionException.java
new file mode 100644
index 0000000..dd3216e
--- /dev/null
+++ b/dods/dap/NoSuchFunctionException.java
@@ -0,0 +1,39 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, University of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: James Gallagher <jgallagher at gso.uri.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/** Thrown when an attempt is made to access a function that does not exist.
+    @version $Revision: 1.3 $
+    @author jhrg */
+
+public class NoSuchFunctionException extends DDSException {
+
+  /** Construct a <code>NoSuchFunctionException</code> with the specified
+   * message.
+   * @param s the detail message. */
+
+   public NoSuchFunctionException(String s) {
+       super(DODSException.MALFORMED_EXPR,s);
+   }
+
+
+  /**
+   * Construct a <code>NoSuchFunctionException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public NoSuchFunctionException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/NoSuchVariableException.java b/dods/dap/NoSuchVariableException.java
new file mode 100644
index 0000000..856ddf7
--- /dev/null
+++ b/dods/dap/NoSuchVariableException.java
@@ -0,0 +1,42 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * Thrown when an attempt is made to access a variable that does not exist.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ */
+public class NoSuchVariableException extends DDSException {
+  /**
+   * Construct a <code>NoSuchVariableException</code> with the specified detail
+   * message.
+   *
+   * @param s the detail message.
+   */
+  public NoSuchVariableException(String s) {
+    super(DODSException.NO_SUCH_VARIABLE,s);
+  }
+
+
+  /**
+   * Construct a <code>NoSuchVariableException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public NoSuchVariableException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/PrimitiveVector.java b/dods/dap/PrimitiveVector.java
new file mode 100644
index 0000000..837c6cb
--- /dev/null
+++ b/dods/dap/PrimitiveVector.java
@@ -0,0 +1,236 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+import dods.dap.Server.*;
+
+/**
+ * A helper class for <code>DVector</code>.  It allows <code>DVector</code>,
+ * and by extension, <code>DArray</code> and <code>DList</code>, to use more
+ * efficient primitive types to hold large arrays of data.
+ * A <code>PrimitiveVector</code> class is defined for each
+ * primitive type supported in DODS, and a
+ * <code>BaseTypePrimitiveVector</code> class handles <code>DArray</code>s
+ * and <code>DList</code>s of compound types.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see BaseType
+ * @see DVector
+ */
+abstract public class PrimitiveVector implements ClientIO, Cloneable {
+  /**
+   * Template variable to use for <code>printDecl</code> and
+   * <code>deserialize</code> (<code>BaseTypePrimitiveVector</code> only).
+   */
+  private BaseType var;
+
+  /**
+   * Constructs a new <code>PrimitiveVector</code>.
+   * @param var the template <code>BaseType</code> to use.
+   */
+  public PrimitiveVector(BaseType var) {
+    this.var = var;
+  }
+
+  /**
+   * Returns a clone of this <code>PrimitiveVector</code>.  A deep copy is
+   * performed on all data inside the variable.
+   *
+   * @return a clone of this <code>PrimitiveVector</code>.
+   */
+  public Object clone() {
+    try {
+      PrimitiveVector v = (PrimitiveVector)super.clone();
+      v.var = (BaseType)var.clone();
+      return v;
+    }
+    catch (CloneNotSupportedException e) {
+      // this shouldn't happen, since we are Cloneable
+      throw new InternalError();
+    }
+  }
+
+  /**
+   * Returns the template variable for this vector.
+   * @return the template variable for this vector.
+   * @see BaseTypePrimitiveVector#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  public final BaseType getTemplate() {
+    return var;
+  }
+
+  /**
+   * Returns the number of elements in the array.
+   * @return the number of elements in the array.
+   */
+  abstract public int getLength();
+
+  /**
+   * Sets the number of elements in the array.  Allocates a new primitive
+   * array of the desired size.  Note that if this is called multiple times,
+   * the old array and its contents will be lost.
+   * <p>
+   * Only called inside of <code>deserialize</code> method or in derived
+   * classes on server.
+   *
+   * @param len the number of elements in the array.
+   */
+  abstract public void setLength(int len);
+
+  /**
+   * Reads data from a <code>DataInputStream</code>. This method is only used
+   * on the client side of the DODS client/server connection.
+   *
+   * @param source a <code>DataInputStream</code> to read from.
+   * @param sv The <code>ServerVersion</code> returned by the server.
+   *    (used by <code>DSequence</code> to determine which protocol version was
+   *    used).
+   * @param statusUI The <code>StatusUI</code> object to use for GUI updates
+   *    and user cancellation notification (may be null).
+   * @exception DataReadException when invalid data is read, or if the user
+   *     cancels the download.
+   * @exception EOFException if EOF is found before the variable is completely
+   *     deserialized.
+   * @exception IOException thrown on any other InputStream exception.
+   * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
+   */
+  abstract public void deserialize(DataInputStream source, ServerVersion sv,
+                                   StatusUI statusUI)
+       throws IOException, EOFException, DataReadException;
+
+  /**
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * primarily by GUI clients which need to download DODS data, manipulate
+     * it, and then re-save it as a binary file.
+     *
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @exception IOException thrown on any <code>OutputStream</code>
+     * exception.
+     */
+  abstract public void externalize(DataOutputStream sink) throws IOException;
+
+    /**
+   * Write a subset of the data to a <code>DataOutputStream</code>.
+   *
+   * @param sink a <code>DataOutputStream</code> to write to.
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @exception IOException thrown on any <code>OutputStream</code> exception.
+   */
+  // abstract public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException;
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * method is used to create textual representation of the Data
+    * Descriptor Structure (DDS).
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean, boolean)
+    */
+    public final void printDecl(PrintWriter os, String space,
+                    boolean print_semi, boolean constrained) {
+
+                //os.println("PrimitiveVector.printDecl()");
+                //os.println(var.getTypeName()+".isProject():"+((ServerMethods)var).isProject());
+                var.printDecl(os, space, print_semi,constrained);
+    }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * method is used to create textual representation of the Data
+    * Descriptor Structure (DDS).
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @see BaseType#printDecl(PrintWriter, String, boolean)
+    */
+    public final void printDecl(PrintWriter os, String space,
+                    boolean print_semi) {
+        printDecl(os,space, print_semi,false);
+    }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  abstract public void printVal(PrintWriter os, String space);
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+  abstract public void printSingleVal(PrintWriter os, int index);
+
+
+
+    /**
+    * Returns (a reference to) the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    abstract public Object getInternalStorage();
+
+    /**
+    * Set the internal storage for PrimitiveVector.
+    * <h2><i>WARNING:</i></h2>
+    * Because this method breaks encapsulation rules the user must beware!
+    * If we (the DODS prgramming team) choose to change the internal
+    * representation(s) of these types your code will probably break.
+    * <p>
+    * This method is provided as an optimization to eliminate massive
+    * copying of data.
+    */
+    abstract public void setInternalStorage(Object o);
+
+  /**
+   * Create a new primitive vector using a subset of the data.
+   *
+   * @param start: starting index (i=start)
+   * @param stop: ending index (i<=stop)
+   * @param stride: index stride (i+=stride)
+   * @return new primitive vector
+   */
+   abstract public PrimitiveVector subset( int start, int stop, int stride);
+
+}
diff --git a/dods/dap/Server/AbstractClause.java b/dods/dap/Server/AbstractClause.java
new file mode 100644
index 0000000..0081ecb
--- /dev/null
+++ b/dods/dap/Server/AbstractClause.java
@@ -0,0 +1,48 @@
+package dods.dap.Server;
+
+import java.util.*;
+
+/** Provides default implementations for the Clause interface methods. 
+ *  This eliminates redundant code and provides a starting point 
+ *  for new implementations of Clause. <p>
+ * 
+ *  Note that every Clause object is expected to implement either 
+ *  SubClause or TopLevelClause. No default implementations are provided
+ *  for the methods of these subinterfaces <p>
+ * 
+ *  Also note that it is not <i>necessary</i> to use this class to 
+ *  create your own implementations, it's just a convenience.<p>
+ *  
+ *  The class has no abstract methods, but is declared abstract because
+ *  it should not be directly instantiated.
+ *
+ * @author joew */
+public abstract class AbstractClause 
+    implements Clause {
+
+    public List getChildren() {
+	return children;
+    }
+
+    public boolean isConstant() {
+	return constant;
+    }
+    
+    public boolean isDefined() {
+	return defined;
+    }
+
+    /** Value to be returned by isConstant(). Should not change for
+     *  the lifetime of the object. */
+    protected boolean constant;
+
+    /** Value to be returned by isDefined(). May change during the
+     *  lifetime of the object. */
+    protected boolean defined;
+
+    /** A list of SubClause objects representing 
+     *  the sub-clauses of this clause. Use caution when modifying
+     *  this list other than at the point of creation, since methods
+     *  such as evaluate() depend on it. */
+    protected List children;
+}
diff --git a/dods/dap/Server/BTFunction.java b/dods/dap/Server/BTFunction.java
new file mode 100644
index 0000000..216beea
--- /dev/null
+++ b/dods/dap/Server/BTFunction.java
@@ -0,0 +1,39 @@
+
+
+/* $Id: BTFunction.java,v 1.3 2004-02-06 15:23:48 donm Exp $
+*
+*/
+
+package dods.dap.Server;
+
+import java.util.List;
+import dods.dap.BaseType;
+
+/** Represents a server-side function, which evaluates to a BaseType.
+ *  Custom server-side functions which return non-boolean values should
+ *  implement this interface.  For an efficient implementation, it is
+ *  suggested, when possible, to use the same BaseType for the getType()
+ *  method and for each successive invocation of evaluate(), changing only
+ *  the BaseType's value. This avoids creation of large numbers of
+ *  BaseTypes during a data request.
+ * @see BTFunctionClause
+ * @author joew */
+public interface BTFunction 
+    extends ServerSideFunction {
+
+    /** A given function must always evaluate to the same class 
+     *  of BaseType. Only the value held by the BaseType may change.
+     *  This method can be used to discover the BaseType class of a 
+     *  function without actually evaluating it.
+     */
+    public BaseType getReturnType(List args) 
+	throws InvalidParameterException;
+
+    /** Evaluates the function using the argument list given.
+     * @exception SDODSException Thrown if the function
+     *  cannot evaluate successfully. The exact type of exception is up
+     *  to the author of the server-side function.
+     */
+    public BaseType evaluate(List args) 
+	throws SDODSException;
+}
diff --git a/dods/dap/Server/BTFunctionClause.java b/dods/dap/Server/BTFunctionClause.java
new file mode 100644
index 0000000..ed87524
--- /dev/null
+++ b/dods/dap/Server/BTFunctionClause.java
@@ -0,0 +1,96 @@
+package dods.dap.Server;
+
+import java.util.*;
+import dods.dap.BaseType;
+
+/** Represents a clause which invokes a function that returns a BaseType. 
+ * @see ClauseFactory
+ * @author joew */
+public class BTFunctionClause
+    extends AbstractClause
+    implements SubClause {
+
+    /** Creates a new BTFunctionClause.
+     * @param function The function invoked by the clause
+     * @param children A list of SubClauses, to be given as arguments 
+     * to the function. If all the arguments are constant, the function
+     * clause will be flagged as constant, and evaluated immediately.
+     * @exception SDODSException Thrown if either 1) the function does not 
+     * accept the arguments given, or 2) the 
+     * clause is constant, and the attempt to evaluate it fails. 
+     */
+    protected BTFunctionClause(BTFunction function,
+			       List children) 
+	throws SDODSException {
+
+	function.checkArgs(children);
+	this.function = function;
+	this.children = children;
+	this.constant = true;
+	Iterator it = children.iterator();
+	while (it.hasNext()) {
+	    SubClause current = (SubClause)it.next();
+	    current.setParent(this);
+	    if (!current.isConstant()) {
+		constant = false;
+	    }
+	}
+	value = function.getReturnType(children);
+	if (constant) {
+	    evaluate();
+	}
+    }
+
+    public Clause getParent() {
+	return parent;
+    }
+
+    public BaseType getValue() {
+	return value;
+    }
+    
+    public BaseType evaluate() 
+	throws SDODSException {
+
+	if (!constant || !defined) {
+	    value = function.evaluate(children);
+	    defined = true;
+	}
+	return value;
+    }
+
+    public void setParent(Clause parent) {
+	this.parent = parent;
+    }
+
+    /** Returns the server-side function invoked by this clause */
+    public BTFunction getFunction() {
+	return function;
+    }
+
+    /** Prints the original string representation of this clause.
+     *  For use in debugging.
+     */
+    public String toString() {
+	StringBuffer buf = new StringBuffer();
+	buf.append(function.getName());
+	buf.append("(");
+	Iterator it = children.iterator();
+	if (it.hasNext()) {
+	    buf.append(it.next().toString());
+	} 
+	while (it.hasNext()) {
+	    buf.append(",");
+	    buf.append(it.next().toString());
+	}
+	buf.append(")");
+	return buf.toString();
+    }
+
+    protected Clause parent;
+
+    protected BTFunction function;
+
+    protected BaseType value;
+
+}
diff --git a/dods/dap/Server/BoolFunction.java b/dods/dap/Server/BoolFunction.java
new file mode 100644
index 0000000..a6c8e06
--- /dev/null
+++ b/dods/dap/Server/BoolFunction.java
@@ -0,0 +1,26 @@
+
+
+/* $Id: BoolFunction.java,v 1.3 2004-02-06 15:23:48 donm Exp $
+*
+*/
+
+package dods.dap.Server;
+
+import java.util.List;
+
+/** Represents a server-side function, which evaluates to a boolean value.
+ *  Custom server-side functions which return boolean values
+ *  should implement this interface.
+ * @see BoolFunctionClause
+ * @author joew */
+public interface BoolFunction 
+    extends ServerSideFunction {
+
+    /** Evaluates the function using the argument list given.
+     * @exception SDODSException Thrown if the function
+     *  cannot evaluate successfully. The exact type of exception is up
+     *  to the author of the server-side function.
+     */
+    public boolean evaluate(List args) 
+	throws SDODSException;
+}
diff --git a/dods/dap/Server/BoolFunctionClause.java b/dods/dap/Server/BoolFunctionClause.java
new file mode 100644
index 0000000..07f6792
--- /dev/null
+++ b/dods/dap/Server/BoolFunctionClause.java
@@ -0,0 +1,83 @@
+package dods.dap.Server;
+
+import java.util.*;
+
+/** Represents a clause which invokes a function that returns a boolean value. 
+ * @see ClauseFactory
+ * @author joew */
+public class BoolFunctionClause
+    extends AbstractClause
+    implements TopLevelClause {
+
+    /** Creates a new BoolFunctionClause.
+     * @param function The function invoked by the clause
+     * @param children A list of SubClauses, to be given as arguments 
+     * to the function. If all the arguments are constant, the function
+     * clause will be flagged as constant, and evaluated immediatelyx.
+     * @exception SDODSException Thrown if either 1) the function does not 
+     * accept the arguments given, or 2) the 
+     * clause is constant, and the attempt to evaluate it fails. 
+     */
+    protected BoolFunctionClause(BoolFunction function,
+				 List children) 
+	throws SDODSException {
+
+	function.checkArgs(children);
+	this.function = function;
+	this.children = children;
+	this.constant = true;
+	Iterator it = children.iterator();
+	while (it.hasNext()) {
+	    SubClause current = (SubClause)it.next();
+	    current.setParent(this);
+	    if (!current.isConstant()) {
+		constant = false;
+	    }
+	}
+	if (constant) {
+	    evaluate();
+	}
+    }
+    
+    public boolean getValue() {
+	return value;
+    }
+    
+    public boolean evaluate() 
+	throws SDODSException {
+
+	if (!constant || !defined) {
+	    defined = true;
+	    value = function.evaluate(children);
+	}
+	return value;
+    }
+
+    /** Returns the server-side function invoked by this clause */
+    public BoolFunction getFunction() {
+	return function;
+    }
+
+    /** Prints the original string representation of this clause.
+     *  For use in debugging.
+     */
+    public String toString() {
+	StringBuffer buf = new StringBuffer();
+	buf.append(function.getName());
+	buf.append("(");
+	Iterator it = children.iterator();
+	if (it.hasNext()) {
+	    buf.append(it.next().toString());
+	} 
+	while (it.hasNext()) {
+	    buf.append(",");
+	    buf.append(it.next().toString());
+	}
+	buf.append(")");
+	return buf.toString();
+    }
+
+    protected BoolFunction function;
+
+    protected boolean value;
+}
diff --git a/dods/dap/Server/CEEvaluator.java b/dods/dap/Server/CEEvaluator.java
new file mode 100644
index 0000000..bafae5b
--- /dev/null
+++ b/dods/dap/Server/CEEvaluator.java
@@ -0,0 +1,329 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, University of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: James Gallagher <jgallagher at gso.uri.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import dods.dap.BaseType;
+import dods.dap.DArrayDimension;
+import dods.dap.DODSException;
+import dods.dap.NoSuchFunctionException;
+import dods.dap.NoSuchVariableException;
+import dods.dap.parser.ExprParser;
+import dods.dap.parser.ParseException;
+import dods.dap.parser.TokenMgrError;
+
+/**
+   This class is used to parse and evaluate a constraint expression. When
+   constructed it must be passed a valid DDS along with the expression. This
+   DDS will be used as the environment (collection of variables and
+   functions) during the parse and evaluation of the constraint expression.
+   <p>
+   A server (servlet, CGI, ...) must first instantiate the DDS (possibly
+   reading it from a cache) and then create and instance of this class. Once
+   created, the constraint may be parsed and then evaluated. The class
+   supports sending data based on the results of CE evaluation. That is, the
+   send() method of the class combines both the evaluation of the constraint
+   and the output of data values so that the server can return data using a
+   single method call.<p>
+
+   <b>Custom parsing</b>
+   The CEEvaluator parses constraint expressions into Clause objects
+   using a ClauseFactory. Customized behavior during parsing can be 
+   achieved by passing a customized ClauseFactory into the CEEvaluator.<p>
+
+   <b>Support for server side functions</b>
+   Server side functions are supported via the FunctionLibrary class.
+   Custom server side function support is achieved by using
+   a customized ClauseFactory which in turn contains a customized
+   FunctionLibrary.<p>
+
+   More details are found in the documentation for the respective classes.
+
+
+   @version $Revision: 1.3 $
+   @author jhrg
+   @see ServerDDS
+   @see ServerMethods
+   @see ClauseFactory
+   @see FunctionLibrary
+   @see Clause */
+
+public class CEEvaluator {
+
+    private static final boolean _Debug = false;
+    
+    /** This contains the DDS to be used during parse and evaluation of the
+	CE. */
+    private ServerDDS _dds;
+
+    /** The Clause objects which hold the parsed selection information. */
+    private Vector _cv;
+
+    /** The factory which will be used by the parser to construct the clause
+     *  tree. This allows servers to pass in a factory which creates
+     *  custom clause objects.
+     */
+    private ClauseFactory clauseFactory;
+
+    /** Construct a new <code>CEEvaluator</code> with <code>dds</code> as the
+	DDS object with which to resolve all variable and function names.
+	@param dds DDS object describing the dataset targeted by this
+	constraint. */
+    public CEEvaluator(ServerDDS dds) {
+	    _dds = dds;
+	    _cv = new Vector();
+    }
+
+    /** Construct a new <code>CEEvaluator</code> with <code>dds</code> as the
+	DDS object with which to resolve all variable and function names, and
+	<code>clauseFactory</code> as a source of Clause objects .
+	@param clauseFactory The factory which will be used by the parser to construct the clause
+	tree. This allows servers to pass in a factory which creates
+	custom clause objects.
+	@param dds DDS object describing the dataset targeted by this
+	constraint. 
+	author Joe Wielgosz (joew at cola.iges.org)
+    */
+    public CEEvaluator(ServerDDS dds, ClauseFactory clauseFactory) {
+	    _dds = dds;
+	    _cv = new Vector();
+	    this.clauseFactory = clauseFactory;
+    }
+
+
+    /** Return a reference to the CEEvaluator's DDS object. */
+    public ServerDDS getDDS() {
+	    return _dds;
+    }
+
+	/**
+	 * Parse a constraint expression. Variables in the projection are marked as
+	 * such in the CEEvaluator's ServerDDS instance. The selection subexpression
+	 * is then parsed and a list of Clause objects is built.
+	 * <p>
+	 * The parser is located in dods.dap.parser.ExprParser.
+	 * 
+	 * @param expression
+	 *            The constraint expression to parse.
+	 * @exception ParseException
+	 * @exception NoSuchVariableException
+	 * @exception NoSuchFunctionException
+	 * @exception InvalidOperatorException
+	 * @exception InvalidParameterException
+	 * @exception SBHException
+	 */
+    
+    public void parseConstraint(String expression) 
+	throws ParseException, DODSException, NoSuchVariableException, 
+	       NoSuchFunctionException, InvalidOperatorException, 
+	       InvalidParameterException, SBHException, WrongTypeException {
+
+        StringReader sExpr = new StringReader(expression);
+        ExprParser exp = new ExprParser(sExpr);
+	
+	
+
+	if (clauseFactory == null) {
+	    clauseFactory = new ClauseFactory();
+	}
+	
+	try {
+	    // Parses constraint expression (duh...) and sets the
+	    // projection flag for each member of the CE's ServerDDS
+	    // instance. This also builds the list of clauses.
+	    exp.constraint_expression(this, _dds.getFactory(), clauseFactory);
+	}
+	catch (TokenMgrError tme) {
+	    throw new ParseException(tme.getMessage());
+	}
+	
+
+        if(_Debug){
+            int it = 0;
+	    Enumeration ec = getClauses();
+            while( ec.hasMoreElements() ) {
+                it++;
+		System.out.println("Results of clause parsing:");
+                System.out.println("Clause " + it + ": " + ec.nextElement());
+		System.out.println();
+	    }	
+        }
+
+
+    }
+
+    /** Add a clause to the constraint expression.
+	@param c The Clause to append. */
+    public void appendClause(Clause c) {	
+        if (c != null) {
+	    _cv.add(c);
+	}
+    }
+
+    /** 
+     * Remove a clause from the constraint expression. This will
+     * will remove the first occurrence of the passed clause from 
+     * the constraint expression. This is done be reference, so if 
+     * the passed Clause object is NOT already in the constraint
+     * expression then nothing happens. And, if it should appear
+     * more than once (which I <b>don't</b> think is possible) only
+     * the first occurrence will be removed.
+     * @param c The Clause to append. 
+     * @return True if constraint expression contained the passed Clause
+     * object and it was successfully removed.
+     */
+    
+    public boolean removeClause(Clause c) {	
+    	if (c != null) {
+    		return(_cv.remove(c));
+    	}
+    	return(false);
+    }
+
+    /** Get access to the list of clauses built by parsing the selection part
+	of the constraint expression. 
+	<p>
+	NB: This is not valid until the CE has been parsed! */
+    public final Enumeration getClauses() {		
+	return _cv.elements();
+    }
+
+	/**
+	 * This function sends the variables described in the constrained DDS to the
+	 * output described by <code>sink</code>. This function calls
+	 * <code>parse_constraint()</code>, <code>BaseType::read()</code>, and
+	 * <code>ServerIO::serialize()</code>.
+	 * 
+	 * @param dataset
+	 *            The name of the dataset to send.
+	 * @param sink
+	 *            A pointer to the output buffer for the data.
+	 * @param specialO
+	 *            Not sure - special object?
+	 * @see #parseConstraint(String)
+	 * @see ServerMethods#serialize(String, DataOutputStream, CEEvaluator,
+	 *      Object) ServerMethods.serialize()
+	 */
+    
+    public void send(String dataset, OutputStream sink, Object specialO) 
+                                            throws NoSuchVariableException, SDODSException, IOException {
+        Enumeration e = _dds.getVariables();
+        while( e.hasMoreElements() ) {
+
+            ServerMethods s = (ServerMethods)e.nextElement();
+
+            if(_Debug) 
+		System.out.println("Sending variable: "+
+				   ((BaseType)s).getName());
+	    
+            if(s.isProject()){
+	    
+	        if(_Debug)
+		    System.out.println("Calling "
+		                   +((BaseType)s).getTypeName()
+				   +".serialize() (Name: "
+				   +((BaseType)s).getName()
+				   + ")");
+				   
+                s.serialize(dataset,(DataOutputStream)sink, this, specialO);
+	    }
+        }
+    }
+
+    /** 
+     * Evaluate all of the Clauses in the Clause vector. 
+     * @param specialO That special Object that can be passed down 
+     * through the <code>DDS.send()</code> method.
+     * @return True if all the Clauses evaluate to true, false otherwise.
+     */
+    
+    public boolean evalClauses(Object specialO) throws NoSuchVariableException, SDODSException, IOException {
+
+        boolean result = true;	
+        Enumeration ec = getClauses();
+	
+        while( ec.hasMoreElements() && result==true ) {
+            Object o = ec.nextElement();
+	    if(_Debug){
+		System.out.println("Evaluating clause: " + ec.nextElement());
+	    }
+	    
+            result = ((TopLevelClause)o).evaluate();
+
+	}
+
+        return(result);
+    }
+    
+    
+
+    /** Mark all the variables in the DDS either as part of the current
+	projection (when <code>state</code> is true) or not
+	(<code>state</code> is false). This is a convenience function that
+	provides a way to clear or set an entire dataset described by a DDS
+	with respect to its projection.
+	@param state true if the variables should all be projected, false is
+	no variable should be projected. */
+    public void markAll(boolean state) 
+	throws InvalidParameterException, NoSuchVariableException,
+	       SBHException
+    {
+	// For all the Variables in the DDS
+	Enumeration e = _dds.getVariables();
+	while( e.hasMoreElements() ) {
+	    // Get the thing
+	    Object o = e.nextElement();
+	    //- Clip this to stop marking all dimensions of Grids and Arrays
+	    // If we are marking all for true, then we need to make sure
+	    // we get all the parts of each array and grid
+	    
+	    // This code should probably be moved into SDArray and SDGrid.
+	    // There we should add a resetProjections() method that changes
+	    // the current projection to be the entire array. 11/18/99 jhrg
+	    if (state) {
+		if(o instanceof SDArray) { // Is this thing a SDArray?
+		    SDArray SDA = (SDArray)o;
+			    
+		    // Get it's DArrayDimensions
+		    Enumeration eSDA = SDA.getDimensions();
+		    while(eSDA.hasMoreElements()){
+			DArrayDimension dad = (DArrayDimension)eSDA.nextElement();
+			// Tweak it's projection state
+			dad.setProjection(0, 1, dad.getSize()-1);
+		    }
+		}
+		else if (o instanceof SDGrid) {  // Is this thing a SDGrid?
+		    SDGrid SDG = (SDGrid)o;
+		    SDArray sdgA = (SDArray)SDG.getVar(0); // Get it's internal SDArray.
+			    		
+		    // Get it's DArrayDimensions
+		    Enumeration eSDA = sdgA.getDimensions();
+		    while(eSDA.hasMoreElements()){
+			DArrayDimension dad = (DArrayDimension)eSDA.nextElement();
+			// Tweak it's projection state
+			dad.setProjection(0, 1, dad.getSize()-1);
+		    }
+		}
+	    }
+	    //-------------------------- End Clip ---------------------------
+			
+	    ServerMethods s = (ServerMethods)o;
+	    s.setProject(state);
+	}
+    }
+}
diff --git a/dods/dap/Server/Clause.java b/dods/dap/Server/Clause.java
new file mode 100644
index 0000000..434c2f3
--- /dev/null
+++ b/dods/dap/Server/Clause.java
@@ -0,0 +1,27 @@
+package dods.dap.Server;
+
+import java.util.List;
+
+/** Represents the common interface of the two types of clause used by the
+ *  constraint expression (CE) parser: TopLevelClause and SubClause.
+ *  See these interfaces for more about CE parsing and evaluation.
+ * 
+ * @author joew */
+public interface Clause {
+
+    /** Returns an ordered list of this clause's sub-clauses.  If the
+     *  clause has no sub-clauses, an empty list will be returned.  */
+    public List getChildren();
+
+    /** A clause is considered "constant" iff it and its subclauses do not
+     *  refer to data values from the dataset being constrained.  A
+     *  constant clause is defined as soon as it is created, and is
+     *  guaranteed not to change its value during its lifetime.  */
+    public boolean isConstant();
+    
+    /** Returns whether or not the clause has a defined value. Non-constant
+     *  clauses do not have a defined value until they are evaluated for the
+     *  first time. Methods for evaluating are found in the TopLevelClause
+     *  and SubClause interfaces. */
+    public boolean isDefined();
+}
diff --git a/dods/dap/Server/ClauseFactory.java b/dods/dap/Server/ClauseFactory.java
new file mode 100644
index 0000000..16ae9c2
--- /dev/null
+++ b/dods/dap/Server/ClauseFactory.java
@@ -0,0 +1,128 @@
+package dods.dap.Server;
+
+import dods.dap.BaseType;
+import dods.dap.NoSuchFunctionException;
+import java.util.List;
+
+/** Represents a source of Clause objects for the constraint expression
+ *  parser.  By inheriting from this class and overriding the "newX" methods,
+ *  you can create a factory that provides your own Clause objects instead 
+ *  of the default ones. This custom factory can be given to the parser
+ *  via the CEEvaluator interface. 
+ *
+ * @see CEEvaluator
+ */
+public class ClauseFactory {
+
+    /** Creates a new clause factory with a blank function library. This
+     *  constructor is sufficient for servers with no server side functions.
+     * @see FunctionLibrary*/
+    public ClauseFactory() {
+	this.functionLibrary = new FunctionLibrary();
+    }
+
+    /** Creates a clause factory which uses the specified function library.
+     *  This constructor allows you to parse CE's using a customized function 
+     *  library.
+     * @param functionLibrary The function library that will be used
+     * when creating clauses that invoke server-side functions. 
+     */
+    public ClauseFactory(FunctionLibrary functionLibrary) {
+	this.functionLibrary = functionLibrary;
+    }
+
+    /** Generates a clause which which compares subclauses, using one of the
+     *  relative operators supported by the Operator class.
+     */
+    public TopLevelClause newRelOpClause(int operator,
+					 SubClause lhs,
+					 List rhs)
+	throws SDODSException {
+
+	return new RelOpClause(operator,
+			       lhs,
+			       rhs);
+    }
+
+    /** Generates a clause which invokes a function that returns a 
+     *  boolean value. 
+     * @see BoolFunctionClause
+     */
+    public TopLevelClause newBoolFunctionClause(String functionName,
+						List children) 
+	throws SDODSException,
+	       NoSuchFunctionException {
+
+	BoolFunction function = 
+	    functionLibrary.getBoolFunction(functionName);
+	if (function == null) {
+	    if (functionLibrary.getBTFunction(functionName) != null) {
+		throw new NoSuchFunctionException
+		    ("The function " + functionName + 
+		     "() does not return a " + 
+		     "boolean value, and must be used in a comparison or " +
+		     "as an argument to another function.");
+	    } else {
+		throw new NoSuchFunctionException
+		    ("This server does not support a " + 
+		     functionName + "() function");
+	    }
+	}
+	return new BoolFunctionClause(function,
+				      children);
+    }
+
+    /** Generates a clause representing a simple value, 
+     *  such as "var1" or "19". 
+     * @see ValueClause
+     */
+    public SubClause newValueClause(BaseType value, 
+				    boolean constant)
+	throws SDODSException {
+
+	return new ValueClause(value,
+			       constant);
+    }
+
+    /** Generates a clause which invokes a function that returns a 
+     *  BaseType. 
+     * @see BTFunctionClause
+     */
+    public SubClause newBTFunctionClause(String functionName,
+					 List children)
+	throws SDODSException,
+	       NoSuchFunctionException {
+
+	BTFunction function = 
+	    functionLibrary.getBTFunction(functionName);
+	if (function == null) {
+	    if (functionLibrary.getBoolFunction(functionName) != null) {
+		throw new NoSuchFunctionException
+		    ("The function " + functionName + 
+		     "() cannot be used as a " +
+		     "sub-expression in a constraint clause");
+	    } else {
+		throw new NoSuchFunctionException
+		    ("This server does not support a " + 
+		     functionName + "() function");
+	    }
+	}
+	return new BTFunctionClause(function,
+				    children);
+    }
+
+    /** Generates a clause representing a remote value, referenced by a URL. 
+     *  Note that dereferencing is not currently supported, and the default
+     *  implementation of this clause type throws an exception when it is
+     *  evaluated.
+     * @see DereferenceClause
+     */
+    public SubClause newDereferenceClause(String url)
+	throws SDODSException {
+
+	return new DereferenceClause(url);
+    }
+
+    protected FunctionLibrary functionLibrary;
+
+}
diff --git a/dods/dap/Server/DereferenceClause.java b/dods/dap/Server/DereferenceClause.java
new file mode 100644
index 0000000..0f2328b
--- /dev/null
+++ b/dods/dap/Server/DereferenceClause.java
@@ -0,0 +1,61 @@
+package dods.dap.Server;
+
+import java.util.*;
+import dods.dap.BaseType;
+import dods.dap.DODSException;
+
+/** Represents a sub-clause that is a URL reference to remote data.
+ *  This feature is not yet supported in Java. Thus this class 
+ *  throws an exception in its constructor.
+ * @see ClauseFactory
+ * @author joew */
+public class DereferenceClause 
+    extends AbstractClause 
+    implements SubClause {
+
+    /** Creates a new DereferenceClause */
+    protected DereferenceClause(String url) 
+	throws SDODSException {
+	this.url = url;
+	this.constant = true;
+	this.defined = true;
+	this.value = retrieve(url);
+	this.children = new ArrayList();
+    }
+
+    public BaseType getValue() {
+	return value;
+    }
+    
+    public BaseType evaluate() {
+	return value;
+    }
+
+    public Clause getParent() {
+	return parent;
+    }
+
+    public void setParent(Clause parent) {
+	this.parent = parent;
+    }
+
+    public String getURL() {
+	return url;
+    }
+
+    protected BaseType retrieve(String url) 
+	throws SDODSException {
+	
+	throw new SDODSException(DODSException.UNKNOWN_ERROR,"dereferencing not supported");
+    }
+
+    public String toString() {
+	return "*\"" + url + "\"";
+    }
+
+    protected String url;
+    protected Clause parent;
+    protected BaseType value;
+}
+
+
diff --git a/dods/dap/Server/FunctionLibrary.java b/dods/dap/Server/FunctionLibrary.java
new file mode 100644
index 0000000..38056fb
--- /dev/null
+++ b/dods/dap/Server/FunctionLibrary.java
@@ -0,0 +1,143 @@
+package dods.dap.Server;
+
+import java.util.*;
+import dods.dap.NoSuchFunctionException;
+
+
+/** Represents a library of available server-side functions for use
+ *  in evaluating constraint expressions. <p>
+ *
+ *  When created, a FunctionLibrary is empty.
+ *  There are two ways to populate it, described below. Once the 
+ *  FunctionLibrary has been created and populated, it should be 
+ *  used to create a ClauseFactory, which can then be given to 
+ *  the CEEvaluator for use in parsing.<p>
+ *  
+ *  <p> The most straightforward is to pass an instance of each 
+ *  ServerSideFunction class to the add() method.  If you have a large
+ *  number of ServerSideFunctions, or if you want to have choose class names
+ *  for your functions independent of the actual function name, this is the
+ *  approach to use. 
+ *  
+ *  A second, more complex method resolves
+ *  server-side function names at runtime. The function library automatically 
+ *  looks for a class whose
+ *  name matches the name requested (if a prefix is set, it will
+ *  look for a class whose name is prefix + name requested). If such a class
+ *  exists, and it implements the ServerSideFunction interface, a new instance
+ *  of the class is created using the default constructor, added to the list 
+ *  of available functions, and returned to the caller. <p>
+ * 
+ *  This is not quite as complicated as it sounds. For instance, say the 
+ *  FunctionLibrary's prefix is set to "dods.servers.test.SSF". Then the 
+ *  first time
+ *  the server gets a constraint expression containing the function "dummy()",
+ *  the FunctionLibrary will  
+ *  automatically load the class "dods.servers.test.SSFdummy", and return
+ *  a new instance using the class's default constructor. <p>
+ *  
+ *  This avoids the need to hardcode a list of server-side functions, and 
+ *  allows you to create new functions at any time. For instance to create
+ *  a function "newfn()" you can simply
+ *  create the class "dods.servers.test.SSFnewfn" and put it somewhere
+ *  in the server's classpath . Then the first CE containing this function will
+ *  cause that class to be automatically loaded, just the like the dummy() 
+ *  function was. 
+ * 
+ *  @author joew
+ */
+public class FunctionLibrary {
+
+    /** Creates a new FunctionLibrary with no prefix set. */
+    public FunctionLibrary() {
+	this("");
+    }
+
+    /** Creates a new FunctionLibrary. 
+    * @param prefix A string that will be prepended to function names in order
+    * to create a classname for that function. For example,  */
+    public FunctionLibrary(String prefix) {
+	this.prefix = prefix;
+	this.boolFunctions = new HashMap();
+	this.btFunctions = new HashMap();
+    }
+
+    /** Sets the prefix to use for classname lookup. 
+     *  For instance, if the prefix
+     *  is "myserver.SSF", then when the library doesn't have an entry
+     *  for function "xyz" it will look for a class "myserver.SSFxyz".
+     */
+    public void setPrefix(String prefix) {
+	this.prefix = prefix;
+    } 
+
+    /** Returns the prefix being used for classname lookup. */
+    public String getPrefix() {
+	return prefix;
+    }
+
+    /** Adds a function to the library. The function will be inspected
+     *  to determine whether it is a boolean or BaseType function.
+     */
+    public void add(ServerSideFunction function) {
+	if (function instanceof BoolFunction) {
+	    boolFunctions.put(function.getName(), function);
+	} 
+	if (function instanceof BTFunction) {
+	    btFunctions.put(function.getName(), function);
+	}	
+    }
+
+    /** Retrieves a boolean function from the library. If the function
+     *  is not found the library will attempt to load it using the 
+     *  mechanism described in the class documentation. 
+     * @param name The name of the function being requested.
+     * @return Null if the function is not
+     * in the library, and the attempt to load it fails.
+     */
+    public BoolFunction getBoolFunction(String name) 
+	throws NoSuchFunctionException {
+
+	if (!boolFunctions.containsKey(name)) {
+	    loadNewFunction(name);
+	}
+	return (BoolFunction)boolFunctions.get(name);
+    }
+
+
+    /** Retrieves a BaseType function from the library. If the function
+     *  is not found the library will attempt to load it using the 
+     *  mechanism described in the class documentation. 
+     * @param name The name of the function being requested.
+     * @return Null if the function is not
+     * in the library, and the attempt to load it fails.
+     */
+    public BTFunction getBTFunction(String name)
+	throws NoSuchFunctionException {
+
+	if (!btFunctions.containsKey(name)) {
+	    loadNewFunction(name);
+	}
+	return (BTFunction)btFunctions.get(name);
+    }
+
+    /** Tries to load a function with the given name. */
+    protected void loadNewFunction(String name) {
+	try {
+	    String fullName = prefix + name;
+	    Class value = Class.forName(fullName);
+	    if ((ServerSideFunction.class).isAssignableFrom(value)) {
+		add((ServerSideFunction)value.newInstance());
+		return;
+	    }
+	} catch (ClassNotFoundException e) {
+	} catch (IllegalAccessException e) {
+	} catch (InstantiationException e) {
+	} 
+    }
+
+    protected Map boolFunctions;
+    protected Map btFunctions;
+    protected String prefix;
+}
+
diff --git a/dods/dap/Server/InvalidOperatorException.java b/dods/dap/Server/InvalidOperatorException.java
new file mode 100644
index 0000000..2f43c0a
--- /dev/null
+++ b/dods/dap/Server/InvalidOperatorException.java
@@ -0,0 +1,69 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+// $Log: not supported by cvs2svn $
+// Revision 1.8  2002/01/03 22:58:47  ndp
+// Merged newClauseImplementation branch into trunk.
+//
+// Revision 1.7.2.1  2002/01/03 19:14:51  ndp
+// New Clause and Function stuff working...
+//
+// Revision 1.7  2001/02/04 01:44:48  ndp
+// Cleaned up javadoc errors
+//
+// Revision 1.6  1999/09/24 23:17:58  ndp
+// Added new constructors to all Exception classes
+//
+// Revision 1.5  1999/09/24 21:59:23  ndp
+// Reorged Exceptions. Code compiles.
+//
+// Revision 1.4  1999/09/16 21:22:52  ndp
+// *** empty log message ***
+//
+// Revision 1.3  1999/08/20 22:59:43  jimg
+// Changed the package declaration to dods.dap.Server so that it matches the
+// directory name.
+//
+
+package dods.dap.Server;
+import dods.dap.DODSException;
+
+/**
+ * Thrown when a <code>RelOp</code> operation is called
+ * on two types for which it makes no sense to compre, such as
+ * attempting to ascertain is a String is less than a Float.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ */
+public class InvalidOperatorException extends SDODSException {
+  /**
+   * Construct a <code>InvalidOperatorException</code> with the specified
+   * detail message.
+   *
+   * @param s the detail message.
+   */
+  public InvalidOperatorException(String s) {
+    super(DODSException.MALFORMED_EXPR,"Invalid Operator Exception: " + s);
+  }
+
+
+  /**
+   * Construct a <code>InvalidOperatorException</code> with the specified
+   * message and DODS error code (see <code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public InvalidOperatorException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/Server/InvalidParameterException.java b/dods/dap/Server/InvalidParameterException.java
new file mode 100644
index 0000000..421513a
--- /dev/null
+++ b/dods/dap/Server/InvalidParameterException.java
@@ -0,0 +1,66 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+// $Log: not supported by cvs2svn $
+// Revision 1.2  2001/02/04 01:44:48  ndp
+// Cleaned up javadoc errors
+//
+// Revision 1.1  1999/10/20 21:42:34  ndp
+// *** empty log message ***
+//
+// Revision 1.6  1999/09/24 23:17:58  ndp
+// Added new constructors to all Exception classes
+//
+// Revision 1.5  1999/09/24 21:59:23  ndp
+// Reorged Exceptions. Code compiles.
+//
+// Revision 1.4  1999/09/16 21:22:52  ndp
+// *** empty log message ***
+//
+// Revision 1.3  1999/08/20 22:59:43  jimg
+// Changed the package declaration to dods.dap.Server so that it matches the
+// directory name.
+//
+
+package dods.dap.Server;
+import dods.dap.DODSException;
+
+/**
+ * Used to indicate that one of the passed parameters to a method
+ * is either the wrong type, is missing, or it's value is 
+ * unacceptable.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ */
+public class InvalidParameterException extends SDODSException {
+  /**
+   * Construct a <code>InvalidParameterException</code> with the specified
+   * detail message.
+   *
+   * @param s the detail message.
+   */
+  public InvalidParameterException(String s) {
+    super(DODSException.MALFORMED_EXPR,"Invalid Parameter Exception: " + s);
+  }
+
+
+  /**
+   * Construct a <code>InvalidParameterException</code> with the specified
+   * message and DODS error code (see <code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public InvalidParameterException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/Server/Operator.java b/dods/dap/Server/Operator.java
new file mode 100644
index 0000000..f9e2d51
--- /dev/null
+++ b/dods/dap/Server/Operator.java
@@ -0,0 +1,1788 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, University of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter <ndp at oce.orst.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+
+import gnu.regexp.RE;
+import gnu.regexp.REException;
+import dods.dap.BaseType;
+import dods.dap.DBoolean;
+import dods.dap.DByte;
+import dods.dap.DFloat32;
+import dods.dap.DFloat64;
+import dods.dap.DInt16;
+import dods.dap.DInt32;
+import dods.dap.DString;
+import dods.dap.DUInt16;
+import dods.dap.DUInt32;
+import dods.dap.DURL;
+import dods.dap.parser.ExprParserConstants;
+
+/** This class contains the code for performing relative
+    operations (RelOps) on BaseTypes. It contains one
+    heavily overloaded method that is smart enough to figure
+    out which actual BaseType were passed in and perform (if
+    appropriate) the RelOp comparison on the 2 types.
+    @author ndp
+    @version $Revision: 1.3 $
+ */
+
+
+
+public class Operator implements ExprParserConstants {
+      
+    /**
+    * Performs the Relatove Operation (RelOp) indicated by the
+    * parameter <code>oprtr</code> on the 2 passed BaseTypes if
+    * appropriate. 
+    * <p>
+    * Obviously some type don't compare logically, such as asking if
+    * String is less than a Float. For these non sensical operations
+    * and <code>InvalidOperatorException</code> is thrown.
+    * 
+    * @param oprtr The operatoration to perform as defined in <code>
+    * dods.dap.parser.ExprParserConstants</code>
+    *
+    * @param lop A BaseType to be used as the left operand.
+    *
+    * @param rop A BaseType to be used as the right operand.
+    *
+    * @return True is the operation evaluates as true, flase otherwise.
+    *
+    * @exception dods.dap.Server.InvalidOperatorException
+    * @exception dods.dap.Server.RegExpException
+    * @exception dods.dap.Server.SBHException
+    *
+    *
+    * @see dods.dap.parser.ExprParserConstants
+    */
+    public static boolean op (int oprtr, BaseType lop, BaseType rop) throws InvalidOperatorException, 
+								       RegExpException,
+								       SBHException {
+		if (lop instanceof DBoolean)
+		   return( op(oprtr,(DBoolean)lop,rop));
+		else if (lop instanceof DByte)
+		   return( op(oprtr,(DByte)lop,rop));
+		else if (lop instanceof DFloat32)
+		   return( op(oprtr,(DFloat32)lop,rop));
+		else if (lop instanceof DFloat64)
+		   return( op(oprtr,(DFloat64)lop,rop));
+		else if (lop instanceof DInt16)
+		   return( op(oprtr,(DInt16)lop,rop));
+		else if (lop instanceof DInt32)
+		   return( op(oprtr,(DInt32)lop,rop));
+		else if (lop instanceof DString)
+		   return( op(oprtr,(DString)lop,rop));
+		else if (lop instanceof DUInt16)
+		   return( op(oprtr,(DUInt16)lop,rop));
+		else if (lop instanceof DUInt32)
+		   return( op(oprtr,(DUInt32)lop,rop));
+		else if (lop instanceof DURL)
+		   return( op(oprtr,(DURL)lop,rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+
+    private static boolean op (int oprtr, DBoolean lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DByte lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DFloat32 lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DFloat64 lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DInt16 lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DInt32 lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DString lop, BaseType rop) throws InvalidOperatorException, 
+									   RegExpException,
+									   SBHException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DUInt16 lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DUInt32 lop, BaseType rop) throws InvalidOperatorException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+    private static boolean op (int oprtr, DURL lop, BaseType rop) throws InvalidOperatorException, 
+									   RegExpException,
+									   SBHException {
+		if (rop instanceof DBoolean)
+		   return( op(oprtr,lop,(DBoolean)rop));
+		else if (rop instanceof DByte)
+		   return( op(oprtr,lop,(DByte)rop));
+		else if (rop instanceof DFloat32)
+		   return( op(oprtr,lop,(DFloat32)rop));
+		else if (rop instanceof DFloat64)
+		   return( op(oprtr,lop,(DFloat64)rop));
+		else if (rop instanceof DInt16)
+		   return( op(oprtr,lop,(DInt16)rop));
+		else if (rop instanceof DInt32)
+		   return( op(oprtr,lop,(DInt32)rop));
+		else if (rop instanceof DString)
+		   return( op(oprtr,lop,(DString)rop));
+		else if (rop instanceof DUInt16)
+		   return( op(oprtr,lop,(DUInt16)rop));
+		else if (rop instanceof DUInt32)
+		   return( op(oprtr,lop,(DUInt32)rop));
+		else if (rop instanceof DURL)
+		   return( op(oprtr,lop,(DURL)rop));
+		else
+		    throw new InvalidOperatorException("Binary operations not supported for the type:" + lop.getClass());
+    }
+
+//******************************************************************************************
+//******************************************************************************************
+//******************************************************************************************
+//******************************************************************************************
+
+
+
+
+    private static String opErrorMsg (int oprtr, String lop, String rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: return("Less Than (<) Operator not valid between types" + lop + "and" +  rop + ".");
+            case LESS_EQL: return("Less Than Equal To (<=) Operator not valid between types" + lop + "and" +  rop + ".");
+            case GREATER: return("Greater Than (>) Operator not valid between types" + lop + "and" +  rop + ".");
+            case GREATER_EQL: return("Greater Than Equal To (>=) Operator not valid between types" + lop + "and" +  rop + ".");
+            case EQUAL: return("Equal To (==) Operator not valid between types" + lop + "and" +  rop + ".");
+            case NOT_EQUAL: return("Not Equal To (!=) Operator not valid between types" + lop + "and" +  rop + ".");
+            case REGEXP: return("Regular Expression cannot beevaluated between types" + lop + "and" +  rop + ".");
+            default: return("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+
+//******************************************************************************************
+//***                              Boolean Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+
+    private static boolean op (int oprtr, DBoolean lop, DBoolean rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case EQUAL: return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL: return(lop.getValue() != rop.getValue());
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+ 		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DByte rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() == foo);
+
+            case NOT_EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() != foo);
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DFloat32 rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() == foo);
+
+            case NOT_EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() != foo);
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DFloat64 rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() == foo);
+
+            case NOT_EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() != foo);
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DInt16 rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() == foo);
+
+            case NOT_EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() != foo);
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DInt32 rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() == foo);
+
+            case NOT_EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() != foo);
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DUInt16 rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() == foo);
+
+            case NOT_EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() != foo);
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DUInt32 rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() == foo);
+
+            case NOT_EQUAL: 
+		if(rop.getValue() != 0)
+			foo = true;
+		return(lop.getValue() != foo);
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DBoolean lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+
+//******************************************************************************************
+//***                              Byte Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+        
+    private static boolean op (int oprtr, DByte lop, DBoolean rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+                if(lop.getValue() != 0)
+	                foo = true;
+                return(foo == rop.getValue());
+
+            case NOT_EQUAL: 
+                if(lop.getValue() != 0)
+	                foo = true;
+                return(foo == rop.getValue());
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		        throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DByte rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFF;
+        int rval = ((int)rop.getValue()) & 0xFF;
+        switch(oprtr){
+            case LESS:        return(lval <  rval);
+            case LESS_EQL:    return(lval <= rval);
+            case GREATER:     return(lval >  rval);
+            case GREATER_EQL: return(lval >= rval);
+            case EQUAL:       return(lval == rval);
+            case NOT_EQUAL:   return(lval != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DFloat32 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DFloat64 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DInt16 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DInt32 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DUInt16 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFF;
+        int rval = ((int)rop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:        return(lval <  rval);
+            case LESS_EQL:    return(lval <= rval);
+            case GREATER:     return(lval >  rval);
+            case GREATER_EQL: return(lval >= rval);
+            case EQUAL:       return(lval == rval);
+            case NOT_EQUAL:   return(lval != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DUInt32 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFF;
+        long rval = ((long)rop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:        return(lval <  rval);
+            case LESS_EQL:    return(lval <= rval);
+            case GREATER:     return(lval >  rval);
+            case GREATER_EQL: return(lval >= rval);
+            case EQUAL:       return(lval == rval);
+            case NOT_EQUAL:   return(lval != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DByte lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+        
+//******************************************************************************************
+//***                              Float32 Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+        
+    private static boolean op (int oprtr, DFloat32 lop, DBoolean rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case NOT_EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DByte rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DFloat32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DFloat64 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DUInt16 rop) throws InvalidOperatorException {
+        int rval = ((int)rop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DUInt32 rop) throws InvalidOperatorException {
+        long rval = ((long)rop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat32 lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+        
+//******************************************************************************************
+//***                              Float64 Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+
+    private static boolean op (int oprtr, DFloat64 lop, DBoolean rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case NOT_EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DByte rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DFloat32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DFloat64 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DUInt16 rop) throws InvalidOperatorException {
+        int rval = ((int)rop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DUInt32 rop) throws InvalidOperatorException {
+        long rval = ((long)rop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DFloat64 lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+       
+//******************************************************************************************
+//***                              Int16 Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+
+    private static boolean op (int oprtr, DInt16 lop, DBoolean rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case NOT_EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DByte rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DFloat32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DFloat64 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DUInt16 rop) throws InvalidOperatorException {
+        int rval = ((int)rop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DUInt32 rop) throws InvalidOperatorException {
+        long rval = ((long)rop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt16 lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+       
+//******************************************************************************************
+//***                              Int32 Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+ 
+    private static boolean op (int oprtr, DInt32 lop, DBoolean rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case NOT_EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DByte rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DFloat32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DFloat64 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS:        return(lop.getValue() <  rop.getValue());
+            case LESS_EQL:    return(lop.getValue() <= rop.getValue());
+            case GREATER:     return(lop.getValue() >  rop.getValue());
+            case GREATER_EQL: return(lop.getValue() >= rop.getValue());
+            case EQUAL:       return(lop.getValue() == rop.getValue());
+            case NOT_EQUAL:   return(lop.getValue() != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DUInt16 rop) throws InvalidOperatorException {
+        int rval = ((int)rop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DUInt32 rop) throws InvalidOperatorException {
+        long rval = ((long)rop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lop.getValue() <  rval);
+            case LESS_EQL:     return(lop.getValue() <= rval);
+            case GREATER:      return(lop.getValue() >  rval);
+            case GREATER_EQL:  return(lop.getValue() >= rval);
+            case EQUAL:        return(lop.getValue() == rval);
+            case NOT_EQUAL:    return(lop.getValue() != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DInt32 lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+       
+//******************************************************************************************
+//***                              String Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+    private static boolean op (int oprtr, DString lop, DBoolean rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DByte rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DFloat32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DFloat64 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DString rop) throws InvalidOperatorException, 
+								       RegExpException,
+								       SBHException {
+        int cmp;
+        switch(oprtr){
+            case LESS: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp < 0) 
+		     return(true);
+	        else
+		     return(false);
+            case LESS_EQL:
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp <= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp > 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER_EQL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp >= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp == 0) 
+		    return(true);
+	        else
+		    return(false);
+            case NOT_EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp != 0) 
+		    return(true);
+	        else
+		    return(false);
+            case REGEXP:
+                try {
+                    RE regexp = new RE(lop.getValue());
+		    return(regexp.isMatch(rop.getValue()));
+		}
+		catch (REException e1) {
+                    throw new RegExpException(e1.getMessage());
+		}
+		catch (Exception e2) {
+                    throw new SBHException(e2.getMessage());
+		}
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DUInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DUInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DString lop, DURL rop) throws InvalidOperatorException, 
+								       RegExpException,
+								       SBHException {
+        int cmp;
+        switch(oprtr){
+            case LESS: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp < 0) 
+		     return(true);
+	        else
+		     return(false);
+            case LESS_EQL:
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp <= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp > 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER_EQL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp >= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp == 0) 
+		    return(true);
+	        else
+		    return(false);
+            case NOT_EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp != 0) 
+		    return(true);
+	        else
+		    return(false);
+            case REGEXP:
+                try {
+                    RE regexp = new RE(lop.getValue());
+		    return(regexp.isMatch(rop.getValue()));
+		}
+		catch (REException e1) {
+                    throw new RegExpException(e1.getMessage());
+		}
+		catch (Exception e2) {
+                    throw new SBHException(e2.getMessage());
+		}
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+        
+//******************************************************************************************
+//***                              UInt16 Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+
+    private static boolean op (int oprtr, DUInt16 lop, DBoolean rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case NOT_EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DByte rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DFloat32 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DFloat64 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DInt16 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DInt32 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DUInt16 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFFFF;
+        int rval = ((int)rop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rval);
+            case LESS_EQL:     return(lval <= rval);
+            case GREATER:      return(lval >  rval);
+            case GREATER_EQL:  return(lval >= rval);
+            case EQUAL:        return(lval == rval);
+            case NOT_EQUAL:    return(lval != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DUInt32 rop) throws InvalidOperatorException {
+        int lval = ((int)lop.getValue()) & 0xFFFF;
+        long rval = ((long)rop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lval <  rval);
+            case LESS_EQL:     return(lval <= rval);
+            case GREATER:      return(lval >  rval);
+            case GREATER_EQL:  return(lval >= rval);
+            case EQUAL:        return(lval == rval);
+            case NOT_EQUAL:    return(lval != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt16 lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+       
+//******************************************************************************************
+//***                              UInt32 Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+
+    private static boolean op (int oprtr, DUInt32 lop, DBoolean rop) throws InvalidOperatorException {
+	boolean foo = false;
+        switch(oprtr){
+
+            case EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case NOT_EQUAL: 
+		if(lop.getValue() != 0)
+			foo = true;
+		return(foo == rop.getValue());
+
+            case LESS:  case LESS_EQL: case GREATER: case GREATER_EQL: case REGEXP:
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DByte rop) throws InvalidOperatorException {
+        long lval = ((long)lop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DFloat32 rop) throws InvalidOperatorException {
+        long lval = ((long)lop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DFloat64 rop) throws InvalidOperatorException {
+        long lval = ((long)lop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DInt16 rop) throws InvalidOperatorException {
+        long lval = ((long)lop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DInt32 rop) throws InvalidOperatorException {
+        long lval = ((long)lop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lval <  rop.getValue());
+            case LESS_EQL:     return(lval <= rop.getValue());
+            case GREATER:      return(lval >  rop.getValue());
+            case GREATER_EQL:  return(lval >= rop.getValue());
+            case EQUAL:        return(lval == rop.getValue());
+            case NOT_EQUAL:    return(lval != rop.getValue());
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DString rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DUInt16 rop) throws InvalidOperatorException {
+        long lval = ((long)lop.getValue()) & 0xFFFFFFFFL;
+        int rval = ((int)rop.getValue()) & 0xFFFF;
+        switch(oprtr){
+            case LESS:         return(lval <  rval);
+            case LESS_EQL:     return(lval <= rval);
+            case GREATER:      return(lval >  rval);
+            case GREATER_EQL:  return(lval >= rval);
+            case EQUAL:        return(lval == rval);
+            case NOT_EQUAL:    return(lval != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DUInt32 rop) throws InvalidOperatorException {
+        long lval = ((long)lop.getValue()) & 0xFFFFFFFFL;
+        long rval = ((long)rop.getValue()) & 0xFFFFFFFFL;
+        switch(oprtr){
+            case LESS:         return(lval <  rval);
+            case LESS_EQL:     return(lval <= rval);
+            case GREATER:      return(lval >  rval);
+            case GREATER_EQL:  return(lval >= rval);
+            case EQUAL:        return(lval == rval);
+            case NOT_EQUAL:    return(lval != rval);
+            case REGEXP: throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DUInt32 lop, DURL rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+
+
+       
+//******************************************************************************************
+//***                              URL Vs The World                                 ****
+//------------------------------------------------------------------------------------------
+    private static boolean op (int oprtr, DURL lop, DBoolean rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DByte rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DFloat32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DFloat64 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DString rop) throws InvalidOperatorException, 
+								       RegExpException,
+								       SBHException {
+        int cmp;
+        switch(oprtr){
+            case LESS: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp < 0) 
+		     return(true);
+	        else
+		     return(false);
+            case LESS_EQL:
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp <= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp > 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER_EQL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp >= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp == 0) 
+		    return(true);
+	        else
+		    return(false);
+            case NOT_EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp != 0) 
+		    return(true);
+	        else
+		    return(false);
+            case REGEXP:
+                try {
+                    RE regexp = new RE(lop.getValue());
+		    return(regexp.isMatch(rop.getValue()));
+		}
+		catch (REException e1) {
+                    throw new RegExpException(e1.getMessage());
+		}
+		catch (Exception e2) {
+                    throw new SBHException(e2.getMessage());
+		}
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DUInt16 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DUInt32 rop) throws InvalidOperatorException {
+        switch(oprtr){
+            case LESS: case LESS_EQL: case GREATER: case GREATER_EQL: case EQUAL: case NOT_EQUAL: case REGEXP:  
+		throw new InvalidOperatorException(opErrorMsg(oprtr,lop.getTypeName(),rop.getTypeName()));
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    private static boolean op (int oprtr, DURL lop, DURL rop) throws InvalidOperatorException, 
+								       RegExpException,
+								       SBHException {
+        int cmp;
+        switch(oprtr){
+            case LESS: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp < 0) 
+		     return(true);
+	        else
+		     return(false);
+            case LESS_EQL:
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp <= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp > 0) 
+		     return(true);
+	        else
+		     return(false);
+            case GREATER_EQL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp >= 0) 
+		     return(true);
+	        else
+		     return(false);
+            case EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp == 0) 
+		    return(true);
+	        else
+		    return(false);
+            case NOT_EQUAL: 
+    	        cmp = lop.getValue().compareTo(rop.getValue());
+	        if (cmp != 0) 
+		    return(true);
+	        else
+		    return(false);
+            case REGEXP:
+                try {
+                    RE regexp = new RE(lop.getValue());
+		    return(regexp.isMatch(rop.getValue()));
+		}
+		catch (REException e1) {
+                    throw new RegExpException(e1.getMessage());
+		}
+		catch (Exception e2) {
+                    throw new SBHException(e2.getMessage());
+		}
+
+            default: throw new InvalidOperatorException("Unknown Operator Requested! RTFM!");
+    	}
+    }
+    
+
+}
diff --git a/dods/dap/Server/RegExpException.java b/dods/dap/Server/RegExpException.java
new file mode 100644
index 0000000..378c887
--- /dev/null
+++ b/dods/dap/Server/RegExpException.java
@@ -0,0 +1,72 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+// $Log: not supported by cvs2svn $
+// Revision 1.5  2001/02/04 01:44:48  ndp
+// Cleaned up javadoc errors
+//
+// Revision 1.4  1999/09/24 23:17:58  ndp
+// Added new constructors to all Exception classes
+//
+// Revision 1.3  1999/09/24 21:59:23  ndp
+// Reorged Exceptions. Code compiles.
+//
+// Revision 1.2  1999/09/16 23:25:50  ndp
+// *** empty log message ***
+//
+// Revision 1.1  1999/09/16 22:04:29  ndp
+// Moved RegExpSyntaxError.java to RegExpException.java
+//
+// Revision 1.2  1999/09/16 19:05:55  ndp
+// Moved SomethingBadHappenedException to SBHException
+//
+// Revision 1.1  1999/09/16 18:28:55  ndp
+// Added RegExpSyntaxError.java
+//
+// Revision 1.3  1999/08/20 22:59:43  jimg
+// Changed the package declaration to dods.dap.Server so that it matches the
+// directory name.
+//
+
+package dods.dap.Server;
+import dods.dap.DODSException;
+
+/**
+ * Thrown by <code>Operator.op</code> when an attempt is made to parse a
+ * improperly formed regular expression. Reular expressions should use
+ * the same syntax as <i>grep</i>.
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see Operator#op(int,BaseType, BaseType)
+ */
+public class RegExpException extends SDODSException {
+  /**
+   * Construct a <code>RegExpException</code> with the specified
+   * detail message.
+   *
+   * @param s the detail message.
+   */
+  public RegExpException(String s) {
+    super(DODSException.MALFORMED_EXPR,"Syntax Error In Regular Expression: " + s);
+  }
+
+
+  /**
+   * Construct a <code>RegExpException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public RegExpException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/Server/RelOpClause.java b/dods/dap/Server/RelOpClause.java
new file mode 100644
index 0000000..2c872eb
--- /dev/null
+++ b/dods/dap/Server/RelOpClause.java
@@ -0,0 +1,134 @@
+package dods.dap.Server;
+
+import java.util.*;
+import dods.dap.parser.ExprParserConstants; // used only for toString()
+import dods.dap.BaseType;
+import dods.dap.Server.ServerDDS;
+import dods.dap.Server.Operator;
+
+/** Represents a clause which compares subclauses, using one of the
+ *  relative operators supported by the Operator class.
+ * @see Operator
+ * @see ClauseFactory
+ * @author joew */
+public class RelOpClause 
+    extends AbstractClause
+    implements TopLevelClause {
+    
+    /** Creates a new RelOpClause. If the lhs and all the elements of the rhs
+     *  are constant, the RelOpClause will be flagged as constant, and 
+     *  evaluated immediately.
+     *
+     * @param operator The operator invoked by the clause
+     * @param lhs The left-hand side of the comparison.
+     * @param rhs A list of SubClauses representing the right-hand side of the
+     * comparison. 
+     * @exception SDODSException Thrown if the clause is constant, but 
+     *            the attempt to evaluate it fails. 
+     */
+    protected RelOpClause(int operator, 
+			  SubClause lhs, 
+			  List rhs) 
+	throws SDODSException {
+
+	this.operator = operator;
+	this.lhs = lhs;
+	this.rhs = rhs;
+	this.children = new ArrayList();
+	children.add(lhs);
+	children.addAll(rhs);
+	this.constant = true;
+	Iterator it = children.iterator();
+	while (it.hasNext()) {
+	    SubClause current = (SubClause)it.next();
+	    current.setParent(this);
+	    if (!current.isConstant()) {
+		constant = false;
+	    }
+	}
+	if (constant) {
+	    evaluate();
+	} 
+    }
+
+
+    public boolean getValue() {
+	return value;
+    }
+
+    public boolean evaluate()
+	throws SDODSException {
+
+	if (constant && defined) {
+	    return value;
+	}
+
+	if (rhs.size() == 1) {
+	    value = Operator.op(operator, 
+				lhs.evaluate(), 
+				((SubClause)rhs.get(0)).evaluate());
+	} else {
+	    value = false;
+	    Iterator it = rhs.iterator();
+	    while (it.hasNext() && !value) {
+		if (Operator.op(operator, 
+				lhs.evaluate(), 
+				((SubClause)it.next()).evaluate())) {
+		    value = true;
+		}
+	    }
+	}
+	defined = true;
+	return value;
+    }
+
+    /**  Returns a SubClause representing the right-hand side of the
+      *  comparison. 
+     */
+    public SubClause getLHS() {
+	return lhs;
+    }
+
+    /**  Returns a list of SubClauses representing the right-hand side of the
+      *  comparison. 
+     */
+    public List getRHS() {
+	return rhs;
+    }
+
+    /** Returns the type of comparison 
+     * @see dods.dap.parser.ExprParserConstants */
+    public int getOperator() {
+	return operator;
+    }
+
+    /** Prints the original string representation of this clause.
+     *  For use in debugging.
+     */
+    public String toString() {
+	StringBuffer buf = new StringBuffer();
+	buf.append(lhs.toString());
+	buf.append(ExprParserConstants.tokenImage[operator].substring(2,3));
+	if (rhs.size() == 1) {
+	    buf.append(rhs.get(0).toString());
+	} else {
+	    buf.append("{");
+	    Iterator it = rhs.iterator();
+	    buf.append(it.next());
+	    while (it.hasNext()) {
+		buf.append(",");
+		buf.append(it.next().toString());
+	    }
+	    buf.append("}");
+	}
+	return buf.toString();
+    }
+
+    protected boolean value;
+
+    protected int operator;
+
+    protected SubClause lhs;
+
+    protected List rhs;
+}
diff --git a/dods/dap/Server/RelOps.java b/dods/dap/Server/RelOps.java
new file mode 100644
index 0000000..530c776
--- /dev/null
+++ b/dods/dap/Server/RelOps.java
@@ -0,0 +1,33 @@
+
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, University of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: James Gallagher <jgallagher at gso.uri.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.BaseType;
+
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DByte won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperator.
+    @version $Revision: 1.3 $
+    @author jhrg */
+
+public interface RelOps {
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+}
+    
diff --git a/dods/dap/Server/SBHException.java b/dods/dap/Server/SBHException.java
new file mode 100644
index 0000000..f7b2044
--- /dev/null
+++ b/dods/dap/Server/SBHException.java
@@ -0,0 +1,78 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+// $Log: not supported by cvs2svn $
+// Revision 1.6  2001/02/04 01:44:48  ndp
+// Cleaned up javadoc errors
+//
+// Revision 1.5  1999/11/20 07:29:03  jimg
+// Added new behavior for the Project property
+//
+// Revision 1.4  1999/09/24 23:17:58  ndp
+// Added new constructors to all Exception classes
+//
+// Revision 1.3  1999/09/24 21:59:24  ndp
+// Reorged Exceptions. Code compiles.
+//
+// Revision 1.2  1999/09/16 19:17:59  ndp
+// *** empty log message ***
+//
+// Revision 1.1  1999/09/16 19:06:25  ndp
+// Moved SomethingBadHappenedException to SBHException
+//
+// Revision 1.1  1999/09/16 18:56:06  ndp
+//  Added SomethingBadHappenedException.java
+//
+// Revision 1.3  1999/08/20 22:59:43  jimg
+// Changed the package declaration to dods.dap.Server so that it matches the
+// directory name.
+//
+
+package dods.dap.Server;
+import java.lang.String;
+import dods.dap.DODSException;
+
+/**
+ * The Something Bad Happened (SBH) Exception.
+ * This gets thrown in situations where something
+ * pretty bad went down and we don't have a good
+ * exception type to describe the problem, or
+ * we don't really know what the hell is going on.
+ * <p>
+ * Yes, its the garbage dump of our exception
+ * classes.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ */
+public class SBHException extends SDODSException {
+  /**
+   * Construct a <code>SBHException</code> with the specified
+   * detail message.
+   *
+   * @param s the detail message.
+   */
+  public SBHException(String s) {
+    super(DODSException.UNKNOWN_ERROR,"Ow! Something Bad Happened! All I know is: " + s);
+  }
+
+
+  /**
+   * Construct a <code>SBHException</code> with the specified
+   * message and DODS error code see (<code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public SBHException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/Server/SDArray.java b/dods/dap/Server/SDArray.java
new file mode 100644
index 0000000..57a822e
--- /dev/null
+++ b/dods/dap/Server/SDArray.java
@@ -0,0 +1,476 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import java.io.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import dods.dap.*;
+
+/**
+ * Holds a DODS Server <code>Array</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDArray extends DArray implements ServerArrayMethods, RelOps {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+
+
+    /** Constructs a new <code>SDArray</code>. */
+    public SDArray() {
+	super();
+	Project = false;
+	Synthesized = false;
+	ReadMe = false;
+
+    }
+
+    /**
+     * Constructs a new <code>SDArray</code> with name <code>n</code>.
+     * @param n the name of the variable. */
+    public SDArray(String n) {
+	super(n);
+	Project = false;
+	Synthesized = false;
+	ReadMe = false;
+    }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean, boolean)
+    */
+    public void printDecl(PrintWriter os, String space, boolean print_semi,
+			  boolean constrained) {
+        if(constrained && !Project)
+	        return;
+
+	// BEWARE! Since printDecl()is (multiple) overloaded in BaseType and
+	// all of the different signatures of printDecl() in BaseType lead to
+	// one signature, we must be careful to override that SAME signature
+	// here. That way all calls to printDecl() for this object lead to
+	// this implementation.
+
+	// Also, since printDecl()is (multiple) overloaded in BaseType and
+	// all of the different signatures of printDecl() in BaseType lead to
+	// the signature we are overriding here, we MUST call the printDecl
+	// with the SAME signature THROUGH the super class reference
+	// (assuming we want the super class functionality). If we do
+	// otherwise, we will create an infinte call loop. OOPS!
+
+
+
+	//os.println("SDArray.printDecl()");
+
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+
+        if(!Project)
+	        return;
+
+
+        PrimitiveVector pv = getPrimitiveVector();
+
+        if(pv instanceof BaseTypePrimitiveVector){
+
+            BaseTypePrimitiveVector vals = (BaseTypePrimitiveVector)pv;
+
+            if (print_decl_p) {
+                printDecl(os, space, false,true);
+                os.print(" = ");
+            }
+
+            os.print("{ ");
+            //vals.printVal(os, "");
+
+            ServerMethods sm;
+            int len = vals.getLength();
+            for(int i=0; i<len-1; i++) {
+                sm = (ServerMethods)vals.getValue(i);
+                if(sm.isProject()){
+                    ((BaseType)sm).printVal(os, "", false);
+                    os.print(", ");
+                }
+            }
+            // print last value, if any, without trailing comma
+            if(len > 0) {
+                sm = (ServerMethods)vals.getValue(len-1);
+                if(sm.isProject()){
+                    ((BaseType)sm).printVal(os, "", false);
+                }
+            }
+
+
+            os.print("}");
+
+            if (print_decl_p)
+                os.println(";");
+            }
+        else {
+            super.printVal(os,space,print_decl_p);
+        }
+
+    }
+
+    /**
+      * Given a size and a name, this function adds a dimension to the array.
+      * For example, if the <code>SDArray</code> is already 10 elements long,
+      * calling <code>appendDim</code> with a size of 5 will transform the
+      * array into a 10x5 matrix. Calling it again with a size of 2 will
+      * create a 10x5x2 array, and so on. This overloads
+      * <code>appendDim</code> of <code>DArray</code> so that projection
+      * information need for the server side implementation can be handled.
+      *
+      * @param size the size of the desired new dimension.
+      * @param name the name of the new dimension. */
+    public void appendDim(int size, String name) {
+        // Add the new dimension to the core array
+        super.appendDim(size,name);
+    }
+
+     /**
+      * Add a dimension to the array. Same as <code>appendDim(size,
+      * null)</code>.
+      * @param size the size of the desired new dimension.
+      * @see DArray#appendDim(int, String) */
+    public final void appendDim(int size) {
+        this.appendDim(size, null);
+    }
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;
+
+
+        PrimitiveVector vals = getPrimitiveVector();
+
+		( (ServerMethods)(vals.getTemplate())).setProject(state,all);
+
+//		if(vals instanceof BaseTypePrimitiveVector){
+//			BaseTypePrimitiveVector btpv = (BaseTypePrimitiveVector)vals;
+//			System.out.println("Setting Projection for Array member: "
+//					+ btpv.getBaseType().getTypeName() + " "
+//					+ btpv.getBaseType().getName());
+//			((ServerMethods)btpv.getBaseType()).setProject(state, all);
+//		}
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+		setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator
+        @see #setProject(boolean)
+        @see #setProject(boolean,boolean)
+        @see #setProjection(int,int,int,int)
+     */
+    public boolean isProject(){
+        return(Project);
+    }
+
+    // RelOps Interface
+
+    /** The RelOps interface defines how each type responds to relational
+	operators. Most (all?) types will not have sensible responses to all
+	of the relational operators (e.g. DArray won't know how to match a
+	regular expression but DString will). For those operators that are
+	nonsensical a class should throw InvalidOperatorException. */
+
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Equals (=) operator does not work with the type SDArray!");
+    }
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Not Equals (!=) operator does not work with the type SDArray!");
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Greater Than (>)operator does not work with the type SDArray!");
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("GreaterThan or equals (<=) operator does not work with the type SDArray!");
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan (<) operator does not work with the type SDArray!");
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan oe equals (<=) operator does not work with the type SDArray!");
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Regular Expression's don't work with the type SDArray!");
+    }
+
+    // FileIO Interface
+
+    /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise.
+    */
+    public boolean isSynthesized() {
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state) {
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead() {
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable.
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+
+
+    /**
+    * <p>
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>). This does not send the entire class as the Java
+    * <code>Serializable</code> interface does, rather it sends only the
+    * binary data values. Other software is responsible for sending variable
+    * type information (see <code>DDS</code>).
+    * </p><p>
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    * </p>
+    * <h2>Caution:</h2>
+    * When serializing arrays of sequences (children of DSequence) it is crucial
+    * that it be handled with great care. Sequences have been implemented so that
+    * only one instance (or row if you will) is retained in memory at a given time.
+    * In order to correctly serialize an array of sequences the read() method for the
+    * array must create an instance of the sequence for each member of the array, typically
+    * by repeatedly cloning the template variable in the PrimitiveVector. The important next
+    * step is to NOT attempt to read any data into the sequences from within the read()
+    * method of the parent array. The sequence's data will get read, and constraint expressions
+    * applied when the serialze() method of the array calls the serialize method of the
+    * sequence. Good Luck!
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @param ce a <code>CEEvaluator</code> containing constraint Clauses.
+    * @param specialO a <code>Object</code> to be used by <code>ServerMethods.read()</code>
+    * @exception IOException thrown on any <code>OutputStream</code>
+    * exception.
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO)
+	throws NoSuchVariableException,SDODSException, IOException {
+        PrimitiveVector vals = getPrimitiveVector();
+
+	if(!isRead())
+	    read(dataset, specialO);
+
+	if(vals.getTemplate() instanceof DSequence || ce.evalClauses(specialO)) {
+	    // Because arrays of primitive types (ie int32, float32, byte,
+	    // etc) are handled in the C++ core using the XDR package we must
+	    // write the length twice for those types. For BaseType vectors,
+	    // we should write it only once. This is in effect a work around
+	    // for a bug in the C++ core as the C++ core does not consume 2
+	    // length values for thge BaseType vectors. Bummer...
+	    int length = vals.getLength();
+	    sink.writeInt(length);
+
+	    // Gotta check for this to make sure that DConstructor types
+	    // (Especially SDSequence) get handled correctly!!!
+	    if(vals instanceof BaseTypePrimitiveVector){
+		for(int i=0; i<length ;i++){
+		    ServerMethods sm = (ServerMethods)
+			((BaseTypePrimitiveVector)vals).getValue(i);
+		    sm.serialize(dataset,sink,ce, specialO);
+		}
+	    }
+	    else {
+		// Because both XDR and DODS read the length, we must write
+		// it twice.
+		sink.writeInt(length);
+		vals.externalize(sink);
+	    }
+	}
+    }
+
+    // Array Projection Interface ----------------------------
+
+    /** Set the projection information for this dimension. The
+      * <code>DArrayDimension</code> associated with the
+      * <code>dimension</code> specified is retrieved and the
+      * <code>start</code> <code>stride</code> and <code>stop</code>
+      * parameters are passed to its <code>setProjection()</code> method.
+      *
+      * @param dimension The dimension that is to be modified.
+      * @param start The starting point for the projection of this
+      * <code>DArrayDimension</code>.
+      * @param stride The size of the stride for the projection of this
+      * <code>DArrayDimension</code>.
+      * @param stop The stopping point for the projection of this
+      * <code>DArrayDimension</code>.
+      *
+      * @see DArray
+      * @see DArrayDimension */
+    public void setProjection(int dimension, int start, int stride, int stop)
+	throws InvalidParameterException  {
+        DArrayDimension d = getDimension(dimension);
+        d.setProjection(start,stride,stop);
+    }
+
+    /** Gets the <b>start</b> value for the array projection. The parameter
+      * <code>dimension</code> is checked against the instance of the
+      * <code>SDArray</code> for bounds violation. */
+    public int  getStart(int dimension) throws InvalidParameterException {
+        DArrayDimension d = getDimension(dimension);
+        return(d.getStart());
+    }
+
+
+    /** Gets the <b>stride</b> value for the array projection. The parameter
+      * <code>dimension</code> is checked against the instance of the
+      * <code>SDArray</code> for bounds violation. */
+    public int  getStride(int dimension) throws InvalidParameterException {
+        DArrayDimension d = getDimension(dimension);
+        return(d.getStride());
+    }
+
+    /** Gets the <b>stop</b> value for the array projection. The parameter
+      * <code>dimension</code> is checked against the instance of the
+      * <code>SDArray</code> for bounds violation. */
+    public int getStop(int dimension) throws InvalidParameterException {
+        DArrayDimension d = getDimension(dimension);
+        return(d.getStop());
+    }
+}
+
diff --git a/dods/dap/Server/SDBoolean.java b/dods/dap/Server/SDBoolean.java
new file mode 100644
index 0000000..0276897
--- /dev/null
+++ b/dods/dap/Server/SDBoolean.java
@@ -0,0 +1,303 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+import dods.dap.NoSuchVariableException;
+
+/**
+ * Holds a DODS Server <code>Boolean</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDBoolean extends DBoolean implements ServerMethods, RelOps, ExprParserConstants {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDBoolean</code>. */
+  public SDBoolean() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+
+  /**
+   * Constructs a new <code>SDBoolean</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDBoolean(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to one signature, we must be careful to override that
+        // SAME signature here. That way all calls to printDecl() for 
+        // this object lead to this implementation.
+
+        // Also, since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to the signature we are overriding here, we MUST call
+        // the printDecl with the SAME signature THROUGH the super class
+        // reference (assuming we want the super class functionality). If
+        // we do otherwise, we will create an infinte call loop. OOPS!
+
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+     */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+        
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+ 
+    
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+
+    
+	    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                            throws  NoSuchVariableException, SDODSException, IOException {
+
+        if(!isRead())
+            read(dataset,specialO);
+
+        if(ce.evalClauses(specialO))
+            externalize(sink);
+	
+    }
+	
+}
diff --git a/dods/dap/Server/SDByte.java b/dods/dap/Server/SDByte.java
new file mode 100644
index 0000000..694b908
--- /dev/null
+++ b/dods/dap/Server/SDByte.java
@@ -0,0 +1,302 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+import dods.dap.NoSuchVariableException;
+
+/**
+ * Holds a DODS Server <code>Byte</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDByte extends DByte implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDByte</code>. */
+  public SDByte() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDByte</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDByte(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DByte won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+    
+    
+    
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #setRead(boolean)
+	@see #read(String, Object)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                            throws  NoSuchVariableException, SDODSException, IOException {
+
+        if(!isRead())
+            read(dataset,specialO);
+
+        if(ce.evalClauses(specialO))
+            externalize(sink);
+	
+    }
+
+}
diff --git a/dods/dap/Server/SDFloat32.java b/dods/dap/Server/SDFloat32.java
new file mode 100644
index 0000000..3e1c280
--- /dev/null
+++ b/dods/dap/Server/SDFloat32.java
@@ -0,0 +1,305 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+
+/**
+ * Holds a DODS Server <code>Float32</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDFloat32 extends DFloat32 implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDFloat32</code>. */
+  public SDFloat32() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDFloat32</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDFloat32(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+        
+    
+
+    
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                            throws NoSuchVariableException, SDODSException, IOException {
+ 	
+        if(!isRead())
+            read(dataset, specialO);
+
+        if(ce.evalClauses(specialO))
+            externalize(sink);
+
+
+    }
+    
+}
diff --git a/dods/dap/Server/SDFloat64.java b/dods/dap/Server/SDFloat64.java
new file mode 100644
index 0000000..1544188
--- /dev/null
+++ b/dods/dap/Server/SDFloat64.java
@@ -0,0 +1,303 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+
+/**
+ * Holds a DODS Server <code>Float64</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDFloat64 extends DFloat64 implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDFloat64</code>. */
+  public SDFloat64() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDFloat64</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDFloat64(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+        
+    
+
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String,Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String,Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String,Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                            throws NoSuchVariableException, SDODSException, IOException {
+	
+        if(!isRead())
+            read(dataset, specialO);
+	    
+        if(ce.evalClauses(specialO))
+            externalize(sink);
+	    		
+    }
+    
+}
diff --git a/dods/dap/Server/SDGrid.java b/dods/dap/Server/SDGrid.java
new file mode 100644
index 0000000..140571d
--- /dev/null
+++ b/dods/dap/Server/SDGrid.java
@@ -0,0 +1,606 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import dods.dap.BaseType;
+import dods.dap.DArray;
+import dods.dap.DArrayDimension;
+import dods.dap.DDS;
+import dods.dap.DGrid;
+import dods.dap.NoSuchVariableException;
+
+/**
+ * Holds a DODS Server <code>Grid</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDGrid extends DGrid implements ServerArrayMethods, RelOps {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    Vector AP;
+
+    /** Constructs a new <code>SDGrid</code>. */
+    public SDGrid() { 
+        super(); 
+        Project = false;
+        Synthesized = false;
+        ReadMe = false;
+    }
+
+    /**
+    * Constructs a new <code>SDGrid</code> with name <code>n</code>.
+    * @param n the name of the variable.
+    */
+    public SDGrid(String n) { 
+        super(n); 
+        Project = false;
+        Synthesized = false;
+        ReadMe = false;
+    }
+
+
+    /** 
+     * When projected (using whatever the current constraint provides in the way
+     * of a projection) am I still a Grid?
+     *
+     * @return True if projected grid is still a grid. False otherwise.
+     */
+    
+    public boolean projectionYieldsGrid() {
+
+        // For each dimension in the Array part, check the corresponding Map
+        // vector to make sure it is present in the projected Grid. If for each
+        // projected dimension in the Array component, there is a matching Map
+        // vector, then the Grid is valid.
+        boolean valid = true;
+	
+        // Don't bother checking if the Array component is not included.
+        if (!((SDArray)arrayVar).isProject())
+            return false;
+
+        Enumeration aDims = arrayVar.getDimensions();
+        Enumeration e = mapVars.elements();
+	
+        while (valid && e.hasMoreElements() && aDims.hasMoreElements() ) {
+
+            DArrayDimension thisDim = (DArrayDimension)aDims.nextElement();
+		    SDArray mapArray = (SDArray)e.nextElement();
+		    DArrayDimension thisMap = mapArray.getFirstDimension();
+		
+            if (thisDim.getSize()>0) {
+			    // System.out.println("Dimension Contains Data.");
+	    
+	            if ( mapArray.isProject()){ // This map vector better be projected!
+			        // System.out.println("Map Vector Projected, checking projection image...");
+	    
+                    // Check the matching Map vector; the Map projection must equal
+                    // the Array dimension projection
+		
+                    valid = thisMap.getStart()  == thisDim.getStart() && 
+		                    thisMap.getStop()   == thisDim.getStop() &&
+				            thisMap.getStride() == thisDim.getStride();
+		        }
+			    else {
+			        // System.out.println("Map Vector not Projected.");
+			        valid = false;
+				}
+
+   		    }
+		    else {
+			    // System.out.println("Dimension empty. Verifing cooresponding Map vector not projected...");
+                // Corresponding Map vector must be excluded from the
+		        // projection or it's not a grid.			
+                valid = !mapArray.isProject();
+            }
+	    
+        }
+
+        if( e.hasMoreElements() != aDims.hasMoreElements())
+	        valid = false;
+		
+        return valid;
+    }
+
+    /**
+     * How many prohected components of this Grid object?
+     * @return The number of projected components.
+     */
+    
+    public int projectedComponents(boolean constrained) {
+        int comp;
+
+        if (constrained) {
+	        comp = ((SDArray)arrayVar).isProject() ? 1: 0;
+
+            Enumeration e = mapVars.elements();
+	    
+	        while(e.hasMoreElements()){
+                if ( ((SDArray)e.nextElement()).isProject() )
+                    comp++;
+		    }
+        }
+        else {
+	        comp = 1 + mapVars.size();
+        }
+
+        return comp;
+    }
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean)
+    */
+    public void printDecl(PrintWriter os, String space, boolean print_semi, boolean constrained) {
+
+        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to one signature, we must be careful to override that
+        // SAME signature here. That way all calls to printDecl() for 
+        // this object lead to this implementation.
+
+
+        boolean isSingle    = false;
+        boolean isStructure = false;
+        boolean isGrid      = false;
+        boolean psemi       = true;
+
+
+        //os.println("The gird contains "+projectedComponents(true)+" projected components");
+
+        if(constrained && projectedComponents(true)==0)
+            return;
+	    
+        // If we are printing the declaration of a constrained Grid then check for
+        // the case where the projection removes all but one component; the
+        // resulting object is a simple array.
+	
+        if (constrained && projectedComponents(true) == 1) {
+	        //os.println("It's a single Array.");
+            isSingle = true;
+            psemi = print_semi;
+        }
+        // If there are M (< N) componets (Array and Maps combined) in a N
+        // component Grid, send the M components as elements of a Struture.
+        // This will preserve the grouping without violating the rules for a
+        // Grid. 
+        else if (constrained && !projectionYieldsGrid()) {
+	        //os.println("It's a Structure.");
+	        isStructure = true;
+        }
+        else {
+            // The number of elements in the (projected) Grid must be such that
+            // we have a valid Grid object; send it as such.
+	        //os.println("It's a Grid.");
+	        isGrid = true;
+        }
+			
+        if(isGrid) os.println(space + getTypeName() + " {" );
+        if(isGrid) os.println(space + " ARRAY:");
+ 		   
+        if(isStructure) os.println(space + "Structure {");
+	
+        ((SDArray)arrayVar).printDecl(os, space + (isSingle ? "":"    ") , psemi, constrained);	
+
+        if(isGrid) os.println(space + " MAPS:");
+        for(Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
+            SDArray sda = (SDArray)e.nextElement();
+            sda.printDecl(os, space + (isSingle ? "":"    ") , psemi, constrained);
+        }
+
+        if(isStructure || isGrid){
+	        os.print(space + "} " + getName());
+            if (print_semi)
+                os.println(";");
+		}
+		
+        return;
+	}
+
+
+      
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+	    
+        if( !isProject() )
+	        return;
+		
+        //System.out.println("\nSome Part of this object is projected...");
+
+        if (print_decl_p) {
+            printDecl(os, space, false, true);
+            os.print(" = ");
+        }
+
+        boolean isStillGrid = projectionYieldsGrid();
+	
+        os.print("{ ");
+        if(isStillGrid) os.print("ARRAY: ");
+
+        if( ((SDArray)arrayVar).isProject())
+	        arrayVar.printVal(os, "", false);
+
+        if(isStillGrid) os.print(" MAPS: ");
+	
+	    boolean firstPass = true;
+        Enumeration e = mapVars.elements();
+        while( e.hasMoreElements() ) {
+	
+            SDArray sda = (SDArray)e.nextElement();
+	
+            if( ((SDArray)sda).isProject() ){
+		        if (!firstPass) os.print(", ");
+		        sda.printVal(os, "", false);
+			    firstPass = false;
+			}
+	
+        }
+        os.print(" }");
+
+        if (print_decl_p)
+            os.println(";");
+
+    }
+
+
+    /**
+    * Adds a variable to the container. This overrides the same method in
+    * the parent class <code>DGrid</code> in order to add array projection
+    * functionality.
+    * @param v the variable to add.
+    * @param part the part of the <code>DGrid</code> to be modified.  Allowed
+    *    values are <code>ARRAY</code> or <code>MAPS</code>.
+    * @exception IllegalArgumentException if an invalid part was given. */
+    public void addVariable(BaseType v, int part) {
+        super.addVariable(v,part);	       
+    }
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all If <code>true</code>, set the Project property of all the
+	members (and their children, and so on).
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+	    if (all) {
+	        // System.out.println("SDGrid:setProject: Blindly setting Project");
+	        ((SDArray)arrayVar).setProject(state);
+
+	        for (Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
+		    ServerMethods sm = (ServerMethods)e.nextElement();
+		    sm.setProject(state);
+	        }
+	    }
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. <p>
+	This is equivalent to setProjection(<code>state</code>,
+	<code>true</code>).
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+        @see #setProjection(int,int,int,int)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DGrid won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Equals (=) operator does not work with the type SDGrid!");
+    }
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Not Equals (!=) operator does not work with the type SDGrid!");
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Greater Than (>)operator does not work with the type SDGrid!");
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("GreaterThan or equals (<=) operator does not work with the type SDGrid!");
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan (<) operator does not work with the type SDGrid!");
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan oe equals (<=) operator does not work with the type SDGrid!");
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Regular Expression's don't work with the type SDGrid!");
+    }
+    
+    
+    
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+     /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+						
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+	                                    throws NoSuchVariableException, SDODSException, IOException {
+	
+        if(!isRead())
+            read(dataset,specialO);
+
+        if(ce.evalClauses(specialO)) {
+            if (((ServerMethods)arrayVar).isProject())
+                ((ServerMethods)arrayVar).serialize(dataset, sink, ce, specialO);
+
+            for (Enumeration e = mapVars.elements(); e.hasMoreElements(); ) {
+                ServerMethods sm = (ServerMethods)e.nextElement();
+                if (sm.isProject())
+                    sm.serialize(dataset, sink, ce, specialO);
+            }
+        }
+    }
+
+//---------------------------------------------------------------------------------
+//------------------------- Array Projection Interface ----------------------------
+//..................................................................................
+
+    /** Set the projection information for this dimension. The internal <code>DArray</code> is
+      * retrieved and then the <code>DArrayDimension</code> 
+      * associated with the <code>dimension</code> specified is retrieved and the <code>start</code> 
+      * <code>stride</code> and <code>stop</code> parameters are passed to its 
+      * <code>setProjection()</code> method.
+      *
+      * @param dimension The dimension that is to be modified.
+      * @param start The starting point for the projection of this <code>DArrayDimension</code>.
+      * @param stride The size of the stride for the projection of this <code>DArrayDimension</code>.
+      * @param stop The stopping point for the projection of this <code>DArrayDimension</code>.
+      *
+      * @see DArray
+      * @see DArrayDimension
+      */
+    public void setProjection(int dimension, int start, int stride, int stop) 
+	throws InvalidParameterException, SBHException  {
+        try {
+            DArray a = (DArray)getVar(0);
+            DArrayDimension d = a.getDimension(dimension);
+            d.setProjection(start,stride,stop);
+	    
+	    DArray map = (DArray)getVar(dimension + 1);
+            DArrayDimension mapD = map.getDimension(0);
+            mapD.setProjection(start,stride,stop);
+	        
+	    }
+	    catch (NoSuchVariableException e){
+	        throw new InvalidParameterException("SDGrid.setProjection(): Bad Value for dimension!: " 
+						    + e.getMessage());
+	    }
+    }
+
+        
+    /** Gets the <b>start</b> value for the projection of the
+      * <code>dimension</code> indicated. The parameter
+      * <code>dimension</code> is checked against the instance of the
+      * <code>SDArray</code> for bounds violation.
+      * @param dimension The dimension from whose projection to retrieve the
+      * <code>start</code> value. */ 
+    public int  getStart(int dimension) throws InvalidParameterException {
+        try{
+            DArray a = (DArray)getVar(0);
+            DArrayDimension d = a.getDimension(dimension);
+            return(d.getStart());
+	    }
+	    catch (NoSuchVariableException e){
+	        throw new InvalidParameterException("SDGrid.getStart(): Bad Value for dimension!: " 
+		                                            + e.getMessage());
+		}
+    }
+
+
+    /** Gets the <b>stride</b> value for the projection of the
+      * <code>dimension</code> indicated. The parameter
+      * <code>dimension</code> is checked against the instance of the
+      * <code>SDArray</code> for bounds violation.
+      * @param dimension The dimension from whose projection to retrieve the
+      * <code>stride</code> value. */ 
+    public int  getStride(int dimension) throws InvalidParameterException {
+        try{
+            DArray a = (DArray)getVar(0);
+            DArrayDimension d = a.getDimension(dimension);
+            return(d.getStride());
+	    }
+	    catch (NoSuchVariableException e){
+	        throw new InvalidParameterException("SDGrid.getStride(): Bad Value for dimension!: " 
+		                                                + e.getMessage());
+		}
+    }
+
+
+    /** Gets the <b>stop</b> value for the projection of the
+      * <code>dimension</code> indicated. The parameter
+      * <code>dimension</code> is checked against the instance of the
+      * <code>SDArray</code> for bounds violation.
+      * @param dimension The dimension from whose projection to retrieve the
+      * <code>stop</code> value. */ 
+    public int getStop(int dimension) throws InvalidParameterException {
+        try{
+            DArray a = (DArray)getVar(0);
+            DArrayDimension d = a.getDimension(dimension);
+            return(d.getStop());
+	    }
+	    catch (NoSuchVariableException e){
+	        throw new InvalidParameterException("SDGrid.getStop(): Bad Value for dimension!: " 
+		                                             + e.getMessage());
+		}
+    }
+}
diff --git a/dods/dap/Server/SDInt16.java b/dods/dap/Server/SDInt16.java
new file mode 100644
index 0000000..ecd6d4b
--- /dev/null
+++ b/dods/dap/Server/SDInt16.java
@@ -0,0 +1,307 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+
+/**
+ * Holds a DODS Server <code>Int16</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDInt16 extends DInt16 implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDInt16</code>. */
+  public SDInt16() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDInt16</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDInt16(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+        
+    
+
+   
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                                throws NoSuchVariableException, SDODSException, IOException {
+	
+	if(!isRead())
+	    read(dataset, specialO);
+	    
+        if(ce.evalClauses(specialO))
+	    externalize(sink);
+	    		
+    }
+    
+}
diff --git a/dods/dap/Server/SDInt32.java b/dods/dap/Server/SDInt32.java
new file mode 100644
index 0000000..0f1d764
--- /dev/null
+++ b/dods/dap/Server/SDInt32.java
@@ -0,0 +1,305 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+import dods.dap.NoSuchVariableException;
+
+/**
+ * Holds a DODS Server <code>Int32</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDInt32 extends DInt32 implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDInt32</code>. */
+  public SDInt32() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDInt32</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDInt32(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+        
+    
+
+   
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                                throws NoSuchVariableException, SDODSException, IOException {
+    
+	if(!isRead())
+	    read(dataset, specialO);
+	    
+        if(ce.evalClauses(specialO))
+	    externalize(sink);
+    }
+
+	      	      
+}
diff --git a/dods/dap/Server/SDList.java b/dods/dap/Server/SDList.java
new file mode 100644
index 0000000..bdfcf98
--- /dev/null
+++ b/dods/dap/Server/SDList.java
@@ -0,0 +1,414 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+
+/**
+ * Holds a DODS Server <code>List</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDList extends DList implements ServerMethods, RelOps {
+
+    private static final boolean _Debug = false;
+
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+    /** Constructs a new <code>SDList</code>. */
+    public SDList() { 
+        super(); 
+        Project = false;
+        Synthesized = false;
+        ReadMe = false;
+    }
+
+    /**
+    * Constructs a new <code>SDList</code> with name <code>n</code>.
+    * @param n the name of the variable.
+    */
+    public SDList(String n) { 
+        super(n); 
+        Project = false;
+        Synthesized = false;
+        ReadMe = false;
+    }
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean, boolean)
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+
+        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to one signature, we must be careful to override that
+        // SAME signature here. That way all calls to printDecl() for 
+        // this object lead to this implementation.
+
+        // Also, since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to the signature we are overriding here, we MUST call
+        // the printDecl with the SAME signature THROUGH the super class
+        // reference (assuming we want the super class functionality). If
+        // we do otherwise, we will create an infinte call loop. OOPS!
+
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+	
+        PrimitiveVector pv = getPrimitiveVector();
+	
+		if(pv instanceof BaseTypePrimitiveVector){
+
+			BaseTypePrimitiveVector vals = (BaseTypePrimitiveVector)pv;
+							
+			if (print_decl_p) {
+				printDecl(os, space, false,true);
+				os.print(" = ");
+			}
+
+			os.print("{ ");
+			//vals.printVal(os, "");
+
+			ServerMethods sm;
+			int len = vals.getLength();
+			for(int i=0; i<len-1; i++) {
+				sm = (ServerMethods)vals.getValue(i);
+				if(sm.isProject()){
+					((BaseType)sm).printVal(os, "", false);
+					os.print(", ");
+				}
+			}
+			// print last value, if any, without trailing comma
+			if(len > 0) {
+				sm = (ServerMethods)vals.getValue(len-1);
+				if(sm.isProject()){
+					((BaseType)sm).printVal(os, "", false);
+				}
+			}
+
+
+			os.print("}");
+
+			if (print_decl_p)
+				os.println(";");
+		}
+		else {
+			super.printVal(os,space,print_decl_p);
+		}	    	
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+        PrimitiveVector vals = getPrimitiveVector();
+		( (ServerMethods)(vals.getTemplate())).setProject(state,all);
+
+//		if(vals instanceof BaseTypePrimitiveVector){
+//			System.out.println("Setting Projecttion for List member: "+
+//			((BaseType)((BaseTypePrimitiveVector)vals).getTemplate()).getTypeName() + " " +
+//			((BaseType)((BaseTypePrimitiveVector)vals).getTemplate()).getName());
+//			((ServerMethods)((BaseTypePrimitiveVector)vals).getTemplate()).setProject(state);
+//		}
+
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+		setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DList won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Equals (=) operator does not work with the type SDList!");
+    }
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Not Equals (!=) operator does not work with the type SDList!");
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Greater Than (>)operator does not work with the type SDList!");
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("GreaterThan or equals (<=) operator does not work with the type SDList!");
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan (<) operator does not work with the type SDList!");
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan oe equals (<=) operator does not work with the type SDList!");
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Regular Expression's don't work with the type SDList!");
+    }
+    
+    
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead (boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+    * <h2>Caution:</h2>
+    * When reading Lists of sequences (children of DSequence) it is crucial
+    * that it be handled with great care. Sequences have been implemented so that
+    * only one instance (or row if you will) is retained in memory at a given time. 
+    * In order to correctly read a List of Sequences the read() method for the
+    * List must create an instance of the Sequence for each member of the List, typically
+    * by repeatedly cloning the template variable in the PrimitiveVector. The important next
+    * step is: DO NOT attempt to read any data into the sequences from within the read()
+    * method of the List. The sequence's data will get read, and constraint expressions
+    * applied when the serialze() method of the List calls the serialize() method
+    * of the Sequence, which in turn calls the read method of the Sequence. Good Luck!
+    * @param datasetName String identifying the file or other data store
+    * from which to read a vaue for this variable.
+    * @param specialO This <code>Object</code> is a goody that is used by Server implementations
+    * to deliver important, and as yet unknown, stuff to the read method. If you
+    * don't need it, make it a <code>null</code>.
+    * @return <code>true</code> if more data remains to be read, otherwise
+    * <code>false</code>. This is an abtsract method that must be implemented
+    * as part of the installation/localization of a DODS server.
+    * @exception IOException
+    * @exception EOFException */
+    
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * <p>
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    * </p><p>
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    * </p>
+    * <h2>Caution:</h2>
+    * When serializing lists of sequences (children of DSequence) it is crucial
+    * that it be handled with great care. Sequences have been implemented so that
+    * only one instance (or row if you will) is retained in memory at a given time. 
+    * In order to correctly serialize a list of sequences the read() method for the
+    * List must create an instance of the sequence for each member of the list, typically
+    * by repeatedly cloning the template variable in the PrimitiveVector. The important next
+    * step is to NOT attempt to read any data into the sequences from within the read()
+    * method of the List. The sequence's data will get read, and constraint expressions
+    * applied when the serialze() method of the List calls the serialize() method
+    * of the sequence. Good Luck!
+    * 
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                            throws NoSuchVariableException, SDODSException, IOException {
+
+        PrimitiveVector vals = getPrimitiveVector();
+
+        if(_Debug) System.out.println("\nSTARTED.............SDList.serialize("+dataset+")");
+
+        if(!isRead())
+            read(dataset,specialO);
+
+        if(vals.getTemplate() instanceof DSequence || ce.evalClauses(specialO)){
+
+            // Because arrays of primitive types (ie int32, float32, byte, etc) are
+            // handled in the C++ core using the XDR package we must write the
+            // length twice for those types. For BaseType vectors, we should write
+            // it only once. This is in effect a work around for a bug in the C++
+            // core as the C++ core does not consume 2 length values for thge
+            // BaseType vectors. Bummer...
+            int length = vals.getLength();
+            sink.writeInt(length);
+
+
+            // Gotta check for this to make sure that DConstructor types
+            // (Especially SDSequence) get handled correctly!!!
+            if(vals instanceof BaseTypePrimitiveVector){
+                for(int i=0; i<length ;i++){
+                    ServerMethods sm = (ServerMethods)((BaseTypePrimitiveVector)vals).getValue(i);
+                    sm.serialize(dataset,sink,ce, specialO);
+                }
+            }
+            else {
+
+                // because both XDR and DODS read the length, we must write it twice
+                sink.writeInt(length);
+                vals.externalize(sink);
+            }
+	    
+
+        }
+        if(_Debug) System.out.println("\nFINISHED............SDList.serialize("+dataset+") ");
+
+    }
+    
+    
+     
+    
+}
diff --git a/dods/dap/Server/SDODSException.java b/dods/dap/Server/SDODSException.java
new file mode 100644
index 0000000..4194985
--- /dev/null
+++ b/dods/dap/Server/SDODSException.java
@@ -0,0 +1,62 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+// $Log: not supported by cvs2svn $
+// Revision 1.4  2001/02/04 01:44:48  ndp
+// Cleaned up javadoc errors
+//
+// Revision 1.3  1999/09/24 21:59:24  ndp
+// Reorged Exceptions. Code compiles.
+//
+// Revision 1.2  1999/08/20 22:58:18  jimg
+// Change the package declaration to dods.dap.Server so that it would match the
+// directory name (which I think Java requires).
+// Added the import of DODSException.
+//
+
+package dods.dap.Server;
+import dods.dap.DODSException;
+
+/**
+ * SDODS exception. This is the root of all the DODS Server exception classes.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ */
+public class SDODSException extends DODSException {
+  /**
+   * Construct a <code>SDODSException</code> with the specified detail
+   * message.
+   *
+   * @param s the detail message.
+   */
+  public SDODSException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/Server/SDSequence.java b/dods/dap/Server/SDSequence.java
new file mode 100644
index 0000000..e603465
--- /dev/null
+++ b/dods/dap/Server/SDSequence.java
@@ -0,0 +1,459 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import dods.dap.BaseType;
+import dods.dap.DDS;
+import dods.dap.DSequence;
+import dods.dap.NoSuchVariableException;
+
+/**
+ * Holds a DODS Server <code>Sequence</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+ 
+public abstract class SDSequence extends DSequence implements  ServerMethods, RelOps {
+
+    private static final boolean _Debug = false;
+    
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+
+    /** Constructs a new <code>SDSequence</code>. */
+    public SDSequence() { 
+        super(); 
+        Project = false;
+        Synthesized = false;
+        ReadMe = false;
+    }
+
+    /**
+    * Constructs a new <code>SDSequence</code> with name <code>n</code>.
+    * @param n the name of the variable.
+    */
+    public SDSequence(String n) { 
+        super(n); 
+        Project = false;
+        Synthesized = false;
+        ReadMe = false;
+    }
+
+    /**
+     * Get the row vector for into which to read a row of data for this sequence.
+     * When serving sequence data to clients the preferred method is to read one row 
+     * of the sequence at a time in to this vector, evaluate the constraint expression
+     * clauses on the current data, and then send it to the client if it satisfies
+     * the constraint. The NOT recommended way is to read the ENTIRE sequence into memory
+     * prior to sending it (that would be most inefficient).
+     *
+     * @return The base (row 0) row vector for this sequence.
+     */
+
+    public Vector getRowVector() throws NoSuchVariableException {
+    
+	
+        if(getRowCount() == 0){
+            if (_Debug) System.out.println("This sequence has "+ getRowCount() + " rows.");
+	    
+            Vector rv = new Vector();
+	    
+            for(int i=0; i<elementCount(false) ;i++){
+                if (_Debug) System.out.println("Building variable "+i+": "+ getVar(i).getName());
+                rv.add(getVar(i));
+            }
+            if (_Debug) System.out.println("Adding row to sequence...");
+	    
+            addRow(rv);
+        }
+
+        return(getRow(0));			    
+    }
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean,boolean)
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+
+        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to one signature, we must be careful to override that
+        // SAME signature here. That way all calls to printDecl() for 
+        // this object lead to this implementation.
+
+	// Also, since printDecl()is (multiple) overloaded in BaseType and
+	// all of the different signatures of printDecl() in BaseType lead to
+	// the signature we are overriding here, we MUST call the printDecl
+	// with the SAME signature THROUGH the super class reference
+	// (assuming we want the super class functionality). If we do
+	// otherwise, we will create an infinte call loop. OOPS!
+
+
+
+        // If we are constrained, make sure some part of this thing is projected
+        if(constrained && !Project)
+	        return;
+
+        super.printDecl(os,space,print_semi,constrained);
+
+
+    }
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    
+    
+        if (!Project)
+	        return;
+    
+        if (print_decl_p) {
+            printDecl(os, space, false,true);
+            os.print(" = ");
+        }
+
+        os.print("{ ");
+
+        try {
+			boolean firstPass = true;	
+            Vector v = getRowVector();
+            for(Enumeration e2 = v.elements(); e2.hasMoreElements(); ) {
+                // get next instance variable
+                BaseType bt = (BaseType)e2.nextElement();
+		
+				if( ((ServerMethods)bt).isProject()){
+					if (!firstPass)
+						os.print(", ");
+					bt.printVal(os, "", false);
+					firstPass = false;
+				}
+				
+            }
+	    }
+	    catch (NoSuchVariableException e){
+	        os.println("Very Bad Things Happened When I Tried To Print "+
+	                   "A Row Of The Sequence: " + getName());
+        }
+        os.print(" }");
+
+        if(print_decl_p)
+            os.println(";");
+    }
+
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all If <code>true</code>, set the Project property of all the
+	members (and their children, and so on).
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+	    if (all) 
+	        for (Enumeration e = varTemplate.elements(); e.hasMoreElements();) {
+		        ServerMethods sm = (ServerMethods)e.nextElement();
+		        sm.setProject(state);
+	        }
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. <p>
+	This is equivalent to setProjection(<code>state</code>,
+	<code>true</code>).
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)        
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DSequence won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Equals (=) operator does not work with the type SDSequence!");
+    }
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Not Equals (!=) operator does not work with the type SDSequence!");
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Greater Than (>)operator does not work with the type SDSequence!");
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("GreaterThan or equals (<=) operator does not work with the type SDSequence!");
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan (<) operator does not work with the type SDSequence!");
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan oe equals (<=) operator does not work with the type SDSequence!");
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Regular Expression's don't work with the type SDSequence!");
+    }
+    
+    
+    
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+		
+	    //for (Enumeration e = varTemplate.elements();e.hasMoreElements();) {
+		  //  ServerMethods sm = (ServerMethods)e.nextElement();
+		    //System.out.println("Setting Read Flag for "+((BaseType)sm).getName()+" to "+state);
+			//sm.setRead(state);
+		//}
+	
+	
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setAllReadFlags(boolean state){
+        ReadMe = state;
+		
+	    for (Enumeration e = varTemplate.elements();e.hasMoreElements();) {
+			ServerMethods sm = (ServerMethods)e.nextElement();
+		    //System.out.println("Setting Read Flag for "+((BaseType)sm).getName()+" to "+state);
+			sm.setRead(state);
+		}
+	
+	
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+     /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of 
+    * <code>BaseType</code>). This does not send the entire class as the
+    * Java <code>Serializable</code> interface does, rather it sends only
+    * the binary data values. Other software is responsible for sending
+    * variable type information (see <code>DDS</code>).<p>
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce,Object specialO) 
+	                        throws NoSuchVariableException, SDODSException, IOException {
+		
+
+        boolean moreToRead = true;
+
+        while(moreToRead){
+
+            if (!isRead()){
+// ************* Pulled out the getLevel() check in order to support the "new" 
+// and "improved" serialization of dods sequences. 8/31/01 ndp
+//                if(getLevel() != 0 )  // Read only the outermost level
+//                    return;
+                moreToRead = read(dataset, specialO);
+            }
+	    
+            //System.out.println("Evaluating Clauses...");
+            if (ce.evalClauses(specialO)){
+                //System.out.println("Clauses evaluated true");
+
+// ************* Pulled out the getLevel() check in order to support the "new" 
+// and "improved" serialization of dods sequences. 8/31/01 ndp
+//                if(getLevel() == 0){
+                    writeMarker(sink, START_OF_INSTANCE);
+//                }
+
+                for (Enumeration e = varTemplate.elements();e.hasMoreElements();) {
+                    ServerMethods sm = (ServerMethods)e.nextElement();
+                    if (sm.isProject()){
+                        if(_Debug) System.out.println("Sending variable: "+((BaseType)sm).getName());					
+                        sm.serialize(dataset, sink, ce, specialO);
+                    }
+                }
+            }
+            if (moreToRead)
+                setAllReadFlags(false);
+        }
+
+// ************* Pulled out the getLevel() check in order to support the "new" 
+// and "improved" serialization of dods sequences. 8/31/01 ndp
+//        if(getLevel() == 0){
+            writeMarker(sink, END_OF_SEQUENCE);
+//        }
+
+        return;
+    }
+    
+}
diff --git a/dods/dap/Server/SDString.java b/dods/dap/Server/SDString.java
new file mode 100644
index 0000000..78c6d92
--- /dev/null
+++ b/dods/dap/Server/SDString.java
@@ -0,0 +1,316 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+
+/**
+ * Holds a DODS Server <code>String</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDString extends DString implements 
+                                                 ServerMethods,
+						 RelOps, 
+						 ExprParserConstants  {
+						 
+						 
+  private static final boolean _Debug = false;
+  private boolean Project;
+  private boolean Synthesized;
+  private boolean ReadMe;
+    
+  /** Constructs a new <code>SDString</code>. */
+  public SDString() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+    
+  }
+
+  /**
+   * Constructs a new <code>SDString</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDString(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to one signature, we must be careful to override that
+        // SAME signature here. That way all calls to printDecl() for 
+        // this object lead to this implementation.
+
+        // Also, since printDecl()is (multiple) overloaded in BaseType
+        // and all of the different signatures of printDecl() in BaseType
+        // lead to the signature we are overriding here, we MUST call
+        // the printDecl with the SAME signature THROUGH the super class
+        // reference (assuming we want the super class functionality). If
+        // we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(REGEXP,this,bt));
+    }
+ 
+
+   
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                                throws NoSuchVariableException, SDODSException, IOException {
+
+	if(!isRead())
+	    read(dataset, specialO);
+	    
+        if(ce.evalClauses(specialO))
+	    externalize(sink);
+	    
+        
+	    
+	    		
+    }
+    
+}
diff --git a/dods/dap/Server/SDStructure.java b/dods/dap/Server/SDStructure.java
new file mode 100644
index 0000000..8f39ff6
--- /dev/null
+++ b/dods/dap/Server/SDStructure.java
@@ -0,0 +1,356 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import java.io.*;
+import java.util.Enumeration;
+import dods.dap.*;
+import dods.dap.Server.ServerMethods;
+
+/**
+ * Holds a DODS Server <code>Structure</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDStructure extends DStructure implements ServerMethods, RelOps {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDStructure</code>. */
+  public SDStructure() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDStructure</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDStructure(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    *
+    * @see BaseType#printDecl(PrintWriter, String, boolean,boolean)
+    */
+    public void printDecl(PrintWriter os, String space,
+                          boolean print_semi, boolean constrained) {
+
+
+	// BEWARE! Since printDecl()is (multiple) overloaded in BaseType and
+	// all of the different signatures of printDecl() in BaseType lead to
+	// one signature, we must be careful to override that SAME signature
+	// here. That way all calls to printDecl() for this object lead to
+	// this implementation.
+
+	// Also, since printDecl()is (multiple) overloaded in BaseType and
+	// all of the different signatures of printDecl() in BaseType lead to
+	// the signature we are overriding here, we MUST call the printDecl
+	// with the SAME signature THROUGH the super class reference
+	// (assuming we want the super class functionality). If we do
+	// otherwise, we will create an infinte call loop. OOPS!
+
+
+        if(constrained && !Project)
+            return;
+
+        super.printDecl(os,space, print_semi, constrained);
+
+    }
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+    
+
+        if(!Project)
+            return;
+
+
+        if (print_decl_p) {
+            printDecl(os, space, false,true);
+            os.print(" = ");
+        }
+	
+	boolean first = true;
+
+        os.print("{ ");
+        for(Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+	
+            BaseType bt = (BaseType)e.nextElement();
+
+            if( ((ServerMethods)bt).isProject()){
+	    
+                if(!first)
+                    os.print(", ");
+		    
+                bt.printVal(os, "", false);
+		first = false;
+		
+            }
+        }
+        os.print(" }");
+
+        if (print_decl_p)
+            os.println(";");
+    }
+
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all If <code>true</code>, set the Project property of all the
+	members (and their children, and so on).
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+	    if (all) 
+	        for (Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+		    ServerMethods sm = (ServerMethods)e.nextElement();
+		    sm.setProject(state, all);
+	    }
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. <p>
+	This is equivalent to setProjection(<code>state</code>,
+	<code>true</code>).
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DStructure won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Equals (=) operator does not work with the type SDStructure!");
+    }
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Not Equals (!=) operator does not work with the type SDStructure!");
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Greater Than (>)operator does not work with the type SDStructure!");
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("GreaterThan or equals (<=) operator does not work with the type SDStructure!");
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan (<) operator does not work with the type SDStructure!");
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("LessThan oe equals (<=) operator does not work with the type SDStructure!");
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+        throw new InvalidOperatorException("Regular Expression's don't work with the type SDStructure!");
+    }
+    
+    
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+     /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce,Object specialO) 
+	                                    throws NoSuchVariableException, SDODSException, IOException {
+		
+        //System.out.println("Serializing Structure "+getName()+"   isRead(): "+isRead()+"  isProject(): " + isProject());
+
+        if(!isRead())
+            read(dataset, specialO);
+
+        if(ce.evalClauses(specialO)) {
+            for (Enumeration e = vars.elements(); e.hasMoreElements(); ) {
+                ServerMethods sm = (ServerMethods)e.nextElement();
+
+                if (sm.isProject())
+                    sm.serialize(dataset, sink, ce, specialO);
+            }
+        }
+    }
+    
+    
+    
+    
+    
+}
diff --git a/dods/dap/Server/SDUInt16.java b/dods/dap/Server/SDUInt16.java
new file mode 100644
index 0000000..3097c71
--- /dev/null
+++ b/dods/dap/Server/SDUInt16.java
@@ -0,0 +1,306 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+
+/**
+ * Holds a DODS Server <code>Unsigned Int16</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDUInt16 extends DUInt16 implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDUInt16</code>. */
+  public SDUInt16() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDUInt16</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDUInt16(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	    setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+        
+    
+
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String,Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String,Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String,Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+     /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO)
+                                                throws NoSuchVariableException, SDODSException, IOException {
+	
+        if(!isRead())
+	    read(dataset, specialO);
+	    
+        if(ce.evalClauses(specialO))
+            externalize(sink);
+	    		
+    }
+    
+}
diff --git a/dods/dap/Server/SDUInt32.java b/dods/dap/Server/SDUInt32.java
new file mode 100644
index 0000000..0bc9cc5
--- /dev/null
+++ b/dods/dap/Server/SDUInt32.java
@@ -0,0 +1,305 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+
+/**
+ * Holds a DODS Server <code>Unsigned Int32</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDUInt32 extends DUInt32 implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDUInt32</code>. */
+  public SDUInt32() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDUInt32</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDUInt32(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException{
+            return(Operator.op(REGEXP,this,bt));
+    }
+        
+    
+
+   
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+     /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+    /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                                throws NoSuchVariableException, SDODSException, IOException {
+	
+	if(!isRead())
+	    read(dataset, specialO);
+	    
+        if(ce.evalClauses(specialO))
+	    externalize(sink);
+	    		
+    }
+    
+}
diff --git a/dods/dap/Server/SDURL.java b/dods/dap/Server/SDURL.java
new file mode 100644
index 0000000..c0e5e90
--- /dev/null
+++ b/dods/dap/Server/SDURL.java
@@ -0,0 +1,308 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import dods.dap.*;
+import java.io.*;
+import dods.dap.parser.ExprParserConstants;
+
+/**
+ * Holds a DODS Server <code>URL</code> value.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ */
+public abstract class SDURL extends DURL implements ServerMethods, RelOps, ExprParserConstants  {
+    private boolean Project;
+    private boolean Synthesized;
+    private boolean ReadMe;
+    
+  /** Constructs a new <code>SDURL</code>. */
+  public SDURL() { 
+    super(); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+  /**
+   * Constructs a new <code>SDURL</code> with name <code>n</code>.
+   * @param n the name of the variable.
+   */
+  public SDURL(String n) { 
+    super(n); 
+    Project = false;
+    Synthesized = false;
+    ReadMe = false;
+  }
+
+
+    /**
+    * Write the variable's declaration in a C-style syntax. This
+    * function is used to create textual representation of the Data
+    * Descriptor Structure (DDS).  See <em>The DODS User Manual</em> for
+    * information about this structure.
+    *
+    * @param os The <code>PrintWriter</code> on which to print the
+    *    declaration.
+    * @param space Each line of the declaration will begin with the
+    *    characters in this string.  Usually used for leading spaces.
+    * @param print_semi a boolean value indicating whether to print a
+    *    semicolon at the end of the declaration.
+    * @param constrained a boolean value indicating whether to print
+    *    the declartion dependent on the projection information. <b>This
+    *    is only used by Server side code.</b>
+    * @see DDS
+    */
+    public void printDecl(PrintWriter os, String space,
+                                    boolean print_semi, boolean constrained) {
+        if(constrained && !Project)
+	        return;
+		
+		// BEWARE! Since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to one signature, we must be careful to override that
+	    // SAME signature here. That way all calls to printDecl() for 
+	    // this object lead to this implementation.
+
+		// Also, since printDecl()is (multiple) overloaded in BaseType
+		// and all of the different signatures of printDecl() in BaseType
+		// lead to the signature we are overriding here, we MUST call
+		// the printDecl with the SAME signature THROUGH the super class
+		// reference (assuming we want the super class functionality). If
+		// we do otherwise, we will create an infinte call loop. OOPS!
+		
+        super.printDecl(os,space,print_semi,constrained);
+    }
+
+
+
+
+    /**
+    * Prints the value of the variable, with its declaration.  This
+    * function is primarily intended for debugging DODS applications and
+    * text-based clients such as geturl.
+    *
+    * <h2> Important Note</h2>
+    * This method overrides the BaseType method of the same name and 
+    * type signature and it significantly changes the behavior for all versions
+    * of <code>printVal()</code> for this type: 
+    * <b><i> All the various versions of printVal() will only
+    * print a value, or a value with declaration, if the variable is
+    * in the projection.</i></b>
+    * <br>
+    * <br>In other words, if a call to 
+    * <code>isProject()</code> for a particular variable returns 
+    * <code>true</code> then <code>printVal()</code> will print a value 
+    * (or a declaration and a value). 
+    * <br>
+    * <br>If <code>isProject()</code> for a particular variable returns 
+    * <code>false</code> then <code>printVal()</code> is basically a No-Op.
+    * <br>
+    * <br>
+    *
+    * @param os the <code>PrintWriter</code> on which to print the value.
+    * @param space this value is passed to the <code>printDecl</code> method,
+    *    and controls the leading spaces of the output.
+    * @param print_decl_p a boolean value controlling whether the
+    *    variable declaration is printed as well as the value.
+    * @see BaseType#printVal(PrintWriter, String, boolean)
+    * @see ServerMethods#isProject()
+    */
+    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
+        if(!Project)
+	        return;
+        super.printVal(os,space,print_decl_p);
+     }
+
+
+
+    // --------------- Projection Interface
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all This parameter has no effect for this type of variable.
+	@see CEEvaluator */
+    public void setProject(boolean state, boolean all) {
+        Project = state;        
+    }
+
+    /** Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>. 
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@see CEEvaluator */
+    public void setProject(boolean state) {
+	setProject(state, true);
+    }
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+	@see CEEvaluator 
+        @see #setProject(boolean)
+    */
+    public boolean isProject(){
+        return(Project);
+    }
+    
+    
+    
+// --------------- RelOps Interface
+/** The RelOps interface defines how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. DBoolean won't know how to match a regular
+    expression but DString will). For those operators that are nonsensical a
+    class should throw InvalidOperatorException.*/
+    
+    
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(EQUAL,this,bt));
+    }    
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(NOT_EQUAL,this,bt));
+    }
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+             return(Operator.op(GREATER,this,bt));
+    }
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(GREATER_EQL,this,bt));
+    }
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(LESS,this,bt));
+    }
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(LESS_EQL,this,bt));
+    }
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
+            return(Operator.op(REGEXP,this,bt));
+    }
+ 
+   
+    
+
+       
+// --------------- FileIO Interface
+
+   /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object)
+    */
+    public void setSynthesized(boolean state){
+        Synthesized = state;
+    }
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise. */
+    public boolean isSynthesized(){
+        return(Synthesized);
+    }
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object)
+    */
+    public void setRead(boolean state){
+        ReadMe = state;
+    }
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object)
+	@see #setRead(boolean)
+    */
+    public boolean isRead(){
+        return(ReadMe);
+    }
+
+    /** Read a value from the named dataset for this variable. 
+	@param datasetName String identifying the file or other data store
+	from which to read a vaue for this variable.
+	@param specialO This <code>Object</code> is a goody that is used by Server implementations
+	to deliver important, and as yet unknown, stuff to the read method. If you
+	don't need it, make it a <code>null</code>.
+	@return <code>true</code> if more data remains to be read, otherwise
+	<code>false</code>. This is an abtsract method that must be implemented
+	as part of the installation/localization of a DODS server.
+	@exception IOException
+	@exception EOFException */
+    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;
+    
+    
+        /** 
+    * Server-side serialization for DODS variables (sub-classes of
+    * <code>BaseType</code>).
+    * This does not send the entire class as the Java <code>Serializable</code>
+    * interface does, rather it sends only the binary data values. Other software
+    * is responsible for sending variable type information (see <code>DDS</code>).
+    *
+    * Writes data to a <code>DataOutputStream</code>. This method is used
+    * on the server side of the DODS client/server connection, and possibly
+    * by GUI clients which need to download DODS data, manipulate it, and
+    * then re-save it as a binary file.
+    *
+    * @param sink a <code>DataOutputStream</code> to write to.
+    * @exception IOException thrown on any <code>OutputStream</code> exception. 
+    * @see BaseType
+    * @see DDS
+    * @see ServerDDS */
+    public void serialize(String dataset,DataOutputStream sink,CEEvaluator ce, Object specialO) 
+                                        throws NoSuchVariableException, SDODSException, IOException {
+	
+	if(!isRead())
+	    read(dataset, specialO);
+	    
+        if(ce.evalClauses(specialO))
+	    externalize(sink);
+	    		
+    }
+
+}
diff --git a/dods/dap/Server/SSFunctionException.java b/dods/dap/Server/SSFunctionException.java
new file mode 100644
index 0000000..cc2fdcd
--- /dev/null
+++ b/dods/dap/Server/SSFunctionException.java
@@ -0,0 +1,67 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+// $Log: not supported by cvs2svn $
+// Revision 1.2  2002/01/03 22:58:47  ndp
+// Merged newClauseImplementation branch into trunk.
+//
+// Revision 1.1.2.1  2002/01/03 19:21:08  ndp
+// *** empty log message ***
+//
+// Revision 1.7  2001/02/04 01:44:48  ndp
+// Cleaned up javadoc errors
+//
+// Revision 1.6  1999/09/24 23:17:58  ndp
+// Added new constructors to all Exception classes
+//
+// Revision 1.5  1999/09/24 21:59:23  ndp
+// Reorged Exceptions. Code compiles.
+//
+// Revision 1.4  1999/09/16 21:22:52  ndp
+// *** empty log message ***
+//
+// Revision 1.3  1999/08/20 22:59:43  jimg
+// Changed the package declaration to dods.dap.Server so that it matches the
+// directory name.
+//
+
+package dods.dap.Server;
+import dods.dap.DODSException;
+
+/**
+ * Thrown when a Server Side Function (SSF) is used incorrectly.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ */
+public class SSFunctionException extends SDODSException {
+  /**
+   * Construct a <code>InvalidOperatorException</code> with the specified
+   * detail message.
+   *
+   * @param s the detail message.
+   */
+  public SSFunctionException(String s) {
+    super(DODSException.MALFORMED_EXPR,"SeverSideFunction Exception: " + s);
+  }
+
+
+  /**
+   * Construct a <code>InvalidOperatorException</code> with the specified
+   * message and DODS error code (see <code>DODSException</code>).
+   *
+   * @param err the DODS error code.
+   * @param s the detail message.
+   */
+  public SSFunctionException(int err, String s) {
+    super(err,s);
+  }
+}
diff --git a/dods/dap/Server/ServerArrayMethods.java b/dods/dap/Server/ServerArrayMethods.java
new file mode 100644
index 0000000..e521c30
--- /dev/null
+++ b/dods/dap/Server/ServerArrayMethods.java
@@ -0,0 +1,69 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+
+package dods.dap.Server;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import dods.dap.BaseType;
+//import dods.dap.NoSuchVariableException;
+ 
+ 
+/** This interface extends the <code>ArrayMethods</code> for DODS types that
+  * extend <code>DArray</code> and <code>DGrid</code> classes. It contains 
+  * additional projection methods needed by the Server side implementations  
+  * of these types.
+  * <p>A projection for an array must include the start, stride and stop
+  * information for each dimension of the array in addition to the basic
+  * information that the array itself is <em>projected</em>. This interface
+  * provides access to that information.
+  * @see dods.dap.DArray
+  * @see dods.dap.DGrid
+  * @see dods.dap.Server.SDArray
+  * @see dods.dap.Server.SDGrid
+  * @see dods.dap.Server.ServerMethods
+  * @see dods.dap.Server.Operator
+    
+  * @version $Revision: 1.3 $
+  * @author jhrg & ndp */
+
+
+public interface ServerArrayMethods extends ServerMethods {
+  
+    public void  setProjection(int dimension,int start, int stride, int stop) 
+	throws InvalidParameterException, SBHException;
+
+    public int   getStart (int dimension) throws InvalidParameterException;
+ 
+    public int   getStride(int dimension) throws InvalidParameterException;
+
+    public int   getStop  (int dimension) throws InvalidParameterException;
+        
+}
diff --git a/dods/dap/Server/ServerDDS.java b/dods/dap/Server/ServerDDS.java
new file mode 100644
index 0000000..0337c41
--- /dev/null
+++ b/dods/dap/Server/ServerDDS.java
@@ -0,0 +1,123 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, University of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: James Gallagher <jgallagher at gso.uri.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+
+import java.io.*;
+import java.util.Enumeration;
+import java.util.Stack;
+import java.util.Vector;
+import java.util.Map;
+
+import dods.dap.*;
+
+/** ServerDDS is a specialization of DDS for the server-side of DODS. This
+    class includes methods used to distinguish synthesized variables
+    (variables added to the DDS by a constraint expression function), methods
+    for CE function management and methods used to return a `constrained DDS'
+    as part of a DODS data document.
+    <p>
+    All of the variables contained by a ServerDDS <em>must</em> implement the
+    Projection interface.
+    @version $Revision: 1.3 $
+    @author jhrg
+    @see DDS
+    @see CEEvaluator */
+public class ServerDDS extends DDS implements Cloneable {
+
+    protected ServerDDS() {
+	super();
+    }
+
+    /** Creates an empty <code>Server DDS</code> with the given dataset name.
+	@param n the dataset name */
+    protected ServerDDS(String n) {
+	super(n);
+    }
+
+    /** Creates an empty <code>ServerDDS</code> with the given
+	<code>BaseTypeFactory</code>. This will be used for DODS servers
+	which need to construct subclasses of the various
+	<code>BaseType</code> objects to hold additional server-side
+	information.
+	@param factory the server <code>BaseTypeFactory</code> object. */
+    public ServerDDS(BaseTypeFactory factory) {
+	this(null, factory);
+    }
+
+    /** Creates an empty <code>ServerDDS</code> with the given dataset name
+	and <code>BaseTypeFactory</code>. This will be used for DODS servers
+	which need to construct subclasses of the various
+	<code>BaseType</code> objects to hold additional server-side
+	information.
+	@param n the dataset name
+	@param factory the server <code>BaseTypeFactory</code> object. */
+    public ServerDDS(String n, BaseTypeFactory factory) {
+	super(n, factory);
+    }
+
+    /** Return a clone of the <code>ServerDDS</code>. A deep copy is
+	performed on this object and those it contains. 
+	@return a ServerDDS object. */
+    public Object clone() {
+    	ServerDDS d = (ServerDDS)super.clone();
+	return(d);
+    }
+
+    /** Set the filename of the dataset. This must be passed to the
+	<code>read()</code> method of the FileIO interface. The filename of
+	the dataset may be a real filename or may be any other string that
+	can be used to identify for the <code>read</code> method how to
+	access the data-store of which a particular variable is a member.
+	@param n The name of the dataset.
+	@see ServerMethods#read(String, Object) ServerMethods.read()
+    */
+    public void setDatasetFilename(String n) {
+	name = n;
+    }
+
+    /** Get the dataset filename.
+	@return The filename of the dataset.
+	@see #setDatasetFilename(String) */
+    public String getDatasetFilename() {
+	String s = name;
+    	System.out.println(s);
+	return(s);
+    }
+
+    /** Print the constrained <code>DDS</code> on the given
+	<code>PrintWriter</code>.
+	@param os the <code>PrintWriter</code> to use for output. */
+    public void printConstrained(PrintWriter os) {
+	os.println("Dataset {");
+	for (Enumeration e = getVariables(); e.hasMoreElements();) {
+	    BaseType bt = (BaseType)e.nextElement();
+	    if (((ServerMethods)bt).isProject())
+		bt.printDecl(os, "    ", true,true);
+	}
+	os.print("} ");
+	if (name != null)
+	    os.print(name);
+	os.println(";");
+    }
+
+    /** Print the constrained <code>DDS</code> on the given
+	<code>OutputStream</code>.
+	@param os the <code>OutputStream</code> to use for output.
+	@see DDS#print(PrintWriter) */
+    public final void printConstrained(OutputStream os) {
+	PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)));
+	printConstrained(pw);
+	pw.flush();
+    }
+
+}
+
diff --git a/dods/dap/Server/ServerMethods.java b/dods/dap/Server/ServerMethods.java
new file mode 100644
index 0000000..051e701
--- /dev/null
+++ b/dods/dap/Server/ServerMethods.java
@@ -0,0 +1,249 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import dods.dap.BaseType;
+import dods.dap.DDS;
+import dods.dap.NoSuchVariableException;
+import dods.dap.Server.SBHException;
+
+/** 
+ *
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see BaseType
+ * @see DDS
+ */
+ 
+ 
+/** This interface defines the additional behaviors that Server side types
+    need to support. These include:
+    
+    <p>The file I/O operations of which each variable must be capable.
+    
+    <p>The projection information. A projection defines the variables
+    specified by a constraint to be returned by the server. These methods 
+    store projection information for a non-vector variable. Each variable 
+    type used on the server-side of DODS must implement this interface or
+    one of its descendents.
+
+    <p>The methods that define how each type responds to relational
+    operators. Most (all?) types will not have sensible responses to all of
+    the relational operators (e.g. SDByte won't know how to match a regular
+    expression but SDString will). For those operators that are nonsensical a
+    class should throw InvalidOperator.
+
+    @see ServerArrayMethods
+    @see Operator
+    
+    @version $Revision: 1.3 $
+    @author jhrg & ndp */
+
+
+public interface ServerMethods  {
+
+    //    FileIO interface
+    
+    /** Set the Synthesized property.
+	@param state If <code>true</code> then the variable is considered a
+	synthetic variable and no part of DODS will ever try to read it from a
+	file, otherwise if <code>false</code> the variable is considered a
+	normal variable whose value should be read using the
+	<code>read()</code> method. By default this property is false.
+	@see #isSynthesized()
+	@see #read(String, Object) 
+    */
+    public void setSynthesized(boolean state);
+
+    /** Get the value of the Synthesized property.
+	@return <code>true</code> if this is a synthetic variable,
+	<code>false</code> otherwise.
+    */
+    public boolean isSynthesized();
+
+    /** Set the Read property. A normal variable is read using the
+	<code>read()</code> method. Once read the <em>Read</em> property is
+	<code>true</code>. Use this function to manually set the property
+	value. By default this property is false.
+	@param state <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #isRead()
+	@see #read(String, Object) 
+    */
+    public void setRead(boolean state);
+
+    /** Get the value of the Read property.
+	@return <code>true</code> if the variable has been read,
+	<code>false</code> otherwise.
+	@see #read(String, Object) 
+	@see #setRead(boolean) 
+    */
+    public boolean isRead();
+    
+	/**
+	 * Read a value from the named dataset for this variable.
+	 * 
+	 * @param datasetName
+	 *            String identifying the file or other data store from which to
+	 *            read a value for this variable.
+	 * @param specialO
+	 *            Object this is a goody that is used by Server implementations
+	 *            to deliver important, and as yet unknown, stuff to the read
+	 *            method. If you don't need it, make it a <code>null</code>.
+	 * @return <code>true</code> if more data remains to be read, otherwise
+	 *         <code>false</code>.
+	 * @exception NoSuchVariableException
+	 * @exception IOException
+	 * @exception EOFException
+	 */
+    
+    public boolean read(String datasetName, Object specialO) 
+	            throws NoSuchVariableException, IOException, EOFException;
+    
+    // Projection Interface
+    
+    /** 
+	Set the state of this variable's projection. <code>true</code> means
+	that this variable is part of the current projection as defined by
+	the current constraint expression, otherwise the current projection
+	for this variable should be <code>false</code>.<p> 
+
+	For simple variables and for children of DVector, the variable either
+	is or is not projected. For children of DConstructor, it may be that
+	the request is for only part of the constructor type (e.g., only oe
+	field of a structure). However, the structure variable itself must be
+	marked as projected given the implementation of serialize. The
+	serialize() method does not search the entire tree of variables; it
+	relies on the fact that for a particular variable to be sent, the
+	<em>path</em> from the top of the DDS to that variable must be marked
+	as `projected', not just the variable itself. This keeps the
+	CEEvaluator.send() method from having to search the entire tree for
+	the variables to be sent.
+	
+	@param state <code>true</code> if the variable is part of the current
+	projection, <code>false</code> otherwise.
+	@param all set (or clear) the Project property of any children.
+	@see CEEvaluator 
+    */
+    public void setProject(boolean state, boolean all);
+
+    /** Set the Project property of this variable. This is equivalent to
+     * calling setProject(<state>, true).
+     * @param state <code>true</code> if the variable is part of the current
+     * projection, <code>false</code> otherwise.
+     * @see #setProject(boolean)
+     * @see CEEvaluator 
+     */
+    public void setProject(boolean state);
+
+    /** Is the given variable marked as projected? If the variable is listed
+	in the projection part of a constraint expression, then the CE parser
+	should mark it as <em>projected</em>. When this method is called on
+	such a variable it should return <code>true</code>, otherwise it
+	should return <code>false</code>.
+	@return <code>true</code> if the variable is part of the current
+	projections, <code>false</code> otherwise.
+        @see #setProject(boolean,boolean)
+        @see #setProject(boolean)
+	@see CEEvaluator 
+    */
+    public boolean isProject();
+    
+    /** 
+	The <code>Operator</code> class contains a generalized implementation
+	of this method. It should be used unless a localized
+	architecture/implementation requires otherwise.
+	@see Operator 
+   */
+    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    /** The <code>Operator</code> class contains a generalized implementation
+      * of this method. It should be used unless a localized
+      * architecture/implementation requires otherwise.
+      @see Operator */
+    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    /** The <code>Operator</code> class contains a generalized implementation
+      * of this method. It should be used unless a localized
+      * architecture/implementation requires otherwise.
+      @see Operator */
+    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    /** The <code>Operator</code> class contains a generalized implementation
+      * of this method. It should be used unless a localized
+      * architecture/implementation requires otherwise.
+      @see Operator */
+    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    /** The <code>Operator</code> class contains a generalized implementation
+      * of this method. It should be used unless a localized
+      * architecture/implementation requires otherwise.
+      @see Operator */
+    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    /** The <code>Operator</code> class contains a generalized implementation
+      * of this method. It should be used unless a localized
+      * architecture/implementation requires otherwise.
+      @see Operator */
+    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+    /** The <code>Operator</code> class contains a generalized implementation
+      * of this method. It should be used unless a localized
+      * architecture/implementation requires otherwise.
+      @see Operator */
+    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException;
+
+
+    /** 
+     * Server-side serialization for DODS variables (sub-classes of
+     * <code>BaseType</code>). This does not send the entire class as the
+     * Java <code>Serializable</code> interface does, rather it sends only
+     * the binary data values. Other software is responsible for sending
+     * variable type information (see <code>DDS</code>).<p>
+     *
+     * Writes data to a <code>DataOutputStream</code>. This method is used
+     * on the server side of the DODS client/server connection, and possibly
+     * by GUI clients which need to download DODS data, manipulate it, and
+     * then re-save it as a binary file.<p>
+     *
+     * For children of DConstructor, this method should call itself on each
+     * of the components. For other types this method should call
+     * externalize(). 
+     *
+     * @param dataset a <code>String</code> indicated which dataset to read
+     * from (Or something else if you so desire).
+     * @param sink a <code>DataOutputStream</code> to write to.
+     * @param ce the <code>CEEvaluator</code> to use in the parse process.
+     * @exception IOException thrown on any <code>OutputStream</code> exception. 
+     * @see BaseType
+     * @see DDS
+     * @see ServerDDS */
+    public void serialize(String dataset, DataOutputStream sink, 
+			  CEEvaluator ce, Object specialO) 
+	throws NoSuchVariableException, SDODSException,  IOException;
+}
diff --git a/dods/dap/Server/ServerSideFunction.java b/dods/dap/Server/ServerSideFunction.java
new file mode 100644
index 0000000..f00303d
--- /dev/null
+++ b/dods/dap/Server/ServerSideFunction.java
@@ -0,0 +1,37 @@
+package dods.dap.Server;
+
+import java.util.List;
+
+/** Represents common interface of all server-side functions (SSF).
+ *  Implementing this interface is not sufficient to create a working
+ *  SSF. SSF's must implement (at least) one of the subinterfaces
+ *  BoolFunction and BTFunction.
+ * @author joew */
+public interface ServerSideFunction {
+
+    /** Returns the name of the server-side function, as it will appear in
+     *  constraint expressions. This must be a valid DODS identifier.
+     *  All functions must have distinct names. 
+     */
+    public String getName();
+
+    /** 
+     * Checks that the arguments given are acceptable arguments for this 
+     * function. This method should only use those attributes of a SubClause
+     * which do not change over its lifetime - whether it is constant,
+     * what class of SubClause it is, what class of BaseType it returns, etc.
+     * Thus, the method should not look at the actual value of an argument 
+     * unless the argument is flagged as constant.
+     * 
+     * The function should return normally if the arguments appear
+     * acceptable, and throw an exception describing the problem otherwise.
+     * 
+     * @param args A list of SubClauses that the caller is considering passing
+     *             to the evaluate() method of the function.
+     * @exception InvalidParameterException Thrown if the function will not
+     * evaluate successfully using these arguments.
+     */
+    
+    public void checkArgs(List args)
+	throws InvalidParameterException;
+}
diff --git a/dods/dap/Server/SubClause.java b/dods/dap/Server/SubClause.java
new file mode 100644
index 0000000..42d5ca6
--- /dev/null
+++ b/dods/dap/Server/SubClause.java
@@ -0,0 +1,50 @@
+package dods.dap.Server;
+
+import dods.dap.BaseType;
+
+/** Represents a sub-clause of the selection portion of a constraint 
+ *  expression. A sub-clause is any part of a constraint that
+ *  can be evaluated to a BaseType value. For instance, the constraint
+ *  "var1>=function(var2,var3)" would have sub clauses "var1" and 
+ *  "function(var2,var3)". The latter would in turn have the sub-clauses
+ *  "var2" and "var3".  <p>
+ * 
+ *  A given instance of SubClause may change the value it returns when 
+ *  evaluated multiple times, but should not change the class of BaseType 
+ *  it returns. This allows function and operator clauses to do type-checking
+ *  using the getValue() method.
+ * 
+ *  The parser supports several kinds of sub-clause. These are described
+ *  in the ClauseFactory interface.
+ *
+ * @see TopLevelClause for more about the parsing of clauses.
+ * @see CEEvaluator for an explanation of how Clauses are evaluated on 
+ *    data.
+ * @see ClauseFactory
+ * @author joew */
+public interface SubClause 
+    extends Clause {
+
+    /** Returns the Clause which contains this subclause. The clause returned
+     *  may be a TopLevelClause or another SubClause. */
+    public Clause getParent();
+
+    /** Returns a BaseType containing the current value of the sub-clause.
+     *  Sub-clauses that are not constant have an undefined value until the
+     *  evaluate() method has been called. However, in such circumstances
+     *  this method is still useful, as it indicates which class of
+     *  BaseType the sub-clause will evaluate to. Implementations of this 
+     *  method should never return null.*/
+    public BaseType getValue();
+    
+    /** Evaluates the clause, first calling evaluate() on any sub-clauses it 
+     *  contains. Implementations of this method  should flag the clause as 
+     *  "defined" if the evaluation is successful.
+     * @exception SDODSException Thrown if the evaluation fails for any reason.
+     */
+    public BaseType evaluate() 
+	throws SDODSException;
+
+    /** Sets the parent of this subclause. Used during parsing. */
+    public void setParent(Clause parent);
+}
diff --git a/dods/dap/Server/TopLevelClause.java b/dods/dap/Server/TopLevelClause.java
new file mode 100644
index 0000000..cb2c58a
--- /dev/null
+++ b/dods/dap/Server/TopLevelClause.java
@@ -0,0 +1,36 @@
+package dods.dap.Server;
+
+/**
+ * Represents a top-level clause in the selection portion of a constraint
+ * expression (CE).
+ * <p>
+ * 
+ * A top-level clause is a boolean expression preceded by "&" in the CE, such as
+ * "lat>10.0", or "function(var1,var2)". The top-level clause may contain
+ * sub-clauses which can be evaluated individually.
+ * 
+ * The parser supports several kinds of top-level clause. These are described in
+ * the ClauseFactory interface.
+ * 
+ * @see SubClause for more about sub-clauses.
+ * @see CEEvaluator for an explanation of how Clauses are evaluated on data.
+ * @see ClauseFactory
+ * 
+ * @author joew
+ */
+
+public interface TopLevelClause 
+    extends Clause {
+
+    /** Returns the current value of the clause. The value of non-constant
+     *  Clauses is undefined until the evaluate() method has been called.  */
+    public boolean getValue();
+    
+    /** Evaluates the clause, first calling evaluate() on any sub-clauses it 
+     *  contains. Implementations of this method  should flag the clause as 
+     *  "defined" if the evaluation is successful.
+     * @exception SDODSException Thrown if the evaluation fails for any reason.
+     */
+    public boolean evaluate() 
+	throws SDODSException;
+}
diff --git a/dods/dap/Server/ValueClause.java b/dods/dap/Server/ValueClause.java
new file mode 100644
index 0000000..1515434
--- /dev/null
+++ b/dods/dap/Server/ValueClause.java
@@ -0,0 +1,71 @@
+package dods.dap.Server;
+
+import java.util.*;
+import java.io.*;
+import dods.dap.BaseType;
+
+/** Represents a clause containing a simple value. If the value is
+ *  an constant value such as "2.0", the clause's isConstant() method
+ *  will return true; if it is a variable of the dataset, isConstant() will
+ *  return false. 
+ * @see ClauseFactory
+ *  @author Joe Wielgosz (joew at cola.iges.org) */
+public class ValueClause 
+    extends AbstractClause
+    implements SubClause {
+    
+    /** Creates a new ValueClause.
+     * @param value The BaseType represented by this clause. This can 
+     * be either a BaseType taken from the DDS of a dataset, or a BaseType
+     * object created to hold a constant value.
+     * @param constant Should be set to false if the value parameter is
+     * from the DDS of a dataset, and true if the value parameter is a
+     * constant value.
+     */
+    protected ValueClause(BaseType value, 
+			  boolean constant) {
+	this.value = value;
+	this.constant = constant;
+	this.defined = constant;
+	this.children = new ArrayList();
+    }
+
+    /** Returns the BaseType represented by this clause. */
+    public BaseType getValue() {
+	return value;
+    }
+
+    /** Returns the BaseType represented by this clause. Equivalent to 
+     *  getValue(), except that
+     *  calling this method flags this clause as "defined". 
+     * @exception SDODSException Not thrown by this type of clause. */
+    public BaseType evaluate() {
+	defined = true;
+	return value;
+    }
+
+    public Clause getParent() {
+	return parent;
+    }
+
+    public void setParent(Clause parent) {
+	this.parent = parent;
+    }
+
+    /** Prints the original string representation of this clause.
+     *  For use in debugging.
+     */
+    public String toString() {
+	if (constant) {
+	    StringWriter w = new StringWriter();
+	    value.printVal(new PrintWriter(w), "",false);
+	    return w.toString();
+	} else {
+	    return value.getName();
+	}
+    }
+
+    protected BaseType value;
+    protected Clause parent;
+
+}
diff --git a/dods/dap/Server/WrongTypeException.java b/dods/dap/Server/WrongTypeException.java
new file mode 100644
index 0000000..1c3d549
--- /dev/null
+++ b/dods/dap/Server/WrongTypeException.java
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, Univ. of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: James Gallagher <jgallagher at gso.uri.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.Server;
+import java.lang.String;
+import dods.dap.DODSException;
+
+/** Report a type-mismatch problem in the constraint expression. Examples are
+ * using relational operators on arrays, using the dot notation on variables
+ * that are not aggregate types.
+ * @author jhrg
+ * @version $Revision: 1.2 $
+ * @see SDODSException
+ * @see DODSException */
+
+public class WrongTypeException extends SDODSException {
+    /** Construct a <code>WrongTypeException</code> with the specified
+     * detail message.
+     * @param s the detail message. */
+    public WrongTypeException(String s) {
+	super(MALFORMED_EXPR, s);
+    }
+} 
diff --git a/dods/dap/Server/package.html b/dods/dap/Server/package.html
new file mode 100644
index 0000000..0c7b747
--- /dev/null
+++ b/dods/dap/Server/package.html
@@ -0,0 +1,8 @@
+<HTML>
+<HEAD>
+<TITLE> The DODS Server classes.</TITLE>
+</HEAD>
+<BODY>
+This package contains the DODS Server classes. These are used directly by DODS by specific implementations of DODS servers to provide the frame work for DODS server behaviours.
+</BODY>
+</HTML>
diff --git a/dods/dap/ServerVersion.java b/dods/dap/ServerVersion.java
new file mode 100644
index 0000000..95f619f
--- /dev/null
+++ b/dods/dap/ServerVersion.java
@@ -0,0 +1,108 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * This class is a convenient place to store the major and minor
+ * version number of the remote server, as well as the full version string.
+ * It is used so that classes which implement ClientIO don't need any knowledge
+ * of the DDS class.
+ *
+ * @version $Revision: 1.3 $
+ * @author jehamby
+ * @see ClientIO
+ * @see DDS
+ */
+public class ServerVersion {
+  /** Major version number. */
+  private int major;
+  /** Minor version number. */
+  private int minor;
+  /** Full version string. */
+  private String versionString;
+
+  /**
+   * Construct a new ServerVersion, setting major and minor version based
+   * on the full version string.
+   *
+   * @param ver the full version string.
+   */
+  public ServerVersion(String ver) {
+    
+    this.versionString = ver;
+    this.major = this.minor = 0;  // set version to default values
+    // search for the String, e.g. DODS/2.15, and set major and minor
+    // accordingly
+    int verIndex = ver.indexOf("/");
+    if (verIndex != -1) {
+      verIndex += 1;  // skip over "/" to number
+      int dotIndex = ver.indexOf('.', verIndex);
+      if (dotIndex != -1) {
+	String majorString = ver.substring(verIndex, dotIndex);
+	major = Integer.parseInt(majorString);
+	String minorString = ver.substring(dotIndex+1);
+	int minorDotIndex = minorString.indexOf('.');
+	if(minorDotIndex != -1) 
+	    minor = Integer.parseInt(minorString.substring(0,minorDotIndex));
+	else 
+	    minor = Integer.parseInt(minorString);
+      }
+    }
+  }
+
+  /**
+   * Construct a new ServerVersion, setting major and minor version explicitly.
+   *
+   * @param major the major version number.
+   * @param minor the minor version number.
+   */
+  public ServerVersion(int major, int minor) {
+    this.versionString = "DODS/" + major + "." + minor;
+    this.major = major;
+    this.minor = minor;
+  }
+
+  /**
+   * Returns the major version number.
+   * @return the major version number.
+   */
+  public final int getMajor() {
+    return major;
+  }
+
+  /** 
+   * Returns the minor version number.
+   * @return the minor version number.
+   */
+  public final int getMinor() {
+    return minor;
+  }
+
+  /**
+   * Returns the full version string.
+   * @return the full version string.
+   */
+  public final String toString() {
+    return versionString;
+  }
+
+  /**
+   * Returns the DODS core version as a <code>String</code>.
+   * This was a convenient place to put this information, rather than
+   * creating a new class.
+   *
+   * @return the current DODS version.
+   */
+  public static String getCurrentVersion() {
+    return "DODS/2.18";
+  }
+}
diff --git a/dods/dap/StatusUI.java b/dods/dap/StatusUI.java
new file mode 100644
index 0000000..e8ed245
--- /dev/null
+++ b/dods/dap/StatusUI.java
@@ -0,0 +1,51 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+
+/**
+ * This interface is implemented by DODS client user interfaces which give
+ * feedback to the user on the status of the current deserialize operation.
+ * The user can also cancel the current deserialize through this interface.
+ *
+ * @version $Revision: 1.1.1.1 $
+ * @author jehamby
+ * @see DataDDS
+ */
+public interface StatusUI {
+  /**
+   * Add bytes to the total deserialize count.  This is called by each
+   * <code>BaseType</code>'s <code>deserialize</code> method to provide the
+   * user with feedback on the number of bytes that have been transferred
+   * so far.  If some future version of DODS provides a correct
+   * Content-Length, then a sophisticated GUI could use this information to
+   * estimate the time remaining to download.
+   *
+   * @param bytes the number of bytes to add.
+   */
+  public void incrementByteCount(int bytes);
+
+  /**
+   * User cancellation status.  This returns true when the user has clicked
+   * the cancel button of a GUI, or false if the download should proceed.  This
+   * is called at various cancellation points throughout the deserialize
+   * process so that the download can be cancelled in an orderly fashion.
+   *
+   * @return true if the download should be cancelled.
+   */
+  public boolean userCancelled();
+
+  /**
+   * Download finished notice.  This allows the GUI to close itself or print
+   * a message to the user that the transfer is finished.
+   */
+  public void finished();
+}
diff --git a/dods/dap/TODO b/dods/dap/TODO
new file mode 100644
index 0000000..64c38d8
--- /dev/null
+++ b/dods/dap/TODO
@@ -0,0 +1,96 @@
+/** This the (albiet primitive) bug tracking system for DODS */
+
+
+(1)    Fix the bug in printval() for DArray, DVector, DList, etc. If it is called prior
+       to reading the object then it throws a NullPointerException. 
+       **** Is This A Bug? I think so, but maybe you are never supposed to call printVal()
+       until after the dataset has been read...
+       
+
+(2)    DGrid.externalize() doesn't properly handle exceptions. 10/25/99
+       *** I just reviewed this code and I don't see the issue that I did earlier.
+       It's possible that there is a problem but I haven't seen it manifest itself
+       since I reported the problem. 12/3/99
+
+	       
+		
+	
+(7)     DDS.functions  --- Implement! 10/25/99
+	 
+(9)     Find ClientIO vectors (????? What the hell does this mean ????) 10/25/99
+
+
+(37)    Need to add a toString() method to all DODS server types (for debugging)
+
+    
+		
+(42)	The parser(s) throw exceptions that are not children of DODSException and thus require
+	special handling. That's BAD! We should revisit that part of the architecture. Maybe
+	we should catch all those pareser exceptions in the parser and make them DODSExceptions.
+		
+		
+46	Think about difficulty of adding caching to DODSServlet
+
+48	Fix up printVal (or toString()?) to work for returning .ascii requests.
+	Maybe a special method os ServerDDS for this???
+	
+49	In DODSservlet implement:
+            doGetDDS()		done
+            doGetDODS()		done
+            doGetHelp()		done
+!!!!!!----> doGetAsc()		Waiting For JAMES!!
+	    doGetVersion()	done
+		
+
+51b     USe getURL() (the c++ one!) to develop a testsuit for the server.
+        Add this to the Java-DODS directory tree in a sensible manner!
+
+53      Fix DDS.clone() so that it might actually work!
+        // What about copying the BaseTypeFactory? 
+        // Do we want a reference to the same one? Or another instance?
+        // Is there a difference? Should we be building the clone
+        // using "new DDS(getFactory())"??
+
+60	Something fundamental wrong with Lists of Sequences. Either should be fixed,
+	or disallowed in the parser as Lists of Sequences are like Lists of Lists 
+	in that they are of unknown size and they re redundant.
+
+61	Java Geturl client cannot parse dds's returned to it from our java server.
+
+62 	C++ geturl cannot handle it when the java server returns (apparently correct) data
+	of the following types:
+
+	Arrays of Sequences (test.51, test.52)
+	Multiply Nested Structures Containing Arrays (test.63, test.64, test.65)
+	
+	*** This may have been fixed by the resolution of (66). Needs testing.
+
+
+
+*63*    Parser (ExprParser) does not allow use to select members of a 
+	Grid by simple name...	(Example: In Java-DODS/dods/dap/Server/test
+	try "make test06c")
+        *** FIXED by jhrg
+	
+	
+*64*	Parser Incorrectly marks multiple members of nested structures when single members
+        are requested (Example: In Java-DODS/dods/dap/Server/test try "make test05a"
+	*** FIXED jgal / ndp
+	
+65	The expect code for sdds-testsuite is broken. In particular, the method geturl_dds() in
+	config/unix.exp is passing \[ to the server instead of [ ...  oops, better fix that...
+		
+*66*	The constrained DDS for sequences containing grids contains the grid declarations
+	(minus their contents), when the grids are not projected. OOPS!
+	*** FIXED - bug in SDArray.printDecl()
+
+*67*    The ExprParser doesn't like strings in the constraint expressions.
+        see dods/dap/Server/test/make test07c
+        see dods/dap/Server/test/make test07d
+        see dods/dap/Server/sql/make test10
+        see dods/dap/Server/sql/make test11
+	*** FIXED by jhrg
+	
+	
+LastEntry: 66
+     
diff --git a/dods/dap/UInt16PrimitiveVector.java b/dods/dap/UInt16PrimitiveVector.java
new file mode 100644
index 0000000..1277504
--- /dev/null
+++ b/dods/dap/UInt16PrimitiveVector.java
@@ -0,0 +1,81 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+//
+// Based on source code and instructions from the work of:
+//
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of unsigned ints.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ * @see PrimitiveVector
+ */
+public class UInt16PrimitiveVector extends Int16PrimitiveVector {
+  /** Constructs a new <code>UInt16PrimitiveVector</code>. */
+  public UInt16PrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = getLength();
+    for(int i=0; i<len-1; i++) {
+      // to print properly, cast to long and convert to unsigned
+      os.print(((long)getValue(i)) & 0xFFFFL);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(((long)getValue(len-1)) & 0xFFFFL);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+ public void printSingleVal(PrintWriter os, int index) {
+    // to print properly, cast to long and convert to unsigned
+    os.print(((long)getValue(index)) & 0xFFFFL);
+  }
+}
diff --git a/dods/dap/UInt32PrimitiveVector.java b/dods/dap/UInt32PrimitiveVector.java
new file mode 100644
index 0000000..d86bc7e
--- /dev/null
+++ b/dods/dap/UInt32PrimitiveVector.java
@@ -0,0 +1,62 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.io.*;
+
+/**
+ * A vector of unsigned ints.
+ *
+ * @version $Revision: 1.1.1.1 $
+ * @author jehamby
+ * @see PrimitiveVector
+ */
+public class UInt32PrimitiveVector extends Int32PrimitiveVector {
+  /** Constructs a new <code>UInt32PrimitiveVector</code>. */
+  public UInt32PrimitiveVector(BaseType var) {
+    super(var);
+  }
+
+  /**
+   * Prints the value of all variables in this vector.  This
+   * method is primarily intended for debugging DODS applications and
+   * text-based clients such as geturl.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param space this value is passed to the <code>printDecl</code> method,
+   *    and controls the leading spaces of the output.
+   * @see BaseType#printVal(PrintWriter, String, boolean)
+   */
+  public void printVal(PrintWriter os, String space) {
+    int len = getLength();
+    for(int i=0; i<len-1; i++) {
+      // to print properly, cast to long and convert to unsigned
+      os.print(((long)getValue(i)) & 0xFFFFFFFFL);
+      os.print(", ");
+    }
+    // print last value, if any, without trailing comma
+    if(len > 0)
+      os.print(((long)getValue(len-1)) & 0xFFFFFFFFL);
+  }
+
+  /**
+   * Prints the value of a single variable in this vector.
+   * method is used by <code>DArray</code>'s <code>printVal</code> method.
+   *
+   * @param os the <code>PrintWriter</code> on which to print the value.
+   * @param index the index of the variable to print.
+   * @see DArray#printVal(PrintWriter, String, boolean)
+   */
+ public void printSingleVal(PrintWriter os, int index) {
+    // to print properly, cast to long and convert to unsigned
+    os.print(((long)getValue(index)) & 0xFFFFFFFFL);
+  }
+}
diff --git a/dods/dap/UIntTest.java b/dods/dap/UIntTest.java
new file mode 100644
index 0000000..95b4fbf
--- /dev/null
+++ b/dods/dap/UIntTest.java
@@ -0,0 +1,113 @@
+package dods.dap; // JC-CHANGED
+import java.io.*;
+
+class UIntTest {
+
+
+        UIntTest(){
+        }
+
+
+    public void sendIt(DataOutputStream fp)throws Exception{
+
+            short   s;
+            byte    b;
+            int     i;
+            long    l;
+
+            s = ((short)65500);
+            System.out.println("\nShort assigned to 65500.    System thinks of it as: " + s);
+            fp.writeShort(s);
+            System.out.println("Wrote it to disk. ");
+
+            s = ((short)65537);
+            System.out.println("\nShort assigned to 65537.    System thinks of it as: " + s);
+            fp.writeShort(s);
+            System.out.println("Wrote it to disk. ");
+
+
+
+            i = ((int)4294967040L);
+            System.out.println("\nInt assigned to 4294967040. System thinks of it as: " + i);
+            fp.writeInt(i);
+            System.out.println("Wrote it to disk. ");
+
+            i = ((int)4294967298L);
+            System.out.println("\nInt assigned to 4294967298. System thinks of it as: " + i);
+            fp.writeInt(i);
+            System.out.println("Wrote it to disk. ");
+
+        }
+
+
+     public void getIt(DataInputStream fp)throws Exception{
+
+            short   s;
+            int     i1, i2;
+            long    l;
+
+
+            System.out.println("\nReading data...");
+            s  = fp.readShort();
+            System.out.println("System read short from file as: " + s);
+            i1 = ((int)s);
+            System.out.println("Converted short to int: " + i1);
+            i1 = i1 & 0xFFFF;
+            System.out.println("And'd with 0xFFFF (represented as an int in memory): " + i1);
+
+            System.out.println("\nReading data...");
+            s  = fp.readShort();
+            System.out.println("System read short from file as: " + s);
+            i1 = ((int)s);
+            System.out.println("Converted short to int: " + i1);
+            i1 = i1 & 0xFFFF;
+            System.out.println("And'd with 0xFFFF (represented as an int in memory): " + i1);
+
+
+
+
+            System.out.println("\nReading data...");
+            i2 = fp.readInt();
+            System.out.println("\nSystem read int from file as: " + i2);
+            l = ((long)i2);
+            System.out.println("Converted int to long: " + l);
+            l = l & 0xFFFFFFFFL;
+            System.out.println("And'd with 0xFFFFFFFFL (represented as a long in memory): " + l);
+
+            System.out.println("\nReading data...");
+            i2 = fp.readInt();
+            System.out.println("\nSystem read int from file as: " + i2);
+            l = ((long)i2);
+            System.out.println("Converted int to long: " + l);
+            l = l & 0xFFFFFFFFL;
+            System.out.println("And'd with 0xFFFFFFFFL (represented as a long in memory): " + l);
+
+
+
+
+        }
+
+
+
+
+
+        static public void main(String args[]) throws Exception {
+
+            UIntTest b = new UIntTest();
+            File f = new File("UIntTest.bin");
+            FileOutputStream fp = new FileOutputStream(f);
+            DataOutputStream sink = new DataOutputStream(fp);
+
+            b.sendIt(sink);
+            sink.close();
+
+            FileInputStream ifp = new FileInputStream(f);
+            DataInputStream source = new DataInputStream(ifp);
+
+            b.getIt(source);
+            source.close();
+
+        }
+
+}
+
diff --git a/dods/dap/Util.java b/dods/dap/Util.java
new file mode 100644
index 0000000..e91be15
--- /dev/null
+++ b/dods/dap/Util.java
@@ -0,0 +1,171 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+ * The Util class holds static methods used by this package.
+ *
+ * @version $Revision: 1.1.1.1 $
+ * @author jehamby
+ */
+class Util {
+  /**
+   * Compares elements in a <code>Vector</code> of <code>BaseType</code>s and
+   * throw a <code>BadSemanticsException</code> if there are any
+   * duplicate elements.
+   *
+   * @param v The <code>Vector</code> to check
+   * @param varName the name of the variable which called us
+   * @param typeName the type name of the variable which called us
+   * @exception BadSemanticsException if there are duplicate elements
+   * @exception IndexOutOfBoundsException if size doesn't match the number
+   *      of elements in the <code>Enumeration</code>
+   */
+  static void uniqueNames(Vector v, String varName,
+			  String typeName) throws BadSemanticsException {
+    String[] names = sortedNames(v);
+    // DEBUG: print out names
+    //for(int i=0; i<names.length; i++) {
+    //  System.err.println("names[" + i + "] = " + names[i]);
+    //}
+    // look for any instance of consecutive names that are ==
+    for(int i=1; i<names.length; i++) {
+      if (names[i-1].equals(names[i])) {
+	throw new BadSemanticsException("The variable `" + names[i] +
+		  "' is used more than once in " + typeName + " `" +
+		  varName + "'");
+      }
+    }
+  }
+
+  /**
+   * Takes a <code>Vector</code> of <code>BaseType</code>s, retrieves their
+   * names into an array of <code>String</code>s, and performs a Quick Sort
+   * on that array.
+   *
+   * @param v The Vector to check
+   * @return a sorted array of <code>String</code>
+   * @exception BadSemanticsException if there is an element with no name
+   */
+  static String[] sortedNames(Vector v) throws BadSemanticsException {
+    String[] names = new String[v.size()];
+    int count = 0;
+    for(Enumeration e = v.elements(); e.hasMoreElements(); ) {
+      BaseType bt = (BaseType)e.nextElement();
+      String tempName = bt.getName();
+      if(tempName == null)
+	throw new BadSemanticsException(bt.getClass().getName() + " variable with no name");
+      names[count++] = tempName;
+    }
+    // DEBUG: print out names
+    //for(int i=0; i<names.length; i++) {
+    //  System.err.println("names[" + i + "] = " + names[i]);
+    //}
+    // assert that size is correct
+    if (count != names.length)
+      throw new IndexOutOfBoundsException("Vector size changed unexpectedly");
+    quickSort(names, 0, names.length-1);
+    return names;
+  }
+
+  /**
+   * Internal recursive method to perform Quick Sort on name array.
+   *
+   * @param a an array of <code>String</code>.
+   * @param lo0 the low index to sort.
+   * @param hi0 the high index to sort.
+   */
+  static private void quickSort(String a[], int lo0, int hi0) {
+    int lo = lo0;
+    int hi = hi0;
+    String mid;
+
+    if (hi0 > lo0) {
+      // Arbitrarily establishing partition element as the array midpoint */
+      mid = a[(lo0 + hi0)/2];
+
+      // loop through the array until indices cross
+      while(lo <= hi) {
+	// find the first element that is >= the partition element
+	// starting from the left index.
+	while ((lo < hi0) && (a[lo].compareTo(mid) < 0))
+	  ++lo;
+
+	// find an element that is <= the partition element
+	// starting from the right index.
+	while ((hi > lo0) && (a[hi].compareTo(mid) > 0))
+	  --hi;
+
+	// if the indexes have not crossed, swap
+	if (lo <= hi) {
+	  swap(a, lo, hi);
+	  ++lo;
+	  --hi;
+	}
+      }
+      // If the right index has not reached the left side of array,
+      // sort the left partition.
+      if (lo0 < hi)
+	quickSort(a, lo0, hi);
+
+      // If the left index has not reached the right side of array,
+      // sort the right partition.
+      if (lo < hi0)
+	quickSort(a, lo, hi0);
+    }
+  }
+
+  /**
+   * Private method to swap two elements in the array
+   * @param a an array of <code>String</code>.
+   * @param i the index of the first element.
+   * @param j the index of the second element.
+   */
+  static private void swap(String a[], int i, int j) {
+    String T;
+    T = a[i];
+    a[i] = a[j];
+    a[j] = T;
+  }
+
+  /**
+   * This function escapes non-printable characters and quotes.  This is used
+   * to make <code>printVal</code> output <code>DString</code> data in the
+   * same way as the C++ version.  Since Java supports Unicode, this will
+   * need to be altered if it's desired to print <code>DString</code> as
+   * UTF-8 or some other character encoding.
+   *
+   * @param s the input <code>String</code>.
+   * @return the escaped <code>String</code>.
+   */
+  static String escattr(String s) {
+    StringBuffer buf = new StringBuffer(s.length());
+    for(int i=0; i<s.length(); i++) {
+      char c = s.charAt(i);
+      if(c == ' ' || (c >= '!' && c <= '~')) {
+	// printable ASCII character
+	buf.append(c);
+      } else {
+	// non-printable ASCII character: print as unsigned octal integer
+	// padded with leading zeros
+	buf.append('\\');
+	String numVal = Integer.toString((int)c & 0xFF, 8);
+	for(int pad=0; pad<(3-numVal.length()); pad++)
+	  buf.append('0');
+	buf.append(numVal);
+      }
+    }
+    return buf.toString();
+  }
+}
diff --git a/dods/dap/functions/Length.java b/dods/dap/functions/Length.java
new file mode 100644
index 0000000..eb3105d
--- /dev/null
+++ b/dods/dap/functions/Length.java
@@ -0,0 +1,30 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, University of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: James Gallagher <jgallagher at gso.uri.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.dap.functions;
+import dods.dap.*;
+
+/** This class implements the length CE function which is used to return the
+    length of List variables. Note that this is the prototypical CE function
+    implementation. The function `length' is implemented in class
+    `dods.dap.functions.Length' which has one method called main which takes
+    an array of BaseType objects and returns its value in a BaseType object.
+    See: CEEvaluator and FunctionClause code.
+    @author jhrg
+    @version $Revision: 1.3 $
+*/
+public class Length {
+    public final static BaseType main(BaseType args[])  {
+	// There must be exactly one argument to this function and it must be
+	// a List variable.
+	return new DInt32("Length_is_unimplemented");
+    }
+}
diff --git a/dods/dap/package.html b/dods/dap/package.html
new file mode 100644
index 0000000..88761f2
--- /dev/null
+++ b/dods/dap/package.html
@@ -0,0 +1,8 @@
+<HTML>
+<HEAD>
+<TITLE> The DODS core classes.</TITLE>
+</HEAD>
+<BODY>
+This package contains the DODS core classes, known as the Data Access Protocol (DAP). These are used directly by DODS clients, and are the parents of the DODS server types
+</BODY>
+</HTML>
diff --git a/dods/dap/parser/DASParser.java b/dods/dap/parser/DASParser.java
new file mode 100644
index 0000000..c05afc6
--- /dev/null
+++ b/dods/dap/parser/DASParser.java
@@ -0,0 +1,1087 @@
+/* Generated By:JavaCC: Do not edit this line. DASParser.java */
+package dods.dap.parser;
+
+import java.util.Stack;
+import dods.dap.*;
+
+public class DASParser implements DASParserConstants {
+    /* $Id: DASParser.java,v 1.4 2007-04-16 16:40:53 tomw Exp $ */
+    private DAS das;
+    private Stack stack;
+    private String name;
+    private int type;
+
+    private static final String attrTupleMsg =
+    "Error: Expected an attribute type. Such as Byte, Int32, String, et c.\n"
+    + "followed by a name and value.\n";
+
+    private static final String noDASMsg =
+    "The attribute object returned from the dataset was null\n"
+    + "Check that the URL is correct.";
+
+    /** Return the topmost AttributeTable on the stack. */
+    private final AttributeTable topOfStack() {
+        return (AttributeTable)stack.peek();
+    }
+
+    /** Is the stack empty? */
+    private final boolean isStackEmpty() {
+        return stack.isEmpty();
+    }
+
+    /** Return the rightmost component of name (separated by '.'). */
+    private final String attrName(String name) {
+        int i = name.lastIndexOf(".");
+        if (i==-1)
+            return name;
+        else
+            return name.substring(i+1);
+    }
+
+  final public void Attributes(DAS das) throws ParseException, DASException {
+    this.das = das;
+    this.stack = new Stack();
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ATTR:
+        label_1:
+        while (true) {
+          Attribute();
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case ATTR:
+            ;
+            break;
+          default:
+            jj_la1[0] = jj_gen;
+            break label_1;
+          }
+        }
+        break;
+      default:
+        jj_la1[1] = jj_gen;
+        error(noDASMsg);
+      }
+    } catch (TokenMgrError e) {
+        error("Error parsing the Attribute object:\n"
+              + e.getMessage() + "\n");
+    } catch (ParseException e) {
+        error("Error parsing the Attribute object:\n"
+              + e.getMessage() + "\n");
+    }
+  }
+
+  final public void Attribute() throws ParseException, DASException {
+    jj_consume_token(ATTR);
+    jj_consume_token(19);
+    AttrList();
+    jj_consume_token(20);
+  }
+
+  final public void AttrList() throws ParseException, DASException {
+    label_2:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ATTR:
+      case ALIAS:
+      case BYTE:
+      case INT16:
+      case UINT16:
+      case INT32:
+      case UINT32:
+      case FLOAT32:
+      case FLOAT64:
+      case STRING:
+      case URL:
+      case WORD:
+        ;
+        break;
+      default:
+        jj_la1[2] = jj_gen;
+        break label_2;
+      }
+      AttrTuple();
+    }
+  }
+
+  final public void AttrTuple() throws ParseException, DASException {
+    Token t = new Token();
+    try {
+      if (jj_2_1(2)) {
+        Alias();
+      } else if (jj_2_2(2)) {
+        jj_consume_token(BYTE);
+                                 type = Attribute.BYTE;
+        t = Name();
+                    name = t.image;
+        Bytes();
+        label_3:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[3] = jj_gen;
+            break label_3;
+          }
+          jj_consume_token(21);
+          Bytes();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_3(2)) {
+        jj_consume_token(INT16);
+                                  type = Attribute.INT16;
+        t = Name();
+                    name = t.image;
+        Ints();
+        label_4:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[4] = jj_gen;
+            break label_4;
+          }
+          jj_consume_token(21);
+          Ints();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_4(2)) {
+        jj_consume_token(UINT16);
+                                   type = Attribute.UINT16;
+        t = Name();
+                    name = t.image;
+        Ints();
+        label_5:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[5] = jj_gen;
+            break label_5;
+          }
+          jj_consume_token(21);
+          Ints();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_5(2)) {
+        jj_consume_token(INT32);
+                                  type = Attribute.INT32;
+        t = Name();
+                    name = t.image;
+        Ints();
+        label_6:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[6] = jj_gen;
+            break label_6;
+          }
+          jj_consume_token(21);
+          Ints();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_6(2)) {
+        jj_consume_token(UINT32);
+                                   type = Attribute.UINT32;
+        t = Name();
+                    name = t.image;
+        Ints();
+        label_7:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[7] = jj_gen;
+            break label_7;
+          }
+          jj_consume_token(21);
+          Ints();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_7(2)) {
+        jj_consume_token(FLOAT32);
+                                    type = Attribute.FLOAT32;
+        t = Name();
+                    name = t.image;
+        Floats();
+        label_8:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[8] = jj_gen;
+            break label_8;
+          }
+          jj_consume_token(21);
+          Floats();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_8(2)) {
+        jj_consume_token(FLOAT64);
+                                    type = Attribute.FLOAT64;
+        t = Name();
+                    name = t.image;
+        Floats();
+        label_9:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[9] = jj_gen;
+            break label_9;
+          }
+          jj_consume_token(21);
+          Floats();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_9(2)) {
+        jj_consume_token(STRING);
+                                   type = Attribute.STRING;
+        t = Name();
+                    name = t.image;
+        Strs();
+        label_10:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[10] = jj_gen;
+            break label_10;
+          }
+          jj_consume_token(21);
+          Strs();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_10(2)) {
+        jj_consume_token(URL);
+                                type = Attribute.URL;
+        t = Name();
+                    name = t.image;
+        Urls();
+        label_11:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case 21:
+            ;
+            break;
+          default:
+            jj_la1[11] = jj_gen;
+            break label_11;
+          }
+          jj_consume_token(21);
+          Urls();
+        }
+        jj_consume_token(22);
+      } else if (jj_2_11(2)) {
+        t = Name();
+             AttributeTable at;
+             if (isStackEmpty()) {
+                 at = das.getAttributeTable(t.image);
+                 if (at == null) {
+                     at = new AttributeTable(t.image);
+                     das.addAttributeTable(t.image, at);
+                 }
+             } else {
+                 Attribute a = topOfStack().getAttribute(t.image);
+                 if (a == null) {
+                     at = topOfStack().appendContainer(t.image);
+                 } else {
+                     at = a.getContainer();
+                 }
+             }
+             stack.push(at);
+        jj_consume_token(19);
+        AttrList();
+                stack.pop();
+        jj_consume_token(20);
+      } else {
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    } catch (ParseException e) {
+        error(attrTupleMsg + "\n"
+              + "The offending line contained the token: '" + t + "'\n"
+              + "ParseException Message: '" + e.getMessage() + "'\n");
+    }
+  }
+
+  final public void Bytes() throws ParseException, DASException {
+    Token t;
+    t = jj_consume_token(WORD);
+        addAttribute(type, name, t.image);
+  }
+
+  final public void Ints() throws ParseException, DASException {
+    Token t;
+    t = jj_consume_token(WORD);
+        addAttribute(type, name, t.image);
+  }
+
+  final public void Floats() throws ParseException, DASException {
+    Token t;
+    t = jj_consume_token(WORD);
+        addAttribute(type, name, t.image);
+  }
+
+  final public void Strs() throws ParseException, DASException {
+    Token t;
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case WORD:
+        t = jj_consume_token(WORD);
+            addAttribute(type, name, t.image);
+        break;
+      case STR:
+        t = jj_consume_token(STR);
+            addAttribute(type, name, t.image);
+        break;
+      default:
+        jj_la1[12] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    } catch (TokenMgrError e) {
+        // If we get an exception thrown inside a quoted string then assume
+        // that the scanner has found EOF before the token (STR) ended (i.e.
+        // we have an unterminated double quote on our hands). 5/29/2002 jhrg
+        error("Unterminated quote: " + e.getMessage() + ")");
+    }
+  }
+
+  final public void Urls() throws ParseException, DASException {
+    Strs();
+  }
+
+  final public void Alias() throws ParseException, DASException {
+    Token t;
+    String alias = "";
+    String attr = "";
+    try {
+      jj_consume_token(ALIAS);
+      t = jj_consume_token(WORD);
+                           alias = t.image;
+      t = jj_consume_token(WORD);
+            attr = t.image;
+            if (isStackEmpty()) {
+                AttributeTable at = das.getAttributeTable(attr);
+                // Note: this won't show up as an Alias when printing the DAS!
+                das.addAttributeTable(alias, at);
+            }
+            else {
+                topOfStack().addAlias(alias, attr);
+            }
+      jj_consume_token(22);
+    } catch (NoSuchAttributeException e) {
+        error("Error: The attribute " + attr + " does not exist.");
+    } catch (AttributeExistsException e) {
+        error("Error: The alias " + alias + " already exists in this DAS.");
+    }
+  }
+
+  final public Token Name() throws ParseException, DASException {
+    Token t;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case WORD:
+      t = jj_consume_token(WORD);
+               {if (true) return t;}
+      break;
+    case ATTR:
+      t = jj_consume_token(ATTR);
+                 {if (true) return t;}
+      break;
+    case ALIAS:
+      t = jj_consume_token(ALIAS);
+                  {if (true) return t;}
+      break;
+    case BYTE:
+      t = jj_consume_token(BYTE);
+                 {if (true) return t;}
+      break;
+    case INT16:
+      t = jj_consume_token(INT16);
+                  {if (true) return t;}
+      break;
+    case UINT16:
+      t = jj_consume_token(UINT16);
+                   {if (true) return t;}
+      break;
+    case INT32:
+      t = jj_consume_token(INT32);
+                  {if (true) return t;}
+      break;
+    case UINT32:
+      t = jj_consume_token(UINT32);
+                   {if (true) return t;}
+      break;
+    case FLOAT32:
+      t = jj_consume_token(FLOAT32);
+                    {if (true) return t;}
+      break;
+    case FLOAT64:
+      t = jj_consume_token(FLOAT64);
+                    {if (true) return t;}
+      break;
+    case STRING:
+      t = jj_consume_token(STRING);
+                   {if (true) return t;}
+      break;
+    case URL:
+      t = jj_consume_token(URL);
+                {if (true) return t;}
+      break;
+    default:
+      jj_la1[13] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  void error(String msg) throws ParseException, DASException {
+    throw new DASException(DODSException.MALFORMED_EXPR,msg);
+  }
+
+  void addAttribute(int type, String name, String value) throws ParseException, DASException {
+    try {
+        if (isStackEmpty()) {
+            String msg = "Whoa! Attribute table stack empty when adding `"
+                + name +".'";
+            error(msg);
+        }
+
+        // appendAttribute throws a variety of DASExceptions if the attribute
+        // tuple is bad. This includes throwing AttribtueBadValueException if
+        // the value is bad (see the private method dispatchCheckValue()).
+        // 5/23/2002 jhrg
+        //System.err.println("Calling appendAttribute (name, type, value): "
+        //		   + name + ", " + type + ", " + value);
+        topOfStack().appendAttribute(name, type, value);
+    }
+    // If the attribute value is bad (the exception thrown by
+    // dispatchCheckValue() above) then add this attribute as a 'Bad
+    // Attribute.'
+    catch (AttributeBadValueException e) {
+        // System.err.println("Caught an AttributeBadValueException");
+        String msg = "`" + value + "' is not " + aOrAn(getTypeName(type))
+            + " " + getTypeName(type) + " value.";
+        addBadAttribute(topOfStack().getName(), type, name, value, msg);
+    }
+  }
+
+  void addBadAttribute(String container_name, int type, String name, String value,
+                String msg) throws ParseException, DASException {
+    String errorContainerName = container_name + "_dods_errors";
+
+    // First, if this bad value is already in a *_dods_errors container,
+    // then just add it. This can happen when the server side processes a DAS
+    // and then hands it off to a client which does the same. The false value
+    // for arg four below supresses checking the value of the attribute
+    // (since we know it's bad and don't want the exception to be generated
+    // again). 
+    if (topOfStack().getName().equals(errorContainerName)) {
+        topOfStack().appendAttribute(name, type, value, false);
+    }
+    // Otherwise, make a new container. Call it <attr's name>_errors. If that
+    // container already exists, use it. Add the attribute. Add the error
+    // string to an attribute in the container called `<name_explanation.'.
+    else {
+        // Does the error container alreay exist? 
+        AttributeTable errorContainer = null;
+        Attribute a = topOfStack().getAttribute(errorContainerName);
+        if (a != null)
+            errorContainer = a.getContainer(); // get value as container
+        else
+            errorContainer = topOfStack().appendContainer(errorContainerName);
+
+        // Arg four == false --> supress type/value checking.
+        errorContainer.appendAttribute(name, type, value, false);
+        errorContainer.appendAttribute(name + "_explanation",
+                                       dods.dap.Attribute.STRING,
+                                       "\"" + msg + "\"");
+    }
+  }
+
+  String aOrAn(String subject) throws ParseException {
+    String vowels = "aeiouAEIOUyY";
+    if (vowels.indexOf(subject.charAt(1)) >= 0)
+        return "an";
+    else
+        return "a";
+  }
+
+  String getTypeName(int type) throws ParseException {
+    switch(type) {
+    case Attribute.CONTAINER: return "Container";
+    case Attribute.BYTE: return "Byte";
+    case Attribute.INT16: return "Int16";
+    case Attribute.UINT16: return "UInt16";
+    case Attribute.INT32: return "Int32";
+    case Attribute.UINT32: return "UInt32";
+    case Attribute.FLOAT32: return "Float32";
+    case Attribute.FLOAT64: return "Float64";
+    case Attribute.STRING: return "String";
+    case Attribute.URL: return "Url";
+    default: return "";
+    }
+  }
+
+  final private boolean jj_2_1(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_1();
+    jj_save(0, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_2(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_2();
+    jj_save(1, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_3(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_3();
+    jj_save(2, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_4(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_4();
+    jj_save(3, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_5(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_5();
+    jj_save(4, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_6(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_6();
+    jj_save(5, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_7(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_7();
+    jj_save(6, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_8(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_8();
+    jj_save(7, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_9(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_9();
+    jj_save(8, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_10(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_10();
+    jj_save(9, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_11(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_11();
+    jj_save(10, xla);
+    return retval;
+  }
+
+  final private boolean jj_3R_13() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3R_14()) {
+    jj_scanpos = xsp;
+    if (jj_3R_15()) {
+    jj_scanpos = xsp;
+    if (jj_3R_16()) {
+    jj_scanpos = xsp;
+    if (jj_3R_17()) {
+    jj_scanpos = xsp;
+    if (jj_3R_18()) {
+    jj_scanpos = xsp;
+    if (jj_3R_19()) {
+    jj_scanpos = xsp;
+    if (jj_3R_20()) {
+    jj_scanpos = xsp;
+    if (jj_3R_21()) {
+    jj_scanpos = xsp;
+    if (jj_3R_22()) {
+    jj_scanpos = xsp;
+    if (jj_3R_23()) {
+    jj_scanpos = xsp;
+    if (jj_3R_24()) {
+    jj_scanpos = xsp;
+    if (jj_3R_25()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_14() {
+    if (jj_scan_token(WORD)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_7() {
+    if (jj_scan_token(FLOAT32)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_6() {
+    if (jj_scan_token(UINT32)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_5() {
+    if (jj_scan_token(INT32)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_4() {
+    if (jj_scan_token(UINT16)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_3() {
+    if (jj_scan_token(INT16)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_2() {
+    if (jj_scan_token(BYTE)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_1() {
+    if (jj_3R_12()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_12() {
+    if (jj_scan_token(ALIAS)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(WORD)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_11() {
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(19)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_25() {
+    if (jj_scan_token(URL)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_24() {
+    if (jj_scan_token(STRING)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_23() {
+    if (jj_scan_token(FLOAT64)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_10() {
+    if (jj_scan_token(URL)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_22() {
+    if (jj_scan_token(FLOAT32)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_21() {
+    if (jj_scan_token(UINT32)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_20() {
+    if (jj_scan_token(INT32)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_19() {
+    if (jj_scan_token(UINT16)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_9() {
+    if (jj_scan_token(STRING)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_18() {
+    if (jj_scan_token(INT16)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_17() {
+    if (jj_scan_token(BYTE)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_16() {
+    if (jj_scan_token(ALIAS)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_8() {
+    if (jj_scan_token(FLOAT64)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_15() {
+    if (jj_scan_token(ATTR)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  public DASParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private Token jj_scanpos, jj_lastpos;
+  private int jj_la;
+  public boolean lookingAhead = false;
+  private boolean jj_semLA;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[14];
+  final private int[] jj_la1_0 = {0x40,0x40,0x3ffc0,0x200000,0x200000,0x200000,0x200000,0x200000,0x200000,0x200000,0x200000,0x200000,0x60000,0x3ffc0,};
+  final private JJCalls[] jj_2_rtns = new JJCalls[11];
+  private boolean jj_rescan = false;
+  private int jj_gc = 0;
+
+  public DASParser(java.io.InputStream stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new DASParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 14; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 14; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public DASParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new DASParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 14; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 14; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public DASParser(DASParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 14; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(DASParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 14; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      if (++jj_gc > 100) {
+        jj_gc = 0;
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+          JJCalls c = jj_2_rtns[i];
+          while (c != null) {
+            if (c.gen < jj_gen) c.first = null;
+            c = c.next;
+          }
+        }
+      }
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  final private boolean jj_scan_token(int kind) {
+    if (jj_scanpos == jj_lastpos) {
+      jj_la--;
+      if (jj_scanpos.next == null) {
+        jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+      } else {
+        jj_lastpos = jj_scanpos = jj_scanpos.next;
+      }
+    } else {
+      jj_scanpos = jj_scanpos.next;
+    }
+    if (jj_rescan) {
+      int i = 0; Token tok = token;
+      while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+      if (tok != null) jj_add_error_token(kind, i);
+    }
+    return (jj_scanpos.kind != kind);
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = lookingAhead ? jj_scanpos : token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.Vector jj_expentries = new java.util.Vector();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+  private int[] jj_lasttokens = new int[100];
+  private int jj_endpos;
+
+  private void jj_add_error_token(int kind, int pos) {
+    if (pos >= 100) return;
+    if (pos == jj_endpos + 1) {
+      jj_lasttokens[jj_endpos++] = kind;
+    } else if (jj_endpos != 0) {
+      jj_expentry = new int[jj_endpos];
+      for (int i = 0; i < jj_endpos; i++) {
+        jj_expentry[i] = jj_lasttokens[i];
+      }
+      boolean exists = false;
+      for (java.util.Enumeration enumx = jj_expentries.elements(); enumx.hasMoreElements();) {
+        int[] oldentry = (int[])(enumx.nextElement());
+        if (oldentry.length == jj_expentry.length) {
+          exists = true;
+          for (int i = 0; i < jj_expentry.length; i++) {
+            if (oldentry[i] != jj_expentry[i]) {
+              exists = false;
+              break;
+            }
+          }
+          if (exists) break;
+        }
+      }
+      if (!exists) jj_expentries.addElement(jj_expentry);
+      if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+    }
+  }
+
+  final public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[23];
+    for (int i = 0; i < 23; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 14; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 23; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    jj_endpos = 0;
+    jj_rescan_token();
+    jj_add_error_token(0, 0);
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = (int[])jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+  final private void jj_rescan_token() {
+    jj_rescan = true;
+    for (int i = 0; i < 11; i++) {
+      JJCalls p = jj_2_rtns[i];
+      do {
+        if (p.gen > jj_gen) {
+          jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+          switch (i) {
+            case 0: jj_3_1(); break;
+            case 1: jj_3_2(); break;
+            case 2: jj_3_3(); break;
+            case 3: jj_3_4(); break;
+            case 4: jj_3_5(); break;
+            case 5: jj_3_6(); break;
+            case 6: jj_3_7(); break;
+            case 7: jj_3_8(); break;
+            case 8: jj_3_9(); break;
+            case 9: jj_3_10(); break;
+            case 10: jj_3_11(); break;
+          }
+        }
+        p = p.next;
+      } while (p != null);
+    }
+    jj_rescan = false;
+  }
+
+  final private void jj_save(int index, int xla) {
+    JJCalls p = jj_2_rtns[index];
+    while (p.gen > jj_gen) {
+      if (p.next == null) { p = p.next = new JJCalls(); break; }
+      p = p.next;
+    }
+    p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+  }
+
+  static final class JJCalls {
+    int gen;
+    Token first;
+    int arg;
+    JJCalls next;
+  }
+
+}
diff --git a/dods/dap/parser/DASParser.jj b/dods/dap/parser/DASParser.jj
new file mode 100644
index 0000000..067fede
--- /dev/null
+++ b/dods/dap/parser/DASParser.jj
@@ -0,0 +1,438 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+//
+// -- 7/14/99 Modified by: Nathan Potter (ndp at oce.orst.edu)
+// Added Support For DInt16, DUInt16, DFloat32.
+// Added (and commented out) support for DBoolean.
+// -- 7/14/99 ndp 
+//  
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+options {
+    STATIC = false;  // support multithreaded clients/servers
+    DEBUG_PARSER = false;
+}
+
+PARSER_BEGIN(DASParser)
+
+package dods.dap.parser;
+
+import java.util.Stack;
+import dods.dap.*;
+
+public class DASParser {
+    /* $Id: DASParser.jj,v 1.3 2004-02-06 15:23:48 donm Exp $ */
+    private DAS das;
+    private Stack stack;
+    private String name;
+    private int type;
+
+    private static final String attrTupleMsg = 
+    "Error: Expected an attribute type. Such as Byte, Int32, String, et c.\n"
+    + "followed by a name and value.\n";
+
+    private static final String noDASMsg =
+    "The attribute object returned from the dataset was null\n"
+    + "Check that the URL is correct.";
+
+    /** Return the topmost AttributeTable on the stack. */
+    private final AttributeTable topOfStack() {
+	return (AttributeTable)stack.peek();
+    }
+
+    /** Is the stack empty? */
+    private final boolean isStackEmpty() {
+	return stack.isEmpty();
+    }
+
+    /** Return the rightmost component of name (separated by '.'). */
+    private final String attrName(String name) {
+	int i = name.lastIndexOf(".");
+	if (i==-1)
+	    return name;
+	else
+	    return name.substring(i+1);
+    }
+}
+
+PARSER_END(DASParser)
+
+SKIP : {
+ " "
+| "\t"
+| "\n"
+| "\r"
+| < "#" (~["\n","\r"])* >
+}
+
+TOKEN : {
+	<ATTR: "attributes"|"Attributes"|"ATTRIBUTES"> |
+	<ALIAS: "ALIAS"|"Alias"|"alias"> |
+	<BYTE: "BYTE"|"Byte"|"byte"> |
+	<INT16: "INT16"|"Int16"|"int16"> |
+	<UINT16: "UINT16"|"UInt16"|"Uint16"|"uint16"> |
+	<INT32: "INT32"|"Int32"|"int32"> |
+	<UINT32: "UINT32"|"UInt32"|"Uint32"|"uint32"> |
+	<FLOAT32: "FLOAT32"|"Float32"|"float32"> |
+	<FLOAT64: "FLOAT64"|"Float64"|"float64"> |
+	<STRING: "STRING"|"String"|"string"> |
+	<URL: "URL"|"Url"|"url"> |
+
+        <WORD:
+	    ["-","+","a"-"z","A"-"Z","0"-"9","_","/","%",".",":","\\","(",")"](["-","+","a"-"z","A"-"Z","0"-"9","_","/","%",".",":","\\","(",")","#"])*> |
+
+	// Got this from Tim Pugh it seems to work... ndp 3/26/02. 
+	// Hacked 5/23/2002 jhrg
+	< STR:
+	  "\""
+	  // (   (~["\"","\\","\n","\r"])
+	  (   (~["\"","\\"])	// Allow newline and return in strings.
+				// 5/23/2002 jhrg
+	    | ("\\"
+		( ["n","t","b","r","f","\\","'","\""]
+		| ["0"-"7"] ( ["0"-"7"] )?
+		| ["0"-"3"] ["0"-"7"] ["0"-"7"]
+		)
+	      )
+	  )*
+	  "\"" >
+}
+
+void Attributes(DAS das) throws DASException:
+{
+    this.das = das;
+    this.stack = new Stack();
+}
+{
+    try {
+	(Attribute() )+
+	| error(noDASMsg)
+    }
+    catch (TokenMgrError e) {
+	error("Error parsing the Attribute object:\n" 
+	      + e.getMessage() + "\n");
+    }
+    catch (ParseException e) {
+	error("Error parsing the Attribute object:\n" 
+	      + e.getMessage() + "\n");
+    }
+}
+
+void Attribute() throws DASException :
+{}
+{
+    <ATTR> "{" AttrList() "}"
+}
+
+void AttrList() throws DASException :
+{}
+{
+    (AttrTuple() )*
+}
+
+void AttrTuple() throws DASException :
+{
+    Token t = new Token();
+}
+{
+    // NOTE:  It may be useful to comment out this try/catch block when
+    // debugging this parser to get a more descriptive error message.
+    try {
+	(
+	 LOOKAHEAD(2) Alias()
+
+	 | LOOKAHEAD(2) <BYTE> { type = Attribute.BYTE; }
+	 t=Name() { name = t.image; }
+	 Bytes() ( "," Bytes() )* ";"
+
+	 | LOOKAHEAD(2) <INT16> { type = Attribute.INT16; }
+	 t=Name() { name = t.image; }
+	 Ints() ( "," Ints() )* ";"
+
+	 | LOOKAHEAD(2) <UINT16> { type = Attribute.UINT16; }
+	 t=Name() { name = t.image; }
+	 Ints() ( "," Ints() )* ";"
+
+	 | LOOKAHEAD(2) <INT32> { type = Attribute.INT32; }
+	 t=Name() { name = t.image; }
+	 Ints() ( "," Ints() )* ";"
+
+	 | LOOKAHEAD(2) <UINT32> { type = Attribute.UINT32; }
+	 t=Name() { name = t.image; }
+	 Ints() ( "," Ints() )* ";"
+
+	 | LOOKAHEAD(2) <FLOAT32> { type = Attribute.FLOAT32; }
+	 t=Name() { name = t.image; }
+	 Floats() ( "," Floats() )* ";"
+
+	 | LOOKAHEAD(2) <FLOAT64> { type = Attribute.FLOAT64; }
+	 t=Name() { name = t.image; }
+	 Floats() ( "," Floats() )* ";"
+
+	 | LOOKAHEAD(2) <STRING> { type = Attribute.STRING; }
+	 t=Name() { name = t.image; }
+	 Strs() ( "," Strs() )* ";"
+
+	 | LOOKAHEAD(2) <URL> { type = Attribute.URL; }
+	 t=Name() { name = t.image; }
+	 Urls() ( "," Urls() )* ";"
+
+	 | LOOKAHEAD(2) t=Name()
+	 {
+	     AttributeTable at;
+	     if (isStackEmpty()) {
+		 at = das.getAttributeTable(t.image);
+		 if (at == null) {
+		     at = new AttributeTable(t.image);
+		     das.addAttributeTable(t.image, at);
+		 }
+	     } else {
+		 Attribute a = topOfStack().getAttribute(t.image);
+		 if (a == null) {
+		     at = topOfStack().appendContainer(t.image);
+		 } else {
+		     at = a.getContainer();
+		 }
+	     }
+	     stack.push(at);
+	 }
+	 "{" AttrList()
+	 {
+		stack.pop();
+	 }
+	 "}"
+	 )			// end of the series of ORed clauses
+    } // end of the try block
+    catch (ParseException e) {
+	error(attrTupleMsg + "\n"
+	      + "The offending line contained the token: '" + t + "'\n"
+	      + "ParseException Message: '" + e.getMessage() + "'\n");
+    }
+}
+
+void Bytes() throws DASException :
+{
+    Token t;
+}
+{
+    t=<WORD>
+    {
+	addAttribute(type, name, t.image);
+    }
+}
+
+void Ints() throws DASException :
+{
+    Token t;
+}
+{
+    t=<WORD>
+    {
+	addAttribute(type, name, t.image);
+    }
+}
+
+void Floats() throws DASException :
+{
+    Token t;
+}
+{
+    t=<WORD>
+    {
+	addAttribute(type, name, t.image);
+    }
+}
+
+void Strs() throws DASException :
+{
+    Token t;
+}
+{
+    try {
+	t=<WORD>
+        {
+	    addAttribute(type, name, t.image);
+        }
+        | t=<STR>
+        {
+	    addAttribute(type, name, t.image);
+	}
+    }
+    catch (TokenMgrError e) {
+	// If we get an exception thrown inside a quoted string then assume
+	// that the scanner has found EOF before the token (STR) ended (i.e.
+	// we have an unterminated double quote on our hands). 5/29/2002 jhrg
+	error("Unterminated quote: " + e.getMessage() + ")");
+    }
+}
+
+void Urls() throws DASException :
+{
+}
+{
+    Strs()
+}
+
+void Alias() throws DASException :
+{
+    Token t;
+    String alias = "";
+    String attr = "";
+}
+{
+    try {
+	// The first word is the alias, the second is the attribute.
+	<ALIAS> t=<WORD> { alias = t.image; }
+        t=<WORD> {
+	    attr = t.image;
+	    if (isStackEmpty()) {
+		AttributeTable at = das.getAttributeTable(attr);
+		// Note: this won't show up as an Alias when printing the DAS!
+		das.addAttributeTable(alias, at);  
+	    }
+	    else {
+	        topOfStack().addAlias(alias, attr);
+	    }
+	}
+	";"
+    }
+    catch (NoSuchAttributeException e) {
+	error("Error: The attribute " + attr + " does not exist.");
+    }
+    catch (AttributeExistsException e) {
+	error("Error: The alias " + alias + " already exists in this DAS.");
+    }
+}
+
+Token
+Name() throws DASException:
+{
+    Token t;
+}
+{
+    t=<WORD> { return t; }
+    | t=<ATTR> { return t; }
+    | t=<ALIAS> { return t; }
+    | t=<BYTE> { return t; }
+    | t=<INT16> { return t; }
+    | t=<UINT16> { return t; }
+    | t=<INT32> { return t; }
+    | t=<UINT32> { return t; }
+    | t=<FLOAT32> { return t; }
+    | t=<FLOAT64> { return t; }
+    | t=<STRING> { return t; }
+    | t=<URL> { return t; }
+}
+
+JAVACODE
+void 
+error(String msg) throws DASException 
+{
+    throw new DASException(DODSException.MALFORMED_EXPR,msg);
+}
+
+JAVACODE
+void 
+addAttribute(int type, String name, String value) throws DASException
+{
+    try {
+	if (isStackEmpty()) {
+	    String msg = "Whoa! Attribute table stack empty when adding `"
+		+ name +".'";
+	    error(msg);
+	}
+    
+	// appendAttribute throws a variety of DASExceptions if the attribute
+	// tuple is bad. This includes throwing AttribtueBadValueException if
+	// the value is bad (see the private method dispatchCheckValue()).
+	// 5/23/2002 jhrg
+	//System.err.println("Calling appendAttribute (name, type, value): "
+	//		   + name + ", " + type + ", " + value);
+	topOfStack().appendAttribute(name, type, value);
+    }
+    // If the attribute value is bad (the exception thrown by
+    // dispatchCheckValue() above) then add this attribute as a 'Bad
+    // Attribute.'
+    catch (AttributeBadValueException e) {
+	// System.err.println("Caught an AttributeBadValueException");
+	String msg = "`" + value + "' is not " + aOrAn(getTypeName(type)) 
+	    + " " + getTypeName(type) + " value.";
+	addBadAttribute(topOfStack().getName(), type, name, value, msg);
+    }
+}
+
+JAVACODE
+void
+addBadAttribute(String container_name, int type, String name, String value,
+		String msg)
+    throws DASException {
+    String errorContainerName = container_name + "_dods_errors";
+
+    // First, if this bad value is already in a *_dods_errors container,
+    // then just add it. This can happen when the server side processes a DAS
+    // and then hands it off to a client which does the same. The false value
+    // for arg four below supresses checking the value of the attribute
+    // (since we know it's bad and don't want the exception to be generated
+    // again). 
+    if (topOfStack().getName().equals(errorContainerName)) {
+  	topOfStack().appendAttribute(name, type, value, false);
+    }
+    // Otherwise, make a new container. Call it <attr's name>_errors. If that
+    // container already exists, use it. Add the attribute. Add the error
+    // string to an attribute in the container called `<name_explanation.'.
+    else {
+	// Does the error container alreay exist? 
+	AttributeTable errorContainer = null;
+	Attribute a = topOfStack().getAttribute(errorContainerName);
+	if (a != null)
+	    errorContainer = a.getContainer(); // get value as container
+	else
+	    errorContainer = topOfStack().appendContainer(errorContainerName);
+
+	// Arg four == false --> supress type/value checking.
+	errorContainer.appendAttribute(name, type, value, false);
+	errorContainer.appendAttribute(name + "_explanation", 
+				       dods.dap.Attribute.STRING, 
+				       "\"" + msg + "\"");
+    }
+}
+
+JAVACODE
+String
+aOrAn(String subject)
+{
+    String vowels = "aeiouAEIOUyY";
+    if (vowels.indexOf(subject.charAt(1)) >= 0)
+	return "an";
+    else
+	return "a";
+}
+
+JAVACODE
+String
+getTypeName(int type)
+{
+    switch(type) {
+    case Attribute.CONTAINER: return "Container";
+    case Attribute.BYTE: return "Byte";
+    case Attribute.INT16: return "Int16";
+    case Attribute.UINT16: return "UInt16";
+    case Attribute.INT32: return "Int32";
+    case Attribute.UINT32: return "UInt32";
+    case Attribute.FLOAT32: return "Float32";
+    case Attribute.FLOAT64: return "Float64";
+    case Attribute.STRING: return "String";
+    case Attribute.URL: return "Url";
+    default: return "";
+    }
+}
diff --git a/dods/dap/parser/DASParserConstants.java b/dods/dap/parser/DASParserConstants.java
new file mode 100644
index 0000000..bbe50b6
--- /dev/null
+++ b/dods/dap/parser/DASParserConstants.java
@@ -0,0 +1,49 @@
+/* Generated By:JavaCC: Do not edit this line. DASParserConstants.java */
+package dods.dap.parser;
+
+public interface DASParserConstants {
+
+  int EOF = 0;
+  int ATTR = 6;
+  int ALIAS = 7;
+  int BYTE = 8;
+  int INT16 = 9;
+  int UINT16 = 10;
+  int INT32 = 11;
+  int UINT32 = 12;
+  int FLOAT32 = 13;
+  int FLOAT64 = 14;
+  int STRING = 15;
+  int URL = 16;
+  int WORD = 17;
+  int STR = 18;
+
+  int DEFAULT = 0;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "\" \"",
+    "\"\\t\"",
+    "\"\\n\"",
+    "\"\\r\"",
+    "<token of kind 5>",
+    "<ATTR>",
+    "<ALIAS>",
+    "<BYTE>",
+    "<INT16>",
+    "<UINT16>",
+    "<INT32>",
+    "<UINT32>",
+    "<FLOAT32>",
+    "<FLOAT64>",
+    "<STRING>",
+    "<URL>",
+    "<WORD>",
+    "<STR>",
+    "\"{\"",
+    "\"}\"",
+    "\",\"",
+    "\";\"",
+  };
+
+}
diff --git a/dods/dap/parser/DASParserTokenManager.java b/dods/dap/parser/DASParserTokenManager.java
new file mode 100644
index 0000000..adc65c2
--- /dev/null
+++ b/dods/dap/parser/DASParserTokenManager.java
@@ -0,0 +1,1017 @@
+/* Generated By:JavaCC: Do not edit this line. DASParserTokenManager.java */
+package dods.dap.parser;
+import java.util.Stack;
+import dods.dap.*;
+
+public class DASParserTokenManager implements DASParserConstants
+{
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 44:
+         return jjStopAtPos(0, 21);
+      case 59:
+         return jjStopAtPos(0, 22);
+      case 123:
+         return jjStopAtPos(0, 19);
+      case 125:
+         return jjStopAtPos(0, 20);
+      default :
+         return jjMoveNfa_0(0, 0);
+   }
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+static final long[] jjbitVec0 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 175;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7ffeb2000000000L & l) != 0L)
+                  {
+                     if (kind > 17)
+                        kind = 17;
+                     jjCheckNAdd(13);
+                  }
+                  else if (curChar == 34)
+                     jjCheckNAddStates(0, 2);
+                  else if (curChar == 35)
+                  {
+                     if (kind > 5)
+                        kind = 5;
+                     jjCheckNAdd(1);
+                  }
+                  break;
+               case 1:
+                  if ((0xffffffffffffdbffL & l) == 0L)
+                     break;
+                  if (kind > 5)
+                     kind = 5;
+                  jjCheckNAdd(1);
+                  break;
+               case 12:
+                  if ((0x7ffeb2000000000L & l) == 0L)
+                     break;
+                  if (kind > 17)
+                     kind = 17;
+                  jjCheckNAdd(13);
+                  break;
+               case 13:
+                  if ((0x7ffeb2800000000L & l) == 0L)
+                     break;
+                  if (kind > 17)
+                     kind = 17;
+                  jjCheckNAdd(13);
+                  break;
+               case 14:
+                  if (curChar == 34)
+                     jjCheckNAddStates(0, 2);
+                  break;
+               case 15:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddStates(0, 2);
+                  break;
+               case 17:
+                  if ((0x8400000000L & l) != 0L)
+                     jjCheckNAddStates(0, 2);
+                  break;
+               case 18:
+                  if (curChar == 34 && kind > 18)
+                     kind = 18;
+                  break;
+               case 19:
+                  if ((0xff000000000000L & l) != 0L)
+                     jjCheckNAddStates(3, 6);
+                  break;
+               case 20:
+                  if ((0xff000000000000L & l) != 0L)
+                     jjCheckNAddStates(0, 2);
+                  break;
+               case 21:
+                  if ((0xf000000000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 22;
+                  break;
+               case 22:
+                  if ((0xff000000000000L & l) != 0L)
+                     jjCheckNAdd(20);
+                  break;
+               case 54:
+                  if (curChar == 54 && kind > 9)
+                     kind = 9;
+                  break;
+               case 55:
+               case 138:
+               case 141:
+                  if (curChar == 49)
+                     jjCheckNAdd(54);
+                  break;
+               case 58:
+                  if (curChar == 50 && kind > 11)
+                     kind = 11;
+                  break;
+               case 59:
+               case 144:
+               case 147:
+                  if (curChar == 51)
+                     jjCheckNAdd(58);
+                  break;
+               case 63:
+                  if (curChar == 54 && kind > 10)
+                     kind = 10;
+                  break;
+               case 64:
+               case 89:
+               case 93:
+               case 97:
+                  if (curChar == 49)
+                     jjCheckNAdd(63);
+                  break;
+               case 68:
+                  if (curChar == 50 && kind > 12)
+                     kind = 12;
+                  break;
+               case 69:
+               case 101:
+               case 105:
+               case 109:
+                  if (curChar == 51)
+                     jjCheckNAdd(68);
+                  break;
+               case 76:
+                  if (curChar == 50 && kind > 13)
+                     kind = 13;
+                  break;
+               case 77:
+               case 117:
+               case 122:
+                  if (curChar == 51)
+                     jjCheckNAdd(76);
+                  break;
+               case 82:
+                  if (curChar == 52 && kind > 14)
+                     kind = 14;
+                  break;
+               case 83:
+               case 127:
+               case 132:
+                  if (curChar == 54)
+                     jjCheckNAdd(82);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7fffffe97fffffeL & l) != 0L)
+                  {
+                     if (kind > 17)
+                        kind = 17;
+                     jjCheckNAdd(13);
+                  }
+                  if (curChar == 65)
+                     jjAddStates(7, 10);
+                  else if (curChar == 73)
+                     jjAddStates(11, 14);
+                  else if (curChar == 70)
+                     jjAddStates(15, 18);
+                  else if (curChar == 85)
+                     jjAddStates(19, 26);
+                  else if (curChar == 102)
+                     jjAddStates(27, 28);
+                  else if (curChar == 117)
+                     jjAddStates(29, 31);
+                  else if (curChar == 105)
+                     jjAddStates(32, 33);
+                  else if (curChar == 97)
+                     jjAddStates(34, 35);
+                  else if (curChar == 66)
+                     jjAddStates(36, 37);
+                  else if (curChar == 83)
+                     jjAddStates(38, 39);
+                  else if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 10;
+                  else if (curChar == 98)
+                     jjstateSet[jjnewStateCnt++] = 4;
+                  break;
+               case 1:
+                  if (kind > 5)
+                     kind = 5;
+                  jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 2:
+                  if (curChar == 101 && kind > 8)
+                     kind = 8;
+                  break;
+               case 3:
+               case 37:
+                  if (curChar == 116)
+                     jjCheckNAdd(2);
+                  break;
+               case 4:
+                  if (curChar == 121)
+                     jjstateSet[jjnewStateCnt++] = 3;
+                  break;
+               case 5:
+                  if (curChar == 98)
+                     jjstateSet[jjnewStateCnt++] = 4;
+                  break;
+               case 6:
+                  if (curChar == 103 && kind > 15)
+                     kind = 15;
+                  break;
+               case 7:
+               case 29:
+                  if (curChar == 110)
+                     jjCheckNAdd(6);
+                  break;
+               case 8:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 7;
+                  break;
+               case 9:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 8;
+                  break;
+               case 10:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 9;
+                  break;
+               case 11:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 10;
+                  break;
+               case 12:
+               case 13:
+                  if ((0x7fffffe97fffffeL & l) == 0L)
+                     break;
+                  if (kind > 17)
+                     kind = 17;
+                  jjCheckNAdd(13);
+                  break;
+               case 15:
+                  if ((0xffffffffefffffffL & l) != 0L)
+                     jjCheckNAddStates(0, 2);
+                  break;
+               case 16:
+                  if (curChar == 92)
+                     jjAddStates(40, 42);
+                  break;
+               case 17:
+                  if ((0x14404410000000L & l) != 0L)
+                     jjCheckNAddStates(0, 2);
+                  break;
+               case 23:
+                  if (curChar == 83)
+                     jjAddStates(38, 39);
+                  break;
+               case 24:
+                  if (curChar == 71 && kind > 15)
+                     kind = 15;
+                  break;
+               case 25:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 24;
+                  break;
+               case 26:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 25;
+                  break;
+               case 27:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 26;
+                  break;
+               case 28:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 27;
+                  break;
+               case 30:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 29;
+                  break;
+               case 31:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 30;
+                  break;
+               case 32:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 31;
+                  break;
+               case 33:
+                  if (curChar == 66)
+                     jjAddStates(36, 37);
+                  break;
+               case 34:
+                  if (curChar == 69 && kind > 8)
+                     kind = 8;
+                  break;
+               case 35:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 34;
+                  break;
+               case 36:
+                  if (curChar == 89)
+                     jjstateSet[jjnewStateCnt++] = 35;
+                  break;
+               case 38:
+                  if (curChar == 121)
+                     jjstateSet[jjnewStateCnt++] = 37;
+                  break;
+               case 39:
+                  if (curChar == 97)
+                     jjAddStates(34, 35);
+                  break;
+               case 40:
+                  if (curChar == 115 && kind > 6)
+                     kind = 6;
+                  break;
+               case 41:
+               case 151:
+                  if (curChar == 101)
+                     jjCheckNAdd(40);
+                  break;
+               case 42:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 41;
+                  break;
+               case 43:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 42;
+                  break;
+               case 44:
+                  if (curChar == 98)
+                     jjstateSet[jjnewStateCnt++] = 43;
+                  break;
+               case 45:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 44;
+                  break;
+               case 46:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 45;
+                  break;
+               case 47:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 46;
+                  break;
+               case 48:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 47;
+                  break;
+               case 49:
+                  if (curChar == 115 && kind > 7)
+                     kind = 7;
+                  break;
+               case 50:
+               case 172:
+                  if (curChar == 97)
+                     jjCheckNAdd(49);
+                  break;
+               case 51:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 50;
+                  break;
+               case 52:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 51;
+                  break;
+               case 53:
+                  if (curChar == 105)
+                     jjAddStates(32, 33);
+                  break;
+               case 56:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 55;
+                  break;
+               case 57:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 56;
+                  break;
+               case 60:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 59;
+                  break;
+               case 61:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 60;
+                  break;
+               case 62:
+                  if (curChar == 117)
+                     jjAddStates(29, 31);
+                  break;
+               case 65:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 64;
+                  break;
+               case 66:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 65;
+                  break;
+               case 67:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 66;
+                  break;
+               case 70:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 69;
+                  break;
+               case 71:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 70;
+                  break;
+               case 72:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 71;
+                  break;
+               case 73:
+                  if (curChar == 108 && kind > 16)
+                     kind = 16;
+                  break;
+               case 74:
+               case 115:
+                  if (curChar == 114)
+                     jjCheckNAdd(73);
+                  break;
+               case 75:
+                  if (curChar == 102)
+                     jjAddStates(27, 28);
+                  break;
+               case 78:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 77;
+                  break;
+               case 79:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 78;
+                  break;
+               case 80:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 79;
+                  break;
+               case 81:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 80;
+                  break;
+               case 84:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 83;
+                  break;
+               case 85:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 84;
+                  break;
+               case 86:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 85;
+                  break;
+               case 87:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 86;
+                  break;
+               case 88:
+                  if (curChar == 85)
+                     jjAddStates(19, 26);
+                  break;
+               case 90:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 89;
+                  break;
+               case 91:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 90;
+                  break;
+               case 92:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 91;
+                  break;
+               case 94:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 93;
+                  break;
+               case 95:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 94;
+                  break;
+               case 96:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 95;
+                  break;
+               case 98:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 97;
+                  break;
+               case 99:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 98;
+                  break;
+               case 100:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 99;
+                  break;
+               case 102:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 101;
+                  break;
+               case 103:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 102;
+                  break;
+               case 104:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 103;
+                  break;
+               case 106:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 105;
+                  break;
+               case 107:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 106;
+                  break;
+               case 108:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 107;
+                  break;
+               case 110:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 109;
+                  break;
+               case 111:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 110;
+                  break;
+               case 112:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 111;
+                  break;
+               case 113:
+                  if (curChar == 76 && kind > 16)
+                     kind = 16;
+                  break;
+               case 114:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 113;
+                  break;
+               case 116:
+                  if (curChar == 70)
+                     jjAddStates(15, 18);
+                  break;
+               case 118:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 117;
+                  break;
+               case 119:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 118;
+                  break;
+               case 120:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 119;
+                  break;
+               case 121:
+                  if (curChar == 76)
+                     jjstateSet[jjnewStateCnt++] = 120;
+                  break;
+               case 123:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 122;
+                  break;
+               case 124:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 123;
+                  break;
+               case 125:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 124;
+                  break;
+               case 126:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 125;
+                  break;
+               case 128:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 127;
+                  break;
+               case 129:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 128;
+                  break;
+               case 130:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 129;
+                  break;
+               case 131:
+                  if (curChar == 76)
+                     jjstateSet[jjnewStateCnt++] = 130;
+                  break;
+               case 133:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 132;
+                  break;
+               case 134:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 133;
+                  break;
+               case 135:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 134;
+                  break;
+               case 136:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 135;
+                  break;
+               case 137:
+                  if (curChar == 73)
+                     jjAddStates(11, 14);
+                  break;
+               case 139:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 138;
+                  break;
+               case 140:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 139;
+                  break;
+               case 142:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 141;
+                  break;
+               case 143:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 142;
+                  break;
+               case 145:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 144;
+                  break;
+               case 146:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 145;
+                  break;
+               case 148:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 147;
+                  break;
+               case 149:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 148;
+                  break;
+               case 150:
+                  if (curChar == 65)
+                     jjAddStates(7, 10);
+                  break;
+               case 152:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 151;
+                  break;
+               case 153:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 152;
+                  break;
+               case 154:
+                  if (curChar == 98)
+                     jjstateSet[jjnewStateCnt++] = 153;
+                  break;
+               case 155:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 154;
+                  break;
+               case 156:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 155;
+                  break;
+               case 157:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 156;
+                  break;
+               case 158:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 157;
+                  break;
+               case 159:
+                  if (curChar == 83 && kind > 6)
+                     kind = 6;
+                  break;
+               case 160:
+                  if (curChar == 69)
+                     jjstateSet[jjnewStateCnt++] = 159;
+                  break;
+               case 161:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 160;
+                  break;
+               case 162:
+                  if (curChar == 85)
+                     jjstateSet[jjnewStateCnt++] = 161;
+                  break;
+               case 163:
+                  if (curChar == 66)
+                     jjstateSet[jjnewStateCnt++] = 162;
+                  break;
+               case 164:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 163;
+                  break;
+               case 165:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 164;
+                  break;
+               case 166:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 165;
+                  break;
+               case 167:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 166;
+                  break;
+               case 168:
+                  if (curChar == 83 && kind > 7)
+                     kind = 7;
+                  break;
+               case 169:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 168;
+                  break;
+               case 170:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 169;
+                  break;
+               case 171:
+                  if (curChar == 76)
+                     jjstateSet[jjnewStateCnt++] = 170;
+                  break;
+               case 173:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 172;
+                  break;
+               case 174:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 173;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 1:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 5)
+                     kind = 5;
+                  jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 15:
+                  if ((jjbitVec0[i2] & l2) != 0L)
+                     jjAddStates(0, 2);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 175 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   15, 16, 18, 15, 16, 20, 18, 158, 167, 171, 174, 140, 143, 146, 149, 121, 
+   126, 131, 136, 92, 96, 100, 104, 108, 112, 114, 115, 81, 87, 67, 72, 74, 
+   57, 61, 48, 52, 36, 38, 28, 32, 17, 19, 21, 
+};
+public static final String[] jjstrLiteralImages = {
+"", null, null, null, null, null, null, null, null, null, null, null, null, 
+null, null, null, null, null, null, "\173", "\175", "\54", "\73", };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+};
+static final long[] jjtoToken = {
+   0x7fffc1L, 
+};
+static final long[] jjtoSkip = {
+   0x3eL, 
+};
+private SimpleCharStream input_stream;
+private final int[] jjrounds = new int[175];
+private final int[] jjstateSet = new int[350];
+protected char curChar;
+public DASParserTokenManager(SimpleCharStream stream)
+{
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public DASParserTokenManager(SimpleCharStream stream, int lexState)
+{
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 175; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 1 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+private final Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public final Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+
+   try { input_stream.backup(0);
+      while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)
+         curChar = input_stream.BeginToken();
+   }
+   catch (java.io.IOException e1) { continue EOFLoop; }
+   jjmatchedKind = 0x7fffffff;
+   jjmatchedPos = 0;
+   curPos = jjMoveStringLiteralDfa0_0();
+   if (jjmatchedKind != 0x7fffffff)
+   {
+      if (jjmatchedPos + 1 < curPos)
+         input_stream.backup(curPos - jjmatchedPos - 1);
+      if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+      {
+         matchedToken = jjFillToken();
+         return matchedToken;
+      }
+      else
+      {
+         continue EOFLoop;
+      }
+   }
+   int error_line = input_stream.getEndLine();
+   int error_column = input_stream.getEndColumn();
+   String error_after = null;
+   boolean EOFSeen = false;
+   try { input_stream.readChar(); input_stream.backup(1); }
+   catch (java.io.IOException e1) {
+      EOFSeen = true;
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+      if (curChar == '\n' || curChar == '\r') {
+         error_line++;
+         error_column = 0;
+      }
+      else
+         error_column++;
+   }
+   if (!EOFSeen) {
+      input_stream.backup(1);
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+   }
+   throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+}
diff --git a/dods/dap/parser/DDSParser.java b/dods/dap/parser/DDSParser.java
new file mode 100644
index 0000000..04a9a6a
--- /dev/null
+++ b/dods/dap/parser/DDSParser.java
@@ -0,0 +1,979 @@
+/* Generated By:JavaCC: Do not edit this line. DDSParser.java */
+package dods.dap.parser;
+import java.util.Stack;
+import dods.dap.*;
+
+public class DDSParser implements DDSParserConstants {
+  /* $Id: DDSParser.java,v 1.4 2007-04-16 16:40:54 tomw Exp $ */
+  private DDS dds;
+  private BaseTypeFactory factory;  // used to construct new types
+  private Stack ctor;        // stack for ctor types
+  private BaseType current;
+  private int part;          // part is defined in each type which uses it
+  private String id;
+
+  private static final String noDDSMsg =
+"The descriptor object returned from the dataset was null\n" +
+"Check that the URL is correct.";
+
+    /** Add the variable pointed to by CURRENT to either the topmost ctor
+	object on the stack CTOR or to the dataset variable table TABLE if
+	CTOR is empty. If it exists, the current ctor object is poped off the
+	stack and assigned to CURRENT.
+
+	NB: the ctor stack is popped for lists and arrays because they are
+	ctors which contain only a single variable. For other ctor types,
+	several varaiables may be members and the parse rule (see
+	`declaration' above) determines when to pop the stack. */
+    private void addEntry() {
+        if (!ctor.empty()) {  // must be parsing a ctor type
+            if (ctor.peek() instanceof DVector) {
+                DVector top = (DVector)(ctor.peek());
+                top.addVariable(current);
+                current = (BaseType)(ctor.pop());
+            }
+            else if (ctor.peek() instanceof DConstructor) {
+                DConstructor top = (DConstructor)(ctor.peek());
+                if (top instanceof DGrid)
+                    top.addVariable(current, part);
+                else
+                    top.addVariable(current);
+            }
+        }
+        else {
+            dds.addVariable(current);
+        }
+    }
+
+    /** A helper function to throw a common exception */
+    private void throwBad(String s1) throws BadSemanticsException {
+        throw new BadSemanticsException("In the dataset descriptor object:\n"
+                               + "`" + s1 + "' is not a valid declaration.");
+    }
+
+    /** A helper function to throw a common exception */
+    private void throwBad(String s1, String s2) throws BadSemanticsException {
+        throw new BadSemanticsException("In the dataset descriptor object:\n"
+                      + "`" + s1 + " " + s2 + "' is not a valid declaration");
+    }
+
+    /** A helper function to check semantics and add a DDS entry */
+    private void checkAdd(String s1) throws BadSemanticsException {
+        try {
+            current.checkSemantics();
+            addEntry();
+        }
+        catch (BadSemanticsException e) {
+            throwBad(s1);
+        }
+    }
+
+    /** A helper function to check semantics and add a DDS entry */
+    private void checkAdd(String s1, String s2) throws BadSemanticsException {
+        try {
+            current.checkSemantics();
+            addEntry();
+        }
+        catch (BadSemanticsException e) {
+            throwBad(s1, s2);
+        }
+    }
+
+    /** A helper to check if the word matches a given keyword. 
+	@param keyword The lower case to test against.
+	@param word Does this match keyword? (Case folded to lower.) */
+    private boolean isKeyword(String word, String keyword) {
+        return keyword.equalsIgnoreCase(word);
+    }
+
+  final public void Dataset(DDS dds, BaseTypeFactory factory) throws ParseException, DDSException {
+    this.dds = dds;
+    this.factory = factory;
+    this.ctor = new Stack();
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case DATASET:
+      jj_consume_token(DATASET);
+      jj_consume_token(21);
+      Declarations();
+      jj_consume_token(22);
+      Name();
+      jj_consume_token(23);
+      break;
+    default:
+      jj_la1[0] = jj_gen;
+      error(noDDSMsg);
+    }
+  }
+
+  final public void Declarations() throws ParseException, DDSException {
+    label_1:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case LIST:
+      case SEQUENCE:
+      case STRUCTURE:
+      case GRID:
+      case BYTE:
+      case INT16:
+      case UINT16:
+      case INT32:
+      case UINT32:
+      case FLOAT32:
+      case FLOAT64:
+      case STRING:
+      case URL:
+        ;
+        break;
+      default:
+        jj_la1[1] = jj_gen;
+        break label_1;
+      }
+      Declaration();
+    }
+  }
+
+  final public void Declaration() throws ParseException, DDSException {
+    String s1, s2;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case LIST:
+      s1 = List();
+      s2 = NonListDecl();
+        checkAdd(s1, s2);
+      break;
+    case SEQUENCE:
+    case STRUCTURE:
+    case GRID:
+    case BYTE:
+    case INT16:
+    case UINT16:
+    case INT32:
+    case UINT32:
+    case FLOAT32:
+    case FLOAT64:
+    case STRING:
+    case URL:
+      NonListDecl();
+      break;
+    default:
+      jj_la1[2] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+  }
+
+// This non-terminal is here only to keep types like `List List Int32' from
+// parsing. DODS does not allow Lists of Lists. Those types make translation
+// to/from arrays too hard.
+  final public String NonListDecl() throws ParseException, DDSException {
+    String s1=null, s2=null;
+    Token t;
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case BYTE:
+      case INT16:
+      case UINT16:
+      case INT32:
+      case UINT32:
+      case FLOAT32:
+      case FLOAT64:
+      case STRING:
+      case URL:
+        s1 = BaseType();
+        s2 = Var();
+        jj_consume_token(23);
+             checkAdd(s1, s2);
+             {if (true) return s2;}
+        break;
+      case STRUCTURE:
+        Structure();
+        jj_consume_token(21);
+        Declarations();
+        jj_consume_token(22);
+             current = (BaseType)ctor.pop();
+        s1 = Var();
+        jj_consume_token(23);
+             checkAdd(s1);
+             {if (true) return s1;}
+        break;
+      case SEQUENCE:
+        Sequence();
+        jj_consume_token(21);
+        Declarations();
+        jj_consume_token(22);
+             current = (BaseType)ctor.pop();
+        s1 = Var();
+        jj_consume_token(23);
+             checkAdd(s1);
+             {if (true) return s1;}
+        break;
+      case GRID:
+        Grid();
+        jj_consume_token(21);
+        t = jj_consume_token(WORD);
+        jj_consume_token(24);
+             if (isKeyword(t.image, "array"))
+                 part = DGrid.ARRAY;
+             else
+                 error("\nParse error: Expected the keyword \"Array:\"\n"
+                       + "but found: " + t.image + " instead.");
+        Declaration();
+        t = jj_consume_token(WORD);
+        jj_consume_token(24);
+             if (isKeyword(t.image, "maps"))
+                 part = DGrid.MAPS;
+             else
+                 error("\nParse error: Expected the keyword \"Maps:\"\n"
+                       + "but found: " + t.image + " instead.");
+        Declarations();
+        jj_consume_token(22);
+             current = (BaseType)ctor.pop();
+        s1 = Var();
+        jj_consume_token(23);
+             checkAdd(s1);
+             {if (true) return s1;}
+        break;
+      default:
+        jj_la1[3] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    } catch (ParseException e) {
+        error("\nParse Error on token: " + s1 + "\n"
+              + "In the dataset descriptor object:\n"
+              + "Expected a variable declaration (e.g., Int32 i;).");
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String List() throws ParseException {
+    Token t;
+    t = jj_consume_token(LIST);
+        ctor.push(factory.newDList());
+        {if (true) return t.image;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String Structure() throws ParseException {
+    Token t;
+    t = jj_consume_token(STRUCTURE);
+        ctor.push(factory.newDStructure());
+        {if (true) return t.image;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String Sequence() throws ParseException {
+    Token t;
+    t = jj_consume_token(SEQUENCE);
+        ctor.push(factory.newDSequence());
+        {if (true) return t.image;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String Grid() throws ParseException {
+    Token t;
+    t = jj_consume_token(GRID);
+        ctor.push(factory.newDGrid());
+        {if (true) return t.image;}
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String BaseType() throws ParseException {
+    Token t;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case BYTE:
+      t = jj_consume_token(BYTE);
+        current = factory.newDByte();
+        {if (true) return t.image;}
+      break;
+    case INT16:
+      t = jj_consume_token(INT16);
+        current = factory.newDInt16();
+        {if (true) return t.image;}
+      break;
+    case UINT16:
+      t = jj_consume_token(UINT16);
+        current = factory.newDUInt16();
+        {if (true) return t.image;}
+      break;
+    case INT32:
+      t = jj_consume_token(INT32);
+        current = factory.newDInt32();
+        {if (true) return t.image;}
+      break;
+    case UINT32:
+      t = jj_consume_token(UINT32);
+        current = factory.newDUInt32();
+        {if (true) return t.image;}
+      break;
+    case FLOAT32:
+      t = jj_consume_token(FLOAT32);
+        current = factory.newDFloat32();
+        {if (true) return t.image;}
+      break;
+    case FLOAT64:
+      t = jj_consume_token(FLOAT64);
+        current = factory.newDFloat64();
+        {if (true) return t.image;}
+      break;
+    case STRING:
+      t = jj_consume_token(STRING);
+        current = factory.newDString();
+        {if (true) return t.image;}
+      break;
+    case URL:
+      t = jj_consume_token(URL);
+        current = factory.newDURL();
+        {if (true) return t.image;}
+      break;
+    default:
+      jj_la1[4] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+// What's going on here!? A variable's name can be either a WORD or one of
+// the previously reserved words Byte, Int16, et cetera. This allows datasets
+// with truly bizarre variable names to be served by DODS. 5/22/2002 jhrg
+  final public String Var() throws ParseException, DDSException {
+    Token t;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case WORD:
+      t = jj_consume_token(WORD);
+                    current.setName(t.image);
+      label_2:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[5] = jj_gen;
+          break label_2;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case BYTE:
+      t = jj_consume_token(BYTE);
+                    current.setName(t.image);
+      label_3:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[6] = jj_gen;
+          break label_3;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case INT16:
+      t = jj_consume_token(INT16);
+                    current.setName(t.image);
+      label_4:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[7] = jj_gen;
+          break label_4;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case UINT16:
+      t = jj_consume_token(UINT16);
+                    current.setName(t.image);
+      label_5:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[8] = jj_gen;
+          break label_5;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case INT32:
+      t = jj_consume_token(INT32);
+                    current.setName(t.image);
+      label_6:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[9] = jj_gen;
+          break label_6;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case UINT32:
+      t = jj_consume_token(UINT32);
+                    current.setName(t.image);
+      label_7:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[10] = jj_gen;
+          break label_7;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case FLOAT32:
+      t = jj_consume_token(FLOAT32);
+                    current.setName(t.image);
+      label_8:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[11] = jj_gen;
+          break label_8;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case FLOAT64:
+      t = jj_consume_token(FLOAT64);
+                    current.setName(t.image);
+      label_9:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[12] = jj_gen;
+          break label_9;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case STRING:
+      t = jj_consume_token(STRING);
+                    current.setName(t.image);
+      label_10:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[13] = jj_gen;
+          break label_10;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case URL:
+      t = jj_consume_token(URL);
+                    current.setName(t.image);
+      label_11:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[14] = jj_gen;
+          break label_11;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case STRUCTURE:
+      t = jj_consume_token(STRUCTURE);
+                    current.setName(t.image);
+      label_12:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[15] = jj_gen;
+          break label_12;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case SEQUENCE:
+      t = jj_consume_token(SEQUENCE);
+                    current.setName(t.image);
+      label_13:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[16] = jj_gen;
+          break label_13;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case GRID:
+      t = jj_consume_token(GRID);
+                    current.setName(t.image);
+      label_14:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[17] = jj_gen;
+          break label_14;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    case LIST:
+      t = jj_consume_token(LIST);
+                    current.setName(t.image);
+      label_15:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          ;
+          break;
+        default:
+          jj_la1[18] = jj_gen;
+          break label_15;
+        }
+        ArrayDecl();
+      }
+                    {if (true) return t.image;}
+      break;
+    default:
+      jj_la1[19] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final public void ArrayDecl() throws ParseException, DDSException {
+    Token t= new Token();
+    try {
+      if (jj_2_1(3)) {
+        jj_consume_token(25);
+        t = jj_consume_token(WORD);
+        jj_consume_token(26);
+             if (current instanceof DArray) {
+                 ((DArray)current).appendDim(Integer.parseInt(t.image));
+             } else {
+                 DArray a = factory.newDArray();
+                 a.addVariable(current);
+                 a.appendDim(Integer.parseInt(t.image));
+                 current = a;
+             }
+      } else {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case 25:
+          jj_consume_token(25);
+          t = jj_consume_token(WORD);
+             id = t.image;
+          jj_consume_token(27);
+          t = jj_consume_token(WORD);
+             if (current instanceof DArray) {
+                 ((DArray)current).appendDim(Integer.parseInt(t.image), id);
+             } else {
+                 DArray a = factory.newDArray();
+                 a.addVariable(current);
+                 a.appendDim(Integer.parseInt(t.image), id);
+                 current = a;
+             }
+          jj_consume_token(26);
+          break;
+        default:
+          jj_la1[20] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+      }
+    } catch (NumberFormatException e) {
+        error("\nThe index: " + t.image + " is not an integer value.\n"
+              + "Index values must be integers.");
+    } catch (ParseException e) {
+        error("\nThere was a problem parsing the DDS:\n"+
+              "Expected an array subscript, but didn't find it\n\n" +
+              "The offending line contains the characters: "+t.image+"\n\n"+
+              "ParseException Message: \n" + e.getMessage() +"\n");
+    }
+  }
+
+  final public void Name() throws ParseException, DDSException {
+    Token t;
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case WORD:
+        t = jj_consume_token(WORD);
+                           dds.setName(t.image);
+        break;
+      case BYTE:
+        t = jj_consume_token(BYTE);
+                           dds.setName(t.image);
+        break;
+      case INT16:
+        t = jj_consume_token(INT16);
+                           dds.setName(t.image);
+        break;
+      case UINT16:
+        t = jj_consume_token(UINT16);
+                           dds.setName(t.image);
+        break;
+      case INT32:
+        t = jj_consume_token(INT32);
+                           dds.setName(t.image);
+        break;
+      case UINT32:
+        t = jj_consume_token(UINT32);
+                           dds.setName(t.image);
+        break;
+      case FLOAT32:
+        t = jj_consume_token(FLOAT32);
+                           dds.setName(t.image);
+        break;
+      case FLOAT64:
+        t = jj_consume_token(FLOAT64);
+                           dds.setName(t.image);
+        break;
+      case STRING:
+        t = jj_consume_token(STRING);
+                           dds.setName(t.image);
+        break;
+      case URL:
+        t = jj_consume_token(URL);
+                           dds.setName(t.image);
+        break;
+      case STRUCTURE:
+        t = jj_consume_token(STRUCTURE);
+                           dds.setName(t.image);
+        break;
+      case SEQUENCE:
+        t = jj_consume_token(SEQUENCE);
+                           dds.setName(t.image);
+        break;
+      case GRID:
+        t = jj_consume_token(GRID);
+                           dds.setName(t.image);
+        break;
+      case LIST:
+        t = jj_consume_token(LIST);
+                           dds.setName(t.image);
+        break;
+      default:
+        jj_la1[21] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    } catch (ParseException e) {
+        error("Error parsing the dataset name.\n" +
+              "The name may be missing or may contain an illegal character.");
+    }
+  }
+
+  void error(String msg) throws ParseException, DDSException {
+    throw new DDSException(DODSException.UNKNOWN_ERROR, msg);
+  }
+
+  final private boolean jj_2_1(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_1();
+    jj_save(0, xla);
+    return retval;
+  }
+
+  final private boolean jj_3_1() {
+    if (jj_scan_token(25)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(WORD)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(26)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  public DDSParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private Token jj_scanpos, jj_lastpos;
+  private int jj_la;
+  public boolean lookingAhead = false;
+  private boolean jj_semLA;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[22];
+  final private int[] jj_la1_0 = {0x40,0xfff80,0xfff80,0xfff00,0xff800,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x2000000,0x1fff80,0x2000000,0x1fff80,};
+  final private JJCalls[] jj_2_rtns = new JJCalls[1];
+  private boolean jj_rescan = false;
+  private int jj_gc = 0;
+
+  public DDSParser(java.io.InputStream stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new DDSParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public DDSParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new DDSParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public DDSParser(DDSParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(DDSParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 22; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      if (++jj_gc > 100) {
+        jj_gc = 0;
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+          JJCalls c = jj_2_rtns[i];
+          while (c != null) {
+            if (c.gen < jj_gen) c.first = null;
+            c = c.next;
+          }
+        }
+      }
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  final private boolean jj_scan_token(int kind) {
+    if (jj_scanpos == jj_lastpos) {
+      jj_la--;
+      if (jj_scanpos.next == null) {
+        jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+      } else {
+        jj_lastpos = jj_scanpos = jj_scanpos.next;
+      }
+    } else {
+      jj_scanpos = jj_scanpos.next;
+    }
+    if (jj_rescan) {
+      int i = 0; Token tok = token;
+      while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+      if (tok != null) jj_add_error_token(kind, i);
+    }
+    return (jj_scanpos.kind != kind);
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = lookingAhead ? jj_scanpos : token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.Vector jj_expentries = new java.util.Vector();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+  private int[] jj_lasttokens = new int[100];
+  private int jj_endpos;
+
+  private void jj_add_error_token(int kind, int pos) {
+    if (pos >= 100) return;
+    if (pos == jj_endpos + 1) {
+      jj_lasttokens[jj_endpos++] = kind;
+    } else if (jj_endpos != 0) {
+      jj_expentry = new int[jj_endpos];
+      for (int i = 0; i < jj_endpos; i++) {
+        jj_expentry[i] = jj_lasttokens[i];
+      }
+      boolean exists = false;
+      for (java.util.Enumeration enumx = jj_expentries.elements(); enumx.hasMoreElements();) {
+        int[] oldentry = (int[])(enumx.nextElement());
+        if (oldentry.length == jj_expentry.length) {
+          exists = true;
+          for (int i = 0; i < jj_expentry.length; i++) {
+            if (oldentry[i] != jj_expentry[i]) {
+              exists = false;
+              break;
+            }
+          }
+          if (exists) break;
+        }
+      }
+      if (!exists) jj_expentries.addElement(jj_expentry);
+      if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+    }
+  }
+
+  final public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[28];
+    for (int i = 0; i < 28; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 22; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 28; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    jj_endpos = 0;
+    jj_rescan_token();
+    jj_add_error_token(0, 0);
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = (int[])jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+  final private void jj_rescan_token() {
+    jj_rescan = true;
+    for (int i = 0; i < 1; i++) {
+      JJCalls p = jj_2_rtns[i];
+      do {
+        if (p.gen > jj_gen) {
+          jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+          switch (i) {
+            case 0: jj_3_1(); break;
+          }
+        }
+        p = p.next;
+      } while (p != null);
+    }
+    jj_rescan = false;
+  }
+
+  final private void jj_save(int index, int xla) {
+    JJCalls p = jj_2_rtns[index];
+    while (p.gen > jj_gen) {
+      if (p.next == null) { p = p.next = new JJCalls(); break; }
+      p = p.next;
+    }
+    p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+  }
+
+  static final class JJCalls {
+    int gen;
+    Token first;
+    int arg;
+    JJCalls next;
+  }
+
+}
diff --git a/dods/dap/parser/DDSParser.jj b/dods/dap/parser/DDSParser.jj
new file mode 100644
index 0000000..bc059bc
--- /dev/null
+++ b/dods/dap/parser/DDSParser.jj
@@ -0,0 +1,470 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+//
+// -- 7/14/99 Modified by: Nathan Potter (ndp at oce.orst.edu)
+// Added Support For DInt16, DUInt16, DFloat32.
+// Added (and commented out) support for DBoolean.
+// -- 7/14/99 ndp 
+//  
+/////////////////////////////////////////////////////////////////////////////
+
+
+options {
+    STATIC = false;  // Make thread-safe
+    // DEBUG_PARSER = true;
+}
+
+PARSER_BEGIN(DDSParser)
+
+package dods.dap.parser;
+import java.util.Stack;
+import dods.dap.*;
+
+public class DDSParser {
+  /* $Id: DDSParser.jj,v 1.3 2004-02-06 15:23:48 donm Exp $ */
+  private DDS dds;
+  private BaseTypeFactory factory;  // used to construct new types
+  private Stack ctor;        // stack for ctor types
+  private BaseType current;
+  private int part;          // part is defined in each type which uses it
+  private String id;
+
+  private static final String noDDSMsg =
+"The descriptor object returned from the dataset was null\n" +
+"Check that the URL is correct.";
+
+    /** Add the variable pointed to by CURRENT to either the topmost ctor
+	object on the stack CTOR or to the dataset variable table TABLE if
+	CTOR is empty. If it exists, the current ctor object is poped off the
+	stack and assigned to CURRENT.
+
+	NB: the ctor stack is popped for lists and arrays because they are
+	ctors which contain only a single variable. For other ctor types,
+	several varaiables may be members and the parse rule (see
+	`declaration' above) determines when to pop the stack. */
+    private void addEntry() {
+	if (!ctor.empty()) {  // must be parsing a ctor type
+	    if (ctor.peek() instanceof DVector) {
+		DVector top = (DVector)(ctor.peek());
+		top.addVariable(current);
+		current = (BaseType)(ctor.pop());
+	    }
+	    else if (ctor.peek() instanceof DConstructor) {
+		DConstructor top = (DConstructor)(ctor.peek());
+		if (top instanceof DGrid)
+		    top.addVariable(current, part);
+		else
+		    top.addVariable(current);
+	    }
+	}
+	else {
+	    dds.addVariable(current);
+	}
+    }
+
+    /** A helper function to throw a common exception */
+    private void throwBad(String s1) throws BadSemanticsException {
+	throw new BadSemanticsException("In the dataset descriptor object:\n"
+			       + "`" + s1 + "' is not a valid declaration.");
+    }
+
+    /** A helper function to throw a common exception */
+    private void throwBad(String s1, String s2) throws BadSemanticsException {
+	throw new BadSemanticsException("In the dataset descriptor object:\n"
+		      + "`" + s1 + " " + s2 + "' is not a valid declaration");
+    }
+
+    /** A helper function to check semantics and add a DDS entry */
+    private void checkAdd(String s1) throws BadSemanticsException {
+	try {
+	    current.checkSemantics();
+	    addEntry();
+	}
+	catch (BadSemanticsException e) {
+	    throwBad(s1);
+	}
+    }
+
+    /** A helper function to check semantics and add a DDS entry */
+    private void checkAdd(String s1, String s2) throws BadSemanticsException {
+	try {
+	    current.checkSemantics();
+	    addEntry();
+	}
+	catch (BadSemanticsException e) {
+	    throwBad(s1, s2);
+	}
+    }
+
+    /** A helper to check if the word matches a given keyword. 
+	@param keyword The lower case to test against.
+	@param word Does this match keyword? (Case folded to lower.) */
+    private boolean isKeyword(String word, String keyword) {
+	return keyword.equalsIgnoreCase(word);
+    }
+}
+
+PARSER_END(DDSParser)
+
+SKIP : {
+ " "
+| "\t"
+| "\n"
+| "\r"
+| < "#" (~["\n","\r"])* >
+}
+
+TOKEN : {
+	<DATASET: "DATASET"|"Dataset"|"dataset"> |
+	<LIST: "LIST"|"List"|"list"> |
+	<SEQUENCE: "SEQUENCE"|"Sequence"|"sequence"> |
+	<STRUCTURE: "STRUCTURE"|"Structure"|"structure"> |
+	<GRID: "GRID"|"Grid"|"grid"> |
+	<BYTE: "BYTE"|"Byte"|"byte"> |
+	<INT16: "INT16"|"Int16"|"int16"> |
+	<UINT16: "UINT16"|"UInt16"|"uint16"> |
+	<INT32: "INT32"|"Int32"|"int32"> |
+	<UINT32: "UINT32"|"UInt32"|"Uint32"|"uint32"> |
+	<FLOAT32: "FLOAT32"|"Float32"|"float32"> |
+	<FLOAT64: "FLOAT64"|"Float64"|"float64"> |
+	<STRING: "STRING"|"String"|"string"> |
+	<URL: "URL"|"Url"|"url"> |
+
+	<WORD: ["+","-","a"-"z","A"-"Z","0"-"9","_","/","%",".","\\"](["-","+","a"-"z","A"-"Z","0"-"9","_","/","%",".","\\","#"])*>
+}
+
+void Dataset(DDS dds, BaseTypeFactory factory) throws DDSException :
+{
+    this.dds = dds;
+    this.factory = factory;
+    this.ctor = new Stack();
+}
+{
+    <DATASET> "{" Declarations() "}" Name() ";"
+    | error(noDDSMsg)
+}
+
+void Declarations() throws DDSException :
+{}
+{
+    ( Declaration() )*
+}
+
+void Declaration() throws DDSException :
+{
+    String s1, s2;
+}
+{
+    s1=List() s2=NonListDecl() {
+	checkAdd(s1, s2);
+    }
+    | NonListDecl()
+}
+
+// This non-terminal is here only to keep types like `List List Int32' from
+// parsing. DODS does not allow Lists of Lists. Those types make translation
+// to/from arrays too hard.
+String NonListDecl() throws DDSException :
+{
+    String s1=null, s2=null;
+    Token t;
+}
+{
+    // NOTE:  It may be useful to comment out this try/catch block when
+    // debugging this parser to get a more descriptive error message.
+    try {
+	(
+	 s1=BaseType() s2=Var() ";" {
+	     checkAdd(s1, s2);
+	     return s2;
+	 }
+
+	 | Structure() "{" Declarations() "}" {
+	     current = (BaseType)ctor.pop();
+	 }
+	 s1=Var() ";" {
+	     checkAdd(s1);
+	     return s1;
+	 }
+
+	 | Sequence() "{" Declarations() "}" {
+	     current = (BaseType)ctor.pop();
+	 }
+	 s1=Var() ";" {
+	     checkAdd(s1);
+	     return s1;
+	 }
+
+	 | Grid() "{" t=<WORD> ":" {
+	     if (isKeyword(t.image, "array"))
+		 part = DGrid.ARRAY;
+	     else 
+		 error("\nParse error: Expected the keyword \"Array:\"\n"
+		       + "but found: " + t.image + " instead.");
+	 }
+	 Declaration() t=<WORD> ":" {
+	     if (isKeyword(t.image, "maps"))
+		 part = DGrid.MAPS;
+	     else 
+		 error("\nParse error: Expected the keyword \"Maps:\"\n"
+		       + "but found: " + t.image + " instead.");
+	 }
+	 Declarations() "}" {
+	     current = (BaseType)ctor.pop();
+	 }
+	 s1=Var() ";" {
+	     checkAdd(s1);
+	     return s1;
+	 }
+	 )
+    } 
+    catch (ParseException e) {
+	error("\nParse Error on token: " + s1 + "\n" 
+	      + "In the dataset descriptor object:\n"
+	      + "Expected a variable declaration (e.g., Int32 i;).");
+    }
+}
+
+String List() :
+{
+    Token t;
+}
+{
+    t=<LIST> {
+	ctor.push(factory.newDList());
+	return t.image;
+    }
+}
+
+String Structure() :
+{
+    Token t;
+}
+{
+    t=<STRUCTURE> {
+	ctor.push(factory.newDStructure());
+	return t.image;
+    }
+}
+
+String Sequence() :
+{
+    Token t;
+}
+{
+    t=<SEQUENCE> {
+	ctor.push(factory.newDSequence());
+	return t.image;
+    }
+}
+
+String Grid() :
+{
+    Token t;
+}
+{
+    t=<GRID> {
+	ctor.push(factory.newDGrid());
+	return t.image;
+    }
+}
+
+String BaseType() :
+{
+    Token t;
+}
+{
+    t=<BYTE> {
+	current = factory.newDByte();
+	return t.image;
+    }
+    | t=<INT16> {
+	current = factory.newDInt16();
+	return t.image;
+    }
+    | t=<UINT16> {
+	current = factory.newDUInt16();
+	return t.image;
+    }
+    | t=<INT32> {
+	current = factory.newDInt32();
+	return t.image;
+    }
+    | t=<UINT32> {
+	current = factory.newDUInt32();
+	return t.image;
+    }
+    | t=<FLOAT32> {
+	current = factory.newDFloat32();
+	return t.image;
+    }
+    | t=<FLOAT64> {
+	current = factory.newDFloat64();
+	return t.image;
+    }
+    | t=<STRING> {
+	current = factory.newDString();
+	return t.image;
+    }
+    | t=<URL> {
+	current = factory.newDURL();
+	return t.image;
+    }
+}
+
+// What's going on here!? A variable's name can be either a WORD or one of
+// the previously reserved words Byte, Int16, et cetera. This allows datasets
+// with truly bizarre variable names to be served by DODS. 5/22/2002 jhrg
+String Var() throws DDSException :
+{
+    Token t;
+}
+{
+  t=<WORD>        { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+  
+  | t=<BYTE>      { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<INT16>     { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<UINT16>    { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<INT32>     { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<UINT32>    { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<FLOAT32>   { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<FLOAT64>   { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<STRING>    { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<URL>       { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<STRUCTURE> { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<SEQUENCE>  { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<GRID>      { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+
+  | t=<LIST>      { current.setName(t.image); }
+                  ( ArrayDecl() )*
+                  { return t.image; }
+}
+
+void ArrayDecl() throws DDSException :
+{
+    Token t= new Token();
+}
+{
+    try {
+	(
+	 LOOKAHEAD(3)
+	 "[" t=<WORD> "]" {
+	     if (current instanceof DArray) {
+		 ((DArray)current).appendDim(Integer.parseInt(t.image));
+	     } else {
+		 DArray a = factory.newDArray();
+		 a.addVariable(current);
+		 a.appendDim(Integer.parseInt(t.image));
+		 current = a;
+	     }
+	 }
+
+	 | "[" t=<WORD> {
+	     id = t.image;
+	 }
+	 "=" t=<WORD> {
+	     if (current instanceof DArray) {
+		 ((DArray)current).appendDim(Integer.parseInt(t.image), id);
+	     } else {
+		 DArray a = factory.newDArray();
+		 a.addVariable(current);
+		 a.appendDim(Integer.parseInt(t.image), id);
+		 current = a;
+	     }
+	 }
+	 "]"
+	 )
+    } 
+    catch (NumberFormatException e) {
+	error("\nThe index: " + t.image + " is not an integer value.\n"
+	      + "Index values must be integers.");
+    }
+    catch (ParseException e) {
+	error("\nThere was a problem parsing the DDS:\n"+ 
+	      "Expected an array subscript, but didn't find it\n\n" +
+	      "The offending line contains the characters: "+t.image+"\n\n"+
+	      "ParseException Message: \n" + e.getMessage() +"\n");
+    }
+}
+
+void Name() throws DDSException :
+{
+    Token t;
+}
+{
+    try {
+	(t=<WORD>        { dds.setName(t.image); }
+	 | t=<BYTE>      { dds.setName(t.image); }
+	 | t=<INT16>     { dds.setName(t.image); }
+	 | t=<UINT16>    { dds.setName(t.image); }
+	 | t=<INT32>     { dds.setName(t.image); }
+	 | t=<UINT32>    { dds.setName(t.image); }
+	 | t=<FLOAT32>   { dds.setName(t.image); }
+	 | t=<FLOAT64>   { dds.setName(t.image); }
+	 | t=<STRING>    { dds.setName(t.image); }
+	 | t=<URL>       { dds.setName(t.image); }
+	 | t=<STRUCTURE> { dds.setName(t.image); }
+	 | t=<SEQUENCE>  { dds.setName(t.image); }
+	 | t=<GRID>      { dds.setName(t.image); }
+	 | t=<LIST>      { dds.setName(t.image); })
+    }
+    catch (ParseException e) {
+	error("Error parsing the dataset name.\n" +
+	      "The name may be missing or may contain an illegal character.");
+    }
+}
+
+JAVACODE
+void error(String msg) throws DDSException {
+    throw new DDSException(DODSException.UNKNOWN_ERROR, msg);
+}
+
+// $Log: not supported by cvs2svn $
+// Revision 1.18  2002/05/23 01:20:56  jimg
+// Added a CVS log to this file.
+//
diff --git a/dods/dap/parser/DDSParserConstants.java b/dods/dap/parser/DDSParserConstants.java
new file mode 100644
index 0000000..6c9dd2e
--- /dev/null
+++ b/dods/dap/parser/DDSParserConstants.java
@@ -0,0 +1,56 @@
+/* Generated By:JavaCC: Do not edit this line. DDSParserConstants.java */
+package dods.dap.parser;
+
+public interface DDSParserConstants {
+
+  int EOF = 0;
+  int DATASET = 6;
+  int LIST = 7;
+  int SEQUENCE = 8;
+  int STRUCTURE = 9;
+  int GRID = 10;
+  int BYTE = 11;
+  int INT16 = 12;
+  int UINT16 = 13;
+  int INT32 = 14;
+  int UINT32 = 15;
+  int FLOAT32 = 16;
+  int FLOAT64 = 17;
+  int STRING = 18;
+  int URL = 19;
+  int WORD = 20;
+
+  int DEFAULT = 0;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "\" \"",
+    "\"\\t\"",
+    "\"\\n\"",
+    "\"\\r\"",
+    "<token of kind 5>",
+    "<DATASET>",
+    "<LIST>",
+    "<SEQUENCE>",
+    "<STRUCTURE>",
+    "<GRID>",
+    "<BYTE>",
+    "<INT16>",
+    "<UINT16>",
+    "<INT32>",
+    "<UINT32>",
+    "<FLOAT32>",
+    "<FLOAT64>",
+    "<STRING>",
+    "<URL>",
+    "<WORD>",
+    "\"{\"",
+    "\"}\"",
+    "\";\"",
+    "\":\"",
+    "\"[\"",
+    "\"]\"",
+    "\"=\"",
+  };
+
+}
diff --git a/dods/dap/parser/DDSParserTokenManager.java b/dods/dap/parser/DDSParserTokenManager.java
new file mode 100644
index 0000000..79dc7a0
--- /dev/null
+++ b/dods/dap/parser/DDSParserTokenManager.java
@@ -0,0 +1,1132 @@
+/* Generated By:JavaCC: Do not edit this line. DDSParserTokenManager.java */
+package dods.dap.parser;
+import java.util.Stack;
+import dods.dap.*;
+
+public class DDSParserTokenManager implements DDSParserConstants
+{
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 58:
+         return jjStopAtPos(0, 24);
+      case 59:
+         return jjStopAtPos(0, 23);
+      case 61:
+         return jjStopAtPos(0, 27);
+      case 91:
+         return jjStopAtPos(0, 25);
+      case 93:
+         return jjStopAtPos(0, 26);
+      case 123:
+         return jjStopAtPos(0, 21);
+      case 125:
+         return jjStopAtPos(0, 22);
+      default :
+         return jjMoveNfa_0(0, 0);
+   }
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+static final long[] jjbitVec0 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 205;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x3ffe82000000000L & l) != 0L)
+                  {
+                     if (kind > 20)
+                        kind = 20;
+                     jjCheckNAdd(22);
+                  }
+                  else if (curChar == 35)
+                  {
+                     if (kind > 5)
+                        kind = 5;
+                     jjCheckNAdd(1);
+                  }
+                  break;
+               case 1:
+                  if ((0xffffffffffffdbffL & l) == 0L)
+                     break;
+                  if (kind > 5)
+                     kind = 5;
+                  jjCheckNAdd(1);
+                  break;
+               case 21:
+                  if ((0x3ffe82000000000L & l) == 0L)
+                     break;
+                  if (kind > 20)
+                     kind = 20;
+                  jjCheckNAdd(22);
+                  break;
+               case 22:
+                  if ((0x3ffe82800000000L & l) == 0L)
+                     break;
+                  if (kind > 20)
+                     kind = 20;
+                  jjCheckNAdd(22);
+                  break;
+               case 75:
+                  if (curChar == 54 && kind > 12)
+                     kind = 12;
+                  break;
+               case 76:
+               case 193:
+               case 196:
+                  if (curChar == 49)
+                     jjCheckNAdd(75);
+                  break;
+               case 79:
+                  if (curChar == 50 && kind > 14)
+                     kind = 14;
+                  break;
+               case 80:
+               case 199:
+               case 202:
+                  if (curChar == 51)
+                     jjCheckNAdd(79);
+                  break;
+               case 84:
+                  if (curChar == 54 && kind > 13)
+                     kind = 13;
+                  break;
+               case 85:
+               case 110:
+               case 114:
+                  if (curChar == 49)
+                     jjCheckNAdd(84);
+                  break;
+               case 89:
+                  if (curChar == 50 && kind > 15)
+                     kind = 15;
+                  break;
+               case 90:
+               case 118:
+               case 122:
+               case 126:
+                  if (curChar == 51)
+                     jjCheckNAdd(89);
+                  break;
+               case 97:
+                  if (curChar == 50 && kind > 16)
+                     kind = 16;
+                  break;
+               case 98:
+               case 172:
+               case 177:
+                  if (curChar == 51)
+                     jjCheckNAdd(97);
+                  break;
+               case 103:
+                  if (curChar == 52 && kind > 17)
+                     kind = 17;
+                  break;
+               case 104:
+               case 182:
+               case 187:
+                  if (curChar == 54)
+                     jjCheckNAdd(103);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7fffffe97fffffeL & l) != 0L)
+                  {
+                     if (kind > 20)
+                        kind = 20;
+                     jjCheckNAdd(22);
+                  }
+                  if (curChar == 73)
+                     jjAddStates(0, 3);
+                  else if (curChar == 70)
+                     jjAddStates(4, 7);
+                  else if (curChar == 83)
+                     jjAddStates(8, 13);
+                  else if (curChar == 85)
+                     jjAddStates(14, 20);
+                  else if (curChar == 102)
+                     jjAddStates(21, 22);
+                  else if (curChar == 117)
+                     jjAddStates(23, 25);
+                  else if (curChar == 105)
+                     jjAddStates(26, 27);
+                  else if (curChar == 115)
+                     jjAddStates(28, 30);
+                  else if (curChar == 68)
+                     jjAddStates(31, 32);
+                  else if (curChar == 76)
+                     jjAddStates(33, 34);
+                  else if (curChar == 71)
+                     jjAddStates(35, 36);
+                  else if (curChar == 66)
+                     jjAddStates(37, 38);
+                  else if (curChar == 98)
+                     jjstateSet[jjnewStateCnt++] = 19;
+                  else if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  else if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 11;
+                  else if (curChar == 100)
+                     jjstateSet[jjnewStateCnt++] = 7;
+                  break;
+               case 1:
+                  if (kind > 5)
+                     kind = 5;
+                  jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 2:
+                  if (curChar == 116 && kind > 6)
+                     kind = 6;
+                  break;
+               case 3:
+               case 48:
+                  if (curChar == 101)
+                     jjCheckNAdd(2);
+                  break;
+               case 4:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 3;
+                  break;
+               case 5:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 4;
+                  break;
+               case 6:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 5;
+                  break;
+               case 7:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 6;
+                  break;
+               case 8:
+                  if (curChar == 100)
+                     jjstateSet[jjnewStateCnt++] = 7;
+                  break;
+               case 9:
+                  if (curChar == 116 && kind > 7)
+                     kind = 7;
+                  break;
+               case 10:
+               case 39:
+                  if (curChar == 115)
+                     jjCheckNAdd(9);
+                  break;
+               case 11:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 10;
+                  break;
+               case 12:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 11;
+                  break;
+               case 13:
+                  if (curChar == 100 && kind > 10)
+                     kind = 10;
+                  break;
+               case 14:
+               case 33:
+                  if (curChar == 105)
+                     jjCheckNAdd(13);
+                  break;
+               case 15:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 14;
+                  break;
+               case 16:
+                  if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  break;
+               case 17:
+                  if (curChar == 101 && kind > 11)
+                     kind = 11;
+                  break;
+               case 18:
+               case 27:
+                  if (curChar == 116)
+                     jjCheckNAdd(17);
+                  break;
+               case 19:
+                  if (curChar == 121)
+                     jjstateSet[jjnewStateCnt++] = 18;
+                  break;
+               case 20:
+                  if (curChar == 98)
+                     jjstateSet[jjnewStateCnt++] = 19;
+                  break;
+               case 21:
+               case 22:
+                  if ((0x7fffffe97fffffeL & l) == 0L)
+                     break;
+                  if (kind > 20)
+                     kind = 20;
+                  jjCheckNAdd(22);
+                  break;
+               case 23:
+                  if (curChar == 66)
+                     jjAddStates(37, 38);
+                  break;
+               case 24:
+                  if (curChar == 69 && kind > 11)
+                     kind = 11;
+                  break;
+               case 25:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 24;
+                  break;
+               case 26:
+                  if (curChar == 89)
+                     jjstateSet[jjnewStateCnt++] = 25;
+                  break;
+               case 28:
+                  if (curChar == 121)
+                     jjstateSet[jjnewStateCnt++] = 27;
+                  break;
+               case 29:
+                  if (curChar == 71)
+                     jjAddStates(35, 36);
+                  break;
+               case 30:
+                  if (curChar == 68 && kind > 10)
+                     kind = 10;
+                  break;
+               case 31:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 30;
+                  break;
+               case 32:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 31;
+                  break;
+               case 34:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 33;
+                  break;
+               case 35:
+                  if (curChar == 76)
+                     jjAddStates(33, 34);
+                  break;
+               case 36:
+                  if (curChar == 84 && kind > 7)
+                     kind = 7;
+                  break;
+               case 37:
+                  if (curChar == 83)
+                     jjstateSet[jjnewStateCnt++] = 36;
+                  break;
+               case 38:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 37;
+                  break;
+               case 40:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 39;
+                  break;
+               case 41:
+                  if (curChar == 68)
+                     jjAddStates(31, 32);
+                  break;
+               case 42:
+                  if (curChar == 84 && kind > 6)
+                     kind = 6;
+                  break;
+               case 43:
+                  if (curChar == 69)
+                     jjstateSet[jjnewStateCnt++] = 42;
+                  break;
+               case 44:
+                  if (curChar == 83)
+                     jjstateSet[jjnewStateCnt++] = 43;
+                  break;
+               case 45:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 44;
+                  break;
+               case 46:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 45;
+                  break;
+               case 47:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 46;
+                  break;
+               case 49:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 48;
+                  break;
+               case 50:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 49;
+                  break;
+               case 51:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 50;
+                  break;
+               case 52:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 51;
+                  break;
+               case 53:
+                  if (curChar == 115)
+                     jjAddStates(28, 30);
+                  break;
+               case 54:
+                  if (curChar == 101 && kind > 8)
+                     kind = 8;
+                  break;
+               case 55:
+               case 141:
+                  if (curChar == 99)
+                     jjCheckNAdd(54);
+                  break;
+               case 56:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 55;
+                  break;
+               case 57:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 56;
+                  break;
+               case 58:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 57;
+                  break;
+               case 59:
+                  if (curChar == 113)
+                     jjstateSet[jjnewStateCnt++] = 58;
+                  break;
+               case 60:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 59;
+                  break;
+               case 61:
+                  if (curChar == 101 && kind > 9)
+                     kind = 9;
+                  break;
+               case 62:
+               case 155:
+                  if (curChar == 114)
+                     jjCheckNAdd(61);
+                  break;
+               case 63:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 62;
+                  break;
+               case 64:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 63;
+                  break;
+               case 65:
+                  if (curChar == 99)
+                     jjstateSet[jjnewStateCnt++] = 64;
+                  break;
+               case 66:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 65;
+                  break;
+               case 67:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 66;
+                  break;
+               case 68:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 67;
+                  break;
+               case 69:
+                  if (curChar == 103 && kind > 18)
+                     kind = 18;
+                  break;
+               case 70:
+               case 167:
+                  if (curChar == 110)
+                     jjCheckNAdd(69);
+                  break;
+               case 71:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 70;
+                  break;
+               case 72:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 71;
+                  break;
+               case 73:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 72;
+                  break;
+               case 74:
+                  if (curChar == 105)
+                     jjAddStates(26, 27);
+                  break;
+               case 77:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 76;
+                  break;
+               case 78:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 77;
+                  break;
+               case 81:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 80;
+                  break;
+               case 82:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 81;
+                  break;
+               case 83:
+                  if (curChar == 117)
+                     jjAddStates(23, 25);
+                  break;
+               case 86:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 85;
+                  break;
+               case 87:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 86;
+                  break;
+               case 88:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 87;
+                  break;
+               case 91:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 90;
+                  break;
+               case 92:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 91;
+                  break;
+               case 93:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 92;
+                  break;
+               case 94:
+                  if (curChar == 108 && kind > 19)
+                     kind = 19;
+                  break;
+               case 95:
+               case 132:
+                  if (curChar == 114)
+                     jjCheckNAdd(94);
+                  break;
+               case 96:
+                  if (curChar == 102)
+                     jjAddStates(21, 22);
+                  break;
+               case 99:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 98;
+                  break;
+               case 100:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 99;
+                  break;
+               case 101:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 100;
+                  break;
+               case 102:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 101;
+                  break;
+               case 105:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 104;
+                  break;
+               case 106:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 105;
+                  break;
+               case 107:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 106;
+                  break;
+               case 108:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 107;
+                  break;
+               case 109:
+                  if (curChar == 85)
+                     jjAddStates(14, 20);
+                  break;
+               case 111:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 110;
+                  break;
+               case 112:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 111;
+                  break;
+               case 113:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 112;
+                  break;
+               case 115:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 114;
+                  break;
+               case 116:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 115;
+                  break;
+               case 117:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 116;
+                  break;
+               case 119:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 118;
+                  break;
+               case 120:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 119;
+                  break;
+               case 121:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 120;
+                  break;
+               case 123:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 122;
+                  break;
+               case 124:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 123;
+                  break;
+               case 125:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 124;
+                  break;
+               case 127:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 126;
+                  break;
+               case 128:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 127;
+                  break;
+               case 129:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 128;
+                  break;
+               case 130:
+                  if (curChar == 76 && kind > 19)
+                     kind = 19;
+                  break;
+               case 131:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 130;
+                  break;
+               case 133:
+                  if (curChar == 83)
+                     jjAddStates(8, 13);
+                  break;
+               case 134:
+                  if (curChar == 69 && kind > 8)
+                     kind = 8;
+                  break;
+               case 135:
+                  if (curChar == 67)
+                     jjstateSet[jjnewStateCnt++] = 134;
+                  break;
+               case 136:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 135;
+                  break;
+               case 137:
+                  if (curChar == 69)
+                     jjstateSet[jjnewStateCnt++] = 136;
+                  break;
+               case 138:
+                  if (curChar == 85)
+                     jjstateSet[jjnewStateCnt++] = 137;
+                  break;
+               case 139:
+                  if (curChar == 81)
+                     jjstateSet[jjnewStateCnt++] = 138;
+                  break;
+               case 140:
+                  if (curChar == 69)
+                     jjstateSet[jjnewStateCnt++] = 139;
+                  break;
+               case 142:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 141;
+                  break;
+               case 143:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 142;
+                  break;
+               case 144:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 143;
+                  break;
+               case 145:
+                  if (curChar == 113)
+                     jjstateSet[jjnewStateCnt++] = 144;
+                  break;
+               case 146:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 145;
+                  break;
+               case 147:
+                  if (curChar == 69 && kind > 9)
+                     kind = 9;
+                  break;
+               case 148:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 147;
+                  break;
+               case 149:
+                  if (curChar == 85)
+                     jjstateSet[jjnewStateCnt++] = 148;
+                  break;
+               case 150:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 149;
+                  break;
+               case 151:
+                  if (curChar == 67)
+                     jjstateSet[jjnewStateCnt++] = 150;
+                  break;
+               case 152:
+                  if (curChar == 85)
+                     jjstateSet[jjnewStateCnt++] = 151;
+                  break;
+               case 153:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 152;
+                  break;
+               case 154:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 153;
+                  break;
+               case 156:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 155;
+                  break;
+               case 157:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 156;
+                  break;
+               case 158:
+                  if (curChar == 99)
+                     jjstateSet[jjnewStateCnt++] = 157;
+                  break;
+               case 159:
+                  if (curChar == 117)
+                     jjstateSet[jjnewStateCnt++] = 158;
+                  break;
+               case 160:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 159;
+                  break;
+               case 161:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 160;
+                  break;
+               case 162:
+                  if (curChar == 71 && kind > 18)
+                     kind = 18;
+                  break;
+               case 163:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 162;
+                  break;
+               case 164:
+                  if (curChar == 73)
+                     jjstateSet[jjnewStateCnt++] = 163;
+                  break;
+               case 165:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 164;
+                  break;
+               case 166:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 165;
+                  break;
+               case 168:
+                  if (curChar == 105)
+                     jjstateSet[jjnewStateCnt++] = 167;
+                  break;
+               case 169:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 168;
+                  break;
+               case 170:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 169;
+                  break;
+               case 171:
+                  if (curChar == 70)
+                     jjAddStates(4, 7);
+                  break;
+               case 173:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 172;
+                  break;
+               case 174:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 173;
+                  break;
+               case 175:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 174;
+                  break;
+               case 176:
+                  if (curChar == 76)
+                     jjstateSet[jjnewStateCnt++] = 175;
+                  break;
+               case 178:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 177;
+                  break;
+               case 179:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 178;
+                  break;
+               case 180:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 179;
+                  break;
+               case 181:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 180;
+                  break;
+               case 183:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 182;
+                  break;
+               case 184:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 183;
+                  break;
+               case 185:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 184;
+                  break;
+               case 186:
+                  if (curChar == 76)
+                     jjstateSet[jjnewStateCnt++] = 185;
+                  break;
+               case 188:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 187;
+                  break;
+               case 189:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 188;
+                  break;
+               case 190:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 189;
+                  break;
+               case 191:
+                  if (curChar == 108)
+                     jjstateSet[jjnewStateCnt++] = 190;
+                  break;
+               case 192:
+                  if (curChar == 73)
+                     jjAddStates(0, 3);
+                  break;
+               case 194:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 193;
+                  break;
+               case 195:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 194;
+                  break;
+               case 197:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 196;
+                  break;
+               case 198:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 197;
+                  break;
+               case 200:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 199;
+                  break;
+               case 201:
+                  if (curChar == 78)
+                     jjstateSet[jjnewStateCnt++] = 200;
+                  break;
+               case 203:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 202;
+                  break;
+               case 204:
+                  if (curChar == 110)
+                     jjstateSet[jjnewStateCnt++] = 203;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 1:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 5)
+                     kind = 5;
+                  jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 205 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   195, 198, 201, 204, 176, 181, 186, 191, 140, 146, 154, 161, 166, 170, 113, 117, 
+   121, 125, 129, 131, 132, 102, 108, 88, 93, 95, 78, 82, 60, 68, 73, 47, 
+   52, 38, 40, 32, 34, 26, 28, 
+};
+public static final String[] jjstrLiteralImages = {
+"", null, null, null, null, null, null, null, null, null, null, null, null, 
+null, null, null, null, null, null, null, null, "\173", "\175", "\73", "\72", 
+"\133", "\135", "\75", };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+};
+static final long[] jjtoToken = {
+   0xfffffc1L, 
+};
+static final long[] jjtoSkip = {
+   0x3eL, 
+};
+private SimpleCharStream input_stream;
+private final int[] jjrounds = new int[205];
+private final int[] jjstateSet = new int[410];
+protected char curChar;
+public DDSParserTokenManager(SimpleCharStream stream)
+{
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public DDSParserTokenManager(SimpleCharStream stream, int lexState)
+{
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 205; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 1 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+private final Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public final Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+
+   try { input_stream.backup(0);
+      while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)
+         curChar = input_stream.BeginToken();
+   }
+   catch (java.io.IOException e1) { continue EOFLoop; }
+   jjmatchedKind = 0x7fffffff;
+   jjmatchedPos = 0;
+   curPos = jjMoveStringLiteralDfa0_0();
+   if (jjmatchedKind != 0x7fffffff)
+   {
+      if (jjmatchedPos + 1 < curPos)
+         input_stream.backup(curPos - jjmatchedPos - 1);
+      if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+      {
+         matchedToken = jjFillToken();
+         return matchedToken;
+      }
+      else
+      {
+         continue EOFLoop;
+      }
+   }
+   int error_line = input_stream.getEndLine();
+   int error_column = input_stream.getEndColumn();
+   String error_after = null;
+   boolean EOFSeen = false;
+   try { input_stream.readChar(); input_stream.backup(1); }
+   catch (java.io.IOException e1) {
+      EOFSeen = true;
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+      if (curChar == '\n' || curChar == '\r') {
+         error_line++;
+         error_column = 0;
+      }
+      else
+         error_column++;
+   }
+   if (!EOFSeen) {
+      input_stream.backup(1);
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+   }
+   throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+}
diff --git a/dods/dap/parser/ErrorParser.java b/dods/dap/parser/ErrorParser.java
new file mode 100644
index 0000000..585eed8
--- /dev/null
+++ b/dods/dap/parser/ErrorParser.java
@@ -0,0 +1,233 @@
+/* Generated By:JavaCC: Do not edit this line. ErrorParser.java */
+package dods.dap.parser;
+import dods.dap.*;
+
+public class ErrorParser implements ErrorParserConstants {
+  private DODSException exception;
+
+  final public void ErrorObject(DODSException exception) throws ParseException {
+    this.exception = exception;
+    jj_consume_token(ERROR);
+    jj_consume_token(16);
+    Contents();
+    jj_consume_token(17);
+    jj_consume_token(18);
+  }
+
+  final public void Contents() throws ParseException {
+    Description();
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case PTYPE:
+      Program();
+      break;
+    default:
+      jj_la1[0] = jj_gen;
+      ;
+    }
+  }
+
+  final public void Description() throws ParseException {
+    Code();
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case MSG:
+      Message();
+      break;
+    default:
+      jj_la1[1] = jj_gen;
+      ;
+    }
+  }
+
+  final public void Program() throws ParseException {
+    ProgramType();
+    ProgramCode();
+  }
+
+  final public void Code() throws ParseException {
+  Token t;
+    jj_consume_token(CODE);
+    jj_consume_token(19);
+    t = jj_consume_token(INT);
+    jj_consume_token(18);
+    try {
+      exception.setErrorCode(Integer.parseInt(t.image));
+    }
+    catch (NumberFormatException e) {
+      exception.setErrorCode(-1);  /* Error setting error code */
+    }
+  }
+
+  final public void Message() throws ParseException {
+  Token t;
+    jj_consume_token(MSG);
+    jj_consume_token(19);
+    t = jj_consume_token(STR);
+    jj_consume_token(18);
+    exception.setErrorMessage(t.image);
+  }
+
+  final public void ProgramType() throws ParseException {
+  Token t;
+    jj_consume_token(PTYPE);
+    jj_consume_token(19);
+    t = jj_consume_token(INT);
+    jj_consume_token(18);
+    try {
+      exception.setProgramType(Integer.parseInt(t.image));
+    }
+    catch (NumberFormatException e) {
+      exception.setProgramType(-1);  /* Error setting program type */
+    }
+  }
+
+  final public void ProgramCode() throws ParseException {
+  Token t;
+    jj_consume_token(PROGRAM);
+    jj_consume_token(19);
+    t = jj_consume_token(STR);
+    jj_consume_token(18);
+    exception.setProgramSource(t.image);
+  }
+
+  public ErrorParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[2];
+  final private int[] jj_la1_0 = {0x200,0x100,};
+
+  public ErrorParser(java.io.InputStream stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ErrorParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 2; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 2; i++) jj_la1[i] = -1;
+  }
+
+  public ErrorParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ErrorParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 2; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 2; i++) jj_la1[i] = -1;
+  }
+
+  public ErrorParser(ErrorParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 2; i++) jj_la1[i] = -1;
+  }
+
+  public void ReInit(ErrorParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 2; i++) jj_la1[i] = -1;
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.Vector jj_expentries = new java.util.Vector();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+
+  final public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[20];
+    for (int i = 0; i < 20; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 2; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 20; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = (int[])jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+}
diff --git a/dods/dap/parser/ErrorParser.jj b/dods/dap/parser/ErrorParser.jj
new file mode 100644
index 0000000..1bb26d2
--- /dev/null
+++ b/dods/dap/parser/ErrorParser.jj
@@ -0,0 +1,128 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+// I don't understand the use - or lack of - exceptions here. The parser
+// takes an instance of DODSException as an argument and modifies it. It
+// never throws ParserExceptions maybe because it assumes (probably
+// correctly) that all text Error objects will have been generated by
+// Error.print() and that method will not make any mistakes. 11/9/99 jhrg
+
+options {
+  STATIC = false;  // we may want to use this in a multithreaded client/server
+}
+
+PARSER_BEGIN(ErrorParser)
+
+package dods.dap.parser;
+import dods.dap.*;
+
+public class ErrorParser {
+  private DODSException exception;
+}
+
+PARSER_END(ErrorParser)
+
+SKIP : {
+ " "
+| "\t"
+| "\n"
+| "\r"
+| < "#" (~["\n","\r"])* >
+}
+
+TOKEN : {
+	<ERROR: "error"|"Error"|"ERROR"> |
+	<CODE: "code"|"Code"|"CODE"> |
+	<MSG: "message"|"Message"|"MESSAGE"> |
+	<PTYPE: "program_type"|"ProgramType"|"PROGRAM_TYPE"|"Program_Type"> |
+	<PROGRAM: "program"|"Program"|"PROGRAM"> |
+	<INT: (["0"-"9"])+> |
+	<STR: <UNQUOTED_STR> | <QUOTED_STR> > |
+        <#UNQUOTED_STR: (["-","+","a"-"z","A"-"Z","0"-"9","_",".","/",":","%","+","\\","-","(",")"])+> |
+        <#QUOTED_STR: "\"" (~["\""] | "\\\"")* "\""> |
+        <UNTERM_QUOTE: "\"" (~["\""] | "\\\"")* >
+}
+
+void ErrorObject(DODSException exception) :
+{
+    this.exception = exception;
+}
+{
+  <ERROR> "{" Contents() "}" ";"
+}
+
+void Contents() :
+{}
+{
+  Description() (Program())?
+}
+
+void Description() :
+{}
+{
+  Code() (Message())?
+}
+
+void Program() :
+{}
+{
+  ProgramType() ProgramCode()
+}
+
+void Code() :
+{
+  Token t;
+}
+{
+  <CODE> "=" t=<INT> ";" {
+    try {
+      exception.setErrorCode(Integer.parseInt(t.image));
+    }
+    catch (NumberFormatException e) {
+      exception.setErrorCode(-1);  /* Error setting error code */
+    }
+  }
+}
+
+void Message() :
+{
+  Token t;
+}
+{
+  <MSG> "=" t=<STR> ";" {
+    exception.setErrorMessage(t.image);
+  }
+}
+
+void ProgramType() :
+{
+  Token t;
+}
+{
+  <PTYPE> "=" t=<INT> ";" {
+    try {
+      exception.setProgramType(Integer.parseInt(t.image));
+    }
+    catch (NumberFormatException e) {
+      exception.setProgramType(-1);  /* Error setting program type */
+    }
+  }
+}
+
+void ProgramCode() :
+{
+  Token t;
+}
+{
+  <PROGRAM> "=" t=<STR> ";" {
+    exception.setProgramSource(t.image);
+  }
+}
diff --git a/dods/dap/parser/ErrorParserConstants.java b/dods/dap/parser/ErrorParserConstants.java
new file mode 100644
index 0000000..4cdda20
--- /dev/null
+++ b/dods/dap/parser/ErrorParserConstants.java
@@ -0,0 +1,43 @@
+/* Generated By:JavaCC: Do not edit this line. ErrorParserConstants.java */
+package dods.dap.parser;
+
+public interface ErrorParserConstants {
+
+  int EOF = 0;
+  int ERROR = 6;
+  int CODE = 7;
+  int MSG = 8;
+  int PTYPE = 9;
+  int PROGRAM = 10;
+  int INT = 11;
+  int STR = 12;
+  int UNQUOTED_STR = 13;
+  int QUOTED_STR = 14;
+  int UNTERM_QUOTE = 15;
+
+  int DEFAULT = 0;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "\" \"",
+    "\"\\t\"",
+    "\"\\n\"",
+    "\"\\r\"",
+    "<token of kind 5>",
+    "<ERROR>",
+    "<CODE>",
+    "<MSG>",
+    "<PTYPE>",
+    "<PROGRAM>",
+    "<INT>",
+    "<STR>",
+    "<UNQUOTED_STR>",
+    "<QUOTED_STR>",
+    "<UNTERM_QUOTE>",
+    "\"{\"",
+    "\"}\"",
+    "\";\"",
+    "\"=\"",
+  };
+
+}
diff --git a/dods/dap/parser/ErrorParserTokenManager.java b/dods/dap/parser/ErrorParserTokenManager.java
new file mode 100644
index 0000000..31c8e1c
--- /dev/null
+++ b/dods/dap/parser/ErrorParserTokenManager.java
@@ -0,0 +1,827 @@
+/* Generated By:JavaCC: Do not edit this line. ErrorParserTokenManager.java */
+package dods.dap.parser;
+import dods.dap.*;
+
+public class ErrorParserTokenManager implements ErrorParserConstants
+{
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 59:
+         return jjStopAtPos(0, 18);
+      case 61:
+         return jjStopAtPos(0, 19);
+      case 123:
+         return jjStopAtPos(0, 16);
+      case 125:
+         return jjStopAtPos(0, 17);
+      default :
+         return jjMoveNfa_0(0, 0);
+   }
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+static final long[] jjbitVec0 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 114;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7ffeb2000000000L & l) != 0L)
+                  {
+                     if (kind > 12)
+                        kind = 12;
+                     jjCheckNAdd(19);
+                  }
+                  else if (curChar == 34)
+                  {
+                     if (kind > 15)
+                        kind = 15;
+                     jjCheckNAddStates(0, 4);
+                  }
+                  else if (curChar == 35)
+                  {
+                     if (kind > 5)
+                        kind = 5;
+                     jjCheckNAdd(1);
+                  }
+                  if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 11)
+                        kind = 11;
+                     jjCheckNAdd(18);
+                  }
+                  break;
+               case 1:
+                  if ((0xffffffffffffdbffL & l) == 0L)
+                     break;
+                  if (kind > 5)
+                     kind = 5;
+                  jjCheckNAdd(1);
+                  break;
+               case 18:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 11)
+                     kind = 11;
+                  jjCheckNAdd(18);
+                  break;
+               case 19:
+                  if ((0x7ffeb2000000000L & l) == 0L)
+                     break;
+                  if (kind > 12)
+                     kind = 12;
+                  jjCheckNAdd(19);
+                  break;
+               case 64:
+                  if (curChar != 34)
+                     break;
+                  if (kind > 15)
+                     kind = 15;
+                  jjCheckNAddStates(0, 4);
+                  break;
+               case 65:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddStates(5, 7);
+                  break;
+               case 66:
+                  if (curChar == 34)
+                     jjCheckNAddStates(5, 7);
+                  break;
+               case 68:
+                  if (curChar == 34 && kind > 12)
+                     kind = 12;
+                  break;
+               case 69:
+                  if ((0xfffffffbffffffffL & l) == 0L)
+                     break;
+                  if (kind > 15)
+                     kind = 15;
+                  jjCheckNAddTwoStates(69, 71);
+                  break;
+               case 70:
+                  if (curChar != 34)
+                     break;
+                  if (kind > 15)
+                     kind = 15;
+                  jjCheckNAddTwoStates(69, 71);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7fffffe97fffffeL & l) != 0L)
+                  {
+                     if (kind > 12)
+                        kind = 12;
+                     jjCheckNAdd(19);
+                  }
+                  if (curChar == 80)
+                     jjAddStates(8, 12);
+                  else if (curChar == 112)
+                     jjAddStates(13, 14);
+                  else if (curChar == 69)
+                     jjAddStates(15, 16);
+                  else if (curChar == 67)
+                     jjAddStates(17, 18);
+                  else if (curChar == 77)
+                     jjAddStates(19, 20);
+                  else if (curChar == 109)
+                     jjstateSet[jjnewStateCnt++] = 16;
+                  else if (curChar == 99)
+                     jjstateSet[jjnewStateCnt++] = 9;
+                  else if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 5;
+                  break;
+               case 1:
+                  if (kind > 5)
+                     kind = 5;
+                  jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 2:
+                  if (curChar == 114 && kind > 6)
+                     kind = 6;
+                  break;
+               case 3:
+               case 39:
+                  if (curChar == 111)
+                     jjCheckNAdd(2);
+                  break;
+               case 4:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 3;
+                  break;
+               case 5:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 4;
+                  break;
+               case 6:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 5;
+                  break;
+               case 7:
+                  if (curChar == 101 && kind > 7)
+                     kind = 7;
+                  break;
+               case 8:
+               case 33:
+                  if (curChar == 100)
+                     jjCheckNAdd(7);
+                  break;
+               case 9:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 8;
+                  break;
+               case 10:
+                  if (curChar == 99)
+                     jjstateSet[jjnewStateCnt++] = 9;
+                  break;
+               case 11:
+                  if (curChar == 101 && kind > 8)
+                     kind = 8;
+                  break;
+               case 12:
+               case 21:
+                  if (curChar == 103)
+                     jjCheckNAdd(11);
+                  break;
+               case 13:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 12;
+                  break;
+               case 14:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 13;
+                  break;
+               case 15:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 14;
+                  break;
+               case 16:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  break;
+               case 17:
+                  if (curChar == 109)
+                     jjstateSet[jjnewStateCnt++] = 16;
+                  break;
+               case 19:
+                  if ((0x7fffffe97fffffeL & l) == 0L)
+                     break;
+                  if (kind > 12)
+                     kind = 12;
+                  jjCheckNAdd(19);
+                  break;
+               case 20:
+                  if (curChar == 77)
+                     jjAddStates(19, 20);
+                  break;
+               case 22:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 21;
+                  break;
+               case 23:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 22;
+                  break;
+               case 24:
+                  if (curChar == 115)
+                     jjstateSet[jjnewStateCnt++] = 23;
+                  break;
+               case 25:
+                  if (curChar == 101)
+                     jjstateSet[jjnewStateCnt++] = 24;
+                  break;
+               case 26:
+                  if (curChar == 69 && kind > 8)
+                     kind = 8;
+                  break;
+               case 27:
+                  if (curChar == 71)
+                     jjstateSet[jjnewStateCnt++] = 26;
+                  break;
+               case 28:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 27;
+                  break;
+               case 29:
+                  if (curChar == 83)
+                     jjstateSet[jjnewStateCnt++] = 28;
+                  break;
+               case 30:
+                  if (curChar == 83)
+                     jjstateSet[jjnewStateCnt++] = 29;
+                  break;
+               case 31:
+                  if (curChar == 69)
+                     jjstateSet[jjnewStateCnt++] = 30;
+                  break;
+               case 32:
+                  if (curChar == 67)
+                     jjAddStates(17, 18);
+                  break;
+               case 34:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 33;
+                  break;
+               case 35:
+                  if (curChar == 69 && kind > 7)
+                     kind = 7;
+                  break;
+               case 36:
+                  if (curChar == 68)
+                     jjstateSet[jjnewStateCnt++] = 35;
+                  break;
+               case 37:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 36;
+                  break;
+               case 38:
+                  if (curChar == 69)
+                     jjAddStates(15, 16);
+                  break;
+               case 40:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 39;
+                  break;
+               case 41:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 40;
+                  break;
+               case 42:
+                  if (curChar == 82 && kind > 6)
+                     kind = 6;
+                  break;
+               case 43:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 42;
+                  break;
+               case 44:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 43;
+                  break;
+               case 45:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 44;
+                  break;
+               case 46:
+                  if (curChar == 112)
+                     jjAddStates(13, 14);
+                  break;
+               case 47:
+                  if (curChar == 101 && kind > 9)
+                     kind = 9;
+                  break;
+               case 48:
+               case 73:
+               case 93:
+                  if (curChar == 112)
+                     jjCheckNAdd(47);
+                  break;
+               case 49:
+                  if (curChar == 121)
+                     jjstateSet[jjnewStateCnt++] = 48;
+                  break;
+               case 50:
+                  if (curChar == 116)
+                     jjstateSet[jjnewStateCnt++] = 49;
+                  break;
+               case 51:
+                  if (curChar == 95)
+                     jjstateSet[jjnewStateCnt++] = 50;
+                  break;
+               case 52:
+                  if (curChar == 109)
+                     jjstateSet[jjnewStateCnt++] = 51;
+                  break;
+               case 53:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 52;
+                  break;
+               case 54:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 53;
+                  break;
+               case 55:
+                  if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 54;
+                  break;
+               case 56:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 55;
+                  break;
+               case 57:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 56;
+                  break;
+               case 58:
+                  if (curChar == 109 && kind > 10)
+                     kind = 10;
+                  break;
+               case 59:
+               case 103:
+                  if (curChar == 97)
+                     jjCheckNAdd(58);
+                  break;
+               case 60:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 59;
+                  break;
+               case 61:
+                  if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 60;
+                  break;
+               case 62:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 61;
+                  break;
+               case 63:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 62;
+                  break;
+               case 65:
+                  jjAddStates(5, 7);
+                  break;
+               case 67:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 66;
+                  break;
+               case 69:
+                  if (kind > 15)
+                     kind = 15;
+                  jjAddStates(21, 22);
+                  break;
+               case 71:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 70;
+                  break;
+               case 72:
+                  if (curChar == 80)
+                     jjAddStates(8, 12);
+                  break;
+               case 74:
+                  if (curChar == 121)
+                     jjstateSet[jjnewStateCnt++] = 73;
+                  break;
+               case 75:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 74;
+                  break;
+               case 76:
+                  if (curChar == 109)
+                     jjstateSet[jjnewStateCnt++] = 75;
+                  break;
+               case 77:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 76;
+                  break;
+               case 78:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 77;
+                  break;
+               case 79:
+                  if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 78;
+                  break;
+               case 80:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 79;
+                  break;
+               case 81:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 80;
+                  break;
+               case 82:
+                  if (curChar == 69 && kind > 9)
+                     kind = 9;
+                  break;
+               case 83:
+                  if (curChar == 80)
+                     jjstateSet[jjnewStateCnt++] = 82;
+                  break;
+               case 84:
+                  if (curChar == 89)
+                     jjstateSet[jjnewStateCnt++] = 83;
+                  break;
+               case 85:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 84;
+                  break;
+               case 86:
+                  if (curChar == 95)
+                     jjstateSet[jjnewStateCnt++] = 85;
+                  break;
+               case 87:
+                  if (curChar == 77)
+                     jjstateSet[jjnewStateCnt++] = 86;
+                  break;
+               case 88:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 87;
+                  break;
+               case 89:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 88;
+                  break;
+               case 90:
+                  if (curChar == 71)
+                     jjstateSet[jjnewStateCnt++] = 89;
+                  break;
+               case 91:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 90;
+                  break;
+               case 92:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 91;
+                  break;
+               case 94:
+                  if (curChar == 121)
+                     jjstateSet[jjnewStateCnt++] = 93;
+                  break;
+               case 95:
+                  if (curChar == 84)
+                     jjstateSet[jjnewStateCnt++] = 94;
+                  break;
+               case 96:
+                  if (curChar == 95)
+                     jjstateSet[jjnewStateCnt++] = 95;
+                  break;
+               case 97:
+                  if (curChar == 109)
+                     jjstateSet[jjnewStateCnt++] = 96;
+                  break;
+               case 98:
+                  if (curChar == 97)
+                     jjstateSet[jjnewStateCnt++] = 97;
+                  break;
+               case 99:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 98;
+                  break;
+               case 100:
+                  if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 99;
+                  break;
+               case 101:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 100;
+                  break;
+               case 102:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 101;
+                  break;
+               case 104:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 103;
+                  break;
+               case 105:
+                  if (curChar == 103)
+                     jjstateSet[jjnewStateCnt++] = 104;
+                  break;
+               case 106:
+                  if (curChar == 111)
+                     jjstateSet[jjnewStateCnt++] = 105;
+                  break;
+               case 107:
+                  if (curChar == 114)
+                     jjstateSet[jjnewStateCnt++] = 106;
+                  break;
+               case 108:
+                  if (curChar == 77 && kind > 10)
+                     kind = 10;
+                  break;
+               case 109:
+                  if (curChar == 65)
+                     jjstateSet[jjnewStateCnt++] = 108;
+                  break;
+               case 110:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 109;
+                  break;
+               case 111:
+                  if (curChar == 71)
+                     jjstateSet[jjnewStateCnt++] = 110;
+                  break;
+               case 112:
+                  if (curChar == 79)
+                     jjstateSet[jjnewStateCnt++] = 111;
+                  break;
+               case 113:
+                  if (curChar == 82)
+                     jjstateSet[jjnewStateCnt++] = 112;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 1:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 5)
+                     kind = 5;
+                  jjstateSet[jjnewStateCnt++] = 1;
+                  break;
+               case 65:
+                  if ((jjbitVec0[i2] & l2) != 0L)
+                     jjAddStates(5, 7);
+                  break;
+               case 69:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 15)
+                     kind = 15;
+                  jjAddStates(21, 22);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 114 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   65, 67, 68, 69, 71, 65, 67, 68, 81, 92, 102, 107, 113, 57, 63, 41, 
+   45, 34, 37, 25, 31, 69, 71, 
+};
+public static final String[] jjstrLiteralImages = {
+"", null, null, null, null, null, null, null, null, null, null, null, null, 
+null, null, null, "\173", "\175", "\73", "\75", };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+};
+static final long[] jjtoToken = {
+   0xf9fc1L, 
+};
+static final long[] jjtoSkip = {
+   0x3eL, 
+};
+private SimpleCharStream input_stream;
+private final int[] jjrounds = new int[114];
+private final int[] jjstateSet = new int[228];
+protected char curChar;
+public ErrorParserTokenManager(SimpleCharStream stream)
+{
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public ErrorParserTokenManager(SimpleCharStream stream, int lexState)
+{
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 114; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 1 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+private final Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public final Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+
+   try { input_stream.backup(0);
+      while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)
+         curChar = input_stream.BeginToken();
+   }
+   catch (java.io.IOException e1) { continue EOFLoop; }
+   jjmatchedKind = 0x7fffffff;
+   jjmatchedPos = 0;
+   curPos = jjMoveStringLiteralDfa0_0();
+   if (jjmatchedKind != 0x7fffffff)
+   {
+      if (jjmatchedPos + 1 < curPos)
+         input_stream.backup(curPos - jjmatchedPos - 1);
+      if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+      {
+         matchedToken = jjFillToken();
+         return matchedToken;
+      }
+      else
+      {
+         continue EOFLoop;
+      }
+   }
+   int error_line = input_stream.getEndLine();
+   int error_column = input_stream.getEndColumn();
+   String error_after = null;
+   boolean EOFSeen = false;
+   try { input_stream.readChar(); input_stream.backup(1); }
+   catch (java.io.IOException e1) {
+      EOFSeen = true;
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+      if (curChar == '\n' || curChar == '\r') {
+         error_line++;
+         error_column = 0;
+      }
+      else
+         error_column++;
+   }
+   if (!EOFSeen) {
+      input_stream.backup(1);
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+   }
+   throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+}
diff --git a/dods/dap/parser/ExprParser.java b/dods/dap/parser/ExprParser.java
new file mode 100644
index 0000000..0143d61
--- /dev/null
+++ b/dods/dap/parser/ExprParser.java
@@ -0,0 +1,1079 @@
+/* Generated By:JavaCC: Do not edit this line. ExprParser.java */
+package dods.dap.parser;
+
+import java.util.Vector;
+import java.util.Stack;
+
+import dods.dap.*;
+import dods.dap.Server.*;
+
+/** The constraint expression parser class. <p>
+    
+    Because it only makes sense to evaluate CEs when serving data, the
+    BaseTyeFactory <em>must</em> create instances of the SDtype classes, not
+    the Dtype classes. The is because we use the setRead method of the class
+    ServerMethods when creating constants (to ensure that the evaluator
+    doesn't try to read tem from the dataset!).
+    
+    @author jhrg */
+
+public class ExprParser implements ExprParserConstants {
+    private ServerDDS sdds;
+    private CEEvaluator ceEval;
+    private BaseTypeFactory factory;
+    private ClauseFactory clauseFactory;
+
+    /** Run the named projection function. Projection functions are run for
+	their side effect; the return value is discarded. 
+	@param name The name of the projection function, look this up in the
+	ServerDDS. 
+	@param btv A vector of BaseType variables that are the arguments of
+	the projection function. */
+    private void runProjectionFunction(String name, Vector btv) {
+    }
+
+    /** Remove double quotes from around a string. If there's not both start
+	and ending quotes, does nothing.
+	@param s The source string.
+	@return The string without double quotes. */
+    private String removeQuotes(String s) {
+        if (s.startsWith("\"") && s.endsWith("\""))
+            return s.substring(1, s.length() - 1);
+        else
+            return s;
+    }
+
+    /** Given a stack of BaseType variables, mark these as part of the
+     * current projection. This function assumes that if the TOS contains a
+     * Ctor type variable, all of its members are to be projected. Also
+     * assume  all variables under the TOS are Ctor variables and
+     * only the ctor itself is to be projected; the member within the Ctor
+     * that is part of the projection will be on the stack, too. */
+    private void markStackedVariables(Stack s) {
+        // Reverse the stack.
+        Stack bts = new Stack();
+        // System.err.println("Variables to be marked:");
+        while (!s.empty()) {
+            // System.err.println(((BaseType)s.peek()).getName());
+            bts.push(s.pop());
+        }
+
+        // For each but the last stack element, set the projection.
+        // setProject(true, false) for a ctor type sets the projection for
+        // the ctor itself but *does not* set the projection for all its
+        // children. Thus, if a user wants the variable S.X, and S contains Y
+        // and Z too, S's projection will be set (so serialize will descend
+        // into S) but X, Y and Z's projection remain clear. In this example,
+        // X's projection is set by the code that follows the while loop.
+        // 1/28/2000 jhrg
+        while (bts.size() > 1) {
+            ServerMethods ct = (ServerMethods)bts.pop();
+            ct.setProject(true, false);
+        }
+
+        // For the last element, project the entire variable.
+        ServerMethods bt = (ServerMethods)bts.pop();
+        bt.setProject(true, true);
+    }
+
+/** This is the entry point for the Constraint expression parser.<p>
+
+    Note that this method catches any ParserException and recasts it to a
+    DODSException after replacing all double quotes with single quotes.
+
+    @param ceEval A CEEvaluator instance where any select clauses will be
+    dumped and from which the DDS, which supplies the evaluation environment,
+    will be read.
+    @param factory A BaseTypeFactory used to generate instances of variables
+    as needed (e.g., to hold values of constants).
+    @param clauseFactory A ClauseFactory used to generate instances of each
+    type of clause. */
+  final public void constraint_expression(CEEvaluator ceEval, BaseTypeFactory factory,
+                           ClauseFactory clauseFactory) throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, DODSException, InvalidParameterException, SBHException, WrongTypeException {
+    this.ceEval = ceEval;
+    this.sdds = ceEval.getDDS();
+    this.factory = factory;
+    this.clauseFactory = clauseFactory;
+    try {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ID:
+        projection();
+        label_1:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case AMPERSAND:
+            ;
+            break;
+          default:
+            jj_la1[0] = jj_gen;
+            break label_1;
+          }
+          selection();
+        }
+        break;
+      default:
+        jj_la1[2] = jj_gen;
+                ceEval.markAll(true); // No projection; mark all
+
+        label_2:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case AMPERSAND:
+            ;
+            break;
+          default:
+            jj_la1[1] = jj_gen;
+            break label_2;
+          }
+          selection();
+        }
+      }
+    } catch (ParseException pe) {
+        // Extract the message and rethrow after changing all the double
+        // quotes to single quotes so that the code that (might) send the
+        // text of this exception back to a client over the network won't
+        // barf. 
+        String msg = pe.getMessage();
+        if (msg != null)
+            msg = msg.replace('\"', '\'');
+        {if (true) throw new DODSException(DODSException.MALFORMED_EXPR, msg);}
+    }
+  }
+
+  final public void selection() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, ParseException, SBHException, DODSException {
+    Clause c;
+    jj_consume_token(AMPERSAND);
+    c = clause();
+        ceEval.appendClause(c);
+  }
+
+  final public Clause clause() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, ParseException, DODSException {
+    Clause c;
+    SubClause lop, rop;
+    Vector ropv;
+    int op;
+    if (jj_2_1(2)) {
+      c = bool_function();
+        {if (true) return c;}
+    } else {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ASTERISK:
+      case ID:
+      case INT:
+      case FLOAT:
+      case STR:
+        lop = value();
+        op = rel_op();
+                                   ropv = new Vector();
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case ASTERISK:
+        case ID:
+        case INT:
+        case FLOAT:
+        case STR:
+          rop = value();
+                           ropv.addElement(rop);
+          break;
+        case LBRACE:
+          jj_consume_token(LBRACE);
+          label_3:
+          while (true) {
+            rop = value();
+                                       ropv.addElement(rop);
+            switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+            case COMMA:
+              jj_consume_token(COMMA);
+              break;
+            default:
+              jj_la1[3] = jj_gen;
+              ;
+            }
+            switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+            case ASTERISK:
+            case ID:
+            case INT:
+            case FLOAT:
+            case STR:
+              ;
+              break;
+            default:
+              jj_la1[4] = jj_gen;
+              break label_3;
+            }
+          }
+          jj_consume_token(RBRACE);
+          break;
+        default:
+          jj_la1[5] = jj_gen;
+          jj_consume_token(-1);
+          throw new ParseException();
+        }
+        {if (true) return clauseFactory.newRelOpClause(op, lop, ropv);}
+        break;
+      default:
+        jj_la1[6] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Clause bool_function() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, ParseException, DODSException {
+    Token name;                 // Name of the function
+    Vector btv;
+    name = jj_consume_token(ID);
+    btv = arg_list();
+        {if (true) return clauseFactory.newBoolFunctionClause(name.image, btv);}
+    throw new Error("Missing return statement in function");
+  }
+
+// Note that I'm using the constants from the ExprParserConstants interface
+// rather than (re)define a new set of constants. 7/20/99 jhrg
+  final public int rel_op() throws ParseException {
+    Token op;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case EQUAL:
+      op = jj_consume_token(EQUAL);
+      {if (true) return op.kind;}
+      break;
+    case NOT_EQUAL:
+      op = jj_consume_token(NOT_EQUAL);
+      {if (true) return op.kind;}
+      break;
+    case GREATER:
+      op = jj_consume_token(GREATER);
+      {if (true) return op.kind;}
+      break;
+    case GREATER_EQL:
+      op = jj_consume_token(GREATER_EQL);
+      {if (true) return op.kind;}
+      break;
+    case LESS:
+      op = jj_consume_token(LESS);
+      {if (true) return op.kind;}
+      break;
+    case LESS_EQL:
+      op = jj_consume_token(LESS_EQL);
+      {if (true) return op.kind;}
+      break;
+    case REGEXP:
+      op = jj_consume_token(REGEXP);
+      {if (true) return op.kind;}
+      break;
+    default:
+      jj_la1[7] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final public void projection() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, ParseException, InvalidParameterException, SBHException, WrongTypeException, DODSException {
+    proj_clause();
+    label_4:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case COMMA:
+        ;
+        break;
+      default:
+        jj_la1[8] = jj_gen;
+        break label_4;
+      }
+      jj_consume_token(COMMA);
+      proj_clause();
+    }
+  }
+
+// Note that we have to keep a count of the array index number for the calls
+// to array_index(). 7/20/99 jhrg
+  final public void proj_clause() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, ParseException, InvalidParameterException, SBHException, WrongTypeException, DODSException {
+    Token t;
+    Vector btv;
+    if (jj_2_2(2)) {
+      t = jj_consume_token(ID);
+      btv = arg_list();
+        ceEval.appendClause(clauseFactory.newBTFunctionClause(t.image, btv));
+        // runProjectionFunction(t.image, btv);
+
+    } else {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ID:
+        proj_variable();
+        break;
+      default:
+        jj_la1[9] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+  final public void proj_variable() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, ParseException, InvalidParameterException, SBHException, WrongTypeException, DODSException {
+    Token t;
+    BaseType bt;
+    Stack comp = new Stack();
+    comp = component(comp);
+    label_5:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case SEPARATOR:
+        ;
+        break;
+      default:
+        jj_la1[10] = jj_gen;
+        break label_5;
+      }
+      jj_consume_token(SEPARATOR);
+      comp = component(comp);
+    }
+        markStackedVariables(comp);
+  }
+
+  final public Stack component(Stack components) throws ParseException, ParseException, DODSException {
+    Token t;
+    int count = 0;
+    ServerArrayMethods abt;
+    if (jj_2_3(2)) {
+      t = jj_consume_token(ID);
+        components = sdds.search(t.image, components);
+        try {
+            abt = (ServerArrayMethods)components.peek();
+        }
+        catch (ClassCastException cce) {
+            String msg = "Attempt to treat the variable `" + t.image
+            + "' as if it is an array.";
+            {if (true) throw new DODSException(DODSException.MALFORMED_EXPR, msg);}
+        }
+      label_6:
+      while (true) {
+        array_index(count++, abt);
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case LBRACKET:
+          ;
+          break;
+        default:
+          jj_la1[11] = jj_gen;
+          break label_6;
+        }
+      }
+        {if (true) return components;}
+    } else {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ID:
+        t = jj_consume_token(ID);
+        components = sdds.search(t.image, components);
+        {if (true) return components;}
+        break;
+      default:
+        jj_la1[12] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final public Vector arg_list() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, ParseException, DODSException {
+    Vector cv = new Vector();
+    Clause c;
+    jj_consume_token(LPAREN);
+    label_7:
+    while (true) {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ASTERISK:
+      case ID:
+      case INT:
+      case FLOAT:
+      case STR:
+        ;
+        break;
+      default:
+        jj_la1[13] = jj_gen;
+        break label_7;
+      }
+      c = value();
+                           cv.addElement(c);
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case COMMA:
+        jj_consume_token(COMMA);
+        break;
+      default:
+        jj_la1[14] = jj_gen;
+        ;
+      }
+    }
+    jj_consume_token(RPAREN);
+              {if (true) return cv;}
+    throw new Error("Missing return statement in function");
+  }
+
+// Note that we must explicitly catch the NumberFormatExceptions since it is
+// a child of RuntimeException. Might as well do it here and munge the
+// message into a DODSException object. 1/6/2000 jhrg 
+  final public void array_index(int count, ServerArrayMethods bt) throws ParseException, ParseException, DODSException, InvalidParameterException, SBHException {
+    Token t1, t2, t3;
+    if (jj_2_4(5)) {
+      jj_consume_token(LBRACKET);
+      t1 = jj_consume_token(INT);
+      jj_consume_token(COLON);
+      t2 = jj_consume_token(INT);
+      jj_consume_token(COLON);
+      t3 = jj_consume_token(INT);
+      jj_consume_token(RBRACKET);
+        try {
+            bt.setProjection(count, Integer.parseInt(t1.image),
+                             Integer.parseInt(t2.image),
+                             Integer.parseInt(t3.image) );
+        }
+        catch (NumberFormatException e) {
+            {if (true) throw new DODSException(DODSException.MALFORMED_EXPR,
+"Could not parse one of " + t1.image + ", " + t2.image + ", " + t3.image +
+" as an integer: " + e.getMessage());}
+        }
+    } else if (jj_2_5(3)) {
+      jj_consume_token(LBRACKET);
+      t1 = jj_consume_token(INT);
+      jj_consume_token(COLON);
+      t2 = jj_consume_token(INT);
+      jj_consume_token(RBRACKET);
+          try {
+              bt.setProjection(count, Integer.parseInt(t1.image), 1,
+                               Integer.parseInt(t2.image) );
+          }
+          catch (NumberFormatException e) {
+              {if (true) throw new DODSException(DODSException.MALFORMED_EXPR,
+"Could not parse one of " + t1.image + ", " + t2.image +
+" as an integer: " + e.getMessage());}
+          }
+    } else {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case LBRACKET:
+        jj_consume_token(LBRACKET);
+        t1 = jj_consume_token(INT);
+        jj_consume_token(RBRACKET);
+           try {
+               bt.setProjection(count, Integer.parseInt(t1.image), 1,
+                                Integer.parseInt(t1.image) );
+           }
+           catch (NumberFormatException e) {
+               {if (true) throw new DODSException(DODSException.MALFORMED_EXPR,
+"Could not parse " + t1.image + " as an integer: " + e.getMessage());}
+           }
+        break;
+      default:
+        jj_la1[15] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+  }
+
+// Values only appear in the selection part of a CE. 
+// Use clauseFactory to create the appropriate type of clause - joew
+  final public SubClause value() throws ParseException, NoSuchVariableException, NoSuchFunctionException, InvalidOperatorException, DODSException, ParseException {
+    Token t;
+    Vector btv;
+    BaseType bt;
+    String name;
+    Stack comp = new Stack();
+    if (jj_2_6(2)) {
+      jj_consume_token(ASTERISK);
+      comp = component(comp);
+      label_8:
+      while (true) {
+        switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+        case SEPARATOR:
+          ;
+          break;
+        default:
+          jj_la1[16] = jj_gen;
+          break label_8;
+        }
+        jj_consume_token(SEPARATOR);
+        comp = component(comp);
+      }
+        bt = (BaseType)comp.pop();
+        try {
+            {if (true) return clauseFactory.newDereferenceClause(((DURL)bt).getValue());}
+        } catch (ClassCastException cce) {
+          {if (true) throw new DODSException("Attempt to reference non-URL component " +
+                                  bt.getName() + " as a URL.");}
+        }
+    } else if (jj_2_7(2)) {
+      jj_consume_token(ASTERISK);
+      t = jj_consume_token(STR);
+        {if (true) return clauseFactory.newDereferenceClause(t.image);}
+    } else if (jj_2_8(2)) {
+      t = jj_consume_token(ID);
+      btv = arg_list();
+        {if (true) return clauseFactory.newBTFunctionClause(t.image, btv);}
+    } else {
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case ID:
+        comp = component(comp);
+        label_9:
+        while (true) {
+          switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+          case SEPARATOR:
+            ;
+            break;
+          default:
+            jj_la1[17] = jj_gen;
+            break label_9;
+          }
+          jj_consume_token(SEPARATOR);
+          comp = component(comp);
+        }
+        bt = (BaseType)comp.pop();
+        {if (true) return clauseFactory.newValueClause(bt, false);}
+        break;
+      case INT:
+      case FLOAT:
+      case STR:
+        bt = constant();
+        {if (true) return clauseFactory.newValueClause(bt, true);}
+        break;
+      default:
+        jj_la1[18] = jj_gen;
+        jj_consume_token(-1);
+        throw new ParseException();
+      }
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final public String field() throws ParseException, ParseException {
+    String name;
+    Token t;
+    t = jj_consume_token(ID);
+        name = t.image;
+    label_10:
+    while (true) {
+      jj_consume_token(SEPARATOR);
+      t = jj_consume_token(ID);
+            name += "." + t.image;
+      switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+      case SEPARATOR:
+        ;
+        break;
+      default:
+        jj_la1[19] = jj_gen;
+        break label_10;
+      }
+    }
+        {if (true) return name;}
+    throw new Error("Missing return statement in function");
+  }
+
+// See my comment above about the NumberFormatExceptions. 8/20/99 jhrg
+  final public BaseType constant() throws ParseException, DODSException, ParseException {
+    Token t;
+    switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+    case INT:
+      t = jj_consume_token(INT);
+        //System.out.println("Setting constant value: "+t.image + " (<INT>)");
+        DInt32 i = factory.newDInt32("constant");
+        try {
+            i.setValue(Integer.parseInt(t.image));
+            ((ServerMethods)i).setRead(true);
+            ((ServerMethods)i).setProject(true);
+
+           // System.out.print("Set value of BaseType. from printVal(): ");
+           // i.printVal(System.out,"");
+           // System.out.println(" from getValue(): " + i.getValue());
+        }
+        catch (NumberFormatException e) {
+            {if (true) throw new DODSException(DODSException.MALFORMED_EXPR,
+            "Could not parse `" + t.image + "' as an integer: " + e.getMessage());}
+        }
+        {if (true) return i;}
+      break;
+    case FLOAT:
+      t = jj_consume_token(FLOAT);
+        System.out.println("Setting constant value: "+t.image + " (<FLOAT>)");
+        DFloat64 f = factory.newDFloat64("constant");
+        try {
+            f.setValue(Double.valueOf(t.image).doubleValue());
+            ((ServerMethods)f).setRead(true);
+            ((ServerMethods)f).setProject(true);
+
+            //System.out.print("Set value of BaseType to: ");
+            //f.printVal(System.out,"");
+            //System.out.println("");
+        }
+        catch (NumberFormatException e) {
+            {if (true) throw new DODSException(DODSException.MALFORMED_EXPR,
+            "Could not parse `" + t.image + "' as an integer: " + e.getMessage());}
+        }
+        {if (true) return f;}
+      break;
+    case STR:
+      t = jj_consume_token(STR);
+        System.out.println("Setting constant value: "+t.image + " (<STR>)");
+        DString s = factory.newDString("constant");
+        s.setValue(removeQuotes(t.image));
+        ((ServerMethods)s).setRead(true);
+        ((ServerMethods)s).setProject(true);
+
+        //System.out.print("Set value of BaseType to: ");
+        //s.printVal(System.out,"");
+        //System.out.println("");
+
+        {if (true) return s;}
+      break;
+    default:
+      jj_la1[20] = jj_gen;
+      jj_consume_token(-1);
+      throw new ParseException();
+    }
+    throw new Error("Missing return statement in function");
+  }
+
+  final private boolean jj_2_1(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_1();
+    jj_save(0, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_2(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_2();
+    jj_save(1, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_3(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_3();
+    jj_save(2, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_4(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_4();
+    jj_save(3, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_5(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_5();
+    jj_save(4, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_6(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_6();
+    jj_save(5, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_7(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_7();
+    jj_save(6, xla);
+    return retval;
+  }
+
+  final private boolean jj_2_8(int xla) {
+    jj_la = xla; jj_lastpos = jj_scanpos = token;
+    boolean retval = !jj_3_8();
+    jj_save(7, xla);
+    return retval;
+  }
+
+  final private boolean jj_3R_13() {
+    if (jj_3R_15()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_12() {
+    if (jj_scan_token(LPAREN)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_6() {
+    if (jj_scan_token(ASTERISK)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_14()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_17() {
+    if (jj_scan_token(LBRACKET)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_11() {
+    if (jj_scan_token(ID)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_12()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_1() {
+    if (jj_3R_11()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_8() {
+    if (jj_scan_token(ID)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_12()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_15() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3_4()) {
+    jj_scanpos = xsp;
+    if (jj_3_5()) {
+    jj_scanpos = xsp;
+    if (jj_3R_17()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_14() {
+    Token xsp;
+    xsp = jj_scanpos;
+    if (jj_3_3()) {
+    jj_scanpos = xsp;
+    if (jj_3R_16()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    } else if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_3() {
+    if (jj_scan_token(ID)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    Token xsp;
+    if (jj_3R_13()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    while (true) {
+      xsp = jj_scanpos;
+      if (jj_3R_13()) { jj_scanpos = xsp; break; }
+      if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    }
+    return false;
+  }
+
+  final private boolean jj_3_4() {
+    if (jj_scan_token(LBRACKET)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(INT)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(COLON)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(INT)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(COLON)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_7() {
+    if (jj_scan_token(ASTERISK)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(STR)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3R_16() {
+    if (jj_scan_token(ID)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_5() {
+    if (jj_scan_token(LBRACKET)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(INT)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_scan_token(COLON)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  final private boolean jj_3_2() {
+    if (jj_scan_token(ID)) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    if (jj_3R_12()) return true;
+    if (jj_la == 0 && jj_scanpos == jj_lastpos) return false;
+    return false;
+  }
+
+  public ExprParserTokenManager token_source;
+  SimpleCharStream jj_input_stream;
+  public Token token, jj_nt;
+  private int jj_ntk;
+  private Token jj_scanpos, jj_lastpos;
+  private int jj_la;
+  public boolean lookingAhead = false;
+  private boolean jj_semLA;
+  private int jj_gen;
+  final private int[] jj_la1 = new int[21];
+  final private int[] jj_la1_0 = {0x20000,0x20000,0x800000,0x10000,0x13808000,0x13908000,0x13808000,0xfe0,0x10000,0x800000,0x400000,0x1000,0x800000,0x13808000,0x10000,0x1000,0x400000,0x400000,0x13800000,0x400000,0x13000000,};
+  final private JJCalls[] jj_2_rtns = new JJCalls[8];
+  private boolean jj_rescan = false;
+  private int jj_gc = 0;
+
+  public ExprParser(java.io.InputStream stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ExprParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 21; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.InputStream stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 21; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public ExprParser(java.io.Reader stream) {
+    jj_input_stream = new SimpleCharStream(stream, 1, 1);
+    token_source = new ExprParserTokenManager(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 21; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(java.io.Reader stream) {
+    jj_input_stream.ReInit(stream, 1, 1);
+    token_source.ReInit(jj_input_stream);
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 21; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public ExprParser(ExprParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 21; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  public void ReInit(ExprParserTokenManager tm) {
+    token_source = tm;
+    token = new Token();
+    jj_ntk = -1;
+    jj_gen = 0;
+    for (int i = 0; i < 21; i++) jj_la1[i] = -1;
+    for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+  }
+
+  final private Token jj_consume_token(int kind) throws ParseException {
+    Token oldToken;
+    if ((oldToken = token).next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    if (token.kind == kind) {
+      jj_gen++;
+      if (++jj_gc > 100) {
+        jj_gc = 0;
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+          JJCalls c = jj_2_rtns[i];
+          while (c != null) {
+            if (c.gen < jj_gen) c.first = null;
+            c = c.next;
+          }
+        }
+      }
+      return token;
+    }
+    token = oldToken;
+    jj_kind = kind;
+    throw generateParseException();
+  }
+
+  final private boolean jj_scan_token(int kind) {
+    if (jj_scanpos == jj_lastpos) {
+      jj_la--;
+      if (jj_scanpos.next == null) {
+        jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+      } else {
+        jj_lastpos = jj_scanpos = jj_scanpos.next;
+      }
+    } else {
+      jj_scanpos = jj_scanpos.next;
+    }
+    if (jj_rescan) {
+      int i = 0; Token tok = token;
+      while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+      if (tok != null) jj_add_error_token(kind, i);
+    }
+    return (jj_scanpos.kind != kind);
+  }
+
+  final public Token getNextToken() {
+    if (token.next != null) token = token.next;
+    else token = token.next = token_source.getNextToken();
+    jj_ntk = -1;
+    jj_gen++;
+    return token;
+  }
+
+  final public Token getToken(int index) {
+    Token t = lookingAhead ? jj_scanpos : token;
+    for (int i = 0; i < index; i++) {
+      if (t.next != null) t = t.next;
+      else t = t.next = token_source.getNextToken();
+    }
+    return t;
+  }
+
+  final private int jj_ntk() {
+    if ((jj_nt=token.next) == null)
+      return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+    else
+      return (jj_ntk = jj_nt.kind);
+  }
+
+  private java.util.Vector jj_expentries = new java.util.Vector();
+  private int[] jj_expentry;
+  private int jj_kind = -1;
+  private int[] jj_lasttokens = new int[100];
+  private int jj_endpos;
+
+  private void jj_add_error_token(int kind, int pos) {
+    if (pos >= 100) return;
+    if (pos == jj_endpos + 1) {
+      jj_lasttokens[jj_endpos++] = kind;
+    } else if (jj_endpos != 0) {
+      jj_expentry = new int[jj_endpos];
+      for (int i = 0; i < jj_endpos; i++) {
+        jj_expentry[i] = jj_lasttokens[i];
+      }
+      boolean exists = false;
+      for (java.util.Enumeration enumx = jj_expentries.elements(); enumx.hasMoreElements();) {
+        int[] oldentry = (int[])(enumx.nextElement());
+        if (oldentry.length == jj_expentry.length) {
+          exists = true;
+          for (int i = 0; i < jj_expentry.length; i++) {
+            if (oldentry[i] != jj_expentry[i]) {
+              exists = false;
+              break;
+            }
+          }
+          if (exists) break;
+        }
+      }
+      if (!exists) jj_expentries.addElement(jj_expentry);
+      if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+    }
+  }
+
+  final public ParseException generateParseException() {
+    jj_expentries.removeAllElements();
+    boolean[] la1tokens = new boolean[32];
+    for (int i = 0; i < 32; i++) {
+      la1tokens[i] = false;
+    }
+    if (jj_kind >= 0) {
+      la1tokens[jj_kind] = true;
+      jj_kind = -1;
+    }
+    for (int i = 0; i < 21; i++) {
+      if (jj_la1[i] == jj_gen) {
+        for (int j = 0; j < 32; j++) {
+          if ((jj_la1_0[i] & (1<<j)) != 0) {
+            la1tokens[j] = true;
+          }
+        }
+      }
+    }
+    for (int i = 0; i < 32; i++) {
+      if (la1tokens[i]) {
+        jj_expentry = new int[1];
+        jj_expentry[0] = i;
+        jj_expentries.addElement(jj_expentry);
+      }
+    }
+    jj_endpos = 0;
+    jj_rescan_token();
+    jj_add_error_token(0, 0);
+    int[][] exptokseq = new int[jj_expentries.size()][];
+    for (int i = 0; i < jj_expentries.size(); i++) {
+      exptokseq[i] = (int[])jj_expentries.elementAt(i);
+    }
+    return new ParseException(token, exptokseq, tokenImage);
+  }
+
+  final public void enable_tracing() {
+  }
+
+  final public void disable_tracing() {
+  }
+
+  final private void jj_rescan_token() {
+    jj_rescan = true;
+    for (int i = 0; i < 8; i++) {
+      JJCalls p = jj_2_rtns[i];
+      do {
+        if (p.gen > jj_gen) {
+          jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+          switch (i) {
+            case 0: jj_3_1(); break;
+            case 1: jj_3_2(); break;
+            case 2: jj_3_3(); break;
+            case 3: jj_3_4(); break;
+            case 4: jj_3_5(); break;
+            case 5: jj_3_6(); break;
+            case 6: jj_3_7(); break;
+            case 7: jj_3_8(); break;
+          }
+        }
+        p = p.next;
+      } while (p != null);
+    }
+    jj_rescan = false;
+  }
+
+  final private void jj_save(int index, int xla) {
+    JJCalls p = jj_2_rtns[index];
+    while (p.gen > jj_gen) {
+      if (p.next == null) { p = p.next = new JJCalls(); break; }
+      p = p.next;
+    }
+    p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+  }
+
+  static final class JJCalls {
+    int gen;
+    Token first;
+    int arg;
+    JJCalls next;
+  }
+
+}
diff --git a/dods/dap/parser/ExprParser.jj b/dods/dap/parser/ExprParser.jj
new file mode 100644
index 0000000..8da367c
--- /dev/null
+++ b/dods/dap/parser/ExprParser.jj
@@ -0,0 +1,646 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, Univ. of Rhode Island
+// ALL RIGHTS RESERVED.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: James Gallagher <jgallagher at gso.uri.edu>
+//
+/////////////////////////////////////////////////////////////////////////////
+
+options {
+    STATIC = false;		// Methods are _not_ static
+    DEBUG_PARSER = false;
+    DEBUG_LOOKAHEAD = false;
+    DEBUG_TOKEN_MANAGER = false;
+}
+
+PARSER_BEGIN(ExprParser)
+
+package dods.dap.parser;
+
+import java.util.Vector;
+import java.util.Stack;
+
+import dods.dap.*;
+import dods.dap.Server.*;
+
+/** The constraint expression parser class. <p>
+    
+    Because it only makes sense to evaluate CEs when serving data, the
+    BaseTyeFactory <em>must</em> create instances of the SDtype classes, not
+    the Dtype classes. The is because we use the setRead method of the class
+    ServerMethods when creating constants (to ensure that the evaluator
+    doesn't try to read tem from the dataset!).
+    
+    @author jhrg */
+
+public class ExprParser {
+    private ServerDDS sdds;
+    private CEEvaluator ceEval;
+    private BaseTypeFactory factory;
+    private ClauseFactory clauseFactory;
+
+    /** Run the named projection function. Projection functions are run for
+	their side effect; the return value is discarded. 
+	@param name The name of the projection function, look this up in the
+	ServerDDS. 
+	@param btv A vector of BaseType variables that are the arguments of
+	the projection function. */
+    private void runProjectionFunction(String name, Vector btv) {
+    }
+    
+    /** Remove double quotes from around a string. If there's not both start
+	and ending quotes, does nothing.
+	@param s The source string.
+	@return The string without double quotes. */
+    private String removeQuotes(String s) {
+	if (s.startsWith("\"") && s.endsWith("\""))
+	    return s.substring(1, s.length() - 1);
+	else
+	    return s;
+    }
+
+    /** Given a stack of BaseType variables, mark these as part of the
+     * current projection. This function assumes that if the TOS contains a
+     * Ctor type variable, all of its members are to be projected. Also
+     * assume  all variables under the TOS are Ctor variables and
+     * only the ctor itself is to be projected; the member within the Ctor
+     * that is part of the projection will be on the stack, too. */
+    private void markStackedVariables(Stack s) {
+	// Reverse the stack.
+	Stack bts = new Stack();
+	// System.err.println("Variables to be marked:");
+	while (!s.empty()) {
+	    // System.err.println(((BaseType)s.peek()).getName());
+	    bts.push(s.pop());
+	}
+	
+	// For each but the last stack element, set the projection.
+	// setProject(true, false) for a ctor type sets the projection for
+	// the ctor itself but *does not* set the projection for all its
+	// children. Thus, if a user wants the variable S.X, and S contains Y
+	// and Z too, S's projection will be set (so serialize will descend
+	// into S) but X, Y and Z's projection remain clear. In this example,
+	// X's projection is set by the code that follows the while loop.
+	// 1/28/2000 jhrg
+	while (bts.size() > 1) {
+	    ServerMethods ct = (ServerMethods)bts.pop();
+	    ct.setProject(true, false);
+	}
+	
+	// For the last element, project the entire variable.
+	ServerMethods bt = (ServerMethods)bts.pop();
+	bt.setProject(true, true);
+    }
+}
+
+PARSER_END(ExprParser)
+
+SKIP : {
+    " "
+    | "\t"
+    | "\n"
+    | "\r"
+}
+
+TOKEN : {
+    <EQUAL: "="> |
+    <NOT_EQUAL:	"!="> |
+    <GREATER: ">"> |
+    <GREATER_EQL: ">="> |
+    <LESS: "<"> |
+    <LESS_EQL: "<="> |
+    <REGEXP: "~="> |
+
+    <LBRACKET: "["> |
+    <RBRACKET: "]"> |
+    <COLON: ":"> |
+    <ASTERISK: "*"> |
+    <COMMA: ","> |
+    <AMPERSAND: "&"> |
+    <LPAREN: "("> |
+    <RPAREN: ")"> |
+    <LBRACE: "{"> |
+    <RBRACE: "}"> |
+    <SEPARATOR: "."> |
+
+    <ID: ["a"-"z","A"-"Z","_","%"](["a"-"z","A"-"Z","0"-"9","_","/","%"])*> |
+    <INT: (["-","+"])?(["0"-"9"])+> |
+
+    <FLOAT: (["-","+"])? <MANTISSA> (<EXPONENT>)?> |
+    <#MANTISSA: (["0"-"9"])+ "." (["0"-"9"])* | "." (["0"-"9"])+> |
+    <#EXPONENT: ["E","e"] (["-","+"])? (["0"-"9"])+> |
+
+    <STR: <UNQUOTED_STR> | <QUOTED_STR> > |
+    // I Removed the `.' from UNQUOTED_STR because variables that name parts
+    // of structures such as `types.b' would match <STR>. Instead I want the
+    // `types' and `b' to match <ID> and the `.' to match <SEPARATOR>. See
+    // the rule `component'. 1/5/2000 jhrg
+    <#UNQUOTED_STR: (["-","+","a"-"z","A"-"Z","0"-"9","_","/","%"])+> |
+    <#QUOTED_STR: "\"" (~["\""] | "\\\"")* "\""> |
+    <UNTERM_QUOTE: "\"" (~["\""] | "\\\"")* >
+}
+
+/** This is the entry point for the Constraint expression parser.<p>
+
+    Note that this method catches any ParserException and recasts it to a
+    DODSException after replacing all double quotes with single quotes.
+
+    @param ceEval A CEEvaluator instance where any select clauses will be
+    dumped and from which the DDS, which supplies the evaluation environment,
+    will be read.
+    @param factory A BaseTypeFactory used to generate instances of variables
+    as needed (e.g., to hold values of constants).
+    @param clauseFactory A ClauseFactory used to generate instances of each
+    type of clause. */
+    
+void constraint_expression(CEEvaluator ceEval, BaseTypeFactory factory,
+                           ClauseFactory clauseFactory)
+    throws NoSuchVariableException, NoSuchFunctionException,
+	   InvalidOperatorException, DODSException, InvalidParameterException,
+	   SBHException, WrongTypeException:
+{
+    this.ceEval = ceEval;
+    this.sdds = ceEval.getDDS();
+    this.factory = factory;
+    this.clauseFactory = clauseFactory;
+}
+{ 
+    try {
+	    projection() ( selection() )* // Projection given
+	    | 
+	    {
+		ceEval.markAll(true); // No projection; mark all
+	    } 
+	    ( selection() )*
+    }
+    catch (ParseException pe) {
+	// Extract the message and rethrow after changing all the double
+	// quotes to single quotes so that the code that (might) send the
+	// text of this exception back to a client over the network won't
+	// barf. 
+	String msg = pe.getMessage();
+	if (msg != null)
+	    msg = msg.replace('\"', '\'');
+	throw new DODSException(DODSException.MALFORMED_EXPR, msg);
+    }
+}
+
+void selection() 
+    throws NoSuchVariableException, NoSuchFunctionException,
+	   InvalidOperatorException, ParseException, SBHException, 
+	   DODSException:
+{
+    Clause c;
+}
+{
+    <AMPERSAND> c = clause()
+    {
+	ceEval.appendClause(c);
+    }
+}
+
+Clause clause() 
+    throws NoSuchVariableException, NoSuchFunctionException,
+	   InvalidOperatorException, ParseException, DODSException:
+{
+    Clause c;
+    SubClause lop, rop;
+    Vector ropv;
+    int op;
+}
+{
+    LOOKAHEAD(2) c = bool_function() 
+    {
+	return c;
+    }
+    
+    | lop = value() op = rel_op() {ropv = new Vector();}
+	  ( 
+	    rop = value() {ropv.addElement(rop);}
+	    | <LBRACE> (rop = value() {ropv.addElement(rop);} (<COMMA>)? )+ <RBRACE> 
+	  )
+    {
+	return clauseFactory.newRelOpClause(op, lop, ropv);
+    }
+}
+
+Clause bool_function() 
+    throws NoSuchVariableException, NoSuchFunctionException,
+	   InvalidOperatorException, ParseException, DODSException:
+{
+    Token name;			// Name of the function
+    Vector btv;			// Vector of BaseTypes; the arguments
+}
+{
+    name = <ID> btv = arg_list() 
+    {
+	return clauseFactory.newBoolFunctionClause(name.image, btv);
+    }
+}
+
+// Note that I'm using the constants from the ExprParserConstants interface
+// rather than (re)define a new set of constants. 7/20/99 jhrg
+int rel_op() :
+{
+    Token op;
+}
+{
+    op = <EQUAL>
+    { return op.kind; }
+    
+    | op = <NOT_EQUAL>
+    { return op.kind; }
+    
+    | op = <GREATER>
+    { return op.kind; }
+    
+    | op = <GREATER_EQL>
+    { return op.kind; }
+    
+    | op = <LESS>
+    { return op.kind; }
+    
+    | op = <LESS_EQL>
+    { return op.kind; }
+    
+    | op = <REGEXP> 
+    { return op.kind; }
+    
+}
+
+void projection() 
+    throws NoSuchVariableException, NoSuchFunctionException,
+	   InvalidOperatorException, ParseException, InvalidParameterException,
+	   SBHException, WrongTypeException, DODSException:
+{}
+{
+    proj_clause() ( <COMMA> proj_clause() )*
+}
+
+// Note that we have to keep a count of the array index number for the calls
+// to array_index(). 7/20/99 jhrg
+void proj_clause() 
+    throws NoSuchVariableException, NoSuchFunctionException,
+	   InvalidOperatorException, ParseException, InvalidParameterException,
+	   SBHException, WrongTypeException, DODSException:
+{
+    Token t;
+    Vector btv;
+}
+{
+    LOOKAHEAD(2) t = <ID> btv = arg_list()
+    {
+	ceEval.appendClause(clauseFactory.newBTFunctionClause(t.image, btv));
+	// runProjectionFunction(t.image, btv);
+    }
+    | proj_variable()
+}
+
+void proj_variable() 
+    throws NoSuchVariableException, NoSuchFunctionException,
+	   InvalidOperatorException, ParseException, InvalidParameterException,
+	   SBHException, WrongTypeException, DODSException:
+{
+    Token t;
+    BaseType bt;
+    Stack comp = new Stack();	// Stack of components
+}
+{
+    comp = component(comp) ( <SEPARATOR> comp = component(comp) )*
+    {
+	markStackedVariables(comp);
+    }
+}
+
+Stack component(Stack components)
+    throws ParseException, DODSException:
+{
+    Token t;
+    int count = 0;
+    ServerArrayMethods abt;
+}
+{
+    LOOKAHEAD(2) t = <ID> 
+    {
+	components = sdds.search(t.image, components);
+	try {
+	    abt = (ServerArrayMethods)components.peek();
+	}
+	catch (ClassCastException cce) {
+	    String msg = "Attempt to treat the variable `" + t.image
+	    + "' as if it is an array.";
+	    throw new DODSException(DODSException.MALFORMED_EXPR, msg);
+	}
+    }
+    (array_index(count++, abt))+
+    {
+	return components;
+    }
+    | t = <ID> 
+    {
+	components = sdds.search(t.image, components);
+	return components;
+    }
+}
+
+Vector arg_list() 
+    throws NoSuchVariableException, NoSuchFunctionException, 
+	   InvalidOperatorException, ParseException, DODSException:
+{
+    Vector cv = new Vector();
+    Clause c;
+}
+{
+    <LPAREN> (c = value() {cv.addElement(c);} (<COMMA>)? )* 
+    <RPAREN> {return cv;}
+}
+
+// Note that we must explicitly catch the NumberFormatExceptions since it is
+// a child of RuntimeException. Might as well do it here and munge the
+// message into a DODSException object. 1/6/2000 jhrg 
+
+void array_index(int count, ServerArrayMethods bt) 
+    throws ParseException, DODSException, InvalidParameterException, 
+	   SBHException:
+{
+    Token t1, t2, t3;
+}
+{
+    LOOKAHEAD(5) <LBRACKET> t1=<INT> <COLON> t2=<INT> <COLON> t3=<INT> <RBRACKET>
+    {
+	try {
+	    bt.setProjection(count, Integer.parseInt(t1.image), 
+			     Integer.parseInt(t2.image), 
+			     Integer.parseInt(t3.image) );
+	}
+	catch (NumberFormatException e) {
+	    throw new DODSException(DODSException.MALFORMED_EXPR,
+"Could not parse one of " + t1.image + ", " + t2.image + ", " + t3.image + 
+" as an integer: " + e.getMessage());
+	}
+    }
+    | LOOKAHEAD(3) <LBRACKET> t1=<INT> <COLON> t2=<INT> <RBRACKET>
+      {
+	  try {
+	      bt.setProjection(count, Integer.parseInt(t1.image), 1, 
+			       Integer.parseInt(t2.image) );
+	  }
+	  catch (NumberFormatException e) {
+	      throw new DODSException(DODSException.MALFORMED_EXPR,
+"Could not parse one of " + t1.image + ", " + t2.image + 
+" as an integer: " + e.getMessage());
+	  }
+      }
+    | <LBRACKET> t1=<INT> <RBRACKET>
+       {
+	   try {
+	       bt.setProjection(count, Integer.parseInt(t1.image), 1, 
+				Integer.parseInt(t1.image) );
+	   }
+	   catch (NumberFormatException e) {
+	       throw new DODSException(DODSException.MALFORMED_EXPR,
+"Could not parse " + t1.image + " as an integer: " + e.getMessage());
+	   }
+       }
+}
+
+// Values only appear in the selection part of a CE. 
+// Use clauseFactory to create the appropriate type of clause - joew
+SubClause value() 
+    throws NoSuchVariableException, NoSuchFunctionException, 
+	   InvalidOperatorException, DODSException, ParseException:
+{
+    Token t;
+    Vector btv;
+    BaseType bt;
+    String name;
+    Stack comp = new Stack();
+}
+{
+    LOOKAHEAD(2) <ASTERISK> comp = component(comp) 
+	( <SEPARATOR> comp = component(comp) )*
+    {
+	bt = (BaseType)comp.pop();
+	try {
+	    return clauseFactory.newDereferenceClause(((DURL)bt).getValue());
+	} catch (ClassCastException cce) {
+	  throw new DODSException("Attempt to reference non-URL component " +
+	 			  bt.getName() + " as a URL.");
+	}
+    }
+
+    | LOOKAHEAD(2) <ASTERISK> t = <STR>
+    {
+	return clauseFactory.newDereferenceClause(t.image);
+    }
+
+    | LOOKAHEAD(2) t = <ID> btv = arg_list()
+    {
+	return clauseFactory.newBTFunctionClause(t.image, btv);
+    }
+
+    | comp = component(comp) 
+	( <SEPARATOR> comp = component(comp) )*
+    {
+	bt = (BaseType)comp.pop();
+	return clauseFactory.newValueClause(bt, false);
+    }
+
+    | bt = constant()
+    {
+	return clauseFactory.newValueClause(bt, true);
+    }
+} 
+ 
+String field()
+    throws ParseException:
+{
+    String name;
+    Token t;
+}
+{
+    t = <ID> 
+    {
+	name = t.image;
+    } 
+    (<SEPARATOR> t = <ID> 
+	{
+	    name += "." + t.image;
+	}
+    )+
+    {
+	return name;
+    }
+}
+
+// See my comment above about the NumberFormatExceptions. 8/20/99 jhrg
+BaseType constant() throws DODSException, ParseException:
+{
+    Token t;
+}
+{
+    t = <INT> 
+    { 
+    
+    	//System.out.println("Setting constant value: "+t.image + " (<INT>)");
+	DInt32 i = factory.newDInt32("constant");
+	try {
+	    i.setValue(Integer.parseInt(t.image));
+	    ((ServerMethods)i).setRead(true);
+	    ((ServerMethods)i).setProject(true);
+	    
+	   // System.out.print("Set value of BaseType. from printVal(): ");
+	   // i.printVal(System.out,"");
+	   // System.out.println(" from getValue(): " + i.getValue());
+	}
+	catch (NumberFormatException e) {
+	    throw new DODSException(DODSException.MALFORMED_EXPR,
+            "Could not parse `" + t.image + "' as an integer: " + e.getMessage());
+	}
+	return i;
+    }
+    | t = <FLOAT>
+    { 
+    	System.out.println("Setting constant value: "+t.image + " (<FLOAT>)");
+	DFloat64 f = factory.newDFloat64("constant");
+	try {
+	    f.setValue(Double.valueOf(t.image).doubleValue());
+	    ((ServerMethods)f).setRead(true);
+	    ((ServerMethods)f).setProject(true);
+	    
+	    //System.out.print("Set value of BaseType to: ");
+	    //f.printVal(System.out,"");
+	    //System.out.println("");
+	}
+	catch (NumberFormatException e) {
+	    throw new DODSException(DODSException.MALFORMED_EXPR,
+            "Could not parse `" + t.image + "' as an integer: " + e.getMessage());
+	}
+	return f;
+    }
+    | t = <STR>
+    { 
+    	System.out.println("Setting constant value: "+t.image + " (<STR>)");
+	DString s = factory.newDString("constant");
+	s.setValue(removeQuotes(t.image));
+	((ServerMethods)s).setRead(true);
+        ((ServerMethods)s).setProject(true);
+	
+	//System.out.print("Set value of BaseType to: ");
+	//s.printVal(System.out,"");
+	//System.out.println("");
+	
+	return s;
+    }
+}
+
+// $Log: not supported by cvs2svn $
+// Revision 1.31  2002/01/19 03:14:56  ndp
+// *** empty log message ***
+//
+// Revision 1.30  2002/01/03 22:58:47  ndp
+// Merged newClauseImplementation branch into trunk.
+//
+// Revision 1.29.2.2  2001/12/18 02:31:25  joew
+// fixed a typo on line 427
+//
+// Revision 1.29.2.1  2001/12/18 02:03:41  joew
+// new implementation of clause parsing
+//
+// Revision 1.29  2001/11/21 00:15:24  ndp
+// *** empty log message ***
+//
+// Revision 1.28  2001/11/21 00:09:03  jimg
+// Added support for functions in the projection part of a CE that return
+// BaseTypes.
+//
+// Revision 1.27  2001/11/15 01:13:20  ndp
+// Checking Joe Weilgoz's ServerSideFunction mods
+//
+// Revision 1.23  2001/01/24 17:59:18  jimg
+// Merged the Beta_1_0 branch
+//
+// Revision 1.22.2.1  2000/10/12 22:49:02  jimg
+// Modified the creation of constants so that their isRead property is true.
+// This will keep the evaluator from trying to read the constant's value from a
+// data source.
+//
+// Revision 1.22  2000/03/08 20:05:56  jimg
+// Added removeQuotes method that strips double quotes from around a string.
+// The quotes were being retained and that made string compares fail.
+//
+// Revision 1.21  2000/02/11 18:19:30  jimg
+// Cleaned
+//
+// Revision 1.20  2000/02/11 01:09:13  jimg
+// Fixed a bug in the DDS.getVariable() method where our shorthand notation
+// for specifying variables names in a CE was not supported. In fixing this,
+// I moved the DDSSearch subclass from ServerDDS to DDS and reimplemented
+// getVariable using the search() method. I move search to DDS, also. Finally,
+// since this breaks using getVariable with the dot notation, I fixed the use
+// of getVariable in the value() rule of the ExprParser.jj code.
+//
+// Revision 1.19  2000/01/28 19:29:31  jimg
+// Fixed a bug where fields of nested ctors were not marked correctly. The
+// parser used [] in rules as if it means the Kleene closure; it actually means
+// zero or one.
+//
+// Revision 1.18  2000/01/28 18:42:37  jimg
+// Removed old trace_enabled code and added some comments to make things
+// clearer.
+//
+// Revision 1.17  2000/01/08 01:10:34  jimg
+// Fixed bug where arrays of ctors did not parse correctly. In the process
+// rewrote the mark() and search() code completely. The parser now correctly
+// sets the projections of fields that are members of arrays of structures.
+//
+// Revision 1.16  1999/12/04 01:09:53  ndp
+// Fixed various outstanding bugs on the TODO list
+//
+// Revision 1.15  1999/12/03 21:30:47  ndp
+// *** empty log message ***
+//
+// Revision 1.14  1999/11/23 00:34:44  jimg
+// Added WrongTypeException
+//
+// Revision 1.13  1999/11/23 00:27:17  jimg
+// Use the new CEEvaluator.mark method
+//
+// Revision 1.12  1999/11/16 23:05:42  jimg
+// Fixed null projections.
+//
+// Revision 1.11  1999/10/29 20:49:01  ndp
+// Fixed bug in ExprParser.rel_op(). (Change made to ExprParser.jj)
+//
+// Revision 1.10  1999/10/29 19:37:15  ndp
+// *** empty log message ***
+//
+// Revision 1.9  1999/10/28 19:09:17  jimg
+// Fixed(?) proj_clause ServerMethods casts.
+//
+// Revision 1.8  1999/10/27 00:05:02  ndp
+// *** empty log message ***
+//
+// Revision 1.7  1999/10/26 21:47:12  ndp
+// Added setProjection() method to DArrayDimension, SDArray, and SDGrid. Tagged where it might be used in ExprParser.jj
+//
+// Revision 1.6  1999/10/25 23:53:47  ndp
+// *** empty log message ***
+//
+// Revision 1.5  1999/09/13 21:51:04  jimg
+// Replaced SeverFactory with the BaseTypeFactory interface.
+//
+// Revision 1.4  1999/09/08 19:18:26  jimg
+// Added import statements for moved classes, add some excpetion classes
+//
+// Revision 1.3  1999/08/21 00:29:47  jimg
+// Fairly massive changes were needed to get this to compile, both here and in
+// the classes it references.
+// I added explicit handling of the NumberFormatExections thrown by the Number
+// class children Integer and Double. These are caught and turned into
+// ParseExceptions with meaningful (I hope) strings.
+//
+
diff --git a/dods/dap/parser/ExprParserConstants.java b/dods/dap/parser/ExprParserConstants.java
new file mode 100644
index 0000000..c6ca19c
--- /dev/null
+++ b/dods/dap/parser/ExprParserConstants.java
@@ -0,0 +1,72 @@
+/* Generated By:JavaCC: Do not edit this line. ExprParserConstants.java */
+package dods.dap.parser;
+
+public interface ExprParserConstants {
+
+  int EOF = 0;
+  int EQUAL = 5;
+  int NOT_EQUAL = 6;
+  int GREATER = 7;
+  int GREATER_EQL = 8;
+  int LESS = 9;
+  int LESS_EQL = 10;
+  int REGEXP = 11;
+  int LBRACKET = 12;
+  int RBRACKET = 13;
+  int COLON = 14;
+  int ASTERISK = 15;
+  int COMMA = 16;
+  int AMPERSAND = 17;
+  int LPAREN = 18;
+  int RPAREN = 19;
+  int LBRACE = 20;
+  int RBRACE = 21;
+  int SEPARATOR = 22;
+  int ID = 23;
+  int INT = 24;
+  int FLOAT = 25;
+  int MANTISSA = 26;
+  int EXPONENT = 27;
+  int STR = 28;
+  int UNQUOTED_STR = 29;
+  int QUOTED_STR = 30;
+  int UNTERM_QUOTE = 31;
+
+  int DEFAULT = 0;
+
+  String[] tokenImage = {
+    "<EOF>",
+    "\" \"",
+    "\"\\t\"",
+    "\"\\n\"",
+    "\"\\r\"",
+    "\"=\"",
+    "\"!=\"",
+    "\">\"",
+    "\">=\"",
+    "\"<\"",
+    "\"<=\"",
+    "\"~=\"",
+    "\"[\"",
+    "\"]\"",
+    "\":\"",
+    "\"*\"",
+    "\",\"",
+    "\"&\"",
+    "\"(\"",
+    "\")\"",
+    "\"{\"",
+    "\"}\"",
+    "\".\"",
+    "<ID>",
+    "<INT>",
+    "<FLOAT>",
+    "<MANTISSA>",
+    "<EXPONENT>",
+    "<STR>",
+    "<UNQUOTED_STR>",
+    "<QUOTED_STR>",
+    "<UNTERM_QUOTE>",
+  };
+
+}
diff --git a/dods/dap/parser/ExprParserTokenManager.java b/dods/dap/parser/ExprParserTokenManager.java
new file mode 100644
index 0000000..b7b7c55
--- /dev/null
+++ b/dods/dap/parser/ExprParserTokenManager.java
@@ -0,0 +1,539 @@
+/* Generated By:JavaCC: Do not edit this line. ExprParserTokenManager.java */
+package dods.dap.parser;
+import java.util.Vector;
+import java.util.Stack;
+import dods.dap.*;
+import dods.dap.Server.*;
+
+public class ExprParserTokenManager implements ExprParserConstants
+{
+  public  java.io.PrintStream debugStream = System.out;
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      case 0:
+         if ((active0 & 0x400000L) != 0L)
+            return 3;
+         return -1;
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private final int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private final int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private final int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 33:
+         return jjMoveStringLiteralDfa1_0(0x40L);
+      case 38:
+         return jjStopAtPos(0, 17);
+      case 40:
+         return jjStopAtPos(0, 18);
+      case 41:
+         return jjStopAtPos(0, 19);
+      case 42:
+         return jjStopAtPos(0, 15);
+      case 44:
+         return jjStopAtPos(0, 16);
+      case 46:
+         return jjStartNfaWithStates_0(0, 22, 3);
+      case 58:
+         return jjStopAtPos(0, 14);
+      case 60:
+         jjmatchedKind = 9;
+         return jjMoveStringLiteralDfa1_0(0x400L);
+      case 61:
+         return jjStopAtPos(0, 5);
+      case 62:
+         jjmatchedKind = 7;
+         return jjMoveStringLiteralDfa1_0(0x100L);
+      case 91:
+         return jjStopAtPos(0, 12);
+      case 93:
+         return jjStopAtPos(0, 13);
+      case 123:
+         return jjStopAtPos(0, 20);
+      case 125:
+         return jjStopAtPos(0, 21);
+      case 126:
+         return jjMoveStringLiteralDfa1_0(0x800L);
+      default :
+         return jjMoveNfa_0(0, 0);
+   }
+}
+private final int jjMoveStringLiteralDfa1_0(long active0)
+{
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) {
+      jjStopStringLiteralDfa_0(0, active0);
+      return 1;
+   }
+   switch(curChar)
+   {
+      case 61:
+         if ((active0 & 0x40L) != 0L)
+            return jjStopAtPos(1, 6);
+         else if ((active0 & 0x100L) != 0L)
+            return jjStopAtPos(1, 8);
+         else if ((active0 & 0x400L) != 0L)
+            return jjStopAtPos(1, 10);
+         else if ((active0 & 0x800L) != 0L)
+            return jjStopAtPos(1, 11);
+         break;
+      default :
+         break;
+   }
+   return jjStartNfa_0(0, active0);
+}
+private final void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private final void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private final void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+private final void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+private final void jjCheckNAddStates(int start)
+{
+   jjCheckNAdd(jjnextStates[start]);
+   jjCheckNAdd(jjnextStates[start + 1]);
+}
+static final long[] jjbitVec0 = {
+   0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private final int jjMoveNfa_0(int startState, int curPos)
+{
+   int[] nextStates;
+   int startsAt = 0;
+   jjnewStateCnt = 22;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int j, kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x3ffa82000000000L & l) != 0L)
+                  {
+                     if (kind > 28)
+                        kind = 28;
+                     jjCheckNAdd(7);
+                  }
+                  else if (curChar == 34)
+                  {
+                     if (kind > 31)
+                        kind = 31;
+                     jjCheckNAddStates(0, 4);
+                  }
+                  else if (curChar == 46)
+                     jjCheckNAdd(3);
+                  if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 24)
+                        kind = 24;
+                     jjCheckNAddStates(5, 7);
+                  }
+                  else if ((0x280000000000L & l) != 0L)
+                     jjCheckNAddStates(8, 10);
+                  else if (curChar == 37)
+                  {
+                     if (kind > 23)
+                        kind = 23;
+                     jjCheckNAdd(1);
+                  }
+                  break;
+               case 1:
+                  if ((0x3ff802000000000L & l) == 0L)
+                     break;
+                  if (kind > 23)
+                     kind = 23;
+                  jjCheckNAdd(1);
+                  break;
+               case 2:
+                  if (curChar == 46)
+                     jjCheckNAdd(3);
+                  break;
+               case 3:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 25)
+                     kind = 25;
+                  jjCheckNAddTwoStates(3, 4);
+                  break;
+               case 5:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(6);
+                  break;
+               case 6:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 25)
+                     kind = 25;
+                  jjCheckNAdd(6);
+                  break;
+               case 7:
+                  if ((0x3ffa82000000000L & l) == 0L)
+                     break;
+                  if (kind > 28)
+                     kind = 28;
+                  jjCheckNAdd(7);
+                  break;
+               case 8:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAddStates(8, 10);
+                  break;
+               case 9:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 24)
+                     kind = 24;
+                  jjCheckNAdd(9);
+                  break;
+               case 10:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(10, 11);
+                  break;
+               case 11:
+                  if (curChar != 46)
+                     break;
+                  if (kind > 25)
+                     kind = 25;
+                  jjCheckNAddTwoStates(12, 4);
+                  break;
+               case 12:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 25)
+                     kind = 25;
+                  jjCheckNAddTwoStates(12, 4);
+                  break;
+               case 13:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 24)
+                     kind = 24;
+                  jjCheckNAddStates(5, 7);
+                  break;
+               case 14:
+                  if (curChar != 34)
+                     break;
+                  if (kind > 31)
+                     kind = 31;
+                  jjCheckNAddStates(0, 4);
+                  break;
+               case 15:
+                  if ((0xfffffffbffffffffL & l) != 0L)
+                     jjCheckNAddStates(11, 13);
+                  break;
+               case 16:
+                  if (curChar == 34)
+                     jjCheckNAddStates(11, 13);
+                  break;
+               case 18:
+                  if (curChar == 34 && kind > 28)
+                     kind = 28;
+                  break;
+               case 19:
+                  if ((0xfffffffbffffffffL & l) == 0L)
+                     break;
+                  if (kind > 31)
+                     kind = 31;
+                  jjCheckNAddTwoStates(19, 21);
+                  break;
+               case 20:
+                  if (curChar != 34)
+                     break;
+                  if (kind > 31)
+                     kind = 31;
+                  jjCheckNAddTwoStates(19, 21);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 0:
+                  if ((0x7fffffe87fffffeL & l) != 0L)
+                  {
+                     if (kind > 28)
+                        kind = 28;
+                     jjCheckNAdd(7);
+                  }
+                  if ((0x7fffffe87fffffeL & l) != 0L)
+                  {
+                     if (kind > 23)
+                        kind = 23;
+                     jjCheckNAdd(1);
+                  }
+                  break;
+               case 1:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 23)
+                     kind = 23;
+                  jjCheckNAdd(1);
+                  break;
+               case 4:
+                  if ((0x2000000020L & l) != 0L)
+                     jjAddStates(14, 15);
+                  break;
+               case 7:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 28)
+                     kind = 28;
+                  jjCheckNAdd(7);
+                  break;
+               case 15:
+                  jjAddStates(11, 13);
+                  break;
+               case 17:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 16;
+                  break;
+               case 19:
+                  if (kind > 31)
+                     kind = 31;
+                  jjAddStates(16, 17);
+                  break;
+               case 21:
+                  if (curChar == 92)
+                     jjstateSet[jjnewStateCnt++] = 20;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         MatchLoop: do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 15:
+                  if ((jjbitVec0[i2] & l2) != 0L)
+                     jjAddStates(11, 13);
+                  break;
+               case 19:
+                  if ((jjbitVec0[i2] & l2) == 0L)
+                     break;
+                  if (kind > 31)
+                     kind = 31;
+                  jjAddStates(16, 17);
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 22 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   15, 17, 18, 19, 21, 9, 10, 11, 9, 10, 2, 15, 17, 18, 5, 6, 
+   19, 21, 
+};
+public static final String[] jjstrLiteralImages = {
+"", null, null, null, null, "\75", "\41\75", "\76", "\76\75", "\74", "\74\75", 
+"\176\75", "\133", "\135", "\72", "\52", "\54", "\46", "\50", "\51", "\173", "\175", 
+"\56", null, null, null, null, null, null, null, null, null, };
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+};
+static final long[] jjtoToken = {
+   0x93ffffe1L, 
+};
+static final long[] jjtoSkip = {
+   0x1eL, 
+};
+private SimpleCharStream input_stream;
+private final int[] jjrounds = new int[22];
+private final int[] jjstateSet = new int[44];
+protected char curChar;
+public ExprParserTokenManager(SimpleCharStream stream)
+{
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+public ExprParserTokenManager(SimpleCharStream stream, int lexState)
+{
+   this(stream);
+   SwitchTo(lexState);
+}
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private final void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 22; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 1 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+private final Token jjFillToken()
+{
+   Token t = Token.newToken(jjmatchedKind);
+   t.kind = jjmatchedKind;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   t.image = (im == null) ? input_stream.GetImage() : im;
+   t.beginLine = input_stream.getBeginLine();
+   t.beginColumn = input_stream.getBeginColumn();
+   t.endLine = input_stream.getEndLine();
+   t.endColumn = input_stream.getEndColumn();
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+public final Token getNextToken() 
+{
+  int kind;
+  Token specialToken = null;
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+
+   try { input_stream.backup(0);
+      while (curChar <= 32 && (0x100002600L & (1L << curChar)) != 0L)
+         curChar = input_stream.BeginToken();
+   }
+   catch (java.io.IOException e1) { continue EOFLoop; }
+   jjmatchedKind = 0x7fffffff;
+   jjmatchedPos = 0;
+   curPos = jjMoveStringLiteralDfa0_0();
+   if (jjmatchedKind != 0x7fffffff)
+   {
+      if (jjmatchedPos + 1 < curPos)
+         input_stream.backup(curPos - jjmatchedPos - 1);
+      if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+      {
+         matchedToken = jjFillToken();
+         return matchedToken;
+      }
+      else
+      {
+         continue EOFLoop;
+      }
+   }
+   int error_line = input_stream.getEndLine();
+   int error_column = input_stream.getEndColumn();
+   String error_after = null;
+   boolean EOFSeen = false;
+   try { input_stream.readChar(); input_stream.backup(1); }
+   catch (java.io.IOException e1) {
+      EOFSeen = true;
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+      if (curChar == '\n' || curChar == '\r') {
+         error_line++;
+         error_column = 0;
+      }
+      else
+         error_column++;
+   }
+   if (!EOFSeen) {
+      input_stream.backup(1);
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+   }
+   throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+}
diff --git a/dods/dap/parser/ParseException.java b/dods/dap/parser/ParseException.java
new file mode 100644
index 0000000..6532f91
--- /dev/null
+++ b/dods/dap/parser/ParseException.java
@@ -0,0 +1,192 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 2.1 */
+package dods.dap.parser;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.  The boolean
+   * flag "specialConstructor" is also set to true to indicate that
+   * this constructor was used to create this object.
+   * This constructor calls its super class with the empty string
+   * to force the "toString" method of parent class "Throwable" to
+   * print the error message in the form:
+   *     ParseException: <result of getMessage>
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super("");
+    specialConstructor = true;
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
+
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
+
+  public ParseException() {
+    super();
+    specialConstructor = false;
+  }
+
+  public ParseException(String message) {
+    super(message);
+    specialConstructor = false;
+  }
+
+  /**
+   * This variable determines which constructor was used to create
+   * this object and thereby affects the semantics of the
+   * "getMessage" method (see below).
+   */
+  protected boolean specialConstructor;
+
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
+
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
+
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * This method has the standard behavior when this object has been
+   * created using the standard constructors.  Otherwise, it uses
+   * "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser), then this method is called during the printing
+   * of the final stack trace, and hence the correct error message
+   * gets displayed.
+   */
+  public String getMessage() {
+    if (!specialConstructor) {
+      return super.getMessage();
+    }
+    String expected = "";
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected += tokenImage[expectedTokenSequences[i][j]] + " ";
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+        expected += "...";
+      }
+      expected += eol + "    ";
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += add_escapes(tok.image);
+      tok = tok.next; 
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
+    }
+    retval += expected;
+    return retval;
+  }
+
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+ 
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  protected String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+}
diff --git a/dods/dap/parser/SimpleCharStream.java b/dods/dap/parser/SimpleCharStream.java
new file mode 100644
index 0000000..f2ed3d1
--- /dev/null
+++ b/dods/dap/parser/SimpleCharStream.java
@@ -0,0 +1,401 @@
+/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 2.1 */
+package dods.dap.parser;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public final class SimpleCharStream
+{
+  public static final boolean staticFlag = false;
+  int bufsize;
+  int available;
+  int tokenBegin;
+  public int bufpos = -1;
+  private int bufline[];
+  private int bufcolumn[];
+
+  private int column = 0;
+  private int line = 1;
+
+  private boolean prevCharIsCR = false;
+  private boolean prevCharIsLF = false;
+
+  private java.io.Reader inputStream;
+
+  private char[] buffer;
+  private int maxNextCharInd = 0;
+  private int inBuf = 0;
+
+  private final void ExpandBuff(boolean wrapAround)
+  {
+     char[] newbuffer = new char[bufsize + 2048];
+     int newbufline[] = new int[bufsize + 2048];
+     int newbufcolumn[] = new int[bufsize + 2048];
+
+     try
+     {
+        if (wrapAround)
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           System.arraycopy(buffer, 0, newbuffer,
+                                             bufsize - tokenBegin, bufpos);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+        }
+        else
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos -= tokenBegin);
+        }
+     }
+     catch (Throwable t)
+     {
+        throw new Error(t.getMessage());
+     }
+
+
+     bufsize += 2048;
+     available = bufsize;
+     tokenBegin = 0;
+  }
+
+  private final void FillBuff() throws java.io.IOException
+  {
+     if (maxNextCharInd == available)
+     {
+        if (available == bufsize)
+        {
+           if (tokenBegin > 2048)
+           {
+              bufpos = maxNextCharInd = 0;
+              available = tokenBegin;
+           }
+           else if (tokenBegin < 0)
+              bufpos = maxNextCharInd = 0;
+           else
+              ExpandBuff(false);
+        }
+        else if (available > tokenBegin)
+           available = bufsize;
+        else if ((tokenBegin - available) < 2048)
+           ExpandBuff(true);
+        else
+           available = tokenBegin;
+     }
+
+     int i;
+     try {
+        if ((i = inputStream.read(buffer, maxNextCharInd,
+                                    available - maxNextCharInd)) == -1)
+        {
+           inputStream.close();
+           throw new java.io.IOException();
+        }
+        else
+           maxNextCharInd += i;
+        return;
+     }
+     catch(java.io.IOException e) {
+        --bufpos;
+        backup(0);
+        if (tokenBegin == -1)
+           tokenBegin = bufpos;
+        throw e;
+     }
+  }
+
+  public final char BeginToken() throws java.io.IOException
+  {
+     tokenBegin = -1;
+     char c = readChar();
+     tokenBegin = bufpos;
+
+     return c;
+  }
+
+  private final void UpdateLineColumn(char c)
+  {
+     column++;
+
+     if (prevCharIsLF)
+     {
+        prevCharIsLF = false;
+        line += (column = 1);
+     }
+     else if (prevCharIsCR)
+     {
+        prevCharIsCR = false;
+        if (c == '\n')
+        {
+           prevCharIsLF = true;
+        }
+        else
+           line += (column = 1);
+     }
+
+     switch (c)
+     {
+        case '\r' :
+           prevCharIsCR = true;
+           break;
+        case '\n' :
+           prevCharIsLF = true;
+           break;
+        case '\t' :
+           column--;
+           column += (8 - (column & 07));
+           break;
+        default :
+           break;
+     }
+
+     bufline[bufpos] = line;
+     bufcolumn[bufpos] = column;
+  }
+
+  public final char readChar() throws java.io.IOException
+  {
+     if (inBuf > 0)
+     {
+        --inBuf;
+
+        if (++bufpos == bufsize)
+           bufpos = 0;
+
+        return buffer[bufpos];
+     }
+
+     if (++bufpos >= maxNextCharInd)
+        FillBuff();
+
+     char c = buffer[bufpos];
+
+     UpdateLineColumn(c);
+     return (c);
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndColumn
+   */
+
+  public final int getColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndLine
+   */
+
+  public final int getLine() {
+     return bufline[bufpos];
+  }
+
+  public final int getEndColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  public final int getEndLine() {
+     return bufline[bufpos];
+  }
+
+  public final int getBeginColumn() {
+     return bufcolumn[tokenBegin];
+  }
+
+  public final int getBeginLine() {
+     return bufline[tokenBegin];
+  }
+
+  public final void backup(int amount) {
+
+    inBuf += amount;
+    if ((bufpos -= amount) < 0)
+       bufpos += bufsize;
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    available = bufsize = buffersize;
+    buffer = new char[buffersize];
+    bufline = new int[buffersize];
+    bufcolumn = new int[buffersize];
+  }
+
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+                                                           int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.Reader dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    if (buffer == null || buffersize != buffer.length)
+    {
+      available = bufsize = buffersize;
+      buffer = new char[buffersize];
+      bufline = new int[buffersize];
+      bufcolumn = new int[buffersize];
+    }
+    prevCharIsLF = prevCharIsCR = false;
+    tokenBegin = inBuf = maxNextCharInd = 0;
+    bufpos = -1;
+  }
+
+  public void ReInit(java.io.Reader dstream, int startline,
+                                                           int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+
+  public void ReInit(java.io.Reader dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+     this(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+                                                           int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  public SimpleCharStream(java.io.InputStream dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize)
+  {
+     ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, 4096);
+  }
+
+  public void ReInit(java.io.InputStream dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  public void ReInit(java.io.InputStream dstream, int startline,
+                                                           int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+  public final String GetImage()
+  {
+     if (bufpos >= tokenBegin)
+        return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+     else
+        return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                              new String(buffer, 0, bufpos + 1);
+  }
+
+  public final char[] GetSuffix(int len)
+  {
+     char[] ret = new char[len];
+
+     if ((bufpos + 1) >= len)
+        System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+     else
+     {
+        System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                                                          len - bufpos - 1);
+        System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+     }
+
+     return ret;
+  }
+
+  public void Done()
+  {
+     buffer = null;
+     bufline = null;
+     bufcolumn = null;
+  }
+
+  /**
+   * Method to adjust line and column numbers for the start of a token.<BR>
+   */
+  public void adjustBeginLineColumn(int newLine, int newCol)
+  {
+     int start = tokenBegin;
+     int len;
+
+     if (bufpos >= tokenBegin)
+     {
+        len = bufpos - tokenBegin + inBuf + 1;
+     }
+     else
+     {
+        len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+     }
+
+     int i = 0, j = 0, k = 0;
+     int nextColDiff = 0, columnDiff = 0;
+
+     while (i < len &&
+            bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+     {
+        bufline[j] = newLine;
+        nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+        bufcolumn[j] = newCol + columnDiff;
+        columnDiff = nextColDiff;
+        i++;
+     } 
+
+     if (i < len)
+     {
+        bufline[j] = newLine++;
+        bufcolumn[j] = newCol + columnDiff;
+
+        while (i++ < len)
+        {
+           if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+              bufline[j] = newLine++;
+           else
+              bufline[j] = newLine;
+        }
+     }
+
+     line = bufline[j];
+     column = bufcolumn[j];
+  }
+
+}
diff --git a/dods/dap/parser/Token.java b/dods/dap/parser/Token.java
new file mode 100644
index 0000000..cca1547
--- /dev/null
+++ b/dods/dap/parser/Token.java
@@ -0,0 +1,81 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 2.1 */
+package dods.dap.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token {
+
+  /**
+   * An integer that describes the kind of this token.  This numbering
+   * system is determined by JavaCCParser, and a table of these numbers is
+   * stored in the file ...Constants.java.
+   */
+  public int kind;
+
+  /**
+   * beginLine and beginColumn describe the position of the first character
+   * of this token; endLine and endColumn describe the position of the
+   * last character of this token.
+   */
+  public int beginLine, beginColumn, endLine, endColumn;
+
+  /**
+   * The string image of the token.
+   */
+  public String image;
+
+  /**
+   * A reference to the next regular (non-special) token from the input
+   * stream.  If this is the last token from the input stream, or if the
+   * token manager has not read tokens beyond this one, this field is
+   * set to null.  This is true only if this token is also a regular
+   * token.  Otherwise, see below for a description of the contents of
+   * this field.
+   */
+  public Token next;
+
+  /**
+   * This field is used to access special tokens that occur prior to this
+   * token, but after the immediately preceding regular (non-special) token.
+   * If there are no such special tokens, this field is set to null.
+   * When there are more than one such special token, this field refers
+   * to the last of these special tokens, which in turn refers to the next
+   * previous special token through its specialToken field, and so on
+   * until the first special token (whose specialToken field is null).
+   * The next fields of special tokens refer to other special tokens that
+   * immediately follow it (without an intervening regular token).  If there
+   * is no such token, this field is null.
+   */
+  public Token specialToken;
+
+  /**
+   * Returns the image.
+   */
+  public final String toString()
+  {
+     return image;
+  }
+
+  /**
+   * Returns a new Token object, by default. However, if you want, you
+   * can create and return subclass objects based on the value of ofKind.
+   * Simply add the cases to the switch for all those special cases.
+   * For example, if you have a subclass of Token called IDToken that
+   * you want to create if ofKind is ID, simlpy add something like :
+   *
+   *    case MyParserConstants.ID : return new IDToken();
+   *
+   * to the following switch statement. Then you can cast matchedToken
+   * variable to the appropriate type and use it in your lexical actions.
+   */
+  public static final Token newToken(int ofKind)
+  {
+     switch(ofKind)
+     {
+       default : return new Token();
+     }
+  }
+
+}
diff --git a/dods/dap/parser/TokenMgrError.java b/dods/dap/parser/TokenMgrError.java
new file mode 100644
index 0000000..f0d27e6
--- /dev/null
+++ b/dods/dap/parser/TokenMgrError.java
@@ -0,0 +1,133 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 2.1 */
+package dods.dap.parser;
+
+public class TokenMgrError extends Error
+{
+   /*
+    * Ordinals for various reasons why an Error of this type can be thrown.
+    */
+
+   /**
+    * Lexical error occured.
+    */
+   static final int LEXICAL_ERROR = 0;
+
+   /**
+    * An attempt wass made to create a second instance of a static token manager.
+    */
+   static final int STATIC_LEXER_ERROR = 1;
+
+   /**
+    * Tried to change to an invalid lexical state.
+    */
+   static final int INVALID_LEXICAL_STATE = 2;
+
+   /**
+    * Detected (and bailed out of) an infinite loop in the token manager.
+    */
+   static final int LOOP_DETECTED = 3;
+
+   /**
+    * Indicates the reason why the exception is thrown. It will have
+    * one of the above 4 values.
+    */
+   int errorCode;
+
+   /**
+    * Replaces unprintable characters by their espaced (or unicode escaped)
+    * equivalents in the given string
+    */
+   protected static final String addEscapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+   /**
+    * Returns a detailed message for the Error when it is thrown by the
+    * token manager to indicate a lexical error.
+    * Parameters : 
+    *    EOFSeen     : indicates if EOF caused the lexicl error
+    *    curLexState : lexical state in which this error occured
+    *    errorLine   : line number when the error occured
+    *    errorColumn : column number when the error occured
+    *    errorAfter  : prefix that was seen before this error occured
+    *    curchar     : the offending character
+    * Note: You can customize the lexical error message by modifying this method.
+    */
+   private static final String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+      return("Lexical error at line " +
+           errorLine + ", column " +
+           errorColumn + ".  Encountered: " +
+           (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+           "after : \"" + addEscapes(errorAfter) + "\"");
+   }
+
+   /**
+    * You can also modify the body of this method to customize your error messages.
+    * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+    * of end-users concern, so you can return something like : 
+    *
+    *     "Internal Error : Please file a bug report .... "
+    *
+    * from this method for such cases in the release version of your parser.
+    */
+   public String getMessage() {
+      return super.getMessage();
+   }
+
+   /*
+    * Constructors of various flavors follow.
+    */
+
+   public TokenMgrError() {
+   }
+
+   public TokenMgrError(String message, int reason) {
+      super(message);
+      errorCode = reason;
+   }
+
+   public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+      this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+   }
+}
diff --git a/dods/dap/parser/package.html b/dods/dap/parser/package.html
new file mode 100644
index 0000000..ebe6d17
--- /dev/null
+++ b/dods/dap/parser/package.html
@@ -0,0 +1,8 @@
+<HTML>
+<HEAD>
+<TITLE>JavaCC generated DAP parser classes.</TITLE>
+</HEAD>
+<BODY>
+This package contains JavaCC generated DAP parser classes.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/dods/util/DODSiniFile.java b/dods/util/DODSiniFile.java
new file mode 100644
index 0000000..d8cd111
--- /dev/null
+++ b/dods/util/DODSiniFile.java
@@ -0,0 +1,76 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.util;
+
+import java.io.*;
+import java.util.*;
+
+
+/**
+The earlier versions of the DODSServlet used .ini files to store configuration
+information for the servlet. This storage has been moved from the .ini file into
+the web.xml file in the WEB-INF directory for the servlet in the servlet engine.
+You can use .ini files for other reasons, and the classes are still present in the
+distribution, but you will have to make use of them yourself, as the DODSServlet 
+no longer accesses the .ini files to get configuration information.
+*/
+
+public class DODSiniFile extends iniFile {
+
+
+    private static final String fname = "DODS.ini";
+    
+	    
+    //************************************************************************
+    /**
+    */
+    public DODSiniFile(){
+	super(null,fname,false);    
+    }
+    //************************************************************************
+    
+    
+    
+    //************************************************************************
+    /**
+    */
+    public DODSiniFile(String path)  {
+
+    
+	super(path,fname,false);    
+   
+    }
+    //************************************************************************
+
+
+    //************************************************************************
+    /**
+    */
+    public DODSiniFile(String path, boolean dbg)  {
+
+    	super(path,fname,dbg);        
+    
+    }
+    //************************************************************************
+
+
+
+           
+    
+}
+
+
diff --git a/dods/util/Debug.java b/dods/util/Debug.java
new file mode 100644
index 0000000..5af8710
--- /dev/null
+++ b/dods/util/Debug.java
@@ -0,0 +1,67 @@
+// $Id: Debug.java,v 1.3 2004-02-06 15:23:51 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package dods.util;
+
+import java.util.TreeMap;
+
+/**
+ * A minimal implementation of a globally-accessible set of Debug flags.
+ */
+
+public class Debug {
+  static private TreeMap map = new TreeMap();
+  static private boolean debug = false, changed = true;
+
+  static public boolean isSet(String flagName) {
+    Object val;
+    if (null == (val = map.get(flagName))) {
+      if (debug) System.out.println("Debug.isSet new "+ flagName);
+      map.put(flagName, new Boolean(false));
+      changed = true;
+      return false;
+    }
+
+    return ((Boolean)val).booleanValue();
+  }
+
+  static public void set(String flagName, boolean value) {
+    Object val;
+    if (null == (val = map.get(flagName))) {
+      changed = true;
+    }
+    map.put(flagName, new Boolean(value));
+    if (debug) System.out.println("  Debug.set "+ flagName+" "+value);
+  }
+
+  static public void clear() {
+   map = new TreeMap();
+  }
+}
+
+/**
+ * $Log: not supported by cvs2svn $
+ * Revision 1.1  2001/10/24 22:51:42  ndp
+ * *** empty log message ***
+ *
+ * Revision 1.1.1.1  2001/09/26 15:36:47  caron
+ * checkin beta1
+ *
+ */
diff --git a/dods/util/Getopts.java b/dods/util/Getopts.java
new file mode 100644
index 0000000..5644da8
--- /dev/null
+++ b/dods/util/Getopts.java
@@ -0,0 +1,308 @@
+// Copyright (C) 1997 by Arieh Markel <arieh at selectjobs.com>.  
+// 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.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+//
+
+package dods.util;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ *  A class for achieving getopts() functionality.
+ *<P>
+ *  Loosely based on perl5's getopt/Std.pm.
+ *<P>
+ *  The object is instantiated with a 'flags' String that is the
+ *  composed of the set of switches allowed to be passed, and the
+ *  'args' String array that has to be parsed opposite the 'flags' 
+ *  string.
+ *<PRE>
+ *	new Getopts("oif:", args)	-o, -i are boolean flags,
+ *					-f takes an argument
+ *</PRE>
+ *	The class processes single-character switches with switch
+ *	clustering.
+ *<P>
+ *  The list of valid switches is accessible through the 'swList()'
+ *  method, which returns an Enumeration of the switch names.
+ *<P>
+ *  A local array including the arguments from the 'args' array that
+ *  was passed as an argument but are the actual command line arguments
+ *  is generated and is accessible through the 'argList()' method.
+ *<P>
+ *  Options switch content fields can be accessible through the
+ *  'OptSwitch' class.
+ *<P>
+ *  @author  Arieh Markel  (arieh at selectjobs.com)
+	     thanks to Mark Skipper (mcs at dmu.ac.uk) for bug fix
+ *  @version 1.1  6/14/98  Updated evaluation following Mark's remarks.
+ * 
+ *   @see OptSwitch
+ *   @see java.util.Enumeration
+ * 
+ */
+ /*  $Id: Getopts.java,v 1.3 2004-02-06 15:23:51 donm Exp $ */
+
+public class Getopts extends Object {
+
+    // The internal storage of options
+    //
+    Hashtable switchtab; 
+    String    arglist[];
+
+    // getSwitch
+    /**  
+     *  method to return the OptSwitch object associated with the 
+     *  'sw' argument.
+     *<P>
+     *  @param sw	switch whose class is requested
+     *<P>
+     */
+    public OptSwitch getSwitch(Character sw) {
+	return (OptSwitch) switchtab.get(sw);
+    }
+
+    /**
+     *  getOption
+     *
+     *  @param sw	Character switch whose option is requested
+     */
+    public String getOption(Character sw) {
+	return getOption(sw);
+    }
+
+    /**
+     *  getOption
+     *
+     *  @param sw	int value switch whose option is requested
+     */
+    public String getOption(int sw) {
+	Character  opt = new Character((char) sw);
+	return getOption(opt);
+    }
+
+    // swList
+    /**
+     *  Method to return an Enumeration of the switches
+     *	that the Getopts object is able to parse (according
+     *	to its initialization).
+     *<P>
+     *  May be later used to step through the OptSwitch objects.
+     */
+    public Enumeration swList() {
+	return switchtab.keys();
+    }
+
+    // argList
+    /**
+     *  Method to return an array of the actual arguments of the
+     *	command line invocation.
+     */
+    public String[] argList() {
+	return arglist;
+    }
+
+    // Getopts
+    /**
+     *  Basic class constructor.  Gets the flags passed, in a
+     *	notation similar to the one used by the sh, ksh, bash,
+     *	and perl getopts.
+     *<P>
+     *  String array 'args' is passed and
+     *	is parsed according to the flags.
+     *<P>
+     *  @param flags a string with the valid switch names
+     *  @param args[] array of strings (usually args)
+     *<P>
+     *  @exception InvalidSwitch	thrown when invalid options are found
+     *<P>
+     */ 
+    public Getopts(String flags, String args[])
+    	throws InvalidSwitch
+    {
+	String throwstring = new String("Invalid Getopts switch(s): ");
+	String usage = new String("Usage: Getopts(String flags, String args[])");
+
+	switchtab = new Hashtable(1,1);
+
+	for (int i = 0; i < flags.length(); i++) {
+	    boolean found;
+	    int  cc = flags.charAt(i);
+	    Character c = new Character((char) cc);
+	    char alpha[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
+			     'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 
+			     's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
+
+	    //  space characters or punctuation marks are not allowed
+	    //  as switch values
+	    //
+	    if (cc == ' ' || cc == '\t' || cc == '\n' || cc == '\r') {
+		throw new InvalidSwitch(throwstring + "Spaces not allowed\n" +
+				        usage + "\n");
+	    }
+
+	    found = false;
+	    for (int j = 0; j < alpha.length; j++) {
+		Character  ch = new Character((char) alpha[j]);
+		char uc = ch.toUpperCase(ch.charValue());
+		if (alpha[j] == cc || uc == cc) {
+		    found = true;
+		    break;
+		}
+	    }
+
+	    //  if the character was not found on the set, throw the exception
+	    //
+	    if (! found && cc != ':' ) {
+		throw new InvalidSwitch(throwstring + "Invalid Character " + c +
+				    "\n" + usage + "\n");
+	    }
+
+	    //  ':' appears when the preceding character accepts a value
+	    //
+	    if (cc == ':') {
+		if (i > 0) {
+		    int	 prv = flags.charAt(i-1);
+		    
+		    if (prv == ':') {
+			throw new InvalidSwitch(throwstring + 
+					"Can't have consecutive ':'\n" +
+				    		usage + "\n");
+		    }
+		    else {
+			Character cp = new Character((char) prv);
+			OptSwitch sw = (OptSwitch) switchtab.get(cp);
+			sw.SetHasValue(OptSwitch.VAL);
+		    }
+		}
+	    }
+	    else {
+		OptSwitch sw = new OptSwitch(cc, OptSwitch.BOOL);
+
+		switchtab.put(c, sw);
+	    }
+	}
+
+	//
+	//  Now, step through the arguments in the argument list
+	//  and identify the options and values
+	//
+	int i;
+	for (i = 0; i < args.length; i++) {
+	    char cc = args[i].charAt(0);	// first character
+
+	    if (cc != '-') {
+	    	// end of options
+	        break;
+	    }
+	    // more options, iterate them
+	    for (int j = 1; j < args[i].length(); j++) {
+		cc = args[i].charAt(j);
+		Character fc = new Character(cc);
+		OptSwitch cs = (OptSwitch) switchtab.get(fc);
+		if (cs == null) {
+		    // The supplied switch wasn't recognised.
+		    throw new InvalidSwitch(throwstring + "invalid switch " +
+			cc + "\n2 Valid switches are: " + flags + "\n" + usage + "\n");
+		} else if (!cs.acceptVal()) {
+		    // the option is a switch and takes no value
+		    cs.SetVal(true);
+		} else if (j+1 < args[i].length()) {
+		    //  the value may follow immediately after the switch 
+		    // (not as a separate token) set value to remainder of string...
+		    cs.SetVal(args[i].substring(j+1));
+		    // ... and move pointer to end, thus consuming the string
+		    j = args[i].length();
+		} else if (++i >= args.length) {
+		    // there wasn't another token
+		     throw new InvalidSwitch(throwstring + 
+			"missing value from switch " + cc + 
+			"\n1 Valid switches are: " + flags + 
+			"\n" + usage + "\n");
+		} else if (args[i].charAt(0) == '-') {
+		    // there was no value, next token starts with flags
+		    throw new InvalidSwitch(
+			throwstring + "missing value from switch " + cc + 
+			"\n0 Valid switches are: " + flags + "\n" +
+			usage + "\n");
+		} else {
+		    // the next token is the value
+		    cs.SetVal(args[i]);
+		    // and move j to the end to mark it consumed
+		    j = args[i].length();
+		}
+	    } // end for j
+	} // end for i
+
+	//  Now insert the array of arguments into the object
+	//
+	arglist = new String[args.length - i];
+	System.arraycopy(args, i, arglist, 0, args.length-i);
+    }
+
+    /**
+     *  method for class testing.
+     *<P>
+     *	Invocation:	
+     *<PRE>
+     * 		java Getopts "option set" arg0 arg1 ... argn
+     *</PRE>
+     *
+     *  @params args		arguments passed
+     *  @exception InvalidSwitch	thrown when invalid options are found
+     */
+    public static void main(String args[]) 
+	throws InvalidSwitch
+    {
+	int i;
+	String args1[] = new String[args.length-1];
+	System.arraycopy(args, 1, args1, 0, args.length-1);
+
+	for (i = 0; i < args.length; i++) {
+	    System.out.println("args[" + i + "] : " + args[i]);
+	}
+
+	try {
+	    Getopts opts = new Getopts(args[0], args1);
+	    Enumeration names = opts.swList();
+	    
+	    i = 0;
+	    while (names.hasMoreElements()) {
+		OptSwitch cs = opts.getSwitch((Character) names.nextElement());
+		System.out.println("args[" + i + "] : " + 
+				   (char) cs.sw + " " + cs.type + " " +
+				   cs.set + " " + cs.val);
+		i++;
+	    }
+
+	    String argp[] = opts.argList();
+	    for (i = 0; i < argp.length; i++) {
+		System.out.println("argv[" + i + "] : " + argp[i]);
+	    }
+	}
+	catch (InvalidSwitch e) {
+	    System.out.print(e);
+	}
+    }
+}
diff --git a/dods/util/InvalidSwitch.java b/dods/util/InvalidSwitch.java
new file mode 100644
index 0000000..95090cb
--- /dev/null
+++ b/dods/util/InvalidSwitch.java
@@ -0,0 +1,47 @@
+// Copyright (C) 1997 by Arieh Markel <arieh at selectjobs.com>.  
+// 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.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+//
+
+package dods.util;
+
+/** Exception throwable by the Getopts class
+ */
+public class InvalidSwitch extends Throwable {
+/**
+ *   This is the specific exception that is thrown when an invalid
+ *   switch or another problem occurred in the Getopts class.
+ */
+    /**
+     *  Used when no notification string other than the
+     *  standard one will be thrown with the exception.
+     */
+    public InvalidSwitch() { super(); }
+
+    /**
+     *	Used when passing the string that will be thrown.
+     *
+     *  @param	s	the error string that is notified
+     */
+    public InvalidSwitch(String s) { super(s); }
+}
diff --git a/dods/util/OptSwitch.java b/dods/util/OptSwitch.java
new file mode 100644
index 0000000..af3b668
--- /dev/null
+++ b/dods/util/OptSwitch.java
@@ -0,0 +1,139 @@
+// Copyright (C) 1997 by Arieh Markel <arieh at selectjobs.com>.  
+// 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.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+//
+
+package dods.util;
+
+/**
+ *  OptSwitch	- class definition for Switches
+ *
+ *  Description:  this class defines a switch element.
+ *	a switch is considered to be used (having been set or assigned
+ *	a value if the 'set' field is false AND the 'val' field is null.
+ *
+ *  Constants:	
+ *
+ *   Permitted values for 'type' field:
+ *
+ *   	protected static final int NONE 	uninitialized
+ *   	protected static final int BOOL 	boolean type switch
+ *   	protected static final int VAL  	value type switch
+ *
+ *  Fields:	sw	the switch name
+ *		type	boolean/value
+ *		set	value is set/clear
+ *		val	switch value (not applicable to boolean type switch)
+ *
+ * @author	Arieh Markel
+ * @version	1.0
+ */
+public class OptSwitch extends Object {
+    protected static final int NONE = 0;
+    protected static final int BOOL = 1;
+    protected static final int VAL  = 2;
+
+    int		sw;		// switch name
+    int		type;		// boolean/value
+    public boolean	set;	// switch is set/unset
+    public String	val;		// value of switch
+    boolean     debug = false;
+
+    public OptSwitch() {
+	set  = false;
+	type = NONE;
+	val  = null;
+	sw = -1;
+    }
+
+    /**
+     *  Invocation with explicit Character switchname and type
+     *
+     *  @param	name  letter to indicate the switch name
+     */
+    public OptSwitch(Character c, int type) {
+	sw  = Character.digit(c.charValue(), 10);
+	this.type = type;
+	set = false;
+	val = null;
+	if (debug) {
+	    System.out.println("sw = " + (char) sw + "; type = " + type +
+	    			"; set = " + set + "; val = " + val);
+	}
+    }
+
+    /**
+     *  Invocation with explicit integer switchname and type 
+     *
+     *  @param	name  letter to indicate the switch name
+     *
+     */
+    public OptSwitch(int c, int type) {
+	sw  = c;
+	this.type = type;
+	set = false;
+	val = null;
+	if (debug) {
+	    System.out.println("sw = " + (char) sw + "; type = " + type +
+	    			"; set = " + set + "; val = " + val);
+	}
+    }
+
+    /**
+     *  Set the value type of the option switch to the type passed
+     *
+     *  @param	type	type of value that switch may accept or be
+     */
+    public void SetHasValue(int type) {
+	this.type = type;
+	if (debug) {
+	    System.out.println("sw = " + (char) sw + "; type = " + type +
+	    			"; set = " + set + "; val = " + val);
+	}
+    }
+
+    /**
+     *  Return whether the option switch accepts values or no
+     */
+    public boolean acceptVal() {
+	return type == VAL;
+    }
+
+    /**
+     *  Set the 'set' field of the option switch to 'b'.
+     *
+     *  @param  b	set the 'set' boolean field to 'b'.
+     */
+    public void SetVal(boolean b) {
+	set = b;
+    }
+
+    /**
+     *  Set the 'val' field of the option switch to 's'.
+     *
+     *  @param s	string to assign to 'val' field.
+     */
+    public void SetVal(String s) {
+	val = s;
+    }
+}
diff --git a/dods/util/SortedTable.java b/dods/util/SortedTable.java
new file mode 100644
index 0000000..895a6bc
--- /dev/null
+++ b/dods/util/SortedTable.java
@@ -0,0 +1,126 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1998, California Institute of Technology.  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Jake Hamby, NASA/Jet Propulsion Laboratory
+//         Jake.Hamby at jpl.nasa.gov
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.util;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/** SortedTable looks exactly like Hashtable but preserves the insertion order
+    of elements.  While this results in slower performance, it ensures that
+    the DAS will always be printed in the same order in which it was read. */
+public final class SortedTable extends Dictionary {
+  private Vector keys, elements;
+
+  public SortedTable() {
+    keys = new Vector();
+    elements = new Vector();
+  }
+
+  /** Returns the number of keys in this table. */
+  public int size() {
+    return keys.size();
+  }
+
+  /** Tests if this table is empty. */
+  public boolean isEmpty() {
+    return keys.isEmpty();
+  }
+
+  /** Returns an enumeration of the keys in this table. */
+  public Enumeration keys() {
+    return keys.elements();
+  }
+
+  /** Returns an enumeration of the values in this table. */
+  public Enumeration elements() {
+    return elements.elements();
+  }
+
+  /** Returns the value to which the key is mapped in this table.
+    @param key a key in this table.
+    @return the value to which the key is mapped, or null if the key is not
+            mapped to any value in the table.
+    */
+  public synchronized Object get(Object key) {
+    int index = keys.indexOf(key);
+    if (index != -1)
+      return elements.elementAt(index);
+    else
+      return null;
+  }
+
+  /** Returns the key at the specified index.
+    @param index the index to return
+    @return the key at the specified index.
+    */
+  public synchronized Object getKey(int index) {
+    return keys.elementAt(index);
+  }
+
+  /** Returns the element at the specified index.
+    @param index the index to return
+    @return the element at the specified index.
+    */
+  public synchronized Object elementAt(int index) {
+    return elements.elementAt(index);
+  }
+
+  /** Maps the specified key to the specified value in this table.
+    @param key the key
+    @param value the value
+    @return the previous value to which the key is mapped, or null if the
+            key did not have a previous mapping.
+    @throw NullPointerException if the key or value is null.
+    */
+  public synchronized Object put(Object key, Object value) throws NullPointerException{
+    if (key == null || value == null)
+      throw new NullPointerException();
+
+    int index = keys.indexOf(key);
+    if (index != -1) {
+      Object prev = elements.elementAt(index);
+      elements.setElementAt(value, index);
+      return prev;
+    } else {
+      keys.addElement(key);
+      elements.addElement(value);
+      return null;
+    }
+  }
+
+  /** Removes the key (and its corresponding value) from this table.  If the
+      key is not in the table, do nothing.
+    @param key the key to remove.
+    @return the value to which the key had been mapped, or null if the key
+            did not have a mapping.
+    */
+  public synchronized Object remove(Object key) {
+    int index = keys.indexOf(key);
+    if(index != -1) {
+      Object prev = elements.elementAt(index);
+      keys.removeElementAt(index);
+      elements.removeElementAt(index);
+      return prev;
+    } else {
+      return null;
+    }
+  }
+
+  /** Returns a Vector containing the elements in the SortedTable.  This is
+    used for more efficient implementation of dods.dap.Util.uniqueNames() by
+    dods.dap.DDS.checkSemantics()
+    @return A Vector containing the elements in this SortedTable.
+    */
+  public Vector getElementVector() {
+    return elements;
+  }
+}
diff --git a/dods/util/Tools.java b/dods/util/Tools.java
new file mode 100644
index 0000000..eb0af20
--- /dev/null
+++ b/dods/util/Tools.java
@@ -0,0 +1,87 @@
+
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//
+/////////////////////////////////////////////////////////////////////////////
+
+
+/* $Id: Tools.java,v 1.3 2004-02-06 15:23:51 donm Exp $
+*
+*/
+
+package dods.util;
+
+import java.lang.reflect.*;
+
+/**
+*
+* @author Nathan David Potter
+*/
+
+
+public abstract class Tools {
+
+    //#*******************************************************************************	
+    /**	
+    *	Show me lots of stuff about the passed in object
+    *
+    *
+    */	
+    public static void probeObject(Object o) {		
+
+        Class c = o.getClass();
+	
+	Class interfaces[] = c.getInterfaces();
+	Class parent = c.getSuperclass();
+        Method m[] = c.getMethods();
+	
+	System.out.println("********* OBJECT PROBE *********");
+	System.out.println("Class Name:  "+c.getName());
+	System.out.println("Super Class: "+parent.getName());
+	System.out.println("Interfaces: ");
+		for(int i=0; i<interfaces.length ;i++){	
+	    System.out.println("    "+interfaces[i].getName());
+	}
+	
+	
+	System.out.println("Methods:");
+	for(int i=0; i<m.length ;i++){
+	
+	
+            Class params[] = m[i].getParameterTypes();
+            Class excepts[] = m[i].getExceptionTypes();
+            Class ret = m[i].getReturnType();
+	
+	    System.out.print("    "+ret.getName() + "  "+m[i].getName()+"(");
+	    
+	    for(int j=0; j<params.length ; j++){
+	        if(j>0)
+		    System.out.print(", ");
+	        System.out.print(params[j].getName());
+	    }
+	    System.out.print(")  throws ");
+	    for(int j=0; j<excepts.length ; j++){
+	        if(j>0)
+		    System.out.print(", ");
+	        System.out.print(excepts[j].getName());
+	    }
+            System.out.println("");
+        }
+	System.out.println("******************");
+
+    }			
+    //#*******************************************************************************	
+
+
+}
diff --git a/dods/util/dasTools.java b/dods/util/dasTools.java
new file mode 100644
index 0000000..32d18ba
--- /dev/null
+++ b/dods/util/dasTools.java
@@ -0,0 +1,184 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+package dods.util;
+
+import java.util.Enumeration;
+import java.io.*;
+
+import dods.dap.*;
+
+/**
+ */
+public class dasTools {
+
+
+     
+    /** This code could use a real `kill-file' some day - 
+    *   about the same time that the rest of the server gets 
+    *   an `rc' file... For the present just return
+    *   false (There is no killing going on here...)
+    *
+    *   The C++ implementation looks like this:
+    *
+    *   static bool
+    *   name_in_kill_file(const string &name)
+    *   {
+    *       static Regex dim(".*_dim_[0-9]*", 1); // HDF `dimension' attributes.
+    *
+    *       return dim.match(name.c_str(), name.length()) != -1;
+    *   }
+    *
+    */
+    public static boolean nameInKillFile(String name){
+        return(false);
+    }
+
+
+
+
+    public static boolean nameInDDS(String name, DDS dds){
+
+	boolean found = true;
+	
+        try { 
+	    dds.getVariable(name);
+	}
+	catch (NoSuchVariableException e) {
+	
+	    found = false;
+	}
+	
+        //System.out.println("nameInDDS(): "+found);
+        return(found);
+    }
+
+
+
+
+    /* C++ implementation
+    static bool
+    name_is_global(string &name)
+    {
+        static Regex global("\\(.*global.*\\)\\|\\(.*dods.*\\)", 1);
+        downcase(name);
+        return global.match(name.c_str(), name.length()) != -1;
+    }
+    */
+    public static boolean nameIsGlobal(String name){
+    
+        String lcName = name.toLowerCase();
+	boolean global = false;
+	
+	if(lcName.indexOf("global") >= 0)
+	    global = true;
+	    
+	if(lcName.indexOf("dods") >= 0)
+	    global = true;
+	
+	
+        //System.out.println("nameIsGlobal(): "+global);
+    
+        return(global);
+    }
+
+
+
+
+
+    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+    public static String fancyTypeName(BaseType bt){
+    
+        String fancy;
+	
+	if(bt instanceof DByte)
+	    return("8 bit Byte");
+
+	if(bt instanceof DUInt16)
+	    return("16 bit Unsigned Integer");
+		
+	if(bt instanceof DInt16)
+	    return("16 bit Integer");
+		
+	if(bt instanceof DUInt32)
+	    return("32 bit Unsigned Integer");
+	
+	if(bt instanceof DInt32)
+	    return("32 bit Integer");
+		
+	if(bt instanceof DFloat32)
+	    return("32 bit Real");
+		
+	if(bt instanceof DFloat64)
+	    return("64 bit Real");
+		
+	if(bt instanceof DURL)
+	    return("URL");
+	    
+	if(bt instanceof DString)
+	    return("String");
+		
+		
+	if(bt instanceof DArray){
+	
+	    DArray a = (DArray) bt;
+	    String type = "Array of " + 
+	                  fancyTypeName(a.getPrimitiveVector().getTemplate()) + 
+			  "s ";
+            
+	    Enumeration e = a.getDimensions();
+	    while(e.hasMoreElements()){
+	        DArrayDimension dad = (DArrayDimension)e.nextElement();
+	
+	        type += "[" + dad.getName() + " = 0.." + (dad.getSize()-1) +"]";
+	
+	    }
+	    type += "\n";
+	    return(type);
+	}
+
+	if(bt instanceof DList){
+	    DList a = (DList) bt;
+	    String type = "List of " + 
+	                  fancyTypeName(a.getPrimitiveVector().getTemplate()) + 
+			  "s\n";
+            
+	   return(type);
+	}
+	
+	if(bt instanceof DStructure)
+	    return("Structure");
+		
+	if(bt instanceof DSequence)
+	    return("Sequence");
+		
+	if(bt instanceof DGrid)
+	    return("Grid");
+
+	return("UNKNOWN");
+  
+
+    }
+    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ 
+  
+  
+  
+}
diff --git a/dods/util/iniFile.java b/dods/util/iniFile.java
new file mode 100644
index 0000000..49f2fa8
--- /dev/null
+++ b/dods/util/iniFile.java
@@ -0,0 +1,428 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University  
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged. 
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//         
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.util;
+
+import java.io.*;
+import java.util.*;
+
+
+/**
+ * This class encapsulates the old .ini file functionality
+ * that we used to see (and still do) in the Microsoft Operating
+ * Systems. This is a handy way of delivering configuration information
+ * to software at runtime. The .ini file structure is as follows:
+ * <p>
+ * <b>[SectionName]</b><br>
+ * <b>PropertyName = Property</b><br>
+ * <b>NextPropertyName = Property2</b><br>
+ * <b>AnotherPropertyName = Property3</b><br>
+ * <p>
+ * <b>[AnotherSectionName]</b><br>
+ * <b>PropertyName = Property</b><br>
+ * <b>NextPropertyName = Property2</b><br>
+ * <b>AnotherPropertyName = Property3</b><br>
+ * <p>
+ * This class opens and parses the iniFile it's constructor. If
+ * the file isn't accesible or is unparsable then the class will
+ * not contain any usable configuration information.
+ *
+ * @version $Revision: 1.3 $
+ * @author ndp
+ */
+
+public class iniFile {
+
+    private boolean Debug = false;
+	    	    
+    private String iniFile;
+    private Vector sectionNames;
+    private Vector sectionProperties;
+    private int currentSection;
+    
+    private String errMsg;
+    
+    //************************************************************************
+    /**
+    * We don't want this to get used so we made it protected...
+    */
+    protected iniFile(){
+    }
+    //************************************************************************
+    
+    
+    
+    
+    /*************************************************************************
+    * Create a <code>iniFile</code> object from the file named in the
+    * parameter <code>fname</code>. The object will get the append to the 
+    * file name to the path returned by the call <code>
+    * System.getProperty("user.home")</code>.
+    *
+    * @param fname A <code>String</code> containing the name of the .ini file.
+    */
+    public iniFile(String fname) {
+    
+	this(null,fname,false);
+    
+
+    }
+    //************************************************************************
+    
+    
+    
+    /*************************************************************************
+    * Create a <code>iniFile</code> object from the file named in the
+    * parameter <code>fname</code>, and found on the parameter<code> path</code>
+    * @param path A <code>String</code> containing the path to the .ini file.
+    * @param fname A <code>String</code> containing the name of the .ini file.
+    */
+    public iniFile(String path ,String fname)  {
+
+    
+	this(path,fname,false);    
+   
+    }
+    //************************************************************************
+
+
+    /*************************************************************************
+    * Create a <code>iniFile</code> object from the file named in the
+    * parameter <code>fname</code>, and found on the parameter<code> path</code>
+    * @param path A <code>String</code> containing the path to the .ini file.
+    * @param fname A <code>String</code> containing the name of the .ini file.
+    * @param dbg A <code>boolean</code> that toggles debugging output.
+    */
+    public iniFile(String path ,String fname, boolean dbg)  {
+
+    
+	Debug = dbg;
+	
+	if(path == null)
+	    path = System.getProperty("user.home");
+
+	    
+        String fileSeperator = System.getProperty("file.separator");
+    
+        iniFile = path + fileSeperator + fname;
+	errMsg = "The file: \""+iniFile+"\" did not contain recognizable init information.";
+
+        currentSection = -1;    
+	sectionNames = null;
+	sectionProperties = null;
+        parseFile();
+
+        if(sectionNames == null)
+	        System.err.println(errMsg);
+    
+    
+    }
+    //************************************************************************
+
+
+
+    /*************************************************************************
+    * Get the name of the .ini file that was used to create this object.
+    *
+    * returns A <code>String</code> containing the name of the .ini file
+    * that was opened a parsed when this object was instantiated.
+    */
+    public String getFileName(){
+        return(iniFile);
+    }
+    //************************************************************************
+
+
+
+    /*************************************************************************
+    * Parse the .ini file indicated by the <code>private String iniFile</code>.
+    */
+    private void parseFile()  {
+
+    
+	try {    
+            BufferedReader fp = new BufferedReader( new InputStreamReader( new FileInputStream(iniFile)));
+	
+	    boolean done = false;
+	    while(!done) {
+	
+	        String thisLine = fp.readLine();
+	    
+	        if(thisLine != null) {
+	            if(Debug) System.out.println("Read: \""+thisLine+"\"");
+                
+                    thisLine.trim();
+	
+	            if (thisLine.startsWith(";") || thisLine.equalsIgnoreCase("")){
+	                // Do nothing, it's a comment
+		        if(Debug) System.out.println("Ignoring comment or blank line...");
+	            }
+	            else {	    
+	                int cindx = thisLine.indexOf(";");
+		
+		        if(cindx > 0)
+		            thisLine = thisLine.substring(0,cindx);
+			
+                        if(Debug) System.out.println("Comments removed: \""+thisLine+"\"");
+	    
+	                thisLine.trim();
+	    
+	                if(thisLine.startsWith("[")  && thisLine.endsWith("]")){
+	    
+	                    String sname = thisLine.substring(1,thisLine.length()-1).trim();
+		    
+			    if(Debug) System.out.println("Found Section Name: "+sname);
+			
+			    if(sectionNames == null)
+			        sectionNames = new Vector();
+			    
+			    sectionNames.add(sname);
+			
+			    if(sectionProperties == null)
+			        sectionProperties = new Vector();
+			    
+			    sectionProperties.add(new Vector());
+			
+			
+			
+                        }
+                        else if(sectionNames!= null && sectionProperties!=null){
+		            int eqidx = thisLine.indexOf("=");
+		    
+		            if(eqidx != -1){
+		                String pair[] = new String[2];
+		                pair[0] = thisLine.substring(0,eqidx).trim();
+		                pair[1] = thisLine.substring(eqidx+1,thisLine.length()).trim();
+		
+			        if(Debug) System.out.println("pair[0]: \""+pair[0]+"\"   pair[1]: \""+pair[1]+"\"");
+			    
+			        // Add the pair to the current property list, which is the
+			        // last element in the sectionProperties vector.
+			        ((Vector)sectionProperties.lastElement()).add(pair);			    
+		            }
+                        }
+	            }
+	        }
+	        else {
+	           done = true;
+	        }
+            }
+     	    fp.close();
+        }
+	catch (FileNotFoundException e) {
+	        System.err.println("Could Not Find ini File: \""+iniFile+"\"");
+	}
+	catch (IOException e) {
+	        System.err.println("Could Not Read ini File: \""+iniFile+"\"");
+	}
+     
+    }
+    //************************************************************************
+    
+    
+    
+    
+    /*************************************************************************
+    * Get the list of properties for the section <code>sectionName</code>.
+    *
+    * @param sectionName A <code>String</code> containing the name of the
+    * section whose property list is desired.
+    *
+    * @returns An enumeration of the properties in the <code>sectionName</code>
+    * Returns <code>null</code> if the section name doesn't exist or there are
+    * no properties for the section.
+    */
+    public Enumeration getPropList(String sectionName){
+    
+        if(sectionNames == null){
+	        System.err.println(errMsg);
+	        return(null);
+	}
+    
+        int sectionIndex = 0;
+	
+	Enumeration e = sectionNames.elements();
+	
+	boolean done = false;
+	while(!done && e.hasMoreElements()){
+	    String thisName = (String) e.nextElement();
+	    if(sectionName.equalsIgnoreCase(thisName))
+	        done = true;
+	    else
+	        sectionIndex++;
+	}
+	
+	if(!done)
+	    return(null);
+	    
+	return(((Vector)sectionProperties.elementAt(sectionIndex)).elements());
+    
+    }
+    //************************************************************************
+   
+
+    
+	    
+    /*************************************************************************
+    * Get the named property from the current section.
+    *
+    * @param propertyName The name of the desired property.
+    *
+    * @returns A <code>String</code> containing the value of property of the 
+    * passed property name. Returns null if the property name doesn't exist
+    * or is not set.
+    */
+    public String getProperty(String propertyName){
+    
+        if(currentSection < 0){
+	        String msg = "You must use the setSection() method before you can use getProperty().";
+	        System.err.println(msg);
+	        return(msg);
+	}
+        String pair[] = null;
+	
+        Enumeration e = ((Vector)sectionProperties.elementAt(currentSection)).elements();
+	boolean done = false;
+	while(!done && e.hasMoreElements()){
+	
+	    pair = (String[]) e.nextElement();
+	    
+	    if(pair[0].equalsIgnoreCase(propertyName))
+		    done = true;
+	}
+	
+	if(done)
+	    return(pair[1]);
+
+    
+        return(null);
+	    
+    }
+    //************************************************************************
+    
+    
+    
+    
+    
+    /*************************************************************************
+    * Get the list of Sections of this .ini File
+    *
+    * @returns An enumeration of the sections in iniFile
+    */
+    public Enumeration getSectionList(){
+    
+        if(sectionNames == null)
+	    return(null);
+
+        return(sectionNames.elements());
+
+    }
+    //************************************************************************
+    
+    
+    
+    
+    
+    /*************************************************************************
+    * Prints the iniFile.
+    *
+    * @param ps The <code>PrintStream</code> to which to print.
+    */
+    public void printProps(PrintStream ps){
+    
+    
+        Enumeration se = getSectionList();
+	
+	if(se == null){
+	     ps.println(errMsg);
+	}
+	else {
+	
+	    while(se.hasMoreElements()){
+	
+	        String sname = (String) se.nextElement();
+	            
+                setSection(sname);
+	    
+	        ps.println("["+sname+"]");
+	    
+                Enumeration pe = getPropList(sname);
+	    
+	        while(pe.hasMoreElements()){
+	        
+		    String pair[] = (String[]) pe.nextElement();
+		
+		    String prop = pair[0];
+		
+		    String valu = getProperty(prop);
+		
+		    ps.println("    \""+prop+"\" = \""+valu+"\"");
+
+	        }
+	    }
+	}
+	    
+    }
+    //************************************************************************
+    
+    
+    
+    
+    
+    /*************************************************************************
+    * Set the section of the iniFile that you wish to work with. This is 
+    * persistent for the life of the object, or until it's set again.
+    *
+    * @param sectionName A <code>String</code> containing the name of the 
+    * section that is desired.
+    *
+    * @returns true if the section exists and the operation was successful, 
+    * false otherwise.
+    */
+    public boolean setSection(String sectionName){
+    
+        if(sectionNames == null){
+	        System.err.println(errMsg);
+	        return(false);
+	}
+    
+        int sectionIndex = 0;
+	
+	Enumeration e = sectionNames.elements();
+	
+	boolean done = false;
+	while(!done && e.hasMoreElements()){
+	    String thisName = (String) e.nextElement();
+	    if(sectionName.equalsIgnoreCase(thisName))
+	        done = true;
+	    else
+	        sectionIndex++;
+	}
+	
+	if(!done)
+	    return(false);
+	    
+        currentSection = sectionIndex;	    
+	    
+	return(true);
+    }
+    //************************************************************************
+    
+           
+    
+}
+
+
diff --git a/dods/util/package.html b/dods/util/package.html
new file mode 100644
index 0000000..70ea27e
--- /dev/null
+++ b/dods/util/package.html
@@ -0,0 +1,8 @@
+<HTML>
+<HEAD>
+<TITLE>Utility classes used by several DODS packages.</TITLE>
+</HEAD>
+<BODY>
+This package contains utility classes used by several DODS packages.
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/dods/util/test_iniFile.java b/dods/util/test_iniFile.java
new file mode 100644
index 0000000..eade476
--- /dev/null
+++ b/dods/util/test_iniFile.java
@@ -0,0 +1,65 @@
+/////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 1999, COAS, Oregon State University
+// ALL RIGHTS RESERVED.   U.S. Government Sponsorship acknowledged.
+//
+// Please read the full copyright notice in the file COPYRIGHT
+// in this directory.
+//
+// Author: Nathan Potter (ndp at oce.orst.edu)
+//
+//                        College of Oceanic and Atmospheric Scieneces
+//                        Oregon State University
+//                        104 Ocean. Admin. Bldg.
+//                        Corvallis, OR 97331-5503
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package dods.util; // JC-CHANGED
+
+import java.io.*;
+import java.util.*;
+
+import dods.util.iniFile;
+
+
+public class test_iniFile {
+
+
+    //************************************************************************
+    /**
+    */
+    public static void main(String args[]){
+        boolean dbgFlag = true;
+                iniFile inf = null;
+
+        switch(args.length){
+            case 1:
+                inf = new iniFile(null,args[0],dbgFlag);
+                break;
+
+            case 2:
+                inf = new iniFile(args[0], args[1],dbgFlag);
+                break;
+
+            case 3:
+                if(args[2].equalsIgnoreCase("false"))
+                    dbgFlag = false;
+                inf = new iniFile(args[0], args[1],dbgFlag);
+                break;
+
+            default:
+                System.err.println("Usage: test_iniFile [path] filename.ini [false]");
+                System.exit(1);
+        }
+
+
+        inf.printProps(System.out);
+
+
+    }
+    //************************************************************************
+
+
+}
+
+
diff --git a/edu/wisc/ssec/mcidas/ABISnav.java b/edu/wisc/ssec/mcidas/ABISnav.java
new file mode 100644
index 0000000..c9ac32b
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/ABISnav.java
@@ -0,0 +1,464 @@
+//
+// ABISnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+
+/**
+ * The ABISnav class creates the ability to navigate ABIS
+ * image data.  It is a math copy of the McIDAS nvxabis.dlm
+ * code.
+ *
+ * When used with AreaFile class, set up like this:
+ *
+ * <pre><code>
+ *  AreaFile af;
+ *  try {
+ *    af = new AreaFile("/home/user/mcidas/data/AREA0001");
+ *  } catch (AreaFileException e) {
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] dir;
+ *  try { dir=af.getDir();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] nav;
+ *  try { nav=af.getNav();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  try {
+ *    ABISnav ng = new ABISnav(nav);  // XXXXnav is the specific implementation
+ *  } catch (IllegalArgumentException excp) {
+ *    System.out.println(excp);
+ *    return;
+ *  }
+ *  ng.setImageStart(dir[5], dir[6]);
+ *  ng.setRes(dir[11], dir[12]);
+ *  ng.setStart(1,1);
+ *  ......................
+ * </code></pre>
+ *
+ * @author Don Murray
+ *
+ */
+public class ABISnav extends AREAnav {
+
+  /** some constants */
+  int itype, lpsi2;
+
+  /** some more constants */
+  double h, re, a, rp, cdr, crd, deltax, deltay, rflon;
+
+  /** params */
+  int[] ioff = new int[3];
+
+  /** satellite subpoint */
+  double sublon;
+
+  /** nstepfullres and nstep */
+  double nstepfullres, nstep;
+
+  /**
+   * Set up for the real math work.  Must pass in the int array
+   * of the ABIS nav 'codicil'.
+   *
+   * @param iparms the nav block from the image file
+   * @throws IllegalArgumentException
+   *           if the nav block is not a ABIS type.
+   */
+  public ABISnav(int[] iparms) throws IllegalArgumentException {
+    this(1, iparms);
+  }
+
+  /**
+   * Set up for the real math work.  Must pass in the int array
+   * of the ABIS nav 'codicil'.
+   * @deprecated  Since ifunc must be 1, replaced with #ABISnav(int[] iparms).
+   *              If ifunc != 1, ifunc is set to 1.
+   *
+   * @param  ifunc   the function to do (always 1 for now)
+   * @param  iparms   the nav block from the image file
+   * @throws IllegalArgumentException
+   *           if the nav block is not a ABIS type.
+   */
+  public ABISnav(int ifunc, int[] iparms) throws IllegalArgumentException {
+    // Only type 1 supported
+    if (ifunc != 1) ifunc = 1;
+
+    // This is not used.  Left over from nvxabis.dlm code for Cartesian
+    // transformations
+    if (ifunc != 1) {
+      if (iparms[0] == XY) itype = 1;
+      if (iparms[0] == LL) itype = 2;
+      return;
+    }
+
+    itype = 1;
+
+    if (iparms[0] != ABIS)
+      throw new IllegalArgumentException("Invalid navigation type" +
+                                         iparms[0]);
+    if (ifunc == 1) { // these should probably be made class variables
+      for (int i = 0; i < 3; i++) {
+        ioff[i] = iparms[3 + i];
+      }
+      re = 6378.155;
+      h = 42164 - re;
+      a = 1. / 297.;
+      rp = re / (1. + a);
+      cdr = Math.PI / 180.;
+      crd = 180. / Math.PI;
+      lpsi2 = 1;
+      double angle=17.76;
+      nstep = 5535.;
+      nstepfullres = 22141.;
+
+      if (iparms[7] != 0 && iparms[8] != 0 && iparms[10] != 0) {
+          nstep = (double)iparms[7];
+          nstepfullres = (double)iparms[8];
+          angle = ((double)iparms[10])/10000.;
+      }
+
+      deltax = angle / nstep;
+      deltay = angle / nstep;
+      rflon = 0.0;
+
+      sublon = McIDASUtil.mcPackedIntegerToDouble(iparms[6]);
+    }
+  }
+
+  /**
+   * converts from satellite coordinates to latitude/longitude
+   *
+   * @param  linele      array of line/element pairs.  Where
+   *                     linele[indexLine][] is a 'line' and
+   *                     linele[indexEle][] is an element. These are in
+   *                     'file' coordinates (not "image" coordinates.)
+   *
+   * @return latlon      array of lat/lon pairs. Output array is
+   *                     latlon[indexLat][] of latitudes and
+   *                     latlon[indexLon][] of longitudes.
+   *
+   */
+  public double[][] toLatLon(double[][] linele) {
+
+    int number = linele[0].length;
+    double[][] latlon = new double[2][number];
+    //  transform line/pixel to geographic coordinates:
+    double imglinele[][] = areaCoordToImageCoord(linele);
+    double xlin, xele, xele2, xlin2, x, y, xr, yr, rs, tanx, tany, val1, val2,
+           yk;
+    double vmu, cosrf, sinrf, xt, yt, zt, teta, xfi, xla, ylat, ylon;
+
+    for (int point = 0; point < number; point++) {
+
+      xlin = imglinele[indexLine][point];
+      xele = imglinele[indexEle][point];
+      xele2 = xele / 4.;
+      xlin2 = xlin / 4.;
+      x = (nstep / 2.) - xele2;
+      y = ((nstepfullres - xlin)/4.) - ioff[2] - ioff[1] + ioff[0];
+      xr = x;
+      yr = y;
+      x = xr * lpsi2 * deltax * cdr;
+      y = yr * lpsi2 * deltay * cdr;
+      rs = re + h;
+      tanx = Math.tan(x);
+      tany = Math.tan(y);
+      val1 = 1. + tanx * tanx;
+      val2 = 1. + (tany * tany) * ((1. + a) * (1. + a));
+      yk = rs / re;
+      if ((val1 * val2) > ((yk * yk) / (yk * yk - 1))) {
+        latlon[indexLat][point] = Double.NaN;
+        latlon[indexLon][point] = Double.NaN;
+        continue;
+      }
+      vmu = (rs -
+             (re *
+              (Math.sqrt((yk * yk) -
+                         (yk * yk - 1) * val1 * val2)))) / (val1 * val2);
+      cosrf = Math.cos(rflon * cdr);
+      sinrf = Math.sin(rflon * cdr);
+      xt = (rs * cosrf) + (vmu * (tanx * sinrf - cosrf));
+      yt = (rs * sinrf) - (vmu * (tanx * cosrf + sinrf));
+      zt = vmu * tany / Math.cos(x);
+      teta = Math.asin(zt / rp);
+      xfi = (Math.atan(((Math.tan(teta)) * re) / rp)) * crd;
+      xla = -Math.atan(yt / xt) * crd;
+//
+//-- CHANGE LONGITUDE FOR CORRECT SUBPOINT
+//
+      xla = xla + sublon;
+      //if (itype == 1) {
+      ylat = xfi;
+      ylon = xla;
+      //call nllxyz(ylat,ylon,xfi,xla,z)
+      //}
+      latlon[indexLat][point] = ylat;
+      latlon[indexLon][point] = -ylon; // McIDAS uses west positive
+    }
+    return latlon;
+  }
+
+
+  /**
+   * toLinEle converts lat/long to satellite line/element
+   *
+   * @param  latlon      array of lat/long pairs. Where latlon[indexLat][]
+   *                     are latitudes and latlon[indexLon][] are longitudes.
+   *
+   * @return linele      array of line/element pairs.  Where
+   *                     linele[indexLine][] is a line and linele[indexEle][]
+   *                     is an element.  These are in 'file' coordinates
+   *                     (not "image" coordinates);
+   */
+  public double[][] toLinEle(double[][] latlon) {
+
+    int number = latlon[0].length;
+    double[][] linele = new double[2][number];
+    //  transform line/pixel to geographic coordinates:
+    double x1, y1, xfi, xla, rom, y, r1, r2, rs, reph, rpph, coslo, sinlo,
+           teta, xt, yt;
+    double zt, px, py, xr, yr;
+
+    for (int point = 0; point < number; point++) {
+      x1 = latlon[indexLat][point];
+      y1 = latlon[indexLon][point]; // seems to want east postitive
+
+      /*  not used.
+      if(itype == 1) {
+         x=vfi;
+         y=vla;
+         //call nxyzll(x,y,z,x1,y1)
+         y1=-y1;
+      }
+      */
+//
+//-- CORRECT FOR SUBLON
+//
+      y1 = y1 + sublon;
+      xfi = x1 * cdr;
+      xla = y1 * cdr;
+      rom = (re * rp) /
+            Math.sqrt(rp * rp * Math.cos(xfi) * Math.cos(xfi) +
+                      re * re * Math.sin(xfi) * Math.sin(xfi));
+      y = Math.sqrt(h * h + rom * rom -
+                    2 * h * rom * Math.cos(xfi) * Math.cos(xla));
+      r1 = y * y + rom * rom;
+      r2 = h * h;
+      if (r1 > r2) {
+        linele[indexLine][point] = Double.NaN;
+        linele[indexEle][point] = Double.NaN;
+        continue;
+      }
+      rs = re + h;
+      reph = re;
+      rpph = rp;
+      coslo = Math.cos(rflon * cdr);
+      sinlo = Math.sin(rflon * cdr);
+      teta = Math.atan((rpph / reph) * Math.tan(xfi));
+      xt = reph * Math.cos(teta) * Math.cos(xla);
+      yt = reph * Math.cos(teta) * Math.sin(xla);
+      zt = rpph * Math.sin(teta);
+      px = Math.atan((coslo * (yt - rs * sinlo) -
+                      (xt - rs * coslo) * sinlo) / (sinlo *
+                      (yt - rs * sinlo) + (xt - rs * coslo) * coslo));
+      py = Math.atan(zt *
+                     ((Math.tan(px) * sinlo - coslo) / (xt - rs * coslo)) *
+                     Math.cos(px));
+      px = px * crd;
+      py = py * crd;
+      xr = px / (deltax * lpsi2);
+      yr = py / (deltay * lpsi2);
+      xr = (nstep / 2.) - xr;
+      yr = yr + ioff[2] + ioff[1] - ioff[0];
+      xr = xr * 4;
+      yr = nstepfullres - yr * 4;
+      linele[indexLine][point] = yr;
+      linele[indexEle][point] = xr;
+    }
+    return imageCoordToAreaCoord(linele, linele);
+  }
+
+  /**
+   * converts from satellite coordinates to latitude/longitude
+   *
+   * @param  linele      array of line/element pairs.  Where
+   *                     linele[indexLine][] is a 'line' and
+   *                     linele[indexEle][] is an element. These are in
+   *                     'file' coordinates (not "image" coordinates.)
+   *
+   * @return latlon      array of lat/lon pairs. Output array is
+   *                     latlon[indexLat][] of latitudes and
+   *                     latlon[indexLon][] of longitudes.
+   *
+   */
+  public float[][] toLatLon(float[][] linele) {
+
+    int number = linele[0].length;
+    float[][] latlon = new float[2][number];
+    //  transform line/pixel to geographic coordinates:
+    float imglinele[][] = areaCoordToImageCoord(linele);
+    double xlin, xele, xele2, xlin2, x, y, xr, yr, rs, tanx, tany, val1, val2,
+           yk;
+    double vmu, cosrf, sinrf, xt, yt, zt, teta, xfi, xla, ylat, ylon;
+
+    for (int point = 0; point < number; point++) {
+
+      xlin = imglinele[indexLine][point];
+      xele = imglinele[indexEle][point];
+      xele2 = xele / 4.;
+      xlin2 = xlin / 4.;
+      x = (nstep / 2.) - xele2;
+      y = ((nstepfullres - xlin)/4.) - ioff[2] - ioff[1] + ioff[0];
+      xr = x;
+      yr = y;
+      x = xr * lpsi2 * deltax * cdr;
+      y = yr * lpsi2 * deltay * cdr;
+      rs = re + h;
+      tanx = Math.tan(x);
+      tany = Math.tan(y);
+      val1 = 1. + tanx * tanx;
+      val2 = 1. + (tany * tany) * ((1. + a) * (1. + a));
+      yk = rs / re;
+      if ((val1 * val2) > ((yk * yk) / (yk * yk - 1))) {
+        latlon[indexLat][point] = Float.NaN;
+        latlon[indexLon][point] = Float.NaN;
+        continue;
+      }
+      vmu = (rs -
+             (re *
+              (Math.sqrt((yk * yk) -
+                         (yk * yk - 1) * val1 * val2)))) / (val1 * val2);
+      cosrf = Math.cos(rflon * cdr);
+      sinrf = Math.sin(rflon * cdr);
+      xt = (rs * cosrf) + (vmu * (tanx * sinrf - cosrf));
+      yt = (rs * sinrf) - (vmu * (tanx * cosrf + sinrf));
+      zt = vmu * tany / Math.cos(x);
+      teta = Math.asin(zt / rp);
+      xfi = (Math.atan(((Math.tan(teta)) * re) / rp)) * crd;
+      xla = -Math.atan(yt / xt) * crd;
+//
+//-- CHANGE LONGITUDE FOR CORRECT SUBPOINT
+//
+      xla = xla + sublon;
+      //if (itype == 1) {
+      ylat = xfi;
+      ylon = xla;
+      //call nllxyz(ylat,ylon,xfi,xla,z)
+      //}
+      latlon[indexLat][point] = (float)ylat;
+      latlon[indexLon][point] = (float)-ylon; // McIDAS uses west positive
+    }
+    return latlon;
+  }
+
+
+  /**
+   * toLinEle converts lat/long to satellite line/element
+   *
+   * @param  latlon      array of lat/long pairs. Where latlon[indexLat][]
+   *                     are latitudes and latlon[indexLon][] are longitudes.
+   *
+   * @return linele      array of line/element pairs.  Where
+   *                     linele[indexLine][] is a line and linele[indexEle][]
+   *                     is an element.  These are in 'file' coordinates
+   *                     (not "image" coordinates);
+   */
+  public float[][] toLinEle(float[][] latlon) {
+
+    int number = latlon[0].length;
+    float[][] linele = new float[2][number];
+    //  transform line/pixel to geographic coordinates:
+    double x1, y1, xfi, xla, rom, y, r1, r2, rs, reph, rpph, coslo, sinlo,
+           teta, xt, yt;
+    double zt, px, py, xr, yr;
+
+    for (int point = 0; point < number; point++) {
+      x1 = latlon[indexLat][point];
+      y1 = latlon[indexLon][point]; // seems to want east postitive
+
+      /*  not used.
+      if(itype == 1) {
+         x=vfi;
+         y=vla;
+         //call nxyzll(x,y,z,x1,y1)
+         y1=-y1;
+      }
+      */
+//
+//-- CORRECT FOR SUBLON
+//
+      y1 = y1 + sublon;
+      xfi = x1 * cdr;
+      xla = y1 * cdr;
+      rom = (re * rp) /
+            Math.sqrt(rp * rp * Math.cos(xfi) * Math.cos(xfi) +
+                      re * re * Math.sin(xfi) * Math.sin(xfi));
+      y = Math.sqrt(h * h + rom * rom -
+                    2 * h * rom * Math.cos(xfi) * Math.cos(xla));
+      r1 = y * y + rom * rom;
+      r2 = h * h;
+      if (r1 > r2) {
+        linele[indexLine][point] = Float.NaN;
+        linele[indexEle][point] = Float.NaN;
+        continue;
+      }
+      rs = re + h;
+      reph = re;
+      rpph = rp;
+      coslo = Math.cos(rflon * cdr);
+      sinlo = Math.sin(rflon * cdr);
+      teta = Math.atan((rpph / reph) * Math.tan(xfi));
+      xt = reph * Math.cos(teta) * Math.cos(xla);
+      yt = reph * Math.cos(teta) * Math.sin(xla);
+      zt = rpph * Math.sin(teta);
+      px = Math.atan((coslo * (yt - rs * sinlo) -
+                      (xt - rs * coslo) * sinlo) / (sinlo *
+                      (yt - rs * sinlo) + (xt - rs * coslo) * coslo));
+      py = Math.atan(zt *
+                     ((Math.tan(px) * sinlo - coslo) / (xt - rs * coslo)) *
+                     Math.cos(px));
+      px = px * crd;
+      py = py * crd;
+      xr = px / (deltax * lpsi2);
+      yr = py / (deltay * lpsi2);
+      xr = (nstep / 2.) - xr;
+      yr = yr + ioff[2] + ioff[1] - ioff[0];
+      xr = xr * 4;
+      yr = nstepfullres - yr * 4;
+      linele[indexLine][point] = (float)yr;
+      linele[indexEle][point] = (float)xr;
+    }
+    return imageCoordToAreaCoord(linele, linele);
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/AREAnav.java b/edu/wisc/ssec/mcidas/AREAnav.java
new file mode 100644
index 0000000..25ff592
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/AREAnav.java
@@ -0,0 +1,742 @@
+//
+// AREAnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * The AREAnav is the superclass for AREA file navigation modules.
+ * When used with AreaFile class, set up like this:
+ *
+ * <pre><code>
+ *  AreaFile af;
+ *  try {
+ *    af = new AreaFile("/home/user/mcidas/data/AREA0001");
+ *  } catch (AreaFileException e) {
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] dir;
+ *  try { dir=af.getDir();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] nav;
+ *  try { nav=af.getNav();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  try { 
+ *    AREAnav ng = new XXXXnav(nav);  // XXXXnav is the specific implementation
+ *  } catch (IllegalArgumentException excp) {
+ *    System.out.println(excp);
+ *    return;
+ *  }
+ *  ng.setImageStart(dir[5], dir[6]);
+ *  ng.setRes(dir[11], dir[12]);
+ *  ng.setStart(0,0);
+ *  ng.setMag(1,1);
+ *  ......................
+ * </code></pre>
+ *
+ * By convention, latitudes are positive North (negative South), and
+ * longitudes are positive East (negative West).
+ *
+ * @author Tom Whittaker/Don Murray
+ * 
+ */
+public abstract class AREAnav 
+    implements java.io.Serializable
+{
+    
+    static final long serialVersionUID = 2334637524537406773L;
+    
+    /** Constant for radians to degrees conversion */
+    public final static double RADIANS_TO_DEGREES = 180./Math.PI;
+
+    /** Constant for degrees to radians conversion */
+    public final static double DEGREES_TO_RADIANS = Math.PI/180.;   
+
+    /** Code value in AREA files used to designate DMSP navigation */
+    public static final int DMSP =  0x444D5250;    
+
+    /** Code value in AREA files used to designate GMSX (GMS) navigation */
+    public static final int GMSX =  0x474D5358;    
+
+    /** Code value in AREA files used to designate GOES (GOES D-H) navigation */
+    public static final int GOES =  0x474F4553;    
+
+    /** Code value in AREA files used to designate GEOS navigation */
+    public static final int GEOS =  0x47454F53;    
+
+
+    /** Code value in AREA files used to designate GVAR (GOES I-M) navigation */
+    public static final int GVAR =  0x47564152;    
+
+    /** Code value in AREA files used to designate MOLL (Mollweide) 
+        navigation */
+    public static final int MOLL =  0x4D4F4C4C;
+
+    /** Code value in AREA files used to designate MSAT (Meteosat) 
+        navigation */
+    public static final int MSAT =  0x4D534154;
+
+    /** Code value in AREA files used to designate MSGT  navigation */
+    public static final int MSGT = 0x4D534754;
+
+    /** Code value in AREA files used to designate MSG navigation */
+    public static final int MSG  = 0x4D534720;
+
+    /** Code value in AREA files used to designate POES navigation */
+    public static final int POES =  0x5449524F;    
+
+    /** Code value in AREA files used to designate RADR (radar) navigation */
+    public static final int RADR =  0x52414452;
+
+    /** Code value in AREA files used to designate RECT (rectilinear) 
+        navigation */
+    public static final int RECT =  0x52454354;
+
+    /** Code value in AREA files used to designate PS (polar stereographic) 
+        navigation */
+    public static final int PS   =  0x50532020;
+
+    /** Code value in AREA files used to designate MERC (mercator) navigation */
+    public static final int MERC =  0x4D455243;
+
+    /** Code value in AREA files used to designate TANC (tangent cone) 
+        navigation */
+    public static final int TANC =  0x54414E43;
+
+    /** Code value in AREA files used to designate SIN (sinusoidal cone) 
+        navigation */
+    public static final int SIN =  0x53494E20;
+
+    /** Code value in AREA files used to designate LAMB (lambert conformal) 
+        navigation */
+    public static final int LAMB =  0x4C414D42;
+
+    /** Code value in AREA files used to designate Lat/Lon */
+    public static final int LALO = 0x4C414C4F;
+
+    /** Code value in AREA files used to designate Lat/Lon */
+    public static final int KALP = 0x4B414C50;
+
+    /** Code value in AREA files used to designate ABIS */
+    public static final int ABIS = 0x41424953;
+
+    /** Code value for specifying Latitude/Longitude transformations */
+    public static final int LL = 123;
+
+    /** Code value for specifying Cartesian (X/Y) transformations */
+    public static final int XY = 234;
+
+    /** "Line" index in line/element array */
+    public final int indexLine=1;
+    /** "Element" index in line/element array */
+    public final int indexEle=0;
+    /** "Latitude" index in latitude/longitude array */
+    public final int indexLat=0;
+    /** "Longitude" index in latitude/longitude array */
+    public final int indexLon=1;
+
+    private boolean isLineFlipped = false;
+    private float lineOffset = 0.0f;
+
+    // the following are ancillary info set by the set/get
+    // public methods Res, Mag, and Start
+    private float resLine = 1.f;
+    private float resElement = 1.f;
+    private float magLine = 1.f;
+    private float magElement = 1.f;
+    private float startLine = 0.f;
+    private float startElement = 0.f;
+    private float startImageLine = 0.f;
+    private float startImageElement = 0.f;
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public abstract double[][] toLatLon(double[][] linele);
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public abstract double[][] toLinEle(double[][] latlon);
+
+    /** converts from satellite coordinates to latitude/longitude.
+     * This implementation converts the input array to doubles
+     * and calls the double signature of {@link #toLatLon(double[][])}.
+     * Subclasses should implement a real float version for better
+     * performance.
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) {
+       return doubleToFloat(toLatLon(floatToDouble(linele)));
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     * This implementation converts the input array to doubles
+     * and calls the double signature of {@link #toLinEle(double[][])}.
+     * Subclasses should implement a real float version for better
+     * performance.
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) {
+       return doubleToFloat(toLinEle(floatToDouble(latlon)));
+    }
+
+    /** 
+     * Define the resolution of the image.
+     * values range from 1 (highest) to n (lowest). Note
+     * that when an image is blown down during display, this
+     * value is changed in the frame object to reflect this
+     * (rather than changing the magnification).
+     *
+     * @param resLine     is the resolution in the 'line' direction.
+     *                    This value is always > 0.
+     *
+     * @param resElement  is the resolution in the 'element'
+     *                    direction.  The value is always >0.
+     *
+     */
+    public void setRes(int resLine, int resElement) 
+    {
+        this.resLine = (float)resLine;
+        this.resElement = (float)resElement;
+    }
+
+    /** 
+     * Define the resolution of the image.
+     * values range from 1 (highest) to n (lowest). Note
+     * that when an image is blown down during display, this
+     * value is changed in the frame object to reflect this
+     * (rather than changing the magnification).
+     *
+     * @param resLine     is the resolution in the 'line' direction.
+     *                    This value is always > 0.
+     *
+     * @param resElement  is the resolution in the 'element'
+     *                    direction.  The value is always >0.
+     *
+     */
+    public void setRes(float resLine, float resElement) 
+    {
+        this.resLine = resLine;
+        this.resElement = resElement;
+    }
+
+
+    /** 
+     * define the magnification factor (in case an image
+     * was blown up when displayed).  This value is always > 0.
+     *
+     * @param  magLine   is the (line) magnification factor that might have
+     *                   been used when the image was displayed.
+     *
+     * @param  magElement is the (element) magnification factor
+     *                   that might have been used when the image was displayed.
+     *
+     */
+    public void setMag(int magLine, int magElement) 
+    {
+        this.magLine = (float)magLine;
+        this.magElement = (float)magElement;
+    }
+
+    /** define the magnification factor (in case an image
+     * was blown up when displayed).  This value is always > 0.
+     *
+     * @param  magLine   is the (line) magnification factor that might have
+     *                   been used when the image was displayed.
+     *
+     * @param  magElement is the (element) magnification factor
+     *                   that might have been used when the image was displayed.
+     *
+     */
+    public void setMag(float magLine, float magElement) 
+    {
+        this.magLine = magLine;
+        this.magElement = magElement;
+    }
+
+    /** define the starting line and element of another
+     * coordinate system -- usually a TV (note that the TV
+     * coordinates start at (1,1).
+     *
+     * @param  startLine     the starting line number in another 
+     *                       coordinate system
+     *
+     * @param  startElement  the starting element number in another 
+     *                       coordinate system
+     *
+     */
+    public void setStart(int startLine, int startElement) 
+    {
+        this.startLine = (float)startLine;
+        this.startElement = (float)startElement;
+    }
+    
+    /** define the coordinate in the [0][0] position of the image.
+     *
+     * @param  startImageLine     redefines the starting image line number
+     *                            (may be different than the signal indicated)
+     *
+     * @param  startImageElement  redefines the starting image element number
+     *                            (may be different than the signal indicated)
+     *
+     */
+    public void setImageStart(int startImageLine, int startImageElement) 
+    {
+        this.startImageLine = (float)startImageLine;
+        this.startImageElement = (float)startImageElement;
+    }
+
+    /** 
+     * specify whether the line coordinates are inverted and the line
+     * offset.
+     *
+     * @param  line  ending line number
+     *
+     */
+    public void setFlipLineCoordinates(int line) 
+    {
+        isLineFlipped = true;
+        lineOffset = (float) line;
+    }
+
+    /**
+     * Determine if navigation is using flipped coordinates
+     *
+     * @return  true if using flipped line coordinates, otherwise false
+     */
+    public boolean isFlippedLineCoordinates()
+    {
+        return isLineFlipped;
+    }
+
+    /** Get the lat,lon of the subpoint if available
+    *
+    * @return double[2] {lat, lon}
+    *
+    */
+    
+    public double[] getSubpoint() {
+      return new double[] {Double.NaN, Double.NaN};
+    }
+
+
+    /**
+     * Get the line offset for flipped coordinates
+     *
+     * @return  line offset
+     */
+    public double getLineOffset()
+    {
+        return (double) lineOffset;
+    }
+
+    /**
+     * Converts line/element array values from AREA (file) to Image 
+     * coordinates.  Creates new array instead of mucking with input.
+     *
+     * @param   linele  input line/element array in AREA coordinates
+     * @return  array in Image coordinates
+     */
+    public double[][] areaCoordToImageCoord(double[][] linele)
+    {
+        return areaCoordToImageCoord(linele, null);
+    }
+
+    /**
+     * Converts line/element array values from AREA (file) to Image 
+     * coordinates.  Creates new array if newvals is null.
+     *
+     * @param   linele  input line/element array in AREA coordinates
+     * @param   newvals return array - create a new array if null 
+     * @return  array in Image coordinates
+     */
+    public double[][] areaCoordToImageCoord(double[][] linele, double[][] newvals)
+    {
+        if (newvals == null) newvals = new double[2][linele[0].length];
+        double line;
+        for (int i = 0; i < linele[0].length; i++)
+        {
+          if (linele[indexLine][i] == linele[indexLine][i]) {
+            // account for flipped coordinates
+            line = isLineFlipped ? lineOffset - linele[indexLine][i]
+                                          : linele[indexLine][i];
+            newvals[indexLine][i] = 
+               startImageLine + (resLine * (line - startLine)) / magLine;
+          }
+          if (linele[indexEle][i] == linele[indexEle][i]) {
+            newvals[indexEle][i] = 
+               startImageElement + (resElement * (linele[indexEle][i] -
+               startElement))/magElement;
+          }
+        }
+        return newvals;
+    }
+
+    /**
+     * Converts line/element array values from Image to AREA (File) 
+     * coordinates.  Creates new array instead of mucking with input.
+     *
+     * @param   linele  input line/element array Image coordinates
+     * @return  array in AREA coordinates
+     */
+    public double[][] imageCoordToAreaCoord(double[][] linele)
+    {
+        return imageCoordToAreaCoord(linele, null);
+    } 
+
+    /**
+     * Converts line/element array values from Image to AREA (File) 
+     * coordinates.  Creates new array if newvals is null.
+     *
+     * @param   linele  input line/element array Image coordinates
+     * @param   newvals return array - create a new array if null 
+     * @return  array in AREA coordinates
+     */
+    public double[][] imageCoordToAreaCoord(double[][] linele, double[][] newvals)
+    {
+        if (newvals == null) newvals = new double[2][linele[0].length];
+        for (int i = 0; i < linele[0].length; i++)
+        {
+          if (linele[indexLine][i] == linele[indexLine][i]) {
+            newvals[indexLine][i] = startLine + 
+                ( magLine * (linele[indexLine][i] -
+                  startImageLine)) / resLine;
+            // account for flipped coordinates
+            if (isLineFlipped) newvals[indexLine][i] = 
+                         lineOffset - newvals[indexLine][i];
+          }
+          if (linele[indexEle][i] == linele[indexEle][i]) {
+            newvals[indexEle][i] = startElement + 
+                ( magElement * (linele[indexEle][i] -
+                  startImageElement)) / resElement;
+          }
+        }
+        return newvals;
+    }
+
+    /**
+     * Converts line/element array values from AREA (file) to Image 
+     * coordinates.  Creates new array instead of mucking with input.
+     *
+     * @param   linele  input line/element array in AREA coordinates
+     * @return  array in Image coordinates
+     */
+    public float[][] areaCoordToImageCoord(float[][] linele)
+    {
+        return areaCoordToImageCoord(linele, null);
+    } 
+
+    /**
+     * Converts line/element array values from AREA (file) to Image 
+     * coordinates.  Creates new array if newvals is null.
+     *
+     * @param   linele  input line/element array in AREA coordinates
+     * @param   newvals return array - create a new array if null 
+     * @return  array in Image coordinates
+     */
+    public float[][] areaCoordToImageCoord(float[][] linele, float[][] newvals)
+    {
+        if (newvals == null) newvals = new float[2][linele[0].length];
+        float line;
+        for (int i = 0; i < linele[0].length; i++)
+        {
+           // account for flipped coordinates
+           if (linele[indexLine][i] == linele[indexLine][i]) {
+             line = isLineFlipped ? lineOffset - linele[indexLine][i]
+                                         : linele[indexLine][i];
+             newvals[indexLine][i] = 
+               startImageLine + (resLine * (line - startLine)) / magLine;
+           }
+           if (linele[indexEle][i] == linele[indexEle][i]) {
+             newvals[indexEle][i] = 
+               startImageElement + (resElement * (linele[indexEle][i] -
+               startElement))/magElement;
+           }
+        }
+        return newvals;
+    }
+
+    /**
+     * Converts line/element array values from Image to AREA (File) 
+     * coordinates.  Creates new array instead of mucking with input.
+     *
+     * @param   linele  input line/element array Image coordinates
+     * @return  array in AREA coordinates
+     */
+    public float[][] imageCoordToAreaCoord(float[][] linele)
+    {
+        return imageCoordToAreaCoord(linele, null);
+    }
+
+    /**
+     * Converts line/element array values from Image to AREA (File) 
+     * coordinates.  Creates new array if newvals is null.
+     *
+     * @param   linele  input line/element array Image coordinates
+     * @param   newvals return array - create a new array if null 
+     * @return  array in AREA coordinates
+     */
+    public float[][] imageCoordToAreaCoord(float[][] linele, float[][] newvals)
+    {
+        if (newvals == null) newvals = new float[2][linele[0].length];
+        for (int i = 0; i < linele[0].length; i++)
+        {
+           if (linele[indexLine][i] == linele[indexLine][i]) {
+             newvals[indexLine][i] = startLine + 
+               ( magLine * (linele[indexLine][i] -
+                 startImageLine)) / resLine;
+             // account for flipped coordinates
+             if (isLineFlipped) newvals[indexLine][i] = 
+                          lineOffset - newvals[indexLine][i];
+           }
+           if (linele[indexEle][i] == linele[indexEle][i]) {
+             newvals[indexEle][i] = startElement + 
+               ( magElement * (linele[indexEle][i] -
+                  startImageElement)) / resElement;
+           }
+        }
+        return newvals;
+    }
+
+  /**
+   * Return an AREAnav based on the input nav block.
+   * @param navBlock  block to use
+   * @return corresponding navigation routine.
+   */
+  public static AREAnav makeAreaNav(int[] navBlock) throws McIDASException {
+     return (makeAreaNav(navBlock, null) ); 
+  }
+
+  public static AREAnav makeAreaNav(int[] navBlock, int[] auxBlock) 
+                       throws McIDASException {
+    AREAnav anav = null;
+    //System.out.println("nav = " + McIDASUtil.intBitsToString(navBlock[0]));
+    try
+    {
+        switch (navBlock[0]) {
+            case GVAR:
+                anav = new GVARnav(navBlock);
+                break;
+            case MOLL:
+                anav = new MOLLnav(navBlock);
+                break;
+            case MSAT:
+                anav = new MSATnav(navBlock);
+                break;
+            case MSG :
+                anav = new MSGnav(navBlock);
+                break;
+            case MSGT:
+                anav = new MSGTnav(navBlock);
+                break;
+            case RADR:
+                anav = new RADRnav(navBlock);
+                break;
+            case RECT:
+                anav = new RECTnav(navBlock);
+                break;
+            case GMSX:
+                anav = new GMSXnav(navBlock);
+                break;
+            case GOES:
+                anav = new GOESnav(navBlock);
+                break;
+            case GEOS:
+                anav = new GEOSnav(navBlock);
+                break;
+            case PS:
+                anav = new PSnav(navBlock);
+                break;
+            case MERC:
+                anav = new MERCnav(navBlock);
+                break;
+            case LAMB:
+                anav = new LAMBnav(navBlock);
+                break;
+            case TANC:
+                anav = new TANCnav(navBlock);
+                break;
+            case SIN:
+                anav = new SINUnav(navBlock);
+                break;
+            case LALO:
+                anav = new LALOnav(navBlock, auxBlock);
+                break;
+            case KALP:
+                anav = new KALPnav(navBlock);
+                break;
+            case ABIS:
+                anav = new ABISnav(navBlock);
+                break;
+            default:
+                throw new McIDASException(
+                     "makeAreaNav: Unknown navigation type" + navBlock[0]);
+        }
+    }
+    catch (IllegalArgumentException excp)
+    {
+        throw new McIDASException( "Wrong nav block passed to AREAnav module:"+excp);
+    }
+    return anav;
+  }
+
+  /**
+   * Determines whether or not the <code>Object</code> in question is
+   * the same as this <code>AREAnav</code>.   Right now, this returns
+   * false until we can figure out when two navigations are equal.  
+   * Subclasses could override if desired.
+   *
+   * @param obj the AREAnav in question
+   */
+  public boolean equals(Object obj)
+  {
+    // return false; WLH 13 April 2000, this broke visad.data.mcidas.TestArea
+    if (obj instanceof AREAnav)
+    {
+        // this should really be done in the subclasses, but that will
+        // have to wait for another day.
+        AREAnav nav = (AREAnav) obj;
+        return (resLine == nav.resLine &&
+                resElement == nav.resElement &&
+                magLine == nav.magLine &&
+                magElement == nav.magElement &&
+                startLine == nav.startLine &&
+                startElement == nav.startElement &&
+                startImageLine == nav.startImageLine &&
+                startImageElement == nav.startImageElement &&
+                isLineFlipped == nav.isLineFlipped &&
+                lineOffset == nav.lineOffset);
+    }
+    else
+    {
+        return false;
+    }
+  }
+
+  /**
+   * Return a <code>String</code> representation of this nav module
+   * @return wordy string.
+   */
+  public String toString() {
+    String className = getClass().getName();
+    int lastDot = className.lastIndexOf('.');
+    className = className.substring(lastDot+1);
+    return className.substring(0,className.indexOf("nav"));
+  }
+
+  /**
+   * See if we can approximate by a spline.  Subclasses can override
+   * @return true
+   */
+  public boolean canApproximateWithSpline() {
+      return true;
+  }
+
+  /**
+   * Convert arrays of floats to doubles
+   * @param value  arrays of floats
+   * @return value converted to arrays of doubles
+   */
+  public static double[][] floatToDouble(float[][] value) {
+    if (value == null) return null;
+    double[][] val = new double[value.length][];
+    for (int i=0; i<value.length; i++) {
+      if (value[i] == null) {
+        val[i] = null;
+      }
+      else {
+        val[i] = new double[value[i].length];
+        for (int j=0; j<value[i].length; j++) {
+          val[i][j] = value[i][j];
+        }
+      }
+    }
+    return val;
+  }
+
+  /**
+   * Convert arrays of doubles to floats
+   * @param value  arrays of doubles
+   * @return value converted to arrays of floats
+   */
+  public static float[][] doubleToFloat(double[][] value) {
+    if (value == null) return null;
+    float[][] val = new float[value.length][];
+    for (int i=0; i<value.length; i++) {
+      if (value[i] == null) {
+        val[i] = null;
+      }
+      else {
+        val[i] = new float[value[i].length];
+        for (int j=0; j<value[i].length; j++) {
+          val[i][j] = (float) value[i][j];
+        }
+      }
+    }
+    return val;
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/AncillaryData.java b/edu/wisc/ssec/mcidas/AncillaryData.java
new file mode 100644
index 0000000..d2831b9
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/AncillaryData.java
@@ -0,0 +1,384 @@
+//
+// AncillaryData.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.lang.String;
+
+/**
+ * AncillaryData creates an object providing access to image
+ * parameters needed to do conversion from McIDAS area format
+ * to some other image format.
+ *
+ * @version 1.6 6 Aug 1999
+ * @author Tommy Jasmin, SSEC
+ */
+ 
+public class AncillaryData {
+
+  private static final int DIR_SIZE = 64;
+  private int numLines = 0;
+  private int numElems = 0;
+  private int firstImageLine = 0;
+  private int firstImageElem = 0;
+  private int lineResolution = 0;
+  private int elemResolution = 0;
+  private int imageDate = 0;
+  private int imageTime = 0;
+  private int creationDate = 0;
+  private int creationTime = 0;
+  private int bandCount = 0;
+  private int sensorId = 0;
+  private int status = 0;
+  private int version = 0;
+  private int dataWidth = 0;
+  private int numBands = 0;
+  private int prefixSize = 0;
+  private int projectNum = 0;
+  private int bandMap = 0;
+  private int calType = 0;
+  private int navOffset = 0;
+  private int calOffset = 0;
+  private int datOffset = 0;
+  private boolean swapWords = false;
+
+  /**
+   *
+   * constructor
+   *
+   * @param dis		data input stream 
+   *
+   */
+
+  public AncillaryData (
+    DataInputStream dis
+  ) 
+    throws IOException
+
+  {
+
+    int [] directory;
+    int i;
+
+    // read in what corresponds to the McIDAS area directory
+    directory = new int[DIR_SIZE];
+    for (i = 0; i < DIR_SIZE; i++) {
+      directory[i] = dis.readInt();
+    }
+
+    // byte swap if necessary
+    if (directory[1] > 255) {
+      ConversionUtility.swap(directory,0,19);
+      // word 20 may contain characters -- if small integer, swap it...
+      if ((directory[20] & 0xffff) == 0) {
+        ConversionUtility.swap(directory,20,20);
+      }
+      ConversionUtility.swap(directory,21,23);
+      // words 24-31 contain memo field
+      ConversionUtility.swap(directory,32, 50);
+      // words 51-2 contain cal info
+      ConversionUtility.swap(directory,53,55);
+      // word 56 contains original source type (ascii)
+      ConversionUtility.swap(directory,57,63);
+      swapWords = true;
+    }
+
+    // now load the class fields with the appropriate data
+    numLines = directory[AreaFile.AD_NUMLINES];
+    numElems = directory[AreaFile.AD_NUMELEMS];
+    firstImageLine = directory[AreaFile.AD_STLINE];
+    firstImageElem = directory[AreaFile.AD_STELEM];
+    lineResolution = directory[AreaFile.AD_LINERES];
+    elemResolution = directory[AreaFile.AD_ELEMRES];
+    imageDate = directory[AreaFile.AD_IMGDATE];
+    imageTime = directory[AreaFile.AD_IMGTIME];
+    bandCount = directory[AreaFile.AD_NUMBANDS];
+    sensorId = directory[AreaFile.AD_SENSORID];
+    creationDate = directory[AreaFile.AD_CRDATE];
+    creationTime = directory[AreaFile.AD_CRTIME];
+    status = directory[AreaFile.AD_STATUS];
+    version = directory[AreaFile.AD_VERSION];
+    dataWidth = directory[AreaFile.AD_DATAWIDTH];
+    numBands = directory[AreaFile.AD_NUMBANDS];
+    prefixSize = directory[AreaFile.AD_PFXSIZE];
+    projectNum = directory[AreaFile.AD_PROJNUM];
+    bandMap = directory[AreaFile.AD_BANDMAP];
+    calType = directory[AreaFile.AD_CALTYPE];
+    navOffset = directory[AreaFile.AD_NAVOFFSET];
+    calOffset = directory[AreaFile.AD_CALOFFSET];
+    datOffset = directory[AreaFile.AD_DATAOFFSET];
+
+  }
+
+  public int getCalType() {
+    char [] calBuf = new char[4];
+    calBuf[0] = (char) ((calType >> 24) & 0xFF);
+    calBuf[1] = (char) ((calType >> 16) & 0xFF);
+    calBuf[2] = (char) ((calType >> 8) & 0xFF);
+    calBuf[3] = (char) ((calType) & 0xFF);
+    if (String.valueOf(calBuf).equals("RAW ")) {
+      System.out.println("determined input cal type is RAW");
+      return Calibrator.CAL_RAW;
+    } else if (String.valueOf(calBuf).equals("BRIT")) {
+      System.out.println("determined input cal type is BRIT");
+      return Calibrator.CAL_BRIT;
+    } else if (String.valueOf(calBuf).equals("TEMP")) {
+      System.out.println("determined input cal type is TEMP");
+      return Calibrator.CAL_TEMP;
+    } else if (String.valueOf(calBuf).equals("RAD ")) {
+      System.out.println("determined input cal type is RAD");
+      return Calibrator.CAL_RAD;
+    } else if (String.valueOf(calBuf).equals("ALB")) {
+      System.out.println("determined input cal type is ALB");
+      return Calibrator.CAL_ALB;
+    }
+    return Calibrator.CAL_NONE;
+
+  }
+
+  /**
+   *
+   * return sensor id
+   *
+   */
+
+  public int getSensorId() {
+    return sensorId;
+  }
+
+  /**
+   *
+   * return number of elements
+   *
+   */
+
+  public int getNumElements() {
+    return numElems;
+  }
+
+  /**
+   *
+   * return number of lines
+   *
+   */
+
+  public int getNumLines() {
+    return numLines;
+  }
+
+  /**
+   *
+   * return starting image line
+   *
+   */
+
+  public int getStartLine() {
+    return firstImageLine;
+  }
+
+  /**
+   *
+   * return starting image element
+   *
+   */
+
+  public int getStartElem() {
+    return firstImageElem;
+  }
+
+  /**
+   *
+   * return line resolution
+   *
+   */
+
+  public int getLineRes() {
+    return lineResolution;
+  }
+
+  /**
+   *
+   * return element resolution
+   *
+   */
+
+  public int getElemRes() {
+    return elemResolution;
+  }
+
+  /**
+   *
+   * return image date
+   *
+   */
+
+  public int getImageDate() {
+    return imageDate;
+  }
+
+  /**
+   *
+   * return image time
+   *
+   */
+
+  public int getImageTime() {
+    return imageTime;
+  }
+
+  /**
+   *
+   * return image creation date
+   *
+   */
+
+  public int getCreationDate() {
+    return creationDate;
+  }
+
+  /**
+   *
+   * return image creation time
+   *
+   */
+
+  public int getCreationTime() {
+    return creationTime;
+  }
+
+  /**
+   *
+   * return image status field
+   *
+   */
+
+  public int getStatus() {
+    return status;
+  }
+
+  /**
+   *
+   * return file format version number
+   *
+   */
+
+  public int getVersion() {
+    return version;
+  }
+
+  /**
+   *
+   * return number of bytes per pixel
+   *
+   */
+
+  public int getDataWidth() {
+    return dataWidth;
+  }
+
+  /**
+   *
+   * return number of bands/channels in image
+   *
+   */
+
+  public int getNumBands() {
+    return numBands;
+  }
+
+  /**
+   *
+   * return size in bytes of line prefix
+   *
+   */
+
+  public int getPrefixSize() {
+    return prefixSize;
+  }
+
+  /**
+   *
+   * return project number associated with image
+   *
+   */
+
+  public int getProjectNum() {
+    return projectNum;
+  }
+
+  /**
+   *
+   * return 32 bit band map
+   *
+   */
+
+  public int getBandMap() {
+    return bandMap;
+  }
+
+  /**
+   *
+   * return offset in bytes to navigation data
+   *
+   */
+
+  public int getNavOffset() {
+    return navOffset;
+  }
+
+  /**
+   *
+   * return offset in bytes to calibration data
+   *
+   */
+
+  public int getCalOffset() {
+    return calOffset;
+  }
+
+  /**
+   *
+   * return offset in bytes to image data
+   *
+   */
+
+  public int getDataOffset() {
+    return datOffset;
+  }
+
+  /**
+   *
+   * return flag indicating certain fields were byte flipped
+   *
+   */
+
+  public boolean isSwapped() {
+    return swapWords;
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/AreaDirectory.java b/edu/wisc/ssec/mcidas/AreaDirectory.java
new file mode 100644
index 0000000..6656e62
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/AreaDirectory.java
@@ -0,0 +1,622 @@
+//
+// AreaDirectory.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Vector;
+
+/** 
+ * AreaDirectory interface for the metadata of McIDAS 'area' file format 
+ * image data.
+ *
+ * @author Don Murray
+ * 
+ */
+public class AreaDirectory implements java.io.Serializable
+{
+
+  static final long serialVersionUID = -3662123383682335190L;
+  
+  private int[] dir = new int[AreaFile.AD_DIRSIZE];   // single directory
+  private Date nominalTime;      // time of the image
+  private Date startTime;      // start time of the image
+  private int[] bands;         // array of the band numbers
+  private double centerLatitude;
+  private double centerLongitude;
+  private double centerLatitudeResolution;
+  private double centerLongitudeResolution;
+  private Vector[] calInfo = null;
+  private String calType, srcType, srcTypeOrig;
+  private String calTypeUnit = null;
+  private int calTypeScaleFactor = 1;
+  private String memo;
+  private String[] sensors = {"Derived Data",
+                "Test Patterns",
+                "Graphics",
+                "Miscellaneous",
+                "PDUS Meteosat visible",
+                "PDUS Meteosat infrared",
+                "PDUS Meteosat water vapor",
+                "Radar",
+                "Miscellaneous Aircraft Data",
+                "Raw Meteosat",
+                "Composite image",
+                "Topography image",
+                "GMS visible",
+                "GMS infrared",
+                "ATS 6 visible",
+                "ATS 6 infrared",
+                "SMS-1 visible",
+                "SMS-1 infrared",
+                "SMS-2 visible",
+                "SMS-2 infrared",
+                "GOES-1 visible",
+                "GOES-1 infrared",
+                "GOES-2 visible",
+                "GOES-2 infrared",
+                "GOES-3 visible",
+                "GOES-3 infrared",
+                "GOES-4 visible (VAS)",
+                "GOES-4 infrared and water vapor (VAS)",
+                "GOES-5 visible (VAS)",
+                "GOES-5 infrared and water vapor (VAS)",
+                "GOES-6 visible",
+                "GOES-6 infrared",
+                "GOES-7 visible, Block 1 supplemental data",
+                "GOES-7 infrared",
+                "FY-2B",
+                "FY-2C",
+                "FY-2D",
+                "FY-2E",
+                "FY-2F",
+                "FY-2G",
+                "FY-2H",
+                "TIROS-N (POES)",
+                "NOAA-6",
+                "NOAA-7",
+                "NOAA-8",
+                "NOAA-9",
+                "Venus",
+                "Voyager 1",
+                "Voyager 2",
+                "Galileo",
+                "Hubble space telescope",
+                "MSG-1",
+                "MSG-2",
+                "MSG-3",
+                "Meteosat-3",
+                "Meteosat-4",
+                "Meteosat-5",
+                "Meteosat-6",
+                "Meteosat-7",
+                "",
+                "NOAA-10",
+                "NOAA-11",
+                "NOAA-12",
+                "NOAA-13",
+                "NOAA-14",
+                "NOAA-15",
+                "NOAA-16",
+                "NOAA-17",
+                "NOAA-18",
+                "NOAA-19",
+                "GOES 8 imager", // 70
+                "GOES 8 sounder",
+                "GOES 9 imager",
+                "GOES 9 sounder",
+                "GOES 10 imager",
+                "GOES 10 sounder",
+                "GOES 11 imager",
+                "GOES 11 sounder",
+                "GOES 12 imager",
+                "GOES 12 sounder",
+                "ERBE",
+                "",
+                "GMS-4",
+                "GMS-5",
+                "GMS-6",
+                "GMS-7",
+                "GMS-8",
+                "DMSP F-8",
+                "DMSP F-9",
+                "DMSP F-10",
+                "DMSP F-11",
+                "DMSP F-12",
+                "DMSP F-13",
+                "DMSP F-14",
+                "DMSP F-15", // 94
+                "FY-1B",
+                "FY-1C",
+                "FY-1D",
+                "", 
+                "",
+                "",
+                "TERRA L1B", // 101
+                "TERRA CLD",
+                "TERRA GEO",
+                "TERRA-AER",
+                "",
+                "TERRA TOP",
+                "TERRA ATM",
+                "TERRA GUE",
+                "TERRA RET",
+                "",
+                "AQUA L1B", // 111
+                "AQUA CLD",
+                "AQUA GEO",
+                "AQUA AER",
+                "",
+                "AQUA TOP",
+                "AQUA ATM",
+                "AQUA GUE",
+                "AQUA RET",
+                "", // 120
+                "", "", "", "", "", "", "", "","", "", // 130
+                "", "", "", "", "", "", "", "","", "", // 140
+                "", "", "", "", "", "", "", "","", "", // 150
+                "", "", "", "", "", "", "", "","", 
+                "TERRA NDVI", // 160
+                "TERRA CREF", 
+                "", "", "", "", "", "", "","", 
+                "AQUA NDVI", // 170
+                "AQUA CREF", 
+                "", "", "", "", "", "", "","", 
+                "GOES 13 imager", // 180
+                "GOES 13 sounder",
+                "GOES 14 imager",
+                "GOES 14 sounder",
+                "GOES 15 imager",
+                "GOES 15 sounder",
+                "GOES 16 imager",
+                "GOES 16 sounder",
+                "", // 188
+                "","", // 190
+                "","","","",
+                "DMSP F-16", // 195
+                "DMSP F-17",
+                "","","", // 199
+                "AIRS L1B", // 200
+                "","","","","","","","","",
+                "AMSR-E L1B", // 210
+                "AMSR-E RAIN",
+                "","","","","","","","","", // 220
+                "","","","","","","","","",
+                "Kalpana-1", // 230
+                "","","","","","","","","",
+                "MetOp-A", // 240
+                "MetOp-B",
+                "MetOp-C",
+                "","","","","","","",
+                "COMS-1", // 250
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","",
+                "NPP-VIIRS", // 300
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","",
+                "METEOSAT-11", // 354
+                "","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","","",
+                "","","","","","","","","",
+                "South Pole Composites", // 400
+                "North Pole Composites", 
+                "","","","","","","","",
+                "Megha-Tropiques", // 410
+                ""};
+
+  /**
+   * Create an AreaDirectory from the raw block of data of
+   * an AreaFile.  Byte-flipping will be handled.
+   *
+   * @param  dirblock   the integer block
+   *
+   * @exception  AreaFileException   not a valid directory
+   */
+  public AreaDirectory(int[] dirblock)
+    throws AreaFileException
+  {
+    centerLatitude =  Double.NaN;
+    centerLongitude =  Double.NaN;
+    centerLatitudeResolution =  Double.NaN;
+    centerLongitudeResolution =  Double.NaN;
+
+    if (dirblock.length != AreaFile.AD_DIRSIZE)
+      throw new AreaFileException("Directory is not the right size");
+    dir = dirblock;
+    // see if the directory needs to be byte-flipped
+    if (dir[AreaFile.AD_VERSION] != AreaFile.VERSION_NUMBER) 
+    {
+      McIDASUtil.flip(dir,0,19);
+      // check again to make sure we got the right type of file
+      if (dir[AreaFile.AD_VERSION] != AreaFile.VERSION_NUMBER)
+        throw new AreaFileException(
+          "Invalid version number - probably not an AREA file");
+      // word 20 may contain characters -- if small int, flip it
+      if ( (dir[20] & 0xffff) == 0) McIDASUtil.flip(dir,20,20);
+      McIDASUtil.flip(dir,21,23);
+      // words 24-31 contain memo field
+      McIDASUtil.flip(dir,32,50);
+      // words 51-2 contain cal info
+      McIDASUtil.flip(dir,53,55);
+      // word 56 contains original source type (ascii)
+      McIDASUtil.flip(dir,57,63);
+    }
+  /*   Debug
+    for (int i = 0; i < AreaFile.AD_DIRSIZE; i++)
+    {
+      System.out.println("dir[" + i +"] = " + dir[i]);
+    }
+  */
+    // Pull out some of the important information
+    nominalTime = new Date(1000* McIDASUtil.mcDayTimeToSecs(
+          dir[AreaFile.AD_IMGDATE], 
+            dir[AreaFile.AD_IMGTIME]));
+
+    if (dir[AreaFile.AD_STARTDATE] == 0 &&
+        dir[AreaFile.AD_STARTTIME] == 0)
+                             startTime = nominalTime;
+    else
+      startTime = new Date( 1000* 
+              McIDASUtil.mcDayTimeToSecs(
+                dir[AreaFile.AD_STARTDATE],
+                dir[AreaFile.AD_STARTTIME]));
+    
+
+    // create the bands array.  Might be more bits set than bands, though cuz of the AVHRR band 3a/b thingy
+
+    int numbands = dir[AreaFile.AD_NUMBANDS];
+    bands = new int[numbands];
+    int j = 0;
+    for (int i=0; i<64; i++) {   // band bits in two consequtive words
+      int bandmask = 1 << ( i%32 );
+      if ( (bandmask & dir[AreaFile.AD_BANDMAP + (i/32) ]) == bandmask) {
+        bands[j] = i+1 ;
+        j++;
+        if (j >= numbands) break;  // done, one way or the other...
+      }
+
+    }
+
+
+    // get memo field
+    int[] memoArray = new int[8];
+    System.arraycopy(dir, 24, memoArray, 0, memoArray.length);
+    memo = McIDASUtil.intBitsToString(memoArray);
+    calType = McIDASUtil.intBitsToString(dir[AreaFile.AD_CALTYPE]).trim();
+    srcType = McIDASUtil.intBitsToString(dir[AreaFile.AD_SRCTYPE]).trim();
+    srcTypeOrig = (dir[AreaFile.AD_SRCTYPEORIG] == 0) 
+        ? srcType
+        : McIDASUtil.intBitsToString(dir[AreaFile.AD_SRCTYPEORIG]).trim();
+    calTypeUnit = 
+       (dir[AreaFile.AD_CALTYPEUNIT] == 0 || 
+        dir[AreaFile.AD_CALTYPEUNIT] == McIDASUtil.MCMISSING) 
+        ? null
+        : McIDASUtil.intBitsToString(dir[AreaFile.AD_CALTYPEUNIT]).trim();
+    //System.out.println("AD.calTypeUnit = >"+calTypeUnit+"<");
+    if (calTypeUnit != null && calTypeUnit.equals("")) calTypeUnit = null;
+    calTypeScaleFactor = 
+      (dir[AreaFile.AD_CALTYPESCALE] == 0 || 
+       dir[AreaFile.AD_CALTYPESCALE] == McIDASUtil.MCMISSING) 
+         ? 1 : dir[AreaFile.AD_CALTYPESCALE];
+  }
+
+  /**
+   * Create an AreaDirectory from another AreaDirectory object.
+   *
+   * @param  directory   the source AreaDirectory
+   *
+   * @exception  AreaFileException   not a valid directory
+   */
+  public AreaDirectory(AreaDirectory directory)
+    throws AreaFileException
+  {
+    this(directory.getDirectoryBlock());
+  }
+  
+  /**
+   * Return a specific value from the directory
+   *
+   * @param  pointer   part of the directory you want returned.  
+   *           Use AreaFile static fields as pointers.
+   *
+   * @exception  AreaFileException  invalid pointer
+   */
+  public int getValue(int pointer)
+    throws AreaFileException
+  {
+    if (pointer < 0 || pointer > AreaFile.AD_DIRSIZE)
+      throw new AreaFileException("Invalid pointer " + pointer);
+    return dir[pointer];
+  }
+
+  /**
+   * Get the raw directory block
+   *
+   * @return integer array of the raw directory values
+   */
+  public int[] getDirectoryBlock()
+  {
+    return dir;
+  }
+
+  /**
+   * returns the nominal time of the image
+   *
+   * @return the nominal time as a Date
+   */
+  public Date getNominalTime()
+  {
+    return nominalTime;
+  }
+
+  /**
+   * returns the nominal time of the image
+   *
+   * @return the nominal time as a Date
+   */
+  public Date getStartTime()
+  {
+    return startTime;
+  }
+
+  /**
+   * returns the number of bands in the image
+   *
+   * @return number of bands
+   */
+  public int getNumberOfBands()
+  {
+    return dir[AreaFile.AD_NUMBANDS];
+  }
+
+  /**
+   * returns the number of lines in the image
+   *
+   * @return line number
+   */
+  public int getLines()
+  {
+    return dir[AreaFile.AD_NUMLINES];
+  }
+
+  /**
+   * returns the number of elements in the image
+   *
+   * @return number of elements
+   */
+  public int getElements()
+  {
+    return dir[AreaFile.AD_NUMELEMS];
+  }
+
+  /**
+   * set the band calibration info (Vector)
+   * array order is identical to bands array, each Vector
+   * element is a pair of String values: first, the code value and
+   * second the descriptive name.
+   *
+   * @param v	the list of calibration parameters
+   */
+   public void setCalInfo(Vector[] v) {
+     calInfo = v;
+   }
+
+   /** get the valid band calibration information
+   *
+   * @return array of Vectors of Strings of calibration info
+   */
+   public Vector[] getCalInfo() {
+     return calInfo;
+   }
+
+
+  /**
+   * returns the bands in each of the images
+   *
+   * @return a array of bands 
+   */
+  public int[] getBands()
+  {
+    return bands;
+  }
+
+  /**
+   * Returns memo field of the directory
+   *
+   * @return string representing the memo
+   */
+  public String getMemoField()
+  {
+    return memo;
+  }
+
+  /**
+   * Returns the sensor type
+   *
+   * @return string representing the sensor type
+   */
+  public String getSensorType()
+  {
+    return sensors[dir[AreaFile.AD_SENSORID]];
+  }
+  
+  public int getSensorID() {
+    return dir[AreaFile.AD_SENSORID];
+  }
+
+  /**
+   * Returns the source type
+   *
+   * @return string representing the cal type
+   */
+  public String getSourceType()
+  {
+    String r = srcType;
+    if (r.equalsIgnoreCase("VISR")) r=srcTypeOrig;
+    return r;
+  }
+
+  /**
+   * Returns the calibration type
+   *
+   * @return string representing the cal type
+   */
+  public String getCalibrationType()
+  {
+    return calType;
+  }
+
+  /** get Latutide at center of image
+  *
+  * @return value of latitude; if not available, return Double.NaN
+  */
+  public double getCenterLatitude() {
+    return centerLatitude;
+  }
+
+  /** set Latitude at center of image
+  *
+  * @param lat	value of latitude at center point of image
+  */
+  public void setCenterLatitude(double lat) {
+    centerLatitude = lat;
+  }
+
+  /** get longitude at center of image
+  *
+  * @return value of longitude; if not available, return Double.NaN.
+  */
+  public double getCenterLongitude() {
+    return centerLongitude;
+  }
+
+  /** set Longitude at center of image
+  *
+  * @param lon	value of Longitude at center point of image
+  */
+  public void setCenterLongitude(double lon) {
+    centerLongitude = lon;
+  }
+
+  /** get Latutide-wise resolution at center of image
+  *
+  * @return value of resolution (usually in KM) .  If not available,
+  * value is Double.NaN.
+  */
+  public double getCenterLatitudeResolution() {
+    return centerLatitudeResolution;
+  }
+
+  /** set Latitude-wise resolution at center of image
+   *
+   * @param res	value of latitude-wise resolution at center point of image
+   */
+  public void setCenterLatitudeResolution(double res) {
+    centerLatitudeResolution = res;
+  }
+
+  /** get longitude-wise resolution at center of image
+  *
+  * @return value of longitude-wise resolution (usually in KM)
+  * return Double.NaN if not available.
+  */
+  public double getCenterLongitudeResolution() {
+    return centerLongitudeResolution;
+  }
+
+  /** set Longitude-wise resolution at center of image
+   *
+   * @param res	value of Longitude-wise resolution at center point of image
+   */
+  public void setCenterLongitudeResolution(double res) {
+    centerLongitudeResolution = res;
+  }
+
+  /** 
+   * Get the string representing the calibration unit
+   *
+   * @return name of calibration unit
+   */
+  public String getCalibrationUnitName() {
+    return calTypeUnit;
+  }
+
+  /** 
+   * Get the scaling factor of the values for this calibration type
+   *
+   * @return scaling factor
+   */
+  public int getCalibrationScaleFactor() {
+    return calTypeScaleFactor;
+  }
+
+  /**
+   * Check the equality of the object in question with this.
+   * @param o object in question
+   */
+  public boolean equals(Object o) {
+     if (!(o instanceof AreaDirectory)) return false;
+     AreaDirectory that = (AreaDirectory) o;
+     return (this == that ||
+            java.util.Arrays.equals(
+                 getDirectoryBlock(), that.getDirectoryBlock()));
+  }
+
+
+  /**
+   * Prints out a formatted listing of the directory info
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer();
+    SimpleDateFormat sdf = new SimpleDateFormat();
+    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+    sdf.applyPattern("yyyy-MMM-dd  HH:mm:ss");
+    buf.append("  ");
+    buf.append(sdf.format(nominalTime, 
+      new StringBuffer(), new FieldPosition(0)).toString());
+    buf.append("  ");
+    buf.append(Integer.toString(getLines()));
+    buf.append("  ");
+    buf.append(Integer.toString(getElements()));
+    buf.append("     ");
+    //buf.append(" "+bands.length);
+    for (int i = 0; i < bands.length; i++) buf.append(bands[i]);
+    return buf.toString();
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/AreaDirectoryList.java b/edu/wisc/ssec/mcidas/AreaDirectoryList.java
new file mode 100644
index 0000000..3997767
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/AreaDirectoryList.java
@@ -0,0 +1,510 @@
+//
+// AreaDirectoryList.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import edu.wisc.ssec.mcidas.adde.AddeURLConnection;
+
+/** 
+ * AreaDirectoryList interface for McIDAS 'area' file format image data.
+ * Provides access to a list of one or more AreaDirectoy objects.
+ *
+ * @author Don Murray
+ * 
+ */
+public class AreaDirectoryList
+{
+  private static boolean debug = false;
+
+  // load protocol for ADDE URLs
+  // See java.net.URL for explanation of URL handling
+  static 
+  {
+    try 
+    {
+      String handlers = System.getProperty("java.protocol.handler.pkgs");
+      String newProperty = null;
+      if (handlers == null)
+        newProperty = "edu.wisc.ssec.mcidas";
+      else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+        newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+      if (newProperty != null)  // was set above
+        System.setProperty("java.protocol.handler.pkgs", newProperty);
+    }
+    catch (Exception e)
+    {
+      System.out.println(
+        "Unable to set System Property: java.protocol.handler.pkgs"); 
+    }
+  }
+
+  private boolean flipwords = false;
+  private DataInputStream inputStream;  // input stream
+  private int status=0;         // read status
+  private URLConnection urlc;       // URL connection
+  private boolean isADDE = false;     // true if ADDE request
+  private int[] dir;          // single directory
+  private Date[] nominalTimes;      // array of dates
+  private int[] bands;          // array of bands
+  private int[] lines;          // array of lines
+  private int[] elements;         // array of elements
+  private ArrayList dirs;         // list of directories
+  private int numDirs = 0;        // number of directories
+  
+  /**
+   * creates an AreaDirectory object that allows reading
+   * of McIDAS 'area' file format image data.  allows reading
+   * either from a disk file, or a server via ADDE.  
+   *
+   * @param imageSource the file name or ADDE URL to read from
+   *
+   * @exception AreaFileException if file cannot be opened
+   *
+   */
+  public AreaDirectoryList(String imageSource) 
+    throws AreaFileException 
+  {
+   
+    // try as a disk file first
+    try 
+    {
+      inputStream = 
+        new DataInputStream (
+          new BufferedInputStream(
+            new FileInputStream(imageSource), 2048));
+    } 
+    catch (IOException eIO) 
+    {
+      // if opening as a file failed, try as a URL
+      URL url;
+      try 
+      {
+        url = new URL(imageSource);
+        urlc = url.openConnection();
+        InputStream is = urlc.getInputStream();
+        inputStream = 
+          new DataInputStream(
+            new BufferedInputStream(is));
+      }
+      catch (Exception e) 
+      {
+        throw new AreaFileException("Error opening AreaFile: " + e);
+      }
+      if (url.getProtocol().equalsIgnoreCase("adde")) isADDE = true;
+    }
+    readDirectory();
+  }
+ 
+
+  /**
+   * creates an AreaDirectory object that allows reading
+   * of the directory of McIDAS 'area' files from a URL
+   *
+   * @param url - the URL to go after
+   *
+   * @exception AreaFileException if file cannot be opened
+   *
+   */
+  public AreaDirectoryList(URL url) 
+    throws AreaFileException 
+  {
+    try 
+    { 
+      inputStream = 
+        new DataInputStream(
+          new BufferedInputStream(url.openStream()));
+    } 
+    catch (IOException e) 
+    {
+      throw new AreaFileException("Error opening URL for AreaFile:"+e);
+    }
+    readDirectory();
+  }
+  
+  /** 
+   * Read the directory information for an area file or area directory
+   * record.
+   *
+   * @exception   AreaFileException  if there is a problem reading 
+   *                   any portion of the metadata.
+   *
+   */
+  private void readDirectory() throws AreaFileException {
+
+    double resolutionLat = Double.NaN;
+    double resolutionLon = Double.NaN;
+    double centerLat = Double.NaN;
+    double centerLon = Double.NaN;
+    int band = 0;
+    String calname = " ", caldesc = " ";
+
+    dirs = new ArrayList();
+    int numBytes = 
+      (isADDE) 
+        ? ((AddeURLConnection) urlc).getInitialRecordSize() 
+        : AreaFile.AD_DIRSIZE;
+    while (numBytes > 0) {
+      try {
+        dir = new int[AreaFile.AD_DIRSIZE];
+  
+        // skip first int which is dataset area number if ADDE request
+        if (isADDE) {
+          int areaNumber = inputStream.readInt();
+          if (debug) System.out.println("Area number = " + areaNumber);
+        }
+    
+        for (int i=0; i < AreaFile.AD_DIRSIZE; i++) {
+          dir[i] = inputStream.readInt();
+        }
+
+        if (!isADDE) dir[0] = 0;
+    
+        // see if the directory needs to be byte-flipped
+        if (dir[AreaFile.AD_VERSION] > 255 || flipwords) {
+          flipwords = true;
+          McIDASUtil.flip(dir,0,19);
+          // word 20 may contain characters -- if small int, flip it
+          if ( (dir[20] & 0xffff) == 0) McIDASUtil.flip(dir,20,20);
+          McIDASUtil.flip(dir,21,23);
+          // words 24-31 contain memo field
+          McIDASUtil.flip(dir,32,50);
+          // words 51-2 contain cal info
+          McIDASUtil.flip(dir,53,55);
+          // word 56 contains original source type (ascii)
+          McIDASUtil.flip(dir,57,63);
+        }
+  
+
+        if (debug) {
+        for (int i = 0; i < AreaFile.AD_DIRSIZE; i++)
+          {
+            System.out.println("dir[" + i +"] = " + dir[i]);
+          }
+        }
+    
+        AreaDirectory ad = new AreaDirectory(dir);
+        int[] bands = ad.getBands();
+        int numBands = ad.getNumberOfBands();
+        // make a Vector to hold the band calibration info (if available)
+        Vector [] calInfo = new Vector[numBands];
+        for (int k=0; k<numBands; k++) {
+          calInfo[k] = new Vector();
+        }
+  
+        if (!isADDE) {
+          numBytes = 0;
+        } else {
+          // last word in trailer is the number of bytes for the
+          // next record so we need to read that
+          /*
+          int skipBytesCount = numBytes - AreaFile.AD_DIRSIZE*4 - 4;
+          inputStream.skipBytes(skipBytesCount);
+          */
+          /* */
+          int numCards = dir[AreaFile.AD_DIRSIZE -1];
+          if (debug) System.out.println("Number of comment cards = " + numCards);
+          for (int i = 0; i < numCards; i++)
+          {
+            byte[] card = new byte[80];
+            int count = 0;
+            boolean prevBlank = true;
+            for (int j = 0; j < 80; j++) {
+              byte b = inputStream.readByte();
+              if (b == ' ') {
+                if (!prevBlank) {
+                card[count++] = b;
+                prevBlank = false;
+                }
+                prevBlank = true;
+              }  else {
+                card[count++] = b;
+                prevBlank = false;
+              }
+            }
+            String cd = new String(card,0,count).trim();
+
+            if (debug) System.out.println("card["+i+"] = " + cd);
+
+            if (cd.indexOf("Center latitude") > -1) {
+              int m = cd.indexOf("=");
+              if (m > 0) {
+                centerLat = Double.valueOf(
+                       cd.substring(m+1).trim()).doubleValue();
+              } else {
+                centerLat = Double.NaN;
+              }
+            } else if (cd.indexOf("Center longitude") > -1) {
+              int m = cd.indexOf("=");
+              if (m > 0) {
+                centerLon = - Double.valueOf(
+                       cd.substring(m+1).trim()).doubleValue();
+              } else {
+                centerLon = Double.NaN;
+              }
+            } else if (cd.indexOf("Computed Latitude") > -1) {
+              int m = cd.indexOf("=");
+              if (m > 0) {
+                resolutionLat = Double.valueOf(
+                       cd.substring(m+1).trim()).doubleValue();
+              } else {
+                resolutionLat = Double.NaN;
+
+              }
+            } else if (cd.indexOf("Computed Longitude") > -1) {
+              int m = cd.indexOf("=");
+              if (m > 0) {
+                resolutionLon = Double.valueOf(
+                       cd.substring(m+1).trim()).doubleValue();
+              } else {
+                resolutionLon = Double.NaN;
+              }
+            } else if (cd.indexOf("Valid calibration unit") > -1) {
+              int m = cd.indexOf("=");
+              if (m > 0) {
+                String cdd = cd.replace('"',' ');
+                StringTokenizer st = new StringTokenizer(cdd," ");
+                int n = st.countTokens();
+                band = 0;
+                calname = " ";
+                caldesc = " ";
+                boolean gotit = false;
+                for (int k=0; k<n; k++) {
+                  if (st.nextToken().trim().equals("band")) {
+                  gotit = true;
+                  break;
+                  }
+                }
+
+                if (gotit) {
+                  band = Integer.parseInt(st.nextToken().trim());
+                  st.nextToken();  // skip = sign
+                  calname = st.nextToken();
+                  caldesc = calname;
+                  if (st.hasMoreTokens()) {
+                    StringBuffer buf = new StringBuffer();
+                    while (st.hasMoreTokens()) {
+                       buf.append(st.nextToken());
+                       buf.append(" ");
+                    }
+                    caldesc = buf.toString().trim();
+                  }
+                  for (int k=0; k<numBands; k++) {
+                    if (band == bands[k]) {
+                      calInfo[k].addElement(calname);
+                      calInfo[k].addElement(caldesc);
+                    }
+                  }
+                }
+              }
+              
+            } 
+          }
+          ad.setCenterLatitude(centerLat);
+          ad.setCenterLongitude(centerLon);
+          ad.setCenterLatitudeResolution(resolutionLat);
+          ad.setCenterLongitudeResolution(resolutionLon);
+          ad.setCalInfo(calInfo);
+
+          /* */
+          numBytes = inputStream.readInt();
+          if (debug) System.out.println("Bytes in next record = " + numBytes);
+        }
+        dirs.add(ad);
+      }
+      catch (IOException e) {
+        status = -1;
+        throw new AreaFileException(
+          "Error reading Area directory:" + e);
+      }
+      status = 1;
+      numDirs++;
+    } 
+  }
+  
+  /** returns the directory blocks for the requested images,
+   *  sorted into an array of AreaDirectories by time and
+   *  then bands as the second dimenison, if multiple directories
+   *  with the same ADDE position number are returned.
+   */
+
+   public AreaDirectory[][] getSortedDirs() throws AreaFileException {
+    if (status <= 0 || dirs.size() <= 0) {
+      throw new AreaFileException( "Error reading directory information");
+    }
+
+    // first gather up the positions and nominal times
+    Date[] dtg = new Date[numDirs];
+    int[] pos = new int[numDirs];
+    int[] insitu = new int[numDirs];
+    ArrayList al = new ArrayList();
+    ArrayList alt = null;
+
+    for (int i=0; i<numDirs; i++) {
+      AreaDirectory ad = (AreaDirectory) dirs.get(i);
+      dtg[i] = ad.getNominalTime();
+      pos[i] = ad.getValue(0);
+      insitu[i] = i;
+    }
+
+    Date ddd = null;
+    // now sort the values by date
+    for (int i=0; i<numDirs; i++) {
+      int swap = i;
+      for (int k=i+1; k<numDirs; k++) {
+        if (dtg[swap].compareTo(dtg[k]) > 0) swap = k;
+      }
+      if (swap != i) {
+        Date dtgt = dtg[i];
+        dtg[i] = dtg[swap];
+        dtg[swap] = dtgt;
+        int intt = pos[i];
+        pos[i] = pos[swap];
+        pos[swap] = intt;
+        intt = insitu[i];
+        insitu[i] = insitu[swap];
+        insitu[swap] = intt;
+      }
+
+      // look to see if date-time has changed
+      if ( (ddd == null) || (ddd.compareTo(dtg[i]) != 0)) {
+
+         if (ddd != null) al.add(alt);  // add the previous one if there
+
+         ddd = dtg[i];
+         alt = new ArrayList();
+      }
+
+      alt.add(dirs.get(insitu[i]) );  // copy the AreaDirectory entry over
+
+    }
+
+    // catch the very last one...
+    if (alt != null && alt.size() > 0) al.add(alt);
+
+    int num = al.size();
+
+    if (num == 0) return null;
+
+    // now make an array of AreaDirectory objects for return...
+    
+    AreaDirectory[][] ada = new AreaDirectory[num][];
+
+    for (int i=0; i<num; i++) {
+       alt = (ArrayList) al.get(i);
+       int knum = alt.size();
+       ada[i] = new AreaDirectory[knum];
+       for (int k=0; k<knum; k++) {
+         ada[i][k] = (AreaDirectory)alt.get(k);
+       }
+    }
+
+    return ada;
+   }
+
+
+
+  /** 
+   * returns the directory blocks for the requested images.  
+   * see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+   *    McIDAS Programmer's Manual</A> for information on the parameters
+   *    for each value.
+   *
+   * @return a ArrayList of AreaDirectorys 
+   *
+   * @exception AreaFileException if there was a problem
+   *      reading the directory
+   *
+   */
+  public ArrayList getDirs() 
+    throws AreaFileException 
+  {
+    if (status <= 0 || dirs.size() <= 0) 
+    {
+      throw new AreaFileException(
+        "Error reading directory information");
+    }
+    return dirs;
+  }
+
+  /**
+   * Prints out a formatted listing of the directory info
+   */
+  public String toString()
+  {
+    if (status <=0 || numDirs <= 0)
+    {
+      return new String("No directory information available");
+    }
+    StringBuffer sb = new StringBuffer();
+    sb.append("    Date         Time    Lin  Ele  Bands \n");
+    sb.append("  -------       -------  ---  ---  --------\n");
+    for (int i = 0; i < dirs.size(); i++)
+    {
+      sb.append( ((AreaDirectory) dirs.get(i)).toString());
+      sb.append("\n");
+    }
+    return sb.toString();
+  }
+
+  public static void main(String[] args)
+    throws Exception
+  {
+    if (args.length == 0)
+    {
+      System.out.println("Must supply a path or ADDE request to images");
+      System.exit(1);
+    }
+    AreaDirectoryList adl = new AreaDirectoryList(args[0]);
+    System.out.println(adl.toString());
+
+    // print out test of getSortedDirs()
+    AreaDirectory[][] ada = adl.getSortedDirs();
+    for (int i=0; i<ada.length; i++) {
+      Date dd = ada[i][0].getNominalTime();
+      System.out.print(dd+" ");
+      for (int k=0; k<ada[i].length; k++) {
+        int[] bands = ada[i][k].getBands();
+
+        for (int b=0; b<bands.length; b++) {
+          System.out.print(" "+bands[b]);
+        }
+      }
+      System.out.println(" ");
+    }
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/AreaFile.java b/edu/wisc/ssec/mcidas/AreaFile.java
new file mode 100644
index 0000000..25af24a
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/AreaFile.java
@@ -0,0 +1,1678 @@
+//
+// AreaFile.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+
+import java.applet.Applet;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.Raster;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import edu.wisc.ssec.mcidas.adde.GetAreaGUI;
+
+
+/**
+ * AreaFile interface with McIDAS 'area' file format image data.
+ *
+ * <p>This will allow 'area' format data to be read from disk; the
+ * navigation block is made available (see GVARnav for example).</p>
+ *
+ * <p>Calibration is handled via classes that implement the {@link Calibrator}
+ * interface.</p>
+ *
+ * <p>This implementation does not check the 'valcode' on each line.</p>
+ *
+ * @author Tom Whittaker, SSEC
+ * @author Tommy Jasmin, SSEC
+ *
+ */
+
+public class AreaFile implements java.io.Serializable {
+
+  /**  */
+  static final long serialVersionUID = 3145724093430859967L;
+
+  // indeces used by this and client classes
+
+  /** AD_STATUS - old status field, now used as position num in ADDE */
+  public static final int AD_STATUS = 0;
+
+  /** AD_VERSION - McIDAS area file format version number */
+  public static final int AD_VERSION = 1;
+
+  /** AD_SENSORID - McIDAS sensor identifier */
+  public static final int AD_SENSORID = 2;
+
+  /** AD_IMGDATE - nominal year and day of the image, YYYDDD format */
+  public static final int AD_IMGDATE = 3;
+
+  /** AD_IMGTIME - nominal time of the image, HHMMSS format */
+  public static final int AD_IMGTIME = 4;
+
+  /** AD_STLINE - upper left image line coordinate */
+  public static final int AD_STLINE = 5;
+
+  /** AD_STELEM - upper left image element coordinate */
+  public static final int AD_STELEM = 6;
+
+  /** AD_NUMLINES - number of lines in the image */
+  public static final int AD_NUMLINES = 8;
+
+  /** AD_NUMELEMS - number of data points per line */
+  public static final int AD_NUMELEMS = 9;
+
+  /** AD_DATAWIDTH - number of bytes per data point */
+  public static final int AD_DATAWIDTH = 10;
+
+  /** AD_LINERES - data resolution in line direction */
+  public static final int AD_LINERES = 11;
+
+  /** AD_ELEMRES - data resolution in element direction */
+  public static final int AD_ELEMRES = 12;
+
+  /** AD_NUMBANDS - number of spectral bands, or channels, in image */
+  public static final int AD_NUMBANDS = 13;
+
+  /** AD_PFXSIZE - length in bytes of line prefix section */
+  public static final int AD_PFXSIZE = 14;
+
+  /** AD_PROJNUM - SSEC project number used in creating image */
+  public static final int AD_PROJNUM = 15;
+
+  /** AD_CRDATE - year and day image was created, CCYYDDD format */
+  public static final int AD_CRDATE = 16;
+
+  /** AD_CRTIME - time image was created, HHMMSS format */
+  public static final int AD_CRTIME = 17;
+
+  /** AD_BANDMAP - spectral band map, bit set for each of 32 bands present */
+  public static final int AD_BANDMAP = 18;
+
+  /** AD_DATAOFFSET - byte offset to start of data block */
+  public static final int AD_DATAOFFSET = 33;
+
+  /** AD_NAVOFFSET - byte offset to start of navigation block */
+  public static final int AD_NAVOFFSET = 34;
+
+  /** AD_VALCODE - validity code */
+  public static final int AD_VALCODE = 35;
+
+  /** AD_STARTDATE - actual image start year and Julian day, yyyddd format */
+  public static final int AD_STARTDATE = 45;
+
+  /**
+   * AD_STARTTIME - actual image start time, hhmmss;
+   *  in milliseconds for POES data
+   */
+  public static final int AD_STARTTIME = 46;
+
+  /** AD_STARTSCAN - starting scan number (sensor based) of image */
+  public static final int AD_STARTSCAN = 47;
+
+  /** AD_DOCLENGTH - length in bytes of line prefix documentation */
+  public static final int AD_DOCLENGTH = 48;
+
+  /** AD_CALLENGTH - length in bytes of line prefix calibration information */
+  public static final int AD_CALLENGTH = 49;
+
+  /** AD_LEVLENGTH - length in bytes of line prefix level section */
+  public static final int AD_LEVLENGTH = 50;
+
+  /** AD_SRCTYPE - McIDAS source type (ascii, satellite specific) */
+  public static final int AD_SRCTYPE = 51;
+
+  /** AD_CALTYPE - McIDAS calibration type (ascii, satellite specific) */
+  public static final int AD_CALTYPE = 52;
+
+  /** AD_AVGSMPFLAG - data is averaged (1), or sampled (0) */
+  public static final int AD_AVGSMPFLAG = 53;
+
+  /** AD_SRCTYPEORIG - original source type (ascii, satellite specific) */
+  public static final int AD_SRCTYPEORIG = 56;
+
+  /** AD_CALTYPEUNIT - calibration unit */
+  public static final int AD_CALTYPEUNIT = 57;
+
+  /** AD_CALTYPESCALE - calibration scaling factor */
+  public static final int AD_CALTYPESCALE = 58;
+
+  /** AD_AUXOFFSET - byte offset to start of auxilliary data section */
+  public static final int AD_AUXOFFSET = 59;
+
+  /** AD_CALOFFSET - byte offset to start of calibration section */
+  public static final int AD_CALOFFSET = 62;
+
+  /** AD_NUMCOMMENTS - number of 80 character comment cards */
+  public static final int AD_NUMCOMMENTS = 63;
+
+  /** AD_DIRSIZE - size in 4 byte words of an image directory block */
+  public static final int AD_DIRSIZE = 64;
+
+  /** VERSION_NUMBER - version number for a valid AREA file (since 1985) */
+  public static final int VERSION_NUMBER = 4;
+
+  /** flag for whether a handler was loaded */
+  private static boolean handlerLoaded = false;
+
+  // load protocol handler for ADDE URLs
+  // See java.net.URL for explanation of URL handling
+  static {
+    try {
+      String handlers = System.getProperty("java.protocol.handler.pkgs");
+      String newProperty = null;
+      if (handlers == null) newProperty = "edu.wisc.ssec.mcidas";
+      else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+             newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+      if (newProperty != null) { // was set above
+        //Properties sysP = System.getProperties();
+        //sysP.put("java.protocol.handler.pkgs", newProperty);
+        //System.setProperties(sysP);
+        System.setProperty("java.protocol.handler.pkgs", newProperty);
+      }
+      handlerLoaded = true;
+    }
+    catch (Exception e) {
+      System.out.println(
+        "Unable to set System Property: java.protocol.handler.pkgs");
+    }
+
+    handlerLoaded = true;
+  }
+
+  /**
+   *
+   *
+   * @return status 
+   */
+  public static boolean isURLHandlerLoaded() {
+    return handlerLoaded;
+  }
+
+  /** flag for whether words should be flipped */
+  private boolean flipwords;
+
+  /** file ok flag */
+  private boolean fileok;
+
+  /** flag for whether or not the readData() has been called */
+  private boolean hasReadData;
+
+  /** the DataInputStream */
+  transient private DataInputStream af;
+
+  /** status flag */
+//  private int status = 0;
+
+  /** location of nav, cal, aux and data blocks */
+  private int navLoc, calLoc, auxLoc, datLoc;
+
+  /** the bytes for navigation, calibration and aux data */
+  private int navbytes, calbytes, auxbytes;
+
+  /** original line data length, line length, num lines/eles bands */
+  private int lineDataLen, lineLength, origNumLines, origNumElements,
+              origNumBands;
+
+  /** line prefix length */
+  private int linePrefixLength;
+
+  /** position indicator */
+  private long position;
+
+  /** bytes to skip */
+  private int skipByteCount;
+
+  /** new position */
+  private long newPosition;
+
+  /** the directory block */
+  int[] dir;
+
+  /** the nav block */
+  int[] nav = null;
+
+  /** the calibration block */
+  int[] cal = null;
+
+  /** the aux block */
+  int[] aux = null;
+
+  /** the data */
+  int[][][] data;
+
+  /** the AreaDirectory representing this image */
+  private AreaDirectory areaDirectory;
+
+  /** the image source */
+  private String imageSource = null;
+
+  /** the AREAnav for this image */
+  private AREAnav areaNav;
+
+  /** the calibration type */
+  private int calType = Calibrator.CAL_NONE;
+
+  /** flag for remote data */
+  private boolean isRemote = false;
+
+  // master of all subsetting paramters
+  private class Subset {
+
+    /**  */
+    int lineNumber, numLines, lineMag, eleNumber, numEles, eleMag, bandNumber;
+
+    /**
+     *
+     *
+     * @return
+     */
+    public String toString() {
+      return "Start_line:" + lineNumber + " Num_lines:" + numLines +
+             " Line_mag:" + lineMag + " Start_ele:" + eleNumber +
+             " Num_ele:" + numEles + " Ele_mag:" + eleMag + " Band:" +
+             bandNumber;
+    }
+  }
+
+  /** subset info holder - if subset is not null, we have been subsetted */
+  private Subset subset = null;
+
+  /**
+   * Creates an AreaFile object that allows reading
+   * of McIDAS 'area' file format image data.  allows reading
+   * either from a disk file, or a server via ADDE.
+   *
+   * @param source the file name, ADDE URL, or local file URL to read from
+   *
+   * @exception AreaFileException if file cannot be opened
+   */
+
+  public AreaFile(String source) throws AreaFileException {
+
+    imageSource = source;
+    if (imageSource.startsWith("adde://") &&
+        (imageSource.endsWith("image?") ||
+         imageSource.endsWith("imagedata?"))) {
+
+      GetAreaGUI gag = new GetAreaGUI((Frame)null, true, "Get data", false,
+                                      true);
+      gag.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          imageSource = e.getActionCommand();
+        }
+      });
+      gag.show();
+    }
+
+    // try as a disk file first
+    try {
+      af = new DataInputStream(new BufferedInputStream(new FileInputStream(imageSource),
+              2048));
+    }
+    catch (IOException eIO) {
+      // if opening as a file failed, try as a URL
+      URL url;
+      try {
+        url = new URL(imageSource);
+        // System.out.println(url);
+        URLConnection urlc = url.openConnection();
+        InputStream is = urlc.getInputStream();
+        af = new DataInputStream(new BufferedInputStream(is));
+      }
+      catch (IOException e) {
+        fileok = false;
+        throw new AreaFileException("Error opening AreaFile: " + e);
+      }
+      isRemote = url.getProtocol().equalsIgnoreCase("adde");
+    }
+    fileok = true;
+    position = 0;
+    readMetaData();
+  }
+
+  /**
+   * creates an AreaFile object that allows reading
+   * of McIDAS 'area' file format image data from an applet
+   *
+   * @param filename the disk filename (incl path) to read from
+   * @param parent the parent applet
+   *
+   * @exception AreaFileException if file cannot be opened
+   *
+   */
+
+  public AreaFile(String filename, Applet parent) throws AreaFileException {
+
+    URL url;
+    imageSource = filename;
+    try {
+      url = new URL(parent.getDocumentBase(), filename);
+    }
+    catch (MalformedURLException e) {
+      fileok = false;
+      throw new AreaFileException("Error opening URL for AreaFile:" + e);
+    }
+
+    try {
+      af = new DataInputStream(new BufferedInputStream(url.openStream()));
+    }
+    catch (IOException e) {
+      fileok = false;
+      throw new AreaFileException("Error opening AreaFile:" + e);
+    }
+
+    isRemote = url.getProtocol().equalsIgnoreCase("adde");
+
+    fileok = true;
+    position = 0;
+    readMetaData();
+
+  }
+
+  /**
+   * create an <code>AreaFile</code> that allows reading of McIDAS 'area'
+   * file format image data from a <code>URL</code> with a protocol of either
+   * <code>file</code> or <code>ADDE</code>.  See
+   * {@link edu.wisc.ssec.mcidas.adde.AddeURLConnection} for more information on
+   * constructing ADDE urls.
+   *
+   *
+   * @param url
+   * @exception AreaFileException if file cannot be opened
+   */
+  public AreaFile(URL url) throws AreaFileException {
+
+    imageSource = url.toString();
+    try {
+      af = new DataInputStream(new BufferedInputStream(url.openStream()));
+    }
+    catch (IOException e) {
+      fileok = false;
+      throw new AreaFileException("Error opening URL for AreaFile:" + e);
+    }
+
+    isRemote = url.getProtocol().equalsIgnoreCase("adde");
+
+    fileok = true;
+    position = 0;
+    readMetaData();
+  }
+
+  /**
+   * Create a subsetted instance. When subsetted, the data not included in the
+   * subset is no longer available to this instance due to the directory block
+   * being updated with the subsetting parameters.
+   *
+   *
+   * @param source  the path to the file
+   * @param startLine the starting image line
+   * @param numLines the total number of lines to return
+   * @param lineMag the line magnification. Valid values are <= -1.
+   *                -1, 0, and 1 are all taken to be full line resolution,
+   *                -2 is every other line, -3 every third, etc...
+   * @param startElem the starting image element
+   * @param numEles the total number of elements to return
+   * @param eleMag the element magnification. Valid values are <= -1.
+   *               -1, 0, and 1 are all taken to be full element resolution,
+   *               -2 is every other element, -3 every third, etc...
+   * @param band the 1-based band number for the subset, which must be present
+   *             in the directory blocks band map or -1 for the first band
+   *
+   * @throws AreaFileException if file cannot be opened
+   * @throws IllegalArgumentException If the magnification is greater than 1,
+   * the band number is not in the band map, or either of the following are
+   * true:
+   * <pre>
+   *  startLine + (numLines * abs(lineMag)) > total number of lines
+   *  startElem + (numEles * abs(eleMag)) > total number of elements
+   * </pre>
+   */
+  public AreaFile(String source, int startLine, int numLines, int lineMag,
+                  int startElem, int numEles, int eleMag, int band)
+          throws AreaFileException {
+
+    this(source);
+
+    // must have subsetted in the url
+    if (isSubsetted()) return;
+
+    // 0, 1, -1 are all full res
+    if (eleMag == 0) eleMag = 1;
+    if (lineMag == 0) lineMag = 1;
+
+    if (lineMag > 1 || eleMag > 1) {
+      throw new IllegalArgumentException("Magnifications greater that 1 are not currently supported");
+
+    }
+    else if (startLine + numLines * Math.abs(lineMag) > origNumLines ||
+             startElem + numEles * Math.abs(eleMag) > origNumElements) {
+      throw new IllegalArgumentException("Arguments outside of file line/element counts");
+    }
+
+    int bandIdx = -1;
+    int[] bands = getAreaDirectory().getBands();
+    if (band == -1) {
+      bandIdx = 0;
+    }
+    else {
+      for (int i = 0; i < bands.length; i++) {
+        if (bands[i] == band) bandIdx = i;
+      }
+    }
+
+    if (bandIdx == -1) {
+      throw new IllegalArgumentException("Band not found in band map");
+    }
+
+    // save subset data
+    subset = new Subset();
+    subset.lineNumber = startLine;
+    subset.numLines = numLines;
+    subset.lineMag = lineMag;
+    subset.eleNumber = startElem;
+    subset.numEles = numEles;
+    subset.eleMag = eleMag;
+    subset.bandNumber = band;
+
+    int newDatOffset = startLine * lineLength;
+    newDatOffset += linePrefixLength;
+    newDatOffset += startElem * (origNumBands * dir[AD_DATAWIDTH]);
+    newDatOffset += (band - 1) * dir[AD_DATAWIDTH];
+
+    // reflect subset in directory
+    dir[AD_DATAOFFSET] = newDatOffset;
+    dir[AD_NUMLINES] = numLines;
+    dir[AD_NUMELEMS] = numEles;
+    dir[AD_NUMBANDS] = 1;
+
+    dir[AD_STLINE] = dir[AD_STLINE] + (startLine * dir[AD_LINERES]);
+    dir[AD_STELEM] = dir[AD_STELEM] + (startElem * dir[AD_ELEMRES]);
+
+    // if mag is positive divide to get resolution, otherwise mult.
+    // NOTE: resolution in area file is not necessarily full resolution, full
+    // resolution is based on the instrument, not the file.
+    if (lineMag < 0) {
+      dir[AD_LINERES] = dir[AD_LINERES] * Math.abs(lineMag);
+    }
+    else {
+      dir[AD_LINERES] = dir[AD_LINERES] / lineMag;;
+    }
+    if (eleMag < 0) {
+      dir[AD_ELEMRES] = dir[AD_ELEMRES] * Math.abs(eleMag);
+    }
+    else {
+      dir[AD_ELEMRES] = dir[AD_ELEMRES] / eleMag;
+    }
+
+    // set the band in the band map. Bandmap is in words 18 and 19
+    if (band <= 32) {
+      dir[AD_BANDMAP] = 1 << (band - 1);
+    }
+    else {
+      dir[AD_BANDMAP + 1] = 1 << (band - 32);
+    }
+
+    // FIXME update word 14?
+
+    // create new directory with subsetted parameters
+    areaDirectory = new AreaDirectory(dir);
+  }
+
+  /**
+   * Is this <code>AreaFile</code> instance subseted.
+   *
+   * @return True if this instance represents a subset of the total data
+   * available.
+   */
+  public boolean isSubsetted() {
+    return !(subset == null);
+  }
+
+  /**
+   * Was this instance create with a remote data source.
+   * @return True if created with an ADDE url
+   */
+  public boolean isRemote() {
+    return isRemote;
+  }
+
+  /**
+   * Read the metadata for an area file (directory, nav, and cal).
+   *
+   * @exception AreaFileException
+   *              if there is a problem reading any portion of the metadata.
+   *
+   */
+  private void readMetaData() throws AreaFileException {
+
+    int i;
+    hasReadData = false;
+
+    if (!fileok) {
+      throw new AreaFileException("Error reading AreaFile directory");
+    }
+
+    dir = new int[AD_DIRSIZE];
+
+    for (i = 0; i < AD_DIRSIZE; i++) {
+      try {
+        dir[i] = af.readInt();
+      }
+      catch (IOException e) {
+        throw new AreaFileException("Error reading AreaFile directory:" + e);
+      }
+    }
+    position += AD_DIRSIZE * 4;
+
+    // see if the directory needs to be byte-flipped
+
+    if (dir[AD_VERSION] != VERSION_NUMBER) {
+      McIDASUtil.flip(dir, 0, 19);
+      // check again
+      if (dir[AD_VERSION] != VERSION_NUMBER)
+        throw new AreaFileException("Invalid version number - probably not an AREA file");
+      // word 20 may contain characters -- if small integer, flip it...
+      if ((dir[20] & 0xffff) == 0) McIDASUtil.flip(dir, 20, 20);
+      McIDASUtil.flip(dir, 21, 23);
+      // words 24-31 contain memo field
+      McIDASUtil.flip(dir, 32, 50);
+      // words 51-2 contain cal info
+      McIDASUtil.flip(dir, 53, 55);
+      // word 56 contains original source type (ascii)
+      McIDASUtil.flip(dir, 57, 63);
+      flipwords = true;
+    }
+
+    areaDirectory = new AreaDirectory(dir);
+
+    // pull together some values needed by other methods
+    navLoc = dir[AD_NAVOFFSET];
+    calLoc = dir[AD_CALOFFSET];
+    auxLoc = dir[AD_AUXOFFSET];
+    datLoc = dir[AD_DATAOFFSET];
+    origNumBands = dir[AD_NUMBANDS];
+    linePrefixLength = dir[AD_DOCLENGTH] + dir[AD_CALLENGTH] +
+                       dir[AD_LEVLENGTH];
+    if (dir[AD_VALCODE] != 0) linePrefixLength = linePrefixLength + 4;
+    if (linePrefixLength != dir[AD_PFXSIZE])
+      throw new AreaFileException("Invalid line prefix length in AREA file.");
+    lineDataLen = origNumBands * dir[AD_NUMELEMS] * dir[AD_DATAWIDTH];
+    lineLength = linePrefixLength + lineDataLen;
+    origNumLines = dir[AD_NUMLINES];
+    origNumElements = dir[AD_NUMELEMS];
+
+    if (datLoc > 0 && datLoc != McIDASUtil.MCMISSING) {
+      navbytes = datLoc - navLoc;
+      calbytes = datLoc - calLoc;
+      auxbytes = datLoc - auxLoc;
+    }
+    if (auxLoc > 0 && auxLoc != McIDASUtil.MCMISSING) {
+      navbytes = auxLoc - navLoc;
+      calbytes = auxLoc - calLoc;
+    }
+
+    if (calLoc > 0 && calLoc != McIDASUtil.MCMISSING) {
+      navbytes = calLoc - navLoc;
+    }
+
+
+    // Read in nav block
+
+    if (navLoc > 0 && navbytes > 0) {
+
+      nav = new int[navbytes / 4];
+
+      newPosition = (long)navLoc;
+      skipByteCount = (int)(newPosition - position);
+      try {
+        af.skipBytes(skipByteCount);
+      }
+      catch (IOException e) {
+        throw new AreaFileException("Error skipping AreaFile bytes: " + e);
+      }
+
+      for (i = 0; i < navbytes / 4; i++) {
+        try {
+          nav[i] = af.readInt();
+        }
+        catch (IOException e) {
+          throw new AreaFileException("Error reading AreaFile navigation:" +
+                                      e);
+        }
+      }
+      if (flipwords) flipnav(nav);
+      position = navLoc + navbytes;
+    }
+
+
+    // Read in cal block
+
+    if (calLoc > 0 && calbytes > 0) {
+
+      cal = new int[calbytes / 4];
+
+      newPosition = (long)calLoc;
+      skipByteCount = (int)(newPosition - position);
+      try {
+        af.skipBytes(skipByteCount);
+      }
+      catch (IOException e) {
+        throw new AreaFileException("Error skipping AreaFile bytes: " + e);
+      }
+
+      for (i = 0; i < calbytes / 4; i++) {
+        try {
+          cal[i] = af.readInt();
+        }
+        catch (IOException e) {
+          throw new AreaFileException("Error reading AreaFile calibration:" +
+                                      e);
+        }
+      }
+      // if (flipwords) flipcal(cal);
+      position = calLoc + calbytes;
+    }
+
+    // Read in aux block
+
+    if (auxLoc > 0 && auxbytes > 0) {
+      aux = new int[auxbytes / 4];
+      newPosition = (long)auxLoc;
+      skipByteCount = (int)(newPosition - position);
+      try {
+        af.skipBytes(skipByteCount);
+      }
+      catch (IOException e) {
+        throw new AreaFileException("Error skipping AreaFile bytes: " + e);
+      }
+      for (i = 0; i < auxbytes / 4; i++) {
+        try {
+          aux[i] = af.readInt();
+        }
+        catch (IOException e) {
+          throw new AreaFileException("Error reading AreaFile aux block:" +
+                                      e);
+        }
+      }
+      position = auxLoc + auxbytes;
+    }
+
+
+    // now return the Dir, as requested...
+    return;
+  }
+
+  /**
+   * returns the string of the image source location
+   *
+   * @return name of image source
+   *
+   */
+  public String getImageSource() {
+    return imageSource;
+  }
+
+  /**
+   * Returns the directory block
+   *
+   * @return an integer array containing the area directory
+   *
+   *
+   */
+  public int[] getDir() {
+    return dir;
+  }
+
+
+  /**
+   * Returns the AreaDirectory object for this AreaFile
+   *
+   * @return AreaDirectory
+   *
+   *
+   */
+  public AreaDirectory getAreaDirectory() {
+    return areaDirectory;
+  }
+
+
+  /**
+   * Returns the navigation block
+   *
+   * @return an integer array containing the nav block data
+   *
+   *
+   */
+  public int[] getNav() {
+
+    if (navLoc <= 0 || navLoc == McIDASUtil.MCMISSING) {
+      nav = null;
+    }
+
+    return nav;
+
+  }
+
+  /**
+   * Get the navigation, and pre-set it
+   * for the ImageStart and Res (from Directory block), and
+   * the file start (0,0), and Mag (1,1).
+   *
+   * @return  AREAnav for this image  (may be null)
+   *
+   * @throws AreaFileException
+   */
+  public AREAnav getNavigation() throws AreaFileException {
+    if (areaNav == null) {
+      // make the nav module
+      try {
+        areaNav = AREAnav.makeAreaNav(getNav(), getAux());
+        areaNav.setImageStart(dir[AD_STLINE], dir[AD_STELEM]);
+        areaNav.setRes(dir[AD_LINERES], dir[AD_ELEMRES]);
+        areaNav.setStart(0, 0);
+        areaNav.setMag(1, 1);
+
+      }
+      catch (McIDASException excp) {
+        areaNav = null;
+      }
+    }
+    return areaNav;
+  }
+
+  /**
+   * Returns calibration block
+   *
+   * @return an integer array containing the nav block data
+   *
+   *
+   */
+  public int[] getCal() {
+
+
+    if (calLoc <= 0 || calLoc == McIDASUtil.MCMISSING) {
+      cal = null;
+    }
+
+    return cal;
+
+  }
+
+
+  /**
+   * Returns AUX block
+   *
+   * @return an integer array containing the aux block data
+   *
+   *
+   */
+  public int[] getAux() {
+
+    if (auxLoc <= 0 || auxLoc == McIDASUtil.MCMISSING) {
+      aux = null;
+    }
+
+    return aux;
+
+  }
+
+  /**
+   * Read the AREA data.
+   *
+   * @return int array[band][lines][element] - If the <code>AreaFile</code>
+   * was created as a subset only the band and subset indicated are returned,
+   * otherwise all bands are returned.
+   *
+   * @exception AreaFileException if there is a problem
+   *
+   */
+  public int[][][] getData() throws AreaFileException {
+    data = new int[origNumBands][dir[AD_NUMLINES]][dir[AD_NUMELEMS]];
+    return getData(data);
+  }
+  
+  /**
+   * Read AREA file data by reference. After reading the internal
+   *  data array is will be a reference to the target array, so any changes made
+   *  to the target array will be reflected in the internal data array.
+   * 
+   * @param target Array to use as the destination of the data read. This array
+   *  must be appropriately dimensioned as [#bands][#lines][#elems].
+   * @return int array[band][lines][element] - If the <code>AreaFile</code>
+   * was created as a subset only the band and subset indicated are returned,
+   * otherwise all bands are returned.
+   * @throws IllegalArgumentException If the target array is not dimensioned 
+   *  according to the subset, if subsetted, or otherwise has dimensions other than
+   *  [bands][lines][elements].
+   * @throws AreaFileException If an error occurs while reading data. 
+   */
+  public int[][][] getData(int[][][] target) throws AreaFileException {
+    if (target == null ||
+        (isSubsetted() && target.length != 1 && target[0].length != subset.numLines
+            && target[0][0].length != subset.numEles)
+      || (target.length != origNumBands && target[0].length != dir[AD_NUMLINES]
+            && target[0][0].length != dir[AD_NUMELEMS])) {
+      throw new IllegalArgumentException("target array is not dimensioned correctly");
+    }
+    
+    if (!hasReadData) {
+      if (subset == null) {
+        readData(target);
+      }
+      else {
+        readData(target, 
+          subset.lineNumber, subset.numLines, subset.lineMag,
+          subset.eleNumber, subset.numEles, subset.eleMag, subset.bandNumber);
+      }
+    }
+    hasReadData = true;
+    data = target;
+    return data;
+  }
+
+  /**
+   * Set the calibration type that will be used on data returned from
+   * <code>getCalibratedData()</code>. This must be called before
+   * <code>getCalibratedData()</code> to get calibrated data, otherwise it will
+   * just return the data in the format specified in the directory.
+   * @param cal calibration type from {@link Calibrator}.
+   */
+  public void setCalType(int cal) {
+    calType = cal;
+  }
+
+  /**
+   * Get the calibration type that will be used on data returned form
+   * <code>getCalibratedData()</code>.
+   * @return calibration type from {@link Calibrator}.
+   */
+  public int getCalType() {
+    return calType;
+  }
+
+
+  /**
+   * Read the AREA file and return the contents as floats. If the calibration
+   * type has been set via <code>setCalType()</code>, <code>isRemote()</code>
+   * is false, and a <code>Calibrator</code> is available data is calibrated
+   * before returning, otherwise the calibration type is ignored.
+   * The original data is alway preserved.
+   *
+   * @return data[band][lines][elements] as described above
+   * @throws AreaFileException on error reading data.
+   * @see Calibrator
+   */
+  public float[][][] getFloatData() throws AreaFileException {
+
+    int[][][] inData = getData();
+    float[][][] outData =
+      new float[dir[AD_NUMBANDS]][dir[AD_NUMLINES]][dir[AD_NUMELEMS]];
+
+    // create the appropriate calibrator
+    Calibrator calibrator = null;
+
+    int origType =
+      AreaFileFactory.calStrToInt(areaDirectory.getCalibrationType());
+
+    if (!isRemote() && getCalType() != Calibrator.CAL_NONE &&
+        origType != getCalType()) {
+
+      try {
+        calibrator =
+          CalibratorFactory.getCalibrator(areaDirectory.getSensorID(), cal);
+      }
+      catch (CalibratorException e) {
+        // can't calibrate
+      }
+    }
+
+    // get all bands
+    if (subset == null) {
+      for (int band_idx = 0; band_idx < inData.length; band_idx++) {
+        for (int line = 0; line < inData[0].length; line++) {
+          for (int elem = 0; elem < inData[0][0].length; elem++) {
+            if (calibrator != null) {
+              outData[band_idx][line][elem] =
+                calibrator.calibrate((float)inData[band_idx][line][elem],
+                                     band_idx + 1, calType);
+            }
+            else {
+              outData[band_idx][line][elem] = inData[band_idx][line][elem];
+            }
+          }
+        }
+      }
+
+      // just subsetted band
+    }
+    else {
+      for (int line = 0; line < inData[0].length; line++) {
+        for (int elem = 0; elem < inData[0][0].length; elem++) {
+          if (!isRemote && calType != Calibrator.CAL_NONE &&
+              calibrator != null) {
+            outData[0][line][elem] =
+              calibrator.calibrate((float)inData[0][line][elem],
+                                   subset.bandNumber, calType);
+          }
+          else {
+            outData[0][line][elem] = inData[0][line][elem];
+          }
+        }
+      }
+    }
+
+    return outData;
+  }
+
+  /**
+   * Read the specified 2-dimensional array of
+   * data values from the AREA file.  Values will always be returned
+   * as int regardless of whether they are 1, 2, or 4 byte values.
+   *
+   * @param lineNumber the file-relative image line number that will
+   *                   be put in array[0][j]
+   * @param eleNumber  the file-relative image element number that will
+   *                   be put into array[i][0]
+   * @param numLines   the number of lines to return
+   * @param numEles    the number of elements to return for each line
+   *
+   * @return int array[lines][elements] with data values.
+   * @deprecated Use one of the factory methods from {@link AreaFileFactory}
+   * with the appropriate subsetting parameters.
+   * @exception AreaFileException if the is a problem reading the file
+   */
+  public int[][] getData(int lineNumber, int eleNumber, int numLines,
+                         int numEles)
+          throws AreaFileException {
+    return getData(lineNumber, eleNumber, numLines, numEles, 1);
+  }
+
+
+  /**
+   * Read the specified 2-dimensional array of
+   * data values from the AREA file.  Values will always be returned
+   * as int regardless of whether they are 1, 2, or 4 byte values.
+   *
+   * @param lineNumber the file-relative image line number that will
+   *                   be put in array[0][j]
+   * @param eleNumber  the file-relative image element number that will
+   *                   be put into array[i][0]
+   * @param numLines   the number of lines to return
+   * @param numEles    the number of elements to return for each line
+   * @param bandNumber the spectral band to return
+   *
+   * @return int array[lines][elements] with data values.
+   * @deprecated Use one of the factory methods from {@link AreaFileFactory}
+   * with the appropriate subsetting parameters.
+   * @exception AreaFileException if the is a problem reading the file
+   */
+  public int[][] getData(int lineNumber, int eleNumber, int numLines,
+                         int numEles, int bandNumber)
+          throws AreaFileException {
+
+    //data = new int[1][numLines][numEles];
+    if (!hasReadData) {
+      data = new int[origNumBands][dir[AD_NUMLINES]][dir[AD_NUMELEMS]];
+      readData(data);
+    }
+    int[][] subset = new int[numLines][numEles];
+    for (int i = 0; i < numLines; i++) {
+      int ii = i + lineNumber;
+      for (int j = 0; j < numEles; j++) {
+        int jj = j + eleNumber;
+        if (ii < 0 || ii > (dir[AD_NUMLINES] - 1) || jj < 0 ||
+            jj > (dir[AD_NUMELEMS] - 1)) {
+          subset[i][j] = 0;
+        }
+        else {
+          subset[i][j] = data[bandNumber - 1][ii][jj];
+        }
+      }
+    }
+    return subset;
+  }
+
+  /**
+   *
+   *
+   * @param s
+   *
+   * @return flipped value
+   */
+  private int flipShort(short s) {
+    return (int)(((s >> 8) & 0xff) | ((s << 8) & 0xff00));
+  }
+
+  /**
+   *
+   *
+   * @param i
+   *
+   * @return flipped value
+   */
+  private int flipInt(int i) {
+    return ((i >>> 24) & 0xff) | ((i >>> 8) & 0xff00) | ((i & 0xff) << 24)
+           | ((i & 0xff00) << 8);
+  }
+
+  /**
+   * Read a single band of subsetted data.
+   *
+   * @param lineNumber
+   * @param numLines
+   * @param lineMag
+   * @param eleNumber
+   * @param numEles
+   * @param eleMag
+   * @param bandNumber
+   *
+   * @throws AreaFileException
+   */
+  private void readData(int[][][] target, 
+                        int lineNumber, int numLines, int lineMag,
+                        int eleNumber, int numEles, int eleMag,
+                        int bandNumber)
+          throws AreaFileException {
+
+    if (!fileok) {
+      throw new AreaFileException("Error reading AreaFile data");
+    }
+
+    // multipliers for line/element skips
+    int lineMagMult = (lineMag >= 1)
+                      ? 0
+                      : Math.abs(lineMag) - 1;
+    int eleMagMult = (eleMag >= 1)
+                     ? 0
+                     : Math.abs(eleMag) - 1;
+
+    int startLoc = dir[AD_DATAOFFSET];
+    int elementSize = origNumBands * dir[AD_DATAWIDTH];
+    int readElements = eleMagMult == 0
+                       ? numEles
+                       : numEles * Math.abs(eleMag);
+
+    // num of lines to skip due to line resolution
+    int lineSkip = lineMagMult * lineLength;
+    // skip to read position on next line
+    int readSkip = (origNumElements - readElements) * elementSize +
+                   linePrefixLength;
+
+    // number of element to skip due to resolution
+    int elementSkip = eleMagMult * elementSize;
+    // skip for bands we are not interrested in
+    int bandSkip = elementSize - dir[AD_DATAWIDTH];
+
+    int nextReadSkip = readSkip + lineSkip;
+    int nextElementSkip = bandSkip + elementSkip;
+
+    short shdata;
+    int intdata;
+
+    try {
+      DataInputStream df = getInputStreamForData();
+      if (df != af) {
+        af = df;
+      }
+    }
+    catch (IOException ioe) {
+      throw new AreaFileException("Error getting input stream for data");
+
+    }
+
+    try {
+      af.skipBytes(startLoc);
+    }
+    catch (IOException e) {
+      throw new AreaFileException("Error skipping to start of data");
+    }
+
+    for (int i = 0; i < numLines; i++) {
+      for (int j = 0; j < numEles; j++) {
+
+        try {
+          // all 1- and 2-byte data are un-signed!
+          if (dir[AD_DATAWIDTH] == 1) {
+            target[0][i][j] = ((int)af.readByte()) & 0xff;
+
+          }
+          else if (dir[AD_DATAWIDTH] == 2) {
+            shdata = af.readShort();
+            if (flipwords) {
+              target[0][i][j] = flipShort(shdata) & 0xffff;
+            }
+            else {
+              target[0][i][j] = ((int)shdata) & 0xffff;
+            }
+
+          }
+          else if (dir[AD_DATAWIDTH] == 4) {
+            intdata = af.readInt();
+            if (flipwords) {
+              target[0][i][j] = flipInt(intdata);
+            }
+            else {
+              target[0][i][j] = intdata;
+            }
+          }
+
+          af.skipBytes(nextElementSkip);
+
+        }
+        catch (IOException e) {
+          throw new AreaFileException("Error reading element " + i +
+                                      " in line " + j);
+        }
+      }
+
+      // done with line, skip to relavent element in next relavent line
+      try {
+        af.skipBytes(nextReadSkip);
+      }
+      catch (IOException e) {
+        throw new AreaFileException("Error skipping to next line");
+      }
+
+    }
+  }
+
+  /**
+   * Read all data including all bands.
+   *
+   * @throws AreaFileException
+   */
+  private void readData(int[][][] target) throws AreaFileException {
+
+    int i, j, k;
+    int numLines = dir[AD_NUMLINES], numEles = dir[AD_NUMELEMS];
+
+    if (!fileok) {
+      throw new AreaFileException("Error reading AreaFile data");
+    }
+
+    try {
+      DataInputStream df = getInputStreamForData();
+      if (df != af) {
+        datLoc = 0;
+        position = 0;
+        af = df;
+      }
+    }
+    catch (IOException ioe) {
+      throw new AreaFileException("Error getting input stream for data");
+
+    }
+    
+    short shdata;
+    int intdata;
+
+    for (i = 0; i < numLines; i++) {
+
+      try {
+        newPosition = (long)(datLoc + linePrefixLength + i * lineLength);
+        skipByteCount = (int)(newPosition - position);
+        af.skipBytes(skipByteCount);
+        position = newPosition;
+
+      }
+      catch (IOException e) {
+        for (j = 0; j < numEles; j++) {
+          for (k = 0; k < origNumBands; k++) {
+            target[k][i][j] = 0;
+          }
+        }
+        break;
+      }
+
+      for (j = 0; j < numEles; j++) {
+
+        for (k = 0; k < origNumBands; k++) {
+
+          if (j > lineDataLen) {
+            target[k][i][j] = 0;
+          }
+          else {
+
+            try {
+              // all 1- and 2-byte data are un-signed!
+
+              if (dir[AD_DATAWIDTH] == 1) {
+                target[k][i][j] = ((int)af.readByte()) & 0xff;
+                position = position + 1;
+              }
+              else if (dir[AD_DATAWIDTH] == 2) {
+                shdata = af.readShort();
+                if (flipwords) {
+                  target[k][i][j] = flipShort(shdata) & 0xffff;
+                }
+                else {
+                  target[k][i][j] = ((int)shdata) & 0xffff;
+                }
+                position = position + 2;
+              }
+              else if (dir[AD_DATAWIDTH] == 4) {
+                intdata = af.readInt();
+                if (flipwords) {
+                  target[k][i][j] = flipInt(intdata);
+                }
+                else {
+                  target[k][i][j] = intdata;
+                }
+                position = position + 4;
+              }
+
+            }
+            catch (IOException e) {
+              target[k][i][j] = 0;
+            }
+          }
+        }
+      }
+
+    }
+
+    hasReadData = true;
+
+    try {
+      af.close();
+    }
+    catch (IOException excp) {
+      System.out.println("Couldn't close input stream for " + imageSource);
+    }
+
+    return;
+
+  } // end of areaReadData method
+
+  /**
+   * Selectively flip the bytes of words in nav block
+   *
+   * @param nav array of nav parameters
+   */
+  private void flipnav(int[] nav) {
+
+    // first word is always the satellite id in ASCII
+    // check on which type:
+
+    if (nav[0] == AREAnav.GVAR) {
+
+      McIDASUtil.flip(nav, 2, 126);
+      McIDASUtil.flip(nav, 129, 254);
+      McIDASUtil.flip(nav, 257, 382);
+      McIDASUtil.flip(nav, 385, 510);
+      McIDASUtil.flip(nav, 513, 638);
+    }
+
+    else if (nav[0] == AREAnav.DMSP) {
+      McIDASUtil.flip(nav, 1, 43);
+      McIDASUtil.flip(nav, 45, 51);
+    }
+
+    else if (nav[0] == AREAnav.POES) {
+      McIDASUtil.flip(nav, 1, 119);
+    }
+
+    else if (nav[0] == AREAnav.GMSX) {}
+    else {
+      McIDASUtil.flip(nav, 1, nav.length - 1);
+    }
+
+    return;
+  }
+
+  /**
+   * Get a String representation of this image metadata/information
+   *
+   * @return string of metadata
+   */
+  public String toString() {
+    AreaDirectory dir = getAreaDirectory();
+    String EOL = "\n";
+    StringBuffer buff = new StringBuffer();
+    buff.append("Directory values =========" + EOL);
+    buff.append("Num Lines: " + dir.getLines() + EOL);
+    buff.append("Num Elements: " + dir.getElements() + EOL);
+    buff.append("Start Line: " + dir.getDirectoryBlock()[AD_STLINE] + EOL);
+    buff.append("Start Element: " + dir.getDirectoryBlock()[AD_STELEM] + EOL);
+    buff.append("Line Res: " + dir.getDirectoryBlock()[AD_LINERES] + EOL);
+    buff.append("Elem Res: " + dir.getDirectoryBlock()[AD_ELEMRES] + EOL);
+    buff.append("Bands:");
+    for (int i = 0; i < dir.getBands().length; i++)
+      buff.append(" " + dir.getBands()[i]);
+    buff.append(EOL);
+    buff.append("Source Type: " + dir.getSourceType() + EOL);
+    buff.append("Sensor Type: " + dir.getSensorType() + EOL);
+    buff.append("Sensor ID: " + dir.getSensorID() + EOL);
+    buff.append("Cal Type: " + dir.getCalibrationType() + EOL);
+    buff.append(
+      "Nominal Time: " + dir.getDirectoryBlock()[AD_IMGDATE] + " " +
+      dir.getDirectoryBlock()[AD_IMGTIME] + EOL);
+    buff.append("==========================" + EOL);
+    try {
+      buff.append("Nav: " + getNavigation() + EOL);
+    }
+    catch (AreaFileException e) {
+    }
+    buff.append(
+      "User Cal Type: " +
+      AreaFileFactory.calIntToStr(getCalType()).toUpperCase());
+    return buff.toString();
+  }
+
+  /**
+   * Test Method.
+   * <pre>
+   * USAGE: AreaFile <source> [(raw|temp|brit|rad|refl)]
+   * </pre>
+   * <p>If source is a file path or url without subsetting information directory
+   * information is printed. If source is a local file url with subsetting
+   * information data is printed according to the parameters.</p>
+   *
+   * <p>This has not been tested with an ADDE url, but it should work ...
+   * maybe.</p>
+   *
+   * @param args
+   * @throws Exception
+   */
+  public static void main(String[] args) throws Exception {
+    if (args == null || args.length == 0) {
+      System.out.println();
+      System.out.println("USAGE: AreaFile <URL or filepath> <show_vals>");
+      System.out.println();
+      System.exit(1);
+    }
+    AreaFile af = AreaFileFactory.getAreaFileInstance(args[0]);
+
+    System.out.println(af);
+
+    System.out.println();
+    System.out.println(af.subset);
+
+    System.out.println();
+    long time = System.currentTimeMillis();
+    System.out.print("Getting data ... ");
+    float data[][][] = af.getFloatData();
+
+    System.out.println(
+      "" + (System.currentTimeMillis() - time) + "ms to retrieve " +
+      AreaFileFactory.calIntToStr(af.getCalType()).toUpperCase() + " data");
+    System.out.println();
+
+    System.out.println(
+      "DATA [" + data.length + "][" + data[0].length + "][" +
+      data[0][0].length + "]");
+
+    if (args.length > 1 && !af.isSubsetted()) {
+      System.err.println("Sorry, I won't print an unsubsetted file");
+      System.exit(0);
+    }
+
+    if (args.length > 1) {
+      // write data to std err so it may be piped to file w/o all the
+      // other garbage
+      for (int i = 0; i < data[0].length; i++) {
+        for (int j = 0; j < data[0][0].length; j++) {
+          System.err.print("" + data[0][i][j] + " ");
+        }
+        System.err.println();
+      }
+    }
+  }
+
+  /**
+   * Get the input stream for the image.  Handles Unidata PNG
+   * compressed images.
+   *
+   * @return  the input stream for the reading
+   *
+   * @throws IOException
+   */
+  private DataInputStream getInputStreamForData() throws IOException {
+
+    if (af.markSupported()) {
+      //System.out.println("mark is supported");
+      // calculate offset to potentially compressed data block
+      int numComments = dir[AD_NUMCOMMENTS];
+      // System.out.println("number  of comment cards = " + numComments);
+      int compressedDataStart = numComments * 80 + datLoc;
+      af.mark((int)(compressedDataStart - position + 10));
+      af.skip(compressedDataStart - position);
+      byte[] test = new byte[8];
+      af.read(test);
+      af.reset();
+      if (isPNG(test)) {
+        //System.out.println("isPNG");
+        if (numComments > 0) {
+          byte[] comments = new byte[numComments * 80];
+          af.read(comments);
+          /*
+          for (int i = 0; i < numComments; i++) {
+              byte[] comment = new byte[80];
+              System.arraycopy(comments, i*80, comment, 0, 80);
+              System.out.println("card["+i+"] = " + new String(comment));
+          }
+          */
+        }
+        int available = af.available();
+
+        byte[] data = new byte[available];
+        af.readFully(data);
+        af.close();
+        ByteArrayInputStream ios = new ByteArrayInputStream(data);
+
+        BufferedImage image = javax.imageio.ImageIO.read(ios);
+        Raster raster = image.getData();
+        DataBuffer db = raster.getDataBuffer();
+
+        if (db instanceof DataBufferByte) {
+          DataBufferByte dbb = (DataBufferByte)db;
+          byte[] udata = dbb.getData();
+          ByteArrayInputStream newios = new ByteArrayInputStream(udata);
+          return new DataInputStream(newios);
+
+        }
+      }
+      else {
+        return af;
+      }
+    }
+
+    return af;
+  }
+
+  /**
+   * Check if this is a PNG compressed image
+   *
+   * @param bytes  bytes to check
+   *
+   * @return  true if it fits the profile
+   */
+  private boolean isPNG(byte[] bytes) {
+
+    if (bytes.length != 8) return false;
+    return bytes[0] == -119 && bytes[1] == 80 && // P
+           bytes[2] == 78 && // N
+           bytes[3] == 71 && // G
+           bytes[4] == 13 && bytes[5] == 10 && bytes[6] == 26 &&
+           bytes[7] == 10;
+  }
+
+  /**
+   * Close this instance.
+   */
+  public void close() {
+    if (af == null) return;
+    try {
+      af.close();
+    }
+    catch (IOException ioe) {
+    }
+  }
+
+  /**
+   * Save this AreaFile to the output location
+   * @param outputFile  path to the output file
+   *
+   * @throws AreaFileException  problem saving to the file
+   */
+  public void save(String outputFile) throws AreaFileException {
+    save(outputFile, false);
+  }
+
+  /**
+   * Save this AreaFile to the output location
+   * @param outputFile  path to the output file
+   * @param verbose   true to print out status messages
+   * @throws AreaFileException on any error writing the file
+   */
+  public void save(String outputFile, boolean verbose)
+          throws AreaFileException {
+
+    int[] dir = getDir();
+    if (dir == null) {
+      System.out.println("No AREA file directory!");
+      return;
+    }
+    if (verbose) {
+      System.out.println("Length of directory = " + dir.length);
+
+      for (int i = 0; i < dir.length; i++) {
+        System.out.println(" index " + i + " = " + dir[i]);
+      }
+    }
+
+    int[] nav = getNav();
+    if (nav == null) {
+      if (verbose) System.out.println("No navigation block!");
+    }
+    else {
+      if (verbose) System.out.println("Length of nav block = " + nav.length);
+    }
+
+    int[] cal = getCal();
+    if (cal == null) {
+      if (verbose) System.out.println("No calibration block!");
+    }
+    else {
+      if (verbose) System.out.println("Length of cal block = " + cal.length);
+    }
+
+    int[] aux = getAux();
+    if (aux == null) {
+      if (verbose) System.out.println("No aux block");
+    }
+    else {
+      if (verbose) System.out.println("Length of aux block = " + aux.length);
+    }
+
+    int NL = dir[8];
+    int NE = dir[9];
+
+    if (verbose)
+      System.out.println("Start reading data, num points=" + (NL * NE));
+
+    int[][] data;
+
+    data = getData(0, 0, NL, NE);
+
+    if (verbose) System.out.println("Finished reading data");
+
+    try {
+      RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");
+
+      if (verbose) System.out.println("Dir to word 0");
+      raf.seek(0);
+      dir[0] = 0; // make sure this is zero!!
+      for (int i = 0; i < dir.length; i++)
+        raf.writeInt(dir[i]);
+
+      if (verbose) System.out.println("Nav to word " + dir[AD_NAVOFFSET]);
+      if (nav != null && dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_NAVOFFSET]);
+        for (int i = 0; i < nav.length; i++)
+          raf.writeInt(nav[i]);
+      }
+
+      if (verbose) System.out.println("Cal to word " + dir[AD_CALOFFSET]);
+      if (cal != null && dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_CALOFFSET]);
+        for (int i = 0; i < cal.length; i++)
+          raf.writeInt(cal[i]);
+      }
+
+      if (verbose) System.out.println("Aux to word " + dir[AD_AUXOFFSET]);
+      if (aux != null && dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_AUXOFFSET]);
+        for (int i = 0; i < aux.length; i++)
+          raf.writeInt(aux[i]);
+      }
+
+      if (verbose) System.out.println("Data to word " + dir[AD_DATAOFFSET]);
+      if (dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_DATAOFFSET]);
+        for (int i = 0; i < data.length; i++) {
+          for (int j = 0; j < data[i].length; j++) {
+            if (dir[AD_DATAWIDTH] == 1) {
+              raf.writeByte(data[i][j]);
+            }
+            else if (dir[AD_DATAWIDTH] == 2) {
+              raf.writeShort(data[i][j]);
+            }
+            else if (dir[AD_DATAWIDTH] == 4) {
+              raf.writeInt(data[i][j]);
+            }
+          }
+        }
+      }
+
+      raf.close();
+    }
+    catch (Exception we) {
+      throw new AreaFileException("Unable to save file " + we.getMessage());
+    }
+    if (verbose)
+      System.out.println("Completed. Data saved to: " + outputFile);
+  }
+}
+
diff --git a/edu/wisc/ssec/mcidas/AreaFileException.java b/edu/wisc/ssec/mcidas/AreaFileException.java
new file mode 100644
index 0000000..9e36052
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/AreaFileException.java
@@ -0,0 +1,51 @@
+//
+// AreaFileException.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+
+/**
+ * AreaFileException class is to handle exceptions when dealing
+ * with McIDAS 'area' files.
+ *
+ * @author Tom Whittaker SSEC
+ */
+
+public class AreaFileException extends McIDASException {
+
+  /**
+   * Constructs an AreaFileException with no specified detail message.
+   */
+  public AreaFileException() {super(); }
+
+  /**
+   * Constructs an AreaFileException with the specified detail message.
+   *
+   * @param  s  the detail message.
+   */
+  public AreaFileException(String s) {super(s); }
+
+}
diff --git a/edu/wisc/ssec/mcidas/AreaFileFactory.java b/edu/wisc/ssec/mcidas/AreaFileFactory.java
new file mode 100644
index 0000000..c41c95a
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/AreaFileFactory.java
@@ -0,0 +1,456 @@
+//
+// AreaFileFactory.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import edu.wisc.ssec.mcidas.adde.AddeURLException;
+
+
+/**
+ * Utility class for creating <code>AreaFile</code> instances. This class
+ * handles subsetting local files using urls whereas the <code>AreaFile</code>
+ * constructors do not.
+ *
+ * <p>No instances of this class can be created.</p>
+ * @version $Id: AreaFileFactory.java,v 1.8 2009-07-01 12:51:04 donm Exp $
+ * @author Bruce Flynn, SSEC
+ */
+public final class AreaFileFactory {
+
+  /** No-op constructor preventing instatiation of this class. */
+  private AreaFileFactory() {}
+
+  /**
+   * String to calibration int.
+   * @param cal calibration type string
+   *
+   * @return calibration type integer, Calibrator.CAL_NONE if unknown
+   */
+  public final static int calStrToInt(String cal) {
+    int ret = Calibrator.CAL_NONE;
+    String calType = cal.trim();
+    if (calType.equalsIgnoreCase("temp")) ret = Calibrator.CAL_TEMP;
+    else if (calType.equalsIgnoreCase("brit")) ret = Calibrator.CAL_BRIT;
+    else if (calType.equalsIgnoreCase("rad")) ret = Calibrator.CAL_RAD;
+    else if (calType.equalsIgnoreCase("raw")) ret = Calibrator.CAL_RAW;
+    else if (calType.equalsIgnoreCase("refl")) ret = Calibrator.CAL_ALB;
+    return ret;
+  }
+
+  /**
+   * Calibration int to string.
+   * @param cal calibration type
+   *
+   * @return calibration type string, raw if unknown
+   */
+  public final static String calIntToStr(int cal) {
+    String ret = "raw";
+    switch (cal) {
+      case Calibrator.CAL_ALB:
+        ret = "refl";
+        break;
+      case Calibrator.CAL_BRIT:
+        ret = "brit";
+        break;
+      case Calibrator.CAL_RAD:
+        ret = "rad";
+        break;
+      case Calibrator.CAL_RAW:
+        ret = "raw";
+        break;
+      case Calibrator.CAL_TEMP:
+        ret = "temp";
+        break;
+    }
+    return ret;
+  }
+
+  /**
+   * Construct a url query string for subsetting a local file.
+   * @param sl starting line number.
+   * @param nl number of lines.
+   * @param lm line magnification.
+   * @param se starting element.
+   * @param ne number of elements.
+   * @param em element magnification.
+   * @param b band number.
+   * @return "?band=B&linele=SL SE&size=NL NE&mag=LM EM"
+   */
+  public final static String makeLocalQuery(int sl, int nl, int lm, int se,
+                                            int ne, int em, int b) {
+    return "?band=" + b + "&linele=" + sl + " " + se + "&size=" + nl + " " +
+           ne + "&mag=" + lm + " " + em;
+  }
+
+  /**
+   * Construct a url query string for subsetting a local file.
+   * @param sl starting line number.
+   * @param nl number of lines.
+   * @param lm line magnification.
+   * @param se starting element.
+   * @param ne number of elements.
+   * @param em element magnification.
+   * @param b band number.
+   * @param c calibration type
+   * @return "?band=B&linele=SL SE&size=NL NE&mag=LM EM&quot&unit=C;
+   */
+  public final static String makeLocalQuery(int sl, int nl, int lm, int se,
+                                            int ne, int em, int b, int c) {
+    String ct = calIntToStr(c);
+    return "?band=" + b + "&linele=" + sl + " " + se + "&size=" + nl + " " +
+           ne + "&mag=" + lm + " " + em + "&unit=" + ct;
+  }
+
+  /**
+   * Construct a url query string for subsetting a local file.
+   * @param sl starting line number.
+   * @param nl number of lines.
+   * @param lm line magnification.
+   * @param se starting element.
+   * @param ne number of elements.
+   * @param em element magnification.
+   * @param b band number.
+   * @param c calibration type as a string
+   * @return "?band=B&linele=SL SE&size=NL NE&mag=LM EM&quot&unit=C;
+   */
+  public final static String makeLocalQuery(int sl, int nl, int lm, int se,
+                                            int ne, int em, int b, String c) {
+    return "?band=" + b + "&linele=" + sl + " " + se + "&size=" + nl + " " +
+           ne + "&mag=" + lm + " " + em + "&unit=" + c;
+  }
+
+
+  /**
+   * Construct a {@link java.net.URL} for subsetting a local file.
+   * @param fpath canonical path to an area file.
+   * @param sl starting line number.
+   * @param nl number of lines.
+   * @param lm line magnification.
+   * @param se starting element.
+   * @param ne number of elements.
+   * @param em element magnification.
+   * @param b band number.
+   * @param u calibration unit
+   * @return a string of the format <code>"file://FPATH?band=B&
+   * linele=SL SE&size=NL NE&mag=LM EM"&unit=U</code>
+   *
+   * @throws MalformedURLException bad URL specification
+   */
+  public final static URL makeLocalSubsetURL(String fpath, int sl, int nl,
+                                             int lm, int se, int ne, int em,
+                                             int b, String u)
+          throws MalformedURLException {
+    String surl = "file://" + fpath +
+                  makeLocalQuery(sl, nl, lm, se, ne, em, b, u);
+    URL url = null;
+    try {
+      url = new URL(surl);
+    }
+    catch (MalformedURLException e) {
+      // this cannot occur because the protocol is hardcoded to a known one
+    }
+    return url;
+  }
+
+  /**
+   * Create an initialized <code>AreaFile</code> instance. First, an attempt is
+   * made to create a <code>URL</code> from <code>src</code>, if an error
+   * occurrs, meaning <code>src</code> is not a valid url, <code>src</code> is
+   * interpreted as a file path.
+   * @param src A relative or canonical path to a local file as a string or a
+   * string representation of a url appropriate for creating an
+   * <code>AreaFile</code> instance. For more information on urls appropriate
+   * for creating <code>AreaFile</code> instances see
+   * {@link #getAreaFileInstance(URL)}.
+   * <p>URLs containing encoded characters in their query strings will be decoded
+   * before parsing.
+   * </p>
+   * @return an initialized, possibly subsetted, instance
+   * @throws AreaFileException on any error constructing the instance.
+   * @throws AddeURLException If the source is a URL and the query string is
+   *  not formatted correctly.
+   */
+  public static final AreaFile getAreaFileInstance(final String src)
+          throws AreaFileException, AddeURLException {
+
+    // handle the special "pop up a gui" case for adde://image?
+    if (src.startsWith("adde://") &&
+        (src.endsWith("image?") || src.endsWith("imagedata?"))) {
+      return new AreaFile(src);
+    }
+
+    // Try to create an instance as a URL first. If a valid URL instance cannot
+    // be created lets try 'src' as a filename. 
+    // NOTE: it's important to make sure the AreaFile and AddeURL exceptions 
+    // are propagated from constructor or factory method.
+    AreaFile af = null;
+    URL url = null;
+    try {
+      url = new URL(src);
+      af = getAreaFileInstance(url);
+    }
+    catch (MalformedURLException e) {
+      af = new AreaFile(src);
+    }
+
+    return af;
+  }
+
+  /**
+   * Create an initialized <code>AreaFile</code> instance.
+   *
+   * <p>A url appropriate for creating an instance will have a protocol of
+   * either <code>adde</code> for remote ADDE data or <code>file</code> for
+   * files on the local disk. Information on consructing ADDE urls can be found
+   * in the {@link edu.wisc.ssec.mcidas.adde.AddeURLConnection} class.</p>
+   *
+   * <p>A local file url may either be a standard file url such as
+   * file:///<absolute file path> or it may specify subsetting
+   * information. If specifying subsetting information, the url can contain the
+   * following parameters from the ADDE image data url specification with the
+   * specified defaults:
+   * <pre>
+   * NAME      DEFAULT
+   * linele  - 0 0 a  Must be used with size.
+   *                  NOTE: only type 'a' is supported at this time
+   * size    - 0 0    Must be used with linele.
+   * mag     - 1 1    Only with linele and size, but not required.
+   * band    - 1      Can be used separately, not required.
+   * unit    - RAW    Calibration type
+   * </pre>
+   * A file url might look like:
+   * <pre>
+   * file://<abs file path>?linele=10 10&band=3&mag=-2 -4&size=500 500&unit=BRIT
+   * </pre>
+   * </p>
+   * <p>URLs containing encoded characters in their query strings will be decoded
+   * before parsing.
+   * </p>
+   * @param url - the url as described above
+   * @return an initialized, possibly subsetted, instance
+   * @throws AreaFileException on any error constructing the instance.
+   * @throws AddeURLException if the query string is not a valid ADDE query
+   *  stirng.
+   */
+  public static final AreaFile getAreaFileInstance(final URL url)
+          throws AddeURLException, AreaFileException {
+
+    // it's a local file, investigate further
+    if (url.getProtocol().equalsIgnoreCase("file")) {
+
+      AreaFile af = null;
+      if (url.getQuery() == null) {
+        af = new AreaFile(url);
+      }
+      else {
+        // open w/o query string
+        af = new AreaFile(url.toString().split("\\?")[0]);
+      }
+
+      // is it a local url with subsetting
+      String query = url.getQuery();
+      if (query != null && query.contains("%")) {
+        try {
+          query = url.toURI().getQuery(); // URI queries are decoded
+        }
+        catch (URISyntaxException e) {
+          throw new AddeURLException(e.getMessage());
+        }
+      }
+      if (query != null) {
+        final String whtspc = "(\\s)+";
+        int startLine = 0;
+        int numLines = af.getAreaDirectory().getLines();
+        int lineMag = 1;
+        int startElem = 0;
+        int numEles = af.getAreaDirectory().getElements();
+        int eleMag = 1;
+        int band = -1;
+        int calType = Calibrator.CAL_NONE;
+        boolean linele = false;
+        boolean size = false;
+        boolean mag = false;
+        int availableLines = numLines;
+        int availableEles = numEles;
+
+        // parse query string
+        String[] props = query.split("&");
+        for (int i = 0; i < props.length; i++) {
+          String[] kv = props[i].split("=");
+
+          if (kv[0].equalsIgnoreCase("mag")) {
+            lineMag = Integer.parseInt(kv[1].split(whtspc)[0]);
+            eleMag = Integer.parseInt(kv[1].split(whtspc)[1]);
+            mag = true;
+          }
+
+          if (kv[0].equalsIgnoreCase("size")) {
+            numLines = Integer.parseInt(kv[1].split(whtspc)[0]);
+            numEles = Integer.parseInt(kv[1].split(whtspc)[1]);
+            size = true;
+          }
+
+          if (kv[0].equalsIgnoreCase("band")) {
+            int bandIdx = -1;
+            int[] bands = af.getAreaDirectory().getBands();
+            if (band == -1) {
+              bandIdx = 0;
+            }
+            else {
+              for (int j = 0; i < bands.length; j++) {
+                if (bands[j] == band) {
+                  bandIdx = j;
+                }
+              }
+            }
+            band = bands[bandIdx];
+          }
+
+          if (kv[0].equalsIgnoreCase("linele")) {
+            String[] vals = kv[1].split(whtspc);
+            startLine = Integer.parseInt(vals[0]);
+            startElem = Integer.parseInt(vals[1]);
+            if (vals.length >= 3 && !vals[2].equalsIgnoreCase("a")) {
+              throw new AddeURLException("Image and earth types are not currenly supported");
+            }
+            availableLines -= startLine;
+            availableEles -= startElem;
+            linele = true;
+          }
+
+          if (kv[0].equalsIgnoreCase("unit")) {
+            calType = calStrToInt(kv[1]);
+            switch (calType) {
+              case Calibrator.CAL_ALB:
+              case Calibrator.CAL_BRIT:
+              case Calibrator.CAL_RAD:
+              case Calibrator.CAL_TEMP:
+              case Calibrator.CAL_RAW:
+              case Calibrator.CAL_NONE:
+                break;
+              default:
+                throw new AreaFileException("Unsupported calibration type: " +
+                                            kv[1]);
+            }
+          }
+
+        }
+
+        if (mag) {
+          availableLines = availableLines / (Math.abs(lineMag) == 0
+                                             ? 1
+                                             : Math.abs(lineMag));
+          availableEles = availableEles / (Math.abs(eleMag) == 0
+                                           ? 1
+                                           : Math.abs(eleMag));
+          if (size) {
+            numLines = Math.min(availableLines, numLines);
+            numEles = Math.min(availableEles, numEles);
+          }
+          else {
+            numLines = availableLines;
+            numEles = availableEles;
+          }
+
+        }
+
+        // recreate with new parameters
+        af = new AreaFile(url.getPath(), startLine, numLines, lineMag,
+                          startElem, numEles, eleMag, band);
+        af.setCalType(calType);
+      }
+      return af;
+
+    }
+    else {
+      return new AreaFile(url);
+    }
+  }
+
+  /**
+   * See {@link AreaFile#AreaFile(String, int, int, int, int, int, int, int)}
+   *
+   * @param fpath  the path to the file
+   * @param startLine the starting image line
+   * @param numLines the total number of lines to return
+   * @param lineMag the line magnification. Valid values are >= -1. -1, 0, and
+   *                1 are all taken to be full line resolution, 2 is every
+   *                other line, 3 every third, etc...
+   * @param startElem the starting image element
+   * @param numEles the total number of elements to return
+   * @param eleMag the element magnification. Valid values are >= -1. -1, 0, and 1
+   *               are all taken to be full element resolution, 2 is every
+   *               other element, 3 every third, etc...
+   * @param band the 1-based band number for the subset, which must be present
+   *             in the directory blocks band map or -1 for the first band
+   *
+   * @return the AreaFile instance
+   *
+   * @throws AreaFileException
+   */
+  public final static AreaFile getAreaFileInstance(String fpath,
+          int startLine, int numLines, int lineMag, int startElem,
+          int numEles, int eleMag, int band)
+          throws AreaFileException {
+    return new AreaFile(fpath, startLine, numLines, lineMag, startElem,
+                        numEles, eleMag, band);
+  }
+
+  /**
+   * Copy an area file from one place to another
+   * @param source  source file or ADDE url
+   * @param outputFile  name of the output file
+   * @throws AreaFileException on any error constructing the instance.
+   * @throws AddeURLException If the source is a URL and the query string is
+   *  not formatted correctly.
+   */
+  public static void copyAreaFile(String source, String outputFile)
+          throws AddeURLException, AreaFileException {
+    copyAreaFile(source, outputFile, false);
+  }
+
+  /**
+   * Copy an area file from one place to another
+   * @param source  source file or ADDE url
+   * @param outputFile  name of the output file
+   * @param verbose  true to print out status messages
+   * @throws AreaFileException on any error constructing the instance.
+   * @throws AddeURLException If the source is a URL and the query string is
+   *  not formatted correctly.
+   */
+  public static void copyAreaFile(String source, String outputFile,
+                                  boolean verbose)
+          throws AddeURLException, AreaFileException {
+    AreaFile area = getAreaFileInstance(source);
+    area.save(outputFile, verbose);
+  }
+}
+
diff --git a/edu/wisc/ssec/mcidas/Calibrator.java b/edu/wisc/ssec/mcidas/Calibrator.java
new file mode 100644
index 0000000..46add52
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/Calibrator.java
@@ -0,0 +1,104 @@
+//
+// Calibrator.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * interface for creating Calibrator classes.
+ *
+ * @version 1.2 16 Nov 1998
+ * @author Tommy Jasmin, SSEC
+ */
+
+public interface Calibrator {
+
+  /**
+	 * @author tommyj
+	 *
+	 */
+	public class CalibratorFY2 {
+
+	}
+
+public static final int CAL_NONE = -1;
+  public static final int CAL_MIN  = 1;
+  public static final int CAL_RAW  = 1;
+  public static final int CAL_RAD  = 2;
+  public static final int CAL_ALB  = 3;
+  public static final int CAL_TEMP = 4;
+  public static final int CAL_BRIT = 5;
+  public static final int CAL_MAX  = 5;
+
+  /** FY-2D */
+  public static final int SENSOR_FY2D = 36;
+  /** FY-2E */
+  public static final int SENSOR_FY2E = 37;
+  /** FY-2F */
+  public static final int SENSOR_FY2F = 38;
+  /** FY-2G */
+  public static final int SENSOR_FY2G = 39;
+  /** FY-2H */
+  public static final int SENSOR_FY2H = 40;
+  /** Meteosat Second Generation imager. */
+  public static final int SENSOR_MSG_IMGR = 51;
+  /** GOES 8 imager. */
+  public static final int SENSOR_GOES8_IMGR = 70;
+  /** GOES 8 sounder. */
+  public static final int SENSOR_GOES8_SNDR = 71;
+  /** GOES 9 imager. */
+  public static final int SENSOR_GOES9_IMGR = 72;
+  /** GOES 9 sounder. */
+  public static final int SENSOR_GOES9_SNDR = 73;
+  /** GOES 10 imager. */
+  public static final int SENSOR_GOES10_IMGR = 74;
+  /** GOES 10 sounder. */
+  public static final int SENSOR_GOES10_SNDR = 75;
+  /** GOES 12 imager. */
+  public static final int SENSOR_GOES12_IMGR = 78;
+  /** GOES 12 sounder. */
+  public static final int SENSOR_GOES12_SNDR = 79;
+  /** GOES 13 imager. */
+  public static final int SENSOR_GOES13_IMGR = 180;
+  /** GOES 13 sounder. */
+  public static final int SENSOR_GOES13_SNDR = 181;
+  
+  public int setCalType (
+    int calType
+  );
+
+  public float[] calibrate (
+    float[] inputData,
+    int band,
+    int calTypeOut
+  );
+
+  public float calibrate (
+    float inputPixel,
+    int band,
+    int calTypeOut
+  );
+
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorDefault.java b/edu/wisc/ssec/mcidas/CalibratorDefault.java
new file mode 100644
index 0000000..734064b
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorDefault.java
@@ -0,0 +1,168 @@
+//
+// CalibratorDefault.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * CalibratorDefault creates a Calibrator object designed to
+ * act in the absence of a Calibrator for a particular sensor.
+ * In other words, if a sensor is not supported yet, just pass
+ * the input value on as the calibrated value.
+ *
+ * @version 1.2 6 Aug 1999
+ * @author Tommy Jasmin, SSEC
+ */
+
+public class CalibratorDefault implements Calibrator {
+
+  // public static final int CAL_NONE = -1;
+  // public static final int CAL_MIN  = 1;
+  // public static final int CAL_RAW  = 1;
+  // public static final int CAL_RAD  = 2;
+  // public static final int CAL_ALB  = 3;
+  // public static final int CAL_TEMP = 4;
+  // public static final int CAL_BRIT = 5;
+  // public static final int CAL_MAX  = 5;
+
+  // var to store current cal type
+  protected static int curCalType = 0;
+
+  /**
+   *
+   * constructor - does nothing for default calibrator
+   *
+   * @param dis         data input stream
+   * @param ad          AncillaryData object
+   *
+   */
+
+  public CalibratorDefault(DataInputStream dis, AncillaryData ad) 
+    throws IOException
+
+  {
+    return;
+  }
+
+  /**
+   *
+   * set calibration type of current (input) data
+   *
+   * @param calType     one of the types defined in Calibrator interface
+   *
+   */
+
+  public int setCalType(int calType) {
+    if ((calType < Calibrator.CAL_MIN) || (calType > Calibrator.CAL_MAX)) {
+      return -1;
+    }
+    curCalType = calType;
+    return 0;
+  }
+
+  /**
+   *
+   * calibrate data buffer to specified units.
+   *
+   * @param inputData   input data buffer
+   * @param band        channel/band number
+   * @param calTypeOut  units to convert input buffer to
+   *
+   */
+
+  public float[] calibrate (
+    float[] inputData,
+    int band,
+    int calTypeOut
+  )
+
+  {
+
+    // create the output data buffer
+    float[] outputData = new float[inputData.length];
+
+    // just call the other calibrate routine for each data point
+    for (int i = 0; i < inputData.length; i++) {
+      outputData[i] = calibrate(inputData[i], band, calTypeOut);
+    }
+
+    // return the calibrated buffer
+    return outputData;
+
+  }
+
+  /**
+   *
+   * calibrate single value to specified units.
+   *
+   * @param inputPixel  input data value
+   * @param band        channel/band number
+   * @param calTypeOut  units to convert input buffer to
+   *
+   */
+
+  public float calibrate (
+    float inputPixel,
+    int band,
+    int calTypeOut
+  )
+
+  {
+
+    float outputData = 0.0f;
+
+    // validate, then calibrate for each combination starting with cur type
+    switch (curCalType) {
+
+      case CAL_RAW:
+        outputData = inputPixel;
+        break;
+
+      case CAL_RAD:
+        outputData = inputPixel;
+        break;
+
+      case CAL_ALB:
+        outputData = inputPixel;
+        break;
+
+      case CAL_TEMP:
+        outputData = inputPixel;
+        break;
+
+      case CAL_BRIT:
+        outputData = inputPixel;
+        break;
+
+    }
+
+    return outputData;
+
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorException.java b/edu/wisc/ssec/mcidas/CalibratorException.java
new file mode 100644
index 0000000..16c0dab
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorException.java
@@ -0,0 +1,51 @@
+//
+// CalibratorException.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+
+package edu.wisc.ssec.mcidas;
+
+
+/**
+ * CalibratorException class is to handle exceptions when calibrating data.
+ *
+ * @version $Id: CalibratorException.java,v 1.4 2009-03-02 23:34:50 curtis Exp $
+ * @author Bruce Flynn, SSEC
+ */
+public class CalibratorException extends McIDASException {
+
+  /**
+   * Constructs an CalibratorException with no specified detail message.
+   */
+  public CalibratorException() {super(); }
+
+  /**
+   * Constructs an CalibratorException with the specified detail message.
+   *
+   * @param  s  the detail message.
+   */
+  public CalibratorException(String s) {super(s); }
+
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorFY2.java b/edu/wisc/ssec/mcidas/CalibratorFY2.java
new file mode 100644
index 0000000..3a88f36
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorFY2.java
@@ -0,0 +1,189 @@
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2012 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.IOException;
+
+/**
+ * @author tommyj
+ * Adapted from the McIDAS-X module kbxfy2.dlm
+ * NOT TESTED YET, USE AT YOUR OWN RISK!
+ *
+ */
+
+public class CalibratorFY2 extends CalibratorDefault implements Calibrator {
+
+	private boolean isVis = false;
+    /** 
+     * Current cal type as set by <code>setCalType</code>
+     */
+    private int curCalType = Calibrator.CAL_RAW;
+    private int numFY2Bands = 4;
+    private int[] visDetectorId = { 0x6C6C0000, 0xB4B40000, 0xD8D80000, 0xFCFC0000 };
+    private int[] prefix;
+    private int[] calBlock;
+    private static final int VIS_BAND_OFFSET = 192;
+    private int[][] albedoFromRaw = new int[numFY2Bands][256];
+    private int[][] britFromAlbedo = new int[numFY2Bands][256];
+    private int[] tempTable = new int[1024];
+    private int[] radTable = new int[1024];
+    private int[] britTable = new int[1024];
+    private int lastBand = -1;
+	
+	public CalibratorFY2(int[] prefix, int[] areaDir, int[] calBlock)
+			throws IOException {
+		super(null, null);
+		this.prefix = prefix;
+		this.calBlock = calBlock;
+		// initialize tables
+		int visOffset = 0;
+		float albedo = 0.0f;
+		for (int i = 0; i < numFY2Bands; i++) {
+			visOffset = VIS_BAND_OFFSET + (i * 64);
+			for (int j = 0; j < 256; j+=4) {
+				albedo = calBlock[visOffset + ((j + 1) / 4)] / 10000.0f;
+				britFromAlbedo[i][j] = (int) Math.round(0.5 + 25.5 * Math.sqrt(albedo));
+                albedoFromRaw[i][j] = (int) Math.round(albedo * 100.0f);
+
+                britFromAlbedo[i][j + 1] = (int) Math.round(0.5 + 25.5 * Math.sqrt(albedo));
+                albedoFromRaw[i][j + 1] = (int) Math.round(albedo * 100.0f);
+
+                britFromAlbedo[i][j + 2] = (int) Math.round(0.5 + 25.5 * Math.sqrt(albedo));
+                albedoFromRaw[i][j + 2] = (int) Math.round(albedo * 100.0f);
+
+                britFromAlbedo[i][j + 3] = (int) Math.round(0.5 + 25.5 * Math.sqrt(albedo));
+                albedoFromRaw[i][j + 3] = (int) Math.round(albedo * 100.0f);
+			}
+		}
+	}
+	
+	/* (non-Javadoc)
+	 * @see edu.wisc.ssec.mcidas.CalibratorDefault#calibrate(float, int, int)
+	 */
+	@Override
+	public float calibrate(float inVal, int band, int calTypeOut) {
+		
+		int detector = 0;
+		int irOffset = 0;
+		float radiance = 0.0f;
+		float temperature = 0.0f;
+		float outVal = 0.0f;
+		
+		// first set the vis-or-ir flag
+		if (band == 1) {
+			isVis = true;
+		} else {
+			isVis = false;
+		}
+		
+		if (isVis) {
+			// read the detector number from line prefix
+			detector = 1;
+			for (int i = 0; i < numFY2Bands; i++) {
+				if (visDetectorId[i] == prefix[1]) {
+					detector = i + 1;
+				}
+			}
+		}
+		
+		// for IR data, we will generate new tables when the band changes
+		if (! isVis) {
+			if (band != lastBand) {
+				irOffset = calBlock[(band - 2) * 2 + 8] / 4;
+				for (int i = 0; i < 1024; i++) {
+					temperature = calBlock[irOffset] / 1000.0f;
+					radiance = tempToRad(temperature, band);
+					tempTable[i] = Math.round(temperature * 100.f);
+					radTable[i] = Math.round(radiance * 1000.f);
+					if (temperature >= 242.0f) {
+						britTable[i] = Math.max(660 - (int) (2 * temperature), 0);
+					} else {
+						britTable[i] = Math.min(418 - (int) (temperature), 255);
+					}
+				}
+			}
+		}
+		
+		// update last band seen
+		lastBand = band;
+		
+		// finally, do the calibration
+		if (calTypeOut == curCalType) {
+			outVal = inVal;
+		} else {
+			if (isVis) {
+				if (calTypeOut == Calibrator.CAL_ALB) {
+					outVal = albedoFromRaw[detector][(int) inVal];
+				}
+				if (calTypeOut == Calibrator.CAL_BRIT) {
+					outVal = britFromAlbedo[detector][(int) inVal];
+				}
+			} else {
+				if (calTypeOut == Calibrator.CAL_RAD) {
+					outVal = radTable[(int) inVal];
+				}
+				if (calTypeOut == Calibrator.CAL_TEMP) {
+					outVal = tempTable[(int) inVal];
+				}
+				if (calTypeOut == Calibrator.CAL_BRIT) {
+					outVal = britTable[(int) inVal];
+				}
+			}
+		}
+		
+		return outVal;
+		
+	}
+	
+	/**
+	 * 
+	 * calibrate from temperature to radiance
+	 * 
+	 * @param inVal
+	 *            input data value
+	 * @param band
+	 *            channel/band number
+	 * 
+	 */
+
+	public float tempToRad(float inVal, int band) {
+
+		float outVal = -1f;
+		// derived constants for each band
+		float[] fk1 = { 9280.38f, 7136.31f, 37258.20f, 224015.00f };
+		float[] fk2 = { 1323.95f, 1212.95f, 2104.22f, 3826.28f };
+	    // derived temp constants for each band
+		float[] tc1 = { 0.72122f, 1.00668f, 3.76883f, 4.00279f };
+		float[] tc2 = { 0.99750f, 0.99621f, 0.99108f, 0.99458f };
+	    // temperature adjusted by derived constants
+	    float adjustedTemp;
+
+		adjustedTemp = tc1[band - 1] + tc2[band - 1] * inVal;
+		outVal = (float) (fk1[band - 1] / 
+				 (Math.exp(fk2[band - 1] / adjustedTemp) - 1.0f));
+
+		return (outVal);
+	}
+	  
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorFactory.java b/edu/wisc/ssec/mcidas/CalibratorFactory.java
new file mode 100644
index 0000000..e9e04aa
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorFactory.java
@@ -0,0 +1,149 @@
+//
+// CalibratorFactory.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Utility class for creating <code>Calibrator</code> instances.
+ * 
+ * @author Bruce Flynn, SSEC
+ * @version $Id: CalibratorFactory.java,v 1.7 2009-05-06 18:45:53 rickk Exp $
+ */
+public final class CalibratorFactory {
+
+	/** Disallow instatiantion. */
+	private CalibratorFactory() {}
+	
+    /**
+     * Get an appropriate <code>Calibrator</code> for the sensor id provided.
+     * This assumes a RAW source data format. See the McIDAS Users Guide 
+     * <a href="http://www.ssec.wisc.edu/mcidas/doc/users_guide/current/app_c-1.html">Appendix C</a>
+     * for a table of sensor ids.
+     * @param id Sensor id from the directory block
+     * @param cal Calibration block used to initialize the <code>Calibrator
+     * </code>
+     * @return initialized <code>Calibrator</code> with a source calibration
+     * type of RAW.
+     * @throws CalibratorException on an error initializing the object.
+     */
+	public final static Calibrator getCalibrator(final int id, final int[] cal) 
+		throws CalibratorException {
+		
+		return getCalibrator(id, Calibrator.CAL_RAW, cal);
+	}
+	
+    /**
+     * Get an appropriate <code>Calibrator</code> for the sensor id provided.
+     * See the McIDAS Users Guide 
+     * <a href="http://www.ssec.wisc.edu/mcidas/doc/users_guide/current/app_c-1.html">Appendix C</a>
+     * for a table of sensor ids.
+     * @param id Sensor id from the directory block
+     * @param srcType the source data type from the directory block
+     * @param cal Calibration block used to initialize the 
+     *        <code>Calibrator</code>
+     * @return initialized <code>Calibrator</code>.
+     * @throws CalibratorException on an error initializing the object or if the
+     *         sensor is unknown.
+     */
+	public final static Calibrator getCalibrator(
+			final int id, final int srcType, final int[] cal) 
+		throws CalibratorException {
+		
+		Calibrator calibrator = null;
+	    switch (id) {
+	      
+	      case Calibrator.SENSOR_MSG_IMGR:
+	    	  calibrator = new CalibratorMsg(cal);
+	    	  calibrator.setCalType(srcType);
+	    	  break;
+	        
+	      case Calibrator.SENSOR_GOES8_IMGR:
+	      case Calibrator.SENSOR_GOES8_SNDR:
+	    	  calibrator = new CalibratorGvarG8(id, cal);
+	    	  calibrator.setCalType(srcType);
+	    	  break;
+	    	  
+	      case Calibrator.SENSOR_GOES9_IMGR:
+	      case Calibrator.SENSOR_GOES9_SNDR:
+	    	  calibrator = new CalibratorGvarG9(id, cal);
+	    	  calibrator.setCalType(srcType);
+	    	  break;
+	    	  
+	      case Calibrator.SENSOR_GOES10_IMGR:
+	      case Calibrator.SENSOR_GOES10_SNDR:
+	    	  calibrator = new CalibratorGvarG10(id, cal);
+	    	  calibrator.setCalType(srcType);
+	    	  break;
+	    	  
+	      case Calibrator.SENSOR_GOES12_IMGR:
+	      case Calibrator.SENSOR_GOES12_SNDR:
+	    	  calibrator = new CalibratorGvarG12(id, cal);
+	    	  calibrator.setCalType(srcType);
+	    	  break;
+	    	  
+	      case Calibrator.SENSOR_GOES13_IMGR:
+	      case Calibrator.SENSOR_GOES13_SNDR:
+	    	  calibrator = new CalibratorGvarG13(id, cal);
+	    	  calibrator.setCalType(srcType);
+	    	  break;
+
+	      default:
+	        throw new CalibratorException(
+	            "Unknown or unimplemented sensor id: " + id
+	        );
+	    }
+	    return calibrator;
+	}
+  
+  /**
+   * Check if there is a <code>Calibrator</code> implemented for a sensor.
+   * 
+   * @param id Id of the sensor from the McIDAS Users Guide
+   * <a href="http://www.ssec.wisc.edu/mcidas/doc/users_guide/current/app_c-1.html">Appendix C</a>
+   * @return True if there is an implemented <code>Calibrator</code>, false
+   *         otherwise.
+   *
+   * see The McIDAS Users Guide 
+   */
+  public final static boolean hasCalibrator(int id) {
+    switch (id) {
+      case Calibrator.SENSOR_GOES13_IMGR:
+      case Calibrator.SENSOR_GOES13_SNDR:
+      case Calibrator.SENSOR_GOES12_IMGR:
+      case Calibrator.SENSOR_GOES12_SNDR:
+      case Calibrator.SENSOR_GOES10_IMGR:
+      case Calibrator.SENSOR_GOES10_SNDR:
+      case Calibrator.SENSOR_GOES8_IMGR:
+      case Calibrator.SENSOR_GOES8_SNDR:
+      case Calibrator.SENSOR_GOES9_IMGR:
+      case Calibrator.SENSOR_GOES9_SNDR:
+      case Calibrator.SENSOR_MSG_IMGR:
+        return true;
+      default:
+        return false;
+    }
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorGvar.java b/edu/wisc/ssec/mcidas/CalibratorGvar.java
new file mode 100644
index 0000000..051efa8
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorGvar.java
@@ -0,0 +1,402 @@
+//
+// CalibratorGvar.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * CalibratorGvar creates a Calibrator object designed specifically
+ * to deal with GVAR data.  Not fully implemented at present - some
+ * calibrations remain to be done.
+ *
+ * @version 1.5 16 Nov 1998
+ * @author Tommy Jasmin, SSEC
+ */
+
+public abstract class CalibratorGvar implements Calibrator {
+
+  protected static final int NUM_BANDS_IMAGER = 5;
+  protected static final int NUM_BANDS_SOUNDER = 18;
+
+  protected static final int NUM_VIS_DETECTORS = 8;
+  protected static final int NUM_IR_DETECTORS  = 2;
+  protected static final int NUM_IR_BANDS      = 4;
+
+  protected static final int LOOKUP_TABLE_SZ_IMGR = 1024;
+  protected static final int LOOKUP_TABLE_SZ_SNDR = 32768;
+
+  // var to store current cal type
+  protected static int curCalType = 0;
+  protected static int index = 0;
+
+  protected float [] visBiasCoef  = new float [NUM_VIS_DETECTORS];
+  protected float [] visGain1Coef = new float [NUM_VIS_DETECTORS];
+  protected float [] visGain2Coef = new float [NUM_VIS_DETECTORS];
+  protected float visRadToAlb = 0.0f;
+  protected float [][] irBiasCoef = new float [NUM_IR_DETECTORS][NUM_IR_BANDS];
+  protected float [][] irGainCoef = new float [NUM_IR_DETECTORS][NUM_IR_BANDS];
+  protected float [] sBiasCoef = new float [NUM_BANDS_SOUNDER];
+  protected float [] sGainCoef = new float [NUM_BANDS_SOUNDER];
+  protected float [][] lookupTable;
+
+  // used in calibrator method
+  private static float gain = 0.0f;
+  private static float bias = 0.0f;
+  private static int scale = 1;
+  private static int bandNum = 0;
+  private static int sid = 0;
+
+  /**
+   *
+   * constructor
+   *
+   * @param dis         data input stream 
+   * @param ad          AncillaryData object
+   * @param calBlock    calibration parameters array
+   *
+   */
+
+  public CalibratorGvar (
+    DataInputStream dis, 
+    AncillaryData ad, 
+    int [] calBlock
+  ) 
+    throws IOException
+
+  {
+	  this(ad.getSensorId(), calBlock);
+  }
+	  
+	  
+  public CalibratorGvar(final int sensorId, int[] calBlock) {
+
+    int calIndex = 0;
+    sid = sensorId;
+
+    // now, correct for satellites starting with G12 (sid = 78)
+    int irOffset = 2;
+    if (sid > 77) irOffset = 0;
+
+    //System.out.println("xxx sid = "+sid);
+    if ((sid % 2) == 0) {
+
+      // initialize lookup table
+      lookupTable = new float [NUM_BANDS_IMAGER] [LOOKUP_TABLE_SZ_IMGR];
+
+      for (int i = 0; i < NUM_BANDS_IMAGER; i++) {
+        for (int j = 0; j < LOOKUP_TABLE_SZ_IMGR; j++) {
+          lookupTable [i][j] = Float.NaN;
+        }
+      }
+
+      // read in an imager format cal block
+      for (int i = 0; i < NUM_VIS_DETECTORS; i++) {
+        visBiasCoef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_VIS_DETECTORS; i++) {
+        visGain1Coef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_VIS_DETECTORS; i++) {
+        visGain2Coef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      visRadToAlb = (float) ConversionUtility.GouldToNative(calBlock[calIndex]);
+      calIndex++;
+
+      for (int i = 0; i < NUM_IR_BANDS; i++) {
+        irBiasCoef[0][(i + irOffset) % NUM_IR_BANDS] = 
+          (float) ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_IR_BANDS; i++) {
+        irBiasCoef[1][(i + irOffset) % NUM_IR_BANDS] = 
+          (float) ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_IR_BANDS; i++) {
+        irGainCoef[0][(i + irOffset) % NUM_IR_BANDS] = 
+          (float) ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_IR_BANDS; i++) {
+        irGainCoef[1][(i + irOffset) % NUM_IR_BANDS] = 
+          (float) ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+    } else {
+
+      // initialize lookup table
+      lookupTable = new float [NUM_BANDS_SOUNDER + 1] [LOOKUP_TABLE_SZ_SNDR];
+      for (int i = 0; i < NUM_BANDS_SOUNDER + 1; i++) {
+        for (int j = 0; j < LOOKUP_TABLE_SZ_SNDR; j++) {
+          lookupTable [i][j] = Float.NaN;
+        }
+      }
+
+      // read in a sounder format cal block
+      for (int i = 0; i < NUM_VIS_DETECTORS / 2; i++) {
+        visBiasCoef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_VIS_DETECTORS / 2; i++) {
+        visGain1Coef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_VIS_DETECTORS / 2; i++) {
+        visGain2Coef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      visRadToAlb = (float) ConversionUtility.GouldToNative(calBlock[calIndex]);
+      calIndex++;
+
+      for (int i = 0; i < NUM_BANDS_SOUNDER; i++) {
+        sBiasCoef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+      for (int i = 0; i < NUM_BANDS_SOUNDER; i++) {
+        sGainCoef[i] = (float) 
+          ConversionUtility.GouldToNative(calBlock[calIndex]);
+        calIndex++;
+      }
+
+    }
+
+  }
+
+  /**
+   *
+   * set calibration type of current (input) data
+   *
+   * @param calType     one of the types defined in Calibrator interface
+   *
+   */
+
+  public int setCalType(int calType) {
+    if ((calType < Calibrator.CAL_MIN) || (calType > Calibrator.CAL_MAX)) {
+      return -1;
+    }
+    curCalType = calType;
+    return 0;
+  }
+
+  /**
+   *
+   * calibrate from radiance to temperature
+   *
+   * @param inVal     	input data value
+   * @param band        channel/band number
+   * @param sId        	sensor id number
+   *
+   */
+
+  public abstract float radToTemp(float inVal, int band, int sId);
+
+  /**
+   *
+   * calibrate data buffer to specified units.
+   *
+   * @param inputData	input data buffer
+   * @param band        channel/band number
+   * @param calTypeOut  units to convert input buffer to
+   *
+   */
+ 
+  public float[] calibrate (
+    float[] inputData,
+    int band,
+    int calTypeOut
+  )
+
+  {
+
+    // create the output data buffer
+    float[] outputData = new float[inputData.length];
+
+    // just call the other calibrate routine for each data point
+    for (int i = 0; i < inputData.length; i++) {
+      outputData[i] = calibrate(inputData[i], band, calTypeOut);
+    }
+
+    // return the calibrated buffer
+    return outputData;
+
+  }
+
+  /**
+   *
+   * calibrate single value to specified units.
+   *
+   * @param inputPixel  input data value 
+   * @param band        channel/band number  
+   * @param calTypeOut  units to convert input buffer to  
+   *
+   */
+
+  public float calibrate (
+    float inputPixel,
+    int band,
+    int calTypeOut
+  )
+
+  {
+
+    float outputData = 0.0f;
+    //System.out.println("####  input pixel="+inputPixel);
+
+    //System.out.println("####  cal band = "+band);
+    //System.out.println("####  len lookup = "+lookupTable.length+" "+lookupTable[3].length);
+
+    // load gain and bias constants based on band requested
+    if (band != bandNum) {
+      bandNum = band;
+      if ((sid % 2) == 0) {
+        if (band == 1) {
+          gain = visGain1Coef[0];
+          bias = visBiasCoef[0];
+        } else {
+          gain = irGainCoef[0][band - 2];
+          bias = irBiasCoef[0][band - 2];
+          //System.out.println("####  band="+band+"  gain="+gain+"  bias"+bias);
+        }
+        scale = 32;
+
+      } else {
+        if (band == 19) {
+          gain = visGain1Coef[0];
+          bias = visBiasCoef[0];
+        } else {
+          gain = sGainCoef[band - 1];
+          bias = sBiasCoef[band - 1];
+        }
+        scale = 2;
+      }
+    }
+
+    // check lookup table first, if there is an entry, use it
+    if (curCalType == CAL_BRIT) {
+      // one byte values are signed, so take absolute value for index
+      index = (int) inputPixel + 128; 
+    } else {
+      // otherwise scale down the 1K possible 15 bit values to an index
+      index = (int) inputPixel / scale;
+    }
+    //System.out.println("xxx band = "+band+" index = "+index+" scale="+scale+ " inputPixel"+inputPixel);
+
+    if (!(Float.isNaN(lookupTable[band - 1][index]))) {
+      return (lookupTable[band - 1][index]);
+    }
+
+    // validate, then calibrate for each combination starting with cur type
+    switch (curCalType) {
+
+      case CAL_RAW:
+
+        outputData = inputPixel;
+
+        // if they want raw, just break right away
+        if (calTypeOut == CAL_RAW) {
+          break;
+        }
+
+        // convert to radiance 
+        if ((sid % 2) == 0) {
+          outputData = inputPixel / scale;
+        }
+        outputData = (outputData - bias) / gain;
+
+        // if they want radiance we are done
+        if (calTypeOut == CAL_RAD) {
+          break;
+        }
+
+        // otherwise, convert to temperature
+        outputData = radToTemp(outputData, band, sid);
+
+        // if they want temperature, break here 
+        if (calTypeOut == CAL_TEMP) {
+          break;
+        }
+
+        // compute brightness from temperature
+        if (outputData >= 242.0f) {
+          outputData = Math.max(660 - (int) (outputData * 2), 0);
+        } else {
+          outputData = Math.min(418 - (int) outputData, 255);
+        }
+
+        // if they want brightness, break here 
+        if (calTypeOut == CAL_BRIT) {
+          break;
+        }
+
+        break;
+
+      case CAL_RAD:
+        outputData = inputPixel;
+        break;
+
+      case CAL_ALB:
+        outputData = inputPixel;
+        break;
+
+      case CAL_TEMP:
+        outputData = inputPixel;
+        break;
+
+      case CAL_BRIT:
+        outputData = inputPixel;
+        break;
+
+    }
+
+    lookupTable[band - 1][index] = outputData;
+    return outputData;
+
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorGvarG10.java b/edu/wisc/ssec/mcidas/CalibratorGvarG10.java
new file mode 100644
index 0000000..72d83a9
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorGvarG10.java
@@ -0,0 +1,208 @@
+//
+// CalibratorGvarG10.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * CalibratorGvarG10 creates a Calibrator object designed specifically
+ * to deal with GOES 10 data.  Not fully implemented at present - some
+ * calibrations remain to be done.  It provides all the constants 
+ * specific to the GOES 10 imager and sounder sensors.
+ *
+ * @version 1.3 6 Aug 1999
+ * @author Tommy Jasmin, SSEC
+ */
+
+public class CalibratorGvarG10 extends CalibratorGvar {
+
+  protected static float [] imager10FK1  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder10FK1 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager10FK2  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder10FK2 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager10TC1  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder10TC1 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager10TC2  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder10TC2 = new float[NUM_BANDS_SOUNDER];
+
+  // the following static init block sets the class temp/rad constants
+  static {
+
+    imager10FK1[0] = 0.00000E0f;
+    imager10FK1[1] = 0.19841E6f;
+    imager10FK1[2] = 0.39086E5f;
+    imager10FK1[3] = 0.97744E4f;
+    imager10FK1[4] = 0.68286E4f;
+
+    sounder10FK1[0]  = 0.37305E4f;
+    sounder10FK1[1]  = 0.40039E4f;
+    sounder10FK1[2]  = 0.43124E4f;
+    sounder10FK1[3]  = 0.46616E4f;
+    sounder10FK1[4]  = 0.49734E4f;
+    sounder10FK1[5]  = 0.58698E4f;
+    sounder10FK1[6]  = 0.68161E4f;
+    sounder10FK1[7]  = 0.89404E4f;
+    sounder10FK1[8]  = 0.12973E5f;
+    sounder10FK1[9]  = 0.28708E5f;
+    sounder10FK1[10] = 0.34401E5f;
+    sounder10FK1[11] = 0.43086E5f;
+    sounder10FK1[12] = 0.12468E6f;
+    sounder10FK1[13] = 0.12882E6f;
+    sounder10FK1[14] = 0.13532E6f;
+    sounder10FK1[15] = 0.16853E6f;
+    sounder10FK1[16] = 0.18862E6f;
+    sounder10FK1[17] = 0.22487E6f;
+
+    imager10FK2[0] = 0.00000E0f;
+    imager10FK2[1] = 0.36745E4f;
+    imager10FK2[2] = 0.21381E4f;
+    imager10FK2[3] = 0.13470E4f;
+    imager10FK2[4] = 0.11953E4f;
+
+    sounder10FK2[0]  = 0.97710E3f;
+    sounder10FK2[1]  = 0.10004E4f;
+    sounder10FK2[2]  = 0.10255E4f;
+    sounder10FK2[3]  = 0.10524E4f;
+    sounder10FK2[4]  = 0.10754E4f;
+    sounder10FK2[5]  = 0.11365E4f;
+    sounder10FK2[6]  = 0.11945E4f;
+    sounder10FK2[7]  = 0.13076E4f;
+    sounder10FK2[8]  = 0.14804E4f;
+    sounder10FK2[9]  = 0.19291E4f;
+    sounder10FK2[10] = 0.20490E4f;
+    sounder10FK2[11] = 0.22087E4f;
+    sounder10FK2[12] = 0.31474E4f;
+    sounder10FK2[13] = 0.31818E4f;
+    sounder10FK2[14] = 0.32345E4f;
+    sounder10FK2[15] = 0.34800E4f;
+    sounder10FK2[16] = 0.36131E4f;
+    sounder10FK2[17] = 0.38311E4f;
+
+    imager10TC1[0] = 0.00000f;
+    imager10TC1[1] = 0.62226f;
+    imager10TC1[2] = 0.61438f;
+    imager10TC1[3] = 0.27791f;
+    imager10TC1[4] = 0.21145f;
+
+    sounder10TC1[0]  = 0.00988f;
+    sounder10TC1[1]  = 0.01196f;
+    sounder10TC1[2]  = 0.01245f;
+    sounder10TC1[3]  = 0.01245f;
+    sounder10TC1[4]  = 0.01366f;
+    sounder10TC1[5]  = 0.04311f;
+    sounder10TC1[6]  = 0.13973f;
+    sounder10TC1[7]  = 0.11707f;
+    sounder10TC1[8]  = 0.03979f;
+    sounder10TC1[9]  = 0.14968f;
+    sounder10TC1[10] = 0.27603f;
+    sounder10TC1[11] = 0.13049f;
+    sounder10TC1[12] = 0.02008f;
+    sounder10TC1[13] = 0.01834f;
+    sounder10TC1[14] = 0.02017f;
+    sounder10TC1[15] = 0.05292f;
+    sounder10TC1[16] = 0.05330f;
+    sounder10TC1[17] = 0.28683f;
+
+    imager10TC2[0] = 0.00000f;
+    imager10TC2[1] = 0.99912f;
+    imager10TC2[2] = 0.99857f;
+    imager10TC2[3] = 0.99905f;
+    imager10TC2[4] = 0.99919f;
+
+    sounder10TC2[0]  = 0.99995f;
+    sounder10TC2[1]  = 0.99994f;
+    sounder10TC2[2]  = 0.99994f;
+    sounder10TC2[3]  = 0.99995f;
+    sounder10TC2[4]  = 0.99994f;
+    sounder10TC2[5]  = 0.99983f;
+    sounder10TC2[6]  = 0.99947f;
+    sounder10TC2[7]  = 0.99959f;
+    sounder10TC2[8]  = 0.99988f;
+    sounder10TC2[9]  = 0.99962f;
+    sounder10TC2[10] = 0.99933f;
+    sounder10TC2[11] = 0.99970f;
+    sounder10TC2[12] = 0.99997f;
+    sounder10TC2[13] = 0.99997f;
+    sounder10TC2[14] = 0.99997f;
+    sounder10TC2[15] = 0.99992f;
+    sounder10TC2[16] = 0.99992f;
+    sounder10TC2[17] = 0.99961f;
+ 
+  }
+
+  /**
+   *
+   * constructor
+   *
+   * @param dis         data input stream
+   * @param ad          AncillaryData object
+   * @param cb		    calibration parameters array
+   *
+   */
+
+  public CalibratorGvarG10(DataInputStream dis, AncillaryData ad, int [] cb) 
+    throws IOException
+  {
+    super(dis, ad, cb);
+  }
+  
+  public CalibratorGvarG10(int sensorId, int[] cb) {
+	super(sensorId, cb);
+  }
+
+  /**
+   *
+   * calibrate from radiance to temperature
+   *
+   * @param inVal       input data value
+   * @param band        channel/band number
+   * @param sId         sensor id number
+   *
+   */
+
+  public float radToTemp(float inVal, int band, int sId) {
+ 
+    double expn;
+    double temp;
+    float outVal;
+ 
+    if ((sId % 2) == 0) {
+      expn = (imager10FK1[band - 1] / inVal) + 1.0;
+      temp = imager10FK2[band - 1] / Math.log(expn);
+      outVal = (float) ((temp - imager10TC1[band - 1]) / imager10TC2[band - 1]);
+    } else {
+      expn = (sounder10FK1[band - 1] / inVal) + 1.0;
+      temp = sounder10FK2[band - 1] / Math.log(expn);
+      outVal = (float) 
+        ((temp - sounder10TC1[band - 1]) / sounder10TC2[band - 1]);
+    }
+ 
+    return (outVal);
+  }
+ 
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorGvarG12.java b/edu/wisc/ssec/mcidas/CalibratorGvarG12.java
new file mode 100644
index 0000000..e871b54
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorGvarG12.java
@@ -0,0 +1,138 @@
+//
+// CalibratorGvarG12.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream; 
+import java.io.IOException; 
+import edu.wisc.ssec.mcidas.AncillaryData;
+
+/**
+ * CalibratorGvarG12 creates a Calibrator object designed specifically
+ * to deal with GOES 10 data.  Not fully implemented at present - some
+ * calibrations remain to be done.  It provides all the constants 
+ * specific to the GOES 10 imager and sounder sensors.
+ *
+ * @version 1.3 6 Aug 1999
+ * @author Tommy Jasmin, SSEC
+ */
+
+public class CalibratorGvarG12 extends CalibratorGvar {
+
+  // the following static init block sets the class temp/rad constants
+  protected static float [] imager12FK1  = {0.f, 0.20096E6f, 0.43702E5f, 0.96859E4f, 0.50471E4f};
+
+  protected static float [] sounder12FK1 = 
+  {0.37778E4f, 0.40086E4f, 0.43085E4f, 
+   0.47041E4f, 0.50134E4f, 0.58645E4f,
+   0.69071E4f, 0.90388E4f, 0.12972E5f,
+   0.28931E5f, 0.34531E5f, 0.43340E5f,
+   0.12492E6f, 0.12822E6f, 0.13535E6f,
+   0.16981E6f, 0.18954E6f, 0.22538E6f};
+
+  protected static float [] imager12FK2  = {0.f, 0.36902E4f,0.22191E4f,0.13430E4f,0.10807E4f};
+
+  protected static float [] sounder12FK2 = 
+    {0.98121E3f,   0.10008E4f,   0.10252E4f,
+     0.10556E4f,   0.10783E4f,   0.11361E4f,
+     0.11998E4f,   0.13124E4f,   0.14803E4f,
+     0.19340E4f,   0.20516E4f,   0.22130E4f,
+     0.31494E4f,   0.31769E4f,   0.32347E4f,
+     0.34888E4f,   0.36189E4f,   0.38340E4f};
+
+  protected static float [] imager12TC1  = {0.f, .69703f, 5.08315f, .37554f, .09537f};
+
+  protected static float [] sounder12TC1 = 
+    {.01010f, .01252f, .01229f, 
+     .01189f, .01264f, .04189f, 
+     .13474f, .12341f, .03844f, 
+     .15764f, .27420f, .13683f, 
+     .02124f, .01780f, .02037f, 
+     .04933f, .05386f, .28872f};
+
+  protected static float [] imager12TC2  = {0.f, .99902f, .98872f, .99872f, .99960f};
+
+  protected static float [] sounder12TC2 = 
+     {.99995f, .99994f, .99994f,
+      .99995f, .99995f, .99983f, 
+      .99949f, .99957f, .99988f, 
+      .99960f, .99934f, .99969f, 
+      .99996f, .99997f, .99997f, 
+      .99993f, .99992f, .99961f};
+
+
+  /**
+   *
+   * constructor
+   *
+   * @param dis         data input stream
+   * @param ad          AncillaryData object
+   * @param cb		    calibration parameters array
+   *
+   */
+
+  public CalibratorGvarG12(DataInputStream dis, AncillaryData ad, int [] cb) 
+    throws IOException
+  {
+    super(dis, ad, cb);
+  }
+
+
+  public CalibratorGvarG12(int sensorId, int[] cb) {
+	  super(sensorId, cb);
+  }
+
+  /**
+   *
+   * calibrate from radiance to temperature
+   *
+   * @param inVal       input data value
+   * @param band        channel/band number
+   * @param sId         sensor id number
+   *
+   */
+
+  public float radToTemp(float inVal, int band, int sId) {
+ 
+    double expn;
+    double temp;
+    float outVal;
+ 
+    if ((sId % 2) == 0) {
+      expn = (imager12FK1[band - 1] / inVal) + 1.0;
+      temp = imager12FK2[band - 1] / Math.log(expn);
+      outVal = (float) ((temp - imager12TC1[band - 1]) / imager12TC2[band - 1]);
+    } else {
+      expn = (sounder12FK1[band - 1] / inVal) + 1.0;
+      temp = sounder12FK2[band - 1] / Math.log(expn);
+      outVal = (float) 
+        ((temp - sounder12TC1[band - 1]) / sounder12TC2[band - 1]);
+    }
+ 
+    return (outVal);
+  }
+ 
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorGvarG13.java b/edu/wisc/ssec/mcidas/CalibratorGvarG13.java
new file mode 100644
index 0000000..9a1594d
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorGvarG13.java
@@ -0,0 +1,137 @@
+//
+// CalibratorGvarG13.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream; 
+import java.io.IOException; 
+import edu.wisc.ssec.mcidas.AncillaryData;
+
+/**
+ * CalibratorGvarG13 creates a Calibrator object designed specifically
+ * to deal with GOES 13 data.  Not fully implemented at present - some
+ * calibrations remain to be done.  It provides all the constants 
+ * specific to the GOES 13 imager and sounder sensors.
+ *
+ * @version 1.3 6 Aug 1999
+ * @author Tommy Jasmin, SSEC
+ */
+
+public class CalibratorGvarG13 extends CalibratorGvar {
+
+  // the following static init block sets the class temp/rad constants
+  protected static float [] imager13FK1  = {0.f, 0.20075E6f, 0.42508E5f, 0.98050E4f, 0.50173E4f};
+
+  protected static float [] sounder13FK1 = 
+    {0.37330E4f, 0.40351E4f, 0.42661E4f,
+     0.46920E4f, 0.49953E4f, 0.58021E4f,
+     0.68338E4f, 0.89420E4f, 0.13042E5f,
+     0.28772E5f, 0.34368E5f, 0.42901E5f,
+     0.12513E6f, 0.12812E6f, 0.13482E6f,
+     0.16892E6f, 0.18938E6f, 0.22608E6f};
+
+  protected static float [] imager13FK2  = {0.f, 0.36890E4f, 0.21987E4f, 0.13484E4f, 0.10786E4f};
+
+  protected static float [] sounder13FK2 = 
+    {0.97732E3f,   0.10030E4f,   0.10218E4f,
+     0.10547E4f,   0.10770E4f,   0.11321E4f,
+     0.11956E4f,   0.13077E4f,   0.14830E4f,
+     0.19305E4f,   0.20483E4f,   0.22055E4f,
+     0.31512E4f,   0.31761E4f,   0.32305E4f,
+     0.34826E4f,   0.36179E4f,   0.38380E4f};
+
+  protected static float [] imager13TC1  = {0.f, 1.47950f, 3.96964f, .36350f, .09502f};
+
+  protected static float [] sounder13TC1 = 
+    {.00944f, .01022f, .01011f,
+     .01291f, .01353f, .04272f,
+     .12493f, .12033f, .03838f,
+     .15609f, .28000f, .18057f,
+     .01799f, .01809f, .02012f,
+     .05092f, .05740f, .29874f};
+
+  protected static float [] imager13TC2  = {0.f, .99794f, .99112f, .99876f, .99960f};
+
+  protected static float [] sounder13TC2 = 
+     {.99996f, .99995f, .99995f,
+      .99994f, .99994f, .99983f,
+      .99952f, .99958f, .99988f,
+      .99961f, .99932f, .99959f,
+      .99997f, .99997f, .99997f,
+      .99992f, .99992f, .99959f};
+
+  /**
+   *
+   * constructor
+   *
+   * @param dis         data input stream
+   * @param ad          AncillaryData object
+   * @param cb		    calibration parameters array
+   *
+   */
+
+  public CalibratorGvarG13(DataInputStream dis, AncillaryData ad, int [] cb) 
+    throws IOException
+  {
+    super(dis, ad, cb);
+  }
+
+
+  public CalibratorGvarG13(int sensorId, int[] cb) {
+	  super(sensorId, cb);
+  }
+
+  /**
+   *
+   * calibrate from radiance to temperature
+   *
+   * @param inVal       input data value
+   * @param band        channel/band number
+   * @param sId         sensor id number
+   *
+   */
+
+  public float radToTemp(float inVal, int band, int sId) {
+ 
+    double expn;
+    double temp;
+    float outVal;
+ 
+    if ((sId % 2) == 0) {
+      expn = (imager13FK1[band - 1] / inVal) + 1.0;
+      temp = imager13FK2[band - 1] / Math.log(expn);
+      outVal = (float) ((temp - imager13TC1[band - 1]) / imager13TC2[band - 1]);
+    } else {
+      expn = (sounder13FK1[band - 1] / inVal) + 1.0;
+      temp = sounder13FK2[band - 1] / Math.log(expn);
+      outVal = (float) 
+        ((temp - sounder13TC1[band - 1]) / sounder13TC2[band - 1]);
+    }
+ 
+    return (outVal);
+  }
+ 
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorGvarG8.java b/edu/wisc/ssec/mcidas/CalibratorGvarG8.java
new file mode 100644
index 0000000..cfd32c3
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorGvarG8.java
@@ -0,0 +1,208 @@
+//
+// CalibratorGvarG8.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * CalibratorGvarG8 creates a Calibrator object designed specifically
+ * to deal with GOES 8 data.  Not fully implemented at present - some
+ * calibrations remain to be done.  It provides all the constants
+ * specific to the GOES 8 imager and sounder sensors.
+ *
+ * @version 1.4 6 Aug 1999
+ * @author Tommy Jasmin, SSEC
+ */
+
+public class CalibratorGvarG8 extends CalibratorGvar {
+
+  protected static float [] imager8FK1  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder8FK1 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager8FK2  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder8FK2 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager8TC1  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder8TC1 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager8TC2  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder8TC2 = new float[NUM_BANDS_SOUNDER];
+ 
+  // the following static init block sets the class temp/rad constants
+  static {
+
+    imager8FK1[0] = 0.0000000E0f;
+    imager8FK1[1] = 0.1999862E6f;
+    imager8FK1[2] = 0.3879239E5f;
+    imager8FK1[3] = 0.9737930E4f;
+    imager8FK1[4] = 0.6944640E4f;
+
+    sounder8FK1[0]  = 0.3756810E4f;
+    sounder8FK1[1]  = 0.4011100E4f;
+    sounder8FK1[2]  = 0.4296870E4f;
+    sounder8FK1[3]  = 0.4681130E4f;
+    sounder8FK1[4]  = 0.4975250E4f;
+    sounder8FK1[5]  = 0.5881410E4f;
+    sounder8FK1[6]  = 0.6787440E4f;
+    sounder8FK1[7]  = 0.8873710E4f;
+    sounder8FK1[8]  = 0.1299794E5f;
+    sounder8FK1[9]  = 0.2862932E5f;
+    sounder8FK1[10] = 0.3424830E5f;
+    sounder8FK1[11] = 0.4311430E5f;
+    sounder8FK1[12] = 0.1242353E6f;
+    sounder8FK1[13] = 0.1281235E6f;
+    sounder8FK1[14] = 0.1351482E6f;
+    sounder8FK1[15] = 0.1691671E6f;
+    sounder8FK1[16] = 0.1882350E6f;
+    sounder8FK1[17] = 0.2257944E6f;
+
+    imager8FK2[0] = 0.0000000E0f;
+    imager8FK2[1] = 0.3684270E4f;
+    imager8FK2[2] = 0.2132720E4f;
+    imager8FK2[3] = 0.1345370E4f;
+    imager8FK2[4] = 0.1201990E4f;
+
+    sounder8FK2[0]  = 0.3765120E4f;
+    sounder8FK2[1]  = 0.3981160E4f;
+    sounder8FK2[2]  = 0.4281880E4f;
+    sounder8FK2[3]  = 0.4678910E4f;
+    sounder8FK2[4]  = 0.4962590E4f;
+    sounder8FK2[5]  = 0.5860420E4f;
+    sounder8FK2[6]  = 0.6770320E4f;
+    sounder8FK2[7]  = 0.8958910E4f;
+    sounder8FK2[8]  = 0.1296593E5f;
+    sounder8FK2[9]  = 0.2839828E5f;
+    sounder8FK2[10] = 0.3420134E5f;
+    sounder8FK2[11] = 0.4252514E5f;
+    sounder8FK2[12] = 0.1240574E6f;
+    sounder8FK2[13] = 0.1280114E6f;
+    sounder8FK2[14] = 0.1348497E6f;
+    sounder8FK2[15] = 0.1678142E6f;
+    sounder8FK2[16] = 0.1888012E6f;
+    sounder8FK2[17] = 0.2258565E6f;
+
+    imager8TC1[0] = 0.0000000E0f;
+    imager8TC1[1] = 0.6357000E0f;
+    imager8TC1[2] = 0.6060000E0f;
+    imager8TC1[3] = 0.3735000E0f;
+    imager8TC1[4] = 0.2217000E0f;
+
+    sounder8TC1[0]  = 0.1230000E-1f;
+    sounder8TC1[1]  = 0.1330000E-1f;
+    sounder8TC1[2]  = 0.1860000E-1f;
+    sounder8TC1[3]  = 0.1500000E-1f;
+    sounder8TC1[4]  = 0.1650000E-1f;
+    sounder8TC1[5]  = 0.4740000E-1f;
+    sounder8TC1[6]  = 0.1318000E0f;
+    sounder8TC1[7]  = 0.1200000E0f;
+    sounder8TC1[8]  = 0.4260000E-1f;
+    sounder8TC1[9]  = 0.1505000E0f;
+    sounder8TC1[10] = 0.2743000E0f;
+    sounder8TC1[11] = 0.1447000E0f;
+    sounder8TC1[12] = 0.2240000E-1f;
+    sounder8TC1[13] = 0.2200000E-1f;
+    sounder8TC1[14] = 0.2170000E-1f;
+    sounder8TC1[15] = 0.5790000E-1f;
+    sounder8TC1[16] = 0.6230000E-1f;
+    sounder8TC1[17] = 0.3675000E0f;
+
+    imager8TC2[0] = 0.0000000E0f;
+    imager8TC2[1] = 0.9991000E0f;
+    imager8TC2[2] = 0.9986000E0f;
+    imager8TC2[3] = 0.9987000E0f;
+    imager8TC2[4] = 0.9992000E0f;
+
+    sounder8TC2[0]  = 0.9999000E0f;
+    sounder8TC2[1]  = 0.9999000E0f;
+    sounder8TC2[2]  = 0.9999000E0f;
+    sounder8TC2[3]  = 0.9999000E0f;
+    sounder8TC2[4]  = 0.9999000E0f;
+    sounder8TC2[5]  = 0.9998000E0f;
+    sounder8TC2[6]  = 0.9995000E0f;
+    sounder8TC2[7]  = 0.9996000E0f;
+    sounder8TC2[8]  = 0.9999000E0f;
+    sounder8TC2[9]  = 0.9996000E0f;
+    sounder8TC2[10] = 0.9993000E0f;
+    sounder8TC2[11] = 0.9997000E0f;
+    sounder8TC2[12] = 0.1000000E1f;
+    sounder8TC2[13] = 0.1000000E1f;
+    sounder8TC2[14] = 0.1000000E1f;
+    sounder8TC2[15] = 0.9999000E0f;
+    sounder8TC2[16] = 0.9999000E0f;
+    sounder8TC2[17] = 0.9995000E0f;
+
+  }
+
+  /**
+   *
+   * constructor
+   *
+   * @param dis         data input stream
+   * @param ad          AncillaryData object
+   * @param cb		    calibration parameters array
+   *
+   */
+
+  public CalibratorGvarG8(DataInputStream dis, AncillaryData ad, int [] cb) 
+    throws IOException 
+  {
+    super(dis, ad, cb);
+  }
+
+  
+  public CalibratorGvarG8(final int id, final int[] cal) {
+	super(id, cal);
+  }
+  
+  /**
+   *
+   * calibrate from radiance to temperature
+   *
+   * @param inVal       input data value
+   * @param band        channel/band number
+   * @param sId         sensor id number
+   *
+   */
+
+  public float radToTemp(float inVal, int band, int sId) {
+
+    double expn;
+    double temp;
+    float outVal;
+
+    if ((sId % 2) == 0) {
+      expn = (imager8FK1[band - 1] / inVal) + 1.0;
+      temp = imager8FK2[band - 1] / Math.log(expn);
+      outVal = (float) ((temp - imager8TC1[band - 1]) / imager8TC2[band - 1]);
+    } else {
+      expn = (sounder8FK1[band - 1] / inVal) + 1.0;
+      temp = sounder8FK2[band - 1] / Math.log(expn);
+      outVal = (float) ((temp - sounder8TC1[band - 1]) / sounder8TC2[band - 1]);
+    }
+
+    return (outVal);
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorGvarG9.java b/edu/wisc/ssec/mcidas/CalibratorGvarG9.java
new file mode 100644
index 0000000..37dca79
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorGvarG9.java
@@ -0,0 +1,207 @@
+//
+// CalibratorGvarG9.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * CalibratorGvarG9 creates a Calibrator object designed specifically
+ * to deal with GOES 9 data.  Not fully implemented at present - some
+ * calibrations remain to be done.  It provides all the constants
+ * specific to the GOES 9 imager and sounder sensors.
+ *
+ * @version 1.3 6 Aug 1999
+ * @author Tommy Jasmin, SSEC
+ */
+
+public class CalibratorGvarG9 extends CalibratorGvar {
+
+  protected static float [] imager9FK1  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder9FK1 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager9FK2  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder9FK2 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager9TC1  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder9TC1 = new float[NUM_BANDS_SOUNDER];
+  protected static float [] imager9TC2  = new float[NUM_BANDS_IMAGER];
+  protected static float [] sounder9TC2 = new float[NUM_BANDS_SOUNDER];
+
+  // the following static init block sets the class temp/rad constants
+  static {
+
+    imager9FK1[0] = 0.0000000E0f;
+    imager9FK1[1] = 0.1988078E6f;
+    imager9FK1[2] = 0.3873241E5f;
+    imager9FK1[3] = 0.9717210E4f;
+    imager9FK1[4] = 0.6899470E4f;
+
+    sounder9FK1[0]  = 0.3765120E4f;
+    sounder9FK1[1]  = 0.3981160E4f;
+    sounder9FK1[2]  = 0.4281880E4f;
+    sounder9FK1[3]  = 0.4678910E4f;
+    sounder9FK1[4]  = 0.4962590E4f;
+    sounder9FK1[5]  = 0.5860420E4f;
+    sounder9FK1[6]  = 0.6770320E4f;
+    sounder9FK1[7]  = 0.8958910E4f;
+    sounder9FK1[8]  = 0.1296593E5f;
+    sounder9FK1[9]  = 0.2839828E5f;
+    sounder9FK1[10] = 0.3420134E5f;
+    sounder9FK1[11] = 0.4252514E5f;
+    sounder9FK1[12] = 0.1240574E6f;
+    sounder9FK1[13] = 0.1280114E6f;
+    sounder9FK1[14] = 0.1348497E6f;
+    sounder9FK1[15] = 0.1678142E6f;
+    sounder9FK1[16] = 0.1888012E6f;
+    sounder9FK1[17] = 0.2258565E6f;
+
+    imager9FK2[0] = 0.0000000E0f;
+    imager9FK2[1] = 0.3677020E4f;
+    imager9FK2[2] = 0.2131620E4f;
+    imager9FK2[3] = 0.1344410E4f;
+    imager9FK2[4] = 0.1199380E4f;
+
+    sounder9FK2[0]  = 0.9801200E3f;
+    sounder9FK2[1]  = 0.9985200E3f;
+    sounder9FK2[2]  = 0.1023050E4f;
+    sounder9FK2[3]  = 0.1053740E4f;
+    sounder9FK2[4]  = 0.1074620E4f;
+    sounder9FK2[5]  = 0.1135870E4f;
+    sounder9FK2[6]  = 0.1191850E4f;
+    sounder9FK2[7]  = 0.1308490E4f;
+    sounder9FK2[8]  = 0.1480080E4f;
+    sounder9FK2[9]  = 0.1922130E4f;
+    sounder9FK2[10] = 0.2045030E4f;
+    sounder9FK2[11] = 0.2199040E4f;
+    sounder9FK2[12] = 0.3142140E4f;
+    sounder9FK2[13] = 0.3175180E4f;
+    sounder9FK2[14] = 0.3230740E4f;
+    sounder9FK2[15] = 0.3475050E4f;
+    sounder9FK2[16] = 0.3614260E4f;
+    sounder9FK2[17] = 0.3836740E4f;
+
+    imager9TC1[0] = 0.0000000E0f;
+    imager9TC1[1] = 0.5864000E0f;
+    imager9TC1[2] = 0.4841000E0f;
+    imager9TC1[3] = 0.3622000E0f;
+    imager9TC1[4] = 0.2014000E0f;
+ 
+    sounder9TC1[0]  = 0.9900000E-2f;
+    sounder9TC1[1]  = 0.1190000E-1f;
+    sounder9TC1[2]  = 0.1220000E-1f;
+    sounder9TC1[3]  = 0.1190000E-1f;
+    sounder9TC1[4]  = 0.1350000E-1f;
+    sounder9TC1[5]  = 0.4400000E-1f;
+    sounder9TC1[6]  = 0.1345000E0f;
+    sounder9TC1[7]  = 0.1193000E0f;
+    sounder9TC1[8]  = 0.4070000E-1f;
+    sounder9TC1[9]  = 0.1438000E0f;
+    sounder9TC1[10] = 0.2762000E0f;
+    sounder9TC1[11] = 0.1370000E0f;
+    sounder9TC1[12] = 0.1890000E-1f;
+    sounder9TC1[13] = 0.1980000E-1f;
+    sounder9TC1[14] = 0.1910000E-1f;
+    sounder9TC1[15] = 0.5310000E-1f;
+    sounder9TC1[16] = 0.6120000E-1f;
+    sounder9TC1[17] = 0.3042000E0f;
+ 
+    imager9TC2[0] = 0.0000000E0f;
+    imager9TC2[1] = 0.9992000E0f;
+    imager9TC2[2] = 0.9989000E0f;
+    imager9TC2[3] = 0.9988000E0f;
+    imager9TC2[4] = 0.9992000E0f;
+ 
+    sounder9TC2[0]  = 0.1000000E1f;
+    sounder9TC2[1]  = 0.9999000E0f;
+    sounder9TC2[2]  = 0.9999000E0f;
+    sounder9TC2[3]  = 0.9999000E0f;
+    sounder9TC2[4]  = 0.9999000E0f;
+    sounder9TC2[5]  = 0.9998000E0f;
+    sounder9TC2[6]  = 0.9995000E0f;
+    sounder9TC2[7]  = 0.9996000E0f;
+    sounder9TC2[8]  = 0.9999000E0f;
+    sounder9TC2[9]  = 0.9996000E0f;
+    sounder9TC2[10] = 0.9993000E0f;
+    sounder9TC2[11] = 0.9997000E0f;
+    sounder9TC2[12] = 0.1000000E1f;
+    sounder9TC2[13] = 0.1000000E1f;
+    sounder9TC2[14] = 0.1000000E1f;
+    sounder9TC2[15] = 0.9999000E0f;
+    sounder9TC2[16] = 0.9999000E0f;
+    sounder9TC2[17] = 0.9996000E0f;
+
+  }
+
+  /**
+   *
+   * constructor
+   *
+   * @param dis         data input stream
+   * @param ad          AncillaryData object
+   * @param cb    calibration parameters array
+   *
+   */
+
+  public CalibratorGvarG9(DataInputStream dis, AncillaryData ad, int [] cb)
+    throws IOException
+  {
+    super(dis, ad, cb);
+  }
+  
+  public CalibratorGvarG9(final int id, final int[] cal) {
+	super(id, cal);
+  }
+
+  /**
+   *
+   * calibrate from radiance to temperature
+   *
+   * @param inVal       input data value
+   * @param band        channel/band number
+   * @param sId         sensor id number
+   *
+   */
+
+  public float radToTemp(float inVal, int band, int sId) {
+ 
+    double expn;
+    double temp;
+    float outVal;
+ 
+    if ((sId % 2) == 0) {
+      expn = (imager9FK1[band - 1] / inVal) + 1.0;
+      temp = imager9FK2[band - 1] / Math.log(expn);
+      outVal = (float) ((temp - imager9TC1[band - 1]) / imager9TC2[band - 1]);
+    } else {
+      expn = (sounder9FK1[band - 1] / inVal) + 1.0;
+      temp = sounder9FK2[band - 1] / Math.log(expn);
+      outVal = (float) ((temp - sounder9TC1[band - 1]) / sounder9TC2[band - 1]);
+    }
+ 
+    return (outVal);
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/CalibratorMsg.java b/edu/wisc/ssec/mcidas/CalibratorMsg.java
new file mode 100644
index 0000000..905f1ed
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/CalibratorMsg.java
@@ -0,0 +1,440 @@
+//
+// CalibratorMsg.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Calibration routines for the Meteosat Second Generation (MSG) instrument.
+ * <p>
+ * At the time this module was written the 
+ * <a href="http://www.ssec.wisc.edu/mcidas/doc/prog_man/2006/">
+ * McIDAS documentation
+ * </a> for the <a href="http://www.esa.int/SPECIALS/MSG/">MSG Instrument</a>
+ * did not reflect that calibration data is now stored by band in the 
+ * calibration block.
+ * </p>
+ * <p>
+ * Current understanding is that an MSG ASCII calibration block should be 
+ * prefixed by the string MSGT followed by the following 6 values for the 
+ * Inverse Planck Funciton corresponding to each band:
+ * <br/><br/>
+ * <table border="1" cellpadding="2">
+ *   <tr><th>Name</th><th>Calculation</th><th>Units</th></tr>
+ *   <tr><td><b>C1W3</b></td><td colspan="2">C1 * W^3</td></tr>
+ *   <tr><td></td><td>C1 = 2.0e5 * P * C^2</td><td>radiance/(cm^-1)^3)</td></tr>
+ *   <tr><td></td><td>W = 1.0e2 * (band wave numbers)</td><td>(cm^-1)</td></tr>
+ *   <tr><td><b>C2W</b></td><td colspan="2">C2 * W</td></tr>
+ *   <tr><td></td><td>C2 = P * C / B</td><td>K/(cm^-1)</td></tr>
+ *   <tr><td></td><td>W = 1.0e2 * (band wave numbers)</td><td>(cm^-1)</td></tr>
+ *   <tr><td><b>ALPHA</b></td><td colspan="2">gain adjustment</td></tr>
+ *   <tr><td><b>BETA</b></td><td colspan="2">bias adjustment</td></tr>
+ *   <tr><td><b>GAIN</b></td><td colspan="2">gain</td></tr>
+ *   <tr><td><b>OFFSET</b></td><td colspan="2">offset</td></tr>
+ * </table>
+ * <br/>
+ * Where C = speed of light, P = planck constant, B = Boltzmann constant.
+ * </p>
+ * <p>
+ * Each string value is 17 characters with 10 decimal places (fortran E17.10), 
+ * and should be parseable using <code>Double.parseDouble</code>. After 
+ * converting cal block from ints to a string calibration values for the first
+ * band, including the header should look like:
+ * </p>
+ * <p>
+ * <i>MSGT 0.0000000000E+00 0.0000000000E+00 0.0000000000E+00 0.0000000000E+00 
+ * 0.2312809974E-01-0.1179533087E+01</i>
+ * </p>
+ * @author Bruce Flynn, SSEC
+ * @version $Id: CalibratorMsg.java,v 1.7 2009-03-02 23:34:50 curtis Exp $
+ */
+public class CalibratorMsg implements Calibrator {
+
+    private static final int C1W3 = 0;
+    private static final int C2W = 1;
+    private static final int ALPHA = 2;
+    private static final int BETA = 3;
+    private static final int GAIN = 4;
+    private static final int OFFSET = 5;
+
+    /** Size of cal block values (fortran E17.10 format code). */
+    private static final int FMT_SIZE = 17;
+    /** Size of a band in the cal block in bytes. */
+    private static final int BAND_SIZE = 103;
+    /** Size of the cal block header in bytes. */
+    private static final int HDR_SIZE = 4;
+    /** Cal block header string. */
+    private static final String HEADER = "MSGT";
+
+    /** Coefficients for individual bands. */
+    private final float[] bandCoefs = new float[] {
+        21.21f,
+        23.24f,
+        19.77f,
+        0f,
+        0f,
+        0f,
+        0f,
+        0f,
+        0f,
+        0f,
+        0f,
+        22.39f
+    };
+
+    /** Coefficients used in the inverse planck function. */
+    private final double[][] planckCoefs;
+
+    /** Cal block converted from an int array. */
+    private byte[] calBytes;
+    /** 
+     * Current cal type as set by <code>setCalType</code>
+     */
+    private int curCalType = CAL_RAW;
+    
+    /**
+     * Construct this object according to the calibration data provided.
+     * On initalization the calibration array is cloned and all bands available
+     * are read from the block.
+     *
+     * @param cal calibration block from an MSG AREA file.
+     * @throws CalibratorException on invalid calibration block.
+     */
+    public CalibratorMsg(final int[] cal)  throws CalibratorException { 
+        int[] calBlock = (int[]) cal.clone();
+
+        // convert int[] to bytes
+        calBytes = calIntsToBytes(calBlock);
+       
+        // if the header is incorrect, flip and try again
+        String msgt = new String(calBytes, 0, 4);
+        if (!msgt.equals(HEADER)) {
+            McIDASUtil.flip(calBlock, 0, calBlock.length - 1);
+            calBytes = calIntsToBytes(calBlock);
+            msgt = new String(calBytes, 0, 4);
+            if (!msgt.equals(HEADER)) {
+                throw new IllegalArgumentException(
+                    "Invalid calibration block header: " + msgt
+                );
+            }
+        }
+
+        planckCoefs = getCalCoefs();
+    }
+    
+    /**
+     * 
+     * @param coefs
+     * @throws CalibratorException
+     */
+    public CalibratorMsg(final double[][] coefs) throws CalibratorException {
+    	planckCoefs = coefs;
+    }
+
+    /**
+     * Set the input data calibration type.
+     *
+     * @param calType calibration type from <code>Calibrator</code>
+     * @return 0 if calibration type is valid, -1 otherwise.
+     */
+    public int setCalType(int calType) {
+        if ((calType < Calibrator.CAL_MIN) || (calType > Calibrator.CAL_MAX)) {
+            return -1;
+        }
+        curCalType = calType;
+        return 0;
+    }
+
+    /**
+     * Calibrate an array of pixels from the current calibration type according
+     * to the parameters provided.
+     *
+     * @param input pixel array to calibrate.
+     * @param band channel for which to perform calibration.
+     * @param calTypeOut Calibration type constant.
+     * @return calibrated pixel value or <code>Float.NaN</code> if the
+     * calibration type cannot be performed for the specified band.
+     */
+    public float[] calibrate(final float[] input, final int band, 
+        final int calTypeOut) {
+        
+        if (calTypeOut == curCalType || calTypeOut == CAL_NONE) { // no-op
+          return (float[])input.clone();
+        }
+      
+        float[] output = new float[input.length];
+
+        for (int i = 0; i < input.length; i++) {
+            output[i] = calibrate(input[i], band, calTypeOut);
+        }
+
+        return output;
+    }
+
+    /**
+     * Calibrate a pixel from the current calibration type according to the 
+     * parameters provided.
+     * 
+     * @param inputPixel pixel value to calibrate.
+     * @param band channel for which to perform calibration.
+     * @param calTypeOut Calibration type constant.
+     * @return calibrated pixel value or <code>Float.NaN</code> if the
+     * calibration type cannot be performed for the specified band.
+     */
+    public float calibrate(
+        final float inputPixel, 
+        final int band, 
+        final int calTypeOut) {
+        float pxl;
+
+        if (calTypeOut == curCalType || calTypeOut == CAL_NONE) { // no-op
+          return inputPixel;
+        }
+        
+        switch (curCalType) {
+            case CAL_ALB:
+                throw new UnsupportedOperationException(
+                    "Calibration from reflectance not implemented"
+                );
+            case CAL_TEMP:
+                throw new UnsupportedOperationException(
+                    "Calibration from temperature not implemented"
+                );
+            case CAL_BRIT:
+                throw new UnsupportedOperationException(
+                    "Calibration from brightness not implemented"
+                );
+            case CAL_RAD:
+                throw new UnsupportedOperationException(
+                    "Calibration from radiance not implemented"
+                );
+            case CAL_RAW:
+                pxl = calibrateFromRaw(inputPixel, band, calTypeOut);
+                break;
+
+            default:
+                throw new IllegalArgumentException(
+                    "Unknown calibration type"
+                );
+        }
+
+        return pxl;
+    }
+
+    /**
+     * Calibrate a pixel from RAW data according to the parameters provided.
+     *
+     * @param inputPixel pixel value to calibrate.
+     * @param band channel for which to perform calibration.
+     * @param calTypeOut Calibration type constant.
+     * @return calibrated pixel value, <code>Float.NaN</code> if the
+     * calibration type cannot be performed for the specified band.
+     */
+    public float calibrateFromRaw(
+        final float inputPixel,
+        final int band,
+        final int calTypeOut) {
+    	
+        if (calTypeOut == CAL_RAW || calTypeOut == CAL_NONE) { // no-op
+            return inputPixel;
+        }
+      
+        double[] coefs = planckCoefs[band - 1];
+
+        double pxl = inputPixel * coefs[GAIN] + coefs[OFFSET];
+
+        if (pxl < 0) {
+            pxl = 0.0;
+        }
+
+        // Visible and near-visible (VIS006, VIS008, IR016, HRV)
+        if (band < 4 || band == 12) {
+            switch (calTypeOut) {
+                case CAL_TEMP: // can't do temp 
+                    pxl = Double.NaN;
+                    break;
+                
+                case CAL_RAD: // radiance
+                    break;
+            
+                case CAL_ALB: // reflectance
+                    pxl = (pxl / bandCoefs[band-1]) * 100.0;
+                    if (pxl < 0) {
+                        pxl = 0.0;
+                    } else if (pxl > 100) {
+                        pxl = 100.0;
+                    }
+                    break;
+                
+                case CAL_BRIT: // brightness
+                    pxl = (pxl / bandCoefs[band-1]) * 100.0;
+                    if (pxl < 0) {
+                        pxl = 0.0;
+                    } else if (pxl > 100) {
+                        pxl = 100.0;
+                    }
+                    pxl = Math.sqrt(pxl) * 25.5;
+                    break;
+
+                default:
+                    throw new IllegalArgumentException(
+                        "Unknown calibration type: " + calTypeOut
+                    );               
+                }
+
+        // IR Channel
+        } else {
+        
+            switch (calTypeOut) {
+                case CAL_TEMP: // temperature
+                    if (pxl > 0) {
+                        pxl = (coefs[C2W] / Math.log(1.0 + coefs[C1W3] / pxl) 
+                            - coefs[BETA]) / coefs[ALPHA];
+                    }
+                    break;
+
+                case CAL_RAD: // radiance
+                    break;
+            
+                case CAL_ALB: // can't do reflectance
+                    pxl = Double.NaN;
+                    break;
+                
+                case CAL_BRIT: // brightness
+                    if (pxl > 0) {
+                        pxl = (coefs[C2W] / Math.log(1.0 + coefs[C1W3] / pxl) 
+                            - coefs[BETA]) / coefs[ALPHA];
+                        pxl = greyScale(pxl);
+                    } else {
+                        pxl = 255.0;
+                    }
+                    break;
+
+                default:
+                    throw new IllegalArgumentException(
+                        "Unsupported calibration type: " + calTypeOut
+                    );
+
+            } 
+        }
+
+        return (float) pxl;
+    }
+    
+    /**
+     * Convert a brightness temperature to grey scale.
+     * 
+     * @param val temperature value in kelvin.
+     * @return brightness value.
+     */
+    private double greyScale(final double val) {
+        final int tempLim = 242;
+        final int c1 = 418;
+        final int c2 = 660;
+        double ret;
+        
+        if (val < tempLim) {
+          ret = Math.min(c1 - val, 255);
+        } else {
+          ret = Math.max(c2 - 2 * val, 0);
+        }
+        
+        return ret;
+    }
+
+    /**
+     * Get cal block coefs, converting from bytes to strings.
+     *
+     * @return array of cal coefs by band.
+     * @throws CalibratorExcpetion when unable to parse calibration block.
+     */
+    private double[][] getCalCoefs() throws CalibratorException {
+
+        double[][] coefs = new double[12][6];
+        
+        for (int i = 0; i < coefs.length; i++) {
+            
+            // add 1 to band size for extra whitespace between bands
+            final int bandOffset = (i * (BAND_SIZE + 1)) + HDR_SIZE;
+
+            String[] strVals = getBandVals(
+                new String(calBytes, bandOffset, BAND_SIZE)
+            );
+
+            try {
+                coefs[i][C1W3] = Double.parseDouble(strVals[0]);
+                coefs[i][C2W] = Double.parseDouble(strVals[1]);
+                coefs[i][ALPHA] = Double.parseDouble(strVals[2]);
+                coefs[i][BETA] = Double.parseDouble(strVals[3]);
+                coefs[i][GAIN] = Double.parseDouble(strVals[4]);
+                coefs[i][OFFSET] = Double.parseDouble(strVals[5]);
+            } catch (NumberFormatException e) {
+                throw new CalibratorException(
+                  "Unable to parse values from calibration block for band "
+                    + (i + 1)
+                );
+            }
+        }
+        return coefs;
+    }
+
+    /**
+     * Split a line corresponding to a single band in the calibration block
+     * into <code>String</code>s. There are some additional operations 
+     * performed to identify and correct an issue where some signed values
+     * were not separated by whitespace.
+     *
+     * @param line the line as mentioned above.
+     * @return array where each value is the string representation of a value
+     * from the cal block corresponding to a band.
+     */
+    private String[] getBandVals(final String line) {
+  
+        String[] strVals = new String[6];
+        for (int i = 0, j = 0; i < strVals.length; i++, j += FMT_SIZE) {
+            strVals[i] = line.substring(j, j + FMT_SIZE);
+        }
+
+        return strVals;
+    }
+
+    /**
+     * Convert an array of ints to an array of bytes.
+     *
+     * @param orig array of ints to convert
+     * @return array of bytes
+     */
+    private byte[] calIntsToBytes(final int[] orig) {
+        byte[] bites = new byte[orig.length * 4];
+        for (int i = 0, j = 0; i < orig.length; i++) {
+            bites[j++] = (byte) (orig[i] & 0x000000ff);
+            bites[j++] = (byte) ((orig[i] >> 8) & 0x000000ff); 
+            bites[j++] = (byte) ((orig[i] >> 16) & 0x000000ff);
+            bites[j++] = (byte) ((orig[i] >> 24) & 0x000000ff);
+        }
+        return bites;
+    }
+
+}
diff --git a/edu/wisc/ssec/mcidas/ConversionUtility.java b/edu/wisc/ssec/mcidas/ConversionUtility.java
new file mode 100644
index 0000000..3c8e526
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/ConversionUtility.java
@@ -0,0 +1,224 @@
+//
+// ConversionUtility.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * A collection of methods for doing various conversions.
+ * <p>
+ *
+ * @version 1.3, 16 Nov 98
+ * @author Tommy Jasmin, SSEC 
+ */
+
+public class ConversionUtility 
+
+{
+
+  /**
+   * Find the distance in km between two points given the lats/lons.
+   *
+   * @param lat1 - latitude of first point
+   * @param lat2 - latitude of second point
+   * @param lon1 - longitude of first point
+   * @param lon2 - longitude of second point
+   * @return - the distance in km between the two input points 
+   */
+
+  public static float LatLonToDistance (
+    float lat1,
+    float lon1,
+    float lat2,
+    float lon2
+  ) 
+
+  {
+
+    double arcl;
+    double cplat;
+    double cplon;
+    double crlat;
+    double crlon;
+    double dist;
+    double plat;
+    double plon;
+    double rlat;
+    double rlon;
+    double srlat;
+    double srlon;
+    double splat;
+    double splon;
+    double xx;
+    double yy;
+    double zz;
+   
+    float r = 6371.0f;
+    float z = 0.017453292f;
+   
+    rlat = (double) (lat1) * z;
+    rlon = (double) (lon1) * z;
+    plat = (double) (lat2) * z;
+    plon = (double) (lon2) * z;
+   
+    crlat = Math.cos(rlat);
+    crlon = Math.cos(rlon);
+    srlat = Math.sin(rlat);
+    srlon = Math.sin(rlon);
+    cplat = Math.cos(plat);
+    cplon = Math.cos(plon);
+    splat = Math.sin(plat);
+    splon = Math.sin(plon);
+   
+    xx = (cplat * cplon) - (crlat * crlon);
+    yy = (cplat * splon) - (crlat * srlon);
+    zz = splat - srlat;
+   
+    dist = Math.sqrt((xx * xx) + (yy * yy) + (zz * zz));
+    arcl = 2.0 * Math.asin(dist / 2.0) * r;
+   
+    return (float) (arcl);
+
+  }
+
+  /**
+   * Convert a latitude or longitude in dddmmss format to floating point.
+   *
+   * @param dddmmss - latitude or longitude
+   * @return - floating point representation of the input parameter.
+   */
+
+  public static float FloatLatLon (
+    int dddmmss
+  )
+ 
+  {
+    int inVal;
+    float negVal;
+    float retVal;
+ 
+    if (dddmmss < 0) {
+      inVal = -dddmmss;
+      negVal = -1.0f;
+    } else {
+      inVal = dddmmss;
+      negVal = 1.0f;
+    }
+ 
+    retVal = (float) (inVal / 10000) +
+      (float) ((inVal / 100) % 100) / 60.0f +
+      (float) (inVal % 100) / 3600.0f;
+ 
+    retVal = negVal * retVal;
+    return (retVal);
+ 
+  }
+ 
+  /**
+   * Convert a Gould format floating point number to native double format
+   *
+   * @param inVal - input Gould value
+   * @return - the input value converted to double floating point
+   */
+
+  public static double GouldToNative(int inVal) {
+
+    int sign;
+    int exponent;
+    float mant;
+    int byte0;
+    int byte1;
+    int byte2;
+    double dblMant;
+    double tempVal;
+    double nativeVal;
+
+   /*
+    * an example conversion:
+    *
+    * input value (hex): BE DA 4D 07
+    *  a) convert to binary:
+    *     1011 1110 1101 1010 0100 1101 0000 0111
+    *  b) sign bit is set, so take twos complement:
+    *     0100 0001 0010 0101 1011 0010 1111 1001
+    *  c) convert this back to hex: 41 25 B2 F9
+    *  d) mantissa = 2470649
+    *  e) exponent = 65
+    *  f) tempVal = mantissa / 16 exp (70 - 65)
+    *  g) outputVal = tempVal * sign = -2.3561944
+    *
+    */
+
+    // set up munging bytes
+    byte0 = (inVal & 0x000000FF);
+    byte1 = ((inVal >> 8) & 0x000000FF);
+    byte2 = ((inVal >> 16) & 0x000000FF);
+
+    sign = 1;
+    if ((inVal & 0x80000000) != 0) {
+      sign = -1;
+      inVal = -inVal;
+    }
+
+    exponent = ((inVal & 0x7F000000) >> 24);
+    if (exponent == 0) {
+      exponent = 64;
+    }
+
+    // determine the value of the mantissa, load into a double
+    mant = (float) (byte0 + (byte1 * 256) + (byte2 * 65536));
+    mant = Math.abs(mant);
+    dblMant = (double) mant;
+
+    // now adjust the mantissa according to the exponent
+    tempVal = Math.pow((double) 16, (double) (70 - exponent));
+    nativeVal = dblMant / tempVal;
+    nativeVal = nativeVal * sign;
+
+    return nativeVal;
+
+  }
+
+  /**
+   * swap the bytes of an integer array
+   *
+   * @param array	 array of integers to be flipped
+   * @param first 	starting element of the array
+   * @param last 	last element of array to flip
+   *
+   */
+
+  public static void swap (int array[], int first, int last) 
+
+  {
+    int i, k;
+    for (i = first; i <= last; i++) {
+      k = array[i];
+      array[i] = ( (k >>> 24) & 0xff) | ( (k >>> 8) & 0xff00) |
+                 ( (k & 0xff) << 24 )  | ( (k & 0xff00) << 8);
+    }
+  }
+ 
+}
diff --git a/edu/wisc/ssec/mcidas/EnhancementTable.java b/edu/wisc/ssec/mcidas/EnhancementTable.java
new file mode 100644
index 0000000..733a61f
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/EnhancementTable.java
@@ -0,0 +1,252 @@
+//
+// EnhancementTable.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.awt.Color;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.File;
+import java.net.URL;
+
+/**
+ * Class for reading a McIDAS enhancement table (.ET file).  The default
+ * constructor creates a grey scale enhancement.
+ */
+public class EnhancementTable
+{
+    private int[][] rgbValues = null;
+    private DataInputStream dataStream;
+
+    /**
+     *  Construct an enhancement table with a grey scale enhancement.
+     */
+    public EnhancementTable()
+    {
+        rgbValues = new int[3][256];
+        for (int i = 0; i < 3; i++)
+        {
+            for (int j = 0; j < 256; j++) 
+            {
+                rgbValues[i][j] = j;
+            }
+        }
+    }
+        
+    /**
+     * Construct an enhancement table from a local disk file or
+     * URL.
+     *
+     * @param source     source of the enhancement table (path or URL)
+     *
+     * @exception  McIDASException  error finding or reading the source.
+     */
+    public EnhancementTable(String source)
+        throws McIDASException
+    {
+        try
+        {
+            dataStream = 
+                new DataInputStream(
+                    new BufferedInputStream(
+                        new FileInputStream(source)));
+        }
+        catch (Exception e)
+        {
+            try
+            {
+                dataStream = 
+                    new DataInputStream(
+                        new BufferedInputStream(
+                           new URL(source).openStream()));
+            }
+            catch (Exception e2)
+            {
+                throw new McIDASException(
+                    "Unable to open enhancement table " + source);
+            }
+        }
+        readRGBValues();
+    }
+
+    /**
+     * Construct an enhancement table from a file object.
+     *
+     * @param file     file object representing the enhancement table.
+     *
+     * @exception  McIDASException  error finding or reading the file.
+     */
+    public EnhancementTable(File file)
+        throws McIDASException
+    {
+        try
+        {
+            dataStream = 
+                new DataInputStream(
+                    new BufferedInputStream(
+                        new FileInputStream(file)));
+        }
+        catch (Exception e)
+        {
+            throw new McIDASException(
+                "Unable to open enhancement table " + file);
+        }
+        readRGBValues();
+    }
+
+    /**
+     * Construct an enhancement table from a remote URL object.
+     *
+     * @param url     URL representing the enhancement table.
+     *
+     * @exception  McIDASException  error finding or reading the URL.
+     */
+    public EnhancementTable(URL url)
+        throws McIDASException
+    {
+        try
+        {
+            dataStream = 
+                new DataInputStream(
+                    new BufferedInputStream(url.openStream()));
+        }
+        catch (Exception e)
+        {
+            throw new McIDASException(
+                "Unable to open enhancement table at URL" + url);
+        }
+        readRGBValues();
+    }
+
+    /**
+     * read in the values
+     */
+    private void readRGBValues()
+        throws McIDASException
+    {
+        rgbValues = new int[3][256];
+        try
+        {
+            int reservedWord = dataStream.readInt();
+            for (int i = 0; i < 3; i++)
+            {
+                for (int j = 0; j < 256; j++) 
+                    rgbValues[i][j] = dataStream.readInt();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new McIDASException("Invalid enhancement table");
+        }
+    }
+
+    /**
+     * Retrieve the data values.  
+     *
+     * @return  integer array [3][256] of red, green, blue values or null if
+     *          the table was not initialized correctly.  Values
+     *          range from 0-255.
+     */
+    public int[][] getRGBValues()
+    {
+        return rgbValues;
+    }
+
+    /**
+     * Look up a unique (hopefully) RGB value and return the index
+     *
+     * @return index value (0-255) or -1 if not found
+     *
+     */
+
+     public int getIndex(int red, int green, int blue) {
+       int inx;
+       for (inx=0; inx<256; inx++) {
+         if (rgbValues[0][inx] == red &&
+             rgbValues[1][inx] == green &&
+             rgbValues[2][inx] == blue) {
+
+           return inx;
+         }
+
+       }
+       return -1;
+     }
+
+    /**
+     * Print out a pretty table.  Currently lists all values, but will
+     * eventually print a format like EU TABLE.
+     *
+     * @return  table of levels, red, green and blue values
+     */
+    public String toString()
+    {
+        StringBuffer sb = new StringBuffer();
+        sb.append(" Brightness  Red       Green      Blue ");
+        sb.append("\n");
+        sb.append("  min max   min max   min max   min max");
+        sb.append("\n");
+        sb.append("  --- ---   --- ---   --- ---   --- ---");
+        sb.append("\n");
+        for (int i = 0; i < 256; i++)
+        {
+            sb.append(" " + i + "  " + 
+                      rgbValues[0][i] + " " + 
+                      rgbValues[1][i] + " " +
+                      rgbValues[2][i]);
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+
+    /**  
+     * Test by running:
+     * <UL>
+     * <LI>java edu.wisc.ssec.mcidas.EnhancementTable   _OR_
+     * <LI>java edu.wisc.ssec.mcidas.EnhancementTable <i>enhancement_file</i>.
+     * </UL>
+     */
+    public static void main(String[] args)
+    {
+        try
+        {
+            EnhancementTable et = 
+                args.length == 0
+                    ? new EnhancementTable()
+                    : new EnhancementTable(args[0]);
+            System.out.println(et.toString());
+        }
+        catch (McIDASException e)
+        {
+            System.out.println(e.toString());
+        }
+    }
+}
+                
+
+        
diff --git a/edu/wisc/ssec/mcidas/GEOSnav.java b/edu/wisc/ssec/mcidas/GEOSnav.java
new file mode 100644
index 0000000..1a755e7
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/GEOSnav.java
@@ -0,0 +1,210 @@
+//
+// GEOSnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/*
+This code translated to Java from the original McIDAS module: nvxgeos.dlm.
+The following notice is included in that code:
+
+C****************************************************************
+C AUTHOR      : Oscar Alonso Lasheras
+C COPYRIGHT GMV S.A. 2006
+C PROPERTY OF GMV S.A.; ALL RIGHTS RESERVED
+C
+C PROJECT     : S3GICast
+C FILE NAME   : nvxgeos.dlm
+C LANGUAGE    : FORTRAN-77
+C TYPE        : Funcion auxliar para creacion de navegacion GEOS
+C DESCRIPTION : Navega los productos MPEF y OSIS, ademas de los productos
+C               de los servidores FSDX y SAFN-HDF5 en McIDAS
+C
+*/
+
+public class GEOSnav extends AREAnav {
+
+  private static final long serialVersionUID = 1L;
+  final int loff, coff, lfac, cfac, plon;
+  final double radpol = 6356.5838;
+  final double radeq = 6378.1690;
+  final double X42 = 42164.0;
+
+  private boolean isEastPositive = true;
+
+  public GEOSnav(int[] iparms) throws IllegalArgumentException {
+
+    if (iparms[0] != GEOS) 
+      throw new IllegalArgumentException ("Invald navigation type " + iparms[0]);
+
+    loff = iparms[1];
+    coff = iparms[2];
+    lfac = iparms[3];
+    cfac = iparms[4];
+    plon = iparms[5];
+   
+  }
+
+
+  /**
+  * @param latlon lat and lon of points (N and E are positive)
+  */
+
+  public double[][] toLinEle(double[][] latlon) {
+    double xlat, xlon, xlin, xele;
+    double c_lat, cosc_lat, rn, r1, r2, r3, rl;
+    double x,y;
+    double lat,lon,splon;
+    double ad2, bd, cd, delta2, halfsom, r_eq2, r_pol2;
+
+    int number = latlon[0].length;
+    double[][] linele = new double[2][number];
+
+    for (int point=0; point < number; point++) {
+      xlat = latlon[indexLat][point];
+      xlon = latlon[indexLon][point];
+      if (!isEastPositive) xlon = -xlon;
+
+
+      // --- Coordinates are computed accroding EUMETSAT's LRIT/HRIT Global Spec
+      // --- Doc No: CGMS 03
+
+      // --- Coordinates are converted to Radians
+      lat   = xlat*Math.PI/180.;
+      lon   = xlon*Math.PI/180.0;
+      splon = plon/10. * Math.PI/180.0;
+
+      // --- Intermediate data
+      c_lat=Math.atan(0.993243*Math.tan(lat));
+      cosc_lat=Math.cos(c_lat);
+      r_pol2= radpol * radpol;
+      r_eq2 = radeq * radeq;
+      rl=radpol/(Math.sqrt(1-((r_eq2-r_pol2)/r_eq2)*cosc_lat*cosc_lat));
+      r1=X42-rl*cosc_lat*Math.cos(lon-splon);
+      r2=-rl*cosc_lat*Math.sin(lon-splon);
+      r3=rl*Math.sin(c_lat);
+      rn=Math.sqrt(r1*r1+r2*r2+r3*r3);
+
+      // --- Compute variables useful to check if pixel is visible
+      ad2 = r1*r1 + r2*r2 + r3*r3*r_eq2 / r_pol2;
+      bd = X42*r1;
+      cd = X42*X42 - r_eq2;
+      delta2 = bd*bd-ad2*cd;
+      halfsom = bd*rn/ad2;
+
+      if ((delta2 >= 0.) && (rn <= halfsom)) {
+      // ------- Intermediate coordinates
+        x = Math.atan(-r2/r1);
+        y = Math.asin(-r3/rn);
+        x = x * 180./Math.PI;
+        y = y * 180./Math.PI;
+
+        xele = coff/10. + x / Math.pow(2,16) * cfac/10.;
+        xlin = loff/10. + y / Math.pow(2,16) * lfac/10.;
+      } else {
+
+        xlin=Double.NaN;
+        xele=Double.NaN;
+      }
+
+      linele[indexLine][point] = xlin;
+      linele[indexEle][point] = xele;
+    }
+
+    return imageCoordToAreaCoord(linele, linele);
+
+  }
+
+  public double[][] toLatLon(double[][] linele) {
+
+
+    double xlat, xlon, xlin, xele;
+    double x,y;
+    double s1, s2, s3, sxy, sn, sd, sdd;
+    double aux, aux2;
+    double cosx, cosy, sinx, siny;
+
+
+
+    // --- Coordinates are computed accroding EUMETSAT's LRIT/HRIT Global Spec
+    // --- Doc No: CGMS 03
+
+    int number = linele[0].length;
+    double[][] latlon = new double[2][number];
+    double[][] imglinele = areaCoordToImageCoord(linele);
+
+    for (int point=0; point < number; point++ ) {
+
+      xlin = imglinele[indexLine][point];
+      xele = imglinele[indexEle][point];
+
+      // --- Intermediate coordinates
+      x = (xele - coff/10.) * Math.pow(2,16) / (cfac/10.);
+      y = (xlin - loff/10.) * Math.pow(2,16) / (lfac/10.);
+      x = x * Math.PI/180.;
+      y = y * Math.PI/180.;
+
+      //c --- Intermediate data
+      cosx=Math.cos(x);
+      cosy=Math.cos(y);
+      sinx=Math.sin(x);
+      siny=Math.sin(y);
+
+      aux=X42*cosx*cosy;
+      aux2=cosy*cosy+1.006803*siny*siny;
+      sdd=aux*aux-aux2*1737121856.0;
+      if (sdd < 0.0) {
+        xlat=Double.NaN;
+        xlon=Double.NaN;
+      } else {
+ 
+        sd=Math.sqrt(sdd);
+        sn=(aux-sd)/aux2;
+        s1=X42 - sn*cosx*cosy;
+        s2=sn*sinx*cosy;
+        s3= -sn*siny;
+        sxy=Math.sqrt(s1*s1+s2*s2);
+ 
+        // --- Computation
+        xlon = Math.atan(s2/s1);
+        xlon = xlon * 180./Math.PI + plon/10.;
+        xlat = Math.atan(1.006803*s3/sxy)* 180./Math.PI;
+ 
+        // --- Longitudes in [-180,180]
+        if(xlon >  180.0) xlon = xlon - 360.;
+        if(xlon < -180.0) xlon = xlon + 360.;
+      }
+
+      if (!isEastPositive) xlon = -xlon;
+
+      latlon[indexLat][point] = xlat;
+      latlon[indexLon][point] = xlon;
+
+    }
+
+    return latlon;
+  }
+ 
+}
diff --git a/edu/wisc/ssec/mcidas/GMSXnav.java b/edu/wisc/ssec/mcidas/GMSXnav.java
new file mode 100644
index 0000000..075c238
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/GMSXnav.java
@@ -0,0 +1,1222 @@
+//
+// GMSXnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.Math;
+
+/**
+ * This class implements GMSX navigation.  The code was modified
+ * from the original FORTRAN code (nvxgmsx.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL)
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see edu.wisc.ssec.mcidas.AREAnav
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author Tommy Jasmin, University of Wisconsin, SSEC
+ */
+
+public class GMSXnav extends AREAnav
+
+{
+
+  private byte bParms[] = new byte[3200];
+  private float subLon;
+  private float subLat;
+  private float [] resLin = new float[4];
+  private float [] resEle = new float[4];
+  private float [] rlic = new float[4];
+  private float [] relmfc = new float[4];
+  private float [] senssu = new float[4];
+  private float [] rline = new float[4];
+  private float [] relem = new float[4];
+  private float [] vmis = new float[3];
+  private float [][] elmis = new float[3][3];
+  private double dtims = 0.0d;
+  private double dspin = 0.0d;
+  private double sitagt = 0.0d;
+  private double sunalp = 0.0d;
+  private double sundel = 0.0d;
+  private double [] sat = new double[3];
+  private double [] sp = new double[3];
+  private double [] ss = new double[3];
+  private double [][] orbt1 = new double[35][8];
+  private double [][] atit = new double[10][10];
+
+  // class constants
+  private static final double cdr = Math.PI / 180.0d;
+  private static final double crd = 180.0d / Math.PI;
+  private static final double hpai = Math.PI / 2.0d;
+  private static final double dpai = Math.PI * 2.0d;
+  private static final double ea = 6378136.0d;
+  private static final double ef = 1.0d / 298.257d;
+
+  /**
+   *
+   * constructor: copy nav block to a byte array, eliminating text fields
+   *
+   * @param navBlock - the navigation block from the image file
+   *
+   */
+
+  public GMSXnav(int[] navBlock)
+
+  {
+
+    int i;
+    int j;
+    int index = 0;
+    byte [] tmpArr;
+
+    // copy data to new array, taking out text at start of each block
+    for (i = 0; i < 126; i++) {
+      tmpArr = intToBytes(navBlock[i + 1]);
+      for (j = 0; j < 4; j++) {
+        bParms[index] = tmpArr[j];
+        index++;
+      }
+    }
+
+    int srcOffset = 128;
+
+    for (int block = 0; block < 4; block++) {
+      for (i = 0; i < 127; i++) {
+        tmpArr = intToBytes(navBlock[i + srcOffset]);
+        for (j = 0; j < 4; j++) {
+          bParms[index] = tmpArr[j];
+          index++;
+        }
+      }
+      srcOffset += 128;
+    }
+
+    decOABlock(bParms, 0);
+
+  } 
+
+
+  /**
+   *
+   * toLinEle converts lat/lon to satellite line/element
+   *
+   * @param latlon	array of lat/long pairs. Where latlon[indexLat][]
+   * are latitudes and latlon[indexLon][] are longitudes.
+   *
+   * @return linele[][] array of line/element pairs.  Where
+   * linele[indexLine][] is a line and linele[indexEle][] is an element.       
+   *
+   */
+
+  public synchronized float[][] toLinEle (float[][] latlon) 
+  {
+
+    int mode = 1;
+    int count = latlon[0].length;
+    float [] rtnPoint; 
+    float[][] linele = new float[2][count];
+    float line = 0.0f;
+    float elem = 0.0f;
+    float lon = 0.0f;
+
+    for (int point = 0; point < count; point++) {
+
+      // initialize value as not navigable
+      linele[indexLine][point] = Float.NaN; 
+      linele[indexEle][point] = Float.NaN; 
+
+      if (Math.abs(latlon[indexLat][point]) > 90.0) {
+        continue;
+      }
+
+      // normalize to -180 to 180
+      lon = latlon[indexLon][point];
+      if (lon > 180.f) lon = lon - 360.f;
+      if (lon < -180.f) lon = lon + 360.f;
+      if (Math.abs(latlon[indexLon][point]) > 180.f) continue;
+      if (-lon > 90-subLon && -lon < 270-subLon) continue;
+      
+      rtnPoint = mgivsr (  
+        mode, 
+        (float) elem, 
+        (float) line, 
+        (float) lon, 
+        (float) latlon[indexLat][point]
+      );
+
+      linele[indexLine][point] = rtnPoint[0];
+      linele[indexEle][point] = rtnPoint[1];
+
+    }
+
+    // Return in 'File' coordinates
+    return imageCoordToAreaCoord(linele, linele);
+
+  }
+
+  public synchronized double[][] toLinEle (double[][] latlon) 
+  {
+
+    int mode = 1;
+    int count = latlon[0].length;
+    float [] rtnPoint; 
+    double[][] linele = new double[2][count];
+    double line = 0.0d;
+    double elem = 0.0d;
+    double lon = 0.0f;
+
+    for (int point = 0; point < count; point++) {
+
+      // initialize value as not navigable
+      linele[indexLine][point] = Double.NaN; 
+      linele[indexEle][point] = Double.NaN; 
+
+      if (Math.abs(latlon[indexLat][point]) > 90.0) {
+        continue;
+      }
+
+      // normalize to -180 to 180
+      lon = latlon[indexLon][point];
+      if (lon > 180.0) lon = lon - 360.0;
+      if (lon < -180.0) lon = lon + 360.0;
+      if (Math.abs(latlon[indexLon][point]) > 180.0) continue;
+      if (-lon > 90-subLon && -lon < 270-subLon) continue;
+      
+      rtnPoint = mgivsr (  
+        mode, 
+        (float) elem, 
+        (float) line, 
+        (float) lon, 
+        (float) latlon[indexLat][point]
+      );
+
+      linele[indexLine][point] = rtnPoint[0];
+      linele[indexEle][point] = rtnPoint[1];
+
+    }
+
+    // Return in 'File' coordinates
+    return imageCoordToAreaCoord(linele, linele);
+
+  }
+
+  /**
+   *
+   * toLatLon converts satellite line/element to lat/lon
+   *
+   * @param linele	 array of line/element pairs.  Where
+   * linele[indexLine][] is a line and linele[indexEle][] is an element.       
+   *
+   * @return array of lat/lon pairs. Where latlon[indexLat][]
+   * are latitudes and latlon[indexLon][] are longitudes.
+   *
+   */
+
+  public synchronized float[][] toLatLon (float[][] linele) 
+  {
+
+    int mode = -1;
+    int count = linele[0].length;
+    float [] rtnPoint;
+    float[][] latlon = new float[2][count];
+    float lat  = 0.0f;
+    float lon  = 0.0f;
+
+    // transform file to image coordinates
+    float[][] imgLinEle = areaCoordToImageCoord(linele);
+
+    for (int point = 0; point < count; point++) {
+
+      // initialize value as not navigable
+      latlon[indexLat][point] = Float.NaN; 
+      latlon[indexLon][point] = Float.NaN; 
+
+      rtnPoint = mgivsr (  
+        mode, 
+        (float) imgLinEle[indexEle][point], 
+        (float) imgLinEle[indexLine][point], 
+        (float) lon, 
+        (float) lat
+      );
+
+      // normalize to -180 to 180
+      lon = rtnPoint[1];
+      if (lon > 180) lon = lon - 360;
+      if (lon < -180) lon = lon + 360;
+      if (Math.abs(lon) > 180 ||  (-lon > 90-subLon && -lon < 270-subLon)) {
+          rtnPoint[0] = Float.NaN;
+          lon  = Float.NaN;
+      }
+
+      latlon[indexLat][point] = rtnPoint[0];
+      latlon[indexLon][point] = lon;
+
+    }
+
+    return (latlon);
+
+  }
+
+
+  public synchronized double[][] toLatLon (double[][] linele) 
+  {
+
+    int mode = -1;
+    int count = linele[0].length;
+    float [] rtnPoint;
+    double[][] latlon = new double[2][count];
+    double lat  = 0.0d;
+    double lon  = 0.0d;
+
+    // transform file to image coordinates
+    double[][] imgLinEle = areaCoordToImageCoord(linele);
+
+    for (int point = 0; point < count; point++) {
+
+      // initialize value as not navigable
+      latlon[indexLat][point] = Double.NaN; 
+      latlon[indexLon][point] = Double.NaN; 
+
+      rtnPoint = mgivsr (  
+        mode, 
+        (float) imgLinEle[indexEle][point], 
+        (float) imgLinEle[indexLine][point], 
+        (float) lon, 
+        (float) lat
+      );
+
+      // normalize to -180 to 180
+      lon = rtnPoint[1];
+      if (lon > 180.) lon = lon - 360.;
+      if (lon < -180.) lon = lon + 360.;
+      if (Math.abs(lon) > 180. ||  (-lon > 90.-subLon && -lon < 270.-subLon)) {
+          rtnPoint[0] = Float.NaN;
+          lon  = Float.NaN;
+      }
+
+      latlon[indexLat][point] = rtnPoint[0];
+      latlon[indexLon][point] = lon;
+
+    }
+
+    return (latlon);
+
+  }
+
+  /** Get the lat,lon of the subpoint if available
+  *
+  * @return double[2] {lat, lon}
+  *
+  */
+  
+  public double[] getSubpoint() {
+    return new double[] {(double)subLat, (double)subLon};
+  }
+
+
+  /**
+   *
+   * sv0100 converts 4 or 6 byte byte values to double
+   *
+   * @param iWord - size of word in bytes from input array
+   * @param iPos - power of 10 to multiply result
+   * @param b - byte array to extract input bytes from
+   * @param bOffs - offset in byte array to begin conversion
+   *
+   * @return converted value
+   *
+   */
+
+  private double sv0100 (
+    int iWord,
+    int iPos,
+    byte[] b,
+    int bOffs
+  ) 
+
+  {
+
+    boolean negative = false; 
+    int [] tmpInt = new int[6];
+    double r8Dat = 0.0d;
+
+    if (b[bOffs] < 0) {
+      negative = true;
+    }
+
+    if (iWord == 4) {
+      for (int i = 1; i < 4; i++) {
+        if (b[i + bOffs] < 0) {
+          tmpInt[i] = (b[i + bOffs] & 0x7F) + 128;
+        } else {
+          tmpInt[i] = b[i + bOffs];
+        }
+      }
+      r8Dat = (
+        ((double) (b[0 + bOffs] & 0x7F) * 16777216.0d) +
+        ((double) (tmpInt[1]) * 65536.0d) +
+        ((double) (tmpInt[2]) * 256.0d) +
+         (double) (tmpInt[3])
+      );
+    }
+
+    if (iWord == 6) {
+      for (int i = 1; i < 6; i++) {
+        if (b[i + bOffs] < 0) {
+          tmpInt[i] = (b[i + bOffs] & 0x7F) + 128;
+        } else {
+          tmpInt[i] = b[i + bOffs];
+        }
+      }
+      r8Dat = (
+        ((double) (b[0 + bOffs] & 0x7F) * Math.pow(2.0d, 40)) +
+        ((double) (tmpInt[1]) * Math.pow(2.0d, 32)) +
+        ((double) (tmpInt[2]) * 16777216.0d) +
+        ((double) (tmpInt[3]) * 65536.0d) +
+        ((double) (tmpInt[4]) * 256.0d) +
+        ((double) (tmpInt[5]))
+      );
+    }
+
+    r8Dat = r8Dat / Math.pow(10.0d, iPos);
+    if (negative) {
+      r8Dat = -r8Dat;
+    }
+
+    return(r8Dat);
+
+  }
+
+
+  /**
+   *
+   * decOABlock: decode Orbit and Attitude data block
+   *
+   * @param b - input byte array to decode
+   * @param form - long (1) or short (0)
+   *
+   */
+
+  private void decOABlock (
+    byte[] b,
+    int form
+  )
+
+  {
+
+    int i = 0;
+    int j = 0;
+    int offset;
+    int tmpLoInt;
+    int tmpHiInt;
+    long tmpLong;
+    float r4Dmy = 0.0f;
+    double r8Dmy = 0.0d;
+
+    dtims = sv0100(6,  8, b,   0);
+    dspin = sv0100(6,  8, b, 240);
+    resLin[0] = (float) sv0100(4,  8, b,  6);
+    resLin[1] = (float) sv0100(4,  8, b, 10);
+    resLin[2] = (float) sv0100(4,  8, b, 10);
+    resLin[3] = (float) sv0100(4,  8, b, 10);
+    resEle[0] = (float) sv0100(4, 10, b, 14);
+    resEle[1] = (float) sv0100(4, 10, b, 18);
+    resEle[2] = (float) sv0100(4, 10, b, 18);
+    resEle[3] = (float) sv0100(4, 10, b, 18);
+    rlic[0] = (float) sv0100(4,  4, b,  22);
+    rlic[1] = (float) sv0100(4,  4, b,  26);
+    rlic[2] = (float) sv0100(4,  4, b, 110);
+    rlic[3] = (float) sv0100(4,  4, b, 114);
+    relmfc[0] = (float) sv0100(4,  4, b,  30);
+    relmfc[1] = (float) sv0100(4,  4, b,  34);
+    relmfc[2] = (float) sv0100(4,  4, b, 118);
+    relmfc[3] = (float) sv0100(4,  4, b, 122);
+    senssu[0] = (float) sv0100(4,  0, b,  38);
+    senssu[1] = (float) sv0100(4,  0, b,  42);
+    senssu[2] = (float) sv0100(4,  0, b,  42);
+    senssu[3] = (float) sv0100(4,  0, b,  42);
+    rline[0] = (float) sv0100(4,  0, b,  46);
+    rline[1] = (float) sv0100(4,  0, b,  50);
+    rline[2] = (float) sv0100(4,  0, b,  50);
+    rline[3] = (float) sv0100(4,  0, b,  50);
+    relem[0] = (float) sv0100(4,  0, b,  54);
+    relem[1] = (float) sv0100(4,  0, b,  58);
+    relem[2] = (float) sv0100(4,  0, b,  58);
+    relem[3] = (float) sv0100(4,  0, b,  58);
+    vmis[0] = (float) sv0100(4, 10, b,  62);
+    vmis[1] = (float) sv0100(4, 10, b,  66);
+    vmis[2] = (float) sv0100(4, 10, b,  70);
+    elmis[0][0] = (float) sv0100(4,  7, b,  74);
+    elmis[1][0] = (float) sv0100(4, 10, b,  78);
+    elmis[2][0] = (float) sv0100(4, 10, b,  82);
+    elmis[0][1] = (float) sv0100(4, 10, b,  86);
+    elmis[1][1] = (float) sv0100(4,  7, b,  90);
+    elmis[2][1] = (float) sv0100(4, 10, b,  94);
+    elmis[0][2] = (float) sv0100(4, 10, b,  98);
+    elmis[1][2] = (float) sv0100(4, 10, b, 102);
+    elmis[2][2] = (float) sv0100(4,  7, b, 106);
+    subLon = (float) sv0100(6,  6, b, 198);
+    subLat = (float) sv0100(6,  6, b, 204);
+
+    for (i = 0; i < 10; i++) {
+      // long form
+      if (form == 1) {
+        j = ((i - 1) * 64) + 256;
+      }
+      // short form
+      if (form == 0) {
+        j = ((i - 1) * 48) + 256;
+      }
+      atit[0][i] = sv0100(6,  8, b, j);
+      atit[2][i] = sv0100(6,  8, b, j + 12);
+      atit[3][i] = sv0100(6, 11, b, j + 18);
+      atit[4][i] = sv0100(6,  8, b, j + 24);
+      atit[5][i] = sv0100(6,  8, b, j + 30);
+    }
+
+    for (i = 0; i < 8; i++) {
+      // long form
+      if (form == 1) {
+        j = ((i - 1) * 256) + 896;
+      }
+      // short form
+      if (form == 0) {
+        j = ((i - 1) * 200) + 736;
+      }
+      orbt1[ 0][i] = sv0100(6,  8, b, j +   0);
+      orbt1[ 8][i] = sv0100(6,  6, b, j +  48);
+      orbt1[ 9][i] = sv0100(6,  6, b, j +  54);
+      orbt1[10][i] = sv0100(6,  6, b, j +  60);
+      orbt1[14][i] = sv0100(6,  8, b, j +  84);
+      orbt1[17][i] = sv0100(6,  8, b, j + 102);
+      orbt1[18][i] = sv0100(6,  8, b, j + 108);
+      orbt1[19][i] = sv0100(6, 12, b, j + 128);
+      orbt1[20][i] = sv0100(6, 14, b, j + 134);
+      orbt1[21][i] = sv0100(6, 14, b, j + 140);
+      orbt1[22][i] = sv0100(6, 14, b, j + 146);
+      orbt1[23][i] = sv0100(6, 12, b, j + 152);
+      orbt1[24][i] = sv0100(6, 16, b, j + 158);
+      orbt1[25][i] = sv0100(6, 12, b, j + 164);
+      orbt1[26][i] = sv0100(6, 16, b, j + 170);
+      orbt1[27][i] = sv0100(6, 12, b, j + 176);
+    }
+
+    return;
+
+  }
+
+
+  /**
+   *
+   * set the sub-point Latitude and Longitude
+   *
+   * @param ll is the [latitude,longitude] of the sub-point
+   *
+   *
+   */
+
+  private void subLatLon (float[] ll) 
+
+  {
+
+    return;
+
+  }
+
+
+  /**
+   *
+   * mgivsr does the actual conversion to/from lat/lon or line/elem
+   *
+   * @param iMode - conversion mode, to lat/lon or to line/elem
+   * @param rPix - float pixel or element value
+   * @param rLin - float line value
+   * @param rLat - float latitude value
+   * @param rLon - float longitude value
+   *
+   * @return array of two floating point values, lat/lon or line/elem pair
+   *
+   */
+
+  private float [] mgivsr (
+    int iMode,
+    float rPix,
+    float rLin,
+    float rLon,
+    float rLat
+  ) 
+
+  {
+
+    int rc = 0;
+    int lMode;
+    float rstep;
+    float rsamp;
+    float rfcl;
+    float rfcp;
+    float sens;
+    float rftl;
+    float rftp;
+    float ri;
+    float rj;
+    float rio;
+    float [] point = new float[2];
+    double bc;
+    double bs;
+    double beta = 0.0d;
+    double dd;
+    double dda;
+    double ddb;
+    double ddc;
+    double def;
+    double ee;
+    double en;
+    double dk;
+    double dk1;
+    double dk2;
+    double dLat = 0.0d;
+    double dLon = 0.0d;
+    double pc, ps;
+    double qc, qs;
+    double rtim = 0.0d; 
+    double tf;
+    double tl = 0.0d;
+    double tp = 0.0d;
+    double [] stn1 = new double[3];
+    double [] sl;
+    double [] slv = new double[3];
+    double [] sx;
+    double [] sy;
+    double [] sw1;
+    double [] sw2;
+    double [] sw3 = new double[3];
+
+    point[0] = Float.NaN;
+    point[1] = Float.NaN;
+
+    // parameter checks
+    if (Math.abs(iMode) > 4) {
+      rc = 1;
+      return (point);
+    }
+    if ((Math.abs(rLat) > 90) && (iMode > 0)) {
+      rc = 2;
+      return (point);
+    }
+
+    // vissr frame information set
+    lMode = Math.abs(iMode) - 1;
+    rstep = resLin[lMode];
+    rsamp = resEle[lMode];
+    rfcl = rlic[lMode];
+    rfcp = relmfc[lMode];
+    sens = senssu[lMode];
+    rftl = (float) (rline[lMode] + 0.5);
+    rftp = (float) (relem[lMode] + 0.5);
+
+    // transformation, geographical -> VISSR
+    if (iMode > 0) {
+
+      dLat = (double) rLat * cdr;
+      dLon = (double) rLon * cdr;
+      ee = (2.0d * ef) - (ef * ef);
+      en = ea / Math.sqrt(1.0d - (ee * Math.sin(dLat) * Math.sin(dLat)));
+      stn1[0] = en * Math.cos(dLat) * Math.cos(dLon);
+      stn1[1] = en * Math.cos(dLat) * Math.sin(dLon);
+      stn1[2] = (en * (1.0d - ee)) * Math.sin(dLat);
+      rio = (float) (rfcl - 
+        Math.atan(Math.sin(dLat) / (6.610689 - Math.cos(dLat))) / rstep);
+      rtim = dtims + (double) (rio / sens / 1440.0d) / dspin;
+
+      loop: while (true) {
+
+        beta = mg1100(rtim);
+        sw1 = mg1220(sp, ss);
+        sw2 = mg1220(sw1, sp);
+        bc = Math.cos(beta);
+        bs = Math.sin(beta);
+        sw3[0] = (sw1[0] * bs) + (sw2[0] * bc);
+        sw3[1] = (sw1[1] * bs) + (sw2[1] * bc);
+        sw3[2] = (sw1[2] * bs) + (sw2[2] * bc);
+        sx = mg1200(sw3);
+        sy = mg1220(sp, sx);
+        slv[0] = stn1[0] - sat[0];
+        slv[1] = stn1[1] - sat[1];
+        slv[2] = stn1[2] - sat[2];
+        sl = mg1200(slv);
+        sw2 = mg1210(sp, sl);
+        sw3 = mg1210(sy, sw2);
+        tp = mg1230(sy, sw2);
+        tf = (sp[0] * sw3[0]) + (sp[1] * sw3[1]) + (sp[2] * sw3[2]);
+        if (tf < 0.0d) {
+          tp = -tp;
+        }
+        tl = mg1230(sp, sl);
+        ri = (float) (hpai - tl) / rstep + rfcl - (vmis[1] / rstep);
+        rj = (float) (tp / rsamp + rfcp + (vmis[2] / rsamp) -
+          (hpai - tl) * Math.tan(vmis[0]) / rsamp);
+        if (Math.abs(ri - rio) >= 1.0d) {
+          rtim = (double) (Math.rint((ri - 1) / sens) + 
+            (rj * rsamp) / dpai) / (dspin * 1440.0) + dtims;
+          rio = ri;
+          continue loop;
+        }
+        break loop;
+      }
+
+      point[0] = ri;
+      point[1] = rj;
+      rLin = ri;
+      rPix = rj;
+      if ((rLin < 0) || (rLin > rftl)) {
+        point[0] = Float.NaN;
+        point[1] = Float.NaN;
+        rc = 4;
+      }
+      if ((rPix < 0) || (rPix > rftp)) {
+        point[0] = Float.NaN;
+        point[1] = Float.NaN;
+        rc = 5;
+      }
+
+    }
+      // transformation, VISSR -> geographical
+    if (iMode < 0) {
+      rtim = (double) (Math.rint((rLin - 1) / sens) + 
+        (rPix * rsamp) / dpai) / (dspin * 1440.0) + dtims;
+      beta = mg1100(rtim);
+      sw1 = mg1220(sp, ss);
+      sw2 = mg1220(sw1, sp);
+      bc = Math.cos(beta);
+      bs = Math.sin(beta);
+      sw3[0] = (sw1[0] * bs) + (sw2[0] * bc);
+      sw3[1] = (sw1[1] * bs) + (sw2[1] * bc);
+      sw3[2] = (sw1[2] * bs) + (sw2[2] * bc);
+      sx = mg1200(sw3);
+      sy = mg1220(sp, sx);
+      pc = Math.cos(rstep * (rLin - rfcl));
+      ps = Math.sin(rstep * (rLin - rfcl));
+      qc = Math.cos(rsamp * (rPix - rfcp));
+      qs = Math.sin(rsamp * (rPix - rfcp));
+      sw1[0] = (elmis[0][0] * pc) + (elmis[0][2] * ps);
+      sw1[1] = (elmis[1][0] * pc) + (elmis[1][2] * ps);
+      sw1[2] = (elmis[2][0] * pc) + (elmis[2][2] * ps);
+      sw2[0] = (qc * sw1[0]) - (qs * sw1[1]);
+      sw2[1] = (qs * sw1[0]) + (qc * sw1[1]);
+      sw2[2] = sw1[2];
+      sw3[0] = (sx[0] * sw2[0]) + (sy[0] * sw2[1]) + (sp[0] * sw2[2]);
+      sw3[1] = (sx[1] * sw2[0]) + (sy[1] * sw2[1]) + (sp[1] * sw2[2]);
+      sw3[2] = (sx[2] * sw2[0]) + (sy[2] * sw2[1]) + (sp[2] * sw2[2]);
+      sl = mg1200(sw3);
+      def = (1.0d - ef) * (1.0d - ef);
+      dda = def * ((sl[0] * sl[0]) + (sl[1] * sl[1])) + (sl[2] * sl[2]);
+      ddb = def * ((sat[0] * sl[0]) + (sat[1] * sl[1])) + (sat[2] * sl[2]);
+      ddc = def * ((sat[0] * sat[0]) + (sat[1] * sat[1]) - (ea * ea)) + 
+        (sat[2] * sat[2]);
+      dd = (ddb * ddb) - (dda * ddc);
+      if ((dd >= 0.0d) && (dda != 0.0d)) {
+        dk1 = (-ddb + Math.sqrt(dd)) / dda;
+        dk2 = (-ddb - Math.sqrt(dd)) / dda;
+      } else {
+        rc = 6;
+        return (point);
+      }
+      if (Math.abs(dk1) < Math.abs(dk2)) {
+        dk = dk1;
+      } else {
+        dk = dk2;
+      }
+      stn1[0] = sat[0] + (dk * sl[0]);
+      stn1[1] = sat[1] + (dk * sl[1]);
+      stn1[2] = sat[2] + (dk * sl[2]);
+      dLat = Math.atan(stn1[2] / 
+        (def * Math.sqrt((stn1[0] * stn1[0]) + (stn1[1] * stn1[1]))));
+      if (stn1[0] != 0.0d) {
+        dLon = Math.atan(stn1[1] / stn1[0]);
+        if ((stn1[0] < 0.0d) && (stn1[1] >= 0.0d)) {
+          dLon = dLon + Math.PI;
+        }
+        if ((stn1[0] < 0.0d) && (stn1[1] < 0.0d)) {
+          dLon = dLon - Math.PI;
+        }
+      } else {
+        if (stn1[1] > 0.0d) {
+          dLon = hpai;
+        } else {
+          dLon = -hpai;
+        }
+      }
+      point[0] = (float) (dLat * crd);
+      point[1] = (float) (dLon * crd);
+    }
+
+    return (point);
+
+  }
+
+
+  /**
+   *
+   * mg1100 conversion routine of some sort
+   *
+   * @param rtim - ?
+   *
+   * @return converted value ?
+   *
+   */
+
+  private double mg1100 (
+    double rtim
+  ) 
+
+  {
+
+    double beta = 0.0d;
+    double delt = 0.0d;
+    double attalp = 0.0d;
+    double attdel = 0.0d;
+    double wkcos = 0.0d;
+    double wksin = 0.0d;
+    double [] att1 = new double[3];
+    double [] att2 = new double[3];
+    double [] att3 = new double[3];
+    double [][] npa = new double[3][3]; 
+
+    for (int i = 0; i < 7; i++) {
+      if ((rtim > orbt1[0][i]) && (rtim < orbt1[0][i+1])) {
+        npa = mg1110(i, rtim, orbt1);
+        break;
+      }
+    }
+
+    for (int i = 0; i < 9; i++) {
+      if ((rtim >= atit[0][i]) && (rtim < atit[0][i+1])) {
+        delt = (rtim - atit[0][i]) / (atit[0][i+1] - atit[0][i]);
+        attalp = atit[2][i] + (atit[2][i+1] - atit[2][i]) * delt;
+        attdel = atit[3][i] + (atit[3][i+1] - atit[3][i]) * delt;
+        beta   = atit[4][i] + (atit[4][i+1] - atit[4][i]) * delt;
+        if ((atit[4][i+1] - atit[4][i]) > 0.0d) {
+          beta = atit[4][i] + (atit[4][i+1] - atit[4][i] - 360.0d * cdr) * delt;
+        }
+        break;
+      }
+    }
+
+    wkcos   = Math.cos(attdel);
+    att1[0] = Math.sin(attdel);
+    att1[1] = wkcos * (-Math.sin(attalp));
+    att1[2] = wkcos * Math.cos(attalp);
+
+    att2[0] = (
+      (npa[0][0] * att1[0]) + 
+      (npa[0][1] * att1[1]) + 
+      (npa[0][2] * att1[2])
+    );
+    att2[1] = (
+      (npa[1][0] * att1[0]) + 
+      (npa[1][1] * att1[1]) + 
+      (npa[1][2] * att1[2])
+    );
+    att2[2] = (
+      (npa[2][0] * att1[0]) + 
+      (npa[2][1] * att1[1]) + 
+      (npa[2][2] * att1[2])
+    );
+
+    wksin = Math.sin(sitagt);
+    wkcos = Math.cos(sitagt);
+
+    att3[0] = ( wkcos * att2[0]) + (wksin * att2[1]);
+    att3[1] = (-wksin * att2[0]) + (wkcos * att2[1]);
+    att3[2] = att2[2];
+    sp = mg1200(att3);
+
+    wkcos   = Math.cos(sundel);
+    ss[0] = wkcos * Math.cos(sunalp);
+    ss[1] = wkcos * Math.sin(sunalp);
+    ss[2] = Math.sin(sundel);
+
+    return(beta);
+
+  }
+
+
+  /**
+   *
+   * mg1110 conversion routine of some sort
+   *
+   * @param i - ?
+   * @param rtim - ?
+   * @param orbta - ?
+   *
+   * @return 3 by 3 double array
+   *
+   */
+
+  private double [][] mg1110 (
+    int i,
+    double rtim,
+    double [][] orbta
+  )
+
+  {
+
+    double [][] npa = new double[3][3];
+    double delt;
+
+    if (i != 7) {
+
+      delt = (rtim - orbta[0][i]) / (orbta[0][i+1] - orbta[0][i]);
+      sat[0] = orbta[ 8][i] + (orbta[ 8][i+1] - orbta[ 8][i]) * delt;
+      sat[1] = orbta[ 9][i] + (orbta[ 9][i+1] - orbta[ 9][i]) * delt;
+      sat[2] = orbta[10][i] + (orbta[10][i+1] - orbta[10][i]) * delt;
+
+      sitagt = (orbta[14][i] + (orbta[14][i+1] - orbta[14][i]) * delt) * cdr;
+      if ((orbta[14][i+1] - orbta[14][i]) < 0.0d) {
+        sitagt = (orbta[14][i] + 
+          (orbta[14][i+1] - orbta[14][i] + 360.0d) * delt) * cdr;
+      }
+      sunalp = (orbta[17][i] + (orbta[17][i+1] - orbta[17][i]) * delt) * cdr;
+      if ((orbta[17][i+1] - orbta[17][i]) > 0.0d) {
+        sunalp = (orbta[17][i] + 
+          (orbta[17][i+1] - orbta[17][i] - 360.0d) * delt) * cdr;
+      }
+      sundel = (orbta[18][i] + (orbta[18][i+1] - orbta[18][i]) * delt) * cdr;
+ 
+      npa[0][0] = orbta[19][i];
+      npa[1][0] = orbta[20][i];
+      npa[2][0] = orbta[21][i];
+      npa[0][1] = orbta[22][i];
+      npa[1][1] = orbta[23][i];
+      npa[2][1] = orbta[24][i];
+      npa[0][2] = orbta[25][i];
+      npa[1][2] = orbta[26][i];
+      npa[2][2] = orbta[27][i];
+
+    }
+
+    return(npa);
+
+  }
+
+
+  /**
+   *
+   * mg1200 conversion routine of some sort
+   *
+   * @param vec - ?
+   *
+   * @return double array size 3
+   *
+   */
+
+  private double[] mg1200 (
+    double [] vec
+  ) 
+
+  {
+
+    double [] vecu = new double[3];
+    double rv1;
+    double rv2;
+
+    rv1 = (vec[0] * vec[0]) + (vec[1] * vec[1]) + (vec[2] * vec[2]);
+
+    if (rv1 == 0.0d) {
+      return(vecu);
+    }
+
+    rv2 = Math.sqrt(rv1);
+    vecu[0] = vec[0] / rv2;
+    vecu[1] = vec[1] / rv2;
+    vecu[2] = vec[2] / rv2;
+
+    return(vecu);
+
+  }
+
+
+  /**
+   *
+   * mg1210 conversion routine of some sort
+   *
+   * @param va - ?
+   * @param vb - ?
+   *
+   * @return double array size 3
+   *
+   */
+
+  private double [] mg1210 (
+    double [] va,
+    double [] vb
+  ) 
+
+  {
+
+    double [] vc = new double[3];
+
+    vc[0] = (va[1] * vb[2]) - (va[2] * vb[1]);
+    vc[1] = (va[2] * vb[0]) - (va[0] * vb[2]);
+    vc[2] = (va[0] * vb[1]) - (va[1] * vb[0]);
+
+    return(vc);
+
+  }
+
+
+  /**
+   *
+   * mg1220 conversion routine of some sort
+   *
+   * @param va - ?
+   * @param vb - ?
+   *
+   * @return double array size 3
+   *
+   */
+
+  private double [] mg1220 (
+    double [] va,
+    double [] vb
+  ) 
+
+  {
+
+    double [] vc = new double[3];
+    double [] vd = new double[3];
+
+    vc[0] = (va[1] * vb[2]) - (va[2] * vb[1]);
+    vc[1] = (va[2] * vb[0]) - (va[0] * vb[2]);
+    vc[2] = (va[0] * vb[1]) - (va[1] * vb[0]);
+
+    vd = mg1200(vc);
+
+    return(vd);
+
+  }
+
+
+  /**
+   *
+   * mg1230 conversion routine of some sort
+   *
+   * @param va - ?
+   * @param vb - ?
+   *
+   * @return double 
+   *
+   */
+
+  private double mg1230 (
+    double [] va,
+    double [] vb
+  ) 
+
+  {
+
+    double as1;
+    double as2;
+    double asita = 0.0d;
+
+    as1 = (va[0] * vb[0]) + (va[1] * vb[1]) + (va[2] * vb[2]);
+    as2 = (((va[0] * va[0]) + (va[1] * va[1]) + (va[2] * va[2])) *
+           ((vb[0] * vb[0]) + (vb[1] * vb[1]) + (vb[2] * vb[2])));
+
+    if (as2 == 0.0d) {
+      return(asita);
+    }
+    //asita = Math.acos(Math.min(Math.max(as1,.0000001),1.0) / Math.sqrt(as2));
+    double temp = as1/Math.sqrt(as2);
+    if (temp > 1.0 && temp-1.0 < 0.000001) temp = 1;
+    asita = Math.acos(temp);
+
+    return(asita);
+
+  }
+
+
+  /**
+   *
+   * mg1240 conversion routine of some sort
+   *
+   * @param va - ?
+   * @param vh - ?
+   * @param vn - ?
+   * @param dpai - double PI (Math.PI * 2)
+   *
+   * @return double
+   *
+   */
+
+  private double mg1240 (
+    double [] va,
+    double [] vh,
+    double [] vn,
+    double dpai
+  ) 
+
+  {
+
+    double azi;
+    double dnai;
+    double [] vb = new double[3];
+    double [] vc = new double[3];
+    double [] vd = new double[3];
+
+    vb = mg1220(vn, vh);
+    vc = mg1220(va, vh);
+    azi = mg1230(vb, vc);
+    vd = mg1220(vb, vc);
+
+    dnai = (vd[0] * vh[0]) + (vd[1] * vh[1]) + (vd[2] * vh[2]);
+    if (dnai > 0.0d) {
+      azi = dpai - azi;
+    }
+
+    return(azi);
+
+  }
+
+
+  /**
+   * intToBytes converts an int to an array of 4 bytes.
+   *
+   * @param  v       input value
+   *
+   * @return         the corresponding array of bytes
+   *
+   */
+
+  static public byte[] intToBytes(int v) {
+
+    byte[] b = new byte[4];
+    int allbits = 255;
+
+    for(int i = 0; i < 4; i++){
+      b[3-i] = (byte)((v & (allbits << i * 8) ) >> i *8);
+    }
+
+    return b;
+
+  }
+
+  // main is used for unit testing
+
+  public static void main(String[] args) {
+
+    int [] navBlock = new int[800];
+    int [] dirBlock = new int[64];
+    DataInputStream dis = null;
+    GMSXnav gmsx = null;
+    String fileName = "GMSXAREA";
+
+    System.out.println("unit test of class GMSXnav begin...");
+
+    if (args.length > 0) fileName = args[0];
+
+    // test assumes presence of test area called GMSXAREA
+    try {
+      dis = new DataInputStream (
+        new BufferedInputStream(new FileInputStream(fileName), 2048)
+      );
+    } catch (Exception e) {
+      System.out.println("error creating DataInputStream: " + e);
+      System.exit(0);
+    }
+
+    // read and discard the directory
+    System.out.println("reading in, discarding directory words...");
+    try {
+      for (int i = 0; i < 64; i++) {
+        dirBlock[i] = dis.readInt();
+      }
+    } catch (IOException e) {
+      System.out.println("error reading area file directory: " + e);
+      System.exit(0);
+    }
+
+    // now read in the navigation data
+    System.out.println("reading in navigation words...");
+    try {
+      for (int i = 0; i < navBlock.length; i++) {
+        navBlock[i] = dis.readInt();
+      }
+    } catch (IOException e) {
+      System.out.println("error reading area file navigation data: " + e);
+      System.exit(0);
+    }
+
+    if (navBlock[0] != GMSX) {
+      System.out.println("error: not a GMS navigation block.");
+      System.exit(0);
+    } 
+
+    System.out.println("creating GMSXnav object...");
+    gmsx = new GMSXnav(navBlock);
+    gmsx.setImageStart(dirBlock[5], dirBlock[6]);
+    System.out.println("####  ImageStart set to:"+dirBlock[5]+" "+dirBlock[6]);
+    gmsx.setRes(dirBlock[11], dirBlock[12]);
+    System.out.println("####  ImageRes set to:"+dirBlock[11]+" "+dirBlock[12]);
+    gmsx.setStart(1,1);
+    gmsx.setFlipLineCoordinates(dirBlock[8]); // invert Y axis coordinates
+
+    System.out.println(" test of toLatLon...");
+    System.out.println("  answer should be close to: -2.37, 133.31");
+
+    double [][] linEle = new double [2][1];
+    double [][] latLon = new double [2][1];
+    linEle[gmsx.indexLine][0] = 471.0f;
+    linEle[gmsx.indexEle][0] = 323.0f;
+
+    latLon = gmsx.toLatLon(linEle);
+    System.out.println("  answer is: " + latLon[gmsx.indexLat][0] + 
+      ", " + latLon[gmsx.indexLon][0]);
+
+    System.out.println(" test of toLinEle...");
+
+    System.out.println("  answer should be close to: 480.0, 1.0");
+    latLon[gmsx.indexLat][0] = -2.0f;
+    latLon[gmsx.indexLon][0] = 118.0f;
+    linEle = gmsx.toLinEle(latLon);
+    System.out.println("  answer is: " + linEle[gmsx.indexLine][0] + 
+      ", " + linEle[gmsx.indexEle][0]);
+
+    System.out.println("  answer should be close to: 16.0, 628.0");
+    latLon[gmsx.indexLat][0] = -24.0f;
+    latLon[gmsx.indexLon][0] = 148.0f;
+    linEle = gmsx.toLinEle(latLon);
+    System.out.println("  answer is: " + linEle[gmsx.indexLine][0] + 
+      ", " + linEle[gmsx.indexEle][0]);
+
+    System.out.println("unit test of class GMSXnav end...");
+
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/GOESnav.java b/edu/wisc/ssec/mcidas/GOESnav.java
new file mode 100644
index 0000000..9454779
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/GOESnav.java
@@ -0,0 +1,1097 @@
+//
+// GOESnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for GOES (GOES D-H) type nav. This code was modified
+ * from the original FORTRAN code (nvxgoes.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class GOESnav extends AREAnav 
+{
+
+    private boolean isEastPositive = true;
+
+    // NAVCOM variables
+    private int navday;
+    private int lintot;
+    private double deglin;
+    private int ieltot;
+    private double degele;
+    private double spinra;
+    private int ietimy;
+    private int ietimh;
+    private double semima;
+    private double oeccen;
+    private double orbinc;
+    private double perhel;
+    private double asnode;
+    private double nopcln;
+    private double declin;
+    private double rascen;
+    private double piclin;
+    private double prerat;
+    private double predir;
+    private double pitch;
+    private double yaw;
+    private double roll;
+    private double skew;
+
+    // BETCOM variables
+    private int iajust;
+    private int ibtcon;
+    private int negbet;
+    private int iseang;
+
+    // VASCOM variables
+    private double scan1;
+    private double time1;
+    private double scan2;
+    private double time2;
+
+    // NAVINI variables
+    private double emega;
+    private double ab;
+    private double asq;
+    private double bsq;
+    private double r;
+    private double rsq;
+    private double rdpdg;
+    private int numsen;
+    private double totlin;
+    private double radlin;
+    private double totele;
+    private double radele;
+    private double picele;
+    private double cpitch;
+    private double cyaw;
+    private double croll;
+    private double pskew;
+    private double rfact;
+    private double roasin;
+    private double tmpscl;
+    private double b11;
+    private double b12;
+    private double b13;
+    private double b21;
+    private double b22;
+    private double b23;
+    private double b31;
+    private double b32;
+    private double b33;
+    private double gamma;
+    private double gamdot;
+    private double rotm11;
+    private double rotm13;
+    private double rotm21;
+    private double rotm23;
+    private double rotm31;
+    private double rotm33;
+    private double pictim;
+    private double xref;
+
+    private int iold = 0;
+
+    // variables needed for satvec
+    private double tdife;
+    private double xmmc;
+    private double epsiln;
+    private double srome2;
+    private double pz;
+    private double py;
+    private double px;
+    private double qz;
+    private double qy;
+    private double qx;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the GOES nav 'codicil'.
+     *
+     * @param iarr  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a GOES type.
+     */
+    public GOESnav (int[] iarr) 
+        throws IllegalArgumentException
+    {
+
+/* No longer needed.  Kept for consistency with nvxgoes.dlm
+        if (ifunc != 1) 
+        {
+            if (iarr[0] == XY ) itype = 1;
+            if (iarr[0] == LL ) itype = 2;
+            return;
+        }
+*/
+
+        if (iarr[0] != GOES ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iarr[0]);
+        
+        int jday = iarr[1];
+        int jtime = iarr[2];
+
+        // INITIALIZE NAVCOM
+        navday = jday%100000;
+         
+        if ( iarr[6] <= 0 && iarr[7] <= 0 && iarr[8] <= 0 && iarr[9] <= 0 &&
+        		iarr[10] <= 0 && iarr[11] <= 0 ) {
+               throw new IllegalArgumentException("Invalid orbital parameters");
+        }
+        
+        ietimy = icon1(iarr[4]);
+        ietimh = 100*(iarr[5]/100) + Math.round(.6f*(iarr[5]%100));
+        semima = (float) (iarr[6])/100.0;
+        oeccen = (float) (iarr[7])/1000000.0;
+        orbinc = (float) (iarr[8])/1000.0;
+        double xmeana = (float) (iarr[9])/1000.0;
+        perhel = (float) (iarr[10])/1000.0;
+        asnode = (float) (iarr[11])/1000.0;
+        if (iarr[4] == 0)
+            throw new IllegalArgumentException("Invalid orbit type");
+
+        //CALL EPOCH(IETIMY,IETIMH,SEMIMA,OECCEN,XMEANA);
+        epoch(ietimy,ietimh,semima,oeccen,xmeana);
+
+        declin = McIDASUtil.mcPackedIntegerToDouble(iarr[12]);
+        rascen = McIDASUtil.mcPackedIntegerToDouble(iarr[13]);
+        piclin = iarr[14];
+        if (iarr[14] >= 1000000) piclin = piclin/10000.;
+        if (iarr[12] == 0 && iarr[13] == 0 && iarr[14] == 0)
+            throw new IllegalArgumentException(
+                            "Invalid ascension/declination parameters");
+
+        // ADDED 9/83 TO SUPPORT FRACTIONAL VALUES FOR PICLIN;
+        if (iarr[15] == 0)
+            throw new IllegalArgumentException("Invalid spin period");
+        spinra = iarr[15]/1000.0;
+        if(iarr[15] != 0 && spinra < 300.0) spinra = 60000.0/spinra;
+
+        deglin = McIDASUtil.mcPackedIntegerToDouble(iarr[16]);
+        lintot = iarr[17];
+        degele = McIDASUtil.mcPackedIntegerToDouble(iarr[18]);
+        ieltot = iarr[19];
+        pitch  = McIDASUtil.mcPackedIntegerToDouble(iarr[20]);
+        yaw    = McIDASUtil.mcPackedIntegerToDouble(iarr[21]);
+        roll   = McIDASUtil.mcPackedIntegerToDouble(iarr[22]);
+        skew   = iarr[28]/100000.0;
+        if (iarr[28] == McIDASUtil.MCMISSING) skew = 0.;
+
+
+        //-----INITIALIZE BETCOM
+        iajust = iarr[24];
+        iseang = iarr[27];
+        ibtcon = 6289920;
+        negbet = 3144960;
+
+        //-----INITIALIZE NAVINI COMMON BLOCK
+        emega = .26251617;
+        ab = 40546851.22;
+        asq = 40683833.48;
+        bsq = 40410330.18;
+        r = 6371.221;
+        rsq = r*r;
+        rdpdg = 1.745329252E-02;
+        numsen = (lintot/100000)%100;
+        if (numsen < 1) numsen = 1;
+        totlin = numsen * (lintot%100000);
+        radlin = rdpdg*deglin/(totlin-1.0);
+        totele = ieltot;
+        radele = rdpdg*degele/(totele-1.0);
+        picele = (1.0+totele)/2.0;
+        cpitch = rdpdg*pitch;
+        cyaw = rdpdg*yaw;
+        croll = rdpdg*roll;
+        pskew = Math.atan2(skew, radlin/radele);
+        double stp = Math.sin(cpitch);
+        double ctp = Math.cos(cpitch);
+        double sty = Math.sin(cyaw-pskew);
+        double cty = Math.cos(cyaw-pskew);
+        double str = Math.sin(croll);
+        double ctr = Math.cos(croll);
+        rotm11 = ctr*ctp;
+        rotm13 = sty*str*ctp + cty*stp;
+        rotm21 = -str;
+        rotm23 = sty*ctr;
+        rotm31 = -ctr*stp;
+        rotm33 = cty*ctp - sty*str*stp;
+        rfact = Math.pow(rotm31,2) + Math.pow(rotm33,2);
+        roasin = Math.atan2(rotm31, rotm33);
+        tmpscl = spinra/3600000.0;
+        double dec = declin*rdpdg;
+        double sindec = Math.sin(dec);
+        double cosdec = Math.cos(dec);
+        double ras = rascen*rdpdg;
+        double sinras = Math.sin(ras);
+        double cosras = Math.cos(ras);
+        b11 = -sinras;
+        b12 = cosras;
+        b13 = 0.0;
+        b21 = -sindec*cosras;
+        b22 = -sindec*sinras;
+        b23 = cosdec;
+        b31 = cosdec*cosras;
+        b32 = cosdec*sinras;
+        b33 = sindec;
+
+        //XREF=RAERAC(NAVDAY,0,0.0)*RDPDG
+        double raha = 
+            McIDASUtil.timdif(74001, 0, navday, 0)*1.00273791/4.0 + 100.26467;
+        double rac = raha%360.0;
+        if (rac < 0) rac = rac + 360.0;
+        xref = rac*rdpdg;
+
+        //-----TIME-SPECIFIC PARAMETERS (INCL GAMMA)
+        pictim = McIDASUtil.mcPackedIntegerToDouble(jtime);
+        gamma  = ((float) iarr[38])/100.;
+        gamdot = ((float) iarr[39])/100.;
+
+        //-----INITIALIZE VASCOM
+        int iss = jday/100000;
+        if ( (iss > 25 || iss == 12) && iarr[30] > 0)
+        {
+        //       THIS SECTION DOES VAS BIRDS AND GMS
+        //       IT USES TIMES AND SCAN LINE FROM BETA RECORDS
+            scan1 = (double) iarr[30];
+            time1 = McIDASUtil.mcPackedIntegerToDouble(iarr[31]);
+            scan2 = (double) iarr[34];
+            time2 = McIDASUtil.mcPackedIntegerToDouble(iarr[35]);
+        }
+        else
+        {
+        //        THIS SECTION DOES THE OLD GOES BIRDS
+            scan1 = 1.0;
+            time1 = McIDASUtil.mcPackedIntegerToDouble(jtime);
+            scan2 = (double) (lintot%100000);
+            time2 = time1 + scan2*tmpscl;
+        }
+        iold = 0;
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) 
+    {
+
+        int ilin;
+        double parlin;
+        double framet;
+        double samtim;
+        double xlin;
+        double xele;
+        double ylin;
+        double yele;
+        double xcor;
+        double ycor;
+        double rot;
+        double coslin;
+        double sinlin;
+        double cosele;
+        double sinele;
+        double eli;
+        double emi;
+        double eni;
+        double temp;
+        double elo;
+        double emo;
+        double eno;
+        double basq;
+        double onemsq;
+        double aq;
+        double bq;
+        double cq;
+        double rad;
+        double s;
+        double x;
+        double y;
+        double z;
+        double ct;
+        double st;
+        double x1;
+        double y1;
+
+        int number = linele[0].length;
+        float[][] latlon = new float[2][number];
+
+        // Convert array to Image coordinates for computations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+            ilin = Math.round( (float) xlin);
+            parlin = (ilin - 1)/numsen + 1;
+            framet = tmpscl*parlin;
+            samtim = framet + pictim;
+            double xyz[] = satvec(samtim);
+            ylin = (xlin - piclin) * radlin;
+            yele = (xele - picele + gamma + gamdot*samtim)*radele;
+            xcor = b11*xyz[0] + b12*xyz[1] + b13*xyz[2];
+            ycor = b21*xyz[0] + b22*xyz[1] + b23*xyz[2];
+            rot = Math.atan2(ycor, xcor) + Math.PI;
+            yele = yele - rot;
+            coslin = Math.cos(ylin);
+            sinlin = Math.sin(ylin);
+            sinele = Math.sin(yele);
+            cosele = Math.cos(yele);
+            eli = rotm11*coslin - rotm13*sinlin;
+            emi = rotm21*coslin - rotm23*sinlin;
+            eni = rotm31*coslin - rotm33*sinlin;
+            temp = eli;
+            eli = cosele*eli + sinele*emi;
+            emi = -sinele*temp + cosele*emi;
+            elo = b11*eli + b21*emi + b31*eni;
+            emo = b12*eli + b22*emi + b32*eni;
+            eno = b13*eli + b23*emi + b33*eni;
+            basq = bsq/asq;
+            onemsq = 1.0 - basq;
+            aq = basq + onemsq*Math.pow(eno,2);
+            bq = 2.0 * ((elo*xyz[0] + emo*xyz[1])*basq + eno*xyz[2]);
+            cq = (Math.pow(xyz[0],2) + Math.pow(xyz[1],2))*basq +
+                            Math.pow(xyz[2],2) - bsq;
+            rad = Math.pow(bq,2) - 4.0*aq*cq;
+            if (rad < 1.0)
+            {
+                latlon[indexLat][point] = Float.NaN;
+                latlon[indexLon][point] = Float.NaN;
+            }
+            else
+            {
+                s = -(bq + Math.sqrt(rad))/(2.0*aq);
+                x = xyz[0] + elo*s;
+                y = xyz[1] + emo*s;
+                z = xyz[2] + eno*s;
+                ct = Math.cos(emega*samtim+xref);
+                st = Math.sin(emega*samtim+xref);
+                x1 = ct*x + st*y;
+                y1 = -st*x + ct*y;
+                double ll[] = nxyzll(x1, y1, z);
+
+                latlon[indexLat][point] = (float) ll[0];
+                //  put longitude into East Positive (form)
+                latlon[indexLon][point] = (isEastPositive) ? (float)-ll[1] : (float)ll[1];
+            }
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        int ilin;
+        double parlin;
+        double framet;
+        double samtim;
+        double xlin;
+        double xele;
+        double ylin;
+        double yele;
+        double xcor;
+        double ycor;
+        double rot;
+        double coslin;
+        double sinlin;
+        double cosele;
+        double sinele;
+        double eli;
+        double emi;
+        double eni;
+        double temp;
+        double elo;
+        double emo;
+        double eno;
+        double basq;
+        double onemsq;
+        double aq;
+        double bq;
+        double cq;
+        double rad;
+        double s;
+        double x;
+        double y;
+        double z;
+        double ct;
+        double st;
+        double x1;
+        double y1;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+            ilin = Math.round( (float) xlin);
+            parlin = (ilin - 1)/numsen + 1;
+            framet = tmpscl*parlin;
+            samtim = framet + pictim;
+            double xyz[] = satvec(samtim);
+            ylin = (xlin - piclin) * radlin;
+            yele = (xele - picele + gamma + gamdot*samtim)*radele;
+            xcor = b11*xyz[0] + b12*xyz[1] + b13*xyz[2];
+            ycor = b21*xyz[0] + b22*xyz[1] + b23*xyz[2];
+            rot = Math.atan2(ycor, xcor) + Math.PI;
+            yele = yele - rot;
+            coslin = Math.cos(ylin);
+            sinlin = Math.sin(ylin);
+            sinele = Math.sin(yele);
+            cosele = Math.cos(yele);
+            eli = rotm11*coslin - rotm13*sinlin;
+            emi = rotm21*coslin - rotm23*sinlin;
+            eni = rotm31*coslin - rotm33*sinlin;
+            temp = eli;
+            eli = cosele*eli + sinele*emi;
+            emi = -sinele*temp + cosele*emi;
+            elo = b11*eli + b21*emi + b31*eni;
+            emo = b12*eli + b22*emi + b32*eni;
+            eno = b13*eli + b23*emi + b33*eni;
+            basq = bsq/asq;
+            onemsq = 1.0 - basq;
+            aq = basq + onemsq*Math.pow(eno,2);
+            bq = 2.0 * ((elo*xyz[0] + emo*xyz[1])*basq + eno*xyz[2]);
+            cq = (Math.pow(xyz[0],2) + Math.pow(xyz[1],2))*basq +
+                            Math.pow(xyz[2],2) - bsq;
+            rad = Math.pow(bq,2) - 4.0*aq*cq;
+            if (rad < 1.0)
+            {
+                latlon[indexLat][point] = Double.NaN;
+                latlon[indexLon][point] = Double.NaN;
+            }
+            else
+            {
+                s = -(bq + Math.sqrt(rad))/(2.0*aq);
+                x = xyz[0] + elo*s;
+                y = xyz[1] + emo*s;
+                z = xyz[2] + eno*s;
+                ct = Math.cos(emega*samtim+xref);
+                st = Math.sin(emega*samtim+xref);
+                x1 = ct*x + st*y;
+                y1 = -st*x + ct*y;
+                double ll[] = nxyzll(x1, y1, z);
+
+                latlon[indexLat][point] = ll[0];
+                //  put longitude into East Positive (form)
+                latlon[indexLon][point] = (isEastPositive) ? -ll[1] : ll[1];
+            }
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) 
+    {
+
+        double xpar;
+        double ypar;
+        double xlin;
+        double xele;
+        double x1;
+        double y1;
+        double samtim;
+        double ct;
+        double st;
+        double x;
+        double y;
+        double z;
+        double xht;
+        double vcste1;
+        double vcste2;
+        double vcste3;
+        double vcses1;
+        double vcses2;
+        double vcses3;
+        double oldlin;
+        double orbtim;
+        double xsat;
+        double ysat;
+        double zsat;
+        double xnorm;
+        double ynorm;
+        double znorm;
+        double umv;
+        double x3;
+        double xyzsat[] = new double[3];
+
+        int number = latlon[0].length;
+        float[][] linele = new float[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xpar = latlon[indexLat][point];
+            // expects positive West Longitude.
+            ypar = isEastPositive 
+                     ? -latlon[indexLon][point]
+                     :  latlon[indexLon][point];
+
+            xlin = Double.NaN;
+            xele = Double.NaN;
+
+            if (Math.abs(xpar) <= 90.)
+            {
+
+                // initialize some variables
+                oldlin = 910.; 
+                orbtim = -99999.; 
+                xsat = ysat = zsat = 0.0;
+                x = y = z = 0.0;
+                xht = znorm = 0.0;
+                double xyz[] = nllxyz(xpar, ypar);
+                x1 = xyz[0];
+                y1 = xyz[1];
+                z = xyz[2];
+                samtim = time1;
+
+                for (int i = 0; i < 2; i++)
+                {
+                    if (Math.abs(samtim - orbtim) >= 0.0005)
+                    {
+                        xyzsat = satvec(samtim);
+                        xsat = xyzsat[0];
+                        ysat = xyzsat[1];
+                        zsat = xyzsat[2];
+                        orbtim = samtim;
+                        xht = Math.sqrt(Math.pow(xyzsat[0],2) +
+                                        Math.pow(xyzsat[1],2) +
+                                        Math.pow(xyzsat[2],2));
+                    }
+                    ct = Math.cos(emega*samtim + xref);
+                    st = Math.sin(emega*samtim + xref);
+                    x = ct*x1 - st*y1;
+                    y = st*x1 + ct*y1;
+                    vcste1 = x - xsat;
+                    vcste2 = y - ysat;
+                    vcste3 = z - zsat;
+                    vcses3 = b31*vcste1 + b32*vcste2 + b33*vcste3;
+                    znorm = Math.sqrt(Math.pow(vcste1,2) +
+                                             Math.pow(vcste2,2) +
+                                             Math.pow(vcste3,2));
+                    x3 = vcses3/znorm;
+                    umv = Math.atan2(x3,Math.sqrt(rfact - Math.pow(x3,2))) - 
+                            roasin;
+                    xlin = piclin - umv/radlin;
+                    if (i == 0)
+                    {
+                        samtim = time2;
+                        oldlin = xlin;
+                    }
+                }
+                double scnnum = ( (double) (oldlin + xlin)/2.0 - 1.0)/numsen;
+                double scnfrc = (scnnum - scan1)/(scan2 - scan1);
+                xlin = oldlin + scnfrc*(xlin - oldlin);
+                samtim = time1 + tmpscl*(scnnum - scan1);
+                xyzsat = satvec(samtim);
+                xsat = xyzsat[0];
+                ysat = xyzsat[1];
+                zsat = xyzsat[2];
+                double cosa = x*xsat + y*ysat + z*zsat;
+                double ctst = 0.0001*r*xht + rsq;
+                if (cosa >= ctst) 
+                {
+                    double xsats1 = b11*xsat + b12*ysat + b13*zsat;
+                    double ysats2 = b21*xsat + b22*ysat + b23*zsat;
+                    ct = Math.cos(emega*samtim + xref);
+                    st = Math.sin(emega*samtim + xref);
+                    x = ct*x1 - st*y1;
+                    y = st*x1 + ct*y1;
+                    vcste1 = x - xsat;
+                    vcste2 = y - ysat;
+                    vcste3 = z - zsat;
+                    vcses1 = b11*vcste1 + b12*vcste2 + b13*vcste3;
+                    vcses2 = b21*vcste1 + b22*vcste2 + b23*vcste3;
+                    vcses3 = b31*vcste1 + b32*vcste2 + b33*vcste3;
+                    xnorm = Math.sqrt(Math.pow(znorm,2) - Math.pow(vcses3,2));
+                    ynorm = Math.sqrt(Math.pow(xsats1,2) + Math.pow(ysats2,2));
+                    znorm = Math.sqrt(Math.pow(vcste1,2) + 
+                                      Math.pow(vcste2,2) + 
+                                      Math.pow(vcste3,2));
+                    x3 = vcses3/znorm;
+                    umv = Math.atan2(x3,Math.sqrt(rfact - Math.pow(x3,2))) - 
+                            roasin;
+                    double slin = Math.sin(umv);
+                    double clin = Math.cos(umv);
+                    double u = rotm11*clin + rotm13*slin;
+                    double v = rotm21*clin + rotm23*slin;
+                    xele = picele + Math.asin(
+                          (xsats1*vcses2 - ysats2*vcses1)/(xnorm*ynorm))/radele;
+                    xele = xele + Math.atan2(v,u)/radele;
+                    xele = xele-gamma-gamdot*samtim;
+                }
+            }
+            linele[indexLine][point] = (float)xlin;
+            linele[indexEle][point]  = (float)xele;
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+
+    public double[][] toLinEle(double[][] latlon) 
+    {
+
+        double xpar;
+        double ypar;
+        double zpar;
+        double xlin;
+        double xele;
+        double xdum;
+        double x1;
+        double y1;
+        double samtim;
+        double ct;
+        double st;
+        double x;
+        double y;
+        double z;
+        double xht;
+        double parlin;
+        double vcste1;
+        double vcste2;
+        double vcste3;
+        double vcses1;
+        double vcses2;
+        double vcses3;
+        double oldlin;
+        double orbtim;
+        double xsat;
+        double ysat;
+        double zsat;
+        double xnorm;
+        double ynorm;
+        double znorm;
+        double umv;
+        double x3;
+        double xyzsat[] = new double[3];
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xpar = latlon[indexLat][point];
+            // expects positive West Longitude.
+            ypar = isEastPositive 
+                     ? -latlon[indexLon][point]
+                     :  latlon[indexLon][point];
+
+            xlin = Double.NaN;
+            xele = Double.NaN;
+
+            if (Math.abs(xpar) <= 90.)
+            {
+
+                // initialize some variables
+                oldlin = 910.; 
+                orbtim = -99999.; 
+                xsat = ysat = zsat = 0.0;
+                x = y = z = 0.0;
+                xht = znorm = 0.0;
+                double xyz[] = nllxyz(xpar, ypar);
+                x1 = xyz[0];
+                y1 = xyz[1];
+                z = xyz[2];
+                xdum = 0.0;
+                samtim = time1;
+
+                for (int i = 0; i < 2; i++)
+                {
+                    if (Math.abs(samtim - orbtim) >= 0.0005)
+                    {
+                        xyzsat = satvec(samtim);
+                        xsat = xyzsat[0];
+                        ysat = xyzsat[1];
+                        zsat = xyzsat[2];
+                        orbtim = samtim;
+                        xht = Math.sqrt(Math.pow(xyzsat[0],2) +
+                                        Math.pow(xyzsat[1],2) +
+                                        Math.pow(xyzsat[2],2));
+                    }
+                    ct = Math.cos(emega*samtim + xref);
+                    st = Math.sin(emega*samtim + xref);
+                    x = ct*x1 - st*y1;
+                    y = st*x1 + ct*y1;
+                    vcste1 = x - xsat;
+                    vcste2 = y - ysat;
+                    vcste3 = z - zsat;
+                    vcses3 = b31*vcste1 + b32*vcste2 + b33*vcste3;
+                    znorm = Math.sqrt(Math.pow(vcste1,2) +
+                                             Math.pow(vcste2,2) +
+                                             Math.pow(vcste3,2));
+                    x3 = vcses3/znorm;
+                    umv = Math.atan2(x3,Math.sqrt(rfact - Math.pow(x3,2))) - 
+                            roasin;
+                    xlin = piclin - umv/radlin;
+                    parlin = (double) (xlin - 1.0)/numsen;
+                    if (i == 0)
+                    {
+                        samtim = time2;
+                        oldlin = xlin;
+                    }
+                }
+                double scnnum = ( (double) (oldlin + xlin)/2.0 - 1.0)/numsen;
+                double scnfrc = (scnnum - scan1)/(scan2 - scan1);
+                xlin = oldlin + scnfrc*(xlin - oldlin);
+                samtim = time1 + tmpscl*(scnnum - scan1);
+                xyzsat = satvec(samtim);
+                xsat = xyzsat[0];
+                ysat = xyzsat[1];
+                zsat = xyzsat[2];
+                double cosa = x*xsat + y*ysat + z*zsat;
+                double ctst = 0.0001*r*xht + rsq;
+                if (cosa >= ctst) 
+                {
+                    double xsats1 = b11*xsat + b12*ysat + b13*zsat;
+                    double ysats2 = b21*xsat + b22*ysat + b23*zsat;
+                    ct = Math.cos(emega*samtim + xref);
+                    st = Math.sin(emega*samtim + xref);
+                    x = ct*x1 - st*y1;
+                    y = st*x1 + ct*y1;
+                    vcste1 = x - xsat;
+                    vcste2 = y - ysat;
+                    vcste3 = z - zsat;
+                    vcses1 = b11*vcste1 + b12*vcste2 + b13*vcste3;
+                    vcses2 = b21*vcste1 + b22*vcste2 + b23*vcste3;
+                    vcses3 = b31*vcste1 + b32*vcste2 + b33*vcste3;
+                    xnorm = Math.sqrt(Math.pow(znorm,2) - Math.pow(vcses3,2));
+                    ynorm = Math.sqrt(Math.pow(xsats1,2) + Math.pow(ysats2,2));
+                    znorm = Math.sqrt(Math.pow(vcste1,2) + 
+                                      Math.pow(vcste2,2) + 
+                                      Math.pow(vcste3,2));
+                    x3 = vcses3/znorm;
+                    umv = Math.atan2(x3,Math.sqrt(rfact - Math.pow(x3,2))) - 
+                            roasin;
+                    double slin = Math.sin(umv);
+                    double clin = Math.cos(umv);
+                    double u = rotm11*clin + rotm13*slin;
+                    double v = rotm21*clin + rotm23*slin;
+                    xele = picele + Math.asin(
+                          (xsats1*vcses2 - ysats2*vcses1)/(xnorm*ynorm))/radele;
+                    xele = xele + Math.atan2(v,u)/radele;
+                    xele = xele-gamma-gamdot*samtim;
+                }
+            }
+            linele[indexLine][point] = xlin;
+            linele[indexEle][point]  = xele;
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    private int icon1(int yymmdd)
+    {
+    /*
+    C $ FUNCTION ICON1 (YYMMDD)
+    C $ CONVERTS YYMMDD TO YEAR-DAY (YYDDD)
+    C $ YYMMDD = (I) INPUT  DATE TO BE CONVERTED
+    C $$ ICON1 = CONVERT, DATE
+    */
+
+        int num[] = {0,31,59,90,120,151,181,212,243,273,304,334};
+        int year  = (yymmdd/10000)%100;
+        int month = (yymmdd/100)%100;
+        int day   = yymmdd%100;
+        if (month < 0 || month > 12) month = 1;
+        int julday = day + num[month - 1];
+        if (year%4 == 0 && month > 2) julday = julday + 1;
+        return (1000*year + julday);
+    }
+
+    private void epoch(int ietimy, int ietimh, double semima,
+                       double oeccen, double xmeana)
+    {
+        double RDPDG = Math.PI/180.;
+        double RE = 6378.388;
+        double GRACON = 0.07436574;
+
+        double axmmc = GRACON*Math.pow(Math.sqrt(RE/semima),3);
+        double xmanom = RDPDG*xmeana;
+        double time = (xmanom-oeccen*Math.sin(xmanom))/(60.0*axmmc);
+        double time1 = McIDASUtil.mcPackedIntegerToDouble(ietimh);
+        time = time1 - time;
+        int iday = 0;
+        if (time > 48.0)
+        {
+            time = time - 48.0;
+            iday = 2;
+        }
+        else if (time > 24.0)
+        {
+            time = time - 24.0;
+            iday = 1;
+        }
+        else if (time < -24.0)
+        {
+            time = time + 48.0;
+            iday = -2;
+        }
+        else if (time < 0.0)
+        {
+            time = time + 24.0;
+            iday = -1;
+        }
+        this.ietimh = McIDASUtil.mcDoubleToPackedInteger(time);
+        if (iday != 0)
+        {
+
+            int jyear = (ietimy/1000)%100;
+            // add 1000 so year will not go under zero
+            jyear = jyear + 1000;
+            int jday = ietimy%1000;
+            jday = jday + iday;
+            if (jday < 1)
+            {
+                jyear = jyear - 1;
+                jday = leapyr(jyear) + jday;
+            }
+            else
+            {
+                int jtot = leapyr(jyear);
+                if (jday > jtot)
+                {
+                    jyear = jyear + 1;
+                    jday = jday + jtot;
+                }
+            }
+            jyear = jyear%100;
+            this.ietimy = 1000*jyear + jday;
+        }  
+    } // End EPOCH
+
+    /* helper method for epoch */
+    private int leapyr(int iy)
+    {
+        return 366-( (iy%4) + 3)/4;
+    }
+
+    /*
+      SUBROUTINE NLLXYZ(XLAT,XLON,X,Y,Z)
+      CONVERT LAT,LON TO EARTH CENTERED X,Y,Z
+      (DALY, 1978)
+      XLAT,XLON ARE IN DEGREES, WITH NORTH AND WEST POSITIVE
+      X,Y,Z ARE GIVEN IN KM. THEY ARE THE COORDINATES IN A RECTANGULAR
+         FRAME WITH ORIGIN AT THE EARTH CENTER, WHOSE POSITIVE
+         X-AXIS PIERCES THE EQUATOR AT LON 0 DEG, WHOSE POSITIVE Y-AXIS
+         PIERCES THE EQUATOR AT LON 90 DEG, AND WHOSE POSITIVE Z-AXIS
+         INTERSECTS THE NORTH POLE.
+    */
+    private double[] nllxyz(double xlat, double xlon)
+    {
+
+        double ylat = rdpdg*xlat;
+        ylat = Math.atan2(bsq*Math.sin(ylat), asq*Math.cos(ylat));
+        double ylon = -rdpdg*xlon;
+        double snlt = Math.sin(ylat);
+        double cslt = Math.cos(ylat);
+        double csln = Math.cos(ylon);
+        double snln = Math.sin(ylon);
+        double tnlt = Math.pow((snlt/cslt),2);
+        double r = ab*Math.sqrt((1.0+tnlt)/(bsq+asq*tnlt));
+        double x = r*cslt*csln;
+        double y = r*cslt*snln;
+        double z = r*snlt;
+        return new double[] {x, y, z};
+    }
+
+
+    /*
+      SUBROUTINE NXYZLL(X,Y,Z,XLAT,XLON)
+      CONVERT EARTH-CENTERED X,Y,Z TO LAT & LON
+      X,Y,Z ARE GIVEN IN KM. THEY ARE THE COORDINATES IN A RECTANGULAR
+         COORDINATE SYSTEM WITH ORIGIN AT THE EARTH CENTER, WHOSE POS.
+         X-AXIS PIERCES THE EQUATOR AT LON 0 DEG, WHOSE POSITIVE Y-AXIS
+         PIERCES THE EQUATOR AT LON 90 DEG, AND WHOSE POSITIVE Z-AXIS
+         INTERSECTS THE NORTH POLE.
+      XLAT,XLON ARE IN DEGREES, WITH NORTH AND WEST POSITIVE
+    */
+    private double[] nxyzll(double x, double y, double z)
+    {
+        double xlat;
+        double xlon;
+
+        xlat = Double.NaN;
+        xlon = Double.NaN;
+        if (x != 0.0 && y != 0.0 && z != 0.0)
+        {
+            double a = Math.atan(z/Math.sqrt(x*x + y*y));
+            xlat = Math.atan2(asq*Math.sin(a), bsq*Math.cos(a))/rdpdg;
+            xlon = -Math.atan2(y,x)/rdpdg;
+        }
+        return new double[] {xlat, xlon};
+    }
+
+/*
+C SATVEC PHILLI 0880 NAVLIB  COMPUTES EARTH SATELLITE AS FUNCTION OF TIM
+C VECTOR EARTH-CENTER-TO-SAT (FUNC OF TIME)
+*/
+    private double[] satvec(double samtim)
+    {
+        if (iold != 1)
+        {
+            iold = 1;
+            double rdpdg = Math.PI/180.0;
+            double re = 6378.388;
+            double gracon = .07436574;
+            double sha = 100.26467;
+            sha = rdpdg*sha;
+            int irayd = 74001;
+            int irahms = 0;
+            double o = rdpdg*orbinc;
+            double p = rdpdg*perhel;
+            double a = rdpdg*asnode;
+            double so = Math.sin(o);
+            double co = Math.cos(o);
+            double sp = Math.sin(p)*semima;
+            double cp = Math.cos(p)*semima;
+            double sa = Math.sin(a);
+            double ca = Math.cos(a);
+            px = cp*ca - sp*sa*co;
+            py = cp*sa + sp*ca*co;
+            pz = sp*so;
+            qx = -sp*ca - cp*sa*co;
+            qy = -sp*sa + cp*ca*co;
+            qz = cp*so;
+            srome2 = Math.sqrt(1.0 - oeccen) * Math.sqrt(1.0 + oeccen);
+            xmmc = gracon*re*Math.sqrt(re/semima)/semima;
+            int iey = (ietimy/1000)%100;
+            int ied = ietimy%1000;
+            int iefac = (iey-1)/4 + 1;
+            double de = 365*(iey-1) + iefac + ied - 1;
+            double te = 
+                1440.0*de + 60.0*McIDASUtil.mcPackedIntegerToDouble(ietimh);
+            int iray = irayd/1000;
+            int irad = irayd%1000;
+            int irafac = (iray-1)/4 + 1;
+            double dra = 365*(iray-1) + irafac + irad -1;
+            double tra = 
+                1440.0*dra + 60.0*McIDASUtil.mcPackedIntegerToDouble(irahms);
+            int inavy = (navday/1000)%100;
+            int inavd = navday%1000;
+            int infac = (inavy-1)/4 + 1;
+            double dnav = 365*(inavy-1) + infac + inavd -1;
+            tdife = dnav*1440. - te;
+            double tdifra = dnav*1440. - tra;
+            epsiln = 1.0E-8;
+        }
+
+        double timsam = samtim*60.0;
+        double diftim = tdife + timsam;
+        double xmanom = xmmc*diftim;
+        double ecanm1 = xmanom;
+        double ecanom = 0;
+        int i = 0;
+        for (i = 0; i < 20; i++)
+        {
+            ecanom = xmanom + oeccen*Math.sin(ecanm1);
+            if (Math.abs(ecanom-ecanm1) < epsiln) break;
+            ecanm1 = ecanom;
+        }
+        double xomega = Math.cos(ecanom) - oeccen;
+        double yomega = srome2*Math.sin(ecanom);
+        double z = xomega*pz + yomega*qz;
+        double y = xomega*py + yomega*qy;
+        double x = xomega*px + yomega*qx;
+        return new double[] {x, y, z};
+    }
+    
+    /** Get the lat,lon of the subpoint if available
+    *
+    * @return double[2] {lat, lon}
+    *
+    */
+    
+    public double[] getSubpoint() {
+    	double samtim;
+    	double[] xyzsat;
+    	double ct;
+    	double st;
+    	double x;
+    	double y;
+    	double z;
+    	double x1;
+    	double y1;
+    	double ssp_lat;
+    	double ssp_lon;
+    	
+    	samtim = time1;
+        xyzsat = satvec(samtim);
+        
+        ct = Math.cos(emega*samtim+xref);
+        st = Math.sin(emega*samtim+xref);
+        x = xyzsat[0];
+        y = xyzsat[1];
+        z = xyzsat[2];
+        x1 = ct*x + st*y;
+        y1 = -st*x + ct*y;
+        
+        double ll[] = nxyzll(x1, y1, z);
+        
+        ssp_lat = ll[0];
+        ssp_lon = (isEastPositive) ? -ll[1] : ll[1];
+
+        return new double[] {ssp_lat, ssp_lon};
+      }
+}
diff --git a/edu/wisc/ssec/mcidas/GRIDnav.java b/edu/wisc/ssec/mcidas/GRIDnav.java
new file mode 100644
index 0000000..f5520ef
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/GRIDnav.java
@@ -0,0 +1,460 @@
+//
+// GRIDnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * GRIDnav is the class for handling the navigation of McIDAS grids.
+ * It is basically a Java version of GRDDEF.FOR.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author Don Murray
+ * 
+ */
+public class GRIDnav
+    implements java.io.Serializable
+{
+
+  static final long serialVersionUID = 8741895066356394200L;
+
+  /** Navigation type for pseudo-mercator grids */
+  final int PSEUDO_MERCATOR = 1;
+  /** Navigation type for polar stero or lambert conformal conic grids */
+  final int PS_OR_LAMBERT_CONIC = 2;
+  /** Navigation type for equidistant grids */
+  final int EQUIDISTANT = 3;
+  /** Navigation type for pseudo-mercator (general case) grids */
+  final int PSEUDO_MERCATOR_GENERAL = 4;
+  final int NO_NAV = 5;
+  /** Navigation type for lambert conformal tangent grids */
+  final int LAMBERT_CONFORMAL_TANGENT = 6;
+  final double EARTH_RADIUS = 6371.23;
+  final double xrad = Math.PI/180.;
+  /** "Row" index in row/column array */
+  public final int indexRow=1;
+  /** "Column" index in row/column array */
+  public final int indexCol=0;
+  /** "Latitude" index in latitude/longitude array */
+  public final int indexLat=0;
+  /** "Longitude" index in latitude/longitude array */
+  public final int indexLon=1;
+
+  /* Default start for grid calculations in McIDAS is (1,1) */
+  private int startRow = 1;
+  private int startColumn = 1;
+
+  /* (1,1) is located in the upper left.  If lower left, it's flipped */
+  private boolean isRowFlipped = false;
+  private double rowOffset = 0.0;
+
+  /* common calculation variables */
+  private int navType;
+  private double xnr;             // number of rows
+  private double xnc;             // number of columns
+  private double xnrow;           // number of rows for calculations
+  private double xncol;           // number of columns for calculations
+  private boolean wierd = false;  // JTYP = 1, navType > 10
+
+  /* Merc and pseudo_merc parameters */
+  private double glamx;           // max latitude
+  private double glomx;           // max longitude
+  private double ginct;           // grid increment in latitude
+  private double gincn;           // grid increment in longitude
+
+  /* PS and CONF projection parameters */
+  private double xrowi;           // row # of North Pole*10000
+  private double xcoli;           // column # of North Pole* 10000
+  private double xqlon;           // longitude parallel to columns
+  private double xspace;          // column spacing at standard latitude
+  private double xh;              // 
+  private double xfac;            //
+  private double xblat;           //
+
+  /* Equidistant params */
+  private double xrot;            // rotation angle
+  private double yspace;
+  private double xblon; 
+
+  /**
+   *  Construct a new GRIDnav from a grid directory block
+   *  @param gridDirBlock  grid header block
+   *  @throws McIDASException  illegal grid header
+   */
+  public GRIDnav(int[] gridDirBlock)
+    throws McIDASException
+  {
+    if (gridDirBlock.length != GridDirectory.DIRSIZE)
+      throw new McIDASException("Directory is not the right size");
+    int gridType = gridDirBlock[GridDirectory.NAV_BLOCK_INDEX];
+    navType = gridType%10;
+    wierd = gridType/10 == 1;
+    xnr = gridDirBlock[GridDirectory.ROWS_INDEX];
+    xnc = gridDirBlock[GridDirectory.COLS_INDEX];
+    xnrow = xnr;
+    xncol = xnc;
+    switch(navType)
+    {
+      case PSEUDO_MERCATOR:
+      case PSEUDO_MERCATOR_GENERAL:
+        glamx=gridDirBlock[34]/10000.;
+        glomx=gridDirBlock[35]/10000.;
+        ginct=gridDirBlock[38]/10000.;
+        gincn= 
+          (navType == PSEUDO_MERCATOR_GENERAL) 
+             ? gridDirBlock[39]/10000. : ginct;
+        if (wierd) {
+          double x = xnr;
+          xnr = xnc;
+          xnc = x;
+        }
+        break;
+      case PS_OR_LAMBERT_CONIC:
+        xrowi  = gridDirBlock[34]/10000.;  // row # of the North pole*10000
+        xcoli  = gridDirBlock[35]/10000.;  // col # of the North pole*10000
+        xspace = gridDirBlock[36]/1000.;   // column spacing at standard lat (m)
+        xqlon  = gridDirBlock[37]/10000.;  // lon parallel to cols (deg*10000)
+        double xt1 = gridDirBlock[38]/10000.;  // first standard lat
+        double xt2 = gridDirBlock[39]/10000.;  // second standard lat
+        xh = (xt1 >= 0) ? 1. : -1.;
+        xt1 =(90.-xh*xt1)*xrad;
+        xt2 =(90.-xh*xt2)*xrad;
+        xfac =1.0;
+        if (xt1 != xt2) 
+           xfac = (Math.log(Math.sin(xt1))-Math.log(Math.sin(xt2)))/
+                  (Math.log(Math.tan(.5*xt1))-Math.log(Math.tan(.5*xt2)));
+        xfac = 1.0/xfac;
+        xblat = 6370. * Math.sin(xt1)/
+                 (xspace*xfac*(Math.pow(Math.tan(xt1*.5),xfac)));
+        if (wierd) {
+           double x=xnr;
+           xnr=xnc;
+           xnc=x;
+           x=xcoli;
+           xcoli=xrowi;
+           xrowi=xnr-x+1.0;
+           xqlon=xqlon+90.;
+        }
+
+        break;
+      case EQUIDISTANT:
+        xrowi = 1.;
+        xcoli = 1.;
+        glamx = gridDirBlock[34]/10000.;       // lat of (1,1) degrees*10000
+        glomx = gridDirBlock[35]/10000.;       // lon of (1,1) degrees*10000
+        xrot  = -xrad*gridDirBlock[36]/10000.; // clockwise rotation of col 1
+        xspace = gridDirBlock[37]/1000.;       // column spacing
+        yspace = gridDirBlock[38]/1000.;       // row spacing
+        xblat = EARTH_RADIUS*xrad/yspace;
+        xblon = EARTH_RADIUS*xrad/xspace;
+
+        if (wierd) {
+          double x = xnr;
+          xnr = xnc;
+          xnc = x;
+        }
+
+        break;
+      case LAMBERT_CONFORMAL_TANGENT:
+        xrowi  = gridDirBlock[34]/10000.;  // row # of the North pole*10000
+        xcoli  = gridDirBlock[35]/10000.;  // col # of the North pole*10000
+        xspace = gridDirBlock[36]/1000.;   // column spacing at standard lat (m)
+        xqlon  = gridDirBlock[37]/10000.;  // lon parallel to cols (deg*10000)
+        double xtl = gridDirBlock[38]/10000.; // standard lat
+        xh = (xtl >= 0) ? 1. : -1.;
+        xtl = (90. - xh * xtl) * xrad;
+        xfac = Math.cos(xtl);
+        xblat = EARTH_RADIUS * Math.tan(xtl) / 
+                   (xspace * Math.pow(Math.tan(xtl*.5), xfac));
+
+        break;
+      default:  
+        break;
+    }
+  }
+
+  /** 
+   * converts from grid coordinates (x,y) or (col, row) to latitude/longitude
+   *
+   * @param  rowcol		  array of row/col pairs.  Where 
+   *                     rowcol[indexRow][] is a row and 
+   *                     rowcol[indexCol][] is a column. 
+   *
+   * @return latlon[][]  array of lat/long pairs. Output array is 
+   *                     latlon[indexLat][] of latitudes and 
+   *                     latlon[indexLon][] of longitudes.
+   *
+   */
+  public double[][] toLatLon(double[][] rowcol)
+  {
+    double[][] latlon = new double[2][rowcol[0].length];
+    double xlat = Double.NaN; // temp variable for lat
+    double xlon = Double.NaN; // temp variable for lon
+    double xrlon = 0.;
+    double xldif, xedif;
+    double radius;
+
+    for (int i = 0; i < rowcol[0].length; i++)
+    {
+      
+      // account for flipped coordinates
+      double xrow = isRowFlipped ? rowOffset - rowcol[indexRow][i] + 1
+                                 : rowcol[indexRow][i];
+      // adjust row/col based on startRow/startCol
+      xrow = xrow + (startRow - 1);
+      double xcol = rowcol[indexCol][i] - (startColumn - 1);
+
+      if (xrow > xnrow || xrow < 1.0 ||
+          xcol > xncol || xcol < 1.0) {
+        xlat = Double.NaN;
+        xlon = Double.NaN;
+      } else {
+        switch (navType)
+        {
+          case PSEUDO_MERCATOR:
+          case PSEUDO_MERCATOR_GENERAL:
+            if (wierd) {
+              double x = xrow;
+              xcol = xrow;
+              xrow = xnr - x + 1.0;
+            }
+            xlat = glamx-((xrow-1.0)*ginct);
+            xlon = glomx-((xcol-1.0)*gincn);
+            break;
+
+          case EQUIDISTANT:
+            break;
+
+          case PS_OR_LAMBERT_CONIC:
+          case LAMBERT_CONFORMAL_TANGENT:
+
+            xldif = xh * (xrow - xrowi) / xblat;
+            xedif =      (xcoli - xcol) / xblat;
+
+            xrlon = 0.;
+            if( !(xldif == 0 && xedif == 0)) xrlon = Math.atan2( xedif,xldif);
+      
+            xlon = xrlon / xfac / xrad + xqlon;
+            if(xlon > 180.) xlon = xlon - 360.;
+      
+            radius = Math.sqrt( xldif * xldif + xedif * xedif);
+            if( radius < 1.E-5 ) {
+               xlat = xh * 90.;
+            } else {
+               xlat = xh * (90. - 2. * Math.atan( 
+                          Math.exp( Math.log(radius) / xfac)) /xrad);
+            }
+            break;
+
+          default:
+            xlat=1.0-(xrow-1.0)/(xnr-1.0);
+            xlon=1.0-(xcol-1.0)/(xnc-1.0);
+            break;
+        }
+      }
+      latlon[indexLat][i] = xlat;
+      latlon[indexLon][i] = -xlon; // convert to east positive
+    }
+    return latlon;
+  }
+
+  /**
+   * toRowCol converts latitude/longitude to grid row/col
+   *
+   * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+   *                    are latitudes and latlon[indexLon][] are longitudes.
+   *
+   * @return rowcol[][] array of row/col pairs.  Where
+   *                    rowcol[indexRow][] is a row and rowcol[indexCol][]
+   *                    is an column.  These are in 'grid' coordinates
+   */
+  public double[][] toRowCol(double[][] latlon)
+  {
+    double[][] rowcol = new double[2][latlon[0].length];
+    double xrow, xcol, xlat, xlon;
+    double xrlon, xclat, xrlat;
+    double glomx1;
+    double xldif, xedif, xdis, xangl, xange;
+
+    for (int i = 0; i < latlon[0].length; i++)
+    {
+      xrow = Double.NaN;
+      xcol = Double.NaN;
+      xlat = latlon[indexLat][i];
+      xlon = -latlon[indexLon][i];  // convert to McIDAS (west pos)
+      switch(navType)
+      {
+        case PSEUDO_MERCATOR:
+        case PSEUDO_MERCATOR_GENERAL:
+          glomx1 = glomx;
+          if (glomx < 0 && glomx*xlon < 0)
+            glomx1 = glomx + 360;
+          xrow = (glamx-xlat)/ginct + 1.0;
+          xcol = (glomx1-xlon)/gincn + 1.0;
+          break;
+        case EQUIDISTANT:
+          xrlon = xlon-glomx;
+          xrlat = xlat-glamx;
+          xldif = xblat*xrlat;
+          xedif = xrlon*xblon*Math.cos(xlat*xrad);
+          xdis  = Math.sqrt(xldif*xldif+xedif*xedif);
+          if( xdis > .001) {
+             xangl = Math.atan2(xldif,xedif)-90.*xrad;
+             xange = Math.atan2(xldif,xedif)+90.*xrad;
+             xldif = xdis*Math.cos(-xrot+xangl);
+             xedif = xdis*Math.sin(-xrot+xange);
+          }
+          xrow = xrowi-xldif;
+          xcol = xcoli-xedif;
+
+          break;
+        case PS_OR_LAMBERT_CONIC:
+        case LAMBERT_CONFORMAL_TANGENT:
+
+          xrlon = xlon - xqlon;
+          if(xrlon > 180.) xrlon = xrlon - 360.;
+          xrlon = xrlon * xfac * xrad;
+     
+          xclat = (90. - xh * xlat) * xrad * .5;
+          xrlat = xblat * Math.pow(Math.tan(xclat), xfac);
+     
+          xrow = xh * xrlat * Math.cos(xrlon) + xrowi;
+          xcol = -xrlat * Math.sin(xrlon) + xcoli;
+      
+          break;
+        default:
+          xrow = (1.0 - xlat)*(xnr-1.0)+1;
+          xcol = (1.0 - xlon)*(xnc-1.0)+1;
+          break;
+      }
+
+      if (xrow > xnrow || xrow < 1.0 ||
+          xcol > xncol || xcol < 1.0) {
+
+        xrow = Double.NaN;
+        xcol = Double.NaN;
+
+      } else {
+     
+        // account for non (1,1) origin
+        xrow = xrow - (startRow - 1);
+        xcol = xcol + (startColumn - 1);
+        // account for flipped coordinates
+        if (isRowFlipped) xrow = rowOffset - xrow + 1;
+
+      }
+
+      rowcol[indexRow][i] = xrow;
+      rowcol[indexCol][i] = xcol;
+    }
+    return rowcol;
+  }
+
+
+  /**
+   * Determines whether or not the <code>Object</code> in question is
+   * the same as this <code>AREAnav</code>.   Right now, this returns
+   * false until we can figure out when two navigations are equal.  
+   * Subclasses could override if desired.
+   *
+   * @param obj the AREAnav in question
+   */
+  public boolean equals(Object obj)
+  {
+    if (! (obj instanceof GRIDnav)) return false;
+    GRIDnav that = (GRIDnav) obj;
+    return (Double.doubleToLongBits(this.xnr) == 
+              Double.doubleToLongBits(that.xnr) &&
+            Double.doubleToLongBits(this.xnc) == 
+              Double.doubleToLongBits(that.xnc) &&
+            this.navType == that.navType);
+  }
+
+  /** 
+   * define the starting row and column of another coordinate system -- 
+   * for example (0,0)
+   *
+   * @param  startRow      the starting row number in another 
+   *                       coordinate system
+   *
+   * @param  startColumn   the starting column number in another 
+   *                       coordinate system
+   *
+   */
+  public void setStart(int startRow, int startColumn) 
+  {
+      this.startRow = startRow;
+      this.startColumn = startColumn;
+  }
+    
+  /** 
+   * specify whether the row coordinates are inverted and the row
+   * offset.
+   *
+   * @param  row  ending row number
+   *
+   */
+  public void setFlipRowCoordinates(int row) 
+  {
+    isRowFlipped = true;
+    rowOffset = (double) row;
+  }
+
+  /**
+   * Determine if navigation is using flipped coordinates.  This would
+   * mean that the start of the coordinate system is in the lower left
+   * instead of the upper left.
+   *
+   * @return  true if using flipped row coordinates, otherwise false
+   */
+  public boolean isFlippedRowCoordinates()
+  {
+    return isRowFlipped;
+  }
+
+  /**
+   * Get the row offset for flipped coordinates
+   *
+   * @return  row offset
+   */
+  public double getRowOffset()
+  {
+    return rowOffset;
+  }
+
+  /**
+   * Get the row index
+   * @return the row index in the returned arrays
+   */
+  public int getRowIndex() { return indexRow; }
+
+  /**
+   * Get the column index
+   * @return the column index in the returned arrays
+   */
+  public int getColumnIndex() { return indexCol; }
+
+}
diff --git a/edu/wisc/ssec/mcidas/GVARnav.java b/edu/wisc/ssec/mcidas/GVARnav.java
new file mode 100644
index 0000000..6527f48
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/GVARnav.java
@@ -0,0 +1,1213 @@
+//
+// GVARnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * The GVARnav class creates the ability to navigate GVAR
+ * image data.  It is a math copy of the McIDAS nvxgvar.dlm
+ * code.
+ *
+ * When used with AreaFile class, set up like this:
+ *
+ * <pre><code>
+ *  AreaFile af;
+ *  try {
+ *    af = new AreaFile("/home/user/mcidas/data/AREA0001");
+ *  } catch (AreaFileException e) {
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] dir;
+ *  try { dir=af.getDir();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] nav;
+ *  try { nav=af.getNav();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  try { 
+ *    GVARnav ng = new GVARnav(nav);  // XXXXnav is the specific implementation
+ *  } catch (IllegalArgumentException excp) {
+ *    System.out.println(excp);
+ *    return;
+ *  }
+ *  ng.setImageStart(dir[5], dir[6]);
+ *  ng.setRes(dir[11], dir[12]);
+ *  ng.setStart(1,1);
+ *  ......................
+ * </code></pre>
+ *
+ * @author Tom Whittaker
+ * 
+ */
+public class GVARnav extends AREAnav
+{
+
+  private boolean isEastPositive = true;
+
+  final double DEG=180.d/Math.PI;
+  final double RAD=Math.PI/180.d; // degrees to radians conversion pi/180
+  final double NOMORB=42164.365d; // nominal radial distance of satellite (km)
+  final double AE=6378.137d; // earth equatorial radius (km)
+  final double FER=1.d-(6356.7533d/AE); // earth flattening coeff = 1-(be/ae)
+  final float AEBE2= (float) ( 1.d/Math.pow(1.d-FER,2d));
+  final float AEBE3= AEBE2-1.f;
+  final float AEBE4=(float) Math.pow(1.d-FER,4d)-1.f;
+
+  private double xs[] = new double[3]; 
+     // normalized s/c position in ecef coordinates
+  private double bt[][] = new double[3][3]; 
+     // ecef to instrument coordinates transformation
+  private double q3; // used in subrtn lpoint
+  private double pitch,roll,yaw; 
+     // pitch,roll,yaw angles of instrument (rad)
+  private float pma; // pitch misalignment of instrument (rad)
+  private float rma; // roll misalignment of instrument (rad)
+
+  private int incmax[] = {6136, 2805}; // number of increments per cycle
+
+  private float elvmax[] = {0.220896f, 0.22089375f}; 
+      // bounds in elevation (radians)
+  private float scnmax[] = {0.24544f,0.2454375f}; 
+      // bounds in scan angle (radians)
+  private float elvinc[] = {8.e-6f, 17.5e-6f}; 
+      // change in elevation angle per increment (rad)
+  private float scninc[] = {16.e-6f, 35.e-6f}; 
+      // change in scan angle per increment (radians)
+  private float elvln[] = {28.e-6f, 280.e-6f}; 
+      // elevation angle per detector line (radians)
+  private float scnpx[] = {16.e-6f, 280.e-6f}; 
+      // scan angle per pixel (radians)
+  private float nsnom[] = {.220896f, .22089375f};
+      // north-south center of instrument (=4.5 x incmax x elvinc
+
+  private float ewnom[] = {.24544f, .2454375f};
+      // east-west center of instrument (=2.5 x incmax x sncinc)
+
+  final int STTYPE = 0; // position of satellite type
+  final int IDNTFR = 1;
+  final int IMCACT = 2; // position of imc active flag
+  final int IYFLIP = 3; // position of yaw flip enabled flag
+  final int REFLON = 5; // position of reference longitude
+  final int REFDIS = 6; // position of reference distance from nominal
+  final int REFLAT = 7; // position of reference latitude
+  final int REFYAW = 8; // position of reference yaw
+  final int RATROL = 9; // position of reference attitude roll
+  final int RATPTC = 10; // position of reference attitude pitch
+  final int RATYAW = 11; // position of reference attitude yaw
+  final int ETIME  = 12; // position of epoch time
+  final int EDTIME = 14; // location of delta from epoch time
+  final int IMCROL = 15; // location of image motion compensation roll
+  final int IMCPTC = 16; // location of image motion compensation pitch
+  final int IMCYAW = 17; // location of image motion compensation yaw
+
+  // ldr1-13: location of longitude delta from reference parameters
+  final int LDR1   = 18; 
+  final int LDR2   = 19;
+  final int LDR3   = 20;
+  final int LDR4   = 21;
+  final int LDR5   = 22;
+  final int LDR6   = 23;
+  final int LDR7   = 24;
+  final int LDR8   = 25;
+  final int LDR9   = 26;
+  final int LDR10  = 27;
+  final int LDR11  = 28;
+  final int LDR12  = 29;
+  final int LDR13  = 30;
+
+  // rddr1-11: location of radial distance delta from reference parameters
+  final int RDDR1  = 31; 
+  final int RDDR2  = 32;
+  final int RDDR3  = 33;
+  final int RDDR4  = 34;
+  final int RDDR5  = 35;
+  final int RDDR6  = 36;
+  final int RDDR7  = 37;
+  final int RDDR8  = 38;
+  final int RDDR9  = 39;
+  final int RDDR10 = 40;
+  final int RDDR11 = 41;
+
+  // dgl1-9: location of geocentric latitude delta parameters
+  final int DGL1   = 42; 
+  final int DGL2   = 43;
+  final int DGL3   = 44;
+  final int DGL4   = 45;
+  final int DGL5   = 46;
+  final int DGL6   = 47;
+  final int DGL7   = 48;
+  final int DGL8   = 49;
+  final int DGL9   = 50;
+
+  // doy1-9: location of orbit yaw delta parameters
+  final int DOY1   = 51; 
+  final int DOY2   = 52;
+  final int DOY3   = 53;
+  final int DOY4   = 54;
+  final int DOY5   = 55;
+  final int DOY6   = 56;
+  final int DOY7   = 57;
+  final int DOY8   = 58;
+  final int DOY9   = 59;
+
+  final int EXPTIM = 61; // exponential start time from epoch
+  final int RAAWDS = 62; // location of start of roll attitude angle words
+  final int PAAWDS = 129; // location of start of pitch attitude angle words
+  final int YAAWDS = 184; // location of start of yaw attitude angle words
+  final int RMAWDS = 257; // location of start of roll misalignment angle words
+  final int PMAWDS = 312; // location of start of pitch misalignment angle words
+
+  final int IMGDAY = 367; // position of image day value  (yyddd)
+  final int IMGTM  = 368; // position of image time value (hhmmss)
+  final int IMGSND = 369; // location of imager/sounder instrument flag
+
+// the following four words were added 5-26-94 to comply w/ the new elug...
+// numbering started at 380 because these same parameters are used
+// in the nav message sent from the ingestor to evx, and we had to
+// start somewhere after the 378 nav parameters
+
+  final int IOFNC = 379;
+  final int IOFEC = 380;
+  final int IOFNI = 381;
+  final int IOFEI = 382;
+
+  final int MXCDSZ=5*128; // maximum directory block entry size
+
+  final int OASIZE = 336; // size of gvar orbit and attitude set
+  final int PCOEFS = 117; // start of pitch coefficients
+  final int RMACFS = 227; // start of rma coefficients
+  final int CUTOF1 = 115; // first dividing point in o&a set (128 word sets)
+  final int CUTOF2 = 225; // second dividing point in o&a set (128 word sets)
+
+  final int IMCFLG = 7; //  bit # in sscan for imc active flag
+  final int FLPFLG = 15; //  bit # in sscan for yaw flip enabled flat
+  private int iflip; // value of FLPFLG
+
+  // now some variables that are shared among the methods...
+
+  private double aec, ts, dr, lam, dlat, dyaw, phi;
+  private double aebe2c, aebe3c, aebe4c, ferc;
+  private int instr, itype;
+  private double sublat, sublon;
+  //private double rlat, rlon, gam, alf;
+  private double[] subpoint;
+
+  final int RELLST [][] = {  { 4,  10}, { 13,  63}, { 65,  94},
+                     { 98, 100}, {103, 105}, {108, 110}, {113, 115},
+                     {116, 118}, {120, 149}, {153, 155}, {158, 160},
+                     {163, 165}, {168, 170}, {171, 173}, {175, 204},
+                     {208, 210}, {213, 215}, {218, 220}, {223, 225},
+                     {226, 228}, {230, 259}, {263, 265}, {268, 270},
+                     {273, 275}, {278, 283}, {285, 314}, {318, 320},
+                     {323, 325}, {328, 330}, {333, 335}, { -1,  -1}};
+
+
+
+  /**
+   * Set up for the real math work.  Must pass in the int array
+   * of the GVAR nav 'codicil'.
+   *
+   * @param iparms the nav block from the image file
+   * @throws IllegalArgumentException
+   *           if the nav block is not a GVAR type.
+   */
+  public GVARnav (int[] iparms) 
+      throws IllegalArgumentException
+  {
+    this(1, iparms);
+  }
+
+  /**
+   * Set up for the real math work.  Must pass in the int array
+   * of the GVAR nav 'codicil'.
+   * @deprecated  Since ifunc must be 1, replaced with #GVARnav(int[] iparms).
+   *              If ifunc != 1, ifunc is set to 1.
+   *
+   * @param  ifunc   the function to do (always 1 for now)
+   * @param  iparms   the nav block from the image file
+   * @throws IllegalArgumentException
+   *           if the nav block is not a GVAR type.
+   */
+  public GVARnav (int ifunc, int[] iparms) 
+      throws IllegalArgumentException
+  {
+
+    double te, psi, u, sinu, cosu;
+    double sinoi, cosoi, slat, asc, sinasc, cosasc;
+    double syaw, w, sw, cw, s2w, c2w;
+    double sw1, cw1, sw3, cw3;
+    double  secs, wa;
+    double imgtim, epoch, timex, time50, r;
+    double[][] b = new double [3][3];
+    int lit,  year, day;
+    int iftok, hour, minuts;
+    int count, offset, loop;
+    int time[] = new int[2];
+    float rparms[] = new float[MXCDSZ];
+
+
+// rellst two dimensional array of location of float in o&a set
+
+// initialize the earth radius constants
+    aec    = AE;
+    ferc   = FER;
+    aebe2c = (double) AEBE2;
+    aebe3c = (double) AEBE3;
+    aebe4c = (double) AEBE4;
+
+    // Only type 1 supported
+    if (ifunc != 1) ifunc = 1;
+
+    // This is not used.  Left over from nvxgvar.dlm code for Cartesian
+    // transformations
+    if (ifunc != 1) {
+      if (iparms[0] == LL ) itype = 1;
+      if (iparms[0] == XY ) itype = 2;
+      return ;
+    }
+
+    if (iparms[STTYPE] != GVAR ) 
+        throw new IllegalArgumentException("Invalid navigation type" + 
+                                            iparms[STTYPE]);
+
+    itype = 1;
+
+    // copy codicil into a float array
+    for (loop = 0; loop<MXCDSZ; loop++) {
+      rparms[loop] = Float.intBitsToFloat(iparms[loop] );
+    }
+
+    count = 0;
+    rparms[IMGTM] = ( (float)iparms[IMGTM])/1000.f;
+    while (RELLST[count][0] != -1) {
+
+      offset = 1;
+      if (RELLST[count][0] > CUTOF1) offset = 13;
+      if (RELLST[count][0] > CUTOF2) offset = 31;
+
+      for (loop = RELLST[count][0]; loop <= RELLST[count][1]; loop++) {
+    if ( (loop == 13) || (loop == 60) || ( ((loop-7) % 55) == 0) 
+           && (loop != 7) ) {
+          rparms[loop+offset] = ( (float)iparms[loop+offset])/100.f;
+        } else {
+          rparms[loop+offset] = ( (float)iparms[loop+offset])/10000000.f;
+        }
+      }
+
+      count ++;
+    } // end while
+
+    //see if this codicil is for imager or sounder
+    instr = iparms[IMGSND];
+
+    // ----------------------------------------------------------
+    // new code from kath kelly for sounder nav - 10/27/??
+    // because change to elug -- nadir postion is avabile in
+    // signal and should be used to compute values instead
+    // of making them hardwired...
+     
+    int nadnsc, nadnsi, nadewc, nadewi;
+    nadnsc = iparms[IOFNC];
+    nadnsi = iparms[IOFNI];
+    nadewc = iparms[IOFEC];
+    nadewi = iparms[IOFEI];
+
+    if (nadnsc!=0 && nadnsi!=0 && nadewc!=0 && nadewi!=0) {
+      if (instr == 1) {
+        elvmax[0] = (nadnsc*incmax[0]+nadnsi)*elvinc[0];
+      } else {
+        elvmax[1]=( (9-nadnsc)*incmax[1]-nadnsi)*elvinc[1];
+      }
+
+      scnmax[instr-1] = (nadewc*incmax[instr-1]+nadewi)*scninc[instr-1];
+    }
+    // end of new code from kathy kelly
+    // ----------------------------------------------------------
+
+    // get contorl info from codicil
+
+    year    = 1900 + iparms[IMGDAY] / 1000;
+    day     = iparms[IMGDAY] - iparms[IMGDAY] / 1000 * 1000;
+    hour    = (int)rparms[IMGTM] / 10000;
+    minuts  = (int)rparms[IMGTM] / 100 - hour * 100;
+    secs    = rparms[IMGTM] - (float)100*minuts - (float)10000*hour;
+
+    // compute the actual time in minutes from Jan 1, 1950:
+
+    int j = day + 1461*(year+4799)/4 - 3 * ((year + 4899) / 100) / 4 - 2465022;
+    imgtim = (double)j * 1440.d + (double)hour*60.d + (double)minuts + (secs/60.d);
+
+    // convert the BCD to binary integer
+
+    int t0, t1, e0, e1, power10;
+    t0 = 0;
+    t1 = 0;
+    power10 = 1;
+    e0 = iparms[ETIME];
+    e1 = iparms[ETIME+1];
+    for (int i=0; i<8; i++) {
+      t0 = t0 + ( e0 & 0xf ) * power10;
+      t1 = t1 + ( e1 & 0xf ) * power10;
+      e0 = e0 >>> 4;
+      e1 = e1 >>> 4;
+      power10 = power10 * 10;
+    }
+
+    int iaa, iab, iac, nbc, def;
+    year = t0 / 10000;
+    day = (int) ((t0 - (year * 10000)) * 0.1);
+    iaa = t0 - (year * 10000);
+    iab = (iaa - (day * 10)) * 10;
+    nbc = t1 / 10000000;
+    iac = t1 - (nbc * 10000000);
+    def = t1 - iac;
+    hour = iab + nbc;
+    minuts = (int) (iac * 0.00001);
+    double s = (t1 - (def + (minuts * 100000))) * 0.001;
+    j = day + 1461*(year+4799)/4 - 3 * ((year + 4899) / 100) / 4 - 2465022;
+    epoch = (double)j * 1440.d + (double)hour*60.d + (double)minuts + (s/60.d);
+
+    int imc = 1;
+    if ( (iparms[IMCACT] & (1 << IMCFLG)) != 0 ) imc = 0;
+    iflip = 1;
+    if ( (iparms[IYFLIP] & (1 << FLPFLG)) != 0) iflip = -1;
+
+    // assign reference values to the subsatellite longitude and
+    // latitude, the radial distance and the orbit yaw.
+    lam = rparms[REFLON];
+    dr  = rparms[REFDIS];
+    phi = rparms[REFLAT];
+    psi = rparms[REFYAW];
+
+    subpoint = new double[2];
+    subpoint[0] = rparms[REFLAT]/RAD;
+    subpoint[1] = rparms[REFLON]/RAD;
+
+
+    // assign reference values to the attitudes and misalignments
+    roll  = rparms[RATROL];
+    pitch = rparms[RATPTC];
+    yaw   = rparms[RATYAW];
+    rma   = 0.f;
+    pma   = 0.f;
+
+    // if imc is off, compute changes in the satellite orbit
+    if (imc != 0) { 
+
+    // set reference radial distance, latitude and orbit yaw to zero
+      dr  = 0.;
+      phi = 0.;
+      psi = 0.;
+
+    // compute time since epoch (in minutes)
+      ts = imgtim - epoch;
+
+    // computes orbit angle and the related trigonometric funktions.
+    // earth rotational rate=.729115e-4 (RAD/s)
+      w   = 0.729115e-4 * 60.0d * ts;
+      sw  = Math.sin(w);
+      cw  = Math.cos(w);
+      sw1 = Math.sin(0.927*w);
+      cw1 = Math.cos(0.927*w);
+      s2w = Math.sin(2.*w);
+      c2w = Math.cos(2.*w);
+      sw3 = Math.sin(1.9268*w);
+      cw3 = Math.cos(1.9268*w);
+
+    // computes change in the imc longitude from the reference
+      lam = lam + rparms[LDR1] + (rparms[LDR2] + rparms[LDR3]*w) * w
+            + (rparms[LDR10]*sw1 + rparms[LDR11]*cw1 + rparms[LDR4]*sw
+            + rparms[LDR5]*cw + rparms[LDR6]*s2w + rparms[LDR7]*c2w
+            + rparms[LDR8]*sw3+rparms[LDR9]*cw3 + w*(rparms[LDR12]*sw
+            + rparms[LDR13]*cw))*2.;
+
+    // computes change in radial distance from the reference (km)
+      dr = dr + rparms[RDDR1] + rparms[RDDR2]*cw + rparms[RDDR3]*sw
+              + rparms[RDDR4]*c2w + rparms[RDDR5]*s2w + rparms[RDDR6]
+              * cw3+rparms[RDDR7]*sw3 + rparms[RDDR8]*cw1
+              + rparms[RDDR9]*sw1 + w*(rparms[RDDR10]*cw
+              + rparms[RDDR11]*sw);
+
+    // computes the sine of the change in the geocentric latitude
+      dlat = rparms[DGL1] + rparms[DGL2]*cw + rparms[DGL3]*sw
+              + rparms[DGL4]*c2w + rparms[DGL5]*s2w + w*(rparms[DGL6]*cw
+              + rparms[DGL7]*sw) + rparms[DGL8]*cw1+rparms[DGL9]*sw1;
+
+    // computes geocentric latitude by using an expansion for arcsine
+      phi = phi + dlat * (1. + dlat * dlat / 6.);
+
+    // computes sine of the change in the orbit yaw
+      dyaw = rparms[DOY1] + rparms[DOY2]*sw + rparms[DOY3]*cw
+               + rparms[DOY4]*s2w + rparms[DOY5]*c2w
+               + w*(rparms[DOY6]*sw + rparms[DOY7]*cw)
+               + rparms[DOY8]*sw1 + rparms[DOY9]*cw1;
+
+    // computes the orbit yaw by using an expansion for arcsine.
+      psi = psi + dyaw * (1. + dyaw * dyaw / 6.);
+
+    }  // calculation of changes in the satellite orbit ends here
+
+
+    // conversion of the imc longitude and orbit yaw to the subsatellite
+    // longitude and the orbit inclination (ref: goes-pcc-tm-2473, inputs
+    // required for earth location and gridding by sps, june 6, 1988)
+    slat  = Math.sin(phi);
+    syaw  = Math.sin(psi);
+    sinoi = slat*slat + syaw*syaw;
+    cosoi = Math.sqrt(1.-sinoi);
+    sinoi = Math.sqrt(sinoi);
+
+    if (slat == 0.0d && syaw == 0.0d) {
+      u = 0.0d;
+    } else {
+      u = Math.atan2(slat,syaw);
+    }
+
+    sinu  = Math.sin(u);
+    cosu  = Math.cos(u);
+
+    // computes longitude of the ascending node
+    asc    = lam-u;
+    sinasc = Math.sin(asc);
+    cosasc = Math.cos(asc);
+
+    // computes the subsatellite geographic latitude
+    sublat = Math.atan(aebe2c * Math.tan(phi));
+
+    // computes the subsatellite longitude
+    sublon = asc + Math.atan2(cosoi*sinu,cosu);
+
+    // computes the spacecraft to earth fixed coordinates transformation
+    // matrix:
+    //     (vector in ecef coordinates) = b * (vector in s/c coordinates)
+
+    b[0][1] = -sinasc*sinoi;
+    b[1][1] =  cosasc*sinoi;
+    b[2][1] = -cosoi;
+    b[0][2] = -cosasc*cosu+sinasc*sinu*cosoi;
+    b[1][2] = -sinasc*cosu-cosasc*sinu*cosoi;
+    b[2][2] = -slat;
+    b[0][0] = -cosasc*sinu-sinasc*cosu*cosoi;
+    b[1][0] = -sinasc*sinu+cosasc*cosu*cosoi;
+    b[2][0] =  cosu*sinoi;
+
+    // computes the normalized spacecraft position vector in earth fixed
+    // coordinates - xs.
+    r     = (NOMORB+dr)/aec;
+    xs[0] = -b[0][2]*r;
+    xs[1] = -b[1][2]*r;
+    xs[2] = -b[2][2]*r;
+
+    // precomputes q3 (used in lpoint funciton (now in navToLatLon() )
+
+    q3 = xs[0]*xs[0] + xs[1]*xs[1] + aebe2c * xs[2]*xs[2] - 1.0;
+
+    // computes the attitudes and misalignments if imc is off
+    if (imc != 0) {
+
+    // computes the solar orbit angle
+      wa = rparms[61-1] * ts;
+
+    // computes the difference between current time, ts, and the
+    // exponential time, iparms(62). note that both times are since epoch.
+      te = ts - rparms[EXPTIM];
+
+    // computes roll + roll misalignment
+      roll = roll + gatt(RAAWDS,rparms,iparms,wa,te);
+
+    // computes pitch + pitch misalignment
+      pitch = pitch + gatt(PAAWDS,rparms,iparms,wa,te);
+
+    // computes yaw
+      yaw = yaw + gatt(YAAWDS,rparms,iparms,wa,te);
+
+    // computes roll misalignment
+      rma = (float) gatt(RMAWDS,rparms,iparms,wa,te);
+
+    // computes pitch misalignment
+      pma = (float) gatt(PMAWDS,rparms,iparms,wa,te);
+
+    // apply the earth sensor compensation if needed
+      roll   = roll + rparms[IMCROL];
+      pitch  = pitch + rparms[IMCPTC];
+      yaw    = yaw + rparms[IMCYAW];
+
+    }  // end if (imc...)
+
+    // computes the instrument to earth fixed coordinates transformation
+    // matrix - bt
+    inst2e(roll,pitch,yaw,b,bt);
+
+    return; 
+
+  }
+
+  private double gatt
+          (int k,float rparms[],int iparms[], double wa, double te) {
+
+    // k =  starting position of a parameter subset in the real o&a set
+    // rparms(mxcdxz) = input o&a parameter set
+    // iparms(mxcdxz) = input o&a parameter set
+    // doulble wa = input solar orbit angle in radians
+    // doulbe te = input exponential time delay from epoch (minutes)
+
+    // local variables
+
+    double ir, jr, mr, att;
+
+    // constant component
+    att = rparms[k+2];
+
+    //  computes the exponential term
+    if (te >= 0) {
+        att = att + rparms[k] * Math.exp(-te / rparms[k+1]);
+    }
+
+    // extracts the number of sinusoids
+    ir = (double) iparms[k+3];
+    int i  = (int) ir;
+
+    // calculation of sinusoids
+    for (int loop = 1; loop<=i; loop++) {
+      att =att + rparms[k+2*loop+2] * Math.cos(wa*(double)loop +
+                 rparms[k+2*loop+3]);
+    }
+
+    // pointer to the number of monomial sinusoids
+    k = k + 34;
+
+    // extacts number of monomial sinusoids
+    ir  = (double) iparms[k];
+    int kkk = iparms[k];
+    int ll;
+
+    // computes monomial sinusoids
+    for (int l=1; l<=kkk; l++) {
+        
+      ll = k + 5 * l;
+
+      // order of sinusoid
+      jr =  (double) iparms[ll-4];
+
+      // order of monomial sinusoid
+      mr  = (double) iparms[ll-3];
+      att = att + rparms[ll-2] * Math.pow((wa - rparms[ll]),mr) * 
+                  Math.cos(jr*wa+rparms[ll-1]);
+    }
+
+    return (att);
+  }
+
+
+
+
+  private void inst2e(double r, double p, double y,double[][] a, double[][]at) {
+
+// r =  roll angle in radians
+// p = pitch angle in radians
+// y = yaw angle in radians
+// a(3,3) = spacecraft to ecef coordinates transformation matrix
+// at(3,3)= instrument to ecef coordinates transformation matrix
+
+    double[][] rpy = new double[3][3];
+    int i, j;
+
+// we compute instrument to body coordinates transformation
+// matrix by using a small angle approximation of trigonometric
+// funktions of the roll, pitch and yaw.
+    rpy[0][0] = 1. - 0.5 * (p * p + y * y);
+    rpy[0][1] = -y;
+    rpy[0][2] = p;
+    rpy[1][0] = y + p * r;
+    rpy[1][1] = 1. - 0.5 * (y * y + r * r);
+    rpy[1][2] = -r;
+    rpy[2][0] = -p + r * y;
+    rpy[2][1] = r + p * y;
+    rpy[2][2] = 1. - 0.5 * (p * p + r * r);
+
+// multiplication of matrices a and rpy
+     
+    for (i=0; i<3; i++) {
+      for (j=0; j<3; j++) {
+        at[i][j] = a[i][0] * rpy[0][j] + a[i][1] * rpy[1][j] + 
+              a[i][2] * rpy[2][j];
+      }
+      }
+  return;
+
+  }
+
+
+
+  /** return the lat,lon of the subpoint
+  *
+  * @return double[2] {lat, lon}
+  *
+  */
+  
+  public double[] getSubpoint() {
+    return subpoint;
+  }
+
+
+  /** converts from satellite coordinates to latitude/longitude
+   *
+   * @param  linele		  array of line/element pairs.  Where 
+   *                     linele[indexLine][] is a 'line' and 
+   *                     linele[indexEle][] is an element. These are in 
+   *                     'file' coordinates (not "image" coordinates.)
+   *
+   * @return latlon[][]  array of lat/lon pairs. Output array is 
+   *                     latlon[indexLat][] of latitudes and 
+   *                     latlon[indexLon][] of longitudes.
+   *
+   */
+  public double[][] toLatLon(double[][] linele) { 
+
+    double rl, rp;
+    double rlat, rlon;
+    int number = linele[0].length;
+
+    // alpha = elevation angle (rad)
+    // zeta = scan angle (rad)
+    double q1, q2, d, h, alpha, zeta, alpha0, zeta0, ff, doff;
+    double[] g1 = new double[3];
+    double[] g = new double[3];
+    double[] u = new double[3];
+    double sa, ca, da, dz, d1, cz;
+    double[][] latlon = new double[2][number];
+
+    //  transform line/pixel to geographic coordinates:
+    double imglinele[][] = areaCoordToImageCoord(linele); 
+
+    for (int point=0; point<number; point++) {
+
+      //  set input line/pixel numbers
+      rl = imglinele[indexLine][point];
+      rp = imglinele[indexEle][point];
+
+      //  if doing sounder nav, have to trick routines into thinking image is
+      //  at res 1, because nav routines take sounder res into account
+      if (instr == 2) {
+        rl = (rl+9.)/10.;
+        rp = (rp+9.)/10.;
+      }
+       
+       //  compute elevation and scan angles (e,s) related to input
+       //  line and pixel numbers
+
+      if (instr == 1) {
+        alpha0 = elvmax[0] - (rl - 4.5) * elvln[0];
+      } else {
+        alpha0 = elvmax[1] - (rl - 2.5) * elvln[1];
+      }
+
+      zeta0 = (rp - 1.0) * scnpx[instr-1] - scnmax[instr - 1];
+
+      // compute sign of misalignment corrections and origin offset
+      ff = (double) iflip;
+      if (instr == 2) ff = -ff;
+      doff = scnmax[instr - 1] - ewnom[instr - 1];;
+
+
+      // add new second order origin offset correction
+      alpha = alpha0- alpha0 * zeta0 * doff;
+      zeta = zeta0+ 0.5f * alpha0 * alpha0 * doff;
+
+      //  transform elevation and scan angles to geographic coordinates
+      //  (this is the old 'lpoint' routine...
+
+     // computes trigonometric funktions of the scan and elevation
+     // angles corrected for the roll and pitch misalignments
+      ca = Math.cos(alpha);
+      sa = Math.sin(alpha);
+      cz = Math.cos(zeta);
+      da = alpha-pma*sa*(ff/cz+Math.tan(zeta))-rma*(1.0d-ca/cz);
+      dz = zeta + ff * rma * sa;
+
+     // corrected scan angle
+      cz = Math.cos(dz);
+
+     // computes pointing vector in instrument coordinates
+      g[0] = Math.sin(dz);
+      g[1] = -cz * Math.sin(da);
+      g[2] = cz * Math.cos(da);
+
+     // transforms the pointing vector to earth fixed coordinates
+      g1[0] = bt[0][0] * g[0] + bt[0][1] * g[1] + bt[0][2] * g[2];
+      g1[1] = bt[1][0] * g[0] + bt[1][1] * g[1] + bt[1][2] * g[2];
+      g1[2] = bt[2][0] * g[0] + bt[2][1] * g[1] + bt[2][2] * g[2];
+
+     // computes coefficients and solves a quadratic equation to
+     // find the intersect of the pointing vector with the earth
+     // surface
+      q1 = g1[0]*g1[0] + g1[1]*g1[1] + aebe2c * g1[2]*g1[2];
+      q2 = xs[0] * g1[0] + xs[1] * g1[1] + aebe2c * xs[2] * g1[2];
+      d  = q2 * q2 - q1 * q3;
+      if (Math.abs(d) < 1.d-9) {
+         d=0.;
+      }
+
+     // if the discriminant of the equation, d, is negative, the
+     // instrument points off the earth
+
+      if (d >= 0.0) {
+        d = Math.sqrt(d);
+
+       // slant distance from the satellite to the earth point
+        h = -(q2 + d) / q1;
+
+       // cartesian coordinates of the earth point
+        u[0] = xs[0] + h * g1[0];
+        u[1] = xs[1] + h * g1[1];
+        u[2] = xs[2] + h * g1[2];
+
+       // sinus of geocentric latitude
+        d1 = u[2] / Math.sqrt(u[0]*u[0] + u[1]*u[1] + u[2]*u[2]);
+
+       // geographic (geodetic) coordinates of the point
+        rlat = Math.atan(aebe2c * d1 / Math.sqrt(1. - d1 * d1));
+        rlon = Math.atan2(u[1],u[0]);
+      } else {
+        latlon[indexLat][point] = Double.NaN;
+        latlon[indexLon][point] = Double.NaN;
+        continue;
+      }
+
+      rlat = rlat * DEG;
+      rlon = rlon * DEG;
+
+       //  put longitude into mcidas form
+      if (!isEastPositive) rlon = -rlon;
+
+       //  see if we have to convert to x y z coordinates
+      if (itype == 2) {
+        // llcart(ylat,ylon,xlat,xlon,z);
+      } else {
+        latlon[indexLat][point] = rlat;
+        latlon[indexLon][point] = rlon;
+      }
+
+    } // end point for loop
+
+    return latlon;
+
+  }
+
+  /** converts from satellite coordinates to latitude/longitude
+   *
+   * @param  linele		  array of line/element pairs.  Where 
+   *                     linele[indexLine][] is a 'line' and 
+   *                     linele[indexEle][] is an element. These are in 
+   *                     'file' coordinates (not "image" coordinates.)
+   *
+   * @return latlon[][]  array of lat/lon pairs. Output array is 
+   *                     latlon[indexLat][] of latitudes and 
+   *                     latlon[indexLon][] of longitudes.
+   *
+   */
+  public float[][] toLatLon(float[][] linele) { 
+
+    double rl, rp;
+    double rlat, rlon;
+    int number = linele[0].length;
+
+    // alpha = elevation angle (rad)
+    // zeta = scan angle (rad)
+    double q1, q2, d, h, alpha, zeta, alpha0, zeta0, ff, doff;
+    double[] g1 = new double[3];
+    double[] g = new double[3];
+    double[] u = new double[3];
+    double sa, ca, da, dz, d1, cz;
+    float[][] latlon = new float[2][number];
+
+    //  transform line/pixel to geographic coordinates:
+    float imglinele[][] = areaCoordToImageCoord(linele); 
+
+    for (int point=0; point<number; point++) {
+
+      //  set input line/pixel numbers
+      rl = imglinele[indexLine][point];
+      rp = imglinele[indexEle][point];
+
+      //  if doing sounder nav, have to trick routines into thinking image is
+      //  at res 1, because nav routines take sounder res into account
+      if (instr == 2) {
+        rl = (rl+9.)/10.;
+        rp = (rp+9.)/10.;
+      }
+       
+       //  compute elevation and scan angles (e,s) related to input
+       //  line and pixel numbers
+
+      if (instr == 1) {
+        alpha0 = elvmax[0] - (rl - 4.5) * elvln[0];
+      } else {
+        alpha0 = elvmax[1] - (rl - 2.5) * elvln[1];
+      }
+
+      zeta0 = (rp - 1.0) * scnpx[instr-1] - scnmax[instr - 1];
+
+      // compute sign of misalignment corrections and origin offset
+      ff = (double) iflip;
+      if (instr == 2) ff = -ff;
+      doff = scnmax[instr - 1] - ewnom[instr - 1];;
+
+
+      // add new second order origin offset correction
+      alpha = alpha0- alpha0 * zeta0 * doff;
+      zeta = zeta0+ 0.5f * alpha0 * alpha0 * doff;
+
+      //  transform elevation and scan angles to geographic coordinates
+      //  (this is the old 'lpoint' routine...
+
+     // computes trigonometric funktions of the scan and elevation
+     // angles corrected for the roll and pitch misalignments
+      ca = Math.cos(alpha);
+      sa = Math.sin(alpha);
+      cz = Math.cos(zeta);
+      da = alpha-pma*sa*(ff/cz+Math.tan(zeta))-rma*(1.0d-ca/cz);
+      dz = zeta + ff * rma * sa;
+
+     // corrected scan angle
+      cz = Math.cos(dz);
+
+     // computes pointing vector in instrument coordinates
+      g[0] = Math.sin(dz);
+      g[1] = -cz * Math.sin(da);
+      g[2] = cz * Math.cos(da);
+
+     // transforms the pointing vector to earth fixed coordinates
+      g1[0] = bt[0][0] * g[0] + bt[0][1] * g[1] + bt[0][2] * g[2];
+      g1[1] = bt[1][0] * g[0] + bt[1][1] * g[1] + bt[1][2] * g[2];
+      g1[2] = bt[2][0] * g[0] + bt[2][1] * g[1] + bt[2][2] * g[2];
+
+     // computes coefficients and solves a quadratic equation to
+     // find the intersect of the pointing vector with the earth
+     // surface
+      q1 = g1[0]*g1[0] + g1[1]*g1[1] + aebe2c * g1[2]*g1[2];
+      q2 = xs[0] * g1[0] + xs[1] * g1[1] + aebe2c * xs[2] * g1[2];
+      d  = q2 * q2 - q1 * q3;
+      if (Math.abs(d) < 1.d-9) {
+         d=0.;
+      }
+
+     // if the discriminant of the equation, d, is negative, the
+     // instrument points off the earth
+
+      if (d >= 0.0) {
+        d = Math.sqrt(d);
+
+       // slant distance from the satellite to the earth point
+        h = -(q2 + d) / q1;
+
+       // cartesian coordinates of the earth point
+        u[0] = xs[0] + h * g1[0];
+        u[1] = xs[1] + h * g1[1];
+        u[2] = xs[2] + h * g1[2];
+
+       // sinus of geocentric latitude
+        d1 = u[2] / Math.sqrt(u[0]*u[0] + u[1]*u[1] + u[2]*u[2]);
+
+       // geographic (geodetic) coordinates of the point
+        rlat = Math.atan(aebe2c * d1 / Math.sqrt(1. - d1 * d1));
+        rlon = Math.atan2(u[1],u[0]);
+      } else {
+        latlon[indexLat][point] = Float.NaN;
+        latlon[indexLon][point] = Float.NaN;
+        continue;
+      }
+
+      rlat = rlat * DEG;
+      rlon = rlon * DEG;
+
+       //  put longitude into mcidas form
+      if (!isEastPositive) rlon = -rlon;
+
+       //  see if we have to convert to x y z coordinates
+      if (itype == 2) {
+        // llcart(ylat,ylon,xlat,xlon,z);
+      } else {
+        latlon[indexLat][point] = (float) rlat;
+        latlon[indexLon][point] = (float) rlon;
+      }
+
+    } // end point for loop
+
+    return latlon;
+
+  }
+
+
+  /**
+   * toLinEle converts lat/long to satellite line/element
+   *
+   * @param  latlon		  array of lat/long pairs. Where latlon[indexLat][]
+   *                     are latitudes and latlon[indexLon][] are longitudes.
+   *
+   * @return linele[][]  array of line/element pairs.  Where
+   *                     linele[indexLine][] is a line and linele[indexEle][] 
+   *                     is an element.  These are in 'file' coordinates
+   *                     (not "image" coordinates);
+   */
+  public double[][] toLinEle(double[][] latlon) {
+
+    double tmplin, tmpele;
+    double sing, slat, w1, w2, ff, doff, alpha1;
+    double rlat, rlon, gam, alf;
+    double [] f = new double[3];
+    double [] ft = new double[3];
+    double [] u = new double[3];
+    int number = latlon[0].length;
+    double[][] linele = new double[2][number];
+
+    ff = (double) iflip;
+    if (instr == 2) ff = -ff;
+    doff = scnmax[instr-1] - ewnom[instr - 1];
+
+    for (int point=0; point<number; point++) {
+
+      if (Math.abs(latlon[indexLat][point]) > 90.) {
+        linele[indexLine][point] = Double.NaN;
+        linele[indexEle][point] = Double.NaN;
+        continue;
+      }
+
+      rlat = (double)latlon[indexLat][point]*RAD;
+      rlon = (double)latlon[indexLon][point]*RAD;
+      if (!isEastPositive) rlon = -rlon;
+
+     // transform lat/lon to elevation and scan angles
+     // (used to be the gpoint routine...)
+
+     // computes sinus of geographic (geodetic) latitude
+      sing = Math.sin(rlat);
+      w1   = aebe4c * sing * sing;
+
+     // sinus of the geocentric latitude
+      slat = ((0.375 * w1 - 0.5) * w1 + 1.) * sing / aebe2c;
+
+     // computes local earth radius at specified point
+      w2 = slat * slat;
+      w1 = aebe3c * w2;
+      w1 = (0.375 * w1 - 0.5) * w1 + 1.;
+
+     // computes cartesian coordinates of the point
+      u[2] = slat * w1;
+      w2   = w1 * Math.sqrt(1. - w2);
+      u[0] = w2 * Math.cos(rlon);
+      u[1] = w2 * Math.sin(rlon);
+
+     // pointing vector from satellite to the earth point
+      f[0] = u[0] - xs[0];
+      f[1] = u[1] - xs[1];
+      f[2] = u[2] - xs[2];
+      w2 = u[0] * f[0] + u[1] * f[1] + u[2] * f[2] * aebe2c;
+
+     // verifies visibility of the point
+     if (w2 <= 0.0) {
+       // converts pointing vector to instrument coordinates
+        ft[0] = bt[0][0] * f[0] + bt[1][0] * f[1] + bt[2][0] * f[2];
+        ft[1] = bt[0][1] * f[0] + bt[1][1] * f[1] + bt[2][1] * f[2];
+        ft[2] = bt[0][2] * f[0] + bt[1][2] * f[1] + bt[2][2] * f[2];
+
+       // converts pointing vector to scan and elevation angles and
+       // corrects for the roll and pitch misalignments
+        gam  = Math.atan(ft[0] / Math.sqrt(ft[1]*ft[1] + ft[2]*ft[2] ) );
+        alf  = -Math.atan( ft[1] / ft[2] );
+        w1   = Math.sin(alf);
+        w2   = Math.cos(gam);
+        alpha1  = alf + rma * (1. - Math.cos(alf) / w2) + pma * w1 * 
+                         (doff / w2 + Math.tan(gam));
+        gam  = gam - ff * rma * w1;
+        alf = alpha1 + alpha1 * gam * doff;
+        gam = gam - 0.5f * alpha1 * alpha1 * doff;
+
+      } else {
+        // not visible...
+        linele[indexLine][point] = Double.NaN;
+        linele[indexEle][point] = Double.NaN;
+        continue;
+      }
+
+   // convert elevation and scan angles to line/pixel coordinates
+
+   // compute fractional line number
+
+      tmplin = (elvmax[instr-1] - alf) / elvln[instr-1];
+      if (instr == 1) {
+        tmplin = tmplin + 4.5;
+      } else {
+        tmplin = tmplin + 2.5;
+      }
+
+   // compute fractional pixel number
+      tmpele = (scnmax[instr-1] + gam) / scnpx[instr-1] + 1.;
+
+   // convert internal 8 byte values to 4 bytes
+      linele[indexLine][point] = tmplin;
+      linele[indexEle][point] = tmpele;
+
+   // if doing sounder nav, change lin & ele returned to res 10 values
+      if (instr == 2) {
+        linele[indexLine][point] = linele[indexLine][point]*10.f-9.f;
+        linele[indexEle][point] = linele[indexEle][point]*10.f-9.f;
+      }
+
+    } // end for loop on points
+
+    // Return in 'File' coordinates
+    return imageCoordToAreaCoord(linele, linele);
+  }
+
+  /**
+   * toLinEle converts lat/long to satellite line/element
+   *
+   * @param  latlon		  array of lat/long pairs. Where latlon[indexLat][]
+   *                     are latitudes and latlon[indexLon][] are longitudes.
+   *
+   * @return linele[][]  array of line/element pairs.  Where
+   *                     linele[indexLine][] is a line and linele[indexEle][] 
+   *                     is an element.  These are in 'file' coordinates
+   *                     (not "image" coordinates);
+   */
+  public float[][] toLinEle(float[][] latlon) {
+
+    double tmplin, tmpele;
+    double sing, slat, w1, w2, ff, doff, alpha1;
+    double rlat, rlon, gam, alf;
+    double [] f = new double[3];
+    double [] ft = new double[3];
+    double [] u = new double[3];
+    int number = latlon[0].length;
+    float[][] linele = new float[2][number];
+
+    ff = (double) iflip;
+    if (instr == 2) ff = -ff;
+    doff = scnmax[instr-1] - ewnom[instr - 1];
+
+    for (int point=0; point<number; point++) {
+
+      if (Math.abs(latlon[indexLat][point]) > 90.) {
+        linele[indexLine][point] = Float.NaN;
+        linele[indexEle][point] = Float.NaN;
+        continue;
+      }
+
+      rlat = (double)latlon[indexLat][point]*RAD;
+      rlon = (double)latlon[indexLon][point]*RAD;
+      if (!isEastPositive) rlon = -rlon;
+
+     // transform lat/lon to elevation and scan angles
+     // (used to be the gpoint routine...)
+
+     // computes sinus of geographic (geodetic) latitude
+      sing = Math.sin(rlat);
+      w1   = aebe4c * sing * sing;
+
+     // sinus of the geocentric latitude
+      slat = ((0.375 * w1 - 0.5) * w1 + 1.) * sing / aebe2c;
+
+     // computes local earth radius at specified point
+      w2 = slat * slat;
+      w1 = aebe3c * w2;
+      w1 = (0.375 * w1 - 0.5) * w1 + 1.;
+
+     // computes cartesian coordinates of the point
+      u[2] = slat * w1;
+      w2   = w1 * Math.sqrt(1. - w2);
+      u[0] = w2 * Math.cos(rlon);
+      u[1] = w2 * Math.sin(rlon);
+
+     // pointing vector from satellite to the earth point
+      f[0] = u[0] - xs[0];
+      f[1] = u[1] - xs[1];
+      f[2] = u[2] - xs[2];
+      w2 = u[0] * f[0] + u[1] * f[1] + u[2] * f[2] * aebe2c;
+
+     // verifies visibility of the point
+     if (w2 <= 0.0) {
+       // converts pointing vector to instrument coordinates
+        ft[0] = bt[0][0] * f[0] + bt[1][0] * f[1] + bt[2][0] * f[2];
+        ft[1] = bt[0][1] * f[0] + bt[1][1] * f[1] + bt[2][1] * f[2];
+        ft[2] = bt[0][2] * f[0] + bt[1][2] * f[1] + bt[2][2] * f[2];
+
+       // converts pointing vector to scan and elevation angles and
+       // corrects for the roll and pitch misalignments
+        gam  = Math.atan(ft[0] / Math.sqrt(ft[1]*ft[1] + ft[2]*ft[2] ) );
+        alf  = -Math.atan( ft[1] / ft[2] );
+        w1   = Math.sin(alf);
+        w2   = Math.cos(gam);
+        alpha1  = alf + rma * (1. - Math.cos(alf) / w2) + pma * w1 * 
+                         (doff / w2 + Math.tan(gam));
+        gam  = gam - ff * rma * w1;
+        alf = alpha1 + alpha1 * gam * doff;
+        gam = gam - 0.5f * alpha1 * alpha1 * doff;
+
+      } else {
+        // not visible...
+        linele[indexLine][point] = Float.NaN;
+        linele[indexEle][point] = Float.NaN;
+        continue;
+      }
+
+   // convert elevation and scan angles to line/pixel coordinates
+
+   // compute fractional line number
+
+      tmplin = (elvmax[instr-1] - alf) / elvln[instr-1];
+      if (instr == 1) {
+        tmplin = tmplin + 4.5;
+      } else {
+        tmplin = tmplin + 2.5;
+      }
+
+   // compute fractional pixel number
+      tmpele = (scnmax[instr-1] + gam) / scnpx[instr-1] + 1.;
+
+   // convert internal 8 byte values to 4 bytes
+      linele[indexLine][point] = (float) tmplin;
+      linele[indexEle][point] = (float) tmpele;
+
+   // if doing sounder nav, change lin & ele returned to res 10 values
+      if (instr == 2) {
+        linele[indexLine][point] = linele[indexLine][point]*10.f-9.f;
+        linele[indexEle][point] = linele[indexEle][point]*10.f-9.f;
+      }
+
+    } // end for loop on points
+
+    // Return in 'File' coordinates
+    return imageCoordToAreaCoord(linele, linele);
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/GridDirectory.java b/edu/wisc/ssec/mcidas/GridDirectory.java
new file mode 100644
index 0000000..6d79c13
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/GridDirectory.java
@@ -0,0 +1,395 @@
+//
+// GridDirectory.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.util.Date;
+
+/** 
+ * Class for modelling a McIDAS grid header.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author Don Murray
+ */
+public class GridDirectory
+{
+    /** GridDirectory block size */
+    public static final int DIRSIZE = 64;
+    /** Grid size (rows*columns) */
+    private static final int GRIDSIZE_INDEX = 0;
+    /**  number of rows */
+    public static final int ROWS_INDEX = 1;
+    /**  number of columns */
+    public static final int COLS_INDEX = 2;
+    /**  ref date */
+    public static final int REFDATE_INDEX = 3;
+    /**  ref time */
+    public static final int REFTIME_INDEX = 4;
+    /**  forecast time */
+    public static final int FTIME_INDEX = 5;
+    /**  param name */
+    public static final int PARAM_NAME_INDEX = 6;
+    /**  param scale */
+    public static final int PARAM_SCALE_INDEX = 7;
+    /**  param units */
+    public static final int PARAM_UNITS_INDEX = 8;
+    /**  level value */
+    public static final int LEVEL_VALUE_INDEX = 9;
+    /**  level scale */
+    public static final int LEVEL_SCALE_INDEX = 10;
+    /**  level unit */
+    public static final int LEVEL_UNITS_INDEX = 11;
+    /**  param type */
+    public static final int PARAM_TYPE_INDEX = 12;
+    /**  second forecast time (for time diff or average) */
+    public static final int SECOND_FTIME_INDEX = 13;
+    /**  second level value */
+    public static final int SECOND_LEVEL_VALUE_INDEX = 14;
+    /**  navigation index */
+    public static final int NAV_BLOCK_INDEX = 33;
+    /**  navigation length */
+    public static final int NAV_BLOCK_LENGTH = 8;
+    /**  grid description */
+    public static final int GRID_DESCR_INDEX = 52;
+    /**  grid description length*/
+    public static final int GRID_DESCR_LENGTH = 12;
+
+    private int[] dir = new int[GridDirectory.DIRSIZE];   // single directory
+    private String paramName;
+    private String gridDescription;
+    private String paramUnitName;
+    private int forecastHour;
+    private Date referenceTime;
+    private Date validTime;
+    private Date secondTime = null;
+    private double paramScale;
+    private double levelValue;
+    private String levelUnitName;
+    private double secondLevelValue;
+    private int rows;
+    private int columns;
+    private int paramType;
+    private int[] navBlock;
+    private GRIDnav gridNav = null;
+
+    /**
+     * Construct a GridDirectory from the grid directory block.
+     *
+     * @param  dirblock  directory block from the McIDAS grid
+     */
+    public GridDirectory(int[] dirblock)
+        throws McIDASException
+    {
+        if (dirblock.length != DIRSIZE)
+            throw new McIDASException("Directory is not the right size");
+        System.arraycopy(
+            dirblock, 0, dir, 0, DIRSIZE);
+
+        // March down the list of parameters
+        rows = dirblock[ROWS_INDEX];
+        columns = dirblock[COLS_INDEX];
+
+        // date and time values
+        int refDay = dirblock[REFDATE_INDEX];
+        int refHMS = dirblock[REFTIME_INDEX];
+        referenceTime = 
+            new Date(McIDASUtil.mcDayTimeToSecs(refDay, refHMS) * 1000);
+        forecastHour = dirblock[FTIME_INDEX];
+        validTime = 
+            new Date( (McIDASUtil.mcDayTimeToSecs(refDay,refHMS)+
+                                    (forecastHour * 3600)) * 1000 );
+        // parameter values
+        paramName = 
+            McIDASUtil.intBitsToString(dirblock[PARAM_NAME_INDEX]).trim();
+        paramScale = Math.pow(10., dirblock[PARAM_SCALE_INDEX]);
+        paramUnitName = 
+            McIDASUtil.intBitsToString(dirblock[PARAM_UNITS_INDEX]).trim();
+        paramType = dirblock[PARAM_TYPE_INDEX];
+
+        // level values
+        levelValue = 
+            dirblock[LEVEL_VALUE_INDEX] *
+                Math.pow(10., dirblock[LEVEL_SCALE_INDEX]); // level * scale
+        levelUnitName = 
+            McIDASUtil.intBitsToString(dirblock[LEVEL_UNITS_INDEX]).trim();
+        // Special case for character levels
+        if (Math.abs(levelValue) > 10000 && levelUnitName.equals(""))
+        {
+            levelUnitName = 
+                McIDASUtil.intBitsToString(dirblock[LEVEL_VALUE_INDEX]).trim();
+            levelValue = 999;
+        }
+        if (paramType == 4 || paramType == 8) // level difference or average
+        {
+            secondLevelValue =
+                dirblock[SECOND_LEVEL_VALUE_INDEX] *
+                    Math.pow(10., dirblock[LEVEL_SCALE_INDEX]); // 2nd * scale
+        }
+        if (paramType == 1 || paramType == 2) // time difference or average
+        {
+            secondTime = 
+                new Date( (McIDASUtil.mcDayTimeToSecs(refDay,refHMS)+
+                     ((dirblock[SECOND_FTIME_INDEX]/10000) * 3600)) * 1000 );
+                     // NB: second time is 10000*time in hours to make HHMMSS
+                     // so we need to divide it out
+        }
+
+        // make the nav block
+        navBlock = new int[NAV_BLOCK_LENGTH];
+        System.arraycopy(
+            dirblock, NAV_BLOCK_INDEX, navBlock, 0, NAV_BLOCK_LENGTH);
+
+        // get the grid description
+        int[] nameBits = new int[GRID_DESCR_LENGTH];
+        System.arraycopy(
+            dirblock, GRID_DESCR_INDEX, nameBits, 0, nameBits.length);
+        gridDescription = 
+            McIDASUtil.intBitsToString(nameBits).trim();
+    }
+
+    /**
+     * Get the raw directory block
+     * @return  array of the raw parameters in int form
+     * @deprecated  use getDirectoryBlock
+     */
+    public int[] getDirBlock()
+    {
+        return getDirectoryBlock();
+    }
+
+    /**
+     * Get the raw directory block
+     * @return  array of the raw parameters in int form
+     */
+    public int[] getDirectoryBlock()
+    {
+        return dir;
+    }
+
+    /**
+     * Get the name of the parameter
+     * @return parameter name
+     */
+    public String getParamName() {
+        return paramName;
+    }
+
+    /**
+     * Get the grid description
+     * @return grid description
+     */
+    public String getGridDescription() {
+        return gridDescription;
+    }
+
+    /**
+     * Get the scale of the parameter values
+     * @return parameter scale  (power of 10)
+     */
+    public double getParamScale() {
+        return paramScale;
+    }
+
+    /**
+     * Get the unit name of the parameter values
+     * @return unit name for this parameter
+     */
+    public String getParamUnitName() {
+        return paramUnitName;
+    }
+
+    /**
+     * Get the reference time for this parameter
+     * @return reference time
+     */
+    public Date getReferenceTime() {
+        return referenceTime;
+    }
+
+    /**
+     * Get the valid time for this parameter if it is a forecast
+     * @return valid time
+     */
+    public Date getValidTime() {
+        return validTime;
+    }
+
+    /**
+     * Get the forecast hour for this parameter if it is a forecast
+     * @return forecast hour
+     */
+    public int getForecastHour() {
+        return forecastHour;
+    }
+
+    /**
+     * Get the second time for this parameter if it is a time difference
+     * @return second time (null if only one time associated with this)
+     */
+    public Date getSecondTime() {
+        return secondTime;
+    }
+
+    /**
+     * Get the vertical level value.  
+     * @return level  By McIDAS conventions, 1013. == mean sea level,
+     *                0. == tropopause, 1001. == surface.  Otherwise,
+     *                value is what is returned.
+     */
+    public double getLevelValue() {
+        return levelValue;
+    }
+
+    /**
+     * Get the units of the vertical level.
+     * @return unit name of the level
+     */
+    public String getLevelUnitName() {
+        return levelUnitName;
+    }
+
+    /**
+     * Get the second vertical level value if one exists.  
+     * @return level  By McIDAS conventions, 1013. == mean sea level,
+     *                0. == tropopause, 1001. == surface.  Otherwise,
+     *                value is what is returned.
+     */
+    public double getSecondLevelValue() {
+        return secondLevelValue;
+    }
+
+    /**
+     * Get the number of rows in the grid
+     * @return  number of rows
+     */
+    public int getRows()
+    {
+        return rows;
+    }
+
+    /**
+     * Get the number of columns in the grid
+     * @return  number of columns
+     */
+    public int getColumns()
+    {
+        return columns;
+    }
+
+    /**
+     * Get the navigation parameters.
+     * @return  array of nav parameters.  The first value is the grid type
+     *          and subsequent values provide the parameters for that type.
+     * see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+     *      McIDAS Programmer's Manual</A> for a description
+     * @see #getNavType()
+     */
+    public int[] getNavBlock()
+    {
+        return navBlock;
+    }
+
+    /**
+     * Get the navigation 
+     * @return  GRIDnav for this grid  (may be null)
+     */
+    public GRIDnav getNavigation()
+    {
+      if (gridNav == null) {
+        // make the nav module
+        try {
+          gridNav = new GRIDnav(getDirectoryBlock());
+        } catch (McIDASException excp) {
+          gridNav = null;
+        }
+      }
+      return gridNav;
+    }
+
+    /**
+     * Get the navigation type.  Type is stored as first element of
+     * the nav block.
+     * @return  nav type
+     * <pre>
+     *          types are:
+     *          1 = pseudo-Mercator
+     *          2 = Polar Stereographic or Lambert Conformal
+     *          3 = Equidistant
+     *          4 = pseudo-Mercator (more general)
+     *          5 = no navigation
+     *          6 = Lambert Conformal Tangent Cone
+     * </pre>
+     * @see #getNavBlock()
+     */
+    public int getNavType()
+    {
+        return navBlock[0];
+    }
+
+    /**
+     * Check the equality of the object in question with this.
+     * @param o object in question
+     */
+    public boolean equals(Object o) {
+       if (!(o instanceof GridDirectory)) return false;
+       GridDirectory that = (GridDirectory) o;
+       return (this == that ||
+              java.util.Arrays.equals(
+                   getDirectoryBlock(), that.getDirectoryBlock()));
+    }
+
+    /**
+     * String representation of the GridDirectory
+     * @return  human readable string
+     */
+    public String toString()
+    {
+        StringBuffer buff = new StringBuffer();
+        buff.append("Grid Directory:");
+        buff.append("\n");
+        buff.append("\tParameter = ");
+        buff.append(paramName + " [" + paramUnitName + "] (");
+        buff.append(gridDescription +")");
+        buff.append("\n");
+        buff.append("\trefTime: ");
+        buff.append(referenceTime.toGMTString());
+        buff.append(" valid: "+validTime.toGMTString());
+        buff.append("\n");
+        buff.append("\tsecond Time: ");
+        buff.append( (secondTime != null) ? secondTime.toGMTString() : "none");
+        buff.append("\n");
+        buff.append("\tLevel: ");
+        buff.append(levelValue+" ["+levelUnitName+"] second: "+secondLevelValue);
+        buff.append("\n");
+        buff.append("\tNav Type: ");
+        buff.append(getNavType());
+        buff.append(" rows: " + rows);
+        buff.append(" cols: " + columns);
+        buff.append("\n");
+        return buff.toString();
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/GridDirectoryList.java b/edu/wisc/ssec/mcidas/GridDirectoryList.java
new file mode 100644
index 0000000..df935f6
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/GridDirectoryList.java
@@ -0,0 +1,266 @@
+//
+// GridDirectoryList.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import edu.wisc.ssec.mcidas.adde.AddeURLConnection;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+
+/** 
+ * GridDirectoryList interface for McIDAS 'grids'.
+ * Provides access to a list of one or more GridDirectoy objects.
+ *
+ * @author Don Murray
+ * 
+ */
+public class GridDirectoryList
+{
+
+  // load protocol for ADDE URLs
+  // See java.net.URL for explanation of URL handling
+  static 
+  {
+    try 
+    {
+      String handlers = System.getProperty("java.protocol.handler.pkgs");
+      String newProperty = null;
+      if (handlers == null)
+        newProperty = "edu.wisc.ssec.mcidas";
+      else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+        newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+      if (newProperty != null)  // was set above
+        System.setProperty("java.protocol.handler.pkgs", newProperty);
+    }
+    catch (Exception e)
+    {
+      System.out.println(
+        "Unable to set System Property: java.protocol.handler.pkgs"); 
+    }
+  }
+
+  private boolean flipwords = false;
+  private DataInputStream inputStream;  // input stream
+  private int status=-1;                 // read status
+  private AddeURLConnection urlc;           // URL connection
+  private ArrayList dirs;         // list of directories
+  private ArrayList fileHeaders;  // list of file headers
+  private int numDirs = 0;        // number of directories
+  private final int HEARTBEAT = 11223344;
+  
+  /**
+   * Creates an GridDirectory object that allows reading
+   * of McIDAS 'grids' from an ADDE server 
+   *
+   * @param gridSource    ADDE URL to read from as a String
+   *
+   * @exception McIDASException if file cannot be opened
+   *
+   */
+  public GridDirectoryList(String gridSource) 
+    throws McIDASException 
+  {
+    try 
+    { 
+      URL url = new URL(gridSource);
+      urlc = (AddeURLConnection) url.openConnection();
+      inputStream = 
+        new DataInputStream(
+          new BufferedInputStream(urlc.getInputStream()));
+    } 
+    catch (IOException e) 
+    {
+      throw new McIDASException("Error opening URL for grids:"+e);
+    }
+    readDirectory();
+  }
+   
+  /**
+   * creates an GridDirectory object that allows reading
+   * of the directory of McIDAS 'grid' files from a URL
+   *
+   * @param url - the URL to go after
+   *
+   * @exception McIDASException if file cannot be opened
+   *
+   */
+  public GridDirectoryList(URL url) 
+    throws McIDASException 
+  {
+    try 
+    { 
+      urlc = (AddeURLConnection) url.openConnection();
+      inputStream = 
+        new DataInputStream(
+          new BufferedInputStream(urlc.getInputStream()));
+    } 
+    catch (IOException e) 
+    {
+      throw new McIDASException("Error opening URL for grids:"+e);
+    }
+    readDirectory();
+  }
+  
+  /** 
+   * Read the directory information for a grid.
+   *
+   * @exception   McIDASException  if there is a problem reading 
+   *                   any portion of the metadata.
+   *
+   */
+  private void readDirectory() throws McIDASException 
+  {
+
+    if (urlc.getRequestType() != AddeURLConnection.GDIR)
+      throw new McIDASException("Request must be of GDIR type");
+    dirs = new ArrayList();
+    fileHeaders = new ArrayList();
+    /* See how many bytes there are to read.  
+       From the McIDAS Programmers Manual a request transmits back:
+        - 4-byte value containing the total number of bytes to transmit 
+        - 4-byte value of zero indicating a new file header is being sent; 
+          this information is repeated for each new file header to transfer 
+          and is sent after all grids from the previous file are transmitted 
+        - 256-byte grid file header; this information is repeated for each 
+          new grid file header to transmit and is sent after the grids from 
+          the previous file are transmitted 
+        - 4-byte value of zero indicating a new grid directory is being sent; 
+          this information is repeated before each grid directory in the file 
+          is sent 
+        - 256-byte grid header; this information is repeated for each grid 
+          in the file being sent 
+        - 4-byte value of 1 indicating no more grid directories in the file 
+     */
+    int numBytes = urlc.getInitialRecordSize();
+    if (numBytes == 0) {
+      throw new McIDASException("No datasets found");
+    }
+    int check;
+    byte[] header = new byte[256];
+    //  Check for heartbeat
+    try
+    {
+      while (numBytes == 4) 
+      {
+        check = inputStream.readInt();
+        if (check != HEARTBEAT) 
+          System.out.println("problem...not heartbeat = "+check);
+          numBytes = inputStream.readInt();
+      }
+      // now we get the file header
+      while ( (check = inputStream.readInt()) == 0)  // new header
+      {
+        inputStream.readFully(header,0,256);
+        String head = new String(header,0,32);
+        fileHeaders.add(head);
+
+        int check2;
+  
+        // now we get the grid directory
+        while( (check2 = inputStream.readInt()) == 0) 
+        {
+           int[] dir = new int[GridDirectory.DIRSIZE];
+           for (int i=0; i < AreaFile.AD_DIRSIZE; i++) 
+           {
+             dir[i] = inputStream.readInt();
+           }
+     
+     /*   Debug
+           for (int i = 0; i < GridDirectory.DIRSIZE; i++)
+             System.out.println("dir[" + i +"] = " + dir[i]);
+     */
+           GridDirectory gridDir = new GridDirectory(dir);
+     
+           System.out.println(gridDir);
+           dirs.add(gridDir);
+        }
+      }
+    }
+    catch (IOException e) 
+    {
+      status = -1;
+      throw new McIDASException("Error reading grid directory:" + e);
+    }
+    status = 1;
+    numDirs++;
+  }
+  
+  /** 
+   * returns the directory blocks for the requested grids.  
+   * see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+   *    McIDAS Programmer's Manual</A> for information on the parameters
+   *    for each value.
+   *
+   * @return a ArrayList of GridDirectorys 
+   *
+   * @exception AreaFileException if there was a problem
+   *      reading the directory
+   *
+   */
+  public ArrayList getDirs() 
+    throws McIDASException 
+  {
+    if (status <= 0 || dirs.size() <= 0) 
+    {
+      throw new McIDASException(
+        "Error reading directory information");
+    }
+    return dirs;
+  }
+
+  /**
+   * Prints out a formatted listing of the directory info
+   */
+  public String toString()
+  {
+    if (status <=0 || numDirs <= 0)
+    {
+      return new String("No directory information available");
+    }
+    StringBuffer sb = new StringBuffer();
+    for (int i = 0; i < dirs.size(); i++)
+    {
+      sb.append( ((GridDirectory) dirs.get(i)).toString());
+      sb.append("\n");
+    }
+    return sb.toString();
+  }
+
+  public static void main(String[] args)
+    throws Exception
+  {
+    if (args.length == 0)
+    {
+      System.out.println("Must supply a ADDE request to grids");
+      System.exit(1);
+    }
+    GridDirectoryList gdl = new GridDirectoryList(args[0]);
+    //System.out.println(gdl.toString());
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/KALPnav.java b/edu/wisc/ssec/mcidas/KALPnav.java
new file mode 100644
index 0000000..808fa93
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/KALPnav.java
@@ -0,0 +1,438 @@
+//
+// KALPnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * The KALPnav class creates the ability to navigate KALP
+ * image data.  It is a math copy of the McIDAS nvxgvar.dlm
+ * code.
+ *
+ * When used with AreaFile class, set up like this:
+ *
+ * <pre><code>
+ *  AreaFile af;
+ *  try {
+ *    af = new AreaFile("/home/user/mcidas/data/AREA0001");
+ *  } catch (AreaFileException e) {
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] dir;
+ *  try { dir=af.getDir();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  int[] nav;
+ *  try { nav=af.getNav();
+ *  } catch (AreaFileException e){
+ *    System.out.println(e);
+ *    return;
+ *  }
+ *  try { 
+ *    GVARnav ng = new GVARnav(nav);  // XXXXnav is the specific implementation
+ *  } catch (IllegalArgumentException excp) {
+ *    System.out.println(excp);
+ *    return;
+ *  }
+ *  ng.setImageStart(dir[5], dir[6]);
+ *  ng.setRes(dir[11], dir[12]);
+ *  ng.setMag(1,1);
+ *  ng.setStart(0,0);
+ *  ......................
+ * </code></pre>
+ *
+ * @author Tom Whittaker
+ * 
+ */
+public class KALPnav extends AREAnav {
+  private int ic;
+  private double h, a, rp, re, cdr, crd, lpsi2, deltax, deltay;
+  private double sublat, sublon, cenlin, cenele, altitude;
+
+  private boolean isEastPositive = true;
+
+  public KALPnav (int[] iparms) {
+     this(1, iparms);
+  }
+
+  public KALPnav (int ifunc, int[] iparms) {
+
+    if (ifunc != 1) ifunc = 1;
+
+    if (iparms[0] != KALP ) {
+        throw new IllegalArgumentException("Invalid navigation type" + iparms[0]);
+    }
+ 
+//        H=42150.766-6378.155
+    altitude = (double)iparms[11] / 10000.0;
+    h=altitude-6378.155;
+
+    re=6378.155;
+    a=1./297.;
+    rp=re/(1.+a);
+    cdr=Math.PI/180.;
+    crd=180./Math.PI;
+    lpsi2=1.0;
+
+//        DELTAX=18.03674/1408.
+//        DELTAY=18.03674/1408.
+    deltax = (double)iparms[12] / 1000000000.0;
+    deltax = deltax * crd;
+    deltay = deltax;
+
+    sublat=(double)iparms[10]/10000.;
+    sublon=(double)iparms[6]/10000.;
+
+//        The center line and element are in full res coords *10
+//        They are used in scan coords, so divide by 40.
+
+    cenlin = (double)iparms[13]/40.;
+    cenele = (double)iparms[14]/40.;
+    if(iparms[13] == 0) {
+       cenlin = 704.5;
+       cenele = 704.5;
+    }
+  }
+
+ 
+  public float[][] toLatLon(float[][] linele) { 
+
+    double xele2, xlin2, x, y, xr, yr, rs, tanx, tany, val1, val2, yk;
+    double vmu, cosrf, sinrf, xt, yt, zt, xfi, xla, teta;
+
+    int number = linele[0].length;
+    float[][] latlon = new float[2][number];
+    float[][] imglinele = areaCoordToImageCoord(linele);
+
+    for (int point=0; point<number; point++) {
+
+      xele2 = imglinele[indexEle][point]/4.0;
+      xlin2 = imglinele[indexLine][point]/4.0;
+      x = cenele - xele2;
+      y = cenlin - xlin2;
+      xr = x;
+      yr = y;
+
+      x=xr*lpsi2*deltax*cdr;
+      y=yr*lpsi2*deltay*cdr;
+      rs=re+h;
+      tanx=Math.tan(x);
+      tany=Math.tan(y);
+      val1=1.+tanx*tanx;
+      val2=1.+(tany*tany)*((1.+a)*(1.+a));
+      yk=rs/re;
+
+      if((val1*val2) > ((yk*yk)/(yk*yk-1.0))) {
+        latlon[indexLine][point] = Float.NaN;
+        latlon[indexEle][point] = Float.NaN;
+        continue;
+      }
+
+      vmu=(rs-(re*(Math.sqrt((yk*yk)-(yk*yk-1)*val1*val2))))/(val1*val2);
+      cosrf=Math.cos(sublat*cdr);
+      sinrf=Math.sin(sublat*cdr);
+      xt=(rs*cosrf)+(vmu*(tanx*sinrf-cosrf));
+      yt=(rs*sinrf)-(vmu*(tanx*cosrf+sinrf));
+      zt=vmu*tany/Math.cos(x);
+      teta=Math.asin(zt/rp);
+      xfi=(Math.atan(((Math.tan(teta))*re)/rp))*crd;
+      xla=-Math.atan(yt/xt)*crd;
+//--- CHANGE LONGITUDE FOR CORRECT SUBPOINT
+      xla=xla+sublon;
+      if (isEastPositive) xla = -xla;
+      latlon[indexLat][point] = (float) xfi;
+      latlon[indexLon][point] = (float) xla;
+    }
+    return latlon;
+  }
+
+  public float[][] toLinEle(float [][] latlon) {
+
+
+    double x1, y1, xfi, xla, rom, y, r1, r2, rs, reph, rpph;
+    double coslo, sinlo, teta, xt, yt, zt, px, py, xr, yr ;
+
+    int number = latlon[0].length;
+    float[][] linele = new float[2][number];
+
+    for (int point=0; point<number; point++) {
+
+      x1 = (double) latlon[indexLat][point];
+      y1 = (double) latlon[indexLon][point];
+      if (!isEastPositive) y1 = -y1;
+
+//--- CORRECT FOR SUBLON
+      y1=y1+sublon;
+      xfi=x1*cdr;
+      xla=y1*cdr;
+      rom=(re*rp)/Math.sqrt(rp*rp*Math.cos(xfi) *
+            Math.cos(xfi)+re*re*Math.sin(xfi)*Math.sin(xfi));
+      y=Math.sqrt(h*h+rom*rom-2*h*rom*Math.cos(xfi)*Math.cos(xla));
+      r1=y*y+rom*rom;
+      r2=h*h;
+      if (r1 > r2) {
+        linele[indexLine][point] = Float.NaN;
+        linele[indexEle][point] = Float.NaN;
+
+      } else {
+        rs=re+h;
+        reph=re;
+        rpph=rp;
+        coslo=Math.cos(sublat*cdr);
+        sinlo=Math.sin(sublat*cdr);
+        teta=Math.atan((rpph/reph)*Math.tan(xfi));
+        xt=reph*Math.cos(teta)*Math.cos(xla);
+        yt=reph*Math.cos(teta)*Math.sin(xla);
+        zt=rpph*Math.sin(teta);
+        px=Math.atan((coslo*(yt-rs*sinlo)-(xt-rs*coslo)*sinlo)/
+         (sinlo*(yt-rs*sinlo)+(xt-rs*coslo)*coslo));
+        py=Math.atan(zt*((Math.tan(px)*sinlo-coslo)/(xt-rs*coslo))*Math.cos(px));
+        px=px*crd;
+        py=py*crd;
+        xr=px/(deltax*lpsi2);
+        yr=py/(deltay*lpsi2);
+        xr=cenele-xr;
+        yr=cenlin-yr;
+        xr=xr*4.0;
+        yr=yr*4.0;
+        linele[indexLine][point] = (float)yr;
+        linele[indexEle][point] = (float)xr;
+      }
+    }
+    return imageCoordToAreaCoord(linele, linele);
+  }
+
+
+  public double[][] toLinEle(double [][] latlon) {
+
+
+    double x1, y1, xfi, xla, rom, y, r1, r2, rs, reph, rpph;
+    double coslo, sinlo, teta, xt, yt, zt, px, py, xr, yr;
+
+    int number = latlon[0].length;
+    double[][] linele = new double[2][number];
+
+    for (int point=0; point<number; point++) {
+
+      x1 = latlon[indexLat][point];
+      y1 = latlon[indexLon][point];
+      if (!isEastPositive) y1 = -y1;
+
+//--- CORRECT FOR SUBLON
+      y1=y1+sublon;
+      xfi=x1*cdr;
+      xla=y1*cdr;
+      rom=(re*rp)/Math.sqrt(rp*rp*Math.cos(xfi) *
+            Math.cos(xfi)+re*re*Math.sin(xfi)*Math.sin(xfi));
+      y=Math.sqrt(h*h+rom*rom-2*h*rom*Math.cos(xfi)*Math.cos(xla));
+      r1=y*y+rom*rom;
+      r2=h*h;
+      if (r1 > r2) {
+        linele[indexLine][point] = Float.NaN;
+        linele[indexEle][point] = Float.NaN;
+        continue;
+
+      }
+      rs=re+h;
+      reph=re;
+      rpph=rp;
+      coslo=Math.cos(sublat*cdr);
+      sinlo=Math.sin(sublat*cdr);
+      teta=Math.atan((rpph/reph)*Math.tan(xfi));
+      xt=reph*Math.cos(teta)*Math.cos(xla);
+      yt=reph*Math.cos(teta)*Math.sin(xla);
+      zt=rpph*Math.sin(teta);
+      px=Math.atan((coslo*(yt-rs*sinlo)-(xt-rs*coslo)*sinlo)/
+       (sinlo*(yt-rs*sinlo)+(xt-rs*coslo)*coslo));
+      py=Math.atan(zt*((Math.tan(px)*sinlo-coslo)/(xt-rs*coslo))*Math.cos(px));
+      px=px*crd;
+      py=py*crd;
+      xr=px/(deltax*lpsi2);
+      yr=py/(deltay*lpsi2);
+      xr=cenele-xr;
+      yr=cenlin-yr;
+      xr=xr*4.0;
+      yr=yr*4.0;
+      linele[indexLine][point] = yr;
+      linele[indexEle][point] = xr;
+    }
+    return imageCoordToAreaCoord(linele, linele);
+  }
+
+  public double[][] toLatLon(double[][] linele) { 
+
+    double xele2, xlin2, x, y, xr, yr, rs, tanx, tany, val1, val2, yk;
+    double vmu, cosrf, sinrf, xt, yt, zt, xfi, xla, teta;
+
+    int number = linele[0].length;
+    double[][] latlon = new double[2][number];
+    double[][] imglinele = areaCoordToImageCoord(linele);
+
+    for (int point=0; point<number; point++) {
+
+      xele2 = imglinele[indexEle][point]/4.0;
+      xlin2 = imglinele[indexLine][point]/4.0;
+      x = cenele - xele2;
+      y = cenlin - xlin2;
+      xr = x;
+      yr = y;
+
+      x=xr*lpsi2*deltax*cdr;
+      y=yr*lpsi2*deltay*cdr;
+      rs=re+h;
+      tanx=Math.tan(x);
+      tany=Math.tan(y);
+      val1=1.+tanx*tanx;
+      val2=1.+(tany*tany)*((1.+a)*(1.+a));
+      yk=rs/re;
+
+      if((val1*val2) > ((yk*yk)/(yk*yk-1.0))) {
+        latlon[indexLine][point] = Float.NaN;
+        latlon[indexEle][point] = Float.NaN;
+        continue;
+      }
+
+      vmu=(rs-(re*(Math.sqrt((yk*yk)-(yk*yk-1)*val1*val2))))/(val1*val2);
+      cosrf=Math.cos(sublat*cdr);
+      sinrf=Math.sin(sublat*cdr);
+      xt=(rs*cosrf)+(vmu*(tanx*sinrf-cosrf));
+      yt=(rs*sinrf)-(vmu*(tanx*cosrf+sinrf));
+      zt=vmu*tany/Math.cos(x);
+      teta=Math.asin(zt/rp);
+      xfi=(Math.atan(((Math.tan(teta))*re)/rp))*crd;
+      xla=-Math.atan(yt/xt)*crd;
+//--- CHANGE LONGITUDE FOR CORRECT SUBPOINT
+      xla=xla+sublon;
+
+      if (isEastPositive) xla = -xla;
+      latlon[indexLat][point] = xfi;
+      latlon[indexLon][point] = xla;
+    }
+
+    return latlon;
+  }
+
+  public static void main(String[] args) {
+
+    int [] navBlock = new int[800];
+    int [] dirBlock = new int[64];
+    DataInputStream dis = null;
+    KALPnav kalp = null;
+    String fileName = "KALPAREA";
+
+    System.out.println("unit test of class KALP begin...");
+
+    if (args.length > 0) fileName = args[0];
+
+    // test assumes presence of test area called KALPXAREA
+    try {
+      dis = new DataInputStream (
+        new BufferedInputStream(new FileInputStream(fileName), 2048)
+      );
+    } catch (Exception e) {
+      System.out.println("error creating DataInputStream: " + e);
+      System.exit(0);
+    }
+
+    // read and discard the directory
+    System.out.println("reading in, discarding directory words...");
+    try {
+      for (int i = 0; i < 64; i++) {
+        dirBlock[i] = dis.readInt();
+      }
+    } catch (IOException e) {
+      System.out.println("error reading area file directory: " + e);
+      System.exit(0);
+    }
+
+    // now read in the navigation data
+    System.out.println("reading in navigation words...");
+    try {
+      for (int i = 0; i < navBlock.length; i++) {
+        navBlock[i] = dis.readInt();
+      }
+    } catch (IOException e) {
+      System.out.println("error reading area file navigation data: " + e);
+      System.exit(0);
+    }
+
+    if (navBlock[0] != KALP) {
+      System.out.println("error: not a KALP navigation block.");
+      System.exit(0);
+    } 
+
+    double [][] linEle = new double [2][1];
+    double [][] latLon = new double [2][1];
+
+    System.out.println("creating KALPnav object...");
+    kalp = new KALPnav(navBlock);
+    kalp.setImageStart(dirBlock[5], dirBlock[6]);
+    System.out.println("####  ImageStart set to:"+dirBlock[5]+" "+dirBlock[6]);
+    kalp.setRes(dirBlock[11], dirBlock[12]);
+    System.out.println("####  ImageRes set to:"+dirBlock[11]+" "+dirBlock[12]);
+    kalp.setStart(0,0);
+
+    //System.out.println("####  setFlipLineCoord called with "+dirBlock[8]);
+    //kalp.setFlipLineCoordinates(dirBlock[8]); // invert Y axis coordinates
+
+    System.out.println(" test of toLinEle...");
+
+    latLon[kalp.indexLat][0] = 11.0f;
+    latLon[kalp.indexLon][0] = 50.f;
+    linEle = kalp.toLinEle(latLon);
+    System.out.println("  answer is: " + linEle[kalp.indexLine][0] + 
+      ", " + linEle[kalp.indexEle][0]);
+
+    latLon[0][0] = Double.NaN;
+    latLon = kalp.toLatLon(linEle);
+    System.out.println("testing inverse of 11N/50E");
+    System.out.println("  answer is: " + latLon[kalp.indexLat][0] + 
+      ", " + latLon[kalp.indexLon][0]);
+
+    System.out.println(" another test of toLinEle...");
+
+    latLon[kalp.indexLat][0] = -20.0f;
+    latLon[kalp.indexLon][0] = 100.f;
+    linEle = kalp.toLinEle(latLon);
+    System.out.println("  answer is: " + linEle[kalp.indexLine][0] + 
+      ", " + linEle[kalp.indexEle][0]);
+
+    latLon[0][0] = Double.NaN;
+    latLon = kalp.toLatLon(linEle);
+    System.out.println("testing inverse of 20S/100E");
+    System.out.println("  answer is: " + latLon[kalp.indexLat][0] + 
+      ", " + latLon[kalp.indexLon][0]);
+
+    System.out.println("unit test of class KALPnav end...");
+
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/LALOnav.java b/edu/wisc/ssec/mcidas/LALOnav.java
new file mode 100644
index 0000000..996ea58
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/LALOnav.java
@@ -0,0 +1,489 @@
+//
+// LALOnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+import java.lang.Float;
+import java.lang.Double;
+import visad.Gridded2DSet;
+
+/**
+ * Navigation class for Radar (RECT) type nav. This code was modified
+ * from the original FORTRAN code (nvxrect.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+
+public final class LALOnav extends AREAnav 
+{
+
+	private static final long serialVersionUID = 1L;
+	int rows, cols, latres, lonres, latpoint, lonpoint, numPoints;
+    int ulline, ulelem, aux_size, lat_aux_offset, lon_aux_offset;
+    int lrlin, lrele;
+    double minlat, maxlat, minlon, maxlon;
+    float[] latData, lonData;
+    float LAT_MISSING = 0.f;
+    float LON_MISSING = 0.f;
+    boolean debug = false;
+    int count = 0;
+    Gridded2DSet gs = null;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the RECT nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a RECT type.
+     */
+    public LALOnav (int[] iparms, int[] auxBlock) 
+        throws IllegalArgumentException
+    {
+
+        if (iparms[0] != LALO ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        if (debug) {
+          System.out.println("Len of nav = "+iparms.length);
+          for (int i=0; i< iparms.length; i++) {
+            System.out.println("i="+i+"  iparm="+iparms[i]);
+
+          }
+        }
+
+        rows = iparms[65];
+        cols = iparms[66];
+        /*
+        minlat = McIDASUtil.integerLatLonToDouble(iparms[67]);
+        minlon = McIDASUtil.integerLatLonToDouble(iparms[68]);
+        maxlat = McIDASUtil.integerLatLonToDouble(iparms[69]);
+        maxlon = McIDASUtil.integerLatLonToDouble(iparms[70]);
+        */
+        minlat = (double) Float.intBitsToFloat(iparms[67]);
+        minlon = (double) Float.intBitsToFloat(iparms[68]);
+        maxlat = (double) Float.intBitsToFloat(iparms[69]);
+        maxlon = (double) Float.intBitsToFloat(iparms[70]);
+
+        latres = iparms[71];
+        lonres = iparms[72];
+        latpoint = iparms[73];
+        lonpoint = iparms[74];
+        ulline = iparms[75];
+        ulelem = iparms[76];
+        aux_size = iparms[77];
+        lat_aux_offset = iparms[78];
+        lon_aux_offset = iparms[79];
+
+
+        int begLat = lat_aux_offset/4;
+        int begLon = lon_aux_offset/4;
+
+        lrlin = ulline + (rows -1) * latres;
+        lrele = ulelem + (cols - 1) * lonres;
+
+        if (debug) {
+          System.out.println("rows, cols="+rows+" "+cols);
+          System.out.println("min/max lat, lon="+minlat+" "+maxlat+" / "+minlon+" "+maxlon);
+
+          System.out.println("latres, lonres="+latres+" "+lonres);
+          System.out.println("latpoint, lonpoint="+latpoint+" "+lonpoint);
+          System.out.println("ulline, ulelem="+ulline+" "+ulelem);
+          System.out.println("size_aux, lat_aux, lon_aux="+aux_size+" "+lat_aux_offset+" "+lon_aux_offset);
+          System.out.println("len of auxBlock"+auxBlock.length);
+
+          System.out.println("begLat/Lon="+begLat+" "+begLon);
+          System.out.println("len of auxBlock="+auxBlock.length);
+        }
+
+        /*
+        float[][] lats = new float[cols][rows];
+        float[][] lons = new float[cols][rows];
+        */
+
+        numPoints = cols * rows;
+        latData = new float[numPoints];
+        lonData = new float[numPoints];
+        float[][] lalo = new float[2][numPoints];
+
+        for (int k=0; k<numPoints; k++) {
+          latData[k] = Float.intBitsToFloat(auxBlock[k + begLat]);
+          lonData[k] = Float.intBitsToFloat(auxBlock[k + begLon]);
+          lalo[0][k] = lonData[k];
+          lalo[1][k] = latData[k];
+
+          if (latData[k] < -90.f || latData[k] > 90.f
+                  || lonData[k] < -180.f || lonData[k] > 360.f) {
+
+            latData[k] = LAT_MISSING;
+            lonData[k] = LON_MISSING;
+            lalo[0][k] = Float.NaN;
+            lalo[1][k] = Float.NaN;
+          }
+        }
+
+        count = 0;
+        // look up VisAD stuff...if available...
+  try {
+    
+    gs = new Gridded2DSet(visad.RealTupleType.SpatialEarth2DTuple,
+       lalo, cols, rows, null, null, null, false, false);
+
+  } catch (Exception ge) {
+    System.out.println("####  The VisAD library visad.jar is needed for this operation");
+    ge.printStackTrace();
+  }
+
+        if (debug) System.out.println("done coverting");
+
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     * this code cobbled from McIDAS laloutil.c
+     */
+    public float[][] toLatLon(float[][] linele) {
+
+      int number = linele[0].length;
+      double rlin, rele;
+      int  tl_entry, tr_entry, bl_entry, br_entry;
+      float  tl_lats, tr_lats, bl_lats, br_lats;
+      float  tl_lons, tr_lons, bl_lons, br_lons;
+      float  frac_row, frac_col;
+      float  ax, bx, cx;
+
+      float[][] latlon = new float[2][number];
+
+      // Convert array to Image coordinates for computations
+      float[][] imglinele = areaCoordToImageCoord(linele);
+
+      for (int point=0; point < number; point++) 
+      {
+          rlin = imglinele[indexLine][point];
+          rele = imglinele[indexEle][point];
+
+          if (debug) {
+            count ++;
+            //if (count < 20) {
+              System.out.println(" floats...ulline, lrlin, ulelem, lrele="+ulline+" "+lrlin+" "+ulelem+" "+lrele);
+              System.out.println(" rlin, rele="+" "+rlin+" "+rele);
+            //}
+          }
+
+          if (rlin < ulline || rlin > lrlin ||
+                         rele < ulelem || rele > lrele) {
+            latlon[indexLat][point] = Float.NaN;
+            latlon[indexLon][point] = Float.NaN;
+
+          } else {
+
+
+           /* offset to the top_left (tl) corner lat/lon */
+           tl_entry = (((((int)rlin-ulline)/latres) * cols) +
+                      ((int)rele-ulelem) / lonres);
+
+
+           /* offsets for top_right, bottom_left and bottom_left lat/lon
+        corners */
+           tr_entry = tl_entry + 1;
+           bl_entry = tl_entry + cols;
+           br_entry = bl_entry + 1;
+
+           // check to see if on last row or column...
+           if ( (((int)rlin - ulline)/latres) >= rows-1) {
+             bl_entry = tl_entry;
+             br_entry = bl_entry + 1;
+           }
+
+           if ( (((int)rele -ulelem)/lonres) >= cols-1) {
+             tr_entry = tl_entry;
+             br_entry = bl_entry;
+           }
+
+           if (debug) {
+             System.out.println(" tl_entry="+tl_entry+ " bl_entry="+bl_entry);
+           }
+
+           /* read the 4 corner latitudes */
+           tl_lats = latData[tl_entry];
+           tr_lats = latData[tr_entry];
+           bl_lats = latData[bl_entry];
+           br_lats = latData[br_entry];
+
+           /* read the 4 corner longitudes */
+           tl_lons = lonData[tl_entry];
+           tr_lons = lonData[tr_entry];
+           bl_lons = lonData[bl_entry];
+           br_lons = lonData[br_entry];
+         
+           /* Check for missing lat/lon */
+
+           if ( (tl_lats == LAT_MISSING && tl_lons == LON_MISSING)  ||
+                (tr_lats == LAT_MISSING && tr_lons == LON_MISSING) ||
+                (bl_lats == LAT_MISSING && bl_lons == LON_MISSING) ||
+                (br_lats == LAT_MISSING && br_lons == LON_MISSING) ) {
+
+                  latlon[indexLat][point] = Float.NaN;
+                  latlon[indexLon][point] = Float.NaN;
+
+           } else {
+
+             /* compute the fractional part of the row and column */
+             frac_row = (float)(((int)rlin-ulline) % latres) / (float)latres;
+             frac_col = (float)(((int)rele-ulelem) % lonres) / (float)lonres;
+
+           if (debug && count < 20) {
+             if (linele[indexLine][point] < .1) {
+               System.out.println(" lats: tl, tr, bl, br="+tl_lats+" "+tr_lats+" "+bl_lats+" "+br_lats);
+               System.out.println(" frac_row="+frac_row+" frac_col="+frac_col);
+             }
+           }
+
+             /*  Calc the real lat */
+             ax = tr_lats - tl_lats;
+             bx = bl_lats - tl_lats;
+             cx = (tl_lats + br_lats) - (bl_lats + tr_lats);
+
+             latlon[indexLat][point] = (ax * frac_col) + (bx * frac_row) + 
+                        (cx * frac_row * frac_col) + tl_lats;
+
+             /*  Calc the real lon */
+             ax = tr_lons - tl_lons;
+             bx = bl_lons - tl_lons;
+             cx = (tl_lons + br_lons) - (bl_lons + tr_lons);
+
+             latlon[indexLon][point] = (ax * frac_col) + (bx * frac_row) + 
+                        (cx * frac_row * frac_col) + tl_lons;
+           }
+            
+          }
+
+          if (debug && count < 20) System.out.println(" line/ele = "+linele[indexLine][point]+"/"+linele[indexEle][point]+"  rlin/rele="+rlin+"/"+rele+" Lat/Lon="+latlon[indexLat][point]+"/"+latlon[indexLon][point]);
+
+      } // end point for loop
+
+      return latlon;
+
+    }
+
+
+
+    public double[][] toLatLon(double[][] linele) {
+
+      int number = linele[0].length;
+      double[][] latlon = new double[2][number];
+      double rlin, rele;
+      int  tl_entry, tr_entry, bl_entry, br_entry;
+      float  tl_lats, tr_lats, bl_lats, br_lats;
+      float  tl_lons, tr_lons, bl_lons, br_lons;
+      float  frac_row, frac_col;
+      float  ax, bx, cx;
+
+      // Convert array to Image coordinates for computations
+      double[][] imglinele = areaCoordToImageCoord(linele);
+
+      for (int point=0; point < number; point++) 
+      {
+          rlin = imglinele[indexLine][point];
+          rele = imglinele[indexEle][point];
+
+          if (debug) {
+            count ++;
+            if (count < 20) {
+              System.out.println(" double....ulline, lrlin, ulelem, lrele="+ulline+" "+lrlin+" "+ulelem+" "+lrele);
+              System.out.println(" rlin, rele="+" "+rlin+" "+rele);
+            }
+          }
+
+          if (rlin < ulline || rlin > lrlin ||
+                         rele < ulelem || rele > lrele) {
+            latlon[indexLat][point] = Double.NaN;
+            latlon[indexLon][point] = Double.NaN;
+
+          } else {
+
+
+
+           /* offset to the top_left (tl) corner lat/lon */
+           tl_entry = (((((int)rlin-ulline)/latres) * cols) +
+                      ((int)rele-ulelem) / lonres);
+
+
+           /* offsets for top_right, bottom_left and bottom_left lat/lon
+        corners */
+           tr_entry = tl_entry + 1;
+           bl_entry = tl_entry + cols;
+           br_entry = bl_entry + 1;
+
+           // check to see if on last row or column...
+           if ( (((int)rlin - ulline)/latres) >= rows-1) {
+             bl_entry = tl_entry;
+             br_entry = bl_entry + 1;
+           }
+
+           if ( (((int)rele -ulelem)/lonres) >= cols-1) {
+             tr_entry = tl_entry;
+             br_entry = bl_entry;
+           }
+
+           if (debug && count < 20) {
+             System.out.println(" tl_entry="+tl_entry+ " bl_entry="+bl_entry);
+           }
+
+           /* read the 4 corner latitudes */
+           tl_lats = latData[tl_entry];
+           tr_lats = latData[tr_entry];
+           bl_lats = latData[bl_entry];
+           br_lats = latData[br_entry];
+
+           /* read the 4 corner longitudes */
+           tl_lons = lonData[tl_entry];
+           tr_lons = lonData[tr_entry];
+           bl_lons = lonData[bl_entry];
+           br_lons = lonData[br_entry];
+         
+           /* Check for missing lat/lon */
+
+           if ( (tl_lats == LAT_MISSING && tl_lons == LON_MISSING)  ||
+                (tr_lats == LAT_MISSING && tr_lons == LON_MISSING) ||
+                (bl_lats == LAT_MISSING && bl_lons == LON_MISSING) ||
+                (br_lats == LAT_MISSING && br_lons == LON_MISSING) ) {
+
+                  latlon[indexLat][point] = Double.NaN;
+                  latlon[indexLon][point] = Double.NaN;
+
+           } else {
+
+             /* compute the fractional part of the row and column */
+             frac_row = (float)(((int)rlin-ulline) % latres) / (float)latres;
+             frac_col = (float)(((int)rele-ulelem) % lonres) / (float)lonres;
+
+           if (debug && count < 20) {
+             System.out.println(" tl_entry="+tl_entry);
+             if (linele[indexLine][point] < .1) {
+               System.out.println(" lats: tl, tr, bl, br="+tl_lats+" "+tr_lats+" "+bl_lats+" "+br_lats);
+               System.out.println(" frac_row="+frac_row+" frac_col="+frac_col);
+             }
+           }
+
+             /*  Calc the real lat */
+             ax = tr_lats - tl_lats;
+             bx = bl_lats - tl_lats;
+             cx = (tl_lats + br_lats) - (bl_lats + tr_lats);
+
+             latlon[indexLat][point] = (ax * frac_col) + (bx * frac_row) + 
+                        (cx * frac_row * frac_col) + tl_lats;
+
+             /*  Calc the real lon */
+             ax = tr_lons - tl_lons;
+             bx = bl_lons - tl_lons;
+             cx = (tl_lons + br_lons) - (bl_lons + tr_lons);
+
+             latlon[indexLon][point] = (ax * frac_col) + (bx * frac_row) + 
+                        (cx * frac_row * frac_col) + tl_lons;
+           }
+            
+          }
+
+          if (debug && count < 20) System.out.println(" line/ele = "+linele[indexLine][point]+"/"+linele[indexEle][point]+"  rlin/rele="+rlin+"/"+rele+" Lat/Lon="+latlon[indexLat][point]+"/"+latlon[indexLon][point]);
+
+      } // end point for loop
+
+      return latlon;
+
+    }
+
+  /**
+   * toLinEle converts lat/long to satellite line/element
+   * transform an array of values in R^DomainDimension to an array
+   * of non-integer grid coordinates
+   *
+   * @param  latlon array of lat/long pairs. Where latlon[indexLat][]
+   *                are latitudes and latlon[indexLon][] are longitudes.
+   *
+   * @return linele[][] array of line/element pairs.  Where
+   *                    linele[indexLine][] is a line and linele[indexEle][]
+   *                    is an element.  These are in 'file' coordinates
+   *                    (not "image" coordinates);
+   */
+
+  public float[][] toLinEle(float[][] latlon) {
+    try {
+      float[][] ll = new float[2][latlon[0].length];
+      for (int k=0; k<ll[0].length; k++) {
+        ll[0][k] = latlon[indexLon][k];
+        ll[1][k] = latlon[indexLat][k];
+      }
+
+      float[][] linele = gs.valueToGrid(ll);
+
+      for (int k=0; k<linele[0].length; k++) {
+        linele[indexLine][k] = ulline + latres*linele[1][k];
+        linele[indexEle][k] = ulelem + lonres*linele[0][k];
+      }
+
+      return imageCoordToAreaCoord(linele,linele);
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  public double[][] toLinEle(double[][] latlon) {
+    try {
+      float[][] ll = new float[2][latlon[0].length];
+      for (int k=0; k<ll[0].length; k++) {
+        ll[0][k] = (float)latlon[indexLon][k];
+        ll[1][k] = (float)latlon[indexLat][k];
+      }
+
+      float[][] xy = gs.valueToGrid(ll);
+
+      double[][] linele = new double[2][xy[0].length];
+      for (int k=0; k<xy[0].length; k++) {
+        linele[indexLine][k] = ulline + latres*xy[1][k];
+        linele[indexEle][k] = ulelem + lonres*xy[0][k];
+      }
+
+      return imageCoordToAreaCoord(linele,linele);
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/LAMBnav.java b/edu/wisc/ssec/mcidas/LAMBnav.java
new file mode 100644
index 0000000..4c55ef7
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/LAMBnav.java
@@ -0,0 +1,191 @@
+//
+// LAMBnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for Lambert Conformal (LAMB) type nav. This code was 
+ * modified from the original FORTRAN code (nvxlamb.dlm) on the McIDAS system. 
+ * It only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class LAMBnav extends AREAnav 
+{
+
+    int iwest;
+    int ihem;
+    double xrow;
+    double xcol;
+    double xlat1;
+    double xlat2;
+    double xspace;
+    double xqlon;
+    double xblat;
+    double xfac;
+    double xpole;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the LAMB nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a LAMB type.
+     */
+    public LAMBnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+
+        if (iparms[0] != LAMB ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        xrow = iparms[1];
+        xcol = iparms[2];
+        int ipole = iparms[11];
+        if (ipole == 0) ipole = 900000;
+        ihem = 1;
+        if (ipole < 0) ihem = -1;
+        xpole = McIDASUtil.integerLatLonToDouble(ipole);
+
+        xlat1 = McIDASUtil.integerLatLonToDouble(iparms[3]);
+        xlat2 = McIDASUtil.integerLatLonToDouble(iparms[4]);
+        xlat1 = (90. - ihem*xlat1)*DEGREES_TO_RADIANS;
+        xlat2 = (90. - ihem*xlat2)*DEGREES_TO_RADIANS;
+
+        xspace = iparms[5]/1000.;
+        xqlon = McIDASUtil.integerLatLonToDouble(iparms[6]);
+        double r = iparms[7]/1000.;
+
+        iwest = iparms[10];
+        if (iwest >= 0) iwest = 1;
+
+        xfac = (Math.log(Math.sin(xlat1)) - Math.log(Math.sin(xlat2)))/
+               (Math.log(Math.tan(.5*xlat1)) - Math.log(Math.tan(.5*xlat2)));
+        xblat = r * Math.sin(xlat1)/
+                (xspace*xfac*Math.pow(Math.tan(xlat1*.5), xfac));
+
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xlon;
+        double xlat;
+        double xrlon, radius;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xldif = ihem*(imglinele[indexLine][point] - xrow)/xblat;
+            xedif = -ihem*(imglinele[indexEle][point] - xcol)/xblat;
+            xrlon = 0;
+            if (!(xldif == 0 && xedif == 0)) xrlon = Math.atan2(xedif, xldif);
+            xlon = ihem*xrlon/xfac/DEGREES_TO_RADIANS + xqlon;
+            xlon = (xlon+900.)%360. - 180.0;
+            radius = Math.sqrt(xldif*xldif + xedif*xedif);
+            if (Math.abs(radius) < 1.e-10)
+                xlat = ihem*90;
+            else
+                xlat = ihem*(90. - 2*Math.atan(
+                          Math.exp(Math.log(radius)/xfac))/DEGREES_TO_RADIANS);
+
+            latlon[indexLat][point] = xlat;
+            latlon[indexLon][point] = (iwest == 1) ? -xlon  : xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double xlon;
+        double xlat;
+        double xrlon, xrlat, xclat;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+            xlat = latlon[indexLat][point];
+            // transform to McIDAS (west positive longitude) coordinates
+            xlon = (iwest == 1) 
+                   ? -latlon[indexLon][point]
+                   : latlon[indexLon][point];
+
+            xrlon = ihem*(xlon-xqlon);
+            xrlon = (xrlon+900.)%360. - 180.;
+            xrlon = xrlon*xfac*DEGREES_TO_RADIANS;
+            xclat = (90. - ihem*xlat)*DEGREES_TO_RADIANS*.5;
+            if (xclat == 0.0)
+                xrlat = 0.0;
+            else
+                xrlat =  xblat*Math.pow(Math.tan(Math.abs(xclat)), xfac);
+
+            linele[indexLine][point] = xrow + ihem*(xrlat*Math.cos(xrlon));
+            linele[indexEle][point]  = xcol - ihem*(xrlat*Math.sin(xrlon));
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/MERCnav.java b/edu/wisc/ssec/mcidas/MERCnav.java
new file mode 100644
index 0000000..5c0c20a
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/MERCnav.java
@@ -0,0 +1,289 @@
+//
+// MERCnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2009 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for Mercator (MERC) type nav. This code was modified 
+ * from the original FORTRAN code (nvxmerc.dlm) on the McIDAS system. 
+ * It only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class MERCnav extends AREAnav 
+{
+
+    int iwest;
+    int leftlon;
+    double xrow;
+    double xcol;
+    double xlat1;
+    double xspace;
+    double xqlon;
+    double xblat;
+    double xblon;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the MERC nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a MERC type.
+     */
+    public MERCnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+
+        if (iparms[0] != MERC ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        xrow = iparms[1];
+        xcol = iparms[2];
+        xlat1 = McIDASUtil.integerLatLonToDouble(iparms[3]);
+        xspace = iparms[4]/1000.;
+        xqlon = McIDASUtil.integerLatLonToDouble(iparms[5]);
+        double r = iparms[6]/1000.;
+        iwest = iparms[9];
+        if (iwest >= 0) iwest = 1;
+        xblat = r * Math.cos(xlat1*DEGREES_TO_RADIANS)/xspace;
+        xblon = DEGREES_TO_RADIANS*r/xspace;
+        leftlon = (int) xqlon-180*iwest;
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xlon;
+        double xlat;
+        double xrlat, xrlon;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xldif = xrow - imglinele[indexLine][point];
+            xedif = xcol - imglinele[indexEle][point];
+            xrlon = iwest*xedif/xblon;
+            xlon = xrlon+xqlon;
+            xrlat = Math.atan(Math.exp(xldif/xblat));
+            xlat = (xrlat/DEGREES_TO_RADIANS - 45.)*2.+xlat1;
+            if (xlon > (360.+leftlon) || xlon < leftlon) 
+            {
+                latlon[indexLat][point] = Double.NaN;
+                latlon[indexLon][point] = Double.NaN;
+            }
+            else
+            {
+                latlon[indexLat][point] = xlat;
+                if (xlon > 180.) xlon = xlon - 360.;
+                if (xlon < -180.) xlon = xlon + 360.;
+                latlon[indexLon][point] = (iwest == 1) ? -xlon  : xlon;
+            }
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double xlon;
+        double xlat;
+        double xrlon, xrlat;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xlat = latlon[indexLat][point];
+            // transform to McIDAS (west positive longitude) coordinates
+            xlon = (iwest == 1) 
+                   ? -latlon[indexLon][point]
+                   : latlon[indexLon][point];
+
+            xrlon = iwest*(xlon-xqlon);
+            if (xrlon > 180.) xrlon -= 360.;
+            if (xrlon < -180.) xrlon += 360.;
+            if (xlat >= 90.) xlat = 89.99;
+            if (xlat <= -90.) xlat = -89.99;
+            xrlat = ((xlat-xlat1)/2 + 45.)*DEGREES_TO_RADIANS;
+            if (xrlat <= 0.0)
+            {
+                linele[indexLine][point] = Double.NaN;
+                linele[indexEle][point] = Double.NaN;
+            }
+            else
+            {
+                linele[indexLine][point] = 
+                    xrow - xblat*Math.log(Math.tan(xrlat));
+                linele[indexEle][point] = xcol - xrlon*xblon;
+            }
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xlon;
+        double xlat;
+        double xrlon, xrlat;
+
+        int number = linele[0].length;
+        float[][] latlon = new float[2][number];
+
+        // Convert array to Image coordinates for computations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xldif = xrow - imglinele[indexLine][point];
+            xedif = xcol - imglinele[indexEle][point];
+            xrlon = iwest*xedif/xblon;
+            xlon = xrlon+xqlon;
+            xrlat = Math.atan(Math.exp(xldif/xblat));
+            xlat = (xrlat/DEGREES_TO_RADIANS - 45.)*2.+xlat1;
+            if (xlon > (360.+leftlon) || xlon < leftlon) 
+            {
+                latlon[indexLat][point] = Float.NaN;
+                latlon[indexLon][point] = Float.NaN;
+            }
+            else
+            {
+                latlon[indexLat][point] = (float) xlat;
+                if (xlon > 180.f) xlon = xlon - 360.f;
+                if (xlon < -180.f) xlon = xlon + 360.f;
+                latlon[indexLon][point] = (float) ((iwest == 1) ? -xlon  : xlon);
+            }
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) 
+    {
+        double xlon;
+        double xlat;
+        double xrlon, xrlat;
+
+        int number = latlon[0].length;
+        float[][] linele = new float[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xlat = latlon[indexLat][point];
+            // transform to McIDAS (west positive longitude) coordinates
+            xlon = (iwest == 1) 
+                   ? -latlon[indexLon][point]
+                   : latlon[indexLon][point];
+
+            xrlon = iwest*(xlon-xqlon);
+            if (xrlon > 180.) xrlon -= 360.;
+            if (xrlon < -180.) xrlon += 360.;
+            if (xlat >= 90.) xlat = 89.99;
+            if (xlat <= -90.) xlat = -89.99;
+            xrlat = ((xlat-xlat1)/2 + 45.)*DEGREES_TO_RADIANS;
+            if (xrlat <= 0.0)
+            {
+                linele[indexLine][point] = Float.NaN;
+                linele[indexEle][point] = Float.NaN;
+            }
+            else
+            {
+                linele[indexLine][point] =  (float)
+                    (xrow - xblat*Math.log(Math.tan(xrlat)));
+                linele[indexEle][point] = (float) (xcol - xrlon*xblon);
+            }
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/MOLLnav.java b/edu/wisc/ssec/mcidas/MOLLnav.java
new file mode 100644
index 0000000..cc5f550
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/MOLLnav.java
@@ -0,0 +1,560 @@
+//
+// MOLLnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for Mollweide (MOLL) type nav. This code was modified
+ * from the original FORTRAN code (nvxmoll.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see edu.wisc.ssec.mcidas.AREAnav
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class MOLLnav extends AREAnav 
+{
+
+    private boolean isEastPositive = true;
+
+    private double drad;
+    private double decc;
+    private double[] tlat = new double[101];
+    private double[] t = new double[101];
+    private double[][] coef = new double[4][101];
+    private double[] lattbl = new double[91];
+
+    private double xrow, xcol, rpix, xqlon;
+    private int itype, iwest, icord;
+    private double asq = 40683833.48;
+    private double bsq = 40410330.18;
+    private double ab = 40546851.22;
+    private double ecc = .081992e0;
+    private double eccsqr = 6.72265e-3;
+    private int kwest = -1;
+    private int kcord = 0;
+
+    private final int KMPP = 1263358032;
+    private final int PPMK = 1347439947;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the MOLL nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a MOLL type.
+     */
+    public MOLLnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+        double res;
+        double x;
+        int i;
+
+/*  No longer needed.  Here for consistency with nvxmoll.dlm
+        if (ifunc != 1) 
+        {
+            if (iparms[0] == XY ) itype = 1;
+            if (iparms[0] == LL ) itype = 2;
+            return;
+        }
+*/
+
+        if (iparms[0] != MOLL ) 
+             throw new IllegalArgumentException("Invalid navigation type" + 
+                                                 iparms[0]);
+
+        itype = 2;
+
+        xrow = iparms[1];
+        xcol = iparms[2];
+        xqlon = iparms[4];
+        drad = iparms[6]/1000.e0;
+        double r = drad;
+
+        if (iparms[14] == KMPP || iparms[14] == PPMK)
+        {
+            res = iparms[3];
+            rpix = (0.7071*r)/res;
+        }
+        else
+        {
+            rpix = iparms[3];
+        }
+
+        decc = iparms[7]/1.e6;
+        iwest = iparms[9];
+        if (iwest >= 0 ) iwest = 1;
+        icord = iparms[8];
+
+        // set up coefficients;
+        //     CALL LLOPT(DRAD,DECC,IWEST,IPARMS(9))
+        asq = drad*drad;
+        ecc = decc;
+        eccsqr = ecc*ecc;
+        double dpole = Math.sqrt(asq*(1.e0-eccsqr));
+        bsq = dpole*dpole;
+        ab = drad*dpole;
+        if(iwest < 0) kwest=1;
+        if(icord < 0) kcord=-1;
+
+        for (i = 0; i < tlat.length; i++)
+        {
+            x = i/100.;
+            if (x >= 1.)
+            {
+                t[i] = 1.;
+                tlat[i] = 1.57080/DEGREES_TO_RADIANS;
+            }
+            else
+            {
+                t[i] = x;
+                tlat[i] = Math.asin((Math.asin(x)
+                           +x*Math.sqrt(1.0-x*x))/1.57080)/DEGREES_TO_RADIANS;
+            }
+        }
+
+        // create cubic spline table - from asspl2.for
+        int n = 100;
+        int m = n - 1;
+        double w, z, t1, s, u, v, zs, zq, ws, wq, aa, ba, ca, da;
+        for (i = 0; i < n; i++)
+        {
+            if (i != 0)
+                t1 = (t[i+1]-t[i-1])/(tlat[i+1]-tlat[i-1]);
+            else
+            {
+                w  = (t[1]+t[2])/2.0;
+                z  = (tlat[1]+tlat[2])/2.0;
+                t1 = (w-t[0])/(z-tlat[0]);
+                t1 = 2.0*(t[1]-t[0])/(tlat[1]-tlat[0])-t1;
+            }
+            if (i != m)
+                s = (t[i+2]-t[i])/(tlat[i+2]-tlat[i]);
+            else
+            {
+                w = (t[n-1]+t[n-2])/2.0;
+                z = (tlat[n-1]+tlat[n-2])/2.0;
+                s = (t[n]-w)/(tlat[n]-z);
+                s = 2.0*(t[n]-t[n-1])/(tlat[n]-tlat[n-1])-s;
+            }
+            u=t[i+1];
+            v=t[i];
+            w=(tlat[i+1]+tlat[i])/2.0;
+            z=(tlat[i+1]-tlat[i])/2.0;
+            zs=z*z;
+            zq=z*zs;
+            ws=w*w;
+            wq=w*ws;
+            aa=.5*(u+v)-.25*z*(s-t1);
+            ba=.75*(u-v)/z-.25*(s+t1);
+            ca=.25*(s-t1)/z;
+            da=.25*(s+t1)/zs-.25*(u-v)/zq;
+            coef[0][i]=aa-ba*w+ca*ws-da*wq;
+            coef[1][i]=ba-2.0*ca*w+3.0*da*ws;
+            coef[2][i]=ca-3.0*da*w;
+            coef[3][i]=da;
+        }
+        for (int j = 0; j < 4; j++)
+        {
+            coef[j][n] = coef[j][n-1];
+        }
+
+        i = 0;
+        int j, k;
+        for (int l = 0; l < 91; l++)
+        {
+            u = l;
+            if (i >= n-1) i=0;
+            if (u < tlat[i] || u > tlat[i+1])
+            {
+                i = 0;
+                j = n;
+                do 
+                {
+                    k = (i+j)/2;
+                    if (u < tlat[k])  j=k;
+                    if (u >= tlat[k]) i=k;
+                } while (j > i+1);
+            }
+            lattbl[l] = coef[0][i]+u*(coef[1][i]+u*(coef[2][i]+u*coef[3][i]));
+        }
+
+    }
+
+    /** 
+     * Converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double xlin, xele;
+        double xldif, xedif;
+        double w;
+        double xlat, xlon;
+        double ylat, ylon;
+        double snlt, cslt, snln, csln, r, tnlt;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+        // Convert from file to Image coordinates for calculations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+
+            xldif = Math.abs(xlin - xrow)/rpix;
+            xedif = (xcol - xele)/rpix;
+
+            // WLH 8 March 2000
+            // if (xldif > 1.0)
+            if (xlin != xlin || xele != xele || xldif > 1.0)
+            {
+                xlat = Double.NaN;
+                xlon = Double.NaN;
+            }
+            else
+            {
+                w = Math.sqrt(1.0 - xldif*xldif);
+                if (w == 0.0 || Math.abs(xedif/w) > 2.0)
+                {
+                    xlat = Double.NaN;
+                    xlon = Double.NaN;
+                }
+                else
+                {
+                    xlat = Math.asin((Math.asin(xldif)+
+                                 xldif*w)/1.57080)/DEGREES_TO_RADIANS;
+                    if (xlin > xrow) xlat = -xlat;
+
+                    // Compute angular displacement from std longitude (XQLON)
+                    xlon = -90.*(xedif/w);
+                    xlon = xqlon - xlon;
+
+                    // Force angles to (-180 < XLON < 180)
+                    if (xlon > 180.) xlon = xlon - 360.;
+                    if (xlon < -180.) xlon = xlon + 360.;
+
+                    // Convert to cartesian? coordinates
+                    if (itype == 1)
+                    {
+                       // LLCART(YLAT,YLON,XLAT,XLON,Z)
+                       ylat=DEGREES_TO_RADIANS*xlat;
+                       if (kcord >= 0) 
+                           ylat = Math.atan2(bsq*Math.sin(ylat),
+                                             asq*Math.cos(ylat));
+                       ylon = kwest*DEGREES_TO_RADIANS*xlon;
+                       snlt = Math.sin(ylat);
+                       cslt = Math.cos(ylat);
+                       csln = Math.cos(ylon);
+                       snln = Math.sin(ylon);
+                       tnlt = Math.pow((snlt/cslt),2.0);
+                       r = ab*Math.sqrt((1.0+tnlt)/(bsq+asq*tnlt));
+                       xlat = r*cslt*csln;
+                       xlon = r*cslt*snln;
+                    }
+                }
+            }
+            // transform from McIDAS (west positive longitude) coordinates
+            if (isEastPositive) xlon = -xlon;
+            latlon[indexLat][point] = xlat;
+            latlon[indexLon][point] = xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double xlin, xele;
+        double xlat, xlon;
+        double flat, t, t2, w, diff_lon, xedif;
+        int isign, ilat;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xlat = latlon[indexLat][point];
+
+            // transform to McIDAS (west longitude positive) coordinates
+            xlon = isEastPositive 
+                     ? - latlon[indexLon][point]
+                     :  latlon[indexLon][point];
+
+            // WLH 8 March 2000
+            if (Double.isNaN(xlat) || Double.isNaN(xlon) ||
+                (Math.abs(xlat) > 90.) ) {   // DRM 10 June 2003
+              xele = Double.NaN;
+              xlin = Double.NaN;
+            }
+            else {
+  
+              isign = -1;
+              if (xlat < 0.0) isign = 1;
+              ilat = (int) (Math.abs(xlat));
+              flat = Math.abs(xlat) - ilat;
+              t = lattbl[ilat];
+              if (ilat != 90) t = t + flat*(lattbl[ilat+1] - lattbl[ilat]);
+              t2 = Math.max(0.0, 1.0-t*t);
+              w = Math.sqrt(t2);
+         
+              //** Here we need to handle the problem of computing
+              //** angular differences across the dateline.
+  
+              diff_lon = xlon - xqlon;
+  
+              if (diff_lon < -180.0) diff_lon = diff_lon  + 360.;
+              if (diff_lon >  180.0) diff_lon = diff_lon  - 360.;
+       
+              xedif = w * (diff_lon)/90.;
+       
+              if (Math.abs(xedif) > 2.0) 
+              {
+                 xele = Double.NaN;
+                 xlin = Double.NaN;
+              }
+              else
+              {
+                 xele = xcol - xedif*rpix;
+                 xlin = isign*t*rpix + xrow;
+              }
+            } // end if (xlat == xlat && xlon == xlon)
+            linele[indexLine][point] = xlin;
+            linele[indexEle][point] = xele;
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    /** 
+     * Converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) 
+    {
+
+        double xlin, xele;
+        double xldif, xedif;
+        double w;
+        double xlat, xlon;
+        double ylat, ylon;
+        double snlt, cslt, snln, csln, r, tnlt;
+
+        int number = linele[0].length;
+        float[][] latlon = new float[2][number];
+        // Convert from file to Image coordinates for calculations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+
+            xldif = Math.abs(xlin - xrow)/rpix;
+            xedif = (xcol - xele)/rpix;
+
+            // WLH 8 March 2000
+            // if (xldif > 1.0)
+            if (xlin != xlin || xele != xele || xldif > 1.0)
+            {
+                xlat = Double.NaN;
+                xlon = Double.NaN;
+            }
+            else
+            {
+                w = Math.sqrt(1.0 - xldif*xldif);
+                if (w == 0.0 || Math.abs(xedif/w) > 2.0)
+                {
+                    xlat = Double.NaN;
+                    xlon = Double.NaN;
+                }
+                else
+                {
+                    xlat = Math.asin((Math.asin(xldif)+
+                                 xldif*w)/1.57080)/DEGREES_TO_RADIANS;
+                    if (xlin > xrow) xlat = -xlat;
+
+                    // Compute angular displacement from std longitude (XQLON)
+                    xlon = -90.*(xedif/w);
+                    xlon = xqlon - xlon;
+
+                    // Force angles to (-180 < XLON < 180)
+                    if (xlon > 180.) xlon = xlon - 360.;
+                    if (xlon < -180.) xlon = xlon + 360.;
+
+                    // Convert to cartesian? coordinates
+                    if (itype == 1)
+                    {
+                       // LLCART(YLAT,YLON,XLAT,XLON,Z)
+                       ylat=DEGREES_TO_RADIANS*xlat;
+                       if (kcord >= 0) 
+                           ylat = Math.atan2(bsq*Math.sin(ylat),
+                                             asq*Math.cos(ylat));
+                       ylon = kwest*DEGREES_TO_RADIANS*xlon;
+                       snlt = Math.sin(ylat);
+                       cslt = Math.cos(ylat);
+                       csln = Math.cos(ylon);
+                       snln = Math.sin(ylon);
+                       tnlt = Math.pow((snlt/cslt),2.0);
+                       r = ab*Math.sqrt((1.0+tnlt)/(bsq+asq*tnlt));
+                       xlat = r*cslt*csln;
+                       xlon = r*cslt*snln;
+                    }
+                }
+            }
+            // transform from McIDAS (west positive longitude) coordinates
+            if (isEastPositive) xlon = -xlon;
+            latlon[indexLat][point] = (float) xlat;
+            latlon[indexLon][point] = (float) xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) 
+    {
+        double xlin, xele;
+        double xlat, xlon;
+        double flat, t, t2, w, diff_lon, xedif;
+        int isign, ilat;
+
+        int number = latlon[0].length;
+        float[][] linele = new float[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xlat = latlon[indexLat][point];
+
+            // transform to McIDAS (west longitude positive) coordinates
+            xlon = isEastPositive 
+                     ? - latlon[indexLon][point]
+                     :  latlon[indexLon][point];
+
+            // WLH 8 March 2000
+            if (Double.isNaN(xlat) || Double.isNaN(xlon) ||
+                (Math.abs(xlat) > 90.) ) {   // DRM 10 June 2003
+              xele = Double.NaN;
+              xlin = Double.NaN;
+            }
+            else {
+  
+              isign = -1;
+              if (xlat < 0.0) isign = 1;
+              ilat = (int) (Math.abs(xlat));
+              flat = Math.abs(xlat) - ilat;
+              t = lattbl[ilat];
+              if (ilat != 90) t = t + flat*(lattbl[ilat+1] - lattbl[ilat]);
+              t2 = Math.max(0.0, 1.0-t*t);
+              w = Math.sqrt(t2);
+         
+              //** Here we need to handle the problem of computing
+              //** angular differences across the dateline.
+  
+              diff_lon = xlon - xqlon;
+  
+              if (diff_lon < -180.0) diff_lon = diff_lon  + 360.;
+              if (diff_lon >  180.0) diff_lon = diff_lon  - 360.;
+       
+              xedif = w * (diff_lon)/90.;
+       
+              if (Math.abs(xedif) > 2.0) 
+              {
+                 xele = Double.NaN;
+                 xlin = Double.NaN;
+              }
+              else
+              {
+                 xele = xcol - xedif*rpix;
+                 xlin = isign*t*rpix + xrow;
+              }
+            } // end if (xlat == xlat && xlon == xlon)
+            linele[indexLine][point] = (float) xlin;
+            linele[indexEle][point] = (float) xele;
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+}
+
diff --git a/edu/wisc/ssec/mcidas/MSATnav.java b/edu/wisc/ssec/mcidas/MSATnav.java
new file mode 100644
index 0000000..bf45357
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/MSATnav.java
@@ -0,0 +1,466 @@
+//
+// MSATnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for Meteosat (MSAT) type nav. This code was modified
+ * from the original FORTRAN code (nvxmsat.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class MSATnav extends AREAnav 
+{
+
+    private boolean isEastPositive = true;
+
+    final double NOMORB=42164.;   // nominal radial distance of satellite (km)
+    final double EARTH_RADIUS=6378.155; // earth equatorial radius (km)
+
+    int itype;
+    double h;
+    double a;
+    double rp;
+    double lpsi2;
+    double deltax;
+    double deltay;
+    double rflon;                  // reference longitude;
+    double sublon;
+    int[] ioff = new int[3];
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the MSAT nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a MSAT type.
+     */
+    public MSATnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+
+/* No longer needed.  Kept for consistency with nvxmsat.dlm
+        if (ifunc != 1) 
+        {
+            if (iparms[0] == XY ) itype = 1;
+            if (iparms[0] == LL ) itype = 2;
+            return;
+        }
+*/
+
+        if (iparms[0] != MSAT ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        itype = 2;
+
+        System.arraycopy(iparms, 3, ioff, 0, 3);
+        h = (double) NOMORB - EARTH_RADIUS;
+        a = 1./297.;
+        rp = EARTH_RADIUS / (1. + a);
+        lpsi2=1;
+        deltax=18./2500.;
+        deltay=18./2500.;
+        rflon=0.0;     
+        sublon = McIDASUtil.integerLatLonToDouble(iparms[6]);
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double xele, xlin;
+        double xele2, xlin2;
+        double xfi, xla;
+        double x, y;
+        double xr, yr;
+        double tanx, tany;
+        double val1, val2;
+        double yk;
+        double vmu;
+        double cosrf, sinrf;
+        double teta;
+        double xt, yt, zt;
+        double rs;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+
+            xele2 = xele/2.;
+            xlin2 = xlin/2.;
+            x = 1250.5 - xele2;
+            y = ioff[2] - (xlin2 + ioff[1] - ioff[0]);
+            xr = x;
+            yr = y;
+            x = xr*lpsi2*deltax*DEGREES_TO_RADIANS;
+            y = yr*lpsi2*deltay*DEGREES_TO_RADIANS;
+            rs = EARTH_RADIUS + h;
+            tanx = Math.tan(x);
+            tany = Math.tan(y);
+            val1=1.+tanx*tanx;
+            val2=1.+(tany*tany)*((1.+a)*(1.+a));
+            yk=rs/EARTH_RADIUS;
+            if ((val1*val2) > ((yk*yk)/(yk*yk-1)))
+            {
+                latlon[indexLat][point] = Double.NaN;
+                latlon[indexLon][point] = Double.NaN;
+            }
+            else
+            {
+                vmu = (rs-(EARTH_RADIUS*(Math.sqrt((yk*yk)-
+                                  (yk*yk-1)*val1*val2))))/(val1*val2); 
+                cosrf = Math.cos(rflon*DEGREES_TO_RADIANS);
+                sinrf = Math.sin(rflon*DEGREES_TO_RADIANS);
+                xt = (rs*cosrf) + (vmu*(tanx*sinrf - cosrf));
+                yt = (rs*sinrf) - (vmu*(tanx*cosrf + sinrf));
+                zt = vmu*tany/Math.cos(x);
+                teta = Math.asin(zt/rp);
+                xfi = (Math.atan(((Math.tan(teta))*EARTH_RADIUS)/rp))*
+                         RADIANS_TO_DEGREES;
+                xla=-Math.atan(yt/xt)*RADIANS_TO_DEGREES;
+                
+                // change longitude for correct subpoint
+                xla = xla + sublon;
+
+                //  put longitude into East Positive (form)
+                if (isEastPositive) xla = -xla;
+    
+                latlon[indexLat][point] = xfi;
+                latlon[indexLon][point] = xla;
+            }  // end lat/lon point calculation 
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double y;
+        double x1, y1;
+        double xfi, xla;
+        double rom;
+        double r1, r2;
+        double coslo, sinlo;
+        double teta;
+        double xt, yt, zt;
+        double px, py;
+        double rs;
+        double reph, rpph;
+        double xr, yr;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            x1 = latlon[indexLat][point];
+
+            // expects positive East Longitude.
+            y1 = isEastPositive 
+                     ?  latlon[indexLon][point]
+                     : -latlon[indexLon][point];
+
+            // if in cartesian coords, transform to lat/lon
+            if (itype == 1)
+            {
+                y = latlon[indexLon][point];
+                // NXYZLL(x,y,z,zlat,zlon);
+                y1 = -y1;
+            }
+
+            // correct for sublon
+            y1 = y1 + sublon;
+            xfi = x1*DEGREES_TO_RADIANS;
+            xla = y1*DEGREES_TO_RADIANS;
+            rom = 
+                (EARTH_RADIUS*rp)/
+                    Math.sqrt(
+                        rp*rp*Math.cos(xfi)*Math.cos(xfi)+
+                        EARTH_RADIUS*EARTH_RADIUS*Math.sin(xfi)*Math.sin(xfi));
+            y = Math.sqrt(h*h+rom*rom-2*h*rom*Math.cos(xfi)*Math.cos(xla));
+            r1 = y*y + rom*rom;
+            r2 = h*h;
+            if (r1 > r2)  // invalid point
+            {
+                linele[indexLine][point] = Double.NaN;
+                linele[indexEle][point] = Double.NaN;
+            }
+            else          // calculate line an element
+            {
+                rs    = EARTH_RADIUS + h;
+                reph  = EARTH_RADIUS;
+                rpph  = rp;
+                coslo = Math.cos(rflon*DEGREES_TO_RADIANS);
+                sinlo = Math.sin(rflon*DEGREES_TO_RADIANS);
+                teta  = Math.atan((rpph/reph)*Math.tan(xfi));
+                xt    = reph*Math.cos(teta)*Math.cos(xla);
+                yt    = reph*Math.cos(teta)*Math.sin(xla);
+                zt    = rpph*Math.sin(teta);
+
+                px    = Math.atan((coslo*(yt-rs*sinlo)-(xt-rs*coslo)*sinlo)/
+                               (sinlo*(yt-rs*sinlo)+(xt-rs*coslo)*coslo));
+                py    = Math.atan(zt*((Math.tan(px)*sinlo-
+                                    coslo)/(xt-rs*coslo))*Math.cos(px));
+                px = px*RADIANS_TO_DEGREES;
+                py = py*RADIANS_TO_DEGREES;
+                xr = px/(deltax*lpsi2);
+                yr = py/(deltay*lpsi2);
+                xr = 1250.5-xr;
+                yr = yr + ioff[2] + ioff[1] - ioff[0];
+                xr = xr*2;
+                yr = 5000-yr*2;
+                linele[indexLine][point] = yr;
+                linele[indexEle][point] = xr;
+
+            }  // end calculations
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) 
+    {
+
+        double xele, xlin;
+        double xele2, xlin2;
+        double xfi, xla;
+        double x, y;
+        double xr, yr;
+        double tanx, tany;
+        double val1, val2;
+        double yk;
+        double vmu;
+        double cosrf, sinrf;
+        double teta;
+        double xt, yt, zt;
+        double rs;
+
+        int number = linele[0].length;
+        float[][] latlon = new float[2][number];
+
+        // Convert array to Image coordinates for computations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+
+            xele2 = xele/2.;
+            xlin2 = xlin/2.;
+            x = 1250.5 - xele2;
+            y = ioff[2] - (xlin2 + ioff[1] - ioff[0]);
+            xr = x;
+            yr = y;
+            x = xr*lpsi2*deltax*DEGREES_TO_RADIANS;
+            y = yr*lpsi2*deltay*DEGREES_TO_RADIANS;
+            rs = EARTH_RADIUS + h;
+            tanx = Math.tan(x);
+            tany = Math.tan(y);
+            val1=1.+tanx*tanx;
+            val2=1.+(tany*tany)*((1.+a)*(1.+a));
+            yk=rs/EARTH_RADIUS;
+            if ((val1*val2) > ((yk*yk)/(yk*yk-1)))
+            {
+                latlon[indexLat][point] = Float.NaN;
+                latlon[indexLon][point] = Float.NaN;
+            }
+            else
+            {
+                vmu = (rs-(EARTH_RADIUS*(Math.sqrt((yk*yk)-
+                                  (yk*yk-1)*val1*val2))))/(val1*val2); 
+                cosrf = Math.cos(rflon*DEGREES_TO_RADIANS);
+                sinrf = Math.sin(rflon*DEGREES_TO_RADIANS);
+                xt = (rs*cosrf) + (vmu*(tanx*sinrf - cosrf));
+                yt = (rs*sinrf) - (vmu*(tanx*cosrf + sinrf));
+                zt = vmu*tany/Math.cos(x);
+                teta = Math.asin(zt/rp);
+                xfi = (Math.atan(((Math.tan(teta))*EARTH_RADIUS)/rp))*
+                         RADIANS_TO_DEGREES;
+                xla=-Math.atan(yt/xt)*RADIANS_TO_DEGREES;
+                
+                // change longitude for correct subpoint
+                xla = xla + sublon;
+
+                //  put longitude into East Positive (form)
+                if (isEastPositive) xla = -xla;
+    
+                latlon[indexLat][point] = (float) xfi;
+                latlon[indexLon][point] = (float) xla;
+            }  // end lat/lon point calculation 
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) 
+    {
+        double y;
+        double x1, y1;
+        double xfi, xla;
+        double rom;
+        double r1, r2;
+        double coslo, sinlo;
+        double teta;
+        double xt, yt, zt;
+        double px, py;
+        double rs;
+        double reph, rpph;
+        double xr, yr;
+
+        int number = latlon[0].length;
+        float[][] linele = new float[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            x1 = latlon[indexLat][point];
+
+            // expects positive East Longitude.
+            y1 = isEastPositive 
+                     ?  latlon[indexLon][point]
+                     : -latlon[indexLon][point];
+
+            // if in cartesian coords, transform to lat/lon
+            if (itype == 1)
+            {
+                y = latlon[indexLon][point];
+                // NXYZLL(x,y,z,zlat,zlon);
+                y1 = -y1;
+            }
+
+            // correct for sublon
+            y1 = y1 + sublon;
+            xfi = x1*DEGREES_TO_RADIANS;
+            xla = y1*DEGREES_TO_RADIANS;
+            rom = 
+                (EARTH_RADIUS*rp)/
+                    Math.sqrt(
+                        rp*rp*Math.cos(xfi)*Math.cos(xfi)+
+                        EARTH_RADIUS*EARTH_RADIUS*Math.sin(xfi)*Math.sin(xfi));
+            y = Math.sqrt(h*h+rom*rom-2*h*rom*Math.cos(xfi)*Math.cos(xla));
+            r1 = y*y + rom*rom;
+            r2 = h*h;
+            if (r1 > r2)  // invalid point
+            {
+                linele[indexLine][point] = Float.NaN;
+                linele[indexEle][point] = Float.NaN;
+            }
+            else          // calculate line an element
+            {
+                rs    = EARTH_RADIUS + h;
+                reph  = EARTH_RADIUS;
+                rpph  = rp;
+                coslo = Math.cos(rflon*DEGREES_TO_RADIANS);
+                sinlo = Math.sin(rflon*DEGREES_TO_RADIANS);
+                teta  = Math.atan((rpph/reph)*Math.tan(xfi));
+                xt    = reph*Math.cos(teta)*Math.cos(xla);
+                yt    = reph*Math.cos(teta)*Math.sin(xla);
+                zt    = rpph*Math.sin(teta);
+
+                px    = Math.atan((coslo*(yt-rs*sinlo)-(xt-rs*coslo)*sinlo)/
+                               (sinlo*(yt-rs*sinlo)+(xt-rs*coslo)*coslo));
+                py    = Math.atan(zt*((Math.tan(px)*sinlo-
+                                    coslo)/(xt-rs*coslo))*Math.cos(px));
+                px = px*RADIANS_TO_DEGREES;
+                py = py*RADIANS_TO_DEGREES;
+                xr = px/(deltax*lpsi2);
+                yr = py/(deltay*lpsi2);
+                xr = 1250.5-xr;
+                yr = yr + ioff[2] + ioff[1] - ioff[0];
+                xr = xr*2;
+                yr = 5000-yr*2;
+                linele[indexLine][point] = (float) yr;
+                linele[indexEle][point] = (float) xr;
+
+            }  // end calculations
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/MSGTnav.java b/edu/wisc/ssec/mcidas/MSGTnav.java
new file mode 100644
index 0000000..c0dc899
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/MSGTnav.java
@@ -0,0 +1,425 @@
+//
+// MSGTnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for MSG type nav. This code was modified
+ * from the original FORTRAN code (nvxmsgt.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Tom Whittaker
+ */
+public final class MSGTnav extends AREAnav 
+{
+
+    private boolean isEastPositive = true;
+
+    final double NOMORB=42164.;   // nominal radial distance of satellite (km)
+    final double EARTH_RADIUS=6378.155; // earth equatorial radius (km)
+
+    int itype;
+    double h;
+    double a;
+    double rp;
+    double cdr;
+    double crd;
+    double rs, yk;
+    double deltax;
+    double deltay;
+    int LOFF, COFF, LFAC, CFAC;
+    int[] ioff = new int[3];
+
+    boolean first = true;
+    int count = 0;
+    double sublon = 0.0;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the MSGT nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a MSGT type.
+     */
+    public MSGTnav (int[] iparms) throws IllegalArgumentException {
+
+        if (iparms[0] != MSGT ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        itype = 2;
+
+        LOFF = iparms[1];
+        COFF = iparms[2];
+        LFAC = iparms[3];
+        CFAC = iparms[4];
+        sublon = iparms[5]/10000.;
+        if (isEastPositive) sublon = -sublon; 
+
+        h = NOMORB - EARTH_RADIUS;
+        rs = EARTH_RADIUS + h;
+        yk = rs/EARTH_RADIUS;
+        a = 1./297.;
+        rp = EARTH_RADIUS / (1. + a);
+        crd = 180. / Math.PI;
+        cdr = Math.PI / 180.;
+
+        deltax = 1.0/(CFAC/1000000.);
+        deltay = 1.0/(LFAC/1000000.);
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) {
+
+        int number = linele[0].length;
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+        double[][] latlon = imglinele;
+
+        double xlin, xele, xr, yr, tanx, tany, v1, v2;
+        double vmu, xt, yt, zt, teta, xlat, xlon;
+
+        for (int point=0; point < number; point++) 
+        {
+
+            if (Double.isNaN(imglinele[indexLine][point]) || 
+                Double.isNaN(imglinele[indexEle][point])) {
+                continue;
+            }
+
+            xlin = 11136.0 - imglinele[indexLine][point] + 1.0;
+            xele = 11136.0 - imglinele[indexEle][point] + 1.0;
+
+            xlin = (xlin + 2.0) / 3.0;
+            xele = (xele + 2.0) / 3.0;
+
+
+            xr = xele - (COFF/10.);
+            yr = xlin - (LOFF/10.);
+            xr = xr*deltax*cdr;
+            yr = yr*deltay*cdr;
+            tanx = Math.tan(xr);
+            tany = Math.tan(yr);
+
+            v1 = 1. + tanx*tanx;
+            v2 = 1. + (tany*tany)*((1.+a)*(1.+a));
+
+            if (v1*v2 > ((yk*yk)/(yk*yk-1))) {
+               xlat = Double.NaN; 
+               xlon =  Double.NaN;
+            } else {
+
+               vmu = (rs - EARTH_RADIUS*Math.sqrt(yk*yk-(yk*yk-1)*v1*v2))/(v1*v2);
+               xt = rs - vmu;
+               yt = - vmu*tanx;
+               zt = vmu * tany/Math.cos(xr);
+               teta = Math.asin(zt/rp);
+
+               xlat = Math.atan(Math.tan(teta)*EARTH_RADIUS/rp) * crd;
+               xlon = Math.atan(yt/xt) * crd;
+
+            }  
+
+            //  put longitude into East Positive (form)
+            xlon = xlon + sublon;
+            if (!isEastPositive) xlon = -xlon;
+
+            latlon[indexLat][point] = xlat;
+            latlon[indexLon][point] = xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) {
+       
+      int number = latlon[0].length;
+      double[][] linele = new double[2][number];
+      double xfi, xla, rom, y, r1, r2, teta, xt, yt, zt;
+      double px, py, xr, yr, xele, xlin;
+      double xlat, xlon;
+
+      if (first) {
+       //System.out.println("####   first time...");
+       first = false;
+      }
+
+      for (int point=0; point < number; point++) 
+      {
+
+          if (Double.isNaN(latlon[indexLat][point]) || 
+              Double.isNaN(latlon[indexLon][point])) {
+              linele[indexLine][point] = Double.NaN;
+              linele[indexEle][point] = Double.NaN;
+              continue;
+          }
+
+          xlat = latlon[indexLat][point];
+
+          // expects positive East Longitude.
+          xlon = isEastPositive 
+                   ?  latlon[indexLon][point]
+                   : -latlon[indexLon][point];
+          xlon = xlon - sublon;
+
+
+          xfi = xlat*cdr;
+          xla = xlon*cdr;
+          rom = EARTH_RADIUS*rp/Math.sqrt(rp*rp*Math.cos(xfi) * 
+              Math.cos(xfi)+EARTH_RADIUS*EARTH_RADIUS * 
+              Math.sin(xfi)*Math.sin(xfi));
+
+          y = Math.sqrt(h*h + rom*rom - 2.*h*rom*Math.cos(xfi)*Math.cos(xla));
+          r1 = y*y + rom*rom;
+          r2 = h*h;
+    
+          if (r1 > r2) {
+            xlin = Double.NaN; 
+            xele =  Double.NaN;
+            linele[indexLine][point] = Double.NaN;
+            linele[indexEle][point] = Double.NaN;
+
+          } else {
+
+            teta = Math.atan((rp/EARTH_RADIUS) * Math.tan(xfi));
+            xt = EARTH_RADIUS * Math.cos(teta) * Math.cos(xla);
+            yt = EARTH_RADIUS * Math.cos(teta) * Math.sin(xla);
+            zt = rp * Math.sin(teta);
+
+            px = Math.atan(yt/(xt-rs));
+            py = Math.atan(-zt/(xt-rs)*Math.cos(px));
+            px = px*crd;
+            py = py*crd;
+            xr = px/deltax;
+            yr = py/deltay;
+            xele = (COFF/10.) + xr;
+            xlin = (LOFF/10.) + yr;
+
+            xlin = xlin * 3.0 - 2.0;
+            xele = xele * 3.0 - 2.0;
+            linele[indexLine][point] = 11136.0 - xlin + 1.0;
+            linele[indexEle][point] = 11136.0 - xele + 1.0;
+
+          }  // end calculations
+
+      } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) {
+
+        int number = linele[0].length;
+
+        // Convert array to Image coordinates for computations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+        float[][] latlon = imglinele;
+
+        double xlin, xele, xr, yr, tanx, tany, v1, v2;
+        double vmu, xt, yt, zt, teta, xlat, xlon;
+
+        for (int point=0; point < number; point++) 
+        {
+
+            if (Float.isNaN(imglinele[indexLine][point]) || 
+                Float.isNaN(imglinele[indexEle][point])) {
+                continue;
+            }
+
+            xlin = 11136.0 - imglinele[indexLine][point] + 1.0;
+            xele = 11136.0 - imglinele[indexEle][point] + 1.0;
+
+            xlin = (xlin + 2.0) / 3.0;
+            xele = (xele + 2.0) / 3.0;
+
+            xr = xele - (COFF/10.);
+            yr = xlin - (LOFF/10.);
+            xr = xr*deltax*cdr;
+            yr = yr*deltay*cdr;
+            tanx = Math.tan(xr);
+            tany = Math.tan(yr);
+
+            v1 = 1. + tanx*tanx;
+            v2 = 1. + (tany*tany)*((1.+a)*(1.+a));
+
+            if (v1*v2 > ((yk*yk)/(yk*yk-1))) {
+               xlat = Float.NaN; 
+               xlon =  Float.NaN;
+            } else {
+
+               vmu = (rs - EARTH_RADIUS*Math.sqrt(yk*yk-(yk*yk-1)*v1*v2))/(v1*v2);
+               xt = rs - vmu;
+               yt = - vmu*tanx;
+               zt = vmu * tany/Math.cos(xr);
+               teta = Math.asin(zt/rp);
+
+               xlat = Math.atan(Math.tan(teta)*EARTH_RADIUS/rp) * crd;
+               xlon = Math.atan(yt/xt) * crd;
+
+            }  
+
+            //  put longitude into East Positive (form)
+            xlon = xlon + sublon;
+            if (!isEastPositive) xlon = -xlon;
+
+            latlon[indexLat][point] = (float) xlat;
+            latlon[indexLon][point] = (float) xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) {
+       
+      int number = latlon[0].length;
+      float[][] linele = new float[2][number];
+      double xfi, xla, rom, y, r1, r2, teta, xt, yt, zt;
+      double px, py, xr, yr, xele, xlin;
+      double xlat, xlon;
+
+      if (first) {
+       //System.out.println("####   first time...");
+       first = false;
+      }
+
+      for (int point=0; point < number; point++) 
+      {
+
+          if (Float.isNaN(latlon[indexLat][point]) || 
+              Float.isNaN(latlon[indexLon][point])) {
+              linele[indexLine][point] = Float.NaN;
+              linele[indexEle][point] = Float.NaN;
+              continue;
+          }
+
+          xlat = latlon[indexLat][point];
+
+          // expects positive East Longitude.
+          xlon = isEastPositive 
+                   ?  latlon[indexLon][point]
+                   : -latlon[indexLon][point];
+          xlon = xlon - sublon;
+
+
+          xfi = xlat*cdr;
+          xla = xlon*cdr;
+          rom = EARTH_RADIUS*rp/Math.sqrt(rp*rp*Math.cos(xfi) * 
+              Math.cos(xfi)+EARTH_RADIUS*EARTH_RADIUS * 
+              Math.sin(xfi)*Math.sin(xfi));
+
+          y = Math.sqrt(h*h + rom*rom - 2.*h*rom*Math.cos(xfi)*Math.cos(xla));
+          r1 = y*y + rom*rom;
+          r2 = h*h;
+    
+          if (r1 > r2) {
+            xlin = Float.NaN; 
+            xele =  Float.NaN;
+            linele[indexLine][point] = Float.NaN;
+            linele[indexEle][point] = Float.NaN;
+
+          } else {
+
+            teta = Math.atan((rp/EARTH_RADIUS) * Math.tan(xfi));
+            xt = EARTH_RADIUS * Math.cos(teta) * Math.cos(xla);
+            yt = EARTH_RADIUS * Math.cos(teta) * Math.sin(xla);
+            zt = rp * Math.sin(teta);
+
+            px = Math.atan(yt/(xt-rs));
+            py = Math.atan(-zt/(xt-rs)*Math.cos(px));
+            px = px*crd;
+            py = py*crd;
+            xr = px/deltax;
+            yr = py/deltay;
+            xele = (COFF/10.) + xr;
+            xlin = (LOFF/10.) + yr;
+
+            xlin = xlin * 3.0 - 2.0;
+            xele = xele * 3.0 - 2.0;
+            linele[indexLine][point] = (float)(11136.0 - xlin + 1.0);
+            linele[indexEle][point] = (float)(11136.0 - xele + 1.0);
+
+          }  // end calculations
+
+      } // end point loop
+
+      // Return in 'File' coordinates
+      return imageCoordToAreaCoord(linele, linele);
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/MSGnav.java b/edu/wisc/ssec/mcidas/MSGnav.java
new file mode 100644
index 0000000..a7171fd
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/MSGnav.java
@@ -0,0 +1,415 @@
+//
+// MSGnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+This code was modified from the original Fortran code on the
+McIDAS system.  The code in this file is Copyright(C) 2011 by Tom
+Whittaker & Don Murray.  It is designed to be used with the VisAD system
+for interactive analysis and visualization of numerical data.
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for MSG type nav. This code was modified
+ * from the original FORTRAN code (nvxmsgt.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class MSGnav extends AREAnav 
+{
+
+    private boolean isEastPositive = true;
+
+    final double NOMORB=42164.;   // nominal radial distance of satellite (km)
+    final double EARTH_RADIUS=6378.169; // earth equatorial radius (km)
+
+    int itype;
+    double h;
+    double a;
+    double rp;
+    double cdr;
+    double crd;
+    double rs, yk;
+    double deltax;
+    double deltay;
+    int[] ioff = new int[3];
+
+    int count = 0;
+    double sublon = 0.0;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the MSG nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a MSG type.
+     */
+    public MSGnav (int[] iparms) throws IllegalArgumentException {
+
+        if (iparms[0] != MSG ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        itype = 2;
+
+        h = NOMORB - EARTH_RADIUS;
+        rs = EARTH_RADIUS + h;
+        yk = rs/EARTH_RADIUS;
+        a = 1./297.;
+        rp = EARTH_RADIUS / (1. + a);
+        crd = 180. / Math.PI;
+        cdr = Math.PI / 180.;
+        //sublon = McIDASUtil.integerLatLonToDouble(iparms[1]); 
+        sublon = iparms[1]/10000.;
+        if (isEastPositive) sublon = -sublon; 
+
+        deltax = 17.832/3712.;
+        deltay = 17.832/3712.;
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) {
+
+
+        int number = linele[0].length;
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+        double[][] latlon = imglinele;
+
+
+        double xlin, xele, xr, yr, tanx, tany, v1, v2;
+        double vmu, xt, yt, zt, teta, xlat, xlon;
+
+        for (int point=0; point < number; point++) 
+        {
+            if (Double.isNaN(imglinele[indexLine][point]) || 
+                Double.isNaN(imglinele[indexEle][point])) {
+                continue;
+            }
+
+            xlin = 3713. - imglinele[indexLine][point]/3.0;
+            xele = 3713. - imglinele[indexEle][point]/3.0;
+
+            xr = xele - 1856.;
+            yr = xlin - 1856.;
+            xr = xr*deltax*cdr;
+            yr = yr*deltay*cdr;
+            tanx = Math.tan(xr);
+            tany = Math.tan(yr);
+
+            v1 = 1. + tanx*tanx;
+            v2 = 1. + (tany*tany)*((1.+a)*(1.+a));
+
+            if (yk*yk-(yk*yk-1)*v1*v2 <= 0.0) { 
+               xlat = Double.NaN; 
+               xlon =  Double.NaN;
+            } else {
+
+               vmu = (rs - EARTH_RADIUS*Math.sqrt(yk*yk-(yk*yk-1)*v1*v2))/(v1*v2);
+               xt = rs - vmu;
+               yt = - vmu*tanx;
+               zt = vmu * tany/Math.cos(xr);
+               teta = Math.asin(zt/rp);
+
+               xlat = Math.atan(Math.tan(teta)*EARTH_RADIUS/rp) * crd;
+               xlon = Math.atan(yt/xt) * crd;
+
+            }  
+
+            //  put longitude into East Positive (form)
+            xlon = xlon + sublon;
+            if (!isEastPositive) xlon = -xlon;
+
+            latlon[indexLat][point] = xlat;
+            latlon[indexLon][point] = xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) {
+       
+      int number = latlon[0].length;
+      double[][] linele = new double[2][number];
+      double xfi, xla, rom, y, r1, r2, teta, xt, yt, zt;
+      double px, py, xr, yr, xele, xlin;
+      double xlat, xlon;
+
+      for (int point=0; point < number; point++) 
+      {
+          if (Double.isNaN(latlon[indexLat][point]) || 
+              Double.isNaN(latlon[indexLon][point])) {
+              linele[indexLine][point] = Double.NaN;
+              linele[indexEle][point] = Double.NaN;
+              continue;
+          }
+
+          xlat = latlon[indexLat][point];
+
+          // expects positive East Longitude.
+          xlon = isEastPositive 
+                   ?  latlon[indexLon][point]
+                   : -latlon[indexLon][point];
+          xlon = xlon - sublon;
+
+
+          xfi = xlat*cdr;
+          xla = xlon*cdr;
+          rom = EARTH_RADIUS*rp/Math.sqrt(rp*rp*Math.cos(xfi) * 
+              Math.cos(xfi)+EARTH_RADIUS*EARTH_RADIUS * 
+              Math.sin(xfi)*Math.sin(xfi));
+
+          y = Math.sqrt(h*h + rom*rom - 2.*h*rom*Math.cos(xfi)*Math.cos(xla));
+          r1 = y*y + rom*rom;
+          r2 = h*h;
+    
+          if (r1 > r2) {
+            xlin = Double.NaN; 
+            xele =  Double.NaN;
+            linele[indexLine][point] = Double.NaN;
+            linele[indexEle][point] = Double.NaN;
+
+          } else {
+
+            teta = Math.atan((rp/EARTH_RADIUS) * Math.tan(xfi));
+            xt = EARTH_RADIUS * Math.cos(teta) * Math.cos(xla);
+            yt = EARTH_RADIUS * Math.cos(teta) * Math.sin(xla);
+            zt = rp * Math.sin(teta);
+
+            px = Math.atan(yt/(xt-rs));
+            py = Math.atan(-zt/(xt-rs)*Math.cos(px));
+            px = px*crd;
+            py = py*crd;
+            xr = px/deltax;
+            yr = py/deltay;
+            xele = 1857. - xr;
+            xlin = 1857. - yr;
+
+            xlin = 3713.0 - xlin;
+            xele = 3713.0 - xele;
+            xlin = 3. * 3712 - 3. * xlin + 3;
+            xele = 3. * 3712 - 3. * xele + 3;
+
+            linele[indexLine][point] = xlin - 1;
+            linele[indexEle][point] = xele - 1;
+
+          }  // end calculations
+
+      } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) {
+
+        int number = linele[0].length;
+
+        // Convert array to Image coordinates for computations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+        float[][] latlon = imglinele;
+
+        double xlin, xele, xr, yr, tanx, tany, v1, v2;
+        double vmu, xt, yt, zt, teta, xlat, xlon;
+
+        for (int point=0; point < number; point++) 
+        {
+
+            if (Float.isNaN(imglinele[indexLine][point]) || 
+                Float.isNaN(imglinele[indexEle][point])) {
+                continue;
+            }
+            xlin = 3713. - imglinele[indexLine][point]/3.0;
+            xele = 3713. - imglinele[indexEle][point]/3.0;
+
+            xr = xele - 1856.;
+            yr = xlin - 1856.;
+            xr = xr*deltax*cdr;
+            yr = yr*deltay*cdr;
+            tanx = Math.tan(xr);
+            tany = Math.tan(yr);
+
+            v1 = 1. + tanx*tanx;
+            v2 = 1. + (tany*tany)*((1.+a)*(1.+a));
+
+            if (yk*yk-(yk*yk-1)*v1*v2 <= 0.0) { 
+               xlat = Float.NaN; 
+               xlon =  Float.NaN;
+            } else {
+
+               vmu = (rs - EARTH_RADIUS*Math.sqrt(yk*yk-(yk*yk-1)*v1*v2))/(v1*v2);
+               xt = rs - vmu;
+               yt = - vmu*tanx;
+               zt = vmu * tany/Math.cos(xr);
+               teta = Math.asin(zt/rp);
+
+               xlat = Math.atan(Math.tan(teta)*EARTH_RADIUS/rp) * crd;
+               xlon = Math.atan(yt/xt) * crd;
+
+            }  
+
+            //  put longitude into East Positive (form)
+            xlon = xlon + sublon;
+            if (!isEastPositive) xlon = -xlon;
+
+            latlon[indexLat][point] = (float) xlat;
+            latlon[indexLon][point] = (float) xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) {
+       
+      int number = latlon[0].length;
+      float[][] linele = new float[2][number];
+      double xfi, xla, rom, y, r1, r2, teta, xt, yt, zt;
+      double px, py, xr, yr, xele, xlin;
+      double xlat, xlon;
+
+      for (int point=0; point < number; point++) 
+      {
+
+
+          if (Float.isNaN(latlon[indexLat][point]) || 
+              Float.isNaN(latlon[indexLon][point])) {
+              linele[indexLine][point] = Float.NaN;
+              linele[indexEle][point] = Float.NaN;
+              continue;
+          }
+          xlat = latlon[indexLat][point];
+
+          // expects positive East Longitude.
+          xlon = isEastPositive 
+                   ?  latlon[indexLon][point]
+                   : -latlon[indexLon][point];
+          xlon = xlon - sublon;
+
+
+          xfi = xlat*cdr;
+          xla = xlon*cdr;
+          rom = EARTH_RADIUS*rp/Math.sqrt(rp*rp*Math.cos(xfi) * 
+              Math.cos(xfi)+EARTH_RADIUS*EARTH_RADIUS * 
+              Math.sin(xfi)*Math.sin(xfi));
+
+          y = Math.sqrt(h*h + rom*rom - 2.*h*rom*Math.cos(xfi)*Math.cos(xla));
+          r1 = y*y + rom*rom;
+          r2 = h*h;
+    
+          if (r1 > r2) {
+            xlin = Float.NaN; 
+            xele =  Float.NaN;
+            linele[indexLine][point] = Float.NaN;
+            linele[indexEle][point] = Float.NaN;
+
+          } else {
+
+            teta = Math.atan((rp/EARTH_RADIUS) * Math.tan(xfi));
+            xt = EARTH_RADIUS * Math.cos(teta) * Math.cos(xla);
+            yt = EARTH_RADIUS * Math.cos(teta) * Math.sin(xla);
+            zt = rp * Math.sin(teta);
+
+            px = Math.atan(yt/(xt-rs));
+            py = Math.atan(-zt/(xt-rs)*Math.cos(px));
+            px = px*crd;
+            py = py*crd;
+            xr = px/deltax;
+            yr = py/deltay;
+            xele = 1857. - xr;
+            xlin = 1857. - yr;
+
+            xlin = 3713.0 - xlin;
+            xele = 3713.0 - xele;
+            xlin = 3. * 3712 - 3. * xlin + 3;
+            xele = 3. * 3712 - 3. * xele + 3;
+
+            linele[indexLine][point] = (float) (xlin - 1);
+            linele[indexEle][point] = (float) (xele - 1);
+
+          }  // end calculations
+
+      } // end point loop
+
+      // Return in 'File' coordinates
+      return imageCoordToAreaCoord(linele, linele);
+
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/McIDASException.java b/edu/wisc/ssec/mcidas/McIDASException.java
new file mode 100644
index 0000000..5e18cc9
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/McIDASException.java
@@ -0,0 +1,50 @@
+//
+// McIDASException.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * McIDASException class is to handle exceptions when dealing
+ * with McIDAS files or data.
+ *
+ * @author Don Murray - Unidata
+ */
+
+public class McIDASException extends Exception {
+
+  /**
+   * Constructs a McIDASException with no specified detail message.
+   */
+  public McIDASException() {super(); }
+
+  /**
+   * Constructs a McIDASException with the specified detail message.
+   *
+   * @param  s  the detail message.
+   */
+  public McIDASException(String s) {super(s); }
+
+}
diff --git a/edu/wisc/ssec/mcidas/McIDASUtil.java b/edu/wisc/ssec/mcidas/McIDASUtil.java
new file mode 100644
index 0000000..ad25782
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/McIDASUtil.java
@@ -0,0 +1,503 @@
+//
+// McIDASUtil.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import java.io.*;
+
+import edu.wisc.ssec.mcidas.AreaFile;
+
+
+/**
+ * Class for static McIDAS utility methods.  In many cases, these
+ * methods are the Java equivalents of  McIDAS library functions.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author Don Murray, Unidata
+ * @author Tom Whittaker, SSEC
+ */
+public final class McIDASUtil {
+
+  /** McIDAS missing value for 4-byte integers */
+  public static final int MCMISSING = 0x80808080;
+
+  /**
+   * Converts a packed integer (SIGN DDD MM SS) latitude/longitude to double.
+   * Java version of McIDAS <code>flalo</code> function except returns a
+   * double instead of a float.
+   *
+   * @param value  integer containing the packed data
+   * @return  double representation of value
+   */
+  public static double integerLatLonToDouble(int value) {
+    return mcPackedIntegerToDouble(value);
+  }
+
+  /**
+   * Converts a double latitude/longitude to a packed integer (SIGN DDD MM SS)
+   * Java version of McIDAS <code>ilalo</code> function.
+   *
+   * @param dvalue  double value of lat/lon
+   *
+   * @return  packed integer representation of value
+   */
+  public static int doubleLatLonToInteger(double dvalue) {
+    return mcDoubleToPackedInteger(dvalue);
+  }
+
+  /**
+   * Converts a packed integer (SIGN DDD/HH MM SS) latitude/longitude
+   * or time (hours) to double.
+   * Java replacements of McIDAS <code>flalo</code> and <code>ftime</code>
+   * functions except returns a double instead of a float.
+   *
+   * @param value  integer containing the packed data
+   * @return  double representation of value
+   */
+  public static double mcPackedIntegerToDouble(int value) {
+    int val = value < 0
+              ? -value
+              : value;
+    double dvalue = ((double)(val / 10000) +
+                     ((double)((val / 100) % 100)) / 60.0 +
+                     (double)(val % 100) / 3600.0);
+    return (value < 0)
+           ? -dvalue
+           : dvalue;
+  }
+
+  /**
+   * Converts a double latitude/longitude or time (hours) to a
+   * packed integer (SIGN DDD/HH MM SS). Java replacements of McIDAS
+   * <code>ilalo</code> and <code>m0itime</code> functions.
+   *
+   * @param dvalue  double value of lat/lon or time
+   *
+   * @return  packed integer representation of value
+   */
+  public static int mcDoubleToPackedInteger(double dvalue) {
+    double dval = dvalue < 0
+                  ? -dvalue
+                  : dvalue;
+    int j = (int)(3600.0 * dval + 0.5);
+    int value = 10000 * (j / 3600) + 100 * ((j / 60) % 60) + j % 60;
+    return (dvalue < 0.0)
+           ? -value
+           : value;
+  }
+
+  /**
+   * Calculate difference in minutes between two dates/times.  Java
+   * version of timdif.for
+   *
+   * @param     yrday1   Year/day of first time (yyddd or yyyyddd)
+   * @param     hms1     Hours/minutes/seconds of first time (hhmmss).
+   * @param     yrday2   Year/day of second time (yyddd).
+   * @param     hms2     Hours/minutes/seconds of second time (hhmmss).
+   *
+   * @return  The difference between the two times (time2 - time1),
+   *          in minutes. If the first time is greater than the second,
+   *          the result will be negative.
+   */
+  public static double timdif(int yrday1, int hms1, int yrday2, int hms2) {
+    long secs1 = mcDayTimeToSecs(yrday1, hms1);
+    long secs2 = mcDayTimeToSecs(yrday2, hms2);
+    return (double)(secs2 - secs1) / 60.;
+  }
+
+  /**
+   * Create a calendar to be used for mcDayTimeToSecs.
+   * Use this to minimize object creation overhead when calling the method
+   * many times.
+   *
+   * @return A calendar to use for mcDayTimeToSecs.
+   */
+  public static GregorianCalendar makeCalendarForDayTimeToSecs() {
+    GregorianCalendar cal = new GregorianCalendar();
+    cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+    cal.set(Calendar.ERA, GregorianCalendar.AD);
+    /*
+       allow us to specify # of days since the year began without having
+       worry about leap years and seconds since the day began, instead
+       of in the minute.  Saves on some calculations.
+    */
+    cal.setLenient(true);
+    return cal;
+  }
+
+
+
+  /**
+   *
+   * Convert day (yyddd or yyyyddd) and time (hhmmss) to seconds since
+   * the epoch (January 1, 1970, 00:00GMT).  Java version of 'mcdaytimetosecs'
+   * except it returns a long instead of an int.
+   *
+   * @param    yearday    year/day in either yyddd or yyyyddd format.
+   *                      Only works for years > 1900.
+   * @param    time       time in packed integer format (hhmmss)
+   *
+   * @return  seconds since the epoch
+   *
+   */
+  public static long mcDayTimeToSecs(int yearday, int time) {
+
+    return mcDayTimeToSecs(yearday, time, null);
+  }
+
+  /**
+   * Convert day (yyddd or yyyyddd) and time (hhmmss) to seconds since
+   * the epoch (January 1, 1970, 00:00GMT).  Java version of 'mcdaytimetosecs'
+   * except it returns a long instead of an int.
+   *
+   * @param    yearday    year/day in either yyddd or yyyyddd format.
+   *                      Only works for years > 1900.
+   * @param    time       time in packed integer format (hhmmss)
+   * @param    cal        If non-null then use this calendar to do the formatting.
+   *                      else create a new one. Note: The calendar you pass in should be
+   *                      one created from makeCalendarForDayTimeToSecs
+   *
+   * @return  seconds since the epoch
+   *
+   */
+  public static long mcDayTimeToSecs(int yearday, int time,
+                                     GregorianCalendar cal) {
+    //jeffmc: Add the cal parameter to this method
+    if (cal == null) {
+      cal = makeCalendarForDayTimeToSecs();
+    }
+
+    int year = ((yearday / 1000) % 1900) + 1900; // convert to yyyyddd first
+    int day = yearday % 1000;
+    double seconds = mcPackedIntegerToDouble(time) * 3600.;
+    cal.clear();
+    cal.set(Calendar.DAY_OF_YEAR, day);
+    cal.set(Calendar.YEAR, year);
+    int secs = ((int)Math.round(seconds * 1000)) / 1000;
+    cal.set(Calendar.SECOND, secs);
+    cal.set(Calendar.MILLISECOND, 0);
+    //jeffmc: Change:
+    //        return cal.getTime().getTime()/1000;
+    //to:
+    return cal.getTimeInMillis() / 1000;
+  }
+
+  /**
+   * Convert date (yymmdd or yyyymmdd) and hms (hhmmss) to seconds since
+   * the epoch (January 1, 1970, 00:00GMT).
+   *
+   * @param    date       year/day in yyymmdd format.
+   *                      Only works for years > 1900.
+   * @param    time       time in packed integer format (hhmmss)
+   *
+   * @return  seconds since the epoch
+   *
+   */
+  public static long mcDateHmsToSecs(int date, int time) {
+    int year = date / 10000;
+    if (year < 50) {
+      year = year + 2000;
+    }
+    else if (year < 1000) {
+      year = year + 1900;
+    }
+    int month = (date % 10000) / 100;
+    int day = date % 100;
+
+    /**
+     *   jeffmc: Comment these out?
+     * System.out.println("year = " + year);
+     * System.out.println("month = " + month);
+     * System.out.println("day = " + day);
+     */
+    double seconds = mcPackedIntegerToDouble(time) * 3600.;
+
+    GregorianCalendar cal = new GregorianCalendar();
+    cal.clear();
+    cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+    cal.set(Calendar.ERA, GregorianCalendar.AD);
+    cal.set(Calendar.YEAR, year);
+    cal.set(Calendar.MONTH, month - 1); // stupid Calendar.MONTH is 0 based
+    cal.set(Calendar.DAY_OF_MONTH, day);
+    int secs = ((int)Math.round(seconds * 1000)) / 1000;
+    cal.set(Calendar.SECOND, secs);
+    cal.set(Calendar.MILLISECOND, 0);
+    return cal.getTime().getTime() / 1000;
+  }
+
+  /**
+   * Convert seconds since the epoch (January 1, 1970, 00:00GMT) to
+   * day (yyyyddd) and time (hhmmss).  Java version of
+   * 'mcsecstodaytime' except it returns an int array instead of pointers
+   *
+   * @param    secs    seconds since the epoch
+   *
+   * @return  int[2] array with day (yyyyddd) as first element  and
+   *          time (hhmmss - packed integer) as second element.
+   */
+  public static int[] mcSecsToDayTime(long secs) {
+    int[] retvals = new int[2];
+    GregorianCalendar cal = new GregorianCalendar();
+    cal.clear();
+    cal.setTimeZone(TimeZone.getTimeZone("GMT"));
+    cal.setTime(new Date(secs * 1000));
+    retvals[0] = (cal.get(cal.YEAR) * 1000) + cal.get(cal.DAY_OF_YEAR);
+    retvals[1] = (cal.get(cal.HOUR_OF_DAY) * 10000) +
+                 (cal.get(cal.MINUTE) * 100) + cal.get(cal.SECOND);
+    return retvals;
+  }
+
+  /**
+   * Convert an HMS integer to a string of form hh:mm:ss.
+   * @param hms  integer hhmmss
+   * @return string representation
+   */
+  public static String mcHmsToStr(int hms) {
+    StringBuffer buf = new StringBuffer();
+    int hours = (int)hms / 10000;
+    int mins = (int)(hms % 10000) / 100;
+    int secs = hms % 100;
+    buf.append(padZero(hours, 2));
+    buf.append(":");
+    buf.append(padZero(mins, 2));
+    buf.append(":");
+    buf.append(padZero(secs, 2));
+    return buf.toString();
+  }
+
+  /**
+   * Left pad the given value with zeros up to the number of digits
+   *
+   * @param value The value.
+   * @param numDigits number of digits
+   * @return The String  represenation of the value, padded with
+   *         leading "0"-s if value < 10E(numDigits-1)
+   */
+  public static String padZero(int value, int numDigits) {
+    return padLeft(String.valueOf(value), numDigits, "0");
+  }
+
+  /**
+   * Pad the given string with padString on the left up to the given length.
+   *
+   * @param s               String to pad
+   * @param desiredLength   ending length
+   * @param padString       String to pad with (e.g, " ")
+   * @return  padded String
+   */
+  public static String padLeft(String s, int desiredLength,
+                               String padString) {
+    while(s.length() < desiredLength) {
+      s = padString + s;
+    }
+    return s;
+  }
+
+  /**
+   * Flip the bytes of an integer.
+   *
+   * @param val value to swap
+   *
+   * @return the flipped integer
+   */
+  public static int swbyt4(int val) {
+    int[] vals = new int[] {val};
+    flip(vals, 0, 0);
+    return vals[0];
+  }
+
+  /**
+   * Flip the bytes of an integer array.  Java version of 'm0swbyt4'.
+   *
+   * @param array   array of integers to be flipped
+   * @param first   starting element of the array
+   * @param num     number of values to swap
+   */
+  public static void swbyt4(int[] array, int first, int num) {
+    flip(array, first, first + num - 1);
+  }
+
+  /**
+   * Flip the bytes of an integer array.  Java version of 'm0swbyt4'.
+   *
+   * @param array array of integers to be flipped
+   * @param first starting element of the array
+   * @param last last element of array to flip
+   *
+   */
+  public static void flip(int array[], int first, int last) {
+    int i, k;
+    for (i = first; i <= last; i++) {
+      k = array[i];
+      array[i] = ((k >>> 24) & 0xff) | ((k >>> 8) & 0xff00)
+                 | ((k & 0xff) << 24) | ((k & 0xff00) << 8);
+    }
+  }
+
+  /**
+   * convert four consequtive bytes into a (signed) int. This
+   * is useful in dealing with McIDAS data files.
+   *
+   * @param b	 array of 4 bytes
+   * @param off is the offset into the byte array
+   *
+   * @return the integer value
+   */
+  public static int bytesToInteger(byte[] b, int off) {
+
+    int k = (b[off] << 24) + ((b[off + 1] << 16) & 0xff0000) +
+            ((b[off + 2] << 8) & 0xff00) + ((b[off + 3] << 0) & 0xff);
+
+    return k;
+  }
+
+  /**
+   * convert consecutive bytes into a (signed) int array. This
+   * is useful in dealing with McIDAS data files.
+   *
+   * @param b array of bytes
+   * @param off is the offset into the byte array
+   * @param num number of integers to create
+   *
+   * @return the array of values as integers
+   */
+  public static int[] bytesToIntegerArray(byte[] b, int off, int num) {
+
+    int[] values = new int[num];
+    for (int i = 0; i < num; i++) {
+      byte[] bytes = new byte[4];
+      System.arraycopy(b, i * 4, bytes, 0, 4);
+      values[i] = bytesToInteger(bytes, 0);
+    }
+    return values;
+  }
+
+  /**
+   * convert signed int to a String representation.  This is useful
+   * in dealing with McIDAS data files. Java version of 'clit'.
+   *
+   * @param value  - integer representation of a string
+   *
+   * @return  String representation of the int
+   */
+  public static String intBitsToString(int value) {
+    byte[] bval = new byte[4];
+    bval[0] = (byte)((value & 0xff000000) >>> 24);
+    bval[1] = (byte)((value & 0x00ff0000) >>> 16);
+    bval[2] = (byte)((value & 0x0000ff00) >>> 8);
+    bval[3] = (byte)((value & 0x000000ff) >>> 0);
+    return new String(bval);
+  }
+
+  /**
+   * convert signed int array to a String representation.  This is useful
+   * in dealing with McIDAS data files.  Java version of 'movwc'.
+   *
+   * @param values  - integer array representation of a string
+   *
+   * @return  String representation of the int array
+   */
+  public static String intBitsToString(int[] values) {
+    StringBuffer sb = new StringBuffer();
+    for (int i = 0; i < values.length; i++)
+      sb.append(intBitsToString(values[i]));
+    return sb.toString();
+  }
+
+  /**
+   * Check to see if the int value is the representation of a
+   * string or not.  Java version of ischar_.c (sort of).
+   *
+   * @param value  integer representation
+   * @return true if the int represents a string
+   */
+  public static boolean isChar(int value) {
+    String valueString = intBitsToString(value);
+    char[] chars = valueString.toCharArray();
+    for (int i = 0; i < 4; i++) {
+      if (!Character.UnicodeBlock.of(chars[i]).equals(
+              Character.UnicodeBlock.BASIC_LATIN) || Character.isISOControl(
+                chars[i]))
+        return false;
+    }
+    return true;
+  }
+
+  /**
+   * Serialize an AreaFile object to disk
+   *
+   * @param filename - name of disk file to write to
+   * @param af 
+   * @return true if no Exception; false otherwise
+   */
+  public static boolean putAreaFile(String filename, AreaFile af) {
+    try {
+      OutputStream os = new FileOutputStream(filename);
+      ObjectOutput oo = new ObjectOutputStream(os);
+      oo.writeObject(af);
+      oo.close();
+      return true;
+
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return false;
+    }
+
+  }
+
+  /**
+   * De-serialize an AreaFile object from disk
+   *
+   * @param filename - name of disk file to read
+   *
+   * @return AreaFile if okay; null otherwise
+   */
+
+  public static AreaFile getAreaFile(String filename) {
+    try {
+      InputStream is = new FileInputStream(filename);
+      ObjectInput oi = new ObjectInputStream(is);
+      AreaFile af = (AreaFile)oi.readObject();
+      oi.close();
+      return af;
+
+    }
+    catch (Exception ei) {
+      ei.printStackTrace();
+      return null;
+    }
+
+  }
+}
+
diff --git a/edu/wisc/ssec/mcidas/PSnav.java b/edu/wisc/ssec/mcidas/PSnav.java
new file mode 100644
index 0000000..9548fa2
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/PSnav.java
@@ -0,0 +1,182 @@
+//
+// PSnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for Polar Stereographic (PS) type nav. This code was 
+ * modified from the original FORTRAN code (nvxps.dlm) on the McIDAS system. 
+ * It only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class PSnav extends AREAnav 
+{
+
+    int iwest;
+    int ihem;
+    double xrow;
+    double xcol;
+    double xpole;
+    double xlat1;
+    double xspace;
+    double xqlon;
+    double xblat;
+    double fac;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the PS nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a RECT type.
+     */
+    public PSnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+
+        if (iparms[0] != PS ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        xrow = iparms[1];
+        xcol = iparms[2];
+        int ipole = iparms[10];
+        if (ipole == 0) ipole = 900000;
+        ihem = 1;
+        if (ipole < 0) ihem = -1;
+        xpole = McIDASUtil.integerLatLonToDouble(ipole);
+        xlat1 = 
+           McIDASUtil.integerLatLonToDouble(ipole - iparms[3]) * 
+               DEGREES_TO_RADIANS;
+        xspace = iparms[4]/1000.;
+        xqlon = McIDASUtil.integerLatLonToDouble(iparms[5]);
+        double r = iparms[6]/1000.;
+        iwest = iparms[9];
+        if (iwest >= 0) iwest = 1;
+        xblat = r * Math.sin(xlat1)/(xspace*Math.tan(xlat1*.5));
+        fac = 1;
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xlon;
+        double xlat;
+        double xrlon, radius;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xldif = ihem * (imglinele[indexLine][point] - xrow)/xblat;
+            xedif = (xcol - imglinele[indexEle][point])/xblat;
+            xrlon = 0;
+            if (!(xldif == 0. && xedif == 0.))
+                xrlon = Math.atan2(xedif, xldif);
+            xlon = iwest * xrlon/DEGREES_TO_RADIANS + xqlon;
+            if (xlon > 180.) xlon -= 360.; 
+            if (xlon < -180.) xlon += 360.; 
+            radius = Math.sqrt(xldif*xldif + xedif*xedif);
+            if (Math.abs(radius) < 1.e-10)
+                xlat = ihem*90;
+            else
+                xlat = ihem*(90. - 2*Math.atan(
+                            Math.exp(Math.log(radius/fac)))/DEGREES_TO_RADIANS);
+            latlon[indexLat][point] = xlat;
+            latlon[indexLon][point] = (iwest == 1) ? -xlon  : xlon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double xlon;
+        double xlat;
+        double xrlon, xclat, xrlat;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xlat = latlon[indexLat][point];
+            // transform to McIDAS (west positive longitude) coordinates
+            xlon = (iwest == 1) 
+                   ? -latlon[indexLon][point]
+                   : latlon[indexLon][point];
+
+            xrlon = ihem*(xlon-xqlon);
+            if (xrlon > 180.) xrlon -= 360.;
+            if (xrlon < -180.) xrlon += 360.;
+            xrlon = iwest*xrlon*DEGREES_TO_RADIANS;
+            xclat = (xpole-xlat)*DEGREES_TO_RADIANS*.5;
+            xrlat = xblat*Math.tan(xclat);
+            linele[indexLine][point] = xrlat*Math.cos(xrlon) + xrow;
+            linele[indexEle][point] = -xrlat*Math.sin(xrlon) + xcol;
+           
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/RADRnav.java b/edu/wisc/ssec/mcidas/RADRnav.java
new file mode 100644
index 0000000..6ff22cf
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/RADRnav.java
@@ -0,0 +1,358 @@
+//
+// RADRnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for Radar (RADR) type nav. This code was modified
+ * from the original FORTRAN code (nvxradr.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class RADRnav extends AREAnav 
+{
+
+    private boolean isEastPositive = true;
+
+    final double EARTH_RADIUS=6371.23; // earth equatorial radius (km)
+    final int MISS = McIDASUtil.MCMISSING;
+
+    int itype;
+    double xrow;
+    double xcol;
+    double xlat;
+    double xlon;
+    double xrot;
+    double xblat;
+    double xblon;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the RADR nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a RADR type.
+     */
+    public RADRnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+
+/* No longer needed.  Kept for consistency with nvxradr.dlm
+        if (ifunc != 1) 
+        {
+            if (iparms[0] == XY ) itype = 1;
+            if (iparms[0] == LL ) itype = 2;
+            return;
+        }
+*/
+
+        if (iparms[0] != RADR ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        itype = 2;
+        xrow = iparms[1];
+        xcol = iparms[2];
+        xlat = McIDASUtil.integerLatLonToDouble(iparms[3]);
+        xlon = McIDASUtil.integerLatLonToDouble(iparms[4]);
+        double xspace = iparms[5]/1000.;
+        double yspace = xspace;
+        if (iparms[7] != 0 && iparms[7] != MISS)
+            yspace = iparms[7]/1000.;
+        xrot = -DEGREES_TO_RADIANS*iparms[6]/1000.;
+        xblat = EARTH_RADIUS*DEGREES_TO_RADIANS/xspace;
+        xblon = EARTH_RADIUS*DEGREES_TO_RADIANS/yspace;
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xdis;
+        double xangl;
+        double xange;
+        double ylat;
+        double ylon;
+
+        int number = linele[0].length;
+        //double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+        double[][] latlon = imglinele;
+
+        for (int point=0; point < number; point++) 
+        {
+           xldif = xrow - imglinele[indexLine][point];
+           xedif = xcol - imglinele[indexEle][point];
+           xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+           if (xdis > 0.001)
+           {
+               xangl = Math.atan2(xldif, xedif) - 90.*DEGREES_TO_RADIANS;
+               xange = Math.atan2(xldif, xedif) + 90.*DEGREES_TO_RADIANS;
+               xldif = xdis*Math.cos(xrot+xangl);
+               xedif = xdis*Math.sin(xrot+xange);
+            }
+            ylat = xlat + xldif/xblat;
+            ylon = xlon + xedif/xblon/Math.cos(ylat* DEGREES_TO_RADIANS);
+
+            // transform from McIDAS coordinates
+            if (isEastPositive) ylon = -ylon;
+            
+            latlon[indexLat][point] = ylat;
+            latlon[indexLon][point] = ylon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double zlat;
+        double zlon;
+        double xrlon;
+        double xrlat;
+        double xldif;
+        double xedif;
+        double xdis;
+        double xangl;
+        double xange;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+            if (Double.isNaN(latlon[indexLat][point]) || 
+                Double.isNaN(latlon[indexLon][point])) {
+                linele[indexLine][point] = Float.NaN;
+                linele[indexEle][point] = Float.NaN;
+                continue;
+            }
+
+            zlat = latlon[indexLat][point];
+
+            // transform to McIDAS (west positive longitude) coordinates
+            zlon = isEastPositive 
+                     ?  -latlon[indexLon][point]
+                     : latlon[indexLon][point];
+            if (zlon > 180) zlon -= 360;
+            if (zlon < -180) zlon += 360;
+            xrlon = zlon - xlon;
+            xrlat = zlat - xlat;
+            xldif = xblat*xrlat;
+            xedif = xrlon*xblon*Math.cos(zlat*DEGREES_TO_RADIANS);
+            xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+            if (xdis > .001) 
+            {
+                xangl = Math.atan2(xldif, xedif)-90*DEGREES_TO_RADIANS;
+                xange = Math.atan2(xldif, xedif)+90*DEGREES_TO_RADIANS;
+                xldif = xdis*Math.cos(-xrot+xangl);
+                xedif = xdis*Math.sin(-xrot+xange);
+            }
+            linele[indexLine][point] = xrow - xldif;
+            linele[indexEle][point] = xcol - xedif;
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xdis;
+        double xangl;
+        double xange;
+        double ylat;
+        double ylon;
+
+        int number = linele[0].length;
+        /*
+        float[][] latlon = new float[2][number];
+        float[] lats = latlon[indexLat];
+        float[] lons = latlon[indexLon];
+        */
+
+        // Convert array to Image coordinates for computations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+        float[][] latlon = imglinele;
+        float[] lats = latlon[indexLat];
+        float[] lons = latlon[indexLon];
+
+        float[] lines = imglinele[indexLine];
+        float[] eles = imglinele[indexEle];
+        for (int point=0; point < number; point++) 
+        {
+           xldif = xrow - lines[point];
+           xedif = xcol - eles[point];
+           xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+           if (xdis > 0.001)
+           {
+               xangl = Math.atan2(xldif, xedif) - 90.*DEGREES_TO_RADIANS;
+               xange = Math.atan2(xldif, xedif) + 90.*DEGREES_TO_RADIANS;
+               xldif = xdis*Math.cos(xrot+xangl);
+               xedif = xdis*Math.sin(xrot+xange);
+            }
+            ylat = xlat + xldif/xblat;
+            ylon = xlon + xedif/xblon/Math.cos(ylat* DEGREES_TO_RADIANS);
+
+            // transform from McIDAS coordinates
+            if (isEastPositive) ylon = -ylon;
+            
+            lats[point] = (float) ylat;
+            lons[point] = (float) ylon;
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) 
+    {
+        double zlat;
+        double zlon;
+        double xrlon;
+        double xrlat;
+        double xldif;
+        double xedif;
+        double xdis;
+        double xangl;
+        double xange;
+
+        int number = latlon[0].length;
+        float[][] linele = new float[2][number];
+        float[] lines = linele[indexLine];
+        float[] eles = linele[indexEle];
+        float[] lats = latlon[indexLat];
+        float[] lons = latlon[indexLon];
+
+        for (int point=0; point < number; point++) 
+        {
+            if (Float.isNaN(lats[point]) || 
+                Float.isNaN(lons[point])) {
+                linele[indexLine][point] = Float.NaN;
+                linele[indexEle][point] = Float.NaN;
+                continue;
+            }
+
+            zlat = lats[point];
+
+            // transform to McIDAS (west positive longitude) coordinates
+            zlon = isEastPositive 
+                     ? -lons[point]
+                     :  lons[point];
+            if (zlon > 180) zlon -= 360;
+            if (zlon < -180) zlon += 360;
+            xrlon = zlon - xlon;
+            xrlat = zlat - xlat;
+            xldif = xblat*xrlat;
+            xedif = xrlon*xblon*Math.cos(zlat*DEGREES_TO_RADIANS);
+            xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+            if (xdis > .001) 
+            {
+                xangl = Math.atan2(xldif, xedif)-90*DEGREES_TO_RADIANS;
+                xange = Math.atan2(xldif, xedif)+90*DEGREES_TO_RADIANS;
+                xldif = xdis*Math.cos(-xrot+xangl);
+                xedif = xdis*Math.sin(-xrot+xange);
+            }
+            lines[point] = (float) (xrow - xldif);
+            eles[point] = (float) (xcol - xedif);
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof RADRnav)) return false;
+        RADRnav that = (RADRnav) o;
+        return (super.equals(o) &&
+               that.xlat == xlat &&
+               that.xlon == xlon &&
+               that.xrow == xrow &&
+               that.xcol == xcol);
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/RECTnav.java b/edu/wisc/ssec/mcidas/RECTnav.java
new file mode 100644
index 0000000..75f1c59
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/RECTnav.java
@@ -0,0 +1,372 @@
+//
+// RECTnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for Radar (RECT) type nav. This code was modified
+ * from the original FORTRAN code (nvxrect.dlm) on the McIDAS system. It
+ * only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class RECTnav extends AREAnav 
+{
+
+    int itype;
+    int iwest;
+    double xrow;
+    double xcol;
+    double zslat;
+    double zslon;
+    double zdlat;
+    double zdlon;
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the RECT nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a RECT type.
+     */
+    public RECTnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+
+/* No longer needed.  Kept for consistency with nvxrect.dlm
+        if (ifunc != 1) 
+        {
+            if (iparms[0] == XY ) itype = 1;
+            if (iparms[0] == LL ) itype = 2;
+            return;
+        }
+*/
+
+        if (iparms[0] != RECT ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        itype = 2;
+
+        xrow = iparms[1];
+
+        int ipowlat = iparms[11];
+        if (ipowlat == 0) ipowlat = 4;
+        zslat = iparms[2]/Math.pow(10.,ipowlat);
+
+        xcol = iparms[3];
+
+        int ipowlon = iparms[12];
+        if (ipowlon == 0) ipowlon = 4;
+        zslon = iparms[4]/Math.pow(10.,ipowlon);
+
+        int ipowdlin = iparms[13];
+        if (ipowdlin == 0) ipowdlin = 4;
+        zdlat = iparms[5]/Math.pow(10.,ipowdlin);
+
+        int ipowdele = iparms[14];
+        if (ipowdele == 0) ipowdele = 4;
+        zdlon = iparms[6]/Math.pow(10.,ipowdele);
+
+        int ipowrad = iparms[15];
+        if (ipowrad == 0) ipowrad = 3;
+
+        int ipowecc = iparms[16];
+        if (ipowecc == 0) ipowecc = 6;
+
+        iwest = (iparms[10] >= 0) ? 1 : -1;
+
+        if (xcol == 1) {
+            zslon=zslon-180.0*iwest;
+        }
+        
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xlon;
+        double xlat;
+        double xele;
+        double xlin;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+
+            xldif = xrow - xlin;
+            if (xcol == 1) {
+               xedif = iwest * (xele-xcol);
+               xlon = zslon + 180*iwest-xedif*zdlon;
+            } else {
+               xedif = iwest * (xcol-xele);
+               xlon = zslon + xedif*zdlon;
+            }
+            xlat = zslat + xldif*zdlat;
+            if  (xlat > 90. || xlat < -90.)
+            {
+                xlat = Double.NaN;
+            }
+            if (xlon > (zslon+180) ||
+                xlon < (zslon-180)) {
+                xlon = Double.NaN;
+            }
+            if (!Double.isNaN(xlon)) {
+                if (xlon < -180.)
+                {
+                    xlon = xlon + 360.;
+                    //if (xlon < -180.) xlon = Double.NaN;
+                }
+                if (xlon > 180)
+                {
+                    xlon = xlon - 360.;
+                    //if (xlon > 180.) xlon = Double.NaN;
+                }
+            }
+            if (Double.isNaN(xlat) || Double.isNaN(xlon))
+            {
+                latlon[indexLat][point] = Double.NaN;
+                latlon[indexLon][point] = Double.NaN;
+            }
+            else
+            {
+                latlon[indexLat][point] = xlat;
+                latlon[indexLon][point] = (iwest == 1) ? -xlon  : xlon;
+            }
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double xlon;
+        double xlat;
+        double xlin;
+        double xele;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xlat = latlon[indexLat][point];
+
+            // transform to McIDAS (west positive longitude) coordinates
+            xlon = (iwest == 1) 
+                   ? -latlon[indexLon][point]
+                   : latlon[indexLon][point];
+            if (xlon > (zslon+180)) {
+                xlon = xlon-360;
+            } else if (xlon < zslon-180) {
+                xlon = xlon+360;
+            }
+            //if (iwest == -1 && xlon < zslon) xlon = xlon +360.;
+            xlin = xrow - (xlat - zslat)/zdlat;
+            if (xcol == 1) {
+                xele = xcol - (xlon - zslon-180*iwest)/(zdlon*iwest);
+            } else {
+                xele = xcol - (xlon - zslon)/(zdlon*iwest);
+            }
+            linele[indexLine][point] = xlin;
+            linele[indexEle][point]  = xele;
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public float[][] toLatLon(float[][] linele) 
+    {
+
+        double xldif;
+        double xedif;
+        double xlon;
+        double xlat;
+        double xele;
+        double xlin;
+
+        int number = linele[0].length;
+        float[][] latlon = new float[2][number];
+
+        // Convert array to Image coordinates for computations
+        float[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            xlin = imglinele[indexLine][point];
+            xele = imglinele[indexEle][point];
+
+            xldif = xrow - xlin;
+            if (xcol == 1) {
+               xedif = iwest * (xele-xcol);
+               xlon = zslon + 180*iwest-xedif*zdlon;
+            } else {
+               xedif = iwest * (xcol-xele);
+               xlon = zslon + xedif*zdlon;
+            }
+            xlat = zslat + xldif*zdlat;
+            if  (xlat > 90. || xlat < -90.)
+            {
+                xlat = Double.NaN;
+            }
+            if (xlon > (zslon+180) ||
+                xlon < (zslon-180)) {
+                xlon = Double.NaN;
+            }
+            if (!Double.isNaN(xlon)) {
+                if (xlon < -180.)
+                {
+                    xlon = xlon + 360.;
+                    //if (xlon < -180.) xlon = Double.NaN;
+                }
+                if (xlon > 180)
+                {
+                    xlon = xlon - 360.;
+                    //if (xlon > 180.) xlon = Double.NaN;
+                }
+            }
+            if (Double.isNaN(xlat) || Double.isNaN(xlon))
+            {
+                latlon[indexLat][point] = Float.NaN;
+                latlon[indexLon][point] = Float.NaN;
+            }
+            else
+            {
+                latlon[indexLat][point] = (float) xlat;
+                latlon[indexLon][point] = (float) ((iwest == 1) ? -xlon  : xlon);
+            }
+
+        } // end point for loop
+
+        return latlon;
+
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     *                    linele[indexLine][] is a line and linele[indexEle][]
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public float[][] toLinEle(float[][] latlon) 
+    {
+        double xlon;
+        double xlat;
+        double xlin;
+        double xele;
+
+        int number = latlon[0].length;
+        float[][] linele = new float[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+
+            xlat = latlon[indexLat][point];
+
+            // transform to McIDAS (west positive longitude) coordinates
+            xlon = (iwest == 1) 
+                   ? -latlon[indexLon][point]
+                   : latlon[indexLon][point];
+            if (xlon > (zslon+180)) {
+                xlon = xlon-360;
+            } else if (xlon < zslon-180) {
+                xlon = xlon+360;
+            }
+            //if (iwest == -1 && xlon < zslon) xlon = xlon +360.;
+            xlin = xrow - (xlat - zslat)/zdlat;
+            if (xcol == 1) {
+                xele = xcol - (xlon - zslon-180*iwest)/(zdlon*iwest);
+            } else {
+                xele = xcol - (xlon - zslon)/(zdlon*iwest);
+            }
+            linele[indexLine][point] = (float) xlin;
+            linele[indexEle][point]  = (float) xele;
+
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+
+}
diff --git a/edu/wisc/ssec/mcidas/SINUnav.java b/edu/wisc/ssec/mcidas/SINUnav.java
new file mode 100644
index 0000000..d402879
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/SINUnav.java
@@ -0,0 +1,293 @@
+//
+// SINUnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+public final class SINUnav extends AREAnav {
+  double drad, decc, r;
+  double xrow, xcol, xlat, xlon;
+  double xblat, xblon, xspace, yspace;
+  int itype, iwest;
+  boolean isEastPositive = true;
+
+
+  public SINUnav(int[] iparms) throws IllegalArgumentException {
+    
+     if (iparms[0] != SIN )
+        throw new IllegalArgumentException("Invalid navigation type "+
+                iparms[0]);
+
+     itype = 2;
+     xrow = iparms[1];
+     xcol = iparms[2];
+
+     xlat = McIDASUtil.integerLatLonToDouble(iparms[3]);
+     xlon = McIDASUtil.integerLatLonToDouble(iparms[4]);
+
+     xspace = iparms[5]/1000.;
+     yspace = xspace;
+
+     drad = iparms[6]/1000.;
+     r = drad;
+     decc = iparms[7]/1.e6;
+     iwest = iparms[9];
+     if (iwest >= 0) iwest = 1;
+     xblat = r*DEGREES_TO_RADIANS/xspace;
+     xblon = DEGREES_TO_RADIANS*r/yspace;
+
+     /*
+     for (int i=0; i<10; i++) {
+       System.out.println("####   i="+i+"  val = "+iparms[i]);
+     }
+     */
+   }
+
+   public double[][] toLatLon(double[][] linele) {
+     
+     double xlin, xele, xldif, xedif, xdis, ylat, ylon;
+
+     int number = linele[0].length;
+
+     double[][] latlon = new double[2][number];
+     double[][] imglinele = areaCoordToImageCoord(linele);
+
+     for (int point=0; point < number; point++ ){
+
+       xlin = imglinele[indexLine][point];
+       xele = imglinele[indexEle][point];
+
+       xldif = xrow - xlin;
+       xedif = xcol - xele;
+       xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+       if (xdis > .001) {
+         double xangl = Math.atan2(xldif, xedif) - 90.*DEGREES_TO_RADIANS;
+         double xange = Math.atan2(xldif,xedif) + 90.*DEGREES_TO_RADIANS;
+         xldif = xdis*Math.cos(xangl);
+         xedif = xdis*Math.sin(xange);
+       }
+
+       ylat = xlat + xldif/xblat;
+       ylon = iwest * xedif/xblon/Math.cos(ylat*DEGREES_TO_RADIANS);
+
+       if (Math.abs(ylon) > 180.0) {
+         latlon[indexLat][point] = Double.NaN;
+         latlon[indexLon][point] = Double.NaN;
+
+       } else {
+       
+         ylon = xlon + ylon;
+         if (ylon < -180.0) {
+           ylon = ylon + 360.0;
+         } else if (ylon > 180.0) {
+           ylon = ylon - 360.0;
+         }
+
+         if (Math.abs(ylat) > 90.0 || Math.abs(ylon) > 180.0) {
+           latlon[indexLat][point] = Double.NaN;
+           latlon[indexLon][point] = Double.NaN;
+         }
+
+         if (isEastPositive) ylon = -ylon;
+
+         latlon[indexLat][point] = ylat;
+         latlon[indexLon][point] = ylon;
+       }
+
+     }
+
+     return latlon;
+
+   }
+
+
+   public double[][] toLinEle(double[][] latlon) {
+     double xdis, xele, xlin, zlat, zlon, xrlon, xrlat, xldif, xedif;
+
+     int number = latlon[0].length;
+     double[][] linele = new double[2][number];
+
+     for (int point=0; point<number; point++) {
+       zlat = latlon[indexLat][point];
+       zlon = isEastPositive
+              ?  -latlon[indexLon][point]
+              :   latlon[indexLon][point];
+       if (Double.isNaN(zlat) || Double.isNaN(zlon) ||
+          (Math.abs(zlat) > 90.) ) {
+            xele = Double.NaN;
+            xlin = Double.NaN;
+       } else {
+          xrlon = iwest*(zlon - xlon);
+          if (xrlon > 180.) xrlon = xrlon - 360.;
+          if (xrlon < -180.) xrlon = xrlon + 360.;
+          xrlat = zlat - xlat;
+          xldif = xblat*xrlat;
+          xedif = xrlon*xblon*Math.cos(zlat * DEGREES_TO_RADIANS);
+          xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+          if (xdis > .001) {
+            double xangl = Math.atan2(xldif, xedif) - 90.*DEGREES_TO_RADIANS;
+            double xange = Math.atan2(xldif, xedif) + 90.*DEGREES_TO_RADIANS;
+            xldif = xdis * Math.cos(xangl);
+            xedif = xdis * Math.sin(xange);
+          }
+
+          xlin = xrow - xldif;
+          xele = xcol - xedif;
+        }
+
+        linele[indexLine][point] = xlin;
+        linele[indexEle][point] = xele;
+      }
+
+      return imageCoordToAreaCoord(linele, linele);
+   }
+
+   public float[][] toLatLon(float[][] linele) {
+     
+     double xlin, xele, xldif, xedif, xdis, ylat, ylon;
+
+     int number = linele[0].length;
+     float[][] latlon = new float[2][number];
+     float[][] imglinele = areaCoordToImageCoord(linele);
+
+     for (int point=0; point < number; point++ ){
+
+       xlin = imglinele[indexLine][point];
+       xele = imglinele[indexEle][point];
+
+       xldif = xrow - xlin;
+       xedif = xcol - xele;
+       xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+
+       if (xdis > .001) {
+         double xangl = Math.atan2(xldif, xedif) - 90.*DEGREES_TO_RADIANS;
+         double xange = Math.atan2(xldif,xedif) + 90.*DEGREES_TO_RADIANS;
+         xldif = xdis*Math.cos(xangl);
+         xedif = xdis*Math.sin(xange);
+       }
+
+       ylat = xlat + xldif/xblat;
+       ylon = iwest * xedif/xblon/Math.cos(ylat*DEGREES_TO_RADIANS);
+
+       if (Math.abs(ylon) > 180.0) {
+         latlon[indexLat][point] = Float.NaN;
+         latlon[indexLon][point] = Float.NaN;
+
+       } else {
+       
+         ylon = xlon + ylon;
+         if (ylon < -180.0) {
+           ylon = ylon + 360.0;
+         } else if (ylon > 180.0) {
+           ylon = ylon - 360.0;
+         }
+
+         if (Math.abs(ylat) > 90.0 || Math.abs(ylon) > 180.0) {
+           latlon[indexLat][point] = Float.NaN;
+           latlon[indexLon][point] = Float.NaN;
+         }
+
+         if (isEastPositive) ylon = -ylon;
+
+         latlon[indexLat][point] = (float) ylat;
+         latlon[indexLon][point] = (float) ylon;
+
+       }
+
+     }
+
+     return latlon;
+
+   }
+
+
+   public float[][] toLinEle(float[][] latlon) {
+     double xdis, xele, xlin, zlat, zlon, xrlon, xrlat, xldif, xedif;
+
+     int number = latlon[0].length;
+     float[][] linele = new float[2][number];
+
+     for (int point=0; point<number; point++) {
+       zlat = latlon[indexLat][point];
+       zlon = isEastPositive
+              ?  -latlon[indexLon][point]
+              :   latlon[indexLon][point];
+       if (Double.isNaN(zlat) || Double.isNaN(zlon) ||
+          (Math.abs(zlat) > 90.) ) {
+            xele = Double.NaN;
+            xlin = Float.NaN;
+       } else {
+          xrlon = iwest*(zlon - xlon);
+          if (xrlon > 180.) xrlon = xrlon - 360.;
+          if (xrlon < -180.) xrlon = xrlon + 360.;
+          xrlat = zlat - xlat;
+          xldif = xblat*xrlat;
+          xedif = xrlon*xblon*Math.cos(zlat * DEGREES_TO_RADIANS);
+          xdis = Math.sqrt(xldif*xldif + xedif*xedif);
+          if (xdis > .001) {
+            double xangl = Math.atan2(xldif, xedif) - 90.*DEGREES_TO_RADIANS;
+            double xange = Math.atan2(xldif, xedif) + 90.*DEGREES_TO_RADIANS;
+            xldif = xdis * Math.cos(xangl);
+            xedif = xdis * Math.sin(xange);
+          }
+
+          xlin = xrow - xldif;
+          xele = xcol - xedif;
+        }
+
+        linele[indexLine][point] = (float)xlin;
+        linele[indexEle][point] = (float)xele;
+      }
+
+      return imageCoordToAreaCoord(linele, linele);
+   }
+
+  public static void main(String[] a) {
+    int[] p = {1397313056, 10000, 10000, 421353,831951,1000,6378388,81992,0,0};
+    SINUnav sn = new SINUnav(p);
+    double [][] latlon = new double[2][1];
+    latlon[0][0] = 43.;
+    latlon[1][0] = -89.;
+    System.out.println("####  Doing double");
+    System.out.println("####  Original latlon="+latlon[0][0]+"  "+latlon[1][0]);
+    double[][] pix = sn.toLinEle(latlon);
+    System.out.println("####  pix="+pix[0][0]+"  "+pix[1][0]);
+
+    latlon = sn.toLatLon(pix);
+    System.out.println("####  latlon="+latlon[0][0]+"  "+latlon[1][0]);
+
+    System.out.println("####  Now doing float....");
+    float [][] flatlon = new float[2][1];
+    flatlon[0][0] = 43.f;
+    flatlon[1][0] = -89.f;
+    System.out.println("####  Original flatlon="+flatlon[0][0]+"  "+flatlon[1][0]);
+    float[][] fpix = sn.toLinEle(flatlon);
+    System.out.println("####  fpix="+fpix[0][0]+"  "+fpix[1][0]);
+
+    flatlon = sn.toLatLon(fpix);
+    System.out.println("####  flatlon="+flatlon[0][0]+"  "+flatlon[1][0]);
+
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/TANCnav.java b/edu/wisc/ssec/mcidas/TANCnav.java
new file mode 100644
index 0000000..d8c5821
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/TANCnav.java
@@ -0,0 +1,249 @@
+//
+// TANCnav.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas;
+
+/**
+ * Navigation class for tangent cone (TANC) type nav. This code was 
+ * modified from the original FORTRAN code (nvxtanc.dlm) on the McIDAS system. 
+ * It only supports latitude/longitude to line/element transformations (LL) 
+ * and vice/versa. Transform to 'XYZ' not implemented.
+ * @see <A HREF="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * @author  Don Murray
+ */
+public final class TANCnav extends AREAnav 
+{
+
+    int iwest;
+    int ihem;
+    double lin0;              // image line of pole
+    double ele0;              // image element of pole
+    double scale;             // km per unit image coordinate
+    double lon0;              // standard longitude
+    double lat0;              // standard latitude
+    double coscl;             // cosine of standard colatitude
+    double tancl;             // tangent of standard colatitude
+    double tancl2;            // tangent of standard colatitude/2
+    double mxtheta;           // limit of angle from std. lon on proj surface
+    private static double Erad = 6371.2;     // earth radius
+
+    /**
+     * Set up for the real math work.  Must pass in the int array
+     * of the TANC nav 'codicil'.
+     *
+     * @param iparms  the nav block from the image file
+     * @throws IllegalArgumentException
+     *           if the nav block is not a TANC type or the parameters are
+     *           bogus
+     */
+    public TANCnav (int[] iparms) 
+        throws IllegalArgumentException
+    {
+
+        if (iparms[0] != TANC ) 
+            throw new IllegalArgumentException("Invalid navigation type" + 
+                                                iparms[0]);
+        lin0  = iparms[1]/10000.;
+        ele0  = iparms[2]/10000.;
+        scale = iparms[3]/10000.;
+        lat0  = iparms[4]/10000.;
+        lon0  = iparms[5]/10000.;
+        if (scale <= 0 || lat0 <= -90. || lat0 >= 90. ||
+            lat0 == 0 || lon0 <= -180. || lon0 >= 180.)
+                throw new IllegalArgumentException("Invalid nav parameters");
+
+        lon0 = -lon0 * DEGREES_TO_RADIANS;   // convert to radians
+        double colat0;
+        if (lat0 < 0)
+            colat0 = Math.PI/2. + lat0*DEGREES_TO_RADIANS;
+        else
+            colat0 = Math.PI/2. - lat0*DEGREES_TO_RADIANS;
+
+        coscl = Math.cos(colat0);
+        tancl = Math.tan(colat0);
+        tancl2 = Math.tan(colat0/2.);
+        mxtheta = Math.PI*coscl;
+    }
+
+    /** converts from satellite coordinates to latitude/longitude
+     *
+     * @param  linele	  array of line/element pairs.  Where 
+     *                     linele[indexLine][] is a 'line' and 
+     *                     linele[indexEle][] is an element. These are in 
+     *                     'file' coordinates (not "image" coordinates.)
+     *
+     * @return latlon[][]  array of lat/long pairs. Output array is 
+     *                     latlon[indexLat][] of latitudes and 
+     *                     latlon[indexLon][] of longitudes.
+     *
+     */
+    public double[][] toLatLon(double[][] linele) 
+    {
+
+        double d_lin;
+        double d_ele;
+        double lon;
+        double lat;
+        double radius;
+        double theta_rh;
+
+        int number = linele[0].length;
+        double[][] latlon = new double[2][number];
+
+        // Convert array to Image coordinates for computations
+        double[][] imglinele = areaCoordToImageCoord(linele);
+
+        for (int point=0; point < number; point++) 
+        {
+            d_lin = imglinele[indexLine][point] - lin0;
+            d_ele = imglinele[indexEle][point] - ele0;
+
+            if ( Math.abs(d_lin) < 0.01 && Math.abs(d_ele) < 0.01)
+            {
+                radius = 0.0;
+                theta_rh = 0.0;
+            }
+            else
+            {
+                double dx = scale*(d_lin);
+                double dy = scale*(d_ele);
+                radius = Math.sqrt(dx*dx + dy*dy);
+                theta_rh = Math.atan2(dy, dx);
+            }
+
+            // convert theta_rh to angle FROM standard longitude (theta)
+            // maintaining theta positive from positive x-axis.
+            double theta;
+            if (lat0 < 0.)
+            {
+                theta = (theta_rh <= 0.)
+                            ? Math.PI - Math.abs(theta_rh)
+                            : -1.*(Math.PI - Math.abs(theta_rh));
+            }
+            else theta = theta_rh;
+
+            // Apply range checking on theta to determine if point is navigable
+            if (theta <= -mxtheta || theta > mxtheta)
+            {
+                latlon[indexLat][point] = Double.NaN;
+                latlon[indexLon][point] = Double.NaN;
+            }
+            else
+            {
+                lon = lon0 + theta/coscl;
+                if (lon <= -Math.PI) lon = lon + 2.*Math.PI;
+                if (lon > Math.PI)   lon = lon - 2.*Math.PI;
+                double colat = 
+                    2.* Math.atan( 
+                        tancl2*Math.pow(radius/(Erad*tancl),1./coscl));
+
+                // convert to degrees
+                lon = lon/DEGREES_TO_RADIANS;
+                lat = 90. - colat/DEGREES_TO_RADIANS;
+                latlon[indexLat][point] = (lat0 < 0) ? -1*lat : lat;
+                latlon[indexLon][point] = lon;
+            }
+
+        } // end point for loop
+        return latlon;
+    }
+
+    /**
+     * toLinEle converts lat/long to satellite line/element
+     *
+     * @param  latlon	 array of lat/long pairs. Where latlon[indexLat][]
+     *                    are latitudes and latlon[indexLon][] are longitudes.
+     *
+     * @return linele[][] array of line/element pairs.  Where
+     
+     *                    is an element.  These are in 'file' coordinates
+     *                    (not "image" coordinates);
+     */
+    public double[][] toLinEle(double[][] latlon) 
+    {
+        double lon;
+        double lat;
+
+        int number = latlon[0].length;
+        double[][] linele = new double[2][number];
+
+        for (int point=0; point < number; point++) 
+        {
+            lat = latlon[indexLat][point];
+            lon = latlon[indexLon][point];
+            if (lat <= -90. || lat >= 90. || lon <= -360. ||
+                lon > 360.)
+            {
+                linele[indexLine][point] = Double.NaN;
+                linele[indexEle][point]  = Double.NaN;
+            }
+            else
+            {
+                double colat = 
+                   (lat0 < 0) 
+                       ? Math.PI/2. + DEGREES_TO_RADIANS*lat
+                       : Math.PI/2. - DEGREES_TO_RADIANS*lat;
+                double in_lon = DEGREES_TO_RADIANS*lon;
+                // map longitude into range -Pi to Pi
+                if (in_lon <= -Math.PI) in_lon = in_lon + 2.*Math.PI;
+                if (in_lon > Math.PI)   in_lon = in_lon - 2.*Math.PI;
+
+              // Now trap opposite Pole. Though a physically possible latitude,
+              // tan(colat/2) -> infinity there so it is not navigable
+                if (colat == Math.PI)
+                {
+                    linele[indexLine][point] = Double.NaN;
+                    linele[indexEle][point]  = Double.NaN;
+                }
+                else
+                {
+                    double radius = 
+                        Erad * tancl * 
+                            Math.pow(Math.tan(colat/2.)/tancl2, coscl);
+                    double theta = in_lon-lon0;
+                    if (theta <= -Math.PI) theta = theta + 2*Math.PI;
+                    if (theta > Math.PI)   theta = theta - 2*Math.PI;
+                    theta = coscl * theta;
+
+               // Compute line and element, check for northern or southern
+               // hemisphere projection cone.  Put north pole on top of frame,
+               // south pole on bottom.  Maintain right-handed coordinate system
+               // by measuring theta positive from the positive x-axis.
+                    if (lat0 < 0) theta = Math.PI + theta;
+                    linele[indexLine][point] = 
+                        lin0 + radius*Math.cos(theta)/scale;
+                    linele[indexEle][point]  = 
+                        ele0 + radius*Math.sin(theta)/scale;
+                }
+            }
+        } // end point loop
+
+        // Return in 'File' coordinates
+        return imageCoordToAreaCoord(linele, linele);
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeDatasetURL.java b/edu/wisc/ssec/mcidas/adde/AddeDatasetURL.java
new file mode 100644
index 0000000..fb96009
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeDatasetURL.java
@@ -0,0 +1,183 @@
+//
+// AddeDatasetURL.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+
+/**
+ * A subclass of AddeURL to support queries on datasets.
+ * <pre>
+ *
+ * URLs must all have the following format:
+ *
+ *   adde://host/request?keyword_1=value_1&keyword_2=value_2
+ *
+ *   group=<groupname>         ADDE dataset group
+ *   descr=<descriptor>        ADDE dataset descriptor
+ *   user=<user_id>            ADDE user identification
+ *   proj=<proj #>             a valid ADDE project number
+ *   trace=<0/1>               setting to 1 tells server to write debug
+ *                               trace file
+ *   version=                  ADDE version number, currently 1 except for
+ *                             griddata requests
+ *   debug=                    set to true to watch the printlns stream by
+ *   compress=                 set to "gzip" if you want to use the GZIP
+ *                             compression or "compress" if you want to use
+ *                             transfers in Unix compress format (You need to
+ *                             have the VisAD package if you want to support
+ *                             this.)  default = none.
+ *   port=                     Socket port to connect on.  Overridden by
+ *                             a port specified in the host
+ *                             (e.g., adde.ucar.edu:500)
+ * </pre>
+ */
+public class AddeDatasetURL extends AddeURL {
+
+  /** Keyword for dataset group */
+  public static final String KEY_GROUP = "GROUP";
+
+  /** Keyword for dataset descriptor */
+  public static final String KEY_DESCRIPTOR = "DESCRIPTOR";
+
+  /**
+   * Dataset name
+   */
+  private String group = null;
+
+  /**
+   * Dataset name
+   */
+  private String descriptor = null;
+
+  /**
+   * Create an ADDE URL
+   */
+  public AddeDatasetURL() {}
+
+  /**
+   * Create an ADDE Dataset URL from the given spec
+   * @param host host to send to
+   * @param requestType   type of request (REQ_*)
+   * @param group   ADDE group
+   */
+  public AddeDatasetURL(String host, String requestType, String group) {
+    this(host, requestType, group, null);
+  }
+
+  /**
+   * Create an ADDE Dataset URL from the given spec
+   * @param host host to send to
+   * @param requestType   type of request (REQ_*)
+   * @param group   ADDE group
+   * @param descriptor   ADDE descriptor (may be null)
+   */
+  public AddeDatasetURL(String host, String requestType, String group,
+                        String descriptor) {
+    this(host, requestType, group, descriptor, null);
+  }
+
+  /**
+   * Create an ADDE URL from the given spec
+   * @param host host to send to
+   * @param requestType   type of request (REQ_*)
+   * @param group   ADDE group
+   * @param descriptor   ADDE descriptor (may be null)
+   * @param extraKeys   extraKeys string (key/value pairs)
+   */
+  public AddeDatasetURL(String host, String requestType, String group,
+                        String descriptor, String extraKeys) {
+    super(host, requestType, extraKeys);
+    this.group = group;
+    this.descriptor = descriptor;
+  }
+
+  /**
+   * Make the query portion of the URL (e.g., key1=value1&key2=value2..)
+   * Subclasses should override.
+   *
+   * @return the query portion of the URL
+   */
+  protected String makeQuery() {
+    StringBuffer buf = new StringBuffer(super.makeQuery());
+    if (getGroup() != null) appendKeyValue(buf, KEY_GROUP, getGroup());
+    if (getDescriptor() != null)
+      appendKeyValue(buf, KEY_DESCRIPTOR, getDescriptor());
+    return buf.toString();
+  }
+
+  /**
+   * Get the group for this ADDE URL
+   * @return the group
+   */
+  public String getGroup() {
+    return group;
+  }
+
+  /**
+   * Set the group for this ADDE URL
+   * @param group the group
+   */
+  public void setGroup(String group) {
+    this.group = group;
+  }
+
+  /**
+   * Get the dataset descriptor for this ADDE URL
+   * @return the dataset descriptor
+   */
+  public String getDescriptor() {
+    return descriptor;
+  }
+
+  /**
+   * Set the dataset descriptor for this ADDE URL
+   * @param desc the dataset descriptor
+   */
+  public void setDescriptor(String desc) {
+    this.descriptor = desc;
+  }
+
+  /**
+   * Parse the query string and set the values accordingly, subclasses
+   * should extend to parse their particular keywords
+   * @param query query string
+   */
+  protected void parseQuery(String query) {
+    super.parseQuery(query);
+    String test = getValue(query, KEY_GROUP);
+    if (test != null) {
+      setGroup(test);
+    }
+    // should be able to do this, but old URLS have desc
+    //test = getValue(query, KEY_DESCRIPTOR);
+    test = getValue(query, "DESC");
+    if (test != null) {
+      setDescriptor(test);
+    }
+  }
+
+}
+
diff --git a/edu/wisc/ssec/mcidas/adde/AddeException.java b/edu/wisc/ssec/mcidas/adde/AddeException.java
new file mode 100644
index 0000000..967ebea
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeException.java
@@ -0,0 +1,111 @@
+//
+// AddeException.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import edu.wisc.ssec.mcidas.McIDASException;
+
+/**
+ * {@code AddeException} class is to handle exceptions when dealing
+ * with ADDE access to data. More general than {@link AddeURLException}.
+ *
+ * @author Don Murray - Unidata
+ */
+
+public class AddeException extends McIDASException 
+{
+  /** ADDE error code associated with this exception, if any. */
+  private final int addeErrorCode;
+
+  /** Whether or not an error code has been set for this exception. */
+  private final boolean hasAddeErrorCode;
+
+  /**
+   * Constructs an AddeException with no specified detail message.
+   */
+  public AddeException() {
+    super();
+    hasAddeErrorCode = false;
+    addeErrorCode = 0;
+  }
+
+  /**
+   * Constructs an AddeException with the specified detail message.
+   *
+   * @param message The detail message.
+   */
+  public AddeException(String message) {
+    super(message);
+    hasAddeErrorCode = false;
+    addeErrorCode = 0;
+  }
+
+  /**
+   * Constructs an {@code AddeException} with an ADDE error code in place of a
+   * detail message.
+   *
+   * @param errorCode ADDE error code.
+   */
+  public AddeException(int errorCode) {
+    super();
+    hasAddeErrorCode = true;
+    addeErrorCode = errorCode;
+  }
+
+  /**
+   * Constructs an {@code AddeException} with an ADDE error code and a detail
+   * message.
+   *
+   * @param errorCode ADDE error code;
+   * @param message Detail message.
+   */
+  public AddeException(int errorCode, String message) {
+    super(message);
+    hasAddeErrorCode = true;
+    addeErrorCode = errorCode;
+  }
+
+  /**
+   * Returns the ADDE error code associated with this exception. <b>Note:</b>
+   * you should first check for the presence of an error code via
+   * {@link #hasAddeErrorCode()}.
+   *
+   * @return The ADDE error code associated with this exception.
+   */
+  public int getAddeErrorCode() {
+    return addeErrorCode;
+  }
+
+  /**
+   * Determine whether or not an error code has been provided for this
+   * exception.
+   *
+   * @return Whether or not an ADDE error code has been provided.
+   */
+  public boolean hasAddeErrorCode() {
+    return hasAddeErrorCode;
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeGridReader.java b/edu/wisc/ssec/mcidas/adde/AddeGridReader.java
new file mode 100644
index 0000000..b283624
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeGridReader.java
@@ -0,0 +1,356 @@
+//
+// GridDirList.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.*;
+import java.lang.*;
+import java.util.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import visad.jmet.*;
+import visad.*;
+import visad.data.mcidas.*;
+import visad.data.units.*;
+import edu.wisc.ssec.mcidas.*;
+import edu.wisc.ssec.mcidas.adde.*;
+
+/** 
+ * AddeGridReader interface for McIDAS ADDE grid data sets.   Simulates a
+ * McIDAS GRDLIST request using an ADDE URL.
+ *
+ * <pre>
+ * URLs must all have the following format:
+ *
+ *   for directory listing:
+ *
+ *   adde://host/griddirectory?keyword_1=value_1&keyword_2=value_2
+ *
+ *   or  (for data)
+ *
+ *   adde://host/griddata?keyword_1=value_1&keyword_2=value_2
+ *
+ * there can be any valid combination of the following supported keywords:
+ *
+ *   group=<groupname>         ADDE group name
+ *   descr=<descriptor>        ADDE descriptor name
+ *   param=<param list>        parameter code list
+ *   time=<model run time>     time in hhmmss format
+ *   day=<model run day>       day in ccyyddd format
+ *   lev=<level list>          list of requested levels (value or SFC, MSL 
+ *                               or TRO)
+ *   ftime=<forecast time>     valid time (hhmmss format) (use with fday)
+ *   fday=<forecast day>       forecast day (ccyyddd)
+ *   fhour=<forecast hours>    forecast hours (offset from model run time)
+ *                                (hhmmss format)
+ *   lat=<min lat> <max lat>   latitude bounding box (needs lon specified)
+ *   lon=<min lon> <max lon>   longitude bounding box (needs lat specified)
+ *   row=<min row> <max row>   row bounding box (needs col specified)
+ *   col=<min col> <max col>   column bounding box (needs row specified)
+ *   skip=<row> <col>          skip factors for rows and columns (def = 1 1)
+ *   gpro=<pro>                grid projection (e.g. TANC)
+ *   src=<s1> ... <s2>         list of grid sources (ETA, AVN, etc)
+ *   drange=<btime> <etime> <inc>  range of primary days 
+ *                                 that the grid represents (cannot use with 
+ *                                 day=)
+ *   frange=<btime> <etime> <inc>  range of forecast times 
+ *                                 that the grid represents (cannot use with
+ *                                 fhour=, fday= or ftime=)
+ *   trange=<btime> <etime> <inc>  range of primary times 
+ *                                 that the grid represents (cannot use with time=)
+ *   num=<max>                 maximum number of grids (nn) to return (def=1)
+ *
+ * the following keywords are required:
+ *
+ *   group
+ *   descr
+ *
+ * an example URL might look like:
+ *   adde://viper/griddirectory?group=rtmodel&descr=eta
+ * </pre>
+ *
+ * @author Tom Whittaker
+ * 
+ */
+public class AddeGridReader {
+
+    // load protocol for ADDE URLs
+    // See java.net.URL for explanation of URL handling
+    static {
+        try 
+        {
+            String handlers = System.getProperty("java.protocol.handler.pkgs");
+            String newProperty = null;
+            if (handlers == null)
+                newProperty = "edu.wisc.ssec.mcidas";
+            else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+                newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+            if (newProperty != null)  // was set above
+                System.setProperty("java.protocol.handler.pkgs", newProperty);
+        }
+        catch (Exception e)
+        {
+            System.out.println(
+                "Unable to set System Property: java.protocol.handler.pkgs"); 
+        }
+    }
+
+    private DataInputStream dis;   // input stream
+    private int status=0;                 // read status
+    private URLConnection urlc;           // URL connection
+    private char[] data;                  // data returned from server
+
+    private final int HEARTBEAT = 11223344;
+    ArrayList fileHeaders, gridHeaders, gridData;
+
+    /**
+      * allows reading of McIDAS grid headers and data
+      *
+      */
+    public AddeGridReader() {
+    }
+    
+    /**
+     * creates an ArrayList of McIDASGridDirectories
+     *
+     * @param request ADDE URL to read from.  See class javadoc.
+     *
+     * <pre>
+     * an example URL might look like:
+     *   adde://viper/griddirectory?group=gvar&type=image
+     * </pre>
+     *
+     * @exception AddeURLException if there are no datasets of the particular
+     *            type or there is an error reading data
+     *
+     */
+    public ArrayList getGridDirectory (String request) throws AddeURLException {
+        URL url;
+        gridHeaders = new ArrayList();
+        fileHeaders = new ArrayList();
+
+        try {
+            url = new URL(request);
+            urlc = url.openConnection();
+            InputStream is = urlc.getInputStream();
+            dis = new DataInputStream(new BufferedInputStream(is));
+        } catch (AddeURLException ae) {
+            throw new AddeURLException("Dataset not found: "+ae);
+        }
+        catch (Exception e) {
+            throw new AddeURLException("Error opening connection: " + e);
+        }
+
+        int numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+        if (numBytes == 0) {
+            status = -1;
+            throw new AddeURLException("No datasets found");
+        }
+
+        try {
+
+          int check;
+          byte[] header = new byte[256];
+          while (numBytes == 4) {
+            check = dis.readInt();
+            if (check != HEARTBEAT) {
+              System.out.println("problem...not heartbeat = "+check);
+            }
+            numBytes = dis.readInt();
+          }
+          //System.out.println("numBytes = "+numBytes);
+
+          while ( (check = dis.readInt()) == 0) {
+            dis.readFully(header,0,256);
+            String head = new String(header,0,32);
+            fileHeaders.add(head);
+            //System.out.println("File Header="+head);
+
+            int check2;
+
+            while( (check2 = dis.readInt()) == 0) {
+              dis.readFully(header,0,256);
+              String name = new String(header,24,4);
+              McIDASGridDirectory mg = new McIDASGridDirectory(header);
+              //System.out.println(mg.toString());
+              gridHeaders.add(mg);
+
+            }
+            //System.out.println("check2 = "+check2);
+          }
+          //System.out.println("check = "+check);
+
+        } catch (Exception re) {System.out.println(re);}
+
+        return gridHeaders;
+
+    }
+
+    public ArrayList getGridHeaders() {
+      return gridHeaders;
+    }
+
+    public ArrayList getFileHeaders() {
+      return fileHeaders;
+    }
+
+    /**
+     * creates an ArrayList of arrays of data, plus an ArrayList
+     * of grid headers (McIDASGridDirectories) which are then
+     * available using the getGridHeaders() method.
+     *
+     * @param request ADDE URL to read from.  See class javadoc.
+     *
+     * <pre>
+     * an example URL might look like:
+     *   adde://viper/griddata?group=abom&descr=grid&parm=T&lev=500
+     * </pre>
+     *
+     * @exception AddeURLException if there are no datasets of the particular
+     *            type or there is an error reading data
+     *
+     */
+    public ArrayList getGridData (String request) throws AddeURLException {
+        URL url;
+        gridHeaders = new ArrayList();
+        gridData = new ArrayList();
+
+        try {
+            url = new URL(request);
+            urlc = url.openConnection();
+            InputStream is = urlc.getInputStream();
+            dis = new DataInputStream(new BufferedInputStream(is));
+        } catch (AddeURLException ae) {
+            throw new AddeURLException("Dataset not found: "+ae);
+        }
+        catch (Exception e) {
+            throw new AddeURLException("Error opening connection: " + e);
+        }
+
+        int numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+        if (numBytes == 0) {
+            status = -1;
+            throw new AddeURLException("No datasets found");
+        }
+
+        try {
+
+          int check;
+          byte[] header = new byte[256];
+          while (numBytes == 4) {
+            check = dis.readInt();
+            if (check != HEARTBEAT) {
+              System.out.println("problem...not heartbeat = "+check);
+            }
+            numBytes = dis.readInt();
+          }
+          //System.out.println("numBytes = "+numBytes);
+
+          int checkBytes = dis.readInt();
+          if (checkBytes != numBytes) {throw new
+            AddeURLException("Invalid number of bytes returned for grid.");}
+
+          int numGrids = dis.readInt();
+          //System.out.println("numGrids = "+numGrids);
+
+          for (int i=0; i<numGrids; i++) {
+
+            dis.readFully(header,0,256);
+            String name = new String(header,24,4);
+            McIDASGridDirectory mg = new McIDASGridDirectory(header);
+            System.out.println(mg.toString());
+            CoordinateSystem c = mg.getCoordinateSystem();
+            gridHeaders.add(mg);
+            int rows = mg.getRows();
+            int cols = mg.getColumns();
+            //System.out.println("# rows & cols = "+rows+" "+cols);
+
+            double scale = mg.getParamScale();
+            //System.out.println("param scale = "+scale+" gridType="+mg.getGridType());
+
+            double[] ddata = new double[rows*cols];
+            int n = 0;
+            // store such that 0,0 is in lower left corner...
+            for (int nc=0; nc<cols; nc++) {
+              for (int nr=0; nr<rows; nr++) {
+                int temp = dis.readInt();
+                ddata[(rows-nr-1)*cols + nc] =        // check for missing value
+                  (temp == McIDASUtil.MCMISSING)
+                    ? Double.NaN
+                    : ( (double) temp) / scale ;
+              }
+            }
+            gridData.add(ddata);
+
+            check = dis.readInt();
+            //System.out.println("check after grid = "+check+"  point value = "+data[100]);
+            if (check != 0) break;
+
+          }
+          //System.out.println("check = "+check);
+
+        } catch (Exception re) {System.out.println(re);}
+
+        return gridData;
+
+    }
+
+
+
+    /** test by running 'java edu.wisc.ssec.mcidas.adde.AddeGridReader' */
+    public static void main (String[] args)
+        throws Exception
+    {
+        String request = 
+            (args.length == 0)
+                ? "adde://sweetpea.ssec.wisc.edu/grid?group=ABOM&descr=GRIDS&pos=302&num=40&lev=500&proj=6999&user=tomw&"
+//                ? "adde://sweetpea.ssec.wisc.edu/grid?group=ABOM&descr=GRIDS&pos=302&num=2&proj=6999&user=tomw&"
+//                ? "adde://noaaport.ssec.wisc.edu/grid?group=NGM&descr=12&num=12&proj=6999&user=tomw&"
+//                  ? "adde://noaaport.ssec.wisc.edu/griddirectory?group=NGM&descr=12&num=15&proj=6999&user=tomw&"
+//                  ? "adde://adde.unidata.ucar.edu/griddirectory?group=rtgrids&descr=ngm&num=all&proj=6999&user=tomw&"
+                  : args[0];
+        AddeGridReader d = new AddeGridReader();
+
+        /*
+        ArrayList v = d.getGridDirectory(request);
+        ArrayList vh = d.getFileHeaders();
+        String s = (String) vh.elementAt(0);
+        System.out.println("File header = "+s);
+
+        for (int i=0; i<v.size(); i++) {
+          McIDASGridDirectory mg = (McIDASGridDirectory) v.elementAt(i);
+          System.out.println(mg.toString());
+        }
+        */
+
+        ArrayList v = d.getGridData(request);
+        ArrayList vd = d.getGridHeaders();
+
+
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeImageURL.java b/edu/wisc/ssec/mcidas/adde/AddeImageURL.java
new file mode 100644
index 0000000..2b66771
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeImageURL.java
@@ -0,0 +1,675 @@
+//
+// AddeImageURL.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+
+import java.util.Date;
+
+import edu.wisc.ssec.mcidas.McIDASUtil;
+
+
+/**
+ * A class for holding the ADDE URL for an image directory or data request.
+ *
+ * <pre>
+ * URLs must all have the following format:
+ *
+ *   adde://host/request?keyword_1=value_1&keyword_2=value_2
+ *
+ * where request can be one of the following:
+ *
+ *   imagedata - request for data in AreaFile format (AGET)
+ *   imagedirectory - request for image directory information (ADIR)
+ *
+ * There can be any valid combination of the following supported keywords:
+ *
+ *   group=<groupname>         ADDE group name
+ *   descr=<descriptor>        ADDE descriptor name
+ *   band=<band>               spectral band or channel number
+ *   mag=<lmag> <emag>         image magnification, postitive for blowup,
+ *                               negative for blowdown (default = 1, emag=lmag)
+ *                               (imagedata only)
+ *   latlon=<lat> <lon>        lat/lon point to center image on (imagedata only)
+ *   linele=<lin> <ele> <type> line/element to center image on (imagedata only)
+ *   place=<placement>         placement of lat/lon or linele points (center
+ *                               or upperleft (def=center)) (imagedata only)
+ *   pos=<position>            request an absolute or relative ADDE position
+ *                               number.  May use <start> <end>  Default
+ *                               for <end> is 0 if start<0, or =start otherwise.
+ *   size=<lines> <elements>   size of image to be returned (imagedata only)
+ *   unit=<unit>               to specify calibration units other than the
+ *                               default
+ *   spac=<bytes>              number of bytes per data point, 1, 2, or 4
+ *                               (imagedata only)
+ *   doc=<yes/no>              specify yes to include line documentation
+ *                               with image (def=no)
+ *   nav=<lalo>                include the lat-lon navigation info and not the O&A.
+ *   aux=<yes/no>              specify yes to include auxilliary information
+ *                               with image
+ *   time=<time1> <time2>      specify the time range of images to select
+ *                               (def=latest image if pos not specified)
+ *   day=<day>                 specify the day of the images to select
+ *                               (def=latest image if pos not specified)
+ *   cal=<cal type>            request a specific calibration on the image
+ *                               (imagedata only)
+ *   id=<stn id>               radar station id
+ *   user=<user_id>            ADDE user identification
+ *   proj=<proj #>             a valid ADDE project number
+ *   trace=<0/1>               setting to 1 tells server to write debug
+ *                               trace file
+ *   version=                  ADDE version number, currently 1 except for
+ *                             griddata requests
+ *   debug=                    set to true to watch the printlns stream by
+ *   compress=                 set to "gzip" if you want to use the GZIP
+ *                             compression or "compress" if you want to use
+ *                             transfers in Unix compress format (You need to
+ *                             have the VisAD package if you want to support
+ *                             this.)  default = none.
+ *   port=                     Socket port to connect on.  Overridden by
+ *                             a port specified in the host
+ *                             (e.g., adde.ucar.edu:500)
+ * </pre>
+ *
+ */
+public class AddeImageURL extends AddeDatasetURL {
+
+  /** Keyword for band */
+  public static final String KEY_BAND = "BAND";
+
+  /** Keyword for position */
+  public static final String KEY_POS = "POS";
+
+  /** Keyword for station id */
+  public static final String KEY_ID = "ID";
+
+  /** Keyword for lat/lon request */
+  public static final String KEY_LATLON = "LATLON";
+
+  /** Keyword for lin/ele request */
+  public static final String KEY_LINEELE = "LINELE";
+
+  /** Keyword for location */
+  public static final String KEY_LOC = "LOC";
+
+  /** Keyword for mag */
+  public static final String KEY_MAG = "MAG";
+
+  /** Keyword for number of items */
+  public static final String KEY_NUM = "NUM";
+
+  /** Keyword for place */
+  public static final String KEY_PLACE = "PLACE";
+
+  /** Keyword for size */
+  public static final String KEY_SIZE = "SIZE";
+
+  /** Keyword for spacing */
+  public static final String KEY_SPAC = "SPAC";
+
+  /** Keyword for calibration unit */
+  public static final String KEY_UNIT = "UNIT";
+
+  /** Keyword for navigation type */
+  public static final String KEY_NAV = "NAV";
+
+  /** Keyword for aux request */
+  public static final String KEY_AUX = "AUX";
+
+  /** Keyword for doc request */
+  public static final String KEY_DOC = "DOC";
+
+  /** Keyword for day request */
+  public static final String KEY_DAY = "DAY";
+
+  /** Keyword for time request */
+  public static final String KEY_TIME = "TIME";
+
+  /** number of lines */
+  private int lines;
+
+  /** number of elements */
+  private int elements;
+
+  /** element magnification */
+  private int emag = 1;
+
+  /** line magnification */
+  private int lmag = 1;
+
+  /** default placement value (CENTER, ULEFT) */
+  private static String DEFAULT_PLACE_VALUE = "ULEFT";
+
+  /** default key for location (LATLON, LINELE) */
+  private static String DEFAULT_LOCATE_KEY = "LINELE";
+
+  /** default value for location */
+  private static String DEFAULT_LOCATE_VALUE = "0 0";
+
+  /** default key for location (LATLON, LINELE) */
+  private String locateKey = DEFAULT_LOCATE_KEY;
+
+  /** placement value (CENTER, ULEFT) */
+  private String placeValue = DEFAULT_PLACE_VALUE;
+
+  /** value for location */
+  private String locateValue = DEFAULT_LOCATE_VALUE;
+
+  /** band */
+  private String band = ALL;
+
+  /** band */
+  private String unit = DEFAULT_VALUE;
+
+  /** nav type */
+  private String navType = DEFAULT_VALUE;
+
+  /** aux value */
+  private String auxValue = YES;
+
+  /** doc value */
+  private String docValue = DEFAULT_VALUE;
+
+  /** spacing value */
+  private int spacing = -1;
+
+  /** station id */
+  private String locationId = null;
+
+  /** dataset position */
+  private int pos = 0;
+
+  /** start time */
+  private Date startDate = null;
+
+  /** end time */
+  private Date endDate = null;
+
+  /** time coordinate */
+  private String timeCoverage = "I";
+
+  /** no arg constructor */
+  public AddeImageURL() {}
+
+  /**
+   * Create an AddeImageURL.
+   *
+   * @param host host to send to
+   * @param requestType   type of request (REQ_IMAGEDATA, REQ_IMAGEDIR)
+   * @param group   ADDE group
+   * @param descriptor   ADDE descriptor
+   */
+  public AddeImageURL(String host, String requestType, String group,
+                      String descriptor) {
+    this(host, requestType, group, descriptor, null);
+  }
+
+  /**
+   * Create an ADDE Image URL from the given specs.
+   *
+   * @param host host to send to
+   * @param requestType   type of request (REQ_IMAGEDATA, REQ_IMAGEDIR)
+   * @param group   ADDE group (may be null)
+   * @param descriptor   ADDE descriptor (may be null)
+   * @param query   query string (key/value pairs)
+   */
+  public AddeImageURL(String host, String requestType, String group,
+                      String descriptor, String query) {
+    super(host, requestType, group, descriptor, query);
+  }
+
+  /**
+   * Create an ADDE Image URL from the given spec
+   * @param host host to send to
+   * @param requestType   type of request (REQ_IMAGEDATA, REQ_IMAGEDIR)
+   * @param group   ADDE group
+   * @param descriptor ADDE descriptor
+   * @param locateKey   locate key
+   * @param locateValue  locate value
+   * @param placeValue  place value
+   * @param lines       number of lines
+   * @param elements    number of elements
+   * @param lmag        line magnification
+   * @param emag        element magnification
+   * @param band        band
+   * @param unit        calibration unit
+   * @param spacing     data size
+   */
+  public AddeImageURL(String host, String requestType, String group,
+                      String descriptor, String locateKey,
+                      String locateValue, String placeValue, int lines,
+                      int elements, int lmag, int emag, String band,
+                      String unit, int spacing) {
+    super(host, requestType, group, descriptor);
+    this.locateKey = locateKey;
+    this.locateValue = locateValue;
+    this.placeValue = placeValue;
+    this.lines = lines;
+    this.elements = elements;
+    this.lmag = lmag;
+    this.emag = emag;
+    this.band = band;
+    this.unit = unit;
+    this.spacing = spacing;
+  }
+
+  /**
+   * Get the PLACE value
+   *
+   * @return the PLACE value
+   */
+  public String getPlaceValue() {
+    return placeValue;
+  }
+
+  /**
+   * Get the locate key
+   *
+   * @return the locate key
+   */
+  public String getLocateKey() {
+    return locateKey;
+  }
+
+  /**
+   * Get the locate value
+   *
+   * @return the locate value
+   */
+  public String getLocateValue() {
+    return locateValue;
+  }
+
+  /**
+   * Get the number of lines
+   *
+   * @return the number of lines
+   */
+  public int getLines() {
+    return lines;
+  }
+
+  /**
+   * Get the number of elements
+   *
+   * @return the number of elements
+   */
+  public int getElements() {
+    return elements;
+  }
+
+  /**
+   * Get the element magnification
+   *
+   * @return the element magnification
+   */
+  public int getElementMag() {
+    return emag;
+  }
+
+  /**
+   * Get the line magnification
+   *
+   * @return the line magnification
+   */
+  public int getLineMag() {
+    return lmag;
+  }
+
+  /**
+   * Set the locate key
+   *
+   * @param value  the locate key
+   */
+  public void setLocateKey(String value) {
+    locateKey = value;
+  }
+
+  /**
+   * Set the locate value
+   *
+   * @param value the locate value
+   */
+  public void setLocateValue(String value) {
+    locateValue = value;
+  }
+
+  /**
+   * Set the place value
+   *
+   * @param value the place value
+   */
+  public void setPlaceValue(String value) {
+    placeValue = value;
+  }
+
+  /**
+   * Set the number of lines
+   *
+   * @param value  the number of lines
+   */
+  public void setLines(int value) {
+    lines = value;
+  }
+
+  /**
+   * Set the number of elements
+   *
+   * @param value the number of elements
+   */
+  public void setElements(int value) {
+    elements = value;
+  }
+
+  /**
+   * Set the element magnification
+   *
+   * @param value the element magnification
+   */
+  public void setElementMag(int value) {
+    emag = value;
+  }
+
+  /**
+   * Set the line magnification
+   *
+   * @param value the line magnification
+   */
+  public void setLineMag(int value) {
+    lmag = value;
+  }
+
+  /**
+   * Set the data size (SPAC)
+   *
+   * @param value the data size
+   */
+  public void setSpacing(int value) {
+    spacing = value;
+  }
+
+  /**
+   * Get the data size (SPAC)
+   *
+   * @return the data size
+   */
+  public int getSpacing() {
+    return spacing;
+  }
+
+  /**
+   * Set the band or band range
+   *
+   * @param value   the band range or ALL.  For  REQ_IMAGEDATA, must
+   *                be a single band
+   */
+  public void setBand(String value) {
+    band = value;
+  }
+
+  /**
+   * Get the band or band range
+   *
+   * @return the band range or ALL.
+   */
+  public String getBand() {
+    return band;
+  }
+
+  /**
+   * Set the calibration unit
+   *
+   * @param value   the calibration unit
+   */
+  public void setUnit(String value) {
+    unit = value;
+  }
+
+  /**
+   * Get the calibration unit
+   *
+   * @return calibration unit
+   */
+  public String getUnit() {
+    return unit;
+  }
+
+  /**
+   * Set the navigation type
+   *
+   * @param value   the navigation type (X or LALO)
+   */
+  public void setNavType(String value) {
+    navType = value;
+  }
+
+  /**
+   * Get the navigation type
+   *
+   * @return navigation type (default or LALO)
+   */
+  public String getNavType() {
+    return navType;
+  }
+
+  /**
+   * Set the location ID for radar images
+   *
+   * @param value   the location ID
+   */
+  public void setId(String value) {
+    locationId = value;
+  }
+
+  /**
+   * Get the location ID for radar images
+   *
+   * @return location ID
+   */
+  public String getId() {
+    return locationId;
+  }
+
+  /**
+   * Set the dataset position
+   *
+   * @param value   the dataset position
+   */
+  public void setDatasetPosition(int value) {
+    pos = value;
+  }
+
+  /**
+   * Get the dataset position
+   *
+   * @return dataset position
+   */
+  public int getDatasetPosition() {
+    return pos;
+  }
+
+  /**
+   * Set the start date for the request
+   *
+   * @param value   the starting date for the request
+   */
+  public void setStartDate(Date value) {
+    startDate = value;
+  }
+
+  /**
+   * Get the start date for the request
+   *
+   * @return the start date for the request
+   */
+  public Date getStartDate() {
+    return startDate;
+  }
+
+  /**
+   * Set the end date for the request
+   *
+   * @param value   the ending date for the request
+   */
+  public void setEndDate(Date value) {
+    endDate = value;
+  }
+
+  /**
+   * Get the end date for the request
+   *
+   * @return the ending date for the request
+   */
+  public Date getEndDate() {
+    return endDate;
+  }
+
+
+  /**
+   * Set the time coverage
+   *
+   * @param value   the time coverage
+   */
+  public void setTimeCoverage(String value) {
+    timeCoverage = value;
+  }
+
+  /**
+   * Set the time coverage
+   *
+   * @return the time coverage
+   */
+  public String getTimeCoverage() {
+    return timeCoverage;
+  }
+
+  /**
+   * Set the AUX keyword value
+   *
+   * @param value   the AUX keyword value (YES, NO or DEFAULT_VALUE)
+   */
+  public void setAuxValue(String value) {
+    auxValue = value;
+  }
+
+  /**
+   * Get the AUX keyword value
+   *
+   * @return the AUX keyword value (YES, NO or DEFAULT_VALUE)
+   */
+  public String getAuxValue() {
+    return auxValue;
+  }
+
+  /**
+   * Set the DOC keyword value
+   *
+   * @param value   the DOC keyword value (YES, NO or DEFAULT_VALUE)
+   */
+  public void setDocValue(String value) {
+    docValue = value;
+  }
+
+  /**
+   * Get the DOC keyword value
+   *
+   * @return the DOC keyword value (YES, NO or DEFAULT_VALUE)
+   */
+  public String getDocValue() {
+    return docValue;
+  }
+
+
+  /**
+   * Create the ADDE URL
+   * @return a Adde URL
+   */
+  protected String makeQuery() {
+    StringBuffer buf = new StringBuffer(super.makeQuery());
+    if (getRequestType().equals(REQ_IMAGEDATA)) {
+      appendKeyValue(buf, KEY_BAND, band);
+      appendKeyValue(buf, getLocateKey(), getLocateValue());
+      appendKeyValue(buf, KEY_PLACE, getPlaceValue());
+      appendKeyValue(buf, KEY_SIZE, getLines() + " " + getElements());
+      appendKeyValue(buf, KEY_UNIT, getUnit());
+      appendKeyValue(buf, KEY_MAG, getLineMag() + " " + getElementMag());
+      appendKeyValue(buf, KEY_SPAC, ((getSpacing() == -1)
+                                     ? DEFAULT_VALUE
+                                     : "" + getSpacing()));
+      appendKeyValue(buf, KEY_NAV, getNavType());
+      appendKeyValue(buf, KEY_AUX, getAuxValue());
+      appendKeyValue(buf, KEY_DOC, getDocValue());
+    }
+    else {
+      appendKeyValue(buf, KEY_BAND, ALL);
+    }
+    // add in for the radar queries
+    if (getId() != null) appendKeyValue(buf, KEY_ID, getId());
+    appendDateOrPosString(buf);
+    return buf.toString();
+  }
+
+  /**
+   * Create a DAY/TIME or POS string
+   * @param buf  buffer to append to
+   */
+  protected void appendDateOrPosString(StringBuffer buf) {
+    if (getStartDate() == null && getEndDate() == null) {
+      appendKeyValue(buf, KEY_POS, "" + getDatasetPosition());
+    }
+    else {
+      int[] start = null;
+      if (getStartDate() != null)
+        start = McIDASUtil.mcSecsToDayTime(getStartDate().getTime() / 1000l);
+      int[] end = null;
+      if (getEndDate() != null)
+        end = McIDASUtil.mcSecsToDayTime(getEndDate().getTime() / 1000l);
+      StringBuffer day = new StringBuffer();
+      StringBuffer time = new StringBuffer();
+      if (start != null) {
+        day.append("" + start[0]);
+        time.append(McIDASUtil.mcHmsToStr(start[1]));
+      }
+      day.append(" ");
+      time.append(" ");
+      if (end != null) {
+        if (getRequestType().equals(REQ_IMAGEDIR)) day.append("" + end[0]);
+        time.append("" + McIDASUtil.mcHmsToStr(end[1]));
+      }
+      else {
+        time.append(McIDASUtil.mcHmsToStr(start[1]));
+      }
+      time.append(" ");
+      time.append(getTimeCoverage());
+      appendKeyValue(buf, KEY_DAY, day.toString().trim());
+      appendKeyValue(buf, KEY_TIME, time.toString().trim());
+      if (getRequestType().equals(REQ_IMAGEDIR)) {
+    	  appendKeyValue(buf, KEY_POS, "ALL");
+      }
+    }
+  }
+}
+
diff --git a/edu/wisc/ssec/mcidas/adde/AddePointDataReader.java b/edu/wisc/ssec/mcidas/adde/AddePointDataReader.java
new file mode 100644
index 0000000..d9dcc8a
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddePointDataReader.java
@@ -0,0 +1,538 @@
+//
+// AddePointDataReader.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Vector;
+import edu.wisc.ssec.mcidas.McIDASUtil;
+
+/** 
+ * AddePointDataReader interface for McIDAS ADDE point data sets.   Simulates a
+ * McIDAS PTLIST output using an ADDE URL if <code>toString() method is used.
+ * 
+ * Note that units are ignored by this client, default units are used.
+ *
+ * <pre>
+ * URLs must all have the following format   
+ *   adde://host/point?keyword_1=value_1&keyword_2=value_2
+ *
+ * there can be any valid combination of the following supported keywords:
+ *
+ *   group=<groupname>         ADDE group name
+ *   descr=<descriptor>        ADDE descriptor name
+ *   pos=<position>            request an absolute or relative ADDE 
+ *                               position number
+ *   select=<select clause>    to specify which data is required
+ *   param=<param list>        what parameters to return
+ *   num=<max>                 maximum number of obs to return
+ *   user=<user_id>            ADDE user identification
+ *   proj=<proj #>             a valid ADDE project number
+ *   trace=<0/1>               setting to 1 tells server to write debug 
+ *                               trace file (imagedata, imagedirectory)
+ *   version=1                 ADDE version number, currently 1 
+ *   
+ * the following keywords are required:
+ *
+ *   group
+ *   descr
+ *
+ * an example URL might look like:
+ *   adde://rtds/point?group=neons&type=metar
+ * </pre>
+ *
+ * @author Don Murray - Unidata and James Kelly - BoM
+ * 
+ */
+public class AddePointDataReader {
+
+    // load protocol for ADDE URLs
+    // See java.net.URL for explanation of URL handling
+    static 
+    {
+        try 
+        {
+            String handlers = System.getProperty("java.protocol.handler.pkgs");
+            String newProperty = null;
+            if (handlers == null)
+                newProperty = "edu.wisc.ssec.mcidas";
+            else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+                newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+            if (newProperty != null)  // was set above
+                System.setProperty("java.protocol.handler.pkgs", newProperty);
+        }
+        catch (Exception e)
+        {
+            System.out.println(
+                "Unable to set System Property: java.protocol.handler.pkgs"); 
+        }
+    }
+
+    /**
+     * Key for getting data in param order [numParams][numObs]
+     */
+    public static final int PARAM_ORDER = 0;
+
+    /**
+     * Key for getting data in obs order [numObs][numParams]
+     */
+    public static final int OB_ORDER = 1;
+
+    /**
+     * Maximum number of parameters - used as a sanity check
+     */
+    public static final int MAXNUMPARM = 402;  // number of parameters
+
+    private int status=0;                      // read status
+    //private URLConnection urlc;                // URL connection
+    private String[] params;       // parameters returned from server
+    private int[] ScalingFactors;  // scaling factors returned from server
+    private String[] units;        // units returned from server
+    private int[][] iData = null;  // data returned from server as array of ints
+    private int[][] oData = null;  // data returned from server as array of ints
+    private Vector data = null;    // holds the obs
+    private int numParams = 0;     // number of parameters
+    private boolean debug = false; // set to true for debugging
+    
+    /**
+     * creates an AddePointDataReader object that allows reading ADDE point
+     * datasets.
+     *
+     * @param request ADDE URL to read from.  See class javadoc.
+     *
+     * <pre>
+     * an example URL might look like:
+     *   adde://rtds.ho.bom.gov.au/point?group=neons&descr=metar
+     * </pre>
+     *
+     * @exception AddeException if there are no datasets of the particular
+     *            type or there is an error reading data
+     *
+     */
+    public AddePointDataReader(String request)
+        throws AddeException
+    {
+
+        DataInputStream dataInputStream;
+        URLConnection urlc;
+        try 
+        {
+            URL url = new URL(request);
+            urlc = url.openConnection();
+            //InputStream is = urlc.getInputStream();
+            dataInputStream = 
+                new DataInputStream(
+                    new BufferedInputStream(
+                        urlc.getInputStream()));
+        }
+        catch (AddeURLException ae) 
+        {
+            throw new AddeException("No datasets found " + ae);
+        }
+        catch (Exception e) 
+        {
+            throw new AddeException("Error opening connection: " + e);
+        }
+        //
+        //  first get number of bytes for Parameter Names
+        //
+        int numParamBytes;
+        if(urlc instanceof AddeURLConnection) {
+          numParamBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+        } else {
+           try {
+             numParamBytes = dataInputStream.readInt();
+           }    catch (IOException e) {
+            throw new AddeException("Error reading data: " + e);
+           }
+        }
+        if (debug) System.out.println("numParamBytes = " + numParamBytes);
+        if (numParamBytes == 0)
+        {
+            status = -1;
+            throw new AddeException("No data found");
+        } else if (numParamBytes/4 > MAXNUMPARM)
+        {
+            status = -1;
+            throw new AddeException("Not an ADDE Point Data set");
+        }
+        else
+        {
+            byte[] bParamNames = new byte[numParamBytes];
+            numParams = numParamBytes/4;
+            params = new String[numParams];
+            try
+            {
+                //
+                //  read Parameter names into paramNames
+                //
+                dataInputStream.readFully(bParamNames, 0, numParamBytes);
+                String sParamNames = new String(bParamNames);
+                if (debug) System.out.println(" sParamNames = " + sParamNames);
+                for (int i = 0; i < numParams; i++)
+                    params[i] = sParamNames.substring(i*4, (i+1)*4).trim();
+
+            }
+            catch (IOException e) 
+            {
+                status = -1;
+                throw new AddeException("Error reading parameters:" + e);
+            }
+        } 
+        //
+        //  next get number of bytes for Unit Names
+        //
+        try
+        {
+            int numUnitBytes = dataInputStream.readInt();
+            units = new String[numUnitBytes/4];
+            if (debug) System.out.println("numUnitBytes = " + numUnitBytes);
+            byte[] bUnitNames = new byte[numUnitBytes];
+            dataInputStream.readFully(bUnitNames, 0, numUnitBytes);
+            String sUnitNames = new String(bUnitNames);
+            if (debug) System.out.println("sUnitNames = " + sUnitNames);
+            for (int i = 0; i < numUnitBytes/4; i++)
+                units[i] = sUnitNames.substring(i*4, (i+1)*4).trim();
+        }
+        catch (IOException e) 
+        {
+            status = -1;
+            throw new AddeException("Error reading units:" + e);
+        }
+        //
+        //  next get number of bytes for Scaling Factors
+        //
+        try
+        {
+            int numScalingBytes = dataInputStream.readInt();
+            if (debug) 
+                System.out.println("numScalingBytes = " + numScalingBytes);
+            ScalingFactors = new int[(int)(numScalingBytes/4)];
+            for (int i=0; i < (int) (numScalingBytes/4); i++) {
+                ScalingFactors[i] = dataInputStream.readInt();
+            }
+        }
+        catch (IOException e) 
+        {
+            status = -1;
+            throw new AddeException("Error reading scaling factors:" + e);
+        }
+        //
+        //  next get number of bytes for the actual data
+        //
+        data = new Vector();
+        byte[] bThisUnitName = new byte[4];
+        try
+        {
+            int numDataBytes = dataInputStream.readInt();
+            while (numDataBytes !=0) {
+                if (debug) System.out.println(" i, Param, Unit, Value " );
+                int[] dataArray = new int[numParams];
+                for (int i=0; i < (int) (numDataBytes/4); i++) {
+                     dataArray[i] = dataInputStream.readInt();
+                }
+                data.addElement(dataArray);
+                numDataBytes = dataInputStream.readInt();
+                if (debug) System.out.println("numDataBytes = " + numDataBytes);
+            }
+        }
+        catch (IOException e) 
+        {
+            status = -1;
+            throw new AddeException("Error reading data:" + e);
+        }
+    }
+
+    /**
+     * Return the data sent by the server
+     *
+     * @return  array of the data.  Data is in the format of an integer array
+     *          of unscaled integers as returned from the server in parameter
+     *          order ([numParams][numObs]).
+     *
+     * @exception AddeException if there was an error reading data
+     */
+    public int[][] getData()
+        throws AddeException
+    {
+       return getData(PARAM_ORDER);
+    }
+
+    /**
+     * Return the data sent by the server in a particular order
+     * (PARAM_ORDER, OB_ORDER). 
+     * @param   order   order of the data.  (PARAM_ORDER, OB_ORDER)
+     *
+     * @return  array of the data.  Data is in the format of an integer array
+     *          of unscaled integers in the specified order
+     *          (PARAM_ORDER = [numParams][numObs],
+     *           OB_ORDER = [numObs][numParams])
+     *
+     * @exception AddeException if there was an error reading data
+     */
+    public int[][] getData(int order)
+        throws AddeException
+    {
+        if (status < 0)
+            throw new AddeException("No data available");
+
+        return (order == PARAM_ORDER)
+                    ? getParamOrderData()
+                    : getObOrderData();
+    }
+
+    /**
+     * Get the list of parameters
+     *
+     * @return  array of the parameter names.  The names will be in the same
+     *          order as the array of data values in the <code>getData()</code>
+     *          method.
+     *
+     * @exception AddeException if there was an error reading data
+     */
+    public String[] getParams()
+        throws AddeException
+    {
+        if (status < 0)
+            throw new AddeException("No data available");
+        return params;
+    }
+
+    /**
+     * Get the list of units
+     *
+     * @return  array of the unit names.  The names will be in the same
+     *          order as the array of data values in the <code>getData()</code>
+     *          method.
+     *
+     * @exception AddeException if there was an error reading data
+     */
+    public String[] getUnits()
+        throws AddeException
+    {
+        if (status < 0)
+            throw new AddeException("No data available");
+        return units;
+    }
+
+    /**
+     * Get the list of scaling factors
+     *
+     * @return  array of the scaling factors (powers of 10).  The scaling 
+     *          factors will be in the same order as the array of data 
+     *          values in the <code>getData()</code> method.
+     *
+     * @exception AddeException if there was an error reading data
+     */
+    public int[] getScales()
+        throws AddeException
+    {
+        if (status < 0)
+            throw new AddeException("No data available");
+        return ScalingFactors;
+    }
+
+    /**
+     * return the number of parameters
+     *
+     * @return  number of parameters returned from the server
+     */
+    public int getNumParams()
+        throws AddeException
+    {
+        if (status < 0)
+            throw new AddeException("No data available");
+        return numParams;
+    }
+
+    /**
+     * Return an array of data for the particular parameter. 
+     *
+     * @return array of values for the particular parameter or
+     *         null if an invalid parameter was entered. This
+     *         will return String[] for parameters with units of CHAR,
+     *         float[1][] for an array of non scaled data, and double[1][] for
+     */
+    public Object[] getData(String parameter)
+        throws AddeException
+    {
+        if (status < 0)
+            throw new AddeException("No data available");
+        for (int i = 0; i < numParams; i++)
+        {
+            if (parameter.equalsIgnoreCase(params[i]))
+            {
+                iData = getParamOrderData();
+                if (units[i].equalsIgnoreCase("CHAR"))
+                {
+                    String[] vals = new String[iData[i].length];
+                    for (int j = 0; j < iData[i].length; j++)
+                    {
+                        vals[j] = McIDASUtil.intBitsToString(iData[i][j]);
+                    }
+                    return vals;
+                }
+                else 
+                if (ScalingFactors[i] != 0)
+                {
+                    double[][] vals = new double[1][iData[i].length];
+                    for (int j = 0; j < iData[i].length; j++)
+                    {
+                        vals[0][j] = iData[i][j]/Math.pow(10.0, 
+                                                 (double) ScalingFactors[i]);
+                    }
+                    return vals;
+                }
+                else
+                {
+                    float[][] vals = new float[1][iData[i].length];
+                    for (int j = 0; j < iData[i].length; j++)
+                    {
+                        vals[0][j] = (float) iData[i][j];
+                    }
+                    return vals;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Create an array of data in parameter order [numParams][numobs].
+     * @return array of data in parameter order
+     */
+    private int[][] getParamOrderData() {
+                
+      if (iData == null) {
+        // Convert to in array
+        iData = new int[numParams][data.size()];
+        if (debug) {
+          System.out.println("number of data records = " + data.size());
+        }
+        for (int i = 0; i < data.size(); i++)
+        {
+            int[] values = (int[]) data.get(i);
+            for (int j = 0; j < numParams; j++) iData[j][i] = values[j];
+        }
+      }
+      return iData;
+    }
+
+    /**
+     * Create an array of data in observation order [numObs][numParams].
+     * @return array of data in obs order
+     */
+    private int[][] getObOrderData() {
+                
+      if (oData == null) {
+        // Convert to in array
+        oData = new int[data.size()][numParams];
+        if (debug) {
+          System.out.println("number of data records = " + data.size());
+        }
+        for (int i = 0; i < data.size(); i++)
+        {
+            oData[i] = (int[]) data.get(i);
+        }
+      }
+      return oData;
+    }
+
+    /**
+     * Return a formated string of the returned data
+     *
+     * @return  formatted representation of the data ala McIDAS PTLIST command.
+     */ 
+    public String toString()
+    {
+        if (status < 0)
+            return new String("No data Available");
+
+        StringBuffer buf = new StringBuffer();
+        for (int i = 0; i < numParams; i++)
+        {
+            buf.append(params[i]);
+            buf.append("[");
+            buf.append(units[i]);
+            buf.append("] ");
+            buf.append("\t");
+        }
+        buf.append("\n");
+        iData = getParamOrderData();
+        for (int i = 0; i < iData[0].length; i++)
+        {
+            for (int j = 0; j < numParams; j++)
+            {
+                if (units[j].equalsIgnoreCase("CHAR"))
+                {
+                     buf.append(McIDASUtil.intBitsToString(iData[j][i]));
+                }
+                else 
+                if (ScalingFactors[j] != 0)
+                {
+                     buf.append( iData[j][i] == McIDASUtil.MCMISSING
+                                   ? "     "
+                                   : Double.toString(
+                                       iData[j][i]/Math.pow(10.0, 
+                                              (double) ScalingFactors[j] )));
+                }
+                else
+                {
+                     buf.append( iData[j][i] == McIDASUtil.MCMISSING
+                                   ? "     "
+                                   : Integer.toString(iData[j][i]));
+                }
+                buf.append("\t");
+            }
+            buf.append("\n");
+        }
+        return buf.toString();
+    }
+
+    /** test by running 'java edu.wisc.ssec.mcidas.adde.AddePointDataReader' */
+    public static void main (String[] args)
+        throws Exception
+    {
+        System.out.println("\nData Requested:");
+
+        String request = 
+            (args.length == 0)
+            // ? "adde://servb.ho.bom.gov.au/point?group=neons&descr=metar&num=2&select='id ymml; time 22 24; day 1999317'&parm=id dir spd t[c] td[c] psl&pos=ALL&version=1"
+            // Unidata server
+            ? "adde://adde.ucar.edu/point?group=rtptsrc&descr=sfchourly&num=2&select='id ypph'&parm=id dir spd t[c] td[c] psl&pos=0&version=1&trace=1"
+            : args[0];
+        AddePointDataReader ptlist = new AddePointDataReader(request);
+        System.out.println(ptlist.toString());
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddePointURL.java b/edu/wisc/ssec/mcidas/adde/AddePointURL.java
new file mode 100644
index 0000000..2fb48cb
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddePointURL.java
@@ -0,0 +1,321 @@
+//
+// AddePointURL.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+
+import java.util.regex.*;
+
+
+/**
+* A class for holding the ADDE URL for an image directory or data request.
+ * Decode the ADDE request for point data.
+ *
+ * If the request contains specific parameters (eg param=t),
+ * then the class variable binaryData is set to this param string
+ *
+ * <pre>
+ *   group=<groupname>         ADDE group name
+ *   descr=<descriptor>        ADDE descriptor name
+ *   pos=<position>            request an absolute or relative ADDE
+ *                               position number
+ *   select=<select clause>    to specify which data is required
+ *   param=<param list>        what parameters to return
+ *                             eg param=t[c]
+ *                             note that the units [c] are ignored by server
+ *                             it is the clients task to convert units
+ *                             Note that if "param=" is used,
+ *                             binaryData is set to the
+ *                             (processed) parameter list
+ *   max=<max>                 maximum number of obs to return
+ *   trace=<0/1>               setting to 1 tells server to write debug
+ *                               trace file (imagedata, imagedirectory)
+ *   binaryData=<param list>   because an unlimited number of parameters may
+ *                             be requested, these must be packaged up at the end
+ *                             of the adde request, and this is known as the
+ *                             "binary data" part of the request
+ *
+ * the following keywords are required:
+ *
+ *   group
+ *
+ * an example URL might look like:
+ *   adde://rtds/point?group=neons&descr=metar
+ *
+ * </pre>
+ */
+public class AddePointURL extends AddeDatasetURL {
+
+  /** Keyword for SELECT */
+  public static final String KEY_SELECT = "SELECT";
+
+  /** Keyword for PARAM */
+  public static final String KEY_PARAM = "PARAM";
+
+  /** Keyword for MAX */
+  public static final String KEY_MAX = "MAX";
+
+  /** Keyword for MAX */
+  public static final String KEY_NUM = "NUM";
+
+  /** Keyword for POS */
+  public static final String KEY_POS = "POS";
+
+  /** the select clause */
+  private String selectClause = "";
+
+  /** the parameters */
+  private String params = "";
+
+  /** the max value */
+  private int max = 1;
+
+  /** the max value */
+  private String pos = DEFAULT_VALUE;
+
+  /** no arg constructor */
+  public AddePointURL() {}
+
+  /**
+   * Create an AddePointURL.
+   *
+   * @param host host to send to
+   * @param requestType   type of request (REQ_IMAGEDATA, REQ_IMAGEDIR)
+   * @param group   ADDE group
+   * @param descriptor   ADDE descriptor
+   */
+  public AddePointURL(String host, String requestType, String group,
+                      String descriptor) {
+    this(host, requestType, group, descriptor, null);
+  }
+
+  /**
+   * Create an ADDE PointURL from the given specs.
+   *
+   * @param host host to send to
+   * @param requestType   type of request (REQ_IMAGEDATA, REQ_IMAGEDIR)
+   * @param group   ADDE group (may be null)
+   * @param descriptor   ADDE descriptor (may be null)
+   * @param query   query string (key/value pairs)
+   */
+  public AddePointURL(String host, String requestType, String group,
+                      String descriptor, String query) {
+    super(host, requestType, group, descriptor, query);
+  }
+
+  /**
+   * Create an ADDE Point URL from the given spec
+   * @param host host to send to
+   * @param requestType   type of request (REQ_IMAGEDATA, REQ_IMAGEDIR)
+   * @param group   ADDE group
+   * @param descriptor ADDE descriptor
+   * @param position   dataset position (number or ALL)
+   * @param select     select clause
+   * @param paramList  parameter list
+   * @param maxNum     maximum number to return
+   */
+  public AddePointURL(String host, String requestType, String group,
+                      String descriptor, String position, String select,
+                      String paramList, int maxNum) {
+    super(host, requestType, group, descriptor);
+    this.pos = position;
+    this.selectClause = selectClause;
+    this.params = paramList;
+    this.max = maxNum;
+  }
+
+  /**
+   * Get the SELECT value
+   *
+   * @return the PLACE value
+   */
+  public String getSelectClause() {
+    return selectClause;
+  }
+
+  /**
+   * Set the SELECT clause
+   *
+   * @param value the SELECT clause
+   */
+  public void setSelectClause(String value) {
+    selectClause = value;
+  }
+
+  /**
+   * Get the PARAM value
+   *
+   * @return the PARAM value
+   */
+  public String getParams() {
+    return params;
+  }
+
+  /**
+   * Set the PARAM value
+   *
+   * @param value the PARAM clause
+   */
+  public void setParams(String value) {
+    params = value;
+  }
+
+  /**
+   * Get the MAX value
+   *
+   * @return the MAX value
+   */
+  public int getMaxNumber() {
+    return max;
+  }
+
+  /**
+   * Set the MAX value
+   *
+   * @param value the MAX clause
+   */
+  public void setMaxNumber(int value) {
+    max = value;
+  }
+
+  /**
+   * Get the POS value
+   *
+   * @return the POS value (number or ALL)
+   */
+  public String getPosition() {
+    return pos;
+  }
+
+  /**
+   * Set the POS value
+   *
+   * @param value the POS clause (number or ALL)
+   */
+  public void setPosition(String value) {
+    pos = value;
+  }
+
+  /**
+   * Create the ADDE URL
+   * @return a Adde URL
+   */
+  protected String makeQuery() {
+    StringBuffer buf = new StringBuffer(super.makeQuery());
+    if (!getSelectClause().equals(""))
+      appendKeyValue(buf, KEY_SELECT, getSelectClause());
+    if (!getParams().equals("")) appendKeyValue(buf, KEY_PARAM, getParams());
+    appendKeyValue(buf, KEY_MAX, String.valueOf(getMaxNumber()));
+    appendKeyValue(buf, KEY_POS, getPosition());
+    return buf.toString();
+  }
+
+  /**
+   * Decode a URL and return an AddePointURL
+   *
+   * @param baseURL   url to decode
+   * @return an AddePointURL object (or null)
+   */
+  public static AddePointURL decodeURL(String baseURL) {
+    Pattern pattern = Pattern.compile("(.*)://(.*)/(.*)\\?(.*)");
+    Matcher matcher = pattern.matcher(baseURL);
+    boolean ok = matcher.find();
+    if (!ok) {
+      return null;
+    }
+    int numGroups = matcher.groupCount();
+    if (numGroups >= 3) {
+      String protocol = matcher.group(1);
+      String requestType = matcher.group(3);
+      if (!(protocol.equals("adde") &&
+            requestType.toLowerCase().startsWith("point")))
+        return null;
+      AddePointURL apu = new AddePointURL();
+      apu.setHost(matcher.group(2));
+      apu.setRequestType(requestType);
+      if (numGroups > 3) {
+        String query = matcher.group(4);
+        apu.parseQuery(query);
+      }
+      return apu;
+    }
+    return null;
+  }
+
+  /**
+   * Parse the query string and set the values accordingly, subclasses
+   * should extend to parse their particular keywords
+   * @param query query string
+   */
+  protected void parseQuery(String query) {
+    super.parseQuery(query);
+    String test = getValue(query, KEY_SELECT);
+    if (test != null) {
+      setSelectClause(test);
+    }
+    test = getValue(query, KEY_PARAM);
+    if (test != null) {
+      setParams(test);
+    }
+    test = getValue(query, KEY_POS);
+    if (test != null) {
+      setPosition(test);
+    }
+    test = getValue(query, KEY_MAX);
+    if (test != null) {
+      setMaxNumber(Integer.parseInt(test));
+    }
+    // and just in case people are using NUM, convert to max else {
+    test = getValue(query, KEY_NUM);
+    if (test != null) {
+      int num = max;
+      if (test.equalsIgnoreCase(ALL)) {
+        num = 99999;
+      }
+      else {
+        try {
+          num = Integer.parseInt(test);
+        }
+        catch (Exception e) {
+        }
+      }
+      setMaxNumber(num);
+    }
+  }
+
+  /**
+   * Test the parsing
+   *
+   * @param args  url to parse
+   */
+  public static void main(String[] args) {
+    if (args.length > 0) {
+      AddePointURL url = AddePointURL.decodeURL(args[0]);
+      System.out.println(url.getURLString());
+    }
+  }
+}
+
diff --git a/edu/wisc/ssec/mcidas/adde/AddeSatBands.java b/edu/wisc/ssec/mcidas/adde/AddeSatBands.java
new file mode 100644
index 0000000..aced0cc
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeSatBands.java
@@ -0,0 +1,171 @@
+//
+// AddeSatBands.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+import java.util.*;
+import java.lang.*;
+import java.io.*;
+
+/** Helper class 
+  * to interpret the band information 
+  * from the ADDE SATBANDS file returned by servers
+*/
+
+public class AddeSatBands {
+  String[] c;
+
+  public AddeSatBands(String[] cards) {
+    c = cards;
+  }
+
+  /** given a sensor and a source type, return a list of bands possible
+  * @param sensor is the satellite sensor ID number
+  * @param src is the sensor source code
+  * 
+  * @return String[] of channel words.  The array is 
+  * 1-based to match the customary way of numbering channels (bands),
+  * so element 0 is usually null
+  */
+  public String[] getBandDescr(int sensor, String src) {
+    if (c == null) return null;
+    int gotit = -1;
+    Vector v = new Vector();
+    for (int i=0; i<c.length; i++) {
+      if ( ! c[i].startsWith("Sat ")) continue;
+      StringTokenizer st = new StringTokenizer(c[i]," ");
+      String temp = st.nextToken();  // throw away the key
+      int m = st.countTokens();
+      for (int k=0; k<m; k++) {
+        int ss = Integer.parseInt(st.nextToken().trim());
+        if (ss == sensor) {
+          gotit = i;
+          break;
+        }
+      }
+
+      if (gotit != -1) break;
+    }
+
+    if (gotit == -1) return null;
+
+    // now look for Source
+    int gotSrc = -1;
+    for (int i=gotit; i<c.length; i++) {
+      if (c[i].startsWith("EndSat")) break;
+      if ( ! c[i].startsWith("Cal ")) continue;
+      String srcVal = c[i].substring(4).trim();
+      if (srcVal.equals(src)) {
+        gotSrc = i;
+        break;
+      }
+
+    }
+
+    if (gotSrc == -1) return null;
+
+    gotSrc++;
+
+    for (int i=gotSrc; i<c.length; i++) {
+      if (c[i].startsWith("C") || c[i].startsWith("S") || c[i].startsWith("E")) break;
+      if (c[i].startsWith("B") ) continue;
+      String b = c[i].substring(0,2);
+      int bi = Integer.parseInt(b.trim());
+      String d = null;
+      int ids = c[i].indexOf("DESC=");  // look for new file format
+
+      if (ids > 0) {  // new format
+        int idsb = c[i].indexOf("'",ids+5);
+        if (idsb < 2) {  // no quoted field
+          d = c[i].substring(ids+5);
+
+        } else {
+          int idse = c[i].indexOf("'", idsb+1);
+          d = c[i].substring(idsb+1,idse);
+        }
+        
+        
+      } else {  // old format
+        d = c[i].substring(3).trim();
+      }
+
+      if (bi >= v.size()) v.setSize(bi+1);
+      v.setElementAt(d, bi);
+    }
+
+    int num = v.size();
+    String[] s = new String[num];
+    for (int i=0; i<num; i++) {
+      s[i] = (String) v.elementAt(i);
+    }
+
+    return s;
+  }
+
+  public static void main(String[] a) {
+    try {
+      DataInputStream das = new DataInputStream(new FileInputStream("/src/edu/wisc/ssec/mcidas/adde/satband.txt"));
+      //DataInputStream das = new DataInputStream(new FileInputStream("/src/edu/wisc/ssec/mcidas/adde/SATBAND-new.txt"));
+
+      Vector v = new Vector();
+      while(true) {
+        String s = das.readLine();
+        if (s == null) break;
+        v.addElement(s);
+      }
+
+      das.close();
+      int num = v.size();
+      System.out.println("size of input file = "+num);
+
+      String sat = "12";
+      //String src = "MSAT";  // this is an error
+      String src = "GMS";
+
+      if (a != null && a.length > 1) {
+        sat = a[0];
+        src = a[1];
+      }
+      
+      String[] sv = new String[num];
+      for (int i=0; i<num; i++) { sv[i] = (String) v.elementAt(i); }
+      AddeSatBands asb = new AddeSatBands(sv);
+      String[] f = asb.getBandDescr(Integer.parseInt(sat), src);
+      System.out.println("return from addesatbands");
+      if (f == null) {
+        System.out.println("####  No matches found...!");
+      } else {
+
+        int numb = f.length;
+        System.out.println("length of return = "+numb);
+        for (int i=0; i<numb; i++) {
+          System.out.println("band = value -> "+i+" = "+f[i]);
+        }
+      }
+
+    } catch (Exception e) {System.out.println(e);}
+
+  }
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeServerInfo.java b/edu/wisc/ssec/mcidas/adde/AddeServerInfo.java
new file mode 100644
index 0000000..99d5f78
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeServerInfo.java
@@ -0,0 +1,658 @@
+//
+// AddeServerInfo.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import edu.wisc.ssec.mcidas.adde.ReadTextFile;
+import edu.wisc.ssec.mcidas.AreaDirectoryList;
+import edu.wisc.ssec.mcidas.AreaDirectory;
+import edu.wisc.ssec.mcidas.McIDASException;
+
+/** 
+ * All things related to ADDE servers, groups and datasets
+ * Certain requests are dependent on appropriate prior requests.
+ * E.g., must set server, then group, before you can request
+ * File Format.
+ *
+ * @author  tomw
+ * @version 0.2
+ */
+
+public class AddeServerInfo extends Object {
+  
+  private String[] serverList;
+  private String selectedServer = null;
+  private boolean hasServers = false;
+  private String dataType = null;
+
+  private String[] bandNames;
+  private boolean hasBandNames = false;
+
+  private String selectedGroup = null;
+  private boolean hasGroup = false;
+
+  private String selectedDataset = null;
+  private boolean hasDataset = false;
+  private AreaDirectory [][]dirs;
+
+  private Vector table, groups;
+  private String status="OK";
+
+  private String userproj = null;
+  private String DATEFORMAT = "yyyy-MM-dd / HH:mm:ss";
+  
+  private boolean debug = true;
+
+  private boolean isArchive = false;
+  private String archiveDate = null;
+
+  /** 
+   * Creates new AddeServerInfo. This collects information about
+   * ADDE server(s) -- their groups, datasets, and date-times (right
+   * now only image date-time implemented).  This is a helper class
+   * for anything else that needs to get these information.
+   * 
+   * The candidate list of ADDE servers should be obtained from a
+   * yet-to-be-identified "well-known list", and made available
+   * in this class.
+   */
+  
+  public AddeServerInfo() {
+    // go find the list of well-known servers ?
+    this(null);
+  }
+    
+  /** 
+   * The non-default parameter is a list of of ADDE servers
+   *
+   * @param l a list of ADDE servers that can be used in lieu
+   * of automatically obtaining it.
+   *
+   */
+  
+  public AddeServerInfo(String[] l) {
+    // well known list of ADDE servers
+	// XXX TJJ - I don't like this and would prefer to force a valid
+	// server name into the constructor, and fail all requests otherwise
+    String[] list = l;
+    if (list == null) {
+      list = new String[] {
+    		  "adde.unidata.ucar.edu",
+    		  "peter.ssec.wisc.edu",
+    		  "motherlode.ucar.edu",
+    		  "uwamrc.ssec.wisc.edu"
+    		 };
+    }
+    serverList = new String[list.length];
+    for (int i=0; i<list.length; i++) {
+      serverList[i] = list[i];
+    }
+    hasServers = true;
+  }
+    
+  /** 
+   * get a list of servers
+   *
+   * @return names of server hosts
+   */
+  
+  public String[] getServerList() {
+    if (hasServers) {
+      return serverList;
+    } else {
+      return null;
+    }
+  }
+
+  /** 
+   * get name of the server that has been selected and for
+   *  which all group and dataset info is currently valid for
+   *
+   * @return name of server host
+   */
+  
+  public String getSelectedServer() {
+    return selectedServer;
+  }
+
+  /** 
+   * define which server to select, and the type of ADDE data group
+   * (image, point, grid, text) to get group and descriptor info for
+   *
+   * This is the workhorse of the code.
+   *
+   * @param s the name of the ADDE server to be selected
+   * @param type the type of data to select.
+   *
+   * @return status code: 0=ok, -1=invalid accounting, -2=didn't get metadata
+   *
+   */
+  
+  public int setSelectedServer(String s, String type) {
+    selectedServer = s.trim();
+    int istatus = 0;
+    dataType = type.trim();
+    groups = new Vector();
+    status = "Failed to get PUBLIC.SRV file from from server "+s+".";
+
+    try {
+      // go get the modified PUBLIC.SRV file from the server...
+      String req = "adde://"+selectedServer+"/text?file=PUBLIC.SRV";
+
+      if (userproj != null) {
+        String req2 = req+"&"+userproj+"&version=1";
+        req = req2;
+      }
+
+      if (debug) System.out.println("Req:"+req);
+
+      ReadTextFile rtf = new ReadTextFile(req);
+      if (debug) System.out.println("Status from RTF="+rtf.getStatus());
+      
+      // see if accounting data is required but not provided...
+      if (rtf.getStatusCode() == -3) {
+        return -1;
+      }
+
+/* do we really want to do this????? */
+
+      // if that fails, try to get RESOLV.SRV, but keep it hidden...
+      if (!rtf.getStatus().equals("OK")) { // try again
+        req = "adde://"+selectedServer+"/text?file=RESOLV.SRV";
+
+        if (userproj != null) {
+          String req2 = req+"&"+userproj+"&version=1";
+          req = req2;
+        }
+
+        if (debug) System.out.println("2ndReq:"+req);
+        rtf = new ReadTextFile(req);
+        if (debug) System.out.println("Status from 2ndRTF="+rtf.getStatus());
+
+      }
+      
+      table = rtf.getText();
+
+      status = "Failed to locate required information on server "+s+".";
+      
+      // look at each table member and pull out info only
+      // when the TYPE='dataType'
+      for (int i=0; i<table.size(); i++) {
+        String a = (String) table.elementAt(i);
+        if (debug) System.out.println("Table: "+a);
+        StringTokenizer st = new StringTokenizer(a,",");
+        int num = st.countTokens();
+        String[] tok = new String[num];
+        String[] val = new String[num];
+        int indexType = -1;
+        int indexN1 = -1;
+        int indexN2 = -1;
+        int indexK = -1;
+        int indexC = -1;
+
+        // number of items in the record
+        for (int j=0; j<num; j++) {
+          String p = st.nextToken();
+          // simple token=value within each group
+          int x = p.indexOf("=");
+          if (x < 0) continue;
+          tok[j] = p.substring(0,x);
+          val[j] = p.substring(x+1).trim();
+          // flags to indicate found needed keys
+          if (tok[j].equalsIgnoreCase("type")) indexType = j;
+          if (tok[j].equalsIgnoreCase("N1")) indexN1 = j;
+          if (tok[j].equalsIgnoreCase("N2")) indexN2 = j;
+          if (tok[j].equalsIgnoreCase("K")) indexK = j;
+          if (tok[j].equalsIgnoreCase("C")) indexC = j;
+        }
+
+        // skip if: a) no =, b> not TYPE type, c) element missing
+        if (debug) {
+          System.out.println("indexType = "+indexType+
+            " dataType="+dataType+" indexN1,N2,C="+indexN1+" "+
+            indexN2+" "+indexC);
+        }
+        
+        if (indexType < 0) continue;
+        if (!val[indexType].equalsIgnoreCase(dataType)) continue;
+        if (indexN1 < 0 || indexN2 < 0) continue;
+        // If no Comment field, just use N2 (kludge)
+        if (indexC < 0) indexC = indexN2;
+
+        boolean hit = false;
+
+        // search to see if this group has been encountered before.
+        // if so, just append descr; otherwise make a new entry
+        for (int j=0; j<groups.size(); j++) {
+          Vector vg = (Vector)groups.elementAt(j);
+
+          // element: 0=groupname, 1=Vector of datasets, 2=Vector of descrs
+          String g = (String)vg.elementAt(0);
+          if (g.equalsIgnoreCase(val[indexN1])) {
+            hit = true;
+            Vector v = (Vector)vg.elementAt(1);
+            v.addElement(val[indexN2]);
+            v = (Vector)vg.elementAt(2);
+            v.addElement(val[indexC]);
+            if (vg.size() >= 4) {
+            	v = (Vector)vg.elementAt(3);
+            	v.addElement(val[indexK]);
+            }
+          }
+        }
+
+        // if needed, make a new entry
+        if (!hit) {
+          Vector v = new Vector();
+          v.addElement(val[indexN1]);
+          Vector v2 = new Vector();
+          v2.addElement(val[indexN2]);
+          Vector v3 = new Vector();
+          v3.addElement(val[indexC]);
+          Vector v4 = null;
+          if (indexK > 0) {
+        	  v4 = new Vector();
+        	  v4.addElement(val[indexK]); 
+          }
+          v.addElement(v2);
+          v.addElement(v3);
+          if (v4 != null) v.addElement(v4);
+          groups.addElement(v);
+        }
+      }
+
+      String reqBand = "adde://"+selectedServer+"/text?file=SATBAND";
+
+      if (userproj != null) {
+        String reqBand2 = reqBand+"&"+userproj+"&version=1";
+        reqBand = reqBand2;
+      }
+
+      if (debug) System.out.println("ReqBand:"+reqBand);
+
+      ReadTextFile rtfBand = new ReadTextFile(reqBand);
+      if (debug) System.out.println("Status from RTFBand="+rtfBand.getStatus());
+
+      
+      Vector vBand = null;
+
+      if (rtfBand.getStatus().equals("OK")) {
+        vBand = rtfBand.getText();
+        int num = vBand.size();
+        bandNames = new String[num];
+        for (int i=0; i<num; i++) {
+          bandNames[i] = (String) vBand.elementAt(i);
+          if (debug) System.out.println("band = "+bandNames[i]);
+        }
+        hasBandNames = true;
+        
+      }
+
+      status = "ADDE group & dataset information retrieved from server "+s+".";
+      istatus = 0;
+
+    } catch (Exception e) {e.printStackTrace(); return -2;}
+    
+    return istatus;
+  }
+  
+  /** 
+   * get a list of all groups valid for this server
+   *
+   * @return array of group ids
+   */
+  
+  public String[] getGroupList() {
+
+    int num = groups.size();
+    String[] sg = new String[num];
+    for (int i=0; i < num; i++) {
+      Vector v = (Vector) groups.elementAt(i);
+      sg[i] = (String) v.elementAt(0);
+    }
+    status = "List of groups on server " + selectedServer + " obtained.";
+    return sg;
+    
+  }
+    
+  /** 
+   * get a list of all datasets valid for this server and
+   * the designated group. 
+   *
+   * @return array of dataset tags
+   *
+   */
+  
+  public String[] getDatasetList() {
+    int num = groups.size();
+    boolean autoUpcase = Boolean.getBoolean("adde.auto-upcase");
+    boolean match;
+    for (int i=0; i<num; i++) {
+      Vector v = (Vector)groups.elementAt(i);
+      String g = (String)v.elementAt(0);
+      if (autoUpcase) {
+        match = g.equalsIgnoreCase(selectedGroup);
+      } else {
+        match = g.equals(selectedGroup);
+      }
+      if (match) {
+        Vector ds = (Vector)v.elementAt(1);
+        int numds = ds.size();
+        String[] sdl = new String[numds];
+        for (int j=0; j<numds; j++) {
+          sdl[j] = (String)ds.elementAt(j);
+        }
+        status = "Dataset list for server "+selectedServer+
+                               " and group "+selectedGroup+" obtained.";
+        return sdl;
+      }
+    }
+    status = "Dataset list for server "+selectedServer+
+                            " and group "+selectedGroup+" not found.";
+    return null;
+  }
+  
+  /** 
+   * get a list of all dataset descriptions for this server and
+   * the designated group 
+   *
+   * @return array of dataset descriptors
+   *
+   */
+  
+  public String[] getDatasetListDescriptions() {
+    boolean autoUpcase = Boolean.getBoolean("adde.auto-upcase");
+    boolean match;
+    int num = groups.size();
+    for (int i=0; i<num; i++) {
+      Vector v = (Vector)groups.elementAt(i);
+      String g = (String)v.elementAt(0);
+      if (autoUpcase) {
+          match = g.equalsIgnoreCase(selectedGroup);
+        } else {
+          match = g.equals(selectedGroup);
+        }
+      if (match) {
+        Vector dsd = (Vector)v.elementAt(2);
+        int numdsd = dsd.size();
+        String[] sdld = new String[numdsd];
+        for (int j=0; j<numdsd; j++) {
+          sdld[j] = (String)dsd.elementAt(j);
+        }
+        status = "Dataset list for server "+selectedServer+
+                              " and group "+selectedGroup+" obtained.";
+        return sdld;
+      }
+    }
+    status = "Dataset list for server "+selectedServer+
+                               " and group "+selectedGroup+" not found.";
+    return null;
+  }
+  
+  /** 
+   * get the valid date-time list for the selected server/group/dataset.
+   * Note that you must 'setSelectedGroup()' and 'setSelectedDataset()'
+   * prior to using this method.
+   *
+   * [Also, Don Murray at Unidata wrote the original - thanks!]
+   *
+   * @return list of date-times ("yy-MM-dd / hh:mm:ss" format)
+   * or the string "No data available"
+   *
+   */
+  
+  public String[] getDateTimeList() {
+    status = "Trying to get date-times for "+selectedGroup+
+                                    " from server "+selectedServer;
+    if (!hasGroup || !hasDataset) return null;
+    StringBuffer addeCmdBuff = 
+      new StringBuffer("adde://"+selectedServer+
+                               "/imagedir?group=" + selectedGroup);
+    addeCmdBuff.append("&descr=");
+    addeCmdBuff.append(selectedDataset);
+    // if user/proj supplied, then put them in here...
+    if (userproj != null) addeCmdBuff.append("&"+userproj);
+    addeCmdBuff.append("&pos=all&version=1");
+
+    if (isArchive && archiveDate != null) {
+      addeCmdBuff.append("&DAY="+archiveDate);
+    }
+
+    if (debug) System.out.println("cmd:"+addeCmdBuff.toString());
+
+    String[] times = {"No data available"};
+    
+    try {
+      AreaDirectoryList adir = 
+        new AreaDirectoryList(addeCmdBuff.toString());
+      dirs = adir.getSortedDirs();
+      int numTimes = dirs.length;
+      times = new String[numTimes];
+      
+      SimpleDateFormat sdf = new SimpleDateFormat();
+      sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
+      sdf.applyPattern(DATEFORMAT);
+      for (int i=0; i < numTimes; i++)
+      {
+        times[i] = 
+          sdf.format( 
+              dirs[i][0].getNominalTime(), 
+              new StringBuffer(), 
+              new FieldPosition(0)).toString();
+      }
+    }
+    catch (McIDASException e) {status = "Error getting times";}
+    return times;
+  }
+  
+  /** 
+   * Get the File Format (AREA, TEXT, NEXR, etc.) for the
+   * currently selected group.
+   *
+   * @return file format for currently selected group
+   *
+   */
+  
+   public String getFileFormat() {
+     int num = groups.size();
+     boolean autoUpcase = Boolean.getBoolean("adde.auto-upcase");
+     boolean match;
+     for (int i=0; i<num; i++) {
+       Vector v = (Vector) groups.elementAt(i);
+       String g = (String) v.elementAt(0);
+       if (autoUpcase) {
+         match = g.equalsIgnoreCase(selectedGroup);
+       } else {
+         match = g.equals(selectedGroup);
+       }
+       if (match) {
+    	   System.out.println("VECTOR SIZE: " + v.size());
+    	   if (v.size() >= 4) {
+    		   Vector ffv = (Vector) v.elementAt(3);
+    		   String ff = (String) ffv.elementAt(0);
+    		   if (ff != null) {
+    			   status = "File Format for server " + selectedServer +
+    			   " and group " + selectedGroup + " obtained.";
+    			   return ff;
+    		   }
+    	   }
+       }
+     }
+     status = "File Format for server "+selectedServer+
+                             " and group "+selectedGroup+" not found.";
+     return null;
+   }
+   
+  /** 
+   * get the sorted list of AreaDirectory objects
+   * 
+   * @return list of directories
+   */
+   
+  public AreaDirectory[][] getAreaDirectories() {
+    return dirs;
+  }
+  
+  /** 
+   * identify the selected group
+   *
+   * @param g the name of the group to select
+   */
+  
+  public void setSelectedGroup(String g) {
+    selectedGroup = g;
+    hasGroup = true;
+  }
+  
+  /** 
+   * identify the selected dataset
+   *
+   * @param d the name of the dataset/descr to select
+   */
+  
+  public void setSelectedDataset(String d) {
+    selectedDataset = d;
+    hasDataset = true;
+  }
+
+  public void setIsArchive(boolean flag) {
+    isArchive = flag;
+  }
+
+  public boolean getIsArchive() {
+    return isArchive;
+  }
+
+  public void setArchiveDate(String d) {
+    archiveDate = d;
+  }
+ 
+  public String getArchiveDate() {
+    return archiveDate;
+  }
+  
+  /** 
+   * return the bandNames text
+   *
+   * @return array of text of the SATBAND data fetched from selectedServer
+   *
+   */
+
+  public String[] getBandNames() {
+    if (hasBandNames) {
+      return bandNames;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Not used??
+   */
+  
+  public String getRequestString(int reqestType) {
+    return null;
+    // requestType  = static ints IMAGE, DIR, etc
+  }
+  
+  /** 
+   * set the userID and proj number if required for transactions,
+   * as required by servers that use accounting.
+   *
+   * @param up the user/project number request string in 
+   * the form "user=tom&proj=1234"
+   *
+   */
+  
+  public void setUserIDandProjString(String up) {
+    userproj = up;
+    return;
+  }
+
+  /** 
+   * get the status of the last request
+   *
+   * @return words describing last transaction
+   */
+  
+  public String getStatus() {
+    return status;
+  }
+
+  /** 
+   * For testing purposes. Edit server, and run the class as if it
+   * were an application.
+   */
+  
+  public static void main(String[] args) {
+	  
+	System.out.println("AddeServerInfo unit test begin...");
+    AddeServerInfo asi = new AddeServerInfo();
+    
+    // NOTE!
+    // if you are testing this class, edit below with your server info!
+    asi.setUserIDandProjString("user=SOSE&proj=1250");
+    int sstat = asi.setSelectedServer("noaaport.ssec.wisc.edu", "image");
+
+    System.out.println("Status = " + asi.getStatus() + " code=" + sstat);
+    String[] a = asi.getGroupList();
+    System.out.println("Status = " + asi.getStatus());
+    for (int i=0; i < a.length; i++) {
+      System.out.println("group = " + a[i]);
+      // set the selected group
+      asi.setSelectedGroup(a[i]);
+      System.out.println(" File Format: " + asi.getFileFormat());
+      String[] b = asi.getDatasetList();
+      String[] c = asi.getDatasetListDescriptions();
+
+      for (int k=0; k < b.length; k++) {
+    	// datasets and their descriptions  
+        System.out.println("    " + b[k] + " == " + c[k]);
+
+        if (i==0 && k==0) {
+          // set the selected Dataset, and fetch its date-time info
+          asi.setSelectedDataset(b[k]);
+          String[] dt = asi.getDateTimeList();
+          for (int m=0; m < dt.length; m++) {
+            System.out.println("DateTime = " + dt[m]);
+          }
+        }
+      }
+      
+    }
+   
+    System.out.println("Status = " + asi.getStatus());
+    
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeTextReader.java b/edu/wisc/ssec/mcidas/adde/AddeTextReader.java
new file mode 100644
index 0000000..d80540c
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeTextReader.java
@@ -0,0 +1,500 @@
+//
+// ReadWxText.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.lang.*;
+import java.util.*;
+import edu.wisc.ssec.mcidas.McIDASUtil;
+
+/** 
+ * Read text from an ADDE server interface for McIDAS ADDE data sets.  
+ * This class handles file, weather text and obs text requests.
+ * <P>
+ * <pre>
+ * For File Reading:
+ *   URLs must all have the following format   
+ *     adde://host/text?file=filename.ext
+ *
+ *   there can be any valid combination of the following supported keywords:
+ *
+ *     file - name of text file on ADDE server
+ *
+ *   the following keywords are required:
+ *
+ *     file
+ *
+ *   an example URL might look like:
+ *     adde://viper/text?file=filename.ext
+ *
+ * For Weather Text Reading:
+ *   URLs must all have the following format   
+ *     adde://host/wxtext?group=group&key1=value1&key2=val2....&keyn=valn
+ *
+ *   there can be any valid combination of the following supported keywords:
+ *
+ *     group=<group>         weather text group (default= RTWXTEXT)
+ *     prod=<product>        predefind product name
+ *     apro=<val1 .. valn>   AFOS/AWIPS product headers to match (don't 
+ *                           use with wmo keyword
+ *     astn=<val1 .. valn>   AFOS/AWIPS stations to match
+ *     wmo= <val1 .. valn>   WMO product headers to match (don't 
+ *                           use with apro keyword
+ *     wstn=<val1 .. valn>   WMO stations to match
+ *     day=<start end>       range of days to search
+ *     dtime=<numhours>      maximum number of hours to search back (def=96)
+ *     match=<match strings> list of character match strings to find from text
+ *     num=<num>             number of matches to find (def=1)
+ *
+ *   the following keywords are required:
+ *
+ *     day  (bug causes it not to default to current day)
+ *     one of the selection criteria
+ *
+ *   an example URL might look like:
+ *     adde://adde.ucar.edu/wxtext?group=rtwxtext&prod=zone_fcst&astn=bou
+ *
+ * For Observational Text Reading:
+ *   URLs must all have the following format   
+ *     adde://host/obtext?group=group&descr=descr&key1=value1....&keyn=valn
+ *
+ *   there can be any valid combination of the following supported keywords:
+ *
+ *     group=<group>         weather text group (default= RTWXTEXT)
+ *     descr=<descriptor>    weather text subgroup (default=SFCHOURLY)
+ *     id=<id1 id2 ... idn>  list of station ids
+ *     co=<co1 co2 ... con>  list of countries
+ *     reg=<reg1 reg2..regn> list of regions
+ *     newest=<day hour>     most recent time to allow in request 
+ *                           (def=current time)
+ *     oldest=<day hour>     oldest observation time to allow in request
+ *     type=<type>           numeric value for the type of ob
+ *     nhours=<numhours>     maximum number of hours to search
+ *     num=<num>             number of matches to find (def=1)
+ *
+ *   the following keywords are required:
+ *
+ *     group
+ *     descr
+ *     id, co, or reg
+ *
+ *   an example URL might look like:
+ *     adde://adde.ucar.edu/obtext?group=rtwxtext&descr=sfchourly&id=kden&num=2
+ *
+ * </pre>
+ *
+ * @author Tom Whittaker/Don Murray
+ * 
+ */
+public class AddeTextReader {
+
+  // load protocol for ADDE URLs
+  // See java.net.URL for explanation of URL handling
+  static 
+  {
+    try 
+    {
+      String handlers = System.getProperty("java.protocol.handler.pkgs");
+      String newProperty = null;
+      if (handlers == null)
+          newProperty = "edu.wisc.ssec.mcidas";
+      else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+          newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+      if (newProperty != null)  // was set above
+          System.setProperty("java.protocol.handler.pkgs", newProperty);
+    }
+    catch (Exception e)
+    {
+        System.out.println(
+            "Unable to set System Property: java.protocol.handler.pkgs"); 
+    }
+  }
+
+  private int status=0;                 // read status
+  private String statusString = "OK";
+  private boolean debug = false;        // debug
+  private Vector linesOfText = null;
+  private URLConnection urlc;           
+  private DataInputStream dis;
+  private final int HEARTBEAT = 11223344;
+  private List<WxTextProduct> wxTextProds = new ArrayList<WxTextProduct>();
+    
+  /**
+   * Creates an AddeTextReader object that allows reading an ADDE
+   * text file or weather text
+   *
+   * @param request ADDE URL to read from.  See class javadoc.
+   */
+  public AddeTextReader(String request) {
+   
+    try {
+      URL url = new URL(request);
+      debug = request.indexOf("debug=true") > 0;
+      if (debug) System.out.println("Request: "+request);
+      urlc = url.openConnection();
+      InputStream is = urlc.getInputStream();
+      dis = new DataInputStream(is);
+    }
+    catch (AddeURLException ae) 
+    {
+      status = -1;
+      statusString = "No data found";
+      String aes = ae.toString();
+      if (aes.indexOf(" Accounting ") != -1) {
+        statusString = "No accounting data";
+        status = -3;
+      }
+      if (debug) System.out.println("AddeTextReader Exception:"+aes);
+    }
+    catch (Exception e) 
+    {
+      status = -2;
+      if (debug) System.out.println("AddeTextReader Exception:"+e);
+      statusString = "Error opening connection: "+e;
+    }
+    linesOfText = new Vector();
+    if (status == 0) readText(((AddeURLConnection) urlc).getRequestType());
+    if (linesOfText.size() < 1) statusString = "No data read";
+    status = linesOfText.size();
+  }
+
+  private void readText(int reqType) {
+
+    switch(reqType) {
+      case AddeURLConnection.TXTG:
+        readTextFile();
+        break;
+      case AddeURLConnection.WTXG:
+        readWxText();
+        break;
+      case AddeURLConnection.OBTG:
+        readObText();
+        break;
+    }
+
+  }
+
+  private void readTextFile() {
+    int numBytes;
+
+    try {
+      numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+      if (debug) 
+       System.out.println("ReadTextFile: initial numBytes = " + numBytes);
+
+      numBytes = dis.readInt();
+      while ((numBytes = dis.readInt()) != 0) {
+
+        if (debug) 
+           System.out.println("ReadTextFile: numBytes = " + numBytes);
+
+        byte[] data = new byte[numBytes];
+        dis.readFully(data,0,numBytes);
+        String s = new String(data);
+        if (debug) System.out.println(s);
+        linesOfText.addElement(s);
+      } 
+
+    } catch (Exception iox) {
+      statusString = " "+iox;
+    }
+  }
+    
+  private void readWxText() {
+    int numBytes;
+
+    /*
+      From the McIDAS programmer's ref:
+
+       The server sends the client the following information:
+
+          o  4-byte value containing the length of the client request 
+             string; this lets users know how their request was expanded 
+             when the PROD keyword is specified the expanded client 
+             request string 
+          o  heartbeat value if needed 
+          o  4-byte value containing the total number of bytes of 
+             data for this text block, including the 64-byte header and
+             the text 64-byte text header 
+          o  n bytes of 80-character text, blank padded 
+
+       The last three pieces of information are repeated until no more 
+       data is found.
+
+   */
+
+   try {
+      numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+      if (debug) 
+          System.out.println("ReadWxText: initial numBytes = " + numBytes);
+
+      // Read in the expanded client request string
+      byte[] expandedRequest = new byte[numBytes];
+      dis.readFully(expandedRequest,0,numBytes);
+      String s = new String(expandedRequest);
+      if (debug) 
+          System.out.println("Server interpreted request as:\n " + s);
+
+      // look for heartbeat
+      while ((numBytes = dis.readInt()) == 4) {
+        int check = dis.readInt();
+        if (check != HEARTBEAT) {  // must be number of bytes to read
+            numBytes = check;
+            break;
+        }
+      }
+
+      // Now we go read the data
+      if (debug) System.out.println("numBytes for text = "+numBytes);
+
+      wxTextProds = new ArrayList<WxTextProduct>();
+
+      while (numBytes != 0) {
+        // read in the header
+        byte[] header = new byte[64];
+        dis.readFully(header,0,64);
+        WxTextProduct wtp = new WxTextProduct(header);
+        String head = new String(header);
+        // note this is not true text so prints as garbage
+        //if (debug) System.out.println(decodeWxTextHeader(header));
+        if (debug) System.out.println(wtp);
+
+
+        // read in the text in 80 byte chunks
+        byte[] text = new byte[numBytes-64];
+        dis.readFully(text,0,numBytes-64);
+        int nLines = text.length/80;
+        if (debug) System.out.println("nLines = " + nLines);
+        StringBuilder wxText = new StringBuilder();
+        for (int i = 0; i < nLines; i++)
+        {
+          String line = new String(text, i*80, 80);
+          linesOfText.add(line);
+          wxText.append(line);
+          wxText.append("\n");
+        }
+        wtp.setText(wxText.toString());
+        wxTextProds.add(wtp);
+        // read in next length, but check for heartbeat
+        while ((numBytes = dis.readInt()) == 4) {
+            int check = dis.readInt();
+            if (check != HEARTBEAT) {  // must be number of bytes to read
+               numBytes = check;
+               break;
+           }
+        }
+
+      }
+
+    } catch (Exception iox) {
+      statusString = " "+iox;
+    }
+  }
+
+  private void readObText() {
+    int numBytes;
+    /*
+      From the McIDAS programmer's ref:
+
+      Once the data is formatted, the text data can be sent 
+      to the client. The server sends the client the following 
+      information:
+
+        o   4-byte value containing the length of the text header, 
+            which is currently 96; when all the data is sent, this 
+            value is reset to zero 
+        o   96-byte text header 
+        o   4-byte value containing the number of bytes of text to be sent 
+        o   n bytes of text; 80 characters per line, blank padded 
+
+      This information should be repeated until no more data is found.
+    */
+
+    try {
+      numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+      if (debug) 
+        System.out.println("ReadObText: initial numBytes = " + numBytes);
+
+      while( numBytes != 0) {
+        // Read in the header
+        byte[] header = new byte[numBytes];
+        dis.readFully(header,0,numBytes);
+        String s = new String(header);
+        if (debug) System.out.println(decodeObsHeader(header));
+
+        numBytes = dis.readInt();
+        // Now we go read the data
+        if (debug) System.out.println("numBytes for text = "+numBytes);
+
+        // read in the data
+        byte[] text = new byte[numBytes];
+        dis.readFully(text,0,numBytes);
+        int nLines = text.length/80;
+        if (debug) System.out.println("nLines = " + nLines);
+        for (int i = 0; i < nLines; i++)
+        {
+          String line = new String(text, i*80, 80);
+          linesOfText.add(line);
+        }
+        numBytes = dis.readInt();
+      }
+
+    } catch (Exception iox) {
+      statusString = " "+iox;
+    }
+
+  }
+
+  /**
+   * Get a string representation of the status code
+   * @return human readable status
+   */
+  public String getStatus() {
+    return statusString;
+  }
+
+  /**
+   * Return the status code of the read
+   * @return status code (>0 == read okay);
+   */
+  public int getStatusCode() {
+      return status;
+  }
+
+  /**
+   * Return the number of lines of text that were read.
+   * @return number of lines.
+   */
+  public int getNumLines() {
+    return linesOfText.size();
+  }
+
+  /**
+   * Return the text read from the server.  If there was a problem
+   * the error message is returned.
+   * @return text from server or error message
+   */
+  public String getText() {
+    StringBuffer buf = new StringBuffer();
+    if (getStatusCode() <= 0) {
+        buf.append(getStatus());
+    } else {
+        for (Iterator iter = linesOfText.iterator(); iter.hasNext();) {
+           buf.append((String) iter.next());
+           buf.append("\n");
+        }
+    }
+    return buf.toString();
+  }
+
+  public Vector getLinesOfText() {
+    Vector v = new Vector();
+    if (getStatusCode() <= 0) {
+        v.add(getStatus());
+    } else {
+        v.addAll(linesOfText);
+    }
+    return v;
+  }
+
+  public List<WxTextProduct> getWxTextProducts() {
+    List<WxTextProduct> retList = new ArrayList<WxTextProduct>();
+    retList.addAll(wxTextProds);
+    return retList;
+  }
+      
+
+  /** test by running 'java edu.wisc.ssec.mcidas.adde.AddeTextReader' */
+  public static void main (String[] args)
+      throws Exception
+  {
+      String request = (args.length == 0)
+        ? "adde://adde.ucar.edu/text?file=PUBLIC.SRV"
+        : args[0];
+
+      AddeTextReader atr = new AddeTextReader(request);
+      String status = atr.getStatus();
+      System.out.println("\n" + atr.getText());
+  }
+
+  private String decodeObsHeader(byte[] header) {
+      StringBuffer buf = new StringBuffer();
+      int[] values = McIDASUtil.bytesToIntegerArray(header,0,13);
+      buf.append("Ver = ");
+      buf.append(values[0]);
+      buf.append(" ObType = ");
+      buf.append(values[1]);
+      buf.append(" ActFlag = ");
+      buf.append(values[2]);
+      buf.append(" IDType = ");
+      buf.append(values[9]);
+      buf.append("\nStarting at ");
+      buf.append(values[3]);
+      buf.append(" ");
+      buf.append(values[4]);
+      buf.append(" ");
+      buf.append(values[5]);
+      buf.append("\nEnding   at ");
+      buf.append(values[6]);
+      buf.append(" ");
+      buf.append(values[7]);
+      buf.append(" ");
+      buf.append(values[8]);
+      return buf.toString();
+  }
+
+  private String decodeWxTextHeader(byte[] header) {
+      StringBuffer buf = new StringBuffer();
+      int[] values = McIDASUtil.bytesToIntegerArray(header,0,13);
+      buf.append("SOU    nb  location   day    time  WMO     WSTN APRO ASTN\n");
+      buf.append("---  ----  -------- ------- ------ ------- ---- ---- ----\n"); 
+      buf.append(McIDASUtil.intBitsToString(values[0]));
+      buf.append(values[1]);
+      buf.append(" ");
+      buf.append(values[2]);
+      buf.append(" ");
+      buf.append(values[10]);
+      buf.append(" ");
+      buf.append(values[3]);
+      buf.append(" ");
+      buf.append(McIDASUtil.intBitsToString(values[4]));
+      buf.append(values[5]);
+      buf.append("  ");
+      buf.append(McIDASUtil.intBitsToString(values[6]));
+      buf.append(" ");
+      buf.append(McIDASUtil.intBitsToString(values[7]));
+      buf.append(" ");
+      buf.append(McIDASUtil.intBitsToString(values[8]));
+      return buf.toString();
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeURL.java b/edu/wisc/ssec/mcidas/adde/AddeURL.java
new file mode 100644
index 0000000..33e0ffb
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeURL.java
@@ -0,0 +1,603 @@
+//
+// AddeURL.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+
+/**
+ * A class for holding information about an ADDE URL.
+ *
+ * <pre>
+ *
+ * URLs must all have the following format:
+ *
+ *   adde://host/request?keyword_1=value_1&keyword_2=value_2
+ *
+ * where request can be one of the following:
+ *
+ *   datasetinfo - request for data set information (LWPR)
+ *   griddirectory - request for grid directory information (GDIR)
+ *   griddata - request for grid data (GGET)
+ *   imagedata - request for data in AreaFile format (AGET)
+ *   imagedirectory - request for image directory information (ADIR)
+ *   pointdata - request for point data (MDKS)
+ *   textdata - request to read a text file (TXTG)
+ *   wxtext   - request to read a weather text file (WTXG)
+ *   obtext   - request to read a observation text file (OBTG)
+ *
+ * There can be any valid combination of the following supported keywords:
+ *
+ * -------for any request
+ *
+ *   group=<groupname>         ADDE group name
+ *   user=<user_id>            ADDE user identification
+ *   proj=<proj #>             a valid ADDE project number
+ *   trace=<0/1>               setting to 1 tells server to write debug
+ *                               trace file
+ *   version=                  ADDE version number, currently 1 except for
+ *                             griddata requests
+ *   debug=                    set to true to watch the printlns stream by
+ *   compress=                 set to "gzip" if you want to use the GZIP
+ *                             compression or "compress" if you want to use
+ *                             transfers in Unix compress format (You need to
+ *                             have the VisAD package if you want to support
+ *                             this.)  default = none.
+ *   port=                     Socket port to connect on.  Overridden by
+ *                             a port specified in the host
+ *                             (e.g., adde.ucar.edu:500)
+ *
+ * </pre>
+ */
+public class AddeURL implements Cloneable {
+
+  /** The protocol */
+  public static final String ADDE_PROTOCOL = "adde";
+
+  /** AGET request type */
+  public final static String REQ_AGET = "aget";
+
+  /** ADIR request type */
+  public final static String REQ_ADIR = "adir";
+
+  /** LWPR request type */
+  public final static String REQ_LWPR = "lwpr";
+
+  /** GDIR request type */
+  public final static String REQ_GDIR = "gdir";
+
+  /** GGET request type */
+  public final static String REQ_GGET = "gget";
+
+  /** MDKS request type */
+  public final static String REQ_MDKS = "mdks";
+
+  /** TXTG request type */
+  public final static String REQ_TXTG = "txtg";
+
+  /** WTXG request type */
+  public final static String REQ_WTXG = "wtxg";
+
+  /** OBTG request type */
+  public final static String REQ_OBTG = "obtg";
+
+  /** Image data request type */
+  public final static String REQ_IMAGEDATA = "imagedata";
+
+  /** Image directory request type */
+  public final static String REQ_IMAGEDIR = "imagedirectory";
+
+  /** Data set info request type */
+  public final static String REQ_DATASETINFO = "datasetinfo";
+
+  /** Text request type */
+  public final static String REQ_TEXT = "text";
+
+  /** weather text request type */
+  public final static String REQ_WXTEXT = "wxtext";
+
+  /** obs text request type */
+  public final static String REQ_OBTEXT = "obtext";
+
+  /** Grid data request type */
+  public final static String REQ_GRIDDATA = "griddata";
+
+  /** Grid directory request type */
+  public final static String REQ_GRIDDIR = "griddir";
+
+  /** Point data request type */
+  public final static String REQ_POINTDATA = "point";
+
+  /** Default value for key/value pairs (X) */
+  public final static String DEFAULT_VALUE = "X";
+
+  /** Value for yes */
+  public final static String YES = "YES";
+
+  /** Value for no */
+  public final static String NO = "NO";
+
+  /** Value for ALL */
+  public final static String ALL = "ALL";
+
+  /** Keyword for trace */
+  public static final String KEY_TRACE = "TRACE";
+
+  /** Keyword for debug */
+  public static final String KEY_DEBUG = "DEBUG";
+
+  /** Keyword for port */
+  public static final String KEY_PORT = "PORT";
+
+  /** Keyword for project */
+  public static final String KEY_PROJ = "PROJ";
+
+  /** Keyword for user */
+  public static final String KEY_USER = "USER";
+
+  /** Keyword for compress */
+  public static final String KEY_COMPRESS = "COMPRESS";
+
+  /** Keyword for version */
+  public static final String KEY_VERSION = "VERSION";
+
+  /** Flag for "compress" compression. */
+  public final static int COMPRESS = 503;
+
+  /** Flag for "no compress" compression. */
+  public final static int NO_COMPRESS = 500;
+
+  /** Flag for GZip compression. */
+  public final static int GZIP = 112;
+
+  /** flag  for trace on */
+  public static final int TRACE_ON = 0;
+
+  /** flag  for trace off */
+  public static final int TRACE_OFF = 1;
+
+  /** the host */
+  private String host = "localhost";
+
+  /** the version */
+  private String version = "" + AddeURLConnection.VERSION_1;
+
+  /** the user id */
+  private String user = AddeURLConnection.DEFAULT_USER;
+
+  /** the project number */
+  private int project = AddeURLConnection.DEFAULT_PROJ;
+
+  /** the trace flag */
+  private int trace = 0;
+
+  /** the extra key/value pairs */
+  private String extraKeys = null;
+
+  /** the compression type */
+  private int compress = -1; // let port determine compression by default
+
+  /** the port */
+  private int port = AddeURLConnection.DEFAULT_PORT;
+
+  /** the request type */
+  private String requestType = "";
+
+  /** the debug type */
+  private boolean debug = false;
+
+  /**
+   * Create an ADDE URL
+   */
+  public AddeURL() {}
+
+  /**
+   * Create an ADDE URL from the given spec
+   * @param host host to send to
+   * @param requestType   type of request (REQ_*)
+   */
+  public AddeURL(String host, String requestType) {
+    this(host, requestType, null);
+  }
+
+  /**
+   * Create an ADDE URL from the given spec
+   * @param host host to send to
+   * @param requestType   type of request (REQ_*)
+   * @param extraKeys     extra key/value arguments
+   */
+  public AddeURL(String host, String requestType, String extraKeys) {
+    this.host = host;
+    this.requestType = requestType;
+    this.extraKeys = extraKeys;
+  }
+
+  /**
+   * Create the ADDE URL as a String
+   *
+   * @return an Adde URL
+   */
+  public String getURLString() {
+    StringBuffer buf = new StringBuffer(ADDE_PROTOCOL);
+    buf.append("://");
+    buf.append(host);
+    buf.append("/");
+    buf.append(requestType);
+    buf.append("?");
+    buf.append(makeQuery());
+    return buf.toString();
+  }
+
+  /**
+   * Make the query portion of the URL (e.g., key1=value1&key2=value2..)
+   * Subclasses should override.
+   *
+   * @return the query portion of the URL
+   */
+  protected String makeQuery() {
+    StringBuffer buf = new StringBuffer();
+    appendKeyValue(buf, KEY_PORT, "" + getPort());
+    appendKeyValue(buf, KEY_COMPRESS, getCompressionType());
+    appendKeyValue(buf, KEY_USER, getUser());
+    appendKeyValue(buf, KEY_PROJ, "" + getProject());
+    appendKeyValue(buf, KEY_VERSION, getVersion());
+    appendKeyValue(buf, KEY_DEBUG, Boolean.toString(getDebug()));
+    appendKeyValue(buf, KEY_TRACE, "" + getTrace());
+    if (getExtraKeys() != null) {
+      if (!getExtraKeys().startsWith("&")) buf.append("&");
+      buf.append(getExtraKeys());
+    }
+    return buf.toString();
+  }
+
+  /**
+   * Parse the query string and set the values accordingly, subclasses
+   * should extend to parse their particular keywords
+   * @param query query string
+   */
+  protected void parseQuery(String query) {
+    String test = getValue(query, KEY_PORT);
+    if (test != null) {
+      setPort(Integer.parseInt(test));
+    }
+    test = getValue(query, KEY_COMPRESS);
+    if (test != null) {
+      setCompressionFromString(test);
+    }
+    test = getValue(query, KEY_USER);
+    if (test != null) {
+      setUser(test);
+    }
+    test = getValue(query, KEY_PROJ);
+    if (test != null) {
+      setProject(Integer.parseInt(test));
+    }
+    test = getValue(query, KEY_VERSION);
+    if (test != null) {
+      setVersion(test);
+    }
+    test = getValue(query, KEY_DEBUG);
+    if (test != null) {
+      setDebug(test.equals("true"));
+    }
+    test = getValue(query, KEY_TRACE);
+    if (test != null) {
+      setTrace(Integer.parseInt(test));
+    }
+  }
+
+  /**
+   * Get the value for a particular key.
+   * @param query  the query string
+   * @param key    the key to search
+   * @return the  value or null if it doesn't exist
+   */
+  public String getValue(String query, String key) {
+    String retVal = null;
+    int keyIndex = query.indexOf(key);
+    if (keyIndex < 0) { // try lowercase version;
+      keyIndex = query.indexOf(key.toLowerCase());
+    }
+    if (keyIndex >= 0) {
+      int equalIndex = query.indexOf("=", keyIndex); 
+      int ampIndex = query.indexOf("&", keyIndex);
+      retVal = (ampIndex >= 0)
+               ? query.substring(equalIndex+1, ampIndex) // to the amp
+               : query.substring(equalIndex+1); // to the end
+    }
+    return retVal;
+  }
+
+
+  /**
+   * A utility method to make a name=value part of the adde request string
+   *
+   * @param buf The buffer to append to
+   * @param name The property name
+   * @param value The value
+   */
+  protected void appendKeyValue(StringBuffer buf, String name, String value) {
+    if ((buf.length() == 0) || (buf.charAt(buf.length() - 1) != '?')) {
+      buf.append("&");
+    }
+    buf.append(name);
+    buf.append("=");
+    buf.append(value);
+  }
+
+  /**
+   * Compare two AddeURLs
+   *
+   * @param o   Object in question
+   *
+   * @return true if they are the same object or if all
+   */
+  public boolean equals(Object o) {
+    if (!(o.getClass().equals(this.getClass()))) {
+      return false;
+    }
+    AddeURL that = (AddeURL)o;
+    if (this == that) {
+      return true;
+    }
+    return getURLString().equals(that.getURLString());
+  }
+
+  /**
+   * Get the hashcode for this
+   *
+   * @return the hashcode
+   */
+  public int hashCode() {
+    return getURLString().hashCode();
+  }
+
+  /**
+   * Get the host for this ADDE URL
+   * @return the host
+   */
+  public String getHost() {
+    return host;
+  }
+
+  /**
+   * Set the host for this ADDE URL
+   * @param host the host
+   */
+  public void setHost(String host) {
+    this.host = host;
+  }
+
+  /**
+   * Get the request type for this ADDE URL
+   * @return the host
+   */
+  public String getRequestType() {
+    return requestType;
+  }
+
+  /**
+   * Set the request type for this ADDE URL
+   *
+   * @param requestType the request Type
+   */
+  public void setRequestType(String requestType) {
+    this.requestType = requestType;
+  }
+
+  /**
+   * Get the extraKeys string for this ADDE URL
+   * @return the extraKeys string
+   */
+  public String getExtraKeys() {
+    return extraKeys;
+  }
+
+  /**
+   * Set the extraKeys string for this ADDE URL
+   * @param extraKeys the extraKeys
+   */
+  public void setExtraKeys(String extraKeys) {
+    this.extraKeys = extraKeys;
+  }
+
+  /**
+   * Get the project for this ADDE URL
+   * @return the project
+   */
+  public String getUser() {
+    return user;
+  }
+
+  /**
+   * Set the user for this ADDE URL
+   * @param user the user
+   */
+  public void setUser(String user) {
+    this.user = user;
+  }
+
+  /**
+   * Get the project for this ADDE URL
+   * @return the project
+   */
+  public int getProject() {
+    return project;
+  }
+
+  /**
+   * Set the project for this ADDE URL
+   * @param project the project
+   */
+  public void setProject(int project) {
+    this.project = project;
+  }
+
+  /**
+   * Get the version for this ADDE URL
+   * @return the version
+   */
+  public String getVersion() {
+    return version;
+  }
+
+  /**
+   * Set the version this ADDE URL
+   * @param version the version
+   */
+  public void setVersion(String version) {
+    this.version = version;
+  }
+
+  /**
+   * Get the debug value for this ADDE URL
+   * @return the debug value (true or false)
+   */
+  public boolean getDebug() {
+    return debug;
+  }
+
+  /**
+   * Set the debug value this ADDE URL
+   * @param debug the debug value (true or false);
+   */
+  public void setDebug(boolean debug) {
+    this.debug = debug;
+  }
+
+  /**
+   * Get the port for this ADDE URL
+   * @return the port
+   */
+  public int getPort() {
+    return port;
+  }
+
+  /**
+   * Set the port used for this ADDE URL
+   * @param port the port
+   */
+  public void setPort(int port) {
+    this.port = port;
+  }
+
+  /**
+   * Get the compression type for this ADDE URL
+   * @return the compression type
+   */
+  public int getCompression() {
+    return compress;
+  }
+
+  /**
+   * Set the compression type used for this ADDE URL
+   * @param compress the compression type (GZIP, NO_COMPRESS, COMPRESS)
+   */
+  public void setCompression(int compress) {
+    this.compress = compress;
+  }
+
+  /**
+   * Get the trace value used for this ADDE URL
+   * @return the trace value (TRACE_ON, TRACE_OFF)
+   */
+  public int getTrace() {
+    return trace;
+  }
+
+  /**
+   * Set the trace value used for this ADDE URL
+   * @param trace   the trace value (TRACE_ON, TRACE_OFF)
+   */
+  public void setTrace(int trace) {
+    this.trace = trace;
+  }
+
+  /**
+   * Get the compression type from the port number if not specified.
+   *
+   * @return the compression type as a string
+   */
+  private String getCompressionType() {
+    String testStr = null;
+    int valueToCheck = (compress == -1)
+                       ? port
+                       : compress;
+    switch (valueToCheck) {
+      case NO_COMPRESS: // port == 500
+      case 1: // port == 1
+        testStr = "none";
+        break;
+      case COMPRESS: // port == 503
+      case 2: // port == 2
+        testStr = "compress";
+        break;
+      case GZIP: // port == 112
+      case 3: // port == 3
+      default:
+        testStr = "gzip";
+        break;
+    }
+    return testStr;
+  }
+
+  /**
+   * Set the compression type from a string
+   *
+   * @param type  the string type
+   */
+  public void setCompressionFromString(String type) {
+    if (type.equals("gzip") || type.equals("112")) {
+      setCompression(112);
+    }
+    else if (type.equals("compress") || type.equals("503")) {
+      setCompression(503);
+    }
+    else if (type.equals("none") || type.equals("500")) {
+      setCompression(500);
+    }
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * <p>This implementation never throws CloneNotSupportException</p>
+   *
+   * @return                            A clone of this instance.
+   * @throws CloneNotSupportedException if cloning isn't supported.
+   */
+  public Object clone() throws CloneNotSupportedException {
+
+    Object clone = null;
+    try {
+      clone = super.clone();
+    }
+    catch (CloneNotSupportedException ex) {
+      throw new Error("Assertion failure"); // can't happen
+    }
+    return clone;
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeURLConnection.java b/edu/wisc/ssec/mcidas/adde/AddeURLConnection.java
new file mode 100644
index 0000000..04ee351
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeURLConnection.java
@@ -0,0 +1,2333 @@
+//
+// AddeURLConnection.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.io.DataInputStream;
+import java.io.BufferedInputStream;
+import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.StringTokenizer;
+import java.util.zip.GZIPInputStream;
+import edu.wisc.ssec.mcidas.McIDASUtil;
+import HTTPClient.UncompressInputStream;
+
+/**
+ * This class extends URLConnection, providing the guts of the
+ * work to establish an ADDE network connection, put together
+ * a request packet, and initiate data flow.  Connections for
+ * image data, image directories, grid data, grid directory, 
+ * point source data, text, weather text, observational text
+ * and dataset information (McIDAS AGET, ADIR, GDIR, GGET, MDKS, 
+ * TXTG, OBTG, WXTG and LWPR requests) are supported.
+ *
+ * @see <A HREF="http://www.ssec.wisc.edu/mcidas/doc//prog_man.html">
+ *      McIDAS Programmer's Manual</A>
+ *
+ * <pre>
+ *
+ * URLs must all have the following format:
+ *
+ *   adde://host/request?keyword_1=value_1&keyword_2=value_2
+ *
+ * where request can be one of the following:
+ *
+ *   datasetinfo - request for data set information (LWPR)
+ *   griddirectory - request for grid directory information (GDIR)
+ *   griddata - request for grid data (GGET)
+ *   imagedata - request for data in AreaFile format (AGET)
+ *   imagedirectory - request for image directory information (ADIR)
+ *   pointdata - request for point data (MDKS)
+ *   textdata - request to read a text file (TXTG) 
+ *   wxtext   - request to read a weather text file (WTXG) 
+ *   obtext   - request to read a observation text file (OBTG) 
+ *
+ * There can be any valid combination of the following supported keywords:
+ *
+ * -------for any request
+ *
+ *   group=<groupname>         ADDE group name
+ *   user=<user_id>            ADDE user identification
+ *   proj=<proj #>             a valid ADDE project number
+ *   trace=<0/1>               setting to 1 tells server to write debug 
+ *                               trace file 
+ *   version=                  ADDE version number, currently 1 except for
+ *                             griddata requests
+ *   debug=                    set to true to watch the printlns stream by
+ *   compress=                 set to "gzip" if you want to use the GZIP
+ *                             compression or "compress" if you want to use 
+ *                             transfers in Unix compress format (You need to 
+ *                             have the VisAD package if you want to support 
+ *                             this.)  default = none.
+ *   port=                     Socket port to connect on.  Overridden by
+ *                             a port specified in the host 
+ *                             (e.g., adde.ucar.edu:500)
+ *
+ * -------for images:
+ *
+ *   descr=<descriptor>        ADDE descriptor name
+ *   band=<band>               spectral band or channel number 
+ *   mag=<lmag> <emag>         image magnification, postitive for blowup, 
+ *                               negative for blowdown (default = 1, emag=lmag)
+ *                               (imagedata only)
+ *   latlon=<lat> <lon>        lat/lon point to center image on (imagedata only)
+ *   linele=<lin> <ele> <type> line/element to center image on (imagedata only)
+ *   place=<placement>         placement of lat/lon or linele points (center 
+ *                               or upperleft (def=center)) (imagedata only)
+ *   track<0>               tell the ADDE server not to track for this
+ *
+ *   pos=<position>            request an absolute or relative ADDE position 
+ *                               number.  May use <start> <end>  Default
+ *                               for <end> is 0 if start<0, or =start otherwise.
+ *   size=<lines> <elements>   size of image to be returned (imagedata only)
+ *   unit=<unit>               to specify calibration units other than the 
+ *                               default 
+ *   spac=<bytes>              number of bytes per data point, 1, 2, or 4 
+ *                               (imagedata only)
+ *   doc=<yes/no>              specify yes to include line documentation 
+ *                               with image (def=no) 
+ *   nav=<lalo>                include the lat-lon navigation info and not the O&A.
+ *   aux=<yes/no>              specify yes to include auxilliary information 
+ *                               with image 
+ *   time=<time1> <time2>      specify the time range of images to select
+ *                               (def=latest image if pos not specified)
+ *   day=<day>                 specify the day of the images to select
+ *                               (def=latest image if pos not specified)
+ *   cal=<cal type>            request a specific calibration on the image 
+ *                               (imagedata only)
+ *   id=<stn id>               radar station id 
+ *
+ * ------ for grids:
+ *
+ *   descr=<descriptor>        ADDE descriptor name
+ *   param=<param list>        parameter code list
+ *   time=<model run time>     time in hhmmss format
+ *   day=<model run day>       day in ccyyddd format
+ *   lev=<level list>          list of requested levels (value or SFC, MSL 
+ *                               or TRO)
+ *   ftime=<forecast time>     valid time (hhmmss format) (use with fday)
+ *   fday=<forecast day>       forecast day (ccyyddd)
+ *   fhour=<forecast hours>    forecast hours (offset from model run time)
+ *                                (hhmmss format)
+ *   lat=<min lat> <max lat>   latitude bounding box (needs lon specified)
+ *   lon=<min lon> <max lon>   longitude bounding box (needs lat specified)
+ *   row=<min row> <max row>   row bounding box (needs col specified)
+ *   col=<min col> <max col>   column bounding box (needs row specified)
+ *   skip=<row> <col>          skip factors for rows and columns (def = 1 1)
+ *   gpro=<pro>                grid projection (e.g. TANC)
+ *   src=<s1> ... <s2>         list of grid sources (ETA, AVN, etc)
+ *   drange=<btime> <etime> <inc>  range of primary days 
+ *                                 that the grid represents (cannot use with 
+ *                                 day=)
+ *   frange=<btime> <etime> <inc>  range of forecast times 
+ *                                 that the grid represents (cannot use with
+ *                                 fhour=, fday= or ftime=)
+ *   trange=<btime> <etime> <inc>  range of primary times 
+ *                                 that the grid represents (cannot use with time=)
+ *   num=<max>                 maximum number of grids (nn) to return (def=1)
+ *
+ * ------ for point data:
+ *
+ *   descr=<descriptor>        ADDE descriptor name
+ *   pos=<position>            request an absolute or relative ADDE 
+ *                               position number
+ *   select=<select clause>    to specify which data is required
+ *   param=<param list>        what parameters to return
+ *   num=<max>                 maximum number of obs to return
+ *   
+ * ------ for text data:
+ *
+ *   descr=<descriptor>        ADDE descriptor name 
+ *                             (may also be "descr=FILE=filename")
+ *   file=<filename>           name of text file to read
+ *
+ * ------ for weather text data:
+ *
+ *   group=<group>         weather text group (default= RTWXTEXT)
+ *   prod=<product>        predefind product name
+ *   apro=<val1 .. valn>   AFOS/AWIPS product headers to match (don't 
+ *                         use with wmo keyword
+ *   astn=<val1 .. valn>   AFOS/AWIPS stations to match
+ *   wmo= <val1 .. valn>   WMO product headers to match (don't 
+ *                         use with apro keyword
+ *   wstn=<val1 .. valn>   WMO stations to match
+ *   day=<start end>       range of days to search
+ *   dtime=<numhours>      maximum number of hours to search back (def=96)
+ *   match=<match strings> list of character match strings to find from text
+ *   num=<num>             number of matches to find (def=1)
+ *
+ * ------ for observational text data:
+ *
+ *   group=<group>         weather text group (default= RTWXTEXT)
+ *   descr=<descriptor>    weather text subgroup (default=SFCHOURLY)
+ *   id=<id1 id2 ... idn>  list of station ids
+ *   co=<co1 co2 ... con>  list of countries
+ *   reg=<reg1 reg2..regn> list of regions
+ *   newest=<day hour>     most recent time to allow in request 
+ *                         (def=current time)
+ *   oldest=<day hour>     oldest observation time to allow in request
+ *   type=<type>           numeric value for the type of ob
+ *   nhours=<numhours>     maximum number of hours to search
+ *   num=<num>             number of matches to find (def=1)
+ *
+ * -------------------------------------------------------------------------
+ * The following keywords are required:
+ *
+ *   group 
+ *   descr (except datasetinfo)
+ *
+ * These are case sensitive.  If you prefer to have them automatically upcased,
+ * you can supply a system property on the command line for your application:
+ *
+ *     <code>-Dadde.auto-upcase=true</code>
+ *
+ * or add the "auto-upcase=true" keyword to the URL
+ *
+ * An example URL for images might look like:
+ *
+ *   adde://viper/imagedata?group=gvar&band=1&user=tjj&proj=6999&version=1
+ *   
+ * </pre>
+ *
+ * @author Tommy Jasmin, University of Wisconsin, SSEC
+ * @author Don Murray, UCAR/Unidata
+ * @author Tom Whittaker, SSEC/CIMSS
+ * @author James Kelly, Australian Bureau of Meteorology
+ */
+public class AddeURLConnection extends URLConnection 
+{
+
+  private InputStream is = null;
+  private DataInputStream dis = null;
+  private DataOutputStream dos = null;
+  private URL url;
+
+  /** 
+     Set from the rawstream attribute. When true we don't read the first int bytes.
+     This enables client code to move the byte stream to disk.
+  */
+  private boolean rawStream = false;
+
+  /** The default number of lines for an image request */
+  private final int DEFAULT_LINES = 480;
+
+  /** The default number of elements for an image request */
+  private final int DEFAULT_ELEMS = 640;
+
+  /** The default user id*/
+  public static final String DEFAULT_USER = "XXXX";
+
+  /** The default number of elements for an image request */
+  public static final int DEFAULT_PROJ = 0;
+
+  /** The default user id*/
+  /** Size of an ADDE trailer */
+  private final static int TRAILER_SIZE = 92;
+
+  /** Size of an ADDE request */
+  private final static int REQUEST_SIZE = 120;
+
+  /** Size of an ADDE error message */
+  private final static int ERRMSG_SIZE = 72;
+
+  /** Size of an ADDE error message offset */
+  private final static int ERRMSG_OFFS = 8;
+
+  /** Flag for "compress" compression.  Used to be synonymous 
+      with the port used for compress transfer */
+  private final static int COMPRESS = 503;
+
+  /** Flag for "no compress" compression.  Used to be synonymous 
+      with the port used for non-compressed transfer */
+  private final static int NO_COMPRESS = 500;
+
+  /** Flag for GZip compression.  Used to be synonymous with the 
+      port used for compressed transfers */
+  private final static int GZIP = 112;
+
+  /** key for no compression */
+  private final int UNCOMPRESS_KEY = 1;
+
+  /** key for compress compression */
+  private final int COMPRESS_KEY = 2;
+
+  /** key for compress compression */
+  private final int GZIP_KEY = 3;
+
+  /** default port transfers */
+  public final static int DEFAULT_PORT = 112;
+
+  /**
+   * ADDE Version 1 indicator
+   */
+  public final static int VERSION_1 = 1;
+
+  // ADDE server requests
+  /** AGET request type */
+  public final static int AGET = 0;
+  /** ADIR request type */
+  public final static int ADIR = 1;
+  /** LWPR request type */
+  public final static int LWPR = 2;
+  /** GDIR request type */
+  public final static int GDIR = 3;
+  /** GGET request type */
+  public final static int GGET = 4;
+  /** MDKS request type */
+  public final static int MDKS = 5;
+  /** TXTG request type */
+  public final static int TXTG = 6;
+  /** WTXG request type */
+  public final static int WTXG = 7;
+  /** OBTG request type */
+  public final static int OBTG = 8;
+
+
+  // ADDE data types (not used?)
+  private final static int IMAGE = 100;
+  private final static int GRID  = 101;
+  private final static int POINT = 102;
+  private final static int TEXT  = 103;
+  private final static int WXTEXT  = 104;
+  private final static int OBTEXT  = 105;
+
+  private int numBytes = 0;
+  private int dataType = IMAGE;
+
+  private byte[] binaryData = null;   // byte array to hold extra binary data
+
+  /** request type - default to AGET */
+  private int reqType = AGET;
+
+  /** debug flag  - value can be overrided with debug=true */
+  private boolean debug = false;
+
+  /** port to use for compression */
+  private int portToUse = 0;  // set to zero for default
+
+  /** compression type */
+  private int compressionType = GZIP; 
+
+  /**
+   *
+   * Constructor: just sets URL and calls superclass constructor.
+   * Actual network connection is established in connect().
+   *
+   * @param url   url to use
+   */
+  AddeURLConnection(URL url)
+    throws IOException
+  {
+    super(url);
+    this.url = normalizeURL(url);
+  } 
+
+  /**
+   *
+   * Establishes an ADDE connection using the URL passed to the
+   * constructor.  Opens a socket on the ADDE port, and formulates
+   * an ADDE image data request based on the file portion of URL.
+   * <p>  
+   * An example URL might look like:
+   * <p>
+   *   adde://viper.ssec.wisc.edu/image?group=gvar&band=1&mag=-8&version=1
+   *   
+   */
+  synchronized public void connect ()
+    throws IOException, AddeURLException
+  {
+
+    // First, see if we can use the URL passed in
+    // verify the service is one we can handle 
+    // get rid of leading /
+    // keep original to preserve case for user= clause
+    String requestOriginal = url.getFile().substring(1);
+    String request = requestOriginal.toLowerCase();
+    String path = url.getPath().substring(1).toLowerCase();
+    String query = url.getQuery();
+    debug = debug || request.indexOf("debug=true") >= 0;
+
+    if (debug) System.out.println("host from URL: " + url.getHost());
+    if (debug) System.out.println("file from URL: " + url.getFile());
+
+    if (!path.startsWith("image") && 
+        (!path.startsWith("dataset")) &&
+        (!path.startsWith("dsinfo")) &&
+        (!path.startsWith("text")) &&
+        (!path.startsWith("wxtext")) &&
+        (!path.startsWith("obtext")) &&
+        (!path.startsWith("grid"))   && 
+        (!path.startsWith("point"))  )
+    {
+        throw new AddeURLException("Request for unknown data");
+    }
+
+    rawStream = (request.indexOf("rawstream=true")>=0);
+
+    // service - for area files, it's either AGET (Area GET) or 
+    // ADIR (AREA directory)
+    byte [] svc = null;
+    if (path.startsWith("imagedir") || path.equals(AddeURL.REQ_ADIR))
+    {
+        svc = "adir".getBytes();
+        reqType = ADIR;
+    }
+    else if (path.startsWith("dataset") || 
+             path.startsWith("dsinfo") ||
+             path.equals(AddeURL.REQ_LWPR))
+    {
+        svc = "lwpr".getBytes();
+        reqType = LWPR;
+    }
+    else if (path.startsWith("text") || path.equals(AddeURL.REQ_TXTG))
+    {
+        svc = "txtg".getBytes();
+        reqType = TXTG;
+    }
+    else if (path.startsWith("wxtext") || path.equals(AddeURL.REQ_WTXG))
+    {
+        svc = "wtxg".getBytes();
+        reqType = WTXG;
+    }
+    else if (path.startsWith("obtext") || path.equals(AddeURL.REQ_OBTG))
+    {
+        svc = "obtg".getBytes();
+        reqType = OBTG;
+    }
+    else if (path.startsWith("image") || path.equals(AddeURL.REQ_AGET))
+    {
+        svc = "aget".getBytes();
+        reqType = AGET;
+    }
+    else if (path.startsWith("griddir") || path.equals(AddeURL.REQ_GDIR))
+    {
+        svc = "gdir".getBytes();
+        reqType = GDIR;
+    }
+    else if (path.startsWith("grid") || path.equals(AddeURL.REQ_GGET))
+    {
+        svc = "gget".getBytes();
+        reqType = GGET;
+    }
+    else if (path.startsWith("point") || path.equals(AddeURL.REQ_MDKS))
+    {
+        svc = "mdks".getBytes();
+        reqType = MDKS;
+    }
+    else
+    {
+      throw new AddeURLException(
+          "Invalid or unsupported ADDE service= "+svc.toString() );
+    }
+    if (debug) System.out.println("Service = " + new String(svc));
+
+    // prep for real thing - get cmd from file part of URL
+    int test = requestOriginal.indexOf("?");
+    //String uCmd = (test >=0) ? requestOriginal.substring(test+1) : requestOriginal;
+    String uCmd = (query == null) ? requestOriginal : query;
+    if (debug) System.out.println("uCmd="+uCmd);
+
+    // build the command string
+    StringBuffer sb = new StringBuffer();
+    switch (reqType)
+    {
+        case AGET:
+            sb = decodeAGETString(uCmd);
+            break;
+        case ADIR:
+            sb = decodeADIRString(uCmd);
+            break;
+        case LWPR:
+            sb = decodeLWPRString(uCmd);
+            break;
+        case GDIR:
+            sb = decodeGDIRString(uCmd);
+            break;
+        case GGET:
+            sb = decodeGDIRString(uCmd);
+            break;
+        case MDKS:
+            sb = decodeMDKSString(uCmd);
+            break;
+        case TXTG:
+            sb = decodeTXTGString(uCmd);
+            break;
+        case WTXG:
+            sb = decodeWTXGString(uCmd);
+            break;
+        case OBTG:
+            sb = decodeOBTGString(uCmd);
+            break;
+    }
+
+    // indefinitely use ADDE version 1 unless it's a GGET request
+    sb.append(" VERSION=");
+    sb.append((reqType == GGET || reqType == WTXG) ? "A" : "1");
+
+    // now convert to array of bytes for output since chars are two byte
+    //String cmd = sb.toString().toUpperCase();
+    boolean a = Boolean.getBoolean("adde.auto-upcase");
+    boolean b = 
+      new Boolean(
+        getValue(uCmd, "auto-upcase=", "false")).booleanValue();
+    String cmd = (a || b) ? sb.toString().toUpperCase() : sb.toString();
+    if (debug) System.out.println(cmd);
+    byte [] ob = cmd.getBytes();
+
+    // get some other stuff
+
+    // user initials - pass on what client supplied in user= keyword
+    byte [] usr; 
+    String testStr = getValue(uCmd, "user=", DEFAULT_USER);
+    if (debug) System.out.println("user = " + testStr);
+    usr = testStr.getBytes();
+
+    // project number - we won't validate, but make sure it's there
+    int proj = 0;
+    testStr = getValue(uCmd, "proj=", ""+DEFAULT_PROJ);
+    if (debug) System.out.println("proj = " + testStr);
+    try {
+      proj = Integer.parseInt(testStr);
+    } catch (NumberFormatException e) {
+      // TODO: Should we really throw an exception or just let it default to 0?
+      throw new AddeURLException("Invalid project number: " + testStr);
+    }
+
+    // Figure out the port.  
+    if (url.getPort() == -1) { // not specified as part of URL
+
+      testStr = getValue(uCmd, "port=", null);
+      if (testStr != null) {
+        try {
+          portToUse = Integer.parseInt(testStr);
+        } catch (NumberFormatException e) {
+          // just use default
+          System.out.println(
+            "Warning: Invalid port number \"" + testStr + 
+            "\" specified;  using default port " + portToUse + " instead");
+        }
+      }
+    } else {  // specified 
+      portToUse = url.getPort();
+    }
+
+    // compression 
+    testStr = getValue(uCmd, "compress=", null);
+
+    // if no compress keyword and if port was specified, use that as 
+    // the default for compression.
+    if (testStr == null) {  
+      switch (portToUse) {
+        case NO_COMPRESS:     // port == 500
+        case UNCOMPRESS_KEY:  // port == 1
+          testStr = "none";
+          break;
+        case COMPRESS:        // port == 503
+        case COMPRESS_KEY:    // port == 2
+          testStr = "compress";
+          break;
+        case GZIP:            // port == 112
+        case GZIP_KEY:        // port == 3
+        default:
+          testStr = "gzip";
+          break;
+      }
+    }
+           
+    if (testStr.equalsIgnoreCase("gzip")) {
+
+      compressionType = GZIP;
+
+    } else if (testStr.equalsIgnoreCase("compress") ||
+               testStr.equalsIgnoreCase("true")) {
+
+      // check to see if we can do uncompression
+      try {
+        Class c = Class.forName("HTTPClient.UncompressInputStream");
+        compressionType = COMPRESS;
+      } catch (ClassNotFoundException cnfe) {
+        System.err.println(
+          "Uncompression code not found, turning compression off");
+      }
+
+    } else if (testStr.equalsIgnoreCase("none")) {
+        compressionType = NO_COMPRESS;
+    }
+
+    if (debug) System.out.println("compression = " + testStr);
+
+    // zero by default, for newer mcservs (1,2,3)
+    if (portToUse < 4) {
+      portToUse = DEFAULT_PORT;
+    }
+
+// end of request decoding
+//-------------------------------------------------------------------------
+// now write this all to the port
+
+    // first figure out which port to connect on.  This can either be
+    // one specified by the user.  
+    //
+    // The priority is URL port, port=keyword, compression port (default)
+    
+    // get the IP address of the server
+    byte [] ipa = new byte[4];
+    InetAddress ia = InetAddress.getByName(url.getHost());
+    ipa = ia.getAddress();
+
+    // if local ADDE host, force the compressionType to "off" 
+    if (ipa[0]==127 && ipa[1]==0 && ipa[2]==0 && ipa[3]==1) {
+      compressionType = NO_COMPRESS;
+    }
+
+    if (debug)  {
+      System.out.println("connecting on port " + portToUse + " using " +
+                         (compressionType == GZIP
+                             ? "gzip"
+                             : compressionType == COMPRESS
+                                 ? "compress"
+                                 : "no") + " compression.");
+    }
+
+    Socket t;
+    try {
+      t = new Socket(url.getHost(), portToUse);   // DRM 03-Mar-2001
+    } catch (UnknownHostException e) {
+      throw new AddeURLException(e.toString());
+    }
+
+    dos = new DataOutputStream ( t.getOutputStream() );
+
+    /*
+     Now start pumping data to the server.  The sequence is:
+
+        -  ADDE version (1 for now)
+        -  Server IP and Port number  (latter used to determine compression)
+        -  Service Type (AGET, ADIR, etc)
+        -  Server IP and Port number (again)
+        -  Client address
+        -  User, project, password
+        -  Service Type (again)
+        -  Actual request
+
+     */
+
+    // send version number - ADDE seems to be stuck at 1
+    dos.writeInt(VERSION_1);
+
+    // send IP address of server
+    // we know the server IP address is good cause we used it above
+    dos.write(ipa, 0, ipa.length);
+
+    // send ADDE port number which server uses to determine compression
+    dos.writeInt(compressionType);
+
+    // send the service type
+    dos.write(svc, 0, svc.length);
+
+    // now build and send request block, repeat some stuff
+    // server IP address, port
+    dos.write(ipa, 0, ipa.length);
+    dos.writeInt(compressionType);   // DRM 03-Mar-2001
+
+    // client IP address
+    InetAddress lh = InetAddress.getLocalHost();
+    ipa = lh.getAddress();
+    dos.write(ipa, 0, ipa.length);
+
+    // gotta send 4 user bytes, ADDE protocol expects it
+    if (usr.length <= 4) {
+      dos.write(usr, 0, usr.length);
+      for (int i = 0; i < 4 - usr.length; i++) {
+        dos.writeByte(' ');
+      }
+    } else {
+      // if id entered was > 4 chars, complain
+      throw new AddeURLException("Invalid user id: " + new String(usr));
+    }
+
+    dos.writeInt(proj);
+
+    // password chars - not used either
+    byte [] pwd = new byte[12];
+    dos.write(pwd, 0, pwd.length);
+
+    // service - resend svc array 
+    dos.write(svc, 0, svc.length);
+
+    // Write out the data.  There are 2 cases:
+    //
+    //  1) ob.length <= 120 
+    //  2) ob.length > 120 
+    //
+    // In either case, there may or may not be additional binary data
+    // 
+
+    int numBinaryBytes = 0;
+    if (binaryData != null) numBinaryBytes = binaryData.length;
+
+    if (ob.length > REQUEST_SIZE)
+    {
+      if (debug)  System.out.println("numBinaryBytes= " + numBinaryBytes);
+      dos.writeInt(ob.length + numBinaryBytes); // number of additional bytes
+      dos.writeInt(ob.length);                  // number of bytes in request
+      for (int i=0; i < REQUEST_SIZE - 4; i++) {  // - 4 accounts for prev line
+        dos.writeByte(0);
+      }
+      dos.write(ob,0,ob.length);
+    } else {
+      if (debug)  System.out.println("numBinaryBytes= " + numBinaryBytes);
+      dos.writeInt(numBinaryBytes);
+      dos.write(ob, 0, ob.length);
+      for (int i=0; i < REQUEST_SIZE - ob.length; i++) {
+        dos.writeByte(' ');
+      }
+    }
+
+    if (numBinaryBytes > 0) dos.write(binaryData, 0, numBinaryBytes);
+
+    is = (compressionType == GZIP) 
+        ? new GZIPInputStream(t.getInputStream())
+        : (compressionType == COMPRESS)
+            ? new UncompressInputStream(t.getInputStream())
+            : t.getInputStream();
+    dis = new DataInputStream(is);
+
+    if (debug && (compressionType != portToUse) ) {
+        System.out.println("Compression is turned "+
+        ((compressionType == NO_COMPRESS)?"OFF":("ON using " +
+                            ((compressionType == GZIP)?"GZIP":"compress"))));
+    }
+
+    // get response from server, byte count coming
+
+    if(!rawStream) {
+      numBytes = dis.readInt();
+      if (debug) System.out.println("server is sending: " + numBytes + " bytes");
+
+      // if server returns zero, there was an error so read trailer and exit
+      if (numBytes == 0) {
+        byte [] trailer = new byte[TRAILER_SIZE];
+        dis.readFully(trailer, 0, trailer.length);
+        String errMsg = new String(trailer, ERRMSG_OFFS, ERRMSG_SIZE);
+        throw new AddeURLException(errMsg);
+      }
+    }
+
+    // if we made it to here, we're getting data
+    connected = true;
+
+  }
+
+  private static char AMPERSAND = '&';
+
+  /**
+   * Search for a value in the string and return the value or the default
+   * @param stringToSearch    String to search for the value
+   * @param key               key to search for (includes '=' to disambiguate);
+   * @param default           default value
+   * @return value in string
+   */
+  private String getValue(String stringToSearch, String key, String deflt) {
+    int startIdx = stringToSearch.toLowerCase().lastIndexOf(key);
+    String retVal = deflt;
+    if (startIdx >= 0) {
+      int endIdx = stringToSearch.indexOf(AMPERSAND, startIdx);
+      if (endIdx == -1) { // last on line
+        endIdx = stringToSearch.length(); 
+      }
+      retVal = stringToSearch.substring((startIdx + key.length()), endIdx);
+    } 
+    return retVal;
+  }
+
+  /**
+   * Get the request type 
+   * @return  type of request (ADIR, AGET, etc)
+   */
+  public int getRequestType()
+  {
+      return reqType;
+  }
+
+  /**
+   * returns a reference to InputStream established in connect().
+   * calls connect() if client has not done so yet.
+   *
+   * @return            InputStream reference
+   */
+
+  synchronized public InputStream getInputStream ()
+    throws IOException
+  {
+    if (!connected) connect();
+    return is;
+  }
+
+  /**
+   *
+   * returns a reference to DataInputStream established in connect().
+   * calls connect() if client has not done so yet.
+   *
+   * @return            DataInputStream reference
+   */
+
+  synchronized public DataInputStream getDataInputStream ()
+    throws IOException
+  {
+    if (!connected) connect();
+    return dis;
+  }
+
+
+  /**
+   * Return the number of bytes being sent by the server for the 
+   * first record.
+   *
+   * @return  number of bytes send in the first record
+   */
+  public int getInitialRecordSize()
+  {
+      return numBytes;
+  }
+
+
+    /**
+     * Decode the ADDE request for image data.
+     *
+     * there can be any valid combination of the following supported keywords:
+     *
+     * <pre>
+     *
+     *   group=<groupname>         ADDE group name
+     *   descr=<descriptor>        ADDE descriptor name
+     *   band=<band>               spectral band or channel number 
+     *   mag=<lmag> <emag>   image magnification, postitive for blowup, 
+     *                                   negative for blowdown (default = 1, 
+     *                                   emag=lmag) 
+     *   latlon=<lat> <lon>  lat/lon point to center image on 
+     *
+     *   linele=<lin> <ele> <type> line/element to center image on 
+     *                                   type = i(image, default), e(earth), a(area)
+     *   place=<placement>         placement of lat/lon or linele points 
+     *                                   c(center, default) or u(upperleft)  
+     *   pos=<position>            request an absolute or relative ADDE position
+     *                                   number
+     *
+     *   size=<lines> <elements>   size of image to be returned
+     *   unit=<unit>               to specify calibration units other than the 
+     *                                   default 
+     *   spac=<bytes>              number of bytes per data point, 1, 2, or 4 
+     *   doc=<yes/no>              specify yes to include line documentation 
+     *                                   with image (def=no) 
+     *   nav=<lalo>                include the lat-lon navigation info and not the O&A.
+     *   aux=<yes/no>              specify yes to include auxilliary information
+     *                                   with image 
+     *   time=<time1> <time2>      specify the time range of images to select
+     *                                   (def=latest image if pos not specified)
+     *   day=<day>                 specify the day of the images to select
+     *                                   (def=latest image if pos not specified)
+     *   cal=<cal type>            request a specific calibration on the image 
+     *   id=<stn id>               radar station id
+     *   trace=<0/1>               setting to 1 tells server to write debug 
+     *                                   trace file (imagedata, imagedirectory)
+     *
+     * the following keywords are required:
+     *
+     *   group
+     *
+     * an example URL might look like:
+     *   adde://viper/imagedata?group=gvar&band=1
+     *   
+     * </pre>
+     */
+    private StringBuffer decodeAGETString(String uCmd)
+    {
+        StringBuffer buf = new StringBuffer();
+        boolean latFlag = false;
+        boolean lonFlag = false;
+        boolean linFlag = false;
+        boolean eleFlag = false;
+        String latString = null;
+        String lonString = null;
+        String linString = null;
+        String eleString = null;
+        String tempString = null;
+        String testString = null;
+        String lctestString = null;
+        // Mandatory strings
+        String groupString = null;
+        String descrString = "ALL";
+        String posString = "0";
+        String numlinString = Integer.toString(DEFAULT_LINES);
+        String numeleString = Integer.toString(DEFAULT_ELEMS);
+        String magString = "X";
+        String traceString = "TRACE=0";
+        String spaceString = "SPAC=X";
+        String unitString = "UNIT=BRIT";
+        String auxString = "AUX=YES";
+        String navString = "NAV=X";
+        String calString = "CAL=X";
+        String docString = "DOC=NO";
+        String timeString = "TIME=X X I";
+        String lineleType = "A";
+        String placement = "C";
+        String trackString = "TRACKING=0";
+
+        StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+        while (cmdTokens.hasMoreTokens())
+        {
+            testString = cmdTokens.nextToken();
+            lctestString = testString.toLowerCase();
+            // group, descr and pos are mandatory
+            if (lctestString.startsWith("grou"))
+            {
+                groupString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("des"))
+            {
+                descrString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("pos"))
+            {
+                posString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("lat"))        // lat or latlon
+            {
+                latString = 
+                    testString.substring(testString.indexOf("=") + 1).trim();
+                latFlag = true;
+                if (latString.indexOf(" ") > 0)  // is latlon, not just lat
+                {
+                    StringTokenizer tok = new StringTokenizer(latString);
+                    if (tok.countTokens() < 2) break;
+                    for (int i = 0; i < 2; i++)
+                    {
+                        tempString = tok.nextToken();
+                        if (i == 0)
+                            latString = tempString;
+                        else
+                        {
+                            lonString = negateLongitude(tempString);
+                            lonFlag = true;
+                        }
+                    }
+                }
+            }
+            else
+            if (lctestString.startsWith("lon"))
+            {
+                lonFlag = true;
+                lonString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("lin"))     // line keyword or linele
+            {
+                tempString = 
+                    testString.substring(testString.indexOf("=") + 1);
+                if (tempString.indexOf(" ") > 0)  // is linele, not just lin
+                {
+                    StringTokenizer tok = new StringTokenizer(tempString);
+                    if (tok.countTokens() < 2) break;
+                    for (int i = 0; i < 2; i++)
+                    {
+                        tempString = tok.nextToken();
+                        if (i == 0)
+                        {
+                           linString = tempString;
+                           linFlag = true;
+                        }
+                        else
+                        {
+                           eleString = tempString;
+                           eleFlag = true;
+                        }
+                    }
+                    if (tok.hasMoreTokens())  // specified file or image coords
+                    {
+                        tempString = tok.nextToken().toLowerCase();
+                        if (tempString.startsWith("i")) lineleType = "i";
+                    }
+                }
+                else  // is just lines string
+                {
+                    numlinString = tempString;
+                }
+            }
+            else
+            if (lctestString.startsWith("ele"))    // elements keyword
+            {
+                numeleString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("pla"))    // placement keyword
+            {
+                if (testString.substring(
+                    testString.indexOf("=") + 1).toLowerCase().startsWith("u"))
+                        placement = "u";
+            }
+            else
+            if (lctestString.startsWith("mag"))
+            {
+                tempString = 
+                    testString.substring(testString.indexOf("=") + 1);
+                if (tempString.indexOf(" ") > 0)  // is more than one mag
+                {
+                    StringTokenizer tok = new StringTokenizer(tempString);
+                    if (tok.countTokens() < 2) break;
+                    for (int i = 0; i < 2; i++)
+                    {
+                        buf.append(" ");
+                        tempString = tok.nextToken();
+                        if (i == 0)
+                           buf.append("LMAG=" + tempString);
+                        else
+                           buf.append("EMAG=" + tempString);
+                    }
+                }
+                else
+                    magString = tempString;
+            }
+            // now get the rest of the keywords (but filter out non-needed)
+            else
+            if (lctestString.startsWith("size"))       // size keyword
+            {
+                tempString = 
+                    testString.substring(testString.indexOf("=") + 1);
+                if (tempString.trim().equalsIgnoreCase("all")) {
+                   numlinString = "99999";
+                   numeleString = "99999";
+                } 
+                else if (tempString.indexOf(" ") > 0) //is linele, not just lin
+                {
+                    StringTokenizer tok = new StringTokenizer(tempString);
+                    if (tok.countTokens() < 2) break;
+                    for (int i = 0; i < 2; i++)
+                    {
+                        tempString = tok.nextToken();
+                        if (i == 0)
+                           numlinString = tempString;
+                        else
+                           numeleString = tempString;
+                    }
+                }
+            }
+            else
+            if (lctestString.startsWith("trace"))       // trace keyword
+            {
+                traceString = testString;
+            }
+            else
+            if (lctestString.startsWith("spa"))       // spa keyword
+            {
+                spaceString = testString;
+            }
+            else
+            if (lctestString.startsWith("nav"))       // nav keyword
+            {
+                navString = testString;
+            }
+            else
+            if (lctestString.startsWith("aux"))       // aux keyword
+            {
+                auxString = testString;
+            }
+            else
+            if (lctestString.startsWith("track"))      // track keyword
+            {
+                trackString = testString;
+            }
+            else
+            if (lctestString.startsWith("uni"))      // unit keyword
+            {
+                unitString = testString;
+            }
+            else
+            if (lctestString.startsWith("cal"))      // cal keyword
+            {
+                calString = testString;
+            }
+            else
+            if (lctestString.startsWith("doc"))      // doc keyword
+            {
+                docString = testString;
+            }
+            else
+            if (lctestString.startsWith("tim"))      // time keyword
+            {
+                timeString = testString;
+            }
+            else
+            if (lctestString.startsWith("ban"))      // band keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("day"))       // day keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("id"))        // id keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("lmag"))      // lmag keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("emag"))      // emag keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+        } 
+        buf.append(" ");
+        buf.append(traceString);
+        buf.append(" ");
+        buf.append(spaceString);
+        buf.append(" ");
+        buf.append(unitString);
+        buf.append(" ");
+        buf.append(navString);
+        buf.append(" ");
+        buf.append(auxString);
+        buf.append(" ");
+        buf.append(trackString);
+        buf.append(" ");
+        buf.append(docString);
+        buf.append(" ");
+        buf.append(timeString);
+        buf.append(" ");
+        buf.append(calString);
+
+        // now create command string
+        StringBuffer posParams = 
+            new StringBuffer(
+                groupString + " " + descrString + " " + posString.toUpperCase() + " ");
+
+        // Set up location information
+        String locString = "X X X ";
+        if (latFlag && lonFlag) {
+            locString = "e" + placement + " " + latString + " " + lonString + " ";
+        } else if (linFlag && eleFlag) {
+            locString = lineleType + placement +"  " + linString + " " + eleString + " ";
+        } 
+        posParams.append(locString.toUpperCase());
+
+        // add on the mag, lin and ele pos params
+        posParams.append(magString + " " + numlinString + 
+                         " " + numeleString + " ");
+
+        return new StringBuffer(posParams + buf.toString().toUpperCase());
+
+    }
+
+    /**
+     * Decode the ADDE request for grid directory information.
+     *
+     *
+     * there can be any valid combination of the following supported keywords:
+     *
+     * <pre>
+     *   group=<groupname>       ADDE group name
+     *   descr=<descriptor>      ADDE descriptor name
+     *   param=<param list>      parameter code list
+     *   time=<model run time>   time in hhmmss format
+     *   day=<model run day>     day in ccyyddd format
+     *   lev=<level list>        list of requested levels (value or SFC, MSL 
+     *                             or TRO)
+     *   ftime=<forecast time>   valid time (hhmmss format) (use with fday)
+     *   fday=<forecast day>     forecast day (ccyyddd)
+     *   fhour=<forecast hours>  forecast hours (offset from model run time)
+     *                                (hhmmss format)
+     *   lat=<min lat> <max lat> latitude bounding box (needs lon specified)
+     *   lon=<min lon> <max lon> longitude bounding box (needs lat specified)
+     *   row=<min row> <max row> row bounding box (needs col specified)
+     *   col=<min col> <max col> column bounding box (needs row specified)
+     *   skip=<row> <col>        skip factors for rows and columns (def = 1 1)
+     *   num=<max>               maximum number of grids (nn) to return (def=1)
+     *   trace=<0/1>             setting to 1 tells server to write debug 
+     *                             trace file (imagedata, imagedirectory)
+     *
+     * the following keywords are required:
+     *
+     *   group
+     *
+     * an example URL might look like:
+     *   adde://noaaport/griddirectory?group=ngm&num=10
+     *   
+     * </pre>
+     */
+    private StringBuffer decodeGDIRString(String uCmd) {
+      StringBuffer buf = new StringBuffer();
+      String testString, tempString, lctestString;
+      String groupString = null;
+      String descrString = "ALL";
+      String sizeString = " 999999 ";
+      String traceString = "TRACE=0";
+      String numString = "NUM=1";
+      String subsetString = null;
+      String latString = null;
+      String lonString = null;
+      String rowString = null;
+      String colString = null;
+      String srcString = null;
+      String skip = null;
+
+      StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+      while (cmdTokens.hasMoreTokens()) {
+        testString = cmdTokens.nextToken();
+        lctestString = testString.toLowerCase();
+
+        // group, descr and pos are mandatory
+        if (lctestString.startsWith("grou")) {
+            groupString = 
+                testString.substring(testString.indexOf("=") + 1);
+
+        } else if (lctestString.startsWith("des")) {
+            descrString = 
+                testString.substring(testString.indexOf("=") + 1);
+
+        // now get the rest of the keywords (but filter out non-needed)
+        } else if (lctestString.startsWith("num")) {
+            numString = testString;
+
+        } else if (lctestString.startsWith("tra")) {      // trace keyword
+          traceString = testString;
+
+        } else if (lctestString.startsWith("pos")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("par")) {
+          buf.append(" ");
+          buf.append("parm=");
+          buf.append(testString.substring(testString.indexOf("=") + 1));
+
+        } else if (lctestString.startsWith("fho")) {
+          buf.append(" ");
+          buf.append("vt=");
+          String iHMS = 
+             testString.substring(testString.indexOf("=") + 1).trim();
+          buf.append(iHMS);
+          if (iHMS.length() < 5) buf.append("0000");
+
+        } else if (lctestString.startsWith("day")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("time")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("lev")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("fday")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("ftime")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("vt")) {    // deprecated
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("lat")) {
+          latString = 
+              ensureTwoValues(
+                  testString.substring(testString.indexOf("=") + 1));
+
+        } else if (lctestString.startsWith("lon")) {
+          lonString = 
+            adjustLongitudes(
+              ensureTwoValues(
+                  testString.substring(testString.indexOf("=") + 1)));
+      
+
+        } else if (lctestString.startsWith("row")) {
+          rowString = 
+              ensureTwoValues(
+                  testString.substring(testString.indexOf("=") + 1));
+
+        } else if (lctestString.startsWith("col")) {
+          colString = 
+              ensureTwoValues(
+                  testString.substring(testString.indexOf("=") + 1));
+
+        } else if (lctestString.startsWith("skip")) {
+          skip = 
+              ensureTwoValues(
+                  testString.substring(testString.indexOf("=") + 1));
+        
+        // added with great pains for James DRM 2001-07-05 ;-)
+        } else if (lctestString.startsWith("src")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("gpro")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("trang")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("frang")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        } else if (lctestString.startsWith("drang")) {
+          buf.append(" ");
+          buf.append(testString);
+
+        /*
+        } else {
+          System.out.println("Unknown token = "+testString);
+        */
+        } 
+      }
+      buf.append(" ");
+      buf.append(numString);
+      buf.append(" ");
+      buf.append(traceString);
+      buf.append(" ");
+      //buf.append(" version=A ");
+
+      // Create a subset string
+      if (latString != null && lonString != null)
+      {
+          StringBuffer subBuf = new StringBuffer();
+          subBuf.append("subset=");
+          subBuf.append(latString);
+          subBuf.append(" ");
+          subBuf.append(lonString);
+          subBuf.append(" ");
+          subBuf.append((skip == null) ? "1 1" : skip);
+          subBuf.append(" LATLON");
+          subsetString = subBuf.toString();
+          if (debug) System.out.println(subsetString);
+      }
+      else if (rowString != null && colString != null)
+      {
+          StringBuffer subBuf = new StringBuffer();
+          subBuf.append("subset=");
+          subBuf.append(rowString);
+          subBuf.append(" ");
+          subBuf.append(colString);
+          subBuf.append(" ");
+          subBuf.append((skip == null) ? "1 1" : skip);
+          subBuf.append(" ROWCOL");
+          subsetString = subBuf.toString();
+          if (debug) System.out.println(subsetString);
+      }
+      else if (skip != null) {  // only skip specified
+          StringBuffer subBuf = new StringBuffer();
+          subBuf.append("subset=1 99999 1 99999");
+          subBuf.append(" ");
+          subBuf.append(skip);
+          subBuf.append(" ROWCOL");
+          subsetString = subBuf.toString();
+          if (debug) System.out.println(subsetString);
+      }
+
+      if (subsetString != null) buf.append(subsetString);
+
+      // create command string
+      String posParams = 
+         groupString + " " + descrString + " " + sizeString + " ";
+
+      return new StringBuffer(posParams + buf.toString().toUpperCase());
+
+
+    }
+
+
+    /**
+     * Decode the ADDE request for image directory information.
+     *
+     * <pre>
+     * there can be any valid combination of the following supported keywords:
+     *
+     *   group=<groupname>         ADDE group name
+     *   descr=<descriptor>        ADDE descriptor name
+     *   band=<band>               spectral band or channel number 
+     *   pos=<position>            request an absolute or relative ADDE position
+     *                               number
+     *   doc=<yes/no>              specify yes to include line documentation 
+     *                               with image (def=no) 
+     *   nav=<lalo>                include the lat-lon navigation info and not the O&A.
+     *   aux=<yes/no>              specify yes to include auxilliary information
+     *                               with image 
+     *   time=<time1> <time2>      specify the time range of images to select
+     *                               (def=latest image if pos not specified)
+     *   day=<day>                 specify the day of the images to select
+     *                               (def=latest image if pos not specified)
+     *   cal=<cal type>            request a specific calibration on the image 
+     *   id=<stn id>               radar station id
+     *   trace=<0/1>               setting to 1 tells server to write debug 
+     *                               trace file (imagedata, imagedirectory)
+     *
+     * the following keywords are required:
+     *
+     *   group
+     *
+     * an example URL might look like:
+     *   adde://viper/imagedirectory?group=gvar&descr=east1km&band=1
+     *   
+     * </pre>
+     */
+    private StringBuffer decodeADIRString(String uCmd)
+    {
+        StringBuffer buf = new StringBuffer();
+        String testString;
+        String lctestString;
+        String tempString;
+        // Mandatory strings
+        String groupString = null;
+        String descrString = "ALL";
+        String posString = "0 0";
+        String traceString = "TRACE=0";
+        String bandString = "BAND=ALL X";
+        String auxString = "AUX=YES";
+        String trackString = "TRACKING=0";
+
+        StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+        while (cmdTokens.hasMoreTokens())
+        {
+            testString = cmdTokens.nextToken();
+            lctestString = testString.toLowerCase();
+            // group, descr and pos are mandatory
+            if (lctestString.startsWith("grou"))
+            {
+                groupString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("des"))
+            {
+                descrString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("pos"))
+            {
+                tempString = 
+                    testString.substring(testString.indexOf("=") + 1).toLowerCase();
+                if (tempString.equals("")) {  // null string
+                  posString = "0 0";
+                } else {
+
+                  // see if a single argument
+                  StringTokenizer stp = new StringTokenizer(tempString," ");
+                  if (stp.countTokens() == 1) {
+                    if (tempString.equals("all")) {  // put in numeric
+                      posString =  "1095519264";
+
+                    } else if (tempString.equals("x")) {
+                      posString = "X X";
+
+                    } else {
+                      int posval = Integer.parseInt(stp.nextToken().trim());
+                      if (posval <= 0) {  // if value < 0 insert 0 as ending
+                        posString = tempString + " 0";
+                      } else {
+                        posString = tempString + " " + tempString;  // else default
+                      }
+                    }
+
+                  } else {  // more than one value...just copy it
+                    posString = tempString;
+                  }
+
+                }
+
+            }
+            // now get the rest of the keywords (but filter out non-needed)
+            else
+            if (lctestString.startsWith("trace"))       // trace keyword
+            {
+                traceString = testString;
+            }
+            else
+            if (lctestString.startsWith("aux"))       // aux keyword
+            {
+                auxString = testString;
+            }
+            else
+            if (lctestString.startsWith("track"))       // track keyword
+            {
+                trackString = testString;
+            }
+            else
+            if (lctestString.startsWith("ban"))      // band keyword
+            {
+                bandString = testString;
+            }
+            else
+            if (lctestString.startsWith("tim"))       // time keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("day"))       // time keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("id"))        // id keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+        } 
+        buf.append(" ");
+        buf.append(traceString);
+        buf.append(" ");
+        buf.append(bandString);
+        buf.append(" ");
+        buf.append(auxString);
+        buf.append(" ");
+        buf.append(trackString);
+
+        // now create case sensitive command string
+        String posParams = 
+                groupString + " " + descrString + " " + posString + " ";
+
+
+        return new StringBuffer(posParams + buf.toString().toUpperCase());
+    }
+
+
+    /**
+     * Decode the ADDE request for a text file.
+     *
+     * <pre>
+     * there can be any valid combination of the following supported keywords:
+     *
+     *   file=<filename>    the text file name on the server
+     *   descr=<dataset>    the dataset name on the server
+     *   group=<group>      the ADDE group name for this TEXT
+     *
+     * the following keywords are required:
+     *
+     *   file or descr
+     *
+     * an example URL might look like:
+     *   adde://viper/text?group=textdata&file=myfile.txt
+     *   
+     * </pre>
+     */
+    public StringBuffer decodeTXTGString(String uCmd)
+    {
+        StringBuffer buf = new StringBuffer();
+        String testString;
+        String lctestString;
+        String groupString = null;
+        String filenameString = null;
+        String descrString = null;
+        String traceString = "TRACE=0";
+
+        StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+        while (cmdTokens.hasMoreTokens())
+        {
+            testString = cmdTokens.nextToken();
+            lctestString = testString.toLowerCase();
+            if (lctestString.startsWith("desc"))
+            {
+                descrString =
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else if (lctestString.startsWith("file"))
+            {
+                filenameString = "FILE="+
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else if (lctestString.startsWith("grou"))
+            {
+                groupString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else if (lctestString.startsWith("tra"))       // trace keyword
+            {
+                traceString = testString;
+            }
+
+        }
+
+        buf.append(groupString);
+        buf.append(" ");
+        buf.append(descrString);
+        buf.append(" ");
+        buf.append(filenameString);
+        buf.append(" ");
+        buf.append(traceString.toUpperCase());
+        return buf;
+    }
+
+
+    /**
+     * Decode the ADDE request for a weather text.
+     *
+     * <pre>
+     * there can be any valid combination of the following supported keywords:
+     *
+     *   group=<group>         weather text group (default= RTWXTEXT)
+     *   prod=<product>        predefind product name
+     *   apro=<val1 .. valn>   AFOS/AWIPS product headers to match (don't 
+     *                         use with wmo keyword
+     *   astn=<val1 .. valn>   AFOS/AWIPS stations to match
+     *   wmo= <val1 .. valn>   WMO product headers to match (don't 
+     *                         use with apro keyword
+     *   wstn=<val1 .. valn>   WMO stations to match
+     *   day=<start end>       range of days to search (def = current)
+     *   dtime=<numhours>      maximum number of hours to search back (def=96)
+     *   match=<match strings> list of character match strings to find from text
+     *   num=<num>             number of matches to find (def=1)
+     *
+     * the following keywords are required:
+     *
+     *   day  (should default to current, but there's a bug)
+     *   apro, astn or wstn
+     *
+     * an example URL might look like:
+     *   adde://viper/text?group=textdata&file=myfile.txt
+     *   
+     * </pre>
+     */
+    public StringBuffer decodeWTXGString(String uCmd)
+    {
+        StringBuffer buf = new StringBuffer();
+        String testString;
+        String lctestString;
+        String tempString;
+        String numString = "NUM=1";
+        String dTimeString = "DTIME=96.0000";
+        String traceString = "TRACE=0";
+        String dayString = "DAY="+McIDASUtil.mcSecsToDayTime(System.currentTimeMillis()/1000l)[0];
+        // Mandatory strings
+        String groupString = "RTWXTEXT";
+
+        StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+        while (cmdTokens.hasMoreTokens())
+        {
+            testString = cmdTokens.nextToken();
+            lctestString = testString.toLowerCase();
+            // group, descr and pos are mandatory
+            if (lctestString.startsWith("grou"))
+            {
+                groupString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("apro"))       // apro keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("astn"))       // astn keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("day"))       // day keyword
+            {
+                dayString = testString;
+            }
+            else
+            if (lctestString.startsWith("mat"))        // match keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("prod"))       // prod keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("sour"))       // source keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("wmo"))       // wmo keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("wstn"))       // wstn keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("tra"))       // trace keyword
+            {
+                traceString = testString;
+            }
+            else
+            if (lctestString.startsWith("num"))       // num keyword
+            {
+                numString = testString;
+            }
+            else
+            if (lctestString.startsWith("dtim"))       // dtime keyword
+            {
+                dTimeString = testString;
+            }
+        } 
+
+        buf.append(" ");
+        buf.append(dayString);
+        buf.append(" ");
+        buf.append(dTimeString);
+        buf.append(" ");
+        buf.append(numString);
+        buf.append(" ");
+        buf.append(traceString);
+
+        // now create case sensitive command string
+        String posParams = groupString + " ";
+
+        return new StringBuffer(posParams + buf.toString().toUpperCase());
+    }
+
+    /**
+     * Decode the ADDE request for a weather observation text.
+     *
+     * <pre>
+     * there can be any valid combination of the following supported keywords:
+     *
+     *
+     *   group=<group>         weather text group (default= RTWXTEXT)
+     *   descr=<descriptor>    weather text subgroup (default=SFCHOURLY)
+     *   id=<id1 id2 ... idn>  list of station ids
+     *   co=<co1 co2 ... con>  list of countries
+     *   reg=<reg1 reg2..regn> list of regions
+     *   newest=<day hour>     most recent time to allow in request 
+     *                         (def=current time)
+     *   oldest=<day hour>     oldest observation time to allow in request
+     *   type=<type>           numeric value for the type of ob
+     *   nhours=<numhours>     maximum number of hours to search
+     *   num=<num>             number of matches to find (def=1)
+     *
+     * the following keywords are required:
+     *
+     *   group
+     *   descr
+     *   id, co, or reg
+     *
+     * an example URL might look like:
+     *  adde://adde.ucar.edu/obtext?group=rtwxtext&descr=sfchourly&id=kden&num=2
+     *   
+     * </pre>
+     */
+    public StringBuffer decodeOBTGString(String uCmd)
+    {
+        StringBuffer buf = new StringBuffer();
+        String testString;
+        String lctestString;
+        String tempString;
+        String numString = "NUM=1";
+        String traceString = "TRACE=0";
+        // Mandatory strings
+        String groupString = "RTWXTEXT";
+        String descrString = "SFCHOURLY";
+        String idreqString = "IDREQ=LIST";
+
+        StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+        while (cmdTokens.hasMoreTokens())
+        {
+            testString = cmdTokens.nextToken();
+            lctestString = testString.toLowerCase();
+            // group, descr and pos are mandatory
+            if (lctestString.startsWith("grou"))
+            {
+                groupString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("desc"))
+            {
+                descrString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("id"))       // id keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("co"))       // co keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("reg"))       // reg keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("nhou"))       // nhour keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("new"))       // newest keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("old"))       // oldest keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("type"))       // type keyword
+            {
+                buf.append(" ");
+                buf.append(testString);
+            }
+            else
+            if (lctestString.startsWith("tra"))       // trace keyword
+            {
+                traceString = testString;
+            }
+            else
+            if (lctestString.startsWith("num"))       // num keyword
+            {
+                numString = testString;
+            }
+        } 
+
+        buf.append(" ");
+        buf.append(numString);
+        buf.append(" ");
+        buf.append(traceString);
+
+        // now create case sensitive command string
+        String posParams = groupString + " " + descrString + " " + idreqString;
+
+        return new StringBuffer(posParams + buf.toString().toUpperCase());
+    }
+
+    /**
+     * Decode the ADDE request for data set information.
+     *
+     * <pre>
+     * there can be any valid combination of the following supported keywords:
+     *
+     *   group=<groupname>    ADDE group name
+     *   type=<datatype>      ADDE data type.  Must be one of the following:
+     *                             IMAGE, POINT, GRID, TEXT, NAV
+     *                        the default is the IMAGE type.
+     *
+     * the following keywords are required:
+     *
+     *   group
+     *
+     * an example URL might look like:
+     *   adde://viper/datasetinfo?group=gvar&type=image
+     *   
+     * </pre>
+     */
+    public StringBuffer decodeLWPRString(String uCmd)
+    {
+        StringBuffer buf = new StringBuffer();
+        String testString;
+        String lctestString;
+        String tempString;
+        String groupString = null;
+        String typeString = "ALA.";
+
+        StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+        while (cmdTokens.hasMoreTokens())
+        {
+            testString = cmdTokens.nextToken();
+            lctestString = testString.toLowerCase();
+            // group, descr and pos are mandatory
+            if (lctestString.startsWith("grou"))
+            {
+                groupString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            if (lctestString.startsWith("type"))
+            {
+                tempString = 
+                    testString.substring(testString.indexOf("=") + 1).toLowerCase();
+                if (tempString.startsWith("i")) 
+                    typeString = "ALA.";
+                if (tempString.startsWith("g")) 
+                    typeString = "ALG.";
+                else if (tempString.startsWith("p"))
+                    typeString = "ALM.";
+                else if (tempString.startsWith("t")) 
+                    typeString = "ALT.";
+                else if (tempString.startsWith("n")) 
+                    typeString = "ALN.";
+                else if (tempString.startsWith("s")) 
+                    typeString = "ALN.";
+            }
+
+        }
+        buf.append(typeString);
+        buf.append(groupString);
+        return buf;
+    }
+
+
+    /**
+     * Decode the ADDE request for point data.
+     *
+     * If the request contains specific parameters (eg param=t),
+     * then the class variable binaryData is set to this param string
+     *
+     * <pre>
+     *   group=<groupname>         ADDE group name
+     *   descr=<descriptor>        ADDE descriptor name
+     *   pos=<position>            request an absolute or relative ADDE 
+     *                               position number
+     *   select=<select clause>    to specify which data is required
+     *   param=<param list>        what parameters to return
+     *                             eg param=t[c]
+     *                             note that the units [c] are ignored by server
+     *                             it is the clients task to convert units
+     *                             Note that if "param=" is used, 
+     *                             binaryData is set to the
+     *                             (processed) parameter list
+     *   max=<max>                 maximum number of obs to return
+     *   trace=<0/1>               setting to 1 tells server to write debug 
+     *                               trace file (imagedata, imagedirectory)
+     *   binaryData=<param list>   because an unlimited number of parameters may
+     *                             be requested, these must be packaged up at the end
+     *                             of the adde request, and this is known as the
+     *                             "binary data" part of the request
+     *
+     * the following keywords are required:
+     *
+     *   group
+     *
+     * an example URL might look like:
+     *   adde://rtds/point?group=neons&descr=metar
+     *   
+     * </pre>
+     */
+    private StringBuffer decodeMDKSString(String uCmd)
+    {
+        String testString = null;
+        String lctestString = null;
+        // Mandatory strings
+        String groupString = null;
+        String descrString = null;
+        String maxString = "MAX=1";
+        String numString = "";
+        // Options strings
+        String posString = "POS=0";
+        String traceString = "TRACE=0";
+        String selectString = "";
+        String parmString = "";
+        String justTheParametersString = "";
+        String justTheSelectString = "";
+        String sBinaryData = "";
+        boolean posInDescriptor = false;
+        // in hard coded notation, the binaryData for "param=t" would look like:
+        // binaryData = new byte[4];
+        // binaryData[0] = (byte) 'T';
+        // binaryData[1] = (byte) ' ';
+        // binaryData[2] = (byte) ' ';
+        // binaryData[3] = (byte) ' ';
+
+        StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&");
+        while (cmdTokens.hasMoreTokens())
+        {
+            testString = cmdTokens.nextToken();
+            lctestString = testString.toLowerCase();
+            // group and descr
+            if (lctestString.startsWith("grou"))
+            {
+                groupString = 
+                    testString.substring(testString.indexOf("=") + 1);
+            }
+            else
+            if (lctestString.startsWith("des"))
+            {
+                descrString = 
+                    testString.substring(testString.indexOf("=") + 1);
+                int pos = descrString.indexOf(".");
+                if (pos >=0) {
+                    posString = "POS=" + descrString.substring(pos+1);
+                    descrString = descrString.substring(0,pos);
+                    posInDescriptor = true;
+                }
+            }
+            else
+            // in McIDAS Clients the parameter request string contains param=
+            // but the adde server looks for parm=
+            // this bit of code forces this change so that Java Clients behave
+            // the same as McIDAS Clients
+            if (lctestString.startsWith("par")) 
+            {
+                justTheParametersString = 
+                    testString.substring(testString.indexOf("=") + 1) ;
+                parmString = 
+                   "PARM=" + justTheParametersString;
+                if (debug)  System.out.println("paramString = " + parmString);
+                sBinaryData =   
+                    new String(decodePARAMString(justTheParametersString));
+                sBinaryData = sBinaryData.toUpperCase();
+                binaryData = sBinaryData.getBytes();
+            }
+            else
+            if (lctestString.startsWith("select"))
+            {
+                justTheSelectString = 
+                    testString.substring(testString.indexOf("=") + 1) ;
+                selectString = 
+                   "SELECT=" + new String(
+                       decodeSELECTString(justTheSelectString));
+                if (debug) 
+                    System.out.println("Server selectString = " + selectString);
+            }
+            else
+            // similarly, McIDAS Clients use num= but the server wants max=
+            if (lctestString.startsWith("num"))
+            {
+                maxString = 
+                   "MAX=" + testString.substring(testString.indexOf("=") + 1) ;
+            }
+            else
+            // allow for clever people who really know that the server uses
+            // max =  :-)
+            if (lctestString.startsWith("max"))
+            {
+                maxString = testString;
+            }
+            // now get the rest of the keywords (but filter out non-needed)
+            else
+            if (lctestString.startsWith("tra"))       // trace keyword
+            {
+                traceString = testString;
+            }
+            else
+            if (lctestString.startsWith("pos") && !posInDescriptor)       
+            {
+                posString = testString;
+            }
+
+        } 
+        // fudge the max string in case ALL is specified.  Some servers
+        // don't handle all
+        if (maxString.trim().equalsIgnoreCase("max=all")) {
+            maxString="MAX=99999";
+        }
+
+        // now create case sensitive command string
+
+        StringBuffer posParams = new StringBuffer();
+        posParams.append(groupString);
+        posParams.append(" ");
+        posParams.append(descrString);
+        posParams.append(" ");
+        posParams.append(parmString.toUpperCase());
+        posParams.append(" ");
+        posParams.append(selectString.toUpperCase());
+        posParams.append(" ");
+        posParams.append(posString.toUpperCase());
+        posParams.append(" ");
+        posParams.append(traceString.toUpperCase());
+        posParams.append(" ");
+        posParams.append(maxString.toUpperCase());
+
+        if (debug) System.out.println("String passed to server = " + posParams);
+        return posParams;
+
+    }
+
+    /**
+     * Helper function for decodeMDKSString to decode
+     * the "param=" part of a point data request.
+     *
+     * @param justTheParametersString   The parameter list which follows 
+     *                                  "param=" eg: "id dir spd t[c] td[c]"
+     * @return parameter list (padded to length 4 for server)
+     *                         without any units (units are ignored by server) 
+     *                         eg: "id  dir spd t   td  "
+     */
+     private String decodePARAMString(String justTheParametersString) {
+
+        String testString = null;
+        String thisParam = null;
+        String thisUnit  = null;
+        StringBuffer buf = new StringBuffer();
+        StringTokenizer paramTokens = 
+            new StringTokenizer(justTheParametersString, " ");
+        while (paramTokens.hasMoreTokens())
+        {
+            testString = (paramTokens.nextToken()).trim();
+            StringTokenizer thisParamToken = 
+                new StringTokenizer(testString, "[]");
+            thisParam = new String((thisParamToken.nextToken()).trim());
+                                                buf.append(thisParam);
+            for (int i=thisParam.length(); i < 4; i++) {
+                buf.append(" ");
+            }
+
+            if (thisParamToken.hasMoreTokens()) {
+            // note that the units are ignored by the server
+            // it is the client's responsibility to do unit conversion
+                thisUnit = (thisParamToken.nextToken()).trim();
+                if (debug) System.out.println("This Unit = " + thisUnit);
+            }
+        }
+
+
+        return (buf.toString());
+    }
+
+    /**
+     * Helper function for decodeMDKSString to decode
+     * the "select=" part of a point data request.
+     *
+     * @param justTheSelectString   The select list which follows "select=" eg:
+     *                'id ymml; time 12 18; day 1999316; t[c] 20 30; td 270 276'
+     *
+     * @return The select list formatted for the server eg:
+     *         'id ymml' 'time 12 to 18' 'day 1999316' 't 20 to 30 c' 'td 270 to 276'
+     *
+     *   Reference
+     *   McIDAS 7.6 source code: m0psort.for
+     */
+    private String decodeSELECTString(String justTheSelectString) {
+
+        String testString = null;
+        String entireSelectString = null;
+        // String trimmedSelectString = null;
+        String thisSelect = null;
+        String thisUnit  = null;
+        StringBuffer buf = new StringBuffer();
+        StringTokenizer entireSelectToken = 
+            new StringTokenizer(justTheSelectString, "'");
+        entireSelectString = (entireSelectToken.nextToken()).trim();
+        //
+        // Break SELECT string up into parts
+        //
+        StringTokenizer selectTokens = 
+            new StringTokenizer(entireSelectString, ";");
+        while (selectTokens.hasMoreTokens())
+        {
+            thisSelect = (selectTokens.nextToken()).trim();
+            if (debug) System.out.println(" this Select = " + thisSelect);
+            //
+            // Break into individual clauses eg:
+            // t[c] 20 30
+            //
+            StringTokenizer thisSelectToken = 
+                new StringTokenizer(thisSelect, " ");
+            int tokenCount = thisSelectToken.countTokens();
+            thisSelect = new String(thisSelectToken.nextToken());
+            if (debug) System.out.println("this Select = " + thisSelect);
+
+            //
+            // Check to see if any units are involved eg:
+                                                // t[c]
+            if (thisSelect.indexOf("[") > 0) {
+                StringTokenizer thisUnitToken = 
+                    new StringTokenizer(thisSelect, "[]");
+                if (thisUnitToken.hasMoreTokens()) {
+                    thisSelect = new String((thisUnitToken.nextToken()).trim());
+                    buf.append("'" + thisSelect);
+                    if (thisUnitToken.hasMoreTokens()) {
+                        thisUnit = 
+                            new String((thisUnitToken.nextToken()).trim());
+                    }
+                }
+            } else {
+                // no units involved eg:
+                // t
+                buf.append("'" + thisSelect);
+           }
+
+           //
+           // Check for first numeric value eg if select='t[c] 20 30':
+           // 20
+           //
+           if (thisSelectToken.hasMoreTokens()) {
+                thisSelect = thisSelectToken.nextToken();
+                if (debug) System.out.println("this Select = " + thisSelect);
+                buf.append(" " + thisSelect);
+           }
+
+           //
+           // Check for second numeric value eg if select='t[c] 20 30':
+           // 30
+           //
+           if (thisSelectToken.hasMoreTokens()) {
+                thisSelect = thisSelectToken.nextToken();
+                // server requires TO for a range of values eg:
+                // 20 to 30
+                buf.append(" TO " + thisSelect);
+                if (debug) System.out.println("this Select = " + thisSelect);
+           }
+
+           //
+           // add unit if specified
+           //
+           if (thisUnit != null) {
+               buf.append(" " + thisUnit);
+               thisUnit = null;
+           }
+
+           buf.append("' ");
+        }
+
+
+        return (buf.toString());
+    }
+
+    /**
+     * Ensures that a string is two values.  If only one, then it
+     * is returned as s + " " + s 
+     * @param s  String to check
+     */
+    private String ensureTwoValues(String s)
+    {
+       String retVal = null;
+       if (s.trim().indexOf(" ") > 0)  // has multiple values
+       {
+           StringTokenizer tok = new StringTokenizer(s);
+           // return null if more than 2
+           if (tok.countTokens() == 2) retVal = s;
+       }
+       else
+       {
+           //retVal = new String(s + " " + s);
+           retVal = s + " " + s;
+       }
+       return retVal;
+    }
+
+    /**
+     * Adjust the longitude from East Postitive to west positive 
+     * @param input  String to check
+     */
+    private String adjustLongitudes(String input)
+    {
+       input = input.trim();
+       String lon1 = negateLongitude(input.substring(0, input.trim().indexOf(" ")).trim());
+       String lon2 = negateLongitude(input.substring(input.trim().indexOf(" ")).trim());
+       return (lon2 + " " + lon1);
+    }
+
+    /**
+     * Negate a longitude.  McIDAS convention is positive west.
+     * @param eastLongitude  eastLongitude to negate
+     */
+    private String negateLongitude(String eastLong)
+    {
+      if (eastLong.indexOf("-") >= 0) // (comes in as -)
+        return eastLong.substring(eastLong.indexOf("-") + 1);
+      else
+        return "-" + eastLong;
+    }
+
+    private static String[] replaceWith = {"&", "<", ">", "\'", "\"", "\r", "\n", " "};
+    private static String[] replaceString = {"&", "<", ">", "'", """, "
", "
", "%20" };
+
+    private URL normalizeURL(URL url) {
+
+      String x;
+      try {
+        x = URLDecoder.decode(url.toString(), "UTF-8");
+      }
+      catch (java.lang.Exception e) {
+        throw new RuntimeException(e.toString());
+      }
+      // common case no replacement
+      boolean ok = true;
+      for (int i=0; i<replaceString.length; i++) {
+        int pos = x.indexOf(replaceString[i]);
+        ok &= (pos < 0);
+      }
+
+      if (!ok) { // gotta do it
+
+        for (int i=0; i<replaceString.length; i++) {
+          int pos = -1;
+          while ((pos = x.indexOf(replaceString[i])) >=0) {
+            if (debug) System.out.println("found " + replaceString[i] + " at " + pos);
+            StringBuffer buf = new StringBuffer(x);
+            buf.replace(pos, pos+(replaceString[i].length()), replaceWith[i]);
+            x = buf.toString();
+          }
+        }
+      }
+      if (debug) System.out.println("normalized url = " + x);
+      try {
+          return new URL(x);
+      } catch (Exception e) {}
+      return url;
+    } 
+
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeURLException.java b/edu/wisc/ssec/mcidas/adde/AddeURLException.java
new file mode 100644
index 0000000..9bf7cc1
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeURLException.java
@@ -0,0 +1,48 @@
+//
+// AddeURLException.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+/**
+ * AddeURLException class is to handle exceptions when using ADDE URLs.
+ *
+ * @author Tommy Jasmin, University of Wisconsin - SSEC
+ */
+
+import java.io.IOException;
+
+public class AddeURLException extends IOException {
+ 
+  public AddeURLException() {
+    super(); 
+  }
+
+  public AddeURLException(String s) {
+    super(s); 
+  }
+ 
+}
+
diff --git a/edu/wisc/ssec/mcidas/adde/AddeURLStreamHandler.java b/edu/wisc/ssec/mcidas/adde/AddeURLStreamHandler.java
new file mode 100644
index 0000000..3662b5a
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeURLStreamHandler.java
@@ -0,0 +1,72 @@
+//
+// AddeURLStreamHandler.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * This class defines the openConnection method, which is
+ * used to create an AddeURLConnection.  Note that this 
+ * class is automatically loaded when a URL of this type
+ * is created, you don't have explicitly create an object.
+ *
+ * @author Tommy Jasmin, University of Wisconsin, SSEC
+ */
+
+public class AddeURLStreamHandler extends URLStreamHandler
+
+{
+
+  /**
+   *
+   * returns a reference to a special URLConnection object
+   * designed to implement the ADDE protocol.
+   *
+   * @param             url - user specified URL, encodes ADDE request
+   * @return            AddeURLConnection reference.
+   */
+
+  protected URLConnection openConnection(URL url)
+    throws IOException
+
+  {
+    return new AddeURLConnection(url);
+  }
+
+  /** 
+   * Returns the default port for a URL parsed by this handler. 
+   * This method is meant to be overidden by handlers with default 
+   * port numbers.
+   */ 
+  protected int getDefaultPort() {
+      return AddeURLConnection.DEFAULT_PORT;
+  }
+
+}
diff --git a/edu/wisc/ssec/mcidas/adde/AddeURLStreamHandlerFactory.java b/edu/wisc/ssec/mcidas/adde/AddeURLStreamHandlerFactory.java
new file mode 100644
index 0000000..e101be1
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/AddeURLStreamHandlerFactory.java
@@ -0,0 +1,62 @@
+//
+// AddeURLStreamHandlerFactory.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.net.URLStreamHandler;
+import java.net.URLStreamHandlerFactory;
+
+/**
+ * This class creates a URLStreamHandler for the ADDE protocol.
+ * An instance is passed to URL.setURLStreamHandlerFactory when
+ * an application will be using ADDE URLs.
+ *
+ * @author Tommy Jasmin, University of Wisconsin, SSEC
+ */
+
+public class AddeURLStreamHandlerFactory 
+  implements URLStreamHandlerFactory
+
+{
+
+  /**
+   *
+   * Creates URLStreamHandler object - not called directly.
+   *
+   * @param             protocol - should be "adde"
+   * @return            AddeURLStreamHandler reference.
+   */
+
+  public URLStreamHandler createURLStreamHandler(String protocol) {
+    if (protocol.equalsIgnoreCase("adde")) {
+      return new AddeURLStreamHandler();
+    } else {
+      return null;
+    }
+  }
+
+}
+
diff --git a/edu/wisc/ssec/mcidas/adde/DataSetInfo.java b/edu/wisc/ssec/mcidas/adde/DataSetInfo.java
new file mode 100644
index 0000000..dd04324
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/DataSetInfo.java
@@ -0,0 +1,339 @@
+//
+// DataSetInfo.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+/** 
+ * DataSetInfo interface for McIDAS ADDE data sets.   Simulates a
+ * McIDAS DSINFO request using an ADDE URL.
+ *
+ * <pre>
+ * URLs must all have the following format   
+ *   adde://host/datasetinfo?keyword_1=value_1&keyword_2=value_2
+ *
+ * there can be any valid combination of the following supported keywords:
+ *
+ *   group - ADDE group name   
+ *   type  - ADDE data type.  Must be one of the following:
+ *               image, point, grid, text, nav
+ *           the default is the image type.
+ *
+ * the following keywords are required:
+ *
+ *   group
+ *
+ * an example URL might look like:
+ *   adde://viper/datasetinfo?group=gvar&type=image
+ * </pre>
+ *
+ * @author Don Murray
+ * 
+ */
+public class DataSetInfo 
+{
+
+    // load protocol for ADDE URLs
+    // See java.net.URL for explanation of URL handling
+    static 
+    {
+        try 
+        {
+            String handlers = System.getProperty("java.protocol.handler.pkgs");
+            String newProperty = null;
+            if (handlers == null)
+                newProperty = "edu.wisc.ssec.mcidas";
+            else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+                newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+            if (newProperty != null)  // was set above
+                System.setProperty("java.protocol.handler.pkgs", newProperty);
+        }
+        catch (Exception e)
+        {
+            System.out.println(
+                "Unable to set System Property: java.protocol.handler.pkgs"); 
+        }
+    }
+
+    private int status=0;                 // read status
+    private char[] data;                  // data returned from server
+    private Hashtable descriptorTable;
+    private boolean debug = false;        // debug
+
+    /** Descriptors returned from server. */
+    private List<String> descriptorList;
+
+    /** Comments returned from server. */
+    private List<String> commentList;
+
+    /**
+     * creates a DataSetInfo object that allows reading
+     *
+     * @param request ADDE URL to read from.  See class javadoc.
+     *
+     * <pre>
+     * an example URL might look like:
+     *   adde://viper/datasetinfo?group=gvar&type=image
+     * </pre>
+     *
+     * @exception AddeURLException if there are no datasets of the particular
+     *            type or there is an error reading data
+     *
+     */
+    public DataSetInfo(String request)
+        throws AddeURLException
+    {
+   
+        URLConnection urlc;
+        BufferedReader reader;
+        debug = debug || request.indexOf("debug=true") >= 0;
+        try 
+        {
+            URL url = new URL(request);
+            urlc = url.openConnection();
+            reader = 
+                new BufferedReader(
+                    new InputStreamReader(
+                        urlc.getInputStream()));
+        }
+        catch (AddeURLException ae) 
+        {
+            status = -1;
+            throw new AddeURLException("No datasets found");
+        }
+        catch (Exception e) 
+        {
+            status = -1;
+            throw new AddeURLException("Error opening connection: " + e);
+        }
+        int numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+        if (debug) System.out.println("DataSetInfo: numBytes = " + numBytes);
+        if (numBytes == 0)
+        {
+            status = -1;
+            throw new AddeURLException("No datasets found");
+        }
+        else
+        {
+            data = new char[numBytes];
+            try
+            {
+                int start = 0;
+                while (start < numBytes)
+                {
+                    int numRead = 
+                        reader.read(data, start, (numBytes - start));
+                    if (debug) System.out.println("bytes read = " + numRead);
+                    start += numRead;
+                }
+            }
+            catch (IOException e) 
+            {
+                status = -1;
+                throw new AddeURLException("Error reading dataset info:" + e);
+            }
+            int numNames = data.length/80;
+            descriptorTable = new Hashtable(numNames);
+            descriptorList = new ArrayList<String>(numNames);
+            commentList = new ArrayList<String>(numNames);
+            if (debug) 
+                System.out.println("Number of descriptors = " + numNames);
+            for (int i = 0; i < numNames; i++)
+            {
+                String temp = new String(data, i*80, 80);
+                if (debug) System.out.println("Parsing: >"+temp+"<");
+                if (temp.trim().isEmpty()) continue;
+                String descriptor = temp.substring(0,12).trim();
+                if (debug) System.out.println("Descriptor = " + descriptor);
+                String comment = descriptor;
+                int pos = temp.indexOf('"');
+                if (debug) System.out.println("Found quote at " + pos);
+                if (pos >= 23)   
+                {
+                    comment = temp.substring(pos + 1).trim();
+                    if (comment.isEmpty()) comment = descriptor;
+                }
+                if (debug) System.out.println("Comment = " + comment);
+                descriptorTable.put(comment, descriptor);
+                descriptorList.add(descriptor);
+                commentList.add(comment);
+            }
+        } 
+    }
+
+    /**
+     * Returns the list of descriptors.
+     * 
+     * @return Either an ArrayList or {@code null}. Note that if an
+     * {@code ArrayList} is returned, it should have the same number of 
+     * elements as the results of {@link #getCommentList()}.
+     */
+    public List<String> getDescriptorList() {
+        return descriptorList;
+    }
+
+    /**
+     * Returns the list of descriptor contents.
+     * 
+     * @return Either an {@link ArrayList} or {@code null}. Note that if an
+     * {@code ArrayList} is returned, it should have the same number of 
+     * elements as the results of {@link #getDescriptorList()}.
+     */
+    public List<String> getCommentList() {
+        return commentList;
+    }
+
+    /**
+     * Return the data sent by the server
+     *
+     * @return  array of the data.  Data is in the format of a McIDAS DSINFO
+     *          (LWPR) request.
+     *
+     * @exception AddeURLException if there was an error reading data
+     */
+    public char[] getData()
+        throws AddeURLException
+    {
+        if (status < 0)
+            throw new AddeURLException("No data available");
+
+        return data;
+    }
+
+    /**
+     * Return a hashtable of descriptive names and ADDE dataset descriptors
+     * Descriptive names are the keys.  If there is no descriptive name,
+     * the dataset descriptor is used.
+     *
+     * @return hashtable of names/desciptors
+     *
+     * @exception AddeURLException if there was an error reading data
+     */
+    public Hashtable getDescriptionTable()
+        throws AddeURLException
+    {
+        if (status < 0)
+            throw new AddeURLException("No data available");
+        return descriptorTable;
+    }
+
+    /**
+     * Return a sorted list of the dataset descriptors
+     *
+     * @return sorted list 
+     *
+     * @exception AddeURLException if there was an error reading data
+     */
+    public String[] getDescriptors()
+        throws AddeURLException
+    {
+        if (status < 0)
+            throw new AddeURLException("No data available");
+        String[] list = new String[descriptorTable.size()];
+        Enumeration elements = descriptorTable.elements();
+        int i = 0;
+        while (elements.hasMoreElements())
+        {
+            list[i] = (String) elements.nextElement();
+            i++;
+        }
+        Arrays.sort(list);
+        return list;
+    }
+
+    /**
+     * Return a formated string of the returned data
+     *
+     * @return  formatted string representing the data.
+     */ 
+    public String toString()
+    {
+        if (status < 0)
+            return new String("No data Available");
+        String header = new String(
+            "Name         NumPos   Content\n" +
+            "------------ ------   --------------------------------------\n");
+        StringBuffer buf = new StringBuffer(header);
+        for (int i = 0; i < data.length/80; i++)
+        {
+            StringBuffer sb = 
+                new StringBuffer("                                          ");
+            String line = new String(data, i*80, 80);
+            sb.insert(0,line.substring(0,12));
+            int brange = 0;
+            int erange = 0;
+            try {
+                brange = Integer.parseInt(line.substring(12,18).trim());
+            } catch (NumberFormatException ne) { brange = 0; }
+            try {
+                erange = Integer.parseInt(line.substring(18,23).trim());
+            } catch (NumberFormatException ne) { erange = 0; }
+            if (erange == 0) erange = brange;
+            int numPos = (erange-brange) + 1;
+            int insertPos = 17;
+            if (numPos >= 10 && numPos < 100)
+                insertPos = 16;
+            else if (numPos >= 100 && numPos < 1000)
+                insertPos = 15;
+            else if (numPos > 1000)
+                insertPos = 14;
+            sb.insert(insertPos,String.valueOf(numPos));
+            int pos = line.indexOf('"');
+            if (pos >= 23) sb.insert(22, line.substring(pos+1));
+            buf.append(sb.toString().trim());
+            buf.append("\n");
+        }
+        return buf.toString();
+    }
+
+    /** test by running 'java edu.wisc.ssec.mcidas.adde.DataSetInfo' */
+    public static void main (String[] args)
+        throws Exception
+    {
+        System.out.println("\nDataset Names:\n");
+
+        String request = (args.length == 0)
+          ? "adde://adde.unidata.ucar.edu/datasetinfo?group=blizzard&type=image"
+          : args[0];
+        DataSetInfo dsinfo = new DataSetInfo(request);
+        System.out.println(dsinfo.toString());
+        String[] descriptors = dsinfo.getDescriptors();
+        System.out.println("Sorted list of Descriptors:");
+        for (int i = 0; i < descriptors.length; i++)
+            System.out.println(descriptors[i]);
+
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/adde/GetAreaFile.java b/edu/wisc/ssec/mcidas/adde/GetAreaFile.java
new file mode 100644
index 0000000..d639628
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/GetAreaFile.java
@@ -0,0 +1,538 @@
+//
+// GetAreaFile.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.io.*;
+import java.util.*;
+import java.lang.*;
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.event.*;
+import javax.swing.*;
+import edu.wisc.ssec.mcidas.*;
+import edu.wisc.ssec.mcidas.adde.*;
+import edu.wisc.ssec.mcidas.AreaFile;
+import edu.wisc.ssec.mcidas.AreaFileException;
+
+/** 
+* Application to fetch an AREA file and write it as a local file.
+*
+* It is command-line driven, but can be forced into "gui mode" by
+* either using the '-gui' option or specifying the URL 'adde://image?'
+* as the source (although in the latter case, you still need to
+* the desitnation filename on the command line; whereas with the -gui
+* option, a FileChooser is presented to the user).
+*
+* @author Tom Whittaker, SSEC/CIMSS
+*
+*/
+public class GetAreaFile implements ActionListener {
+  final String[] paramNames = {"host", "group", "descr", "user", "proj", 
+  "trace", "band","mag","linele","place","pos",
+   "size","unit","spac","doc","latlon",
+   "aux","time","day","cal","id" };
+
+  String[] flags = {"h","g","d", "k","j","t",
+   "b","m","l","n","p", 
+   "s","u","z","o","r",
+   "a","i","y","c","e"};
+
+  String[] paramValues;
+  String[] serverNames;
+  String paramFile, outputFile;
+  Properties pars;
+  boolean verbose;
+  boolean doGUI=false;
+  GetAreaGUI gag = null;
+  String gagRequest = null;
+
+  /** AD_DATAOFFSET - byte offset to start of data block */
+  public static final int AD_DATAOFFSET = 33;
+  /** AD_NAVOFFSET - byte offset to start of navigation block */
+  public static final int AD_NAVOFFSET  = 34;
+  /** AD_AUXOFFSET - byte offset to start of auxilliary data section */
+  public static final int AD_AUXOFFSET  = 59;
+  /** AD_CALOFFSET - byte offset to start of calibration section */
+  public static final int AD_CALOFFSET  = 62;
+  /** AD_DATAWIDTH - number of bytes per data point */
+  public static final int AD_DATAWIDTH  = 10;
+
+  public static void main(String args[] ) {
+    GetAreaFile gaf = new GetAreaFile(args);
+  }
+
+  public GetAreaFile(String args[]) {
+
+    paramFile = "params.properties";
+    verbose = false;
+
+    // if no arguments, emit a "help" message
+    if (args == null || args.length < 1) {
+      System.out.println("Usage:  java edu.wisc.ssec.mcidas.GetAreaFile [options] output_file");
+      System.out.println("   Command line [options] are:");
+      for (int i=0; i<paramNames.length; i++) {
+        System.out.println("    -"+flags[i]+" = "+paramNames[i]);
+      }
+      System.out.println("    -f = parameter save filename (def=params.properties)");
+      System.out.println("    -v  (verbose text output)");
+      System.out.println("    -gui = use GUI interface (no other options should be used with this)");
+      System.out.println(" Note: for multi-argument options (like -s), you need to enclose the values in quotes. e.g., -s \"200 200\"");
+      return;
+    }
+
+    paramValues = new String[paramNames.length];
+
+    // first try to get all the command line parameters
+    outputFile = doArguments(args);
+    if (outputFile == null) {
+      System.out.println("No output file specified...");
+      return;
+    }
+
+    // now go for the properties file
+    pars = fetchParams(paramFile);
+
+    if (doGUI) {
+      
+     doGUI();
+     // ug = new UseGUI(this, pars);
+    } else {
+      doRequest(pars);
+    }
+
+
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    System.out.println("got action performed...");
+    if (!e.getActionCommand().startsWith("adde://")) return;
+    Object source = e.getSource();
+    if (source.equals(gag)) {
+      Properties p = new Properties();
+      putProp(p,"host",gag.getServer());
+      putProp(p,"group",gag.getGroup());
+      putProp(p,"descr",gag.getDescr());
+      putProp(p,"user",gag.getUserName());
+      putProp(p,"proj",gag.getProjectNumber());
+      putProp(p,"trace",null);
+      putProp(p,"band",gag.getBand());
+      putProp(p,"mag",gag.getMag());
+      String ctype = gag.getCoordType();
+
+      if (ctype.equals("E")) {
+        putProp(p,"latlon",gag.getLocationString());
+      } else if (ctype.equals("I")) {
+        putProp(p,"linele",gag.getLocationString());
+      } else if (ctype.equals("S")) {
+        putProp(p,"id",gag.getLocationString());
+      }
+
+      putProp(p,"time",gag.getTime());
+      putProp(p,"day",gag.getDay());
+
+      putProp(p,"size",gag.getNumLinesEles());
+
+      putProp(p,"unit",gag.getUnit());
+      putProp(p,"doc",gag.getDoc());
+
+      gagRequest = e.getActionCommand();
+      JFileChooser getout = new JFileChooser();
+      getout.setDialogTitle("Name of AREA file to write into");
+      int status = getout.showSaveDialog(gag);
+      if (status == JFileChooser.APPROVE_OPTION) {
+        File fn = getout.getSelectedFile();
+        putProp(p,"outfile",fn.toString()); // temporary...
+        doRequest(p);
+      } else {
+        gag.status("File not saved!!!");
+      }
+    return;
+    }
+
+  }
+
+  void putProp(Properties p, String name, String value) {
+    if (value == null) return;
+    if (value.trim().length() < 1) return;
+    p.put(name,value);
+    return;
+  }
+
+  
+  private void doGUI() {
+    String thost, tgroup, tdescr;
+    String tpos, tday, ttime;
+    String tlatlon, tlinele;
+    String tsize, tmag, tspac;
+    String tband, tunit;
+    String taux, tcal, tid, tdoc;
+    String ttrace, tuser, tproj;
+    gag = new GetAreaGUI("Select File", false);
+    gag.addActionListener(this);
+    gag.show();
+    tuser = pars.getProperty("user");
+    if (tuser != null) gag.setUserName(tuser);
+    tproj = pars.getProperty("proj");
+    if (tproj != null) gag.setProjectNumber(tproj);
+
+    tpos = pars.getProperty("pos");
+    //if (tpos != null) gag.setDatasetPosition(tpos);
+
+    tday = pars.getProperty("day");
+    ttime = pars.getProperty("time");
+
+    tlatlon = pars.getProperty("latlon");
+    tlinele = pars.getProperty("linele");
+    tid = pars.getProperty("id");
+
+    if (tlatlon != null) {
+      gag.setCoordType("E");
+      gag.setLocationString(tlatlon);
+        
+    } else if (tlinele != null) {
+      gag.setCoordType("I");
+      gag.setLocationString(tlinele);
+
+    } else if (tid != null) {
+      gag.setCoordType("S");
+      gag.setLocationString(tid);
+    }
+
+    tsize = pars.getProperty("size");
+    if (tsize != null) {
+      gag.setNumLinesEles(tsize);
+    } else {
+      gag.setNumLinesEles("480 640");
+    }
+
+    tmag = pars.getProperty("mag");
+    if (tmag != null) gag.setMag(tmag);
+
+    tband = pars.getProperty("band");
+    if (tband != null) gag.setBand(tband);
+
+    tspac = pars.getProperty("spac");
+    //if (tspac != null) gag.setNumBytes(tspac);
+
+    tunit = pars.getProperty("unit");
+    if (tunit != null) gag.setUnit(tunit);
+
+    tcal = pars.getProperty("cal");
+    //if (tcal != null) gag.setCal(tcal);
+
+    tdoc = pars.getProperty("doc");
+    if (tdoc != null) gag.setDoc(tdoc);
+
+    taux = pars.getProperty("aux");
+    //if (taux != null) gag.setAux(taux);
+  }
+
+  public void doRequest(Properties params) {
+
+    String request = makeADDEString(params);
+    if (gagRequest != null) request = gagRequest;
+
+    if (gag != null) outputFile = params.getProperty("outfile");
+
+    System.out.println("Request sent: "+request);
+    if (gag != null) gag.status("Request sent: "+request);
+
+    AreaFile af;
+    
+    try {
+      af = new AreaFile(request);
+    } catch (AreaFileException e) {
+      System.out.println("While getting AreaFile:"+e);
+      String es = e.toString();
+      String es2 = es;
+      int ei = es.lastIndexOf("Exception:");
+      if (ei > 0) es = es2.substring(ei+11);
+      if (gag != null) gag.status("Error: "+es);
+      return;
+    }
+    int[] dir;
+
+    dir=af.getDir();
+    if (dir == null) {
+      System.out.println("No AREA file directory!");
+      return;
+    }
+    if (verbose) {
+      System.out.println("Length of directory = "+dir.length);
+
+      for (int i=0; i<dir.length; i++) {
+       System.out.println(" index "+i+" = "+dir[i]);
+      }
+    }
+
+    int[] nav=null;
+    nav=af.getNav();
+    if (nav == null) {
+      System.out.println("No navigation block!");
+    } else {
+      if (verbose) System.out.println("Length of nav block = "+nav.length);
+    }
+
+    int[] cal=null;
+    cal=af.getCal();
+    if (cal == null) {
+      System.out.println("No calibration block!");
+    } else {
+      if (verbose) System.out.println("Length of cal block = "+cal.length);
+    }
+
+    int[] aux=null;
+    aux=af.getAux();
+    if (aux == null) {
+      System.out.println("No aux block");
+    } else {
+      if (verbose) System.out.println("Length of aux block = "+aux.length);
+    }
+
+    int NL=dir[8];
+    int NE=dir[9];
+
+    if (verbose) System.out.println("Start reading data, num points="+(NL*NE));
+    if (gag != null) gag.status("Start reading data, num points="+(NL*NE));
+
+
+    int[][]data;
+
+    try { data = af.getData(0,0,NL,NE); }
+    catch (AreaFileException e) {System.out.println(e);return;}
+
+    if (verbose) System.out.println("Finished reading data");
+    if (gag != null) gag.status("Finished reading data");
+
+
+    try {
+      RandomAccessFile raf = new RandomAccessFile(outputFile,"rw");
+
+    if (verbose) System.out.println("Dir to word 0");
+      raf.seek(0);
+      dir[0] = 0; // make sure this is zero!!
+      for (int i=0; i<dir.length; i++) raf.writeInt(dir[i]);
+
+    if (verbose) System.out.println("Nav to word "+dir[AD_NAVOFFSET]);
+      if (nav != null && dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_NAVOFFSET]);
+        for (int i=0; i<nav.length; i++) raf.writeInt(nav[i]);
+      }
+
+    if (verbose) System.out.println("Cal to word "+dir[AD_CALOFFSET]);
+      if (cal != null && dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_CALOFFSET]);
+        for (int i=0; i<cal.length; i++) raf.writeInt(cal[i]);
+      }
+
+    if (verbose) System.out.println("Aux to word "+dir[AD_AUXOFFSET]);
+      if (aux != null && dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_AUXOFFSET]);
+        for (int i=0; i<aux.length; i++) raf.writeInt(aux[i]);
+      }
+
+    if (verbose) System.out.println("Data to word "+dir[AD_DATAOFFSET]);
+      if (dir[AD_NAVOFFSET] > 0) {
+        raf.seek(dir[AD_DATAOFFSET]);
+        for (int i=0; i<data.length; i++) {
+          for (int j=0; j<data[i].length; j++) {
+            if (dir[AD_DATAWIDTH] == 1) {
+              raf.writeByte(data[i][j]);
+            } else if (dir[AD_DATAWIDTH] == 2) {
+              raf.writeShort(data[i][j]);
+            } else if (dir[AD_DATAWIDTH] == 4) {
+              raf.writeInt(data[i][j]);
+            }
+          }
+        }
+      }
+
+      raf.close();
+    } catch (Exception we) {System.out.println(we);}
+
+    System.out.println( "Completed. Data saved to: "+outputFile+
+                               "   Saving parameters to: "+paramFile);
+    if (gag != null) gag.status("Completed. Data saved to: "+outputFile+
+                               "   Saving parameters to: "+paramFile);
+
+    writeParams(paramFile,params);
+  }
+
+
+  /** make the ADDE request string out of the various parameters
+  * the host, group and descr are required.  Everything else is
+  * optional.  version=1 is appended...
+  */
+  public String makeADDEString(Properties params) {
+    String host = params.getProperty("host");
+    if (host == null) return null;
+
+    String group = params.getProperty("group");
+    if (group == null) return null;
+
+    String descr = params.getProperty("descr");
+    if (descr == null) return null;
+
+    StringBuffer sb = new StringBuffer("adde://"+host+"/image?");
+    for (int i=0; i<paramNames.length; i++) {
+      if (paramNames[i] != "host" && params.getProperty(paramNames[i]) != null) {
+        sb.append(paramNames[i]+"="+params.getProperty(paramNames[i])+"&");
+      }
+    }
+    sb.append("version=1");
+    return sb.toString();
+  }
+
+  /** scans the input argument list and if it finds any legitimate
+  * flags, it replaces the value of the parameter
+  */
+  String doArguments(String[] arg) {
+
+    String outfile = arg[arg.length - 1];
+
+    // if there is only one parameter, see if it's the '-gui' switch
+    // instead of the 'outfile' name
+    if (outfile.equalsIgnoreCase("-gui")) {
+      doGUI = true;
+      outfile = " ";
+      return outfile;
+    }
+
+    for (int k=0; k<arg.length-1; k++) {
+      String s = arg[k];
+      if ((s.length()) > 1 && s.startsWith("-")) {
+        if (s.equalsIgnoreCase("-gui")) {
+          doGUI = true;
+          continue;
+        }
+
+        String r = s.substring(1,2);
+        if (r.equals("f")) {
+          if (s.length() == 2) {
+            paramFile = arg[++k];
+          } else {
+            paramFile = s.substring(2);
+          }
+
+        } else if (r.equals("v")) {
+          verbose = true;
+
+        } else {
+          for (int i=0; i<paramNames.length; i++) {
+            if (r.equals(flags[i])) {
+              if (s.length() == 2) {
+                paramValues[i] = arg[++k];
+              } else {
+                paramValues[i] = s.substring(2);
+              }
+            }
+          }
+        }
+      } else {
+        System.out.println("Problem with parameter: "+s);
+        return null;
+      }
+
+    }
+    return outfile;
+  }
+
+
+  /** fetch the parameters from the property file
+  */
+  Properties fetchParams(String filename) {
+    
+    Properties p = new Properties();
+    try {
+
+      InputStream is = new FileInputStream(filename);
+      p.load(is);
+      is.close();
+    } catch (Exception e) {System.out.println(e);}
+
+    for (int i=0; i<paramNames.length; i++) {
+      if (paramValues[i] != null) p.put(paramNames[i], paramValues[i]);
+    }
+    //if (outputFile != null && outputFile.length>1) p.put("outfile",outputFile); 
+    return p;
+
+  }
+
+
+  /** write the property file back out...
+  */
+  void writeParams(String filename, Properties p) {
+    try {
+      OutputStream os = new FileOutputStream(filename);
+      p.save(os,"GetAreaFile properties");
+      os.flush();
+      os.close();
+    } catch (Exception e) {System.out.println(e);}
+  }
+
+}
+
+
+/* 
+ *-g   group=<groupname>         ADDE group name
+ *-k   user=<user_id>            ADDE user identification
+ *-j   proj=<proj #>             a valid ADDE project number
+ *-t   trace=<0/1>               setting to 1 tells server to write debug
+ *                               trace file (imagedata, imagedirectory)
+ *     version=1                 ADDE version number, currently 1
+ *
+ *-d   descr=<descriptor>        ADDE descriptor name
+ *-b   band=<band>               spectral band or channel number
+ *-m   mag=<lmag> <emag>         image magnification, postitive for blowup,
+ *                               negative for blowdown (default = 1, emag=lmag)
+ *                               (imagedata only)
+ *-l   linele=<lin> <ele> <type> line/element to center image on 
+ *-n   place=<placement>         placement of lat/lon or linele points (center
+ *                               or upperleft (def=center)) 
+ *-p   pos=<position>            request an absolute or relative ADDE position
+ *                               number
+ *-s   size=<lines> <elements>   size of image to be returned (imagedata only)
+ *-u   unit=<unit>               to specify calibration units other than the
+ *                               default
+ *-z   spac=<bytes>              number of bytes per data point, 1, 2, or 4
+ *                               (imagedata only)
+ *-o   doc=<yes/no>              specify yes to include line documentation
+ *                               with image (def=no)
+ *-r   latlon=<lat> <lon>        lat/lon point to center image on 
+ *-a   aux=<yes/no>              specify yes to include auxilliary information
+ *                               with image for calibration purposes
+ *-i   time=<time1> <time2>      specify the time range of images to select
+ *                               (def=latest image if pos not specified)
+ *-y   day=<day>                 specify the day of the images to select
+ *                               (def=latest image if pos not specified)
+ *-c   cal=<cal type>            request a specific calibration on the image
+ *                               (imagedata only)
+ *-i   id=<stn id>               radar station id
+ *-h   host=                     ADDE server hostname or IP address
+ *
+*/
+
diff --git a/edu/wisc/ssec/mcidas/adde/GetAreaGUI.java b/edu/wisc/ssec/mcidas/adde/GetAreaGUI.java
new file mode 100644
index 0000000..380857c
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/GetAreaGUI.java
@@ -0,0 +1,1643 @@
+//
+// GetAreaGUI.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+// Created on August 29, 2000, 8:45 AM
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.text.*;
+import java.io.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Properties;
+import java.util.Vector;
+import edu.wisc.ssec.mcidas.*;
+import edu.wisc.ssec.mcidas.AreaDirectory;
+import edu.wisc.ssec.mcidas.adde.*;
+import edu.wisc.ssec.mcidas.adde.AddeServerInfo;
+
+/**
+ * A GUI wrapper for whatever class/method tries to
+ * get image data via ADDE, but needs a way to let the
+ * user explore the availability of data.
+ *
+ * Possible defaults are written into the GetAreaGUI.properties
+ * file, and when the user indicates "use my defaults", these are
+ * employed when ever the correct combo of server/group/descr are
+ * chosen.  
+ *
+ * @author tomw
+ * @version 0.1
+ */
+public class GetAreaGUI extends JPanel {
+  AddeServerInfo asi;
+  String[] sl;
+  String selectedServer, selectedGroup, selectedDescr, selectedDateTime;
+  // note that 'descr' and 'dataset' are the same thing...
+  
+  String coordType;
+  String actionButtonString;
+  String userName, projectNumber;
+  boolean serverUpdated, groupUpdated, descrUpdated, multipleImages;
+  ActionListener al = null;
+  int baseNumLines, baseNumEles;
+  double resLat, resLon;
+  int[] bandListIndex;
+  String[] bandList;
+  String selectedBand, selectedUnit;
+  int selectedBandIndex;
+  String[][][] calInfo = null;
+  boolean doingRes;
+  NumberFormat nf3;
+  Properties dataProp;
+  StringBuffer serverList;
+  AreaDirectory [][] ad;
+  int areaIndex;
+  String[] bandNames;
+  AddeSatBands asb;
+  boolean closeOnAction = true;
+  boolean useDefaults = false;
+  boolean gotUserDefaults = false;
+  String propFile;
+  String cmdout=null;
+  ArrayList imageList = null;
+  JDialog dialog;
+
+  /** 
+  * @param s is the label for the action button
+  *
+  */
+  public GetAreaGUI(String s) {
+    this ((Frame)null, false, s, false, true, true);
+  }
+  
+  /** 
+  * @param s is the label for the action button
+  * @param multi is true if multiple selection mode is to be used 
+  *  (this does not work yet!!)
+  *
+  */
+  public GetAreaGUI(String s, boolean multi) {
+    this((Frame) null, false, s, multi, true, true);
+  }
+  
+  /** 
+  * @param s is the label for the action button
+  * @param multi is true if multiple selection mode is to be used 
+  *  (this does not work yet!!)
+  * @param coa is true if the Dialog should close the window after
+  *  the actionPerformed is done.
+  *
+  */
+  public GetAreaGUI(String s, boolean multi, boolean coa) {
+    this((Frame) null, false, s, multi, coa, true);
+  }
+
+  /** 
+  * @param s is the label for the action button
+  * @param multi is true if multiple selection mode is to be used 
+  *  (this does not work yet!!)
+  * @param coa is true if the Dialog should close the window after
+  *  the actionPerformed is done.
+  * @param modal is true if this should be a modal dialog
+  *
+  */
+  public GetAreaGUI(String s, boolean multi, boolean coa, boolean modal) {
+    this((Frame) null, modal, s, multi, coa, true);
+  }
+  /** 
+  * @param owner is the top-level Frame that owns this
+  * @param modal is true if this should be a modal dialog
+  * @param s is the label for the action button
+  * @param multi is true if multiple selection mode is to be used 
+  *  (this does not work yet!!)
+  * @param coa is true if the Dialog should close the window after
+  *  the actionPerformed is done.
+  *
+  */
+  public GetAreaGUI(Frame owner, boolean modal, String s, boolean multi,
+                    boolean coa) {
+    this(owner, modal, s, multi, coa, true);
+  }
+
+  /** 
+  * @param owner is the top-level Frame that owns this
+  * @param modal is true if this should be a modal dialog
+  * @param s is the label for the action button
+  * @param multi is true if multiple selection mode is to be used 
+  *  (this does not work yet!!)
+  * @param coa is true if the Dialog should close the window after
+  *  the actionPerformed is done.
+  * @param dodialog is true if this should pop up a Dialog interface
+  * box.  If this is false, then owner and modal may be null.
+  *
+  */
+  public GetAreaGUI(Frame owner, boolean modal, String s, boolean multi, boolean coa, boolean dodialog) {
+
+    setupGUI(s, multi, coa, owner, modal, dodialog);
+  }
+    
+  private void setupGUI(String s, boolean multi, boolean coa, Frame parent, boolean modal, boolean dod) {
+
+    serverUpdated = false;
+    groupUpdated = false;
+    descrUpdated = false;
+    bandNames = null;
+    closeOnAction = coa;
+    selectedUnit = " ";
+    selectedBand = " ";
+    selectedBandIndex = -1;
+    doingRes = false;
+    asb = null;
+    multipleImages = multi;
+    nf3 = NumberFormat.getNumberInstance();
+    nf3.setMaximumFractionDigits(3);
+    actionButtonString = s;
+    asi = new AddeServerInfo();
+    String[] sla = asi.getServerList();
+    dataProp = new Properties();
+    try {
+      String path = System.getProperty("user.home");
+      propFile =
+          path+System.getProperty("file.separator")+"GetAreaGUI.properties";
+      FileInputStream fi = new FileInputStream(propFile);
+      dataProp.load(fi);
+      fi.close();
+    } catch (Exception e) {;}
+    
+    String usl = (String) dataProp.get("user|server|list");
+    serverList = new StringBuffer();
+    ArrayList als = new ArrayList();
+    for (int i=0; i<sla.length; i++) {
+      als.add(sla[i]);
+    }
+    if (usl != null) {
+      serverList.append(usl); 
+      StringTokenizer slt = new StringTokenizer(usl,",");
+      int n = slt.countTokens();
+      for (int i=0; i<n; i++) {
+        als.add(slt.nextToken());
+      }
+    }
+
+    sl = new String[als.size()];
+    for (int i=0; i<als.size(); i++) {
+      sl[i] = (String) als.get(i);
+    }
+        
+    selectedServer = null;
+    selectedGroup = null;
+    selectedDescr = null;
+    selectedDateTime = null;
+    
+    baseNumLines = -1;
+    baseNumEles = -1;
+    
+    initComponents ();
+    buttGroupLoc = new javax.swing.ButtonGroup();
+    buttGroupLoc.add(LatLonButton);
+    buttGroupLoc.add(LinEleButton);
+    buttGroupLoc.add(IDButton);
+    
+    dialog = null;
+    if (dod) {
+      dialog = new JDialog(parent, "ADDE Image Data Selector", modal);
+      dialog.getContentPane().add(this);
+      dialog.pack();
+    }
+  }
+
+  public void show() {
+    if (dialog != null) dialog.show();
+  }
+
+    /** define the name of the ADDE server to select
+     * @param s - The ADDE server hostname.
+     */
+  public void setServer(String s) {
+    selectedServer = s;
+  }
+    /** fetch the name of the currently selected ADDE server
+     * @return The ADDE server (host) name
+     */
+  public String getServer() {
+    return selectedServer;
+  }
+    /** force a particular ADDE Group to be selected.  If this is done, then
+     * then you must also select the Dataset (aka Descr).
+     * @param s The name of the ADDE data group
+     */
+  public void setGroup(String s) {
+    selectedGroup = s;
+  }
+    /** fetch the name of the currently selected ADDE group
+     * @return the ADDE group name (abreviation)
+     */
+  public String getGroup() {
+    return selectedGroup;
+  }
+    /** force the selection of a particular ADDE Descr (dataset).  This will trigger
+     * fetch from the server of available times/days.
+     * @param s The name (abreviation) of the dataset (aka 'descr')
+     */
+  public void setDescr(String s) {
+    selectedDescr = s;
+  }
+    /** fetch the name (abreviation) of the currently selected dataset (descr)
+     * @return The name (abreviation) of the selected dataset/descr
+     */
+  public String getDescr() {
+    return selectedDescr;
+  }
+  
+  /** set the maginification (line element) factors
+  * 
+  */
+  public void setMag(String m) {
+    if (m != null) {
+      StringTokenizer st = new StringTokenizer(m," ");
+      int lm = Integer.parseInt(st.nextToken().trim());
+      int em = Integer.parseInt(st.nextToken().trim());
+      setLineMag(lm);
+      setEleMag(em);
+    } else {
+      setLineMag(1);
+      setEleMag(1);
+    }
+  }
+  
+  /** get the magnification factors (line element)
+  *
+  * @return string of line & element magnification, separated by a space
+  */
+  public String getMag() {
+    return (LMagValue+" "+EMagValue);
+    
+  }
+    /** define the value of the line maginfication (-50 thru +50).    Calling this also forces the Element Magnification
+     * to be set to this value.  You may set it separately, as needed.
+     * @param m The value of the line magnification (-50 thru +50).
+     * Values of -1, 0, and 1 will be treated as 1.
+     */
+  public void setLineMag(int m) {
+    LMagValue = m;
+    if (LMagValue > -2 && LMagValue < 2) LMagValue = 1;
+    LMagSlider.setValue(LMagValue+50);
+    if (doingRes) {
+      double v = resLat/LMagValue;
+      if (LMagValue < 0) v = -(resLat * LMagValue);
+      LMagLabel.setText("Line Resolution = "+nf3.format(v));
+    } else {
+      LMagLabel.setText("Line Magnification = "+LMagValue);
+    }
+    // need to set slider, too
+  }
+    /** fetch the current line magnification factor
+     * @return the line magnification factor
+     */
+  public int getLinMag() {
+    return LMagValue;
+  }
+  
+    /** set the element magnification factor.  This call should be made after a call
+     * to setLineMag()
+     * @param m The element magnification factor (-50 thru +50).
+     * Values of -1, 0, and 1 are treated as 1.
+     */
+  public void setEleMag(int m) {
+    EMagValue = m;
+    if (EMagValue > -2 && EMagValue < 2) EMagValue = 1;
+    EMagSlider.setValue(EMagValue+50);
+    if (doingRes) {
+      double v = resLon/EMagValue;
+      if (EMagValue < 0) v = -(resLon * EMagValue);
+      EMagLabel.setText("Element Resolution = "+nf3.format(v));
+    } else {
+      EMagLabel.setText("Element Magnification = "+EMagValue);
+    }
+  }
+  /** get the Element magnification
+  *
+  * @return element magnification factor
+  */
+  public int getEleMag() {
+    return EMagValue;
+  }
+  
+    /** define the coodinate type for the centered location values
+     * @param c coordinate = "E" for earth, "I" for image, "S" for radar
+     * station
+     */
+  public void setCoordType(String c) {
+    LatLonButton.setSelected(false);
+    LinEleButton.setSelected(false);
+    IDButton.setSelected(false);
+    if (c.equalsIgnoreCase("E")) LatLonButton.setSelected(true);
+    if (c.equalsIgnoreCase("I")) LinEleButton.setSelected(true);
+    if (c.equalsIgnoreCase("S")) IDButton.setSelected(true);
+    setLocButtonLabel();
+    // need to set slider, too
+  }
+
+    /** fetch the current coordinate type
+     * @return coordinate type value ("E", "I", or "S")
+     */
+  public String getCoordType() {
+    return coordType;
+  }
+  
+    /** define the location(s) for the coodinate type.  It is assumed that you will
+     * call setCoordType() and then setLocationString() with consistent values.
+     * @param c The coordinate locations. If two values, separate by
+     * one of more blanks.  For Earth coordinate, the latitude
+     * and longitude (e.g., "43.1234 -89.2313"); for Image
+     * coordinates, the line and element (e.g., "12345 23412"),
+     * and for Radar Stations, the station name (e.g., "KMKX").
+     */
+  public void setLocationString(String c) {
+    
+    String locOne = " ";
+    String locTwo = " ";
+    if (c != null) {
+      StringTokenizer st = new StringTokenizer(c," ");
+      locOne = st.nextToken();
+      
+      if (st.hasMoreTokens() ) {
+        locTwo = st.nextToken();
+      }
+    }
+    LatLineText.setText(locOne);
+    LonEleText.setText(locTwo);
+  }
+  
+    /** fetch the current locatoin string
+     * @return The value of the location string(s) as a single string
+     * with one blank space between values (if more than one)
+     */
+  public String getLocationString() {
+    String loc = LatLineText.getText() + " " + LonEleText.getText();
+    return loc;
+  }
+  
+    /** define the satellite band number to use
+     * @param c the band number (as a String)
+     */
+  public void setBand(String c) {
+  }
+    /** fetch the current band number
+     * @return the currently selected band number
+     */
+  public String getBand() {
+    return selectedBand;
+  }
+  
+    /** define the size of the image to get
+     * @param c The number of lines and number of elements, in a string
+     * with one or more blank spaces between (e.g., "480 640")
+     *
+     */
+  public void setNumLinesEles(String c) {
+    if (c == null || c.trim().length() < 3) {
+      NumLinesText.setText(" ");
+      NumElesText.setText(" ");
+      baseNumLines = -1;
+      baseNumEles = -1;
+      return;
+    }
+    StringTokenizer st = new StringTokenizer(c," ");
+    String lin = st.nextToken();
+    String ele = st.nextToken();
+    NumLinesText.setText(lin);
+    NumElesText.setText(ele);
+    baseNumLines = Integer.parseInt(lin.trim());
+    baseNumEles = Integer.parseInt(ele.trim());
+  }
+  
+    /** fetch the number of lines and elements defined
+     * @return the number of lines and elements, as a String with one
+     * blank between values (e.g., "480 640")
+     */
+  public String getNumLinesEles() {
+    String s = NumLinesText.getText() + " " + NumElesText.getText();
+    return s;
+  }
+  
+  /* get the day of the index-th selected item
+  */
+  public String getDay(int index) {
+    Object [] vals = DateTimeList.getSelectedValues();
+    selectedDateTime = (String) vals[index];
+    String day = null;
+    if (selectedDateTime != null) {
+      int i = selectedDateTime.indexOf("/");
+      if (i > 0) {
+        day = selectedDateTime.substring(0,i).trim();
+      }
+    }
+    return day;
+    
+  }
+
+  /** return the day of the selected image
+  * @return the day in the format:  yyyy-mm-dd
+  */
+  public String getDay() {
+    selectedDateTime = (String) DateTimeList.getSelectedValue();
+    String day = null;
+    if (selectedDateTime != null) {
+      int i = selectedDateTime.indexOf("/");
+      if (i > 0) {
+        day = selectedDateTime.substring(0,i).trim();
+      }
+    }
+    return day;
+    
+  }
+
+  /** set the day
+  *
+  * @param d the day in the form:  yyyy-mm-dd
+  */
+  public void setDay (String d) {
+    setDateTime(d,getTime());
+  }
+  
+  /** get the time of the index-th selected image
+  */
+  public String getTime(int index) {
+    Object [] vals = DateTimeList.getSelectedValues();
+    selectedDateTime = (String) vals[index];
+    String time = null;
+    if (selectedDateTime != null) {
+      int i = selectedDateTime.indexOf("/");
+      if (i > 0) {
+        time = selectedDateTime.substring(i+1).trim();
+      }
+    }
+    return time;
+  }
+
+  /** get the time of the selected image
+  *
+  * @return the time in the format:  hh:mm:ss
+  */
+  public String getTime() {
+    selectedDateTime = (String) DateTimeList.getSelectedValue();
+    String time = null;
+    if (selectedDateTime != null) {
+      int i = selectedDateTime.indexOf("/");
+      if (i > 0) {
+        time = selectedDateTime.substring(i+1).trim();
+      }
+    }
+    return time;
+  }
+  /** set the time 
+  *
+  * @param t is the time in the format:  hh:mm:ss
+  */
+  public void setTime(String t) {
+    setDateTime(getDay(), t);
+  }
+  
+  public void setDateTime(String d, String t) {
+    selectedDateTime = d+" / "+t;
+    return;
+  }
+  
+    /** define the units of the data to get
+     * @param c the name of the Units (e.g., "BRIT")
+     */
+  public void setUnit(String c) {
+    //UnitText.setText(c);
+  }
+    /** fetch the name of the units defined
+     * @return the name of the units
+     */
+  public String getUnit() {
+    //return UnitText.getText();
+    return selectedUnit;
+  }
+  
+    /** define the calibration type to use (e.g., "VISSR")
+     * @param c the calibration type
+     */
+  public void setCal (String c) {
+    calText = c;
+  }
+    /** fetch the current Calibration type
+     * @return the current calibration type
+     */
+  public String getCal() {
+    return calText;
+  }
+  
+    /** define whether the "documentation block" will be returned with the data.
+     * @param v set to 'true' to return the history documentation.
+     */
+  public void setDoc (String v) {
+    if (v != null && v.indexOf("1")>-1) {
+//      DocBox.setSelected(true);
+    } else {
+      //DocBox.setSelected(false);
+    }
+    
+  }
+    /** fetch the state of the doc request switch
+     * @return the state of the doc request switch
+     */
+  public String getDoc() {
+//    if (DocBox.isSelected()) {
+      return "1";
+//    } else {
+//      return null;
+    //}
+  }
+  
+    /** define a user name (required by some ADDE servers for accounting).  If given,
+     * this will be included in all requests generated by GetAreaGUI.
+     * @param c The user's ADDE identifier (e.g., "jack")
+     */
+  public void setUserName (String c) {
+    userName = c;
+  }
+    /** fetch the currently defined user name
+     * @return the currently defined user name
+     */
+  public String getUserName() {
+    return userName;
+  }
+  
+    /** define a project number, which is required by some ADDE servers
+     * @param c The project number to use (e.g., "12345")
+     */
+  public void setProjectNumber (String c) {
+    projectNumber = c;;
+  }
+    /** fetch the currently defined project number
+     * @return the project number value
+     */
+  public String getProjectNumber() {
+    return projectNumber;
+  }
+  
+  
+    /** This method is called from within the constructor to
+     * initialize the form.
+     * WARNING: Do NOT modify this code. The content of this method is
+     * always regenerated by the FormEditor.
+     */
+  private void initComponents() {//GEN-BEGIN:initComponents
+            PanelSGD = new javax.swing.JPanel();
+            PanelSG = new javax.swing.JPanel();
+            PanelServer = new javax.swing.JPanel();
+            ServerLabel = new javax.swing.JLabel();
+            ServerSelector = new javax.swing.JComboBox();
+            PanelGroup = new javax.swing.JPanel();
+            jLabel2 = new javax.swing.JLabel();
+            GroupSelector = new javax.swing.JComboBox();
+            PanelDescr = new javax.swing.JPanel();
+            jLabel3 = new javax.swing.JLabel();
+            DescrSelector = new javax.swing.JComboBox();
+            PanelListMag = new javax.swing.JPanel();
+            PanelMag = new javax.swing.JPanel();
+            jPanel13 = new javax.swing.JPanel();
+            jPanel9 = new javax.swing.JPanel();
+            LMagSlider = new javax.swing.JSlider();
+            LMagLabel = new javax.swing.JLabel();
+            jPanel10 = new javax.swing.JPanel();
+            EMagLabel = new javax.swing.JLabel();
+            EMagSlider = new javax.swing.JSlider();
+            DateTimeLabel = new javax.swing.JLabel();
+            PanelList = new javax.swing.JPanel();
+            DateTimeScrollPanel = new javax.swing.JScrollPane();
+            String [] prompt = {"Date-times of available","images will appear here"};
+            DateTimeList =  new javax.swing.JList(prompt);
+            LinesElesPanel = new javax.swing.JPanel();
+            SizeLabel = new javax.swing.JLabel();
+            jPanel7 = new javax.swing.JPanel();
+            NumLinesLabel = new javax.swing.JLabel();
+            NumLinesText = new javax.swing.JTextField();
+            NumElesLabel = new javax.swing.JLabel();
+            NumElesText = new javax.swing.JTextField();
+            userDefaultsCheckBox = new javax.swing.JCheckBox();
+            PanelBandUnit = new javax.swing.JPanel();
+            BandPanel = new javax.swing.JPanel();
+            BandLabel = new javax.swing.JLabel();
+            BandBox = new javax.swing.JComboBox();
+            UnitsPanel = new javax.swing.JPanel();
+            UnitLabel = new javax.swing.JLabel();
+            UnitBox = new javax.swing.JComboBox();
+            UserActionPanel = new javax.swing.JPanel();
+            userActionButton = new javax.swing.JButton();
+            PanelStatus = new javax.swing.JPanel();
+            statusLabel = new javax.swing.JTextField();
+            PanelLoc = new javax.swing.JPanel();
+            jPanel11 = new javax.swing.JPanel();
+            PlaceLabel = new javax.swing.JLabel();
+            LatLonButton = new javax.swing.JRadioButton();
+            LinEleButton = new javax.swing.JRadioButton();
+            IDButton = new javax.swing.JRadioButton();
+            jPanel12 = new javax.swing.JPanel();
+            LatLineLabel = new javax.swing.JLabel();
+            LonEleLabel = new javax.swing.JLabel();
+            LatLineText = new javax.swing.JTextField();
+            LonEleText = new javax.swing.JTextField();
+            
+            setLayout(new java.awt.GridBagLayout());
+            java.awt.GridBagConstraints gridBagConstraints1;
+            
+            setFont(new java.awt.Font("SansSerif", 0, 10));
+            
+            PanelSGD.setLayout(new java.awt.BorderLayout());
+            
+            PanelSGD.setMaximumSize(new java.awt.Dimension(500, 160));
+            PanelServer.setLayout(new java.awt.GridLayout(2, 1));
+            
+            PanelServer.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+            PanelServer.setPreferredSize(new java.awt.Dimension(180, 45));
+            ServerLabel.setText("Server");
+            ServerLabel.setToolTipText("Select an ADDE Server from the list");
+            ServerLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+            PanelServer.add(ServerLabel);
+            
+            ServerSelector.setToolTipText("Select an ADDE Server from the list");
+            ServerSelector.setBackground(java.awt.Color.lightGray);
+            ServerSelector.setEditable(true);
+            ServerSelector.setActionCommand("serverSelected");
+            replaceList(ServerSelector, sl, "Select ADDE server");
+            ServerSelector.setSelectedIndex(0);
+            ServerSelector.addActionListener(new java.awt.event.ActionListener() {
+                public void actionPerformed(java.awt.event.ActionEvent evt) {
+                    ServerSelectorActionPerformed(evt);
+                }
+            });
+            
+            PanelServer.add(ServerSelector);
+            
+            PanelSG.add(PanelServer);
+          
+          PanelGroup.setLayout(new java.awt.GridLayout(2, 1));
+            
+            PanelGroup.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+            PanelGroup.setPreferredSize(new java.awt.Dimension(160, 45));
+            jLabel2.setText("Dataset");
+            jLabel2.setToolTipText("Select a dataset from the list");
+            jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+            PanelGroup.add(jLabel2);
+            
+            GroupSelector.setToolTipText("Select an ADDE data group from the list");
+            GroupSelector.setBackground(java.awt.Color.lightGray);
+            GroupSelector.setActionCommand("groupSelected");
+            GroupSelector.addActionListener(new java.awt.event.ActionListener() {
+                public void actionPerformed(java.awt.event.ActionEvent evt) {
+                    GroupSelectorActionPerformed(evt);
+                }
+            });
+            
+            PanelGroup.add(GroupSelector);
+            
+            PanelSG.add(PanelGroup);
+          
+          PanelSGD.add(PanelSG, java.awt.BorderLayout.NORTH);
+        
+        PanelDescr.setLayout(new java.awt.BorderLayout());
+          
+          PanelDescr.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+          PanelDescr.setMaximumSize(new java.awt.Dimension(400, 50));
+          jLabel3.setText("Data Type");
+          jLabel3.setToolTipText("Select a Data Type; available dates and times will appear below");
+          jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+          PanelDescr.add(jLabel3, java.awt.BorderLayout.NORTH);
+          
+          DescrSelector.setToolTipText("Select an ADDE dataset; available dates and times will appear below");
+          DescrSelector.setBackground(java.awt.Color.lightGray);
+          DescrSelector.setName("descrType");
+          DescrSelector.setActionCommand("descrSelected");
+          DescrSelector.setMaximumSize(new java.awt.Dimension(500, 20));
+          DescrSelector.addActionListener(new java.awt.event.ActionListener() {
+              public void actionPerformed(java.awt.event.ActionEvent evt) {
+                  DescrSelectorActionPerformed(evt);
+              }
+          });
+          
+          PanelDescr.add(DescrSelector, java.awt.BorderLayout.SOUTH);
+          
+          PanelSGD.add(PanelDescr, java.awt.BorderLayout.SOUTH);
+        
+        gridBagConstraints1 = new java.awt.GridBagConstraints();
+              gridBagConstraints1.gridx = 0;
+              gridBagConstraints1.gridy = 0;
+              gridBagConstraints1.insets = new java.awt.Insets(2, 0, 2, 0);
+              add(PanelSGD, gridBagConstraints1);
+              
+              PanelListMag.setLayout(new java.awt.GridBagLayout());
+              java.awt.GridBagConstraints gridBagConstraints2;
+              
+              PanelListMag.setMinimumSize(new java.awt.Dimension(400, 150));
+              PanelMag.setLayout(new java.awt.BorderLayout(10, 5));
+              
+              jPanel13.setLayout(new java.awt.BorderLayout(10, 0));
+              
+              jPanel13.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+              jPanel9.setLayout(new java.awt.BorderLayout());
+              
+              LMagSlider.setToolTipText("Slide to set line magnification factor");
+              LMagSlider.addChangeListener(new javax.swing.event.ChangeListener() {
+                  public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                      LMagSliderStateChanged(evt);
+                  }
+              });
+              
+              jPanel9.add(LMagSlider, java.awt.BorderLayout.SOUTH);
+              
+              LMagLabel.setText("Line Magnification");
+              LMagLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+              jPanel9.add(LMagLabel, java.awt.BorderLayout.NORTH);
+              
+              jPanel13.add(jPanel9, java.awt.BorderLayout.NORTH);
+            
+            jPanel10.setLayout(new java.awt.BorderLayout());
+              
+              EMagLabel.setText("Element Magnification");
+              EMagLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+              jPanel10.add(EMagLabel, java.awt.BorderLayout.NORTH);
+              
+              EMagSlider.setToolTipText("Slide to select element magnification factor");
+              EMagSlider.addChangeListener(new javax.swing.event.ChangeListener() {
+                  public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                      EMagSliderStateChanged(evt);
+                  }
+              });
+              
+              jPanel10.add(EMagSlider, java.awt.BorderLayout.SOUTH);
+              
+              jPanel13.add(jPanel10, java.awt.BorderLayout.SOUTH);
+            
+            PanelMag.add(jPanel13, java.awt.BorderLayout.NORTH);
+          
+          gridBagConstraints2 = new java.awt.GridBagConstraints();
+        gridBagConstraints2.gridx = 0;
+        gridBagConstraints2.gridy = 0;
+        gridBagConstraints2.gridheight = 3;
+        gridBagConstraints2.insets = new java.awt.Insets(5, 5, 5, 5);
+        gridBagConstraints2.anchor = java.awt.GridBagConstraints.NORTH;
+        PanelListMag.add(PanelMag, gridBagConstraints2);
+        
+        DateTimeLabel.setText("List of available Date / Times");
+        gridBagConstraints2 = new java.awt.GridBagConstraints();
+        gridBagConstraints2.gridx = 1;
+        gridBagConstraints2.gridy = 0;
+        gridBagConstraints2.anchor = java.awt.GridBagConstraints.SOUTH;
+        PanelListMag.add(DateTimeLabel, gridBagConstraints2);
+        
+        PanelList.setLayout(new java.awt.BorderLayout(0, 10));
+            
+            PanelList.setPreferredSize(new java.awt.Dimension(180, 150));
+            PanelList.setMinimumSize(new java.awt.Dimension(180, 150));
+            DateTimeScrollPanel.setPreferredSize(new java.awt.Dimension(180, 150));
+            DateTimeScrollPanel.setMinimumSize(new java.awt.Dimension(180, 150));
+            DateTimeList.setToolTipText("Click on the date-time you want");
+            DateTimeList.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+            DateTimeList.setName("");
+            DateTimeList.setVisibleRowCount(10);
+            if( multipleImages) {
+              DateTimeList.setSelectionMode(javax.swing.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+            } else {
+              DateTimeList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+            }
+            DateTimeList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
+                public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
+                    DateTimeListValueChanged(evt);
+                }
+            });
+            
+            DateTimeScrollPanel.setViewportView(DateTimeList);
+            
+            PanelList.add(DateTimeScrollPanel, java.awt.BorderLayout.CENTER);
+          
+          gridBagConstraints2 = new java.awt.GridBagConstraints();
+        gridBagConstraints2.gridx = 1;
+        gridBagConstraints2.gridy = 1;
+        gridBagConstraints2.gridheight = 4;
+        gridBagConstraints2.insets = new java.awt.Insets(0, 3, 0, 0);
+        gridBagConstraints2.anchor = java.awt.GridBagConstraints.NORTH;
+        PanelListMag.add(PanelList, gridBagConstraints2);
+        
+        LinesElesPanel.setLayout(new java.awt.BorderLayout());
+          
+          LinesElesPanel.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+          SizeLabel.setText("Size of Image to Get");
+          SizeLabel.setToolTipText("Enter the number of lines and elements for the image you want (def=480 x 640)");
+          SizeLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+          LinesElesPanel.add(SizeLabel, java.awt.BorderLayout.NORTH);
+          
+          jPanel7.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 0));
+            
+            NumLinesLabel.setText("Lines:");
+            NumLinesLabel.setToolTipText("Enter the number of image lines you want");
+            jPanel7.add(NumLinesLabel);
+            
+            NumLinesText.setToolTipText("Enter number of lines (def=480)");
+            NumLinesText.setColumns(5);
+            NumLinesText.setText(" ");
+            jPanel7.add(NumLinesText);
+            
+            NumElesLabel.setText("       Elements:");
+            NumElesLabel.setToolTipText("Enter the number of image Elements you want");
+            jPanel7.add(NumElesLabel);
+            
+            NumElesText.setToolTipText("Enter number of elements (def=640)");
+            NumElesText.setColumns(5);
+            NumElesText.setText(" ");
+            jPanel7.add(NumElesText);
+            
+            LinesElesPanel.add(jPanel7, java.awt.BorderLayout.SOUTH);
+          
+          gridBagConstraints2 = new java.awt.GridBagConstraints();
+        gridBagConstraints2.gridx = 0;
+        gridBagConstraints2.gridy = 3;
+        gridBagConstraints2.insets = new java.awt.Insets(2, 0, 2, 0);
+        PanelListMag.add(LinesElesPanel, gridBagConstraints2);
+        
+        userDefaultsCheckBox.setToolTipText("Check this box to use your defaults for size and location");
+        userDefaultsCheckBox.setSelected(false);
+        userDefaultsCheckBox.setText("Use my defaults");
+        userDefaultsCheckBox.setAlignmentX(0.5F);
+        userDefaultsCheckBox.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                userDefaultsCheckBoxActionPerformed(evt);
+            }
+        });
+        
+        gridBagConstraints2 = new java.awt.GridBagConstraints();
+        gridBagConstraints2.gridx = 0;
+        gridBagConstraints2.gridy = 4;
+        gridBagConstraints2.anchor = java.awt.GridBagConstraints.NORTH;
+        PanelListMag.add(userDefaultsCheckBox, gridBagConstraints2);
+        
+        gridBagConstraints1 = new java.awt.GridBagConstraints();
+          gridBagConstraints1.gridx = 0;
+          gridBagConstraints1.gridy = 1;
+          add(PanelListMag, gridBagConstraints1);
+          
+          PanelBandUnit.setLayout(new java.awt.GridBagLayout());
+          java.awt.GridBagConstraints gridBagConstraints3;
+          
+          PanelBandUnit.setPreferredSize(new java.awt.Dimension(500, 75));
+          PanelBandUnit.setMinimumSize(new java.awt.Dimension(300, 75));
+          BandPanel.setLayout(new java.awt.BorderLayout());
+          
+          BandPanel.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
+          BandLabel.setText("Channel");
+          BandLabel.setToolTipText("Select channel from list");
+          BandLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+          BandLabel.setAlignmentX(0.5F);
+          BandPanel.add(BandLabel, java.awt.BorderLayout.NORTH);
+          
+          BandBox.setToolTipText("Select a band/channel number from the list or enter one");
+          BandBox.setActionCommand("bandBoxChanged");
+          BandBox.addActionListener(new java.awt.event.ActionListener() {
+              public void actionPerformed(java.awt.event.ActionEvent evt) {
+                  BandBoxActionPerformed(evt);
+              }
+          });
+          
+          BandPanel.add(BandBox, java.awt.BorderLayout.WEST);
+          
+          gridBagConstraints3 = new java.awt.GridBagConstraints();
+        gridBagConstraints3.gridx = 0;
+        gridBagConstraints3.gridy = 0;
+        gridBagConstraints3.gridwidth = java.awt.GridBagConstraints.RELATIVE;
+        gridBagConstraints3.ipady = 5;
+        gridBagConstraints3.insets = new java.awt.Insets(0, 5, 0, 5);
+        PanelBandUnit.add(BandPanel, gridBagConstraints3);
+        
+        UnitsPanel.setLayout(new java.awt.BorderLayout());
+          
+          UnitsPanel.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
+          UnitLabel.setText("Unit");
+          UnitLabel.setToolTipText("Select unit from list after selecting Band");
+          UnitLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+          UnitsPanel.add(UnitLabel, java.awt.BorderLayout.NORTH);
+          
+          UnitBox.setToolTipText("Select a unit from the list or enter a units code");
+          UnitBox.setActionCommand("unitBoxChanged");
+          UnitBox.addActionListener(new java.awt.event.ActionListener() {
+              public void actionPerformed(java.awt.event.ActionEvent evt) {
+                  UnitBoxActionPerformed(evt);
+              }
+          });
+          
+          UnitsPanel.add(UnitBox, java.awt.BorderLayout.WEST);
+          
+          gridBagConstraints3 = new java.awt.GridBagConstraints();
+        gridBagConstraints3.gridx = 1;
+        gridBagConstraints3.gridy = 0;
+        gridBagConstraints3.gridwidth = java.awt.GridBagConstraints.RELATIVE;
+        gridBagConstraints3.ipadx = 2;
+        gridBagConstraints3.ipady = 5;
+        gridBagConstraints3.insets = new java.awt.Insets(0, 4, 0, 4);
+        PanelBandUnit.add(UnitsPanel, gridBagConstraints3);
+        
+        gridBagConstraints1 = new java.awt.GridBagConstraints();
+        gridBagConstraints1.gridx = 0;
+        gridBagConstraints1.gridy = 2;
+        add(PanelBandUnit, gridBagConstraints1);
+        
+        userActionButton.setToolTipText("Click button to "+actionButtonString);
+        userActionButton.setText(actionButtonString);
+        userActionButton.setActionCommand("user_action");
+        userActionButton.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                userActionButtonActionPerformed(evt);
+            }
+        });
+        
+        UserActionPanel.add(userActionButton);
+        
+        gridBagConstraints1 = new java.awt.GridBagConstraints();
+        gridBagConstraints1.gridx = 0;
+        gridBagConstraints1.gridy = 5;
+        add(UserActionPanel, gridBagConstraints1);
+        
+        statusLabel.setEditable(false);
+        statusLabel.setColumns(40);
+        statusLabel.setForeground(java.awt.Color.white);
+        statusLabel.setText("First pick an ADDE server");
+        statusLabel.setBackground(java.awt.Color.black);
+        statusLabel.setPreferredSize(new java.awt.Dimension(440, 20));
+        statusLabel.setMinimumSize(new java.awt.Dimension(100, 20));
+        statusLabel.addActionListener(new java.awt.event.ActionListener() {
+            public void actionPerformed(java.awt.event.ActionEvent evt) {
+                statusLabelActionPerformed(evt);
+            }
+        });
+        
+        PanelStatus.add(statusLabel);
+        
+        gridBagConstraints1 = new java.awt.GridBagConstraints();
+          gridBagConstraints1.gridx = 0;
+          gridBagConstraints1.gridy = 7;
+          add(PanelStatus, gridBagConstraints1);
+          
+          PanelLoc.setLayout(new java.awt.BorderLayout());
+          
+          PanelLoc.setBorder(new javax.swing.border.BevelBorder(javax.swing.border.BevelBorder.RAISED));
+          jPanel11.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 0));
+          
+          PlaceLabel.setText("Location:");
+          jPanel11.add(PlaceLabel);
+          
+          LatLonButton.setToolTipText("Click to select lat/lon coordinates");
+          LatLonButton.setSelected(true);
+          LatLonButton.setLabel("Lat/Lon");
+          LatLonButton.setActionCommand("LatLonButt");
+          LatLonButton.addChangeListener(new javax.swing.event.ChangeListener() {
+              public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                  LatLonButtonStateChanged(evt);
+              }
+          });
+          
+          jPanel11.add(LatLonButton);
+          
+          LinEleButton.setToolTipText("Click to select line/element coordinates");
+          LinEleButton.setLabel("Line/Ele");
+          LinEleButton.setActionCommand("LineEleButt");
+          LinEleButton.addChangeListener(new javax.swing.event.ChangeListener() {
+              public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                  LinEleButtonStateChanged(evt);
+              }
+          });
+          
+          jPanel11.add(LinEleButton);
+          
+          IDButton.setToolTipText("Click to select a station ID (Note: RADAR DATA ONLY!)");
+          IDButton.setText("Stn ID");
+          IDButton.setActionCommand("IDButt");
+          IDButton.addChangeListener(new javax.swing.event.ChangeListener() {
+              public void stateChanged(javax.swing.event.ChangeEvent evt) {
+                  IDButtonStateChanged(evt);
+              }
+          });
+          
+          jPanel11.add(IDButton);
+          
+          PanelLoc.add(jPanel11, java.awt.BorderLayout.NORTH);
+        
+        jPanel12.setLayout(new java.awt.GridLayout(2, 2, 5, 0));
+          
+          LatLineLabel.setText("Latitude");
+          LatLineLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+          jPanel12.add(LatLineLabel);
+          
+          LonEleLabel.setText("Longitude");
+          LonEleLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+          jPanel12.add(LonEleLabel);
+          
+          LatLineText.setToolTipText("Enter latitude or line coordinate or Radar Station ID");
+          LatLineText.setText("Lat or line or stn ID value");
+          jPanel12.add(LatLineText);
+          
+          LonEleText.setToolTipText("Enter longitude or element coordinate value");
+          LonEleText.setText("Long or Element value");
+          jPanel12.add(LonEleText);
+          
+          PanelLoc.add(jPanel12, java.awt.BorderLayout.SOUTH);
+        
+        gridBagConstraints1 = new java.awt.GridBagConstraints();
+      gridBagConstraints1.gridx = 0;
+      gridBagConstraints1.gridy = 3;
+      add(PanelLoc, gridBagConstraints1);
+      
+  }//GEN-END:initComponents
+
+  private void statusLabelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_statusLabelActionPerformed
+      // Add your handling code here:
+  }//GEN-LAST:event_statusLabelActionPerformed
+
+  private void userDefaultsCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_userDefaultsCheckBoxActionPerformed
+// Add your handling code here:
+    useDefaults = userDefaultsCheckBox.isSelected();
+  }//GEN-LAST:event_userDefaultsCheckBoxActionPerformed
+    
+  private void BandBoxActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_BandBoxActionPerformed
+    // Add your handling code here:
+    int i = BandBox.getSelectedIndex();
+    if (i > 0) {
+      replaceList(UnitBox,calInfo[1][i-1],"Select Unit");
+      int bi = bandListIndex[i-1];
+      selectedBand = bandList[i-1];
+      selectedBandIndex = i-1;
+    
+      if (!useDefaults || !gotUserDefaults) {
+        double clat = ad[areaIndex][bi].getCenterLatitude();
+        double clon = ad[areaIndex][bi].getCenterLongitude();
+        if (! (Double.isNaN(clat) || Double.isNaN(clon)) ) {
+          setCoordType("E");
+          setLocationString(clat+" "+clon);
+        }
+
+        resLat = ad[areaIndex][bi].getCenterLatitudeResolution();
+        resLon = ad[areaIndex][bi].getCenterLongitudeResolution();
+
+        // if we have resolution data, change the sliders...
+        if (! (Double.isNaN(resLat) || Double.isNaN(resLon)) ) {
+          doingRes = true;
+        }
+        setLineMag(1);
+        setEleMag(1);
+
+        int nlines= ad[areaIndex][bi].getLines();
+        int neles = ad[areaIndex][bi].getElements();
+        setNumLinesEles(nlines+" "+neles);
+        
+        if (useDefaults) gotUserDefaults = true;
+      }   
+    }
+  }//GEN-LAST:event_BandBoxActionPerformed
+  
+  private void UnitBoxActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_UnitBoxActionPerformed
+    // Add your handling code here:
+    int unitIndex = UnitBox.getSelectedIndex();
+    if (unitIndex > 0 && selectedBandIndex >= 0) 
+       selectedUnit = calInfo[0][selectedBandIndex][unitIndex - 1];
+    
+  }//GEN-LAST:event_UnitBoxActionPerformed
+  
+  private void DateTimeListValueChanged (javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_DateTimeListValueChanged
+    
+    selectedDateTime = (String) DateTimeList.getSelectedValue();
+    if (selectedDateTime == null) return;
+    
+    areaIndex = DateTimeList.getSelectedIndex();
+    
+    double clat = ad[areaIndex][0].getCenterLatitude();
+    double clon = ad[areaIndex][0].getCenterLongitude();
+    
+    if (!useDefaults || !gotUserDefaults) {
+      
+      if (!(Double.isNaN(clat) || Double.isNaN(clon)) ) {
+        setCoordType("E");
+        setLocationString(clat+" "+clon);
+      }
+
+      resLat = ad[areaIndex][0].getCenterLatitudeResolution();
+      resLon = ad[areaIndex][0].getCenterLongitudeResolution();
+
+      // if we have resolution data, change the sliders...
+      if (! (Double.isNaN(resLat) || Double.isNaN(resLon)) ) {
+        doingRes = true;
+      }
+      setLineMag(1);
+      setEleMag(1);
+
+      int nlines= ad[areaIndex][0].getLines();
+      int neles = ad[areaIndex][0].getElements();
+      setNumLinesEles(nlines+" "+neles);
+
+      if (useDefaults) gotUserDefaults = true;
+    }
+    
+    ArrayList arrayBandList = new ArrayList();
+    ArrayList arrayBandIndex = new ArrayList();
+    ArrayList arrayCalInfo = new ArrayList();
+    asb = new AddeSatBands(asi.getBandNames());
+    String[][] ci;
+    String[] bandInfo = null;
+    
+    for (int na=0; na<ad[areaIndex].length; na++) {
+      
+      int nbands = ad[areaIndex][na].getNumberOfBands();
+      int [] bl = ad[areaIndex][na].getBands();
+      String [] sb = new String[nbands];
+      Vector[] vb = ad[areaIndex][na].getCalInfo();
+      int sid = ad[areaIndex][na].getSensorID();
+      String srcType = ad[areaIndex][na].getSourceType() ;
+      bandInfo = asb.getBandDescr(sid, srcType);
+          
+      for (int k=0; k<nbands; k++) {
+        
+        sb[k] = Integer.toString(bl[k]);
+        arrayBandList.add(sb[k]);
+        arrayBandIndex.add(new Integer(na));
+        ci = new String[2][1];
+        ci[0][0] = "RAW";
+        ci[1][0] = "Raw values";
+        if (vb[k] != null) {
+          int vbnum = vb[k].size()/2;
+          if (vbnum > 0) {
+            ci = new String[2][vbnum];
+            for (int j=0; j<vbnum; j++) {
+              ci[0][j] = (String) vb[k].elementAt(2*j);
+              ci[1][j] = (String) vb[k].elementAt(2*j + 1);
+            }
+          }
+        }
+        arrayCalInfo.add(ci);
+      }
+    }
+
+    int ns = arrayBandList.size();
+    if (ns != 0) {
+      
+      bandList = new String[ns];
+      bandListIndex = new int[ns];
+      String[] bandListNames = new String[ns];
+      calInfo = new String[2][ns][];
+      for (int ks=0; ks<ns; ks++) {
+        bandList[ks] = (String) arrayBandList.get(ks);
+        bandListNames[ks] = bandList[ks];
+        if (bandInfo != null) {
+          int bn = (Integer.valueOf(bandList[ks])).intValue();
+          if (bn >0 && bn < bandInfo.length) {
+            bandListNames[ks] = bandList[ks]+" - "+bandInfo[bn];
+          }
+        }
+        
+        bandListIndex[ks] = ( (Integer) arrayBandIndex.get(ks)).intValue();
+        ci = (String[][]) arrayCalInfo.get(ks);
+        int nc = ci[0].length;
+        calInfo[0][ks] = ci[0];
+        calInfo[1][ks] = ci[1];
+      }
+
+      if (bandList.length == 1) {
+        replaceList(BandBox,bandListNames,null);
+        replaceList(UnitBox,calInfo[1][0],"Select Unit");
+        selectedBand = bandList[0];
+        selectedBandIndex = 0;
+      } else {
+        replaceList(BandBox, bandListNames, "Select band");
+        replaceList(UnitBox, null, " ");
+        selectedBand = bandList[0];
+        selectedBandIndex = 1;
+      }
+    }
+    status("Set the other parameters you want");
+  }//GEN-LAST:event_DateTimeListValueChanged
+  
+  public synchronized void status(String m) {
+    /*
+    String mt = m.trim();
+    int k = (mt.length() > 80) ? 80 : mt.length();
+    statusLabel.setText(mt.substring(0,k));
+    System.out.println("Status:"+mt.substring(0,k));
+     */
+    statusLabel.setText(m);
+    //System.out.println("Status:"+m);
+    statusLabel.validate();
+  }
+  
+  public void addActionListener(ActionListener o) {
+    al = o;
+  }
+
+  public void removeActionListener(ActionListener o) {
+    if (o == al) al = null;
+  }
+
+  private void userActionButtonActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_userActionButtonActionPerformed
+    // Add your handling code here:
+    status("Processing...please wait...");
+
+    String prop = getServer()+"|"+getGroup()+"|"+getDescr();
+    dataProp.put(prop+"|CoordType",getCoordType());
+    dataProp.put(prop+"|Location",getLocationString());
+    dataProp.put(prop+"|Size",getNumLinesEles());
+    dataProp.put(prop+"|Mag",getMag());
+    if (getUserName() != null) 
+           dataProp.put(getServer()+"|UserName",getUserName());
+    if (getProjectNumber() != null) 
+           dataProp.put(getServer()+"|ProjectNumber",getProjectNumber());
+    if (serverList != null) dataProp.put("user|server|list",serverList.toString());
+    
+    try {
+      FileOutputStream fo = new FileOutputStream(propFile);
+      dataProp.save(fo,"GetAreaGUI");
+      fo.close();
+    } catch (Exception es) {;}
+   
+    String loc = " ";
+    if (coordType.equals("E")) {
+      loc = "&latlon=";
+    } else if (coordType.equals("I")) {
+      loc = "&linele=";
+    } else if (coordType.equals("S")) {
+      loc = "&id=";
+    }
+      
+    String up = getUserName();
+    if (up == null || up.length() < 2) {
+      up = " ";
+    } else {
+      up = "&user="+getUserName()+"&proj="+getProjectNumber();
+    }
+    
+    String un = getUnit();
+    if (un == null || un.length()<3) un="BRIT";
+    imageList = new ArrayList();
+    
+    if (multipleImages) {
+      int numimages = DateTimeList.getSelectedIndices().length;
+      StringBuffer cmdbuf = new StringBuffer();
+
+      for (int i=0; i<numimages; i++) {
+
+        String addecmd = new String
+        ("adde://"+getServer()+"/imagedata?group="+getGroup()+
+          "&descr="+getDescr()+loc+getLocationString()+
+          "&size="+getNumLinesEles()+"&day="+getDay(i)+"&time="+getTime(i)+" "+
+          getTime(i)+" I "+
+          "&band="+getBand()+"&unit="+un+"&mag="+getMag()+ up + "&version=1");
+        cmdbuf.append(addecmd);
+        imageList.add(addecmd);
+        if (i+1 < numimages) cmdbuf.append("|");
+      }
+      cmdout = cmdbuf.toString();
+
+    } else {
+      cmdout = "adde://"+getServer()+"/imagedata?group="+getGroup()+
+         "&descr="+getDescr()+loc+getLocationString()+
+         "&size="+getNumLinesEles()+"&day="+getDay()+"&time="+getTime()+" "+
+         getTime()+" I "+
+         "&band="+getBand()+"&unit="+un+"&mag="+getMag()+ up +
+         "&version=1";
+      imageList.add(cmdout);
+    }
+    System.out.println("cmdout = "+cmdout);
+
+    if (al != null) {
+      al.actionPerformed(
+            new ActionEvent(this,ActionEvent.ACTION_PERFORMED, cmdout) );
+    }
+
+    if (closeOnAction && (dialog != null)) dialog.dispose();
+    
+  }//GEN-LAST:event_userActionButtonActionPerformed
+  
+  public List getImageList() {
+    return (List) imageList;
+  }
+
+  public String toString() {
+    return cmdout;
+  }
+
+  private synchronized void setLocButtonLabel() {
+    if (LatLonButton.isSelected()) {
+      LatLineLabel.setText("Latitude");
+      LonEleLabel.setText("Longitude");
+      coordType = "E";
+      
+    } else if (LinEleButton.isSelected()) {
+      LatLineLabel.setText("Image Line");
+      LonEleLabel.setText("Image Element");
+      coordType = "I";
+      
+    } else if (IDButton.isSelected()) {
+      LatLineLabel.setText("Radar Station ID");
+      LonEleLabel.setText(" ");
+      coordType = "S";
+    }
+  }
+  private void IDButtonStateChanged (javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_IDButtonStateChanged
+    // Add your handling code here:
+    setLocButtonLabel();
+  }//GEN-LAST:event_IDButtonStateChanged
+  
+  private void LinEleButtonStateChanged (javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_LinEleButtonStateChanged
+    // Add your handling code here:
+    setLocButtonLabel();
+  }//GEN-LAST:event_LinEleButtonStateChanged
+  
+  private void LatLonButtonStateChanged (javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_LatLonButtonStateChanged
+    // Add your handling code here:
+    setLocButtonLabel();
+    
+  }//GEN-LAST:event_LatLonButtonStateChanged
+  
+  private void EMagSliderStateChanged (javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_EMagSliderStateChanged
+    // Add your handling code here:
+    int v = EMagSlider.getValue();
+    setEleMag(v - 50);
+    if (baseNumEles < 1 ) return;
+    int x = baseNumEles * EMagValue;
+    if (EMagValue < 0) x = baseNumEles / -EMagValue;
+    NumElesText.setText(Integer.toString(x));
+  }//GEN-LAST:event_EMagSliderStateChanged
+  
+  private void LMagSliderStateChanged (javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_LMagSliderStateChanged
+    // Add your handling code here:
+    int v = LMagSlider.getValue();
+    setLineMag(v - 50);
+    setEleMag(v - 50);
+    if (baseNumLines < 1 ) return;
+    int x = baseNumLines * LMagValue;
+    if (LMagValue < 0) x = baseNumLines / -LMagValue;
+    NumLinesText.setText(Integer.toString(x));
+    
+    
+  }//GEN-LAST:event_LMagSliderStateChanged
+  
+  private void DescrSelectorActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_DescrSelectorActionPerformed
+    // Add your handling code here:
+    if (serverUpdated) return;
+    if (groupUpdated) return;
+    areaIndex = -1;
+    Thread tdsap = new Thread() {
+      public void run() {
+        int i = DescrSelector.getSelectedIndex();
+        String[] dl = asi.getDatasetList();
+        if ( (i > 0) && (dl != null) ) {
+          selectedDescr = dl[i-1];  // there was a header in the combo box
+          status("Getting dates and times from server...may take a while!!");
+          asi.setSelectedDataset( selectedDescr );
+          String[] times = asi.getDateTimeList();
+          ad = asi.getAreaDirectories();
+          DateTimeList.setListData(times);
+          DateTimeList.revalidate();
+          status("Now pick a day & time from the list, or set a Position number");
+          String prop = getServer()+"|"+getGroup()+"|"+getDescr();
+          String pp = (String) dataProp.get(prop+"|CoordType");
+          gotUserDefaults = false;
+          if (pp != null) {setCoordType(pp); gotUserDefaults = true;}
+          pp = (String) dataProp.get(prop+"|Location");
+          if (pp != null) {setLocationString(pp); gotUserDefaults = true;}
+          pp = (String) dataProp.get(prop+"|Size");
+          if (pp != null) {setNumLinesEles(pp); gotUserDefaults = true;}
+          pp = (String) dataProp.get(prop+"|Mag");
+          if (pp != null) {setMag(pp); gotUserDefaults = true;}
+        }
+      }
+    };
+    
+    tdsap.start();
+    doingRes = false;
+    setLineMag(1);
+    setEleMag(1);
+    setCoordType("E");
+    setLocationString(null);
+    setNumLinesEles(null);
+    
+    String[] ld = {"Day and times for","selected dataset will","appear here"};
+    DateTimeList.setListData(ld);
+    DateTimeList.revalidate();
+    
+  }//GEN-LAST:event_DescrSelectorActionPerformed
+  
+  private void GroupSelectorActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_GroupSelectorActionPerformed
+    // Add your handling code here:
+    if (serverUpdated) return;
+    areaIndex = -1;
+    Thread tgsap = new Thread() {
+      public void run() {
+        selectedGroup = (String) GroupSelector.getSelectedItem();
+        asi.setSelectedGroup( selectedGroup );
+        bandNames = asi.getBandNames();
+        String[] dl = asi.getDatasetListDescriptions();
+        groupUpdated = true;
+        replaceList(DescrSelector, dl, "Select Dataset");
+        groupUpdated = false;
+        status("Now choose a Dataset from the list");
+        replaceList(UnitBox, null, "        ");
+        replaceList(BandBox, null, "        ");
+      }
+    };
+    tgsap.start();
+    DateTimeList.removeAll();
+    String[] ld = {"Day and times for","selected dataset will","appear here"};
+    DateTimeList.setListData(ld);
+    DateTimeList.revalidate();
+    
+  }//GEN-LAST:event_GroupSelectorActionPerformed
+  
+  private void ServerSelectorActionPerformed (java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ServerSelectorActionPerformed
+    
+    Thread tssap = new Thread() {
+      public void run() {
+        serverUpdated = true;
+        replaceList(GroupSelector, null, "Please wait...");
+        status("Reading information from server...");
+        selectedServer = (String) ServerSelector.getSelectedItem();
+        int sstat = -1;
+        while (sstat == -1) {
+          // first see if a user name & proj number have been set...
+          String un = getUserName();
+          if (un == null || un.length() < 2) {
+            un = (String) dataProp.get(selectedServer+"|UserName");
+            String pn = (String) dataProp.get(selectedServer+"|ProjectNumber");
+            if (un != null) {
+              setUserName(un);
+              setProjectNumber(pn);
+              String ups = "user="+un+"&proj="+pn;
+              asi.setUserIDandProjString(ups);
+            }
+          } else {
+            String ups = "user="+un+"&proj="+getProjectNumber();
+            asi.setUserIDandProjString(ups);
+          }
+          // now see if the Server can be found and read from...
+          sstat = asi.setSelectedServer(selectedServer, "image");
+          if (sstat == -1) {
+            String pus = JOptionPane.showInputDialog(
+            "User ID and project number required by this server!\nPlease enter them here (eg., jack 1234)");
+            if (pus != null) {
+              StringTokenizer stp = new StringTokenizer(pus," ");
+              if (stp.countTokens() != 2) continue;
+              setUserName(stp.nextToken());
+              setProjectNumber(stp.nextToken());
+            } else {
+              sstat = -5;
+            }
+          }
+        }
+        
+        if (sstat >= 0) {
+          if (ServerSelector.getSelectedIndex() == -1) {
+            // append server name onto list
+            ServerSelector.addItem(selectedServer);
+            // and onto properties list
+            serverList.append(","+selectedServer);
+          }
+          String[] gl = asi.getGroupList();
+          String prompt;
+          if (gl == null) {
+            GroupSelector.setEditable(true);
+            prompt = "Enter the group name";
+          } else {
+            GroupSelector.setEditable(false);
+            prompt = "Select group";
+          }
+          replaceList(GroupSelector, gl, prompt);
+          serverUpdated = false;
+          status("Now - "+prompt+" to use...");
+        } else {
+          status("Pick a different server!");
+          serverUpdated = false;
+        }
+      }
+    };
+    tssap.start();
+    areaIndex = -1;
+    DescrSelector.removeAllItems();
+    DateTimeList.removeAll();
+    
+    String[] ld = {"Day and times for","selected dataset will","appear here"};
+    DateTimeList.setListData(ld);
+    DateTimeList.revalidate();
+    DescrSelector.revalidate();
+    // Add your handling code here:
+  }//GEN-LAST:event_ServerSelectorActionPerformed
+  
+  private synchronized void replaceList(JComboBox b, String[] s, String first) {
+    if (b.getItemCount() > 0) b.removeAllItems();
+    if (first != null) b.addItem(first);
+    if (s != null) {
+      for (int i=0; i < s.length; i++) {
+        b.addItem(s[i].trim());
+      }
+    }
+    b.setSelectedIndex(0);
+    b.revalidate();
+  }
+    
+    /**
+     * @param args the command line arguments
+     */
+    public static void main (String args[]) {
+      GetAreaGUI gag = new GetAreaGUI ("avoid pain", false, false);
+      gag.setUserName("tomw");
+      gag.setProjectNumber("1234");
+      gag.show();
+      
+    }
+    
+    
+    // Variables declaration - do not modify//GEN-BEGIN:variables
+    private javax.swing.JPanel PanelSGD;
+    private javax.swing.JPanel PanelSG;
+    private javax.swing.JPanel PanelServer;
+    private javax.swing.JLabel ServerLabel;
+    private javax.swing.JComboBox ServerSelector;
+    private javax.swing.JPanel PanelGroup;
+    private javax.swing.JLabel jLabel2;
+    private javax.swing.JComboBox GroupSelector;
+    private javax.swing.JPanel PanelDescr;
+    private javax.swing.JLabel jLabel3;
+    private javax.swing.JComboBox DescrSelector;
+    private javax.swing.JPanel PanelListMag;
+    private javax.swing.JPanel PanelMag;
+    private javax.swing.JPanel jPanel13;
+    private javax.swing.JPanel jPanel9;
+    private javax.swing.JSlider LMagSlider;
+    private javax.swing.JLabel LMagLabel;
+    private javax.swing.JPanel jPanel10;
+    private javax.swing.JLabel EMagLabel;
+    private javax.swing.JSlider EMagSlider;
+    private javax.swing.JLabel DateTimeLabel;
+    private javax.swing.JPanel PanelList;
+    private javax.swing.JScrollPane DateTimeScrollPanel;
+    private javax.swing.JList DateTimeList;
+    private javax.swing.JPanel LinesElesPanel;
+    private javax.swing.JLabel SizeLabel;
+    private javax.swing.JPanel jPanel7;
+    private javax.swing.JLabel NumLinesLabel;
+    private javax.swing.JTextField NumLinesText;
+    private javax.swing.JLabel NumElesLabel;
+    private javax.swing.JTextField NumElesText;
+    private javax.swing.JCheckBox userDefaultsCheckBox;
+    private javax.swing.JPanel PanelBandUnit;
+    private javax.swing.JPanel BandPanel;
+    private javax.swing.JLabel BandLabel;
+    private javax.swing.JComboBox BandBox;
+    private javax.swing.JPanel UnitsPanel;
+    private javax.swing.JLabel UnitLabel;
+    private javax.swing.JComboBox UnitBox;
+    private javax.swing.JPanel UserActionPanel;
+    private javax.swing.JButton userActionButton;
+    private javax.swing.JPanel PanelStatus;
+    private javax.swing.JTextField statusLabel;
+    private javax.swing.JPanel PanelLoc;
+    private javax.swing.JPanel jPanel11;
+    private javax.swing.JLabel PlaceLabel;
+    private javax.swing.JRadioButton LatLonButton;
+    private javax.swing.JRadioButton LinEleButton;
+    private javax.swing.JRadioButton IDButton;
+    private javax.swing.JPanel jPanel12;
+    private javax.swing.JLabel LatLineLabel;
+    private javax.swing.JLabel LonEleLabel;
+    private javax.swing.JTextField LatLineText;
+    private javax.swing.JTextField LonEleText;
+    // End of variables declaration//GEN-END:variables
+    private javax.swing.ButtonGroup buttGroupLoc;
+    private int LMagValue, EMagValue;
+    private String calText = null;
+}
diff --git a/edu/wisc/ssec/mcidas/adde/Handler.java b/edu/wisc/ssec/mcidas/adde/Handler.java
new file mode 100644
index 0000000..8af9ebc
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/Handler.java
@@ -0,0 +1,43 @@
+//
+// Handler.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+/**
+ * This class defines the openConnection method, which is
+ * used to create an AddeURLConnection.  Note that this 
+ * class is automatically loaded when a URL of this type
+ * is created, you don't have explicitly create an object.<P> 
+ * 
+ * The class name allows this to work if the system property
+ * java.protocol.handler.pkgs includes the edu.wisc.ssec.mcidas
+ * package.
+ * @see java.net.URL  for more information
+ *
+ * @author Don Murray, UCAR/Unidata
+ */
+
+public class Handler extends AddeURLStreamHandler {}
diff --git a/edu/wisc/ssec/mcidas/adde/ReadTextFile.java b/edu/wisc/ssec/mcidas/adde/ReadTextFile.java
new file mode 100644
index 0000000..1a7ead7
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/ReadTextFile.java
@@ -0,0 +1,201 @@
+//
+// ReadTextFile.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.lang.*;
+import java.util.*;
+
+/** 
+ * ReadTextFile interface for McIDAS ADDE data sets.  
+ *
+ * <pre>
+ * URLs must all have the following format   
+ *   adde://host/text?file=filename.ext
+ *
+ * there can be any valid combination of the following supported keywords:
+ *
+ *   file - name of text file on ADDE server
+ *
+ * the following keywords are required:
+ *
+ *   file
+ *
+ * an example URL might look like:
+ *   adde://viper/text?file=filename.ext
+ * </pre>
+ *
+ * @author Tom Whittaker (derived from DataSetInfo by Don Murray)
+ * 
+ */
+public class ReadTextFile 
+{
+
+    // load protocol for ADDE URLs
+    // See java.net.URL for explanation of URL handling
+    static 
+    {
+        try 
+        {
+            String handlers = System.getProperty("java.protocol.handler.pkgs");
+            String newProperty = null;
+            if (handlers == null)
+                newProperty = "edu.wisc.ssec.mcidas";
+            else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
+                newProperty = "edu.wisc.ssec.mcidas | " + handlers;
+            if (newProperty != null)  // was set above
+                System.setProperty("java.protocol.handler.pkgs", newProperty);
+        }
+        catch (Exception e)
+        {
+            System.out.println(
+                "Unable to set System Property: java.protocol.handler.pkgs"); 
+        }
+    }
+
+    private int status=0;                 // read status
+    private String statusString = null;
+    private boolean debug = false;        // debug
+    private Vector linesOfText = null;
+    private URLConnection urlc;           
+    private DataInputStream dis;
+    
+    /**
+     * creates a ReadTextFile object that allows reading an ADDE
+     * text file or TEXT dataset
+     *
+     * @param request ADDE URL to read from.  See class javadoc.
+     *
+     * <pre>
+     * an example URL might look like:
+     *   adde://viper/datasetinfo?group=gvar&descr=FILE=RESOLV.SRV
+     * </pre>
+     *
+     * @exception AddeURLException if there are no datasets of the particular
+     *            type or there is an error reading data
+     *
+     */
+    public ReadTextFile(String request) {
+   
+      status = 0;
+      statusString = "OK";
+      try {
+          URL url = new URL(request);
+          if (debug) System.out.println("Request: "+request);
+          urlc = url.openConnection();
+          InputStream is = urlc.getInputStream();
+          dis = new DataInputStream(is);
+      }
+      catch (AddeURLException ae) 
+      {
+          status = -1;
+          statusString = "No file found";
+          String aes = ae.toString();
+          if (aes.indexOf(" Accounting ") != -1) {
+            statusString = "No accounting data";
+            status = -3;
+          }
+          if (debug) System.out.println("ReadText AF Exception:"+aes);
+      }
+      catch (Exception e) 
+      {
+          status = -2;
+          if (debug) System.out.println("ReadText Exception:"+e);
+          statusString = "Error opening connection: "+e;
+      }
+
+     int numBytes;
+     linesOfText = new Vector();
+
+     if (status == 0) {
+
+       try {
+         numBytes = ((AddeURLConnection) urlc).getInitialRecordSize();
+         if (debug) 
+          System.out.println("ReadTextFile: initial numBytes = " + numBytes);
+
+         numBytes = dis.readInt();
+          while ((numBytes = dis.readInt()) != 0) {
+
+            if (debug) 
+               System.out.println("ReadTextFile: numBytes = " + numBytes);
+
+              byte[] data = new byte[numBytes];
+              dis.readFully(data,0,numBytes);
+              String s = new String(data);
+              if (debug) System.out.println(s);
+              linesOfText.addElement(s);
+         } 
+
+       } catch (Exception iox) {
+         statusString = " "+iox;
+         System.out.println(" "+iox);
+       }
+       
+       if (linesOfText.size() < 1) statusString = "No data read";
+       status = linesOfText.size();
+     }
+    }
+
+    public String getStatus() {
+      return statusString;
+    }
+    public int getStatusCode() {
+      return status;
+    }
+
+    public Vector getText() {
+      return linesOfText;
+    }
+ 
+
+
+
+    /** test by running 'java edu.wisc.ssec.mcidas.adde.ReadTextFile' */
+    public static void main (String[] args)
+        throws Exception
+    {
+        String request = (args.length == 0)
+          ? "adde://adde.ucar.edu/text?file=PUBLIC.SRV"
+          : args[0];
+
+        ReadTextFile rtf = new ReadTextFile(request);
+        String status = rtf.getStatus();
+        System.out.println("Status = "+status);
+        if (status.equals("OK")) {
+          Vector l = rtf.getText();
+          for (int i=0; i<l.size(); i++) {
+            System.out.println( (String)l.elementAt(i));
+          }
+        }
+    }
+}
diff --git a/edu/wisc/ssec/mcidas/adde/WxTextProduct.java b/edu/wisc/ssec/mcidas/adde/WxTextProduct.java
new file mode 100644
index 0000000..5a470eb
--- /dev/null
+++ b/edu/wisc/ssec/mcidas/adde/WxTextProduct.java
@@ -0,0 +1,222 @@
+//
+// WxTextProduct.java
+//
+
+/*
+This source file is part of the edu.wisc.ssec.mcidas package and is
+Copyright (C) 1998 - 2011 by Tom Whittaker, Tommy Jasmin, Tom Rink,
+Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
+and others.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package edu.wisc.ssec.mcidas.adde;
+
+
+import edu.wisc.ssec.mcidas.McIDASUtil;
+
+import java.util.Date;
+
+
+/**
+ * Class to hold a weather text product returned from an ADDE server
+ */
+public class WxTextProduct {
+
+  /** soure of the data */
+  private String source = "";
+
+  /** number of bytes */
+  private int numBytes = 0;
+
+  /** location in the file */
+  private int location = 0;
+
+  /** day of report */
+  private int day = 0;
+
+  /** time of report */
+  private int time = 0;
+
+  /** WMO id*/
+  private String wmo = "";
+
+  /** WMO station */
+  private String wstn = "";
+
+  /** AFOS product id */
+  private String apro = "";
+
+  /** AFOS station */
+  private String astn = "";
+
+  /** product text */
+  private String text = "";
+
+  /** date of bulleting */
+  private Date date = new Date();
+
+  /**
+   * Create a new WxTextProduct from the header info.
+   *
+   * @param header   the ADDE header
+   */
+  public WxTextProduct(byte[] header) {
+    int[] values = McIDASUtil.bytesToIntegerArray(header, 0, 13);
+    source = McIDASUtil.intBitsToString(values[0]);
+    numBytes = values[1];
+    location = values[2];
+    day = values[10];
+    time = values[3];
+    String wmoBase = McIDASUtil.intBitsToString(values[4]);
+    int wmoNum = values[5];
+    wmo = wmoBase + ((wmoNum < 10)
+                       ? "0"
+                       : "") + wmoNum;
+    wstn = McIDASUtil.intBitsToString(values[6]);
+    apro = McIDASUtil.intBitsToString(values[7]);
+    astn = McIDASUtil.intBitsToString(values[8]);
+    date = new Date(McIDASUtil.mcDayTimeToSecs(day, time) * 1000);
+  }
+
+  /**
+   *  Adde text to the existing text
+   *
+   * @param newText 
+   */
+  public void addText(String newText) {
+    text = text + newText;
+  }
+
+  /**
+   * Set the text of the bulleting
+   *
+   * @param newText  the text
+   */
+  public void setText(String newText) {
+    text = newText;
+  }
+
+  /**
+   * Get the product text
+   *
+   * @return the product text
+   */
+  public String getText() {
+    return text;
+  }
+
+  /**
+   * Get the source of the product
+   *
+   * @return the source of the product
+   */
+  public String getSource() {
+    return source;
+  }
+
+  /**
+   * Get the day of the product
+   *
+   * @return the day of the product
+   */
+  public int getDay() {
+    return day;
+  }
+
+  /**
+   * Get the time of the product
+   *
+   * @return the time of the product
+   */
+  public int getTime() {
+    return time;
+  }
+
+  /**
+   * Get the wmo id of the product
+   *
+   * @return the wmo id of the product
+   */
+  public String getWmo() {
+    return wmo;
+  }
+
+  /**
+   * Get the wmo station
+   *
+   * @return the wmo station
+   */
+  public String getWstn() {
+    return wstn;
+  }
+
+  /**
+   * Get the AFOS product id
+   *
+   * @return the AFOS product id
+   */
+  public String getApro() {
+    return apro;
+  }
+
+  /**
+   * Get the AFOS station
+   *
+   * @return the AFOS station
+   */
+  public String getAstn() {
+    return astn;
+  }
+
+  /**
+   * Get the date of the product
+   *
+   * @return the date of the product
+   */
+  public Date getDate() {
+    return date;
+  }
+
+  /**
+   * Get the string representation of this product
+   */
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("SOU    nb  location   day    time  WMO     WSTN APRO ASTN\n");
+    buf.append("---  ----  -------- ------- ------ ------- ---- ---- ----\n"); 
+    buf.append(source);
+    buf.append(numBytes);
+    buf.append(" ");
+    buf.append(location);
+    buf.append(" ");
+    buf.append(day);
+    buf.append(" ");
+    buf.append(time);
+    buf.append(" ");
+    buf.append(wmo);
+    buf.append("  ");
+    buf.append(wstn);
+    buf.append(" ");
+    buf.append(apro);
+    buf.append(" ");
+    buf.append(astn);
+    return buf.toString();
+  }
+
+}
+
diff --git a/examples/AspectRatio.java b/examples/AspectRatio.java
new file mode 100644
index 0000000..f586f6f
--- /dev/null
+++ b/examples/AspectRatio.java
@@ -0,0 +1,107 @@
+//
+// AspectRatio.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.*;
+import java.awt.event.*;
+import java.rmi.RemoteException;
+import javax.swing.JFrame;
+import visad.*;
+import visad.java2d.DisplayImplJ2D;
+
+/** Demonstrates how to link a display's aspect ratio
+    to the size of the display frame. */
+public class AspectRatio extends JFrame {
+
+  /** tests the AspectRatio application */
+  public static void main(String[] argv)
+    throws VisADException, RemoteException
+  {
+    AspectRatio ar = new AspectRatio();
+
+    // close program when frame dies
+    ar.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) { System.exit(0); }
+    });
+
+    // display the application onscreen
+    ar.pack();
+    ar.setVisible(true);
+  }
+
+  /** the projection control of the display */
+  private ProjectionControl pc;
+
+  /** constructs a new AspectRatio application */
+  public AspectRatio() throws VisADException, RemoteException {
+    // construct data type
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    // construct data set
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    // construct display
+    DisplayImplJ2D disp = new DisplayImplJ2D("disp");
+
+    // add scalar maps
+    disp.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    disp.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    disp.addMap(new ScalarMap(vis_radiance, Display.RGB));
+
+    // link data to display with a data reference
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    disp.addReference(ref_imaget1, null);
+
+    // grab the display's projection control
+    pc = disp.getProjectionControl();
+
+    // set up the GUI
+    getContentPane().add(disp.getComponent());
+    setTitle("Aspect ratio linked to frame size");
+  }
+
+  /** called when the frame gets resized */
+  public void doLayout() {
+    super.doLayout();
+
+    // adjust the aspect ratio to match the new size
+    Dimension size = getSize();
+    try {
+      pc.setAspect(new double[] {1.0, (double) size.height / size.width});
+    }
+    catch (VisADException exc) { }
+    catch (RemoteException exc) { }
+  }
+
+}
+
diff --git a/examples/CollabMapTest.java b/examples/CollabMapTest.java
new file mode 100644
index 0000000..ad9475e
--- /dev/null
+++ b/examples/CollabMapTest.java
@@ -0,0 +1,233 @@
+//
+// CollabMapTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.event.*;
+import java.net.MalformedURLException;
+import java.rmi.*;
+import java.util.Vector;
+import javax.swing.*;
+import visad.*;
+import visad.data.*;
+import visad.java3d.DisplayImplJ3D;
+import visad.ss.MappingDialog;
+
+/** A simple test for collaborative ScalarMap editing */
+public class CollabMapTest extends JFrame implements ActionListener {
+
+  /** true if server, false if client */
+  private boolean server;
+
+  /** data reference pointing to the data */
+  private DataReference ref;
+
+  /** display that shows the data */
+  private DisplayImpl disp;
+
+  /** builds the GUI */
+  private void constructGUI(String arg, boolean enableButtons) {
+    JPanel pane = new JPanel();
+    setContentPane(pane);
+    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+    pane.add(disp.getComponent());
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
+    JButton detect = new JButton("Detect maps");
+    JButton edit = new JButton("Edit maps");
+    JButton clear = new JButton("Clear maps");
+    detect.addActionListener(this);
+    detect.setActionCommand("detect");
+    detect.setEnabled(enableButtons);
+    edit.addActionListener(this);
+    edit.setActionCommand("edit");
+    edit.setEnabled(enableButtons);
+    clear.addActionListener(this);
+    clear.setActionCommand("clear");
+    clear.setEnabled(enableButtons);
+    buttons.add(detect);
+    buttons.add(edit);
+    buttons.add(clear);
+    pane.add(buttons);
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) { System.exit(0); }
+    });
+    setTitle("CollabMapTest " + (server ? "server" : "client") + ": " + arg);
+    pack();
+    setVisible(true);
+  }
+
+  /** usage message */
+  private static final String usage =
+    "Usage: java CollabMapTest [-s filename] [-c address]";
+
+  /** runs the test */
+  public static void main(String[] argv)
+    throws VisADException, RemoteException
+  {
+    if (argv.length < 2) {
+      System.err.println("Not enough arguments.");
+      System.err.println(usage);
+      System.exit(1);
+    }
+    String sc = argv[0];
+    String arg = argv[1];
+    boolean serv = false;
+    if (sc.equalsIgnoreCase("-s")) serv = true;
+    else if (!sc.equalsIgnoreCase("-c")) {
+      System.err.println("Please specify either -s or -c");
+      System.err.println(usage);
+      System.exit(2);
+    }
+    CollabMapTest test = new CollabMapTest(serv, arg);
+  }
+
+  /** constructs a new CollabMapTest display */
+  public CollabMapTest(boolean serv, String arg)
+    throws VisADException, RemoteException
+  {
+    server = serv;
+
+    if (server) {
+      // load data
+      DefaultFamily loader = new DefaultFamily("loader");
+      Data data = null;
+      try {
+        data = loader.open(arg);
+      }
+      catch (BadFormException exc) {
+        System.err.println("The specified data file could not be loaded. " +
+          "The file is missing, corrupt, or of the wrong type.");
+        exc.printStackTrace();
+        throw new VisADException(exc.getMessage());
+      }
+
+      // set up data reference
+      ref = new DataReferenceImpl("ref");
+      ref.setData(data);
+
+      // construct 3-D display
+      disp = new DisplayImplJ3D("disp");
+      disp.addReference(ref);
+
+      // set up server
+      RemoteServerImpl rs = new RemoteServerImpl();
+      try {
+        Naming.rebind("///CollabMapTest", rs);
+      }
+      catch (ConnectException exc) {
+        System.err.println("Please run rmiregistry first.");
+        throw new VisADException(exc.getMessage());
+      }
+      catch (MalformedURLException exc) {
+        exc.printStackTrace();
+        throw new VisADException(exc.getMessage());
+      }
+      RemoteDisplayImpl remote = new RemoteDisplayImpl(disp);
+      rs.addDisplay(remote);
+
+      // construct GUI
+      constructGUI(arg, true);
+    }
+    else {
+      // set up server
+      RemoteServer rs;
+      try {
+        rs = (RemoteServer) Naming.lookup("//" + arg + "/CollabMapTest");
+      }
+      catch (NotBoundException exc) {
+        System.err.println("The specified address is not " +
+          "running a CollabMapTest server!");
+        throw new VisADException(exc.getMessage());
+      }
+      catch (MalformedURLException exc) {
+        System.err.println("The specified address is not valid!");
+        throw new VisADException(exc.getMessage());
+      }
+      RemoteDisplay remote = rs.getDisplay(0);
+
+      // construct 3-D display
+      disp = new DisplayImplJ3D(remote);
+
+      // grab data reference from cloned display
+      Vector links = disp.getLinks();
+      ReferenceActionLink link = (ReferenceActionLink) links.elementAt(0);
+      DataReference ref = (DataReference) link.getThingReference();
+
+      // construct GUI
+      constructGUI(arg, false);
+    }
+  }
+
+  /** sets the display to use the given mappings */
+  private void setMaps(ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    disp.removeReference(ref);
+    disp.clearMaps();
+    for (int i=0; i<maps.length; i++) disp.addMap(maps[i]);
+    disp.addReference(ref);
+  }
+
+  /** handles button clicks */
+  public synchronized void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("detect")) {
+      // detect maps
+      try {
+        setMaps(ref.getData().getType().guessMaps(true));
+      }
+      catch (VisADException exc) { exc.printStackTrace(); }
+      catch (RemoteException exc) { exc.printStackTrace(); }
+    }
+    else if (cmd.equals("edit")) {
+      // edit maps
+      try {
+        Vector mapVector = disp.getMapVector();
+        int len = mapVector.size();
+        ScalarMap[] maps = (len > 0 ? new ScalarMap[len] : null);
+        for (int i=0; i<len; i++) maps[i] = (ScalarMap) mapVector.elementAt(i);
+        MappingDialog dialog =
+          new MappingDialog(this, ref.getData(), maps, true, true);
+        dialog.display();
+        if (!dialog.okPressed()) return;
+        setMaps(dialog.getMaps());
+      }
+      catch (VisADException exc) { exc.printStackTrace(); }
+      catch (RemoteException exc) { exc.printStackTrace(); }
+    }
+    else if (cmd.equals("clear")) {
+      // clear maps
+      try {
+        disp.removeReference(ref);
+        disp.clearMaps();
+        disp.addReference(ref);
+      }
+      catch (VisADException exc) { exc.printStackTrace(); }
+      catch (RemoteException exc) { exc.printStackTrace(); }
+    }
+  }
+
+}
+
diff --git a/examples/DelaunayTest.java b/examples/DelaunayTest.java
new file mode 100644
index 0000000..0631faa
--- /dev/null
+++ b/examples/DelaunayTest.java
@@ -0,0 +1,380 @@
+//
+// DelaunayTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+   DelaunayTest provides a graphical demonstration of implemented
+   Delaunay triangulation algorithms, in 2-D or 3-D.
+*/
+public class DelaunayTest {
+
+  public static final int CLARKSON = 1;
+  public static final int WATSON = 2;
+  public static final int FAST = 3;
+
+  public static final int NONE = 1;
+  public static final int BOXES = 2;
+  public static final int TRIANGLES = 3;
+  public static final int VERTICES = 4;
+
+  /** Run 'java DelaunayTest' for usage instructions */
+  public static void main(String[] argv) throws VisADException,
+                                                RemoteException {
+    boolean problem = false;
+    int numpass = 0;
+    int dim = 0;
+    int points = 0;
+    int type = 0;
+    int l = 1;
+    boolean test = false;
+    if (argv.length < 3) problem = true;
+    else {
+      try {
+        dim = Integer.parseInt(argv[0]);
+        points = Integer.parseInt(argv[1]);
+        type = Integer.parseInt(argv[2]);
+        if (argv.length > 3) l = Integer.parseInt(argv[3]);
+        test = argv.length > 4;
+        if (dim < 2 || dim > 3 || points < 1 || type < 1 || l < 1 || l > 4) {
+          problem = true;
+        }
+        if (dim == 3 && type > 2) {
+          System.out.println("Only Clarkson and Watson support " +
+                             "3-D triangulation.\n");
+          System.exit(2);
+        }
+      }
+      catch (NumberFormatException exc) {
+        problem = true;
+      }
+    }
+    if (problem) {
+      System.out.println("Usage:\n" +
+        "   java DelaunayTest dim points type [label] [test]\n" +
+        "dim    = The dimension of the triangulation\n" +
+        "         2 = 2-D\n" +
+        "         3 = 3-D\n" +
+        "points = The number of points to triangulate.\n" +
+        "type   = The triangulation method to use:\n" +
+        "         1 = Clarkson\n" +
+        "         2 = Watson\n" +
+        "         3 = Fast\n" +
+        "     X + 3 = Fast with X improvement passes\n" +
+        "label  = How to label the diagram:\n" +
+        "         1 = No labels (default)\n" +
+        "         2 = Vertex boxes\n" +
+        "         3 = Triangle numbers\n" +
+        "         4 = Vertex numbers\n" +
+        "test   = Whether to test the triangulation (default: no)\n");
+      System.exit(1);
+    }
+    if (type > 3) {
+      numpass = type - 3;
+      type = 3;
+    }
+
+    float[][] samples = null;
+    if (dim == 2) samples = new float[2][points];
+    else samples = new float[3][points];
+
+    float[] samp0 = samples[0];
+    float[] samp1 = samples[1];
+    float[] samp2 = null;
+    if (dim == 3) samp2 = samples[2];
+
+    for (int i=0; i<points; i++) {
+      samp0[i] = (float) (500 * Math.random());
+      samp1[i] = (float) (500 * Math.random());
+    }
+    if (dim == 3) {
+      for (int i=0; i<points; i++) {
+        samp2[i] = (float) (500 * Math.random());
+      }
+    }
+    visTriang(makeTriang(samples, type, numpass, test), samples, l);
+  }
+
+  /**
+   * Triangulates the given samples according to the specified algorithm.
+   *
+   * @param type One of CLARKSON, WATSON, FAST
+   * @param numpass Number of improvement passes
+   * @param test Whether to test the triangulation for errors
+   */
+  public static Delaunay makeTriang(float[][] samples, int type,
+    int numpass, boolean test) throws VisADException, RemoteException
+  {
+    int dim = samples.length;
+    int points = samples[0].length;
+    System.out.print("Triangulating " + points + " points " +
+                     "in " + dim + "-D with ");
+
+    long start = 0;
+    long end = 0;
+    Delaunay delaun = null;
+    if (type == CLARKSON) {
+      System.out.println("the Clarkson algorithm.");
+      start = System.currentTimeMillis();
+      delaun = (Delaunay) new DelaunayClarkson(samples);
+      end = System.currentTimeMillis();
+    }
+    else if (type == WATSON) {
+      System.out.println("the Watson algorithm.");
+      start = System.currentTimeMillis();
+      delaun = (Delaunay) new DelaunayWatson(samples);
+      end = System.currentTimeMillis();
+    }
+    else if (type == FAST) {
+      System.out.println("the Fast algorithm.");
+      start = System.currentTimeMillis();
+      delaun = (Delaunay) new DelaunayFast(samples);
+      end = System.currentTimeMillis();
+    }
+    float time = (end - start) / 1000f;
+    System.out.println("Triangulation took " + time + " seconds.");
+    if (numpass > 0) {
+      System.out.println("Improving samples: " + numpass + " pass" +
+                         (numpass > 1 ? "es..." : "..."));
+      start = System.currentTimeMillis();
+      delaun.improve(samples, numpass);
+      end = System.currentTimeMillis();
+      time = (end - start) / 1000f;
+      System.out.println("Improvement took " + time + " seconds.");
+    }
+    if (test) {
+      System.out.print("Testing triangulation integrity...");
+      if (delaun.test(samples)) System.out.println("OK");
+      else System.out.println("FAILED!");
+    }
+    return delaun;
+  }
+
+  /**
+   * Displays the results for the given Delaunay triangulation of the
+   * specified samples in a window.
+   *
+   * @param delaun The triangulation to visualize
+   * @param samples The samples corresponding to the triangulation
+   * @param label One of NONE, BOXES, TRIANGLES, VERTICES
+   */
+  public static void visTriang(Delaunay delaun, float[][] samples,
+    int labels) throws VisADException, RemoteException
+  {
+    int dim = samples.length;
+    int points = samples[0].length;
+
+    // set up final variables
+    final int label = labels;
+    final int[][] tri = delaun.Tri;
+    final int[][] edges = delaun.Edges;
+    final int numedges = delaun.NumEdges;
+
+    // set up frame
+    JFrame frame = new JFrame();
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+
+    float[] samp0 = samples[0];
+    float[] samp1 = samples[1];
+    float[] samp2 = null;
+    if (dim == 3) samp2 = samples[2];
+
+    if (dim == 2) {
+      // set up GUI components in 2-D
+      final float[] s0 = samp0;
+      final float[] s1 = samp1;
+      JComponent jc = new JComponent() {
+        public void paint(Graphics gr) {
+
+          // draw triangles
+          for (int i=0; i<tri.length; i++) {
+            int[] t = tri[i];
+            gr.drawLine((int) s0[t[0]], (int) s1[t[0]],
+                        (int) s0[t[1]], (int) s1[t[1]]);
+            gr.drawLine((int) s0[t[1]], (int) s1[t[1]],
+                        (int) s0[t[2]], (int) s1[t[2]]);
+            gr.drawLine((int) s0[t[2]], (int) s1[t[2]],
+                        (int) s0[t[0]], (int) s1[t[0]]);
+          }
+
+          // draw labels if specified
+          if (label == 2) {        // vertex boxes
+            for (int i=0; i<s0.length; i++) {
+              gr.drawRect((int) s0[i]-2, (int) s1[i]-2, 4, 4);
+            }
+          }
+          else if (label == 3) {   // triangle numbers
+            for (int i=0; i<tri.length; i++) {
+              int t0 = tri[i][0];
+              int t1 = tri[i][1];
+              int t2 = tri[i][2];
+              int avgX = (int) ((s0[t0] + s0[t1] + s0[t2])/3);
+              int avgY = (int) ((s1[t0] + s1[t1] + s1[t2])/3);
+              gr.drawString(String.valueOf(i), avgX-4, avgY);
+            }
+          }
+          else if (label == 4) {   // vertex numbers
+            for (int i=0; i<s0.length; i++) {
+              gr.drawString("" + i, (int) s0[i], (int) s1[i]);
+            }
+          }
+        }
+      };
+      frame.getContentPane().add(jc);
+    }
+    else {
+      // set up GUI components in 3-D
+      final float[][] samps = samples;
+      final float[] s0 = samp0;
+      final float[] s1 = samp1;
+      final float[] s2 = samp2;
+
+      // construct a UnionSet of line segments (tetrahedra edges)
+      final RealType x = RealType.getRealType("x");
+      final RealType y = RealType.getRealType("y");
+      final RealType z = RealType.getRealType("z");
+      RealTupleType xyz = new RealTupleType(x, y, z);
+      int[] e0 = {0, 0, 0, 1, 1, 2};
+      int[] e1 = {1, 2, 3, 2, 3, 3};
+      Gridded3DSet[] gsp = new Gridded3DSet[numedges];
+      for (int i=0; i<numedges; i++) gsp[i] = null;
+      for (int i=0; i<edges.length; i++) {
+        int[] trii = tri[i];
+        int[] edgesi = edges[i];
+        for (int j=0; j<6; j++) {
+          if (gsp[edgesi[j]] == null) {
+            float[][] pts = new float[3][2];
+            float[] p0 = pts[0];
+            float[] p1 = pts[1];
+            float[] p2 = pts[2];
+            int tp0 = trii[e0[j]];
+            int tp1 = trii[e1[j]];
+            p0[0] = samp0[tp0];
+            p1[0] = samp1[tp0];
+            p2[0] = samp2[tp0];
+            p0[1] = samp0[tp1];
+            p1[1] = samp1[tp1];
+            p2[1] = samp2[tp1];
+            gsp[edgesi[j]] = new Gridded3DSet(xyz, pts, 2);
+          }
+        }
+      }
+      UnionSet tet = new UnionSet(xyz, gsp);
+      final DataReference tetref = new DataReferenceImpl("tet");
+      tetref.setData(tet);
+
+      // set up Java3D Display
+      DisplayImpl display = new DisplayImplJ3D("image display");
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.YAxis));
+      display.addMap(new ScalarMap(z, Display.ZAxis));
+      display.addMap(new ConstantMap(1, Display.Red));
+      display.addMap(new ConstantMap(1, Display.Green));
+      display.addMap(new ConstantMap(0, Display.Blue));
+
+      // draw labels if specified
+      if (label == 2) {
+        throw new UnimplementedException(
+          "DelaunayTest.testTriang: vertex boxes");
+      }
+      else if (label == 3) {   // triangle numbers
+        int len = tri.length;
+        TextType text = new TextType("text");
+        RealType t = RealType.getRealType("t");
+        RealTupleType rtt = new RealTupleType(new RealType[] {t});
+        Linear1DSet timeSet = new Linear1DSet(rtt, 0, len - 1, len);
+        TupleType textTuple = new TupleType(new MathType[] {x, y, z, text});
+        FunctionType textFunction = new FunctionType(t, textTuple);
+        FieldImpl textField = new FieldImpl(textFunction, timeSet);
+        for (int i=0; i<len; i++) {
+          int t0 = tri[i][0];
+          int t1 = tri[i][1];
+          int t2 = tri[i][2];
+          int t3 = tri[i][3];
+          int avgX = (int) ((s0[t0] + s0[t1] + s0[t2] + s0[t3])/4);
+          int avgY = (int) ((s1[t0] + s1[t1] + s1[t2] + s1[t3])/4);
+          int avgZ = (int) ((s2[t0] + s2[t1] + s2[t2] + s2[t3])/4);
+          Data[] td = {new Real(x, avgX),
+                       new Real(y, avgY),
+                       new Real(z, avgZ),
+                       new Text(text, "" + i)};
+          TupleIface tt = new Tuple(textTuple, td);
+          textField.setSample(i, tt);
+        }
+        display.addMap(new ScalarMap(text, Display.Text));
+        DataReferenceImpl rtf = new DataReferenceImpl("rtf");
+        rtf.setData(textField);
+        display.addReference(rtf, null);
+      }
+      else if (label == 4) {   // vertex numbers
+        int len = s0.length;
+        TextType text = new TextType("text");
+        RealType t = RealType.getRealType("t");
+        RealTupleType rtt = new RealTupleType(new RealType[] {t});
+        Linear1DSet timeSet = new Linear1DSet(rtt, 0, len - 1, len);
+        TupleType textTuple = new TupleType(new MathType[] {x, y, z, text});
+        FunctionType textFunction = new FunctionType(t, textTuple);
+        FieldImpl textField = new FieldImpl(textFunction, timeSet);
+        for (int i=0; i<len; i++) {
+          Data[] td = {new Real(x, s0[i]),
+                       new Real(y, s1[i]),
+                       new Real(z, s2[i]),
+                       new Text(text, "" + i)};
+          TupleIface tt = new Tuple(textTuple, td);
+          textField.setSample(i, tt);
+        }
+        display.addMap(new ScalarMap(text, Display.Text));
+        DataReferenceImpl rtf = new DataReferenceImpl("rtf");
+        rtf.setData(textField);
+        display.addReference(rtf, null);
+      }
+
+      // finish setting up Java3D Display
+      display.getDisplayRenderer().setBoxOn(false);
+      display.addReference(tetref);
+
+      // set up frame's panel
+      JPanel panel = new JPanel();
+      panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+      panel.add(display.getComponent());
+      frame.getContentPane().add(panel);
+    }
+    frame.setSize(new Dimension(510, 530));
+    frame.setTitle("Triangulation results");
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/examples/DisplayEventTest.java b/examples/DisplayEventTest.java
new file mode 100644
index 0000000..de546b1
--- /dev/null
+++ b/examples/DisplayEventTest.java
@@ -0,0 +1,150 @@
+//
+// DisplayEventTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+import java.awt.event.*;
+import javax.swing.*;
+
+import visad.*;
+import visad.java2d.*;
+import visad.java3d.*;
+import visad.util.Util;
+
+public class DisplayEventTest {
+
+  /**
+   * Run 'java visad.DisplayEvent' to test DisplayEvents in Java3D,
+   * or 'java visad.DisplayEvent x' to test them in Java2D.
+   */
+  public static void main(String[] args) throws Exception {
+    boolean j2d = args.length > 0;
+
+    DisplayImpl display = j2d ?
+      (DisplayImpl) new DisplayImplJ2D("display") :
+      (DisplayImpl) new DisplayImplJ3D("display");
+
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    display.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    display.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    if (!j2d) display.addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+
+    display.addMap(new ScalarMap(RealType.Latitude, Display.Red));
+    display.addMap(new ScalarMap(RealType.Longitude, Display.Green));
+    display.addMap(new ScalarMap(vis_radiance, Display.Blue));
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setTextureEnable(false);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    display.addReference(ref_imaget1, null);
+
+    final String[] ids = {
+      "?", "MOUSE_PRESSED", "TRANSFORM_DONE", "FRAME_DONE",
+      "MOUSE_PRESSED_CENTER", "MOUSE_PRESSED_LEFT",  "MOUSE_PRESSED_RIGHT",
+      "MOUSE_RELEASED", "MOUSE_RELEASED_CENTER", "MOUSE_RELEASED_LEFT",
+      "MOUSE_RELEASED_RIGHT", "MAP_ADDED", "MAPS_CLEARED", "REFERENCE_ADDED",
+      "REFERENCE_REMOVED", "DESTROYED", "KEY_PRESSED", "KEY_RELEASED",
+      "MOUSE_DRAGGED", "MOUSE_ENTERED", "MOUSE_EXITED", "MOUSE_MOVED",
+      "WAIT_ON", "WAIT_OFF"
+    };
+
+    // enable extra mouse event handling
+    display.enableEvent(DisplayEvent.MOUSE_DRAGGED);
+    display.enableEvent(DisplayEvent.MOUSE_ENTERED); 
+    display.enableEvent(DisplayEvent.MOUSE_EXITED);
+    display.enableEvent(DisplayEvent.MOUSE_MOVED);
+    display.enableEvent(DisplayEvent.WAIT_ON);
+    display.enableEvent(DisplayEvent.WAIT_OFF);
+
+    // enable extra keyboard event handling
+    if (j2d) {
+      DisplayRendererJ2D dr =
+        (DisplayRendererJ2D) display.getDisplayRenderer();
+      KeyboardBehaviorJ2D kb = new KeyboardBehaviorJ2D(dr);
+      dr.addKeyboardBehavior(kb);
+    }
+    else {
+      DisplayRendererJ3D dr =
+        (DisplayRendererJ3D) display.getDisplayRenderer();
+      KeyboardBehaviorJ3D kb = new KeyboardBehaviorJ3D(dr);
+      dr.addKeyboardBehavior(kb);
+    }
+
+    display.addDisplayListener(new DisplayListener() {
+      public void displayChanged(DisplayEvent e) {
+        int id = e.getId();
+        System.out.print(System.currentTimeMillis() + ": " + ids[id]);
+        InputEvent ie = e.getInputEvent();
+        if (ie == null) System.out.println();
+        else {
+          System.out.print(" [ ");
+          if (ie instanceof MouseEvent) {
+            MouseEvent me = (MouseEvent) ie;
+            int x = me.getX();
+            int y = me.getY();
+            System.out.print("(" + x + ", " + y + ") ");
+          }
+          else if (ie instanceof KeyEvent) {
+            KeyEvent ke = (KeyEvent) ie;
+            char key = ke.getKeyChar();
+            System.out.print("'" + key + "' ");
+          }
+          int mods = ie.getModifiers();
+          if ((mods & InputEvent.CTRL_MASK) != 0) System.out.print("CTRL ");
+          if ((mods & InputEvent.SHIFT_MASK) != 0) System.out.print("SHIFT ");
+          System.out.println("]");
+        }
+      }
+    });
+
+    JFrame frame = new JFrame("VisAD DisplayEvent test");
+    JPanel pane = new JPanel();
+    pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
+    frame.setContentPane(pane);
+    pane.add(display.getComponent());
+
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) { System.exit(0); }
+    });
+    frame.pack();
+    Util.centerWindow(frame);
+    frame.show();
+
+    display.getComponent().requestFocus();
+  }
+
+}
diff --git a/examples/DisplaySwitch.java b/examples/DisplaySwitch.java
new file mode 100644
index 0000000..5010b11
--- /dev/null
+++ b/examples/DisplaySwitch.java
@@ -0,0 +1,650 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+import visad.java3d.DisplayImplJ3D;
+
+import visad.util.AnimationWidget;
+import visad.util.ClientServer;
+import visad.util.ContourWidget;
+import visad.util.GMCWidget;
+import visad.util.LabeledColorWidget;
+import visad.util.ProjWidget;
+import visad.util.RangeWidget;
+import visad.util.SelectRangeWidget;
+
+public class DisplaySwitch
+  extends Thread
+{
+  private DisplayImpl display = null;
+  private DataReferenceImpl[] dpyRefs = null;
+  private Data alphaData0 = null;
+  private Data betaData0 = null;
+  private Data betaData1 = null;
+  private ConstantMap[] betaConstMaps = null;
+
+  private JPanel dpyPanel = null;
+  private JButton switchDim = null;
+  private JButton switchData = null;
+
+  private boolean startServer = false;
+  private String hostName = null;
+
+  private static final int maximumWaitTime = 60;
+
+  private boolean dpy3D = false;
+  private boolean dpyBeta = false;
+
+  public DisplaySwitch(String[] args)
+    throws RemoteException, VisADException
+  {
+    if (!processArgs(args)) {
+      System.err.println("Exiting...");
+      System.exit(1);
+    }
+    startThreads();
+  }
+
+  public boolean processArgs(String[] args)
+  {
+    boolean usage = false;
+
+    String className = getClass().getName();
+    int pt = className.lastIndexOf('.');
+    final int ds = className.lastIndexOf('$');
+    if (ds > pt) {
+      pt = ds;
+    }
+    String progName = className.substring(pt == -1 ? 0 : pt + 1);
+
+    for (int i = 0; args != null && i < args.length; i++) {
+      if (args[i].length() > 0 && args[i].charAt(0) == '-') {
+        char ch = args[i].charAt(1);
+
+        String str, result;
+
+        switch (ch) {
+        case 'c':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName + ": Missing hostname for \"-c\"");
+            usage = true;
+          } else if (startServer) {
+            System.err.println(progName +
+                               ": Cannot specify both '-c' and '-s'!");
+            usage = true;
+          } else {
+            hostName = str;
+          }
+          break;
+        case 's':
+          if (hostName != null) {
+            System.err.println(progName +
+                               ": Cannot specify both '-c' and '-s'!");
+            usage = true;
+          } else {
+            startServer = true;
+          }
+          break;
+        default:
+          System.err.println(progName + ": Unknown option \"-" + ch + "\"");
+          usage = true;
+          break;
+        }
+      } else {
+        System.err.println(progName + ": Unknown keyword \"" + args[i] + "\"");
+        usage = true;
+      }
+    }
+
+    if (usage) {
+      System.err.println("Usage: " + getClass().getName() +
+                         " [-c(lient) hostname] [-s(erver)]");
+    }
+
+    return !usage;
+  }
+
+  boolean isServer() { return (startServer && hostName == null); }
+  boolean isClient() { return (!startServer && hostName != null); }
+  boolean isStandalone() { return (!startServer && hostName == null); }
+
+  String getClientServerTitle()
+  {
+    if (isServer()) {
+      return " server";
+    } else if (isClient()) {
+      return " client";
+    } else if (isStandalone()) {
+      return " standalone";
+    }
+    return " unknown";
+  }
+
+  LocalDisplay setupClientData()
+    throws RemoteException, VisADException
+  {
+    RemoteServer client;
+    try {
+      client = ClientServer.connectToServer(hostName, getClass().getName(),
+                                            true);
+    } catch (VisADException ve) {
+      System.err.println(ve.getMessage());
+      System.exit(1);
+      client = null;
+    }
+
+    LocalDisplay[] dpys = ClientServer.getClientDisplays(client);
+    if (dpys == null) {
+      throw new VisADException("No remote displays found!");
+    }
+    if (dpys.length != 1) {
+      throw new VisADException("Multiple remote displays found!");
+    }
+
+    return dpys[0];
+  }
+
+  public void startThreads()
+    throws RemoteException, VisADException
+  {
+    LocalDisplay local;
+    if (isClient()) {
+      local = setupClientData();
+    } else {
+      DisplayImpl dpy = setupServerDisplay();
+
+      RemoteServerImpl server;
+      if (!startServer) {
+        server = null;
+      } else {
+        server = ClientServer.startServer(getClass().getName());
+
+        // add display to server
+        if (dpy != null) {
+          server.addDisplay(new RemoteDisplayImpl(dpy));
+        }
+      }
+
+      local = dpy;
+      setupServerData(local);
+    }
+
+    setupUI(local);
+  }
+
+//////////////////////////////////////////////////////////////////////////////
+
+  DisplayImpl setupServerDisplay()
+    throws RemoteException, VisADException
+  {
+    display = new DisplayImplJ2D("display");
+    return display;
+  }
+
+  private static ScalarMap findMap(LocalDisplay dpy, RealType displayScalar)
+  {
+    if (displayScalar == null) {
+      return null;
+    }
+
+    Iterator maps;
+
+    try {
+      maps = dpy.getMapVector().iterator();
+    } catch (Exception e) {
+      maps = null;
+    }
+
+    if (maps != null) {
+      while (maps.hasNext()) {
+        ScalarMap smap = (ScalarMap )maps.next();
+        if (displayScalar.equals(smap.getDisplayScalar())) {
+          return smap;
+        }
+      }
+    }
+
+    try {
+      maps = dpy.getConstantMapVector().iterator();
+    } catch (Exception e) {
+      maps = null;
+    }
+
+    if (maps != null) {
+      while (maps.hasNext()) {
+        ConstantMap cmap = (ConstantMap )maps.next();
+        if (displayScalar.equals(cmap.getDisplayScalar())) {
+          return cmap;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  private void buildMaps(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    RealType dom0 = RealType.getRealType("dom0");
+    RealType dom1 = RealType.getRealType("dom1");
+
+    dpy.addMap(new ScalarMap(RealType.Latitude, Display.XAxis));
+    dpy.addMap(new ScalarMap(RealType.Longitude, Display.YAxis));
+
+    dpy.addMap(new ScalarMap(dom1, Display.Green));
+    dpy.addMap(new ConstantMap(0.5, Display.Blue));
+    dpy.addMap(new ConstantMap(0.5, Display.Red));
+    dpy.addMap(new ScalarMap(dom0, Display.IsoContour));
+    dpy.addMap(new ScalarMap(dom1, Display.SelectRange));
+    dpy.addMap(new ScalarMap(dom1, Display.RGBA));
+    dpy.addMap(new ScalarMap(RealType.Time, Display.Animation));
+  }
+
+  private DataImpl buildAlphaRefZero()
+    throws RemoteException, VisADException
+  {
+    RealType dom0 = RealType.getRealType("dom0");
+    RealType dom1 = RealType.getRealType("dom1");
+
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earthLocation = new RealTupleType(types);
+
+    RealType[] viTypes = {dom0, dom1};
+    RealTupleType viTT = new RealTupleType(viTypes);
+    FunctionType viFunc = new FunctionType(earthLocation, viTT);
+
+    RealType[] ivTypes = {dom1, dom0};
+    RealTupleType ivTT = new RealTupleType(ivTypes);
+    FunctionType ivFunc = new FunctionType(earthLocation, ivTT);
+
+    RealType[] time = {RealType.Time};
+    RealTupleType timeType = new RealTupleType(time);
+    FunctionType timeVI = new FunctionType(timeType, viFunc);
+    FunctionType timeIV = new FunctionType(timeType, ivFunc);
+
+    int size = 64;
+    int ntimes = 4;
+
+    // 2 May 99, 15:51:00
+    double start = new DateTime(1999, 122, 57060).getValue();
+    Set timeSet = new Linear1DSet(timeType, start, start + 3000.0, ntimes);
+    double[][] times =
+      {{start, start + 600.0, start + 1200.0,
+        start + 1800.0, start + 2400.0, start + 3000.0}};
+    Set timeGrid = new Gridded1DDoubleSet(timeType, times, times[0].length);
+
+    FieldImpl setSequence = new FieldImpl(timeVI, timeSet);
+    FieldImpl gridSequence = new FieldImpl(timeIV, timeGrid);
+    FlatField flatVI = FlatField.makeField(viFunc, size, false);
+    FlatField flatIV = FlatField.makeField(ivFunc, size, false);
+    Real[] reals = {new Real(dom0, (float) size / 4.0f),
+                    new Real(dom1, (float) size / 8.0f)};
+    RealTuple val = new RealTuple(reals);
+    for (int i=0; i<ntimes; i++) {
+      setSequence.setSample(i, flatVI);
+      flatVI = (FlatField) flatVI.add(val);
+    }
+    for (int i=0; i<times[0].length; i++) {
+      gridSequence.setSample(i, flatIV);
+      flatIV = (FlatField) flatIV.add(val);
+    }
+    FieldImpl[] images = {setSequence, gridSequence};
+    return new Tuple(images);
+  }
+
+  void setupAlphaRefs(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    if (alphaData0 == null) {
+      alphaData0 = buildAlphaRefZero();
+    }
+
+    dpyRefs = new DataReferenceImpl[1];
+
+    dpyRefs[0] = new DataReferenceImpl("dpyRef0");
+    dpyRefs[0].setData(alphaData0);
+    dpy.addReference(dpyRefs[0], null);
+  }
+
+  private DataImpl buildBetaRefZero()
+    throws RemoteException, VisADException
+  {
+    RealType dom0 = RealType.getRealType("dom0");
+    RealType dom1 = RealType.getRealType("dom1");
+
+    int isize = 16;
+
+    RealTupleType revLocation;
+    revLocation = new RealTupleType(RealType.Longitude, RealType.Latitude);
+
+    RealTupleType domTypes = new RealTupleType(dom0, dom1);
+
+    FunctionType func = new FunctionType(revLocation, domTypes);
+
+    FlatField flat = FlatField.makeField(func, isize, false);
+
+    double[][] vals = new double[2][isize * isize];
+    for (int i=0; i<isize; i++) {
+      for (int j=0; j<isize; j++) {
+        vals[0][j + isize * i] = (i + 1) * (j + 1);
+        vals[1][j + isize * i] = ((double )i * 1.2) * ((double )j / 1.2);
+      }
+    }
+
+    return flat;
+  }
+
+  private DataImpl buildBetaRefOne()
+    throws RemoteException, VisADException
+  {
+    RealType dom0 = RealType.getRealType("dom0");
+    RealType dom1 = RealType.getRealType("dom1");
+
+    int isize = 16;
+
+    RealTupleType earthLocation;
+    earthLocation = new RealTupleType(RealType.Latitude, RealType.Longitude);
+
+    RealTupleType domTypes = new RealTupleType(dom0, dom1);
+
+    FunctionType func = new FunctionType(earthLocation, domTypes);
+
+    FlatField flat = FlatField.makeField(func, isize, false);
+
+    double[][] vals = new double[2][isize * isize];
+    for (int i=0; i<isize; i++) {
+      for (int j=0; j<isize; j++) {
+        vals[0][j + isize * i] = (i + 1) * (j + 1);
+        vals[1][j + isize * i] = ((double )i * 1.2) * ((double )j / 1.2);
+      }
+    }
+
+    flat.setSamples(vals, false);
+    return flat;
+  }
+
+  void setupBetaRefs(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    if (betaData0 == null) {
+      betaData0 = buildBetaRefZero();
+    }
+    if (betaData1 == null) {
+      betaData1 = buildBetaRefOne();
+    }
+    if (betaConstMaps == null) {
+      betaConstMaps = new ConstantMap[3];
+      betaConstMaps[0] = new ConstantMap(1.0, Display.Blue);
+      betaConstMaps[1] = new ConstantMap(1.0, Display.Red);
+      betaConstMaps[2] = new ConstantMap(0.0, Display.Green);
+    }
+
+    dpyRefs = new DataReferenceImpl[2];
+
+    dpyRefs[0] = new DataReferenceImpl("dpyRef0");
+    dpyRefs[0].setData(betaData0);
+    dpy.addReference(dpyRefs[0], null);
+
+
+    dpyRefs[1] = new DataReferenceImpl("dpyRef1");
+    dpyRefs[1].setData(betaData1);
+    dpy.addReference(dpyRefs[1], betaConstMaps);
+  }
+
+  void setupServerData(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    buildMaps(dpy);
+
+    setupAlphaRefs(dpy);
+  }
+
+  void switchDisplay(boolean rerollData)
+  {
+    // make sure we know which display we're switching
+    if (display == null) {
+      System.err.println("No display found!");
+      return;
+    }
+
+    // grab some info from the current display
+    String name = display.getName();
+    int api;
+    try {
+      api = display.getAPI();
+    } catch (VisADException ve) {
+      api = 0;
+    }
+
+    // try to create a new display in a different dimension
+    DisplayImpl newDpy;
+    try {
+      if (dpy3D) {
+        newDpy = new DisplayImplJ3D(name, api);
+      } else {
+        newDpy = new DisplayImplJ2D(name, api);
+      }
+    } catch (Exception e) {
+      System.err.println("Couldn't create new display!");
+      return;
+    }
+
+    // save the old maps
+    Vector sMaps = display.getMapVector();
+    Vector cMaps = display.getConstantMapVector();
+
+    // destroy the old display
+    try {
+      dpyPanel.remove(display.getComponent());
+      display.removeAllReferences();
+      display.clearMaps();
+      display = null;
+    } catch (Exception e) {
+      System.err.println("Ignoring " + e.getClass().getName() +
+                         ": " + e.getMessage());
+      // ignore any errors due to clearing out the old display
+    }
+
+    // add maps to new display
+    if (sMaps != null) {
+      int len = sMaps.size();
+      for (int i = 0; i < len; i++) {
+        ScalarMap map = (ScalarMap )sMaps.elementAt(i);
+        try {
+          newDpy.addMap(map);
+        } catch (Exception e) {
+          System.err.println("Couldn't re-add ScalarMap " + map + ": " +
+                             e.getClass().getName() + ": " + e.getMessage());
+e.printStackTrace();
+        }
+      }
+    }
+    if (cMaps != null) {
+      int len = cMaps.size();
+      for (int i = 0; i < len; i++) {
+        ConstantMap map = (ConstantMap )cMaps.elementAt(i);
+        try {
+          newDpy.addMap(map);
+        } catch (Exception e) {
+          System.err.println("Couldn't re-add ConstantMap " + map + ": " +
+                             e.getClass().getName() + ": " + e.getMessage());
+e.printStackTrace();
+        }
+      }
+    }
+    if (dpyRefs == null || rerollData) {
+      if (dpyBeta) {
+        try {
+          setupBetaRefs(newDpy);
+        } catch (Exception e) {
+          System.err.println("Couldn't re-init beta refs: " +
+                             e.getClass().getName() + ": " + e.getMessage());
+e.printStackTrace();
+        }
+      } else {
+        try {
+          setupAlphaRefs(newDpy);
+        } catch (Exception e) {
+          System.err.println("Couldn't re-init alpha refs: " +
+                             e.getClass().getName() + ": " + e.getMessage());
+e.printStackTrace();
+        }
+      }
+    } else {
+      for (int i = 0; i < dpyRefs.length; i++) {
+        try {
+          newDpy.addReference(dpyRefs[i], null);
+        } catch (Exception e) {
+          System.err.println("Couldn't re-add " + dpyRefs[i] + ": " +
+                             e.getClass().getName() + ": " + e.getMessage());
+e.printStackTrace();
+        }
+      }
+    }
+
+    display = newDpy;
+    dpyPanel.add(display.getComponent());
+  }
+
+  private Component buildSwitchButtons()
+  {
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
+
+    switchDim = new JButton("Change to 3D");
+    switchDim.addActionListener(new ActionListener()
+      {
+        public void actionPerformed(ActionEvent e)
+        {
+          dpy3D = !dpy3D;
+          switchDisplay(false);
+          if (dpy3D) {
+            switchDim.setText("Change to 2D");
+          } else {
+            switchDim.setText("Change to 3D");
+          }
+        }
+      });
+    buttons.add(switchDim);
+
+    switchData = new JButton("Change Data");
+    switchData.addActionListener(new ActionListener()
+      {
+        public void actionPerformed(ActionEvent e)
+        {
+          dpyBeta = !dpyBeta;
+          switchDisplay(true);
+        }
+      });
+    buttons.add(switchData);
+
+    return buttons;
+  }
+
+  Component getSpecialComponent(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    Vector v = dpy.getMapVector();
+
+    ScalarMap rgbaMap = findMap(dpy, Display.RGBA);
+
+    JPanel widgets = new JPanel();
+    widgets.setLayout(new BoxLayout(widgets, BoxLayout.Y_AXIS));
+
+    dpyPanel = new JPanel();
+    dpyPanel.add(dpy.getComponent());
+
+    widgets.add(dpyPanel);
+
+    widgets.add(new AnimationWidget(findMap(dpy, Display.Animation), 500));
+    widgets.add(new ContourWidget(findMap(dpy, Display.IsoContour)));
+    widgets.add(new GMCWidget(dpy.getGraphicsModeControl()));
+    widgets.add(new LabeledColorWidget(rgbaMap));
+    widgets.add(new ProjWidget(dpy.getProjectionControl()));
+    widgets.add(new RangeWidget(rgbaMap));
+    widgets.add(new SelectRangeWidget(findMap(dpy, Display.SelectRange)));
+
+    if (isServer() || isStandalone()) {
+      widgets.add(buildSwitchButtons());
+    }
+
+    return widgets;
+  }
+
+  void setupUI(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    Component special = getSpecialComponent(dpy);
+
+    Container content;
+    if (special instanceof Container) {
+      content = (Container )special;
+    } else {
+      JPanel wrapper = new JPanel();
+      wrapper.setLayout(new BorderLayout());
+      wrapper.add("Center", special);
+      content = wrapper;
+    }
+
+    JFrame jframe = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+    jframe.setContentPane(content);
+    jframe.pack();
+    jframe.setVisible(true);
+  }
+
+  String getFrameTitle() { return "2d/3d display switch"; }
+
+  public String toString() { return ": Changing between 2d and 3d display"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new DisplaySwitch(args);
+  }
+}
diff --git a/examples/DisplayTest.java b/examples/DisplayTest.java
new file mode 100644
index 0000000..3da303a
--- /dev/null
+++ b/examples/DisplayTest.java
@@ -0,0 +1,101 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.VisADException;
+
+/**
+    DisplayTest is the general class for testing Displays.<P>
+*/
+public class DisplayTest {
+
+  static int no_self = 0;
+
+  private static TestSkeleton getTestClass(int num)
+  {
+    String caseName;
+    if (num < 10) {
+      caseName = "Test0" + num;
+    } else {
+      caseName = "Test" + num;
+    }
+
+    TestSkeleton skel = null;
+    try {
+      Class caseClass = Class.forName(caseName);
+      try {
+        skel = (TestSkeleton )caseClass.newInstance();
+      } catch (ClassCastException e1) {
+      } catch (InstantiationException e2) {
+      } catch (IllegalAccessException e3) {
+      }
+    } catch (ClassNotFoundException e) {
+    }
+
+    return skel;
+  }
+
+  /** run 'java visad.java3d.DisplayImplJ3D to test list options */
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+
+    int caseNum = -1;
+    TestSkeleton skel = null;
+    if (args.length > 0) {
+      try {
+        caseNum = Integer.parseInt(args[0]);
+      } catch(NumberFormatException e) {
+        System.err.println("Bad DisplayTest \"" + caseNum + "\"");
+        caseNum = -1;
+      }
+
+      if (caseNum != -1) {
+        skel = getTestClass(caseNum);
+      }
+    }
+
+    if (skel != null) {
+      String[] nargs = new String[args.length - 1];
+      for (int i = 1; i < args.length; i++) {
+        nargs[i-1] = args[i];
+      }
+
+      skel.processArgs(nargs);
+      System.out.println(" " + caseNum + skel);
+      skel.startThreads();
+    } else {
+      System.out.println("To test VisAD's displays, run");
+      System.out.println("  java DisplayTest N, where N =");
+
+      for (int n = 0; true; n++) {
+        skel = getTestClass(n);
+        if (skel == null) {
+          break;
+        }
+
+        System.out.println(" " + n + skel);
+      }
+    }
+  }
+}
diff --git a/examples/Earth.java b/examples/Earth.java
new file mode 100644
index 0000000..7d4be02
--- /dev/null
+++ b/examples/Earth.java
@@ -0,0 +1,149 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Frame;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.rmi.RemoteException;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.RealType;
+import visad.RealTupleType;
+import visad.ScalarMap;
+import visad.VisADException;
+import visad.data.netcdf.Plain;
+import visad.util.LabeledColorWidget;
+
+
+/**
+ * Draws a picture of the globe with topography and color.
+ */
+class Earth
+{
+    /*
+     * Set altitude scaling.
+     */
+    private static double[]
+    setAltitudeScaling(ScalarMap radiusMap)
+        throws InterruptedException, VisADException, RemoteException
+    {
+        double[]        coeffs = new double[2];         // scale and offset
+        double[]        altitudeRange = new double[2];  // data min and max
+        double[]        radiusRange = new double[2];    // display min and max
+
+        radiusMap.getScale(coeffs, altitudeRange, radiusRange);
+        while (Double.isNaN(altitudeRange[0]))
+        {
+            Thread.sleep(1000);
+            radiusMap.getScale(coeffs, altitudeRange, radiusRange);
+        }
+
+        double[]        newRadiusRange = {0.925, 1.075};
+        double          newSlope = (newRadiusRange[1] - newRadiusRange[0]) /
+            (altitudeRange[1] - altitudeRange[0]);
+        double          newIntercept = newRadiusRange[0] -
+            newSlope * altitudeRange[0];
+        double          newMinAltitude = (radiusRange[0] - newIntercept) /
+            newSlope;
+        double          newMaxAltitude = (radiusRange[1] - newIntercept) /
+            newSlope;
+
+        radiusMap.setRange(newMinAltitude, newMaxAltitude);
+
+        return altitudeRange;
+    }
+
+
+    /**
+     * Set the color map.
+     */
+    private static void
+    setColorMap(ScalarMap colorMap, double min, double max)
+        throws VisADException, RemoteException
+    {
+/* WLH 11 Dec 98
+        LabeledColorWidget lw =
+            new LabeledColorWidget(colorMap, (float)min, (float)max);
+*/
+        LabeledColorWidget lw =
+            new LabeledColorWidget(colorMap);
+
+        Frame frame = new Frame("VisAD Color Widget");
+        frame.addWindowListener(new WindowAdapter() {
+          public void windowClosing(WindowEvent e) {System.exit(0);}
+        });
+        frame.add(lw);
+        frame.setSize(lw.getPreferredSize());
+        frame.setVisible(true);
+    }
+
+
+    /**
+     * Test this class.
+     */
+    public static void
+    main(String[] args)
+        throws Exception
+    {
+        /* CTR: 28 Sep 1998 */
+        // print a nice error message if user doesn't specify a file
+        if (args.length < 1) {
+          System.out.println("Usage: \"java Earth file.nc\", " +
+                             "where file.nc is a netCDF file.");
+          System.out.println("This program is designed to work with the " +
+                             "lowresTerrain.nc file available at:");
+          System.out.println("  ftp://ftp.ssec.wisc.edu/pub/" +
+                             "visad-2.0/lowresTerrain.nc");
+          System.exit(0);
+        }
+
+        GeoDisplay      display = new GeoDisplay();
+        FlatField       earth = (FlatField) new Plain().open(args[0]);
+        FunctionType    earthType = (FunctionType) earth.getType();
+        RealType        altitudeType = (RealType)earthType.getRange();
+
+        /* WLH 11 Sept 98 - this works */
+        RealTupleType domain = earthType.getDomain();
+        RealType lon = (RealType) domain.getComponent(0);
+        RealType lat = (RealType) domain.getComponent(1);
+        display.addMap(new ScalarMap(lon, Display.Longitude));
+        display.addMap(new ScalarMap(lat, Display.Latitude));
+
+        ScalarMap       radiusMap = new ScalarMap(altitudeType, Display.Radius);
+        ScalarMap       colorMap = new ScalarMap(altitudeType, Display.RGB);
+        DataReference   earthRef = new DataReferenceImpl("earthRef");
+
+        display.addMap(radiusMap);
+        display.addMap(colorMap);
+
+        earthRef.setData(earth);
+
+        display.addReference(earthRef);
+
+        double[]        altitudeRange = setAltitudeScaling(radiusMap);
+        setColorMap(colorMap, altitudeRange[0], altitudeRange[1]);
+
+    }
+}
diff --git a/examples/FlowTest.java b/examples/FlowTest.java
new file mode 100644
index 0000000..9914a6a
--- /dev/null
+++ b/examples/FlowTest.java
@@ -0,0 +1,167 @@
+//
+// FlowTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+import java.rmi.*;
+
+
+public class FlowTest extends Object {
+
+  public FlowTest () {
+    super();
+  }
+
+  static final int N = 6;
+
+  /** run 'java FlowTest middle_latitude'
+          to test with (lat, lon)
+      run 'java FlowTest middle_latitude x'
+          to test with (lon, lat)
+      adjust middle_latitude for south or north */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    double mid_lat = -10.0;
+    if (args.length > 0) {
+      try {
+        mid_lat = Double.valueOf(args[0]).doubleValue();
+      }
+      catch(NumberFormatException e) { }
+    }
+    boolean swap = (args.length > 1);
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType[] types;
+    if (swap) {
+      types = new RealType[] {lon, lat};
+    }
+    else {
+      types = new RealType[] {lat, lon};
+    }
+    RealTupleType earth_location = new RealTupleType(types);
+    System.out.println("earth_location = " + earth_location +
+                       " mid_lat = " + mid_lat);
+
+    RealType flowx = RealType.getRealType("flowx",
+                          CommonUnit.meterPerSecond);
+    RealType flowy = RealType.getRealType("flowy",
+                          CommonUnit.meterPerSecond);
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+    EarthVectorType flowxy = new EarthVectorType(flowx, flowy);
+    TupleType range = null;
+
+    range = new TupleType(new MathType[] {flowxy, red, green});
+    FunctionType flow_field = new FunctionType(earth_location, range);
+
+    DisplayImpl display =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap xmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(xmap);
+    ScalarMap ymap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(ymap);
+    ScalarMap flowx_map = new ScalarMap(flowx, Display.Flow1X);
+    display.addMap(flowx_map);
+    flowx_map.setRange(-10.0, 10.0);
+    ScalarMap flowy_map = new ScalarMap(flowy, Display.Flow1Y);
+    display.addMap(flowy_map);
+    flowy_map.setRange(-10.0, 10.0);
+    FlowControl flow_control = (FlowControl) flowy_map.getControl();
+    flow_control.setFlowScale(0.05f);
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    double lonlow = -10.0;
+    double lonhi = 10.0;
+    double latlow = mid_lat - 10.0;
+    double lathi = mid_lat + 10.0;
+    Linear2DSet set;
+    if (swap) {
+      set = new Linear2DSet(earth_location, lonlow, lonhi, N,
+                                            latlow, lathi, N);
+    }
+    else {
+      set = new Linear2DSet(earth_location, latlow, lathi, N,
+                                            lonlow, lonhi, N);
+    }
+    double[][] values = new double[4][N * N];
+    int m = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        int k = i;
+        int l = j;
+        if (swap) {
+          k = j;
+          l = i;
+        }
+        double u = (N - 1.0) / 2.0 - l;
+        double v = k - (N - 1.0) / 2.0;
+        // double u = 2.0 * k / (N - 1.0) - 1.0;
+        // double v = 2.0 * l / (N - 1.0);
+        double fx = 6.0 * u;
+        double fy = 6.0 * v;
+        values[0][m] = fx;
+        values[1][m] = fy;
+        values[2][m] = u;
+        values[3][m] = v;
+        m++;
+      }
+    }
+    FlatField field = new FlatField(flow_field, set);
+    field.setSamples(values);
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(field);
+    display.addReference(ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test FlowTest");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/examples/GeoDisplay.java b/examples/GeoDisplay.java
new file mode 100644
index 0000000..8921a0e
--- /dev/null
+++ b/examples/GeoDisplay.java
@@ -0,0 +1,76 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+import visad.VisADException;
+import visad.java3d.DisplayImplJ3D;
+
+
+class GeoDisplay
+    extends	DisplayImplJ3D
+{
+    /**
+     * Construct from nothing.
+     */
+    GeoDisplay()
+	throws VisADException, RemoteException
+    {
+	this("GeoDisplay");
+    }
+
+
+    /**
+     * Construct from a name for the display.
+     */
+    GeoDisplay(String name)
+	throws VisADException, RemoteException
+    {
+	super(name, DisplayImplJ3D.APPLETFRAME);
+
+	/*
+	 * Map data dimensions to display dimensions.
+	 */
+/* WLH 11 Sept 98 - doesn't work anymore
+	{
+	    QuantityDB	quantityDB = StandardQuantityDB.instance();
+
+	    addMap(new ScalarMap(quantityDB.get("longitude", SI.radian),
+		Display.Longitude));
+	    addMap(new ScalarMap(quantityDB.get("latitude", SI.radian),
+		Display.Latitude));
+	}
+*/
+    }
+
+
+    /**
+     * Test this class.
+     */
+    public static void main(String[] args)
+	throws Exception
+    {
+	/*
+	 * Create and display a GeoDisplay.
+	 */
+	new GeoDisplay().doAction();
+    }
+}
diff --git a/examples/HSVDisplay.java b/examples/HSVDisplay.java
new file mode 100644
index 0000000..e00c7d7
--- /dev/null
+++ b/examples/HSVDisplay.java
@@ -0,0 +1,356 @@
+//
+// HSVDisplay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import visad.*;
+import visad.java3d.*;
+import visad.util.*;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+
+
+// JFC packages
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+import javax.swing.border.*;
+
+// AWT packages
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+   HSVDisplay is an application for interactively exploring
+   the relation between the HSV and RGB color coordiantes.<P>
+*/
+public class HSVDisplay extends Object implements ActionListener {
+
+  DisplayImplJ3D display1 = null;
+
+  RealType red = null;
+  RealType green = null;
+  RealType blue = null;
+
+  RealType hue = null;
+  RealType saturation = null;
+  RealType value = null;
+
+  ContourCell cell_hue = null;
+  ContourCell cell_saturation = null;
+  ContourCell cell_value = null;
+
+  ContourControl controlhcontour = null;
+  ContourControl controlscontour = null;
+  ContourControl controlvcontour = null;
+
+  int state = 0; // number of ScalarMaps added, 0 through 9
+  ScalarMap[] maps = new ScalarMap[9];
+
+  public static void main(String args[])
+         throws IOException, VisADException, RemoteException {
+
+    HSVDisplay dummy = new HSVDisplay();
+  }
+
+  public HSVDisplay()
+         throws IOException, VisADException, RemoteException {
+
+    // define an rgb color space
+    // (not to be confused with system's RGB DisplayTupleType)
+    red = RealType.getRealType("red");
+    green = RealType.getRealType("green");
+    blue = RealType.getRealType("blue");
+    RealTupleType rgb = new RealTupleType(red, green, blue);
+
+    // define an hsv color space
+    // (not to be confused with system's HSV DisplayTupleType)
+    hue = RealType.getRealType("hue", CommonUnit.degree);
+    saturation = RealType.getRealType("saturation");
+    value = RealType.getRealType("value");
+    // note that we use the same HSVCoordinateSystem that the
+    // system uses to define the relation between its RGB and HSV
+    CoordinateSystem hsv_system = new HSVCoordinateSystem(rgb);
+    RealTupleType hsv = new RealTupleType(hue, saturation, value,
+                                          hsv_system, null);
+
+    // construct a sampling of the hsv color space;
+    // since hue is composed of six linear (in rgb) pieces with
+    // discontinuous derivative bwteen pieces, it should be sampled
+    // at 6*n+1 points with n not too small;
+    // for a given hue, saturation and value are both linear in rgb
+    // so 2 samples suffice for each of them;
+    // the HSV - RGB transform is degenerate at saturation = 0.0
+    // and value = 0.0 so avoid those values;
+    // hue is in Units of degrees so that must be used in the Set
+    // constructor
+    Linear3DSet cube_set =
+      new Linear3DSet(hsv, 0.0, 360.0, 37,
+                           0.01, 1.0, 2,
+                           0.01, 1.0, 2, null,
+                      new Unit[] {CommonUnit.degree, null, null},
+                      null);
+
+    // construct a DataReference to cube_set so it can be displayed
+    DataReference cube_ref = new DataReferenceImpl("cube");
+    cube_ref.setData(cube_set);
+
+    DataReference hue_ref =
+      new DataReferenceImpl("hue");
+    DataReference saturation_ref =
+      new DataReferenceImpl("saturation");
+    DataReference value_ref =
+      new DataReferenceImpl("value");
+    VisADSlider hue_slider =
+      new VisADSlider("hue", 0, 359, 0, 1.0, hue_ref,
+                      RealType.Generic);
+    VisADSlider saturation_slider =
+      new VisADSlider("saturation", 0, 100, 0, 0.01, saturation_ref,
+                      RealType.Generic);
+    VisADSlider value_slider =
+      new VisADSlider("value", 0, 100, 0, 0.01, value_ref,
+                      RealType.Generic);
+
+    // construct a Display
+    display1 = new DisplayImplJ3D("display1");
+
+    // makeMaps();
+    // makeColorMaps();
+    for (int i=0; i<9; i++) addMap();
+
+    display1.getGraphicsModeControl().setScaleEnable(true);
+
+    DisplayRendererJ3D dr = (DisplayRendererJ3D) display1.getDisplayRenderer();
+    KeyboardBehaviorJ3D kbd = new KeyboardBehaviorJ3D(dr);
+    dr.addKeyboardBehavior(kbd);
+
+    // display cube_set
+    display1.addReference(cube_ref);
+
+    JFrame frame = new JFrame("VisAD HSV Color Coordinates");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in frame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    panel.add(hue_slider);
+    panel.add(saturation_slider);
+    panel.add(value_slider);
+
+    panel.add(display1.getComponent());
+
+    JPanel panel2 = new JPanel();
+    panel2.setLayout(new BoxLayout(panel2, BoxLayout.X_AXIS));
+    panel2.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel2.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+
+    JButton clear = new JButton("Clear");
+    clear.addActionListener(this);
+    clear.setActionCommand("clear");
+    panel2.add(clear);
+
+    JButton add = new JButton("Add");
+    add.addActionListener(this);
+    add.setActionCommand("add");
+    panel2.add(add);
+
+    JButton remove = new JButton("Remove");
+    remove.addActionListener(this);
+    remove.setActionCommand("remove");
+    panel2.add(remove);
+
+    panel.add(panel2);
+
+    int WIDTH = 500;
+    int HEIGHT = 700;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+
+    cell_hue =
+      new ContourCell(controlhcontour, hue_ref);
+    cell_hue.addReference(hue_ref);
+    cell_saturation =
+      new ContourCell(controlscontour, saturation_ref);
+    cell_saturation.addReference(saturation_ref);
+    cell_value =
+      new ContourCell(controlvcontour, value_ref);
+    cell_value.addReference(value_ref);
+  }
+
+  /** This method handles button presses */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    try {
+      if (cmd.equals("clear")) {
+        display1.clearMaps();
+        controlhcontour = null;
+        controlscontour = null;
+        controlvcontour = null;
+        setControls();
+        for (int i=0; i<9; i++) maps[i] = null;
+        state = 0;
+      }
+      else if (cmd.equals("add")) {
+        addMap();
+      }
+      else if (cmd.equals("remove")) {
+        removeMap();
+      }
+    }
+    catch (VisADException ex) {
+      System.out.println("call clearMaps ex = " + ex);
+    }
+    catch (RemoteException ex) {
+      System.out.println("call clearMaps ex = " + ex);
+    }
+  }
+
+  private void addMap()
+          throws VisADException, RemoteException {
+    switch (state) {
+      case 0:
+        maps[0] = new ScalarMap(red, Display.XAxis);
+        display1.addMap(maps[0]);
+        break;
+      case 1:
+        maps[1] = new ScalarMap(green, Display.YAxis);
+        display1.addMap(maps[1]);
+        break;
+      case 2:
+        maps[2] = new ScalarMap(blue, Display.ZAxis);
+        display1.addMap(maps[2]);
+        break;
+      case 3:
+        maps[3] = new ScalarMap(hue, Display.IsoContour);
+        display1.addMap(maps[3]);
+        controlhcontour = (ContourControl) maps[3].getControl();
+        if (cell_hue != null) cell_hue.setControl(controlhcontour);
+        break;
+      case 4:
+        maps[4] = new ScalarMap(saturation, Display.IsoContour);
+        display1.addMap(maps[4]);
+        controlscontour = (ContourControl) maps[4].getControl();
+        if (cell_saturation != null) cell_saturation.setControl(controlscontour);
+        break;
+      case 5:
+        maps[5] = new ScalarMap(value, Display.IsoContour);
+        display1.addMap(maps[5]);
+        controlvcontour = (ContourControl) maps[5].getControl();
+        if (cell_value != null) cell_value.setControl(controlvcontour);
+        break;
+      case 6:
+        maps[6] = new ScalarMap(hue, Display.Hue);
+        display1.addMap(maps[6]);
+        break;
+      case 7:
+        maps[7] = new ScalarMap(saturation, Display.Saturation);
+        display1.addMap(maps[7]);
+        break;
+      case 8:
+        maps[8] = new ScalarMap(value, Display.Value);
+        display1.addMap(maps[8]);
+        break;
+      case 9:
+        return;
+    }
+    state++;
+  }
+
+  private void removeMap() 
+          throws VisADException, RemoteException {
+    switch (state) {
+      case 0:
+        return;
+      case 4:
+        controlhcontour = null;
+        if (cell_hue != null) cell_hue.setControl(controlhcontour);
+        break;
+      case 5:
+        controlscontour = null;
+        if (cell_saturation != null) cell_saturation.setControl(controlscontour);
+        break;
+      case 6:
+        controlvcontour = null;
+        if (cell_value != null) cell_value.setControl(controlvcontour);
+        break;
+      default:
+        break;
+    }
+    state--;
+    display1.removeMap(maps[state]);
+    maps[state] = null;
+  }
+
+  private void setControls()
+          throws VisADException, RemoteException {
+    cell_hue.setControl(controlhcontour);
+    cell_saturation.setControl(controlscontour);
+    cell_value.setControl(controlvcontour);
+  }
+
+  class ContourCell extends CellImpl {
+    ContourControl control;
+    DataReference ref;
+    double value;
+
+    ContourCell(ContourControl cc, DataReference r)
+           throws VisADException, RemoteException {
+      control = cc;
+      ref = r;
+      value = ((Real) ref.getData()).getValue();
+    }
+
+    public void setControl(ContourControl cc)
+           throws VisADException, RemoteException {
+      control = cc;
+      value = Double.NaN;
+      doAction();
+    }
+
+    public void doAction() throws VisADException, RemoteException {
+      double val = ((Real) ref.getData()).getValue();
+      ContourControl cc = control;
+      if (val == val && val != value && cc != null) {
+        cc.setSurfaceValue((float) ((Real) ref.getData()).getValue());
+        cc.enableContours(true);
+        value = val;
+      }
+    }
+
+  }
+
+}
+
diff --git a/examples/IDeskMcIDAS.java b/examples/IDeskMcIDAS.java
new file mode 100644
index 0000000..cdbafa3
--- /dev/null
+++ b/examples/IDeskMcIDAS.java
@@ -0,0 +1,157 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import javax.media.j3d.*;
+
+import visad.*;
+import visad.data.mcidas.*;
+import visad.java3d.*;
+import visad.bom.*;
+
+public class IDeskMcIDAS implements DisplayListener {
+
+  // run 'java IDeskMcIDAS AREA2001 OUTLSUPW tracker_key controller_key'
+  public static void main (String[] args)
+         throws VisADException, RemoteException, IOException {
+
+    IDeskMcIDAS idesk = new IDeskMcIDAS(args);
+  }
+
+  public IDeskMcIDAS(String[] args)
+         throws VisADException, RemoteException, IOException {
+    int tracker_shmkey = 4148;
+    int controller_shmkey = 4147;
+    if (args.length >= 3) tracker_shmkey = Integer.parseInt(args[3]);
+    if (args.length >= 4) controller_shmkey = Integer.parseInt(args[4]);
+
+    // set up display
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+
+    GraphicsConfigTemplate3D gct3d = new GraphicsConfigTemplate3D();
+    gct3d.setStereo(GraphicsConfigTemplate3D.REQUIRED);
+
+    GraphicsConfiguration config =
+    gct3d.getBestConfiguration(gd.getConfigurations());
+
+    if (config == null) {
+      System.err.println("Unable to find a Stereo visual");
+      System.exit(1);
+    }
+
+    ImmersaDeskDisplayRendererJ3D display_renderer =
+      new ImmersaDeskDisplayRendererJ3D(tracker_shmkey, controller_shmkey);
+    DisplayImplJ3D display =
+      new DisplayImplJ3D("display1", display_renderer, config);
+
+    // read McIDAS AREA file
+    AreaAdapter areaAdapter = new AreaAdapter(args[0]);
+    Data image = areaAdapter.getData();
+
+    // get type of image radiance
+    FunctionType imageFunctionType = (FunctionType) image.getType();
+    RealType radianceType = (RealType)
+      ((RealTupleType) imageFunctionType.getRange()).getComponent(0);
+
+    // define display coordinates
+    display.addMap(new ScalarMap(RealType.Latitude, Display.Latitude));
+    display.addMap(new ScalarMap(RealType.Longitude, Display.Longitude));
+    ScalarMap rgbMap = new ScalarMap(radianceType, Display.RGB);
+    display.addMap(rgbMap);
+
+    // read McIDAS map file
+    BaseMapAdapter baseMapAdapter = new BaseMapAdapter(args[1]);
+    Data map = baseMapAdapter.getData();
+
+    // link map to display
+    DataReference maplinesRef = new DataReferenceImpl("MapLines");
+    maplinesRef.setData(map);
+    ConstantMap[] maplinesConstantMap = new ConstantMap[]
+      {new ConstantMap(1.002, Display.Radius),
+       new ConstantMap(0.0, Display.Blue)};
+    display.addReference(maplinesRef, maplinesConstantMap);
+
+    // link image to display
+    DataReferenceImpl imageRef = new DataReferenceImpl("ImageRef");
+    imageRef.setData(image);
+    display.addReferences(new ImageRendererJ3D(), imageRef);
+
+    display.addDisplayListener(this);
+    rotate_x(display);
+
+    // create frame (window) on screen
+    JFrame frame = new JFrame("Satellite Display");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+
+    // add 3-D display to panel in frame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.add(display.getComponent());
+
+    // finish off frame
+    frame.getContentPane().add(panel);
+    int WIDTH = 1280;
+    int HEIGHT = 1024;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+  }
+
+  public void displayChanged(DisplayEvent e)
+    throws RemoteException, VisADException {
+    if (e.getId() == DisplayEvent.FRAME_DONE) {
+      rotate((LocalDisplay) e.getDisplay());
+    }
+  }
+
+  public void rotate(LocalDisplay display)
+    throws RemoteException, VisADException {
+    ProjectionControl control = display.getProjectionControl();
+    double[] matrix = control.getMatrix();
+    double[] mult = display.make_matrix(0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+    control.setMatrix(display.multiply_matrix(mult, matrix));
+  }
+
+  public void rotate_x(LocalDisplay display)
+    throws RemoteException, VisADException {
+    ProjectionControl control = display.getProjectionControl();
+    double[] matrix = control.getMatrix();
+    double[] mult = display.make_matrix(90.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+    control.setMatrix(display.multiply_matrix(mult, matrix));
+  }
+
+}
+
diff --git a/examples/MapProjectionDisplay.java b/examples/MapProjectionDisplay.java
new file mode 100644
index 0000000..1dfc427
--- /dev/null
+++ b/examples/MapProjectionDisplay.java
@@ -0,0 +1,526 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+import java.awt.event.*;
+import java.net.URL;
+import java.rmi.RemoteException;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+import visad.*;
+import visad.data.mcidas.AreaAdapter;
+import visad.data.mcidas.BaseMapAdapter;
+import visad.georef.EarthLocation;
+import visad.georef.EarthLocationTuple;
+import visad.georef.MapProjection;
+import visad.java3d.*;
+import visad.jmet.GRIBCoordinateSystem;
+
+/**
+ * Example class to show how to create a Display side CoordinateSystem
+ * for supporting various map projections.  Also shows how to create
+ * a component for displaying the EarthLocation of the cursor.
+ */
+public class MapProjectionDisplay extends DisplayImplJ3D
+{
+
+    CoordinateSystem coordinateSystem = null;
+    ScalarMap latitudeMap = null;
+    ScalarMap longitudeMap = null;
+    ScalarMap altitudeMap = null;
+    JLabel locationLabel = new JLabel("Lat: Lon: Alt:");
+    DisplayRendererJ3D displayRenderer;
+
+    /**
+     * Construct a new MapProjectionDisplay using an image for
+     * the projection.  Will be displayed on a 2D surface
+     * @param imageSource image to use
+     */
+    public MapProjectionDisplay(String imageSource)
+        throws Exception
+    {
+        this(imageSource, false);
+    }
+
+    /**
+     * Construct a new MapProjectionDisplay using an image for
+     * the projection.
+     * @param imageSource image to use for MapProjection
+     * @param do3D  construct as a 3D display if true
+     * @see visad.georef.MapProjection
+     */
+    public MapProjectionDisplay(String imageSource, boolean do3D)
+        throws Exception
+    {
+        super("MapProjectionDisplay",
+            (do3D == true)
+                ? (DisplayRendererJ3D) new DefaultDisplayRendererJ3D()
+                : (DisplayRendererJ3D) new TwoDDisplayRendererJ3D());
+
+        displayRenderer = (DisplayRendererJ3D) getDisplayRenderer();
+
+        if (imageSource != null) {
+            // get an image and use the domain CS as the map projection
+            AreaAdapter aa = new AreaAdapter(imageSource);
+            coordinateSystem = 
+                new MapProjectionAdapter( (MapProjection) aa.getCoordinateSystem());
+        } else {  // use another known MapProjection class
+            coordinateSystem = 
+                // Lambert grid
+                new MapProjectionAdapter(new GRIBCoordinateSystem(211));
+                //LatLon Grid
+                //new MapProjectionAdapter(
+                //    new GRIBCoordinateSystem(0, 73, 73, -90, -30, 90, 240, 5, 2.5));
+        }
+
+        // create new DisplayTupleType
+        // These are the DisplayTypes that Latitude/Longitude/Altitude
+        // are mapped to.
+        DisplayRealType displayLatitudeType =
+            new DisplayRealType(
+                "ProjectionLat",
+                true, -90.0, 90.0, 0.0,
+                CommonUnit.degree);
+        DisplayRealType displayLongitudeType =
+            new DisplayRealType(
+                "ProjectionLon",
+                true, -180.0, 180.0, 0.0,
+                CommonUnit.degree);
+        DisplayRealType displayAltitudeType =
+            new DisplayRealType(
+                "ProjectionAlt",
+                true, -1.0, 1.0, -1.0, null);  // default of -1 sets at bottom
+        DisplayTupleType displayTupleType =
+            new DisplayTupleType(
+                new DisplayRealType[] {
+                    displayLatitudeType,
+                    displayLongitudeType,
+                    displayAltitudeType},
+                    coordinateSystem);
+
+        /* 
+         * Add in the ScalarMaps
+         *    RealType.Latitude  -> displayLatitudeType
+         *    RealType.Longitude -> displayLongitudeType
+         *    RealType.Altitude  -> displayAltitudeType
+         * Thus any data that we add to the display that has lat/lon/alt
+         * will get displayed automagically.
+         *
+         * We also set up ScalarMaps of:
+         *    RealType.XAxis  -> Display.XAxis
+         *    RealType.YAxis  -> Display.YAxis
+         *    RealType.ZAxis  -> Display.ZAxis
+         * to set the bounds for these axes.
+         */
+        latitudeMap = new ScalarMap(RealType.Latitude, displayLatitudeType);
+        addMap(latitudeMap);
+        latitudeMap.setRangeByUnits();
+
+        longitudeMap = new ScalarMap(RealType.Longitude, displayLongitudeType);
+        addMap(longitudeMap);
+        longitudeMap.setRangeByUnits();
+
+        ScalarMap map = new ScalarMap(RealType.XAxis, Display.XAxis);
+        map.setRange(-1.0, 1.0);
+        addMap(map);
+
+        map = new ScalarMap(RealType.YAxis, Display.YAxis);
+        map.setRange(-1.0, 1.0);
+        addMap(map);
+
+        if (do3D) {
+            altitudeMap =
+                new ScalarMap( RealType.Altitude, displayAltitudeType);
+            altitudeMap.setRange(0, 16000);
+            addMap(altitudeMap);
+
+            map = new ScalarMap(RealType.ZAxis, Display.ZAxis);
+            map.setRange(-1.0, 1.0);
+            addMap(map);
+        }
+
+        /* uncomment if you want to see the image as well. 
+        if (imageSource != null) {
+            FlatField imgData = aa.getData();
+            FunctionType ftype = (FunctionType) imgData.getType();
+            RealTupleType rtype = (RealTupleType)ftype.getRange();
+            RealType imageType = (RealType) rtype.getComponent(0);
+
+            DataReference imageRef = new DataReferenceImpl("ref");
+            imageRef.setData(imgData);
+            ConstantMap[] zMap = 
+                new ConstantMap[] { new ConstantMap(0.0, Display.ZAxis) };
+            addReference(imageRef, zMap);
+        }
+        */
+    
+        DataReference mapRef = new DataReferenceImpl("maplines");
+        BaseMapAdapter bma = 
+            new BaseMapAdapter(
+                new URL("ftp://ftp.ssec.wisc.edu/pub/visad-2.0/OUTLSUPW"));
+        mapRef.setData(bma.getData());
+        addReference(mapRef);
+
+        // Enable the MOUSE_MOVED event so we can display the cursor's
+        // EarthLocation
+        enableEvent(DisplayEvent.MOUSE_MOVED);
+        addDisplayListener(new DisplayListener() {
+            public void displayChanged(DisplayEvent event) {
+                int id = event.getId();
+                try {
+                    if (id == event.MOUSE_MOVED) {
+                        pointerMoved(event.getX(), event.getY());
+                    }
+                } catch (Exception e) {
+                    System.err.println(e);
+                }
+            }
+        });
+
+        // Add a KeyboardBehavior so we can use Ctrl-R to reset the
+        // display and use other keyboard controls for zoom/pan
+        displayRenderer.addKeyboardBehavior(
+            new KeyboardBehaviorJ3D(displayRenderer));
+    }
+
+    /**
+     * Test with 'java -Xmx64m MapProjectionDisplay <do3D> <image>'.
+     * @param do3D   "true" if you want a 3D display, X for 2D
+     * @param image  image for MapProjection (file or ADDE URL)
+     */
+    public static void main(String[] args)
+        throws Exception
+    {
+        boolean do3D = 
+            (args.length > 0)
+              ? args[0].equalsIgnoreCase("true")
+              : false;
+        String imageSource = 
+            (args.length > 1) 
+                ? args[1] 
+                : null;
+        MapProjectionDisplay display = 
+            new MapProjectionDisplay(imageSource, do3D);
+        JFrame frame = new JFrame("Map Projection test");
+        frame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e)
+            {
+                System.exit(0);
+            }
+        });
+        frame.getContentPane().add(display.getComponent());
+        frame.getContentPane().add("South", display.getLocationIndicator());
+        frame.pack();
+        frame.show();
+    }
+
+    /**
+     * Get the center lat/lon/alt of the projection.
+     * @return center location
+     */
+    public EarthLocation getCenterPoint() {
+        return getEarthLocation(new double[] {0, 0, 0});
+    }
+
+    /**
+     * Get the EarthLocation of a point in XYZ space
+     * @param  xyz  RealTuple with MathType 
+     *              RealTupleType.SpatialCartesian3DTuple)
+     * @return point in lat/lon/alt space.
+     */
+    public EarthLocation getEarthLocation(RealTuple xyz)
+    {
+        EarthLocation el = null;
+        try
+        {
+            el = getEarthLocation(
+                new double[] { ((Real) xyz.getComponent(0)).getValue(),
+                               ((Real) xyz.getComponent(1)).getValue(),
+                               ((Real) xyz.getComponent(2)).getValue()});
+        }
+        catch (VisADException e)
+        { e.printStackTrace();}      // can't happen
+        catch (RemoteException e)
+        { e.printStackTrace();}      // can't happen
+        return el;
+    }
+
+    /**
+     * Get the EarthLocation of a point in XYZ space
+     * @param  xyz  double[3] of x,y,z coords.
+     * @return point in lat/lon/alt space.
+     */
+    public EarthLocation getEarthLocation(double[] xyz)
+    {
+        EarthLocationTuple value = null;
+        try
+        {
+            float[][] numbers = visad.Set.doubleToFloat(
+                coordinateSystem.fromReference(
+                    new double[][] {
+                        new double[] {xyz[0]},
+                        new double[] {xyz[1]},
+                        new double[] {xyz[2]}}));
+            Real lat = new Real(
+                RealType.Latitude,
+                getScaledValue( latitudeMap, numbers[0][0]),
+                coordinateSystem.getCoordinateSystemUnits()[0]);
+            Real lon = new Real(
+                RealType.Longitude,
+                getScaledValue( longitudeMap, numbers[1][0]),
+                coordinateSystem.getCoordinateSystemUnits()[1]);
+            Real alt = null;
+            if (isDisplay3D())
+            {
+                Real fakeAltitude = 
+                    new Real( RealType.ZAxis,
+                              getScaledValue( altitudeMap, numbers[2][0]),
+                              coordinateSystem.getCoordinateSystemUnits()[2]);
+                alt = new Real(RealType.Altitude, fakeAltitude.getValue()); 
+            }
+            else
+            {
+                alt = new Real(RealType.Altitude, 0);
+            }
+            value = new EarthLocationTuple(lat, lon, alt);
+        }
+        catch (VisADException e)
+        { e.printStackTrace();}      // can't happen
+        catch (RemoteException e)
+        { e.printStackTrace();}      // can't happen
+        return value;
+    }
+
+    /**
+     * Returns the spatial (XYZ) coordinates of the particular EarthLocation
+     * @return  RealTuple of display coordinates.
+     */
+    public RealTuple getSpatialCoordinates(EarthLocation el)
+    {
+        if (el == null)
+            throw new NullPointerException(
+              "MapProjectionDisplay.getSpatialCoorindate():  " + 
+              "null input EarthLocation");
+        RealTuple spatialLoc = null;
+        try
+        {
+            float[][] temp = 
+                coordinateSystem.toReference(
+                    new float[][] {
+                        latitudeMap.scaleValues(
+                            new double[] { el.getLatitude().getValue(CommonUnit.degree) }),
+                        longitudeMap.scaleValues(
+                            new double[] { el.getLongitude().getValue(CommonUnit.degree) }),
+                        (isDisplay3D() == true)
+                            ? altitudeMap.scaleValues(
+                               new double[] { el.getAltitude().getValue(CommonUnit.meter) })
+                            : new float[] {0}
+                    });
+            double[] xyz = new double[3];
+            xyz[0] = temp[0][0];
+            xyz[1] = temp[1][0];
+            xyz[2] = temp[2][0];
+            spatialLoc = 
+                new RealTuple(RealTupleType.SpatialCartesian3DTuple, xyz);
+
+        }
+        catch (VisADException e)
+        { e.printStackTrace();}      // can't happen
+        catch (RemoteException e)
+        { e.printStackTrace();}      // can't happen
+        return spatialLoc;
+    }
+
+    /** return a scaled value from the ScalarMap */
+    private float getScaledValue(ScalarMap map, float value)
+    {
+        return (map != null) 
+                  ? map.inverseScaleValues(new float[] {value})[0]
+                  : 0.f;
+    }
+
+    /**
+     * See if this is a 2D or 3D display.
+     * @return  true if 3D
+     */
+    public boolean isDisplay3D() {
+        return !displayRenderer.getMode2D();
+    }
+
+    /**
+     * Handles a change in the position of the mouse-pointer.
+     */
+    private void pointerMoved(int x, int y)
+            throws UnitException, VisADException, RemoteException {
+
+        /*
+         * Convert from (pixel, line) Java Component coordinates to (latitude,
+         * longitude)
+         */
+        VisADRay ray        =
+            displayRenderer.getMouseBehavior().findRay(x, y);
+        EarthLocation el = 
+            getEarthLocation(
+                new double[] {ray.position[0], ray.position[1], ray.position[2] });
+        locationLabel.setText(el.toString());
+    }
+
+    /**
+     * Get the component that will show the location.
+     * @return a component to show the cursor location.
+     */
+    public Component getLocationIndicator() {
+        return locationLabel;
+    }
+
+    /**
+     * An adapter for MapProjection coordinate systems (ie: ones with
+     * a reference of Lat/Lon).  Allows for the conversion from lat/lon
+     * to Display.DisplaySpatialCartesianTuple (XYZ).  Altitude (z) values
+     * are held constant.
+     */
+    protected class MapProjectionAdapter extends CoordinateSystem
+    {
+        private final MapProjection mapProjection;
+        private final int latIndex;
+        private final int lonIndex;
+        private final double scaleX;
+        private final double scaleY;
+        private final double offsetX;
+        private final double offsetY;
+    
+        /**
+         * Construct a new CoordinateSystem which uses a MapProjection for
+         * the transformations between x,y and lat/lon.
+         *
+         * @param  mapProjection  CoordinateSystem that transforms from xy
+         *                        in the data space to lat/lon.
+         * @exception  VisADException  can't create the necessary VisAD object
+         */
+        public MapProjectionAdapter(MapProjection mapProjection)
+            throws VisADException
+        {
+            super(
+                Display.DisplaySpatialCartesianTuple, 
+                new Unit[] 
+                    {CommonUnit.degree, CommonUnit.degree, null});
+            this.mapProjection = mapProjection;
+            latIndex = mapProjection.getLatitudeIndex();
+            lonIndex = mapProjection.getLongitudeIndex();
+            java.awt.geom.Rectangle2D bounds = 
+                    mapProjection.getDefaultMapArea();
+            /*
+            System.out.println("X = " + bounds.getX() +
+                               " Y = "+ bounds.getY() +
+                               " width = "+ bounds.getWidth() +
+                               " height = "+ bounds.getHeight());
+            */
+            scaleX  = bounds.getWidth()/2.0;
+            scaleY  = bounds.getHeight()/2.0;
+            offsetX = bounds.getX() + scaleX;
+            offsetY = bounds.getY() + scaleY;
+        }
+            
+        /**
+         * Transform latitude/longitude/altitude value to XYZ
+         *
+         * @param  latlonalt   array of latitude, longitude, altitude values
+         * @return array of display xyz values.
+         *
+         * @exception VisADException  can't create the necessary VisAD object
+         */
+        public double[][] toReference(double[][] latlonalt) 
+            throws VisADException 
+        {
+            if (latlonalt == null || latlonalt[0].length < 1) 
+                throw new VisADException(
+                    "MapProjection.toReference: null input array");
+            int numpoints = latlonalt[0].length;
+            double[][] t2 = new double[2][numpoints];
+            for (int i = 0; i < numpoints; i++)
+            {
+                t2[latIndex][i] = latlonalt[0][i];
+                t2[lonIndex][i] = latlonalt[1][i];
+            }
+            t2 = mapProjection.fromReference(t2);
+            if (t2 == null) 
+                throw new VisADException(
+                    "MapProjection.toReference: " + 
+                    "Can't do (lat,lon) to (x,y) transformation");
+            for (int i = 0; i < numpoints; i++)
+            {
+                latlonalt[0][i] = (t2[0][i]-offsetX)/scaleX;
+                latlonalt[1][i] = (t2[1][i]-offsetY)/scaleY;
+            }
+            return latlonalt;
+        }
+         
+        /**
+         * Transform display XYZ values to latitude/longitude/altitude
+         *
+         * @param  xyz   array of Display.DisplaySpatialCartesianTuple XYZ values
+         * @return array of display lat/lon/alt values.
+         *
+         * @exception VisADException  can't create the necessary VisAD object
+         */
+        public double[][] fromReference(double[][] xyz) 
+            throws VisADException 
+        {
+            if (xyz == null || xyz[0].length < 1) 
+                throw new VisADException(
+                    "MapProjection.fromReference: null input array");
+            int numpoints = xyz[0].length;
+            double[][] t2 = new double[2][numpoints];
+            for (int i = 0; i < numpoints; i++)
+            {
+                t2[0][i] = xyz[0][i]*scaleX + offsetX;
+                t2[1][i] = xyz[1][i]*scaleY + offsetY;
+            }
+            t2 = mapProjection.toReference(t2);
+            if (t2 == null) 
+                throw new VisADException(
+                    "MapProjection.toReference: " + 
+                    "Can't do (x,y) to (lat,lon) transformation");
+            for (int i = 0; i < numpoints; i++)
+            {
+                xyz[0][i] = t2[latIndex][i];
+                xyz[1][i] = t2[lonIndex][i];
+            }
+            return xyz;
+        }
+    
+        public boolean equals(Object obj)
+        {
+            if (!(obj instanceof MapProjectionAdapter))
+                return false;
+            MapProjectionAdapter that = 
+                (MapProjectionAdapter) obj;
+            return
+                (that.mapProjection).equals(mapProjection);
+        }
+
+        public String toString() {
+            return "Using " + mapProjection.toString();
+        }
+    }
+}
diff --git a/examples/MiniSheet.java b/examples/MiniSheet.java
new file mode 100644
index 0000000..26cc7bc
--- /dev/null
+++ b/examples/MiniSheet.java
@@ -0,0 +1,296 @@
+//
+// MiniSheet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import visad.data.netcdf.Plain;
+import visad.ss.BasicSSCell;
+import visad.ss.FancySSCell;
+import visad.util.Util;
+
+/** MiniSheet is a "toy" version of visad.ss.SpreadSheet,
+    with only two cells.  It demonstrates how the developer can use
+    the classes in the visad.ss package as GUI components for their
+    own applications.<P> */
+public class MiniSheet extends JFrame implements ActionListener {
+
+  // type 'java MiniSheet' to run this application
+
+  /** The main method just constructs a MiniSheet, displays it, and exits */
+  public static void main(String[] argv) {
+    MiniSheet ms = new MiniSheet();
+    ms.pack();
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    Dimension appSize = ms.getSize();
+    ms.setLocation(screenSize.width/2 - appSize.width/2,
+                   screenSize.height/2 - appSize.height/2);
+    ms.setVisible(true);
+  }
+
+  /** Creates a label with black text s and adds it to p */
+  private static void createLabel(JPanel p, String s) {
+    JLabel l = new JLabel(s);
+    l.setForeground(Color.black);
+    p.add(l);
+  }
+
+  /** Two spreadsheet cells */
+  private FancySSCell Cell1, Cell2;
+
+  /** Two mapping buttons */
+  private JButton Maps1, Maps2;
+
+  /** Two text fields */
+  private JTextField Formula1, Formula2;
+
+  /** Constructs the MiniSheet frame */
+  public MiniSheet() {
+    // construct a frame with appropriate title
+    super("MiniSheet");
+
+    // mapping dialog is the wrong color without this line
+    setBackground(Color.white);
+
+    // end program when this frame is closed
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        quitProgram();
+      }
+    });
+
+    // construct main panel
+    JPanel main = new JPanel();
+    setContentPane(main);
+    main.setLayout(new BoxLayout(main, BoxLayout.X_AXIS));
+
+    // construct left panel
+    JPanel left = new JPanel();
+    left.setBorder(new CompoundBorder(new EtchedBorder(),
+                                      new EmptyBorder(5, 10, 5, 10)));
+    left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
+    main.add(left);
+
+    // add JLabels to left panel
+    createLabel(left, "MiniSheet -- a toy spreadsheet example program");
+    createLabel(left, "created using components from the");
+    createLabel(left, "VisAD Visualization SpreadSheet.  See:");
+    createLabel(left, "    http://www.ssec.wisc.edu/~curtis/ss.html");
+    createLabel(left, "for more information about the SpreadSheet.");
+    createLabel(left, "  ");
+    createLabel(left, "Drag the left mouse button for 3-D rotation.");
+    createLabel(left, "  ");
+    createLabel(left, "Click the Load button to import a data set.");
+    createLabel(left, "  ");
+    createLabel(left, "Click the Save button to export a data set");
+    createLabel(left, "to a netCDF file.");
+    createLabel(left, "  ");
+    createLabel(left, "Click the Maps button to change the mappings");
+    createLabel(left, "from the data to the display.");
+    createLabel(left, "  ");
+    createLabel(left, "Click the Show button to display a cell's");
+    createLabel(left, "controls if they were closed.");
+    createLabel(left, "  ");
+    createLabel(left, "Type a formula into a cell's Formula text field");
+    createLabel(left, "to compute the cell using that formula.");
+    createLabel(left, "For example, try typing \"sqrt(CELL1) / 3\"");
+    createLabel(left, "into the second cell's Formula text field");
+    createLabel(left, "after loading data into the first cell.");
+
+    // create panel for Quit button so the button can be centered
+    JPanel qpanel = new JPanel();
+    qpanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    qpanel.setLayout(new BoxLayout(qpanel, BoxLayout.X_AXIS));
+    left.add(Box.createRigidArea(new Dimension(0, 15)));
+    left.add(qpanel);
+
+    // add Quit button to its panel
+    JButton quit = new JButton("Quit");
+    quit.addActionListener(this);
+    quit.setActionCommand("quit");
+    qpanel.add(Box.createHorizontalGlue());
+    qpanel.add(quit);
+    qpanel.add(Box.createHorizontalGlue());
+
+    // don't let left panel shrink or grow in size
+    Dimension lps = left.getPreferredSize();
+    left.setMinimumSize(lps);
+    left.setMaximumSize(lps);
+
+    // construct two spreadsheet cells and related GUI components
+    for (int i=1; i<=2; i++) {
+      JPanel cellPanel = new JPanel();
+      cellPanel.setLayout(new BoxLayout(cellPanel, BoxLayout.Y_AXIS));
+      main.add(cellPanel);
+      FancySSCell fCell = null;
+      try {
+        fCell = new FancySSCell("CELL" + i, this);
+        fCell.setDimension(BasicSSCell.JAVA3D_3D);
+      }
+      catch (Exception exc) {
+        System.out.println("Could not create the first spreadsheet cell!");
+        System.out.println("Received the following exception:");
+        exc.printStackTrace();
+        System.exit(i);
+      }
+      fCell.setPreferredSize(new Dimension(400, 400));
+      fCell.setMaximumSize(new Dimension(400, 400));
+      JPanel bPanel = new JPanel();
+      bPanel.setLayout(new BoxLayout(bPanel, BoxLayout.X_AXIS));
+      JButton load = new JButton("Load");
+      load.addActionListener(this);
+      load.setActionCommand("load" + i);
+      bPanel.add(load);
+      JButton save = new JButton("Save");
+      save.addActionListener(this);
+      save.setActionCommand("save" + i);
+      bPanel.add(save);
+      JButton maps = new JButton("Maps");
+      maps.addActionListener(this);
+      maps.setActionCommand("maps" + i);
+      bPanel.add(maps);
+      JButton show = new JButton("Show");
+      show.addActionListener(this);
+      show.setActionCommand("show" + i);
+      bPanel.add(show);
+      JTextField tf = new JTextField();
+      Util.adjustTextField(tf);
+      JPanel lPanel = new JPanel();
+      lPanel.setLayout(new BoxLayout(lPanel, BoxLayout.X_AXIS));
+      lPanel.add(Box.createHorizontalGlue());
+      JLabel l = new JLabel("CELL" + i);
+      l.setForeground(Color.blue);
+      lPanel.add(l);
+      lPanel.add(Box.createHorizontalGlue());
+      JPanel fPanel = new JPanel();
+      fPanel.setLayout(new BoxLayout(fPanel, BoxLayout.X_AXIS));
+      createLabel(fPanel, "Formula:  ");
+      JTextField textf = new JTextField();
+      Util.adjustTextField(textf);
+      textf.addActionListener(this);
+      textf.setActionCommand("formula" + i);
+      fPanel.add(textf);
+      cellPanel.add(lPanel);
+      cellPanel.add(fPanel);
+      cellPanel.add(fCell);
+      cellPanel.add(bPanel);
+      if (i == 1) {
+        Cell1 = fCell;
+        Maps1 = maps;
+        Formula1 = textf;
+      }
+      else {
+        Cell2 = fCell;
+        Maps2 = maps;
+        Formula2 = textf;
+      }
+    }
+  }
+
+  /** This method handles button presses */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("quit")) quitProgram();
+    else if (cmd.equals("load1")) {
+      try {
+        Cell1.removeAllReferences();
+      }
+      catch (Exception exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      Cell1.loadDataDialog();
+    }
+    else if (cmd.equals("load2")) {
+      try {
+        Cell2.removeAllReferences();
+      }
+      catch (Exception exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      Cell2.loadDataDialog();
+    }
+    else if (cmd.equals("save1")) {
+      try {
+        Cell1.saveDataDialog(Cell1.getFirstVariableName(), new Plain());
+      }
+      catch (Exception exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+    }
+    else if (cmd.equals("save2")) {
+      try {
+        Cell2.saveDataDialog(Cell2.getFirstVariableName(), new Plain());
+      }
+      catch (Exception exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+    }
+    else if (cmd.equals("maps1")) Cell1.addMapDialog();
+    else if (cmd.equals("maps2")) Cell2.addMapDialog();
+    else if (cmd.equals("show1")) Cell1.showWidgetFrame();
+    else if (cmd.equals("show2")) Cell2.showWidgetFrame();
+    else if (cmd.equals("formula1")) {
+      Maps1.requestFocus();
+      try {
+        Cell1.addDataSource(Formula1.getText());
+      }
+      catch (Exception exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+    }
+    else if (cmd.equals("formula2")) {
+      Maps2.requestFocus();
+      try {
+        Cell2.addDataSource(Formula2.getText());
+      }
+      catch (Exception exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+    }
+  }
+
+  /** Waits for files to finish saving before quitting */
+  void quitProgram() {
+    Thread t = new Thread() {
+      public void run() {
+        if (BasicSSCell.isSaving()) {
+          System.out.println("Please wait for MiniSheet to finish " +
+                             "saving files...");
+        }
+        while (BasicSSCell.isSaving()) {
+          try {
+            sleep(500);
+          }
+          catch (InterruptedException exc) { }
+        }
+        System.exit(0);
+      }
+    };
+    t.start();
+  }
+}
+
diff --git a/examples/MultiData.java b/examples/MultiData.java
new file mode 100644
index 0000000..4fd43bf
--- /dev/null
+++ b/examples/MultiData.java
@@ -0,0 +1,152 @@
+//
+// MultiData.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Dimension;
+import java.awt.event.*;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import javax.swing.*;
+import visad.*;
+import visad.data.*;
+import visad.java3d.DisplayImplJ3D;
+
+/** Demonstrates how to switch multiple data objects
+    between a single display. */
+public class MultiData extends JFrame {
+
+  /** imports data objects from the given list of files */
+  private static Data[] loadData(String[] files)
+    throws VisADException, RemoteException, BadFormException, IOException
+  {
+    DefaultFamily loader = new DefaultFamily("loader");
+    Data[] d = new Data[files.length];
+    for (int i=0; i<files.length; i++) {
+      d[i] = loader.open(files[i]);
+    }
+    return d;
+  }
+
+  /** sets up the given display to use the specified data reference and data */
+  private static void setupDisplay(Display display, DataReferenceImpl ref,
+    Data data) throws VisADException, RemoteException
+  {
+    // get a list of decent mappings for this data
+    MathType type = data.getType();
+    ScalarMap[] maps = type.guessMaps(true);
+
+    // add the maps to the display
+    for (int i=0; i<maps.length; i++) {
+      display.addMap(maps[i]);
+    }
+
+    // add the data reference to the display
+    ref.setData(data);
+    display.addReference(ref);
+  }
+
+  /** constructs a new MultiData application */
+  public MultiData(String[] files) throws Exception {
+    // load all data files and set up VisAD display
+    final Data[] data = loadData(files);
+    final DisplayImplJ3D display = new DisplayImplJ3D("display");
+    final DataReferenceImpl ref = new DataReferenceImpl("ref");
+    setupDisplay(display, ref, data[0]);
+
+    // construct user interface
+    setTitle("Multiple data viewer");
+    JPanel pane = new JPanel();
+    setContentPane(pane);
+    pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
+    JPanel left = new JPanel();
+    left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
+    left.add(display.getComponent());
+    final JComboBox combo = new JComboBox();
+    combo.setEditable(false);
+    for (int i=0; i<files.length; i++) {
+      combo.addItem(files[i]);
+    }
+    left.add(combo);
+    pane.add(left);
+    final JPanel widgetPanel = new JPanel();
+    widgetPanel.setPreferredSize(new Dimension(400, 0));
+    widgetPanel.setMaximumSize(new Dimension(400, Integer.MAX_VALUE));
+    widgetPanel.add(display.getWidgetPanel());
+    pane.add(widgetPanel);
+
+    // update the GUI whenever the user selects new data from the list
+    combo.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        int index = combo.getSelectedIndex();
+        try {
+          // remove the old widgets from the user interface
+          widgetPanel.remove(0);
+          // remove the old data from the display
+          display.removeReference(ref);
+          display.clearMaps();
+          // add the new data to the display
+          try {
+            setupDisplay(display, ref, data[index]);
+          }
+          catch (VisADException exc) {
+            exc.printStackTrace();
+          }
+          catch (RemoteException exc) {
+            exc.printStackTrace();
+          }
+          // add the new widgets to the user interface and refresh it
+          widgetPanel.add(display.getWidgetPanel());
+          widgetPanel.validate();
+          widgetPanel.repaint();
+        }
+        catch (VisADException exc) {
+          exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          exc.printStackTrace();
+        }
+      }
+    });
+  }
+
+  /** tests the MultiData application */
+  public static void main(String[] argv) throws Exception {
+    if (argv.length < 2) {
+      System.err.println("Please specify at least two data files " +
+        "on the command line.");
+      System.exit(1);
+    }
+    MultiData md = new MultiData(argv);
+    md.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    md.setSize(700, 400);
+    md.setVisible(true);
+  }
+
+}
+
diff --git a/examples/Parallel.java b/examples/Parallel.java
new file mode 100644
index 0000000..94b7cac
--- /dev/null
+++ b/examples/Parallel.java
@@ -0,0 +1,129 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// import needed classes
+import visad.*;
+import visad.java3d.*;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class Parallel {
+
+  private static final int NCOORDS = 5;
+  private static final int NROWS = 6;
+
+  // type 'java Parallel' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    RealType index = RealType.getRealType("index");
+    RealType[] coords = new RealType[NCOORDS];
+    for (int i=0; i<NCOORDS; i++) {
+      coords[i] = RealType.getRealType("coord" + i);
+    }
+    RealTupleType range = new RealTupleType(coords);
+    FunctionType ftype = new FunctionType(index, range);
+    Integer1DSet index_set = new Integer1DSet(NROWS);
+
+    float[][] samples = new float[NCOORDS][NROWS];
+    for (int i=0; i<NCOORDS; i++) {
+      for (int j=0; j<NROWS; j++) {
+        samples[i][j] = (float) Math.random();
+      }
+    }
+
+    FlatField data = new FlatField(ftype, index_set);
+    data.setSamples(samples, false);
+
+    // create a 2-D Display using Java3D
+    DisplayImpl display =
+      new DisplayImplJ3D("display", new TwoDDisplayRendererJ3D());
+
+    parallel(display, data);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("Parallel Coordinates VisAD Application");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+  /** create parallel coordinates display for data */
+  public static void parallel(DisplayImpl display, FlatField data)
+         throws VisADException, RemoteException {
+
+    FunctionType ftype = (FunctionType) data.getType();
+    RealType index = (RealType) ftype.getDomain().getComponent(0);
+    RealTupleType range = (RealTupleType) ftype.getRange();
+    int ncoords = range.getDimension();
+    int nrows = data.getLength();
+    Set index_set = data.getDomainSet();
+    float[][] samples = data.getFloats(false);
+
+    RealType x = RealType.getRealType("coordinate");
+    RealType y = RealType.getRealType("value");
+    SetType xy = new SetType(new RealTupleType(x, y));
+    FunctionType ptype = new FunctionType(index, xy);
+    FieldImpl pfield = new FieldImpl(ptype, index_set);
+    for (int j=0; j<nrows; j++) {
+      float[][] locs = new float[2][ncoords];
+      for (int i=0; i<ncoords; i++) {
+        locs[0][i] = i;
+        locs[1][i] = samples[i][j];
+      }
+      Gridded2DSet set = new Gridded2DSet(xy, locs, ncoords);
+      pfield.setSample(j, set, false);
+    }
+
+    // create a DataReference for river system
+    DataReference parallel_ref = new DataReferenceImpl("parallel");
+    parallel_ref.setData(pfield);
+
+    display.addMap(new ScalarMap(x, Display.XAxis));
+    display.addMap(new ScalarMap(y, Display.YAxis));
+
+    // enable axis scales
+    display.getGraphicsModeControl().setScaleEnable(true);
+
+    // link display to parallel display
+    display.addReference(parallel_ref);
+  }
+
+}
+
diff --git a/examples/ReflectionTest.java b/examples/ReflectionTest.java
new file mode 100644
index 0000000..17f6134
--- /dev/null
+++ b/examples/ReflectionTest.java
@@ -0,0 +1,121 @@
+//
+// ReflectionTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// we only need ReflectedUniverse and VisADException
+import visad.util.ReflectedUniverse;
+import visad.VisADException;
+
+// GUI classes--you could modify the example to reflect these also, if desired
+import java.awt.Component;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ * This example demonstrates the power and flexibility of
+ * visad.util.ReflectedUniverse for coding applications with reflection.
+ * The only VisAD classes needed to compile this source file are
+ * visad.util.ReflectedUniverse and visad.VisADException.
+ */
+public class ReflectionTest {
+
+  public static void main(String[] args) throws VisADException {
+    // create reflected universe
+    ReflectedUniverse r = new ReflectedUniverse();
+
+    // import needed classes
+    r.exec("import visad.ConstantMap");
+    r.exec("import visad.DataReferenceImpl");
+    r.exec("import visad.Display");
+    r.exec("import visad.FlatField");
+    r.exec("import visad.FunctionType");
+    r.exec("import visad.RealType");
+    r.exec("import visad.RealTupleType");
+    r.exec("import visad.ScalarMap");
+    r.exec("import visad.java3d.DisplayImplJ3D");
+    r.exec("import visad.util.Util");
+
+    // import some constants into reflected universe
+    r.setVar("false", false);
+    r.setVar("one_half", 0.5);
+    r.setVar("size", 64);
+
+    // construct data object
+    r.setVar("ir_radiance_name", "ir_radiance");
+    r.exec("ir_radiance = RealType.getRealType(ir_radiance_name)");
+    r.setVar("count_name", "count");
+    r.exec("count = RealType.getRealType(count_name)");
+    r.exec("ir_histogram = new FunctionType(ir_radiance, count)");
+    r.exec("histogram1 = FlatField.makeField(ir_histogram, size, false)");
+    r.setVar("vis_radiance_name", "vis_radiance");
+    r.exec("vis_radiance = RealType.getRealType(vis_radiance_name)");
+    r.exec("earth_location = new RealTupleType(" +
+      "RealType.Latitude, RealType.Longitude)");
+    r.exec("radiance = new RealTupleType(vis_radiance, ir_radiance)");
+    r.exec("image_tuple = new FunctionType(earth_location, radiance)");
+    r.exec("imaget1 = FlatField.makeField(image_tuple, size, false)");
+
+    // construct display and mappings
+    r.setVar("display_name", "display");
+    r.exec("d = new DisplayImplJ3D(display_name)");
+    r.exec("xmap = new ScalarMap(RealType.Longitude, Display.XAxis)");
+    r.exec("ymap = new ScalarMap(RealType.Latitude, Display.YAxis)");
+    r.exec("zmap = new ScalarMap(vis_radiance, Display.ZAxis)");
+    r.exec("rmap = new ConstantMap(one_half, Display.Red)");
+    r.exec("gmap = new ScalarMap(vis_radiance, Display.Green)");
+    r.exec("bmap = new ConstantMap(one_half, Display.Blue)");
+    r.exec("d.addMap(xmap)");
+    r.exec("d.addMap(ymap)");
+    r.exec("d.addMap(zmap)");
+    r.exec("d.addMap(rmap)");
+    r.exec("d.addMap(gmap)");
+    r.exec("d.addMap(bmap)");
+
+    // construct data reference
+    r.setVar("ref_name", "ref");
+    r.exec("ref = new DataReferenceImpl(ref_name)");
+    r.exec("ref.setData(imaget1)");
+    r.exec("d.addReference(ref)");
+
+    // display results onscreen
+    r.exec("comp = d.getComponent()");
+    Component c = (Component) r.getVar("comp");
+    JFrame f = new JFrame("VisAD with reflection");
+    f.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    JPanel p = new JPanel();
+    f.setContentPane(p);
+    p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+    p.add(c);
+    f.pack();
+    r.setVar("f", f);
+    r.exec("Util.centerWindow(f)");
+    f.show();
+  }
+
+}
diff --git a/examples/Region.java b/examples/Region.java
new file mode 100644
index 0000000..8b9e389
--- /dev/null
+++ b/examples/Region.java
@@ -0,0 +1,96 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// import needed classes
+import visad.*;
+import visad.java2d.DisplayImplJ2D;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class Region {
+
+  // type 'java Region' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    RealTupleType earth =
+      new RealTupleType(RealType.Latitude, RealType.Longitude);
+
+    // construct boundary of 7-pointed star
+    int np = 14;
+    float[][] samples = new float[2][np];
+    float radius = 7.0f;
+    for (int i=0; i<np; i++) {
+      double b = 2.0 * Math.PI * i / np;
+      samples[0][i] = radius * ((float) Math.cos(b));
+      samples[1][i] = radius * ((float) Math.sin(b));
+      radius = 10.0f - radius;
+    }
+
+    // compute triangles to fill star, and use them to construct Delaunay
+    int[][] tris = DelaunayCustom.fill(samples);
+    DelaunayCustom delaunay = new DelaunayCustom(samples, tris);
+
+    Irregular2DSet region =
+      new Irregular2DSet(earth, samples, null, null, null, delaunay);
+
+    // create a DataReference for region
+    final DataReference region_ref = new DataReferenceImpl("region");
+    region_ref.setData(region);
+
+    // create a Display using Java3D
+    // DisplayImpl display = new DisplayImplJ3D("image display");
+    // create a Display using Java2D
+    DisplayImpl display = new DisplayImplJ2D("image display");
+
+    // map earth coordinates to display coordinates
+    display.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    display.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+
+    // link the Display to region_ref
+    display.addReference(region_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("Region VisAD Application");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/examples/Region3D.java b/examples/Region3D.java
new file mode 100644
index 0000000..2625621
--- /dev/null
+++ b/examples/Region3D.java
@@ -0,0 +1,185 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// import needed classes
+import visad.*;
+import visad.java3d.DisplayImplJ3D;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class Region3D {
+
+  // type 'java Region3D' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    RealTupleType earth =
+      new RealTupleType(RealType.Latitude, RealType.Longitude);
+
+    // construct boundary of 7-pointed star
+    int np = 14;
+    float[][] samples = new float[2][np];
+    float radius = 1.0f;
+    for (int i=0; i<np; i++) {
+      double b = 2.0 * Math.PI * i / np;
+      samples[0][i] = radius * ((float) Math.cos(b));
+      samples[1][i] = radius * ((float) Math.sin(b));
+      radius = 1.5f - radius;
+System.out.println("sample[" + i + "] = (" + samples[1][i] + ", " +
+                   samples[0][i] + ")");
+    }
+
+    // compute triangles to fill star, and use them to construct Delaunay
+    int[][] tris = DelaunayCustom.fill(samples);
+for (int i=0; i<tris.length; i++) {
+  System.out.println("triangle[" + i + "] = (" + tris[i][0] + ", " +
+                     tris[i][1] + ", " + tris[i][2] + ")");
+}
+    DelaunayCustom delaunay = new DelaunayCustom(samples, tris);
+
+    Irregular2DSet region =
+      new Irregular2DSet(earth, samples, null, null, null, delaunay);
+
+    // create a DataReference for region
+    final DataReference region_ref = new DataReferenceImpl("region");
+    region_ref.setData(region);
+
+    // create a Display using Java3D
+    // DisplayImpl display = new DisplayImplJ3D("image display");
+    // create a Display using Java2D
+    DisplayImpl display = new DisplayImplJ3D("image display");
+
+    // map earth coordinates to display coordinates
+    ScalarMap xmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    display.addMap(xmap);
+    xmap.setRange(-1.0, 1.0);
+    ScalarMap ymap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    display.addMap(ymap);
+    ymap.setRange(-1.0, 1.0);
+    display.addMap(new ConstantMap(0.5f, Display.Alpha));
+
+    // link the Display to region_ref
+    display.addReference(region_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("Region3D VisAD Application");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
+/*
+demedici% java Region3D
+sample[0] = (0.0, 1.0)
+sample[1] = (0.21694186, 0.45048442)
+sample[2] = (0.7818315, 0.6234898)
+sample[3] = (0.48746395, 0.11126047)
+sample[4] = (0.9749279, -0.22252093)
+sample[5] = (0.39091575, -0.3117449)
+sample[6] = (0.43388373, -0.90096885)
+sample[7] = (6.123234E-17, -0.5)
+sample[8] = (-0.43388373, -0.90096885)
+sample[9] = (-0.39091575, -0.3117449)
+sample[10] = (-0.9749279, -0.22252093)
+sample[11] = (-0.48746395, 0.11126047)
+sample[12] = (-0.7818315, 0.6234898)
+sample[13] = (-0.21694186, 0.45048442)
+
+triangle[0] = (1, 2, 3)
+triangle[1] = (3, 4, 5)
+triangle[2] = (5, 6, 7)
+triangle[3] = (7, 8, 9)
+triangle[4] = (9, 10, 11)
+triangle[5] = (11, 12, 13)
+triangle[6] = (13, 0, 1)
+triangle[7] = (13, 1, 3)
+triangle[8] = (13, 3, 5)
+triangle[9] = (13, 5, 7)
+triangle[10] = (13, 7, 9)
+triangle[11] = (13, 9, 11)
+
+strip[0] = (0.7818315, 0.6234898, 0.0)       2
+strip[1] = (0.48746395, 0.11126047, 0.0)     3
+strip[2] = (0.21694186, 0.45048442, 0.0)     1
+strip[3] = (-0.21694186, 0.45048442, 0.0)    13
+strip[4] = (0.0, 1.0, 0.0)                   0
+strip[5] = (0.0, 1.0, 0.0)                   0
+strip[6] = (0.9749279, -0.22252093, 0.0)     4
+strip[7] = (0.9749279, -0.22252093, 0.0)     4
+strip[8] = (0.39091575, -0.3117449, 0.0)     5  bad
+strip[9] = (0.48746395, 0.11126047, 0.0)     3  bad
+strip[10] = (-0.21694186, 0.45048442, 0.0)   13 bad
+strip[11] = (0.39091575, -0.3117449, 0.0)    5  bad
+strip[12] = (6.123234E-17, -0.5, 0.0)        7
+strip[13] = (0.43388373, -0.90096885, 0.0)   6
+strip[14] = (0.43388373, -0.90096885, 0.0)   6
+strip[15] = (-0.43388373, -0.90096885, 0.0)  8
+strip[16] = (-0.43388373, -0.90096885, 0.0)  8
+strip[17] = (-0.39091575, -0.3117449, 0.0)   9  bad
+strip[18] = (6.123234E-17, -0.5, 0.0)        7  bad
+strip[19] = (-0.21694186, 0.45048442, 0.0)   13 bad
+strip[20] = (-0.39091575, -0.3117449, 0.0)   9  bad
+strip[21] = (-0.48746395, 0.11126047, 0.0)   11
+strip[22] = (-0.9749279, -0.22252093, 0.0)   10
+strip[23] = (-0.9749279, -0.22252093, 0.0)   10
+strip[24] = (-0.48746395, 0.11126047, 0.0)   11
+strip[25] = (-0.48746395, 0.11126047, 0.0)   11
+strip[26] = (-0.7818315, 0.6234898, 0.0)     12
+strip[27] = (-0.21694186, 0.45048442, 0.0)   13
+
+shaded regions in inner heptagon:
+
+               0
+
+   12                      2
+           13      1
+          ... ...
+          ...  ......
+      11 .....   .......3
+         .....    ......
+        ......      ....
+10      .......      ..       4
+        9......       5
+           .....
+               7
+
+        8             6
+
+*/
+
diff --git a/examples/Rivers.java b/examples/Rivers.java
new file mode 100644
index 0000000..f6b28ae
--- /dev/null
+++ b/examples/Rivers.java
@@ -0,0 +1,97 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// import needed classes
+import visad.*;
+import visad.java2d.DisplayImplJ2D;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class Rivers {
+
+  // type 'java Rivers' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    RealTupleType earth =
+      new RealTupleType(RealType.Latitude, RealType.Longitude);
+
+    // construct straight south flowing river1
+    float[][] points1 = {{3.0f, 2.0f, 1.0f, 0.0f},
+                         {0.0f, 0.0f, 0.0f, 0.0f}};
+    Gridded2DSet river1 = new Gridded2DSet(earth, points1, 4);
+
+    // construct east feeder river2
+    float[][] points2 = {{3.0f, 2.0f, 1.0f},
+                         {2.0f, 1.0f, 0.0f}};
+    Gridded2DSet river2 = new Gridded2DSet(earth, points2, 3);
+
+    // construct west feeder river3
+    float[][] points3 = {{4.0f, 3.0f, 2.0f},
+                         {-2.0f, -1.0f, 0.0f}};
+    Gridded2DSet river3 = new Gridded2DSet(earth, points3, 3);
+
+    // construct river system
+    Gridded2DSet[] river_system = {river1, river2, river3};
+    UnionSet rivers = new UnionSet(earth, river_system);
+
+    // create a DataReference for river system
+    final DataReference rivers_ref = new DataReferenceImpl("rivers");
+    rivers_ref.setData(rivers);
+
+    // create a Display using Java3D
+    // DisplayImpl display = new DisplayImplJ3D("image display");
+    // create a Display using Java2D
+    DisplayImpl display = new DisplayImplJ2D("image display");
+
+    // map earth coordinates to display coordinates
+    display.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    display.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+
+    // link the Display to rivers_ref
+    display.addReference(rivers_ref);
+    rivers_ref.setData(rivers);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("Rivers VisAD Application");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/examples/SatDisplay.java b/examples/SatDisplay.java
new file mode 100644
index 0000000..bcbb021
--- /dev/null
+++ b/examples/SatDisplay.java
@@ -0,0 +1,233 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+//
+// SatDisplay.java by Don Murray of Unidata
+//
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.net.URL;
+import javax.swing.JFrame;
+import visad.ColorControl;
+import visad.CoordinateSystem;
+import visad.ConstantMap;
+import visad.Data;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Linear2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.data.mcidas.AreaAdapter;
+import visad.data.mcidas.BaseMapAdapter;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.TwoDDisplayRendererJ3D;
+import visad.java3d.DefaultRendererJ3D;
+import visad.DisplayListener;
+import visad.DisplayEvent;
+import visad.VisADException;
+import java.rmi.RemoteException;
+
+/**
+ * Example class to display a satellite image in VisAD.
+ *
+ * @author  Don Murray - Unidata
+ */
+public class SatDisplay implements DisplayListener
+{
+
+    private DisplayImpl display;
+
+    private DefaultRendererJ3D drmap;
+    private DefaultRendererJ3D drimage;
+
+    /**
+     * Construct a satellite display using the specified McIDAS map file,
+     * image source.  The image can be displayed on a 3D globe or on a
+     * flat rectillinear projection.
+     *
+     * @param  mapFile      location of the McIDAS map file (path or URL)
+     * @param  imageSource  location of the image source (path or URL)
+     * @param  display3D    if true, use 3D display, otherwise flat rectillinear
+     * @param  remap        remap the image into a domain over North America
+     */
+    public SatDisplay(String mapFile, String imageSource,
+                      boolean display3D, boolean remap)
+    {
+        try
+        {
+            //  Read in the map file
+            BaseMapAdapter baseMapAdapter;
+            if (mapFile.indexOf("://") > 0)   // URL specified
+            {
+               baseMapAdapter = new BaseMapAdapter(new URL(mapFile) );
+            }
+            else   // local disk file
+            {
+               baseMapAdapter = new BaseMapAdapter(mapFile);
+            }
+
+            // Create the display and set up the scalar maps to map
+            // data to the display
+            ScalarMap latMap;     // latitude  -> YAxis
+            ScalarMap lonMap;     // longitude -> XAxis
+            if (display3D)
+            {
+                display = new DisplayImplJ3D("display");
+                latMap = new ScalarMap(RealType.Latitude, Display.Latitude);
+                lonMap = new ScalarMap(RealType.Longitude, Display.Longitude);
+            }
+            else
+            {
+                display = new DisplayImplJ3D("display",
+                                               new TwoDDisplayRendererJ3D());
+                latMap = new ScalarMap(RealType.Latitude, Display.YAxis);
+                lonMap = new ScalarMap(RealType.Longitude, Display.XAxis);
+            }
+            display.addMap(latMap);
+            display.addMap(lonMap);
+
+            // set the display to a global scale
+            latMap.setRange(-90.0, 90.0);
+            lonMap.setRange(-180.0, 180.0);
+
+            // create a reference for the map line
+            DataReference maplinesRef = new DataReferenceImpl("MapLines");
+            maplinesRef.setData(baseMapAdapter.getData());
+
+            // set the attributes of the map lines (color, location)
+            ConstantMap[] maplinesConstantMap = new ConstantMap[4];
+            maplinesConstantMap[0] = new ConstantMap(0., Display.Blue);
+            maplinesConstantMap[1] = new ConstantMap(1., Display.Red);
+            maplinesConstantMap[2] = new ConstantMap(0., Display.Green);
+            maplinesConstantMap[3] =
+                new ConstantMap(1.001, Display.Radius); // just above the image
+
+            // read in the image
+            AreaAdapter areaAdapter = new AreaAdapter(imageSource);
+            FlatField image = areaAdapter.getData();
+
+            // Extract the metadata from the image
+            FunctionType imageFunctionType =
+                (FunctionType) image.getType();
+            RealTupleType imageDomainType = imageFunctionType.getDomain();
+            RealTupleType imageRangeType =
+                (RealTupleType) imageFunctionType.getRange();
+
+            // remap and resample the image
+            if (remap)
+            {
+                int SIZE = 256;
+                RealTupleType latlonType =
+                  ((CoordinateSystem)
+                      imageDomainType.getCoordinateSystem()).getReference();
+                Linear2DSet remapDomainSet =
+                    new Linear2DSet(
+                        latlonType, -4.0, 70.0, SIZE, -150.0, 5.0, SIZE);
+                image =
+                    (FlatField) image.resample(
+                        remapDomainSet, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+            }
+
+            // select which band to show...
+            ScalarMap rgbMap =
+                new ScalarMap(
+                    (RealType) imageRangeType.getComponent(0), Display.RGB);
+            display.addMap(rgbMap);
+
+            // set the enhancement to a grey scale
+            ColorControl colorControl = (ColorControl) rgbMap.getControl();
+            colorControl.initGreyWedge();
+
+            // create a data reference for the image
+            DataReferenceImpl imageRef = new DataReferenceImpl("ImageRef");
+            imageRef.setData(image);
+
+            // add the data references to the display
+            display.disableAction();
+            drmap = new DefaultRendererJ3D();
+            drimage = new DefaultRendererJ3D();
+            drmap.toggle(false);
+            drimage.toggle(false);
+            display.addDisplayListener(this);
+    
+            display.addReferences(drmap, maplinesRef, maplinesConstantMap);
+            display.addReferences(drimage, imageRef,null);
+            display.enableAction();
+        }
+        catch (Exception ne)
+        {
+            ne.printStackTrace(); System.exit(1);
+        }
+
+    }
+
+    public void displayChanged(DisplayEvent e)
+         throws VisADException, RemoteException {
+      if (e.getId() == DisplayEvent.TRANSFORM_DONE) {
+        drmap.toggle(true);
+        drimage.toggle(true);
+      }
+    }
+
+    /**
+     * <UL>
+     * <LI>run 'java -mx64m SatDisplay' for globe display
+     * <LI>run 'java -mx64m SatDisplay X remap' for remapped globe display
+     * <LI>run 'java -mx64m SatDisplay X 2D' for flat display
+     * </UL>
+     */
+    public static void main (String[] args) {
+
+        String mapFile = "ftp://ftp.ssec.wisc.edu/pub/visad-2.0/OUTLSUPW";
+        String imageSource = "ftp://ftp.ssec.wisc.edu/pub/visad-2.0/AREA2001";
+        boolean use3D = true;
+        boolean remap = false;
+
+        JFrame frame = new JFrame("Satellite Display");
+        frame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e) {
+                System.exit(0);
+            }
+        });
+
+        if (args.length > 0 && !args[0].equals("X")) {
+           imageSource = args[0];
+           // mapFile = args[0];
+        }
+        if (args.length == 2) {
+           use3D = (args[1].indexOf("2") >= 0) ? false : true;
+           remap = (args[1].indexOf("2") >= 0) ? false : true;
+        }
+
+        SatDisplay satDisplay =
+            new SatDisplay(mapFile, imageSource, use3D, remap);
+        frame.getContentPane().add(satDisplay.display.getComponent());
+        frame.setSize(500, 500);
+        frame.setVisible(true);
+    }
+}
diff --git a/examples/ScaleTest.java b/examples/ScaleTest.java
new file mode 100644
index 0000000..aeb2c9d
--- /dev/null
+++ b/examples/ScaleTest.java
@@ -0,0 +1,401 @@
+//
+// ScaleTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.*;
+import java.awt.event.*;
+import java.rmi.RemoteException;
+import java.text.*;
+import java.util.Hashtable;
+import javax.swing.*;
+import javax.swing.event.*;
+import visad.*;
+import visad.data.units.*;
+import visad.java2d.*;
+import visad.java3d.*;
+import visad.util.*;
+
+/**
+ * Class to demostrate how to programatically control the
+ * AxisScales for ScalarMaps
+ * @author  Don Murray, Unidata
+ */
+public class ScaleTest extends JFrame {
+
+    DisplayImpl display;
+    ScalarMap tMap;
+    ScalarMap tdMap;
+    ScalarMap timeMap;
+    RealType temp;
+    RealType dewpoint;
+
+    /**
+     * Construct the ScaleTest.
+     * @param  do3D  true to use a 3D display
+     * @throws VisADException  problem creating a VisAD object
+     * @throws RemoteException  problem creating a remote object
+     */
+    public ScaleTest(boolean do3D)
+        throws VisADException, RemoteException
+    {
+        addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e)
+            {
+                System.exit(0);
+            }
+        });
+        if (do3D)
+        {
+            display = new DisplayImplJ3D("Display");
+              //new TwoDDisplayRendererJ3D());
+        }
+        else
+        {
+            display = new DisplayImplJ2D("Display");
+        }
+        GraphicsModeControl gmc = display.getGraphicsModeControl();
+        gmc.setScaleEnable(true);
+        // uncomment this if you want to show the GMC widget
+        //JFrame frame = new JFrame("GMC control");
+        //frame.getContentPane().add(new GMCWidget(gmc));
+        //frame.pack();
+        // set that aspect
+        ProjectionControl pc = display.getProjectionControl();
+        pc.setAspectCartesian((do3D == true) 
+                                ? new double[] {.8, 1.0, 1.0 }
+                                : new double[] {.8, 1.0 });
+        Unit cel = null;
+        try
+        {
+            cel = Parser.parse("degC");
+            temp = RealType.getRealType("Temperature", SI.kelvin);
+            //temp = RealType.getRealType("Temperature", cel);
+            dewpoint = RealType.getRealType("DewPoint", cel);
+        }
+        catch (Exception e) {e.printStackTrace();}
+        tMap = new ScalarMap(temp, Display.YAxis);
+        tMap.setOverrideUnit(cel);
+        tdMap = new ScalarMap(dewpoint,  
+                             (do3D == true) ? Display.ZAxis : Display.YAxis);
+        timeMap = new ScalarMap(RealType.Time, Display.XAxis);
+        // user defined labels
+        Hashtable timeLabels = new Hashtable();
+        timeLabels.put(new Double(0), "First");
+        timeLabels.put(new Double(10), "Last");
+        timeMap.getAxisScale().setLabelTable(timeLabels);
+        Hashtable tdLabels = new Hashtable();
+        tdLabels.put(new Double(-20), "Low");
+        tdLabels.put(new Double(5), "High");
+        tdMap.getAxisScale().setLabelTable(tdLabels);
+
+        //  orientation
+        tdMap.getAxisScale().setSide(AxisScale.SECONDARY);
+
+        // minor ticks
+        tdMap.getAxisScale().setMinorTickSpacing(2.5);
+
+        // format labelling
+        AxisScale tScale = tMap.getAxisScale();
+        DecimalFormat formatter = (DecimalFormat) DecimalFormat.getInstance();
+        formatter.applyPattern("0.0E0");
+        tScale.setNumberFormat(formatter);
+        // calculate labels from user input (NB: format must be set first)
+        //tScale.createStandardLabels(50, 10, 50, 40);
+
+        // set title
+        tScale.setTitle(tScale.getTitle() + " (" + cel + ")");
+        display.addMap(tMap);
+        display.addMap(tdMap);
+        display.addMap(timeMap);
+        DataReference ref = new DataReferenceImpl("data");
+        float[][] timeVals = 
+            new float[][] { { 0.f, 2.f, 4.f, 6.f, 8.f, 10.f} };
+        Gridded1DSet timeSet = new Gridded1DSet(RealType.Time, timeVals, 6);
+        float[][] tVals =   // values in K, display in Cel
+            new float[][] { { 294.15f, 326.15f, 310.15f, 
+                              278.15f, 278.15f, 293.15f}};
+        float[][] tdVals = new float[][] { { 1.f, 3.f, 7.f, -15.f, -22.f,4.f}};
+        FunctionType fieldType1 = 
+            new FunctionType(RealType.Time, temp);
+        FlatField field1 = new FlatField(fieldType1, timeSet);
+        field1.setSamples(tVals);
+        FunctionType fieldType2 = 
+            new FunctionType(RealType.Time, dewpoint);
+        FlatField field2 = new FlatField(fieldType2, timeSet);
+        field2.setSamples(tdVals);
+        ref.setData(new Tuple(new FlatField[] { field1, field2 }));
+        display.addReference(ref);
+        // add some controls
+        JPanel left = new JPanel();
+        left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
+        left.add(new ScaleControlPanel(tMap));
+        left.add(new ScaleControlPanel(tdMap));
+        left.add(new ScaleControlPanel(timeMap));
+        JButton print = new JButton("Print Me");
+        print.addActionListener(new visad.util.PrintActionListener(display));
+        left.add(print);
+        Container mainPanel = getContentPane();
+        mainPanel.setLayout(new GridLayout(1,2));
+        mainPanel.add(left);
+        mainPanel.add(display.getComponent());
+        pack();
+        // uncomment if you want GMC frame
+        //frame.show();
+    }
+
+    /** Run using java ScaleTest */
+    public static void main(String[] args)
+        throws Exception
+    {
+        ScaleTest frame = new ScaleTest(args.length > 0);
+        frame.show();
+    }
+
+    /**
+     *  Class for creating a FontSelector
+     */
+    class FontSelector extends JComboBox
+    {
+        /**
+         *  Construct a FontSelector
+         */
+        FontSelector()
+        {
+            GraphicsEnvironment ge =
+               GraphicsEnvironment.getLocalGraphicsEnvironment();
+            Font[] fonts = ge.getAllFonts();
+
+           // this will put all available fonts in a platform independent way
+
+           // mind you, not all of them work for me
+
+            for(int i=0;i<fonts.length;i++){
+              String fn = fonts[i].getFontName();
+              addItem( fn );
+            }
+            addItem("HersheyFont.futural");
+            addItem("HersheyFont.futuram");
+            addItem("HersheyFont.cursive");
+            addItem("HersheyFont.timesr");
+            addItem("HersheyFont.timesrb");
+            addItem("HersheyFont.rowmans");
+            addItem("HersheyFont.rowmant");
+        }
+    }
+
+    /**
+     *  Class for creating a UI for controlling each AxisScale
+     */
+    class ScaleControlPanel extends JPanel
+    {
+        JSlider labelSize;
+        AxisScale scale;
+        ScalarMap myMap;
+
+        /**
+         *  Construct a ScaleControlPanel for the AxisScale associated
+         *  with the specified ScalarMap.
+         *  @param  map  map with the scale.
+         */
+        ScaleControlPanel(ScalarMap map)
+        {
+            myMap = map;
+            scale = map.getAxisScale();
+            if (scale == null) return;
+            setLayout(new GridLayout(0,2));
+            String scalarName = map.getScalarName();
+            setBorder(
+                BorderFactory.createTitledBorder( 
+                    scalarName + " AxisScale Control"));
+
+            // Title control
+            JLabel label = new JLabel("Title: ");
+            add(label);
+            JTextField nameInput = new JTextField(scalarName);
+            nameInput.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setTitle(((JTextField)e.getSource()).getText());
+                  //myMap.setScalarName(((JTextField)e.getSource()).getText());
+                }
+            });
+            add(nameInput);
+
+            // Font/size control
+            label = new JLabel("Title/Label Size:");
+            add(label);
+            labelSize = new JSlider(0, 48, 12);
+            labelSize.setPaintTicks(true);
+            //labelSize.setSnapToTicks(true);
+            labelSize.setMinorTickSpacing(2);
+            labelSize.setMajorTickSpacing(10);
+            labelSize.setPaintLabels(true);
+            labelSize.setExtent(2);
+            labelSize.addChangeListener(new ChangeListener() {
+              public void stateChanged(ChangeEvent e) {
+                 JSlider slider = (JSlider) e.getSource();
+                 if (!slider.getValueIsAdjusting()) {
+                     scale.setLabelSize(slider.getValue());
+                 }
+              }
+            });
+            add(labelSize);
+            label = new JLabel("Label Font:");
+            add(label);
+            FontSelector fontSelector = new FontSelector();
+            fontSelector.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e)
+                {
+                    String fontName = 
+                      (String) ((FontSelector)e.getSource()).getSelectedItem();
+
+                    if (fontName.startsWith("Hershey")) {
+                      HersheyFont font = new HersheyFont( 
+                          fontName.substring(fontName.indexOf(".")+1) );
+                      scale.setFont(font);
+                      labelSize.setValue(12);  // HersheyFonts are 12 point
+
+                    } else {
+                      Font font = Font.decode(fontName);
+                      int fSize=labelSize.getValue();
+
+                      // this will derive a new font based on the old 
+                      // labelSize.value this leaves the Slider in the 
+                      //current font size
+                      font = font.deriveFont((float) fSize);
+                      scale.setFont(font);
+                      labelSize.setValue(font.getSize());
+                    }
+
+                }
+            });
+            add(fontSelector);
+
+            //  Color selector
+            JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
+            p.add(new JLabel("Color: "));
+            JPanel q = new JPanel();
+            JButton color = new JButton("");
+            color.setSize(16,16);
+            color.setBackground(scale.getColor());
+            color.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    JButton button = (JButton) e.getSource();
+                    Color newColor =
+                        JColorChooser.showDialog(
+                            ScaleControlPanel.this,
+                            "Set AxisScale Color",
+                            button.getBackground());
+                    if (newColor != null)
+                    {
+                        scale.setColor(newColor);
+                        button.setBackground(newColor);
+                    }
+                }
+            });
+            q.add(color);
+            p.add(q);
+            add(p);
+            // Snap to box
+            p = new JPanel(new FlowLayout(FlowLayout.LEFT));
+            JCheckBox snapToBox = 
+                new JCheckBox("Snap to Box", scale.getSnapToBox());
+            snapToBox.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setSnapToBox(((JCheckBox)e.getSource()).isSelected());
+                }
+            });
+            p.add(snapToBox);
+
+            // Visibility
+            JCheckBox visible = 
+                new JCheckBox("Visible", scale.isVisible());
+            visible.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setVisible(((JCheckBox)e.getSource()).isSelected());
+                }
+            });
+            p.add(visible);
+            add(p);
+
+            // Visibility
+            JCheckBox labelAll = 
+                new JCheckBox("Label All", scale.getLabelAllTicks());
+            labelAll.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setLabelAllTicks(((JCheckBox)e.getSource()).isSelected());
+                }
+            });
+            p.add(labelAll);
+            add(p);
+
+            // Side control
+            p = new JPanel(new FlowLayout(FlowLayout.LEFT));
+            p.add(new JLabel("Axis side: "));
+            JRadioButton primary = 
+              new JRadioButton("Primary", (scale.getSide() == scale.PRIMARY));
+            primary.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setSide(scale.PRIMARY);
+                }
+            });
+            p.add(primary);
+            JRadioButton secondary = 
+              new JRadioButton("Secondary", 
+                                (scale.getSide() == scale.SECONDARY));
+            secondary.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setSide(scale.SECONDARY);
+                }
+            });
+            p.add(secondary);
+            ButtonGroup group = new ButtonGroup();
+            group.add(primary);
+            group.add(secondary);
+            add(p);
+
+            // Tick orientation
+            p = new JPanel(new FlowLayout(FlowLayout.LEFT));
+            p.add(new JLabel("Tick orient: "));
+            JRadioButton prime = new JRadioButton("Primary", true);
+            prime.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setTickOrientation(scale.PRIMARY);
+                }
+            });
+            p.add(prime);
+            JRadioButton second = new JRadioButton("Secondary");
+            second.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    scale.setTickOrientation(scale.SECONDARY);
+                }
+            });
+            p.add(second);
+            ButtonGroup group2 = new ButtonGroup();
+            group2.add(prime);
+            group2.add(second);
+            add(p);
+        }
+    }
+}
diff --git a/examples/Simple.java b/examples/Simple.java
new file mode 100644
index 0000000..41991a0
--- /dev/null
+++ b/examples/Simple.java
@@ -0,0 +1,129 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// import needed classes
+import visad.*;
+import visad.java3d.DisplayImplJ3D;
+import visad.util.VisADSlider;
+import visad.data.netcdf.Plain;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class Simple {
+
+  // type 'java Simple' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    // create a DataReference for an image
+    final DataReference image_ref = new DataReferenceImpl("image");
+
+    // create a netCDF reader
+    Plain plain = new Plain();
+
+    // open a netCDF file containing an image sequence and adapt
+    // it to a Field Data object
+    Field imagesNC = null;
+    try {
+      imagesNC = (Field) plain.open("images.nc");
+    }
+    catch (IOException exc) {
+      String s = "To run this example, the images.nc file must be "
+        +"present in\nyour visad/examples directory."
+        +"You can obtain this file from:\n"
+        +"  ftp://ftp.ssec.wisc.edu/pub/visad-2.0/images.nc.Z";
+      System.out.println(s);
+      System.exit(0);
+    }
+    final Field image_sequence = imagesNC;
+
+    // create a Display using Java3D
+    DisplayImpl display = new DisplayImplJ3D("image display");
+
+    // extract the type of image and use
+    // it to determine how images are displayed
+    FunctionType image_sequence_type =
+      (FunctionType) image_sequence.getType();
+    FunctionType image_type =
+      (FunctionType) image_sequence_type.getRange();
+    RealTupleType domain_type = image_type.getDomain();
+    // map image coordinates to display coordinates
+    display.addMap(new ScalarMap((RealType) domain_type.getComponent(0),
+                                 Display.XAxis));
+    display.addMap(new ScalarMap((RealType) domain_type.getComponent(1),
+                                 Display.YAxis));
+    // map image brightness values to RGB (default is grey scale)
+    display.addMap(new ScalarMap((RealType) image_type.getRange(),
+                                 Display.RGB));
+
+    // link the Display to image_ref
+    // display will update whenever image changes
+    display.addReference(image_ref);
+
+    // create a DataReference and RealType for an 'hour' value
+    final DataReference hour_ref = new DataReferenceImpl("hour");
+    RealType hour_type =
+      (RealType) image_sequence_type.getDomain().getComponent(0);
+    // and link it to a slider
+    VisADSlider slider = new VisADSlider("hour", 0, 3, 0, 1.0,
+                                         hour_ref, hour_type);
+
+    // create a Cell to extract an image at 'hour'
+    // (this is an anonymous inner class extending CellImpl)
+    Cell cell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        // extract image from sequence by evaluating image_sequence
+        // Field at 'hour' value
+        image_ref.setData(image_sequence.evaluate(
+                                       (Real) hour_ref.getData()));
+      }
+    };
+    // link cell to hour_ref to trigger doAction whenever
+    // 'hour' value changes
+    cell.addReference(hour_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("Simple VisAD Application");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add slider and display to JPanel
+    panel.add(slider);
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 600);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/examples/SimpleAnimate.java b/examples/SimpleAnimate.java
new file mode 100644
index 0000000..5f25d55
--- /dev/null
+++ b/examples/SimpleAnimate.java
@@ -0,0 +1,125 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// import needed classes
+import visad.*;
+import visad.java2d.DisplayImplJ2D;
+import visad.data.netcdf.Plain;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+public class SimpleAnimate {
+
+  // type 'java SimpleAnimate' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    int step = 1000;
+    if (args.length > 0) {
+      try {
+        step = Integer.parseInt(args[0]);
+      }
+      catch(NumberFormatException e) {
+        step = 1000;
+      }
+    }
+    if (step < 1) step = 1;
+
+    // create a netCDF reader
+    Plain plain = new Plain();
+
+    // open a netCDF file containing an image sequence and adapt
+    // it to a Field Data object
+    Field image_sequence = null;
+    try {
+      image_sequence = (Field) plain.open("images.nc");
+    }
+    catch (IOException exc) {
+      String s = "To run this example, the images.nc file must be "
+        +"present in\nyour visad/examples directory."
+        +"You can obtain this file from:\n"
+        +"  ftp://ftp.ssec.wisc.edu/pub/visad-2.0/images.nc.Z";
+      System.out.println(s);
+      System.exit(0);
+    }
+
+    // create a DataReference for image sequence
+    final DataReference image_ref = new DataReferenceImpl("image");
+    image_ref.setData(image_sequence);
+
+    // create a Display using Java3D
+    // DisplayImpl display = new DisplayImplJ3D("image display");
+    // create a Display using Java2D
+    DisplayImpl display = new DisplayImplJ2D("image display");
+
+    // extract the type of image and use
+    // it to determine how images are displayed
+    FunctionType image_sequence_type =
+      (FunctionType) image_sequence.getType();
+    FunctionType image_type =
+      (FunctionType) image_sequence_type.getRange();
+    RealTupleType domain_type = image_type.getDomain();
+    // map image coordinates to display coordinates
+    display.addMap(new ScalarMap((RealType) domain_type.getComponent(0),
+                                 Display.XAxis));
+    display.addMap(new ScalarMap((RealType) domain_type.getComponent(1),
+                                 Display.YAxis));
+    // map image brightness values to RGB (default is grey scale)
+    display.addMap(new ScalarMap((RealType) image_type.getRange(),
+                                 Display.RGB));
+    RealType hour_type =
+      (RealType) image_sequence_type.getDomain().getComponent(0);
+    ScalarMap animation_map = new ScalarMap(hour_type, Display.Animation);
+    display.addMap(animation_map);
+    AnimationControl animation_control =
+      (AnimationControl) animation_map.getControl();
+    animation_control.setStep(step);
+    animation_control.setOn(true);
+
+    // link the Display to image_ref
+    display.addReference(image_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("SimpleAnimate VisAD Application");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/examples/SimpleImage.java b/examples/SimpleImage.java
new file mode 100644
index 0000000..1d3019b
--- /dev/null
+++ b/examples/SimpleImage.java
@@ -0,0 +1,125 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import visad.*;
+import visad.Set;
+import visad.java3d.*;
+import visad.util.*;
+import java.io.IOException;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.awt.print.*;
+import javax.swing.*;
+import java.util.*;
+import java.io.*;
+
+/* Simple image display example.  Define a function that maps from
+ * (line,element) to a brightness value.  Create a FlatField that
+ * realizes this mapping for a domain of (300,300).  Fill the FlatField
+ * with values in the range (0-255).
+ *
+ * Create ScalaMappings of (line->YAxis), (element->XAxis) and
+ * (brightness -> RGB).
+ *
+ * Also, set a range on the ScalaMap for the YAxis to illustrate the
+ * effect/use of this.
+*/
+
+public class SimpleImage {
+
+  public static void main(String args[])
+         throws VisADException, IOException {
+
+    // define types
+    RealType line = RealType.getRealType("row");
+    RealType element = RealType.getRealType("col");
+    RealTupleType domain = new RealTupleType(line, element);
+    RealType range = RealType.getRealType("brightness");
+
+    FunctionType image_func = new FunctionType(domain, range);
+
+    // now, define the Data objects
+    Set domain_set = new Integer2DSet(300,300);
+    FlatField image_data = new FlatField(image_func, domain_set);
+
+    // make up some data (line,element) => brightness values; dimensioned
+    // values[number_of_range_components][number_of_samples_in_domain_set]
+
+    double[][] values = new double[1][300*300];
+    for (int i=0; i<300; i++) {
+      for (int j=0; j<300; j++) {
+        values[0][i + 300*j] = ((16*i)/300.) * ((16*j)/300);
+      }
+    }
+
+    // put the data values into the FlatField image_data
+    image_data.setSamples(values);
+
+    // now make a reference for the data so it can be displayed
+    DataReference image_ref =new DataReferenceImpl("image");
+    image_ref.setData(image_data);
+
+    // define the mappings of the display
+    DisplayImpl di = new DisplayImplJ3D("display");
+
+    // override the default range on display's Y axis
+    ScalarMap line_map = new ScalarMap(line, Display.YAxis);
+    line_map.setRange(-100,400);
+
+    di.addMap(line_map);
+    di.addMap(new ScalarMap(element, Display.XAxis));
+    di.addMap(new ScalarMap(range, Display.RGB));
+
+    // add the data reference
+    di.addReference(image_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    // (cobbled from the DisplayTest examples)
+
+    JFrame frame = new JFrame("Simple VisAD Application");
+
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    frame.getContentPane().add(panel);
+
+    panel.add(di.getComponent());
+
+    // add a button for printing the screen
+    JButton butt = new JButton("Print me");
+    butt.addActionListener(new visad.util.PrintActionListener(di));
+    panel.add(butt);
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 400);
+    frame.setVisible(true);
+  }
+
+}
diff --git a/examples/SimpleMcIDAS.java b/examples/SimpleMcIDAS.java
new file mode 100644
index 0000000..87e7418
--- /dev/null
+++ b/examples/SimpleMcIDAS.java
@@ -0,0 +1,110 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.util.*;
+import visad.data.mcidas.*;
+import visad.java3d.*;
+import visad.bom.*;
+
+public class SimpleMcIDAS {
+
+  // run 'java SimpleMcIDAS AREA2001 OUTLSUPW'
+  public static void main (String[] args)
+         throws VisADException, RemoteException, IOException {
+
+    // construct a 3-D display
+    DisplayImpl display = new DisplayImplJ3D("display");
+
+    // read McIDAS AREA file
+    AreaAdapter areaAdapter = new AreaAdapter(args[0]);
+    Data image = areaAdapter.getData();
+
+    // get type of image radiance
+    FunctionType imageFunctionType = (FunctionType) image.getType();
+    RealType radianceType = (RealType)
+      ((RealTupleType) imageFunctionType.getRange()).getComponent(0);
+
+    // define display coordinates
+    display.addMap(new ScalarMap(RealType.Latitude, Display.Latitude));
+    display.addMap(new ScalarMap(RealType.Longitude, Display.Longitude));
+    ScalarMap rgbMap = new ScalarMap(radianceType, Display.RGB);
+    display.addMap(rgbMap);
+
+    // read McIDAS map file
+    BaseMapAdapter baseMapAdapter = new BaseMapAdapter(args[1]);
+    Data map = baseMapAdapter.getData();
+
+    // link map to display
+    DataReference maplinesRef = new DataReferenceImpl("MapLines");
+    maplinesRef.setData(map);
+    ConstantMap[] maplinesConstantMap = new ConstantMap[]
+      {new ConstantMap(1.002, Display.Radius),
+       new ConstantMap(0.0, Display.Blue)};
+    display.addReference(maplinesRef, maplinesConstantMap);
+
+    // link image to display
+    DataReferenceImpl imageRef = new DataReferenceImpl("ImageRef");
+    imageRef.setData(image);
+    display.addReferences(new ImageRendererJ3D(), imageRef);
+
+    // create frame (window) on screen
+    JFrame frame = new JFrame("Satellite Display");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+
+    // add 3-D display to panel in frame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.add(display.getComponent());
+
+    // construct color widget for image radiances
+    LabeledColorWidget lcw =
+      new LabeledColorWidget(new ColorMapWidget(rgbMap, false));
+
+    // add color widget to panel in frame
+    JPanel sub_panel = new JPanel();
+    sub_panel.setMaximumSize(new Dimension(500, 150));
+    sub_panel.setBorder(new CompoundBorder(new EtchedBorder(),
+                        new EmptyBorder(5, 5, 5, 5)));
+    sub_panel.add(lcw);
+    panel.add(sub_panel);
+
+    // finish off frame
+    frame.getContentPane().add(panel);
+    frame.setSize(500, 700);
+    frame.setVisible(true);
+
+    // and now exit - leaving display, color widget,
+    // map and image to take care of themselves
+  }
+}
+
diff --git a/examples/SocketDataTest.java b/examples/SocketDataTest.java
new file mode 100644
index 0000000..95607aa
--- /dev/null
+++ b/examples/SocketDataTest.java
@@ -0,0 +1,232 @@
+//
+// SocketDataTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.event.*;
+import java.io.IOException;
+import java.rmi.*;
+import javax.swing.*;
+import visad.*;
+import visad.data.*;
+import visad.java3d.DisplayImplJ3D;
+import visad.util.Util;
+
+/** A simple test for collaboration using a socket data server */
+public class SocketDataTest extends JFrame implements ActionListener {
+
+  /** true if server, false if client */
+  private boolean server;
+
+  /** data reference pointing to the data */
+  private DataReferenceImpl ref;
+
+  /** display that shows the data */
+  private DisplayImpl disp;
+
+  /** dialog for loading data files */
+  private JFileChooser dialog;
+
+  /** builds the GUI */
+  private void constructGUI(String arg, boolean enableButtons) {
+    // construct JFileChooser dialog for later use
+    dialog = Util.getVisADFileChooser();
+
+    // construct main window
+    JPanel pane = new JPanel();
+    setContentPane(pane);
+    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+    pane.add(disp.getComponent());
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
+    JButton load = new JButton("Load data");
+    JButton reset = new JButton("Reset to default");
+    load.addActionListener(this);
+    load.setActionCommand("load");
+    load.setEnabled(enableButtons);
+    reset.addActionListener(this);
+    reset.setActionCommand("reset");
+    reset.setEnabled(enableButtons);
+    buttons.add(load);
+    buttons.add(reset);
+    pane.add(buttons);
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) { System.exit(0); }
+    });
+    setTitle("SocketDataTest " + (server ? "server" : "client") + ": " + arg);
+    pack();
+    setVisible(true);
+  }
+
+  /** usage message */
+  private static final String usage =
+    "Usage: java SocketDataTest [-s port] [-c ip.address:port]";
+
+  /** runs the test */
+  public static void main(String[] argv)
+    throws VisADException, RemoteException, IOException
+  {
+    if (argv.length < 2) {
+      System.err.println("Not enough arguments.");
+      System.err.println(usage);
+      System.exit(1);
+    }
+    String sc = argv[0];
+    String arg = argv[1];
+    boolean serv = false;
+    if (sc.equalsIgnoreCase("-s")) serv = true;
+    else if (!sc.equalsIgnoreCase("-c")) {
+      System.err.println("Please specify either -s or -c");
+      System.err.println(usage);
+      System.exit(2);
+    }
+    SocketDataTest test = new SocketDataTest(serv, arg);
+  }
+
+  /** constructs a new SocketDataTest display */
+  public SocketDataTest(boolean serv, String arg)
+    throws VisADException, RemoteException, IOException
+  {
+    server = serv;
+
+    // construct 3-D display
+    disp = new DisplayImplJ3D("disp");
+
+    if (server) {
+      // determine socket port
+      int port = -1;
+      try {
+        port = Integer.parseInt(arg);
+      }
+      catch (NumberFormatException exc) { }
+      if (port < 0 || port > 9999) {
+        System.err.println("Invalid port: " + arg);
+        System.exit(3);
+      }
+
+      // construct socket data server at given port
+      ref = new DataReferenceImpl("ref");
+      disp.addReference(ref);
+      SocketDataServer server = new SocketDataServer(port, ref);
+      loadData(null);
+
+      // construct GUI
+      constructGUI(arg, true);
+    }
+    else {
+      // open data from socket source
+      SocketDataSource source = new SocketDataSource("SocketDataTest");
+      source.open(arg);
+      ref = source.getReference();
+      disp.addReference(ref);
+      CellImpl mapsCell = new CellImpl() {
+        public synchronized void doAction()
+          throws VisADException, RemoteException
+        {
+          // auto-detect maps when source data changes
+          Data data = ref.getData();
+          if (data != null) setMaps(data);
+        }
+      };  
+      mapsCell.addReference(ref);
+
+      // construct GUI
+      constructGUI(arg, false);
+    }
+  }
+
+  /** loads a data set from the given file, or reverts back to the default */
+  private void loadData(String file)
+    throws VisADException, RemoteException
+  {
+    Data data = null;
+
+    if (file == null) {
+      // revert to default data set
+      int size = 64;
+      RealType ir_radiance = RealType.getRealType("ir_radiance");
+      RealType vis_radiance = RealType.getRealType("vis_radiance");
+      RealType[] types = {RealType.Latitude, RealType.Longitude};
+      RealType[] types2 = {vis_radiance, ir_radiance};
+      RealTupleType earth_location = new RealTupleType(types);
+      RealTupleType radiance = new RealTupleType(types2);
+      FunctionType image_tuple = new FunctionType(earth_location, radiance);
+      data = FlatField.makeField(image_tuple, size, false);
+    }
+    else {
+      // load data set from the given file
+      DefaultFamily loader = new DefaultFamily("loader");
+      try {
+        data = loader.open(file);
+      }
+      catch (BadFormException exc) {
+        throw new VisADException(exc.getMessage());
+      }
+    }
+
+    // set up mappings and data reference
+    if (data != null) {
+      setMaps(data);
+      ref.setData(data);
+    }
+  }
+
+  /** sets the current data sets mappings */
+  private void setMaps(Data data) throws VisADException, RemoteException {
+    // guess good mappings
+    ScalarMap[] maps = data.getType().guessMaps(true);
+
+    // set the mappings
+    disp.removeReference(ref);
+    disp.clearMaps();
+    for (int i=0; i<maps.length; i++) disp.addMap(maps[i]);
+    disp.addReference(ref);
+  }
+
+  /** handles button clicks */
+  public synchronized void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    try {
+      if (cmd.equals("load")) {
+        // load new data set
+        int returnVal = dialog.showOpenDialog(this);
+        if (returnVal == JFileChooser.APPROVE_OPTION) {
+          loadData(dialog.getSelectedFile().getAbsolutePath());
+        }
+      }
+      else if (cmd.equals("reset")) {
+        // reset to default data set
+        loadData(null);
+      }
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      exc.printStackTrace();
+    }
+  }
+
+}
+
diff --git a/examples/Stream2DTest.java b/examples/Stream2DTest.java
new file mode 100644
index 0000000..b796cc7
--- /dev/null
+++ b/examples/Stream2DTest.java
@@ -0,0 +1,151 @@
+//
+// Stream2DTest.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import visad.*;
+import visad.java3d.DisplayImplJ3D;
+import java.rmi.RemoteException;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+public class Stream2DTest {
+
+public static void main(String[] args)
+       throws VisADException, RemoteException
+{
+  int nr = 50;
+  int nc = 50;
+
+  DisplayImpl dpy = new DisplayImplJ3D("display");
+
+  RealType u_wind = RealType.getRealType("u_wind");
+  RealType v_wind = RealType.getRealType("v_wind");
+
+  RealTupleType uv = new RealTupleType(u_wind, v_wind);
+
+
+  FunctionType f_type =
+    new FunctionType(RealTupleType.SpatialCartesian2DTuple, uv);
+
+  Integer2DSet d_set =
+    new Integer2DSet(RealTupleType.SpatialCartesian2DTuple, nr, nc);
+
+  FlatField uv_field =
+    new FlatField(f_type, d_set);
+
+  float[][] uv_values = new float[2][nr*nc];
+  
+  double ang = 2*Math.PI/nr;
+
+  for ( int jj = 0; jj < nc; jj++ ) {
+    for ( int ii = 0; ii < nr; ii++ ) {
+      int idx = jj*nr + ii;
+      uv_values[0][idx] = 5 + -20f*((float) Math.cos(0.5*ang*ii));
+      uv_values[1][idx] = -10f*((float) Math.cos(0.5*ang*ii));
+    }
+  }
+
+  uv_field.setSamples(uv_values, false);
+                                      
+
+  int[] numl = new int[1];
+  int maxv = 1000;
+  int max_lines = 100;
+  int[][] n_verts = new int[1][];
+  float[][][] vr = new float[1][max_lines][maxv];
+  float[][][] vc = new float[1][max_lines][maxv];
+
+
+  Stream2D.stream(uv_values[0], uv_values[1], nr, nc, 1f, 1, 1f,
+                  vr, vc, n_verts, numl, d_set, 1f, 3f, 0, 1f);
+
+  
+  ScalarMap xmap = new ScalarMap(RealType.XAxis, Display.XAxis);
+  ScalarMap ymap = new ScalarMap(RealType.YAxis, Display.YAxis);
+  dpy.addMap(xmap);
+  dpy.addMap(ymap);
+
+  ScalarMap flowx = new ScalarMap(u_wind, Display.Flow1X);
+  ScalarMap flowy = new ScalarMap(v_wind, Display.Flow1Y);
+  dpy.addMap(flowx);
+  dpy.addMap(flowy);
+
+  FlowControl flow_cntrl = (FlowControl) flowx.getControl();
+  flow_cntrl.setFlowScale(0.04f);
+
+  flow_cntrl = (FlowControl) flowy.getControl();
+  flow_cntrl.setFlowScale(0.04f);
+
+  DataReferenceImpl ref = new DataReferenceImpl("wind");
+  ref.setData(uv_field);
+
+  dpy.addReference(ref);
+
+
+  Gridded2DSet[] gsets = new Gridded2DSet[numl[0]];
+  for ( int s_idx = 0; s_idx < numl[0]; s_idx++ ) {
+    float[][] strm_values = new float[2][n_verts[0][s_idx]];
+    System.arraycopy(vc[0][s_idx], 0, strm_values[0], 0, n_verts[0][s_idx]);
+    System.arraycopy(vr[0][s_idx], 0, strm_values[1], 0, n_verts[0][s_idx]);
+
+    gsets[s_idx] =
+      new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, strm_values, n_verts[0][s_idx]);
+  }
+
+  UnionSet uset = new UnionSet(gsets);
+  DataReferenceImpl strm_ref = new DataReferenceImpl("stream");
+  strm_ref.setData(uset);
+
+  ConstantMap[] strm_cm =
+    new ConstantMap[]
+  {
+    new ConstantMap(0.1, Display.Red),
+    new ConstantMap(0.8, Display.Green),
+    new ConstantMap(0.1, Display.Blue),
+    new ConstantMap(1.5, Display.LineWidth)
+  };
+
+  dpy.addReference(strm_ref, strm_cm);
+
+
+  JFrame jframe  = new JFrame();
+  jframe.addWindowListener(new WindowAdapter() {
+    public void windowClosing(WindowEvent e) {System.exit(0);}
+  });
+
+  jframe.setContentPane((JPanel) dpy.getComponent());
+  jframe.setSize(500, 500);
+  jframe.setVisible(true);
+}
+
+}
+
diff --git a/examples/Test00.java b/examples/Test00.java
new file mode 100644
index 0000000..acf3201
--- /dev/null
+++ b/examples/Test00.java
@@ -0,0 +1,153 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test00
+  extends UISkeleton
+{
+  public Test00() { }
+
+  public Test00(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ3D("display1");
+    dpys[1] = new DisplayImplJ3D("display2");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    GraphicsModeControl mode;
+
+    final RealType ir_radiance =
+      RealType.getRealType("ir_radiance", CommonUnit.degree);
+    Unit cycles = CommonUnit.dimensionless.divide(CommonUnit.second);
+    Unit hz = cycles.clone("Hz");
+    final RealType count = RealType.getRealType("count", hz);
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    final RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+    Real direct = new Real(ir_radiance, 2.0);
+    Real[] reals3 = {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                     new Real(vis_radiance, 1.0)};
+    RealTuple direct_tuple = new RealTuple(reals3);
+
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    ScalarMap irmap = new ScalarMap(ir_radiance, Display.XAxis);
+    dpys[0].addMap(irmap);
+    irmap.setOverrideUnit(CommonUnit.radian);
+    dpys[0].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(count, Display.Green));
+
+    mode = dpys[0].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+    mode.setPointMode(false);
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl ref_direct = new DataReferenceImpl("ref_direct");
+    ref_direct.setData(direct);
+    DataReference[] refs1 = {ref_direct};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refs1, null);
+
+    DataReferenceImpl ref_direct_tuple =
+      new DataReferenceImpl("ref_direct_tuple");
+    ref_direct_tuple.setData(direct_tuple);
+    DataReference[] refs2 = {ref_direct_tuple};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refs2, null);
+
+    DataReferenceImpl ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    DataReference[] refs3 = {ref_histogram1};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refs3, null);
+
+    dpys[1].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[1].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.Green));
+    final DisplayRenderer dr0 = dpys[0].getDisplayRenderer();
+    final DisplayRenderer dr1 = dpys[1].getDisplayRenderer();
+    dr0.setCursorStringOn(true);
+    dr1.setCursorStringOn(false);
+
+    mode = dpys[1].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+    mode.setPointMode(false);
+    mode.setScaleEnable(true);
+
+    dpys[1].addReferences(new DirectManipulationRendererJ3D(), refs1, null);
+    dpys[1].addReferences(new DirectManipulationRendererJ3D(), refs2, null);
+    dpys[1].addReferences(new DirectManipulationRendererJ3D(), refs3, null);
+
+    MouseHelper helper = dr1.getMouseBehavior().getMouseHelper();
+    helper.setFunctionMap(new int[][][]
+      {{{MouseHelper.DIRECT, MouseHelper.DIRECT},
+        {MouseHelper.DIRECT, MouseHelper.DIRECT}},
+       {{MouseHelper.ROTATE, MouseHelper.NONE},
+        {MouseHelper.NONE, MouseHelper.NONE}},
+       {{MouseHelper.NONE, MouseHelper.NONE},
+        {MouseHelper.NONE, MouseHelper.NONE}}});
+
+    CellImpl cell = new CellImpl() {
+      public void doAction() throws RemoteException, VisADException {
+        double vir = dr1.getDirectAxisValue(ir_radiance);
+        double vvis = dr1.getDirectAxisValue(vis_radiance);
+        double vc = dr1.getDirectAxisValue(count);
+        //System.out.println("ir_radiance = " + vir + " count = " + vc + " vis_radiance = " + vvis);
+
+        java.util.Vector csv = dr1.getCursorStringVectorUnconditional();
+        for (int i=0; i<csv.size(); i++) {
+          System.out.println((String) csv.elementAt(i));
+        }
+      }
+    };
+    cell.addReference(ref_direct);
+    cell.addReference(ref_direct_tuple);
+    cell.addReference(ref_histogram1);
+
+  }
+
+  String getFrameTitle() { return "Java3D direct manipulation"; }
+
+  public String toString() { return ": direct manipulation and Mouse options"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test00(args);
+  }
+}
diff --git a/examples/Test01.java b/examples/Test01.java
new file mode 100644
index 0000000..cf77629
--- /dev/null
+++ b/examples/Test01.java
@@ -0,0 +1,132 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// JFC packages
+import javax.swing.*;
+
+// AWT packages
+import java.awt.*;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.ContourWidget;
+import visad.util.LabeledColorWidget;
+
+public class Test01
+  extends UISkeleton
+{
+  ScalarMap map1color = null;
+  ScalarMap map1contour = null;
+  static int size3d = 6;
+
+  public Test01() { }
+
+  public Test01(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { size3d = 6; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    try {
+      size3d = Integer.parseInt(args[0]);
+      if (size3d < 1) size3d = 6;
+    }
+    catch(NumberFormatException e) {
+      size3d = 6;
+    }
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types3d = {RealType.Latitude, RealType.Longitude, RealType.Radius};
+    RealTupleType earth_location3d = new RealTupleType(types3d);
+    RealType vis_radiance = RealType.getRealType("vis_radiance", CommonUnit.degree);
+    RealType ir_radiance = RealType.getRealType("ir_radiance", CommonUnit.degree);
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType grid_tuple = new FunctionType(earth_location3d, radiance);
+
+    FlatField grid3d = FlatField.makeField(grid_tuple, size3d, false);
+
+    ScalarMap lat_map = new ScalarMap(RealType.Latitude, Display.YAxis);
+    dpys[0].addMap(lat_map);
+    // WLH 31 Aug 2000
+    lat_map.setOverrideUnit(CommonUnit.radian);
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Radius, Display.ZAxis));
+    map1color = new ScalarMap(ir_radiance, Display.RGB);
+    dpys[0].addMap(map1color);
+    map1color.setOverrideUnit(CommonUnit.radian);
+    map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+    map1contour.setOverrideUnit(CommonUnit.radian);
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl ref_grid3d = new DataReferenceImpl("ref_grid3d");
+    ref_grid3d.setData(grid3d);
+    dpys[0].addReference(ref_grid3d, null);
+  }
+
+  String getFrameTitle() { return "VisAD iso-level controls"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    panel.add(new ContourWidget(map1contour));
+    panel.add(new LabeledColorWidget(map1color));
+    return panel;
+  }
+
+  public String toString()
+  {
+    return ": colored iso-surfaces from regular grids and ContourWidget";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test01(args);
+  }
+}
diff --git a/examples/Test02.java b/examples/Test02.java
new file mode 100644
index 0000000..47f32ca
--- /dev/null
+++ b/examples/Test02.java
@@ -0,0 +1,136 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// JFC packages
+import javax.swing.*;
+
+// AWT packages
+import java.awt.*;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.ContourWidget;
+import visad.util.LabeledColorWidget;
+
+public class Test02
+  extends UISkeleton
+{
+  ScalarMap map1color = null;
+  ScalarMap map1contour = null;
+  int size3d;
+
+  public Test02() { }
+
+  public Test02(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { size3d = 6; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    try {
+      size3d = Integer.parseInt(args[0]);
+      if (size3d < 1) size3d = 6;
+    }
+    catch(NumberFormatException e) {
+      size3d = 6;
+    }
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types3d = {RealType.Latitude, RealType.Longitude, RealType.Radius};
+    RealTupleType earth_location3d = new RealTupleType(types3d);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType grid_tuple = new FunctionType(earth_location3d, radiance);
+
+    float level = 2.5f;
+    FlatField grid3d = FlatField.makeField(grid_tuple, size3d, true);
+
+    if ((size3d % 2) != 0) {
+      double last = size3d - 1.0;
+      Linear3DSet set =
+        new Linear3DSet(earth_location3d, 0.0, last, size3d,
+                                          0.0, last, size3d,
+                                          0.0, last, size3d);
+      grid3d = (FlatField)
+        grid3d.resample(set, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+        // grid3d.resample(set, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+    }
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Radius, Display.ZAxis));
+    map1color = new ScalarMap(ir_radiance, Display.RGB);
+    dpys[0].addMap(map1color);
+    map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+
+    DataReferenceImpl ref_grid3d = new DataReferenceImpl("ref_grid3d");
+    ref_grid3d.setData(grid3d);
+    dpys[0].addReference(ref_grid3d, null);
+  }
+
+  String getFrameTitle() { return "VisAD irregular iso-level controls"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    panel.add(new ContourWidget(map1contour));
+    panel.add(new LabeledColorWidget(map1color));
+    return panel;
+  }
+
+  public String toString()
+  {
+    return ": colored iso-surfaces from irregular grids and ContourWidget";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test02(args);
+  }
+}
diff --git a/examples/Test03.java b/examples/Test03.java
new file mode 100644
index 0000000..9061aaa
--- /dev/null
+++ b/examples/Test03.java
@@ -0,0 +1,145 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.AnimationWidget;
+
+public class Test03
+  extends UISkeleton
+{
+  public Test03() { }
+
+  public Test03(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] time = {RealType.Time};
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+    RealType[] types4 = {ir_radiance, vis_radiance};
+    RealTupleType ecnaidar = new RealTupleType(types4);
+    FunctionType image_bumble = new FunctionType(earth_location, ecnaidar);
+    RealTupleType time_type = new RealTupleType(time);
+    FunctionType time_images = new FunctionType(time_type, image_tuple);
+    FunctionType time_bee = new FunctionType(time_type, image_bumble);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+    FlatField wasp = FlatField.makeField(image_bumble, size, false);
+
+    int ntimes1 = 4;
+    int ntimes2 = 6;
+
+    // different time resolution test
+    // 2 May 99, 15:51:00
+    DateTime base = new DateTime(1999, 122, 57060);
+    double start = base.getValue();
+    Set time_set =
+      new Linear1DSet(time_type, start, start + 3000.0, ntimes1);
+    Unit v5d_time_unit = new OffsetUnit(
+                             visad.data.units.UnitParser.encodeTimestamp(
+                                1900, 1, 1, 0, 0, 0, 0), SI.second);
+    start = base.getValue(v5d_time_unit);
+    double[][] times =
+      {{start, start + 600.0, start + 1200.0,
+        start + 1800.0, start + 2400.0, start + 3000.0}};
+    Set time_hornet = new Gridded1DDoubleSet(time_type, times, 6,
+                                   null, new Unit[] {v5d_time_unit}, null);
+
+    FieldImpl image_sequence = new FieldImpl(time_images, time_set);
+    FieldImpl image_stinger = new FieldImpl(time_bee, time_hornet);
+    FlatField temp = imaget1;
+    FlatField tempw = wasp;
+    Real[] reals = {new Real(vis_radiance, (float) size / 4.0f),
+                    new Real(ir_radiance, (float) size / 8.0f)};
+    RealTuple val = new RealTuple(reals);
+    for (int i=0; i<ntimes1; i++) {
+      image_sequence.setSample(i, temp);
+      temp = (FlatField) temp.add(val);
+    }
+    for (int i=0; i<ntimes2; i++) {
+      image_stinger.setSample(i, tempw);
+      tempw = (FlatField) tempw.add(val);
+    }
+    FieldImpl[] images = {image_sequence, image_stinger};
+    Tuple big_tuple = new Tuple(images);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1animation =
+      new ScalarMap(RealType.Time, Display.Animation);
+    dpys[0].addMap(map1animation);
+
+    DataReferenceImpl ref_big_tuple =
+      new DataReferenceImpl("ref_big_tuple");
+    ref_big_tuple.setData(big_tuple);
+    dpys[0].addReference(ref_big_tuple, null);
+  }
+
+  String getFrameTitle() { return "VisAD animation controls"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap map1animation = (ScalarMap )dpys[0].getMapVector().lastElement();
+    return new AnimationWidget(map1animation, 3000);
+  }
+
+  public String toString()
+  {
+    return ": Animation different time resolutions and AnimationWidget";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test03(args);
+  }
+}
diff --git a/examples/Test04.java b/examples/Test04.java
new file mode 100644
index 0000000..a2841f6
--- /dev/null
+++ b/examples/Test04.java
@@ -0,0 +1,82 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test04
+  extends TestSkeleton
+{
+  public Test04() { }
+
+  public Test04(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Latitude));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Longitude));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.RGB));
+    // dpys[0].addMap(new ScalarMap(vis_radiance, Display.Radius));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setTextureEnable(false);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": spherical coordinates"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test04(args);
+  }
+}
diff --git a/examples/Test05.java b/examples/Test05.java
new file mode 100644
index 0000000..877bb56
--- /dev/null
+++ b/examples/Test05.java
@@ -0,0 +1,148 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.BorderLayout;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.ContourWidget;
+
+public class Test05
+  extends UISkeleton
+{
+  private boolean uneven;
+
+  public Test05() { }
+
+  public Test05(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { uneven = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    uneven = true;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.RGB));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.ZAxis));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1contour;
+    map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+    if (uneven) {
+      ContourControl control = (ContourControl) map1contour.getControl();
+      float[] levs = {10.0f, 12.0f, 14.0f, 16.0f, 24.0f, 32.0f, 40.0f};
+      control.setLevels(levs, 15.0f, true);
+      control.enableLabels(true);
+    }
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  private String getFrameTitle0() { return "regular contours in Java3D"; }
+
+  private String getFrameTitle1() { return "VisAD contour controls"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle0() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) dpys[0].getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+
+    if (!uneven) {
+      ScalarMap map1contour = (ScalarMap )dpys[0].getMapVector().lastElement();
+      ContourWidget cw = new ContourWidget(map1contour);
+
+      JPanel big_panel = new JPanel();
+      big_panel.setLayout(new BorderLayout());
+      big_panel.add("Center", cw);
+
+      JFrame jframe2  = new JFrame(getFrameTitle1());
+      jframe2.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+
+      jframe2.setContentPane(big_panel);
+      jframe2.pack();
+      jframe2.setVisible(true);
+    }
+  }
+
+  public String toString()
+  {
+    return " uneven: colored 2-D contours from regular grids and ContourWidget";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test05(args);
+  }
+}
diff --git a/examples/Test06.java b/examples/Test06.java
new file mode 100644
index 0000000..9d7b0ae
--- /dev/null
+++ b/examples/Test06.java
@@ -0,0 +1,127 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test06
+  extends UISkeleton
+{
+  private boolean uneven;
+
+  public Test06() { }
+
+  public Test06(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { uneven = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    uneven = true;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, true);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.ZAxis));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1contour;
+    map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+    ContourControl control1contour =
+      (ContourControl) map1contour.getControl();
+    control1contour.enableContours(true);
+    if (uneven) {
+      float[] levs = {10.0f, 12.0f, 14.0f, 16.0f, 24.0f, 32.0f, 40.0f};
+      control1contour.setLevels(levs, 15.0f, true);
+    }
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  private String getFrameTitle0() { return "irregular contours in Java3D"; }
+
+  private String getFrameTitle1() { return "VisAD contour controls"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle0() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) dpys[0].getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+  }
+
+  public String toString()
+  {
+    return " uneven: colored 2-D contours from irregular grids";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test06(args);
+  }
+}
diff --git a/examples/Test07.java b/examples/Test07.java
new file mode 100644
index 0000000..3695bfd
--- /dev/null
+++ b/examples/Test07.java
@@ -0,0 +1,81 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test07
+  extends TestSkeleton
+{
+  public Test07() { }
+
+  public Test07(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Green));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Alpha));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": variable transparency"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test07(args);
+  }
+}
diff --git a/examples/Test08.java b/examples/Test08.java
new file mode 100644
index 0000000..9fdfa16
--- /dev/null
+++ b/examples/Test08.java
@@ -0,0 +1,82 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test08
+  extends TestSkeleton
+{
+  public Test08() { }
+
+  public Test08(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Green));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.ZAxisOffset));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": offset"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test08(args);
+  }
+}
diff --git a/examples/Test09.java b/examples/Test09.java
new file mode 100644
index 0000000..f19aaa3
--- /dev/null
+++ b/examples/Test09.java
@@ -0,0 +1,135 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.data.gif.GIFForm;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test09
+  extends UISkeleton
+{
+  private String fileName;
+
+  public Test09() { }
+
+  public Test09(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { fileName = null; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (fileName == null) {
+      fileName = args[argc];
+    } else {
+      System.err.println(testName + ": Ignoring extra filename \"" +
+                         args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  public String keywordUsage()
+  {
+    return super.keywordUsage() + " file";
+  }
+
+  public boolean finalizeArgs(String mainName)
+  {
+    if (fileName == null) {
+      System.err.println(mainName + ": No filename specified!");
+      return false;
+    }
+
+    return true;
+  }
+
+  private DataReferenceImpl loadFile()
+    throws RemoteException, VisADException
+  {
+    GIFForm gif_form = new GIFForm();
+    FlatField img = (FlatField) gif_form.open(fileName);
+
+    DataReferenceImpl ref = new DataReferenceImpl("image");
+    ref.setData(img);
+
+    return ref;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    DataReference ref = loadFile();
+    if (ref == null) {
+      System.err.println("must specify GIF or JPEG file name");
+      System.exit(1);
+      return;
+    }
+
+    FlatField img = (FlatField )ref.getData();
+
+    // compute ScalarMaps from type components
+    FunctionType ftype = (FunctionType) img.getType();
+    RealTupleType dtype = ftype.getDomain();
+    RealTupleType rtype9 = (RealTupleType) ftype.getRange();
+    dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(0),
+                                  Display.XAxis));
+    dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(1),
+                                  Display.YAxis));
+    dpys[0].addMap(new ScalarMap((RealType) rtype9.getComponent(0),
+                                   Display.Red));
+    dpys[0].addMap(new ScalarMap((RealType) rtype9.getComponent(1),
+                                   Display.Green));
+    dpys[0].addMap(new ScalarMap((RealType) rtype9.getComponent(2),
+                                   Display.Blue));
+
+    dpys[0].addReference(ref, null);
+  }
+
+  String getFrameTitle() { return "GIF / JPEG in Java2D"; }
+
+  public String toString()
+  {
+    return " file_name: GIF / JPEG reader using Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test09(args);
+  }
+}
diff --git a/examples/Test10.java b/examples/Test10.java
new file mode 100644
index 0000000..9ade881
--- /dev/null
+++ b/examples/Test10.java
@@ -0,0 +1,186 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.data.netcdf.Plain;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test10
+  extends TestSkeleton
+{
+  private String fileName;
+
+  public Test10() { }
+
+  public Test10(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { fileName = null; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (fileName == null) {
+      fileName = args[argc];
+    } else {
+      System.err.println(testName + ": Ignoring extra filename \"" +
+                         args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  public String keywordUsage()
+  {
+    return super.keywordUsage() + " file";
+  }
+
+  public boolean finalizeArgs(String mainName)
+  {
+    if (fileName == null) {
+      System.err.println(mainName + ": No filename specified!");
+      return false;
+    }
+
+    return true;
+  }
+
+  private DataReferenceImpl loadFile()
+    throws RemoteException, VisADException
+  {
+    FieldImpl data;
+    try {
+      data = (FieldImpl )new Plain().open(fileName);
+    } catch (IOException e) {
+      System.err.println("Couldn't open \"" + fileName + "\": " +
+                         e.getMessage());
+      System.exit(1);
+      return null;
+    }
+    //System.out.println("data type = " + data.getType());
+
+    DataReferenceImpl ref = new DataReferenceImpl("netcdf");
+    ref.setData(data);
+
+    return ref;
+  }
+
+  DataReference[] getClientDataReferences()
+    throws RemoteException, VisADException
+  {
+    DataReference ref = loadFile();
+    if (ref == null) {
+      return null;
+    }
+
+    return new DataReference[] { ref };
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    Unit super_degree = CommonUnit.degree.scale(2.5);
+    RealType lon = RealType.getRealType("lon", super_degree);
+
+    DataReference ref = loadFile();
+    if (ref == null) {
+      System.err.println("must specify netCDF file name");
+      System.exit(1);
+      return;
+    }
+
+    FieldImpl netcdf_data = (FieldImpl )ref.getData();
+
+    // compute ScalarMaps from type components
+    FunctionType ftype = (FunctionType) netcdf_data.getType();
+    RealTupleType dtype = ftype.getDomain();
+    MathType rntype = ftype.getRange();
+    int n = dtype.getDimension();
+    dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(0),
+                                  Display.XAxis));
+    if (n > 1) {
+      dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(1),
+                                    Display.YAxis));
+    }
+    if (n > 2) {
+      dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(2),
+                                    Display.ZAxis));
+    }
+    if (rntype instanceof RealType) {
+      dpys[0].addMap(new ScalarMap((RealType) rntype, Display.Green));
+      if (n <= 2) {
+        dpys[0].addMap(new ScalarMap((RealType) rntype, Display.ZAxis));
+      }
+    }
+    else if (rntype instanceof RealTupleType) {
+      int m = ((RealTupleType) rntype).getDimension();
+      RealType rr = (RealType) ((RealTupleType) rntype).getComponent(0);
+      dpys[0].addMap(new ScalarMap(rr, Display.Green));
+      if (n <= 2) {
+        if (m > 1) {
+          rr = (RealType) ((RealTupleType) rntype).getComponent(1);
+        }
+        dpys[0].addMap(new ScalarMap(rr, Display.ZAxis));
+      }
+    }
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    dpys[0].addMap(new ConstantMap(0.0, Display.Blue));
+
+    dpys[0].addReference(ref, null);
+
+
+    System.out.println("now saving data as 'save.nc' and re-reading");
+
+    Plain plain = new Plain();
+    try {
+      plain.save("save.nc", netcdf_data, true);
+      netcdf_data = (FieldImpl )plain.open("save.nc");
+    } catch (IOException e) {
+      System.err.println("Couldn't open \"save.nc\": " + e.getMessage());
+      System.exit(1);
+      return;
+    }
+  }
+
+  public String toString() { return " file_name: netCDF adapter"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test10(args);
+  }
+}
diff --git a/examples/Test11.java b/examples/Test11.java
new file mode 100644
index 0000000..c7828ce
--- /dev/null
+++ b/examples/Test11.java
@@ -0,0 +1,93 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test11
+  extends TestSkeleton
+{
+  public Test11() { }
+
+  public Test11(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    Unit super_degree = CommonUnit.degree.scale(2.5);
+    RealType lon = RealType.getRealType("lon", super_degree);
+    RealType radius = RealType.getRealType("radius");
+    RealTupleType cartesian = new RealTupleType(x, y);
+    PolarCoordinateSystem polar_coord_sys =
+      new PolarCoordinateSystem(cartesian);
+    RealTupleType polar =
+      new RealTupleType(lon, radius, polar_coord_sys, null);
+
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+
+    FunctionType image_polar = new FunctionType(polar, radiance);
+    Unit[] units = {super_degree, null};
+    Linear2DSet domain_set =
+      new Linear2DSet(polar, 0.0, 60.0, 61, 0.0, 60.0, 61,
+                      polar_coord_sys, units, null);
+    FlatField imaget1 = new FlatField(image_polar, domain_set);
+    FlatField.fillField(imaget1, 1.0, 30.0);
+
+    dpys[0].addMap(new ScalarMap(x, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(y, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    dpys[0].addMap(new ConstantMap(0.0, Display.Blue));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": CoordinateSystem and Unit"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test11(args);
+  }
+}
diff --git a/examples/Test12.java b/examples/Test12.java
new file mode 100644
index 0000000..fc63520
--- /dev/null
+++ b/examples/Test12.java
@@ -0,0 +1,160 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.util.ColorMapWidget;
+import visad.util.LabeledColorWidget;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test12
+  extends UISkeleton
+{
+  boolean dynamic;
+
+  public Test12() { }
+
+  public Test12(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { dynamic = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    dynamic = true;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.RGB));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setTextureEnable(false);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    super.setupUI(dpys);
+
+    if (dynamic) {
+      ScalarMap colorMap = (ScalarMap )dpys[0].getMapVector().lastElement();
+      ColorControl control = (ColorControl) colorMap.getControl();
+
+      final int CEILING = 1024;
+
+      boolean growing = true;
+      while (true) {
+        try {
+          Thread.sleep(5000);
+        }
+        catch (InterruptedException e) {
+        }
+
+        int size;
+        while (true) {
+          if (growing) {
+            size = control.getNumberOfColors() * 2;
+          } else {
+            size = control.getNumberOfColors() / 2;
+          }
+
+          if (size > 4 && size <= CEILING) {
+            break;
+          }
+
+          growing = !growing;
+        }
+
+        System.out.println("\n" + size + " colors\n");
+
+        float[][] table = new float[3][size];
+        final float scale = 1.0f / (size - 1.0f);
+        for (int i=0; i<size; i++) {
+          table[0][i] = scale * i;
+          table[1][i] = scale * i;
+          table[2][i] = scale * i;
+        }
+        control.setTable(table);
+      }
+    }
+  }
+
+  String getFrameTitle() { return "VisAD Color Widget"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap colorMap = (ScalarMap )dpys[0].getMapVector().lastElement();
+    if (dynamic) {
+      return new LabeledColorWidget(colorMap);
+    }
+    else {
+      return new LabeledColorWidget(new ColorMapWidget(colorMap, false));
+    }
+  }
+
+  public String toString() { return ": 2-D surface and ColorWidget"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test12(args);
+  }
+}
diff --git a/examples/Test13.java b/examples/Test13.java
new file mode 100644
index 0000000..8b2e514
--- /dev/null
+++ b/examples/Test13.java
@@ -0,0 +1,94 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test13
+  extends TestSkeleton
+{
+  public Test13() { }
+
+  public Test13(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+    Real direct = new Real(ir_radiance, 2.0);
+    Real[] realsx3 = {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                      new Real(vis_radiance, 1.0)};
+    RealTuple direct_tuple = new RealTuple(realsx3);
+
+    // these ScalarMap should generate 3 Exceptions
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.RGB));
+    dpys[0].addMap(new ScalarMap(count, Display.Animation));
+
+    DataReferenceImpl ref_direct = new DataReferenceImpl("ref_direct");
+    ref_direct.setData(direct);
+    DataReference[] refsx1 = {ref_direct};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refsx1, null);
+
+    DataReferenceImpl ref_direct_tuple;
+    ref_direct_tuple = new DataReferenceImpl("ref_direct_tuple");
+    ref_direct_tuple.setData(direct_tuple);
+    DataReference[] refsx2 = {ref_direct_tuple};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refsx2, null);
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    DataReference[] refsx3 = {ref_histogram1};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refsx3, null);
+  }
+
+  public String toString() { return ": Exception display"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test13(args);
+  }
+}
diff --git a/examples/Test14.java b/examples/Test14.java
new file mode 100644
index 0000000..64c36ae
--- /dev/null
+++ b/examples/Test14.java
@@ -0,0 +1,123 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.Naming;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test14
+  extends TestSkeleton
+{
+  boolean hasClientServerMode() { return false; }
+
+  public Test14() { }
+
+  public Test14(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    try {
+
+      int size = 64;
+      FlatField histogram1;
+      histogram1 = FlatField.makeField(ir_histogram, size, false);
+      Real direct = new Real(ir_radiance, 2.0);
+      Real[] reals14 = {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                       new Real(vis_radiance, 1.0)};
+      RealTuple direct_tuple = new RealTuple(reals14);
+
+      dpys[0].addMap(new ScalarMap(vis_radiance, Display.XAxis));
+      dpys[0].addMap(new ScalarMap(ir_radiance, Display.YAxis));
+      dpys[0].addMap(new ScalarMap(count, Display.ZAxis));
+
+      GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+      mode.setPointSize(5.0f);
+      mode.setPointMode(false);
+
+      DataReferenceImpl ref_direct = new DataReferenceImpl("ref_direct");
+      ref_direct.setData(direct);
+      DataReference[] refs141 = {ref_direct};
+      dpys[0].addReferences(new DirectManipulationRendererJ3D(),
+                            refs141, null);
+
+      DataReferenceImpl ref_direct_tuple;
+      ref_direct_tuple = new DataReferenceImpl("ref_direct_tuple");
+      ref_direct_tuple.setData(direct_tuple);
+      DataReference[] refs142 = {ref_direct_tuple};
+      dpys[0].addReferences(new DirectManipulationRendererJ3D(),
+                            refs142, null);
+
+      DataReferenceImpl ref_histogram1;
+      ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+      ref_histogram1.setData(histogram1);
+      DataReference[] refs143 = {ref_histogram1};
+      dpys[0].addReferences(new DirectManipulationRendererJ3D(),
+                            refs143, null);
+
+      RemoteServerImpl obj = new RemoteServerImpl();
+      obj.addDisplay((DisplayImpl) dpys[0]);
+      Naming.rebind("///RemoteServerTest", obj);
+
+      System.out.println("RemoteServer bound in registry");
+    }
+    catch (Exception e) {
+      System.out.println("\n\nDid you run 'rmiregistry &' first?\n\n");
+      System.out.println("collaboration server exception: " + e.getMessage());
+      e.printStackTrace();
+    }
+  }
+
+  public String toString()
+  {
+    return ": collaborative direct manipulation server" +
+                "\n\trun rmiregistry first" +
+                "\n\tany number of clients may connect";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test14(args);
+  }
+}
diff --git a/examples/Test15.java b/examples/Test15.java
new file mode 100644
index 0000000..10d67f7
--- /dev/null
+++ b/examples/Test15.java
@@ -0,0 +1,106 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.Naming;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test15
+  extends TestSkeleton
+{
+  private String domain;
+  private RemoteServer remote_obj = null;
+
+  boolean hasClientServerMode() { return false; }
+
+  public Test15() { }
+
+  public Test15(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { domain = null; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (domain == null) {
+      domain = args[argc];
+    } else {
+      System.err.println(testName + ": Ignoring extra domain \"" +
+                         args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+
+    try {
+      System.out.println("RemoteClientTestImpl.main: begin remote activity");
+      System.out.println("  to " + domain);
+
+      if (domain == null) {
+        domain = "///RemoteServerTest";
+      }
+      else {
+        domain = "//" + domain + "/RemoteServerTest";
+      }
+      RemoteServer remote_obj = (RemoteServer) Naming.lookup(domain);
+
+      System.out.println("connected");
+
+      RemoteDisplay rmtDpy = remote_obj.getDisplay(0);
+      dpys[0] = new DisplayImplJ3D(rmtDpy);
+    }
+    catch (Exception e) {
+      System.out.println("collaboration client exception: " + e.getMessage());
+      e.printStackTrace(System.out);
+    }
+
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+  }
+
+  public String toString()
+  {
+    return " ip.name: collaborative direct manipulation client" +
+                "\n\tsecond parameter is server IP name";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test15(args);
+  }
+}
diff --git a/examples/Test16.java b/examples/Test16.java
new file mode 100644
index 0000000..cf624db
--- /dev/null
+++ b/examples/Test16.java
@@ -0,0 +1,99 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test16
+  extends TestSkeleton
+{
+  private int size = 47;
+
+  public Test16() { }
+
+  public Test16(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { size = 47; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    try {
+      size = Integer.parseInt(args[0]);
+      if (size < 1) size = 47;
+    }
+    catch(NumberFormatException e) {
+      size = 47;
+    }
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    // int size = 47;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setTextureEnable(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": opaque texture mapping"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test16(args);
+  }
+}
diff --git a/examples/Test17.java b/examples/Test17.java
new file mode 100644
index 0000000..b9c326f
--- /dev/null
+++ b/examples/Test17.java
@@ -0,0 +1,81 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test17
+  extends TestSkeleton
+{
+  public Test17() { }
+
+  public Test17(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.25, Display.Alpha));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": constant transparency"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test17(args);
+  }
+}
diff --git a/examples/Test18.java b/examples/Test18.java
new file mode 100644
index 0000000..a311ea7
--- /dev/null
+++ b/examples/Test18.java
@@ -0,0 +1,133 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test18
+  extends TestSkeleton
+{
+  public Test18() { }
+
+  public Test18(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] time = {RealType.Time};
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+    RealType[] types4 = {ir_radiance, vis_radiance};
+    RealTupleType ecnaidar = new RealTupleType(types4);
+    FunctionType image_bumble = new FunctionType(earth_location, ecnaidar);
+    RealTupleType time_type = new RealTupleType(time);
+    FunctionType time_images = new FunctionType(time_type, image_tuple);
+    FunctionType time_bee = new FunctionType(time_type, image_bumble);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+    FlatField wasp = FlatField.makeField(image_bumble, size, false);
+
+    int ntimes1 = 4;
+    int ntimes2 = 6;
+
+    // different time extents test
+    // 2 May 99, 15:51:00
+    double start = new DateTime(1999, 122, 57060).getValue();
+    Set time_set = new Linear1DSet(time_type, start,
+                              start + 3600.0 * (ntimes1 - 1.0), ntimes1);
+    Set time_hornet = new Linear1DSet(time_type, start,
+                              start + 3600.0 * (ntimes2 - 1.0), ntimes2);
+
+    FieldImpl image_sequence = new FieldImpl(time_images, time_set);
+    FieldImpl image_stinger = new FieldImpl(time_bee, time_hornet);
+    FlatField temp = imaget1;
+    FlatField tempw = wasp;
+    Real[] reals18 = {new Real(vis_radiance, (float) size / 4.0f),
+                      new Real(ir_radiance, (float) size / 8.0f)};
+    RealTuple val = new RealTuple(reals18);
+    for (int i=0; i<ntimes1; i++) {
+      image_sequence.setSample(i, temp);
+      temp = (FlatField) temp.add(val);
+    }
+    for (int i=0; i<ntimes2; i++) {
+      image_stinger.setSample(i, tempw);
+      tempw = (FlatField) tempw.add(val);
+    }
+    FieldImpl[] images18 = {image_sequence, image_stinger};
+    Tuple big_tuple = new Tuple(images18);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1animation;
+    map1animation = new ScalarMap(RealType.Time, Display.Animation);
+    dpys[0].addMap(map1animation);
+
+    DataReferenceImpl ref_big_tuple;
+    ref_big_tuple = new DataReferenceImpl("ref_big_tuple");
+    ref_big_tuple.setData(big_tuple);
+    dpys[0].addReference(ref_big_tuple, null);
+  }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap map1animation = (ScalarMap )dpys[0].getMapVector().lastElement();
+
+    AnimationControl animation1control =
+      (AnimationControl) map1animation.getControl();
+    animation1control.setOn(true);
+    animation1control.setStep(3000);
+  }
+
+  public String toString() { return ": Animation different time extents"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test18(args);
+  }
+}
diff --git a/examples/Test19.java b/examples/Test19.java
new file mode 100644
index 0000000..3bd52a0
--- /dev/null
+++ b/examples/Test19.java
@@ -0,0 +1,169 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.util.VisADSlider;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test19
+  extends UISkeleton
+{
+  private DataReference value_ref;
+
+  public Test19() { }
+
+  public Test19(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+    RealType[] types4 = {ir_radiance, vis_radiance};
+    RealTupleType ecnaidar = new RealTupleType(types4);
+    FunctionType image_bumble = new FunctionType(earth_location, ecnaidar);
+    RealType[] time = {RealType.Time};
+    RealTupleType time_type = new RealTupleType(time);
+    FunctionType time_images = new FunctionType(time_type, image_tuple);
+    FunctionType time_bee = new FunctionType(time_type, image_bumble);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+    FlatField wasp = FlatField.makeField(image_bumble, size, false);
+
+    int ntimes1 = 4;
+    int ntimes2 = 6;
+    // different time resolutions for test
+    Set time_set =
+      new Linear1DSet(time_type, 0.0, 1.0, ntimes1);
+    Set time_hornet =
+      new Linear1DSet(time_type, 0.0, 1.0, ntimes2);
+
+    FieldImpl image_sequence = new FieldImpl(time_images, time_set);
+    FieldImpl image_stinger = new FieldImpl(time_bee, time_hornet);
+    FlatField temp = imaget1;
+    FlatField tempw = wasp;
+    Real[] reals19 = {new Real(vis_radiance, (float) size / 4.0f),
+                      new Real(ir_radiance, (float) size / 8.0f)};
+    RealTuple val = new RealTuple(reals19);
+    for (int i=0; i<ntimes1; i++) {
+      image_sequence.setSample(i, temp);
+      temp = (FlatField) temp.add(val);
+    }
+    for (int i=0; i<ntimes2; i++) {
+      image_stinger.setSample(i, tempw);
+      tempw = (FlatField) tempw.add(val);
+    }
+    FieldImpl[] images19 = {image_sequence, image_stinger};
+    Tuple big_tuple = new Tuple(images19);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1value = new ScalarMap(RealType.Time, Display.SelectValue);
+    dpys[0].addMap(map1value);
+
+    DataReferenceImpl ref_big_tuple;
+    ref_big_tuple = new DataReferenceImpl("ref_big_tuple");
+    ref_big_tuple.setData(big_tuple);
+    dpys[0].addReference(ref_big_tuple, null);
+
+  }
+
+  void finishClientSetup(RemoteServer client)
+    throws RemoteException, VisADException
+  {
+    value_ref = (DataReference )client.getDataReference(0);
+  }
+
+  void setServerDataReferences(RemoteServerImpl server)
+    throws RemoteException, VisADException
+  {
+    DataReferenceImpl dref = new DataReferenceImpl("value");
+    RemoteDataReferenceImpl ref = new RemoteDataReferenceImpl(dref);
+    if (server != null) {
+      server.addDataReference(ref);
+    }
+    value_ref = dref;
+  }
+
+  String getFrameTitle() { return "VisAD select slider"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap map1value = (ScalarMap )dpys[0].getMapVector().lastElement();
+
+    final ValueControl value1control =
+      (ValueControl) map1value.getControl();
+
+    VisADSlider slider =
+      new VisADSlider("value", 0, 100, 0, 0.01, value_ref, RealType.Generic);
+
+    if (value_ref instanceof ThingReferenceImpl) {
+      final DataReference cell_ref = value_ref;
+
+      CellImpl cell = new CellImpl() {
+        public void doAction() throws RemoteException, VisADException {
+          value1control.setValue(((Real) cell_ref.getData()).getValue());
+        }
+      };
+      cell.addReference(cell_ref);
+    }
+
+    return slider;
+  }
+
+
+  public String toString() { return ": SelectValue"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test19(args);
+  }
+}
diff --git a/examples/Test20.java b/examples/Test20.java
new file mode 100644
index 0000000..7169309
--- /dev/null
+++ b/examples/Test20.java
@@ -0,0 +1,95 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.util.LabeledColorWidget;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test20
+  extends UISkeleton
+{
+  public Test20() { }
+
+  public Test20(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    // System.out.println(" (known problems with Java3D transparency)");
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+
+    ScalarMap color1map = new ScalarMap(ir_radiance, Display.RGBA);
+    dpys[0].addMap(color1map);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle0() { return "VisAD Color Alpha Widget"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap color1map = (ScalarMap )dpys[0].getMapVector().lastElement();
+    return new LabeledColorWidget(color1map);
+  }
+
+  public String toString() { return ": 2-D surface and ColorAlphaWidget"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test20(args);
+  }
+}
diff --git a/examples/Test21.java b/examples/Test21.java
new file mode 100644
index 0000000..92d5fd7
--- /dev/null
+++ b/examples/Test21.java
@@ -0,0 +1,114 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+import visad.util.SelectRangeWidget;
+
+public class Test21
+  extends UISkeleton
+{
+  private boolean texture;
+
+  public Test21() { }
+
+  public Test21(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  public void initializeArgs() { texture = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    texture = true;
+    return 1;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.XAxis, RealType.YAxis};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.XAxis, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.YAxis, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    // dpys[0].addMap(new ConstantMap(0.25, Display.Alpha));
+
+    ScalarMap range1map = new ScalarMap(ir_radiance, Display.SelectRange);
+    dpys[0].addMap(range1map);
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setPointSize(2.0f);
+    mode.setPointMode(false);
+    mode.setMissingTransparent(true);
+    mode.setTextureEnable(texture);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "VisAD select range slider"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap range1map = (ScalarMap )dpys[0].getMapVector().lastElement();
+    return new SelectRangeWidget(range1map);
+  }
+
+  public String toString() { return " texture: SelectRange and SelectRangeWidget"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test21(args);
+  }
+}
diff --git a/examples/Test22.java b/examples/Test22.java
new file mode 100644
index 0000000..33791c7
--- /dev/null
+++ b/examples/Test22.java
@@ -0,0 +1,81 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test22
+  extends TestSkeleton
+{
+  public Test22() { }
+
+  public Test22(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Saturation));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Hue));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Value));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": Hue & Saturation"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test22(args);
+  }
+}
diff --git a/examples/Test23.java b/examples/Test23.java
new file mode 100644
index 0000000..d5a5c8b
--- /dev/null
+++ b/examples/Test23.java
@@ -0,0 +1,81 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test23
+  extends TestSkeleton
+{
+  public Test23() { }
+
+  public Test23(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Cyan));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Magenta));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Yellow));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": Cyan & Magenta"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test23(args);
+  }
+}
diff --git a/examples/Test24.java b/examples/Test24.java
new file mode 100644
index 0000000..97e69e6
--- /dev/null
+++ b/examples/Test24.java
@@ -0,0 +1,96 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test24
+  extends TestSkeleton
+{
+  public Test24() { }
+
+  public Test24(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.HSV));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    super.setupUI(dpys);
+    ScalarMap colorMap = (ScalarMap )dpys[0].getMapVector().lastElement();
+    ColorControl control = (ColorControl) colorMap.getControl();
+    int size = 256;
+    float[][] table = new float[3][size];
+    final float scale = 1.0f / (size - 1.0f);
+    for (int i=0; i<size; i++) {
+      table[0][i] = 360.0f * scale * i;
+      table[1][i] = scale * i;
+      table[2][i] = scale * i;
+    }
+    control.setTable(table);
+  }
+
+  public String toString() { return ": HSV"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test24(args);
+  }
+}
diff --git a/examples/Test25.java b/examples/Test25.java
new file mode 100644
index 0000000..ceb4463
--- /dev/null
+++ b/examples/Test25.java
@@ -0,0 +1,79 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test25
+  extends TestSkeleton
+{
+  public Test25() { }
+
+  public Test25(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.CMY));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": CMY"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test25(args);
+  }
+}
diff --git a/examples/Test26.java b/examples/Test26.java
new file mode 100644
index 0000000..7e1aa2c
--- /dev/null
+++ b/examples/Test26.java
@@ -0,0 +1,140 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test26
+  extends TestSkeleton
+{
+  public Test26() { }
+
+  public Test26(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    ScalarMap map1lat = new ScalarMap(RealType.Latitude, Display.YAxis);
+    dpys[0].addMap(map1lat);
+    /* Old way  DRM 17-Nov-2000
+    map1lat.setScalarName("Distance to Wall (m)");
+    map1lat.setScaleColor(new float[] {0.0f, 1.0f, 0.0f});
+    */
+    // New Way
+    AxisScale latScale = map1lat.getAxisScale();
+    latScale.setTitle("Distance to Wall (m)");
+    latScale.setColor(java.awt.Color.green);
+    latScale.setFont(java.awt.Font.decode("serif"));
+
+    ScalarMap map1lon = new ScalarMap(RealType.Longitude, Display.XAxis);
+    //map1lon.setScaleEnable(false);  Old way DRM: 2001-08-09
+    map1lon.getAxisScale().setVisible(false);
+    dpys[0].addMap(map1lon);
+
+    ScalarMap map1vis = new ScalarMap(vis_radiance, Display.ZAxis);
+    map1vis.setUnderscoreToBlank(true);
+    // could also use map1vis.getAxisScale().setLabel("vis radiance") above
+    map1vis.getAxisScale().setColor(new float[] {1.0f, 0.0f, 0.0f});
+    dpys[0].addMap(map1vis);
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    Vector v = dpys[0].getMapVector();
+
+    ScalarMap map1lat = (ScalarMap )v.elementAt(0);
+    ScalarMap map1lon = (ScalarMap )v.elementAt(1);
+    ScalarMap map1vis = (ScalarMap )v.elementAt(2);
+
+    boolean forever = true;
+    while (forever) {
+      // delay(5000);
+      try {
+        Thread.sleep(5000);
+      }
+      catch (InterruptedException e) {
+      }
+      System.out.println("\ndelay\n");
+      double[] range1lat = map1lat.getRange();
+      double[] range1lon = map1lon.getRange();
+      double[] range1vis = map1vis.getRange();
+      double inclat = 0.05 * (range1lat[1] - range1lat[0]);
+      double inclon = 0.05 * (range1lon[1] - range1lon[0]);
+      double incvis = 0.05 * (range1vis[1] - range1vis[0]);
+      map1lat.setRange(range1lat[1] + inclat, range1lat[0] - inclat);
+      map1lat.getAxisScale().setMinorTickSpacing(
+        map1lat.getAxisScale().getMajorTickSpacing()/2);
+      map1lon.setRange(range1lon[1] + inclon, range1lon[0] - inclon);
+      boolean visible = !map1lon.getAxisScale().isVisible();
+      map1lon.getAxisScale().setVisible(visible);
+      map1vis.setRange(range1vis[1] + incvis, range1vis[0] - incvis);
+    }
+
+  }
+
+  public String toString() { return ": scale"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test26(args);
+  }
+}
diff --git a/examples/Test27.java b/examples/Test27.java
new file mode 100644
index 0000000..64273b7
--- /dev/null
+++ b/examples/Test27.java
@@ -0,0 +1,207 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test27
+  extends TestSkeleton
+{
+  static int no_self = 0;
+  RealType vis_radiance;
+
+  boolean hasClientServerMode() { return false; }
+
+  public Test27() { }
+
+  public Test27(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Altitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    final RealType junk = RealType.getRealType("junk");
+
+    System.out.println("  drag yellow points with right mouse button");
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    final ScalarMap map2lat = new ScalarMap(RealType.Latitude, Display.YAxis);
+    dpys[0].addMap(map2lat);
+    final ScalarMap map2lon = new ScalarMap(RealType.Altitude, Display.XAxis);
+    dpys[0].addMap(map2lon);
+    final ScalarMap map2vis = new ScalarMap(vis_radiance, Display.ZAxis);
+    dpys[0].addMap(map2vis);
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    ScalarMap smap = new ScalarMap(junk, Display.Shape);
+    dpys[0].addMap(smap);
+
+    Gridded1DSet count_set =
+      new Gridded1DSet(RealType.Latitude, new float[][] {{0.0f}}, 1);
+    ShapeControl shape_control = (ShapeControl) smap.getControl();
+    shape_control.setShapeSet(count_set);
+    VisADLineArray cross = new VisADLineArray();
+    cross.coordinates = new float[]
+      {0.1f,  0.0f,  0.0f,    -0.1f,  0.0f,  0.0f,
+       0.0f, -0.1f,  0.0f,     0.0f,  0.1f,  0.0f,
+       0.0f,  0.0f,  0.1f,     0.0f,  0.0f, -0.1f};
+    cross.vertexCount = cross.coordinates.length / 3;
+    VisADGeometryArray[] shapes = {cross};
+    shape_control.setShapes(shapes);
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    mode.setPointMode(false);
+
+    mode.setProjectionPolicy(DisplayImplJ3D.PARALLEL_PROJECTION);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+
+    try {
+      Thread.sleep(2000);
+    }
+    catch (InterruptedException e) {
+    }
+    double[] range1lat = map2lat.getRange();
+    double[] range1lon = map2lon.getRange();
+    double[] range1vis = map2vis.getRange();
+
+    RealTuple direct_low = new RealTuple(new Real[]
+                     {new Real(RealType.Latitude, range1lat[0]),
+                      new Real(RealType.Altitude, range1lon[0]),
+                      new Real(vis_radiance, range1vis[0]),
+                      new Real(junk, 0.0)});
+    RealTuple direct_hi = new RealTuple(new Real[]
+                     {new Real(RealType.Latitude, range1lat[1]),
+                      new Real(RealType.Altitude, range1lon[1]),
+                      new Real(vis_radiance, range1vis[1]),
+                      new Real(junk, 0.0)});
+
+    final DataReferenceImpl ref_direct_low =
+      new DataReferenceImpl("ref_direct_low");
+    ref_direct_low.setData(direct_low);
+    // color low and hi tuples yellow
+    ConstantMap[][] maps = {{new ConstantMap(1.0f, Display.Red),
+                             new ConstantMap(1.0f, Display.Green),
+                             new ConstantMap(0.0f, Display.Blue),
+                             new ConstantMap(3.0f, Display.LineWidth)}};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(),
+                           new DataReference[] {ref_direct_low}, maps);
+
+    final DataReferenceImpl ref_direct_hi =
+      new DataReferenceImpl("ref_direct_hi");
+    ref_direct_hi.setData(direct_hi);
+    maps = new ConstantMap[][] {{new ConstantMap(1.0f, Display.Red),
+                                 new ConstantMap(1.0f, Display.Green),
+                                 new ConstantMap(0.0f, Display.Blue),
+                                 new ConstantMap(3.0f, Display.LineWidth)}};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(),
+                           new DataReference[] {ref_direct_hi}, maps);
+
+    no_self = 0;
+
+    CellImpl cell = new CellImpl() {
+      public synchronized void doAction()
+        throws RemoteException, VisADException {
+        if (no_self > 0) {
+          no_self--;
+          if (no_self > 0) return;
+        }
+        RealTuple low = (RealTuple) ref_direct_low.getData();
+        RealTuple hi = (RealTuple) ref_direct_hi.getData();
+        double[] lows = {((Real) low.getComponent(0)).getValue(),
+                         ((Real) low.getComponent(1)).getValue(),
+                         ((Real) low.getComponent(2)).getValue()};
+        double[] his = {((Real) hi.getComponent(0)).getValue(),
+                        ((Real) hi.getComponent(1)).getValue(),
+                        ((Real) hi.getComponent(2)).getValue()};
+        boolean changed = false;
+        for (int i=0; i<3; i++) {
+          if (his[i] < lows[i] + 0.00001) {
+            double m = 0.5 * (lows[i] + his[i]);
+            lows[i] = m - 0.000005;
+            his[i] = m + 0.000005;
+            changed = true;
+          }
+        }
+
+        if (changed) {
+          RealTuple dlow = new RealTuple(new Real[]
+                     {new Real(RealType.Latitude, lows[0]),
+                      new Real(RealType.Altitude, lows[1]),
+                      new Real(vis_radiance, lows[2]),
+                      new Real(junk, 0.0)});
+          RealTuple dhi = new RealTuple(new Real[]
+                     {new Real(RealType.Latitude, his[0]),
+                      new Real(RealType.Altitude, his[1]),
+                      new Real(vis_radiance, his[2]),
+                      new Real(junk, 0.0)});
+          ref_direct_low.setData(dlow);
+          ref_direct_hi.setData(dhi);
+          no_self += 2;
+        }
+
+        map2lat.setRange(lows[0], his[0]);
+        map2lon.setRange(lows[1], his[1]);
+        map2vis.setRange(lows[2], his[2]);
+      }
+    };
+    cell.addReference(ref_direct_low);
+    cell.addReference(ref_direct_hi);
+  }
+
+  public String toString() { return ": interactive scale"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test27(args);
+  }
+}
diff --git a/examples/Test28.java b/examples/Test28.java
new file mode 100644
index 0000000..4af1cfb
--- /dev/null
+++ b/examples/Test28.java
@@ -0,0 +1,83 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test28
+  extends TestSkeleton
+{
+  public Test28() { }
+
+  public Test28(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    ScalarMap map28flow = new ScalarMap(vis_radiance, Display.Flow1X);
+    dpys[0].addMap(map28flow);
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Flow1Y));
+
+    FlowControl control28flow = (FlowControl) map28flow.getControl();
+    control28flow.setFlowScale(0.06f);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": flow"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test28(args);
+  }
+}
diff --git a/examples/Test29.java b/examples/Test29.java
new file mode 100644
index 0000000..7c32499
--- /dev/null
+++ b/examples/Test29.java
@@ -0,0 +1,81 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test29
+  extends TestSkeleton
+{
+  public Test29() { }
+
+  public Test29(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, true);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": 2-D irregular surface"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test29(args);
+  }
+}
diff --git a/examples/Test30.java b/examples/Test30.java
new file mode 100644
index 0000000..0347d68
--- /dev/null
+++ b/examples/Test30.java
@@ -0,0 +1,107 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test30
+  extends TestSkeleton
+{
+  public Test30() { }
+
+  public Test30(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] time = {RealType.Time};
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+    RealType[] types4 = {ir_radiance, vis_radiance};
+    RealTupleType ecnaidar = new RealTupleType(types4);
+    FunctionType image_bumble = new FunctionType(earth_location, ecnaidar);
+    RealTupleType time_type = new RealTupleType(time);
+    FunctionType time_images = new FunctionType(time_type, image_tuple);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+    FlatField wasp = FlatField.makeField(image_bumble, size, false);
+
+    int ntimes1 = 4;
+
+    double start = new DateTime(1999, 122, 57060).getValue();
+    Set time_set = new Linear1DSet(time_type, start,
+                              start + 3600.0 * (ntimes1 - 1.0), ntimes1);
+
+    FieldImpl image_sequence = new FieldImpl(time_images, time_set);
+    FlatField temp = imaget1;
+    Real[] reals30 = {new Real(vis_radiance, (float) size / 4.0f),
+                      new Real(ir_radiance, (float) size / 8.0f)};
+    RealTuple val = new RealTuple(reals30);
+    for (int i=0; i<ntimes1; i++) {
+      image_sequence.setSample(i, temp);
+      temp = (FlatField) temp.add(val);
+    }
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Red));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ScalarMap(RealType.Time, Display.ZAxis));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReference ref_image_sequence = new DataReferenceImpl("ref_big_tuple");
+    ref_image_sequence.setData(image_sequence);
+    dpys[0].addReference(ref_image_sequence, null);
+  }
+
+  public String toString() { return ": time stack and time axis label"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test30(args);
+  }
+}
diff --git a/examples/Test31.java b/examples/Test31.java
new file mode 100644
index 0000000..b8aaa88
--- /dev/null
+++ b/examples/Test31.java
@@ -0,0 +1,91 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test31
+  extends TestSkeleton
+{
+  public Test31() { }
+
+  public Test31(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    RealType[] scatter_list = {vis_radiance, ir_radiance, count, RealType.Latitude,
+                               RealType.Longitude, RealType.Radius};
+    RealTupleType scatter = new RealTupleType(scatter_list);
+    RealType[] time = {RealType.Time};
+    RealTupleType time_type = new RealTupleType(time);
+    FunctionType scatter_function = new FunctionType(time_type, scatter);
+
+    int size = 64;
+
+    FlatField imaget1;
+    imaget1 = FlatField.makeField(scatter_function, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Green));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    // WLH 28 April 99 - test alpha with points
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Alpha));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": scatter diagram"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test31(args);
+  }
+}
diff --git a/examples/Test32.java b/examples/Test32.java
new file mode 100644
index 0000000..4c492ae
--- /dev/null
+++ b/examples/Test32.java
@@ -0,0 +1,168 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.data.fits.FitsForm;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test32
+  extends TestSkeleton
+{
+  private String fileName;
+
+  public Test32() { }
+
+  public Test32(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { fileName = null; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (fileName == null) {
+      fileName = args[argc];
+    } else {
+      System.err.println(testName + ": Ignoring extra filename \"" +
+                         args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  public String keywordUsage()
+  {
+    return super.keywordUsage() + " file";
+  }
+
+  private DataReferenceImpl loadFile()
+    throws RemoteException, VisADException
+  {
+    if (fileName == null) {
+      return null;
+    }
+
+    FitsForm fits = new FitsForm();
+    Data data;
+    try {
+      data = fits.open(fileName);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't load \"" + fileName + "\"");
+      ve.printStackTrace();
+      System.exit(1);
+      return null;
+    }
+
+    if (!(data instanceof FieldImpl)) {
+      System.err.println("File \"" + fileName + "\" resolves to " +
+                         data.getClass().getName() + ", not " +
+                         FieldImpl.class.getName());
+      System.exit(1);
+      return null;
+    }
+
+    //System.out.println("data type = " + data.getType());
+
+    DataReferenceImpl ref = new DataReferenceImpl("fits");
+    ref.setData(data);
+    return ref;
+  }
+
+  DataReference[] getClientDataReferences()
+    throws RemoteException, VisADException
+  {
+    DataReference ref = loadFile();
+    if (ref == null) {
+      return null;
+    }
+
+    return new DataReference[] { ref };
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    DataReference ref = loadFile();
+    if (ref == null) {
+      System.err.println("Must specify FITS file name");
+      return;
+    }
+
+    FieldImpl fits_data = (FieldImpl )ref.getData();
+
+    // compute ScalarMaps from type components
+    FunctionType ftype = (FunctionType) fits_data.getType();
+    RealTupleType dtype = ftype.getDomain();
+    MathType rntype = ftype.getRange();
+    int n = dtype.getDimension();
+    dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(0),
+                                  Display.XAxis));
+    if (n > 1) {
+      dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(1),
+                                    Display.YAxis));
+    }
+    if (n > 2) {
+      dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(2),
+                                    Display.ZAxis));
+    }
+    if (rntype instanceof RealType) {
+      dpys[0].addMap(new ScalarMap((RealType) rntype, Display.Green));
+    }
+    else if (rntype instanceof RealTupleType) {
+      int m = ((RealTupleType) rntype).getDimension();
+      RealType rr = (RealType) ((RealTupleType) rntype).getComponent(0);
+      dpys[0].addMap(new ScalarMap(rr, Display.Green));
+      if (n <= 2) {
+        if (m > 1) {
+          rr = (RealType) ((RealTupleType) rntype).getComponent(1);
+        }
+        dpys[0].addMap(new ScalarMap(rr, Display.ZAxis));
+      }
+    }
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    dpys[0].addMap(new ConstantMap(0.0, Display.Blue));
+
+    dpys[0].addReference(ref, null);
+  }
+
+  public String toString() { return " file_name: FITS adapter"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test32(args);
+  }
+}
diff --git a/examples/Test33.java b/examples/Test33.java
new file mode 100644
index 0000000..d529ba9
--- /dev/null
+++ b/examples/Test33.java
@@ -0,0 +1,107 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.util.LabeledColorWidget;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test33
+  extends UISkeleton
+{
+  public Test33() { }
+
+  public Test33(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  private static float[][] buildTable()
+  {
+    float[][] table = new float[3][256];
+    for (int i=0; i<256; i++) {
+      float a = ((float) i) / 255.0f;
+      table[0][i] = a;
+      table[1][i] = 1.0f - a;
+      table[2][i] = 0.5f;
+    }
+    return table;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+
+    ScalarMap color1map = new ScalarMap(ir_radiance, Display.RGB);
+    dpys[0].addMap(color1map);
+
+    ((BaseColorControl )(color1map.getControl())).setTable(buildTable());
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "VisAD Color Widget"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap color1map = (ScalarMap )dpys[0].getMapVector().lastElement();
+    return new LabeledColorWidget(color1map);
+  }
+
+  public String toString() { return ": ColorWidget with non-default table"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test33(args);
+  }
+}
diff --git a/examples/Test34.java b/examples/Test34.java
new file mode 100644
index 0000000..dd62008
--- /dev/null
+++ b/examples/Test34.java
@@ -0,0 +1,122 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+import visad.java2d.DisplayRendererJ2D;
+import visad.java2d.DirectManipulationRendererJ2D;
+
+public class Test34
+  extends UISkeleton
+{
+  public Test34() { }
+
+  public Test34(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ2D("display1");
+    dpys[1] = new DisplayImplJ2D("display2");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+    Real direct = new Real(ir_radiance, 2.0);
+    Real[] reals3;
+    reals3 = new Real[] {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                         new Real(vis_radiance, 1.0)};
+    RealTuple direct_tuple = new RealTuple(reals3);
+
+    GraphicsModeControl mode;
+
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(count, Display.Green));
+
+    mode = dpys[0].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+    mode.setPointMode(false);
+
+    DisplayRendererJ2D dr = (DisplayRendererJ2D )dpys[0].getDisplayRenderer();
+    dr.setClip(-1.0f, 1.0f, -1.0f, 1.0f);
+
+    DataReferenceImpl ref_direct = new DataReferenceImpl("ref_direct");
+    ref_direct.setData(direct);
+    DataReference[] refs1 = new DataReferenceImpl[] {ref_direct};
+    dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs1, null);
+
+    DataReferenceImpl ref_direct_tuple;
+    ref_direct_tuple = new DataReferenceImpl("ref_direct_tuple");
+    ref_direct_tuple.setData(direct_tuple);
+    DataReference[] refs2 = new DataReference[] {ref_direct_tuple};
+    dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs2, null);
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    DataReference[] refs3 = new DataReference[] {ref_histogram1};
+    dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs3, null);
+
+    dpys[1].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.Green));
+
+    mode = dpys[1].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+    mode.setPointMode(false);
+
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs1, null);
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs2, null);
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs3, null);
+  }
+
+  String getFrameTitle() { return "Java2D direct manipulation and clipping"; }
+
+  public String toString()
+  {
+    return ": direct manipulation and clipping in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test34(args);
+  }
+}
diff --git a/examples/Test35.java b/examples/Test35.java
new file mode 100644
index 0000000..5bd7928
--- /dev/null
+++ b/examples/Test35.java
@@ -0,0 +1,159 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.java2d.DisplayImplJ2D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java2d.DirectManipulationRendererJ2D;
+import visad.java3d.DisplayRendererJ3D;
+
+import visad.util.Delay;
+
+public class Test35
+  extends UISkeleton
+  implements DisplayListener
+{
+  public Test35() { }
+
+  public Test35(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ3D("display1");
+    dpys[1] = new DisplayImplJ2D("display2");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+    Real direct = new Real(ir_radiance, 2.0);
+    Real[] reals3;
+    reals3 = new Real[] {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                         new Real(vis_radiance, 1.0)};
+    RealTuple direct_tuple = new RealTuple(reals3);
+
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(count, Display.Green));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+    mode.setPointMode(false);
+    mode.setScaleEnable(true);
+    DisplayRendererJ3D dr = (DisplayRendererJ3D) dpys[0].getDisplayRenderer();
+    dr.setClip(0, true,  1.0f,  0.0f,  0.0f, -1.0f);
+    dr.setClip(1, true, -1.0f,  0.0f,  0.0f, -1.0f);
+    dr.setClip(2, true,  0.0f,  1.0f,  0.0f, -1.0f);
+    dr.setClip(3, true,  0.0f, -1.0f,  0.0f, -1.0f);
+    dr.setClip(4, true,  0.0f,  0.0f,  1.0f, -1.0f);
+    dr.setClip(5, true,  0.0f,  0.0f, -1.0f, -1.0f);
+
+    DataReferenceImpl ref_direct = new DataReferenceImpl("ref_direct");
+    ref_direct.setData(direct);
+    DataReference[] refs1 = new DataReferenceImpl[] {ref_direct};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refs1, null);
+
+    DataReferenceImpl ref_direct_tuple;
+    ref_direct_tuple = new DataReferenceImpl("ref_direct_tuple");
+    ref_direct_tuple.setData(direct_tuple);
+    DataReference[] refs2 = new DataReference[] {ref_direct_tuple};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refs2, null);
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    DataReference[] refs3 = new DataReference[] {ref_histogram1};
+    dpys[0].addReferences(new DirectManipulationRendererJ3D(), refs3, null);
+
+    new Delay(500);
+
+    dpys[1].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.Green));
+
+    GraphicsModeControl mode2 = dpys[1].getGraphicsModeControl();
+    mode2.setPointSize(5.0f);
+    mode2.setPointMode(false);
+
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs1, null);
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs2, null);
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs3, null);
+
+    dpys[0].addDisplayListener(this);
+    dpys[1].addDisplayListener(this);
+  }
+
+  public void displayChanged(DisplayEvent e)
+    throws RemoteException, VisADException {
+    if (e.getId() == DisplayEvent.FRAME_DONE) {
+      DisplayImpl display = (DisplayImpl) e.getDisplay();
+      DisplayRenderer dr = display.getDisplayRenderer();
+      MouseBehavior mb = dr.getMouseBehavior();
+      double[] position1 = null;
+      double[] position2 = null;
+      if (display instanceof DisplayImplJ3D) {
+        position1 = new double[] { 1.0,  1.0,  1.0};
+        position2 = new double[] {-1.0, -1.0, -1.0};
+      }
+      else {
+        position1 = new double[] { 1.0,  1.0};
+        position2 = new double[] {-1.0, -1.0};
+      }
+      int[] screen1 = mb.getScreenCoords(position1);
+      int[] screen2 = mb.getScreenCoords(position2);
+      //System.out.println("screen1 = (" + screen1[0] + ", " + screen1[1] +")");
+      //System.out.println("screen2 = (" + screen2[0] + ", " + screen2[1] +")");
+    }
+  }
+
+  String getFrameTitle() { return "clipped Java3D -- Java2D direct manipulation"; }
+
+  public String toString()
+  {
+    return ": direct manipulation linking Java2D and clipped Java3D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test35(args);
+  }
+}
diff --git a/examples/Test36.java b/examples/Test36.java
new file mode 100644
index 0000000..4230263
--- /dev/null
+++ b/examples/Test36.java
@@ -0,0 +1,87 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test36
+  extends UISkeleton
+{
+  public Test36() { }
+
+  public Test36(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    Unit super_degree = CommonUnit.degree.scale(2.5);
+    RealType lon = RealType.getRealType("lon", super_degree);
+    RealType radius = RealType.getRealType("radius");
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Radius));
+    ScalarMap lonmap = new ScalarMap(RealType.Longitude, Display.Longitude);
+    lonmap.setRangeByUnits();
+    dpys[0].addMap(lonmap);
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.RGB));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "polar coordinates in Java2D"; }
+
+  public String toString() { return ": polar coordinates in Java2D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test36(args);
+  }
+}
diff --git a/examples/Test37.java b/examples/Test37.java
new file mode 100644
index 0000000..c373c7b
--- /dev/null
+++ b/examples/Test37.java
@@ -0,0 +1,198 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.BorderLayout;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.ContourWidget;
+import visad.util.LabeledRGBAWidget;
+
+public class Test37
+  extends TestSkeleton
+{
+  private boolean reverse;
+  ScalarMap rgbaMap;
+
+  public Test37() { }
+
+  public Test37(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { reverse = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    reverse = true;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude, RealType.Altitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = new RealType("vis_radiance", null, null);
+    RealType ir_radiance = new RealType("ir_radiance", null, null);
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+    RealType[] typesxx = {RealType.Longitude, RealType.Latitude};
+    RealTupleType earth_locationxx = new RealTupleType(typesxx);
+    FunctionType image_tuplexx = new FunctionType(earth_locationxx, radiance);
+
+    int size = 64;
+    FlatField imaget1;
+    if (!reverse) {
+      imaget1 = FlatField.makeField(image_tuple, size, false);
+    }
+    else {
+      imaget1 = FlatField.makeField(image_tuplexx, size, false);
+    }
+
+    double first = 0.0;
+    double last = size - 1.0;
+    double step = 1.0;
+    double half = 0.5 * last;
+
+    int nr = size;
+    int nc = size;
+    double ang = 2*Math.PI/nr;
+    float[][] locs = new float[3][nr*nc];
+    for ( int jj = 0; jj < nc; jj++ ) {
+      for ( int ii = 0; ii < nr; ii++ ) {
+        int idx = jj*nr + ii;
+        locs[0][idx] = ii;
+        locs[1][idx] = jj;
+        locs[2][idx] =
+           2f*((float)Math.sin(2*ang*ii)) + 2f*((float)Math.sin(2*ang*jj));
+      }
+    }
+    Gridded3DSet d_set =
+      new Gridded3DSet(RealTupleType.SpatialCartesian3DTuple, locs, nr, nc);
+    imaget1 = new FlatField(image_tuple, d_set);
+    FlatField.fillField(imaget1, step, half);
+
+
+    ScalarMap xmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    dpys[0].addMap(xmap);
+    ScalarMap ymap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    dpys[0].addMap(ymap);
+    ScalarMap zmap = new ScalarMap(RealType.Altitude, Display.ZAxis);
+    dpys[0].addMap(zmap);
+    rgbaMap = new ScalarMap(vis_radiance, Display.RGBA);
+    dpys[0].addMap(rgbaMap);
+    zmap.setRange(-20, 20);
+   
+    ScalarMap map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+    ContourControl ctr_cntrl = (ContourControl) map1contour.getControl();
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    mode.setPointSize(2);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  private String getFrameTitle0() { return "regular contours in Java3D"; }
+
+  private String getFrameTitle1() { return "VisAD contour controls"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle0() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) dpys[0].getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+
+    ScalarMap map1contour = (ScalarMap )dpys[0].getMapVector().lastElement();
+    ContourWidget cw = new ContourWidget(map1contour);
+
+    JPanel big_panel = new JPanel();
+    big_panel.setLayout(new BorderLayout());
+    big_panel.add("Center", cw);
+  
+
+    JFrame jframe2  = new JFrame(getFrameTitle1());
+    jframe2.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe2.setContentPane(big_panel);
+    jframe2.pack();
+    jframe2.setVisible(true);
+
+    LabeledRGBAWidget caw = new LabeledRGBAWidget(rgbaMap);
+    JPanel big_panel2 = new JPanel();
+    big_panel2.setLayout(new BorderLayout());
+    big_panel2.add("Center", caw);
+
+
+    JFrame jframe3  = new JFrame(getFrameTitle1());
+    jframe3.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+    jframe3.setContentPane(big_panel2);
+    jframe3.pack();
+    jframe3.setVisible(true);
+
+  }
+
+  public String toString()
+  {
+    return ": colored contours from regular grids and ContourWidget in Java3D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test37(args);
+  }
+}
diff --git a/examples/Test38.java b/examples/Test38.java
new file mode 100644
index 0000000..e043d34
--- /dev/null
+++ b/examples/Test38.java
@@ -0,0 +1,91 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test38
+  extends UISkeleton
+{
+  public Test38() { }
+
+  public Test38(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 64;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, true);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1contour;
+    map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+    ContourControl control1contour;
+    control1contour = (ContourControl) map1contour.getControl();
+    control1contour.enableContours(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "irregular contours in Java2D"; }
+
+  public String toString()
+  {
+    return ": colored contours from irregular grids in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test38(args);
+  }
+}
diff --git a/examples/Test39.java b/examples/Test39.java
new file mode 100644
index 0000000..49fdf7c
--- /dev/null
+++ b/examples/Test39.java
@@ -0,0 +1,126 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.BorderLayout;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import java.awt.Dimension;
+import visad.java2d.DisplayRendererJ2D;
+import visad.util.LabeledColorWidget;
+import visad.java2d.DisplayImplJ2D;
+
+public class Test39
+  extends TestSkeleton
+{
+  public Test39() { }
+
+  public Test39(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+
+    ScalarMap color1map = new ScalarMap(vis_radiance, Display.RGB);
+    dpys[0].addMap(color1map);
+
+    DisplayRendererJ2D dr = (DisplayRendererJ2D )dpys[0].getDisplayRenderer();
+    dr.getCanvas().setPreferredSize(new Dimension(256, 256));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "VisAD Color Widget in Java2D"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    JPanel lil_panel = new JPanel();
+    lil_panel.setAlignmentX(JPanel.CENTER_ALIGNMENT);
+    lil_panel.setLayout(new BorderLayout());
+    lil_panel.add("Center", dpys[0].getComponent());
+
+    ScalarMap color1map = (ScalarMap )dpys[0].getMapVector().lastElement();
+    LabeledColorWidget lw = new LabeledColorWidget(color1map);
+
+    JPanel big_panel = new JPanel();
+    big_panel.setLayout(new BoxLayout(big_panel, BoxLayout.Y_AXIS));
+    big_panel.add(lw);
+    big_panel.add(lil_panel);
+
+    jframe.setContentPane(big_panel);
+    jframe.setSize(400, 600);
+    jframe.setVisible(true);
+  }
+
+  public String toString()
+  {
+    return ": color array and ColorWidget in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test39(args);
+  }
+}
diff --git a/examples/Test40.java b/examples/Test40.java
new file mode 100644
index 0000000..dcd5dd1
--- /dev/null
+++ b/examples/Test40.java
@@ -0,0 +1,114 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+import visad.java2d.DirectManipulationRendererJ2D;
+
+public class Test40
+  extends UISkeleton
+{
+  public Test40() { }
+
+  public Test40(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ2D("display1");
+    dpys[1] = new DisplayImplJ2D("display2");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    Unit super_degree = CommonUnit.degree.scale(2.5);
+    RealType lon = RealType.getRealType("lon", super_degree);
+    RealType radius = RealType.getRealType("radius");
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealTupleType cartesian = new RealTupleType(x, y);
+    PolarCoordinateSystem polar_coord_sys = new PolarCoordinateSystem(cartesian);
+    RealTupleType polar = new RealTupleType(lon, radius, polar_coord_sys, null);
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+    Real[] reals3;
+    reals3 = new Real[] {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                         new Real(vis_radiance, 1.0)};
+    RealTuple direct_tuple = new RealTuple(reals3);
+
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Radius));
+    dpys[0].addMap(new ScalarMap(count, Display.Longitude));
+    dpys[0].addMap(new ScalarMap(count, Display.Green));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+    mode.setPointMode(false);
+
+    DataReferenceImpl ref_direct_tuple;
+    ref_direct_tuple = new DataReferenceImpl("ref_direct_tuple");
+    ref_direct_tuple.setData(direct_tuple);
+    DataReference[] refs2 = new DataReference[] {ref_direct_tuple};
+    dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs2, null);
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    DataReference[] refs3 = new DataReference[] {ref_histogram1};
+    dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs3, null);
+
+    dpys[1].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[1].addMap(new ScalarMap(count, Display.Green));
+
+    GraphicsModeControl mode2 = dpys[1].getGraphicsModeControl();
+    mode2.setPointSize(5.0f);
+    mode2.setPointMode(false);
+
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs2, null);
+    dpys[1].addReferences(new DirectManipulationRendererJ2D(), refs3, null);
+  }
+
+  String getFrameTitle() { return "Java2D direct manipulation"; }
+
+  public String toString() { return ": polar direct manipulation in Java2D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test40(args);
+  }
+}
diff --git a/examples/Test41.java b/examples/Test41.java
new file mode 100644
index 0000000..44125c6
--- /dev/null
+++ b/examples/Test41.java
@@ -0,0 +1,139 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test41
+  extends UISkeleton
+{
+  private boolean autoAspect;
+
+  public Test41() { }
+
+  public Test41(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { autoAspect = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    autoAspect = true;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImplJ2D dpys1 = new DisplayImplJ2D("display1");
+    DisplayImplJ2D dpys2 = new DisplayImplJ2D("display2");
+    dpys1.setAutoAspect(autoAspect);
+    dpys2.setAutoAspect(autoAspect);
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = dpys1;
+    dpys[1] = dpys2;
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+
+    // construct types
+    int isize = 16;
+    RealType dom0 = RealType.getRealType("dom0");
+    RealType dom1 = RealType.getRealType("dom1");
+    RealType ran = RealType.getRealType("ran");
+    RealTupleType dom = new RealTupleType(dom0, dom1);
+    FunctionType ftype = new FunctionType(dom, ran);
+    FlatField imaget1;
+    imaget1 = new FlatField(ftype, new Integer2DSet(isize, isize));
+    double[][] vals = new double[1][isize * isize];
+    for (int i=0; i<isize; i++) {
+      for (int j=0; j<isize; j++) {
+        vals[0][j + isize * i] = (i + 1) * (j + 1);
+      }
+    }
+    imaget1.setSamples(vals, false);
+
+    RealType oogle = RealType.getRealType("oogle");
+    FunctionType ftype2 = new FunctionType(dom, oogle);
+    FlatField imaget2 = new FlatField(ftype2, imaget1.getDomainSet());
+    imaget2.setSamples(vals, false);
+
+    dpys[0].addMap(new ScalarMap(dom0, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(dom1, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(ran, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.3, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.3, Display.Red));
+    dpys[0].addMap(new ScalarMap(oogle, Display.IsoContour));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setTextureEnable(false);
+
+    ConstantMap[] omaps1 = {new ConstantMap(1.0, Display.Blue),
+                            new ConstantMap(1.0, Display.Red),
+                            new ConstantMap(0.0, Display.Green)};
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+
+    DataReferenceImpl ref_imaget2 = new DataReferenceImpl("ref_imaget2");
+    ref_imaget2.setData(imaget2);
+    dpys[0].addReference(ref_imaget2, omaps1);
+
+    dpys[1].addMap(new ScalarMap(dom0, Display.XAxis));
+    dpys[1].addMap(new ScalarMap(dom1, Display.YAxis));
+    dpys[1].addMap(new ScalarMap(ran, Display.Green));
+    dpys[1].addMap(new ConstantMap(0.3, Display.Blue));
+    dpys[1].addMap(new ConstantMap(0.3, Display.Red));
+    dpys[1].addMap(new ScalarMap(oogle, Display.IsoContour));
+
+    ConstantMap[] omaps2 = {new ConstantMap(1.0, Display.Blue),
+                            new ConstantMap(1.0, Display.Red),
+                            new ConstantMap(0.0, Display.Green)};
+
+    dpys[1].addReference(ref_imaget1, null);
+    dpys[1].addReference(ref_imaget2, omaps2);
+  }
+
+  String getFrameTitle() { return "image / contour alignment in Java2D"; }
+
+  public String toString()
+  {
+    return " auto: image / contour alignment & autoAspect in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test41(args);
+  }
+}
diff --git a/examples/Test42.java b/examples/Test42.java
new file mode 100644
index 0000000..d485deb
--- /dev/null
+++ b/examples/Test42.java
@@ -0,0 +1,141 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+import java.awt.Font;
+
+import visad.*;
+
+import visad.java3d.TwoDDisplayRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test42
+  extends UISkeleton
+{
+  public Test42() { }
+
+  public Test42(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    dpys[1] = new DisplayImplJ3D("display2", new TwoDDisplayRendererJ3D());
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+
+    // construct types
+    int isize = 16;
+    RealType dom0 = RealType.getRealType("dom0");
+    RealType dom1 = RealType.getRealType("dom1");
+    RealType ran = RealType.getRealType("ran");
+    RealTupleType dom = new RealTupleType(dom0, dom1);
+    FunctionType ftype = new FunctionType(dom, ran);
+    FlatField imaget1;
+    imaget1 = new FlatField(ftype, new Integer2DSet(isize, isize));
+    double[][] vals = new double[1][isize * isize];
+    for (int i=0; i<isize; i++) {
+      for (int j=0; j<isize; j++) {
+        vals[0][j + isize * i] = (i + 1) * (j + 1);
+      }
+    }
+    imaget1.setSamples(vals, false);
+
+    RealType oogle = RealType.getRealType("oogle");
+    FunctionType ftype2 = new FunctionType(dom, oogle);
+    FlatField imaget2 = new FlatField(ftype2, imaget1.getDomainSet());
+    imaget2.setSamples(vals, false);
+
+    ScalarMap xmap0 = new ScalarMap(dom0, Display.XAxis);
+    xmap0.getAxisScale().setScreenBased(true);
+    xmap0.getAxisScale().setLabelAllTicks(true);
+    xmap0.getAxisScale().setTitle("test");
+    xmap0.getAxisScale().setFont(Font.decode("dialog-16"));
+    dpys[0].addMap(xmap0);
+    ScalarMap ymap0 = new ScalarMap(dom1, Display.YAxis);
+    ymap0.getAxisScale().setScreenBased(true);
+    dpys[0].addMap(ymap0);
+    dpys[0].addMap(new ScalarMap(ran, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.3, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.3, Display.Red));
+    dpys[0].addMap(new ScalarMap(oogle, Display.IsoContour));
+
+    GraphicsModeControl mode0 = dpys[0].getGraphicsModeControl();
+    mode0.setTextureEnable(false);
+    mode0.setScaleEnable(true);
+
+    ConstantMap[] omaps1;
+    omaps1 = new ConstantMap[] {new ConstantMap(1.0, Display.Blue),
+                                new ConstantMap(1.0, Display.Red),
+                                new ConstantMap(0.0, Display.Green)};
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+
+    DataReferenceImpl ref_imaget2 = new DataReferenceImpl("ref_imaget2");
+    ref_imaget2.setData(imaget2);
+    dpys[0].addReference(ref_imaget2, omaps1);
+
+    ScalarMap xmap1 = new ScalarMap(dom0, Display.XAxis);
+    xmap1.getAxisScale().setScreenBased(false);
+    dpys[1].addMap(xmap1);
+    ScalarMap ymap1 = new ScalarMap(dom1, Display.YAxis);
+    ymap1.getAxisScale().setScreenBased(false);
+    dpys[1].addMap(ymap1);
+    dpys[1].addMap(new ScalarMap(ran, Display.Green));
+    dpys[1].addMap(new ConstantMap(0.3, Display.Blue));
+    dpys[1].addMap(new ConstantMap(0.3, Display.Red));
+    dpys[1].addMap(new ScalarMap(oogle, Display.IsoContour));
+
+    GraphicsModeControl mode1 = dpys[1].getGraphicsModeControl();
+    mode1.setScaleEnable(true);
+
+    ConstantMap[] omaps2;
+    omaps2 = new ConstantMap[] {new ConstantMap(1.0, Display.Blue),
+                                new ConstantMap(1.0, Display.Red),
+                                new ConstantMap(0.0, Display.Green)};
+
+    dpys[1].addReference(ref_imaget1, null);
+    dpys[1].addReference(ref_imaget2, omaps2);
+  }
+
+  String getFrameTitle() { return "image / contour alignment in Java3D"; }
+
+  public String toString() { return ": image / contour alignment in Java3D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test42(args);
+  }
+}
diff --git a/examples/Test43.java b/examples/Test43.java
new file mode 100644
index 0000000..d4575ac
--- /dev/null
+++ b/examples/Test43.java
@@ -0,0 +1,177 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test43
+  extends UISkeleton
+{
+  public Test43() { }
+
+  public Test43(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ2D("display1");
+    dpys[1] = new DisplayImplJ2D("display2");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    GraphicsModeControl mode;
+
+    int domain_flag = 0;
+
+    int LengthX = 201;
+    int LengthY = 201;
+    int n_samples = LengthX*LengthY;
+    int ii, jj;
+    int index;
+    FlatField d_field;
+    Set  domainSet = null;
+    RealType x_axis = RealType.getRealType( "x_axis", SI.meter );
+    RealType y_axis = RealType.getRealType( "y_axis", SI.meter );
+    MathType Domain = (MathType) new RealTupleType( x_axis, y_axis );
+
+    MathType rangeTemp = (MathType) RealType.getRealType( "Temperature", SI.kelvin );
+
+    FunctionType domain_temp = new FunctionType( Domain, rangeTemp );
+
+    if ( domain_flag == 0 )
+    {
+       domainSet = (Set) new Linear2DSet( Domain, 0.d, 1000.d, LengthX,
+                                          0.d, 1000.d, LengthY );
+    }
+    else if ( domain_flag == 1 )
+    {
+      float[][] d_samples = new float[2][n_samples];
+
+      index = 0;
+      for ( ii = 0; ii < LengthY; ii++ ) {
+        for ( jj = 0; jj < LengthX; jj++ ) {
+          d_samples[0][index] = jj*5f;
+          d_samples[1][index] = ii*5f;
+          index++;
+        }
+      }
+      domainSet = (Set) new Gridded2DSet( Domain, d_samples, LengthX, LengthY,
+                                          null, null, null );
+    }
+    else if ( domain_flag == 3)
+    {
+
+    }
+
+    FlatField f_field = new FlatField( domain_temp, domainSet );
+
+    double[][] samples = new double[1][n_samples];
+
+    index = 0;
+    double wave_number = 2;
+    double PI = Math.PI;
+    for ( ii = 0; ii < LengthY; ii++ )
+    {
+      for ( jj = 0; jj < LengthX; jj++ )
+      {
+        samples[0][index] =  (50)*Math.sin( ((wave_number*2d*PI)/1000)*5*jj )*
+                                  Math.sin( ((wave_number*2d*PI)/1000)*5*ii );
+        index++;
+      }
+    }
+    f_field.setSamples( samples );
+
+    System.out.println("Starting derivative computation...");
+      d_field = (FlatField) f_field.derivative( x_axis, Data.NO_ERRORS );
+    System.out.println("...derivative done");
+
+    RealType f_range = (RealType) ((FunctionType)d_field.getType()).getRange();
+
+    dpys[0].addMap( new ScalarMap( (RealType)x_axis, Display.XAxis ));
+    dpys[0].addMap( new ScalarMap( (RealType)y_axis, Display.YAxis ));
+    dpys[0].addMap( new ScalarMap( (RealType)rangeTemp, Display.Green));
+    dpys[0].addMap( new ConstantMap( 0.5, Display.Red));
+    dpys[0].addMap( new ConstantMap( 0.5, Display.Blue));
+    /**
+    ScalarMap map1contour;
+    map1contour = new ScalarMap( (RealType)rangeTemp, Display.IsoContour );
+    dpys[0].addMap( map1contour );
+    ContourControl control1contour;
+    control1contour = (ContourControl) map1contour.getControl();
+
+    control1contour.enableContours(true);
+    control1contour.enableLabels(false);
+     **/
+
+    mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    dpys[1].addMap( new ScalarMap( (RealType)x_axis, Display.XAxis ));
+    dpys[1].addMap( new ScalarMap( (RealType)y_axis, Display.YAxis ));
+    dpys[1].addMap( new ScalarMap( (RealType)f_range, Display.Green));
+    dpys[1].addMap( new ConstantMap( 0.5, Display.Red));
+    dpys[1].addMap( new ConstantMap( 0.5, Display.Blue));
+     /**
+    map1contour = new ScalarMap( (RealType)f_range, Display.IsoContour );
+    dpys[1].addMap( map1contour );
+    control1contour = (ContourControl) map1contour.getControl();
+
+    control1contour.enableContours(true);
+    control1contour.enableLabels(false);
+      **/
+
+    mode = dpys[1].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData( f_field );
+    dpys[0].addReference( ref_imaget1, null);
+
+    DataReferenceImpl ref_imaget2 = new DataReferenceImpl("ref_imaget2");
+    ref_imaget2.setData( d_field );
+    dpys[1].addReference( ref_imaget2, null );
+  }
+
+  String getFrameTitle() { return "sinusoidal field    and    (d/dx)field"; }
+
+  public String toString()
+  {
+    return ": Function.derivative with Linear2DSet in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test43(args);
+  }
+}
diff --git a/examples/Test44.java b/examples/Test44.java
new file mode 100644
index 0000000..7d85e2b
--- /dev/null
+++ b/examples/Test44.java
@@ -0,0 +1,153 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+import javax.swing.border.*;
+
+// AWT packages
+import java.awt.*;
+import java.awt.event.*;
+
+import java.rmi.RemoteException;
+
+import java.util.Enumeration;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.TwoDDisplayRendererJ3D;
+import visad.util.TextControlWidget;
+
+public class Test44
+  extends UISkeleton
+{
+  public Test44() { }
+
+  public Test44(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    TextType text = new TextType("text");
+    RealType[] time = {RealType.Time};
+    RealTupleType time_type = new RealTupleType(time);
+    MathType[] mtypes = {RealType.Latitude, RealType.Longitude, text};
+    TupleType text_tuple = new TupleType(mtypes);
+    FunctionType text_function = new FunctionType(RealType.Time, text_tuple);
+
+    String[] names = {"aaa", "bbbb", "ccccc", "defghi"};
+    int ntimes1 = names.length;
+    Set time_set =
+      new Linear1DSet(time_type, 0.0, (double) (ntimes1 - 1.0), ntimes1);
+
+    FieldImpl text_field = new FieldImpl(text_function, time_set);
+
+    for (int i=0; i<ntimes1; i++) {
+      Data[] td = {new Real(RealType.Latitude, (double) i),
+                   new Real(RealType.Longitude, (double) (ntimes1 - i)),
+                   new Text(text, names[i])};
+
+      Tuple tt = new Tuple(text_tuple, td);
+      text_field.setSample(i, tt);
+    }
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap text_map = new ScalarMap(text, Display.Text);
+    dpys[0].addMap(text_map);
+
+    DataReferenceImpl ref_text_field =
+      new DataReferenceImpl("ref_text_field");
+    ref_text_field.setData(text_field);
+    dpys[0].addReference(ref_text_field, null);
+  }
+
+  String getFrameTitle() {
+    return "text in Java2D with interactive settings";
+  }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    boolean foundCtrl = false;
+   TextControl text_control = null;
+    Enumeration en = dpys[0].getMapVector().elements();
+    while (en.hasMoreElements()) {
+      ScalarMap sm = (ScalarMap )en.nextElement();
+
+      Control ctrl = sm.getControl();
+      if (ctrl != null && ctrl instanceof TextControl) {
+        text_control = (TextControl) ctrl;
+        // text_control.setSize(0.75);
+        // text_control.setJustification(TextControl.Justification.RIGHT);
+        // text_control.setRotation(10.0);
+        // text_control.setAutoSize(true);
+        foundCtrl = true;
+      }
+    }
+
+    if (!foundCtrl) {
+      System.err.println("Didn't find a TextControl for this display!");
+      System.err.println("Don't be surprised if things don't work...");
+    }
+
+    // SL 16 July 2003
+    //    return null;
+    JFrame jframe = new JFrame("VisAD font Selection Widget");
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+    jframe.setContentPane(new TextControlWidget(text_control));
+    jframe.pack();
+    jframe.setVisible(true);
+
+    return null;
+  }
+
+  public String toString() {
+    return ": text in Java2D with interactive settings";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test44(args);
+  }
+}
diff --git a/examples/Test45.java b/examples/Test45.java
new file mode 100644
index 0000000..767aa31
--- /dev/null
+++ b/examples/Test45.java
@@ -0,0 +1,146 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.*;
+
+// AWT packages
+import java.awt.*;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.TextControlWidget;
+
+public class Test45
+  extends UISkeleton
+{
+  private boolean sphere;
+  private TextControl tcontrol;
+
+  public Test45() { }
+
+  public Test45(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { sphere = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    sphere = true;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    // SL 16 July 2003
+    //    dpys[0] = new DisplayImplJ3D("display");
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    TextType text = new TextType("text");
+    RealType[] time = {RealType.Time};
+    RealTupleType time_type = new RealTupleType(time);
+    MathType[] mtypes = {RealType.Latitude, RealType.Longitude, text};
+    TupleType text_tuple = new TupleType(mtypes);
+    FunctionType text_function = new FunctionType(RealType.Time, text_tuple);
+
+    String[] names = new String[] {"a b c d e f g h i j k l m",
+                                   "nopqrstuvwxyz",
+                                   "A B C D E F G H I J K L M",
+                                   "NOPQRSTUVWXYZ",
+                                   "0123456789  - + = / [ ] ( ) { }"};
+
+    int ntimes1 = names.length;
+    Set time_set =
+      new Linear1DSet(time_type, 0.0, (double) (ntimes1 - 1.0), ntimes1);
+
+    FieldImpl text_field = new FieldImpl(text_function, time_set);
+
+    for (int i=0; i<ntimes1; i++) {
+      Data[] td = {new Real(RealType.Latitude, 30.0 * i - 60.0),
+                   new Real(RealType.Longitude, 60.0 * (ntimes1 - i) - 120.0),
+                   new Text(text, names[i])};
+
+      Tuple tt = new Tuple(text_tuple, td);
+      text_field.setSample(i, tt);
+    }
+
+    ScalarMap tmap = new ScalarMap(text, Display.Text);
+    dpys[0].addMap(tmap);
+    tcontrol = (TextControl) tmap.getControl();
+    tcontrol.setSphere(sphere);
+    tcontrol.setCenter(true);
+    if (sphere) {
+      dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Latitude));
+      dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Longitude));
+    }
+    else {
+      dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+      dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+      tcontrol.setAutoSize(true);
+    }
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    DataReferenceImpl ref_text_field =
+      new DataReferenceImpl("ref_text_field");
+    ref_text_field.setData(text_field);
+    dpys[0].addReference(ref_text_field, null);
+  }
+
+  // SL 16 July 2003
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BorderLayout());
+    panel.add(new TextControlWidget(tcontrol));
+    return panel;
+  }
+
+  String getFrameTitle() {
+    return "text in Java3D with interactive settings";
+  }
+
+  public String toString() {
+    return " sphere: text in Java3D with interactive settings";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test45(args);
+  }
+}
diff --git a/examples/Test46.java b/examples/Test46.java
new file mode 100644
index 0000000..92d8942
--- /dev/null
+++ b/examples/Test46.java
@@ -0,0 +1,146 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test46
+  extends UISkeleton
+{
+  public Test46() { }
+
+  public Test46(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+
+    float[][] values = {{0.0f, 1.0f, 2.0f, 3.0f, 0.0f, 1.0f}};
+    int size = values[0].length;
+    Integer1DSet ir_set = new Integer1DSet(size);
+    FlatField histogram1 = new FlatField(ir_histogram, ir_set);
+    histogram1.setSamples(values);
+
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.ShapeScale));
+    dpys[0].addMap(new ScalarMap(count, Display.Green));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Blue));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Red));
+    ScalarMap shape_map = new ScalarMap(count, Display.Shape);
+    dpys[0].addMap(shape_map);
+
+    ScalarMap shape_map2 = new ScalarMap(count, Display.Shape);
+    dpys[0].addMap(shape_map2);
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    dpys[0].addReference(ref_histogram1, null);
+  }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    Vector v = dpys[0].getMapVector();
+    ScalarMap shape_map = (ScalarMap )v.elementAt(3);
+    ScalarMap shape_map2 = (ScalarMap )v.elementAt(4);
+
+    RealType count = (RealType )shape_map.getScalar();
+
+    VisADLineArray cross = new VisADLineArray();
+    cross.coordinates = new float[]
+      {0.2f,  0.2f, 0.0f,    -0.2f, -0.2f, 0.0f,
+       0.2f, -0.2f, 0.0f,    -0.2f,  0.2f, 0.0f};
+    cross.vertexCount = cross.coordinates.length / 3;
+
+    VisADLineArray box = new VisADLineArray();
+    box.coordinates = new float[]
+      {0.1f,  0.1f, 0.0f,     0.1f, -0.1f, 0.0f,
+       0.1f, -0.1f, 0.0f,    -0.1f, -0.1f, 0.0f,
+      -0.1f, -0.1f, 0.0f,    -0.1f,  0.1f, 0.0f,
+      -0.1f,  0.1f, 0.0f,     0.1f,  0.1f, 0.0f};
+    box.vertexCount = box.coordinates.length / 3;
+
+    VisADTriangleArray tri = new VisADTriangleArray();
+    tri.coordinates = new float[]
+      {-0.1f, -0.05f, 0.0f,    0.1f, -0.05f, 0.0f,
+        0.0f,  0.1f,  0.0f};
+    tri.vertexCount = tri.coordinates.length / 3;
+    // explicitly set colors in tri to override any color ScalarMaps
+    tri.colors = new byte[]
+      {-1, -1, 0,  -1, -1, 0,  -1, -1, 0};
+
+    VisADQuadArray square = new VisADQuadArray();
+    square.coordinates = new float[]
+      {0.1f,  0.1f, 0.0f,     0.1f, -0.1f, 0.0f,
+      -0.1f, -0.1f, 0.0f,    -0.1f,  0.1f, 0.0f};
+    square.vertexCount = square.coordinates.length / 3;
+
+    float[][] counts = {{0.0f, 1.0f, 2.0f, 3.0f}};
+    Gridded1DSet count_set =
+      new Gridded1DSet(count, counts, counts[0].length);
+
+    VisADGeometryArray[] shapes = {cross, box, tri, square};
+    ShapeControl shape_control = (ShapeControl) shape_map.getControl();
+    shape_control.setShapeSet(count_set);
+    shape_control.setShapes(shapes);
+
+    VisADGeometryArray[] shapes2 = {square, tri, box, cross};
+    ShapeControl shape_control2 = (ShapeControl) shape_map2.getControl();
+    shape_control2.setShapeSet(count_set);
+    shape_control2.setShapes(shapes2);
+
+    return null;
+  }
+
+  String getFrameTitle() { return "shape in Java2D"; }
+
+  public String toString() { return ": shape in Java2D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test46(args);
+  }
+}
diff --git a/examples/Test47.java b/examples/Test47.java
new file mode 100644
index 0000000..ab7fd86
--- /dev/null
+++ b/examples/Test47.java
@@ -0,0 +1,182 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test47
+  extends UISkeleton
+{
+  public Test47() { }
+
+  public Test47(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    TextType text = new TextType("text");
+
+    float[][] values;
+    values = new float[][] {{0.0f, 1.0f, 2.0f, 3.0f, 0.0f, 1.0f}};
+    int size = values[0].length;
+    Integer1DSet ir_set = new Integer1DSet(size);
+    FlatField histogram1 = new FlatField(ir_histogram, ir_set);
+    histogram1.setSamples(values);
+
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.ShapeScale));
+    dpys[0].addMap(new ScalarMap(count, Display.Green));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Blue));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Red));
+    ScalarMap shape_map = new ScalarMap(count, Display.Shape);
+    dpys[0].addMap(shape_map);
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    dpys[0].addReference(ref_histogram1, null);
+  }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap shape_map = (ScalarMap )dpys[0].getMapVector().lastElement();
+
+    RealType count = (RealType )shape_map.getScalar();
+
+    float[][] counts = new float[][] {{0.0f, 1.0f, 2.0f, 3.0f}};
+
+    Gridded1DSet count_set = new Gridded1DSet(count, counts, counts[0].length);
+
+    VisADLineArray cross = new VisADLineArray();
+    cross.coordinates = new float[]
+      {0.1f,  0.1f, 0.0f,    -0.1f, -0.1f, 0.0f,
+       0.1f, -0.1f, 0.0f,    -0.1f,  0.1f, 0.0f};
+    cross.vertexCount = cross.coordinates.length / 3;
+
+    VisADQuadArray cube = new VisADQuadArray();
+    cube.coordinates = new float[]
+      {0.1f,  0.1f, -0.1f,     0.1f, -0.1f, -0.1f,
+       0.1f, -0.1f, -0.1f,    -0.1f, -0.1f, -0.1f,
+      -0.1f, -0.1f, -0.1f,    -0.1f,  0.1f, -0.1f,
+      -0.1f,  0.1f, -0.1f,     0.1f,  0.1f, -0.1f,
+
+       0.1f,  0.1f,  0.1f,     0.1f, -0.1f,  0.1f,
+       0.1f, -0.1f,  0.1f,    -0.1f, -0.1f,  0.1f,
+      -0.1f, -0.1f,  0.1f,    -0.1f,  0.1f,  0.1f,
+      -0.1f,  0.1f,  0.1f,     0.1f,  0.1f,  0.1f,
+
+       0.1f,  0.1f,  0.1f,     0.1f,  0.1f, -0.1f,
+       0.1f,  0.1f, -0.1f,     0.1f, -0.1f, -0.1f,
+       0.1f, -0.1f, -0.1f,     0.1f, -0.1f,  0.1f,
+       0.1f, -0.1f,  0.1f,     0.1f,  0.1f,  0.1f,
+
+      -0.1f,  0.1f,  0.1f,    -0.1f,  0.1f, -0.1f,
+      -0.1f,  0.1f, -0.1f,    -0.1f, -0.1f, -0.1f,
+      -0.1f, -0.1f, -0.1f,    -0.1f, -0.1f,  0.1f,
+      -0.1f, -0.1f,  0.1f,    -0.1f,  0.1f,  0.1f,
+
+       0.1f,  0.1f,  0.1f,     0.1f,  0.1f, -0.1f,
+       0.1f,  0.1f, -0.1f,    -0.1f,  0.1f, -0.1f,
+      -0.1f,  0.1f, -0.1f,    -0.1f,  0.1f,  0.1f,
+      -0.1f,  0.1f,  0.1f,     0.1f,  0.1f,  0.1f,
+
+       0.1f, -0.1f,  0.1f,     0.1f, -0.1f, -0.1f,
+       0.1f, -0.1f, -0.1f,    -0.1f, -0.1f, -0.1f,
+      -0.1f, -0.1f, -0.1f,    -0.1f, -0.1f,  0.1f,
+      -0.1f, -0.1f,  0.1f,     0.1f, -0.1f,  0.1f};
+
+    cube.vertexCount = cube.coordinates.length / 3;
+    cube.normals = new float[144];
+    for (int i=0; i<24; i+=3) {
+      cube.normals[i]     =  0.0f;
+      cube.normals[i+1]   =  0.0f;
+      cube.normals[i+2]   = -1.0f;
+
+      cube.normals[i+24]  =  0.0f;
+      cube.normals[i+25]  =  0.0f;
+      cube.normals[i+26]  =  1.0f;
+
+      cube.normals[i+48]  =  1.0f;
+      cube.normals[i+49]  =  0.0f;
+      cube.normals[i+50]  =  0.0f;
+
+      cube.normals[i+72]  = -1.0f;
+      cube.normals[i+73]  =  0.0f;
+      cube.normals[i+74]  =  0.0f;
+
+      cube.normals[i+96]  =  0.0f;
+      cube.normals[i+97]  =  1.0f;
+      cube.normals[i+98]  =  0.0f;
+
+      cube.normals[i+120] =  0.0f;
+      cube.normals[i+121] = -1.0f;
+      cube.normals[i+122] =  0.0f;
+    }
+
+    double[] start = {0.0, 0.0, 0.0}; // text at origin
+    double[] base = {0.1, 0.0, 0.0};  // text out along XAxis
+    double[] up = {0.0, 0.1, 0.0};    // character up along YAxis
+    boolean center = true;            // center text
+    VisADLineArray one_two =
+      PlotText.render_label("1.2", start, base, up, center);
+
+    VisADGeometryArray[] shapes;
+    shapes = new VisADGeometryArray[] {one_two, cube, cross, cube};
+
+    ShapeControl shape_control = (ShapeControl) shape_map.getControl();
+    shape_control.setShapeSet(count_set);
+    shape_control.setShapes(shapes);
+
+    return null;
+  }
+
+  String getFrameTitle() { return "shape in Java3D"; }
+
+  public String toString() { return ": shape in Java3D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test47(args);
+  }
+}
diff --git a/examples/Test48.java b/examples/Test48.java
new file mode 100644
index 0000000..bf259ab
--- /dev/null
+++ b/examples/Test48.java
@@ -0,0 +1,85 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test48
+  extends TestSkeleton
+{
+  public Test48() { }
+
+  public Test48(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+
+    dpys[0].addMap(new ConstantMap(0.0, Display.Red));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.0, Display.Blue));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setTextureEnable(false);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": 2-D surface and ConstantMap colors"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test48(args);
+  }
+}
diff --git a/examples/Test49.java b/examples/Test49.java
new file mode 100644
index 0000000..327a05b
--- /dev/null
+++ b/examples/Test49.java
@@ -0,0 +1,85 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test49
+  extends TestSkeleton
+{
+  public Test49() { }
+
+  public Test49(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+
+    float[][] values = histogram1.getFloats();
+    for (int i=0; i<values[0].length; i+=13) values[0][i] = Float.NaN;
+    histogram1.setSamples(values);
+
+    dpys[0].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+
+    dpys[0].addMap(new ConstantMap(0.0, Display.Red));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.0, Display.Blue));
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    dpys[0].addReference(ref_histogram1, null);
+  }
+
+  public String toString()
+  {
+    return ": 1-D line w/missing and ConstantMap colors";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test49(args);
+  }
+}
diff --git a/examples/Test50.java b/examples/Test50.java
new file mode 100644
index 0000000..b8e28c2
--- /dev/null
+++ b/examples/Test50.java
@@ -0,0 +1,130 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.Graphics;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.awt.image.BufferedImage;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test50
+  extends TestSkeleton
+{
+  public Test50() { }
+
+  public Test50(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.RGB));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "capture image in Java2D"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) dpys[0].getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+
+    JFrame jframe1 = new JFrame("captured image from Java2D");
+    jframe1.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    JPanel panel1 = new JPanel();
+    panel1.setLayout(new BoxLayout(panel1, BoxLayout.X_AXIS));
+    panel1.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel1.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    jframe1.setContentPane(panel1);
+    jframe1.pack();
+    jframe1.setSize(jframe.getSize().width, jframe.getSize().height);
+    jframe1.setVisible(true);
+
+    while (true) {
+      Graphics gp = panel1.getGraphics();
+      BufferedImage image = dpys[0].getImage();
+      gp.drawImage(image, 0, 0, panel1);
+      gp.dispose();
+      try {
+        Thread.sleep(1000);
+      }
+      catch (InterruptedException e) {
+      }
+    }
+  }
+
+  public String toString() { return ": image capture in Java2D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test50(args);
+  }
+}
diff --git a/examples/Test51.java b/examples/Test51.java
new file mode 100644
index 0000000..dbe6bb4
--- /dev/null
+++ b/examples/Test51.java
@@ -0,0 +1,145 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.Graphics;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.awt.image.BufferedImage;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.*;
+
+public class Test51
+  extends TestSkeleton
+{
+  public Test51() { }
+
+  public Test51(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.RGB));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    // mode.setTextureEnable(false);
+    mode.setTextureEnable(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "capture image in Java3D"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) dpys[0].getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+
+    JFrame jframe1 = new JFrame("captured image from Java3D");
+    jframe1.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    JPanel panel1 = new JPanel();
+    panel1.setLayout(new BoxLayout(panel1, BoxLayout.X_AXIS));
+    panel1.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel1.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    jframe1.setContentPane(panel1);
+    jframe1.pack();
+    jframe1.setSize(jframe.getSize().width, jframe.getSize().height);
+    jframe1.setVisible(true);
+
+    while (true) {
+/*
+VisADCanvasJ3D canvas =
+  ((DisplayRendererJ3D) dpys[0].getDisplayRenderer()).getCanvas();
+Screen3D scr = canvas.getScreen3D();
+System.out.println(scr.getPhysicalScreenHeight() + " " +
+                   scr.getPhysicalScreenWidth() + " " +
+                   scr.getSize().getHeight() + " " +
+                   scr.getSize().getWidth());
+// prints 0.28899555555555556 0.3612444444444445 1024.0 1280.0
+*/
+      Graphics gp = panel1.getGraphics();
+      BufferedImage image = dpys[0].getImage();
+      gp.drawImage(image, 0, 0, panel1);
+      gp.dispose();
+      try {
+        Thread.sleep(1000);
+      }
+      catch (InterruptedException e) {
+      }
+    }
+  }
+
+  public String toString() { return ": image capture in Java3D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test51(args);
+  }
+}
diff --git a/examples/Test52.java b/examples/Test52.java
new file mode 100644
index 0000000..18324ce
--- /dev/null
+++ b/examples/Test52.java
@@ -0,0 +1,125 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.Graphics;
+
+import java.awt.image.BufferedImage;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test52
+  extends TestSkeleton
+{
+  public Test52() { }
+
+  public Test52(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display", 300, 300);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.RGB));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "captured image from Java2D"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    JPanel panel1 = new JPanel();
+    panel1.setLayout(new BoxLayout(panel1, BoxLayout.X_AXIS));
+    panel1.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel1.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    jframe.setContentPane(panel1);
+    jframe.pack();
+    jframe.setSize(300, 300);
+    jframe.setVisible(true);
+
+    while (true) {
+      Graphics gp = panel1.getGraphics();
+      BufferedImage image = dpys[0].getImage();
+      gp.drawImage(image, 0, 0, panel1);
+      gp.dispose();
+      try {
+        Thread.sleep(1000);
+      }
+      catch (InterruptedException e) {
+      }
+      System.out.println("delay & redraw");
+    }
+  }
+
+  public String toString()
+  {
+    return ": image capture from offscreen in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test52(args);
+  }
+}
diff --git a/examples/Test53.java b/examples/Test53.java
new file mode 100644
index 0000000..0ccc9c9
--- /dev/null
+++ b/examples/Test53.java
@@ -0,0 +1,185 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test53
+  extends UISkeleton
+{
+  private boolean isServer = false;
+
+  public Test53() { }
+
+  public Test53(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+
+    dpys[0].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+
+    dpys[0].addMap(new ConstantMap(0.0, Display.Red));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.0, Display.Blue));
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    dpys[0].addReference(ref_histogram1, null);
+
+    isServer = true;
+  }
+
+  private static String colorName(float[] color)
+  {
+    int red = (int )(256.0f * color[0]) / 64;
+    int green = (int )(256.0f * color[1]) / 64;
+    int blue = (int )(256.0f * color[2]) / 64;
+
+    if (red == green && green == blue) {
+      switch (red) {
+      case 0: return "black";
+      case 1: return "dark gray";
+      case 2: return "light gray";
+      default: return "white";
+      }
+    }
+    if (red > 0) {
+      if (green > 0) {
+        if (blue > 0) {
+        } else {
+          return "yellow";
+        }
+      } else {                    // green == 0
+        if (blue > 0) {   // green == 0
+          return "magenta";
+        } else {
+          return "red";
+        }
+      }
+    } else {                             // red == 0
+      if (green > 0) {
+        if (blue > 0) {
+          return "cyan";
+        } else {
+          return "green";
+        }
+      } else {
+        if (blue > 0) {
+          return "blue";
+        }
+      }
+    }
+
+    return "color[" + color[0] + "/" + color[1] + "/" + color[2] + "]";
+  }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    super.setupUI(dpys);
+
+    float[][] bg_color = {{1.0f, 0.0f, 1.0f},
+                          {1.0f, 1.0f, 0.0f},
+                          {0.0f, 1.0f, 1.0f}};
+    float[][] box_color = {{1.0f, 0.0f, 0.0f},
+                           {0.0f, 1.0f, 0.0f},
+                           {0.0f, 0.0f, 1.0f},
+                           {0.5f, 0.5f, 0.5f}};
+    float[][] cursor_color = {{0.5f, 0.5f, 0.5f},
+                              {1.0f, 0.0f, 0.0f},
+                              {0.0f, 1.0f, 0.0f},
+                              {0.0f, 0.0f, 1.0f}};
+    DisplayRenderer displayRenderer = dpys[0].getDisplayRenderer();
+    int i3 = 0;
+    int i4 = 0;
+    while (isServer) {
+      // delay(5000);
+      try {
+        Thread.sleep(5000);
+      }
+      catch (InterruptedException e) {
+      }
+
+      boolean boxOn = (i3 != 2);
+//      System.out.println("\ndelay\n");
+      System.out.println("\n" +
+                         colorName(bg_color[i3]) + " background, " +
+                         (boxOn ? colorName(box_color[i4]) + " box" :
+                          "box off") + ", " +
+                         colorName(cursor_color[i4]) + " cursor\n");
+      displayRenderer.setBackgroundColor(bg_color[i3][0],
+                                         bg_color[i3][1],
+                                         bg_color[i3][2]);
+      displayRenderer.setBoxOn(boxOn);
+      displayRenderer.setBoxColor(box_color[i4][0],
+                                  box_color[i4][1],
+                                  box_color[i4][2]);
+      displayRenderer.setCursorColor(cursor_color[i4][0],
+                                     cursor_color[i4][1],
+                                     cursor_color[i4][2]);
+      if (i3 == 2) {
+        i3 = 0;
+      } else {
+        i3++;
+      }
+      if (i4 == 3) {
+        i4 = 0;
+      } else {
+        i4++;
+      }
+    }
+  }
+
+  String getFrameTitle() { return "background color in Java2D"; }
+
+  public String toString() { return ": background color in Java2D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test53(args);
+  }
+}
diff --git a/examples/Test54.java b/examples/Test54.java
new file mode 100644
index 0000000..b093484
--- /dev/null
+++ b/examples/Test54.java
@@ -0,0 +1,113 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test54
+  extends TestSkeleton
+{
+  public Test54() { }
+
+  public Test54(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+
+    dpys[0].addMap(new ScalarMap(count, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+
+    dpys[0].addMap(new ConstantMap(0.0, Display.Red));
+    dpys[0].addMap(new ConstantMap(1.0, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.0, Display.Blue));
+
+    dpys[0].getDisplayRenderer().setBackgroundColor(1.0f, 0.0f, 1.0f);
+
+    DataReferenceImpl ref_histogram1;
+    ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    dpys[0].addReference(ref_histogram1, null);
+
+    boolean forever = true;
+    boolean[] box_on = {true, true, true, false};
+    float[][] box_color = {{1.0f, 0.0f, 0.0f},
+                           {0.0f, 1.0f, 0.0f},
+                           {0.0f, 0.0f, 1.0f},
+                           {0.5f, 0.5f, 0.5f}};
+    float[][] cursor_color = {{0.5f, 0.5f, 0.5f},
+                              {1.0f, 0.0f, 0.0f},
+                              {0.0f, 1.0f, 0.0f},
+                              {0.0f, 0.0f, 1.0f}};
+    DisplayRenderer displayRenderer = dpys[0].getDisplayRenderer();
+    int index = 0;
+    while (forever) {
+      // delay(5000);
+      try {
+        Thread.sleep(5000);
+      }
+      catch (InterruptedException e) {
+      }
+      System.out.println("\ndelay\n");
+      displayRenderer.setBoxOn(box_on[index]);
+      displayRenderer.setBoxColor(box_color[index][0],
+                                  box_color[index][1],
+                                  box_color[index][2]);
+      displayRenderer.setCursorColor(cursor_color[index][0],
+                                     cursor_color[index][1],
+                                     cursor_color[index][2]);
+      index++;
+      if (index > 3) index = 0;
+    }
+  }
+
+  String getFrameTitle() { return "background color in Java3D"; }
+
+  public String toString() { return ": background color in Java3D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test54(args);
+  }
+}
diff --git a/examples/Test55.java b/examples/Test55.java
new file mode 100644
index 0000000..bc87d28
--- /dev/null
+++ b/examples/Test55.java
@@ -0,0 +1,132 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.Naming;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DirectManipulationRendererJ2D;
+import visad.java2d.DisplayImplJ2D;
+
+public class Test55
+  extends UISkeleton
+{
+  boolean hasClientServerMode() { return false; }
+
+  public Test55() { }
+
+  public Test55(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    try {
+
+      int size = 64;
+      FlatField histogram1;
+      histogram1 = FlatField.makeField(ir_histogram, size, false);
+      Real direct = new Real(ir_radiance, 2.0);
+      Real[] reals14 = {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                       new Real(vis_radiance, 1.0)};
+      RealTuple direct_tuple = new RealTuple(reals14);
+
+      dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+      dpys[0].addMap(new ScalarMap(count, Display.YAxis));
+
+      GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+      mode.setPointSize(5.0f);
+      mode.setPointMode(false);
+
+      DataReferenceImpl ref_direct = new DataReferenceImpl("ref_direct");
+      ref_direct.setData(direct);
+      DataReference[] refs141 = {ref_direct};
+      dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs141, null);
+
+      DataReferenceImpl ref_direct_tuple;
+      ref_direct_tuple = new DataReferenceImpl("ref_direct_tuple");
+      ref_direct_tuple.setData(direct_tuple);
+      DataReference[] refs142 = {ref_direct_tuple};
+      dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs142, null);
+
+      DataReferenceImpl ref_histogram1;
+      ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+      ref_histogram1.setData(histogram1);
+      DataReference[] refs143 = {ref_histogram1};
+      dpys[0].addReferences(new DirectManipulationRendererJ2D(), refs143, null);
+
+      // create local DataReferenceImpls
+      DataReferenceImpl[] data_refs = new DataReferenceImpl[3];
+      data_refs[0] = ref_histogram1;
+      data_refs[1] = ref_direct;
+      data_refs[2] = ref_direct_tuple;
+
+      // create RemoteDataReferences
+      RemoteDataReferenceImpl[] rem_data_refs;
+      rem_data_refs = new RemoteDataReferenceImpl[3];
+      rem_data_refs[0] = new RemoteDataReferenceImpl(data_refs[0]);
+      rem_data_refs[1] = new RemoteDataReferenceImpl(data_refs[1]);
+      rem_data_refs[2] = new RemoteDataReferenceImpl(data_refs[2]);
+
+      RemoteServerImpl obj = new RemoteServerImpl(rem_data_refs);
+      Naming.rebind("///RemoteServerTest", obj);
+
+      System.out.println("RemoteServer bound in registry");
+    }
+    catch (Exception e) {
+      System.out.println("\n\nDid you run 'rmiregistry &' first?\n\n");
+      System.out.println("collaboration server exception: " + e.getMessage());
+      e.printStackTrace();
+      System.exit(1);
+    }
+  }
+
+  public String toString()
+  {
+    return ": collaborative direct manipulation server in Java2D" +
+                "\n\trun rmiregistry first" +
+                "\n\tany number of clients may connect";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test55(args);
+  }
+}
diff --git a/examples/Test56.java b/examples/Test56.java
new file mode 100644
index 0000000..1a3f264
--- /dev/null
+++ b/examples/Test56.java
@@ -0,0 +1,138 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.Naming;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DirectManipulationRendererJ2D;
+import visad.java2d.DisplayImplJ2D;
+
+public class Test56
+  extends UISkeleton
+{
+  private String domain;
+
+  boolean hasClientServerMode() { return false; }
+
+  public Test56() { }
+
+  public Test56(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { domain = null; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (domain == null) {
+      domain = args[argc];
+    } else {
+      System.err.println(testName + ": Ignoring extra domain \"" +
+                         args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    try {
+
+      System.out.println("RemoteClientTestImpl.main: begin remote activity");
+      System.out.println("  to " + domain);
+
+      if (domain == null) {
+        domain = "///RemoteServerTest";
+      }
+      else {
+        domain = "//" + domain + "/RemoteServerTest";
+      }
+      RemoteServer remote_obj = (RemoteServer) Naming.lookup(domain);
+
+      System.out.println("connected");
+
+      RemoteDataReference histogram_ref = remote_obj.getDataReference(0);
+      RemoteDataReference direct_ref = remote_obj.getDataReference(1);
+      RemoteDataReference direct_tuple_ref = remote_obj.getDataReference(2);
+
+      RealTupleType dtype;
+      dtype = (RealTupleType) direct_tuple_ref.getData().getType();
+
+      dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(0),
+                                    Display.XAxis));
+      dpys[0].addMap(new ScalarMap((RealType) dtype.getComponent(1),
+                                    Display.YAxis));
+
+      GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+      mode.setPointSize(5.0f);
+      mode.setPointMode(false);
+
+      if (!(dpys[0] instanceof DisplayImpl)) {
+        throw new VisADException("Need DisplayImpl for collaborative client test");
+      }
+      DisplayImpl di = (DisplayImpl )dpys[0];
+
+      RemoteDisplayImpl remote_display1 = new RemoteDisplayImpl(di);
+      DataReference[] refs151 = {histogram_ref};
+      remote_display1.addReferences(new DirectManipulationRendererJ2D(),
+                                    refs151, null);
+
+      DataReference[] refs152 = {direct_ref};
+      remote_display1.addReferences(new DirectManipulationRendererJ2D(),
+                                    refs152, null);
+
+      DataReference[] refs153 = {direct_tuple_ref};
+      remote_display1.addReferences(new DirectManipulationRendererJ2D(),
+                                    refs153, null);
+    }
+    catch (Exception e) {
+      System.out.println("collaboration client exception: " + e.getMessage());
+      e.printStackTrace(System.out);
+      System.exit(1);
+    }
+  }
+
+  public String toString()
+  {
+    return " ip.name: collaborative direct manipulation client in Java2D" +
+                "\n\tsecond parameter is server IP name";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test56(args);
+  }
+}
diff --git a/examples/Test57.java b/examples/Test57.java
new file mode 100644
index 0000000..aed558a
--- /dev/null
+++ b/examples/Test57.java
@@ -0,0 +1,136 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test57
+  extends UISkeleton
+  implements DisplayListener
+{
+
+  ProjectionControl control;
+
+  public Test57() { }
+
+  public Test57(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "fly-through in Java3D"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) dpys[0].getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+
+    if (!isClient()) {
+      // change aspect ratio
+      control = dpys[0].getProjectionControl();
+      // control.setAspect(new double[] {1.0, 0.5, 0.25});
+      control.setAspectCartesian(new double[] {2.0, 1.0, 0.5});
+      control.setAspect(new double[] {0.5, 0.5, 0.5});
+
+      dpys[0].addDisplayListener(this);
+      rotate(dpys[0]);
+    }
+  }
+
+  public void displayChanged(DisplayEvent e)
+    throws RemoteException, VisADException {
+    if (e.getId() == DisplayEvent.FRAME_DONE) {
+      rotate((LocalDisplay) e.getDisplay());
+    }
+  }
+
+  public void rotate(LocalDisplay display)
+    throws RemoteException, VisADException {
+    double[] matrix = control.getMatrix();
+    double[] mult = display.make_matrix(0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0);
+    control.setMatrix(display.multiply_matrix(mult, matrix));
+  }
+
+  public String toString()
+  {
+    return ": scripted fly-through & aspect ratio in Java3D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test57(args);
+  }
+}
diff --git a/examples/Test58.java b/examples/Test58.java
new file mode 100644
index 0000000..7ff9298
--- /dev/null
+++ b/examples/Test58.java
@@ -0,0 +1,134 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test58
+  extends UISkeleton
+  implements DisplayListener
+{
+
+  ProjectionControl control;
+
+  public Test58() { }
+
+  public Test58(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "fly-through in Java2D"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) dpys[0].getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+
+    if (!isClient()) {
+      /* change aspect ratio */
+      control = dpys[0].getProjectionControl();
+      // control.setAspect(new double[] {1.0, 0.5});
+      control.setAspectCartesian(new double[] {1.0, 0.5});
+
+      dpys[0].addDisplayListener(this);
+      rotate(dpys[0]);
+    }
+  }
+
+  public void displayChanged(DisplayEvent e)
+    throws RemoteException, VisADException {
+    if (e.getId() == DisplayEvent.FRAME_DONE) {
+      rotate((LocalDisplay) e.getDisplay());
+    }
+  }
+
+  public void rotate(LocalDisplay display)
+    throws RemoteException, VisADException {
+    double[] matrix = control.getMatrix();
+    double[] mult = display.make_matrix(0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0);
+    control.setMatrix(display.multiply_matrix(mult, matrix));
+  }
+
+  public String toString()
+  {
+    return ": scripted fly-through & aspect ratio in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test58(args);
+  }
+}
diff --git a/examples/Test59.java b/examples/Test59.java
new file mode 100644
index 0000000..52e9f58
--- /dev/null
+++ b/examples/Test59.java
@@ -0,0 +1,113 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+import java.util.Random;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.ContourWidget;
+
+public class Test59
+  extends UISkeleton
+{
+  public Test59() { }
+
+  public Test59(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType index = RealType.getRealType("index");
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types = {RealType.Latitude, RealType.Longitude,
+                        vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types);
+    FunctionType image_tuple = new FunctionType(index, radiance);
+
+    int size = 216;
+    Set domain_set = new Integer1DSet(size);
+    FlatField imaget1 = new FlatField(image_tuple, domain_set);
+    float[][] values = new float[4][size];
+    Random random = new Random();
+    for (int i=0; i<size; i++) {
+      values[0][i] = 2.0f * random.nextFloat() - 1.0f;
+      values[1][i] = 2.0f * random.nextFloat() - 1.0f;
+      values[2][i] = 2.0f * random.nextFloat() - 1.0f;
+      values[3][i] = (float) Math.sqrt(values[0][i] * values[0][i] +
+                                       values[1][i] * values[1][i] +
+                                       values[2][i] * values[2][i]);
+    }
+    imaget1.setSamples(values);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Radius, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1contour;
+    map1contour = new ScalarMap(ir_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "VisAD irregular iso-level controls"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap map1contour = (ScalarMap )dpys[0].getMapVector().lastElement();
+    return new ContourWidget(map1contour);
+  }
+
+  public String toString()
+  {
+    return ": colored iso-surfaces from scatter data and ContourWidget";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test59(args);
+  }
+}
diff --git a/examples/Test60.java b/examples/Test60.java
new file mode 100644
index 0000000..f565eff
--- /dev/null
+++ b/examples/Test60.java
@@ -0,0 +1,103 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+import java.util.Random;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class Test60
+  extends UISkeleton
+{
+  public Test60() { }
+
+  public Test60(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType index = RealType.getRealType("index");
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types = {RealType.Latitude, RealType.Longitude,
+                        vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types);
+    FunctionType image_tuple = new FunctionType(index, radiance);
+
+    int size = 216;
+    Set domain_set = new Integer1DSet(size);
+    FlatField imaget1 = new FlatField(image_tuple, domain_set);
+    float[][] values = new float[4][size];
+    Random random = new Random();
+    for (int i=0; i<size; i++) {
+      values[0][i] = 2.0f * random.nextFloat() - 1.0f;
+      values[1][i] = 2.0f * random.nextFloat() - 1.0f;
+      values[2][i] = 2.0f * random.nextFloat() - 1.0f;
+      values[3][i] = (float) Math.sqrt(values[0][i] * values[0][i] +
+                                       values[1][i] * values[1][i]);
+    }
+    imaget1.setSamples(values);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1contour;
+    map1contour = new ScalarMap(ir_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+    ContourControl control1contour;
+    control1contour = (ContourControl) map1contour.getControl();
+    control1contour.enableContours(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  String getFrameTitle() { return "contours from scatter data in Java2D"; }
+
+  public String toString()
+  {
+    return ": colored contours from scatter data in Java2D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test60(args);
+  }
+}
diff --git a/examples/Test61.java b/examples/Test61.java
new file mode 100644
index 0000000..cc087b9
--- /dev/null
+++ b/examples/Test61.java
@@ -0,0 +1,208 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+import javax.swing.JPanel;
+import javax.swing.BoxLayout;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.LabeledColorWidget;
+import visad.util.SelectRangeWidget;
+
+public class Test61
+  extends UISkeleton
+{
+  private boolean nice = false;
+
+  private int texture3DMode = GraphicsModeControl.STACK2D;
+
+  public Test61() { }
+
+  public Test61(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { 
+    nice = false; 
+    texture3DMode = GraphicsModeControl.STACK2D;
+  }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (argc == 0 && !args[argc].equals("t")) {
+       nice = true;
+    }
+    if (argc == 1 || args[argc].equals("t")) {
+       texture3DMode = GraphicsModeControl.TEXTURE3D;
+    }
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  private static float[][] buildTable(float[][] table)
+  {
+    int length = table[0].length;
+    for (int i=0; i<length; i++) {
+      float a = ((float) i) / ((float) (table[3].length - 1));
+      table[3][i] = a;
+    }
+    return table;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType xr = RealType.getRealType("xr");
+    RealType yr = RealType.getRealType("yr");
+    RealType zr = RealType.getRealType("zr");
+    RealType wr = RealType.getRealType("wr");
+    RealType[] types3d = {xr, yr, zr};
+    RealTupleType earth_location3d = new RealTupleType(types3d);
+    FunctionType grid_tuple = new FunctionType(earth_location3d, wr);
+
+    // int NX = 32;
+    // int NY = 32;
+    // int NZ = 32;
+    int NX = 35;
+    int NY = 35;
+    int NZ = 35;
+    Integer3DSet set = new Integer3DSet(NX, NY, NZ);
+    FlatField grid3d = new FlatField(grid_tuple, set);
+
+    float[][] values = new float[1][NX * NY * NZ];
+    int k = 0;
+    for (int iz=0; iz<NZ; iz++) {
+      // double z = Math.PI * (-1.0 + 2.0 * iz / (NZ - 1.0));
+      double z = Math.PI * (-1.0 + 2.0 * iz * iz / ((NZ - 1.0)*(NZ - 1.0)) );
+      for (int iy=0; iy<NY; iy++) {
+        double y = -1.0 + 2.0 * iy / (NY - 1.0);
+        for (int ix=0; ix<NX; ix++) {
+          double x = -1.0 + 2.0 * ix / (NX - 1.0);
+          double r = x - 0.5 * Math.cos(z);
+          double s = y - 0.5 * Math.sin(z);
+          double dist = Math.sqrt(r * r + s * s);
+          values[0][k] = (float) ((dist < 0.1) ? 10.0 : 1.0 / dist);
+          k++;
+        }
+      }
+    }
+    grid3d.setSamples(values);
+
+    dpys[0].addMap(new ScalarMap(xr, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(yr, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(zr, Display.ZAxis));
+
+    ScalarMap xrange = new ScalarMap(xr, Display.SelectRange);
+    ScalarMap yrange = new ScalarMap(yr, Display.SelectRange);
+    ScalarMap zrange = new ScalarMap(zr, Display.SelectRange);
+    dpys[0].addMap(xrange);
+    dpys[0].addMap(yrange);
+    dpys[0].addMap(zrange);
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    if (nice) mode.setTransparencyMode(DisplayImplJ3D.NICEST);
+    mode.setTexture3DMode(texture3DMode);
+
+    // new
+    RealType duh = RealType.getRealType("duh");
+    int NT = 32;
+    Linear2DSet set2 = new Linear2DSet(0.0, (double) NX, NT,
+                                       0.0, (double) NY, NT);
+    RealType[] types2d = {xr, yr};
+    RealTupleType domain2 = new RealTupleType(types2d);
+    FunctionType ftype2 = new FunctionType(domain2, duh);
+    float[][] v2 = new float[1][NT * NT];
+    for (int i=0; i<NT*NT; i++) {
+      v2[0][i] = (i * i) % (NT/2 +3);
+    }
+    // float[][] v2 = {{1.0f,2.0f,3.0f,4.0f}};
+    FlatField field2 = new FlatField(ftype2,set2);
+    field2.setSamples(v2);
+    dpys[0].addMap(new ScalarMap(duh, Display.RGB));
+
+    ScalarMap map1color = new ScalarMap(wr, Display.RGBA);
+    dpys[0].addMap(map1color);
+
+    ColorAlphaControl control = (ColorAlphaControl) map1color.getControl();
+    control.setTable(buildTable(control.getTable()));
+
+    DataReferenceImpl ref_grid3d = new DataReferenceImpl("ref_grid3d");
+    ref_grid3d.setData(grid3d);
+
+    DataReferenceImpl ref2 = new DataReferenceImpl("ref2");
+    ref2.setData(field2);
+
+    ConstantMap[] cmaps = {new ConstantMap(0.0, Display.TextureEnable)};
+    dpys[0].addReference(ref2, cmaps);
+
+    dpys[0].addReference(ref_grid3d, null);
+  }
+
+  String getFrameTitle0() { return "VisAD Color Alpha Widget"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    java.util.Vector mapVector = dpys[0].getMapVector();
+    final int numMaps = mapVector.size();
+
+    ScalarMap xrange = (ScalarMap )mapVector.elementAt(numMaps-5);
+    ScalarMap yrange = (ScalarMap )mapVector.elementAt(numMaps-4);
+    ScalarMap zrange = (ScalarMap )mapVector.elementAt(numMaps-3);
+
+    ScalarMap map1color = (ScalarMap )mapVector.elementAt(numMaps-1);
+
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.add(new LabeledColorWidget(map1color));
+    panel.add(new SelectRangeWidget(xrange));
+    panel.add(new SelectRangeWidget(yrange));
+    panel.add(new SelectRangeWidget(zrange));
+    return panel;
+  }
+
+  public String toString()
+  {
+    return " smooth texture3D: volume rendering and ColorAlphaWidget";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test61(args);
+  }
+}
diff --git a/examples/Test62.java b/examples/Test62.java
new file mode 100644
index 0000000..ddc108d
--- /dev/null
+++ b/examples/Test62.java
@@ -0,0 +1,103 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.ContourWidget;
+
+public class Test62
+  extends UISkeleton
+{
+  public Test62() { }
+
+  public Test62(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display1", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types3d = {RealType.Latitude, RealType.Longitude, RealType.Radius};
+    RealTupleType earth_location3d = new RealTupleType(types3d);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType grid_tuple = new FunctionType(earth_location3d, radiance);
+
+    int size3d = 6;
+    float level = 2.5f;
+    FlatField grid3d = FlatField.makeField(grid_tuple, size3d, true);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Radius, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1contour;
+    map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+    dpys[0].addMap(map1contour);
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setPolygonMode(DisplayImplJ3D.POLYGON_LINE);
+
+    DataReferenceImpl ref_grid3d = new DataReferenceImpl("ref_grid3d");
+    ref_grid3d.setData(grid3d);
+    dpys[0].addReference(ref_grid3d, null);
+  }
+
+  String getFrameTitle() { return "VisAD irregular iso-level controls"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    ScalarMap map1contour = (ScalarMap )dpys[0].getMapVector().lastElement();
+    return new ContourWidget(map1contour);
+  }
+
+  public String toString()
+  {
+    return ": edges of iso-surfaces from irregular grids and ContourWidget";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test62(args);
+  }
+}
diff --git a/examples/Test63.java b/examples/Test63.java
new file mode 100644
index 0000000..3db53c8
--- /dev/null
+++ b/examples/Test63.java
@@ -0,0 +1,153 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.*;
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test63
+  extends UISkeleton
+{
+  private boolean twoD;
+
+  boolean hasClientServerMode() { return false; }
+
+  public Test63() { }
+
+  public Test63(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { twoD = false; }
+
+  public int checkOption(String progName, char ch, String arg)
+  {
+    if (ch == '2') {
+      twoD = true;
+      return 1;
+    }
+
+    return 0;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    if (twoD) {
+      dpys[0] = new DisplayImplJ2D("display1");
+    } else {
+      dpys[0] = new DisplayImplJ3D("display1");
+    }
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    int size = 64;
+    DisplayImpl display1 = (DisplayImpl) dpys[0];
+
+    if (twoD) {
+      RealType count = RealType.getRealType("count");
+      FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+      FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+
+      System.out.print("Creating Java2D display...");
+      display1.addMap(new ScalarMap(count, Display.YAxis));
+      display1.addMap(new ScalarMap(ir_radiance, Display.XAxis));
+      display1.addMap(new ConstantMap(0.0, Display.Red));
+      display1.addMap(new ConstantMap(1.0, Display.Green));
+      display1.addMap(new ConstantMap(0.0, Display.Blue));
+
+      DataReferenceImpl ref_histogram1;
+      ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+      ref_histogram1.setData(histogram1);
+      display1.addReference(ref_histogram1, null);
+    } else {
+      RealType vis_radiance = RealType.getRealType("vis_radiance");
+      RealType[] types = {RealType.Latitude, RealType.Longitude};
+      RealType[] types2 = {vis_radiance, ir_radiance};
+      RealTupleType earth_location = new RealTupleType(types);
+      RealTupleType radiance = new RealTupleType(types2);
+      FunctionType image_tuple = new FunctionType(earth_location, radiance);
+      FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+      System.out.print("Creating Java3D display...");
+      display1.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+      display1.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+      display1.addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+      display1.addMap(new ScalarMap(vis_radiance, Display.Green));
+      display1.addMap(new ConstantMap(0.5, Display.Blue));
+      display1.addMap(new ConstantMap(0.5, Display.Red));
+
+      GraphicsModeControl mode = display1.getGraphicsModeControl();
+      mode.setPointSize(2.0f);
+      mode.setPointMode(false);
+      mode.setMissingTransparent(true);
+
+      DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+      ref_imaget1.setData(imaget1);
+      display1.addReference(ref_imaget1, null);
+    }
+
+    // create remote display
+    RemoteDisplayImpl[] rdi = new RemoteDisplayImpl[1];
+    rdi[0] = new RemoteDisplayImpl(display1);
+    System.out.println("done.");
+
+    // create RemoteServer
+    try {
+      RemoteServerImpl server = new RemoteServerImpl(rdi);
+      Naming.rebind("///RemoteSlaveDisplayTest", server);
+      System.out.println("RemoteServer bound in registry");
+    }
+    catch (Exception e) {
+      System.out.println("Couldn't construct RMI server.  Make sure " +
+                         "rmiregistry is running first.");
+      System.exit(1);
+    }
+  }
+
+  String getFrameTitle() { return "Remote slave display server"; }
+
+  public String toString()
+  {
+    return " [-2d]: remote slave display server" +
+                "\n\trun rmiregistry first" +
+                "\n\tany number of clients may connect" +
+                "\n\toptional parameter enables Java2D instead of Java3D";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test63(args);
+  }
+}
diff --git a/examples/Test64.java b/examples/Test64.java
new file mode 100644
index 0000000..fbca6cb
--- /dev/null
+++ b/examples/Test64.java
@@ -0,0 +1,125 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.*;
+import java.awt.event.*;
+import java.rmi.Naming;
+import java.rmi.RemoteException;
+import javax.swing.*;
+import visad.*;
+
+public class Test64
+  extends TestSkeleton
+{
+  private String domain;
+
+  boolean hasClientServerMode() { return false; }
+
+  public Test64() { }
+
+  public Test64(String args[])
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { domain = null; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (domain == null) {
+      domain = args[argc];
+    } else {
+      System.err.println(testName + ": Ignoring extra domain \"" +
+                         args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    return null;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+  }
+
+  void setupUI(LocalDisplay[] dpys) throws VisADException, RemoteException {
+    JFrame jframe  = new JFrame("Remote slave display client");
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    JPanel p = new JPanel();
+    p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
+    jframe.setContentPane(p);
+
+    try {
+      System.out.print("Connecting to ");
+      if (domain == null) {
+        System.out.print("localhost...");
+        domain = "///RemoteSlaveDisplayTest";
+      }
+      else {
+        System.out.print(domain + "...");
+        domain = "//" + domain + "/RemoteSlaveDisplayTest";
+      }
+      RemoteServer server = (RemoteServer) Naming.lookup(domain);
+      RemoteDisplay[] rmt_dpys = server.getDisplays();
+      RemoteDisplay display = rmt_dpys[0];
+      RemoteSlaveDisplayImpl rsdi = new RemoteSlaveDisplayImpl(display);
+      p.add(rsdi.getComponent());
+      System.out.println("connected");
+    }
+    catch (java.rmi.ConnectException e) {
+      System.out.println("couldn't connect!");
+      System.out.println("Make sure there is a server running at the " +
+                         "specified IP address.");
+      System.exit(1);
+    }
+    catch (Exception e) {
+      System.out.println("slave display client exception: " + e.getMessage());
+      e.printStackTrace();
+      System.exit(2);
+    }
+
+    jframe.pack();
+    jframe.setVisible(true);
+  }
+
+  public String toString()
+  {
+    return " [ip.name]: remote slave display client" +
+                "\n\tsecond parameter is server IP name (default = localhost)";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test64(args);
+  }
+}
diff --git a/examples/Test65.java b/examples/Test65.java
new file mode 100644
index 0000000..2f967a2
--- /dev/null
+++ b/examples/Test65.java
@@ -0,0 +1,98 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test65
+  extends UISkeleton
+{
+  public Test65() { }
+
+  public Test65(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display1", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types3d = {RealType.Latitude, RealType.Longitude, RealType.Radius};
+    RealTupleType earth_location3d = new RealTupleType(types3d);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType grid_tuple = new FunctionType(earth_location3d, radiance);
+
+    int size3d = 6;
+    float level = 2.5f;
+    FlatField grid3d = FlatField.makeField(grid_tuple, size3d, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Radius, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+    ScalarMap map1contour = new ScalarMap(vis_radiance, Display.IsoContour);
+
+    dpys[0].addMap(map1contour);
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.SelectRange));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.SelectRange));
+
+    DataReferenceImpl ref_grid3d = new DataReferenceImpl("ref_grid3d");
+    ref_grid3d.setData(grid3d);
+    dpys[0].addReference(ref_grid3d, null);
+
+    // set initial surface value to something reasonable
+    ContourControl cc = (ContourControl) map1contour.getControl();
+    cc.setSurfaceValue(2.5f);
+  }
+
+  String getFrameTitle() { return "VisAD widget panel test"; }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    return dpys[0].getWidgetPanel();
+  }
+
+  public String toString() { return ": test VisAD widget panel"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test65(args);
+  }
+}
diff --git a/examples/Test66.java b/examples/Test66.java
new file mode 100644
index 0000000..ed59727
--- /dev/null
+++ b/examples/Test66.java
@@ -0,0 +1,253 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Component;
+import java.awt.Container;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import visad.ConstantMap;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.LocalDisplay;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.VisADException;
+
+import visad.java3d.DisplayImplJ3D;
+
+//import visad.util.AnimationWidget;
+//import visad.util.ColorWidget;
+import visad.util.ContourWidget;
+import visad.util.GMCWidget;
+import visad.util.LabeledColorWidget;
+import visad.util.ProjWidget;
+import visad.util.RangeWidget;
+import visad.util.SelectRangeWidget;
+
+public class Test66
+  extends TestSkeleton
+{
+  public Test66() { }
+
+  public Test66(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys;
+    dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ3D("D0");
+    dpys[1] = new DisplayImplJ3D("D1");
+    return dpys;
+  }
+
+  private void setupDisplayZero(LocalDisplay dpy,
+                                RealType visRadiance, RealType irRadiance)
+    throws RemoteException, VisADException
+  {
+    dpy.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpy.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+
+    ScalarMap vz = new ScalarMap(visRadiance, Display.ZAxis);
+    dpy.addMap(vz);
+    vz.setUnderscoreToBlank(true);
+
+    dpy.addMap(new ConstantMap(0.5, Display.Red));
+    dpy.addMap(new ScalarMap(visRadiance, Display.Green));
+    dpy.addMap(new ConstantMap(0.5, Display.Blue));
+
+    ScalarMap isr = new ScalarMap(irRadiance, Display.SelectRange);
+    dpy.addMap(isr);
+    isr.setUnderscoreToBlank(true);
+    ScalarMap irgb = new ScalarMap(irRadiance, Display.RGBA);
+    dpy.addMap(irgb);
+    irgb.setUnderscoreToBlank(true);
+
+    dpy.getGraphicsModeControl().setScaleEnable(true);
+  }
+
+  private void setupDisplayOne(LocalDisplay dpy,
+                                RealType visRadiance, RealType irRadiance)
+    throws RemoteException, VisADException
+  {
+    dpy.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpy.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+
+    dpy.addMap(new ConstantMap(0.5, Display.Red));
+    dpy.addMap(new ScalarMap(irRadiance, Display.Green));
+    dpy.addMap(new ConstantMap(0.5, Display.Blue));
+
+    ScalarMap vic = new ScalarMap(visRadiance, Display.IsoContour);
+    dpy.addMap(vic);
+    vic.setUnderscoreToBlank(true);
+    ScalarMap irgb = new ScalarMap(irRadiance, Display.RGB);
+    dpy.addMap(irgb);
+    irgb.setUnderscoreToBlank(true);
+
+    dpy.getGraphicsModeControl().setScaleEnable(true);
+  }
+
+  private void addData(LocalDisplay[] dpys,
+                       RealType visRadiance, RealType irRadiance)
+    throws RemoteException, VisADException
+  {
+    RealType[] llTypes = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earthLoc = new RealTupleType(llTypes);
+
+    RealType[] radTypes = {visRadiance, irRadiance};
+    RealTupleType radTuple = new RealTupleType(radTypes);
+
+    FunctionType imageFunc = new FunctionType(earthLoc, radTuple);
+    FlatField data = FlatField.makeField(imageFunc, 64, false);
+
+    DataReferenceImpl dataRef = new DataReferenceImpl("data");
+    dataRef.setData(data);
+
+    for (int i = 0; i < dpys.length; i++) {
+      dpys[i].addReference(dataRef);
+    }
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType visRadiance = RealType.getRealType("vis_radiance");
+    RealType irRadiance = RealType.getRealType("ir_radiance");
+
+    setupDisplayZero(dpys[0], visRadiance, irRadiance);
+    setupDisplayOne(dpys[1], visRadiance, irRadiance);
+
+    addData(dpys, visRadiance, irRadiance);
+  }
+
+  private void addWidget(Container cont, Component comp)
+  {
+    cont.add(new JLabel(comp.getClass().getName()));
+    cont.add(comp);
+  }
+
+  private Component displayZeroUI(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    Vector v = dpy.getMapVector();
+    int vSize = v.size();
+
+    ScalarMap rgbaMap = (ScalarMap )v.elementAt(vSize - 1);
+    ScalarMap selectMap = (ScalarMap )v.elementAt(vSize - 2);
+
+    JPanel widgets = new JPanel();
+    widgets.setLayout(new BoxLayout(widgets, BoxLayout.Y_AXIS));
+
+    addWidget(widgets, new LabeledColorWidget(rgbaMap));
+    addWidget(widgets, new SelectRangeWidget(selectMap));
+    addWidget(widgets, new GMCWidget(dpy.getGraphicsModeControl()));
+    addWidget(widgets, new ProjWidget(dpy.getProjectionControl()));
+
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+    panel.add(widgets);
+    panel.add(dpy.getComponent());
+
+    return panel;
+  }
+
+  private Component displayOneUI(LocalDisplay dpy)
+    throws RemoteException, VisADException
+  {
+    Vector v = dpy.getMapVector();
+    int vSize = v.size();
+
+    ScalarMap rgbMap = (ScalarMap )v.elementAt(vSize - 1);
+    ScalarMap contourMap = (ScalarMap )v.elementAt(vSize - 2);
+
+    JPanel widgets = new JPanel();
+    widgets.setLayout(new BoxLayout(widgets, BoxLayout.Y_AXIS));
+
+    addWidget(widgets, new RangeWidget(rgbMap));
+    addWidget(widgets, new LabeledColorWidget(rgbMap));
+    addWidget(widgets, new ContourWidget(contourMap));
+
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+    panel.add(widgets);
+    panel.add(dpy.getComponent());
+
+    return panel;
+  }
+
+  private Container buildContent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+    panel.add(displayZeroUI(dpys[0]));
+    panel.add(displayOneUI(dpys[1]));
+
+    return panel;
+  }
+
+  String getFrameTitle() { return "Test all widgets"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    JFrame jframe  = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane(buildContent(dpys));
+    jframe.pack();
+    jframe.setVisible(true);
+  }
+
+  public String toString() { return ": Test all widgets"; }
+
+  /** main method for standalone testing */
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test66(args);
+  }
+}
diff --git a/examples/Test67.java b/examples/Test67.java
new file mode 100644
index 0000000..fdc0fab
--- /dev/null
+++ b/examples/Test67.java
@@ -0,0 +1,171 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test67
+  extends UISkeleton
+{
+  private int dim;
+
+  public Test67() { }
+
+  public Test67(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { dim = 1; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    int d = 0;
+    try {
+      d = Integer.parseInt(args[argc]);
+    }
+    catch (NumberFormatException exc) { }
+
+    if (d < 1 || d > 3) {
+      System.err.println(testName + ": Bad parameter \"" + args[argc] +
+        "\": dimension must be 1, 2 or 3");
+      return -1;
+    }
+
+    dim = d;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[2];
+    dpys[0] = new DisplayImplJ3D("double");
+    dpys[1] = new DisplayImplJ3D("float");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    DataReferenceImpl f_ref = new DataReferenceImpl("ref");
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealType z = RealType.getRealType("z");
+    RealType v = RealType.getRealType("v");
+    FunctionType function;
+    GriddedSet set, f_set;
+    int size = 20;
+    int nrs = (int) Math.pow(size, dim);
+    double eps = 1.0 / size;
+
+    // compute samples
+    double[][] samples = new double[dim][nrs];
+    float[][] f_samples = new float[dim][nrs];
+    for (int j=0; j<dim; j++) {
+      for (int i=0; i<nrs; i++) {
+        int element = i;
+        for (int k=0; k<j; k++) element /= size;
+        element %= size;
+        double frac = (double) element / size;
+        samples[j][i] = frac * 2 * Math.PI + eps * (Math.random() - 0.5);
+        f_samples[j][i] = (float) samples[j][i];
+      }
+    }
+
+    // compute field values
+    double[][] values = new double[1][nrs];
+    float[][] f_values = new float[1][nrs];
+    for (int i=0; i<nrs; i++) {
+      double sum = 0.0;
+      for (int j=0; j<dim; j++) sum += samples[j][i];
+      values[0][i] = Math.sin(sum);
+      f_values[0][i] = (float) values[0][i];
+    }
+
+
+    if (dim == 1) {
+      function = new FunctionType(x, y);
+      set = new Gridded1DDoubleSet(x, samples, size);
+      f_set = new Gridded1DSet(x, f_samples, size);
+    }
+    else if (dim == 2) {
+      RealTupleType xy = new RealTupleType(x, y);
+      function = new FunctionType(xy, z);
+      set = new Gridded2DDoubleSet(xy, samples, size, size);
+      f_set = new Gridded2DSet(xy, f_samples, size, size);
+    }
+    else { // dim == 3
+      RealTupleType xyz = new RealTupleType(x, y, z);
+      function = new FunctionType(xyz, v);
+      set = new Gridded3DDoubleSet(xyz, samples, size, size, size);
+      f_set = new Gridded3DSet(xyz, f_samples, size, size, size);
+    }
+
+    FlatField field = new FlatField(function, set);
+    field.setSamples(values);
+    ref.setData(field);
+
+    FlatField f_field = new FlatField(function, f_set);
+    f_field.setSamples(f_values);
+    f_ref.setData(f_field);
+
+    for (int i=0; i<2; i++) {
+      dpys[i].addMap(new ScalarMap(x, Display.XAxis));
+      dpys[i].addMap(new ScalarMap(y, Display.YAxis));
+      if (dim > 1) dpys[i].addMap(new ScalarMap(z, Display.ZAxis));
+      if (dim > 2) dpys[i].addMap(new ScalarMap(v, Display.RGB));
+    }
+    if (dim < 3) {
+      dpys[0].addMap(new ConstantMap(0.0, Display.Red));
+      dpys[0].addMap(new ConstantMap(0.0, Display.Green));
+      dpys[0].addMap(new ConstantMap(1.0, Display.Blue));
+      dpys[1].addMap(new ConstantMap(1.0, Display.Red));
+      dpys[1].addMap(new ConstantMap(0.0, Display.Green));
+      dpys[1].addMap(new ConstantMap(0.0, Display.Blue));
+    }
+    else {
+      dpys[0].getGraphicsModeControl().setPointSize(2.0f);
+      dpys[1].getGraphicsModeControl().setPointSize(2.0f);
+    }
+    dpys[0].addReference(ref);
+    dpys[1].addReference(f_ref);
+  }
+
+  String getFrameTitle() { return "Gridded" + dim + "DDoubleSet"; }
+
+  public String toString()
+  {
+    return " dim: GriddedDoubleSets";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test67(args);
+  }
+}
diff --git a/examples/Test68.java b/examples/Test68.java
new file mode 100644
index 0000000..8e6498a
--- /dev/null
+++ b/examples/Test68.java
@@ -0,0 +1,193 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.Container;
+import java.awt.event.*;
+import java.io.IOException;
+import java.rmi.RemoteException;
+
+import javax.swing.*;
+
+import visad.*;
+import visad.java2d.DisplayImplJ2D;
+import visad.java3d.DisplayImplJ3D;
+
+public class Test68
+  extends UISkeleton
+{
+  private boolean twoD;
+  private int port;
+
+  boolean hasClientServerMode() { return false; }
+
+  public Test68() { }
+
+  public Test68(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { twoD = false; port = 0; }
+
+  public int checkOption(String progName, char ch, String arg)
+  {
+    if (ch == '2') {
+      twoD = true;
+      return 1;
+    }
+
+    return 0;
+  }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    String arg = args[argc];
+    int d = 0;
+    try {
+      d = Integer.parseInt(arg);
+    }
+    catch (NumberFormatException exc) { }
+
+    if (d < 1 || d > 9999) {
+      System.err.println(testName + ": Bad parameter \"" + arg +
+        "\": port must be between 1 and 9999");
+      return -1;
+    }
+
+    port = d;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    if (twoD) {
+      dpys[0] = new DisplayImplJ2D("display");
+    } else {
+      dpys[0] = new DisplayImplJ3D("display");
+    }
+    return dpys;
+  }
+
+  GraphicsModeControl gmc;
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    int size = 64;
+    DisplayImpl display1 = (DisplayImpl) dpys[0];
+
+    if (twoD) {
+      RealType count = RealType.getRealType("count");
+      FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+      FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+
+      System.out.print("Creating Java2D display...");
+      display1.addMap(new ScalarMap(count, Display.YAxis));
+      display1.addMap(new ScalarMap(ir_radiance, Display.XAxis));
+      display1.addMap(new ConstantMap(0.0, Display.Red));
+      display1.addMap(new ConstantMap(1.0, Display.Green));
+      display1.addMap(new ConstantMap(0.0, Display.Blue));
+
+      gmc = display1.getGraphicsModeControl();
+
+      DataReferenceImpl ref_histogram1;
+      ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+      ref_histogram1.setData(histogram1);
+      display1.addReference(ref_histogram1, null);
+    } else {
+      RealType vis_radiance = RealType.getRealType("vis_radiance");
+      RealType[] types = {RealType.Latitude, RealType.Longitude};
+      RealType[] types2 = {vis_radiance, ir_radiance};
+      RealTupleType earth_location = new RealTupleType(types);
+      RealTupleType radiance = new RealTupleType(types2);
+      FunctionType image_tuple = new FunctionType(earth_location, radiance);
+      FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+      System.out.print("Creating Java3D display...");
+      display1.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+      display1.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+      display1.addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+      display1.addMap(new ScalarMap(vis_radiance, Display.Green));
+      display1.addMap(new ScalarMap(vis_radiance, Display.IsoContour));
+      display1.addMap(new ConstantMap(0.5, Display.Blue));
+      display1.addMap(new ConstantMap(0.5, Display.Red));
+
+      gmc = display1.getGraphicsModeControl();
+      gmc.setPointSize(2.0f);
+      gmc.setPointMode(false);
+      gmc.setMissingTransparent(true);
+
+      DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+      ref_imaget1.setData(imaget1);
+      display1.addReference(ref_imaget1, null);
+    }
+
+    // create the SocketSlaveDisplay for automatic handling of socket clients
+    SocketSlaveDisplay serv = null;
+    try {
+      if (port > 0) {
+        serv = new SocketSlaveDisplay(display1, port);
+      } else {
+        serv = new SocketSlaveDisplay(display1);
+      }
+    }
+    catch (IOException exc) {
+      System.err.println("Unable to create the SocketSlaveDisplay:");
+      exc.printStackTrace();
+    }
+    if (serv != null) {
+      System.out.println("SocketSlaveDisplay created.\n" +
+        "To connect a client from within a web browser,\n" +
+        "use the VisADApplet applet found in visad/browser.\n" +
+        "Note that an applet cannot communicate with a server\n" +
+        "via the network unless both applet and server\n" +
+        "originate from the same machine.");
+    }
+
+    // set up widget frame
+    JFrame widgetFrame = new JFrame("Controls");
+    widgetFrame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    JPanel pane = new JPanel();
+    Container widgets = display1.getWidgetPanel();
+    widgetFrame.setContentPane(widgets);
+    widgetFrame.pack();
+    widgetFrame.setVisible(true);
+  }
+
+  String getFrameTitle() { return "SocketSlaveDisplay server"; }
+
+  public String toString() { return " [-2d] port: SocketSlaveDisplay"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test68(args);
+  }
+}
diff --git a/examples/Test69.java b/examples/Test69.java
new file mode 100644
index 0000000..bb386c8
--- /dev/null
+++ b/examples/Test69.java
@@ -0,0 +1,157 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+import java.awt.*;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.util.HersheyFont;
+
+public class Test69
+  extends UISkeleton
+{
+  private boolean sphere;
+  private String hfont;
+
+  public Test69() { }
+
+  public Test69(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { 
+    sphere = false;
+    hfont = null;
+  }
+
+ public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if ((args[argc].length() >= 3 && "sphere".startsWith(args[argc])) ||
+         "1".equals(args[argc])) {
+      sphere = true;
+      return 1;
+    }
+
+    hfont = args[argc];
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    // dpys[0] = new DisplayImplJ2D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    TextType text = new TextType("text");
+    RealType[] time = {RealType.Time};
+    RealTupleType time_type = new RealTupleType(time);
+    MathType[] mtypes = {RealType.Latitude, RealType.Longitude, text};
+    TupleType text_tuple = new TupleType(mtypes);
+    FunctionType text_function = new FunctionType(RealType.Time, text_tuple);
+
+		String[] names = new String[] {
+				"a b c d e f g h i j k l m",
+				"nopqrstuvwxyz",
+				"A B C D E F G H I J K L M",
+				"NOPQRSTUVWXYZ",
+				"0123456789  - + = / [ ] ( ) { }",
+				// for this last one concatenate a bunch of odd Unicode chars
+				// lower case vowels with acute, small n with tilde,
+				// upper case vowels with acute, capital N with tilde
+				"\u00e1" + "\u00e9" + "\u00ed" + "\u00f3" + "\u00fa" + "\u00f1"
+						+ "\u00c1" + "\u00c9" + "\u00cd" + "\u00d3" + "\u00da"
+						+ "\u00d1" };
+		
+    if (sphere) {
+      names = new String[] {"", "", "a b c d e f g h i j k l m n o p q " +
+                            "r s t u v w x y z A B C D E F G H I J K L " +
+                            "M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 " +
+                            "7 8 9   T H I S   I S N ' T   Y O U R   G " +
+                            "R A N D F A T H E R ' S   M C I D A S", "", ""};
+    }
+    int ntimes1 = names.length;
+    Set time_set =
+      new Linear1DSet(time_type, 0.0, (double) (ntimes1 - 1.0), ntimes1);
+
+    FieldImpl text_field = new FieldImpl(text_function, time_set);
+
+    for (int i=0; i<ntimes1; i++) {
+      Data[] td = {new Real(RealType.Latitude, 30.0 * i - 60.0),
+                   new Real(RealType.Longitude, 60.0 * (ntimes1 - i) - 120.0),
+                   new Text(text, names[i])};
+
+      Tuple tt = new Tuple(text_tuple, td);
+      text_field.setSample(i, tt);
+    }
+
+    ScalarMap tmap = new ScalarMap(text, Display.Text);
+    dpys[0].addMap(tmap);
+    TextControl tcontrol = (TextControl) tmap.getControl();
+    if (hfont == null) {
+      Font font = new Font("Serif", Font.PLAIN, 60);
+      tcontrol.setFont(font);
+    } else {
+      HersheyFont font = new HersheyFont(hfont);
+      tcontrol.setFont(font);
+    }
+    tcontrol.setSphere(sphere);
+    tcontrol.setCenter(true);
+    tcontrol.setSize(2.0);
+    if (sphere) {
+      tcontrol.setRotation(10.0);
+      dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Latitude));
+      dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Longitude));
+    }
+    else {
+      dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+      dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    }
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    DataReferenceImpl ref_text_field =
+      new DataReferenceImpl("ref_text_field");
+    ref_text_field.setData(text_field);
+    dpys[0].addReference(ref_text_field, null);
+  }
+
+  String getFrameTitle() { return "text with font in Java3D"; }
+
+  public String toString() { return " [sphere  <HersheyFontName>]: text with font in Java3D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test69(args);
+  }
+}
diff --git a/examples/Test70.java b/examples/Test70.java
new file mode 100644
index 0000000..5c9b8ba
--- /dev/null
+++ b/examples/Test70.java
@@ -0,0 +1,124 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+import java.text.*;
+import java.awt.Font;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test70
+  extends UISkeleton
+{
+  private boolean sphere;
+
+  public Test70() { }
+
+  public Test70(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { sphere = false; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    sphere = true;
+    return 1;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType text = RealType.getRealType("text");
+    RealType[] time = {RealType.Time};
+    RealTupleType time_type = new RealTupleType(time);
+    RealType[] mtypes = {RealType.Latitude, RealType.Longitude, text};
+    RealTupleType text_tuple = new RealTupleType(mtypes);
+    FunctionType text_function = new FunctionType(RealType.Time, text_tuple);
+
+    // double[] values = {1.23, 0.001, -23.4, 0.0, 10};
+    double[] values = {1.23, 0.001, Double.NaN, 0.0, 10};
+
+    int ntimes1 = values.length;
+    Set time_set =
+      new Linear1DSet(time_type, 0.0, (double) (ntimes1 - 1.0), ntimes1);
+
+    FlatField text_field = new FlatField(text_function, time_set);
+
+    for (int i=0; i<ntimes1; i++) {
+      Real[] td = {new Real(RealType.Latitude, 30.0 * i - 60.0),
+                   new Real(RealType.Longitude, 60.0 * (ntimes1 - i) - 120.0),
+                   new Real(text, values[i])};
+
+      RealTuple tt = new RealTuple(td);
+      text_field.setSample(i, tt);
+    }
+
+    ScalarMap tmap = new ScalarMap(text, Display.Text);
+    dpys[0].addMap(tmap);
+    TextControl tcontrol = (TextControl) tmap.getControl();
+    tcontrol.setSphere(sphere);
+    tcontrol.setCenter(true);
+    tcontrol.setNumberFormat(new DecimalFormat());
+    Font txtfont = new Font("Arial", Font.PLAIN, 4);
+    tcontrol.setFont(txtfont);
+    // tcontrol.setRotation(10.0);
+    if (sphere) {
+      dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Latitude));
+      dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Longitude));
+    }
+    else {
+      dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+      dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    }
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.Green));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    DataReferenceImpl ref_text_field =
+      new DataReferenceImpl("ref_text_field");
+    ref_text_field.setData(text_field);
+    dpys[0].addReference(ref_text_field, null);
+  }
+
+  String getFrameTitle() { return "text from RealType in Java3D"; }
+
+  public String toString() { return " sphere: text from RealType in Java3D"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test70(args);
+  }
+}
diff --git a/examples/Test71.java b/examples/Test71.java
new file mode 100644
index 0000000..13c3eed
--- /dev/null
+++ b/examples/Test71.java
@@ -0,0 +1,222 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.data.DefaultFamily;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test71
+  extends UISkeleton
+{
+  class SwitchGIFs
+    implements ActivityHandler
+  {
+    SwitchGIFs(LocalDisplay dpy) { toggleDisplay(dpy, true); }
+
+    public void busyDisplay(LocalDisplay dpy) { toggleDisplay(dpy, false); }
+
+    public void idleDisplay(LocalDisplay dpy) { toggleDisplay(dpy, true); }
+
+    private void toggleDisplay(LocalDisplay dpy, boolean showFirstGIF)
+    {
+      java.util.Vector v = dpy.getRenderers();
+
+      final int size = v.size();
+      if (size != 2) {
+        System.err.println("Expected 2 DataRenderers, but Display has " +
+                           size);
+        return;
+      }
+
+      ((DataRenderer )v.get(0)).toggle(showFirstGIF);
+      ((DataRenderer )v.get(1)).toggle(!showFirstGIF);
+    }
+  }
+
+  private String file1;
+  private String file2;
+
+  public Test71() { }
+
+  public Test71(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { file1 = file2 = null; }
+
+  public int checkKeyword(String testName, int argc, String[] args)
+  {
+    if (file1 == null) {
+      file1 = args[argc];
+    } else if (file2 == null) {
+      file2 = args[argc];
+    } else {
+      System.err.println(testName + ": Ignoring extra filename \"" +
+                         args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  public String keywordUsage()
+  {
+    return super.keywordUsage() + " file1 file2";
+  }
+
+  public boolean finalizeArgs(String progName)
+  {
+    if (file1 == null) {
+      System.err.println(progName + ": Please specify two files");
+      return false;
+    }
+
+    if (file2 == null) {
+      System.err.println(progName + ": Please specify both files");
+      return false;
+    }
+
+    return true;
+  }
+
+  private DataReferenceImpl loadFile(DefaultFamily df, String fileName,
+                                     String refName)
+    throws RemoteException, VisADException
+  {
+    if (fileName == null) {
+      return null;
+    }
+
+    Data data = (FlatField )df.open(fileName);
+
+    DataReferenceImpl ref = new DataReferenceImpl(refName);
+    ref.setData(data);
+
+    return ref;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display");
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    DefaultFamily df = new DefaultFamily("loader");
+
+    DataReference ref1 = loadFile(df, file1, "img1");
+    if (ref1 == null) {
+      System.err.println("\"" + file1 + "\" is not a valid file");
+      System.exit(1);
+      return;
+    }
+
+    DataReference ref2 = loadFile(df, file2, "img2");
+    if (ref2 == null) {
+      System.err.println("\"" + file2 + "\" is not a valid file");
+      System.exit(1);
+      return;
+    }
+
+    FlatField img1 = (FlatField )ref1.getData();
+    FlatField img2 = (FlatField )ref2.getData();
+
+/*
+    if (!img1.getType().equals(img2.getType())) {
+      System.err.println("Incompatible file types:");
+      System.err.println("  " + file1 + ": " + img1.getType());
+      System.err.println("  " + file2 + ": " + img2.getType());
+      System.exit(1);
+      return;
+    }
+*/
+
+    // compute ScalarMaps from type components
+    FunctionType ftype = (FunctionType) img1.getType();
+    RealTupleType dtype = ftype.getDomain();
+    RealTupleType rtype = (RealTupleType) ftype.getRange();
+
+    /* map domain elements to spatial axes */
+    final int dLen = dtype.getDimension();
+    for (int i = 0; i < dLen; i++) {
+      ScalarType scalT;
+      DisplayRealType dpyRT;
+
+      switch (i) {
+      case 0: dpyRT = Display.XAxis; break;
+      case 1: dpyRT = Display.YAxis; break;
+      case 2: dpyRT = Display.ZAxis; break;
+      default: dpyRT = null; break;
+      }
+
+      if (dpyRT != null) {
+        dpys[0].addMap(new ScalarMap((RealType )dtype.getComponent(i), dpyRT));
+      }
+    }
+
+    /* map range elements to colors */
+    final int rLen = rtype.getDimension();
+    for (int i = 0; i < rLen; i++) {
+      ScalarType scalT;
+      DisplayRealType dpyRT;
+
+      switch (i) {
+      case 0: dpyRT = Display.Red; break;
+      case 1: dpyRT = Display.Green; break;
+      case 2: dpyRT = Display.Blue; break;
+      default: dpyRT = null; break;
+      }
+
+      if (dpyRT != null) {
+        dpys[0].addMap(new ScalarMap((RealType )rtype.getComponent(i), dpyRT));
+      }
+    }
+
+    dpys[0].addReference(ref1, null);
+    dpys[0].addReference(ref2, null);
+
+    dpys[0].addActivityHandler(new SwitchGIFs(dpys[0]));
+  }
+
+  String getFrameTitle() { return "Idle/Busy test"; }
+
+  public String toString()
+  {
+    return " gif_file gif_file: Idle/Busy test";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test71(args);
+  }
+}
diff --git a/examples/Test72.java b/examples/Test72.java
new file mode 100644
index 0000000..fa7a080
--- /dev/null
+++ b/examples/Test72.java
@@ -0,0 +1,84 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Test72
+  extends TestSkeleton
+{
+  public Test72() { }
+
+  public Test72(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display", DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    ScalarMap map72flow = new ScalarMap(vis_radiance, Display.Flow1X);
+    dpys[0].addMap(map72flow);
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.Flow1Y));
+
+    FlowControl control72flow = (FlowControl) map72flow.getControl();
+    control72flow.enableStreamlines(true);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": streamlines"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test72(args);
+  }
+}
diff --git a/examples/Test73.java b/examples/Test73.java
new file mode 100644
index 0000000..8cd50db
--- /dev/null
+++ b/examples/Test73.java
@@ -0,0 +1,181 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.image.BufferedImage;
+import java.rmi.RemoteException;
+import java.util.Vector;
+import visad.*;
+import visad.bom.ImageRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.TwoDDisplayRendererJ3D;
+import visad.util.CursorUtil;
+
+public class Test73 extends TestSkeleton implements DisplayListener {
+  private String fileName;
+  private boolean norm;
+  private ImageFlatField ff;
+
+  public Test73() { }
+
+  public Test73(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  public void initializeArgs() { fileName = null; norm = false; }
+
+  public int checkOption(String progName, char ch, String arg) {
+    if (ch == 'n') {
+      norm = true;
+      return 1;
+    }
+
+    return 0;
+  }
+
+
+  public int checkKeyword(String testName, int argc, String[] args) {
+    if (fileName == null) fileName = args[argc];
+    else {
+      System.err.println(testName +
+        ": Ignoring extra filename \"" + args[argc] + "\"");
+    }
+
+    return 1;
+  }
+
+  public String keywordUsage() {
+    return super.keywordUsage() + " [-n(ormalize)] file";
+  }
+
+  public boolean finalizeArgs(String mainName) {
+    if (fileName == null) {
+      System.err.println(mainName + ": No filename specified!");
+      return false;
+    }
+
+    return true;
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display",
+      new TwoDDisplayRendererJ3D(), DisplayImplJ3D.APPLETFRAME);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    // load image from disk
+    BufferedImage image = null;
+    try {
+      image = javax.imageio.ImageIO.read(new java.io.File(fileName));
+    }
+    catch (java.io.IOException exc) {
+      exc.printStackTrace();
+      return;
+    }
+
+    // convert image to more efficient representation (optional)
+    if (norm) image = ImageFlatField.make3ByteRGB(image);
+
+    // convert image to VisAD object
+    ff = new ImageFlatField(image);
+
+    // create display mappings
+    RealType[] xy = ff.getDomainTypes();
+    RealType[] v = ff.getRangeTypes();
+    dpys[0].addMap(new ScalarMap(xy[0], Display.XAxis));
+    dpys[0].addMap(new ScalarMap(xy[1], Display.YAxis));
+    if (v.length == 3) {
+      dpys[0].addMap(new ScalarMap(v[0], Display.Red));
+      dpys[0].addMap(new ScalarMap(v[1], Display.Green));
+      dpys[0].addMap(new ScalarMap(v[2], Display.Blue));
+    }
+    else {
+      for (int i=0; i<v.length; i++) {
+        dpys[0].addMap(new ScalarMap(v[i], Display.RGB));
+      }
+    }
+
+    // configure display
+    GraphicsModeControl gmc = dpys[0].getGraphicsModeControl();
+    gmc.setTextureEnable(true);
+    gmc.setScaleEnable(true);
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(ff);
+    dpys[0].addReferences(new ImageRendererJ3D(), ref, null);
+    dpys[0].addDisplayListener(this);
+  }
+
+  public void displayChanged(DisplayEvent e) {
+    int id = e.getId();
+    if (id == DisplayEvent.FRAME_DONE) {
+      // check for active cursor
+      Display display = e.getDisplay();
+      if (!(display instanceof DisplayImpl)) return;
+      DisplayImpl d = (DisplayImpl) display;
+      DisplayRenderer dr = d.getDisplayRenderer();
+      Vector cursorStringVector = dr.getCursorStringVector();
+      if (cursorStringVector == null || cursorStringVector.size() == 0) return;
+
+      // get cursor value
+      double[] cur = dr.getCursor();
+      if (cur == null || cur.length == 0 || cur[0] != cur[0]) return;
+
+      // get range values at the given cursor location
+      double[] domain = CursorUtil.cursorToDomain(d, cur);
+      double[] range = null;
+      try {
+        range = CursorUtil.evaluate(ff, domain);
+      }
+      catch (VisADException exc) { exc.printStackTrace(); }
+      catch (RemoteException exc) { exc.printStackTrace(); }
+
+      System.out.print("Cursor =");
+      for (int i=0; i<2; i++) System.out.print(" " + domain[i]);
+      System.out.print(" ->");
+      if (range == null) System.out.println(" null");
+      else {
+        for (int i=0; i<range.length; i++) System.out.print(" " + range[i]);
+        System.out.println();
+      }
+    }
+  }
+
+  String getFrameTitle() { return "ImageFlatField with ImageRendererJ3D"; }
+
+  public String toString() {
+    return " [-n(ormalize)] file_name: ImageFlatField";
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new Test73(args);
+  }
+
+}
diff --git a/examples/TestEvents.java b/examples/TestEvents.java
new file mode 100644
index 0000000..31c7185
--- /dev/null
+++ b/examples/TestEvents.java
@@ -0,0 +1,700 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.Random;
+
+import visad.BaseColorControl;
+import visad.ColorAlphaControl;
+import visad.Control;
+import visad.ConstantMap;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.LocalDisplay;
+import visad.RealType;
+import visad.RemoteDisplayImpl;
+import visad.ScalarMap;
+import visad.VisADException;
+
+import visad.collab.DisplaySyncImpl;
+
+import visad.java2d.DisplayImplJ2D;
+
+import visad.util.Delay;
+
+/**
+ * Start up a bunch of collaborative clients, then have the
+ * server and clients go through a number of rounds where
+ * they all tweak their controls to trigger a bunch of events
+ * and, after a predetermined amount of time, check to make
+ * sure everyone is synchronized.<br>
+ * <br>
+ * <table>
+ * <tr><th>Option</th><th>Description</th></tr>
+ * <tr><td>-c <i>num</i></td><td>Number of clients</td></tr>
+ * <tr><td>-d <i>num</i></td><td>Seconds to delay * 100</td></tr>
+ * <tr><td>-e <i>num</i></td><td>Number of events per round</td></tr>
+ * <tr><td>-r <i>num</i></td><td>Number of rounds</td></tr>
+ * </table>
+ */
+public class TestEvents
+{
+  private static Object waiter = new Object();
+
+  private static java.util.Random rand = null;
+
+  private static RealType mapType = null;
+
+  private int numClients = 6;
+  private int numRounds = 6;
+  private int numEvents = 6;
+
+  private long randSeed = -1;
+  private int delayUsecs = 150;
+  private boolean verbose = false;
+
+  public TestEvents(String[] args)
+  {
+    if (!processArgs(args)) {
+      System.err.println("Exiting...");
+      System.exit(1);
+    }
+
+    if (rand == null) {
+      if (randSeed == -1) {
+        rand = new Random();
+      } else {
+        rand = new Random(randSeed);
+      }
+    }
+
+    mapType = RealType.getRealType("Map");
+
+    DisplayImpl server = createServer();
+    if (server == null) {
+      System.err.println("Couldn't create server!");
+      System.exit(1);
+      return;
+    }
+
+    Bundle[] bundle = createBundle(server, numClients);
+    if (bundle == null) {
+      System.exit(1);
+      return;
+    }
+
+    boolean result = true;
+    for (int i = 0; i < numRounds; i++) {
+      if (verbose) {
+        System.err.println("--- Round "+i);
+        System.err.flush();
+      }
+
+      for (int j = 0; j < numEvents; j++) {
+        //new Delay(1000);
+        synchronized (waiter) { waiter.notifyAll(); }
+        Thread.yield();
+      }
+
+      for (int d = 0; d < 10; d++) {
+        new Delay(delayUsecs);
+        if (verbose) {
+          System.err.println("++ Finished Delay#" + d + ":" +
+                             System.currentTimeMillis());
+          System.err.flush();
+        }
+      }
+
+      result &= compareAll(bundle);
+    }
+
+    StringBuffer dangle = null;
+    for (int i = 0; i < bundle.length; i++) {
+      DisplayImpl dpy = (DisplayImpl )bundle[i].getDisplay();
+      DisplaySyncImpl dsi = (DisplaySyncImpl )dpy.getDisplaySync();
+      if (dsi.isThreadRunning()) {
+        if (dangle == null) {
+          dangle = new StringBuffer(dsi.getName());
+        } else {
+          dangle.append(", ");
+          dangle.append(dsi.getName());
+        }
+        result = false;
+      }
+    }
+    if (dangle != null) {
+      dangle.insert(0, "Yikes ... thread running for ");
+      String dangleStr = dangle.toString();
+      System.out.println(dangleStr);
+      System.err.println(dangleStr);
+    }
+
+    System.out.flush();
+    System.err.flush();
+
+    if (!result) {
+      System.exit(1);
+      return;
+    }
+
+    System.out.println("Happy happy, joy joy!");
+    System.exit(0);
+  }
+
+  private boolean compareAll(Bundle[] bundle)
+  {
+    StringBuffer badList = null;
+    final TriggerControl tc = bundle[0].getTriggerControl();
+    final BaseColorControl cc = bundle[0].getColorControl();
+
+    boolean allTcCmp = true;
+    boolean allCcCmp = true;
+
+    for (int i = 1; i < bundle.length; i++) {
+      final TriggerControl btc = bundle[i].getTriggerControl();
+      final BaseColorControl bcc = bundle[i].getColorControl();
+
+      final boolean tcCmp = btc.equals(tc);
+      final boolean ccCmp = bcc.equals(cc);
+
+      if (!tcCmp || !ccCmp) {
+        DisplayImpl dpy = (DisplayImpl )bundle[i].getDisplay();
+        if (badList == null) {
+          badList = new StringBuffer();
+        } else {
+          badList.append(", ");
+        }
+
+        badList.append(dpy.getName());
+        badList.append('=');
+
+        if (!tcCmp) {
+          badList.append(btc);
+          allTcCmp = false;
+
+          if (!ccCmp) {
+            badList.append(',');
+            badList.append(bcc);
+            allCcCmp = false;
+          }
+        } else if (!ccCmp) {
+          badList.append(bcc);
+          allCcCmp = false;
+        }
+      }
+    }
+
+    final boolean matched = (badList == null);
+    if (!matched) {
+      badList.insert(0, ", got ");
+      if (!allCcCmp) {
+        badList.insert(0, cc);
+      }
+      if (!allTcCmp) {
+        if (!allCcCmp) {
+          badList.insert(0, ',');
+        }
+        badList.insert(0, tc);
+      }
+      badList.insert(0, "Wanted ");
+
+      String badStr = badList.toString();
+
+      System.err.println(badStr);
+      System.out.println(badStr);
+    }
+
+    return matched;
+  }
+
+  private DisplayImpl createServer()
+  {
+    DisplayImplJ2D dpy;
+    try {
+      dpy = new DisplayImplJ2D("root");
+    } catch (RemoteException re) {
+      return null;
+    } catch (VisADException ve) {
+      return null;
+    }
+
+    return dpy;
+  }
+
+  private Bundle[] createBundle(DisplayImpl server, int num)
+  {
+    Bundle[] bundle = new Bundle[num+1];
+
+    try {
+      bundle[0] = new Bundle(server, false);
+    } catch (RemoteException re) {
+      System.err.println("Couldn't create server wrapper");
+      re.printStackTrace();
+      return null;
+    } catch (VisADException ve) {
+      System.err.println("Couldn't create server wrapper");
+      ve.printStackTrace();
+      return null;
+    }
+
+    for (int i = 0; i < num; i++) {
+      try {
+        bundle[i+1] = new Bundle(server);
+      } catch (RemoteException re) {
+        System.err.println("Couldn't create client #" + i + "!");
+        re.printStackTrace();
+        return null;
+      } catch (VisADException ve) {
+        System.err.println("Couldn't create client #" + i + "!");
+        ve.printStackTrace();
+        return null;
+      }
+    }
+
+    return bundle;
+  }
+
+  private static final TriggerControl getTriggerCtl(DisplayImpl dpy)
+  {
+    Class tClass;
+    try {
+      tClass = Class.forName(TriggerControl.class.getName());
+    } catch (ClassNotFoundException cnfe) {
+      tClass = null;
+    }
+
+    return (TriggerControl )dpy.getControl(tClass, 0);
+  }
+
+  public boolean processArgs(String[] args)
+  {
+    boolean usage = false;
+
+    String className = getClass().getName();
+    int pt = className.lastIndexOf('.');
+    final int ds = className.lastIndexOf('$');
+    if (ds > pt) {
+      pt = ds;
+    }
+    String progName = className.substring(pt == -1 ? 0 : pt + 1);
+
+    for (int i = 0; args != null && i < args.length; i++) {
+      if (args[i].length() > 0 && args[i].charAt(0) == '-') {
+        char ch = args[i].charAt(1);
+
+        String str, result;
+
+        switch (ch) {
+        case 'c':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing number of clients for \"-c\"");
+            usage = true;
+          } else {
+            try {
+              numClients = Integer.parseInt(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad number of clients \"" + str + "\"");
+              numClients = 2;
+              usage = true;
+            }
+
+            if (numClients < 1) {
+              System.err.println(progName +
+                                 ": Need at least one client!");
+              usage = true;
+            }
+          }
+          break;
+        case 'd':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing delay usecs for \"-d\"");
+            usage = true;
+          } else {
+            try {
+              delayUsecs = Integer.parseInt(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad delay usecs \"" + str + "\"");
+              delayUsecs = 100;
+              usage = true;
+            }
+
+            if (delayUsecs < 1) {
+              System.err.println(progName +
+                                 ": Delay usecs needs to be" +
+                                 " greater than zero!");
+              usage = true;
+            }
+          }
+          break;
+        case 'e':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing number of events for \"-e\"");
+            usage = true;
+          } else {
+            try {
+              numEvents = Integer.parseInt(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad number of events \"" + str + "\"");
+              numEvents = 3;
+              usage = true;
+            }
+
+            if (numEvents < 1) {
+              System.err.println(progName +
+                                 ": Need at least one event!");
+              usage = true;
+            }
+          }
+          break;
+        case 'r':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing  rounds for \"-r\"");
+            usage = true;
+          } else {
+            try {
+              numRounds = Integer.parseInt(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad number of rounds \"" + str + "\"");
+              numRounds = 1;
+              usage = true;
+            }
+
+            if (numRounds < 1) {
+              System.err.println(progName +
+                                 ": Need at least one round!");
+              usage = true;
+            }
+          }
+          break;
+        case 's':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing random seed value for \"-s\"");
+            usage = true;
+          } else {
+            try {
+              randSeed = Long.parseLong(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad random seed value \"" + str + "\"");
+              usage = true;
+            }
+          }
+          break;
+        case 'v':
+          verbose = true;
+          break;
+        default:
+          System.err.println(progName +
+                             ": Unknown option \"-" + ch + "\"");
+          usage = true;
+          break;
+        }
+      } else {
+        System.err.println(progName + ": Unknown keyword \"" + args[i] + "\"");
+        usage = true;
+      }
+    }
+
+    if (usage) {
+      System.err.println("Usage: " + getClass().getName() +
+                         " [-c numClients]" +
+                         " [-d delayUSecs]" +
+                         " [-e numEvents]" +
+                         " [-r numRounds]" +
+                         " [-s randomSeed]" +
+                         " [-v(erbose)]" +
+                         "");
+    }
+
+    return !usage;
+  }
+
+  class Bundle
+    extends Thread
+  {
+    private DisplayImplJ2D dpy;
+    private TriggerControl tc;
+    private ColorAlphaControl cac;
+
+    public Bundle(DisplayImpl server)
+      throws RemoteException, VisADException
+    {
+      this(server, true);
+    }
+
+    public Bundle(DisplayImpl server, boolean buildClient)
+      throws RemoteException, VisADException
+    {
+      if (!buildClient) {
+        dpy = (DisplayImplJ2D )server;
+
+        ScalarMap map = new ScalarMap(mapType, Display.RGBA);
+        dpy.addMap(map);
+      } else {
+        RemoteDisplayImpl rmtdpy = new RemoteDisplayImpl(server);
+        dpy = new DisplayImplJ2D(rmtdpy);
+      }
+
+      tc = new TriggerControl(dpy);
+      dpy.addControl(tc);
+
+      ScalarMap map = findMap(dpy, Display.RGBA);
+      cac = (ColorAlphaControl )map.getControl();
+
+      start();
+    }
+
+    private ScalarMap findMap(LocalDisplay dpy, RealType displayScalar)
+    {
+      if (displayScalar == null) {
+        return null;
+      }
+
+      java.util.Iterator maps;
+
+      try {
+        maps = dpy.getMapVector().iterator();
+      } catch (Exception e) {
+        maps = null;
+      }
+
+      if (maps != null) {
+        while (maps.hasNext()) {
+          ScalarMap smap = (ScalarMap )maps.next();
+          if (displayScalar.equals(smap.getDisplayScalar())) {
+            return smap;
+          }
+        }
+      }
+
+      try {
+        maps = dpy.getConstantMapVector().iterator();
+      } catch (Exception e) {
+        maps = null;
+      }
+
+      if (maps != null) {
+        while (maps.hasNext()) {
+          ConstantMap cmap = (ConstantMap )maps.next();
+          if (displayScalar.equals(cmap.getDisplayScalar())) {
+            return cmap;
+          }
+        }
+      }
+
+      return null;
+    }
+
+    public DisplayImpl getDisplay() { return dpy; }
+
+    private float[][] getRandomTable(int w, int h)
+    {
+      float[][] t = new float[w][h];
+      for (int i = 0; i < w; i++) {
+        for (int j = 0; j < h; j++) {
+          t[i][j] = rand.nextFloat();
+        }
+      }
+      return t;
+    }
+
+    public BaseColorControl getColorControl() { return cac; }
+    public TriggerControl getTriggerControl() { return tc; }
+
+    public void run()
+    {
+      while (true) {
+        synchronized (waiter) {
+          try { waiter.wait(); } catch (InterruptedException ie) { }
+        }
+
+        try {
+          tc.fire();
+          cac.setTable(getRandomTable(cac.getNumberOfComponents(),
+                                      cac.getNumberOfColors()));
+        } catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    }
+  }
+
+  interface Revolver
+  {
+    void triggered();
+  }
+
+  private static int masterTriggerNum = 1;
+
+  class TriggerControl
+    extends Control
+  {
+    private ArrayList list;
+    private int num, val;
+
+    public TriggerControl(DisplayImpl dpy)
+    {
+      super(dpy);
+
+      list = new ArrayList();
+      num = masterTriggerNum++;
+      val = 0;
+    }
+
+    public void addListener(Revolver rvlvr)
+    {
+      synchronized (list) {
+        list.add(rvlvr);
+      }
+    }
+
+    private final void delay()
+    {
+      int dLen = rand.nextInt() % 50;
+      dLen = (dLen < 0 ? -dLen : (dLen == 0 ? 1 : dLen));
+      new Delay(dLen);
+    }
+
+    public void fire()
+      throws RemoteException, VisADException
+    {
+      val += num;
+      changeControl(true);
+      notifyListeners();
+    }
+
+    public String getSaveString() { return Integer.toString(val); }
+
+    public int getValue() { return val; }
+
+    public void notifyListeners()
+    {
+      ListIterator iter = list.listIterator();
+      while (iter.hasNext()) {
+        Revolver r = (Revolver )iter.next();
+        r.triggered();
+      }
+    }
+
+    public void setSaveString(String save)
+      throws VisADException, RemoteException
+    {
+      if (save == null) {
+        throw new VisADException("Invalid save string");
+      }
+
+      try {
+        val = Integer.parseInt(save);
+      } catch (NumberFormatException nfe) {
+        throw new VisADException("Bad TriggerControl save string \"" + save +
+                                 "\"");
+      }
+    }
+
+    public void syncControl(Control ctl)
+      throws VisADException
+    {
+      if (ctl == null) {
+        throw new VisADException("Cannot synchronize " +
+                                 this.getClass().getName() +
+                                 " with null Control object");
+      }
+
+      if (!(ctl instanceof TriggerControl)) {
+        throw new VisADException("Cannot synchronize " +
+                                 this.getClass().getName() +
+                                 " with " + ctl.getClass().getName());
+      }
+
+      TriggerControl tc = (TriggerControl )ctl;
+
+      boolean changed = false;
+
+      if (val != tc.val) {
+        changed = true;
+        val = tc.val;
+        notifyListeners();
+      }
+
+      if (changed) {
+        try {
+          changeControl(true);
+        } catch (RemoteException re) {
+          throw new VisADException("Could not indicate that control" +
+                                   " changed: " + re.getMessage());
+        }
+      }
+    }
+
+    public boolean equals(Object o)
+    {
+      if (!super.equals(o)) {
+        return false;
+      }
+
+      TriggerControl tc = (TriggerControl )o;
+
+      if (val != tc.val) {
+        return false;
+      }
+
+      return true;
+    }
+
+    public String toString()
+    {
+      StringBuffer buf = new StringBuffer("TriggerControl[#");
+      buf.append(num);
+      buf.append('=');
+      buf.append(val);
+      buf.append(']');
+      return buf.toString();
+    }
+  }
+
+  public static void main(String[] args)
+  {
+    new TestEvents(args);
+  }
+}
diff --git a/examples/TestIDesk.java b/examples/TestIDesk.java
new file mode 100644
index 0000000..d0fddeb
--- /dev/null
+++ b/examples/TestIDesk.java
@@ -0,0 +1,148 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+import visad.java3d.*;
+import visad.java3d.DisplayImplJ3D;
+
+import javax.media.j3d.*;
+import java.io.IOException;
+
+// JFC packages
+import javax.swing.*;
+
+// AWT packages
+import java.awt.*;
+import java.awt.event.*;
+
+
+public class TestIDesk extends Object {
+
+  private static int tracker_shmkey, controller_shmkey;
+
+  public static void main(String args[])
+         throws IOException, VisADException, RemoteException {
+
+    if (args.length != 2) {
+      System.err.println("must have 2 integer arguments");
+      System.exit(1);
+    }
+
+    try {
+      tracker_shmkey = Integer.parseInt(args[0]);
+      controller_shmkey = Integer.parseInt(args[1]);
+    }
+    catch (NumberFormatException exc) {
+      System.err.println("args must be integers " + args[0] + " " + args[1]);
+      System.exit(1);
+    }
+
+    // set up display
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+
+    GraphicsConfigTemplate3D gct3d = new GraphicsConfigTemplate3D();
+    gct3d.setStereo(GraphicsConfigTemplate3D.REQUIRED);
+
+    GraphicsConfiguration config =
+    gct3d.getBestConfiguration(gd.getConfigurations());
+
+    if (config == null) {
+      System.err.println("Unable to find a Stereo visual");
+      System.exit(1);
+    }
+
+    ImmersaDeskDisplayRendererJ3D display_renderer =
+      new ImmersaDeskDisplayRendererJ3D(tracker_shmkey, controller_shmkey);
+    DisplayImplJ3D display =
+      new DisplayImplJ3D("display1", display_renderer, config);
+
+    // set up data
+    final RealType ir_radiance = RealType.getRealType("ir_radiance");
+    final RealType count = RealType.getRealType("count", CommonUnit.second);
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+    final RealType vis_radiance = RealType.getRealType("vis_radiance");
+
+    int size = 64;
+    FlatField histogram1 = FlatField.makeField(ir_histogram, size, false);
+    Real direct = new Real(ir_radiance, 2.0);
+    Real[] reals3 = {new Real(count, 1.0), new Real(ir_radiance, 2.0),
+                     new Real(vis_radiance, 1.0)};
+    RealTuple direct_tuple = new RealTuple(reals3);
+
+    // link data to display
+    display.addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    display.addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    display.addMap(new ScalarMap(count, Display.YAxis));
+    display.addMap(new ScalarMap(count, Display.Green));
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+    mode.setPointMode(false);
+
+    DataReferenceImpl ref_direct = new DataReferenceImpl("ref_direct");
+    ref_direct.setData(direct);
+    DataReference[] refs1 = {ref_direct};
+    display.addReferences(new DirectManipulationRendererJ3D(), refs1, null);
+
+    DataReferenceImpl ref_direct_tuple =
+      new DataReferenceImpl("ref_direct_tuple");
+    ref_direct_tuple.setData(direct_tuple);
+    DataReference[] refs2 = {ref_direct_tuple};
+    display.addReferences(new DirectManipulationRendererJ3D(), refs2, null);
+
+    DataReferenceImpl ref_histogram1 = new DataReferenceImpl("ref_histogram1");
+    ref_histogram1.setData(histogram1);
+    DataReference[] refs3 = {ref_histogram1};
+    display.addReferences(new DirectManipulationRendererJ3D(), refs3, null);
+
+    // create JFrame and JPanel for display
+    JFrame frame = new JFrame("VisAD ImmersaDesk Test");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in frame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    panel.add(display.getComponent());
+
+    int WIDTH = 1280;
+    int HEIGHT = 1024;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/examples/TestMsg.java b/examples/TestMsg.java
new file mode 100644
index 0000000..36381e9
--- /dev/null
+++ b/examples/TestMsg.java
@@ -0,0 +1,305 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import java.util.Random;
+
+import visad.FlatField;
+import visad.LocalDisplay;
+import visad.MessageEvent;
+import visad.MessageListener;
+import visad.RemoteDisplayImpl;
+import visad.VisADException;
+
+import visad.java2d.DisplayImplJ2D;
+
+////////////////////////////////////////
+import visad.FunctionType;
+import visad.RemoteFieldImpl;
+import visad.Set;
+////////////////////////////////////////
+
+public class TestMsg
+{
+  private static java.util.Random rand = null;
+
+  private int numClients = 6;
+  private int numMessages = 10;
+
+  private long randSeed = -1;
+  private boolean verbose = false;
+
+  public TestMsg(String[] args)
+  {
+    if (!processArgs(args)) {
+      System.err.println("Exiting...");
+      System.exit(1);
+    }
+
+    if (rand == null) {
+      if (randSeed == -1) {
+        rand = new Random();
+      } else {
+        rand = new Random(randSeed);
+      }
+    }
+
+    LocalDisplay[] dpys = createAllDisplays();
+    if (dpys == null) {
+      System.err.println("Couldn't create Displays!");
+      System.exit(1);
+      return;
+    }
+
+    float[][] samples = new float[1][1];
+    samples[0][0] = 0.0f;
+
+    StupidData data;
+    try {
+      Set set = new visad.Gridded1DSet(visad.RealType.Generic, samples,
+                                       samples.length);
+
+      data = new StupidData(set);
+      data.setSamples(samples);
+    } catch (RemoteException re) {
+      re.printStackTrace();
+      data = null;
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      data = null;
+    }
+
+    for (int i = 0; i < numMessages; i++) {
+      int n = rand.nextInt() % dpys.length;
+      if (n < 0) {
+        n = -n;
+      }
+
+      try {
+        samples[0][0] += 1.0f;
+        data.setSamples(samples);
+      } catch (RemoteException re) {
+        re.printStackTrace();
+        data = null;
+      } catch (VisADException ve) {
+        ve.printStackTrace();
+        data = null;
+      }
+
+      String msgStr = "Msg#" + i + " from dpy#" + n;
+      try {
+        dpys[n].sendMessage(new MessageEvent(msgStr,
+                                             new RemoteFieldImpl(data)));
+      } catch (RemoteException re) {
+        System.err.println("Couldn't send message \"" + msgStr + "\":");
+        re.printStackTrace();
+      }
+
+      try { Thread.sleep(1000); } catch (InterruptedException ie) { }
+    }
+
+    try { Thread.sleep(5000); } catch (InterruptedException ie) { }
+
+    System.exit(0);
+  }
+
+  private LocalDisplay[] createAllDisplays()
+  {
+    DisplayImplJ2D srvr;
+    try {
+      srvr = new DisplayImplJ2D("root");
+    } catch (RemoteException re) {
+      return null;
+    } catch (VisADException ve) {
+      return null;
+    }
+    srvr.addMessageListener(new MsgListener("server"));
+
+    LocalDisplay[] dpys = new LocalDisplay[numClients+1];
+    dpys[0] = srvr;
+
+    RemoteDisplayImpl rmtSrvr;
+    try {
+      rmtSrvr = new RemoteDisplayImpl(srvr);
+    } catch (RemoteException re) {
+      return null;
+    }
+
+    for (int i = 0; i < numClients; i++) {
+      try {
+        dpys[i+1] = new DisplayImplJ2D(rmtSrvr);
+      } catch (RemoteException re) {
+        return null;
+      } catch (VisADException ve) {
+        return null;
+      }
+
+      dpys[i+1].addMessageListener(new MsgListener("cli#" + i));
+    }
+
+    return dpys;
+  }
+
+  public boolean processArgs(String[] args)
+  {
+    boolean usage = false;
+
+    String className = getClass().getName();
+    int pt = className.lastIndexOf('.');
+    final int ds = className.lastIndexOf('$');
+    if (ds > pt) {
+      pt = ds;
+    }
+    String progName = className.substring(pt == -1 ? 0 : pt + 1);
+
+    for (int i = 0; args != null && i < args.length; i++) {
+      if (args[i].length() > 0 && args[i].charAt(0) == '-') {
+        char ch = args[i].charAt(1);
+
+        String str, result;
+
+        switch (ch) {
+        case 'c':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing number of clients for \"-c\"");
+            usage = true;
+          } else {
+            try {
+              numClients = Integer.parseInt(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad number of clients \"" + str + "\"");
+              numClients = 2;
+              usage = true;
+            }
+
+            if (numClients < 1) {
+              System.err.println(progName +
+                                 ": Need at least one client!");
+              usage = true;
+            }
+          }
+          break;
+        case 'm':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing number of messages for \"-m\"");
+            usage = true;
+          } else {
+            try {
+              numMessages = Integer.parseInt(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad number of messages \"" + str + "\"");
+              numMessages = 2;
+              usage = true;
+            }
+
+            if (numMessages < 1) {
+              System.err.println(progName +
+                                 ": Need at least one client!");
+              usage = true;
+            }
+          }
+          break;
+        case 's':
+          str = (args[i].length() > 2 ? args[i].substring(2) :
+                 ((i + 1) < args.length ? args[++i] : null));
+          if (str == null) {
+            System.err.println(progName +
+                               ": Missing random seed value for \"-s\"");
+            usage = true;
+          } else {
+            try {
+              randSeed = Long.parseLong(str);
+            } catch (NumberFormatException nfe) {
+              System.err.println(progName +
+                                 ": Bad random seed value \"" + str + "\"");
+              usage = true;
+            }
+          }
+          break;
+        case 'v':
+          verbose = true;
+          break;
+        default:
+          System.err.println(progName +
+                             ": Unknown option \"-" + ch + "\"");
+          usage = true;
+          break;
+        }
+      } else {
+        System.err.println(progName + ": Unknown keyword \"" + args[i] + "\"");
+        usage = true;
+      }
+    }
+
+    if (usage) {
+      System.err.println("Usage: " + getClass().getName() +
+                         " [-c numClients]" +
+                         " [-m numMessages]" +
+                         " [-s randomSeed]" +
+                         " [-v(erbose)]" +
+                         "");
+    }
+
+    return !usage;
+  }
+
+  class MsgListener
+    implements MessageListener
+  {
+    private String name;
+
+    public MsgListener(String name)
+    {
+      this.name = name;
+    }
+
+    public void receiveMessage(MessageEvent msg)
+      throws RemoteException
+    {
+      System.out.println(name + ": " + msg);
+    }
+  }
+
+  public static void main(String[] args)
+  {
+    new TestMsg(args);
+  }
+
+  class StupidData
+    extends FlatField
+  {
+    public StupidData(Set set)
+      throws VisADException
+    {
+      super(FunctionType.REAL_1TO1_FUNCTION, set);
+    }
+  }
+}
diff --git a/examples/TestPlotDigits.java b/examples/TestPlotDigits.java
new file mode 100644
index 0000000..621f00d
--- /dev/null
+++ b/examples/TestPlotDigits.java
@@ -0,0 +1,122 @@
+//
+// TestPlotDigits.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import visad.*;
+
+/**
+   TestPlotDigits calculates an array of points to be plotted to
+   the screen as vector pairs, given a number and a bounding
+   rectangle, for use as a label on a contour R^2.<P>
+
+   It is implemented as an applet so that it can be
+   tested graphically with the appletviewer utility.<P>
+*/
+public class TestPlotDigits extends JPanel implements MouseListener {
+
+  // Variables
+  protected PlotDigits plot;
+  protected int reverseLetters = 0;
+  protected int width, height;
+
+  /* run 'java TestPlotDigits' to test the PlotDigits class. */
+  public static void main(String[] args) {
+    if (args.length < 3) args = new String[] {"-73.81", "600", "200"};
+    float number = (float) Double.parseDouble(args[0]);
+    int width = Integer.parseInt(args[1]);
+    int height = Integer.parseInt(args[2]);
+
+    JFrame frame = new JFrame("PlotDigits test window");
+    frame.setContentPane(new TestPlotDigits(number, width, height));
+    frame.setBounds(100, 100, width, height);
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    frame.show();
+  }
+
+  public TestPlotDigits(float number, int width, int height) {
+    addMouseListener(this);
+    plot = new PlotDigits();
+    plot.Number = number;
+    this.width = width;
+    this.height = height;
+    try {
+      boolean[] swap = {false, false, false};
+      plot.plotdigits(plot.Number, 0, 0, height, 7*width/8, 150, swap);
+    }
+    catch (VisADException VE) {
+      System.out.println("TestPlotDigits: "+VE);
+      System.exit(1);
+    }
+  }
+
+  public void mouseClicked(MouseEvent e) {
+    reverseLetters = (reverseLetters+1)%4;
+    Graphics g = getGraphics();
+    if (g != null) {
+      paint(g);
+      g.dispose();
+    }
+  }
+
+  public void mousePressed(MouseEvent e) {;}
+  public void mouseReleased(MouseEvent e) {;}
+  public void mouseEntered(MouseEvent e) {;}
+  public void mouseExited(MouseEvent e) {;}
+
+  public void paint(Graphics g) {
+    g.setColor(Color.white);
+    g.fillRect(0, 0, width, height);
+    g.setColor(Color.black);
+    for (int i=0; i<plot.NumVerts; i+=2) {
+      int v1, v2, v3, v4;
+      if (reverseLetters%2 == 1) { // y is backwards
+        v1 = (int) plot.VyB[i];
+        v3 = (int) plot.VyB[(i+1)%plot.NumVerts];
+      }
+      else {
+        v1 = (int) plot.Vy[i];
+        v3 = (int) plot.Vy[(i+1)%plot.NumVerts];
+      }
+      if (reverseLetters > 1) { // x is backwards
+        v2 = (int) plot.VxB[i];
+        v4 = (int) plot.VxB[(i+1)%plot.NumVerts];
+      }
+      else {
+        v2 = (int) plot.Vx[i];
+        v4 = (int) plot.Vx[(i+1)%plot.NumVerts];
+      }
+      g.drawLine(v1, v2, v3, v4);
+    }
+  }
+
+}
+
diff --git a/examples/TestSkeleton.java b/examples/TestSkeleton.java
new file mode 100644
index 0000000..031c898
--- /dev/null
+++ b/examples/TestSkeleton.java
@@ -0,0 +1,264 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import java.util.logging.Level;
+import visad.DataReference;
+import visad.DisplayImpl;
+import visad.LocalDisplay;
+import visad.RemoteDisplayImpl;
+import visad.RemoteServer;
+import visad.RemoteServerImpl;
+import visad.RemoteSourceListener;
+import visad.VisADException;
+
+import visad.util.ClientServer;
+import visad.util.CmdlineConsumer;
+import visad.util.CmdlineParser;
+import visad.util.Util;
+
+public abstract class TestSkeleton
+  extends Thread
+  implements CmdlineConsumer, RemoteSourceListener
+{
+  boolean startServer;
+  String hostName;
+
+  private static final int maximumWaitTime = 60;
+  private int verbosity = 0;
+
+  private CmdlineParser cmdline;
+
+  public TestSkeleton()
+  {
+    cmdline = new CmdlineParser(this);
+  }
+
+  public TestSkeleton(String[] args)
+    throws RemoteException, VisADException
+  {
+    this();
+
+    if (!processArgs(args)) {
+      System.err.println("Exiting...");
+      System.exit(1);
+    }
+    startThreads();
+  }
+
+  boolean hasClientServerMode() { return true; }
+
+  public void initializeArgs() { startServer = false; hostName = null; }
+
+  public int checkOption(String mainName, char ch, String arg)
+  {
+    if (ch == 'c') {
+      if (arg == null) {
+        System.err.println(mainName + ": Missing hostname for \"-c\"");
+        return -1;
+      }
+
+      if (!hasClientServerMode()) {
+        System.err.println("Client/server mode not supported" +
+                           " for " + mainName);
+        return -1;
+      }
+
+      if (startServer) {
+        System.err.println(mainName +
+                           ": Cannot specify both '-c' and '-s'!");
+        return -1;
+      }
+
+      hostName = arg;
+      return 2;
+    }
+
+    if (ch == 's') {
+      if (hostName != null) {
+        System.err.println(mainName +
+                           ": Cannot specify both '-c' and '-s'!");
+        return -1;
+      }
+
+      if (!hasClientServerMode()) {
+        System.err.println("Client/server mode not supported" +
+                           " for " + mainName);
+        return -1;
+      }
+
+      startServer = true;
+      return 1;
+    }
+
+    if (ch == 'v') {
+      verbosity++;
+      return 1;
+    }
+
+    return 0;
+  }      
+
+  public String optionUsage()
+  {
+    if (hasClientServerMode()) {
+      return " [-c(lient) hostname] [-s(erver)]";
+    }
+
+    return "";
+  }
+
+  public int checkKeyword(String mainName, int argc, String[] args)
+  {
+    return 0;
+  }
+
+  public String keywordUsage() { return ""; }
+
+  public boolean finalizeArgs(String mainName) {
+    Util.configureLogging(verbosity);
+    return true;
+  }
+
+  boolean processArgs(String[] args) { return cmdline.processArgs(args); }
+
+  boolean isServer() { return (startServer && hostName == null); }
+  boolean isClient() { return (!startServer && hostName != null); }
+  boolean isStandalone() { return (!startServer && hostName == null); }
+
+  String getClientServerTitle()
+  {
+    if (isServer()) {
+      return " server";
+    } else if (isClient()) {
+      return " client";
+    } else if (isStandalone()) {
+      return " standalone";
+    }
+    return " unknown";
+  }
+
+  DataReference[] getClientDataReferences()
+    throws RemoteException, VisADException
+  {
+    return null;
+  }
+
+  void finishClientSetup(RemoteServer client)
+    throws RemoteException, VisADException
+  {
+  }
+
+  public void dataSourceLost(String name)
+  {
+    System.err.println("Lost Data object \"" + name + "\"");
+  }
+
+  public void collabSourceLost(int connectionID)
+  {
+    System.err.println("Lost collaboration source #" + connectionID);
+  }
+
+  LocalDisplay[] setupClientData()
+    throws RemoteException, VisADException
+  {
+    // build local data references
+    DataReference[] refs = getClientDataReferences();
+
+    RemoteServer client;
+    try {
+      client = ClientServer.connectToServer(hostName, getClass().getName(),
+                                            true);
+    } catch (VisADException ve) {
+      System.err.println(ve.getMessage());
+      System.exit(1);
+      client = null;
+    }
+
+    LocalDisplay[] dpys = ClientServer.getClientDisplays(client, refs);
+    if (dpys == null) {
+      throw new VisADException("No remote displays found!");
+    }
+
+    for (int i = 0; i < dpys.length; i++) {
+      ((DisplayImpl )dpys[i]).addRemoteSourceListener(this);
+    }
+
+    // fetch any data references from server
+    finishClientSetup(client);
+
+    return dpys;
+  }
+
+  abstract DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException;
+
+  abstract void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException;
+
+  void setServerDataReferences(RemoteServerImpl server)
+    throws RemoteException, VisADException
+  {
+  }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+  }
+
+  public void startThreads()
+    throws RemoteException, VisADException
+  {
+    LocalDisplay[] local;
+    if (isClient()) {
+      local = setupClientData();
+    } else {
+      DisplayImpl[] displays = setupServerDisplays();
+
+      RemoteServerImpl server;
+      if (!startServer) {
+        server = null;
+      } else {
+        server = ClientServer.startServer(getClass().getName());
+
+        // add all displays to server
+        if (displays != null) {
+          for (int i = 0; i < displays.length; i++) {
+            server.addDisplay(new RemoteDisplayImpl(displays[i]));
+          }
+        }
+      }
+      setServerDataReferences(server);
+
+      local = displays;
+      setupServerData(local);
+    }
+
+    setupUI(local);
+  }
+
+  public String toString()
+  {
+    return null;
+  }
+}
diff --git a/examples/TestStereo.java b/examples/TestStereo.java
new file mode 100644
index 0000000..4ffe09c
--- /dev/null
+++ b/examples/TestStereo.java
@@ -0,0 +1,112 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import java.rmi.RemoteException;
+
+import visad.*;
+import java.awt.*;
+import javax.media.j3d.*;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class TestStereo
+  extends TestSkeleton
+{
+  public TestStereo() { }
+
+  public TestStereo(String args[])
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  DisplayImpl[] setupServerDisplays()
+    throws RemoteException, VisADException
+  {
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+
+    GraphicsConfigTemplate3D gct3d = new GraphicsConfigTemplate3D();
+    gct3d.setStereo(GraphicsConfigTemplate3D.REQUIRED);
+
+    GraphicsConfiguration config =
+    gct3d.getBestConfiguration(gd.getConfigurations());
+
+    if (config == null)
+    {
+        System.err.println("Unable to find a Stereo visual");
+        System.exit(1);
+    }
+
+    DisplayImpl[] dpys = new DisplayImpl[1];
+    dpys[0] = new DisplayImplJ3D("display1", DisplayImplJ3D.APPLETFRAME,
+                                 config);
+    return dpys;
+  }
+
+  void setupServerData(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+    RealType[] scatter_list = {vis_radiance, ir_radiance, count, RealType.Latitude,
+                               RealType.Longitude, RealType.Radius};
+    RealTupleType scatter = new RealTupleType(scatter_list);
+    RealType[] time = {RealType.Time};
+    RealTupleType time_type = new RealTupleType(time);
+    FunctionType scatter_function = new FunctionType(time_type, scatter);
+
+    int size = 64;
+
+    FlatField imaget1;
+    imaget1 = FlatField.makeField(scatter_function, size, false);
+
+    dpys[0].addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    dpys[0].addMap(new ScalarMap(RealType.Longitude, Display.Green));
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.ZAxis));
+    dpys[0].addMap(new ScalarMap(ir_radiance, Display.XAxis));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Blue));
+    dpys[0].addMap(new ConstantMap(0.5, Display.Red));
+
+    // WLH 28 April 99 - test alpha with points
+    dpys[0].addMap(new ScalarMap(vis_radiance, Display.Alpha));
+
+    GraphicsModeControl mode = dpys[0].getGraphicsModeControl();
+    mode.setPointSize(5.0f);
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    dpys[0].addReference(ref_imaget1, null);
+  }
+
+  public String toString() { return ": stereo scatter diagram"; }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new TestStereo(args);
+  }
+}
+
diff --git a/examples/UISkeleton.java b/examples/UISkeleton.java
new file mode 100644
index 0000000..02a03dd
--- /dev/null
+++ b/examples/UISkeleton.java
@@ -0,0 +1,96 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.rmi.RemoteException;
+
+import visad.LocalDisplay;
+import visad.VisADException;
+
+public abstract class UISkeleton
+  extends TestSkeleton
+{
+  public UISkeleton() { }
+
+  public UISkeleton(String[] args)
+    throws RemoteException, VisADException
+  {
+    super(args);
+  }
+
+  Component getSpecialComponent(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    return null;
+  }
+
+  String getFrameTitle() { return "VisAD generic user interface"; }
+
+  void setupUI(LocalDisplay[] dpys)
+    throws RemoteException, VisADException
+  {
+    Component special = getSpecialComponent(dpys);
+    if (special == null && dpys.length == 1) {
+      special = dpys[0].getComponent();
+    }
+
+    Container content;
+    if (special != null) {
+      if (special instanceof Container) {
+        content = (Container )special;
+      } else {
+        JPanel wrapper = new JPanel();
+        wrapper.setLayout(new BorderLayout());
+        wrapper.add("Center", special);
+        content = wrapper;
+      }
+    } else {
+      JPanel big_panel = new JPanel();
+      big_panel.setLayout(new BoxLayout(big_panel, BoxLayout.X_AXIS));
+      big_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+      big_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+      for (int i = 0; i < dpys.length; i++) {
+        big_panel.add(dpys[i].getComponent());
+      }
+      content = big_panel;
+    }
+
+    JFrame jframe = new JFrame(getFrameTitle() + getClientServerTitle());
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+    jframe.setContentPane(content);
+    jframe.pack();
+    jframe.setVisible(true);
+  }
+}
diff --git a/examples/VerySimple.java b/examples/VerySimple.java
new file mode 100644
index 0000000..181ec33
--- /dev/null
+++ b/examples/VerySimple.java
@@ -0,0 +1,62 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// import needed classes
+import visad.*;
+import visad.util.DataUtility;
+import visad.data.netcdf.Plain;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import javax.swing.*;
+
+public class VerySimple {
+
+  // type 'java VerySimple' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    // create a netCDF reader
+    Plain plain = new Plain();
+
+    // read an image sequence from a netCDF file into a data object
+    DataImpl image_sequence = plain.open("images.nc");
+
+    // create a display for the image sequence
+    DisplayImpl display = DataUtility.makeSimpleDisplay(image_sequence);
+
+    // start animation
+    AnimationControl control =
+      (AnimationControl) display.getControl(AnimationControl.class);
+    control.setOn(true);
+
+    // create JFrame (i.e., a window) for the display
+    JFrame frame = new JFrame("VerySimple VisAD Application");
+
+    // link the display to the JFrame
+    frame.getContentPane().add(display.getComponent());
+
+    // set the size of the JFrame and make it visible
+    frame.setSize(400, 400);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/examples/VisuTraj.java b/examples/VisuTraj.java
new file mode 100644
index 0000000..10fc8e7
--- /dev/null
+++ b/examples/VisuTraj.java
@@ -0,0 +1,153 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/* by Dr. Christian C. Mullon, University of Cape Town */
+
+import visad.*;
+import visad.java3d.DisplayImplJ3D;
+import java.rmi.RemoteException;
+import java.awt.*;
+import javax.swing.*;
+import java.awt.event.*;
+
+public class VisuTraj{
+
+    int nbTrajectories ;
+
+    float [][][] vectTrajectories ;
+    // -------------------------------------------
+    DataReferenceImpl referenceTrajectories ;
+    // -------------------------------------------
+    RealType latitude ;
+    RealType longitude ;
+    RealType profondeur ;
+    RealType trajectories ;
+    // -------------------------------------------
+    RealTupleType domain3D ;
+
+// -------------------------------------------
+public VisuTraj() throws RemoteException, VisADException {
+    nbTrajectories = 60;
+    }
+
+
+// -------------------------------------------
+DataImpl makeTrajectories() throws VisADException, RemoteException {
+  SampledSet[] setTrajectories = new SampledSet[nbTrajectories];
+  for(int t=0;t < nbTrajectories; t ++){
+    float [][] traject = new float [3][5];
+    for(int i=0;i<3;i++) for(int p=0;p<5;p++)
+      traject[i][p] = vectTrajectories[i][p][t];
+    setTrajectories[t] = new Gridded3DSet( domain3D, traject,5,null,null,null);
+    }
+  return new UnionSet(domain3D, setTrajectories);
+  }
+// -------------------------------------------
+public void initVisad(){
+ try{
+    // -------------------------------------------
+    latitude = RealType.getRealType("latitude");
+    longitude = RealType.getRealType("longitude");
+    profondeur = RealType.getRealType("profondeur");
+    trajectories = RealType.getRealType("trajectories");
+    // -------------------------------------------
+    domain3D = new RealTupleType(latitude, longitude, profondeur);
+    // -------------------------------------------
+    referenceTrajectories = new DataReferenceImpl("trajectories");
+    DataImpl theTrajectories = makeTrajectories();
+    referenceTrajectories.setData(theTrajectories);
+    // -------------------------------------------
+    ScalarMap latMap = new ScalarMap(latitude, Display.YAxis);
+    ScalarMap lonMap = new ScalarMap(longitude, Display.XAxis);
+    ScalarMap altMap = new ScalarMap(profondeur, Display.ZAxis);
+    ScalarMap colMap = new ScalarMap(profondeur, Display.RGBA );
+    // -------------------------------------------
+    DisplayImpl display = new DisplayImplJ3D("display");
+    // -------------------------------------------
+    display.addMap( latMap );
+    display.addMap( lonMap );
+    display.addMap( altMap );
+    // -------------------------------------------
+    display.addReference(referenceTrajectories);
+    // -------------------------------------------
+    JFrame jframe = new JFrame("VisAD Tutorial");
+    // -------------------------------------------
+    jframe.getContentPane().add(display.getComponent());
+    jframe.setSize(600, 600);
+    jframe.setLocation(300,300);
+    // -------------------------------------------
+    jframe.setVisible(true);
+    }
+    catch(Exception e){
+      e.printStackTrace();
+      System.exit(0);
+      }
+  }
+
+// -------------------------------------------
+public void step(int t){
+    int n = 0;
+    try{ Thread.sleep(100);
+        setData();
+        DataImpl theTrajectories = makeTrajectories();
+        referenceTrajectories.setData(theTrajectories);
+      System.out.println(" iteration "+t);
+       }
+    catch(Exception e){
+      e.printStackTrace();
+      System.exit(0);
+      }
+   }
+// -------------------------------------------
+public void initData(){
+    vectTrajectories = new float[3][5][nbTrajectories];
+    for(int p = 0; p<nbTrajectories;p++){
+     for(int i=0;i<3;i++) {
+        vectTrajectories[i][0][p] =   (float)Math.random()*50.1f;
+        for(int t =1; t<5;t++){
+          vectTrajectories[i][t][p] =   vectTrajectories[i][t-1][p]+
+                                 ((float)Math.random()- 0.5f)* 4.0f;
+          }
+        }
+      }
+     }
+// -------------------------------------------
+public void setData(){
+    for(int p = 0; p<nbTrajectories;p++){
+     for(int i=0;i<3;i++) {
+        for(int t =0; t<4;t++) {
+          vectTrajectories[i][t][p] =   vectTrajectories[i][t+1][p];
+          }
+        vectTrajectories[i][4][p] +=   ((float)Math.random()- 0.5f)* 4.0f;
+        }
+      }
+     }
+// -------------------------------------------
+public static void main(String[] args) throws RemoteException, VisADException{
+   VisuTraj vb = new VisuTraj();
+    vb.initData();
+    vb.initVisad();
+    for(int it = 0;it<500;it++)vb.step(it);
+    }
+
+}
+
diff --git a/gnu/regexp/CharIndexed.java b/gnu/regexp/CharIndexed.java
new file mode 100644
index 0000000..7f535a0
--- /dev/null
+++ b/gnu/regexp/CharIndexed.java
@@ -0,0 +1,27 @@
+/*
+ *  gnu/regexp/CharIndexed.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package gnu.regexp;
+
+interface CharIndexed {
+  public static final char OUT_OF_BOUNDS = '\uFFFF';
+
+  public char charAt(int index);
+  public boolean move(int index);
+  public boolean isValid();
+}
diff --git a/gnu/regexp/CharIndexedCharArray.java b/gnu/regexp/CharIndexedCharArray.java
new file mode 100644
index 0000000..309f18b
--- /dev/null
+++ b/gnu/regexp/CharIndexedCharArray.java
@@ -0,0 +1,41 @@
+/*
+ *  gnu/regexp/CharIndexedCharArray.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package gnu.regexp;
+
+class CharIndexedCharArray implements CharIndexed {
+  private char[] s;
+  private int m_index;
+
+  CharIndexedCharArray(char[] str, int index) {
+    s = str;
+    m_index = index;
+  }
+
+  public char charAt(int index) {
+    return ((m_index + index) < s.length) ? s[m_index + index] : CharIndexed.OUT_OF_BOUNDS;
+  }
+
+  public boolean isValid() {
+    return (m_index < s.length);
+  }
+
+  public boolean move(int index) {
+    return ((m_index += index) < s.length);
+  }
+}
diff --git a/gnu/regexp/CharIndexedInputStream.java b/gnu/regexp/CharIndexedInputStream.java
new file mode 100644
index 0000000..7203129
--- /dev/null
+++ b/gnu/regexp/CharIndexedInputStream.java
@@ -0,0 +1,113 @@
+/*
+ *  gnu/regexp/CharIndexedReader.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+
+// FIXME: Integer.MAX_VALUE is a hack
+// TODO: move(x) shouldn't rely on calling next() x times
+
+class CharIndexedInputStream implements CharIndexed {
+  private static final int BUFFER_INCREMENT = 1024;
+
+  private BufferedInputStream br;
+  private int m_index, m_end, m_bufsize;
+  private char cached;
+  
+  CharIndexedInputStream(InputStream str, int index) {
+    if (str instanceof BufferedInputStream) br = (BufferedInputStream) str;
+    else br = new BufferedInputStream(str,BUFFER_INCREMENT);
+    m_bufsize = BUFFER_INCREMENT;
+    m_index = -1;
+    m_end = Integer.MAX_VALUE; // end is unknown
+    next();
+    if (index > 0) move(index);
+  }
+
+  private boolean next() {
+    if (m_end == 1) return false;
+    m_end--; // closer to end
+    try {
+      if (m_index != -1) {
+	br.reset();
+      }
+      int i = br.read();
+      br.mark(m_bufsize);
+      if (i == -1) {
+	m_end = 1;
+	cached = CharIndexed.OUT_OF_BOUNDS;
+	return false;
+      }
+      cached = (char) i;
+      m_index = 1;
+    } catch (IOException e) { 
+      e.printStackTrace();
+      cached = CharIndexed.OUT_OF_BOUNDS;
+      return false; 
+    }
+    return true;
+  }
+
+  public char charAt(int index) {
+    if (index == 0) return cached;
+    if (index >= m_end) return CharIndexed.OUT_OF_BOUNDS;
+    if (index >= m_bufsize) {
+      // Allocate more space in the buffer.
+      try {
+	while (m_bufsize <= index) m_bufsize += BUFFER_INCREMENT;
+	br.reset();
+	br.mark(m_bufsize);
+	br.skip(index-1);
+      } catch (IOException e) { }
+    } else if (m_index != index) {
+      try {
+	br.reset();
+	br.skip(index-1);
+      } catch (IOException e) { }
+    }
+    char ch = CharIndexed.OUT_OF_BOUNDS;
+
+    try {
+      int i = br.read();
+      m_index = index+1; // m_index is index of next pos relative to charAt(0)
+      if (i == -1) {
+	// set flag that next should fail next time?
+	m_end = index;
+	return ch;
+      }
+      ch = (char) i;
+    } catch (IOException ie) { }
+
+    return ch;
+  }
+
+  public boolean move(int index) {
+    // move read position [index] clicks from 'charAt(0)'
+    boolean retval = true;
+    while (retval && (index-- > 0)) retval = next();
+    return retval;
+  }
+
+  public boolean isValid() {
+    return (cached != CharIndexed.OUT_OF_BOUNDS);
+  }
+}
+
diff --git a/gnu/regexp/CharIndexedString.java b/gnu/regexp/CharIndexedString.java
new file mode 100644
index 0000000..8b0e63c
--- /dev/null
+++ b/gnu/regexp/CharIndexedString.java
@@ -0,0 +1,41 @@
+/*
+ *  gnu/regexp/CharIndexedString.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package gnu.regexp;
+
+class CharIndexedString implements CharIndexed {
+  private String s;
+  private int m_index;
+
+  CharIndexedString(String str, int index) {
+    s = str;
+    m_index = index;
+  }
+
+  public char charAt(int index) {
+    return ((m_index + index) < s.length()) ? s.charAt(m_index + index) : CharIndexed.OUT_OF_BOUNDS;
+  }
+
+  public boolean isValid() {
+    return (m_index < s.length());
+  }
+
+  public boolean move(int index) {
+    return ((m_index += index) < s.length());
+  }
+}
diff --git a/gnu/regexp/CharIndexedStringBuffer.java b/gnu/regexp/CharIndexedStringBuffer.java
new file mode 100644
index 0000000..c4d30fa
--- /dev/null
+++ b/gnu/regexp/CharIndexedStringBuffer.java
@@ -0,0 +1,41 @@
+/*
+ *  gnu/regexp/CharIndexedStringBuffer.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package gnu.regexp;
+
+class CharIndexedStringBuffer implements CharIndexed {
+  private StringBuffer s;
+  private int m_index;
+
+  CharIndexedStringBuffer(StringBuffer str, int index) {
+    s = str;
+    m_index = index;
+  }
+
+  public char charAt(int index) {
+    return ((m_index + index) < s.length()) ? s.charAt(m_index + index) : CharIndexed.OUT_OF_BOUNDS;
+  }
+
+  public boolean isValid() {
+    return (m_index < s.length());
+  }
+
+  public boolean move(int index) {
+    return ((m_index += index) < s.length());
+  }
+}
diff --git a/gnu/regexp/Makefile.dist b/gnu/regexp/Makefile.dist
new file mode 100644
index 0000000..1982033
--- /dev/null
+++ b/gnu/regexp/Makefile.dist
@@ -0,0 +1,69 @@
+
+# As long as javac, javacc, and javadoc are in the path, no configure script
+# should be needed.
+
+JAVA = java
+JAVAC = javac
+JAVAFLAGS =
+JAVACC = javacc
+JAVADOC = javadoc
+JAVADOCFLAGS = -version -author
+
+# testsuite specifics
+RUNTEST = runtest
+RUNTESTFLAGS = #--all --verbose
+
+# Simple dialog box classes
+JAVA = 				\
+	 RE.java \
+	 CharIndexed.java \
+	 CharIndexedCharArray.java \
+	 CharIndexedInputStream.java \
+	 CharIndexedString.java \
+	 CharIndexedStringBuffer.java \
+	 REException.java \
+	 REFilterInputStream.java \
+	 REMatch.java \
+	 REMatchEnumeration.java \
+	 RESyntax.java \
+	 REToken.java \
+	 RETokenAny.java \
+	 RETokenBackRef.java \
+	 RETokenChar.java \
+	 RETokenEnd.java \
+	 RETokenOneOf.java \
+	 RETokenPOSIX.java \
+	 RETokenRange.java \
+	 RETokenRepeated.java \
+	 RETokenStart.java 
+
+
+# Classes used by the JavaCC parser
+CLASSES := $(JAVA:%.java=%.class)
+
+#
+# Build the *.java files from the grammar files and then build the *.class
+# files. 
+
+all: util $(CLASSES)
+
+.PHONY: util
+util:
+	(cd util; $(MAKE) $(MFLAGS) all)
+
+# Remove all classes
+clean:
+	-$(RM) *.class
+	-$(RM) *~
+	-$(RM) *%
+	-$(RM) -r doc/[a-z]*
+	(cd util; $(MAKE) $(MFLAGS) clean)
+
+
+distclean: clean
+	(cd util; $(MAKE) $(MFLAGS) distclean)
+
+# Rules
+
+%.class : %.java
+	$(JAVAC) $(JAVAFLAGS) $<
diff --git a/gnu/regexp/RE.java b/gnu/regexp/RE.java
new file mode 100644
index 0000000..2942da0
--- /dev/null
+++ b/gnu/regexp/RE.java
@@ -0,0 +1,1128 @@
+/*
+ *  gnu/regexp/RE.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.io.InputStream;
+import java.util.Vector;
+
+class IntPair {
+  public int first, second;
+}
+
+class CharUnit {
+  public char ch;
+  public boolean bk;
+}
+
+/**
+ * RE provides the user interface for compiling and matching regular
+ * expressions.
+ * <P>
+ * A regular expression object (class RE) is compiled by constructing it
+ * from a String, StringBuffer or character array, with optional 
+ * compilation flags (below)
+ * and an optional syntax specification (see RESyntax; if not specified,
+ * <code>RESyntax.RE_SYNTAX_PERL5</code> is used).
+ * <P>
+ * Various methods attempt to match input text against a compiled
+ * regular expression.  These methods are:
+ * <LI><code>isMatch</code>: returns true if the input text in its entirety
+ * matches the regular expression pattern.
+ * <LI><code>getMatch</code>: returns the first match found in the input text,
+ * or null if no match is found.
+ * <LI><code>getAllMatches</code>: returns an array of all non-overlapping 
+ * matches found in the input text.  If no matches are found, the array is
+ * zero-length.
+ * <LI><code>substitute</code>: substitute the first occurence of the pattern
+ * in the input text with a replacement string (which may include
+ * metacharacters $0-$9, see REMatch.substituteInto).
+ * <LI><code>substituteAll</code>: same as above, but repeat for each match
+ * before returning.
+ * <LI><code>getMatchEnumeration</code>: returns an REMatchEnumeration object
+ * that allows iteration over the matches (see REMatchEnumeration for some
+ * reasons why you may want to do this instead of using <code>getAllMatches</code>.
+ * <P>
+ * These methods all have similar argument lists.  The input can be a
+ * String, a character array, a StringBuffer or an InputStream of some sort.
+ * Note that
+ * when using an InputStream, the stream read position cannot be guaranteed
+ * after attempting a match (this is not a bug, but a consequence of the way
+ * regular expressions work).  Using an REMatchEnumeration can eliminate most
+ * positioning problems.
+ * <P>
+ * The optional index argument specifies the offset from the beginning of the
+ * text at which the search should start (see the descriptions of some of
+ * the execution flags for how this can affect positional pattern operators).
+ * For an InputStream, this means an offset from the current read position,
+ * so subsequent calls with the same index argument on an InputStream will not
+ * necessarily be accessing the same position on the stream, whereas repeated
+ * searches at a given index in a fixed string will return consistent
+ * results.
+ * <P>
+ * You can optionally affect the execution environment by using a
+ * combination of execution flags (constants listed below).
+ *
+ * @author <A HREF="mailto:wes at cacas.org">Wes Biggs</A>
+ * @version 1.0.8, 21 March 1999
+ */
+
+public class RE extends REToken {
+  // This String will be returned by getVersion()
+  private static final String s_version = "1.0.8";
+
+  // These are, respectively, the first and last tokens in our linked list
+  // If there is only one token, firstToken == lastToken
+  private REToken firstToken, lastToken;
+
+  // This is the number of subexpressions in this regular expression,
+  // with a minimum value of zero.  Returned by getNumSubs()
+  private int m_numSubs;
+
+  /**
+   * Compilation flag. Do  not  differentiate  case.   Subsequent
+   * searches  using  this  RE will be case insensitive.
+   */
+  public static final int REG_ICASE = 2;
+
+  /**
+   * Compilation flag. The match-any-character operator (dot)
+   * will match a newline character.  When set this overrides the syntax
+   * bit RE_DOT_NEWLINE (see RESyntax for details).  This is equivalent to
+   * the "/s" operator in Perl.
+   */
+  public static final int REG_DOT_NEWLINE = 4;
+
+  /**
+   * Compilation flag. Use multiline mode.  In this mode, the ^ and $
+   * anchors will match based on newlines within the input. This is
+   * equivalent to the "/m" operator in Perl.
+   */
+  public static final int REG_MULTILINE = 8;
+
+  /**
+   * Execution flag.
+   * The match-beginning operator (^) will not match at the beginning
+   * of the input string. Useful for matching on a substring when you
+   * know the context of the input is such that position zero of the
+   * input to the match test is not actually position zero of the text.
+   * <P>
+   * This example demonstrates the results of various ways of matching on
+   * a substring.
+   * <P>
+   * <CODE>
+   * String s = "food bar fool";<BR>
+   * RE exp = new RE("^foo.");<BR>
+   * REMatch m0 = exp.getMatch(s);<BR>
+   * REMatch m1 = exp.getMatch(s.substring(8));<BR>
+   * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL); <BR>
+   * REMatch m3 = exp.getMatch(s,8);                            <BR>
+   * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX);         <BR>
+   * <P>
+   * // Results:<BR>
+   * //  m0 = "food"<BR>
+   * //  m1 = "fool"<BR>
+   * //  m2 = null<BR>
+   * //  m3 = null<BR>
+   * //  m4 = "fool"<BR>
+   * </CODE>
+   */
+  public static final int REG_NOTBOL = 16;
+
+  /**
+   * Execution flag.
+   * The match-end operator ($) does not match at the end
+   * of the input string. Useful for matching on substrings.
+   */
+  public static final int REG_NOTEOL = 32;
+
+  /**
+   * Execution flag.
+   * The match-beginning operator (^) matches not at position 0
+   * in the input string, but at the position the search started at
+   * (based on the index input given to the getMatch function).  See
+   * the example under REG_NOTBOL.
+   */
+  public static final int REG_ANCHORINDEX = 64;
+
+  /** Returns a string representing the version of the gnu.regexp package. */
+  public static final String version() {
+    return s_version;
+  }
+
+  /**
+   * Constructs a regular expression pattern buffer without any compilation
+   * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5).
+   *
+   * @param pattern A regular expression pattern, in the form of a String,
+   *   StringBuffer or char[].
+   * @exception REException The input pattern could not be parsed.
+   * @exception IllegalArgumentException The pattern was not a String, 
+   *   StringBuffer or char[].
+   * @exception NullPointerException The pattern was null.
+   */
+  public RE(Object pattern) throws REException {
+    this(pattern,0,RESyntax.RE_SYNTAX_PERL5,0,0);
+  }
+
+  /**
+   * Constructs a regular expression pattern buffer using the specified
+   * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5).
+   *
+   * @param pattern A regular expression pattern, in the form of a String,
+   *   StringBuffer, or char[].
+   * @param cflags The logical OR of any combination of the compilation flags listed above.
+   * @exception REException The input pattern could not be parsed.
+   * @exception IllegalArgumentException The pattern was not a String, 
+   *   StringBuffer or char[].
+   * @exception NullPointerException The pattern was null.
+   */
+  public RE(Object pattern, int cflags) throws REException {
+    this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5,0,0);
+  }
+
+  /**
+   * Constructs a regular expression pattern buffer using the specified
+   * compilation flags and regular expression syntax.
+   *
+   * @param pattern A regular expression pattern, in the form of a String,
+   *   StringBuffer, or char[].
+   * @param cflags The logical OR of any combination of the compilation flags listed above.
+   * @param syntax The type of regular expression syntax to use.
+   * @exception REException The input pattern could not be parsed.
+   * @exception IllegalArgumentException The pattern was not a String, 
+   *   StringBuffer or char[].
+   * @exception NullPointerException The pattern was null.
+   */
+  public RE(Object pattern, int cflags, RESyntax syntax) throws REException {
+    this(pattern,cflags,syntax,0,0);
+  }
+
+  // internal constructor used for alternation
+  private RE(REToken f_first, REToken f_last,int f_subs, int f_subIndex) {
+    super(f_subIndex); // ???
+    firstToken = f_first;
+    lastToken = f_last;
+    m_numSubs = f_subs;
+  }
+
+  // Actual constructor implementation
+  private RE(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException {
+    super(myIndex); // Subexpression index of this token.
+    char[] pattern;
+    if (patternObj instanceof String) {
+      pattern = ((String) patternObj).toCharArray();
+    } else if (patternObj instanceof char[]) {
+      pattern = (char[]) patternObj;
+    } else if (patternObj instanceof StringBuffer) {
+      pattern = new char [((StringBuffer) patternObj).length()];
+      ((StringBuffer) patternObj).getChars(0,pattern.length,pattern,0);
+    } else throw new IllegalArgumentException("Invalid class for pattern");
+
+    int pLength = pattern.length;
+
+    m_numSubs = 0; // Number of subexpressions in this token.
+    Vector branches = null;
+
+    // linked list of tokens (sort of -- some closed loops can exist)
+    firstToken = lastToken = null;
+
+    // Precalculate these so we don't pay for the math every time we
+    // need to access them.
+    boolean insens = ((cflags & REG_ICASE) > 0);
+
+    // Parse pattern into tokens.  Does anyone know if it's more efficient
+    // to use char[] than a String.charAt()?  I'm assuming so.
+
+    // index tracks the position in the char array
+    int index = 0;
+
+    // this will be the current parse character (pattern[index])
+    CharUnit unit = new CharUnit();
+
+    // This is used for {x,y} calculations
+    IntPair minMax = new IntPair();
+
+    // Buffer a token so we can create a TokenRepeated, etc.
+    REToken currentToken = null;
+    char ch;
+
+    while (index < pLength) {
+      // read the next character unit (including backslash escapes)
+      index = getCharUnit(pattern,index,unit);
+
+      // ALTERNATION OPERATOR
+      //  \| or | (if RE_NO_BK_VBAR) or newline (if RE_NEWLINE_ALT)
+      //  not available if RE_LIMITED_OPS is set
+
+      // TODO: the '\n' literal here should be a test against REToken.newline,
+      // which unfortunately may be more than a single character.
+      if ( ( (unit.ch == '|' && (syntax.get(RESyntax.RE_NO_BK_VBAR) ^ unit.bk))
+	     || (syntax.get(RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') && !unit.bk) )
+	   && !syntax.get(RESyntax.RE_LIMITED_OPS)) {
+	// make everything up to here be a branch. create vector if nec.
+	if (branches == null) branches = new Vector();
+	addToken(currentToken);
+	branches.addElement(new RE(firstToken,lastToken,m_numSubs,m_subIndex));
+	firstToken = lastToken = currentToken = null;
+      }
+      
+      // INTERVAL OPERATOR:
+      //  {x} | {x,} | {x,y}  (RE_INTERVALS && RE_NO_BK_BRACES)
+      //  \{x\} | \{x,\} | \{x,y\} (RE_INTERVALS && !RE_NO_BK_BRACES)
+      //
+      // OPEN QUESTION: 
+      //  what is proper interpretation of '{' at start of string?
+
+      else if ((unit.ch == '{') && syntax.get(RESyntax.RE_INTERVALS) && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) {
+	if (currentToken == null) throw new REException("{ without preceding token",REException.REG_EBRACE,index);
+	  
+	index = getMinMax(pattern,index,minMax,syntax);
+	if ((currentToken.getMinimumLength() == 0) && (minMax.second == Integer.MAX_VALUE))
+	  throw new REException("repeated argument may be empty",REException.REG_BADRPT,index);
+	currentToken = setRepeated(currentToken,minMax.first,minMax.second,index);  
+      }
+      
+      // LIST OPERATOR:
+      //  [...] | [^...]
+
+      else if ((unit.ch == '[') && !unit.bk) {
+	Vector options = new Vector();
+	boolean negative = false;
+	char lastChar = 0;
+	if (index == pLength) throw new REException("unmatched [",REException.REG_EBRACK,index);
+	
+	// Check for initial caret, negation
+	if ((ch = pattern[index]) == '^') {
+	  negative = true;
+	  if (++index == pLength) throw new REException("no end of list",REException.REG_EBRACK,index);
+	  ch = pattern[index];
+	}
+
+	// Check for leading right bracket literal
+	if (ch == ']') {
+	  lastChar = ch;
+	  if (++index == pLength) throw new REException("no end of list",REException.REG_EBRACK,index);
+	}
+
+	while ((ch = pattern[index++]) != ']') {
+	  if ((ch == '-') && (lastChar != 0)) {
+	    if (index == pLength) throw new REException("no end of list",REException.REG_EBRACK,index);
+	    if ((ch = pattern[index]) == ']') {
+	      options.addElement(new RETokenChar(m_subIndex,lastChar,insens));
+	      lastChar = '-';
+	    } else {
+	      options.addElement(new RETokenRange(m_subIndex,lastChar,ch,insens));
+	      lastChar = 0;
+	      index++;
+	    }
+          } else if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) {
+            if (index == pLength) throw new REException("no end of list",REException.REG_EBRACK,index);
+	    int posixID = -1;
+	    boolean negate = false;
+	    if (syntax.get(RESyntax.RE_CHAR_CLASS_ESC_IN_LISTS)) {
+	      switch (pattern[index]) {
+	      case 'D':
+		negate = true;
+	      case 'd':
+		posixID = RETokenPOSIX.DIGIT;
+		break;
+	      case 'S':
+		negate = true;
+	      case 's':
+		posixID = RETokenPOSIX.SPACE;
+		break;
+	      case 'W':
+		negate = true;
+	      case 'w':
+		posixID = RETokenPOSIX.ALNUM;
+		break;
+	      }
+	    }
+	    if (lastChar != 0) options.addElement(new RETokenChar(m_subIndex,lastChar,insens));
+	    
+	    if (posixID != -1) {
+	      options.addElement(new RETokenPOSIX(m_subIndex,posixID,insens,negate));
+	    } else {
+	      lastChar = pattern[index];
+	    }
+	    ++index;
+	  } else if ((ch == '[') && (syntax.get(RESyntax.RE_CHAR_CLASSES)) && (pattern[index] == ':')) {
+	    StringBuffer posixSet = new StringBuffer();
+	    index = getPosixSet(pattern,index+1,posixSet);
+	    int posixId = RETokenPOSIX.intValue(posixSet.toString());
+	    if (posixId != -1)
+	      options.addElement(new RETokenPOSIX(m_subIndex,posixId,insens,false));
+	  } else {
+	    if (lastChar != 0) options.addElement(new RETokenChar(m_subIndex,lastChar,insens));
+	    lastChar = ch;
+	  }
+	  if (index == pLength) throw new REException("no end of list",REException.REG_EBRACK,index);
+	} // while in list
+	// Out of list, index is one past ']'
+	    
+	if (lastChar != 0) options.addElement(new RETokenChar(m_subIndex,lastChar,insens));
+	    
+	// Create a new RETokenOneOf
+	addToken(currentToken);
+	options.trimToSize();
+	currentToken = new RETokenOneOf(m_subIndex,options,negative);
+      }
+
+      // SUBEXPRESSIONS
+      //  (...) | \(...\) depending on RE_NO_BK_PARENS
+
+      else if ((unit.ch == '(') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) {
+	boolean pure = false;
+	boolean comment = false;
+	if ((index+1 < pLength) && (pattern[index] == '?')) {
+	  switch (pattern[index+1]) {
+	  case ':':
+	    if (syntax.get(RESyntax.RE_PURE_GROUPING)) {
+	      pure = true;
+	      index += 2;
+	    }
+	    break;
+	  case '#':
+	    if (syntax.get(RESyntax.RE_COMMENTS)) {
+	      comment = true;
+	    }
+	    break;
+	  }
+	}
+
+	// find end of subexpression
+	int endIndex = index;
+	int nextIndex = index;
+	int nested = 0;
+
+	while ( ((nextIndex = getCharUnit(pattern,endIndex,unit)) > 0)
+		&& !(nested == 0 && (unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk)) )
+	  if ((endIndex = nextIndex) >= pLength)
+	    throw new REException("no end of subexpression",REException.REG_ESUBREG,index-1);
+	  else if (unit.ch == '(' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))
+	    nested++;
+	  else if (unit.ch == ')' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))
+	    nested--;
+
+	// endIndex is now position at a ')','\)' 
+	// nextIndex is end of string or position after ')' or '\)'
+
+	if (comment) index = nextIndex;
+	else { // not a comment
+	  // create RE subexpression as token.
+	  addToken(currentToken);
+	  if (!pure) {
+	    nextSub++;
+	    m_numSubs++;
+	  }
+
+	  int useIndex = pure ? 0 : nextSub;
+
+	  currentToken = new RE(String.valueOf(pattern,index,endIndex-index).toCharArray(),cflags,syntax,useIndex,nextSub);
+	  nextSub += ((RE) currentToken).getNumSubs();
+	  m_numSubs += ((RE) currentToken).getNumSubs();
+	  index = nextIndex;
+	} // not a comment
+      } // subexpression
+    
+      // UNMATCHED RIGHT PAREN
+      // ) or \)?  need to implement throw exception if
+      // !syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD)
+      else if (!syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) && ((unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ unit.bk))) {
+	throw new REException("unmatched right paren",REException.REG_EPAREN,index);
+      }
+
+      // START OF LINE OPERATOR
+      //  ^
+
+      else if ((unit.ch == '^') && !unit.bk) {
+	addToken(currentToken);
+	currentToken = null;
+	addToken(new RETokenStart(m_subIndex,(cflags & REG_MULTILINE) > 0));
+      }
+
+      // END OF LINE OPERATOR
+      //  $
+
+      else if ((unit.ch == '$') && !unit.bk) {
+	addToken(currentToken);
+	currentToken = null;
+	addToken(new RETokenEnd(m_subIndex,(cflags & REG_MULTILINE) > 0));
+      }
+
+      // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null)
+      //  .
+
+      else if ((unit.ch == '.') && !unit.bk) {
+	addToken(currentToken);
+	currentToken = new RETokenAny(m_subIndex,syntax.get(RESyntax.RE_DOT_NEWLINE) || ((cflags & REG_DOT_NEWLINE) > 0),syntax.get(RESyntax.RE_DOT_NOT_NULL));
+      }
+
+      // ZERO-OR-MORE REPEAT OPERATOR
+      //  *
+
+      else if ((unit.ch == '*') && !unit.bk) {
+	if ((currentToken == null) || (currentToken.getMinimumLength() == 0))
+	  throw new REException("repeated argument may be empty",REException.REG_BADRPT,index);
+	currentToken = setRepeated(currentToken,0,Integer.MAX_VALUE,index);
+      }
+
+      // ONE-OR-MORE REPEAT OPERATOR
+      //  + | \+ depending on RE_BK_PLUS_QM
+      //  not available if RE_LIMITED_OPS is set
+
+      else if ((unit.ch == '+') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) {
+	if ((currentToken == null) || (currentToken.getMinimumLength() == 0))
+	  throw new REException("repeated argument may be empty",REException.REG_BADRPT,index);
+	currentToken = setRepeated(currentToken,1,Integer.MAX_VALUE,index);
+      }
+
+      // ZERO-OR-ONE REPEAT OPERATOR / STINGY MATCHING OPERATOR
+      //  ? | \? depending on RE_BK_PLUS_QM
+      //  not available if RE_LIMITED_OPS is set
+      //  stingy matching if RE_STINGY_OPS is set and it follows a quantifier
+
+      else if ((unit.ch == '?') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ unit.bk)) {
+	if (currentToken == null) throw new REException("? without preceding token",REException.REG_BADRPT,index);
+
+	// Check for stingy matching on RETokenRepeated
+	if ((currentToken instanceof RETokenRepeated) && (syntax.get(RESyntax.RE_STINGY_OPS)))
+	  ((RETokenRepeated) currentToken).makeStingy();
+	else
+	  currentToken = setRepeated(currentToken,0,1,index);
+      }
+	
+      // BACKREFERENCE OPERATOR
+      //  \1 \2 \3 \4 ...
+      // not available if RE_NO_BK_REFS is set
+
+      else if (unit.bk && Character.isDigit(unit.ch) && !syntax.get(RESyntax.RE_NO_BK_REFS)) {
+	addToken(currentToken);
+	currentToken = new RETokenBackRef(m_subIndex,Character.digit(unit.ch,10),insens);
+      }
+
+      // START OF STRING OPERATOR
+      //  \A if RE_STRING_ANCHORS is set
+      
+      else if (unit.bk && (unit.ch == 'A') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
+	addToken(currentToken);
+	currentToken = new RETokenStart(m_subIndex,false);
+      }
+      
+      // DIGIT OPERATOR
+      //  \d if RE_CHAR_CLASS_ESCAPES is set
+      
+      else if (unit.bk && (unit.ch == 'd') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+	addToken(currentToken);
+	currentToken = new RETokenPOSIX(m_subIndex,RETokenPOSIX.DIGIT,insens,false);
+      }
+
+      // NON-DIGIT OPERATOR
+      //  \D
+
+	else if (unit.bk && (unit.ch == 'D') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+	  addToken(currentToken);
+	  currentToken = new RETokenPOSIX(m_subIndex,RETokenPOSIX.DIGIT,insens,true);
+	}
+
+	// NEWLINE ESCAPE
+        //  \n
+
+	else if (unit.bk && (unit.ch == 'n')) {
+	  addToken(currentToken);
+	  currentToken = new RETokenChar(m_subIndex,'\n',false);
+	}
+
+	// RETURN ESCAPE
+        //  \r
+
+	else if (unit.bk && (unit.ch == 'r')) {
+	  addToken(currentToken);
+	  currentToken = new RETokenChar(m_subIndex,'\r',false);
+	}
+
+	// WHITESPACE OPERATOR
+        //  \s if RE_CHAR_CLASS_ESCAPES is set
+
+	else if (unit.bk && (unit.ch == 's') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+	  addToken(currentToken);
+	  currentToken = new RETokenPOSIX(m_subIndex,RETokenPOSIX.SPACE,insens,false);
+	}
+
+	// NON-WHITESPACE OPERATOR
+        //  \S
+
+	else if (unit.bk && (unit.ch == 'S') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+	  addToken(currentToken);
+	  currentToken = new RETokenPOSIX(m_subIndex,RETokenPOSIX.SPACE,insens,true);
+	}
+
+	// TAB ESCAPE
+        //  \t
+
+	else if (unit.bk && (unit.ch == 't')) {
+	  addToken(currentToken);
+	  currentToken = new RETokenChar(m_subIndex,'\t',false);
+	}
+
+	// ALPHANUMERIC OPERATOR
+        //  \w
+
+	else if (unit.bk && (unit.ch == 'w') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+	  addToken(currentToken);
+	  currentToken = new RETokenPOSIX(m_subIndex,RETokenPOSIX.ALNUM,insens,false);
+	}
+
+	// NON-ALPHANUMERIC OPERATOR
+        //  \W
+
+	else if (unit.bk && (unit.ch == 'W') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) {
+	  addToken(currentToken);
+	  currentToken = new RETokenPOSIX(m_subIndex,RETokenPOSIX.ALNUM,insens,true);
+	}
+
+	// END OF STRING OPERATOR
+        //  \Z
+
+	else if (unit.bk && (unit.ch == 'Z') && syntax.get(RESyntax.RE_STRING_ANCHORS)) {
+	  addToken(currentToken);
+	  currentToken = new RETokenEnd(m_subIndex,false);
+	}
+
+	// NON-SPECIAL CHARACTER (or escape to make literal)
+        //  c | \* for example
+
+	else {  // not a special character
+	  addToken(currentToken);
+	  currentToken = new RETokenChar(m_subIndex,unit.ch,insens);
+	} 
+      } // end while
+
+    // Add final buffered token if applicable
+    addToken(currentToken);
+      
+    if (branches != null) {
+      branches.addElement(new RE(firstToken,lastToken,m_numSubs,m_subIndex));
+      branches.trimToSize(); // compact the Vector
+      firstToken = lastToken = new RETokenOneOf(m_subIndex,branches,false);
+    }
+  }
+
+  private static int getCharUnit(char[] input, int index, CharUnit unit) throws REException {
+    unit.ch = input[index++];
+    if (unit.bk = (unit.ch == '\\'))
+      if (index < input.length)
+	unit.ch = input[index++];
+      else throw new REException("\\ at end of pattern.",REException.REG_ESCAPE,index);
+    return index;
+  }
+
+  /**
+   * Checks if the input in its entirety is an exact match of
+   * this regular expression.
+   *
+   * @param input The input text.
+   * @exception IllegalArgumentException The input text was not a String, char[], or InputStream.
+   */
+  public boolean isMatch(Object input) {
+    return isMatch(input,0,0);
+  }
+  
+  /**
+   * Checks if the input string, starting from index, is an exact match of
+   * this regular expression.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public boolean isMatch(Object input,int index) {
+    return isMatch(input,index,0);
+  }
+  
+
+  /**
+   * Checks if the input, starting from index and using the specified
+   * execution flags, is an exact match of this regular expression.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @param eflags The logical OR of any execution flags above.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public boolean isMatch(Object input,int index,int eflags) {
+    return isMatchImpl(makeCharIndexed(input,index),index,eflags);
+  }
+
+  private boolean isMatchImpl(CharIndexed input, int index, int eflags) {
+    if (firstToken == null)  // Trivial case
+      return (input.charAt(0) == CharIndexed.OUT_OF_BOUNDS);
+    int[] i = firstToken.match(input,0,eflags,new REMatch(m_numSubs,index));
+    return (i != null) && (input.charAt(i[0]) == CharIndexed.OUT_OF_BOUNDS);
+  }
+    
+  /**
+   * Returns the maximum number of subexpressions in this regular expression.
+   * If the expression contains branches, the value returned will be the
+   * maximum subexpressions in any of the branches.
+   */
+  public int getNumSubs() {
+    return m_numSubs;
+  }
+
+  // Overrides REToken.setUncle
+  void setUncle(REToken f_uncle) {
+    lastToken.setUncle(f_uncle);
+  }
+
+  // Overrides REToken.chain
+  boolean chain(REToken f_next) {
+    super.chain(f_next);
+    if (lastToken != null) lastToken.setUncle(f_next);
+    return true;
+  }
+    
+  /**
+   * Returns the minimum number of characters that could possibly
+   * constitute a match of this regular expression.
+   */
+  public int getMinimumLength() {
+    int min = 0;
+    REToken t = firstToken;
+    if (t == null) return 0;
+    do {
+      min += t.getMinimumLength();
+    } while ((t = t.m_next) != null);
+    return min;
+  }
+
+  /**
+   * Returns an array of all matches found in the input.
+   *
+   * @param input The input text.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatch[] getAllMatches(Object input) {
+    return getAllMatches(input,0,0);
+  }
+
+  /**
+   * Returns an array of all matches found in the input,
+   * beginning at the specified index position.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatch[] getAllMatches(Object input, int index) {
+    return getAllMatches(input,index,0);
+  }
+
+  /**
+   * Returns an array of all matches found in the input string,
+   * beginning at the specified index position and using the specified
+   * execution flags.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @param eflags The logical OR of any execution flags above.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatch[] getAllMatches(Object input, int index, int eflags) {
+    return getAllMatchesImpl(makeCharIndexed(input,index),index,eflags);
+  }
+
+  // this has been changed since 1.03 to be non-overlapping matches
+  private REMatch[] getAllMatchesImpl(CharIndexed input, int index, int eflags) {
+    Vector all = new Vector();
+    REMatch m = null;
+    while ((m = getMatchImpl(input,index,eflags,null)) != null) {
+      all.addElement(m);
+      index = m.getEndIndex();
+      if (m.end[0] == 0) {   // handle pathological case of zero-length match
+	index++;
+	input.move(1);
+      } else {
+	input.move(m.end[0]);
+      }
+    }
+    REMatch[] mset = new REMatch[all.size()];
+    all.copyInto(mset);
+    return mset;
+  }
+  
+  /* Implements abstract method REToken.match() */
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) { 
+    if (firstToken == null) return new int[] { index }; // Trivial case
+    /*
+    if ((mymatch.start[m_subIndex] == -1) 
+       	|| (mymatch.start[m_subIndex] > index))
+    */
+    int oldstart = mymatch.start[m_subIndex];
+    mymatch.start[m_subIndex] = index;
+    int[] newIndex = firstToken.match(input,index,eflags,mymatch);
+    if (newIndex == null) { 
+	mymatch.start[m_subIndex] = oldstart;
+    } else {
+      // If this match succeeded, then whole rest of string is good,
+      // and newIndex[0] is the end of the match AT THIS LEVEL
+
+      // We need to make list of all possible nexts.
+      int[] doables = new int[0];
+      int[] thisResult;
+      for (int i = 0; i < newIndex.length; i++) {
+	thisResult = next(input,newIndex[i],eflags,mymatch);
+	if (thisResult != null) {
+	  int[] temp = new int[doables.length + thisResult.length];
+	  System.arraycopy(doables,0,temp,0,doables.length);
+	  for (int j = 0; j < thisResult.length; j++) {
+	    temp[doables.length + j] = thisResult[j];
+	  }
+	  doables = temp;
+	}
+      }
+      return (doables.length == 0) ? null : doables;
+    }
+    return null;
+  }
+  
+  /**
+   * Returns the first match found in the input.
+   *
+   * @param input The input text.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatch getMatch(Object input) {
+    return getMatch(input,0,0);
+  }
+  
+  /**
+   * Returns the first match found in the input, beginning
+   * the search at the specified index.
+   *
+   * @param input The input text.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatch getMatch(Object input, int index) {
+    return getMatch(input,index,0);
+  }
+  
+  /**
+   * Returns the first match found in the input, beginning
+   * the search at the specified index, and using the specified
+   * execution flags.  If no match is found, returns null.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @param eflags The logical OR of any execution flags above.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatch getMatch(Object input, int index, int eflags) {
+    return getMatch(input,index,eflags,null);
+  }
+
+  /**
+   * Returns the first match found in the input, beginning
+   * the search at the specified index, and using the specified
+   * execution flags.  If no match is found, returns null.  If a StringBuffer
+   * is provided and is non-null, the contents of the input text from the index to the
+   * beginning of the match (or to the end of the input, if there is no match)
+   * are appended to the StringBuffer.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @param eflags The logical OR of any execution flags above.
+   * @param buffer The StringBuffer to save pre-match text in.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatch getMatch(Object input, int index, int eflags, StringBuffer buffer) {
+    return getMatchImpl(makeCharIndexed(input,index),index,eflags,buffer);
+  }
+
+  REMatch getMatchImpl(CharIndexed input, int index, int eflags, StringBuffer buffer) {
+    // check if input is at a valid position
+    if (!input.isValid()) return null;
+    REMatch mymatch = new REMatch(m_numSubs,index);
+    do {
+      int[] result = match(input,0,eflags,mymatch);
+      if (result != null) {
+	mymatch.end[0] = result[0]; // may break leftmost longest
+	mymatch.finish(input);
+	return mymatch;
+      }
+      mymatch.clear(++index);
+      if (buffer != null) buffer.append(input.charAt(0));
+    } while (input.move(1));
+
+    return null;
+  }
+
+  /**
+   * Returns an REMatchEnumeration that can be used to iterate over the
+   * matches found in the input text.
+   *
+   * @param input The input text.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatchEnumeration getMatchEnumeration(Object input) {
+    return getMatchEnumeration(input,0,0);
+  }
+
+
+  /**
+   * Returns an REMatchEnumeration that can be used to iterate over the
+   * matches found in the input text.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatchEnumeration getMatchEnumeration(Object input, int index) {
+    return getMatchEnumeration(input,index,0);
+  }
+
+  /**
+   * Returns an REMatchEnumeration that can be used to iterate over the
+   * matches found in the input text.
+   *
+   * @param input The input text.
+   * @param index The offset index at which the search should be begin.
+   * @param eflags The logical OR of any execution flags above.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public REMatchEnumeration getMatchEnumeration(Object input, int index, int eflags) {
+    return new REMatchEnumeration(this,makeCharIndexed(input,index),index,eflags);
+  }
+
+
+  /**
+   * Substitutes the replacement text for the first match found in the input.
+   *
+   * @param input The input text.
+   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public String substitute(Object input,String replace) {
+    return substitute(input,replace,0,0);
+  }
+
+  /**
+   * Substitutes the replacement text for the first match found in the input
+   * beginning at the specified index position.
+   *
+   * @param input The input text.
+   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+   * @param index The offset index at which the search should be begin.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public String substitute(Object input,String replace,int index) {
+    return substitute(input,replace,index,0);
+  }
+
+  /**
+   * Substitutes the replacement text for the first match found in the input
+   * string, beginning at the specified index position and using the
+   * specified execution flags.
+   *
+   * @param input The input text.
+   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+   * @param index The offset index at which the search should be begin.
+   * @param eflags The logical OR of any execution flags above.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public String substitute(Object input,String replace,int index,int eflags) {
+    return substituteImpl(makeCharIndexed(input,index),replace,index,eflags);
+  }
+
+  private String substituteImpl(CharIndexed input,String replace,int index,int eflags) {
+    StringBuffer buffer = new StringBuffer();
+    REMatch m = getMatchImpl(input,index,eflags,buffer);
+    if (m==null) return buffer.toString();
+    buffer.append(m.substituteInto(replace));
+    if (input.move(m.end[0])) {
+      do {
+	buffer.append(input.charAt(0));
+      } while (input.move(1));
+    }
+    return buffer.toString();
+  }
+  
+  /**
+   * Substitutes the replacement text for each non-overlapping match found 
+   * in the input text.
+   *
+   * @param input The input text.
+   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public String substituteAll(Object input,String replace) {
+    return substituteAll(input,replace,0,0);
+  }
+
+  /**
+   * Substitutes the replacement text for each non-overlapping match found 
+   * in the input text, starting at the specified index.
+   *
+   * @param input The input text.
+   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+   * @param index The offset index at which the search should be begin.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public String substituteAll(Object input,String replace,int index) {
+    return substituteAll(input,replace,index,0);
+  }
+ 
+  /**
+   * Substitutes the replacement text for each non-overlapping match found 
+   * in the input text, starting at the specified index and using the
+   * specified execution flags.
+   *
+   * @param input The input text.
+   * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto).
+   * @param index The offset index at which the search should be begin.
+   * @param eflags The logical OR of any execution flags above.
+   * @exception IllegalArgumentException The input text was not a String, char[], StringBuffer or InputStream.
+   */
+  public String substituteAll(Object input,String replace,int index,int eflags) {
+    return substituteAllImpl(makeCharIndexed(input,index),replace,index,eflags);
+  }
+
+  private String substituteAllImpl(CharIndexed input,String replace,int index,int eflags) {
+    StringBuffer buffer = new StringBuffer();
+    REMatch m;
+    while ((m = getMatchImpl(input,index,eflags,buffer)) != null) {
+      buffer.append(m.substituteInto(replace));
+      index = m.getEndIndex();
+      if (m.end[0] == 0) {
+	char ch = input.charAt(0);
+	if (ch != CharIndexed.OUT_OF_BOUNDS) 
+	  buffer.append(ch);
+	input.move(1);
+      } else {
+	input.move(m.end[0]);
+      }
+    }
+    return buffer.toString();
+  }
+  
+  /* Helper function for constructor */
+  private void addToken(REToken next) {
+    if (next == null) return;
+    if (firstToken == null)
+      lastToken = firstToken = next;
+    else
+      // if chain returns false, it "rejected" the token due to
+      // an optimization, and next was combined with lastToken
+      if (lastToken.chain(next)) lastToken = next;
+  }
+
+  private static REToken setRepeated(REToken current, int min, int max, int index) throws REException {
+    if (current == null) throw new REException("repeat preceding token",REException.REG_BADRPT,index);
+    return new RETokenRepeated(current.m_subIndex,current,min,max);
+  }
+
+  private static int getPosixSet(char[] pattern,int index,StringBuffer buf) {
+    // Precondition: pattern[index-1] == ':'
+    // we will return pos of closing ']'.
+    int i;
+    for (i=index; i<(pattern.length-1); i++) {
+      if ((pattern[i] == ':') && (pattern[i+1] == ']'))
+	return i+2;
+      buf.append(pattern[i]);
+    }
+    return index; // didn't match up
+  }
+
+  private int getMinMax(char[] input,int index,IntPair minMax,RESyntax syntax) throws REException {
+    // Precondition: input[index-1] == '{', minMax != null
+
+    if (index == input.length) throw new REException("no matching brace",REException.REG_EBRACE,index);
+	
+    int min,max=0;
+    CharUnit unit = new CharUnit();
+    StringBuffer buf = new StringBuffer();
+    
+    // Read string of digits
+    while (((index = getCharUnit(input,index,unit)) != input.length)
+	   && Character.isDigit(unit.ch))
+      buf.append(unit.ch);
+
+    // Check for {} tomfoolery
+    if (buf.length() == 0) throw new REException("bad brace construct",REException.REG_EBRACE,index);
+
+    min = Integer.parseInt(buf.toString());
+	
+    if ((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk))
+      max = min;
+    else if ((unit.ch == ',') && !unit.bk) {
+      buf = new StringBuffer();
+      // Read string of digits
+      while (((index = getCharUnit(input,index,unit)) != input.length)
+	     && Character.isDigit(unit.ch))
+	buf.append(unit.ch);
+
+      if (!((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)))
+	throw new REException("expected end of interval",REException.REG_EBRACE,index);
+
+      // This is the case of {x,}
+      if (buf.length() == 0) max = Integer.MAX_VALUE;
+      else max = Integer.parseInt(buf.toString());
+    } else throw new REException("invalid character in brace expression",REException.REG_EBRACE,index);
+
+    // We know min and max now, and they are valid.
+
+    minMax.first = min;
+    minMax.second = max;
+
+    // return the index following the '}'
+    return index;
+  }
+
+   /**
+    * Return a human readable form of the compiled regular expression,
+    * useful for debugging.
+    */
+   public String toString() {
+     StringBuffer sb = new StringBuffer();
+     dump(sb);
+     return sb.toString();
+   }
+
+  void dump(StringBuffer os) {
+    os.append('(');
+    if (m_subIndex == 0)
+      os.append("?:");
+    if (firstToken != null)
+      firstToken.dumpAll(os);
+    os.append(')');
+  }
+
+  // Cast input appropriately or throw exception
+  private static CharIndexed makeCharIndexed(Object input, int index) {
+    if (input instanceof String)
+      return new CharIndexedString((String) input,index);
+    else if (input instanceof char[])
+      return new CharIndexedCharArray((char[]) input,index);
+    else if (input instanceof StringBuffer)
+      return new CharIndexedStringBuffer((StringBuffer) input,index);
+    else if (input instanceof InputStream)
+      return new CharIndexedInputStream((InputStream) input,index);
+    else throw new IllegalArgumentException("Invalid class for input text");
+  }
+}
diff --git a/gnu/regexp/REException.java b/gnu/regexp/REException.java
new file mode 100644
index 0000000..d2682fa
--- /dev/null
+++ b/gnu/regexp/REException.java
@@ -0,0 +1,160 @@
+/*
+ *  gnu/regexp/REException.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+/**
+ * This is the regular expression exception class.  An exception of this type
+ * defines the three attributes:
+ * <OL>
+ * <LI> A descriptive message of the error.
+ * <LI> An integral type code equivalent to one of the statically
+ *      defined symbols listed below.
+ * <LI> The approximate position in the input string where the error
+ *      occurred.
+ * </OL>
+ *
+ * @author <A HREF="mailto:wes at cacas.org">Wes Biggs</A>
+ */
+
+public class REException extends Exception {
+  private int m_type;
+  private int m_pos;
+
+  // Error conditions from GNU regcomp(3) manual
+
+  /**
+   * Error flag.
+   * Invalid use of repetition operators such  as  using
+   * `*' as the first character.
+   */
+  public static final int REG_BADRPT  =  1;
+
+  /**
+   * Error flag.
+   * Invalid use of back reference operator.
+   */
+  public static final int REG_BADBR   =  2;
+
+  /**
+   * Error flag.
+   * Un-matched brace interval operators.
+   */
+  public static final int REG_EBRACE  =  3;
+
+  /**
+   * Error flag.
+   * Un-matched bracket list operators.
+   */
+  public static final int REG_EBRACK  =  4;
+
+  /**
+   * Error flag.
+   * Invalid  use  of the range operator, eg. the ending
+   * point of the range occurs  prior  to  the  starting
+   * point.
+   */
+  public static final int REG_ERANGE  =  5;
+
+  /**
+   * Error flag.
+   * Unknown character class name. <B>Not implemented</B>.
+   */
+  public static final int REG_ECTYPE  =  6;
+
+  /**
+   * Error flag.
+   * Un-matched parenthesis group operators.
+   */
+  public static final int REG_EPAREN  =  7;
+
+  /**
+   * Error flag.
+   * Invalid back reference to a subexpression.
+   */
+  public static final int REG_ESUBREG =  8;
+
+  /**
+   * Error flag.
+   * Non specific error. <B>Not implemented</B>.
+   */
+  public static final int REG_EEND    =  9;
+
+  /**
+   * Error flag.
+   * Invalid escape sequence. <B>Not implemented</B>.
+   */
+  public static final int REG_ESCAPE  = 10;
+
+  /**
+   * Error flag.
+   * Invalid  use  of pattern operators such as group or list.
+   */
+  public static final int REG_BADPAT  = 11;
+
+  /**
+   * Error flag.
+   * Compiled  regular  expression  requires  a  pattern
+   * buffer larger than 64Kb. <B>Not implemented</B>.
+   */
+  public static final int REG_ESIZE   = 12;
+
+  /**
+   * Error flag.
+   * The regex routines ran out of memory. <B>Not implemented</B>.
+   */
+  public static final int REG_ESPACE  = 13;
+
+  REException(String msg, int type, int position) { 
+    super(msg); 
+    m_type = type;
+    m_pos = position;
+  }
+
+  /**
+   * Returns the type of the exception, one of the constants listed above.
+   */
+
+  public int getType() {
+    return m_type;
+  }
+
+  /**
+   * Returns the position, relative to the string or character array being
+   * compiled, where the error occurred.  This position is generally the point
+   * where the error was detected, not necessarily the starting index of
+   * a bad subexpression.
+   */
+  public int getPosition() {
+    return m_pos;
+  }
+
+  /**
+   * Reports the descriptive message associated with this exception
+   * as well as its index position in the string or character array
+   * being compiled.
+   */
+  public String getMessage() {
+    StringBuffer sb = new StringBuffer();
+    sb.append("At position "+m_pos+" in regular expression pattern: ");
+    sb.append('\n');
+    sb.append(super.getMessage());
+    return sb.toString();
+  }
+}
diff --git a/gnu/regexp/REFilterInputStream.java b/gnu/regexp/REFilterInputStream.java
new file mode 100644
index 0000000..2c85c71
--- /dev/null
+++ b/gnu/regexp/REFilterInputStream.java
@@ -0,0 +1,117 @@
+/*
+ *  gnu/regexp/REFilterInputStream.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.io.FilterInputStream;
+import java.io.InputStream;
+
+/**
+ * Replaces instances of a given RE with replacement text. 
+ *
+ * @author <A HREF="mailto:wes at cacas.org">Wes Biggs</A>
+ * @since gnu.regexp 1.0.5
+ */
+
+public class REFilterInputStream extends FilterInputStream {
+
+  private RE m_expr;
+  private String m_replace;
+  private String m_buffer;
+  private int m_bufpos;
+  private int m_offset;
+  private CharIndexedInputStream m_stream;
+
+  /**
+   * Creates an REFilterInputStream.  When reading from this stream,
+   * occurrences of patterns matching the supplied regular expression
+   * will be replaced with the supplied replacement text (the
+   * metacharacters $0 through $9 may be used to refer to the full
+   * match or subexpression matches.
+   *
+   * @param f_stream The InputStream to be filtered.
+   * @param f_expr The regular expression to search for.
+   * @param f_replace The text pattern to replace matches with.  
+   */
+  public REFilterInputStream(InputStream f_stream, RE f_expr, String f_replace) {
+    super(f_stream);
+    m_stream = new CharIndexedInputStream(f_stream,0);
+    m_expr = f_expr;
+    m_replace = f_replace;
+  }
+
+  /**
+   * Reads the next byte from the stream per the general contract of
+   * InputStream.read().  Returns -1 on error or end of stream.
+   */
+  public int read() {
+    // If we have buffered replace data, use it.
+    if ((m_buffer != null) && (m_bufpos < m_buffer.length())) {
+      return (int) m_buffer.charAt(m_bufpos++);
+    }
+
+    // check if input is at a valid position
+    if (!m_stream.isValid()) return -1;
+
+    REMatch mymatch = new REMatch(m_expr.getNumSubs(),m_offset);
+    int[] result = m_expr.match(m_stream,0,0,mymatch);
+    if (result != null) {
+      mymatch.end[0] = result[0];
+      mymatch.finish(m_stream);
+      m_stream.move(mymatch.toString().length());
+      m_offset += mymatch.toString().length();
+      m_buffer = mymatch.substituteInto(m_replace);
+      m_bufpos = 1;
+
+      // This is prone to infinite loops if replace string turns out empty.
+      return m_buffer.charAt(0);
+    } else {
+      char ch = m_stream.charAt(0);
+      if (ch == CharIndexed.OUT_OF_BOUNDS) return -1;
+      m_stream.move(1);
+      m_offset++;
+      return ch;
+    }
+  }
+
+  /** 
+   * Returns false.  REFilterInputStream does not support mark() and
+   * reset() methods. 
+   */
+  public boolean markSupported() {
+    return false;
+  }
+
+  /** Reads from the stream into the provided array. */
+  public int read(byte[] b, int off, int len) {
+    int i;
+    int ok = 0;
+    while (len-- > 0) {
+      i = read();
+      if (i == -1) return (ok == 0) ? -1 : ok;
+      b[off++] = (byte) i;
+      ok++;
+    }
+    return ok;
+  }
+
+  /** Reads from the stream into the provided array. */
+  public int read(byte[] b) {
+    return read(b,0,b.length);
+  }
+}
diff --git a/gnu/regexp/REMatch.java b/gnu/regexp/REMatch.java
new file mode 100644
index 0000000..45b64ba
--- /dev/null
+++ b/gnu/regexp/REMatch.java
@@ -0,0 +1,170 @@
+/*
+ *  gnu/regexp/REMatch.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+/**
+ * An instance of this class represents a match
+ * completed by a gnu.regexp matching function. It can be used
+ * to obtain relevant information about the location of a match
+ * or submatch.
+ *
+ * @author <A HREF="mailto:wes at cacas.org">Wes Biggs</A>
+ */
+public class REMatch {
+  private String m_match;
+  int offset, anchor;
+  int[] start; // package scope for
+  int[] end;   //  quick access internally
+  int[] count; // runtime count of times through each subexpression
+
+  REMatch(int f_subs, int f_index) {
+    start = new int[f_subs+1];
+    end = new int[f_subs+1];
+    count = new int[f_subs+1];
+    anchor = f_index;
+    clear(f_index);
+  }
+
+  void finish(CharIndexed text) {
+    start[0] = 0;
+    StringBuffer sb = new StringBuffer();
+    int i;
+    for (i = 0; i < end[0]; i++)
+      sb.append(text.charAt(i));
+    m_match = sb.toString();
+    for (i = 0; i < start.length; i++) {
+      if (start[i] == -1) end[i] = -1;
+    }
+  }
+
+    void reset(int f_subIndex) {
+	for (int i = f_subIndex; i < start.length; i++) {
+	    start[i] = end[i] = -1;
+	    count[i] = 0;
+	}
+    }
+
+  void clear(int f_index) {
+    offset = f_index;
+    for (int i = 0; i < start.length; i++) {
+      start[i] = end[i] = -1;
+      count[i] = 0;
+    }
+  }
+      
+  /**
+   * Returns the string matching the pattern.  This makes it convenient
+   * to write code like the following:
+   * <P>
+   * <code> REMatch myMatch = myExpression.getMatch(myString);<br>
+   * if (myMatch != null) System.out.println("Regexp found: "+myMatch);</code>
+   */
+  public String toString() {
+    return m_match;
+  }
+  
+  /**
+   * Returns the index within the input text where the match in its entirety
+   * began.
+   */
+  public int getStartIndex() {
+    return offset + start[0];
+  }
+  
+  /**
+   * Returns the index within the input string where the match in its entirety 
+   * ends.  The return value is the next position after the end of the string;
+   * therefore, a match created by the following call:
+   * <P>
+   * <code>REMatch myMatch = myExpression.getMatch(myString);</code>
+   * <P>
+   * can be viewed (given that myMatch is not null) by creating
+   * <P>
+   * <code>String theMatch = myString.substring(myMatch.getStartIndex(),
+   * myMatch.getEndIndex());</code>
+   * <P>
+   * But you can save yourself that work, since the <code>toString()</code>
+   * method (above) does exactly that for you.
+   */
+  public int getEndIndex() {
+    return offset + end[0];
+  }
+  
+  /**
+   * Returns the string matching the given subexpression.
+   *
+   * @param sub Index of the subexpression.
+   */
+  public String toString(int sub) {
+    if ((sub >= start.length) || (start[sub] == -1)) return "";
+    return (m_match.substring(start[sub],end[sub]));
+  }
+
+  /** 
+   * Returns the index within the input string used to generate this match
+   * where subexpression number <i>sub</i> begins, or <code>-1</code> if
+   * the subexpression does not exist.
+   *
+   * @param sub Subexpression index
+   */
+  public int getSubStartIndex(int sub) {
+    if (sub >= start.length) return -1;
+    int x = start[sub];
+    return (x == -1) ? x : offset + x;
+  }
+  
+  /** 
+   * Returns the index within the input string used to generate this match
+   * where subexpression number <i>sub</i> ends, or <code>-1</code> if
+   * the subexpression does not exist.
+   *
+   * @param sub Subexpression index
+   */
+  public int getSubEndIndex(int sub) {
+    if (sub >= start.length) return -1;
+    int x = end[sub];
+    return (x == -1) ? x : offset + x;
+  }
+  
+  /**
+   * Substitute the results of this match to create a new string.
+   * This is patterned after PERL, so the tokens to watch out for are
+   * <code>$0</code> through <code>$9</code>.  <code>$0</code> matches
+   * the full substring matched; <code>$<i>n</i></code> matches
+   * subexpression number <i>n</i>.
+   *
+   * @param input A string consisting of literals and <code>$<i>n</i></code> tokens.
+   */
+  public String substituteInto(String input) {
+    // a la Perl, $0 is whole thing, $1 - $9 are subexpressions
+    StringBuffer output = new StringBuffer();
+    int pos;
+    for (pos = 0; pos < input.length()-1; pos++) {
+      if ((input.charAt(pos) == '$') && (Character.isDigit(input.charAt(pos+1)))) {
+	int val = Character.digit(input.charAt(++pos),10);
+	if (val < start.length) {
+	  output.append(toString(val));
+	} 
+      } else output.append(input.charAt(pos));
+    }
+    if (pos < input.length()) output.append(input.charAt(pos));
+    return output.toString();
+  }
+}
diff --git a/gnu/regexp/REMatchEnumeration.java b/gnu/regexp/REMatchEnumeration.java
new file mode 100644
index 0000000..ecade4b
--- /dev/null
+++ b/gnu/regexp/REMatchEnumeration.java
@@ -0,0 +1,107 @@
+/*
+ *  gnu/regexp/REMatchEnumeration.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package gnu.regexp;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+ * An REMatchEnumeration enumerates regular expression matches over a given
+ * input text.  You obtain a reference to an enumeration using the
+ * <code>getMatchEnumeration()</code> methods on an instance of RE.
+ * <P>
+ * REMatchEnumeration does lazy computation; that is, it will not search for
+ * a match until it needs to.  If you'd rather just get all the matches at
+ * once in a big array, use the <code>getAllMatches()</code> methods on RE.
+ * However, using an enumeration can help speed performance when the entire
+ * text does not need to be searched immediately.
+ * <P>
+ * The enumerated type is especially useful when searching on an InputStream,
+ * because the InputStream read position cannot be guaranteed after calling
+ * <code>getMatch()</code> (see the description of that method for an
+ * explanation of why).  Enumeration also saves a lot of overhead required
+ * when calling <code>getMatch()</code> multiple times.
+ * 
+ * @author <A HREF="mailto:wes at cacas.org">Wes Biggs</A>
+ */
+public class REMatchEnumeration implements Enumeration {
+  private static final int YES = 1;
+  private static final int MAYBE = 0;
+  private static final int NO = -1;
+  
+  private int m_more;
+  private REMatch m_match;
+  private RE m_expr;
+  private CharIndexed m_input;
+  private int m_index;
+  private int m_eflags;
+  private StringBuffer m_buffer;
+
+  // Package scope constructor is used by RE.getMatchEnumeration()
+  REMatchEnumeration(RE expr, CharIndexed input, int index, int eflags) {
+    m_more = MAYBE;
+    m_expr = expr;
+    m_input = input;
+    m_index = index;
+    m_eflags = eflags;
+  }
+
+  /** Returns true if there are more matches in the input text. */
+  public boolean hasMoreElements() {
+    return hasMoreMatches(null);
+  }
+
+  /** Returns true if there are more matches in the input text. */
+  public boolean hasMoreMatches() {
+    return hasMoreMatches(null);
+  }
+
+  /** Returns true if there are more matches in the input text.
+   * Saves the text leading up to the match (or to the end of the input)
+   * in the specified buffer.
+   */
+  public boolean hasMoreMatches(StringBuffer f_buffer) {
+    if (m_more == MAYBE) {
+      m_match = m_expr.getMatchImpl(m_input,m_index,m_eflags,f_buffer);
+      if (m_match != null) {
+	m_index = m_match.getEndIndex();
+	m_input.move((m_match.end[0] > 0) ? m_match.end[0] : 1);
+	m_more = YES;
+      } else m_more = NO;
+    }
+    return (m_more == YES);
+  }
+
+  /** Returns the next match in the input text. */
+  public Object nextElement() throws NoSuchElementException {
+    return nextMatch();
+  }
+
+  /** 
+   * Returns the next match in the input text. This method is provided
+   * for convenience to avoid having to explicitly cast the return value
+   * to class REMatch.
+   */
+  public REMatch nextMatch() throws NoSuchElementException {
+    if (hasMoreElements()) {
+      m_more = MAYBE;
+      return m_match;
+    }
+    throw new NoSuchElementException();
+  }
+}
diff --git a/gnu/regexp/RESyntax.java b/gnu/regexp/RESyntax.java
new file mode 100644
index 0000000..d577901
--- /dev/null
+++ b/gnu/regexp/RESyntax.java
@@ -0,0 +1,406 @@
+/*
+ *  gnu/regexp/RESyntax.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.util.BitSet;
+
+/**
+ * An RESyntax specifies the way a regular expression will be compiled.
+ * This class provides a number of predefined useful constants for
+ * emulating popular regular expression syntaxes.  Additionally the
+ * user may construct his or her own syntax, using any combination of the
+ * syntax bit constants.  The syntax is an optional argument to any of the
+ * matching methods on class RE.
+ *
+ * @author <A HREF="mailto:wes at cacas.org">Wes Biggs</A>
+ */
+
+public class RESyntax {
+  private BitSet bits;
+
+  // Values for constants are bit indexes
+
+  /**
+   * Syntax bit. Backslash is an escape character in lists.
+   */
+  public static final int RE_BACKSLASH_ESCAPE_IN_LISTS =  0;
+
+  /**
+   * Syntax bit. Use \? instead of ? and \+ instead of +.
+   */
+  public static final int RE_BK_PLUS_QM                =  1;
+
+  /**
+   * Syntax bit. POSIX character classes ([:...:]) in lists are allowed.
+   */
+  public static final int RE_CHAR_CLASSES              =  2;
+
+  /**
+   * Syntax bit. ^ and $ are special everywhere.
+   * <B>Not implemented.</B>
+   */
+  public static final int RE_CONTEXT_INDEP_ANCHORS     =  3; 
+
+  /**
+   * Syntax bit. Repetition operators are only special in valid positions.
+   * <B>Not implemented.</B>
+   */
+  public static final int RE_CONTEXT_INDEP_OPS         =  4; 
+
+  /**
+   * Syntax bit. Repetition and alternation operators are invalid
+   * at start and end of pattern and other places. 
+   * <B>Not implemented</B>.
+   */
+  public static final int RE_CONTEXT_INVALID_OPS       =  5; 
+
+  /**
+   * Syntax bit. Match-any-character operator (.) matches a newline.
+   */
+  public static final int RE_DOT_NEWLINE               =  6;
+
+  /**
+   * Syntax bit. Match-any-character operator (.) does not match a null.
+   */
+  public static final int RE_DOT_NOT_NULL              =  7;
+
+  /**
+   * Syntax bit. Intervals ({x}, {x,}, {x,y}) are allowed.
+   */
+  public static final int RE_INTERVALS                 =  8;
+
+  /**
+   * Syntax bit. No alternation (|), match one-or-more (+), or 
+   * match zero-or-one (?) operators.
+   */
+  public static final int RE_LIMITED_OPS               =  9;
+
+  /**
+   * Syntax bit. Newline is an alternation operator.
+   */
+  public static final int RE_NEWLINE_ALT               = 10; // impl.
+
+  /**
+   * Syntax bit. Intervals use { } instead of \{ \}
+   */
+  public static final int RE_NO_BK_BRACES              = 11; 
+
+  /**
+   * Syntax bit. Grouping uses ( ) instead of \( \).
+   */
+  public static final int RE_NO_BK_PARENS              = 12;
+
+  /**
+   * Syntax bit. Backreferences not allowed.
+   */
+  public static final int RE_NO_BK_REFS                = 13;
+
+  /**
+   * Syntax bit. Alternation uses | instead of \|
+   */
+  public static final int RE_NO_BK_VBAR                = 14;
+
+  /**
+   * Syntax bit. <B>Not implemented</B>.
+   */
+  public static final int RE_NO_EMPTY_RANGES           = 15;
+
+  /**
+   * Syntax bit. An unmatched right parenthesis (')' or '\)', depending
+   * on RE_NO_BK_PARENS) will throw an exception when compiling.
+   */
+  public static final int RE_UNMATCHED_RIGHT_PAREN_ORD = 16;
+
+  /**
+   * Syntax bit. <B>Not implemented.</B>
+   */
+  public static final int RE_HAT_LISTS_NOT_NEWLINE     = 17;
+
+  /**
+   * Syntax bit.  Stingy matching is allowed (+?, *?, ??, {x,y}?).
+   */
+  public static final int RE_STINGY_OPS                = 18;
+
+  /**
+   * Syntax bit. Allow character class escapes (\d, \D, \s, \S, \w, \W).
+   */
+  public static final int RE_CHAR_CLASS_ESCAPES        = 19;
+
+  /**
+   * Syntax bit. Allow use of (?:xxx) grouping (subexpression is not saved).
+   */
+  public static final int RE_PURE_GROUPING             = 20;
+
+  /**
+   * Syntax bit. <B>Not implemented</B>.
+   */
+  public static final int RE_LOOKAHEAD                 = 21;
+
+  /**
+   * Syntax bit. Allow beginning- and end-of-string anchors (\A, \Z).
+   */
+  public static final int RE_STRING_ANCHORS            = 22;
+
+  /**
+   * Syntax bit. Allow embedded comments, (#comment), as in Perl5.
+   */
+  public static final int RE_COMMENTS                  = 23;
+
+  /**
+   * Syntax bit. Allow character class escapes within lists, as in Perl5.
+   */
+  public static final int RE_CHAR_CLASS_ESC_IN_LISTS   = 24;
+
+  private static final int BIT_TOTAL                   = 25;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the awk utility.
+   */
+  public static final RESyntax RE_SYNTAX_AWK;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the ed utility.
+   */
+  public static final RESyntax RE_SYNTAX_ED;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the egrep utility.
+   */
+  public static final RESyntax RE_SYNTAX_EGREP;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the GNU Emacs editor.
+   */
+  public static final RESyntax RE_SYNTAX_EMACS;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the grep utility.
+   */
+  public static final RESyntax RE_SYNTAX_GREP;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the POSIX awk specification.
+   */
+  public static final RESyntax RE_SYNTAX_POSIX_AWK;
+
+  /**
+   * Predefined syntax.
+   * Emulates POSIX basic regular expression support.
+   */
+  public static final RESyntax RE_SYNTAX_POSIX_BASIC;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the POSIX egrep specification.
+   */
+  public static final RESyntax RE_SYNTAX_POSIX_EGREP;
+
+  /**
+   * Predefined syntax.
+   * Emulates POSIX extended regular expression support.
+   */
+  public static final RESyntax RE_SYNTAX_POSIX_EXTENDED;
+
+  /**
+   * Predefined syntax.
+   * Emulates POSIX basic minimal regular expressions.
+   */
+  public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_BASIC;
+
+  /**
+   * Predefined syntax.
+   * Emulates POSIX extended minimal regular expressions.
+   */
+  public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_EXTENDED;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in the sed utility.
+   */
+  public static final RESyntax RE_SYNTAX_SED;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in Larry Wall's perl, version 4,
+   */
+  public static final RESyntax RE_SYNTAX_PERL4;
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in Larry Wall's perl, version 4,
+   * using single line mode (/s modifier).
+   */
+  public static final RESyntax RE_SYNTAX_PERL4_S; // single line mode (/s)
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in Larry Wall's perl, version 5.
+   */
+  public static final RESyntax RE_SYNTAX_PERL5;  
+
+  /**
+   * Predefined syntax.
+   * Emulates regular expression support in Larry Wall's perl, version 5,
+   * using single line mode (/s modifier).
+   */
+  public static final RESyntax RE_SYNTAX_PERL5_S;
+  
+  static {
+    // Define syntaxes
+
+    RE_SYNTAX_EMACS = new RESyntax();
+
+    RESyntax RE_SYNTAX_POSIX_COMMON = new RESyntax()
+      .set(RE_CHAR_CLASSES)
+      .set(RE_DOT_NEWLINE)
+      .set(RE_DOT_NOT_NULL)
+      .set(RE_INTERVALS)
+      .set(RE_NO_EMPTY_RANGES);
+
+    RE_SYNTAX_POSIX_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+      .set(RE_BK_PLUS_QM);
+
+    RE_SYNTAX_POSIX_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+      .set(RE_CONTEXT_INDEP_ANCHORS)
+      .set(RE_CONTEXT_INDEP_OPS)
+      .set(RE_NO_BK_BRACES)
+      .set(RE_NO_BK_PARENS)
+      .set(RE_NO_BK_VBAR)
+      .set(RE_UNMATCHED_RIGHT_PAREN_ORD);
+
+    RE_SYNTAX_AWK = new RESyntax()
+      .set(RE_BACKSLASH_ESCAPE_IN_LISTS)
+      .set(RE_DOT_NOT_NULL)
+      .set(RE_NO_BK_PARENS)
+      .set(RE_NO_BK_REFS)
+      .set(RE_NO_BK_VBAR)
+      .set(RE_NO_EMPTY_RANGES)
+      .set(RE_UNMATCHED_RIGHT_PAREN_ORD);
+    
+    RE_SYNTAX_POSIX_AWK = new RESyntax(RE_SYNTAX_POSIX_EXTENDED)
+      .set(RE_BACKSLASH_ESCAPE_IN_LISTS);
+    
+    RE_SYNTAX_GREP = new RESyntax()
+      .set(RE_BK_PLUS_QM)
+      .set(RE_CHAR_CLASSES)
+      .set(RE_HAT_LISTS_NOT_NEWLINE)
+      .set(RE_INTERVALS)
+      .set(RE_NEWLINE_ALT);
+
+    RE_SYNTAX_EGREP = new RESyntax()
+      .set(RE_CHAR_CLASSES)
+      .set(RE_CONTEXT_INDEP_ANCHORS)
+      .set(RE_CONTEXT_INDEP_OPS)
+      .set(RE_HAT_LISTS_NOT_NEWLINE)
+      .set(RE_NEWLINE_ALT)
+      .set(RE_NO_BK_PARENS)
+      .set(RE_NO_BK_VBAR);
+    
+    RE_SYNTAX_POSIX_EGREP = new RESyntax(RE_SYNTAX_EGREP)
+      .set(RE_INTERVALS)
+      .set(RE_NO_BK_BRACES);
+    
+    /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff.  */
+    
+    RE_SYNTAX_ED = new RESyntax(RE_SYNTAX_POSIX_BASIC);
+    
+    RE_SYNTAX_SED = new RESyntax(RE_SYNTAX_POSIX_BASIC);
+    
+    RE_SYNTAX_POSIX_MINIMAL_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+      .set(RE_LIMITED_OPS);
+    
+    /* Differs from RE_SYNTAX_POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS
+       replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */
+
+    RE_SYNTAX_POSIX_MINIMAL_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON)
+      .set(RE_CONTEXT_INDEP_ANCHORS)
+      .set(RE_CONTEXT_INVALID_OPS)
+      .set(RE_NO_BK_BRACES)
+      .set(RE_NO_BK_PARENS)
+      .set(RE_NO_BK_REFS)
+      .set(RE_NO_BK_VBAR)
+      .set(RE_UNMATCHED_RIGHT_PAREN_ORD);
+    
+    /* There is no official Perl spec, but here's a "best guess" */
+    
+    RE_SYNTAX_PERL4 = new RESyntax()
+      .set(RE_BACKSLASH_ESCAPE_IN_LISTS)
+      .set(RE_CONTEXT_INDEP_ANCHORS)
+      .set(RE_CONTEXT_INDEP_OPS)          // except for '{', apparently
+      .set(RE_INTERVALS)
+      .set(RE_NO_BK_BRACES)
+      .set(RE_NO_BK_PARENS)
+      .set(RE_NO_BK_VBAR)
+      .set(RE_NO_EMPTY_RANGES)
+      .set(RE_CHAR_CLASS_ESCAPES);    // \d,\D,\w,\W,\s,\S
+
+    RE_SYNTAX_PERL4_S = new RESyntax(RE_SYNTAX_PERL4)
+      .set(RE_DOT_NEWLINE);
+    
+    RE_SYNTAX_PERL5 = new RESyntax(RE_SYNTAX_PERL4)
+      .set(RE_PURE_GROUPING)          // (?:)
+      .set(RE_STINGY_OPS)             // *?,??,+?,{}?
+      .set(RE_LOOKAHEAD)              // (?=)(?!) not implemented
+      .set(RE_STRING_ANCHORS)         // \A,\Z
+      .set(RE_CHAR_CLASS_ESC_IN_LISTS)// \d,\D,\w,\W,\s,\S within []
+      .set(RE_COMMENTS);              // (?#)
+    
+    RE_SYNTAX_PERL5_S = new RESyntax(RE_SYNTAX_PERL5)
+      .set(RE_DOT_NEWLINE);
+  }
+
+
+  /**
+   * Construct a new syntax object with all bits turned off.
+   * This is equivalent to RE_SYNTAX_EMACS.
+   */
+  public RESyntax() {
+    bits = new BitSet(BIT_TOTAL);
+  }
+
+  /**
+   * Construct a new syntax object with all bits set the same 
+   * as the other syntax.
+   */
+  public RESyntax(RESyntax other) {
+    bits = (BitSet) other.bits.clone();
+  }
+
+  /**
+   * Check if a given bit is set in this syntax.
+   */
+  public boolean get(int index) {
+    return bits.get(index);
+  }
+
+  /**
+   * Set a given bit in this syntax.  Returns a reference to this syntax
+   * for easy chaining.
+   */
+  public RESyntax set(int index) {
+    bits.set(index);
+    return this;
+  }
+}
diff --git a/gnu/regexp/REToken.java b/gnu/regexp/REToken.java
new file mode 100644
index 0000000..43f238c
--- /dev/null
+++ b/gnu/regexp/REToken.java
@@ -0,0 +1,74 @@
+/*
+ *  gnu/regexp/REToken.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.io.ByteArrayOutputStream;
+
+abstract class REToken {
+  // used by RETokenStart and RETokenEnd  
+  static final String newline = System.getProperty("line.separator"); 
+
+  protected REToken m_next = null;
+  protected REToken m_uncle = null;
+  protected int m_subIndex;
+
+  protected REToken(int f_subIndex) {
+    m_subIndex = f_subIndex;
+  }
+
+  int getMinimumLength() {
+    return 0;
+  }
+
+  void setUncle(REToken f_uncle) {
+    m_uncle = f_uncle;
+  }
+
+  abstract int[] match(CharIndexed input, int index, int eflags, REMatch mymatch);
+  
+  protected int[] next(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    mymatch.end[m_subIndex] = index;
+    if (m_next == null) {
+      if (m_uncle == null) {
+	return new int[] { index };
+      } else {
+	if (m_uncle.match(input,index,eflags,mymatch) == null) {
+	  return null;
+	} else {
+	  return new int[] { index };
+	}
+      }
+    } else {
+	return m_next.match(input,index,eflags,mymatch);
+    }
+  }
+  
+  boolean chain(REToken next) {
+    m_next = next;
+    return true; // Token was accepted
+  }
+
+  void dump(StringBuffer os) { 
+  }
+
+  void dumpAll(StringBuffer os) {
+    dump(os);
+    if (m_next != null) m_next.dumpAll(os);
+  }
+}
diff --git a/gnu/regexp/RETokenAny.java b/gnu/regexp/RETokenAny.java
new file mode 100644
index 0000000..e167fa4
--- /dev/null
+++ b/gnu/regexp/RETokenAny.java
@@ -0,0 +1,53 @@
+/*
+ *  gnu/regexp/RETokenAny.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+class RETokenAny extends REToken {
+  /** True if '.' can match a newline (RE_DOT_NEWLINE) */
+  private boolean m_newline; 
+
+  /** True if '.' can't match a null (RE_DOT_NOT_NULL) */
+  private boolean m_null;    
+  
+  RETokenAny(int f_subIndex, boolean f_newline, boolean f_null) { 
+    super(f_subIndex);
+    m_newline = f_newline;
+    m_null = f_null;
+  }
+
+  int getMinimumLength() {
+    return 1;
+  }
+
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    char ch = input.charAt(index);
+    if ((ch == CharIndexed.OUT_OF_BOUNDS)
+	|| (!m_newline && (ch == '\n'))
+	|| (m_null && (ch == 0)))
+      return null;
+
+    return next(input,index+1,eflags,mymatch);
+  }
+
+  void dump(StringBuffer os) {
+    os.append('.');
+  }
+}
+
diff --git a/gnu/regexp/RETokenBackRef.java b/gnu/regexp/RETokenBackRef.java
new file mode 100644
index 0000000..5007026
--- /dev/null
+++ b/gnu/regexp/RETokenBackRef.java
@@ -0,0 +1,51 @@
+/*
+ *  gnu/regexp/RETokenBackRef.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+class RETokenBackRef extends REToken {
+  private int num;
+  private boolean insens;
+  
+  RETokenBackRef(int f_subIndex, int mynum, boolean ins) {
+    super(f_subIndex);
+    insens = ins;
+    num = mynum;
+  }
+
+  // should implement getMinimumLength() -- any ideas?
+
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    int b,e;
+    b = mymatch.start[num];
+    e = mymatch.end[num];
+    if ((b==-1)||(e==-1)) return null; // this shouldn't happen, but...
+    for (int i=b; i<e; i++) {
+      if (input.charAt(index+i-b) != input.charAt(i)) return null;
+    }
+
+    return next(input,index+e-b,eflags,mymatch);
+  }
+
+  void dump(StringBuffer os) {
+    os.append('\\').append(num);
+  }
+}
+
+
diff --git a/gnu/regexp/RETokenChar.java b/gnu/regexp/RETokenChar.java
new file mode 100644
index 0000000..eb2837e
--- /dev/null
+++ b/gnu/regexp/RETokenChar.java
@@ -0,0 +1,68 @@
+/*
+ *  gnu/regexp/RETokenChar.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+class RETokenChar extends REToken {
+  private char[] ch;
+  private boolean insens;
+
+  RETokenChar(int f_subIndex, char c, boolean ins) {
+    super(f_subIndex);
+    ch = new char [1];
+    ch[0] = (insens = ins) ? Character.toLowerCase(c) : c;
+  }
+
+  int getMinimumLength() {
+    return ch.length;
+  }
+  
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    int z = ch.length;
+    char c;
+    for (int i=0; i<z; i++) {
+      c = input.charAt(index+i);
+      if (( (insens) ? Character.toLowerCase(c) : c ) != ch[i]) return null;
+    }
+    return next(input,index+z,eflags,mymatch);
+  }
+
+  // Overrides REToken.chain() to optimize for strings
+  boolean chain(REToken next) {
+    if (next instanceof RETokenChar) {
+      RETokenChar cnext = (RETokenChar) next;
+      // assume for now that next can only be one character
+      int newsize = ch.length + cnext.ch.length;
+      
+      char[] chTemp = new char [newsize];
+      
+      System.arraycopy(ch,0,chTemp,0,ch.length);
+      System.arraycopy(cnext.ch,0,chTemp,ch.length,cnext.ch.length);
+      
+      ch = chTemp;
+      return false;
+    } else return super.chain(next);
+  }
+
+  void dump(StringBuffer os) {
+    os.append(ch);
+  }
+}
+
+
diff --git a/gnu/regexp/RETokenEnd.java b/gnu/regexp/RETokenEnd.java
new file mode 100644
index 0000000..7dbf301
--- /dev/null
+++ b/gnu/regexp/RETokenEnd.java
@@ -0,0 +1,44 @@
+/*
+ *  gnu/regexp/RETokenEnd.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+class RETokenEnd extends REToken {
+  private boolean newline;
+
+  RETokenEnd(int f_subIndex,boolean f_newline) { 
+    super(f_subIndex);
+    newline = f_newline;
+  }
+
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    // this may not work on systems that use \r\n as line separator. FIXME
+    // use System.getProperty("line.separator");
+    char ch = input.charAt(index);
+    if (ch == CharIndexed.OUT_OF_BOUNDS)
+      return ((eflags & RE.REG_NOTEOL)>0) ? 
+	null : next(input,index,eflags,mymatch);
+    return (newline && (ch == '\n')) ? 
+      next(input,index,eflags,mymatch) : null;
+  }
+
+  void dump(StringBuffer os) {
+    os.append('$');
+  }
+}
diff --git a/gnu/regexp/RETokenOneOf.java b/gnu/regexp/RETokenOneOf.java
new file mode 100644
index 0000000..21c9446
--- /dev/null
+++ b/gnu/regexp/RETokenOneOf.java
@@ -0,0 +1,135 @@
+/*
+ *  gnu/regexp/RETokenOneOf.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.util.Vector;
+
+class RETokenOneOf extends REToken {
+  private Vector options;
+  private boolean negative;
+
+  // This constructor is used for convenience when we know the set beforehand,
+  // e.g. \d --> new RETokenOneOf("0123456789",false, ..)
+  //      \D --> new RETokenOneOf("0123456789",true, ..)
+
+  RETokenOneOf(int f_subIndex, String f_options,boolean f_negative,boolean f_insens) {
+    super(f_subIndex);
+    options = new Vector();
+    negative = f_negative;
+    for (int i=0; i<f_options.length(); i++)
+      options.addElement(new RETokenChar(f_subIndex,f_options.charAt(i),f_insens));
+  }
+
+  RETokenOneOf(int f_subIndex, Vector f_options,boolean f_negative) {
+    super(f_subIndex);
+    options = f_options;
+    negative = f_negative;
+  }
+
+  int getMinimumLength() {
+    int min = Integer.MAX_VALUE;
+    int x;
+    for (int i=0; i < options.size(); i++) {
+      if ((x = ((REToken) options.elementAt(i)).getMinimumLength()) < min)
+	min = x;
+    }
+    return min;
+  }
+
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    if (negative && (input.charAt(index) == CharIndexed.OUT_OF_BOUNDS)) 
+      return null;
+
+    int[] newIndex;
+    int[] possibles = new int[0];
+    REToken tk;
+    for (int i=0; i < options.size(); i++) {
+	tk = (REToken) options.elementAt(i);
+      newIndex = tk.match(input,index,eflags,mymatch);
+
+      // Voodoo.
+      if ((newIndex == null) && (tk instanceof RE) && (tk.m_subIndex > 0)) {
+	  mymatch.reset(tk.m_subIndex + 1);
+      }
+
+      if (newIndex != null) { // match was successful
+	if (negative) return null;
+	// Add newIndex to list of possibilities.
+
+	int[] temp = new int[possibles.length + newIndex.length];
+	System.arraycopy(possibles,0,temp,0,possibles.length);
+	for (int j = 0; j < newIndex.length; j++) 
+	  temp[possibles.length + j] = newIndex[j];
+	possibles = temp;
+      }
+    } // try next option
+    // Now possibles is array of all possible matches.
+    // Try next with each possibility.
+
+    int[] doables = new int[0];
+    for (int i = 0; i < possibles.length; i++) {
+      newIndex = next(input,possibles[i],eflags,mymatch);
+      if (newIndex != null) {
+	int[] temp = new int[doables.length + newIndex.length];
+	System.arraycopy(doables,0,temp,0,doables.length);
+	for (int j = 0; j < newIndex.length; j++) 
+	  temp[doables.length + j] = newIndex[j];
+	doables = temp;
+      } else {
+	  // Voodoo.
+	  if (m_subIndex > 0) {
+	      mymatch.reset(m_subIndex + 1);
+	  }
+      }
+
+    }
+
+    if (doables.length > 0)
+      return (negative) ? 
+	null : doables;
+    else return (negative) ? 
+	   next(input,index+1,eflags,mymatch) : null;
+
+    // index+1 works for [^abc] lists, not for generic lookahead (--> index)
+  }
+
+  void dump(StringBuffer os) {
+    os.append(negative ? "[^" : "(?:");
+    for (int i = 0; i < options.size(); i++) {
+      if (!negative && (i > 0)) os.append('|');
+      ((REToken) options.elementAt(i)).dumpAll(os);
+    }
+    os.append(negative ? ']' : ')');
+  }  
+
+  // Overrides REToken.chain
+  boolean chain(REToken f_next) {
+    super.chain(f_next);
+    for (int i = 0; i < options.size(); i++)
+      ((REToken) options.elementAt(i)).setUncle(f_next);
+    return true;
+  }
+
+  /*
+  void setUncle(REToken f_next) {
+    for (int i = 0; i < options.size(); i++)
+      ((REToken) options.elementAt(i)).setUncle(f_next);
+  }
+  */
+}
diff --git a/gnu/regexp/RETokenPOSIX.java b/gnu/regexp/RETokenPOSIX.java
new file mode 100644
index 0000000..47625da
--- /dev/null
+++ b/gnu/regexp/RETokenPOSIX.java
@@ -0,0 +1,120 @@
+/*
+ *  gnu/regexp/RETokenPOSIX.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.util.Hashtable;
+
+class RETokenPOSIX extends REToken {
+  int m_type;
+  boolean m_insens;
+  boolean m_negated;
+
+  static final int  ALNUM = 0;
+  static final int  ALPHA = 1;
+  static final int  BLANK = 2;
+  static final int  CNTRL = 3;
+  static final int  DIGIT = 4;
+  static final int  GRAPH = 5;
+  static final int  LOWER = 6;
+  static final int  PRINT = 7;
+  static final int  PUNCT = 8;
+  static final int  SPACE = 9;
+  static final int  UPPER = 10;
+  static final int XDIGIT = 11;
+
+  // Array indices correspond to constants defined above.
+  static final String[] s_nameTable =  {
+    "alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower",
+    "print", "punct", "space", "upper", "xdigit" 
+  };
+
+  // The RE constructor uses this to look up the constant for a string
+  static int intValue(String key) {
+    for (int i = 0; i < s_nameTable.length; i++) {
+      if (s_nameTable[i].equals(key)) return i;
+    }
+    return -1;
+  }
+
+  RETokenPOSIX(int f_subIndex, int f_type,boolean f_insens, boolean f_negated) {
+    super(f_subIndex);
+    m_type = f_type;
+    m_insens = f_insens;
+    m_negated = f_negated;
+  }
+
+  int getMinimumLength() {
+    return 1;
+  }
+
+  int[] match(CharIndexed input, int index, int eflags,REMatch mymatch) {
+    char ch = input.charAt(index);
+    if (ch == CharIndexed.OUT_OF_BOUNDS)
+      return null;
+    
+    boolean retval = false;
+    switch (m_type) {
+    case ALNUM:
+      retval = Character.isLetterOrDigit(ch);
+      break;
+    case ALPHA:
+      retval = Character.isLetter(ch);
+      break;
+    case BLANK:
+      retval = ((ch == ' ') || (ch == '\t'));
+      break;
+    case CNTRL:
+      retval = Character.isISOControl(ch);
+      break;
+    case DIGIT:
+      retval = Character.isDigit(ch);
+      break;
+    case GRAPH:
+      retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch)));
+      break;
+    case LOWER:
+      retval = ((m_insens && Character.isLetter(ch)) || Character.isLowerCase(ch));
+      break;
+    case PRINT:
+      retval = Character.isLetterOrDigit(ch);
+      break;
+    case PUNCT:
+      retval = ("`~!@#$%^&*()-_=+[]{}\\|;:'\"/?,.<>".indexOf(ch)!=-1);
+      break;
+    case SPACE:
+      retval = Character.isWhitespace(ch);
+      break;
+    case UPPER:
+      retval = ((m_insens && Character.isLetter(ch)) || Character.isUpperCase(ch));
+      break;
+    case XDIGIT:
+      retval = (Character.isDigit(ch) || ("abcdefABCDEF".indexOf(ch)!=-1));
+      break;
+    }
+
+    if (m_negated) retval = !retval;
+    if (retval) return next(input,index+1,eflags,mymatch);
+    else return null;
+  }
+
+  void dump(StringBuffer os) {
+    if (m_negated) os.append('^');
+    os.append("[:" + s_nameTable[m_type] + ":]");
+  }
+}
diff --git a/gnu/regexp/RETokenRange.java b/gnu/regexp/RETokenRange.java
new file mode 100644
index 0000000..e6268e4
--- /dev/null
+++ b/gnu/regexp/RETokenRange.java
@@ -0,0 +1,48 @@
+/*
+ *  gnu/regexp/RETokenRange.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+class RETokenRange extends REToken {
+  private char lo, hi;
+  private boolean insens;
+
+  RETokenRange(int f_subIndex, char f_lo, char f_hi, boolean ins) {
+    super(f_subIndex);
+    lo = (insens = ins) ? Character.toLowerCase(f_lo) : f_lo;
+    hi = ins ? Character.toLowerCase(f_hi) : f_hi;
+  }
+
+  int getMinimumLength() {
+    return 1;
+  }
+
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    char c = input.charAt(index);
+    if (c == CharIndexed.OUT_OF_BOUNDS) return null;
+    if (insens) c = Character.toLowerCase(c);
+    return ((c >= lo) && (c <= hi)) ? 
+      next(input,index+1,eflags,mymatch) : null;
+  }
+
+  void dump(StringBuffer os) {
+    os.append(lo).append('-').append(hi);
+  }
+}
+
diff --git a/gnu/regexp/RETokenRepeated.java b/gnu/regexp/RETokenRepeated.java
new file mode 100644
index 0000000..d3db12b
--- /dev/null
+++ b/gnu/regexp/RETokenRepeated.java
@@ -0,0 +1,118 @@
+/*
+ *  gnu/regexp/RETokenRepeated.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+import java.util.Vector;
+
+class RETokenRepeated extends REToken {
+  private REToken token;
+  private int min,max;
+  private boolean stingy;
+
+  RETokenRepeated(int f_subIndex, REToken f_token, int f_min, int f_max) {
+    super(f_subIndex);
+    token = f_token;
+    min = f_min;
+    max = f_max;
+  }
+ 
+  void makeStingy() {
+    stingy = true;
+  }
+
+  int getMinimumLength() {
+    return (min * token.getMinimumLength());
+  }
+
+  int[] match(CharIndexed input, int index,int eflags,REMatch mymatch) {
+    int numRepeats = 0;
+    Vector positions = new Vector();
+    int[] newIndex = new int[] { index };
+    do {
+      // positions.elementAt(i) == position [] in input after <<i>> matches
+      positions.addElement(newIndex);
+      
+      // Check for stingy match for each possibility.
+      if (stingy && (numRepeats >= min)) {
+	for (int i = 0; i < newIndex.length; i++) {
+	  int[] s = next(input,newIndex[i],eflags,mymatch);
+	  if (s != null) return s;
+	}
+      }
+
+      int[] doables = new int[0];
+      int[] thisResult;
+      for (int i = 0; i < newIndex.length; i++) {
+	if ((thisResult = token.match(input,newIndex[i],eflags,mymatch)) != null) {
+	  // add to doables array
+	  int[] temp = new int[doables.length + thisResult.length];
+	  System.arraycopy(doables,0,temp,0,doables.length);
+	  for (int j = 0; j < thisResult.length; j++) {
+	    temp[doables.length + j] = thisResult[j];
+	  }
+	  doables = temp;
+	}
+      }
+      if (doables.length == 0) break;
+
+      newIndex = doables;
+    } while (numRepeats++ < max);
+
+    // If there aren't enough repeats, then fail
+    if (numRepeats < min) return null;
+    
+    // We're greedy, but ease off until a true match is found 
+    int posIndex = positions.size();
+    
+    // At this point we've either got too many or just the right amount.
+    // See if this numRepeats works with the rest of the regexp.
+    int[] doneIndex;
+    while (--posIndex >= min) {
+      newIndex = (int[]) positions.elementAt(posIndex);
+      // If rest of pattern matches
+      for (int i = 0; i < newIndex.length; i++) 
+	if ((doneIndex = next(input,newIndex[i],eflags,mymatch)) != null)
+	  return doneIndex;
+      
+      // else did not match rest of the tokens, try again on smaller sample
+    }
+    return null;
+  }
+
+  void dump(StringBuffer os) {
+    os.append('(');
+    if (token.m_subIndex == 0)
+      os.append("?:");
+    token.dumpAll(os);
+    os.append(')');
+    if ((max == Integer.MAX_VALUE) && (min <= 1))
+      os.append( (min == 0) ? '*' : '+' );
+    else if ((min == 0) && (max == 1))
+      os.append('?');
+    else {
+      os.append('{').append(min);
+      if (max > min) {
+	os.append(',');
+	if (max != Integer.MAX_VALUE) os.append(max);
+      }
+      os.append('}');
+    }
+    if (stingy) os.append('?');
+  }
+}
diff --git a/gnu/regexp/RETokenStart.java b/gnu/regexp/RETokenStart.java
new file mode 100644
index 0000000..a285008
--- /dev/null
+++ b/gnu/regexp/RETokenStart.java
@@ -0,0 +1,50 @@
+/*
+ *  gnu/regexp/RETokenStart.java
+ *  Copyright (C) 1998 Wes Biggs
+ *
+ *  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 program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package gnu.regexp;
+
+class RETokenStart extends REToken {
+  private boolean newline; // matches after a newline
+  
+  RETokenStart(int f_subIndex, boolean f_newline) {
+    super(f_subIndex);
+    newline = f_newline;
+  }
+  
+  int[] match(CharIndexed input, int index, int eflags, REMatch mymatch) {
+    // charAt(index-1) may be unknown on an InputStream. FIXME
+    // Match after a newline if in multiline mode
+    if (newline && (mymatch.offset > 0) && (input.charAt(index - 1) == '\n')) 
+      return next(input,index,eflags,mymatch);
+
+    // Don't match at all if REG_NOTBOL is set.
+    if ((eflags & RE.REG_NOTBOL) > 0) return null;
+    
+    if ((eflags & RE.REG_ANCHORINDEX) > 0)
+      return (mymatch.anchor == mymatch.offset) ? 
+	next(input,index,eflags,mymatch) : null;
+    else
+      return ((index == 0) && (mymatch.offset == 0)) ?
+	next(input,index,eflags,mymatch) : null;
+  }
+
+  void dump(StringBuffer os) {
+    os.append('^');
+  }
+}
diff --git a/loci/formats/AggregateMetadataStore.java b/loci/formats/AggregateMetadataStore.java
new file mode 100644
index 0000000..a49aba5
--- /dev/null
+++ b/loci/formats/AggregateMetadataStore.java
@@ -0,0 +1,430 @@
+//
+// AggregateMetadataStore.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A metadata store which delegates the actual storage to one or more <i>sub</i>
+ * metadata stores.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/AggregateMetadataStore.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/AggregateMetadataStore.java">SVN</a></dd></dl>
+ *
+ * @author Chris Allan callan at blackcat.ca
+ */
+public class AggregateMetadataStore implements MetadataStore {
+
+  /** The active metadata store delegates */
+  private List delegates = new ArrayList();
+
+  /**
+   * Creates a new instance.
+   * @param delegates of type {@link MetadataStore}.
+   */
+  public AggregateMetadataStore(List delegates) {
+    this.delegates = delegates;
+  }
+
+  /**
+   * Adds a delegate to the metadata store.
+   * @param delegate a <code>MetadataStore</code>
+   */
+  public void addDelegate(MetadataStore delegate) {
+    delegates.add(delegate);
+  }
+
+  /**
+   * Removes a delegate from the metadata store.
+   * @param delegate a <code>MetadataStore</code>
+   */
+  public void removeDelegate(MetadataStore delegate) {
+    delegates.remove(delegate);
+  }
+
+  /**
+   * Retrieves the current list of metadata store delegates.
+   * @return list of {@link MetadataStore} delegates.
+   */
+  public List getDelegates() {
+    return delegates;
+  }
+
+  /* @see MetadataStore#createRoot() */
+  public void createRoot() {
+    for (Iterator i = delegates.iterator(); i.hasNext();) {
+      ((MetadataStore) i.next()).createRoot();
+    }
+  }
+
+  /**
+   * Unsupported with an AggregateMetadataStore.
+   * Throws a RuntimeException up to the caller.
+   */
+  public Object getRoot() {
+    throw new RuntimeException("Unsupported with AggregateMetadataStore. " +
+      "Use getDelegates() and getRoot().");
+  }
+
+  /**
+   * Unsupported with an AggregateMetadataStore.
+   * Throws a RuntimeException up to the caller.
+   */
+  public void setRoot(Object root) {
+    throw new RuntimeException("Unsupported with AggregateMetadataStore. " +
+      "Use getDelegates() and setRoot().");
+  }
+
+  /* @see MetadataStore#setChannelGlobalMinMax(int, Double, Double, Integer) */
+  public void setChannelGlobalMinMax(int channel, Double globalMin,
+    Double globalMax, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setChannelGlobalMinMax(channel, globalMin, globalMax, i);
+    }
+  }
+
+  /* @see MetadataStore#setDefaultDisplaySettings(Integer) */
+  public void setDefaultDisplaySettings(Integer i) {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setDefaultDisplaySettings(i);
+    }
+  }
+
+  /*
+   * @see MetadataStore#setDimensions(Float,
+   *   Float, Float, Float, Float, Integer)
+   */
+  public void setDimensions(Float pixelSizeX, Float pixelSizeY,
+    Float pixelSizeZ, Float pixelSizeC, Float pixelSizeT, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setDimensions(pixelSizeX, pixelSizeY, pixelSizeZ, pixelSizeC,
+        pixelSizeT, i);
+    }
+  }
+
+  /*
+   * @see MetadataStore#setDisplayROI(Integer, Integer, Integer,
+   *   Integer, Integer, Integer, Integer, Integer, Object, Integer)
+   */
+  public void setDisplayROI(Integer x0, Integer y0, Integer z0, Integer x1,
+    Integer y1, Integer z1, Integer t0, Integer t1, Object displayOptions,
+    Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setDisplayROI(x0, y0, z0, x1, y1, z1, t0, t1, displayOptions, i);
+    }
+  }
+
+  /*
+   * @see MetadataStore#setExperimenter(String,
+   *   String, String, String, String, Object, Integer)
+   */
+  public void setExperimenter(String firstName, String lastName, String email,
+    String institution, String dataDirectory, Object group, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setExperimenter(firstName, lastName, email, institution, dataDirectory,
+                        group, i);
+    }
+  }
+
+  /* @see MetadataStore#setGroup(String, Object, Object, Integer) */
+  public void setGroup(String name, Object leader, Object contact, Integer i) {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setGroup(name, leader, contact, i);
+    }
+  }
+
+  /* @see MetadataStore#setImage(String, String, String, Integer) */
+  public void setImage(String name, String creationDate, String description,
+    Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setImage(name, creationDate, description, i);
+    }
+  }
+
+  /*
+   * @see MetadataStore#setInstrument(String, String, String, String, Integer)
+   */
+  public void setInstrument(String manufacturer, String model,
+    String serialNumber, String type, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setInstrument(manufacturer, model, serialNumber, type, i);
+    }
+  }
+
+  /*
+   * @see MetadataStore#setLogicalChannel(int, String, Integer, Integer,
+   * Integer, Float, Integer, Integer, Integer, Float, Float, String,
+   * Integer, String, String, String, Integer, Float, String, Integer, Integer,
+   * Integer, String, Float, Integer)
+   */
+  public void setLogicalChannel(int channelIdx, String name,
+    Integer samplesPerPixel, Integer filter, Integer lightSource,
+    Float lightAttenuation, Integer lightWavelength, Integer otf,
+    Integer detector, Float detectorOffset, Float detectorGain,
+    String illuminationType, Integer pinholeSize,
+    String photometricInterpretation, String mode, String contrastMethod,
+    Integer auxLightSource, Float auxLightAttenuation, String auxTechnique,
+    Integer auxLightWavelength, Integer emWave, Integer exWave, String fluor,
+    Float ndFilter, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setLogicalChannel(channelIdx, name, samplesPerPixel, filter,
+        lightSource, lightAttenuation, lightWavelength, otf, detector,
+        detectorOffset, detectorGain, illuminationType, pinholeSize,
+        photometricInterpretation, mode, contrastMethod, auxLightSource,
+        auxLightAttenuation, auxTechnique, auxLightWavelength, emWave, exWave,
+        fluor, ndFilter, i);
+    }
+  }
+
+  /*
+   * @see MetadataStore#setPixels(Integer, Integer, Integer,
+   *   Integer, Integer, String, Boolean, String, Integer, Integer)
+   */
+  public void setPixels(Integer sizeX, Integer sizeY, Integer sizeZ,
+    Integer sizeC, Integer sizeT, Integer pixelType, Boolean bigEndian,
+    String dimensionOrder, Integer imageNo, Integer pixelsNo)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setPixels(sizeX, sizeY, sizeZ, sizeC, sizeT, pixelType, bigEndian,
+                  dimensionOrder, imageNo, pixelsNo);
+    }
+  }
+
+  /* @see MetadataStore#setPlaneInfo(int, int, int, Float, Float, Integer) */
+  public void setPlaneInfo(int theZ, int theC, int theT, Float timestamp,
+    Float exposureTime, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setPlaneInfo(theZ, theC, theT, timestamp, exposureTime, i);
+    }
+  }
+
+  /* @see MetadataStore#setStageLabel(String, Float, Float, Float, Integer) */
+  public void setStageLabel(String name, Float x, Float y, Float z, Integer i) {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setStageLabel(name, x, y, z, i);
+    }
+  }
+
+  /* @see MetadataStore#setImagingEnvironment(Float, Float, Float,
+   * Float, Integer)
+   */
+  public void setImagingEnvironment(Float temperature, Float airPressure,
+    Float humidity, Float co2Percent, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setImagingEnvironment(temperature, airPressure, humidity,
+        co2Percent, i);
+    }
+  }
+
+  /* @see MetadataStore#setDisplayChannel(Integer, Double, Double, Float
+   * Integer)
+   */
+  public void setDisplayChannel(Integer channelNumber, Double blackLevel,
+    Double whiteLevel, Float gamma, Integer i)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setDisplayChannel(channelNumber, blackLevel, whiteLevel, gamma, i);
+    }
+  }
+
+  /* @see MetadataStore#setDisplayOptions(Float, Boolean, Boolean, Boolean,
+   * Boolean, String, Integer, Integer, Integer, Integer, Integer, Integer,
+   * Integer, Integer, Integer, Integer)
+   */
+  public void setDisplayOptions(Float zoom, Boolean redChannelOn,
+    Boolean greenChannelOn, Boolean blueChannelOn, Boolean displayRGB,
+    String colorMap, Integer zstart, Integer zstop, Integer tstart,
+    Integer tstop, Integer imageNdx, Integer pixelNdx, Integer redChannel,
+    Integer greenChannel, Integer blueChannel, Integer grayChannel)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setDisplayOptions(zoom, redChannelOn, greenChannelOn, blueChannelOn,
+        displayRGB, colorMap, zstart, zstop, tstart, tstop, imageNdx, pixelNdx,
+        redChannel, greenChannel, blueChannel, grayChannel);
+    }
+  }
+
+  /* @see MetadataStore#setLightSource(String, String, String,
+   * Integer, Integer)
+   */
+  public void setLightSource(String manufacturer, String model,
+    String serialNumber, Integer instrumentNdx, Integer lightNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setLightSource(manufacturer, model, serialNumber, instrumentNdx,
+        lightNdx);
+    }
+  }
+
+  /* @see MetadataStore#setLaser(String, String, Integer, Boolean, Boolean,
+   * String, Float, Integer, Integer, Integer, Integer)
+   */
+  public void setLaser(String type, String medium, Integer wavelength,
+    Boolean frequencyDoubled, Boolean tunable, String pulse, Float power,
+    Integer instrumentNdx, Integer lightNdx, Integer pumpNdx, Integer laserNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setLaser(type, medium, wavelength, frequencyDoubled, tunable, pulse,
+        power, instrumentNdx, lightNdx, pumpNdx, laserNdx);
+    }
+  }
+
+  /* @see MetadataStore#setFilament(String, Float, Integer, Integer) */
+  public void setFilament(String type, Float power, Integer lightNdx,
+    Integer filamentNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setFilament(type, power, lightNdx, filamentNdx);
+    }
+  }
+
+  /* @see MetadataStore#setArc(String, Float, Integer, Integer) */
+  public void setArc(String type, Float power, Integer lightNdx, Integer arcNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setArc(type, power, lightNdx, arcNdx);
+    }
+  }
+
+  /* @see MetadataStore#setDetector(String, String, String, String, Float,
+   * Float, Float, Integer, Integer)
+   */
+  public void setDetector(String manufacturer, String model,
+    String serialNumber, String type, Float gain, Float voltage, Float offset,
+    Integer instrumentNdx, Integer detectorNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setDetector(manufacturer, model, serialNumber, type, gain, voltage,
+        offset, instrumentNdx, detectorNdx);
+    }
+  }
+
+  /* @see MetadataStore#setObjective(String, String, String, Float, Float,
+   * Integer, Integer)
+   */
+  public void setObjective(String manufacturer, String model,
+    String serialNumber, Float lensNA, Float magnification,
+    Integer instrumentNdx, Integer objectiveNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setObjective(manufacturer, model, serialNumber, lensNA, magnification,
+        instrumentNdx, objectiveNdx);
+    }
+  }
+
+  /* @see MetadataStore#setExcitationFilter(String, String, String, String,
+   * Integer)
+   */
+  public void setExcitationFilter(String manufacturer, String model,
+    String lotNumber, String type, Integer filterNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setExcitationFilter(manufacturer, model, lotNumber, type, filterNdx);
+    }
+  }
+
+  /* @see MetadataStore#setDichroic(String, String, String, Integer) */
+  public void setDichroic(String manufacturer, String model, String lotNumber,
+    Integer dichroicNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setDichroic(manufacturer, model, lotNumber, dichroicNdx);
+    }
+  }
+
+  /* @see MetadataStore#setEmissionFilter(String, String, String,
+   * String, Integer)
+   */
+  public void setEmissionFilter(String manufacturer, String model,
+    String lotNumber, String type, Integer filterNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setEmissionFilter(manufacturer, model, lotNumber, type, filterNdx);
+    }
+  }
+
+  /* @see MetadataStore#setFilterSet(String, String, String, Integer,
+   * Integer)
+   */
+  public void setFilterSet(String manufacturer, String model, String lotNumber,
+    Integer filterSetNdx, Integer filterNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setFilterSet(manufacturer, model, lotNumber, filterSetNdx, filterNdx);
+    }
+  }
+
+  /* @see MetadataStore#setOTF(Integer, Integer, String, String,
+   * Boolean, Integer, Integer, Integer, Integer)
+   */
+  public void setOTF(Integer sizeX, Integer sizeY, String pixelType,
+    String path, Boolean opticalAxisAverage, Integer instrumentNdx,
+    Integer otfNdx, Integer filterNdx, Integer objectiveNdx)
+  {
+    for (Iterator iter = delegates.iterator(); iter.hasNext();) {
+      MetadataStore s = (MetadataStore) iter.next();
+      s.setOTF(sizeX, sizeY, pixelType, path, opticalAxisAverage,
+        instrumentNdx, otfNdx, filterNdx, objectiveNdx);
+    }
+  }
+
+}
diff --git a/loci/formats/AxisGuesser.java b/loci/formats/AxisGuesser.java
new file mode 100644
index 0000000..153188d
--- /dev/null
+++ b/loci/formats/AxisGuesser.java
@@ -0,0 +1,385 @@
+//
+// AxisGuesser.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+/**
+ * AxisGuesser guesses which blocks in a file pattern correspond to which
+ * dimensional axes (Z, T or C), potentially recommending an adjustment in
+ * dimension order within the files, depending on the confidence of each guess.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/AxisGuesser.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/AxisGuesser.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class AxisGuesser {
+
+  // -- Constants --
+
+  /** Axis type for unclassified axes. */
+  public static final int UNKNOWN_AXIS = 0;
+
+  /** Axis type for focal planes. */
+  public static final int Z_AXIS = 1;
+
+  /** Axis type for time points. */
+  public static final int T_AXIS = 2;
+
+  /** Axis type for channels. */
+  public static final int C_AXIS = 3;
+
+  /** Prefix endings indicating space dimension. */
+  protected static final String[] Z = {
+    "fp", "sec", "z", "zs", "focal", "focalplane"
+  };
+
+  /** Prefix endings indicating time dimension. */
+  protected static final String[] T = {"t", "tl", "tp", "time"};
+
+  /** Prefix endings indicating channel dimension. */
+  protected static final String[] C = {"c", "ch", "w", "wavelength"};
+
+  protected static final BigInteger TWO = new BigInteger("2");
+  protected static final BigInteger THREE = new BigInteger("3");
+
+  // -- Fields --
+
+  /** File pattern identifying dimensional axis blocks. */
+  protected FilePattern fp;
+
+  /** Original ordering of internal dimensional axes. */
+  protected String dimOrder;
+
+  /** Adjusted ordering of internal dimensional axes. */
+  protected String newOrder;
+
+  /** Guessed axis types. */
+  protected int[] axisTypes;
+
+  /** Whether the guesser is confident that all axis types are correct. */
+  protected boolean certain;
+
+  // -- Constructor --
+
+  /**
+   * Guesses dimensional axis assignments corresponding to the given
+   * file pattern, using the specified dimensional information from
+   * within each file as a guide.
+   *
+   * @param fp The file pattern of the files
+   * @param dimOrder The dimension order (e.g., XYZTC) within each file
+   * @param sizeZ The number of Z positions within each file
+   * @param sizeT The number of T positions within each file
+   * @param sizeC The number of C positions within each file
+   * @param isCertain Whether the dimension order given is known to be good,
+   *   or merely a guess
+   *
+   * @see FilePattern
+   */
+  public AxisGuesser(FilePattern fp, String dimOrder,
+    int sizeZ, int sizeT, int sizeC, boolean isCertain)
+  {
+    this.fp = fp;
+    this.dimOrder = dimOrder;
+
+    newOrder = dimOrder;
+    String[] prefixes = fp.getPrefixes();
+    String suffix = fp.getSuffix();
+    BigInteger[] first = fp.getFirst();
+    BigInteger[] last = fp.getLast();
+    BigInteger[] step = fp.getStep();
+    int[] count = fp.getCount();
+    axisTypes = new int[count.length];
+    boolean foundZ = false, foundT = false, foundC = false;
+
+    // -- 1) fill in "known" axes based on known patterns and conventions --
+
+    for (int i=0; i<axisTypes.length; i++) {
+      String p = prefixes[i].toLowerCase();
+
+      // strip trailing digits and divider characters
+      char[] ch = p.toCharArray();
+      int l = ch.length - 1;
+      while (l >= 0 && (ch[l] >= '0' && ch[l] <= '9' ||
+        ch[l] == ' ' || ch[l] == '-' || ch[l] == '_' || ch[l] == '.'))
+      {
+        l--;
+      }
+
+      // useful prefix segment consists of trailing alphanumeric characters
+      int f = l;
+      while (f >= 0 && ch[f] >= 'a' && ch[f] <= 'z') f--;
+      p = p.substring(f + 1, l + 1);
+
+      // check against known Z prefixes
+      for (int j=0; j<Z.length; j++) {
+        if (p.equals(Z[j])) {
+          axisTypes[i] = Z_AXIS;
+          foundZ = true;
+          break;
+        }
+      }
+      if (axisTypes[i] != UNKNOWN_AXIS) continue;
+
+      // check against known T prefixes
+      for (int j=0; j<T.length; j++) {
+        if (p.equals(T[j])) {
+          axisTypes[i] = T_AXIS;
+          foundT = true;
+          break;
+        }
+      }
+      if (axisTypes[i] != UNKNOWN_AXIS) continue;
+
+      // check against known C prefixes
+      for (int j=0; j<C.length; j++) {
+        if (p.equals(C[j])) {
+          axisTypes[i] = C_AXIS;
+          foundC = true;
+          break;
+        }
+      }
+      if (axisTypes[i] != UNKNOWN_AXIS) continue;
+
+      // check special case: <2-3> (Bio-Rad PIC)
+      if (first[i].equals(TWO) && last[i].equals(THREE) &&
+        step[i].equals(BigInteger.ONE) && suffix.equalsIgnoreCase(".pic"))
+      {
+        axisTypes[i] = C_AXIS;
+        foundC = true;
+        break;
+      }
+    }
+
+    // -- 2) check for special cases where dimension order should be swapped --
+
+    if (!isCertain) { // only switch if dimension order is uncertain
+      if (foundZ && !foundT && sizeZ > 1 && sizeT == 1 ||
+        foundT && !foundZ && sizeT > 1 && sizeZ == 1)
+      {
+        // swap Z and T dimensions
+        int indexZ = newOrder.indexOf('Z');
+        int indexT = newOrder.indexOf('T');
+        char[] ch = newOrder.toCharArray();
+        ch[indexZ] = 'T';
+        ch[indexT] = 'Z';
+        newOrder = new String(ch);
+        int sz = sizeT;
+        sizeT = sizeZ;
+        sizeZ = sz;
+      }
+    }
+
+    // -- 3) fill in remaining axis types --
+
+    boolean canBeZ = !foundZ && sizeZ == 1;
+    boolean canBeT = !foundT && sizeT == 1;
+
+    certain = isCertain;
+
+    for (int i=0; i<axisTypes.length; i++) {
+      if (axisTypes[i] != UNKNOWN_AXIS) continue;
+      certain = false;
+
+      if (canBeZ) {
+        axisTypes[i] = Z_AXIS;
+        canBeZ = false;
+      }
+      else if (canBeT) {
+        axisTypes[i] = T_AXIS;
+        canBeT = false;
+      }
+      else axisTypes[i] = C_AXIS;
+    }
+  }
+
+  // -- AxisGuesser API methods --
+
+  /** Gets the file pattern. */
+  public FilePattern getFilePattern() { return fp; }
+
+  /** Gets the original dimension order. */
+  public String getOriginalOrder() { return dimOrder; }
+
+  /** Gets the adjusted dimension order. */
+  public String getAdjustedOrder() { return newOrder; }
+
+  /** Gets whether the guesser is confident that all axes are correct. */
+  public boolean isCertain() { return certain; }
+
+  /**
+   * Gets the guessed axis type for each dimensional block.
+   * @return An array containing values from the enumeration:
+   *   <ul>
+   *     <li>Z_AXIS: focal planes</li>
+   *     <li>T_AXIS: time points</li>
+   *     <li>C_AXIS: channels</li>
+   *   </ul>
+   */
+  public int[] getAxisTypes() { return axisTypes; }
+
+  /**
+   * Sets the axis type for each dimensional block.
+   * @param axes An array containing values from the enumeration:
+   *   <ul>
+   *     <li>Z_AXIS: focal planes</li>
+   *     <li>T_AXIS: time points</li>
+   *     <li>C_AXIS: channels</li>
+   *   </ul>
+   */
+  public void setAxisTypes(int[] axes) { axisTypes = axes; }
+
+  /** Gets the number of Z axes in the pattern. */
+  public int getAxisCountZ() { return getAxisCount(Z_AXIS); }
+
+  /** Gets the number of T axes in the pattern. */
+  public int getAxisCountT() { return getAxisCount(T_AXIS); }
+
+  /** Gets the number of C axes in the pattern. */
+  public int getAxisCountC() { return getAxisCount(C_AXIS); }
+
+  /** Gets the number of axes in the pattern of the given type.
+   *  @param axisType One of:
+   *   <ul>
+   *     <li>Z_AXIS: focal planes</li>
+   *     <li>T_AXIS: time points</li>
+   *     <li>C_AXIS: channels</li>
+   *   </ul>
+   */
+  public int getAxisCount(int axisType) {
+    int num = 0;
+    for (int i=0; i<axisTypes.length; i++) {
+      if (axisTypes[i] == axisType) num++;
+    }
+    return num;
+  }
+
+  // -- Main method --
+
+  /** Method for testing pattern guessing logic. */
+  public static void main(String[] args) throws FormatException, IOException {
+    Location file = args.length < 1 ?
+      new Location(System.getProperty("user.dir")).listFiles()[0] :
+      new Location(args[0]);
+    LogTools.println("File = " + file.getAbsoluteFile());
+    String pat = FilePattern.findPattern(file);
+    if (pat == null) LogTools.println("No pattern found.");
+    else {
+      LogTools.println("Pattern = " + pat);
+      FilePattern fp = new FilePattern(pat);
+      if (fp.isValid()) {
+        LogTools.println("Pattern is valid.");
+        String id = fp.getFiles()[0];
+        if (!new Location(id).exists()) {
+          LogTools.println("File '" + id + "' does not exist.");
+        }
+        else {
+          // read dimensional information from first file
+          LogTools.print("Reading first file ");
+          ImageReader reader = new ImageReader();
+          reader.setId(id);
+          String dimOrder = reader.getDimensionOrder();
+          int sizeZ = reader.getSizeZ();
+          int sizeT = reader.getSizeT();
+          int sizeC = reader.getSizeC();
+          boolean certain = reader.isOrderCertain();
+          reader.close();
+          LogTools.println("[done]");
+          LogTools.println("\tdimOrder = " + dimOrder +
+            (certain ? " (certain)" : " (uncertain)"));
+          LogTools.println("\tsizeZ = " + sizeZ);
+          LogTools.println("\tsizeT = " + sizeT);
+          LogTools.println("\tsizeC = " + sizeC);
+
+          // guess axes
+          AxisGuesser ag = new AxisGuesser(fp,
+            dimOrder, sizeZ, sizeT, sizeC, certain);
+
+          // output results
+          String[] blocks = fp.getBlocks();
+          String[] prefixes = fp.getPrefixes();
+          int[] axes = ag.getAxisTypes();
+          String newOrder = ag.getAdjustedOrder();
+          LogTools.println("Axis types:");
+          for (int i=0; i<blocks.length; i++) {
+            String axis;
+            switch (axes[i]) {
+              case Z_AXIS:
+                axis = "Z";
+                break;
+              case T_AXIS:
+                axis = "T";
+                break;
+              case C_AXIS:
+                axis = "C";
+                break;
+              default:
+                axis = "?";
+            }
+            LogTools.println("\t" + blocks[i] + "\t" +
+              axis + " (prefix = " + prefixes[i] + ")");
+          }
+          if (!dimOrder.equals(newOrder)) {
+            LogTools.println("Adjusted dimension order = " + newOrder);
+          }
+        }
+      }
+      else LogTools.println("Pattern is invalid: " + fp.getErrorMessage());
+    }
+  }
+
+}
+
+// -- Notes --
+
+// INPUTS: file pattern, dimOrder, sizeZ, sizeT, sizeC, isCertain
+//
+// 1) Fill in all "known" dimensional axes based on known patterns and
+//    conventions
+//      * known internal axes (ZCT) have isCertain == true
+//      * known dimensional axes have a known pattern or convention
+//    After that, we are left with only unknown slots, which we must guess.
+//
+// 2) First, we decide whether we really "believe" the reader. There is a
+//    special case where we may decide that it got Z and T mixed up:
+//      * if a Z block was found, but not a T block:
+//          if !isOrderCertain, and sizeZ > 1, and sizeT == 1, swap 'em
+//      * else if a T block was found, but not a Z block:
+//          if !isOrderCertain and sizeT > 1, and sizeZ == 1, swap 'em
+//    At this point, we can (have to) trust the internal ordering, and use it
+//    to decide how to fill in the remaining dimensional blocks.
+//
+// 3) Set canBeZ to true iff no Z block is assigned and sizeZ == 1.
+//    Set canBeT to true iff no T block is assigned and sizeT == 1.
+//    Go through the blocks in order from left to right:
+//      * If canBeZ, assign Z and set canBeZ to false.
+//      * If canBeT, assign T and set canBeT to false.
+//      * Otherwise, assign C.
+//
+// OUTPUTS: list of axis assignments, new dimOrder
diff --git a/loci/formats/ChannelFiller.java b/loci/formats/ChannelFiller.java
new file mode 100644
index 0000000..f376d62
--- /dev/null
+++ b/loci/formats/ChannelFiller.java
@@ -0,0 +1,142 @@
+//
+// ChannelFiller.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+/**
+ * Expands indexed color images to RGB.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ChannelFiller.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ChannelFiller.java">SVN</a></dd></dl>
+ */
+public class ChannelFiller extends ReaderWrapper {
+
+  // -- Constructors --
+
+  /** Constructs a ChannelFiller around a new image reader. */
+  public ChannelFiller() { super(); }
+
+  /** Constructs a ChannelFiller with a given reader. */
+  public ChannelFiller(IFormatReader r) { super(r); }
+
+  // -- IFormatReader API methods --
+
+  /* @see IFormatReader#isIndexed() */
+  public boolean isIndexed() {
+    return false;
+  }
+
+  /* @see IFormatReader#isFalseColor() */
+  public boolean isFalseColor() {
+    return false;
+  }
+
+  /* @see IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() {
+    try {
+      return reader.isFalseColor() ? reader.get8BitLookupTable() : null;
+    }
+    catch (FormatException e) { }
+    catch (IOException e) { }
+    return null;
+  }
+
+  /* @see IFormatReader#get16BitLookupTable() */
+  public short[][] get16BitLookupTable() {
+    try {
+      return reader.isFalseColor() ? reader.get16BitLookupTable() : null;
+    }
+    catch (FormatException e) { }
+    catch (IOException e) { }
+    return null;
+  }
+
+  /* @see IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    if (reader.isIndexed() && !reader.isFalseColor()) {
+      if (getPixelType() == FormatTools.UINT8) {
+        byte[][] b = ImageTools.indexedToRGB(reader.get8BitLookupTable(),
+          reader.openBytes(no));
+        byte[] rtn = new byte[b.length * b[0].length];
+        if (isInterleaved()) {
+          int pt = 0;
+          for (int i=0; i<b[0].length; i++) {
+            for (int j=0; j<b.length; j++) {
+              rtn[pt++] = b[j][i];
+            }
+          }
+        }
+        else {
+          for (int i=0; i<b.length; i++) {
+            System.arraycopy(b[i], 0, rtn, i*b[i].length, b[i].length);
+          }
+        }
+        return rtn;
+      }
+      else {
+        short[][] s = ImageTools.indexedToRGB(reader.get16BitLookupTable(),
+          reader.openBytes(no), isLittleEndian());
+        byte[] rtn = new byte[s.length * s[0].length * 2];
+
+        if (isInterleaved()) {
+          int pt = 0;
+          for (int i=0; i<s[0].length; i++) {
+            for (int j=0; j<s.length; j++) {
+              rtn[pt++] = (byte) (isLittleEndian() ?
+                (s[j][i] & 0xff) : (s[j][i] >> 8));
+              rtn[pt++] = (byte) (isLittleEndian() ?
+                (s[j][i] >> 8) : (s[j][i] & 0xff));
+            }
+          }
+        }
+        else {
+          int pt = 0;
+          for (int i=0; i<s.length; i++) {
+            for (int j=0; j<s[i].length; j++) {
+              rtn[pt++] = (byte) (isLittleEndian() ?
+                (s[j][i] & 0xff) : (s[j][i] >> 8));
+              rtn[pt++] = (byte) (isLittleEndian() ?
+                (s[j][i] >> 8) : (s[j][i] & 0xff));
+            }
+          }
+        }
+        return rtn;
+      }
+    }
+    return reader.openBytes(no);
+  }
+
+  /* @see IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    if (reader.isIndexed() && !reader.isFalseColor()) {
+      return ImageTools.indexedToRGB(reader.openImage(no), isLittleEndian());
+    }
+    return reader.openImage(no);
+  }
+
+}
diff --git a/loci/formats/ChannelMerger.java b/loci/formats/ChannelMerger.java
new file mode 100644
index 0000000..af7bb74
--- /dev/null
+++ b/loci/formats/ChannelMerger.java
@@ -0,0 +1,148 @@
+//
+// ChannelMerger.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+
+/**
+ * Logic to automatically merge channels in a file.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ChannelMerger.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ChannelMerger.java">SVN</a></dd></dl>
+ */
+public class ChannelMerger extends ReaderWrapper {
+
+  // -- Constructor --
+
+  /** Constructs a ChannelMerger around a new image reader. */
+  public ChannelMerger() { super(); }
+
+  /** Constructs a ChannelMerger with the given reader. */
+  public ChannelMerger(IFormatReader r) { super(r); }
+
+  // -- ChannelMerger API methods --
+
+  /** Determines whether the channels in the file can be merged. */
+  public boolean canMerge() {
+    int c = getSizeC();
+    return c > 1 && c <= 4 && !reader.isRGB() && !reader.isIndexed();
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see IFormatReader#getImageCount() */
+  public int getImageCount() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    int no = reader.getImageCount();
+    if (canMerge()) no /= getSizeC();
+    return no;
+  }
+
+  /* @see IFormatReader#getDimensionOrder() */
+  public String getDimensionOrder() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    String order = reader.getDimensionOrder();
+    if (canMerge()) {
+      StringBuffer sb = new StringBuffer(order);
+      while (order.indexOf("C") != 2) {
+        char pre = order.charAt(order.indexOf("C") - 1);
+        sb.setCharAt(order.indexOf("C"), pre);
+        sb.setCharAt(order.indexOf(pre), 'C');
+        order = sb.toString();
+      }
+    }
+    return order;
+  }
+
+  /* @see IFormatReader#isRGB() */
+  public boolean isRGB() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return canMerge() || reader.isRGB();
+  }
+
+  /* @see IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    if (!canMerge()) return super.openImage(no);
+    int sizeC = getSizeC();
+    int[] nos = getZCTCoords(no);
+
+    int z = nos[0], t = nos[2];
+    String order = reader.getDimensionOrder();
+    int ic = order.indexOf("C") - 2;
+    if (ic < 0 || ic > 2) {
+      throw new FormatException("Invalid dimension order: " + order);
+    }
+    BufferedImage[] img = new BufferedImage[sizeC];
+    for (int c=0; c<sizeC; c++) {
+      img[c] = reader.openImage(reader.getIndex(z, c, t));
+    }
+    return ImageTools.mergeChannels(img);
+  }
+
+  /**
+   * Obtains the specified image from the given file as a byte array.
+   * For convenience, the channels are sequential, i.e. "RRR...GGG...BBB".
+   */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    if (!canMerge()) return super.openBytes(no);
+    int sizeC = getSizeC();
+    int[] nos = getZCTCoords(no);
+    int z = nos[0], t = nos[2];
+    byte[] bytes = null;
+    for (int c=0; c<sizeC; c++) {
+      byte[] b = reader.openBytes(reader.getIndex(z, c, t));
+
+      if (c == 0) {
+        // assume array lengths for each channel are equal
+        bytes = new byte[sizeC * b.length];
+      }
+      System.arraycopy(b, 0, bytes, c * b.length, b.length);
+    }
+    return bytes;
+  }
+
+  /* @see IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    if (!canMerge()) return super.openThumbImage(no);
+    return ImageTools.scale(openImage(no), getThumbSizeX(),
+      getThumbSizeY(), true);
+  }
+
+  public int getIndex(int z, int c, int t) {
+    return FormatTools.getIndex(this, z, c, t);
+  }
+
+  public int[] getZCTCoords(int index) {
+    return FormatTools.getZCTCoords(this, index);
+  }
+
+}
diff --git a/loci/formats/ChannelSeparator.java b/loci/formats/ChannelSeparator.java
new file mode 100644
index 0000000..57883d0
--- /dev/null
+++ b/loci/formats/ChannelSeparator.java
@@ -0,0 +1,180 @@
+//
+// ChannelSeparator.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+/**
+ * Logic to automatically separate the channels in a file.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ChannelSeparator.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ChannelSeparator.java">SVN</a></dd></dl>
+ */
+public class ChannelSeparator extends ReaderWrapper {
+
+  // -- Fields --
+
+  /** Last image opened. */
+  private byte[] lastImage;
+
+  /** Index of last image opened. */
+  private int lastImageIndex = -1;
+
+  /** Series of last image opened. */
+  private int lastImageSeries = -1;
+
+  // -- Constructors --
+
+  /** Constructs a ChannelSeparator around a new image reader. */
+  public ChannelSeparator() { super(); }
+
+  /** Constructs a ChannelSeparator with the given reader. */
+  public ChannelSeparator(IFormatReader r) { super(r); }
+
+  // -- IFormatReader API methods --
+
+  /* @see IFormatReader#setId(String) */
+  public void setId(String id) throws FormatException, IOException {
+    super.setId(id);
+
+    // clear last image cache
+    lastImage = null;
+    lastImageIndex = -1;
+    lastImageSeries = -1;
+  }
+
+  /* @see IFormatReader#setId(id, force) */
+  public void setId(String id, boolean force)
+    throws FormatException, IOException
+  {
+    super.setId(id, force);
+
+    // clear last image cache
+    lastImage = null;
+    lastImageIndex = -1;
+    lastImageSeries = -1;
+  }
+
+  /* @see IFormatReader#getImageCount() */
+  public int getImageCount() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return (reader.isRGB() && !reader.isIndexed()) ?
+      (getSizeC() / reader.getEffectiveSizeC()) * reader.getImageCount() :
+      reader.getImageCount();
+  }
+
+  /* @see IFormatReader#getDimensionOrder() */
+  public String getDimensionOrder() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    String order = super.getDimensionOrder();
+    if (reader.isRGB() && !reader.isIndexed()) {
+      String newOrder = "XYC";
+      if (order.indexOf("Z") > order.indexOf("T")) newOrder += "TZ";
+      else newOrder += "ZT";
+      return newOrder;
+    }
+    return order;
+  }
+
+  /* @see IFormatReader#isRGB() */
+  public boolean isRGB() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return isIndexed() && !isFalseColor();
+  }
+
+  /* @see IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    FormatTools.checkPlaneNumber(this, no);
+
+    if (isIndexed()) return reader.openImage(no);
+
+    int bytes = FormatTools.getBytesPerPixel(getPixelType());
+
+    byte[] b = openBytes(no);
+
+    if (getPixelType() == FormatTools.FLOAT) {
+      float[] f =
+        (float[]) DataTools.makeDataArray(b, 4, true, isLittleEndian());
+      if (isNormalized()) f = DataTools.normalizeFloats(f);
+      return ImageTools.makeImage(f, getSizeX(), getSizeY());
+    }
+
+    return ImageTools.makeImage(b, getSizeX(), getSizeY(), 1, false,
+      bytes, isLittleEndian());
+  }
+
+  /* @see IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    FormatTools.checkPlaneNumber(this, no);
+
+    if (reader.isRGB() && !reader.isIndexed()) {
+      int c = getSizeC() / reader.getEffectiveSizeC();
+      int source = no / c;
+      int channel = no % c;
+      int series = getSeries();
+
+      if (source != lastImageIndex || series != lastImageSeries) {
+        lastImage = reader.openBytes(source);
+        lastImageIndex = source;
+        lastImageSeries = series;
+      }
+
+      return ImageTools.splitChannels(lastImage, c,
+        FormatTools.getBytesPerPixel(getPixelType()),
+        false, !isInterleaved())[channel];
+    }
+    else return reader.openBytes(no);
+  }
+
+  /* @see IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return ImageTools.scale(openImage(no), getThumbSizeX(),
+      getThumbSizeY(), true);
+  }
+
+  /* @see IFormatReader#close() */
+  public void close() throws IOException {
+    super.close();
+    lastImage = null;
+    lastImageIndex = -1;
+    lastImageSeries = -1;
+  }
+
+  public int getIndex(int z, int c, int t) {
+    return FormatTools.getIndex(this, z, c, t);
+  }
+
+  public int[] getZCTCoords(int index) {
+    return FormatTools.getZCTCoords(this, index);
+  }
+
+}
diff --git a/loci/formats/ClassList.java b/loci/formats/ClassList.java
new file mode 100644
index 0000000..6b97807
--- /dev/null
+++ b/loci/formats/ClassList.java
@@ -0,0 +1,143 @@
+//
+// ClassList.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Vector;
+
+/**
+ * ClassList is a list of classes for use with ImageReader or ImageWriter,
+ * parsed from a configuration file such as readers.txt or writers.txt.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ClassList.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ClassList.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class ClassList {
+
+  // -- Fields --
+
+  /** Base class to which all classes are assignable. */
+  private Class base;
+
+  /** List of classes. */
+  private Vector classes;
+
+  // -- Constructor --
+
+  /**
+   * Constructs a list of classes, initially empty.
+   * @param base Base class to which all classes are assignable.
+   */
+  public ClassList(Class base) {
+    this.base = base;
+    classes = new Vector();
+  }
+
+  /**
+   * Constructs a list of classes from the given configuration file.
+   * @param file Configuration file containing the list of classes.
+   * @param base Base class to which all classes are assignable.
+   * @throws IOException if the file cannot be read.
+   */
+  public ClassList(String file, Class base) throws IOException {
+    this.base = base;
+    classes = new Vector();
+    if (file == null) return;
+
+    // read classes from file
+    BufferedReader in = new BufferedReader(new InputStreamReader(
+      getClass().getResourceAsStream(file)));
+    while (true) {
+      String line = null;
+      line = in.readLine();
+      if (line == null) break;
+
+      // ignore characters following # sign (comments)
+      int ndx = line.indexOf("#");
+      if (ndx >= 0) line = line.substring(0, ndx);
+      line = line.trim();
+      if (line.equals("")) continue;
+
+      // load reader class
+      Class c = null;
+      try { c = Class.forName(line); }
+      catch (ClassNotFoundException exc) {
+        if (FormatHandler.debug) LogTools.trace(exc);
+      }
+      catch (NoClassDefFoundError err) {
+        if (FormatHandler.debug) LogTools.trace(err);
+      }
+      catch (ExceptionInInitializerError err) {
+        if (FormatHandler.debug) LogTools.trace(err);
+      }
+      catch (RuntimeException exc) {
+        // HACK: workaround for bug in Apache Axis2
+        String msg = exc.getMessage();
+        if (msg != null && msg.indexOf("ClassNotFound") < 0) throw exc;
+        if (FormatHandler.debug) LogTools.trace(exc);
+      }
+      if (c == null || (base != null && !base.isAssignableFrom(c))) {
+        LogTools.println("Error: \"" + line + "\" is not valid.");
+        continue;
+      }
+      classes.add(c);
+    }
+    in.close();
+  }
+
+  // -- ClassList API methods --
+
+  /**
+   * Adds the given class, which must be assignable
+   * to the base class, to the list.
+   *
+   * @throws FormatException if the class is not assignable to the base class.
+   */
+  public void addClass(Class c) throws FormatException {
+    if (base != null && !base.isAssignableFrom(c)) {
+      throw new FormatException(
+        "Class is not assignable to the base class");
+    }
+    classes.add(c);
+  }
+
+  /** Removes the given class from the list. */
+  public void removeClass(Class c) {
+    classes.remove(c);
+  }
+
+  /** Gets the list of classes as an array. */
+  public Class[] getClasses() {
+    Class[] c = new Class[classes.size()];
+    classes.copyInto(c);
+    return c;
+  }
+
+}
diff --git a/loci/formats/CoreMetadata.java b/loci/formats/CoreMetadata.java
new file mode 100644
index 0000000..61c79ea
--- /dev/null
+++ b/loci/formats/CoreMetadata.java
@@ -0,0 +1,72 @@
+//
+// CoreMetadata.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.util.Hashtable;
+
+/**
+ * Encompasses core metadata values.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/CoreMetadata.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/CoreMetadata.java">SVN</a></dd></dl>
+ */
+public class CoreMetadata {
+  public int[] sizeX, sizeY, sizeZ, sizeC, sizeT;
+  public int[] thumbSizeX, thumbSizeY;
+  public int[] pixelType;
+  public int[] imageCount;
+  public int[][] cLengths;
+  public String[][] cTypes;
+  public String[] currentOrder;
+  public boolean[] orderCertain, rgb, littleEndian, interleaved;
+  public boolean[] indexed, falseColor, metadataComplete;
+  public Hashtable[] seriesMetadata;
+
+  public CoreMetadata(int series) {
+    sizeX = new int[series];
+    sizeY = new int[series];
+    sizeZ = new int[series];
+    sizeC = new int[series];
+    sizeT = new int[series];
+    thumbSizeX = new int[series];
+    thumbSizeY = new int[series];
+    pixelType = new int[series];
+    imageCount = new int[series];
+    cLengths = new int[series][];
+    cTypes = new String[series][];
+    currentOrder = new String[series];
+    orderCertain = new boolean[series];
+    rgb = new boolean[series];
+    littleEndian = new boolean[series];
+    interleaved = new boolean[series];
+    indexed = new boolean[series];
+    falseColor = new boolean[series];
+    metadataComplete = new boolean[series];
+    seriesMetadata = new Hashtable[series]; for (int i=0; i<series; i++)
+    seriesMetadata[i] = new Hashtable();
+  }
+
+}
diff --git a/loci/formats/DataTools.java b/loci/formats/DataTools.java
new file mode 100644
index 0000000..54a6aca
--- /dev/null
+++ b/loci/formats/DataTools.java
@@ -0,0 +1,550 @@
+//
+// DataTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+import java.text.*;
+import java.util.Date;
+
+/**
+ * A utility class with convenience methods for
+ * reading, writing and decoding words.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/DataTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/DataTools.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Chris Allan callan at blackcat.ca
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public final class DataTools {
+
+  // -- Constants --
+
+  /** Timestamp formats. */
+  public static final int UNIX = 0;  // January 1, 1970
+  public static final int COBOL = 1;  // January 1, 1601
+
+  /** Milliseconds until UNIX epoch. */
+  public static final long UNIX_EPOCH = 0;
+  public static final long COBOL_EPOCH = 11644444800000L;
+
+  // -- Static fields --
+
+  /**
+   * Persistent byte array for calling
+   * {@link java.io.DataInput#readFully(byte[], int, int)} efficiently.
+   */
+  private static ThreadLocal eightBytes = new ThreadLocal() {
+    protected synchronized Object initialValue() {
+      return new byte[8];
+    }
+  };
+
+  // -- Constructor --
+
+  private DataTools() { }
+
+  // -- Data reading --
+
+  /** Reads 1 signed byte [-128, 127]. */
+  public static byte readSignedByte(DataInput in) throws IOException {
+    byte[] b = (byte[]) eightBytes.get();
+    in.readFully(b, 0, 1);
+    return b[0];
+  }
+
+  /** Reads 1 unsigned byte [0, 255]. */
+  public static short readUnsignedByte(DataInput in) throws IOException {
+    short q = readSignedByte(in);
+    if (q < 0) q += 256;
+    return q;
+  }
+
+  /** Reads 2 signed bytes [-32768, 32767]. */
+  public static short read2SignedBytes(DataInput in, boolean little)
+    throws IOException
+  {
+    byte[] b = (byte[]) eightBytes.get();
+    in.readFully(b, 0, 2);
+    return bytesToShort(b, little);
+  }
+
+  /** Reads 2 unsigned bytes [0, 65535]. */
+  public static int read2UnsignedBytes(DataInput in, boolean little)
+    throws IOException
+  {
+    int q = read2SignedBytes(in, little);
+    if (q < 0) q += 65536;
+    return q;
+  }
+
+  /** Reads 4 signed bytes [-2147483648, 2147483647]. */
+  public static int read4SignedBytes(DataInput in, boolean little)
+    throws IOException
+  {
+    byte[] b = (byte[]) eightBytes.get();
+    in.readFully(b, 0, 4);
+    return bytesToInt(b, little);
+  }
+
+  /** Reads 4 unsigned bytes [0, 4294967296]. */
+  public static long read4UnsignedBytes(DataInput in, boolean little)
+    throws IOException
+  {
+    long q = read4SignedBytes(in, little);
+    if (q < 0) q += 4294967296L;
+    return q;
+  }
+
+  /** Reads 8 signed bytes [-9223372036854775808, 9223372036854775807]. */
+  public static long read8SignedBytes(DataInput in, boolean little)
+    throws IOException
+  {
+    byte[] b = (byte[]) eightBytes.get();
+    in.readFully(b, 0, 8);
+    return bytesToLong(b, little);
+  }
+
+  /** Reads 4 bytes in single precision IEEE format. */
+  public static float readFloat(DataInput in, boolean little)
+    throws IOException
+  {
+    return Float.intBitsToFloat(read4SignedBytes(in, little));
+  }
+
+  /** Reads 8 bytes in double precision IEEE format. */
+  public static double readDouble(DataInput in, boolean little)
+    throws IOException
+  {
+    return Double.longBitsToDouble(read8SignedBytes(in, little));
+  }
+
+  // -- Data writing --
+
+  /** Writes a string to the given data output destination. */
+  public static void writeString(DataOutput out, String s)
+    throws IOException
+  {
+    byte[] b =  s.getBytes("UTF-8");
+    out.write(b);
+  }
+
+  /** Writes an integer to the given data output destination. */
+  public static void writeInt(DataOutput out, int v, boolean little)
+    throws IOException
+  {
+    if (little) {
+      out.write(v & 0xFF);
+      out.write((v >>> 8) & 0xFF);
+      out.write((v >>> 16) & 0xFF);
+      out.write((v >>> 24) & 0xFF);
+    }
+    else {
+      out.write((v >>> 24) & 0xFF);
+      out.write((v >>> 16) & 0xFF);
+      out.write((v >>> 8) & 0xFF);
+      out.write(v & 0xFF);
+    }
+  }
+
+  /** Writes a short to the given data output destination. */
+  public static void writeShort(DataOutput out, int v, boolean little)
+    throws IOException
+  {
+    if (little) {
+      out.write(v & 0xFF);
+      out.write((v >>> 8) & 0xFF);
+    }
+    else {
+      out.write((v >>> 8) & 0xFF);
+      out.write(v & 0xFF);
+    }
+  }
+
+  // -- Word decoding --
+
+  /**
+   * Translates up to the first len bytes of a byte array beyond the given
+   * offset to a short. If there are fewer than 2 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static short bytesToShort(byte[] bytes, int off, int len,
+    boolean little)
+  {
+    if (bytes.length - off < len) len = bytes.length - off;
+    short total = 0;
+    for (int i=0, ndx=off; i<len; i++, ndx++) {
+      total |= (bytes[ndx] < 0 ? 256 + bytes[ndx] :
+        (int) bytes[ndx]) << ((little ? i : len - i - 1) * 8);
+    }
+    return total;
+  }
+
+  /**
+   * Translates up to the first 2 bytes of a byte array beyond the given
+   * offset to a short. If there are fewer than 2 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static short bytesToShort(byte[] bytes, int off, boolean little) {
+    return bytesToShort(bytes, off, 2, little);
+  }
+
+  /**
+   * Translates up to the first 2 bytes of a byte array to a short.
+   * If there are fewer than 2 bytes in the array, the MSBs are all
+   * assumed to be zero (regardless of endianness).
+   */
+  public static short bytesToShort(byte[] bytes, boolean little) {
+    return bytesToShort(bytes, 0, 2, little);
+  }
+
+  /**
+   * Translates up to the first len bytes of a byte array byond the given
+   * offset to a short. If there are fewer than 2 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static short bytesToShort(short[] bytes, int off, int len,
+    boolean little)
+  {
+    if (bytes.length - off < len) len = bytes.length - off;
+    short total = 0;
+    for (int i=0, ndx=off; i<len; i++, ndx++) {
+      total |= ((int) bytes[ndx]) << ((little ? i : len - i - 1) * 8);
+    }
+    return total;
+  }
+
+  /**
+   * Translates up to the first 2 bytes of a byte array byond the given
+   * offset to a short. If there are fewer than 2 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static short bytesToShort(short[] bytes, int off, boolean little) {
+    return bytesToShort(bytes, off, 2, little);
+  }
+
+  /**
+   * Translates up to the first 2 bytes of a byte array to a short.
+   * If there are fewer than 2 bytes in the array, the MSBs are all
+   * assumed to be zero (regardless of endianness).
+   */
+  public static short bytesToShort(short[] bytes, boolean little) {
+    return bytesToShort(bytes, 0, 2, little);
+  }
+
+  /**
+   * Translates up to the first len bytes of a byte array beyond the given
+   * offset to an int. If there are fewer than 4 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static int bytesToInt(byte[] bytes, int off, int len,
+    boolean little)
+  {
+    if (bytes.length - off < len) len = bytes.length - off;
+    int total = 0;
+    for (int i=0, ndx=off; i<len; i++, ndx++) {
+      total |= (bytes[ndx] < 0 ? 256 + bytes[ndx] :
+        (int) bytes[ndx]) << ((little ? i : len - i - 1) * 8);
+    }
+    return total;
+  }
+
+  /**
+   * Translates up to the first 4 bytes of a byte array beyond the given
+   * offset to an int. If there are fewer than 4 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static int bytesToInt(byte[] bytes, int off, boolean little) {
+    return bytesToInt(bytes, off, 4, little);
+  }
+
+  /**
+   * Translates up to the first 4 bytes of a byte array to an int.
+   * If there are fewer than 4 bytes in the array, the MSBs are all
+   * assumed to be zero (regardless of endianness).
+   */
+  public static int bytesToInt(byte[] bytes, boolean little) {
+    return bytesToInt(bytes, 0, 4, little);
+  }
+
+  /**
+   * Translates up to the first len bytes of a byte array beyond the given
+   * offset to an int. If there are fewer than 4 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static int bytesToInt(short[] bytes, int off, int len,
+    boolean little)
+  {
+    if (bytes.length - off < len) len = bytes.length - off;
+    int total = 0;
+    for (int i=0, ndx=off; i<len; i++, ndx++) {
+      total |= ((int) bytes[ndx]) << ((little ? i : len - i - 1) * 8);
+    }
+    return total;
+  }
+
+  /**
+   * Translates up to the first 4 bytes of a byte array beyond the given
+   * offset to an int. If there are fewer than 4 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static int bytesToInt(short[] bytes, int off, boolean little) {
+    return bytesToInt(bytes, off, 4, little);
+  }
+
+  /**
+   * Translates up to the first 4 bytes of a byte array to an int.
+   * If there are fewer than 4 bytes in the array, the MSBs are all
+   * assumed to be zero (regardless of endianness).
+   */
+  public static int bytesToInt(short[] bytes, boolean little) {
+    return bytesToInt(bytes, 0, 4, little);
+  }
+
+  /**
+   * Translates up to the first len bytes of a byte array beyond the given
+   * offset to a long. If there are fewer than 8 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static long bytesToLong(byte[] bytes, int off, int len,
+    boolean little)
+  {
+    if (bytes.length - off < len) len = bytes.length - off;
+    long total = 0;
+    for (int i=0, ndx=off; i<len; i++, ndx++) {
+      total |= (bytes[ndx] < 0 ? 256L + bytes[ndx] :
+        (long) bytes[ndx]) << ((little ? i : len - i - 1) * 8);
+    }
+    return total;
+  }
+
+  /**
+   * Translates up to the first 8 bytes of a byte array beyond the given
+   * offset to a long. If there are fewer than 8 bytes in the array,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static long bytesToLong(byte[] bytes, int off, boolean little) {
+    return bytesToLong(bytes, off, 8, little);
+  }
+
+  /**
+   * Translates up to the first 8 bytes of a byte array to a long.
+   * If there are fewer than 8 bytes in the array, the MSBs are all
+   * assumed to be zero (regardless of endianness).
+   */
+  public static long bytesToLong(byte[] bytes, boolean little) {
+    return bytesToLong(bytes, 0, 8, little);
+  }
+
+  /**
+   * Translates up to the first len bytes of a byte array beyond the given
+   * offset to a long. If there are fewer than 8 bytes to be translated,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static long bytesToLong(short[] bytes, int off, int len,
+    boolean little)
+  {
+    if (bytes.length - off < len) len = bytes.length - off;
+    long total = 0;
+    for (int i=0, ndx=off; i<len; i++, ndx++) {
+      total |= ((long) bytes[ndx]) << ((little ? i : len - i - 1) * 8);
+    }
+    return total;
+  }
+
+  /**
+   * Translates up to the first 8 bytes of a byte array beyond the given
+   * offset to a long. If there are fewer than 8 bytes to be translated,
+   * the MSBs are all assumed to be zero (regardless of endianness).
+   */
+  public static long bytesToLong(short[] bytes, int off, boolean little) {
+    return bytesToLong(bytes, off, 8, little);
+  }
+
+  /**
+   * Translates up to the first 8 bytes of a byte array to a long.
+   * If there are fewer than 8 bytes in the array, the MSBs are all
+   * assumed to be zero (regardless of endianness).
+   */
+  public static long bytesToLong(short[] bytes, boolean little) {
+    return bytesToLong(bytes, 0, 8, little);
+  }
+
+  /**
+   * Convert a byte array to the appropriate primitive type array.
+   * @param b Byte array to convert.
+   * @param bpp Denotes the number of bytes in the returned primitive type
+   *   (e.g. if bpp == 2, we should return an array of type short).
+   * @param fp If set and bpp == 4 or bpp == 8, then return floats or doubles.
+   * @param little Whether byte array is in little-endian order.
+   */
+  public static Object makeDataArray(byte[] b,
+    int bpp, boolean fp, boolean little)
+  {
+    if (bpp == 1) return b;
+    else if (bpp == 2) {
+      short[] s = new short[b.length / 2];
+      for (int i=0; i<s.length; i++) {
+        s[i] = bytesToShort(b, i*2, 2, little);
+      }
+      return s;
+    }
+    else if (bpp == 4 && fp) {
+      float[] f = new float[b.length / 4];
+      for (int i=0; i<f.length; i++) {
+        f[i] = Float.intBitsToFloat(bytesToInt(b, i*4, 4, little));
+      }
+      return f;
+    }
+    else if (bpp == 4) {
+      int[] i = new int[b.length / 4];
+      for (int j=0; j<i.length; j++) {
+        i[j] = bytesToInt(b, j*4, 4, little);
+      }
+      return i;
+    }
+    else if (bpp == 8 && fp) {
+      double[] d = new double[b.length / 8];
+      for (int i=0; i<d.length; i++) {
+        d[i] = Double.longBitsToDouble(bytesToLong(b, i*8, 8, little));
+      }
+      return d;
+    }
+    else if (bpp == 8) {
+      long[] l = new long[b.length / 8];
+      for (int i=0; i<l.length; i++) {
+        l[i] = bytesToLong(b, i*8, 8, little);
+      }
+      return l;
+    }
+    return null;
+  }
+
+  // -- Byte swapping --
+
+  public static short swap(short x) {
+    return (short) ((x << 8) | ((x >> 8) & 0xFF));
+  }
+
+  public static char swap(char x) {
+    return (char) ((x << 8) | ((x >> 8) & 0xFF));
+  }
+
+  public static int swap(int x) {
+    return (int) ((swap((short) x) << 16) | (swap((short) (x >> 16)) & 0xFFFF));
+  }
+
+  public static long swap(long x) {
+    return (long) (((long) swap((int) x) << 32) |
+      ((long) swap((int) (x >> 32)) & 0xFFFFFFFFL));
+  }
+
+  // -- Miscellaneous --
+
+  /** Remove null bytes from a string. */
+  public static String stripString(String toStrip) {
+    char[] toRtn = new char[toStrip.length()];
+    int counter = 0;
+    for (int i=0; i<toRtn.length; i++) {
+      if (toStrip.charAt(i) != 0) {
+        toRtn[counter] = toStrip.charAt(i);
+        counter++;
+      }
+    }
+    toStrip = new String(toRtn);
+    toStrip = toStrip.trim();
+    return toStrip;
+  }
+
+  /** Check if two filenames have the same prefix. */
+  public static boolean samePrefix(String s1, String s2) {
+    if (s1 == null || s2 == null) return false;
+    int n1 = s1.indexOf(".");
+    int n2 = s2.indexOf(".");
+    if ((n1 == -1) || (n2 == -1)) return false;
+
+    int slash1 = s1.lastIndexOf(File.pathSeparator);
+    int slash2 = s2.lastIndexOf(File.pathSeparator);
+
+    String sub1 = s1.substring((slash1 == -1) ? 0 : slash1 + 1, n1);
+    String sub2 = s2.substring((slash2 == -1) ? 0 : slash2 + 1, n2);
+    return sub1.equals(sub2) || sub1.startsWith(sub2) || sub2.startsWith(sub1);
+  }
+
+  /**
+   * Normalize the given float array so that the minimum value maps to 0.0
+   * and the maximum value maps to 1.0.
+   */
+  public static float[] normalizeFloats(float[] data) {
+    float[] rtn = new float[data.length];
+
+    // make a quick pass through to determine the real min and max values
+
+    float min = Float.MAX_VALUE;
+    float max = Float.MIN_VALUE;
+
+    for (int i=0; i<data.length; i++) {
+      if (data[i] < min) min = data[i];
+      if (data[i] > max) {
+        max = data[i];
+      }
+    }
+
+    // now normalize; min => 0.0, max => 1.0
+
+    for (int i=0; i<rtn.length; i++) {
+      rtn[i] = (data[i] - min) / (max - min);
+    }
+
+    return rtn;
+  }
+
+  // -- Date handling --
+
+  /** Converts the given timestamp into an ISO 8061 date. */
+  public static String convertDate(long stamp, int format) {
+    // see http://www.merlyn.demon.co.uk/critdate.htm for more information on
+    // dates than you will ever need (or want)
+
+    long ms = stamp;
+
+    switch (format) {
+      case COBOL:
+        ms -= COBOL_EPOCH;
+        break;
+    }
+
+    SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+    StringBuffer sb = new StringBuffer();
+
+    Date d = new Date(ms);
+
+    fmt.format(d, sb, new FieldPosition(0));
+    return sb.toString();
+  }
+
+}
diff --git a/loci/formats/DimensionSwapper.java b/loci/formats/DimensionSwapper.java
new file mode 100644
index 0000000..33a10e4
--- /dev/null
+++ b/loci/formats/DimensionSwapper.java
@@ -0,0 +1,154 @@
+//
+// DimensionSwapper.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * Handles swapping the dimension order of a file.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/DimensionSwapper.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/DimensionSwapper.java">SVN</a></dd></dl>
+ */
+public class DimensionSwapper extends ReaderWrapper {
+
+  // -- Constructors --
+
+  /** Constructs a DimensionSwapper around a new image reader. */
+  public DimensionSwapper() { super(); }
+
+  /** Constructs a DimensionSwapper with the given reader. */
+  public DimensionSwapper(IFormatReader r) { super(r); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#getSizeX() */
+  public int getSizeX() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return getCoreMetadata().sizeX[getSeries()];
+  }
+
+  /* @see loci.formats.IFormatReader#getSizeY() */
+  public int getSizeY() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return getCoreMetadata().sizeY[getSeries()];
+  }
+
+  /* @see loci.formats.IFormatReader#getSizeZ() */
+  public int getSizeZ() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return getCoreMetadata().sizeZ[getSeries()];
+  }
+
+  /* @see loci.formats.IFormatReader#getSizeC() */
+  public int getSizeC() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return getCoreMetadata().sizeC[getSeries()];
+  }
+
+  /* @see loci.formats.IFormatReader#getSizeT() */
+  public int getSizeT() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return getCoreMetadata().sizeT[getSeries()];
+  }
+
+  /* @see loci.formats.IFormatReader#getDimensionOrder() */
+  public String getDimensionOrder() {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return getCoreMetadata().currentOrder[getSeries()];
+  }
+
+  // -- DimensionSwapper API methods --
+
+  /**
+   * Swaps the dimensions according to the given dimension order.  If the given
+   * order is identical to the file's native order, then nothing happens.
+   * Note that this method will throw an exception if X and Y do not appear in
+   * positions 0 and 1 (although X and Y can be reversed).
+   */
+  public void swapDimensions(String order) {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+
+    if (order == null) throw new IllegalArgumentException("order is null");
+
+    String oldOrder = getDimensionOrder();
+    if (order.equals(oldOrder)) return;
+
+    if (order.length() != 5) {
+      throw new IllegalArgumentException("order is unexpected length (" +
+        order.length() + ")");
+    }
+
+    int newX = order.indexOf("X");
+    int newY = order.indexOf("Y");
+    int newZ = order.indexOf("Z");
+    int newC = order.indexOf("C");
+    int newT = order.indexOf("T");
+
+    if (newX < 0) throw new IllegalArgumentException("X does not appear");
+    if (newY < 0) throw new IllegalArgumentException("Y does not appear");
+    if (newZ < 0) throw new IllegalArgumentException("Z does not appear");
+    if (newC < 0) throw new IllegalArgumentException("C does not appear");
+    if (newT < 0) throw new IllegalArgumentException("T does not appear");
+
+    if (newX > 1) {
+      throw new IllegalArgumentException("X in unexpected position (" +
+        newX + ")");
+    }
+    if (newY > 1) {
+      throw new IllegalArgumentException("Y in unexpected position (" +
+        newY + ")");
+    }
+
+    int[] dims = new int[5];
+
+    int oldX = oldOrder.indexOf("X");
+    int oldY = oldOrder.indexOf("Y");
+    int oldZ = oldOrder.indexOf("Z");
+    int oldC = oldOrder.indexOf("C");
+    int oldT = oldOrder.indexOf("T");
+
+    dims[oldX] = getSizeX();
+    dims[oldY] = getSizeY();
+    dims[oldZ] = getSizeZ();
+    dims[oldC] = getSizeC();
+    dims[oldT] = getSizeT();
+
+    int series = getSeries();
+    CoreMetadata core = getCoreMetadata();
+
+    core.sizeX[series] = dims[newX];
+    core.sizeY[series] = dims[newY];
+    core.sizeZ[series] = dims[newZ];
+    core.sizeC[series] = dims[newC];
+    core.sizeT[series] = dims[newT];
+    core.currentOrder[series] = order;
+
+    MetadataStore store = getMetadataStore();
+    store.setPixels(new Integer(dims[newX]), new Integer(dims[newY]),
+      new Integer(dims[newZ]), new Integer(dims[newC]),
+      new Integer(dims[newT]), null, null, order, new Integer(series), null);
+  }
+
+}
diff --git a/loci/formats/DummyMetadata.java b/loci/formats/DummyMetadata.java
new file mode 100644
index 0000000..e5be2cc
--- /dev/null
+++ b/loci/formats/DummyMetadata.java
@@ -0,0 +1,344 @@
+//
+// DummyMetadata.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * A dummy implementation for {@link MetadataStore} and
+ * {@link MetadataRetrieve} that is used when no other
+ * metadata implementations are available.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/DummyMetadata.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/DummyMetadata.java">SVN</a></dd></dl>
+ *
+ * @author Chris Allan callan at blackcat.ca
+ */
+public class DummyMetadata implements MetadataStore, MetadataRetrieve {
+
+  // -- MetadataRetrieve API methods --
+
+  public int getImageCount() { return 0; }
+  public String getImageName(Integer n) { return null; }
+  public String getCreationDate(Integer n) { return null; }
+  public String getDescription(Integer n) { return null; }
+
+  public int getExperimenterCount() { return 0; }
+  public String getFirstName(Integer n) { return null; }
+  public String getLastName(Integer n) { return null; }
+  public String getEmail(Integer n) { return null; }
+  public String getInstitution(Integer n) { return null; }
+  public String getDataDirectory(Integer n) { return null; }
+  public Object getGroup(Integer n) { return null; }
+
+  public int getGroupCount() { return 0; }
+  public String getGroupName(Integer n) { return null; }
+  public Object getLeader(Integer n) { return null; }
+  public Object getContact(Integer n) { return null; }
+
+  public int getInstrumentCount() { return 0; }
+  public String getManufacturer(Integer n) { return null; }
+  public String getModel(Integer n) { return null; }
+  public String getSerialNumber(Integer n) { return null; }
+  public String getType(Integer n) { return null; }
+
+  public Float getPixelSizeX(Integer n) { return null; }
+  public Float getPixelSizeY(Integer n) { return null; }
+  public Float getPixelSizeZ(Integer n) { return null; }
+  public Float getPixelSizeC(Integer n) { return null; }
+  public Float getPixelSizeT(Integer n) { return null; }
+
+  public int getDisplayROICount() { return 0; }
+  public Integer getX0(Integer n) { return null; }
+  public Integer getY0(Integer n) { return null; }
+  public Integer getZ0(Integer n) { return null; }
+  public Integer getT0(Integer n) { return null; }
+  public Integer getX1(Integer n) { return null; }
+  public Integer getY1(Integer n) { return null; }
+  public Integer getZ1(Integer n) { return null; }
+  public Integer getT1(Integer n) { return null; }
+  public Object getDisplayOptions(Integer n) { return null; }
+
+  public int getPixelsCount(Integer n) { return 0; }
+  public Integer getSizeX(Integer image) { return null; }
+  public Integer getSizeY(Integer image) { return null; }
+  public Integer getSizeZ(Integer image) { return null; }
+  public Integer getSizeC(Integer image) { return null; }
+  public Integer getSizeT(Integer image) { return null; }
+  public String getPixelType(Integer image) { return null; }
+  public Boolean getBigEndian(Integer image) { return null; }
+  public String getDimensionOrder(Integer image) { return null; }
+
+  public int getStageLabelCount() { return 0; }
+  public String getStageName(Integer n) { return null; }
+  public Float getStageX(Integer n) { return null; }
+  public Float getStageY(Integer n) { return null; }
+  public Float getStageZ(Integer n) { return null; }
+
+  public int getChannelCount(Integer n) { return 0; }
+  public String getChannelName(Integer pixels, Integer channel) { return null; }
+  public Float getChannelNDFilter(Integer pixels, Integer channel) {
+    return null;
+  }
+  public Integer getEmWave(Integer pixels, Integer channel) { return null; }
+  public Integer getExWave(Integer pixels, Integer channel) { return null; }
+  public String getPhotometricInterpretation(Integer pixels, Integer channel) {
+    return null;
+  }
+  public String getMode(Integer pixels, Integer channel) { return null; }
+  public Double getGlobalMin(Integer pixels, Integer channel) { return null; }
+  public Double getGlobalMax(Integer pixels, Integer channel) { return null; }
+
+  public Float getTimestamp(Integer pixels, Integer z, Integer c, Integer t) {
+    return null;
+  }
+  public Float getExposureTime(Integer pixels,
+    Integer z, Integer c, Integer t)
+  {
+    return null;
+  }
+
+  public Float getTemperature(Integer n) { return null; }
+  public Float getAirPressure(Integer n) { return null; }
+  public Float getHumidity(Integer n) { return null; }
+  public Float getCO2Percent(Integer n) { return null; }
+
+  public Double getBlackLevel(Integer pixels, Integer channel) { return null; }
+  public Double getWhiteLevel(Integer pixels, Integer channel) { return null; }
+  public Float getGamma(Integer pixels, Integer channel) { return null; }
+
+  public Float getZoom(Integer image) { return null; }
+  public Boolean isRedChannelOn(Integer image) { return null; }
+  public Boolean isGreenChannelOn(Integer image) { return null; }
+  public Boolean isBlueChannelOn(Integer image) { return null; }
+  public Boolean isDisplayRGB(Integer image) { return null; }
+  public String getColorMap(Integer image) { return null; }
+  public Integer getZStart(Integer image) { return null; }
+  public Integer getZStop(Integer image) { return null; }
+  public Integer getTStart(Integer image) { return null; }
+  public Integer getTStop(Integer image) { return null; }
+
+  public String getLightManufacturer(Integer light) { return null; }
+  public String getLightModel(Integer light) { return null; }
+  public String getLightSerial(Integer light) { return null; }
+
+  public String getLaserType(Integer laser) { return null; }
+  public String getLaserMedium(Integer laser) { return null; }
+  public Integer getLaserWavelength(Integer laser) { return null; }
+  public Boolean isFrequencyDoubled(Integer laser) { return null; }
+  public Boolean isTunable(Integer laser) { return null; }
+  public String getPulse(Integer laser) { return null; }
+  public Float getPower(Integer laser) { return null; }
+
+  public String getFilamentType(Integer filament) { return null; }
+  public Float getFilamentPower(Integer filament) { return null; }
+
+  public String getArcType(Integer arc) { return null; }
+  public Float getArcPower(Integer arc) { return null; }
+
+  public String getDetectorManufacturer(Integer detector) { return null; }
+  public String getDetectorModel(Integer detector) { return null; }
+  public String getDetectorSerial(Integer detector) { return null; }
+  public String getDetectorType(Integer detector) { return null; }
+  public Float getDetectorGain(Integer detector) { return null; }
+  public Float getDetectorVoltage(Integer detector) { return null; }
+  public Float getDetectorOffset(Integer detector) { return null; }
+
+  public String getObjectiveManufacturer(Integer objective) { return null; }
+  public String getObjectiveModel(Integer objective) { return null; }
+  public String getObjectiveSerial(Integer objective) { return null; }
+  public Float getLensNA(Integer objective) { return null; }
+  public Float getObjectiveMagnification(Integer objective) { return null; }
+
+  public String getExcitationManufacturer(Integer filter) { return null; }
+  public String getExcitationModel(Integer filter) { return null; }
+  public String getExcitationLotNumber(Integer filter) { return null; }
+  public String getExcitationType(Integer filter) { return null; }
+
+  public String getDichroicManufacturer(Integer dichroic) { return null; }
+  public String getDichroicModel(Integer dichroic) { return null; }
+  public String getDichroicLotNumber(Integer dichroic) { return null; }
+
+  public String getEmissionManufacturer(Integer filter) { return null; }
+  public String getEmissionModel(Integer filter) { return null; }
+  public String getEmissionLotNumber(Integer filter) { return null; }
+  public String getEmissionType(Integer filter) { return null; }
+
+  public String getFilterSetManufacturer(Integer filterSet) { return null; }
+  public String getFilterSetModel(Integer filterSet) { return null; }
+  public String getFilterSetLotNumber(Integer filterSet) { return null; }
+
+  public Integer getOTFSizeX(Integer otf) { return null; }
+  public Integer getOTFSizeY(Integer otf) { return null; }
+  public String getOTFPixelType(Integer otf) { return null; }
+  public String getOTFPath(Integer otf) { return null; }
+  public Boolean getOTFOpticalAxisAverage(Integer otf) { return null; }
+
+  // -- MetadataStore API methods --
+
+  public void createRoot() { }
+
+  public Object getRoot() { return null; }
+
+  public void setChannelGlobalMinMax(int channel, Double globalMin,
+    Double globalMax, Integer i)
+  {
+  }
+
+  public void setDefaultDisplaySettings(Integer i) { }
+
+  public void setDimensions(Float pixelSizeX, Float pixelSizeY,
+    Float pixelSizeZ, Float pixelSizeC, Float pixelSizeT, Integer i)
+  {
+  }
+
+  public void setDisplayROI(Integer x0, Integer y0, Integer z0, Integer x1,
+    Integer y1, Integer z1, Integer t0, Integer t1, Object displayOptions,
+    Integer i)
+  {
+  }
+
+  public void setExperimenter(String firstName, String lastName, String email,
+    String institution, String dataDirectory, Object group, Integer i)
+  {
+  }
+
+  public void setGroup(String name, Object leader, Object contact, Integer i) {
+  }
+
+  public void setImage(String name, String creationDate, String description,
+    Integer i)
+  {
+  }
+
+  public void setInstrument(String manufacturer, String model,
+    String serialNumber, String type, Integer i)
+  {
+  }
+
+  public void setLogicalChannel(int channelIdx, String name,
+    Integer samplesPerPixel, Integer filter, Integer lightSource,
+    Float lightAttenuation, Integer lightWavelength, Integer otf,
+    Integer detector, Float detectorOffset, Float detectorGain,
+    String illuminationType, Integer pinholeSize,
+    String photometricInterpretation, String mode, String contrastMethod,
+    Integer auxLightSource, Float auxLightAttenuation, String auxTechnique,
+    Integer auxLightWavelength, Integer emWave, Integer exWave, String fluor,
+    Float ndFilter, Integer i)
+  {
+  }
+
+  public void setPixels(Integer sizeX, Integer sizeY, Integer sizeZ,
+    Integer sizeC, Integer sizeT, Integer pixelType, Boolean bigEndian,
+    String dimensionOrder, Integer imageNo, Integer pixelsNo)
+  {
+  }
+
+  public void setPlaneInfo(int theZ, int theC, int theT, Float timestamp,
+    Float exposureTime, Integer i)
+  {
+  }
+
+  public void setRoot(Object root) { }
+
+  public void setStageLabel(String name, Float x, Float y, Float z, Integer i) {
+  }
+
+  public void setImagingEnvironment(Float temperature, Float airPressure,
+    Float humidity, Float co2Percent, Integer i)
+  {
+  }
+
+  public void setDisplayChannel(Integer channelNumber, Double blackLevel,
+    Double whiteLevel, Float gamma, Integer i)
+  {
+  }
+
+  public void setDisplayOptions(Float zoom, Boolean redChannelOn,
+    Boolean greenChannelOn, Boolean blueChannelOn, Boolean displayRGB,
+    String colorMap, Integer zstart, Integer zstop, Integer tstart,
+    Integer tstop, Integer imageNdx, Integer pixelNdx, Integer redChannel,
+    Integer greenChannel, Integer blueChannel, Integer grayChannel)
+  {
+  }
+
+  public void setLightSource(String manufacturer, String model,
+    String serialNumber, Integer instrumentNdx, Integer lightNdx)
+  {
+  }
+
+  public void setLaser(String type, String medium, Integer wavelength,
+    Boolean frequencyDoubled, Boolean tunable, String pulse, Float power,
+    Integer instrumentNdx, Integer lightNdx, Integer pumpNdx, Integer laserNdx)
+  {
+  }
+
+  public void setFilament(String type, Float power, Integer lightNdx,
+    Integer filamentNdx)
+  {
+  }
+
+  public void setArc(String type, Float power, Integer lightNdx, Integer arcNdx)
+  {
+  }
+
+  public void setDetector(String manufacturer, String model,
+    String serialNumber, String type, Float gain, Float voltage, Float offset,
+    Integer instrumentNdx, Integer detectorNdx)
+  {
+  }
+
+  public void setObjective(String manufacturer, String model,
+    String serialNumber, Float lensNA, Float magnification,
+    Integer instrumentNdx, Integer objectiveNdx)
+  {
+  }
+
+  public void setExcitationFilter(String manufacturer, String model,
+    String lotNumber, String type, Integer filterNdx)
+  {
+  }
+
+  public void setDichroic(String manufacturer, String model, String lotNumber,
+    Integer dichroicNdx)
+  {
+  }
+
+  public void setEmissionFilter(String manufacturer, String model,
+    String lotNumber, String type, Integer filterNdx)
+  {
+  }
+
+  public void setFilterSet(String manufacturer, String model, String lotNumber,
+    Integer filterSetNdx, Integer filterNdx)
+  {
+  }
+
+  public void setOTF(Integer sizeX, Integer sizeY, String pixelType,
+    String path, Boolean opticalAxisAverage, Integer instrumentNdx,
+    Integer otfNdx, Integer filterNdx, Integer objectiveNdx)
+  {
+  }
+
+}
diff --git a/loci/formats/FilePattern.java b/loci/formats/FilePattern.java
new file mode 100644
index 0000000..ef4836d
--- /dev/null
+++ b/loci/formats/FilePattern.java
@@ -0,0 +1,717 @@
+//
+// FilePattern.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.File;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Vector;
+
+/**
+ * FilePattern is a collection of methods for handling file patterns, a way of
+ * succinctly representing a collection of files meant to be part of the same
+ * data series.
+ *
+ * Examples:
+ * <ul>
+ *   <li>C:\data\BillM\sdub<1-12>.pic</li>
+ *   <li>C:\data\Kevin\80<01-59>0<2-3>.pic</li>
+ *   <li>/data/Josiah/cell-Z<0-39>.C<0-1>.tiff</li>
+ * </ul>
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FilePattern.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FilePattern.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class FilePattern {
+
+  // -- Fields --
+
+  /** The file pattern string. */
+  private String pattern;
+
+  /** The validity of the file pattern. */
+  private boolean valid;
+
+  /** Error message generated during file pattern construction. */
+  private String msg;
+
+  /** Indices into the pattern indicating the start of a numerical block. */
+  private int[] startIndex;
+
+  /** Indices into the pattern indicating the end of a numerical block. */
+  private int[] endIndex;
+
+  /** First number of each numerical block. */
+  private BigInteger[] begin;
+
+  /** Last number of each numerical block. */
+  private BigInteger[] end;
+
+  /** Step size of each numerical block. */
+  private BigInteger[] step;
+
+  /** Total numbers withins each numerical block. */
+  private int[] count;
+
+  /** Whether each numerical block is fixed width. */
+  private boolean[] fixed;
+
+  /** The number of leading zeroes for each numerical block. */
+  private int[] zeroes;
+
+  /** File listing for this file pattern. */
+  private String[] files;
+
+  // -- Constructors --
+
+  /** Creates a pattern object using the given file as a template. */
+  public FilePattern(Location file) { this(FilePattern.findPattern(file)); }
+
+  /**
+   * Creates a pattern object using the given
+   * filename and directory path as a template.
+   */
+  public FilePattern(String name, String dir) {
+    this(FilePattern.findPattern(name, dir));
+  }
+
+  /** Creates a pattern object for files with the given pattern string. */
+  public FilePattern(String pattern) {
+    this.pattern = pattern;
+    valid = false;
+    if (pattern == null) {
+      msg = "Null pattern string.";
+      return;
+    }
+
+    // locate numerical blocks
+    int len = pattern.length();
+    Vector lt = new Vector(len);
+    Vector gt = new Vector(len);
+    int left = -1;
+    while (true) {
+      left = pattern.indexOf("<", left + 1);
+      if (left < 0) break;
+      lt.add(new Integer(left));
+    }
+    int right = -1;
+    while (true) {
+      right = pattern.indexOf(">", right + 1);
+      if (right < 0) break;
+      gt.add(new Integer(right));
+    }
+
+    // assemble numerical block indices
+    int num = lt.size();
+    if (num != gt.size()) {
+      msg = "Mismatched numerical block markers.";
+      return;
+    }
+    startIndex = new int[num];
+    endIndex = new int[num];
+    for (int i=0; i<num; i++) {
+      int val = ((Integer) lt.elementAt(i)).intValue();
+      if (i > 0 && val < endIndex[i - 1]) {
+        msg = "Bad numerical block marker order.";
+        return;
+      }
+      startIndex[i] = val;
+      val = ((Integer) gt.elementAt(i)).intValue();
+      if (val <= startIndex[i]) {
+        msg = "Bad numerical block marker order.";
+        return;
+      }
+      endIndex[i] = val + 1;
+    }
+
+    // parse numerical blocks
+    begin = new BigInteger[num];
+    end = new BigInteger[num];
+    step = new BigInteger[num];
+    count = new int[num];
+    fixed = new boolean[num];
+    zeroes = new int[num];
+    for (int i=0; i<num; i++) {
+      String block = pattern.substring(startIndex[i], endIndex[i]);
+      int dash = block.indexOf("-");
+      String b, e, s;
+      if (dash < 0) {
+        // no range; assume entire block is a single number (e.g., <15>)
+        b = e = block.substring(1, block.length() - 1);
+        s = "1";
+      }
+      else {
+        int colon = block.indexOf(":");
+        b = block.substring(1, dash);
+        if (colon < 0) {
+          e = block.substring(dash + 1, block.length() - 1);
+          s = "1";
+        }
+        else {
+          e = block.substring(dash + 1, colon);
+          s = block.substring(colon + 1, block.length() - 1);
+        }
+      }
+      try {
+        begin[i] = new BigInteger(b);
+        end[i] = new BigInteger(e);
+        if (begin[i].compareTo(end[i]) > 0) {
+          msg = "Begin value cannot be greater than ending value.";
+          return;
+        }
+        step[i] = new BigInteger(s);
+        if (step[i].compareTo(BigInteger.ONE) < 0) {
+          msg = "Step value must be at least one.";
+          return;
+        }
+        count[i] = end[i].subtract(begin[i]).divide(step[i]).intValue() + 1;
+        fixed[i] = b.length() == e.length();
+        int z = 0;
+        for (z=0; z<e.length(); z++) {
+          if (e.charAt(z) != '0') break;
+        }
+        zeroes[i] = z;
+      }
+      catch (NumberFormatException exc) {
+        msg = "Invalid numerical range values.";
+        return;
+      }
+    }
+
+    // build file listing
+    Vector v = new Vector();
+    buildFiles("", num, v);
+    files = new String[v.size()];
+    v.copyInto(files);
+
+    valid = true;
+  }
+
+  // -- FilePattern API methods --
+
+  /** Gets the file pattern string. */
+  public String getPattern() { return pattern; }
+
+  /** Gets whether the file pattern string is valid. */
+  public boolean isValid() { return valid; }
+
+  /** Gets the file pattern error message, if any. */
+  public String getErrorMessage() { return msg; }
+
+  /** Gets the first number of each numerical block. */
+  public BigInteger[] getFirst() { return begin; }
+
+  /** Gets the last number of each numerical block. */
+  public BigInteger[] getLast() { return end; }
+
+  /** Gets the step increment of each numerical block. */
+  public BigInteger[] getStep() { return step; }
+
+  /** Gets the total count of each numerical block. */
+  public int[] getCount() { return count; }
+
+  /** Gets a listing of all files matching the given file pattern. */
+  public String[] getFiles() { return files; }
+
+  /** Gets the specified numerical block. */
+  public String getBlock(int i) {
+    if (i < 0 || i >= startIndex.length) return null;
+    return pattern.substring(startIndex[i], endIndex[i]);
+  }
+
+  /** Gets each numerical block. */
+  public String[] getBlocks() {
+    String[] s = new String[startIndex.length];
+    for (int i=0; i<s.length; i++) s[i] = getBlock(i);
+    return s;
+  }
+
+  /** Gets the pattern's text string before any numerical ranges. */
+  public String getPrefix() {
+    int s = pattern.lastIndexOf(File.separator) + 1;
+    int e;
+    if (startIndex.length > 0) e = startIndex[0];
+    else {
+      int dot = pattern.lastIndexOf(".");
+      e = dot < s ? pattern.length() : dot;
+    }
+    return s <= e ? pattern.substring(s, e) : "";
+  }
+
+  /** Gets the pattern's text string after all numerical ranges. */
+  public String getSuffix() {
+    return endIndex.length > 0 ?
+      pattern.substring(endIndex[endIndex.length - 1]) : pattern;
+  }
+
+  /** Gets the pattern's text string before the given numerical block. */
+  public String getPrefix(int i) {
+    if (i < 0 || i >= startIndex.length) return null;
+    int s = i > 0 ? endIndex[i - 1] :
+      (pattern.lastIndexOf(File.separator) + 1);
+    int e = startIndex[i];
+    return s <= e ? pattern.substring(s, e) : null;
+  }
+
+  /** Gets the pattern's text string before each numerical block. */
+  public String[] getPrefixes() {
+    String[] s = new String[startIndex.length];
+    for (int i=0; i<s.length; i++) s[i] = getPrefix(i);
+    return s;
+  }
+
+  // -- Utility methods --
+
+  /**
+   * Identifies the group pattern from a given file within that group.
+   * @param file The file to use as a template for the match.
+   */
+  public static String findPattern(Location file) {
+    return findPattern(file.getName(), file.getAbsoluteFile().getParent());
+  }
+
+  /**
+   * Identifies the group pattern from a given file within that group.
+   * @param file The file to use as a template for the match.
+   */
+  public static String findPattern(File file) {
+    return findPattern(file.getName(), file.getAbsoluteFile().getParent());
+  }
+
+  /**
+   * Identifies the group pattern from a given file within that group.
+   * @param name The filename to use as a template for the match.
+   * @param dir The directory in which to search for matching files.
+   */
+  public static String findPattern(String name, String dir) {
+    if (dir == null) dir = ""; // current directory
+    else if (!dir.equals("") && !dir.endsWith(File.separator)) {
+      dir += File.separator;
+    }
+    Location dirFile = new Location(dir.equals("") ? "." : dir);
+
+    // list files in the given directory
+    Location[] f = dirFile.listFiles();
+    if (f == null) return null;
+    String[] nameList = new String[f.length];
+    for (int i=0; i<nameList.length; i++) nameList[i] = f[i].getName();
+
+    return findPattern(name, dir, nameList);
+  }
+
+  /**
+   * Identifies the group pattern from a given file within that group.
+   * @param name The filename to use as a template for the match.
+   * @param dir The directory prefix to use for matching files.
+   * @param nameList The names through which to search for matching files.
+   */
+  public static String findPattern(String name, String dir, String[] nameList) {
+    if (dir == null) dir = ""; // current directory
+    else if (!dir.equals("") && !dir.endsWith(File.separator)) {
+      dir += File.separator;
+    }
+
+    // compile list of numerical blocks
+    int len = name.length();
+    int bound = (len + 1) / 2;
+    int[] indexList = new int[bound];
+    int[] endList = new int[bound];
+    int q = 0;
+    boolean num = false;
+    int ndx = -1, e = 0;
+    for (int i=0; i<len; i++) {
+      char c = name.charAt(i);
+      if (c >= '0' && c <= '9') {
+        if (num) e++;
+        else {
+          num = true;
+          ndx = i;
+          e = ndx + 1;
+        }
+      }
+      else if (num) {
+        num = false;
+        indexList[q] = ndx;
+        endList[q] = e;
+        q++;
+      }
+    }
+    if (num) {
+      indexList[q] = ndx;
+      endList[q] = e;
+      q++;
+    }
+
+    // analyze each block, building pattern as we go
+    StringBuffer sb = new StringBuffer(dir);
+
+    for (int i=0; i<q; i++) {
+      int last = i > 0 ? endList[i - 1] : 0;
+      sb.append(name.substring(last, indexList[i]));
+      String pre = name.substring(0, indexList[i]);
+      String post = name.substring(endList[i]);
+
+      NumberFilter filter = new NumberFilter(pre, post);
+      String[] list = matchFiles(nameList, filter);
+      if (list == null || list.length == 0) return null;
+      if (list.length == 1) {
+        // false alarm; this number block is constant
+        sb.append(name.substring(indexList[i], endList[i]));
+        continue;
+      }
+      boolean fix = true;
+      for (int j=0; j<list.length; j++) {
+        if (list[j].length() != len) {
+          fix = false;
+          break;
+        }
+      }
+      if (fix) {
+        // tricky; this fixed-width block could represent multiple numberings
+        int width = endList[i] - indexList[i];
+
+        // check each character for duplicates
+        boolean[] same = new boolean[width];
+        for (int j=0; j<width; j++) {
+          same[j] = true;
+          int jx = indexList[i] + j;
+          char c = name.charAt(jx);
+          for (int k=0; k<list.length; k++) {
+            if (list[k].charAt(jx) != c) {
+              same[j] = false;
+              break;
+            }
+          }
+        }
+
+        // break down each sub-block
+        int j = 0;
+        while (j < width) {
+          int jx = indexList[i] + j;
+          if (same[j]) {
+            sb.append(name.charAt(jx));
+            j++;
+          }
+          else {
+            while (j < width && !same[j]) j++;
+            String p = findPattern(name, nameList, jx, indexList[i] + j, "");
+            if (p == null) {
+              // unable to find an appropriate breakdown of numerical blocks
+              return null;
+            }
+            sb.append(p);
+          }
+        }
+      }
+      else {
+        // assume variable-width block represents only one numbering
+        BigInteger[] numbers = new BigInteger[list.length];
+        for (int j=0; j<list.length; j++) {
+          numbers[j] = filter.getNumber(list[j]);
+        }
+        Arrays.sort(numbers);
+        String bounds = getBounds(numbers, false);
+        if (bounds == null) return null;
+        sb.append(bounds);
+      }
+    }
+    sb.append(q > 0 ? name.substring(endList[q - 1]) : name);
+    return sb.toString();
+  }
+
+  // -- Utility helper methods --
+
+  /** Recursive method for parsing a fixed-width numerical block. */
+  private static String findPattern(String name,
+    String[] nameList, int ndx, int end, String p)
+  {
+    if (ndx == end) return p;
+    for (int i=end-ndx; i>=1; i--) {
+      NumberFilter filter = new NumberFilter(
+        name.substring(0, ndx), name.substring(ndx + i));
+      String[] list = matchFiles(nameList, filter);
+      BigInteger[] numbers = new BigInteger[list.length];
+      for (int j=0; j<list.length; j++) {
+        numbers[j] = new BigInteger(list[j].substring(ndx, ndx + i));
+      }
+      Arrays.sort(numbers);
+      String bounds = getBounds(numbers, true);
+      if (bounds == null) continue;
+      String pat = findPattern(name, nameList, ndx + i, end, p + bounds);
+      if (pat != null) return pat;
+    }
+    // no combination worked; this parse path is infeasible
+    return null;
+  }
+
+  /**
+   * Gets a string containing start, end and step values
+   * for a sorted list of numbers.
+   */
+  private static String getBounds(BigInteger[] numbers, boolean fixed) {
+    if (numbers.length < 2) return null;
+    BigInteger b = numbers[0];
+    BigInteger e = numbers[numbers.length - 1];
+    BigInteger s = numbers[1].subtract(b);
+    if (s.equals(BigInteger.ZERO)) {
+      // step size must be positive
+      return null;
+    }
+    for (int i=2; i<numbers.length; i++) {
+      if (!numbers[i].subtract(numbers[i - 1]).equals(s)) {
+        // step size is not constant
+        return null;
+      }
+    }
+    String sb = b.toString();
+    String se = e.toString();
+    StringBuffer bounds = new StringBuffer("<");
+    if (fixed) {
+      int zeroes = se.length() - sb.length();
+      for (int i=0; i<zeroes; i++) bounds.append("0");
+    }
+    bounds.append(sb);
+    bounds.append("-");
+    bounds.append(se);
+    if (!s.equals(BigInteger.ONE)) {
+      bounds.append(":");
+      bounds.append(s);
+    }
+    bounds.append(">");
+    return bounds.toString();
+  }
+
+  /** Filters the given list of filenames according to the specified filter. */
+  private static String[] matchFiles(String[] inFiles, NumberFilter filter) {
+    Vector v = new Vector();
+    for (int i=0; i<inFiles.length; i++) {
+      if (filter.accept(inFiles[i])) v.add(inFiles[i]);
+    }
+    String[] s = new String[v.size()];
+    v.copyInto(s);
+    return s;
+  }
+
+  // -- Helper methods --
+
+  /** Recursive method for building filenames for the file listing. */
+  private void buildFiles(String prefix, int ndx, Vector fileList) {
+    // compute bounds for constant (non-block) pattern fragment
+    int num = startIndex.length;
+    int n1 = ndx == 0 ? 0 : endIndex[ndx - 1];
+    int n2 = ndx == num ? pattern.length() : startIndex[ndx];
+    String pre = pattern.substring(n1, n2);
+
+    if (ndx == 0) fileList.add(pre + prefix);
+    else {
+      // for (int i=begin[ndx]; i<end[ndx]; i+=step[ndx])
+      BigInteger bi = begin[--ndx];
+      while (bi.compareTo(end[ndx]) <= 0) {
+        String s = bi.toString();
+        int z = zeroes[ndx];
+        if (fixed[ndx]) z += end[ndx].toString().length() - s.length();
+        for (int j=0; j<z; j++) s = "0" + s;
+        buildFiles(s + pre + prefix, ndx, fileList);
+        bi = bi.add(step[ndx]);
+      }
+    }
+  }
+
+  // -- Main method --
+
+  /** Method for testing file pattern logic. */
+  public static void main(String[] args) {
+    String pat = null;
+    if (args.length > 0) {
+      // test file pattern detection based on the given file on disk
+      Location file = new Location(args[0]);
+      LogTools.println("File = " + file.getAbsoluteFile());
+      pat = findPattern(file);
+    }
+    else {
+      // test file pattern detection from a virtual file list
+      String[] nameList = new String[2 * 4 * 3 * 12 + 1];
+      nameList[0] = "outlier.ext";
+      int count = 1;
+      for (int i=1; i<=2; i++) {
+        for (int j=1; j<=4; j++) {
+          for (int k=0; k<=2; k++) {
+            for (int l=1; l<=12; l++) {
+              String sl = (l < 10 ? "0" : "") + l;
+              nameList[count++] =
+                "hypothetical" + sl + k + j + "c" + i + ".ext";
+            }
+          }
+        }
+      }
+      pat = findPattern(nameList[1], null, nameList);
+    }
+    if (pat == null) LogTools.println("No pattern found.");
+    else {
+      LogTools.println("Pattern = " + pat);
+      FilePattern fp = new FilePattern(pat);
+      if (fp.isValid()) {
+        LogTools.println("Pattern is valid.");
+        LogTools.println("Files:");
+        String[] ids = fp.getFiles();
+        for (int i=0; i<ids.length; i++) {
+          LogTools.println("  #" + i + ": " + ids[i]);
+        }
+      }
+      else LogTools.println("Pattern is invalid: " + fp.getErrorMessage());
+    }
+  }
+
+}
+
+// -- Notes --
+
+// Some patterns observed:
+//
+//   TAABA1.PIC TAABA2.PIC TAABA3.PIC ... TAABA45.PIC
+//
+//   0m.tiff 3m.tiff 6m.tiff ... 36m.tiff
+//
+//   cell-Z0.C0.tiff cell-Z1.C0.tiff cell-Z2.C0.tiff ... cell-Z39.C0.tiff
+//   cell-Z0.C1.tiff cell-Z1.C1.tiff cell-Z2.C1.tiff ... cell-Z39.C1.tiff
+//
+//   CRG401.PIC
+//
+//   TST00101.PIC TST00201.PIC TST00301.PIC
+//   TST00102.PIC TST00202.PIC TST00302.PIC
+//
+//   800102.pic 800202.pic 800302.pic ... 805902.pic
+//   800103.pic 800203.pic 800303.pic ... 805903.pic
+//
+//   nd400102.pic nd400202.pic nd400302.pic ... nd406002.pic
+//   nd400103.pic nd400203.pic nd400303.pic ... nd406003.pic
+//
+//   WTERZ2_Series13_z000_ch00.tif ... WTERZ2_Series13_z018_ch00.tif
+//
+// --------------------------------------------------------------------------
+//
+// The file pattern notation defined here encompasses all patterns above.
+//
+//   TAABA<1-45>.PIC
+//   <0-36:3>m.tiff
+//   cell-Z<0-39>.C<0-1>.tiff
+//   CRG401.PIC
+//   TST00<1-3>0<1-2>.PIC
+//   80<01-59>0<2-3>.pic
+//   nd40<01-60>0<2-3>.pic
+//   WTERZ2_Series13_z0<00-18>_ch00.tif
+//
+// In general: <B-E:S> where B is the start number, E is the end number, and S
+// is the step increment. If zero padding has been used, the start number B
+// will have leading zeroes to indicate that. If the step increment is one, it
+// can be omitted.
+//
+// --------------------------------------------------------------------------
+//
+// If file groups not limited to numbering need to be handled, we can extend
+// the notation as follows:
+//
+// A pattern such as:
+//
+//   ABCR.PIC ABCG.PIC ABCB.PIC
+//
+// Could be represented as:
+//
+//   ABC<R|G|B>.PIC
+//
+// If such cases come up, they will need to be identified heuristically and
+// incorporated into the detection algorithm.
+//
+// --------------------------------------------------------------------------
+//
+// Here is a sketch of the algorithm for determining the pattern from a given
+// file within a particular group:
+//
+//   01 - Detect number blocks within the file name, marking them with stars.
+//        For example:
+//
+//          xyz800303b.pic -> xyz<>b.pic
+//
+//        Where <> represents a numerical block with unknown properties.
+//
+//   02 - Get a file listing for all files matching the given pattern. In the
+//        example above, we'd get:
+//
+//        xyz800102b.pic, xyz800202b.pic, ..., xyz805902b.pic,
+//        xyz800103b.pic, xyz800203b.pic, ..., xyz805903b.pic
+//
+//   03 - There are two possibilities: "fixed width" and "variable width."
+//
+//        Variable width: Not all filenames are the same length in characters.
+//        Assume the block only covers a single number. Extract that number
+//        from each filename, sort them and analyze as described below.
+//
+//        Fixed width: All filenames are the same length in characters. The
+//        block could represent more than one number.
+//
+//        First, for each character, determine if that character varies between
+//        filenames. If not, lock it down, splitting the block as necessary
+//        into fixed-width blocks. When finished, the above example looks like:
+//
+//          xyz80<2>0<1>b.pic
+//
+//        Where <N> represents a numerical block of width N.
+//
+//        For each remaining block, extract the numbers from each matching
+//        filename, sort the lists, and analyze as described below.
+//
+//   04 - In either case, analyze each list of numbers. The first on the list
+//        is B. The last one is E. And S is the second one minus B. But check
+//        the list to make sure no numbers are missing for that step size.
+//
+// NOTE: The fixed width algorithm above is insufficient for patterns like
+// "0101.pic" through "2531.pic," where no fixed constant pads the two
+// numerical counts. An additional step is required, as follows:
+//
+//   05 - For each fixed-width block, recursively divide it into pieces, and
+//        analyze the numerical scheme according to those pieces. For example,
+//        in the problem case given above, we'd have:
+//
+//          <4>.pic
+//
+//        Recursively, we'd analyze:
+//
+//          <4>.pic
+//          <3><R1>.pic
+//          <2><R2>.pic
+//          <1><R3>.pic
+//
+//        The <Rx> blocks represent recursive calls to analyze the remainder of
+//        the width.
+//
+//        The function decides if a given combination of widths is valid by
+//        determining if each individual width is valid. An individual width
+//        is valid if the computed B, S and E properly cover the numerical set.
+//
+//        If no combination of widths is found to be valid, the file numbering
+//        is screwy. Print an error message.
diff --git a/loci/formats/FileStitcher.java b/loci/formats/FileStitcher.java
new file mode 100644
index 0000000..02ae556
--- /dev/null
+++ b/loci/formats/FileStitcher.java
@@ -0,0 +1,1286 @@
+//
+// FileStitcher.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+
+/**
+ * Logic to stitch together files with similar names.
+ * Assumes that all files have the same dimensions.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FileStitcher.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FileStitcher.java">SVN</a></dd></dl>
+ */
+public class FileStitcher implements IFormatReader {
+
+  // -- Fields --
+
+  /** FormatReader to use as a template for constituent readers. */
+  private IFormatReader reader;
+
+  /**
+   * Whether string ids given should be treated
+   * as file patterns rather than single file paths.
+   */
+  private boolean patternIds = false;
+
+  /** Current file pattern string. */
+  private String currentId;
+
+  /** File pattern object used to build the list of files. */
+  private FilePattern fp;
+
+  /** Axis guesser object used to guess which dimensional axes are which. */
+  private AxisGuesser[] ag;
+
+  /** The matching files. */
+  private String[] files;
+
+  /** Used files list. */
+  private String[] usedFiles;
+
+  /** Reader used for each file. */
+  private IFormatReader[] readers;
+
+  /** Blank buffered image, for use when image counts vary between files. */
+  private BufferedImage[] blankImage;
+
+  /** Blank image bytes, for use when image counts vary between files. */
+  private byte[][] blankBytes;
+
+  /** Blank buffered thumbnail, for use when image counts vary between files. */
+  private BufferedImage[] blankThumb;
+
+  /** Blank thumbnail bytes, for use when image counts vary between files. */
+  private byte[][] blankThumbBytes;
+
+  /** Number of images per file. */
+  private int[] imagesPerFile;
+
+  /** Dimensional axis lengths per file. */
+  private int[] sizeZ, sizeC, sizeT;
+
+  /** Component lengths for each axis type. */
+  private int[][] lenZ, lenC, lenT;
+
+  /** Core metadata. */
+  private CoreMetadata core;
+
+  // -- Constructors --
+
+  /** Constructs a FileStitcher around a new image reader. */
+  public FileStitcher() { this(new ImageReader()); }
+
+  /**
+   * Constructs a FileStitcher around a new image reader.
+   * @param patternIds Whether string ids given should be treated as file
+   *    patterns rather than single file paths.
+   */
+  public FileStitcher(boolean patternIds) {
+    this(new ImageReader(), patternIds);
+  }
+
+  /**
+   * Constructs a FileStitcher with the given reader.
+   * @param r The reader to use for reading stitched files.
+   */
+  public FileStitcher(IFormatReader r) { this(r, false); }
+
+  /**
+   * Constructs a FileStitcher with the given reader.
+   * @param r The reader to use for reading stitched files.
+   * @param patternIds Whether string ids given should be treated as file
+   *   patterns rather than single file paths.
+   */
+  public FileStitcher(IFormatReader r, boolean patternIds) {
+    reader = r;
+    this.patternIds = patternIds;
+  }
+
+  // -- FileStitcher API methods --
+
+  /** Gets the wrapped reader prototype. */
+  public IFormatReader getReader() { return reader; }
+
+  /**
+   * Gets the axis type for each dimensional block.
+   * @return An array containing values from the enumeration:
+   *   <ul>
+   *     <li>AxisGuesser.Z_AXIS: focal planes</li>
+   *     <li>AxisGuesser.T_AXIS: time points</li>
+   *     <li>AxisGuesser.C_AXIS: channels</li>
+   *   </ul>
+   */
+  public int[] getAxisTypes() {
+    FormatTools.assertId(currentId, true, 2);
+    return ag[getSeries()].getAxisTypes();
+  }
+
+  /**
+   * Sets the axis type for each dimensional block.
+   * @param axes An array containing values from the enumeration:
+   *   <ul>
+   *     <li>AxisGuesser.Z_AXIS: focal planes</li>
+   *     <li>AxisGuesser.T_AXIS: time points</li>
+   *     <li>AxisGuesser.C_AXIS: channels</li>
+   *   </ul>
+   */
+  public void setAxisTypes(int[] axes) throws FormatException {
+    FormatTools.assertId(currentId, true, 2);
+    ag[getSeries()].setAxisTypes(axes);
+    computeAxisLengths();
+  }
+
+  /** Gets the file pattern object used to build the list of files. */
+  public FilePattern getFilePattern() {
+    FormatTools.assertId(currentId, true, 2);
+    return fp;
+  }
+
+  /**
+   * Gets the axis guesser object used to guess
+   * which dimensional axes are which.
+   */
+  public AxisGuesser getAxisGuesser() {
+    FormatTools.assertId(currentId, true, 2);
+    return ag[getSeries()];
+  }
+
+  /**
+   * Finds the file pattern for the given ID, based on the state of the file
+   * stitcher. Takes both ID map entries and the patternIds flag into account.
+   */
+  public FilePattern findPattern(String id) {
+    FormatTools.assertId(currentId, true, 2);
+    if (!patternIds) {
+      // find the containing pattern
+      Hashtable map = Location.getIdMap();
+      String pattern = null;
+      if (map.containsKey(id)) {
+        // search ID map for pattern, rather than files on disk
+        String[] idList = new String[map.size()];
+        Enumeration en = map.keys();
+        for (int i=0; i<idList.length; i++) {
+          idList[i] = (String) en.nextElement();
+        }
+        pattern = FilePattern.findPattern(id, null, idList);
+      }
+      else {
+        // id is an unmapped file path; look to similar files on disk
+
+        pattern = FilePattern.findPattern(new Location(id));
+      }
+      if (pattern != null) id = pattern;
+    }
+    return new FilePattern(id);
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return reader.isThisType(block);
+  }
+
+  /* @see IFormatReader#setId(String) */
+  public void setId(String id) throws FormatException, IOException {
+    if (!id.equals(currentId)) initFile(id);
+  }
+
+  /* @see IFormatReader#setId(String, boolean) */
+  public void setId(String id, boolean force)
+    throws FormatException, IOException
+  {
+    if (!id.equals(currentId) || force) initFile(id);
+  }
+
+  /* @see IFormatReader#getImageCount() */
+  public int getImageCount() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.imageCount[getSeries()];
+  }
+
+  /* @see IFormatReader#isRGB() */
+  public boolean isRGB() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.rgb[getSeries()];
+  }
+
+  /* @see IFormatReader#getSizeX() */
+  public int getSizeX() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.sizeX[getSeries()];
+  }
+
+  /* @see IFormatReader#getSizeY() */
+  public int getSizeY() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.sizeY[getSeries()];
+  }
+
+  /* @see IFormatReader#getSizeZ() */
+  public int getSizeZ() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.sizeZ[getSeries()];
+  }
+
+  /* @see IFormatReader#getSizeC() */
+  public int getSizeC() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.sizeC[getSeries()];
+  }
+
+  /* @see IFormatReader#getSizeT() */
+  public int getSizeT() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.sizeT[getSeries()];
+  }
+
+  /* @see IFormatReader#getPixelType() */
+  public int getPixelType() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.pixelType[getSeries()];
+  }
+
+  /* @see IFormatReader#getEffectiveSizeC() */
+  public int getEffectiveSizeC() {
+    FormatTools.assertId(currentId, true, 2);
+    return getImageCount() / (getSizeZ() * getSizeT());
+  }
+
+  /* @see IFormatReader#getRGBChannelCount() */
+  public int getRGBChannelCount() {
+    FormatTools.assertId(currentId, true, 2);
+    return getSizeC() / getEffectiveSizeC();
+  }
+
+  /* @see IFormatReader#isIndexed() */
+  public boolean isIndexed() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.isIndexed();
+  }
+
+  /* @see IFormatReader#isFalseColor() */
+  public boolean isFalseColor() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.isFalseColor();
+  }
+
+  /* @see IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.get8BitLookupTable();
+  }
+
+  /* @see IFormatReader#get16BitLookupTable() */
+  public short[][] get16BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.get16BitLookupTable();
+  }
+
+  /* @see IFormatReader#getChannelDimLengths() */
+  public int[] getChannelDimLengths() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.cLengths[getSeries()];
+  }
+
+  /* @see IFormatReader#getChannelDimTypes() */
+  public String[] getChannelDimTypes() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.cTypes[getSeries()];
+  }
+
+  /* @see IFormatReader#getThumbSizeX() */
+  public int getThumbSizeX() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getThumbSizeX();
+  }
+
+  /* @see IFormatReader#getThumbSizeY() */
+  public int getThumbSizeY() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getThumbSizeY();
+  }
+
+  /* @see IFormatReader#isLittleEndian() */
+  public boolean isLittleEndian() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.isLittleEndian();
+  }
+
+  /* @see IFormatReader#getDimensionOrder() */
+  public String getDimensionOrder() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.currentOrder[getSeries()];
+  }
+
+  /* @see IFormatReader#isOrderCertain() */
+  public boolean isOrderCertain() {
+    FormatTools.assertId(currentId, true, 2);
+    return core.orderCertain[getSeries()];
+  }
+
+  /* @see IFormatReader#isInterleaved() */
+  public boolean isInterleaved() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.isInterleaved();
+  }
+
+  /* @see IFormatReader#isInterleaved(int) */
+  public boolean isInterleaved(int subC) {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.isInterleaved(subC);
+  }
+
+  /* @see IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    int[] q = computeIndices(no);
+    int fno = q[0], ino = q[1];
+    if (ino < readers[fno].getImageCount()) {
+      return readers[fno].openImage(ino);
+    }
+
+    // return a blank image to cover for the fact that
+    // this file does not contain enough image planes
+    int sno = getSeries();
+    if (blankImage[sno] == null) {
+      blankImage[sno] = ImageTools.blankImage(core.sizeX[sno], core.sizeY[sno],
+        sizeC[sno], getPixelType());
+    }
+
+    return blankImage[sno];
+  }
+
+  /* @see IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    int[] q = computeIndices(no);
+    int fno = q[0], ino = q[1];
+    if (ino < readers[fno].getImageCount()) {
+      return readers[fno].openBytes(ino);
+    }
+
+    // return a blank image to cover for the fact that
+    // this file does not contain enough image planes
+    int sno = getSeries();
+    if (blankBytes[sno] == null) {
+      int bytes = FormatTools.getBytesPerPixel(getPixelType());
+      blankBytes[sno] = new byte[core.sizeX[sno] * core.sizeY[sno] *
+        bytes * getRGBChannelCount()];
+    }
+    return blankBytes[sno];
+  }
+
+  /* @see IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 2);
+    int[] q = computeIndices(no);
+    int fno = q[0], ino = q[1];
+    if (ino < readers[fno].getImageCount()) {
+      return readers[fno].openBytes(ino, buf);
+    }
+
+    // return a blank image to cover for the fact that
+    // this file does not contain enough image planes
+    Arrays.fill(buf, (byte) 0);
+    return buf;
+  }
+
+  /* @see IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 2);
+    int[] q = computeIndices(no);
+    int fno = q[0], ino = q[1];
+    if (ino < readers[fno].getImageCount()) {
+      return readers[fno].openThumbImage(ino);
+    }
+
+    // return a blank image to cover for the fact that
+    // this file does not contain enough image planes
+    int sno = getSeries();
+    if (blankThumb[sno] == null) {
+      blankThumb[sno] = ImageTools.blankImage(getThumbSizeX(),
+        getThumbSizeY(), sizeC[sno], getPixelType());
+    }
+    return blankThumb[sno];
+  }
+
+  /* @see IFormatReader#openThumbBytes(int) */
+  public byte[] openThumbBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    int[] q = computeIndices(no);
+    int fno = q[0], ino = q[1];
+    if (ino < readers[fno].getImageCount()) {
+      return readers[fno].openThumbBytes(ino);
+    }
+
+    // return a blank image to cover for the fact that
+    // this file does not contain enough image planes
+    int sno = getSeries();
+    if (blankThumbBytes[sno] == null) {
+      int bytes = FormatTools.getBytesPerPixel(getPixelType());
+      blankThumbBytes[sno] = new byte[getThumbSizeX() * getThumbSizeY() *
+        bytes * getRGBChannelCount()];
+    }
+    return blankThumbBytes[sno];
+  }
+
+  /* @see IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (readers == null) reader.close(fileOnly);
+    else {
+      for (int i=0; i<readers.length; i++) readers[i].close(fileOnly);
+    }
+    if (!fileOnly) {
+      readers = null;
+      blankImage = null;
+      blankBytes = null;
+      currentId = null;
+    }
+  }
+
+  /* @see IFormatReader#close() */
+  public void close() throws IOException {
+    if (readers == null) reader.close();
+    else {
+      for (int i=0; i<readers.length; i++) readers[i].close();
+    }
+    readers = null;
+    blankImage = null;
+    blankBytes = null;
+    currentId = null;
+  }
+
+  /* @see IFormatReader#getSeriesCount() */
+  public int getSeriesCount() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getSeriesCount();
+  }
+
+  /* @see IFormatReader#setSeries(int) */
+  public void setSeries(int no) {
+    FormatTools.assertId(currentId, true, 2);
+    reader.setSeries(no);
+  }
+
+  /* @see IFormatReader#getSeries() */
+  public int getSeries() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getSeries();
+  }
+
+  /* @see IFormatReader#setGroupFiles(boolean) */
+  public void setGroupFiles(boolean group) {
+    for (int i=0; i<readers.length; i++) readers[i].setGroupFiles(group);
+  }
+
+  /* @see IFormatReader#isGroupFiles() */
+  public boolean isGroupFiles() {
+    return readers[0].isGroupFiles();
+  }
+
+  /* @see IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    return readers[0].fileGroupOption(id);
+  }
+
+  /* @see IFormatReader#isMetadataComplete() */
+  public boolean isMetadataComplete() {
+    return readers[0].isMetadataComplete();
+  }
+
+  /* @see IFormatReader#setNormalized(boolean) */
+  public void setNormalized(boolean normalize) {
+    FormatTools.assertId(currentId, false, 2);
+    if (readers == null) reader.setNormalized(normalize);
+    else {
+      for (int i=0; i<readers.length; i++) {
+        readers[i].setNormalized(normalize);
+      }
+    }
+  }
+
+  /* @see IFormatReader#isNormalized() */
+  public boolean isNormalized() { return reader.isNormalized(); }
+
+  /* @see IFormatReader#setMetadataCollected(boolean) */
+  public void setMetadataCollected(boolean collect) {
+    FormatTools.assertId(currentId, false, 2);
+    if (readers == null) reader.setMetadataCollected(collect);
+    else {
+      for (int i=0; i<readers.length; i++) {
+        readers[i].setMetadataCollected(collect);
+      }
+    }
+  }
+
+  /* @see IFormatReader#isMetadataCollected() */
+  public boolean isMetadataCollected() {
+    return reader.isMetadataCollected();
+  }
+
+  /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */
+  public void setOriginalMetadataPopulated(boolean populate) {
+    FormatTools.assertId(currentId, false, 1);
+    if (readers == null) reader.setOriginalMetadataPopulated(populate);
+    else {
+      for (int i=0; i<readers.length; i++) {
+        readers[i].setOriginalMetadataPopulated(populate);
+      }
+    }
+  }
+
+  /* @see IFormatReader#isOriginalMetadataPopulated() */
+  public boolean isOriginalMetadataPopulated() {
+    return reader.isOriginalMetadataPopulated();
+  }
+
+  /* @see IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 2);
+
+    // returning the files list directly here is fast, since we do not
+    // have to call initFile on each constituent file; but we can only do so
+    // when each constituent file does not itself have multiple used files
+
+    if (reader.getUsedFiles().length > 1) {
+      // each constituent file has multiple used files; we must build the list
+      // this could happen with, e.g., a stitched collection of ICS/IDS pairs
+      // we have no datasets structured this way, so this logic is untested
+      if (usedFiles == null) {
+        String[][] used = new String[files.length][];
+        int total = 0;
+        for (int i=0; i<files.length; i++) {
+          try {
+            readers[i].setId(files[i]);
+          }
+          catch (FormatException exc) {
+            LogTools.trace(exc);
+            return null;
+          }
+          catch (IOException exc) {
+            LogTools.trace(exc);
+            return null;
+          }
+          used[i] = readers[i].getUsedFiles();
+          total += used[i].length;
+        }
+        usedFiles = new String[total];
+        for (int i=0, off=0; i<used.length; i++) {
+          System.arraycopy(used[i], 0, usedFiles, off, used[i].length);
+          off += used[i].length;
+        }
+      }
+      return usedFiles;
+    }
+    // assume every constituent file has no other used files
+    // this logic could fail if the first constituent has no extra used files,
+    // but later constituents do; in practice, this scenario seems unlikely
+    return files;
+  }
+
+  /* @see IFormatReader#getCurrentFile() */
+  public String getCurrentFile() { return currentId; }
+
+  /* @see IFormatReader#getIndex(int, int, int) */
+  public int getIndex(int z, int c, int t) {
+    return FormatTools.getIndex(this, z, c, t);
+  }
+
+  /* @see IFormatReader#getZCTCoords(int) */
+  public int[] getZCTCoords(int index) {
+    return FormatTools.getZCTCoords(this, index);
+  }
+
+  /* @see IFormatReader#getMetadataValue(String) */
+  public Object getMetadataValue(String field) {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getMetadataValue(field);
+  }
+
+  /* @see IFormatReader#getMetadata() */
+  public Hashtable getMetadata() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getMetadata();
+  }
+
+  /* @see IFormatReader#getCoreMetadata() */
+  public CoreMetadata getCoreMetadata() {
+    FormatTools.assertId(currentId, true, 2);
+    return core;
+  }
+
+  /* @see IFormatReader#setMetadataFiltered(boolean) */
+  public void setMetadataFiltered(boolean filter) {
+    FormatTools.assertId(currentId, false, 2);
+    reader.setMetadataFiltered(filter);
+  }
+
+  /* @see IFormatReader#isMetadataFiltered() */
+  public boolean isMetadataFiltered() {
+    return reader.isMetadataFiltered();
+  }
+
+  /* @see IFormatReader#setMetadataStore(MetadataStore) */
+  public void setMetadataStore(MetadataStore store) {
+    FormatTools.assertId(currentId, false, 2);
+    reader.setMetadataStore(store);
+  }
+
+  /* @see IFormatReader#getMetadataStore() */
+  public MetadataStore getMetadataStore() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getMetadataStore();
+  }
+
+  /* @see IFormatReader#getMetadataStoreRoot() */
+  public Object getMetadataStoreRoot() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getMetadataStoreRoot();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see IFormatHandler#isThisType(String) */
+  public boolean isThisType(String name) {
+    return reader.isThisType(name);
+  }
+
+  /* @see IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    return reader.isThisType(name, open);
+  }
+
+  /* @see IFormatHandler#getFormat() */
+  public String getFormat() {
+    FormatTools.assertId(currentId, true, 2);
+    return reader.getFormat();
+  }
+
+  /* @see IFormatHandler#getSuffixes() */
+  public String[] getSuffixes() {
+    return reader.getSuffixes();
+  }
+
+  // -- StatusReporter API methods --
+
+  /* @see IFormatHandler#addStatusListener(StatusListener) */
+  public void addStatusListener(StatusListener l) {
+    if (readers == null) reader.addStatusListener(l);
+    else {
+      for (int i=0; i<readers.length; i++) readers[i].addStatusListener(l);
+    }
+  }
+
+  /* @see IFormatHandler#removeStatusListener(StatusListener) */
+  public void removeStatusListener(StatusListener l) {
+    if (readers == null) reader.removeStatusListener(l);
+    else {
+      for (int i=0; i<readers.length; i++) readers[i].removeStatusListener(l);
+    }
+  }
+
+  /* @see IFormatHandler#getStatusListeners() */
+  public StatusListener[] getStatusListeners() {
+    return reader.getStatusListeners();
+  }
+
+  // -- Helper methods --
+
+  /** Initializes the given file. */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (FormatHandler.debug) {
+      LogTools.println("calling FileStitcher.initFile(" + id + ")");
+    }
+
+    currentId = id;
+    fp = findPattern(id);
+
+    // verify that file pattern is valid and matches existing files
+    String msg = " Please rename your files or disable file stitching.";
+    if (!fp.isValid()) {
+      throw new FormatException("Invalid " +
+        (patternIds ? "file pattern" : "filename") +
+        " (" + currentId + "): " + fp.getErrorMessage() + msg);
+    }
+    files = fp.getFiles();
+
+    if (files == null) {
+      throw new FormatException("No files matching pattern (" +
+        fp.getPattern() + "). " + msg);
+    }
+    for (int i=0; i<files.length; i++) {
+      if (!new Location(files[i]).exists()) {
+        throw new FormatException("File #" + i +
+          " (" + files[i] + ") does not exist.");
+      }
+    }
+
+    // determine reader type for these files; assume all are the same type
+    Vector classes = new Vector();
+    IFormatReader r = reader;
+    while (r instanceof ReaderWrapper) {
+      classes.add(r.getClass());
+      r = ((ReaderWrapper) r).getReader();
+    }
+    if (r instanceof ImageReader) r = ((ImageReader) r).getReader(files[0]);
+    classes.add(r.getClass());
+
+    // construct list of readers for all files
+    readers = new IFormatReader[files.length];
+    readers[0] = reader;
+    for (int i=1; i<readers.length; i++) {
+      // use crazy reflection to instantiate a reader of the proper type
+      try {
+        r = null;
+        for (int j=classes.size()-1; j>=0; j--) {
+          Class c = (Class) classes.elementAt(j);
+          if (r == null) r = (IFormatReader) c.newInstance();
+          else {
+            r = (IFormatReader) c.getConstructor(
+              new Class[] {IFormatReader.class}).newInstance(new Object[] {r});
+          }
+        }
+        readers[i] = (IFormatReader) r;
+      }
+      catch (InstantiationException exc) { LogTools.trace(exc); }
+      catch (IllegalAccessException exc) { LogTools.trace(exc); }
+      catch (NoSuchMethodException exc) { LogTools.trace(exc); }
+      catch (InvocationTargetException exc) { LogTools.trace(exc); }
+    }
+
+    // sync reader configurations with original reader
+    boolean normalized = reader.isNormalized();
+    boolean metadataFiltered = reader.isMetadataFiltered();
+    boolean metadataCollected = reader.isMetadataCollected();
+    StatusListener[] statusListeners = reader.getStatusListeners();
+    for (int i=1; i<readers.length; i++) {
+      readers[i].setNormalized(normalized);
+      readers[i].setMetadataFiltered(metadataFiltered);
+      readers[i].setMetadataCollected(metadataCollected);
+      for (int j=0; j<statusListeners.length; j++) {
+        readers[i].addStatusListener(statusListeners[j]);
+      }
+    }
+
+    reader.setId(files[0]);
+
+    int seriesCount = reader.getSeriesCount();
+    ag = new AxisGuesser[seriesCount];
+    blankImage = new BufferedImage[seriesCount];
+    blankBytes = new byte[seriesCount][];
+    blankThumb = new BufferedImage[seriesCount];
+    blankThumbBytes = new byte[seriesCount][];
+    imagesPerFile = new int[seriesCount];
+    sizeZ = new int[seriesCount];
+    sizeC = new int[seriesCount];
+    sizeT = new int[seriesCount];
+    boolean[] certain = new boolean[seriesCount];
+    lenZ = new int[seriesCount][];
+    lenC = new int[seriesCount][];
+    lenT = new int[seriesCount][];
+
+    // analyze first file; assume each file has the same parameters
+    core = new CoreMetadata(seriesCount);
+    int oldSeries = reader.getSeries();
+    for (int i=0; i<seriesCount; i++) {
+      reader.setSeries(i);
+      core.sizeX[i] = reader.getSizeX();
+      core.sizeY[i] = reader.getSizeY();
+      // NB: core.sizeZ populated in computeAxisLengths below
+      // NB: core.sizeC populated in computeAxisLengths below
+      // NB: core.sizeT populated in computeAxisLengths below
+      core.pixelType[i] = reader.getPixelType();
+      imagesPerFile[i] = reader.getImageCount();
+      core.imageCount[i] = files.length * imagesPerFile[i];
+      core.thumbSizeX[i] = reader.getThumbSizeX();
+      core.thumbSizeY[i] = reader.getThumbSizeY();
+      // NB: core.cLengths[i] populated in computeAxisLengths below
+      // NB: core.cTypes[i] populated in computeAxisLengths below
+      core.currentOrder[i] = reader.getDimensionOrder();
+      // NB: core.orderCertain[i] populated below
+      core.rgb[i] = reader.isRGB();
+      core.littleEndian[i] = reader.isLittleEndian();
+      core.interleaved[i] = reader.isInterleaved();
+      core.seriesMetadata[i] = reader.getMetadata();
+      sizeZ[i] = reader.getSizeZ();
+      sizeC[i] = reader.getSizeC();
+      sizeT[i] = reader.getSizeT();
+      certain[i] = reader.isOrderCertain();
+    }
+    reader.setSeries(oldSeries);
+
+    // guess at dimensions corresponding to file numbering
+    for (int i=0; i<seriesCount; i++) {
+      ag[i] = new AxisGuesser(fp, core.currentOrder[i],
+        sizeZ[i], sizeT[i], sizeC[i], certain[i]);
+    }
+
+    // order may need to be adjusted
+    for (int i=0; i<seriesCount; i++) {
+      setSeries(i);
+      core.currentOrder[i] = ag[i].getAdjustedOrder();
+      core.orderCertain[i] = ag[i].isCertain();
+      computeAxisLengths();
+    }
+    setSeries(oldSeries);
+
+    // initialize used files list only when requested
+    usedFiles = null;
+  }
+
+  /** Computes axis length arrays, and total axis lengths. */
+  protected void computeAxisLengths() throws FormatException {
+    int sno = getSeries();
+
+    int[] count = fp.getCount();
+    int[] axes = ag[sno].getAxisTypes();
+    int numZ = ag[sno].getAxisCountZ();
+    int numC = ag[sno].getAxisCountC();
+    int numT = ag[sno].getAxisCountT();
+
+    core.sizeZ[sno] = sizeZ[sno];
+    core.sizeC[sno] = sizeC[sno];
+    core.sizeT[sno] = sizeT[sno];
+    lenZ[sno] = new int[numZ + 1];
+    lenC[sno] = new int[numC + 1];
+    lenT[sno] = new int[numT + 1];
+    lenZ[sno][0] = sizeZ[sno];
+    lenC[sno][0] = sizeC[sno];
+    lenT[sno][0] = sizeT[sno];
+    for (int i=0, z=1, c=1, t=1; i<axes.length; i++) {
+      switch (axes[i]) {
+        case AxisGuesser.Z_AXIS:
+          core.sizeZ[sno] *= count[i];
+          lenZ[sno][z++] = count[i];
+          break;
+        case AxisGuesser.C_AXIS:
+          core.sizeC[sno] *= count[i];
+          lenC[sno][c++] = count[i];
+          break;
+        case AxisGuesser.T_AXIS:
+          core.sizeT[sno] *= count[i];
+          lenT[sno][t++] = count[i];
+          break;
+        default:
+          throw new FormatException("Unknown axis type for axis #" +
+            i + ": " + axes[i]);
+      }
+    }
+
+    int[] cLengths = reader.getChannelDimLengths();
+    String[] cTypes = reader.getChannelDimTypes();
+    int cCount = 0;
+    for (int i=0; i<cLengths.length; i++) {
+      if (cLengths[i] > 1) cCount++;
+    }
+    for (int i=1; i<lenC[sno].length; i++) {
+      if (lenC[sno][i] > 1) cCount++;
+    }
+    if (cCount == 0) {
+      core.cLengths[sno] = new int[] {1};
+      core.cTypes[sno] = new String[] {FormatTools.CHANNEL};
+    }
+    else {
+      core.cLengths[sno] = new int[cCount];
+      core.cTypes[sno] = new String[cCount];
+    }
+    int c = 0;
+    for (int i=0; i<cLengths.length; i++) {
+      if (cLengths[i] == 1) continue;
+      core.cLengths[sno][c] = cLengths[i];
+      core.cTypes[sno][c] = cTypes[i];
+      c++;
+    }
+    for (int i=1; i<lenC[sno].length; i++) {
+      if (lenC[sno][i] == 1) continue;
+      core.cLengths[sno][c] = lenC[sno][i];
+      core.cTypes[sno][c] = FormatTools.CHANNEL;
+    }
+
+    // populate metadata store
+    int pixelType = getPixelType();
+    boolean little = reader.isLittleEndian();
+    MetadataStore s = reader.getMetadataStore();
+    s.setPixels(new Integer(core.sizeX[sno]), new Integer(core.sizeY[sno]),
+      new Integer(core.sizeZ[sno]), new Integer(core.sizeC[sno]),
+      new Integer(core.sizeT[sno]), new Integer(pixelType),
+      new Boolean(!little), core.currentOrder[sno], new Integer(sno), null);
+  }
+
+  /**
+   * Gets the file index, and image index into that file,
+   * corresponding to the given global image index.
+   *
+   * @return An array of size 2, dimensioned {file index, image index}.
+   */
+  protected int[] computeIndices(int no) throws FormatException, IOException {
+    int sno = getSeries();
+
+    int[] axes = ag[sno].getAxisTypes();
+    int[] count = fp.getCount();
+
+    // get Z, C and T positions
+    int[] zct = getZCTCoords(no);
+    zct[1] *= getRGBChannelCount();
+    int[] posZ = FormatTools.rasterToPosition(lenZ[sno], zct[0]);
+    int[] posC = FormatTools.rasterToPosition(lenC[sno], zct[1]);
+    int[] posT = FormatTools.rasterToPosition(lenT[sno], zct[2]);
+
+    // convert Z, C and T position lists into file index and image index
+    int[] pos = new int[axes.length];
+    int z = 1, c = 1, t = 1;
+    for (int i=0; i<axes.length; i++) {
+      if (axes[i] == AxisGuesser.Z_AXIS) pos[i] = posZ[z++];
+      else if (axes[i] == AxisGuesser.C_AXIS) pos[i] = posC[c++];
+      else if (axes[i] == AxisGuesser.T_AXIS) pos[i] = posT[t++];
+      else {
+        throw new FormatException("Unknown axis type for axis #" +
+          i + ": " + axes[i]);
+      }
+    }
+
+    int fno = FormatTools.positionToRaster(count, pos);
+
+    // configure the reader, in case we haven't done this one yet
+    readers[fno].setId(files[fno]);
+    readers[fno].setSeries(reader.getSeries());
+
+    int ino;
+    if (posZ[0] < readers[fno].getSizeZ() &&
+      posC[0] < readers[fno].getSizeC() && posT[0] < readers[fno].getSizeT())
+    {
+      ino = FormatTools.getIndex(readers[fno], posZ[0], posC[0], posT[0]);
+    }
+    else ino = Integer.MAX_VALUE; // coordinates out of range
+
+    return new int[] {fno, ino};
+  }
+
+  /**
+   * Gets a list of readers to include in relation to the given C position.
+   * @return Array with indices corresponding to the list of readers, and
+   *   values indicating the internal channel index to use for that reader.
+   */
+  protected int[] getIncludeList(int theC) throws FormatException, IOException {
+    int[] include = new int[readers.length];
+    Arrays.fill(include, -1);
+    for (int t=0; t<sizeT[getSeries()]; t++) {
+      for (int z=0; z<sizeZ[getSeries()]; z++) {
+        int no = getIndex(z, theC, t);
+        int[] q = computeIndices(no);
+        int fno = q[0], ino = q[1];
+        include[fno] = ino;
+      }
+    }
+    return include;
+  }
+
+  // -- Deprecated FileStitcher API methods --
+
+  /** @deprecated Replaced by {@link #getAxisTypes()} */
+  public int[] getAxisTypes(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getAxisTypes();
+  }
+
+  /** @deprecated Replaced by {@link #setAxisTypes(int[])} */
+  public void setAxisTypes(String id, int[] axes)
+    throws FormatException, IOException
+  {
+    setId(id);
+    setAxisTypes(axes);
+  }
+
+  /** @deprecated Replaced by {@link #getFilePattern()} */
+  public FilePattern getFilePattern(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getFilePattern();
+  }
+
+  /** @deprecated Replaced by {@link #getAxisGuesser()} */
+  public AxisGuesser getAxisGuesser(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getAxisGuesser();
+  }
+
+  // -- Deprecated IFormatReader API methods --
+
+  /** @deprecated Replaced by {@link #getImageCount()} */
+  public int getImageCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getImageCount();
+  }
+
+  /** @deprecated Replaced by {@link #isRGB()} */
+  public boolean isRGB(String id) throws FormatException, IOException {
+    setId(id);
+    return isRGB();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeX()} */
+  public int getSizeX(String id) throws FormatException, IOException {
+    setId(id);
+    return getSizeX();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeY()} */
+  public int getSizeY(String id) throws FormatException, IOException {
+    setId(id);
+    return getSizeY();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeZ()} */
+  public int getSizeZ(String id) throws FormatException, IOException {
+    setId(id);
+    return getSizeZ();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeC()} */
+  public int getSizeC(String id) throws FormatException, IOException {
+    setId(id);
+    return getSizeC();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeT()} */
+  public int getSizeT(String id) throws FormatException, IOException {
+    setId(id);
+    return getSizeT();
+  }
+
+  /** @deprecated Replaced by {@link #getPixelType()} */
+  public int getPixelType(String id) throws FormatException, IOException {
+    setId(id);
+    return getPixelType();
+  }
+
+  /** @deprecated Replaced by {@link #getEffectiveSizeC()} */
+  public int getEffectiveSizeC(String id) throws FormatException, IOException {
+    setId(id);
+    return getEffectiveSizeC();
+  }
+
+  /** @deprecated Replaced by {@link #getRGBChannelCount()} */
+  public int getRGBChannelCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getSizeC() / getEffectiveSizeC();
+  }
+
+  /** @deprecated Replaced by {@link #getChannelDimLengths()} */
+  public int[] getChannelDimLengths(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getChannelDimLengths();
+  }
+
+  /** @deprecated Replaced by {@link #getChannelDimTypes()} */
+  public String[] getChannelDimTypes(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getChannelDimTypes();
+  }
+
+  /** @deprecated Replaced by {@link #getThumbSizeX()} */
+  public int getThumbSizeX(String id) throws FormatException, IOException {
+    setId(id);
+    return getThumbSizeX();
+  }
+
+  /** @deprecated Replaced by {@link #getThumbSizeY()} */
+  public int getThumbSizeY(String id) throws FormatException, IOException {
+    setId(id);
+    return getThumbSizeY();
+  }
+
+  /** @deprecated Replaced by {@link #isLittleEndian()} */
+  public boolean isLittleEndian(String id) throws FormatException, IOException {
+    setId(id);
+    return isLittleEndian();
+  }
+
+  /** @deprecated Replaced by {@link #getDimensionOrder()} */
+  public String getDimensionOrder(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getDimensionOrder();
+  }
+
+  /** @deprecated Replaced by {@link #isOrderCertain()} */
+  public boolean isOrderCertain(String id) throws FormatException, IOException {
+    setId(id);
+    return isOrderCertain();
+  }
+
+  /** @deprecated Replaced by {@link #isInterleaved()} */
+  public boolean isInterleaved(String id) throws FormatException, IOException {
+    setId(id);
+    return isInterleaved();
+  }
+
+  /** @deprecated Replaced by {@link #isInterleaved(int)} */
+  public boolean isInterleaved(String id, int subC)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return isInterleaved(subC);
+  }
+
+  /** @deprecated Replaced by {@link #openImage(int)} */
+  public BufferedImage openImage(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return openImage(no);
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int)} */
+  public byte[] openBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return openBytes(no);
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int, byte[])} */
+  public byte[] openBytes(String id, int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return openBytes(no, buf);
+  }
+
+  /** @deprecated Replaced by {@link #openThumbImage(int)} */
+  public BufferedImage openThumbImage(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return openThumbImage(no);
+  }
+
+  /** @deprecated Replaced by {@link #openThumbImage(int)} */
+  public byte[] openThumbBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return openThumbBytes(no);
+  }
+
+  /** @deprecated Replaced by {@link #getSeriesCount()} */
+  public int getSeriesCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getSeriesCount();
+  }
+
+  /** @deprecated Replaced by {@link #setSeries(int)} */
+  public void setSeries(String id, int no) throws FormatException, IOException {
+    setId(id);
+    setSeries(no);
+  }
+
+  /** @deprecated Replaced by {@link #getSeries()} */
+  public int getSeries(String id) throws FormatException, IOException {
+    setId(id);
+    return getSeries();
+  }
+
+  /** @deprecated Replaced by {@link #getUsedFiles()} */
+  public String[] getUsedFiles(String id) throws FormatException, IOException {
+    setId(id);
+    return getUsedFiles();
+  }
+
+  /** @deprecated Replaced by {@link #getIndex(int, int, int)} */
+  public int getIndex(String id, int z, int c, int t)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getIndex(z, c, t);
+  }
+
+  /** @deprecated Replaced by {@link #getZCTCoords(int)} */
+  public int[] getZCTCoords(String id, int index)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getZCTCoords(index);
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataValue(String)} */
+  public Object getMetadataValue(String id, String field)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getMetadataValue(field);
+  }
+
+  /** @deprecated Replaced by {@link #getMetadata()} */
+  public Hashtable getMetadata(String id) throws FormatException, IOException {
+    setId(id);
+    return getMetadata();
+  }
+
+  /** @deprecated Replaced by {@link #getCoreMetadata()} */
+  public CoreMetadata getCoreMetadata(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getCoreMetadata();
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataStore()} */
+  public MetadataStore getMetadataStore(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getMetadataStore();
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataStoreRoot()} */
+  public Object getMetadataStoreRoot(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getMetadataStoreRoot();
+  }
+
+}
diff --git a/loci/formats/FormatException.java b/loci/formats/FormatException.java
new file mode 100644
index 0000000..38b7b29
--- /dev/null
+++ b/loci/formats/FormatException.java
@@ -0,0 +1,43 @@
+//
+// FormatException.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * FormatException is the exception thrown when something
+ * goes wrong performing a file format operation.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FormatException.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FormatException.java">SVN</a></dd></dl>
+ */
+public class FormatException extends Exception {
+
+  public FormatException() { super(); }
+  public FormatException(String s) { super(s); }
+  public FormatException(String s, Throwable cause) { super(s, cause); }
+  public FormatException(Throwable cause) { super(cause); }
+
+}
+
diff --git a/loci/formats/FormatHandler.java b/loci/formats/FormatHandler.java
new file mode 100644
index 0000000..afbcd5a
--- /dev/null
+++ b/loci/formats/FormatHandler.java
@@ -0,0 +1,189 @@
+//
+// FormatHandler.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+import java.util.Vector;
+
+/**
+ * Abstract superclass of all biological file format readers and writers.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FormatHandler.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FormatHandler.java">SVN</a></dd></dl>
+ */
+public abstract class FormatHandler implements IFormatHandler {
+
+  // -- Static fields --
+
+  /** Debugging flag. */
+  public static boolean debug = false;
+
+  /** Debugging level. 1=basic, 2=extended, 3=everything, 4=insane. */
+  public static int debugLevel = 1;
+
+  // -- Fields --
+
+  /** Name of this file format. */
+  protected String format;
+
+  /** Valid suffixes for this file format. */
+  protected String[] suffixes;
+
+  /** List of status listeners. */
+  protected Vector statusListeners = new Vector();
+
+  /** Name of current file. */
+  protected String currentId;
+
+  // -- Constructors --
+
+  /** Constructs a format handler with the given name and default suffix. */
+  public FormatHandler(String format, String suffix) {
+    this(format, suffix == null ? null : new String[] {suffix});
+  }
+
+  /** Constructs a format handler with the given name and default suffixes. */
+  public FormatHandler(String format, String[] suffixes) {
+    this.format = format;
+    this.suffixes = suffixes == null ? new String[0] : suffixes;
+  }
+
+  // -- Internal FormatHandler API methods --
+
+  /** Fires a status update event. */
+  protected void status(String message) {
+    status(new StatusEvent(message));
+  }
+
+  /** Fires a status update event. */
+  protected void status(int progress, int maximum, String message) {
+    status(new StatusEvent(progress, maximum, message));
+  }
+
+  /** Fires a status update event. */
+  protected void status(StatusEvent e) {
+    StatusListener[] l = getStatusListeners();
+    for (int i=0; i<l.length; i++) l[i].statusUpdated(e);
+  }
+
+  /** Issues a debugging statement. Convenience method for format handlers. */
+  protected void debug(String s) {
+    String name = getClass().getName();
+    String prefix = "loci.formats.";
+    if (name.startsWith(prefix)) {
+      name = name.substring(name.lastIndexOf(".") + 1);
+    }
+    String msg = System.currentTimeMillis() + ": " + name + ": " + s;
+    if (debugLevel > 3) LogTools.trace(msg);
+    else LogTools.println(msg);
+  }
+
+  /** Issues a stack trace. Convenience method for format handlers. */
+  protected void trace(String s) { LogTools.trace(s); }
+
+  /** Issues a stack trace. Convenience method for format handlers. */
+  protected void trace(Throwable t) { LogTools.trace(t); }
+
+  // -- IFormatHandler API methods --
+
+  /**
+   * Checks if a file matches the type of this format handler.
+   * The default implementation checks filename suffixes against
+   * those known for this format.
+   */
+  public boolean isThisType(String name) { return isThisType(name, true); }
+
+  /**
+   * Checks if a file matches the type of this format handler.
+   * The default implementation checks filename suffixes against
+   * those known for this format (the open parameter does nothing).
+   * @param open If true, and the file extension is insufficient to determine
+   *   the file type, the (existing) file is opened for further analysis.
+   *   Does nothing in the default implementation.
+   */
+  public boolean isThisType(String name, boolean open) {
+    String lname = name.toLowerCase();
+    for (int i=0; i<suffixes.length; i++) {
+      if (lname.endsWith("." + suffixes[i])) return true;
+      if (lname.endsWith("." + suffixes[i] + ".gz")) return true;
+      if (lname.endsWith("." + suffixes[i] + ".bz2")) return true;
+      if (lname.endsWith("." + suffixes[i] + ".zip")) return true;
+    }
+    return false;
+  }
+
+  /* @see IFormatHandler#getFormat() */
+  public String getFormat() { return format; }
+
+  /* @see IFormatHandler#getSuffixes() */
+  public String[] getSuffixes() { return suffixes; }
+
+  /* @see IFormatHandler#setId(String) */
+  public void setId(String id) throws FormatException, IOException {
+    setId(id, false);
+  }
+
+  // -- StatusReporter API methods --
+
+  /* @see StatusReporter#addStatusListener(StatusListener) */
+  public void addStatusListener(StatusListener l) {
+    synchronized (statusListeners) {
+      if (!statusListeners.contains(l)) statusListeners.add(l);
+    }
+  }
+
+  /* @see StatusReporter#removeStatusListener(StatusListener) */
+  public void removeStatusListener(StatusListener l) {
+    synchronized (statusListeners) {
+      statusListeners.remove(l);
+    }
+  }
+
+  /* @see StatusReporter#getStatusListeners() */
+  public StatusListener[] getStatusListeners() {
+    synchronized (statusListeners) {
+      StatusListener[] l = new StatusListener[statusListeners.size()];
+      statusListeners.copyInto(l);
+      return l;
+    }
+  }
+
+  // -- Utility methods --
+
+  /** Toggles debug mode (more verbose output and error messages). */
+  public static void setDebug(boolean debug) {
+    FormatHandler.debug = debug;
+  }
+
+  /**
+   * Toggles debug mode verbosity (which kinds of output are produced).
+   * @param debugLevel 1=basic, 2=extended, 3=everything.
+   */
+  public static void setDebugLevel(int debugLevel) {
+    FormatHandler.debugLevel = debugLevel;
+  }
+
+}
diff --git a/loci/formats/FormatReader.java b/loci/formats/FormatReader.java
new file mode 100644
index 0000000..5fc3c71
--- /dev/null
+++ b/loci/formats/FormatReader.java
@@ -0,0 +1,837 @@
+//
+// FormatReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Abstract superclass of all biological file format readers.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FormatReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FormatReader.java">SVN</a></dd></dl>
+ */
+public abstract class FormatReader extends FormatHandler
+  implements IFormatReader
+{
+
+  // -- Constants --
+
+  /** Default thumbnail width and height. */
+  protected static final int THUMBNAIL_DIMENSION = 128;
+
+  // -- Fields --
+
+  /** Current file. */
+  protected RandomAccessStream in;
+
+  /** Hashtable containing metadata key/value pairs. */
+  protected Hashtable metadata;
+
+  /** The number of the current series. */
+  protected int series = 0;
+
+  /** Core metadata values. */
+  protected CoreMetadata core;
+
+  /** Whether or not to normalize float data. */
+  protected boolean normalizeData;
+
+  /** Whether or not to filter out invalid metadata. */
+  protected boolean filterMetadata;
+
+  /** Whether or not to collect metadata. */
+  protected boolean collectMetadata = true;
+
+  /** Whether or not to save proprietary metadata in the MetadataStore. */
+  protected boolean saveOriginalMetadata = false;
+
+  /** Whether or not to group multi-file formats. */
+  protected boolean group = true;
+
+  /**
+   * Current metadata store. Should <b>never</b> be accessed directly as the
+   * semantics of {@link #getMetadataStore()} prevent "null" access.
+   */
+  protected MetadataStore metadataStore = new DummyMetadata();
+
+  // -- Constructors --
+
+  /** Constructs a format reader with the given name and default suffix. */
+  public FormatReader(String format, String suffix) { super(format, suffix); }
+
+  /** Constructs a format reader with the given name and default suffixes. */
+  public FormatReader(String format, String[] suffixes) {
+    super(format, suffixes);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /**
+   * Initializes the given file (parsing header information, etc.).
+   * Most subclasses should override this method to perform
+   * initialization operations such as parsing metadata.
+   */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (currentId != null) {
+      String[] s = getUsedFiles();
+      for (int i=0; i<s.length; i++) {
+        if (id.equals(s[i])) return;
+      }
+    }
+
+    series = 0;
+    close();
+    currentId = id;
+    metadata = new Hashtable();
+
+    core = new CoreMetadata(1);
+    Arrays.fill(core.orderCertain, true);
+
+    // reinitialize the MetadataStore
+    getMetadataStore().createRoot();
+  }
+
+  /**
+   * Opens the given file, reads in the first few KB and calls
+   * isThisType(byte[]) to check whether it matches this format.
+   */
+  protected boolean checkBytes(String name, int maxLen) {
+    try {
+      RandomAccessStream ras = new RandomAccessStream(name);
+      long len = ras.length();
+      byte[] buf = new byte[len < maxLen ? (int) len : maxLen];
+      ras.readFully(buf);
+      ras.close();
+      return isThisType(buf);
+    }
+    catch (IOException exc) { return false; }
+  }
+
+  /** Returns true if the given file name is in the used files list. */
+  protected boolean isUsedFile(String file) {
+    String[] usedFiles = getUsedFiles();
+    for (int i=0; i<usedFiles.length; i++) {
+      if (usedFiles[i].equals(file) ||
+        usedFiles[i].equals(new Location(file).getAbsolutePath()))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /** Adds an entry to the metadata table. */
+  protected void addMeta(String key, Object value) {
+    if (key == null || value == null || !collectMetadata) return;
+    if (filterMetadata) {
+      // verify key & value are not empty
+      if (key.length() == 0) return;
+      String val = value.toString();
+      if (val.length() == 0) return;
+
+      // verify key & value are reasonable length
+//      int maxLen = 8192;
+//      if (key.length() > maxLen) return;
+//      if (val.length() > maxLen) return;
+
+      // verify key & value start with printable characters
+      if (key.charAt(0) < 32) return;
+      if (val.charAt(0) < 32) return;
+
+      // verify key contains at least one alphabetic character
+      if (!key.matches(".*[a-zA-Z].*")) return;
+    }
+
+    if (saveOriginalMetadata) {
+      MetadataStore store = getMetadataStore();
+      if (MetadataTools.isOMEXMLMetadata(store)) {
+        MetadataTools.populateOriginalMetadata(store, key, value.toString());
+      }
+    }
+
+    metadata.put(key, value);
+  }
+
+  /** Gets a value from the metadata table. */
+  protected Object getMeta(String key) {
+    return metadata.get(key);
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see IFormatReader#getImageCount() */
+  public int getImageCount() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.imageCount[series];
+  }
+
+  /* @see IFormatReader#isRGB() */
+  public boolean isRGB() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.rgb[series];
+  }
+
+  /* @see IFormatReader#getSizeX() */
+  public int getSizeX() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.sizeX[series];
+  }
+
+  /* @see IFormatReader#getSizeY() */
+  public int getSizeY() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.sizeY[series];
+  }
+
+  /* @see IFormatReader#getSizeZ() */
+  public int getSizeZ() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.sizeZ[series];
+  }
+
+  /* @see IFormatReader#getSizeC() */
+  public int getSizeC() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.sizeC[series];
+  }
+
+  /* @see IFormatReader#getSizeT() */
+  public int getSizeT() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.sizeT[series];
+  }
+
+  /* @see IFormatReader#getPixelType() */
+  public int getPixelType() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.pixelType[series];
+  }
+
+  /* @see IFormatReader#getEffectiveSizeC() */
+  public int getEffectiveSizeC() {
+    // NB: by definition, imageCount == effectiveSizeC * sizeZ * sizeT
+    return getImageCount() / (getSizeZ() * getSizeT());
+  }
+
+  /* @see IFormatReader#getRGBChannelCount() */
+  public int getRGBChannelCount() {
+    return getSizeC() / getEffectiveSizeC();
+  }
+
+  /* @see IFormatReader#isIndexed() */
+  public boolean isIndexed() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.indexed[series];
+  }
+
+  /* @see IFormatReader#isFalseColor() */
+  public boolean isFalseColor() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.falseColor[series];
+  }
+
+  /* @see IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    return null;
+  }
+
+  /* @see IFormatReader#get16BitLookupTable() */
+  public short[][] get16BitLookupTable() throws FormatException, IOException {
+    return null;
+  }
+
+  /* @see IFormatReader#getChannelDimLengths() */
+  public int[] getChannelDimLengths() {
+    FormatTools.assertId(currentId, true, 1);
+    if (core.cLengths[series] == null) {
+      core.cLengths[series] = new int[] {core.sizeC[series]};
+    }
+    return core.cLengths[series];
+  }
+
+  /* @see IFormatReader#getChannelDimTypes() */
+  public String[] getChannelDimTypes() {
+    FormatTools.assertId(currentId, true, 1);
+    if (core.cTypes[series] == null) {
+      core.cTypes[series] = new String[] {FormatTools.CHANNEL};
+    }
+    return core.cTypes[series];
+  }
+
+  /* @see IFormatReader#getThumbSizeX() */
+  public int getThumbSizeX() {
+    FormatTools.assertId(currentId, true, 1);
+    if (core.thumbSizeX[series] == 0) {
+      int sx = getSizeX();
+      int sy = getSizeY();
+      core.thumbSizeX[series] =
+        sx > sy ? THUMBNAIL_DIMENSION : sx * THUMBNAIL_DIMENSION / sy;
+    }
+    return core.thumbSizeX[series];
+  }
+
+  /* @see IFormatReader#getThumbSizeY() */
+  public int getThumbSizeY() {
+    FormatTools.assertId(currentId, true, 1);
+    if (core.thumbSizeY[series] == 0) {
+      int sx = getSizeX();
+      int sy = getSizeY();
+      core.thumbSizeY[series] =
+        sy > sx ? THUMBNAIL_DIMENSION : sy * THUMBNAIL_DIMENSION / sx;
+    }
+    return core.thumbSizeY[series];
+  }
+
+  /* @see IFormatReader.isLittleEndian() */
+  public boolean isLittleEndian() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.littleEndian[series];
+  }
+
+  /* @see IFormatReader#getDimensionOrder() */
+  public String getDimensionOrder() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.currentOrder[series];
+  }
+
+  /* @see IFormatReader#isOrderCertain() */
+  public boolean isOrderCertain() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.orderCertain[series];
+  }
+
+  /* @see IFormatReader#isInterleaved() */
+  public boolean isInterleaved() {
+    return isInterleaved(0);
+  }
+
+  /* @see IFormatReader#isInterleaved(int) */
+  public boolean isInterleaved(int subC) {
+    FormatTools.assertId(currentId, true, 1);
+    return core.interleaved[series];
+  }
+
+  /* @see IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    byte[] buf = new byte[getSizeX() * getSizeY() * getRGBChannelCount() *
+      FormatTools.getBytesPerPixel(getPixelType())];
+    return openBytes(no, buf);
+  }
+
+  /* @see IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    byte[] buf = openBytes(no);
+
+    if (getPixelType() == FormatTools.FLOAT) {
+      float[] f =
+        (float[]) DataTools.makeDataArray(buf, 4, true, isLittleEndian());
+
+      if (normalizeData) f = DataTools.normalizeFloats(f);
+      return ImageTools.makeImage(f, core.sizeX[series], core.sizeY[series],
+        getRGBChannelCount(), true);
+    }
+
+    BufferedImage b = ImageTools.makeImage(buf, core.sizeX[series],
+      core.sizeY[series], isIndexed() ? 1 : getRGBChannelCount(),
+      core.interleaved[0], FormatTools.getBytesPerPixel(core.pixelType[series]),
+      core.littleEndian[series]);
+    if (isIndexed()) {
+      IndexedColorModel model = null;
+      if (core.pixelType[series] == FormatTools.UINT8 ||
+        core.pixelType[series] == FormatTools.INT8)
+      {
+        byte[][] table = get8BitLookupTable();
+        model = new IndexedColorModel(8, table[0].length, table);
+      }
+      else if (core.pixelType[series] == FormatTools.UINT16 ||
+        core.pixelType[series] == FormatTools.INT16)
+      {
+        short[][] table = get16BitLookupTable();
+        model = new IndexedColorModel(16, table[0].length, table);
+      }
+      if (model != null) {
+        WritableRaster raster = Raster.createWritableRaster(b.getSampleModel(),
+          b.getData().getDataBuffer(), null);
+        b = new BufferedImage(model, raster, false, null);
+      }
+    }
+    return b;
+  }
+
+  /* @see IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    return ImageTools.scale(openImage(no), getThumbSizeX(),
+      getThumbSizeY(), false);
+  }
+
+  /* @see IFormatReader#openThumbBytes(int) */
+  public byte[] openThumbBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    BufferedImage img = openThumbImage(no);
+    byte[][] bytes = ImageTools.getPixelBytes(img, core.littleEndian[series]);
+    if (bytes.length == 1) return bytes[0];
+    byte[] rtn = new byte[getRGBChannelCount() * bytes[0].length];
+    for (int i=0; i<getRGBChannelCount(); i++) {
+      System.arraycopy(bytes[i], 0, rtn, bytes[0].length * i, bytes[i].length);
+    }
+    return rtn;
+  }
+
+  /* @see IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly) {
+      if (in != null) in.close();
+    }
+    else close();
+  }
+
+  /* @see IFormatReader#getSeriesCount() */
+  public int getSeriesCount() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.sizeX.length;
+  }
+
+  /* @see IFormatReader#setSeries(int) */
+  public void setSeries(int no) {
+    if (no < 0 || no >= getSeriesCount()) {
+      throw new IllegalArgumentException("Invalid series: " + no);
+    }
+    series = no;
+  }
+
+  /* @see IFormatReader#getSeries() */
+  public int getSeries() {
+    return series;
+  }
+
+  /* @see IFormatReader#setGroupFiles(boolean) */
+  public void setGroupFiles(boolean groupFiles) {
+    FormatTools.assertId(currentId, false, 1);
+    group = groupFiles;
+  }
+
+  /* @see IFormatReader#isGroupFiles() */
+  public boolean isGroupFiles() {
+    return group;
+  }
+
+  /* @see IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id)
+    throws FormatException, IOException
+  {
+    return FormatTools.CANNOT_GROUP;
+  }
+
+  /* @see IFormatReader#isMetadataComplete() */
+  public boolean isMetadataComplete() {
+    FormatTools.assertId(currentId, true, 1);
+    return core.metadataComplete[series];
+  }
+
+  /* @see IFormatReader#setNormalized(boolean) */
+  public void setNormalized(boolean normalize) {
+    FormatTools.assertId(currentId, false, 1);
+    normalizeData = normalize;
+  }
+
+  /* @see IFormatReader#isNormalized() */
+  public boolean isNormalized() {
+    return normalizeData;
+  }
+
+  /* @see IFormatReader#setMetadataCollected(boolean) */
+  public void setMetadataCollected(boolean collect) {
+    FormatTools.assertId(currentId, false, 1);
+    collectMetadata = collect;
+  }
+
+  /* @see IFormatReader#isMetadataCollected() */
+  public boolean isMetadataCollected() {
+    return collectMetadata;
+  }
+
+  /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */
+  public void setOriginalMetadataPopulated(boolean populate) {
+    FormatTools.assertId(currentId, false, 1);
+    saveOriginalMetadata = populate;
+  }
+
+  /* @see IFormatReader#isOriginalMetadataPopulated() */
+  public boolean isOriginalMetadataPopulated() {
+    return saveOriginalMetadata;
+  }
+
+  /* @see IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    return new String[] {currentId};
+  }
+
+  /* @see IFormatReader#getCurrentFile() */
+  public String getCurrentFile() {
+    return currentId == null ? "" : currentId;
+  }
+
+  /* @see IFormatReader#getIndex(int, int, int) */
+  public int getIndex(int z, int c, int t) {
+    FormatTools.assertId(currentId, true, 1);
+    return FormatTools.getIndex(this, z, c, t);
+  }
+
+  /* @see IFormatReader#getZCTCoords(int) */
+  public int[] getZCTCoords(int index) {
+    FormatTools.assertId(currentId, true, 1);
+    return FormatTools.getZCTCoords(this, index);
+  }
+
+  /* @see IFormatReader#getMetadataValue(String) */
+  public Object getMetadataValue(String field) {
+    FormatTools.assertId(currentId, true, 1);
+    return getMeta(field);
+  }
+
+  /* @see IFormatReader#getMetadata() */
+  public Hashtable getMetadata() {
+    FormatTools.assertId(currentId, true, 1);
+    return metadata;
+  }
+
+  /* @see IFormatReader#getCoreMetadata() */
+  public CoreMetadata getCoreMetadata() {
+    FormatTools.assertId(currentId, true, 1);
+    return core;
+  }
+
+  /* @see IFormatReader#setMetadataFiltered(boolean) */
+  public void setMetadataFiltered(boolean filter) {
+    FormatTools.assertId(currentId, false, 1);
+    filterMetadata = filter;
+  }
+
+  /* @see IFormatReader#isMetadataFiltered() */
+  public boolean isMetadataFiltered() {
+    return filterMetadata;
+  }
+
+  /* @see IFormatReader#setMetadataStore(MetadataStore) */
+  public void setMetadataStore(MetadataStore store) {
+    FormatTools.assertId(currentId, false, 1);
+    if (store == null) {
+      throw new IllegalArgumentException("Metadata object is null");
+    }
+    metadataStore = store;
+  }
+
+  /* @see IFormatReader#getMetadataStore() */
+  public MetadataStore getMetadataStore() {
+    return metadataStore;
+  }
+
+  /* @see IFormatReader#getMetadataStoreRoot() */
+  public Object getMetadataStoreRoot() {
+    FormatTools.assertId(currentId, true, 1);
+    return getMetadataStore().getRoot();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see IFormatHandler#setId(String, boolean) */
+  public void setId(String id, boolean force)
+    throws FormatException, IOException
+  {
+    if (!id.equals(currentId) || force) initFile(id);
+  }
+
+  /* @see IFormatHandler#close() */
+  public void close() throws IOException {
+    if (in != null) in.close();
+    in = null;
+    currentId = null;
+  }
+
+  // -- Deprecated IFormatReader API methods --
+
+  /** @deprecated Replaced by {@link #getImageCount()} */
+  public int getImageCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getImageCount();
+  }
+
+  /** @deprecated Replaced by {@link #isRGB()} */
+  public boolean isRGB(String id) throws FormatException, IOException {
+    return getRGBChannelCount(id) > 1;
+  }
+
+  /** @deprecated Replaced by {@link #getSizeX()} */
+  public int getSizeX(String id) throws FormatException, IOException {
+    setId(id);
+    return core.sizeX[series];
+  }
+
+  /** @deprecated Replaced by {@link #getSizeY()} */
+  public int getSizeY(String id) throws FormatException, IOException {
+    setId(id);
+    return core.sizeY[series];
+  }
+
+  /** @deprecated Replaced by {@link #getSizeZ()} */
+  public int getSizeZ(String id) throws FormatException, IOException {
+    setId(id);
+    return core.sizeZ[series];
+  }
+
+  /** @deprecated Replaced by {@link #getSizeC()} */
+  public int getSizeC(String id) throws FormatException, IOException {
+    setId(id);
+    return core.sizeC[series];
+  }
+
+  /** @deprecated Replaced by {@link #getSizeT()} */
+  public int getSizeT(String id) throws FormatException, IOException {
+    setId(id);
+    return core.sizeT[series];
+  }
+
+  /** @deprecated Replaced by {@link #getPixelType()} */
+  public int getPixelType(String id) throws FormatException, IOException {
+    setId(id);
+    return core.pixelType[series];
+  }
+
+  /** @deprecated Replaced by {@link #getEffectiveSizeC()} */
+  public int getEffectiveSizeC(String id) throws FormatException, IOException {
+    // NB: by definition, imageCount == effectiveSizeC * sizeZ * sizeT
+    return getImageCount(id) / (getSizeZ(id) * getSizeT(id));
+  }
+
+  /** @deprecated Replaced by {@link #getRGBChannelCount()} */
+  public int getRGBChannelCount(String id) throws FormatException, IOException {
+    return getSizeC(id) / getEffectiveSizeC(id);
+  }
+
+  /** @deprecated Replaced by {@link #getChannelDimLengths()} */
+  public int[] getChannelDimLengths(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    if (core.cLengths[series] == null) {
+      core.cLengths[series] = new int[] {core.sizeC[series]};
+    }
+    return core.cLengths[series];
+  }
+
+  /** @deprecated Replaced by {@link #getChannelDimTypes()} */
+  public String[] getChannelDimTypes(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    if (core.cTypes[series] == null) {
+      core.cTypes[series] = new String[] {FormatTools.CHANNEL};
+    }
+    return core.cTypes[series];
+  }
+
+  /** @deprecated Replaced by {@link #getThumbSizeX()} */
+  public int getThumbSizeX(String id) throws FormatException, IOException {
+    int sx = getSizeX(id);
+    int sy = getSizeY(id);
+    return sx > sy ? THUMBNAIL_DIMENSION : sx * THUMBNAIL_DIMENSION / sy;
+  }
+
+  /** @deprecated Replaced by {@link #getThumbSizeY()} */
+  public int getThumbSizeY(String id) throws FormatException, IOException {
+    int sx = getSizeX(id);
+    int sy = getSizeY(id);
+    return sy > sx ? THUMBNAIL_DIMENSION : sy * THUMBNAIL_DIMENSION / sx;
+  }
+
+  /** @deprecated Replaced by {@link #isLittleEndian()} */
+  public boolean isLittleEndian(String id) throws FormatException, IOException {
+    setId(id);
+    return isLittleEndian();
+  }
+
+  /** @deprecated Replaced by {@link #getDimensionOrder()} */
+  public String getDimensionOrder(String id) throws FormatException, IOException
+  {
+    setId(id);
+    return core.currentOrder[series];
+  }
+
+  /** @deprecated Replaced by {@link #isOrderCertain()} */
+  public boolean isOrderCertain(String id) throws FormatException, IOException {
+    setId(id);
+    return core.orderCertain[series];
+  }
+
+  /** @deprecated Replaced by {@link #isInterleaved()} */
+  public boolean isInterleaved(String id)
+    throws FormatException, IOException
+  {
+    return isInterleaved(id, 0);
+  }
+
+  /** @deprecated Replaced by {@link #isInterleaved(int)} */
+  public boolean isInterleaved(String id, int subC)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return isInterleaved(subC);
+  }
+
+  /** @deprecated Replaced by {@link #openImage(int)} */
+  public BufferedImage openImage(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return openImage(no);
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int)} */
+  public byte[] openBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return openBytes(no);
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int, byte[])} */
+  public byte[] openBytes(String id, int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    return openBytes(id, no);
+  }
+
+  /** @deprecated Replaced by {@link #openThumbImage(int)} */
+  public BufferedImage openThumbImage(String id, int no)
+    throws FormatException, IOException
+  {
+    return ImageTools.scale(openImage(id, no),
+      getThumbSizeX(id), getThumbSizeY(id), false);
+  }
+
+  /** @deprecated Replaced by {@link #openThumbBytes(int)} */
+  public byte[] openThumbBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    BufferedImage img = openThumbImage(id, no);
+    byte[][] bytes = ImageTools.getBytes(img);
+    if (bytes.length == 1) return bytes[0];
+    byte[] rtn = new byte[getRGBChannelCount(id) * bytes[0].length];
+    for (int i=0; i<getRGBChannelCount(id); i++) {
+      System.arraycopy(bytes[i], 0, rtn, bytes[0].length * i, bytes[i].length);
+    }
+    return rtn;
+  }
+
+  /** @deprecated Replaced by {@link #getSeriesCount()} */
+  public int getSeriesCount(String id) throws FormatException, IOException {
+    setId(id);
+    return 1;
+  }
+
+  /** @deprecated Replaced by {@link #setSeries(int)} */
+  public void setSeries(String id, int no) throws FormatException, IOException {
+    if (no < 0 || no >= getSeriesCount(id)) {
+      throw new FormatException("Invalid series: " + no);
+    }
+    series = no;
+  }
+
+  /** @deprecated Replaced by {@link #getSeries()} */
+  public int getSeries(String id) throws FormatException, IOException {
+    setId(id);
+    return series;
+  }
+
+  /** @deprecated Replaced by {@link #getUsedFiles()} */
+  public String[] getUsedFiles(String id) throws FormatException, IOException {
+    setId(id);
+    return new String[] {id};
+  }
+
+  /** @deprecated Replaced by {@link #getIndex(int, int, int)} */
+  public int getIndex(String id, int z, int c, int t)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return FormatTools.getIndex(this, z, c, t);
+  }
+
+  /** @deprecated Replaced by {@link #getZCTCoords(int)} */
+  public int[] getZCTCoords(String id, int index)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return FormatTools.getZCTCoords(this, index);
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataValue(String)} */
+  public Object getMetadataValue(String id, String field)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getMeta(field);
+  }
+
+  /** @deprecated Replaced by {@link #getMetadata()} */
+  public Hashtable getMetadata(String id) throws FormatException, IOException {
+    setId(id);
+    return metadata;
+  }
+
+  /** @deprecated Replaced by {@link #getCoreMetadata()} */
+  public CoreMetadata getCoreMetadata(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return core;
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataStore()} */
+  public MetadataStore getMetadataStore(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return metadataStore;
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataStoreRoot()} */
+  public Object getMetadataStoreRoot(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getMetadataStore().getRoot();
+  }
+
+}
diff --git a/loci/formats/FormatTools.java b/loci/formats/FormatTools.java
new file mode 100644
index 0000000..5fc9bca
--- /dev/null
+++ b/loci/formats/FormatTools.java
@@ -0,0 +1,466 @@
+//
+// FormatTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * A utility class for format reader and writer implementations.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FormatTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FormatTools.java">SVN</a></dd></dl>
+ */
+public final class FormatTools {
+
+  // -- Constants --
+
+  /** Identifies the <i>INT8</i> data type used to store pixel values. */
+  public static final int INT8 = 0;
+
+  /** Identifies the <i>UINT8</i> data type used to store pixel values. */
+  public static final int UINT8 = 1;
+
+  /** Identifies the <i>INT16</i> data type used to store pixel values. */
+  public static final int INT16 = 2;
+
+  /** Identifies the <i>UINT16</i> data type used to store pixel values. */
+  public static final int UINT16 = 3;
+
+  /** Identifies the <i>INT32</i> data type used to store pixel values. */
+  public static final int INT32 = 4;
+
+  /** Identifies the <i>UINT32</i> data type used to store pixel values. */
+  public static final int UINT32 = 5;
+
+  /** Identifies the <i>FLOAT</i> data type used to store pixel values. */
+  public static final int FLOAT = 6;
+
+  /** Identifies the <i>DOUBLE</i> data type used to store pixel values. */
+  public static final int DOUBLE = 7;
+
+  /** Human readable pixel type. */
+  private static String[] pixelTypes;
+  static {
+    pixelTypes = new String[8];
+    pixelTypes[INT8] = "int8";
+    pixelTypes[UINT8] = "uint8";
+    pixelTypes[INT16] = "int16";
+    pixelTypes[UINT16] = "uint16";
+    pixelTypes[INT32] = "int32";
+    pixelTypes[UINT32] = "uint32";
+    pixelTypes[FLOAT] = "float";
+    pixelTypes[DOUBLE] = "double";
+  }
+
+  /**
+   * Identifies the <i>Channel</i> dimensional type,
+   * representing a generic channel dimension.
+   */
+  public static final String CHANNEL = "Channel";
+
+  /**
+   * Identifies the <i>Spectra</i> dimensional type,
+   * representing a dimension consisting of spectral channels.
+   */
+  public static final String SPECTRA = "Spectra";
+
+  /**
+   * Identifies the <i>Lifetime</i> dimensional type,
+   * representing a dimension consisting of a lifetime histogram.
+   */
+  public static final String LIFETIME = "Lifetime";
+
+  /**
+   * Identifies the <i>Polarization</i> dimensional type,
+   * representing a dimension consisting of polarization states.
+   */
+  public static final String POLARIZATION = "Polarization";
+
+  /** File grouping options. */
+  public static final int MUST_GROUP = 0;
+  public static final int CAN_GROUP = 1;
+  public static final int CANNOT_GROUP = 2;
+
+  // -- Constructor --
+
+  private FormatTools() { }
+
+  // -- Utility methods - dimensional positions --
+
+  /**
+   * Gets the rasterized index corresponding
+   * to the given Z, C and T coordinates.
+   */
+  public static int getIndex(IFormatReader reader, int z, int c, int t) {
+    String order = reader.getDimensionOrder();
+    int zSize = reader.getSizeZ();
+    int cSize = reader.getEffectiveSizeC();
+    int tSize = reader.getSizeT();
+    int num = reader.getImageCount();
+    return getIndex(order, zSize, cSize, tSize, num, z, c, t);
+  }
+
+  /**
+   * Gets the rasterized index corresponding
+   * to the given Z, C and T coordinates.
+   */
+  public static int getIndex(String order, int zSize, int cSize, int tSize,
+    int num, int z, int c, int t)
+  {
+    // check DimensionOrder
+    if (order == null) {
+      throw new IllegalArgumentException("Dimension order is null");
+    }
+    if (!order.startsWith("XY") && !order.startsWith("YX")) {
+      throw new IllegalArgumentException("Invalid dimension order: " + order);
+    }
+    int iz = order.indexOf("Z") - 2;
+    int ic = order.indexOf("C") - 2;
+    int it = order.indexOf("T") - 2;
+    if (iz < 0 || iz > 2 || ic < 0 || ic > 2 || it < 0 || it > 2) {
+      throw new IllegalArgumentException("Invalid dimension order: " + order);
+    }
+
+    // check SizeZ
+    if (zSize <= 0) {
+      throw new IllegalArgumentException("Invalid Z size: " + zSize);
+    }
+    if (z < 0 || z >= zSize) {
+      throw new IllegalArgumentException("Invalid Z index: " + z + "/" + zSize);
+    }
+
+    // check SizeC
+    if (cSize <= 0) {
+      throw new IllegalArgumentException("Invalid C size: " + cSize);
+    }
+    if (c < 0 || c >= cSize) {
+      throw new IllegalArgumentException("Invalid C index: " + c + "/" + cSize);
+    }
+
+    // check SizeT
+    if (tSize <= 0) {
+      throw new IllegalArgumentException("Invalid T size: " + tSize);
+    }
+    if (t < 0 || t >= tSize) {
+      throw new IllegalArgumentException("Invalid T index: " + t + "/" + tSize);
+    }
+
+    // check image count
+    if (num <= 0) {
+      throw new IllegalArgumentException("Invalid image count: " + num);
+    }
+    if (num != zSize * cSize * tSize) {
+      // if this happens, there is probably a bug in metadata population --
+      // either one of the ZCT sizes, or the total number of images --
+      // or else the input file is invalid
+      throw new IllegalArgumentException("ZCT size vs image count mismatch " +
+        "(sizeZ=" + zSize + ", sizeC=" + cSize + ", sizeT=" + tSize +
+        ", total=" + num + ")");
+    }
+
+    // assign rasterization order
+    int v0 = iz == 0 ? z : (ic == 0 ? c : t);
+    int v1 = iz == 1 ? z : (ic == 1 ? c : t);
+    int v2 = iz == 2 ? z : (ic == 2 ? c : t);
+    int len0 = iz == 0 ? zSize : (ic == 0 ? cSize : tSize);
+    int len1 = iz == 1 ? zSize : (ic == 1 ? cSize : tSize);
+
+    return v0 + v1 * len0 + v2 * len0 * len1;
+  }
+
+  /**
+   * Gets the Z, C and T coordinates corresponding
+   * to the given rasterized index value.
+   */
+  public static int[] getZCTCoords(IFormatReader reader, int index) {
+    String order = reader.getDimensionOrder();
+    int zSize = reader.getSizeZ();
+    int cSize = reader.getEffectiveSizeC();
+    int tSize = reader.getSizeT();
+    int num = reader.getImageCount();
+    return getZCTCoords(order, zSize, cSize, tSize, num, index);
+  }
+
+  /**
+   * Gets the Z, C and T coordinates corresponding to the given rasterized
+   * index value.
+   */
+  public static int[] getZCTCoords(String order,
+    int zSize, int cSize, int tSize, int num, int index)
+  {
+    // check DimensionOrder
+    if (order == null) {
+      throw new IllegalArgumentException("Dimension order is null");
+    }
+    if (!order.startsWith("XY") && !order.startsWith("YX")) {
+      throw new IllegalArgumentException("Invalid dimension order: " + order);
+    }
+    int iz = order.indexOf("Z") - 2;
+    int ic = order.indexOf("C") - 2;
+    int it = order.indexOf("T") - 2;
+    if (iz < 0 || iz > 2 || ic < 0 || ic > 2 || it < 0 || it > 2) {
+      throw new IllegalArgumentException("Invalid dimension order: " + order);
+    }
+
+    // check SizeZ
+    if (zSize <= 0) {
+      throw new IllegalArgumentException("Invalid Z size: " + zSize);
+    }
+
+    // check SizeC
+    if (cSize <= 0) {
+      throw new IllegalArgumentException("Invalid C size: " + cSize);
+    }
+
+    // check SizeT
+    if (tSize <= 0) {
+      throw new IllegalArgumentException("Invalid T size: " + tSize);
+    }
+
+    // check image count
+    if (num <= 0) {
+      throw new IllegalArgumentException("Invalid image count: " + num);
+    }
+    if (num != zSize * cSize * tSize) {
+      // if this happens, there is probably a bug in metadata population --
+      // either one of the ZCT sizes, or the total number of images --
+      // or else the input file is invalid
+      throw new IllegalArgumentException("ZCT size vs image count mismatch " +
+        "(sizeZ=" + zSize + ", sizeC=" + cSize + ", sizeT=" + tSize +
+        ", total=" + num + ")");
+    }
+    if (index < 0 || index >= num) {
+      throw new IllegalArgumentException("Invalid image index: " +
+        index + "/" + num);
+    }
+
+    // assign rasterization order
+    int len0 = iz == 0 ? zSize : (ic == 0 ? cSize : tSize);
+    int len1 = iz == 1 ? zSize : (ic == 1 ? cSize : tSize);
+    //int len2 = iz == 2 ? sizeZ : (ic == 2 ? sizeC : sizeT);
+    int v0 = index % len0;
+    int v1 = index / len0 % len1;
+    int v2 = index / len0 / len1;
+    int z = iz == 0 ? v0 : (iz == 1 ? v1 : v2);
+    int c = ic == 0 ? v0 : (ic == 1 ? v1 : v2);
+    int t = it == 0 ? v0 : (it == 1 ? v1 : v2);
+
+    return new int[] {z, c, t};
+  }
+
+  /** Converts indices from the given dimension order to the native one. */
+  public static int getReorderedIndex(IFormatReader r, String order, int no)
+    throws FormatException
+  {
+    int[] zct = getZCTCoords(order, r.getSizeZ(), r.getSizeC(), r.getSizeT(),
+      r.getImageCount(), no);
+    return getIndex(r.getDimensionOrder(), r.getSizeZ(), r.getSizeC(),
+      r.getSizeT(), r.getImageCount(), zct[0], zct[1], zct[2]);
+  }
+
+  /**
+   * Computes a unique 1-D index corresponding
+   * to the given multidimensional position.
+   * @param lengths the maximum value for each positional dimension
+   * @param pos position along each dimensional axis
+   * @return rasterized index value
+   */
+  public static int positionToRaster(int[] lengths, int[] pos) {
+    int offset = 1;
+    int raster = 0;
+    for (int i=0; i<pos.length; i++) {
+      raster += offset * pos[i];
+      offset *= lengths[i];
+    }
+    return raster;
+  }
+
+  /**
+   * Computes a unique N-D position corresponding
+   * to the given rasterized index value.
+   * @param lengths the maximum value at each positional dimension
+   * @param raster rasterized index value
+   * @return position along each dimensional axis
+   */
+  public static int[] rasterToPosition(int[] lengths, int raster) {
+    return rasterToPosition(lengths, raster, new int[lengths.length]);
+  }
+
+  /**
+   * Computes a unique N-D position corresponding
+   * to the given rasterized index value.
+   * @param lengths the maximum value at each positional dimension
+   * @param raster rasterized index value
+   * @param pos preallocated position array to populate with the result
+   * @return position along each dimensional axis
+   */
+  public static int[] rasterToPosition(int[] lengths, int raster, int[] pos) {
+    int offset = 1;
+    for (int i=0; i<pos.length; i++) {
+      int offset1 = offset * lengths[i];
+      int q = i < pos.length - 1 ? raster % offset1 : raster;
+      pos[i] = q / offset;
+      raster -= q;
+      offset = offset1;
+    }
+    return pos;
+  }
+
+  /**
+   * Computes the number of raster values for a positional array
+   * with the given lengths.
+   */
+  public static int getRasterLength(int[] lengths) {
+    int len = 1;
+    for (int i=0; i<lengths.length; i++) len *= lengths[i];
+    return len;
+  }
+
+  // -- Utility methods - pixel types --
+
+  /**
+   * Takes a string value and maps it to one of the pixel type enumerations.
+   * @param pixelTypeAsString the pixel type as a string.
+   * @return type enumeration value for use with class constants.
+   */
+  public static int pixelTypeFromString(String pixelTypeAsString) {
+    String lowercaseTypeAsString = pixelTypeAsString.toLowerCase();
+    for (int i = 0; i < pixelTypes.length; i++) {
+      if (pixelTypes[i].equals(lowercaseTypeAsString)) return i;
+    }
+    throw new RuntimeException("Unknown type: '" + pixelTypeAsString + "'");
+  }
+
+  /**
+   * Takes a pixel type value and gets a corresponding string representation.
+   * @param pixelType the pixel type.
+   * @return string value for human-readable output.
+   */
+  public static String getPixelTypeString(int pixelType) {
+    if (pixelType < 0 || pixelType >= pixelTypes.length) {
+      throw new IllegalArgumentException("Unknown pixel type: " + pixelType);
+    }
+    return pixelTypes[pixelType];
+  }
+
+  /**
+   * Retrieves how many bytes per pixel the current plane or section has.
+   * @param pixelType the pixel type as retrieved from
+   *   {@link IFormatReader#getPixelType()}.
+   * @return the number of bytes per pixel.
+   * @see IFormatReader#getPixelType(String)
+   */
+  public static int getBytesPerPixel(int pixelType) {
+    switch (pixelType) {
+      case INT8:
+      case UINT8:
+        return 1;
+      case INT16:
+      case UINT16:
+        return 2;
+      case INT32:
+      case UINT32:
+      case FLOAT:
+        return 4;
+      case DOUBLE:
+        return 8;
+    }
+    throw new IllegalArgumentException("Unknown pixel type: " + pixelType);
+  }
+
+  // -- Utility methods - metadata
+
+  /**
+   * Populates the 'pixels' element of the given metadata store, using core
+   * metadata from the given reader.
+   */
+  public static void populatePixels(MetadataStore store, IFormatReader r) {
+    int oldSeries = r.getSeries();
+    for (int i=0; i<r.getSeriesCount(); i++) {
+      Integer ii = new Integer(i);
+      r.setSeries(i);
+      store.setPixels(new Integer(r.getSizeX()), new Integer(r.getSizeY()),
+        new Integer(r.getSizeZ()), new Integer(r.getSizeC()),
+        new Integer(r.getSizeT()), new Integer(r.getPixelType()),
+        new Boolean(!r.isLittleEndian()), r.getDimensionOrder(), ii, null);
+    }
+    r.setSeries(oldSeries);
+  }
+
+  // -- Utility methods - sanity checking
+
+  /**
+   * Asserts that the current file is either null, or not, according to the
+   * given flag. If the assertion fails, an IllegalStateException is thrown.
+   * @param currentId File name to test.
+   * @param notNull True iff id should be non-null.
+   * @param depth How far back in the stack the calling method is; this name
+   *   is reported as part of the exception message, if available. Use zero
+   *   to suppress output of the calling method name.
+   */
+  public static void assertId(String currentId, boolean notNull, int depth) {
+    String msg = null;
+    if (currentId == null && notNull) {
+      msg = "Current file should not be null; call setId(String) first";
+    }
+    else if (currentId != null && !notNull) {
+      msg = "Current file should be null, but is '" +
+        currentId + "'; call close() first";
+    }
+    if (msg == null) return;
+
+    StackTraceElement[] ste = new Exception().getStackTrace();
+    String header;
+    if (depth > 0 && ste.length > depth) {
+      String c = ste[depth].getClassName();
+      if (c.startsWith("loci.formats.")) {
+        c = c.substring(c.lastIndexOf(".") + 1);
+      }
+      header = c + "." + ste[depth].getMethodName() + ": ";
+    }
+    else header = "";
+    throw new IllegalStateException(header + msg);
+  }
+
+  /** Checks that the given plane number is valid for the given reader. */
+  public static void checkPlaneNumber(IFormatReader r, int no)
+    throws FormatException
+  {
+    if (no < 0 || no >= r.getImageCount()) {
+      throw new FormatException("Invalid image number: " + no);
+    }
+  }
+
+  public static void checkBufferSize(IFormatReader r, int len)
+    throws FormatException
+  {
+    int size = r.getSizeX() * r.getSizeY() *
+      (r.isIndexed() ? 1 : r.getRGBChannelCount()) *
+      getBytesPerPixel(r.getPixelType());
+    if (size > len) {
+      throw new FormatException("Buffer too small.");
+    }
+  }
+
+}
diff --git a/loci/formats/FormatWriter.java b/loci/formats/FormatWriter.java
new file mode 100644
index 0000000..6709e0e
--- /dev/null
+++ b/loci/formats/FormatWriter.java
@@ -0,0 +1,218 @@
+//
+// FormatWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.Image;
+import java.awt.image.*;
+import java.io.IOException;
+
+/**
+ * Abstract superclass of all biological file format writers.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/FormatWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/FormatWriter.java">SVN</a></dd></dl>
+ */
+public abstract class FormatWriter extends FormatHandler
+  implements IFormatWriter
+{
+
+  // -- Fields --
+
+  /** Frame rate to use when writing in frames per second, if applicable. */
+  protected int fps = 10;
+
+  /** Default color model. */
+  protected ColorModel cm;
+
+  /** Available compression types. */
+  protected String[] compressionTypes;
+
+  /** Current compression type. */
+  protected String compression;
+
+  /** Whether the current file has been prepped for writing. */
+  protected boolean initialized;
+
+  /**
+   * Current metadata retrieval object. Should <b>never</b> be accessed
+   * directly as the semantics of {@link #getMetadataRetrieve()}
+   * prevent "null" access.
+   */
+  protected MetadataRetrieve metadataRetrieve = new DummyMetadata();
+
+  // -- Constructors --
+
+  /** Constructs a format writer with the given name and default suffix. */
+  public FormatWriter(String format, String suffix) { super(format, suffix); }
+
+  /** Constructs a format writer with the given name and default suffixes. */
+  public FormatWriter(String format, String[] suffixes) {
+    super(format, suffixes);
+  }
+
+  // -- IFormatWriter API methods --
+
+  /* @see IFormatWriter#saveBytes(byte[], boolean) */
+  public void saveBytes(byte[] bytes, boolean last)
+    throws FormatException, IOException
+  {
+    saveBytes(bytes, 0, last, last);
+  }
+
+  /* @see IFormatWriter#saveBytes(byte[], int, boolean, boolean) */
+  public void saveBytes(byte[] bytes, int series, boolean lastInSeries,
+    boolean last) throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    MetadataRetrieve r = getMetadataRetrieve();
+    if (r == null) throw new FormatException("MetadataRetrieve cannot be null");
+    Integer ss = new Integer(series);
+    int width = r.getSizeX(ss).intValue();
+    int height = r.getSizeY(ss).intValue();
+    int channels = r.getSizeC(ss).intValue();
+    int type = FormatTools.pixelTypeFromString(r.getPixelType(ss));
+    boolean littleEndian = !r.getBigEndian(ss).booleanValue();
+
+    BufferedImage img = ImageTools.makeImage(bytes, width, height, channels,
+      true, FormatTools.getBytesPerPixel(type), littleEndian);
+    saveImage(img, series, lastInSeries, last);
+  }
+
+  /* @see IFormatWriter#saveImage(Image, int, boolean, boolean) */
+  public void saveImage(Image image, int series, boolean lastInSeries,
+    boolean last) throws FormatException, IOException
+  {
+    throw new FormatException("Not implemented yet.");
+  }
+
+  /* @see IFormatWriter#canDoStacks() */
+  public boolean canDoStacks() { return false; }
+
+  /* @see IFormatWriter#setMetadataRetrieve(MetadataRetrieve) */
+  public void setMetadataRetrieve(MetadataRetrieve retrieve) {
+    FormatTools.assertId(currentId, false, 1);
+    if (retrieve == null) {
+      throw new IllegalArgumentException("Metadata object is null");
+    }
+    metadataRetrieve = retrieve;
+  }
+
+  /* @see IFormatWriter#getMetadataRetrieve() */
+  public MetadataRetrieve getMetadataRetrieve() {
+    return metadataRetrieve;
+  }
+
+  /* @see IFormatWriter#setColorModel(ColorModel) */
+  public void setColorModel(ColorModel model) { cm = model; }
+
+  /* @see IFormatWriter#getColorModel() */
+  public ColorModel getColorModel() { return cm; }
+
+  /* @see IFormatWriter#setFramesPerSecond(int) */
+  public void setFramesPerSecond(int rate) { fps = rate; }
+
+  /* @see IFormatWriter#getFramesPerSecond() */
+  public int getFramesPerSecond() { return fps; }
+
+  /* @see IFormatWriter#getCompressionTypes() */
+  public String[] getCompressionTypes() { return compressionTypes; }
+
+  /* @see IFormatWriter#setCompression(compress) */
+  public void setCompression(String compress) throws FormatException {
+    // check that this is a valid type
+    for (int i=0; i<compressionTypes.length; i++) {
+      if (compressionTypes[i].equals(compress)) {
+        compression = compress;
+        return;
+      }
+    }
+    throw new FormatException("Invalid compression type: " + compress);
+  }
+
+  /* @see IFormatWriter#getPixelTypes() */
+  public int[] getPixelTypes() {
+    return new int[] {FormatTools.UINT8, FormatTools.UINT16,
+      FormatTools.UINT32, FormatTools.FLOAT};
+  }
+
+  /* @see IFormatWriter#isSupportedType(int) */
+  public boolean isSupportedType(int type) {
+    int[] types = getPixelTypes();
+    for (int i=0; i<types.length; i++) {
+      if (type == types[i]) return true;
+    }
+    return false;
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see IFormatHandler#setId(String, boolean) */
+  public void setId(String id, boolean force)
+    throws FormatException, IOException
+  {
+    if (id.equals(currentId) && !force) return;
+    close();
+    currentId = id;
+    initialized = false;
+  }
+
+  // -- Deprecated IFormatWriter API methods --
+
+  /** @deprecated Replaced by {@link #canDoStacks()} */
+  public boolean canDoStacks(String id) throws FormatException {
+    try {
+      setId(id);
+    }
+    catch (IOException exc) {
+      // NB: should never happen
+      throw new FormatException(exc);
+    }
+    return canDoStacks(id);
+  }
+
+  /** @deprecated Replaced by {@link #getPixelTypes()} */
+  public int[] getPixelTypes(String id) throws FormatException, IOException {
+    setId(id);
+    return getPixelTypes(id);
+  }
+
+  /** @deprecated Replaced by {@link #isSupportedType(int type)} */
+  public boolean isSupportedType(String id, int type)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return isSupportedType(type);
+  }
+
+  /** @deprecated Replaced by {@link #saveImage(Image, boolean)} */
+  public void save(String id, Image image, boolean last)
+    throws FormatException, IOException
+  {
+    setId(id);
+    saveImage(image, last);
+  }
+
+}
diff --git a/loci/formats/IFormatHandler.java b/loci/formats/IFormatHandler.java
new file mode 100644
index 0000000..c6efe2d
--- /dev/null
+++ b/loci/formats/IFormatHandler.java
@@ -0,0 +1,66 @@
+//
+// IFormatHandler.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.IOException;
+
+/**
+ * Interface for all biological file format readers and writers.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/IFormatHandler.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/IFormatHandler.java">SVN</a></dd></dl>
+ */
+public interface IFormatHandler extends StatusReporter {
+
+  /** Checks if the given string is a valid filename for this file format. */
+  boolean isThisType(String name);
+
+  /**
+   * Checks if the given string is a valid filename for this file format.
+   * @param open If true, and the file extension is insufficient to determine
+   *   the file type, the (existing) file is opened for further analysis.
+   */
+  boolean isThisType(String name, boolean open);
+
+  /** Gets the name of this file format. */
+  String getFormat();
+
+  /** Gets the default file suffixes for this file format. */
+  String[] getSuffixes();
+
+  /** Sets the current file name. */
+  void setId(String id) throws FormatException, IOException;
+
+  /**
+   * Sets the current file name.
+   * @param force If set, the handler will be re-initialized no matter what.
+   */
+  void setId(String id, boolean force) throws FormatException, IOException;
+
+  /** Closes currently open file(s) and frees allocated memory. */
+  void close() throws IOException;
+
+}
diff --git a/loci/formats/IFormatReader.java b/loci/formats/IFormatReader.java
new file mode 100644
index 0000000..57c4bdc
--- /dev/null
+++ b/loci/formats/IFormatReader.java
@@ -0,0 +1,447 @@
+//
+// IFormatReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.Hashtable;
+
+/**
+ * Interface for all biological file format readers.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/IFormatReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/IFormatReader.java">SVN</a></dd></dl>
+ */
+public interface IFormatReader extends IFormatHandler {
+
+  // -- Constants --
+
+  /** File grouping options. */
+  int MUST_GROUP = 0;
+  int CAN_GROUP = 1;
+  int CANNOT_GROUP = 2;
+
+  // -- IFormatReader API methods --
+
+  /** Checks if the given block is a valid header for this file format. */
+  boolean isThisType(byte[] block);
+
+  /** Determines the number of images in the current file. */
+  int getImageCount();
+
+  /** Checks if the images in the file are RGB. */
+  boolean isRGB();
+
+  /** Gets the size of the X dimension. */
+  int getSizeX();
+
+  /** Gets the size of the Y dimension. */
+  int getSizeY();
+
+  /** Gets the size of the Z dimension. */
+  int getSizeZ();
+
+  /** Gets the size of the C dimension. */
+  int getSizeC();
+
+  /** Gets the size of the T dimension. */
+  int getSizeT();
+
+  /**
+   * Gets the pixel type.
+   * @return the pixel type as an enumeration from <code>FormatTools</code>
+   * <i>static</i> pixel types such as <code>INT8</code>.
+   */
+  int getPixelType();
+
+  /**
+   * Gets the effective size of the C dimension, guaranteeing that
+   * getEffectiveSizeC() * getSizeZ() * getSizeT() == getImageCount()
+   * regardless of the result of isRGB().
+   */
+  int getEffectiveSizeC();
+
+  /** Gets the number of channels per RGB image (if not RGB, this returns 1). */
+  int getRGBChannelCount();
+
+  /** Gets whether the images are indexed color. */
+  boolean isIndexed();
+
+  /**
+   * Returns false if isIndexed is false, or if isIndexed is true and the lookup
+   * table represents "real" color data.  Returns true if isIndexed is true
+   * and the lookup table is only present to aid in visualization.
+   */
+  boolean isFalseColor();
+
+  /**
+   * Gets the 8-bit color lookup table associated with
+   * the most recently opened image.
+   * If no images have been opened, or if isIndexed() returns false, then
+   * this returns null.  Also, if getPixelType() returns anything other than
+   * <code>INT8</code> or <code>UINT8</code>, this method will return null.
+   */
+  byte[][] get8BitLookupTable() throws FormatException, IOException;
+
+  /**
+   * Gets the 16-bit color lookup table associated with
+   * the most recently opened image.
+   * If no images have been opened, or if isIndexed() returns false, then
+   * this returns null.  Also, if getPixelType() returns anything other than
+   * <code>INT16</code> or <code>UINT16</code>, this method will return null.
+   */
+  short[][] get16BitLookupTable() throws FormatException, IOException;
+
+  /**
+   * Gets the lengths of each subdimension of C,
+   * in fastest-to-sloweset rasterization order.
+   */
+  int[] getChannelDimLengths();
+
+  /**
+   * Gets the name of each subdimension of C,
+   * in fastest-to-slowest rasterization order.
+   * Common subdimensional types are enumerated in {@link FormatTools}.
+   */
+  String[] getChannelDimTypes();
+
+  /** Get the size of the X dimension for the thumbnail. */
+  int getThumbSizeX();
+
+  /** Get the size of the Y dimension for the thumbnail. */
+  int getThumbSizeY();
+
+  /** Gets whether the data is in little-endian format. */
+  boolean isLittleEndian();
+
+  /**
+   * Gets a five-character string representing the
+   * dimension order within the file. Valid orders are:<ul>
+   *   <li>XYCTZ</li>
+   *   <li>XYCZT</li>
+   *   <li>XYTCZ</li>
+   *   <li>XYTZC</li>
+   *   <li>XYZCT</li>
+   *   <li>XYZTC</li>
+   * </ul>
+   * In cases where the channels are interleaved (e.g., CXYTZ), C will be
+   * the first dimension after X and Y (e.g., XYCTZ) and the
+   * {@link #isInterleaved(String)} method will return true.
+   */
+  String getDimensionOrder();
+
+  /**
+   * Gets whether the dimension order and sizes are known, or merely guesses.
+   */
+  boolean isOrderCertain();
+
+  /**
+   * Gets whether or not the channels are interleaved. This method exists
+   * because X and Y must appear first in the dimension order. For
+   * interleaved data, XYCTZ or XYCZT is used, and this method returns true.
+   */
+  boolean isInterleaved();
+
+  /**
+   * Gets whether or not the given sub-channel is interleaved. This method
+   * exists because some data with multiple rasterized sub-dimensions within
+   * C have one sub-dimension interleaved, and the other not—e.g.,
+   * {@link loci.formats.in.SDTReader} handles spectral-lifetime data with
+   * the interleaved lifetime bins and non-interleaved spectral channels.
+   */
+  boolean isInterleaved(int subC);
+
+  /**
+   * Obtains the specified image from the current file as a byte array.
+   */
+  byte[] openBytes(int no) throws FormatException, IOException;
+
+  /**
+   * Obtains the specified image from the current file into a pre-allocated byte
+   * array of (sizeX * sizeY * bytesPerPixel).
+   * @param no the image index within the file.
+   * @param buf a pre-allocated buffer.
+   * @return the pre-allocated buffer <code>buf</code> for convenience.
+   * @throws FormatException if there was a problem parsing the metadata of the
+   * file.
+   * @throws IOException if there was a problem reading the file.
+   */
+  byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException;
+
+  /** Obtains the specified image from the current file. */
+  BufferedImage openImage(int no)
+    throws FormatException, IOException;
+
+  /**
+   * Obtains a thumbnail for the specified image from the current file,
+   * as a byte array.
+   */
+  byte[] openThumbBytes(int no) throws FormatException, IOException;
+
+  /** Obtains a thumbnail for the specified image from the current file. */
+  BufferedImage openThumbImage(int no)
+    throws FormatException, IOException;
+
+  /**
+   * Closes the currently open file. If the flag is set, this is all that
+   * happens; if unset, it is equivalent to calling
+   * {@link IFormatHandler#close()}.
+   */
+  void close(boolean fileOnly) throws IOException;
+
+  /** Gets the number of series in this file. */
+  int getSeriesCount();
+
+  /** Activates the specified series. */
+  void setSeries(int no);
+
+  /** Gets the currently active series. */
+  int getSeries();
+
+  /** Specifies whether or not to normalize float data. */
+  void setNormalized(boolean normalize);
+
+  /** Returns true if we should normalize float data. */
+  boolean isNormalized();
+
+  /** Specifies whether or not to collect metadata. */
+  void setMetadataCollected(boolean collect);
+
+  /** Returns true if we should collect metadata. */
+  boolean isMetadataCollected();
+
+  /**
+   * Specifies whether or not to save proprietary metadata
+   * in the MetadataStore.
+   */
+  void setOriginalMetadataPopulated(boolean populate);
+
+  /**
+   * Returns true if we should save proprietary metadata
+   * in the MetadataStore.
+   */
+  boolean isOriginalMetadataPopulated();
+
+  /** Specifies whether or not to force grouping in multi-file formats. */
+  void setGroupFiles(boolean group);
+
+  /** Returns true if we should group files in multi-file formats.*/
+  boolean isGroupFiles();
+
+  /** Returns true if this format's metadata is completely parsed. */
+  boolean isMetadataComplete();
+
+  /**
+   * Returns an int indicating that we cannot, must, or might group the files
+   * in a given dataset.
+   */
+  int fileGroupOption(String id) throws FormatException, IOException;
+
+  /** Returns an array of filenames needed to open this dataset. */
+  String[] getUsedFiles();
+
+  /** Returns the current file. */
+  String getCurrentFile();
+
+  /**
+   * Gets the rasterized index corresponding
+   * to the given Z, C and T coordinates.
+   */
+  int getIndex(int z, int c, int t);
+
+  /**
+   * Gets the Z, C and T coordinates corresponding
+   * to the given rasterized index value.
+   */
+  int[] getZCTCoords(int index);
+
+  /**
+   * Obtains the specified metadata field's value for the current file.
+   * @param field the name associated with the metadata field
+   * @return the value, or null if the field doesn't exist
+   */
+  Object getMetadataValue(String field);
+
+  /**
+   * Obtains the hashtable containing the metadata field/value pairs from
+   * the current file.
+   * @return the hashtable containing all metadata from the file
+   */
+  Hashtable getMetadata();
+
+  /** Obtains the core metadata values for the current file. */
+  CoreMetadata getCoreMetadata();
+
+  /**
+   * Specifies whether ugly metadata (entries with unprintable characters,
+   * and extremely large entries) should be discarded from the metadata table.
+   */
+  void setMetadataFiltered(boolean filter);
+
+  /**
+   * Returns true if ugly metadata (entries with unprintable characters,
+   * and extremely large entries) are discarded from the metadata table.
+   */
+  boolean isMetadataFiltered();
+
+  /**
+   * Sets the default metadata store for this reader.
+   * @param store a metadata store implementation.
+   */
+  void setMetadataStore(MetadataStore store);
+
+  /**
+   * Retrieves the current metadata store for this reader. You can be
+   * assured that this method will <b>never</b> return a <code>null</code>
+   * metadata store.
+   * @return A metadata store implementation.
+   */
+  MetadataStore getMetadataStore();
+
+  /**
+   * Retrieves the current metadata store's root object. It is guaranteed that
+   * all file parsing has been performed by the reader prior to retrieval.
+   * Requests for a full populated root object should be made using this method.
+   * @return Current metadata store's root object fully populated.
+   */
+  Object getMetadataStoreRoot();
+
+  // -- Deprecated API methods --
+
+  /** @deprecated Replaced by {@link #getImageCount()} */
+  int getImageCount(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #isRGB()} */
+  boolean isRGB(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getSizeX()} */
+  int getSizeX(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getSizeY()} */
+  int getSizeY(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getSizeZ()} */
+  int getSizeZ(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getSizeC()} */
+  int getSizeC(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getSizeT()} */
+  int getSizeT(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getPixelType()} */
+  int getPixelType(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getEffectiveSizeC()} */
+  int getEffectiveSizeC(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getRGBChannelCount()} */
+  int getRGBChannelCount(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getChannelDimLengths()} */
+  int[] getChannelDimLengths(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getChannelDimTypes()} */
+  String[] getChannelDimTypes(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getThumbSizeX()} */
+  int getThumbSizeX(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getThumbSizeY()} */
+  int getThumbSizeY(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #isLittleEndian()} */
+  boolean isLittleEndian(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getDimensionOrder()} */
+  String getDimensionOrder(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #isOrderCertain()} */
+  boolean isOrderCertain(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #isInterleaved()} */
+  boolean isInterleaved(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #isInterleaved(int)} */
+  boolean isInterleaved(String id, int subC)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #openImage(int)} */
+  BufferedImage openImage(String id, int no)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #openBytes(int)} */
+  byte[] openBytes(String id, int no) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #openBytes(int, byte[])} */
+  byte[] openBytes(String id, int no, byte[] buf)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #openThumbImage(int)} */
+  BufferedImage openThumbImage(String id, int no)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #openThumbBytes(int)} */
+  byte[] openThumbBytes(String id, int no) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getSeriesCount()} */
+  int getSeriesCount(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #setSeries(int)} */
+  void setSeries(String id, int no) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getSeries()} */
+  int getSeries(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getUsedFiles()} */
+  String[] getUsedFiles(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getIndex(int, int, int)} */
+  int getIndex(String id, int z, int c, int t)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getZCTCoords(int)} */
+  int[] getZCTCoords(String id, int index)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getMetadataValue(String)} */
+  Object getMetadataValue(String id, String field)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getMetadata()} */
+  Hashtable getMetadata(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getCoreMetadata()} */
+  CoreMetadata getCoreMetadata(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getMetadataStore()} */
+  MetadataStore getMetadataStore(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #getMetadataStoreRoot()} */
+  Object getMetadataStoreRoot(String id) throws FormatException, IOException;
+
+}
diff --git a/loci/formats/IFormatWriter.java b/loci/formats/IFormatWriter.java
new file mode 100644
index 0000000..77279c2
--- /dev/null
+++ b/loci/formats/IFormatWriter.java
@@ -0,0 +1,127 @@
+//
+// IFormatWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.Image;
+import java.awt.image.ColorModel;
+import java.io.IOException;
+
+/**
+ * Interface for all biological file format writers.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/IFormatWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/IFormatWriter.java">SVN</a></dd></dl>
+ */
+public interface IFormatWriter extends IFormatHandler {
+
+  /**
+   * Saves the given image to the current file.
+   * If this image is the last one in the file, the last flag must be set.
+   */
+  void saveImage(Image image, boolean last) throws FormatException, IOException;
+
+  /**
+   * Saves the given image to the given series in the current file.
+   * If this image is the last one in the series, the lastInSeries flag
+   * must be set.
+   * If this image is the last one in the file, the last flag must be set.
+   */
+  void saveImage(Image image, int series, boolean lastInSeries, boolean last)
+    throws FormatException, IOException;
+
+  /**
+   * Saves the given byte array to the current file.
+   * If this is the last array to be written, the last flag must be set.
+   */
+  void saveBytes(byte[] bytes, boolean last)
+    throws FormatException, IOException;
+
+  /**
+   * Saves the given byte array to the given series in the current file.
+   * If this is the last array in the series, the lastInSeries flag must be set.
+   * If this is the last array to be written, the last flag must be set.
+   */
+  void saveBytes(byte[] bytes, int series, boolean lastInSeries, boolean last)
+    throws FormatException, IOException;
+
+  /** Reports whether the writer can save multiple images to a single file. */
+  boolean canDoStacks();
+
+  /**
+   * Sets the metadata retrieval object from
+   * which to retrieve standardized metadata.
+   */
+  void setMetadataRetrieve(MetadataRetrieve r);
+
+  /**
+   * Retrieves the current metadata retrieval object for this writer. You can
+   * be assured that this method will <b>never</b> return a <code>null</code>
+   * metadata retrieval object.
+   * @return A metadata retrieval object.
+   */
+  MetadataRetrieve getMetadataRetrieve();
+
+  /** Sets the color model. */
+  void setColorModel(ColorModel cm);
+
+  /** Gets the color model. */
+  ColorModel getColorModel();
+
+  /** Sets the frames per second to use when writing. */
+  void setFramesPerSecond(int rate);
+
+  /** Gets the frames per second to use when writing. */
+  int getFramesPerSecond();
+
+  /** Gets the available compression types. */
+  String[] getCompressionTypes();
+
+  /** Gets the supported pixel types. */
+  int[] getPixelTypes();
+
+  /** Checks if the given pixel type is supported. */
+  boolean isSupportedType(int type);
+
+  /** Sets the current compression type. */
+  void setCompression(String compress) throws FormatException;
+
+  // -- Deprecated API methods --
+
+  /** @deprecated Replaced by {@link #canDoStacks()} */
+  boolean canDoStacks(String id) throws FormatException;
+
+  /** @deprecated Replaced by {@link #getPixelTypes()} */
+  int[] getPixelTypes(String id) throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #isSupportedType(int type)} */
+  boolean isSupportedType(String id, int type)
+    throws FormatException, IOException;
+
+  /** @deprecated Replaced by {@link #saveImage(Image, boolean)} */
+  void save(String id, Image image, boolean last)
+    throws FormatException, IOException;
+
+}
diff --git a/loci/formats/IRandomAccess.java b/loci/formats/IRandomAccess.java
new file mode 100644
index 0000000..41e83d7
--- /dev/null
+++ b/loci/formats/IRandomAccess.java
@@ -0,0 +1,73 @@
+//
+// IRandomAccess.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+
+/**
+ * Interface for random access into structures (e.g., files or arrays).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/IRandomAccess.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/IRandomAccess.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public interface IRandomAccess extends DataInput, DataOutput {
+
+  /**
+   * Closes this random access file stream and releases
+   * any system resources associated with the stream.
+   */
+  void close() throws IOException;
+
+  /** Returns the current offset in this file. */
+  long getFilePointer() throws IOException;
+
+  /** Returns the length of this file. */
+  long length() throws IOException;
+
+  /** Reads a byte of data from this file. */
+  int read() throws IOException;
+
+  /**
+   * Reads up to b.length bytes of data
+   * from this file into an array of bytes.
+   */
+  int read(byte[] b) throws IOException;
+
+  /** Reads up to len bytes of data from this file into an array of bytes. */
+  int read(byte[] b, int off, int len) throws IOException;
+
+  /**
+   * Sets the file-pointer offset, measured from the beginning
+   * of this file, at which the next read or write occurs.
+   */
+  void seek(long pos) throws IOException;
+
+  /** Sets the length of this file. */
+  void setLength(long newLength) throws IOException;
+
+}
diff --git a/loci/formats/ImageReader.java b/loci/formats/ImageReader.java
new file mode 100644
index 0000000..c4fb3a8
--- /dev/null
+++ b/loci/formats/ImageReader.java
@@ -0,0 +1,811 @@
+//
+// ImageReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * ImageReader is the master file format reader for all supported formats.
+ * It uses one instance of each reader subclass (specified in readers.txt,
+ * or other class list source) to identify file formats and read data.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ImageReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ImageReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class ImageReader implements IFormatReader {
+
+  // -- Static fields --
+
+  /** Default list of reader classes, for use with noargs constructor. */
+  private static ClassList defaultClasses;
+
+  // -- Static helper methods --
+
+  private static ClassList getDefaultReaderClasses() {
+    if (defaultClasses == null) {
+      // load built-in reader classes from readers.txt file
+      try {
+        defaultClasses = new ClassList("readers.txt", IFormatReader.class);
+      }
+      catch (IOException exc) {
+        defaultClasses = new ClassList(IFormatReader.class);
+        LogTools.trace(exc);
+      }
+    }
+    return defaultClasses;
+  }
+
+  // -- Fields --
+
+  /** List of supported file format readers. */
+  private IFormatReader[] readers;
+
+  /**
+   * Valid suffixes for this file format.
+   * Populated the first time getSuffixes() is called.
+   */
+  private String[] suffixes;
+
+  /** Name of current file. */
+  private String currentId;
+
+  /** Current form index. */
+  private int current;
+
+  // -- Constructors --
+
+  /**
+   * Constructs a new ImageReader with the default
+   * list of reader classes from readers.txt.
+   */
+  public ImageReader() {
+    this(getDefaultReaderClasses());
+  }
+
+  /** Constructs a new ImageReader from the given list of reader classes. */
+  public ImageReader(ClassList classList) {
+    // add readers to the list
+    Vector v = new Vector();
+    Class[] c = classList.getClasses();
+    for (int i=0; i<c.length; i++) {
+      IFormatReader reader = null;
+      try {
+        reader = (IFormatReader) c[i].newInstance();
+      }
+      catch (IllegalAccessException exc) { }
+      catch (InstantiationException exc) { }
+      if (reader == null) {
+        LogTools.println("Error: " + c[i].getName() +
+          " cannot be instantiated.");
+        continue;
+      }
+      v.add(reader);
+    }
+    readers = new IFormatReader[v.size()];
+    v.copyInto(readers);
+  }
+
+  // -- ImageReader API methods --
+
+  /** Gets a string describing the file format for the given file. */
+  public String getFormat(String id) throws FormatException, IOException {
+    return getReader(id).getFormat();
+  }
+
+  /** Gets the reader used to open the given file. */
+  public IFormatReader getReader(String id)
+    throws FormatException, IOException
+  {
+    if (!id.equals(currentId)) {
+      // initialize file
+      boolean success = false;
+      for (int i=0; i<readers.length; i++) {
+        if (readers[i].isThisType(id)) {
+          current = i;
+          currentId = id;
+          success = true;
+          break;
+        }
+      }
+      if (!success) throw new FormatException("Unknown file format: " + id);
+    }
+    return readers[current];
+  }
+
+  /** Gets the reader used to open the current file. */
+  public IFormatReader getReader() {
+    return readers[current];
+  }
+
+  /** Gets the file format reader instance matching the given class. */
+  public IFormatReader getReader(Class c) {
+    for (int i=0; i<readers.length; i++) {
+      if (readers[i].getClass().equals(c)) return readers[i];
+    }
+    return null;
+  }
+
+  /** Gets all constituent file format readers. */
+  public IFormatReader[] getReaders() {
+    IFormatReader[] r = new IFormatReader[readers.length];
+    System.arraycopy(readers, 0, r, 0, readers.length);
+    return r;
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see IFormatReader.isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    for (int i=0; i<readers.length; i++) {
+      if (readers[i].isThisType(block)) return true;
+    }
+    return false;
+  }
+
+  /* @see IFormatReader#getImageCount() */
+  public int getImageCount() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getImageCount();
+  }
+
+  /* @see IFormatReader#isRGB() */
+  public boolean isRGB() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isRGB();
+  }
+
+  /* @see IFormatReader#getSizeX() */
+  public int getSizeX() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getSizeX();
+  }
+
+  /* @see IFormatReader#getSizeY() */
+  public int getSizeY() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getSizeY();
+  }
+
+  /* @see IFormatReader#getSizeC() */
+  public int getSizeC() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getSizeC();
+  }
+
+  /* @see IFormatReader#getSizeZ() */
+  public int getSizeZ() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getSizeZ();
+  }
+
+  /* @see IFormatReader#getSizeT() */
+  public int getSizeT() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getSizeT();
+  }
+
+  /* @see IFormatReader#getPixelType() */
+  public int getPixelType() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getPixelType();
+  }
+
+  /* @see IFormatReader#getEffectiveSizeC() */
+  public int getEffectiveSizeC() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getEffectiveSizeC();
+  }
+
+  /* @see IFormatReader#getRGBChannelCount() */
+  public int getRGBChannelCount() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getRGBChannelCount();
+  }
+
+  /* @see IFormatReader#isIndexed() */
+  public boolean isIndexed() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isIndexed();
+  }
+
+  /* @see IFormatReader#isFalseColor() */
+  public boolean isFalseColor() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isFalseColor();
+  }
+
+  /* @see IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().get8BitLookupTable();
+  }
+
+  /* @see IFormatReader#get16BitLookupTable() */
+  public short[][] get16BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().get16BitLookupTable();
+  }
+
+  /* @see IFormatReader#getChannelDimLengths() */
+  public int[] getChannelDimLengths() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getChannelDimLengths();
+  }
+
+  /* @see IFormatReader#getChannelDimTypes() */
+  public String[] getChannelDimTypes() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getChannelDimTypes();
+  }
+
+  /* @see IFormatReader#getThumbSizeX() */
+  public int getThumbSizeX() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getThumbSizeX();
+  }
+
+  /* @see IFormatReader#getThumbSizeY() */
+  public int getThumbSizeY() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getThumbSizeY();
+  }
+
+  /* @see IFormatReader#isLittleEndian() */
+  public boolean isLittleEndian() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isLittleEndian();
+  }
+
+  /* @see IFormatReader#getDimensionOrder() */
+  public String getDimensionOrder() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getDimensionOrder();
+  }
+
+  /* @see IFormatReader#isOrderCertain() */
+  public boolean isOrderCertain() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isOrderCertain();
+  }
+
+  /* @see IFormatReader#isInterleaved() */
+  public boolean isInterleaved() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isInterleaved();
+  }
+
+  /* @see IFormatReader#isInterleaved(int) */
+  public boolean isInterleaved(int subC) {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isInterleaved(subC);
+  }
+
+  /* @see IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().openImage(no);
+  }
+
+  /* @see IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().openBytes(no);
+  }
+
+  /* @see IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().openBytes(no, buf);
+  }
+
+  /* @see IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().openThumbImage(no);
+  }
+
+  /* @see IFormatReader#openThumbBytes(int) */
+  public byte[] openThumbBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().openThumbBytes(no);
+  }
+
+  /* @see IFormatReader#getSeriesCount() */
+  public int getSeriesCount() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getSeriesCount();
+  }
+
+  /* @see IFormatReader#setSeries(int) */
+  public void setSeries(int no) {
+    FormatTools.assertId(currentId, true, 2);
+    getReader().setSeries(no);
+  }
+
+  /* @see IFormatReader#getSeries() */
+  public int getSeries() {
+    return getReader().getSeries();
+  }
+
+  /* @see IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getUsedFiles();
+  }
+
+  /* @see IFormatReader#getIndex(int, int, int) */
+  public int getIndex(int z, int c, int t) {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getIndex(z, c, t);
+  }
+
+  /* @see IFormatReader#getZCTCoords(int) */
+  public int[] getZCTCoords(int index) {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getZCTCoords(index);
+  }
+
+  /* @see IFormatReader#getMetadataValue(String) */
+  public Object getMetadataValue(String field) {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getMetadataValue(field);
+  }
+
+  /* @see IFormatReader#getMetadata() */
+  public Hashtable getMetadata() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getMetadata();
+  }
+
+  /* @see IFormatReader#getCoreMetadata() */
+  public CoreMetadata getCoreMetadata() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getCoreMetadata();
+  }
+
+  /* @see IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    for (int i=0; i<readers.length; i++) readers[i].close(fileOnly);
+  }
+
+  /* @see IFormatReader#setGroupFiles(boolean) */
+  public void setGroupFiles(boolean group) {
+    FormatTools.assertId(currentId, false, 2);
+    for (int i=0; i<readers.length; i++) readers[i].setGroupFiles(group);
+  }
+
+  /* @see IFormatReader#isGroupFiles() */
+  public boolean isGroupFiles() {
+    return getReader().isGroupFiles();
+  }
+
+  /* @see IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    return getReader(id).fileGroupOption(id);
+  }
+
+  /* @see IFormatReader#isMetadataComplete() */
+  public boolean isMetadataComplete() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().isMetadataComplete();
+  }
+
+  /* @see IFormatReader#setNormalized(boolean) */
+  public void setNormalized(boolean normalize) {
+    FormatTools.assertId(currentId, false, 2);
+    for (int i=0; i<readers.length; i++) readers[i].setNormalized(normalize);
+  }
+
+  /* @see IFormatReader#isNormalized() */
+  public boolean isNormalized() {
+    // NB: all readers should have the same normalization setting
+    return readers[0].isNormalized();
+  }
+
+  /* @see IFormatReader#setMetadataCollected(boolean) */
+  public void setMetadataCollected(boolean collect) {
+    FormatTools.assertId(currentId, false, 2);
+    for (int i=0; i<readers.length; i++) {
+      readers[i].setMetadataCollected(collect);
+    }
+  }
+
+  /* @see IFormatReader#isMetadataCollected() */
+  public boolean isMetadataCollected() {
+    return readers[0].isMetadataCollected();
+  }
+
+  /* @see IFormatReader#setOriginalMetadataPopulated(boolean) */
+  public void setOriginalMetadataPopulated(boolean populate) {
+    FormatTools.assertId(currentId, false, 1);
+    for (int i=0; i<readers.length; i++) {
+      readers[i].setOriginalMetadataPopulated(populate);
+    }
+  }
+
+  /* @see IFormatReader#isOriginalMetadataPopulated() */
+  public boolean isOriginalMetadataPopulated() {
+    return readers[0].isOriginalMetadataPopulated();
+  }
+
+  /* @see IFormatReader#getCurrentFile() */
+  public String getCurrentFile() {
+    return getReader().getCurrentFile();
+  }
+
+  /* @see IFormatReader#setMetadataFiltered(boolean) */
+  public void setMetadataFiltered(boolean filter) {
+    FormatTools.assertId(currentId, false, 2);
+    for (int i=0; i<readers.length; i++) readers[i].setMetadataFiltered(filter);
+  }
+
+  /* @see IFormatReader#isMetadataFiltered() */
+  public boolean isMetadataFiltered() {
+    // NB: all readers should have the same metadata filtering setting
+    return readers[0].isMetadataFiltered();
+  }
+
+  /* @see IFormatReader#setMetadataStore(MetadataStore) */
+  public void setMetadataStore(MetadataStore store) {
+    FormatTools.assertId(currentId, false, 2);
+    for (int i=0; i<readers.length; i++) readers[i].setMetadataStore(store);
+  }
+
+  /* @see IFormatReader#getMetadataStore() */
+  public MetadataStore getMetadataStore() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getMetadataStore();
+  }
+
+  /* @see IFormatReader#getMetadataStoreRoot() */
+  public Object getMetadataStoreRoot() {
+    FormatTools.assertId(currentId, true, 2);
+    return getReader().getMetadataStoreRoot();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see IFormatHandler#isThisType(String) */
+  public boolean isThisType(String name) {
+    // NB: Unlike individual format readers, ImageReader defaults to *not*
+    // allowing files to be opened to analyze type, because doing so is
+    // quite slow with the large number of supported formats.
+    return isThisType(name, false);
+  }
+
+  /* @see IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    for (int i=0; i<readers.length; i++) {
+      if (readers[i].isThisType(name, open)) return true;
+    }
+    return false;
+  }
+
+  /* @see IFormatHandler#getFormat() */
+  public String getFormat() { return getReader().getFormat(); }
+
+  /* @see IFormatHandler#getSuffixes() */
+  public String[] getSuffixes() {
+    if (suffixes == null) {
+      HashSet suffixSet = new HashSet();
+      for (int i=0; i<readers.length; i++) {
+        String[] suf = readers[i].getSuffixes();
+        for (int j=0; j<suf.length; j++) suffixSet.add(suf[j]);
+      }
+      suffixes = new String[suffixSet.size()];
+      suffixSet.toArray(suffixes);
+      Arrays.sort(suffixes);
+    }
+    return suffixes;
+  }
+
+  /* @see IFormatHandler#setId(String) */
+  public void setId(String id) throws FormatException, IOException {
+    getReader(id).setId(id);
+  }
+
+  /* @see IFormatHandler#setId(String, boolean) */
+  public void setId(String id, boolean force)
+    throws FormatException, IOException
+  {
+    getReader(id).setId(id, force);
+  }
+
+  /* @see IFormatHandler#close() */
+  public void close() throws IOException {
+    currentId = null;
+    for (int i=0; i<readers.length; i++) readers[i].close();
+  }
+
+  // -- StatusReporter API methods --
+
+  /* @see IFormatHandler#addStatusListener(StatusListener) */
+  public void addStatusListener(StatusListener l) {
+    for (int i=0; i<readers.length; i++) readers[i].addStatusListener(l);
+  }
+
+  /* @see IFormatHandler#removeStatusListener(StatusListener) */
+  public void removeStatusListener(StatusListener l) {
+    for (int i=0; i<readers.length; i++) readers[i].removeStatusListener(l);
+  }
+
+  /* @see IFormatHandler#getStatusListeners() */
+  public StatusListener[] getStatusListeners() {
+    // NB: all readers should have the same status listeners
+    return readers[0].getStatusListeners();
+  }
+
+  // -- Deprecated IFormatReader API methods --
+
+  /** @deprecated Replaced by {@link #getImageCount()} */
+  public int getImageCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getImageCount();
+  }
+
+  /** @deprecated Replaced by {@link #isRGB()} */
+  public boolean isRGB(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().isRGB();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeX()} */
+  public int getSizeX(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getSizeX();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeY()} */
+  public int getSizeY(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getSizeY();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeZ()} */
+  public int getSizeZ(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getSizeZ();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeC()} */
+  public int getSizeC(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getSizeC();
+  }
+
+  /** @deprecated Replaced by {@link #getSizeT()} */
+  public int getSizeT(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getSizeT();
+  }
+
+  /** @deprecated Replaced by {@link #getPixelType()} */
+  public int getPixelType(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getPixelType();
+  }
+
+  /** @deprecated Replaced by {@link #getEffectiveSizeC()} */
+  public int getEffectiveSizeC(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getEffectiveSizeC();
+  }
+
+  /** @deprecated Replaced by {@link #getRGBChannelCount()} */
+  public int getRGBChannelCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getRGBChannelCount();
+  }
+
+  /** @deprecated Replaced by {@link #getChannelDimLengths()} */
+  public int[] getChannelDimLengths(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getChannelDimLengths();
+  }
+
+  /** @deprecated Replaced by {@link #getChannelDimTypes()} */
+  public String[] getChannelDimTypes(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getChannelDimTypes();
+  }
+
+  /** @deprecated Replaced by {@link #getThumbSizeX()} */
+  public int getThumbSizeX(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getThumbSizeX();
+  }
+
+  /** @deprecated Replaced by {@link #getThumbSizeY()} */
+  public int getThumbSizeY(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getThumbSizeY();
+  }
+
+  /** @deprecated Replaced by {@link #isLittleEndian()} */
+  public boolean isLittleEndian(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().isLittleEndian();
+  }
+
+  /** @deprecated Replaced by {@link #getDimensionOrder()} */
+  public String getDimensionOrder(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getDimensionOrder();
+  }
+
+  /** @deprecated Replaced by {@link #isOrderCertain()} */
+  public boolean isOrderCertain(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().isOrderCertain();
+  }
+
+  /** @deprecated Replaced by {@link #isInterleaved()} */
+  public boolean isInterleaved(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().isInterleaved();
+  }
+
+  /** @deprecated Replaced by {@link #isInterleaved(int)} */
+  public boolean isInterleaved(String id, int subC)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().isInterleaved(subC);
+  }
+
+  /** @deprecated Replaced by {@link #openImage(int)} */
+  public BufferedImage openImage(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().openImage(no);
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int)} */
+  public byte[] openBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().openBytes(no);
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int, byte[])} */
+  public byte[] openBytes(String id, int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().openBytes(no, buf);
+  }
+
+  /** @deprecated Replaced by {@link #openThumbImage(int)} */
+  public BufferedImage openThumbImage(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().openThumbImage(no);
+  }
+
+  /** @deprecated Replaced by {@link #openThumbBytes(int)} */
+  public byte[] openThumbBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().openThumbBytes(no);
+  }
+
+  /** @deprecated Replaced by {@link #getSeriesCount()} */
+  public int getSeriesCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getSeriesCount();
+  }
+
+  /** @deprecated Replaced by {@link #setSeries(int)} */
+  public void setSeries(String id, int no) throws FormatException, IOException {
+    setId(id);
+    getReader().setSeries(no);
+  }
+
+  /** @deprecated Replaced by {@link #getSeries()} */
+  public int getSeries(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getSeries();
+  }
+
+  /** @deprecated Replaced by {@link #getUsedFiles()} */
+  public String[] getUsedFiles(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getUsedFiles();
+  }
+
+  /** @deprecated Replaced by {@link #getIndex(int, int, int)} */
+  public int getIndex(String id, int z, int c, int t)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getIndex(z, c, t);
+  }
+
+  /** @deprecated Replaced by {@link #getZCTCoords(int)} */
+  public int[] getZCTCoords(String id, int index)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getZCTCoords(index);
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataValue(String)} */
+  public Object getMetadataValue(String id, String field)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getMetadataValue(field);
+  }
+
+  /** @deprecated Replaced by {@link #getMetadata()} */
+  public Hashtable getMetadata(String id) throws FormatException, IOException {
+    setId(id);
+    return getReader().getMetadata();
+  }
+
+  /** @deprecated Replaced by {@link #getCoreMetadata()} */
+  public CoreMetadata getCoreMetadata(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getCoreMetadata();
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataStore()} */
+  public MetadataStore getMetadataStore(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getMetadataStore();
+  }
+
+  /** @deprecated Replaced by {@link #getMetadataStoreRoot()} */
+  public Object getMetadataStoreRoot(String id)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return getReader().getMetadataStoreRoot();
+  }
+
+}
diff --git a/loci/formats/ImageTools.java b/loci/formats/ImageTools.java
new file mode 100644
index 0000000..39e896b
--- /dev/null
+++ b/loci/formats/ImageTools.java
@@ -0,0 +1,1894 @@
+//
+// ImageTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.image.*;
+
+/**
+ * A utility class with convenience methods for manipulating images.
+ *
+ * Much code was stolen and adapted from DrLaszloJamf's posts at:
+ *   http://forum.java.sun.com/thread.jspa?threadID=522483
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ImageTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ImageTools.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public final class ImageTools {
+
+  // -- Constants --
+
+  /** ImageObserver for working with AWT images. */
+  protected static final Component OBS = new Container();
+
+  // -- Constructor --
+
+  private ImageTools() { }
+
+  // -- Image construction - from 1D (single channel) data arrays --
+
+  /**
+   * Creates an image from the given single-channel unsigned byte data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(byte[] data, int w, int h) {
+    return makeImage(new byte[][] {data}, w, h);
+  }
+
+  /**
+   * Creates an image from the given single-channel unsigned short data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(short[] data, int w, int h) {
+    return makeImage(new short[][] {data}, w, h);
+  }
+
+  /**
+   * Creates an image from the given single-channel signed int data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(int[] data, int w, int h) {
+    return makeImage(new int[][] {data}, w, h);
+  }
+
+  /**
+   * Creates an image from the given single-channel float data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(float[] data, int w, int h) {
+    return makeImage(new float[][] {data}, w, h);
+  }
+
+  /**
+   * Creates an image from the given single-channel double data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(double[] data, int w, int h) {
+    return makeImage(new double[][] {data}, w, h);
+  }
+
+  // -- Image construction - from 1D (interleaved or banded) data arrays --
+
+  /**
+   * Creates an image from the given unsigned byte data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param interleaved If set, the channels are assumed to be interleaved;
+   *   otherwise they are assumed to be sequential.
+   *   For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
+   *   while "RRR...GGG...BBB..." is sequential.
+   */
+  public static BufferedImage makeImage(byte[] data,
+    int w, int h, int c, boolean interleaved)
+  {
+    if (c == 1) return makeImage(data, w, h);
+    int dataType = DataBuffer.TYPE_BYTE;
+    DataBuffer buffer = new DataBufferByte(data, c * w * h);
+    return constructImage(c, dataType, w, h, interleaved, false, buffer);
+  }
+
+  /**
+   * Creates an image from the given unsigned short data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param interleaved If set, the channels are assumed to be interleaved;
+   *   otherwise they are assumed to be sequential.
+   *   For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
+   *   while "RRR...GGG...BBB..." is sequential.
+   */
+  public static BufferedImage makeImage(short[] data,
+    int w, int h, int c, boolean interleaved)
+  {
+    if (c == 1) return makeImage(data, w, h);
+    int dataType = DataBuffer.TYPE_USHORT;
+    DataBuffer buffer = new DataBufferUShort(data, c * w * h);
+    return constructImage(c, dataType, w, h, interleaved, false, buffer);
+  }
+
+  /**
+   * Creates an image from the given signed int data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param interleaved If set, the channels are assumed to be interleaved;
+   *   otherwise they are assumed to be sequential.
+   *   For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
+   *   while "RRR...GGG...BBB..." is sequential.
+   */
+  public static BufferedImage makeImage(int[] data,
+    int w, int h, int c, boolean interleaved)
+  {
+    if (c == 1) return makeImage(data, w, h);
+    int dataType = DataBuffer.TYPE_INT;
+    DataBuffer buffer = new DataBufferInt(data, c * w * h);
+    return constructImage(c, dataType, w, h, interleaved, false, buffer);
+  }
+
+  /**
+   * Creates an image from the given float data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param interleaved If set, the channels are assumed to be interleaved;
+   *   otherwise they are assumed to be sequential.
+   *   For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
+   *   while "RRR...GGG...BBB..." is sequential.
+   */
+  public static BufferedImage makeImage(float[] data,
+    int w, int h, int c, boolean interleaved)
+  {
+    if (c == 1) return makeImage(data, w, h);
+    int dataType = DataBuffer.TYPE_FLOAT;
+    DataBuffer buffer = new DataBufferFloat(data, c * w * h);
+    return constructImage(c, dataType, w, h, interleaved, false, buffer);
+  }
+
+  /**
+   * Creates an image from the given double data.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param interleaved If set, the channels are assumed to be interleaved;
+   *   otherwise they are assumed to be sequential.
+   *   For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
+   *   while "RRR...GGG...BBB..." is sequential.
+   */
+  public static BufferedImage makeImage(double[] data,
+    int w, int h, int c, boolean interleaved)
+  {
+    if (c == 1) return makeImage(data, w, h);
+    int dataType = DataBuffer.TYPE_DOUBLE;
+    DataBuffer buffer = new DataBufferDouble(data, c * w * h);
+    return constructImage(c, dataType, w, h, interleaved, false, buffer);
+  }
+
+  // -- Image construction - from 2D (banded) data arrays --
+
+  /**
+   * Creates an image from the given unsigned byte data.
+   * @param data Array containing image data.
+   *   It is assumed that each channel corresponds to one element of the array.
+   *   For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(byte[][] data, int w, int h) {
+    int dataType = DataBuffer.TYPE_BYTE;
+    DataBuffer buffer = new DataBufferByte(data, data[0].length);
+    return constructImage(data.length, dataType, w, h, false, true, buffer);
+  }
+
+  /**
+   * Creates an image from the given unsigned short data.
+   * @param data Array containing image data.
+   *   It is assumed that each channel corresponds to one element of the array.
+   *   For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(short[][] data, int w, int h) {
+    int dataType = DataBuffer.TYPE_USHORT;
+    DataBuffer buffer = new DataBufferUShort(data, data[0].length);
+    return constructImage(data.length, dataType, w, h, false, true, buffer);
+  }
+
+  /**
+   * Creates an image from the given signed int data.
+   * @param data Array containing image data.
+   *   It is assumed that each channel corresponds to one element of the array.
+   *   For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(int[][] data, int w, int h) {
+    int dataType = DataBuffer.TYPE_INT;
+    DataBuffer buffer = new DataBufferInt(data, data[0].length);
+    return constructImage(data.length, dataType, w, h, false, true, buffer);
+  }
+
+  /**
+   * Creates an image from the given single-precision floating point data.
+   * @param data Array containing image data.
+   *   It is assumed that each channel corresponds to one element of the array.
+   *   For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(float[][] data, int w, int h) {
+    int dataType = DataBuffer.TYPE_FLOAT;
+    DataBuffer buffer = new DataBufferFloat(data, data[0].length);
+    return constructImage(data.length, dataType, w, h, false, true, buffer);
+  }
+
+  /**
+   * Creates an image from the given double-precision floating point data.
+   * @param data Array containing image data.
+   *   It is assumed that each channel corresponds to one element of the array.
+   *   For example, for RGB data, data[0] is R, data[1] is G, and data[2] is B.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   */
+  public static BufferedImage makeImage(double[][] data, int w, int h) {
+    int dataType = DataBuffer.TYPE_DOUBLE;
+    DataBuffer buffer = new DataBufferDouble(data, data[0].length);
+    return constructImage(data.length, dataType, w, h, false, true, buffer);
+  }
+
+  // -- Image construction - with type conversion --
+
+  /**
+   * Creates an image from the given data,
+   * performing type conversions as necessary.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param interleaved If set, the channels are assumed to be interleaved;
+   *   otherwise they are assumed to be sequential.
+   *   For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
+   *   while "RRR...GGG...BBB..." is sequential.
+   * @param bpp Denotes the number of bytes in the returned primitive type
+   *   (e.g. if bpp == 2, we should return an array of type short).
+   * @param little Whether byte array is in little-endian order.
+   */
+  public static BufferedImage makeImage(byte[] data, int w, int h, int c,
+    boolean interleaved, int bpp, boolean little)
+  {
+    return makeImage(data, w, h, c, interleaved, bpp, false, little);
+  }
+
+  /**
+   * Creates an image from the given data,
+   * performing type conversions as necessary.
+   * @param data Array containing image data.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param interleaved If set, the channels are assumed to be interleaved;
+   *   otherwise they are assumed to be sequential.
+   *   For example, for RGB data, the pattern "RGBRGBRGB..." is interleaved,
+   *   while "RRR...GGG...BBB..." is sequential.
+   * @param bpp Denotes the number of bytes in the returned primitive type
+   *   (e.g. if bpp == 2, we should return an array of type short).
+   * @param fp If set and bpp == 4 or bpp == 8, then return floats or doubles.
+   * @param little Whether byte array is in little-endian order.
+   */
+  public static BufferedImage makeImage(byte[] data, int w, int h, int c,
+    boolean interleaved, int bpp, boolean fp, boolean little)
+  {
+    Object pixels = DataTools.makeDataArray(data,
+      bpp % 3 == 0 ? bpp / 3 : bpp, fp, little);
+
+    if (pixels instanceof byte[]) {
+      return makeImage((byte[]) pixels, w, h, c, interleaved);
+    }
+    else if (pixels instanceof short[]) {
+      return makeImage((short[]) pixels, w, h, c, interleaved);
+    }
+    else if (pixels instanceof int[]) {
+      return makeImage((int[]) pixels, w, h, c, interleaved);
+    }
+    else if (pixels instanceof float[]) {
+      return makeImage((float[]) pixels, w, h, c, interleaved);
+    }
+    else if (pixels instanceof double[]) {
+      return makeImage((double[]) pixels, w, h, c, interleaved);
+    }
+    return null;
+  }
+
+  /**
+   * Creates an image from the given data,
+   * performing type conversions as necessary.
+   * @param data Array containing image data, one channel per element.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param bpp Denotes the number of bytes in the returned primitive type
+   *   (e.g. if bpp == 2, we should return an array of type short).
+   * @param little Whether byte array is in little-endian order.
+   */
+  public static BufferedImage makeImage(byte[][] data,
+    int w, int h, int bpp, boolean little)
+  {
+    return makeImage(data, w, h, bpp, false, little);
+  }
+
+  /**
+   * Creates an image from the given data,
+   * performing type conversions as necessary.
+   * @param data Array containing image data, one channel per element.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param bpp Denotes the number of bytes in the returned primitive type
+   *   (e.g. if bpp == 2, we should return an array of type short).
+   * @param fp If set and bpp == 4 or bpp == 8, then return floats or doubles.
+   * @param little Whether byte array is in little-endian order.
+   */
+  public static BufferedImage makeImage(byte[][] data,
+    int w, int h, int bpp, boolean fp, boolean little)
+  {
+    int c = data.length;
+    Object v = null;
+    for (int i=0; i<c; i++) {
+      Object pixels = DataTools.makeDataArray(data[i],
+        bpp % 3 == 0 ? bpp / 3 : bpp, fp, little);
+      if (pixels instanceof byte[]) {
+        if (v == null) v = new byte[c][];
+        ((byte[][]) v)[i] = (byte[]) pixels;
+      }
+      else if (pixels instanceof short[]) {
+        if (v == null) v = new short[c][];
+        ((short[][]) v)[i] = (short[]) pixels;
+      }
+      else if (pixels instanceof int[]) {
+        if (v == null) v = new int[c][];
+        ((int[][]) v)[i] = (int[]) pixels;
+      }
+      else if (pixels instanceof float[]) {
+        if (v == null) v = new float[c][];
+        ((float[][]) v)[i] = (float[]) pixels;
+      }
+      else if (pixels instanceof double[]) {
+        if (v == null) v = new double[c][];
+        ((double[][]) v)[i] = (double[]) pixels;
+      }
+    }
+    if (v instanceof byte[][]) return makeImage((byte[][]) v, w, h);
+    else if (v instanceof short[][]) return makeImage((short[][]) v, w, h);
+    else if (v instanceof int[][]) return makeImage((int[][]) v, w, h);
+    else if (v instanceof float[][]) return makeImage((float[][]) v, w, h);
+    else if (v instanceof double[][]) return makeImage((double[][]) v, w, h);
+    return null;
+  }
+
+  // -- Image construction - miscellaneous --
+
+  /**
+   * Creates a blank image with the given dimensions and transfer type.
+   * @param w Width of image plane.
+   * @param h Height of image plane.
+   * @param c Number of channels.
+   * @param type One of the following types:<ul>
+   *   <li>FormatReader.INT8</li>
+   *   <li>FormatReader.UINT8</li>
+   *   <li>FormatReader.INT16</li>
+   *   <li>FormatReader.UINT16</li>
+   *   <li>FormatReader.INT32</li>
+   *   <li>FormatReader.UINT32</li>
+   *   <li>FormatReader.FLOAT</li>
+   *   <li>FormatReader.DOUBLE</li>
+   * </ul>
+   */
+  public static BufferedImage blankImage(int w, int h, int c, int type) {
+    switch (type) {
+      case FormatTools.INT8:
+      case FormatTools.UINT8:
+        return makeImage(new byte[c][w * h], w, h);
+      case FormatTools.INT16:
+      case FormatTools.UINT16:
+        return makeImage(new short[c][w * h], w, h);
+      case FormatTools.INT32:
+      case FormatTools.UINT32:
+        return makeImage(new int[c][w * h], w, h);
+      case FormatTools.FLOAT:
+        return makeImage(new float[c][w * h], w, h);
+      case FormatTools.DOUBLE:
+        return makeImage(new double[c][w * h], w, h);
+    }
+    return null;
+  }
+
+  /** Creates an image with the given DataBuffer. */
+  private static BufferedImage constructImage(int c, int type, int w,
+    int h, boolean interleaved, boolean banded, DataBuffer buffer)
+  {
+    ColorModel colorModel = makeColorModel(c, type);
+    if (colorModel == null) return null;
+
+    SampleModel model;
+    if (banded) model = new BandedSampleModel(type, w, h, c);
+    else if (interleaved) {
+      int[] bandOffsets = new int[c];
+      for (int i=0; i<c; i++) bandOffsets[i] = i;
+      model = new PixelInterleavedSampleModel(type,
+        w, h, c, c * w, bandOffsets);
+    }
+    else {
+      int[] bandOffsets = new int[c];
+      for (int i=0; i<c; i++) bandOffsets[i] = i * w * h;
+      model = new ComponentSampleModel(type, w, h, 1, w, bandOffsets);
+    }
+
+    WritableRaster raster = Raster.createWritableRaster(model, buffer, null);
+    return new BufferedImage(colorModel, raster, false, null);
+  }
+
+  // -- Data extraction --
+
+  /**
+   * Gets the image's pixel data as arrays of primitives, one per channel.
+   * The returned type will be either byte[][], short[][], int[][], float[][]
+   * or double[][], depending on the image's transfer type.
+   */
+  public static Object getPixels(BufferedImage image) {
+    WritableRaster raster = image.getRaster();
+    int tt = raster.getTransferType();
+    if (tt == DataBuffer.TYPE_BYTE) return getBytes(image);
+    else if (tt == DataBuffer.TYPE_USHORT) return getShorts(image);
+    else if (tt == DataBuffer.TYPE_INT) return getInts(image);
+    else if (tt == DataBuffer.TYPE_FLOAT) return getFloats(image);
+    else if (tt == DataBuffer.TYPE_DOUBLE) return getDoubles(image);
+    else return null;
+  }
+
+  /** Extracts pixel data as arrays of unsigned bytes, one per channel. */
+  public static byte[][] getBytes(BufferedImage image) {
+    WritableRaster r = image.getRaster();
+    if (canUseBankDataDirectly(image, 1,
+      DataBuffer.TYPE_BYTE, DataBufferByte.class))
+    {
+      return ((DataBufferByte) r.getDataBuffer()).getBankData();
+    }
+    //return getBytes(makeType(image, dataType));
+    int w = image.getWidth(), h = image.getHeight(), c = r.getNumBands();
+    byte[][] samples = new byte[c][w * h];
+    int[] buf = new int[w * h];
+    for (int i=0; i<c; i++) {
+      r.getSamples(0, 0, w, h, i, buf);
+      for (int j=0; j<buf.length; j++) samples[i][j] = (byte) buf[j];
+    }
+    return samples;
+  }
+
+  /** Extracts pixel data as arrays of unsigned shorts, one per channel. */
+  public static short[][] getShorts(BufferedImage image) {
+    WritableRaster r = image.getRaster();
+    if (canUseBankDataDirectly(image, 2,
+      DataBuffer.TYPE_USHORT, DataBufferUShort.class))
+    {
+      return ((DataBufferUShort) r.getDataBuffer()).getBankData();
+    }
+    //return getShorts(makeType(image, DataBuffer.TYPE_USHORT));
+    int w = image.getWidth(), h = image.getHeight(), c = r.getNumBands();
+    short[][] samples = new short[c][w * h];
+    int[] buf = new int[w * h];
+    for (int i=0; i<c; i++) {
+      r.getSamples(0, 0, w, h, i, buf);
+      for (int j=0; j<buf.length; j++) samples[i][j] = (short) buf[j];
+    }
+    return samples;
+  }
+
+  /** Extracts pixel data as arrays of signed integers, one per channel. */
+  public static int[][] getInts(BufferedImage image) {
+    WritableRaster r = image.getRaster();
+    if (canUseBankDataDirectly(image, 4,
+      DataBuffer.TYPE_INT, DataBufferInt.class))
+    {
+      return ((DataBufferInt) r.getDataBuffer()).getBankData();
+    }
+    // NB: an order of magnitude faster than the naive makeType solution
+    int w = image.getWidth(), h = image.getHeight(), c = r.getNumBands();
+    int[][] samples = new int[c][w * h];
+    for (int i=0; i<c; i++) r.getSamples(0, 0, w, h, i, samples[i]);
+    return samples;
+  }
+
+  /** Extracts pixel data as arrays of floats, one per channel. */
+  public static float[][] getFloats(BufferedImage image) {
+    WritableRaster r = image.getRaster();
+    if (canUseBankDataDirectly(image, 4,
+      DataBuffer.TYPE_FLOAT, DataBufferFloat.class))
+    {
+      return ((DataBufferFloat) r.getDataBuffer()).getBankData();
+    }
+    // NB: an order of magnitude faster than the naive makeType solution
+    int w = image.getWidth(), h = image.getHeight(), c = r.getNumBands();
+    float[][] samples = new float[c][w * h];
+    for (int i=0; i<c; i++) r.getSamples(0, 0, w, h, i, samples[i]);
+    return samples;
+  }
+
+  /** Extracts pixel data as arrays of doubles, one per channel. */
+  public static double[][] getDoubles(BufferedImage image) {
+    WritableRaster r = image.getRaster();
+    if (canUseBankDataDirectly(image, 8,
+      DataBuffer.TYPE_DOUBLE, DataBufferDouble.class))
+    {
+      return ((DataBufferDouble) r.getDataBuffer()).getBankData();
+    }
+    // NB: an order of magnitude faster than the naive makeType solution
+    int w = image.getWidth(), h = image.getHeight(), c = r.getNumBands();
+    double[][] samples = new double[c][w * h];
+    for (int i=0; i<c; i++) r.getSamples(0, 0, w, h, i, samples[i]);
+    return samples;
+  }
+
+  /**
+   * Whether we can return the data buffer's bank data
+   * without performing any copy or conversion operations.
+   */
+  private static boolean canUseBankDataDirectly(BufferedImage image,
+    int bytesPerPixel, int transferType, Class dataBufferClass)
+  {
+    WritableRaster r = image.getRaster();
+    int tt = r.getTransferType();
+    if (tt != transferType) return false;
+    DataBuffer buffer = r.getDataBuffer();
+    if (!dataBufferClass.isInstance(buffer)) return false;
+    SampleModel model = r.getSampleModel();
+    if (!(model instanceof ComponentSampleModel)) return false;
+    ComponentSampleModel csm = (ComponentSampleModel) model;
+    int pixelStride = csm.getPixelStride();
+    if (pixelStride != 1) return false;
+    int w = r.getWidth();
+    int scanlineStride = csm.getScanlineStride();
+    if (scanlineStride != w) return false;
+    int c = r.getNumBands();
+    int[] bandOffsets = csm.getBandOffsets();
+    if (bandOffsets.length != c) return false;
+    for (int i=0; i<bandOffsets.length; i++) {
+      if (bandOffsets[i] != 0) return false;
+    }
+    int[] bankIndices = csm.getBankIndices();
+    for (int i=0; i<bandOffsets.length; i++) {
+      if (bandOffsets[i] != i) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Return a 2D array of bytes representing the image.  If the transfer type
+   * is something other than DataBuffer.TYPE_BYTE, then each pixel value is
+   * converted to the appropriate number of bytes.  In other words, if we
+   * are given an image with 16-bit data, each channel of the resulting array
+   * will have width * height * 2 bytes.
+   */
+  public static byte[][] getPixelBytes(BufferedImage img, boolean little) {
+    Object pixels = getPixels(img);
+
+    if (pixels instanceof byte[][]) {
+      byte[][] b = (byte[][]) pixels;
+      return (byte[][]) pixels;
+    }
+    else if (pixels instanceof short[][]) {
+      short[][] s = (short[][]) pixels;
+      byte[][] b = new byte[s.length][s[0].length * 2];
+      for (int i=0; i<b.length; i++) {
+        for (int j=0; j<s[0].length; j++) {
+          short v = s[i][j];
+          if (little) {
+            b[i][j*2] = (byte) (v & 0xff);
+            b[i][j*2+1] = (byte) ((v >>> 8) & 0xff);
+          }
+          else {
+            b[i][j*2] = (byte) ((v >>> 8) & 0xff);
+            b[i][j*2+1] = (byte) (v & 0xff);
+          }
+        }
+      }
+      return b;
+    }
+    else if (pixels instanceof int[][]) {
+      int[][] in = (int[][]) pixels;
+      byte[][] b = new byte[in.length][in[0].length * 4];
+      for (int i=0; i<b.length; i++) {
+        for (int j=0; j<in[0].length; j++) {
+          int v = in[i][j];
+          if (little) {
+            b[i][j*4] = (byte) (v & 0xff);
+            b[i][j*4+1] = (byte) ((v >> 8) & 0xff);
+            b[i][j*4+2] = (byte) ((v >> 16) & 0xff);
+            b[i][j*4+3] = (byte) ((v >> 24) & 0xff);
+          }
+          else {
+            b[i][j*4] = (byte) ((v >> 24) & 0xff);
+            b[i][j*4+1] = (byte) ((v >> 16) & 0xff);
+            b[i][j*4+2] = (byte) ((v >> 8) & 0xff);
+            b[i][j*4+3] = (byte) (v & 0xff);
+          }
+        }
+      }
+      return b;
+    }
+    else if (pixels instanceof float[][]) {
+      float[][] in = (float[][]) pixels;
+      byte[][] b = new byte[in.length][in[0].length * 4];
+      for (int i=0; i<b.length; i++) {
+        for (int j=0; j<in[0].length; j++) {
+          int v = Float.floatToIntBits(in[i][j]);
+          if (little) {
+            b[i][j*4] = (byte) (v & 0xff);
+            b[i][j*4+1] = (byte) ((v >> 8) & 0xff);
+            b[i][j*4+2] = (byte) ((v >> 16) & 0xff);
+            b[i][j*4+3] = (byte) ((v >> 24) & 0xff);
+          }
+          else {
+            b[i][j*4] = (byte) ((v >> 24) & 0xff);
+            b[i][j*4+1] = (byte) ((v >> 16) & 0xff);
+            b[i][j*4+2] = (byte) ((v >> 8) & 0xff);
+            b[i][j*4+3] = (byte) (v & 0xff);
+          }
+        }
+      }
+      return b;
+    }
+    else if (pixels instanceof double[][]) {
+
+    }
+    return null;
+  }
+
+  /**
+   * Gets the pixel type of the given image.
+   * @return One of the following types:<ul>
+   *   <li>FormatReader.INT8</li>
+   *   <li>FormatReader.UINT8</li>
+   *   <li>FormatReader.INT16</li>
+   *   <li>FormatReader.UINT16</li>
+   *   <li>FormatReader.INT32</li>
+   *   <li>FormatReader.UINT32</li>
+   *   <li>FormatReader.FLOAT</li>
+   *   <li>FormatReader.DOUBLE</li>
+   *   <li>-1 (unknown type)</li>
+   * </ul>
+   */
+  public static int getPixelType(BufferedImage image) {
+    int type = image.getRaster().getDataBuffer().getDataType();
+    switch (type) {
+      case DataBuffer.TYPE_BYTE:
+        return FormatTools.UINT8;
+      case DataBuffer.TYPE_DOUBLE:
+        return FormatTools.DOUBLE;
+      case DataBuffer.TYPE_FLOAT:
+        return FormatTools.FLOAT;
+      case DataBuffer.TYPE_INT:
+        return FormatTools.INT32;
+      case DataBuffer.TYPE_SHORT:
+        return FormatTools.INT16;
+      case DataBuffer.TYPE_USHORT:
+        return FormatTools.UINT16;
+      default:
+        return -1;
+    }
+  }
+
+  // -- Image conversion --
+
+  // NB: The commented out makeType method below is broken in that it results
+  // in rescaled data in some circumstances. We were using it for getBytes and
+  // getShorts, but due to this problem we implemented a different solution
+  // using Raster.getPixels instead. But we have left the makeType method here
+  // in case we decide to explore this issue any further in the future.
+
+  ///** Copies the given image into a result with the specified data type. */
+  //public static BufferedImage makeType(BufferedImage image, int type) {
+  //  WritableRaster r = image.getRaster();
+  //  int w = image.getWidth(), h = image.getHeight(), c = r.getNumBands();
+  //  ColorModel colorModel = makeColorModel(c, type);
+  //  if (colorModel == null) return null;
+  //
+  //  int s = w * h;
+  //  DataBuffer buf = null;
+  //  if (type == DataBuffer.TYPE_BYTE) buf = new DataBufferByte(s, c);
+  //  else if (type == DataBuffer.TYPE_USHORT) buf = new DataBufferUShort(s, c);
+  //  else if (type == DataBuffer.TYPE_INT) buf = new DataBufferInt(s, c);
+  //  else if (type == DataBuffer.TYPE_SHORT) buf = new DataBufferShort(s, c);
+  //  else if (type == DataBuffer.TYPE_FLOAT) buf = new DataBufferFloat(s, c);
+  //  else if (type == DataBuffer.TYPE_DOUBLE) buf = new DataBufferDouble(s, c);
+  //  if (buf == null) return null;
+  //
+  //  SampleModel model = new BandedSampleModel(type, w, h, c);
+  //  WritableRaster raster = Raster.createWritableRaster(model, buf, null);
+  //  BufferedImage target = new BufferedImage(colorModel, raster, false, null);
+  //  Graphics2D g2 = target.createGraphics();
+  //  g2.drawRenderedImage(image, null);
+  //  g2.dispose();
+  //  return target;
+  //}
+
+  /** Get the bytes from an image, merging the channels as necessary. */
+  public static byte[] getBytes(BufferedImage img, boolean separated, int c) {
+    byte[][] p = getBytes(img);
+    if (separated || p.length == 1) return p[0];
+    else {
+      byte[] rtn = new byte[p.length * p[0].length];
+      for (int i=0; i<p.length; i++) {
+        System.arraycopy(p[i], 0, rtn, i * p[0].length, p[i].length);
+      }
+      return rtn;
+    }
+  }
+
+  /**
+   * Convert an arbitrary primitive type array with 3 samples per pixel to
+   * a 3 x (width * height) byte array.
+   */
+  public static byte[][] make24Bits(Object pixels, int w, int h,
+    boolean interleaved, boolean reverse)
+  {
+    int[] pix = make24Bits(pixels, w, h, interleaved);
+    byte[][] rtn = new byte[3][pix.length];
+    for (int i=0; i<pix.length; i++) {
+      byte r = (byte) ((pix[i] >> 16) & 0xff);
+      rtn[1][i] = (byte) ((pix[i] >> 8) & 0xff);
+      byte b = (byte) (pix[i] & 0xff);
+      rtn[0][i] = reverse ? b : r;
+      rtn[2][i] = reverse ? r : b;
+    }
+    return rtn;
+  }
+
+  /**
+   * Convert an arbitrary primitive type array with 3 samples per pixel to
+   * an int array, i.e. RGB color with 8 bits per pixel.
+   * Does not perform any scaling.
+   */
+  public static int[] make24Bits(Object pixels, int w, int h,
+    boolean interleaved)
+  {
+    int[] rtn = new int[w * h];
+
+    byte[] b = null;
+
+    // adapted from ImageJ's TypeConverter methods
+
+    if (pixels instanceof byte[]) b = (byte[]) pixels;
+    else if (pixels instanceof short[]) {
+      short[] s = (short[]) pixels;
+      b = new byte[s.length];
+      for (int i=0; i<s.length; i++) {
+        int v = s[i] & 0xffff;
+        b[i] = (byte) v;
+      }
+    }
+    else if (pixels instanceof int[]) {
+      int[] s = (int[]) pixels;
+      b = new byte[s.length];
+      for (int i=0; i<s.length; i++) {
+        int value = s[i] & 0xffffffff;
+        b[i] = (byte) value;
+      }
+    }
+    else if (pixels instanceof float[]) {
+      float[] s = (float[]) pixels;
+      b = new byte[s.length];
+      for (int i=0; i<s.length; i++) {
+        float value = s[i];
+        b[i] = (byte) (255 * value);
+      }
+    }
+    else if (pixels instanceof double[]) {
+      double[] s = (double[]) pixels;
+      b = new byte[s.length];
+      for (int i=0; i<s.length; i++) {
+        double value = s[i];
+        b[i] = (byte) Math.round(value);
+      }
+    }
+
+    int c = b.length / rtn.length;
+
+    for (int i=0; i<rtn.length; i++) {
+      byte[] a = new byte[4];
+      for (int j=c-1; j>=0; j--) {
+        a[j] = b[interleaved ? i*c + j : i + j*w*h];
+      }
+      if (c == 1) {
+        for (int j=1; j<a.length; j++) {
+          a[j] = a[0];
+        }
+      }
+
+      byte tmp = a[0];
+      a[0] = a[2];
+      a[2] = tmp;
+      rtn[i] = DataTools.bytesToInt(a, true);
+    }
+
+    return rtn;
+  }
+
+  // -- Image manipulation --
+
+  /**
+   * Splits the given multi-channel array into a 2D array.
+   * The "reverse" parameter is false if channels are in RGB order, true if
+   * channels are in BGR order.
+   */
+  public static byte[][] splitChannels(byte[] array, int c, int bytes,
+    boolean reverse, boolean interleaved)
+  {
+    byte[][] rtn = new byte[c][array.length / c];
+
+    if (interleaved) {
+      if (reverse) {
+        int offset = 0;
+        for (int i=c-1; i>=0; i--) {
+          System.arraycopy(array, offset, rtn[i], 0, rtn[i].length);
+          offset += rtn[c].length;
+        }
+      }
+      else {
+        for (int i=0; i<c; i++) {
+          System.arraycopy(array, i * rtn[i].length, rtn[i], 0, rtn[i].length);
+        }
+      }
+    }
+    else {
+      if (reverse) {
+        int next = 0;
+        for (int i=0; i<array.length; i+=c*bytes) {
+          for (int j=c-1; j>=0; j--) {
+            for (int k=0; k<bytes; k++) {
+              if (next < rtn[j].length) {
+                rtn[c - j - 1][next] = array[i + j*bytes + k];
+              }
+              next++;
+            }
+            next -= bytes;
+          }
+          next += bytes;
+        }
+      }
+      else {
+        int next = 0;
+        for (int i=0; i<array.length; i+=c*bytes) {
+          for (int j=0; j<c; j++) {
+            for (int k=0; k<bytes; k++) {
+              if (next < rtn[j].length) rtn[j][next] = array[i + j*bytes + k];
+              next++;
+            }
+            next -= bytes;
+          }
+          next += bytes;
+        }
+      }
+    }
+    return rtn;
+  }
+
+  /** Splits the given multi-channel image into single-channel images. */
+  public static BufferedImage[] splitChannels(BufferedImage image) {
+    int w = image.getWidth(), h = image.getHeight();
+    int c = image.getRaster().getNumBands();
+    if (c == 1) return new BufferedImage[] {image};
+    BufferedImage[] results = new BufferedImage[c];
+
+    Object o = getPixels(image);
+    if (o instanceof byte[][]) {
+      byte[][] pix = (byte[][]) o;
+      for (int i=0; i<c; i++) results[i] = makeImage(pix[i], w, h);
+    }
+    else if (o instanceof short[][]) {
+      short[][] pix = (short[][]) o;
+      for (int i=0; i<c; i++) results[i] = makeImage(pix[i], w, h);
+    }
+    else if (o instanceof int[][]) {
+      int[][] pix = (int[][]) o;
+      for (int i=0; i<c; i++) results[i] = makeImage(pix[i], w, h);
+    }
+    else if (o instanceof float[][]) {
+      float[][] pix = (float[][]) o;
+      for (int i=0; i<c; i++) results[i] = makeImage(pix[i], w, h);
+    }
+    else if (o instanceof double[][]) {
+      double[][] pix = (double[][]) o;
+      for (int i=0; i<c; i++) results[i] = makeImage(pix[i], w, h);
+    }
+
+    return results;
+  }
+
+  /** Merges the given images into a single multi-channel image. */
+  public static BufferedImage mergeChannels(BufferedImage[] images) {
+    if (images == null || images.length == 0) return null;
+
+    // create list of pixels arrays
+    Object[] list = new Object[images.length];
+    int c = 0, type = 0;
+    for (int i=0; i<images.length; i++) {
+      Object o = getPixels(images[i]);
+      if (o instanceof byte[][]) {
+        if (i == 0) type = DataBuffer.TYPE_BYTE;
+        else if (type != DataBuffer.TYPE_BYTE) return null;
+        c += ((byte[][]) o).length;
+      }
+      else if (o instanceof short[][]) {
+        if (i == 0) type = DataBuffer.TYPE_USHORT;
+        else if (type != DataBuffer.TYPE_USHORT) return null;
+        c += ((short[][]) o).length;
+      }
+      else if (o instanceof int[][]) {
+        if (i == 0) type = DataBuffer.TYPE_INT;
+        else if (type != DataBuffer.TYPE_INT) return null;
+        c += ((int[][]) o).length;
+      }
+      else if (o instanceof float[][]) {
+        if (i == 0) type = DataBuffer.TYPE_FLOAT;
+        else if (type != DataBuffer.TYPE_FLOAT) return null;
+        c += ((float[][]) o).length;
+      }
+      else if (o instanceof double[][]) {
+        if (i == 0) type = DataBuffer.TYPE_DOUBLE;
+        else if (type != DataBuffer.TYPE_DOUBLE) return null;
+        c += ((double[][]) o).length;
+      }
+      if (c > 4) return null;
+      list[i] = o;
+    }
+    if (c < 1 || c > 4) return null;
+
+    // compile results into a single array
+    int w = images[0].getWidth(), h = images[0].getHeight();
+    if (type == DataBuffer.TYPE_BYTE) {
+      byte[][] pix = new byte[c][];
+      int ndx = 0;
+      for (int i=0; i<list.length; i++) {
+        byte[][] b = (byte[][]) list[i];
+        for (int j=0; j<b.length; j++) pix[ndx++] = b[j];
+      }
+      while (ndx < pix.length) pix[ndx++] = new byte[w * h]; // blank channel
+      return makeImage(pix, w, h);
+    }
+    if (type == DataBuffer.TYPE_USHORT) {
+      short[][] pix = new short[c][];
+      int ndx = 0;
+      for (int i=0; i<list.length; i++) {
+        short[][] b = (short[][]) list[i];
+        for (int j=0; j<b.length; j++) pix[ndx++] = b[j];
+      }
+      while (ndx < pix.length) pix[ndx++] = new short[w * h]; // blank channel
+      return makeImage(pix, w, h);
+    }
+    if (type == DataBuffer.TYPE_INT) {
+      int[][] pix = new int[c][];
+      int ndx = 0;
+      for (int i=0; i<list.length; i++) {
+        int[][] b = (int[][]) list[i];
+        for (int j=0; j<b.length; j++) pix[ndx++] = b[j];
+      }
+      while (ndx < pix.length) pix[ndx++] = new int[w * h]; // blank channel
+      return makeImage(pix, w, h);
+    }
+    if (type == DataBuffer.TYPE_FLOAT) {
+      float[][] pix = new float[c][];
+      int ndx = 0;
+      for (int i=0; i<list.length; i++) {
+        float[][] b = (float[][]) list[i];
+        for (int j=0; j<b.length; j++) pix[ndx++] = b[j];
+      }
+      while (ndx < pix.length) pix[ndx++] = new float[w * h]; // blank channel
+      return makeImage(pix, w, h);
+    }
+    if (type == DataBuffer.TYPE_DOUBLE) {
+      double[][] pix = new double[c][];
+      int ndx = 0;
+      for (int i=0; i<list.length; i++) {
+        double[][] b = (double[][]) list[i];
+        for (int j=0; j<b.length; j++) pix[ndx++] = b[j];
+      }
+      while (ndx < pix.length) pix[ndx++] = new double[w * h]; // blank channel
+      return makeImage(pix, w, h);
+    }
+
+    return null;
+  }
+
+  /**
+   * Pads (or crops) the image to the given width and height.
+   * The image will be centered within the new bounds.
+   */
+  public static BufferedImage padImage(BufferedImage img, int width, int height)
+  {
+    if (img == null) {
+      byte[][] data = new byte[1][width * height];
+      return makeImage(data, width, height);
+    }
+
+    boolean needsPadding = img.getWidth() != width || img.getHeight() != height;
+
+    if (needsPadding) {
+      Object pixels = getPixels(img);
+
+      if (pixels instanceof byte[][]) {
+        byte[][] b = (byte[][]) pixels;
+        byte[][] newBytes = new byte[b.length][width * height];
+        for (int i=0; i<b.length; i++) {
+          newBytes[i] = padImage(b[i], false, 1, img.getWidth(), width, height);
+        }
+        return makeImage(newBytes, width, height);
+      }
+      else if (pixels instanceof short[][]) {
+        short[][] b = (short[][]) pixels;
+        short[][] newShorts = new short[b.length][width * height];
+        for (int i=0; i<b.length; i++) {
+          newShorts[i] =
+            padImage(b[i], false, 1, img.getWidth(), width, height);
+        }
+        return makeImage(newShorts, width, height);
+      }
+      else if (pixels instanceof int[][]) {
+        int[][] b = (int[][]) pixels;
+        int[][] newInts = new int[b.length][width * height];
+        for (int i=0; i<b.length; i++) {
+          newInts[i] = padImage(b[i], false, 1, img.getWidth(), width, height);
+        }
+        return makeImage(newInts, width, height);
+      }
+      else if (pixels instanceof float[][]) {
+        float[][] b = (float[][]) pixels;
+        float[][] newFloats = new float[b.length][width * height];
+        for (int i=0; i<b.length; i++) {
+          newFloats[i] =
+            padImage(b[i], false, 1, img.getWidth(), width, height);
+        }
+        return makeImage(newFloats, width, height);
+      }
+      else if (pixels instanceof double[][]) {
+        double[][] b = (double[][]) pixels;
+        double[][] newDoubles = new double[b.length][width * height];
+        for (int i=0; i<b.length; i++) {
+          newDoubles[i] =
+            padImage(b[i], false, 1, img.getWidth(), width, height);
+        }
+        return makeImage(newDoubles, width, height);
+      }
+      return null;
+    }
+    return img;
+  }
+
+  /**
+   * Pads (or crops) the byte array to the given width and height.
+   * The image will be centered within the new bounds.
+   */
+  public static byte[] padImage(byte[] b, boolean interleaved, int c,
+    int oldWidth, int width, int height)
+  {
+    int oldHeight = b.length / (oldWidth * c);
+    byte[] padded = new byte[height * width * c];
+
+    int wClip = (width - oldWidth) / 2;
+    int hClip = (height - oldHeight) / 2;
+
+    int h = height < oldHeight ? height : oldHeight;
+
+    if (interleaved) {
+      int len = oldWidth < width ? oldWidth : width;
+      if (h == oldHeight) {
+        for (int y=0; y<h*c; y++) {
+          int oldIndex = oldWidth * y;
+          int index = width * y;
+          System.arraycopy(b, oldIndex, padded, index, len);
+        }
+      }
+      else {
+        for (int ch=0; ch<c; ch++) {
+          for (int y=0; y<h; y++) {
+            int oldIndex = oldWidth * ch * oldHeight + oldWidth * y;
+            int index = width * ch * height + width * y;
+            System.arraycopy(b, oldIndex, padded, index, len);
+          }
+        }
+      }
+    }
+    else {
+      int len = oldWidth < width ? oldWidth * c : width * c;
+      for (int oy=0, y=0; oy<oldHeight; oy++, y++) {
+        int oldIndex = oldWidth * c * y;
+        int index = width * c * (y + hClip) + c * wClip;
+        System.arraycopy(b, oldIndex, padded, index, len);
+      }
+    }
+    return padded;
+  }
+
+  /**
+   * Pads (or crops) the short array to the given width and height.
+   * The image will be centered within the new bounds.
+   */
+  public static short[] padImage(short[] b, boolean interleaved, int c,
+    int oldWidth, int width, int height)
+  {
+    int oldHeight = b.length / (oldWidth * c);
+    short[] padded = new short[height * width * c];
+
+    int wClip = (width - oldWidth) / 2;
+    int hClip = (height - oldHeight) / 2;
+
+    int h = height < oldHeight ? height : oldHeight;
+
+    if (interleaved) {
+      int len = oldWidth < width ? oldWidth : width;
+      if (h == oldHeight) {
+        for (int y=0; y<h*c; y++) {
+          int oldIndex = oldWidth * y;
+          int index = width * y;
+          System.arraycopy(b, oldIndex, padded, index, len);
+        }
+      }
+      else {
+        for (int ch=0; ch<c; ch++) {
+          for (int y=0; y<h; y++) {
+            int oldIndex = oldWidth * ch * oldHeight + oldWidth * y;
+            int index = width * ch * height + width * y;
+            System.arraycopy(b, oldIndex, padded, index, len);
+          }
+        }
+      }
+    }
+    else {
+      int len = oldWidth < width ? oldWidth * c : width * c;
+      for (int oy=0, y=0; oy<oldHeight; oy++, y++) {
+        int oldIndex = oldWidth * c * y;
+        int index = width * c * (y + hClip) + c * wClip;
+        System.arraycopy(b, oldIndex, padded, index, len);
+      }
+    }
+    return padded;
+  }
+
+  /**
+   * Pads (or crops) the int array to the given width and height.
+   * The image will be centered within the new bounds.
+   */
+  public static int[] padImage(int[] b, boolean interleaved, int c,
+    int oldWidth, int width, int height)
+  {
+    int oldHeight = b.length / (oldWidth * c);
+    int[] padded = new int[height * width * c];
+
+    int wClip = (width - oldWidth) / 2;
+    int hClip = (height - oldHeight) / 2;
+
+    int h = height < oldHeight ? height : oldHeight;
+
+    if (interleaved) {
+      int len = oldWidth < width ? oldWidth : width;
+      if (h == oldHeight) {
+        for (int y=0; y<h*c; y++) {
+          int oldIndex = oldWidth * y;
+          int index = width * y;
+          System.arraycopy(b, oldIndex, padded, index, len);
+        }
+      }
+      else {
+        for (int ch=0; ch<c; ch++) {
+          for (int y=0; y<h; y++) {
+            int oldIndex = oldWidth * ch * oldHeight + oldWidth * y;
+            int index = width * ch * height + width * y;
+            System.arraycopy(b, oldIndex, padded, index, len);
+          }
+        }
+      }
+    }
+    else {
+      int len = oldWidth < width ? oldWidth * c : width * c;
+      for (int oy=0, y=0; oy<oldHeight; oy++, y++) {
+        int oldIndex = oldWidth * c * y;
+        int index = width * c * (y + hClip) + c * wClip;
+        System.arraycopy(b, oldIndex, padded, index, len);
+      }
+    }
+    return padded;
+  }
+
+  /**
+   * Pads (or crops) the float array to the given width and height.
+   * The image will be centered within the new bounds.
+   */
+  public static float[] padImage(float[] b, boolean interleaved, int c,
+    int oldWidth, int width, int height)
+  {
+    int oldHeight = b.length / (oldWidth * c);
+    float[] padded = new float[height * width * c];
+
+    int wClip = (width - oldWidth) / 2;
+    int hClip = (height - oldHeight) / 2;
+
+    int h = height < oldHeight ? height : oldHeight;
+
+    if (interleaved) {
+      int len = oldWidth < width ? oldWidth : width;
+      if (h == oldHeight) {
+        for (int y=0; y<h*c; y++) {
+          int oldIndex = oldWidth * y;
+          int index = width * y;
+          System.arraycopy(b, oldIndex, padded, index, len);
+        }
+      }
+      else {
+        for (int ch=0; ch<c; ch++) {
+          for (int y=0; y<h; y++) {
+            int oldIndex = oldWidth * ch * oldHeight + oldWidth * y;
+            int index = width * ch * height + width * y;
+            System.arraycopy(b, oldIndex, padded, index, len);
+          }
+        }
+      }
+    }
+    else {
+      int len = oldWidth < width ? oldWidth * c : width * c;
+      for (int oy=0, y=0; oy<oldHeight; oy++, y++) {
+        int oldIndex = oldWidth * c * y;
+        int index = width * c * (y + hClip) + c * wClip;
+        System.arraycopy(b, oldIndex, padded, index, len);
+      }
+    }
+    return padded;
+  }
+
+  /**
+   * Pads (or crops) the double array to the given width and height.
+   * The image will be centered within the new bounds.
+   */
+  public static double[] padImage(double[] b, boolean interleaved, int c,
+    int oldWidth, int width, int height)
+  {
+    int oldHeight = b.length / (oldWidth * c);
+    double[] padded = new double[height * width * c];
+
+    int wClip = (width - oldWidth) / 2;
+    int hClip = (height - oldHeight) / 2;
+
+    int h = height < oldHeight ? height : oldHeight;
+
+    if (interleaved) {
+      int len = oldWidth < width ? oldWidth : width;
+      if (h == oldHeight) {
+        for (int y=0; y<h*c; y++) {
+          int oldIndex = oldWidth * y;
+          int index = width * y;
+          System.arraycopy(b, oldIndex, padded, index, len);
+        }
+      }
+      else {
+        for (int ch=0; ch<c; ch++) {
+          for (int y=0; y<h; y++) {
+            int oldIndex = oldWidth * ch * oldHeight + oldWidth * y;
+            int index = width * ch * height + width * y;
+            System.arraycopy(b, oldIndex, padded, index, len);
+          }
+        }
+      }
+    }
+    else {
+      int len = oldWidth < width ? oldWidth * c : width * c;
+      for (int oy=0, y=0; oy<oldHeight; oy++, y++) {
+        int oldIndex = oldWidth * c * y;
+        int index = width * c * (y + hClip) + c * wClip;
+        System.arraycopy(b, oldIndex, padded, index, len);
+      }
+    }
+    return padded;
+  }
+
+  /** Perform demosaicing on a byte array, assuming a {B, G, G, R} mosaic. */
+  public static short[][] demosaic(short[][] input, int w, int h) {
+    for (int i=0; i<input[0].length; i++) {
+      // determine which color components need to be calculated
+
+      boolean needsRed = !(((i / w) % 2 == 1) && ((i % w) % 2 == 1));
+      boolean needsBlue = !(((i / w) % 2 == 0) && ((i % w) % 2 == 0));
+      boolean needsGreen = needsBlue ^ needsRed;
+
+      if (needsRed) {
+        int sum = 0;
+        int count = 0;
+
+        int[] indices = null;
+
+        if (!needsBlue) {
+          indices = new int[] {i - w - 1, i - w + 1, i + w - 1, i + w + 1};
+        }
+        else if ((i / w) % 2 == 1) indices = new int[] {i - 1, i + 1};
+        else indices = new int[] {i - w, i + w};
+
+        for (int j=0; j<indices.length; j++) {
+          if (indices[j] < input[0].length && indices[j] >= 0) {
+            sum += (int) input[0][indices[j]];
+            count++;
+          }
+        }
+
+        if (count > 0) {
+          input[0][i] = (short) (sum / count);
+        }
+      }
+
+      if (needsGreen) {
+        int sum = 0;
+        int count = 0;
+
+        int[] indices = {i - w, i - 1, i + 1, i + w};
+
+        for (int j=0; j<indices.length; j++) {
+          if (indices[j] < input[0].length && indices[j] >= 0) {
+            sum += (int) input[1][indices[j]];
+            count++;
+          }
+        }
+
+        if (count > 0) {
+          input[1][i] = (short) (sum / count);
+        }
+      }
+
+      if (needsBlue) {
+        int sum = 0;
+        int count = 0;
+
+        int[] indices = null;
+
+        if (!needsRed) {
+          indices = new int[] {i - w - 1, i - w + 1, i + w - 1, i + w + 1};
+        }
+        else if ((i / w) % 2 == 1) indices = new int[] {i - w, i + w};
+        else indices = new int[] {i - 1, i + 1};
+
+        for (int j=0; j<indices.length; j++) {
+          if (indices[j] < input[0].length && indices[j] >= 0) {
+            sum += (int) input[2][indices[j]];
+            count++;
+          }
+        }
+
+        if (count > 0) {
+          input[2][i] = (short) (sum / count);
+        }
+      }
+    }
+
+    // Before returning, perform convolution with a 5x5 pseudo-Gaussian filter.
+    // This is *not* an optimal filter, but it works reasonably well.
+
+    short[] newRed = new short[w * h];
+    short[] newGreen = new short[w * h];
+    short[] newBlue = new short[w * h];
+
+    float[][] kernel = new float[5][5];
+    kernel[0] = new float[] {0.458f, 0.823f, 1f, 0.823f, 0.458f};
+    kernel[1] = new float[] {0.823f, 1f, 1.09f, 1f, 0.823f};
+    kernel[2] = new float[] {1f, 1.09f, 1.135f, 1.09f, 1f};
+    kernel[3] = new float[] {0.823f, 1f, 1.09f, 1f, 0.823f};
+    kernel[4] = new float[] {0.458f, 0.823f, 1f, 0.823f, 0.458f};
+
+    for (int i=0; i<h; i++) {
+      for (int j=0; j<w; j++) {
+        float redRow = 0, greenRow = 0, blueRow = 0;
+        float[] kernelRow = kernel[i % 5];
+
+        int diff = w - j;
+        float sum = 0;
+
+        if (diff < 5) {
+          while (diff > 0) {
+            int off = i*w + j + diff - 1;
+            redRow += kernelRow[diff - 1] * input[0][off];
+            greenRow += kernelRow[diff - 1] * input[1][off];
+            blueRow += kernelRow[diff - 1] * input[2][off];
+            sum += kernelRow[diff - 1];
+            diff--;
+          }
+        }
+        else {
+          for (int m=0; m<5; m++) {
+            int off = i*w + j + m;
+            redRow += kernelRow[m] * input[0][off];
+            greenRow += kernelRow[m] * input[1][off];
+            blueRow += kernelRow[m] * input[2][off];
+            sum += kernelRow[m];
+          }
+        }
+        if (sum == 0) sum = 1;
+        newRed[i*w + j] = (short) (redRow / sum);
+        newGreen[i*w + j] = (short) (greenRow / sum);
+        newBlue[i*w + j] = (short) (blueRow / sum);
+      }
+    }
+
+    for (int i=0; i<h; i++) {
+      for (int j=0; j<w; j++) {
+        float redCol = 0, greenCol = 0, blueCol = 0;
+        float[] kernelCol = kernel[j % 5];
+
+        int diff = h - i;
+        float sum = 0;
+
+        if (diff < 5) {
+          while (diff > 0) {
+            int off = (i + diff - 1) * w + j;
+            redCol += kernelCol[diff - 1] * newRed[off];
+            greenCol += kernelCol[diff - 1] * newGreen[off];
+            blueCol += kernelCol[diff - 1] * newBlue[off];
+            sum += kernelCol[diff - 1];
+            diff--;
+          }
+        }
+        else {
+          for (int m=0; m<5; m++) {
+            int off = (i + m) * w + j;
+            redCol += kernelCol[m] * newRed[off];
+            greenCol += kernelCol[m] * newGreen[off];
+            blueCol += kernelCol[m] * newBlue[off];
+            sum += kernelCol[m];
+          }
+        }
+
+        if (sum == 0) sum = 1;
+        input[0][i*w + j] = (short) (redCol / sum);
+        input[1][i*w + j] = (short) (greenCol / sum);
+        input[2][i*w + j] = (short) (blueCol / sum);
+      }
+    }
+
+    return input;
+  }
+
+  /**
+   * Perform autoscaling on the given BufferedImage;
+   * map min to 0 and max to 255.  If the BufferedImage has 8 bit data, then
+   * nothing happens.
+   */
+  public static BufferedImage autoscale(BufferedImage img, int min, int max) {
+    Object pixels = getPixels(img);
+
+    if (pixels instanceof byte[][]) return img;
+    else if (pixels instanceof short[][]) {
+      short[][] shorts = (short[][]) pixels;
+      byte[][] out = new byte[shorts.length][shorts[0].length];
+
+      for (int i=0; i<out.length; i++) {
+        for (int j=0; j<out[i].length; j++) {
+          if (shorts[i][j] < 0) shorts[i][j] += 32767;
+
+          float diff = (float) max - (float) min;
+          float dist = (float) (shorts[i][j] - min) / diff;
+
+          if (shorts[i][j] >= max) out[i][j] = (byte) 255;
+          else if (shorts[i][j] <= min) out[i][j] = 0;
+          else out[i][j] = (byte) (dist * 256);
+        }
+      }
+
+      return makeImage(out, img.getWidth(), img.getHeight());
+    }
+    else if (pixels instanceof int[][]) {
+      int[][] ints = (int[][]) pixels;
+      byte[][] out = new byte[ints.length][ints[0].length];
+
+      for (int i=0; i<out.length; i++) {
+        for (int j=0; j<out[i].length; j++) {
+          if (ints[i][j] >= max) out[i][j] = (byte) 255;
+          else if (ints[i][j] <= min) out[i][j] = 0;
+          else {
+            int diff = max - min;
+            float dist = (ints[i][j] - min) / diff;
+            out[i][j] = (byte) (dist * 256);
+          }
+        }
+      }
+
+      return makeImage(out, img.getWidth(), img.getHeight());
+    }
+    else if (pixels instanceof float[][]) {
+      float[][] floats = (float[][]) pixels;
+      byte[][] out = new byte[floats.length][floats[0].length];
+
+      for (int i=0; i<out.length; i++) {
+        for (int j=0; j<out[i].length; j++) {
+          if (floats[i][j] >= max) out[i][j] = (byte) 255;
+          else if (floats[i][j] <= min) out[i][j] = 0;
+          else {
+            int diff = max - min;
+            float dist = (floats[i][j] - min) / diff;
+            out[i][j] = (byte) (dist * 256);
+          }
+        }
+      }
+
+      return makeImage(out, img.getWidth(), img.getHeight());
+    }
+    else if (pixels instanceof double[][]) {
+      double[][] doubles = (double[][]) pixels;
+      byte[][] out = new byte[doubles.length][doubles[0].length];
+
+      for (int i=0; i<out.length; i++) {
+        for (int j=0; j<out[i].length; j++) {
+          if (doubles[i][j] >= max) out[i][j] = (byte) 255;
+          else if (doubles[i][j] <= min) out[i][j] = 0;
+          else {
+            int diff = max - min;
+            float dist = (float) (doubles[i][j] - min) / diff;
+            out[i][j] = (byte) (dist * 256);
+          }
+        }
+      }
+
+      return makeImage(out, img.getWidth(), img.getHeight());
+    }
+    return img;
+  }
+
+  /**
+   * Perform autoscaling on the given byte array;
+   * map min to 0 and max to 255.  If the number of bytes per pixel is 1, then
+   * nothing happens.
+   */
+  public static byte[] autoscale(byte[] b, int min, int max, int bpp,
+    boolean little)
+  {
+    if (bpp == 1) return b;
+
+    byte[] out = new byte[b.length / bpp];
+
+    for (int i=0; i<b.length; i+=bpp) {
+      int s = DataTools.bytesToInt(b, i, bpp, little);
+
+      if (s >= max) s = 255;
+      else if (s <= min) s = 0;
+      else {
+        int diff = max - min;
+        float dist = (s - min) / diff;
+
+        s = (int) dist * 256;
+      }
+
+      out[i / bpp] = (byte) s;
+    }
+    return out;
+  }
+
+  /** Scan a plane for the channel min and max values. */
+  public static Double[] scanData(byte[] plane, int bits, boolean littleEndian)
+  {
+    int max = 0;
+    int min = Integer.MAX_VALUE;
+
+    if (bits <= 8) {
+      for (int j=0; j<plane.length; j++) {
+        if (plane[j] < min) min = plane[j];
+        if (plane[j] > max) max = plane[j];
+      }
+    }
+    else if (bits == 16) {
+      for (int j=0; j<plane.length; j+=2) {
+        short s = DataTools.bytesToShort(plane, j, 2, littleEndian);
+        if (s < min) min = s;
+        if (s > max) max = s;
+      }
+    }
+    else if (bits == 32) {
+      for (int j=0; j<plane.length; j+=4) {
+        int s = DataTools.bytesToInt(plane, j, 4, littleEndian);
+        if (s < min) min = s;
+        if (s > max) max = s;
+      }
+    }
+
+    Double[] rtn = new Double[2];
+    rtn[0] = new Double(min);
+    rtn[1] = new Double(max);
+    return rtn;
+  }
+
+  // -- Image scaling --
+
+  /** Copies the source image into the target, applying scaling. */
+  public static BufferedImage copyScaled(BufferedImage source,
+    BufferedImage target, Object hint)
+  {
+    if (hint == null) hint = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
+    Graphics2D g2 = target.createGraphics();
+    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
+    double scalex = (double) target.getWidth() / source.getWidth();
+    double scaley = (double) target.getHeight() / source.getHeight();
+    AffineTransform xform = AffineTransform.getScaleInstance(scalex, scaley);
+    g2.drawRenderedImage(source, xform);
+    g2.dispose();
+    return target;
+  }
+
+  /**
+   * Scales the image using the Java2D API, with the resultant
+   * image optimized for the given graphics configuration.
+   */
+  public static BufferedImage scale2D(BufferedImage image,
+    int width, int height, Object hint, GraphicsConfiguration gc)
+  {
+    if (gc == null) gc = getDefaultConfiguration();
+    int trans = image.getColorModel().getTransparency();
+    return copyScaled(image,
+      gc.createCompatibleImage(width, height, trans), hint);
+  }
+
+  /**
+   * Scales the image using the Java2D API, with the
+   * resultant image having the given color model.
+   */
+  public static BufferedImage scale2D(BufferedImage image,
+    int width, int height, Object hint, ColorModel cm)
+  {
+    WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
+    boolean isRasterPremultiplied = cm.isAlphaPremultiplied();
+    return copyScaled(image, new BufferedImage(cm,
+      raster, isRasterPremultiplied, null), hint);
+  }
+
+  /** Scales the image using the AWT Image API. */
+  public static Image scaleAWT(BufferedImage source, int width,
+    int height, int hint)
+  {
+    return source.getScaledInstance(width, height, hint);
+  }
+
+  /**
+   * Scales the image using the most appropriate API, with the resultant image
+   * having the same color model as the original image.
+   */
+  public static BufferedImage scale(BufferedImage source,
+    int width, int height, boolean pad)
+  {
+    int w = source.getWidth();
+    int h = source.getHeight();
+    if (w == width && h == height) return source;
+
+    int finalWidth = width, finalHeight = height;
+
+    if (pad) {
+      // keep aspect ratio the same
+      double r = (double) w / h;
+      double ratio = (double) width / height;
+      if (r > ratio) {
+        // bounded by width; adjust height
+        height = (h * width) / w;
+      }
+      else {
+        // bounded by height; adjust width
+        width = (w * height) / h;
+      }
+    }
+
+    BufferedImage result = null;
+    Image scaled = scaleAWT(source, width, height, Image.SCALE_AREA_AVERAGING);
+    result = makeBuffered(scaled, source.getColorModel());
+    return padImage(result, finalWidth, finalHeight);
+  }
+
+  // -- AWT images --
+
+  /**
+   * Creates a buffered image from the given AWT image object.
+   * If the AWT image is already a buffered image, no new object is created.
+   */
+  public static BufferedImage makeBuffered(Image image) {
+    if (image instanceof BufferedImage) return (BufferedImage) image;
+
+    // TODO: better way to handle color model (don't just assume RGB)
+    loadImage(image);
+    BufferedImage img = new BufferedImage(image.getWidth(OBS),
+      image.getHeight(OBS), BufferedImage.TYPE_INT_RGB);
+    Graphics g = img.getGraphics();
+    g.drawImage(image, 0, 0, OBS);
+    g.dispose();
+    return img;
+  }
+
+  /**
+   * Creates a buffered image possessing the given color model,
+   * from the specified AWT image object. If the AWT image is already a
+   * buffered image with the given color model, no new object is created.
+   */
+  public static BufferedImage makeBuffered(Image image, ColorModel cm) {
+    if (image instanceof BufferedImage) {
+      BufferedImage bi = (BufferedImage) image;
+      if (cm.equals(bi.getColorModel())) return bi;
+    }
+    loadImage(image);
+    int w = image.getWidth(OBS), h = image.getHeight(OBS);
+    boolean alphaPremultiplied = cm.isAlphaPremultiplied();
+    WritableRaster raster = cm.createCompatibleWritableRaster(w, h);
+    BufferedImage result = new BufferedImage(cm,
+      raster, alphaPremultiplied, null);
+    Graphics2D g = result.createGraphics();
+    g.drawImage(image, 0, 0, OBS);
+    g.dispose();
+    return result;
+  }
+
+  /** Ensures the given AWT image is fully loaded. */
+  public static boolean loadImage(Image image) {
+    if (image instanceof BufferedImage) return true;
+    MediaTracker tracker = new MediaTracker(OBS);
+    tracker.addImage(image, 0);
+    try { tracker.waitForID(0); }
+    catch (InterruptedException exc) { return false; }
+    if (MediaTracker.COMPLETE != tracker.statusID(0, false)) return false;
+    return true;
+  }
+
+  /**
+   * Gets the width and height of the given AWT image,
+   * waiting for it to finish loading if necessary.
+   */
+  public static Dimension getSize(Image image) {
+    if (image == null) return new Dimension(0, 0);
+    if (image instanceof BufferedImage) {
+      BufferedImage bi = (BufferedImage) image;
+      return new Dimension(bi.getWidth(), bi.getHeight());
+    }
+    loadImage(image);
+    return new Dimension(image.getWidth(OBS), image.getHeight(OBS));
+  }
+
+  // -- Graphics configuration --
+
+  /**
+   * Creates a buffered image compatible with the given graphics
+   * configuration, using the given buffered image as a source.
+   * If gc is null, the default graphics configuration is used.
+   */
+  public static BufferedImage makeCompatible(BufferedImage image,
+    GraphicsConfiguration gc)
+  {
+    if (gc == null) gc = getDefaultConfiguration();
+    int w = image.getWidth(), h = image.getHeight();
+    int trans = image.getColorModel().getTransparency();
+    BufferedImage result = gc.createCompatibleImage(w, h, trans);
+    Graphics2D g2 = result.createGraphics();
+    g2.drawRenderedImage(image, null);
+    g2.dispose();
+    return result;
+  }
+
+  /** Gets the default graphics configuration for the environment. */
+  public static GraphicsConfiguration getDefaultConfiguration() {
+    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+    return gd.getDefaultConfiguration();
+  }
+
+  // -- Color model --
+
+  /** Gets a color space for the given number of color components. */
+  public static ColorModel makeColorModel(int c, int dataType) {
+    int type;
+    switch (c) {
+      case 1:
+        type = ColorSpace.CS_GRAY;
+        break;
+      case 2:
+        type = TwoChannelColorSpace.CS_2C;
+        break;
+      case 3:
+        type = ColorSpace.CS_sRGB;
+        break;
+      case 4:
+        type = ColorSpace.CS_sRGB;
+        break;
+      default:
+        return null;
+    }
+    return new ComponentColorModel(TwoChannelColorSpace.getInstance(type),
+      c == 4, false, ColorModel.TRANSLUCENT, dataType);
+  }
+
+  // -- Indexed color conversion --
+
+  /** Converts an indexed color BufferedImage to an RGB BufferedImage. */
+  public static BufferedImage indexedToRGB(BufferedImage img, boolean le) {
+    byte[][] indices = getPixelBytes(img, le);
+    if (indices.length > 1) return img;
+    if (getPixelType(img) == FormatTools.UINT8) {
+      IndexedColorModel model = (IndexedColorModel) img.getColorModel();
+      byte[][] b = new byte[3][indices[0].length];
+      for (int i=0; i<indices[0].length; i++) {
+        b[0][i] = (byte) (model.getRed(indices[0][i] & 0xff) & 0xff);
+        b[1][i] = (byte) (model.getGreen(indices[0][i] & 0xff) & 0xff);
+        b[2][i] = (byte) (model.getBlue(indices[0][i] & 0xff) & 0xff);
+      }
+      return makeImage(b, img.getWidth(), img.getHeight());
+    }
+    else if (getPixelType(img) == FormatTools.UINT16) {
+      IndexedColorModel model = (IndexedColorModel) img.getColorModel();
+      short[][] s = new short[3][indices[0].length / 2];
+      for (int i=0; i<s[0].length; i++) {
+        int ndx = DataTools.bytesToInt(indices[0], i*2, 2, le) & 0xffff;
+        s[0][i] = (short) (model.getRed(ndx) & 0xffff);
+        s[1][i] = (short) (model.getRed(ndx) & 0xffff);
+        s[2][i] = (short) (model.getRed(ndx) & 0xffff);
+      }
+      return makeImage(s, img.getWidth(), img.getHeight());
+    }
+    return null;
+  }
+
+  /** Converts a LUT and an array of indices into an array of RGB tuples. */
+  public static byte[][] indexedToRGB(byte[][] lut, byte[] b) {
+    byte[][] rtn = new byte[lut.length][b.length];
+
+    for (int i=0; i<b.length; i++) {
+      for (int j=0; j<lut.length; j++) {
+        rtn[j][i] = lut[j][b[i]];
+      }
+    }
+    return rtn;
+  }
+
+  /** Converts a LUT and an array of indices into an array of RGB tuples. */
+  public static short[][] indexedToRGB(short[][] lut, byte[] b, boolean le) {
+    short[][] rtn = new short[lut.length][b.length / 2];
+    for (int i=0; i<b.length/2; i++) {
+      for (int j=0; j<lut.length; j++) {
+        rtn[j][i] = lut[j][DataTools.bytesToShort(b, i*2, 2, le)];
+      }
+    }
+    return rtn;
+  }
+
+}
diff --git a/loci/formats/ImageWriter.java b/loci/formats/ImageWriter.java
new file mode 100644
index 0000000..2e56e8b
--- /dev/null
+++ b/loci/formats/ImageWriter.java
@@ -0,0 +1,384 @@
+//
+// ImageWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.Image;
+import java.awt.image.ColorModel;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * ImageWriter is the master file format writer for all supported formats.
+ * It uses one instance of each writer subclass (specified in writers.txt,
+ * or other class list source) to identify file formats and write data.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ImageWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ImageWriter.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class ImageWriter implements IFormatWriter {
+
+  // -- Static fields --
+
+  /** Default list of writer classes, for use with noargs constructor. */
+  private static ClassList defaultClasses;
+
+  // -- Static helper methods --
+
+  private static ClassList getDefaultWriterClasses() {
+    if (defaultClasses == null) {
+      // load built-in writer classes from writers.txt file
+      try {
+        defaultClasses = new ClassList("writers.txt", IFormatWriter.class);
+      }
+      catch (IOException exc) {
+        defaultClasses = new ClassList(IFormatWriter.class);
+        LogTools.trace(exc);
+      }
+    }
+    return defaultClasses;
+  }
+
+  // -- Fields --
+
+  /** List of supported file format writers. */
+  protected IFormatWriter[] writers;
+
+  /**
+   * Valid suffixes for all file format writers.
+   * Populated the first time getSuffixes() is called.
+   */
+  private String[] suffixes;
+
+  /**
+   * Compression types for all file format writers.
+   * Populated the first time getCompressionTypes() is called.
+   */
+  protected String[] compressionTypes;
+
+  /** Name of current file. */
+  protected String currentId;
+
+  /** Current form index. */
+  protected int current;
+
+  // -- Constructor --
+
+  /**
+   * Constructs a new ImageWriter with the default
+   * list of writer classes from writers.txt.
+   */
+  public ImageWriter() {
+    this(getDefaultWriterClasses());
+  }
+
+  /** Constructs a new ImageWriter from the given list of writer classes. */
+  public ImageWriter(ClassList classList) {
+    // add writers to the list
+    Vector v = new Vector();
+    Class[] c = classList.getClasses();
+    for (int i=0; i<c.length; i++) {
+      IFormatWriter writer = null;
+      try {
+        writer = (IFormatWriter) c[i].newInstance();
+      }
+      catch (IllegalAccessException exc) { }
+      catch (InstantiationException exc) { }
+      if (writer == null) {
+        LogTools.println("Error: " + c[i].getName() +
+          " cannot be instantiated.");
+        continue;
+      }
+      v.add(writer);
+    }
+    writers = new IFormatWriter[v.size()];
+    v.copyInto(writers);
+  }
+
+  // -- ImageWriter API methods --
+
+  /** Gets a string describing the file format for the given file. */
+  public String getFormat(String id) throws FormatException, IOException {
+    return getWriter(id).getFormat();
+  }
+
+  /** Gets the writer used to save the given file. */
+  public IFormatWriter getWriter(String id) throws FormatException {
+    if (!id.equals(currentId)) {
+      // initialize file
+      boolean success = false;
+      for (int i=0; i<writers.length; i++) {
+        if (writers[i].isThisType(id)) {
+          current = i;
+          currentId = id;
+          success = true;
+          break;
+        }
+      }
+      if (!success) throw new FormatException("Unknown file format: " + id);
+    }
+    return writers[current];
+  }
+
+  /** Gets the writer used to save the current file. */
+  public IFormatWriter getWriter() {
+    return writers[current];
+  }
+
+  /** Gets the file format writer instance matching the given class. */
+  public IFormatWriter getWriter(Class c) {
+    for (int i=0; i<writers.length; i++) {
+      if (writers[i].getClass().equals(c)) return writers[i];
+    }
+    return null;
+  }
+
+  /** Gets all constituent file format writers. */
+  public IFormatWriter[] getWriters() {
+    IFormatWriter[] w = new IFormatWriter[writers.length];
+    System.arraycopy(writers, 0, w, 0, writers.length);
+    return w;
+  }
+
+  // -- IFormatWriter API methods --
+
+  /* @see IFormatWriter#saveBytes(byte[], boolean) */
+  public void saveBytes(byte[] bytes, boolean last)
+    throws FormatException, IOException
+  {
+    getWriter().saveBytes(bytes, last);
+  }
+
+  /* @see IFormatWriter#saveBytes(byte[], int, boolean, boolean) */
+  public void saveBytes(byte[] bytes, int series, boolean lastInSeries,
+    boolean last) throws FormatException, IOException
+  {
+    getWriter().saveBytes(bytes, series, lastInSeries, last);
+  }
+
+  /* @see IFormatWriter#saveImage(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    getWriter().saveImage(image, last);
+  }
+
+  /* @see IFormatWriter#saveImage(Image, int, boolean, boolean) */
+  public void saveImage(Image image, int series, boolean lastInSeries,
+    boolean last) throws FormatException, IOException
+  {
+    getWriter().saveImage(image, series, lastInSeries, last);
+  }
+
+  /* @see IFormatWriter#canDoStacks() */
+  public boolean canDoStacks() {
+    return getWriter().canDoStacks();
+  }
+
+  /* @see IFormatWriter#setMetadataRetrieve(MetadataRetrieve) */
+  public void setMetadataRetrieve(MetadataRetrieve r) {
+    for (int i=0; i<writers.length; i++) writers[i].setMetadataRetrieve(r);
+  }
+
+  /* @see IFormatReader#getMetadataStore() */
+  public MetadataRetrieve getMetadataRetrieve() {
+    return getWriter().getMetadataRetrieve();
+  }
+
+  /* @see IFormatWriter#setColorModel(ColorModel) */
+  public void setColorModel(ColorModel cm) {
+    for (int i=0; i<writers.length; i++) writers[i].setColorModel(cm);
+  }
+
+  /* @see IFormatWriter#getColorModel() */
+  public ColorModel getColorModel() {
+    // NB: all writers should have the same color model
+    return writers[0].getColorModel();
+  }
+
+  /* @see IFormatWriter#setFramesPerSecond(int) */
+  public void setFramesPerSecond(int rate) {
+    for (int i=0; i<writers.length; i++) writers[i].setFramesPerSecond(rate);
+  }
+
+  /* @see IFormatWriter#getFramesPerSecond() */
+  public int getFramesPerSecond() {
+    // NB: all writers should have the same frames per second
+    return writers[0].getFramesPerSecond();
+  }
+
+  /* @see IFormatWriter#getCompressionTypes() */
+  public String[] getCompressionTypes() {
+    if (compressionTypes == null) {
+      HashSet set = new HashSet();
+      for (int i=0; i<writers.length; i++) {
+        String[] s = writers[i].getCompressionTypes();
+        if (s != null) {
+          for (int j=0; j<s.length; j++) set.add(s[j]);
+        }
+      }
+      compressionTypes = new String[set.size()];
+      set.toArray(compressionTypes);
+      Arrays.sort(compressionTypes);
+    }
+    return compressionTypes;
+  }
+
+  /* @see IFormatWriter#getPixelTypes() */
+  public int[] getPixelTypes() {
+    return getWriter().getPixelTypes();
+  }
+
+  /* @see IFormatWriter#isSupportedType(int) */
+  public boolean isSupportedType(int type) {
+    return getWriter().isSupportedType(type);
+  }
+
+  /* @see IFormatWriter#setCompression(String) */
+  public void setCompression(String compress) throws FormatException {
+    boolean ok = false;
+    for (int i=0; i<writers.length; i++) {
+      String[] s = writers[i].getCompressionTypes();
+      for (int j=0; j<s.length; j++) {
+        if (s[j].equals(compress)) {
+          // valid compression type for this format
+          writers[i].setCompression(compress);
+          ok = true;
+        }
+      }
+    }
+    if (!ok) throw new FormatException("Invalid compression type: " + compress);
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see IFormatHandler#isThisType(String) */
+  public boolean isThisType(String name) {
+    // NB: Unlike individual format writers, ImageWriter defaults to *not*
+    // allowing files to be opened to analyze type, because doing so is
+    // quite slow with the large number of supported formats.
+    return isThisType(name, false);
+  }
+
+  /* @see IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    for (int i=0; i<writers.length; i++) {
+      if (writers[i].isThisType(name, open)) return true;
+    }
+    return false;
+  }
+
+  /* @see IFormatHandler#getFormat() */
+  public String getFormat() { return getWriter().getFormat(); }
+
+  /* @see IFormatHandler#getSuffixes() */
+  public String[] getSuffixes() {
+    if (suffixes == null) {
+      HashSet suffixSet = new HashSet();
+      for (int i=0; i<writers.length; i++) {
+        String[] suf = writers[i].getSuffixes();
+        for (int j=0; j<suf.length; j++) suffixSet.add(suf[j]);
+      }
+      suffixes = new String[suffixSet.size()];
+      suffixSet.toArray(suffixes);
+      Arrays.sort(suffixes);
+    }
+    return suffixes;
+  }
+
+  /* @see IFormatHandler#setId(String) */
+  public void setId(String id) throws FormatException, IOException {
+    getWriter(id).setId(id);
+  }
+
+  /* @see IFormatHandler#setId(String, boolean) */
+  public void setId(String id, boolean force)
+    throws FormatException, IOException
+  {
+    getWriter(id).setId(id, force);
+  }
+
+  /* @see IFormatHandler#close() */
+  public void close() throws IOException {
+    for (int i=0; i<writers.length; i++) writers[i].close();
+  }
+
+  // -- StatusReporter API methods --
+
+  /* @see IFormatHandler#addStatusListener(StatusListener) */
+  public void addStatusListener(StatusListener l) {
+    for (int i=0; i<writers.length; i++) writers[i].addStatusListener(l);
+  }
+
+  /* @see IFormatHandler#removeStatusListener(StatusListener) */
+  public void removeStatusListener(StatusListener l) {
+    for (int i=0; i<writers.length; i++) writers[i].removeStatusListener(l);
+  }
+
+  /* @see IFormatHandler#getStatusListeners() */
+  public StatusListener[] getStatusListeners() {
+    // NB: all writers should have the same status listeners
+    return writers[0].getStatusListeners();
+  }
+
+  // -- Deprecated IFormatWriter API methods --
+
+  /** @deprecated Replaced by {@link #canDoStacks()} */
+  public boolean canDoStacks(String id) throws FormatException {
+    try {
+      setId(id);
+    }
+    catch (IOException exc) {
+      // NB: should never happen
+      throw new FormatException(exc);
+    }
+    return canDoStacks(id);
+  }
+
+  /** @deprecated Replaced by {@link #getPixelTypes()} */
+  public int[] getPixelTypes(String id) throws FormatException, IOException {
+    setId(id);
+    return getPixelTypes(id);
+  }
+
+  /** @deprecated Replaced by {@link #isSupportedType(int type)} */
+  public boolean isSupportedType(String id, int type)
+    throws FormatException, IOException
+  {
+    setId(id);
+    return isSupportedType(type);
+  }
+
+  /** @deprecated Replaced by {@link #saveImage(Image, boolean)} */
+  public void save(String id, Image image, boolean last)
+    throws FormatException, IOException
+  {
+    setId(id);
+    saveImage(image, last);
+  }
+
+}
diff --git a/loci/formats/IndexedColorModel.java b/loci/formats/IndexedColorModel.java
new file mode 100644
index 0000000..5e65774
--- /dev/null
+++ b/loci/formats/IndexedColorModel.java
@@ -0,0 +1,206 @@
+//
+// IndexedColorModel.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.*;
+import java.io.IOException;
+
+/**
+ * TODO - IndexedColorModel javadoc.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/IndexedColorModel.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/IndexedColorModel.java">SVN</a></dd></dl>
+ */
+public class IndexedColorModel extends ColorModel {
+
+  // -- Fields --
+
+  /** Lookup tables. */
+  private byte[] redByte, greenByte, blueByte, alphaByte;
+  private short[] redShort, greenShort, blueShort, alphaShort;
+  private int[] redInt, greenInt, blueInt, alphaInt;
+
+  /** Length of lookup table. */
+  private int tableSize;
+
+  private int pixelBits;
+
+  // -- Constructors --
+
+  public IndexedColorModel(int bits, int size, byte[][] table)
+    throws IOException
+  {
+    super(bits);
+
+    if (table == null) throw new IOException("LUT cannot be null");
+    for (int i=0; i<table.length; i++) {
+      if (table[i].length < size) {
+        throw new IOException("LUT " + i + " too small");
+      }
+    }
+
+    if (table.length > 0) redByte = table[0];
+    if (table.length > 1) greenByte = table[1];
+    if (table.length > 2) blueByte = table[2];
+    if (table.length > 3) alphaByte = table[3];
+    tableSize = size;
+    pixelBits = bits;
+  }
+
+  public IndexedColorModel(int bits, int size, short[][] table)
+    throws IOException
+  {
+    super(bits);
+
+    if (table == null) throw new IOException("LUT cannot be null");
+    for (int i=0; i<table.length; i++) {
+      if (table[i].length < size) {
+        throw new IOException("LUT " + i + " too small");
+      }
+    }
+
+    if (table.length > 0) redShort = table[0];
+    if (table.length > 1) greenShort = table[1];
+    if (table.length > 2) blueShort = table[2];
+    if (table.length > 3) alphaShort = table[3];
+    tableSize = size;
+    pixelBits = bits;
+  }
+
+  public IndexedColorModel(int bits, int size, int[][] table)
+    throws IOException
+  {
+    super(bits);
+
+    if (table == null) throw new IOException("LUT cannot be null");
+    for (int i=0; i<table.length; i++) {
+      if (table[i].length < size) {
+        throw new IOException("LUT " + i + " too small");
+      }
+    }
+
+    if (table.length > 0) redInt = table[0];
+    if (table.length > 1) greenInt = table[1];
+    if (table.length > 2) blueInt = table[2];
+    if (table.length > 3) alphaInt = table[3];
+    tableSize = size;
+    pixelBits = bits;
+  }
+
+  // -- ColorModel API methods --
+
+  /* @see java.awt.image.ColorModel#getDataElements(int, Object) */
+  public synchronized Object getDataElements(int rgb, Object pixel) {
+    int red = (rgb >> 16) & 0xff;
+    int green = (rgb >> 8) & 0xff;
+    int blue = rgb & 0xff;
+    int alpha = (rgb >>> 24);
+
+    if (redByte != null) {
+      byte[] p = pixel == null ? new byte[3] : (byte[]) pixel;
+      p[0] = (byte) red;
+      p[1] = (byte) green;
+      p[2] = (byte) blue;
+
+      return p;
+    }
+    if (redShort != null) {
+      short[] p = pixel == null ? new short[3] : (short[]) pixel;
+      p[0] = (short) red;
+      p[1] = (short) green;
+      p[2] = (short) blue;
+      return p;
+    }
+    if (redInt != null) {
+      int[] p = pixel == null ? new int[3] : (int[]) pixel;
+      p[0] = red;
+      p[1] = green;
+      p[2] = blue;
+      return p;
+    }
+    throw new UnsupportedOperationException("Invalid transfer type");
+  }
+
+  /* @see java.awt.image.ColorModel#isCompatibleRaster(Raster) */
+  public boolean isCompatibleRaster(Raster raster) {
+    return raster.getNumBands() == 1;
+  }
+
+  /* @see java.awt.image.ColorModel#createCompatibleWritableRaster(int, int) */
+  public WritableRaster createCompatibleWritableRaster(int w, int h) {
+    WritableRaster raster;
+
+    if (pixelBits == 1 || pixelBits == 2 || pixelBits == 4) {
+      raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, w, h, 1,
+        pixelBits, null);
+    }
+    else if (pixelBits <= 8) {
+      raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, w, h, 1,
+        null);
+    }
+    else if (pixelBits <= 16) {
+      raster = Raster.createInterleavedRaster(DataBuffer.TYPE_USHORT, w, h, 1,
+        null);
+    }
+    else {
+      throw new UnsupportedOperationException("Pixel bits > 16 not supported");
+    }
+    return raster;
+  }
+
+  /* @see java.awt.image.ColorModel#getAlpha(int) */
+  public int getAlpha(int pixel) {
+    if (alphaByte != null) return alphaByte[pixel] & 0xff;
+    if (alphaShort != null) return alphaShort[pixel] & 0xffff;
+    if (alphaInt != null) return alphaInt[pixel];
+    return 255;
+  }
+
+  /* @see java.awt.image.ColorModel#getBlue(int) */
+  public int getBlue(int pixel) {
+    if (blueByte != null) return blueByte[pixel] & 0xff;
+    if (blueShort != null) return blueShort[pixel] & 0xffff;
+    if (blueInt != null) return blueInt[pixel];
+    return 0;
+  }
+
+  /* @see java.awt.image.ColorModel#getGreen(int) */
+  public int getGreen(int pixel) {
+    if (greenByte != null) return greenByte[pixel] & 0xff;
+    if (greenShort != null) return greenShort[pixel] & 0xffff;
+    if (greenInt != null) return greenInt[pixel];
+    return 0;
+  }
+
+  /* @see java.awt.image.ColorModel#getRed(int) */
+  public int getRed(int pixel) {
+    if (redByte != null) return redByte[pixel] & 0xff;
+    if (redShort != null) return redShort[pixel] & 0xffff;
+    if (redInt != null) return redInt[pixel];
+    return 0;
+  }
+
+}
diff --git a/loci/formats/LegacyQTTools.java b/loci/formats/LegacyQTTools.java
new file mode 100644
index 0000000..0c962a1
--- /dev/null
+++ b/loci/formats/LegacyQTTools.java
@@ -0,0 +1,269 @@
+//
+// LegacyQTTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.*;
+import java.awt.image.DirectColorModel;
+import java.awt.image.MemoryImageSource;
+import java.net.*;
+import java.util.StringTokenizer;
+
+/**
+ * Utility class for working with QuickTime for Java.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/LegacyQTTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/LegacyQTTools.java">SVN</a></dd></dl>
+ */
+public class LegacyQTTools {
+
+  // -- Constants --
+
+  public static final String NO_QT_MSG =
+    "QuickTime for Java is required to read some QuickTime files. " +
+    "Please install QuickTime for Java from http://www.apple.com/quicktime/";
+
+  public static final String EXPIRED_QT_MSG =
+    "Your version of QuickTime for Java has expired. " +
+    "Please reinstall QuickTime for Java from http://www.apple.com/quicktime/";
+
+  protected static final String[] SUFFIXES = {"mov", "qt"};
+
+  protected static final boolean MAC_OS_X =
+    System.getProperty("os.name").equals("Mac OS X");
+
+  // -- Static fields --
+
+  /**
+   * This custom class loader searches additional paths for the QTJava.zip
+   * library. Java has a restriction where only one class loader can have a
+   * native library loaded within a JVM. So the class loader must be static,
+   * shared by all QTForms, or else an UnsatisfiedLinkError is thrown when
+   * attempting to initialize QTJava multiple times.
+   */
+  protected static final ClassLoader LOADER = constructLoader();
+
+  protected static ClassLoader constructLoader() {
+    // set up additional QuickTime for Java paths
+    URL[] paths = null;
+
+    if (MAC_OS_X) {
+      try {
+        paths = new URL[] {
+          new URL("file:/System/Library/Java/Extensions/QTJava.zip")
+        };
+      }
+      catch (MalformedURLException exc) { LogTools.trace(exc); }
+      return paths == null ? null : new URLClassLoader(paths);
+    }
+
+    // case for Windows
+    String windir = System.getProperty("java.library.path");
+    StringTokenizer st = new StringTokenizer(windir, ";");
+
+    while (st.hasMoreTokens()) {
+      Location f = new Location(st.nextToken(), "QTJava.zip");
+      if (f.exists()) {
+        try {
+          paths = new URL[] {f.toURL()};
+        }
+        catch (MalformedURLException exc) { LogTools.trace(exc); }
+        return paths == null ? null : new URLClassLoader(paths);
+      }
+    }
+
+    return null;
+  }
+
+  // -- Fields --
+
+  /** Flag indicating this class has been initialized. */
+  protected boolean initialized = false;
+
+  /** Flag indicating QuickTime for Java is not installed. */
+  protected boolean noQT = false;
+
+  /** Flag indicating QuickTime for Java has expired. */
+  protected boolean expiredQT = false;
+
+  /** Reflection tool for QuickTime for Java calls. */
+  protected ReflectedUniverse r;
+
+  // -- LegacyQTTools API methods --
+
+  /** Initializes the class. */
+  protected void initClass() {
+    if (initialized) return;
+    boolean needClose = false;
+    r = new ReflectedUniverse(LOADER);
+    try {
+      r.exec("import quicktime.QTSession");
+      r.exec("QTSession.open()");
+      needClose = true;
+
+      // for LegacyQTReader and LegacyQTWriter
+      r.exec("import quicktime.io.QTFile");
+      r.exec("import quicktime.std.movies.Movie");
+
+      // for LegacyQTReader
+      r.exec("import quicktime.app.view.MoviePlayer");
+      r.exec("import quicktime.app.view.QTImageProducer");
+      r.exec("import quicktime.io.OpenMovieFile");
+      r.exec("import quicktime.qd.QDDimension");
+      r.exec("import quicktime.std.StdQTConstants");
+      r.exec("import quicktime.std.movies.TimeInfo");
+      r.exec("import quicktime.std.movies.Track");
+
+      // for LegacyQTWriter
+      r.exec("import quicktime.qd.QDGraphics");
+      r.exec("import quicktime.qd.QDRect");
+      r.exec("import quicktime.std.image.CSequence");
+      r.exec("import quicktime.std.image.CodecComponent");
+      r.exec("import quicktime.std.image.ImageDescription");
+      r.exec("import quicktime.std.movies.media.VideoMedia");
+      r.exec("import quicktime.util.QTHandle");
+      r.exec("import quicktime.util.RawEncodedImage");
+      r.exec("import quicktime.util.EndianOrder");
+    }
+    catch (ExceptionInInitializerError err) {
+      noQT = true;
+      Throwable t = err.getException();
+      if (t instanceof SecurityException) {
+        SecurityException exc = (SecurityException) t;
+        if (exc.getMessage().indexOf("expired") >= 0) expiredQT = true;
+      }
+    }
+    catch (Throwable t) {
+      noQT = true;
+      if (FormatHandler.debug) LogTools.trace(t);
+    }
+    finally {
+      if (needClose) {
+        try { r.exec("QTSession.close()"); }
+        catch (Throwable t) {
+          if (FormatHandler.debug) LogTools.trace(t);
+        }
+      }
+      initialized = true;
+    }
+  }
+
+  /** Whether QuickTime is available to this JVM. */
+  public boolean canDoQT() {
+    if (!initialized) initClass();
+    return !noQT;
+  }
+
+  /** Whether QuickTime for Java has expired. */
+  public boolean isQTExpired() {
+    if (!initialized) initClass();
+    return expiredQT;
+  }
+
+  /** Gets QuickTime for Java reflected universe. */
+  public ReflectedUniverse getUniverse() {
+    if (!initialized) initClass();
+    return r;
+  }
+
+  /** Gets width and height for the given PICT bytes. */
+  public Dimension getPictDimensions(byte[] bytes)
+    throws FormatException, ReflectException
+  {
+    if (isQTExpired()) throw new FormatException(EXPIRED_QT_MSG);
+    if (!canDoQT()) throw new FormatException(NO_QT_MSG);
+
+    try {
+      r.exec("QTSession.open()");
+      r.setVar("bytes", bytes);
+      r.exec("pict = new Pict(bytes)");
+      r.exec("box = pict.getPictFrame()");
+      int width = ((Integer) r.exec("box.getWidth()")).intValue();
+      int height = ((Integer) r.exec("box.getHeight()")).intValue();
+      r.exec("QTSession.close()");
+      return new Dimension(width, height);
+    }
+    catch (ReflectException e) {
+      r.exec("QTSession.close()");
+      throw new FormatException("PICT height determination failed", e);
+    }
+  }
+
+  /** Converts the given byte array in PICT format to a Java image. */
+  public synchronized Image pictToImage(byte[] bytes)
+    throws FormatException
+  {
+    if (isQTExpired()) throw new FormatException(EXPIRED_QT_MSG);
+    if (!canDoQT()) throw new FormatException(NO_QT_MSG);
+
+    try {
+      r.exec("QTSession.open()");
+
+      // Code adapted from:
+      //   http://www.onjava.com/pub/a/onjava/2002/12/23/jmf.html?page=2
+      r.setVar("bytes", bytes);
+      r.exec("pict = new Pict(bytes)");
+      r.exec("box = pict.getPictFrame()");
+      int width = ((Integer) r.exec("box.getWidth()")).intValue();
+      int height = ((Integer) r.exec("box.getHeight()")).intValue();
+      // note: could get a RawEncodedImage from the Pict, but
+      // apparently no way to get a PixMap from the REI
+      r.exec("g = new QDGraphics(box)");
+      r.exec("pict.draw(g, box)");
+      // get data from the QDGraphics
+      r.exec("pixMap = g.getPixMap()");
+      r.exec("rei = pixMap.getPixelData()");
+
+      // copy bytes to an array
+      int rowBytes = ((Integer) r.exec("pixMap.getRowBytes()")).intValue();
+      int intsPerRow = rowBytes / 4;
+      int pixLen = intsPerRow * height;
+      r.setVar("pixLen", pixLen);
+      int[] pixels = new int[pixLen];
+      r.setVar("pixels", pixels);
+      r.setVar("zero", new Integer(0));
+      r.exec("rei.copyToArray(zero, pixels, zero, pixLen)");
+
+      // now coax into image, ignoring alpha for speed
+      int bitsPerSample = 32;
+      int redMask = 0x00ff0000;
+      int greenMask = 0x0000ff00;
+      int blueMask = 0x000000ff;
+      int alphaMask = 0x00000000;
+      DirectColorModel colorModel = new DirectColorModel(
+        bitsPerSample, redMask, greenMask, blueMask, alphaMask);
+
+      r.exec("QTSession.close()");
+      return Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(
+        width, height, colorModel, pixels, 0, intsPerRow));
+    }
+    catch (ReflectException e) {
+      try { r.exec("QTSession.close()"); }
+      catch (ReflectException exc) { LogTools.trace(exc); }
+      throw new FormatException("PICT extraction failed", e);
+    }
+  }
+
+}
diff --git a/loci/formats/Location.java b/loci/formats/Location.java
new file mode 100644
index 0000000..4b52b8b
--- /dev/null
+++ b/loci/formats/Location.java
@@ -0,0 +1,316 @@
+//
+// Location.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+import java.net.*;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Pseudo-extension of java.io.File that supports reading over HTTP.
+ * It is strongly recommended that you use this instead of java.io.File.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/Location.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/Location.java">SVN</a></dd></dl>
+ */
+public class Location {
+
+  // -- Static fields --
+
+  /** Map from given filenames to actual filenames. */
+  private static Hashtable idMap = new Hashtable();
+
+  // -- Fields --
+
+  private boolean isURL = true;
+  private URL url;
+  private File file;
+
+  // -- Constructors --
+
+  public Location(String pathname) {
+    try {
+      url = new URL(getMappedId(pathname));
+    }
+    catch (MalformedURLException e) {
+      isURL = false;
+    }
+    if (!isURL) file = new File(getMappedId(pathname));
+  }
+
+  public Location(File file) {
+    isURL = false;
+    this.file = file;
+  }
+
+  public Location(String parent, String child) {
+    this(parent + "/" + child);
+  }
+
+  public Location(Location parent, String child) {
+    this(parent.getAbsolutePath(), child);
+  }
+
+  // -- Location API methods --
+
+  /**
+   * Maps the given id to the actual filename on disk. Typically actual
+   * filenames are used for ids, making this step unnecessary, but in some
+   * cases it is useful; e.g., if the file has been renamed to conform to a
+   * standard naming scheme and the original file extension is lost, then
+   * using the original filename as the id assists format handlers with type
+   * identification and pattern matching, and the id can be mapped to the
+   * actual filename for reading the file's contents.
+   * @see #getMappedId(String)
+   */
+  public static void mapId(String id, String filename) {
+    if (idMap == null) idMap = new Hashtable();
+    if (id == null) return;
+    if (filename == null) idMap.remove(id);
+    else idMap.put(id, filename);
+  }
+
+  /**
+   * Gets the actual filename on disk for the given id. Typically the id itself
+   * is the filename, but in some cases may not be; e.g., if OMEIS has renamed
+   * a file from its original name to a standard location such as Files/101,
+   * the original filename is useful for checking the file extension and doing
+   * pattern matching, but the renamed filename is required to read its
+   * contents.
+   * @see #mapId(String, String)
+   */
+  public static String getMappedId(String id) {
+    if (idMap == null) return id;
+    String filename = id == null ? null : (String) idMap.get(id);
+    return filename == null ? id : filename;
+  }
+
+  public static Hashtable getIdMap() { return idMap; }
+
+  public static void setIdMap(Hashtable map) {
+    if (map != null) idMap = map;
+    else idMap = new Hashtable();
+  }
+
+  // -- File API methods --
+
+  /* @see java.io.File#canRead() */
+  public boolean canRead() {
+    return isURL ? true : file.canRead();
+  }
+
+  /* @see java.io.File#canWrite() */
+  public boolean canWrite() {
+    return isURL ? false : file.canWrite();
+  }
+
+  /* @see java.io.File#createNewFile() */
+  public boolean createNewFile() throws IOException {
+    if (isURL) throw new IOException("Unimplemented");
+    return file.createNewFile();
+  }
+
+  /* @see java.io.File#delete() */
+  public boolean delete() {
+    return isURL ? false : file.delete();
+  }
+
+  /* @see java.io.File#deleteOnExit() */
+  public void deleteOnExit() {
+    if (!isURL) file.deleteOnExit();
+  }
+
+  /* @see java.io.File#equals(Object) */
+  public boolean equals(Object obj) {
+    return isURL ? url.equals(obj) : file.equals(obj);
+  }
+
+  /* @see java.io.File#exists() */
+  public boolean exists() {
+    if (isURL) {
+      try {
+        url.getContent();
+        return true;
+      }
+      catch (IOException e) {
+        return false;
+      }
+    }
+    return file.exists();
+  }
+
+  /* @see java.io.File#getAbsoluteFile() */
+  public Location getAbsoluteFile() {
+    return new Location(getAbsolutePath());
+  }
+
+  /* @see java.io.File#getAbsolutePath() */
+  public String getAbsolutePath() {
+    return isURL ? url.toExternalForm() : file.getAbsolutePath();
+  }
+
+  /* @see java.io.File#getCanonicalFile() */
+  public Location getCanonicalFile() throws IOException {
+    return getAbsoluteFile();
+  }
+
+  /* @see java.io.File#getCanonicalPath() */
+  public String getCanonicalPath() throws IOException {
+    return isURL ? getAbsolutePath() : file.getCanonicalPath();
+  }
+
+  /* @see java.io.File#getName() */
+  public String getName() {
+    if (isURL) {
+      String name = url.getFile();
+      name = name.substring(name.lastIndexOf("/") + 1);
+      return name;
+    }
+    return file.getName();
+  }
+
+  /* @see java.io.File#getParent() */
+  public String getParent() {
+    if (isURL) {
+      String absPath = getAbsolutePath();
+      absPath = absPath.substring(0, absPath.lastIndexOf("/"));
+      return absPath;
+    }
+    return file.getParent();
+  }
+
+  /* @see java.io.File#getParentFile() */
+  public Location getParentFile() {
+    return new Location(getParent());
+  }
+
+  /* @see java.io.File#getPath() */
+  public String getPath() {
+    return isURL ? url.getHost() + url.getPath() : file.getPath();
+  }
+
+  /* @see java.io.File#isAbsolute() */
+  public boolean isAbsolute() {
+    return isURL ? true : file.isAbsolute();
+  }
+
+  /* @see java.io.File#isDirectory() */
+  public boolean isDirectory() {
+    return isURL ? lastModified() == 0 : file.isDirectory();
+  }
+
+  /* @see java.io.File#isFile() */
+  public boolean isFile() {
+    return isURL ? lastModified() > 0 : file.isFile();
+  }
+
+  /* @see java.io.File#isHidden() */
+  public boolean isHidden() {
+    return isURL ? false : file.isHidden();
+  }
+
+  /* @see java.io.File#lastModified() */
+  public long lastModified() {
+    if (isURL) {
+      try {
+        return url.openConnection().getLastModified();
+      }
+      catch (IOException e) { return 0; }
+    }
+    return file.lastModified();
+  }
+
+  /* @see java.io.File#length() */
+  public long length() {
+    if (isURL) {
+      try {
+        return url.openConnection().getContentLength();
+      }
+      catch (IOException e) { return 0; }
+    }
+    return file.length();
+  }
+
+  /* @see java.io.File#list() */
+  public String[] list() {
+    if (isURL) {
+      if (!isDirectory()) return null;
+      try {
+        URLConnection c = url.openConnection();
+        InputStream is = c.getInputStream();
+        boolean foundEnd = false;
+
+        Vector files = new Vector();
+        while (!foundEnd) {
+          byte[] b = new byte[is.available()];
+          String s = new String(b);
+          if (s.toLowerCase().indexOf("</html>") != -1) foundEnd = true;
+
+          while (s.indexOf("a href") != -1) {
+            int ndx = s.indexOf("a href") + 8;
+            String f = s.substring(ndx, s.indexOf("\"", ndx));
+            s = s.substring(s.indexOf("\"", ndx) + 1);
+            Location check = new Location(getAbsolutePath(), f);
+            if (check.exists()) {
+              files.add(check.getName());
+            }
+          }
+        }
+        return (String[]) files.toArray(new String[0]);
+      }
+      catch (IOException e) {
+        return null;
+      }
+    }
+    return file.list();
+  }
+
+  /* @see java.io.File#listFiles() */
+  public Location[] listFiles() {
+    String[] s = list();
+    if (s == null) return null;
+    Location[] f = new Location[s.length];
+    for (int i=0; i<f.length; i++) {
+      f[i] = new Location(getAbsolutePath(), s[i]);
+      f[i] = f[i].getAbsoluteFile();
+    }
+    return f;
+  }
+
+  /* @see java.io.File#toURL() */
+  public URL toURL() throws MalformedURLException {
+    return isURL ? url : file.toURI().toURL();
+  }
+
+  // -- Object API methods --
+
+  /* @see java.lang.Object#toString() */
+  public String toString() {
+    return isURL ? url.toString() : file.toString();
+  }
+
+}
diff --git a/loci/formats/Log.java b/loci/formats/Log.java
new file mode 100644
index 0000000..91c3cb0
--- /dev/null
+++ b/loci/formats/Log.java
@@ -0,0 +1,81 @@
+//
+// Log.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+/**
+ * A simple logging class.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/Log.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/Log.java">SVN</a></dd></dl>
+ */
+public class Log {
+
+  // -- Constants --
+
+  public static final String NL = System.getProperty("line.separator");
+
+  // -- Log API methods --
+
+  /** Main output method. Override to control how logging occurs. */
+  public void print(String x) {
+    // default implementation writes to the console
+    System.out.print(x);
+  }
+
+  /** Flushes buffer to the log. Override to control how logging occurs. */
+  public void flush() {
+    // default implementation flushes the console
+    System.out.flush();
+  }
+
+  public void print(boolean x) { print("" + x); }
+  public void print(char x) { print("" + x); }
+  public void print(double x) { print("" + x); }
+  public void print(float x) { print("" + x); }
+  public void print(int x) { print("" + x); }
+  public void print(long x) { print("" + x); }
+  public void print(Object x) { print(x.toString()); }
+  public void println() { println(""); }
+  public void println(boolean x) { println("" + x); }
+  public void println(char x) { println("" + x); }
+  public void println(double x) { println("" + x); }
+  public void println(float x) { println("" + x); }
+  public void println(int x) { println("" + x); }
+  public void println(long x) { println("" + x); }
+  public void println(Object x) { println("" + x); }
+  public void println(String x) { print(x + NL); }
+
+  public void trace(String s) { trace(new Exception(s)); }
+  public void trace(Throwable t) {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    t.printStackTrace(new PrintStream(out));
+    println(out);
+  }
+
+}
diff --git a/loci/formats/LogTools.java b/loci/formats/LogTools.java
new file mode 100644
index 0000000..8f4b7af
--- /dev/null
+++ b/loci/formats/LogTools.java
@@ -0,0 +1,72 @@
+//
+// LogTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * A utility class for logging.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/LogTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/LogTools.java">SVN</a></dd></dl>
+ */
+public final class LogTools {
+
+  // -- Static fields --
+
+  private static Log log = new Log();
+
+  // -- Constructor --
+
+  private LogTools() { }
+
+  // -- Utility methods - logging --
+
+  public static void setLog(Log log) { LogTools.log = log; }
+  public static Log getLog() { return log; }
+
+  public static void print(boolean x) { log.print(x); }
+  public static void print(char x) { log.print(x); }
+  public static void print(double x) { log.print(x); }
+  public static void print(float x) { log.print(x); }
+  public static void print(int x) { log.print(x); }
+  public static void print(long x) { log.print(x); }
+  public static void print(Object x) { log.print(x); }
+  public static void print(String x) { log.print(x); }
+  public static void println() { log.println(); }
+  public static void println(boolean x) { log.println(x); }
+  public static void println(char x) { log.println(x); }
+  public static void println(double x) { log.println(x); }
+  public static void println(float x) { log.println(x); }
+  public static void println(int x) { log.println(x); }
+  public static void println(long x) { log.println(x); }
+  public static void println(Object x) { log.println(x); }
+  public static void println(String x) { log.println(x); }
+
+  public static void trace(String s) { log.trace(s); }
+  public static void trace(Throwable t) { log.trace(t); }
+
+  public static void flush()  { log.flush(); }
+
+}
diff --git a/loci/formats/MetadataRetrieve.java b/loci/formats/MetadataRetrieve.java
new file mode 100644
index 0000000..10605fd
--- /dev/null
+++ b/loci/formats/MetadataRetrieve.java
@@ -0,0 +1,946 @@
+//
+// MetadataRetrieve.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * TODO - MetadataRetrieve javadoc.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/MetadataRetrieve.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/MetadataRetrieve.java">SVN</a></dd></dl>
+ */
+public interface MetadataRetrieve {
+
+  // -- Image attribute retrieval methods --
+
+  /** Returns the number of images. */
+  int getImageCount();
+
+  /**
+   * Gets the nth image's name.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  String getImageName(Integer n);
+
+  /**
+   * Gets the nth image's creation date.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  String getCreationDate(Integer n);
+
+  /**
+   * Gets the nth image's description.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  String getDescription(Integer n);
+
+  // -- Experimenter attribute retrieval methods --
+
+  /** Returns the number of experimenters. */
+  int getExperimenterCount();
+
+  /**
+   * Gets the first name of the nth experimenter.
+   * @param n the index of the experimenter.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getFirstName(Integer n);
+
+  /**
+   * Gets the last name of the nth experimenter.
+   * @param n the index of the experimenter.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getLastName(Integer n);
+
+  /**
+   * Gets the email address of the nth experimenter.
+   * @param n the index of the experimenter.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getEmail(Integer n);
+
+  /**
+   * Gets the institution of the nth experimenter.
+   * @param n the index of the experimenter.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getInstitution(Integer n);
+
+  /**
+   * Gets the data directory of the nth experimenter.
+   * @param n the index of the experimenter.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getDataDirectory(Integer n);
+
+  /**
+   * Gets the group of the nth experimenter.
+   * @param n the index of the experimenter.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Object getGroup(Integer n);
+
+  // -- Group attribute retrieval methods --
+
+  /** Returns the number of groups. */
+  int getGroupCount();
+
+  /**
+   * Get the name of the nth group.
+   * @param n the index of the group.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getGroupName(Integer n);
+
+  /**
+   * Get the leader of the nth group.
+   * @param n the index of the group.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Object getLeader(Integer n);
+
+  /**
+   * Get the contact of the nth group.
+   * @param n the index of the group.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Object getContact(Integer n);
+
+  // -- Instrument attribute retrieval methods --
+
+  /** Returns the number of instruments. */
+  int getInstrumentCount();
+
+  /**
+   * Get the manufacturer of the nth instrument.
+   * @param n the index of the instrument.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getManufacturer(Integer n);
+
+  /**
+   * Get the model of the nth instrument.
+   * @param n the index of the instrument.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getModel(Integer n);
+
+  /**
+   * Get the serial number of the nth instrument.
+   * @param n the index of the instrument.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getSerialNumber(Integer n);
+
+  /**
+   * Get the type of the nth instrument.
+   * @param n the index of the instrument.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getType(Integer n);
+
+  // -- Dimensions attribute retrieval methods --
+
+  /**
+   * Gets the nth image's PixelSizeX attribute.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getPixelSizeX(Integer n);
+
+  /**
+   * Gets the nth image's PixelSizeY attribute.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getPixelSizeY(Integer n);
+
+  /**
+   * Gets the nth image's PixelSizeZ attribute.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getPixelSizeZ(Integer n);
+
+  /**
+   * Gets the nth image's PixelSizeC attribute.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getPixelSizeC(Integer n);
+
+  /**
+   * Gets the nth image's PixelSizeT attribute.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getPixelSizeT(Integer n);
+
+  // -- Display ROI attribute retrieval methods --
+
+  /** Returns the number of DisplayROIs. */
+  int getDisplayROICount();
+
+  /**
+   * Get the lower X bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getX0(Integer n);
+
+  /**
+   * Get the lower Y bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getY0(Integer n);
+
+  /**
+   * Get the lower Z bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getZ0(Integer n);
+
+  /**
+   * Get the lower T bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getT0(Integer n);
+
+  /**
+   * Get the upper X bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getX1(Integer n);
+
+  /**
+   * Get the upper Y bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getY1(Integer n);
+
+  /**
+   * Get the upper Z bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getZ1(Integer n);
+
+  /**
+   * Get the upper T bound of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getT1(Integer n);
+
+  /**
+   * Get the display options of the nth ROI.
+   * @param n the index of the ROI.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Object getDisplayOptions(Integer n);
+
+  // -- Pixels attribute retrieval methods --
+
+  /** Returns the number of pixels elements for the given image. */
+  int getPixelsCount(Integer n);
+
+  /**
+   * Gets the SizeX attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getSizeX(Integer image);
+
+  /**
+   * Gets the SizeY attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getSizeY(Integer image);
+
+  /**
+   * Gets the SizeZ attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getSizeZ(Integer image);
+
+  /**
+   * Gets the SizeC attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getSizeC(Integer image);
+
+  /**
+   * Gets the SizeT attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getSizeT(Integer image);
+
+  /**
+   * Gets the PixelType attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getPixelType(Integer image);
+
+  /**
+   * Gets the BigEndian attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Boolean getBigEndian(Integer image);
+
+  /**
+   * Gets the DimensionOrder attribute of the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getDimensionOrder(Integer image);
+
+  // -- StageLabel attribute retrieval methods --
+
+  /** Returns the number of stage labels. */
+  int getStageLabelCount();
+
+  /**
+   * Gets the name of the nth stage label.
+   * @param n the index of the stage label.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getStageName(Integer n);
+
+  /**
+   * Gets the X coordinate of the nth stage label.
+   * @param n the index of the stage label.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getStageX(Integer n);
+
+  /**
+   * Gets the Y coordinate of the nth stage label.
+   * @param n the index of the stage label.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getStageY(Integer n);
+
+  /**
+   * Gets the Z coordinate of the nth stage label.
+   * @param n the index of the stage label.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getStageZ(Integer n);
+
+  // -- LogicalChannel attribute retrieval methods --
+
+  /** Returns the number of channels for the given pixels element. */
+  int getChannelCount(Integer n);
+
+  /**
+   * Gets the name of the given channel in the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getChannelName(Integer pixels, Integer channel);
+
+  /**
+   * Gets the ND filter value for the given channel in the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getChannelNDFilter(Integer pixels, Integer channel);
+
+  /**
+   * Gets the emission wavelength of the given channel in the given
+   * set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getEmWave(Integer pixels, Integer channel);
+
+  /**
+   * Gets the excitation wavelength of the given channel in the given
+   * set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getExWave(Integer pixels, Integer channel);
+
+  /**
+   * Gets the photometric interpretation of the given channel in the given
+   * set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getPhotometricInterpretation(Integer pixels, Integer channel);
+
+  /**
+   * Gets the mode of the given channel in the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getMode(Integer pixels, Integer channel);
+
+  /**
+   * Gets the minimum pixel value within the given channel in the
+   * given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Double getGlobalMin(Integer pixels, Integer channel);
+
+  /**
+   * Gets the maximum pixel value within the given channel in the
+   * given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Double getGlobalMax(Integer pixels, Integer channel);
+
+  // -- PlaneInfo attribute retrieval methods --
+
+  /**
+   * Gets the timestamp of the plane with the given Z, C and T coordinates in
+   * the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param z the Z coordinate of the plane.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param c the C coordinate of the plane.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param t the T coordinate of the plane.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getTimestamp(Integer pixels, Integer z, Integer c, Integer t);
+
+  /**
+   * Gets the exposure time of the plane with the given Z, C and T coordinates
+   * in the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param z the Z coordinate of the plane.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param c the C coordinate of the plane.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param t the T coordinate of the plane.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getExposureTime(Integer pixels, Integer z, Integer c, Integer t);
+
+  // -- ImagingEnvironment attribute retrieval methods --
+
+  /**
+   * Gets the temperature associated with the given image.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getTemperature(Integer n);
+
+  /**
+   * Gets the air pressure associated with the given image.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getAirPressure(Integer n);
+
+  /**
+   * Gets the humidity associated with the given image.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getHumidity(Integer n);
+
+  /**
+   * Gets the CO2 percentage associated with the given image.
+   * @param n the index of the image.  If <code>null</code> the default index
+   * of 0 will be used.
+   */
+  Float getCO2Percent(Integer n);
+
+  // -- DisplayChannel attribute retrieval methods --
+
+  /**
+   * Gets the black level of the given channel in the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Double getBlackLevel(Integer pixels, Integer channel);
+
+  /**
+   * Gets the white level of the given channel in the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Double getWhiteLevel(Integer pixels, Integer channel);
+
+  /**
+   * Gets the gamma value of the given channel in the given set of pixels.
+   * @param pixels the index of the pixels.  If <code>null</code> the default
+   * index of 0 will be used.
+   * @param channel the index of the channel.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getGamma(Integer pixels, Integer channel);
+
+  // -- DisplayOptions attribute retrieval methods --
+
+  /**
+   * Gets the zoom value associated with the given pixels in the given image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getZoom(Integer image);
+
+  /**
+   * Gets whether or not the red channel is activated.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Boolean isRedChannelOn(Integer image);
+
+  /**
+   * Gets whether or not the green channel is activated.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Boolean isGreenChannelOn(Integer image);
+
+  /**
+   * Gets whether or not the blue channel is activated.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Boolean isBlueChannelOn(Integer image);
+
+  /**
+   * Gets whether or not the given pixels are displayed as an RGB image.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Boolean isDisplayRGB(Integer image);
+
+  /**
+   * Gets the color map associated with the given pixels.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getColorMap(Integer image);
+
+  /**
+   * Gets the minimum Z coordinate for which the display settings apply.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getZStart(Integer image);
+
+  /**
+   * Gets the maximum Z coordinate for which the display settings apply.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getZStop(Integer image);
+
+  /**
+   * Gets the minimum T coordinate for which the display settings apply.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getTStart(Integer image);
+
+  /**
+   * Gets the maximum T coordinate for which the display settings apply.
+   * @param image the index of the image.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getTStop(Integer image);
+
+  // -- LightSource attribute retrieval methods --
+
+  /**
+   * Gets the manufacturer of the given light source.
+   * @param light the index of the light.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getLightManufacturer(Integer light);
+
+  /**
+   * Gets the model of the given light source.
+   * @param light the index of the light.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getLightModel(Integer light);
+
+  /**
+   * Gets the serial number of the given light source.
+   * @param light the index of the light.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getLightSerial(Integer light);
+
+  // -- Laser attribute retrieval methods --
+
+  /**
+   * Gets the type of the given laser.
+   * @param laser the index of the laser.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getLaserType(Integer laser);
+
+  /**
+   * Gets the medium of the given laser.
+   * @param laser the index of the laser.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getLaserMedium(Integer laser);
+
+  /**
+   * Gets the wavelength of the given laser.
+   * @param laser the index of the laser.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Integer getLaserWavelength(Integer laser);
+
+  /**
+   * Gets whether or not the frequency is doubled for the given laser.
+   * @param laser the index of the laser.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Boolean isFrequencyDoubled(Integer laser);
+
+  /**
+   * Gets whether or not the given laser is tunable.
+   * @param laser the index of the laser.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Boolean isTunable(Integer laser);
+
+  /**
+   * Gets the pulse of the given laser.
+   * @param laser the index of the laser.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getPulse(Integer laser);
+
+  /**
+   * Gets the power (in watts) of the given laser.
+   * @param laser the index of the laser.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getPower(Integer laser);
+
+  // -- Filament attribute retrieval methods --
+
+  /**
+   * Gets the type of the given filament.
+   * @param filament the index of the filament.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getFilamentType(Integer filament);
+
+  /**
+   * Gets the power (in watts) of the given filament.
+   * @param filament the index of the filament.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Float getFilamentPower(Integer filament);
+
+  // -- Arc attribute retrieval methods --
+
+  /**
+   * Gets the type of the given arc.
+   * @param arc the index of the arc.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  String getArcType(Integer arc);
+
+  /**
+   * Gets the power (in watts) of the given arc.
+   * @param arc the index of the arc.  If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  Float getArcPower(Integer arc);
+
+  // -- Detector attribute retrieval methods --
+
+  /**
+   * Gets the manufacturer of the given detector.
+   * @param detector the index of the detector.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getDetectorManufacturer(Integer detector);
+
+  /**
+   * Gets the model of the given detector.
+   * @param detector the index of the detector.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getDetectorModel(Integer detector);
+
+  /**
+   * Gets the serial number of the given detector.
+   * @param detector the index of the detector.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getDetectorSerial(Integer detector);
+
+  /**
+   * Gets the type of the given detector.
+   * @param detector the index of the detector.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getDetectorType(Integer detector);
+
+  /**
+   * Gets the gain value of the given detector.
+   * @param detector the index of the detector.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Float getDetectorGain(Integer detector);
+
+  /**
+   * Gets the voltage of the given detector.
+   * @param detector the index of the detector.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Float getDetectorVoltage(Integer detector);
+
+  /**
+   * Gets the offset of the given detector.
+   * @param detector the index of the detector.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Float getDetectorOffset(Integer detector);
+
+  // -- Objective attribute retrieval methods --
+
+  /**
+   * Gets the manufacturer of the given objective.
+   * @param objective the index of the objective.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getObjectiveManufacturer(Integer objective);
+
+  /**
+   * Gets the model of the given objective.
+   * @param objective the index of the objective.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getObjectiveModel(Integer objective);
+
+  /**
+   * Gets the serial number of the given objective.
+   * @param objective the index of the objective.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getObjectiveSerial(Integer objective);
+
+  /**
+   * Gets the lens NA of the given objective.
+   * @param objective the index of the objective.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Float getLensNA(Integer objective);
+
+  /**
+   * Gets the magnification value of the given objective.
+   * @param objective the index of the objective.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Float getObjectiveMagnification(Integer objective);
+
+  // -- ExcitationFilter attribute retrieval methods --
+
+  /**
+   * Gets the manufacturer of the given excitation filter.
+   * @param filter the index of the filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getExcitationManufacturer(Integer filter);
+
+  /**
+   * Gets the model of the given excitation filter.
+   * @param filter the index of the filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getExcitationModel(Integer filter);
+
+  /**
+   * Gets the lot number of the given excitation filter.
+   * @param filter the index of the filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getExcitationLotNumber(Integer filter);
+
+  /**
+   * Gets the type of the given excitation filter.
+   * @param filter the index of the filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getExcitationType(Integer filter);
+
+  // -- Dichroic attribute retrieval methods --
+
+  /**
+   * Gets the manufacturer of the given dichroic.
+   * @param dichroic the index of the dichroic.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getDichroicManufacturer(Integer dichroic);
+
+  /**
+   * Gets the model of the given dichroic.
+   * @param dichroic the index of the dichroic.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getDichroicModel(Integer dichroic);
+
+  /**
+   * Gets the lot number of the given dichroic.
+   * @param dichroic the index of the dichroic.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getDichroicLotNumber(Integer dichroic);
+
+  // -- EmissionFilter attribute retrieval methods --
+
+  /**
+   * Gets the manufacturer of the given emission filter.
+   * @param filter the index of the emission filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getEmissionManufacturer(Integer filter);
+
+  /**
+   * Gets the model of the given emission filter.
+   * @param filter the index of the emission filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getEmissionModel(Integer filter);
+
+  /**
+   * Gets the lot number of the given emission filter.
+   * @param filter the index of the emission filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getEmissionLotNumber(Integer filter);
+
+  /**
+   * Gets the type of the given emission filter.
+   * @param filter the index of the emission filter.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getEmissionType(Integer filter);
+
+  // -- FilterSet attribute retrieval methods --
+
+  /**
+   * Gets the manufacturer of the given filter set.
+   * @param filterSet the index of the filter set.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getFilterSetManufacturer(Integer filterSet);
+
+  /**
+   * Gets the manufacturer of the given filter set.
+   * @param filterSet the index of the filter set.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getFilterSetModel(Integer filterSet);
+
+  /**
+   * Gets the manufacturer of the given filter set.
+   * @param filterSet the index of the filter set.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getFilterSetLotNumber(Integer filterSet);
+
+  // -- OTF attribute retrieval methods --
+
+  /**
+   * Gets the width of the given OTF.
+   * @param otf the index of the OTF.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Integer getOTFSizeX(Integer otf);
+
+  /**
+   * Gets the height of the given OTF.
+   * @param otf the index of the OTF.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Integer getOTFSizeY(Integer otf);
+
+  /**
+   * Gets the pixel type of the given OTF.
+   * @param otf the index of the OTF.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getOTFPixelType(Integer otf);
+
+  /**
+   * Gets the path to the given OTF.
+   * @param otf the index of the OTF.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  String getOTFPath(Integer otf);
+
+  /**
+   * Gets whether or not optical axis averaging is used for the given OTF.
+   * @param otf the index of the OTF.  If <code>null</code> the
+   * default index of 0 will be used.
+   */
+  Boolean getOTFOpticalAxisAverage(Integer otf);
+}
diff --git a/loci/formats/MetadataStore.java b/loci/formats/MetadataStore.java
new file mode 100644
index 0000000..d5a55a4
--- /dev/null
+++ b/loci/formats/MetadataStore.java
@@ -0,0 +1,327 @@
+//
+// MetadataStore.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * A proxy whose responsibility it is to marshal biological image data into a
+ * particular storage medium.
+ *
+ * The <code>MetadataStore</code> interface encompasses the basic metadata that
+ * any specific storage medium (file, relational database, etc.) should be
+ * expected to store and be expected to return with relationships maintained.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/MetadataStore.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/MetadataStore.java">SVN</a></dd></dl>
+ *
+ * @author Chris Allan callan at blackcat.ca
+ *
+ * TODO: Further work needs to be done to ensure the validity of the arguments
+ * to these methods with not-null constraints and NullPointerException
+ * declarations (should be unchecked exceptions).
+ */
+public interface MetadataStore {
+
+  /**
+   * Creates a new <i>root</i> object to be used by the metadata store and
+   * resets the internal state of the metadata store.
+   */
+  void createRoot();
+
+  /**
+   * Sets the <i>root</i> object of the metadata store.
+   * @param root object that the store can use as its root.
+   */
+  void setRoot(Object root);
+
+  /**
+   * Retrieves the <i>root</i> object of the metadata store.
+   * @return object that the store is using as its root.
+   */
+  Object getRoot();
+
+  /**
+   * Creates an image in the metadata store with a particular index.
+   * @param name the full name of the image.
+   * @param creationDate the creation date of the image.
+   * @param description the full description of the image.
+   * @param i the index to use in the store. If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  void setImage(String name, String creationDate,
+                String description, Integer i);
+
+  /**
+   * Creates an experimenter in the metadata store with a particular index.
+   * @param firstName the first name of the experimenter
+   * @param lastName the last name of the experimenter
+   * @param email the e-mail address of the experimenter
+   * @param institution the institution for which the experimenter belongs
+   * @param dataDirectory the fully qualified path to the experimenter's data
+   * @param group the group to which the experimenter belongs
+   * @param i the index to use in the store. If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  void setExperimenter(String firstName, String lastName, String email,
+                       String institution, String dataDirectory,
+                       Object group, Integer i);
+
+  /**
+   * Creates a group in the metadata store with a particular index.
+   * @param name the name of the group.
+   * @param leader the leader of the group.
+   * @param contact the contact for the group.
+   * @param i the index to use in the store. If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  void setGroup(String name, Object leader, Object contact, Integer i);
+
+  /**
+   * Creates an instrument in the metadata store with a particular index.
+   * @param manufacturer the name of the manufacturer.
+   * @param model the model number of the instrument.
+   * @param serialNumber the serial number of the instrument.
+   * @param type the type of the instrument.
+   * @param i the index to use in the store. If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  void setInstrument(String manufacturer, String model,
+                     String serialNumber, String type, Integer i);
+
+  /**
+   * Creates a set of pixel dimensions in the metadata store with a particular
+   * index.  Unless both values are non-null, the MetadataStore should assume
+   * pixelSizeX equals pixelSizeY (i.e., should populate the null field with
+   * the other field's value).
+   * @param pixelSizeX size of an individual pixel's X axis in microns.
+   * @param pixelSizeY size of an individual pixel's Y axis in microns.
+   * @param pixelSizeZ size of an individual pixel's Z axis in microns.
+   * @param pixelSizeC FIXME: Unknown
+   * @param pixelSizeT FIXME: Unknown
+   * @param i the index to use in the store. If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  void setDimensions(Float pixelSizeX, Float pixelSizeY,
+                     Float pixelSizeZ, Float pixelSizeC,
+                     Float pixelSizeT, Integer i);
+
+  /**
+   * Creates a 5D bounding box region of interest and a set of display options
+   * in the metadata store with a particular index.
+   * @param x0 the starting X coordinate.
+   * @param y0 the starting Y coordinate.
+   * @param z0 the starting Z coordinate.
+   * @param x1 the ending X coordinate.
+   * @param y1 the ending Y coordinate.
+   * @param z1 the ending Z coordinate.
+   * @param t0 the starting timepoint.
+   * @param t1 the ending timepoint.
+   * @param displayOptions the display options to attach to this region of
+   * interest.
+   * @param i the index to use in the store. If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  void setDisplayROI(Integer x0, Integer y0, Integer z0,
+                     Integer x1, Integer y1, Integer z1,
+                     Integer t0, Integer t1, Object displayOptions,
+                     Integer i);
+
+  /**
+   * Creates a pixels set in the metadata store with a particular
+   * image and pixels index.
+   * @param sizeX size of an individual plane or section's X axis (width)
+   * @param sizeY size of an individual plane of section's Y axis (height)
+   * @param sizeZ number of optical sections per channel, per timepoint
+   * (per stack)
+   * @param sizeC number of channels per timepoint.
+   * @param sizeT number of timepoints.
+   * @param pixelType the pixel type. One of the enumerated static values
+   * present in {@link FormatReader}.
+   * @param bigEndian if the pixels set is big endian or not.
+   * @param dimensionOrder the dimension order of the pixels set.
+   * @param imageNo the image index to use in the store.
+   * If <code>null</code> the default index of 0 will be used.
+   * @param pixelsNo the pixels index to use in the store.
+   * If <code>null</code> the default index of 0 will be used.
+   */
+  void setPixels(Integer sizeX, Integer sizeY, Integer sizeZ,
+                 Integer sizeC, Integer sizeT, Integer pixelType,
+                 Boolean bigEndian, String dimensionOrder,
+                 Integer imageNo, Integer pixelsNo);
+
+  /**
+   * Creates a stage label in the metadata store with a particular index.
+   * @param name a name for the stage label.
+   * @param x coordinate of the stage.
+   * @param y coordinate of the stage.
+   * @param z coordinate of the stage.
+   * @param i the index to use in the store. If <code>null</code> the default
+   * index of 0 will be used.
+   */
+  void setStageLabel(String name, Float x, Float y, Float z, Integer i);
+
+  /**
+   * Creates a logical channel and physical channel in the metadata store for a
+   * particular pixels.
+   * @param channelIdx the index of the channel within the pixels set.
+   * @param name the logical channel's name.
+   * @param samplesPerPixel
+   * @param filter index of the filter associated with this channel.
+   * @param lightSource index of the primary light source.
+   * @param lightAttenuation  the primary light source attenuation.
+   * @param lightWavelength the primary light source wavelength.
+   * @param otf the index of the OTF associated with this channel.
+   * @param detector  the index of the detector associated with this channel.
+   * @param detectorOffset the detector offset.
+   * @param detectorGain the detector gain.
+   * @param illuminationType the illumination type.
+   * @param pinholeSize the size of the pinhole.
+   * @param photometricInterpretation the photometric interpretation type.
+   * @param mode the acquisition mode.
+   * @param contrastMethod the constrast method name.
+   * @param auxLightSource index of the auxiliary light source.
+   * @param auxLightAttenuation the auxiliary light source attenuation.
+   * @param auxTechnique the auxiliary technique type.
+   * @param auxLightWavelength the auxiliary light source wavelength.
+   * @param emWave the emission wavelength.
+   * @param exWave the excitation wavelength.
+   * @param fluor the fluorescence type.
+   * @param ndFilter the neutral-density filter value.
+   * @param i the index of the pixels set within the metadata store.
+   */
+  void setLogicalChannel(int channelIdx, String name, Integer samplesPerPixel,
+    Integer filter, Integer lightSource, Float lightAttenuation,
+    Integer lightWavelength, Integer otf, Integer detector,
+    Float detectorOffset, Float detectorGain, String illuminationType,
+    Integer pinholeSize, String photometricInterpretation, String mode,
+    String contrastMethod, Integer auxLightSource, Float auxLightAttenuation,
+    String auxTechnique, Integer auxLightWavelength, Integer emWave,
+    Integer exWave, String fluor, Float ndFilter, Integer i);
+
+  /**
+   * Sets a channel's global min and global max in the metadata store for a
+   * particular pixels set.
+   *
+   * NOTE: The implementation of this method is optional and can be purely a
+   * no-op. It is here to ensure compatability with certain stores which require
+   * this data to be specified explicitly.
+   *
+   * @param channel the index of the channel within the pixels set.
+   * @param globalMin the global minimum pixel value for the channel.
+   * @param globalMax the global maximum pixel value for the channel.
+   * @param i the index of the pixels set within the metadata store.
+   */
+  void setChannelGlobalMinMax(int channel, Double globalMin,
+                              Double globalMax, Integer i);
+
+  /**
+   * Sets the plane information for a particular X-Y plane (section) within a
+   * particular pixels set.
+   *
+   * NOTE: The implementation of this method is optional as this is a
+   * transitional type. More information about the PlaneInfo type can be found
+   * <a href="http://cvs.openmicroscopy.org.uk/tiki/tiki-index.php?page=DataModelProposal#id119301">here</a>.
+   *
+   * @param theZ the optical section index.
+   * @param theC the channel index.
+   * @param theT the timepoint.
+   * @param timestamp the time of acquisition in seconds of the plane (section)
+   * with zero being the start of acquistion.
+   * @param exposureTime exposure time in seconds.
+   * @param i the index of the pixels set within the metadata store.
+   */
+  void setPlaneInfo(int theZ, int theC, int theT, Float timestamp,
+                    Float exposureTime, Integer i);
+
+  /**
+   * Instructs the metadata store to set the default display settings for a
+   * particular pixels set.
+   * @param i the index of the pixels set within the metadata store.
+   */
+  void setDefaultDisplaySettings(Integer i);
+
+  /** Sets the imaging environment for a particular image. */
+  void setImagingEnvironment(Float temperature, Float airPressure,
+    Float humidity, Float co2Percent, Integer i);
+
+  /** Sets information about the specified channel for a particular image. */
+  void setDisplayChannel(Integer channelNumber, Double blackLevel,
+    Double whiteLevel, Float gamma, Integer i);
+
+  /** Sets various display options for a particular pixels set. */
+  void setDisplayOptions(Float zoom, Boolean redChannelOn,
+    Boolean greenChannelOn, Boolean blueChannelOn, Boolean displayRGB,
+    String colorMap, Integer zstart, Integer zstop, Integer tstart,
+    Integer tstop, Integer imageNdx, Integer pixelsNdx, Integer redChannel,
+    Integer greenChannel, Integer blueChannel, Integer grayChannel);
+
+  /** Sets a light source for a particular instrument. */
+  void setLightSource(String manufacturer, String model, String serialNumber,
+    Integer instrumentIndex, Integer lightIndex);
+
+  /** Sets a laser for a particular instrument. */
+  void setLaser(String type, String medium, Integer wavelength,
+    Boolean frequencyDoubled, Boolean tunable, String pulse, Float power,
+    Integer instrumentNdx, Integer lightNdx, Integer pumpNdx, Integer laserNdx);
+
+  /** Sets a filament for a particular instrument. */
+  void setFilament(String type, Float power, Integer lightNdx,
+    Integer filamentNdx);
+
+  /** Sets an arc for a particular instrument. */
+  void setArc(String type, Float power, Integer lightNdx, Integer arcNdx);
+
+  /** Sets a detector for a particular instrument. */
+  void setDetector(String manufacturer, String model, String serialNumber,
+    String type, Float gain, Float voltage, Float offset, Integer instrumentNdx,
+    Integer detectorNdx);
+
+  /** Sets an objective for a particular instrument. */
+  void setObjective(String manufacturer, String model, String serialNumber,
+    Float lensNA, Float magnification, Integer instrumentNdx,
+    Integer objectiveNdx);
+
+  /** Sets an excitation filter for a particular instrument. */
+  void setExcitationFilter(String manufacturer, String model, String lotNumber,
+    String type, Integer filterNdx);
+
+  /** Sets a dichroic for a particular instrument. */
+  void setDichroic(String manufacturer, String model, String lotNumber,
+    Integer dichroicNdx);
+
+  /** Sets an emission filter for a particular instrument. */
+  void setEmissionFilter(String manufacturer, String model, String lotNumber,
+    String type, Integer filterNdx);
+
+  /** Sets a filter set for a particular instrument. */
+  void setFilterSet(String manufacturer, String model, String lotNumber,
+    Integer filterSetNdx, Integer filterNdx);
+
+  /** Sets an OTF for a particular instrument. */
+  void setOTF(Integer sizeX, Integer sizeY, String pixelType, String path,
+    Boolean opticalAxisAverage, Integer instrumentNdx, Integer otfNdx,
+    Integer filterNdx, Integer objectiveNdx);
+
+}
diff --git a/loci/formats/MetadataTools.java b/loci/formats/MetadataTools.java
new file mode 100644
index 0000000..3c65b67
--- /dev/null
+++ b/loci/formats/MetadataTools.java
@@ -0,0 +1,337 @@
+//
+// MetadataTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXParseException;
+
+/**
+ * A utility class for working with metadata objects,
+ * including {@link MetadataStore}, {@link MetadataRetrieve},
+ * and OME-XML strings.
+ * Most of the methods require the optional {@link loci.formats.ome}
+ * package, and optional ome-java.jar library, to be present at runtime.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/MetadataTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/MetadataTools.java">SVN</a></dd></dl>
+ */
+public final class MetadataTools {
+
+  // -- Constructor --
+
+  private MetadataTools() { }
+
+  // -- Utility methods - OME-XML --
+
+  /**
+   * Creates an OME-XML metadata object using reflection, to avoid
+   * direct dependencies on the optional {@link loci.formats.ome} package.
+   * @return A new instance of {@link loci.formats.ome.OMEXMLMetadata},
+   *   or null if the class is not available.
+   */
+  public static MetadataStore createOMEXMLMetadata() {
+    return createOMEXMLMetadata(null);
+  }
+
+  /**
+   * Creates an OME-XML metadata object using reflection, to avoid
+   * direct dependencies on the optional {@link loci.formats.ome} package,
+   * wrapping a DOM representation of the given OME-XML string.
+   * @return A new instance of {@link loci.formats.ome.OMEXMLMetadata},
+   *   or null if the class is not available.
+   */
+  public static MetadataStore createOMEXMLMetadata(String xml) {
+    MetadataStore store = null;
+    ReflectedUniverse r = new ReflectedUniverse();
+    try {
+      r.exec("import loci.formats.ome.OMEXMLMetadata");
+      r.setVar("xml", xml);
+      store = (MetadataStore) r.exec("new OMEXMLMetadata(xml)");
+    }
+    catch (ReflectException exc) { }
+    return store;
+  }
+
+  /**
+   * Checks whether the given object is an OME-XML metadata object.
+   * @return True iff the object is an instance of
+   *   {@link loci.formats.ome.OMEXMLMetadata}.
+   */
+  public static boolean isOMEXMLMetadata(Object o) {
+    ReflectedUniverse r = new ReflectedUniverse();
+    try {
+      r.exec("import loci.formats.ome.OMEXMLMetadata");
+      Class c = (Class) r.getVar("OMEXMLMetadata");
+      return c.isInstance(o);
+    }
+    catch (ReflectException exc) { }
+    return false;
+  }
+
+  /**
+   * Adds the specified key/value pair as a new OriginalMetadata node
+   * to the given OME-XML metadata object.
+   * Does nothing unless the given object is an OME-XML metadata object.
+   * @param o An object of type {@link loci.formats.ome.OMEXMLMetadata}.
+   * @param key Metadata key to populate.
+   * @param value Metadata value corresponding to the specified key.
+   */
+  public static void populateOriginalMetadata(Object o,
+    String key, String value)
+  {
+    ReflectedUniverse r = new ReflectedUniverse();
+    r.setVar("omexmlMeta", o);
+    r.setVar("key", key);
+    r.setVar("value", value);
+    try {
+      r.exec("omexmlMeta.populateOriginalMetadata(key, value)");
+    }
+    catch (ReflectException exc) { }
+  }
+
+  /**
+   * Extracts an OME-XML metadata string from the given metadata object,
+   * by converting to an OME-XML metadata object if necessary.
+   */
+  public static String getOMEXML(MetadataRetrieve src) {
+    MetadataStore omexmlMeta;
+    if (isOMEXMLMetadata(src)) {
+      // the metadata is already an OME-XML metadata object
+      omexmlMeta = (MetadataStore) src;
+    }
+    else {
+      // populate a new OME-XML metadata object with metadata
+      // converted from the non-OME-XML metadata object
+      omexmlMeta = createOMEXMLMetadata();
+      MetadataTools.convertMetadata(src, omexmlMeta);
+    }
+    ReflectedUniverse r = new ReflectedUniverse();
+    r.setVar("omexmlMeta", omexmlMeta);
+    try {
+      return (String) r.exec("omexmlMeta.dumpXML()");
+    }
+    catch (ReflectException exc) { }
+    return null;
+  }
+
+  /**
+   * Attempts to validate the given OME-XML string using
+   * Java's XML validation facility. Requires Java 1.5+.
+   */
+  public static void validateOMEXML(String xml) {
+    XMLTools.validateXML(xml, "OME-XML");
+  }
+
+  // -- Utility methods -- metadata conversion --
+
+  /**
+   * Converts information from an OME-XML string (source)
+   * into a metadata store (destination).
+   */
+  public static void convertMetadata(String xml, MetadataStore dest) {
+    if (isOMEXMLMetadata(dest)) {
+      // metadata store is already an OME-XML metadata object;
+      // populate OME-XML string directly
+      ReflectedUniverse r = new ReflectedUniverse();
+      try {
+        r.setVar("xml", xml);
+        r.setVar("omexmlMeta", dest);
+        r.exec("omexmlMeta.createRoot(xml)");
+      }
+      catch (ReflectException exc) { }
+    }
+    else {
+      // metadata store is foreign; create an OME-XML
+      // metadata object and copy it into the destination
+      MetadataRetrieve src = (MetadataRetrieve) createOMEXMLMetadata(xml);
+      convertMetadata(src, dest);
+    }
+  }
+
+  /**
+   * Copies information from a metadata retrieval object
+   * (source) into a metadata store (destination).
+   */
+  public static void convertMetadata(MetadataRetrieve src, MetadataStore dest) {
+    Integer ii = null;
+    int globalPixCount = 0;
+
+    for (int i=0; i<src.getImageCount(); i++) {
+      ii = new Integer(i);
+      dest.setImage(src.getImageName(ii), src.getCreationDate(ii),
+        src.getDescription(ii), ii);
+
+      dest.setDimensions(src.getPixelSizeX(ii),
+        src.getPixelSizeY(ii), src.getPixelSizeZ(ii),
+        src.getPixelSizeC(ii), src.getPixelSizeT(ii), ii);
+
+      for (int j=0; j<src.getPixelsCount(ii); j++) {
+        Integer p = new Integer(j);
+        dest.setPixels(src.getSizeX(ii), src.getSizeY(ii),
+          src.getSizeZ(ii), src.getSizeC(ii),
+          src.getSizeT(ii),
+          new Integer(FormatTools.pixelTypeFromString(src.getPixelType(ii))),
+          src.getBigEndian(ii), src.getDimensionOrder(ii), ii, p);
+
+        dest.setDisplayOptions(src.getZoom(ii),
+          src.isRedChannelOn(ii), src.isGreenChannelOn(ii),
+          src.isBlueChannelOn(ii), src.isDisplayRGB(ii),
+          src.getColorMap(ii), src.getZStart(ii),
+          src.getZStop(ii), src.getTStart(ii),
+          src.getTStop(ii), ii, p, new Integer(0), new Integer(1),
+            new Integer(2), new Integer(0));
+
+        Integer globalPix = new Integer(globalPixCount);
+        for (int ch=0; ch<src.getChannelCount(globalPix); ch++) {
+          Integer c = new Integer(ch);
+          dest.setLogicalChannel(ch, src.getChannelName(globalPix, c),
+            null, null, null, null, null, null, null, null, null, null, null,
+            src.getPhotometricInterpretation(globalPix, c),
+            src.getMode(globalPix, c), null, null, null, null, null,
+            src.getEmWave(globalPix, c), src.getExWave(globalPix, c),
+            null, src.getChannelNDFilter(globalPix, c), globalPix);
+
+          dest.setChannelGlobalMinMax(ch, src.getGlobalMin(globalPix, c),
+            src.getGlobalMax(globalPix, c), globalPix);
+
+          dest.setDisplayChannel(c, src.getBlackLevel(globalPix, c),
+            src.getWhiteLevel(globalPix, c), src.getGamma(globalPix, c),
+            globalPix);
+        }
+
+        globalPixCount++;
+      }
+
+      dest.setImagingEnvironment(src.getTemperature(ii),
+        src.getAirPressure(ii), src.getHumidity(ii),
+        src.getCO2Percent(ii), ii);
+    }
+
+    for (int i=0; i<src.getExperimenterCount(); i++) {
+      ii = new Integer(i);
+      dest.setExperimenter(src.getFirstName(ii),
+        src.getLastName(ii), src.getEmail(ii),
+        src.getInstitution(ii), src.getDataDirectory(ii),
+        src.getGroup(ii), ii);
+    }
+
+    for (int i=0; i<src.getGroupCount(); i++) {
+      ii = new Integer(i);
+      dest.setGroup(src.getGroupName(ii), src.getLeader(ii),
+        src.getContact(ii), ii);
+    }
+
+    for (int i=0; i<src.getInstrumentCount(); i++) {
+      ii = new Integer(i);
+      dest.setInstrument(src.getManufacturer(ii),
+        src.getModel(ii), src.getSerialNumber(ii),
+        src.getType(ii), ii);
+    }
+
+    for (int i=0; i<src.getDisplayROICount(); i++) {
+      ii = new Integer(i);
+      dest.setDisplayROI(src.getX0(ii), src.getY0(ii),
+        src.getZ0(ii), src.getX1(ii), src.getY1(ii),
+        src.getZ1(ii), src.getT0(ii), src.getT1(ii),
+        src.getDisplayOptions(ii), ii);
+    }
+
+    for (int i=0; i<src.getStageLabelCount(); i++) {
+      ii = new Integer(i);
+      dest.setStageLabel(src.getStageName(ii), src.getStageX(ii),
+        src.getStageY(ii), src.getStageZ(ii), ii);
+    }
+
+    ii = null;
+
+    dest.setPlaneInfo(0, 0, 0, src.getTimestamp(ii, ii, ii, ii),
+      src.getExposureTime(ii, ii, ii, ii), ii);
+
+    dest.setLightSource(src.getLightManufacturer(ii),
+      src.getLightModel(ii), src.getLightSerial(ii), ii, ii);
+
+    dest.setLaser(src.getLaserType(ii), src.getLaserMedium(ii),
+      src.getLaserWavelength(ii), src.isFrequencyDoubled(ii),
+      src.isTunable(ii), src.getPulse(ii),
+      src.getPower(ii), ii, ii, ii, ii);
+
+    dest.setFilament(src.getFilamentType(ii),
+      src.getFilamentPower(ii), ii, ii);
+
+    dest.setArc(src.getArcType(ii), src.getArcPower(ii), ii, ii);
+
+    dest.setDetector(src.getDetectorManufacturer(ii),
+      src.getDetectorModel(ii), src.getDetectorSerial(ii),
+      src.getDetectorType(ii), src.getDetectorGain(ii),
+      src.getDetectorVoltage(ii),
+      src.getDetectorOffset(ii), ii, ii);
+
+    dest.setObjective(src.getObjectiveManufacturer(ii),
+      src.getObjectiveModel(ii), src.getObjectiveSerial(ii),
+      src.getLensNA(ii),
+      src.getObjectiveMagnification(ii), ii, ii);
+
+    dest.setExcitationFilter(src.getExcitationManufacturer(ii),
+      src.getExcitationModel(ii), src.getExcitationLotNumber(ii),
+      src.getExcitationType(ii), ii);
+
+    dest.setDichroic(src.getDichroicManufacturer(ii),
+      src.getDichroicModel(ii), src.getDichroicLotNumber(ii), ii);
+
+    dest.setEmissionFilter(src.getEmissionManufacturer(ii),
+      src.getEmissionModel(ii), src.getEmissionLotNumber(ii),
+      src.getEmissionType(ii), ii);
+
+    dest.setFilterSet(src.getFilterSetManufacturer(ii),
+      src.getFilterSetModel(ii),
+      src.getFilterSetLotNumber(ii), ii, ii);
+
+    dest.setOTF(src.getOTFSizeX(ii), src.getOTFSizeY(ii),
+      src.getOTFPixelType(ii), src.getOTFPath(ii),
+      src.getOTFOpticalAxisAverage(ii), ii, ii, ii, ii);
+  }
+
+  // -- Helper classes --
+
+  /** Used by testRead to handle XML validation errors. */
+  private static class ValidationHandler implements ErrorHandler {
+    private boolean ok = true;
+    public boolean ok() { return ok; }
+    public void error(SAXParseException e) {
+      LogTools.println("error: " + e.getMessage());
+      ok = false;
+    }
+    public void fatalError(SAXParseException e) {
+      LogTools.println("fatal error: " + e.getMessage());
+      ok = false;
+    }
+    public void warning(SAXParseException e) {
+      LogTools.println("warning: " + e.getMessage());
+      ok = false;
+    }
+  }
+
+}
diff --git a/loci/formats/MinMaxCalculator.java b/loci/formats/MinMaxCalculator.java
new file mode 100644
index 0000000..f380edd
--- /dev/null
+++ b/loci/formats/MinMaxCalculator.java
@@ -0,0 +1,425 @@
+//
+// MinMaxCalculator.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.*;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Logic to compute minimum and maximum values for each channel.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/MinMaxCalculator.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/MinMaxCalculator.java">SVN</a></dd></dl>
+ */
+public class MinMaxCalculator extends ReaderWrapper {
+
+  // -- Fields --
+
+  /** Min values for each channel. */
+  protected double[][] chanMin;
+
+  /** Max values for each channel. */
+  protected double[][] chanMax;
+
+  /** Min values for each plane. */
+  protected double[][] planeMin;
+
+  /** Max values for each plane. */
+  protected double[][] planeMax;
+
+  /** Number of planes for which min/max computations have been completed. */
+  protected int[] minMaxDone;
+
+  // -- Constructors --
+
+  /** Constructs a MinMaxCalculator around a new image reader. */
+  public MinMaxCalculator() { super(); }
+
+  /** Constructs a MinMaxCalculator with the given reader. */
+  public MinMaxCalculator(IFormatReader r) { super(r); }
+
+  // -- MinMaxCalculator API methods --
+
+  /**
+   * Retrieves a specified channel's global minimum.
+   * Returns null if some of the image planes have not been read.
+   */
+  public Double getChannelGlobalMinimum(int theC)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    if (theC < 0 || theC >= getSizeC()) {
+      throw new FormatException("Invalid channel index: " + theC);
+    }
+
+    int series = getSeries();
+
+    // check that all planes have been reade
+    if (minMaxDone == null || minMaxDone[series] < getImageCount()) {
+      return null;
+    }
+    return new Double(chanMin[series][theC]);
+  }
+
+  /**
+   * Retrieves a specified channel's global maximum.
+   * Returns null if some of the image planes have not been read.
+   */
+  public Double getChannelGlobalMaximum(int theC)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    if (theC < 0 || theC >= getSizeC()) {
+      throw new FormatException("Invalid channel index: " + theC);
+    }
+
+    int series = getSeries();
+
+    // check that all planes have been reade
+    if (minMaxDone == null || minMaxDone[series] < getImageCount()) {
+      return null;
+    }
+    return new Double(chanMax[series][theC]);
+  }
+
+  /**
+   * Retrieves the specified channel's minimum based on the images that have
+   * been read.  Returns null if no image planes have been read yet.
+   */
+  public Double getChannelKnownMinimum(int theC)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return chanMin == null ? null : new Double(chanMin[getSeries()][theC]);
+  }
+
+  /**
+   * Retrieves the specified channel's maximum based on the images that
+   * have been read.  Returns null if no image planes have been read yet.
+   */
+  public Double getChannelKnownMaximum(int theC)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return chanMax == null ? null : new Double(chanMax[getSeries()][theC]);
+  }
+
+  /**
+   * Retrieves the minimum pixel value for the specified plane.  If each
+   * image plane contains more than one channel (i.e.,
+   * {@link #getRGBChannelCount(String)}), returns the maximum value for each
+   * embedded channel.  Returns null if the plane has not already been read.
+   */
+  public Double[] getPlaneMinimum(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    if (planeMin == null) return null;
+
+    int numRGB = getRGBChannelCount();
+    int pBase = no * numRGB;
+    int series = getSeries();
+    if (planeMin[series][pBase] != planeMin[series][pBase]) {
+      return null;
+    }
+
+    Double[] min = new Double[numRGB];
+    for (int c=0; c<numRGB; c++) {
+      min[c] = new Double(planeMin[series][pBase + c]);
+    }
+    return min;
+  }
+
+  /**
+   * Retrieves the maximum pixel value for the specified plane.  If each
+   * image plane contains more than one channel (i.e.,
+   * {@link #getRGBChannelCount(String)}), returns the maximum value for each
+   * embedded channel.  Returns null if the plane has not already been read.
+   */
+  public Double[] getPlaneMaximum(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    if (planeMax == null) return null;
+
+    int numRGB = getRGBChannelCount();
+    int pBase = no * numRGB;
+    int series = getSeries();
+    if (planeMax[series][pBase] != planeMax[series][pBase]) {
+      return null;
+    }
+
+    Double[] max = new Double[numRGB];
+    for (int c=0; c<numRGB; c++) {
+      max[c] = new Double(planeMax[series][pBase + c]);
+    }
+    return max;
+  }
+
+  /**
+   * Returns true if the values returned by
+   * getChannelGlobalMinimum/Maximum can be trusted.
+   */
+  public boolean isMinMaxPopulated() throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    return minMaxDone != null && minMaxDone[getSeries()] == getImageCount();
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    BufferedImage b = super.openImage(no);
+    updateMinMax(b, no);
+    return b;
+  }
+
+  /* @see IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    byte[] b = super.openBytes(no);
+    updateMinMax(b, no);
+    return b;
+  }
+
+  /* @see IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(getCurrentFile(), true, 2);
+    super.openBytes(no, buf);
+    updateMinMax(buf, no);
+    return buf;
+  }
+
+  /* @see IFormatReader#close() */
+  public void close() throws IOException {
+    reader.close();
+    chanMin = null;
+    chanMax = null;
+    planeMin = null;
+    planeMax = null;
+    minMaxDone = null;
+  }
+
+  // -- Helper methods --
+
+  /** Updates min/max values based on the given BufferedImage. */
+  protected void updateMinMax(BufferedImage b, int ndx)
+    throws FormatException, IOException
+  {
+    if (b == null) return;
+    initMinMax();
+
+    int numRGB = getRGBChannelCount();
+    int series = getSeries();
+
+    // check whether min/max values have already been computed for this plane
+    if (planeMin[series][ndx * numRGB] ==
+      planeMin[series][ndx * numRGB])
+    {
+      return;
+    }
+
+    int[] coords = getZCTCoords(ndx);
+    int cBase = coords[1] * numRGB;
+    int pBase = ndx * numRGB;
+    for (int c=0; c<numRGB; c++) {
+      planeMin[series][pBase + c] = Double.POSITIVE_INFINITY;
+      planeMax[series][pBase + c] = Double.NEGATIVE_INFINITY;
+    }
+
+    WritableRaster pixels = b.getRaster();
+    for (int x=0; x<b.getWidth(); x++) {
+      for (int y=0; y<b.getHeight(); y++) {
+        for (int c=0; c<numRGB; c++) {
+          double v = pixels.getSampleDouble(x, y, c);
+          if (v > chanMax[series][cBase + c]) {
+            chanMax[series][cBase + c] = v;
+          }
+          if (v < chanMin[series][cBase + c]) {
+            chanMin[series][cBase + c] = v;
+          }
+          if (v > planeMax[series][pBase + c]) {
+            planeMax[series][pBase + c] = v;
+          }
+          if (v < planeMin[series][pBase + c]) {
+            planeMin[series][pBase + c] = v;
+          }
+        }
+      }
+    }
+
+    minMaxDone[series]++;
+
+    if (minMaxDone[series] == getImageCount()) {
+      MetadataStore store = getMetadataStore();
+      for (int c=0; c<getSizeC(); c++) {
+        store.setChannelGlobalMinMax(c, new Double(chanMin[series][c]),
+          new Double(chanMax[series][c]), new Integer(getSeries()));
+      }
+    }
+  }
+
+  /** Updates min/max values based on the given byte array. */
+  protected void updateMinMax(byte[] b, int ndx)
+    throws FormatException, IOException
+  {
+    if (b == null) return;
+    initMinMax();
+
+    int numRGB = getRGBChannelCount();
+    int series = getSeries();
+    // check whether min/max values have already been computed for this plane
+    if (planeMin[series][ndx * numRGB] ==
+      planeMin[series][ndx * numRGB])
+    {
+      return;
+    }
+
+    boolean little = isLittleEndian();
+    int bytes = FormatTools.getBytesPerPixel(getPixelType());
+    int pixels = getSizeX() * getSizeY();
+    boolean interleaved = isInterleaved();
+
+    int[] coords = getZCTCoords(ndx);
+    int cBase = coords[1] * numRGB;
+    int pBase = ndx * numRGB;
+    for (int c=0; c<numRGB; c++) {
+      planeMin[series][pBase + c] = Double.POSITIVE_INFINITY;
+      planeMax[series][pBase + c] = Double.NEGATIVE_INFINITY;
+    }
+
+    boolean fp = getPixelType() == FormatTools.FLOAT ||
+      getPixelType() == FormatTools.DOUBLE;
+
+    for (int i=0; i<pixels; i++) {
+      for (int c=0; c<numRGB; c++) {
+        int idx = bytes * (interleaved ? i * numRGB + c : c * pixels + i);
+        long bits = DataTools.bytesToLong(b, idx, bytes, little);
+        double v = fp ? Double.longBitsToDouble(bits) : (double) bits;
+        if (v > chanMax[series][cBase + c]) {
+          chanMax[series][cBase + c] = v;
+        }
+        if (v < chanMin[series][cBase + c]) {
+          chanMin[series][cBase + c] = v;
+        }
+        if (v > planeMax[series][ndx]) {
+          planeMax[series][ndx] = v;
+        }
+        if (v < planeMin[series][pBase + c]) {
+          planeMin[series][pBase + c] = v;
+        }
+      }
+    }
+
+    minMaxDone[series]++;
+
+    if (minMaxDone[getSeries()] == getImageCount()) {
+      MetadataStore store = getMetadataStore();
+      for (int c=0; c<getSizeC(); c++) {
+        store.setChannelGlobalMinMax(c, new Double(chanMin[getSeries()][c]),
+          new Double(chanMax[getSeries()][c]), new Integer(getSeries()));
+      }
+    }
+  }
+
+  /** Ensures internal min/max variables are initialized properly. */
+  protected void initMinMax() throws FormatException, IOException {
+    int seriesCount = getSeriesCount();
+    int oldSeries = getSeries();
+
+    if (chanMin == null) {
+      chanMin = new double[seriesCount][];
+      for (int i=0; i<seriesCount; i++) {
+        setSeries(i);
+        chanMin[i] = new double[getSizeC()];
+        Arrays.fill(chanMin[i], Double.POSITIVE_INFINITY);
+      }
+      setSeries(oldSeries);
+    }
+    if (chanMax == null) {
+      chanMax = new double[seriesCount][];
+      for (int i=0; i<seriesCount; i++) {
+        setSeries(i);
+        chanMax[i] = new double[getSizeC()];
+        Arrays.fill(chanMax[i], Double.NEGATIVE_INFINITY);
+      }
+      setSeries(oldSeries);
+    }
+    if (planeMin == null) {
+      planeMin = new double[seriesCount][];
+      for (int i=0; i<seriesCount; i++) {
+        setSeries(i);
+        int numRGB = getRGBChannelCount();
+        planeMin[i] = new double[getImageCount() * numRGB];
+        Arrays.fill(planeMin[i], Double.NaN);
+      }
+      setSeries(oldSeries);
+    }
+    if (planeMax == null) {
+      planeMax = new double[seriesCount][];
+      for (int i=0; i<seriesCount; i++) {
+        setSeries(i);
+        int numRGB = getRGBChannelCount();
+        planeMax[i] = new double[getImageCount() * numRGB];
+        Arrays.fill(planeMax[i], Double.NaN);
+      }
+      setSeries(oldSeries);
+    }
+    if (minMaxDone == null) minMaxDone = new int[seriesCount];
+  }
+
+  // -- Deprecated IFormatReader API methods --
+
+  /** @deprecated Replaced by {@link #openImage(int)} */
+  public BufferedImage openImage(String id, int no)
+    throws FormatException, IOException
+  {
+    setId(id);
+    BufferedImage b = super.openImage(no);
+    updateMinMax(b, no);
+    return b;
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int)} */
+  public byte[] openBytes(String id, int no) throws FormatException, IOException
+  {
+    setId(id);
+    byte[] b = super.openBytes(no);
+    updateMinMax(b, no);
+    return b;
+  }
+
+  /** @deprecated Replaced by {@link #openBytes(int, byte[])} */
+  public byte[] openBytes(String id, int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    setId(id);
+    super.openBytes(no, buf);
+    updateMinMax(buf, no);
+    return buf;
+  }
+
+}
diff --git a/loci/formats/NumberFilter.java b/loci/formats/NumberFilter.java
new file mode 100644
index 0000000..8d16af1
--- /dev/null
+++ b/loci/formats/NumberFilter.java
@@ -0,0 +1,82 @@
+//
+// NumberFilter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.math.BigInteger;
+
+/**
+ * NumberFilter is a helper filter for FilePattern.findPattern().
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/NumberFilter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/NumberFilter.java">SVN</a></dd></dl>
+ */
+public class NumberFilter implements FileFilter {
+
+  // -- Fields --
+
+  /** String appearing before the numerical block. */
+  private String pre;
+
+  /** String appearing after the numerical block. */
+  private String post;
+
+  // -- Constructor --
+
+  /**
+   * Creates a filter for files containing a numerical block,
+   * sandwiched between the given strings.
+   */
+  public NumberFilter(String pre, String post) {
+    this.pre = pre;
+    this.post = post;
+  }
+
+  // -- NumberFilter API methods --
+
+  /** Gets numbers filling the asterisk positions. */
+  public BigInteger getNumber(String name) {
+    if (!name.startsWith(pre) || !name.endsWith(post)) return null;
+    int ndx = pre.length();
+    int end = name.length() - post.length();
+    try { return new BigInteger(name.substring(ndx, end)); }
+    catch (NumberFormatException exc) { return null; }
+  }
+
+  /** Tests if a specified file should be included in a file list. */
+  public boolean accept(String name) {
+    return getNumber(name) != null;
+  }
+
+  // -- FileFilter API methods --
+
+  /** Tests if a specified file should be included in a file list. */
+  public boolean accept(File pathname) {
+    return accept(pathname.getName());
+  }
+
+}
diff --git a/loci/formats/RABytes.java b/loci/formats/RABytes.java
new file mode 100644
index 0000000..0874406
--- /dev/null
+++ b/loci/formats/RABytes.java
@@ -0,0 +1,326 @@
+//
+// RABytes.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+
+/**
+ * A wrapper for a byte array that implements the IRandomAccess interface.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/RABytes.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/RABytes.java">SVN</a></dd></dl>
+ *
+ * @see IRandomAccess
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class RABytes implements IRandomAccess {
+
+  // -- Fields --
+
+  /** The byte array backing this RABytes. */
+  protected byte[] array;
+
+  /** The file pointer. */
+  protected int fp;
+
+  // -- Constructors --
+
+  /**
+   * Creates a random access byte stream to read from, and
+   * write to, the bytes specified by the byte[] argument.
+   */
+  public RABytes(byte[] bytes) {
+    array = bytes;
+    fp = 0;
+  }
+
+  // -- RABytes API methods --
+
+  /** Gets the byte array backing this RAFile. */
+  public byte[] getBytes() { return array; }
+
+  // -- IRandomAccess API methods --
+
+  /* @see IRandomAccess.close() */
+  public void close() { }
+
+  /* @see IRandomAccess.getFilePointer() */
+  public long getFilePointer() {
+    return fp;
+  }
+
+  /* @see IRandomAccess.length() */
+  public long length() {
+    return array.length;
+  }
+
+  /* @see IRandomAccess.read() */
+  public int read() {
+    return fp < array.length ? array[fp++] : 0;
+  }
+
+  /* @see IRandomAccess.read(byte[]) */
+  public int read(byte[] b) throws IOException {
+    return read(b, 0, b.length);
+  }
+
+  /* @see IRandomAccess.read(byte[], int, int) */
+  public int read(byte[] b, int off, int len) throws IOException {
+    if (fp + len > array.length) len = array.length - fp;
+    System.arraycopy(array, fp, b, off, len);
+    fp += len;
+    return len;
+  }
+
+  /* @see IRandomAccess.seek(long) */
+  public void seek(long pos) throws IOException {
+    if (pos < 0) throw new IOException("pos < 0");
+    if (pos > Integer.MAX_VALUE) throw new IOException("pos is too large");
+    fp = (int) pos;
+  }
+
+  /* @see IRandomAccess.setLength(long) */
+  public void setLength(long newLength) throws IOException {
+    if (newLength > Integer.MAX_VALUE) {
+      throw new IOException("newLength is too large");
+    }
+    int len = (int) newLength;
+    byte[] bytes = new byte[len];
+    System.arraycopy(array, 0, bytes, 0,
+      array.length < len ? array.length : len);
+    array = bytes;
+  }
+
+  // -- DataInput API methods --
+
+  /* @see java.io.DataInput.readBoolean() */
+  public boolean readBoolean() throws IOException {
+    return readByte() != 0;
+  }
+
+  /* @see java.io.DataInput.readByte() */
+  public byte readByte() throws IOException {
+    if (fp + 1 > array.length) throw new EOFException();
+    return array[fp++];
+  }
+
+  /* @see java.io.DataInput.readChar() */
+  public char readChar() throws IOException {
+    if (fp + 2 > array.length) throw new EOFException();
+    char c = (char) DataTools.bytesToShort(array, fp, false);
+    fp += 2;
+    return c;
+  }
+
+  /* @see java.io.DataInput.readDouble() */
+  public double readDouble() throws IOException {
+    if (fp + 8 > array.length) throw new EOFException();
+    double d = Double.longBitsToDouble(DataTools.bytesToLong(array, fp, false));
+    fp += 8;
+    return d;
+  }
+
+  /* @see java.io.DataInput.readFloat() */
+  public float readFloat() throws IOException {
+    if (fp + 4 > array.length) throw new EOFException();
+    float f = Float.intBitsToFloat(DataTools.bytesToInt(array, fp, false));
+    fp += 4;
+    return f;
+  }
+
+  /* @see java.io.DataInput.readFully(byte[]) */
+  public void readFully(byte[] b) throws IOException {
+    readFully(b, 0, b.length);
+  }
+
+  /* @see java.io.DataInput.readFully(byte[], int, int) */
+  public void readFully(byte[] b, int off, int len) throws IOException {
+    if (fp + len > array.length) throw new EOFException();
+    System.arraycopy(array, fp, b, off, len);
+    fp += len;
+  }
+
+  /* @see java.io.DataInput.readInt() */
+  public int readInt() throws IOException {
+    if (fp + 4 > array.length) throw new EOFException();
+    int i = DataTools.bytesToInt(array, fp, false);
+    fp += 4;
+    return i;
+  }
+
+  /* @see java.io.DataInput.readLine() */
+  public String readLine() throws IOException {
+    throw new IOException("Unimplemented");
+  }
+
+  /* @see java.io.DataInput.readLong() */
+  public long readLong() throws IOException {
+    if (fp + 8 > array.length) throw new EOFException();
+    long l = DataTools.bytesToLong(array, fp, false);
+    fp += 8;
+    return l;
+  }
+
+  /* @see java.io.DataInput.readShort() */
+  public short readShort() throws IOException {
+    if (fp + 2 > array.length) throw new EOFException();
+    short s = DataTools.bytesToShort(array, fp, false);
+    fp += 2;
+    return s;
+  }
+
+  /* @see java.io.DataInput.readUnsignedByte() */
+  public int readUnsignedByte() throws IOException {
+    if (fp + 1 > array.length) throw new EOFException();
+    return DataTools.bytesToInt(array, fp++, 1, false);
+  }
+
+  /* @see java.io.DataInput.readUnsignedShort() */
+  public int readUnsignedShort() throws IOException {
+    if (fp + 2 > array.length) throw new EOFException();
+    int i = DataTools.bytesToInt(array, fp, 2, false);
+    fp += 2;
+    return i;
+  }
+
+  /* @see java.io.DataInput.readUTF() */
+  public String readUTF() throws IOException {
+    throw new IOException("Unimplemented");
+  }
+
+  /* @see java.io.DataInput.skipBytes(int) */
+  public int skipBytes(int n) {
+    if (n < 0) n = 0;
+    if (fp + n > array.length) n = array.length - fp;
+    fp += n;
+    return n;
+  }
+
+  // -- DataOutput API metthods --
+
+  /* @see java.io.DataOutput.write(byte[]) */
+  public void write(byte[] b) throws IOException {
+    write(b, 0, b.length);
+  }
+
+  /* @see java.io.DataOutput.write(byte[], int, int) */
+  public void write(byte[] b, int off, int len) throws IOException {
+    if (fp + len > array.length) setLength(fp + len);
+    System.arraycopy(b, off, array, fp, len);
+    fp += b.length;
+  }
+
+  /* @see java.io.DataOutput.write(int b) */
+  public void write(int b) throws IOException {
+    if (fp + 1 > array.length) setLength(fp + 1);
+    array[fp++] = (byte) b;
+  }
+
+  /* @see java.io.DataOutput.writeBoolean(boolean) */
+  public void writeBoolean(boolean v) throws IOException {
+    write(v ? 1 : 0);
+  }
+
+  /* @see java.io.DataOutput.writeByte(int) */
+  public void writeByte(int v) throws IOException {
+    write(v);
+  }
+
+  /* @see java.io.DataOutput.writeBytes(String) */
+  public void writeBytes(String s) throws IOException {
+    char[] c = s.toCharArray();
+    byte[] b = new byte[c.length];
+    for (int i=0; i<c.length; i++) b[i] = (byte) c[i];
+    write(b);
+  }
+
+  /* @see java.io.DataOutput.writeChar(int) */
+  public void writeChar(int v) throws IOException {
+    if (fp + 2 > array.length) setLength(fp + 2);
+    array[fp++] = (byte) (0xff & (v >> 8));
+    array[fp++] = (byte) (0xff & v);
+  }
+
+  /* @see java.io.DataOutput.writeChars(String) */
+  public void writeChars(String s) throws IOException {
+    int len = 2 * s.length();
+    if (fp + len > array.length) setLength(fp + len);
+    char[] c = s.toCharArray();
+    for (int i=0; i<c.length; i++) {
+      char v = c[i];
+      array[fp++] = (byte) (0xff & (v >> 8));
+      array[fp++] = (byte) (0xff & v);
+    }
+  }
+
+  /* @see java.io.DataOutput.writeDouble(double) */
+  public void writeDouble(double v) throws IOException {
+    writeLong(Double.doubleToLongBits(v));
+  }
+
+  /* @see java.io.DataOutput.writeFloat(float) */
+  public void writeFloat(float v) throws IOException {
+    writeInt(Float.floatToIntBits(v));
+  }
+
+  /* @see java.io.DataOutput.writeInt(int) */
+  public void writeInt(int v) throws IOException {
+    if (fp + 4 > array.length) setLength(fp + 4);
+    array[fp++] = (byte) (0xff & (v >> 24));
+    array[fp++] = (byte) (0xff & (v >> 16));
+    array[fp++] = (byte) (0xff & (v >> 8));
+    array[fp++] = (byte) (0xff & v);
+  }
+
+  /* @see java.io.DataOutput.writeLong(long) */
+  public void writeLong(long v) throws IOException {
+    if (fp + 8 > array.length) setLength(fp + 8);
+    array[fp++] = (byte) (0xff & (v >> 56));
+    array[fp++] = (byte) (0xff & (v >> 48));
+    array[fp++] = (byte) (0xff & (v >> 40));
+    array[fp++] = (byte) (0xff & (v >> 32));
+    array[fp++] = (byte) (0xff & (v >> 24));
+    array[fp++] = (byte) (0xff & (v >> 16));
+    array[fp++] = (byte) (0xff & (v >> 8));
+    array[fp++] = (byte) (0xff & v);
+  }
+
+  /* @see java.io.DataOutput.writeShort(int) */
+  public void writeShort(int v) throws IOException {
+    if (fp + 2 > array.length) setLength(fp + 2);
+    array[fp++] = (byte) (0xff & (v >> 24));
+    array[fp++] = (byte) (0xff & (v >> 16));
+    array[fp++] = (byte) (0xff & (v >> 8));
+    array[fp++] = (byte) (0xff & v);
+  }
+
+  /* @see java.io.DataOutput.writeUTF(String)  */
+  public void writeUTF(String str) throws IOException {
+    throw new IOException("Unimplemented");
+  }
+
+}
diff --git a/loci/formats/RAFile.java b/loci/formats/RAFile.java
new file mode 100644
index 0000000..63b45ba
--- /dev/null
+++ b/loci/formats/RAFile.java
@@ -0,0 +1,262 @@
+//
+// RAFile.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+
+/**
+ * A wrapper for RandomAccessFile that implements the IRandomAccess interface.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/RAFile.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/RAFile.java">SVN</a></dd></dl>
+ *
+ * @see IRandomAccess
+ * @see java.io.RandomAccessFile
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class RAFile implements IRandomAccess {
+
+  // -- Fields --
+
+  /** The random access file object backing this RAFile. */
+  protected RandomAccessFile raf;
+
+  // -- Constructors --
+
+  /**
+   * Creates a random access file stream to read from, and
+   * optionally to write to, the file specified by the File argument.
+   */
+  public RAFile(File file, String mode) throws FileNotFoundException {
+    raf = new RandomAccessFile(file, mode);
+  }
+
+  /**
+   * Creates a random access file stream to read from, and
+   * optionally to write to, a file with the specified name.
+   */
+  public RAFile(String name, String mode) throws FileNotFoundException {
+    raf = new RandomAccessFile(name, mode);
+  }
+
+  // -- RAFile API methods --
+
+  /** Gets the random access file object backing this RAFile. */
+  public RandomAccessFile getRandomAccessFile() { return raf; }
+
+  // -- IRandomAccess API methods --
+
+  /* @see IRandomAccess.close() */
+  public void close() throws IOException {
+    raf.close();
+  }
+
+  /* @see IRandomAccess.getFilePointer() */
+  public long getFilePointer() throws IOException {
+    return raf.getFilePointer();
+  }
+
+  /* @see IRandomAccess.length() */
+  public long length() throws IOException {
+    return raf.length();
+  }
+
+  /* @see IRandomAccess.read() */
+  public int read() throws IOException {
+    return raf.read();
+  }
+
+  /* @see IRandomAccess.read(byte[]) */
+  public int read(byte[] b) throws IOException {
+    return raf.read(b);
+  }
+
+  /* @see IRandomAccess.read(byte[], int, int) */
+  public int read(byte[] b, int off, int len) throws IOException {
+    return raf.read(b, off, len);
+  }
+
+  /* @see IRandomAccess.seek(long) */
+  public void seek(long pos) throws IOException {
+    raf.seek(pos);
+  }
+
+  /* @see IRandomAccess.setLength(long) */
+  public void setLength(long newLength) throws IOException {
+    raf.setLength(newLength);
+  }
+
+  // -- DataInput API methods --
+
+  /* @see java.io.DataInput.readBoolean() */
+  public boolean readBoolean() throws IOException {
+    return raf.readBoolean();
+  }
+
+  /* @see java.io.DataInput.readByte() */
+  public byte readByte() throws IOException {
+    return raf.readByte();
+  }
+
+  /* @see java.io.DataInput.readChar() */
+  public char readChar() throws IOException {
+    return raf.readChar();
+  }
+
+  /* @see java.io.DataInput.readDouble() */
+  public double readDouble() throws IOException {
+    return raf.readDouble();
+  }
+
+  /* @see java.io.DataInput.readFloat() */
+  public float readFloat() throws IOException {
+    return raf.readFloat();
+  }
+
+  /* @see java.io.DataInput.readFully(byte[]) */
+  public void readFully(byte[] b) throws IOException {
+    raf.readFully(b);
+  }
+
+  /* @see java.io.DataInput.readFully(byte[], int, int) */
+  public void readFully(byte[] b, int off, int len) throws IOException {
+    raf.readFully(b, off, len);
+  }
+
+  /* @see java.io.DataInput.readInt() */
+  public int readInt() throws IOException {
+    return raf.readInt();
+  }
+
+  /* @see java.io.DataInput.readLine() */
+  public String readLine() throws IOException {
+    return raf.readLine();
+  }
+
+  /* @see java.io.DataInput.readLong() */
+  public long readLong() throws IOException {
+    return raf.readLong();
+  }
+
+  /* @see java.io.DataInput.readShort() */
+  public short readShort() throws IOException {
+    return raf.readShort();
+  }
+
+  /* @see java.io.DataInput.readUnsignedByte() */
+  public int readUnsignedByte() throws IOException {
+    return raf.readUnsignedByte();
+  }
+
+  /* @see java.io.DataInput.readUnsignedShort() */
+  public int readUnsignedShort() throws IOException {
+    return raf.readUnsignedShort();
+  }
+
+  /* @see java.io.DataInput.readUTF() */
+  public String readUTF() throws IOException {
+    return raf.readUTF();
+  }
+
+  /* @see java.io.DataInput.skipBytes(int) */
+  public int skipBytes(int n) throws IOException {
+    return raf.skipBytes(n);
+  }
+
+  // -- DataOutput API metthods --
+
+  /* @see java.io.DataOutput.write(byte[]) */
+  public void write(byte[] b) throws IOException {
+    raf.write(b);
+  }
+
+  /* @see java.io.DataOutput.write(byte[], int, int) */
+  public void write(byte[] b, int off, int len) throws IOException {
+    raf.write(b, off, len);
+  }
+
+  /* @see java.io.DataOutput.write(int b) */
+  public void write(int b) throws IOException {
+    raf.write(b);
+  }
+
+  /* @see java.io.DataOutput.writeBoolean(boolean) */
+  public void writeBoolean(boolean v) throws IOException {
+    raf.writeBoolean(v);
+  }
+
+  /* @see java.io.DataOutput.writeByte(int) */
+  public void writeByte(int v) throws IOException {
+    raf.writeByte(v);
+  }
+
+  /* @see java.io.DataOutput.writeBytes(String) */
+  public void writeBytes(String s) throws IOException {
+    raf.writeBytes(s);
+  }
+
+  /* @see java.io.DataOutput.writeChar(int) */
+  public void writeChar(int v) throws IOException {
+    raf.writeChar(v);
+  }
+
+  /* @see java.io.DataOutput.writeChars(String) */
+  public void writeChars(String s) throws IOException {
+    raf.writeChars(s);
+  }
+
+  /* @see java.io.DataOutput.writeDouble(double) */
+  public void writeDouble(double v) throws IOException {
+    raf.writeDouble(v);
+  }
+
+  /* @see java.io.DataOutput.writeFloat(float) */
+  public void writeFloat(float v) throws IOException {
+    raf.writeFloat(v);
+  }
+
+  /* @see java.io.DataOutput.writeInt(int) */
+  public void writeInt(int v) throws IOException {
+    raf.writeInt(v);
+  }
+
+  /* @see java.io.DataOutput.writeLong(long) */
+  public void writeLong(long v) throws IOException {
+    raf.writeLong(v);
+  }
+
+  /* @see java.io.DataOutput.writeShort(int) */
+  public void writeShort(int v) throws IOException {
+    raf.writeShort(v);
+  }
+
+  /* @see java.io.DataOutput.writeUTF(String)  */
+  public void writeUTF(String str) throws IOException {
+    raf.writeUTF(str);
+  }
+
+}
diff --git a/loci/formats/RAUrl.java b/loci/formats/RAUrl.java
new file mode 100644
index 0000000..f28ae57
--- /dev/null
+++ b/loci/formats/RAUrl.java
@@ -0,0 +1,343 @@
+//
+// RAUrl.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * Provides random access to data over HTTP using the IRandomAccess interface.
+ * This is slow, but functional.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/RAUrl.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/RAUrl.java">SVN</a></dd></dl>
+ *
+ * @see IRandomAccess
+ * @see java.net.HttpURLConnection
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class RAUrl implements IRandomAccess {
+
+  // -- Fields --
+
+  /** URL of open socket */
+  private String url;
+
+  /** Socket underlying this stream */
+  private HttpURLConnection conn;
+
+  /** Input stream */
+  private DataInputStream is;
+
+  /** Output stream */
+  private DataOutputStream os;
+
+  /** Stream pointer */
+  private long fp;
+
+  /** Number of bytes in the stream */
+  private long length;
+
+  /** Reset marker */
+  private long mark;
+
+  // -- Constructors --
+
+  public RAUrl(String url, String mode) throws IOException {
+    if (!url.startsWith("http")) url = "http://" + url;
+    conn = (HttpURLConnection) (new URL(url)).openConnection();
+    if (mode.equals("r")) {
+      is = new DataInputStream(new BufferedInputStream(
+        conn.getInputStream(), 65536));
+    }
+    else if (mode.equals("w")) {
+      conn.setDoOutput(true);
+      os = new DataOutputStream(conn.getOutputStream());
+    }
+    fp = 0;
+    length = conn.getContentLength();
+    if (is != null) is.mark((int) length);
+    this.url = url;
+  }
+
+  // -- IRandomAccess API methods --
+
+  /* @see IRandomAccess#close() */
+  public void close() throws IOException {
+    if (is != null) is.close();
+    if (os != null) os.close();
+    conn.disconnect();
+  }
+
+  /* @see IRandomAccess#getFilePointer() */
+  public long getFilePointer() throws IOException {
+    return fp;
+  }
+
+  /* @see IRandomAccess#length() */
+  public long length() throws IOException { return length; }
+
+  /* @see IRandomAccess#read() */
+  public int read() throws IOException {
+    int value = is.read();
+    while (value == -1 && fp < length()) value = is.read();
+    if (value != -1) fp++;
+    markManager();
+    return value;
+  }
+
+  /* @see IRandomAccess#read(byte[]) */
+  public int read(byte[] b) throws IOException {
+    return read(b, 0, b.length);
+  }
+
+  /* @see IRandomAccess#read(byte[], int, int) */
+  public int read(byte[] b, int off, int len) throws IOException {
+    int read = is.read(b, off, len);
+    if (read != -1) fp += read;
+    if (read == -1) read = 0;
+    markManager();
+    while (read < len && fp < length()) {
+      int oldRead = read;
+      read += read(b, off + read, len - read);
+      if (read < oldRead) read = oldRead;
+    }
+    return read == 0 ? -1 : read;
+  }
+
+  /* @see IRandomAccess#seek(long) */
+  public void seek(long pos) throws IOException {
+    if (pos >= fp) {
+      skipBytes((int) (pos - fp));
+      return;
+    }
+    else if (pos >= mark) {
+      try {
+        is.reset();
+        fp = mark;
+        skipBytes((int) (pos - fp));
+        return;
+      }
+      catch (IOException e) { }
+    }
+
+    close();
+    conn = (HttpURLConnection) (new URL(url)).openConnection();
+    conn.setDoOutput(true);
+    if (is != null) {
+      is = new DataInputStream(new BufferedInputStream(
+        conn.getInputStream(), 65536));
+      is.mark((int) length());
+      mark = 0;
+    }
+    if (os != null) os = new DataOutputStream(conn.getOutputStream());
+    this.url = url;
+    fp = 0;
+    skipBytes((int) pos);
+  }
+
+  /* @see IRandomAccess#setLength(long) */
+  public void setLength(long newLength) throws IOException {
+    length = newLength;
+  }
+
+  // -- DataInput API methods --
+
+  /* @see java.io.DataInput#readBoolean() */
+  public boolean readBoolean() throws IOException {
+    fp++;
+    return is.readBoolean();
+  }
+
+  /* @see java.io.DataInput#readByte() */
+  public byte readByte() throws IOException {
+    fp++;
+    return is.readByte();
+  }
+
+  /* @see java.io.DataInput#readChar() */
+  public char readChar() throws IOException {
+    fp++;
+    return is.readChar();
+  }
+
+  /* @see java.io.DataInput#readDouble() */
+  public double readDouble() throws IOException {
+    fp += 8;
+    return is.readDouble();
+  }
+
+  /* @see java.io.DataInput#readFloat() */
+  public float readFloat() throws IOException {
+    fp += 4;
+    return is.readFloat();
+  }
+
+  /* @see java.io.DataInput#readFully(byte[]) */
+  public void readFully(byte[] b) throws IOException {
+    fp += b.length;
+    is.readFully(b);
+  }
+
+  /* @see java.io.DataInput#readFully(byte[], int, int) */
+  public void readFully(byte[] b, int off, int len) throws IOException {
+    fp += len;
+    is.readFully(b, off, len);
+  }
+
+  /* @see java.io.DataInput#readInt() */
+  public int readInt() throws IOException {
+    fp += 4;
+    return is.readInt();
+  }
+
+  /* @see java.io.DataInput#readLine() */
+  public String readLine() throws IOException {
+    throw new IOException("Unimplemented");
+  }
+
+  /* @see java.io.DataInput#readLong() */
+  public long readLong() throws IOException {
+    fp += 8;
+    return is.readLong();
+  }
+
+  /* @see java.io.DataInput#readShort() */
+  public short readShort() throws IOException {
+    fp += 2;
+    return is.readShort();
+  }
+
+  /* @see java.io.DataInput#readUnsignedByte() */
+  public int readUnsignedByte() throws IOException {
+    fp++;
+    return is.readUnsignedByte();
+  }
+
+  /* @see java.io.DataInput#readUnsignedShort() */
+  public int readUnsignedShort() throws IOException {
+    fp += 2;
+    return is.readUnsignedShort();
+  }
+
+  /* @see java.io.DataInput#readUTF() */
+  public String readUTF() throws IOException {
+    fp += 2;
+    return is.readUTF();
+  }
+
+  /* @see java.io.DataInput#skipBytes(int) */
+  public int skipBytes(int n) throws IOException {
+    int skipped = 0;
+    for (int i=0; i<n; i++) {
+      if (read() != -1) skipped++;
+      markManager();
+    }
+    return skipped;
+  }
+
+  // -- DataOutput API methods --
+
+  /* @see java.io.DataOutput#write(byte[]) */
+  public void write(byte[] b) throws IOException {
+    os.write(b);
+  }
+
+  /* @see java.io.DataOutput#write(byte[], int, int) */
+  public void write(byte[] b, int off, int len) throws IOException {
+    os.write(b, off, len);
+  }
+
+  /* @see java.io.DataOutput#write(int b) */
+  public void write(int b) throws IOException {
+    os.write(b);
+  }
+
+  /* @see java.io.DataOutput#writeBoolean(boolean) */
+  public void writeBoolean(boolean v) throws IOException {
+    os.writeBoolean(v);
+  }
+
+  /* @see java.io.DataOutput#writeByte(int) */
+  public void writeByte(int v) throws IOException {
+    os.writeByte(v);
+  }
+
+  /* @see java.io.DataOutput#writeBytes(String) */
+  public void writeBytes(String s) throws IOException {
+    os.writeBytes(s);
+  }
+
+  /* @see java.io.DataOutput#writeChar(int) */
+  public void writeChar(int v) throws IOException {
+    os.writeChar(v);
+  }
+
+  /* @see java.io.DataOutput#writeChars(String) */
+  public void writeChars(String s) throws IOException {
+    os.writeChars(s);
+  }
+
+  /* @see java.io.DataOutput#writeDouble(double) */
+  public void writeDouble(double v) throws IOException {
+    os.writeDouble(v);
+  }
+
+  /* @see java.io.DataOutput#writeFloat(float) */
+  public void writeFloat(float v) throws IOException {
+    os.writeFloat(v);
+  }
+
+  /* @see java.io.DataOutput#writeInt(int) */
+  public void writeInt(int v) throws IOException {
+    os.writeInt(v);
+  }
+
+  /* @see java.io.DataOutput#writeLong(long) */
+  public void writeLong(long v) throws IOException {
+    os.writeLong(v);
+  }
+
+  /* @see java.io.DataOutput#writeShort(int) */
+  public void writeShort(int v) throws IOException {
+    os.writeShort(v);
+  }
+
+  /* @see java.io.DataOutput#writeUTF(String) */
+  public void writeUTF(String str) throws IOException {
+    os.writeUTF(str);
+  }
+
+  // -- Helper methods --
+
+  private void markManager() throws IOException {
+    if (fp >= mark + 65535) {
+      mark = fp;
+      is.mark((int) length());
+    }
+  }
+}
diff --git a/loci/formats/RandomAccessStream.java b/loci/formats/RandomAccessStream.java
new file mode 100644
index 0000000..cd0c894
--- /dev/null
+++ b/loci/formats/RandomAccessStream.java
@@ -0,0 +1,732 @@
+//
+// RandomAccessStream.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+import loci.formats.codec.CBZip2InputStream;
+
+/**
+ * RandomAccessStream provides methods for "intelligent" reading of files and
+ * byte arrays.  It also automagically deals with closing and reopening files
+ * to prevent an IOException caused by too many open files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/RandomAccessStream.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/RandomAccessStream.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class RandomAccessStream extends InputStream implements DataInput {
+
+  // -- Constants --
+
+  /** Maximum size of the buffer used by the DataInputStream. */
+  // 256 KB - please don't change this
+  protected static final int MAX_OVERHEAD = 262144;
+
+  /** Maximum number of buffer sizes to keep. */
+  protected static final int MAX_HISTORY = 50;
+
+  /** Maximum number of open files. */
+  protected static final int MAX_FILES = 100;
+
+  /** Indicators for most efficient method of reading. */
+  protected static final int DIS = 0;
+  protected static final int RAF = 1;
+  protected static final int ARRAY = 2;
+
+  // -- Static fields --
+
+  /** Hashtable of all files that have been opened at some point. */
+  private static Hashtable fileCache = new Hashtable();
+
+  /** Number of currently open files. */
+  private static int openFiles = 0;
+
+  /** Recent buffer sizes. */
+  private static int[] bufferSizes = new int[MAX_HISTORY];
+
+  // -- Fields --
+
+  protected IRandomAccess raf;
+  protected DataInputStream dis;
+
+  /** Length of the file. */
+  protected long length;
+
+  /** The file pointer within the DIS. */
+  protected long fp;
+
+  /** The "absolute" file pointer. */
+  protected long afp;
+
+  /** Most recent mark. */
+  protected long mark;
+
+  /** Next place to mark. */
+  protected long nextMark;
+
+  /** The file name. */
+  protected String file;
+
+  /** Starting buffer. */
+  protected byte[] buf;
+
+  /** Endianness of the stream. */
+  protected boolean littleEndian = false;
+
+  /** Number of bytes by which to extend the stream. */
+  protected int ext = 0;
+
+  /** Number of valid entries in the buffer size array. */
+  protected int lastValid = 0;
+
+  /** Flag indicating this file has been compressed. */
+  protected boolean compressed = false;
+
+  // -- Constructors --
+
+  /**
+   * Constructs a hybrid RandomAccessFile/DataInputStream
+   * around the given file.
+   */
+  public RandomAccessStream(String file) throws IOException {
+    File f = new File(Location.getMappedId(file));
+    f = f.getAbsoluteFile();
+    if (f.exists()) {
+      raf = new RAFile(f, "r");
+
+      BufferedInputStream bis = new BufferedInputStream(
+        new FileInputStream(Location.getMappedId(file)), MAX_OVERHEAD);
+
+      String path = f.getPath().toLowerCase();
+
+      if (path.endsWith(".gz")) {
+        dis = new DataInputStream(new GZIPInputStream(bis));
+        compressed = true;
+
+        length = 0;
+
+        while (dis.available() != 0) {
+          length += dis.skipBytes(1024);
+        }
+
+        bis = new BufferedInputStream(
+          new FileInputStream(Location.getMappedId(file)), MAX_OVERHEAD);
+        dis = new DataInputStream(new GZIPInputStream(bis));
+      }
+      else if (path.endsWith(".zip")) {
+        ZipFile zf = new ZipFile(Location.getMappedId(file));
+        InputStream zip =
+          zf.getInputStream((ZipEntry) zf.entries().nextElement());
+
+        compressed = true;
+
+        length = 0;
+
+        while (zip.available() != 0) {
+          zip.read();
+          length++;
+        }
+
+        zf = new ZipFile(Location.getMappedId(file));
+        zip = new BufferedInputStream(zf.getInputStream(
+          (ZipEntry) zf.entries().nextElement()), MAX_OVERHEAD);
+        dis = new DataInputStream(zip);
+      }
+      else if (path.endsWith(".bz2")) {
+        bis.skip(2);
+        dis = new DataInputStream(new CBZip2InputStream(bis));
+        compressed = true;
+
+        length = 0;
+
+        int s = 0;
+
+        while (s != -1) {
+          s = dis.read();
+          length++;
+        }
+
+        bis = new BufferedInputStream(
+          new FileInputStream(Location.getMappedId(file)), MAX_OVERHEAD);
+        bis.skip(2);
+        dis = new DataInputStream(new CBZip2InputStream(bis));
+      }
+      else dis = new DataInputStream(bis);
+
+      if (!compressed) {
+        length = raf.length();
+        buf = new byte[(int) (length < MAX_OVERHEAD ? length : MAX_OVERHEAD)];
+        raf.readFully(buf);
+        raf.seek(0);
+        bufferSizes[0] = MAX_OVERHEAD / 2;
+        lastValid = 1;
+        nextMark = MAX_OVERHEAD;
+      }
+    }
+    else if (file.startsWith("http")) {
+      raf = new RAUrl(Location.getMappedId(file), "r");
+      length = raf.length();
+    }
+    else throw new IOException("File not found : " + file);
+    this.file = file;
+    fp = 0;
+    afp = 0;
+    fileCache.put(this, Boolean.TRUE);
+    openFiles++;
+    if (openFiles > MAX_FILES) cleanCache();
+  }
+
+  /** Constructs a random access stream around the given byte array. */
+  public RandomAccessStream(byte[] array) throws IOException {
+    // this doesn't use a file descriptor, so we don't need to add it to the
+    // file cache
+    raf = new RABytes(array);
+    fp = 0;
+    afp = 0;
+    length = raf.length();
+  }
+
+  // -- RandomAccessStream API methods --
+
+  /** Returns the underlying InputStream. */
+  public DataInputStream getInputStream() {
+    try {
+      if (fileCache.get(this) == Boolean.FALSE) reopen();
+    }
+    catch (IOException e) {
+      return null;
+    }
+    return dis;
+  }
+
+  /**
+   * Sets the number of bytes by which to extend the stream.  This only applies
+   * to InputStream API methods.
+   */
+  public void setExtend(int extend) { ext = extend; }
+
+  /** Seeks to the given offset within the stream. */
+  public void seek(long pos) throws IOException { afp = pos; }
+
+  /** Alias for readByte(). */
+  public int read() throws IOException {
+    int b = (int) readByte();
+    if (b == -1 && (afp >= length()) && ext > 0) return 0;
+    return b;
+  }
+
+  /** Gets the number of bytes in the file. */
+  public long length() throws IOException {
+    if (fileCache.get(this) == Boolean.FALSE) reopen();
+    return length;
+  }
+
+  /** Gets the current (absolute) file pointer. */
+  public long getFilePointer() { return afp; }
+
+  /** Closes the streams. */
+  public void close() throws IOException {
+    if (raf != null) raf.close();
+    raf = null;
+    if (dis != null) dis.close();
+    dis = null;
+    buf = null;
+    if (fileCache.get(this) != Boolean.FALSE) {
+      fileCache.put(this, Boolean.FALSE);
+      openFiles--;
+    }
+  }
+
+  /** Sets the endianness of the stream. */
+  public void order(boolean little) { littleEndian = little; }
+
+  /** Gets the endianness of the stream. */
+  public boolean isLittleEndian() { return littleEndian; }
+
+  // -- DataInput API methods --
+
+  /** Read an input byte and return true if the byte is nonzero. */
+  public boolean readBoolean() throws IOException {
+    return (readByte() != 0);
+  }
+
+  /** Read one byte and return it. */
+  public byte readByte() throws IOException {
+    int status = checkEfficiency(1);
+
+    long oldAFP = afp;
+    if (afp < length - 1) afp++;
+
+    if (status == DIS) {
+      byte b = dis.readByte();
+      fp++;
+      return b;
+    }
+    else if (status == ARRAY) {
+      return buf[(int) oldAFP];
+    }
+    else {
+      byte b = raf.readByte();
+      return b;
+    }
+  }
+
+  /** Read an input char. */
+  public char readChar() throws IOException {
+    return (char) readByte();
+  }
+
+  /** Read eight bytes and return a double value. */
+  public double readDouble() throws IOException {
+    return Double.longBitsToDouble(readLong());
+  }
+
+  /** Read four bytes and return a float value. */
+  public float readFloat() throws IOException {
+    return Float.intBitsToFloat(readInt());
+  }
+
+  /** Read four input bytes and return an int value. */
+  public int readInt() throws IOException {
+    return DataTools.read4SignedBytes(this, littleEndian);
+  }
+
+  /** Read the next line of text from the input stream. */
+  public String readLine() throws IOException {
+    StringBuffer sb = new StringBuffer();
+    char c = readChar();
+    while (c != '\n') {
+      sb = sb.append(c);
+      c = readChar();
+    }
+    return sb.toString();
+  }
+
+  /** Read a string of length n. */
+  public String readString(int n) throws IOException {
+    byte[] b = new byte[n];
+    read(b);
+    return new String(b);
+  }
+
+  /** Read eight input bytes and return a long value. */
+  public long readLong() throws IOException {
+    return DataTools.read8SignedBytes(this, littleEndian);
+  }
+
+  /** Read two input bytes and return a short value. */
+  public short readShort() throws IOException {
+    return DataTools.read2SignedBytes(this, littleEndian);
+  }
+
+  /** Read an input byte and zero extend it appropriately. */
+  public int readUnsignedByte() throws IOException {
+    return DataTools.readUnsignedByte(this);
+  }
+
+  /** Read two bytes and return an int in the range 0 through 65535. */
+  public int readUnsignedShort() throws IOException {
+    return DataTools.read2UnsignedBytes(this, littleEndian);
+  }
+
+  /** Read a string that has been encoded using a modified UTF-8 format. */
+  public String readUTF() throws IOException {
+    return null;  // not implemented yet...we don't really need this
+  }
+
+  /** Skip n bytes within the stream. */
+  public int skipBytes(int n) throws IOException {
+    afp += n;
+    return n;
+  }
+
+  /** Read bytes from the stream into the given array. */
+  public int read(byte[] array) throws IOException {
+    int status = checkEfficiency(array.length);
+    int n = 0;
+
+    if (status == DIS) {
+      return read(array, 0, array.length);
+    }
+    else if (status == ARRAY) {
+      n = array.length;
+      if ((buf.length - afp) < array.length) {
+        n = buf.length - (int) afp;
+      }
+      System.arraycopy(buf, (int) afp, array, 0, n);
+    }
+    else n = raf.read(array);
+
+    afp += n;
+    if (status == DIS) fp += n;
+    if (n < array.length && ext > 0) {
+      while (n < array.length && ext > 0) {
+        n++;
+        ext--;
+      }
+    }
+    return n;
+  }
+
+  /**
+   * Read n bytes from the stream into the given array at the specified offset.
+   */
+  public int read(byte[] array, int offset, int n) throws IOException {
+    int toRead = n;
+    int status = checkEfficiency(n);
+
+    if (status == DIS) {
+      int p = dis.read(array, offset, n);
+      if (p == -1) return -1;
+      if ((p >= 0) && ((fp + p) < length)) {
+        int k = p;
+        while ((k >= 0) && (p < n) && ((afp + p) <= length) &&
+          ((offset + p) < array.length))
+        {
+          k = dis.read(array, offset + p, n - p);
+          if (k >= 0) p += k;
+        }
+      }
+      n = p;
+    }
+    else if (status == ARRAY) {
+      if ((buf.length - afp) < n) n = buf.length - (int) afp;
+      System.arraycopy(buf, (int) afp, array, offset, n);
+    }
+    else {
+      n = raf.read(array, offset, n);
+    }
+    afp += n;
+    if (status == DIS) fp += n;
+    if (n < toRead && ext > 0) {
+      while (n < array.length && ext > 0) {
+        n++;
+        ext--;
+      }
+    }
+
+    return n;
+  }
+
+  /** Read bytes from the stream into the given array. */
+  public void readFully(byte[] array) throws IOException {
+    int status = checkEfficiency(array.length);
+
+    if (status == DIS) {
+      readFully(array, 0, array.length);
+    }
+    else if (status == ARRAY) {
+      System.arraycopy(buf, (int) afp, array, 0, array.length);
+    }
+    else {
+      raf.readFully(array, 0, array.length);
+    }
+    afp += array.length;
+    if (status == DIS) fp += array.length;
+  }
+
+  /**
+   * Read n bytes from the stream into the given array at the specified offset.
+   */
+  public void readFully(byte[] array, int offset, int n) throws IOException {
+    int status = checkEfficiency(n);
+
+    if (status == DIS) {
+      dis.readFully(array, offset, n);
+    }
+    else if (status == ARRAY) {
+      System.arraycopy(buf, (int) afp, array, offset, n);
+    }
+    else {
+      raf.readFully(array, offset, n);
+    }
+    afp += n;
+    if (status == DIS) fp += n;
+  }
+
+  // -- InputStream API methods --
+
+  public int available() throws IOException {
+    if (fileCache.get(this) == Boolean.FALSE) reopen();
+    int available = dis != null ? dis.available() + ext :
+      (int) (length() - getFilePointer());
+    if (available < 0) available = Integer.MAX_VALUE;
+    return available;
+  }
+
+  public void mark(int readLimit) {
+    try {
+      if (fileCache.get(this) == Boolean.FALSE) reopen();
+    }
+    catch (IOException e) { }
+    if (!compressed) dis.mark(readLimit);
+  }
+
+  public boolean markSupported() { return !compressed; }
+
+  public void reset() throws IOException {
+    if (fileCache.get(this) == Boolean.FALSE) reopen();
+    dis.reset();
+    fp = length() - dis.available();
+  }
+
+  // -- Helper methods - I/O --
+
+  /** Naive heuristic for determining a "good" buffer size for the DIS. */
+  protected int determineBuffer() {
+    // first we want the weighted average of previous buffer sizes
+
+    int sum = 0;
+    int div = 0;
+    int ndx = 0;
+
+    while ((ndx < lastValid) && (ndx < MAX_HISTORY)) {
+      int size = bufferSizes[ndx];
+      sum += (size * ((ndx / (MAX_HISTORY / 5)) + 1));
+      div += (ndx / (MAX_HISTORY / 5)) + 1;
+      ndx++;
+    }
+
+    int newSize = sum / div;
+    if (newSize > MAX_OVERHEAD) newSize = MAX_OVERHEAD;
+    if (lastValid < MAX_HISTORY) {
+      bufferSizes[lastValid] = newSize;
+      lastValid++;
+    }
+    else {
+      bufferSizes[0] = newSize;
+    }
+
+    return newSize;
+  }
+
+  /**
+   * Determine whether it is more efficient to use the DataInputStream or
+   * RandomAccessFile for reading (based on the current file pointers).
+   * Returns 0 if we should use the DataInputStream, 1 if we should use the
+   * RandomAccessFile, and 2 for a direct array access.
+   */
+  protected int checkEfficiency(int toRead) throws IOException {
+    if (fileCache.get(this) == Boolean.FALSE) reopen();
+    int oldBufferSize = bufferSizes[bufferSizes.length - 1];
+
+    if (compressed) {
+      // can only read from the input stream
+
+      if (afp < fp) {
+        dis.close();
+
+        BufferedInputStream bis = new BufferedInputStream(
+          new FileInputStream(Location.getMappedId(file)), MAX_OVERHEAD);
+
+        String path = Location.getMappedId(file).toLowerCase();
+
+        if (path.endsWith(".gz")) {
+          dis = new DataInputStream(new GZIPInputStream(bis));
+        }
+        else if (path.endsWith(".zip")) {
+          ZipFile zf = new ZipFile(Location.getMappedId(file));
+          InputStream zip = new BufferedInputStream(zf.getInputStream(
+            (ZipEntry) zf.entries().nextElement()), MAX_OVERHEAD);
+          dis = new DataInputStream(zip);
+        }
+        else if (path.endsWith(".bz2")) {
+          bis.skip(2);
+          dis = new DataInputStream(new CBZip2InputStream(bis));
+        }
+        fp = 0;
+      }
+
+      while (fp < afp) {
+        fp += dis.skipBytes((int) (afp - fp));
+      }
+
+      return DIS;
+    }
+
+    if (dis != null) {
+      while (fp > (length() - dis.available())) {
+        while (fp - length() + dis.available() > Integer.MAX_VALUE) {
+          dis.skipBytes(Integer.MAX_VALUE);
+        }
+        dis.skipBytes((int) (fp - (length() - dis.available())));
+      }
+    }
+
+    if (dis != null && raf != null &&
+      afp + toRead < MAX_OVERHEAD && afp + toRead < raf.length())
+    {
+      // this is a really special case that allows us to read directly from
+      // an array when working with the first MAX_OVERHEAD bytes of the file
+      // ** also note that it doesn't change the stream
+      return ARRAY;
+    }
+    else if (afp >= fp && dis != null) {
+      while (fp < afp) {
+        while (afp - fp > Integer.MAX_VALUE) {
+          fp += dis.skipBytes(Integer.MAX_VALUE);
+        }
+        int skip = dis.skipBytes((int) (afp - fp));
+        if (skip == 0) break;
+        fp += skip;
+      }
+
+      if (lastValid < MAX_HISTORY) {
+        bufferSizes[lastValid] = MAX_OVERHEAD;
+        lastValid++;
+      }
+      else {
+        bufferSizes[0] = MAX_OVERHEAD;
+      }
+
+      if (fp >= nextMark) {
+        dis.mark(MAX_OVERHEAD);
+      }
+      nextMark = fp + MAX_OVERHEAD;
+      mark = fp;
+
+      return DIS;
+    }
+    else {
+      if (dis != null && afp >= mark && fp < mark + oldBufferSize) {
+        int newBufferSize = determineBuffer();
+
+        boolean valid = true;
+
+        try {
+          dis.reset();
+        }
+        catch (IOException io) {
+          valid = false;
+        }
+
+        if (valid) {
+          dis.mark(newBufferSize);
+          //fp = mark;
+
+          fp = length() - dis.available();
+          while (fp < afp) {
+            while (afp - fp > Integer.MAX_VALUE) {
+              fp += dis.skipBytes(Integer.MAX_VALUE);
+            }
+            int skip = dis.skipBytes((int) (afp - fp));
+            if (skip == 0) break;
+            fp += skip;
+          }
+
+          if (fp >= nextMark) {
+            dis.mark(newBufferSize);
+          }
+          nextMark = fp + newBufferSize;
+          mark = fp;
+
+          return DIS;
+        }
+        else {
+          raf.seek(afp);
+          return RAF;
+        }
+      }
+      else {
+        // we don't want this to happen very often
+        raf.seek(afp);
+        return RAF;
+      }
+    }
+  }
+
+  // -- Helper methods - cache management --
+
+  /** Re-open a file that has been closed */
+  private void reopen() throws IOException {
+    File f = new File(Location.getMappedId(file));
+    f = f.getAbsoluteFile();
+    if (f.exists()) {
+      raf = new RAFile(f, "r");
+
+      BufferedInputStream bis = new BufferedInputStream(
+        new FileInputStream(Location.getMappedId(file)), MAX_OVERHEAD);
+
+      String path = f.getPath().toLowerCase();
+
+      if (path.endsWith(".gz")) {
+        dis = new DataInputStream(new GZIPInputStream(bis));
+        compressed = true;
+      }
+      else if (path.endsWith(".zip")) {
+        ZipFile zf = new ZipFile(Location.getMappedId(file));
+        InputStream zip = new BufferedInputStream(zf.getInputStream(
+          (ZipEntry) zf.entries().nextElement()), MAX_OVERHEAD);
+        dis = new DataInputStream(zip);
+        compressed = true;
+      }
+      else if (path.endsWith(".bz2")) {
+        bis.skip(2);
+        dis = new DataInputStream(new CBZip2InputStream(bis));
+        compressed = true;
+      }
+      else dis = new DataInputStream(bis);
+
+      if (!compressed) {
+        length = raf.length();
+        buf = new byte[(int) (length < MAX_OVERHEAD ? length : MAX_OVERHEAD)];
+        raf.readFully(buf);
+        raf.seek(0);
+      }
+    }
+    else {
+      raf = new RAUrl(Location.getMappedId(file), "r");
+      length = raf.length();
+    }
+    fileCache.put(this, Boolean.TRUE);
+    openFiles++;
+    if (openFiles > MAX_FILES) cleanCache();
+  }
+
+  /** If we have too many open files, close most of them. */
+  private void cleanCache() {
+    int toClose = MAX_FILES - 10;
+    RandomAccessStream[] files = (RandomAccessStream[])
+      fileCache.keySet().toArray(new RandomAccessStream[0]);
+    int closed = 0;
+    int ndx = 0;
+
+    while (closed < toClose) {
+      if (!this.equals(files[ndx]) &&
+        !fileCache.get(files[ndx]).equals(Boolean.FALSE))
+      {
+        try { files[ndx].close(); }
+        catch (IOException exc) { LogTools.trace(exc); }
+        closed++;
+      }
+      ndx++;
+    }
+  }
+
+}
diff --git a/loci/formats/ReaderWrapper.java b/loci/formats/ReaderWrapper.java
new file mode 100644
index 0000000..007e9e6
--- /dev/null
+++ b/loci/formats/ReaderWrapper.java
@@ -0,0 +1,570 @@
+//
+// ReaderWrapper.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.Hashtable;
+
+/**
+ * Abstract superclass of reader logic that wraps other readers.
+ * All methods are simply delegated to the wrapped reader.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ReaderWrapper.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ReaderWrapper.java">SVN</a></dd></dl>
+ */
+public abstract class ReaderWrapper implements IFormatReader {
+
+  // -- Fields --
+
+  /** FormatReader used to read the file. */
+  protected IFormatReader reader;
+
+  // -- Constructors --
+
+  /** Constructs a reader wrapper around a new image reader. */
+  public ReaderWrapper() { this(new ImageReader()); }
+
+  /** Constructs a reader wrapper around the given reader. */
+  public ReaderWrapper(IFormatReader r) {
+    if (r == null) {
+      throw new IllegalArgumentException("Format reader cannot be null");
+    }
+    reader = r;
+  }
+
+  // -- ReaderWrapper API methods --
+
+  /** Gets the wrapped reader. */
+  public IFormatReader getReader() { return reader; }
+
+  // -- IFormatReader API methods --
+
+  public boolean isThisType(byte[] block) {
+    return reader.isThisType(block);
+  }
+
+  public void setId(String id) throws FormatException, IOException {
+    reader.setId(id);
+  }
+
+  public void setId(String id, boolean force)
+    throws FormatException, IOException
+  {
+    reader.setId(id, force);
+  }
+
+  public int getImageCount() {
+    return reader.getImageCount();
+  }
+
+  public boolean isRGB() {
+    return reader.isRGB();
+  }
+
+  public int getSizeX() {
+    return reader.getSizeX();
+  }
+
+  public int getSizeY() {
+    return reader.getSizeY();
+  }
+
+  public int getSizeZ() {
+    return reader.getSizeZ();
+  }
+
+  public int getSizeC() {
+    return reader.getSizeC();
+  }
+
+  public int getSizeT() {
+    return reader.getSizeT();
+  }
+
+  public int getPixelType() {
+    return reader.getPixelType();
+  }
+
+  public int getEffectiveSizeC() {
+    return getImageCount() / (getSizeZ() * getSizeT());
+  }
+
+  public int getRGBChannelCount() {
+    return getSizeC() / getEffectiveSizeC();
+  }
+
+  public boolean isIndexed() {
+    return reader.isIndexed();
+  }
+
+  public boolean isFalseColor() {
+    return reader.isFalseColor();
+  }
+
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    return reader.get8BitLookupTable();
+  }
+
+  public short[][] get16BitLookupTable() throws FormatException, IOException {
+    return reader.get16BitLookupTable();
+  }
+
+  public int[] getChannelDimLengths() {
+    return reader.getChannelDimLengths();
+  }
+
+  public String[] getChannelDimTypes() {
+    return reader.getChannelDimTypes();
+  }
+
+  public int getThumbSizeX() {
+    return reader.getThumbSizeX();
+  }
+
+  public int getThumbSizeY() {
+    return reader.getThumbSizeY();
+  }
+
+  public boolean isLittleEndian() {
+    return reader.isLittleEndian();
+  }
+
+  public String getDimensionOrder() {
+    return reader.getDimensionOrder();
+  }
+
+  public boolean isOrderCertain() {
+    return reader.isOrderCertain();
+  }
+
+  public boolean isInterleaved() {
+    return reader.isInterleaved();
+  }
+
+  public boolean isInterleaved(int subC) {
+    return reader.isInterleaved(subC);
+  }
+
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    return reader.openImage(no);
+  }
+
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    return reader.openBytes(no);
+  }
+
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    return reader.openBytes(no, buf);
+  }
+
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    return reader.openThumbImage(no);
+  }
+
+  public byte[] openThumbBytes(int no) throws FormatException, IOException {
+    return reader.openThumbBytes(no);
+  }
+
+  public void close(boolean fileOnly) throws IOException {
+    reader.close(fileOnly);
+  }
+
+  public void close() throws IOException {
+    reader.close();
+  }
+
+  public int getSeriesCount() {
+    return reader.getSeriesCount();
+  }
+
+  public void setSeries(int no) {
+    reader.setSeries(no);
+  }
+
+  public int getSeries() {
+    return reader.getSeries();
+  }
+
+  public void setGroupFiles(boolean group) {
+    reader.setGroupFiles(group);
+  }
+
+  public boolean isGroupFiles() {
+    return reader.isGroupFiles();
+  }
+
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    return reader.fileGroupOption(id);
+  }
+
+  public boolean isMetadataComplete() {
+    return reader.isMetadataComplete();
+  }
+
+  public void setNormalized(boolean normalize) {
+    reader.setNormalized(normalize);
+  }
+
+  public boolean isNormalized() { return reader.isNormalized(); }
+
+  public void setMetadataCollected(boolean collect) {
+    reader.setMetadataCollected(collect);
+  }
+
+  public boolean isMetadataCollected() { return reader.isMetadataCollected(); }
+
+  public void setOriginalMetadataPopulated(boolean populate) {
+    reader.setOriginalMetadataPopulated(populate);
+  }
+
+  public boolean isOriginalMetadataPopulated() {
+    return reader.isOriginalMetadataPopulated();
+  }
+
+  public String[] getUsedFiles() {
+    return reader.getUsedFiles();
+  }
+
+  public String getCurrentFile() { return reader.getCurrentFile(); }
+
+  public int getIndex(int z, int c, int t) {
+    return reader.getIndex(z, c, t);
+  }
+
+  public int[] getZCTCoords(int index) {
+    return reader.getZCTCoords(index);
+  }
+
+  public Object getMetadataValue(String field) {
+    return reader.getMetadataValue(field);
+  }
+
+  public Hashtable getMetadata() {
+    return reader.getMetadata();
+  }
+
+  public CoreMetadata getCoreMetadata() {
+    return reader.getCoreMetadata();
+  }
+
+  public void setMetadataFiltered(boolean filter) {
+    reader.setMetadataFiltered(filter);
+  }
+
+  public boolean isMetadataFiltered() { return reader.isMetadataFiltered(); }
+
+  public void setMetadataStore(MetadataStore store) {
+    reader.setMetadataStore(store);
+  }
+
+  public MetadataStore getMetadataStore() {
+    return reader.getMetadataStore();
+  }
+
+  public Object getMetadataStoreRoot() {
+    return reader.getMetadataStoreRoot();
+  }
+
+  // -- IFormatHandler API methods --
+
+  public boolean isThisType(String name) {
+    return reader.isThisType(name);
+  }
+
+  public boolean isThisType(String name, boolean open) {
+    return reader.isThisType(name, open);
+  }
+
+  public String getFormat() {
+    return reader.getFormat();
+  }
+
+  public String[] getSuffixes() {
+    return reader.getSuffixes();
+  }
+
+  // -- StatusReporter API methods --
+
+  public void addStatusListener(StatusListener l) {
+    reader.addStatusListener(l);
+  }
+
+  public void removeStatusListener(StatusListener l) {
+    reader.removeStatusListener(l);
+  }
+
+  public StatusListener[] getStatusListeners() {
+    return reader.getStatusListeners();
+  }
+
+  // -- Deprecated IFormatReader API methods --
+
+  /** @deprecated Replaced by IFormatReader#getImageCount() */
+  public int getImageCount(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getImageCount();
+  }
+
+  /** @deprecated Replaced by IFormatReader#isRGB() */
+  public boolean isRGB(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.isRGB();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getSizeX() */
+  public int getSizeX(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getSizeX();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getSizeY() */
+  public int getSizeY(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getSizeY();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getSizeZ() */
+  public int getSizeZ(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getSizeZ();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getSizeC() */
+  public int getSizeC(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getSizeC();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getSizeT() */
+  public int getSizeT(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getSizeT();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getPixelType() */
+  public int getPixelType(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getPixelType();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getEffectiveSizeC() */
+  public int getEffectiveSizeC(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return getImageCount() / (getSizeZ() * getSizeT());
+  }
+
+  /** @deprecated Replaced by IFormatReader#getRGBChannelCount() */
+  public int getRGBChannelCount(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return getSizeC() / getEffectiveSizeC();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getChannelDimLengths() */
+  public int[] getChannelDimLengths(String id)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getChannelDimLengths();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getChannelDimTypes() */
+  public String[] getChannelDimTypes(String id)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getChannelDimTypes();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getThumbSizeX() */
+  public int getThumbSizeX(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getThumbSizeX();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getThumbSizeY() */
+  public int getThumbSizeY(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getThumbSizeY();
+  }
+
+  /** @deprecated Replaced by IFormatReader#isLittleEndian() */
+  public boolean isLittleEndian(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.isLittleEndian();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getDimensionOrder() */
+  public String getDimensionOrder(String id)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getDimensionOrder();
+  }
+
+  /** @deprecated Replaced by IFormatReader#isOrderCertain() */
+  public boolean isOrderCertain(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.isOrderCertain();
+  }
+
+  /** @deprecated Replaced by IFormatReader#isInterleaved() */
+  public boolean isInterleaved(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.isInterleaved();
+  }
+
+  /** @deprecated Replaced by IFormatReader#isInterleaved(int) */
+  public boolean isInterleaved(String id, int subC)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.isInterleaved(subC);
+  }
+
+  /** @deprecated Replaced by IFormatReader#openImage(int) */
+  public BufferedImage openImage(String id, int no)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.openImage(no);
+  }
+
+  /** @deprecated Replaced by IFormatReader#openBytes(int) */
+  public byte[] openBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.openBytes(no);
+  }
+
+  /** @deprecated Replaced by IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(String id, int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.openBytes(no, buf);
+  }
+
+  /** @deprecated Replaced by IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(String id, int no)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.openThumbImage(no);
+  }
+
+  /** @deprecated Replaced by IFormatReader#openThumbBytes(int) */
+  public byte[] openThumbBytes(String id, int no)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.openThumbBytes(no);
+  }
+
+  /** @deprecated Replaced by IFormatReader#getSeriesCount() */
+  public int getSeriesCount(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getSeriesCount();
+  }
+
+  /** @deprecated Replaced by IFormatReader#setSeries(int) */
+  public void setSeries(String id, int no) throws FormatException, IOException {
+    reader.setId(id);
+    reader.setSeries(no);
+  }
+
+  /** @deprecated Replaced by IFormatReader#getSeries() */
+  public int getSeries(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getSeries();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getUsedFiles();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getIndex(int, int, int) */
+  public int getIndex(String id, int z, int c, int t)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getIndex(z, c, t);
+  }
+
+  /** @deprecated Replaced by IFormatReader#getZCTCoords(int) */
+  public int[] getZCTCoords(String id, int index)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getZCTCoords(index);
+  }
+
+  /** @deprecated Replaced by IFormatReader#getMetadataValue(String) */
+  public Object getMetadataValue(String id, String field)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getMetadataValue(field);
+  }
+
+  /** @deprecated Replaced by IFormatReader#getMetadata() */
+  public Hashtable getMetadata(String id) throws FormatException, IOException {
+    reader.setId(id);
+    return reader.getMetadata();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getCoreMetadata() */
+  public CoreMetadata getCoreMetadata(String id)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getCoreMetadata();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getMetadataStore() */
+  public MetadataStore getMetadataStore(String id)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getMetadataStore();
+  }
+
+  /** @deprecated Replaced by IFormatReader#getMetadataStoreRoot() */
+  public Object getMetadataStoreRoot(String id)
+    throws FormatException, IOException
+  {
+    reader.setId(id);
+    return reader.getMetadataStoreRoot();
+  }
+
+}
diff --git a/loci/formats/ReflectException.java b/loci/formats/ReflectException.java
new file mode 100644
index 0000000..1590099
--- /dev/null
+++ b/loci/formats/ReflectException.java
@@ -0,0 +1,43 @@
+//
+// ReflectException.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * ReflectException is the exception thrown when something
+ * goes wrong performing a reflected operation with ReflectedUniverse.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ReflectException.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ReflectException.java">SVN</a></dd></dl>
+ */
+public class ReflectException extends Exception {
+
+  public ReflectException() { super(); }
+  public ReflectException(String s) { super(s); }
+  public ReflectException(String s, Throwable cause) { super(s, cause); }
+  public ReflectException(Throwable cause) { super(cause); }
+
+}
+
diff --git a/loci/formats/ReflectedUniverse.java b/loci/formats/ReflectedUniverse.java
new file mode 100644
index 0000000..3c67c13
--- /dev/null
+++ b/loci/formats/ReflectedUniverse.java
@@ -0,0 +1,454 @@
+//
+// ReflectedUniverse.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.lang.reflect.*;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * A general-purpose reflection wrapper class.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/ReflectedUniverse.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/ReflectedUniverse.java">SVN</a></dd></dl>
+ */
+public class ReflectedUniverse {
+
+  // -- Fields --
+
+  /** Hashtable containing all variables present in the universe. */
+  protected Hashtable variables;
+
+  /** Class loader for imported classes. */
+  protected ClassLoader loader;
+
+  /** Whether to force our way past restrictive access modifiers. */
+  protected boolean force;
+
+  /** Debugging flag. */
+  protected boolean debug;
+
+  // -- Constructors --
+
+  /** Constructs a new reflected universe. */
+  public ReflectedUniverse() { this((ClassLoader) null); }
+
+  /**
+   * Constructs a new reflected universe, with the given URLs
+   * representing additional search paths for imported classes
+   * (in addition to the CLASSPATH).
+   */
+  public ReflectedUniverse(URL[] urls) {
+    this(urls == null ? null : new URLClassLoader(urls));
+  }
+
+  /** Constructs a new reflected universe that uses the given class loader. */
+  public ReflectedUniverse(ClassLoader loader) {
+    variables = new Hashtable();
+    this.loader = loader == null ? getClass().getClassLoader() : loader;
+    debug = false;
+  }
+
+  // -- Utility methods --
+
+  /**
+   * Returns whether the given object is compatible with the
+   * specified class for the purposes of reflection.
+   */
+  public static boolean isInstance(Class c, Object o) {
+    return (o == null || c.isInstance(o) ||
+      (c == byte.class && o instanceof Byte) ||
+      (c == short.class && o instanceof Short) ||
+      (c == int.class && o instanceof Integer) ||
+      (c == long.class && o instanceof Long) ||
+      (c == float.class && o instanceof Float) ||
+      (c == double.class && o instanceof Double) ||
+      (c == boolean.class && o instanceof Boolean) ||
+      (c == char.class && o instanceof Character));
+  }
+
+  // -- ReflectedUniverse API methods --
+
+  /**
+   * Executes a command in the universe. The following syntaxes are valid:
+   * <li>import fully.qualified.package.ClassName
+   * <li>var = new ClassName(param1, ..., paramN)
+   * <li>var.method(param1, ..., paramN)
+   * <li>var2 = var.method(param1, ..., paramN)
+   * <li>ClassName.method(param1, ..., paramN)
+   * <li>var2 = ClassName.method(param1, ..., paramN)
+   * <li>var2 = var
+   * <p>
+   * Important guidelines:
+   * <li>Any referenced class must be imported first using "import".
+   * <li>Variables can be exported from the universe with getVar().
+   * <li>Variables can be imported to the universe with setVar().
+   * <li>Each parameter must be either:
+   *     1) a variable in the universe;
+   *     2) a static or instance field (i.e., no nested methods);
+   *     3) a string literal (remember to escape the double quotes);
+   *     4) an integer literal;
+   *     6) a long literal (ending in L);
+   *     7) a double literal (containing a decimal point);
+   *     8) a boolean literal (true or false);
+   *     or 9) the null keyword.
+   */
+  public Object exec(String command) throws ReflectException {
+    command = command.trim();
+    if (command.startsWith("import ")) {
+      // command is an import statement
+      command = command.substring(7).trim();
+      int dot = command.lastIndexOf(".");
+      String varName = dot < 0 ? command : command.substring(dot + 1);
+      Class c;
+      try {
+        c = Class.forName(command, true, loader);
+      }
+      catch (NoClassDefFoundError err) {
+        if (debug) LogTools.trace(err);
+        throw new ReflectException("No such class: " + command, err);
+      }
+      catch (ClassNotFoundException exc) {
+        if (debug) LogTools.trace(exc);
+        throw new ReflectException("No such class: " + command, exc);
+      }
+      catch (RuntimeException exc) {
+        // HACK: workaround for bug in Apache Axis2
+        String msg = exc.getMessage();
+        if (msg != null && msg.indexOf("ClassNotFound") < 0) throw exc;
+        if (debug) LogTools.trace(exc);
+        throw new ReflectException("No such class: " + command, exc);
+      }
+      setVar(varName, c);
+      return null;
+    }
+
+    // get variable where results of command should be stored
+    int eqIndex = command.indexOf("=");
+    String target = null;
+    if (eqIndex >= 0) {
+      target = command.substring(0, eqIndex).trim();
+      command = command.substring(eqIndex + 1).trim();
+    }
+
+    Object result = null;
+
+    // parse parentheses
+    int leftParen = command.indexOf("(");
+    if (leftParen < 0) {
+      // command is a simple assignment
+      result = getVar(command);
+      if (target != null) setVar(target, result);
+      return result;
+    }
+    else if (leftParen != command.lastIndexOf("(") ||
+      command.indexOf(")") != command.length() - 1)
+    {
+      throw new ReflectException("Invalid parentheses");
+    }
+
+    // parse arguments
+    String arglist = command.substring(leftParen + 1);
+    StringTokenizer st = new StringTokenizer(arglist, "(,)");
+    int len = st.countTokens();
+    Object[] args = new Object[len];
+    for (int i=0; i<len; i++) {
+      String arg = st.nextToken().trim();
+      args[i] = getVar(arg);
+    }
+    command = command.substring(0, leftParen);
+
+    if (command.startsWith("new ")) {
+      // command is a constructor call
+      String className = command.substring(4).trim();
+      Object var = getVar(className);
+      if (var == null) {
+        throw new ReflectException("Class not found: " + className);
+      }
+      else if (!(var instanceof Class)) {
+        throw new ReflectException("Not a class: " + className);
+      }
+      Class cl = (Class) var;
+
+      // Search for a constructor that matches the arguments. Unfortunately,
+      // calling cl.getConstructor(argClasses) does not work, because
+      // getConstructor() is not flexible enough to detect when the arguments
+      // are subclasses of the constructor argument classes, making a brute
+      // force search through all public constructors necessary.
+      Constructor constructor = null;
+      Constructor[] c = cl.getConstructors();
+      for (int i=0; i<c.length; i++) {
+        if (force) c[i].setAccessible(true);
+        Class[] params = c[i].getParameterTypes();
+        if (params.length == args.length) {
+          boolean match = true;
+          for (int j=0; j<params.length; j++) {
+            if (!isInstance(params[j], args[j])) {
+              match = false;
+              break;
+            }
+          }
+          if (match) {
+            constructor = c[i];
+            break;
+          }
+        }
+      }
+      if (constructor == null) {
+        StringBuffer sb = new StringBuffer(command);
+        for (int i=0; i<args.length; i++) {
+          sb.append(i == 0 ? "(" : ", ");
+          sb.append(args[i].getClass().getName());
+        }
+        sb.append(")");
+        throw new ReflectException("No such constructor: " + sb.toString());
+      }
+
+      // invoke constructor
+      Exception exc = null;
+      try { result = constructor.newInstance(args); }
+      catch (InstantiationException e) { exc = e; }
+      catch (IllegalAccessException e) { exc = e; }
+      catch (InvocationTargetException e) { exc = e; }
+      if (exc != null) {
+        if (debug) LogTools.trace(exc);
+        throw new ReflectException("Cannot instantiate object", exc);
+      }
+    }
+    else {
+      // command is a method call
+      int dot = command.indexOf(".");
+      if (dot < 0) throw new ReflectException("Syntax error");
+      String varName = command.substring(0, dot).trim();
+      String methodName = command.substring(dot + 1).trim();
+      Object var = getVar(varName);
+      if (var == null) {
+        throw new ReflectException("No such variable: " + varName);
+      }
+      Class varClass = var instanceof Class ? (Class) var : var.getClass();
+
+      // Search for a method that matches the arguments. Unfortunately,
+      // calling varClass.getMethod(methodName, argClasses) does not work,
+      // because getMethod() is not flexible enough to detect when the
+      // arguments are subclasses of the method argument classes, making a
+      // brute force search through all public methods necessary.
+      Method method = null;
+      Method[] m = varClass.getMethods();
+      for (int i=0; i<m.length; i++) {
+        if (force) m[i].setAccessible(true);
+        if (methodName.equals(m[i].getName())) {
+          Class[] params = m[i].getParameterTypes();
+          if (params.length == args.length) {
+            boolean match = true;
+            for (int j=0; j<params.length; j++) {
+              if (!isInstance(params[j], args[j])) {
+                match = false;
+                break;
+              }
+            }
+            if (match) {
+              method = m[i];
+              break;
+            }
+          }
+        }
+      }
+      if (method == null) {
+        throw new ReflectException("No such method: " + methodName);
+      }
+
+      // invoke method
+      Exception exc = null;
+      try { result = method.invoke(var, args); }
+      catch (IllegalAccessException e) { exc = e; }
+      catch (InvocationTargetException e) { exc = e; }
+      if (exc != null) {
+        if (debug) LogTools.trace(exc);
+        throw new ReflectException("Cannot execute method: " + methodName, exc);
+      }
+    }
+
+    // assign result to proper variable
+    if (target != null) setVar(target, result);
+    return result;
+  }
+
+  /** Registers a variable in the universe. */
+  public void setVar(String varName, Object obj) {
+    if (obj == null) variables.remove(varName);
+    else variables.put(varName, obj);
+  }
+
+  /** Registers a variable of primitive type boolean in the universe. */
+  public void setVar(String varName, boolean b) {
+    setVar(varName, new Boolean(b));
+  }
+
+  /** Registers a variable of primitive type byte in the universe. */
+  public void setVar(String varName, byte b) {
+    setVar(varName, new Byte(b));
+  }
+
+  /** Registers a variable of primitive type char in the universe. */
+  public void setVar(String varName, char c) {
+    setVar(varName, new Character(c));
+  }
+
+  /** Registers a variable of primitive type double in the universe. */
+  public void setVar(String varName, double d) {
+    setVar(varName, new Double(d));
+  }
+
+  /** Registers a variable of primitive type float in the universe. */
+  public void setVar(String varName, float f) {
+    setVar(varName, new Float(f));
+  }
+
+  /** Registers a variable of primitive type int in the universe. */
+  public void setVar(String varName, int i) {
+    setVar(varName, new Integer(i));
+  }
+
+  /** Registers a variable of primitive type long in the universe. */
+  public void setVar(String varName, long l) {
+    setVar(varName, new Long(l));
+  }
+
+  /** Registers a variable of primitive type short in the universe. */
+  public void setVar(String varName, short s) {
+    setVar(varName, new Short(s));
+  }
+
+  /**
+   * Returns the value of a variable or field in the universe.
+   * Primitive types will be wrapped in their Java Object wrapper classes.
+   */
+  public Object getVar(String varName) throws ReflectException {
+    if (varName.equals("null")) {
+      // variable is a null value
+      return null;
+    }
+    else if (varName.equals("true")) {
+      // variable is a boolean literal
+      return new Boolean(true);
+    }
+    else if (varName.equals("false")) {
+      // variable is a boolean literal
+      return new Boolean(false);
+    }
+    else if (varName.startsWith("\"") && varName.endsWith("\"")) {
+      // variable is a string literal
+      return varName.substring(1, varName.length() - 1);
+    }
+    try {
+      if (varName.matches("-?\\d+")) {
+        // variable is an int literal
+        return new Integer(varName);
+      }
+      else if (varName.matches("-?\\d+L")) {
+        // variable is a long literal
+        return new Long(varName);
+      }
+      else if (varName.matches("-?\\d*\\.\\d*")) {
+        // variable is a double literal
+        return new Double(varName);
+      }
+    }
+    catch (NumberFormatException exc) {
+      throw new ReflectException("Invalid literal: " + varName, exc);
+    }
+    int dot = varName.indexOf(".");
+    if (dot >= 0) {
+      // get field value of variable
+      String className = varName.substring(0, dot).trim();
+      Object var = variables.get(className);
+      if (var == null) {
+        throw new ReflectException("No such class: " + className);
+      }
+      Class varClass = var instanceof Class ? (Class) var : var.getClass();
+      String fieldName = varName.substring(dot + 1).trim();
+      Field field;
+      try {
+        field = varClass.getField(fieldName);
+        if (force) field.setAccessible(true);
+      }
+      catch (NoSuchFieldException exc) {
+        if (debug) LogTools.trace(exc);
+        throw new ReflectException("No such field: " + varName, exc);
+      }
+      Object fieldVal;
+      try { fieldVal = field.get(var); }
+      catch (IllegalAccessException exc) {
+        if (debug) LogTools.trace(exc);
+        throw new ReflectException("Cannot get field value: " + varName, exc);
+      }
+      return fieldVal;
+    }
+    else {
+      // get variable
+      Object var = variables.get(varName);
+      return var;
+    }
+  }
+
+  /** Sets whether access modifiers (protected, private, etc.) are ignored. */
+  public void setAccessibilityIgnored(boolean ignore) { force = ignore; }
+
+  /** Gets whether access modifiers (protected, private, etc.) are ignored. */
+  public boolean isAccessibilityIgnored() { return force; }
+
+  /** Enables or disables extended debugging output. */
+  public void setDebug(boolean debug) { this.debug = debug; }
+
+  /** Gets whether extended debugging output is enabled. */
+  public boolean isDebug() { return debug; }
+
+  // -- Main method --
+
+  /**
+   * Allows exploration of a reflected universe in an interactive environment.
+   */
+  public static void main(String[] args) throws IOException {
+    ReflectedUniverse r = new ReflectedUniverse();
+    LogTools.println("Reflected universe test environment. " +
+      "Type commands, or press ^D to quit.");
+    if (args.length > 0) {
+      r.setAccessibilityIgnored(true);
+      LogTools.println("Ignoring accessibility modifiers.");
+    }
+    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
+    while (true) {
+      LogTools.print("> ");
+      String line = in.readLine();
+      if (line == null) break;
+      try { r.exec(line); }
+      catch (ReflectException exc) { LogTools.trace(exc); }
+    }
+    LogTools.println();
+  }
+
+}
diff --git a/loci/formats/StatusEvent.java b/loci/formats/StatusEvent.java
new file mode 100644
index 0000000..18434be
--- /dev/null
+++ b/loci/formats/StatusEvent.java
@@ -0,0 +1,72 @@
+//
+// StatusEvent.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * A event indicating a status update.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/StatusEvent.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/StatusEvent.java">SVN</a></dd></dl>
+ */
+public class StatusEvent {
+
+  // -- Fields --
+
+  /** Current progress value. */
+  protected int progress;
+
+  /** Current progress maximum. */
+  protected int maximum;
+
+  /** Current status message. */
+  protected String status;
+
+  // -- Constructor --
+
+  /** Constructs a status event. */
+  public StatusEvent(String message) {
+    this(-1, -1, message);
+  }
+
+  /** Constructs a status event. */
+  public StatusEvent(int progress, int maximum, String message) {
+    this.progress = progress;
+    this.maximum = maximum;
+    status = message;
+  }
+
+  // -- StatusEvent API methods --
+
+  /** Gets progress value. Returns -1 if progress is unknown. */
+  public int getProgressValue() { return progress; }
+
+  /** Gets progress maximum. Returns -1 if progress is unknown. */
+  public int getProgressMaximum() { return maximum; }
+
+  /** Gets status message. */
+  public String getStatusMessage() { return status; }
+
+}
diff --git a/loci/formats/StatusListener.java b/loci/formats/StatusListener.java
new file mode 100644
index 0000000..7d5bc14
--- /dev/null
+++ b/loci/formats/StatusListener.java
@@ -0,0 +1,39 @@
+//
+// StatusListener.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * A listener for status updates.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/StatusListener.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/StatusListener.java">SVN</a></dd></dl>
+ */
+public interface StatusListener {
+
+  /** Called when status is updated. */
+  void statusUpdated(StatusEvent e);
+
+}
diff --git a/loci/formats/StatusReporter.java b/loci/formats/StatusReporter.java
new file mode 100644
index 0000000..b1179a2
--- /dev/null
+++ b/loci/formats/StatusReporter.java
@@ -0,0 +1,45 @@
+//
+// StatusReporter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * Interface for components capable of reporting status updates.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/StatusReporter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/StatusReporter.java">SVN</a></dd></dl>
+ */
+public interface StatusReporter {
+
+  /** Adds a listener for status update events. */
+  void addStatusListener(StatusListener l);
+
+  /** Removes a listener for status update events. */
+  void removeStatusListener(StatusListener l);
+
+  /** Gets a list of all registered status update listeners. */
+  StatusListener[] getStatusListeners();
+
+}
diff --git a/loci/formats/TiffIFDEntry.java b/loci/formats/TiffIFDEntry.java
new file mode 100644
index 0000000..12d66c4
--- /dev/null
+++ b/loci/formats/TiffIFDEntry.java
@@ -0,0 +1,86 @@
+//
+// TiffIFDEntry.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * This class represents a single raw TIFF IFD entry. It does not retrieve or
+ * store the values from the entry's specific offset and is based on the TIFF
+ * 6.0 specification of an IFD entry.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/TiffIFDEntry.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/TiffIFDEntry.java">SVN</a></dd></dl>
+ *
+ * @author Chris Allan callan at blackcat.ca
+ */
+public class TiffIFDEntry {
+
+  /** The <i>Tag</i> that identifies the field. */
+  private int tag;
+
+  /** The field <i>Type</i>. */
+  private int type;
+
+  /** The number of values, <i>Count</i> of the indicated <i>Type</i>. */
+  private int valueCount;
+
+  /**
+   * The <i>Value Offset</i>, the file offset (in bytes) of the <i>Value</i>
+   * for the field.
+   */
+  private int valueOffset;
+
+  public TiffIFDEntry(int tag, int type, int valueCount, int valueOffset) {
+    this.tag = tag;
+    this.type = type;
+    this.valueCount = valueCount;
+    this.valueOffset = valueOffset;
+  }
+
+  /**
+   * Retrieves the entry's <i>Tag</i> value.
+   * @return the entry's <i>Tag</i> value.
+   */
+  public int getTag() { return tag; }
+
+  /**
+   * Retrieves the entry's <i>Type</i> value.
+   * @return the entry's <i>Type</i> value.
+   */
+  public int getType() { return type; }
+
+  /**
+   * Retrieves the entry's <i>ValueCount</i> value.
+   * @return the entry's <i>ValueCount</i> value.
+   */
+  public int getValueCount() { return valueCount; }
+
+  /**
+   * Retrieves the entry's <i>ValueOffset</i> value.
+   * @return the entry's <i>ValueOffset</i> value.
+   */
+  public int getValueOffset() { return valueOffset; }
+
+}
diff --git a/loci/formats/TiffRational.java b/loci/formats/TiffRational.java
new file mode 100644
index 0000000..cdead6b
--- /dev/null
+++ b/loci/formats/TiffRational.java
@@ -0,0 +1,121 @@
+//
+// TiffRational.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * A rational number (numerator over denominator).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/TiffRational.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/TiffRational.java">SVN</a></dd></dl>
+ */
+public class TiffRational extends Number implements Comparable {
+
+  // -- Fields --
+
+  /** Components of the rational's fractional representation. */
+  protected long numer, denom;
+
+  // -- Constructor --
+
+  /** Constructs a rational number. */
+  public TiffRational(long numer, long denom) {
+    this.numer = numer;
+    this.denom = denom;
+  }
+
+  // -- TiffRational API methods --
+
+  /** Gets this number's numerator. */
+  public long getNumerator() { return numer; }
+
+  /** Gets this number's denominator. */
+  public long getDenominator() { return denom; }
+
+  /** Reduces this rational's fraction to lowest terms. */
+  public void reduce() {
+    long sqrt1 = (long) Math.sqrt(numer);
+    long sqrt2 = (long) Math.sqrt(denom);
+    long gcdMax = sqrt1 < sqrt2 ? sqrt1 : sqrt2;
+    // search for greatest common divisor
+    for (long i=gcdMax; i>=2; i--) {
+      if (numer % i == 0 && denom % i == 0) {
+        numer /= i;
+        denom /= i;
+        reduce();
+        break;
+      }
+    }
+  }
+
+  // -- Number API methods --
+
+  /** Returns the value of the specified number as a byte. */
+  public byte byteValue() { return (byte) longValue(); }
+
+  /** Returns the value of the specified number as a double. */
+  public double doubleValue() { return (double) longValue(); }
+
+  /** Returns the value of the specified number as a float. */
+  public float floatValue() { return (float) longValue(); }
+
+  /** Returns the value of the specified number as an int. */
+  public int intValue() { return (int) longValue(); }
+
+  /** Returns the value of the specified number as a long. */
+  public long longValue() {
+    return denom == 0 ? Long.MAX_VALUE : (numer / denom);
+  }
+
+  /** Returns the value of the specified number as a short. */
+  public short shortValue() { return (short) longValue(); }
+
+  // -- Object API methods --
+
+  /** Indicates whether some other object is "equal to" this one. */
+  public boolean equals(Object o) { return compareTo(o) == 0; }
+
+  /** Reasonable hash value for use with hashtables. */
+  public int hashCode() { return (int) (numer - denom); }
+
+  /** Returns a string representation of the object. */
+  public String toString() { return numer + "/" + denom; }
+
+  // -- Comparable API methods --
+
+  /**
+   * Compares this object with the specified object for order.
+   * Returns a negative integer, zero, or a positive integer as this object
+   * is less than, equal to, or greater than the specified object.
+   */
+  public int compareTo(Object o) {
+    TiffRational q = (TiffRational) o;
+    long diff = (numer * q.denom - q.numer * denom);
+    if (diff > Integer.MAX_VALUE) diff = Integer.MAX_VALUE;
+    else if (diff < Integer.MIN_VALUE) diff = Integer.MIN_VALUE;
+    return (int) diff;
+  }
+
+}
diff --git a/loci/formats/TiffTools.java b/loci/formats/TiffTools.java
new file mode 100644
index 0000000..f093c99
--- /dev/null
+++ b/loci/formats/TiffTools.java
@@ -0,0 +1,2552 @@
+//
+// TiffTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.io.*;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.nio.*;
+import java.util.*;
+import loci.formats.codec.*;
+
+/**
+ * A utility class for manipulating TIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/TiffTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/TiffTools.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Eric Kjellman egkjellman at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ * @author Chris Allan callan at blackcat.ca
+ */
+public final class TiffTools {
+
+  // -- Constants --
+
+  private static final boolean DEBUG = false;
+
+  /** The number of bytes in each IFD entry. */
+  public static final int BYTES_PER_ENTRY = 12;
+
+  /** The number of bytes in each IFD entry of a BigTIFF file. */
+  public static final int BIG_TIFF_BYTES_PER_ENTRY = 20;
+
+  // non-IFD tags (for internal use)
+  public static final int LITTLE_ENDIAN = 0;
+  public static final int BIG_TIFF = 1;
+
+  // IFD types
+  public static final int BYTE = 1;
+  public static final int ASCII = 2;
+  public static final int SHORT = 3;
+  public static final int LONG = 4;
+  public static final int RATIONAL = 5;
+  public static final int SBYTE = 6;
+  public static final int UNDEFINED = 7;
+  public static final int SSHORT = 8;
+  public static final int SLONG = 9;
+  public static final int SRATIONAL = 10;
+  public static final int FLOAT = 11;
+  public static final int DOUBLE = 12;
+  public static final int LONG8 = 16;
+  public static final int SLONG8 = 17;
+  public static final int IFD8 = 18;
+
+  public static final int[] BYTES_PER_ELEMENT = {
+    -1, // invalid type
+    1, // BYTE
+    1, // ASCII
+    2, // SHORT
+    4, // LONG
+    8, // RATIONAL
+    1, // SBYTE
+    1, // UNDEFINED
+    2, // SSHORT
+    4, // SLONG
+    8, // SRATIONAL
+    4, // FLOAT
+    8, // DOUBLE
+    -1, // invalid type
+    -1, // invalid type
+    -1, // invalid type
+    -1, // invalid type
+    8, // LONG8
+    8, // SLONG8
+    8 // IFD8
+  };
+
+  // IFD tags
+  public static final int NEW_SUBFILE_TYPE = 254;
+  public static final int SUBFILE_TYPE = 255;
+  public static final int IMAGE_WIDTH = 256;
+  public static final int IMAGE_LENGTH = 257;
+  public static final int BITS_PER_SAMPLE = 258;
+  public static final int COMPRESSION = 259;
+  public static final int PHOTOMETRIC_INTERPRETATION = 262;
+  public static final int THRESHHOLDING = 263;
+  public static final int CELL_WIDTH = 264;
+  public static final int CELL_LENGTH = 265;
+  public static final int FILL_ORDER = 266;
+  public static final int DOCUMENT_NAME = 269;
+  public static final int IMAGE_DESCRIPTION = 270;
+  public static final int MAKE = 271;
+  public static final int MODEL = 272;
+  public static final int STRIP_OFFSETS = 273;
+  public static final int ORIENTATION = 274;
+  public static final int SAMPLES_PER_PIXEL = 277;
+  public static final int ROWS_PER_STRIP = 278;
+  public static final int STRIP_BYTE_COUNTS = 279;
+  public static final int MIN_SAMPLE_VALUE = 280;
+  public static final int MAX_SAMPLE_VALUE = 281;
+  public static final int X_RESOLUTION = 282;
+  public static final int Y_RESOLUTION = 283;
+  public static final int PLANAR_CONFIGURATION = 284;
+  public static final int PAGE_NAME = 285;
+  public static final int X_POSITION = 286;
+  public static final int Y_POSITION = 287;
+  public static final int FREE_OFFSETS = 288;
+  public static final int FREE_BYTE_COUNTS = 289;
+  public static final int GRAY_RESPONSE_UNIT = 290;
+  public static final int GRAY_RESPONSE_CURVE = 291;
+  public static final int T4_OPTIONS = 292;
+  public static final int T6_OPTIONS = 293;
+  public static final int RESOLUTION_UNIT = 296;
+  public static final int PAGE_NUMBER = 297;
+  public static final int TRANSFER_FUNCTION = 301;
+  public static final int SOFTWARE = 305;
+  public static final int DATE_TIME = 306;
+  public static final int ARTIST = 315;
+  public static final int HOST_COMPUTER = 316;
+  public static final int PREDICTOR = 317;
+  public static final int WHITE_POINT = 318;
+  public static final int PRIMARY_CHROMATICITIES = 319;
+  public static final int COLOR_MAP = 320;
+  public static final int HALFTONE_HINTS = 321;
+  public static final int TILE_WIDTH = 322;
+  public static final int TILE_LENGTH = 323;
+  public static final int TILE_OFFSETS = 324;
+  public static final int TILE_BYTE_COUNTS = 325;
+  public static final int INK_SET = 332;
+  public static final int INK_NAMES = 333;
+  public static final int NUMBER_OF_INKS = 334;
+  public static final int DOT_RANGE = 336;
+  public static final int TARGET_PRINTER = 337;
+  public static final int EXTRA_SAMPLES = 338;
+  public static final int SAMPLE_FORMAT = 339;
+  public static final int S_MIN_SAMPLE_VALUE = 340;
+  public static final int S_MAX_SAMPLE_VALUE = 341;
+  public static final int TRANSFER_RANGE = 342;
+  public static final int JPEG_PROC = 512;
+  public static final int JPEG_INTERCHANGE_FORMAT = 513;
+  public static final int JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
+  public static final int JPEG_RESTART_INTERVAL = 515;
+  public static final int JPEG_LOSSLESS_PREDICTORS = 517;
+  public static final int JPEG_POINT_TRANSFORMS = 518;
+  public static final int JPEG_Q_TABLES = 519;
+  public static final int JPEG_DC_TABLES = 520;
+  public static final int JPEG_AC_TABLES = 521;
+  public static final int Y_CB_CR_COEFFICIENTS = 529;
+  public static final int Y_CB_CR_SUB_SAMPLING = 530;
+  public static final int Y_CB_CR_POSITIONING = 531;
+  public static final int REFERENCE_BLACK_WHITE = 532;
+  public static final int COPYRIGHT = 33432;
+
+  // compression types
+  public static final int UNCOMPRESSED = 1;
+  public static final int CCITT_1D = 2;
+  public static final int GROUP_3_FAX = 3;
+  public static final int GROUP_4_FAX = 4;
+  public static final int LZW = 5;
+  //public static final int JPEG = 6;
+  public static final int JPEG = 7;
+  public static final int PACK_BITS = 32773;
+  public static final int PROPRIETARY_DEFLATE = 32946;
+  public static final int DEFLATE = 8;
+  public static final int THUNDERSCAN = 32809;
+  public static final int NIKON = 34713;
+  public static final int LURAWAVE = -1;
+
+  // photometric interpretation types
+  public static final int WHITE_IS_ZERO = 0;
+  public static final int BLACK_IS_ZERO = 1;
+  public static final int RGB = 2;
+  public static final int RGB_PALETTE = 3;
+  public static final int TRANSPARENCY_MASK = 4;
+  public static final int CMYK = 5;
+  public static final int Y_CB_CR = 6;
+  public static final int CIE_LAB = 8;
+  public static final int CFA_ARRAY = -32733;
+
+  // TIFF header constants
+  public static final int MAGIC_NUMBER = 42;
+  public static final int BIG_TIFF_MAGIC_NUMBER = 43;
+  public static final int LITTLE = 0x49;
+  public static final int BIG = 0x4d;
+
+  // -- Constructor --
+
+  private TiffTools() { }
+
+  // -- TiffTools API methods --
+
+  /**
+   * Tests the given data block to see if it represents
+   * the first few bytes of a TIFF file.
+   */
+  public static boolean isValidHeader(byte[] block) {
+    return checkHeader(block) != null;
+  }
+
+  /**
+   * Checks the TIFF header.
+   * @return true if little-endian,
+   *         false if big-endian,
+   *         or null if not a TIFF.
+   */
+  public static Boolean checkHeader(byte[] block) {
+    if (block.length < 4) return null;
+
+    // byte order must be II or MM
+    boolean littleEndian = block[0] == LITTLE && block[1] == LITTLE; // II
+    boolean bigEndian = block[0] == BIG && block[1] == BIG; // MM
+    if (!littleEndian && !bigEndian) return null;
+
+    // check magic number (42)
+    short magic = DataTools.bytesToShort(block, 2, littleEndian);
+    if (magic != MAGIC_NUMBER && magic != BIG_TIFF_MAGIC_NUMBER) return null;
+
+    return new Boolean(littleEndian);
+  }
+
+  /** Gets whether this is a BigTIFF IFD. */
+  public static boolean isBigTiff(Hashtable ifd) throws FormatException {
+    return ((Boolean)
+      getIFDValue(ifd, BIG_TIFF, false, Boolean.class)).booleanValue();
+  }
+
+  /** Gets whether the TIFF information in the given IFD is little-endian. */
+  public static boolean isLittleEndian(Hashtable ifd) throws FormatException {
+    return ((Boolean)
+      getIFDValue(ifd, LITTLE_ENDIAN, true, Boolean.class)).booleanValue();
+  }
+
+  // --------------------------- Reading TIFF files ---------------------------
+
+  // -- IFD parsing methods --
+
+  /**
+   * Gets all IFDs within the given TIFF file, or null
+   * if the given file is not a valid TIFF file.
+   */
+  public static Hashtable[] getIFDs(RandomAccessStream in) throws IOException {
+    // check TIFF header
+    Boolean result = checkHeader(in);
+    if (result == null) return null;
+
+    in.seek(2);
+    boolean bigTiff = in.readShort() == BIG_TIFF_MAGIC_NUMBER;
+
+    long offset = getFirstOffset(in, bigTiff);
+
+    // compute maximum possible number of IFDs, for loop safety
+    // each IFD must have at least one directory entry, which means that
+    // each IFD must be at least 2 + 12 + 4 = 18 bytes in length
+    long ifdMax = (in.length() - 8) / 18;
+
+    // read in IFDs
+    Vector v = new Vector();
+    for (long ifdNum=0; ifdNum<ifdMax; ifdNum++) {
+      Hashtable ifd = getIFD(in, ifdNum, offset, bigTiff);
+      if (ifd == null || ifd.size() <= 1) break;
+      v.add(ifd);
+      offset = bigTiff ? in.readLong() : in.readInt();
+      if (offset <= 0 || offset >= in.length()) break;
+    }
+
+    Hashtable[] ifds = new Hashtable[v.size()];
+    v.copyInto(ifds);
+    return ifds;
+  }
+
+  /**
+   * Gets the first IFD within the given TIFF file, or null
+   * if the given file is not a valid TIFF file.
+   */
+  public static Hashtable getFirstIFD(RandomAccessStream in) throws IOException
+  {
+    // check TIFF header
+    Boolean result = checkHeader(in);
+    if (result == null) return null;
+
+    long offset = getFirstOffset(in);
+
+    return getIFD(in, 0, offset);
+  }
+
+  /**
+   * Retrieve a given entry from the first IFD in a stream.
+   *
+   * @param in the stream to retrieve the entry from.
+   * @param tag the tag of the entry to be retrieved.
+   * @return an object representing the entry's fields.
+   * @throws IOException when there is an error accessing the stream <i>in</i>.
+   */
+  public static TiffIFDEntry getFirstIFDEntry(RandomAccessStream in, int tag)
+    throws IOException
+  {
+    // First lets re-position the file pointer by checking the TIFF header
+    Boolean result = checkHeader(in);
+    if (result == null) return null;
+
+    // Get the offset of the first IFD
+    long offset = getFirstOffset(in);
+
+    // The following loosely resembles the logic of getIFD()...
+    in.seek(offset);
+    int numEntries = in.readShort() & 0xffff;
+
+    for (int i = 0; i < numEntries; i++) {
+      in.seek(offset + // The beginning of the IFD
+        2 + // The width of the initial numEntries field
+        BYTES_PER_ENTRY * i);
+
+      int entryTag = in.readShort() & 0xffff;
+
+      // Skip this tag unless it matches the one we want
+      if (entryTag != tag) continue;
+
+      // Parse the entry's "Type"
+      int entryType = in.readShort() & 0xffff;
+
+      // Parse the entry's "ValueCount"
+      int valueCount = in.readInt();
+      if (valueCount < 0) {
+        throw new RuntimeException("Count of '" + valueCount + "' unexpected.");
+      }
+
+      // Parse the entry's "ValueOffset"
+      int valueOffset = in.readInt();
+
+      return new TiffIFDEntry(entryTag, entryType, valueCount, valueOffset);
+    }
+    throw new UnknownTagException();
+  }
+
+  /**
+   * Checks the TIFF header.
+   * @return true if little-endian,
+   *         false if big-endian,
+   *         or null if not a TIFF.
+   */
+  public static Boolean checkHeader(RandomAccessStream in) throws IOException {
+    if (DEBUG) debug("getIFDs: reading IFD entries");
+
+    // start at the beginning of the file
+    in.seek(0);
+
+    byte[] header = new byte[4];
+    in.readFully(header);
+    Boolean b = checkHeader(header);
+    if (b != null) in.order(b.booleanValue());
+    return b;
+  }
+
+  /**
+   * Gets offset to the first IFD, or -1 if stream is not TIFF.
+   * Assumes the stream is positioned properly (checkHeader just called).
+   */
+  public static long getFirstOffset(RandomAccessStream in)
+    throws IOException
+  {
+    return getFirstOffset(in, false);
+  }
+
+  /**
+   * Gets offset to the first IFD, or -1 if stream is not TIFF.
+   * Assumes the stream is positioned properly (checkHeader just called).
+   * 
+   * @param bigTiff true if this is a BigTIFF file (8 byte pointers).
+   */
+  public static long getFirstOffset(RandomAccessStream in, boolean bigTiff)
+    throws IOException
+  {
+    if (bigTiff) in.skipBytes(4);
+    return bigTiff ? in.readLong() : in.readInt();
+  }
+
+  /** Gets the IFD stored at the given offset. */
+  public static Hashtable getIFD(RandomAccessStream in, long ifdNum,
+    long offset) throws IOException
+  {
+    return getIFD(in, ifdNum, offset, false);
+  }
+
+  /** Gets the IFD stored at the given offset. */
+  public static Hashtable getIFD(RandomAccessStream in,
+    long ifdNum, long offset, boolean bigTiff) throws IOException
+  {
+    Hashtable ifd = new Hashtable();
+
+    // save little-endian flag to internal LITTLE_ENDIAN tag
+    ifd.put(new Integer(LITTLE_ENDIAN), new Boolean(in.isLittleEndian()));
+    ifd.put(new Integer(BIG_TIFF), new Boolean(bigTiff));
+
+    // read in directory entries for this IFD
+    if (DEBUG) {
+      debug("getIFDs: seeking IFD #" + ifdNum + " at " + offset);
+    }
+    in.seek(offset);
+    long numEntries = bigTiff ? in.readLong() : in.readShort() & 0xffff;
+    if (DEBUG) debug("getIFDs: " + numEntries + " directory entries to read");
+    if (numEntries == 0 || numEntries == 1) return ifd;
+
+    int bytesPerEntry = bigTiff ? BIG_TIFF_BYTES_PER_ENTRY : BYTES_PER_ENTRY;
+    int baseOffset = bigTiff ? 8 : 2;
+    int threshhold = bigTiff ? 8 : 4;
+
+    for (int i=0; i<numEntries; i++) {
+      in.seek(offset + baseOffset + bytesPerEntry * i);
+      int tag = in.readShort() & 0xffff;
+      int type = in.readShort() & 0xffff;
+      // BigTIFF case is a slight hack
+      int count = bigTiff ? (int) (in.readLong() & 0xffffffff) : in.readInt();
+
+      if (DEBUG) {
+        debug("getIFDs: read " + getIFDTagName(tag) +
+          " (type=" + getIFDTypeName(type) + "; count=" + count + ")");
+      }
+      if (count < 0) return null; // invalid data
+      Object value = null;
+
+      if (type == BYTE) {
+        // 8-bit unsigned integer
+        if (count > threshhold) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Short(in.readByte());
+        else {
+          short[] bytes = new short[count];
+          for (int j=0; j<count; j++) {
+            bytes[j] = in.readByte();
+            if (bytes[j] < 0) bytes[j] += 255;
+          }
+          value = bytes;
+        }
+      }
+      else if (type == ASCII) {
+        // 8-bit byte that contain a 7-bit ASCII code;
+        // the last byte must be NUL (binary zero)
+        byte[] ascii = new byte[count];
+        if (count > threshhold) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        in.read(ascii);
+
+        // count number of null terminators
+        int nullCount = 0;
+        for (int j=0; j<count; j++) {
+          if (ascii[j] == 0 || j == count - 1) nullCount++;
+        }
+
+        // convert character array to array of strings
+        String[] strings = nullCount == 1 ? null : new String[nullCount];
+        String s = null;
+        int c = 0, ndx = -1;
+        for (int j=0; j<count; j++) {
+          if (ascii[j] == 0) {
+            s = new String(ascii, ndx + 1, j - ndx - 1);
+            ndx = j;
+          }
+          else if (j == count - 1) {
+            // handle non-null-terminated strings
+            s = new String(ascii, ndx + 1, j - ndx);
+          }
+          else s = null;
+          if (strings != null && s != null) strings[c++] = s;
+        }
+        value = strings == null ? (Object) s : strings;
+      }
+      else if (type == SHORT) {
+        // 16-bit (2-byte) unsigned integer
+        if (count > threshhold / 2) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Integer(in.readShort());
+        else {
+          int[] shorts = new int[count];
+          for (int j=0; j<count; j++) {
+            shorts[j] = in.readShort() & 0xffff;
+          }
+          value = shorts;
+        }
+      }
+      else if (type == LONG) {
+        // 32-bit (4-byte) unsigned integer
+        if (count > threshhold / 4) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Long(in.readInt());
+        else {
+          long[] longs = new long[count];
+          for (int j=0; j<count; j++) longs[j] = in.readInt();
+          value = longs;
+        }
+      }
+      else if (type == LONG8 || type == SLONG8 || type == IFD8) {
+        if (count > threshhold / 8) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Long(in.readLong());
+        else {
+          long[] longs = new long[count];
+          for (int j=0; j<count; j++) longs[j] = in.readLong();
+          value = longs;
+        }
+      }
+      else if (type == RATIONAL || type == SRATIONAL) {
+        // Two LONGs: the first represents the numerator of a fraction;
+        // the second, the denominator
+        // Two SLONG's: the first represents the numerator of a fraction,
+        // the second the denominator
+        long pointer = bigTiff ? in.readLong() : in.readInt();
+        if (count > threshhold / 8) in.seek(pointer);
+        if (count == 1) value = new TiffRational(in.readInt(), in.readInt());
+        else {
+          TiffRational[] rationals = new TiffRational[count];
+          for (int j=0; j<count; j++) {
+            rationals[j] = new TiffRational(in.readInt(), in.readInt());
+          }
+          value = rationals;
+        }
+      }
+      else if (type == SBYTE || type == UNDEFINED) {
+        // SBYTE: An 8-bit signed (twos-complement) integer
+        // UNDEFINED: An 8-bit byte that may contain anything,
+        // depending on the definition of the field
+        if (count > threshhold) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Byte(in.readByte());
+        else {
+          byte[] sbytes = new byte[count];
+          in.readFully(sbytes);
+          value = sbytes;
+        }
+      }
+      else if (type == SSHORT) {
+        // A 16-bit (2-byte) signed (twos-complement) integer
+        if (count > threshhold / 2) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Short(in.readShort());
+        else {
+          short[] sshorts = new short[count];
+          for (int j=0; j<count; j++) sshorts[j] = in.readShort();
+          value = sshorts;
+        }
+      }
+      else if (type == SLONG) {
+        // A 32-bit (4-byte) signed (twos-complement) integer
+        if (count > threshhold / 4) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Integer(in.readInt());
+        else {
+          int[] slongs = new int[count];
+          for (int j=0; j<count; j++) slongs[j] = in.readInt();
+          value = slongs;
+        }
+      }
+      else if (type == FLOAT) {
+        // Single precision (4-byte) IEEE format
+        if (count > threshhold / 4) {
+          long pointer = bigTiff ? in.readLong() : in.readInt();
+          in.seek(pointer);
+        }
+        if (count == 1) value = new Float(in.readFloat());
+        else {
+          float[] floats = new float[count];
+          for (int j=0; j<count; j++) floats[j] = in.readFloat();
+          value = floats;
+        }
+      }
+      else if (type == DOUBLE) {
+        // Double precision (8-byte) IEEE format
+        long pointer = bigTiff ? in.readLong() : in.readInt();
+        in.seek(pointer);
+        if (count == 1) value = new Double(in.readDouble());
+        else {
+          double[] doubles = new double[count];
+          for (int j=0; j<count; j++) {
+            doubles[j] = in.readDouble();
+          }
+          value = doubles;
+        }
+      }
+      if (value != null) ifd.put(new Integer(tag), value);
+    }
+    in.seek(offset + baseOffset + bytesPerEntry * numEntries);
+
+    return ifd;
+  }
+
+  /** Gets the name of the IFD tag encoded by the given number. */
+  public static String getIFDTagName(int tag) { return getFieldName(tag); }
+
+  /** Gets the name of the IFD type encoded by the given number. */
+  public static String getIFDTypeName(int type) { return getFieldName(type); }
+
+  /**
+   * This method uses reflection to scan the values of this class's
+   * static fields, returning the first matching field's name. It is
+   * probably not very efficient, and is mainly intended for debugging.
+   */
+  public static String getFieldName(int value) {
+    Field[] fields = TiffTools.class.getFields();
+    for (int i=0; i<fields.length; i++) {
+      try {
+        if (fields[i].getInt(null) == value) return fields[i].getName();
+      }
+      catch (IllegalAccessException exc) { }
+      catch (IllegalArgumentException exc) { }
+    }
+    return "" + value;
+  }
+
+  /** Gets the given directory entry value from the specified IFD. */
+  public static Object getIFDValue(Hashtable ifd, int tag) {
+    return ifd.get(new Integer(tag));
+  }
+
+  /**
+   * Gets the given directory entry value from the specified IFD,
+   * performing some error checking.
+   */
+  public static Object getIFDValue(Hashtable ifd,
+    int tag, boolean checkNull, Class checkClass) throws FormatException
+  {
+    Object value = ifd.get(new Integer(tag));
+    if (checkNull && value == null) {
+      throw new FormatException(
+        getIFDTagName(tag) + " directory entry not found");
+    }
+    if (checkClass != null && value != null &&
+      !checkClass.isInstance(value))
+    {
+      // wrap object in array of length 1, if appropriate
+      Class cType = checkClass.getComponentType();
+      Object array = null;
+      if (cType == value.getClass()) {
+        array = Array.newInstance(value.getClass(), 1);
+        Array.set(array, 0, value);
+      }
+      if (cType == boolean.class && value instanceof Boolean) {
+        array = Array.newInstance(boolean.class, 1);
+        Array.setBoolean(array, 0, ((Boolean) value).booleanValue());
+      }
+      else if (cType == byte.class && value instanceof Byte) {
+        array = Array.newInstance(byte.class, 1);
+        Array.setByte(array, 0, ((Byte) value).byteValue());
+      }
+      else if (cType == char.class && value instanceof Character) {
+        array = Array.newInstance(char.class, 1);
+        Array.setChar(array, 0, ((Character) value).charValue());
+      }
+      else if (cType == double.class && value instanceof Double) {
+        array = Array.newInstance(double.class, 1);
+        Array.setDouble(array, 0, ((Double) value).doubleValue());
+      }
+      else if (cType == float.class && value instanceof Float) {
+        array = Array.newInstance(float.class, 1);
+        Array.setFloat(array, 0, ((Float) value).floatValue());
+      }
+      else if (cType == int.class && value instanceof Integer) {
+        array = Array.newInstance(int.class, 1);
+        Array.setInt(array, 0, ((Integer) value).intValue());
+      }
+      else if (cType == long.class && value instanceof Long) {
+        array = Array.newInstance(long.class, 1);
+        Array.setLong(array, 0, ((Long) value).longValue());
+      }
+      else if (cType == short.class && value instanceof Short) {
+        array = Array.newInstance(short.class, 1);
+        Array.setShort(array, 0, ((Short) value).shortValue());
+      }
+      if (array != null) return array;
+
+      throw new FormatException(getIFDTagName(tag) +
+        " directory entry is the wrong type (got " +
+        value.getClass().getName() + ", expected " + checkClass.getName());
+    }
+    return value;
+  }
+
+  /**
+   * Gets the given directory entry value in long format from the
+   * specified IFD, performing some error checking.
+   */
+  public static long getIFDLongValue(Hashtable ifd, int tag,
+    boolean checkNull, long defaultValue) throws FormatException
+  {
+    long value = defaultValue;
+    Number number = (Number) getIFDValue(ifd, tag, checkNull, Number.class);
+    if (number != null) value = number.longValue();
+    return value;
+  }
+
+  /**
+   * Gets the given directory entry value in int format from the
+   * specified IFD, or -1 if the given directory does not exist.
+   */
+  public static int getIFDIntValue(Hashtable ifd, int tag) {
+    int value = -1;
+    try {
+      value = getIFDIntValue(ifd, tag, false, -1);
+    }
+    catch (FormatException exc) { }
+    return value;
+  }
+
+  /**
+   * Gets the given directory entry value in int format from the
+   * specified IFD, performing some error checking.
+   */
+  public static int getIFDIntValue(Hashtable ifd, int tag,
+    boolean checkNull, int defaultValue) throws FormatException
+  {
+    int value = defaultValue;
+    Number number = (Number) getIFDValue(ifd, tag, checkNull, Number.class);
+    if (number != null) value = number.intValue();
+    return value;
+  }
+
+  /**
+   * Gets the given directory entry value in rational format from the
+   * specified IFD, performing some error checking.
+   */
+  public static TiffRational getIFDRationalValue(Hashtable ifd, int tag,
+    boolean checkNull) throws FormatException
+  {
+    return (TiffRational) getIFDValue(ifd, tag, checkNull, TiffRational.class);
+  }
+
+  /**
+   * Gets the given directory entry values in long format
+   * from the specified IFD, performing some error checking.
+   */
+  public static long[] getIFDLongArray(Hashtable ifd,
+    int tag, boolean checkNull) throws FormatException
+  {
+    Object value = getIFDValue(ifd, tag, checkNull, null);
+    long[] results = null;
+    if (value instanceof long[]) results = (long[]) value;
+    else if (value instanceof Number) {
+      results = new long[] {((Number) value).longValue()};
+    }
+    else if (value instanceof Number[]) {
+      Number[] numbers = (Number[]) value;
+      results = new long[numbers.length];
+      for (int i=0; i<results.length; i++) results[i] = numbers[i].longValue();
+    }
+    else if (value instanceof int[]) { // convert int[] to long[]
+      int[] integers = (int[]) value;
+      results = new long[integers.length];
+      for (int i=0; i<integers.length; i++) results[i] = integers[i];
+    }
+    else if (value != null) {
+      throw new FormatException(getIFDTagName(tag) +
+        " directory entry is the wrong type (got " +
+        value.getClass().getName() +
+        ", expected Number, long[], Number[] or int[])");
+    }
+    return results;
+  }
+
+  /**
+   * Gets the given directory entry values in int format
+   * from the specified IFD, performing some error checking.
+   */
+  public static int[] getIFDIntArray(Hashtable ifd,
+    int tag, boolean checkNull) throws FormatException
+  {
+    Object value = getIFDValue(ifd, tag, checkNull, null);
+    int[] results = null;
+    if (value instanceof int[]) results = (int[]) value;
+    else if (value instanceof Number) {
+      results = new int[] {((Number) value).intValue()};
+    }
+    else if (value instanceof Number[]) {
+      Number[] numbers = (Number[]) value;
+      results = new int[numbers.length];
+      for (int i=0; i<results.length; i++) results[i] = numbers[i].intValue();
+    }
+    else if (value != null) {
+      throw new FormatException(getIFDTagName(tag) +
+        " directory entry is the wrong type (got " +
+        value.getClass().getName() + ", expected Number, int[] or Number[])");
+    }
+    return results;
+  }
+
+  /**
+   * Gets the given directory entry values in short format
+   * from the specified IFD, performing some error checking.
+   */
+  public static short[] getIFDShortArray(Hashtable ifd,
+    int tag, boolean checkNull) throws FormatException
+  {
+    Object value = getIFDValue(ifd, tag, checkNull, null);
+    short[] results = null;
+    if (value instanceof short[]) results = (short[]) value;
+    else if (value instanceof Number) {
+      results = new short[] {((Number) value).shortValue()};
+    }
+    else if (value instanceof Number[]) {
+      Number[] numbers = (Number[]) value;
+      results = new short[numbers.length];
+      for (int i=0; i<results.length; i++) {
+        results[i] = numbers[i].shortValue();
+      }
+    }
+    else if (value != null) {
+      throw new FormatException(getIFDTagName(tag) +
+        " directory entry is the wrong type (got " +
+        value.getClass().getName() +
+        ", expected Number, short[] or Number[])");
+    }
+    return results;
+  }
+
+  /** Convenience method for obtaining a file's first ImageDescription. */
+  public static String getComment(String id)
+    throws FormatException, IOException
+  {
+    // read first IFD
+    RandomAccessStream in = new RandomAccessStream(id);
+    Hashtable ifd = TiffTools.getFirstIFD(in);
+    in.close();
+
+    // extract comment
+    Object o = TiffTools.getIFDValue(ifd, TiffTools.IMAGE_DESCRIPTION);
+    String comment = null;
+    if (o instanceof String) comment = (String) o;
+    else if (o instanceof String[]) {
+      String[] s = (String[]) o;
+      if (s.length > 0) comment = s[0];
+    }
+    else if (o != null) comment = o.toString();
+
+    if (comment != null) {
+      // sanitize line feeds
+      comment = comment.replaceAll("\r\n", "\n");
+      comment = comment.replaceAll("\r", "\n");
+    }
+    return comment;
+  }
+
+  // -- Image reading methods --
+
+  /** Reads the image defined in the given IFD from the specified file. */
+  public static byte[][] getSamples(Hashtable ifd, RandomAccessStream in)
+    throws FormatException, IOException
+  {
+    int samplesPerPixel = getSamplesPerPixel(ifd);
+    int photoInterp = getPhotometricInterpretation(ifd);
+    int bpp = getBitsPerSample(ifd)[0];
+    while ((bpp % 8) != 0) bpp++;
+    bpp /= 8;
+    long width = getImageWidth(ifd);
+    long length = getImageLength(ifd);
+    byte[] b = new byte[(int) (width * length * samplesPerPixel * bpp)];
+
+    getSamples(ifd, in, b);
+    byte[][] samples = new byte[samplesPerPixel][(int) (width * length * bpp)];
+    for (int i=0; i<samplesPerPixel; i++) {
+      System.arraycopy(b, (int) (i*width*length*bpp), samples[i], 0,
+        samples[i].length);
+    }
+    b = null;
+    return samples;
+  }
+
+  public static byte[] getSamples(Hashtable ifd, RandomAccessStream in,
+    byte[] buf) throws FormatException, IOException
+  {
+    if (DEBUG) debug("parsing IFD entries");
+
+    // get internal non-IFD entries
+    boolean littleEndian = isLittleEndian(ifd);
+    in.order(littleEndian);
+
+    // get relevant IFD entries
+    long imageWidth = getImageWidth(ifd);
+    long imageLength = getImageLength(ifd);
+    int[] bitsPerSample = getBitsPerSample(ifd);
+    int samplesPerPixel = getSamplesPerPixel(ifd);
+    int compression = getCompression(ifd);
+    int photoInterp = getPhotometricInterpretation(ifd);
+    long[] stripOffsets = getStripOffsets(ifd);
+    long[] stripByteCounts = getStripByteCounts(ifd);
+    long[] rowsPerStripArray = getRowsPerStrip(ifd);
+
+    boolean fakeByteCounts = stripByteCounts == null;
+    boolean fakeRPS = rowsPerStripArray == null;
+    boolean isTiled = stripOffsets == null;
+
+    long[] maxes = getIFDLongArray(ifd, MAX_SAMPLE_VALUE, false);
+    long maxValue = maxes == null ? 0 : maxes[0];
+
+    if (isTiled) {
+      stripOffsets = getIFDLongArray(ifd, TILE_OFFSETS, true);
+      stripByteCounts = getIFDLongArray(ifd, TILE_BYTE_COUNTS, true);
+      rowsPerStripArray = new long[] {imageLength};
+    }
+    else if (fakeByteCounts) {
+      // technically speaking, this shouldn't happen (since TIFF writers are
+      // required to write the StripByteCounts tag), but we'll support it
+      // anyway
+
+      // don't rely on RowsPerStrip, since it's likely that if the file doesn't
+      // have the StripByteCounts tag, it also won't have the RowsPerStrip tag
+      stripByteCounts = new long[stripOffsets.length];
+      if (stripByteCounts.length == 1) {
+        stripByteCounts[0] = imageWidth * imageLength * (bitsPerSample[0] / 8);
+      }
+      else {
+        stripByteCounts[0] = stripOffsets[0];
+        for (int i=1; i<stripByteCounts.length; i++) {
+          stripByteCounts[i] = stripOffsets[i] - stripByteCounts[i-1];
+        }
+      }
+    }
+
+    boolean lastBitsZero = bitsPerSample[bitsPerSample.length - 1] == 0;
+
+    if (fakeRPS && !isTiled) {
+      // create a false rowsPerStripArray if one is not present
+      // it's sort of a cheap hack, but here's how it's done:
+      // RowsPerStrip = stripByteCounts / (imageLength * bitsPerSample)
+      // since stripByteCounts and bitsPerSample are arrays, we have to
+      // iterate through each item
+
+      rowsPerStripArray = new long[bitsPerSample.length];
+
+      long temp = stripByteCounts[0];
+      stripByteCounts = new long[bitsPerSample.length];
+      for (int i=0; i<stripByteCounts.length; i++) stripByteCounts[i] = temp;
+      temp = bitsPerSample[0];
+      if (temp == 0) temp = 8;
+      bitsPerSample = new int[bitsPerSample.length];
+      for (int i=0; i<bitsPerSample.length; i++) bitsPerSample[i] = (int) temp;
+      temp = stripOffsets[0];
+      /*
+      stripOffsets = new long[bitsPerSample.length];
+      for (int i=0; i<bitsPerSample.length; i++) {
+        stripOffsets[i] = i == 0 ? temp :
+          stripOffsets[i - 1] + stripByteCounts[i];
+      }
+      */
+
+      // we have two files that reverse the endianness for BitsPerSample,
+      // StripOffsets, and StripByteCounts
+
+      if (bitsPerSample[0] > 64) {
+        byte[] bps = new byte[2];
+        byte[] stripOffs = new byte[4];
+        byte[] byteCounts = new byte[4];
+        if (littleEndian) {
+          bps[0] = (byte) (bitsPerSample[0] & 0xff);
+          bps[1] = (byte) ((bitsPerSample[0] >>> 8) & 0xff);
+
+          int ndx = stripOffsets.length - 1;
+
+          stripOffs[0] = (byte) (stripOffsets[ndx] & 0xff);
+          stripOffs[1] = (byte) ((stripOffsets[ndx] >>> 8) & 0xff);
+          stripOffs[2] = (byte) ((stripOffsets[ndx] >>> 16) & 0xff);
+          stripOffs[3] = (byte) ((stripOffsets[ndx] >>> 24) & 0xff);
+
+          ndx = stripByteCounts.length - 1;
+
+          byteCounts[0] = (byte) (stripByteCounts[ndx] & 0xff);
+          byteCounts[1] = (byte) ((stripByteCounts[ndx] >>> 8) & 0xff);
+          byteCounts[2] = (byte) ((stripByteCounts[ndx] >>> 16) & 0xff);
+          byteCounts[3] = (byte) ((stripByteCounts[ndx] >>> 24) & 0xff);
+        }
+        else {
+          bps[1] = (byte) ((bitsPerSample[0] >>> 16) & 0xff);
+          bps[0] = (byte) ((bitsPerSample[0] >>> 24) & 0xff);
+
+          stripOffs[3] = (byte) (stripOffsets[0] & 0xff);
+          stripOffs[2] = (byte) ((stripOffsets[0] >>> 8) & 0xff);
+          stripOffs[1] = (byte) ((stripOffsets[0] >>> 16) & 0xff);
+          stripOffs[0] = (byte) ((stripOffsets[0] >>> 24) & 0xff);
+
+          byteCounts[3] = (byte) (stripByteCounts[0] & 0xff);
+          byteCounts[2] = (byte) ((stripByteCounts[0] >>> 8) & 0xff);
+          byteCounts[1] = (byte) ((stripByteCounts[0] >>> 16) & 0xff);
+          byteCounts[0] = (byte) ((stripByteCounts[0] >>> 24) & 0xff);
+        }
+
+        bitsPerSample[0] = DataTools.bytesToInt(bps, !littleEndian);
+        stripOffsets[0] = DataTools.bytesToInt(stripOffs, !littleEndian);
+        stripByteCounts[0] = DataTools.bytesToInt(byteCounts, !littleEndian);
+      }
+
+      if (rowsPerStripArray.length == 1 && stripByteCounts[0] !=
+        (imageWidth * imageLength * (bitsPerSample[0] / 8)) &&
+        compression == UNCOMPRESSED)
+      {
+        for (int i=0; i<stripByteCounts.length; i++) {
+          stripByteCounts[i] =
+            imageWidth * imageLength * (bitsPerSample[i] / 8);
+          stripOffsets[0] = (int) (in.length() - stripByteCounts[0] -
+            48 * imageWidth);
+          if (i != 0) {
+            stripOffsets[i] = stripOffsets[i - 1] + stripByteCounts[i];
+          }
+
+          in.seek((int) stripOffsets[i]);
+          in.read(buf, (int) (i*imageWidth), (int) imageWidth);
+          boolean isZero = true;
+          for (int j=0; j<imageWidth; j++) {
+            if (buf[(int) (i*imageWidth + j)] != 0) {
+              isZero = false;
+              break;
+            }
+          }
+
+          while (isZero) {
+            stripOffsets[i] -= imageWidth;
+            in.seek((int) stripOffsets[i]);
+            in.read(buf, (int) (i*imageWidth), (int) imageWidth);
+            for (int j=0; j<imageWidth; j++) {
+              if (buf[(int) (i*imageWidth + j)] != 0) {
+                isZero = false;
+                stripOffsets[i] -= (stripByteCounts[i] - imageWidth);
+                break;
+              }
+            }
+          }
+        }
+      }
+
+      for (int i=0; i<bitsPerSample.length; i++) {
+        // case 1: we're still within bitsPerSample array bounds
+        if (i < bitsPerSample.length) {
+          if (i == samplesPerPixel) {
+            bitsPerSample[i] = 0;
+            lastBitsZero = true;
+          }
+
+          // remember that the universe collapses when we divide by 0
+          if (bitsPerSample[i] != 0) {
+            rowsPerStripArray[i] = (long) stripByteCounts[i] /
+              (imageWidth * (bitsPerSample[i] / 8));
+          }
+          else if (bitsPerSample[i] == 0 && i > 0) {
+            rowsPerStripArray[i] = (long) stripByteCounts[i] /
+              (imageWidth * (bitsPerSample[i - 1] / 8));
+            bitsPerSample[i] = bitsPerSample[i - 1];
+          }
+          else {
+            throw new FormatException("BitsPerSample is 0");
+          }
+        }
+        // case 2: we're outside bitsPerSample array bounds
+        else if (i >= bitsPerSample.length) {
+          rowsPerStripArray[i] = (long) stripByteCounts[i] /
+            (imageWidth * (bitsPerSample[bitsPerSample.length - 1] / 8));
+        }
+      }
+
+      //samplesPerPixel = stripOffsets.length;
+    }
+
+    if (lastBitsZero) {
+      bitsPerSample[bitsPerSample.length - 1] = 0;
+      //samplesPerPixel--;
+    }
+
+    TiffRational xResolution = getIFDRationalValue(ifd, X_RESOLUTION, false);
+    TiffRational yResolution = getIFDRationalValue(ifd, Y_RESOLUTION, false);
+    int planarConfig = getIFDIntValue(ifd, PLANAR_CONFIGURATION, false, 1);
+    int resolutionUnit = getIFDIntValue(ifd, RESOLUTION_UNIT, false, 2);
+    if (xResolution == null || yResolution == null) resolutionUnit = 0;
+    int[] colorMap = getIFDIntArray(ifd, COLOR_MAP, false);
+    int predictor = getIFDIntValue(ifd, PREDICTOR, false, 1);
+
+    // If the subsequent color maps are empty, use the first IFD's color map
+    //if (colorMap == null) {
+    //  colorMap = getIFDIntArray(getFirstIFD(in), COLOR_MAP, false);
+    //}
+
+    // use special color map for YCbCr
+    if (photoInterp == Y_CB_CR) {
+      int[] tempColorMap = getIFDIntArray(ifd, Y_CB_CR_COEFFICIENTS, false);
+      int[] refBlackWhite = getIFDIntArray(ifd, REFERENCE_BLACK_WHITE, false);
+      colorMap = new int[tempColorMap.length + refBlackWhite.length];
+      System.arraycopy(tempColorMap, 0, colorMap, 0, tempColorMap.length);
+      System.arraycopy(refBlackWhite, 0, colorMap, tempColorMap.length,
+        refBlackWhite.length);
+    }
+
+    if (DEBUG) {
+      StringBuffer sb = new StringBuffer();
+      sb.append("IFD directory entry values:");
+      sb.append("\n\tLittleEndian=");
+      sb.append(littleEndian);
+      sb.append("\n\tImageWidth=");
+      sb.append(imageWidth);
+      sb.append("\n\tImageLength=");
+      sb.append(imageLength);
+      sb.append("\n\tBitsPerSample=");
+      sb.append(bitsPerSample[0]);
+      for (int i=1; i<bitsPerSample.length; i++) {
+        sb.append(",");
+        sb.append(bitsPerSample[i]);
+      }
+      sb.append("\n\tSamplesPerPixel=");
+      sb.append(samplesPerPixel);
+      sb.append("\n\tCompression=");
+      sb.append(compression);
+      sb.append("\n\tPhotometricInterpretation=");
+      sb.append(photoInterp);
+      sb.append("\n\tStripOffsets=");
+      sb.append(stripOffsets[0]);
+      for (int i=1; i<stripOffsets.length; i++) {
+        sb.append(",");
+        sb.append(stripOffsets[i]);
+      }
+      sb.append("\n\tRowsPerStrip=");
+      sb.append(rowsPerStripArray[0]);
+      for (int i=1; i<rowsPerStripArray.length; i++) {
+        sb.append(",");
+        sb.append(rowsPerStripArray[i]);
+      }
+      sb.append("\n\tStripByteCounts=");
+      sb.append(stripByteCounts[0]);
+      for (int i=1; i<stripByteCounts.length; i++) {
+        sb.append(",");
+        sb.append(stripByteCounts[i]);
+      }
+      sb.append("\n\tXResolution=");
+      sb.append(xResolution);
+      sb.append("\n\tYResolution=");
+      sb.append(yResolution);
+      sb.append("\n\tPlanarConfiguration=");
+      sb.append(planarConfig);
+      sb.append("\n\tResolutionUnit=");
+      sb.append(resolutionUnit);
+      sb.append("\n\tColorMap=");
+      if (colorMap == null) sb.append("null");
+      else {
+        sb.append(colorMap[0]);
+        for (int i=1; i<colorMap.length; i++) {
+          sb.append(",");
+          sb.append(colorMap[i]);
+        }
+      }
+      sb.append("\n\tPredictor=");
+      sb.append(predictor);
+      debug(sb.toString());
+    }
+
+    for (int i=0; i<samplesPerPixel; i++) {
+      if (bitsPerSample[i] < 1) {
+        throw new FormatException("Illegal BitsPerSample (" +
+          bitsPerSample[i] + ")");
+      }
+      // don't support odd numbers of bits (except for 1)
+      else if (bitsPerSample[i] % 2 != 0 && bitsPerSample[i] != 1) {
+        throw new FormatException("Sorry, unsupported BitsPerSample (" +
+          bitsPerSample[i] + ")");
+      }
+    }
+
+    if (bitsPerSample.length < samplesPerPixel) {
+      throw new FormatException("BitsPerSample length (" +
+        bitsPerSample.length + ") does not match SamplesPerPixel (" +
+        samplesPerPixel + ")");
+    }
+    else if (photoInterp == TRANSPARENCY_MASK) {
+      throw new FormatException(
+        "Sorry, Transparency Mask PhotometricInterpretation is not supported");
+    }
+    else if (photoInterp == Y_CB_CR) {
+      throw new FormatException(
+        "Sorry, YCbCr PhotometricInterpretation is not supported");
+    }
+    else if (photoInterp == CIE_LAB) {
+      throw new FormatException(
+        "Sorry, CIELAB PhotometricInterpretation is not supported");
+    }
+    else if (photoInterp != WHITE_IS_ZERO &&
+      photoInterp != BLACK_IS_ZERO && photoInterp != RGB &&
+      photoInterp != RGB_PALETTE && photoInterp != CMYK &&
+      photoInterp != Y_CB_CR && photoInterp != CFA_ARRAY)
+    {
+      throw new FormatException("Unknown PhotometricInterpretation (" +
+        photoInterp + ")");
+    }
+
+    long rowsPerStrip = rowsPerStripArray[0];
+    for (int i=1; i<rowsPerStripArray.length; i++) {
+      if (rowsPerStrip != rowsPerStripArray[i]) {
+        throw new FormatException(
+          "Sorry, non-uniform RowsPerStrip is not supported");
+      }
+    }
+
+    long numStrips = (imageLength + rowsPerStrip - 1) / rowsPerStrip;
+
+    if (isTiled || fakeRPS) numStrips = stripOffsets.length;
+    if (planarConfig == 2) numStrips *= samplesPerPixel;
+
+    if (stripOffsets.length < numStrips && !fakeRPS) {
+      throw new FormatException("StripOffsets length (" +
+        stripOffsets.length + ") does not match expected " +
+        "number of strips (" + numStrips + ")");
+    }
+    else if (fakeRPS) numStrips = stripOffsets.length;
+
+    if (stripByteCounts.length < numStrips) {
+      throw new FormatException("StripByteCounts length (" +
+        stripByteCounts.length + ") does not match expected " +
+        "number of strips (" + numStrips + ")");
+    }
+
+    if (imageWidth > Integer.MAX_VALUE || imageLength > Integer.MAX_VALUE ||
+      imageWidth * imageLength > Integer.MAX_VALUE)
+    {
+      throw new FormatException("Sorry, ImageWidth x ImageLength > " +
+        Integer.MAX_VALUE + " is not supported (" +
+        imageWidth + " x " + imageLength + ")");
+    }
+    int numSamples = (int) (imageWidth * imageLength);
+
+    if (planarConfig != 1 && planarConfig != 2) {
+      throw new FormatException(
+        "Unknown PlanarConfiguration (" + planarConfig + ")");
+    }
+
+    // read in image strips
+    if (DEBUG) {
+      debug("reading image data (samplesPerPixel=" +
+        samplesPerPixel + "; numSamples=" + numSamples + ")");
+    }
+
+    if (photoInterp == CFA_ARRAY) {
+      int[] tempMap = new int[colorMap.length + 2];
+      System.arraycopy(colorMap, 0, tempMap, 0, colorMap.length);
+      tempMap[tempMap.length - 2] = (int) imageWidth;
+      tempMap[tempMap.length - 1] = (int) imageLength;
+      colorMap = tempMap;
+    }
+
+    if (stripOffsets.length > 1 && (stripOffsets[stripOffsets.length - 1] ==
+      stripOffsets[stripOffsets.length - 2]))
+    {
+      long[] tmp = stripOffsets;
+      stripOffsets = new long[tmp.length - 1];
+      System.arraycopy(tmp, 0, stripOffsets, 0, stripOffsets.length);
+      numStrips--;
+    }
+
+    short[][] samples = new short[samplesPerPixel][numSamples];
+    byte[] altBytes = new byte[0];
+
+    if (bitsPerSample[0] == 16) littleEndian = !littleEndian;
+
+    if (isTiled) {
+      long tileWidth = getIFDLongValue(ifd, TILE_WIDTH, true, 0);
+      long tileLength = getIFDLongValue(ifd, TILE_LENGTH, true, 0);
+
+      byte[] data = new byte[(int) (imageWidth * imageLength *
+        samplesPerPixel * (bitsPerSample[0] / 8))];
+
+      int row = 0;
+      int col = 0;
+
+      int bytes = bitsPerSample[0] / 8;
+
+      for (int i=0; i<stripOffsets.length; i++) {
+        byte[] b = new byte[(int) stripByteCounts[i]];
+        in.seek(stripOffsets[i]);
+        in.read(b);
+
+        b = uncompress(b, compression);
+
+        int ext = (int) (b.length / (tileWidth * tileLength));
+        int rowBytes = (int) (tileWidth * ext);
+        if (tileWidth + col > imageWidth) {
+          rowBytes = (int) ((imageWidth - col) * ext);
+        }
+
+        for (int j=0; j<tileLength; j++) {
+          if (row + j < imageLength) {
+            System.arraycopy(b, rowBytes*j, data,
+              (int) ((row + j)*imageWidth*ext + ext*col), rowBytes);
+          }
+          else break;
+        }
+
+        // update row and column
+
+        col += (int) tileWidth;
+        if (col >= imageWidth) {
+          row += (int) tileLength;
+          col = 0;
+        }
+      }
+
+      undifference(data, bitsPerSample, imageWidth, planarConfig, predictor);
+      unpackBytes(samples, 0, data, bitsPerSample, photoInterp, colorMap,
+        littleEndian, maxValue, planarConfig, 0, 1, imageWidth);
+    }
+    else {
+      int overallOffset = 0;
+
+      for (int strip=0, row=0; strip<numStrips; strip++, row+=rowsPerStrip) {
+        try {
+          if (DEBUG) debug("reading image strip #" + strip);
+          in.seek((int) stripOffsets[strip]);
+
+          if (stripByteCounts[strip] > Integer.MAX_VALUE) {
+            throw new FormatException("Sorry, StripByteCounts > " +
+              Integer.MAX_VALUE + " is not supported");
+          }
+          byte[] bytes = new byte[(int) stripByteCounts[strip]];
+          in.read(bytes);
+          if (compression != PACK_BITS) {
+            bytes = uncompress(bytes, compression);
+            undifference(bytes, bitsPerSample,
+              imageWidth, planarConfig, predictor);
+            int offset = (int) (imageWidth * row);
+            if (planarConfig == 2) {
+              offset = overallOffset / samplesPerPixel;
+            }
+            unpackBytes(samples, offset, bytes, bitsPerSample,
+              photoInterp, colorMap, littleEndian, maxValue, planarConfig,
+              strip, (int) numStrips, imageWidth);
+            overallOffset += bytes.length / bitsPerSample.length;
+          }
+          else {
+            // concatenate contents of bytes to altBytes
+            byte[] tempPackBits = new byte[altBytes.length];
+            System.arraycopy(altBytes, 0, tempPackBits, 0, altBytes.length);
+            altBytes = new byte[altBytes.length + bytes.length];
+            System.arraycopy(tempPackBits, 0, altBytes, 0, tempPackBits.length);
+            System.arraycopy(bytes, 0, altBytes,
+              tempPackBits.length, bytes.length);
+          }
+        }
+        catch (Exception e) {
+          // CTR TODO - eliminate catch-all exception handling
+          if (strip == 0) {
+            if (e instanceof FormatException) throw (FormatException) e;
+            else throw new FormatException(e);
+          }
+          byte[] bytes = new byte[samples[0].length];
+          undifference(bytes, bitsPerSample, imageWidth, planarConfig,
+            predictor);
+          int offset = (int) (imageWidth * row);
+          if (planarConfig == 2) offset = overallOffset / samplesPerPixel;
+          unpackBytes(samples, offset, bytes, bitsPerSample, photoInterp,
+            colorMap, littleEndian, maxValue, planarConfig,
+            strip, (int) numStrips, imageWidth);
+          overallOffset += bytes.length / bitsPerSample.length;
+        }
+      }
+    }
+
+    // only do this if the image uses PackBits compression
+    if (altBytes.length != 0) {
+      altBytes = uncompress(altBytes, compression);
+      undifference(altBytes, bitsPerSample,
+        imageWidth, planarConfig, predictor);
+      unpackBytes(samples, (int) imageWidth, altBytes, bitsPerSample,
+        photoInterp, colorMap, littleEndian, maxValue, planarConfig, 0, 1,
+        imageWidth);
+    }
+
+    // construct field
+    if (DEBUG) debug("constructing image");
+
+    // Since the lowest common denominator for all pixel operations is "byte"
+    // we're going to normalize everything to byte.
+
+    if (bitsPerSample[0] == 12) bitsPerSample[0] = 16;
+
+    if (photoInterp == CFA_ARRAY) {
+      samples =
+        ImageTools.demosaic(samples, (int) imageWidth, (int) imageLength);
+    }
+
+    if (bitsPerSample[0] == 16) {
+      int pt = 0;
+      for (int i = 0; i < samplesPerPixel; i++) {
+        for (int j = 0; j < numSamples; j++) {
+          buf[pt++] = (byte) ((samples[i][j] & 0xff00) >> 8);
+          buf[pt++] = (byte) (samples[i][j] & 0xff);
+        }
+      }
+    }
+    else if (bitsPerSample[0] == 32) {
+      int pt = 0;
+      for (int i=0; i<samplesPerPixel; i++) {
+        for (int j=0; j<numSamples; j++) {
+          buf[pt++] = (byte) ((samples[i][j] & 0xff000000) >> 24);
+          buf[pt++] = (byte) ((samples[i][j] & 0xff0000) >> 16);
+          buf[pt++] = (byte) ((samples[i][j] & 0xff00) >> 8);
+          buf[pt++] = (byte) (samples[i][j] & 0xff);
+        }
+      }
+    }
+    else {
+      for (int i=0; i<samplesPerPixel; i++) {
+        for (int j=0; j<numSamples; j++) {
+          buf[j + i*numSamples] = (byte) samples[i][j];
+        }
+      }
+    }
+
+    return buf;
+  }
+
+  /** Reads the image defined in the given IFD from the specified file. */
+  public static BufferedImage getImage(Hashtable ifd, RandomAccessStream in)
+    throws FormatException, IOException
+  {
+    // construct field
+    if (DEBUG) debug("constructing image");
+
+    byte[][] samples = getSamples(ifd, in);
+    int[] bitsPerSample = getBitsPerSample(ifd);
+    long imageWidth = getImageWidth(ifd);
+    long imageLength = getImageLength(ifd);
+    int samplesPerPixel = getSamplesPerPixel(ifd);
+    int photoInterp = getPhotometricInterpretation(ifd);
+
+    if (bitsPerSample[0] == 16 || bitsPerSample[0] == 12) {
+      // First wrap the byte arrays and then use the features of the
+      // ByteBuffer to transform to a ShortBuffer. Finally, use the ShortBuffer
+      // bulk get method to copy the data into a usable form for makeImage().
+      int len = samples.length == 2 ? 3 : samples.length;
+
+      short[][] sampleData = new short[len][samples[0].length / 2];
+      for (int i = 0; i < samplesPerPixel; i++) {
+        ShortBuffer sampleBuf = ByteBuffer.wrap(samples[i]).asShortBuffer();
+        sampleBuf.get(sampleData[i]);
+      }
+
+      // Now make our image.
+      return ImageTools.makeImage(sampleData,
+        (int) imageWidth, (int) imageLength);
+    }
+    else if (bitsPerSample[0] == 24) {
+      int[][] intData = new int[samplesPerPixel][samples[0].length / 3];
+      for (int i=0; i<samplesPerPixel; i++) {
+        for (int j=0; j<intData[i].length; j++) {
+          intData[i][j] = DataTools.bytesToInt(samples[i], j*3, 3,
+            isLittleEndian(ifd));
+        }
+      }
+      return ImageTools.makeImage(intData, (int) imageWidth, (int) imageLength);
+    }
+    else if (bitsPerSample[0] == 32) {
+      int type = getIFDIntValue(ifd, SAMPLE_FORMAT);
+      if (type == 3) {
+        // float data
+        float[][] floatData = new float[samplesPerPixel][samples[0].length / 4];
+        for (int i=0; i<samplesPerPixel; i++) {
+          floatData[i] = (float[]) DataTools.makeDataArray(samples[i], 4, true,
+            isLittleEndian(ifd));
+        }
+        return ImageTools.makeImage(floatData,
+          (int) imageWidth, (int) imageLength);
+      }
+      else {
+        // int data
+        int[][] intData = new int[samplesPerPixel][samples[0].length / 4];
+        for (int i=0; i<samplesPerPixel; i++) {
+          IntBuffer sampleBuf = ByteBuffer.wrap(samples[i]).asIntBuffer();
+          sampleBuf.get(intData[i]);
+        }
+
+        byte[][] shortData = new byte[samplesPerPixel][intData[0].length];
+        for (int i=0; i<samplesPerPixel; i++) {
+          for (int j=0; j<shortData[0].length; j++) {
+            shortData[i][j] = (byte) intData[i][j];
+          }
+        }
+
+        return ImageTools.makeImage(shortData,
+          (int) imageWidth, (int) imageLength);
+      }
+    }
+    if (samplesPerPixel == 1) {
+      return ImageTools.makeImage(samples[0], (int) imageWidth,
+        (int) imageLength, 1, false);
+    }
+
+    return ImageTools.makeImage(samples, (int) imageWidth, (int) imageLength);
+  }
+
+  /**
+   * Extracts pixel information from the given byte array according to the
+   * bits per sample, photometric interpretation, and the specified byte
+   * ordering.
+   * No error checking is performed.
+   * This method is tailored specifically for planar (separated) images.
+   */
+  public static void planarUnpack(short[][] samples, int startIndex,
+    byte[] bytes, int[] bitsPerSample, int photoInterp, boolean littleEndian,
+    int strip, int numStrips) throws FormatException
+  {
+    int numChannels = bitsPerSample.length;  // this should always be 3
+    if (bitsPerSample[bitsPerSample.length - 1] == 0) numChannels--;
+
+    // determine which channel the strip belongs to
+
+    int channelNum = strip / (numStrips / numChannels);
+
+    startIndex = (strip % (numStrips / numChannels)) * bytes.length;
+
+    int index = 0;
+    int counter = 0;
+    for (int j=0; j<bytes.length; j++) {
+      int numBytes = bitsPerSample[0] / 8;
+
+      if (bitsPerSample[0] % 8 != 0) {
+        // bits per sample is not a multiple of 8
+        //
+        // while images in this category are not in violation of baseline TIFF
+        // specs, it's a bad idea to write bits per sample values that aren't
+        // divisible by 8
+
+        if (index == bytes.length) {
+          //throw new FormatException("bad index : i = " + i + ", j = " + j);
+          index--;
+        }
+
+        short b = bytes[index];
+        index++;
+
+        int offset = (bitsPerSample[0] * (samples.length*j + channelNum)) % 8;
+        if (offset <= (8 - (bitsPerSample[0] % 8))) {
+          index--;
+        }
+
+        if (channelNum == 0) counter++;
+        if (counter % 4 == 0 && channelNum == 0) {
+          index++;
+        }
+
+        int ndx = startIndex + j;
+        if (ndx >= samples[channelNum].length) {
+          ndx = samples[channelNum].length - 1;
+        }
+        samples[channelNum][ndx] = (short) (b < 0 ? 256 + b : b);
+
+        if (photoInterp == WHITE_IS_ZERO || photoInterp == CMYK) {
+          samples[channelNum][ndx] =
+            (short) (Integer.MAX_VALUE - samples[channelNum][ndx]);
+        }
+      }
+      else if (numBytes == 1) {
+        float b = bytes[index];
+        index++;
+
+        int ndx = startIndex + j;
+        if (ndx < samples[channelNum].length) {
+          samples[channelNum][ndx] = (short) (b < 0 ? 256 + b : b);
+
+          if (photoInterp == WHITE_IS_ZERO) { // invert color value
+            samples[channelNum][ndx] =
+              (short) ((65535 - samples[channelNum][ndx]) & 0xffff);
+          }
+          else if (photoInterp == CMYK) {
+            samples[channelNum][ndx] =
+              (short) (Integer.MAX_VALUE - samples[channelNum][ndx]);
+          }
+        }
+      }
+      else {
+        byte[] b = new byte[numBytes];
+        if (numBytes + index < bytes.length) {
+          System.arraycopy(bytes, index, b, 0, numBytes);
+        }
+        else {
+          System.arraycopy(bytes, bytes.length - numBytes, b, 0, numBytes);
+        }
+        index += numBytes;
+        int ndx = startIndex + j;
+        if (ndx >= samples[0].length) ndx = samples[0].length - 1;
+        samples[channelNum][ndx] =
+          (short) DataTools.bytesToLong(b, !littleEndian);
+
+        if (photoInterp == WHITE_IS_ZERO) { // invert color value
+          long max = 1;
+          for (int q=0; q<numBytes; q++) max *= 8;
+          samples[channelNum][ndx] = (short) (max - samples[channelNum][ndx]);
+        }
+        else if (photoInterp == CMYK) {
+          samples[channelNum][ndx] =
+            (short) (Integer.MAX_VALUE - samples[channelNum][ndx]);
+        }
+      }
+    }
+  }
+
+  /**
+   * Extracts pixel information from the given byte array according to the
+   * bits per sample, photometric interpretation and color map IFD directory
+   * entry values, and the specified byte ordering.
+   * No error checking is performed.
+   */
+  public static void unpackBytes(short[][] samples, int startIndex,
+    byte[] bytes, int[] bitsPerSample, int photoInterp, int[] colorMap,
+    boolean littleEndian, long maxValue, int planar, int strip, int numStrips,
+    long imageWidth) throws FormatException
+  {
+    if (planar == 2) {
+      planarUnpack(samples, startIndex, bytes, bitsPerSample, photoInterp,
+        littleEndian, strip, numStrips);
+      return;
+    }
+
+    int totalBits = 0;
+    for (int i=0; i<bitsPerSample.length; i++) totalBits += bitsPerSample[i];
+    int sampleCount = 8 * bytes.length / totalBits;
+
+    if (DEBUG) {
+      debug("unpacking " + sampleCount + " samples (startIndex=" + startIndex +
+        "; totalBits=" + totalBits + "; numBytes=" + bytes.length + ")");
+    }
+    if (startIndex + sampleCount > samples[0].length) {
+      int trunc = startIndex + sampleCount - samples[0].length;
+      if (DEBUG) debug("WARNING: truncated " + trunc + " extra samples");
+      sampleCount -= trunc;
+    }
+
+    // rules on incrementing the index:
+    // 1) if the planar configuration is set to 1 (interleaved), then add one
+    //    to the index
+    // 2) if the planar configuration is set to 2 (separated), then go to
+    //    j + (i*(bytes.length / sampleCount))
+
+    int bps0 = bitsPerSample[0];
+    int bpsPow = (int) Math.pow(2, bps0);
+    int numBytes = bps0 / 8;
+    boolean noDiv8 = bps0 % 8 != 0;
+    boolean bps8 = bps0 == 8;
+    boolean bps16 = bps0 == 16;
+
+    if (photoInterp == CFA_ARRAY) {
+      imageWidth = colorMap[colorMap.length - 2];
+    }
+
+    int row = 0, col = 0;
+
+    if (imageWidth != 0) row = startIndex / (int) imageWidth;
+
+    int cw = 0;
+    int ch = 0;
+
+    if (photoInterp == CFA_ARRAY) {
+      byte[] c = new byte[2];
+      c[0] = (byte) colorMap[0];
+      c[1] = (byte) colorMap[1];
+
+      cw = DataTools.bytesToInt(c, littleEndian);
+      c[0] = (byte) colorMap[2];
+      c[1] = (byte) colorMap[3];
+      ch = DataTools.bytesToInt(c, littleEndian);
+
+      int[] tmp = colorMap;
+      colorMap = new int[tmp.length - 6];
+      System.arraycopy(tmp, 4, colorMap, 0, colorMap.length);
+    }
+
+    int index = 0;
+    int count = 0;
+
+    BitBuffer bb = new BitBuffer(bytes);
+
+    byte[] copyByteArray = new byte[numBytes];
+    ByteBuffer nioBytes = MappedByteBuffer.wrap(bytes);
+    if (!littleEndian)
+      nioBytes.order(ByteOrder.LITTLE_ENDIAN);
+
+    for (int j=0; j<sampleCount; j++) {
+      for (int i=0; i<samples.length; i++) {
+        if (noDiv8) {
+          // bits per sample is not a multiple of 8
+
+          int ndx = startIndex + j;
+          short s = 0;
+          if ((i == 0 && (photoInterp == CFA_ARRAY ||
+            photoInterp == RGB_PALETTE) || (photoInterp != CFA_ARRAY &&
+            photoInterp != RGB_PALETTE)))
+          {
+            s = (short) bb.getBits(bps0);
+            if ((ndx % imageWidth) == imageWidth - 1) {
+              bb.skipBits((imageWidth * bps0 * sampleCount) % 8);
+            }
+          }
+          short b = s;
+          if (photoInterp != CFA_ARRAY) samples[i][ndx] = s;
+
+          if (photoInterp == WHITE_IS_ZERO || photoInterp == CMYK) {
+            samples[i][ndx] =
+              (short) (Integer.MAX_VALUE - samples[i][ndx]); // invert colors
+          }
+          else if (photoInterp == CFA_ARRAY) {
+            if (i == 0) {
+              int pixelIndex = (int) ((row + (count / cw))*imageWidth + col +
+                (count % cw));
+
+              samples[colorMap[count]][pixelIndex] = s;
+              count++;
+
+              if (count == colorMap.length) {
+                count = 0;
+                col += cw*ch;
+                if (col == imageWidth) col = cw;
+                else if (col > imageWidth) {
+                  row += ch;
+                  col = 0;
+                }
+              }
+            }
+          }
+        }
+        else if (bps8) {
+          // special case handles 8-bit data more quickly
+          //if (planar == 2) { index = j+(i*(bytes.length / samples.length)); }
+          short b = (short) (bytes[index] & 0xff);
+          index++;
+
+          int ndx = startIndex + j;
+          samples[i][ndx] = (short) (b < 0 ? Integer.MAX_VALUE + b : b);
+
+          if (photoInterp == WHITE_IS_ZERO) { // invert color value
+            samples[i][ndx] = (short) ((65535 - samples[i][ndx]) & 0xffff);
+          }
+          else if (photoInterp == CMYK) {
+            samples[i][ndx] = (short) (Integer.MAX_VALUE - samples[i][ndx]);
+          }
+          else if (photoInterp == Y_CB_CR) {
+            if (i == bitsPerSample.length - 1) {
+              int lumaRed = colorMap[0];
+              int lumaGreen = colorMap[1];
+              int lumaBlue = colorMap[2];
+              int red = (int)
+                (samples[2][ndx]*(2 - 2*lumaRed) + samples[0][ndx]);
+              int blue = (int)
+                (samples[1][ndx]*(2 - 2*lumaBlue) + samples[0][ndx]);
+              int green = (int)
+                (samples[0][ndx] - lumaBlue*blue - lumaRed*red);
+              if (lumaGreen != 0) green = green / lumaGreen;
+              samples[0][ndx] = (short) (red - colorMap[4]);
+              samples[1][ndx] = (short) (green - colorMap[6]);
+              samples[2][ndx] = (short) (blue - colorMap[8]);
+            }
+          }
+        }  // End if (bps8)
+        else if (bps16) {
+          int ndx = startIndex + j;
+          if (numBytes + index < bytes.length) {
+            samples[i][ndx] = nioBytes.getShort(index);
+          } else {
+            samples[i][ndx] = nioBytes.getShort(bytes.length - numBytes);
+          }
+          index += numBytes;
+
+          if (photoInterp == WHITE_IS_ZERO) { // invert color value
+            long max = 1;
+            for (int q=0; q<numBytes; q++) max *= 8;
+            samples[i][ndx] = (short) (max - samples[i][ndx]);
+          }
+          else if (photoInterp == CMYK) {
+            samples[i][ndx] = (short) (Integer.MAX_VALUE - samples[i][ndx]);
+          }
+        }  // End if (bps16)
+        else {
+          if (numBytes + index < bytes.length) {
+            System.arraycopy(bytes, index, copyByteArray, 0, numBytes);
+          }
+          else {
+            System.arraycopy(bytes, bytes.length - numBytes, copyByteArray,
+              0, numBytes);
+          }
+          index += numBytes;
+          int ndx = startIndex + j;
+          samples[i][ndx] =
+              (short) DataTools.bytesToLong(copyByteArray, !littleEndian);
+
+          if (photoInterp == WHITE_IS_ZERO) { // invert color value
+            long max = 1;
+            for (int q=0; q<numBytes; q++) max *= 8;
+            samples[i][ndx] = (short) (max - samples[i][ndx]);
+          }
+          else if (photoInterp == CMYK) {
+            samples[i][ndx] = (short) (Integer.MAX_VALUE - samples[i][ndx]);
+          }
+        } // end else
+      }
+    }
+  }
+
+  // -- Decompression methods --
+
+  /** Decodes a strip of data compressed with the given compression scheme. */
+  public static byte[] uncompress(byte[] input, int compression)
+    throws FormatException, IOException
+  {
+    if (compression < 0) compression += 65536;
+    if (compression == UNCOMPRESSED) return input;
+    else if (compression == CCITT_1D) {
+      throw new FormatException(
+        "Sorry, CCITT Group 3 1-Dimensional Modified Huffman " +
+        "run length encoding compression mode is not supported");
+    }
+    else if (compression == GROUP_3_FAX) {
+      throw new FormatException("Sorry, CCITT T.4 bi-level encoding " +
+        "(Group 3 Fax) compression mode is not supported");
+    }
+    else if (compression == GROUP_4_FAX) {
+      throw new FormatException("Sorry, CCITT T.6 bi-level encoding " +
+        "(Group 4 Fax) compression mode is not supported");
+    }
+    else if (compression == LZW) {
+      return new LZWCodec().decompress(input);
+    }
+    else if (compression == JPEG) {
+      throw new FormatException(
+        "Sorry, JPEG compression mode is not supported");
+    }
+    else if (compression == PACK_BITS) {
+      return new PackbitsCodec().decompress(input);
+    }
+    else if (compression == PROPRIETARY_DEFLATE || compression == DEFLATE) {
+      return new AdobeDeflateCodec().decompress(input);
+    }
+    else if (compression == THUNDERSCAN) {
+      throw new FormatException("Sorry, " +
+        "Thunderscan compression mode is not supported");
+    }
+    else if (compression == NIKON) {
+      //return new NikonCodec().decompress(input);
+      throw new FormatException("Sorry, Nikon compression mode is not " +
+        "supported; we hope to support it in the future");
+    }
+    else if (compression == LURAWAVE) {
+      return new LuraWaveCodec().decompress(input);
+    }
+    else {
+      throw new FormatException(
+        "Unknown Compression type (" + compression + ")");
+    }
+  }
+
+  /** Undoes in-place differencing according to the given predictor value. */
+  public static void undifference(byte[] input, int[] bitsPerSample,
+    long width, int planarConfig, int predictor) throws FormatException
+  {
+    if (predictor == 2) {
+      if (DEBUG) debug("reversing horizontal differencing");
+      int len = bitsPerSample.length;
+      if (bitsPerSample[len - 1] == 0) len = 1;
+      for (int b=0; b<input.length; b++) {
+        if (b / len % width == 0) continue;
+        input[b] += input[b - len];
+      }
+    }
+    else if (predictor != 1) {
+      throw new FormatException("Unknown Predictor (" + predictor + ")");
+    }
+  }
+
+  // --------------------------- Writing TIFF files ---------------------------
+
+  // -- IFD population methods --
+
+  /** Adds a directory entry to an IFD. */
+  public static void putIFDValue(Hashtable ifd, int tag, Object value) {
+    ifd.put(new Integer(tag), value);
+  }
+
+  /** Adds a directory entry of type BYTE to an IFD. */
+  public static void putIFDValue(Hashtable ifd, int tag, short value) {
+    putIFDValue(ifd, tag, new Short(value));
+  }
+
+  /** Adds a directory entry of type SHORT to an IFD. */
+  public static void putIFDValue(Hashtable ifd, int tag, int value) {
+    putIFDValue(ifd, tag, new Integer(value));
+  }
+
+  /** Adds a directory entry of type LONG to an IFD. */
+  public static void putIFDValue(Hashtable ifd, int tag, long value) {
+    putIFDValue(ifd, tag, new Long(value));
+  }
+
+  // -- IFD writing methods --
+
+  /**
+   * Writes the given IFD value to the given output object.
+   * @param ifdOut output object for writing IFD stream
+   * @param extraBuf buffer to which "extra" IFD information should be written
+   * @param extraOut data output wrapper for extraBuf (passed for efficiency)
+   * @param offset global offset to use for IFD offset values
+   * @param tag IFD tag to write
+   * @param value IFD value to write
+   */
+  public static void writeIFDValue(DataOutput ifdOut,
+    ByteArrayOutputStream extraBuf, DataOutputStream extraOut, int offset,
+    int tag, Object value) throws FormatException, IOException
+  {
+    // convert singleton objects into arrays, for simplicity
+    if (value instanceof Short) {
+      value = new short[] {((Short) value).shortValue()};
+    }
+    else if (value instanceof Integer) {
+      value = new int[] {((Integer) value).intValue()};
+    }
+    else if (value instanceof Long) {
+      value = new long[] {((Long) value).longValue()};
+    }
+    else if (value instanceof TiffRational) {
+      value = new TiffRational[] {(TiffRational) value};
+    }
+    else if (value instanceof Float) {
+      value = new float[] {((Float) value).floatValue()};
+    }
+    else if (value instanceof Double) {
+      value = new double[] {((Double) value).doubleValue()};
+    }
+
+    // write directory entry to output buffers
+    ifdOut.writeShort(tag); // tag
+    if (value instanceof short[]) { // BYTE
+      short[] q = (short[]) value;
+      ifdOut.writeShort(BYTE); // type
+      ifdOut.writeInt(q.length); // count
+      if (q.length <= 4) {
+        for (int i=0; i<q.length; i++) ifdOut.writeByte(q[i]); // value(s)
+        for (int i=q.length; i<4; i++) ifdOut.writeByte(0); // padding
+      }
+      else {
+        ifdOut.writeInt(offset + extraBuf.size()); // offset
+        for (int i=0; i<q.length; i++) extraOut.writeByte(q[i]); // values
+      }
+    }
+    else if (value instanceof String) { // ASCII
+      char[] q = ((String) value).toCharArray();
+      ifdOut.writeShort(ASCII); // type
+      ifdOut.writeInt(q.length + 1); // count
+      if (q.length < 4) {
+        for (int i=0; i<q.length; i++) ifdOut.writeByte(q[i]); // value(s)
+        for (int i=q.length; i<4; i++) ifdOut.writeByte(0); // padding
+      }
+      else {
+        ifdOut.writeInt(offset + extraBuf.size()); // offset
+        for (int i=0; i<q.length; i++) extraOut.writeByte(q[i]); // values
+        extraOut.writeByte(0); // concluding NULL byte
+      }
+    }
+    else if (value instanceof int[]) { // SHORT
+      int[] q = (int[]) value;
+      ifdOut.writeShort(SHORT); // type
+      ifdOut.writeInt(q.length); // count
+      if (q.length <= 2) {
+        for (int i=0; i<q.length; i++) ifdOut.writeShort(q[i]); // value(s)
+        for (int i=q.length; i<2; i++) ifdOut.writeShort(0); // padding
+      }
+      else {
+        ifdOut.writeInt(offset + extraBuf.size()); // offset
+        for (int i=0; i<q.length; i++) extraOut.writeShort(q[i]); // values
+      }
+    }
+    else if (value instanceof long[]) { // LONG
+      long[] q = (long[]) value;
+      ifdOut.writeShort(LONG); // type
+      ifdOut.writeInt(q.length); // count
+      if (q.length <= 1) {
+        if (q.length == 1) ifdOut.writeInt((int) q[0]); // value
+        else ifdOut.writeInt(0); // padding
+      }
+      else {
+        ifdOut.writeInt(offset + extraBuf.size()); // offset
+        for (int i=0; i<q.length; i++) {
+          extraOut.writeInt((int) q[i]); // values
+        }
+      }
+    }
+    else if (value instanceof TiffRational[]) { // RATIONAL
+      TiffRational[] q = (TiffRational[]) value;
+      ifdOut.writeShort(RATIONAL); // type
+      ifdOut.writeInt(q.length); // count
+      ifdOut.writeInt(offset + extraBuf.size()); // offset
+      for (int i=0; i<q.length; i++) {
+        extraOut.writeInt((int) q[i].getNumerator()); // values
+        extraOut.writeInt((int) q[i].getDenominator()); // values
+      }
+    }
+    else if (value instanceof float[]) { // FLOAT
+      float[] q = (float[]) value;
+      ifdOut.writeShort(FLOAT); // type
+      ifdOut.writeInt(q.length); // count
+      if (q.length <= 1) {
+        if (q.length == 1) ifdOut.writeFloat(q[0]); // value
+        else ifdOut.writeInt(0); // padding
+      }
+      else {
+        ifdOut.writeInt(offset + extraBuf.size()); // offset
+        for (int i=0; i<q.length; i++) extraOut.writeFloat(q[i]); // values
+      }
+    }
+    else if (value instanceof double[]) { // DOUBLE
+      double[] q = (double[]) value;
+      ifdOut.writeShort(DOUBLE); // type
+      ifdOut.writeInt(q.length); // count
+      ifdOut.writeInt(offset + extraBuf.size()); // offset
+      for (int i=0; i<q.length; i++) extraOut.writeDouble(q[i]); // values
+    }
+    else {
+      throw new FormatException("Unknown IFD value type (" +
+        value.getClass().getName() + "): " + value);
+    }
+  }
+
+  /**
+   * Surgically overwrites an existing IFD value with the given one. This
+   * method requires that the IFD directory entry already exist. It
+   * intelligently updates the count field of the entry to match the new
+   * length. If the new length is longer than the old length, it appends the
+   * new data to the end of the file and updates the offset field; if not, or
+   * if the old data is already at the end of the file, it overwrites the old
+   * data in place.
+   */
+  public static void overwriteIFDValue(RandomAccessFile raf,
+    int ifd, int tag, Object value) throws FormatException, IOException
+  {
+    if (DEBUG) {
+      debug("overwriteIFDValue (ifd=" + ifd + "; tag=" + tag + "; value=" +
+        value + ")");
+    }
+    byte[] header = new byte[4];
+    raf.seek(0);
+    raf.readFully(header);
+    if (!isValidHeader(header)) {
+      throw new FormatException("Invalid TIFF header");
+    }
+    boolean little = header[0] == LITTLE && header[1] == LITTLE; // II
+    long offset = 4; // offset to the IFD
+    int num = 0; // number of directory entries
+
+    // skip to the correct IFD
+    for (int i=0; i<=ifd; i++) {
+      offset = DataTools.read4UnsignedBytes(raf, little);
+      if (offset <= 0) {
+        throw new FormatException("No such IFD (" + ifd + " of " + i + ")");
+      }
+      raf.seek(offset);
+      num = DataTools.read2UnsignedBytes(raf, little);
+      if (i < ifd) raf.seek(offset + 2 + BYTES_PER_ENTRY * num);
+    }
+
+    // search directory entries for proper tag
+    for (int i=0; i<num; i++) {
+      int oldTag = DataTools.read2UnsignedBytes(raf, little);
+      int oldType = DataTools.read2UnsignedBytes(raf, little);
+      int oldCount = DataTools.read4SignedBytes(raf, little);
+      int oldOffset = DataTools.read4SignedBytes(raf, little);
+      if (oldTag == tag) {
+        // write new value to buffers
+        ByteArrayOutputStream ifdBuf = new ByteArrayOutputStream(14);
+        DataOutputStream ifdOut = new DataOutputStream(ifdBuf);
+        ByteArrayOutputStream extraBuf = new ByteArrayOutputStream();
+        DataOutputStream extraOut = new DataOutputStream(extraBuf);
+        writeIFDValue(ifdOut, extraBuf, extraOut, oldOffset, tag, value);
+        byte[] bytes = ifdBuf.toByteArray();
+        byte[] extra = extraBuf.toByteArray();
+
+        // extract new directory entry parameters
+        int newTag = DataTools.bytesToInt(bytes, 0, 2, false);
+        int newType = DataTools.bytesToInt(bytes, 2, 2, false);
+        int newCount = DataTools.bytesToInt(bytes, 4, false);
+        int newOffset = DataTools.bytesToInt(bytes, 8, false);
+        boolean terminate = false;
+        if (DEBUG) {
+          debug("overwriteIFDValue:\n\told: (tag=" + oldTag + "; type=" +
+            oldType + "; count=" + oldCount + "; offset=" + oldOffset +
+            ");\n\tnew: (tag=" + newTag + "; type=" + newType + "; count=" +
+            newCount + "; offset=" + newOffset + ")");
+        }
+
+        // determine the best way to overwrite the old entry
+        if (extra.length == 0) {
+          // new entry is inline; if old entry wasn't, old data is orphaned
+          // do not override new offset value since data is inline
+          if (DEBUG) debug("overwriteIFDValue: new entry is inline");
+        }
+        else if (oldOffset +
+          oldCount * BYTES_PER_ELEMENT[oldType] == raf.length())
+        {
+          // old entry was already at EOF; overwrite it
+          newOffset = oldOffset;
+          terminate = true;
+          if (DEBUG) debug("overwriteIFDValue: old entry is at EOF");
+        }
+        else if (newCount <= oldCount) {
+          // new entry is as small or smaller than old entry; overwrite it
+          newOffset = oldOffset;
+          if (DEBUG) debug("overwriteIFDValue: new entry is <= old entry");
+        }
+        else {
+          // old entry was elsewhere; append to EOF, orphaning old entry
+          newOffset = (int) raf.length();
+          if (DEBUG) debug("overwriteIFDValue: old entry will be orphaned");
+        }
+
+        // overwrite old entry
+        raf.seek(raf.getFilePointer() - 10); // jump back
+        DataTools.writeShort(raf, newType, little);
+        DataTools.writeInt(raf, newCount, little);
+        DataTools.writeInt(raf, newOffset, little);
+        if (extra.length > 0) {
+          raf.seek(newOffset);
+          raf.write(extra);
+        }
+        if (terminate) raf.setLength(raf.getFilePointer());
+        return;
+      }
+    }
+
+    throw new FormatException("Tag not found (" + getIFDTagName(tag) + ")");
+  }
+
+  /** Convenience method for overwriting a file's first ImageDescription. */
+  public static void overwriteComment(String id, Object value)
+    throws FormatException, IOException
+  {
+    RandomAccessFile raf = new RandomAccessFile(id, "rw");
+    overwriteIFDValue(raf, 0, TiffTools.IMAGE_DESCRIPTION, value);
+    raf.close();
+  }
+
+  // -- Image writing methods --
+
+  /**
+   * Writes the given field to the specified output stream using the given
+   * byte offset and IFD, in big-endian format.
+   *
+   * @param img The field to write
+   * @param ifd Hashtable representing the TIFF IFD; can be null
+   * @param out The output stream to which the TIFF data should be written
+   * @param offset The value to use for specifying byte offsets
+   * @param last Whether this image is the final IFD entry of the TIFF data
+   * @return total number of bytes written
+   */
+  public static long writeImage(BufferedImage img, Hashtable ifd,
+    OutputStream out, int offset, boolean last)
+    throws FormatException, IOException
+  {
+    if (img == null) throw new FormatException("Image is null");
+    if (DEBUG) debug("writeImage (offset=" + offset + "; last=" + last + ")");
+
+    byte[][] values = ImageTools.getPixelBytes(img, false);
+
+    int width = img.getWidth();
+    int height = img.getHeight();
+
+    if (values.length < 1 || values.length > 3) {
+      throw new FormatException("Image has an unsupported " +
+        "number of range components (" + values.length + ")");
+    }
+    if (values.length == 2) {
+      // pad values with extra set of zeroes
+      values = new byte[][] {
+        values[0], values[1], new byte[values[0].length]
+      };
+    }
+
+    int bytesPerPixel = values[0].length / (width * height);
+
+    // populate required IFD directory entries (except strip information)
+    if (ifd == null) ifd = new Hashtable();
+    putIFDValue(ifd, IMAGE_WIDTH, width);
+    putIFDValue(ifd, IMAGE_LENGTH, height);
+    if (getIFDValue(ifd, BITS_PER_SAMPLE) == null) {
+      int bps = 8 * bytesPerPixel;
+      int[] bpsArray = new int[values.length];
+      Arrays.fill(bpsArray, bps);
+      putIFDValue(ifd, BITS_PER_SAMPLE, bpsArray);
+    }
+    if (img.getRaster().getTransferType() == DataBuffer.TYPE_FLOAT) {
+      putIFDValue(ifd, SAMPLE_FORMAT, 3);
+    }
+    if (getIFDValue(ifd, COMPRESSION) == null) {
+      putIFDValue(ifd, COMPRESSION, UNCOMPRESSED);
+    }
+    if (getIFDValue(ifd, PHOTOMETRIC_INTERPRETATION) == null) {
+      putIFDValue(ifd, PHOTOMETRIC_INTERPRETATION, values.length == 1 ? 1 : 2);
+    }
+    if (getIFDValue(ifd, SAMPLES_PER_PIXEL) == null) {
+      putIFDValue(ifd, SAMPLES_PER_PIXEL, values.length);
+    }
+    if (getIFDValue(ifd, X_RESOLUTION) == null) {
+      putIFDValue(ifd, X_RESOLUTION, new TiffRational(1, 1)); // no unit
+    }
+    if (getIFDValue(ifd, Y_RESOLUTION) == null) {
+      putIFDValue(ifd, Y_RESOLUTION, new TiffRational(1, 1)); // no unit
+    }
+    if (getIFDValue(ifd, RESOLUTION_UNIT) == null) {
+      putIFDValue(ifd, RESOLUTION_UNIT, 1); // no unit
+    }
+    if (getIFDValue(ifd, SOFTWARE) == null) {
+      putIFDValue(ifd, SOFTWARE, "LOCI Bio-Formats");
+    }
+    if (getIFDValue(ifd, IMAGE_DESCRIPTION) == null) {
+      putIFDValue(ifd, IMAGE_DESCRIPTION, "");
+    }
+
+    // create pixel output buffers
+    int stripSize = 8192;
+    int rowsPerStrip = stripSize / (width * bytesPerPixel);
+    int stripsPerImage = (height + rowsPerStrip - 1) / rowsPerStrip;
+    int[] bps = (int[]) getIFDValue(ifd, BITS_PER_SAMPLE, true, int[].class);
+    ByteArrayOutputStream[] stripBuf =
+      new ByteArrayOutputStream[stripsPerImage];
+    DataOutputStream[] stripOut = new DataOutputStream[stripsPerImage];
+    for (int i=0; i<stripsPerImage; i++) {
+      stripBuf[i] = new ByteArrayOutputStream(stripSize);
+      stripOut[i] = new DataOutputStream(stripBuf[i]);
+    }
+
+    // write pixel strips to output buffers
+    for (int y=0; y<height; y++) {
+      int strip = y / rowsPerStrip;
+      for (int x=0; x<width; x++) {
+        int ndx = y * width * bytesPerPixel + x * bytesPerPixel;
+        for (int c=0; c<values.length; c++) {
+          int q = values[c][ndx];
+          if (bps[c] == 8) stripOut[strip].writeByte(q);
+          else if (bps[c] == 16) {
+            stripOut[strip].writeByte(q);
+            stripOut[strip].writeByte(values[c][ndx+1]);
+          }
+          else if (bps[c] == 32) {
+            for (int i=0; i<4; i++) {
+              stripOut[strip].writeByte(values[c][ndx + i]);
+            }
+          }
+          else {
+            throw new FormatException("Unsupported bits per sample value (" +
+              bps[c] + ")");
+          }
+        }
+      }
+    }
+
+    // compress strips according to given differencing and compression schemes
+    int planarConfig = getIFDIntValue(ifd, PLANAR_CONFIGURATION, false, 1);
+    int predictor = getIFDIntValue(ifd, PREDICTOR, false, 1);
+    int compression = getIFDIntValue(ifd, COMPRESSION, false, UNCOMPRESSED);
+    byte[][] strips = new byte[stripsPerImage][];
+    for (int i=0; i<stripsPerImage; i++) {
+      strips[i] = stripBuf[i].toByteArray();
+      difference(strips[i], bps, width, planarConfig, predictor);
+      strips[i] = compress(strips[i], compression);
+    }
+
+    // record strip byte counts and offsets
+    long[] stripByteCounts = new long[stripsPerImage];
+    long[] stripOffsets = new long[stripsPerImage];
+    putIFDValue(ifd, STRIP_OFFSETS, stripOffsets);
+    putIFDValue(ifd, ROWS_PER_STRIP, rowsPerStrip);
+    putIFDValue(ifd, STRIP_BYTE_COUNTS, stripByteCounts);
+
+    Object[] keys = ifd.keySet().toArray();
+    Arrays.sort(keys); // sort IFD tags in ascending order
+    int ifdBytes = 2 + BYTES_PER_ENTRY * keys.length + 4;
+    long pixelBytes = 0;
+    for (int i=0; i<stripsPerImage; i++) {
+      stripByteCounts[i] = strips[i].length;
+      stripOffsets[i] = pixelBytes + offset + ifdBytes;
+      pixelBytes += stripByteCounts[i];
+    }
+
+    // create IFD output buffers
+    ByteArrayOutputStream ifdBuf = new ByteArrayOutputStream(ifdBytes);
+    DataOutputStream ifdOut = new DataOutputStream(ifdBuf);
+    ByteArrayOutputStream extraBuf = new ByteArrayOutputStream();
+    DataOutputStream extraOut = new DataOutputStream(extraBuf);
+
+    offset += ifdBytes + pixelBytes;
+
+    // write IFD to output buffers
+    ifdOut.writeShort(keys.length); // number of directory entries
+    for (int k=0; k<keys.length; k++) {
+      Object key = keys[k];
+      if (!(key instanceof Integer)) {
+        throw new FormatException("Malformed IFD tag (" + key + ")");
+      }
+      if (((Integer) key).intValue() == LITTLE_ENDIAN) continue;
+      Object value = ifd.get(key);
+      if (DEBUG) {
+        String sk = getIFDTagName(((Integer) key).intValue());
+        String sv = value instanceof int[] ?
+          ("int[" + ((int[]) value).length + "]") : value.toString();
+        debug("writeImage: writing " + sk + " (value=" + sv + ")");
+      }
+      writeIFDValue(ifdOut, extraBuf, extraOut, offset,
+        ((Integer) key).intValue(), value);
+    }
+    ifdOut.writeInt(last ? 0 : offset + extraBuf.size()); // offset to next IFD
+
+    // flush buffers to output stream
+    byte[] ifdArray = ifdBuf.toByteArray();
+    byte[] extraArray = extraBuf.toByteArray();
+    long numBytes = ifdArray.length + extraArray.length;
+    out.write(ifdArray);
+    for (int i=0; i<strips.length; i++) {
+      out.write(strips[i]);
+      numBytes += strips[i].length;
+    }
+    out.write(extraArray);
+    return numBytes;
+  }
+
+  /**
+   * Retrieves the image's width (TIFF tag ImageWidth) from a given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the image's width.
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   */
+  public static long getImageWidth(Hashtable ifd) throws FormatException {
+    return getIFDLongValue(ifd, IMAGE_WIDTH, true, 0);
+  }
+
+  /**
+   * Retrieves the image's length (TIFF tag ImageLength) from a given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the image's length.
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   */
+  public static long getImageLength(Hashtable ifd) throws FormatException {
+    return getIFDLongValue(ifd, IMAGE_LENGTH, true, 0);
+  }
+
+  /**
+   * Retrieves the image's bits per sample (TIFF tag BitsPerSample) from a given
+   * TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the image's bits per sample. The length of the array is equal to
+   *   the number of samples per pixel.
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   * @see #getSamplesPerPixel(Hashtable)
+   */
+  public static int[] getBitsPerSample(Hashtable ifd) throws FormatException {
+    int[] bitsPerSample = getIFDIntArray(ifd, BITS_PER_SAMPLE, false);
+    if (bitsPerSample == null) bitsPerSample = new int[] {1};
+    return bitsPerSample;
+  }
+
+  /**
+   * Retrieves the number of samples per pixel for the image (TIFF tag
+   * SamplesPerPixel) from a given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the number of samples per pixel.
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   */
+  public static int getSamplesPerPixel(Hashtable ifd) throws FormatException {
+    return getIFDIntValue(ifd, SAMPLES_PER_PIXEL, false, 1);
+  }
+
+  /**
+   * Retrieves the image's compression type (TIFF tag Compression) from a
+   * given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the image's compression type. As of TIFF 6.0 this is one of:
+   * <ul>
+   *  <li>Uncompressed (1)</li>
+   *  <li>CCITT 1D (2)</li>
+   *  <li>Group 3 Fax (3)</li>
+   *  <li>Group 4 Fax (4)</li>
+   *  <li>LZW (5)</li>
+   *  <li>JPEG (6)</li>
+   *  <li>PackBits (32773)</li>
+   * </ul>
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   */
+  public static int getCompression(Hashtable ifd) throws FormatException {
+    return getIFDIntValue(ifd, COMPRESSION, false, UNCOMPRESSED);
+  }
+
+  /**
+   * Retrieves the image's photometric interpretation (TIFF tag
+   * PhotometricInterpretation) from a given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the image's photometric interpretation. As of TIFF 6.0 this is one
+   * of:
+   * <ul>
+   *  <li>WhiteIsZero (0)</li>
+   *  <li>BlackIsZero (1)</li>
+   *  <li>RGB (2)</li>
+   *  <li>RGB Palette (3)</li>
+   *  <li>Transparency mask (4)</li>
+   *  <li>CMYK (5)</li>
+   *  <li>YbCbCr (6)</li>
+   *  <li>CIELab (8)</li>
+   * </ul>
+   *
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   */
+  public static int getPhotometricInterpretation(Hashtable ifd)
+    throws FormatException
+  {
+    return getIFDIntValue(ifd, PHOTOMETRIC_INTERPRETATION, true, 0);
+  }
+
+  /**
+   * Retrieves the strip offsets for the image (TIFF tag StripOffsets) from a
+   * given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the strip offsets for the image. The lenght of the array is equal
+   *   to the number of strips per image. <i>StripsPerImage =
+   *   floor ((ImageLength + RowsPerStrip - 1) / RowsPerStrip)</i>.
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   * @see #getStripByteCounts(Hashtable)
+   * @see #getRowsPerStrip(Hashtable)
+   */
+  public static long[] getStripOffsets(Hashtable ifd) throws FormatException {
+    return getIFDLongArray(ifd, STRIP_OFFSETS, false);
+  }
+
+  /**
+   * Retrieves strip byte counts for the image (TIFF tag StripByteCounts) from a
+   * given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the byte counts for each strip. The length of the array is equal to
+   *   the number of strips per image. <i>StripsPerImage =
+   *   floor((ImageLength + RowsPerStrip - 1) / RowsPerStrip)</i>.
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   * @see #getStripOffsets(Hashtable)
+   */
+  public static long[] getStripByteCounts(Hashtable ifd) throws FormatException
+  {
+    return getIFDLongArray(ifd, STRIP_BYTE_COUNTS, false);
+  }
+
+  /**
+   * Retrieves the number of rows per strip for image (TIFF tag RowsPerStrip)
+   * from a given TIFF IFD.
+   * @param ifd a TIFF IFD hashtable.
+   * @return the number of rows per strip.
+   * @throws FormatException if there is a problem parsing the IFD metadata.
+   */
+  public static long[] getRowsPerStrip(Hashtable ifd) throws FormatException {
+    return getIFDLongArray(ifd, ROWS_PER_STRIP, false);
+  }
+
+  // -- Compression methods --
+
+  /** Encodes a strip of data with the given compression scheme. */
+  public static byte[] compress(byte[] input, int compression)
+    throws FormatException, IOException
+  {
+    if (compression == UNCOMPRESSED) return input;
+    else if (compression == CCITT_1D) {
+      throw new FormatException(
+        "Sorry, CCITT Group 3 1-Dimensional Modified Huffman " +
+        "run length encoding compression mode is not supported");
+    }
+    else if (compression == GROUP_3_FAX) {
+      throw new FormatException("Sorry, CCITT T.4 bi-level encoding " +
+        "(Group 3 Fax) compression mode is not supported");
+    }
+    else if (compression == GROUP_4_FAX) {
+      throw new FormatException("Sorry, CCITT T.6 bi-level encoding " +
+        "(Group 4 Fax) compression mode is not supported");
+    }
+    else if (compression == LZW) {
+      LZWCodec c = new LZWCodec();
+      return c.compress(input, 0, 0, null, null);
+      // return Compression.lzwCompress(input);
+    }
+
+    else if (compression == JPEG) {
+      throw new FormatException(
+        "Sorry, JPEG compression mode is not supported");
+    }
+    else if (compression == PACK_BITS) {
+      throw new FormatException(
+        "Sorry, PackBits compression mode is not supported");
+    }
+    else {
+      throw new FormatException(
+        "Unknown Compression type (" + compression + ")");
+    }
+  }
+
+  /** Performs in-place differencing according to the given predictor value. */
+  public static void difference(byte[] input, int[] bitsPerSample,
+    long width, int planarConfig, int predictor) throws FormatException
+  {
+    if (predictor == 2) {
+      if (DEBUG) debug("performing horizontal differencing");
+      for (int b=input.length-1; b>=0; b--) {
+        if (b / bitsPerSample.length % width == 0) continue;
+        input[b] -= input[b - bitsPerSample.length];
+      }
+    }
+    else if (predictor != 1) {
+      throw new FormatException("Unknown Predictor (" + predictor + ")");
+    }
+  }
+
+  // -- Debugging --
+
+  /** Prints a debugging message with current time. */
+  public static void debug(String message) {
+    LogTools.println(System.currentTimeMillis() + ": " + message);
+  }
+
+}
diff --git a/loci/formats/TwoChannelColorSpace.java b/loci/formats/TwoChannelColorSpace.java
new file mode 100644
index 0000000..e657ea6
--- /dev/null
+++ b/loci/formats/TwoChannelColorSpace.java
@@ -0,0 +1,85 @@
+//
+// TwoChannelColorSpace.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.awt.color.*;
+
+/** ColorSpace for 2-channel images. */
+public class TwoChannelColorSpace extends ColorSpace {
+
+  // -- Constants --
+
+  public static final int CS_2C = -1;
+
+  private static final int NUM_COMPONENTS = 2;
+
+  // -- Constructor --
+
+  protected TwoChannelColorSpace(int type, int components) {
+    super(type, components);
+  }
+
+  // -- ColorSpace API methods --
+
+  public float[] fromCIEXYZ(float[] color) {
+    ColorSpace rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+    return rgb.fromCIEXYZ(toRGB(color));
+  }
+
+  public float[] fromRGB(float[] rgb) {
+    return new float[] {rgb[0], rgb[1]};
+  }
+
+  public static ColorSpace getInstance(int colorSpace) {
+    if (colorSpace == CS_2C) {
+      return new TwoChannelColorSpace(ColorSpace.TYPE_2CLR, NUM_COMPONENTS);
+    }
+    return ColorSpace.getInstance(colorSpace);
+  }
+
+  public String getName(int idx) {
+    return idx == 0 ? "Red" : "Green";
+  }
+
+  public int getNumComponents() {
+    return NUM_COMPONENTS;
+  }
+
+  public int getType() {
+    return ColorSpace.TYPE_2CLR;
+  }
+
+  public boolean isCS_sRGB() { return false; }
+
+  public float[] toCIEXYZ(float[] color) {
+    ColorSpace rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+    return rgb.toCIEXYZ(toRGB(color));
+  }
+
+  public float[] toRGB(float[] color) {
+    return new float[] {color[0], color[1], 0};
+  }
+
+}
diff --git a/loci/formats/UnknownTagException.java b/loci/formats/UnknownTagException.java
new file mode 100644
index 0000000..f68412e
--- /dev/null
+++ b/loci/formats/UnknownTagException.java
@@ -0,0 +1,36 @@
+//
+// UnknownTagException.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+/**
+ * Thrown when a given entry does not have a particular tag.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/UnknownTagException.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/UnknownTagException.java">SVN</a></dd></dl>
+ *
+ * @author Chris Allan callan at blackcat.ca
+ */
+public class UnknownTagException extends RuntimeException { }
diff --git a/loci/formats/XMLTools.java b/loci/formats/XMLTools.java
new file mode 100644
index 0000000..d4ee01e
--- /dev/null
+++ b/loci/formats/XMLTools.java
@@ -0,0 +1,248 @@
+//
+// XMLTools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import javax.xml.parsers.*;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * A utility class for working with XML (not necessarily OME-XML).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/XMLTools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/XMLTools.java">SVN</a></dd></dl>
+ */
+public final class XMLTools {
+
+  // -- Constructor --
+
+  private XMLTools() { }
+
+  // -- Utility methods --
+
+  /**
+   * Attempts to validate the given XML string using
+   * Java's XML validation facility. Requires Java 1.5+.
+   * @param xml The XML string to validate.
+   */
+  public static void validateXML(String xml) { validateXML(xml, null); }
+
+  /**
+   * Attempts to validate the given XML string using
+   * Java's XML validation facility. Requires Java 1.5+.
+   * @param xml The XML string to validate.
+   * @param label String describing the type of XML being validated.
+   */
+  public static void validateXML(String xml, String label) {
+    if (label == null) label = "XML";
+
+    // check Java version (XML validation only works in Java 1.5+)
+    String version = System.getProperty("java.version");
+    int dot = version.indexOf(".");
+    if (dot >= 0) dot = version.indexOf(".", dot + 1);
+    float ver = Float.NaN;
+    if (dot >= 0) {
+      try {
+        ver = Float.parseFloat(version.substring(0, dot));
+      }
+      catch (NumberFormatException exc) { }
+    }
+    if (ver != ver) {
+      LogTools.println("Warning: cannot determine if Java version\"" +
+        version + "\" supports Java v1.5. XML validation may fail.");
+    }
+
+    if (ver < 1.5f) return; // do not attempt validation if not Java 1.5+
+
+    // get path to schema from root element using SAX
+    LogTools.println("Parsing schema path");
+    ValidationSAXHandler saxHandler = new ValidationSAXHandler();
+    SAXParserFactory saxFactory = SAXParserFactory.newInstance();
+    Exception exception = null;
+    try {
+      SAXParser saxParser = saxFactory.newSAXParser();
+      InputStream is = new ByteArrayInputStream(xml.getBytes());
+      saxParser.parse(is, saxHandler);
+    }
+    catch (ParserConfigurationException exc) { exception = exc; }
+    catch (SAXException exc) { exception = exc; }
+    catch (IOException exc) { exception = exc; }
+    if (exception != null) {
+      LogTools.println("Error parsing schema path from " + label + ":");
+      LogTools.trace(exception);
+      return;
+    }
+    String schemaPath = saxHandler.getSchemaPath();
+    if (schemaPath == null) {
+      LogTools.println("No schema path found. Validation cannot continue.");
+      return;
+    }
+    else LogTools.println(schemaPath);
+
+    LogTools.println("Validating " + label);
+
+    // use reflection to avoid compile-time dependency on optional
+    // org.openmicroscopy.xml or javax.xml.validation packages
+    ReflectedUniverse r = new ReflectedUniverse();
+
+    try {
+      // look up a factory for the W3C XML Schema language
+      r.setVar("xmlSchemaPath", "http://www.w3.org/2001/XMLSchema");
+      r.exec("import javax.xml.validation.SchemaFactory");
+      r.exec("factory = SchemaFactory.newInstance(xmlSchemaPath)");
+
+      // compile the schema
+      r.exec("import java.net.URL");
+      r.setVar("schemaPath", schemaPath);
+      r.exec("schemaLocation = new URL(schemaPath)");
+      r.exec("schema = factory.newSchema(schemaLocation)");
+
+      // HACK - workaround for weird Linux bug preventing use of
+      // schema.newValidator() method even though it is "public final"
+      r.setAccessibilityIgnored(true);
+
+      // get a validator from the schema
+      r.exec("validator = schema.newValidator()");
+
+      // prepare the XML source
+      r.exec("import java.io.StringReader");
+      r.setVar("xml", xml);
+      r.exec("reader = new StringReader(xml)");
+      r.exec("import org.xml.sax.InputSource");
+      r.exec("is = new InputSource(reader)");
+      r.exec("import javax.xml.transform.sax.SAXSource");
+      r.exec("source = new SAXSource(is)");
+
+      // validate the XML
+      ValidationErrorHandler errorHandler = new ValidationErrorHandler();
+      r.setVar("errorHandler", errorHandler);
+      r.exec("validator.setErrorHandler(errorHandler)");
+      r.exec("validator.validate(source)");
+      if (errorHandler.ok()) LogTools.println("No validation errors found.");
+    }
+    catch (ReflectException exc) {
+      LogTools.println("Error validating " + label + ":");
+      LogTools.trace(exc);
+    }
+  }
+
+  /** Indents XML to be more readable. */
+  public static String indentXML(String xml) { return indentXML(xml, 3); }
+
+  /** Indents XML by the given spacing to be more readable. */
+  public static String indentXML(String xml, int spacing) {
+    int indent = 0;
+    StringBuffer sb = new StringBuffer();
+    StringTokenizer st = new StringTokenizer(xml, "<>", true);
+    boolean element = false;
+    while (st.hasMoreTokens()) {
+      String token = st.nextToken().trim();
+      if (token.equals("")) continue;
+      if (token.equals("<")) {
+        element = true;
+        continue;
+      }
+      if (element && token.equals(">")) {
+        element = false;
+        continue;
+      }
+      if (element && token.startsWith("/")) indent -= spacing;
+      for (int j=0; j<indent; j++) sb.append(" ");
+      if (element) sb.append("<");
+      sb.append(token);
+      if (element) sb.append(">");
+      sb.append("\n");
+      if (element && !token.startsWith("?") &&
+        !token.startsWith("/") && !token.endsWith("/"))
+      {
+        indent += spacing;
+      }
+    }
+    return sb.toString();
+  }
+
+  // -- Helper classes --
+
+  /** Used by validateXML to parse the XML block's schema path using SAX. */
+  private static class ValidationSAXHandler extends DefaultHandler {
+    private String schemaPath;
+    private boolean first;
+    public String getSchemaPath() { return schemaPath; }
+    public void startDocument() {
+      schemaPath = null;
+      first = true;
+    }
+    public void startElement(String uri,
+      String localName, String qName, Attributes attributes)
+    {
+      if (!first) return;
+      first = false;
+
+      int len = attributes.getLength();
+      String xmlns = null, xsiSchemaLocation = null;
+      for (int i=0; i<len; i++) {
+        String name = attributes.getQName(i);
+        if (name.equals("xmlns")) xmlns = attributes.getValue(i);
+        else if (name.equals("xsi:schemaLocation")) {
+          xsiSchemaLocation = attributes.getValue(i);
+        }
+      }
+      if (xmlns == null || xsiSchemaLocation == null) return; // not found
+
+      StringTokenizer st = new StringTokenizer(xsiSchemaLocation);
+      while (st.hasMoreTokens()) {
+        String token = st.nextToken();
+        if (xmlns.equals(token)) {
+          // next token is the actual schema path
+          if (st.hasMoreTokens()) schemaPath = st.nextToken();
+          break;
+        }
+      }
+    }
+  }
+
+  /** Used by validateXML to handle XML validation errors. */
+  private static class ValidationErrorHandler implements ErrorHandler {
+    private boolean ok = true;
+    public boolean ok() { return ok; }
+    public void error(SAXParseException e) {
+      LogTools.println("error: " + e.getMessage());
+      ok = false;
+    }
+    public void fatalError(SAXParseException e) {
+      LogTools.println("fatal error: " + e.getMessage());
+      ok = false;
+    }
+    public void warning(SAXParseException e) {
+      LogTools.println("warning: " + e.getMessage());
+      ok = false;
+    }
+  }
+
+}
+
diff --git a/loci/formats/codec/AdobeDeflateCodec.java b/loci/formats/codec/AdobeDeflateCodec.java
new file mode 100644
index 0000000..06295e1
--- /dev/null
+++ b/loci/formats/codec/AdobeDeflateCodec.java
@@ -0,0 +1,94 @@
+//
+// AdobeDeflateCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import loci.formats.FormatException;
+
+/**
+ * This class implements Adobe Deflate decompression. Compression is not yet
+ * implemented.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/AdobeDeflateCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/AdobeDeflateCodec.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class AdobeDeflateCodec extends BaseCodec implements Codec {
+
+  /**
+   * Compresses a block of Adobe Deflate data. Currently not supported.
+   *
+   * @param data the data to be compressed
+   * @param x length of the x dimension of the image data, if appropriate
+   * @param y length of the y dimension of the image data, if appropriate
+   * @param dims the dimensions of the image data, if appropriate
+   * @param options options to be used during compression, if appropriate
+   * @return The compressed data
+   * @throws FormatException If input is not an Adobe data block.
+   */
+  public byte[] compress(byte[] data, int x, int y,
+      int[] dims, Object options) throws FormatException
+  {
+    // TODO: Add compression support.
+    throw new FormatException("Adobe Deflate Compression not currently " +
+                               "supported");
+  }
+
+  /** Decodes an Adobe Deflate (Zip) compressed image strip.
+   *
+   * @param input the data to be decompressed
+   * @return The decompressed data
+   * @throws FormatException if data is not valid compressed data for this
+   *                         decompressor
+   */
+  public byte[] decompress(byte[] input, Object options)
+    throws FormatException
+  {
+    try {
+      Inflater inf = new Inflater(false);
+      inf.setInput(input);
+      InflaterInputStream i =
+        new InflaterInputStream(new PipedInputStream(), inf);
+      ByteVector bytes = new ByteVector();
+      byte[] buf = new byte[8192];
+      while (true) {
+        int r = i.read(buf, 0, buf.length);
+        if (r == -1) break; // eof
+        bytes.add(buf, 0, r);
+      }
+      return bytes.toByteArray();
+    }
+    catch (IOException e) {
+      throw new FormatException("Error uncompressing " +
+        "Adobe Deflate (ZLIB) compressed image strip.", e);
+    }
+  }
+
+}
diff --git a/loci/formats/codec/BZip2Constants.java b/loci/formats/codec/BZip2Constants.java
new file mode 100644
index 0000000..a0487e9
--- /dev/null
+++ b/loci/formats/codec/BZip2Constants.java
@@ -0,0 +1,125 @@
+//
+// BZip2Constants.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/*
+ * Copyright  2001,2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron at aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package loci.formats.codec;
+
+/**
+ * Base class for both the compress and decompress classes.
+ * Holds common arrays, and static data.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/BZip2Constants.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/BZip2Constants.java">SVN</a></dd></dl>
+ */
+public interface BZip2Constants {
+
+  int BASE_BLOCK_SIZE = 100000;
+  int MAX_ALPHA_SIZE = 258;
+  int MAX_CODE_LEN = 23;
+  int RUNA = 0;
+  int RUNB = 1;
+  int N_GROUPS = 6;
+  int G_SIZE = 50;
+  int N_ITERS = 4;
+  int MAX_SELECTORS = (2 + (900000 / G_SIZE));
+  int NUM_OVERSHOOT_BYTES = 20;
+
+  int[] R_NUMS = {
+    619, 720, 127, 481, 931, 816, 813, 233, 566, 247,
+    985, 724, 205, 454, 863, 491, 741, 242, 949, 214,
+    733, 859, 335, 708, 621, 574, 73, 654, 730, 472,
+    419, 436, 278, 496, 867, 210, 399, 680, 480, 51,
+    878, 465, 811, 169, 869, 675, 611, 697, 867, 561,
+    862, 687, 507, 283, 482, 129, 807, 591, 733, 623,
+    150, 238, 59, 379, 684, 877, 625, 169, 643, 105,
+    170, 607, 520, 932, 727, 476, 693, 425, 174, 647,
+    73, 122, 335, 530, 442, 853, 695, 249, 445, 515,
+    909, 545, 703, 919, 874, 474, 882, 500, 594, 612,
+    641, 801, 220, 162, 819, 984, 589, 513, 495, 799,
+    161, 604, 958, 533, 221, 400, 386, 867, 600, 782,
+    382, 596, 414, 171, 516, 375, 682, 485, 911, 276,
+    98, 553, 163, 354, 666, 933, 424, 341, 533, 870,
+    227, 730, 475, 186, 263, 647, 537, 686, 600, 224,
+    469, 68, 770, 919, 190, 373, 294, 822, 808, 206,
+    184, 943, 795, 384, 383, 461, 404, 758, 839, 887,
+    715, 67, 618, 276, 204, 918, 873, 777, 604, 560,
+    951, 160, 578, 722, 79, 804, 96, 409, 713, 940,
+    652, 934, 970, 447, 318, 353, 859, 672, 112, 785,
+    645, 863, 803, 350, 139, 93, 354, 99, 820, 908,
+    609, 772, 154, 274, 580, 184, 79, 626, 630, 742,
+    653, 282, 762, 623, 680, 81, 927, 626, 789, 125,
+    411, 521, 938, 300, 821, 78, 343, 175, 128, 250,
+    170, 774, 972, 275, 999, 639, 495, 78, 352, 126,
+    857, 956, 358, 619, 580, 124, 737, 594, 701, 612,
+    669, 112, 134, 694, 363, 992, 809, 743, 168, 974,
+    944, 375, 748, 52, 600, 747, 642, 182, 862, 81,
+    344, 805, 988, 739, 511, 655, 814, 334, 249, 515,
+    897, 955, 664, 981, 649, 113, 974, 459, 893, 228,
+    433, 837, 553, 268, 926, 240, 102, 654, 459, 51,
+    686, 754, 806, 760, 493, 403, 415, 394, 687, 700,
+    946, 670, 656, 610, 738, 392, 760, 799, 887, 653,
+    978, 321, 576, 617, 626, 502, 894, 679, 243, 440,
+    680, 879, 194, 572, 640, 724, 926, 56, 204, 700,
+    707, 151, 457, 449, 797, 195, 791, 558, 945, 679,
+    297, 59, 87, 824, 713, 663, 412, 693, 342, 606,
+    134, 108, 571, 364, 631, 212, 174, 643, 304, 329,
+    343, 97, 430, 751, 497, 314, 983, 374, 822, 928,
+    140, 206, 73, 263, 980, 736, 876, 478, 430, 305,
+    170, 514, 364, 692, 829, 82, 855, 953, 676, 246,
+    369, 970, 294, 750, 807, 827, 150, 790, 288, 923,
+    804, 378, 215, 828, 592, 281, 565, 555, 710, 82,
+    896, 831, 547, 261, 524, 462, 293, 465, 502, 56,
+    661, 821, 976, 991, 658, 869, 905, 758, 745, 193,
+    768, 550, 608, 933, 378, 286, 215, 979, 792, 961,
+    61, 688, 793, 644, 986, 403, 106, 366, 905, 644,
+    372, 567, 466, 434, 645, 210, 389, 550, 919, 135,
+    780, 773, 635, 389, 707, 100, 626, 958, 165, 504,
+    920, 176, 193, 713, 857, 265, 203, 50, 668, 108,
+    645, 990, 626, 197, 510, 357, 358, 850, 858, 364,
+    936, 638
+  };
+
+}
diff --git a/loci/formats/codec/Base64Codec.java b/loci/formats/codec/Base64Codec.java
new file mode 100644
index 0000000..1741eff
--- /dev/null
+++ b/loci/formats/codec/Base64Codec.java
@@ -0,0 +1,253 @@
+//
+// Base64Codec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.FormatException;
+
+/**
+ * Implements encoding (compress) and decoding (decompress) methods
+ * for Base64.  This code was adapted from the Jakarta Commons Codec source,
+ * http://jakarta.apache.org/commons
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/Base64Codec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/Base64Codec.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class Base64Codec extends BaseCodec implements Codec {
+
+  // Base64 alphabet and codes
+
+  private static final int FOURBYTE = 4;
+  private static final byte PAD = (byte) '=';
+
+  private static byte[] base64Alphabet = new byte[255];
+  private static byte[] lookupBase64Alphabet = new byte[255];
+
+  static {
+    for (int i=0; i<255; i++) {
+      base64Alphabet[i] = (byte) -1;
+    }
+    for (int i = 'Z'; i >= 'A'; i--) {
+      base64Alphabet[i] = (byte) (i - 'A');
+    }
+    for (int i = 'z'; i >= 'a'; i--) {
+      base64Alphabet[i] = (byte) (i - 'a' + 26);
+    }
+    for (int i = '9'; i >= '0'; i--) {
+      base64Alphabet[i] = (byte) (i - '0' + 52);
+    }
+
+    base64Alphabet['+'] = 62;
+    base64Alphabet['/'] = 63;
+
+    for (int i=0; i<=25; i++) {
+      lookupBase64Alphabet[i] = (byte) ('A' + i);
+    }
+
+    for (int i=26, j=0; i<=51; i++, j++) {
+      lookupBase64Alphabet[i] = (byte) ('a' + j);
+    }
+
+    for (int i=52, j=0; i<=61; i++, j++) {
+      lookupBase64Alphabet[i] = (byte) ('0' + j);
+    }
+
+    lookupBase64Alphabet[62] = (byte) '+';
+    lookupBase64Alphabet[63] = (byte) '/';
+  }
+
+  /**
+   * Encodes a block of data into Base64.
+   *
+   * @param input the data to be encoded.
+   * @param x ignored.
+   * @param y ignored.
+   * @param dims ignored.
+   * @param options ignored.
+   * @return The encoded data.
+   */
+  public byte[] compress(byte[] input, int x, int y, int[] dims,
+    Object options) throws FormatException
+  {
+    int dataBits = input.length * 8;
+    int fewerThan24 = dataBits % 24;
+    int numTriples = dataBits / 24;
+    byte[] encoded = null;
+    int encodedLength = 0;
+
+    if (fewerThan24 != 0) encodedLength = (numTriples + 1) * 4;
+    else encodedLength = numTriples * 4;
+
+    encoded = new byte[encodedLength];
+
+    byte k, l, b1, b2, b3;
+
+    int encodedIndex = 0;
+    int dataIndex = 0;
+
+    for (int i=0; i<numTriples; i++) {
+      dataIndex = i * 3;
+      b1 = input[dataIndex];
+      b2 = input[dataIndex + 1];
+      b3 = input[dataIndex + 2];
+
+      l = (byte) (b2 & 0x0f);
+      k = (byte) (b1 & 0x03);
+
+      byte v1 = ((b1 & -128) == 0) ? (byte) (b1 >> 2) :
+        (byte) ((b1) >> 2 ^ 0xc0);
+      byte v2 = ((b2 & -128) == 0) ? (byte) (b2 >> 4) :
+        (byte) ((b2) >> 4 ^ 0xf0);
+      byte v3 = ((b3 & -128) == 0) ? (byte) (b3 >> 6) :
+        (byte) ((b3) >> 6 ^ 0xfc);
+
+      encoded[encodedIndex] = lookupBase64Alphabet[v1];
+      encoded[encodedIndex + 1] = lookupBase64Alphabet[v2 | (k << 4)];
+      encoded[encodedIndex + 2] = lookupBase64Alphabet[(l << 2) | v3];
+      encoded[encodedIndex + 3] = lookupBase64Alphabet[b3 & 0x3f];
+      encodedIndex += 4;
+    }
+
+    dataIndex = numTriples * 3;
+
+    if (fewerThan24 == 8) {
+      b1 = input[dataIndex];
+      k = (byte) (b1 & 0x03);
+      byte v = ((b1 & -128) == 0) ? (byte) (b1 >> 2) :
+        (byte) ((b1) >> 2 ^ 0xc0);
+      encoded[encodedIndex] = lookupBase64Alphabet[v];
+      encoded[encodedIndex + 1] = lookupBase64Alphabet[k << 4];
+      encoded[encodedIndex + 2] = (byte) '=';
+      encoded[encodedIndex + 3] = (byte) '=';
+    }
+    else if (fewerThan24 == 16) {
+      b1 = input[dataIndex];
+      b2 = input[dataIndex + 1];
+      l = (byte) (b2 & 0x0f);
+      k = (byte) (b1 & 0x03);
+
+      byte v1 = ((b1 & -128) == 0) ? (byte) (b1 >> 2) :
+        (byte) ((b1) >> 2 ^ 0xc0);
+      byte v2 = ((b2 & -128) == 0) ? (byte) (b2 >> 4) :
+        (byte) ((b2) >> 4 ^ 0xf0);
+
+      encoded[encodedIndex] = lookupBase64Alphabet[v1];
+      encoded[encodedIndex + 1] = lookupBase64Alphabet[v2 | (k << 4)];
+      encoded[encodedIndex + 2] = lookupBase64Alphabet[l << 2];
+      encoded[encodedIndex + 3] = (byte) '=';
+    }
+
+    return encoded;
+  }
+
+  /**
+   * Decompresses a block of data.
+   *
+   * @param base64Data the data to be decoded
+   * @return The decoded data
+   * @throws FormatException if data is not valid Base64 data
+   */
+  public byte[] decompress(byte[] base64Data, Object options)
+    throws FormatException
+  {
+    // TODO: Add checks for invalid data.
+    if (base64Data.length == 0) return new byte[0];
+
+    int numberQuadruple = base64Data.length / FOURBYTE;
+    byte[] decodedData = null;
+    byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+    int encodedIndex = 0;
+    int dataIndex = 0;
+
+    int lastData = base64Data.length;
+    while (base64Data[lastData - 1] == PAD) {
+      if (--lastData == 0) {
+        return new byte[0];
+      }
+    }
+    decodedData = new byte[lastData - numberQuadruple];
+
+    for (int i=0; i<numberQuadruple; i++) {
+      dataIndex = i * 4;
+      marker0 = base64Data[dataIndex + 2];
+      marker1 = base64Data[dataIndex + 3];
+
+      b1 = base64Alphabet[base64Data[dataIndex]];
+      b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+      if (marker0 != PAD && marker1 != PAD) {
+        b3 = base64Alphabet[marker0];
+        b4 = base64Alphabet[marker1];
+
+        decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+        decodedData[encodedIndex + 1] =
+          (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+        decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+      }
+      else if (marker0 == PAD) {
+        decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+      }
+      else if (marker1 == PAD) {
+        b3 = base64Alphabet[marker0];
+
+        decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+        decodedData[encodedIndex + 1] =
+          (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+      }
+      encodedIndex += 3;
+    }
+    return decodedData;
+  }
+
+  /**
+   * Decodes a Base64 String by converting to bytes and passing to the
+   * decompress method.
+   *
+   * @param s the String to be decoded
+   * @return The decoded data
+   * @throws FormatException if s is not valid Base64 data.
+   */
+  public byte[] base64Decode(String s) throws FormatException {
+    byte[] base64Data = s.getBytes();
+    return decompress(base64Data);
+  }
+
+  /**
+   * Main testing method. test is inherited from parent class.
+   *
+   * @param args ignored
+   * @throws FormatException Can only occur if there is a bug in the
+   *                         compress method.
+   */
+
+  public static void main(String[] args) throws FormatException {
+    Base64Codec c = new Base64Codec();
+    c.test();
+  }
+
+}
diff --git a/loci/formats/codec/BaseCodec.java b/loci/formats/codec/BaseCodec.java
new file mode 100644
index 0000000..3b9808d
--- /dev/null
+++ b/loci/formats/codec/BaseCodec.java
@@ -0,0 +1,186 @@
+//
+// BaseCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.util.*;
+import loci.formats.FormatException;
+import loci.formats.LogTools;
+
+/**
+ * BaseCodec contains default implementation and testing for classes
+ * implementing the Codec interface, and acts as a base class for any
+ * of the compression classes.
+ * Base 1D compression and decompression methods are not implemented here, and
+ * are left as abstract. 2D methods do simple concatenation and call to the 1D
+ * methods
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/BaseCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/BaseCodec.java">SVN</a></dd></dl>
+ *
+ * @author Eric Kjellman egkjellman at wisc.edu
+ */
+public abstract class BaseCodec implements Codec {
+
+  // -- BaseCodec API methods --
+
+  /**
+   * Main testing method default implementation.
+   *
+   * This method tests whether the data is the same after compressing and
+   * decompressing, as well as doing a basic test of the 2D methods.
+   *
+   * @throws FormatException Can only occur if there is a bug in the
+   *   compress method.
+   */
+  public void test() throws FormatException {
+    byte[] testdata = new byte[50000];
+    Random r = new Random();
+    LogTools.println("Testing " + this.getClass().getName());
+    LogTools.println("Generating random data");
+    r.nextBytes(testdata);
+    LogTools.println("Compressing data");
+    byte[] compressed = compress(testdata, 0, 0, null, null);
+    LogTools.println("Compressed size: " + compressed.length);
+    LogTools.println("Decompressing data");
+    byte[] decompressed = decompress(compressed);
+    LogTools.print("Comparing data... ");
+    if (testdata.length != decompressed.length) {
+      LogTools.println("Test data differs in length from uncompressed data");
+      LogTools.println("Exiting...");
+      System.exit(-1);
+    }
+    else {
+      boolean equalsFlag = true;
+      for (int i = 0; i < testdata.length; i++) {
+        if (testdata[i] != decompressed[i]) {
+          LogTools.println("Test data and uncompressed data differs at byte" +
+                             i);
+          equalsFlag = false;
+        }
+      }
+      if (!equalsFlag) {
+        LogTools.println("Comparison failed. \nExiting...");
+        System.exit(-1);
+      }
+    }
+    LogTools.println("Success.");
+    LogTools.println("Generating 2D byte array test");
+    byte[][] twoDtest = new byte[100][500];
+    for (int i = 0; i < 100; i++) {
+      System.arraycopy(testdata, 500*i, twoDtest[i], 0, 500);
+    }
+    byte[] twoDcompressed = compress(twoDtest, 0, 0, null, null);
+    LogTools.print("Comparing compressed data... ");
+    if (twoDcompressed.length != compressed.length) {
+      LogTools.println("1D and 2D compressed data not same length");
+      LogTools.println("Exiting...");
+      System.exit(-1);
+    }
+    boolean equalsFlag = true;
+    for (int i = 0; i < twoDcompressed.length; i++) {
+      if (twoDcompressed[i] != compressed[i]) {
+        LogTools.println("1D data and 2D compressed data differs at byte" +
+                           i);
+        equalsFlag = false;
+      }
+      if (!equalsFlag) {
+        LogTools.println("Comparison failed. \nExiting...");
+        System.exit(-1);
+      }
+    }
+    LogTools.println("Success.");
+    LogTools.println("Test complete.");
+  }
+
+  // -- Codec API methods --
+
+  /**
+   * 2D data block encoding default implementation.
+   * This method simply concatenates data[0] + data[1] + ... + data[i] into
+   * a 1D block of data, then calls the 1D version of compress.
+   *
+   * @param data The data to be compressed.
+   * @param x Length of the x dimension of the image data, if appropriate.
+   * @param y Length of the y dimension of the image data, if appropriate.
+   * @param dims The dimensions of the image data, if appropriate.
+   * @param options Options to be used during compression, if appropriate.
+   * @return The compressed data.
+   * @throws FormatException If input is not a compressed data block of the
+   *   appropriate type.
+   */
+  public byte[] compress(byte[][] data, int x, int y,
+    int[] dims, Object options) throws FormatException
+  {
+    int len = 0;
+    for (int i = 0; i < data.length; i++) {
+      len += data[i].length;
+    }
+    byte[] toCompress = new byte[len];
+    int curPos = 0;
+    for (int i = 0; i < data.length; i++) {
+      System.arraycopy(data[i], 0, toCompress, curPos, data[i].length);
+      curPos += data[i].length;
+    }
+    return compress(toCompress, x, y, dims, options);
+  }
+
+  /* @see Codec#decompress(byte[]) */
+  public byte[] decompress(byte[] data) throws FormatException {
+    return decompress(data, null);
+  }
+
+  /* @see Codec#decompress(byte[][]) */
+  public byte[] decompress(byte[][] data) throws FormatException {
+    return decompress(data, null);
+  }
+
+  /**
+   * 2D data block decoding default implementation.
+   * This method simply concatenates data[0] + data[1] + ... + data[i] into
+   * a 1D block of data, then calls the 1D version of decompress.
+   *
+   * @param data The data to be decompressed.
+   * @return The decompressed data.
+   * @throws FormatException If input is not a compressed data block of the
+   *   appropriate type.
+   */
+  public byte[] decompress(byte[][] data, Object options)
+    throws FormatException
+  {
+    int len = 0;
+    for (int i = 0; i < data.length; i++) {
+      len += data[i].length;
+    }
+    byte[] toDecompress = new byte[len];
+    int curPos = 0;
+    for (int i = 0; i < data.length; i++) {
+      System.arraycopy(data[i], 0, toDecompress, curPos, data[i].length);
+      curPos += data[i].length;
+    }
+    return decompress(toDecompress, options);
+  }
+
+}
diff --git a/loci/formats/codec/BitBuffer.java b/loci/formats/codec/BitBuffer.java
new file mode 100644
index 0000000..c296ea9
--- /dev/null
+++ b/loci/formats/codec/BitBuffer.java
@@ -0,0 +1,220 @@
+//
+// BitBuffer.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.util.Random; // used in main method test
+import loci.formats.LogTools;
+
+/**
+ * A class for reading arbitrary numbers of bits from a byte array.
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/BitBuffer.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/BitBuffer.java">SVN</a></dd></dl>
+ *
+ * @author Eric Kjellman egkjellman at wisc.edu
+ */
+public class BitBuffer {
+
+  // Various bitmasks for the 0000xxxx side of a byte
+  private static final int[] BACK_MASK = {
+    0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F
+  };
+
+  // Various bitmasks for the xxxx0000 side of a byte
+  private static final int[] FRONT_MASK = {
+    0x0000, 0x0080, 0x00C0, 0x00E0, 0x00F0, 0x00F8, 0x00FC, 0x00FE
+  };
+
+  private int currentByte;
+  private int currentBit;
+  private byte[] byteBuffer;
+  private int eofByte;
+  private boolean eofFlag;
+
+  /**
+   * Default constructor.
+   */
+  public BitBuffer(byte[] byteBuffer) {
+    this.byteBuffer = byteBuffer;
+    currentByte = 0;
+    currentBit = 0;
+    eofByte = byteBuffer.length;
+  }
+
+  /**
+   * Skips a number of bits in the BitBuffer.
+   *
+   * @param bits Number of bits to skip
+   */
+  public void skipBits(long bits) {
+    if(bits < 0) {
+      throw new IllegalArgumentException("Bits to skip may not be negative");
+    }
+
+    // handles skipping past eof
+    if((long) eofByte * 8 < (long) currentByte * 8 + currentBit + bits) {
+      eofFlag = true;
+      currentByte = eofByte;
+      currentBit = 0;
+      return;
+    }
+
+    int skipBytes = (int) (bits / 8);
+    int skipBits = (int) (bits % 8);
+    currentByte += skipBytes;
+    currentBit += skipBits;
+    if(currentBit >= 8) {
+      currentByte++;
+      currentBit -= 8;
+    }
+  }
+
+  /**
+   * Returns an int value representing the value of the bits read from
+   * the byte array, from the current position. Bits are extracted from the
+   * "left side" or high side of the byte.<p>
+   * The current position is modified by this call.<p>
+   * Bits are pushed into the int from the right, endianness is not
+   * considered by the method on its own. So, if 5 bits were read from the
+   * buffer "10101", the int would be the integer representation of
+   * 000...0010101 on the target machine. <p>
+   * In general, this also means the result will be positive unless a full
+   * 32 bits are read. <p>
+   * Requesting more than 32 bits is allowed, but only up to 32 bits worth of
+   * data will be returned (the last 32 bits read). <p>
+   *
+   * @param bitsToRead the number of bits to read from the bit buffer
+   * @return the value of the bits read
+   */
+
+  public int getBits(int bitsToRead) {
+    if (bitsToRead == 0) return 0;
+    if (eofFlag) return -1; // Already at end of file
+    int toStore = 0;
+    while (bitsToRead != 0 && !eofFlag) {
+      // if we need to read from more than the current byte in the buffer...
+      if (bitsToRead >= 8 - currentBit) {
+        if (currentBit == 0) {
+          // we can read in a whole byte, so we'll do that.
+          toStore = toStore << 8;
+          int cb = ((int) byteBuffer[currentByte]);
+          toStore += (cb<0 ? (int) 256 + cb : (int) cb);
+          bitsToRead -= 8;
+          currentByte++;
+        }
+        else {
+          // otherwise, only read the appropriate number of bits off the back
+          // side of the byte, in order to "finish" the current byte in the
+          // buffer.
+          toStore = toStore << (8 - currentBit);
+          toStore += ((int)
+            byteBuffer[currentByte]) & BACK_MASK[8 - currentBit];
+          bitsToRead -= (8 - currentBit);
+          currentBit = 0;
+          currentByte++;
+        }
+      }
+      else {
+        // We will be able to finish using the current byte.
+        // read the appropriate number of bits off the front side of the byte,
+        // then push them into the int.
+        toStore = toStore << bitsToRead;
+        int cb = ((int) byteBuffer[currentByte]);
+        cb = (cb<0 ? (int) 256 + cb : (int) cb);
+        toStore += ((cb) & (0x00FF - FRONT_MASK[currentBit])) >>
+          (8 - (currentBit + bitsToRead));
+        currentBit += bitsToRead;
+        bitsToRead = 0;
+      }
+      // If we reach the end of the buffer, return what we currently have.
+      if (currentByte == eofByte) {
+        eofFlag = true;
+        return toStore;
+      }
+    }
+    return toStore;
+  }
+
+  /**
+   * Testing method.
+   * @param args Ignored.
+   */
+  public static void main(String[] args) {
+    int trials = 50000;
+    int[] nums = new int[trials];
+    int[] len = new int[trials];
+    BitWriter bw = new BitWriter();
+    int totallen = 0;
+
+    Random r = new Random();
+    LogTools.println("Generating " + trials + " trials.");
+    LogTools.println("Writing to byte array");
+    // we want the trials to be able to be all possible bit lengths.
+    // r.nextInt() by itself is not sufficient... in 50000 trials it would be
+    // extremely unlikely to produce bit strings of 1 bit.
+    // instead, we randomly choose from 0 to 2^(i % 32).
+    // Except, 1 << 31 is a negative number in two's complement, so we make it
+    // a random number in the entire range.
+    for(int i = 0; i < trials; i++) {
+      if(31 == i % 32) {
+        nums[i] = r.nextInt();
+      }
+      else {
+        nums[i] = r.nextInt(1 << (i % 32));
+      }
+      // How many bits are required to represent this number?
+      len[i] = (Integer.toBinaryString(nums[i])).length();
+      totallen += len[i];
+      bw.write(nums[i], len[i]);
+    }
+    BitBuffer bb = new BitBuffer(bw.toByteArray());
+    int readint;
+    LogTools.println("Reading from BitBuffer");
+    // Randomly skip or read bytes
+    for(int i = 0; i < trials; i++) {
+      int c = r.nextInt(100);
+      if(c > 50) {
+        readint = bb.getBits(len[i]);
+        if(readint != nums[i]) {
+          LogTools.println("Error at #" + i + ": " + readint + " received, " +
+            nums[i] + " expected.");
+        }
+      }
+      else {
+        bb.skipBits(len[i]);
+      }
+    }
+    // Test reading past end of buffer.
+    LogTools.println("Testing end of buffer");
+    bb = new BitBuffer(bw.toByteArray());
+    // The total length could be mid byte. Add one byte to test.
+    bb.skipBits(totallen + 8);
+    int read = bb.getBits(1);
+    if(-1 != read) {
+      LogTools.println("-1 expected at end of buffer, " +
+                         read + " received.");
+    }
+  }
+}
diff --git a/loci/formats/codec/BitWriter.java b/loci/formats/codec/BitWriter.java
new file mode 100644
index 0000000..189839c
--- /dev/null
+++ b/loci/formats/codec/BitWriter.java
@@ -0,0 +1,178 @@
+//
+// BitWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.util.*;
+import loci.formats.LogTools;
+
+/**
+ * A class for writing arbitrary numbers of bits to a byte array.
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/BitWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/BitWriter.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class BitWriter {
+
+  // -- Fields --
+
+  /** Buffer storing all bits written thus far. */
+  private byte[] buf;
+
+  /** Byte index into the buffer. */
+  private int index;
+
+  /** Bit index into current byte of the buffer. */
+  private int bit;
+
+  // -- Constructors --
+
+  /** Constructs a new bit writer. */
+  public BitWriter() { this(10); }
+
+  /** Constructs a new bit writer with the given initial buffer size. */
+  public BitWriter(int size) { buf = new byte[size]; }
+
+  // -- BitWriter API methods --
+
+  /** Writes the given value using the given number of bits. */
+  public void write(int value, int numBits) {
+    byte[] bits = new byte[numBits];
+    for (int i=0; i<numBits; i++) {
+      bits[i] = (byte) (value & 0x0001);
+      value >>= 1;
+    }
+    for (int i=numBits-1; i>=0; i--) {
+      int b = bits[i] << (7 - bit);
+      buf[index] |= b;
+      bit++;
+      if (bit > 7) {
+        bit = 0;
+        index++;
+        if (index >= buf.length) {
+          // buffer is full; increase the size
+          byte[] newBuf = new byte[buf.length * 2];
+          System.arraycopy(buf, 0, newBuf, 0, buf.length);
+          buf = newBuf;
+        }
+      }
+    }
+  }
+
+  /**
+   *  Writes the bits represented by a bit string to the buffer.
+   *  All characters in the string must be 0 or 1, or this will
+   *  throw an IllegalArgumentException */
+
+  public void write(String bitString) {
+    for (int i = 0; i < bitString.length(); i++) {
+      if ('1' == bitString.charAt(i)) {
+        int b = 1 << (7 - bit);
+        buf[index] |= b;
+      }
+      else if ('0' != bitString.charAt(i)) {
+        throw new IllegalArgumentException(bitString.charAt(i) +
+          "found at character " + i +
+          "; 0 or 1 expected. Write only partially completed.");
+      }
+      bit++;
+      if (bit > 7) {
+        bit = 0;
+        index++;
+        if (index >= buf.length) {
+          // buffer is full; increase the size
+          byte[] newBuf = new byte[buf.length * 2];
+          System.arraycopy(buf, 0, newBuf, 0, buf.length);
+          buf = newBuf;
+        }
+      }
+    }
+  }
+
+  /** Gets an array containing all bits written thus far. */
+  public byte[] toByteArray() {
+    int size = index;
+    if (bit > 0) size++;
+    byte[] b = new byte[size];
+    System.arraycopy(buf, 0, b, 0, size);
+    return b;
+  }
+
+  // -- Main method --
+
+  /** Tests the BitWriter class. */
+  public static void main(String[] args) {
+    int max = 50000;
+    // randomize values
+    LogTools.println("Generating random list of " + max + " values");
+    int[] values = new int[max];
+    int[] bits = new int[max];
+    double log2 = Math.log(2);
+    for (int i=0; i<values.length; i++) {
+      values[i] = (int) (50000 * Math.random()) + 1;
+      int minBits = (int) Math.ceil(Math.log(values[i] + 1) / log2);
+      bits[i] = (int) (10 * Math.random()) + minBits;
+    }
+
+    // write values out
+    LogTools.println("Writing values to byte array");
+    BitWriter out = new BitWriter();
+    for (int i=0; i<values.length; i++) out.write(values[i], bits[i]);
+
+    // read values back in
+    LogTools.println("Reading values from byte array");
+    BitBuffer bb = new BitBuffer(out.toByteArray());
+    for (int i=0; i<values.length; i++) {
+      int value = bb.getBits(bits[i]);
+      if (value != values[i]) {
+        LogTools.println("Value #" + i + " does not match (got " +
+          value + "; expected " + values[i] + "; " + bits[i] + " bits)");
+      }
+    }
+
+    // Testing string functionality
+    Random r = new Random();
+    LogTools.println("Generating 5000 random bits for String test");
+    StringBuffer sb = new StringBuffer(5000);
+    for (int i = 0; i < 5000; i++) {
+      sb.append(r.nextInt(2));
+    }
+    out = new BitWriter();
+    LogTools.println("Writing values to byte array");
+    out.write(sb.toString());
+    LogTools.println("Reading values from byte array");
+    bb = new BitBuffer(out.toByteArray());
+    for (int i = 0; i < 5000; i++) {
+      int value = bb.getBits(1);
+      int expected = (sb.charAt(i) == '1') ? 1 : 0;
+      if (value != expected) {
+        LogTools.println("Bit #" + i + " does not match (got " + value +
+          "; expected " + expected + ".");
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/codec/ByteVector.java b/loci/formats/codec/ByteVector.java
new file mode 100644
index 0000000..895c28f
--- /dev/null
+++ b/loci/formats/codec/ByteVector.java
@@ -0,0 +1,92 @@
+//
+// ByteVector.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+/**
+ * A growable array of bytes.
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/ByteVector.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/ByteVector.java">SVN</a></dd></dl>
+ *
+ * @author Wayne Rasband wsr at nih.gov
+ */
+public class ByteVector {
+  private byte[] data;
+  private int size;
+
+  public ByteVector() {
+    data = new byte[10];
+    size = 0;
+  }
+
+  public ByteVector(int initialSize) {
+    data = new byte[initialSize];
+    size = 0;
+  }
+
+  public ByteVector(byte[] byteBuffer) {
+    data = byteBuffer;
+    size = 0;
+  }
+
+  public void add(byte x) {
+    while (size >= data.length) doubleCapacity();
+    data[size++] = x;
+  }
+
+  public int size() {
+    return size;
+  }
+
+  public void add(byte[] array) { add(array, 0, array.length); }
+
+  public void add(byte[] array, int off, int len) {
+    while (data.length < size + len) doubleCapacity();
+    if (len == 1) data[size] = array[off];
+    else if (len < 35) {
+      // for loop is faster for small number of elements
+      for (int i=0; i<len; i++) data[size + i] = array[off + i];
+    }
+    else System.arraycopy(array, off, data, size, len);
+    size += len;
+  }
+
+  void doubleCapacity() {
+    byte[] tmp = new byte[data.length*2 + 1];
+    System.arraycopy(data, 0, tmp, 0, data.length);
+    data = tmp;
+  }
+
+  public void clear() {
+    size = 0;
+  }
+
+  public byte[] toByteArray() {
+    byte[] bytes = new byte[size];
+    System.arraycopy(data, 0, bytes, 0, size);
+    return bytes;
+  }
+
+}
diff --git a/loci/formats/codec/CBZip2InputStream.java b/loci/formats/codec/CBZip2InputStream.java
new file mode 100644
index 0000000..4efcfcb
--- /dev/null
+++ b/loci/formats/codec/CBZip2InputStream.java
@@ -0,0 +1,951 @@
+//
+// CBZip2InputStream.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron at aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package loci.formats.codec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import loci.formats.LogTools;
+
+/**
+ * An input stream that decompresses from the BZip2 format (without the file
+ * header chars) to be read as any other stream.
+ *
+ * <p>The decompression requires large amounts of memory. Thus you
+ * should call the {@link #close() close()} method as soon as
+ * possible, to force <tt>CBZip2InputStream</tt> to release the
+ * allocated memory.</p>
+ *
+ * <p><tt>CBZip2InputStream</tt> reads bytes from the compressed
+ * source stream via the single byte {@link java.io.InputStream#read()
+ * read()} method exclusively. Thus you should consider to use a
+ * buffered source stream.</p>
+ *
+ * <p>Instances of this class are not threadsafe.</p>
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/CBZip2InputStream.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/CBZip2InputStream.java">SVN</a></dd></dl>
+ */
+public class CBZip2InputStream extends InputStream implements BZip2Constants {
+
+  private static void reportCRCError() throws IOException {
+    // The clean way would be to throw an exception.
+    //throw new IOException("crc error");
+
+    // Just print a message, like the previous versions of this class did
+    LogTools.println("BZip2 CRC error");
+  }
+
+  private void makeMaps() {
+    final boolean[] inUse  = this.data.inUse;
+    final byte[] seqToUnseq = this.data.seqToUnseq;
+
+    int nInUseShadow = 0;
+
+    for (int i = 0; i < 256; i++) {
+      if (inUse[i]) seqToUnseq[nInUseShadow++] = (byte) i;
+    }
+
+    this.nInUse = nInUseShadow;
+  }
+
+  /**
+   * Index of the last char in the block, so the block size == last + 1.
+   */
+  private int  last;
+
+  /**
+   * Index in zptr[] of original string after sorting.
+   */
+  private int  origPtr;
+
+  /**
+   * always: in the range 0 .. 9.
+   * The current block size is 100000 * this number.
+   */
+  private int blockSize100k;
+
+  private boolean blockRandomised;
+
+  private int bsBuff;
+  private int bsLive;
+  private final CRC crc = new CRC();
+
+  private int nInUse;
+
+  private InputStream in;
+
+  private int currentChar = -1;
+
+  private static final int EOF            = 0;
+  private static final int START_BLOCK_STATE = 1;
+  private static final int RAND_PART_A_STATE = 2;
+  private static final int RAND_PART_B_STATE = 3;
+  private static final int RAND_PART_C_STATE = 4;
+  private static final int NO_RAND_PART_A_STATE = 5;
+  private static final int NO_RAND_PART_B_STATE = 6;
+  private static final int NO_RAND_PART_C_STATE = 7;
+
+  private int currentState = START_BLOCK_STATE;
+
+  private int storedBlockCRC, storedCombinedCRC;
+  private int computedBlockCRC, computedCombinedCRC;
+
+  // Variables used by setup* methods exclusively
+
+  private int suCount;
+  private int suCh2;
+  private int suChPrev;
+  private int suI2;
+  private int suJ2;
+  private int suRNToGo;
+  private int suRTPos;
+  private int suTPos;
+  private char suZ;
+
+  /**
+   * All memory intensive stuff.
+   * This field is initialized by initBlock().
+   */
+  private CBZip2InputStream.Data data;
+
+  /**
+   * Constructs a new CBZip2InputStream which decompresses bytes read from
+   * the specified stream.
+   *
+   * <p>Although BZip2 headers are marked with the magic
+   * <tt>"Bz"</tt> this constructor expects the next byte in the
+   * stream to be the first one after the magic.  Thus callers have
+   * to skip the first two bytes. Otherwise this constructor will
+   * throw an exception. </p>
+   *
+   * @throws IOException
+   *   if the stream content is malformed or an I/O error occurs.
+   * @throws NullPointerException
+   *   if <tt>in == null</tt>
+   */
+  public CBZip2InputStream(final InputStream in) throws IOException {
+    super();
+
+    this.in = in;
+    init();
+  }
+
+  public int read() throws IOException {
+    if (this.in != null) return read0();
+    else throw new IOException("stream closed");
+  }
+
+  public int read(final byte[] dest, final int offs, final int len)
+    throws IOException
+  {
+    if (offs < 0) {
+      throw new IndexOutOfBoundsException("offs(" + offs + ") < 0.");
+    }
+    if (len < 0) {
+      throw new IndexOutOfBoundsException("len(" + len + ") < 0.");
+    }
+    if (offs + len > dest.length) {
+      throw new IndexOutOfBoundsException("offs(" + offs + ") + len(" +
+        len + ") > dest.length(" + dest.length + ").");
+    }
+    if (this.in == null) throw new IOException("stream closed");
+
+    final int hi = offs + len;
+    int destOffs = offs;
+    for (int b; (destOffs < hi) && ((b = read0()) >= 0);) {
+      dest[destOffs++] = (byte) b;
+    }
+
+    return (destOffs == offs) ? -1 : (destOffs - offs);
+  }
+
+  private int read0() throws IOException {
+    final int retChar = this.currentChar;
+
+    switch (this.currentState) {
+      case EOF:
+        return -1;
+
+      case START_BLOCK_STATE:
+        throw new IllegalStateException();
+
+      case RAND_PART_A_STATE:
+        throw new IllegalStateException();
+
+      case RAND_PART_B_STATE:
+        setupRandPartB();
+        break;
+
+      case RAND_PART_C_STATE:
+        setupRandPartC();
+        break;
+
+      case NO_RAND_PART_A_STATE:
+        throw new IllegalStateException();
+
+      case NO_RAND_PART_B_STATE:
+        setupNoRandPartB();
+        break;
+
+      case NO_RAND_PART_C_STATE:
+        setupNoRandPartC();
+        break;
+
+      default:
+        throw new IllegalStateException();
+    }
+
+    return retChar;
+  }
+
+  private void init() throws IOException {
+    int magic2 = this.in.read();
+    if (magic2 != 'h') {
+      throw new IOException("Stream is not BZip2 formatted: expected 'h'" +
+        " as first byte but got '" + (char) magic2 + "'");
+    }
+
+    int blockSize = this.in.read();
+    if ((blockSize < '1') || (blockSize > '9')) {
+      throw new IOException("Stream is not BZip2 formatted: illegal " +
+        "blocksize " + (char) blockSize);
+    }
+
+    this.blockSize100k = blockSize - '0';
+
+    initBlock();
+    setupBlock();
+  }
+
+  private void initBlock() throws IOException {
+    char magic0 = bsGetUByte();
+    char magic1 = bsGetUByte();
+    char magic2 = bsGetUByte();
+    char magic3 = bsGetUByte();
+    char magic4 = bsGetUByte();
+    char magic5 = bsGetUByte();
+
+    if (magic0 == 0x17 &&
+      magic1 == 0x72 &&
+      magic2 == 0x45 &&
+      magic3 == 0x38 &&
+      magic4 == 0x50 &&
+      magic5 == 0x90)
+    {
+      complete(); // end of file
+    }
+    else if (magic0 != 0x31 || // '1'
+           magic1 != 0x41 || // ')'
+           magic2 != 0x59 || // 'Y'
+           magic3 != 0x26 || // '&'
+           magic4 != 0x53 || // 'S'
+           magic5 != 0x59) // 'Y'
+    {
+      this.currentState = EOF;
+      throw new IOException("bad block header");
+    }
+    else {
+      this.storedBlockCRC = bsGetInt();
+      this.blockRandomised = bsR(1) == 1;
+
+      // Allocate data here instead in constructor, so we do not
+      // allocate it if the input file is empty.
+      if (this.data == null) {
+        this.data = new Data(this.blockSize100k);
+      }
+
+      // currBlockNo++;
+      getAndMoveToFrontDecode();
+
+      this.crc.initialiseCRC();
+      this.currentState = START_BLOCK_STATE;
+    }
+  }
+
+  private void endBlock() throws IOException {
+    this.computedBlockCRC = this.crc.getFinalCRC();
+
+    // A bad CRC is considered a fatal error.
+    if (this.storedBlockCRC != this.computedBlockCRC) {
+      // make next blocks readable without error
+      // (repair feature, not yet documented, not tested)
+      this.computedCombinedCRC =
+        (this.storedCombinedCRC << 1) | (this.storedCombinedCRC >>> 31);
+      this.computedCombinedCRC ^= this.storedBlockCRC;
+
+      reportCRCError();
+    }
+
+    this.computedCombinedCRC =
+      (this.computedCombinedCRC << 1) | (this.computedCombinedCRC >>> 31);
+    this.computedCombinedCRC ^= this.computedBlockCRC;
+  }
+
+  private void complete() throws IOException {
+    this.storedCombinedCRC = bsGetInt();
+    this.currentState = EOF;
+    this.data = null;
+
+    if (this.storedCombinedCRC != this.computedCombinedCRC) {
+      reportCRCError();
+    }
+  }
+
+  public void close() throws IOException {
+    InputStream inShadow = this.in;
+    if (inShadow != null) {
+      try {
+        if (inShadow != System.in) inShadow.close();
+      }
+      finally {
+        this.data = null;
+        this.in = null;
+      }
+    }
+  }
+
+  private int bsR(final int n) throws IOException {
+    int bsLiveShadow = this.bsLive;
+    int bsBuffShadow = this.bsBuff;
+
+    if (bsLiveShadow < n) {
+      final InputStream inShadow = this.in;
+      do {
+        int thech = inShadow.read();
+
+        if (thech < 0) throw new IOException("unexpected end of stream");
+
+        bsBuffShadow = (bsBuffShadow << 8) | thech;
+        bsLiveShadow += 8;
+      }
+      while (bsLiveShadow < n);
+
+      this.bsBuff = bsBuffShadow;
+    }
+
+    this.bsLive = bsLiveShadow - n;
+    return (bsBuffShadow >> (bsLiveShadow - n)) & ((1 << n) - 1);
+  }
+
+  private boolean bsGetBit() throws IOException {
+    int bsLiveShadow = this.bsLive;
+    int bsBuffShadow = this.bsBuff;
+
+    if (bsLiveShadow < 1) {
+      int thech = this.in.read();
+
+      if (thech < 0) throw new IOException("unexpected end of stream");
+
+      bsBuffShadow = (bsBuffShadow << 8) | thech;
+      bsLiveShadow += 8;
+      this.bsBuff = bsBuffShadow;
+    }
+
+    this.bsLive = bsLiveShadow - 1;
+    return ((bsBuffShadow >> (bsLiveShadow - 1)) & 1) != 0;
+  }
+
+  private char bsGetUByte() throws IOException {
+    return (char) bsR(8);
+  }
+
+  private int bsGetInt() throws IOException {
+    return (((((bsR(8) << 8) | bsR(8)) << 8) | bsR(8)) << 8) | bsR(8);
+  }
+
+  /** Called by createHuffmanDecodingTables() exclusively. */
+  private static void hbCreateDecodeTables(final int[] limit,
+    final int[] base, final int[] perm, final char[] length,
+    final int minLen, final int maxLen, final int alphaSize)
+  {
+    for (int i = minLen, pp = 0; i <= maxLen; i++) {
+      for (int j = 0; j < alphaSize; j++) {
+        if (length[j] == i) perm[pp++] = j;
+      }
+    }
+
+    for (int i = MAX_CODE_LEN; --i > 0;) {
+      base[i] = 0;
+      limit[i] = 0;
+    }
+
+    for (int i = 0; i < alphaSize; i++) {
+      base[length[i] + 1]++;
+    }
+
+    for (int i = 1, b = base[0]; i < MAX_CODE_LEN; i++) {
+      b += base[i];
+      base[i] = b;
+    }
+
+    for (int i = minLen, vec = 0, b = base[i]; i <= maxLen; i++) {
+      final int nb = base[i + 1];
+      vec += nb - b;
+      b = nb;
+      limit[i] = vec - 1;
+      vec <<= 1;
+    }
+
+    for (int i = minLen + 1; i <= maxLen; i++) {
+      base[i] = ((limit[i - 1] + 1) << 1) - base[i];
+    }
+  }
+
+  private void recvDecodingTables() throws IOException {
+    final Data dataShadow    = this.data;
+    final boolean[] inUse    = dataShadow.inUse;
+    final byte[] pos       = dataShadow.recvDecodingTablesPos;
+    final byte[] selector    = dataShadow.selector;
+    final byte[] selectorMtf  = dataShadow.selectorMtf;
+
+    int inUse16 = 0;
+
+    // Receive the mapping table
+    for (int i = 0; i < 16; i++) {
+      if (bsGetBit()) {
+        inUse16 |= 1 << i;
+      }
+    }
+
+    for (int i = 256; --i >= 0;) inUse[i] = false;
+
+    for (int i = 0; i < 16; i++) {
+      if ((inUse16 & (1 << i)) != 0) {
+        final int i16 = i << 4;
+        for (int j = 0; j < 16; j++) {
+          if (bsGetBit()) inUse[i16 + j] = true;
+        }
+      }
+    }
+
+    makeMaps();
+    final int alphaSize = this.nInUse + 2;
+
+    // Now the selectors
+    final int nGroups = bsR(3);
+    final int nSelectors = bsR(15);
+
+    for (int i = 0; i < nSelectors; i++) {
+      int j = 0;
+      while (bsGetBit()) j++;
+      selectorMtf[i] = (byte) j;
+    }
+
+    // Undo the MTF values for the selectors.
+    for (int v = nGroups; --v >= 0;) pos[v] = (byte) v;
+
+    for (int i = 0; i < nSelectors; i++) {
+      int v = selectorMtf[i] & 0xff;
+      final byte tmp = pos[v];
+      while (v > 0) {
+        // nearly all times v is zero, 4 in most other cases
+        pos[v] = pos[v - 1];
+        v--;
+      }
+      pos[0] = tmp;
+      selector[i] = tmp;
+    }
+
+    final char[][] len  = dataShadow.tempCharArray2d;
+
+    // Now the coding tables
+    for (int t = 0; t < nGroups; t++) {
+      int curr = bsR(5);
+      final char[] tLen = len[t];
+      for (int i = 0; i < alphaSize; i++) {
+        while (bsGetBit()) curr += bsGetBit() ? -1 : 1;
+        tLen[i] = (char) curr;
+      }
+    }
+
+    // finally create the Huffman tables
+    createHuffmanDecodingTables(alphaSize, nGroups);
+  }
+
+  /** Called by recvDecodingTables() exclusively. */
+  private void createHuffmanDecodingTables(final int alphaSize,
+    final int nGroups)
+  {
+    final Data dataShadow = this.data;
+    final char[][] len  = dataShadow.tempCharArray2d;
+    final int[] minLens = dataShadow.minLens;
+    final int[][] limit = dataShadow.limit;
+    final int[][] base  = dataShadow.base;
+    final int[][] perm  = dataShadow.perm;
+
+    for (int t = 0; t < nGroups; t++) {
+      int minLen = 32;
+      int maxLen = 0;
+      final char[] tLen = len[t];
+      for (int i = alphaSize; --i >= 0;) {
+        final char lent = tLen[i];
+        if (lent > maxLen) maxLen = lent;
+        if (lent < minLen) minLen = lent;
+      }
+      hbCreateDecodeTables(limit[t], base[t], perm[t], len[t], minLen,
+        maxLen, alphaSize);
+      minLens[t] = minLen;
+    }
+  }
+
+  private void getAndMoveToFrontDecode() throws IOException {
+    this.origPtr = bsR(24);
+    recvDecodingTables();
+
+    final InputStream inShadow = this.in;
+    final Data dataShadow  = this.data;
+    final byte[] ll8      = dataShadow.ll8;
+    final int[] unzftab    = dataShadow.unzftab;
+    final byte[] selector  = dataShadow.selector;
+    final byte[] seqToUnseq = dataShadow.seqToUnseq;
+    final char[] yy      = dataShadow.getAndMoveToFrontDecodeYY;
+    final int[] minLens    = dataShadow.minLens;
+    final int[][] limit    = dataShadow.limit;
+    final int[][] base    = dataShadow.base;
+    final int[][] perm    = dataShadow.perm;
+    final int limitLast    = this.blockSize100k * 100000;
+
+    // Setting up the unzftab entries here is not strictly
+    // necessary, but it does save having to do it later
+    // in a separate pass, and so saves a block's worth of
+    // cache misses.
+    for (int i = 256; --i >= 0;) {
+      yy[i] = (char) i;
+      unzftab[i] = 0;
+    }
+
+    int groupNo    = 0;
+    int groupPos   = G_SIZE - 1;
+    final int eob  = this.nInUse + 1;
+    int nextSym    = getAndMoveToFrontDecode0(0);
+    int bsBuffShadow    = this.bsBuff;
+    int bsLiveShadow    = this.bsLive;
+    int lastShadow      = -1;
+    int zt       = selector[groupNo] & 0xff;
+    int[] baseZT  = base[zt];
+    int[] limitZT  = limit[zt];
+    int[] permZT  = perm[zt];
+    int minLensZT  = minLens[zt];
+
+    while (nextSym != eob) {
+      if ((nextSym == RUNA) || (nextSym == RUNB)) {
+        int s = -1;
+
+        for (int n = 1; true; n <<= 1) {
+          if (nextSym == RUNA) s += n;
+          else if (nextSym == RUNB) s += n << 1;
+          else break;
+
+          if (groupPos == 0) {
+            groupPos   = G_SIZE - 1;
+            zt       = selector[++groupNo] & 0xff;
+            baseZT    = base[zt];
+            limitZT   = limit[zt];
+            permZT    = perm[zt];
+            minLensZT  = minLens[zt];
+          }
+          else groupPos--;
+
+          int zn = minLensZT;
+
+          // Inlined:
+          // int zvec = bsR(zn);
+          while (bsLiveShadow < zn) {
+            final int thech = inShadow.read();
+            if (thech >= 0) {
+              bsBuffShadow = (bsBuffShadow << 8) | thech;
+              bsLiveShadow += 8;
+              continue;
+            }
+            else throw new IOException("unexpected end of stream");
+          }
+          int zvec = (bsBuffShadow >> (bsLiveShadow - zn)) & ((1 << zn) - 1);
+          bsLiveShadow -= zn;
+
+          while (zvec > limitZT[zn]) {
+            zn++;
+            while (bsLiveShadow < 1) {
+              final int thech = inShadow.read();
+              if (thech >= 0) {
+                bsBuffShadow = (bsBuffShadow << 8) | thech;
+                bsLiveShadow += 8;
+                continue;
+              }
+              else throw new IOException("unexpected end of stream");
+            }
+            bsLiveShadow--;
+            zvec = (zvec << 1) | ((bsBuffShadow >> bsLiveShadow) & 1);
+          }
+          nextSym = permZT[zvec - baseZT[zn]];
+        }
+
+        final byte ch = seqToUnseq[yy[0]];
+        unzftab[ch & 0xff] += s + 1;
+
+        while (s-- >= 0) ll8[++lastShadow] = ch;
+
+        if (lastShadow >= limitLast) throw new IOException("block overrun");
+      }
+      else {
+        if (++lastShadow >= limitLast) {
+          throw new IOException("block overrun");
+        }
+
+        final char tmp = yy[nextSym - 1];
+        unzftab[seqToUnseq[tmp] & 0xff]++;
+        ll8[lastShadow] = seqToUnseq[tmp];
+
+        /*
+         This loop is hammered during decompression,
+         hence avoid native method call overhead of
+         System.arraycopy for very small ranges to copy.
+        */
+        if (nextSym <= 16) {
+          for (int j = nextSym - 1; j > 0;) yy[j] = yy[--j];
+        }
+        else System.arraycopy(yy, 0, yy, 1, nextSym - 1);
+
+        yy[0] = tmp;
+
+        if (groupPos == 0) {
+          groupPos   = G_SIZE - 1;
+          zt       = selector[++groupNo] & 0xff;
+          baseZT    = base[zt];
+          limitZT   = limit[zt];
+          permZT    = perm[zt];
+          minLensZT  = minLens[zt];
+        }
+        else groupPos--;
+
+        int zn = minLensZT;
+
+        // Inlined:
+        // int zvec = bsR(zn);
+        while (bsLiveShadow < zn) {
+          final int thech = inShadow.read();
+          if (thech >= 0) {
+            bsBuffShadow = (bsBuffShadow << 8) | thech;
+            bsLiveShadow += 8;
+            continue;
+          }
+          else throw new IOException("unexpected end of stream");
+        }
+        int zvec = (bsBuffShadow >> (bsLiveShadow - zn)) & ((1 << zn) - 1);
+        bsLiveShadow -= zn;
+
+        while (zvec > limitZT[zn]) {
+          zn++;
+          while (bsLiveShadow < 1) {
+            final int thech = inShadow.read();
+            if (thech >= 0) {
+              bsBuffShadow = (bsBuffShadow << 8) | thech;
+              bsLiveShadow += 8;
+              continue;
+            }
+            else throw new IOException("unexpected end of stream");
+          }
+          bsLiveShadow--;
+          zvec = (zvec << 1) | ((bsBuffShadow >> bsLiveShadow) & 1);
+        }
+        nextSym = permZT[zvec - baseZT[zn]];
+      }
+    }
+
+    this.last = lastShadow;
+    this.bsLive = bsLiveShadow;
+    this.bsBuff = bsBuffShadow;
+  }
+
+  private int getAndMoveToFrontDecode0(final int groupNo)
+    throws IOException
+  {
+    final InputStream inShadow  = this.in;
+    final Data dataShadow  = this.data;
+    final int zt       = dataShadow.selector[groupNo] & 0xff;
+    final int[] limitZT  = dataShadow.limit[zt];
+    int zn = dataShadow.minLens[zt];
+    int zvec = bsR(zn);
+    int bsLiveShadow = this.bsLive;
+    int bsBuffShadow = this.bsBuff;
+
+    while (zvec > limitZT[zn]) {
+      zn++;
+      while (bsLiveShadow < 1) {
+        final int thech = inShadow.read();
+
+        if (thech >= 0) {
+          bsBuffShadow = (bsBuffShadow << 8) | thech;
+          bsLiveShadow += 8;
+          continue;
+        }
+        else throw new IOException("unexpected end of stream");
+      }
+      bsLiveShadow--;
+      zvec = (zvec << 1) | ((bsBuffShadow >> bsLiveShadow) & 1);
+    }
+
+    this.bsLive = bsLiveShadow;
+    this.bsBuff = bsBuffShadow;
+
+    return dataShadow.perm[zt][zvec - dataShadow.base[zt][zn]];
+  }
+
+  private void setupBlock() throws IOException {
+    if (this.data == null) return;
+
+    final int[] cftab = this.data.cftab;
+    final int[] tt   = this.data.initTT(this.last + 1);
+    final byte[] ll8  = this.data.ll8;
+    cftab[0] = 0;
+    System.arraycopy(this.data.unzftab, 0, cftab, 1, 256);
+
+    for (int i = 1, c = cftab[0]; i <= 256; i++) {
+      c += cftab[i];
+      cftab[i] = c;
+    }
+
+    for (int i = 0, lastShadow = this.last; i <= lastShadow; i++) {
+      tt[cftab[ll8[i] & 0xff]++] = i;
+    }
+
+    if ((this.origPtr < 0) || (this.origPtr >= tt.length)) {
+      throw new IOException("stream corrupted");
+    }
+
+    this.suTPos = tt[this.origPtr];
+    this.suCount = 0;
+    this.suI2 = 0;
+    this.suCh2 = 256;  /* not a char and not EOF */
+
+    if (this.blockRandomised) {
+      this.suRNToGo = 0;
+      this.suRTPos = 0;
+      setupRandPartA();
+    }
+    else setupNoRandPartA();
+  }
+
+  private void setupRandPartA() throws IOException {
+    if (this.suI2 <= this.last) {
+      this.suChPrev = this.suCh2;
+      int suCh2Shadow = this.data.ll8[this.suTPos] & 0xff;
+      this.suTPos = this.data.tt[this.suTPos];
+      if (this.suRNToGo == 0) {
+        this.suRNToGo = BZip2Constants.R_NUMS[this.suRTPos] - 1;
+        if (++this.suRTPos == 512) this.suRTPos = 0;
+      }
+      else this.suRNToGo--;
+      this.suCh2 = suCh2Shadow ^= (this.suRNToGo == 1) ? 1 : 0;
+      this.suI2++;
+      this.currentChar = suCh2Shadow;
+      this.currentState = RAND_PART_B_STATE;
+      this.crc.updateCRC(suCh2Shadow);
+    }
+    else {
+      endBlock();
+      initBlock();
+      setupBlock();
+    }
+  }
+
+  private void setupNoRandPartA() throws IOException {
+    if (this.suI2 <= this.last) {
+      this.suChPrev = this.suCh2;
+      int suCh2Shadow = this.data.ll8[this.suTPos] & 0xff;
+      this.suCh2 = suCh2Shadow;
+      this.suTPos = this.data.tt[this.suTPos];
+      this.suI2++;
+      this.currentChar = suCh2Shadow;
+      this.currentState = NO_RAND_PART_B_STATE;
+      this.crc.updateCRC(suCh2Shadow);
+    }
+    else {
+      this.currentState = NO_RAND_PART_A_STATE;
+      endBlock();
+      initBlock();
+      setupBlock();
+    }
+  }
+
+  private void setupRandPartB() throws IOException {
+    if (this.suCh2 != this.suChPrev) {
+      this.currentState = RAND_PART_A_STATE;
+      this.suCount = 1;
+      setupRandPartA();
+    }
+    else if (++this.suCount >= 4) {
+      this.suZ = (char) (this.data.ll8[this.suTPos] & 0xff);
+      this.suTPos = this.data.tt[this.suTPos];
+      if (this.suRNToGo == 0) {
+        this.suRNToGo = BZip2Constants.R_NUMS[this.suRTPos] - 1;
+        if (++this.suRTPos == 512) {
+          this.suRTPos = 0;
+        }
+      }
+      else this.suRNToGo--;
+      this.suJ2 = 0;
+      this.currentState = RAND_PART_C_STATE;
+      if (this.suRNToGo == 1) this.suZ ^= 1;
+      setupRandPartC();
+    }
+    else {
+      this.currentState = RAND_PART_A_STATE;
+      setupRandPartA();
+    }
+  }
+
+  private void setupRandPartC() throws IOException {
+    if (this.suJ2 < this.suZ) {
+      this.currentChar = this.suCh2;
+      this.crc.updateCRC(this.suCh2);
+      this.suJ2++;
+    }
+    else {
+      this.currentState = RAND_PART_A_STATE;
+      this.suI2++;
+      this.suCount = 0;
+      setupRandPartA();
+    }
+  }
+
+  private void setupNoRandPartB() throws IOException {
+    if (this.suCh2 != this.suChPrev) {
+      this.suCount = 1;
+      setupNoRandPartA();
+    }
+    else if (++this.suCount >= 4) {
+      this.suZ = (char) (this.data.ll8[this.suTPos] & 0xff);
+      this.suTPos = this.data.tt[this.suTPos];
+      this.suJ2 = 0;
+      setupNoRandPartC();
+    }
+    else setupNoRandPartA();
+  }
+
+  private void setupNoRandPartC() throws IOException {
+    if (this.suJ2 < this.suZ) {
+      int suCh2Shadow = this.suCh2;
+      this.currentChar = suCh2Shadow;
+      this.crc.updateCRC(suCh2Shadow);
+      this.suJ2++;
+      this.currentState = NO_RAND_PART_C_STATE;
+    }
+    else {
+      this.suI2++;
+      this.suCount = 0;
+      setupNoRandPartA();
+    }
+  }
+
+  private static final class Data extends Object {
+    // (with blockSize 900k)
+    final boolean[] inUse  = new boolean[256];                  //     256 byte
+
+    final byte[] seqToUnseq  = new byte[256];                   //     256 byte
+    final byte[] selector    = new byte[MAX_SELECTORS];         //   18002 byte
+    final byte[] selectorMtf  = new byte[MAX_SELECTORS];        //   18002 byte
+
+    /**
+     * Freq table collected to save a pass over the data during
+     * decompression.
+     */
+    final int[] unzftab = new int[256];                         //    1024 byte
+
+    final int[][] limit = new int[N_GROUPS][MAX_ALPHA_SIZE];    //    6192 byte
+    final int[][] base  = new int[N_GROUPS][MAX_ALPHA_SIZE];    //    6192 byte
+    final int[][] perm  = new int[N_GROUPS][MAX_ALPHA_SIZE];    //    6192 byte
+    final int[] minLens = new int[N_GROUPS];                    //      24 byte
+
+    final int[]    cftab    = new int[257];                     //    1028 byte
+    final char[]   getAndMoveToFrontDecodeYY = new char[256];   //     512 byte
+
+    //                                                                3096 byte
+    final char[][]  tempCharArray2d  = new char[N_GROUPS][MAX_ALPHA_SIZE];
+
+    final byte[] recvDecodingTablesPos = new byte[N_GROUPS];    //       6 byte
+    //---------------
+    //   60798 byte
+
+    int[] tt;                                                   // 3600000 byte
+    byte[] ll8;                                                 //  900000 byte
+    //---------------
+    //  4560782 byte
+    //===============
+
+    Data(int blockSize100k) {
+      super();
+
+      this.ll8 = new byte[blockSize100k * BZip2Constants.BASE_BLOCK_SIZE];
+    }
+
+    /**
+     * Initializes the {@link #tt} array.
+     *
+     * This method is called when the required length of the array
+     * is known.  I don't initialize it at construction time to
+     * avoid unneccessary memory allocation when compressing small
+     * files.
+     */
+    int[] initTT(int length) {
+      int[] ttShadow = this.tt;
+
+      // tt.length should always be >= length, but theoretically
+      // it can happen, if the compressor mixed small and large
+      // blocks.  Normally only the last block will be smaller
+      // than others.
+      if ((ttShadow == null) || (ttShadow.length < length)) {
+        this.tt = ttShadow = new int[length];
+      }
+
+      return ttShadow;
+    }
+  }
+
+}
+
diff --git a/loci/formats/codec/CRC.java b/loci/formats/codec/CRC.java
new file mode 100644
index 0000000..0f26efe
--- /dev/null
+++ b/loci/formats/codec/CRC.java
@@ -0,0 +1,165 @@
+//
+// CRC.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/*
+ * Copyright  2001-2002,2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron at aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package loci.formats.codec;
+
+/**
+ * A simple class the hold and calculate the CRC for sanity checking
+ * of the data.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/CRC.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/CRC.java">SVN</a></dd></dl>
+ */
+public class CRC {
+
+  // -- Constants --
+
+  public static final int[] CRC_32_TABLE = {
+    0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
+    0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+    0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
+    0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+    0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
+    0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+    0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
+    0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+    0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+    0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+    0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
+    0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+    0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
+    0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+    0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
+    0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+    0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
+    0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+    0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+    0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+    0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
+    0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+    0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
+    0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+    0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
+    0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+    0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
+    0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+    0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+    0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+    0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
+    0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+    0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
+    0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+    0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
+    0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+    0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
+    0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+    0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+    0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+    0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
+    0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+    0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
+    0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+    0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
+    0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+    0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
+    0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+    0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+    0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+    0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
+    0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+    0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
+    0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+    0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
+    0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+    0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
+    0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+    0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+    0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+    0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
+    0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+    0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
+    0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+  };
+
+  // -- Fields --
+
+  private int globalCrc;
+
+  // -- Constructor --
+
+  public CRC() {
+    initialiseCRC();
+  }
+
+  // -- CRC API methods --
+
+  public void initialiseCRC() {
+    globalCrc = 0xffffffff;
+  }
+
+  public int getFinalCRC() {
+    return ~globalCrc;
+  }
+
+  public int getGlobalCRC() {
+    return globalCrc;
+  }
+
+  public void setGlobalCRC(int newCrc) {
+    globalCrc = newCrc;
+  }
+
+  public void updateCRC(int inCh) {
+    int temp = (globalCrc >> 24) ^ inCh;
+    if (temp < 0) {
+      temp = 256 + temp;
+    }
+    globalCrc = (globalCrc << 8) ^ CRC.CRC_32_TABLE[temp];
+  }
+
+}
+
diff --git a/loci/formats/codec/Codec.java b/loci/formats/codec/Codec.java
new file mode 100644
index 0000000..93a0300
--- /dev/null
+++ b/loci/formats/codec/Codec.java
@@ -0,0 +1,122 @@
+//
+// Codec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.FormatException;
+
+/**
+ * This class is an interface for any kind of compression or decompression.
+ * Data is presented to the compressor in a 1D or 2D byte array,
+ * with (optionally, depending on the compressor) pixel dimensions and
+ * an Object containing any other options the compressor may need.
+ *
+ * If an argument is not appropriate for the compressor type, it is expected
+ * to completely ignore the argument. i.e.: Passing a compressor that does not
+ * require pixel dimensions null for the dimensions must not cause the
+ * compressor to throw a NullPointerException.
+ *
+ * Classes implementing the Codec interface are expected to either
+ * implement both compression methods or neither. (The same is expected for
+ * decompression).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/Codec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/Codec.java">SVN</a></dd></dl>
+ *
+ * @author Eric Kjellman egkjellman at wisc.edu
+ */
+public interface Codec {
+
+  /**
+   * Compresses a block of data.
+   *
+   * @param data The data to be compressed.
+   * @param x Length of the x dimension of the image data, if appropriate.
+   * @param y Length of the y dimension of the image data, if appropriate.
+   * @param dims The dimensions of the image data, if appropriate.
+   * @param options Options to be used during compression, if appropriate.
+   * @return The compressed data.
+   * @throws FormatException If input is not a compressed data block of the
+   *   appropriate type.
+   */
+  byte[] compress(byte[] data, int x, int y,
+      int[] dims, Object options) throws FormatException;
+
+  /**
+   * Compresses a block of data.
+   *
+   * @param data The data to be compressed.
+   * @param x Length of the x dimension of the image data, if appropriate.
+   * @param y Length of the y dimension of the image data, if appropriate.
+   * @param dims The dimensions of the image data, if appropriate.
+   * @param options Options to be used during compression, if appropriate.
+   * @return The compressed data.
+   * @throws FormatException If input is not a compressed data block of the
+   *   appropriate type.
+   */
+  byte[] compress(byte[][] data, int x, int y,
+      int[] dims, Object options) throws FormatException;
+
+  /**
+   * Decompresses a block of data.
+   *
+   * @param data the data to be decompressed
+   * @param options Options to be used during decompression.
+   * @return the decompressed data.
+   * @throws FormatException If data is not valid.
+   */
+  byte[] decompress(byte[] data, Object options) throws FormatException;
+
+  /**
+   * Decompresses a block of data.
+   *
+   * @param data the data to be decompressed
+   * @param options Options to be used during decompression.
+   * @return the decompressed data.
+   * @throws FormatException If data is not valid.
+   */
+  byte[] decompress(byte[][] data, Object options) throws FormatException;
+
+  /**
+   * Decompresses a block of data.
+   *
+   * @param data the data to be decompressed.
+   * @return The decompressed data.
+   * @throws FormatException If data is not valid compressed data for this
+   *   decompressor.
+   */
+  byte[] decompress(byte[] data) throws FormatException;
+
+  /**
+   * Decompresses a block of data.
+   *
+   * @param data The data to be decompressed.
+   * @return The decompressed data.
+   * @throws FormatException If data is not valid compressed data for this
+   *   decompressor.
+   */
+  byte[] decompress(byte[][] data) throws FormatException;
+
+}
diff --git a/loci/formats/codec/JPEGCodec.java b/loci/formats/codec/JPEGCodec.java
new file mode 100644
index 0000000..7cceb59
--- /dev/null
+++ b/loci/formats/codec/JPEGCodec.java
@@ -0,0 +1,95 @@
+//
+// JPEGCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import javax.imageio.ImageIO;
+import loci.formats.*;
+
+/**
+ * This class implements JPEG decompression. Compression is not yet
+ * implemented.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/JPEGCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/JPEGCodec.java">SVN</a></dd></dl>
+ */
+public class JPEGCodec extends BaseCodec implements Codec {
+
+  /**
+   * Compresses a block of JPEG data. Currently not supported.
+   *
+   * @param data the data to be compressed
+   * @param x length of the x dimension of the image data, if appropriate
+   * @param y length of the y dimension of the image data, if appropriate
+   * @param dims the dimensions of the image data, if appropriate
+   * @param options options to be used during compression, if appropriate
+   * @return The compressed data
+   * @throws FormatException If input is not an Adobe data block.
+   */
+  public byte[] compress(byte[] data, int x, int y,
+      int[] dims, Object options) throws FormatException
+  {
+    // TODO: Add compression support.
+    throw new FormatException("JPEG Compression not currently supported");
+  }
+
+  /**
+   * Decodes an image strip using JPEG compression algorithm.
+   *
+   * @param input input data to be decompressed
+   * @return The decompressed data
+   * @throws FormatException if data is not valid compressed data for this
+   *                         decompressor
+   */
+  public byte[] decompress(byte[] input, Object options) throws FormatException
+  {
+    BufferedImage b;
+    try {
+      RandomAccessStream s = new RandomAccessStream(input);
+      while (s.read() != (byte) 0xff || s.read() != (byte) 0xd8);
+      int offset = (int) s.getFilePointer() - 2;
+      b = ImageIO.read(new BufferedInputStream(new ByteArrayInputStream(input,
+        offset, input.length - offset)));
+    }
+    catch (IOException exc) {
+      LogTools.println(
+        "An I/O error occurred decompressing image. Stack dump follows:");
+      LogTools.trace(exc);
+      return null;
+    }
+
+    byte[][] buf = ImageTools.getBytes(b);
+    byte[] rtn = new byte[buf.length * buf[0].length];
+    if (buf.length == 1) rtn = buf[0];
+    else {
+      for (int i=0; i<buf.length; i++) {
+        System.arraycopy(buf[i], 0, rtn, i*buf[0].length, buf[i].length);
+      }
+    }
+    return rtn;
+  }
+}
diff --git a/loci/formats/codec/LZOCodec.java b/loci/formats/codec/LZOCodec.java
new file mode 100644
index 0000000..4ee6929
--- /dev/null
+++ b/loci/formats/codec/LZOCodec.java
@@ -0,0 +1,219 @@
+//
+// LZOCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.FormatException;
+
+/**
+ * This class implements LZO decompression. Compression is not yet
+ * implemented.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/LZOCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/LZOCodec.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class LZOCodec extends BaseCodec implements Codec {
+
+  // LZO compression codes
+  private static final int LZO_OVERRUN = -6;
+
+  /**
+   * Compresses a block of lzo data. Currently not supported.
+   *
+   * @param data the data to be compressed
+   * @param x length of the x dimension of the image data, if appropriate
+   * @param y length of the y dimension of the image data, if appropriate
+   * @param dims the dimensions of the image data, if appropriate
+   * @param options options to be used during compression, if appropriate
+   * @return The compressed data
+   * @throws FormatException If input is not an LZO-compressed data block.
+   */
+  public byte[] compress(byte[] data, int x, int y,
+      int[] dims, Object options) throws FormatException
+  {
+    // TODO: Add LZO compression support.
+    throw new FormatException("LZO Compression not currently supported");
+  }
+
+  /**
+   * Decodes an LZO-compressed array.
+   * Adapted from LZO for Java, available at
+   * http://www.oberhumer.com/opensource/lzo/
+   *
+   * @param src the data to be decompressed
+   * @return The decompressed data
+   * @throws FormatException if data is not valid compressed data for this
+   *                         decompressor
+   */
+  public byte[] decompress(byte[] src, Object options) throws FormatException {
+    int ip = 0;
+    int op = 0;
+    byte[] dst = new byte[src.length];
+    int t = src[ip++] & 0xff;
+    int mPos;
+
+    if (t > 17) {
+      t -= 17;
+      // do dst[op++] = src[ip++]; while (--t > 0);
+      do {
+        dst[op++] = src[ip++];
+        if(op == dst.length) {
+          byte[] newdst = new byte[dst.length * 2];
+          System.arraycopy(dst, 0, newdst, 0, dst.length);
+          dst = newdst;
+        }
+      } while (--t > 0);
+      t = src[ip++] & 0xff;
+//      if (t < 16) return;
+      if(t < 16) {
+        byte[] newdst = new byte[op];
+        System.arraycopy(dst, 0, newdst, 0, op);
+        return newdst;
+      }
+    }
+
+  loop:
+    for (;; t = src[ip++] & 0xff) {
+      if (t < 16) {
+        if (t == 0) {
+          while (src[ip] == 0) {
+            t += 255;
+            ip++;
+          }
+          t += 15 + (src[ip++] & 0xff);
+        }
+        t += 3;
+        // do dst[op++] = src[ip++]; while (--t > 0);
+        do {
+          dst[op++] = src[ip++];
+          if(op == dst.length) {
+            byte[] newdst = new byte[dst.length * 2];
+            System.arraycopy(dst, 0, newdst, 0, dst.length);
+            dst = newdst;
+          }
+        } while (--t > 0);
+        t = src[ip++] & 0xff;
+        if (t < 16) {
+          mPos = op - 0x801 - (t >> 2) - ((src[ip++] & 0xff) << 2);
+          if (mPos < 0) {
+            t = LZO_OVERRUN;
+            break loop;
+          }
+          t = 3;
+          do {
+            dst[op++] = dst[mPos++];
+            if(op == dst.length || mPos == dst.length) {
+              byte[] newdst = new byte[dst.length * 2];
+              System.arraycopy(dst, 0, newdst, 0, dst.length);
+              dst = newdst;
+            }
+          } while (--t > 0);
+//          do dst[op++] = dst[mPos++]; while (--t > 0);
+          t = src[ip - 2] & 3;
+          if (t == 0) continue;
+//          do dst[op++] = src[ip++]; while (--t > 0);
+          do {
+            dst[op++] = src[ip++];
+            if(op == dst.length) {
+              byte[] newdst = new byte[dst.length * 2];
+              System.arraycopy(dst, 0, newdst, 0, dst.length);
+              dst = newdst;
+            }
+          } while (--t > 0);
+          t = src[ip++] & 0xff;
+        }
+      }
+      for (;; t = src[ip++] & 0xff) {
+        if (t >= 64) {
+          mPos = op - 1 - ((t >> 2) & 7) - ((src[ip++] & 0xff) << 3);
+          t = (t >> 5) - 1;
+        }
+        else if (t >= 32) {
+          t &= 31;
+          if (t == 0) {
+            while (src[ip] == 0) {
+              t += 255;
+              ip++;
+            }
+            t += 31 + (src[ip++] & 0xff);
+          }
+          mPos = op - 1 - ((src[ip++] & 0xff) >> 2);
+          mPos -= ((src[ip++] & 0xff) << 6);
+        }
+        else if (t >= 16) {
+          mPos = op - ((t & 8) << 11);
+          t &= 7;
+          if (t == 0) {
+            while (src[ip] == 0) {
+              t += 255;
+              ip++;
+            }
+            t += 7 + (src[ip++] & 0xff);
+          }
+          mPos -= ((src[ip++] & 0xff) >> 2);
+          mPos -= ((src[ip++] & 0xff) << 6);
+          if (mPos == op) break loop;
+          mPos -= 0x4000;
+        }
+        else {
+          mPos = op - 1 - (t >> 2) - ((src[ip++] & 0xff) << 2);
+          t = 0;
+        }
+
+        if (mPos < 0) {
+          t = LZO_OVERRUN;
+          break loop;
+        }
+
+        t += 2;
+//        do dst[op++] = dst[mPos++]; while (--t > 0);
+        do {
+          dst[op++] = dst[mPos++];
+          if(op == dst.length || mPos == dst.length) {
+            byte[] newdst = new byte[dst.length * 2];
+            System.arraycopy(dst, 0, newdst, 0, dst.length);
+            dst = newdst;
+          }
+        } while (--t > 0);
+        t = src[ip - 2] & 3;
+        if (t == 0) break;
+//        do dst[op++] = src[ip++]; while (--t > 0);
+        do {
+          dst[op++] = src[ip++];
+          if(op == dst.length) {
+            byte[] newdst = new byte[dst.length * 2];
+            System.arraycopy(dst, 0, newdst, 0, dst.length);
+            dst = newdst;
+          }
+        } while (--t > 0);
+      }
+    }
+    byte[] newdst = new byte[op];
+    System.arraycopy(dst, 0, newdst, 0, op);
+    return newdst;
+  }
+}
diff --git a/loci/formats/codec/LZWCodec.java b/loci/formats/codec/LZWCodec.java
new file mode 100644
index 0000000..ff072b7
--- /dev/null
+++ b/loci/formats/codec/LZWCodec.java
@@ -0,0 +1,186 @@
+//
+// LZWCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.FormatException;
+
+/**
+ * Implements basic LZW compression and decompression, as outlined in the
+ * TIFF 6.0 Specification at
+ * http://partners.adobe.com/asn/developer/pdfs/tn/TIFF6.pdf (page 61).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/LZWCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/LZWCodec.java">SVN</a></dd></dl>
+ *
+ * @author Eric Kjellman egkjellman at wisc.edu
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Wayne Rasband wsr at nih.gov
+ */
+public class LZWCodec extends BaseCodec implements Codec {
+
+  // LZW compression codes
+  protected static final int CLEAR_CODE = 256;
+  protected static final int EOI_CODE = 257;
+
+  /**
+   * Compresses a block of data using LZW compression. If input is null or of
+   * 0 length, simply returns input.
+   *
+   * @param input the data to be compressed
+   * @param x ignored for LZW.
+   * @param y ignored for LZW.
+   * @param dims ignored for LZW.
+   * @param options ignored for LZW.
+   * @return The compressed data
+   */
+  public byte[] compress(byte[] input, int x, int y, int[] dims,
+    Object options) throws FormatException
+  {
+    if (input == null || input.length == 0) return input;
+
+    // initialize symbol table
+    LZWTreeNode symbols = new LZWTreeNode(-1);
+    symbols.initialize();
+    int nextCode = 258;
+    int numBits = 9;
+
+    BitWriter out = new BitWriter();
+    out.write(CLEAR_CODE, numBits);
+    ByteVector omega = new ByteVector();
+    for (int i=0; i<input.length; i++) {
+      byte k = input[i];
+      LZWTreeNode omegaNode = symbols.nodeFromString(omega);
+      LZWTreeNode omegaKNode = omegaNode.getChild(k);
+      if (omegaKNode != null) {
+        // omega+k is in the symbol table
+        omega.add(k);
+      }
+      else {
+        out.write(omegaNode.getCode(), numBits);
+        omega.add(k);
+        symbols.addTableEntry(omega, nextCode++);
+        omega.clear();
+        omega.add(k);
+        if (nextCode == 512) numBits = 10;
+        else if (nextCode == 1024) numBits = 11;
+        else if (nextCode == 2048) numBits = 12;
+        else if (nextCode == 4096) {
+          out.write(CLEAR_CODE, numBits);
+          symbols.initialize();
+          nextCode = 258;
+          numBits = 9;
+        }
+      }
+    }
+    out.write(symbols.codeFromString(omega), numBits);
+    out.write(EOI_CODE, numBits);
+
+    return out.toByteArray();
+  }
+
+  /**
+   * Decodes an LZW-compressed data block.
+   *
+   * @param input the data to be decompressed
+   * @return The decompressed data
+   * @throws FormatException If input is not an LZW-compressed data block.
+   */
+  public byte[] decompress(byte[] input, Object options) throws FormatException
+  {
+    if (input == null || input.length == 0) return input;
+
+    byte[][] symbolTable = new byte[4096][1];
+    int bitsToRead = 9;
+    int nextSymbol = 258;
+    int code;
+    int oldCode = -1;
+    ByteVector out = new ByteVector(8192);
+    BitBuffer bb = new BitBuffer(input);
+    byte[] byteBuffer1 = new byte[16];
+    byte[] byteBuffer2 = new byte[16];
+
+    while (true) {
+      code = bb.getBits(bitsToRead);
+      if (code == EOI_CODE || code == -1) break;
+      if (code == CLEAR_CODE) {
+        // initialize symbol table
+        for (int i = 0; i < 256; i++) symbolTable[i][0] = (byte) i;
+        nextSymbol = 258;
+        bitsToRead = 9;
+        code = bb.getBits(bitsToRead);
+        if (code == EOI_CODE || code == -1) break;
+        out.add(symbolTable[code]);
+        oldCode = code;
+      }
+      else {
+        if (code < nextSymbol) {
+          // code is in table
+          out.add(symbolTable[code]);
+          // add string to table
+          ByteVector symbol = new ByteVector(byteBuffer1);
+          try {
+            symbol.add(symbolTable[oldCode]);
+          }
+          catch (ArrayIndexOutOfBoundsException a) {
+            throw new FormatException("Sorry, old LZW codes not supported");
+          }
+          symbol.add(symbolTable[code][0]);
+          symbolTable[nextSymbol] = symbol.toByteArray(); //**
+          oldCode = code;
+          nextSymbol++;
+        }
+        else {
+          // out of table
+          ByteVector symbol = new ByteVector(byteBuffer2);
+          symbol.add(symbolTable[oldCode]);
+          symbol.add(symbolTable[oldCode][0]);
+          byte[] outString = symbol.toByteArray();
+          out.add(outString);
+          symbolTable[nextSymbol] = outString; //**
+          oldCode = code;
+          nextSymbol++;
+        }
+        if (nextSymbol == 511) bitsToRead = 10;
+        if (nextSymbol == 1023) bitsToRead = 11;
+        if (nextSymbol == 2047) bitsToRead = 12;
+      }
+    }
+    return out.toByteArray();
+  }
+
+  /**
+   * Main testing method.
+   *
+   * @param args ignored
+   * @throws FormatException Can only occur if there is a bug in the
+   *                         compress method.
+   */
+  public static void main(String[] args) throws FormatException {
+    LZWCodec c = new LZWCodec();
+    c.test();
+  }
+
+}
diff --git a/loci/formats/codec/LZWTreeNode.java b/loci/formats/codec/LZWTreeNode.java
new file mode 100644
index 0000000..3d5eba7
--- /dev/null
+++ b/loci/formats/codec/LZWTreeNode.java
@@ -0,0 +1,111 @@
+//
+// LZWTreeNode.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+/**
+ * An LZW-compression helper class for building a symbol table in tree format.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/LZWTreeNode.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/LZWTreeNode.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class LZWTreeNode {
+
+  // -- Fields --
+
+  /** List of up to 256 children. */
+  protected LZWTreeNode[] children;
+
+  /** Code corresponding to this node. */
+  protected int theCode;
+
+  // -- Constructor --
+
+  /** Constructs a new LZW symbol tree node. */
+  public LZWTreeNode(int code) {
+    children = new LZWTreeNode[256];
+    theCode = code;
+  }
+
+  // -- LZWTreeNode API methods --
+
+  /** Initializes this node as the root of the symbol table. */
+  public void initialize() {
+    for (int i=0; i<256; i++) children[i] = new LZWTreeNode(i);
+  }
+
+  /** Gets the code corresponding to this node. */
+  public int getCode() {
+    return theCode;
+  }
+
+  /** Gets this node's indexth child. */
+  public LZWTreeNode getChild(byte index) {
+    int ndx = index;
+    if (ndx < 0) ndx += 256;
+    return children[ndx];
+  }
+
+  /** Sets this node's indexth child to match the given node. */
+  public void addChild(int index, LZWTreeNode node) {
+    children[index] = node;
+  }
+
+  /** Gets the code for the given byte sequence, or -1 if none. */
+  public int codeFromString(ByteVector string) {
+    LZWTreeNode node = nodeFromString(string);
+    return node == null ? -1 : node.theCode;
+  }
+
+  /** Gets the node for the given byte sequence, or null if none. */
+  public LZWTreeNode nodeFromString(ByteVector string) {
+    byte[] b = string.toByteArray();
+    LZWTreeNode node = this;
+    for (int i=0; i<b.length; i++) {
+      int q = (int) b[i];
+      if (q < 0) q += 256;
+      node = node.children[q];
+      if (node == null) return null;
+    }
+    return node;
+  }
+
+  /** Adds the given code for the specified byte sequence. */
+  public void addTableEntry(ByteVector string, int code) {
+    byte[] b = string.toByteArray();
+    LZWTreeNode node = this;
+    for (int i=0; i<b.length-1; i++) {
+      int q = b[i];
+      if (q < 0) q += 256;
+      node = node.children[q];
+    }
+    int q = b[b.length - 1];
+    if (q < 0) q += 256;
+    node.addChild(q, new LZWTreeNode(code));
+  }
+
+}
diff --git a/loci/formats/codec/LuraWaveCodec.java b/loci/formats/codec/LuraWaveCodec.java
new file mode 100644
index 0000000..74bb026
--- /dev/null
+++ b/loci/formats/codec/LuraWaveCodec.java
@@ -0,0 +1,137 @@
+//
+// LuraWaveCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * This class provides LuraWave decompression, using LuraWave's Java decoding
+ * library. Compression is not supported. Decompression requires a LuraWave
+ * license code, specified in the lurawave.license system property (e.g.,
+ * <code>-Dlurawave.license=XXXX</code> on the command line).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/LuraWaveCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/LuraWaveCodec.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class LuraWaveCodec extends BaseCodec implements Codec {
+
+  // -- Constants --
+
+  /** System property to check for the LuraWave license code. */
+  public static final String LICENSE_PROPERTY = "lurawave.license";
+
+  /** Message displayed if the LuraWave LWF decoder library is not found. */
+  public static final String NO_LURAWAVE_MSG =
+    "The LuraWave decoding library, lwf_jsdk2.6.jar, is required to decode " +
+    "this file. Please make sure it is present in your classpath.";
+
+  /** Message to display if no LuraWave license code is given. */
+  public static final String NO_LICENSE_MSG =
+    "No LuraWave license code was specified. Please set one in the " +
+    LICENSE_PROPERTY + " system property (e.g., with -D" + LICENSE_PROPERTY +
+    "=XXXX from the command line).";
+
+  /** Message to display if an invalid LuraWave license code is given. */
+  public static final String INVALID_LICENSE_MSG = "Invalid license code: ";
+
+  // -- Static fields --
+
+  /** True iff the LuraWave decoding library is not available. */
+  protected static boolean noLuraWave;
+
+  /** License code for LuraWave decoding library. */
+  protected static String licenseCode;
+
+  /** Reflected universe for LuraWave decoding library calls. */
+  protected static ReflectedUniverse r;
+
+  // -- Static initializer --
+
+  static {
+    r = new ReflectedUniverse();
+    try {
+      r.exec("import com.luratech.lwf.lwfDecoder");
+      r.setVar("-1", -1);
+      r.setVar("1024", 1024);
+      r.setVar("0", 0);
+    }
+    catch (ReflectException exc) {
+      noLuraWave = true;
+    }
+  }
+
+  // -- Codec API methods --
+
+  /* @see Codec#compress(byte[], int, int, int[], Object) */
+  public byte[] compress(byte[] data, int x, int y,
+    int[] dims, Object options) throws FormatException
+  {
+    throw new FormatException("LuraWave compression not supported");
+  }
+
+  /* @see Codec#decompress(byte[], Object) */
+  public byte[] decompress(byte[] input, Object options) throws FormatException
+  {
+    if (noLuraWave) throw new FormatException(NO_LURAWAVE_MSG);
+    licenseCode = System.getProperty(LICENSE_PROPERTY);
+    if (licenseCode == null) throw new FormatException(NO_LICENSE_MSG);
+    r.setVar("stream",
+      new BufferedInputStream(new ByteArrayInputStream(input), 4096));
+    try {
+      r.setVar("licenseCode", licenseCode);
+      r.exec("lwf = new lwfDecoder(stream, null, licenseCode)");
+    }
+    catch (ReflectException exc) {
+      throw new FormatException(INVALID_LICENSE_MSG + licenseCode, exc);
+    }
+    int[] image8 = null;
+    try {
+      int w = ((Integer) r.exec("lwf.getWidth()")).intValue();
+      int h = ((Integer) r.exec("lwf.getHeight()")).intValue();
+      image8 = new int[w * h];
+      r.setVar("image8", image8);
+      r.exec("lwf.decodeToMemory(image8, -1, 1024, 0)");
+    }
+    catch (ReflectException exc) {
+      throw new FormatException("Could not decode LuraWave data", exc);
+    }
+    int len = image8.length;
+    byte[] output = new byte[len];
+    for (int i=0; i<len; i++) {
+      // image is 8-bit grayscale encoded as 24/32-bit RGB
+      byte b0 = (byte) ((image8[i]) & 0xff);
+      //byte b1 = (byte) ((image8[i] >> 8) & 0xff);
+      //byte b2 = (byte) ((image8[i] >> 16) & 0xff);
+      //byte b3 = (byte) ((image8[i] >> 24) & 0xff);
+      output[i] = b0;
+    }
+    return output;
+  }
+
+}
diff --git a/loci/formats/codec/MJPBCodec.java b/loci/formats/codec/MJPBCodec.java
new file mode 100644
index 0000000..e438a34
--- /dev/null
+++ b/loci/formats/codec/MJPBCodec.java
@@ -0,0 +1,350 @@
+//
+// MJPBCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.io.*;
+//import java.util.Arrays;
+import loci.formats.*;
+
+/**
+ * Methods for compressing and decompressing QuickTime Motion JPEG-B data.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/MJPBCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/MJPBCodec.java">SVN</a></dd></dl>
+ */
+public class MJPBCodec extends BaseCodec implements Codec {
+
+  // -- Constants --
+
+  private static final byte[] HEADER = new byte[] {
+    (byte) 0xff, (byte) 0xd8, 0, 16, 0x4a, 0x46, 0x49, 0x46, 0,
+    1, 1, 0, 0x48, 0x48, 0, 0
+  };
+
+  // -- Codec API methods --
+
+  /* @see Codec#compress(byte[], int, int, int[], Object) */
+  public byte[] compress(byte[] data, int x, int y, int[] dims, Object options)
+    throws FormatException
+  {
+    throw new FormatException("Motion JPEG-B compression not supported.");
+  }
+
+  /* @see Codec#decompress(byte[], Object) */
+  public byte[] decompress(byte[] data, Object options) throws FormatException {
+    if (options == null || !(options instanceof int[])) return null;
+    int[] o = (int[]) options;
+    int x = o[0];
+    int y = o[1];
+    int bits = o[2];
+    boolean interlaced = o[3] == 1;
+
+    byte[] raw = null;
+    byte[] raw2 = null;
+
+    try {
+      RandomAccessStream ras = new RandomAccessStream(data);
+      ras.order(false);
+      ras.skipBytes(20);
+
+      byte[] lumDcBits = null, lumAcBits = null, lumDc = null, lumAc = null;
+      byte[] quant = null;
+
+      byte[] a = new byte[4];
+      ras.read(a);
+      String s1 = new String(a);
+      ras.seek(ras.getFilePointer() - 20);
+      ras.read(a);
+      String s2 = new String(a);
+      ras.skipBytes(12);
+      if (s1.equals("mjpg") || s2.equals("mjpg")) {
+        int extra = 16;
+        if (s2.startsWith("m")) {
+          extra = 0;
+          ras.seek(4);
+        }
+        ras.skipBytes(12);
+
+        int offset = ras.readInt() + extra;
+        int quantOffset = ras.readInt() + extra;
+        int huffmanOffset = ras.readInt() + extra;
+        int sof = ras.readInt() + extra;
+        int sos = ras.readInt() + extra;
+        int sod = ras.readInt() + extra;
+
+        if (quantOffset != 0) {
+          ras.seek(quantOffset);
+          int len = ras.readShort();
+          ras.skipBytes(1);
+          quant = new byte[64];
+          ras.read(quant);
+        }
+
+        if (huffmanOffset != 0) {
+          ras.seek(huffmanOffset);
+          int len = ras.readShort();
+          ras.skipBytes(1);
+          lumDcBits = new byte[16];
+          ras.read(lumDcBits);
+          lumDc = new byte[12];
+          ras.read(lumDc);
+          ras.skipBytes(1);
+          lumAcBits = new byte[16];
+          ras.read(lumAcBits);
+
+          int sum = 0;
+
+          for (int i=0; i<lumAcBits.length; i++) {
+            sum += lumAcBits[i] & 0xff;
+          }
+
+          lumAc = new byte[sum];
+          ras.read(lumAc);
+          /*
+          if (sum == 162) ras.read(lumAc);
+          else {
+            byte[] tmp = new byte[162];
+            ras.read(tmp);
+
+            ByteVector v = new ByteVector(sum);
+            int[] count = new int[lumAcBits.length];
+            Arrays.fill(count, (byte) 0);
+            for (int i=0; i<tmp.length; i++) {
+              int size = 0;
+              int val = tmp[i] & 0xff;
+              while (Math.pow(2, size) < val) size++;
+              if (count[size] < lumAcBits[size]) {
+                v.add(tmp[i]);
+                count[size]++;
+              }
+              else if (size == 8) {
+                for (int j=size+1; j<lumAcBits.length; j++) {
+                  if (count[j] < lumAcBits[j]) {
+                    v.add(tmp[i]);
+                    count[j]++;
+                  }
+                }
+              }
+            }
+
+            lumAc = v.toByteArray();
+          }
+          */
+        }
+
+        ras.seek(sof + 7);
+
+        int channels = ras.read();
+
+        int[] sampling = new int[channels];
+        for (int i=0; i<channels; i++) {
+          ras.skipBytes(1);
+          sampling[i] = ras.read();
+          ras.skipBytes(1);
+        }
+
+        ras.seek(sos + 3);
+        int[] tables = new int[channels];
+        for (int i=0; i<channels; i++) {
+          ras.skipBytes(1);
+          tables[i] = ras.read();
+        }
+
+        ras.seek(sod);
+        int numBytes = (int) (offset - ras.getFilePointer());
+        if (offset == 0) numBytes = (int) (ras.length() - ras.getFilePointer());
+        raw = new byte[numBytes];
+        ras.read(raw);
+
+        if (offset != 0) {
+          ras.seek(offset + 36);
+          int n = ras.readInt();
+          ras.skipBytes(n);
+          ras.seek(ras.getFilePointer() - 40);
+
+          numBytes = (int) (ras.length() - ras.getFilePointer());
+          raw2 = new byte[numBytes];
+          ras.read(raw2);
+        }
+      }
+
+      if (raw == null) raw = data;
+
+      // insert zero after each byte equal to 0xff
+      ByteVector b = new ByteVector();
+      for (int i=0; i<raw.length; i++) {
+        b.add((byte) raw[i]);
+        if (raw[i] == (byte) 0xff) {
+          b.add((byte) 0);
+        }
+      }
+
+      if (raw2 == null) raw2 = new byte[0];
+      ByteVector b2 = new ByteVector();
+      for (int i=0; i<raw2.length; i++) {
+        b2.add((byte) raw2[i]);
+        if (raw2[i] == (byte) 0xff) {
+          b2.add((byte) 0);
+        }
+      }
+
+      // assemble fake JPEG plane
+
+      ByteVector v = new ByteVector(1000);
+      v.add(HEADER);
+
+      v.add(new byte[] {(byte) 0xff, (byte) 0xdb});
+
+      int length = 4 + quant.length*2;
+      v.add((byte) ((length >>> 8) & 0xff));
+      v.add((byte) (length & 0xff));
+      v.add((byte) 0);
+      v.add(quant);
+
+      v.add((byte) 1);
+      v.add(quant);
+
+      v.add(new byte[] {(byte) 0xff, (byte) 0xc4});
+      length = (lumDcBits.length + lumDc.length + lumAcBits.length +
+        lumAc.length)*2 + 6;
+      v.add((byte) ((length >>> 8) & 0xff));
+      v.add((byte) (length & 0xff));
+
+      v.add((byte) 0);
+      v.add(lumDcBits);
+      v.add(lumDc);
+      v.add((byte) 1);
+      v.add(lumDcBits);
+      v.add(lumDc);
+      v.add((byte) 16);
+      v.add(lumAcBits);
+      v.add(lumAc);
+      v.add((byte) 17);
+      v.add(lumAcBits);
+      v.add(lumAc);
+
+      v.add((byte) 0xff);
+      v.add((byte) 0xc0);
+
+      length = (bits >= 40) ? 11 : 17;
+      v.add((byte) ((length >>> 8) & 0xff));
+      v.add((byte) (length & 0xff));
+
+      int fieldHeight = y;
+      if (interlaced) fieldHeight /= 2;
+      if (y % 2 == 1) fieldHeight++;
+
+      int c = bits == 24 ? 3 : (bits == 32 ? 4 : 1);
+
+      v.add(bits >= 40 ? (byte) (bits - 32) : (byte) (bits / c));
+      v.add((byte) ((fieldHeight >>> 8) & 0xff));
+      v.add((byte) (fieldHeight & 0xff));
+      v.add((byte) ((x >>> 8) & 0xff));
+      v.add((byte) (x & 0xff));
+      v.add((bits >= 40) ? (byte) 1 : (byte) 3);
+
+      v.add((byte) 1);
+      v.add((byte) 33);
+      v.add((byte) 0);
+
+      if (bits < 40) {
+        v.add((byte) 2);
+        v.add((byte) 17);
+        v.add((byte) 1);
+        v.add((byte) 3);
+        v.add((byte) 17);
+        v.add((byte) 1);
+      }
+
+      v.add((byte) 0xff);
+      v.add((byte) 0xda);
+
+      length = (bits >= 40) ? 8 : 12;
+      v.add((byte) ((length >>> 8) & 0xff));
+      v.add((byte) (length & 0xff));
+
+      v.add((bits >= 40) ? (byte) 1 : (byte) 3);
+      v.add((byte) 1);
+      v.add((byte) 0);
+
+      if (bits < 40) {
+        v.add((byte) 2);
+        v.add((byte) 1);
+        v.add((byte) 3);
+        v.add((byte) 1);
+      }
+
+      v.add((byte) 0);
+      v.add((byte) 0x3f);
+      v.add((byte) 0);
+
+      if (interlaced) {
+        ByteVector v2 = new ByteVector(v.size());
+        v2.add(v.toByteArray());
+
+        v.add(b.toByteArray());
+        v.add((byte) 0xff);
+        v.add((byte) 0xd9);
+        v2.add(b2.toByteArray());
+        v2.add((byte) 0xff);
+        v2.add((byte) 0xd9);
+
+        JPEGCodec jpeg = new JPEGCodec();
+        byte[] top = jpeg.decompress(v.toByteArray());
+        byte[] bottom = jpeg.decompress(v2.toByteArray());
+
+        int bpp = bits < 40 ? bits / 8 : (bits - 32) / 8;
+        int ch = bits < 40 ? 3 : 1;
+        byte[] result = new byte[x * y * bpp * ch];
+
+        int topNdx = 0;
+        int bottomNdx = 0;
+
+        for (int yy=0; yy<y; yy++) {
+          if (yy % 2 == 0) {
+            System.arraycopy(top, topNdx*x*bpp, result, yy*x*bpp, x*bpp);
+            topNdx++;
+          }
+          else {
+            System.arraycopy(bottom, bottomNdx*x*bpp, result, yy*x*bpp, x*bpp);
+            bottomNdx++;
+          }
+        }
+        return result;
+      }
+      else {
+        v.add(b.toByteArray());
+        v.add((byte) 0xff);
+        v.add((byte) 0xd9);
+        return new JPEGCodec().decompress(v.toByteArray());
+      }
+    }
+    catch (IOException e) {
+      throw new FormatException(e);
+    }
+  }
+
+}
diff --git a/loci/formats/codec/MSRLECodec.java b/loci/formats/codec/MSRLECodec.java
new file mode 100644
index 0000000..cdcdbb2
--- /dev/null
+++ b/loci/formats/codec/MSRLECodec.java
@@ -0,0 +1,122 @@
+//
+// MSRLECodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.*;
+
+/**
+ * Methods for compressing and decompressing data using Microsoft RLE.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/MSRLECodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/MSRLECodec.java">SVN</a></dd></dl>
+ */
+public class MSRLECodec extends BaseCodec implements Codec {
+
+  /* @see Codec#compress(byte[], int, int, int[], Object) */
+  public byte[] compress(byte[] data, int x, int y, int[] dims, Object options)
+    throws FormatException
+  {
+    throw new FormatException("MSRLE compression not supported.");
+  }
+
+  /* @see Codec#decompress(byte[], Object) */
+  public byte[] decompress(byte[] data, Object options) throws FormatException {
+    if (options == null || !(options instanceof Object[])) return null;
+
+    Object[] o = (Object[]) options;
+    byte[] prev = (byte[]) o[1];
+    int[] dims = (int[]) o[0];
+    int x = dims[0];
+    int y = dims[1];
+
+    int pt = 0;
+    short code = 0;
+    short extra = 0;
+    short stream = 0;
+
+    int pixelPt = 0;
+    int row = x;
+    int rowPt = (y - 1) * row;
+    int frameSize = y * row;
+
+    if (prev == null) prev = new byte[frameSize];
+
+    while (rowPt >= 0 && pt < data.length && pixelPt < prev.length) {
+      stream = data[pt++];
+      if (stream < 0) stream += 256;
+      code = stream;
+
+      if (code == 0) {
+        stream = data[pt++];
+        if (stream < 0) stream += 256;
+        if (stream == 0) {
+          rowPt -= row;
+          pixelPt = 0;
+        }
+        else if (stream == 1) return prev;
+        else if (stream == 2) {
+          stream = data[pt++];
+          if (stream < 0) stream += 256;
+          pixelPt += stream;
+          stream = data[pt++];
+          if (stream < 0) stream += 256;
+          rowPt -= stream * row;
+        }
+        else {
+          if ((rowPt + pixelPt + stream > frameSize) || (rowPt < 0)) {
+            return prev;
+          }
+
+          code = stream;
+          extra = (short) (stream & 0x01);
+          if (stream + code + extra > data.length) return prev;
+
+          while (code-- > 0) {
+            stream = data[pt++];
+            prev[rowPt + pixelPt] = (byte) stream;
+            pixelPt++;
+          }
+          if (extra != 0) pt++;
+        }
+      }
+      else {
+        if ((rowPt + pixelPt + stream > frameSize) || (rowPt < 0)) {
+          return prev;
+        }
+
+        stream = data[pt++];
+
+        while (code-- > 0) {
+          prev[rowPt + pixelPt] = (byte) stream;
+          pixelPt++;
+        }
+      }
+    }
+
+    return prev;
+  }
+
+}
diff --git a/loci/formats/codec/MSVideoCodec.java b/loci/formats/codec/MSVideoCodec.java
new file mode 100644
index 0000000..c7dcde7
--- /dev/null
+++ b/loci/formats/codec/MSVideoCodec.java
@@ -0,0 +1,251 @@
+//
+// MSVideoCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.DataTools;
+import loci.formats.FormatException;
+
+/**
+ * Methods for compressing and decompressing data using Microsoft Video 1.
+ *
+ * See http://wiki.multimedia.cx/index.php?title=Microsoft_Video_1 for an
+ * excellent description of MSV1.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/MSVideoCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/MSVideoCodec.java">SVN</a></dd></dl>
+ */
+public class MSVideoCodec extends BaseCodec implements Codec {
+
+  /* @see Codec#compress(byte[], int, int, int[], Object) */
+  public byte[] compress(byte[] data, int x, int y, int[] dims, Object options)
+    throws FormatException
+  {
+    throw new FormatException("MS Video 1 compression not supported.");
+  }
+
+  /* @see Codec#decompress(byte[], Object) */
+  public byte[] decompress(byte[] data, Object options) throws FormatException {
+    if (options == null || !(options instanceof Object[])) return null;
+    Object[] optionsArray = (Object[]) options;
+    int bitsPerPixel = ((Integer) optionsArray[0]).intValue();
+    int width = ((Integer) optionsArray[1]).intValue();
+    int height = ((Integer) optionsArray[2]).intValue();
+    byte[] lastImage = (byte[]) optionsArray[3];
+
+    int pt = 0;
+
+    int blocksPerRow = (width + 3) / 4;
+    int blocksPerColumn = (height + 3) / 4;
+
+    int row = 0;
+    int column = 0;
+
+    byte[] bytes = new byte[width * height];
+    short[] shorts = new short[width * height];
+
+    while (true) {
+      if (pt >= data.length || row >= width || column >= height) break;
+      short a = (short) (data[pt++] & 0xff);
+      short b = (short) (data[pt++] & 0xff);
+      if (a == 0 && b == 0 && pt >= data.length) break;
+      if (b >= 0x84 && b < 0x88) {
+        // indicates that we are skipping some blocks
+
+        int skip = (b - 0x84) * 256 + a;
+        for (int i=0; i<skip; i++) {
+          if (lastImage != null) {
+            for (int y=0; y<4; y++) {
+              for (int x=0; x<4; x++) {
+                if (row + x >= width) break;
+                if (column + y >= height) break;
+                int ndx = width*(column + y) + row + x;
+                int oldNdx = width*(height - 1 - y - column) + row + x;
+                if (bitsPerPixel == 8) {
+                  bytes[ndx] = lastImage[oldNdx];
+                }
+                else {
+                  byte red = lastImage[oldNdx];
+                  byte green = lastImage[oldNdx + width*height];
+                  byte blue = lastImage[oldNdx + 2*width*height];
+                  shorts[ndx] = (short) (((blue & 0x1f) << 10) |
+                    ((green & 0x1f) << 5) | (red & 0x1f));
+                }
+              }
+            }
+          }
+
+          row += 4;
+          if (row >= width) {
+            row = 0;
+            column += 4;
+          }
+        }
+      }
+      else if (b >= 0 && b < 0x80) {
+        if (bitsPerPixel == 8) {
+          byte colorA = data[pt++];
+          byte colorB = data[pt++];
+
+          for (int y=0; y<4; y++) {
+            for (int x=3; x>=0; x--) {
+              int ndx = width*(column + y) + row + x;
+              short flag = y < 2 ? b : a;
+              int shift = 4 - 4*(y % 2) + x;
+              int cmp = 1 << shift;
+              if ((flag & cmp) == cmp) bytes[ndx] = colorA;
+              else bytes[ndx] = colorB;
+            }
+          }
+        }
+        else {
+          short check1 = DataTools.bytesToShort(data, pt, true);
+          pt += 2;
+          short check2 = DataTools.bytesToShort(data, pt, true);
+          pt += 2;
+
+          if ((check1 & 0x8000) == 0x8000) {
+            // 8 color encoding
+            short q1a = check1;
+            short q1b = check2;
+            short q2a = DataTools.bytesToShort(data, pt, true);
+            pt += 2;
+            short q2b = DataTools.bytesToShort(data, pt, true);
+            pt += 2;
+            short q3a = DataTools.bytesToShort(data, pt, true);
+            pt += 2;
+            short q3b = DataTools.bytesToShort(data, pt, true);
+            pt += 2;
+            short q4a = DataTools.bytesToShort(data, pt, true);
+            pt += 2;
+            short q4b = DataTools.bytesToShort(data, pt, true);
+            pt += 2;
+
+            for (int y=0; y<4; y++) {
+              for (int x=3; x>= 0; x--) {
+                int ndx = width*(column + y) + row + x;
+
+                short colorA =
+                  x < 2 ? (y < 2 ? q3a : q1a) : (y < 2 ? q4a : q2a);
+                short colorB =
+                  x < 2 ? (y < 2 ? q3b : q1b) : (y < 2 ? q4b : q2b);
+
+                short flag = y < 2 ? b : a;
+                int shift = 4 - 4*(y % 2) + x;
+                int cmp = 1 << shift;
+                if ((flag & cmp) == cmp) shorts[ndx] = colorA;
+                else shorts[ndx] = colorB;
+              }
+            }
+          }
+          else {
+            // 2 color encoding
+
+            short colorA = check1;
+            short colorB = check2;
+
+            for (int y=0; y<4; y++) {
+              for (int x=3; x>=0; x--) {
+                int ndx = width*(column + y) + row + x;
+                if (ndx >= shorts.length) break;
+                short flag = y < 2 ? b : a;
+                int shift = 4 - 4*(y % 2) + x;
+                int cmp = 1 << shift;
+                if ((flag & cmp) == cmp) shorts[ndx] = colorA;
+                else shorts[ndx] = colorB;
+              }
+            }
+          }
+        }
+
+        row += 4;
+        if (row >= width) {
+          row = 0;
+          column += 4;
+        }
+      }
+      else if (bitsPerPixel == 8 && 0x90 < b) {
+        byte[] colors = new byte[8];
+        System.arraycopy(data, pt, colors, 0, colors.length);
+        pt += colors.length;
+
+        for (int y=0; y<4; y++) {
+          for (int x=3; x>=0; x--) {
+            int ndx = width*(column + y) + row + x;
+            byte colorA = y < 2 ? (x < 2 ? colors[4] : colors[6]) :
+              (x < 2 ? colors[0] : colors[2]);
+            byte colorB = y < 2 ? (x < 2 ? colors[5] : colors[7]) :
+              (x < 2 ? colors[1] : colors[3]);
+
+            short flag = y < 2 ? b : a;
+            int shift = 4 - 4*(y % 2) + x;
+            int cmp = 1 << shift;
+            if ((flag & cmp) == cmp) bytes[ndx] = colorA;
+            else bytes[ndx] = colorB;
+          }
+        }
+      }
+      else {
+        for (int y=0; y<4; y++) {
+          for (int x=0; x<4; x++) {
+            int ndx = width*(column + y) + row + x;
+            if (bitsPerPixel == 8) bytes[ndx] = (byte) (a & 0xff);
+            else shorts[ndx] = (short) (((b << 8) | a) & 0xffff);
+          }
+        }
+        row += 4;
+        if (row >= width) {
+          row = 0;
+          column += 4;
+        }
+      }
+    }
+
+    if (bitsPerPixel == 8) {
+      byte[] tmp = bytes;
+      bytes = new byte[tmp.length];
+      for (int y=0; y<height; y++) {
+        System.arraycopy(tmp, y*width, bytes, (height-y-1)*width, width);
+      }
+      return bytes;
+    }
+
+    byte[] b = new byte[width * height * 3];
+    // expand RGB 5-5-5 to 3 byte tuple
+
+    for (int y=0; y<height; y++) {
+      for (int x=0; x<width; x++) {
+        int off = y*width + x;
+        int dest = (height - y - 1)*width + x;
+        b[dest + 2*width*height] = (byte) ((shorts[off] & 0x7c00) >> 10);
+        b[dest + width*height] = (byte) ((shorts[off] & 0x3e0) >> 5);
+        b[dest] = (byte) (shorts[off] & 0x1f);
+      }
+    }
+
+    return b;
+  }
+
+}
diff --git a/loci/formats/codec/NikonCodec.java b/loci/formats/codec/NikonCodec.java
new file mode 100644
index 0000000..c5b3058
--- /dev/null
+++ b/loci/formats/codec/NikonCodec.java
@@ -0,0 +1,119 @@
+//
+// NikonCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.FormatException;
+
+/**
+ * This class implements Nikon decompression. Compression is not yet
+ * implemented.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/NikonCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/NikonCodec.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class NikonCodec extends BaseCodec implements Codec {
+
+  /** Huffman tree for the Nikon decoder. */
+  private static final int[] NIKON_TREE = {
+    0, 1, 5, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0,
+    0, 5, 4, 3, 6, 2, 7, 1, 0, 8, 9, 11, 10, 12
+  };
+
+  /**
+   * Compresses a block of Nikon data. Currently not supported.
+   *
+   * @param data the data to be compressed
+   * @param x length of the x dimension of the image data, if appropriate
+   * @param y length of the y dimension of the image data, if appropriate
+   * @param dims the dimensions of the image data, if appropriate
+   * @param options options to be used during compression, if appropriate
+   * @return The compressed data
+   * @throws FormatException If input is not an Adobe data block.
+   */
+  public byte[] compress(byte[] data, int x, int y,
+      int[] dims, Object options) throws FormatException
+  {
+    // TODO: Add compression support.
+    throw new FormatException("Nikon Compression not currently supported");
+  }
+
+  /**
+   * Decodes an image strip using Nikon's compression algorithm (a variant on
+   * Huffman coding).
+   *
+   * TODO : this is broken
+   *
+   * @param input input data to be decompressed
+   * @return The decompressed data
+   * @throws FormatException if data is not valid compressed data for this
+   *                         decompressor
+   */
+  public byte[] decompress(byte[] input, Object options) throws FormatException
+  {
+    BitWriter out = new BitWriter(input.length);
+    BitBuffer bb = new BitBuffer(input);
+    boolean eof = false;
+    while (!eof) {
+      boolean codeFound = false;
+      int code = 0;
+      int bitsRead = 0;
+      while (!codeFound) {
+        int bit = bb.getBits(1);
+        if (bit == -1) {
+          eof = true;
+          break;
+        }
+        bitsRead++;
+        code >>= 1;
+        code += bit;
+        for (int i=16; i<NIKON_TREE.length; i++) {
+          if (code == NIKON_TREE[i]) {
+            int ndx = i;
+            int count = 0;
+            while (ndx > 16) {
+              ndx -= NIKON_TREE[count];
+              count++;
+            }
+            if (ndx < 16) count--;
+            if (bitsRead == count + 1) {
+              codeFound = true;
+              i = NIKON_TREE.length;
+              break;
+            }
+          }
+        }
+      }
+      while (code > 0) {
+        out.write(bb.getBits(1), 1);
+        code--;
+      }
+    }
+    byte[] b = out.toByteArray();
+    return b;
+  }
+}
diff --git a/loci/formats/codec/PackbitsCodec.java b/loci/formats/codec/PackbitsCodec.java
new file mode 100644
index 0000000..9b4fa59
--- /dev/null
+++ b/loci/formats/codec/PackbitsCodec.java
@@ -0,0 +1,88 @@
+//
+// PackbitsCodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.FormatException;
+
+/**
+ * This class implements packbits decompression. Compression is not yet
+ * implemented.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/PackbitsCodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/PackbitsCodec.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class PackbitsCodec extends BaseCodec implements Codec {
+
+  /**
+   * Compresses a block of Packbits data. Currently not supported.
+   *
+   * @param data the data to be compressed
+   * @param x length of the x dimension of the image data, if appropriate
+   * @param y length of the y dimension of the image data, if appropriate
+   * @param dims the dimensions of the image data, if appropriate
+   * @param options options to be used during compression, if appropriate
+   * @return The compressed data
+   * @throws FormatException If input is not an Adobe data block.
+   */
+  public byte[] compress(byte[] data, int x, int y,
+      int[] dims, Object options) throws FormatException
+  {
+    // TODO: Add compression support.
+    throw new FormatException("Packbits Compression not currently supported");
+  }
+
+  /**
+   * Decodes a PackBits (Macintosh RLE) compressed image.
+   * Adapted from the TIFF 6.0 specification, page 42.
+   *
+   * @param input input data to be decompressed
+   * @return The decompressed data
+   * @throws FormatException if data is not valid compressed data for this
+   *                         decompressor
+   */
+  public byte[] decompress(byte[] input, Object options) throws FormatException
+  {
+    ByteVector output = new ByteVector(input.length);
+    int pt = 0;
+    while (pt < input.length) {
+      byte n = input[pt++];
+      if (n >= 0) { // 0 <= n <= 127
+        int len = pt + n + 1 > input.length ? (input.length - pt) : (n + 1);
+        output.add(input, pt, len);
+        pt += len;
+      }
+      else if (n != -128) { // -127 <= n <= -1
+        if (pt >= input.length) break;
+        int len = -n + 1;
+        byte inp = input[pt++];
+        for (int i=0; i<len; i++) output.add(inp);
+      }
+    }
+    return output.toByteArray();
+  }
+}
diff --git a/loci/formats/codec/QTRLECodec.java b/loci/formats/codec/QTRLECodec.java
new file mode 100644
index 0000000..6aae63c
--- /dev/null
+++ b/loci/formats/codec/QTRLECodec.java
@@ -0,0 +1,170 @@
+//
+// QTRLECodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * Methods for compressing and decompressing data using QuickTime RLE.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/QTRLECodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/QTRLECodec.java">SVN</a></dd></dl>
+ */
+public class QTRLECodec extends BaseCodec implements Codec {
+
+  /* @see Codec#compress(byte[], int, int, int[], Object) */
+  public byte[] compress(byte[] data, int x, int y, int[] dims, Object options)
+    throws FormatException
+  {
+    throw new FormatException("QTRLE compression not supported.");
+  }
+
+  /* @see Codec#decompress(byte[], Object) */
+  public byte[] decompress(byte[] data, Object options) throws FormatException {
+    if (options == null || !(options instanceof Object[])) return null;
+
+    Object[] o = (Object[]) options;
+    byte[] prev = (byte[]) o[1];
+    int[] dims = (int[]) o[0];
+    int x = dims[0];
+    int y = dims[1];
+    int bpp = dims[2];
+    int numLines = y;
+
+    if (data.length < 8) return prev;
+
+    try {
+      RABytes s = new RABytes(data);
+      s.skipBytes(4);
+
+      int header = s.readShort();
+      int off = 0;
+      int start = 0;
+
+      byte[] output = new byte[x * y * bpp];
+
+      if ((header & 8) == 8) {
+        start = s.readShort();
+        s.skipBytes(2);
+        numLines = s.readShort();
+        s.skipBytes(2);
+
+        if (prev != null) {
+          for (int i=0; i<start; i++) {
+            off = i * x * bpp;
+            System.arraycopy(prev, off, output, off, x * bpp);
+          }
+        }
+        off += x * bpp;
+
+        if (prev != null) {
+          for (int i=start+numLines; i<y; i++) {
+            int offset = i * x * bpp;
+            System.arraycopy(prev, offset, output, offset, x * bpp);
+          }
+        }
+      }
+      else throw new FormatException("Unsupported header : " + header);
+
+      // uncompress remaining lines
+
+      int skip = 0; // number of bytes to skip
+      byte rle = 0; // RLE code
+
+      int rowPointer = start * x * bpp;
+
+      for (int i=0; i<numLines; i++) {
+        skip = s.read();
+        if (skip < 0) skip += 256;
+
+        if (prev != null) {
+          try {
+            System.arraycopy(prev, rowPointer, output, rowPointer,
+              (skip - 1) * bpp);
+          }
+          catch (ArrayIndexOutOfBoundsException e) { }
+        }
+
+        off = rowPointer + ((skip - 1) * bpp);
+        while (true) {
+          rle = (byte) (s.read() & 0xff);
+
+          if (rle == 0) {
+            skip = s.read();
+
+            if (prev != null) {
+              try {
+                System.arraycopy(prev, off, output, off, (skip - 1) * bpp);
+              }
+              catch (ArrayIndexOutOfBoundsException e) { }
+            }
+
+            off += (skip - 1) * bpp;
+          }
+          else if (rle == -1) {
+            if (off < (rowPointer + (x * bpp)) && prev != null) {
+              System.arraycopy(prev, off, output, off, rowPointer +
+                (x * bpp) - off);
+            }
+            break;
+          }
+          else if (rle < -1) {
+            // unpack next pixel and copy it to output -(rle) times
+            for (int j=0; j<(-1*rle); j++) {
+              if (off < output.length) {
+                System.arraycopy(data, (int) s.getFilePointer(), output,
+                  off, bpp);
+                off += bpp;
+              }
+              else break;
+            }
+            s.skipBytes(bpp);
+          }
+          else {
+            // copy (rle) pixels to output
+            int len = rle * bpp;
+            if (output.length - off < len) len = output.length - off;
+            if (s.length() - s.getFilePointer() < len) {
+              len = (int) (s.length() - s.getFilePointer());
+            }
+            if (len < 0) len = 0;
+            if (off > output.length) off = output.length;
+            s.read(output, off, len);
+            off += len;
+          }
+          if (s.getFilePointer() >= s.length()) return output;
+        }
+        rowPointer += x * bpp;
+      }
+      return output;
+    }
+    catch (IOException e) {
+      throw new FormatException(e);
+    }
+  }
+
+}
diff --git a/loci/formats/codec/RPZACodec.java b/loci/formats/codec/RPZACodec.java
new file mode 100644
index 0000000..9b61284
--- /dev/null
+++ b/loci/formats/codec/RPZACodec.java
@@ -0,0 +1,215 @@
+//
+// RPZACodec.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.codec;
+
+import loci.formats.*;
+
+/**
+ * Implements encoding and decoding methods for Apple RPZA.  This code was
+ * adapted from the RPZA codec for ffmpeg - see http://ffmpeg.mplayerhq.hu
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/RPZACodec.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/RPZACodec.java">SVN</a></dd></dl>
+ */
+public class RPZACodec extends BaseCodec implements Codec {
+
+  /* @see Codec#compress(byte[], int, int, int[], Object) */
+  public byte[] compress(byte[] input, int x, int y, int[] dims,
+    Object options) throws FormatException
+  {
+    throw new FormatException("RPZA compression not supported.");
+  }
+
+  /* @see Codec#decompress(byte[], Object) */
+  public byte[] decompress(byte[] data, Object options) throws FormatException {
+    if (options == null || !(options instanceof int[])) return null;
+
+    int[] o = (int[]) options;
+    int x = o[0];
+    int y = o[1];
+
+    int stride = x;
+    int rowInc = stride - 4;
+    int streamPtr = 8;
+    short opcode;
+    int nBlocks;
+    int colorA = 0, colorB;
+    int[] color4 = new int[4];
+    int index, idx;
+    int ta, tb;
+    int rowPtr = 0, pixelPtr = 0, blockPtr = 0;
+    int pixelX, pixelY;
+    int totalBlocks;
+
+    int[] pixels = new int[x * y];
+    byte[] rtn = new byte[x * y * 3];
+
+    while (data[streamPtr] != (byte) 0xe1) streamPtr++;
+    streamPtr += 4;
+
+    totalBlocks = ((x + 3) / 4) * ((y + 3) / 4);
+
+    while (streamPtr < data.length) {
+      opcode = data[streamPtr++];
+      nBlocks = (opcode & 0x1f) + 1;
+
+      if ((opcode & 0x80) == 0) {
+        if (streamPtr >= data.length) break;
+        colorA = (opcode << 8) | data[streamPtr++];
+        opcode = 0;
+        if (streamPtr >= data.length) break;
+        if ((data[streamPtr] & 0x80) != 0) {
+          opcode = 0x20;
+          nBlocks = 1;
+        }
+      }
+
+      switch (opcode & 0xe0) {
+        case 0x80:
+          while (nBlocks-- > 0) {
+            pixelPtr += 4;
+            if (pixelPtr >= x) {
+              pixelPtr = 0;
+              rowPtr += stride * 4;
+            }
+            totalBlocks--;
+          }
+          break;
+        case 0xa0:
+          colorA = DataTools.bytesToInt(data, streamPtr, 2, false);
+          streamPtr += 2;
+          while (nBlocks-- > 0) {
+            blockPtr = rowPtr + pixelPtr;
+            for (pixelY=0; pixelY<4; pixelY++) {
+              for (pixelX=0; pixelX<4; pixelX++) {
+                if (blockPtr >= pixels.length) break;
+                pixels[blockPtr] = colorA;
+
+                short s = (short) (pixels[blockPtr] & 0x7fff);
+                unpack(s, rtn, blockPtr, pixels.length);
+                blockPtr++;
+              }
+              blockPtr += rowInc;
+            }
+            pixelPtr += 4;
+            if (pixelPtr >= x) {
+              pixelPtr = 0;
+              rowPtr += stride * 4;
+            }
+            totalBlocks--;
+          }
+          break;
+        case 0xc0:
+        case 0x20:
+          if ((opcode & 0xe0) == 0xc0) {
+            colorA = DataTools.bytesToInt(data, streamPtr, 2, false);
+            streamPtr += 2;
+          }
+
+          colorB = DataTools.bytesToInt(data, streamPtr, 2, false);
+          streamPtr += 2;
+
+          color4[0] = colorB;
+          color4[1] = 0;
+          color4[2] = 0;
+          color4[3] = colorA;
+
+          ta = (colorA >> 10) & 0x1f;
+          tb = (colorB >> 10) & 0x1f;
+          color4[1] |= ((11*ta + 21*tb) >> 5) << 10;
+          color4[2] |= ((21*ta + 11*tb) >> 5) << 10;
+
+          ta = (colorA >> 5) & 0x1f;
+          tb = (colorB >> 5) & 0x1f;
+          color4[1] |= ((11*ta + 21*tb) >> 5) << 5;
+          color4[2] |= ((21*ta + 11*tb) >> 5) << 5;
+
+          ta = colorA & 0x1f;
+          tb = colorB & 0x1f;
+          color4[1] |= (11*ta + 21*tb) >> 5;
+          color4[2] |= (21*ta + 11*tb) >> 5;
+
+          while (nBlocks-- > 0) {
+            blockPtr = rowPtr + pixelPtr;
+            for (pixelY=0; pixelY<4; pixelY++) {
+              if (streamPtr >= data.length) break;
+              index = data[streamPtr++];
+              for (pixelX=0; pixelX<4; pixelX++) {
+                idx = (index >> (2*(3 - pixelX))) & 3;
+                if (blockPtr >= pixels.length) break;
+                pixels[blockPtr] = color4[idx];
+
+                short s = (short) (pixels[blockPtr] & 0x7fff);
+                unpack(s, rtn, blockPtr, pixels.length);
+                blockPtr++;
+              }
+              blockPtr += rowInc;
+            }
+            pixelPtr += 4;
+            if (pixelPtr >= x) {
+              pixelPtr = 0;
+              rowPtr += stride * 4;
+            }
+            totalBlocks--;
+          }
+          break;
+        case 0x00:
+          blockPtr = rowPtr + pixelPtr;
+          for (pixelY=0; pixelY<4; pixelY++) {
+            for (pixelX=0; pixelX<4; pixelX++) {
+              if ((pixelY != 0) || (pixelX != 0)) {
+                colorA = DataTools.bytesToInt(data, streamPtr, 2, false);
+                streamPtr += 2;
+              }
+              if (blockPtr >= pixels.length) break;
+              pixels[blockPtr] = colorA;
+
+              short s = (short) (pixels[blockPtr] & 0x7fff);
+              unpack(s, rtn, blockPtr, pixels.length);
+              blockPtr++;
+            }
+            blockPtr += rowInc;
+          }
+          pixelPtr += 4;
+          if (pixelPtr >= x) {
+            pixelPtr = 0;
+            rowPtr += stride * 4;
+          }
+          totalBlocks--;
+          break;
+      }
+    }
+    return rtn;
+  }
+
+  // -- Helper methods --
+
+  private void unpack(short s, byte[] array, int offset, int len) {
+    array[offset] = (byte) (255 - ((s & 0x7c00) >> 10));
+    array[offset + len] = (byte) (255 - ((s & 0x3e0) >> 5));
+    array[offset + 2*len] = (byte) (255 - (s & 0x1f));
+  }
+}
diff --git a/loci/formats/gui/ComboFileFilter.java b/loci/formats/gui/ComboFileFilter.java
new file mode 100644
index 0000000..1fb99fc
--- /dev/null
+++ b/loci/formats/gui/ComboFileFilter.java
@@ -0,0 +1,148 @@
+//
+// ComboFileFilter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.gui;
+
+import java.io.File;
+import java.util.*;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A file filter that recognizes files from a union of other filters.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/gui/ComboFileFilter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/gui/ComboFileFilter.java">SVN</a></dd></dl>
+ */
+public class ComboFileFilter extends FileFilter
+  implements java.io.FileFilter, Comparable
+{
+
+  // -- Fields --
+
+  /** List of filters to be combined. */
+  private FileFilter[] filts;
+
+  /** Description. */
+  private String desc;
+
+  // -- Constructor --
+
+  /** Constructs a new filter from a list of other filters. */
+  public ComboFileFilter(FileFilter[] filters, String description) {
+    filts = new FileFilter[filters.length];
+    System.arraycopy(filters, 0, filts, 0, filters.length);
+    desc = description;
+  }
+
+  // -- ComboFileFilter API methods --
+
+  /** Gets the list of file filters forming this filter combination. */
+  public FileFilter[] getFilters() {
+    FileFilter[] ff = new FileFilter[filts.length];
+    System.arraycopy(filts, 0, ff, 0, filts.length);
+    return ff;
+  }
+
+  // -- Static ComboFileFilter API methods --
+
+  /**
+   * Sorts the given list of file filters, and combines filters with identical
+   * descriptions into a combination filter that accepts anything any of its
+   * constituant filters do.
+   */
+  public static FileFilter[] sortFilters(FileFilter[] filters) {
+    return sortFilters(new Vector(Arrays.asList(filters)));
+  }
+
+  /**
+   * Sorts the given list of file filters, and combines filters with identical
+   * descriptions into a combination filter that accepts anything any of its
+   * constituant filters do.
+   */
+  public static FileFilter[] sortFilters(Vector filters) {
+    // sort filters alphanumerically
+    Collections.sort(filters);
+
+    // combine matching filters
+    int len = filters.size();
+    Vector v = new Vector(len);
+    for (int i=0; i<len; i++) {
+      FileFilter ffi = (FileFilter) filters.elementAt(i);
+      int ndx = i + 1;
+      while (ndx < len) {
+        FileFilter ff = (FileFilter) filters.elementAt(ndx);
+        if (!ffi.getDescription().equals(ff.getDescription())) break;
+        ndx++;
+      }
+      if (ndx > i + 1) {
+        // create combination filter for matching filters
+        FileFilter[] temp = new FileFilter[ndx - i];
+        for (int j=0; j<temp.length; j++) {
+          temp[j] = (FileFilter) filters.elementAt(i + j);
+        }
+        v.add(new ComboFileFilter(temp, temp[0].getDescription()));
+        i += temp.length - 1; // skip next temp-1 filters
+      }
+      else v.add(ffi);
+    }
+    FileFilter[] result = new FileFilter[v.size()];
+    v.copyInto(result);
+    return result;
+  }
+
+  // -- FileFilter API methods --
+
+  /** Accepts files with the proper filename prefix. */
+  public boolean accept(File f) {
+    for (int i=0; i<filts.length; i++) {
+      if (filts[i].accept(f)) return true;
+    }
+    return false;
+  }
+
+  /** Returns the filter's description. */
+  public String getDescription() { return desc; }
+
+  // -- Object API methods --
+
+  /** Gets a string representation of this file filter. */
+  public String toString() {
+    StringBuffer sb = new StringBuffer("ComboFileFilter: ");
+    sb.append(desc);
+    for (int i=0; i<filts.length; i++) {
+      sb.append("\n\t");
+      sb.append(filts[i].toString());
+    }
+    return sb.toString();
+  }
+
+  // -- Comparable API methods --
+
+  /** Compares two FileFilter objects alphanumerically. */
+  public int compareTo(Object o) {
+    return desc.compareToIgnoreCase(((FileFilter) o).getDescription());
+  }
+
+}
diff --git a/loci/formats/gui/ExtensionFileFilter.java b/loci/formats/gui/ExtensionFileFilter.java
new file mode 100644
index 0000000..1725226
--- /dev/null
+++ b/loci/formats/gui/ExtensionFileFilter.java
@@ -0,0 +1,117 @@
+//
+// ExtensionFileFilter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.gui;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A file filter based on file extensions, for use with a JFileChooser.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/gui/ExtensionFileFilter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/gui/ExtensionFileFilter.java">SVN</a></dd></dl>
+ */
+public class ExtensionFileFilter extends FileFilter
+  implements java.io.FileFilter, Comparable
+{
+
+  // -- Fields --
+
+  /** List of valid extensions. */
+  private String[] exts;
+
+  /** Description. */
+  private String desc;
+
+  // -- Constructors --
+
+  /** Constructs a new filter that accepts the given extension. */
+  public ExtensionFileFilter(String extension, String description) {
+    this(new String[] {extension}, description);
+  }
+
+  /** Constructs a new filter that accepts the given extensions. */
+  public ExtensionFileFilter(String[] extensions, String description) {
+    exts = new String[extensions.length];
+    System.arraycopy(extensions, 0, exts, 0, extensions.length);
+    StringBuffer sb = new StringBuffer(description);
+    boolean first = true;
+    for (int i=0; i<exts.length; i++) {
+      if (exts[i] == null) exts[i] = "";
+      if (exts[i].equals("")) continue;
+      if (first) {
+        sb.append(" (");
+        first = false;
+      }
+      else sb.append(", ");
+      sb.append("*.");
+      sb.append(exts[i]);
+    }
+    sb.append(")");
+    desc = sb.toString();
+  }
+
+  // -- ExtensionFileFilter API methods --
+
+  /** Gets the filter's first valid extension. */
+  public String getExtension() { return exts[0]; }
+
+  /** Gets the filter's valid extensions. */
+  public String[] getExtensions() { return exts; }
+
+  // -- FileFilter API methods --
+
+  /** Accepts files with the proper extensions. */
+  public boolean accept(File f) {
+    if (f.isDirectory()) return true;
+
+    String name = f.getName();
+    int index = name.lastIndexOf('.');
+    String ext = index < 0 ? "" : name.substring(index + 1);
+
+    for (int i=0; i<exts.length; i++) {
+      if (ext.equalsIgnoreCase(exts[i])) return true;
+    }
+
+    return false;
+  }
+
+  /** Gets the filter's description. */
+  public String getDescription() { return desc; }
+
+  // -- Object API methods --
+
+  /** Gets a string representation of this file filter. */
+  public String toString() { return "ExtensionFileFilter: " + desc; }
+
+  // -- Comparable API methods --
+
+  /** Compares two FileFilter objects alphanumerically. */
+  public int compareTo(Object o) {
+    return desc.compareToIgnoreCase(((FileFilter) o).getDescription());
+  }
+
+}
diff --git a/loci/formats/gui/FormatFileFilter.java b/loci/formats/gui/FormatFileFilter.java
new file mode 100644
index 0000000..a1234a8
--- /dev/null
+++ b/loci/formats/gui/FormatFileFilter.java
@@ -0,0 +1,109 @@
+//
+// FormatFileFilter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.gui;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+import loci.formats.IFormatReader;
+
+/**
+ * A file filter for a biological file format, for use with a JFileChooser.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/gui/FormatFileFilter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/gui/FormatFileFilter.java">SVN</a></dd></dl>
+ */
+public class FormatFileFilter extends FileFilter
+  implements java.io.FileFilter, Comparable
+{
+
+  // -- Fields --
+
+  /** Associated file format reader. */
+  private IFormatReader reader;
+
+  /** Whether it is ok to open a file to determine its type. */
+  private boolean allowOpen;
+
+  /** Description. */
+  private String desc;
+
+  // -- Constructors --
+
+  /** Constructs a new filter that accepts files of the given reader's type. */
+  public FormatFileFilter(IFormatReader reader) {
+    this(reader, true);
+  }
+
+  /**
+   * Constructs a new filter that accepts files of the given reader's type,
+   * allowing the reader to open files only if the allowOpen flag is set.
+   * @param reader The reader to use for verifying a file's type.
+   * @param allowOpen Whether it is ok to open a file to determine its type.
+   */
+  public FormatFileFilter(IFormatReader reader, boolean allowOpen) {
+    this.reader = reader;
+    this.allowOpen = allowOpen;
+    StringBuffer sb = new StringBuffer(reader.getFormat());
+    String[] exts = reader.getSuffixes();
+    boolean first = true;
+    for (int i=0; i<exts.length; i++) {
+      if (exts[i] == null || exts[i].equals("")) continue;
+      if (first) {
+        sb.append(" (");
+        first = false;
+      }
+      else sb.append(", ");
+      sb.append("*.");
+      sb.append(exts[i]);
+    }
+    sb.append(")");
+    desc = sb.toString();
+  }
+
+  // -- FileFilter API methods --
+
+  /** Accepts files in accordance with the file format reader. */
+  public boolean accept(File f) {
+    if (f.isDirectory()) return true;
+    return reader.isThisType(f.getPath(), allowOpen);
+  }
+
+  /** Gets the filter's description. */
+  public String getDescription() { return desc; }
+
+  // -- Object API methods --
+
+  /** Gets a string representation of this file filter. */
+  public String toString() { return "FormatFileFilter: " + desc; }
+
+  // -- Comparable API methods --
+
+  /** Compares two FileFilter objects alphanumerically. */
+  public int compareTo(Object o) {
+    return desc.compareTo(((FileFilter) o).getDescription());
+  }
+
+}
diff --git a/loci/formats/gui/GUITools.java b/loci/formats/gui/GUITools.java
new file mode 100644
index 0000000..724d5fc
--- /dev/null
+++ b/loci/formats/gui/GUITools.java
@@ -0,0 +1,175 @@
+//
+// GUITools.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.gui;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Vector;
+import javax.swing.JFileChooser;
+import javax.swing.SwingUtilities;
+import javax.swing.filechooser.FileFilter;
+import loci.formats.*;
+
+/**
+ * A utility class for working with graphical user interfaces.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/gui/GUITools.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/gui/GUITools.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public final class GUITools {
+
+  // -- Constructor --
+
+  private GUITools() { }
+
+  // -- File chooser --
+
+  /** Constructs a list of file filters for the given file format handler. */
+  public static FileFilter[] buildFileFilters(IFormatHandler handler) {
+    FileFilter[] ff = null;
+
+    // unwrap reader
+    while (true) {
+      if (handler instanceof ReaderWrapper) {
+        handler = ((ReaderWrapper) handler).getReader();
+      }
+      else if (handler instanceof FileStitcher) {
+        handler = ((FileStitcher) handler).getReader();
+      }
+      else break;
+    }
+
+    // handle special cases of ImageReader and ImageWriter
+    if (handler instanceof ImageReader) {
+      IFormatReader[] readers = ((ImageReader) handler).getReaders();
+      Vector v = new Vector();
+      for (int i=0; i<readers.length; i++) {
+        // NB: By default, some readers might need to open a file to
+        // determine if it is the proper type, when the extension alone
+        // isn't enough to distinguish.
+        //
+        // We want to disable that behavior for ImageReader,
+        // because otherwise the combination filter is too slow.
+        //
+        // Also, most of the formats that do this are TIFF-based, and the
+        // TIFF reader will already green-light anything with .tif
+        // extension, making more thorough checks redundant.
+        v.add(new FormatFileFilter(readers[i], false));
+      }
+      ff = ComboFileFilter.sortFilters(v);
+    }
+    else if (handler instanceof ImageWriter) {
+      IFormatWriter[] writers = ((ImageWriter) handler).getWriters();
+      Vector v = new Vector();
+      for (int i=0; i<writers.length; i++) {
+        String[] suffixes = writers[i].getSuffixes();
+        String format = writers[i].getFormat();
+        v.add(new ExtensionFileFilter(suffixes, format));
+      }
+      ff = ComboFileFilter.sortFilters(v);
+    }
+
+    // handle default reader and writer cases
+    else if (handler instanceof IFormatReader) {
+      IFormatReader reader = (IFormatReader) handler;
+      ff = new FileFilter[] {new FormatFileFilter(reader)};
+    }
+    else {
+      String[] suffixes = handler.getSuffixes();
+      String format = handler.getFormat();
+      ff = new FileFilter[] {new ExtensionFileFilter(suffixes, format)};
+    }
+    return ff;
+  }
+
+  /** Constructs a file chooser for the given file format handler. */
+  public static JFileChooser buildFileChooser(IFormatHandler handler) {
+    return buildFileChooser(handler, true);
+  }
+
+  /**
+   * Constructs a file chooser for the given file format handler.
+   * If preview flag is set, chooser has an preview pane showing
+   * a thumbnail and other information for the selected file.
+   */
+  public static JFileChooser buildFileChooser(IFormatHandler handler,
+    boolean preview)
+  {
+    return buildFileChooser(buildFileFilters(handler), preview);
+  }
+
+  /**
+   * Builds a file chooser with the given file filters,
+   * as well as an "All supported file types" combo filter.
+   */
+  public static JFileChooser buildFileChooser(final FileFilter[] filters) {
+    return buildFileChooser(filters, true);
+  }
+
+  /**
+   * Builds a file chooser with the given file filters,
+   * as well as an "All supported file types" combo filter.
+   * If preview flag is set, chooser has an preview pane showing
+   * a thumbnail and other information for the selected file.
+   */
+  public static JFileChooser buildFileChooser(final FileFilter[] filters,
+    final boolean preview)
+  {
+    // NB: must construct JFileChooser in the
+    // AWT worker thread, to avoid deadlocks
+    final JFileChooser[] jfc = new JFileChooser[1];
+    Runnable r = new Runnable() {
+      public void run() {
+        JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
+        FileFilter[] ff = ComboFileFilter.sortFilters(filters);
+        FileFilter combo = null;
+        if (ff.length > 1) {
+          combo = new ComboFileFilter(ff, "All supported file types");
+          fc.addChoosableFileFilter(combo);
+        }
+        for (int i=0; i<ff.length; i++) fc.addChoosableFileFilter(ff[i]);
+        if (combo != null) fc.setFileFilter(combo);
+        if (preview) new PreviewPane(fc);
+        jfc[0] = fc;
+      }
+    };
+    if (Thread.currentThread().getName().startsWith("AWT-EventQueue")) {
+      // current thread is the AWT event queue thread; just execute the code
+      r.run();
+    }
+    else {
+      // execute the code with the AWT event thread
+      try {
+        SwingUtilities.invokeAndWait(r);
+      }
+      catch (InterruptedException exc) { return null; }
+      catch (InvocationTargetException exc) { return null; }
+    }
+    return jfc[0];
+  }
+
+}
diff --git a/loci/formats/gui/ImageViewer.java b/loci/formats/gui/ImageViewer.java
new file mode 100644
index 0000000..8792c9c
--- /dev/null
+++ b/loci/formats/gui/ImageViewer.java
@@ -0,0 +1,544 @@
+//
+// ImageViewer.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.gui;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.*;
+import java.io.File;
+import java.io.IOException;
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import loci.formats.*;
+
+/**
+ * ImageViewer is a simple viewer/converter
+ * for the Bio-Formats image formats.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/gui/ImageViewer.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/gui/ImageViewer.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class ImageViewer extends JFrame
+  implements ActionListener, ChangeListener, MouseMotionListener
+{
+
+  // -- Constants --
+
+  protected static final String TITLE = "Bio-Formats Viewer";
+  protected static final GraphicsConfiguration GC =
+    ImageTools.getDefaultConfiguration();
+
+  // -- Fields --
+
+  protected JPanel pane;
+  protected ImageIcon icon;
+  protected JLabel iconLabel;
+  protected JPanel sliderPanel;
+  protected JSlider nSlider, zSlider, tSlider, cSlider;
+  protected JLabel probeLabel;
+  protected JMenuItem fileSave;
+
+  protected IFormatReader myReader;
+  protected ImageWriter myWriter;
+
+  protected String filename;
+  protected IFormatReader in;
+  protected BufferedImage[] images;
+  protected int sizeZ, sizeT, sizeC;
+
+  // -- Constructor --
+
+  /** Constructs an image viewer. */
+  public ImageViewer() {
+    super(TITLE);
+    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+    pane = new JPanel();
+    pane.setLayout(new BorderLayout());
+    setContentPane(pane);
+    setSize(350, 350); // default size
+
+    // navigation sliders
+    sliderPanel = new JPanel();
+    sliderPanel.setVisible(false);
+    sliderPanel.setBorder(new EmptyBorder(5, 3, 5, 3));
+    sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.Y_AXIS));
+    pane.add(BorderLayout.SOUTH, sliderPanel);
+
+    JPanel nPanel = new JPanel();
+    nPanel.setLayout(new BoxLayout(nPanel, BoxLayout.X_AXIS));
+    sliderPanel.add(nPanel);
+    sliderPanel.add(Box.createVerticalStrut(2));
+
+    nSlider = new JSlider(1, 1);
+    nSlider.setEnabled(false);
+    nSlider.addChangeListener(this);
+    nPanel.add(new JLabel("N"));
+    nPanel.add(Box.createHorizontalStrut(3));
+    nPanel.add(nSlider);
+
+    JPanel ztcPanel = new JPanel();
+    ztcPanel.setLayout(new BoxLayout(ztcPanel, BoxLayout.X_AXIS));
+    sliderPanel.add(ztcPanel);
+
+    zSlider = new JSlider(1, 1);
+    Dimension dim = zSlider.getPreferredSize();
+    dim.width = 50;
+    zSlider.setPreferredSize(dim);
+    zSlider.setEnabled(false);
+    zSlider.addChangeListener(this);
+    ztcPanel.add(new JLabel("Z"));
+    ztcPanel.add(Box.createHorizontalStrut(3));
+    ztcPanel.add(zSlider);
+    ztcPanel.add(Box.createHorizontalStrut(7));
+
+    tSlider = new JSlider(1, 1);
+    tSlider.setPreferredSize(dim);
+    tSlider.setEnabled(false);
+    tSlider.addChangeListener(this);
+    ztcPanel.add(new JLabel("T"));
+    ztcPanel.add(Box.createHorizontalStrut(3));
+    ztcPanel.add(tSlider);
+    ztcPanel.add(Box.createHorizontalStrut(7));
+
+    cSlider = new JSlider(1, 1);
+    cSlider.setPreferredSize(dim);
+    cSlider.setEnabled(false);
+    cSlider.addChangeListener(this);
+    ztcPanel.add(new JLabel("C"));
+    ztcPanel.add(Box.createHorizontalStrut(3));
+    ztcPanel.add(cSlider);
+    ztcPanel.add(Box.createHorizontalStrut(7));
+
+    // image icon
+    BufferedImage dummy = ImageTools.makeImage(new byte[1][1], 1, 1);
+    icon = new ImageIcon(dummy);
+    iconLabel = new JLabel(icon, SwingConstants.LEFT);
+    iconLabel.setVerticalAlignment(SwingConstants.TOP);
+    pane.add(new JScrollPane(iconLabel));
+
+    // cursor probe
+    probeLabel = new JLabel(" ");
+    probeLabel.setHorizontalAlignment(SwingConstants.CENTER);
+    probeLabel.setBorder(new BevelBorder(BevelBorder.RAISED));
+    pane.add(BorderLayout.NORTH, probeLabel);
+    iconLabel.addMouseMotionListener(this);
+
+    // menu bar
+    JMenuBar menubar = new JMenuBar();
+    setJMenuBar(menubar);
+    JMenu file = new JMenu("File");
+    file.setMnemonic('f');
+    menubar.add(file);
+    JMenuItem fileOpen = new JMenuItem("Open...");
+    fileOpen.setMnemonic('o');
+    fileOpen.setActionCommand("open");
+    fileOpen.addActionListener(this);
+    file.add(fileOpen);
+    fileSave = new JMenuItem("Save...");
+    fileSave.setMnemonic('s');
+    fileSave.setEnabled(false);
+    fileSave.setActionCommand("save");
+    fileSave.addActionListener(this);
+    file.add(fileSave);
+    boolean canDoNotes = false;
+    try {
+      Class c = Class.forName("loci.ome.notes.Notes");
+      if (c != null) canDoNotes = true;
+    }
+    catch (Throwable t) {
+      if (FormatHandler.debug) LogTools.trace(t);
+    }
+    if (canDoNotes) {
+      JMenuItem fileView = new JMenuItem("View Metadata...");
+      fileView.setMnemonic('m');
+      fileView.setEnabled(true);
+      fileView.setActionCommand("view");
+      fileView.addActionListener(this);
+      file.add(fileView);
+    }
+    JMenuItem fileExit = new JMenuItem("Exit");
+    fileExit.setMnemonic('x');
+    fileExit.setActionCommand("exit");
+    fileExit.addActionListener(this);
+    file.add(fileExit);
+    JMenu help = new JMenu("Help");
+    help.setMnemonic('h');
+    menubar.add(help);
+    JMenuItem helpAbout = new JMenuItem("About...");
+    helpAbout.setMnemonic('a');
+    helpAbout.setActionCommand("about");
+    helpAbout.addActionListener(this);
+    help.add(helpAbout);
+
+    // image I/O engine
+    myReader = new ChannelMerger(new FileStitcher());
+    myWriter = new ImageWriter();
+  }
+
+  /** Opens the given file using the ImageReader. */
+  public void open(String id) {
+    wait(true);
+    try {
+      Location f = new Location(id);
+      id = f.getAbsolutePath();
+      myReader.setId(id);
+      int num = myReader.getImageCount();
+      ProgressMonitor progress = new ProgressMonitor(this,
+        "Reading " + id, null, 0, num + 1);
+      sizeZ = myReader.getSizeZ();
+      sizeT = myReader.getSizeT();
+      sizeC = myReader.getEffectiveSizeC();
+      //if (myReader.isRGB(id)) sizeC = (sizeC + 2) / 3; // adjust for RGB
+      progress.setProgress(1);
+      BufferedImage[] img = new BufferedImage[num];
+      for (int i=0; i<num; i++) {
+        if (progress.isCanceled()) break;
+        img[i] = myReader.openImage(i);
+        if (i == 0) setImages(myReader, img);
+        progress.setProgress(i + 2);
+      }
+      myReader.close(true);
+    }
+    catch (FormatException exc) {
+      LogTools.trace(exc);
+      wait(false);
+      return;
+    }
+    catch (IOException exc) {
+      LogTools.trace(exc);
+      wait(false);
+      return;
+    }
+    wait(false);
+  }
+
+  /** Saves the current images to the given file using the ImageWriter. */
+  public void save(String id) {
+    if (images == null) return;
+    wait(true);
+    try {
+      myWriter.setId(id);
+      boolean stack = myWriter.canDoStacks();
+      ProgressMonitor progress = new ProgressMonitor(this,
+        "Saving " + id, null, 0, stack ? images.length : 1);
+      if (stack) {
+        // save entire stack
+        for (int i=0; i<images.length; i++) {
+          progress.setProgress(i);
+          boolean canceled = progress.isCanceled();
+          myWriter.saveImage(images[i], i == images.length - 1 || canceled);
+          if (canceled) break;
+        }
+        progress.setProgress(images.length);
+      }
+      else {
+        // save current image only
+        myWriter.saveImage(getImage(), true);
+        progress.setProgress(1);
+      }
+    }
+    catch (FormatException exc) { LogTools.trace(exc); }
+    catch (IOException exc) { LogTools.trace(exc); }
+    wait(false);
+  }
+
+  /** Sets the viewer to display the given images. */
+  public void setImages(BufferedImage[] img) { setImages(null, img); }
+
+  /**
+   * Sets the viewer to display the given images, obtaining
+   * corresponding core metadata from the specified format reader.
+   */
+  public void setImages(IFormatReader reader, BufferedImage[] img) {
+    filename = reader == null ? null : reader.getCurrentFile();
+    in = reader;
+    images = img;
+
+    if (reader == null) sizeZ = sizeC = sizeT = 1;
+    else {
+      sizeZ = reader.getSizeZ();
+      sizeT = reader.getSizeT();
+      sizeC = reader.getEffectiveSizeC();
+    }
+
+    fileSave.setEnabled(true);
+    nSlider.removeChangeListener(this);
+    zSlider.removeChangeListener(this);
+    tSlider.removeChangeListener(this);
+    cSlider.removeChangeListener(this);
+    nSlider.setValue(1);
+    nSlider.setMaximum(images.length);
+    nSlider.setEnabled(images.length > 1);
+    zSlider.setValue(1);
+    zSlider.setMaximum(sizeZ);
+    zSlider.setEnabled(sizeZ > 1);
+    tSlider.setValue(1);
+    tSlider.setMaximum(sizeT);
+    tSlider.setEnabled(sizeT > 1);
+    cSlider.setValue(1);
+    cSlider.setMaximum(sizeC);
+    cSlider.setEnabled(sizeC > 1);
+    nSlider.addChangeListener(this);
+    zSlider.addChangeListener(this);
+    tSlider.addChangeListener(this);
+    cSlider.addChangeListener(this);
+    sliderPanel.setVisible(images.length > 1);
+
+    updateLabel(-1, -1);
+    sb.setLength(0);
+    if (filename != null) {
+      sb.append(reader.getCurrentFile());
+      sb.append(" ");
+    }
+    String format = reader == null ? null : reader.getFormat();
+    if (format != null) {
+      sb.append("(");
+      sb.append(format);
+      sb.append(")");
+      sb.append(" ");
+    }
+    if (filename != null || format != null) sb.append("- ");
+    sb.append(TITLE);
+    setTitle(sb.toString());
+    icon.setImage(images == null ? null : images[0]);
+    pack();
+  }
+
+  /** Gets the currently displayed image. */
+  public BufferedImage getImage() {
+    int ndx = getImageIndex();
+    return images == null || ndx >= images.length ? null : images[ndx];
+  }
+
+  /** Gets the index of the currently displayed image. */
+  public int getImageIndex() { return nSlider.getValue() - 1; }
+
+  /** Gets the Z value of the currently displayed image. */
+  public int getZ() { return zSlider.getValue() - 1; }
+
+  /** Gets the T value of the currently displayed image. */
+  public int getT() { return tSlider.getValue() - 1; }
+
+  /** Gets the C value of the currently displayed image. */
+  public int getC() { return cSlider.getValue() - 1; }
+
+  // -- ActionListener API methods --
+
+  /** Handles menu commands. */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if ("open".equals(cmd)) {
+      wait(true);
+      JFileChooser chooser = GUITools.buildFileChooser(myReader);
+      wait(false);
+      int rval = chooser.showOpenDialog(this);
+      if (rval == JFileChooser.APPROVE_OPTION) {
+        final File file = chooser.getSelectedFile();
+        if (file != null) {
+          new Thread("ImageViewer-Opener") {
+            public void run() { open(file.getAbsolutePath()); }
+          }.start();
+        }
+      }
+    }
+    else if ("save".equals(cmd)) {
+      wait(true);
+      JFileChooser chooser = GUITools.buildFileChooser(myWriter);
+      wait(false);
+      int rval = chooser.showSaveDialog(this);
+      if (rval == JFileChooser.APPROVE_OPTION) {
+        final File file = chooser.getSelectedFile();
+        if (file != null) {
+          new Thread("ImageViewer-Saver") {
+            public void run() { save(file.getPath()); }
+          }.start();
+        }
+      }
+    }
+    else if ("view".equals(cmd)) {
+      // NB: avoid dependencies on optional loci.ome.notes package
+      ReflectedUniverse r = new ReflectedUniverse();
+      try {
+        r.exec("import loci.ome.notes.Notes");
+        r.setVar("filename", filename);
+        r.exec("new Notes(null, filename)");
+      }
+      catch (ReflectException exc) { LogTools.trace(exc); }
+    }
+    else if ("exit".equals(cmd)) dispose();
+    else if ("about".equals(cmd)) {
+      // HACK - JOptionPane prevents shutdown on dispose
+      setDefaultCloseOperation(EXIT_ON_CLOSE);
+
+      JOptionPane.showMessageDialog(this,
+        "LOCI Bio-Formats\n" +
+        "Built @date@\n\n" +
+        "The Bio-Formats library is LOCI software written by\n" +
+        "Melissa Linkert, Curtis Rueden, Chris Allan, Eric Kjellman\n" +
+        "and Brian Loranger.\n" +
+        "http://www.loci.wisc.edu/ome/formats.html",
+        "Bio-Formats", JOptionPane.INFORMATION_MESSAGE);
+    }
+  }
+
+  // -- ChangeListener API methods --
+
+  /** Handles slider events. */
+  public void stateChanged(ChangeEvent e) {
+    Object src = e.getSource();
+    if (src == nSlider) {
+      // update Z, T and C sliders
+      int ndx = getImageIndex();
+      int[] zct = in == null ? new int[] {-1, -1, -1} : in.getZCTCoords(ndx);
+      if (zct[0] >= 0) {
+        zSlider.removeChangeListener(this);
+        zSlider.setValue(zct[0] + 1);
+        zSlider.addChangeListener(this);
+      }
+      if (zct[1] >= 0) {
+        cSlider.removeChangeListener(this);
+        cSlider.setValue(zct[1] + 1);
+        cSlider.addChangeListener(this);
+      }
+      if (zct[2] >= 0) {
+        tSlider.removeChangeListener(this);
+        tSlider.setValue(zct[2] + 1);
+        tSlider.addChangeListener(this);
+      }
+    }
+    else {
+      // update N slider
+      int ndx = in == null ? -1 : in.getIndex(getZ(), getC(), getT());
+      if (ndx >= 0) {
+        nSlider.removeChangeListener(this);
+        nSlider.setValue(ndx + 1);
+        nSlider.addChangeListener(this);
+      }
+    }
+    updateLabel(-1, -1);
+    BufferedImage image = getImage();
+    if (image != null) icon.setImage(getImage());
+    iconLabel.repaint();
+  }
+
+  // -- MouseMotionListener API methods --
+
+  /** Handles cursor probes. */
+  public void mouseDragged(MouseEvent e) { updateLabel(e.getX(), e.getY()); }
+
+  /** Handles cursor probes. */
+  public void mouseMoved(MouseEvent e) { updateLabel(e.getX(), e.getY()); }
+
+  // -- Helper methods --
+
+  protected StringBuffer sb = new StringBuffer();
+
+  /** Updates cursor probe label. */
+  protected void updateLabel(int x, int y) {
+    if (images == null) return;
+    int ndx = getImageIndex();
+    sb.setLength(0);
+    if (images.length > 1) {
+      sb.append("N=");
+      sb.append(ndx + 1);
+      sb.append("/");
+      sb.append(images.length);
+    }
+    if (sizeZ > 1) {
+      sb.append("; Z=");
+      sb.append(getZ() + 1);
+      sb.append("/");
+      sb.append(sizeZ);
+    }
+    if (sizeT > 1) {
+      sb.append("; T=");
+      sb.append(getT() + 1);
+      sb.append("/");
+      sb.append(sizeT);
+    }
+    if (sizeC > 1) {
+      sb.append("; C=");
+      sb.append(getC() + 1);
+      sb.append("/");
+      sb.append(sizeC);
+    }
+    BufferedImage image = images[ndx];
+    int w = image == null ? -1 : image.getWidth();
+    int h = image == null ? -1 : image.getHeight();
+    if (x >= w) x = w - 1;
+    if (y >= h) y = h - 1;
+    if (x >= 0 && y >= 0) {
+      if (images.length > 1) sb.append("; ");
+      sb.append("X=");
+      sb.append(x);
+      if (w > 0) {
+        sb.append("/");
+        sb.append(w);
+      }
+      sb.append("; Y=");
+      sb.append(y);
+      if (h > 0) {
+        sb.append("/");
+        sb.append(h);
+      }
+      if (image != null) {
+        Raster r = image.getRaster();
+        double[] pix = r.getPixel(x, y, (double[]) null);
+        sb.append("; value");
+        sb.append(pix.length > 1 ? "s=(" : "=");
+        for (int i=0; i<pix.length; i++) {
+          if (i > 0) sb.append(", ");
+          sb.append(pix[i]);
+        }
+        if (pix.length > 1) sb.append(")");
+        sb.append("; type=");
+        int pixelType = ImageTools.getPixelType(image);
+        sb.append(FormatTools.getPixelTypeString(pixelType));
+      }
+    }
+    sb.append(" ");
+    probeLabel.setText(sb.toString());
+  }
+
+  /** Toggles wait cursor. */
+  protected void wait(boolean wait) {
+    setCursor(wait ? Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR) : null);
+  }
+
+  // -- Main method --
+
+  public static void main(String[] args) {
+    ImageViewer viewer = new ImageViewer();
+    viewer.setVisible(true);
+    if (args.length > 0) viewer.open(args[0]);
+  }
+
+}
diff --git a/loci/formats/gui/PreviewPane.java b/loci/formats/gui/PreviewPane.java
new file mode 100644
index 0000000..0215a74
--- /dev/null
+++ b/loci/formats/gui/PreviewPane.java
@@ -0,0 +1,225 @@
+//
+// PreviewPane.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.gui;
+
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.IOException;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import loci.formats.*;
+
+/**
+ * PreviewPane is a panel for use as a JFileChooser accessory, displaying
+ * a thumbnail for the selected image, loaded in a separate thread.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/gui/PreviewPane.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/gui/PreviewPane.java">SVN</a></dd></dl>
+ */
+public class PreviewPane extends JPanel
+  implements PropertyChangeListener, Runnable
+{
+
+  // -- Fields --
+
+  /** Reader for use when loading thumbnails. */
+  protected IFormatReader reader;
+
+  /** Labels containing thumbnail and dimensional information. */
+  protected JLabel iconLabel, resLabel, zctLabel, typeLabel;
+
+  /** Thumbnail loading thread. */
+  protected Thread loader;
+
+  /** Flag indicating whether loader thread should keep running. */
+  protected boolean loaderAlive;
+
+  /** Current ID to load. */
+  protected String loadId;
+
+  /** Last ID loaded. */
+  protected String lastId;
+
+  // -- Constructors --
+
+  /** Constructs a preview pane for the given file chooser. */
+  public PreviewPane(JFileChooser jc) {
+    super();
+    setBorder(new EmptyBorder(0, 10, 0, 0));
+
+    reader = new ImageReader();
+    reader.setNormalized(true);
+
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    iconLabel = new JLabel();
+    iconLabel.setMinimumSize(new java.awt.Dimension(128, -1));
+    iconLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    add(iconLabel);
+    add(Box.createVerticalStrut(7));
+    resLabel = new JLabel("");
+    resLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    add(resLabel);
+    zctLabel = new JLabel("");
+    zctLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    add(zctLabel);
+    typeLabel = new JLabel("");
+    typeLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    add(typeLabel);
+
+    if (jc != null) {
+      jc.setAccessory(this);
+      jc.addPropertyChangeListener(this);
+
+      // start separate loader thread
+      loaderAlive = true;
+      loader = new Thread(this, "Preview");
+      loader.start();
+    }
+  }
+
+  // -- Component API methods --
+
+  /* @see java.awt.Component.getPreferredSize() */
+  public Dimension getPreferredSize() {
+    Dimension prefSize = super.getPreferredSize();
+    return new Dimension(128, prefSize.height);
+  }
+
+  // -- PropertyChangeListener API methods --
+
+  /**
+   * Property change event, to listen for when a new
+   * file is selected, or the file chooser closes.
+   */
+  public void propertyChange(PropertyChangeEvent e) {
+    String prop = e.getPropertyName();
+    if (prop.equals("JFileChooserDialogIsClosingProperty")) {
+      // notify loader thread that it should stop
+      loaderAlive = false;
+    }
+
+    if (!prop.equals(JFileChooser.SELECTED_FILE_CHANGED_PROPERTY)) return;
+
+    File selection = (File) e.getNewValue();
+    if (selection == null) return;
+
+    loadId = selection.getAbsolutePath();
+  }
+
+  // -- Runnable API methods --
+
+  /** Thumbnail loading routine. */
+  public void run() {
+    while (loaderAlive) {
+      try { Thread.sleep(100); }
+      catch (InterruptedException exc) { LogTools.trace(exc); }
+
+      String id = loadId;
+      if (id == lastId) continue;
+      if (id != null && lastId != null) {
+        String[] files = reader.getUsedFiles();
+        boolean found = false;
+        for (int i=0; i<files.length; i++) {
+          if (id.equals(files[i])) {
+            found = true;
+            break;
+          }
+        }
+        if (found) continue;
+      }
+      lastId = id;
+
+      iconLabel.setIcon(null);
+      resLabel.setText(id == null ? "" : "Reading...");
+      zctLabel.setText("");
+      typeLabel.setText("");
+
+      if (id == null) continue;
+
+      try { reader.setId(id); }
+      catch (FormatException exc) {
+        LogTools.trace(exc);
+        resLabel.setText("Unsupported");
+        boolean badFormat = exc.getMessage().startsWith("Unknown file format");
+        zctLabel.setText(badFormat ? "format" : "file");
+        continue;
+      }
+      catch (IOException exc) {
+        LogTools.trace(exc);
+        resLabel.setText("Unsupported");
+        zctLabel.setText("file");
+        continue;
+      }
+      if (id != loadId) continue;
+
+      iconLabel.setIcon(id == null ? null :
+        new ImageIcon(makeImage("Loading...")));
+      resLabel.setText(reader.getSizeX() + " x " + reader.getSizeY());
+      zctLabel.setText(reader.getSizeZ() + "Z x " +
+        reader.getSizeT() + "T x " + reader.getSizeC() + "C");
+      typeLabel.setText(reader.getRGBChannelCount() + " x " +
+        FormatTools.getPixelTypeString(reader.getPixelType()));
+
+      // open middle image thumbnail
+      int z = reader.getSizeZ() / 2;
+      int t = reader.getSizeT() / 2;
+      int ndx = reader.getIndex(z, 0, t);
+      BufferedImage thumb = null;
+      try { thumb = reader.openThumbImage(ndx); }
+      catch (FormatException exc) { LogTools.trace(exc); }
+      catch (IOException exc) { LogTools.trace(exc); }
+      iconLabel.setIcon(new ImageIcon(thumb == null ?
+        makeImage("Failed") : thumb));
+      repaint();
+    }
+  }
+
+  // -- Helper methods --
+
+  /**
+   * Creates a blank image with the given message painted on top (e.g.,
+   * a loading or error message), matching the size of the active reader's
+   * thumbnails.
+   */
+  private BufferedImage makeImage(String message) {
+    int w = reader.getThumbSizeX(), h = reader.getThumbSizeY();
+    if (w < 128) w = 128;
+    if (h < 32) h = 32;
+    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+    Graphics2D g = image.createGraphics();
+    Rectangle2D.Float r = (Rectangle2D.Float)
+      g.getFont().getStringBounds(message, g.getFontRenderContext());
+    g.drawString(message, (w - r.width) / 2, (h - r.height) / 2 + r.height);
+    g.dispose();
+    return image;
+  }
+
+}
diff --git a/loci/formats/in/AVIReader.java b/loci/formats/in/AVIReader.java
new file mode 100644
index 0000000..41ccf69
--- /dev/null
+++ b/loci/formats/in/AVIReader.java
@@ -0,0 +1,508 @@
+//
+// AVIReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.Vector;
+import loci.formats.*;
+import loci.formats.codec.BitBuffer;
+import loci.formats.codec.*;
+
+/**
+ * AVIReader is the file format reader for AVI files.
+ *
+ * Much of this code was adapted from Wayne Rasband's AVI Movie Reader
+ * plugin for ImageJ (available at http://rsb.info.nih.gov/ij).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/AVIReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/AVIReader.java">SVN</a></dd></dl>
+ */
+public class AVIReader extends FormatReader {
+
+  // -- Constants --
+
+  /** Supported compression types. */
+  private static final int MSRLE = 1;
+  private static final int MS_VIDEO = 1296126531;
+
+  // -- Fields --
+
+  /** Offset to each plane. */
+  private Vector offsets;
+
+  /** Number of bytes in each plane. */
+  private Vector lengths;
+
+  private String type = "error";
+  private String fcc = "error";
+  private int size = -1;
+  private long pos;
+
+  // Stream Format chunk fields
+
+  private int bmpColorsUsed, bmpWidth;
+  private int bmpCompression, bmpScanLineSize;
+  private short bmpBitsPerPixel;
+  private byte[][] lut = null;
+
+  private byte[] lastImage;
+
+  // -- Constructor --
+
+  /** Constructs a new AVI reader. */
+  public AVIReader() { super("Audio Video Interleave", "avi"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return new String(block).startsWith("RIFF");
+  }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    return lut;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int bytes = FormatTools.getBytesPerPixel(core.pixelType[0]);
+    double p = ((double) bmpScanLineSize) / bmpBitsPerPixel;
+    int effectiveWidth = (int) (bmpScanLineSize / p);
+    if (effectiveWidth == 0 || effectiveWidth < core.sizeX[0]) {
+      effectiveWidth = core.sizeX[0];
+    }
+
+    long fileOff = ((Long) offsets.get(no)).longValue();
+    in.seek(fileOff);
+
+    if (bmpCompression != 0) return uncompress(no, buf);
+
+    if (bmpBitsPerPixel < 8) {
+      int rawSize = bytes * core.sizeY[0] * effectiveWidth * core.sizeC[0];
+      rawSize /= (8 / bmpBitsPerPixel);
+
+      byte[] b = new byte[rawSize];
+
+      int len = rawSize / core.sizeY[0];
+      for (int y=0; y<core.sizeY[0]; y++) {
+        in.read(b, (core.sizeY[0] - y - 1) * len, len);
+      }
+
+      BitBuffer bb = new BitBuffer(b);
+
+      for (int i=0; i<buf.length; i++) {
+        buf[i] = (byte) bb.getBits(bmpBitsPerPixel);
+      }
+
+      return buf;
+    }
+
+    int pad = bmpScanLineSize - core.sizeX[0]*(bmpBitsPerPixel / 8);
+    int scanline = core.sizeX[0] * (bmpBitsPerPixel / 8);
+
+    for (int i=core.sizeY[0] - 1; i>=0; i--) {
+      in.read(buf, i*scanline, scanline);
+      if (bmpBitsPerPixel == 24) {
+        for (int j=0; j<core.sizeX[0]; j++) {
+          byte r = buf[i*scanline + j*3 + 2];
+          buf[i*scanline + j*3 + 2] = buf[i*scanline + j*3];
+          buf[i*scanline + j*3] = r;
+        }
+      }
+      in.skipBytes(pad * (bmpBitsPerPixel / 8));
+    }
+
+    if (bmpBitsPerPixel == 16) {
+      // channels are separated, need to swap them
+      byte[] r = new byte[core.sizeX[0] * core.sizeY[0] * 2];
+      System.arraycopy(buf, 2 * (buf.length / 3), r, 0, r.length);
+      System.arraycopy(buf, 0, buf, 2 * (buf.length / 3), r.length);
+      System.arraycopy(r, 0, buf, 0, r.length);
+    }
+
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("AVIReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(true);
+
+    status("Verifying AVI format");
+
+    offsets = new Vector();
+    lengths = new Vector();
+
+    String listString;
+
+    type = in.readString(4);
+    size = in.readInt();
+    fcc = in.readString(4);
+
+    if (type.equals("RIFF")) {
+      if (!fcc.equals("AVI ")) {
+        throw new FormatException("Sorry, AVI RIFF format not found.");
+      }
+    }
+    else throw new FormatException("Not an AVI file");
+
+    pos = in.getFilePointer();
+    long spos = pos;
+
+    status("Searching for image data");
+
+    while ((in.length() - in.getFilePointer()) > 4) {
+      listString = in.readString(4);
+      in.seek(pos);
+      if (listString.equals(" JUN")) {
+        in.skipBytes(1);
+        pos++;
+      }
+
+      if (listString.equals("JUNK")) {
+        type = in.readString(4);
+        size = in.readInt();
+
+        if (type.equals("JUNK")) {
+          in.skipBytes(size);
+        }
+      }
+      else if (listString.equals("LIST")) {
+        spos = in.getFilePointer();
+        type = in.readString(4);
+        size = in.readInt();
+        fcc = in.readString(4);
+
+        in.seek(spos);
+        if (fcc.equals("hdrl")) {
+          type = in.readString(4);
+          size = in.readInt();
+          fcc = in.readString(4);
+
+          if (type.equals("LIST")) {
+            if (fcc.equals("hdrl")) {
+              type = in.readString(4);
+              size = in.readInt();
+              if (type.equals("avih")) {
+                spos = in.getFilePointer();
+
+                addMeta("Microseconds per frame", new Integer(in.readInt()));
+                addMeta("Max. bytes per second", new Integer(in.readInt()));
+
+                in.skipBytes(8);
+
+                addMeta("Total frames", new Integer(in.readInt()));
+                addMeta("Initial frames", new Integer(in.readInt()));
+
+                in.skipBytes(8);
+                core.sizeX[0] = in.readInt();
+
+                addMeta("Frame height", new Integer(in.readInt()));
+                addMeta("Scale factor", new Integer(in.readInt()));
+                addMeta("Frame rate", new Integer(in.readInt()));
+                addMeta("Start time", new Integer(in.readInt()));
+                addMeta("Length", new Integer(in.readInt()));
+
+                addMeta("Frame width", new Integer(core.sizeX[0]));
+
+                if (spos + size <= in.length()) {
+                  in.seek(spos + size);
+                }
+              }
+            }
+          }
+        }
+        else if (fcc.equals("strl")) {
+          long startPos = in.getFilePointer();
+          long streamSize = size;
+
+          type = in.readString(4);
+          size = in.readInt();
+          fcc = in.readString(4);
+
+          if (type.equals("LIST")) {
+            if (fcc.equals("strl")) {
+              type = in.readString(4);
+              size = in.readInt();
+
+              if (type.equals("strh")) {
+                spos = in.getFilePointer();
+                in.skipBytes(40);
+
+                addMeta("Stream quality", new Integer(in.readInt()));
+                addMeta("Stream sample size", new Integer(in.readInt()));
+
+                if (spos + size <= in.length()) {
+                  in.seek(spos + size);
+                }
+              }
+
+              type = in.readString(4);
+              size = in.readInt();
+              if (type.equals("strf")) {
+                spos = in.getFilePointer();
+
+                in.skipBytes(4);
+                bmpWidth = in.readInt();
+                core.sizeY[0] = in.readInt();
+                in.skipBytes(2);
+                bmpBitsPerPixel = in.readShort();
+                bmpCompression = in.readInt();
+                in.skipBytes(4);
+
+                addMeta("Horizontal resolution", new Integer(in.readInt()));
+                addMeta("Vertical resolution", new Integer(in.readInt()));
+
+                bmpColorsUsed = in.readInt();
+                in.skipBytes(4);
+
+                addMeta("Bitmap compression value",
+                  new Integer(bmpCompression));
+                addMeta("Number of colors used", new Integer(bmpColorsUsed));
+                addMeta("Bits per pixel", new Integer(bmpBitsPerPixel));
+
+                // scan line is padded with zeros to be a multiple of 4 bytes
+                int npad = bmpWidth % 4;
+                if (npad > 0) npad = 4 - npad;
+
+                bmpScanLineSize = (bmpWidth + npad) * (bmpBitsPerPixel / 8);
+
+                int bmpActualColorsUsed = 0;
+                if (bmpColorsUsed != 0) {
+                  bmpActualColorsUsed = bmpColorsUsed;
+                }
+                else if (bmpBitsPerPixel < 16) {
+                  // a value of 0 means we determine this based on the
+                  // bits per pixel
+                  bmpActualColorsUsed = 1 << bmpBitsPerPixel;
+                }
+
+                if (bmpCompression != MSRLE && bmpCompression != 0 &&
+                  bmpCompression != MS_VIDEO)
+                {
+                  throw new FormatException("Unsupported compression type " +
+                    bmpCompression);
+                }
+
+                if (!(bmpBitsPerPixel == 4 || bmpBitsPerPixel == 8 ||
+                  bmpBitsPerPixel == 24 || bmpBitsPerPixel == 16 ||
+                  bmpBitsPerPixel == 32))
+                {
+                  throw new FormatException(bmpBitsPerPixel +
+                    " bits per pixel not supported");
+                }
+
+                if (bmpActualColorsUsed != 0) {
+                  // read the palette
+                  lut = new byte[3][bmpColorsUsed];
+
+                  for (int i=0; i<bmpColorsUsed; i++) {
+                    lut[2][i] = in.readByte();
+                    lut[1][i] = in.readByte();
+                    lut[0][i] = in.readByte();
+                    in.skipBytes(1);
+                  }
+                }
+
+                in.seek(spos + size);
+              }
+            }
+
+            spos = in.getFilePointer();
+            type = in.readString(4);
+            size = in.readInt();
+            if (type.equals("strd")) {
+              in.skipBytes(size);
+            }
+            else {
+              in.seek(spos);
+            }
+
+            spos = in.getFilePointer();
+            type = in.readString(4);
+            size = in.readInt();
+            if (type.equals("strn")) {
+              in.skipBytes(size);
+            }
+            else {
+              in.seek(spos);
+            }
+          }
+
+          if (startPos + streamSize + 8 <= in.length()) {
+            in.seek(startPos + 8 + streamSize);
+          }
+        }
+        else if (fcc.equals("movi")) {
+          type = in.readString(4);
+          size = in.readInt();
+          fcc = in.readString(4);
+
+          if (type.equals("LIST")) {
+            if (fcc.equals("movi")) {
+              spos = in.getFilePointer();
+              if (spos >= in.length() - 12) break;
+              type = in.readString(4);
+              size = in.readInt();
+              fcc = in.readString(4);
+              if (!(type.equals("LIST") && fcc.equals("rec "))) {
+                in.seek(spos);
+              }
+
+              spos = in.getFilePointer();
+              type = in.readString(4);
+              size = in.readInt();
+
+              while (type.substring(2).equals("db") ||
+                type.substring(2).equals("dc") ||
+                type.substring(2).equals("wb"))
+              {
+                if (type.substring(2).equals("db") ||
+                  type.substring(2).equals("dc"))
+                {
+                  offsets.add(new Long(in.getFilePointer()));
+                  lengths.add(new Long(size));
+                  in.skipBytes(size);
+                }
+
+                spos = in.getFilePointer();
+
+                type = in.readString(4);
+                size = in.readInt();
+                if (type.equals("JUNK")) {
+                  in.skipBytes(size);
+                  spos = in.getFilePointer();
+                  type = in.readString(4);
+                  size = in.readInt();
+                }
+              }
+              in.seek(spos);
+            }
+          }
+        }
+        else {
+          // skipping unknown block
+          try {
+            in.skipBytes(8 + size);
+          }
+          catch (IllegalArgumentException iae) { }
+        }
+      }
+      else {
+        // skipping unknown block
+        type = in.readString(4);
+        if (in.getFilePointer() + size + 4 <= in.length()) {
+          size = in.readInt();
+          in.skipBytes(size);
+        }
+      }
+      pos = in.getFilePointer();
+    }
+    status("Populating metadata");
+
+    core.imageCount[0] = offsets.size();
+
+    core.rgb[0] = bmpBitsPerPixel > 8 || (bmpCompression != 0);
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = core.rgb[0] ? 3 : 1;
+    core.sizeT[0] = core.imageCount[0];
+    core.currentOrder[0] = core.sizeC[0] == 3 ? "XYCTZ" : "XYTCZ";
+    core.littleEndian[0] = true;
+    core.interleaved[0] = bmpBitsPerPixel != 16;
+    core.indexed[0] = bmpBitsPerPixel == 8 && bmpCompression != 0;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    if (bmpBitsPerPixel <= 8) core.pixelType[0] = FormatTools.UINT8;
+    else if (bmpBitsPerPixel == 16) core.pixelType[0] = FormatTools.UINT16;
+    else if (bmpBitsPerPixel == 32) core.pixelType[0] = FormatTools.UINT32;
+    else if (bmpBitsPerPixel == 24) core.pixelType[0] = FormatTools.UINT8;
+    else {
+      throw new FormatException(
+          "Unknown matching for pixel bit width of: " + bmpBitsPerPixel);
+    }
+
+    if (bmpCompression != 0) core.pixelType[0] = FormatTools.UINT8;
+
+    MetadataStore store = getMetadataStore();
+
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null);
+    }
+  }
+
+  // -- Helper methods --
+
+  private byte[] uncompress(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    byte[] b = new byte[(int) ((Long) lengths.get(no)).longValue()];
+    in.read(b);
+    if (bmpCompression == MSRLE) {
+      Object[] options = new Object[2];
+      options[1] = lastImage;
+      options[0] = new int[] {core.sizeX[0], core.sizeY[0]};
+      MSRLECodec codec = new MSRLECodec();
+      buf = codec.decompress(b, options);
+      lastImage = buf;
+      if (no == core.imageCount[0] - 1) lastImage = null;
+      return buf;
+    }
+    else if (bmpCompression == MS_VIDEO) {
+      Object[] options = new Object[4];
+      options[0] = new Integer(bmpBitsPerPixel);
+      options[1] = new Integer(core.sizeX[0]);
+      options[2] = new Integer(core.sizeY[0]);
+      options[3] = lastImage;
+
+      MSVideoCodec codec = new MSVideoCodec();
+      buf = codec.decompress(b, options);
+      lastImage = buf;
+      if (no == core.imageCount[0] - 1) lastImage = null;
+      return buf;
+    }
+    throw new FormatException("Unsupported compression : " + bmpCompression);
+  }
+
+}
diff --git a/loci/formats/in/AliconaReader.java b/loci/formats/in/AliconaReader.java
new file mode 100644
index 0000000..c086a5c
--- /dev/null
+++ b/loci/formats/in/AliconaReader.java
@@ -0,0 +1,176 @@
+//
+// AliconaReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * AliconaReader is the file format reader for Alicona AL3D files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/AliconaReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/AliconaReader.java">SVN</a></dd></dl>
+ */
+public class AliconaReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Image offset. */
+  private int textureOffset;
+
+  /** Number of bytes per pixel (either 1 or 2). */
+  private int numBytes;
+
+  // -- Constructor --
+
+  /** Constructs a new Alicona reader. */
+  public AliconaReader() { super("Alicona AL3D", "al3d"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return (new String(block)).indexOf("Alicona") != -1;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int pad = (8 - (core.sizeX[0] % 8)) % 8;
+
+    for (int i=0; i<numBytes; i++) {
+      in.seek(textureOffset + (no * (core.sizeX[0] + pad)*core.sizeY[0]*(i+1)));
+      for (int j=0; j<core.sizeX[0] * core.sizeY[0]; j++) {
+        buf[j*numBytes + i] = in.readByte();
+        if (j % core.sizeX[0] == core.sizeX[0] - 1) in.skipBytes(pad);
+      }
+    }
+
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("AliconaReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    // check that this is a valid AL3D file
+    status("Verifying Alicona format");
+    String magicString = in.readString(17);
+    if (!magicString.trim().equals("AliconaImaging")) {
+      throw new FormatException("Invalid magic string : " +
+        "expected 'AliconaImaging', got " + magicString);
+    }
+
+    // now we read a series of tags
+    // each one is 52 bytes - 20 byte key + 30 byte value + 2 byte CRLF
+
+    status("Reading tags");
+
+    int count = 2;
+
+    boolean hasC = false;
+    String voltage = null, magnification = null;
+    String pntX = null, pntY = null, pntZ = null;
+
+    for (int i=0; i<count; i++) {
+      String key = in.readString(20).trim();
+      String value = in.readString(30).trim();
+
+      addMeta(key, value);
+      in.skipBytes(2);
+
+      if (key.equals("TagCount")) count += Integer.parseInt(value);
+      else if (key.equals("Rows")) core.sizeY[0] = Integer.parseInt(value);
+      else if (key.equals("Cols")) core.sizeX[0] = Integer.parseInt(value);
+      else if (key.equals("NumberOfPlanes")) {
+        core.imageCount[0] = Integer.parseInt(value);
+      }
+      else if (key.equals("TextureImageOffset")) {
+        textureOffset = Integer.parseInt(value);
+      }
+      else if (key.equals("TexturePtr") && !value.equals("7")) hasC = true;
+      else if (key.equals("Voltage")) voltage = value;
+      else if (key.equals("Magnification")) magnification = value;
+      else if (key.equals("PlanePntX")) pntX = value;
+      else if (key.equals("PlanePntY")) pntY = value;
+      else if (key.equals("PlanePntZ")) pntZ = value;
+    }
+
+    status("Populating metadata");
+
+    numBytes = (int) (in.length() - textureOffset) /
+      (core.sizeX[0] * core.sizeY[0] * core.imageCount[0]);
+
+    core.sizeC[0] = hasC ? 3 : 1;
+    core.sizeZ[0] = 1;
+    core.sizeT[0] = core.imageCount[0] / core.sizeC[0];
+    core.rgb[0] = false;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = true;
+
+    core.pixelType[0] = numBytes == 2 ? FormatTools.UINT16 : FormatTools.UINT8;
+    core.currentOrder[0] = "XYCTZ";
+    core.metadataComplete[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    MetadataStore store = getMetadataStore();
+
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+
+    if (voltage != null) {
+      store.setDetector(null, null, null, null, null, new Float(voltage),
+        null, null, null);
+    }
+    if (magnification != null) {
+      store.setObjective(null, null, null, null, new Float(magnification),
+        null, null);
+    }
+
+    if (pntX != null && pntY != null && pntZ != null) {
+      store.setDimensions(new Float(pntX.trim()), new Float(pntY.trim()),
+        new Float(pntZ.trim()), null, null, null);
+    }
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+       null, null, null, null, null, null, null, null, null, null, null, null,
+       null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/BMPReader.java b/loci/formats/in/BMPReader.java
new file mode 100644
index 0000000..bc800fe
--- /dev/null
+++ b/loci/formats/in/BMPReader.java
@@ -0,0 +1,258 @@
+//
+// BMPReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * BMPReader is the file format reader for Microsoft Bitmap (BMP) files.
+ * See http://astronomy.swin.edu.au/~pbourke/dataformats/bmp/ for a nice
+ * description of the BMP file format.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/BMPReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/BMPReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class BMPReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Offset to the image data. */
+  protected int offset;
+
+  /** Number of bits per pixel. */
+  protected int bpp;
+
+  /** The palette for indexed color images. */
+  protected byte[][] palette;
+
+  /**
+   * Compression type:
+   * 0 = no compression,
+   * 1 = 8 bit run length encoding,
+   * 2 = 4 bit run length encoding,
+   * 3 = RGB bitmap with mask.
+   */
+  protected int compression;
+
+  /** Offset to image data. */
+  private long global;
+
+  // -- Constructor --
+
+  /** Constructs a new BMP reader. */
+  public BMPReader() { super("Windows Bitmap", "bmp"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return new String(block).startsWith("BM");
+  }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    return palette;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    if (compression != 0) {
+      throw new FormatException("Compression type " + compression +
+        " not supported");
+    }
+
+    in.seek(global);
+
+    if ((palette != null && palette[0].length > 0) || core.sizeC[0] == 1) {
+      for (int y=core.sizeY[0]-1; y>=0; y--) {
+        in.read(buf, y*core.sizeX[0], core.sizeX[0]);
+      }
+    }
+    else {
+      for (int y=core.sizeY[0]-1; y>=0; y--) {
+        in.read(buf, y*core.sizeX[0]*3, core.sizeX[0]*3);
+      }
+      for (int i=0; i<buf.length/3; i++) {
+        byte tmp = buf[i*3 + 2];
+        buf[i*3 + 2] = buf[i*3];
+        buf[i*3] = tmp;
+      }
+    }
+    return buf;
+  }
+
+  // -- Internel FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("BMPReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    status("Reading bitmap header");
+
+    in.order(true);
+
+    // read the first header - 14 bytes
+
+    addMeta("Magic identifier", in.readString(2));
+
+    addMeta("File size (in bytes)", "" + in.readInt());
+    in.skipBytes(4); // reserved
+
+    // read the offset to the image data
+    offset = in.readInt();
+
+    // read the second header - 40 bytes
+
+    in.skipBytes(4);
+
+    // get the dimensions
+
+    core.sizeX[0] = in.readInt();
+    core.sizeY[0] = in.readInt();
+
+    if (core.sizeX[0] < 1 || core.sizeY[0] < 1) {
+      throw new FormatException("Invalid image dimensions: " +
+        core.sizeX[0] + " x " + core.sizeY[0]);
+    }
+    addMeta("Image width", "" + core.sizeX[0]);
+    addMeta("Image height", "" + core.sizeY[0]);
+
+    addMeta("Color planes", "" + in.readShort());
+    bpp = in.readShort();
+    addMeta("Bits per pixel", "" + bpp);
+
+    compression = in.readInt();
+    String comp = "invalid";
+
+    switch (compression) {
+      case 0:
+        comp = "None";
+        break;
+      case 1:
+        comp = "8 bit run length encoding";
+        break;
+      case 2:
+        comp = "4 bit run length encoding";
+        break;
+      case 3:
+        comp = "RGB bitmap with mask";
+        break;
+    }
+
+    addMeta("Compression type", comp);
+
+    in.skipBytes(4);
+    int pixelSizeX = in.readInt();
+    int pixelSizeY = in.readInt();
+    addMeta("X resolution", "" + pixelSizeX);
+    addMeta("Y resolution", "" + pixelSizeY);
+    int nColors = in.readInt();
+    in.skipBytes(4);
+
+    // read the palette, if it exists
+
+    if (offset != in.getFilePointer() && nColors > 0) {
+      palette = new byte[3][nColors];
+
+      for (int i=0; i<nColors; i++) {
+        for (int j=palette.length; j>0; j--) {
+          palette[j][i] = in.readByte();
+        }
+        in.skipBytes(1);
+      }
+    }
+
+    global = in.getFilePointer();
+    addMeta("Indexed color", palette == null ? "false" : "true");
+
+    status("Populating metadata");
+
+    core.sizeC[0] = (palette == null && bpp == 8) ? 1 : 3;
+    if (bpp > 8) bpp /= 3;
+    while (bpp % 8 != 0) bpp++;
+
+    switch (bpp) {
+      case 8:
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 16:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      case 32:
+        core.pixelType[0] = FormatTools.UINT32;
+        break;
+    }
+
+    if (core.sizeX[0] % 2 == 1) core.sizeX[0]++;
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.littleEndian[0] = true;
+    core.interleaved[0] = true;
+    core.imageCount[0] = 1;
+    core.sizeZ[0] = 1;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYCTZ";
+    core.metadataComplete[0] = true;
+    core.indexed[0] = palette != null;
+    core.falseColor[0] = false;
+
+    // Populate metadata store.
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+
+    // resolution is stored as pixels per meter; we want to convert to
+    // microns per pixel
+
+    float correctedX = (1 / (float) pixelSizeX) * 1000000;
+    float correctedY = (1 / (float) pixelSizeY) * 1000000;
+
+    store.setDimensions(new Float(correctedX), new Float(correctedY), null,
+      null, null, null);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+       null, null, null, null, null, null, null, null, null, null, null, null,
+       null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/BZip2Constants.java b/loci/formats/in/BZip2Constants.java
new file mode 100644
index 0000000..bf5a11c
--- /dev/null
+++ b/loci/formats/in/BZip2Constants.java
@@ -0,0 +1,122 @@
+//
+// BZip2Constants.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005-2007 Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/*
+ * Copyright  2001,2004 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron at aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package loci.formats.in;
+
+/**
+ * Base class for both the compress and decompress classes.
+ * Holds common arrays, and static data.
+ */
+public interface BZip2Constants {
+
+  int BASE_BLOCK_SIZE = 100000;
+  int MAX_ALPHA_SIZE = 258;
+  int MAX_CODE_LEN = 23;
+  int RUNA = 0;
+  int RUNB = 1;
+  int N_GROUPS = 6;
+  int G_SIZE = 50;
+  int N_ITERS = 4;
+  int MAX_SELECTORS = (2 + (900000 / G_SIZE));
+  int NUM_OVERSHOOT_BYTES = 20;
+
+  int[] R_NUMS = {
+    619, 720, 127, 481, 931, 816, 813, 233, 566, 247,
+    985, 724, 205, 454, 863, 491, 741, 242, 949, 214,
+    733, 859, 335, 708, 621, 574, 73, 654, 730, 472,
+    419, 436, 278, 496, 867, 210, 399, 680, 480, 51,
+    878, 465, 811, 169, 869, 675, 611, 697, 867, 561,
+    862, 687, 507, 283, 482, 129, 807, 591, 733, 623,
+    150, 238, 59, 379, 684, 877, 625, 169, 643, 105,
+    170, 607, 520, 932, 727, 476, 693, 425, 174, 647,
+    73, 122, 335, 530, 442, 853, 695, 249, 445, 515,
+    909, 545, 703, 919, 874, 474, 882, 500, 594, 612,
+    641, 801, 220, 162, 819, 984, 589, 513, 495, 799,
+    161, 604, 958, 533, 221, 400, 386, 867, 600, 782,
+    382, 596, 414, 171, 516, 375, 682, 485, 911, 276,
+    98, 553, 163, 354, 666, 933, 424, 341, 533, 870,
+    227, 730, 475, 186, 263, 647, 537, 686, 600, 224,
+    469, 68, 770, 919, 190, 373, 294, 822, 808, 206,
+    184, 943, 795, 384, 383, 461, 404, 758, 839, 887,
+    715, 67, 618, 276, 204, 918, 873, 777, 604, 560,
+    951, 160, 578, 722, 79, 804, 96, 409, 713, 940,
+    652, 934, 970, 447, 318, 353, 859, 672, 112, 785,
+    645, 863, 803, 350, 139, 93, 354, 99, 820, 908,
+    609, 772, 154, 274, 580, 184, 79, 626, 630, 742,
+    653, 282, 762, 623, 680, 81, 927, 626, 789, 125,
+    411, 521, 938, 300, 821, 78, 343, 175, 128, 250,
+    170, 774, 972, 275, 999, 639, 495, 78, 352, 126,
+    857, 956, 358, 619, 580, 124, 737, 594, 701, 612,
+    669, 112, 134, 694, 363, 992, 809, 743, 168, 974,
+    944, 375, 748, 52, 600, 747, 642, 182, 862, 81,
+    344, 805, 988, 739, 511, 655, 814, 334, 249, 515,
+    897, 955, 664, 981, 649, 113, 974, 459, 893, 228,
+    433, 837, 553, 268, 926, 240, 102, 654, 459, 51,
+    686, 754, 806, 760, 493, 403, 415, 394, 687, 700,
+    946, 670, 656, 610, 738, 392, 760, 799, 887, 653,
+    978, 321, 576, 617, 626, 502, 894, 679, 243, 440,
+    680, 879, 194, 572, 640, 724, 926, 56, 204, 700,
+    707, 151, 457, 449, 797, 195, 791, 558, 945, 679,
+    297, 59, 87, 824, 713, 663, 412, 693, 342, 606,
+    134, 108, 571, 364, 631, 212, 174, 643, 304, 329,
+    343, 97, 430, 751, 497, 314, 983, 374, 822, 928,
+    140, 206, 73, 263, 980, 736, 876, 478, 430, 305,
+    170, 514, 364, 692, 829, 82, 855, 953, 676, 246,
+    369, 970, 294, 750, 807, 827, 150, 790, 288, 923,
+    804, 378, 215, 828, 592, 281, 565, 555, 710, 82,
+    896, 831, 547, 261, 524, 462, 293, 465, 502, 56,
+    661, 821, 976, 991, 658, 869, 905, 758, 745, 193,
+    768, 550, 608, 933, 378, 286, 215, 979, 792, 961,
+    61, 688, 793, 644, 986, 403, 106, 366, 905, 644,
+    372, 567, 466, 434, 645, 210, 389, 550, 919, 135,
+    780, 773, 635, 389, 707, 100, 626, 958, 165, 504,
+    920, 176, 193, 713, 857, 265, 203, 50, 668, 108,
+    645, 990, 626, 197, 510, 357, 358, 850, 858, 364,
+    936, 638
+  };
+
+}
diff --git a/loci/formats/in/BaseTiffReader.java b/loci/formats/in/BaseTiffReader.java
new file mode 100644
index 0000000..da3a093
--- /dev/null
+++ b/loci/formats/in/BaseTiffReader.java
@@ -0,0 +1,845 @@
+//
+// BaseTiffReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.text.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * BaseTiffReader is the superclass for file format readers compatible with
+ * or derived from the TIFF 6.0 file format.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/BaseTiffReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/BaseTiffReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public abstract class BaseTiffReader extends FormatReader {
+
+  // -- Fields --
+
+  /** List of IFDs for the current TIFF. */
+  protected Hashtable[] ifds;
+
+  // -- Constructors --
+
+  /** Constructs a new BaseTiffReader. */
+  public BaseTiffReader(String name, String suffix) { super(name, suffix); }
+
+  /** Constructs a new BaseTiffReader. */
+  public BaseTiffReader(String name, String[] suffixes) {
+    super(name, suffixes);
+  }
+
+  // -- BaseTiffReader API methods --
+
+  /** Gets the dimensions of the given (possibly multi-page) TIFF file. */
+  public int[] getTiffDimensions() throws FormatException, IOException {
+    if (ifds == null || ifds.length == 0) return null;
+    return new int[] {
+      TiffTools.getIFDIntValue(ifds[0], TiffTools.IMAGE_WIDTH, false, -1),
+      TiffTools.getIFDIntValue(ifds[0], TiffTools.IMAGE_LENGTH, false, -1),
+      core.imageCount[0]
+    };
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return TiffTools.isValidHeader(block);
+  }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    int[] bits = TiffTools.getBitsPerSample(ifds[0]);
+    if (bits[0] <= 8) {
+      int[] colorMap =
+        (int[]) TiffTools.getIFDValue(ifds[0], TiffTools.COLOR_MAP);
+      if (colorMap == null) return null;
+
+      byte[][] table = new byte[3][colorMap.length / 3];
+      int next = 0;
+      for (int j=0; j<table.length; j++) {
+        for (int i=0; i<table[0].length; i++) {
+          if (isLittleEndian()) {
+            int n = colorMap[next++];
+            if ((n & 0xffff) > 255) table[j][i] = (byte) ((n & 0xff00) >> 8);
+            else table[j][i] = (byte) (n & 0xff);
+          }
+          else table[j][i] = (byte) ((colorMap[next++] & 0xff00) >> 8);
+        }
+      }
+
+      return table;
+    }
+    return null;
+  }
+
+  /* @see loci.formats.IFormatReader#get16BitLookupTable() */
+  public short[][] get16BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    int[] bits = TiffTools.getBitsPerSample(ifds[0]);
+    if (bits[0] <= 16 && bits[0] > 8) {
+      int[] colorMap =
+        (int[]) TiffTools.getIFDValue(ifds[0], TiffTools.COLOR_MAP);
+      if (colorMap == null) return null;
+      short[][] table = new short[3][colorMap.length / 3];
+      int next = 0;
+      for (int i=0; i<table.length; i++) {
+        for (int j=0; j<table[0].length; j++) {
+          if (core.littleEndian[0]) {
+            table[i][j] = (short) (colorMap[next++] & 0xffff);
+          }
+          else {
+            int n = colorMap[next++];
+            table[i][j] =
+              (short) (((n & 0xff0000) >> 8) | ((n & 0xff000000) >> 24));
+          }
+        }
+      }
+      return table;
+    }
+    return null;
+  }
+
+  /* @see loci.formats.IFormatReader#getMetadataValue(String) */
+  public Object getMetadataValue(String field) {
+    FormatTools.assertId(currentId, true, 1);
+    return getMeta(field);
+  }
+
+  /* @see loci.formats.FormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    TiffTools.getSamples(ifds[no], in, buf);
+    return swapIfRequired(buf);
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /** Populates the metadata hashtable and metadata store. */
+  protected void initMetadata() throws FormatException, IOException {
+    initStandardMetadata();
+    initMetadataStore();
+  }
+
+  /**
+   * Parses standard metadata.
+   *
+   * NOTE: Absolutely <b>no</b> calls to the metadata store should be made in
+   * this method or methods that override this method. Data <b>will</b> be
+   * overwritten if you do so.
+   */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    Hashtable ifd = ifds[0];
+    put("ImageWidth", ifd, TiffTools.IMAGE_WIDTH);
+    put("ImageLength", ifd, TiffTools.IMAGE_LENGTH);
+    put("BitsPerSample", ifd, TiffTools.BITS_PER_SAMPLE);
+
+    int comp = TiffTools.getIFDIntValue(ifd, TiffTools.COMPRESSION);
+    String compression = null;
+    switch (comp) {
+      case TiffTools.UNCOMPRESSED:
+        compression = "None";
+        break;
+      case TiffTools.CCITT_1D:
+        compression = "CCITT Group 3 1-Dimensional Modified Huffman";
+        break;
+      case TiffTools.GROUP_3_FAX:
+        compression = "CCITT T.4 bilevel encoding";
+        break;
+      case TiffTools.GROUP_4_FAX:
+        compression = "CCITT T.6 bilevel encoding";
+        break;
+      case TiffTools.LZW:
+        compression = "LZW";
+        break;
+      case TiffTools.JPEG:
+        compression = "JPEG";
+        break;
+      case TiffTools.PACK_BITS:
+        compression = "PackBits";
+        break;
+    }
+    put("Compression", compression);
+
+    int photo = TiffTools.getIFDIntValue(ifd,
+      TiffTools.PHOTOMETRIC_INTERPRETATION);
+    String photoInterp = null;
+    String metaDataPhotoInterp = null;
+
+    switch (photo) {
+      case TiffTools.WHITE_IS_ZERO:
+        photoInterp = "WhiteIsZero";
+        metaDataPhotoInterp = "Monochrome";
+        break;
+      case TiffTools.BLACK_IS_ZERO:
+        photoInterp = "BlackIsZero";
+        metaDataPhotoInterp = "Monochrome";
+        break;
+      case TiffTools.RGB:
+        photoInterp = "RGB";
+        metaDataPhotoInterp = "RGB";
+        break;
+      case TiffTools.RGB_PALETTE:
+        photoInterp = "Palette";
+        metaDataPhotoInterp = "Monochrome";
+        break;
+      case TiffTools.TRANSPARENCY_MASK:
+        photoInterp = "Transparency Mask";
+        metaDataPhotoInterp = "RGB";
+        break;
+      case TiffTools.CMYK:
+        photoInterp = "CMYK";
+        metaDataPhotoInterp = "CMYK";
+        break;
+      case TiffTools.Y_CB_CR:
+        photoInterp = "YCbCr";
+        metaDataPhotoInterp = "RGB";
+        break;
+      case TiffTools.CIE_LAB:
+        photoInterp = "CIELAB";
+        metaDataPhotoInterp = "RGB";
+        break;
+      case TiffTools.CFA_ARRAY:
+        photoInterp = "Color Filter Array";
+        metaDataPhotoInterp = "RGB";
+        break;
+    }
+    put("PhotometricInterpretation", photoInterp);
+    put("MetaDataPhotometricInterpretation", metaDataPhotoInterp);
+
+    putInt("CellWidth", ifd, TiffTools.CELL_WIDTH);
+    putInt("CellLength", ifd, TiffTools.CELL_LENGTH);
+
+    int or = TiffTools.getIFDIntValue(ifd, TiffTools.ORIENTATION);
+
+    // adjust the width and height if necessary
+    if (or == 8) {
+      put("ImageWidth", ifd, TiffTools.IMAGE_LENGTH);
+      put("ImageLength", ifd, TiffTools.IMAGE_WIDTH);
+    }
+
+    String orientation = null;
+    // there is no case 0
+    switch (or) {
+      case 1:
+        orientation = "1st row -> top; 1st column -> left";
+        break;
+      case 2:
+        orientation = "1st row -> top; 1st column -> right";
+        break;
+      case 3:
+        orientation = "1st row -> bottom; 1st column -> right";
+        break;
+      case 4:
+        orientation = "1st row -> bottom; 1st column -> left";
+        break;
+      case 5:
+        orientation = "1st row -> left; 1st column -> top";
+        break;
+      case 6:
+        orientation = "1st row -> right; 1st column -> top";
+        break;
+      case 7:
+        orientation = "1st row -> right; 1st column -> bottom";
+        break;
+      case 8:
+        orientation = "1st row -> left; 1st column -> bottom";
+        break;
+    }
+    put("Orientation", orientation);
+    putInt("SamplesPerPixel", ifd, TiffTools.SAMPLES_PER_PIXEL);
+
+    put("Software", ifd, TiffTools.SOFTWARE);
+    put("Instrument Make", ifd, TiffTools.MAKE);
+    put("Instrument Model", ifd, TiffTools.MODEL);
+    put("Document Name", ifd, TiffTools.DOCUMENT_NAME);
+    put("DateTime", ifd, TiffTools.DATE_TIME);
+    put("Artist", ifd, TiffTools.ARTIST);
+
+    put("HostComputer", ifd, TiffTools.HOST_COMPUTER);
+    put("Copyright", ifd, TiffTools.COPYRIGHT);
+
+    put("NewSubfileType", ifd, TiffTools.NEW_SUBFILE_TYPE);
+
+    int thresh = TiffTools.getIFDIntValue(ifd, TiffTools.THRESHHOLDING);
+    String threshholding = null;
+    switch (thresh) {
+      case 1:
+        threshholding = "No dithering or halftoning";
+        break;
+      case 2:
+        threshholding = "Ordered dithering or halftoning";
+        break;
+      case 3:
+        threshholding = "Randomized error diffusion";
+        break;
+    }
+    put("Threshholding", threshholding);
+
+    int fill = TiffTools.getIFDIntValue(ifd, TiffTools.FILL_ORDER);
+    String fillOrder = null;
+    switch (fill) {
+      case 1:
+        fillOrder = "Pixels with lower column values are stored " +
+          "in the higher order bits of a byte";
+        break;
+      case 2:
+        fillOrder = "Pixels with lower column values are stored " +
+          "in the lower order bits of a byte";
+        break;
+    }
+    put("FillOrder", fillOrder);
+
+    putInt("Make", ifd, TiffTools.MAKE);
+    putInt("Model", ifd, TiffTools.MODEL);
+    putInt("MinSampleValue", ifd, TiffTools.MIN_SAMPLE_VALUE);
+    putInt("MaxSampleValue", ifd, TiffTools.MAX_SAMPLE_VALUE);
+    putInt("XResolution", ifd, TiffTools.X_RESOLUTION);
+    putInt("YResolution", ifd, TiffTools.Y_RESOLUTION);
+
+    int planar = TiffTools.getIFDIntValue(ifd,
+      TiffTools.PLANAR_CONFIGURATION);
+    String planarConfig = null;
+    switch (planar) {
+      case 1:
+        planarConfig = "Chunky";
+        break;
+      case 2:
+        planarConfig = "Planar";
+        break;
+    }
+    put("PlanarConfiguration", planarConfig);
+
+    putInt("XPosition", ifd, TiffTools.X_POSITION);
+    putInt("YPosition", ifd, TiffTools.Y_POSITION);
+    putInt("FreeOffsets", ifd, TiffTools.FREE_OFFSETS);
+    putInt("FreeByteCounts", ifd, TiffTools.FREE_BYTE_COUNTS);
+    putInt("GrayResponseUnit", ifd, TiffTools.GRAY_RESPONSE_UNIT);
+    putInt("GrayResponseCurve", ifd, TiffTools.GRAY_RESPONSE_CURVE);
+    putInt("T4Options", ifd, TiffTools.T4_OPTIONS);
+    putInt("T6Options", ifd, TiffTools.T6_OPTIONS);
+
+    int res = TiffTools.getIFDIntValue(ifd, TiffTools.RESOLUTION_UNIT);
+    String resUnit = null;
+    switch (res) {
+      case 1:
+        resUnit = "None";
+        break;
+      case 2:
+        resUnit = "Inch";
+        break;
+      case 3:
+        resUnit = "Centimeter";
+        break;
+    }
+    put("ResolutionUnit", resUnit);
+
+    putInt("PageNumber", ifd, TiffTools.PAGE_NUMBER);
+    putInt("TransferFunction", ifd, TiffTools.TRANSFER_FUNCTION);
+
+    int predict = TiffTools.getIFDIntValue(ifd, TiffTools.PREDICTOR);
+    String predictor = null;
+    switch (predict) {
+      case 1:
+        predictor = "No prediction scheme";
+        break;
+      case 2:
+        predictor = "Horizontal differencing";
+        break;
+    }
+    put("Predictor", predictor);
+
+    putInt("WhitePoint", ifd, TiffTools.WHITE_POINT);
+    putInt("PrimaryChromacities", ifd, TiffTools.PRIMARY_CHROMATICITIES);
+
+    putInt("HalftoneHints", ifd, TiffTools.HALFTONE_HINTS);
+    putInt("TileWidth", ifd, TiffTools.TILE_WIDTH);
+    putInt("TileLength", ifd, TiffTools.TILE_LENGTH);
+    putInt("TileOffsets", ifd, TiffTools.TILE_OFFSETS);
+    putInt("TileByteCounts", ifd, TiffTools.TILE_BYTE_COUNTS);
+
+    int ink = TiffTools.getIFDIntValue(ifd, TiffTools.INK_SET);
+    String inkSet = null;
+    switch (ink) {
+      case 1:
+        inkSet = "CMYK";
+        break;
+      case 2:
+        inkSet = "Other";
+        break;
+    }
+    put("InkSet", inkSet);
+
+    putInt("InkNames", ifd, TiffTools.INK_NAMES);
+    putInt("NumberOfInks", ifd, TiffTools.NUMBER_OF_INKS);
+    putInt("DotRange", ifd, TiffTools.DOT_RANGE);
+    put("TargetPrinter", ifd, TiffTools.TARGET_PRINTER);
+    putInt("ExtraSamples", ifd, TiffTools.EXTRA_SAMPLES);
+
+    int fmt = TiffTools.getIFDIntValue(ifd, TiffTools.SAMPLE_FORMAT);
+    String sampleFormat = null;
+    switch (fmt) {
+      case 1:
+        sampleFormat = "unsigned integer";
+        break;
+      case 2:
+        sampleFormat = "two's complement signed integer";
+        break;
+      case 3:
+        sampleFormat = "IEEE floating point";
+        break;
+      case 4:
+        sampleFormat = "undefined";
+        break;
+    }
+    put("SampleFormat", sampleFormat);
+
+    putInt("SMinSampleValue", ifd, TiffTools.S_MIN_SAMPLE_VALUE);
+    putInt("SMaxSampleValue", ifd, TiffTools.S_MAX_SAMPLE_VALUE);
+    putInt("TransferRange", ifd, TiffTools.TRANSFER_RANGE);
+
+    int jpeg = TiffTools.getIFDIntValue(ifd, TiffTools.JPEG_PROC);
+    String jpegProc = null;
+    switch (jpeg) {
+      case 1:
+        jpegProc = "baseline sequential process";
+        break;
+      case 14:
+        jpegProc = "lossless process with Huffman coding";
+        break;
+    }
+    put("JPEGProc", jpegProc);
+
+    putInt("JPEGInterchangeFormat", ifd, TiffTools.JPEG_INTERCHANGE_FORMAT);
+    putInt("JPEGRestartInterval", ifd, TiffTools.JPEG_RESTART_INTERVAL);
+
+    putInt("JPEGLosslessPredictors",
+      ifd, TiffTools.JPEG_LOSSLESS_PREDICTORS);
+    putInt("JPEGPointTransforms", ifd, TiffTools.JPEG_POINT_TRANSFORMS);
+    putInt("JPEGQTables", ifd, TiffTools.JPEG_Q_TABLES);
+    putInt("JPEGDCTables", ifd, TiffTools.JPEG_DC_TABLES);
+    putInt("JPEGACTables", ifd, TiffTools.JPEG_AC_TABLES);
+    putInt("YCbCrCoefficients", ifd, TiffTools.Y_CB_CR_COEFFICIENTS);
+
+    int ycbcr = TiffTools.getIFDIntValue(ifd,
+      TiffTools.Y_CB_CR_SUB_SAMPLING);
+    String subSampling = null;
+    switch (ycbcr) {
+      case 1:
+        subSampling = "chroma image dimensions = luma image dimensions";
+        break;
+      case 2:
+        subSampling = "chroma image dimensions are " +
+          "half the luma image dimensions";
+        break;
+      case 4:
+        subSampling = "chroma image dimensions are " +
+          "1/4 the luma image dimensions";
+        break;
+    }
+    put("YCbCrSubSampling", subSampling);
+
+    putInt("YCbCrPositioning", ifd, TiffTools.Y_CB_CR_POSITIONING);
+    putInt("ReferenceBlackWhite", ifd, TiffTools.REFERENCE_BLACK_WHITE);
+
+    // bits per sample and number of channels
+    Object bpsObj = TiffTools.getIFDValue(ifd, TiffTools.BITS_PER_SAMPLE);
+    int bps = -1, numC = 3;
+    if (bpsObj instanceof int[]) {
+      int[] q = (int[]) bpsObj;
+      bps = q[0];
+      numC = q.length;
+    }
+    else if (bpsObj instanceof Number) {
+      bps = ((Number) bpsObj).intValue();
+      numC = 1;
+    }
+
+    // numC isn't set properly if we have an indexed color image, so we need
+    // to reset it here
+
+    int p = TiffTools.getIFDIntValue(ifd, TiffTools.PHOTOMETRIC_INTERPRETATION);
+    if (p == TiffTools.RGB_PALETTE || p == TiffTools.CFA_ARRAY) {
+      numC = 3;
+      bps *= 3;
+    }
+
+    put("BitsPerSample", bps);
+    put("NumberOfChannels", numC);
+
+    // TIFF comment
+    String comment = null;
+    Object o = TiffTools.getIFDValue(ifd, TiffTools.IMAGE_DESCRIPTION);
+    if (o instanceof String) comment = (String) o;
+    else if (o instanceof String[]) {
+      String[] s = (String[]) o;
+      if (s.length > 0) comment = s[0];
+    }
+    else if (o != null) comment = o.toString();
+    if (comment != null) {
+      // sanitize comment
+      comment = comment.replaceAll("\r\n", "\n"); // CR-LF to LF
+      comment = comment.replaceAll("\r", "\n"); // CR to LF
+      put("Comment", comment);
+    }
+
+    int samples = TiffTools.getIFDIntValue(ifds[0],
+      TiffTools.SAMPLES_PER_PIXEL, false, 1);
+    core.rgb[0] = samples > 1 || p == TiffTools.RGB_PALETTE ||
+      p == TiffTools.CFA_ARRAY || p == TiffTools.RGB;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]);
+
+    core.sizeX[0] =
+      TiffTools.getIFDIntValue(ifds[0], TiffTools.IMAGE_WIDTH, false, 0);
+    core.sizeY[0] =
+      TiffTools.getIFDIntValue(ifds[0], TiffTools.IMAGE_LENGTH, false, 0);
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = core.rgb[0] ? samples : 1;
+    core.sizeT[0] = ifds.length;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = TiffTools.getIFDIntValue(ifds[0],
+      TiffTools.PHOTOMETRIC_INTERPRETATION) == TiffTools.RGB_PALETTE;
+    core.falseColor[0] = false;
+
+    int bitFormat = TiffTools.getIFDIntValue(ifds[0],
+      TiffTools.SAMPLE_FORMAT);
+
+    while (bps % 8 != 0) bps++;
+    if (bps == 24 || bps == 48) bps /= 3;
+
+    if (bitFormat == 3) core.pixelType[0] = FormatTools.FLOAT;
+    else if (bitFormat == 2) {
+      switch (bps) {
+        case 16:
+          core.pixelType[0] = FormatTools.INT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.INT32;
+          break;
+        default:
+          core.pixelType[0] = FormatTools.UINT8;
+      }
+    }
+    else {
+      switch (bps) {
+        case 16:
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.UINT32;
+          break;
+        default:
+          core.pixelType[0] = FormatTools.UINT8;
+      }
+    }
+
+    core.currentOrder[0] = "XYCZT";
+  }
+
+  /**
+   * Populates the metadata store using the data parsed in
+   * {@link #initStandardMetadata()} along with some further parsing done in
+   * the method itself.
+   *
+   * All calls to the active <code>MetadataStore</code> should be made in this
+   * method and <b>only</b> in this method. This is especially important for
+   * sub-classes that override the getters for pixel set array size, etc.
+   */
+  protected void initMetadataStore() {
+    Hashtable ifd = ifds[0];
+    try {
+      // the metadata store we're working with
+      MetadataStore store = getMetadataStore();
+
+      // set the pixel values in the metadata store
+      FormatTools.populatePixels(store, this);
+
+      // populate Experimenter element
+      String artist = (String) TiffTools.getIFDValue(ifd, TiffTools.ARTIST);
+      if (artist != null) {
+        String firstName = null, lastName = null;
+        int ndx = artist.indexOf(" ");
+        if (ndx < 0) lastName = artist;
+        else {
+          firstName = artist.substring(0, ndx);
+          lastName = artist.substring(ndx + 1);
+        }
+        String email = (String)
+          TiffTools.getIFDValue(ifd, TiffTools.HOST_COMPUTER);
+        store.setExperimenter(firstName, lastName, email,
+          null, null, null, null);
+      }
+
+      // format the creation date to ISO 8061
+
+      String creationDate = getImageCreationDate();
+      try {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+        SimpleDateFormat parse = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
+        Date date = parse.parse(creationDate, new ParsePosition(0));
+        creationDate = sdf.format(date);
+      }
+      catch (NullPointerException e) {
+        try {
+          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+          SimpleDateFormat parse =
+            new SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SS");
+          Date date = parse.parse(creationDate, new ParsePosition(0));
+          creationDate = sdf.format(date);
+        }
+        catch (NullPointerException exc) {
+          if (debug) trace(exc);
+          creationDate = null;
+        }
+      }
+
+      // populate Image element
+
+      store.setImage(getImageName(),
+        creationDate, getImageDescription(), null);
+
+      // populate Logical Channel elements
+      for (int i=0; i<getSizeC(); i++) {
+        try {
+          setLogicalChannel(i);
+        }
+        catch (FormatException exc) {
+          if (debug) trace(exc);
+        }
+        catch (IOException exc) {
+          if (debug) trace(exc);
+        }
+      }
+
+      // set the X and Y pixel dimensions
+
+      int resolutionUnit = TiffTools.getIFDIntValue(ifd,
+        TiffTools.RESOLUTION_UNIT);
+      TiffRational xResolution = TiffTools.getIFDRationalValue(ifd,
+        TiffTools.X_RESOLUTION, false);
+      TiffRational yResolution = TiffTools.getIFDRationalValue(ifd,
+        TiffTools.Y_RESOLUTION, false);
+      float pixX = xResolution == null ? 0f : xResolution.floatValue();
+      float pixY = yResolution == null ? 0f : yResolution.floatValue();
+
+      switch (resolutionUnit) {
+        case 2:
+          // resolution is expressed in pixels per inch
+          pixX *= 0.0254;
+          pixY *= 0.0254;
+          break;
+        case 3:
+          // resolution is expressed in pixels per centimeter
+          pixX /= 100;
+          pixY /= 100;
+          break;
+      }
+
+      store.setDimensions(new Float(pixX), new Float(pixY), null,
+        null, null, null);
+
+      // populate StageLabel element
+      Object x = TiffTools.getIFDValue(ifd, TiffTools.X_POSITION);
+      Object y = TiffTools.getIFDValue(ifd, TiffTools.Y_POSITION);
+      Float stageX;
+      Float stageY;
+      if (x instanceof TiffRational) {
+        stageX = x == null ? null : new Float(((TiffRational) x).floatValue());
+        stageY = y == null ? null : new Float(((TiffRational) y).floatValue());
+      }
+      else {
+        stageX = x == null ? null : new Float((String) x);
+        stageY = y == null ? null : new Float((String) y);
+      }
+      if (stageX != null || stageY != null) {
+        store.setStageLabel(null, stageX, stageY, null, null);
+      }
+
+      // populate Instrument element
+      String model = (String) TiffTools.getIFDValue(ifd, TiffTools.MODEL);
+      String serialNumber = (String)
+        TiffTools.getIFDValue(ifd, TiffTools.MAKE);
+      store.setInstrument(null, model, serialNumber, null, null);
+    }
+    catch (FormatException exc) { trace(exc); }
+  }
+
+  /**
+   * Retrieves the image name from the TIFF.
+   * @return the image name.
+   */
+  protected String getImageName() { return currentId; }
+
+  /**
+   * Retrieves the image creation date.
+   * @return the image creation date.
+   */
+  protected String getImageCreationDate() {
+    Object o = TiffTools.getIFDValue(ifds[0], TiffTools.DATE_TIME);
+    if (o instanceof String) return (String) o;
+    if (o instanceof String[]) return ((String[]) o)[0];
+    return null;
+  }
+
+  /**
+   * Retrieves the image description.
+   * @return the image description.
+   */
+  protected String getImageDescription() {
+    return (String) getMeta("Comment");
+  }
+
+  /**
+   * Examines a byte array to see if it needs to be byte swapped and modifies
+   * the byte array directly.
+   * @param byteArray The byte array to check and modify if required.
+   * @return the <i>byteArray</i> either swapped or not for convenience.
+   * @throws IOException if there is an error read from the file.
+   * @throws FormatException if there is an error during metadata parsing.
+   */
+  protected byte[] swapIfRequired(byte[] byteArray)
+    throws FormatException, IOException
+  {
+    int bitsPerSample = TiffTools.getBitsPerSample(ifds[0])[0];
+
+    // We've got nothing to do if the samples are only 8-bits wide or if they
+    // are floating point.
+    if (bitsPerSample == 8 || bitsPerSample == 32) return byteArray;
+
+    if (isLittleEndian()) {
+      if (bitsPerSample == 16) { // short
+        ShortBuffer buf = ByteBuffer.wrap(byteArray).asShortBuffer();
+        for (int i = 0; i < (byteArray.length / 2); i++) {
+          buf.put(i, DataTools.swap(buf.get(i)));
+        }
+      }
+      else {
+        throw new FormatException(
+          "Unsupported sample bit width: '" + bitsPerSample + "'");
+      }
+    }
+    // We've got a big-endian file with a big-endian byte array.
+    return byteArray;
+  }
+
+  // -- Internal FormatReader API methods - metadata convenience --
+
+  protected void put(String key, Object value) {
+    if (value == null) return;
+    if (value instanceof String) value = ((String) value).trim();
+    addMeta(key, value);
+  }
+
+  protected void put(String key, int value) {
+    if (value == -1) return; // indicates missing value
+    addMeta(key, new Integer(value));
+  }
+
+  protected void put(String key, boolean value) {
+    put(key, new Boolean(value));
+  }
+  protected void put(String key, byte value) { put(key, new Byte(value)); }
+  protected void put(String key, char value) { put(key, new Character(value)); }
+  protected void put(String key, double value) { put(key, new Double(value)); }
+  protected void put(String key, float value) { put(key, new Float(value)); }
+  protected void put(String key, long value) { put(key, new Long(value)); }
+  protected void put(String key, short value) { put(key, new Short(value)); }
+
+  protected void put(String key, Hashtable ifd, int tag) {
+    put(key, TiffTools.getIFDValue(ifd, tag));
+  }
+
+  protected void putInt(String key, Hashtable ifd, int tag) {
+    put(key, TiffTools.getIFDIntValue(ifd, tag));
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("BaseTiffReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(in.readShort() == 0x4949);
+
+    status("Reading IFDs");
+
+    ifds = TiffTools.getIFDs(in);
+    if (ifds == null) throw new FormatException("No IFDs found");
+
+    status("Populating metadata");
+
+    core.imageCount[0] = ifds.length;
+    initMetadata();
+  }
+
+  // -- Helper methods --
+
+  /**
+   * Sets the logical channel in the metadata store.
+   * @param i the logical channel number.
+   * @throws FormatException if there is an error parsing metadata.
+   * @throws IOException if there is an error reading the file.
+   */
+  private void setLogicalChannel(int i) throws FormatException, IOException {
+    getMetadataStore().setLogicalChannel(i, getChannelName(i), null, null, null,
+      null, null, null, null, null, null, null, null,
+      getPhotometricInterpretation(i), getMode(i), null, null, null, null, null,
+      getEmWave(i), getExWave(i), null, getNdFilter(i), null);
+  }
+
+  private String getChannelName(int i) { return null; }
+
+  private Float getNdFilter(int i) { return null; }
+
+  private Integer getEmWave(int i) { return null; }
+
+  private Integer getExWave(int i) { return null; }
+
+  private String getPhotometricInterpretation(int i)
+    throws FormatException, IOException
+  {
+    return (String) getMetadataValue("metaDataPhotometricInterpretation");
+  }
+
+  private String getMode(int i) { return null; }
+
+}
diff --git a/loci/formats/in/BioRadReader.java b/loci/formats/in/BioRadReader.java
new file mode 100644
index 0000000..96b1f8a
--- /dev/null
+++ b/loci/formats/in/BioRadReader.java
@@ -0,0 +1,782 @@
+//
+// BioRadReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import loci.formats.*;
+
+/**
+ * BioRadReader is the file format reader for Bio-Rad PIC files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/BioRadReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/BioRadReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+
+public class BioRadReader extends FormatReader {
+
+  // -- Constants --
+
+  /** Numerical ID of a valid Bio-Rad PIC file. */
+  private static final int PIC_FILE_ID = 12345;
+
+  /** Always little endian. */
+  private static final boolean LITTLE_ENDIAN = true;
+
+  /** List of merge types. */
+  private static final String[] MERGE_NAMES = {
+    "MERGE_OFF", "MERGE_16", "MERGE_ALTERNATE", "MERGE_COLUMN", "MERGE_ROW",
+    "MERGE_MAXIMUM", "MERGE_OPT12", "MERGE_OPT12_V2"
+  };
+
+  // Note types
+
+  /** List of note types. */
+  public static final String[] NOTE_NAMES = {
+    "0", "LIVE", "FILE1", "NUMBER", "USER", "LINE", "COLLECT", "FILE2",
+    "SCALEBAR", "MERGE", "THRUVIEW", "ARROW", "12", "13", "14", "15",
+    "16", "17", "18", "19", "VARIABLE", "STRUCTURE", "4D SERIES"
+  };
+
+  // -- Fields --
+
+  /** Flag indicating current Bio-Rad PIC is packed with bytes. */
+  private boolean byteFormat;
+
+  private Vector used;
+
+  // -- Constructor --
+
+  /** Constructs a new BioRadReader. */
+  public BioRadReader() { super("Bio-Rad PIC", "pic"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 56) return false;
+    return DataTools.bytesToShort(block, 54, 2, LITTLE_ENDIAN) == PIC_FILE_ID;
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    return (String[]) used.toArray(new String[0]);
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    long offset = no * core.sizeX[0] * core.sizeY[0] * (byteFormat ? 1 : 2);
+    in.seek(offset + 76);
+    in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("BioRadReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(true);
+
+    used = new Vector();
+    used.add(currentId);
+
+    status("Reading image dimensions");
+
+    // read header
+
+    core.sizeX[0] = in.readShort();
+    core.sizeY[0] = in.readShort();
+    core.imageCount[0] = in.readShort();
+
+    int ramp1min = in.readShort();
+    int ramp1max = in.readShort();
+    boolean notes = in.readInt() != 0;
+    byteFormat = in.readShort() != 0;
+    int imageNumber = in.readShort();
+    String name = in.readString(32);
+    int merged = in.readShort();
+    int color1 = in.readShort();
+    int fileId = in.readShort();
+    int ramp2min = in.readShort();
+    int ramp2max = in.readShort();
+    int color2 = in.readShort();
+    int edited = in.readShort();
+    int lens = in.readShort();
+    float magFactor = in.readFloat();
+
+    // check validity of header
+    if (fileId != PIC_FILE_ID) {
+      throw new FormatException("Invalid file header : " + fileId);
+    }
+
+    // populate metadata fields
+    addMeta("nx", new Integer(core.sizeX[0]));
+    addMeta("ny", new Integer(core.sizeY[0]));
+    addMeta("npic", new Integer(core.imageCount[0]));
+    addMeta("ramp1_min", new Integer(ramp1min));
+    addMeta("ramp1_max", new Integer(ramp1max));
+    addMeta("notes", new Boolean(notes));
+    addMeta("byte_format", new Boolean(byteFormat));
+    addMeta("image_number", new Integer(imageNumber));
+    addMeta("name", name);
+    addMeta("merged", MERGE_NAMES[merged]);
+    addMeta("color1", new Integer(color1));
+    addMeta("file_id", new Integer(fileId));
+    addMeta("ramp2_min", new Integer(ramp2min));
+    addMeta("ramp2_max", new Integer(ramp2max));
+    addMeta("color2", new Integer(color2));
+    addMeta("edited", new Integer(edited));
+    addMeta("lens", new Integer(lens));
+    addMeta("mag_factor", new Float(magFactor));
+
+    // skip image data
+    int imageLen = core.sizeX[0] * core.sizeY[0];
+    int bpp = byteFormat ? 1 : 2;
+    in.skipBytes(bpp * core.imageCount[0] * imageLen + 6);
+
+    Vector pixelSize = new Vector();
+
+    core.sizeZ[0] = core.imageCount[0];
+    core.sizeC[0] = 1;
+    core.sizeT[0] = 1;
+
+    core.orderCertain[0] = false;
+    core.rgb[0] = false;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = LITTLE_ENDIAN;
+    core.metadataComplete[0] = true;
+
+    status("Reading notes");
+
+    String zoom = null, zstart = null, zstop = null, mag = null;
+    String gain1 = null, gain2 = null, gain3 = null;
+    String offset1 = null;
+    String ex1 = null, ex2 = null, ex3 = null;
+    String em1 = null, em2 = null, em3 = null;
+
+    // read notes
+    int noteCount = 0;
+    while (notes) {
+      // read in note
+
+      int level = in.readShort();
+      notes = in.readInt() != 0;
+      int num = in.readShort();
+      int status = in.readShort();
+      int type = in.readShort();
+      int x = in.readShort();
+      int y = in.readShort();
+      String text = in.readString(80);
+
+      // be sure to remove binary data from the note text
+      int ndx = text.length();
+      for (int i=0; i<text.length(); i++) {
+        if (text.charAt(i) == 0) {
+          ndx = i;
+          i = text.length();
+        }
+      }
+
+      text = text.substring(0, ndx).trim();
+
+      // add note to list
+      noteCount++;
+
+      switch (type) {
+        case 8: // NOTE_TYPE_SCALEBAR
+          int eq = text.indexOf("=");
+          if (eq != -1) {
+            text = text.substring(eq + 1).trim();
+            String len = text.substring(0, text.indexOf(" ")).trim();
+            String angle = text.substring(text.indexOf(" ")).trim();
+            addMeta("Scalebar length (in microns)", len);
+            addMeta("Scalebar angle (in degrees)", angle);
+          }
+          break;
+        case 11: // NOTE_TYPE_ARROW
+          eq = text.indexOf("=");
+          if (eq != -1) {
+            text = text.substring(eq + 1).trim();
+            StringTokenizer st = new StringTokenizer(text, " ");
+            addMeta("Arrow width", st.nextToken());
+            addMeta("Arrow height", st.nextToken());
+            addMeta("Arrow angle", st.nextToken());
+            addMeta("Arrow fill type", st.nextToken());
+          }
+          break;
+        case 20: // NOTE_TYPE_VARIABLE
+          eq = text.indexOf("=");
+          if (eq != -1) {
+            String key = text.substring(0, eq);
+            String value = text.substring(eq + 1);
+            addMeta(key, value);
+          }
+          break;
+        case 21: // NOTE_TYPE_STRUCTURE
+          StringTokenizer st = new StringTokenizer(text, " ");
+
+          String[] keys = new String[0];
+          int idx = 0;
+
+          switch (y) {
+            case 1:
+              keys = new String[] {"Scan Channel", "Both Mode", "Speed",
+                "Filter", "Factor", "Number of scans",
+                "Photon counting mode (channel 1)",
+                "Photon counting detector (channel 1)",
+                "Photon counting mode (channel 2)",
+                "Photon counting detector (channel 2)", "Photon mode",
+                "Objective lens magnification", "Zoom factor (user selected)",
+                "Motor on", "Z step size"};
+              break;
+            case 2:
+              keys = new String[] {"Z start", "Z stop", "Scan area - cx",
+                "Scan area - cy", "Scan area - lx", "Scan area - ly"};
+              break;
+            case 3:
+              keys = new String[] {"PMT 1 Iris", "PMT 1 Gain",
+                "PMT 1 Black level", "PMT 1 Emission filter", "PMT 2 Iris",
+                "PMT 2 Gain", "PMT 2 Black level", "PMT 2 Emission filter",
+                "PMT 3 Iris", "PMT 3 Gain", "PMT 3 Black level",
+                "PMT 3 Emission filter", "Multiplier of channel 1",
+                "Multiplier of channel 2", "Multiplier of channel 3"};
+              break;
+            case 4:
+              keys = new String[] {"Number of lasers",
+                "Number of transmission detectors", "Number of PMTs",
+                "Shutter present for laser 1",
+                "Shutter present for laser 2", "Shutter present for laser 3",
+                "Neutral density filter for laser 1",
+                "Excitation filter for laser 1", "Use laser 1",
+                "Neutral density filter for laser 2",
+                "Excitation filter for laser 2", "Use laser 2",
+                "Neutral density filter for laser 3",
+                "Excitation filter for laser 3", "Use laser 3",
+                "Neutral density filter name - laser 1",
+                "Neutral density filter name - laser 2",
+                "Neutral density filter name - laser 3"};
+              break;
+            case 5:
+              keys = new String[] {"Excitation filter name - laser 1",
+                "Excitation filter name - laser 2",
+                "Excitation filter name - laser 3"};
+              break;
+            case 6:
+              keys = new String[] {"Emission filter name - laser 1",
+                "Emission filter name - laser 2",
+                "Emission filter name - laser 3"};
+              break;
+            case 7:
+              keys = new String[] {"Mixer 0 - enhanced",
+                "Mixer 0 - PMT 1 percentage",
+                "Mixer 0 - PMT 2 percentage", "Mixer 0 - PMT 3 percentage",
+                "Mixer 0 - Transmission 1 percentage",
+                "Mixer 0 - Transmission 2 percentage",
+                "Mixer 0 - Transmission 3 percentage", "Mixer 1 - enhanced",
+                "Mixer 1 - PMT 1 percentage", "Mixer 1 - PMT 2 percentage",
+                "Mixer 1 - PMT 3 percentage",
+                "Mixer 1 - Transmission 1 percentage",
+                "Mixer 1 - Transmission 2 percentage",
+                "Mixer 1 - Transmission 3 percentage",
+                "Mixer 0 - low signal on", "Mixer 1 - low signal on"};
+              break;
+            case 8:
+              keys = new String[] {"Laser 1 name"};
+              break;
+            case 9:
+              keys = new String[] {"Laser 2 name"};
+              break;
+            case 10:
+              keys = new String[] {"Laser 3 name"};
+              break;
+            case 11:
+              keys = new String[] {"Transmission detector 1 - offset",
+                "Transmission detector 1 - gain",
+                "Transmission detector 1 - black level",
+                "Transmission detector 2 - offset",
+                "Transmission detector 2 - gain",
+                "Transmission detector 2 - black level",
+                "Transmission detector 3 - offset",
+                "Transmission detector 3 - gain",
+                "Transmission detector 3 - black level"};
+              break;
+            case 12:
+              keys = new String[] {"Part number of laser 1",
+                "Part number of excitation filter for laser 1",
+                "Part number of ND filter for laser 1",
+                "Part number of emission filter for laser 1",
+                "Part number of laser 2",
+                "Part number of excitation filter for laser 2",
+                "Part number of ND filter for laser 2",
+                "Part number of emission filter for laser 2"};
+              break;
+            case 13:
+              keys = new String[] {"Part number of laser 3",
+                "Part number of excitation filter for laser 3",
+                "Part number of ND filter for laser 3",
+                "Part number of emission filter for laser 3",
+                "Part number of filter block 1",
+                "Part number of filter block 2",
+                "Filter block 1", "Filter block 2"};
+              break;
+            case 14:
+              keys = new String[] {"Filter block 1 name",
+                "Filter block 2 name"};
+              break;
+            case 15:
+              keys = new String[] {"Image band 1 status", "Image band 1 min",
+                "Image band 1 max", "Image band 2 status", "Image band 2 min",
+                "Image band 2 max", "Image band 3 status", "Image band 3 min",
+                "Image band 3 max", "Image band 4 status", "Image band 4 min",
+                "Image band 4 max", "Image band 5 status", "Image band 5 min",
+                "Image band 5 max"};
+              break;
+            case 16:
+              keys = new String[] {"Image band 5 status", "Image band 5 min",
+                "Image band 5 max"};
+              break;
+            case 17:
+              keys = new String[] {"Date stamp (seconds)",
+                "Date stamp (minutes)",
+                "Date stamp (hours)", "Date stamp (day of month)",
+                "Date stamp (month)", "Date stamp (year: actual year - 1900)",
+                "Date stamp (day of week)", "Date stamp (day of year)",
+                "Daylight savings?"};
+              break;
+            case 18:
+              keys = new String[] {"Mixer 3 - enhanced",
+                "Mixer 3 - PMT 1 percentage",
+                "Mixer 3 - PMT 2 percentage", "Mixer 3 - PMT 3 percentage",
+                "Mixer 3 - Transmission 1 percentage",
+                "Mixer 3 - Transmission 2 percentage",
+                "Mixer 3 - Transmission 3 percentage",
+                "Mixer 3 - low signal on",
+                "Mixer 3 - photon counting 1", "Mixer 3 - photon counting 2",
+                "Mixer 3 - photon counting 3", "Mixer 3 - mode"};
+              break;
+            case 19:
+              keys = new String[] {"Mixer 1 - photon counting 1",
+                "Mixer 1 - photon counting 2", "Mixer 1 - photon counting 3",
+                "Mixer 1 - mode", "Mixer2 - photon counting 1",
+                "Mixer 2 - photon counting 2", "Mixer 2 - photon counting 3",
+                "Mixer 2 - mode"};
+              break;
+            case 20:
+              keys = new String[] {"Display mode", "Course",
+                "Time Course - experiment type",
+                "Time Course - kd factor"};
+              break;
+            case 21:
+              keys = new String[] {"Time Course - ion name"};
+              break;
+            case 22:
+              keys = new String[] {"PIC file generated on Isoscan (lite)",
+                "Photon counting used (PMT 1)", "Photon counting used (PMT 2)",
+                "Photon counting used (PMT 3)", "Hot spot filter used (PMT 1)",
+                "Hot spot filter used (PMT 2)", "Hot spot filter used (PMT 3)",
+                "Tx selector used (TX 1)", "Tx selected used (TX 2)",
+                "Tx selector used (TX 3)"};
+              break;
+          }
+
+          String value;
+          while (st.hasMoreTokens() && idx < keys.length) {
+            value = st.nextToken();
+            addMeta(keys[idx], value);
+            if (keys[idx].equals("Zoom factor (user selected)")) zoom = value;
+            else if (keys[idx].equals("Z start")) zstart = value;
+            else if (keys[idx].equals("Z stop")) zstop = value;
+            else if (keys[idx].equals("Transmission detector 1 - gain")) {
+              gain1 = value;
+            }
+            else if (keys[idx].equals("Transmission detector 2 - gain")) {
+              gain2 = value;
+            }
+            else if (keys[idx].equals("Transmission detector 3 - gain")) {
+              gain3 = value;
+            }
+            else if (keys[idx].equals("Transmission detector 1 - offset")) {
+              offset1 = value;
+            }
+            else if (keys[idx].equals(
+              "Part number of excitation filter for laser 1"))
+            {
+              ex1 = value;
+            }
+            else if (keys[idx].equals(
+              "Part number of excitation filter for laser 2"))
+            {
+              ex2 = value;
+            }
+            else if (keys[idx].equals(
+              "Part number of excitation filter for laser 3"))
+            {
+              ex3 = value;
+            }
+            else if (keys[idx].equals(
+              "Part number of emission filter for laser 1"))
+            {
+              em1 = value;
+            }
+            else if (keys[idx].equals(
+              "Part number of emission filter for laser 2"))
+            {
+              em2 = value;
+            }
+            else if (keys[idx].equals(
+              "Part number of emission filter for laser 3"))
+            {
+              em3 = value;
+            }
+            else if (keys[idx].equals("Objective lens magnification")) {
+              mag = value;
+            }
+
+            idx++;
+          }
+          break;
+        default:
+          addMeta("note" + noteCount,
+            noteString(num, level, status, type, x, y, text));
+      }
+
+      // if the text of the note contains "AXIS", parse the text
+      // more thoroughly (see pg. 21 of the BioRad specs)
+
+      if (text.indexOf("AXIS") != -1) {
+        // use StringTokenizer to break up the text
+        StringTokenizer st = new StringTokenizer(text);
+        String key = st.nextToken();
+        String noteType = "";
+        if (st.hasMoreTokens()) {
+          noteType = st.nextToken();
+        }
+
+        int axisType = Integer.parseInt(noteType);
+        Vector params = new Vector();
+        while (st.hasMoreTokens()) {
+          params.add(st.nextToken());
+        }
+
+        if (params.size() > 1) {
+          switch (axisType) {
+            case 1:
+              String dx = (String) params.get(0), dy = (String) params.get(1);
+              addMeta(key + " distance (X) in microns", dx);
+              addMeta(key + " distance (Y) in microns", dy);
+              pixelSize.add(dy);
+              break;
+            case 2:
+              if (text.indexOf("AXIS_4") != -1) {
+                addMeta(key + " time (X) in seconds", params.get(0));
+                addMeta(key + " time (Y) in seconds", params.get(1));
+                core.sizeZ[0] = 1;
+                core.sizeT[0] = core.imageCount[0];
+                core.orderCertain[0] = true;
+              }
+              break;
+            case 3:
+              addMeta(key + " angle (X) in degrees", params.get(0));
+              addMeta(key + " angle (Y) in degrees", params.get(1));
+              break;
+            case 4:
+              addMeta(key + " intensity (X)", params.get(0));
+              addMeta(key + " intensity (Y)", params.get(1));
+              break;
+            case 6:
+              addMeta(key + " ratio (X)", params.get(0));
+              addMeta(key + " ratio (Y)", params.get(1));
+              break;
+            case 7:
+              addMeta(key + " log ratio (X)", params.get(0));
+              addMeta(key + " log ratio (Y)", params.get(1));
+              break;
+            case 9:
+              addMeta(key + " noncalibrated intensity min", params.get(0));
+              addMeta(key + " noncalibrated intensity max", params.get(1));
+              addMeta(key + " calibrated intensity min", params.get(2));
+              addMeta(key + " calibrated intensity max", params.get(3));
+              break;
+            case 11:
+              addMeta(key + " RGB type (X)", params.get(0));
+              addMeta(key + " RGB type (Y)", params.get(1));
+              break;
+            case 14:
+              addMeta(key + " time course type (X)", params.get(0));
+              addMeta(key + " time course type (Y)", params.get(1));
+              break;
+            case 15:
+              addMeta(key + " inverse sigmoid calibrated intensity (min)",
+                params.get(0));
+              addMeta(key + " inverse sigmoid calibrated intensity (max)",
+                params.get(1));
+              addMeta(key +
+                " inverse sigmoid calibrated intensity (beta)", params.get(2));
+              addMeta(key + " inverse sigmoid calibrated intensity (Kd)",
+                params.get(3));
+              addMeta(key + " inverse sigmoid calibrated intensity " +
+                "(calibrated max)", params.get(0));
+              break;
+            case 16:
+              addMeta(key + " log inverse sigmoid calibrated " +
+                "intensity (min)", params.get(0));
+              addMeta(key + " log inverse sigmoid calibrated " +
+                "intensity (max)", params.get(1));
+              addMeta(key + " log inverse sigmoid calibrated " +
+                "intensity (beta)", params.get(2));
+              addMeta(key + " log inverse sigmoid calibrated " +
+                "intensity (Kd)", params.get(3));
+              addMeta(key + " log inverse sigmoid calibrated " +
+                "intensity (calibrated max)", params.get(0));
+              break;
+          }
+
+        }
+      }
+    }
+
+    status("Reading color table");
+
+    // read color tables
+    int numLuts = 0;
+    byte[][] lut = new byte[3][768];
+    boolean eof = false;
+    while (!eof && numLuts < 3) {
+      if (in.getFilePointer() + lut[numLuts].length <= in.length()) {
+        in.read(lut[numLuts]);
+        numLuts++;
+      }
+      else eof = true;
+    }
+
+    if (debug && debugLevel >= 2) {
+      debug(numLuts + " color table" + (numLuts == 1 ? "" : "s") + " present.");
+    }
+
+    // convert color table bytes to floats
+    float[][][] colors = new float[numLuts][3][256];
+    for (int i=0; i<numLuts; i++) {
+      for (int l=0; l<256; l++) {
+        colors[i][0][l] = (float) (lut[i][l] & 0xff);
+        colors[i][1][l] = (float) (lut[i][l + 256] & 0xff);
+        colors[i][2][l] = (float) (lut[i][l + 512] & 0xff);
+      }
+    }
+
+    String colorString = "";
+    for (int i=0; i<numLuts; i++) {
+      for (int j=0; j<256; j++) {
+        for (int k=0; k<3; k++) {
+          colorString += (colors[i][k][j]);
+          if (!(j == 255 && k == 2)) colorString += ",";
+        }
+      }
+      colorString += "\n\n";
+    }
+
+    addMeta("luts", colorString);
+
+    status("Populating metadata");
+
+    // look for companion metadata files
+
+    Location parent = new Location(currentId).getAbsoluteFile().getParentFile();
+    String[] list = parent.list();
+
+    for (int i=0; i<list.length; i++) {
+      if (list[i].endsWith("data.raw")) {
+        RandomAccessStream raw = new RandomAccessStream(
+          new Location(parent.getAbsolutePath(), list[i]).getAbsolutePath());
+        used.add(new Location(
+          parent.getAbsolutePath(), list[i]).getAbsolutePath());
+        String line = raw.readLine();
+        while (line != null && line.length() > 0) {
+          if (line.charAt(0) != '[') {
+            String key = line.substring(0, line.indexOf("="));
+            String value = line.substring(line.indexOf("=") + 1);
+            addMeta(key.trim(), value.trim());
+          }
+          line = raw.readLine();
+        }
+        raw.close();
+      }
+      else if (list[i].endsWith("lse.xml")) {
+        RandomAccessStream raw = new RandomAccessStream(
+          new Location(parent.getAbsolutePath(), list[i]).getAbsolutePath());
+        used.add(new Location(
+          parent.getAbsolutePath(), list[i]).getAbsolutePath());
+        byte[] b = new byte[(int) raw.length()];
+        raw.read(b);
+        String xml = new String(b);
+
+        if (xml.indexOf("SectionInfo") != -1) {
+          int start = xml.indexOf("<SectionInfo>") + 13;
+          int end = xml.indexOf("</SectionInfo>");
+          xml = xml.substring(start, end);
+
+          // parse the timestamps
+          while (xml.length() > 0) {
+            String element = xml.substring(0, xml.indexOf(">") + 1);
+            xml = xml.substring(xml.indexOf(">") + 1);
+
+            int ndx = element.indexOf("TimeCompleted") + 15;
+            String stamp = element.substring(ndx, element.indexOf("\"", ndx));
+
+            String key = element.substring(1, element.indexOf("\"",
+              element.indexOf("\"") + 1));
+            key = key.replace('\"', '\0');
+            key = key.replace('=', ' ');
+
+            addMeta(key + " Timestamp", stamp);
+          }
+        }
+        raw.close();
+        b = null;
+      }
+    }
+
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    // Populate the metadata store
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    // populate Image element
+    store.setImage(name, null, null, null);
+
+    // populate Pixels element
+    in.seek(14);
+    core.pixelType[0] = in.readShort() == 1 ? FormatTools.UINT8 :
+      FormatTools.UINT16;
+
+    core.currentOrder[0] = "XY";
+    int[] dims = new int[] {core.sizeZ[0], core.sizeC[0], core.sizeT[0]};
+    int max = 0;
+    int min = Integer.MAX_VALUE;
+    int median = 1;
+
+    for (int i=0; i<dims.length; i++) {
+      if (dims[i] < min) min = dims[i];
+      if (dims[i] > max) max = dims[i];
+      else median = dims[i];
+    }
+
+    int[] orderedDims = new int[] {max, median, min};
+    for (int i=0; i<orderedDims.length; i++) {
+      if (orderedDims[i] == core.sizeZ[0] &&
+        core.currentOrder[0].indexOf("Z") == -1)
+      {
+        core.currentOrder[0] += "Z";
+      }
+      else if (orderedDims[i] == core.sizeC[0] &&
+        core.currentOrder[0].indexOf("C") == -1)
+      {
+        core.currentOrder[0] += "C";
+      }
+      else core.currentOrder[0] += "T";
+    }
+
+    FormatTools.populatePixels(store, this);
+
+    // populate Dimensions element
+    int size = pixelSize.size();
+    Float pixelSizeX = null, pixelSizeY = null, pixelSizeZ = null;
+    if (size >= 1) pixelSizeX = new Float((String) pixelSize.get(0));
+    if (size >= 2) pixelSizeY = new Float((String) pixelSize.get(1));
+    if (size >= 3) pixelSizeZ = new Float((String) pixelSize.get(2));
+    store.setDimensions(pixelSizeX, pixelSizeY, pixelSizeZ, null, null, null);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      String gain = i == 0 ? gain1 : i == 1 ? gain2 : gain3;
+      String offset = i == 0 ? offset1 : i == 1 ? gain2 : gain3;
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+       offset == null ? null : new Float(offset),
+       gain == null ? null : new Float(gain), null, null, null, null, null,
+       null, null, null, null, null, null, null, null, null);
+      store.setDisplayChannel(new Integer(i), new Double(ramp1max),
+        new Double(ramp1min), null, null);
+    }
+    store.setDisplayOptions(zoom == null ? null : new Float(zoom),
+      new Boolean(core.sizeC[0] > 1), new Boolean(core.sizeC[0] >= 2),
+      new Boolean(core.sizeC[0] >= 3), Boolean.FALSE, null,
+      zstart == null ? null :
+      new Integer((int) (new Double(zstart).doubleValue())), zstop == null ?
+      null : new Integer((int) (new Double(zstop).doubleValue())), null, null,
+      null, null, core.sizeC[0] > 1 ? new Integer(0) : null,
+      core.sizeC[0] > 1 ? new Integer(1) : null,
+      core.sizeC[0] > 1 ? new Integer(2) : null, new Integer(0));
+
+    for (int i=0; i<3; i++) {
+      String exc = i == 0 ? ex1 : i == 1 ? ex2 : ex3;
+      String ems = i == 0 ? em1 : i == 1 ? em2 : em3;
+      if (exc != null) store.setExcitationFilter(null, null, exc, null, null);
+      if (ems != null) store.setEmissionFilter(null, null, ems, null, null);
+    }
+    if (mag != null) {
+      store.setObjective(null, null, null, null, new Float(mag), null, null);
+    }
+  }
+
+  // -- Helper methods --
+
+  private String noteString(int n, int l,
+    int s, int t, int x, int y, String p)
+  {
+    StringBuffer sb = new StringBuffer(100);
+    sb.append("level=");
+    sb.append(l);
+    sb.append("; num=");
+    sb.append(n);
+    sb.append("; status=");
+    sb.append(s);
+    sb.append("; type=");
+    sb.append(NOTE_NAMES[t]);
+    sb.append("; x=");
+    sb.append(x);
+    sb.append("; y=");
+    sb.append(y);
+    sb.append("; text=");
+    sb.append(p == null ? "null" : p.trim());
+    return sb.toString();
+  }
+
+}
diff --git a/loci/formats/in/CBZip2InputStream.java b/loci/formats/in/CBZip2InputStream.java
new file mode 100644
index 0000000..1bb02ae
--- /dev/null
+++ b/loci/formats/in/CBZip2InputStream.java
@@ -0,0 +1,858 @@
+//
+// CBZip2InputStream.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005-2007 Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/*
+ * Copyright  2001-2004 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron at aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+package loci.formats.in;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An input stream that decompresses from the BZip2 format (without the file
+ * header chars) to be read as any other stream.
+ */
+public class CBZip2InputStream extends InputStream implements BZip2Constants {
+  private static void cadvise() {
+    System.out.println("CRC Error");
+    //throw new CCoruptionError();
+  }
+
+  private static void compressedStreamEOF() {
+    cadvise();
+  }
+
+  private void makeMaps() {
+    int q;
+    nInUse = 0;
+    for (q = 0; q < 256; q++) {
+      if (inUse[q]) {
+        seqToUnseq[nInUse] = (char) q;
+        unseqToSeq[q] = (char) nInUse;
+        nInUse++;
+      }
+    }
+  }
+
+  /*
+    index of the last char in the block, so
+    the block size == last + 1.
+  */
+  private int  last;
+
+  /*
+    index in zptr[] of original string after sorting.
+  */
+  private int  origPtr;
+
+  /*
+    always: in the range 0 .. 9.
+    The current block size is 100000 * this number.
+  */
+  private int blockSize100k;
+
+  private boolean blockRandomised;
+
+  private int bsBuff;
+  private int bsLive;
+  private CRC mCrc = new CRC();
+
+  private boolean[] inUse = new boolean[256];
+  private int nInUse;
+
+  private char[] seqToUnseq = new char[256];
+  private char[] unseqToSeq = new char[256];
+
+  private char[] selector = new char[MAX_SELECTORS];
+  private char[] selectorMtf = new char[MAX_SELECTORS];
+
+  private int[] tt;
+  private char[] ll8;
+
+  /*
+    freq table collected to save a pass over the data
+    during decompression.
+  */
+  private int[] unzftab = new int[256];
+
+  private int[][] limit = new int[N_GROUPS][MAX_ALPHA_SIZE];
+  private int[][] base = new int[N_GROUPS][MAX_ALPHA_SIZE];
+  private int[][] perm = new int[N_GROUPS][MAX_ALPHA_SIZE];
+  private int[] minLens = new int[N_GROUPS];
+
+  private InputStream bsStream;
+
+  private boolean streamEnd = false;
+
+  private int currentChar = -1;
+
+  private static final int START_BLOCK_STATE = 1;
+  private static final int RAND_PART_A_STATE = 2;
+  private static final int RAND_PART_B_STATE = 3;
+  private static final int RAND_PART_C_STATE = 4;
+  private static final int NO_RAND_PART_A_STATE = 5;
+  private static final int NO_RAND_PART_B_STATE = 6;
+  private static final int NO_RAND_PART_C_STATE = 7;
+
+  private int currentState = START_BLOCK_STATE;
+
+  private int storedBlockCRC, storedCombinedCRC;
+  private int computedBlockCRC, computedCombinedCRC;
+
+  protected int i2, count, chPrev, ch2;
+  protected int i, tPos;
+  protected int rNToGo = 0;
+  protected int rTPos  = 0;
+  protected int j2;
+  protected char z;
+
+  public CBZip2InputStream(InputStream zStream) {
+    ll8 = null;
+    tt = null;
+    bsSetStream(zStream);
+    initialize();
+    initBlock();
+    setupBlock();
+  }
+
+  public int read() {
+    if (streamEnd) {
+      return -1;
+    }
+    else {
+      int retChar = currentChar;
+      switch (currentState) {
+        case START_BLOCK_STATE:
+          break;
+        case RAND_PART_A_STATE:
+          break;
+        case RAND_PART_B_STATE:
+          setupRandPartB();
+          break;
+        case RAND_PART_C_STATE:
+          setupRandPartC();
+          break;
+        case NO_RAND_PART_A_STATE:
+          break;
+        case NO_RAND_PART_B_STATE:
+          setupNoRandPartB();
+          break;
+        case NO_RAND_PART_C_STATE:
+          setupNoRandPartC();
+          break;
+        default:
+          break;
+      }
+      return retChar;
+    }
+  }
+
+  private void initialize() {
+    char magic3, magic4;
+    magic3 = bsGetUChar();
+    magic4 = bsGetUChar();
+
+    if (magic3 != 'h' || magic4 < '1' || magic4 > '9') {
+      bsFinishedWithStream();
+      streamEnd = true;
+      return;
+    }
+
+    setDecompressStructureSizes(magic4 - '0');
+    computedCombinedCRC = 0;
+  }
+
+  private void initBlock() {
+    char magic1, magic2, magic3, magic4;
+    char magic5, magic6;
+    magic1 = bsGetUChar();
+    magic2 = bsGetUChar();
+    magic3 = bsGetUChar();
+    magic4 = bsGetUChar();
+    magic5 = bsGetUChar();
+    magic6 = bsGetUChar();
+    if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 &&
+      magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90)
+    {
+      complete();
+      return;
+    }
+
+    if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 ||
+      magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59)
+    {
+      badBlockHeader();
+      streamEnd = true;
+      return;
+    }
+
+    storedBlockCRC = bsGetInt32();
+
+    if (bsR(1) == 1) {
+      blockRandomised = true;
+    }
+    else {
+      blockRandomised = false;
+    }
+
+    //currBlockNo++;
+    getAndMoveToFrontDecode();
+
+    mCrc.initialiseCRC();
+    currentState = START_BLOCK_STATE;
+  }
+
+  private void endBlock() {
+    computedBlockCRC = mCrc.getFinalCRC();
+    // A bad CRC is considered a fatal error.
+    if (storedBlockCRC != computedBlockCRC) {
+      crcError();
+    }
+
+    computedCombinedCRC = (computedCombinedCRC << 1) |
+      (computedCombinedCRC >>> 31);
+    computedCombinedCRC ^= computedBlockCRC;
+  }
+
+  private void complete() {
+    storedCombinedCRC = bsGetInt32();
+    if (storedCombinedCRC != computedCombinedCRC) {
+      crcError();
+    }
+
+    bsFinishedWithStream();
+    streamEnd = true;
+  }
+
+  private static void blockOverrun() {
+    cadvise();
+  }
+
+  private static void badBlockHeader() {
+    cadvise();
+  }
+
+  private static void crcError() {
+    cadvise();
+  }
+
+  private void bsFinishedWithStream() {
+    try {
+      if (this.bsStream != null) {
+        if (this.bsStream != System.in) {
+          this.bsStream.close();
+          this.bsStream = null;
+        }
+      }
+    }
+    catch (IOException ioe) {
+      //ignore
+    }
+  }
+
+  private void bsSetStream(InputStream f) {
+    bsStream = f;
+    bsLive = 0;
+    bsBuff = 0;
+  }
+
+  private int bsR(int n) {
+    int v;
+    while (bsLive < n) {
+      int zzi;
+      char thech = 0;
+      try {
+        thech = (char) bsStream.read();
+      }
+      catch (IOException e) {
+        compressedStreamEOF();
+      }
+      if (thech == -1) {
+        compressedStreamEOF();
+      }
+      zzi = thech;
+      bsBuff = (bsBuff << 8) | (zzi & 0xff);
+      bsLive += 8;
+    }
+
+    v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1);
+    bsLive -= n;
+    return v;
+  }
+
+  private char bsGetUChar() {
+    return (char) bsR(8);
+  }
+
+  private int bsGetint() {
+    int u = 0;
+    u = (u << 8) | bsR(8);
+    u = (u << 8) | bsR(8);
+    u = (u << 8) | bsR(8);
+    u = (u << 8) | bsR(8);
+    return u;
+  }
+
+  private int bsGetIntVS(int numBits) {
+    return (int) bsR(numBits);
+  }
+
+  private int bsGetInt32() {
+    return (int) bsGetint();
+  }
+
+  private void hbCreateDecodeTables(int[] tLimit, int[] tBase,
+    int[] tPerm, char[] length, int minLen, int maxLen, int alphaSize)
+  {
+    int pp, q, j, vec;
+
+    pp = 0;
+    for (q = minLen; q <= maxLen; q++) {
+      for (j = 0; j < alphaSize; j++) {
+        if (length[j] == q) {
+          tPerm[pp] = j;
+          pp++;
+        }
+      }
+    }
+
+    for (q = 0; q < MAX_CODE_LEN; q++) {
+      tBase[q] = 0;
+    }
+    for (q = 0; q < alphaSize; q++) {
+      tBase[length[q] + 1]++;
+    }
+
+    for (q = 1; q < MAX_CODE_LEN; q++) {
+      tBase[q] += tBase[q - 1];
+    }
+
+    for (q = 0; q < MAX_CODE_LEN; q++) {
+      tLimit[q] = 0;
+    }
+    vec = 0;
+
+    for (q = minLen; q <= maxLen; q++) {
+      vec += (tBase[q + 1] - tBase[q]);
+      tLimit[q] = vec - 1;
+      vec <<= 1;
+    }
+    for (q = minLen + 1; q <= maxLen; q++) {
+      tBase[q] = ((tLimit[q - 1] + 1) << 1) - tBase[q];
+    }
+  }
+
+  private void recvDecodingTables() {
+    char[][] len = new char[N_GROUPS][MAX_ALPHA_SIZE];
+    int q, j, t, nGroups, nSelectors, alphaSize;
+    int minLen, maxLen;
+    boolean[] inUse16 = new boolean[16];
+
+    /* Receive the mapping table */
+    for (q = 0; q < 16; q++) {
+      if (bsR(1) == 1) {
+        inUse16[q] = true;
+      }
+      else {
+        inUse16[q] = false;
+      }
+    }
+
+    for (q = 0; q < 256; q++) {
+      inUse[q] = false;
+    }
+
+    for (q = 0; q < 16; q++) {
+      if (inUse16[q]) {
+        for (j = 0; j < 16; j++) {
+          if (bsR(1) == 1) {
+            inUse[q * 16 + j] = true;
+          }
+        }
+      }
+    }
+
+    makeMaps();
+    alphaSize = nInUse + 2;
+
+    /* Now the selectors */
+    nGroups = bsR(3);
+    nSelectors = bsR(15);
+    for (q = 0; q < nSelectors; q++) {
+      j = 0;
+      while (bsR(1) == 1) {
+        j++;
+      }
+      selectorMtf[q] = (char) j;
+    }
+
+    // undo the MTF values for the selectors.
+    char[] pos = new char[N_GROUPS];
+    char tmp, v;
+    for (v = 0; v < nGroups; v++) {
+      pos[v] = v;
+    }
+
+    for (q = 0; q < nSelectors; q++) {
+      v = selectorMtf[q];
+      tmp = pos[v];
+      while (v > 0) {
+        pos[v] = pos[v - 1];
+        v--;
+      }
+      pos[0] = tmp;
+      selector[q] = tmp;
+    }
+
+    /* Now the coding tables */
+    for (t = 0; t < nGroups; t++) {
+      int curr = bsR(5);
+      for (q = 0; q < alphaSize; q++) {
+        while (bsR(1) == 1) {
+          if (bsR(1) == 0) {
+            curr++;
+          }
+          else {
+            curr--;
+          }
+        }
+        len[t][q] = (char) curr;
+      }
+    }
+
+    /* Create the Huffman decoding tables */
+    for (t = 0; t < nGroups; t++) {
+      minLen = 32;
+      maxLen = 0;
+      for (q = 0; q < alphaSize; q++) {
+        if (len[t][q] > maxLen) {
+          maxLen = len[t][q];
+        }
+        if (len[t][q] < minLen) {
+          minLen = len[t][q];
+        }
+      }
+      hbCreateDecodeTables(limit[t], base[t], perm[t],
+        len[t], minLen, maxLen, alphaSize);
+      minLens[t] = minLen;
+    }
+  }
+
+  private void getAndMoveToFrontDecode() {
+    char[] yy = new char[256];
+    int q, j, nextSym, limitLast;
+    int eob, groupNo, groupPos;
+
+    limitLast = BASE_BLOCK_SIZE * blockSize100k;
+    origPtr = bsGetIntVS(24);
+
+    recvDecodingTables();
+    eob = nInUse + 1;
+    groupNo = -1;
+    groupPos = 0;
+
+    /*
+      Setting up the unzftab entries here is not strictly
+      necessary, but it does save having to do it later
+      in a separate pass, and so saves a block's worth of
+      cache misses.
+    */
+    for (q = 0; q <= 255; q++) {
+      unzftab[i] = 0;
+    }
+
+    for (q = 0; q <= 255; q++) {
+      yy[q] = (char) q;
+    }
+
+    last = -1;
+
+    int zt, zn, zvec, zj;
+    if (groupPos == 0) {
+      groupNo++;
+      groupPos = G_SIZE;
+    }
+    groupPos--;
+    zt = selector[groupNo];
+    zn = minLens[zt];
+    zvec = bsR(zn);
+    while (zvec > limit[zt][zn]) {
+      zn++;
+      while (bsLive < 1) {
+        int zzi;
+        char thech = 0;
+        try {
+          thech = (char) bsStream.read();
+        }
+        catch (IOException e) {
+          compressedStreamEOF();
+        }
+        if (thech == -1) {
+          compressedStreamEOF();
+        }
+        zzi = thech;
+        bsBuff = (bsBuff << 8) | (zzi & 0xff);
+        bsLive += 8;
+      }
+      zj = (bsBuff >> (bsLive - 1)) & 1;
+      bsLive--;
+      zvec = (zvec << 1) | zj;
+    }
+    nextSym = perm[zt][zvec - base[zt][zn]];
+
+    while (true) {
+
+      if (nextSym == eob) {
+        break;
+      }
+
+      if (nextSym == RUNA || nextSym == RUNB) {
+        char ch;
+        int s = -1;
+        int n = 1;
+        do {
+          if (nextSym == RUNA) {
+            s = s + (0 + 1) * n;
+          }
+          else if (nextSym == RUNB) {
+            s = s + (1 + 1) * n;
+          }
+          n *= 2;
+
+          //int zt, zn, zvec, zj;
+          if (groupPos == 0) {
+            groupNo++;
+            groupPos = G_SIZE;
+          }
+          groupPos--;
+          zt = selector[groupNo];
+          zn = minLens[zt];
+          zvec = bsR(zn);
+          while (zvec > limit[zt][zn]) {
+            zn++;
+            while (bsLive < 1) {
+              int zzi;
+              char thech = 0;
+              try {
+                thech = (char) bsStream.read();
+              }
+              catch (IOException e) {
+                compressedStreamEOF();
+              }
+              if (thech == -1) {
+                compressedStreamEOF();
+              }
+              zzi = thech;
+              bsBuff = (bsBuff << 8) | (zzi & 0xff);
+              bsLive += 8;
+            }
+            zj = (bsBuff >> (bsLive - 1)) & 1;
+            bsLive--;
+            zvec = (zvec << 1) | zj;
+          }
+          nextSym = perm[zt][zvec - base[zt][zn]];
+        }
+        while (nextSym == RUNA || nextSym == RUNB);
+
+        s++;
+        ch = seqToUnseq[yy[0]];
+        unzftab[ch] += s;
+
+        while (s > 0) {
+          last++;
+          ll8[last] = ch;
+          s--;
+        }
+
+        if (last >= limitLast) {
+          blockOverrun();
+        }
+        continue;
+      }
+      else {
+        char tmp;
+        last++;
+        if (last >= limitLast) {
+          blockOverrun();
+        }
+
+        tmp = yy[nextSym - 1];
+        unzftab[seqToUnseq[tmp]]++;
+        ll8[last] = seqToUnseq[tmp];
+
+        /*
+          This loop is hammered during decompression,
+          hence the unrolling.
+
+          for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1];
+        */
+
+        j = nextSym - 1;
+        for (; j > 3; j -= 4) {
+          yy[j]   = yy[j - 1];
+          yy[j - 1] = yy[j - 2];
+          yy[j - 2] = yy[j - 3];
+          yy[j - 3] = yy[j - 4];
+        }
+        for (; j > 0; j--) {
+          yy[j] = yy[j - 1];
+        }
+
+        yy[0] = tmp;
+
+        //int zt, zn, zvec, zj;
+        if (groupPos == 0) {
+          groupNo++;
+          groupPos = G_SIZE;
+        }
+        groupPos--;
+        zt = selector[groupNo];
+        zn = minLens[zt];
+        zvec = bsR(zn);
+        while (zvec > limit[zt][zn]) {
+          zn++;
+          while (bsLive < 1) {
+            int zzi;
+            char thech = 0;
+            try {
+              thech = (char) bsStream.read();
+            }
+            catch (IOException e) {
+              compressedStreamEOF();
+            }
+            zzi = thech;
+            bsBuff = (bsBuff << 8) | (zzi & 0xff);
+            bsLive += 8;
+          }
+          zj = (bsBuff >> (bsLive - 1)) & 1;
+          bsLive--;
+          zvec = (zvec << 1) | zj;
+        }
+        nextSym = perm[zt][zvec - base[zt][zn]];
+
+        continue;
+      }
+    }
+  }
+
+  private void setupBlock() {
+    int[] cftab = new int[257];
+    char ch;
+
+    cftab[0] = 0;
+    for (i = 1; i <= 256; i++) {
+      cftab[i] = unzftab[i - 1];
+    }
+    for (i = 1; i <= 256; i++) {
+      cftab[i] += cftab[i - 1];
+    }
+
+    for (i = 0; i <= last; i++) {
+      ch = (char) ll8[i];
+      tt[cftab[ch]] = i;
+      cftab[ch]++;
+    }
+    cftab = null;
+
+    tPos = tt[origPtr];
+
+    count = 0;
+    i2 = 0;
+    ch2 = 256;   /* not a char and not EOF */
+
+    if (blockRandomised) {
+      rNToGo = 0;
+      rTPos = 0;
+      setupRandPartA();
+    }
+    else {
+      setupNoRandPartA();
+    }
+  }
+
+  private void setupRandPartA() {
+    if (i2 <= last) {
+      chPrev = ch2;
+      ch2 = ll8[tPos];
+      tPos = tt[tPos];
+      if (rNToGo == 0) {
+        rNToGo = R_NUMS[rTPos];
+        rTPos++;
+        if (rTPos == 512) {
+          rTPos = 0;
+        }
+      }
+      rNToGo--;
+      ch2 ^= (int) ((rNToGo == 1) ? 1 : 0);
+      i2++;
+
+      currentChar = ch2;
+      currentState = RAND_PART_B_STATE;
+      mCrc.updateCRC(ch2);
+    }
+    else {
+      endBlock();
+      initBlock();
+      setupBlock();
+    }
+  }
+
+  private void setupNoRandPartA() {
+    if (i2 <= last) {
+      chPrev = ch2;
+      ch2 = ll8[tPos];
+      tPos = tt[tPos];
+      i2++;
+
+      currentChar = ch2;
+      currentState = NO_RAND_PART_B_STATE;
+      mCrc.updateCRC(ch2);
+    }
+    else {
+      endBlock();
+      initBlock();
+      setupBlock();
+    }
+  }
+
+  private void setupRandPartB() {
+    if (ch2 != chPrev) {
+      currentState = RAND_PART_A_STATE;
+      count = 1;
+      setupRandPartA();
+    }
+    else {
+      count++;
+      if (count >= 4) {
+        z = ll8[tPos];
+        tPos = tt[tPos];
+        if (rNToGo == 0) {
+          rNToGo = R_NUMS[rTPos];
+          rTPos++;
+          if (rTPos == 512) {
+            rTPos = 0;
+          }
+        }
+        rNToGo--;
+        z ^= ((rNToGo == 1) ? 1 : 0);
+        j2 = 0;
+        currentState = RAND_PART_C_STATE;
+        setupRandPartC();
+      }
+      else {
+        currentState = RAND_PART_A_STATE;
+        setupRandPartA();
+      }
+    }
+  }
+
+  private void setupRandPartC() {
+    if (j2 < (int) z) {
+      currentChar = ch2;
+      mCrc.updateCRC(ch2);
+      j2++;
+    }
+    else {
+      currentState = RAND_PART_A_STATE;
+      i2++;
+      count = 0;
+      setupRandPartA();
+    }
+  }
+
+  private void setupNoRandPartB() {
+    if (ch2 != chPrev) {
+      currentState = NO_RAND_PART_A_STATE;
+      count = 1;
+      setupNoRandPartA();
+    }
+    else {
+      count++;
+      if (count >= 4) {
+        z = ll8[tPos];
+        tPos = tt[tPos];
+        currentState = NO_RAND_PART_C_STATE;
+        j2 = 0;
+        setupNoRandPartC();
+      }
+      else {
+        currentState = NO_RAND_PART_A_STATE;
+        setupNoRandPartA();
+      }
+    }
+  }
+
+  private void setupNoRandPartC() {
+    if (j2 < (int) z) {
+      currentChar = ch2;
+      mCrc.updateCRC(ch2);
+      j2++;
+    }
+    else {
+      currentState = NO_RAND_PART_A_STATE;
+      i2++;
+      count = 0;
+      setupNoRandPartA();
+    }
+  }
+
+  private void setDecompressStructureSizes(int newSize100k) {
+    if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k &&
+      blockSize100k <= 9))
+    {
+      // throw new IOException("Invalid block size");
+    }
+
+    blockSize100k = newSize100k;
+
+    if (newSize100k == 0) {
+      return;
+    }
+
+    int n = BASE_BLOCK_SIZE * newSize100k;
+    ll8 = new char[n];
+    tt = new int[n];
+  }
+}
+
diff --git a/loci/formats/in/CRC.java b/loci/formats/in/CRC.java
new file mode 100644
index 0000000..b56f1b4
--- /dev/null
+++ b/loci/formats/in/CRC.java
@@ -0,0 +1,162 @@
+//
+// CRC.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005-2007 Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+/*
+ * Copyright  2001-2002,2004 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+/*
+ * This package is based on the work done by Keiron Liddle, Aftex Software
+ * <keiron at aftexsw.com> to whom the Ant project is very grateful for his
+ * great code.
+ */
+
+package loci.formats.in;
+
+/**
+ * A simple class the hold and calculate the CRC for sanity checking
+ * of the data.
+ */
+public class CRC {
+
+  // -- Constants --
+
+  public static final int[] CRC_32_TABLE = {
+    0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
+    0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+    0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
+    0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+    0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
+    0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+    0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
+    0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+    0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+    0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+    0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
+    0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+    0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
+    0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+    0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
+    0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+    0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
+    0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+    0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+    0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+    0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
+    0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+    0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
+    0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+    0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
+    0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+    0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
+    0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+    0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+    0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+    0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
+    0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+    0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
+    0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+    0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
+    0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+    0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
+    0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+    0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+    0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+    0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
+    0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+    0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
+    0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+    0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
+    0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+    0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
+    0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+    0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+    0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+    0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
+    0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+    0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
+    0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+    0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
+    0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+    0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
+    0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+    0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+    0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+    0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
+    0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+    0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
+    0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+  };
+
+  // -- Fields --
+
+  private int globalCrc;
+
+  // -- Constructor --
+
+  public CRC() {
+    initialiseCRC();
+  }
+
+  // -- CRC API methods --
+
+  public void initialiseCRC() {
+    globalCrc = 0xffffffff;
+  }
+
+  public int getFinalCRC() {
+    return ~globalCrc;
+  }
+
+  public int getGlobalCRC() {
+    return globalCrc;
+  }
+
+  public void setGlobalCRC(int newCrc) {
+    globalCrc = newCrc;
+  }
+
+  public void updateCRC(int inCh) {
+    int temp = (globalCrc >> 24) ^ inCh;
+    if (temp < 0) {
+      temp = 256 + temp;
+    }
+    globalCrc = (globalCrc << 8) ^ CRC.CRC_32_TABLE[temp];
+  }
+
+}
+
diff --git a/loci/formats/in/DeltavisionReader.java b/loci/formats/in/DeltavisionReader.java
new file mode 100644
index 0000000..b89db29
--- /dev/null
+++ b/loci/formats/in/DeltavisionReader.java
@@ -0,0 +1,610 @@
+//
+// DeltavisionReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * DeltavisionReader is the file format reader for Deltavision files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/DeltavisionReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/DeltavisionReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class DeltavisionReader extends FormatReader {
+
+  // -- Constants --
+
+  private static final short LITTLE_ENDIAN = -16224;
+  private static final int HEADER_LENGTH = 1024;
+
+  // -- Fields --
+
+  /** Size of extended header. */
+  private int extSize;
+
+  /** Bytes per pixel. */
+  private int bytesPerPixel;
+
+  /** Size of one wave in the extended header. */
+  protected int wSize;
+
+  /** Size of one z section in the extended header. */
+  protected int zSize;
+
+  /** Size of one time element in the extended header. */
+  protected int tSize;
+
+  /**
+   * The number of ints in each extended header section. These fields appear
+   * to be all blank but need to be skipped to get to the floats afterwards
+   */
+  protected int numIntsPerSection;
+  protected int numFloatsPerSection;
+
+  /** Initialize an array of Extended Header Field structures. */
+  protected DVExtHdrFields[][][] extHdrFields = null;
+
+  // -- Constructor --
+
+  /** Constructs a new Deltavision reader. */
+  public DeltavisionReader() {
+    super("Deltavision", new String[] {"dv", "r3d", "r3d_d3d"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    // read the image plane's pixel data
+    long offset = HEADER_LENGTH + extSize;
+    long bytes = core.sizeX[0] * core.sizeY[0] * bytesPerPixel;
+    in.seek(offset + bytes*no);
+    in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("DeltavisionReader.initFile(" + id + ")");
+    super.initFile(id);
+
+    in = new RandomAccessStream(id);
+
+    status("Reading header");
+
+    // read in the image header data
+    in.seek(96);
+    in.order(true);
+    core.littleEndian[0] = in.readShort() == LITTLE_ENDIAN;
+
+    in.order(core.littleEndian[0]);
+    in.seek(8);
+
+    core.imageCount[0] = in.readInt();
+
+    in.seek(92);
+    extSize = in.readInt();
+
+    in.seek(0);
+    core.sizeX[0] = in.readInt();
+    core.sizeY[0] = in.readInt();
+
+    Integer xSize = new Integer(core.sizeX[0]);
+    Integer ySize = new Integer(core.sizeY[0]);
+    addMeta("ImageWidth", xSize);
+    addMeta("ImageHeight", ySize);
+    addMeta("NumberOfImages", new Integer(in.readInt()));
+    int filePixelType = in.readInt();
+    String pixel;
+
+    switch (filePixelType) {
+      case 0:
+        pixel = "8 bit unsigned integer";
+        core.pixelType[0] = FormatTools.UINT8;
+        bytesPerPixel = 1;
+        break;
+      case 1:
+        pixel = "16 bit signed integer";
+        core.pixelType[0] = FormatTools.UINT16;
+        bytesPerPixel = 2;
+        break;
+      case 2:
+        pixel = "32 bit floating point";
+        core.pixelType[0] = FormatTools.FLOAT;
+        bytesPerPixel = 4;
+        break;
+      case 3:
+        pixel = "32 bit complex";
+        core.pixelType[0] = FormatTools.UINT32;
+        bytesPerPixel = 4;
+        break;
+      case 4:
+        pixel = "64 bit complex";
+        core.pixelType[0] = FormatTools.FLOAT;
+        bytesPerPixel = 8;
+        break;
+      case 6:
+        pixel = "16 bit unsigned integer";
+        core.pixelType[0] = FormatTools.UINT16;
+        bytesPerPixel = 2;
+        break;
+      default:
+        pixel = "unknown";
+        core.pixelType[0] = FormatTools.UINT8;
+        bytesPerPixel = 1;
+    }
+
+    addMeta("PixelType", pixel);
+    addMeta("Sub-image starting point (X)", new Integer(in.readInt()));
+    addMeta("Sub-image starting point (Y)", new Integer(in.readInt()));
+    addMeta("Sub-image starting point (Z)", new Integer(in.readInt()));
+    addMeta("Pixel sampling size (X)", new Integer(in.readInt()));
+    addMeta("Pixel sampling size (Y)", new Integer(in.readInt()));
+    addMeta("Pixel sampling size (Z)", new Integer(in.readInt()));
+
+    float pixX = in.readFloat();
+    float pixY = in.readFloat();
+    float pixZ = in.readFloat();
+
+    addMeta("X element length (in um)", new Float(pixX));
+    addMeta("Y element length (in um)", new Float(pixY));
+    addMeta("Z element length (in um)", new Float(pixZ));
+    addMeta("X axis angle", new Float(in.readFloat()));
+    addMeta("Y axis angle", new Float(in.readFloat()));
+    addMeta("Z axis angle", new Float(in.readFloat()));
+    addMeta("Column axis sequence", new Integer(in.readInt()));
+    addMeta("Row axis sequence", new Integer(in.readInt()));
+    addMeta("Section axis sequence", new Integer(in.readInt()));
+    Float wave1Min = new Float(in.readFloat());
+    addMeta("Wavelength 1 min. intensity", wave1Min);
+    Float wave1Max = new Float(in.readFloat());
+    addMeta("Wavelength 1 max. intensity", wave1Max);
+    addMeta("Wavelength 1 mean intensity", new Float(in.readFloat()));
+    addMeta("Space group number", new Integer(in.readInt()));
+
+    in.seek(132);
+    addMeta("Number of Sub-resolution sets", new Integer(in.readShort()));
+    addMeta("Z axis reduction quotient", new Integer(in.readShort()));
+    Float wave2Min = new Float(in.readFloat());
+    addMeta("Wavelength 2 min. intensity", wave2Min);
+    Float wave2Max = new Float(in.readFloat());
+    addMeta("Wavelength 2 max. intensity", wave2Max);
+
+    Float wave3Min = new Float(in.readFloat());
+    addMeta("Wavelength 3 min. intensity", wave3Min);
+
+    Float wave3Max = new Float(in.readFloat());
+    addMeta("Wavelength 3 max. intensity", wave3Max);
+
+    Float wave4Min = new Float(in.readFloat());
+    addMeta("Wavelength 4 min. intensity", wave4Min);
+
+    Float wave4Max = new Float(in.readFloat());
+    addMeta("Wavelength 4 max. intensity", wave4Max);
+
+    int type = in.readShort();
+    String imageType;
+    switch (type) {
+      case 0:
+        imageType = "normal";
+        break;
+      case 1:
+        imageType = "Tilt-series";
+        break;
+      case 2:
+        imageType = "Stereo tilt-series";
+        break;
+      case 3:
+        imageType = "Averaged images";
+        break;
+      case 4:
+        imageType = "Averaged stereo pairs";
+        break;
+      default:
+        imageType = "unknown";
+    }
+
+    addMeta("Image Type", imageType);
+    addMeta("Lens ID Number", new Integer(in.readShort()));
+
+    in.seek(172);
+    Float wave5Min = new Float(in.readFloat());
+    addMeta("Wavelength 5 min. intensity", wave5Min);
+
+    Float wave5Max = new Float(in.readFloat());
+    addMeta("Wavelength 5 max. intensity", wave5Max);
+
+    core.sizeT[0] = in.readShort();
+    addMeta("Number of timepoints", new Integer(core.sizeT[0]));
+
+    int sequence = in.readShort();
+    String imageSequence;
+    switch (sequence) {
+      case 0:
+        imageSequence = "ZTW"; core.currentOrder[0] = "XYZTC";
+        break;
+      case 1:
+        imageSequence = "WZT"; core.currentOrder[0] = "XYCZT";
+        break;
+      case 2:
+        imageSequence = "ZWT"; core.currentOrder[0] = "XYZCT";
+        break;
+      case 65536:
+        imageSequence = "WZT"; core.currentOrder[0] = "XYCZT";
+        break;
+      default:
+        imageSequence = "unknown"; core.currentOrder[0] = "XYZTC";
+    }
+    addMeta("Image sequence", imageSequence);
+
+    addMeta("X axis tilt angle", new Float(in.readFloat()));
+    addMeta("Y axis tilt angle", new Float(in.readFloat()));
+    addMeta("Z axis tilt angle", new Float(in.readFloat()));
+
+    core.sizeC[0] = in.readShort();
+    addMeta("Number of wavelengths", new Integer(core.sizeC[0]));
+    core.sizeZ[0] = core.imageCount[0] / (core.sizeC[0] * core.sizeT[0]);
+    addMeta("Number of focal planes", new Integer(core.sizeZ[0]));
+
+    core.rgb[0] = false;
+    core.interleaved[0] = false;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    short[] waves = new short[5];
+    for (int i=0; i<waves.length; i++) waves[i] = in.readShort();
+
+    addMeta("Wavelength 1 (in nm)", new Integer(waves[0]));
+    addMeta("Wavelength 2 (in nm)", new Integer(waves[1]));
+    addMeta("Wavelength 3 (in nm)", new Integer(waves[2]));
+    addMeta("Wavelength 4 (in nm)", new Integer(waves[3]));
+    addMeta("Wavelength 5 (in nm)", new Integer(waves[4]));
+    addMeta("X origin (in um)", new Float(in.readFloat()));
+    addMeta("Y origin (in um)", new Float(in.readFloat()));
+    addMeta("Z origin (in um)", new Float(in.readFloat()));
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    in.seek(224);
+
+    String title = null;
+    for (int i=1; i<=10; i++) {
+      // Make sure that "null" characters are stripped out
+      title = in.readString(80).replaceAll("\0", "");
+      addMeta("Title " + i, title);
+    }
+
+    // ----- The Extended Header data handler begins here ------
+
+    status("Reading extended header");
+
+    in.seek(128);
+    numIntsPerSection = in.readShort();
+    numFloatsPerSection = in.readShort();
+    setOffsetInfo(sequence, core.sizeZ[0], core.sizeC[0], core.sizeT[0]);
+    extHdrFields =
+      new DVExtHdrFields[core.sizeZ[0]][core.sizeC[0]][core.sizeT[0]];
+
+    FormatTools.populatePixels(store, this);
+
+    store.setDimensions(new Float(pixX), new Float(pixY), new Float(pixZ),
+      null, null, null);
+
+    if (title == null) title = "";
+    title = title.length() == 0 ? null : title;
+    store.setImage(id, null, title, null);
+
+    // Run through every timeslice, for each wavelength, for each z section
+    // and fill in the Extended Header information array for that image
+    for (int z=0; z<core.sizeZ[0]; z++) {
+      for (int t=0; t<core.sizeT[0]; t++) {
+        for (int w=0; w<core.sizeC[0]; w++) {
+          in.seek(HEADER_LENGTH);
+          extHdrFields[z][w][t] = new DVExtHdrFields(getTotalOffset(z, w, t),
+            numIntsPerSection, in, core.littleEndian[0]);
+
+          store.setPlaneInfo(z, w, t,
+            new Float(extHdrFields[z][w][t].getTimeStampSeconds()),
+            new Float(extHdrFields[z][w][t].getExpTime()), null);
+        }
+      }
+    }
+
+    status("Populating metadata");
+
+    for (int w=0; w<core.sizeC[0]; w++) {
+      store.setLogicalChannel(w, null, null, null, null, null, null, null, null,
+        null, null, null, null, "Monochrome", "Wide-field", null, null, null,
+        null, null, new Integer(waves[w]),
+        new Integer((int) extHdrFields[0][w][0].getExFilter()), null,
+        new Float(extHdrFields[0][w][0].getNdFilter()), null);
+    }
+
+    store.setStageLabel("ome",
+      new Float(extHdrFields[0][0][0].getStageXCoord()),
+      new Float(extHdrFields[0][0][0].getStageYCoord()),
+      new Float(extHdrFields[0][0][0].getStageZCoord()), null);
+
+    if (core.sizeC[0] > 0) {
+      store.setChannelGlobalMinMax(0, new Double(wave1Min.floatValue()),
+        new Double(wave1Max.floatValue()), null);
+    }
+    if (core.sizeC[0] > 1) {
+      store.setChannelGlobalMinMax(1, new Double(wave2Min.floatValue()),
+        new Double(wave2Max.floatValue()), null);
+    }
+    if (core.sizeC[0] > 2) {
+      store.setChannelGlobalMinMax(2, new Double(wave3Min.floatValue()),
+        new Double(wave3Max.floatValue()), null);
+    }
+    if (core.sizeC[0] > 3) {
+      store.setChannelGlobalMinMax(3, new Double(wave4Min.floatValue()),
+        new Double(wave4Max.floatValue()), null);
+    }
+    if (core.sizeC[0] > 4) {
+      store.setChannelGlobalMinMax(4, new Double(wave5Min.floatValue()),
+        new Double(wave5Max.floatValue()), null);
+    }
+
+    //store.setDefaultDisplaySettings(null);
+  }
+
+  // -- Helper methods --
+
+  /**
+   * This method calculates the size of a w, t, z section depending on which
+   * sequence is being used (either ZTW, WZT, or ZWT)
+   * @param imgSequence
+   * @param numZSections
+   * @param numWaves
+   * @param numTimes
+   */
+  private void setOffsetInfo(int imgSequence, int numZSections,
+    int numWaves, int numTimes)
+  {
+    int smallOffset = (numIntsPerSection + numFloatsPerSection) * 4;
+    switch (imgSequence) {
+      // ZTW sequence
+      case 0:
+        zSize = smallOffset;
+        tSize = zSize * numZSections;
+        wSize = tSize * numTimes;
+        break;
+
+      // WZT sequence
+      case 1:
+        wSize = smallOffset;
+        zSize = wSize * numWaves;
+        tSize = zSize * numZSections;
+        break;
+
+      // ZWT sequence
+      case 2:
+        zSize = smallOffset;
+        wSize = zSize * numZSections;
+        tSize = wSize * numWaves;
+        break;
+    }
+  }
+
+  /**
+   * Given any specific Z, W, and T of a plane, determine the totalOffset from
+   * the start of the extended header.
+   * @param currentZ
+   * @param currentW
+   * @param currentT
+   */
+  public int getTotalOffset(int currentZ, int currentW, int currentT) {
+    return (zSize * currentZ) + (wSize * currentW) + (tSize * currentT);
+  }
+
+  /**
+   * This method returns the a plane number from when given a Z, W
+   * and T offsets.
+   * @param currentZ
+   * @param currentW
+   * @param currentT
+   */
+  public int getPlaneNumber(int currentZ, int currentW, int currentT) {
+    int smallOffset = (numIntsPerSection + numFloatsPerSection) * 4;
+    return getTotalOffset(currentZ, currentW, currentT) / smallOffset;
+  }
+
+  // -- Helper classes --
+
+  /**
+   * This private class structure holds the details for the extended header
+   * @author Brian W. Loranger
+   */
+  private class DVExtHdrFields {
+
+    private int offsetWithInts;
+
+    private float oDFilter;
+
+    /** Photosensor reading. Typically in mV. */
+    private float photosensorReading;
+
+    /** Time stamp in seconds since the experiment began. */
+    private float timeStampSeconds;
+
+    /** X stage coordinates. */
+    private float stageXCoord;
+
+    /** Y stage coordinates. */
+    private float stageYCoord;
+
+    /** Z stage coordinates. */
+    private float stageZCoord;
+
+    /** Minimum intensity */
+    private float minInten;
+
+    /** Maxiumum intensity. */
+    private float maxInten;
+
+    /** Mean intesity. */
+    private float meanInten;
+
+    /** Exposure time in seconds. */
+    private float expTime;
+
+    /** Neutral density value. */
+    private float ndFilter;
+
+    /** Excitation filter number. */
+    private float exFilter;
+
+    /** Emiision filter number. */
+    private float emFilter;
+
+    /** Excitation filter wavelength. */
+    private float exWavelen;
+
+    /** Emission filter wavelength. */
+    private float emWavelen;
+
+    /** Intensity scaling factor. Usually 1. */
+    private float intenScaling;
+
+    /** Energy conversion factor. Usually 1. */
+    private float energyConvFactor;
+
+    /**
+     * Helper function which overrides toString, printing out the values in
+     * the header section.
+     */
+    public String toString() {
+      String s = new String();
+
+      s += "photosensorReading: " + photosensorReading + "\n";
+      s += "timeStampSeconds: " + timeStampSeconds + "\n";
+      s += "stageXCoord: " + stageXCoord + "\n";
+      s += "stageYCoord: " + stageYCoord + "\n";
+      s += "stageZCoord: " + stageZCoord + "\n";
+      s += "minInten: " + minInten + "\n";
+      s += "maxInten: " + maxInten + "\n";
+      s += "meanInten: " + meanInten + "\n";
+      s += "expTime: " + expTime + "\n";
+      s += "ndFilter: " + ndFilter + "\n";
+      s += "exFilter: " + exFilter + "\n";
+      s += "emFilter: " + emFilter + "\n";
+      s += "exWavelen: " + exWavelen + "\n";
+      s += "emWavelen: " + emWavelen + "\n";
+      s += "intenScaling: " + intenScaling + "\n";
+      s += "energyConvFactor: " + energyConvFactor + "\n";
+
+      return s;
+    }
+
+    /**
+     * Given the starting offset of a specific entry in the extended header
+     * this method will go through each element in the entry and fill each
+     * element's variable with its extended header value.
+     * @param startingOffset
+     * @param numIntsPerSection
+     * @param in
+     * @param little
+     */
+    protected DVExtHdrFields(int startingOffset, int numIntsPerSection,
+      RandomAccessStream in, boolean little)
+    {
+      try {
+        long fp = in.getFilePointer();
+        // skip over the int values that have nothing in them
+        offsetWithInts = startingOffset + (numIntsPerSection * 4);
+
+        // DV files store the ND (neuatral density) Filter
+        // (normally expressed as a %T (transmittance)) as an OD
+        // (optical density) rating.
+        // To convert from one to the other the formula is %T = 10^(-OD) X 100.
+        in.skipBytes(offsetWithInts + 36);
+        oDFilter = in.readFloat();
+
+        // fill in the extended header information for the floats
+        in.seek(fp + offsetWithInts);
+        photosensorReading = in.readFloat();
+        timeStampSeconds = in.readFloat();
+        stageXCoord = in.readFloat();
+        stageYCoord = in.readFloat();
+        stageZCoord = in.readFloat();
+        minInten = in.readFloat();
+        maxInten = in.readFloat();
+        meanInten = in.readFloat();
+        expTime = in.readFloat();
+        ndFilter = (float) Math.pow(10.0, -oDFilter);
+        in.skipBytes(4);
+
+        exFilter = in.readFloat();
+        emFilter = in.readFloat();
+        exWavelen = in.readFloat();
+        emWavelen = in.readFloat();
+        intenScaling = in.readFloat();
+        energyConvFactor = in.readFloat();
+      }
+      catch (IOException e) {
+        LogTools.trace(e);
+      }
+    }
+
+    /** Various getters for the Extended header fields. */
+    public float getPhotosensorReading() { return photosensorReading; }
+    public float getTimeStampSeconds() { return timeStampSeconds; }
+    public float getStageXCoord() { return stageXCoord; }
+    public float getStageYCoord() { return stageYCoord; }
+    public float getStageZCoord() { return stageZCoord; }
+    public float getMinInten() { return minInten; }
+    public float getMaxInten() { return maxInten; }
+    public float getMeanInten() { return meanInten; }
+    public float getExpTime() { return expTime; }
+    public float getNdFilter() { return ndFilter; }
+    public float getExFilter() { return exFilter; }
+    public float getEmFilter() { return emFilter; }
+    public float getExWavelen() { return exWavelen; }
+    public float getEmWavelen() { return emWavelen; }
+    public float getIntenScaling() { return intenScaling; }
+
+  }
+
+}
diff --git a/loci/formats/in/DicomReader.java b/loci/formats/in/DicomReader.java
new file mode 100644
index 0000000..83a8c18
--- /dev/null
+++ b/loci/formats/in/DicomReader.java
@@ -0,0 +1,1191 @@
+//
+// DicomReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import loci.formats.*;
+import loci.formats.codec.*;
+
+/**
+ * DicomReader is the file format reader for DICOM files.
+ * Much of this code is adapted from ImageJ's DICOM reader; see
+ * http://rsb.info.nih.gov/ij/developer/source/ij/plugin/DICOM.java.html
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/DicomReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/DicomReader.java">SVN</a></dd></dl>
+ */
+public class DicomReader extends FormatReader {
+
+  // -- Constants --
+
+  private static final Hashtable TYPES = buildTypes();
+
+  private static final int PIXEL_REPRESENTATION = 0x00280103;
+  private static final int TRANSFER_SYNTAX_UID = 0x00020010;
+  private static final int SLICE_SPACING = 0x00180088;
+  private static final int SAMPLES_PER_PIXEL = 0x00280002;
+  private static final int PHOTOMETRIC_INTERPRETATION = 0x00280004;
+  private static final int PLANAR_CONFIGURATION = 0x00280006;
+  private static final int NUMBER_OF_FRAMES = 0x00280008;
+  private static final int ROWS = 0x00280010;
+  private static final int COLUMNS = 0x00280011;
+  private static final int PIXEL_SPACING = 0x00280030;
+  private static final int BITS_ALLOCATED = 0x00280100;
+  private static final int WINDOW_CENTER = 0x00281050;
+  private static final int WINDOW_WIDTH = 0x00281051;
+  private static final int RESCALE_INTERCEPT = 0x00281052;
+  private static final int RESCALE_SLOPE = 0x00281053;
+  private static final int ICON_IMAGE_SEQUENCE = 0x00880200;
+  private static final int ITEM = 0xFFFEE000;
+  private static final int ITEM_DELIMINATION = 0xFFFEE00D;
+  private static final int SEQUENCE_DELIMINATION = 0xFFFEE0DD;
+  private static final int PIXEL_DATA = 0x7FE00010;
+
+  private static final int AE = 0x4145, AS = 0x4153, AT = 0x4154, CS = 0x4353;
+  private static final int DA = 0x4441, DS = 0x4453, DT = 0x4454, FD = 0x4644;
+  private static final int FL = 0x464C, IS = 0x4953, LO = 0x4C4F, LT = 0x4C54;
+  private static final int PN = 0x504E, SH = 0x5348, SL = 0x534C, SS = 0x5353;
+  private static final int ST = 0x5354, TM = 0x544D, UI = 0x5549, UL = 0x554C;
+  private static final int US = 0x5553, UT = 0x5554, OB = 0x4F42, OW = 0x4F57;
+  private static final int SQ = 0x5351, UN = 0x554E, QQ = 0x3F3F;
+
+  private static final int IMPLICIT_VR = 0x2d2d;
+
+  // -- Fields --
+
+  /** Bits per pixel. */
+  protected int bitsPerPixel;
+
+  /** Offset to first plane. */
+  protected int offsets;
+
+  private int location;
+  private int elementLength;
+  private int vr;
+  private boolean oddLocations;
+  private boolean inSequence;
+  private boolean bigEndianTransferSyntax;
+  private byte[][] lut;
+
+  private boolean isJPEG = false;
+  private boolean isRLE = false;
+
+  // -- Constructor --
+
+  /** Constructs a new DICOM reader. */
+  // "Digital Imaging and Communications in Medicine" is nasty long.
+  public DicomReader() {
+    super("Digital Img. & Comm. in Med.", new String[] {"dcm", "dicom"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() {
+    FormatTools.assertId(currentId, true, 1);
+    return lut;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int bytes = core.sizeX[0] * core.sizeY[0] *
+      FormatTools.getBytesPerPixel(core.pixelType[0]) *
+      (isIndexed() ? 1 : core.sizeC[0]);
+    in.seek(offsets + bytes * no);
+    if (isRLE) {
+      in.skipBytes(67);
+      while (in.read() == 0);
+      in.seek(in.getFilePointer() - 1);
+    }
+    in.read(buf);
+
+    if (isRLE) {
+      PackbitsCodec codec = new PackbitsCodec();
+      buf = codec.decompress(buf);
+
+      int b = FormatTools.getBytesPerPixel(core.pixelType[0]);
+      int plane = bytes / b;
+      if (b > 1) {
+        byte[][] tmp = new byte[b][plane];
+        for (int i=0; i<b; i++) {
+          System.arraycopy(buf, i*plane, tmp[i], 0, plane);
+        }
+        for (int i=0; i<plane; i++) {
+          for (int j=0; j<b; j++) {
+            buf[i*b + j] = core.littleEndian[0] ? tmp[b - j - 1][i] : tmp[j][i];
+          }
+        }
+      }
+
+      if (buf.length < plane * b) {
+        byte[] tmp = buf;
+        buf = new byte[plane * b];
+        System.arraycopy(tmp, 0, buf, 0, tmp.length);
+      }
+    }
+
+    if (isJPEG) {
+      JPEGCodec codec = new JPEGCodec();
+      buf = codec.decompress(buf);
+    }
+
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("DicomReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(true);
+
+    core.littleEndian[0] = true;
+    location = 0;
+
+    // some DICOM files have a 128 byte header followed by a 4 byte identifier
+
+    status("Verifying DICOM format");
+
+    in.seek(128);
+    if (in.readString(4).equals("DICM")) {
+      // header exists, so we'll read it
+      in.seek(0);
+      addMeta("Header information", in.readString(128));
+      in.readInt();
+      location = 128;
+    }
+    else in.seek(0);
+
+    status("Reading tags");
+
+    boolean decodingTags = true;
+
+    while (decodingTags) {
+      int tag = getNextTag();
+
+      if ((location & 1) != 0) oddLocations = true;
+      if (inSequence) {
+        addInfo(tag, null);
+        if (in.getFilePointer() >= (in.length() - 4)) {
+          decodingTags = false;
+        }
+        continue;
+      }
+
+      String s;
+      switch (tag) {
+        case TRANSFER_SYNTAX_UID:
+          s = in.readString(elementLength);
+          addInfo(tag, s);
+          if (s.startsWith("1.2.840.10008.1.2.4")) isJPEG = true;
+          else if (s.startsWith("1.2.840.10008.1.2.5")) isRLE = true;
+          else if (s.indexOf("1.2.4") > -1 || s.indexOf("1.2.5") > -1) {
+            throw new FormatException("Sorry, compressed DICOM images not " +
+              "supported");
+          }
+          if (s.indexOf("1.2.840.10008.1.2.2") >= 0) {
+            bigEndianTransferSyntax = true;
+          }
+          break;
+        case NUMBER_OF_FRAMES:
+          s = in.readString(elementLength);
+          addInfo(tag, s);
+          double frames = Double.parseDouble(s);
+          if (frames > 1.0) core.imageCount[0] = (int) frames;
+          break;
+        case SAMPLES_PER_PIXEL:
+          int samplesPerPixel = in.readShort();
+          addInfo(tag, samplesPerPixel);
+          break;
+        case PHOTOMETRIC_INTERPRETATION:
+          addInfo(tag, in.readString(elementLength));
+          break;
+        case PLANAR_CONFIGURATION:
+          addInfo(tag, in.readShort());
+          break;
+        case ROWS:
+          core.sizeY[0] = in.readShort();
+          addInfo(tag, core.sizeY[0]);
+          break;
+        case COLUMNS:
+          core.sizeX[0] = in.readShort();
+          addInfo(tag, core.sizeX[0]);
+          break;
+        case PIXEL_SPACING:
+          addInfo(tag, in.readString(elementLength));
+          break;
+        case SLICE_SPACING:
+          addInfo(tag, in.readString(elementLength));
+          break;
+        case BITS_ALLOCATED:
+          bitsPerPixel = in.readShort();
+          addInfo(tag, bitsPerPixel);
+          break;
+        case PIXEL_REPRESENTATION:
+          addInfo(tag, in.readShort());
+          break;
+        case WINDOW_CENTER:
+        case WINDOW_WIDTH:
+        case RESCALE_INTERCEPT:
+        case RESCALE_SLOPE:
+          addInfo(tag, in.readString(elementLength));
+          break;
+        case PIXEL_DATA:
+          if (elementLength != 0) {
+            offsets = (int) in.getFilePointer();
+            addInfo(tag, location);
+            decodingTags = false;
+          }
+          else addInfo(tag, null);
+          break;
+        case 0x7f880010:
+          if (elementLength != 0) {
+            offsets = location + 4;
+            decodingTags = false;
+          }
+          break;
+        default:
+          addInfo(tag, null);
+      }
+      if (in.getFilePointer() >= (in.length() - 4)) {
+        decodingTags = false;
+      }
+    }
+    if (core.imageCount[0] == 0) core.imageCount[0] = 1;
+
+    status("Populating metadata");
+
+    core.sizeZ[0] = core.imageCount[0];
+    if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYCZT";
+    core.interleaved[0] = !(isJPEG || isRLE);
+    core.metadataComplete[0] = true;
+    core.falseColor[0] = false;
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    while (bitsPerPixel % 8 != 0) bitsPerPixel++;
+    if (bitsPerPixel == 24 || bitsPerPixel == 48) bitsPerPixel /= 3;
+
+    switch (bitsPerPixel) {
+      case 8:
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 16:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      case 32:
+        core.pixelType[0] = FormatTools.UINT32;
+        break;
+    }
+
+    // populate OME-XML node
+    FormatTools.populatePixels(store, this);
+
+    String date = (String) getMeta("Content Date");
+    String time = (String) getMeta("Content Time");
+
+    String stamp = null;
+
+    if (date != null && time != null) {
+      stamp = date + " " + time;
+      SimpleDateFormat parse =
+        new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSSSSS");
+      Date d = parse.parse(stamp, new ParsePosition(0));
+      SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+      if (d != null) stamp = fmt.format(d);
+      else stamp = null;
+    }
+
+    if (stamp == null || stamp.trim().equals("")) stamp = null;
+
+    store.setImage(currentId, stamp, (String) getMeta("Image Type"), null);
+
+    store.setInstrument(
+      (String) getMeta("Manufacturer"),
+      (String) getMeta("Manufacturer's Model Name"),
+      null, null, null);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+       null, null, null, null, null, null, null, null, null, null, null, null,
+       null, null, null, null);
+    }
+  }
+
+  // -- Helper methods --
+
+  private void addInfo(int tag, String value) throws IOException {
+    long oldFp = in.getFilePointer();
+    String info = getHeaderInfo(tag, value);
+
+    if (inSequence && info != null && vr != SQ) info = ">" + info;
+    if (info != null && tag != ITEM) {
+      String key = (String) TYPES.get(new Integer(tag));
+      if (key == null) key = "" + tag;
+      if (key.equals("Samples per pixel")) {
+        core.sizeC[0] = Integer.parseInt(info.trim());
+        if (core.sizeC[0] > 1) core.rgb[0] = true;
+      }
+      else if (key.equals("Photometric Interpretation")) {
+        if (info.trim().equals("PALETTE COLOR")) {
+          core.indexed[0] = true;
+          core.sizeC[0] = 3;
+          core.rgb[0] = true;
+          lut = new byte[3][];
+        }
+      }
+      else if (key.indexOf("Palette Color LUT Data") != -1) {
+        String color = key.substring(0, key.indexOf(" ")).trim();
+        int ndx = color.equals("Red") ? 0 : color.equals("Green") ? 1 : 2;
+        long fp = in.getFilePointer();
+        in.seek(oldFp + ndx*ndx);
+        lut[ndx] = new byte[elementLength / 2];
+        for (int i=0; i<lut[ndx].length; i++) {
+          lut[ndx][i] = (byte) (in.read() & 0xff);
+          in.skipBytes(1);
+        }
+        in.seek(fp);
+      }
+
+      if (tag != PIXEL_DATA) addMeta(key, info);
+    }
+  }
+
+  private void addInfo(int tag, int value) throws IOException {
+    addInfo(tag, Integer.toString(value));
+  }
+
+  private String getHeaderInfo(int tag, String value) throws IOException {
+    if (tag == ITEM_DELIMINATION || tag == SEQUENCE_DELIMINATION) {
+      inSequence = false;
+    }
+
+    Integer key = new Integer(tag);
+    String id = (String) TYPES.get(key);
+
+    if (id != null) {
+      if (vr == IMPLICIT_VR && id != null) {
+        vr = (id.charAt(0) << 8) + id.charAt(1);
+      }
+      if (id.length() > 2) id = id.substring(2);
+    }
+
+    if (tag == ITEM) return id != null ? id : null;
+    if (value != null) return value;
+
+    boolean skip = false;
+    switch (vr) {
+      case AE:
+      case AS:
+      case AT:
+      case CS:
+      case DA:
+      case DS:
+      case DT:
+      case IS:
+      case LO:
+      case LT:
+      case PN:
+      case SH:
+      case ST:
+      case TM:
+      case UI:
+        value = in.readString(elementLength);
+        break;
+      case US:
+        if (elementLength == 2) value = Integer.toString(in.readShort());
+        else {
+          value = "";
+          int n = elementLength / 2;
+          for (int i=0; i<n; i++) {
+            value += Integer.toString(in.readShort()) + " ";
+          }
+        }
+        break;
+      case IMPLICIT_VR:
+        value = in.readString(elementLength);
+        if (elementLength <= 4 || elementLength > 44) value = null;
+        break;
+      case SQ:
+        value = "";
+        boolean privateTag = ((tag >> 16) & 1) != 0;
+        if (tag == ICON_IMAGE_SEQUENCE || privateTag) skip = true;
+        break;
+      default:
+        skip = true;
+    }
+    if (skip) {
+      long skipCount = (long) elementLength;
+      if (in.getFilePointer() + skipCount <= in.length()) {
+        in.skipBytes((int) skipCount);
+      }
+      location += elementLength;
+      value = "";
+    }
+
+    if (value != null && id == null && !value.equals("")) return value;
+    else if (id == null) return null;
+    else return value;
+  }
+
+  private int getLength() throws IOException {
+    byte[] b = new byte[4];
+    in.read(b);
+
+    // We cannot know whether the VR is implicit or explicit
+    // without the full DICOM Data Dictionary for public and
+    // private groups.
+
+    // We will assume the VR is explicit if the two bytes
+    // match the known codes. It is possible that these two
+    // bytes are part of a 32-bit length for an implicit VR.
+
+    vr = (b[0] << 8) + b[1];
+    switch (vr) {
+      case OB:
+      case OW:
+      case SQ:
+      case UN:
+        // Explicit VR with 32-bit length if other two bytes are zero
+        if ((b[2] == 0) || (b[3] == 0)) {
+          return in.readInt();
+        }
+        vr = IMPLICIT_VR;
+        if (core.littleEndian[0]) {
+          return (b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0];
+        }
+        return (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
+      case AE:
+      case AS:
+      case AT:
+      case CS:
+      case DA:
+      case DS:
+      case DT:
+      case FD:
+      case FL:
+      case IS:
+      case LO:
+      case LT:
+      case PN:
+      case SH:
+      case SL:
+      case SS:
+      case ST:
+      case TM:
+      case UI:
+      case UL:
+      case US:
+      case UT:
+      case QQ:
+        // Explicit VR with 16-bit length
+        if (core.littleEndian[0]) return (b[3] << 8) + b[2];
+        else return (b[2] << 8) + b[3];
+      default:
+        vr = IMPLICIT_VR;
+        if (core.littleEndian[0]) {
+          return (b[3] << 24) + (b[2] << 16) + (b[1] << 8) + b[0];
+        }
+        return (b[0] << 24) + (b[1] << 16) + (b[2] << 8) + b[3];
+    }
+  }
+
+  private int getNextTag() throws IOException {
+    int groupWord = in.readShort();
+    if (groupWord == 0x0800 && bigEndianTransferSyntax) {
+      core.littleEndian[0] = false;
+      groupWord = 0x0008;
+      in.order(false);
+    }
+
+    int elementWord = in.readShort();
+    int tag = ((groupWord << 16) & 0xffff0000) | (elementWord & 0xffff);
+
+    if (tag == PIXEL_DATA && (isRLE || isJPEG)) {
+      in.skipBytes(20);
+    }
+
+    elementLength = getLength();
+
+    // HACK - needed to read some GE files
+    // The element length must be even!
+    if (elementLength == 13 && !oddLocations) elementLength = 10;
+
+    // "Undefined" element length.
+    // This is a sort of bracket that encloses a sequence of elements.
+    if (elementLength == -1) {
+      elementLength = 0;
+      inSequence = true;
+    }
+    return tag;
+  }
+
+  // -- Utility methods --
+
+  /**
+   * Assemble the data dictionary.
+   * This is incomplete at best, since there are literally thousands of
+   * fields defined by the DICOM specifications.
+   */
+  private static Hashtable buildTypes() {
+    Hashtable dict = new Hashtable();
+
+    dict.put(new Integer(0x00020002), "Media Storage SOP Class UID");
+    dict.put(new Integer(0x00020003), "Media Storage SOP Instance UID");
+    dict.put(new Integer(0x00020010), "Transfer Syntax UID");
+    dict.put(new Integer(0x00020012), "Implementation Class UID");
+    dict.put(new Integer(0x00020013), "Implementation Version Name");
+    dict.put(new Integer(0x00020016), "Source Application Entity Title");
+    dict.put(new Integer(0x00080005), "Specific Character Set");
+    dict.put(new Integer(0x00080008), "Image Type");
+    dict.put(new Integer(0x00080010), "Recognition Code");
+    dict.put(new Integer(0x00080012), "Instance Creation Date");
+    dict.put(new Integer(0x00080013), "Instance Creation Time");
+    dict.put(new Integer(0x00080014), "Instance Creator UID");
+    dict.put(new Integer(0x00080016), "SOP Class UID");
+    dict.put(new Integer(0x00080018), "SOP Instance UID");
+    dict.put(new Integer(0x0008001a), "Related General SOP Class UID");
+    dict.put(new Integer(0x0008001b), "Original Specialized SOP Class UID");
+    dict.put(new Integer(0x00080020), "Study Date");
+    dict.put(new Integer(0x00080021), "Series Date");
+    dict.put(new Integer(0x00080022), "Acquisition Date");
+    dict.put(new Integer(0x00080023), "Content Date");
+    dict.put(new Integer(0x00080024), "Overlay Date");
+    dict.put(new Integer(0x00080025), "Curve Date");
+    dict.put(new Integer(0x0008002a), "Acquisition Date/Time");
+    dict.put(new Integer(0x00080030), "Study Time");
+    dict.put(new Integer(0x00080031), "Series Time");
+    dict.put(new Integer(0x00080032), "Acquisition Time");
+    dict.put(new Integer(0x00080033), "Content Time");
+    dict.put(new Integer(0x00080034), "Overlay Time");
+    dict.put(new Integer(0x00080035), "Curve Time");
+    dict.put(new Integer(0x00080041), "Data Set Subtype");
+    dict.put(new Integer(0x00080050), "Accession Number");
+    dict.put(new Integer(0x00080052), "Query/Retrieve Level");
+    dict.put(new Integer(0x00080054), "Retrieve AE Title");
+    dict.put(new Integer(0x00080056), "Instance Availability");
+    dict.put(new Integer(0x00080058), "Failed SOP Instance UID List");
+    dict.put(new Integer(0x00080060), "Modality");
+    dict.put(new Integer(0x00080061), "Modalities in Study");
+    dict.put(new Integer(0x00080062), "SOP Classes in Study");
+    dict.put(new Integer(0x00080064), "Conversion Type");
+    dict.put(new Integer(0x00080068), "Presentation Intent Type");
+    dict.put(new Integer(0x00080070), "Manufacturer");
+    dict.put(new Integer(0x00080080), "Institution Name");
+    dict.put(new Integer(0x00080081), "Institution Address");
+    dict.put(new Integer(0x00080082), "Institution Code Sequence");
+    dict.put(new Integer(0x00080090), "Referring Physician's Name");
+    dict.put(new Integer(0x00080092), "Referring Physician's Address");
+    dict.put(new Integer(0x00080094), "Referring Physician's Telephone");
+    dict.put(new Integer(0x00080096), "Referring Physician ID");
+    dict.put(new Integer(0x00080100), "Code Value");
+    dict.put(new Integer(0x00080102), "Coding Scheme Designator");
+    dict.put(new Integer(0x00080103), "Coding Scheme Version");
+    dict.put(new Integer(0x00080104), "Code Meaning");
+    dict.put(new Integer(0x00080105), "Mapping Resource");
+    dict.put(new Integer(0x00080106), "Context Group Version");
+    dict.put(new Integer(0x00080107), "Context Group Local Version");
+    dict.put(new Integer(0x0008010b), "Context Group Extension Flag");
+    dict.put(new Integer(0x0008010c), "Coding Scheme UID");
+    dict.put(new Integer(0x0008010d), "Context Group Extension Creator UID");
+    dict.put(new Integer(0x0008010f), "Context ID");
+    dict.put(new Integer(0x00080110), "Coding Scheme ID");
+    dict.put(new Integer(0x00080112), "Coding Scheme Registry");
+    dict.put(new Integer(0x00080114), "Coding Scheme External ID");
+    dict.put(new Integer(0x00080115), "Coding Scheme Name");
+    dict.put(new Integer(0x00080116), "Responsible Organization");
+    dict.put(new Integer(0x00080201), "Timezone Offset from UTC");
+    dict.put(new Integer(0x00081010), "Station Name");
+    dict.put(new Integer(0x00081030), "Study Description");
+    dict.put(new Integer(0x00081032), "Procedure Code Sequence");
+    dict.put(new Integer(0x0008103e), "Series Description");
+    dict.put(new Integer(0x00081040), "Institutional Department Name");
+    dict.put(new Integer(0x00081048), "Physician(s) of Record");
+    dict.put(new Integer(0x00081049), "Physician(s) of Record ID");
+    dict.put(new Integer(0x00081050), "Performing Physician's Name");
+    dict.put(new Integer(0x00081052), "Performing Physican ID");
+    dict.put(new Integer(0x00081060), "Name of Physician(s) Reading Study");
+    dict.put(new Integer(0x00081062), "Physician(s) Reading Study ID");
+    dict.put(new Integer(0x00081070), "Operator's Name");
+    dict.put(new Integer(0x00081072), "Operator ID");
+    dict.put(new Integer(0x00081080), "Admitting Diagnoses Description");
+    dict.put(new Integer(0x00081084), "Admitting Diagnoses Code Sequence");
+    dict.put(new Integer(0x00081090), "Manufacturer's Model Name");
+    dict.put(new Integer(0x00081100), "Referenced Results Sequence");
+    dict.put(new Integer(0x00081110), "Referenced Study Sequence");
+    dict.put(new Integer(0x00081111), "Referenced Performed Procedure Step");
+    dict.put(new Integer(0x00081115), "Referenced Series Sequence");
+    dict.put(new Integer(0x00081120), "Referenced Patient Sequence");
+    dict.put(new Integer(0x00081125), "Referenced Visit Sequence");
+    dict.put(new Integer(0x00081130), "Referenced Overlay Sequence");
+    dict.put(new Integer(0x0008113a), "Referenced Waveform Sequence");
+    dict.put(new Integer(0x00081140), "Referenced Image Sequence");
+    dict.put(new Integer(0x00081145), "Referenced Curve Sequence");
+    dict.put(new Integer(0x0008114a), "Referenced Instance Sequence");
+    dict.put(new Integer(0x00081150), "Referenced SOP Class UID");
+    dict.put(new Integer(0x00081155), "Referenced SOP Instance UID");
+    dict.put(new Integer(0x0008115a), "SOP Classes Supported");
+    dict.put(new Integer(0x00081160), "Referenced Frame Number");
+    dict.put(new Integer(0x00081195), "Transaction UID");
+    dict.put(new Integer(0x00081197), "Failure Reason");
+    dict.put(new Integer(0x00081198), "Failed SOP Sequence");
+    dict.put(new Integer(0x00081199), "Referenced SOP Sequence");
+    dict.put(new Integer(0x00081200),
+      "Studies Containing Other Referenced Instances Sequence");
+    dict.put(new Integer(0x00081250), "Related Series Sequence");
+    dict.put(new Integer(0x00082111), "Derivation Description");
+    dict.put(new Integer(0x00082112), "Source Image Sequence");
+    dict.put(new Integer(0x00082120), "Stage Name");
+    dict.put(new Integer(0x00082122), "Stage Number");
+    dict.put(new Integer(0x00082124), "Number of Stages");
+    dict.put(new Integer(0x00082127), "View Name");
+    dict.put(new Integer(0x00082128), "View Number");
+    dict.put(new Integer(0x00082129), "Number of Event Timers");
+    dict.put(new Integer(0x0008212a), "Number of Views in Stage");
+    dict.put(new Integer(0x00082130), "Event Elapsed Time(s)");
+    dict.put(new Integer(0x00082132), "Event Timer Name(s)");
+    dict.put(new Integer(0x00082142), "Start Trim");
+    dict.put(new Integer(0x00082143), "Stop Trim");
+    dict.put(new Integer(0x00082144), "Recommended Display Frame Rate");
+    dict.put(new Integer(0x00082218), "Anatomic Region Sequence");
+    dict.put(new Integer(0x00082220), "Anatomic Region Modifier Sequence");
+    dict.put(new Integer(0x00082228), "Primary Anatomic Structure Sequence");
+    dict.put(new Integer(0x00082229), "Anatomic Structure Sequence");
+    dict.put(new Integer(0x00082230), "Primary Anatomic Structure Modifier");
+    dict.put(new Integer(0x00082240), "Transducer Position Sequence");
+    dict.put(new Integer(0x00082242), "Transducer Position Modifier Sequence");
+    dict.put(new Integer(0x00082244), "Transducer Orientation Sequence");
+    dict.put(new Integer(0x00082246), "Transducer Orientation Modifier");
+    dict.put(new Integer(0x00083001), "Alternate Representation Sequence");
+    dict.put(new Integer(0x00089007), "Frame Type");
+    dict.put(new Integer(0x00089092), "Referenced Image Evidence Sequence");
+    dict.put(new Integer(0x00089121), "Referenced Raw Data Sequence");
+    dict.put(new Integer(0x00089123), "Creator-Version UID");
+    dict.put(new Integer(0x00089124), "Derivation Image Sequence");
+    dict.put(new Integer(0x00089154), "Source Image Evidence Sequence");
+    dict.put(new Integer(0x00089205), "Pixel Representation");
+    dict.put(new Integer(0x00089206), "Volumetric Properties");
+    dict.put(new Integer(0x00089207), "Volume Based Calculation Technique");
+    dict.put(new Integer(0x00089208), "Complex Image Component");
+    dict.put(new Integer(0x00089209), "Acquisition Contrast");
+    dict.put(new Integer(0x00089215), "Derivation Code Sequence");
+    dict.put(new Integer(0x00089237),
+      "Reference Grayscale Presentation State");
+    dict.put(new Integer(0x00100010), "Patient's Name");
+    dict.put(new Integer(0x00100020), "Patient ID");
+    dict.put(new Integer(0x00100021), "Issuer of Patient ID");
+    dict.put(new Integer(0x00100030), "Patient's Birth Date");
+    dict.put(new Integer(0x00100032), "Patient's Birth Time");
+    dict.put(new Integer(0x00100040), "Patient's Sex");
+    dict.put(new Integer(0x00100050), "Patient's Insurance Plane Code");
+    dict.put(new Integer(0x00100101), "Patient's Primary Language Code");
+    dict.put(new Integer(0x00100102), "Patient's Primary Language Modifier");
+    dict.put(new Integer(0x00101000), "Other Patient IDs");
+    dict.put(new Integer(0x00101001), "Other Patient Names");
+    dict.put(new Integer(0x00101005), "Patient's Birth Name");
+    dict.put(new Integer(0x00101010), "Patient's Age");
+    dict.put(new Integer(0x00101020), "Patient's Size");
+    dict.put(new Integer(0x00101030), "Patient's Weight");
+    dict.put(new Integer(0x00101040), "Patient's Address");
+    dict.put(new Integer(0x00101060), "Patient's Mother's Birth Name");
+    dict.put(new Integer(0x00101080), "Military Rank");
+    dict.put(new Integer(0x00101081), "Branch of Service");
+    dict.put(new Integer(0x00101090), "Medical Record Locator");
+    dict.put(new Integer(0x00102000), "Medical Alerts");
+    dict.put(new Integer(0x00102110), "Contrast Allergies");
+    dict.put(new Integer(0x00102150), "Country of Residence");
+    dict.put(new Integer(0x00102152), "Region of Residence");
+    dict.put(new Integer(0x00102154), "Patient's Telephone Numbers");
+    dict.put(new Integer(0x00102160), "Ethnic Group");
+    dict.put(new Integer(0x00102180), "Occupation");
+    dict.put(new Integer(0x001021a0), "Smoking Status");
+    dict.put(new Integer(0x001021b0), "Additional Patient History");
+    dict.put(new Integer(0x001021c0), "Pregnancy Status");
+    dict.put(new Integer(0x001021d0), "Last Menstrual Date");
+    dict.put(new Integer(0x001021f0), "Patient's Religious Preference");
+    dict.put(new Integer(0x00104000), "Patient Comments");
+    dict.put(new Integer(0x00120010), "Clinical Trial Sponsor Name");
+    dict.put(new Integer(0x00120020), "Clinical Trial Protocol ID");
+    dict.put(new Integer(0x00120021), "Clinical Trial Protocol Name");
+    dict.put(new Integer(0x00120030), "Clinical Trial Site ID");
+    dict.put(new Integer(0x00120031), "Clinical Trial Site Name");
+    dict.put(new Integer(0x00120040), "Clinical Trial Subject ID");
+    dict.put(new Integer(0x00120042), "Clinical Trial Subject Reading ID");
+    dict.put(new Integer(0x00120050), "Clinical Trial Time Point ID");
+    dict.put(new Integer(0x00120051), "Clinical Trial Time Point Description");
+    dict.put(new Integer(0x00120060), "Clinical Trial Coordinating Center");
+    dict.put(new Integer(0x00180010), "Contrast/Bolus Agent");
+    dict.put(new Integer(0x00180012), "Contrast/Bolus Agent Sequence");
+    dict.put(new Integer(0x00180014), "Contrast/Bolus Admin. Route Sequence");
+    dict.put(new Integer(0x00180015), "Body Part Examined");
+    dict.put(new Integer(0x00180020), "Scanning Sequence");
+    dict.put(new Integer(0x00180021), "Sequence Variant");
+    dict.put(new Integer(0x00180022), "Scan Options");
+    dict.put(new Integer(0x00180023), "MR Acquisition Type");
+    dict.put(new Integer(0x00180024), "Sequence Name");
+    dict.put(new Integer(0x00180025), "Angio Flag");
+    dict.put(new Integer(0x00180026),
+      "Intervention Drug Information Sequence");
+    dict.put(new Integer(0x00180027), "Intervention Drug Stop Time");
+    dict.put(new Integer(0x00180028), "Intervention Drug Dose");
+    dict.put(new Integer(0x00180029), "Intervention Drug Sequence");
+    dict.put(new Integer(0x0018002a), "Additional Drug Sequence");
+    dict.put(new Integer(0x00180031), "Radiopharmaceutical");
+    dict.put(new Integer(0x00180034), "Intervention Drug Name");
+    dict.put(new Integer(0x00180035), "Intervention Drug Start Time");
+    dict.put(new Integer(0x00180036), "Intervention Sequence");
+    dict.put(new Integer(0x00180038), "Intervention Status");
+    dict.put(new Integer(0x0018003a), "Intervention Description");
+    dict.put(new Integer(0x00180040), "Cine Rate");
+    dict.put(new Integer(0x00180050), "Slice Thickness");
+    dict.put(new Integer(0x00180060), "KVP");
+    dict.put(new Integer(0x00180070), "Counts Accumulated");
+    dict.put(new Integer(0x00180071), "Acquisition Termination Condition");
+    dict.put(new Integer(0x00180072), "Effective Duration");
+    dict.put(new Integer(0x00180073), "Acquisition Start Condition");
+    dict.put(new Integer(0x00180074), "Acquisition Start Condition Data");
+    dict.put(new Integer(0x00180075),
+      "Acquisition Termination Condition Data");
+    dict.put(new Integer(0x00180080), "Repetition Time");
+    dict.put(new Integer(0x00180081), "Echo Time");
+    dict.put(new Integer(0x00180082), "Inversion Time");
+    dict.put(new Integer(0x00180083), "Number of Averages");
+    dict.put(new Integer(0x00180084), "Imaging Frequency");
+    dict.put(new Integer(0x00180085), "Imaged Nucleus");
+    dict.put(new Integer(0x00180086), "Echo Number(s)");
+    dict.put(new Integer(0x00180087), "Magnetic Field Strength");
+    dict.put(new Integer(0x00180088), "Spacing Between Slices");
+    dict.put(new Integer(0x00180089), "Number of Phase Encoding Steps");
+    dict.put(new Integer(0x00180090), "Data Collection Diameter");
+    dict.put(new Integer(0x00180091), "Echo Train Length");
+    dict.put(new Integer(0x00180093), "Percent Sampling");
+    dict.put(new Integer(0x00180094), "Percent Phase Field of View");
+    dict.put(new Integer(0x00180095), "Pixel Bandwidth");
+    dict.put(new Integer(0x00181000), "Device Serial Number");
+    dict.put(new Integer(0x00181004), "Plate ID");
+    dict.put(new Integer(0x00181010), "Secondary Capture Device ID");
+    dict.put(new Integer(0x00181011), "Hardcopy Creation Device ID");
+    dict.put(new Integer(0x00181012), "Date of Secondary Capture");
+    dict.put(new Integer(0x00181014), "Time of Secondary Capture");
+    dict.put(new Integer(0x00181016), "Secondary Capture Device Manufacturer");
+    dict.put(new Integer(0x00181017), "Hardcopy Device Manufacturer");
+    dict.put(new Integer(0x00181018), "Secondary Capture Device Model Name");
+    dict.put(new Integer(0x00181019),
+      "Secondary Capture Device Software Version");
+    dict.put(new Integer(0x0018101a), "Hardcopy Device Software Version");
+    dict.put(new Integer(0x0018101b), "Hardcopy Device Model Name");
+    dict.put(new Integer(0x00181020), "Software Version(s)");
+    dict.put(new Integer(0x00181022), "Video Image Format Acquired");
+    dict.put(new Integer(0x00181023), "Digital Image Format Acquired");
+    dict.put(new Integer(0x00181030), "Protocol Name");
+    dict.put(new Integer(0x00181040), "Contrast/Bolus Route");
+    dict.put(new Integer(0x00181041), "Contrast/Bolus Volume");
+    dict.put(new Integer(0x00181042), "Contrast/Bolus Start Time");
+    dict.put(new Integer(0x00181043), "Contrast/Bolus Stop Time");
+    dict.put(new Integer(0x00181044), "Contrast/Bolus Total Dose");
+    dict.put(new Integer(0x00181045), "Syringe Counts");
+    dict.put(new Integer(0x00181046), "Contrast Flow Rate");
+    dict.put(new Integer(0x00181047), "Contrast Flow Duration");
+    dict.put(new Integer(0x00181048), "Contrast/Bolus Ingredient");
+    dict.put(new Integer(0x00181049), "Contrast Ingredient Concentration");
+    dict.put(new Integer(0x00181050), "Spatial Resolution");
+    dict.put(new Integer(0x00181060), "Trigger Time");
+    dict.put(new Integer(0x00181061), "Trigger Source or Type");
+    dict.put(new Integer(0x00181062), "Nominal Interval");
+    dict.put(new Integer(0x00181063), "Frame Time");
+    dict.put(new Integer(0x00181064), "Framing Type");
+    dict.put(new Integer(0x00181065), "Frame Time Vector");
+    dict.put(new Integer(0x00181066), "Frame Delay");
+    dict.put(new Integer(0x00181067), "Image Trigger Delay");
+    dict.put(new Integer(0x00181068), "Multiplex Group Time Offset");
+    dict.put(new Integer(0x00181069), "Trigger Time Offset");
+    dict.put(new Integer(0x0018106a), "Synchronization Trigger");
+    dict.put(new Integer(0x0018106c), "Synchronization Channel");
+    dict.put(new Integer(0x0018106e), "Trigger Sample Position");
+    dict.put(new Integer(0x00181070), "Radiopharmaceutical Route");
+    dict.put(new Integer(0x00181071), "Radiopharmaceutical Volume");
+    dict.put(new Integer(0x00181072), "Radiopharmaceutical Start Time");
+    dict.put(new Integer(0x00181073), "Radiopharmaceutical Stop Time");
+    dict.put(new Integer(0x00181074), "Radionuclide Total Dose");
+    dict.put(new Integer(0x00181075), "Radionuclide Half Life");
+    dict.put(new Integer(0x00181076), "Radionuclide Positron Fraction");
+    dict.put(new Integer(0x00181077), "Radiopharmaceutical Specific Activity");
+    dict.put(new Integer(0x00181080), "Beat Rejection Flag");
+    dict.put(new Integer(0x00181081), "Low R-R Value");
+    dict.put(new Integer(0x00181082), "High R-R Value");
+    dict.put(new Integer(0x00181083), "Intervals Acquired");
+    dict.put(new Integer(0x00181084), "Intervals Rejected");
+    dict.put(new Integer(0x00181085), "PVC Rejection");
+    dict.put(new Integer(0x00181086), "Skip Beats");
+    dict.put(new Integer(0x00181088), "Heart Rate");
+    dict.put(new Integer(0x00181090), "Cardiac Number of Images");
+    dict.put(new Integer(0x00181094), "Trigger Window");
+    dict.put(new Integer(0x00181100), "Reconstruction Diameter");
+    dict.put(new Integer(0x00181110), "Distance Source to Detector");
+    dict.put(new Integer(0x00181111), "Distance Source to Patient");
+    dict.put(new Integer(0x00181114), "Estimated Radiographic Mag. Factor");
+    dict.put(new Integer(0x00181120), "Gantry/Detector Tilt");
+    dict.put(new Integer(0x00181121), "Gantry/Detector Skew");
+    dict.put(new Integer(0x00181130), "Table Height");
+    dict.put(new Integer(0x00181131), "Table Traverse");
+    dict.put(new Integer(0x00181134), "Table Motion");
+    dict.put(new Integer(0x00181135), "Table Vertical Increment");
+    dict.put(new Integer(0x00181136), "Table Lateral Increment");
+    dict.put(new Integer(0x00181137), "Table Longitudinal Increment");
+    dict.put(new Integer(0x00181138), "Table Angle");
+    dict.put(new Integer(0x0018113a), "Table Type");
+    dict.put(new Integer(0x00181140), "Rotation Direction");
+    dict.put(new Integer(0x00181141), "Angular Position");
+    dict.put(new Integer(0x00181142), "Radial Position");
+    dict.put(new Integer(0x00181143), "Scan Arc");
+    dict.put(new Integer(0x00181144), "Angular Step");
+    dict.put(new Integer(0x00181145), "Center of Rotation Offset");
+    dict.put(new Integer(0x00181147), "Field of View Shape");
+    dict.put(new Integer(0x00181149), "Field of View Dimension(s)");
+    dict.put(new Integer(0x00181150), "Exposure Time");
+    dict.put(new Integer(0x00181151), "X-ray Tube Current");
+    dict.put(new Integer(0x00181152), "Exposure");
+    dict.put(new Integer(0x00181153), "Exposure in uAs");
+    dict.put(new Integer(0x00181154), "Average Pulse Width");
+    dict.put(new Integer(0x00181155), "Radiation Setting");
+    dict.put(new Integer(0x00181156), "Rectification Type");
+    dict.put(new Integer(0x0018115a), "Radiation Mode");
+    dict.put(new Integer(0x0018115e), "Image Area Dose Product");
+    dict.put(new Integer(0x00181160), "Filter Type");
+    dict.put(new Integer(0x00181161), "Type of Filters");
+    dict.put(new Integer(0x00181162), "Intensifier Size");
+    dict.put(new Integer(0x00181164), "Imager Pixel Spacing");
+    dict.put(new Integer(0x00181166), "Grid");
+    dict.put(new Integer(0x00181170), "Generator Power");
+    dict.put(new Integer(0x00181180), "Collimator/Grid Name");
+    dict.put(new Integer(0x00181181), "Collimator Type");
+    dict.put(new Integer(0x00181182), "Focal Distance");
+    dict.put(new Integer(0x00181183), "X Focus Center");
+    dict.put(new Integer(0x00181184), "Y Focus Center");
+    dict.put(new Integer(0x00181190), "Focal Spot(s)");
+    dict.put(new Integer(0x00181191), "Anode Target Material");
+    dict.put(new Integer(0x001811a0), "Body Part Thickness");
+    dict.put(new Integer(0x001811a2), "Compression Force");
+    dict.put(new Integer(0x00181200), "Date of Last Calibration");
+    dict.put(new Integer(0x00181201), "Time of Last Calibration");
+    dict.put(new Integer(0x00181210), "Convolution Kernel");
+    dict.put(new Integer(0x00181242), "Actual Frame Duration");
+    dict.put(new Integer(0x00181243), "Count Rate");
+    dict.put(new Integer(0x00181244), "Preferred Playback Sequencing");
+    dict.put(new Integer(0x00181250), "Receive Coil Name");
+    dict.put(new Integer(0x00181251), "Transmit Coil Name");
+    dict.put(new Integer(0x00181260), "Plate Type");
+    dict.put(new Integer(0x00181261), "Phosphor Type");
+    dict.put(new Integer(0x00181300), "Scan Velocity");
+    dict.put(new Integer(0x00181301), "Whole Body Technique");
+    dict.put(new Integer(0x00181302), "Scan Length");
+    dict.put(new Integer(0x00181310), "Acquisition Matrix");
+    dict.put(new Integer(0x00181312), "In-plane Phase Encoding Direction");
+    dict.put(new Integer(0x00181314), "Flip Angle");
+    dict.put(new Integer(0x00181315), "Variable Flip Angle Flag");
+    dict.put(new Integer(0x00181316), "SAR");
+    dict.put(new Integer(0x00181318), "dB/dt");
+    dict.put(new Integer(0x00181400), "Acquisition Device Processing Descr.");
+    dict.put(new Integer(0x00181401), "Acquisition Device Processing Code");
+    dict.put(new Integer(0x00181402), "Cassette Orientation");
+    dict.put(new Integer(0x00181403), "Cassette Size");
+    dict.put(new Integer(0x00181404), "Exposures on Plate");
+    dict.put(new Integer(0x00181405), "Relative X-ray Exposure");
+    dict.put(new Integer(0x00181450), "Column Angulation");
+    dict.put(new Integer(0x00181460), "Tomo Layer Height");
+    dict.put(new Integer(0x00181470), "Tomo Angle");
+    dict.put(new Integer(0x00181480), "Tomo Time");
+    dict.put(new Integer(0x00181490), "Tomo Type");
+    dict.put(new Integer(0x00181491), "Tomo Class");
+    dict.put(new Integer(0x00181495), "Number of Tomosynthesis Source Images");
+    dict.put(new Integer(0x00181500), "Positioner Motion");
+    dict.put(new Integer(0x00181508), "Positioner Type");
+    dict.put(new Integer(0x00181510), "Positioner Primary Angle");
+    dict.put(new Integer(0x00181511), "Positioner Secondary Angle");
+    dict.put(new Integer(0x00181520), "Positioner Primary Angle Increment");
+    dict.put(new Integer(0x00181521), "Positioner Secondary Angle Increment");
+    dict.put(new Integer(0x00181530), "Detector Primary Angle");
+    dict.put(new Integer(0x00181531), "Detector Secondary Angle");
+    dict.put(new Integer(0x00181600), "Shutter Shape");
+    dict.put(new Integer(0x00181602), "Shutter Left Vertical Edge");
+    dict.put(new Integer(0x00181604), "Shutter Right Vertical Edge");
+    dict.put(new Integer(0x00181606), "Shutter Upper Horizontal Edge");
+    dict.put(new Integer(0x00181608), "Shutter Lower Horizontal Edge");
+    dict.put(new Integer(0x00181610), "Center of Circular Shutter");
+    dict.put(new Integer(0x00181612), "Radius of Circular Shutter");
+    dict.put(new Integer(0x00181620), "Vertices of the Polygonal Shutter");
+    dict.put(new Integer(0x00181622), "Shutter Presentation Value");
+    dict.put(new Integer(0x00181623), "Shutter Overlay Group");
+    dict.put(new Integer(0x00181700), "Collimator Shape");
+    dict.put(new Integer(0x00181702), "Collimator Left Vertical Edge");
+    dict.put(new Integer(0x00181704), "Collimator Right Vertical Edge");
+    dict.put(new Integer(0x00181706), "Collimator Upper Horizontal Edge");
+    dict.put(new Integer(0x00181708), "Collimator Lower Horizontal Edge");
+    dict.put(new Integer(0x00181710), "Center of Circular Collimator");
+    dict.put(new Integer(0x00181712), "Radius of Circular Collimator");
+    dict.put(new Integer(0x00181720), "Vertices of the polygonal Collimator");
+    dict.put(new Integer(0x00181800), "Acquisition Time Synchronized");
+    dict.put(new Integer(0x00181801), "Time Source");
+    dict.put(new Integer(0x00181802), "Time Distribution Protocol");
+    dict.put(new Integer(0x00181803), "NTP Source Address");
+    dict.put(new Integer(0x00182001), "Page Number Vector");
+    dict.put(new Integer(0x00182002), "Frame Label Vector");
+    dict.put(new Integer(0x00182003), "Frame Primary Angle Vector");
+    dict.put(new Integer(0x00182004), "Frame Secondary Angle Vector");
+    dict.put(new Integer(0x00182005), "Slice Location Vector");
+    dict.put(new Integer(0x00182006), "Display Window Label Vector");
+    dict.put(new Integer(0x00182010), "Nominal Scanned Pixel Spacing");
+    dict.put(new Integer(0x00182020), "Digitizing Device Transport Direction");
+    dict.put(new Integer(0x00182030), "Rotation of Scanned Film");
+    dict.put(new Integer(0x00183100), "IVUS Acquisition");
+    dict.put(new Integer(0x00183101), "IVUS Pullback Rate");
+    dict.put(new Integer(0x00183102), "IVUS Gated Rate");
+    dict.put(new Integer(0x00183103), "IVUS Pullback Start Frame Number");
+    dict.put(new Integer(0x00183104), "IVUS Pullback Stop Frame Number");
+    dict.put(new Integer(0x00183105), "Lesion Number");
+    dict.put(new Integer(0x00185000), "Output Power");
+    dict.put(new Integer(0x00185010), "Transducer Data");
+    dict.put(new Integer(0x00185012), "Focus Depth");
+    dict.put(new Integer(0x00185020), "Processing Function");
+    dict.put(new Integer(0x00185021), "Postprocessing Fuction");
+    dict.put(new Integer(0x00185022), "Mechanical Index");
+    dict.put(new Integer(0x00185024), "Bone Thermal Index");
+    dict.put(new Integer(0x00185026), "Cranial Thermal Index");
+    dict.put(new Integer(0x00185027), "Soft Tissue Thermal Index");
+    dict.put(new Integer(0x00185028), "Soft Tissue-focus Thermal Index");
+    dict.put(new Integer(0x00185029), "Soft Tissue-surface Thermal Index");
+    dict.put(new Integer(0x00185050), "Depth of scan field");
+    dict.put(new Integer(0x00185100), "Patient Position");
+    dict.put(new Integer(0x00185101), "View Position");
+    dict.put(new Integer(0x00185104), "Projection Eponymous Name Code");
+    dict.put(new Integer(0x00186000), "Sensitivity");
+    dict.put(new Integer(0x00186011), "Sequence of Ultrasound Regions");
+    dict.put(new Integer(0x00186012), "Region Spatial Format");
+    dict.put(new Integer(0x00186014), "Region Data Type");
+    dict.put(new Integer(0x00186016), "Region Flags");
+    dict.put(new Integer(0x00186018), "Region Location Min X0");
+    dict.put(new Integer(0x0018601a), "Region Location Min Y0");
+    dict.put(new Integer(0x0018601c), "Region Location Max X1");
+    dict.put(new Integer(0x0018601e), "Region Location Max Y1");
+    dict.put(new Integer(0x00186020), "Reference Pixel X0");
+    dict.put(new Integer(0x00186022), "Reference Pixel Y0");
+    dict.put(new Integer(0x00186024), "Physical Units X Direction");
+    dict.put(new Integer(0x00186026), "Physical Units Y Direction");
+    dict.put(new Integer(0x00186028), "Reference Pixel Physical Value X");
+    dict.put(new Integer(0x0018602a), "Reference Pixel Physical Value Y");
+    dict.put(new Integer(0x0018602c), "Physical Delta X");
+    dict.put(new Integer(0x0018602e), "Physical Delta Y");
+    dict.put(new Integer(0x00186030), "Transducer Frequency");
+    dict.put(new Integer(0x00186031), "Transducer Type");
+    dict.put(new Integer(0x00186032), "Pulse Repetition Frequency");
+    dict.put(new Integer(0x00186034), "Doppler Correction Angle");
+    dict.put(new Integer(0x00186036), "Steering Angle");
+    dict.put(new Integer(0x00186039), "Doppler Sample Volume X Position");
+    dict.put(new Integer(0x0018603b), "Doppler Sample Volume Y Position");
+    dict.put(new Integer(0x0018603d), "TM-Line Position X0");
+    dict.put(new Integer(0x0018603f), "TM-Line Position Y0");
+    dict.put(new Integer(0x00186041), "TM-Line Position X1");
+    dict.put(new Integer(0x00186043), "TM-Line Position Y1");
+    dict.put(new Integer(0x00186044), "Pixel Component Organization");
+    dict.put(new Integer(0x00186046), "Pixel Component Mask");
+    dict.put(new Integer(0x00186048), "Pixel Component Range Start");
+    dict.put(new Integer(0x0018604a), "Pixel Component Range Stop");
+    dict.put(new Integer(0x0018604c), "Pixel Component Physical Units");
+    dict.put(new Integer(0x0018604e), "Pixel Component Data Type");
+    dict.put(new Integer(0x00186050), "Number of Table Break Points");
+    dict.put(new Integer(0x00186052), "Table of X Break Points");
+    dict.put(new Integer(0x00186054), "Table of Y Break Points");
+    dict.put(new Integer(0x00186056), "Number of Table Entries");
+    dict.put(new Integer(0x00186058), "Table of Pixel Values");
+    dict.put(new Integer(0x0018605a), "Table of Parameter Values");
+    dict.put(new Integer(0x00186060), "R Wave Time Vector");
+    dict.put(new Integer(0x00187000), "Detector Conditions Nominal Flag");
+    dict.put(new Integer(0x00187001), "Detector Temperature");
+    dict.put(new Integer(0x00187004), "Detector Type");
+    dict.put(new Integer(0x00187005), "Detector Configuration");
+    dict.put(new Integer(0x00187006), "Detector Description");
+    dict.put(new Integer(0x00187008), "Detector Mode");
+    dict.put(new Integer(0x0018700a), "Detector ID");
+    dict.put(new Integer(0x0018700c), "Date of Last Detector Calibration");
+    dict.put(new Integer(0x0018700e), "Time of Last Detector Calibration");
+    dict.put(new Integer(0x00187012), "Detector Time Since Last Exposure");
+    dict.put(new Integer(0x00187014), "Detector Active Time");
+    dict.put(new Integer(0x00187016), "Detector Activation Offset");
+    dict.put(new Integer(0x0018701a), "Detector Binning");
+    dict.put(new Integer(0x00187020), "Detector Element Physical Size");
+    dict.put(new Integer(0x00187022), "Detector Element Spacing");
+    dict.put(new Integer(0x00187024), "Detector Active Shape");
+    dict.put(new Integer(0x00187026), "Detector Active Dimension(s)");
+    dict.put(new Integer(0x00187028), "Detector Active Origin");
+    dict.put(new Integer(0x0018702a), "Detector Manufacturer Name");
+    dict.put(new Integer(0x0018702b), "Detector Model Name");
+    dict.put(new Integer(0x00187030), "Field of View Origin");
+    dict.put(new Integer(0x00187032), "Field of View Rotation");
+    dict.put(new Integer(0x00187034), "Field of View Horizontal Flip");
+    dict.put(new Integer(0x00187040), "Grid Absorbing Material");
+    dict.put(new Integer(0x00187041), "Grid Spacing Material");
+    dict.put(new Integer(0x00187042), "Grid Thickness");
+    dict.put(new Integer(0x00187044), "Grid Pitch");
+    dict.put(new Integer(0x00187046), "Grid Aspect Ratio");
+    dict.put(new Integer(0x00187048), "Grid Period");
+    dict.put(new Integer(0x0018704c), "Grid Focal Distance");
+    dict.put(new Integer(0x00187050), "Filter Material");
+    dict.put(new Integer(0x00187052), "Filter Thickness Min");
+    dict.put(new Integer(0x00187054), "Filter Thickness Max");
+    dict.put(new Integer(0x00187060), "Exposure Control Mode");
+    dict.put(new Integer(0x0020000d), "Study Instance UID");
+    dict.put(new Integer(0x0020000e), "Series Instance UID");
+    dict.put(new Integer(0x00200011), "Series Number");
+    dict.put(new Integer(0x00200012), "Acquisition Number");
+    dict.put(new Integer(0x00200013), "Instance Number");
+    dict.put(new Integer(0x00200020), "Patient Orientation");
+    dict.put(new Integer(0x00200030), "Image Position");
+    dict.put(new Integer(0x00200032), "Image Position (Patient)");
+    dict.put(new Integer(0x00200037), "Image Orientation (Patient)");
+    dict.put(new Integer(0x00200050), "Location");
+    dict.put(new Integer(0x00200052), "Frame of Reference UID");
+    dict.put(new Integer(0x00200070), "Image Geometry Type");
+    dict.put(new Integer(0x00201001), "Acquisitions in Series");
+    dict.put(new Integer(0x00201020), "Reference");
+    dict.put(new Integer(0x00201041), "Slice Location");
+    // skipped a bunch of stuff here - not used
+    dict.put(new Integer(0x00280002), "Samples per pixel");
+    dict.put(new Integer(0x00280003), "Samples per pixel used");
+    dict.put(new Integer(0x00280004), "Photometric Interpretation");
+    dict.put(new Integer(0x00280006), "Planar Configuration");
+    dict.put(new Integer(0x00280008), "Number of frames");
+    dict.put(new Integer(0x00280009), "Frame Increment Pointer");
+    dict.put(new Integer(0x0028000a), "Frame Dimension Pointer");
+    dict.put(new Integer(0x00280010), "Rows");
+    dict.put(new Integer(0x00280011), "Columns");
+    dict.put(new Integer(0x00280012), "Planes");
+    dict.put(new Integer(0x00280014), "Ultrasound Color Data Present");
+    dict.put(new Integer(0x00280030), "Pixel Spacing");
+    dict.put(new Integer(0x00280031), "Zoom Factor");
+    dict.put(new Integer(0x00280032), "Zoom Center");
+    dict.put(new Integer(0x00280034), "Pixel Aspect Ratio");
+    dict.put(new Integer(0x00280051), "Corrected Image");
+    dict.put(new Integer(0x00280100), "Bits Allocated");
+    dict.put(new Integer(0x00280101), "Bits Stored");
+    dict.put(new Integer(0x00280102), "High Bit");
+    dict.put(new Integer(0x00280103), "Pixel Representation");
+    dict.put(new Integer(0x00280106), "Smallest Image Pixel Value");
+    dict.put(new Integer(0x00280107), "Largest Image Pixel Value");
+    dict.put(new Integer(0x00280108), "Smallest Pixel Value in Series");
+    dict.put(new Integer(0x00280109), "Largest Pixel Value in Series");
+    dict.put(new Integer(0x00280110), "Smallest Image Pixel Value in Plane");
+    dict.put(new Integer(0x00280111), "Largest Image Pixel Value in Plane");
+    dict.put(new Integer(0x00280120), "Pixel Padding Value");
+    dict.put(new Integer(0x00280300), "Quality Control Image");
+    dict.put(new Integer(0x00280301), "Burned in Annotation");
+    dict.put(new Integer(0x00281040), "Pixel Intensity Relationship");
+    dict.put(new Integer(0x00281041), "Pixel Intensity Relationship Sign");
+    dict.put(new Integer(0x00281050), "Window Center");
+    dict.put(new Integer(0x00281051), "Window Width");
+    dict.put(new Integer(0x00281052), "Rescale Intercept");
+    dict.put(new Integer(0x00281053), "Rescale Slope");
+    dict.put(new Integer(0x00281054), "Rescale Type");
+    dict.put(new Integer(0x00281055), "Window Center and Width Explanation");
+    dict.put(new Integer(0x00281090), "Recommended Viewing Mode");
+    dict.put(new Integer(0x00281101), "Red Palette Color LUT Descriptor");
+    dict.put(new Integer(0x00281102), "Green Palette Color LUT Descriptor");
+    dict.put(new Integer(0x00281103), "Blue Palette Color LUT Descriptor");
+    dict.put(new Integer(0x00281199), "Palette Color LUT UID");
+    dict.put(new Integer(0x00281201), "Red Palette Color LUT Data");
+    dict.put(new Integer(0x00281202), "Green Palette Color LUT Data");
+    dict.put(new Integer(0x00281203), "Blue Palette Color LUT Data");
+    dict.put(new Integer(0x00281221), "Segmented Red Palette Color LUT Data");
+    dict.put(new Integer(0x00281222),
+      "Segmented Green Palette Color LUT Data");
+    dict.put(new Integer(0x00281223), "Segmented Blue Palette Color LUT Data");
+    dict.put(new Integer(0x00281300), "Implant Present");
+    dict.put(new Integer(0x00281350), "Partial View");
+    dict.put(new Integer(0x00281351), "Partial View Description");
+    dict.put(new Integer(0x00282110), "Lossy Image Compression");
+    dict.put(new Integer(0x00282112), "Lossy Image Compression Ratio");
+    dict.put(new Integer(0x00282114), "Lossy Image Compression Method");
+    dict.put(new Integer(0x00283000), "Modality LUT Sequence");
+    dict.put(new Integer(0x00283002), "LUT Descriptor");
+    dict.put(new Integer(0x00283003), "LUT Explanation");
+    dict.put(new Integer(0x00283004), "Modality LUT Type");
+    dict.put(new Integer(0x00283006), "LUT Data");
+    dict.put(new Integer(0x00283010), "VOI LUT Sequence");
+    dict.put(new Integer(0x00283110), "Softcopy VOI LUT Sequence");
+    dict.put(new Integer(0x00285000), "Bi-Plane Acquisition Sequence");
+    dict.put(new Integer(0x00286010), "Representative Frame Number");
+    dict.put(new Integer(0x00286020), "Frame Numbers of Interest (FOI)");
+    dict.put(new Integer(0x00286022), "Frame(s) of Interest Description");
+    dict.put(new Integer(0x00286023), "Frame of Interest Type");
+    dict.put(new Integer(0x00286040), "R Wave Pointer");
+    dict.put(new Integer(0x00286100), "Mask Subtraction Sequence");
+    dict.put(new Integer(0x00286101), "Mask Operation");
+    dict.put(new Integer(0x00286102), "Applicable Frame Range");
+    dict.put(new Integer(0x00286110), "Mask Frame Numbers");
+    dict.put(new Integer(0x00286112), "Contrast Frame Averaging");
+    dict.put(new Integer(0x00286114), "Mask Sub-pixel Shift");
+    dict.put(new Integer(0x00286120), "TID Offset");
+    dict.put(new Integer(0x00286190), "Mask Operation Explanation");
+    dict.put(new Integer(0x00289001), "Data Point Rows");
+    dict.put(new Integer(0x00289002), "Data Point Columns");
+    dict.put(new Integer(0x00289003), "Signal Domain Columns");
+    dict.put(new Integer(0x00289108), "Data Representation");
+    dict.put(new Integer(0x00289110), "Pixel Measures Sequence");
+    dict.put(new Integer(0x00289132), "Frame VOI LUT Sequence");
+    dict.put(new Integer(0x00289145), "Pixel Value Transformation Sequence");
+    dict.put(new Integer(0x00289235), "Signal Domain Rows");
+    // skipping some more stuff
+    dict.put(new Integer(0x00540011), "Number of Energy Windows");
+    dict.put(new Integer(0x00540021), "Number of Detectors");
+    dict.put(new Integer(0x00540051), "Number of Rotations");
+    dict.put(new Integer(0x00540080), "Slice Vector");
+    dict.put(new Integer(0x00540081), "Number of Slices");
+    dict.put(new Integer(0x00540202), "Type of Detector Motion");
+    dict.put(new Integer(0x00540400), "Image ID");
+    dict.put(new Integer(0x20100100), "Border Density");
+
+    return dict;
+  }
+
+}
diff --git a/loci/formats/in/EPSReader.java b/loci/formats/in/EPSReader.java
new file mode 100644
index 0000000..cfff542
--- /dev/null
+++ b/loci/formats/in/EPSReader.java
@@ -0,0 +1,301 @@
+//
+// EPSReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import loci.formats.*;
+
+/**
+ * Reader is the file format reader for Encapsulated PostScript (EPS) files.
+ * Some regular PostScript files are also supported.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/EPSReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/EPSReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class EPSReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Bits per sample. */
+  private int bps;
+
+  /** Starting line of pixel data. */
+  private int start;
+
+  /** Flag indicating binary data. */
+  private boolean binary;
+
+  private boolean isTiff;
+  private Hashtable[] ifds;
+
+  // -- Constructor --
+
+  /** Constructs a new EPS reader. */
+  public EPSReader() {
+    super("Encapsulated PostScript", new String[] {"eps", "epsi", "ps"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    if (isTiff) {
+      long[] offsets = TiffTools.getStripOffsets(ifds[0]);
+      in.seek(offsets[0]);
+
+      int[] map = TiffTools.getIFDIntArray(ifds[0], TiffTools.COLOR_MAP, false);
+      if (map == null) {
+        in.read(buf);
+        return buf;
+      }
+
+      byte[] b = new byte[core.sizeX[0] * core.sizeY[0]];
+      for (int i=0; i<b.length; i++) {
+        b[i] = (byte) in.read();
+        in.read();
+      }
+
+      for (int i=0; i<b.length; i++) {
+        int ndx = b[i];
+        if (ndx < 0) ndx += 256;
+        for (int j=0; j<core.sizeC[0]; j++) {
+          buf[i*core.sizeC[0] + j] = (byte) map[ndx + j*256];
+        }
+      }
+
+      return buf;
+    }
+
+    RandomAccessStream ras = new RandomAccessStream(currentId);
+    int line = 0;
+
+    while (line <= start) {
+      ras.readLine();
+      line++;
+    }
+
+    if (binary) {
+      ras.read(buf, 0, buf.length);
+    }
+    else {
+      long pos = ras.getFilePointer();
+      ras.seek(pos);
+
+      char[] chars = new char[2];
+
+      for (int i=0; i<buf.length; i++) {
+        chars[0] = ras.readChar();
+        while (chars[0] == '\n') chars[0] = ras.readChar();
+        chars[1] = ras.readChar();
+        while (chars[1] == '\n') chars[1] = ras.readChar();
+        String s = new String(chars);
+        buf[i] = (byte) Integer.parseInt(s, 16);
+      }
+    }
+    ras.close();
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("EPSReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    status("Verifying EPS format");
+
+    String line = in.readLine();
+    if (!line.trim().startsWith("%!PS")) {
+      // read the TIFF preview
+
+      isTiff = true;
+
+      in.order(true);
+      in.seek(20);
+      int offset = in.readInt();
+      int len = in.readInt();
+
+      byte[] b = new byte[len];
+      in.seek(offset);
+      in.read(b);
+
+      in = new RandomAccessStream(b);
+      ifds = TiffTools.getIFDs(in);
+
+      core.sizeX[0] = (int) TiffTools.getImageWidth(ifds[0]);
+      core.sizeY[0] = (int) TiffTools.getImageLength(ifds[0]);
+      core.sizeZ[0] = 1;
+      core.sizeT[0] = 1;
+      core.sizeC[0] = TiffTools.getSamplesPerPixel(ifds[0]);
+      core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]);
+      core.interleaved[0] = true;
+      core.rgb[0] = core.sizeC[0] > 1;
+
+      bps = TiffTools.getBitsPerSample(ifds[0])[0];
+      switch (bps) {
+        case 16: core.pixelType[0] = FormatTools.UINT16; break;
+        case 32: core.pixelType[0] = FormatTools.UINT32; break;
+        default: core.pixelType[0] = FormatTools.UINT8;
+      }
+
+      core.imageCount[0] = 1;
+      core.currentOrder[0] = "XYCZT";
+      core.metadataComplete[0] = true;
+      core.indexed[0] = false;
+      core.falseColor[0] = false;
+
+      MetadataStore store = getMetadataStore();
+      store.setImage(currentId, null, null, null);
+
+      FormatTools.populatePixels(store, this);
+      for (int i=0; i<core.sizeC[0]; i++) {
+        store.setLogicalChannel(i, null, null, null, null, null, null, null,
+         null, null, null, null, null, null, null, null, null, null, null,
+         null, null, null, null, null, null);
+      }
+
+      return;
+    }
+
+    status("Finding image data");
+
+    binary = false;
+
+    String image = "image";
+    int lineNum = 1;
+
+    line = in.readLine();
+
+    while (line != null) {
+      if (line.trim().equals(image) || line.trim().endsWith(image)) {
+        if (line.trim().endsWith(image) && !line.trim().startsWith(image)) {
+          if (line.indexOf("colorimage") != -1) core.sizeC[0] = 3;
+          StringTokenizer t = new StringTokenizer(line, " ");
+          try {
+            core.sizeX[0] = Integer.parseInt(t.nextToken());
+            core.sizeY[0] = Integer.parseInt(t.nextToken());
+            bps = Integer.parseInt(t.nextToken());
+          }
+          catch (NumberFormatException exc) {
+            if (debug) trace(exc);
+            core.sizeC[0] = Integer.parseInt(t.nextToken());
+          }
+        }
+
+        start = lineNum;
+        break;
+      }
+      else if (line.startsWith("%%")) {
+        if (line.startsWith("%%BoundingBox:")) {
+          line = line.substring(14);
+          StringTokenizer t = new StringTokenizer(line, " ");
+          int originX = Integer.parseInt(t.nextToken());
+          int originY = Integer.parseInt(t.nextToken());
+          core.sizeX[0] = Integer.parseInt(t.nextToken()) - originX;
+          core.sizeY[0] = Integer.parseInt(t.nextToken()) - originY;
+
+          addMeta("X-coordinate of origin", new Integer(originX));
+          addMeta("Y-coordinate of origin", new Integer(originY));
+        }
+        else if (line.startsWith("%%BeginBinary")) {
+          binary = true;
+        }
+        else {
+          // parse key/value pairs
+
+          int ndx = line.indexOf(":");
+          if (ndx != -1) {
+            String key = line.substring(0, ndx);
+            String value = line.substring(ndx + 1);
+            addMeta(key, value);
+          }
+        }
+      }
+      else if (line.startsWith("%ImageData:")) {
+        line = line.substring(11);
+        StringTokenizer t = new StringTokenizer(line, " ");
+        core.sizeX[0] = Integer.parseInt(t.nextToken());
+        core.sizeY[0] = Integer.parseInt(t.nextToken());
+        bps = Integer.parseInt(t.nextToken());
+        core.sizeC[0] = Integer.parseInt(t.nextToken());
+        while (t.hasMoreTokens()) {
+          image = t.nextToken().trim();
+          if (image.length() > 1) {
+            image = image.substring(1, image.length() - 1);
+          }
+        }
+      }
+      lineNum++;
+      line = in.readLine();
+    }
+
+    status("Populating metadata");
+
+    if (bps == 0) bps = 8;
+
+    if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+
+    core.sizeZ[0] = 1;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYCZT";
+    core.pixelType[0] = FormatTools.UINT8;
+    core.rgb[0] = core.sizeC[0] == 3;
+    core.interleaved[0] = true;
+    core.littleEndian[0] = true;
+    core.imageCount[0] = 1;
+
+    // Populate metadata store
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+       null, null, null, null, null, null, null, null, null, null, null, null,
+       null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/FitsReader.java b/loci/formats/in/FitsReader.java
new file mode 100644
index 0000000..ab4b782
--- /dev/null
+++ b/loci/formats/in/FitsReader.java
@@ -0,0 +1,146 @@
+//
+// FitsReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * FitsReader is the file format reader for
+ * Flexible Image Transport System (FITS) images.
+ *
+ * Much of this code was adapted from ImageJ (http://rsb.info.nih.gov/ij).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/FitsReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/FitsReader.java">SVN</a></dd></dl>
+ */
+public class FitsReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Number of lines in the header. */
+  private int count;
+
+  // -- Constructor --
+
+  /** Constructs a new FitsReader. */
+  public FitsReader() { super("Flexible Image Transport System", "fits"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return true;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(2880 + 2880 * (((count * 80) - 1) / 2880));
+    int line = core.sizeX[0] * FormatTools.getBytesPerPixel(core.pixelType[0]);
+    for (int y=core.sizeY[0]-1; y>=0; y--) {
+      in.read(buf, y*line, line);
+    }
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    count = 1;
+
+    String line = in.readString(80);
+    if (!line.startsWith("SIMPLE")) {
+      throw new FormatException("Unsupported FITS file.");
+    }
+
+    while (true) {
+      count++;
+      line = in.readString(80);
+
+      // parse key/value pair
+      int ndx = line.indexOf("=");
+      int comment = line.indexOf("/", ndx);
+      if (comment < 0) comment = line.length();
+
+      String key = "", value = "";
+
+      if (ndx >= 0) {
+        key = line.substring(0, ndx).trim();
+        value = line.substring(ndx + 1, comment).trim();
+      }
+      else key = line.trim();
+
+      if (key.equals("END")) break;
+
+      if (key.equals("BITPIX")) {
+        int bits = Integer.parseInt(value);
+        switch (bits) {
+          case 8: core.pixelType[0] = FormatTools.UINT8; break;
+          case 16: core.pixelType[0] = FormatTools.UINT16; break;
+          case 32: core.pixelType[0] = FormatTools.UINT32; break;
+          case -32: core.pixelType[0] = FormatTools.FLOAT; break;
+          default: throw new FormatException("Unsupported pixel type: " + bits);
+        }
+      }
+      else if (key.equals("NAXIS1")) core.sizeX[0] = Integer.parseInt(value);
+      else if (key.equals("NAXIS2")) core.sizeY[0] = Integer.parseInt(value);
+      else if (key.equals("NAXIS3")) core.sizeZ[0] = Integer.parseInt(value);
+
+      addMeta(key, value);
+    }
+
+    core.sizeC[0] = 1;
+    core.sizeT[0] = 1;
+    if (core.sizeZ[0] == 0) core.sizeZ[0] = 1;
+    core.imageCount[0] = core.sizeZ[0];
+    core.rgb[0] = false;
+    core.littleEndian[0] = false;
+    core.interleaved[0] = false;
+    core.currentOrder[0] = "XYZCT";
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    store.setLogicalChannel(0, null, null, null, null, null, null, null, null,
+      null, null, null, null, null, null, null, null, null, null, null,
+      null, null, null, null, null);
+  }
+
+}
diff --git a/loci/formats/in/FlexReader.java b/loci/formats/in/FlexReader.java
new file mode 100644
index 0000000..1d0ad32
--- /dev/null
+++ b/loci/formats/in/FlexReader.java
@@ -0,0 +1,214 @@
+//
+// FlexReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Vector;
+import javax.xml.parsers.*;
+import loci.formats.*;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * FlexReader is a file format reader for Evotec Flex files.
+ * To use it, the LuraWave decoder library, lwf_jsdk2.6.jar, must be available,
+ * and a LuraWave license key must be specified in the lurawave.license system
+ * property (e.g., <code>-Dlurawave.license=XXXX</code> on the command line).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/FlexReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/FlexReader.java">SVN</a></dd></dl>
+ */
+public class FlexReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  /** Custom IFD entry for Flex XML. */
+  protected static final int FLEX = 65200;
+
+  /** Factory for generating SAX parsers. */
+  public static final SAXParserFactory SAX_FACTORY =
+    SAXParserFactory.newInstance();
+
+  // -- Fields --
+
+  /** Scale factor for each image. */
+  protected double[] factors;
+
+  // -- Constructor --
+
+  /** Constructs a new Flex reader. */
+  public FlexReader() { super("Evotec Flex", "flex"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) { return false; }
+
+  /* @see loci.formats.FormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+
+    // expand pixel values with multiplication by factor[no]
+    byte[] bytes = super.openBytes(no, buf);
+    if (core.pixelType[0] == FormatTools.UINT8) {
+      int num = bytes.length;
+      for (int i=num-1; i>=0; i--) {
+        int q = (int) ((bytes[i] & 0xff) * factors[no]);
+        bytes[i] = (byte) (q & 0xff);
+      }
+    }
+    if (core.pixelType[0] == FormatTools.UINT16) {
+      int num = bytes.length / 2;
+      for (int i=num-1; i>=0; i--) {
+        int q = (int) ((bytes[i] & 0xff) * factors[no]);
+        byte b0 = (byte) (q & 0xff);
+        byte b1 = (byte) ((q >> 8) & 0xff);
+        int ndx = 2 * i;
+        if (core.littleEndian[0]) {
+          bytes[ndx] = b0;
+          bytes[ndx + 1] = b1;
+        }
+        else {
+          bytes[ndx] = b1;
+          bytes[ndx + 1] = b0;
+        }
+      }
+    }
+    else if (core.pixelType[0] == FormatTools.UINT32) {
+      int num = bytes.length / 4;
+      for (int i=num-1; i>=0; i--) {
+        int q = (int) ((bytes[i] & 0xff) * factors[no]);
+        byte b0 = (byte) (q & 0xff);
+        byte b1 = (byte) ((q >> 8) & 0xff);
+        byte b2 = (byte) ((q >> 16) & 0xff);
+        byte b3 = (byte) ((q >> 24) & 0xff);
+        int ndx = 4 * i;
+        if (core.littleEndian[0]) {
+          bytes[ndx] = b0;
+          bytes[ndx + 1] = b1;
+          bytes[ndx + 2] = b2;
+          bytes[ndx + 3] = b3;
+        }
+        else {
+          bytes[ndx] = b3;
+          bytes[ndx + 1] = b2;
+          bytes[ndx + 2] = b1;
+          bytes[ndx + 3] = b0;
+        }
+      }
+    }
+    return bytes;
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see loci.formats.BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    core.orderCertain[0] = false;
+
+    // parse factors from XML
+    String xml = (String) TiffTools.getIFDValue(ifds[0],
+      FLEX, true, String.class);
+    Vector n = new Vector();
+    Vector f = new Vector();
+    FlexHandler handler = new FlexHandler(n, f);
+    try {
+      SAXParser saxParser = SAX_FACTORY.newSAXParser();
+      saxParser.parse(new ByteArrayInputStream(xml.getBytes()), handler);
+    }
+    catch (ParserConfigurationException exc) {
+      throw new FormatException(exc);
+    }
+    catch (SAXException exc) {
+      throw new FormatException(exc);
+    }
+
+    // verify factor count
+    int nsize = n.size();
+    int fsize = f.size();
+    if (debug && (nsize != fsize || nsize != core.imageCount[0])) {
+      LogTools.println("Warning: mismatch between image count, " +
+        "names and factors (count=" + core.imageCount[0] +
+        ", names=" + nsize + ", factors=" + fsize + ")");
+    }
+    for (int i=0; i<nsize; i++) addMeta("Name " + i, n.get(i));
+    for (int i=0; i<fsize; i++) addMeta("Factor " + i, f.get(i));
+
+    // parse factor values
+    factors = new double[core.imageCount[0]];
+    int max = 0;
+    for (int i=0; i<fsize; i++) {
+      String factor = (String) f.get(i);
+      double q = 1;
+      try {
+        q = Double.parseDouble(factor);
+      }
+      catch (NumberFormatException exc) {
+        if (debug) {
+          LogTools.println("Warning: invalid factor #" + i + ": " + factor);
+        }
+      }
+      factors[i] = q;
+      if (q > factors[max]) max = i;
+    }
+    Arrays.fill(factors, fsize, factors.length, 1);
+
+    // determine pixel type
+    if (factors[max] > 256) core.pixelType[0] = FormatTools.UINT32;
+    else if (factors[max] > 1) core.pixelType[0] = FormatTools.UINT16;
+    else core.pixelType[0] = FormatTools.UINT8;
+  }
+
+  // -- Helper classes --
+
+  /** SAX handler for parsing XML. */
+  public class FlexHandler extends DefaultHandler {
+    private Vector names, factors;
+    public FlexHandler(Vector names, Vector factors) {
+      this.names = names;
+      this.factors = factors;
+    }
+    public void startElement(String uri,
+      String localName, String qName, Attributes attributes)
+    {
+      if (!qName.equals("Array")) return;
+      int len = attributes.getLength();
+      for (int i=0; i<len; i++) {
+        String name = attributes.getQName(i);
+        if (name.equals("Name")) names.add(attributes.getValue(i));
+        else if (name.equals("Factor")) factors.add(attributes.getValue(i));
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/FluoviewReader.java b/loci/formats/in/FluoviewReader.java
new file mode 100644
index 0000000..85298b5
--- /dev/null
+++ b/loci/formats/in/FluoviewReader.java
@@ -0,0 +1,386 @@
+//
+// FluoviewReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * FluoviewReader is the file format reader for
+ * Olympus Fluoview TIFF files AND Andor Bio-imaging Division (ABD) TIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/FluoviewReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/FluoviewReader.java">SVN</a></dd></dl>
+ *
+ * @author Eric Kjellman egkjellman at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class FluoviewReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  /** Maximum number of bytes to check for Fluoview header information. */
+  private static final int BLOCK_CHECK_LEN = 16384;
+
+  /** String identifying a Fluoview file. */
+  private static final String FLUOVIEW_MAGIC_STRING = "FLUOVIEW";
+
+  /** Private TIFF tags */
+  private static final int MMHEADER = 34361;
+  private static final int MMSTAMP = 34362;
+
+  // -- Fields --
+
+  /** Pixel dimensions for this file. */
+  private float voxelX = 0f, voxelY = 0f, voxelZ = 0f, voxelC = 0f, voxelT = 0f;
+
+  /** First image. */
+  private BufferedImage zeroImage = null;
+
+  // -- Constructor --
+
+  /** Constructs a new Fluoview TIFF reader. */
+  public FluoviewReader() {
+    super("Olympus Fluoview/ABD TIFF", new String[] {"tif", "tiff"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (!TiffTools.isValidHeader(block)) return false;
+
+    if (block.length < 3) return false;
+    if (block.length < 8) return true;
+
+    String test = new String(block);
+    if (test.indexOf(FLUOVIEW_MAGIC_STRING) != -1) {
+      return true;
+    }
+
+    int ifdlocation = DataTools.bytesToInt(block, 4, true);
+    if (ifdlocation < 0 || ifdlocation + 1 > block.length) return false;
+    else {
+      int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, true);
+      for (int i=0; i<ifdnumber; i++) {
+        if (ifdlocation + 3 + (i*12) > block.length) return false;
+        else {
+          int ifdtag = DataTools.bytesToInt(block, ifdlocation + 2 + (i*12),
+            2, true);
+          if (ifdtag == MMHEADER || ifdtag == MMSTAMP) return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    if (core.sizeY[0] == TiffTools.getImageLength(ifds[0])) {
+      return super.openBytes(no);
+    }
+    return openBytes(no, new byte[core.sizeX[0] *
+      FormatTools.getBytesPerPixel(core.pixelType[0])]);
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    if (core.sizeY[0] == TiffTools.getImageLength(ifds[0])) {
+      return super.openBytes(no, buf);
+    }
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    byte[] b = new byte[core.sizeX[0] *
+      (int) TiffTools.getImageLength(ifds[0]) *
+      getRGBChannelCount() * FormatTools.getBytesPerPixel(core.pixelType[0])];
+    super.openBytes(0, b);
+    System.arraycopy(b, 0, buf, 0, buf.length);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    if (core.sizeY[0] == TiffTools.getImageLength(ifds[0])) {
+      return super.openImage(no);
+    }
+
+    if (zeroImage == null) zeroImage = super.openImage(0);
+    return zeroImage.getSubimage(0, no, core.sizeX[0], 1);
+  }
+
+  /* @see loci.formats.IFormatReader#close() */
+  public void close() throws IOException {
+    super.close();
+    zeroImage = null;
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (!super.isThisType(name, open)) return false; // check extension
+
+    // just checking the filename isn't enough to differentiate between
+    // Fluoview and regular TIFF; open the file and check more thoroughly
+    return open ? checkBytes(name, BLOCK_CHECK_LEN) : true;
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see loci.formats.BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    // First, we want to determine whether this file is a Fluoview TIFF.
+    // Originally, Andor TIFF had its own reader; however, the two formats are
+    // very similar, so it made more sense to merge the two formats into one
+    // reader.
+
+    byte[] buf = new byte[BLOCK_CHECK_LEN];
+    in.seek(0);
+    in.read(buf);
+
+    short[] s = TiffTools.getIFDShortArray(ifds[0], MMHEADER, true);
+    byte[] mmheader = new byte[s.length];
+    for (int i=0; i<mmheader.length; i++) {
+      mmheader[i] = (byte) s[i];
+      if (mmheader[i] < 0) mmheader[i]++;
+    }
+
+    RandomAccessStream ras = new RandomAccessStream(mmheader);
+    ras.order(isLittleEndian());
+
+    put("Header Flag", ras.readShort());
+    put("Image Type", ras.readChar());
+
+    put("Image name", ras.readString(257));
+
+    ras.skipBytes(4); // skip pointer to data field
+
+    put("Number of colors", ras.readInt());
+    ras.skipBytes(4); // skip pointer to palette field
+    ras.skipBytes(4); // skip pointer to other palette field
+
+    put("Comment size", ras.readInt());
+    ras.skipBytes(4); // skip pointer to comment field
+
+    // read dimension information
+    String[] names = new String[10];
+    int[] sizes = new int[10];
+    double[] resolutions = new double[10];
+    for (int i=0; i<10; i++) {
+      names[i] = ras.readString(16);
+      sizes[i] = ras.readInt();
+      double origin = ras.readDouble();
+      resolutions[i] = ras.readDouble();
+
+      put("Dimension " + (i+1) + " Name", names[i]);
+      put("Dimension " + (i+1) + " Size", sizes[i]);
+      put("Dimension " + (i+1) + " Origin", origin);
+      put("Dimension " + (i+1) + " Resolution", resolutions[i]);
+      put("Dimension " + (i+1) + " Units", ras.readString(64));
+    }
+
+    ras.skipBytes(4); // skip pointer to spatial position data
+
+    put("Map type", ras.readShort());
+    put("Map min", ras.readDouble());
+    put("Map max", ras.readDouble());
+    put("Min value", ras.readDouble());
+    put("Max value", ras.readDouble());
+
+    ras.skipBytes(4); // skip pointer to map data
+
+    put("Gamma", ras.readDouble());
+    put("Offset", ras.readDouble());
+
+    // read gray channel data
+    put("Gray Channel Name", ras.readString(16));
+    put("Gray Channel Size", ras.readInt());
+    put("Gray Channel Origin", ras.readDouble());
+    put("Gray Channel Resolution", ras.readDouble());
+    put("Gray Channel Units", ras.readString(64));
+
+    ras.skipBytes(4); // skip pointer to thumbnail data
+
+    put("Voice field", ras.readInt());
+    ras.skipBytes(4); // skip pointer to voice field
+
+    // now we need to read the MMSTAMP data to determine dimension order
+
+    double[][] stamps = new double[8][ifds.length];
+    for (int i=0; i<ifds.length; i++) {
+      s = TiffTools.getIFDShortArray(ifds[i], MMSTAMP, true);
+      byte[] stamp = new byte[s.length];
+      for (int j=0; j<s.length; j++) {
+        stamp[j] = (byte) s[j];
+        if (stamp[j] < 0) stamp[j]++;
+      }
+      ras = new RandomAccessStream(stamp);
+
+      // each stamp is 8 doubles, representing the position on dimensions 3-10
+      for (int j=0; j<8; j++) {
+        stamps[j][i] = ras.readDouble();
+      }
+    }
+
+    // calculate the dimension order and axis sizes
+
+    core.sizeZ[0] = core.sizeC[0] = core.sizeT[0] = 1;
+    core.currentOrder[0] = "XY";
+    core.metadataComplete[0] = true;
+
+    for (int i=0; i<10; i++) {
+      String name = names[i];
+      int size = sizes[i];
+      float voxel = (float) resolutions[i];
+      if (name == null || size == 0) continue;
+      name = name.toLowerCase().trim();
+      if (name.length() == 0) continue;
+
+      if (name.equals("x")) {
+        if (core.sizeX[0] == 0) core.sizeX[0] = size;
+        voxelX = voxel;
+      }
+      else if (name.equals("y")) {
+        core.sizeY[0] = size;
+        voxelY = voxel;
+      }
+      else if (name.equals("z") || name.equals("event")) {
+        core.sizeZ[0] *= size;
+        if (core.currentOrder[0].indexOf("Z") == -1) {
+          core.currentOrder[0] += "Z";
+        }
+        voxelZ = voxel;
+      }
+      else if (name.equals("ch") || name.equals("wavelength")) {
+        core.sizeC[0] *= size;
+        if (core.currentOrder[0].indexOf("C") == -1) {
+          core.currentOrder[0] += "C";
+        }
+        voxelC = voxel;
+      }
+      else {
+        core.sizeT[0] *= size;
+        if (core.currentOrder[0].indexOf("T") == -1) {
+          core.currentOrder[0] += "T";
+        }
+        voxelT = voxel;
+      }
+    }
+
+    if (core.currentOrder[0].indexOf("Z") == -1) core.currentOrder[0] += "Z";
+    if (core.currentOrder[0].indexOf("T") == -1) core.currentOrder[0] += "T";
+    if (core.currentOrder[0].indexOf("C") == -1) core.currentOrder[0] += "C";
+
+    core.imageCount[0] = ifds.length;
+
+    if (core.imageCount[0] == 1 && (core.sizeT[0] == core.sizeY[0] ||
+      core.sizeZ[0] == core.sizeY[0]) && (core.sizeT[0] > core.imageCount[0] ||
+      core.sizeZ[0] > core.imageCount[0]))
+    {
+      core.sizeY[0] = 1;
+      core.imageCount[0] = core.sizeZ[0] * core.sizeT[0] * core.sizeC[0];
+    }
+
+    // cut up the comment, if necessary
+    String comment = (String) getMeta("Comment");
+
+    if (comment != null && comment.startsWith("[")) {
+      int start = comment.indexOf("[Acquisition Parameters]");
+      int end = comment.indexOf("[Acquisition Parameters End]");
+      if (start != -1 && end != -1 && end > start) {
+        String parms = comment.substring(start + 24, end).trim();
+
+        // this is an INI-style comment, with one key/value pair per line
+
+        StringTokenizer st = new StringTokenizer(parms, "\n");
+        while (st.hasMoreTokens()) {
+          String token = st.nextToken();
+          int eq = token.indexOf("=");
+          if (eq != -1) {
+            String key = token.substring(0, eq);
+            String value = token.substring(eq + 1);
+            addMeta(key, value);
+          }
+        }
+      }
+
+      start = comment.indexOf("[Version Info]");
+      end = comment.indexOf("[Version Info End]");
+      if (start != -1 && end != -1 && end > start) {
+        comment = comment.substring(start + 14, end).trim();
+        start = comment.indexOf("=") + 1;
+        end = comment.indexOf("\n");
+        if (end > start) comment = comment.substring(start, end).trim();
+        else comment = comment.substring(start).trim();
+      }
+      else comment = "";
+    }
+    addMeta("Comment", comment);
+  }
+
+  /* @see loci.formats.in.BaseTiffReader#initMetadataStore() */
+  protected void initMetadataStore() {
+    super.initMetadataStore();
+    MetadataStore store = getMetadataStore();
+    store.setDimensions(new Float(voxelX), new Float(voxelY),
+      new Float(voxelZ), new Float(voxelC), new Float(voxelT), null);
+
+    Double gamma = (Double) getMeta("Gamma");
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setDisplayChannel(new Integer(i), null, null,
+        gamma == null ? null : new Float(gamma.floatValue()), null);
+
+      String gain = (String) getMeta("Gain Ch" + (i+1));
+      String voltage = (String) getMeta("PMT Voltage Ch" + (i+1));
+      String offset = (String) getMeta("Offset Ch" + (i+1));
+
+      if (gain != null || voltage != null || offset != null) {
+        store.setDetector((String) getMeta("System Configuration"), null,
+          null, null, gain == null ? null : new Float(gain),
+          voltage == null ? null : new Float(voltage),
+          offset == null ? null : new Float(offset), null, new Integer(i));
+      }
+    }
+
+    String mag = (String) getMeta("Magnification");
+    if (mag != null && mag.toLowerCase().endsWith("x")) {
+      mag = mag.substring(0, mag.length() - 1);
+    }
+    else if (mag == null) mag = "1";
+    store.setObjective((String) getMeta("Objective Lens"), null, null, null,
+      new Float(mag), null, null);
+  }
+
+}
diff --git a/loci/formats/in/GIFReader.java b/loci/formats/in/GIFReader.java
new file mode 100644
index 0000000..b0f3636
--- /dev/null
+++ b/loci/formats/in/GIFReader.java
@@ -0,0 +1,527 @@
+//
+// GIFReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.Vector;
+import loci.formats.*;
+
+/**
+ * GIFReader is the file format reader for Graphics Interchange Format
+ * (GIF) files.  Much of this code was adapted from the Animated GIF Reader
+ * plugin for ImageJ (http://rsb.info.nih.gov/ij).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/GIFReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/GIFReader.java">SVN</a></dd></dl>
+ */
+public class GIFReader extends FormatReader {
+
+  // -- Constants --
+
+  /** Maximum buffer size. */
+  private static final int MAX_STACK_SIZE = 4096;
+
+  // -- Fields --
+
+  /** Global color table used. */
+  private boolean gctFlag;
+
+  /** Size of global color table. */
+  private int gctSize;
+
+  /** Global color table. */
+  private int[] gct;
+
+  /** Local color table. */
+  private int[] lct;
+
+  /** Active color table. */
+  private int[] act;
+
+  /** Local color table flag. */
+  private boolean lctFlag;
+
+  /** Interlace flag. */
+  private boolean interlace;
+
+  /** Local color table size. */
+  private int lctSize;
+
+  /** Current image rectangle. */
+  private int ix, iy, iw, ih;
+
+  /** Current data block. */
+  private byte[] dBlock = new byte[256];
+
+  /** Block size. */
+  private int blockSize = 0;
+
+  private int dispose = 0;
+  private int lastDispose = 0;
+
+  /** Use transparent color. */
+  private boolean transparency = false;
+
+  /** Transparent color index. */
+  private int transIndex;
+
+  // LZW working arrays
+  private short[] prefix;
+  private byte[] suffix;
+  private byte[] pixelStack;
+  private byte[] pixels;
+
+  private Vector images;
+  private Vector colorTables;
+
+  // -- Constructor --
+
+  /** Constructs a new GIF reader. */
+  public GIFReader() {
+    super("Graphics Interchange Format", "gif");
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) { return false; }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    byte[][] table = new byte[3][act.length];
+    for (int i=0; i<act.length; i++) {
+      table[0][i] = (byte) ((act[i] >> 16) & 0xff);
+      table[1][i] = (byte) ((act[i] >> 8) & 0xff);
+      table[2][i] = (byte) (act[i] & 0xff);
+    }
+    return table;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    act = (int[]) colorTables.get(no);
+
+    buf = (byte[]) images.get(no);
+    if (no > 0) {
+      byte[] prev = (byte[]) images.get(no - 1);
+      for (int i=0; i<buf.length; i++) {
+        if ((act[buf[i] & 0xff] & 0xffffff) == 0) {
+          buf[i] = prev[i];
+        }
+      }
+      images.setElementAt(buf, no);
+    }
+
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("GIFReader.initFile(" + id + ")");
+    super.initFile(id);
+
+    status("Verifying GIF format");
+
+    in = new RandomAccessStream(id);
+    in.order(true);
+    images = new Vector();
+    colorTables = new Vector();
+
+    String ident = in.readString(6);
+
+    if (!ident.startsWith("GIF")) {
+      throw new FormatException("Not a valid GIF file.");
+    }
+
+    status("Reading dimensions");
+
+    core.sizeX[0] = in.readShort();
+    core.sizeY[0] = in.readShort();
+
+    int packed = in.read() & 0xff;
+    gctFlag = (packed & 0x80) != 0;
+    gctSize = 2 << (packed & 7);
+    in.skipBytes(1);
+    in.skipBytes(1);
+
+    if (gctFlag) {
+      int nbytes = 3 * gctSize;
+      byte[] c = new byte[nbytes];
+      in.read(c);
+
+      gct = new int[256];
+      int i = 0;
+      int j = 0;
+      int r, g, b;
+      while (i < gctSize) {
+        r = c[j++] & 0xff;
+        g = c[j++] & 0xff;
+        b = c[j++] & 0xff;
+        gct[i++] = 0xff000000 | (r << 16) | (g << 8) | b;
+      }
+    }
+
+    status("Reading data blocks");
+
+    boolean done = false;
+    while (!done) {
+      int code = in.read() & 0xff;
+      switch (code) {
+        case 0x2c: // image separator:
+          ix = in.readShort();
+          iy = in.readShort();
+          iw = in.readShort();
+          ih = in.readShort();
+
+          packed = in.read();
+          lctFlag = (packed & 0x80) != 0;
+          interlace = (packed & 0x40) != 0;
+          lctSize = 2 << (packed & 7);
+
+          if (lctFlag) {
+            int nbytes = 3 * lctSize;
+            byte[] c = new byte[nbytes];
+            int n = 0;
+            try { n = in.read(c); }
+            catch (IOException e) { }
+
+            if (n < nbytes) {
+              throw new FormatException("Local color table not found");
+            }
+
+            lct = new int[256];
+            int i = 0;
+            int j = 0;
+            while (i < lctSize) {
+              int r = c[j++] & 0xff;
+              int g = c[j++] & 0xff;
+              int b = c[j++] & 0xff;
+              lct[i++] = 0xff000000 | (r << 16) + (g << 8) | b;
+            }
+
+            act = lct;
+          }
+          else {
+            act = gct;
+          }
+
+          int save = 0;
+
+          if (transparency) {
+            save = act[transIndex];
+            act[transIndex] = 0;
+          }
+
+          if (act == null) throw new FormatException("Color table not found.");
+
+          decodeImageData();
+
+          int check = 0;
+          do { check = readBlock(); }
+          while (blockSize > 0 && check != -1);
+
+          core.imageCount[0]++;
+
+          if (transparency) act[transIndex] = save;
+
+          lastDispose = dispose;
+          lct = null;
+
+          break;
+        case 0x21: // extension
+          code = in.read() & 0xff;
+          switch (code) {
+            case 0xf9: // graphics control extension
+              in.skipBytes(1);
+              packed = in.read() & 0xff;
+              dispose = (packed & 0x1c) >> 1;
+              transparency = (packed & 1) != 0;
+              in.skipBytes(2);
+              transIndex = in.read() & 0xff;
+              in.skipBytes(1);
+              break;
+            case 0xff:  // application extension
+              if (readBlock() == -1) {
+                done = true;
+                break;
+              }
+
+              String app = new String(dBlock, 0, 11);
+              if (app.equals("NETSCAPE2.0")) {
+                do {
+                  check = readBlock();
+                }
+                while (blockSize > 0 && check != -1);
+              }
+              else {
+                do {
+                  check = readBlock();
+                }
+                while (blockSize > 0 && check != -1);
+              }
+              break;
+            default:
+              do {
+                check = readBlock();
+              }
+              while (blockSize > 0 && check != -1);
+          }
+          break;
+        case 0x3b: // terminator
+          done = true;
+          break;
+      }
+    }
+
+    status("Populating metadata");
+
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = 3;
+    core.sizeT[0] = core.imageCount[0];
+    core.currentOrder[0] = "XYCTZ";
+    core.rgb[0] = true;
+    core.littleEndian[0] = true;
+    core.interleaved[0] = true;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = true;
+    core.falseColor[0] = false;
+
+    // populate metadata store
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    core.pixelType[0] = FormatTools.UINT8;
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Reads the next variable length block. */
+  private int readBlock() throws IOException {
+    if (in.getFilePointer() == in.length()) return -1;
+    blockSize = in.read() & 0xff;
+    int n = 0;
+    int count;
+
+    if (blockSize > 0) {
+      try {
+        while (n < blockSize) {
+          count = in.read(dBlock, n, blockSize - n);
+          if (count == -1) break;
+          n += count;
+        }
+      }
+      catch (IOException e) { }
+    }
+    return n;
+  }
+
+  /** Decodes LZW image data into a pixel array.  Adapted from ImageMagick. */
+  private void decodeImageData() throws IOException {
+    int nullCode = -1;
+    int npix = iw * ih;
+
+    int available, clear, codeMask, codeSize, eoi, inCode, oldCode, bits, code,
+      count, i, datum, dataSize, first, top, bi, pi;
+
+    if (pixels == null || pixels.length < npix) pixels = new byte[npix];
+
+    if (prefix == null) prefix = new short[MAX_STACK_SIZE];
+    if (suffix == null) suffix = new byte[MAX_STACK_SIZE];
+    if (pixelStack == null) pixelStack = new byte[MAX_STACK_SIZE + 1];
+
+    // initialize GIF data stream decoder
+
+    dataSize = in.read() & 0xff;
+
+    clear = 1 << dataSize;
+    eoi = clear + 1;
+    available = clear + 2;
+    oldCode = nullCode;
+    codeSize = dataSize + 1;
+    codeMask = (1 << codeSize) - 1;
+    for (code=0; code<clear; code++) {
+      prefix[code] = 0;
+      suffix[code] = (byte) code;
+    }
+
+    // decode GIF pixel stream
+
+    datum = bits = count = first = top = pi = bi = 0;
+
+    for (i=0; i<npix;) {
+      if (top == 0) {
+        if (bits < codeSize) {
+          if (count == 0) {
+            count = readBlock();
+            if (count <= 0) break;
+            bi = 0;
+          }
+          datum += (((int) dBlock[bi]) & 0xff) << bits;
+          bits += 8;
+          bi++;
+          count--;
+          continue;
+        }
+
+        // get the next code
+        code = datum & codeMask;
+        datum >>= codeSize;
+        bits -= codeSize;
+
+        // interpret the code
+
+        if ((code > available) || (code == eoi)) {
+          break;
+        }
+        if (code == clear) {
+          // reset the decoder
+          codeSize = dataSize + 1;
+          codeMask = (1 << codeSize) - 1;
+          available = clear + 2;
+          oldCode = nullCode;
+          continue;
+        }
+
+        if (oldCode == nullCode) {
+          pixelStack[top++] = suffix[code];
+          oldCode = code;
+          first = code;
+          continue;
+        }
+
+        inCode = code;
+        if (code == available) {
+          pixelStack[top++] = (byte) first;
+          code = oldCode;
+        }
+
+        while (code > clear) {
+          pixelStack[top++] = suffix[code];
+          code = prefix[code];
+        }
+        first = ((int) suffix[code]) & 0xff;
+
+        if (available >= MAX_STACK_SIZE) break;
+        pixelStack[top++] = (byte) first;
+        prefix[available] = (short) oldCode;
+        suffix[available] = (byte) first;
+        available++;
+
+        if (((available & codeMask) == 0) && (available < MAX_STACK_SIZE)) {
+          codeSize++;
+          codeMask += available;
+        }
+        oldCode = inCode;
+      }
+      top--;
+      pixels[pi++] = pixelStack[top];
+      i++;
+    }
+
+    for (i=pi; i<npix; i++) pixels[i] = 0;
+    setPixels();
+  }
+
+  private void setPixels() {
+    // expose destination image's pixels as an int array
+    byte[] dest = new byte[core.sizeX[0] * core.sizeY[0]];
+    int lastImage = -1;
+
+    // fill in starting image contents based on last image's dispose code
+    if (lastDispose > 0) {
+      if (lastDispose == 3) { // use image before last
+        int n = core.imageCount[0] - 2;
+        if (n > 0) lastImage = n - 1;
+      }
+
+      if (lastImage != -1) {
+        byte[] prev = (byte[]) images.get(lastImage);
+        System.arraycopy(prev, 0, dest, 0, core.sizeX[0] * core.sizeY[0]);
+      }
+    }
+
+    // copy each source line to the appropriate place in the destination
+
+    int pass = 1;
+    int inc = 8;
+    int iline = 0;
+    for (int i=0; i<ih; i++) {
+      int line = i;
+      if (interlace) {
+        if (iline >= ih) {
+          pass++;
+          switch (pass) {
+            case 2:
+              iline = 4;
+              break;
+            case 3:
+              iline = 2;
+              inc = 4;
+              break;
+            case 4:
+              iline = 1;
+              inc = 2;
+              break;
+          }
+        }
+        line = iline;
+        iline += inc;
+      }
+      line += iy;
+      if (line < core.sizeY[0]) {
+        int k = line * core.sizeX[0];
+        int dx = k + ix; // start of line in dest
+        int dlim = dx + iw; // end of dest line
+        if ((k + core.sizeX[0]) < dlim) dlim = k + core.sizeX[0];
+        int sx = i * iw; // start of line in source
+        while (dx < dlim) {
+          // map color and insert in destination
+          int index = ((int) pixels[sx++]) & 0xff;
+          dest[dx++] = (byte) index;
+        }
+      }
+    }
+    colorTables.add(act);
+    images.add(dest);
+  }
+
+}
diff --git a/loci/formats/in/GatanReader.java b/loci/formats/in/GatanReader.java
new file mode 100644
index 0000000..8070ac5
--- /dev/null
+++ b/loci/formats/in/GatanReader.java
@@ -0,0 +1,377 @@
+//
+// GatanReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.util.Vector;
+import loci.formats.*;
+
+/**
+ * GatanReader is the file format reader for Gatan files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/GatanReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/GatanReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class GatanReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Offset to pixel data. */
+  private long pixelOffset;
+
+  /** List of pixel sizes. */
+  private Vector pixelSizes;
+
+  private int bytesPerPixel;
+
+  protected int pixelDataNum = 0;
+  protected int datatype;
+
+  // -- Constructor --
+
+  /** Constructs a new Gatan reader. */
+  public GatanReader() { super("Gatan Digital Micrograph", "dm3"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block == null || block.length < 4) return false;
+    return DataTools.bytesToInt(block, false) == 3;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(pixelOffset);
+    in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("GatanReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    pixelOffset = 0;
+
+    status("Verifying Gatan format");
+
+    core.littleEndian[0] = false;
+    pixelSizes = new Vector();
+
+    in.order(false);
+
+    // only support version 3
+    if (in.readInt() != 3) {
+      throw new FormatException("invalid header");
+    }
+
+    status("Reading tags");
+
+    in.skipBytes(4);
+    core.littleEndian[0] = in.readInt() == 1;
+
+    // TagGroup instance
+
+    in.skipBytes(2);
+    in.order(!core.littleEndian[0]);
+    parseTags(in.readInt(), "initFile");
+
+    status("Populating metadata");
+
+    switch (datatype) {
+      case 1:
+      case 10:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      case 2:
+      case 3:
+      case 5:
+      case 12:
+      case 13:
+        core.pixelType[0] = FormatTools.FLOAT;
+        break;
+      case 7:
+      case 8:
+      case 11:
+      case 23:
+        core.pixelType[0] = FormatTools.UINT32;
+        break;
+      default:
+        core.pixelType[0] = FormatTools.UINT8;
+    }
+
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = 1;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYZTC";
+    core.imageCount[0] = 1;
+    core.rgb[0] = false;
+    core.interleaved[0] = false;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+
+    Float pixX = new Float(1);
+    Float pixY = new Float(1);
+    Float pixZ = new Float(1);
+
+    if (pixelSizes.size() > 0) {
+      pixX = new Float((String) pixelSizes.get(0));
+    }
+
+    if (pixelSizes.size() > 1) {
+      pixY = new Float((String) pixelSizes.get(1));
+    }
+
+    if (pixelSizes.size() > 2) {
+      pixZ = new Float((String) pixelSizes.get(2));
+    }
+
+    store.setDimensions(pixX, pixY, pixZ, null, null, null);
+
+    String gamma = (String) getMeta("Gamma");
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+      store.setDisplayChannel(new Integer(i), null, null,
+        gamma == null ? null : new Float(gamma), null);
+    }
+
+    String mag = (String) getMeta("Indicated Magnification");
+    store.setObjective(null, null, null, null,
+      mag == null ? null : new Float(mag), null, null);
+  }
+
+  // -- Helper methods --
+
+  /**
+   * Parses Gatan DM3 tags.
+   * Information on the DM3 structure found at:
+   * http://rsb.info.nih.gov/ij/plugins/DM3Format.gj.html and
+   * http://www-hrem.msm.cam.ac.uk/~cbb/info/dmformat/
+   */
+  private void parseTags(int numTags, String parent) throws IOException {
+    for (int i=0; i<numTags; i++) {
+      byte type = in.readByte();  // can be 21 (data) or 20 (tag group)
+      int length = in.readShort();
+      String labelString = in.readString(length);
+
+      // image data is in tag with type 21 and label 'Data'
+      // image dimensions are in type 20 tag with 2 type 15 tags
+      // bytes per pixel is in type 21 tag with label 'PixelDepth'
+
+      if (type == 21) {
+        in.skipBytes(4);  // equal to '%%%%'
+        int n = in.readInt();
+        int dataType = 0;
+        if (n == 1) {
+          dataType = in.readInt();
+          String data;
+          in.order(core.littleEndian[0]);
+          switch (dataType) {
+            case 2:
+            case 4:
+              data = "" + in.readShort();
+              break;
+            case 3:
+            case 5:
+              data = "" + in.readInt();
+              break;
+            case 6:
+              data = "" + in.readFloat();
+              break;
+            case 7:
+              data = "" + in.readFloat();
+              in.skipBytes(4);
+              break;
+            case 8:
+            case 9:
+            case 10:
+              data = "" + in.read();
+              break;
+            default:
+              data = "0";
+          }
+          if (parent.equals("Dimensions")) {
+            if (i == 0) core.sizeX[0] = Integer.parseInt(data);
+            else if (i == 1) core.sizeY[0] = Integer.parseInt(data);
+          }
+          if (labelString.equals("PixelDepth")) {
+            bytesPerPixel = Integer.parseInt(data);
+          }
+          else if (labelString.equals("Scale") && !data.equals("1.0") &&
+            !data.equals("0.0"))
+          {
+            pixelSizes.add(data);
+          }
+          addMeta(labelString, data);
+          if (labelString.equals("DataType")) datatype = Integer.parseInt(data);
+          in.order(!core.littleEndian[0]);
+        }
+        else if (n == 2) {
+          in.order(core.littleEndian[0]);
+          dataType = in.readInt();
+          if (dataType == 18) { // this should always be true
+            length = in.readInt();
+          }
+          addMeta(labelString, in.readString(length));
+          in.order(!core.littleEndian[0]);
+        }
+        else if (n == 3) {
+          dataType = in.readInt();
+          if (dataType == 20) { // this should always be true
+            dataType = in.readInt();
+            length = in.readInt();
+
+            if ("Data".equals(labelString)) pixelDataNum++;
+
+            if ("Data".equals(labelString) /*&& pixelDataNum == 2*/) {
+              // we're given the number of pixels, but the tag containing
+              // bytes per pixel doesn't occur until after the image data
+              //
+              // this is a messy way to read pixel data, which uses the fact
+              // that the first byte after the pixel data is either 20 or 21
+
+              byte check = 0;
+              double bpp = 0.5;
+              long pos = in.getFilePointer();
+              while (check != 20 && check != 21) {
+                bpp *= 2;
+                in.seek(pos);
+                pixelOffset = pos;
+                in.skipBytes((int) (bpp * length));
+                check = in.readByte();
+              }
+              in.seek((long) (pos + bpp * length));
+            }
+            else {
+              int[] data = new int[length];
+              for (int j=0; j<length; j++) {
+                if (dataType == 2 || dataType == 4) {
+                  data[j] = in.readShort();
+                }
+                else if (dataType == 7) in.skipBytes(8);
+                else if (dataType == 8 || dataType == 9) in.skipBytes(1);
+                else {
+                  data[j] = in.readInt();
+                }
+              }
+            }
+          }
+        }
+        else {
+          dataType = in.readInt();
+          // this is a normal struct of simple types
+          if (dataType == 15) {
+            int skip = in.readInt();
+            int numFields = in.readInt();
+            for (int j=0; j<numFields; j++) {
+              skip += in.readInt();
+              dataType = in.readInt();
+
+              switch (dataType) {
+                case 2:
+                case 4:
+                  skip += 2;
+                  break;
+                case 3:
+                case 5:
+                case 6:
+                  skip += 4;
+                  break;
+                case 7:
+                  skip += 8;
+                  break;
+                case 8:
+                case 9:
+                  skip += 1;
+                  break;
+              }
+            }
+            in.skipBytes(skip);
+          }
+          else if (dataType == 20) {
+            // this is an array of structs
+            int skip = 0;
+            dataType = in.readInt();
+            if (dataType == 15) { // should always be true
+              skip += in.readInt();
+              int numFields = in.readInt();
+              for (int j=0; j<numFields; j++) {
+                skip += in.readInt();
+                dataType = in.readInt();
+
+                switch (dataType) {
+                  case 2:
+                  case 4:
+                    skip += 2;
+                    break;
+                  case 3:
+                  case 5:
+                  case 6:
+                    skip += 4;
+                    break;
+                  case 7:
+                    skip += 8;
+                    break;
+                  case 8:
+                  case 9:
+                    skip += 1;
+                    break;
+                }
+              }
+            }
+            skip *= in.readInt();
+            in.skipBytes(skip);
+          }
+        }
+      }
+      else if (type == 20) {
+        in.skipBytes(2);
+        parseTags(in.readInt(), labelString);
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/GelReader.java b/loci/formats/in/GelReader.java
new file mode 100644
index 0000000..5b02ef3
--- /dev/null
+++ b/loci/formats/in/GelReader.java
@@ -0,0 +1,109 @@
+//
+// GelReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * GelReader is the file format reader for
+ * Molecular Dynamics GEL TIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/GelReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/GelReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class GelReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  // GEL TIFF private IFD tags.
+  private static final int MD_FILETAG = 33445;
+  private static final int MD_SCALE_PIXEL = 33446;
+  private static final int MD_LAB_NAME = 33448;
+  private static final int MD_SAMPLE_INFO = 33449;
+  private static final int MD_PREP_DATE = 33450;
+  private static final int MD_PREP_TIME = 33451;
+  private static final int MD_FILE_UNITS = 33452;
+
+  // -- Constructor --
+
+  /** Constructs a new GEL reader. */
+  public GelReader() {
+    super("Molecular Dynamics GEL TIFF", new String[] {"gel"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) { return false; }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see loci.formats.BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    core.imageCount[0]--;
+
+    try {
+      long fmt = TiffTools.getIFDLongValue(ifds[1], MD_FILETAG, true, 128);
+      addMeta("Data format", fmt == 2 ? "square root" : "linear");
+    }
+    catch (FormatException exc) {
+      trace(exc);
+    }
+
+    TiffRational scale =
+      (TiffRational) TiffTools.getIFDValue(ifds[1], MD_SCALE_PIXEL);
+    addMeta("Scale factor", scale == null ? new TiffRational(1, 1) : scale);
+
+    // ignore MD_COLOR_TABLE
+
+    String lab = (String) TiffTools.getIFDValue(ifds[1], MD_LAB_NAME);
+    addMeta("Lab name", lab == null ? "unknown" : lab);
+
+    String info = (String) TiffTools.getIFDValue(ifds[1], MD_SAMPLE_INFO);
+    addMeta("Sample info", info == null ? "unknown" : info);
+
+    String prepDate = (String) TiffTools.getIFDValue(ifds[1], MD_PREP_DATE);
+    addMeta("Date prepared", prepDate == null ? "unknown" : prepDate);
+
+    String prepTime = (String) TiffTools.getIFDValue(ifds[1], MD_PREP_TIME);
+    addMeta("Time prepared", prepTime == null ? "unknown" : prepTime);
+
+    String units = (String) TiffTools.getIFDValue(ifds[1], MD_FILE_UNITS);
+    addMeta("File units", units == null ? "unknown" : units);
+
+    core.sizeT[series] = core.imageCount[series];
+
+    MetadataStore store = getMetadataStore();
+    store.setDimensions(new Float(scale.floatValue()),
+      new Float(scale.floatValue()), null, null, null, null);
+  }
+
+}
diff --git a/loci/formats/in/ICSReader.java b/loci/formats/in/ICSReader.java
new file mode 100644
index 0000000..5c3fc61
--- /dev/null
+++ b/loci/formats/in/ICSReader.java
@@ -0,0 +1,462 @@
+//
+// ICSReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.zip.*;
+import loci.formats.*;
+import loci.formats.codec.ByteVector;
+
+/**
+ * ICSReader is the file format reader for ICS (Image Cytometry Standard)
+ * files. More information on ICS can be found at http://libics.sourceforge.net
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ICSReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ICSReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+
+public class ICSReader extends FormatReader {
+
+  // -- Constants --
+
+  /** Metadata field categories. */
+  private static final String[] CATEGORIES = new String[] {
+    "ics_version", "filename", "source", "layout", "representation",
+    "parameter", "sensor", "history", "document", "view", "end"
+  };
+
+  /** Metadata field subcategories. */
+  private static final String[] SUB_CATEGORIES = new String[] {
+    "file", "offset", "parameters", "order", "sizes", "coordinates",
+    "significant_bits", "format", "sign", "compression", "byte_order",
+    "origin", "scale", "units", "labels", "SCIL_TYPE", "type", "model",
+    "s_params", "laser", "gain1", "gain2", "gain3", "gain4", "dwell",
+    "shutter1", "shutter2", "shutter3", "pinhole", "laser1", "laser2",
+    "laser3", "objective", "PassCount", "step1", "step2", "step3", "view",
+    "view1", "date", "GMTdate", "label"
+  };
+
+  /** Metadata field sub-subcategories. */
+  private static final String[] SUB_SUB_CATEGORIES = new String[] {
+    "Channels", "PinholeRadius", "LambdaEx", "LambdaEm", "ExPhotonCnt",
+    "RefInxMedium", "NumAperture", "RefInxLensMedium", "PinholeSpacing",
+    "power", "wavelength", "name", "Type", "Magnification", "NA",
+    "WorkingDistance", "Immersion", "Pinhole", "Channel 1", "Channel 2",
+    "Channel 3", "Channel 4", "Gain 1", "Gain 2", "Gain 3", "Gain 4",
+    "Shutter 1", "Shutter 2", "Shutter 3", "Position", "Size", "Port",
+    "Cursor", "Color", "BlackLevel", "Saturation", "Gamma", "IntZoom",
+    "Live", "Synchronize", "ShowIndex", "AutoResize", "UseUnits", "Zoom",
+    "IgnoreAspect", "ShowCursor", "ShowAll", "Axis", "Order", "Tile", "scale",
+    "DimViewOption"
+  };
+
+  // -- Fields --
+
+  /** Current filename. */
+  protected String currentIcsId;
+  protected String currentIdsId;
+
+  /** Current ICS file. */
+  protected Location icsIn;
+
+  /** Number of bits per pixel. */
+  protected int bitsPerPixel;
+
+  /** Flag indicating whether current file is v2.0. */
+  protected boolean versionTwo;
+
+  /** Image data. */
+  protected byte[] data;
+
+  // -- Constructor --
+
+  /** Constructs a new ICSReader. */
+  public ICSReader() {
+    super("Image Cytometry Standard", new String[] {"ics", "ids"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    return FormatTools.MUST_GROUP;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int bpp = bitsPerPixel / 8;
+
+    int len = core.sizeX[0] * core.sizeY[0] * bpp * getRGBChannelCount();
+    int offset = len * no;
+    if (!core.rgb[0] && core.sizeC[0] > 4) {
+      int pt = 0;
+      for (int i=no*bpp; i<data.length; i+=core.sizeC[0]*bpp) {
+        System.arraycopy(data, i, buf, pt, bpp);
+        pt += bpp;
+      }
+    }
+    else System.arraycopy(data, offset, buf, 0, len);
+
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    if (versionTwo) {
+      return new String[] {currentIdsId == null ? "" : currentIdsId};
+    }
+    return new String[] {currentIdsId, currentIcsId};
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    icsIn = null;
+    currentIcsId = null;
+    currentIdsId = null;
+    data = null;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("ICSReader.initFile(" + id + ")");
+    super.initFile(id);
+
+    status("Finding companion file");
+
+    //String icsId = l.getPath(), idsId = l.getPath();
+    String icsId = id, idsId = id;
+    int dot = id.lastIndexOf(".");
+    String ext = dot < 0 ? "" : id.substring(dot + 1).toLowerCase();
+    if (ext.equals("ics")) {
+      // convert C to D regardless of case
+      char[] c = idsId.toCharArray();
+      c[c.length - 2]++;
+      idsId = new String(c);
+    }
+    else if (ext.equals("ids")) {
+      // convert D to C regardless of case
+      char[] c = icsId.toCharArray();
+      c[c.length - 2]--;
+      icsId = new String(c);
+    }
+
+    if (icsId == null) throw new FormatException("No ICS file found.");
+    Location icsFile = new Location(icsId);
+    if (!icsFile.exists()) throw new FormatException("ICS file not found.");
+
+    status("Checking file version");
+
+    // check if we have a v2 ICS file
+    RandomAccessStream f = new RandomAccessStream(icsId);
+    byte[] b = new byte[17];
+    f.read(b);
+    f.close();
+    if (new String(b).trim().equals("ics_version\t2.0")) {
+      in = new RandomAccessStream(icsId);
+      versionTwo = true;
+    }
+    else {
+      if (idsId == null) throw new FormatException("No IDS file found.");
+      Location idsFile = new Location(idsId);
+      if (!idsFile.exists()) throw new FormatException("IDS file not found.");
+      currentIdsId = idsId;
+      in = new RandomAccessStream(idsId);
+    }
+
+    currentIcsId = icsId;
+
+    icsIn = icsFile;
+
+    status("Reading metadata");
+
+    String layoutSizes = null, layoutOrder = null, byteOrder = null;
+    String rFormat = null, compression = null, scale = null;
+
+    RandomAccessStream reader = new RandomAccessStream(icsIn.getAbsolutePath());
+    StringTokenizer t;
+    String token;
+    String s = reader.readString((int) reader.length());
+    reader.close();
+    StringTokenizer st = new StringTokenizer(s, "\n");
+    String line = st.nextToken();
+    line = st.nextToken();
+    while (line != null && !line.trim().equals("end")) {
+      t = new StringTokenizer(line);
+      StringBuffer key = new StringBuffer();
+      while (t.hasMoreTokens()) {
+        token = t.nextToken();
+        boolean foundValue = true;
+        for (int i=0; i<CATEGORIES.length; i++) {
+          if (token.equals(CATEGORIES[i])) foundValue = false;
+        }
+        for (int i=0; i<SUB_CATEGORIES.length; i++) {
+          if (token.equals(SUB_CATEGORIES[i])) foundValue = false;
+        }
+        for (int i=0; i<SUB_SUB_CATEGORIES.length; i++) {
+          if (token.equals(SUB_SUB_CATEGORIES[i])) foundValue = false;
+        }
+
+        if (foundValue) {
+          StringBuffer value = new StringBuffer();
+          value.append(token);
+          while (t.hasMoreTokens()) {
+            value.append(" ");
+            value.append(t.nextToken());
+          }
+          String k = key.toString().trim();
+          String v = value.toString().trim();
+          addMeta(k, v);
+
+          if (k.equals("layout sizes")) layoutSizes = v;
+          else if (k.equals("layout order")) layoutOrder = v;
+          else if (k.equals("representation byte_order")) byteOrder = v;
+          else if (k.equals("representation format")) rFormat = v;
+          else if (k.equals("representation compression")) compression = v;
+          else if (k.equals("parameter scale")) scale = v;
+        }
+        else {
+          key.append(token);
+          key.append(" ");
+        }
+      }
+      if (st.hasMoreTokens()) line = st.nextToken();
+      else line = null;
+    }
+
+    status("Populating metadata");
+
+    layoutOrder = layoutOrder.trim();
+    // bpp, width, height, z, channels
+    StringTokenizer t1 = new StringTokenizer(layoutSizes);
+    StringTokenizer t2 = new StringTokenizer(layoutOrder);
+
+    core.rgb[0] = layoutOrder.indexOf("ch") >= 0 &&
+      layoutOrder.indexOf("ch") < layoutOrder.indexOf("x");
+
+    String imageToken;
+    String orderToken;
+    while (t1.hasMoreTokens() && t2.hasMoreTokens()) {
+      imageToken = t1.nextToken().trim();
+      orderToken = t2.nextToken().trim();
+      if (orderToken.equals("bits")) {
+        bitsPerPixel = Integer.parseInt(imageToken);
+      }
+      else if (orderToken.equals("x")) {
+        core.sizeX[0] = Integer.parseInt(imageToken);
+      }
+      else if (orderToken.equals("y")) {
+        core.sizeY[0] = Integer.parseInt(imageToken);
+      }
+      else if (orderToken.equals("z")) {
+        core.sizeZ[0] = Integer.parseInt(imageToken);
+      }
+      else if (orderToken.equals("ch")) {
+        core.sizeC[0] = Integer.parseInt(imageToken);
+        if (core.sizeC[0] > 4) core.rgb[0] = false;
+      }
+      else {
+        core.sizeT[0] = Integer.parseInt(imageToken);
+      }
+    }
+
+    if (core.sizeZ[0] == 0) core.sizeZ[0] = 1;
+    if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+    if (core.sizeT[0] == 0) core.sizeT[0] = 1;
+
+    if (core.imageCount[0] == 0) core.imageCount[0] = 1;
+    core.rgb[0] = core.rgb[0] && core.sizeC[0] > 1;
+    core.interleaved[0] = core.rgb[0];
+    core.imageCount[0] = core.sizeZ[0] * core.sizeT[0];
+    if (!core.rgb[0]) core.imageCount[0] *= core.sizeC[0];
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    String endian = byteOrder;
+    core.littleEndian[0] = true;
+
+    if (endian != null) {
+      StringTokenizer endianness = new StringTokenizer(endian);
+      String firstByte = endianness.nextToken();
+      int first = Integer.parseInt(firstByte);
+      core.littleEndian[0] = rFormat.equals("real") ? first == 1 : first != 1;
+    }
+
+    String test = compression;
+    boolean gzip = (test == null) ? false : test.equals("gzip");
+
+    if (versionTwo) {
+      s = in.readLine();
+      while(!s.trim().equals("end")) s = in.readLine();
+    }
+    data = new byte[(int) (in.length() - in.getFilePointer())];
+
+    // extra check is because some of our datasets are labeled as 'gzip', and
+    // have a valid GZIP header, but are actually uncompressed
+    if (gzip && ((data.length / (core.imageCount[0]) <
+      (core.sizeX[0] * core.sizeY[0] * bitsPerPixel / 8))))
+    {
+      status("Decompressing pixel data");
+      in.read(data);
+      byte[] buf = new byte[8192];
+      ByteVector v = new ByteVector();
+      try {
+        GZIPInputStream decompressor =
+          new GZIPInputStream(new ByteArrayInputStream(data));
+        int r = decompressor.read(buf, 0, buf.length);
+        while (r > 0) {
+          v.add(buf, 0, r);
+          r = decompressor.read(buf, 0, buf.length);
+        }
+        data = v.toByteArray();
+      }
+      catch (IOException dfe) {
+        throw new FormatException("Error uncompressing gzip'ed data", dfe);
+      }
+    }
+    else in.readFully(data);
+
+    status("Populating metadata");
+
+    // Populate metadata store
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    String fname = (String) getMeta("filename");
+    if (fname == null) fname = currentId;
+    store.setImage(fname, null, null, null);
+
+    // populate Pixels element
+
+    String o = layoutOrder;
+    o = o.trim();
+    o = o.substring(o.indexOf("x")).trim();
+    char[] tempOrder = new char[(o.length() / 2) + 1];
+    int pt = 0;
+    for (int i=0; i<o.length(); i+=2) {
+      tempOrder[pt] = o.charAt(i);
+      pt++;
+    }
+    o = new String(tempOrder).toUpperCase().trim();
+    if (o.indexOf("Z") == -1) o = o + "Z";
+    if (o.indexOf("T") == -1) o = o + "T";
+    if (o.indexOf("C") == -1) o = o + "C";
+
+    String fmt = rFormat;
+
+    if (bitsPerPixel < 32) core.littleEndian[0] = !core.littleEndian[0];
+
+    if (fmt.equals("real")) core.pixelType[0] = FormatTools.FLOAT;
+    else if (fmt.equals("integer")) {
+      while (bitsPerPixel % 8 != 0) bitsPerPixel++;
+      if (bitsPerPixel == 24 || bitsPerPixel == 48) bitsPerPixel /= 3;
+
+      switch (bitsPerPixel) {
+        case 8:
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 16:
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.UINT32;
+          break;
+      }
+    }
+    else {
+      throw new RuntimeException("Unknown pixel format: " + format);
+    }
+
+    core.currentOrder[0] = o.trim();
+
+    FormatTools.populatePixels(store, this);
+
+    String pixelSizes = scale;
+    o = layoutOrder;
+    if (pixelSizes != null) {
+      StringTokenizer pixelSizeTokens = new StringTokenizer(pixelSizes);
+      StringTokenizer axisTokens = new StringTokenizer(o);
+
+      Float pixX = null, pixY = null, pixZ = null, pixC = null, pixT = null;
+
+      while (pixelSizeTokens.hasMoreTokens()) {
+        String axis = axisTokens.nextToken().trim().toLowerCase();
+        String size = pixelSizeTokens.nextToken().trim();
+        if (axis.equals("x")) pixX = new Float(size);
+        else if (axis.equals("y")) pixY = new Float(size);
+        else if (axis.equals("ch")) pixC = new Float(size);
+        else if (axis.equals("z")) pixZ = new Float(size);
+        else if (axis.equals("t")) pixT = new Float(size);
+      }
+      store.setDimensions(pixX, pixY, pixZ, pixC, pixT, null);
+    }
+
+    String em = (String) getMeta("sensor s_params LambdaEm");
+    String ex = (String) getMeta("sensor s_params LambdaEx");
+    int[] emWave = new int[core.sizeC[0]];
+    int[] exWave = new int[core.sizeC[0]];
+    if (em != null) {
+      StringTokenizer emTokens = new StringTokenizer(em);
+      for (int i=0; i<core.sizeC[0]; i++) {
+        emWave[i] = (int) Float.parseFloat(emTokens.nextToken().trim());
+      }
+    }
+    if (ex != null) {
+      StringTokenizer exTokens = new StringTokenizer(ex);
+      for (int i=0; i<core.sizeC[0]; i++) {
+        exWave[i] = (int) Float.parseFloat(exTokens.nextToken().trim());
+      }
+    }
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+       null, null, null, null, null, null, null, null, null, null, null,
+       new Integer(emWave[i]), new Integer(exWave[i]), null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/IPLabReader.java b/loci/formats/in/IPLabReader.java
new file mode 100644
index 0000000..e0c8168
--- /dev/null
+++ b/loci/formats/in/IPLabReader.java
@@ -0,0 +1,343 @@
+//
+// IPLabReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * IPLabReader is the file format reader for IPLab (.IPL) files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/IPLabReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/IPLabReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class IPLabReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Bytes per pixel. */
+  private int bps;
+
+  /** Total number of pixel bytes. */
+  private int dataSize;
+
+  // -- Constructor --
+
+  /** Constructs a new IPLab reader. */
+  public IPLabReader() { super("IPLab", "ipl"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 12) return false; // block length too short
+    String s = new String(block, 0, 4);
+    boolean big = s.equals("iiii");
+    boolean little = s.equals("mmmm");
+    if (!big && !little) return false;
+    int size = DataTools.bytesToInt(block, 4, 4, little);
+    if (size != 4) return false; // first block size should be 4
+    int version = DataTools.bytesToInt(block, 8, 4, little);
+    return version >= 0x100e;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int numPixels = core.sizeX[0] * core.sizeY[0] * core.sizeC[0] * bps;
+    in.seek(numPixels * (no / core.sizeC[0]) + 44);
+    in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("IPLabReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    status("Populating metadata");
+
+    core.littleEndian[0] = in.readString(4).equals("iiii");
+
+    in.order(core.littleEndian[0]);
+
+    // populate standard metadata hashtable and OME root node
+    in.skipBytes(12);
+
+    dataSize = in.readInt() - 28;
+    core.sizeX[0] = in.readInt();
+    core.sizeY[0] = in.readInt();
+    core.sizeC[0] = in.readInt();
+    core.sizeZ[0] = in.readInt();
+    core.sizeT[0] = in.readInt();
+    int filePixelType = in.readInt();
+
+    core.imageCount[0] = core.sizeZ[0] * core.sizeT[0];
+
+    addMeta("Width", new Long(core.sizeX[0]));
+    addMeta("Height", new Long(core.sizeY[0]));
+    addMeta("Channels", new Long(core.sizeC[0]));
+    addMeta("ZDepth", new Long(core.sizeZ[0]));
+    addMeta("TDepth", new Long(core.sizeT[0]));
+
+    String ptype;
+    bps = 1;
+    switch (filePixelType) {
+      case 0:
+        ptype = "8 bit unsigned";
+        core.pixelType[0] = FormatTools.UINT8;
+        bps = 1;
+        break;
+      case 1:
+        ptype = "16 bit signed short";
+        core.pixelType[0] = FormatTools.UINT16;
+        bps = 2;
+        break;
+      case 2:
+        ptype = "16 bit unsigned short";
+        core.pixelType[0] = FormatTools.UINT16;
+        bps = 2;
+        break;
+      case 3:
+        ptype = "32 bit signed long";
+        core.pixelType[0] = FormatTools.UINT32;
+        bps = 4;
+        break;
+      case 4:
+        ptype = "32 bit single-precision float";
+        core.pixelType[0] = FormatTools.FLOAT;
+        bps = 4;
+        break;
+      case 5:
+        ptype = "Color24";
+        core.pixelType[0] = FormatTools.UINT32;
+        bps = 1;
+        break;
+      case 6:
+        ptype = "Color48";
+        core.pixelType[0] = FormatTools.UINT16;
+        bps = 2;
+        break;
+      case 10:
+        ptype = "64 bit double-precision float";
+        core.pixelType[0] = FormatTools.DOUBLE;
+        bps = 8;
+        break;
+      default:
+        ptype = "reserved"; // for values 7-9
+    }
+
+    addMeta("PixelType", ptype);
+    in.skipBytes(dataSize);
+
+    core.currentOrder[0] = "XY";
+    if (core.sizeC[0] > 1) core.currentOrder[0] += "CZT";
+    else core.currentOrder[0] += "ZTC";
+
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.interleaved[0] = false;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+
+    status("Reading tags");
+
+    String tag = in.readString(4);
+    while (!tag.equals("fini") && in.getFilePointer() < in.length() - 4) {
+      if (tag.equals("clut")) {
+        // read in Color Lookup Table
+        int size = in.readInt();
+        if (size == 8) {
+          // indexed lookup table
+          in.skipBytes(4);
+          int type = in.readInt();
+
+          String[] types = new String[] {
+            "monochrome", "reverse monochrome", "BGR", "classify", "rainbow",
+            "red", "green", "blue", "cyan", "magenta", "yellow",
+            "saturated pixels"
+          };
+          String clutType = (type >= 0 && type < types.length) ? types[type] :
+            "unknown";
+          addMeta("LUT type", clutType);
+        }
+        else {
+          // explicitly defined lookup table
+          // length is 772
+          in.skipBytes(772);
+        }
+      }
+      else if (tag.equals("norm")) {
+        // read in normalization information
+
+        int size = in.readInt();
+        // error checking
+
+        if (size != (44 * core.sizeC[0])) {
+          throw new FormatException("Bad normalization settings");
+        }
+
+        String[] types = new String[] {
+          "user", "plane", "sequence", "saturated plane",
+          "saturated sequence", "ROI"
+        };
+
+        for (int i=0; i<core.sizeC[0]; i++) {
+          int source = in.readInt();
+
+          String sourceType = (source >= 0 && source < types.length) ?
+            types[source] : "user";
+          addMeta("NormalizationSource" + i, sourceType);
+
+          double min = in.readDouble();
+          double max = in.readDouble();
+          double gamma = in.readDouble();
+          double black = in.readDouble();
+          double white = in.readDouble();
+
+          addMeta("NormalizationMin" + i, new Double(min));
+          addMeta("NormalizationMax" + i, new Double(max));
+          addMeta("NormalizationGamma" + i, new Double(gamma));
+          addMeta("NormalizationBlack" + i, new Double(black));
+          addMeta("NormalizationWhite" + i, new Double(white));
+
+          store = getMetadataStore();
+          store.setChannelGlobalMinMax(i, new Double(min),
+            new Double(max), null);
+
+          store.setDisplayChannel(new Integer(core.sizeC[0]), new Double(black),
+            new Double(white), new Float(gamma), null);
+        }
+      }
+      else if (tag.equals("head")) {
+        // read in header labels
+
+        int size = in.readInt();
+        for (int i=0; i<size / 22; i++) {
+          int num = in.readShort();
+          addMeta("Header" + num, in.readString(20));
+        }
+      }
+      else if (tag.equals("mmrc")) {
+        in.skipBytes(in.readInt());
+      }
+      else if (tag.equals("roi ")) {
+        // read in ROI information
+
+        in.skipBytes(8);
+        int roiLeft = in.readInt();
+        int roiTop = in.readInt();
+        int roiRight = in.readInt();
+        int roiBottom = in.readInt();
+        int numRoiPts = in.readInt();
+
+        Integer x0 = new Integer(roiLeft);
+        Integer x1 = new Integer(roiRight);
+        Integer y0 = new Integer(roiBottom);
+        Integer y1 = new Integer(roiTop);
+        store.setDisplayROI(x0, y0, null, x1, y1, null, null, null, null, null);
+
+        in.skipBytes(8 * numRoiPts);
+      }
+      else if (tag.equals("mask")) {
+        // read in Segmentation Mask
+        in.skipBytes(in.readInt());
+      }
+      else if (tag.equals("unit")) {
+        // read in units
+        in.skipBytes(4);
+
+        for (int i=0; i<4; i++) {
+          int xResStyle = in.readInt();
+          int unitsPerPixel = in.readInt();
+          int xUnitName = in.readInt();
+
+          addMeta("ResolutionStyle" + i, new Long(xResStyle));
+          addMeta("UnitsPerPixel" + i, new Long(unitsPerPixel));
+
+          if (i == 0) {
+            Float pixelSize = new Float(1 / (float) unitsPerPixel);
+            store.setDimensions(pixelSize, pixelSize, null, null, null, null);
+          }
+
+          addMeta("UnitName" + i, new Long(xUnitName));
+        }
+      }
+      else if (tag.equals("view")) {
+        // read in view
+        in.skipBytes(4);
+      }
+      else if (tag.equals("plot")) {
+        // read in plot
+        // skipping this field for the moment
+        in.skipBytes(2512);
+      }
+      else if (tag.equals("notes")) {
+        // read in notes (image info)
+        in.skipBytes(4);
+        String descriptor = in.readString(64);
+        String notes = in.readString(512);
+        addMeta("Descriptor", descriptor);
+        addMeta("Notes", notes);
+
+        store.setImage(currentId, null, notes, null);
+      }
+
+      if (in.getFilePointer() + 4 <= in.length()) {
+        tag = in.readString(4);
+      }
+      else {
+        tag = "fini";
+      }
+      if (in.getFilePointer() >= in.length() && !tag.equals("fini")) {
+        tag = "fini";
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/IPWReader.java b/loci/formats/in/IPWReader.java
new file mode 100644
index 0000000..f0bbcba
--- /dev/null
+++ b/loci/formats/in/IPWReader.java
@@ -0,0 +1,451 @@
+//
+// IPWReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * IPWReader is the file format reader for Image-Pro Workspace (IPW) files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/IPWReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/IPWReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class IPWReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  private static final String NO_POI_MSG =
+    "Jakarta POI is required to read IPW files. Please " +
+    "obtain poi-loci.jar from http://loci.wisc.edu/ome/formats.html";
+
+  // -- Static fields --
+
+  private static boolean noPOI = false;
+  private static ReflectedUniverse r = createReflectedUniverse();
+
+  private static ReflectedUniverse createReflectedUniverse() {
+    r = null;
+    try {
+      r = new ReflectedUniverse();
+      r.exec("import org.apache.poi.poifs.filesystem.POIFSFileSystem");
+      r.exec("import org.apache.poi.poifs.filesystem.DirectoryEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentInputStream");
+      r.exec("import java.util.Iterator");
+    }
+    catch (Throwable t) {
+      noPOI = true;
+      if (debug) LogTools.trace(t);
+    }
+    return r;
+  }
+
+  // -- Fields --
+
+  private Hashtable pixels;
+  private Hashtable names;
+  private byte[] header;  // general image header data
+  private byte[] tags; // tags data
+
+  // -- Constructor --
+
+  /** Constructs a new IPW reader. */
+  public IPWReader() { super("Image-Pro Workspace", "ipw"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    // all of our samples begin with 0xd0cf11e0
+    return (block[0] == 0xd0 && block[1] == 0xcf &&
+      block[2] == 0x11 && block[3] == 0xe0);
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    RandomAccessStream stream = getStream(no);
+    ifds = TiffTools.getIFDs(stream);
+    core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]);
+    TiffTools.getSamples(ifds[0], stream, buf);
+    stream.close();
+    return buf;
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+
+    pixels = null;
+    names = null;
+    header = null;
+    tags = null;
+
+    String[] vars = {"dirName", "root", "dir", "document", "dis",
+      "numBytes", "data", "fis", "fs", "iter", "isInstance", "isDocument",
+      "entry", "documentName", "entryName"};
+    for (int i=0; i<vars.length; i++) r.setVar(vars[i], null);
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initMetadata() */
+  public void initMetadata() throws FormatException, IOException {
+    String directory = (String) pixels.get(new Integer(0));
+    String name = (String) names.get(new Integer(0));
+
+    try {
+      r.setVar("dirName", directory);
+      r.exec("root = fs.getRoot()");
+      if (!directory.equals("Root Entry")) {
+        r.exec("dir = root.getEntry(dirName)");
+        r.setVar("entryName", name);
+        r.exec("document = dir.getEntry(entryName)");
+      }
+      else {
+        r.setVar("entryName", name);
+        r.exec("document = root.getEntry(entryName)");
+      }
+
+      r.exec("dis = new DocumentInputStream(document)");
+      r.exec("numBytes = dis.available()");
+      int numBytes = ((Integer) r.getVar("numBytes")).intValue();
+      byte[] b = new byte[numBytes + 4]; // append 0 for final offset
+      r.setVar("data", b);
+      r.exec("dis.read(data)");
+
+      RandomAccessStream stream = new RandomAccessStream(b);
+      ifds = TiffTools.getIFDs(stream);
+      stream.close();
+    }
+    catch (ReflectException e) { }
+
+    core.rgb[0] = (TiffTools.getIFDIntValue(ifds[0],
+      TiffTools.SAMPLES_PER_PIXEL, false, 1) > 1);
+
+    if (!core.rgb[0]) {
+      core.rgb[0] = TiffTools.getIFDIntValue(ifds[0],
+        TiffTools.PHOTOMETRIC_INTERPRETATION, false, 1) ==
+        TiffTools.RGB_PALETTE;
+    }
+
+    core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]);
+
+    // parse the image description
+    String description = new String(tags, 22, tags.length-22);
+    addMeta("Image Description", description);
+
+    // default values
+
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = 1;
+    core.sizeT[0] = getImageCount();
+    addMeta("slices", "1");
+    addMeta("channels", "1");
+    addMeta("frames", new Integer(getImageCount()));
+
+    // parse the description to get channels/slices/times where applicable
+    // basically the same as in SEQReader
+    if (description != null) {
+      StringTokenizer tokenizer = new StringTokenizer(description, "\n");
+      while (tokenizer.hasMoreTokens()) {
+        String token = tokenizer.nextToken();
+        String label = "Timestamp";
+        String data;
+        if (token.indexOf("=") != -1) {
+          label = token.substring(0, token.indexOf("="));
+          data = token.substring(token.indexOf("=")+1);
+        }
+        else {
+          data = token.trim();
+        }
+        addMeta(label, data);
+        if (label.equals("frames")) core.sizeZ[0] = Integer.parseInt(data);
+        else if (label.equals("slices")) core.sizeT[0] = Integer.parseInt(data);
+        else if (label.equals("channels")) {
+          core.sizeC[0] = Integer.parseInt(data);
+        }
+      }
+    }
+
+    addMeta("Version", new String(header).trim());
+
+    Hashtable h = ifds[0];
+    core.sizeX[0] = TiffTools.getIFDIntValue(h, TiffTools.IMAGE_WIDTH);
+    core.sizeY[0] = TiffTools.getIFDIntValue(h, TiffTools.IMAGE_LENGTH);
+    core.currentOrder[0] = "XY";
+
+    if (core.sizeZ[0] == 0) core.sizeZ[0] = 1;
+    if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+    if (core.sizeT[0] == 0) core.sizeT[0] = 1;
+
+    if (core.rgb[0]) core.sizeC[0] *= 3;
+
+    int maxNdx = 0, max = 0;
+    int[] dims = {core.sizeZ[0], core.sizeC[0], core.sizeT[0]};
+    String[] axes = {"Z", "C", "T"};
+
+    for (int i=0; i<dims.length; i++) {
+      if (dims[i] > max) {
+        max = dims[i];
+        maxNdx = i;
+      }
+    }
+
+    core.currentOrder[0] += axes[maxNdx];
+
+    if (maxNdx != 1) {
+      if (core.sizeC[0] > 1) {
+        core.currentOrder[0] += "C";
+        core.currentOrder[0] += (maxNdx == 0 ? axes[2] : axes[0]);
+      }
+      else core.currentOrder[0] += (maxNdx == 0 ? axes[2] : axes[0]) + "C";
+    }
+    else {
+      if (core.sizeZ[0] > core.sizeT[0]) core.currentOrder[0] += "ZT";
+      else core.currentOrder[0] += "TZ";
+    }
+
+    int bitsPerSample = TiffTools.getIFDIntValue(ifds[0],
+      TiffTools.BITS_PER_SAMPLE);
+    int bitFormat = TiffTools.getIFDIntValue(ifds[0], TiffTools.SAMPLE_FORMAT);
+
+    while (bitsPerSample % 8 != 0) bitsPerSample++;
+    if (bitsPerSample == 24 || bitsPerSample == 48) bitsPerSample /= 3;
+
+    core.pixelType[0] = FormatTools.UINT8;
+
+    if (bitFormat == 3) core.pixelType[0] = FormatTools.FLOAT;
+    else if (bitFormat == 2) {
+      switch (bitsPerSample) {
+        case 8:
+          core.pixelType[0] = FormatTools.INT8;
+          break;
+        case 16:
+          core.pixelType[0] = FormatTools.INT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.INT32;
+          break;
+      }
+    }
+    else {
+      switch (bitsPerSample) {
+        case 8:
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 16:
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.UINT32;
+          break;
+      }
+    }
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    FormatTools.populatePixels(store, this);
+    store.setImage(currentId, null, (String) getMeta("Version"), null);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("IPWReader.initFile(" + id + ")");
+    if (noPOI) throw new FormatException(NO_POI_MSG);
+
+    currentId = id;
+    metadata = new Hashtable();
+    core = new CoreMetadata(1);
+    Arrays.fill(core.orderCertain, true);
+    getMetadataStore().createRoot();
+
+    in = new RandomAccessStream(id);
+
+    pixels = new Hashtable();
+    names = new Hashtable();
+
+    try {
+      r.setVar("fis", in);
+      r.exec("fs = new POIFSFileSystem(fis)");
+      r.exec("dir = fs.getRoot()");
+      parseDir(0, r.getVar("dir"));
+      status("Populating metadata");
+      initMetadata();
+    }
+    catch (Throwable t) {
+      noPOI = true;
+      if (debug) trace(t);
+    }
+
+  }
+
+  // -- Helper methods --
+
+  protected void parseDir(int depth, Object dir)
+    throws IOException, FormatException, ReflectException
+  {
+    r.setVar("dir", dir);
+    r.exec("dirName = dir.getName()");
+    r.setVar("depth", depth);
+    r.exec("iter = dir.getEntries()");
+    Iterator iter = (Iterator) r.getVar("iter");
+    while (iter.hasNext()) {
+      r.setVar("entry", iter.next());
+      r.exec("isInstance = entry.isDirectoryEntry()");
+      r.exec("isDocument = entry.isDocumentEntry()");
+      boolean isInstance = ((Boolean) r.getVar("isInstance")).booleanValue();
+      boolean isDocument = ((Boolean) r.getVar("isDocument")).booleanValue();
+      r.setVar("dir", dir);
+      r.exec("dirName = dir.getName()");
+      if (isInstance)  {
+        status("Parsing embedded folder (" + (depth + 1) + ")");
+        parseDir(depth + 1, r.getVar("entry"));
+      }
+      else if (isDocument) {
+        status("Parsing embedded file (" + depth + ")");
+        r.exec("entryName = entry.getName()");
+        r.exec("dis = new DocumentInputStream(entry)");
+        r.exec("numBytes = dis.available()");
+        int numbytes = ((Integer) r.getVar("numBytes")).intValue();
+        byte[] data = new byte[numbytes + 4]; // append 0 for final offset
+        r.setVar("data", data);
+        r.exec("dis.read(data)");
+
+        RandomAccessStream ds = new RandomAccessStream(data);
+        ds.order(true);
+
+        String entryName = (String) r.getVar("entryName");
+        String dirName = (String) r.getVar("dirName");
+
+        boolean isContents = entryName.equals("CONTENTS");
+
+        if (isContents) {
+          // software version
+          header = data;
+        }
+        else if (entryName.equals("FrameRate")) {
+          // should always be exactly 4 bytes
+          // only exists if the file has more than one image
+          addMeta("Frame Rate", new Long(ds.readInt()));
+        }
+        else if (entryName.equals("FrameInfo")) {
+          // should always be 16 bytes (if present)
+          for(int i=0; i<data.length/2; i++) {
+            addMeta("FrameInfo "+i, new Short(ds.readShort()));
+          }
+        }
+        else if (entryName.equals("ImageInfo")) {
+          // acquisition data
+          tags = data;
+        }
+        else if (entryName.equals("ImageResponse")) {
+          // skip this entry
+        }
+        else if (entryName.equals("ImageTIFF")) {
+          // pixel data
+          String name;
+          if (!dirName.equals("Root Entry")) {
+            name = dirName.substring(11, dirName.length());
+          }
+          else name = "0";
+
+          Integer imageNum = Integer.valueOf(name);
+          pixels.put(imageNum, dirName);
+          names.put(imageNum, entryName);
+          core.imageCount[0]++;
+        }
+        ds.close();
+        r.exec("dis.close()");
+        if (debug) {
+          print(depth + 1, data.length + " bytes read.");
+        }
+      }
+    }
+  }
+
+  /** Retrieve the file corresponding to the given image number. */
+  private RandomAccessStream getStream(int no) throws IOException {
+    try {
+      String directory = (String) pixels.get(new Integer(no));
+      String name = (String) names.get(new Integer(no));
+
+      r.setVar("dirName", directory);
+      r.exec("root = fs.getRoot()");
+
+      if (!directory.equals("Root Entry")) {
+        r.exec("dir = root.getEntry(dirName)");
+        r.setVar("entryName", name);
+        r.exec("document = dir.getEntry(entryName)");
+      }
+      else {
+        r.setVar("entryName", name);
+        r.exec("document = root.getEntry(entryName)");
+      }
+
+      r.exec("dis = new DocumentInputStream(document)");
+      r.exec("numBytes = dis.available()");
+      int numBytes = ((Integer) r.getVar("numBytes")).intValue();
+      byte[] b = new byte[numBytes + 4];
+      r.setVar("data", b);
+      r.exec("dis.read(data)");
+
+      return new RandomAccessStream(b);
+    }
+    catch (ReflectException e) {
+      noPOI = true;
+      return null;
+    }
+  }
+
+  /** Debugging helper method. */
+  protected void print(int depth, String s) {
+    StringBuffer sb = new StringBuffer();
+    for (int i=0; i<depth; i++) sb.append("  ");
+    sb.append(s);
+    debug(sb.toString());
+  }
+
+}
diff --git a/loci/formats/in/ImageIOReader.java b/loci/formats/in/ImageIOReader.java
new file mode 100644
index 0000000..c756433
--- /dev/null
+++ b/loci/formats/in/ImageIOReader.java
@@ -0,0 +1,140 @@
+//
+// ImageIOReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+import loci.formats.*;
+
+/**
+ * ImageIOReader is the superclass for file format readers
+ * that use the javax.imageio package.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ImageIOReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ImageIOReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public abstract class ImageIOReader extends FormatReader {
+
+  // -- Constructors --
+
+  /** Constructs a new ImageIOReader. */
+  public ImageIOReader(String name, String suffix) { super(name, suffix); }
+
+  /** Constructs a new ImageIOReader. */
+  public ImageIOReader(String name, String[] suffixes) {
+    super(name, suffixes);
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) { return false; }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    buf = ImageTools.getBytes(openImage(no), false, no);
+    int bytesPerChannel = core.sizeX[0] * core.sizeY[0];
+    if (buf.length > bytesPerChannel) {
+      byte[] tmp = buf;
+      buf = new byte[bytesPerChannel * 3];
+      for (int i=0; i<3; i++) {
+        System.arraycopy(tmp, i * bytesPerChannel, buf, i*bytesPerChannel,
+          bytesPerChannel);
+      }
+    }
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    RandomAccessStream ras = new RandomAccessStream(currentId);
+    DataInputStream dis =
+      new DataInputStream(new BufferedInputStream(ras, 4096));
+    BufferedImage b = ImageIO.read(dis);
+    ras.close();
+    dis.close();
+    return b;
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException { }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    currentId = null;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("ImageIOReader.initFile(" + id + ")");
+    super.initFile(id);
+
+    status("Populating metadata");
+    core.imageCount[0] = 1;
+    BufferedImage img = openImage(0);
+
+    core.sizeX[0] = img.getWidth();
+    core.sizeY[0] = img.getHeight();
+
+    core.rgb[0] = img.getRaster().getNumBands() > 1;
+
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = core.rgb[0] ? 3 : 1;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYCZT";
+    core.pixelType[0] = ImageTools.getPixelType(img);
+    core.interleaved[0] = false;
+    core.littleEndian[0] = false;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    // populate the metadata store
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/ImarisReader.java b/loci/formats/in/ImarisReader.java
new file mode 100644
index 0000000..09a9ca4
--- /dev/null
+++ b/loci/formats/in/ImarisReader.java
@@ -0,0 +1,191 @@
+//
+// ImarisReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * ImarisReader is the file format reader for Bitplane Imaris files.
+ * Specifications available at
+ * http://flash.bitplane.com/support/faqs/faqsview.cfm?inCat=6&inQuestionID=104
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ImarisReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ImarisReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class ImarisReader extends FormatReader {
+
+  // -- Constants --
+
+  /** Magic number; present in all files. */
+  private static final int IMARIS_MAGIC_NUMBER = 5021964;
+
+  /** Specifies endianness. */
+  private static final boolean IS_LITTLE = false;
+
+  // -- Fields --
+
+  /** Offsets to each image. */
+  private int[] offsets;
+
+  // -- Constructor --
+
+  /** Constructs a new Imaris reader. */
+  public ImarisReader() { super("Bitplane Imaris", "ims"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return DataTools.bytesToInt(block, 0, 4, IS_LITTLE) == IMARIS_MAGIC_NUMBER;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(offsets[no]);
+    int row = core.sizeY[0] - 1;
+    for (int i=0; i<core.sizeY[0]; i++) {
+      in.read(buf, row*core.sizeX[0], core.sizeX[0]);
+      row--;
+    }
+    return buf;
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (!super.isThisType(name, open)) return false; // check extension
+    if (!open) return true; // not allowed to check the file contents
+    try {
+      RandomAccessStream ras = new RandomAccessStream(name);
+      byte[] b = new byte[4];
+      ras.readFully(b);
+      ras.close();
+      return isThisType(b);
+    }
+    catch (IOException e) { return false; }
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("ImarisReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    status("Verifying Imaris RAW format");
+
+    in.order(IS_LITTLE);
+
+    long magic = in.readInt();
+    if (magic != IMARIS_MAGIC_NUMBER) {
+      throw new FormatException("Imaris magic number not found.");
+    }
+
+    status("Reading header");
+
+    int version = in.readInt();
+    addMeta("Version", new Integer(version));
+    in.readInt();
+
+    addMeta("Image name", in.readString(128));
+
+    core.sizeX[0] = in.readShort();
+    core.sizeY[0] = in.readShort();
+    core.sizeZ[0] = in.readShort();
+
+    in.skipBytes(2);
+
+    core.sizeC[0] = in.readInt();
+    in.skipBytes(2);
+
+    addMeta("Original date", in.readString(32));
+
+    float dx = in.readFloat();
+    float dy = in.readFloat();
+    float dz = in.readFloat();
+    int mag = in.readShort();
+
+    addMeta("Image comment", in.readString(128));
+    int isSurvey = in.readInt();
+    addMeta("Survey performed", isSurvey == 0 ? "true" : "false");
+
+    status("Calculating image offsets");
+
+    core.imageCount[0] = core.sizeZ[0] * core.sizeC[0];
+    offsets = new int[core.imageCount[0]];
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      int offset = 332 + ((i + 1) * 168) + (i * core.sizeX[0] *
+        core.sizeY[0] * core.sizeZ[0]);
+      for (int j=0; j<core.sizeZ[0]; j++) {
+        offsets[i*core.sizeZ[0] + j] =
+          offset + (j * core.sizeX[0] * core.sizeY[0]);
+      }
+    }
+
+    status("Populating metadata");
+
+    core.sizeT[0] = core.imageCount[0] / (core.sizeC[0] * core.sizeZ[0]);
+    core.currentOrder[0] = "XYZCT";
+    core.rgb[0] = false;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = IS_LITTLE;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    core.pixelType[0] = FormatTools.UINT8;
+
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    store.setDimensions(new Float(dx), new Float(dy), new Float(dz),
+      new Float(1), new Float(1), null);
+
+    store.setObjective(null, null, null, null, new Float(mag), null, null);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/ImarisTiffReader.java b/loci/formats/in/ImarisTiffReader.java
new file mode 100644
index 0000000..28e0ff3
--- /dev/null
+++ b/loci/formats/in/ImarisTiffReader.java
@@ -0,0 +1,206 @@
+//
+// ImarisTiffReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * ImarisTiffReader is the file format reader for
+ * Imaris 5 files (TIFF variant).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ImarisTiffReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ImarisTiffReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class ImarisTiffReader extends BaseTiffReader {
+
+  // -- Constructor --
+
+  /** Constructs a new Imaris TIFF reader. */
+  public ImarisTiffReader() {
+    super("Imaris 5 (TIFF)", "ims");
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    // adapted from MetamorphReader.isThisType(byte[])
+    if (block.length < 3) return false;
+    if (block.length < 8) {
+      return true; // we have no way of verifying further
+    }
+
+    boolean little = (block[0] == 0x49 && block[1] == 0x49);
+
+    int ifdlocation = DataTools.bytesToInt(block, 4, little);
+    if (ifdlocation < 0) return false;
+    else if (ifdlocation + 1 > block.length) return true;
+    else {
+      int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, little);
+      for (int i=0; i<ifdnumber; i++) {
+        if (ifdlocation + 3 + (i*12) > block.length) {
+          return false;
+        }
+        else {
+          int ifdtag = DataTools.bytesToInt(block,
+            ifdlocation + 2 + (i*12), 2, little);
+          if (ifdtag == TiffTools.TILE_WIDTH) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (!super.isThisType(name, open)) return false; // check extension
+
+    // just checking the filename isn't enough to differentiate between
+    // Andor and regular TIFF; open the file and check more thoroughly
+    return open ? checkBytes(name, 1024) : true;
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("ImarisTiffReader.initFile(" + id + ")");
+    super.initFile(id);
+
+    in = new RandomAccessStream(id);
+    if (in.readShort() == 0x4949) in.order(true);
+
+    ifds = TiffTools.getIFDs(in);
+    if (ifds == null) throw new FormatException("No IFDs found");
+
+    // hack up the IFDs
+    //
+    // Imaris TIFFs store a thumbnail in the first IFD; each of the remaining
+    // IFDs defines a stack of tiled planes.
+
+    status("Verifying IFD sanity");
+
+    Vector tmp = new Vector();
+
+    for (int i=1; i<ifds.length; i++) {
+      long[] byteCounts = TiffTools.getIFDLongArray(ifds[i],
+        TiffTools.TILE_BYTE_COUNTS, false);
+      long[] offsets = TiffTools.getIFDLongArray(ifds[i],
+        TiffTools.TILE_OFFSETS, false);
+
+      for (int j=0; j<byteCounts.length; j++) {
+        Hashtable t = (Hashtable) ifds[i].clone();
+        TiffTools.putIFDValue(t, TiffTools.TILE_BYTE_COUNTS, byteCounts[j]);
+        TiffTools.putIFDValue(t, TiffTools.TILE_OFFSETS, offsets[j]);
+        tmp.add(t);
+      }
+    }
+
+    status("Populating metadata");
+
+    core.sizeC[0] = ifds.length - 1;
+    core.sizeZ[0] = tmp.size() / core.sizeC[0];
+    core.sizeT[0] = 1;
+    core.sizeX[0] =
+      TiffTools.getIFDIntValue(ifds[1], TiffTools.IMAGE_WIDTH, false, 0);
+    core.sizeY[0] =
+      TiffTools.getIFDIntValue(ifds[1], TiffTools.IMAGE_LENGTH, false, 0);
+
+    ifds = (Hashtable[]) tmp.toArray(new Hashtable[0]);
+    core.imageCount[0] = core.sizeC[0] * core.sizeZ[0];
+    core.currentOrder[0] = "XYZCT";
+    core.interleaved[0] = false;
+    core.rgb[0] =
+      core.imageCount[0] != core.sizeZ[0] * core.sizeC[0] * core.sizeT[0];
+
+    int bitsPerSample = TiffTools.getIFDIntValue(ifds[0],
+      TiffTools.BITS_PER_SAMPLE);
+    int bitFormat = TiffTools.getIFDIntValue(ifds[0], TiffTools.SAMPLE_FORMAT);
+
+    while (bitsPerSample % 8 != 0) bitsPerSample++;
+    if (bitsPerSample == 24 || bitsPerSample == 48) bitsPerSample /= 3;
+
+    if (bitFormat == 3) core.pixelType[0] = FormatTools.FLOAT;
+    else if (bitFormat == 2) {
+      switch (bitsPerSample) {
+        case 8:
+          core.pixelType[0] = FormatTools.INT8;
+          break;
+        case 16:
+          core.pixelType[0] = FormatTools.INT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.INT32;
+          break;
+      }
+    }
+    else {
+      switch (bitsPerSample) {
+        case 8:
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 16:
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.UINT32;
+          break;
+      }
+    }
+
+    status("Parsing comment");
+
+    String comment = (String) getMeta("Comment");
+
+    // likely an INI-style comment, although we can't be sure
+
+    if (comment != null && comment.startsWith("[")) {
+      // parse key/value pairs
+      StringTokenizer st = new StringTokenizer(comment, "\n");
+      while (st.hasMoreTokens()) {
+        String line = st.nextToken();
+        int equals = line.indexOf("=");
+        if (equals < 0) continue;
+        String key = line.substring(0, equals);
+        String value = line.substring(equals + 1);
+        addMeta(key.trim(), value.trim());
+      }
+      metadata.remove("Comment");
+    }
+
+    MetadataStore store = getMetadataStore();
+    FormatTools.populatePixels(store, this);
+  }
+
+}
diff --git a/loci/formats/in/ImprovisionTiffReader.java b/loci/formats/in/ImprovisionTiffReader.java
new file mode 100644
index 0000000..220aa08
--- /dev/null
+++ b/loci/formats/in/ImprovisionTiffReader.java
@@ -0,0 +1,205 @@
+//
+// ImprovisionTiffReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * ImprovisionTiffReader is the file format reader for
+ * Improvision TIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ImprovisionTiffReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ImprovisionTiffReader.java">SVN</a></dd></dl>
+ */
+public class ImprovisionTiffReader extends BaseTiffReader {
+
+  // -- Fields --
+
+  private String[] cNames;
+  private int pixelSizeT;
+
+  // -- Constructor --
+
+  public ImprovisionTiffReader() {
+    super("Improvision TIFF", new String[] {"tif", "tiff"});
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (!super.isThisType(name, open)) return false; // check extension
+    if (!open) return true; // not allowed to check the file contents
+
+    // just checking the filename isn't enough to differentiate between
+    // Improvision and regular TIFF; open the file and check more thoroughly
+    try {
+      RandomAccessStream ras = new RandomAccessStream(name);
+      Hashtable ifd = TiffTools.getFirstIFD(ras);
+      ras.close();
+      if (ifd == null) return false;
+
+      String comment =
+        (String) ifd.get(new Integer(TiffTools.IMAGE_DESCRIPTION));
+      return comment == null ? false : comment.indexOf("Improvision") != -1;
+    }
+    catch (IOException exc) {
+      if (debug) trace(exc);
+      return false;
+    }
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    put("Improvision", "yes");
+
+    // parse key/value pairs in the comment
+    String comment = (String) getMeta("Comment");
+    if (comment != null) {
+      StringTokenizer st = new StringTokenizer(comment, "\n");
+      while (st.hasMoreTokens()) {
+        String line = st.nextToken();
+        int equals = line.indexOf("=");
+        if (equals < 0) continue;
+        String key = line.substring(0, equals);
+        String value = line.substring(equals + 1);
+        addMeta(key, value);
+      }
+      metadata.remove("Comment");
+    }
+
+    String tz = (String) getMeta("TotalZPlanes");
+    String tc = (String) getMeta("TotalChannels");
+    String tt = (String) getMeta("TotalTimepoints");
+
+    if (tz == null) tz = "1";
+    if (tc == null) tc = "1";
+    if (tt == null) tt = "1";
+
+    core.sizeZ[0] = Integer.parseInt(tz);
+    core.sizeC[0] = Integer.parseInt(tc);
+    core.sizeT[0] = Integer.parseInt(tt);
+
+    if (core.sizeZ[0] * core.sizeC[0] * core.sizeT[0] < core.imageCount[0]) {
+      core.sizeC[0] = core.imageCount[0];
+    }
+
+    // parse each of the comments to determine axis ordering
+
+    long[] stamps = new long[ifds.length];
+    int[][] coords = new int[ifds.length][3];
+
+    cNames = new String[core.sizeC[0]];
+
+    for (int i=0; i<ifds.length; i++) {
+      comment = (String) TiffTools.getIFDValue(ifds[i],
+        TiffTools.IMAGE_DESCRIPTION);
+      comment = comment.replaceAll("\r\n", "\n");
+      comment = comment.replaceAll("\r", "\n");
+      StringTokenizer st = new StringTokenizer(comment, "\n");
+      String channelName = null;
+      while (st.hasMoreTokens()) {
+        String line = st.nextToken();
+        int equals = line.indexOf("=");
+        if (equals < 0) continue;
+        String key = line.substring(0, equals);
+        String value = line.substring(equals + 1);
+
+        if (key.equals("TimeStampMicroSeconds")) {
+          stamps[i] = Long.parseLong(value);
+        }
+        else if (key.equals("ZPlane")) coords[i][0] = Integer.parseInt(value);
+        else if (key.equals("ChannelNo")) {
+          coords[i][1] = Integer.parseInt(value);
+        }
+        else if (key.equals("TimepointName")) {
+          coords[i][2] = Integer.parseInt(value);
+        }
+        else if (key.equals("ChannelName")) {
+          channelName = value;
+        }
+        else if (key.equals("ChannelNo")) {
+          int ndx = Integer.parseInt(value);
+          if (cNames[ndx] == null) cNames[ndx] = channelName;
+        }
+      }
+    }
+    // determine average time per plane
+
+    long sum = 0;
+    for (int i=1; i<stamps.length; i++) {
+      long diff = stamps[i] - stamps[i - 1];
+      if (diff > 0) sum += diff;
+    }
+    pixelSizeT = (int) (sum / core.sizeT[0]);
+
+    // determine dimension order
+
+    core.currentOrder[0] = "XY";
+    for (int i=1; i<coords.length; i++) {
+      int zDiff = coords[i][0] - coords[i - 1][0];
+      int cDiff = coords[i][1] - coords[i - 1][1];
+      int tDiff = coords[i][2] - coords[i - 1][2];
+
+      if (zDiff > 0 && core.currentOrder[0].indexOf("Z") < 0) {
+        core.currentOrder[0] += "Z";
+      }
+      if (cDiff > 0 && core.currentOrder[0].indexOf("C") < 0) {
+        core.currentOrder[0] += "C";
+      }
+      if (tDiff > 0 && core.currentOrder[0].indexOf("T") < 0) {
+        core.currentOrder[0] += "T";
+      }
+      if (core.currentOrder[0].length() == 5) break;
+    }
+
+    if (core.currentOrder[0].indexOf("Z") < 0) core.currentOrder[0] += "Z";
+    if (core.currentOrder[0].indexOf("C") < 0) core.currentOrder[0] += "C";
+    if (core.currentOrder[0].indexOf("T") < 0) core.currentOrder[0] += "T";
+  }
+
+  /* @see BaseTiffReader#initMetadataStore() */
+  protected void initMetadataStore() {
+    super.initMetadataStore();
+    MetadataStore store = getMetadataStore();
+
+    FormatTools.populatePixels(store, this);
+
+    float fx = Float.parseFloat((String) getMeta("XCalibrationMicrons"));
+    float fy = Float.parseFloat((String) getMeta("YCalibrationMicrons"));
+    float fz = Float.parseFloat((String) getMeta("ZCalibrationMicrons"));
+
+    store.setDimensions(new Float(fx), new Float(fy), new Float(fz),
+      null, new Float(pixelSizeT / 1000000.0), null);
+  }
+
+}
diff --git a/loci/formats/in/JPEGReader.java b/loci/formats/in/JPEGReader.java
new file mode 100644
index 0000000..190c27a
--- /dev/null
+++ b/loci/formats/in/JPEGReader.java
@@ -0,0 +1,46 @@
+//
+// JPEGReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+/**
+ * JPEGReader is the file format reader for JPEG images.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/JPEGReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/JPEGReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class JPEGReader extends ImageIOReader {
+
+  // -- Constructor --
+
+  /** Constructs a new JPEGReader. */
+  public JPEGReader() {
+    super("Joint Photographic Experts Group",
+      new String[] {"jpg", "jpeg", "jpe"});
+  }
+
+}
diff --git a/loci/formats/in/KhorosReader.java b/loci/formats/in/KhorosReader.java
new file mode 100644
index 0000000..540b204
--- /dev/null
+++ b/loci/formats/in/KhorosReader.java
@@ -0,0 +1,185 @@
+//
+// KhorosReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * Reader for Khoros XV files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/KhorosReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/KhorosReader.java">SVN</a></dd></dl>
+ */
+public class KhorosReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Global lookup table. */
+  private byte[] lut;
+
+  /** Image offset. */
+  private long offset;
+
+  // -- Constructor --
+
+  /** Constructs a new Khoros reader. */
+  public KhorosReader() { super("Khoros XV", "xv"); }
+
+  // -- FormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return block[0] == (byte) 0xab && block[1] == 1;
+  }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    if (lut == null) return null;
+    byte[][] table = new byte[3][lut.length / 3];
+    int next = 0;
+    for (int i=0; i<table[0].length; i++) {
+      for (int j=0; j<table.length; j++) {
+        table[j][i] = lut[next++];
+      }
+    }
+    return table;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int bufSize = core.sizeX[0] * core.sizeY[0] *
+      FormatTools.getBytesPerPixel(core.pixelType[0]);
+
+    in.seek(offset + no * bufSize);
+    in.read(buf, 0, bufSize);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#close() */
+  public void close() throws IOException {
+    super.close();
+    lut = null;
+    offset = 0;
+  }
+
+  /** Initialize the given file. */
+  protected void initFile(String id) throws FormatException, IOException {
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    in.skipBytes(4);
+    in.order(true);
+    int dependency = in.readInt();
+
+    addMeta("Comment", in.readString(512));
+
+    in.order(dependency == 4 || dependency == 8);
+
+    core.sizeX[0] = in.readInt();
+    core.sizeY[0] = in.readInt();
+    in.skipBytes(28);
+    core.imageCount[0] = in.readInt();
+    if (core.imageCount[0] == 0) core.imageCount[0] = 1;
+    core.sizeC[0] = in.readInt();
+
+    int type = in.readInt();
+
+    switch (type) {
+      case 0:
+      case 1:
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 2:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      case 4:
+        core.pixelType[0] = FormatTools.UINT32;
+        break;
+      case 5:
+        core.pixelType[0] = FormatTools.FLOAT;
+        break;
+      case 9:
+        core.pixelType[0] = FormatTools.DOUBLE;
+        break;
+      default: throw new FormatException("Unsupported pixel type : " + type);
+    }
+
+    in.skipBytes(12);
+    int c = in.readInt();
+    if (c > 1) {
+      core.sizeC[0] = c;
+      int n = in.readInt();
+      lut = new byte[n * c];
+      in.skipBytes(436);
+
+      for (int i=0; i<lut.length; i++) {
+        int value = in.read();
+        if (i < n) {
+          lut[i*3] = (byte) value;
+          lut[i*3 + 1] = (byte) value;
+          lut[i*3 + 2] = (byte) value;
+        }
+        else if (i < n*2) {
+          lut[(i % n)*3 + 1] = (byte) value;
+        }
+        else if (i < n*3) {
+          lut[(i % n)*3 + 2] = (byte) value;
+        }
+      }
+    }
+    else in.skipBytes(440);
+    offset = in.getFilePointer();
+
+    core.sizeZ[0] = core.imageCount[0];
+    core.sizeT[0] = 1;
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = dependency == 4 || dependency == 8;
+    core.currentOrder[0] = "XYCZT";
+    core.indexed[0] = lut != null;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/LIFReader.java b/loci/formats/in/LIFReader.java
new file mode 100644
index 0000000..dabac4b
--- /dev/null
+++ b/loci/formats/in/LIFReader.java
@@ -0,0 +1,687 @@
+//
+// LIFReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import loci.formats.*;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * LIFReader is the file format reader for Leica LIF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/LIFReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/LIFReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class LIFReader extends FormatReader {
+
+  // -- Constants --
+
+  /** Factory for generating SAX parsers. */
+  public static final SAXParserFactory SAX_FACTORY =
+    SAXParserFactory.newInstance();
+
+  // -- Fields --
+
+  /** Offsets to memory blocks, paired with their corresponding description. */
+  protected Vector offsets;
+
+  /** Bits per pixel. */
+  private int[] bitsPerPixel;
+
+  /** Extra dimensions. */
+  private int[] extraDimensions;
+
+  private int bpp;
+  private Vector xcal;
+  private Vector ycal;
+  private Vector zcal;
+  private Vector seriesNames;
+  private Vector containerNames;
+  private Vector containerCounts;
+
+  // -- Constructor --
+
+  /** Constructs a new Leica LIF reader. */
+  public LIFReader() { super("Leica Image File Format", "lif"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return block[0] == 0x70;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    long offset = ((Long) offsets.get(series)).longValue();
+    in.seek(offset + core.sizeX[series] * core.sizeY[series] * no *
+      FormatTools.getBytesPerPixel(getPixelType()) * getRGBChannelCount());
+
+    in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("LIFReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    offsets = new Vector();
+
+    core.littleEndian[0] = true;
+    in.order(core.littleEndian[0]);
+
+    xcal = new Vector();
+    ycal = new Vector();
+    zcal = new Vector();
+
+    // read the header
+
+    status("Reading header");
+
+    byte checkOne = (byte) in.read();
+    in.skipBytes(2);
+    byte checkTwo = (byte) in.read();
+    if (checkOne != 0x70 && checkTwo != 0x70) {
+      throw new FormatException(id + " is not a valid Leica LIF file");
+    }
+
+    in.skipBytes(4);
+
+    // read and parse the XML description
+
+    if (in.read() != 0x2a) {
+      throw new FormatException("Invalid XML description");
+    }
+
+    // number of Unicode characters in the XML block
+
+    int nc = in.readInt();
+    String xml = DataTools.stripString(in.readString(nc * 2));
+
+    status("Finding image offsets");
+
+    while (in.getFilePointer() < in.length()) {
+      if (in.readInt() != 0x70) {
+        throw new FormatException("Invalid Memory Block");
+      }
+
+      in.skipBytes(4);
+      if (in.read() != 0x2a) {
+        throw new FormatException("Invalid Memory Description");
+      }
+
+      long blockLength = in.readInt();
+      if (in.read() != 0x2a) {
+        in.seek(in.getFilePointer() - 5);
+        blockLength = in.readLong();
+        if (in.read() != 0x2a) {
+          throw new FormatException("Invalid Memory Description");
+        }
+      }
+
+      int descrLength = in.readInt();
+      in.skipBytes(descrLength * 2);
+
+      if (blockLength > 0) {
+        offsets.add(new Long(in.getFilePointer()));
+      }
+      long skipped = 0;
+      while (skipped < blockLength) {
+        if (blockLength - skipped > 4096) {
+          skipped += in.skipBytes(4096);
+        }
+        else {
+          skipped += in.skipBytes((int) (blockLength - skipped));
+        }
+      }
+    }
+    initMetadata(xml);
+  }
+
+  // -- Helper methods --
+
+  /** Parses a string of XML and puts the values in a Hashtable. */
+  private void initMetadata(String xml) throws FormatException, IOException {
+    // parse raw key/value pairs - adapted from FlexReader
+
+    containerNames = new Vector();
+    containerCounts = new Vector();
+    seriesNames = new Vector();
+
+    LIFHandler handler = new LIFHandler();
+
+    xml = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><LEICA>" + xml +
+      "</LEICA>";
+
+    // strip out invalid characters
+    for (int i=0; i<xml.length(); i++) {
+      char c = xml.charAt(i);
+      if (Character.isISOControl(c) || !Character.isDefined(c)) {
+        xml = xml.replace(c, ' ');
+      }
+    }
+
+    try {
+      SAXParser parser = SAX_FACTORY.newSAXParser();
+      parser.parse(new ByteArrayInputStream(xml.getBytes()), handler);
+    }
+    catch (ParserConfigurationException exc) {
+      throw new FormatException(exc);
+    }
+    catch (SAXException exc) {
+      throw new FormatException(exc);
+    }
+
+    Vector elements = new Vector();
+
+    status("Populating native metadata");
+
+    // first parse each element in the XML string
+
+    StringTokenizer st = new StringTokenizer(xml, ">");
+    while (st.hasMoreTokens()) {
+      String token = st.nextToken();
+      elements.add(token.substring(1));
+    }
+
+    // the first element contains version information
+
+    String token = (String) elements.get(0);
+    String key = token.substring(0, token.indexOf("\""));
+    String value = token.substring(token.indexOf("\"") + 1, token.length()-1);
+    addMeta(key, value);
+
+    // what we have right now is a vector of XML elements, which need to
+    // be parsed into the appropriate image dimensions
+
+    int ndx = 1;
+
+    // the image data we need starts with the token "ElementName='blah'" and
+    // ends with the token "/ImageDescription"
+
+    int numDatasets = 0;
+    Vector widths = new Vector();
+    Vector heights = new Vector();
+    Vector zs = new Vector();
+    Vector ts = new Vector();
+    Vector channels = new Vector();
+    Vector bps = new Vector();
+    Vector extraDims = new Vector();
+
+    while (ndx < elements.size()) {
+      token = (String) elements.get(ndx);
+
+      // if the element contains a key/value pair, parse it and put it in
+      // the metadata hashtable
+
+      if (token.startsWith("ScannerSettingRecord")) {
+        if (token.indexOf("csScanMode") != -1) {
+          int index = token.indexOf("Variant") + 7;
+          String ordering = token.substring(index + 2,
+            token.indexOf("\"", index + 3));
+          ordering = ordering.toLowerCase();
+
+          if (ordering.indexOf("x") == -1 || ordering.indexOf("y") == -1 ||
+            ordering.indexOf("xy") == -1)
+          {
+            int xPos = ordering.indexOf("x");
+            int yPos = ordering.indexOf("y");
+            int zPos = ordering.indexOf("z");
+            int tPos = ordering.indexOf("t");
+
+            if (xPos < 0) xPos = 0;
+            if (yPos < 0) yPos = 1;
+            if (zPos < 0) zPos = 2;
+            if (tPos < 0) tPos = 3;
+
+            int x = ((Integer) widths.get(widths.size() - 1)).intValue();
+            int y = ((Integer) heights.get(widths.size() - 1)).intValue();
+            int z = ((Integer) zs.get(widths.size() - 1)).intValue();
+            int t = ((Integer) ts.get(widths.size() - 1)).intValue();
+
+            int[] dimensions = {x, y, z, t};
+
+            x = dimensions[xPos];
+            y = dimensions[yPos];
+            z = dimensions[zPos];
+            t = dimensions[tPos];
+
+            widths.setElementAt(new Integer(x), widths.size() - 1);
+            heights.setElementAt(new Integer(y), heights.size() - 1);
+            zs.setElementAt(new Integer(z), zs.size() - 1);
+            ts.setElementAt(new Integer(t), ts.size() - 1);
+          }
+        }
+        else if (token.indexOf("dblVoxel") != -1) {
+          int index = token.indexOf("Variant") + 7;
+          String size = token.substring(index + 2,
+            token.indexOf("\"", index + 3));
+          float cal = Float.parseFloat(size) * 1000000;
+          if (token.indexOf("X") != -1) xcal.add(new Float(cal));
+          else if (token.indexOf("Y") != -1) ycal.add(new Float(cal));
+          else if (token.indexOf("Z") != -1) zcal.add(new Float(cal));
+        }
+      }
+      else if (token.startsWith("Element Name")) {
+        // loop until we find "/ImageDescription"
+
+        numDatasets++;
+        int numChannels = 0;
+        int extras = 1;
+
+        while (token.indexOf("/ImageDescription") == -1) {
+          if (token.indexOf("=") != -1) {
+            // create a small hashtable to store just this element's data
+
+            if (token.startsWith("Element Name")) {
+              // hack to override first series name
+              int idx = numDatasets - 1;
+              if (idx >= seriesNames.size()) {
+                numDatasets = seriesNames.size();
+                idx = numDatasets - 1;
+              }
+            }
+
+            Hashtable tmp = new Hashtable();
+            while (token.length() > 2) {
+              key = token.substring(0, token.indexOf("\"") - 1);
+              value = token.substring(token.indexOf("\"") + 1,
+                token.indexOf("\"", token.indexOf("\"") + 1));
+
+              token = token.substring(key.length() + value.length() + 3);
+
+              key = key.trim();
+              value = value.trim();
+              tmp.put(key, value);
+            }
+
+            if (tmp.get("ChannelDescription DataType") != null) {
+              // found channel description block
+              numChannels++;
+              if (numChannels == 1) {
+                bps.add(new Integer((String) tmp.get("Resolution")));
+              }
+            }
+            else if (tmp.get("DimensionDescription DimID") != null) {
+              // found dimension description block
+
+              int w = Integer.parseInt((String) tmp.get("NumberOfElements"));
+              int id = Integer.parseInt((String)
+                tmp.get("DimensionDescription DimID"));
+
+              switch (id) {
+                case 1:
+                  widths.add(new Integer(w));
+                  break;
+                case 2:
+                  heights.add(new Integer(w));
+                  break;
+                case 3:
+                  zs.add(new Integer(w));
+                  break;
+                case 4:
+                  ts.add(new Integer(w));
+                  break;
+                default:
+                  extras *= w;
+              }
+            }
+          }
+
+          ndx++;
+          if (elements != null && ndx < elements.size()) {
+            token = (String) elements.get(ndx);
+          }
+          else break;
+        }
+        extraDims.add(new Integer(extras));
+        if (numChannels == 0) numChannels++;
+        channels.add(new Integer(numChannels));
+
+        if (widths.size() < numDatasets && heights.size() < numDatasets) {
+          numDatasets--;
+        }
+        else {
+          if (widths.size() < numDatasets) widths.add(new Integer(1));
+          if (heights.size() < numDatasets) heights.add(new Integer(1));
+          if (zs.size() < numDatasets) zs.add(new Integer(1));
+          if (ts.size() < numDatasets) ts.add(new Integer(1));
+          if (bps.size() < numDatasets) bps.add(new Integer(8));
+        }
+      }
+      ndx++;
+    }
+
+    numDatasets = widths.size();
+
+    bitsPerPixel = new int[numDatasets];
+    extraDimensions = new int[numDatasets];
+
+    // Populate metadata store
+
+    status("Populating metadata");
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    core = new CoreMetadata(numDatasets);
+    Arrays.fill(core.orderCertain, true);
+
+    for (int i=0; i<numDatasets; i++) {
+      core.sizeX[i] = ((Integer) widths.get(i)).intValue();
+      core.sizeY[i] = ((Integer) heights.get(i)).intValue();
+      core.sizeZ[i] = ((Integer) zs.get(i)).intValue();
+      core.sizeC[i] = ((Integer) channels.get(i)).intValue();
+      core.sizeT[i] = ((Integer) ts.get(i)).intValue();
+      core.currentOrder[i] =
+        (core.sizeZ[i] > core.sizeT[i]) ? "XYCZT" : "XYCTZ";
+
+      bitsPerPixel[i] = ((Integer) bps.get(i)).intValue();
+      extraDimensions[i] = ((Integer) extraDims.get(i)).intValue();
+
+      if (extraDimensions[i] > 1) {
+        if (core.sizeZ[i] == 1) core.sizeZ[i] = extraDimensions[i];
+        else core.sizeT[i] *= extraDimensions[i];
+        extraDimensions[i] = 1;
+      }
+
+      core.metadataComplete[i] = true;
+      core.littleEndian[i] = true;
+      core.rgb[i] = false;
+      core.interleaved[i] = false;
+      core.imageCount[i] = core.sizeZ[i] * core.sizeT[i];
+      core.imageCount[i] *= core.sizeC[i];
+      core.indexed[i] = false;
+      core.falseColor[i] = false;
+
+      while (bitsPerPixel[i] % 8 != 0) bitsPerPixel[i]++;
+      switch (bitsPerPixel[i]) {
+        case 8:
+          core.pixelType[i] = FormatTools.UINT8;
+          break;
+        case 16:
+          core.pixelType[i] = FormatTools.UINT16;
+          break;
+        case 32:
+          core.pixelType[i] = FormatTools.FLOAT;
+          break;
+      }
+
+      Integer ii = new Integer(i);
+
+      String seriesName = (String) seriesNames.get(i);
+      if (seriesName == null || seriesName.trim().length() == 0) {
+        seriesName = "Series " + (i + 1);
+      }
+      store.setImage(seriesName, null, null, ii);
+
+      FormatTools.populatePixels(store, this);
+
+      Float xf = i < xcal.size() ? (Float) xcal.get(i) : null;
+      Float yf = i < ycal.size() ? (Float) ycal.get(i) : null;
+      Float zf = i < zcal.size() ? (Float) zcal.get(i) : null;
+
+      store.setDimensions(xf, yf, zf, null, null, ii);
+      for (int j=0; j<core.sizeC[i]; j++) {
+        store.setLogicalChannel(j, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, ii);
+      }
+
+      String zoom = (String) getMeta(seriesName + " - dblZoom");
+      store.setDisplayOptions(zoom == null ? null : new Float(zoom),
+        new Boolean(core.sizeC[i] > 1), new Boolean(core.sizeC[i] > 1),
+        new Boolean(core.sizeC[i] > 2), new Boolean(isRGB()), null,
+        null, null, null, null, ii, null, null, null, null, null);
+
+      Enumeration keys = metadata.keys();
+      while (keys.hasMoreElements()) {
+        String k = (String) keys.nextElement();
+        if (k.startsWith((String) seriesNames.get(i) + " ")) {
+          core.seriesMetadata[i].put(k, metadata.get(k));
+        }
+      }
+    }
+  }
+
+  // -- Helper class --
+
+  /** SAX handler for parsing XML. */
+  class LIFHandler extends DefaultHandler {
+    private String series;
+    private String fullSeries;
+    private int count = 0;
+    private boolean firstElement = true;
+    private boolean dcroiOpen = false;
+
+    public void endElement(String uri, String localName, String qName) {
+      if (qName.equals("Element")) {
+        if (dcroiOpen) {
+          dcroiOpen = false;
+          return;
+        }
+        if (fullSeries.indexOf("/") != -1) {
+          fullSeries = fullSeries.substring(0, fullSeries.lastIndexOf("/"));
+        }
+        else fullSeries = "";
+      }
+    }
+
+    public void startElement(String uri, String localName, String qName,
+      Attributes attributes)
+    {
+      if (qName.equals("Element")) {
+        if (!attributes.getValue("Name").equals("DCROISet") && !firstElement) {
+          series = attributes.getValue("Name");
+          containerNames.add(series);
+          if (fullSeries == null || fullSeries.equals("")) fullSeries = series;
+          else fullSeries += "/" + series;
+        }
+        else if (firstElement) firstElement = false;
+
+        if (attributes.getValue("Name").equals("DCROISet")) {
+          dcroiOpen = true;
+        }
+      }
+      else if (qName.equals("Experiment")) {
+        for (int i=0; i<attributes.getLength(); i++) {
+          addMeta(attributes.getQName(i), attributes.getValue(i));
+        }
+      }
+      else if (qName.equals("Image")) {
+        containerNames.remove(series);
+        if (containerCounts.size() < containerNames.size()) {
+          containerCounts.add(new Integer(1));
+        }
+        else if (containerCounts.size() > 0) {
+          int ndx = containerCounts.size() - 1;
+          int n = ((Integer) containerCounts.get(ndx)).intValue();
+          containerCounts.setElementAt(new Integer(n + 1), ndx);
+        }
+        if (fullSeries == null || fullSeries.equals("")) fullSeries = series;
+        seriesNames.add(fullSeries);
+      }
+      else if (qName.equals("ChannelDescription")) {
+        String prefix = fullSeries + " - Channel " + count + " - ";
+        addMeta(prefix + "Min", attributes.getValue("Min"));
+        addMeta(prefix + "Max", attributes.getValue("Max"));
+        addMeta(prefix + "Resolution", attributes.getValue("Resolution"));
+        addMeta(prefix + "LUTName", attributes.getValue("LUTName"));
+        addMeta(prefix + "IsLUTInverted", attributes.getValue("IsLUTInverted"));
+        count++;
+      }
+      else if (qName.equals("DimensionDescription")) {
+        String prefix = fullSeries + " - Dimension " + count + " - ";
+        addMeta(prefix + "NumberOfElements",
+          attributes.getValue("NumberOfElements"));
+        addMeta(prefix + "Length", attributes.getValue("Length"));
+        addMeta(prefix + "Origin", attributes.getValue("Origin"));
+        addMeta(prefix + "DimID", attributes.getValue("DimID"));
+      }
+      else if (qName.equals("ScannerSettingRecord")) {
+        String key = attributes.getValue("Identifier") + " - " +
+          attributes.getValue("Description");
+        addMeta(fullSeries + " - " + key, attributes.getValue("Variant"));
+      }
+      else if (qName.equals("FilterSettingRecord")) {
+        String key = attributes.getValue("ObjectName") + " - " +
+          attributes.getValue("Description") + " - " +
+          attributes.getValue("Attribute");
+        addMeta(fullSeries + " - " + key, attributes.getValue("Variant"));
+      }
+      else if (qName.equals("ATLConfocalSettingDefinition")) {
+        if (fullSeries.endsWith(" - Master sequential setting")) {
+          fullSeries = fullSeries.replaceAll(" - Master sequential setting",
+            " - Sequential Setting 0");
+        }
+
+        if (fullSeries.indexOf("- Sequential Setting ") == -1) {
+          fullSeries += " - Master sequential setting";
+        }
+        else {
+          int ndx = fullSeries.indexOf(" - Sequential Setting ") + 22;
+          int n = Integer.parseInt(fullSeries.substring(ndx));
+          n++;
+          fullSeries = fullSeries.substring(0, ndx) + String.valueOf(n);
+        }
+
+        for (int i=0; i<attributes.getLength(); i++) {
+          addMeta(fullSeries + " - " + attributes.getQName(i),
+            attributes.getValue(i));
+        }
+      }
+      else if (qName.equals("Wheel")) {
+        String prefix = fullSeries + " - Wheel " + count + " - ";
+        addMeta(prefix + "Qualifier", attributes.getValue("Qualifier"));
+        addMeta(prefix + "FilterIndex", attributes.getValue("FilterIndex"));
+        addMeta(prefix + "FilterSpectrumPos",
+          attributes.getValue("FilterSpectrumPos"));
+        addMeta(prefix + "IsSpectrumTurnMode",
+          attributes.getValue("IsSpectrumTurnMode"));
+        addMeta(prefix + "IndexChanged", attributes.getValue("IndexChanged"));
+        addMeta(prefix + "SpectrumChanged",
+          attributes.getValue("SpectrumChanged"));
+        count++;
+      }
+      else if (qName.equals("WheelName")) {
+        String prefix = fullSeries + " - Wheel " + (count - 1) + " - WheelName ";
+        int ndx = 0;
+        while (getMeta(prefix + ndx) != null) ndx++;
+
+        addMeta(prefix + ndx, attributes.getValue("FilterName"));
+      }
+      else if (qName.equals("MultiBand")) {
+        String prefix = fullSeries + " - MultiBand Channel " +
+          attributes.getValue("Channel") + " - ";
+        addMeta(prefix + "LeftWorld", attributes.getValue("LeftWorld"));
+        addMeta(prefix + "RightWorld", attributes.getValue("RightWorld"));
+        addMeta(prefix + "DyeName", attributes.getValue("DyeName"));
+      }
+      else if (qName.equals("LaserLineSetting")) {
+        String prefix = fullSeries + " - LaserLine " +
+          attributes.getValue("LaserLine") + " - ";
+        addMeta(prefix + "IntensityDev", attributes.getValue("IntensityDev"));
+        addMeta(prefix + "IntensityLowDev",
+          attributes.getValue("IntensityLowDev"));
+        addMeta(prefix + "AOBSIntensityDev",
+          attributes.getValue("AOBSIntensityDev"));
+        addMeta(prefix + "AOBSIntensityLowDev",
+          attributes.getValue("AOBSIntensityLowDev"));
+        addMeta(prefix + "EnableDoubleMode",
+          attributes.getValue("EnableDoubleMode"));
+        addMeta(prefix + "LineIndex", attributes.getValue("LineIndex"));
+        addMeta(prefix + "Qualifier", attributes.getValue("Qualifier"));
+        addMeta(prefix + "SequenceIndex",
+          attributes.getValue("SequenceIndex"));
+      }
+      else if (qName.equals("Detector")) {
+        String prefix = fullSeries + " - Detector Channel " +
+          attributes.getValue("Channel") + " - ";
+        addMeta(prefix + "IsActive", attributes.getValue("IsActive"));
+        addMeta(prefix + "IsReferenceUnitActivatedForCorrection",
+          attributes.getValue("IsReferenceUnitActivatedForCorrection"));
+        addMeta(prefix + "Gain", attributes.getValue("Gain"));
+        addMeta(prefix + "Offset", attributes.getValue("Offset"));
+      }
+      else if (qName.equals("Laser")) {
+        String prefix = fullSeries + " Laser " +
+          attributes.getValue("LaserName") + " - ";
+        addMeta(prefix + "CanDoLinearOutputPower",
+          attributes.getValue("CanDoLinearOutputPower"));
+        addMeta(prefix + "OutputPower", attributes.getValue("OutputPower"));
+        addMeta(prefix + "Wavelength", attributes.getValue("Wavelength"));
+      }
+      else if (qName.equals("TimeStamp")) {
+
+        long high = Long.parseLong(attributes.getValue("HighInteger"));
+        long low = Long.parseLong(attributes.getValue("LowInteger"));
+
+        long stamp = 0;
+        high <<= 32;
+        if ((int) low < 0) {
+          low &= 0xffffffffL;
+        }
+        stamp = high + low;
+
+        long ms = stamp / 10000;
+
+        String n = String.valueOf(count);
+        while (n.length() < 4) n = "0" + n;
+        addMeta(fullSeries + " - TimeStamp " + n,
+          DataTools.convertDate(ms, DataTools.COBOL));
+        count++;
+      }
+      else if (qName.equals("ChannelScalingInfo")) {
+        String prefix = fullSeries + " - ChannelScalingInfo " + count + " - ";
+        addMeta(prefix + "WhiteValue", attributes.getValue("WhiteValue"));
+        addMeta(prefix + "BlackValue", attributes.getValue("BlackValue"));
+        addMeta(prefix + "GammaValue", attributes.getValue("GammaValue"));
+        addMeta(prefix + "Automatic", attributes.getValue("Automatic"));
+      }
+      else if (qName.equals("RelTimeStamp")) {
+        addMeta(fullSeries + " RelTimeStamp " + attributes.getValue("Frame"),
+          attributes.getValue("Time"));
+      }
+      else count = 0;
+    }
+  }
+
+}
diff --git a/loci/formats/in/LIMReader.java b/loci/formats/in/LIMReader.java
new file mode 100644
index 0000000..d2e0937
--- /dev/null
+++ b/loci/formats/in/LIMReader.java
@@ -0,0 +1,143 @@
+//
+// LIMReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * LIMReader is the file format reader for Laboratory Imaging/Nikon LIM files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="http://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/LIMReader.java">Trac</a>
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/LIMReader.java">SVN</a></dd></dl>
+ */
+public class LIMReader extends FormatReader {
+
+  // -- Fields --
+
+  private boolean isCompressed;
+
+  // -- Constructor --
+
+  /** Constructs a new LIM reader. */
+  public LIMReader() { super("Laboratory Imaging", "lim"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(0x94b);
+    in.read(buf);
+
+    // swap red and blue channels
+    if (core.rgb[0]) {
+      for (int i=0; i<buf.length/3; i++) {
+        byte tmp = buf[i*3];
+        buf[i*3] = buf[i*3 + 2];
+        buf[i*3 + 2] = tmp;
+      }
+    }
+
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("LIMReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    core.littleEndian[0] = true;
+    in.order(core.littleEndian[0]);
+
+    core.sizeX[0] = in.readShort() & 0x7fff;
+    core.sizeY[0] = in.readShort();
+    int bits = in.readShort();
+
+    while (bits % 8 != 0) bits++;
+    switch (bits) {
+      case 8:
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 16:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      case 24:
+        core.pixelType[0] = FormatTools.UINT8;
+        core.sizeC[0] = 3;
+        break;
+      case 32:
+        core.pixelType[0] = FormatTools.UINT32;
+        break;
+      case 48:
+        core.pixelType[0] = FormatTools.UINT16;
+        core.sizeC[0] = 3;
+        break;
+      default:
+        throw new FormatException("Unsupported bits per pixel : " + bits);
+    }
+
+    isCompressed = in.readShort() != 0;
+    if (isCompressed) {
+      throw new FormatException("Compressed LIM files not supported.");
+    }
+
+    core.imageCount[0] = 1;
+    core.sizeZ[0] = 1;
+    core.sizeT[0] = 1;
+    if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.currentOrder[0] = "XYZCT";
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.interleaved[0] = true;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    FormatTools.populatePixels(store, this);
+    store.setImage(currentId, null, null, null);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null);
+    }
+
+  }
+
+}
diff --git a/loci/formats/in/LegacyPictReader.java b/loci/formats/in/LegacyPictReader.java
new file mode 100644
index 0000000..3d3e39d
--- /dev/null
+++ b/loci/formats/in/LegacyPictReader.java
@@ -0,0 +1,123 @@
+//
+// LegacyPictReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * LegacyPictReader is the old file format reader for Apple PICT files.
+ * To use it, QuickTime for Java must be installed.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/LegacyPictReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/LegacyPictReader.java">SVN</a></dd></dl>
+ */
+public class LegacyPictReader extends FormatReader {
+
+  // -- Static fields --
+
+  /** Helper for reading PICT data with QTJava library. */
+  private static LegacyQTTools qtTools = new LegacyQTTools();
+
+  // -- Constructor --
+
+  /** Constructs a new PICT reader. */
+  public LegacyPictReader() { super("PICT", "pict"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    buf = ImageTools.getBytes(openImage(no), false, 3);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    // read in PICT data
+    RandomAccessStream fin = new RandomAccessStream(currentId);
+    int len = (int) (fin.length() - 512);
+    byte[] bytes = new byte[len];
+    fin.skip(512);  // skip 512 byte PICT header
+    int read = 0;
+    int left = len;
+    while (left > 0) {
+      int r = fin.read(bytes, read, left);
+      read += r;
+      left -= r;
+    }
+    fin.close();
+    return ImageTools.makeBuffered(qtTools.pictToImage(bytes));
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException { }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("LegacyPictReader.initFile(" + id + ")");
+    super.initFile(id);
+    status("Populating metadata");
+    BufferedImage img = openImage(0);
+    core.sizeX[0] = img.getWidth();
+    core.sizeY[0] = img.getHeight();
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = img.getRaster().getNumBands();
+    core.sizeT[0] = 1;
+    core.pixelType[0] = FormatTools.INT8;
+    core.currentOrder[0] = "XYCZT";
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.interleaved[0] = false;
+    core.imageCount[0] = 1;
+    core.littleEndian[0] = false;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/LegacyQTReader.java b/loci/formats/in/LegacyQTReader.java
new file mode 100644
index 0000000..737eb0d
--- /dev/null
+++ b/loci/formats/in/LegacyQTReader.java
@@ -0,0 +1,239 @@
+//
+// LegacyQTReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImageProducer;
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+import loci.formats.*;
+
+/**
+ * LegacyQTReader is a file format reader for QuickTime movie files.
+ * To use it, QuickTime for Java must be installed.
+ *
+ * Much of this code was based on the QuickTime Movie Opener for ImageJ
+ * (available at http://rsb.info.nih.gov/ij/plugins/movie-opener.html).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/LegacyQTReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/LegacyQTReader.java">SVN</a></dd></dl>
+ */
+public class LegacyQTReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Instance of LegacyQTTools to handle QuickTime for Java detection. */
+  protected LegacyQTTools tools;
+
+  /** Reflection tool for QuickTime for Java calls. */
+  protected ReflectedUniverse r;
+
+  /** Time offset for each frame. */
+  protected int[] times;
+
+  /** Image containing current frame. */
+  protected Image image;
+
+  // -- Constructor --
+
+  /** Constructs a new QT reader. */
+  public LegacyQTReader() { super("QuickTime", "mov"); }
+
+  /** Constructs a new QT reader with the given id mappings. */
+  public LegacyQTReader(Hashtable idMap) {
+    super("QuickTime", "mov");
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) { return false; }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    buf = ImageTools.getBytes(openImage(no), false, 3);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    // paint frame into image
+    try {
+      r.setVar("time", times[no]);
+      r.exec("moviePlayer.setTime(time)");
+      r.exec("qtip.redraw(null)");
+      r.exec("qtip.updateConsumers(null)");
+    }
+    catch (ReflectException re) {
+      throw new FormatException("Open movie failed", re);
+    }
+    return ImageTools.makeBuffered(image);
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    try {
+      if (r.getVar("openMovieFile") != null) {
+        r.exec("openMovieFile.close()");
+        if (!fileOnly) {
+          r.exec("m.disposeQTObject()");
+          r.exec("imageTrack.disposeQTObject()");
+          r.exec("QTSession.close()");
+        }
+      }
+    }
+    catch (ReflectException e) {
+      IOException io = new IOException("Close movie failed");
+      io.initCause(e);
+      throw io;
+    }
+    if (!fileOnly) currentId = null;
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    close(false);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("LegacyQTReader.initFile(" + id + ")");
+
+    status("Checking for QuickTime Java");
+
+    if (tools == null) {
+      tools = new LegacyQTTools();
+      r = tools.getUniverse();
+    }
+    if (tools.isQTExpired()) {
+      throw new FormatException(LegacyQTTools.EXPIRED_QT_MSG);
+    }
+    if (!tools.canDoQT()) {
+      throw new FormatException(LegacyQTTools.NO_QT_MSG);
+    }
+
+    super.initFile(id);
+
+    status("Reading movie dimensions");
+    try {
+      r.exec("QTSession.open()");
+
+      // open movie file
+      Location file = new Location(id);
+      r.setVar("path", file.getAbsolutePath());
+      r.exec("qtf = new QTFile(path)");
+      r.exec("openMovieFile = OpenMovieFile.asRead(qtf)");
+      r.exec("m = Movie.fromFile(openMovieFile)");
+
+      int numTracks = ((Integer) r.exec("m.getTrackCount()")).intValue();
+      int trackMostLikely = 0;
+      int trackNum = 0;
+      while (++trackNum <= numTracks && trackMostLikely == 0) {
+        r.setVar("trackNum", trackNum);
+        r.exec("imageTrack = m.getTrack(trackNum)");
+        r.exec("d = imageTrack.getSize()");
+        Integer w = (Integer) r.exec("d.getWidth()");
+        if (w.intValue() > 0) trackMostLikely = trackNum;
+      }
+
+      r.setVar("trackMostLikely", trackMostLikely);
+      r.exec("imageTrack = m.getTrack(trackMostLikely)");
+      r.exec("d = imageTrack.getSize()");
+      Integer w = (Integer) r.exec("d.getWidth()");
+      Integer h = (Integer) r.exec("d.getHeight()");
+
+      r.exec("moviePlayer = new MoviePlayer(m)");
+      r.setVar("dim", new Dimension(w.intValue(), h.intValue()));
+      ImageProducer qtip = (ImageProducer)
+        r.exec("qtip = new QTImageProducer(moviePlayer, dim)");
+      image = Toolkit.getDefaultToolkit().createImage(qtip);
+
+      r.setVar("zero", 0);
+      r.setVar("one", 1f);
+      r.exec("timeInfo = new TimeInfo(zero, zero)");
+      r.exec("moviePlayer.setTime(zero)");
+      Vector v = new Vector();
+      int time = 0;
+      Integer q = new Integer(time);
+      do {
+        v.add(q);
+        r.exec("timeInfo = imageTrack.getNextInterestingTime(" +
+          "StdQTConstants.nextTimeMediaSample, timeInfo.time, one)");
+        q = (Integer) r.getVar("timeInfo.time");
+        time = q.intValue();
+      }
+      while (time >= 0);
+      core.imageCount[0] = v.size();
+      times = new int[core.imageCount[0]];
+      for (int i=0; i<times.length; i++) {
+        q = (Integer) v.elementAt(i);
+        times[i] = q.intValue();
+      }
+
+      status("Populating metadata");
+
+      BufferedImage img = ImageTools.makeBuffered(image);
+
+      core.sizeX[0] = img.getWidth();
+      core.sizeY[0] = img.getHeight();
+      core.sizeZ[0] = 1;
+      core.sizeC[0] = img.getRaster().getNumBands();
+      core.sizeT[0] = core.imageCount[0];
+      core.pixelType[0] = ImageTools.getPixelType(img);
+      core.currentOrder[0] = "XYCTZ";
+      core.rgb[0] = true;
+      core.interleaved[0] = false;
+      core.littleEndian[0] = false;
+      core.indexed[0] = false;
+      core.falseColor[0] = false;
+
+      MetadataStore store = getMetadataStore();
+      store.setImage(currentId, null, null, null);
+      FormatTools.populatePixels(store, this);
+
+      for (int i=0; i<core.sizeC[0]; i++) {
+        store.setLogicalChannel(i, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, null);
+      }
+    }
+    catch (ReflectException e) {
+      throw new FormatException("Open movie failed", e);
+    }
+  }
+
+}
diff --git a/loci/formats/in/LegacyZVIReader.java b/loci/formats/in/LegacyZVIReader.java
new file mode 100644
index 0000000..6c4e957
--- /dev/null
+++ b/loci/formats/in/LegacyZVIReader.java
@@ -0,0 +1,575 @@
+//
+// LegacyZVIReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Vector;
+import loci.formats.*;
+
+/**
+ * LegacyZVIReader is the legacy file format reader for Zeiss ZVI files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/LegacyZVIReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/LegacyZVIReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ * @author Michel Boudinot Michel dot boudinot at iaf.cnrs-gif.fr
+ */
+public class LegacyZVIReader extends FormatReader {
+
+  // -- Constants --
+
+  /** First few bytes of every ZVI file. */
+  private static final byte[] ZVI_SIG = {
+    -48, -49, 17, -32, -95, -79, 26, -31
+  };
+
+  /** Block identifying start of useful header information. */
+  private static final byte[] ZVI_MAGIC_BLOCK_1 = {65, 0, 16}; // 41 00 10
+
+  /** Block identifying second part of useful header information. */
+  private static final byte[] ZVI_MAGIC_BLOCK_2 = {65, 0, -128}; // 41 00 80
+
+  /** Block identifying third part of useful header information. */
+  private static final byte[] ZVI_MAGIC_BLOCK_3 = {32, 0, 16}; // 20 00 10
+
+  /** Memory buffer size in bytes, for reading from disk. */
+  private static final int BUFFER_SIZE = 8192;
+
+  /** String apologizing for the fact that this ZVI support still sucks. */
+  private static final String WHINING = "Sorry, " +
+    "ZVI support is still preliminary.  It will be improved as time permits.";
+
+  // -- Fields --
+
+  /** List of image blocks. */
+  private Vector blockList;
+
+  /** Bytes per pixel. */
+  private int bytesPerPixel;
+
+  /** Counters of image elements */
+  private int numZ = 0, numC = 0, numT = 0;
+
+  private int cFlag = 0, zFlag = 0, tFlag = 0;
+
+  // -- Constructor --
+
+  /** Constructs a new legacy ZVI reader. */
+  public LegacyZVIReader() { super("Legacy ZVI", "zvi"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block == null) return false;
+    int len = block.length < ZVI_SIG.length ? block.length : ZVI_SIG.length;
+    for (int i=0; i<len; i++) {
+      if (block[i] != ZVI_SIG[i]) return false;
+    }
+    return true;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    ZVIBlock zviBlock = (ZVIBlock) blockList.elementAt(no);
+    zviBlock.readBytes(in, buf);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    if (debug) debug("Reading image #" + no + "...");
+    ZVIBlock zviBlock = (ZVIBlock) blockList.elementAt(no);
+    return zviBlock.readImage(in);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("LegacyZVIReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(true);
+
+    // Highly questionable decoding strategy:
+    //
+    // Note that all byte ordering is little endian, includeing 4-byte header
+    // fields. Other examples: 16-bit data is LSB MSB, and 3-channel data is
+    // BGR instead of RGB.
+    //
+    // 1) Find image header byte sequence:
+    //    A) Find 41 00 10. (ZVI_MAGIC_BLOCK_1)
+    //    B) Skip 19 bytes of stuff.
+    //    C) Read 41 00 80. (ZVI_MAGIC_BLOCK_2)
+    //    D) Read 11 bytes of 00
+    //    E) Read potential header information:
+    //       - Z-slice (4 bytes)
+    //       - channel (4 bytes)
+    //       - timestep (4 bytes)
+    //    F) Read 108 bytes of 00.
+    //
+    // 2) If byte sequence is not as expected at any point (e.g.,
+    //    stuff that is supposed to be 00 isn't), start over at 1A.
+    //
+    // 3) Find 20 00 10. (ZVI_MAGIC_BLOCK_3)
+    //
+    // 4) Read more header information:
+    //    - width (4 bytes)
+    //    - height (4 bytes)
+    //    - ? (4 bytes; always 1)
+    //    - bytesPerPixel (4 bytes)
+    //    - pixelType (this is what the AxioVision software calls it)
+    //       - 1=24-bit (3 color components, 8-bit each)
+    //       - 3=8-bit (1 color component, 8-bit)
+    //       - 4=16-bit (1 color component, 16-bit)
+    //    - bitDepth (4 bytes--usually, but not always, bytesPerPixel * 8)
+    //
+    // 5) Read image data (width * height * bytesPerPixel)
+    //
+    // 6) Repeat the entire process until no more headers are identified.
+
+    long pos = 0;
+    blockList = new Vector();
+    Set zSet = new HashSet(); // to hold Z plan index collection.
+    Set cSet = new HashSet(); // to hold C channel index collection
+    Set tSet = new HashSet(); // to hold T time index collection.
+    numZ = 0;
+    numC = 0;
+    numT = 0;
+    int numI = 0;
+
+    while (true) {
+      // search for start of next image header
+      status("Searching for next image");
+      long header = findBlock(in, ZVI_MAGIC_BLOCK_1, pos);
+
+      if (header < 0) {
+        // no more potential headers found; we're done
+        break;
+      }
+      pos = header + ZVI_MAGIC_BLOCK_1.length;
+
+      if (debug) debug("Found potential image block: " + header);
+
+      // these byte don't matter
+      in.skipBytes(19);
+      pos += 19;
+
+      // these bytes should match ZVI_MAGIC_BLOCK_2
+      byte[] b = new byte[ZVI_MAGIC_BLOCK_2.length];
+      in.readFully(b);
+      boolean ok = true;
+      for (int i=0; i<b.length; i++) {
+        if (b[i] != ZVI_MAGIC_BLOCK_2[i]) {
+          ok = false;
+          break;
+        }
+        pos++;
+      }
+      if (!ok) continue;
+
+      // these bytes should be 00
+      b = new byte[11];
+      in.readFully(b);
+      for (int i=0; i<b.length; i++) {
+        if (b[i] != 0) {
+          ok = false;
+          break;
+        }
+        pos++;
+      }
+      if (!ok) continue;
+
+      // read potential header information
+      int theZ = in.readInt();
+      int theC = in.readInt();
+      int theT = in.readInt();
+      pos += 12;
+
+      // these byte should be 00
+      b = new byte[108];
+      in.readFully(b);
+      for (int i=0; i<b.length; i++) {
+        if (b[i] != 0) {
+          ok = false;
+          break;
+        }
+        pos++;
+      }
+      if (!ok) continue;
+
+      // everything checks out; looks like an image header to me
+      //+ (mb) decoding strategy modification
+      //      Some zvi images have the following structure:
+      //        ZVI_SIG                    Decoding:
+      //        ZVI_MAGIC_BLOCK_1
+      //        ZVI_MAGIC_BLOCK_2      <== Start of header information
+      //        - Z-slice (4 bytes)     -> theZ = 0
+      //        - channel (4 bytes)     -> theC = 0
+      //        - timestep (4 bytes)    -> theT = 0
+      //        ZVI_MAGIC_BLOCK_2      <==  Start of header information
+      //        - Z-slice (4 bytes)     -> theZ actual value
+      //        - channel (4 bytes)     -> theC actual value
+      //        - timestep (4 bytes)    -> theT actual value
+      //        ZVI_MAGIC_BLOCK_3      <== End of header information
+      //        ...
+      //
+      //        Two consecutive Start of header information ZVI_MAGIC_BLOCK_2
+      //        make test 3) of original decoding strategy fail. The first
+      //        null values are taken as theZ, theC and theT values, the
+      //        following actual values are ignored.
+      //        Parsing the rest of the file appears to be ok.
+      //
+      //        New decoding strategy looks for the last header information
+      //        ZVI_MAGIC_BLOCK_2 / ZVI_MAGIC_BLOCK_3 to get proper image
+      //        slice theZ, theC and theT values.
+
+      // these bytes don't matter
+      in.skipBytes(89);
+      pos += 89;
+
+      byte[] magic3 = new byte[ZVI_MAGIC_BLOCK_3.length];
+      in.readFully(magic3);
+      for (int i=0; i<magic3.length; i++) {
+        if (magic3[i] != ZVI_MAGIC_BLOCK_3[i]) {
+          ok = false;
+          break;
+        }
+      }
+      if (!ok) continue;
+      pos += ZVI_MAGIC_BLOCK_3.length;
+
+      status("Reading image header");
+
+      // read more header information
+      core.sizeX[0] = in.readInt();
+      core.sizeY[0] = in.readInt();
+      // don't know what this is for
+      int alwaysOne = in.readInt();
+      bytesPerPixel = in.readInt();
+      // not clear what this value signifies
+      int pixType = in.readInt();
+      // doesn't always equal bytesPerPixel * 8
+      int bitDepth = in.readInt();
+      pos += 24;
+
+      String type = "";
+      switch (pixType) {
+        case 1:
+          type = "8 bit rgb tuple, 24 bpp";
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 2:
+          type = "8 bit rgb quad, 32 bpp";
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 3:
+          type = "8 bit grayscale";
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 4:
+          type = "16 bit signed int, 16 bpp";
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 5:
+          type = "32 bit int, 32 bpp";
+          core.pixelType[0] = FormatTools.UINT32;
+          break;
+        case 6:
+          type = "32 bit float, 32 bpp";
+          core.pixelType[0] = FormatTools.FLOAT;
+          break;
+        case 7:
+          type = "64 bit float, 64 bpp";
+          core.pixelType[0] = FormatTools.DOUBLE;
+          break;
+        case 8:
+          type = "16 bit unsigned short triple, 48 bpp";
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 9:
+          type = "32 bit int triple, 96 bpp";
+          core.pixelType[0] = FormatTools.UINT32;
+          break;
+        default:
+          type = "undefined pixel type (" + pixType + ")";
+      }
+
+      addMeta("Width", new Integer(core.sizeX[0]));
+      addMeta("Height", new Integer(core.sizeY[0]));
+      addMeta("PixelType", type);
+      addMeta("BPP", new Integer(bytesPerPixel));
+
+      ZVIBlock zviBlock = new ZVIBlock(theZ, theC, theT, core.sizeX[0],
+        core.sizeY[0], alwaysOne, bytesPerPixel, pixType, bitDepth, pos);
+      if (debug) debug(zviBlock.toString());
+
+      // perform some checks on the header info
+      // populate Z, C and T index collections
+      zSet.add(new Integer(theZ));
+      cSet.add(new Integer(theC));
+      tSet.add(new Integer(theT));
+      numI++;
+      // sorry not a very clever way to find dimension order
+
+      if ((numI == 2) && (cSet.size() == 2)) cFlag = 1;
+      if ((numI == 2) && (zSet.size() == 2)) zFlag = 1;
+      if ((numI == 2) && (tSet.size() == 2)) tFlag = 1;
+
+      if ((numI % 3 == 0) && (zSet.size() > 1) && (cFlag == 1)) {
+        core.currentOrder[0] = "XYCZT";
+      }
+      if ((numI % 3 == 0) && (tSet.size() > 1) && (cFlag == 1)) {
+        core.currentOrder[0] = "XYCTZ";
+      }
+      if ((numI % 3 == 0) && (cSet.size() > 1) && (zFlag == 1)) {
+        core.currentOrder[0] = "XYZCT";
+      }
+      if ((numI % 3 == 0) && (tSet.size() > 1) && (zFlag == 1)) {
+        core.currentOrder[0] = "XYZTC";
+      }
+      if ((numI % 3 == 0) && (cSet.size() > 1) && (tFlag == 1)) {
+        core.currentOrder[0] = "XYTCZ";
+      }
+      if ((numI % 3 == 0) && (zSet.size() > 1) && (tFlag == 1)) {
+        core.currentOrder[0] = "XYTZC";
+      }
+
+      if (core.currentOrder[0] == null) core.currentOrder[0] = "XYZCT";
+
+      // save this image block's position
+      blockList.add(zviBlock);
+      pos += core.sizeX[0] * core.sizeY[0] * bytesPerPixel;
+
+      core.imageCount[0] = blockList.size();
+      core.sizeX[0] = openImage(0).getWidth();
+      core.sizeY[0] = openImage(0).getHeight();
+      core.sizeZ[0] = zSet.size();
+      core.sizeC[0] = cSet.size();
+      core.sizeT[0] = tSet.size();
+      core.rgb[0] = bytesPerPixel == 3 || bytesPerPixel > 4;
+      core.interleaved[0] = false;
+      core.littleEndian[0] = true;
+      core.indexed[0] = false;
+      core.falseColor[0] = false;
+
+      // Populate metadata store
+
+      MetadataStore store = getMetadataStore();
+      store.setImage(currentId, null, null, null);
+
+      FormatTools.populatePixels(store, this);
+
+      for (int i=0; i<core.sizeC[0]; i++) {
+        store.setLogicalChannel(i, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, null);
+      }
+    }
+
+    status("Verifying image count");
+
+    if (blockList.isEmpty()) {
+      throw new FormatException("No image data found." + WHINING);
+    }
+    // number of Z, C and T index
+    numZ = zSet.size();
+    numC = cSet.size();
+    numT = tSet.size();
+    if (numZ * numC * numT != blockList.size()) {
+      LogTools.println("Warning: image counts do not match. " + WHINING);
+    }
+  }
+
+  // -- Utility methods --
+
+  /**
+   * Finds the first occurence of the given byte block within the file,
+   * starting from the given file position.
+   */
+  private static long findBlock(RandomAccessStream in, byte[] block, long start)
+    throws IOException
+  {
+    long filePos = start;
+    long fileSize = in.length();
+    byte[] buf = new byte[BUFFER_SIZE];
+    long spot = -1;
+    int step = 0;
+    boolean found = false;
+    in.seek(start);
+
+    while (true) {
+      int len = (int) (fileSize - filePos);
+      if (len < 0) break;
+      if (len > buf.length) len = buf.length;
+      in.readFully(buf, 0, len);
+
+      for(int i=0; i<len; i++) {
+        if (buf[i] == block[step]) {
+          if (step == 0) {
+            // could be a match; flag this spot
+            spot = filePos + i;
+          }
+          step++;
+          if (step == block.length) {
+            // found complete match; done searching
+            found = true;
+            break;
+          }
+        }
+        else {
+          // no match; reset step indicator
+          spot = -1;
+          step = 0;
+        }
+      }
+      if (found) break;  // found a match; we're done
+      if (len < buf.length) break;  // EOF reached; we're done
+
+      filePos += len;
+    }
+
+    // set file pointer to byte immediately following pattern
+    if (spot >= 0) in.seek(spot + block.length);
+    return spot;
+  }
+
+  // -- Helper classes --
+
+  /** Contains information collected from a ZVI image header. */
+  private class ZVIBlock {
+    private int theZ, theC, theT;
+    private int width, height;
+    private int alwaysOne;
+    private int bytesPerPixel;
+    private int pixelType;
+    private int bitDepth;
+    private long imagePos;
+
+    private int numPixels;
+    private int imageSize;
+    private int numChannels;
+    private int bytesPerChannel;
+
+    public ZVIBlock(int theZ, int theC, int theT, int width, int height,
+      int alwaysOne, int bytesPerPixel, int pixelType, int bitDepth,
+      long imagePos)
+    {
+      this.theZ = theZ;
+      this.theC = theC;
+      this.theT = theT;
+      this.width = width;
+      this.height = height;
+      this.alwaysOne = alwaysOne;
+      this.bytesPerPixel = bytesPerPixel;
+      this.pixelType = pixelType;
+      this.bitDepth = bitDepth;
+      this.imagePos = imagePos;
+
+      numPixels = width * height;
+      imageSize = numPixels * bytesPerPixel;
+      numChannels = pixelType == 1 ? 3 : 1;  // a total shot in the dark
+      if (bytesPerPixel % numChannels != 0) {
+        LogTools.println("Warning: incompatible bytesPerPixel (" +
+          bytesPerPixel + ") and numChannels (" + numChannels +
+          "). Assuming grayscale data. " + WHINING);
+        numChannels = 1;
+      }
+      bytesPerChannel = bytesPerPixel / numChannels;
+    }
+
+    /** Reads in this block's image bytes from the given file. */
+    public byte[] readBytes(RandomAccessStream raf, byte[] buf)
+      throws IOException, FormatException
+    {
+      long fileSize = raf.length();
+      if (imagePos + imageSize > fileSize) {
+        throw new FormatException("File is not big enough to contain the " +
+          "pixels (width=" + width + "; height=" + height +
+          "; bytesPerPixel=" + bytesPerPixel + "; imagePos=" + imagePos +
+          "; fileSize=" + fileSize + "). " + WHINING);
+      }
+      if (buf.length < imageSize) throw new FormatException("Buffer too small");
+
+      // read image
+      raf.seek(imagePos);
+      raf.readFully(buf);
+      return buf;
+    }
+
+    /** Reads in this block's image data from the given file. */
+    public BufferedImage readImage(RandomAccessStream raf)
+      throws IOException, FormatException
+    {
+      byte[] imageBytes = readBytes(raf, new byte[imageSize]);
+
+      // convert image bytes into BufferedImage
+      if (bytesPerPixel > 4) {
+        numChannels = bytesPerPixel / 2;
+        bytesPerPixel /= numChannels;
+        bytesPerChannel = bytesPerPixel;
+      }
+      int index = 0;
+      short[][] samples = new short[numChannels][numPixels * bytesPerPixel];
+      for (int i=0; i<numPixels; i++) {
+        for (int c=numChannels-1; c>=0; c--) {
+          byte[] b = new byte[bytesPerChannel];
+          System.arraycopy(imageBytes, index, b, 0, bytesPerChannel);
+          index += bytesPerChannel;
+          // our zvi images are 16 bit per pixel (BitsPerPixel) but
+          // with an Acquisition Bit Depth of 12
+          samples[c][i] = (short) (DataTools.bytesToShort(b, true) * 8);
+        }
+      }
+      return ImageTools.makeImage(samples, width, height);
+    }
+
+    public String toString() {
+      return "Image header block:\n" +
+        "  theZ = " + theZ + "\n" + "  theC = " + theC + "\n" +
+        "  theT = " + theT + "\n" + "  width = " + width + "\n" +
+        "  height = " + height + "\n" + "  alwaysOne = " + alwaysOne + "\n" +
+        "  bytesPerPixel = " + bytesPerPixel + "\n" +
+        "  pixelType = " + pixelType + "\n" + "  bitDepth = " + bitDepth;
+    }
+  }
+
+}
diff --git a/loci/formats/in/LeicaReader.java b/loci/formats/in/LeicaReader.java
new file mode 100644
index 0000000..528dfe1
--- /dev/null
+++ b/loci/formats/in/LeicaReader.java
@@ -0,0 +1,1069 @@
+//
+// LeicaReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * LeicaReader is the file format reader for Leica files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/LeicaReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/LeicaReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class LeicaReader extends FormatReader {
+
+  // -- Constants -
+
+  /** All Leica TIFFs have this tag. */
+  private static final int LEICA_MAGIC_TAG = 33923;
+
+  // -- Fields --
+
+  protected Hashtable[] ifds;
+
+  /** Array of IFD-like structures containing metadata. */
+  protected Hashtable[] headerIFDs;
+
+  /** Helper readers. */
+  protected TiffReader[][] tiff;
+
+  /** Array of image file names. */
+  protected Vector[] files;
+
+  /** Number of series in the file. */
+  private int numSeries;
+
+  /** Name of current LEI file */
+  private String leiFilename;
+
+  private int bpp;
+  private Vector seriesNames;
+
+  // -- Constructor --
+
+  /** Constructs a new Leica reader. */
+  public LeicaReader() {
+    super("Leica", new String[] {"lei", "tif", "tiff"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 4) return false;
+
+    if (block.length < 8) {
+      // we can only check whether it is a TIFF
+      return (block[0] == 0x49 && block[1] == 0x49 && block[2] == 0x49 &&
+        block[3] == 0x49) || (block[0] == 0x4d && block[1] == 0x4d &&
+        block[2] == 0x4d && block[3] == 0x4d);
+    }
+
+    int ifdlocation = DataTools.bytesToInt(block, 4, true);
+    if (ifdlocation < 0 || ifdlocation + 1 > block.length) {
+      return false;
+    }
+    else {
+      int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, true);
+      for (int i=0; i<ifdnumber; i++) {
+        if (ifdlocation + 3 + (i*12) > block.length) return false;
+        else {
+          int ifdtag = DataTools.bytesToInt(block,
+            ifdlocation + 2 + (i*12), 2, true);
+          if (ifdtag == LEICA_MAGIC_TAG) return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    tiff[0][0].setId((String) files[0].get(0));
+    return tiff[0][0].get8BitLookupTable();
+  }
+
+  /* @see loci.formats.IFormatReader#get16BitLookupTable() */
+  public short[][] get16BitLookupTable() throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    tiff[0][0].setId((String) files[0].get(0));
+    return tiff[0][0].get16BitLookupTable();
+  }
+
+  /* @see loci.formats.IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    return id.toLowerCase().endsWith(".lei") ? FormatTools.MUST_GROUP :
+      FormatTools.CAN_GROUP;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    tiff[series][no].setId((String) files[series].get(no));
+    return tiff[series][no].openBytes(0, buf);
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    Vector v = new Vector();
+    v.add(leiFilename);
+    for (int i=0; i<files.length; i++) {
+      for (int j=0; j<files[i].size(); j++) {
+        v.add(files[i].get(j));
+      }
+    }
+    return (String[]) v.toArray(new String[0]);
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly) {
+      if (in != null) in.close();
+      if (tiff != null) {
+        for (int i=0; i<tiff.length; i++) {
+          if (tiff[i] != null) {
+            for (int j=0; j<tiff[i].length; j++) {
+              if (tiff[i][j] != null) tiff[i][j].close(fileOnly);
+            }
+          }
+        }
+      }
+    }
+    else close();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    String lname = name.toLowerCase();
+    if (lname.endsWith(".lei")) return true;
+    else if (!lname.endsWith(".tif") && !lname.endsWith(".tiff")) return false;
+    if (!open) return true; // not allowed to check the file contents
+    if (!isGroupFiles()) return false;
+
+    // just checking the filename isn't enough to differentiate between
+    // Leica and regular TIFF; open the file and check more thoroughly
+    Location file = new Location(name);
+    if (!file.exists()) return false;
+    long len = file.length();
+    if (len < 4) return false;
+
+    try {
+      RandomAccessStream ras = new RandomAccessStream(name);
+      Hashtable ifd = TiffTools.getFirstIFD(ras);
+      ras.close();
+      if (ifd == null) return false;
+
+      String descr = (String) ifd.get(new Integer(TiffTools.IMAGE_DESCRIPTION));
+      int ndx = descr == null ? -1 : descr.indexOf("Series Name");
+
+      if (ndx == -1) return false;
+
+      File f = new File(name).getAbsoluteFile();
+      String[] listing = null;
+      if (f.exists()) listing = f.getParentFile().list();
+      else {
+        listing =
+          (String[]) Location.getIdMap().keySet().toArray(new String[0]);
+      }
+
+      for (int i=0; i<listing.length; i++) {
+        if (listing[i].toLowerCase().endsWith(".lei")) return true;
+      }
+      return false;
+    }
+    catch (IOException exc) {
+      if (debug) trace(exc);
+    }
+    catch (ClassCastException exc) {
+      if (debug) trace(exc);
+    }
+    return false;
+  }
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    leiFilename = null;
+    files = null;
+    if (tiff != null) {
+      for (int i=0; i<tiff.length; i++) {
+        if (tiff[i] != null) {
+          for (int j=0; j<tiff[i].length; j++) {
+            if (tiff[i][j] != null) tiff[i][j].close();
+          }
+        }
+      }
+    }
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("LeicaReader.initFile(" + id + ")");
+    String idLow = id.toLowerCase();
+    close();
+
+    if (idLow.endsWith("tif") || idLow.endsWith("tiff")) {
+      if (ifds == null) super.initFile(id);
+
+      in = new RandomAccessStream(id);
+
+      if (in.readShort() == 0x4949) {
+        in.order(true);
+      }
+
+      in.seek(0);
+
+      status("Finding companion file name");
+
+      // open the TIFF file and look for the "Image Description" field
+
+      ifds = TiffTools.getIFDs(in);
+      if (ifds == null) throw new FormatException("No IFDs found");
+      String descr = (String) TiffTools.getIFDValue(ifds[0],
+        TiffTools.IMAGE_DESCRIPTION);
+
+      int ndx = descr.indexOf("Series Name");
+
+      // should be more graceful about this
+      if (ndx == -1) throw new FormatException("LEI file not found");
+
+      String lei = descr.substring(descr.indexOf("=", ndx) + 1);
+      int newLineNdx = lei.indexOf("\n");
+      lei = lei.substring(0, newLineNdx == -1 ? lei.length() : newLineNdx);
+      lei = lei.trim();
+
+      String dir = id.substring(0, id.lastIndexOf("/") + 1);
+      lei = dir + lei;
+
+      // parse key/value pairs in ImageDescription
+
+      // first thing is to remove anything of the form "[blah]"
+
+      String first;
+      String last;
+
+      while (descr.indexOf("[") != -1) {
+        first = descr.substring(0, descr.indexOf("["));
+        last = descr.substring(descr.indexOf("\n", descr.indexOf("[")));
+        descr = first + last;
+      }
+
+      // each remaining line in descr is a (key, value) pair,
+      // where '=' separates the key from the value
+
+      String key;
+      String value;
+      int eqIndex = descr.indexOf("=");
+
+      while (eqIndex != -1) {
+        key = descr.substring(0, eqIndex);
+        newLineNdx = descr.indexOf("\n", eqIndex);
+        if (newLineNdx == -1) newLineNdx = descr.length();
+        value = descr.substring(eqIndex+1, newLineNdx);
+        addMeta(key.trim(), value.trim());
+        newLineNdx = descr.indexOf("\n", eqIndex);
+        if (newLineNdx == -1) newLineNdx = descr.length();
+        descr = descr.substring(newLineNdx);
+        eqIndex = descr.indexOf("=");
+      }
+
+      // now open the LEI file
+
+      Location l = new Location(lei);
+      if (l.getAbsoluteFile().exists()) initFile(lei);
+      else {
+        l = l.getAbsoluteFile().getParentFile();
+        String[] list = l.list();
+        for (int i=0; i<list.length; i++) {
+          if (list[i].toLowerCase().endsWith("lei")) {
+            initFile(list[i]);
+            return;
+          }
+        }
+      }
+    }
+    else {
+      // parse the LEI file
+
+      super.initFile(id);
+
+      leiFilename = id;
+      in = new RandomAccessStream(id);
+
+      seriesNames = new Vector();
+
+      byte[] fourBytes = new byte[4];
+      in.read(fourBytes);
+      core.littleEndian[0] = (fourBytes[0] == TiffTools.LITTLE &&
+        fourBytes[1] == TiffTools.LITTLE &&
+        fourBytes[2] == TiffTools.LITTLE &&
+        fourBytes[3] == TiffTools.LITTLE);
+
+      in.order(core.littleEndian[0]);
+
+      status("Reading metadata blocks");
+
+      in.skipBytes(8);
+      int addr = in.readInt();
+      Vector v = new Vector();
+      while (addr != 0) {
+        numSeries++;
+        Hashtable ifd = new Hashtable();
+        v.add(ifd);
+        in.seek(addr + 4);
+
+        int tag = in.readInt();
+
+        while (tag != 0) {
+          // create the IFD structure
+          int offset = in.readInt();
+
+          long pos = in.getFilePointer();
+          in.seek(offset + 12);
+
+          int size = in.readInt();
+          byte[] data = new byte[size];
+          in.read(data);
+          ifd.put(new Integer(tag), (Object) data);
+          in.seek(pos);
+          tag = in.readInt();
+        }
+
+        addr = in.readInt();
+      }
+
+      if (v.size() < numSeries) numSeries = v.size();
+
+      core = new CoreMetadata(numSeries);
+
+      headerIFDs = new Hashtable[numSeries];
+      files = new Vector[numSeries];
+
+      v.copyInto(headerIFDs);
+
+      // determine the length of a filename
+
+      int nameLength = 0;
+
+      int maxPlanes = 0;
+
+      status("Parsing metadata blocks");
+
+      core.littleEndian[0] = !core.littleEndian[0];
+
+      for (int i=0; i<headerIFDs.length; i++) {
+        if (headerIFDs[i].get(new Integer(10)) != null) {
+          byte[] temp = (byte[]) headerIFDs[i].get(new Integer(10));
+          nameLength = DataTools.bytesToInt(temp, 8, 4, core.littleEndian[0]);
+        }
+
+        Vector f = new Vector();
+        byte[] tempData = (byte[]) headerIFDs[i].get(new Integer(15));
+        int tempImages = DataTools.bytesToInt(tempData, 0, 4,
+          core.littleEndian[0]);
+
+        File dirFile = new File(id).getAbsoluteFile();
+        String[] listing = null;
+        String dirPrefix = "";
+        if (dirFile.exists()) {
+          listing = dirFile.getParentFile().list();
+          dirPrefix = dirFile.getParent();
+        }
+        else {
+          listing =
+            (String[]) Location.getIdMap().keySet().toArray(new String[0]);
+        }
+
+        Vector list = new Vector();
+
+        for (int k=0; k<listing.length; k++) {
+          if (listing[k].toLowerCase().endsWith(".tif") ||
+            listing[k].toLowerCase().endsWith(".tiff"))
+          {
+            list.add(listing[k]);
+          }
+        }
+
+        listing = (String[]) list.toArray(new String[0]);
+
+        boolean tiffsExist = true;
+
+        String prefix = "";
+        for (int j=0; j<tempImages; j++) {
+          // read in each filename
+          prefix = DataTools.stripString(new String(tempData,
+            20 + 2*(j*nameLength), 2*nameLength));
+          f.add(dirPrefix + File.separator + prefix);
+          // test to make sure the path is valid
+          Location test = new Location((String) f.get(f.size() - 1));
+          if (tiffsExist) tiffsExist = test.exists();
+
+          // get the series name from the stored file name
+          int firstUnderscore = prefix.indexOf("_") + 1;
+          int secondUnderscore = prefix.indexOf("_", firstUnderscore);
+          String name = null;
+          if (firstUnderscore < 0 || secondUnderscore < 0) name = prefix;
+          else {
+            String s = prefix.substring(firstUnderscore, secondUnderscore);
+            if (seriesNames.contains(s)) {
+              int suffix = 2;
+              do {
+                name = s + "-" + suffix;
+                suffix++;
+              }
+              while (seriesNames.contains(name));
+            }
+            else name = s;
+          }
+          seriesNames.add(name);
+        }
+
+        // at least one of the TIFF files was renamed
+
+        if (!tiffsExist) {
+          status("Handling renamed TIFF files");
+
+          // first thing is to get original LEI name associate with each TIFF
+          // this lets us figure out which TIFFs we need for this dataset
+          Hashtable leiMapping = new Hashtable();
+          int numLeis = 0;
+          for (int j=0; j<listing.length; j++) {
+            RandomAccessStream ras = new RandomAccessStream(
+              new Location(dirPrefix, listing[j]).getAbsolutePath());
+            Hashtable ifd = TiffTools.getFirstIFD(ras);
+            ras.close();
+            String descr =
+              (String) ifd.get(new Integer(TiffTools.IMAGE_DESCRIPTION));
+            int ndx = descr.indexOf("=", descr.indexOf("Series Name"));
+            String leiFile = descr.substring(ndx + 1, descr.indexOf("\n", ndx));
+            leiFile = leiFile.trim();
+            if (!leiMapping.contains(leiFile)) numLeis++;
+            leiMapping.put(listing[j], leiFile);
+          }
+
+          // compare original TIFF prefix with original LEI prefix
+
+          f.clear();
+          String[] keys = (String[]) leiMapping.keySet().toArray(new String[0]);
+          for (int j=0; j<keys.length; j++) {
+            String lei = (String) leiMapping.get(keys[j]);
+            if (DataTools.samePrefix(lei, prefix)) {
+              f.add(keys[j]);
+            }
+          }
+
+          // now that we have our list of files, all that remains is to figure
+          // out how they should be ordered
+
+          // we'll try looking for a naming convention, using FilePattern
+          String[] usedFiles = null;
+          for (int j=0; j<f.size(); j++) {
+            if (usedFiles != null) {
+              for (int k=0; k<usedFiles.length; k++) {
+                if (usedFiles[k].equals((String) f.get(j)) ||
+                  usedFile((String) f.get(j)))
+                {
+                  k = 0;
+                  j++;
+                }
+              }
+            }
+            if (j >= f.size()) break;
+
+            FilePattern fp = new FilePattern(new Location((String) f.get(j)));
+            if (fp != null) usedFiles = fp.getFiles();
+            if (usedFiles != null && usedFiles.length == tempImages) {
+              files[i] = new Vector();
+              for (int k=0; k<usedFiles.length; k++) {
+                files[i].add(new Location(usedFiles[k]).getAbsolutePath());
+              }
+              break;
+            }
+          }
+
+          // failing that, we can check the datestamp in each TIFF file
+          // note that this is not guaranteed to work - some versions of
+          // the Leica software will write a blank datestamp
+          if (files[i] == null || files[i].size() == 0) {
+            files[i] = new Vector();
+            Hashtable h = new Hashtable();
+            for (int j=0; j<listing.length; j++) {
+              RandomAccessStream ras = new RandomAccessStream(
+                new Location(dirPrefix, listing[j]).getAbsolutePath());
+              Hashtable fd = TiffTools.getFirstIFD(ras);
+              String stamp =
+                (String) TiffTools.getIFDValue(fd, TiffTools.DATE_TIME);
+              if (h.size() == tempImages) {
+                String[] ks = (String[]) h.keySet().toArray(new String[0]);
+                Arrays.sort(ks);
+                for (int k=0; k<ks.length; k++) {
+                  files[i].add(new Location(dirPrefix,
+                    (String) h.get(ks[k])).getAbsolutePath());
+                }
+                h.clear();
+                break;
+              }
+              else {
+                if (!h.contains(stamp)) h.put(stamp, listing[j]);
+                else {
+                  h.clear();
+                  h.put(stamp, listing[j]);
+                }
+              }
+              ras.close();
+            }
+            if (h.size() == tempImages) {
+              String[] ks = (String[]) h.keySet().toArray(new String[0]);
+              Arrays.sort(ks);
+              for (int k=0; k<ks.length; k++) {
+                files[i].add(new Location(dirPrefix,
+                  (String) h.get(ks[k])).getAbsolutePath());
+              }
+            }
+          }
+
+          // Our final effort is to just sort the filenames lexicographically.
+          // This gives us a pretty good chance of getting the order right,
+          // but it's not perfect.  Basically covers the (hopefully) unlikely
+          // case where filenames are nonsensical, and datestamps are invalid.
+          if (files[i] == null || files[i].size() == 0) {
+            if (debug) debug("File ordering is not obvious.");
+            files[i] = new Vector();
+            Arrays.sort(listing);
+            int ndx = 0;
+            for (int j=0; j<i; j++) ndx += files[j].size();
+            for (int j=ndx; j<ndx+tempImages; j++) {
+              files[i].add(new Location(dirPrefix,
+                listing[j]).getAbsolutePath());
+            }
+          }
+
+          // Ways to break the renaming heuristics:
+          //
+          // 1) Don't use a detectable naming convention, and remove datestamps
+          //    from TIFF files.
+          // 2) Use a naming convention such as plane 0 -> "5.tif",
+          //    plane 1 -> "4.tif", plane 2 -> "3.tif", etc.
+          // 3) Place two datasets in the same folder:
+          //      a) swap the two LEI file names
+          //      b) use the same naming convention for both sets of TIFF files
+          //      c) use the same naming convention AND make sure the datestamps
+          //         are the same between TIFF files
+        }
+        else files[i] = f;
+        core.imageCount[i] = files[i].size();
+        if (core.imageCount[i] > maxPlanes) maxPlanes = core.imageCount[i];
+      }
+
+      tiff = new TiffReader[numSeries][maxPlanes];
+
+      for (int i=0; i<tiff.length; i++) {
+        for (int j=0; j<tiff[i].length; j++) {
+          tiff[i][j] = new TiffReader();
+          if (j > 0) tiff[i][j].setMetadataCollected(false);
+        }
+      }
+
+      status("Populating metadata");
+      initMetadata();
+    }
+  }
+
+  // -- Helper methods --
+
+  protected void initMetadata() throws FormatException, IOException {
+    if (headerIFDs == null) headerIFDs = ifds;
+
+    for (int i=0; i<headerIFDs.length; i++) {
+      byte[] temp = (byte[]) headerIFDs[i].get(new Integer(10));
+      if (temp != null) {
+        // the series data
+        // ID_SERIES
+        addMeta("Version",
+          new Integer(DataTools.bytesToInt(temp, 0, 4, core.littleEndian[0])));
+        addMeta("Number of Series",
+          new Integer(DataTools.bytesToInt(temp, 4, 4, core.littleEndian[0])));
+        addMeta("Length of filename",
+          new Integer(DataTools.bytesToInt(temp, 8, 4, core.littleEndian[0])));
+        Integer fileExtLen =
+          new Integer(DataTools.bytesToInt(temp, 12, 4, core.littleEndian[0]));
+        addMeta("Length of file extension", fileExtLen);
+        addMeta("Image file extension",
+          DataTools.stripString(new String(temp, 16, fileExtLen.intValue())));
+      }
+
+      temp = (byte[]) headerIFDs[i].get(new Integer(15));
+      if (temp != null) {
+        // the image data
+        // ID_IMAGES
+
+        core.sizeZ[i] = DataTools.bytesToInt(temp, 0, 4, core.littleEndian[0]);
+        core.sizeX[i] = DataTools.bytesToInt(temp, 4, 4, core.littleEndian[0]);
+        core.sizeY[i] = DataTools.bytesToInt(temp, 8, 4, core.littleEndian[0]);
+
+        addMeta("Number of images", new Integer(core.sizeZ[i]));
+        addMeta("Image width", new Integer(core.sizeX[i]));
+        addMeta("Image height", new Integer(core.sizeY[i]));
+        addMeta("Bits per Sample",
+          new Integer(DataTools.bytesToInt(temp, 12, 4, core.littleEndian[0])));
+        addMeta("Samples per pixel",
+          new Integer(DataTools.bytesToInt(temp, 16, 4, core.littleEndian[0])));
+      }
+
+      temp = (byte[]) headerIFDs[i].get(new Integer(20));
+      if (temp != null) {
+        // dimension description
+        // ID_DIMDESCR
+        int pt = 0;
+        addMeta("Voxel Version", new Integer(
+          DataTools.bytesToInt(temp, 0, 4, core.littleEndian[0])));
+        int voxelType = DataTools.bytesToInt(temp, 4, 4, core.littleEndian[0]);
+        String type = "";
+        switch (voxelType) {
+          case 0:
+            type = "undefined";
+            break;
+          case 10:
+            type = "gray normal";
+            break;
+          case 20:
+            type = "RGB";
+            break;
+        }
+
+        addMeta("VoxelType", type);
+
+        bpp = DataTools.bytesToInt(temp, 8, 4, core.littleEndian[0]);
+        addMeta("Bytes per pixel", new Integer(bpp));
+        addMeta("Real world resolution",
+          new Integer(DataTools.bytesToInt(temp, 12, 4, core.littleEndian[0])));
+        int length = DataTools.bytesToInt(temp, 16, 4, core.littleEndian[0]);
+        addMeta("Maximum voxel intensity",
+          DataTools.stripString(new String(temp, 20, length)));
+        pt = 20 + length;
+        pt += 4;
+        addMeta("Minimum voxel intensity",
+          DataTools.stripString(new String(temp, pt, length)));
+        pt += length;
+        length = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4 + length + 4;
+
+        length = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        for (int j=0; j<length; j++) {
+          int dimId = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          String dimType = "";
+          switch (dimId) {
+            case 0:
+              dimType = "undefined";
+              break;
+            case 120:
+              dimType = "x";
+              break;
+            case 121:
+              dimType = "y";
+              break;
+            case 122:
+              dimType = "z";
+              break;
+            case 116:
+              dimType = "t";
+              break;
+            case 6815843:
+              dimType = "channel";
+              break;
+            case 6357100:
+              dimType = "wave length";
+              break;
+            case 7602290:
+              dimType = "rotation";
+              break;
+            case 7798904:
+              dimType = "x-wide for the motorized xy-stage";
+              break;
+            case 7798905:
+              dimType = "y-wide for the motorized xy-stage";
+              break;
+            case 7798906:
+              dimType = "z-wide for the z-stage-drive";
+              break;
+            case 4259957:
+              dimType = "user1 - unspecified";
+              break;
+            case 4325493:
+              dimType = "user2 - unspecified";
+              break;
+            case 4391029:
+              dimType = "user3 - unspecified";
+              break;
+            case 6357095:
+              dimType = "graylevel";
+              break;
+            case 6422631:
+              dimType = "graylevel1";
+              break;
+            case 6488167:
+              dimType = "graylevel2";
+              break;
+            case 6553703:
+              dimType = "graylevel3";
+              break;
+            case 7864398:
+              dimType = "logical x";
+              break;
+            case 7929934:
+              dimType = "logical y";
+              break;
+            case 7995470:
+              dimType = "logical z";
+              break;
+            case 7602254:
+              dimType = "logical t";
+              break;
+            case 7077966:
+              dimType = "logical lambda";
+              break;
+            case 7471182:
+              dimType = "logical rotation";
+              break;
+            case 5767246:
+              dimType = "logical x-wide";
+              break;
+            case 5832782:
+              dimType = "logical y-wide";
+              break;
+            case 5898318:
+              dimType = "logical z-wide";
+              break;
+          }
+
+          //if (dimType.equals("channel")) numChannels++;
+          addMeta("Dim" + j + " type", dimType);
+          pt += 4;
+          addMeta("Dim" + j + " size", new Integer(
+            DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0])));
+          pt += 4;
+          int dist = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          addMeta("Dim" + j + " distance between sub-dimensions",
+            new Integer(dist));
+          pt += 4;
+
+          int len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+          addMeta("Dim" + j + " physical length",
+            DataTools.stripString(new String(temp, pt, len)));
+          pt += len;
+
+          len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+          addMeta("Dim" + j + " physical origin",
+            DataTools.stripString(new String(temp, pt, len)));
+          pt += len;
+
+          len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+          addMeta("Dim" + j + " name",
+            DataTools.stripString(new String(temp, pt, len)));
+          pt += len;
+
+          len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+          addMeta("Dim" + j + " description",
+            DataTools.stripString(new String(temp, pt, len)));
+        }
+      }
+
+      temp = (byte[]) headerIFDs[i].get(new Integer(30));
+      if (temp != null) {
+        // filter data
+        // ID_FILTERSET
+
+        // not currently used
+      }
+
+      temp = (byte[]) headerIFDs[i].get(new Integer(40));
+
+      if (temp != null) {
+        // time data
+        // ID_TIMEINFO
+        int nDims = DataTools.bytesToInt(temp, 0, 4, core.littleEndian[0]);
+        addMeta("Number of time-stamped dimensions", new Integer(nDims));
+        addMeta("Time-stamped dimension",
+          new Integer(DataTools.bytesToInt(temp, 4, 4, core.littleEndian[0])));
+
+        int pt = 8;
+
+        for (int j=0; j < nDims; j++) {
+          int v = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          addMeta("Dimension " + j + " ID", new Integer(v));
+          pt += 4;
+          v = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          addMeta("Dimension " + j + " size", new Integer(v));
+          pt += 4;
+          v = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          addMeta("Dimension " + j + " distance between dimensions",
+            new Integer(v));
+          pt += 4;
+        }
+
+        int numStamps = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4;
+        addMeta("Number of time-stamps", new Integer(numStamps));
+        for (int j=0; j<numStamps; j++) {
+          addMeta("Timestamp " + j,
+            DataTools.stripString(new String(temp, pt, 64)));
+          pt += 64;
+        }
+
+        int numTMs = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4;
+        addMeta("Number of time-markers", new Integer(numTMs));
+        for (int j=0; j<numTMs; j++) {
+          int numDims = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+
+          for (int k=0; k<numDims; k++) {
+            int v = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+            addMeta("Time-marker " + j +
+              " Dimension " + k + " coordinate", new Integer(v));
+            pt += 4;
+          }
+          addMeta("Time-marker " + j,
+            DataTools.stripString(new String(temp, pt, 64)));
+          pt += 64;
+        }
+      }
+
+      temp = (byte[]) headerIFDs[i].get(new Integer(50));
+      if (temp != null) {
+        // scanner data
+        // ID_SCANNERSET
+
+        // not currently used
+      }
+
+      temp = (byte[]) headerIFDs[i].get(new Integer(60));
+      if (temp != null) {
+        // experiment data
+        // ID_EXPERIMENT
+        int pt = 8;
+        int len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4;
+
+        addMeta("Image Description",
+          DataTools.stripString(new String(temp, pt, 2*len)));
+        pt += 2*len;
+        len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4;
+
+        addMeta("Main file extension",
+          DataTools.stripString(new String(temp, pt, 2*len)));
+        pt += 2*len;
+
+        len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4;
+        addMeta("Single image format identifier",
+          DataTools.stripString(new String(temp, pt, 2*len)));
+        pt += 2*len;
+
+        len = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4;
+        addMeta("Single image extension",
+          DataTools.stripString(new String(temp, pt, 2*len)));
+      }
+
+      temp = (byte[]) headerIFDs[i].get(new Integer(70));
+      if (temp != null) {
+        // LUT data
+        // ID_LUTDESC
+        int pt = 0;
+        int nChannels = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+        pt += 4;
+        addMeta("Number of LUT channels", new Integer(nChannels));
+        addMeta("ID of colored dimension",
+          new Integer(DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0])));
+        pt += 4;
+
+        if (nChannels > 4) nChannels = 3;
+        core.sizeC[i] = nChannels;
+
+        for (int j=0; j<nChannels; j++) {
+          int v = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          addMeta("LUT Channel " + j + " version", new Integer(v));
+          pt += 4;
+
+          int invert = DataTools.bytesToInt(temp, pt, 1, core.littleEndian[0]);
+          pt += 1;
+          boolean inverted = invert == 1;
+          addMeta("LUT Channel " + j + " inverted?",
+            new Boolean(inverted).toString());
+
+          int length = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+          addMeta("LUT Channel " + j + " description",
+            DataTools.stripString(new String(temp, pt, length)));
+
+          pt += length;
+          length = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+          addMeta("LUT Channel " + j + " filename",
+            DataTools.stripString(new String(temp, pt, length)));
+          pt += length;
+          length = DataTools.bytesToInt(temp, pt, 4, core.littleEndian[0]);
+          pt += 4;
+
+          String name = DataTools.stripString(new String(temp, pt, length));
+
+          addMeta("LUT Channel " + j + " name", name);
+          pt += length;
+
+          pt += 8;
+        }
+      }
+    }
+
+    //core = new CoreMetadata(numSeries);
+    Arrays.fill(core.orderCertain, true);
+
+    // sizeC is null here if the file we opened was a TIFF.
+    // However, the sizeC field will be adjusted anyway by
+    // a later call to initMetadata.
+    if (core.sizeC != null) {
+      int oldSeries = getSeries();
+      for (int i=0; i<core.sizeC.length; i++) {
+        setSeries(i);
+        core.sizeZ[i] /= core.sizeC[i];
+      }
+      setSeries(oldSeries);
+    }
+
+    Integer v = (Integer) getMeta("Real world resolution");
+
+    // the metadata store we're working with
+    MetadataStore store = getMetadataStore();
+
+    byte[] f = new byte[4];
+    for (int i=0; i<numSeries; i++) {
+      core.orderCertain[i] = true;
+      core.interleaved[i] = true;
+
+      in.seek(0);
+      in.read(f);
+      core.littleEndian[i] = (f[0] == TiffTools.LITTLE &&
+        f[1] == TiffTools.LITTLE && f[2] == TiffTools.LITTLE &&
+        f[3] == TiffTools.LITTLE);
+
+      if (core.sizeC[i] == 0) core.sizeC[i] = 1;
+      core.sizeT[i] += 1;
+      core.currentOrder[i] = core.sizeC[i] == 1 ? "XYZTC" : "XYCZT";
+      if (core.sizeZ[i] == 0) core.sizeZ[i] = 1;
+
+      switch (bpp) {
+        case 1:
+          core.pixelType[i] = FormatTools.UINT8;
+          break;
+        case 2:
+          core.pixelType[i] = FormatTools.UINT16;
+          break;
+        case 3:
+          core.pixelType[i] = FormatTools.UINT8;
+          break;
+        case 4:
+          core.pixelType[i] = FormatTools.INT32;
+          break;
+        case 6:
+          core.pixelType[i] = FormatTools.INT16;
+          break;
+        case 8:
+          core.pixelType[i] = FormatTools.DOUBLE;
+          break;
+      }
+
+      core.rgb[i] =
+        core.imageCount[i] != core.sizeC[i] * core.sizeZ[i] * core.sizeT[i];
+
+      Integer ii = new Integer(i);
+
+      core.rgb[i] = false;
+      //core.sizeC[i] *= 3;
+
+      core.indexed[i] = true;
+      core.falseColor[i] = true;
+      core.metadataComplete[i] = true;
+
+      String timestamp = (String) getMeta("Timestamp " + (i+1));
+      String description = (String) getMeta("Image Description");
+
+      if (timestamp != null) {
+        SimpleDateFormat parse =
+          new SimpleDateFormat("yyyy:MM:dd,HH:mm:ss:SSS");
+        Date date = parse.parse(timestamp, new ParsePosition(0));
+        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+        timestamp = fmt.format(date);
+      }
+
+      store.setImage((String) seriesNames.get(i), timestamp, description, ii);
+    }
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC.length; i++) {
+      for (int j=0; j<core.sizeC[i]; j++) {
+        store.setLogicalChannel(j, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, new Integer(i));
+        // TODO: get channel min/max from metadata
+//        store.setChannelGlobalMinMax(j, getChannelGlobalMinimum(currentId, j),
+//          getChannelGlobalMaximum(currentId, j), ii);
+      }
+    }
+  }
+
+  private boolean usedFile(String s) {
+    if (files == null) return false;
+
+    for (int i=0; i<files.length; i++) {
+      if (files[i] == null) continue;
+      for (int j=0; j<files[i].size(); j++) {
+        if (((String) files[i].get(j)).endsWith(s)) return true;
+      }
+    }
+    return false;
+  }
+
+}
diff --git a/loci/formats/in/MDBParser.java b/loci/formats/in/MDBParser.java
new file mode 100644
index 0000000..57899ba
--- /dev/null
+++ b/loci/formats/in/MDBParser.java
@@ -0,0 +1,185 @@
+//
+// MDBParser.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * Utility class for parsing MDB database files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/MDBParser.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/MDBParser.java">SVN</a></dd></dl>
+ */
+public final class MDBParser {
+
+  // -- Constants --
+
+  private static final String NO_MDB_MSG =
+    "The Java port of MDB tools is required to read MDB files. Please " +
+    "obtain mdbtools-java.jar from http://loci.wisc.edu/ome/formats.html";
+
+  // -- Static fields --
+
+  private static boolean noMDB = false;
+  private static ReflectedUniverse r = createReflectedUniverse();
+
+  private static ReflectedUniverse createReflectedUniverse() {
+    r = null;
+    try {
+      r = new ReflectedUniverse();
+      r.exec("import java.util.Vector");
+      r.exec("import mdbtools.jdbc2.File");
+      r.exec("import mdbtools.libmdb.Catalog");
+      r.exec("import mdbtools.libmdb.Constants");
+      r.exec("import mdbtools.libmdb.Data");
+      r.exec("import mdbtools.libmdb.Holder");
+      r.exec("import mdbtools.libmdb.MdbCatalogEntry");
+      r.exec("import mdbtools.libmdb.MdbColumn");
+      r.exec("import mdbtools.libmdb.MdbHandle");
+      r.exec("import mdbtools.libmdb.MdbTableDef");
+      r.exec("import mdbtools.libmdb.Table");
+      r.exec("import mdbtools.libmdb.file");
+      r.exec("import mdbtools.libmdb.mem");
+    }
+    catch (Throwable t) {
+      noMDB = true;
+      if (FormatHandler.debug) LogTools.trace(t);
+    }
+    return r;
+  }
+
+  // -- Constructor --
+
+  private MDBParser() { }
+
+  // -- Utility methods --
+
+  /** Parses table structure for a specified MDB file. */
+  public static void parseDatabase(String filename, Hashtable h)
+    throws FormatException
+  {
+    if (noMDB) throw new FormatException(NO_MDB_MSG);
+
+    try {
+      // initialize
+
+      r.setVar("twoFiveSix", 256);
+      r.exec("boundValues = new Vector()");
+      r.setVar("delimiter", ",");
+
+      r.exec("mem.mdb_init()");
+
+      // print out all data
+
+      r.setVar("filename", filename);
+      r.exec("dbfile = new File(filename)");
+      r.exec("mdb = file.mdb_open(dbfile)");
+      r.exec("Catalog.mdb_read_catalog(mdb, Constants.MDB_TABLE)");
+
+      int num = ((Integer) r.getVar("mdb.num_catalog")).intValue();
+
+      for (int i=0; i<num; i++) {
+        r.setVar("c", (List) r.getVar("mdb.catalog"));
+        r.setVar("i", i);
+        r.exec("entry = c.get(i)");
+        r.setVar("objType",
+          ((Integer) r.getVar("entry.object_type")).intValue());
+        r.setVar("objName", (String) r.getVar("entry.object_name"));
+
+        int objType = ((Integer) r.getVar("objType")).intValue();
+        int tableType = ((Integer) r.getVar("Constants.MDB_TABLE")).intValue();
+        boolean isTable = objType == tableType;
+
+        String objName = (String) r.getVar("objName");
+
+        if (isTable && !objName.startsWith("MSys")) {
+          r.exec("table = Table.mdb_read_table(entry)");
+          try {
+            r.exec("Table.mdb_read_columns(table)");
+          }
+          catch (ReflectException e) { break; }
+          r.exec("Data.mdb_rewind_table(table)");
+
+          r.setVar("numCols",
+            ((Integer) r.getVar("table.num_cols")).intValue());
+
+          int numCols = ((Integer) r.getVar("numCols")).intValue();
+
+          for (int j=0; j<numCols; j++) {
+            r.setVar("j", j);
+            r.exec("blah = new Holder()");
+            r.setVar("l", j + 1);
+            r.exec("Data.mdb_bind_column(table, l, blah)");
+            r.exec("boundValues.add(blah)");
+          }
+
+          StringBuffer[] sbs = new StringBuffer[numCols];
+          for (int j=0; j<sbs.length; j++) sbs[j] = new StringBuffer();
+
+          boolean moreRows = true;
+          try {
+            r.exec("moreRows = Data.mdb_fetch_row(table)");
+            moreRows = ((Boolean) r.getVar("moreRows")).booleanValue();
+          }
+          catch (ReflectException e) { moreRows = false; }
+
+          while (moreRows) {
+            for (int j=0; j<numCols; j++) {
+              r.setVar("j", j);
+              r.setVar("columns", (List) r.getVar("table.columns"));
+              r.exec("col = columns.get(j)");
+              if (sbs[j].length() > 0) sbs[j].append(",");
+              r.exec("blah = boundValues.get(j)");
+              sbs[j].append((String) r.getVar("blah.s"));
+            }
+            try {
+              r.exec("moreRows = Data.mdb_fetch_row(table)");
+              moreRows = ((Boolean) r.getVar("moreRows")).booleanValue();
+            }
+            catch (ReflectException e) { moreRows = false; }
+          }
+
+          // place column of data in the hashtable
+          // key is table name + column name, value is each value in the
+          // column, separated by commas
+
+          for (int j=0; j<sbs.length; j++) {
+            r.setVar("j", j);
+            r.setVar("columns", (List) r.getVar("table.columns"));
+            r.exec("col = columns.get(j)");
+            h.put(objName + " - " + (String) r.getVar("col.name"),
+              sbs[j].toString());
+          }
+        }
+      }
+    }
+    catch (ReflectException exc) {
+      LogTools.trace(exc);
+    }
+  }
+
+}
diff --git a/loci/formats/in/MNGReader.java b/loci/formats/in/MNGReader.java
new file mode 100644
index 0000000..59fde57
--- /dev/null
+++ b/loci/formats/in/MNGReader.java
@@ -0,0 +1,191 @@
+//
+// MNGReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.Vector;
+import javax.imageio.ImageIO;
+import loci.formats.*;
+
+/**
+ * MNGReader is the file format reader for Multiple Network Graphics (MNG)
+ * files.  Does not support JNG (JPEG Network Graphics).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/MNGReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/MNGReader.java">SVN</a></dd></dl>
+ */
+public class MNGReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Offsets to each plane. */
+  private Vector offsets;
+
+  /** Length (in bytes) of each plane. */
+  private Vector lengths;
+
+  // -- Constructor --
+
+  /** Constructs a new MNG reader. */
+  public MNGReader() { super("Multiple Network Graphics", "mng"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 8) return false;
+    return block[0] == 0x8a && block[1] == 0x4d && block[2] == 0x4e &&
+      block[3] == 0x47 && block[4] == 0x0d && block[5] == 0x0a &&
+      block[6] == 0x1a && block[7] == 0x0a;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    buf = ImageTools.getBytes(openImage(no), true, core.sizeC[0]);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    long offset = ((Long) offsets.get(no)).longValue();
+    in.seek(offset);
+    long end = ((Long) lengths.get(no)).longValue();
+    byte[] b = new byte[(int) (end - offset + 8)];
+    in.read(b, 8, b.length - 8);
+    b[0] = (byte) 0x89;
+    b[1] = 0x50;
+    b[2] = 0x4e;
+    b[3] = 0x47;
+    b[4] = 0x0d;
+    b[5] = 0x0a;
+    b[6] = 0x1a;
+    b[7] = 0x0a;
+
+    return ImageIO.read(new ByteArrayInputStream(b));
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("MNGReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(false);
+
+    status("Verifying MNG format");
+
+    offsets = new Vector();
+    lengths = new Vector();
+
+    in.skipBytes(8);
+
+    in.skipBytes(4);
+    byte[] b = new byte[4];
+    in.read(b);
+    if (!"MHDR".equals(new String(b))) {
+      throw new FormatException("Invalid MNG file.");
+    }
+
+    status("Reading dimensions");
+
+    core.sizeX[0] = in.readInt();
+    core.sizeY[0] = in.readInt();
+    in.skipBytes(24);
+
+    Vector stack = new Vector();
+    int maxIterations = 0;
+    int currentIteration = 0;
+
+    status("Finding image offsets");
+
+    while (in.getFilePointer() < in.length()) {
+      int len = in.readInt();
+      in.read(b);
+      String code = new String(b);
+
+      long fp = in.getFilePointer();
+
+      if (code.equals("IHDR")) {
+        offsets.add(new Long(in.getFilePointer() - 8));
+        core.imageCount[0]++;
+      }
+      else if (code.equals("IEND")) {
+        lengths.add(new Long(fp + len + 4));
+      }
+      else if (code.equals("LOOP")) {
+        stack.add(new Long(in.getFilePointer() + len + 4));
+        in.skipBytes(1);
+        maxIterations = in.readInt();
+      }
+      else if (code.equals("ENDL")) {
+        int seek = ((Integer) stack.get(stack.size() - 1)).intValue();
+        if (currentIteration < maxIterations) {
+          in.seek(seek);
+          currentIteration++;
+        }
+        else {
+          stack.remove(stack.size() - 1);
+          maxIterations = 0;
+          currentIteration = 0;
+        }
+      }
+
+      in.seek(fp + len + 4);
+    }
+
+    status("Populating metadata");
+
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = openImage(0).getRaster().getNumBands();
+    core.sizeT[0] = core.imageCount[0];
+    core.currentOrder[0] = "XYCZT";
+    core.pixelType[0] = FormatTools.UINT8;
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = false;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/MRCReader.java b/loci/formats/in/MRCReader.java
new file mode 100644
index 0000000..d8f143a
--- /dev/null
+++ b/loci/formats/in/MRCReader.java
@@ -0,0 +1,230 @@
+//
+// MRCReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * MRCReader is the file format reader for MRC files.
+ * Specifications available at
+ * http://bio3d.colorado.edu/imod/doc/mrc_format.txt
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/MRCReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/MRCReader.java">SVN</a></dd></dl>
+ */
+public class MRCReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Number of bytes per pixel */
+  private int bpp = 0;
+
+  /** Size of extended header */
+  private int extHeaderSize = 0;
+
+  /** Flag set to true if we are using float data. */
+  private boolean isFloat = false;
+
+  // -- Constructor --
+
+  /** Constructs a new MRC reader. */
+  public MRCReader() {
+    super("Medical Research Council (MRC)", "mrc");
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false; // no way to tell if this is an MRC file or not
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+    in.seek(1024 + (no * core.sizeX[0] * core.sizeY[0] * bpp));
+    in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  public void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("MRCReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    status("Reading header");
+
+    // check endianness
+
+    in.seek(213);
+    core.littleEndian[0] = in.read() == 68;
+
+    // read 1024 byte header
+
+    in.seek(0);
+    in.order(core.littleEndian[0]);
+
+    core.sizeX[0] = in.readInt();
+    core.sizeY[0] = in.readInt();
+    core.sizeZ[0] = in.readInt();
+
+    core.sizeC[0] = 1;
+
+    int mode = in.readInt();
+    switch (mode) {
+      case 0:
+        bpp = 1;
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 1:
+      case 6:
+        bpp = 2;
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      case 2:
+        bpp = 4;
+        isFloat = true;
+        core.pixelType[0] = FormatTools.FLOAT;
+        break;
+      case 3:
+        bpp = 4;
+        core.pixelType[0] = FormatTools.UINT32;
+        break;
+      case 4:
+        bpp = 8;
+        isFloat = true;
+        core.pixelType[0] = FormatTools.DOUBLE;
+        break;
+      case 16:
+        bpp = 2;
+        core.sizeC[0] = 3;
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+    }
+
+    // pixel size = xlen / mx
+
+    int mx = in.readInt();
+    int my = in.readInt();
+    int mz = in.readInt();
+
+    float xlen = in.readFloat();
+    float ylen = in.readFloat();
+    float zlen = in.readFloat();
+
+    addMeta("Pixel size (X)", "" + (xlen / mx));
+    addMeta("Pixel size (Y)", "" + (ylen / my));
+    addMeta("Pixel size (Z)", "" + (zlen / mz));
+
+    addMeta("Alpha angle", "" + in.readFloat());
+    addMeta("Beta angle", "" + in.readFloat());
+    addMeta("Gamma angle", "" + in.readFloat());
+
+    in.skipBytes(12);
+
+    // min, max and mean pixel values
+
+    addMeta("Minimum pixel value", "" + in.readFloat());
+    addMeta("Maximum pixel value", "" + in.readFloat());
+    addMeta("Mean pixel value", "" + in.readFloat());
+
+    in.skipBytes(4);
+    extHeaderSize = in.readInt();
+
+    in.skipBytes(64);
+
+    int idtype = in.readShort();
+
+    String[] types = new String[] {"mono", "tilt", "tilts", "lina", "lins"};
+    String type = (idtype >= 0 && idtype < types.length) ? types[idtype] :
+      "unknown";
+
+    addMeta("Series type", type);
+    addMeta("Lens", "" + in.readShort());
+    addMeta("ND1", "" + in.readShort());
+    addMeta("ND2", "" + in.readShort());
+    addMeta("VD1", "" + in.readShort());
+    addMeta("VD2", "" + in.readShort());
+
+    float[] angles = new float[6];
+    for (int i=0; i<angles.length; i++) {
+      angles[i] = in.readFloat();
+      addMeta("Angle " + (i+1), "" + angles[i]);
+    }
+
+    in.skipBytes(24);
+
+    int nUsefulLabels = in.readInt();
+    addMeta("Number of useful labels", "" + nUsefulLabels);
+
+    for (int i=0; i<10; i++) {
+      addMeta("Label " + (i+1), in.readString(80));
+    }
+
+    in.skipBytes(extHeaderSize);
+
+    status("Populating metadata");
+
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYZTC";
+    core.imageCount[0] = core.sizeZ[0];
+    core.rgb[0] = false;
+    core.interleaved[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    Float x = new Float(xlen / mx);
+    Float y = new Float(ylen / my);
+    Float z = new Float(zlen / mz);
+    if (x.floatValue() == Float.POSITIVE_INFINITY) x = new Float(1.0);
+    if (y.floatValue() == Float.POSITIVE_INFINITY) y = new Float(1.0);
+    if (z.floatValue() == Float.POSITIVE_INFINITY) z = new Float(1.0);
+
+    store.setDimensions(x, y, z, null, null, null);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+      // TODO : get channel min/max from metadata
+      //store.setChannelGlobalMinMax(i, getChannelGlobalMinimum(id, i),
+      //  getChannelGlobalMaximum(id, i), null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/MetamorphReader.java b/loci/formats/in/MetamorphReader.java
new file mode 100644
index 0000000..cdd5942
--- /dev/null
+++ b/loci/formats/in/MetamorphReader.java
@@ -0,0 +1,1035 @@
+//
+// MetamorphReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.text.DecimalFormat;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * Reader is the file format reader for Metamorph STK files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/MetamorphReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/MetamorphReader.java">SVN</a></dd></dl>
+ *
+ * @author Eric Kjellman egkjellman at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Sebastien Huart Sebastien dot Huart at curie.fr
+ */
+public class MetamorphReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  // IFD tag numbers of important fields
+  private static final int METAMORPH_ID = 33628;
+  private static final int UIC1TAG = METAMORPH_ID;
+  private static final int UIC2TAG = 33629;
+  private static final int UIC3TAG = 33630;
+  private static final int UIC4TAG = 33631;
+
+  // -- Fields --
+
+  /** The TIFF's name */
+  private String imageName;
+
+  /** The TIFF's creation date */
+  private String imageCreationDate;
+
+  //** The TIFF's emWavelength */
+  private long[] emWavelength;
+
+  private int mmPlanes; //number of metamorph planes
+
+  private MetamorphReader r;
+
+  /** List of STK files in the dataset. */
+  private String[][] stks;
+
+  // -- Constructor --
+
+  /** Constructs a new Metamorph reader. */
+  public MetamorphReader() {
+    super("Metamorph STK", new String[] {"stk", "nd"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    // If the file is a Metamorph STK file, it should have a specific IFD tag.
+    // Most Metamorph files seem to have the IFD information at the end, so it
+    // is difficult to determine whether or not the block is a Metamorph block
+    // without being passed the entire file.  Therefore, we will check the only
+    // things we can reasonably check at the beginning of the file, and if we
+    // happen to be passed the entire file, well, great, we'll check that too.
+
+    // Must be little-endian TIFF
+    if (block.length < 3) return false;
+    if (block[0] != TiffTools.LITTLE) return false; // denotes little-endian
+    if (block[1] != TiffTools.LITTLE) return false;
+    if (block[2] != TiffTools.MAGIC_NUMBER) return false; // denotes TIFF
+    if (block.length < 8) return true; // we have no way of verifying further
+    int ifdlocation = DataTools.bytesToInt(block, 4, true);
+    if (ifdlocation + 1 > block.length) {
+      // we have no way of verifying this is a Metamorph file.
+      // It is at least a TIFF.
+      return true;
+    }
+    else {
+      int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, true);
+      for (int i = 0; i < ifdnumber; i++) {
+        if (ifdlocation + 3 + (i * 12) > block.length) return true;
+        else {
+          int ifdtag = DataTools.bytesToInt(block,
+            ifdlocation + 2 + (i * 12), 2, true);
+          if (ifdtag == METAMORPH_ID) return true; // absolutely a valid file
+        }
+      }
+      return false; // we went through the IFD; the ID wasn't found.
+    }
+  }
+
+  /* @see loci.formats.IFormatReader#close() */
+  public void close() throws IOException {
+    super.close();
+    if (r != null) r.close();
+  }
+
+  /* @see loci.formats.IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    if (id.toLowerCase().endsWith(".nd")) return FormatTools.MUST_GROUP;
+
+    Location l = new Location(id).getAbsoluteFile();
+    String[] files = l.getParentFile().list();
+
+    for (int i=0; i<files.length; i++) {
+      String s = files[i].toLowerCase();
+      if (s.endsWith(".nd") && id.startsWith(files[i].substring(0,
+        s.lastIndexOf("."))))
+      {
+        return FormatTools.CAN_GROUP;
+      }
+    }
+
+    return FormatTools.CANNOT_GROUP;
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    return stks == null ? super.getUsedFiles() : stks[series];
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    if (stks == null || stks[series].length == 1) {
+      return super.openBytes(no, buf);
+    }
+
+    int[] coords = FormatTools.getZCTCoords(this, no % core.sizeZ[series]);
+    int ndx = no / core.sizeZ[series];
+    String file = stks[series][ndx];
+
+    if (r == null) r = new MetamorphReader();
+    r.setId(file);
+    return r.openBytes(coords[0], buf);
+  }
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (id.toLowerCase().endsWith(".nd")) {
+      if (currentId != null) {
+        String[] s = getUsedFiles();
+        for (int i=0; i<s.length; i++) {
+          if (id.equals(s[i])) return;
+        }
+      }
+
+      close();
+      currentId = id;
+      metadata = new Hashtable();
+
+      core = new CoreMetadata(1);
+      Arrays.fill(core.orderCertain, true);
+
+      // reinitialize the MetadataStore
+      getMetadataStore().createRoot();
+
+      // find an associated STK file
+      String stkFile = id.substring(0, id.lastIndexOf("."));
+      Location parent = new Location(id).getAbsoluteFile().getParentFile();
+      String[] dirList = parent.list();
+      for (int i=0; i<dirList.length; i++) {
+        String s = dirList[i].toLowerCase();
+        if (s.endsWith(".stk") && (dirList[i].indexOf(stkFile.substring(
+          stkFile.lastIndexOf(File.separator) + 1) + "_w") != -1))
+        {
+          stkFile =
+            new Location(parent.getPath(), dirList[i]).getAbsolutePath();
+          break;
+        }
+      }
+
+      super.initFile(stkFile);
+    }
+    else super.initFile(id);
+
+    Location ndfile = null;
+
+    if (id.toLowerCase().endsWith(".nd")) ndfile = new Location(id);
+
+    if (ndfile != null && ndfile.exists() &&
+      (fileGroupOption(id) == FormatTools.MUST_GROUP || isGroupFiles()))
+    {
+      RandomAccessStream ndStream =
+        new RandomAccessStream(ndfile.getAbsolutePath());
+      String line = ndStream.readLine().trim();
+
+      while (!line.equals("\"EndFile\"")) {
+        String key = line.substring(1, line.indexOf(",") - 1).trim();
+        String value = line.substring(line.indexOf(",") + 1).trim();
+
+        addMeta(key, value);
+        line = ndStream.readLine().trim();
+      }
+
+      // figure out how many files we need
+
+      String z = (String) getMeta("NZSteps");
+      String c = (String) getMeta("NWavelengths");
+      String t = (String) getMeta("NTimePoints");
+
+      int zc = core.sizeZ[0], cc = core.sizeC[0], tc = core.sizeT[0];
+
+      if (z != null) zc = Integer.parseInt(z);
+      if (c != null) cc = Integer.parseInt(c);
+      if (t != null) tc = Integer.parseInt(t);
+
+      int numFiles = cc * tc;
+
+      // determine series count
+
+      boolean[] hasZ = new boolean[cc];
+      int seriesCount = 1;
+      for (int i=0; i<cc; i++) {
+        hasZ[i] = ((String) getMeta("WaveDoZ" + (i + 1))).equals("TRUE");
+        if (i > 0 && hasZ[i] != hasZ[i - 1]) seriesCount = 2;
+      }
+
+      int channelsInFirstSeries = cc;
+      if (seriesCount == 2) {
+        channelsInFirstSeries = 0;
+        for (int i=0; i<cc; i++) {
+          if (hasZ[i]) channelsInFirstSeries++;
+        }
+      }
+
+      stks = new String[seriesCount][];
+      if (seriesCount == 1) stks[0] = new String[numFiles];
+      else {
+        stks[0] = new String[channelsInFirstSeries * tc];
+        stks[1] = new String[(cc - channelsInFirstSeries) * tc];
+      }
+
+      String prefix = ndfile.getPath();
+      prefix = prefix.substring(prefix.lastIndexOf(File.separator) + 1,
+        prefix.lastIndexOf("."));
+
+      int[] pt = new int[seriesCount];
+      for (int i=0; i<tc; i++) {
+        for (int j=0; j<cc; j++) {
+          String chName = (String) getMeta("WaveName" + (j + 1));
+          chName = chName.substring(1, chName.length() - 1);
+          int seriesNdx = seriesCount == 1 ? 0 : (hasZ[j] ? 0 : 1);
+          stks[seriesNdx][pt[seriesNdx]++] =
+            prefix + "_w" + (j + 1) + chName + "_t" + (i + 1) + ".STK";
+        }
+      }
+
+      ndfile = ndfile.getAbsoluteFile();
+
+      for (int s=0; s<stks.length; s++) {
+        for (int f=0; f<stks[s].length; f++) {
+          Location l = new Location(ndfile.getParent(), stks[s][f]);
+          if (!l.exists()) {
+            // '%' can be converted to '-'
+            if (stks[s][f].indexOf("%") != -1) {
+              stks[s][f] = stks[s][f].replaceAll("%", "-");
+              l = new Location(ndfile.getParent(), stks[s][f]);
+              if (!l.exists()) {
+                // try replacing extension
+                stks[s][f] = stks[s][f].substring(0,
+                  stks[s][f].lastIndexOf(".")) + ".TIF";
+                l = new Location(ndfile.getParent(), stks[s][f]);
+                if (!l.exists()) {
+                  stks = null;
+                  return;
+                }
+              }
+            }
+
+            if (!l.exists()) {
+              // try replacing extension
+              stks[s][f] = stks[s][f].substring(0,
+                stks[s][f].lastIndexOf(".")) + ".TIF";
+              l = new Location(ndfile.getParent(), stks[s][f]);
+              if (!l.exists()) {
+                stks = null;
+                return;
+              }
+            }
+          }
+          stks[s][f] = l.getAbsolutePath();
+        }
+      }
+
+      core.sizeZ[0] = zc;
+      core.sizeC[0] = cc;
+      core.sizeT[0] = tc;
+      core.imageCount[0] = zc * tc * cc;
+      core.currentOrder[0] = "XYZCT";
+
+      if (stks.length > 1) {
+        CoreMetadata newCore = new CoreMetadata(stks.length);
+        for (int i=0; i<stks.length; i++) {
+          newCore.sizeX[i] = core.sizeX[0];
+          newCore.sizeY[i] = core.sizeY[0];
+          newCore.sizeZ[i] = core.sizeZ[0];
+          newCore.sizeC[i] = core.sizeC[0];
+          newCore.sizeT[i] = core.sizeT[0];
+          newCore.pixelType[i] = core.pixelType[0];
+          newCore.imageCount[i] = core.imageCount[0];
+          newCore.currentOrder[i] = core.currentOrder[0];
+          newCore.rgb[i] = core.rgb[0];
+          newCore.littleEndian[i] = core.littleEndian[0];
+          newCore.interleaved[i] = core.interleaved[0];
+          newCore.orderCertain[i] = true;
+        }
+        newCore.sizeC[0] = stks[0].length / newCore.sizeT[0];
+        newCore.sizeC[1] = stks[1].length / newCore.sizeT[1];
+        newCore.sizeZ[1] = 1;
+        newCore.imageCount[0] =
+          newCore.sizeC[0] * newCore.sizeT[0] * newCore.sizeZ[0];
+        newCore.imageCount[1] = newCore.sizeC[1] * newCore.sizeT[1];
+        core = newCore;
+      }
+    }
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    try {
+      // Now that the base TIFF standard metadata has been parsed, we need to
+      // parse out the STK metadata from the UIC4TAG.
+      TiffIFDEntry uic1tagEntry = TiffTools.getFirstIFDEntry(in, UIC1TAG);
+      TiffIFDEntry uic2tagEntry = TiffTools.getFirstIFDEntry(in, UIC2TAG);
+      TiffIFDEntry uic4tagEntry = TiffTools.getFirstIFDEntry(in, UIC4TAG);
+      int planes = uic4tagEntry.getValueCount();
+      mmPlanes = planes;
+      parseUIC2Tags(uic2tagEntry.getValueOffset());
+      parseUIC4Tags(uic4tagEntry.getValueOffset());
+      parseUIC1Tags(uic1tagEntry.getValueOffset(),
+        uic1tagEntry.getValueCount());
+      in.seek(uic4tagEntry.getValueOffset());
+
+      // copy ifds into a new array of Hashtables that will accommodate the
+      // additional image planes
+      long[] uic2 = TiffTools.getIFDLongArray(ifds[0], UIC2TAG, true);
+      core.imageCount[0] = uic2.length;
+
+      long[] uic3 = TiffTools.getIFDLongArray(ifds[0], UIC3TAG, true);
+      for (int i=0; i<uic3.length; i++) {
+        in.seek(uic3[i]);
+        put("Wavelength [" + intFormatMax(i, mmPlanes) + "]",
+          in.readLong() / in.readLong());
+      }
+
+      Hashtable[] tempIFDs = new Hashtable[core.imageCount[0]];
+
+      long[] oldOffsets = TiffTools.getIFDLongArray(ifds[0],
+          TiffTools.STRIP_OFFSETS, true);
+
+      long[] stripByteCounts = TiffTools.getIFDLongArray(ifds[0],
+          TiffTools.STRIP_BYTE_COUNTS, true);
+
+      int stripsPerImage = oldOffsets.length;
+
+      int check = TiffTools.getIFDIntValue(ifds[0],
+        TiffTools.PHOTOMETRIC_INTERPRETATION);
+      if (check == TiffTools.RGB_PALETTE) {
+        TiffTools.putIFDValue(ifds[0], TiffTools.PHOTOMETRIC_INTERPRETATION,
+          TiffTools.BLACK_IS_ZERO);
+      }
+
+      emWavelength = TiffTools.getIFDLongArray(ifds[0], UIC3TAG, true);
+
+      // for each image plane, construct an IFD hashtable
+
+      int pointer = 0;
+
+      Hashtable temp;
+      for(int i=0; i<core.imageCount[0]; i++) {
+        temp = new Hashtable();
+
+        // copy most of the data from 1st IFD
+        temp.put(new Integer(TiffTools.LITTLE_ENDIAN), ifds[0].get(
+            new Integer(TiffTools.LITTLE_ENDIAN)));
+        temp.put(new Integer(TiffTools.IMAGE_WIDTH), ifds[0].get(
+            new Integer(TiffTools.IMAGE_WIDTH)));
+        temp.put(new Integer(TiffTools.IMAGE_LENGTH),
+            ifds[0].get(new Integer(TiffTools.IMAGE_LENGTH)));
+        temp.put(new Integer(TiffTools.BITS_PER_SAMPLE), ifds[0].get(
+            new Integer(TiffTools.BITS_PER_SAMPLE)));
+        temp.put(new Integer(TiffTools.COMPRESSION), ifds[0].get(
+            new Integer(TiffTools.COMPRESSION)));
+        temp.put(new Integer(TiffTools.PHOTOMETRIC_INTERPRETATION),
+            ifds[0].get(new Integer(TiffTools.PHOTOMETRIC_INTERPRETATION)));
+        temp.put(new Integer(TiffTools.STRIP_BYTE_COUNTS), ifds[0].get(
+            new Integer(TiffTools.STRIP_BYTE_COUNTS)));
+        temp.put(new Integer(TiffTools.ROWS_PER_STRIP), ifds[0].get(
+            new Integer(TiffTools.ROWS_PER_STRIP)));
+        temp.put(new Integer(TiffTools.X_RESOLUTION), ifds[0].get(
+            new Integer(TiffTools.X_RESOLUTION)));
+        temp.put(new Integer(TiffTools.Y_RESOLUTION), ifds[0].get(
+            new Integer(TiffTools.Y_RESOLUTION)));
+        temp.put(new Integer(TiffTools.RESOLUTION_UNIT), ifds[0].get(
+            new Integer(TiffTools.RESOLUTION_UNIT)));
+        temp.put(new Integer(TiffTools.PREDICTOR), ifds[0].get(
+            new Integer(TiffTools.PREDICTOR)));
+
+        // now we need a StripOffsets entry
+
+        long planeOffset = i*(oldOffsets[stripsPerImage - 1] +
+            stripByteCounts[stripsPerImage - 1] - oldOffsets[0]);
+
+        long[] newOffsets = new long[oldOffsets.length];
+        newOffsets[0] = planeOffset + oldOffsets[0];
+
+        for(int j=1; j<newOffsets.length; j++) {
+          newOffsets[j] = newOffsets[j-1] + stripByteCounts[0];
+        }
+
+        temp.put(new Integer(TiffTools.STRIP_OFFSETS), newOffsets);
+
+        tempIFDs[pointer] = temp;
+        pointer++;
+      }
+      ifds = tempIFDs;
+    }
+    catch (UnknownTagException exc) { trace(exc); }
+    catch (NullPointerException exc) { trace(exc); }
+    catch (IOException exc) { trace(exc); }
+    catch (FormatException exc) { trace(exc); }
+
+    try {
+      super.initStandardMetadata();
+    }
+    catch (FormatException exc) {
+      if (debug) trace(exc);
+    }
+    catch (IOException exc) {
+      if (debug) trace(exc);
+    }
+
+    // parse (mangle) TIFF comment
+    String descr = (String) getMeta("Comment");
+    if (descr != null) {
+      StringTokenizer st = new StringTokenizer(descr, "\n");
+      StringBuffer sb = new StringBuffer();
+      boolean first = true;
+      while (st.hasMoreTokens()) {
+        String line = st.nextToken();
+        int colon = line.indexOf(": ");
+
+        if (colon < 0) {
+          // normal line (not a key/value pair)
+          if (line.trim().length() > 0) {
+            // not a blank line
+            sb.append(line);
+            if (!line.endsWith(".")) sb.append(".");
+            sb.append("  ");
+          }
+          first = false;
+          continue;
+        }
+
+        if (first) {
+          // first line could be mangled; make a reasonable guess
+          int dot = line.lastIndexOf(".", colon);
+          if (dot >= 0) {
+            String s = line.substring(0, dot + 1);
+            sb.append(s);
+            if (!s.endsWith(".")) sb.append(".");
+            sb.append("  ");
+          }
+          line = line.substring(dot + 1);
+          colon -= dot + 1;
+          first = false;
+        }
+
+        // add key/value pair embedded in comment as separate metadata
+        String key = line.substring(0, colon);
+        String value = line.substring(colon + 2);
+        put(key, value);
+      }
+
+      // replace comment with trimmed version
+      descr = sb.toString().trim();
+      if (descr.equals("")) metadata.remove("Comment");
+      else put("Comment", descr);
+    }
+    try {
+      if (core.sizeZ[0] == 0) {
+        core.sizeZ[0] =
+          TiffTools.getIFDLongArray(ifds[0], UIC2TAG, true).length;
+      }
+      if (core.sizeT[0] == 0) core.sizeT[0] = getImageCount() / core.sizeZ[0];
+    }
+    catch (FormatException exc) {
+      if (debug) trace(exc);
+    }
+  }
+
+  /* @see BaseTiffReader#getImageName() */
+  protected String getImageName() {
+    if (imageName == null) return super.getImageName();
+    return imageName;
+  }
+
+  /* @see BaseTiffReader#getImageCreationDate() */
+  protected String getImageCreationDate() {
+    if (imageCreationDate == null) return super.getImageCreationDate();
+    return imageCreationDate;
+  }
+
+  Integer getEmWave(int i) {
+    if (emWavelength == null || emWavelength[i] == 0)  return null;
+    return new Integer((int) emWavelength[i]);
+  }
+
+  // -- Helper methods --
+
+  /**
+   * Populates metadata fields with some contained in MetaMorph UIC2 Tag.
+   * (for each plane: 6 integers:
+   * zdistance numerator, zdistance denominator,
+   * creation date, creation time, modif date, modif time)
+   * @param uic2offset offset to UIC2 (33629) tag entries
+   *
+   * not a regular tiff tag (6*N entries, N being the tagCount)
+   * @throws IOException
+   */
+  void parseUIC2Tags(long uic2offset) throws IOException {
+
+    long saveLoc = in.getFilePointer();
+    in.seek(uic2offset);
+
+    /*number of days since the 1st of January 4713 B.C*/
+    int cDate;
+    /*milliseconds since 0:00*/
+    int cTime;
+
+    /*z step, distance separating previous slice from  current one*/
+    double zDistance;
+    String iAsString;
+
+    for (int i=0; i<mmPlanes; i++) {
+      iAsString = intFormatMax(i, mmPlanes);
+      int num = in.readInt();
+      int den = in.readInt();
+      zDistance = (double) num / den;
+      put("zDistance[" + iAsString + "]", zDistance);
+      cDate = in.readInt();
+      put("creationDate[" + iAsString + "]", decodeDate(cDate));
+
+      cTime = in.readInt();
+      put("creationTime[" + iAsString + "]", decodeTime(cTime));
+      // modification date and time are skipped as they all seem equal to 0...?
+      in.skip(8);
+    }
+    in.seek(saveLoc);
+  }
+
+  /**
+   * UIC4 metadata parser
+   *
+   * UIC4 Table contains per-plane blocks of metadata
+   * stage X/Y positions,
+   * camera chip offsets,
+   * stage labels...
+   * @param long uic4offset: offset of UIC4 table (not tiff-compliant)
+   * @throws IOException
+   */
+  private void parseUIC4Tags(long uic4offset) throws IOException {
+    long saveLoc = in.getFilePointer();
+    in.seek(uic4offset);
+    boolean end=false;
+    short id;
+    while (!end) {
+      id = in.readShort();
+
+      switch (id) {
+        case 0:
+          end=true;
+          break;
+        case 28:
+          readStagePositions();
+          break;
+        case 29:
+          readCameraChipOffsets();
+          break;
+        case 37:
+          readStageLabels();
+          break;
+        case 40:
+          readAbsoluteZ();
+          break;
+        case 41:
+          readAbsoluteZValid();
+          break;
+        default:
+          //unknown tags: do nothing
+          break;
+        //28->stagePositions
+        //29->cameraChipOffsets
+        //30->stageLabel
+        //40->AbsoluteZ
+        //41AbsoluteZValid
+        //0->end
+      }
+    }
+    in.seek(saveLoc);
+  }
+
+  void readStagePositions() throws IOException {
+    int nx, dx, ny, dy;
+    // for each plane:
+    // 2 ints (rational:numerator,denominator) for stage X,
+    // 2 ints (idem) for stage Y position
+    double xPosition, yPosition;
+    String iAsString;
+    for(int i=0; i<mmPlanes; i++) {
+      nx = in.readInt();
+      dx = in.readInt();
+      ny = in.readInt();
+      dy = in.readInt();
+      xPosition = (dx == 0) ? Double.NaN : (double) nx / dx;
+      yPosition = (dy == 0) ? Double.NaN : (double) ny / dy;
+      iAsString = intFormatMax(i, mmPlanes);
+      put("stageX[" + iAsString + "]", xPosition);
+      put("stageY[" + iAsString + "]", yPosition);
+    }
+
+  }
+
+  void readCameraChipOffsets() throws IOException {
+    int nx, dx, ny, dy;
+    double cameraXChipOffset, cameraYChipOffset;
+    String iAsString;
+    for(int i=0; i<mmPlanes; i++) {
+      iAsString = intFormatMax(i, mmPlanes);
+      nx = in.readInt();
+      dx = in.readInt();
+      ny = in.readInt();
+      dy = in.readInt();
+      cameraXChipOffset = (dx == 0) ? Double.NaN: (double) nx / dx;
+      cameraYChipOffset = (dy == 0) ? Double.NaN: (double) ny/ dy;
+      put("cameraXChipOffset[" + iAsString + "]", cameraXChipOffset);
+      put("cameraYChipOffset[" + iAsString + "]", cameraYChipOffset);
+    }
+  }
+
+  void readStageLabels() throws IOException {
+    int strlen;
+    byte[] curlabel;
+    String iAsString;
+    for (int i=0; i<mmPlanes; i++) {
+      iAsString = intFormatMax(i, mmPlanes);
+      strlen = in.readInt();
+      curlabel = new byte[strlen];
+      in.read(curlabel);
+      put("stageLabel[" + iAsString + "]", new String(curlabel));
+    }
+  }
+
+  void readAbsoluteZ() throws IOException {
+    int nz, dz;
+    double absoluteZ;
+    for(int i=0; i<mmPlanes; i++) {
+      nz = in.readInt();
+      dz = in.readInt();
+      absoluteZ = (dz == 0) ? Double.NaN : (double) nz / dz;
+      put("absoluteZ[" + intFormatMax(i, mmPlanes) + "]", absoluteZ);
+    }
+  }
+
+  void readAbsoluteZValid() throws IOException {
+    for (int i=0; i<mmPlanes; i++) {
+      put("absoluteZValid[" + intFormatMax(i, mmPlanes) + "]", in.readInt());
+    }
+  }
+
+  /**
+   * UIC1 entry parser
+   * @throws IOException
+   * @param long uic1offset : offset as found in the tiff tag 33628 (UIC1Tag)
+   * @param int uic1count : number of entries in UIC1 table (not tiff-compliant)
+   */
+  private void parseUIC1Tags(long uic1offset, int uic1count) throws IOException
+  {
+    // Loop through and parse out each field. A field whose
+    // code is "0" represents the end of the fields so we'll stop
+    // when we reach that; much like a NULL terminated C string.
+    long saveLoc = in.getFilePointer();
+    in.seek(uic1offset);
+    int currentID, valOrOffset;
+    // variable declarations, because switch is dumb
+    int num, denom;
+    String thedate, thetime;
+    long lastOffset;
+    byte[] toread;
+    for (int i=0; i<uic1count; i++) {
+      currentID = in.readInt();
+      valOrOffset = in.readInt();
+
+      switch (currentID) {
+        case 1:
+          put("MinScale", valOrOffset);
+          break;
+        case 2:
+          put("MaxScale", valOrOffset);
+          break;
+        case 3:
+          int calib = valOrOffset;
+          String calibration = calib != 0 ? "on" : "off";
+          put("Spatial Calibration", calibration);
+          break;
+        case 4:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("XCalibration", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 5:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("YCalibration", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 6:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          toread = new byte[num];
+          in.read(toread);
+          put("CalibrationUnits", new String(toread));
+          in.seek(lastOffset);
+          break;
+        case 7:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          toread = new byte[num];
+          in.read(toread);
+          String name = new String(toread);
+          put("Name", name);
+          imageName = name;
+          in.seek(lastOffset);
+          break;
+
+        case 8:
+          int thresh = valOrOffset;
+          String threshState = "off";
+          if (thresh == 1) threshState = "inside";
+          else if (thresh == 2) threshState = "outside";
+          put("ThreshState", threshState);
+          break;
+        case 9:
+          put("ThreshStateRed", valOrOffset);
+          break;
+          // there is no 10
+        case 11:
+          put("ThreshStateGreen", valOrOffset);
+          break;
+        case 12:
+          put("ThreshStateBlue", valOrOffset);
+          break;
+        case 13:
+          put("ThreshStateLo", valOrOffset);
+          break;
+        case 14:
+          put("ThreshStateHi", valOrOffset);
+          break;
+        case 15:
+          int zoom = valOrOffset;
+          put("Zoom", zoom);
+          //OMETools.setAttribute(ome, "DisplayOptions", "Zoom", "" + zoom);
+          break;
+        case 16: // oh how we hate you Julian format...
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          thedate = decodeDate(in.readInt());
+          thetime = decodeTime(in.readInt());
+          put("DateTime", thedate + " " + thetime);
+          imageCreationDate = thedate + " " + thetime;
+          in.seek(lastOffset);
+          break;
+        case 17:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          thedate = decodeDate(in.readInt());
+          thetime = decodeTime(in.readInt());
+          put("LastSavedTime", thedate + " " + thetime);
+          in.seek(lastOffset);
+          break;
+        case 18:
+          put("currentBuffer", valOrOffset);
+          break;
+        case 19:
+          put("grayFit", valOrOffset);
+          break;
+        case 20:
+          put("grayPointCount", valOrOffset);
+          break;
+        case 21:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("grayX", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 22:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("gray", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 23:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("grayMin", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 24:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("grayMax", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 25:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          toread = new byte[num];
+          in.read(toread);
+          put("grayUnitName", new String(toread));
+          in.seek(lastOffset);
+          break;
+        case 26:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          int standardLUT = in.readInt();
+          in.seek(lastOffset);
+          String standLUT;
+          switch (standardLUT) {
+            case 0:
+              standLUT = "monochrome";
+              break;
+            case 1:
+              standLUT = "pseudocolor";
+              break;
+            case 2:
+              standLUT = "Red";
+              break;
+            case 3:
+              standLUT = "Green";
+              break;
+            case 4:
+              standLUT = "Blue";
+              break;
+            case 5:
+              standLUT = "user-defined";
+              break;
+            default:
+              standLUT = "monochrome"; break;
+          }
+          put("StandardLUT", standLUT);
+          break;
+        case 27:
+          put("Wavelength", valOrOffset);
+          break;
+        case 30:
+          put("OverlayMask", valOrOffset);
+          break;
+        case 31:
+          put("OverlayCompress", valOrOffset);
+          break;
+        case 32:
+          put("Overlay", valOrOffset);
+          break;
+        case 33:
+          put("SpecialOverlayMask", valOrOffset);
+          break;
+        case 34:
+          put("SpecialOverlayCompress", in.readInt());
+          break;
+        case 35:
+          put("SpecialOverlay", valOrOffset);
+          break;
+        case 36:
+          put("ImageProperty", valOrOffset);
+          break;
+        case 38:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("AutoScaleLoInfo", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 39:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          num = in.readInt();
+          denom = in.readInt();
+          put("AutoScaleHiInfo", new TiffRational(num, denom));
+          in.seek(lastOffset);
+          break;
+        case 42:
+          put("Gamma", valOrOffset);
+          break;
+        case 43:
+          put("GammaRed", valOrOffset);
+          break;
+        case 44:
+          put("GammaGreen", valOrOffset);
+          break;
+        case 45:
+          put("GammaBlue", valOrOffset);
+          break;
+        case 46:
+          lastOffset = in.getFilePointer();
+          in.seek(valOrOffset);
+          int xBin, yBin;
+          xBin = in.readInt();
+          yBin = in.readInt();
+          put("CameraBin", new String("(" + xBin + "," + yBin + ")"));
+          in.seek(lastOffset);
+          break;
+        default:
+          break;
+      }
+    }
+    in.seek(saveLoc);
+  }
+
+  // -- Utility methods --
+
+  /** Converts a Julian date value into a human-readable string. */
+  public static String decodeDate(int julian) {
+    long a, b, c, d, e, alpha, z;
+    short day, month, year;
+
+    // code reused from the Metamorph data specification
+    z = julian + 1;
+
+    if (z < 2299161L) a = z;
+    else {
+      alpha = (long) ((z - 1867216.25) / 36524.25);
+      a = z + 1 + alpha - alpha / 4;
+    }
+
+    b = (a > 1721423L ? a + 1524 : a + 1158);
+    c = (long) ((b - 122.1) / 365.25);
+    d = (long) (365.25 * c);
+    e = (long) ((b - d) / 30.6001);
+
+    day = (short) (b - d - (long) (30.6001 * e));
+    month = (short) ((e < 13.5) ? e - 1 : e - 13);
+    year = (short) ((month > 2.5) ? (c - 4716) : c - 4715);
+
+    return day + "/" + month + "/" + year;
+  }
+
+  /** Converts a time value in milliseconds into a human-readable string. */
+  public static String decodeTime(int millis) {
+    int ms, seconds, minutes, hours;
+
+    ms = millis % 1000;
+    millis -= ms;
+    millis /= 1000;
+    seconds = millis % 60;
+    millis -= seconds;
+    millis /= 60;
+    minutes = millis % 60;
+    millis -= minutes;
+    millis /= 60;
+    hours = millis;
+    return intFormat(hours, 2) + ":" + intFormat(minutes, 2) + ":" +
+      intFormat(seconds, 2) + "." + intFormat(ms, 3);
+  }
+
+  /** Formats an integer value with leading 0s if needed. */
+  public static String intFormat(int myint, int digits) {
+    String formatstring = "0";
+    while (formatstring.length() < digits) {
+      formatstring += "0";
+    }
+    DecimalFormat df = new DecimalFormat(formatstring);
+    return df.format(myint);
+  }
+
+  /**
+   * Formats an integer with leading 0 using maximum sequence number.
+   *
+   * @param myint integer to format
+   * @param maxint max of "myint"
+   * @return String
+   */
+  public static String intFormatMax(int myint, int maxint) {
+    return intFormat(myint, new Integer(maxint).toString().length());
+  }
+
+}
diff --git a/loci/formats/in/MicromanagerReader.java b/loci/formats/in/MicromanagerReader.java
new file mode 100644
index 0000000..dd6a6b9
--- /dev/null
+++ b/loci/formats/in/MicromanagerReader.java
@@ -0,0 +1,242 @@
+//
+// MicromanagerReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * MicromanagerReader is the file format reader for Micro-Manager files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/MicromanagerReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/MicromanagerReader.java">SVN</a></dd></dl>
+ */
+public class MicromanagerReader extends FormatReader {
+
+  // -- Constants --
+
+  /** File containing extra metadata. */
+  private static final String METADATA = "metadata.txt";
+
+  // -- Fields --
+
+  /** Helper reader for TIFF files. */
+  private TiffReader tiffReader;
+
+  /** List of TIFF files to open. */
+  private Vector tiffs;
+
+  // -- Constructor --
+
+  /** Constructs a new Micromanager reader. */
+  public MicromanagerReader() {
+    super("Micro-Manager", new String[] {"tif", "tiff", "txt"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] b) {
+    return tiffReader.isThisType(b);
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    String[] s = new String[tiffs.size() + 1];
+    tiffs.copyInto(s);
+    s[tiffs.size()] = currentId;
+    return s;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    tiffReader.setId((String) tiffs.get(no));
+    return tiffReader.openBytes(0, buf);
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly) {
+      if (tiffReader != null) tiffReader.close(fileOnly);
+    }
+    else close();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    File f = new File(name).getAbsoluteFile();
+    String[] list = null;
+    if (f.exists()) list = f.getParentFile().list();
+    else list = (String[]) Location.getIdMap().keySet().toArray(new String[0]);
+
+    if (list == null) return false;
+    for (int i=0; i<list.length; i++) {
+      if (list[i].endsWith("metadata.txt")) return super.isThisType(name, open);
+    }
+    return false;
+  }
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    if (tiffReader != null) tiffReader.close();
+    tiffReader = null;
+    tiffs = null;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  public void initFile(String id) throws FormatException, IOException {
+    super.initFile(id);
+    tiffReader = new TiffReader();
+
+    status("Reading metadata file");
+
+    // find metadata.txt
+
+    File file = new File(currentId).getAbsoluteFile();
+    in = new RandomAccessStream(file.exists() ? new File(file.getParentFile(),
+      METADATA).getAbsolutePath() : METADATA);
+    String parent = file.exists() ? file.getParentFile().getAbsolutePath() : "";
+
+    // usually a small file, so we can afford to read it into memory
+
+    byte[] meta = new byte[(int) in.length()];
+    in.read(meta);
+    String s = new String(meta);
+    meta = null;
+
+    status("Finding image file names");
+
+    // first find the name of each TIFF file
+    tiffs = new Vector();
+    int pos = 0;
+    while (true) {
+      pos = s.indexOf("FileName", pos);
+      if (pos == -1 || pos >= in.length()) break;
+      String name = s.substring(s.indexOf(":", pos), s.indexOf(",", pos));
+      tiffs.add(0,
+        parent + File.separator + name.substring(3, name.length() - 1));
+      pos++;
+    }
+
+    // now parse the rest of the metadata
+
+    status("Populating metadata");
+
+    int start = s.indexOf("Summary");
+    int end = s.indexOf("}", start);
+    if (start != -1 && end > start) {
+      s = s.substring(s.indexOf("\n", start), end).trim();
+    }
+
+    StringTokenizer st = new StringTokenizer(s, "\n");
+    while (st.hasMoreTokens()) {
+      String token = st.nextToken();
+      boolean open = token.indexOf("[") != -1;
+      boolean closed = token.indexOf("]") != -1;
+      if (open || (!open && !closed)) {
+        int quote = token.indexOf("\"") + 1;
+        String key = token.substring(quote, token.indexOf("\"", quote));
+
+        if (!open && !closed) {
+          String value = token.substring(token.indexOf(":") + 1).trim();
+          value = value.substring(0, value.length() - 1);
+          addMeta(key, value);
+          if (key.equals("Channels")) core.sizeC[0] = Integer.parseInt(value);
+        }
+        else if (!closed){
+          StringBuffer valueBuffer = new StringBuffer();
+          while (!closed) {
+            token = st.nextToken();
+            closed = token.indexOf("]") != -1;
+            valueBuffer.append(token);
+          }
+          String value = valueBuffer.toString();
+          value.replaceAll("\n", "").trim();
+          value = value.substring(0, value.length() - 1);
+          addMeta(key, value);
+          if (key.equals("Channels")) core.sizeC[0] = Integer.parseInt(value);
+        }
+        else {
+          String value =
+            token.substring(token.indexOf("[") + 1, token.indexOf("]")).trim();
+          value = value.substring(0, value.length() - 1);
+          addMeta(key, value);
+          if (key.equals("Channels")) core.sizeC[0] = Integer.parseInt(value);
+        }
+      }
+    }
+    tiffReader.setId((String) tiffs.get(0));
+
+    String z = (String) metadata.get("Slices");
+    if (z != null) {
+      core.sizeZ[0] = Integer.parseInt(z);
+    }
+    else core.sizeZ[0] = 1;
+
+    String t = (String) metadata.get("Frames");
+    if (t != null) {
+      core.sizeT[0 ] = Integer.parseInt(t);
+    }
+    else core.sizeT[0] = tiffs.size() / core.sizeC[0];
+
+    core.sizeX[0] = tiffReader.getSizeX();
+    core.sizeY[0] = tiffReader.getSizeY();
+    core.currentOrder[0] = "XYZCT";
+    core.pixelType[0] = tiffReader.getPixelType();
+    core.rgb[0] = tiffReader.isRGB();
+    core.interleaved[0] = false;
+    core.littleEndian[0] = tiffReader.isLittleEndian();
+    core.imageCount[0] = tiffs.size();
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+      // TODO : retrieve min/max from the metadata
+      //store.setChannelGlobalMinMax(i, getChannelGlobalMinimum(id, i),
+      //  getChannelGlobalMaximum(id, i), null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/ND2Reader.java b/loci/formats/in/ND2Reader.java
new file mode 100644
index 0000000..a98f299
--- /dev/null
+++ b/loci/formats/in/ND2Reader.java
@@ -0,0 +1,831 @@
+//
+// ND2Reader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.*;
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.xml.parsers.*;
+import loci.formats.*;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * ND2Reader is the file format reader for Nikon ND2 files.
+ * The JAI ImageIO library is required to use this reader; it is available from
+ * http://jai-imageio.dev.java.net. Note that JAI ImageIO is bundled with a
+ * version of the JJ2000 library, so it is important that either:
+ * (1) the JJ2000 jar file is *not* in the classpath; or
+ * (2) the JAI jar file precedes JJ2000 in the classpath.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ND2Reader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ND2Reader.java">SVN</a></dd></dl>
+ */
+public class ND2Reader extends FormatReader {
+
+  // -- Constants --
+
+  private static final String NO_J2K_MSG =
+    "The JAI Image I/O Tools are required to read ND2 files. Please " +
+    "obtain jai_imageio.jar from http://loci.wisc.edu/ome/formats.html";
+
+  private static final String J2K_READER =
+    "com.sun.media.imageioimpl.plugins.jpeg2000.J2KImageReader";
+
+  /** Factory for generating SAX parsers. */
+  public static final SAXParserFactory SAX_FACTORY =
+    SAXParserFactory.newInstance();
+
+  // -- Static fields --
+
+  private static boolean noJ2k = false;
+  private static ReflectedUniverse r = createReflectedUniverse();
+
+  private static ReflectedUniverse createReflectedUniverse() {
+    // NB: ImageJ does not access the jai_imageio classes with the normal
+    // class loading scheme, and thus the necessary service provider stuff is
+    // not automatically registered. Instead, we register the J2KImageReader
+    // with the IIORegistry manually, merely so that we can obtain a
+    // J2KImageReaderSpi object from the IIORegistry's service provider
+    // lookup function, then use it to construct a J2KImageReader object
+    // directly, which we can use to process ND2 files one plane at a time.
+
+    ReflectedUniverse ru = null;
+    try {
+      // register J2KImageReader with IIORegistry
+      String j2kReaderSpi = J2K_READER + "Spi";
+      Class j2kSpiClass = null;
+      try {
+        j2kSpiClass = Class.forName(j2kReaderSpi);
+      }
+      catch (ClassNotFoundException exc) {
+        if (debug) LogTools.trace(exc);
+        noJ2k = true;
+      }
+      catch (NoClassDefFoundError err) {
+        if (debug) LogTools.trace(err);
+        noJ2k = true;
+      }
+      catch (RuntimeException exc) {
+        // HACK: workaround for bug in Apache Axis2
+        String msg = exc.getMessage();
+        if (msg != null && msg.indexOf("ClassNotFound") < 0) throw exc;
+        if (debug) LogTools.trace(exc);
+        noJ2k = true;
+      }
+      IIORegistry registry = IIORegistry.getDefaultInstance();
+      if (j2kSpiClass != null) {
+        Iterator providers = ServiceRegistry.lookupProviders(j2kSpiClass);
+        registry.registerServiceProviders(providers);
+      }
+
+      // obtain J2KImageReaderSpi instance from IIORegistry
+      Object j2kSpi = registry.getServiceProviderByClass(j2kSpiClass);
+
+      ru = new ReflectedUniverse();
+
+      // for computing offsets in initFile
+      ru.exec("import jj2000.j2k.fileformat.reader.FileFormatReader");
+      ru.exec("import jj2000.j2k.io.BEBufferedRandomAccessFile");
+      ru.exec("import jj2000.j2k.util.ISRandomAccessIO");
+
+      // for reading pixel data in openImage
+      ru.exec("import " + J2K_READER);
+      ru.setVar("j2kSpi", j2kSpi);
+      ru.exec("j2kReader = new J2KImageReader(j2kSpi)");
+    }
+    catch (Throwable t) {
+      noJ2k = true;
+      if (debug) LogTools.trace(t);
+    }
+    return ru;
+  }
+
+  // -- Fields --
+
+  /** Array of image offsets. */
+  private long[] offsets;
+
+  /** Whether or not the pixel data is compressed using JPEG 2000. */
+  private boolean isJPEG;
+
+  /** Whether or not the pixel data is losslessly compressed. */
+  private boolean isLossless;
+
+  private boolean adjustImageCount;
+
+  private Vector zs = new Vector();
+  private Vector ts = new Vector();
+
+  // -- Constructor --
+
+  /** Constructs a new ND2 reader. */
+  public ND2Reader() { super("Nikon ND2", new String[] {"nd2", "jp2"}); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 8) return false;
+    return block[4] == 0x6a && block[5] == 0x50 && block[6] == 0x20 &&
+      block[7] == 0x20;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(offsets[no]);
+
+    if (isJPEG) {
+      BufferedImage b = openImage(no);
+      byte[][] pixels = ImageTools.getPixelBytes(b, false);
+      if (pixels.length == 1 && core.sizeC[0] > 1) {
+        pixels = ImageTools.splitChannels(pixels[0], core.sizeC[0],
+          FormatTools.getBytesPerPixel(core.pixelType[0]), false,
+          !core.interleaved[0]);
+      }
+      for (int i=0; i<core.sizeC[0]; i++) {
+        System.arraycopy(pixels[i], 0, buf, i*pixels[i].length,
+          pixels[i].length);
+      }
+      pixels = null;
+    }
+    else if (isLossless) {
+      byte[] b = new byte[buf.length];
+      in.read(b);
+
+      if ((core.sizeX[0] % 2) != 0) {
+        buf = new byte[(core.sizeX[0] + 1) * core.sizeY[0] *
+          getRGBChannelCount() *
+          FormatTools.getBytesPerPixel(core.pixelType[0])];
+      }
+
+      Inflater decompresser = new Inflater();
+      decompresser.setInput(b);
+      try { decompresser.inflate(buf); }
+      catch (DataFormatException e) { throw new FormatException(e); }
+      decompresser.end();
+
+      if ((core.sizeX[0] % 2) != 0) {
+        byte[] tmp = buf;
+        buf = new byte[core.sizeX[0] * core.sizeY[0] * getRGBChannelCount() *
+          FormatTools.getBytesPerPixel(core.pixelType[0])];
+        int row = core.sizeX[0] * getRGBChannelCount() *
+          FormatTools.getBytesPerPixel(core.pixelType[0]);
+        int padRow = (core.sizeX[0] + 1) * getRGBChannelCount() *
+          FormatTools.getBytesPerPixel(core.pixelType[0]);
+        for (int i=0; i<core.sizeY[0]; i++) {
+          System.arraycopy(tmp, padRow * i, buf, row * i, row);
+        }
+      }
+    }
+    else in.readFully(buf);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    if (!isJPEG) {
+      return ImageTools.makeImage(openBytes(no), core.sizeX[0], core.sizeY[0],
+        core.sizeC[0], core.interleaved[0],
+        FormatTools.getBytesPerPixel(core.pixelType[0]), core.littleEndian[0]);
+    }
+
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    in.seek(offsets[no]);
+
+    long len = no < core.imageCount[0] - 1 ? offsets[no + 1] - offsets[no] :
+      in.length() - offsets[no];
+
+    byte[] b = new byte[(int) len];
+    in.readFully(b);
+
+    ByteArrayInputStream bis = new ByteArrayInputStream(b);
+    // NB: Even after registering J2KImageReader with
+    // IIORegistry manually, the following still does not work:
+    //BufferedImage img = ImageIO.read(bis);
+    MemoryCacheImageInputStream mciis = new MemoryCacheImageInputStream(bis);
+    BufferedImage img = null;
+    try {
+      r.setVar("mciis", mciis);
+      r.exec("j2kReader.setInput(mciis)");
+      r.setVar("zero", 0);
+      r.setVar("param", null);
+      img = (BufferedImage) r.exec("j2kReader.read(zero, param)");
+    }
+    catch (ReflectException exc) {
+      throw new FormatException(exc);
+    }
+    bis.close();
+    mciis.close();
+    b = null;
+
+    return img;
+  }
+
+  /* @see loci.formats.IFormatReader#close() */
+  public void close() throws IOException {
+    super.close();
+
+    offsets = null;
+    zs.clear();
+    ts.clear();
+    adjustImageCount = false;
+    isJPEG = false;
+    isLossless = false;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("ND2Reader.initFile(" + id + ")");
+    if (noJ2k) throw new FormatException(NO_J2K_MSG);
+    super.initFile(id);
+
+    in = new RandomAccessStream(id);
+
+    if (in.read() == -38 && in.read() == -50) {
+      // newer version of ND2 - doesn't use JPEG2000
+
+      isJPEG = false;
+      in.seek(0);
+      in.order(true);
+
+      byte[] b = new byte[1024 * 1024];
+      while (in.getFilePointer() < in.length()) {
+        if (in.read() == -38 && in.read() == -50 && in.read() == -66 &&
+          in.read() == 10)
+        {
+          // found a data chunk
+          int len = in.readInt() + in.readInt();
+          if (len > b.length) {
+            // make sure size at least doubles, for efficiency
+            int size = b.length + b.length;
+            if (size < len) size = len;
+            b = new byte[size];
+          }
+          in.skipBytes(4);
+
+          if (debug) {
+            debug("Reading chunk of size " + len +
+              " at position " + in.getFilePointer());
+          }
+          in.readFully(b, 0, len);
+
+          if (len >= 12 && b[0] == 'I' && b[1] == 'm' && b[2] == 'a' &&
+            b[3] == 'g' && b[4] == 'e' && b[5] == 'D' && b[6] == 'a' &&
+            b[7] == 't' && b[8] == 'a' && b[9] == 'S' && b[10] == 'e' &&
+            b[11] == 'q') // b.startsWith("ImageDataSeq")
+          {
+            // found pixel data
+
+            StringBuffer sb = new StringBuffer();
+            int pt = 13;
+            while (b[pt] != '!') {
+              sb.append((char) b[pt]);
+              pt++;
+            }
+            int ndx = Integer.parseInt(sb.toString());
+
+            if (core.sizeC[0] == 0) {
+              core.sizeC[0] = len / (core.sizeX[0] * core.sizeY[0] *
+                FormatTools.getBytesPerPixel(core.pixelType[0]));
+            }
+            offsets[ndx] = in.getFilePointer() - len + sb.length() + 21;
+          }
+          else if (len >= 5 && b[0] == 'I' && b[1] == 'm' && b[2] == 'a' &&
+            b[3] == 'g' && b[4] == 'e') // b.startsWith("Image")
+          {
+            // XML metadata
+
+            ND2Handler handler = new ND2Handler();
+
+            // strip out invalid characters
+            int off = 0;
+            for (int i=0; i<len; i++) {
+              char c = (char) b[i];
+              if (off == 0 && c == '!') off = i + 1;
+
+              if (Character.isISOControl(c) || !Character.isDefined(c)) {
+                b[i] = (byte) ' ';
+              }
+            }
+
+            if (len - off >= 5 && b[off] == '<' && b[off + 1] == '?' &&
+              b[off + 2] == 'x' && b[off + 3] == 'm' &&
+              b[off + 4] == 'l') // b.substring(off, off + 5).equals("<?xml")
+            {
+              ByteArrayInputStream s =
+                new ByteArrayInputStream(b, off, len - off);
+
+              try {
+                SAXParser parser = SAX_FACTORY.newSAXParser();
+                parser.parse(s, handler);
+              }
+              catch (ParserConfigurationException exc) {
+                throw new FormatException(exc);
+              }
+              catch (SAXException exc) {
+                throw new FormatException(exc);
+              }
+            }
+          }
+
+          if (core.imageCount[0] > 0 && offsets == null) {
+            offsets = new long[core.imageCount[0]];
+          }
+
+          if (in.getFilePointer() < in.length() - 1) {
+            if (in.read() != -38) in.skipBytes(15);
+            else in.seek(in.getFilePointer() - 1);
+          }
+        }
+      }
+
+      if (isLossless) {
+        for (int i=0; i<offsets.length; i++) {
+          offsets[i]++;
+        }
+      }
+
+      if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+      core.currentOrder[0] = "XYCZT";
+      core.rgb[0] = core.sizeC[0] > 1;
+      if (core.sizeC[0] > 1 && adjustImageCount) {
+        core.imageCount[0] /= 3;
+        core.sizeZ[0] /= 3;
+      }
+      core.littleEndian[0] = isLossless;
+      core.interleaved[0] = true;
+      core.indexed[0] = false;
+      core.falseColor[0] = false;
+      core.metadataComplete[0] = true;
+
+      MetadataStore store = getMetadataStore();
+      store.setImage(currentId, null, null, null);
+      FormatTools.populatePixels(store, this);
+      for (int i=0; i<core.sizeC[0]; i++) {
+        store.setLogicalChannel(i, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, null);
+      }
+
+      return;
+    }
+    else in.seek(0);
+
+    isJPEG = true;
+
+    status("Calculating image offsets");
+
+    Vector vs = new Vector();
+
+    long pos = in.getFilePointer();
+    boolean lastBoxFound = false;
+    int length = 0;
+    int box = 0;
+
+    while (!lastBoxFound) {
+      pos = in.getFilePointer();
+      length = in.readInt();
+      if (pos + length >= in.length() || length == 0) lastBoxFound = true;
+      box = in.readInt();
+      pos = in.getFilePointer();
+      length -= 8;
+
+      if (box == 0x6a703263) {
+        vs.add(new Long(in.getFilePointer()));
+      }
+      if (!lastBoxFound) in.seek(pos + length);
+    }
+
+    offsets = new long[vs.size()];
+    for (int i=0; i<offsets.length; i++) {
+      offsets[i] = ((Long) vs.get(i)).longValue();
+    }
+    vs.clear();
+    vs = null;
+
+    status("Finding XML metadata");
+
+    core.imageCount[0] = offsets.length;
+
+    core.pixelType[0] = FormatTools.UINT8;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    // read XML metadata from the end of the file
+
+    in.seek(offsets[offsets.length - 1]);
+
+    boolean found = false;
+    long off = -1;
+    byte[] buf = new byte[2048];
+    while (!found && in.getFilePointer() < in.length()) {
+      int read = 0;
+      if (in.getFilePointer() == offsets[offsets.length - 1]) {
+        read = in.read(buf);
+      }
+      else {
+        System.arraycopy(buf, buf.length - 10, buf, 0, 10);
+        read = in.read(buf, 10, buf.length - 10);
+      }
+
+      if (read == buf.length) read -= 10;
+      for (int i=0; i<read+9; i++) {
+        if (buf[i] == (byte) 0xff && buf[i+1] == (byte) 0xd9) {
+          found = true;
+          off = in.getFilePointer() - (read + 10) + i;
+          i = buf.length;
+          break;
+        }
+      }
+    }
+
+    buf = null;
+
+    status("Parsing XML");
+
+    if (off > 0 && off < in.length() - 5) {
+      in.seek(off + 5);
+      byte[] b = new byte[(int) (in.length() - off - 5)];
+      in.readFully(b);
+      String xml = new String(b);
+
+      // assume that this XML string will be malformed, since that's how both
+      // sample files are; this means we need to manually parse it :-(
+
+      // strip out binary data at the end - this is irrelevant for our purposes
+      xml = xml.substring(0, xml.lastIndexOf("</MetadataSeq>") + 14);
+
+      // strip out all comments
+      xml = xml.replaceAll("<!--*-->", "");
+
+      // each chunk appears on a separate line, so split up the chunks
+
+      StringTokenizer st = new StringTokenizer(xml, "\r\n");
+      while (st.hasMoreTokens()) {
+        String token = st.nextToken().trim();
+        if (token.indexOf("<") != -1) {
+          String prefix = token.substring(1, token.indexOf(">")).trim();
+          token = token.substring(token.indexOf(">") + 1);
+
+          while (token.indexOf("<") != -1) {
+            int start = token.indexOf("<");
+            String s = token.substring(start + 1, token.indexOf(">", start));
+            token = token.substring(token.indexOf(">", start));
+
+            // get the prefix for this tag
+            if (s.indexOf(" ") != -1) {
+              String pre = s.substring(0, s.indexOf(" ")).trim();
+              s = s.substring(s.indexOf(" ") + 1);
+
+              // get key/value pairs
+              while (s.indexOf("=") != -1) {
+                int eq = s.indexOf("=");
+                String key = s.substring(0, eq).trim();
+                String value =
+                  s.substring(eq + 2, s.indexOf("\"", eq + 2)).trim();
+
+                // strip out the data types
+                if (key.indexOf("runtype") == -1) {
+                  if (prefix.startsWith("Metadata_V1.2")) {
+                    prefix = "";
+                  }
+                  String effectiveKey = prefix + " " + pre + " " + key;
+                  if (!metadata.containsKey(effectiveKey)) {
+                    addMeta(effectiveKey, value);
+
+                    if (effectiveKey.equals(
+                      "MetadataSeq _SEQUENCE_INDEX=\"0\" uiCompCount value"))
+                    {
+                      if (value != null) {
+                        core.sizeC[0] = Integer.parseInt(value);
+                      }
+                    }
+                    else if (effectiveKey.endsWith("dTimeAbsolute value")) {
+                      long v = (long) Double.parseDouble(value);
+                      if (!ts.contains(new Long(v))) {
+                        core.sizeT[0]++;
+                        ts.add(new Long(v));
+                      }
+                    }
+                    else if (effectiveKey.endsWith("dZPos value")) {
+                      long v = (long) Double.parseDouble(value);
+                      if (!zs.contains(new Long(v))) {
+                        core.sizeZ[0]++;
+                        zs.add(new Long(v));
+                      }
+                    }
+                  }
+                  else {
+                    String v = (String) getMeta(effectiveKey);
+                    boolean parse = v != null;
+                    if (parse) {
+                      for (int i=0; i<v.length(); i++) {
+                        if (Character.isLetter(v.charAt(i)) ||
+                          Character.isWhitespace(v.charAt(i)))
+                        {
+                          parse = false;
+                          break;
+                        }
+                      }
+                    }
+                    if (parse) {
+                      addMeta(effectiveKey, value);
+                    }
+                  }
+                }
+                s = s.substring(s.indexOf("\"", eq + 2) + 1);
+              }
+            }
+          }
+        }
+      }
+      b = null;
+      xml = null;
+      st = null;
+    }
+
+    status("Populating metadata");
+
+    BufferedImage img = openImage(0);
+    core.sizeX[0] = img.getWidth();
+    core.sizeY[0] = img.getHeight();
+    core.sizeC[0] = img.getRaster().getNumBands();
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.pixelType[0] = ImageTools.getPixelType(img);
+
+    int numInvalid = 0;
+
+    for (int i=1; i<offsets.length; i++) {
+      if (offsets[i] - offsets[i - 1] < (core.sizeX[0] * core.sizeY[0] / 4)) {
+        offsets[i - 1] = 0;
+        numInvalid++;
+      }
+    }
+
+    long[] tempOffsets = new long[core.imageCount[0] - numInvalid];
+    int pt = 0;
+    for (int i=0; i<offsets.length; i++) {
+      if (offsets[i] != 0) {
+        tempOffsets[pt] = offsets[i];
+        pt++;
+      }
+    }
+    offsets = tempOffsets;
+    core.imageCount[0] = offsets.length;
+
+    String sigBits =
+      (String) getMeta("AdvancedImageAttributes SignificantBits value");
+    int bits = 0;
+    if (sigBits != null && sigBits.length() > 0) {
+      bits = Integer.parseInt(sigBits.trim());
+    }
+
+    // determine the pixel size
+    String pixX = (String)
+      getMeta("CalibrationSeq _SEQUENCE_INDEX=\"0\" dCalibration value");
+    String pixZ = (String)
+      getMeta("CalibrationSeq _SEQUENCE_INDEX=\"0\" dAspect value");
+
+    float pixSizeX = 0f;
+    float pixSizeZ = 0f;
+
+    if (pixX != null && pixX.length() > 0) {
+      pixSizeX = Float.parseFloat(pixX.trim());
+    }
+    if (pixZ != null && pixZ.length() > 0) {
+      pixSizeZ = Float.parseFloat(pixZ.trim());
+    }
+
+    core.currentOrder[0] = "XY";
+    long deltaT = ts.size() > 1 ?
+      ((Long) ts.get(1)).longValue() - ((Long) ts.get(0)).longValue() : 1;
+    long deltaZ = zs.size() > 1 ?
+      ((Long) zs.get(1)).longValue() - ((Long) zs.get(0)).longValue() : 1;
+
+    if (deltaT < deltaZ || deltaZ == 0) core.currentOrder[0] += "CTZ";
+    else core.currentOrder[0] += "CZT";
+
+    // we calculate this directly (instead of calling getEffectiveSizeC) because
+    // sizeZ and sizeT have not been accurately set yet
+    int effectiveC = ((core.sizeC[0] - 1) / 3) + 1;
+
+    if (core.imageCount[0] < core.sizeZ[0] * core.sizeT[0]) {
+      if (core.sizeT[0] == core.imageCount[0]) {
+        core.sizeT[0] /= core.sizeZ[0] * effectiveC;
+        while (core.imageCount[0] > core.sizeZ[0] * core.sizeT[0] * effectiveC)
+        {
+          core.sizeT[0]++;
+        }
+      }
+      else if (core.sizeZ[0] == core.imageCount[0]) {
+        core.sizeZ[0] /= core.sizeT[0] * effectiveC;
+        while (core.imageCount[0] > core.sizeZ[0] * core.sizeT[0] * effectiveC)
+        {
+          core.sizeZ[0]++;
+        }
+      }
+
+      if (core.imageCount[0] < core.sizeZ[0] * core.sizeT[0] * effectiveC) {
+        if (core.sizeZ[0] < core.sizeT[0]) {
+          core.sizeZ[0]--;
+          while (core.imageCount[0] >
+            core.sizeZ[0] * core.sizeT[0] * effectiveC)
+          {
+            core.sizeT[0]++;
+          }
+          while (core.imageCount[0] <
+            core.sizeZ[0] * core.sizeT[0] * effectiveC)
+          {
+            core.sizeT[0]--;
+          }
+        }
+        else {
+          core.sizeT[0]--;
+          while (core.imageCount[0] >
+            core.sizeZ[0] * core.sizeT[0] * effectiveC)
+          {
+            core.sizeZ[0]++;
+          }
+          if (core.imageCount[0] < core.sizeZ[0] * core.sizeT[0] * effectiveC) {
+            core.sizeZ[0]--;
+          }
+        }
+        while (core.imageCount[0] > core.sizeZ[0] * core.sizeT[0] * effectiveC)
+        {
+          core.imageCount[0]--;
+        }
+      }
+    }
+
+    if (bits != 0) {
+      int bpp = bits;
+      while (bpp % 8 != 0) bpp++;
+      switch (bpp) {
+        case 8:
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 16:
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 32:
+          core.pixelType[0] = FormatTools.UINT8;
+          core.sizeC[0] = 4;
+          break;
+        default:
+          throw new FormatException("Unsupported bits per pixel: " + bpp);
+      }
+
+    }
+
+    if (core.sizeZ[0] == 0) core.sizeZ[0] = 1;
+    if (core.sizeT[0] == 0) core.sizeT[0] = 1;
+
+    if (core.imageCount[0] < core.sizeZ[0] * core.sizeT[0] * core.sizeC[0]) {
+      core.sizeT[0] = core.imageCount[0];
+      core.sizeZ[0] = 1;
+    }
+
+    if (core.sizeZ[0] * core.sizeT[0] * core.sizeC[0] < core.imageCount[0]) {
+      core.sizeT[0] = 1;
+      core.sizeZ[0] = core.imageCount[0];
+    }
+
+    core.rgb[0] = core.sizeC[0] >= 3;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    store.setDimensions(new Float(pixSizeX), new Float(pixSizeX),
+      new Float(pixSizeZ), null, null, null);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+
+    String prefix = "MetadataSeq _SEQUENCE_INDEX=\"0\" ";
+
+    String gain = (String) getMeta(prefix + "dGain value");
+    String voltage = (String) getMeta(prefix + "dLampVoltage value");
+    String mag = (String) getMeta(prefix + "dObjectiveMag value");
+    String na = (String) getMeta(prefix + "dObjectiveNA value");
+
+    store.setDetector(null, null, null, null,
+      gain == null ? null : new Float(gain),
+      voltage == null ? null : new Float(voltage), null, null, null);
+    store.setObjective(null, null, null, na == null ? null : new Float(na),
+      mag == null ? null : new Float(mag), null, null);
+  }
+
+  // -- Helper class --
+
+  /** SAX handler for parsing XML. */
+  class ND2Handler extends DefaultHandler {
+    public void startElement(String uri, String localName, String qName,
+      Attributes attributes)
+    {
+      if (qName.equals("uiWidth")) {
+        core.sizeX[0] = Integer.parseInt(attributes.getValue("value"));
+      }
+      else if (qName.equals("uiWidthBytes")) {
+        int bytes =
+          Integer.parseInt(attributes.getValue("value")) / core.sizeX[0];
+        switch (bytes) {
+          case 2:
+            core.pixelType[0] = FormatTools.UINT16;
+            break;
+          case 4:
+            core.pixelType[0] = FormatTools.UINT32;
+            break;
+          default: core.pixelType[0] = FormatTools.UINT8;
+        }
+      }
+      else if (qName.equals("bValid")) {
+        adjustImageCount = attributes.getValue("value").equals("true");
+      }
+      else if (qName.equals("uiComp")) {
+        core.sizeC[0] = Integer.parseInt(attributes.getValue("value"));
+      }
+      else if (qName.equals("uiBpcInMemory")) {
+        if (attributes.getValue("value") == null) return;
+    	  int bits = Integer.parseInt(attributes.getValue("value"));
+        int bytes = bits / 8;
+        switch (bytes) {
+          case 1:
+            core.pixelType[0] = FormatTools.UINT8;
+            break;
+          case 2:
+        	core.pixelType[0] = FormatTools.UINT16;
+        	break;
+          case 4:
+        	core.pixelType[0] = FormatTools.UINT32;
+        	break;
+          default: core.pixelType[0] = FormatTools.UINT8;
+        }
+        addMeta(qName, attributes.getValue("value"));
+      }
+      else if (qName.equals("uiHeight")) {
+        core.sizeY[0] = Integer.parseInt(attributes.getValue("value"));
+      }
+      else if (qName.equals("uiCount")) {
+        int n = Integer.parseInt(attributes.getValue("value"));
+        if (core.imageCount[0] == 0) {
+          core.imageCount[0] = n;
+          core.sizeZ[0] = n;
+        }
+        core.sizeT[0] = 1;
+      }
+      else if (qName.equals("dCompressionParam")) {
+        isLossless = !attributes.getValue("value").equals("0");
+        addMeta(qName, attributes.getValue("value"));
+      }
+      else {
+        addMeta(qName, attributes.getValue("value"));
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/NRRDReader.java b/loci/formats/in/NRRDReader.java
new file mode 100644
index 0000000..842de8f
--- /dev/null
+++ b/loci/formats/in/NRRDReader.java
@@ -0,0 +1,219 @@
+//
+// NRRDReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import loci.formats.*;
+
+/**
+ * File format reader for NRRD files;  see http://teem.sourceforge.net/nrrd.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/NRRDReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/NRRDReader.java">SVN</a></dd></dl>
+ */
+public class NRRDReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Helper reader. */
+  private ImageReader helper;
+
+  /** Name of data file, if the current extension is 'nhdr'. */
+  private String dataFile;
+
+  /** Data encoding. */
+  private String encoding;
+
+  /** Offset to pixel data. */
+  private long offset;
+
+  // -- Constructor --
+
+  /** Constructs a new NRRD reader. */
+  public NRRDReader() { super("NRRD", new String[] {"nrrd", "nhdr"}); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 4) return false;
+    return new String(block).startsWith("NRRD");
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    if (dataFile == null) return new String[] {currentId};
+    return new String[] {currentId, dataFile};
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    // TODO : add support for additional encoding types
+    if (dataFile == null) {
+      if (encoding.equals("raw")) {
+        in.seek(offset + no * buf.length);
+        in.read(buf);
+        return buf;
+      }
+      else throw new FormatException("Unsupported encoding: " + encoding);
+    }
+    return helper.openBytes(no, buf);
+  }
+
+  /* @see loci.formats.IFormatReader#close() */
+  public void close() throws IOException {
+    super.close();
+    if (helper != null) helper.close();
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    helper = new ImageReader();
+
+    boolean finished = false;
+    String line, key, v;
+
+    int numDimensions = 0;
+
+    core.sizeX[0] = 1;
+    core.sizeY[0] = 1;
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = 1;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYCZT";
+
+    while (!finished) {
+      line = in.readLine().trim();
+      if (!line.startsWith("#") && line.length() > 0 &&
+        !line.startsWith("NRRD"))
+      {
+        // parse key/value pair
+        key = line.substring(0, line.indexOf(":")).trim();
+        v = line.substring(line.indexOf(":") + 1).trim();
+        addMeta(key, v);
+
+        if (key.equals("type")) {
+          if (v.indexOf("char") != -1 || v.indexOf("8") != -1) {
+            core.pixelType[0] = FormatTools.UINT8;
+          }
+          else if (v.indexOf("short") != -1 || v.indexOf("16") != -1) {
+            core.pixelType[0] = FormatTools.UINT16;
+          }
+          else if (v.equals("int") || v.equals("signed int") ||
+            v.equals("int32") || v.equals("int32_t") || v.equals("uint") ||
+            v.equals("unsigned int") || v.equals("uint32") ||
+            v.equals("uint32_t"))
+          {
+            core.pixelType[0] = FormatTools.UINT32;
+          }
+          else if (v.equals("float")) core.pixelType[0] = FormatTools.FLOAT;
+          else if (v.equals("double")) core.pixelType[0] = FormatTools.DOUBLE;
+          else throw new FormatException("Unsupported data type: " + v);
+        }
+        else if (key.equals("dimension")) {
+          numDimensions = Integer.parseInt(v);
+        }
+        else if (key.equals("sizes")) {
+          StringTokenizer tokens = new StringTokenizer(v, " ");
+          for (int i=0; i<numDimensions; i++) {
+            String t = tokens.nextToken();
+            int size = Integer.parseInt(t);
+
+            if (numDimensions >= 3 && i == 0 && size > 1 && size <= 4) {
+              core.sizeC[0] = size;
+            }
+            else if (i == 0 || (core.sizeC[0] > 1 && i == 1)) {
+              core.sizeX[0] = size;
+            }
+            else if (i == 1 || (core.sizeC[0] > 1 && i == 2)) {
+              core.sizeY[0] = size;
+            }
+            else if (i == 2 || (core.sizeC[0] > 1 && i == 3)) {
+              core.sizeZ[0] = size;
+            }
+            else if (i == 3 || (core.sizeC[0] > 1 && i == 4)) {
+              core.sizeT[0] = size;
+            }
+          }
+        }
+        else if (key.equals("data file") || key.equals("datafile")) {
+          dataFile = v;
+        }
+        else if (key.equals("encoding")) encoding = v;
+        else if (key.equals("endian")) {
+          core.littleEndian[0] = v.equals("little");
+        }
+      }
+
+      if ((line.length() == 0 && dataFile == null) || line == null) {
+        finished = true;
+      }
+      if (dataFile != null && (in.length() - in.getFilePointer() < 2)) {
+        finished = true;
+      }
+    }
+
+    if (dataFile == null) offset = in.getFilePointer();
+    else {
+      File f = new File(currentId);
+      if (f.exists() && f.getParentFile() != null) {
+        dataFile =
+          f.getParentFile().getAbsolutePath() + File.separator + dataFile;
+      }
+      helper.setId(dataFile);
+    }
+
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.interleaved[0] = true;
+    core.imageCount[0] = core.sizeZ[0] * core.sizeT[0];
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/NikonReader.java b/loci/formats/in/NikonReader.java
new file mode 100644
index 0000000..53fa612
--- /dev/null
+++ b/loci/formats/in/NikonReader.java
@@ -0,0 +1,403 @@
+//
+// NikonReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * NikonReader is the file format reader for
+ * Nikon NEF (TIFF) files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/NikonReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/NikonReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class NikonReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  /** Maximum number of bytes to check for Nikon header information. */
+  private static final int BLOCK_CHECK_LEN = 16384;
+
+  // Tags that give a good indication of whether this is an NEF file.
+  private static final int EXIF_IFD_POINTER = 34665;
+  private static final int TIFF_EPS_STANDARD = 37398;
+
+  // EXIF IFD tags.
+  private static final int CFA_REPEAT_DIM = 33421;
+  private static final int EXPOSURE_TIME = 33434;
+  private static final int APERTURE = 33437;
+  private static final int EXPOSURE_PROGRAM = 34850;
+  private static final int DATE_TIME_DIGITIZED = 36867;
+  private static final int DATE_TIME_ORIGINAL = 36868;
+  private static final int EXPOSURE_BIAS_VALUE = 37380;
+  private static final int MAX_APERTURE_VALUE = 37381;
+  private static final int METERING_MODE = 37383;
+  private static final int LIGHT_SOURCE = 37384;
+  private static final int FLASH = 37385;
+  private static final int FOCAL_LENGTH = 37386;
+  private static final int SENSING_METHOD = 37399;
+  private static final int MAKER_NOTE = 37500;
+  private static final int USER_COMMENT = 37510;
+  private static final int SUBSEC_TIME = 37520;
+  private static final int SUBSEC_TIME_ORIGINAL = 37521;
+  private static final int SUBSEC_TIME_DIGITIZED = 37522;
+  private static final int COLOR_SPACE = 40961;
+  private static final int FILE_SOURCE = 41728;
+  private static final int SCENE_TYPE = 41729;
+  private static final int CFA_PATTERN = 41730;
+  private static final int CUSTOM_RENDERED = 41985;
+  private static final int EXPOSURE_MODE = 41986;
+  private static final int WHITE_BALANCE = 41987;
+  private static final int DIGITAL_ZOOM_RATIO = 41988;
+  private static final int FOCAL_LENGTH_35MM_FILM = 41989;
+  private static final int SCENE_CAPTURE_TYPE = 41990;
+  private static final int GAIN_CONTROL = 41991;
+  private static final int CONTRAST = 41992;
+  private static final int SATURATION = 41993;
+  private static final int SHARPNESS = 41994;
+  private static final int SUBJECT_DISTANCE_RANGE = 41996;
+
+  // Maker Note tags.
+  private static final int FIRMWARE_VERSION = 1;
+  private static final int ISO = 2;
+  private static final int QUALITY = 4;
+  private static final int MAKER_WHITE_BALANCE = 5;
+  private static final int SHARPENING = 6;
+  private static final int FOCUS_MODE = 7;
+  private static final int FLASH_SETTING = 8;
+  private static final int FLASH_MODE = 9;
+  private static final int WHITE_BALANCE_FINE = 11;
+  private static final int WHITE_BALANCE_RGB_COEFFS = 12;
+  private static final int FLASH_COMPENSATION = 18;
+  private static final int TONE_COMPENSATION = 129;
+  private static final int LENS_TYPE = 131;
+  private static final int LENS = 132;
+  private static final int FLASH_USED = 135;
+  private static final int CURVE = 140;
+  private static final int COLOR_MODE = 141;
+  private static final int LIGHT_TYPE = 144;
+  private static final int HUE = 146;
+  private static final int CAPTURE_EDITOR_DATA = 3585;
+
+  // -- Fields --
+
+  /** Offset to the Nikon Maker Note. */
+  protected int makerNoteOffset;
+
+  /** The original IFD. */
+  protected Hashtable original;
+
+  // -- Constructor --
+
+  /** Constructs a new Nikon reader. */
+  public NikonReader() {
+    super("Nikon NEF (TIFF)", new String[] {"nef", "tif", "tiff"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    // adapted from MetamorphReader.isThisType(byte[])
+
+    if (block.length < 3) {
+      return false;
+    }
+    if (block.length < 8) {
+      return true; // we have no way of verifying further
+    }
+
+    boolean little = (block[0] == 0x49 && block[1] == 0x49);
+
+    int ifdlocation = DataTools.bytesToInt(block, 4, little);
+    if (ifdlocation < 0 || ifdlocation + 1 > block.length) {
+      return false;
+    }
+    else {
+      int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, little);
+      for (int i=0; i<ifdnumber; i++) {
+        if (ifdlocation + 3 + (i*12) > block.length) {
+          return false;
+        }
+        else {
+          int ifdtag = DataTools.bytesToInt(block,
+            ifdlocation + 2 + (i*12), 2, little);
+          if (ifdtag == TIFF_EPS_STANDARD) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    String lname = name.toLowerCase();
+    if (lname.endsWith(".nef")) return true;
+    else if (!lname.endsWith(".tif") && !lname.endsWith(".tiff")) return false;
+
+    // just checking the filename isn't enough to differentiate between
+    // Nikon and regular TIFF; open the file and check more thoroughly
+    return open ? checkBytes(name, BLOCK_CHECK_LEN) : true;
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    // look for the TIFF_EPS_STANDARD tag
+    // it should contain version information
+
+    try {
+      short[] version = (short[])
+        TiffTools.getIFDValue(original, TIFF_EPS_STANDARD);
+      String v = "";
+      for (int i=0; i<version.length; i++) v += version[i];
+      addMeta("Version", v);
+    }
+    catch (NullPointerException e) { }
+
+    core.littleEndian[0] = true;
+    try {
+      core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]);
+    }
+    catch (FormatException f) { }
+
+    // now look for the EXIF IFD pointer
+
+    try {
+      int exif = TiffTools.getIFDIntValue(original, EXIF_IFD_POINTER);
+      if (exif != -1) {
+        Hashtable exifIFD = TiffTools.getIFD(in, 0, exif);
+
+        // put all the EXIF data in the metadata hashtable
+
+        if (exifIFD != null) {
+          Enumeration e = exifIFD.keys();
+          Integer key;
+          while (e.hasMoreElements()) {
+            key = (Integer) e.nextElement();
+            int tag = key.intValue();
+            if (tag == CFA_PATTERN) {
+              byte[] cfa = (byte[]) exifIFD.get(key);
+              int[] colorMap = new int[cfa.length];
+              for (int i=0; i<cfa.length; i++) colorMap[i] = (int) cfa[i];
+              addMeta(getTagName(tag), colorMap);
+            }
+            else addMeta(getTagName(tag), exifIFD.get(key));
+          }
+        }
+      }
+    }
+    catch (IOException io) { }
+    catch (NullPointerException e) { }
+
+    // read the maker note
+
+    byte[] offsets = (byte[]) getMeta("Offset to maker note");
+    if (offsets != null) makerNoteOffset = offsets[0];
+    try {
+      if (makerNoteOffset >= in.length() || makerNoteOffset == 0) return;
+      Hashtable makerNote = TiffTools.getIFD(in, 0, makerNoteOffset);
+      if (makerNote != null) {
+        Enumeration e = makerNote.keys();
+        Integer key;
+        while (e.hasMoreElements()) {
+          key = (Integer) e.nextElement();
+          int tag = key.intValue();
+          if (makerNote.containsKey(key)) {
+            addMeta(getTagName(tag), makerNote.get(key));
+          }
+        }
+      }
+    }
+    catch (IOException exc) {
+      if (debug) trace(exc);
+    }
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("NikonReader.initFile(" + id + ")");
+    super.initFile(id);
+
+    in = new RandomAccessStream(id);
+    if (in.readShort() == 0x4949) in.order(true);
+
+    ifds = TiffTools.getIFDs(in);
+    if (ifds == null) throw new FormatException("No IFDs found");
+
+    // look for the SubIFD tag (330);
+
+    int offset = 0;
+    try {
+      offset = TiffTools.getIFDIntValue(ifds[0], 330, false, 0);
+    }
+    catch (FormatException exc) {
+      if (debug) trace(exc);
+      long[] array = TiffTools.getIFDLongArray(ifds[0], 330, false);
+      offset = (int) array[array.length - 1];
+    }
+
+    Hashtable realImage = TiffTools.getIFD(in, 1, offset);
+
+    original = ifds[0];
+    ifds[0] = realImage;
+    core.imageCount[0] = 1;
+
+    Object pattern = getMeta("CFA pattern");
+    if (pattern != null) {
+      realImage.put(new Integer(TiffTools.COLOR_MAP), getMeta("CFA pattern"));
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Gets the name of the IFD tag encoded by the given number. */
+  private String getTagName(int tag) {
+    switch (tag) {
+      case CFA_REPEAT_DIM:
+        return "CFA Repeat Dimensions";
+      case EXPOSURE_TIME:
+        return "Exposure Time";
+      case APERTURE:
+        return "Aperture";
+      case EXPOSURE_PROGRAM:
+        return "Exposure Program";
+      case DATE_TIME_DIGITIZED:
+        return "Date/Time Digitized";
+      case DATE_TIME_ORIGINAL:
+        return "Date/Time Original";
+      case EXPOSURE_BIAS_VALUE:
+        return "Exposure Bias Value";
+      case MAX_APERTURE_VALUE:
+        return "Max Aperture Value";
+      case METERING_MODE:
+        return "Metering Mode";
+      case LIGHT_SOURCE:
+        return "Light Source";
+      case FLASH:
+        return "Flash Enabled?";
+      case FOCAL_LENGTH:
+        return "Focal length of lens";
+      case SENSING_METHOD:
+        return "Sensing Method";
+      case MAKER_NOTE:
+        return "Offset to maker note";
+      case USER_COMMENT:
+        return "User comment";
+      case SUBSEC_TIME:
+        return "Subsec. Sampling for Date/Time field";
+      case SUBSEC_TIME_ORIGINAL:
+        return "Subsec. Sampling for original date";
+      case SUBSEC_TIME_DIGITIZED:
+        return "Subsec. Sampling for digitized date";
+      case COLOR_SPACE:
+        return "Color space";
+      case FILE_SOURCE:
+        return "File source";
+      case SCENE_TYPE:
+        return "Scene type";
+      case CFA_PATTERN:
+        return "CFA pattern";
+      case CUSTOM_RENDERED:
+        return "Custom Rendered?";
+      case EXPOSURE_MODE:
+        return "Exposure mode";
+      case WHITE_BALANCE:
+        return "White Balance";
+      case DIGITAL_ZOOM_RATIO:
+        return "Digital Zoom Ratio";
+      case FOCAL_LENGTH_35MM_FILM:
+        return "Focal Length of 35mm lens";
+      case SCENE_CAPTURE_TYPE:
+        return "Scene Capture Type";
+      case GAIN_CONTROL:
+        return "Gain Control";
+      case CONTRAST:
+        return "Contrast";
+      case SATURATION:
+        return "Saturation";
+      case SHARPNESS:
+        return "Sharpness";
+      case SUBJECT_DISTANCE_RANGE:
+        return "Subject Distance Range";
+      case FIRMWARE_VERSION:
+        return "Firmware version";
+      case ISO:
+        return "ISO";
+      case QUALITY:
+        return "Quality";
+      case MAKER_WHITE_BALANCE:
+        return "White Balance (Maker)";
+      case SHARPENING:
+        return "Sharpening";
+      case FOCUS_MODE:
+        return "Focus Mode";
+      case FLASH_SETTING:
+        return "Flash Setting";
+      case FLASH_MODE:
+        return "Flash Mode";
+      case WHITE_BALANCE_FINE:
+        return "White Balance Fine";
+      case WHITE_BALANCE_RGB_COEFFS:
+        return "White Balance (RGB coefficients)";
+      case FLASH_COMPENSATION:
+        return "Flash compensation";
+      case TONE_COMPENSATION:
+        return "Tone compensation";
+      case LENS_TYPE:
+        return "Lens type";
+      case LENS:
+        return "Lens";
+      case FLASH_USED:
+        return "Flash used?";
+      case CURVE:
+        return "Curve";
+      case COLOR_MODE:
+        return "Color mode";
+      case LIGHT_TYPE:
+        return "Light type";
+      case HUE:
+        return "Hue";
+      case CAPTURE_EDITOR_DATA:
+        return "Capture Editor Data";
+    }
+    return "" + tag;
+  }
+
+}
diff --git a/loci/formats/in/OIBReader.java b/loci/formats/in/OIBReader.java
new file mode 100644
index 0000000..6dbf5b6
--- /dev/null
+++ b/loci/formats/in/OIBReader.java
@@ -0,0 +1,614 @@
+//
+// OIBReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * OIBReader is the file format reader for Fluoview FV1000 OIB files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OIBReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OIBReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class OIBReader extends FormatReader {
+
+  // -- Constants --
+
+  private static final String NO_POI_MSG =
+    "Jakarta POI is required to read OIB files. Please " +
+    "obtain poi-loci.jar from http://loci.wisc.edu/ome/formats.html";
+
+  // -- Static fields --
+
+  private static boolean noPOI = false;
+  private static ReflectedUniverse r = createReflectedUniverse();
+
+  private static ReflectedUniverse createReflectedUniverse() {
+    r = null;
+    try {
+      r = new ReflectedUniverse();
+      r.exec("import org.apache.poi.poifs.filesystem.POIFSFileSystem");
+      r.exec("import org.apache.poi.poifs.filesystem.DirectoryEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentInputStream");
+      r.exec("import java.util.Iterator");
+    }
+    catch (ReflectException exc) {
+      noPOI = true;
+    }
+    return r;
+  }
+
+  // -- Fields --
+
+  /** Number of images. */
+  private Vector nImages;
+
+  /** Image width. */
+  private Vector width;
+
+  /** Image height. */
+  private Vector height;
+
+  /** Number of channels. */
+  private Vector nChannels;
+
+  /** Number of timepoints. */
+  private Vector tSize;
+
+  /** Number of Z slices. */
+  private Vector zSize;
+
+  /** Number of bytes per pixel. */
+  private Vector bpp;
+
+  /** Hashtable containing the directory entry for each plane. */
+  private Vector pixels;
+
+  /**
+   * Hashtable containing the document name for each plane,
+   * indexed by the plane number.
+   */
+  private Vector names;
+
+  private Vector rgb;
+
+  /** Axis data. */
+  private String[] labels = new String[9];
+  private String[] dims = new String[9];
+  private String[] starts = new String[9];
+  private String[] stops = new String[9];
+
+  // -- Constructor --
+
+  /** Constructs a new OIB reader. */
+  public OIBReader() { super("Fluoview FV1000 OIB", "oib"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return (block[0] == 0xd0 && block[1] == 0xcf &&
+      block[2] == 0x11 && block[3] == 0xe0);
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    try {
+      Integer ii = new Integer(no);
+      String directory = (String) ((Hashtable) pixels.get(series)).get(ii);
+      String name = (String) ((Hashtable) names.get(series)).get(ii);
+
+      r.setVar("dirName", directory);
+      r.exec("root = fs.getRoot()");
+      r.exec("dir = root.getEntry(dirName)");
+      r.setVar("entryName", name);
+      r.exec("document = dir.getEntry(entryName)");
+      r.exec("dis = new DocumentInputStream(document)");
+      r.exec("numBytes = dis.available()");
+      int numBytes = ((Integer) r.getVar("numBytes")).intValue();
+      byte[] b = new byte[numBytes + 4]; // append 0 for final offset
+      r.setVar("data", b);
+      r.exec("dis.read(data)");
+
+      RandomAccessStream stream = new RandomAccessStream(b);
+      Hashtable[] ifds = TiffTools.getIFDs(stream);
+      TiffTools.getSamples(ifds[0], stream, buf);
+      stream.close();
+      return buf;
+    }
+    catch (ReflectException e) {
+      throw new FormatException(e);
+    }
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    String[] vars = {"dirName", "root", "dir", "document", "dis",
+      "numBytes", "data", "fis", "fs", "iter", "isInstance", "isDocument",
+      "entry", "documentName", "entryName"};
+    for (int i=0; i<vars.length; i++) r.setVar(vars[i], null);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("OIBReader.initFile(" + id + ")");
+    if (noPOI) throw new FormatException(NO_POI_MSG);
+    super.initFile(id);
+
+    width = new Vector();
+    height = new Vector();
+    nChannels = new Vector();
+    zSize = new Vector();
+    tSize = new Vector();
+    pixels = new Vector();
+    names = new Vector();
+    nImages = new Vector();
+    bpp = new Vector();
+    bpp.add(new Integer(0));
+    rgb = new Vector();
+
+    try {
+      in = new RandomAccessStream(id);
+      if (in.length() % 4096 != 0) {
+        in.setExtend(4096 - (int) (in.length() % 4096));
+      }
+      r.setVar("fis", in);
+      r.exec("fs = new POIFSFileSystem(fis)");
+      r.exec("dir = fs.getRoot()");
+      parseDir(0, r.getVar("dir"));
+
+      int numSeries = width.size();
+
+      status("Sorting images");
+
+      // sort names
+
+      for (int i=0; i<numSeries; i++) {
+        Vector newKeys = new Vector();
+        Vector comps = new Vector();
+        Enumeration keys = ((Hashtable) names.get(i)).keys();
+        while (keys.hasMoreElements()) {
+          Object key = keys.nextElement();
+          String value = (String) ((Hashtable) names.get(i)).get(key);
+          int comp = Integer.parseInt(value.substring(value.indexOf("0")));
+          int size = newKeys.size();
+          for (int j=0; j<size; j++) {
+            if (comp < ((Integer) comps.get(j)).intValue()) {
+              newKeys.add(j, value);
+              comps.add(j, new Integer(comp));
+              j = size;
+            }
+            else if (j == size - 1) {
+              newKeys.add(value);
+              comps.add(new Integer(comp));
+              j = size;
+            }
+          }
+          if (newKeys.size() == 0) {
+            newKeys.add(value);
+            comps.add(new Integer(comp));
+          }
+        }
+
+        Hashtable newNames = new Hashtable();
+        for (int j=0; j<newKeys.size(); j++) {
+          newNames.put(new Integer(j), newKeys.get(j));
+        }
+        names.setElementAt(newNames, i);
+      }
+
+      status("Populating metadata");
+
+      for (int i=0; i<labels.length; i++) {
+        if (labels[i] == null) labels[i] = "";
+        if (dims[i] == null) dims[i] = "0";
+        if (starts[i] == null) starts[i] = "0";
+        if (stops[i] == null) stops[i] = "0";
+      }
+
+      for (int i=0; i<labels.length; i++) {
+        if (labels[i].equals("\"X\"") || labels[i].equals("\"Y\"")) { }
+        else if (labels[i].equals("\"C\"")) {
+          if (!starts[i].equals(stops[i])) nChannels.add(new Integer(dims[i]));
+          else nChannels.add(new Integer(1));
+        }
+        else if (labels[i].equals("\"Z\"")) {
+          if (!starts[i].equals(stops[i])) zSize.add(new Integer(dims[i]));
+          else zSize.add(new Integer(1));
+        }
+        else if (labels[i].equals("\"T\"")) {
+          if (!starts[i].equals(stops[i])) tSize.add(new Integer(dims[i]));
+          else tSize.add(new Integer(1));
+        }
+        else if (!dims[i].equals("0")) {
+          if (nChannels.size() > 0) {
+            int ch = ((Integer) nChannels.get(nChannels.size() - 1)).intValue();
+            ch *= Integer.parseInt(dims[i]);
+            nChannels.setElementAt(new Integer(ch), nChannels.size() - 1);
+          }
+          else nChannels.add(new Integer(dims[i]));
+        }
+      }
+
+      core = new CoreMetadata(numSeries);
+
+      for (int i=0; i<numSeries; i++) {
+        core.indexed[i] = false;
+        core.falseColor[i] = false;
+
+        core.sizeX[i] = ((Integer) width.get(i)).intValue();
+        core.sizeY[i] = ((Integer) height.get(i)).intValue();
+
+        if (i < zSize.size()) {
+          core.sizeZ[i] = ((Integer) zSize.get(i)).intValue();
+        }
+        else core.sizeZ[i] = 1;
+
+        if (i < nChannels.size()) {
+          core.sizeC[i] = ((Integer) nChannels.get(i)).intValue();
+        }
+        else core.sizeC[i] = 1;
+
+        if (i < tSize.size()) {
+          core.sizeT[i] = ((Integer) tSize.get(i)).intValue();
+        }
+        else core.sizeT[i] = 1;
+
+        if (core.sizeZ[i] == 0) core.sizeZ[i]++;
+        if (core.sizeT[i] == 0) core.sizeT[i]++;
+
+        core.currentOrder[i] =
+          (core.sizeZ[i] > core.sizeT[i]) ? "XYCZT" : "XYCTZ";
+
+        core.imageCount[i] = ((Integer) nImages.get(i)).intValue();
+
+        if (core.imageCount[i] > core.sizeZ[i] * core.sizeT[i] * core.sizeC[i])
+        {
+          int diff = core.imageCount[i] -
+            (core.sizeZ[i] * core.sizeT[i] * core.sizeC[i]);
+
+          if (diff % core.sizeZ[i] == 0 && core.sizeZ[i] > 1) {
+            while (core.imageCount[i] >
+              core.sizeZ[i] * core.sizeT[i] * core.sizeC[i])
+            {
+              core.sizeT[i]++;
+            }
+          }
+          else if (diff % core.sizeT[i] == 0 && core.sizeT[i] > 1) {
+            while (core.imageCount[i] >
+              core.sizeZ[i] * core.sizeT[i] * core.sizeC[i])
+            {
+              core.sizeZ[i]++;
+            }
+          }
+          else if (diff % core.sizeC[i] == 0) {
+            if (core.sizeZ[i] > core.sizeT[i]) {
+              while (core.imageCount[i] >
+                core.sizeZ[i] * core.sizeC[i] * core.sizeT[i])
+              {
+                core.sizeZ[i]++;
+              }
+            }
+            else {
+              while (core.imageCount[i] >
+                core.sizeZ[i] * core.sizeC[i] * core.sizeT[i])
+              {
+                core.sizeT[i]++;
+              }
+            }
+          }
+        }
+
+        int oldSeries = getSeries();
+        setSeries(i);
+        while (core.imageCount[i] <
+          core.sizeZ[i] * core.sizeT[i] * getEffectiveSizeC())
+        {
+          core.imageCount[i]++;
+        }
+        nImages.setElementAt(new Integer(core.imageCount[i]), i);
+        setSeries(oldSeries);
+
+        core.rgb[i] = ((Boolean) rgb.get(i)).booleanValue();
+        core.interleaved[i] = false;
+        core.metadataComplete[i] = true;
+      }
+
+      Integer ii = new Integer(0);
+      String directory = (String) ((Hashtable) pixels.get(series)).get(ii);
+      String name = (String) ((Hashtable) names.get(series)).get(ii);
+
+      r.setVar("dirName", directory);
+      r.exec("root = fs.getRoot()");
+      r.exec("dir = root.getEntry(dirName)");
+      r.setVar("entryName", name);
+      r.exec("document = dir.getEntry(entryName)");
+      r.exec("dis = new DocumentInputStream(document)");
+      r.exec("numBytes = dis.available()");
+      int numBytes = ((Integer) r.getVar("numBytes")).intValue();
+      byte[] b = new byte[numBytes + 4]; // append 0 for final offset
+      r.setVar("data", b);
+      r.exec("dis.read(data)");
+
+      RandomAccessStream stream = new RandomAccessStream(b);
+      Hashtable[] ifds = TiffTools.getIFDs(stream);
+
+      Arrays.fill(core.littleEndian, !TiffTools.isLittleEndian(ifds[0]));
+    }
+    catch (ReflectException e) {
+      throw new FormatException(e);
+    }
+
+    try {
+      initMetadata();
+    }
+    catch (FormatException exc) {
+      if (debug) trace(exc);
+    }
+    catch (IOException exc) {
+      if (debug) trace(exc);
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Initialize metadata hashtable and OME-XML structure. */
+  private void initMetadata() throws FormatException, IOException {
+    for (int i=0; i<width.size(); i++) {
+      switch (((Integer) bpp.get(0)).intValue() % 3) {
+        case 2:
+          core.pixelType[i] = FormatTools.UINT16;
+          break;
+        default: core.pixelType[i] = FormatTools.UINT8;
+      }
+    }
+
+    MetadataStore store = getMetadataStore();
+    String name = (String) getMeta("[File Info] - DataName");
+    if (name == null) name = currentId;
+
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<width.size(); i++) {
+      String acquisition = "[Acquisition Parameters Common] - ";
+
+      String stamp = (String) getMeta(acquisition + "ImageCaputreDate");
+
+      if (stamp != null) {
+        stamp = stamp.substring(1, stamp.length() - 1);
+        SimpleDateFormat parse = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        Date date = parse.parse(stamp, new ParsePosition(0));
+        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+        stamp = fmt.format(date);
+      }
+
+      store.setImage(name, stamp, null, new Integer(i));
+
+      String pre = "[Reference Image Parameter] - ";
+      String x = (String) getMeta(pre + "WidthConvertValue");
+      String y = (String) getMeta(pre + "HeightConvertValue");
+
+      store.setDimensions(x == null ? null : new Float(x),
+        y == null ? null : new Float(y), null, null, null, new Integer(i));
+      for (int j=0; j<core.sizeC[i]; j++) {
+        String prefix = "[Channel " + (j + 1) + " Parameters] - ";
+        String gain = (String) getMeta(prefix + "AnalogPMTGain");
+        String offset = (String) getMeta(prefix + "AnalogPMTOffset");
+        String voltage = (String) getMeta(prefix + "AnalogPMTVoltage");
+
+        if (gain != null) gain = gain.replaceAll("\"", "");
+        if (offset != null) offset = offset.replaceAll("\"", "");
+        if (voltage != null) voltage = voltage.replaceAll("\"", "");
+
+        store.setDetector(null, null, null, null, null, voltage == null ? null :
+          new Float(voltage), null, null, new Integer(j));
+
+        store.setLogicalChannel(j, null, null, null, null, null, null, null,
+          null, offset == null ? null : new Float(offset),
+          gain == null ? null : new Float(gain), null, null, null, null, null,
+          null, null, null, null, null, null, null, null, new Integer(i));
+      }
+
+      String laserCount = (String) getMeta(acquisition + "Number of use Laser");
+      int numLasers = laserCount == null ? 0 : Integer.parseInt(laserCount);
+
+      for (int j=0; j<numLasers; j++) {
+        String wave =
+          (String) getMeta(acquisition + "LaserWavelength0" + (j + 1));
+        if (wave == null) wave = "0";
+        store.setLaser(null, null, new Integer(wave), null, null, null, null,
+          null, null, null, new Integer(j));
+      }
+    }
+  }
+
+  protected void parseDir(int depth, Object dir)
+    throws IOException, FormatException, ReflectException
+  {
+    r.setVar("dir", dir);
+    r.exec("dirName = dir.getName()");
+    r.setVar("depth", depth);
+    r.exec("iter = dir.getEntries()");
+    Iterator iter = (Iterator) r.getVar("iter");
+    while (iter.hasNext()) {
+      r.setVar("entry", iter.next());
+      r.exec("isInstance = entry.isDirectoryEntry()");
+      r.exec("isDocument = entry.isDocumentEntry()");
+      boolean isInstance = ((Boolean) r.getVar("isInstance")).booleanValue();
+      boolean isDocument = ((Boolean) r.getVar("isDocument")).booleanValue();
+      r.setVar("dir", dir);
+      r.exec("dirName = dir.getName()");
+      if (isInstance)  {
+        status("Parsing embedded folder (" + (depth + 1) + ")");
+        parseDir(depth + 1, r.getVar("entry"));
+      }
+      else if (isDocument) {
+        status("Parsing embedded file (" + depth + ")");
+        r.exec("entryName = entry.getName()");
+        if (debug) {
+          print(depth + 1, "Found document: " + r.getVar("entryName"));
+        }
+        r.exec("dis = new DocumentInputStream(entry)");
+        r.exec("numBytes = dis.available()");
+        int numbytes = ((Integer) r.getVar("numBytes")).intValue();
+        byte[] data = new byte[numbytes + 4]; // append 0 for final offset
+        r.setVar("data", data);
+        r.exec("dis.read(data)");
+
+        String entryName = (String) r.getVar("entryName");
+        String dirName = (String) r.getVar("dirName");
+
+        // check the first 2 bytes of the stream
+
+        byte[] b = {data[0], data[1], data[2], data[3]};
+
+        if (data[0] == 0x42 && data[1] == 0x4d) {
+          // this is the thumbnail
+        }
+        else if (TiffTools.checkHeader(b) != null) {
+          // this is an actual image plane
+
+          RandomAccessStream ras = new RandomAccessStream(data);
+          Hashtable ifd = TiffTools.getIFDs(ras)[0];
+          ras.close();
+          int w = (int) TiffTools.getImageWidth(ifd);
+          int h = (int) TiffTools.getImageLength(ifd);
+
+          boolean isRGB = TiffTools.getSamplesPerPixel(ifd) > 1;
+          if (!isRGB) {
+            int p = TiffTools.getPhotometricInterpretation(ifd);
+            isRGB = p == TiffTools.RGB_PALETTE ||
+              p == TiffTools.CFA_ARRAY || p == TiffTools.RGB;
+          }
+
+          boolean added = false;
+          for (int i=0; i<width.size(); i++) {
+            if (((Integer) width.get(i)).intValue() == w &&
+              ((Integer) height.get(i)).intValue() == h)
+            {
+              int num = ((Integer) nImages.get(i)).intValue();
+              ((Hashtable) pixels.get(i)).put(new Integer(num), dirName);
+              ((Hashtable) names.get(i)).put(new Integer(num), entryName);
+              num++;
+              nImages.setElementAt(new Integer(num), i);
+              added = true;
+            }
+          }
+
+          if (!added) {
+            Hashtable ht = new Hashtable();
+            ht.put(new Integer(0), dirName);
+            pixels.add(ht);
+            ht = new Hashtable();
+            ht.put(new Integer(0), entryName);
+            names.add(ht);
+            nImages.add(new Integer(1));
+            width.add(new Integer(w));
+            height.add(new Integer(h));
+            rgb.add(new Boolean(isRGB));
+          }
+        }
+        else if (entryName.equals("OibInfo.txt")) { }
+        else {
+        //else if (data[0] == (byte) 0xff && data[1] == (byte) 0xfe) {
+          String ini = DataTools.stripString(new String(data));
+          StringTokenizer st = new StringTokenizer(ini, "\n");
+          String prefix = "";
+          while (st.hasMoreTokens()) {
+            String line = st.nextToken().trim();
+            if (!line.startsWith("[") && (line.indexOf("=") > 0)) {
+              String key = line.substring(0, line.indexOf("=")).trim();
+              String value = line.substring(line.indexOf("=") + 1).trim();
+
+              if (prefix.equals("[FileInformation] - ") &&
+                key.equals("Resolution"))
+              {
+                int max = Integer.parseInt(value);
+                int bytes = ((Integer) bpp.get(0)).intValue();
+                while (Math.pow(2, bytes) < max) bytes++;
+                bytes /= 8;
+                for (int i=0; i<bpp.size(); i++) {
+                  bpp.setElementAt(new Integer(bytes), i);
+                }
+              }
+
+              if (prefix.indexOf("Red") == -1 &&
+                prefix.indexOf("Green") == -1 && prefix.indexOf("Blue") == -1)
+              {
+                addMeta(prefix + key, value);
+
+                if (prefix.startsWith("[Axis ") &&
+                  prefix.endsWith("Parameters Common] - "))
+                {
+                  int ndx = Integer.parseInt(
+                    prefix.substring(6, prefix.indexOf("P")).trim());
+                  if (key.equals("AxisCode")) labels[ndx] = value;
+                  else if (key.equals("MaxSize")) dims[ndx] = value;
+                  else if (key.equals("StartPosition")) starts[ndx] = value;
+                  else if (key.equals("EndPosition")) stops[ndx] = value;
+                }
+              }
+            }
+            else {
+              if (line.indexOf("[") == 2) {
+                line = line.substring(2, line.length());
+              }
+              prefix = line + " - ";
+            }
+          }
+          data = null;
+        }
+
+        r.exec("dis.close()");
+      }
+    }
+  }
+
+  /** Debugging helper method. */
+  protected void print(int depth, String s) {
+    StringBuffer sb = new StringBuffer();
+    for (int i=0; i<depth; i++) sb.append("  ");
+    sb.append(s);
+    debug(sb.toString());
+  }
+
+}
diff --git a/loci/formats/in/OIFReader.java b/loci/formats/in/OIFReader.java
new file mode 100644
index 0000000..bfe35bc
--- /dev/null
+++ b/loci/formats/in/OIFReader.java
@@ -0,0 +1,429 @@
+//
+// OIFReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * OIFReader is the file format reader for Fluoview FV 1000 OIF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OIFReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OIFReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class OIFReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Names of every TIFF file to open. */
+  protected Vector tiffs;
+
+  /** Helper reader to open TIFF files. */
+  protected TiffReader[] tiffReader;
+
+  /** Helper reader to open the thumbnail. */
+  protected BMPReader thumbReader;
+
+  /** List of files in the current OIF dataset. */
+  protected Vector usedFiles;
+
+  protected String[] size = new String[9], code = new String[9];
+  protected int imageDepth;
+
+  // -- Constructor --
+
+  /** Constructs a new OIF reader. */
+  public OIFReader() {
+    super("Fluoview FV1000 OIF",
+      new String[] {"oif", "roi", "pty", "lut", "bmp"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    return FormatTools.MUST_GROUP;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    tiffReader[no].openBytes(0, buf);
+    tiffReader[no].close();
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    String dir =
+      currentId.substring(0, currentId.lastIndexOf(File.separator) + 1);
+    dir += currentId.substring(currentId.lastIndexOf(File.separator) + 1) +
+      ".files" + File.separator;
+
+    String thumbId = dir + currentId.substring(currentId.lastIndexOf(
+      File.separator) + 1, currentId.lastIndexOf(".")) + "_Thumb.bmp";
+    thumbReader.setId(thumbId);
+    return thumbReader.openImage(0);
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    return (String[]) usedFiles.toArray(new String[0]);
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly) {
+      if (in != null) in.close();
+      if (thumbReader != null) thumbReader.close(fileOnly);
+      if (tiffReader != null) {
+        for (int i=0; i<tiffReader.length; i++) {
+          if (tiffReader[i] != null) tiffReader[i].close(fileOnly);
+        }
+      }
+    }
+    else close();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    if (thumbReader != null) thumbReader.close();
+    if (tiffReader != null) {
+      for (int i=0; i<tiffReader.length; i++) {
+        if (tiffReader[i] != null) tiffReader[i].close();
+      }
+    }
+    tiffs = null;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("OIFReader.initFile(" + id + ")");
+
+    // check to make sure that we have the OIF file
+    // if not, we need to look for it in the parent directory
+
+    status("Finding metadata file");
+
+    String oifFile = id;
+    if (!id.toLowerCase().endsWith("oif")) {
+      Location current = new Location(id);
+      current = current.getAbsoluteFile();
+      String parent = current.getParent();
+      Location tmp = new Location(parent);
+      parent = tmp.getParent();
+
+      // strip off the filename
+
+      id = current.getPath();
+
+      oifFile = id.substring(id.lastIndexOf(File.separator));
+      oifFile = parent + oifFile.substring(0, oifFile.indexOf("_")) + ".oif";
+
+      tmp = new Location(oifFile);
+      if (!tmp.exists()) {
+        oifFile = oifFile.substring(0, oifFile.lastIndexOf(".")) + ".OIF";
+        tmp = new Location(oifFile);
+        if (!tmp.exists()) throw new FormatException("OIF file not found");
+        currentId = oifFile;
+      }
+      else currentId = oifFile;
+    }
+
+    super.initFile(oifFile);
+    in = new RandomAccessStream(oifFile);
+
+    usedFiles = new Vector();
+    usedFiles.add(new Location(oifFile).getAbsolutePath());
+
+    int slash = oifFile.lastIndexOf(File.separator);
+    String path = slash < 0 ? "." : oifFile.substring(0, slash);
+
+    // parse each key/value pair (one per line)
+
+    status("Parsing metadata values");
+
+    byte[] b = new byte[(int) in.length()];
+    in.read(b);
+    String s = new String(b);
+    StringTokenizer st = new StringTokenizer(s, "\r\n");
+
+    Hashtable filenames = new Hashtable();
+    String prefix = "";
+    while (st.hasMoreTokens()) {
+      String line = DataTools.stripString(st.nextToken().trim());
+      if (!line.startsWith("[") && (line.indexOf("=") > 0)) {
+        String key = line.substring(0, line.indexOf("=")).trim();
+        String value = line.substring(line.indexOf("=") + 1).trim();
+        if (key.startsWith("IniFileName") && key.indexOf("Thumb") == -1) {
+          int pos = Integer.parseInt(key.substring(11));
+          filenames.put(new Integer(pos), value.trim());
+        }
+        addMeta(prefix + key, value);
+
+        if (prefix.startsWith("[Axis ") &&
+          prefix.endsWith("Parameters Common] - "))
+        {
+          int ndx =
+            Integer.parseInt(prefix.substring(6, prefix.indexOf("P")).trim());
+          if (key.equals("AxisCode")) code[ndx] = value;
+          else if (key.equals("MaxSize")) size[ndx] = value;
+        }
+        else if ((prefix + key).equals("[Axis Parameter Common] - AxisOrder")) {
+          core.currentOrder[0] = value;
+        }
+        else if ((prefix + key).equals(
+          "[Reference Image Parameter] - ImageDepth"))
+        {
+          imageDepth = Integer.parseInt(value);
+        }
+      }
+      else if (line.length() > 0) {
+        if (line.indexOf("[") == 2) {
+          line = line.substring(2, line.length());
+        }
+        prefix = line + " - ";
+      }
+    }
+
+    int reference = ((String) filenames.get(new Integer(0))).length();
+    int numFiles = filenames.size();
+    for (int i=0; i<numFiles; i++) {
+      String value = (String) filenames.get(new Integer(i));
+      if (value.length() > reference) {
+        filenames.remove(new Integer(i));
+      }
+    }
+
+    status("Initializing helper readers");
+
+    thumbReader = new BMPReader();
+    core.imageCount[0] = filenames.size();
+    tiffs = new Vector(core.imageCount[0]);
+
+    tiffReader = new TiffReader[core.imageCount[0]];
+    for (int i=0; i<core.imageCount[0]; i++) {
+      tiffReader[i] = new TiffReader();
+      if (i > 0) tiffReader[i].setMetadataCollected(false);
+    }
+
+    // open each INI file (.pty extension)
+
+    status("Reading additional metadata");
+
+    String tiffPath = null;
+    RandomAccessStream ptyReader;
+
+    for (int i=0; i<core.imageCount[0]; i++) {
+      String file = (String) filenames.get(new Integer(i));
+      file = file.substring(1, file.length() - 1);
+      file = file.replace('\\', File.separatorChar);
+      file = file.replace('/', File.separatorChar);
+      file = path + File.separator + file;
+      tiffPath = file.substring(0, file.lastIndexOf(File.separator));
+
+      ptyReader = new RandomAccessStream(file);
+      b = new byte[(int) ptyReader.length()];
+      ptyReader.read(b);
+      s = new String(b);
+      st = new StringTokenizer(s, "\n");
+
+      while (st.hasMoreTokens()) {
+        String line = st.nextToken().trim();
+        if (!line.startsWith("[") && (line.indexOf("=") > 0)) {
+          String key = line.substring(0, line.indexOf("=") - 1).trim();
+          String value = line.substring(line.indexOf("=") + 1).trim();
+          key = DataTools.stripString(key);
+          value = DataTools.stripString(value);
+          if (key.equals("DataName")) {
+            value = value.substring(1, value.length() - 1);
+            if (value.indexOf("-R") == -1) {
+              tiffs.add(i, tiffPath + File.separator + value);
+              tiffReader[i].setId((String) tiffs.get(i));
+            }
+          }
+          addMeta("Image " + i + " : " + key, value);
+        }
+      }
+      ptyReader.close();
+    }
+
+    if (tiffPath != null) {
+      Location dir = new Location(tiffPath);
+      String[] list = dir.list();
+      for (int i=0; i<list.length; i++) {
+        usedFiles.add(new Location(tiffPath, list[i]).getAbsolutePath());
+      }
+    }
+
+    status("Populating metadata");
+
+    for (int i=0; i<9; i++) {
+      int ss = Integer.parseInt(size[i]);
+      if (code[i].equals("\"X\"")) core.sizeX[0] = ss;
+      else if (code[i].equals("\"Y\"")) core.sizeY[0] = ss;
+      else if (code[i].equals("\"C\"")) core.sizeC[0] = ss;
+      else if (code[i].equals("\"T\"")) core.sizeT[0] = ss;
+      else if (code[i].equals("\"Z\"")) core.sizeZ[0] = ss;
+    }
+
+    if (core.sizeZ[0] == 0) core.sizeZ[0] = 1;
+    if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+    if (core.sizeT[0] == 0) core.sizeT[0] = 1;
+
+    while (core.imageCount[0] >
+      core.sizeZ[0] * core.sizeT[0] * getEffectiveSizeC())
+    {
+      if (core.sizeZ[0] == 1) core.sizeT[0]++;
+      else if (core.sizeT[0] == 1) core.sizeZ[0]++;
+    }
+
+    core.currentOrder[0] =
+      core.currentOrder[0].substring(1, core.currentOrder[0].length() - 1);
+    if (core.currentOrder[0] == null) core.currentOrder[0] = "XYZTC";
+    else {
+      String[] names = new String[] {"X", "Y", "Z", "C", "T"};
+      if (core.currentOrder[0].length() < 5) {
+        for (int i=0; i<names.length; i++) {
+          if (core.currentOrder[0].indexOf(names[i]) == -1) {
+            core.currentOrder[0] += names[i];
+          }
+        }
+      }
+    }
+
+    BufferedImage thumbImage = openThumbImage(0);
+    core.thumbSizeX[0] = thumbImage.getWidth();
+    core.thumbSizeY[0] = thumbImage.getHeight();
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    switch (imageDepth) {
+      case 1:
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 2:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      case 4:
+        core.pixelType[0] = FormatTools.UINT32;
+        break;
+      default:
+        throw new RuntimeException(
+          "Unknown matching for pixel depth of: " + imageDepth);
+    }
+
+    core.rgb[0] = tiffReader[0].isRGB();
+    core.littleEndian[0] = true;
+    core.interleaved[0] = false;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = tiffReader[0].isIndexed();
+    core.falseColor[0] = false;
+
+    FormatTools.populatePixels(store, this);
+
+    prefix = "[Reference Image Parameter] - ";
+    String px = (String) getMeta(prefix + "WidthConvertValue");
+    String py = (String) getMeta(prefix + "HeightConvertValue");
+    Float pixX = null, pixY = null;
+    if (px != null) pixX = new Float(px);
+    if (py != null) pixY = new Float(py);
+    store.setDimensions(pixX, pixY, null, null, null, null);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      prefix = "[Channel " + (i+1) + " Parameters] - ";
+      String name = (String) getMeta(prefix + "CH Name");
+      String emWave = (String) getMeta(prefix + "EmissionWavelength");
+      String exWave = (String) getMeta(prefix + "ExcitationWavelength");
+
+      prefix = "[Channel " + (i+1) + " Parameters] - ";
+      String gain = (String) getMeta(prefix + "CountingPMTGain");
+      String voltage = (String) getMeta(prefix + "CountingPMTVoltage");
+      String offset = (String) getMeta(prefix + "CountingPMTOffset");
+
+      if (gain != null) gain.replaceAll("\"", "");
+      if (voltage != null) voltage.replaceAll("\"", "");
+      if (offset != null) offset.replaceAll("\"", "");
+
+      if (voltage != null) {
+        store.setDetector(null, null, null, null, null, new Float(voltage),
+          null, null, new Integer(i));
+      }
+
+      store.setLogicalChannel(i, name, null, null, null, null, null, null,
+        null, offset == null ? null : new Float(offset),
+        gain == null ? null : new Float(gain), null, null, null, null, null,
+        null, null, null, null, emWave == null ? null : new Integer(emWave),
+        exWave == null ? null : new Integer(exWave), null, null, null);
+    }
+
+    String mag = (String) getMeta("Image 0 : Magnification");
+    if (mag != null) {
+      store.setObjective(null, null, null, null, new Float(mag), null, null);
+    }
+
+    String num =
+      (String) getMeta("[Acquisition Parameters Common] - Number of use Laser");
+    if (num != null) {
+      int numLasers = Integer.parseInt(num);
+      for (int i=0; i<numLasers; i++) {
+        String wave = (String) getMeta("[Acquisition Parameters Common] - " +
+          "LaserWavelength0" + (i+1));
+        if (wave != null) {
+          store.setLaser(null, null, new Integer(wave), null, null, null, null,
+            null, null, null, new Integer(i));
+        }
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/OMETiffReader.java b/loci/formats/in/OMETiffReader.java
new file mode 100644
index 0000000..7c3e221
--- /dev/null
+++ b/loci/formats/in/OMETiffReader.java
@@ -0,0 +1,472 @@
+//
+// OMETiffReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import loci.formats.*;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * OMETiffReader is the file format reader for
+ * <a href="http://www.loci.wisc.edu/ome/ome-tiff-spec.html">OME-TIFF</a>
+ * files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OMETiffReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OMETiffReader.java">SVN</a></dd></dl>
+ */
+public class OMETiffReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  /** Factory for generating SAX parsers. */
+  public static final SAXParserFactory SAX_FACTORY =
+    SAXParserFactory.newInstance();
+
+  // -- Fields --
+
+  /** List of used files. */
+  private String[] usedFiles;
+
+  /** List of Image IDs. */
+  private Vector imageIDs;
+
+  /** List of Pixels IDs. */
+  private Vector pixelsIDs;
+
+  private Vector tempIfdMap, tempFileMap, tempIfdCount;
+  private int currentFile, currentSeries, seriesCount;
+  private int[] numIFDs;
+  private int[][] ifdMap, fileMap;
+  private boolean lsids, isWiscScan;
+  private Hashtable[][] usedIFDs;
+
+  // -- Constructor --
+
+  public OMETiffReader() {
+    super("OME-TIFF", new String[] {"tif", "tiff"});
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    OMETiffHandler handler = new OMETiffHandler();
+    String comment = (String) getMeta("Comment");
+
+    currentSeries = -1;
+    seriesCount = 0;
+    imageIDs = null;
+    pixelsIDs = null;
+    lsids = true;
+
+    tempIfdMap = new Vector();
+    tempFileMap = new Vector();
+    tempIfdCount = new Vector();
+
+    try {
+      SAXParser parser = SAX_FACTORY.newSAXParser();
+      parser.parse(new ByteArrayInputStream(comment.getBytes()), handler);
+    }
+    catch (ParserConfigurationException exc) {
+      throw new FormatException(exc);
+    }
+    catch (SAXException exc) {
+      throw new FormatException(exc);
+    }
+
+    // MAJOR HACK : check for OME-XML in the comment of the second IFD
+    // There is a version of WiscScan which writes OME-XML to every IFD,
+    // but with SizeZ and SizeT equal to 1.
+
+    String s = null;
+    if (ifds.length > 1) {
+      s = (String) TiffTools.getIFDValue(ifds[1], TiffTools.IMAGE_DESCRIPTION);
+    }
+    isWiscScan = s != null && s.indexOf("ome.xsd") != -1;
+
+    // look for additional files in the dataset
+    Vector files = new Vector();
+    Location l = new Location(currentId);
+    l = l.getAbsoluteFile().getParentFile();
+    String[] fileList = l.list();
+
+    if (!lsids) {
+      fileList = new String[] {currentId};
+      LogTools.println("Not searching for other files - " +
+        "Image LSID not present");
+    }
+
+    for (int i=0; i<fileList.length; i++) {
+      String check = fileList[i].toLowerCase();
+      if (check.endsWith(".tif") || check.endsWith(".tiff")) {
+        status("Checking " + fileList[i]);
+        String iid = l.getAbsolutePath() + File.separator + fileList[i];
+        String icomment = TiffTools.getComment(iid);
+        boolean addToList = true;
+        if (imageIDs != null) {
+          for (int k=0; k<imageIDs.size(); k++) {
+            if (icomment.indexOf((String) imageIDs.get(k)) == -1) {
+              addToList = false;
+              break;
+            }
+          }
+          if (addToList) {
+            for (int k=0; k<pixelsIDs.size(); k++) {
+              if (icomment.indexOf((String) pixelsIDs.get(k)) == -1) {
+                addToList = false;
+                break;
+              }
+            }
+          }
+        }
+        if (addToList) files.add(iid);
+      }
+    }
+
+    // parse grouped files
+
+    ifdMap = new int[seriesCount][];
+    fileMap = new int[seriesCount][];
+    numIFDs = new int[seriesCount];
+
+    for (int i=0; i<seriesCount; i++) {
+      int ii = ((Integer) tempIfdCount.get(i)).intValue();
+      ifdMap[i] = new int[ii];
+      fileMap[i] = new int[ii];
+    }
+
+    // copy temp IFD/file maps
+
+    for (int i=0; i<tempIfdMap.size(); i++) {
+      Vector v = (Vector) tempIfdMap.get(i);
+      for (int j=0; j<v.size(); j++) {
+        ifdMap[i][j] = ((Integer) v.get(j)).intValue();
+      }
+      numIFDs[i] = v.size();
+    }
+
+    for (int i=0; i<tempFileMap.size(); i++) {
+      Vector v = (Vector) tempFileMap.get(i);
+      for (int j=0; j<v.size(); j++) {
+        fileMap[i][j] = ((Integer) v.get(j)).intValue();
+      }
+    }
+
+    usedFiles = (String[]) files.toArray(new String[0]);
+    usedIFDs = new Hashtable[usedFiles.length][];
+
+    for (int i=0; i<usedFiles.length; i++) {
+      if (usedFiles[i].endsWith(currentId)) {
+        usedIFDs[i] = ifds;
+        continue;
+      }
+      status("Parsing " + usedFiles[i]);
+      currentSeries = -1;
+      tempIfdMap = null;
+      tempFileMap = null;
+      tempIfdCount = null;
+      currentFile = i;
+
+      usedIFDs[i] = TiffTools.getIFDs(new RandomAccessStream(usedFiles[i]));
+      String c = (String)
+        TiffTools.getIFDValue(usedIFDs[i][0], TiffTools.IMAGE_DESCRIPTION);
+      try {
+        SAXParser parser = SAX_FACTORY.newSAXParser();
+        parser.parse(new ByteArrayInputStream(c.getBytes()), handler);
+      }
+      catch (ParserConfigurationException exc) {
+        throw new FormatException(exc);
+      }
+      catch (SAXException exc) {
+        throw new FormatException(exc);
+      }
+    }
+
+    for (int i=0; i<getSeriesCount(); i++) {
+      if (numIFDs != null && lsids) {
+        if (numIFDs[i] < core.imageCount[i]) {
+          LogTools.println("Too few IFDs; got " + numIFDs[i] +
+            ", expected " + core.imageCount[i]);
+        }
+        else if (numIFDs[i] > core.imageCount[i]) {
+          LogTools.println("Too many IFDs; got " + numIFDs[i] +
+            ", expected " + core.imageCount[i]);
+        }
+      }
+      else if (core.imageCount[i] > ifds.length) {
+        core.imageCount[i] = ifds.length;
+        if (core.sizeZ[i] > ifds.length) {
+          core.sizeZ[i] = ifds.length / (core.rgb[i] ? core.sizeC[i] : 1);
+          core.sizeT[i] = 1;
+          if (!core.rgb[i]) core.sizeC[i] = 1;
+        }
+        else if (core.sizeT[i] > ifds.length) {
+          core.sizeT[i] = ifds.length / (core.rgb[i] ? core.sizeC[i] : 1);
+          core.sizeZ[i] = 1;
+          if (!core.rgb[i]) core.sizeC[i] = 1;
+        }
+      }
+    }
+  }
+
+  /* @see BaseTiffReader#initMetadataStore() */
+  protected void initMetadataStore() {
+    String comment = (String) getMeta("Comment");
+    metadata.remove("Comment");
+    MetadataStore store = getMetadataStore();
+    MetadataTools.convertMetadata(comment, store);
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    return usedFiles;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int ifd = ifdMap[series][no];
+    int fileIndex = fileMap[series][no];
+
+    in = new RandomAccessStream(usedFiles[fileIndex]);
+    TiffTools.getSamples(usedIFDs[fileIndex][ifd], in, buf);
+    in.close();
+    return swapIfRequired(buf);
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (!super.isThisType(name, open)) return false; // check extension
+    if (!open) return true; // not allowed to check the file contents
+
+    // just checking the filename isn't enough to differentiate between
+    // OME-TIFF and regular TIFF; open the file and check more thoroughly
+    try {
+      RandomAccessStream ras = new RandomAccessStream(name);
+      Hashtable ifd = TiffTools.getFirstIFD(ras);
+      ras.close();
+      if (ifd == null) return false;
+
+      String comment = (String)
+        ifd.get(new Integer(TiffTools.IMAGE_DESCRIPTION));
+      if (comment == null) return false;
+      return comment.indexOf("ome.xsd") >= 0;
+    }
+    catch (IOException e) { return false; }
+  }
+
+  // -- Helper class --
+
+  /** SAX handler for parsing XML. */
+  private class OMETiffHandler extends DefaultHandler {
+    private String order;
+    private int sizeZ, sizeC, sizeT;
+
+    public void startElement(String uri, String localName, String qName,
+      Attributes attributes)
+    {
+      if (qName.equals("Image")) {
+        String id = attributes.getValue("ID");
+        if (id.startsWith("urn:lsid:")) {
+          if (imageIDs == null) imageIDs = new Vector();
+          imageIDs.add(id);
+        }
+        else lsids = false;
+      }
+      else if (qName.equals("Pixels")) {
+        currentSeries++;
+        String id = attributes.getValue("ID");
+        if (id.startsWith("urn:lsid:")) {
+          if (pixelsIDs == null) pixelsIDs = new Vector();
+          pixelsIDs.add(id);
+        }
+        else lsids = false;
+
+        order = attributes.getValue("DimensionOrder");
+        sizeZ = Integer.parseInt(attributes.getValue("SizeZ"));
+        sizeC = Integer.parseInt(attributes.getValue("SizeC"));
+        sizeT = Integer.parseInt(attributes.getValue("SizeT"));
+        if (tempIfdCount != null) {
+          tempIfdCount.add(new Integer(sizeZ * sizeC * sizeT));
+        }
+
+        if (sizeZ < 1) sizeZ = 1;
+        if (sizeC < 1) sizeC = 1;
+        if (sizeT < 1) sizeT = 1;
+
+        if (core.sizeZ.length <= currentSeries) {
+          CoreMetadata tempCore = new CoreMetadata(currentSeries + 1);
+          int ss = core.sizeX.length;
+          System.arraycopy(core.sizeX, 0, tempCore.sizeX, 0, ss);
+          System.arraycopy(core.sizeY, 0, tempCore.sizeY, 0, ss);
+          System.arraycopy(core.sizeZ, 0, tempCore.sizeZ, 0, ss);
+          System.arraycopy(core.sizeC, 0, tempCore.sizeC, 0, ss);
+          System.arraycopy(core.sizeT, 0, tempCore.sizeT, 0, ss);
+          System.arraycopy(core.thumbSizeX, 0, tempCore.thumbSizeX, 0, ss);
+          System.arraycopy(core.thumbSizeY, 0, tempCore.thumbSizeY, 0, ss);
+          System.arraycopy(core.pixelType, 0, tempCore.pixelType, 0, ss);
+          System.arraycopy(core.imageCount, 0, tempCore.imageCount, 0, ss);
+          System.arraycopy(core.currentOrder, 0, tempCore.currentOrder, 0, ss);
+          System.arraycopy(core.orderCertain, 0, tempCore.orderCertain, 0, ss);
+          System.arraycopy(core.rgb, 0, tempCore.rgb, 0, ss);
+          System.arraycopy(core.littleEndian, 0, tempCore.littleEndian, 0, ss);
+          System.arraycopy(core.interleaved, 0, tempCore.interleaved, 0, ss);
+          System.arraycopy(core.indexed, 0, tempCore.indexed, 0, ss);
+          System.arraycopy(core.falseColor, 0, tempCore.falseColor, 0, ss);
+          System.arraycopy(core.metadataComplete, 0,
+            tempCore.metadataComplete, 0, ss);
+          core = tempCore;
+        }
+
+        core.sizeX[currentSeries] =
+          Integer.parseInt(attributes.getValue("SizeX"));
+        core.sizeY[currentSeries] =
+          Integer.parseInt(attributes.getValue("SizeY"));
+        core.sizeZ[currentSeries] = sizeZ;
+        core.sizeC[currentSeries] = sizeC;
+        core.sizeT[currentSeries] = sizeT;
+        core.currentOrder[currentSeries] = order;
+        core.rgb[currentSeries] = isRGB();
+        core.indexed[currentSeries] = isIndexed();
+        core.falseColor[currentSeries] = isFalseColor();
+
+        int sc = core.sizeC[currentSeries];
+        if (core.rgb[currentSeries] && !core.indexed[currentSeries]) sc /= 3;
+        if (core.indexed[currentSeries]) core.sizeC[currentSeries] *= 3;
+        core.imageCount[currentSeries] =
+          core.sizeZ[currentSeries] * sc * core.sizeT[currentSeries];
+        core.pixelType[currentSeries] =
+          FormatTools.pixelTypeFromString(attributes.getValue("PixelType"));
+        if (core.pixelType[currentSeries] == FormatTools.INT8 ||
+          core.pixelType[currentSeries] == FormatTools.INT16 ||
+          core.pixelType[currentSeries] == FormatTools.INT32)
+        {
+          core.pixelType[currentSeries]++;
+        }
+
+        if (isWiscScan) core.sizeT[currentSeries] = core.imageCount[0];
+
+        core.orderCertain[currentSeries] = true;
+
+        seriesCount++;
+      }
+      else if (qName.equals("TiffData")) {
+        String ifd = attributes.getValue("IFD");
+        String numPlanes = attributes.getValue("NumPlanes");
+        String z = attributes.getValue("FirstZ");
+        String c = attributes.getValue("FirstC");
+        String t = attributes.getValue("FirstT");
+        if (ifd == null || ifd.equals("")) ifd = "0";
+        if (numPlanes == null || numPlanes.equals("")) {
+          if (usedIFDs != null) numPlanes = "" + usedIFDs[currentSeries].length;
+          else numPlanes = "" + ifds.length;
+        }
+        if (z == null || z.equals("")) z = "0";
+        if (c == null || c.equals("")) c = "0";
+        if (t == null || t.equals("")) t = "0";
+
+        try {
+          if (usedIFDs != null && usedIFDs[currentFile] != null) {
+            int f = Integer.parseInt(ifd);
+            int x = (int) TiffTools.getImageWidth(usedIFDs[currentFile][f]);
+            int y = (int) TiffTools.getImageLength(usedIFDs[currentFile][f]);
+            if (x != core.sizeX[currentSeries]) {
+              LogTools.println("Mismatched width: got " +
+                core.sizeX[currentSeries] + ", expected " + x);
+              core.sizeX[currentSeries] = x;
+            }
+            if (y != core.sizeY[currentSeries]) {
+              LogTools.println("Mismatched height: got " +
+                core.sizeY[currentSeries] + ", expected " + y);
+              core.sizeY[currentSeries] = y;
+            }
+          }
+        }
+        catch (FormatException e) { }
+
+        int idx = FormatTools.getIndex(order, sizeZ, sizeC, sizeT,
+          sizeZ * sizeC * sizeT, Integer.parseInt(z), Integer.parseInt(c),
+          Integer.parseInt(t));
+
+        if (tempIfdMap != null) {
+          Vector v = new Vector(sizeZ * sizeC * sizeT);
+          Vector y = new Vector(sizeZ * sizeC * sizeT);
+          if (tempIfdMap.size() >= seriesCount && tempIfdMap.size() > 0) {
+            v = (Vector) tempIfdMap.get(seriesCount - 1);
+            y = (Vector) tempFileMap.get(seriesCount - 1);
+          }
+          else {
+            for (int i=0; i<sizeZ*sizeC*sizeT; i++) {
+              v.add(new Integer(-1));
+              y.add(new Integer(-1));
+            }
+          }
+
+          v.setElementAt(new Integer(ifd), idx);
+          y.setElementAt(new Integer(0), idx);
+
+          for (int i=1; i<Integer.parseInt(numPlanes); i++) {
+            v.setElementAt(new Integer(Integer.parseInt(ifd) + i), idx + i);
+            y.setElementAt(new Integer(0), idx + i);
+          }
+
+          if (tempIfdMap.size() >= seriesCount) {
+            tempIfdMap.setElementAt(v, seriesCount - 1);
+            tempFileMap.setElementAt(y, seriesCount - 1);
+          }
+          else {
+            tempIfdMap.add(v);
+            tempFileMap.add(y);
+          }
+        }
+        else {
+          ifdMap[currentSeries][idx] = Integer.parseInt(ifd);
+          fileMap[currentSeries][idx] = currentFile;
+          for (int i=1; i<Integer.parseInt(numPlanes); i++) {
+            ifdMap[currentSeries][idx + i] = ifdMap[currentSeries][idx] + i;
+            fileMap[currentSeries][idx + i] = currentFile;
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/OMEXMLReader.java b/loci/formats/in/OMEXMLReader.java
new file mode 100644
index 0000000..4800d43
--- /dev/null
+++ b/loci/formats/in/OMEXMLReader.java
@@ -0,0 +1,493 @@
+//
+// OMEXMLReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+import loci.formats.*;
+import loci.formats.codec.Base64Codec;
+import loci.formats.codec.CBZip2InputStream;
+
+/**
+ * OMEXMLReader is the file format reader for OME-XML files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OMEXMLReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OMEXMLReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class OMEXMLReader extends FormatReader {
+
+  // -- Constants --
+
+  private static final String NO_OME_JAVA_MSG =
+    "The Java OME-XML library is required to read OME-XML files. Please " +
+    "obtain ome-java.jar from http://loci.wisc.edu/ome/formats.html";
+
+  // -- Static fields --
+
+  private static boolean noOME = false;
+
+  static {
+    try {
+      Class.forName("org.openmicroscopy.xml.OMENode");
+    }
+    catch (Throwable t) {
+      noOME = true;
+      if (debug) LogTools.trace(t);
+    }
+  }
+
+  // -- Fields --
+
+  /** Number of bits per pixel. */
+  protected int[] bpp;
+
+  /** Offset to each plane's data. */
+  protected Vector[] offsets;
+
+  /** String indicating the compression type. */
+  protected String[] compression;
+
+  // -- Constructor --
+
+  /** Constructs a new OME-XML reader. */
+  public OMEXMLReader() { super("OME-XML", "ome"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return new String(block, 0, 5).equals("<?xml");
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(((Integer) offsets[series].get(no)).intValue());
+
+    byte[] b;
+    if (no < getImageCount() - 1) {
+      b = new byte[((Integer) offsets[series].get(no + 1)).intValue() -
+        ((Integer) offsets[series].get(no)).intValue()];
+    }
+    else {
+      b = new byte[(int) (in.length() -
+        ((Integer) offsets[series].get(no)).intValue())];
+    }
+    in.read(b);
+    String data = new String(b);
+    b = null;
+
+    // retrieve the compressed pixel data
+
+    int dataStart = data.indexOf(">") + 1;
+    String pix = data.substring(dataStart);
+    if (pix.indexOf("<") > 0) {
+      pix = pix.substring(0, pix.indexOf("<"));
+    }
+    data = null;
+
+    Base64Codec e = new Base64Codec();
+    byte[] pixels = e.base64Decode(pix);
+    pix = null;
+
+    if (compression[series].equals("bzip2")) {
+      byte[] tempPixels = pixels;
+      pixels = new byte[tempPixels.length - 2];
+      System.arraycopy(tempPixels, 2, pixels, 0, pixels.length);
+
+      ByteArrayInputStream bais = new ByteArrayInputStream(pixels);
+      CBZip2InputStream bzip = new CBZip2InputStream(bais);
+      pixels = new byte[core.sizeX[series] * core.sizeY[series] * bpp[series]];
+      for (int i=0; i<pixels.length; i++) {
+        pixels[i] = (byte) bzip.read();
+      }
+      tempPixels = null;
+      bais.close();
+      bais = null;
+      bzip = null;
+    }
+    else if (compression[series].equals("zlib")) {
+      try {
+        Inflater decompressor = new Inflater();
+        decompressor.setInput(pixels, 0, pixels.length);
+        pixels = new byte[core.sizeX[series]*core.sizeY[series]*bpp[series]];
+        decompressor.inflate(pixels);
+        decompressor.end();
+      }
+      catch (DataFormatException dfe) {
+        throw new FormatException("Error uncompressing zlib data.");
+      }
+    }
+    buf = pixels;
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("OMEXMLReader.initFile(" + id + ")");
+    if (noOME) throw new FormatException(NO_OME_JAVA_MSG);
+    super.initFile(id);
+
+    in = new RandomAccessStream(id);
+    ReflectedUniverse r = new ReflectedUniverse();
+    try {
+      r.exec("import loci.formats.ome.OMEXMLMetadata");
+      r.exec("import org.openmicroscopy.xml.OMENode");
+      r.exec("omexmlMeta = new OMEXMLMetadata()");
+    }
+    catch (ReflectException exc) {
+      throw new FormatException(exc);
+    }
+
+    r.setVar("ome", null);
+    try {
+      File f = new File(Location.getMappedId(id));
+      f = f.getAbsoluteFile();
+      String path = f.getPath().toLowerCase();
+      if (f.exists() && path.endsWith(".ome")) {
+        r.setVar("f", f);
+        r.exec("ome = new OMENode(f)");
+      }
+      else {
+        byte[] b = new byte[(int) in.length()];
+        long oldFp = in.getFilePointer();
+        in.seek(0);
+        in.read(b);
+        in.seek(oldFp);
+        r.setVar("s", new String(b));
+        r.exec("ome = new OMENode(s)");
+        b = null;
+      }
+    }
+    catch (ReflectException exc) {
+      throw new FormatException(exc);
+    }
+    try {
+      r.exec("omexmlMeta.setRoot(ome)");
+    }
+    catch (ReflectException exc) {
+      throw new FormatException(exc);
+    }
+
+    status("Determining endianness");
+
+    in.skipBytes(200);
+
+    int numDatasets = 0;
+    Vector endianness = new Vector();
+    Vector bigEndianPos = new Vector();
+
+    byte[] buf = new byte[1];
+
+    while (in.getFilePointer() < in.length()) {
+      // read a block of 8192 characters, looking for the "BigEndian" pattern
+      buf = new byte[8192];
+      boolean found = false;
+      while (!found) {
+        if (in.getFilePointer() < in.length()) {
+          int read = in.read(buf, 9, 8183);
+          String test = new String(buf);
+
+          int ndx = test.indexOf("BigEndian");
+          if (ndx != -1) {
+            found = true;
+            String endian = test.substring(ndx + 11).trim();
+            if (endian.startsWith("\"")) endian = endian.substring(1);
+            endianness.add(new Boolean(!endian.toLowerCase().startsWith("t")));
+            bigEndianPos.add(new Long(in.getFilePointer() - read - 9 + ndx));
+            numDatasets++;
+          }
+        }
+        else if (numDatasets == 0) {
+          throw new FormatException("Pixel data not found.");
+        }
+        else found = true;
+      }
+    }
+
+    offsets = new Vector[numDatasets];
+
+    for (int i=0; i<numDatasets; i++) {
+      offsets[i] = new Vector();
+    }
+
+    status("Finding image offsets");
+
+    // look for the first BinData element in each series
+
+    for (int i=0; i<numDatasets; i++) {
+      in.seek(((Long) bigEndianPos.get(i)).longValue());
+      boolean found = false;
+      buf = new byte[8192];
+      in.read(buf, 0, 14);
+
+      while (!found) {
+        if (in.getFilePointer() < in.length()) {
+          int numRead = in.read(buf, 14, 8192-14);
+
+          String test = new String(buf);
+
+          int ndx = test.indexOf("<Bin");
+          if (ndx == -1) {
+            byte[] b = buf;
+            System.arraycopy(b, 8192 - 15, buf, 0, 14);
+          }
+          else {
+            while (!((ndx != -1) && (ndx != test.indexOf("<Bin:External")) &&
+              (ndx != test.indexOf("<Bin:BinaryFile"))))
+            {
+              ndx = test.indexOf("<Bin", ndx+1);
+            }
+            found = true;
+            numRead += 14;
+            offsets[i].add(new Integer(
+              (int) in.getFilePointer() - (numRead - ndx)));
+          }
+          test = null;
+        }
+        else {
+          throw new FormatException("Pixel data not found");
+        }
+      }
+    }
+
+    in.seek(0);
+
+    for (int i=0; i<numDatasets; i++) {
+      if (i == 0) {
+        buf = new byte[((Integer) offsets[i].get(0)).intValue()];
+      }
+      else {
+        // look for the next Image element
+
+        boolean found = false;
+        buf = new byte[8192];
+        in.read(buf, 0, 14);
+        while (!found) {
+          if (in.getFilePointer() < in.length()) {
+            in.read(buf, 14, 8192-14);
+
+            String test = new String(buf);
+
+            int ndx = test.indexOf("<Image ");
+            if (ndx == -1) {
+              byte[] b = buf;
+              System.arraycopy(b, 8192 - 15, buf, 0, 14);
+              b = null;
+            }
+            else {
+              found = true;
+              in.seek(in.getFilePointer() - (8192 - ndx));
+            }
+            test = null;
+          }
+          else {
+            throw new FormatException("Pixel data not found");
+          }
+        }
+
+        int bufSize = (int) (((Long) offsets[i].get(0)).longValue() -
+          in.getFilePointer());
+        buf = new byte[bufSize];
+      }
+      in.read(buf);
+    }
+    buf = null;
+
+    status("Populating metadata");
+
+    core = new CoreMetadata(numDatasets);
+
+    bpp = new int[numDatasets];
+    compression = new String[numDatasets];
+
+    int oldSeries = getSeries();
+
+    try {
+      r.exec("omexmlMeta.setRoot(ome)");
+    }
+    catch (ReflectException exc) {
+      throw new FormatException(exc);
+    }
+    for (int i=0; i<numDatasets; i++) {
+      setSeries(i);
+
+      core.littleEndian[i] = ((Boolean) endianness.get(i)).booleanValue();
+
+      Integer w = null, h = null, t = null, z = null, c = null;
+      String pixType = null;
+      try {
+        r.setVar("ndx", i);
+        w = (Integer) r.exec("omexmlMeta.getSizeX(ndx)");
+        h = (Integer) r.exec("omexmlMeta.getSizeY(ndx)");
+        t = (Integer) r.exec("omexmlMeta.getSizeT(ndx)");
+        z = (Integer) r.exec("omexmlMeta.getSizeZ(ndx)");
+        c = (Integer) r.exec("omexmlMeta.getSizeC(ndx)");
+        pixType = (String) r.exec("omexmlMeta.getPixelType(ndx)");
+        core.currentOrder[i] =
+          (String) r.exec("omexmlMeta.getDimensionOrder(ndx)");
+      }
+      catch (ReflectException exc) {
+        throw new FormatException(exc);
+      }
+      core.sizeX[i] = w.intValue();
+      core.sizeY[i] = h.intValue();
+      core.sizeT[i] = t.intValue();
+      core.sizeZ[i] = z.intValue();
+      core.sizeC[i] = c.intValue();
+      core.rgb[i] = false;
+      core.interleaved[i] = false;
+      core.indexed[i] = false;
+      core.falseColor[i] = false;
+
+      String type = pixType.toLowerCase();
+      if (type.endsWith("16")) {
+        bpp[i] = 2;
+        core.pixelType[i] = FormatTools.UINT16;
+      }
+      else if (type.endsWith("32")) {
+        bpp[i] = 4;
+        core.pixelType[i] = FormatTools.UINT32;
+      }
+      else if (type.equals("float")) {
+        bpp[i] = 4;
+        core.pixelType[i] = FormatTools.FLOAT;
+      }
+      else {
+        bpp[i] = 1;
+        core.pixelType[i] = FormatTools.UINT8;
+      }
+
+      // calculate the number of raw bytes of pixel data that we are expecting
+      int expected = core.sizeX[i] * core.sizeY[i] * bpp[i];
+
+      // find the compression type and adjust 'expected' accordingly
+      in.seek(((Integer) offsets[i].get(0)).intValue());
+      buf = new byte[256];
+      in.read(buf);
+      String data = new String(buf);
+
+      int compressionStart = data.indexOf("Compression") + 13;
+      int compressionEnd = data.indexOf("\"", compressionStart);
+      if (compressionStart != -1 && compressionEnd != -1) {
+        compression[i] = data.substring(compressionStart, compressionEnd);
+      }
+      else compression[i] = "none";
+
+      expected /= 2;
+
+      in.seek(((Integer) offsets[i].get(0)).intValue());
+
+      int planes = core.sizeZ[i] * core.sizeC[i] * core.sizeT[i];
+
+      searchForData(expected, planes);
+      core.imageCount[i] = offsets[i].size();
+      if (core.imageCount[i] < planes) {
+        // hope this doesn't happen too often
+        in.seek(((Integer) offsets[i].get(0)).intValue());
+        searchForData(0, planes);
+        core.imageCount[i] = offsets[i].size();
+      }
+      buf = null;
+    }
+    setSeries(oldSeries);
+    Arrays.fill(core.orderCertain, true);
+
+    // populate assigned metadata store with the
+    // contents of the internal OME-XML metadata object
+    MetadataStore store = getMetadataStore();
+
+    MetadataRetrieve omexmlMeta = null;
+    try {
+      omexmlMeta = (MetadataRetrieve) r.getVar("omexmlMeta");
+    }
+    catch (ReflectException e) {
+      if (debug) LogTools.trace(e);
+    }
+
+    String xml = MetadataTools.getOMEXML(omexmlMeta);
+    MetadataTools.convertMetadata(xml, store);
+  }
+
+  // -- Helper methods --
+
+  /** Searches for BinData elements, skipping 'safe' bytes in between. */
+  private void searchForData(int safe, int numPlanes) throws IOException {
+    int iteration = 0;
+    boolean found = false;
+    if (offsets[series].size() > 1) {
+      Object zeroth = offsets[series].get(0);
+      offsets[series].clear();
+      offsets[series].add(zeroth);
+    }
+
+    in.skipBytes(1);
+    while (((in.getFilePointer() + safe) < in.length()) &&
+      (offsets[series].size() < numPlanes))
+    {
+      in.skipBytes(safe);
+
+      // look for next BinData element
+      found = false;
+      byte[] buf = new byte[8192];
+      while (!found) {
+        if (in.getFilePointer() < in.length()) {
+          int numRead = in.read(buf, 20, buf.length - 20);
+          String test = new String(buf);
+
+          // datasets with small planes could have multiple sets of pixel data
+          // in this block
+          int ndx = test.indexOf("<Bin");
+          while (ndx != -1) {
+            found = true;
+            if (numRead == buf.length - 20) numRead = buf.length;
+            offsets[series].add(new Integer(
+              (int) in.getFilePointer() - (numRead - ndx)));
+            ndx = test.indexOf("<Bin", ndx+1);
+          }
+          test = null;
+        }
+        else {
+          found = true;
+        }
+      }
+      buf = null;
+
+      iteration++;
+    }
+  }
+
+}
diff --git a/loci/formats/in/OpenlabRawReader.java b/loci/formats/in/OpenlabRawReader.java
new file mode 100644
index 0000000..672c03c
--- /dev/null
+++ b/loci/formats/in/OpenlabRawReader.java
@@ -0,0 +1,186 @@
+//
+// OpenlabRawReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import loci.formats.*;
+
+/**
+ * OpenlabRawReader is the file format reader for Openlab RAW files.
+ * Specifications available at
+ * http://www.improvision.com/support/tech_notes/detail.php?id=344
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OpenlabRawReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OpenlabRawReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class OpenlabRawReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Offset to each image's pixel data. */
+  protected int[] offsets;
+
+  /** Number of bytes per pixel. */
+  private int bytesPerPixel;
+
+  // -- Constructor --
+
+  /** Constructs a new RAW reader. */
+  public OpenlabRawReader() { super("Openlab RAW", "raw"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return (block[0] == 'O') && (block[1] == 'L') && (block[2] == 'R') &&
+      (block[3] == 'W');
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(offsets[no / core.sizeC[0]] + 288);
+    in.read(buf);
+
+    if (bytesPerPixel == 1) {
+      // need to invert the pixels
+      for (int i=0; i<buf.length; i++) {
+        buf[i] = (byte) (255 - buf[i]);
+      }
+    }
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("OpenlabRawReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    // read the 12 byte file header
+
+    status("Verifying Openlab RAW format");
+
+    byte[] header = new byte[4];
+    in.read(header);
+    String check = new String(header);
+    if (!check.equals("OLRW")) {
+      throw new FormatException("Openlab RAW magic string not found.");
+    }
+
+    status("Populating metadata");
+
+    int version = in.readInt();
+    addMeta("Version", new Integer(version));
+
+    core.imageCount[0] = in.readInt();
+    offsets = new int[core.imageCount[0]];
+    offsets[0] = 12;
+
+    in.readLong();
+    core.sizeX[0] = in.readInt();
+    core.sizeY[0] = in.readInt();
+    in.read();
+    core.sizeC[0] = in.read();
+    bytesPerPixel = in.read();
+    in.read();
+
+    long stamp = in.readLong();
+    Date timestamp = null;
+    SimpleDateFormat sdf = null;
+    if (stamp > 0) {
+      stamp /= 1000000;
+      stamp -= (67 * 365.25 * 24 * 60 * 60);
+
+      timestamp = new Date(stamp);
+      sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+      addMeta("Timestamp", sdf.format(timestamp));
+    }
+    in.skipBytes(4);
+    byte[] s = new byte[256];
+    in.read(s);
+    int len = s[0] > 0 ? s[0] : (s[0] + 256);
+    addMeta("Image name", new String(s, 1, len).trim());
+
+    if (core.sizeC[0] <= 1) core.sizeC[0] = 1;
+    else core.sizeC[0] = 3;
+    addMeta("Width", new Integer(core.sizeX[0]));
+    addMeta("Height", new Integer(core.sizeY[0]));
+    addMeta("Bytes per pixel", new Integer(bytesPerPixel));
+
+    int plane = core.sizeX[0] * core.sizeY[0] * bytesPerPixel;
+    for (int i=1; i<core.imageCount[0]; i++) {
+      offsets[i] = offsets[i - 1] + 288 + plane;
+    }
+
+    core.sizeZ[0] = core.imageCount[0];
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYZTC";
+    core.rgb[0] = core.sizeC[0] > 1;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = false;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    switch (bytesPerPixel) {
+      case 1:
+      case 3:
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 2:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      default:
+        core.pixelType[0] = FormatTools.FLOAT;
+    }
+
+    store.setImage((String) getMeta("Image name"),
+      timestamp == null ? null : sdf.format(timestamp), null, null);
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/OpenlabReader.java b/loci/formats/in/OpenlabReader.java
new file mode 100644
index 0000000..5a0e9cf
--- /dev/null
+++ b/loci/formats/in/OpenlabReader.java
@@ -0,0 +1,731 @@
+//
+// OpenlabReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.Arrays;
+import java.util.Vector;
+import loci.formats.*;
+import loci.formats.codec.LZOCodec;
+
+/**
+ * OpenlabReader is the file format reader for Openlab LIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OpenlabReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OpenlabReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ * @author Eric Kjellman egkjellman at wisc.edu
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class OpenlabReader extends FormatReader {
+
+  // -- Constants --
+
+  /** Image types. */
+  private static final int MAC_1_BIT = 1;
+  private static final int MAC_256_GREYS = 5;
+  private static final int MAC_256_COLORS = 6;
+  private static final int MAC_24_BIT = 8;
+  private static final int GREY_16_BIT = 16;
+
+  // -- Static fields --
+
+  /** Helper reader to read PICT data. */
+  private static PictReader pict = new PictReader();
+
+  // -- Fields --
+
+  /** LIFF version (should be 2 or 5). */
+  private int version;
+
+  /** Number of series. */
+  private int numSeries;
+
+  private Vector[] layerInfoList;
+  private float xCal, yCal, zCal;
+  private int bytesPerPixel;
+
+  private int tag = 0, subTag = 0;
+  private String fmt = "";
+
+  // -- Constructor --
+
+  /** Constructs a new OpenlabReader. */
+  public OpenlabReader() { super("Openlab LIFF", "liff"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return block.length >= 8 && block[0] == 0 && block[1] == 0 &&
+      block[2] == -1 && block[3] == -1 && block[4] == 105 &&
+      block[5] == 109 && block[6] == 112 && block[7] == 114;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    buf = openBytes(no);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int) */
+  public byte[] openBytes(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    LayerInfo info = (LayerInfo) layerInfoList[series].get(no);
+    in.seek(info.layerStart);
+
+    readTagHeader();
+
+    if ((tag != 67 && tag != 68) ||
+      (!fmt.equals("PICT") && !fmt.equals("RAWi")))
+    {
+      throw new FormatException("Corrupt LIFF file.");
+    }
+
+    in.skipBytes(24);
+    int volumeType = in.readShort();
+    in.skipBytes(272);
+
+    int top, left, bottom, right;
+
+    if (version == 2) {
+      in.skipBytes(2);
+      top = in.readShort();
+      left = in.readShort();
+      bottom = in.readShort();
+      right = in.readShort();
+
+      if (core.sizeX[series] == 0) core.sizeX[series] = right - left;
+      if (core.sizeY[series] == 0) core.sizeY[series] = bottom - top;
+    }
+    else {
+      core.sizeX[series] = in.readInt();
+      core.sizeY[series] = in.readInt();
+    }
+
+    in.seek(info.layerStart);
+
+    byte[] b = new byte[0];
+
+    if (version == 2) {
+      long nextTag = readTagHeader();
+
+      if ((tag != 67 && tag != 68) || !fmt.equals("PICT")) {
+        throw new FormatException("Corrupt LIFF file.");
+      }
+      in.skipBytes(298);
+
+      // open image using pict reader
+      Exception exception = null;
+      try {
+        b = new byte[(int) (nextTag - in.getFilePointer())];
+        in.read(b);
+        BufferedImage img = pict.open(b);
+        byte[][] tmp = ImageTools.getBytes(img);
+        b = new byte[tmp.length * tmp[0].length];
+        int pt = 0;
+        for (int i=0; i<tmp[0].length; i++) {
+          for (int j=0; j<tmp.length; j++) {
+            b[pt++] = tmp[j][i];
+          }
+        }
+      }
+      catch (FormatException exc) { exception = exc; }
+      catch (IOException exc) { exception = exc; }
+      if (exception != null) {
+        if (debug) trace(exception);
+
+        b = null;
+        in.seek(info.layerStart + 12);
+        int blockSize = DataTools.read4SignedBytes(in, false);
+        byte toRead = (byte) in.read();
+
+        // right now I'm gonna skip all the header info
+        // check to see whether or not this is v2 data
+        if (toRead == 1) in.skipBytes(128);
+        in.skipBytes(169);
+        // read in the block of data
+        byte[] q = new byte[blockSize];
+        in.read(q);
+        byte[] pixelData = new byte[blockSize];
+        int pixPos = 0;
+
+        int length = q.length;
+        int num, size;
+        int totalBlocks = -1; // set to allow loop to start.
+        int expectedBlock = 0;
+        int pos = 0;
+
+        while (expectedBlock != totalBlocks) {
+
+          while (pos + 7 < length &&
+            (q[pos] != 73 || q[pos + 1] != 86 || q[pos + 2] != 69 ||
+            q[pos + 3] != 65 || q[pos + 4] != 100 || q[pos + 5] != 98 ||
+            q[pos + 6] != 112 || q[pos + 7] != 113))
+          {
+            pos++;
+          }
+
+          pos += 8; // skip the block type we just found
+
+          // Read info from the iPic comment. This serves as a
+          // starting point to read the rest.
+          num = DataTools.bytesToInt(q, pos, 4, false);
+          if (num != expectedBlock) {
+            throw new FormatException("Expected iPic block not found");
+          }
+          expectedBlock++;
+          if (totalBlocks == -1) {
+            totalBlocks = DataTools.bytesToInt(q, pos + 4, 4, false);
+          }
+          else {
+            if (DataTools.bytesToInt(q, pos + 4, 4, false) != totalBlocks) {
+              throw new FormatException("Unexpected totalBlocks numbein.read");
+            }
+          }
+
+          // skip to size
+          pos += 16;
+
+          size = DataTools.bytesToInt(q, pos, 4, false);
+          pos += 8;
+
+          // copy into our data array.
+          System.arraycopy(q, pos, pixelData, pixPos, size);
+          pixPos += size;
+        }
+        System.gc();
+        b = new byte[pixPos];
+        System.arraycopy(pixelData, 0, b, 0, b.length);
+      }
+    }
+    else {
+      readTagHeader();
+
+      if (tag != 68 || !fmt.equals("RAWi")) {
+        throw new FormatException("Corrupt LIFF file.");
+      }
+
+      if (subTag != 0) {
+        throw new FormatException("Wrong compression type.");
+      }
+
+      in.skipBytes(24);
+      volumeType = in.readShort();
+
+      in.skipBytes(280);
+      int size = in.readInt();
+      int compressedSize = in.readInt();
+      b = new byte[size];
+      byte[] c = new byte[compressedSize];
+      in.read(c);
+
+      LZOCodec lzoc = new LZOCodec();
+      b = lzoc.decompress(c);
+      if (b.length != size) {
+        LogTools.println("LZOCodec failed to predict image size");
+        LogTools.println(size + " expected, got " + b.length +
+          ". The image displayed may not be correct.");
+      }
+
+      if (volumeType == MAC_24_BIT) {
+        bytesPerPixel =
+          b.length >= core.sizeX[series] * core.sizeY[series] * 4 ? 4 : 3;
+
+        int destRowBytes = core.sizeX[series] * bytesPerPixel;
+        int srcRowBytes = b.length / core.sizeY[series];
+
+        byte[] tmp = new byte[destRowBytes * core.sizeY[series]];
+        int src = 0;
+        int dest = 0;
+        for (int y=0; y<core.sizeY[series]; y++) {
+          System.arraycopy(b, src, tmp, dest, destRowBytes);
+          src += srcRowBytes;
+          dest += destRowBytes;
+        }
+
+        // strip out alpha channel and force channel separation
+
+        if (bytesPerPixel == 4) {
+          b = new byte[(3 * tmp.length) / 4];
+          dest = 0;
+          for (int i=0; i<tmp.length; i+=4) {
+            b[dest] = tmp[i + 1];
+            b[dest + (b.length / 3)] = tmp[i + 2];
+            b[dest + ((2 * b.length) / 3)] = tmp[i + 3];
+            dest++;
+          }
+          bytesPerPixel = 3;
+        }
+      }
+      else if (volumeType == MAC_256_GREYS) {
+        byte[] tmp = b;
+        b = new byte[core.sizeX[series] * core.sizeY[series]];
+        for (int y=0; y<core.sizeY[series]; y++) {
+          System.arraycopy(tmp, y*(core.sizeX[series] + 16), b,
+            y*core.sizeX[series], core.sizeX[series]);
+        }
+      }
+      else if (volumeType < MAC_24_BIT) {
+        throw new FormatException("Unsupported image type : " + volumeType);
+      }
+    }
+
+    int bpp = b.length / (core.sizeX[series] * core.sizeY[series]);
+    int expected = core.sizeX[series] * core.sizeY[series] * bpp;
+    if (b.length > expected) {
+      byte[] tmp = b;
+      b = new byte[expected];
+      System.arraycopy(tmp, 0, b, 0, b.length);
+    }
+    return b;
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly) {
+      if (in != null) in.close();
+      if (pict != null) pict.close(fileOnly);
+    }
+    else close();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (super.isThisType(name, open)) return true; // check extension
+
+    if (open) {
+      byte[] b = new byte[8];
+      try {
+        in = new RandomAccessStream(name);
+        in.read(b);
+      }
+      catch (IOException exc) {
+        if (debug) trace(exc);
+        return false;
+      }
+      return isThisType(b);
+    }
+    else { // not allowed to check the file contents
+      return name.indexOf(".") < 0; // file appears to have no extension
+    }
+  }
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    if (pict != null) pict.close();
+    layerInfoList = null;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("OpenlabReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    status("Verifying Openlab LIFF format");
+
+    in.order(false);
+    in.skipBytes(4);
+    if (!in.readString(4).equals("impr")) {
+      throw new FormatException("Invalid LIFF file.");
+    }
+
+    version = in.readInt();
+
+    if (version != 2 && version != 5) {
+      throw new FormatException("Invalid version : " + version);
+    }
+
+    // skip the layer count and ID seed
+    in.skipBytes(4);
+
+    // read offset to first plane
+    int offset = in.readInt();
+    in.seek(offset);
+
+    status("Finding image offsets");
+
+    layerInfoList = new Vector[2];
+    for (int i=0; i<layerInfoList.length; i++) layerInfoList[i] = new Vector();
+    xCal = yCal = zCal = (float) 0.0;
+
+    // scan through the file, and read image information
+
+    while (in.getFilePointer() < in.length()) {
+      long nextTag, startPos;
+
+      subTag = tag = 0;
+      try {
+        startPos = in.getFilePointer();
+        nextTag = readTagHeader();
+      }
+      catch (IOException exc) {
+        if (debug) trace(exc);
+
+        if (in.getFilePointer() >= in.length()) break;
+        else throw new FormatException(exc.getMessage());
+      }
+
+      try {
+        if (tag == 67 || tag == 68 ||
+          fmt.equals("PICT") || fmt.equals("RAWi"))
+        {
+          LayerInfo info = new LayerInfo();
+          info.layerStart = (int) startPos;
+          info.zPosition = -1;
+          info.wavelength = -1;
+
+          in.skipBytes(24);
+          int volumeType = in.readShort();
+
+          if (volumeType == MAC_1_BIT || volumeType == MAC_256_GREYS ||
+            volumeType == MAC_256_COLORS ||
+            (volumeType >= MAC_24_BIT && volumeType <= GREY_16_BIT))
+          {
+            in.skipBytes(16);
+            info.layerName = in.readString(128);
+
+            if (!info.layerName.trim().equals("Original Image")) {
+              info.timestamp = in.readLong();
+              layerInfoList[0].add(info);
+            }
+          }
+
+        }
+        else if (tag == 69) {
+          in.skipBytes(18);
+
+          xCal = in.readFloat();
+          yCal = in.readFloat();
+        }
+        else if (tag == 72 || fmt.equals("USER")) {
+          char aChar = (char) in.read();
+          StringBuffer sb = new StringBuffer();
+          while (aChar != 0) {
+            sb = sb.append(aChar);
+            aChar = (char) in.read();
+          }
+
+          String className = sb.toString();
+
+          if (className.equals("CVariableList")) {
+            aChar = (char) in.read();
+
+            if (aChar == 1) {
+              int numVars = in.readShort();
+              while (numVars > 0) {
+                aChar = (char) in.read();
+                sb = new StringBuffer();
+                while (aChar != 0) {
+                  sb = sb.append(aChar);
+                  aChar = (char) in.read();
+                }
+                //in.read();
+
+                String varName = "";
+                String varStringValue = "";
+                double varNumValue = 0.0;
+
+                className = sb.toString();
+
+                int derivedClassVersion = in.read();
+                if (derivedClassVersion != 1) {
+                  throw new FormatException("Invalid revision.");
+                }
+
+                if (className.equals("CStringVariable")) {
+                  int strSize = in.readInt();
+                  varStringValue = in.readString(strSize);
+                  varNumValue = Float.parseFloat(varStringValue);
+
+                  in.skipBytes(1);
+                }
+                else if (className.equals("CFloatVariable")) {
+                  varNumValue = in.readDouble();
+                  varStringValue = "" + varNumValue;
+                }
+
+                int baseClassVersion = in.read();
+                if (baseClassVersion == 1 || baseClassVersion == 2) {
+                  int strSize = in.readInt();
+                  varName = in.readString(strSize);
+                  in.skipBytes(baseClassVersion == 1 ? 3 : 2);
+                }
+                else {
+                  throw new FormatException("Invalid revision.");
+                }
+
+                addMeta(varName, varStringValue);
+                numVars--;
+              }
+            }
+          }
+        }
+
+        in.seek(nextTag);
+      }
+      catch (Exception exc) {
+        // CTR TODO - eliminate catch-all exception handling
+        if (debug) trace(exc);
+        in.seek(nextTag);
+      }
+    }
+
+    Vector tmp = new Vector();
+    for (int i=0; i<layerInfoList[0].size(); i++) {
+      tmp.add(layerInfoList[0].get(i));
+    }
+
+    core = new CoreMetadata(2);
+
+    core.imageCount[0] = tmp.size();
+
+    // determine if we have a multi-series file
+
+    status("Determining series count");
+
+    int oldChannels = openBytes(0).length / (core.sizeX[0] * core.sizeY[0] * 3);
+    int oldWidth = core.sizeX[0];
+
+    for (int i=0; i<tmp.size(); i++) {
+      LayerInfo layer = (LayerInfo) tmp.get(i);
+      in.seek(layer.layerStart);
+
+      long nextTag = readTagHeader();
+      if (fmt.equals("PICT")) {
+        in.skipBytes(298);
+
+        int top, left, bottom, right;
+
+        if (version == 2) {
+          in.skipBytes(2);
+          top = in.readShort();
+          left = in.readShort();
+          bottom = in.readShort();
+          right = in.readShort();
+
+          if (core.sizeX[series] == 0) core.sizeX[series] = right - left;
+          if (core.sizeY[series] == 0) core.sizeY[series] = bottom - top;
+        }
+        else {
+          core.sizeX[series] = in.readInt();
+          core.sizeY[series] = in.readInt();
+        }
+
+        in.seek(layer.layerStart);
+
+        if (version == 2) {
+          nextTag = readTagHeader();
+
+          if ((tag != 67 && tag != 68) || !fmt.equals("PICT")) {
+            throw new FormatException("Corrupt LIFF file.");
+          }
+          in.skipBytes(298);
+
+          // open image using pict reader
+          try {
+            byte[] b = new byte[(int) (nextTag - in.getFilePointer())];
+            in.read(b);
+            BufferedImage img = pict.open(b);
+            if (img.getRaster().getNumBands() != oldChannels ||
+              img.getWidth() != oldWidth)
+            {
+              layerInfoList[1].add(tmp.get(i));
+              layerInfoList[0].remove(tmp.get(i));
+            }
+          }
+          catch (FormatException e) {
+          }
+        }
+      }
+      else {
+        in.skipBytes(24);
+        int type = DataTools.read2SignedBytes(in, false);
+        if (type == MAC_24_BIT) {
+          layerInfoList[1].add(tmp.get(i));
+          layerInfoList[0].remove(tmp.get(i));
+        }
+      }
+    }
+
+    if (layerInfoList[1].size() == 0 || layerInfoList[0].size() == 0) {
+      core.sizeC = new int[1];
+      core.sizeC[0] = layerInfoList[1].size() == 0 ? 1 : 3;
+      if (core.sizeC[0] == 1 && oldChannels == 1) core.sizeC[0] = 3;
+
+      int oldImages = core.imageCount[0];
+      core.imageCount = new int[1];
+      core.imageCount[0] = oldImages;
+      if (layerInfoList[0].size() == 0) layerInfoList[0] = layerInfoList[1];
+
+      int x = core.sizeX[0];
+      core.sizeX = new int[1];
+      core.sizeX[0] = x;
+    }
+    else {
+      core.imageCount[0] = layerInfoList[0].size();
+      core.imageCount[1] = layerInfoList[1].size();
+      core.sizeC[0] = 1;
+      core.sizeC[1] = 3;
+      int oldW = core.sizeX[0];
+      int oldH = core.sizeY[0];
+      core.sizeX = new int[2];
+      core.sizeY = new int[2];
+      core.sizeX[0] = oldW;
+      core.sizeX[1] = oldW;
+      core.sizeY[0] = oldH;
+      core.sizeY[1] = oldH;
+    }
+
+    Arrays.fill(core.metadataComplete, true);
+
+    status("Populating metadata");
+
+    numSeries = core.imageCount.length;
+
+    int[] bpp = new int[numSeries];
+
+    Arrays.fill(core.orderCertain, true);
+
+    int oldSeries = getSeries();
+    for (int i=0; i<bpp.length; i++) {
+      setSeries(i);
+      if (core.sizeC[i] == 0) core.sizeC[i] = 1;
+      bpp[i] = openBytes(0).length / (core.sizeX[i] * core.sizeY[i]);
+    }
+    setSeries(oldSeries);
+
+    if (bytesPerPixel == 3) bytesPerPixel = 1;
+    if (bytesPerPixel == 0) bytesPerPixel++;
+
+    // finish populating metadata hashtable
+    addMeta("Version", new Integer(version));
+    addMeta("Number of Series", new Integer(numSeries));
+    for (int i=0; i<numSeries; i++) {
+      addMeta("Width (Series " + i + ")", new Integer(core.sizeX[i]));
+      addMeta("Height (Series " + i + ")", new Integer(core.sizeY[i]));
+      addMeta("Bit depth (Series " + i + ")",
+        new Integer(bpp[i] * 8));
+      addMeta("Number of channels (Series " + i + ")",
+        new Integer(core.sizeC[i]));
+      addMeta("Number of images (Series " + i + ")",
+        new Integer(core.imageCount[i]));
+    }
+
+    // populate MetadataStore
+
+    MetadataStore store = getMetadataStore();
+
+    for (int i=0; i<numSeries; i++) {
+      core.sizeT[i] += 1;
+      core.sizeZ[i] = core.imageCount[i] / core.sizeT[i];
+      core.currentOrder[i] = isRGB() ? "XYCZT" : "XYZCT";
+      core.rgb[i] = core.sizeC[i] > 1;
+      core.interleaved[i] = true;
+      core.littleEndian[i] = false;
+
+      try {
+        if (i != 0) {
+          if (bpp[i] == bpp[0]) bpp[i] = bpp[i + 1];
+        }
+      }
+      catch (ArrayIndexOutOfBoundsException a) { }
+
+      switch (bpp[i]) {
+        case 1:
+        case 3:
+          core.pixelType[i] = FormatTools.UINT8;
+          break;
+        case 2:
+        case 6:
+          core.pixelType[i] = FormatTools.UINT16;
+          break;
+        case 4:
+          core.pixelType[i] = FormatTools.UINT32;
+          break;
+      }
+
+      store.setImage("Series " + i, null, null, new Integer(i));
+      store.setDimensions(new Float(xCal), new Float(yCal), new Float(zCal),
+        null, null, new Integer(i));
+    }
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<numSeries; i++) {
+      for (int j=0; j<core.sizeC[i]; j++) {
+        store.setLogicalChannel(j, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, new Integer(i));
+      }
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Read the next tag. */
+  private long readTagHeader()
+    throws IOException
+  {
+    tag = in.readShort();
+    subTag = in.readShort();
+
+    long nextTag = (version == 2 ? in.readInt() : in.readLong());
+
+    byte[] b = new byte[4];
+    in.read(b);
+    fmt = new String(b);
+    if (version == 2) in.skipBytes(4);
+    else in.skipBytes(8);
+    return nextTag;
+  }
+
+  // -- Helper classes --
+
+  /** Helper class for storing layer info. */
+  protected class LayerInfo {
+    protected int layerStart;
+    protected int zPosition;
+    protected int wavelength;
+    protected String layerName;
+    protected long timestamp;
+  }
+
+}
diff --git a/loci/formats/in/PCIReader.java b/loci/formats/in/PCIReader.java
new file mode 100644
index 0000000..41a767a
--- /dev/null
+++ b/loci/formats/in/PCIReader.java
@@ -0,0 +1,291 @@
+//
+// PCIReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * PCIReader is the file format reader for SimplePCI (Compix) .cxd files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/PCIReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/PCIReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class PCIReader extends FormatReader {
+
+  // -- Constants --
+
+  private static final String NO_POI_MSG =
+    "Jakarta POI is required to read SimplePCI files. Please " +
+    "obtain poi-loci.jar from http://loci.wisc.edu/ome/formats.html";
+
+  // -- Static fields --
+
+  private static boolean noPOI = false;
+  private static ReflectedUniverse r = createReflectedUniverse();
+
+  private static ReflectedUniverse createReflectedUniverse() {
+    r = null;
+    try {
+      r = new ReflectedUniverse();
+      r.exec("import org.apache.poi.poifs.filesystem.POIFSFileSystem");
+      r.exec("import org.apache.poi.poifs.filesystem.DirectoryEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentInputStream");
+      r.exec("import java.util.Iterator");
+    }
+    catch (ReflectException exc) {
+      noPOI = true;
+      if (debug) LogTools.trace(exc);
+    }
+    return r;
+  }
+
+  // -- Fields --
+
+  private Hashtable imageDirectories;
+  private Hashtable imageFiles;
+  private String currentParent;
+
+  // -- Constructor --
+
+  /** Constructs a new SimplePCI reader. */
+  public PCIReader() { super("Simple-PCI (Compix)", "cxd"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return (block[0] == 0xd0 && block[1] == 0xcf && block[2] == 0x11 &&
+      block[3] == 0xe0);
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    if (noPOI) throw new FormatException(NO_POI_MSG);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    Integer ii = new Integer(no);
+    Object directory = imageDirectories.get(ii);
+    String name = (String) imageFiles.get(ii);
+
+    try {
+      r.setVar("dir", directory);
+      r.setVar("entryName", name);
+      r.exec("document = dir.getEntry(entryName)");
+      r.exec("dis = new DocumentInputStream(document)");
+      r.exec("numBytes = dis.available()");
+      r.setVar("data", buf);
+      r.exec("dis.read(data)");
+    }
+    catch (ReflectException e) {
+      throw new FormatException(NO_POI_MSG, e);
+    }
+
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("PCIReader.initFile(" + id + ")");
+    if (noPOI) throw new FormatException(NO_POI_MSG);
+
+    super.initFile(id);
+
+    imageDirectories = new Hashtable();
+    imageFiles = new Hashtable();
+
+    try {
+      in = new RandomAccessStream(id);
+
+      r.setVar("fis", in);
+      r.exec("fs = new POIFSFileSystem(fis)");
+      r.exec("dir = fs.getRoot()");
+      parseDir(0, r.getVar("dir"));
+
+      core.sizeZ[0] = core.imageCount[0];
+      core.sizeT[0] = 1;
+      core.rgb[0] = core.sizeC[0] > 1;
+      core.interleaved[0] = false;
+      core.currentOrder[0] = "XYCZT";
+      core.littleEndian[0] = true;
+      core.indexed[0] = false;
+      core.falseColor[0] = false;
+      core.metadataComplete[0] = true;
+
+      MetadataStore store = getMetadataStore();
+      FormatTools.populatePixels(store, this);
+      store.setImage(currentId, null, null, null);
+      for (int i=0; i<core.sizeC[0]; i++) {
+        store.setLogicalChannel(i, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, null);
+      }
+    }
+    catch (ReflectException exc) {
+      noPOI = true;
+    }
+  }
+
+  // -- Helper methods --
+
+  private void parseDir(int depth, Object dir)
+    throws IOException, FormatException, ReflectException
+  {
+    r.setVar("dir", dir);
+    r.exec("dirName = dir.getName()");
+    r.setVar("depth", depth);
+    r.exec("iter = dir.getEntries()");
+    Iterator iter = (Iterator) r.getVar("iter");
+    while (iter.hasNext()) {
+      r.setVar("entry", iter.next());
+      r.exec("isInstance = entry.isDirectoryEntry()");
+      r.exec("isDocument = entry.isDocumentEntry()");
+      boolean isInstance = ((Boolean) r.getVar("isInstance")).booleanValue();
+      boolean isDocument = ((Boolean) r.getVar("isDocument")).booleanValue();
+      r.setVar("dir", dir);
+      r.exec("dirName = dir.getName()");
+
+      if (isInstance) {
+        if (debug) print(depth, (String) r.getVar("dirName"));
+        if (((String) r.getVar("dirName")).trim().startsWith("Field")) {
+          currentParent = ((String) r.getVar("dirName")).trim();
+        }
+        parseDir(depth + 1, r.getVar("entry"));
+      }
+      else if (isDocument) {
+        r.exec("entryName = entry.getName()");
+        if (debug) print(depth + 1, (String) r.getVar("entryName"));
+        r.exec("dis = new DocumentInputStream(entry)");
+        r.exec("numBytes = dis.available()");
+        int numBytes = ((Integer) r.getVar("numBytes")).intValue();
+        byte[] data = new byte[numBytes];
+        r.setVar("data", data);
+        r.exec("dis.read(data)");
+
+        String entryName = (String) r.getVar("entryName");
+        String dirName = (String) r.getVar("dirName");
+        RandomAccessStream s = new RandomAccessStream(data);
+        s.order(true);
+
+        if (entryName.equals("Field Count")) {
+          core.imageCount[0] = s.readInt();
+        }
+        else if (entryName.equals("File Has Image")) {
+          if (s.readShort() == 0) {
+            throw new FormatException("This file does not contain image data.");
+          }
+        }
+        else if (entryName.equals("Comments")) {
+          String comments = new String(data).trim();
+          StringTokenizer st = new StringTokenizer(comments, "\n");
+          while (st.hasMoreTokens()) {
+            String token = st.nextToken().trim();
+            if (token.indexOf("=") != -1) {
+              int idx = token.indexOf("=");
+              String key = token.substring(0, idx).trim();
+              String value = token.substring(idx + 1).trim();
+              addMeta(key, value);
+            }
+          }
+        }
+        else if (entryName.startsWith("Bitmap")) {
+          int space = currentParent.indexOf(" ") + 1;
+          int num = Integer.parseInt(currentParent.substring(space)) - 1;
+          Integer ii = new Integer(num);
+          imageDirectories.put(ii, r.getVar("dir"));
+          imageFiles.put(ii, entryName);
+
+          if (core.sizeX[0] != 0 && core.sizeY[0] != 0) {
+            core.sizeC[0] = data.length / (core.sizeX[0] * core.sizeY[0] *
+              FormatTools.getBytesPerPixel(core.pixelType[0]));
+          }
+        }
+        else if (entryName.indexOf("Image_Depth") != -1) {
+          s.order(false);
+          int bits = (int) (s.readLong() & 0x1f00) >> 8;
+          while (bits % 8 != 0) bits++;
+          switch (bits) {
+            case 8:
+              core.pixelType[0] = FormatTools.UINT8;
+              break;
+            case 16:
+              core.pixelType[0] = FormatTools.UINT16;
+              break;
+            case 32:
+              core.pixelType[0] = FormatTools.UINT32;
+              break;
+            default:
+              throw new FormatException("Unsupported bits per pixel : " + bits);
+          }
+          s.order(true);
+        }
+        else if (entryName.indexOf("Image_Height") != -1) {
+          s.order(false);
+          core.sizeY[0] = (int) ((s.readLong() & 0x1f00) >> 8) * 64;
+          s.order(true);
+        }
+        else if (entryName.indexOf("Image_Width") != -1) {
+          s.order(false);
+          core.sizeX[0] = (int) ((s.readLong() & 0x1f00) >> 8) * 64;
+          s.order(true);
+        }
+        else if (entryName.indexOf("Time_From_Start") != -1) {
+        }
+        else if (entryName.indexOf("Event_Marker") != -1) {
+
+        }
+        else if (entryName.indexOf("Time_From_Last") != -1) {
+
+        }
+        else {
+
+        }
+
+        s.close();
+        data = null;
+        r.exec("dis.close()");
+      }
+    }
+  }
+
+  private void print(int depth, String s) {
+    StringBuffer sb = new StringBuffer();
+    for (int i=0; i<depth; i++) sb.append("  ");
+    sb.append(s);
+    debug(sb.toString());
+  }
+
+}
diff --git a/loci/formats/in/PGMReader.java b/loci/formats/in/PGMReader.java
new file mode 100644
index 0000000..3f507df
--- /dev/null
+++ b/loci/formats/in/PGMReader.java
@@ -0,0 +1,150 @@
+//
+// PGMReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import loci.formats.*;
+
+/**
+ * PGMReader is the file format reader for Portable Gray Map (PGM) images.
+ *
+ * Much of this code was adapted from ImageJ (http://rsb.info.nih.gov/ij).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/PGMReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/PGMReader.java">SVN</a></dd></dl>
+ */
+public class PGMReader extends FormatReader {
+
+  // -- Fields --
+
+  private boolean rawBits;
+
+  /** Offset to pixel data. */
+  private long offset;
+
+  // -- Constructor --
+
+  /** Constructs a new PGMReader. */
+  public PGMReader() { super("Portable Gray Map", "pgm"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return block[0] == 'P';
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    in.seek(offset);
+    if (rawBits) in.read(buf);
+    else {
+      int pt = 0;
+      while (pt < buf.length) {
+        String line = in.readLine().trim();
+        line = line.replaceAll("[^0-9]", " ");
+        StringTokenizer t = new StringTokenizer(line, " ");
+        while (t.hasMoreTokens()) {
+          int q = Integer.parseInt(t.nextToken().trim());
+          if (core.pixelType[0] == FormatTools.UINT16) {
+            short s = (short) q;
+            buf[pt] = (byte) ((s & 0xff00) >> 8);
+            buf[pt + 1] = (byte) (s & 0xff);
+            pt += 2;
+          }
+          else {
+            buf[pt] = (byte) q;
+            pt++;
+          }
+        }
+      }
+    }
+
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    String magic = in.readLine().trim();
+
+    boolean isBlackAndWhite = false;
+
+    rawBits = magic.equals("P4") || magic.equals("P5") || magic.equals("P6");
+    core.sizeC[0] = (magic.equals("P3") || magic.equals("P6")) ? 3 : 1;
+    isBlackAndWhite = magic.equals("P1") || magic.equals("P4");
+
+    String line = in.readLine().trim();
+    while (line.startsWith("#") || line.length() == 0) line = in.readLine();
+
+    line = line.replaceAll("[^0-9]", " ");
+    core.sizeX[0] =
+      Integer.parseInt(line.substring(0, line.indexOf(" ")).trim());
+    core.sizeY[0] =
+      Integer.parseInt(line.substring(line.indexOf(" ") + 1).trim());
+
+    if (!isBlackAndWhite) {
+      int max = Integer.parseInt(in.readLine().trim());
+      if (max > 255) core.pixelType[0] = FormatTools.UINT16;
+      else core.pixelType[0] = FormatTools.UINT8;
+    }
+
+    offset = in.getFilePointer();
+
+    core.rgb[0] = core.sizeC[0] == 3;
+    core.currentOrder[0] = "XYCZT";
+    core.littleEndian[0] = true;
+    core.interleaved[0] = false;
+    core.sizeZ[0] = 1;
+    core.sizeT[0] = 1;
+    core.imageCount[0] = 1;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/PNGReader.java b/loci/formats/in/PNGReader.java
new file mode 100644
index 0000000..01d22cd
--- /dev/null
+++ b/loci/formats/in/PNGReader.java
@@ -0,0 +1,45 @@
+//
+// PNGReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+/**
+ * PNGReader is the file format reader for
+ * Portable Network Graphics (PNG) images.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/PNGReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/PNGReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class PNGReader extends ImageIOReader {
+
+  // -- Constructor --
+
+  /** Constructs a new PNGReader. */
+  public PNGReader() {
+    super("Portable Network Graphics", new String[] {"png", "pnm"});
+  }
+}
diff --git a/loci/formats/in/PSDReader.java b/loci/formats/in/PSDReader.java
new file mode 100644
index 0000000..2868222
--- /dev/null
+++ b/loci/formats/in/PSDReader.java
@@ -0,0 +1,260 @@
+//
+// PSDReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import loci.formats.*;
+import loci.formats.codec.PackbitsCodec;
+
+/** PSDReader is the file format reader for Photoshop PSD files. */
+public class PSDReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Lookup table. */
+  private byte[][] lut;
+
+  // -- Constructor --
+
+  /** Constructs a new PSD reader. */
+  public PSDReader() { super("Adobe Photoshop", "psd"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return new String(block).startsWith("8BPS");
+  }
+
+  /* @see loci.formats.IFormatReader#get8BitLookupTable() */
+  public byte[][] get8BitLookupTable() {
+    FormatTools.assertId(currentId, true, 1);
+    return lut;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    if (in.getFilePointer() % 2 == 1) in.skipBytes(1);
+    in.skipBytes(4);
+    while (in.read() != '8');
+    in.skipBytes(7);
+    int len = in.readInt();
+    in.skipBytes(len);
+
+    while (in.readString(4).equals("8BIM")) {
+      in.skipBytes(4);
+      len = in.readInt();
+      in.skipBytes(len);
+    }
+    in.seek(in.getFilePointer() - 4);
+
+    int plane = core.sizeX[0] * core.sizeY[0] *
+      FormatTools.getBytesPerPixel(core.pixelType[0]);
+    int[][] lens = new int[core.sizeC[0]][core.sizeY[0]];
+    boolean compressed = in.readShort() == 1;
+
+    if (compressed) {
+      int pt = 0;
+      PackbitsCodec codec = new PackbitsCodec();
+      for (int c=0; c<core.sizeC[0]; c++) {
+        for (int y=0; y<core.sizeY[0]; y++) {
+          lens[c][y] = in.readShort();
+        }
+      }
+
+      for (int c=0; c<core.sizeC[0]; c++) {
+        for (int y=0; y<core.sizeY[0]; y++) {
+          byte[] b = new byte[lens[c][y]];
+          in.read(b);
+          b = codec.decompress(b);
+          System.arraycopy(b, 0, buf, pt, b.length);
+          pt += b.length;
+        }
+      }
+    }
+    else in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug ("PSDReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    core.littleEndian[0] = false;
+
+    if (!in.readString(4).equals("8BPS")) {
+      throw new FormatException("Not a valid Photoshop file.");
+    }
+
+    int version = in.readShort();
+    addMeta("Version", new Integer(version));
+
+    in.skipBytes(6); // reserved, set to 0
+    core.sizeC[0] = in.readShort();
+    core.sizeY[0] = in.readInt();
+    core.sizeX[0] = in.readInt();
+
+    int bits = in.readShort();
+    addMeta("Bits per pixel", new Integer(bits));
+    switch (bits) {
+      case 16:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+      default: core.pixelType[0] = FormatTools.UINT8;
+    }
+
+    int colorMode = in.readShort();
+    String modeString = null;
+    switch (colorMode) {
+      case 0:
+        modeString = "monochrome";
+        break;
+      case 1:
+        modeString = "gray-scale";
+        break;
+      case 2:
+        modeString = "palette color";
+        break;
+      case 3:
+        modeString = "RGB";
+        break;
+      case 4:
+        modeString = "CMYK";
+        break;
+      case 6:
+        modeString = "Duotone";
+        break;
+      case 7:
+        modeString = "Multichannel color";
+        break;
+      case 8:
+        modeString = "Duotone";
+        break;
+      case 9:
+        modeString = "LAB color";
+        break;
+    }
+    addMeta("Color mode", modeString);
+
+    // read color mode block, if present
+
+    int modeDataLength = in.readInt();
+    long fp = in.getFilePointer();
+    if (modeDataLength != 0) {
+      if (colorMode == 2) {
+        lut = new byte[3][256];
+        for (int i=0; i<lut.length; i++) {
+          in.read(lut[i]);
+        }
+      }
+      in.seek(fp + modeDataLength);
+    }
+
+    // read image resources block
+
+    in.skipBytes(4);
+
+    while (in.readString(4).equals("8BIM")) {
+      int tag = in.readShort();
+      int read = 1;
+      while (in.read() != 0) read++;
+      if (read % 2 == 1) in.skipBytes(1);
+
+      int size = in.readInt();
+      if (size % 2 == 1) size++;
+      byte[] data = new byte[size];
+      in.read(data);
+    }
+    in.seek(in.getFilePointer() - 4);
+
+    int blockLen = in.readInt();
+    int layerLen = in.readInt();
+    int layerCount = in.readShort();
+    int[] w = new int[layerCount];
+    int[] h = new int[layerCount];
+    int[] c = new int[layerCount];
+    for (int i=0; i<layerCount; i++) {
+      int top = in.readInt();
+      int left = in.readInt();
+      int bottom = in.readInt();
+      int right = in.readInt();
+      w[i] = right - left;
+      h[i] = bottom - top;
+      c[i] = in.readShort();
+      in.skipBytes(c[i] * 6);
+      in.skipBytes(4 + 4 + 4);
+      int len = in.readInt();
+      if (len % 2 == 1) len++;
+      in.skipBytes(len);
+    }
+
+    // skip over pixel data for each layer
+    for (int i=0; i<layerCount; i++) {
+      int[] lens = new int[h[i]];
+      for (int cc=0; cc<c[i]; cc++) {
+        boolean compressed = in.readShort() == 1;
+        if (!compressed) in.skipBytes(w[i] * h[i]);
+        else {
+          for (int y=0; y<h[i]; y++) {
+            lens[y] = in.readShort();
+          }
+          for (int y=0; y<h[i]; y++) {
+            in.skipBytes(lens[y]);
+          }
+        }
+      }
+    }
+
+    core.sizeZ[0] = 1;
+    core.sizeT[0] = 1;
+    core.rgb[0] = modeString.equals("RGB");
+    core.imageCount[0] = core.sizeC[0] / (core.rgb[0] ? 3 : 1);
+    core.indexed[0] = modeString.equals("palette color");
+    core.falseColor[0] = false;
+    core.currentOrder[0] = "XYCZT";
+    core.interleaved[0] = false;
+    core.littleEndian[0] = true;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    FormatTools.populatePixels(store, this);
+    store.setImage(currentId, null, null, null);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/PerkinElmerReader.java b/loci/formats/in/PerkinElmerReader.java
new file mode 100644
index 0000000..7fe2a80
--- /dev/null
+++ b/loci/formats/in/PerkinElmerReader.java
@@ -0,0 +1,634 @@
+//
+// PerkinElmerReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.text.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * PerkinElmerReader is the file format reader for PerkinElmer files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/PerkinElmerReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/PerkinElmerReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class PerkinElmerReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Helper reader. */
+  protected TiffReader[] tiff;
+
+  /** Tiff files to open. */
+  protected String[] files;
+
+  /** Flag indicating that the image data is in TIFF format. */
+  private boolean isTiff = true;
+
+  /** List of all files to open */
+  private Vector allFiles;
+
+  private String details, sliceSpace;
+
+  // -- Constructor --
+
+  /** Constructs a new PerkinElmer reader. */
+  public PerkinElmerReader() {
+    super("PerkinElmer", new String[] {
+      "ano", "cfg", "csv", "htm", "rec", "tim", "zpo"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) { return false; }
+
+  /* @see loci.formats.IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    return FormatTools.MUST_GROUP;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    if (isTiff) {
+      tiff[no / core.sizeC[0]].setId(files[no / core.sizeC[0]]);
+      return tiff[no / core.sizeC[0]].openBytes(0, buf);
+    }
+
+    FormatTools.checkBufferSize(this, buf.length);
+
+    String file = files[no];
+    RandomAccessStream ras = new RandomAccessStream(file);
+    ras.skipBytes(6);
+    ras.read(buf);
+    ras.close();
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    return (String[]) allFiles.toArray(new String[0]);
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly) {
+      if (tiff != null) {
+        for (int i=0; i<tiff.length; i++) {
+          if (tiff[i] != null) tiff[i].close(fileOnly);
+        }
+      }
+    }
+    else close();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    currentId = null;
+    files = null;
+    if (tiff != null) {
+      for (int i=0; i<tiff.length; i++) {
+        if (tiff[i] != null) tiff[i].close();
+      }
+    }
+  }
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (name.toLowerCase().endsWith(".cfg")) {
+      try {
+        RandomAccessStream s = new RandomAccessStream(name);
+        String ss = s.readString(512);
+        return ss.indexOf("Series") != -1;
+      }
+      catch (IOException e) {
+        if (debug) trace(e);
+        return false;
+      }
+    }
+    return super.isThisType(name, open);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (currentId != null && (id.equals(currentId) || isUsedFile(id))) return;
+
+    status("Finding HTML companion file");
+
+    if (debug) debug("PerkinElmerReader.initFile(" + id + ")");
+    // always init on the HTML file - this prevents complications with
+    // initializing the image files
+
+    if (!id.toLowerCase().endsWith(".htm")) {
+      Location parent = new Location(id).getAbsoluteFile().getParentFile();
+      String[] ls = parent.list();
+      for (int i=0; i<ls.length; i++) {
+        if (ls[i].toLowerCase().endsWith(".htm")) {
+          id = new Location(parent.getAbsolutePath(), ls[i]).getAbsolutePath();
+          break;
+        }
+      }
+    }
+
+    super.initFile(id);
+
+    allFiles = new Vector();
+
+    // get the working directory
+    Location tempFile = new Location(id).getAbsoluteFile();
+    Location workingDir = tempFile.getParentFile();
+    if (workingDir == null) workingDir = new Location(".");
+    String workingDirPath = workingDir.getPath() + File.separator;
+    String[] ls = workingDir.list();
+
+    allFiles.add(id);
+
+    status("Searching for all metadata companion files");
+
+    // check if we have any of the required header file types
+
+    int cfgPos = -1;
+    int anoPos = -1;
+    int recPos = -1;
+    int timPos = -1;
+    int csvPos = -1;
+    int zpoPos = -1;
+    int htmPos = -1;
+    int filesPt = 0;
+    files = new String[ls.length];
+
+    String tempFileName = tempFile.getName();
+    int dot = tempFileName.lastIndexOf(".");
+    String check = dot < 0 ? tempFileName : tempFileName.substring(0, dot);
+
+    // locate appropriate .tim, .csv, .zpo, .htm and .tif files
+
+    String prefix = null;
+
+    for (int i=0; i<ls.length; i++) {
+      // make sure that the file has a name similar to the name of the
+      // specified file
+
+      int d = ls[i].lastIndexOf(".");
+      while (d == -1 && i < ls.length - 1) {
+        i++;
+        d = ls[i].lastIndexOf(".");
+      }
+      String s = dot < 0 ? ls[i] : ls[i].substring(0, d);
+
+      String filename = ls[i].toLowerCase();
+
+      if (s.startsWith(check) || check.startsWith(s) ||
+        ((prefix != null) && (s.startsWith(prefix))))
+      {
+        if (cfgPos == -1 && filename.endsWith(".cfg")) {
+          cfgPos = i;
+          prefix = ls[i].substring(0, d);
+        }
+        if (anoPos == -1 && filename.endsWith(".ano")) {
+          anoPos = i;
+          prefix = ls[i].substring(0, d);
+        }
+        if (recPos == -1 && filename.endsWith(".rec")) {
+          recPos = i;
+          prefix = ls[i].substring(0, d);
+        }
+        if (timPos == -1 && filename.endsWith(".tim")) {
+          timPos = i;
+          prefix = ls[i].substring(0, d);
+        }
+        if (csvPos == -1 && filename.endsWith(".csv")) {
+          csvPos = i;
+          prefix = ls[i].substring(0, d);
+        }
+        if (zpoPos == -1 && filename.endsWith(".zpo")) {
+          zpoPos = i;
+          prefix = ls[i].substring(0, d);
+        }
+        if (htmPos == -1 && filename.endsWith(".htm")) {
+          htmPos = i;
+          prefix = ls[i].substring(0, d);
+        }
+
+        if (filename.endsWith(".tif") || filename.endsWith(".tiff")) {
+          files[filesPt] = workingDirPath + ls[i];
+          filesPt++;
+        }
+
+        try {
+          String ext = filename.substring(filename.lastIndexOf(".") + 1);
+          Integer.parseInt(ext);
+          isTiff = false;
+          files[filesPt] = workingDirPath + ls[i];
+          filesPt++;
+        }
+        catch (NumberFormatException e) {
+          try {
+            String ext = filename.substring(filename.lastIndexOf(".") + 1);
+            Integer.parseInt(ext, 16);
+            isTiff = false;
+            files[filesPt] = workingDirPath + ls[i];
+            filesPt++;
+          }
+          catch (NumberFormatException exc) {
+            if (debug) trace(exc);
+          }
+        }
+      }
+    }
+
+    // re-order the files
+
+    String[] tempFiles = files;
+    files = new String[filesPt];
+
+    // determine the number of different extensions we have
+
+    status("Finding image files");
+
+    int extCount = 0;
+    Vector foundExts = new Vector();
+    for (int i=0; i<filesPt; i++) {
+      String ext = tempFiles[i].substring(tempFiles[i].lastIndexOf(".") + 1);
+      if (!foundExts.contains(ext)) {
+        extCount++;
+        foundExts.add(ext);
+      }
+    }
+
+    for (int i=0; i<filesPt; i+=extCount) {
+      Vector extSet = new Vector();
+      for (int j=0; j<extCount; j++) {
+        if (extSet.size() == 0) extSet.add(tempFiles[i + j]);
+        else {
+          String ext =
+            tempFiles[i+j].substring(tempFiles[i+j].lastIndexOf(".") + 1);
+          int extNum = Integer.parseInt(ext, 16);
+
+          int insert = -1;
+          int pos = 0;
+          while (insert == -1 && pos < extSet.size()) {
+            String posString = (String) extSet.get(pos);
+            posString = posString.substring(posString.lastIndexOf(".") + 1);
+            int posNum = Integer.parseInt(posString, 16);
+
+            if (extNum < posNum) insert = pos;
+            pos++;
+          }
+          if (insert == -1) extSet.add(tempFiles[i+j]);
+          else extSet.add(insert, tempFiles[i + j]);
+        }
+      }
+
+      for (int j=0; j<extCount; j++) {
+        files[i+j] = (String) extSet.get(j);
+      }
+    }
+
+    for (int i=0; i<files.length; i++) allFiles.add(files[i]);
+
+    core.imageCount[0] = files.length;
+    RandomAccessStream read;
+    byte[] data;
+    StringTokenizer t;
+
+    tiff = new TiffReader[core.imageCount[0]];
+    for (int i=0; i<tiff.length; i++) {
+      tiff[i] = new TiffReader();
+      if (i > 0) tiff[i].setMetadataCollected(false);
+    }
+
+    // we always parse the .tim and .htm files if they exist, along with
+    // either the .csv file or the .zpo file
+
+    status("Parsing metadata values");
+
+    if (timPos != -1) {
+      tempFile = new Location(workingDir, ls[timPos]);
+      allFiles.add(tempFile.getAbsolutePath());
+      read = new RandomAccessStream(tempFile.getAbsolutePath());
+      data = new byte[(int) tempFile.length()];
+      read.read(data);
+      t = new StringTokenizer(new String(data));
+      int tNum = 0;
+      // can ignore "Zero x" and "Extra int"
+      String[] hashKeys = {"Number of Wavelengths/Timepoints", "Zero 1",
+        "Zero 2", "Number of slices", "Extra int", "Calibration Unit",
+        "Pixel Size Y", "Pixel Size X", "Image Width", "Image Length",
+        "Origin X", "SubfileType X", "Dimension Label X", "Origin Y",
+        "SubfileType Y", "Dimension Label Y", "Origin Z",
+        "SubfileType Z", "Dimension Label Z"};
+
+      // there are 9 additional tokens, but I don't know what they're for
+
+      while (t.hasMoreTokens() && tNum<hashKeys.length) {
+        String token = t.nextToken();
+        while ((tNum == 1 || tNum == 2) && !token.trim().equals("0")) {
+          tNum++;
+        }
+
+        if (tNum == 4) {
+          try { Integer.parseInt(token); }
+          catch (NumberFormatException e) { tNum++; }
+        }
+        addMeta(hashKeys[tNum], token);
+        if (hashKeys[tNum].equals("Image Width")) {
+          core.sizeX[0] = Integer.parseInt(token);
+        }
+        else if (hashKeys[tNum].equals("Image Length")) {
+          core.sizeY[0] = Integer.parseInt(token);
+        }
+        else if (hashKeys[tNum].equals("Number of slices")) {
+          core.sizeZ[0] = Integer.parseInt(token);
+        }
+        else if (hashKeys[tNum].equals("Experiment details:")) details = token;
+        else if (hashKeys[tNum].equals("Z slice space")) sliceSpace = token;
+        tNum++;
+      }
+      read.close();
+    }
+
+    if (csvPos != -1) {
+      tempFile = new Location(workingDir, ls[csvPos]);
+      allFiles.add(tempFile.getAbsolutePath());
+      read = new RandomAccessStream(tempFile.getAbsolutePath());
+      data = new byte[(int) tempFile.length()];
+      read.read(data);
+      t = new StringTokenizer(new String(data));
+      int tNum = 0;
+      String[] hashKeys = {"Calibration Unit", "Pixel Size X", "Pixel Size Y",
+        "Z slice space"};
+      int pt = 0;
+      while (t.hasMoreTokens()) {
+        if (tNum < 7) { t.nextToken(); }
+        else if ((tNum > 7 && tNum < 12) ||
+          (tNum > 12 && tNum < 18) || (tNum > 18 && tNum < 22))
+        {
+          t.nextToken();
+        }
+        else if (pt < hashKeys.length) {
+          String token = t.nextToken();
+          addMeta(hashKeys[pt], token);
+          if (hashKeys[pt].equals("Image Width")) {
+            core.sizeX[0] = Integer.parseInt(token);
+          }
+          else if (hashKeys[pt].equals("Image Length")) {
+            core.sizeY[0] = Integer.parseInt(token);
+          }
+          else if (hashKeys[pt].equals("Number of slices")) {
+            core.sizeZ[0] = Integer.parseInt(token);
+          }
+          else if (hashKeys[pt].equals("Experiment details:")) details = token;
+          else if (hashKeys[pt].equals("Z slice space")) sliceSpace = token;
+          pt++;
+        }
+        else {
+          String key = t.nextToken() + t.nextToken();
+          String value = t.nextToken();
+          addMeta(key, value);
+          if (key.equals("Image Width")) {
+            core.sizeX[0] = Integer.parseInt(value);
+          }
+          else if (key.equals("Image Length")) {
+            core.sizeY[0] = Integer.parseInt(value);
+          }
+          else if (key.equals("Number of slices")) {
+            core.sizeZ[0] = Integer.parseInt(value);
+          }
+          else if (key.equals("Experiment details:")) details = value;
+          else if (key.equals("Z slice space")) sliceSpace = value;
+        }
+        tNum++;
+      }
+      read.close();
+    }
+    else if (zpoPos != -1) {
+      tempFile = new Location(workingDir, ls[zpoPos]);
+      allFiles.add(tempFile.getAbsolutePath());
+      read = new RandomAccessStream(tempFile.getAbsolutePath());
+      data = new byte[(int) tempFile.length()];
+      read.read(data);
+      t = new StringTokenizer(new String(data));
+      int tNum = 0;
+      while (t.hasMoreTokens()) {
+        addMeta("Z slice #" + tNum + " position", t.nextToken());
+        tNum++;
+      }
+      read.close();
+    }
+
+    // be aggressive about parsing the HTML file, since it's the only one that
+    // explicitly defines the number of wavelengths and timepoints
+
+    if (htmPos != -1) {
+      tempFile = new Location(workingDir, ls[htmPos]);
+      allFiles.add(tempFile.getAbsolutePath());
+      read = new RandomAccessStream(tempFile.getAbsolutePath());
+      data = new byte[(int) tempFile.length()];
+      read.read(data);
+
+      String regex = "<p>|</p>|<br>|<hr>|<b>|</b>|<HTML>|<HEAD>|</HTML>|" +
+        "</HEAD>|<h1>|</h1>|<HR>|</body>";
+
+      // use reflection to avoid dependency on Java 1.4-specific split method
+      Class c = String.class;
+      String[] tokens = new String[0];
+      Throwable th = null;
+      try {
+        Method split = c.getMethod("split", new Class[] {c});
+        tokens = (String[]) split.invoke(new String(data),
+          new Object[] {regex});
+      }
+      catch (NoSuchMethodException exc) { if (debug) trace(exc); }
+      catch (IllegalAccessException exc) { if (debug) trace(exc); }
+      catch (InvocationTargetException exc) { if (debug) trace(exc); }
+
+      for (int j=0; j<tokens.length; j++) {
+        if (tokens[j].indexOf("<") != -1) tokens[j] = "";
+      }
+
+      for (int j=0; j<tokens.length-1; j+=2) {
+        if (tokens[j].indexOf("Wavelength") != -1) {
+          addMeta("Camera Data " + tokens[j].charAt(13), tokens[j]);
+          j--;
+        }
+        else if (!tokens[j].trim().equals("")) {
+          addMeta(tokens[j].trim(), tokens[j+1].trim());
+          if (tokens[j].trim().equals("Image Width")) {
+            core.sizeX[0] = Integer.parseInt(tokens[j+1].trim());
+          }
+          else if (tokens[j].trim().equals("Image Length")) {
+            core.sizeY[0] = Integer.parseInt(tokens[j+1].trim());
+          }
+          else if (tokens[j].trim().equals("Number of slices")) {
+            core.sizeZ[0] = Integer.parseInt(tokens[j+1].trim());
+          }
+          else if (tokens[j].trim().equals("Experiment details:")) {
+            details = tokens[j+1].trim();
+          }
+          else if (tokens[j].trim().equals("Z slice space")) {
+            sliceSpace = tokens[j+1].trim();
+          }
+        }
+      }
+      read.close();
+    }
+    else {
+      throw new FormatException("Valid header files not found.");
+    }
+
+    // parse details to get number of wavelengths and timepoints
+
+    String wavelengths = "1";
+    if (details != null) {
+      t = new StringTokenizer(details);
+      int tokenNum = 0;
+      boolean foundId = false;
+      String prevToken = "";
+      while (t.hasMoreTokens()) {
+        String token = t.nextToken();
+        foundId = token.equals("Wavelengths");
+        if (foundId) {
+          wavelengths = prevToken;
+        }
+        tokenNum++;
+        prevToken = token;
+      }
+    }
+
+    status("Populating metadata");
+
+    core.sizeC[0] = Integer.parseInt(wavelengths);
+
+    core.sizeT[0] = getImageCount() / (core.sizeZ[0] * core.sizeC[0]);
+    if (isTiff) {
+      tiff[0].setId(files[0]);
+      core.pixelType[0] = tiff[0].getPixelType();
+    }
+    else {
+      RandomAccessStream tmp = new RandomAccessStream(files[0]);
+      int bpp = (int) (tmp.length() - 6) / (core.sizeX[0] * core.sizeY[0]);
+      tmp.close();
+      switch (bpp) {
+        case 1:
+        case 3:
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 2:
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 4:
+          core.pixelType[0] = FormatTools.UINT32;
+          break;
+      }
+    }
+
+    core.currentOrder[0] = "XYC";
+
+    if (core.sizeZ[0] <= 0) {
+      core.sizeZ[0] = 1;
+      core.sizeT[0] = getImageCount() / (core.sizeZ[0] * core.sizeC[0]);
+    }
+    if (core.sizeC[0] <= 0) {
+      core.sizeC[0] = 1;
+      core.sizeT[0] = getImageCount() / (core.sizeZ[0] * core.sizeC[0]);
+    }
+    if (core.sizeT[0] <= 0) core.sizeT[0] = 1;
+
+    if (sliceSpace != null) {
+      core.currentOrder[0] += "TZ";
+    }
+    else core.currentOrder[0] += "ZT"; // doesn't matter, since Z = T = 1
+
+    core.rgb[0] = isTiff ? tiff[0].isRGB() : false;
+    core.interleaved[0] = false;
+    core.littleEndian[0] = isTiff ? tiff[0].isLittleEndian() : true;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = isTiff ? tiff[0].isIndexed() : false;
+    core.falseColor[0] = false;
+
+    // Populate metadata store
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+
+    // populate Dimensions element
+    String pixelSizeX = (String) getMeta("Pixel Size X");
+    String pixelSizeY = (String) getMeta("Pixel Size Y");
+    store.setDimensions(pixelSizeX == null ? null : new Float(pixelSizeX),
+      pixelSizeY == null ? null : new Float(pixelSizeY),
+      null, null, null, null);
+
+    // populate Image element
+    String time = (String) getMeta("Finish Time:");
+
+    if (time != null) {
+      SimpleDateFormat parse = new SimpleDateFormat("HH:mm:ss (MM/dd/yyyy)");
+      Date date = parse.parse(time, new ParsePosition(0));
+      SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+      time = fmt.format(date);
+    }
+    store.setImage(currentId, time, null, null);
+
+    // populate Pixels element
+    FormatTools.populatePixels(store, this);
+
+    // populate StageLabel element
+    String originX = (String) getMeta("Origin X");
+    String originY = (String) getMeta("Origin Y");
+    String originZ = (String) getMeta("Origin Z");
+
+    try {
+      store.setStageLabel(null, originX == null ? null : new Float(originX),
+        originY == null ? null : new Float(originY),
+        originZ == null ? null : new Float(originZ), null);
+    }
+    catch (NumberFormatException exc) {
+      if (debug) trace(exc);
+    }
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null);
+    }
+  }
+
+}
diff --git a/loci/formats/in/PictReader.java b/loci/formats/in/PictReader.java
new file mode 100644
index 0000000..72f3ae9
--- /dev/null
+++ b/loci/formats/in/PictReader.java
@@ -0,0 +1,841 @@
+//
+// PictReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+import loci.formats.codec.PackbitsCodec;
+
+/**
+ * PictReader is the file format reader for Apple PICT files.
+ * Most of this code was adapted from the PICT readers in JIMI
+ * (http://java.sun.com/products/jimi/index.html), ImageMagick
+ * (http://www.imagemagick.org), and Java QuickDraw.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/PictReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/PictReader.java">SVN</a></dd></dl>
+ */
+public class PictReader extends FormatReader {
+
+  // -- Constants --
+
+  // opcodes that we need
+  private static final int PICT_CLIP_RGN = 1;
+  private static final int PICT_BITSRECT = 0x90;
+  private static final int PICT_BITSRGN = 0x91;
+  private static final int PICT_PACKBITSRECT = 0x98;
+  private static final int PICT_PACKBITSRGN = 0x99;
+  private static final int PICT_9A = 0x9a;
+  private static final int PICT_END = 0xff;
+  private static final int PICT_LONGCOMMENT = 0xa1;
+
+  // possible image states
+  private static final int INITIAL = 1;
+  private static final int STATE2 = 2;
+
+  // other stuff?
+  private static final int INFOAVAIL = 2;
+  private static final int IMAGEAVAIL = 4;
+
+  /** Table used in expanding pixels that use less than 8 bits. */
+  private static final byte[] EXPANSION_TABLE = new byte[256 * 8];
+
+  static {
+    int index = 0;
+    for (int i=0; i<256; i++) {
+      EXPANSION_TABLE[index++] = (i & 128) == 0 ? (byte) 0 : (byte) 1;
+      EXPANSION_TABLE[index++] = (i & 64) == 0 ? (byte) 0 : (byte) 1;
+      EXPANSION_TABLE[index++] = (i & 32) == 0 ? (byte) 0 : (byte) 1;
+      EXPANSION_TABLE[index++] = (i & 16) == 0 ? (byte) 0 : (byte) 1;
+      EXPANSION_TABLE[index++] = (i & 8) == 0 ? (byte) 0 : (byte) 1;
+      EXPANSION_TABLE[index++] = (i & 4) == 0 ? (byte) 0 : (byte) 1;
+      EXPANSION_TABLE[index++] = (i & 2) == 0 ? (byte) 0 : (byte) 1;
+      EXPANSION_TABLE[index++] = (i & 1) == 0 ? (byte) 0 : (byte) 1;
+    }
+  }
+
+  // -- Fields --
+
+  /** Stream for reading pixel data. */
+  protected RandomAccessStream ras;
+
+  /** Pixel bytes. */
+  protected byte[] bytes;
+
+  /** Number of bytes in a row of pixel data (variable). */
+  protected int rowBytes;
+
+  /** Decoder state. */
+  protected int state;
+
+  /** Image state. */
+  protected int pictState;
+
+  /** Vector of byte arrays representing individual rows. */
+  protected Vector strips;
+
+  /** Whether or not the file is PICT v1. */
+  protected boolean versionOne;
+
+  /** Color lookup table for palette color images. */
+  protected short[][] lookup;
+
+  /** Helper reader in case this one fails. */
+  protected LegacyQTTools qtTools = new LegacyQTTools();
+
+  // -- Constructor --
+
+  /** Constructs a new PICT reader. */
+  public PictReader() { super("PICT", new String[] {"pict", "pct"}); }
+
+  // -- PictReader API methods --
+
+  /** Get the dimensions of a PICT file from the first 4 bytes after header. */
+  public Dimension getDimensions(byte[] stuff) throws FormatException {
+    if (stuff.length < 10) {
+      throw new FormatException("Need 10 bytes to calculate dimension");
+    }
+    int w = DataTools.bytesToInt(stuff, 6, 2, core.littleEndian[0]);
+    int h = DataTools.bytesToInt(stuff, 8, 2, core.littleEndian[0]);
+    if (debug) debug("getDimensions: " + w + " x " + h);
+    return new Dimension(h, w);
+  }
+
+  /** Open a PICT image from an array of bytes (used by OpenlabReader). */
+  public BufferedImage open(byte[] pix) throws FormatException, IOException {
+    // handles case when we call this method directly, instead of
+    // through initFile(String)
+    if (debug) debug("open");
+
+    if (core == null) core = new CoreMetadata(1);
+
+    strips = new Vector();
+
+    state = 0;
+    pictState = INITIAL;
+    bytes = pix;
+    ras = new RandomAccessStream(bytes);
+    ras.order(false);
+
+    try {
+      while (driveDecoder()) { }
+    }
+    catch (FormatException exc) {
+      trace(exc);
+      return ImageTools.makeBuffered(qtTools.pictToImage(pix));
+    }
+
+    // combine everything in the strips Vector
+
+    if ((core.sizeY[0]*4 < strips.size()) && (((strips.size() / 3) %
+      core.sizeY[0]) != 0))
+    {
+      core.sizeY[0] = strips.size();
+    }
+
+    if (strips.size() == 0) {
+      return ImageTools.makeBuffered(qtTools.pictToImage(pix));
+    }
+
+    if (lookup != null) {
+      // 8 bit data
+      short[][] data = new short[3][core.sizeY[0] * core.sizeX[0]];
+
+      byte[] row;
+
+      for (int i=0; i<core.sizeY[0]; i++) {
+        row = (byte[]) strips.get(i);
+
+        for (int j=0; j<row.length; j++) {
+          if (j < core.sizeX[0]) {
+            int ndx = row[j];
+            if (ndx < 0) ndx += lookup[0].length;
+            ndx = ndx % lookup[0].length;
+
+            int outIndex = i*core.sizeX[0] + j;
+            if (outIndex >= data[0].length) outIndex = data[0].length - 1;
+
+            data[0][outIndex] = lookup[0][ndx];
+            data[1][outIndex] = lookup[1][ndx];
+            data[2][outIndex] = lookup[2][ndx];
+          }
+          else j = row.length;
+        }
+      }
+
+      if (debug) {
+        debug("openBytes: 8-bit data, " + core.sizeX[0] + " x " +
+          core.sizeY[0] + ", length=" + data.length + "x" + data[0].length);
+      }
+      return ImageTools.makeImage(data, core.sizeX[0], core.sizeY[0]);
+    }
+    else if (core.sizeY[0]*3 == strips.size()) {
+      // 24 bit data
+      byte[][] data = new byte[3][core.sizeX[0] * core.sizeY[0]];
+
+      int outIndex = 0;
+      for (int i=0; i<3*core.sizeY[0]; i+=3) {
+        byte[] c0 = (byte[]) strips.get(i);
+        byte[] c1 = (byte[]) strips.get(i+1);
+        byte[] c2 = (byte[]) strips.get(i+2);
+        System.arraycopy(c0, 0, data[0], outIndex, c0.length);
+        System.arraycopy(c1, 0, data[1], outIndex, c1.length);
+        System.arraycopy(c2, 0, data[2], outIndex, c2.length);
+        outIndex += core.sizeX[0];
+      }
+
+      if (debug) {
+        debug("openBytes: 24-bit data, " + core.sizeX[0] + " x " +
+          core.sizeY[0] + ", length=" + data.length + "x" + data[0].length);
+      }
+      return ImageTools.makeImage(data, core.sizeX[0], core.sizeY[0]);
+    }
+    else if (core.sizeY[0]*4 == strips.size()) {
+      // 32 bit data
+      byte[][] data = new byte[3][core.sizeX[0] * core.sizeY[0]];
+
+      int outIndex = 0;
+      for (int i=0; i<4*core.sizeY[0]; i+=4) {
+        //byte[] a = (byte[]) strips.get(i);
+        byte[] r = (byte[]) strips.get(i+1);
+        byte[] g = (byte[]) strips.get(i+2);
+        byte[] b = (byte[]) strips.get(i+3);
+        System.arraycopy(r, 0, data[0], outIndex, r.length);
+        System.arraycopy(g, 0, data[1], outIndex, g.length);
+        System.arraycopy(b, 0, data[2], outIndex, b.length);
+        //System.arraycopy(a, 0, data[3], outIndex, a.length);
+        outIndex += core.sizeX[0];
+      }
+
+      if (debug) {
+        debug("openBytes: 32-bit data, " + core.sizeX[0] + " x " +
+          core.sizeY[0] + ", length=" + data.length + "x" + data[0].length);
+      }
+      return ImageTools.makeImage(data, core.sizeX[0], core.sizeY[0]);
+    }
+    else {
+      // 16 bit data
+      short[] data = new short[3 * core.sizeY[0] * core.sizeX[0]];
+
+      int outIndex = 0;
+      for (int i=0; i<core.sizeY[0]; i++) {
+        int[] row = (int[]) strips.get(i);
+
+        for (int j=0; j<row.length; j++, outIndex+=3) {
+          if (j < core.sizeX[0]) {
+            if (outIndex >= data.length - 2) break;
+            int s0 = (row[j] & 0x1f);
+            int s1 = (row[j] & 0x3e0) >> 5; // 0x1f << 5;
+            int s2 = (row[j] & 0x7c00) >> 10; // 0x1f << 10;
+            data[outIndex] = (short) s2;
+            data[outIndex+1] = (short) s1;
+            data[outIndex+2] = (short) s0;
+          }
+          else j = row.length;
+        }
+      }
+
+      if (debug) {
+        debug("openBytes: 16-bit data, " + core.sizeX[0] + " x " +
+          core.sizeY[0] + ", length=" + data.length);
+      }
+      return ImageTools.makeImage(data, core.sizeX[0], core.sizeY[0], 3, true);
+    }
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 528) return false;
+    return true;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    buf = ImageTools.getBytes(openImage(no), false, no % 3);
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    return open(bytes);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("PictReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    status("Populating metadata");
+
+    core.littleEndian[0] = false;
+
+    // skip the header and read in the remaining bytes
+    int len = (int) (in.length() - 512);
+    bytes = new byte[len];
+    in.seek(512);
+    in.read(bytes);
+
+    byte[] b = new byte[20];
+    in.seek(512);
+    in.read(b);
+    Dimension d = getDimensions(b);
+
+    core.sizeX[0] = d.width;
+    while (core.sizeX[0] % 8 != 0) core.sizeX[0]++;
+    core.sizeY[0] = d.height;
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = 3;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYCZT";
+    core.rgb[0] = true;
+    core.interleaved[0] = false;
+    core.imageCount[0] = 1;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    core.pixelType[0] = ImageTools.getPixelType(openImage(0));
+
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null);
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Loop through the remainder of the file and find relevant opcodes. */
+  private boolean driveDecoder() throws FormatException, IOException {
+    if (debug) debug("driveDecoder");
+    int opcode;
+
+    switch (pictState) {
+      case INITIAL:
+        ras.skipBytes(10);
+        int verOpcode = ras.read();
+        int verNumber = ras.read();
+
+        if (verOpcode == 0x11 && verNumber == 0x01) versionOne = true;
+        else if (verOpcode == 0x00 && verNumber == 0x11) {
+          versionOne = false;
+          int verNumber2 = ras.readShort();
+
+          if (verNumber2 != 0x02ff) {
+            throw new FormatException("Invalid PICT file : " + verNumber2);
+          }
+
+          // skip over v2 header -- don't need it here
+          ras.skipBytes(26);
+        }
+        else throw new FormatException("Invalid PICT file");
+
+        pictState = STATE2;
+        state |= INFOAVAIL;
+        return true;
+
+      case STATE2:
+        if (versionOne) opcode = ras.read();
+        else {
+          // if at odd boundary skip a byte for opcode in PICT v2
+
+          if ((ras.getFilePointer() & 0x1L) != 0) {
+            ras.skipBytes(1);
+          }
+          opcode = ras.readShort();
+        }
+        return drivePictDecoder(opcode);
+    }
+    return true;
+  }
+
+  /** Handles the opcodes in the PICT file. */
+  private boolean drivePictDecoder(int opcode)
+    throws FormatException, IOException
+  {
+    if (debug) debug("drivePictDecoder");
+
+    switch (opcode) {
+      case PICT_BITSRGN:  // rowBytes must be < 8
+      case PICT_PACKBITSRGN: // rowBytes must be < 8
+      case PICT_BITSRECT: // rowBytes must be < 8
+      case PICT_PACKBITSRECT:
+      case PICT_9A:
+        handlePackBits(opcode);
+        break;
+      case PICT_CLIP_RGN:
+        int x = ras.readShort();
+        ras.skipBytes(x - 2);
+        break;
+      case PICT_LONGCOMMENT:
+        ras.skipBytes(2);
+        x = ras.readShort();
+        ras.skipBytes(x);
+        break;
+      case PICT_END: // end of PICT
+        state |= IMAGEAVAIL;
+        return false;
+    }
+
+    return ras.getFilePointer() < ras.length();
+  }
+
+  /** Handles bitmap and pixmap opcodes of PICT format. */
+  private void handlePackBits(int opcode)
+    throws FormatException, IOException
+  {
+    if (debug) debug("handlePackBits(" + opcode + ")");
+    if (opcode == PICT_9A) {
+      // special case
+      handlePixmap(opcode);
+    }
+    else {
+      rowBytes = ras.readShort();
+      if (versionOne || (rowBytes & 0x8000) == 0) handleBitmap(opcode);
+      else handlePixmap(opcode);
+    }
+  }
+
+  /** Extract the image data in a PICT bitmap structure. */
+  private void handleBitmap(int opcode)
+    throws FormatException, IOException
+  {
+    if (debug) debug("handleBitmap(" + opcode + ")");
+    int row;
+    byte[] buf;  // raw byte buffer for data from file
+    byte[] uBuf; // uncompressed data -- possibly still pixel packed
+    byte[] outBuf; // expanded pixel data
+
+    rowBytes &= 0x3fff;  // mask off flags
+
+    // read the bitmap data -- 3 rectangles + mode
+
+    int tlY = ras.readShort();
+    int tlX = ras.readShort();
+    int brY = ras.readShort();
+    int brX = ras.readShort();
+
+    // skip next two rectangles
+    ras.skipBytes(18);
+
+    core.sizeX[0] = brX - tlX;
+    core.sizeY[0] = brY - tlY;
+
+    // allocate enough space to handle compressed data length for rowBytes
+
+    try {
+      buf = new byte[rowBytes + 1 + rowBytes/128];
+      uBuf = new byte[rowBytes];
+      outBuf = new byte[core.sizeX[0]];
+    }
+    catch (NegativeArraySizeException n) {
+      throw new FormatException("Sorry, vector data not supported.");
+    }
+
+    for (row=0; row < core.sizeY[0]; ++row) {
+      if (rowBytes < 8) {  // data is not compressed
+        ras.read(buf, 0, rowBytes);
+
+        for (int j=buf.length; --j >= 0;) {
+          buf[j] = (byte) ~buf[j];
+        }
+        expandPixels(1, buf, outBuf, outBuf.length);
+      }
+      else {
+        int rawLen;
+        if (rowBytes > 250) rawLen = ras.readShort();
+        else rawLen = ras.read();
+
+        try {
+          ras.read(buf, 0, rawLen);
+        }
+        catch (ArrayIndexOutOfBoundsException e) {
+          throw new FormatException("Sorry, vector data not supported.");
+        }
+
+        PackbitsCodec c = new PackbitsCodec();
+        uBuf = c.decompress(buf);
+        //uBuf = Compression.packBitsUncompress(buf);
+
+        // invert the pixels -- PICT images map zero to white
+        for (int j=0; j<uBuf.length; j++) uBuf[j] = (byte) ~uBuf[j];
+
+        expandPixels(1, uBuf, outBuf, outBuf.length);
+      }
+      strips.add(outBuf);
+    }
+  }
+
+  /** Extracts the image data in a PICT pixmap structure. */
+  private void handlePixmap(int opcode)
+    throws FormatException, IOException
+  {
+    if (debug) debug("handlePixmap(" + opcode + ")");
+    int pixelSize;
+    int compCount;
+
+    // handle 9A variation
+    if (opcode == PICT_9A) {
+      // this is the only opcode that holds 16, 24, and 32 bit data
+
+      // read the pixmap (9A)
+
+      ras.skipBytes(6);
+
+      // read the bounding box
+      int tlY = ras.readShort();
+      int tlX = ras.readShort();
+      int brY = ras.readShort();
+      int brX = ras.readShort();
+
+      ras.skipBytes(18);
+
+      pixelSize = ras.readShort();
+      compCount = ras.readShort();
+      ras.skipBytes(14);
+
+      core.sizeX[0] = brX - tlX;
+      core.sizeY[0] = brY - tlY;
+
+      // rowBytes doesn't exist, so set it to its logical value
+      switch (pixelSize) {
+        case 32:
+          rowBytes = core.sizeX[0] * compCount;
+          break;
+        case 16:
+          rowBytes = core.sizeX[0] * 2;
+          break;
+        default:
+          throw new FormatException("Sorry, vector data not supported.");
+      }
+    }
+    else {
+      rowBytes &= 0x3fff;  // mask off flags
+
+      int tlY = ras.readShort();
+      int tlX = ras.readShort();
+      int brY = ras.readShort();
+      int brX = ras.readShort();
+
+      ras.skipBytes(18);
+
+      pixelSize = ras.readShort();
+      compCount = ras.readShort();
+
+      ras.skipBytes(14);
+
+      // read the lookup table
+
+      ras.skipBytes(4);
+      int flags = ras.readShort();
+      int count = ras.readShort();
+
+      count++;
+      lookup = new short[3][count];
+
+      for (int i=0; i<count; i++) {
+        int index = ras.readShort();
+        if ((flags & 0x8000) != 0) index = i;
+        lookup[0][index] = ras.readShort();
+        lookup[1][index] = ras.readShort();
+        lookup[2][index] = ras.readShort();
+      }
+
+      core.sizeX[0] = brX - tlX;
+      core.sizeY[0] = brY - tlY;
+    }
+
+    // skip over two rectangles
+    ras.skipBytes(18);
+
+    if (opcode == PICT_BITSRGN || opcode == PICT_PACKBITSRGN) ras.skipBytes(2);
+
+    handlePixmap(rowBytes, pixelSize, compCount);
+  }
+
+  /** Handles the unpacking of the image data. */
+  private void handlePixmap(int rBytes, int pixelSize, int compCount)
+    throws FormatException, IOException
+  {
+    if (debug) {
+      debug("handlePixmap(" + rBytes + ", " +
+        pixelSize + ", " + compCount + ")");
+    }
+    int rawLen;
+    byte[] buf;  // row raw bytes
+    byte[] uBuf = null;  // row uncompressed data
+    int[] uBufI = null;  // row uncompressed data - 16+ bit pixels
+    int bufSize;
+    int outBufSize;
+    byte[] outBuf = null;  // used to expand pixel data
+
+    boolean compressed = (rBytes >= 8) || (pixelSize == 32);
+
+    bufSize = rBytes;
+
+    outBufSize = core.sizeX[0];
+
+    // allocate buffers
+
+    switch (pixelSize) {
+      case 32:
+        if (!compressed) uBufI = new int[core.sizeX[0]];
+        else uBuf = new byte[bufSize];
+        break;
+      case 16:
+        uBufI = new int[core.sizeX[0]];
+        break;
+      case 8:
+        uBuf = new byte[bufSize];
+        break;
+      default:
+        outBuf = new byte[outBufSize];
+        uBuf = new byte[bufSize];
+        break;
+    }
+
+    if (!compressed) {
+      if (debug) {
+        debug("Pixel data is uncompressed (pixelSize=" + pixelSize + ").");
+      }
+      buf = new byte[bufSize];
+      for (int row=0; row<core.sizeY[0]; row++) {
+        ras.read(buf, 0, rBytes);
+
+        switch (pixelSize) {
+          case 16:
+            for (int i=0; i<core.sizeX[0]; i++) {
+              uBufI[i] = ((buf[i*2] & 0xff) << 8) + (buf[i*2+1] & 0xff);
+            }
+            strips.add(uBufI);
+            break;
+          case 8:
+            strips.add(buf);
+            break;
+          default: // pixel size < 8
+            expandPixels(pixelSize, buf, outBuf, outBuf.length);
+            strips.add(outBuf);
+        }
+      }
+    }
+    else {
+      if (debug) {
+        debug("Pixel data is compressed (pixelSize=" +
+          pixelSize + "; compCount=" + compCount + ").");
+      }
+      buf = new byte[bufSize + 1 + bufSize / 128];
+      for (int row=0; row<core.sizeY[0]; row++) {
+        if (rBytes > 250) rawLen = ras.readShort();
+        else rawLen = ras.read();
+
+        if (rawLen > buf.length) rawLen = buf.length;
+
+        if ((ras.length() - ras.getFilePointer()) <= rawLen) {
+          rawLen = (int) (ras.length() - ras.getFilePointer() - 1);
+        }
+
+        if (rawLen < 0) {
+          rawLen = 0;
+          ras.seek(ras.length() - 1);
+        }
+
+        ras.read(buf, 0, rawLen);
+
+        if (pixelSize == 16) {
+          uBufI = new int[core.sizeX[0]];
+          unpackBits(buf, uBufI);
+          strips.add(uBufI);
+        }
+        else {
+          PackbitsCodec c = new PackbitsCodec();
+          uBuf = c.decompress(buf);
+          //uBuf = Compression.packBitsUncompress(buf);
+        }
+
+        if (pixelSize < 8) {
+          expandPixels(pixelSize, uBuf, outBuf, outBuf.length);
+          strips.add(outBuf);
+        }
+        else if (pixelSize == 8) strips.add(uBuf);
+        else if (pixelSize == 24 || pixelSize == 32) {
+          byte[] newBuf = null;
+          int offset = 0;
+
+          if (compCount == 4) {
+            // alpha channel
+            //newBuf = new byte[width];
+            //System.arraycopy(uBuf, offset, newBuf, 0, width);
+            strips.add(newBuf);
+            offset += core.sizeX[0];
+          }
+
+          // red channel
+          newBuf = new byte[core.sizeX[0]];
+          System.arraycopy(uBuf, offset, newBuf, 0, core.sizeX[0]);
+          strips.add(newBuf);
+          offset += core.sizeX[0];
+
+          // green channel
+          newBuf = new byte[core.sizeX[0]];
+          System.arraycopy(uBuf, offset, newBuf, 0, core.sizeX[0]);
+          strips.add(newBuf);
+          offset += core.sizeX[0];
+
+          // blue channel
+          newBuf = new byte[core.sizeX[0]];
+          System.arraycopy(uBuf, offset, newBuf, 0, core.sizeX[0]);
+          strips.add(newBuf);
+        }
+      }
+    }
+  }
+
+  /** Expand an array of bytes. */
+  private void expandPixels(int bitSize, byte[] ib, byte[] ob, int outLen)
+    throws FormatException
+  {
+    if (debug) {
+      debug("expandPixels(" + bitSize + ", " +
+        ib.length + ", " + ob.length + ", " + outLen + ")");
+    }
+    if (bitSize == 1) {
+      int remainder = outLen % 8;
+      int max = outLen / 8;
+      for (int i=0; i<max; i++) {
+        if (i < ib.length) {
+          int look = (ib[i] & 0xff) * 8;
+          System.arraycopy(EXPANSION_TABLE, look, ob, i*8, 8);
+        }
+        else i = max;
+      }
+
+      if (remainder != 0) {
+        if (max < ib.length) {
+          System.arraycopy(EXPANSION_TABLE, (ib[max] & 0xff) * 8, ob,
+            max*8, remainder);
+        }
+      }
+
+      return;
+    }
+
+    int i;
+    int o;
+    int t;
+    byte v;
+    int count = 8 / bitSize; // number of pixels in a byte
+    int maskshift = bitSize; // num bits to shift mask
+    int pixelshift = 8 - bitSize; // num bits to shift pixel
+    int tpixelshift = 0;
+    int pixelshiftdelta = bitSize;
+    int mask = 0;
+    int tmask; // temp mask
+
+    if (bitSize != 1 && bitSize != 2 && bitSize != 4) {
+      throw new FormatException("Can only expand 1, 2, and 4 bit values");
+    }
+
+    switch (bitSize) {
+      case 1:
+        mask = 0x80;
+        break;
+      case 2:
+        mask = 0xC0;
+        break;
+      case 4:
+        mask = 0xF0;
+        break;
+    }
+
+    i = 0;
+    for (o = 0; o < ob.length;) {
+      tmask = mask;
+      tpixelshift = pixelshift;
+      v = ib[i];
+      for (t = 0; t < count && o < ob.length; ++t, ++o) {
+        ob[o] = (byte) (((v & tmask) >>> tpixelshift) & 0xff);
+        tmask = (byte) ((tmask & 0xff) >>> maskshift);
+        tpixelshift -= pixelshiftdelta;
+      }
+      ++i;
+    }
+  }
+
+  /** PackBits variant that outputs an int array. */
+  private void unpackBits(byte[] ib, int[] ob) {
+    if (debug) debug("unpackBits(" + ib + ", " + ob + ")");
+    int i = 0;
+    int o = 0;
+    int b;
+    int rep;
+    int end;
+
+    for (o=0; o<ob.length;) {
+      if (i+1 < ib.length) {
+        b = ib[i++];
+        if (b >= 0) {
+          b++;
+          end = o + b;
+          for(; o < end; o++, i+=2) {
+            if (o < ob.length && (i+1) < ib.length) {
+              ob[o] = (((ib[i] & 0xff) << 8) + (ib[i+1] & 0xff)) & 0xffff;
+            }
+            else o = end;
+          }
+        }
+        else if (b != -128) {
+          rep = (((ib[i] & 0xff) << 8) + (ib[i+1] & 0xff)) & 0xffff;
+          i += 2;
+          end = o - b + 1;
+          for (; o < end; o++) {
+            if (o < ob.length) ob[o] = rep;
+            else o = end;
+          }
+        }
+      }
+      else o = ob.length;
+    }
+  }
+
+}
diff --git a/loci/formats/in/PrairieReader.java b/loci/formats/in/PrairieReader.java
new file mode 100644
index 0000000..c6a01bc
--- /dev/null
+++ b/loci/formats/in/PrairieReader.java
@@ -0,0 +1,422 @@
+//
+// PrairieReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * PrairieReader is the file format reader for
+ * Prairie Technologies' TIFF variant.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/PrairieReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/PrairieReader.java">SVN</a></dd></dl>
+ */
+public class PrairieReader extends FormatReader {
+
+  // -- Constants --
+
+  // Private tags present in Prairie TIFF files
+  // IMPORTANT NOTE: these are the same as Metamorph's private tags - therefore,
+  //                 it is likely that Prairie TIFF files will be incorrectly
+  //                 identified unless the XML or CFG file is specified
+  private static final int PRAIRIE_TAG_1 = 33628;
+  private static final int PRAIRIE_TAG_2 = 33629;
+  private static final int PRAIRIE_TAG_3 = 33630;
+
+  // -- Fields --
+
+  /** List of files in the current dataset */
+  private String[] files;
+
+  /** Helper reader for opening images */
+  private TiffReader tiff;
+
+  /** Names of the associated XML files */
+  private String xmlFile, cfgFile;
+  private boolean readXML = false, readCFG = false;
+
+  // -- Constructor --
+
+  /** Constructs a new Prairie TIFF reader. */
+  public PrairieReader() {
+    super("Prairie (TIFF)", new String[] {"tif", "tiff", "cfg", "xml"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    // adapted from MetamorphReader.isThisType(byte[])
+    if (block.length < 3) return false;
+    if (block.length < 8) {
+      return true; // we have no way of verifying further
+    }
+
+    String s = new String(block);
+    if (s.indexOf("xml") != -1 && s.indexOf("PV") != -1) return true;
+
+    boolean little = (block[0] == 0x49 && block[1] == 0x49);
+
+    int ifdlocation = DataTools.bytesToInt(block, 4, little);
+
+    if (ifdlocation < 0) return false;
+    else if (ifdlocation + 1 > block.length) return true;
+    else {
+      int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, little);
+      for (int i=0; i<ifdnumber; i++) {
+        if (ifdlocation + 3 + (i*12) > block.length) {
+          return false;
+        }
+        else {
+          int ifdtag = DataTools.bytesToInt(block,
+            ifdlocation + 2 + (i*12), 2, little);
+          if (ifdtag == PRAIRIE_TAG_1 || ifdtag == PRAIRIE_TAG_2 ||
+            ifdtag == PRAIRIE_TAG_3)
+          {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+  }
+
+  /* @see loci.formats.IFormatReader#fileGroupOption(String) */
+  public int fileGroupOption(String id) throws FormatException, IOException {
+    id = id.toLowerCase();
+    return (id.endsWith(".cfg") || id.endsWith(".xml")) ?
+      FormatTools.MUST_GROUP : FormatTools.CAN_GROUP;
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    FormatTools.assertId(currentId, true, 1);
+    String[] s = new String[files.length + 2];
+    System.arraycopy(files, 0, s, 0, files.length);
+    s[files.length] = xmlFile;
+    s[files.length + 1] = cfgFile;
+    return s;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    tiff.setId(files[no]);
+    return tiff.openBytes(0, buf);
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly && tiff != null) tiff.close(fileOnly);
+    else if (!fileOnly) close();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (!super.isThisType(name, open)) return false; // check extension
+    if (!isGroupFiles()) return false;
+
+    // check if there is an XML file in the same directory
+    Location  f = new Location(name);
+    f = f.getAbsoluteFile();
+    Location parent = f.getParentFile();
+    String[] listing = parent.list();
+    int xmlCount = 0;
+    for (int i=0; i<listing.length; i++) {
+      if (listing[i].toLowerCase().endsWith(".xml")) {
+        try {
+          RandomAccessStream s = new RandomAccessStream(
+            parent.getAbsolutePath() + File.separator + listing[i]);
+          if (s.readString(512).indexOf("PV") != -1) xmlCount++;
+        }
+        catch (IOException e) { }
+      }
+    }
+    if (xmlCount == 0) {
+      listing = (String[]) Location.getIdMap().keySet().toArray(new String[0]);
+      for (int i=0; i<listing.length; i++) {
+        if (listing[i].toLowerCase().endsWith(".xml")) {
+          try {
+            RandomAccessStream s = new RandomAccessStream(listing[i]);
+            if (s.readString(512).indexOf("PV") != -1) xmlCount++;
+          }
+          catch (IOException e) { }
+        }
+      }
+    }
+
+    boolean xml = xmlCount > 0;
+
+    // just checking the filename isn't enough to differentiate between
+    // Prairie and regular TIFF; open the file and check more thoroughly
+    return open ? checkBytes(name, 524304) && xml : xml;
+  }
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    files = null;
+    if (tiff != null) tiff.close();
+    tiff = null;
+    currentId = null;
+    readXML = false;
+    readCFG = false;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("PrairieReader.initFile(" + id + ")");
+
+    if (metadata == null) metadata = new Hashtable();
+    if (core == null) core = new CoreMetadata(1);
+
+    if (id.endsWith("xml") || id.endsWith("cfg")) {
+      // we have been given the XML file that lists TIFF files (best case)
+
+      status("Parsing XML");
+
+      if (id.endsWith("xml")) {
+        super.initFile(id);
+        tiff = new TiffReader();
+        xmlFile = id;
+        readXML = true;
+      }
+      else if (id.endsWith("cfg")) {
+        cfgFile = id;
+        readCFG = true;
+      }
+
+      RandomAccessStream is = new RandomAccessStream(id);
+      byte[] b = new byte[(int) is.length()];
+      is.read(b);
+      is.close();
+      String s = new String(b);
+
+      Vector elements = new Vector();
+
+      while (s.length() > 0) {
+        int ndx = s.indexOf("<");
+        int val1 = s.indexOf(">", ndx);
+        if (val1 != -1 && val1 > ndx) {
+          String sub = s.substring(ndx + 1, val1);
+          s = s.substring(val1 + 1);
+          elements.add(sub);
+        }
+      }
+
+      int zt = 0;
+      boolean isZ = false;
+      Vector f = new Vector();
+      int fileIndex = 1;
+      if (id.endsWith(".xml")) core.imageCount[0] = 0;
+
+      String pastPrefix = "";
+      for (int i=1; i<elements.size(); i++) {
+        String el = (String) elements.get(i);
+        if (el.indexOf(" ") != -1) {
+          boolean closed = el.endsWith("/");
+
+          String prefix = el.substring(0, el.indexOf(" "));
+          if (prefix.equals("File")) core.imageCount[0]++;
+          if (prefix.equals("Frame")) {
+            zt++;
+            fileIndex = 1;
+          }
+
+          if (!prefix.equals("Key") && !prefix.equals("Frame")) {
+            el = el.substring(el.indexOf(" ") + 1);
+            while (el.indexOf("=") != -1) {
+              int eq = el.indexOf("=");
+              String key = el.substring(0, eq);
+              String value = el.substring(eq + 2, el.indexOf("\"", eq + 2));
+              if (prefix.equals("File")) {
+                addMeta(pastPrefix + " " + prefix + " " + fileIndex +
+                  " " + key, value);
+                if (key.equals("filename")) fileIndex++;
+              }
+              else {
+                addMeta(pastPrefix + " " + prefix + " " + key, value);
+                if (pastPrefix.equals("PVScan") &&
+                  prefix.equals("Sequence") && key.equals("type"))
+                {
+                  isZ = value.equals("ZSeries");
+                }
+              }
+              el = el.substring(el.indexOf("\"", eq + 2) + 1).trim();
+              if (prefix.equals("File") && key.equals("filename")) {
+                File current = new File(id).getAbsoluteFile();
+                String dir = "";
+                if (current.exists()) {
+                  dir = current.getPath();
+                  dir = dir.substring(0, dir.lastIndexOf(File.separator) + 1);
+                }
+                f.add(dir + value);
+              }
+            }
+          }
+          else if (prefix.equals("Key")) {
+            int keyIndex = el.indexOf("key") + 5;
+            int valueIndex = el.indexOf("value") + 7;
+            String key = el.substring(keyIndex, el.indexOf("\"", keyIndex));
+            String value =
+              el.substring(valueIndex, el.indexOf("\"", valueIndex));
+            addMeta(key, value);
+
+            if (key.equals("pixelsPerLine")) {
+              core.sizeX[0] = Integer.parseInt(value);
+            }
+            else if (key.equals("linesPerFrame")) {
+              core.sizeY[0] = Integer.parseInt(value);
+            }
+          }
+          if (!closed) {
+            pastPrefix = prefix;
+            if (prefix.equals("Frame")) {
+              int index = el.indexOf("index") + 7;
+              String idx = el.substring(index, el.indexOf("\"", index));
+              pastPrefix += " " + idx;
+            }
+          }
+        }
+      }
+
+      if (id.endsWith("xml")) {
+        files = new String[f.size()];
+        f.copyInto(files);
+        tiff.setId(files[0]);
+
+        status("Populating metadata");
+
+        if (zt == 0) zt = 1;
+
+        core.sizeZ[0] = isZ ? zt : 1;
+        core.sizeT[0] = isZ ? 1 : zt;
+        core.sizeC[0] = core.imageCount[0] / (core.sizeZ[0] * core.sizeT[0]);
+        core.currentOrder[0] = "XYC" + (isZ ? "ZT" : "TZ");
+        core.pixelType[0] = FormatTools.UINT16;
+        core.rgb[0] = false;
+        core.interleaved[0] = false;
+        core.littleEndian[0] = tiff.isLittleEndian();
+        core.indexed[0] = tiff.isIndexed();
+        core.falseColor[0] = false;
+
+        String px = (String) getMeta("micronsPerPixel_XAxis");
+        String py = (String) getMeta("micronsPerPixel_YAxis");
+        float pixSizeX = px == null ? 0f : Float.parseFloat(px);
+        float pixSizeY = py == null ? 0f : Float.parseFloat(py);
+
+        MetadataStore store = getMetadataStore();
+
+        FormatTools.populatePixels(store, this);
+        store.setDimensions(new Float(pixSizeX), new Float(pixSizeY), null,
+          null, null, null);
+        for (int i=0; i<core.sizeC[0]; i++) {
+          String gain = (String) getMeta("pmtGain_" + i);
+          String offset = (String) getMeta("pmtOffset_" + i);
+
+          store.setLogicalChannel(i, null, null,
+            null, null, null, null, null,
+            null, offset == null ? null : new Float(offset),
+            gain == null ? null : new Float(gain), null, null, null, null,
+            null, null, null, null, null, null, null, null, null, null);
+        }
+
+        String date = (String) getMeta(" PVScan date");
+
+        if (date != null) {
+          SimpleDateFormat parse = new SimpleDateFormat("MM/dd/yyyy h:mm:ss a");
+          Date d = parse.parse(date, new ParsePosition(0));
+          SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+          date = fmt.format(d);
+        }
+        store.setImage(currentId, date, null, null);
+
+        String laserPower = (String) getMeta("laserPower_0");
+
+        store.setLaser(null, null, null, null, null, null,
+          laserPower == null ? null : new Float(laserPower),
+          null, null, null, null);
+
+        /*
+        String zoom = (String) getMeta("opticalZoom");
+        if (zoom != null) {
+          store.setDisplayOptions(new Float(zoom),
+            new Boolean(core.sizeC[0] > 1), new Boolean(core.sizeC[0] > 1),
+            new Boolean(core.sizeC[0] > 2), Boolean.FALSE,
+            null, null, null, null, null, null, null, null, null, null, null);
+        }
+        */
+      }
+
+      if (!readXML || !readCFG) {
+        File file = new File(id).getAbsoluteFile();
+        File parent = file.getParentFile();
+        String[] listing = file.exists() ? parent.list() :
+          (String[]) Location.getIdMap().keySet().toArray(new String[0]);
+        for (int i=0; i<listing.length; i++) {
+          String path = listing[i].toLowerCase();
+          if ((!readXML && path.endsWith(".xml")) ||
+            (readXML && path.endsWith(".cfg")))
+          {
+            String dir = "";
+            if (file.exists()) {
+              dir = parent.getPath();
+              if (!dir.endsWith(File.separator)) dir += File.separator;
+            }
+            initFile(dir + listing[i]);
+          }
+        }
+      }
+    }
+    else {
+      // we have been given a TIFF file - reinitialize with the proper XML file
+
+      status("Finding XML file");
+
+      Location f = new Location(id);
+      f = f.getAbsoluteFile();
+      Location parent = f.getParentFile();
+      String[] listing = parent.list();
+      for (int i=0; i<listing.length; i++) {
+        String path = listing[i].toLowerCase();
+        if (path.endsWith(".xml") || path.endsWith(".cfg")) {
+          initFile(new Location(path).getAbsolutePath());
+        }
+      }
+    }
+    if (currentId == null) currentId = id;
+  }
+
+}
diff --git a/loci/formats/in/QTReader.java b/loci/formats/in/QTReader.java
new file mode 100644
index 0000000..00df924
--- /dev/null
+++ b/loci/formats/in/QTReader.java
@@ -0,0 +1,742 @@
+//
+// QTReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.Vector;
+import java.util.zip.*;
+import loci.formats.*;
+import loci.formats.codec.*;
+
+/**
+ * QTReader is the file format reader for QuickTime movie files.
+ * It does not require any external libraries to be installed.
+ *
+ * Video codecs currently supported: raw, rle, jpeg, mjpb, rpza.
+ * Additional video codecs will be added as time permits.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/QTReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/QTReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class QTReader extends FormatReader {
+
+  // -- Constants --
+
+  /** List of identifiers for each container atom. */
+  private static final String[] CONTAINER_TYPES = {
+    "moov", "trak", "udta", "tref", "imap", "mdia", "minf", "stbl", "edts",
+    "mdra", "rmra", "imag", "vnrp", "dinf"
+  };
+
+  // -- Fields --
+
+  /** Offset to start of pixel data. */
+  private int pixelOffset;
+
+  /** Total number of bytes of pixel data. */
+  private int pixelBytes;
+
+  /** Pixel depth. */
+  private int bitsPerPixel;
+
+  /** Raw plane size, in bytes. */
+  private int rawSize;
+
+  /** Offsets to each plane's pixel data. */
+  private Vector offsets;
+
+  /** Pixel data for the previous image plane. */
+  private byte[] prevPixels;
+
+  /** Previous plane number. */
+  private int prevPlane;
+
+  /** Flag indicating whether we can safely use prevPixels. */
+  private boolean canUsePrevious;
+
+  /** Video codec used by this movie. */
+  private String codec;
+
+  /** Some movies use two video codecs -- this is the second codec. */
+  private String altCodec;
+
+  /** Number of frames that use the alternate codec. */
+  private int altPlanes;
+
+  /** An instance of the old QuickTime reader, in case this one fails. */
+  private LegacyQTReader legacy;
+
+  /** Flag indicating whether to use legacy reader by default. */
+  private boolean useLegacy;
+
+  /** Amount to subtract from each offset. */
+  private int scale;
+
+  /** Number of bytes in each plane. */
+  private Vector chunkSizes;
+
+  /** Set to true if the scanlines in a plane are interlaced (mjpb only). */
+  private boolean interlaced;
+
+  /** Flag indicating whether the resource and data fork are separated. */
+  private boolean spork;
+
+  private boolean flip;
+
+  // -- Constructor --
+
+  /** Constructs a new QuickTime reader. */
+  public QTReader() { super("QuickTime", "mov"); }
+
+  // -- QTReader API methods --
+
+  /** Sets whether to use the legacy reader (QTJava) by default. */
+  public void setLegacy(boolean legacy) { useLegacy = legacy; }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#setMetadataStore(MetadataStore) */
+  public void setMetadataStore(MetadataStore store) {
+    FormatTools.assertId(currentId, false, 1);
+    super.setMetadataStore(store);
+    if (useLegacy) legacy.setMetadataStore(store);
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    String code = codec;
+    if (no >= getImageCount() - altPlanes) code = altCodec;
+
+    boolean doLegacy = useLegacy;
+    if (!doLegacy && !code.equals("raw ") && !code.equals("rle ") &&
+      !code.equals("jpeg") && !code.equals("mjpb") && !code.equals("rpza"))
+    {
+      if (debug) {
+        debug("Unsupported codec (" + code + "); using QTJava reader");
+      }
+      doLegacy = true;
+    }
+    if (doLegacy) {
+      if (legacy == null) legacy = createLegacyReader();
+      legacy.setId(currentId);
+      return legacy.openBytes(no);
+    }
+
+    int offset = ((Integer) offsets.get(no)).intValue();
+    int nextOffset = pixelBytes;
+
+    scale = ((Integer) offsets.get(0)).intValue();
+    offset -= scale;
+
+    if (no < offsets.size() - 1) {
+      nextOffset = ((Integer) offsets.get(no + 1)).intValue();
+      nextOffset -= scale;
+    }
+
+    if ((nextOffset - offset) < 0) {
+      int temp = offset;
+      offset = nextOffset;
+      nextOffset = temp;
+    }
+
+    byte[] pixs = new byte[nextOffset - offset];
+
+    in.seek(pixelOffset + offset);
+    in.read(pixs);
+
+    canUsePrevious = (prevPixels != null) && (prevPlane == no - 1) &&
+      !code.equals(altCodec);
+
+    buf = uncompress(pixs, code);
+    if (code.equals("rpza")) {
+      for (int i=0; i<buf.length; i++) {
+        buf[i] = (byte) (255 - buf[i]);
+      }
+      prevPlane = no;
+      return buf;
+    }
+
+    // on rare occassions, we need to trim the data
+    if (canUsePrevious && (prevPixels.length < buf.length)) {
+      byte[] temp = buf;
+      buf = new byte[prevPixels.length];
+      System.arraycopy(temp, 0, buf, 0, buf.length);
+    }
+
+    prevPixels = buf;
+    prevPlane = no;
+
+    // determine whether we need to strip out any padding bytes
+
+    int pad = core.sizeX[0] % 4;
+    pad = (4 - pad) % 4;
+    if (codec.equals("mjpb")) pad = 0;
+
+    int size = core.sizeX[0] * core.sizeY[0];
+    if (size * (bitsPerPixel / 8) == prevPixels.length) pad = 0;
+
+    if (pad > 0) {
+      buf = new byte[prevPixels.length - core.sizeY[0]*pad];
+
+      for (int row=0; row<core.sizeY[0]; row++) {
+        System.arraycopy(prevPixels, row*(core.sizeX[0]+pad), buf,
+          row*core.sizeX[0], core.sizeX[0]);
+      }
+    }
+
+    if ((bitsPerPixel == 40 || bitsPerPixel == 8) && !code.equals("mjpb")) {
+      // invert the pixels
+      for (int i=0; i<buf.length; i++) {
+        buf[i] = (byte) (255 - buf[i]);
+      }
+      return buf;
+    }
+    else if (bitsPerPixel == 32) {
+      // strip out alpha channel
+      byte[][] data = new byte[3][buf.length / 4];
+      for (int i=0; i<data[0].length; i++) {
+        data[0][i] = buf[4*i + 1];
+        data[1][i] = buf[4*i + 2];
+        data[2][i] = buf[4*i + 3];
+      }
+
+      byte[] rtn = new byte[data.length * data[0].length];
+      for (int i=0; i<data.length; i++) {
+        System.arraycopy(data[i], 0, rtn, i * data[0].length, data[i].length);
+      }
+      return rtn;
+    }
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#openImage(int) */
+  public BufferedImage openImage(int no) throws FormatException, IOException {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    String code = codec;
+    if (no >= getImageCount() - altPlanes) code = altCodec;
+
+    boolean doLegacy = useLegacy;
+    if (!doLegacy && !code.equals("raw ") && !code.equals("rle ") &&
+      !code.equals("jpeg") && !code.equals("mjpb") && !code.equals("rpza"))
+    {
+      if (debug) {
+        debug("Unsupported codec (" + code + "); using QTJava reader");
+      }
+      doLegacy = true;
+    }
+    if (doLegacy) {
+      if (legacy == null) legacy = createLegacyReader();
+      legacy.setId(currentId);
+      return legacy.openImage(no);
+    }
+
+    int bpp = bitsPerPixel / 8;
+    if (bpp == 3 || bpp == 4 || bpp == 5) bpp = 1;
+    return ImageTools.makeImage(openBytes(no), core.sizeX[0],
+      core.sizeY[0], core.sizeC[0], false, bpp, core.littleEndian[0]);
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    prevPixels = null;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("QTReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+
+    spork = true;
+    offsets = new Vector();
+    chunkSizes = new Vector();
+    status("Parsing tags");
+
+    Exception exc = null;
+    try { parse(0, 0, in.length()); }
+    catch (FormatException e) { exc = e; }
+    catch (IOException e) { exc = e; }
+    if (exc != null) {
+      if (debug) trace(exc);
+      useLegacy = true;
+      legacy = createLegacyReader();
+      legacy.setId(id, true);
+      core = legacy.getCoreMetadata();
+      return;
+    }
+
+    core.imageCount[0] = offsets.size();
+    if (chunkSizes.size() < core.imageCount[0] && chunkSizes.size() > 0) {
+      core.imageCount[0] = chunkSizes.size();
+    }
+
+    status("Populating metadata");
+
+    int bytesPerPixel = bitsPerPixel / 8;
+    bytesPerPixel %= 4;
+
+    switch (bytesPerPixel) {
+      case 0:
+      case 1:
+      case 3:
+        core.pixelType[0] = FormatTools.UINT8;
+        break;
+      case 2:
+        core.pixelType[0] = FormatTools.UINT16;
+        break;
+    }
+
+    core.rgb[0] = bitsPerPixel < 40;
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = core.rgb[0] ? 3 : 1;
+    core.sizeT[0] = core.imageCount[0];
+    core.currentOrder[0] = "XYCZT";
+    core.littleEndian[0] = false;
+    core.interleaved[0] = false;
+    core.metadataComplete[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+
+    // The metadata store we're working with.
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null);
+    }
+
+    // this handles the case where the data and resource forks have been
+    // separated
+    if (spork) {
+      // first we want to check if there is a resource fork present
+      // the resource fork will generally have the same name as the data fork,
+      // but will have either the prefix "._" or the suffix ".qtr"
+      // (or <filename>/rsrc on a Mac)
+
+      String base = null;
+      if (id.indexOf(".") != -1) {
+        base = id.substring(0, id.lastIndexOf("."));
+      }
+      else base = id;
+
+      Location f = new Location(base + ".qtr");
+      if (f.exists()) {
+        in = new RandomAccessStream(f.getAbsolutePath());
+
+        stripHeader();
+        parse(0, 0, in.length());
+        core.imageCount[0] = offsets.size();
+        return;
+      }
+      else {
+        f = new Location(base.substring(0,
+          base.lastIndexOf(File.separator) + 1) + "._" +
+          base.substring(base.lastIndexOf(File.separator) + 1));
+        if (f.exists()) {
+          in = new RandomAccessStream(f.getAbsolutePath());
+          stripHeader();
+          parse(0, 0, in.length());
+          core.imageCount[0] = offsets.size();
+          return;
+        }
+        else {
+          f = new Location(base + "/rsrc");
+          if (f.exists()) {
+            in = new RandomAccessStream(f.getAbsolutePath());
+            stripHeader();
+            parse(0, 0, in.length());
+            core.imageCount[0] = offsets.size();
+            return;
+          }
+        }
+      }
+
+      throw new FormatException("QuickTime resource fork not found. " +
+        " To avoid this issue, please flatten your QuickTime movies " +
+        "before importing with Bio-Formats.");
+
+      /* TODO
+      // If we didn't find the resource fork, we can check to see if the file
+      // uses a JPEG-compatible codec.  In this case, we can do some guesswork
+      // to read the file; otherwise we will fail gracefully.
+
+      if (debug) {
+        debug("Failed to find the QuickTime resource fork. " +
+          "Attempting to proceed using only the data fork.");
+      }
+
+      // read through the file looking for occurences of the codec string
+      core.imageCount[0] = 0;
+      String codecString = new String(pixels, 4, 4);
+      if (codecString.equals("mjpg")) codec = "mjpb";
+      else codec = codecString;
+
+      if (codec.equals("mjpb") || codec.equals("jpeg")) {
+        // grab the width, height, and bits per pixel from the first plane
+
+      }
+      else {
+        throw new FormatException("Sorry, this QuickTime movie does not " +
+          "contain a Resource Fork.  Support for this case will be improved " +
+          "as time permits.  To avoid this issue, please flatten your " +
+          "QuickTime movies before importing with Bio-Formats.");
+      }
+
+      boolean canAdd = true;
+      for (int i=0; i<pixels.length-5; i++) {
+        if (codecString.equals(new String(pixels, i, 4))) {
+          if (canAdd) {
+            offsets.add(new Integer(i - 4));
+            core.imageCount[0]++;
+            canAdd = false;
+          }
+          else {
+            canAdd = true;
+          }
+          i += 1000;
+        }
+      }
+      */
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Parse all of the atoms in the file. */
+  private void parse(int depth, long offset, long length)
+    throws FormatException, IOException
+  {
+    while (offset < length) {
+      in.seek(offset);
+
+      // first 4 bytes are the atom size
+      long atomSize = in.readInt();
+      if (atomSize < 0) atomSize += 4294967296L;
+
+      // read the atom type
+      String atomType = in.readString(4);
+
+      // if atomSize is 1, then there is an 8 byte extended size
+      if (atomSize == 1) {
+        atomSize = in.readLong();
+      }
+
+      if (atomSize < 0) {
+        LogTools.println("QTReader: invalid atom size: " + atomSize);
+      }
+
+      if (debug) {
+        debug("Seeking to " + offset +
+          "; atomType=" + atomType + "; atomSize=" + atomSize);
+      }
+
+      byte[] data = new byte[0];
+
+      // if this is a container atom, parse the children
+      if (isContainer(atomType)) {
+        parse(depth++, in.getFilePointer(), offset + atomSize);
+      }
+      else {
+        if (atomSize == 0) atomSize = in.length();
+        int oldpos = (int) in.getFilePointer();
+
+        if (atomType.equals("mdat")) {
+          // we've found the pixel data
+          pixelOffset = (int) in.getFilePointer();
+          pixelBytes = (int) atomSize;
+
+          if (pixelBytes > (in.length() - pixelOffset)) {
+            pixelBytes = (int) (in.length() - pixelOffset);
+          }
+        }
+        else if (atomType.equals("tkhd")) {
+          // we've found the dimensions
+
+          in.skipBytes(38);
+          int[][] matrix = new int[3][3];
+
+          for (int i=0; i<matrix.length; i++) {
+            for (int j=0; j<matrix[0].length; j++) {
+              matrix[i][j] = in.readInt();
+            }
+          }
+
+          // The contents of the matrix we just read determine whether or not
+          // we should flip the width and height.  We can check the first two
+          // rows of the matrix - they should correspond to the first two rows
+          // of an identity matrix.
+
+          // TODO : adapt to use the value of flip
+          flip = matrix[0][0] == 0 && matrix[1][0] != 0;
+
+          if (core.sizeX[0] == 0) core.sizeX[0] = in.readInt();
+          if (core.sizeY[0] == 0) core.sizeY[0] = in.readInt();
+        }
+        else if (atomType.equals("cmov")) {
+          in.skipBytes(8);
+          byte[] b = new byte[4];
+          in.read(b);
+          if ("zlib".equals(new String(b))) {
+            atomSize = in.readInt();
+            in.skipBytes(4);
+            int uncompressedSize = in.readInt();
+
+            b = new byte[(int) (atomSize - 12)];
+            in.read(b);
+
+            Inflater inf = new Inflater();
+            inf.setInput(b, 0, b.length);
+            byte[] output = new byte[uncompressedSize];
+            try {
+              inf.inflate(output);
+            }
+            catch (DataFormatException exc) {
+              if (debug) trace(exc);
+              throw new FormatException("Compressed header not supported.");
+            }
+            inf.end();
+
+            RandomAccessStream oldIn = in;
+            in = new RandomAccessStream(output);
+            parse(0, 0, output.length);
+            in.close();
+            in = oldIn;
+          }
+          else throw new FormatException("Compressed header not supported.");
+        }
+        else if (atomType.equals("stco")) {
+          // we've found the plane offsets
+
+          if (offsets.size() > 0) break;
+          spork = false;
+          in.readInt();
+          int numPlanes = in.readInt();
+          if (numPlanes != core.imageCount[0]) {
+            in.seek(in.getFilePointer() - 4);
+            int off = in.readInt();
+            offsets.add(new Integer(off));
+            for (int i=1; i<core.imageCount[0]; i++) {
+              if ((chunkSizes.size() > 0) && (i < chunkSizes.size())) {
+                rawSize = ((Integer) chunkSizes.get(i)).intValue();
+              }
+              else i = core.imageCount[0];
+              off += rawSize;
+              offsets.add(new Integer(off));
+            }
+          }
+          else {
+            for (int i=0; i<numPlanes; i++) {
+              offsets.add(new Integer(in.readInt()));
+            }
+          }
+        }
+        else if (atomType.equals("stsd")) {
+          // found video codec and pixel depth information
+
+          in.readInt();
+          int numEntries = in.readInt();
+          in.readInt();
+
+          for (int i=0; i<numEntries; i++) {
+            if (i == 0) {
+              codec = in.readString(4);
+
+              if (!codec.equals("raw ") && !codec.equals("rle ") &&
+                !codec.equals("rpza") && !codec.equals("mjpb") &&
+                !codec.equals("jpeg"))
+              {
+                throw new FormatException("Unsupported codec: " + codec);
+              }
+
+              in.skipBytes(16);
+              if (in.readShort() == 0) {
+                in.skipBytes(56);
+
+                bitsPerPixel = in.readShort();
+                if (codec.equals("rpza")) bitsPerPixel = 8;
+                in.readShort();
+                in.readDouble();
+                int fieldsPerPlane = in.read();
+                interlaced = fieldsPerPlane == 2;
+                addMeta("Codec", codec);
+                addMeta("Bits per pixel", new Integer(bitsPerPixel));
+                in.readDouble();
+                in.read();
+              }
+            }
+            else {
+              altCodec = in.readString(4);
+              addMeta("Second codec", altCodec);
+            }
+          }
+        }
+        else if (atomType.equals("stsz")) {
+          // found the number of planes
+          in.readInt();
+          rawSize = in.readInt();
+          core.imageCount[0] = in.readInt();
+
+          if (rawSize == 0) {
+            in.seek(in.getFilePointer() - 4);
+            for (int b=0; b<core.imageCount[0]; b++) {
+              chunkSizes.add(new Integer(in.readInt()));
+            }
+          }
+        }
+        else if (atomType.equals("stsc")) {
+          in.readInt();
+
+          int numChunks = in.readInt();
+
+          if (altCodec != null) {
+            int prevChunk = 0;
+            for (int i=0; i<numChunks; i++) {
+              int chunk = in.readInt();
+              int planesPerChunk = in.readInt();
+              int id = in.readInt();
+
+              if (id == 2) altPlanes += planesPerChunk * (chunk - prevChunk);
+
+              prevChunk = chunk;
+            }
+          }
+        }
+        else if (atomType.equals("stts")) {
+          in.readDouble();
+          in.readInt();
+          int fps = in.readInt();
+          addMeta("Frames per second", new Integer(fps));
+        }
+        if (oldpos + atomSize < in.length()) {
+          in.seek(oldpos + atomSize);
+        }
+        else break;
+      }
+
+      if (atomSize == 0) offset = in.length();
+      else offset += atomSize;
+
+      // if a 'udta' atom, skip ahead 4 bytes
+      if (atomType.equals("udta")) offset += 4;
+      if (debug) print(depth, atomSize, atomType, data);
+    }
+  }
+
+  /** Checks if the given String is a container atom type. */
+  private boolean isContainer(String type) {
+    for (int i=0; i<CONTAINER_TYPES.length; i++) {
+      if (type.equals(CONTAINER_TYPES[i])) return true;
+    }
+    return false;
+  }
+
+  /** Debugging method; prints information on an atom. */
+  private void print(int depth, long size, String type, byte[] data) {
+    StringBuffer sb = new StringBuffer();
+    for (int i=0; i<depth; i++) sb.append(" ");
+    sb.append(type + " : [" + size + "]");
+    debug(sb.toString());
+  }
+
+  /** Uncompresses an image plane according to the the codec identifier. */
+  private byte[] uncompress(byte[] pixs, String code)
+    throws FormatException, IOException
+  {
+    if (code.equals("raw ")) return pixs;
+    else if (code.equals("rle ")) {
+      Object[] options = new Object[2];
+      options[0] = new int[] {core.sizeX[0], core.sizeY[0],
+        bitsPerPixel < 40 ? bitsPerPixel / 8 : (bitsPerPixel - 32) / 8};
+      options[1] = canUsePrevious ? prevPixels : null;
+      return new QTRLECodec().decompress(pixs, options);
+    }
+    else if (code.equals("rpza")) {
+      int[] options = new int[2];
+      options[0] = core.sizeX[0];
+      options[1] = core.sizeY[0];
+      return new RPZACodec().decompress(pixs, options);
+    }
+    else if (code.equals("mjpb")) {
+      int[] options = new int[4];
+      options[0] = core.sizeX[0];
+      options[1] = core.sizeY[0];
+      options[2] = bitsPerPixel;
+      options[3] = interlaced ? 1 : 0;
+      return new MJPBCodec().decompress(pixs, options);
+    }
+    else if (code.equals("jpeg")) {
+      return new JPEGCodec().decompress(pixs);
+    }
+    else throw new FormatException("Unsupported codec : " + code);
+  }
+
+  /** Cut off header bytes from a resource fork file. */
+  private void stripHeader() throws IOException {
+    // seek to 4 bytes before first occurence of 'moov'
+
+    String test = null;
+    boolean found = false;
+    while (!found && in.getFilePointer() < (in.length() - 4)) {
+      test = in.readString(4);
+      if (test.equals("moov")) {
+        found = true;
+        in.seek(in.getFilePointer() - 8);
+      }
+      else in.seek(in.getFilePointer() - 3);
+    }
+  }
+
+  /** Creates a legacy QT reader. */
+  private LegacyQTReader createLegacyReader() {
+    // use the same id mappings that this reader does
+    return new LegacyQTReader();
+  }
+
+}
diff --git a/loci/formats/in/SDTInfo.java b/loci/formats/in/SDTInfo.java
new file mode 100644
index 0000000..8a55c73
--- /dev/null
+++ b/loci/formats/in/SDTInfo.java
@@ -0,0 +1,815 @@
+//
+// SDTInfo.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import loci.formats.RandomAccessStream;
+
+/**
+ * SDTInfo encapsulates the header information for
+ * Becker & Hickl SPC-Image SDT files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/SDTInfo.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/SDTInfo.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class SDTInfo {
+
+  // -- Constants --
+
+  public static final short BH_HEADER_CHKSUM = 0x55aa;
+  public static final short BH_HEADER_NOT_VALID = 0x1111;
+  public static final short BH_HEADER_VALID = 0x5555;
+
+  /** For .set files (setup only). */
+  public static final String SETUP_IDENTIFIER = "SPC Setup Script File";
+
+  /** For normal .sdt files (setup + data). */
+  public static final String DATA_IDENTIFIER = "SPC Setup & Data File";
+
+  /**
+   * For .sdt files created automatically in Continuous Flow mode measurement
+   * (no setup, only data).
+   */
+  public static final String FLOW_DATA_IDENTIFIER = "SPC Flow Data File";
+
+  /**
+   * For .sdt files created using DLL function SPC_save_data_to_sdtfile
+   * (no setup, only data).
+   */
+  public static final String DLL_DATA_IDENTIFIER = "SPC DLL Data File";
+
+  /**
+   * For .sdt files created in FIFO mode
+   * (setup, data blocks = Decay, FCS, FIDA, FILDA & MCS curves
+   * for each used routing channel).
+   */
+  public static final String FCS_DATA_IDENTIFIER = "SPC FCS Data File";
+
+  public static final String X_STRING = "#SP [SP_SCAN_X,I,";
+  public static final String Y_STRING = "#SP [SP_SCAN_Y,I,";
+  public static final String T_STRING = "#SP [SP_ADC_RE,I,";
+  public static final String C_STRING = "#SP [SP_SCAN_RX,I,";
+
+  // -- Fields --
+
+  public int width, height, timeBins, channels;
+
+  // -- Fields - File header --
+
+  /** Software revision number (lower 4 bits >= 10(decimal)). */
+  public short revision;
+
+  /**
+   * Offset of the info part which contains general text
+   * information (Title, date, time, contents etc.).
+   */
+  public int infoOffs;
+
+  /** Length of the info part. */
+  public short infoLength;
+
+  /**
+   * Offset of the setup text data
+   * (system parameters, display parameters, trace parameters etc.).
+   */
+  public int setupOffs;
+
+  /** Length of the setup data. */
+  public short setupLength;
+
+  /** Offset of the first data block. */
+  public int dataBlockOffs;
+
+  /**
+   * no_of_data_blocks valid only when in 0 .. 0x7ffe range,
+   * if equal to 0x7fff  the  field 'reserved1' contains
+   * valid no_of_data_blocks.
+   */
+  public short noOfDataBlocks;
+
+  // length of the longest block in the file
+  public int dataBlockLength;
+
+  // offset to 1st. measurement description block
+  // (system parameters connected to data blocks)
+  public int measDescBlockOffs;
+
+  // number of measurement description blocks
+  public short noOfMeasDescBlocks;
+
+  // length of the measurement description blocks
+  public short measDescBlockLength;
+
+  // valid: 0x5555, not valid: 0x1111
+  public int headerValid;
+
+  // reserved1 now contains noOfDataBlocks
+  public long reserved1; // unsigned
+
+  public int reserved2;
+
+  // checksum of file header
+  public int chksum;
+
+  // -- Fields - File Info --
+
+  public String info;
+
+  // -- Fields -- Setup --
+
+  public String setup;
+
+  // -- Fields - MeasureInfo --
+
+  public boolean hasMeasureInfo;
+
+  /** Time of creation. */
+  public String time;
+
+  /** Date of creation. */
+  public String date;
+
+  /** Serial number of the module. */
+  public String modSerNo;
+
+  public short measMode;
+  public float cfdLL;
+  public float cfdLH;
+  public float cfdZC;
+  public float cfdHF;
+  public float synZC;
+  public short synFD;
+  public float synHF;
+  public float tacR;
+  public short tacG;
+  public float tacOF;
+  public float tacLL;
+  public float tacLH;
+  public short adcRE;
+  public short ealDE;
+  public short ncx;
+  public short ncy;
+  public int page;
+  public float colT;
+  public float repT;
+  public short stopt;
+  public int overfl;
+  public short useMotor;
+  public int steps;
+  public float offset;
+  public short dither;
+  public short incr;
+  public short memBank;
+
+  /** Module type. */
+  public String modType;
+
+  public float synTH;
+  public short deadTimeComp;
+
+  /** 2 = disabled line markers. */
+  public short polarityL;
+
+  public short polarityF;
+  public short polarityP;
+
+  /** Line predivider = 2 ** (linediv). */
+  public short linediv;
+
+  public short accumulate;
+  public int flbckY;
+  public int flbckX;
+  public int bordU;
+  public int bordL;
+  public float pixTime;
+  public short pixClk;
+  public short trigger;
+  public int scanX;
+  public int scanY;
+  public int scanRX;
+  public int scanRY;
+  public short fifoTyp;
+  public int epxDiv;
+  public int modTypeCode;
+
+  /** New in v.8.4. */
+  public int modFpgaVer;
+
+  public float overflowCorrFactor;
+  public int adcZoom;
+
+  /** Cycles (accumulation cycles in FLOW mode). */
+  public int cycles;
+
+  // -- Fields - MeasStopInfo --
+
+  public boolean hasMeasStopInfo;
+
+  /** Last SPC_test_state return value (status). */
+  public int status;
+
+  /** Scan clocks bits 2-0 (frame, line, pixel), rates_read - bit 15. */
+  public int flags;
+
+  /**
+   * Time from start to  - disarm (simple measurement)
+   * - or to the end of the cycle (for complex measurement).
+   */
+  public float stopTime;
+
+  /** Current step (if multi-step measurement). */
+  public int curStep;
+
+  /**
+   * Current cycle (accumulation cycle in FLOW mode) -
+   * (if multi-cycle measurement).
+   */
+  public int curCycle;
+
+  /** Current measured page. */
+  public int curPage;
+
+  /** Minimum rates during the measurement. */
+  public float minSyncRate;
+
+  /** (-1.0 - not set). */
+  public float minCfdRate;
+
+  public float minTacRate;
+  public float minAdcRate;
+
+  /** Maximum rates during the measurement. */
+  public float maxSyncRate;
+
+  /** (-1.0 - not set). */
+  public float maxCfdRate;
+
+  public float maxTacRate;
+  public float maxAdcRate;
+  public int mReserved1;
+  public float mReserved2;
+
+  // -- Fields - MeasFCSInfo --
+
+  public boolean hasMeasFCSInfo;
+
+  /** Routing channel number. */
+  public int chan;
+
+  /**
+   * Bit 0 = 1 - decay curve calculated.
+   * Bit 1 = 1 - fcs   curve calculated.
+   * Bit 2 = 1 - FIDA  curve calculated.
+   * Bit 3 = 1 - FILDA curve calculated.
+   * Bit 4 = 1 - MCS curve calculated.
+   * Bit 5 = 1 - 3D Image calculated.
+   */
+  public int fcsDecayCalc;
+
+  /** Macro time clock in 0.1 ns units. */
+  public long mtResol; // unsigned
+
+  /** Correlation time [ms]. */
+  public float cortime;
+
+  /** No of photons. */
+  public long calcPhotons; // unsigned
+
+  /** No of FCS values. */
+  public int fcsPoints;
+
+  /** Macro time of the last photon. */
+  public float endTime;
+
+  /**
+   * No of Fifo overruns
+   * when > 0  fcs curve & endTime are not valid.
+   */
+  public int overruns;
+
+  /**
+   * 0 - linear FCS with log binning (100 bins/log)
+   * when bit 15 = 1 (0x8000) - Multi-Tau FCS
+   * where bits 14-0 = ktau parameter.
+   */
+  public int fcsType;
+
+  /**
+   * Cross FCS routing channel number
+   * when chan = crossChan and mod == crossMod - Auto FCS
+   * otherwise - Cross FCS.
+   */
+  public int crossChan;
+
+  /** Module number. */
+  public int mod;
+
+  /** Cross FCS module number. */
+  public int crossMod;
+
+  /** Macro time clock of cross FCS module in 0.1 ns units. */
+  public long crossMtResol; // unsigned
+
+  // -- Fields - extended MeasureInfo -
+
+  public boolean hasExtendedMeasureInfo;
+
+  /**
+   * 4 subsequent fields valid only for Camera mode
+   * or FIFO_IMAGE mode.
+   */
+  public int imageX;
+  public int imageY;
+  public int imageRX;
+  public int imageRY;
+
+  /** Gain for XY ADCs (SPC930). */
+  public short xyGain;
+
+  /** Use or not  Master Clock (SPC140 multi-module). */
+  public short masterClock;
+
+  /** ADC sample delay (SPC-930). */
+  public short adcDE;
+
+  /** Detector type (SPC-930 in camera mode). */
+  public short detType;
+
+  /** X axis representation (SPC-930). */
+  public short xAxis;
+
+  // -- Fields - MeasHISTInfo --
+
+  public boolean hasMeasHISTInfo;
+
+  /** Interval time [ms] for FIDA histogram. */
+  public float fidaTime;
+
+  /** Interval time [ms] for FILDA histogram. */
+  public float fildaTime;
+
+  /** No of FIDA values. */
+  public int fidaPoints;
+
+  /** No of FILDA values. */
+  public int fildaPoints;
+
+  /** Interval time [ms] for MCS histogram. */
+  public float mcsTime;
+
+  /** No of MCS values. */
+  public int mcsPoints;
+
+  // -- Fields - BHFileBlockHeader --
+
+  /**
+   * Number of the block in the file.
+   * Valid only when in 0..0x7ffe range, otherwise use lblock_no field
+   * obsolete now, lblock_no contains full block no information.
+   */
+  public short blockNo;
+
+  /** Offset of the data block from the beginning of the file. */
+  public int dataOffs;
+
+  /** Offset to the data block header of the next data block. */
+  public int nextBlockOffs;
+
+  /** See blockType defines below. */
+  public int blockType;
+
+  /**
+   * Number of the measurement description block
+   * corresponding to this data block.
+   */
+  public short measDescBlockNo;
+
+  /** Long blockNo - see remarks below. */
+  public long lblockNo; // unsigned
+
+  /** reserved2 now contains block (set) length. */
+  public long blockLength; // unsigned
+
+  // -- Constructor --
+
+  /**
+   * Constructs a new SDT header by reading values from the given input source,
+   * populating the given metadata table.
+   */
+  public SDTInfo(RandomAccessStream in, Hashtable meta) throws IOException {
+    // read bhfileHeader
+    revision = in.readShort();
+    infoOffs = in.readInt();
+    infoLength = in.readShort();
+    setupOffs = in.readInt();
+    setupLength = in.readShort();
+    dataBlockOffs = in.readInt();
+    noOfDataBlocks = in.readShort();
+    dataBlockLength = in.readInt();
+    measDescBlockOffs = in.readInt();
+    noOfMeasDescBlocks = in.readShort();
+    measDescBlockLength = in.readShort();
+    headerValid = in.readUnsignedShort();
+    reserved1 = (0xffffffffL & in.readInt()); // unsigned
+    reserved2 = in.readUnsignedShort();
+    chksum = in.readUnsignedShort();
+
+    // save bhfileHeader to metadata table
+    if (meta != null) {
+      final String bhfileHeader = "bhfileHeader.";
+      meta.put(bhfileHeader + "revision", new Short(revision));
+      meta.put(bhfileHeader + "infoOffs", new Integer(infoOffs));
+      meta.put(bhfileHeader + "infoLength", new Short(infoLength));
+      meta.put(bhfileHeader + "setupOffs", new Integer(setupOffs));
+      meta.put(bhfileHeader + "dataBlockOffs", new Integer(dataBlockOffs));
+      meta.put(bhfileHeader + "noOfDataBlocks", new Short(noOfDataBlocks));
+      meta.put(bhfileHeader + "dataBlockLength",
+        new Integer(dataBlockLength));
+      meta.put(bhfileHeader + "measDescBlockOffs",
+        new Integer(measDescBlockOffs));
+      meta.put(bhfileHeader + "noOfMeasDescBlocks",
+        new Short(noOfMeasDescBlocks));
+      meta.put(bhfileHeader + "measDescBlockLength",
+        new Integer(measDescBlockLength));
+      meta.put(bhfileHeader + "headerValid", new Integer(headerValid));
+      meta.put(bhfileHeader + "reserved1", new Long(reserved1));
+      meta.put(bhfileHeader + "reserved2", new Integer(reserved2));
+      meta.put(bhfileHeader + "chksum", new Integer(chksum));
+    }
+
+    // read file info
+    in.seek(infoOffs);
+    byte[] infoBytes = new byte[infoLength];
+    in.readFully(infoBytes);
+    info = new String(infoBytes);
+
+    // save file info to metadata table
+    if (meta != null) meta.put("File Info", info);
+    // TODO: parse individual parameters from info string and store them
+
+    // read setup
+    in.seek(setupOffs);
+    byte[] setupBytes = new byte[setupLength];
+    in.readFully(setupBytes);
+    setup = new String(setupBytes);
+
+    // save setup to metadata table
+    if (meta != null) meta.put("Setup", setup);
+    // TODO: parse individual parameters from setup string and store them
+
+    // extract dimensional parameters from setup string
+    int xIndex = setup.indexOf(X_STRING);
+    if (xIndex > 0) {
+      int ndx = xIndex + X_STRING.length();
+      int end = setup.indexOf("]", ndx);
+      width = Integer.parseInt(setup.substring(ndx, end));
+    }
+    int yIndex = setup.indexOf(Y_STRING);
+    if (yIndex > 0) {
+      int ndx = yIndex + Y_STRING.length();
+      int end = setup.indexOf("]", ndx);
+      height = Integer.parseInt(setup.substring(ndx, end));
+    }
+    int tIndex = setup.indexOf(T_STRING);
+    if (tIndex > 0) {
+      int ndx = tIndex + T_STRING.length();
+      int end = setup.indexOf("]", ndx);
+      timeBins = Integer.parseInt(setup.substring(ndx, end));
+    }
+    int cIndex = setup.indexOf(C_STRING);
+    if (cIndex > 0) {
+      int ndx = cIndex + C_STRING.length();
+      int end = setup.indexOf("]", ndx);
+      channels = Integer.parseInt(setup.substring(ndx, end));
+    }
+
+    // read measurement data
+    if (noOfMeasDescBlocks > 0) {
+      in.seek(measDescBlockOffs);
+
+      hasMeasureInfo = measDescBlockLength >= 211;
+      hasMeasStopInfo = measDescBlockLength >= 211 + 60;
+      hasMeasFCSInfo = measDescBlockLength >= 211 + 60 + 38;
+      hasExtendedMeasureInfo = measDescBlockLength >= 211 + 60 + 38 + 26;
+      hasMeasHISTInfo = measDescBlockLength >= 211 + 60 + 38 + 26 + 24;
+
+      if (hasMeasureInfo) {
+        byte[] timeBytes = new byte[9];
+        in.readFully(timeBytes);
+        time = new String(timeBytes);
+
+        byte[] dateBytes = new byte[11];
+        in.readFully(dateBytes);
+        date = new String(dateBytes);
+
+        byte[] modSerNoBytes = new byte[16];
+        in.readFully(modSerNoBytes);
+        modSerNo = new String(modSerNoBytes);
+
+        measMode = in.readShort();
+        cfdLL = in.readFloat();
+        cfdLH = in.readFloat();
+        cfdZC = in.readFloat();
+        cfdHF = in.readFloat();
+        synZC = in.readFloat();
+        synFD = in.readShort();
+        synHF = in.readFloat();
+        tacR = in.readFloat();
+        tacG = in.readShort();
+        tacOF = in.readFloat();
+        tacLL = in.readFloat();
+        tacLH = in.readFloat();
+        adcRE = in.readShort();
+        ealDE = in.readShort();
+        ncx = in.readShort();
+        ncy = in.readShort();
+        page = in.readUnsignedShort();
+        colT = in.readFloat();
+        repT = in.readFloat();
+        stopt = in.readShort();
+        overfl = in.readUnsignedByte();
+        useMotor = in.readShort();
+        steps = in.readUnsignedShort();
+        offset = in.readFloat();
+        dither = in.readShort();
+        incr = in.readShort();
+        memBank = in.readShort();
+
+        byte[] modTypeBytes = new byte[16];
+        in.readFully(modTypeBytes);
+        modType = new String(modTypeBytes);
+
+        synTH = in.readFloat();
+        deadTimeComp = in.readShort();
+        polarityL = in.readShort();
+        polarityF = in.readShort();
+        polarityP = in.readShort();
+        linediv = in.readShort();
+        accumulate = in.readShort();
+        flbckY = in.readInt();
+        flbckX = in.readInt();
+        bordU = in.readInt();
+        bordL = in.readInt();
+        pixTime = in.readFloat();
+        pixClk = in.readShort();
+        trigger = in.readShort();
+        scanX = in.readInt();
+        scanY = in.readInt();
+        scanRX = in.readInt();
+        scanRY = in.readInt();
+        fifoTyp = in.readShort();
+        epxDiv = in.readInt();
+        modTypeCode = in.readUnsignedShort();
+        modFpgaVer = in.readUnsignedShort();
+        overflowCorrFactor = in.readFloat();
+        adcZoom = in.readInt();
+        cycles = in.readInt();
+
+        // save MeasureInfo to metadata table
+        if (meta != null) {
+          final String measureInfo = "MeasureInfo.";
+          meta.put(measureInfo + "time", time);
+          meta.put(measureInfo + "date", date);
+          meta.put(measureInfo + "modSerNo", modSerNo);
+          meta.put(measureInfo + "measMode", new Short(measMode));
+          meta.put(measureInfo + "cfdLL", new Float(cfdLL));
+          meta.put(measureInfo + "cfdLH", new Float(cfdLH));
+          meta.put(measureInfo + "cfdZC", new Float(cfdZC));
+          meta.put(measureInfo + "cfdHF", new Float(cfdHF));
+          meta.put(measureInfo + "synZC", new Float(synZC));
+          meta.put(measureInfo + "synFD", new Short(synFD));
+          meta.put(measureInfo + "synHF", new Float(synHF));
+          meta.put(measureInfo + "tacR", new Float(tacR));
+          meta.put(measureInfo + "tacG", new Short(tacG));
+          meta.put(measureInfo + "tacOF", new Float(tacOF));
+          meta.put(measureInfo + "tacLL", new Float(tacLL));
+          meta.put(measureInfo + "tacLH", new Float(tacLH));
+          meta.put(measureInfo + "adcRE", new Short(adcRE));
+          meta.put(measureInfo + "ealDE", new Short(ealDE));
+          meta.put(measureInfo + "ncx", new Short(ncx));
+          meta.put(measureInfo + "ncy", new Short(ncy));
+          meta.put(measureInfo + "page", new Integer(page));
+          meta.put(measureInfo + "colT", new Float(colT));
+          meta.put(measureInfo + "repT", new Float(repT));
+          meta.put(measureInfo + "stopt", new Short(stopt));
+          meta.put(measureInfo + "overfl", new Integer(overfl));
+          meta.put(measureInfo + "useMotor", new Short(useMotor));
+          meta.put(measureInfo + "steps", new Integer(steps));
+          meta.put(measureInfo + "offset", new Float(offset));
+          meta.put(measureInfo + "dither", new Short(dither));
+          meta.put(measureInfo + "incr", new Short(incr));
+          meta.put(measureInfo + "memBank", new Short(memBank));
+          meta.put(measureInfo + "modType", modType);
+          meta.put(measureInfo + "synTH", new Float(synTH));
+          meta.put(measureInfo + "deadTimeComp", new Short(deadTimeComp));
+          meta.put(measureInfo + "polarityL", new Short(polarityL));
+          meta.put(measureInfo + "polarityF", new Short(polarityF));
+          meta.put(measureInfo + "polarityP", new Short(polarityP));
+          meta.put(measureInfo + "linediv", new Short(linediv));
+          meta.put(measureInfo + "accumulate", new Short(accumulate));
+          meta.put(measureInfo + "flbckY", new Integer(flbckY));
+          meta.put(measureInfo + "flbckX", new Integer(flbckX));
+          meta.put(measureInfo + "bordU", new Integer(bordU));
+          meta.put(measureInfo + "bordL", new Integer(bordL));
+          meta.put(measureInfo + "pixTime", new Float(pixTime));
+          meta.put(measureInfo + "pixClk", new Short(pixClk));
+          meta.put(measureInfo + "trigger", new Short(trigger));
+          meta.put(measureInfo + "scanX", new Integer(scanX));
+          meta.put(measureInfo + "scanY", new Integer(scanY));
+          meta.put(measureInfo + "scanRX", new Integer(scanRX));
+          meta.put(measureInfo + "scanRY", new Integer(scanRY));
+          meta.put(measureInfo + "fifoTyp", new Short(fifoTyp));
+          meta.put(measureInfo + "epxDiv", new Integer(epxDiv));
+          meta.put(measureInfo + "modTypeCode", new Integer(modTypeCode));
+          meta.put(measureInfo + "modFpgaVer", new Integer(modFpgaVer));
+          meta.put(measureInfo + "overflowCorrFactor",
+            new Float(overflowCorrFactor));
+          meta.put(measureInfo + "adcZoom", new Integer(adcZoom));
+          meta.put(measureInfo + "cycles", new Integer(cycles));
+        }
+
+        // extract dimensional parameters from measure info
+        width = scanX;
+        height = scanY;
+        timeBins = adcRE;
+        channels = scanRX;
+      }
+
+      if (hasMeasStopInfo) {
+        // MeasStopInfo - information collected when measurement is finished
+        status = in.readUnsignedShort();
+        flags = in.readUnsignedShort();
+        stopTime = in.readFloat();
+        curStep = in.readInt();
+        curCycle = in.readInt();
+        curPage = in.readInt();
+        minSyncRate = in.readFloat();
+        minCfdRate = in.readFloat();
+        minTacRate = in.readFloat();
+        minAdcRate = in.readFloat();
+        maxSyncRate = in.readFloat();
+        maxCfdRate = in.readFloat();
+        maxTacRate = in.readFloat();
+        maxAdcRate = in.readFloat();
+        mReserved1 = in.readInt();
+        mReserved2 = in.readFloat();
+
+        // save MeasStopInfo to metadata table
+        if (meta != null) {
+          final String measStopInfo = "MeasStopInfo.";
+          meta.put(measStopInfo + "status", new Integer(status));
+          meta.put(measStopInfo + "flags", new Integer(flags));
+          meta.put(measStopInfo + "stopTime", new Float(stopTime));
+          meta.put(measStopInfo + "curStep", new Integer(curStep));
+          meta.put(measStopInfo + "curCycle", new Integer(curCycle));
+          meta.put(measStopInfo + "curPage", new Integer(curPage));
+          meta.put(measStopInfo + "minSyncRate", new Float(minSyncRate));
+          meta.put(measStopInfo + "minCfdRate", new Float(minCfdRate));
+          meta.put(measStopInfo + "minTacRate", new Float(minTacRate));
+          meta.put(measStopInfo + "minAdcRate", new Float(minAdcRate));
+          meta.put(measStopInfo + "maxSyncRate", new Float(maxSyncRate));
+          meta.put(measStopInfo + "maxCfdRate", new Float(maxCfdRate));
+          meta.put(measStopInfo + "maxTacRate", new Float(maxTacRate));
+          meta.put(measStopInfo + "maxAdcRate", new Float(maxAdcRate));
+          meta.put(measStopInfo + "reserved1", new Integer(mReserved1));
+          meta.put(measStopInfo + "reserved2", new Float(mReserved2));
+        }
+      }
+
+      if (hasMeasFCSInfo) {
+        // MeasFCSInfo - information collected when FIFO measurement is finished
+        chan = in.readUnsignedShort();
+        fcsDecayCalc = in.readUnsignedShort();
+        mtResol = (0xffffffffL & in.readInt()); // unsigned
+        cortime = in.readFloat();
+        calcPhotons = (0xffffffffL & in.readInt()); // unsigned
+        fcsPoints = in.readInt();
+        endTime = in.readFloat();
+        overruns = in.readUnsignedShort();
+        fcsType = in.readUnsignedShort();
+        crossChan = in.readUnsignedShort();
+        mod = in.readUnsignedShort();
+        crossMod = in.readUnsignedShort();
+        crossMtResol = (0xffffffffL & in.readInt()); // unsigned
+
+        // save MeasFCSInfo to metadata table
+        if (meta != null) {
+          final String measFCSInfo = "MeasFCSInfo.";
+          meta.put(measFCSInfo + "chan", new Integer(chan));
+          meta.put(measFCSInfo + "fcsDecayCalc", new Integer(fcsDecayCalc));
+          meta.put(measFCSInfo + "mtResol", new Long(mtResol));
+          meta.put(measFCSInfo + "cortime", new Float(cortime));
+          meta.put(measFCSInfo + "calcPhotons", new Long(calcPhotons));
+          meta.put(measFCSInfo + "fcsPoints", new Integer(fcsPoints));
+          meta.put(measFCSInfo + "endTime", new Float(endTime));
+          meta.put(measFCSInfo + "overruns", new Integer(overruns));
+          meta.put(measFCSInfo + "fcsType", new Integer(fcsType));
+          meta.put(measFCSInfo + "crossChan", new Integer(crossChan));
+          meta.put(measFCSInfo + "mod", new Integer(mod));
+          meta.put(measFCSInfo + "crossMod", new Integer(crossMod));
+          meta.put(measFCSInfo + "crossMtResol", new Float(crossMtResol));
+        }
+      }
+
+      if (hasExtendedMeasureInfo) {
+        imageX = in.readInt();
+        imageY = in.readInt();
+        imageRX = in.readInt();
+        imageRY = in.readInt();
+        xyGain = in.readShort();
+        masterClock = in.readShort();
+        adcDE = in.readShort();
+        detType = in.readShort();
+        xAxis = in.readShort();
+
+        // save extra MeasureInfo to metadata table
+        if (meta != null) {
+          final String measureInfo = "MeasureInfo.";
+          meta.put(measureInfo + "imageX", new Integer(imageX));
+          meta.put(measureInfo + "imageY", new Integer(imageY));
+          meta.put(measureInfo + "imageRX", new Integer(imageRX));
+          meta.put(measureInfo + "imageRY", new Integer(imageRY));
+          meta.put(measureInfo + "xyGain", new Short(xyGain));
+          meta.put(measureInfo + "masterClock", new Short(masterClock));
+          meta.put(measureInfo + "adcDE", new Short(adcDE));
+          meta.put(measureInfo + "detType", new Short(detType));
+          meta.put(measureInfo + "xAxis", new Short(xAxis));
+        }
+      }
+
+      if (hasMeasHISTInfo) {
+        // MeasHISTInfo - extension of FCSInfo, valid only for FIFO meas
+        // extension of MeasFCSInfo for other histograms (FIDA, FILDA, MCS)
+        fidaTime = in.readFloat();
+        fildaTime = in.readFloat();
+        fidaPoints = in.readInt();
+        fildaPoints = in.readInt();
+        mcsTime = in.readFloat();
+        mcsPoints = in.readInt();
+
+        // save MeasHISTInfo to metadata table
+        if (meta != null) {
+          final String measHISTInfo = "MeasHISTInfo.";
+          meta.put(measHISTInfo + "fidaTime", new Float(fidaTime));
+          meta.put(measHISTInfo + "fildaTime", new Float(fildaTime));
+          meta.put(measHISTInfo + "fidaPoints", new Integer(fidaPoints));
+          meta.put(measHISTInfo + "fildaPoints", new Integer(fildaPoints));
+          meta.put(measHISTInfo + "mcsTime", new Float(mcsTime));
+          meta.put(measHISTInfo + "mcsPoints", new Integer(mcsPoints));
+        }
+      }
+    }
+
+    in.seek(dataBlockOffs);
+
+    // read BHFileBlockHeader
+    blockNo = in.readShort();
+    dataOffs = in.readInt();
+    nextBlockOffs = in.readInt();
+    blockType = in.readUnsignedShort();
+    measDescBlockNo = in.readShort();
+    lblockNo = (0xffffffffL & in.readInt()); // unsigned
+    blockLength = (0xffffffffL & in.readInt()); // unsigned
+
+    // save BHFileBlockHeader to metadata table
+    if (meta != null) {
+      final String bhFileBlockHeader = "BHFileBlockHeader.";
+      meta.put(bhFileBlockHeader + "blockNo", new Short(blockNo));
+      meta.put(bhFileBlockHeader + "dataOffs", new Integer(dataOffs));
+      meta.put(bhFileBlockHeader + "nextBlockOffs",
+        new Integer(nextBlockOffs));
+      meta.put(bhFileBlockHeader + "blockType", new Integer(blockType));
+      meta.put(bhFileBlockHeader + "measDescBlockNo",
+        new Short(measDescBlockNo));
+      meta.put(bhFileBlockHeader + "lblockNo", new Long(lblockNo));
+      meta.put(bhFileBlockHeader + "blockLength", new Long(blockLength));
+    }
+  }
+
+}
diff --git a/loci/formats/in/SDTReader.java b/loci/formats/in/SDTReader.java
new file mode 100644
index 0000000..df84636
--- /dev/null
+++ b/loci/formats/in/SDTReader.java
@@ -0,0 +1,227 @@
+//
+// SDTReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * SDTReader is the file format reader for
+ * Becker & Hickl SPC-Image SDT files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/SDTReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/SDTReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class SDTReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Object containing SDT header information. */
+  protected SDTInfo info;
+
+  /** Offset to binary data. */
+  protected int off;
+
+  /** Number of time bins in lifetime histogram. */
+  protected int timeBins;
+
+  /** Number of spectral channels. */
+  protected int channels;
+
+  /** Whether to combine lifetime bins into single intensity image planes. */
+  protected boolean intensity = true;
+
+  // -- Constructor --
+
+  /** Constructs a new SDT reader. */
+  public SDTReader() { super("SPCImage Data", "sdt"); }
+
+  // -- SDTReader API methods --
+
+  /**
+   * Toggles whether the reader should return intensity
+   * data only (the sum of each lifetime histogram).
+   */
+  public void setIntensity(boolean intensity) {
+    FormatTools.assertId(currentId, false, 1);
+    this.intensity = intensity;
+  }
+
+  /**
+   * Gets whether the reader is combining each lifetime
+   * histogram into a summed intensity image plane.
+   */
+  public boolean isIntensity() { return intensity; }
+
+  /** Gets the number of bins in the lifetime histogram. */
+  public int getTimeBinCount() {
+    return timeBins;
+  }
+
+  /** Gets the number of spectral channels. */
+  public int getChannelCount() {
+    return channels;
+  }
+
+  /** Gets object containing SDT header information. */
+  public SDTInfo getInfo() {
+    return info;
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) { return false; }
+
+  /* @see loci.formats.IFormatReader#getChannelDimLengths() */
+  public int[] getChannelDimLengths() {
+    FormatTools.assertId(currentId, true, 1);
+    return intensity ? new int[] {channels} : new int[] {timeBins, channels};
+  }
+
+  /* @see loci.formats.IFormatReader#getChannelDimTypes() */
+  public String[] getChannelDimTypes() {
+    FormatTools.assertId(currentId, true, 1);
+    return intensity ? new String[] {FormatTools.SPECTRA} :
+      new String[] {FormatTools.LIFETIME, FormatTools.SPECTRA};
+  }
+
+  /* @see loci.formats.IFormatReader#isInterleaved(int) */
+  public boolean isInterleaved(int subC) {
+    FormatTools.assertId(currentId, true, 1);
+    return !intensity && subC == 0;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    if (intensity) {
+      in.seek(off + 2 * core.sizeX[series] * core.sizeY[series] *
+        timeBins * no);
+      byte[] timeBin = new byte[timeBins * 2];
+      for (int y=0; y<core.sizeY[series]; y++) {
+        for (int x=0; x<core.sizeX[series]; x++) {
+          // read all lifetime bins at this pixel for this channel
+
+          // combine lifetime bins into intensity value
+          short sum = 0;
+          in.read(timeBin);
+          for (int t=0; t<timeBins; t++) {
+            sum += DataTools.bytesToShort(timeBin, t*2, true);
+          }
+          int ndx = 2 * (core.sizeX[0] * y + x);
+          buf[ndx] = (byte) (sum & 0xff);
+          buf[ndx + 1] = (byte) ((sum >> 8) & 0xff);
+        }
+      }
+    }
+    else {
+      in.seek(off + 2 * core.sizeX[series]*core.sizeY[series] * timeBins * no);
+      for (int y=0; y<core.sizeY[series]; y++) {
+        for (int x=0; x<core.sizeX[series]; x++) {
+          for (int t=0; t<timeBins; t++) {
+            int ndx = 2 * (timeBins * core.sizeX[0] * y + timeBins * x + t);
+            in.readFully(buf, ndx, 2);
+          }
+        }
+      }
+    }
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /** Initializes the given SDT file. */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("SDTReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(true);
+
+    status("Reading header");
+
+    // read file header information
+    info = new SDTInfo(in, metadata);
+    off = info.dataBlockOffs + 22;
+    timeBins = info.timeBins;
+    channels = info.channels;
+    addMeta("time bins", new Integer(timeBins));
+    addMeta("channels", new Integer(channels));
+
+    status("Populating metadata");
+
+    core.sizeX[0] = info.width;
+    core.sizeY[0] = info.height;
+    core.sizeZ[0] = 1;
+    core.sizeC[0] = intensity ? channels : timeBins * channels;
+    core.sizeT[0] = 1;
+    core.currentOrder[0] = "XYZTC";
+    core.pixelType[0] = FormatTools.UINT16;
+    core.rgb[0] = !intensity;
+    core.littleEndian[0] = true;
+    core.imageCount[0] = channels;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null);
+    }
+  }
+
+  // -- Deprecated API methods --
+
+  /** @deprecated Replaced by {@link #getTimeBinCount()} */
+  public int getTimeBinCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getTimeBinCount();
+  }
+
+  /** @deprecated Replaced by {@link #getChannelCount()} */
+  public int getChannelCount(String id) throws FormatException, IOException {
+    setId(id);
+    return getChannelCount();
+  }
+
+  /** @deprecated Replaced by {@link #getInfo()} */
+  public SDTInfo getInfo(String id) throws FormatException, IOException {
+    setId(id);
+    return getInfo();
+  }
+
+}
diff --git a/loci/formats/in/SEQReader.java b/loci/formats/in/SEQReader.java
new file mode 100644
index 0000000..785330a
--- /dev/null
+++ b/loci/formats/in/SEQReader.java
@@ -0,0 +1,146 @@
+//
+// SEQReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import loci.formats.*;
+
+/**
+ * SEQReader is the file format reader for Image-Pro Sequence files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/SEQReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/SEQReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class SEQReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  /**
+   * An array of shorts (length 12) with identical values in all of our
+   * samples; assuming this is some sort of format identifier.
+   */
+  private static final int IMAGE_PRO_TAG_1 = 50288;
+
+  /** Frame rate. */
+  private static final int IMAGE_PRO_TAG_2 = 40105;
+
+  // -- Constructor --
+
+  /** Constructs a new Image-Pro SEQ reader. */
+  public SEQReader() { super("Image-Pro Sequence", "seq"); }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    core.sizeZ[0] = 0;
+    core.sizeT[0] = 0;
+
+    for (int j=0; j<ifds.length; j++) {
+      short[] tag1 = (short[]) TiffTools.getIFDValue(ifds[j], IMAGE_PRO_TAG_1);
+
+      if (tag1 != null) {
+        String seqId = "";
+        for (int i=0; i<tag1.length; i++) seqId = seqId + tag1[i];
+        addMeta("Image-Pro SEQ ID", seqId);
+      }
+
+      int tag2 = TiffTools.getIFDIntValue(ifds[0], IMAGE_PRO_TAG_2);
+
+      if (tag2 != -1) {
+        // should be one of these for every image plane
+        core.sizeZ[0]++;
+        addMeta("Frame Rate", new Integer(tag2));
+      }
+
+      addMeta("Number of images", new Integer(core.sizeZ[0]));
+    }
+
+    if (core.sizeZ[0] == 0) core.sizeZ[0] = 1;
+    if (core.sizeT[0] == 0) core.sizeT[0] = 1;
+
+    if (core.sizeZ[0] == 1 && core.sizeT[0] == 1) {
+      core.sizeZ[0] = ifds.length;
+    }
+
+    // default values
+    addMeta("frames", "" + core.sizeZ[0]);
+    addMeta("channels", "" + super.getSizeC());
+    addMeta("slices", "" + core.sizeT[0]);
+
+    // parse the description to get channels, slices and times where applicable
+    String descr = (String) TiffTools.getIFDValue(ifds[0],
+      TiffTools.IMAGE_DESCRIPTION);
+    metadata.remove("Comment");
+    if (descr != null) {
+      StringTokenizer tokenizer = new StringTokenizer(descr, "\n");
+      while (tokenizer.hasMoreTokens()) {
+        String token = tokenizer.nextToken();
+        String label = token.substring(0, token.indexOf("="));
+        String data = token.substring(token.indexOf("=") + 1);
+        addMeta(label, data);
+        if (label.equals("channels")) core.sizeC[0] = Integer.parseInt(data);
+        else if (label.equals("frames")) core.sizeZ[0] = Integer.parseInt(data);
+        else if (label.equals("slices")) core.sizeT[0] = Integer.parseInt(data);
+      }
+    }
+
+    if (isRGB() && core.sizeC[0] != 3) core.sizeC[0] *= 3;
+
+    core.currentOrder[0] = "XY";
+
+    int maxNdx = 0, max = 0;
+    int[] dims = {core.sizeZ[0], core.sizeC[0], core.sizeT[0]};
+    String[] axes = {"Z", "C", "T"};
+
+    for (int i=0; i<dims.length; i++) {
+      if (dims[i] > max) {
+        max = dims[i];
+        maxNdx = i;
+      }
+    }
+
+    core.currentOrder[0] += axes[maxNdx];
+
+    if (maxNdx != 1) {
+      if (core.sizeC[0] > 1) {
+        core.currentOrder[0] += "C";
+        core.currentOrder[0] += (maxNdx == 0 ? axes[2] : axes[0]);
+      }
+      else core.currentOrder[0] += (maxNdx == 0 ? axes[2] : axes[0]) + "C";
+    }
+    else {
+      if (core.sizeZ[0] > core.sizeT[0]) core.currentOrder[0] += "ZT";
+      else core.currentOrder[0] += "TZ";
+    }
+  }
+
+}
diff --git a/loci/formats/in/SlidebookReader.java b/loci/formats/in/SlidebookReader.java
new file mode 100644
index 0000000..e018926
--- /dev/null
+++ b/loci/formats/in/SlidebookReader.java
@@ -0,0 +1,309 @@
+//
+// SlidebookReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.Vector;
+
+import loci.formats.*;
+
+/**
+ * SlidebookReader is the file format reader for 3I Slidebook files.
+ * The strategies employed by this reader are highly suboptimal, as we
+ * have very little information on the Slidebook format.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/SlidebookReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/SlidebookReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class SlidebookReader extends FormatReader {
+
+  // -- Fields --
+
+  private Vector metadataOffsets;
+  private Vector pixelOffsets;
+  private Vector pixelLengths;
+
+  // -- Constructor --
+
+  /** Constructs a new Slidebook reader. */
+  public SlidebookReader() { super("Olympus Slidebook", "sld"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 8) return false;
+    return block[0] == 0x6c && block[1] == 0 && block[2] == 0 &&
+      block[3] == 1 && block[4] == 0x49 && block[5] == 0x49 && block[6] == 0;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int plane = core.sizeX[series] * core.sizeY[series] * 2;
+
+    long offset = ((Long) pixelOffsets.get(series)).longValue() + plane * no;
+    in.seek(offset);
+    in.read(buf);
+    return buf;
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("SlidebookReader.initFile(" + id + ")");
+    super.initFile(id);
+    in = new RandomAccessStream(id);
+    in.order(true);
+
+    status("Finding offsets to pixel data");
+
+    in.skipBytes(4);
+    core.littleEndian[0] = in.read() == 0x49;
+
+    metadataOffsets = new Vector();
+    pixelOffsets = new Vector();
+    pixelLengths = new Vector();
+
+    in.seek(0);
+
+    while (in.getFilePointer() < in.length() - 8) {
+      in.skipBytes(4);
+      int checkOne = in.read();
+      int checkTwo = in.read();
+      if (checkOne == 'I' && checkTwo == 'I') {
+        metadataOffsets.add(new Long(in.getFilePointer() - 6));
+        if (in.read() == 0) in.skipBytes(249);
+        else in.skipBytes(121);
+      }
+      else {
+        String s = null;
+        long fp = in.getFilePointer() - 6;
+        in.seek(fp);
+        int len = in.read();
+        if (len > 0 && len <= 32) {
+          byte[] b = new byte[len];
+          in.read(b);
+          s = new String(b);
+        }
+
+        if (s != null && s.indexOf("Annotation") != -1) {
+          if (s.equals("CTimelapseAnnotation")) {
+            in.skipBytes(41);
+            if (in.read() == 0) in.skipBytes(10);
+            else in.seek(in.getFilePointer() - 1);
+          }
+          else if (s.equals("CIntensityBarAnnotation")) {
+            in.skipBytes(56);
+            int n = in.read();
+            while (n == 0 || n < 6 || n > 0x80) n = in.read();
+            in.seek(in.getFilePointer() - 1);
+          }
+          else if (s.equals("CCubeAnnotation")) {
+            in.skipBytes(66);
+            int n = in.read();
+            if (n != 0) in.seek(in.getFilePointer() - 1);
+          }
+          else if (s.equals("CScaleBarAnnotation")) {
+            in.skipBytes(52);
+          }
+        }
+        else if (s != null && s.indexOf("Decon") != -1) {
+          in.seek(fp);
+          while (in.read() != ']');
+        }
+        else {
+          in.seek(fp);
+          pixelOffsets.add(new Long(fp));
+          try {
+            byte[] buf = new byte[8192];
+            boolean found = false;
+            int n = in.read(buf);
+
+            while (!found && in.getFilePointer() < in.length()) {
+              for (int i=0; i<buf.length-6; i++) {
+                if (buf[i] == 'h' && buf[i+4] == 'I' && buf[i+5] == 'I') {
+                  found = true;
+                  in.seek(in.getFilePointer() - n + i - 20);
+                  break;
+                }
+              }
+              if (!found) {
+                byte[] tmp = buf;
+                buf = new byte[8192];
+                System.arraycopy(tmp, tmp.length - 20, buf, 0, 20);
+                n = in.read(buf, 20, buf.length - 20);
+              }
+            }
+
+            if (in.getFilePointer() <= in.length()) {
+              pixelLengths.add(new Long(in.getFilePointer() - fp));
+            }
+            else pixelOffsets.remove(pixelOffsets.size() - 1);
+          }
+          catch (EOFException e) {
+            pixelOffsets.remove(pixelOffsets.size() - 1);
+          }
+        }
+      }
+    }
+
+    core = new CoreMetadata(pixelOffsets.size() - 1);
+
+    status("Determining dimensions");
+
+    // determine total number of pixel bytes
+
+    long pixelBytes = 0;
+    for (int i=0; i<pixelLengths.size(); i++) {
+      pixelBytes += ((Long) pixelLengths.get(i)).longValue();
+    }
+
+    // try to find the width and height
+    int iCount = 0;
+    int hCount = 0;
+    int uCount = 0;
+    for (int i=0; i<metadataOffsets.size(); i++) {
+      long off = ((Long) metadataOffsets.get(i)).longValue();
+      in.seek(off);
+      int n = in.read();
+      if (n == 'i') {
+        in.skipBytes(79);
+        core.sizeX[0] = in.readShort();
+        core.sizeY[0] = in.readShort();
+        iCount++;
+      }
+      else if (n == 'h') hCount++;
+      else if (n == 'u') uCount++;
+    }
+
+    core.rgb[0] = false;
+    core.sizeC[0] = iCount < 5 ? iCount : 1;
+
+    if (core.sizeC[0] == 0) core.sizeC[0] = 1;
+    if (core.sizeZ[0] == 0) core.sizeZ[0] = 1;
+    if (core.sizeT[0] == 0) core.sizeT[0] = 1;
+
+    if (core.sizeX[0] * core.sizeY[0] * 2 * core.sizeC[0] *
+      core.sizeZ[0] * core.sizeT[0] != pixelBytes && hCount > 0)
+    {
+      core.sizeZ[0] = hCount / core.sizeC[0];
+    }
+
+    int n = core.sizeZ[0] * core.sizeC[0] * core.sizeT[0];
+
+    if (uCount == core.sizeZ[0] * core.sizeT[0]) uCount = n;
+    if (uCount < core.sizeZ[0] * core.sizeT[0] * core.sizeC[0]) {
+      int planesPerMontage = (n * 2) / uCount;
+
+      while (planesPerMontage > 1) {
+        core.sizeY[0] /= 2;
+        planesPerMontage /= 2;
+        if (planesPerMontage > 1) {
+          core.sizeX[0] /= 2;
+          planesPerMontage /= 2;
+        }
+      }
+      if (core.sizeC[0] == 1) core.sizeC[0] = 2;
+      else core.sizeT[0] *= 2;
+    }
+    else if (uCount > core.sizeZ[0] * core.sizeT[0] * core.sizeC[0]) {
+      int planesPerMontage =
+        (int) ((core.sizeX[0] * core.sizeY[0] * 2) / (pixelBytes / uCount));
+      if (planesPerMontage % 2 != 0) planesPerMontage++;
+      if (planesPerMontage == 2) planesPerMontage += 2;
+      if (planesPerMontage == 0) planesPerMontage++;
+
+      int plane = core.sizeX[0] * core.sizeY[0];
+      while (uCount * core.sizeC[0] * (plane*2 / planesPerMontage) <
+        (pixelBytes - plane))
+      {
+        uCount++;
+      }
+
+      while (planesPerMontage > 1 &&
+        core.sizeX[0] * core.sizeY[0] * 2 * uCount > pixelBytes)
+      {
+        core.sizeY[0] /= 2;
+        planesPerMontage /= 2;
+        if (planesPerMontage > 1 &&
+          core.sizeX[0] * core.sizeY[0] * 2 * uCount > pixelBytes)
+        {
+          core.sizeX[0] /= 2;
+          planesPerMontage /= 2;
+        }
+        else planesPerMontage = 1;
+      }
+      core.sizeZ[0] = uCount;
+    }
+
+    // couldn't find the dimensions; these are reasonable guesses
+    if (core.sizeX[0] == 0) core.sizeX[0] = 512;
+    if (core.sizeY[0] == 0) core.sizeY[0] = 512;
+
+    for (int i=0; i<core.sizeX.length; i++) {
+      core.sizeX[i] = core.sizeX[0];
+      core.sizeY[i] = core.sizeY[0];
+      core.currentOrder[i] = "XYZCT";
+      core.pixelType[i] = FormatTools.UINT16;
+      core.littleEndian[i] = true;
+
+      core.sizeC[i] = core.sizeC[0];
+      core.sizeT[i] = core.sizeT[0];
+      long len = ((Long) pixelLengths.get(i)).longValue();
+      core.sizeZ[i] =
+        (int) (len / (core.sizeX[i] * core.sizeY[i] * 2 * core.sizeC[i]));
+
+      core.imageCount[i] = core.sizeC[i] * core.sizeZ[i] * core.sizeT[i];
+
+      core.indexed[i] = false;
+      core.falseColor[i] = false;
+      core.metadataComplete[i] = true;
+    }
+
+    MetadataStore store = getMetadataStore();
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeX.length; i++) {
+      Integer ii = new Integer(i);
+      store.setImage(currentId, null, null, ii);
+      for (int j=0; j<core.sizeC[i]; j++) {
+        store.setLogicalChannel(j, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, ii);
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/TCSReader.java b/loci/formats/in/TCSReader.java
new file mode 100644
index 0000000..49af049
--- /dev/null
+++ b/loci/formats/in/TCSReader.java
@@ -0,0 +1,155 @@
+//
+// TCSReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.text.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * TCSReader is the file format reader for Leica TCS TIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/TCSReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/TCSReader.java">SVN</a></dd></dl>
+ */
+public class TCSReader extends BaseTiffReader {
+
+  // -- Constructor --
+
+  public TCSReader() {
+    super("Leica TCS TIFF", new String[] {"tif", "tiff"});
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
+  public boolean isThisType(String name, boolean open) {
+    if (!super.isThisType(name, open)) return false; // check extension
+    if (!open) return true; // not allowed to check the file contents
+
+    // just checking the filename isn't enough to differentiate between
+    // Leica TCS and regular TIFF; open the file and check more thoroughly
+    try {
+      RandomAccessStream ras = new RandomAccessStream(name);
+      Hashtable ifd = TiffTools.getFirstIFD(ras);
+      ras.close();
+      if (ifd == null) return false;
+
+      String document = (String) ifd.get(new Integer(TiffTools.DOCUMENT_NAME));
+      if (document == null) return false;
+      return document.startsWith("CHANNEL");
+    }
+    catch (IOException e) { return false; }
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+
+    int[] ch = new int[ifds.length];
+    int[] idx = new int[ifds.length];
+    long[] stamp = new long[ifds.length];
+
+    int channelCount = 0;
+    SimpleDateFormat fmt = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss.SSS");
+
+    for (int i=0; i<ifds.length; i++) {
+      String document =
+        (String) ifds[i].get(new Integer(TiffTools.DOCUMENT_NAME));
+
+      int index = document.indexOf("INDEX");
+      String c = document.substring(8, index).trim();
+      ch[i] = Integer.parseInt(c);
+      if (ch[i] > channelCount) channelCount = ch[i];
+
+      String n = document.substring(index + 6,
+        document.indexOf(" ", index + 6)).trim();
+      idx[i] = Integer.parseInt(n);
+
+      String date = document.substring(document.indexOf(" ", index + 6),
+        document.indexOf("FORMAT")).trim();
+      stamp[i] = fmt.parse(date, new ParsePosition(0)).getTime();
+    }
+
+    core.sizeT[0] = 0;
+    core.currentOrder[0] = core.rgb[0] ? "XYC" : "XY";
+
+    // determine the axis sizes and ordering
+    boolean unique = true;
+    for (int i=0; i<stamp.length; i++) {
+      for (int j=i+1; j<stamp.length; j++) {
+        if (stamp[j] == stamp[i]) {
+          unique = false;
+          break;
+        }
+      }
+      if (unique) {
+        core.sizeT[0]++;
+        if (core.currentOrder[0].indexOf("T") < 0) core.currentOrder[0] += "T";
+      }
+      else if (i > 0) {
+        if ((ch[i] != ch[i - 1]) && core.currentOrder[0].indexOf("C") < 0) {
+          core.currentOrder[0] += "C";
+        }
+        else if (core.currentOrder[0].indexOf("Z") < 0) {
+          core.currentOrder[0] += "Z";
+        }
+      }
+      unique = true;
+    }
+
+    if (core.currentOrder[0].indexOf("Z") < 0) core.currentOrder[0] += "Z";
+    if (core.currentOrder[0].indexOf("C") < 0) core.currentOrder[0] += "C";
+    if (core.currentOrder[0].indexOf("T") < 0) core.currentOrder[0] += "T";
+
+    if (core.sizeT[0] == 0) core.sizeT[0] = 1;
+    if (channelCount == 0) channelCount = 1;
+    core.sizeZ[0] = ifds.length / (core.sizeT[0] * channelCount);
+    core.sizeC[0] *= channelCount;
+    core.imageCount[0] = core.sizeZ[0] * core.sizeT[0] * channelCount;
+
+    // cut up comment
+
+    String comment = (String) getMeta("Comment");
+    if (comment != null && comment.startsWith("[")) {
+      StringTokenizer st = new StringTokenizer(comment, "\n");
+      while (st.hasMoreTokens()) {
+        String token = st.nextToken();
+        if (!token.startsWith("[")) {
+          int eq = token.indexOf("=");
+          String key = token.substring(0, eq);
+          String value = token.substring(eq + 1);
+          addMeta(key, value);
+        }
+      }
+      metadata.remove("Comment");
+    }
+  }
+
+}
diff --git a/loci/formats/in/TiffReader.java b/loci/formats/in/TiffReader.java
new file mode 100644
index 0000000..2fb4021
--- /dev/null
+++ b/loci/formats/in/TiffReader.java
@@ -0,0 +1,106 @@
+//
+// TiffReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.IOException;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * TiffReader is the file format reader for TIFF files, including OME-TIFF.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/TiffReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/TiffReader.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class TiffReader extends BaseTiffReader {
+
+  // -- Constructor --
+
+  /** Constructs a new Tiff reader. */
+  public TiffReader() {
+    super("Tagged Image File Format", new String[] {"tif", "tiff"});
+  }
+
+  // -- Internal TiffReader API methods --
+
+  /**
+   * Allows a class which is delegating parsing responsibility to
+   * <code>TiffReader</code> the ability to affect the <code>sizeZ</code> value
+   * that is inserted into the metadata store.
+   * @param zSize the number of optical sections to use when making a call to
+   * {@link loci.formats.MetadataStore#setPixels(Integer, Integer, Integer,
+   *   Integer, Integer, Integer, Boolean, String, Integer, Integer)}.
+   */
+  protected void setSizeZ(int zSize) {
+    if (core.sizeZ == null) core.sizeZ = new int[1];
+    core.sizeZ[0] = zSize;
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initStandardMetadata() */
+  protected void initStandardMetadata() throws FormatException, IOException {
+    super.initStandardMetadata();
+    String comment = (String) getMeta("Comment");
+
+    status("Checking comment style");
+
+    if (ifds.length > 1) core.orderCertain[0] = false;
+
+    // check for ImageJ-style TIFF comment
+    boolean ij = comment != null && comment.startsWith("ImageJ=");
+    if (ij) {
+      int nl = comment.indexOf("\n");
+      put("ImageJ", nl < 0 ? comment.substring(7) : comment.substring(7, nl));
+      metadata.remove("Comment");
+    }
+
+    // check for MetaMorph-style TIFF comment
+    boolean metamorph = comment != null && getMeta("Software") != null &&
+      ((String) getMeta("Software")).indexOf("MetaMorph") != -1;
+    put("MetaMorph", metamorph ? "yes" : "no");
+
+    if (metamorph) {
+      // parse key/value pairs
+      StringTokenizer st = new StringTokenizer(comment, "\n");
+      while (st.hasMoreTokens()) {
+        String line = st.nextToken();
+        int colon = line.indexOf(":");
+        if (colon < 0) {
+          addMeta("Comment", line);
+          continue;
+        }
+        String key = line.substring(0, colon);
+        String value = line.substring(colon + 1);
+        addMeta(key, value);
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/VisitechReader.java b/loci/formats/in/VisitechReader.java
new file mode 100644
index 0000000..574607e
--- /dev/null
+++ b/loci/formats/in/VisitechReader.java
@@ -0,0 +1,208 @@
+//
+// VisitechReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * VisitechReader is the file format reader for Visitech XYS files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/VisitechReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/VisitechReader.java">SVN</a></dd></dl>
+ */
+public class VisitechReader extends FormatReader {
+
+  // -- Fields --
+
+  /** Files in this dataset. */
+  private Vector files;
+
+  // -- Constructor --
+
+  public VisitechReader() {
+    super("Visitech XYS", new String[] {"xys", "html"});
+  }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    int plane = core.sizeX[0] * core.sizeY[0] *
+      FormatTools.getBytesPerPixel(core.pixelType[0]);
+
+    int div = core.sizeZ[0] * core.sizeT[0];
+    int fileIndex = no / div;
+    int planeIndex = no % div;
+
+    String file = (String) files.get(fileIndex);
+    RandomAccessStream s = new RandomAccessStream(file);
+    s.skipBytes(374);
+    while (s.read() != (byte) 0xf0);
+    s.skipBytes((plane + 164) * planeIndex + 1);
+    s.read(buf);
+    s.close();
+    return buf;
+  }
+
+  /* @see loci.formats.IFormatReader#getUsedFiles() */
+  public String[] getUsedFiles() {
+    if (files == null) return new String[0];
+    return (String[]) files.toArray(new String[0]);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    super.initFile(id);
+
+    // first, make sure we have the HTML file
+
+    if (!id.toLowerCase().endsWith("html")) {
+      Location file = new Location(id).getAbsoluteFile();
+      String path = file.exists() ? file.getPath() : id;
+      int ndx = path.lastIndexOf(File.separator);
+      String base = path.substring(ndx + 1, path.indexOf(" ", ndx));
+
+      String suffix = " Report.html";
+      currentId = null;
+      initFile(file.exists() ? new Location(file.getParent(),
+        base + suffix).getAbsolutePath() : base + suffix);
+      return;
+    }
+
+    // parse the HTML file
+
+    in = new RandomAccessStream(id);
+    String s = in.readString((int) in.length());
+
+    // strip out "style", "a", and "script" tags
+
+    s = s.replaceAll("<[bB][rR]>", "\n");
+    s = s.replaceAll("<[sS][tT][yY][lL][eE]\\p{ASCII}*?" +
+      "[sS][tT][yY][lL][eE]>", "");
+    s = s.replaceAll("<[sS][cC][rR][iI][pP][tT]\\p{ASCII}*?" +
+      "[sS][cC][rR][iI][pP][tT]>", "");
+
+    StringTokenizer st = new StringTokenizer(s, "\n");
+    String token = null, key = null, value = null;
+    while (st.hasMoreTokens()) {
+      token = st.nextToken().trim();
+
+      if ((token.startsWith("<") && !token.startsWith("</")) ||
+        token.indexOf("pixels") != -1)
+      {
+        token = token.replaceAll("<.*?>", "");
+        int ndx = token.indexOf(":");
+
+        if (ndx != -1) {
+          key = token.substring(0, ndx).trim();
+          value = token.substring(ndx + 1).trim();
+
+          if (key.equals("Number of steps")) {
+            core.sizeZ[0] = Integer.parseInt(value);
+          }
+          else if (key.equals("Image bit depth")) {
+            int bits = Integer.parseInt(value);
+            while ((bits % 8) != 0) bits++;
+            switch (bits) {
+              case 16:
+                core.pixelType[0] = FormatTools.UINT16;
+                break;
+              case 32:
+                core.pixelType[0] = FormatTools.UINT32;
+                break;
+              default: core.pixelType[0] = FormatTools.UINT8;
+            }
+          }
+          else if (key.equals("Image dimensions")) {
+            int n = value.indexOf(",");
+            core.sizeX[0] = Integer.parseInt(value.substring(1, n).trim());
+            core.sizeY[0] = Integer.parseInt(value.substring(n + 1,
+              value.length() - 1).trim());
+          }
+
+          addMeta(key, value);
+        }
+
+        if (token.indexOf("pixels") != -1) {
+          core.sizeC[0]++;
+          core.imageCount[0] +=
+            Integer.parseInt(token.substring(0, token.indexOf(" ")));
+        }
+      }
+    }
+
+    core.sizeT[0] = core.imageCount[0] / (core.sizeZ[0] * core.sizeC[0]);
+    core.rgb[0] = false;
+    core.currentOrder[0] = "XYZTC";
+    core.interleaved[0] = false;
+    core.littleEndian[0] = true;
+    core.indexed[0] = false;
+    core.falseColor[0] = false;
+    core.metadataComplete[0] = true;
+
+    // find pixels files - we think there is one channel per file
+
+    files = new Vector();
+
+    int ndx = currentId.lastIndexOf(File.separator);
+    String base = currentId.substring(ndx + 1, currentId.indexOf(" ", ndx));
+
+    File f = new File(currentId).getAbsoluteFile();
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      files.add((f.exists() ? f.getParent() + File.separator : "") + base +
+        " " + (i + 1) + ".xys");
+    }
+    files.add(currentId);
+
+    MetadataStore store = getMetadataStore();
+    store.setImage(currentId, null, null, null);
+    FormatTools.populatePixels(store, this);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null);
+    }
+
+  }
+
+}
diff --git a/loci/formats/in/ZeissLSMReader.java b/loci/formats/in/ZeissLSMReader.java
new file mode 100644
index 0000000..3fa7a07
--- /dev/null
+++ b/loci/formats/in/ZeissLSMReader.java
@@ -0,0 +1,697 @@
+//
+// ZeissLSMReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.Hashtable;
+import loci.formats.*;
+
+/**
+ * ZeissLSMReader is the file format reader for Zeiss LSM files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ZeissLSMReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ZeissLSMReader.java">SVN</a></dd></dl>
+ *
+ * @author Eric Kjellman egkjellman at wisc.edu
+ * @author Melissa Linkert linkert at wisc.edu
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public class ZeissLSMReader extends BaseTiffReader {
+
+  // -- Constants --
+
+  /** Tag identifying a Zeiss LSM file. */
+  private static final int ZEISS_ID = 34412;
+
+  // -- Constructor --
+
+  /** Constructs a new Zeiss LSM reader. */
+  public ZeissLSMReader() { super("Zeiss Laser-Scanning Microscopy", "lsm"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    if (block.length < 3) return false;
+    if (block[0] != TiffTools.LITTLE) return false; // denotes little-endian
+    if (block[1] != TiffTools.LITTLE) return false;
+    if (block[2] != TiffTools.MAGIC_NUMBER) return false; // denotes TIFF
+    if (block.length < 8) return true; // we have no way of verifying
+    int ifdlocation = DataTools.bytesToInt(block, 4, true);
+    if (ifdlocation + 1 > block.length) {
+      // no way of verifying this is a Zeiss file; it is at least a TIFF
+      return true;
+    }
+    else {
+      int ifdnumber = DataTools.bytesToInt(block, ifdlocation, 2, true);
+      for (int i=0; i<ifdnumber; i++) {
+        if (ifdlocation + 3 + (i * 12) > block.length) return true;
+        else {
+          int ifdtag = DataTools.bytesToInt(block,
+            ifdlocation + 2 + (i * 12), 2, true);
+          if (ifdtag == ZEISS_ID) return true; // absolutely a valid file
+        }
+      }
+      return false; // we went through the IFD; the ID wasn't found.
+    }
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    ifds = TiffTools.getIFDs(in);
+    TiffTools.getSamples(ifds[2*no], in, buf);
+    return swapIfRequired(buf);
+  }
+
+  /* @see loci.formats.IFormatReader#openThumbImage(int) */
+  public BufferedImage openThumbImage(int no)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    FormatTools.checkPlaneNumber(this, no);
+
+    if (2*no + 1 < ifds.length) return TiffTools.getImage(ifds[2*no + 1], in);
+    return super.openThumbImage(no);
+  }
+
+  // -- Internal BaseTiffReader API methods --
+
+  /* @see BaseTiffReader#initMetadata() */
+  protected void initMetadata() {
+    Hashtable ifd = ifds[0];
+
+    try {
+      boolean little = TiffTools.isLittleEndian(ifd);
+      in.order(little);
+
+      super.initMetadata();
+
+      // get TIF_CZ_LSMINFO structure
+      short[] s = TiffTools.getIFDShortArray(ifd, ZEISS_ID, true);
+      byte[] cz = new byte[s.length];
+      for (int i=0; i<s.length; i++) {
+        cz[i] = (byte) s[i];
+        if (cz[i] < 0) cz[i]++; // account for byte->short conversion
+      }
+
+      RandomAccessStream ras = new RandomAccessStream(cz);
+      ras.order(little);
+
+      put("MagicNumber", ras.readInt());
+      put("StructureSize", ras.readInt());
+      put("DimensionX", ras.readInt());
+      put("DimensionY", ras.readInt());
+
+      core.sizeZ[0] = ras.readInt();
+      int c = ras.readInt();
+      core.sizeT[0] = ras.readInt();
+
+      if (c > core.sizeC[0] || c != 1) core.sizeC[0] = c;
+      if (core.sizeC[0] == 0) core.sizeC[0]++;
+
+      while (core.imageCount[0] > core.sizeZ[0] * core.sizeC[0] * core.sizeT[0])
+      {
+        if (core.sizeZ[0] > core.sizeT[0]) core.sizeZ[0]++;
+        else core.sizeT[0]++;
+      }
+
+      while (core.imageCount[0] > core.sizeZ[0] * core.sizeT[0] *
+        getEffectiveSizeC())
+      {
+        core.imageCount[0]--;
+      }
+
+      put("DimensionZ", core.sizeZ[0]);
+      put("DimensionChannels", core.sizeC[0]);
+      put("DimensionTime", core.sizeT[0]);
+
+      int dataType = ras.readInt();
+      switch (dataType) {
+        case 1:
+          put("DataType", "8 bit unsigned integer");
+          core.pixelType[0] = FormatTools.UINT8;
+          break;
+        case 2:
+          put("DataType", "12 bit unsigned integer");
+          core.pixelType[0] = FormatTools.UINT16;
+          break;
+        case 5:
+          put("DataType", "32 bit float");
+          core.pixelType[0] = FormatTools.FLOAT;
+          break;
+        case 0:
+          put("DataType", "varying data types");
+          core.pixelType[0] = -1;
+          break;
+        default:
+          put("DataType", "8 bit unsigned integer");
+          core.pixelType[0] = -1;
+      }
+
+      if (core.pixelType[0] == -1) {
+        int[] bps = TiffTools.getBitsPerSample(ifd);
+        switch (bps[0]) {
+          case 16:
+            core.pixelType[0] = FormatTools.UINT16;
+            break;
+          case 32:
+            core.pixelType[0] = FormatTools.FLOAT;
+            break;
+          default:
+            core.pixelType[0] = FormatTools.UINT8;
+        }
+      }
+
+      put("ThumbnailX", ras.readInt());
+      put("ThumbnailY", ras.readInt());
+
+      put("VoxelSizeX", ras.readDouble());
+      put("VoxelSizeY", ras.readDouble());
+      put("VoxelSizeZ", ras.readDouble());
+
+      put("OriginX", ras.readDouble());
+      put("OriginY", ras.readDouble());
+      put("OriginZ", ras.readDouble());
+
+      int scanType = ras.readShort();
+      switch (scanType) {
+        case 0:
+          put("ScanType", "x-y-z scan");
+          core.currentOrder[0] = "XYZCT";
+          break;
+        case 1:
+          put("ScanType", "z scan (x-z plane)");
+          core.currentOrder[0] = "XYZCT";
+          break;
+        case 2:
+          put("ScanType", "line scan");
+          core.currentOrder[0] = "XYZCT";
+          break;
+        case 3:
+          put("ScanType", "time series x-y");
+          core.currentOrder[0] = "XYTCZ";
+          break;
+        case 4:
+          put("ScanType", "time series x-z");
+          core.currentOrder[0] = "XYZTC";
+          break;
+        case 5:
+          put("ScanType", "time series 'Mean of ROIs'");
+          core.currentOrder[0] = "XYTCZ";
+          break;
+        case 6:
+          put("ScanType", "time series x-y-z");
+          core.currentOrder[0] = "XYZTC";
+          break;
+        case 7:
+          put("ScanType", "spline scan");
+          core.currentOrder[0] = "XYCTZ";
+          break;
+        case 8:
+          put("ScanType", "spline scan x-z");
+          core.currentOrder[0] = "XYCZT";
+          break;
+        case 9:
+          put("ScanType", "time series spline plane x-z");
+          core.currentOrder[0] = "XYTCZ";
+          break;
+        case 10:
+          put("ScanType", "point mode");
+          core.currentOrder[0] = "XYZCT";
+          break;
+        default:
+          put("ScanType", "x-y-z scan");
+          core.currentOrder[0] = "XYZCT";
+      }
+
+      MetadataStore store = getMetadataStore();
+
+      FormatTools.populatePixels(store, this);
+      for (int i=0; i<core.sizeC[0]; i++) {
+        store.setLogicalChannel(i, null, null, null, null, null, null, null,
+          null, null, null, null, null, null, null, null, null, null, null,
+          null, null, null, null, null, null);
+      }
+
+      int spectralScan = ras.readShort();
+      switch (spectralScan) {
+        case 0:
+          put("SpectralScan", "no spectral scan");
+          break;
+        case 1:
+          put("SpectralScan", "acquired with spectral scan");
+          break;
+        default:
+          put("SpectralScan", "no spectral scan");
+      }
+
+      long type = ras.readInt();
+      switch ((int) type) {
+        case 0:
+          put("DataType2", "original scan data");
+          break;
+        case 1:
+          put("DataType2", "calculated data");
+          break;
+        case 2:
+          put("DataType2", "animation");
+          break;
+        default:
+          put("DataType2", "original scan data");
+      }
+
+      long overlayOffset = ras.readInt();
+      long inputLUTOffset = ras.readInt();
+      long outputLUTOffset = ras.readInt();
+      long channelColorsOffset = ras.readInt();
+
+      put("TimeInterval", ras.readDouble());
+
+      ras.skipBytes(12);
+      long timeStampOffset = ras.readInt();
+      long eventListOffset = ras.readInt();
+      long roiOffset = ras.readInt();
+      long bleachRoiOffset = ras.readInt();
+      ras.skipBytes(4);
+
+      put("DisplayAspectX", ras.readDouble());
+      put("DisplayAspectY", ras.readDouble());
+      put("DisplayAspectZ", ras.readDouble());
+      put("DisplayAspectTime", ras.readDouble());
+
+      long meanOfRoisOverlayOffset = ras.readInt();
+      long topoIsolineOverlayOffset = ras.readInt();
+      long topoProfileOverlayOffset = ras.readInt();
+      long linescanOverlayOffset = ras.readInt();
+
+      put("ToolbarFlags", ras.readInt());
+      ras.skipBytes(20);
+
+      // read referenced structures
+
+      if (overlayOffset != 0) {
+        parseOverlays(overlayOffset, "OffsetVectorOverlay", little);
+      }
+
+      if (inputLUTOffset != 0) {
+        parseSubBlocks(inputLUTOffset, "OffsetInputLut", little);
+      }
+
+      if (outputLUTOffset != 0) {
+        parseSubBlocks(outputLUTOffset, "OffsetOutputLut", little);
+      }
+
+      if (channelColorsOffset != 0) {
+        in.seek(channelColorsOffset + 4);
+        int numColors = in.readInt();
+        int numNames = in.readInt();
+
+        if (numColors > core.sizeC[0]) {
+          in.seek(channelColorsOffset - 2);
+          in.order(!little);
+          in.readInt();
+          numColors = in.readInt();
+          numNames = in.readInt();
+        }
+
+        long namesOffset = in.readInt() + channelColorsOffset;
+        in.skipBytes(4);
+
+        // read in the intensity value for each color
+
+        if (namesOffset >= 0) {
+          in.seek(namesOffset);
+          for (int i=0; i<numColors; i++) put("Intensity" + i, in.readInt());
+        }
+
+        // read in the channel names
+
+        for (int i=0; i<numNames; i++) {
+          // we want to read until we find a null char
+          StringBuffer sb = new StringBuffer();
+          char current = (char) in.read();
+          while (current != 0) {
+            if (current < 128) sb.append(current);
+            current = (char) in.read();
+          }
+          if (sb.length() <= 128) put("ChannelName" + i, sb.toString());
+          else put("ChannelName" + i, "");
+        }
+      }
+
+      if (timeStampOffset != 0) {
+        in.seek(timeStampOffset + 4);
+        int numberOfStamps = in.readInt();
+        for (int i=0; i<numberOfStamps; i++) {
+          put("TimeStamp" + i, in.readDouble());
+        }
+      }
+
+      if (eventListOffset != 0) {
+        in.seek(eventListOffset);
+        in.skipBytes(4); // skipping the block size
+        int numEvents = in.readInt();
+        for (int i=0; i<numEvents; i++) {
+          int size = in.readInt();
+          double eventTime = in.readDouble();
+          int eventType = in.readInt();
+          byte[] b = new byte[size - 16];
+          in.read(b);
+          put("Event" + i + " Time", eventTime);
+          put("Event" + i + " Type", eventType);
+          put("Event" + i + " Description", new String(b));
+        }
+      }
+
+      if (roiOffset != 0) parseOverlays(roiOffset, "ROIOffset", little);
+
+      if (bleachRoiOffset != 0) {
+        parseOverlays(bleachRoiOffset, "BleachROIOffset", little);
+      }
+
+      if (meanOfRoisOverlayOffset != 0) {
+        parseOverlays(meanOfRoisOverlayOffset,
+          "OffsetMeanOfRoisOverlay", little);
+      }
+
+      if (topoIsolineOverlayOffset != 0) {
+        parseOverlays(topoIsolineOverlayOffset,
+          "OffsetTopoIsolineOverlay", little);
+      }
+
+      if (topoProfileOverlayOffset != 0) {
+        parseOverlays(topoProfileOverlayOffset,
+          "OffsetTopoProfileOverlay", little);
+      }
+
+      if (linescanOverlayOffset != 0) {
+        parseOverlays(linescanOverlayOffset, "OffsetLinescanOverlay", little);
+      }
+    }
+    catch (FormatException exc) {
+      if (debug) trace(exc);
+    }
+    catch (IOException exc) {
+      if (debug) trace(exc);
+    }
+
+    Object pixelSizeX = getMeta("VoxelSizeX");
+    Object pixelSizeY = getMeta("VoxelSizeY");
+    Object pixelSizeZ = getMeta("VoxelSizeZ");
+
+    Float pixX = new Float(pixelSizeX == null ? "0" : pixelSizeX.toString());
+    Float pixY = new Float(pixelSizeY == null ? "0" : pixelSizeY.toString());
+    Float pixZ = new Float(pixelSizeZ == null ? "0" : pixelSizeZ.toString());
+
+    MetadataStore store = getMetadataStore();
+    store.setDimensions(pixX, pixY, pixZ, null, null, null);
+
+    // see if we have an associated MDB file
+
+    Location dir = new Location(currentId).getAbsoluteFile().getParentFile();
+    String[] dirList = dir.list();
+
+    for (int i=0; i<dirList.length; i++) {
+      if (dirList[i].toLowerCase().endsWith(".mdb")) {
+        try {
+          MDBParser.parseDatabase((new Location(dir.getPath(),
+            dirList[i])).getAbsolutePath(), metadata);
+        }
+        catch (FormatException exc) {
+          if (debug) trace(exc);
+        }
+        i = dirList.length;
+      }
+    }
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("ZeissLSMReader.initFile(" + id + ")");
+    super.initFile(id);
+
+    // go through the IFD hashtable array and
+    // remove anything with NEW_SUBFILE_TYPE = 1
+    // NEW_SUBFILE_TYPE = 1 indicates that the IFD
+    // contains a thumbnail image
+
+    status("Removing thumbnails");
+
+    int numThumbs = 0;
+    long prevOffset = 0;
+    byte[] b = new byte[48];
+    byte[] c = new byte[48];
+    for (int i=0; i<ifds.length; i++) {
+      long subFileType = TiffTools.getIFDLongValue(ifds[i],
+        TiffTools.NEW_SUBFILE_TYPE, true, 0);
+      long[] offsets = TiffTools.getStripOffsets(ifds[i]);
+
+      if (subFileType == 1) {
+        ifds[i] = null;
+        numThumbs++;
+      }
+      else if (i > 0) {
+        // make sure that we don't grab the thumbnail by accident
+        // there's probably a better way to do this
+
+        in.seek(prevOffset);
+        in.read(b);
+        in.seek(offsets[0]);
+        in.read(c);
+
+        boolean equal = true;
+        for (int j=0; j<48; j++) {
+          if (b[j] != c[j]) {
+            equal = false;
+            j = 48;
+          }
+        }
+
+        if (equal) {
+          offsets[0] += (offsets[0] - prevOffset);
+          TiffTools.putIFDValue(ifds[i], TiffTools.STRIP_OFFSETS, offsets);
+        }
+      }
+      prevOffset = offsets[0];
+    }
+
+    // now copy ifds to a temp array so that we can get rid of
+    // any null entries
+
+    int ifdPointer = 0;
+    Hashtable[] tempIFDs = new Hashtable[ifds.length - numThumbs];
+    for (int i=0; i<tempIFDs.length; i++) {
+      if (ifds[ifdPointer] != null) {
+        tempIFDs[i] = ifds[ifdPointer];
+        ifdPointer++;
+      }
+      else {
+        while ((ifds[ifdPointer] == null) && ifdPointer < ifds.length) {
+          ifdPointer++;
+        }
+        tempIFDs[i] = ifds[ifdPointer];
+        ifdPointer++;
+      }
+    }
+
+    // reset numImages and ifds
+    core.imageCount[0] = tempIFDs.length;
+    ifds = tempIFDs;
+    initMetadata();
+    ifds = TiffTools.getIFDs(in);
+
+    if (ifds.length > 1) {
+      core.thumbSizeX[0] = TiffTools.getIFDIntValue(ifds[1],
+        TiffTools.IMAGE_WIDTH, false, 1);
+      core.thumbSizeY[0] = TiffTools.getIFDIntValue(ifds[1],
+        TiffTools.IMAGE_LENGTH, false, 1);
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Parses overlay-related fields. */
+  protected void parseOverlays(long data, String suffix, boolean little)
+    throws IOException
+  {
+    if (data == 0) return;
+
+    in.seek(data);
+
+    int nde = in.readInt();
+    put("NumberDrawingElements-" + suffix, nde);
+    int size = in.readInt();
+    int idata = in.readInt();
+    put("LineWidth-" + suffix, idata);
+    idata = in.readInt();
+    put("Measure-" + suffix, idata);
+    in.readDouble();
+    put("ColorRed-" + suffix, in.read());
+    put("ColorGreen-" + suffix, in.read());
+    put("ColorBlue-" + suffix, in.read());
+    in.read();
+
+    put("Valid-" + suffix, in.readInt());
+    put("KnotWidth-" + suffix, in.readInt());
+    put("CatchArea-" + suffix, in.readInt());
+
+    // some fields describing the font
+    put("FontHeight-" + suffix, in.readInt());
+    put("FontWidth-" + suffix, in.readInt());
+    put("FontEscapement-" + suffix, in.readInt());
+    put("FontOrientation-" + suffix, in.readInt());
+    put("FontWeight-" + suffix, in.readInt());
+    put("FontItalic-" + suffix, in.readInt());
+    put("FontUnderline-" + suffix, in.readInt());
+    put("FontStrikeOut-" + suffix, in.readInt());
+    put("FontCharSet-" + suffix, in.readInt());
+    put("FontOutPrecision-" + suffix, in.readInt());
+    put("FontClipPrecision-" + suffix, in.readInt());
+    put("FontQuality-" + suffix, in.readInt());
+    put("FontPitchAndFamily-" + suffix, in.readInt());
+    byte[] temp = new byte[64];
+    in.read(temp);
+    put("FontFaceName-" + suffix, new String(temp));
+
+    // some flags for measuring values of different drawing element types
+    put("ClosedPolyline-" + suffix, in.read());
+    put("OpenPolyline-" + suffix, in.read());
+    put("ClosedBezierCurve-" + suffix, in.read());
+    put("OpenBezierCurve-" + suffix, in.read());
+    put("ArrowWithClosedTip-" + suffix, in.read());
+    put("ArrowWithOpenTip-" + suffix, in.read());
+    put("Ellipse-" + suffix, in.read());
+    put("Circle-" + suffix, in.read());
+    put("Rectangle-" + suffix, in.read());
+    put("Line-" + suffix, in.read());
+    try {
+      int drawingEl = (size - 194) / nde;
+      for (int i=0; i<nde; i++) {
+        byte[] draw = new byte[drawingEl];
+        in.read(draw);
+        put("DrawingElement" + i + "-" + suffix, new String(draw));
+      }
+    }
+    catch (ArithmeticException exc) {
+      if (debug) trace(exc);
+    }
+  }
+
+  /** Parses subblock-related fields. */
+  protected void parseSubBlocks(long data, String suffix, boolean little)
+    throws IOException, FormatException
+  {
+    if (data == 0) return;
+
+    in.seek((int) data);
+
+    in.order(little);
+
+    long size = in.readInt();
+    if (size < 0) size += 4294967296L;
+    long numSubBlocks = in.readInt();
+    if (numSubBlocks < 0) numSubBlocks += 4294967296L;
+    put("NumSubBlocks-" + suffix, numSubBlocks);
+    long numChannels = in.readInt();
+    if (numChannels < 0) numChannels += 4294967296L;
+    put("NumChannels-" + suffix, numChannels);
+    data = in.readInt();
+    if (data < 0) data += 4294967296L;
+    put("LutType-" + suffix, data);
+    data = in.readInt();
+    if (data < 0) data += 4294967296L;
+    put("Advanced-" + suffix, data);
+    data = in.readInt();
+    if (data < 0) data += 4294967296L;
+    put("CurrentChannel-" + suffix, data);
+    in.skipBytes(36);
+
+    if (numSubBlocks > 100) numSubBlocks = 20;
+
+    for (int i=0; i<numSubBlocks; i++) {
+      data = in.readInt();
+      if (data < 0) data += 4294967296L;
+      put("Type" + i + "-" + suffix, data);
+      put("Size" + i + "-" + suffix, in.readInt());
+
+      switch ((int) data) {
+        case 1:
+          for (int j=0; j<numChannels; j++) {
+            put("GammaChannel" + j + "-" + i + "-" + suffix, in.readDouble());
+          }
+          break;
+        case 2:
+          for (int j=0; j<numChannels; j++) {
+            put("BrightnessChannel" + j + "-" + i + "-" + suffix,
+              in.readDouble());
+          }
+          break;
+
+        case 3:
+          for (int j=0; j<numChannels; j++) {
+            put("ContrastChannel" + j + "-" + i + "-" + suffix,
+              in.readDouble());
+          }
+          break;
+
+        case 4:
+          for (int j=0; j<numChannels; j++) {
+            put("RampStartXChannel" + j + "-" + i + "-" + suffix,
+              in.readDouble());
+            put("RampStartYChannel" + j + "-" + i + "-" + suffix,
+              in.readDouble());
+            put("RampEndXChannel" + j + "-" + i + "-" + suffix,
+              in.readDouble());
+            put("RampEndYChannel" + j + "-" + i + "-" + suffix,
+              in.readDouble());
+            j += 4;
+          }
+          break;
+
+        case 5:
+          // the specs are unclear as to how
+          // this subblock should be read, so I'm
+          // skipping it for the present
+          break;
+
+        case 6:
+          // also skipping this block for
+          // the moment
+          break;
+      }
+    }
+  }
+
+}
diff --git a/loci/formats/in/ZeissZVIReader.java b/loci/formats/in/ZeissZVIReader.java
new file mode 100644
index 0000000..fe64da4
--- /dev/null
+++ b/loci/formats/in/ZeissZVIReader.java
@@ -0,0 +1,1334 @@
+//
+// ZeissZVIReader.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.in;
+
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+import loci.formats.codec.JPEGCodec;
+
+/**
+ * ZeissZVIReader is the file format reader for Zeiss ZVI files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ZeissZVIReader.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ZeissZVIReader.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class ZeissZVIReader extends FormatReader {
+
+  // -- Constants --
+
+  private static final String NO_POI_MSG =
+    "Jakarta POI is required to read ZVI files. Please " +
+    "obtain poi-loci.jar from http://loci.wisc.edu/ome/formats.html";
+
+  // -- Static fields --
+
+  private LegacyZVIReader legacy = new LegacyZVIReader();
+  private static boolean noPOI = false;
+  private static ReflectedUniverse r = createReflectedUniverse();
+
+  private static ReflectedUniverse createReflectedUniverse() {
+    r = null;
+    try {
+      r = new ReflectedUniverse();
+      r.exec("import org.apache.poi.poifs.filesystem.POIFSFileSystem");
+      r.exec("import org.apache.poi.poifs.filesystem.DirectoryEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentEntry");
+      r.exec("import org.apache.poi.poifs.filesystem.DocumentInputStream");
+      r.exec("import java.util.Iterator");
+    }
+    catch (ReflectException exc) {
+      noPOI = true;
+      if (debug) LogTools.trace(exc);
+    }
+    return r;
+  }
+
+  // -- Fields --
+
+  /** Flag set to true if we need to use the legacy reader. */
+  private boolean needLegacy = false;
+
+  /** Number of bytes per pixel. */
+  private int bpp;
+
+  /** Hashtable containing the directory entry for each plane. */
+  private Hashtable pixels;
+
+  /**
+   * Hashtable containing the document name for each plane,
+   * indexed by the plane number.
+   */
+  private Hashtable names;
+
+  /** Vector containing Z indices. */
+  private Vector zIndices;
+
+  /** Vector containing C indices. */
+  private Vector cIndices;
+
+  /** Vector containing T indices. */
+  private Vector tIndices;
+
+  private Hashtable offsets;
+
+  private int zIndex = -1, cIndex = -1, tIndex = -1;
+
+  private boolean isTiled;
+  private int tileRows, tileColumns;
+  private boolean isJPEG;
+
+  // -- Constructor --
+
+  /** Constructs a new ZeissZVI reader. */
+  public ZeissZVIReader() { super("Zeiss Vision Image (ZVI)", "zvi"); }
+
+  // -- IFormatReader API methods --
+
+  /* @see loci.formats.IFormatReader#isThisType(byte[]) */
+  public boolean isThisType(byte[] block) {
+    // all of our samples begin with 0xd0cf11e0
+    return (block[0] == 0xd0 && block[1] == 0xcf &&
+      block[2] == 0x11 && block[3] == 0xe0);
+  }
+
+  /* @see loci.formats.IFormatReader#setMetadataStore(MetadataStore) */
+  public void setMetadataStore(MetadataStore store) {
+    FormatTools.assertId(currentId, false, 1);
+    super.setMetadataStore(store);
+    if (noPOI || needLegacy) legacy.setMetadataStore(store);
+  }
+
+  /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
+  public byte[] openBytes(int no, byte[] buf)
+    throws FormatException, IOException
+  {
+    FormatTools.assertId(currentId, true, 1);
+    if (noPOI || needLegacy) return legacy.openBytes(no, buf);
+    FormatTools.checkPlaneNumber(this, no);
+    FormatTools.checkBufferSize(this, buf.length);
+
+    try {
+      int tiles = tileRows * tileColumns;
+      if (tiles == 0) {
+        tiles = 1;
+        tileRows = 1;
+        tileColumns = 1;
+      }
+      int start = no * tiles;
+
+      int bytes =
+        FormatTools.getBytesPerPixel(core.pixelType[0]) * getRGBChannelCount();
+      int ex = core.sizeX[0] / tileColumns;
+      int ey = core.sizeY[0] / tileRows;
+      int row = ex * bytes;
+
+      int[] tileOrder = new int[tiles];
+      if (tiles == 1) tileOrder[0] = no;
+      else {
+        int p = 0;
+        for (int r=0; r<tileRows; r++) {
+          for (int c=0; c<tileColumns; c++) {
+            int n = (no % core.sizeC[0]) +
+              (tiles * core.sizeC[0] * (no / core.sizeC[0]));
+            if (r % 2 == 0) {
+              tileOrder[p] = p*core.sizeC[0] + n;
+            }
+            else {
+              tileOrder[p] = (tileColumns - c)*core.sizeC[0];
+              if (p > 0 && c == 0) tileOrder[p] += tileOrder[p - 1];
+              else if (c > 0) tileOrder[p] = tileOrder[p - 1] - core.sizeC[0];
+            }
+            p++;
+          }
+        }
+      }
+
+      for (int i=start; i<start+tiles; i++) {
+        Integer ii = new Integer(tileOrder[i - start]);
+        Object directory = pixels.get(ii);
+        String name = (String) names.get(ii);
+
+        r.setVar("dir", directory);
+        r.setVar("entryName", name);
+        r.exec("document = dir.getEntry(entryName)");
+        r.exec("dis = new DocumentInputStream(document)");
+        r.exec("numBytes = dis.available()");
+        r.setVar("skipBytes", ((Integer) offsets.get(ii)).longValue());
+        r.exec("blah = dis.skip(skipBytes)");
+        r.setVar("data", buf);
+
+        int xf = ((i - start) % tileColumns) * ex * bytes;
+        int yf = ((i - start) / tileRows) * ey;
+        int offset = yf*core.sizeX[0]*bytes + xf;
+        for (int y=0; y<ey; y++) {
+          r.setVar("offset", offset);
+          r.setVar("len", row);
+          try {
+            r.exec("dis.read(data, offset, len)");
+          }
+          catch (ReflectException e) { }
+          offset += core.sizeX[0]*bytes;
+        }
+
+        if (isJPEG) {
+          JPEGCodec codec = new JPEGCodec();
+          buf = codec.decompress(buf);
+        }
+      }
+
+      if (bpp > 6) bpp = 1;
+      if (bpp == 3) {
+        // reverse bytes in groups of 3 to account for BGR storage
+        for (int i=0; i<buf.length; i+=3) {
+          byte b = buf[i + 2];
+          buf[i + 2] = buf[i];
+          buf[i] = b;
+        }
+      }
+      return buf;
+    }
+    catch (ReflectException e) {
+      needLegacy = true;
+      return openBytes(no, buf);
+    }
+  }
+
+  /* @see loci.formats.IFormatReader#close(boolean) */
+  public void close(boolean fileOnly) throws IOException {
+    if (fileOnly) {
+      if (in != null) in.close();
+      if (legacy != null) legacy.close(fileOnly);
+    }
+    else close();
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    super.close();
+    needLegacy = false;
+
+    if (legacy != null) legacy.close();
+    pixels = null;
+    names = null;
+    offsets = null;
+
+    String[] vars = {"dirName", "root", "dir", "document", "dis",
+      "numBytes", "data", "fis", "fs", "iter", "isInstance", "isDocument",
+      "entry", "documentName", "entryName"};
+    for (int i=0; i<vars.length; i++) r.setVar(vars[i], null);
+  }
+
+  // -- Internal FormatReader API methods --
+
+  /* @see loci.formats.FormatReader#initFile(String) */
+  protected void initFile(String id) throws FormatException, IOException {
+    if (debug) debug("ZeissZVIReader.initFile(" + id + ")");
+    if (noPOI || needLegacy) {
+      legacy.setId(id);
+      core = legacy.getCoreMetadata();
+      return;
+    }
+    super.initFile(id);
+
+    pixels = new Hashtable();
+    names = new Hashtable();
+    offsets = new Hashtable();
+    zIndices = new Vector();
+    cIndices = new Vector();
+    tIndices = new Vector();
+
+    try {
+      in = new RandomAccessStream(id);
+
+      // Don't uncomment this block.  Even though OIBReader has something
+      // like this, it's really a bad idea here.  Every ZVI file we have *will*
+      // break if you uncomment it.
+      //
+      //if (in.length() % 4096 != 0) {
+      //  in.setExtend((4096 - (int) (in.length() % 4096)));
+      //}
+
+      r.setVar("fis", in);
+      r.exec("fs = new POIFSFileSystem(fis)");
+      r.exec("dir = fs.getRoot()");
+      parseDir(0, r.getVar("dir"));
+
+      status("Populating metadata");
+
+      core.rgb[0] = core.sizeC[0] > 1 &&
+        (core.sizeZ[0] * core.sizeC[0] * core.sizeT[0] != core.imageCount[0]);
+      core.littleEndian[0] = true;
+      core.interleaved[0] = !isJPEG;
+      core.indexed[0] = false;
+      core.falseColor[0] = false;
+      core.metadataComplete[0] = true;
+
+      core.sizeZ[0] = zIndices.size();
+      core.sizeT[0] = tIndices.size();
+
+      if (core.sizeC[0] != cIndices.size()) core.sizeC[0] *= cIndices.size();
+
+      core.imageCount[0] = core.sizeZ[0] * core.sizeT[0] *
+        (core.rgb[0] ? core.sizeC[0] / 3 : core.sizeC[0]);
+
+      if (isTiled) {
+        String zeroIndex = (String) getMeta("ImageTile Index 0");
+        String oneIndex = (String) getMeta("ImageTile Index 1");
+        if (zeroIndex == null || zeroIndex.equals("")) zeroIndex = null;
+        if (oneIndex == null || oneIndex.equals("")) oneIndex = null;
+        if (zeroIndex == null || oneIndex == null) {
+          isTiled = false;
+        }
+        else {
+          int lowerLeft = Integer.parseInt(zeroIndex);
+          int middle = Integer.parseInt(oneIndex);
+
+          tileColumns = lowerLeft - middle - 1;
+          tileRows = (lowerLeft / tileColumns) + 1;
+          if (tileColumns < 0) tileColumns = 1;
+          if (tileRows < 0) tileRows = 1;
+          core.sizeX[0] *= tileColumns;
+          core.sizeY[0] *= tileRows;
+          if (tileColumns == 1 && tileRows == 1) isTiled = false;
+        }
+      }
+
+      if (cIndex != -1) {
+        int[] dims = {core.sizeZ[0], core.sizeC[0], core.sizeT[0]};
+        int max = 0, min = Integer.MAX_VALUE, maxNdx = 0, minNdx = 0;
+        String[] axes = {"Z", "C", "T"};
+
+        for (int i=0; i<dims.length; i++) {
+          if (dims[i] > max) {
+            max = dims[i];
+            maxNdx = i;
+          }
+          if (dims[i] < min) {
+            min = dims[i];
+            minNdx = i;
+          }
+        }
+
+        int medNdx = 0;
+        for (int i=0; i<3; i++) {
+          if (i != maxNdx && i != minNdx) medNdx = i;
+        }
+
+        core.currentOrder[0] =
+          "XY" + axes[maxNdx] + axes[medNdx] + axes[minNdx];
+
+        int num = core.sizeZ[0] * core.sizeT[0] - core.sizeC[0];
+        if ((zIndex != -1 && tIndex != -1) && (zIndex != num && tIndex != num))
+        {
+          if (zIndex != core.sizeZ[0]) {
+            if (core.sizeZ[0] != 1) {
+              core.currentOrder[0] =
+                core.currentOrder[0].replaceAll("Z", "") + "Z";
+            }
+            else {
+              core.currentOrder[0] =
+                core.currentOrder[0].replaceAll("T", "") + "T";
+            }
+          }
+        }
+
+        if (core.sizeZ[0] == core.sizeC[0] && core.sizeC[0] == core.sizeT[0]) {
+          legacy.setId(id);
+          core.currentOrder[0] = legacy.getDimensionOrder();
+        }
+      }
+      else if (core.rgb[0]) {
+        core.currentOrder[0] =
+          (core.sizeZ[0] > core.sizeT[0]) ? "XYCZT" : "XYCTZ";
+      }
+      else {
+        if (metadata.get("MultiChannelEnabled") != null ||
+          metadata.get("MultiChannelEnabled 0") != null)
+        {
+          core.currentOrder[0] =
+            (core.sizeZ[0] > core.sizeT[0]) ? "XYCZT" : "XYCTZ";
+        }
+        else {
+          core.currentOrder[0] =
+            (core.sizeZ[0] > core.sizeT[0]) ? "XYZTC" : "XYTZC";
+        }
+      }
+    }
+    catch (ReflectException exc) {
+      needLegacy = true;
+      if (debug) trace(exc);
+      initFile(id);
+    }
+
+    // rearrange axis sizes, if necessary
+
+    int lastZ = zIndices.size() == 0 ? Integer.MAX_VALUE :
+      ((Integer) zIndices.get(zIndices.size() - 1)).intValue();
+    int lastT = tIndices.size() == 0 ? Integer.MAX_VALUE :
+      ((Integer) tIndices.get(tIndices.size() - 1)).intValue();
+
+    if ((zIndex > lastZ || tIndex > lastT) && (zIndex == core.sizeC[0] - 1 ||
+      tIndex == core.sizeC[0] - 1 ||
+      (zIndex != 0 && zIndex % core.sizeC[0] == 0) ||
+      (tIndex != 0 && tIndex % core.sizeC[0] == 0)) && zIndex != lastT)
+    {
+      if (zIndex >= core.sizeZ[0] || tIndex >= core.sizeT[0]) {
+        int tmp = core.sizeZ[0];
+        core.sizeZ[0] = core.sizeT[0];
+        core.sizeT[0] = tmp;
+      }
+    }
+
+    // correct emission/excitation wavelengths, if necessary
+
+    if (metadata.size() > 0) {
+      // HACK
+      String lastEM =
+        (String) getMeta("Emission Wavelength " + (core.sizeC[0] - 1));
+      String nextToLastEM =
+        (String) getMeta("Emission Wavelength " + (core.sizeC[0] - 2));
+      if (lastEM == null || nextToLastEM == null ||
+        lastEM.equals(nextToLastEM))
+      {
+        String lastDye = (String) getMeta("Reflector " + (core.sizeC[0] - 1));
+        String nextToLastDye =
+          (String) getMeta("Reflector " + (core.sizeC[0] - 2));
+        if (lastDye == null) lastDye = "";
+        if (nextToLastDye == null) nextToLastDye = "";
+
+        lastDye = DataTools.stripString(lastDye);
+        nextToLastDye = DataTools.stripString(nextToLastDye);
+
+        if (nextToLastDye.indexOf("Rhodamine") != -1) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "580");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "540");
+        }
+        else if (nextToLastDye.indexOf("DAPI") != -1) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "461");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "359");
+        }
+        else if (nextToLastDye.startsWith("Alexa Fluor")) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "519");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "495");
+        }
+        else if (nextToLastDye.indexOf("Alexa Fluor") != -1) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "668");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "650");
+        }
+
+        if (lastDye.indexOf("Rhodamine") != -1) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "580");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "540");
+        }
+        else if (lastDye.indexOf("DAPI") != -1) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "461");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "359");
+        }
+        else if (lastDye.startsWith("Alexa Fluor")) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "519");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "495");
+        }
+        else if (lastDye.indexOf("Alexa Fluor") != -1) {
+          addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "668");
+          addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "650");
+        }
+      }
+    }
+
+    try {
+      initMetadata();
+    }
+    catch (FormatException exc) {
+      if (debug) trace(exc);
+    }
+    catch (IOException exc) {
+      if (debug) trace(exc);
+    }
+
+    // remove extra (invalid) metadata
+
+    String[] keys = (String[]) metadata.keySet().toArray(new String[0]);
+    for (int i=0; i<keys.length; i++) {
+      String n = keys[i];
+      if (n.indexOf(" ") != -1) {
+        n = n.substring(n.lastIndexOf(" ") + 1);
+        try {
+          int ndx = Integer.parseInt(n);
+          if (ndx >= core.sizeC[0]) metadata.remove(keys[i]);
+        }
+        catch (NumberFormatException e) { }
+      }
+    }
+  }
+
+  // -- Helper methods --
+
+  /** Initialize metadata hashtable and OME-XML structure. */
+  private void initMetadata() throws FormatException, IOException {
+    MetadataStore store = getMetadataStore();
+
+    String fname = (String) getMeta("Title");
+    if (fname == null) fname = currentId;
+    store.setImage(fname, null, null, null);
+
+    if (bpp == 1 || bpp == 3) core.pixelType[0] = FormatTools.UINT8;
+    else if (bpp == 2 || bpp == 6) core.pixelType[0] = FormatTools.UINT16;
+
+    FormatTools.populatePixels(store, this);
+
+    String pixX = (String) getMeta("Scale Factor for X");
+    String pixY = (String) getMeta("Scale Factor for Y");
+    String pixZ = (String) getMeta("Scale Factor for Z");
+
+    store.setDimensions(
+      pixX == null ? null : new Float(pixX),
+      pixY == null ? null : new Float(pixY),
+      pixZ == null ? null : new Float(pixZ),
+      null, null, null);
+
+    String scopeName = (String) getMeta("Microscope Name");
+    if (scopeName == null) scopeName = (String) getMeta("Microscope Name 0");
+    store.setInstrument(null, scopeName, null, null, null);
+
+    for (int i=0; i<core.sizeC[0]; i++) {
+      int idx = FormatTools.getIndex(this, 0, i % getEffectiveSizeC(), 0);
+      String emWave = (String) getMeta("Emission Wavelength " + idx);
+      String exWave = (String) getMeta("Excitation Wavelength " + idx);
+
+      if (emWave != null && emWave.indexOf(".") != -1) {
+        emWave = emWave.substring(0, emWave.indexOf("."));
+      }
+      if (exWave != null && exWave.indexOf(".") != -1) {
+        exWave = exWave.substring(0, exWave.indexOf("."));
+      }
+      store.setLogicalChannel(i, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        emWave == null ? null : new Integer(emWave),
+        exWave == null ? null : new Integer(exWave), null, null, null);
+
+      String black = (String) getMeta("BlackValue " + idx);
+      String white = (String) getMeta("WhiteValue " + idx);
+      String gamma = (String) getMeta("GammaValue " + idx);
+
+      Double blackValue = null, whiteValue = null;
+      Float gammaValue = null;
+
+      try { blackValue = new Double(black); }
+      catch (NumberFormatException e) { }
+      catch (NullPointerException e) { }
+      try { whiteValue = new Double(white); }
+      catch (NumberFormatException e) { }
+      catch (NullPointerException e) { }
+      try { gammaValue = new Float(gamma); }
+      catch (NumberFormatException e) { }
+      catch (NullPointerException e) { }
+
+      store.setDisplayChannel(new Integer(i), blackValue, whiteValue,
+        gammaValue, null);
+    }
+
+    for (int i=0; i<core.imageCount[0]; i++) {
+      int[] zct = FormatTools.getZCTCoords(this, i);
+      String exposure = (String) getMeta("Exposure Time [ms] " + i);
+      Float exp = new Float(0.0);
+      try { exp = new Float(exposure); }
+      catch (NumberFormatException e) { }
+      catch (NullPointerException e) { }
+      store.setPlaneInfo(zct[0], zct[1], zct[2], new Float(0.0), exp, null);
+    }
+
+    String objectiveName = (String) getMeta("Objective Name 0");
+    if (objectiveName != null) {
+      objectiveName = DataTools.stripString(objectiveName);
+    }
+
+    String objectiveMag = (String) getMeta("Objective Magnification 0");
+    if (objectiveMag != null) {
+      objectiveMag = DataTools.stripString(objectiveMag);
+    }
+    else objectiveMag = "1.0";
+
+    String objectiveNA = (String) getMeta("Objective N.A. 0");
+    if (objectiveNA != null) objectiveNA = DataTools.stripString(objectiveNA);
+    else objectiveNA = "1.0";
+
+    store.setObjective(null, objectiveName, null, new Float(objectiveNA),
+      new Float(objectiveMag), null, null);
+  }
+
+  protected void parseDir(int depth, Object dir)
+    throws IOException, FormatException, ReflectException
+  {
+    r.setVar("dir", dir);
+    r.exec("dirName = dir.getName()");
+    r.setVar("depth", depth);
+    r.exec("iter = dir.getEntries()");
+    Iterator iter = (Iterator) r.getVar("iter");
+    while (iter.hasNext()) {
+      r.setVar("entry", iter.next());
+      r.exec("isInstance = entry.isDirectoryEntry()");
+      r.exec("isDocument = entry.isDocumentEntry()");
+      boolean isInstance = ((Boolean) r.getVar("isInstance")).booleanValue();
+      boolean isDocument = ((Boolean) r.getVar("isDocument")).booleanValue();
+      r.setVar("dir", dir);
+      r.exec("dirName = dir.getName()");
+
+      if (isInstance)  {
+        status("Parsing embedded folder (" + (depth + 1) + ")");
+        parseDir(depth + 1, r.getVar("entry"));
+      }
+      else if (isDocument) {
+        status("Parsing embedded file (" + depth + ")");
+        r.exec("entryName = entry.getName()");
+        if (debug) {
+          print(depth + 1, "Found document: " + r.getVar("entryName"));
+        }
+        r.exec("dis = new DocumentInputStream(entry)");
+        r.exec("numBytes = dis.available()");
+        int numbytes = ((Integer) r.getVar("numBytes")).intValue();
+        byte[] data = new byte[numbytes + 4]; // append 0 for final offset
+        r.setVar("data", data);
+
+        // Suppressing an exception here looks like poor style.
+        // However, this at least gives us a chance at reading files with
+        // corrupt blocks.
+        try {
+          r.exec("dis.read(data)");
+        }
+        catch (ReflectException exc) {
+          if (debug) trace(exc);
+        }
+
+        String entryName = (String) r.getVar("entryName");
+        String dirName = (String) r.getVar("dirName");
+
+        boolean isContents = entryName.toUpperCase().equals("CONTENTS");
+        Object directory = r.getVar("dir");
+
+        RandomAccessStream s = new RandomAccessStream(data);
+        s.order(true);
+
+        if (dirName.toUpperCase().equals("ROOT ENTRY") ||
+          dirName.toUpperCase().equals("ROOTENTRY"))
+        {
+          if (entryName.equals("Tags")) {
+            try { parseTags(s); }
+            catch (EOFException e) { }
+          }
+        }
+        else if (dirName.equals("Tags") && isContents) {
+          try { parseTags(s); }
+          catch (EOFException e) { }
+        }
+        else if (isContents && (dirName.equals("Image") ||
+          dirName.toUpperCase().indexOf("ITEM") != -1) &&
+          (data.length > core.sizeX[0]*core.sizeY[0]))
+        {
+          s.skipBytes(6);
+
+          int vt = s.readShort();
+          if (vt == 3) {
+            s.skipBytes(6);
+          }
+          else if (vt == 8) {
+            int l = s.readShort();
+            s.skipBytes(l + 2);
+          }
+          int len = s.readShort();
+          if (s.readShort() != 0) s.seek(s.getFilePointer() - 2);
+
+          if (s.getFilePointer() + len <= s.length()) {
+            s.skipBytes(len);
+          }
+          else break;
+
+          vt = s.readShort();
+          if (vt == 8) {
+            len = s.readInt();
+            s.skipBytes(len + 2);
+          }
+
+          int tw = s.readInt();
+          if (core.sizeX[0] == 0 || (tw < core.sizeX[0] && tw > 0)) {
+            core.sizeX[0] = tw;
+          }
+          s.skipBytes(2);
+          int th = s.readInt();
+          if (core.sizeY[0] == 0 || (th < core.sizeY[0] && th > 0)) {
+            core.sizeY[0] = th;
+          }
+
+          s.skipBytes(14);
+
+          int numImageContainers = s.readInt();
+          s.skipBytes(6);
+
+          // VT_CLSID - PluginCLSID
+          while (s.readShort() != 65);
+
+          // VT_BLOB - Others
+          len = s.readInt();
+          s.skipBytes(len);
+
+          // VT_STORED_OBJECT - Layers
+          s.skipBytes(2);
+          long old = s.getFilePointer();
+          len = s.readInt();
+
+          s.skipBytes(8);
+
+          int tidx = s.readInt();
+          int cidx = s.readInt();
+          int zidx = s.readInt();
+
+          Integer zndx = new Integer(zidx);
+          Integer cndx = new Integer(cidx);
+          Integer tndx = new Integer(tidx);
+
+          if (!zIndices.contains(zndx)) zIndices.add(zndx);
+          if (!cIndices.contains(cndx)) cIndices.add(cndx);
+          if (!tIndices.contains(tndx)) tIndices.add(tndx);
+
+          s.seek(old + len + 4);
+
+          boolean foundWidth = s.readInt() == core.sizeX[0];
+          boolean foundHeight = s.readInt() == core.sizeY[0];
+          boolean findFailed = false;
+          while ((!foundWidth || !foundHeight) &&
+            s.getFilePointer() + 1 < s.length())
+          {
+            s.seek(s.getFilePointer() - 7);
+            foundWidth = s.readInt() == core.sizeX[0];
+            foundHeight = s.readInt() == core.sizeY[0];
+          }
+          s.seek(s.getFilePointer() - 16);
+          findFailed = !foundWidth && !foundHeight;
+
+          // image header and data
+
+          if (dirName.toUpperCase().indexOf("ITEM") != -1 ||
+            (dirName.equals("Image") && numImageContainers == 0))
+          {
+            if (findFailed) s.seek(old + len + 92);
+            long fp = s.getFilePointer();
+            byte[] o = new byte[(int) (s.length() - fp)];
+            s.read(o);
+
+            int imageNum = 0;
+            if (dirName.toUpperCase().indexOf("ITEM") != -1) {
+              String num = dirName.substring(5);
+              num = num.substring(0, num.length() - 1);
+              imageNum = Integer.parseInt(num);
+            }
+
+            offsets.put(new Integer(imageNum), new Integer((int) fp + 32));
+            parsePlane(o, imageNum, directory, entryName);
+          }
+        }
+        else {
+          try { parseTags(s); }
+          catch (IOException e) { }
+        }
+
+        s.close();
+        data = null;
+        r.exec("dis.close()");
+      }
+    }
+  }
+
+  /** Debugging helper method. */
+  protected void print(int depth, String s) {
+    StringBuffer sb = new StringBuffer();
+    for (int i=0; i<depth; i++) sb.append("  ");
+    sb.append(s);
+    debug(sb.toString());
+  }
+
+  /** Parse a plane of data. */
+  private void parsePlane(byte[] data, int num, Object directory, String entry)
+    throws IOException
+  {
+    RandomAccessStream s = new RandomAccessStream(data);
+    s.order(true);
+
+    if (s.readInt() == 0) s.skipBytes(4);
+
+    core.sizeX[0] = s.readInt();
+    core.sizeY[0] = s.readInt();
+    s.skipBytes(4);
+    bpp = s.readInt();
+    //s.skipBytes(8);
+    s.skipBytes(4);
+    int valid = s.readInt();
+    isJPEG = valid == 0 || valid == 1;
+
+    pixels.put(new Integer(num), directory);
+    names.put(new Integer(num), entry);
+    core.imageCount[0]++;
+    if (bpp % 3 == 0) core.sizeC[0] = 3;
+    else core.sizeC[0] = 1;
+  }
+
+  /** Parse all of the tags in a stream. */
+  private void parseTags(RandomAccessStream s) throws IOException {
+    s.skipBytes(24);
+
+    int count = s.readInt();
+
+    // limit count to 4096
+    if (count > 4096) count = 4096;
+
+    for (int i=0; i<count; i++) {
+      if (s.getFilePointer() + 2 >= s.length()) break;
+      int type = s.readShort();
+
+      String value = "";
+      switch (type) {
+        case 0:
+          break;
+        case 1:
+          break;
+        case 2:
+          value = "" + s.readShort();
+          break;
+        case 3:
+        case 22:
+        case 23:
+          value = "" + s.readInt();
+          break;
+        case 4:
+          value = "" + s.readFloat();
+          break;
+        case 5:
+          value = "" + s.readDouble();
+          break;
+        case 7:
+        case 20:
+        case 21:
+          value = "" + s.readLong();
+          break;
+        case 69:
+        case 8:
+          int len = s.readInt();
+          if (s.getFilePointer() + len < s.length()) {
+            value = s.readString(len);
+          }
+          else return;
+          break;
+        case 66:
+          int l = s.readShort();
+          s.seek(s.getFilePointer() - 2);
+          value = s.readString(l + 2);
+          break;
+        default:
+          long old = s.getFilePointer();
+          while (s.readShort() != 3 &&
+            s.getFilePointer() + 2 < s.length());
+          long fp = s.getFilePointer() - 2;
+          s.seek(old - 2);
+          value = s.readString((int) (fp - old + 2));
+      }
+
+      s.skipBytes(2);
+      int tagID = 0;
+
+      try { tagID = s.readInt(); }
+      catch (IOException e) { }
+
+      s.skipBytes(6);
+
+      String key = getKey(tagID);
+      if (key.equals("Image Index Z")) {
+        try {
+          zIndex = Integer.parseInt(DataTools.stripString(value));
+        }
+        catch (NumberFormatException f) { }
+      }
+      else if (key.equals("Image Index T")) {
+        try {
+          tIndex = Integer.parseInt(DataTools.stripString(value));
+        }
+        catch (NumberFormatException f) { }
+      }
+      else if (key.equals("Image Channel Index")) {
+        try {
+          cIndex = Integer.parseInt(DataTools.stripString(value));
+        }
+        catch (NumberFormatException f) { }
+      }
+      else if (key.equals("ImageWidth")) {
+        try {
+          if (core.sizeX[0] == 0) core.sizeX[0] = Integer.parseInt(value);
+        }
+        catch (NumberFormatException f) { }
+      }
+      else if (key.equals("ImageHeight")) {
+        try {
+          if (core.sizeY[0] == 0) core.sizeY[0] = Integer.parseInt(value);
+        }
+        catch (NumberFormatException f) { }
+      }
+
+      if (metadata.get(key) != null || metadata.get(key + " 0") != null) {
+        if (metadata.get(key) != null) {
+          Object v = metadata.remove(key);
+          metadata.put(key + " 0", v);
+        }
+
+        int ndx = 0;
+        while (metadata.get(key + " " + ndx) != null) ndx++;
+        key += " " + ndx;
+      }
+
+      if (key.indexOf("ImageTile") != -1) isTiled = true;
+      addMeta(key, value);
+    }
+  }
+
+  /** Return the string corresponding to the given ID. */
+  private String getKey(int tagID) {
+    switch (tagID) {
+      case 222: return "Compression";
+      case 258: return "BlackValue";
+      case 259: return "WhiteValue";
+      case 260: return "ImageDataMappingAutoRange";
+      case 261: return "Thumbnail";
+      case 262: return "GammaValue";
+      case 264: return "ImageOverExposure";
+      case 265: return "ImageRelativeTime1";
+      case 266: return "ImageRelativeTime2";
+      case 267: return "ImageRelativeTime3";
+      case 268: return "ImageRelativeTime4";
+      case 333: return "RelFocusPosition1";
+      case 334: return "RelFocusPosition2";
+      case 513: return "ObjectType";
+      case 515: return "ImageWidth";
+      case 516: return "ImageHeight";
+      case 517: return "Number Raw Count";
+      case 518: return "PixelType";
+      case 519: return "NumberOfRawImages";
+      case 520: return "ImageSize";
+      case 523: return "Acquisition pause annotation";
+      case 530: return "Document Subtype";
+      case 531: return "Acquisition Bit Depth";
+      case 532: return "Image Memory Usage (RAM)";
+      case 534: return "Z-Stack single representative";
+      case 769: return "Scale Factor for X";
+      case 770: return "Scale Unit for X";
+      case 771: return "Scale Width";
+      case 772: return "Scale Factor for Y";
+      case 773: return "Scale Unit for Y";
+      case 774: return "Scale Height";
+      case 775: return "Scale Factor for Z";
+      case 776: return "Scale Unit for Z";
+      case 777: return "Scale Depth";
+      case 778: return "Scaling Parent";
+      case 1001: return "Date";
+      case 1002: return "code";
+      case 1003: return "Source";
+      case 1004: return "Message";
+      case 1025: return "Acquisition Date";
+      case 1026: return "8-bit acquisition";
+      case 1027: return "Camera Bit Depth";
+      case 1029: return "MonoReferenceLow";
+      case 1030: return "MonoReferenceHigh";
+      case 1031: return "RedReferenceLow";
+      case 1032: return "RedReferenceHigh";
+      case 1033: return "GreenReferenceLow";
+      case 1034: return "GreenReferenceHigh";
+      case 1035: return "BlueReferenceLow";
+      case 1036: return "BlueReferenceHigh";
+      case 1041: return "FrameGrabber Name";
+      case 1042: return "Camera";
+      case 1044: return "CameraTriggerSignalType";
+      case 1045: return "CameraTriggerEnable";
+      case 1046: return "GrabberTimeout";
+      case 1281: return "MultiChannelEnabled";
+      case 1282: return "MultiChannel Color";
+      case 1283: return "MultiChannel Weight";
+      case 1284: return "Channel Name";
+      case 1536: return "DocumentInformationGroup";
+      case 1537: return "Title";
+      case 1538: return "Author";
+      case 1539: return "Keywords";
+      case 1540: return "Comments";
+      case 1541: return "SampleID";
+      case 1542: return "Subject";
+      case 1543: return "RevisionNumber";
+      case 1544: return "Save Folder";
+      case 1545: return "FileLink";
+      case 1546: return "Document Type";
+      case 1547: return "Storage Media";
+      case 1548: return "File ID";
+      case 1549: return "Reference";
+      case 1550: return "File Date";
+      case 1551: return "File Size";
+      case 1553: return "Filename";
+      case 1792: return "ProjectGroup";
+      case 1793: return "Acquisition Date";
+      case 1794: return "Last modified by";
+      case 1795: return "User company";
+      case 1796: return "User company logo";
+      case 1797: return "Image";
+      case 1800: return "User ID";
+      case 1801: return "User Name";
+      case 1802: return "User City";
+      case 1803: return "User Address";
+      case 1804: return "User Country";
+      case 1805: return "User Phone";
+      case 1806: return "User Fax";
+      case 2049: return "Objective Name";
+      case 2050: return "Optovar";
+      case 2051: return "Reflector";
+      case 2052: return "Condenser Contrast";
+      case 2053: return "Transmitted Light Filter 1";
+      case 2054: return "Transmitted Light Filter 2";
+      case 2055: return "Reflected Light Shutter";
+      case 2056: return "Condenser Front Lens";
+      case 2057: return "Excitation Filter Name";
+      case 2060: return "Transmitted Light Fieldstop Aperture";
+      case 2061: return "Reflected Light Aperture";
+      case 2062: return "Condenser N.A.";
+      case 2063: return "Light Path";
+      case 2064: return "HalogenLampOn";
+      case 2065: return "Halogen Lamp Mode";
+      case 2066: return "Halogen Lamp Voltage";
+      case 2068: return "Fluorescence Lamp Level";
+      case 2069: return "Fluorescence Lamp Intensity";
+      case 2070: return "LightManagerEnabled";
+      case 2071: return "tag_ID_2071";
+      case 2072: return "Focus Position";
+      case 2073: return "Stage Position X";
+      case 2074: return "Stage Position Y";
+      case 2075: return "Microscope Name";
+      case 2076: return "Objective Magnification";
+      case 2077: return "Objective N.A.";
+      case 2078: return "MicroscopeIllumination";
+      case 2079: return "External Shutter 1";
+      case 2080: return "External Shutter 2";
+      case 2081: return "External Shutter 3";
+      case 2082: return "External Filter Wheel 1 Name";
+      case 2083: return "External Filter Wheel 2 Name";
+      case 2084: return "Parfocal Correction";
+      case 2086: return "External Shutter 4";
+      case 2087: return "External Shutter 5";
+      case 2088: return "External Shutter 6";
+      case 2089: return "External Filter Wheel 3 Name";
+      case 2090: return "External Filter Wheel 4 Name";
+      case 2103: return "Objective Turret Position";
+      case 2104: return "Objective Contrast Method";
+      case 2105: return "Objective Immersion Type";
+      case 2107: return "Reflector Position";
+      case 2109: return "Transmitted Light Filter 1 Position";
+      case 2110: return "Transmitted Light Filter 2 Position";
+      case 2112: return "Excitation Filter Position";
+      case 2113: return "Lamp Mirror Position";
+      case 2114: return "External Filter Wheel 1 Position";
+      case 2115: return "External Filter Wheel 2 Position";
+      case 2116: return "External Filter Wheel 3 Position";
+      case 2117: return "External Filter Wheel 4 Position";
+      case 2118: return "Lightmanager Mode";
+      case 2119: return "Halogen Lamp Calibration";
+      case 2120: return "CondenserNAGoSpeed";
+      case 2121: return "TransmittedLightFieldstopGoSpeed";
+      case 2122: return "OptovarGoSpeed";
+      case 2123: return "Focus calibrated";
+      case 2124: return "FocusBasicPosition";
+      case 2125: return "FocusPower";
+      case 2126: return "FocusBacklash";
+      case 2127: return "FocusMeasurementOrigin";
+      case 2128: return "FocusMeasurementDistance";
+      case 2129: return "FocusSpeed";
+      case 2130: return "FocusGoSpeed";
+      case 2131: return "FocusDistance";
+      case 2132: return "FocusInitPosition";
+      case 2133: return "Stage calibrated";
+      case 2134: return "StagePower";
+      case 2135: return "StageXBacklash";
+      case 2136: return "StageYBacklash";
+      case 2137: return "StageSpeedX";
+      case 2138: return "StageSpeedY";
+      case 2139: return "StageSpeed";
+      case 2140: return "StageGoSpeedX";
+      case 2141: return "StageGoSpeedY";
+      case 2142: return "StageStepDistanceX";
+      case 2143: return "StageStepDistanceY";
+      case 2144: return "StageInitialisationPositionX";
+      case 2145: return "StageInitialisationPositionY";
+      case 2146: return "MicroscopeMagnification";
+      case 2147: return "ReflectorMagnification";
+      case 2148: return "LampMirrorPosition";
+      case 2149: return "FocusDepth";
+      case 2150: return "MicroscopeType";
+      case 2151: return "Objective Working Distance";
+      case 2152: return "ReflectedLightApertureGoSpeed";
+      case 2153: return "External Shutter";
+      case 2154: return "ObjectiveImmersionStop";
+      case 2155: return "Focus Start Speed";
+      case 2156: return "Focus Acceleration";
+      case 2157: return "ReflectedLightFieldstop";
+      case 2158: return "ReflectedLightFieldstopGoSpeed";
+      case 2159: return "ReflectedLightFilter 1";
+      case 2160: return "ReflectedLightFilter 2";
+      case 2161: return "ReflectedLightFilter1Position";
+      case 2162: return "ReflectedLightFilter2Position";
+      case 2163: return "TransmittedLightAttenuator";
+      case 2164: return "ReflectedLightAttenuator";
+      case 2165: return "Transmitted Light Shutter";
+      case 2166: return "TransmittedLightAttenuatorGoSpeed";
+      case 2167: return "ReflectedLightAttenuatorGoSpeed";
+      case 2176: return "TransmittedLightVirtualFilterPosition";
+      case 2177: return "TransmittedLightVirtualFilter";
+      case 2178: return "ReflectedLightVirtualFilterPosition";
+      case 2179: return "ReflectedLightVirtualFilter";
+      case 2180: return "ReflectedLightHalogenLampMode";
+      case 2181: return "ReflectedLightHalogenLampVoltage";
+      case 2182: return "ReflectedLightHalogenLampColorTemperature";
+      case 2183: return "ContrastManagerMode";
+      case 2184: return "Dazzle Protection Active";
+      case 2195: return "Zoom";
+      case 2196: return "ZoomGoSpeed";
+      case 2197: return "LightZoom";
+      case 2198: return "LightZoomGoSpeed";
+      case 2199: return "LightZoomCoupled";
+      case 2200: return "TransmittedLightHalogenLampMode";
+      case 2201: return "TransmittedLightHalogenLampVoltage";
+      case 2202: return "TransmittedLightHalogenLampColorTemperature";
+      case 2203: return "Reflected Coldlight Mode";
+      case 2204: return "Reflected Coldlight Intensity";
+      case 2205: return "Reflected Coldlight Color Temperature";
+      case 2206: return "Transmitted Coldlight Mode";
+      case 2207: return "Transmitted Coldlight Intensity";
+      case 2208: return "Transmitted Coldlight Color Temperature";
+      case 2209: return "Infinityspace Portchanger Position";
+      case 2210: return "Beamsplitter Infinity Space";
+      case 2211: return "TwoTv VisCamChanger Position";
+      case 2212: return "Beamsplitter Ocular";
+      case 2213: return "TwoTv CamerasChanger Position";
+      case 2214: return "Beamsplitter Cameras";
+      case 2215: return "Ocular Shutter";
+      case 2216: return "TwoTv CamerasChangerCube";
+      case 2218: return "Ocular Magnification";
+      case 2219: return "Camera Adapter Magnification";
+      case 2220: return "Microscope Port";
+      case 2221: return "Ocular Total Magnification";
+      case 2222: return "Field of View";
+      case 2223: return "Ocular";
+      case 2224: return "CameraAdapter";
+      case 2225: return "StageJoystickEnabled";
+      case 2226: return "ContrastManager Contrast Method";
+      case 2229: return "CamerasChanger Beamsplitter Type";
+      case 2235: return "Rearport Slider Position";
+      case 2236: return "Rearport Source";
+      case 2237: return "Beamsplitter Type Infinity Space";
+      case 2238: return "Fluorescence Attenuator";
+      case 2239: return "Fluorescence Attenuator Position";
+      case 2261: return "Objective ID";
+      case 2262: return "Reflector ID";
+      case 2307: return "Camera Framestart Left";
+      case 2308: return "Camera Framestart Top";
+      case 2309: return "Camera Frame Width";
+      case 2310: return "Camera Frame Height";
+      case 2311: return "Camera Binning";
+      case 2312: return "CameraFrameFull";
+      case 2313: return "CameraFramePixelDistance";
+      case 2318: return "DataFormatUseScaling";
+      case 2319: return "CameraFrameImageOrientation";
+      case 2320: return "VideoMonochromeSignalType";
+      case 2321: return "VideoColorSignalType";
+      case 2322: return "MeteorChannelInput";
+      case 2323: return "MeteorChannelSync";
+      case 2324: return "WhiteBalanceEnabled";
+      case 2325: return "CameraWhiteBalanceRed";
+      case 2326: return "CameraWhiteBalanceGreen";
+      case 2327: return "CameraWhiteBalanceBlue";
+      case 2331: return "CameraFrameScalingFactor";
+      case 2562: return "Meteor Camera Type";
+      case 2564: return "Exposure Time [ms]";
+      case 2568: return "CameraExposureTimeAutoCalculate";
+      case 2569: return "Meteor Gain Value";
+      case 2571: return "Meteor Gain Automatic";
+      case 2572: return "MeteorAdjustHue";
+      case 2573: return "MeteorAdjustSaturation";
+      case 2574: return "MeteorAdjustRedLow";
+      case 2575: return "MeteorAdjustGreenLow";
+      case 2576: return "Meteor Blue Low";
+      case 2577: return "MeteorAdjustRedHigh";
+      case 2578: return "MeteorAdjustGreenHigh";
+      case 2579: return "MeteorBlue High";
+      case 2582: return "CameraExposureTimeCalculationControl";
+      case 2585: return "AxioCamFadingCorrectionEnable";
+      case 2587: return "CameraLiveImage";
+      case 2588: return "CameraLiveEnabled";
+      case 2589: return "LiveImageSyncObjectName";
+      case 2590: return "CameraLiveSpeed";
+      case 2591: return "CameraImage";
+      case 2592: return "CameraImageWidth";
+      case 2593: return "CameraImageHeight";
+      case 2594: return "CameraImagePixelType";
+      case 2595: return "CameraImageShMemoryName";
+      case 2596: return "CameraLiveImageWidth";
+      case 2597: return "CameraLiveImageHeight";
+      case 2598: return "CameraLiveImagePixelType";
+      case 2599: return "CameraLiveImageShMemoryName";
+      case 2600: return "CameraLiveMaximumSpeed";
+      case 2601: return "CameraLiveBinning";
+      case 2602: return "CameraLiveGainValue";
+      case 2603: return "CameraLiveExposureTimeValue";
+      case 2604: return "CameraLiveScalingFactor";
+      case 2819: return "Image Index Z";
+      case 2820: return "Image Channel Index";
+      case 2821: return "Image Index T";
+      case 2822: return "ImageTile Index";
+      case 2823: return "Image acquisition Index";
+      case 2827: return "Image IndexS";
+      case 2841: return "Original Stage Position X";
+      case 2842: return "Original Stage Position Y";
+      case 3088: return "LayerDrawFlags";
+      case 3334: return "RemainingTime";
+      case 3585: return "User Field 1";
+      case 3586: return "User Field 2";
+      case 3587: return "User Field 3";
+      case 3588: return "User Field 4";
+      case 3589: return "User Field 5";
+      case 3590: return "User Field 6";
+      case 3591: return "User Field 7";
+      case 3592: return "User Field 8";
+      case 3593: return "User Field 9";
+      case 3594: return "User Field 10";
+      case 3840: return "ID";
+      case 3841: return "Name";
+      case 3842: return "Value";
+      case 5501: return "PvCamClockingMode";
+      case 8193: return "Autofocus Status Report";
+      case 8194: return "Autofocus Position";
+      case 8195: return "Autofocus Position Offset";
+      case 8196: return "Autofocus Empty Field Threshold";
+      case 8197: return "Autofocus Calibration Name";
+      case 8198: return "Autofocus Current Calibration Item";
+      case 20478: return "tag_ID_20478";
+      case 65537: return "CameraFrameFullWidth";
+      case 65538: return "CameraFrameFullHeight";
+      case 65541: return "AxioCam Shutter Signal";
+      case 65542: return "AxioCam Delay Time";
+      case 65543: return "AxioCam Shutter Control";
+      case 65544: return "AxioCam BlackRefIsCalculated";
+      case 65545: return "AxioCam Black Reference";
+      case 65547: return "Camera Shading Correction";
+      case 65550: return "AxioCam Enhance Color";
+      case 65551: return "AxioCam NIR Mode";
+      case 65552: return "CameraShutterCloseDelay";
+      case 65553: return "CameraWhiteBalanceAutoCalculate";
+      case 65556: return "AxioCam NIR Mode Available";
+      case 65557: return "AxioCam Fading Correction Available";
+      case 65559: return "AxioCam Enhance Color Available";
+      case 65565: return "MeteorVideoNorm";
+      case 65566: return "MeteorAdjustWhiteReference";
+      case 65567: return "MeteorBlackReference";
+      case 65568: return "MeteorChannelInputCountMono";
+      case 65570: return "MeteorChannelInputCountRGB";
+      case 65571: return "MeteorEnableVCR";
+      case 65572: return "Meteor Brightness";
+      case 65573: return "Meteor Contrast";
+      case 65575: return "AxioCam Selector";
+      case 65576: return "AxioCam Type";
+      case 65577: return "AxioCam Info";
+      case 65580: return "AxioCam Resolution";
+      case 65581: return "AxioCam Color Model";
+      case 65582: return "AxioCam MicroScanning";
+      case 65585: return "Amplification Index";
+      case 65586: return "Device Command";
+      case 65587: return "BeamLocation";
+      case 65588: return "ComponentType";
+      case 65589: return "ControllerType";
+      case 65590: return "CameraWhiteBalanceCalculationRedPaint";
+      case 65591: return "CameraWhiteBalanceCalculationBluePaint";
+      case 65592: return "CameraWhiteBalanceSetRed";
+      case 65593: return "CameraWhiteBalanceSetGreen";
+      case 65594: return "CameraWhiteBalanceSetBlue";
+      case 65595: return "CameraWhiteBalanceSetTargetRed";
+      case 65596: return "CameraWhiteBalanceSetTargetGreen";
+      case 65597: return "CameraWhiteBalanceSetTargetBlue";
+      case 65598: return "ApotomeCamCalibrationMode";
+      case 65599: return "ApoTome Grid Position";
+      case 65600: return "ApotomeCamScannerPosition";
+      case 65601: return "ApoTome Full Phase Shift";
+      case 65602: return "ApoTome Grid Name";
+      case 65603: return "ApoTome Staining";
+      case 65604: return "ApoTome Processing Mode";
+      case 65605: return "ApotmeCamLiveCombineMode";
+      case 65606: return "ApoTome Filter Name";
+      case 65607: return "Apotome Filter Strength";
+      case 65608: return "ApotomeCamFilterHarmonics";
+      case 65609: return "ApoTome Grating Period";
+      case 65610: return "ApoTome Auto Shutter Used";
+      case 65611: return "Apotome Cam Status";
+      case 65612: return "ApotomeCamNormalize";
+      case 65613: return "ApotomeCamSettingsManager";
+      case 65614: return "DeepviewCamSupervisorMode";
+      case 65615: return "DeepView Processing";
+      case 65616: return "DeepviewCamFilterName";
+      case 65617: return "DeepviewCamStatus";
+      case 65618: return "DeepviewCamSettingsManager";
+      case 65619: return "DeviceScalingName";
+      case 65620: return "CameraShadingIsCalculated";
+      case 65621: return "CameraShadingCalculationName";
+      case 65622: return "CameraShadingAutoCalculate";
+      case 65623: return "CameraTriggerAvailable";
+      case 65626: return "CameraShutterAvailable";
+      case 65627: return "AxioCam ShutterMicroScanningEnable";
+      case 65628: return "ApotomeCamLiveFocus";
+      case 65629: return "DeviceInitStatus";
+      case 65630: return "DeviceErrorStatus";
+      case 65631: return "ApotomeCamSliderInGridPosition";
+      case 65632: return "Orca NIR Mode Used";
+      case 65633: return "Orca Analog Gain";
+      case 65634: return "Orca Analog Offset";
+      case 65635: return "Orca Binning";
+      case 65636: return "Orca Bit Depth";
+      case 65637: return "ApoTome Averaging Count";
+      case 65638: return "DeepView DoF";
+      case 65639: return "DeepView EDoF";
+      case 65643: return "DeepView Slider Name";
+      case 65655: return "DeepView Slider Name";
+      case 5439491: return "Acquisition Sofware";
+      case 16777488: return "Excitation Wavelength";
+      case 16777489: return "Emission Wavelength";
+      case 101515267: return "File Name";
+      case 101253123:
+      case 101777411:
+        return "Image Name";
+      default: return "" + tagID;
+    }
+  }
+
+}
diff --git a/loci/formats/out/AVIWriter.java b/loci/formats/out/AVIWriter.java
new file mode 100644
index 0000000..23aefe6
--- /dev/null
+++ b/loci/formats/out/AVIWriter.java
@@ -0,0 +1,539 @@
+//
+// AVIWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.awt.Image;
+import java.awt.image.*;
+import java.io.*;
+import java.util.Vector;
+import loci.formats.*;
+
+/**
+ * AVIWriter is the file format writer for AVI files.
+ *
+ * Much of this writer's code was adapted from Wayne Rasband's
+ * AVI Movie Writer plugin for ImageJ
+ * (available at http://rsb.info.nih.gov/ij/).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/AVIWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/AVIWriter.java">SVN</a></dd></dl>
+ */
+public class AVIWriter extends FormatWriter {
+
+  // -- Fields --
+
+  private RandomAccessFile raFile;
+
+  private int planesWritten = 0;
+
+  private int bytesPerPixel;
+  private File file;
+  private int xDim, yDim, zDim, tDim, xPad;
+  private int microSecPerFrame;
+
+  // location of file size in bytes not counting first 8 bytes
+  private long saveFileSize;
+
+  // location of length of CHUNK with first LIST - not including first 8
+  // bytes with LIST and size. JUNK follows the end of this CHUNK
+  private long saveLIST1Size;
+
+  // location of length of CHUNK with second LIST - not including first 8
+  // bytes with LIST and size. Note that saveLIST1subSize = saveLIST1Size +
+  // 76, and that the length size written to saveLIST2Size is 76 less than
+  // that written to saveLIST1Size. JUNK follows the end of this CHUNK.
+  private long saveLIST1subSize;
+
+  // location of length of strf CHUNK - not including the first 8 bytes with
+  // strf and size. strn follows the end of this CHUNK.
+  private long savestrfSize;
+
+  private byte[] text;
+  private long savestrnPos;
+  private long saveJUNKsignature;
+  private int paddingBytes;
+  private long saveLIST2Size;
+  private byte[] dataSignature;
+  private Vector savedbLength;
+  private long idx1Pos;
+  private long endPos;
+  private long saveidx1Length;
+  private int z;
+  private long savemovi;
+  private int xMod;
+  private long frameOffset;
+  private long frameOffset2;
+
+  // -- Constructor --
+
+  public AVIWriter() { super("Audio Video Interleave", "avi"); }
+
+  // -- IFormatWriter API methods --
+
+  /* @see loci.formats.IFormatWriter#saveImage(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    if (image == null) {
+      throw new FormatException("Image is null");
+    }
+    BufferedImage img = null;
+    if (cm != null) img = ImageTools.makeBuffered(image, cm);
+    else img = ImageTools.makeBuffered(image);
+    byte[][] byteData = ImageTools.getBytes(img);
+
+    if (!initialized) {
+      initialized = true;
+      planesWritten = 0;
+      bytesPerPixel = byteData.length;
+
+      file = new File(currentId);
+      raFile = new RandomAccessFile(file, "rw");
+      raFile.seek(raFile.length());
+      saveFileSize = 4;
+      saveLIST1Size = 16;
+      saveLIST1subSize = 23 * 4;
+      frameOffset = 48;
+      frameOffset2 = 35 * 4;
+      savestrfSize = 42 * 4;
+      savestrnPos = savestrfSize + 44 + (bytesPerPixel == 1 ? 4 * 256 : 0);
+      saveJUNKsignature = savestrnPos + 24;
+      saveLIST2Size = 4088;
+      savemovi = 4092;
+
+      savedbLength = new Vector();
+
+      dataSignature = new byte[4];
+      dataSignature[0] = 48; // 0
+      dataSignature[1] = 48; // 0
+      dataSignature[2] = 100; // d
+      dataSignature[3] = 98; // b
+
+      tDim = 1;
+      zDim = 1;
+      yDim = img.getHeight();
+      xDim = img.getWidth();
+
+      xPad = 0;
+      xMod = xDim % 4;
+      if (xMod != 0) {
+        xPad = 4 - xMod;
+        xDim += xPad;
+      }
+
+      if (raFile.length() == 0) {
+        DataTools.writeString(raFile, "RIFF"); // signature
+        // Bytes 4 thru 7 contain the length of the file. This length does
+        // not include bytes 0 thru 7.
+        DataTools.writeInt(raFile, 0, true); // for now write 0 for size
+        DataTools.writeString(raFile, "AVI "); // RIFF type
+        // Write the first LIST chunk, which contains
+        // information on data decoding
+        DataTools.writeString(raFile, "LIST"); // CHUNK signature
+        // Write the length of the LIST CHUNK not including the first 8 bytes
+        // with LIST and size. Note that the end of the LIST CHUNK is followed
+        // by JUNK.
+        DataTools.writeInt(raFile, (bytesPerPixel == 1) ? 1240 : 216, true);
+        DataTools.writeString(raFile, "hdrl"); // CHUNK type
+        DataTools.writeString(raFile, "avih"); // Write the avih sub-CHUNK
+
+        // Write the length of the avih sub-CHUNK (38H) not including the
+        // the first 8 bytes for avihSignature and the length
+        DataTools.writeInt(raFile, 0x38, true);
+
+        // dwMicroSecPerFrame - Write the microseconds per frame
+        microSecPerFrame = (int) (1.0 / fps * 1.0e6);
+        DataTools.writeInt(raFile, microSecPerFrame, true);
+
+        // Write the maximum data rate of the file in bytes per second
+        DataTools.writeInt(raFile, 0, true); // dwMaxBytesPerSec
+
+        DataTools.writeInt(raFile, 0, true); // dwReserved1 - set to 0
+        // dwFlags - just set the bit for AVIF_HASINDEX
+        DataTools.writeInt(raFile, 0x10, true);
+
+        // 10H AVIF_HASINDEX: The AVI file has an idx1 chunk containing
+        //   an index at the end of the file. For good performance, all
+        //   AVI files should contain an index.
+        // 20H AVIF_MUSTUSEINDEX: Index CHUNK, rather than the physical
+        // ordering of the chunks in the file, must be used to determine the
+        // order of the frames.
+        // 100H AVIF_ISINTERLEAVED: Indicates that the AVI file is interleaved.
+        //   This is used to read data from a CD-ROM more efficiently.
+        // 800H AVIF_TRUSTCKTYPE: USE CKType to find key frames
+        // 10000H AVIF_WASCAPTUREFILE: The AVI file is used for capturing
+        //   real-time video. Applications should warn the user before
+        //   writing over a file with this fla set because the user
+        //   probably defragmented this file.
+        // 20000H AVIF_COPYRIGHTED: The AVI file contains copyrighted data
+        //   and software. When, this flag is used, software should not
+        //   permit the data to be duplicated.
+
+        // dwTotalFrames - total frame number
+        DataTools.writeInt(raFile, zDim * tDim, true);
+
+        // dwInitialFrames -Initial frame for interleaved files.
+        // Noninterleaved files should specify 0.
+        DataTools.writeInt(raFile, 0, true);
+
+        // dwStreams - number of streams in the file - here 1 video and
+        // zero audio.
+        DataTools.writeInt(raFile, 1, true);
+
+        // dwSuggestedBufferSize - Suggested buffer size for reading the file.
+        // Generally, this size should be large enough to contain the largest
+        // chunk in the file.
+        DataTools.writeInt(raFile, 0, true);
+
+        // dwWidth - image width in pixels
+        DataTools.writeInt(raFile, xDim - xPad, true);
+        DataTools.writeInt(raFile, yDim, true); // dwHeight - height in pixels
+
+        // dwReserved[4] - Microsoft says to set the following 4 values to 0.
+        DataTools.writeInt(raFile, 0, true);
+        DataTools.writeInt(raFile, 0, true);
+        DataTools.writeInt(raFile, 0, true);
+        DataTools.writeInt(raFile, 0, true);
+
+        // Write the Stream line header CHUNK
+        DataTools.writeString(raFile, "LIST");
+
+        // Write the size of the first LIST subCHUNK not including the first 8
+        // bytes with LIST and size. Note that saveLIST1subSize = saveLIST1Size
+        // + 76, and that the length written to saveLIST1subSize is 76 less than
+        // the length written to saveLIST1Size. The end of the first LIST
+        // subCHUNK is followed by JUNK.
+
+        DataTools.writeInt(raFile, (bytesPerPixel == 1) ? 1164 : 140 , true);
+        DataTools.writeString(raFile, "strl");   // Write the chunk type
+        DataTools.writeString(raFile, "strh"); // Write the strh sub-CHUNK
+        DataTools.writeInt(raFile, 56, true); // Write length of strh sub-CHUNK
+
+        // fccType - Write the type of data stream - here vids for video stream
+        DataTools.writeString(raFile, "vids");
+
+        // Write DIB for Microsoft Device Independent Bitmap.
+        // Note: Unfortunately, at least 3 other four character codes are
+        // sometimes used for uncompressed AVI videos: 'RGB ', 'RAW ',
+        // 0x00000000
+        DataTools.writeString(raFile, "DIB ");
+
+        DataTools.writeInt(raFile, 0, true); // dwFlags
+
+        // 0x00000001 AVISF_DISABLED The stram data should be rendered only when
+        // explicitly enabled.
+        // 0x00010000 AVISF_VIDEO_PALCHANGES Indicates that a palette change is
+        // included in the AVI file. This flag warns the playback software that
+        // it will need to animate the palette.
+
+        // dwPriority - priority of a stream type. For example, in a file with
+        // multiple audio streams, the one with the highest priority might be
+        // the default one.
+        DataTools.writeInt(raFile, 0, true);
+
+        // dwInitialFrames - Specifies how far audio data is skewed ahead of
+        // video frames in interleaved files. Typically, this is about 0.75
+        // seconds. In interleaved files specify the number of frames in the
+        // file prior to the initial frame of the AVI sequence.
+        // Noninterleaved files should use zero.
+        DataTools.writeInt(raFile, 0, true);
+
+        // rate/scale = samples/second
+        DataTools.writeInt(raFile, 1, true); // dwScale
+
+        //  dwRate - frame rate for video streams
+        DataTools.writeInt(raFile, fps, true);
+
+        // dwStart - this field is usually set to zero
+        DataTools.writeInt(raFile, 0, true);
+
+        // dwLength - playing time of AVI file as defined by scale and rate
+        // Set equal to the number of frames
+        DataTools.writeInt(raFile, tDim * zDim, true);
+
+        // dwSuggestedBufferSize - Suggested buffer size for reading the stream.
+        // Typically, this contains a value corresponding to the largest chunk
+        // in a stream.
+        DataTools.writeInt(raFile, 0, true);
+
+        // dwQuality - encoding quality given by an integer between 0 and
+        // 10,000. If set to -1, drivers use the default quality value.
+        DataTools.writeInt(raFile, -1, true);
+
+        // dwSampleSize #
+        // 0 if the video frames may or may not vary in size
+        // If 0, each sample of data(such as a video frame) must be in a
+        // separate chunk. If nonzero, then multiple samples of data can be
+        // grouped into a single chunk within the file.
+        DataTools.writeInt(raFile, 0, true);
+
+        // rcFrame - Specifies the destination rectangle for a text or video
+        // stream within the movie rectangle specified by the dwWidth and
+        // dwHeight members of the AVI main header structure. The rcFrame member
+        // is typically used in support of multiple video streams. Set this
+        // rectangle to the coordinates corresponding to the movie rectangle to
+        // update the whole movie rectangle. Units for this member are pixels.
+        // The upper-left corner of the destination rectangle is relative to the
+        // upper-left corner of the movie rectangle.
+        DataTools.writeShort(raFile, (short) 0, true); // left
+        DataTools.writeShort(raFile, (short) 0, true); // top
+        DataTools.writeShort(raFile, (short) 0, true); // right
+        DataTools.writeShort(raFile, (short) 0, true); // bottom
+
+        // Write the size of the stream format CHUNK not including the first 8
+        // bytes for strf and the size. Note that the end of the stream format
+        // CHUNK is followed by strn.
+        DataTools.writeString(raFile, "strf"); // Write the stream format chunk
+
+        // write the strf CHUNK size
+        DataTools.writeInt(raFile, (bytesPerPixel == 1) ? 1068 : 44, true);
+
+        // Applications should use this size to determine which BITMAPINFO
+        // header structure is being used. This size includes this biSize field.
+        // biSize- Write header size of BITMAPINFO header structure
+
+        DataTools.writeInt(raFile, 40, true);
+
+        // biWidth - image width in pixels
+        DataTools.writeInt(raFile, xDim, true);
+
+        // biHeight - image height in pixels. If height is positive, the bitmap
+        // is a bottom up DIB and its origin is in the lower left corner. If
+        // height is negative, the bitmap is a top-down DIB and its origin is
+        // the upper left corner. This negative sign feature is supported by the
+        // Windows Media Player, but it is not supported by PowerPoint.
+        DataTools.writeInt(raFile, yDim, true);
+
+        // biPlanes - number of color planes in which the data is stored
+        // This must be set to 1.
+        DataTools.writeShort(raFile, 1, true);
+
+        int bitsPerPixel = (bytesPerPixel == 3) ? 24 : 8;
+
+        // biBitCount - number of bits per pixel #
+        // 0L for BI_RGB, uncompressed data as bitmap
+        DataTools.writeShort(raFile, (short) bitsPerPixel, true);
+
+        //writeInt(bytesPerPixel * xDim * yDim * zDim * tDim); // biSizeImage #
+        DataTools.writeInt(raFile, 0, true); // biSizeImage #
+        DataTools.writeInt(raFile, 0, true); // biCompression - compression type
+        // biXPelsPerMeter - horizontal resolution in pixels
+        DataTools.writeInt(raFile, 0, true);
+        // biYPelsPerMeter - vertical resolution in pixels per meter
+        DataTools.writeInt(raFile, 0, true);
+        if (bitsPerPixel == 8) DataTools.writeInt(raFile, 256, true);
+        else DataTools.writeInt(raFile, 0, true); // biClrUsed
+
+        // biClrImportant - specifies that the first x colors of the color table
+        // are important to the DIB. If the rest of the colors are not
+        // available, the image still retains its meaning in an acceptable
+        // manner. When this field is set to zero, all the colors are important,
+        // or, rather, their relative importance has not been computed.
+        DataTools.writeInt(raFile, 0, true);
+
+        // Write the LUTa.getExtents()[1] color table entries here. They are
+        // written: blue byte, green byte, red byte, 0 byte
+        if (bytesPerPixel == 1) {
+          byte[] lutWrite = new byte[4 * 256];
+          for (int i=0; i<256; i++) {
+            lutWrite[4*i] = (byte) i; // blue
+            lutWrite[4*i+1] = (byte) i; // green
+            lutWrite[4*i+2] = (byte) i; // red
+            lutWrite[4*i+3] = 0;
+          }
+          raFile.write(lutWrite);
+        }
+
+        raFile.seek(savestrfSize);
+        DataTools.writeInt(raFile,
+          (int) (savestrnPos - (savestrfSize + 4)), true);
+        raFile.seek(savestrnPos);
+
+        // Use strn to provide zero terminated text string describing the stream
+        DataTools.writeString(raFile, "strn");
+        DataTools.writeInt(raFile, 16, true); // Write length of strn sub-CHUNK
+        text = new byte[16];
+        text[0] = 70; // F
+        text[1] = 105; // i
+        text[2] = 108; // l
+        text[3] = 101; // e
+        text[4] = 65; // A
+        text[5] = 86; // V
+        text[6] = 73; // I
+        text[7] = 32; // space
+        text[8] = 119; // w
+        text[9] = 114; // r
+        text[10] = 105; // i
+        text[11] = 116; // t
+        text[12] = 101; // e
+        text[13] = 32; // space
+        text[14] = 32; // space
+        text[15] = 0; // termination byte
+        raFile.write(text);
+
+        raFile.seek(saveLIST1Size);
+        DataTools.writeInt(raFile,
+          (int) (saveJUNKsignature - (saveLIST1Size + 4)), true);
+        raFile.seek(saveLIST1subSize);
+        DataTools.writeInt(raFile,
+          (int) (saveJUNKsignature - (saveLIST1subSize + 4)), true);
+        raFile.seek(saveJUNKsignature);
+
+        // write a JUNK CHUNK for padding
+        DataTools.writeString(raFile, "JUNK");
+        paddingBytes = (int) (4084 - (saveJUNKsignature + 8));
+        DataTools.writeInt(raFile, paddingBytes, true);
+        for (int i=0; i<paddingBytes/2; i++) {
+          DataTools.writeShort(raFile, (short) 0, true);
+        }
+
+        // Write the second LIST chunk, which contains the actual data
+        DataTools.writeString(raFile, "LIST");
+
+        // Write the length of the LIST CHUNK not including the first 8 bytes
+        // with LIST and size. The end of the second LIST CHUNK is followed by
+        // idx1.
+        saveLIST2Size = raFile.getFilePointer();
+
+        DataTools.writeInt(raFile, 0, true);  // For now write 0
+        DataTools.writeString(raFile, "movi"); // Write CHUNK type 'movi'
+      }
+    }
+
+    // Write the data. Each 3-byte triplet in the bitmap array represents the
+    // relative intensities of blue, green, and red, respectively, for a pixel.
+    // The color bytes are in reverse order from the Windows convention.
+
+    int width = xDim - xPad;
+    int height = byteData[0].length / width;
+
+    raFile.write(dataSignature);
+    savedbLength.add(new Long(raFile.getFilePointer()));
+    // Write the data length
+
+    DataTools.writeInt(raFile, bytesPerPixel * xDim * yDim, true);
+
+    if (bytesPerPixel == 1) {
+      for (int i=(height-1); i>=0; i--) {
+        raFile.write(byteData[0], i*width, width);
+        for (int j=0; j<xPad; j++) raFile.write(0);
+      }
+    }
+    else {
+      byte[] buf = new byte[bytesPerPixel * xDim * yDim];
+      int offset = 0;
+      int next = 0;
+      for (int i=(height-1); i>=0; i--) {
+        for (int j=0; j<width; j++) {
+          offset = i*width + j;
+          for (int k=(byteData.length - 1); k>=0; k--) {
+            buf[next] = byteData[k][offset];
+            next++;
+          }
+        }
+        next += xPad * byteData.length;
+      }
+      raFile.write(buf);
+      buf = null;
+    }
+
+    planesWritten++;
+
+    if (last) {
+      // Write the idx1 CHUNK
+      // Write the 'idx1' signature
+      idx1Pos = raFile.getFilePointer();
+      raFile.seek(saveLIST2Size);
+      DataTools.writeInt(raFile, (int) (idx1Pos - (saveLIST2Size + 4)), true);
+      raFile.seek(idx1Pos);
+      DataTools.writeString(raFile, "idx1");
+
+      saveidx1Length = raFile.getFilePointer();
+
+      // Write the length of the idx1 CHUNK not including the idx1 signature
+      DataTools.writeInt(raFile, 4 + (planesWritten*16), true);
+
+      for (z=0; z<planesWritten; z++) {
+        // In the ckid field write the 4 character code to identify the chunk
+        // 00db or 00dc
+        raFile.write(dataSignature);
+        // Write the flags - select AVIIF_KEYFRAME
+        if (z == 0) DataTools.writeInt(raFile, 0x10, true);
+        else DataTools.writeInt(raFile, 0x00, true);
+
+        // AVIIF_KEYFRAME 0x00000010L
+        // The flag indicates key frames in the video sequence.
+        // Key frames do not need previous video information to be
+        // decompressed.
+        // AVIIF_NOTIME 0x00000100L The CHUNK does not influence video timing
+        // (for example a palette change CHUNK).
+        // AVIIF_LIST 0x00000001L Marks a LIST CHUNK.
+        // AVIIF_TWOCC 2L
+        // AVIIF_COMPUSE 0x0FFF0000L These bits are for compressor use.
+        DataTools.writeInt(raFile, (int) (((Long)
+          savedbLength.get(z)).longValue() - 4 - savemovi), true);
+
+        // Write the offset (relative to the 'movi' field) to the relevant
+        // CHUNK. Write the length of the relevant CHUNK. Note that this length
+        // is also written at savedbLength
+        DataTools.writeInt(raFile, bytesPerPixel*xDim*yDim, true);
+      }
+      endPos = raFile.getFilePointer();
+      raFile.seek(saveFileSize);
+      DataTools.writeInt(raFile, (int) (endPos - (saveFileSize + 4)), true);
+
+      raFile.seek(saveidx1Length);
+      DataTools.writeInt(raFile, (int) (endPos - (saveidx1Length + 4)), true);
+
+      // write the total number of planes
+      raFile.seek(frameOffset);
+      DataTools.writeInt(raFile, planesWritten, true);
+      raFile.seek(frameOffset2);
+      DataTools.writeInt(raFile, planesWritten, true);
+
+      raFile.close();
+    }
+  }
+
+  /* @see loci.formats.IFormatWriter#canDoStacks() */
+  public boolean canDoStacks() { return true; }
+
+  /* @see loci.formats.IFormatWriter#getPixelTypes() */
+  public int[] getPixelTypes() {
+    return new int[] {FormatTools.UINT8};
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    if (raFile != null) raFile.close();
+    raFile = null;
+    currentId = null;
+    initialized = false;
+  }
+
+}
diff --git a/loci/formats/out/EPSWriter.java b/loci/formats/out/EPSWriter.java
new file mode 100644
index 0000000..93a6341
--- /dev/null
+++ b/loci/formats/out/EPSWriter.java
@@ -0,0 +1,162 @@
+//
+// EPSWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.awt.Image;
+import java.awt.image.*;
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * EPSWriter is the file format writer for Encapsulated PostScript (EPS) files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/EPSWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/EPSWriter.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+public class EPSWriter extends FormatWriter {
+
+  /** Current file. */
+  protected RandomAccessFile out;
+
+  // -- Constructor --
+
+  public EPSWriter() {
+    super("Encapsulated PostScript", new String[] {"eps", "epsi"});
+  }
+
+  // -- IFormatWriter API methods --
+
+  /* @see loci.formats.IFormatWriter#saveImage(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    if (image == null) {
+      throw new FormatException("Image is null");
+    }
+
+    out = new RandomAccessFile(currentId, "rw");
+
+    BufferedImage img = (cm == null) ?
+      ImageTools.makeBuffered(image) : ImageTools.makeBuffered(image, cm);
+
+    // get the width and height of the image
+    int width = img.getWidth();
+    int height = img.getHeight();
+
+    // retrieve pixel data for this plane
+    byte[][] byteData = ImageTools.getBytes(img);
+
+    // write the header
+
+    DataTools.writeString(out, "%!PS-Adobe-2.0 EPSF-1.2\n");
+    DataTools.writeString(out, "%%Title: " + currentId + "\n");
+    DataTools.writeString(out, "%%Creator: LOCI Bio-Formats\n");
+    DataTools.writeString(out, "%%Pages: 1\n");
+    DataTools.writeString(out, "%%BoundingBox: 0 0 " + width +
+      " " + height + "\n");
+    DataTools.writeString(out, "%%EndComments\n\n");
+
+    DataTools.writeString(out, "/ld {load def} bind def\n");
+    DataTools.writeString(out, "/s /stroke ld /f /fill ld /m /moveto ld /l " +
+      "/lineto ld /c /curveto ld /rgb {255 div 3 1 roll 255 div 3 1 " +
+      "roll 255 div 3 1 roll setrgbcolor} def\n");
+    DataTools.writeString(out, "0 0 translate\n");
+    DataTools.writeString(out, ((float) width) + " " + ((float) height) +
+      " scale\n");
+    DataTools.writeString(out, "/picstr 40 string def\n");
+    if (byteData.length == 1) {
+      DataTools.writeString(out, width + " " + height + " 8 [" + width +
+        " 0 0 " + (-1*height) + " 0 " + height + "] {currentfile picstr " +
+        "readhexstring pop} image\n");
+
+      // write pixel data
+      // for simplicity, write 80 char lines
+
+      int charCount = 0;
+      for (int i=0; i<byteData[0].length; i++) {
+        for (int j=0; j<1; j++) {
+          String s = Integer.toHexString(byteData[j][i]);
+          // only want last 2 characters of s
+          if (s.length() > 1) s = s.substring(s.length() - 2);
+          else s = "0" + s;
+          DataTools.writeString(out, s);
+          charCount++;
+          if (charCount == 40) {
+            DataTools.writeString(out, "\n");
+            charCount = 0;
+          }
+        }
+      }
+    }
+    else {
+      DataTools.writeString(out, width + " " + height + " 8 [" + width +
+        " 0 0 " + (-1*height) + " 0 " + height + "] {currentfile picstr " +
+        "readhexstring pop} false 3 colorimage\n");
+
+      // write pixel data
+      // for simplicity, write 80 char lines
+
+      int charCount = 0;
+      for (int i=0; i<byteData[0].length; i++) {
+        for (int j=0; j<byteData.length; j++) {
+          String s = Integer.toHexString(byteData[j][i]);
+          // only want last 2 characters of s
+          if (s.length() > 1) s = s.substring(s.length() - 2);
+          else s = "0" + s;
+          DataTools.writeString(out, s);
+          charCount++;
+          if (charCount == 40) {
+            DataTools.writeString(out, "\n");
+            charCount = 0;
+          }
+        }
+      }
+    }
+
+    // write footer
+
+    DataTools.writeString(out, "showpage\n");
+    out.close();
+  }
+
+  /* @see loci.formats.IFormatWriter#getPixelTypes() */
+  public int[] getPixelTypes() {
+    return new int[] {FormatTools.UINT8};
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    if (out != null) out.close();
+    out = null;
+    currentId = null;
+    initialized = false;
+  }
+
+}
diff --git a/loci/formats/out/ImageIOWriter.java b/loci/formats/out/ImageIOWriter.java
new file mode 100644
index 0000000..61e9dcf
--- /dev/null
+++ b/loci/formats/out/ImageIOWriter.java
@@ -0,0 +1,103 @@
+//
+// ImageIOWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import javax.imageio.ImageIO;
+import loci.formats.*;
+
+/**
+ * ImageIOWriter is the superclass for file format writers that use the
+ * javax.imageio library.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/ImageIOWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/ImageIOWriter.java">SVN</a></dd></dl>
+ *
+ * @author Curtis Rueden ctrueden at wisc.edu
+ */
+public abstract class ImageIOWriter extends FormatWriter {
+
+  // -- Fields --
+
+  protected String kind;
+  protected DataOutputStream out;
+
+  // -- Constructors --
+
+  /**
+   * Constructs an ImageIO-based writer with the given name, default suffix
+   * and output type (e.g., png, jpeg).
+   */
+  public ImageIOWriter(String format, String suffix, String kind) {
+    super(format, suffix);
+    this.kind = kind;
+  }
+
+  /**
+   * Constructs an ImageIO-based writer with the given name, default suffixes
+   * and output type (e.g., png, jpeg). */
+  public ImageIOWriter(String format, String[] suffixes, String kind) {
+    super(format, suffixes);
+    this.kind = kind;
+  }
+
+  // -- IFormatWriter API methods --
+
+  /* @see loci.formats.IFormatWriter#saveImage(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    BufferedImage img = (cm == null) ?
+      ImageTools.makeBuffered(image) : ImageTools.makeBuffered(image, cm);
+    if (ImageTools.getPixelType(img) == FormatTools.FLOAT) {
+      throw new FormatException("Floating point data not supported.");
+    }
+    out = new DataOutputStream(new BufferedOutputStream(
+      new FileOutputStream(currentId), 4096));
+    ImageIO.write(img, kind, out);
+  }
+
+  /* @see loci.formats.IFormatWriter#getPixelTypes() */
+  public int[] getPixelTypes() {
+    return new int[] {FormatTools.UINT8, FormatTools.UINT16};
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    if (out != null) out.close();
+    out = null;
+    currentId = null;
+    initialized = false;
+  }
+
+}
diff --git a/loci/formats/out/JPEGWriter.java b/loci/formats/out/JPEGWriter.java
new file mode 100644
index 0000000..afdfc54
--- /dev/null
+++ b/loci/formats/out/JPEGWriter.java
@@ -0,0 +1,68 @@
+//
+// JPEGWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * JPEGWriter is the file format writer for JPEG files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/JPEGWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/JPEGWriter.java">SVN</a></dd></dl>
+ */
+public class JPEGWriter extends ImageIOWriter {
+
+  // -- Constructor --
+
+  public JPEGWriter() {
+    super("Joint Photographic Experts Group",
+      new String[] {"jpg", "jpeg", "jpe"}, "jpeg");
+  }
+
+  // -- IFormatWriter API methods --
+
+  /* @see loci.formats.IFormatWriter#save(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    BufferedImage img = (cm == null) ?
+      ImageTools.makeBuffered(image) : ImageTools.makeBuffered(image, cm);
+    int type = ImageTools.getPixelType(img);
+    if (type == FormatTools.UINT16 || type == FormatTools.INT16) {
+      throw new FormatException("16-bit data not supported.");
+    }
+    super.saveImage(image, last);
+  }
+
+  /* @see loci.formats.IFormatWriter#getPixelTypes(String) */
+  public int[] getPixelTypes() {
+    return new int[] {FormatTools.UINT8};
+  }
+
+}
diff --git a/loci/formats/out/LegacyQTWriter.java b/loci/formats/out/LegacyQTWriter.java
new file mode 100644
index 0000000..9af5325
--- /dev/null
+++ b/loci/formats/out/LegacyQTWriter.java
@@ -0,0 +1,294 @@
+//
+// LegacyQTWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import loci.formats.*;
+
+/**
+ * LegacyQTWriter is a file format writer for QuickTime movies. It uses the
+ * QuickTime for Java library, and allows the user to choose between a variety
+ * of common video codecs.
+ *
+ * Much of this code was based on the QuickTime Movie Writer for ImageJ
+ * (available at http://rsb.info.nih.gov/ij/plugins/movie-writer.html).
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/LegacyQTWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/LegacyQTWriter.java">SVN</a></dd></dl>
+ */
+public class LegacyQTWriter extends FormatWriter {
+
+  // -- Constants --
+
+  /** Time scale. */
+  private static final int TIME_SCALE = 600;
+
+  // -- Fields --
+
+  /** Instance of LegacyQTTools to handle QuickTime for Java detection. */
+  protected LegacyQTTools tools;
+
+  /** Reflection tool for QuickTime for Java calls. */
+  protected ReflectedUniverse r;
+
+  /** The codec to use. */
+  protected int codec = QTWriter.CODEC_RAW;
+
+  /** The quality to use. */
+  protected int quality = QTWriter.QUALITY_NORMAL;
+
+  /** Number of frames written. */
+  private int numWritten = 0;
+
+  /** Frame width. */
+  private int width;
+
+  /** Frame height. */
+  private int height;
+
+  private int[] pixels2 = null;
+
+  // -- Constructor --
+
+  public LegacyQTWriter() {
+    super("Legacy QuickTime", "mov");
+  }
+
+  // -- LegacyQTWriter API methods --
+
+  /**
+   * Sets the encoded movie's codec.
+   * @param codec Codec value:<ul>
+   *   <li>QTWriter.CODEC_CINEPAK</li>
+   *   <li>QTWriter.CODEC_ANIMATION</li>
+   *   <li>QTWriter.CODEC_H_263</li>
+   *   <li>QTWriter.CODEC_SORENSON</li>
+   *   <li>QTWriter.CODEC_SORENSON_3</li>
+   *   <li>QTWriter.CODEC_MPEG_4</li>
+   *   <li>QTWriter.CODEC_RAW</li>
+   * </ul>
+   */
+  public void setCodec(int codec) { this.codec = codec; }
+
+  /**
+   * Sets the quality of the encoded movie.
+   * @param quality Quality value:<ul>
+   *   <li>QTWriter.QUALITY_LOW</li>
+   *   <li>QTWriter.QUALITY_MEDIUM</li>
+   *   <li>QTWriter.QUALITY_HIGH</li>
+   *   <li>QTWriter.QUALITY_MAXIMUM</li>
+   * </ul>
+   */
+  public void setQuality(int quality) { this.quality = quality; }
+
+  // -- IFormatWriter API methods --
+
+  /* @see loci.formats.IFormatWriter#saveImage(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    if (tools == null) {
+      tools = new LegacyQTTools();
+      r = tools.getUniverse();
+    }
+
+    if (tools.isQTExpired()) {
+      throw new FormatException(LegacyQTTools.EXPIRED_QT_MSG);
+    }
+    if (!tools.canDoQT()) throw new FormatException(LegacyQTTools.NO_QT_MSG);
+
+    if (!initialized) {
+      initialized = true;
+
+      try {
+        r.exec("QTSession.open()");
+        BufferedImage img = ImageTools.makeBuffered(image);
+        width = img.getWidth();
+        height = img.getHeight();
+        File f = new File(currentId);
+        r.setVar("f", f);
+        r.setVar("width", (float) width);
+        r.setVar("height", (float) height);
+
+        r.exec("movFile = new QTFile(f)");
+        r.setVar("val", -2147483648 | 268435456);
+        r.setVar("kMoviePlayer", 1414942532);
+        r.exec("movie = Movie.createMovieFile(movFile, kMoviePlayer, val)");
+        int timeScale = TIME_SCALE;
+        r.setVar("timeScale", timeScale);
+        r.setVar("zero", 0);
+        r.setVar("zeroFloat", (float) 0);
+        r.exec("videoTrack = movie.addTrack(width, height, zeroFloat)");
+        r.exec("videoMedia = new VideoMedia(videoTrack, timeScale)");
+        r.exec("videoMedia.beginEdits()");
+
+        r.setVar("pixelFormat", 32);
+        r.exec("imgDesc2 = new ImageDescription(pixelFormat)");
+        r.setVar("width", width);
+        r.setVar("height", height);
+        r.exec("imgDesc2.setWidth(width)");
+        r.exec("imgDesc2.setHeight(height)");
+
+        r.exec("gw = new QDGraphics(imgDesc2, zero)");
+        r.exec("bounds = new QDRect(zero, zero, width, height)");
+
+        r.exec("pixMap = gw.getPixMap()");
+        r.exec("pixSize = pixMap.getPixelSize()");
+        r.setVar("codec", codec);
+        r.setVar("quality", quality);
+
+        int rawImageSize = width * height * 4;
+        r.setVar("rawImageSize", rawImageSize);
+
+        r.setVar("boolTrue", true);
+        r.exec("imageHandle = new QTHandle(rawImageSize, boolTrue)");
+        r.exec("imageHandle.lock()");
+        r.exec("compressedImage = RawEncodedImage.fromQTHandle(imageHandle)");
+
+        r.setVar("rate", 30);
+
+        r.exec("seq = new CSequence(gw, bounds, pixSize, codec, " +
+          "CodecComponent.bestFidelityCodec, quality, quality, rate, null, " +
+          "zero)");
+
+        r.exec("imgDesc = seq.getDescription()");
+      }
+      catch (ReflectException e) {
+        trace(e);
+        throw new FormatException("Legacy QuickTime writer failed", e);
+      }
+    }
+
+    numWritten++;
+
+    try {
+      r.exec("pixMap = gw.getPixMap()");
+      r.exec("pixelData = pixMap.getPixelData()");
+
+      r.exec("intsPerRow = pixelData.getRowBytes()");
+      int intsPerRow = ((Integer) r.getVar("intsPerRow")).intValue() / 4;
+
+      byte[][] px = ImageTools.getBytes(ImageTools.makeBuffered(image));
+
+      int[] pixels = new int[px[0].length];
+      for (int i=0; i<pixels.length; i++) {
+        byte[] b = new byte[4];
+        for (int j=0; j<px.length; j++) {
+          b[j] = px[j][i];
+        }
+        for (int j=px.length; j<4; j++) {
+          b[j] = px[j % px.length][i];
+        }
+        pixels[i] = DataTools.bytesToInt(b, true);
+      }
+
+      if (pixels2 == null) pixels2 = new int[intsPerRow * height];
+      r.exec("nativeLittle = EndianOrder.isNativeLittleEndian()");
+      boolean nativeLittle =
+        ((Boolean) r.getVar("nativeLittle")).booleanValue();
+      if (nativeLittle) {
+        int offset1, offset2;
+        for (int y=0; y<height; y++) {
+          offset1 = y * width;
+          offset2 = y * intsPerRow;
+          for (int x=0; x<width; x++) {
+            r.setVar("thisByte", pixels[offset1++]);
+            r.exec("b = EndianOrder.flipBigEndianToNative32(thisByte)");
+            pixels2[offset2++] = ((Integer) r.getVar("b")).intValue();
+          }
+        }
+      }
+      else {
+        for (int i=0; i<height; i++) {
+          System.arraycopy(pixels, i*width, pixels2, i*intsPerRow, width);
+        }
+      }
+
+      r.setVar("pixels2", pixels2);
+      r.setVar("len", intsPerRow * height);
+
+      r.exec("pixelData.copyFromArray(zero, pixels2, zero, len)");
+      r.setVar("four", 4);
+      r.exec("cfInfo = seq.compressFrame(gw, bounds, four, compressedImage)");
+
+      // see developer.apple.com/qa/qtmcc/qtmcc20.html
+      r.exec("similarity = cfInfo.getSimilarity()");
+      int sim = ((Integer) r.getVar("similarity")).intValue();
+      r.setVar("syncSample", sim == 0);
+      r.exec("dataSize = cfInfo.getDataSize()");
+      r.setVar("fps", fps);
+      r.setVar("frameRate", 600);
+      r.setVar("rate", 600 / fps);
+      boolean sync = ((Boolean) r.getVar("syncSample")).booleanValue();
+      int syncSample = sync ? 0 : 1;
+
+      r.setVar("sync", syncSample);
+      r.setVar("one", 1);
+      r.exec("videoMedia.addSample(imageHandle, zero, dataSize, " +
+        "rate, imgDesc, one, sync)");
+    }
+    catch (ReflectException e) {
+      trace(e);
+      throw new FormatException("Legacy QuickTime writer failed", e);
+    }
+    if (last) {
+      try {
+        r.exec("videoMedia.endEdits()");
+        r.exec("duration = videoMedia.getDuration()");
+        r.setVar("floatOne", (float) 1.0);
+        r.exec("videoTrack.insertMedia(zero, zero, duration, floatOne)");
+        r.exec("omf = OpenMovieFile.asWrite(movFile)");
+        r.exec("name = movFile.getName()");
+        r.setVar("minusOne", -1);
+        r.exec("movie.addResource(omf, minusOne, name)");
+        r.exec("QTSession.close()");
+      }
+      catch (ReflectException e) {
+        trace(e);
+        throw new FormatException("Legacy QuickTime writer failed", e);
+      }
+    }
+  }
+
+  /* @see loci.formats.IFormatWriter#canDoStacks() */
+  public boolean canDoStacks() { return true; }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    r = null;
+    numWritten = 0;
+    width = 0;
+    height = 0;
+    pixels2 = null;
+    currentId = null;
+    initialized = false;
+  }
+
+}
diff --git a/loci/formats/out/OMETiffWriter.java b/loci/formats/out/OMETiffWriter.java
new file mode 100644
index 0000000..e6b87de
--- /dev/null
+++ b/loci/formats/out/OMETiffWriter.java
@@ -0,0 +1,76 @@
+//
+// OMETiffWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.io.IOException;
+import loci.formats.*;
+
+/**
+ * OMETiffWriter is the file format writer for OME-TIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/OMETiffWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/OMETiffWriter.java">SVN</a></dd></dl>
+ */
+public class OMETiffWriter extends TiffWriter {
+
+  // -- Constructor --
+
+  public OMETiffWriter() {
+    super("OME-TIFF", new String[] {"ome.tif", "ome.tiff"});
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    if (out != null) out.close();
+    out = null;
+    if (currentId != null) {
+      // extract OME-XML string from metadata object
+      MetadataRetrieve retrieve = getMetadataRetrieve();
+      String xml = MetadataTools.getOMEXML(retrieve);
+
+      // insert TiffData element
+      int pix = xml.indexOf("<Pixels ");
+      int end = xml.indexOf("/>", pix);
+      xml = xml.substring(0, end) + "><TiffData/></Pixels>" +
+        xml.substring(end + 2);
+
+      // write OME-XML to the first IFD's comment
+      try {
+        TiffTools.overwriteComment(currentId, xml);
+      }
+      catch (FormatException exc) {
+        IOException io =
+          new IOException("Unable to append OME-XML comment");
+        io.initCause(exc);
+        throw io;
+      }
+    }
+    super.close();
+  }
+
+}
diff --git a/loci/formats/out/PNGWriter.java b/loci/formats/out/PNGWriter.java
new file mode 100644
index 0000000..a02c44f
--- /dev/null
+++ b/loci/formats/out/PNGWriter.java
@@ -0,0 +1,42 @@
+//
+// PNGWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+/**
+ * PNGWriter is the file format writer for PNG files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/PNGWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/PNGWriter.java">SVN</a></dd></dl>
+ */
+public class PNGWriter extends ImageIOWriter {
+
+  // -- Constructor --
+
+  public PNGWriter() {
+    super("Portable Network Graphics", "png", "png");
+  }
+
+}
diff --git a/loci/formats/out/QTWriter.java b/loci/formats/out/QTWriter.java
new file mode 100644
index 0000000..d4c2569
--- /dev/null
+++ b/loci/formats/out/QTWriter.java
@@ -0,0 +1,618 @@
+//
+// QTWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.awt.Image;
+import java.awt.image.*;
+import java.io.*;
+import java.util.Vector;
+import loci.formats.*;
+
+/**
+ * QTWriter is the file format writer for uncompressed QuickTime movie files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/QTWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/QTWriter.java">SVN</a></dd></dl>
+ *
+ * @author Melissa Linkert linkert at wisc.edu
+ */
+
+public class QTWriter extends FormatWriter {
+
+  // -- Constants --
+
+  // NB: Writing to Motion JPEG-B with QTJava seems to be broken.
+  /** Value indicating Motion JPEG-B codec. */
+  public static final int CODEC_MOTION_JPEG_B = 1835692130;
+
+  /** Value indicating Cinepack codec. */
+  public static final int CODEC_CINEPAK = 1668704612;
+
+  /** Value indicating Animation codec. */
+  public static final int CODEC_ANIMATION = 1919706400;
+
+  /** Value indicating H.263 codec. */
+  public static final int CODEC_H_263 = 1748121139;
+
+  /** Value indicating Sorenson codec. */
+  public static final int CODEC_SORENSON = 1398165809;
+
+  /** Value indicating Sorenson 3 codec. */
+  public static final int CODEC_SORENSON_3 = 0x53565133;
+
+  /** Value indicating MPEG-4 codec. */
+  public static final int CODEC_MPEG_4 = 0x6d703476;
+
+  /** Value indicating Raw codec. */
+  public static final int CODEC_RAW = 0;
+
+  /** Value indicating Low quality. */
+  public static final int QUALITY_LOW = 256;
+
+  /** Value indicating Normal quality. */
+  public static final int QUALITY_NORMAL = 512;
+
+  /** Value indicating High quality. */
+  public static final int QUALITY_HIGH = 768;
+
+  /** Value indicating Maximum quality. */
+  public static final int QUALITY_MAXIMUM = 1023;
+
+  // -- Fields --
+
+  /** Current file. */
+  protected RandomAccessFile out;
+
+  /** The codec to use. */
+  protected int codec = CODEC_RAW;
+
+  /** The quality to use. */
+  protected int quality = QUALITY_NORMAL;
+
+  /** Number of planes written. */
+  protected int numWritten;
+
+  /** Seek to this offset to update the total number of pixel bytes. */
+  protected long byteCountOffset;
+
+  /** Total number of pixel bytes. */
+  protected int numBytes;
+
+  /** Vector of plane offsets. */
+  protected Vector offsets;
+
+  /** Time the file was created. */
+  protected int created;
+
+  /** Whether we need the legacy writer. */
+  protected boolean needLegacy = false;
+
+  /** Legacy QuickTime writer. */
+  protected LegacyQTWriter legacy;
+
+  // -- Constructor --
+
+  public QTWriter() {
+    super("QuickTime", "mov");
+    compressionTypes = new String[] {
+      "Uncompressed",
+      // NB: Writing to Motion JPEG-B with QTJava seems to be broken.
+      "Motion JPEG-B",
+      "Cinepak", "Animation", "H.263", "Sorenson", "Sorenson 3", "MPEG 4"
+    };
+
+  }
+
+  // -- QTWriter API methods --
+
+  /**
+   * Sets the encoded movie's codec.
+   * @param codec Codec value:<ul>
+   *   <li>QTWriter.CODEC_CINEPAK</li>
+   *   <li>QTWriter.CODEC_ANIMATION</li>
+   *   <li>QTWriter.CODEC_H_263</li>
+   *   <li>QTWriter.CODEC_SORENSON</li>
+   *   <li>QTWriter.CODEC_SORENSON_3</li>
+   *   <li>QTWriter.CODEC_MPEG_4</li>
+   *   <li>QTWriter.CODEC_RAW</li>
+   * </ul>
+   */
+  public void setCodec(int codec) { this.codec = codec; }
+
+  /**
+   * Sets the quality of the encoded movie.
+   * @param quality Quality value:<ul>
+   *   <li>QTWriter.QUALITY_LOW</li>
+   *   <li>QTWriter.QUALITY_MEDIUM</li>
+   *   <li>QTWriter.QUALITY_HIGH</li>
+   *   <li>QTWriter.QUALITY_MAXIMUM</li>
+   * </ul>
+   */
+  public void setQuality(int quality) { this.quality = quality; }
+
+  // -- IFormatWriter API methods --
+
+  /* @see loci.formats.IFormatWriter#saveImage(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    if (image == null) throw new FormatException("Image is null");
+    if (legacy == null) legacy = new LegacyQTWriter();
+
+    if (needLegacy) {
+      legacy.setId(currentId);
+      legacy.saveImage(image, last);
+      return;
+    }
+
+    BufferedImage img = (cm == null) ?
+      ImageTools.makeBuffered(image) : ImageTools.makeBuffered(image, cm);
+
+    // get the width and height of the image
+    int width = img.getWidth();
+    int height = img.getHeight();
+
+    // retrieve pixel data for this plane
+    byte[][] byteData = ImageTools.getPixelBytes(img, false);
+
+    // need to check if the width is a multiple of 8
+    // if it is, great; if not, we need to pad each scanline with enough
+    // bytes to make the width a multiple of 8
+
+    int pad = width % 4;
+    pad = (4 - pad) % 4;
+
+    int bytesPerPixel = byteData[0].length / (width * height);
+
+    if (bytesPerPixel > 1) {
+      throw new FormatException("Unsupported bits per pixel : " +
+        (8 * bytesPerPixel) + ".");
+    }
+
+    pad *= bytesPerPixel;
+
+    byte[][] temp = byteData;
+    byteData = new byte[temp.length][temp[0].length + height*pad];
+
+    int rowLength = width * bytesPerPixel;
+    for (int oldScanline=0; oldScanline<height; oldScanline++) {
+      for (int k=0; k<temp.length; k++) {
+        System.arraycopy(temp[k], oldScanline*rowLength, byteData[k],
+          oldScanline*(rowLength + pad), rowLength);
+      }
+    }
+
+    // invert each pixel
+    // this will makes the colors look right in other readers (e.g. xine),
+    // but needs to be reversed in QTReader
+
+    if (byteData.length == 1 && bytesPerPixel == 1) {
+      for (int i=0; i<byteData.length; i++) {
+        for (int k=0; k<byteData[0].length; k++) {
+          byteData[i][k] = (byte) (255 - byteData[i][k]);
+        }
+      }
+    }
+
+    if (!initialized) {
+      initialized = true;
+      setCodec();
+      if (codec != 0) {
+        needLegacy = true;
+        legacy.setCodec(codec);
+        legacy.setId(currentId);
+        legacy.saveImage(image, last);
+        return;
+      }
+
+      // -- write the header --
+
+      offsets = new Vector();
+      out = new RandomAccessFile(currentId, "rw");
+      created = (int) System.currentTimeMillis();
+      numWritten = 0;
+      numBytes = byteData.length * byteData[0].length;
+      byteCountOffset = 8;
+
+      if (out.length() == 0) {
+        // -- write the first header --
+
+        DataTools.writeInt(out, 8, false);
+        DataTools.writeString(out, "wide");
+
+        DataTools.writeInt(out, numBytes + 8, false);
+        DataTools.writeString(out, "mdat");
+      }
+      else {
+        out.seek(byteCountOffset);
+        numBytes = (int) DataTools.read4UnsignedBytes(out, false) - 8;
+        numWritten = numBytes / (byteData[0].length * byteData.length);
+
+        numBytes += byteData.length * byteData[0].length;
+
+        out.seek(byteCountOffset);
+        DataTools.writeInt(out, numBytes + 8, false);
+
+        for (int i=0; i<numWritten; i++) {
+          offsets.add(
+            new Integer(16 + i * byteData.length * byteData[0].length));
+        }
+
+        out.seek(out.length());
+      }
+
+      // -- write the first plane of pixel data (mdat) --
+
+      offsets.add(new Integer((int) out.length()));
+
+      numWritten++;
+
+      for (int i=0; i<byteData.length; i++) {
+        out.write(byteData[i]);
+      }
+    }
+    else {
+      // update the number of pixel bytes written
+      int planeOffset = numBytes;
+      numBytes += (byteData.length * byteData[0].length);
+      out.seek(byteCountOffset);
+      DataTools.writeInt(out, numBytes + 8, false);
+
+      // write this plane's pixel data
+      out.seek(out.length());
+
+      for (int i=0; i<byteData.length; i++) {
+        out.write(byteData[i]);
+      }
+
+      offsets.add(new Integer(planeOffset + 16));
+      numWritten++;
+    }
+
+    if (last) {
+      int timeScale = 100;
+      int duration = numWritten * (timeScale / fps);
+      int bitsPerPixel = (byteData.length > 1) ? bytesPerPixel * 24 :
+        bytesPerPixel * 8 + 32;
+      int channels = (bitsPerPixel >= 40) ? 1 : 3;
+
+      // -- write moov atom --
+
+      int atomLength = 685 + 8*numWritten;
+      DataTools.writeInt(out, atomLength, false);
+      DataTools.writeString(out, "moov");
+
+      // -- write mvhd atom --
+
+      DataTools.writeInt(out, 108, false);
+      DataTools.writeString(out, "mvhd");
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, created, false); // creation time
+      DataTools.writeInt(out, (int) System.currentTimeMillis(), false);
+      DataTools.writeInt(out, timeScale, false); // time scale
+      DataTools.writeInt(out, duration, false); // duration
+      out.write(new byte[] {0, 1, 0, 0});  // preferred rate & volume
+      out.write(new byte[] {0, -1, 0, 0, 0, 0, 0, 0, 0, 0}); // reserved
+
+      // 3x3 matrix - tells reader how to rotate image
+
+      DataTools.writeInt(out, 1, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 1, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 16384, false);
+
+      DataTools.writeShort(out, 0, false); // not sure what this is
+      DataTools.writeInt(out, 0, false); // preview duration
+      DataTools.writeInt(out, 0, false); // preview time
+      DataTools.writeInt(out, 0, false); // poster time
+      DataTools.writeInt(out, 0, false); // selection time
+      DataTools.writeInt(out, 0, false); // selection duration
+      DataTools.writeInt(out, 0, false); // current time
+      DataTools.writeInt(out, 2, false); // next track's id
+
+      // -- write trak atom --
+
+      atomLength -= 116;
+      DataTools.writeInt(out, atomLength, false);
+      DataTools.writeString(out, "trak");
+
+      // -- write tkhd atom --
+
+      DataTools.writeInt(out, 92, false);
+      DataTools.writeString(out, "tkhd");
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 15, false); // flags
+
+      DataTools.writeInt(out, created, false); // creation time
+      DataTools.writeInt(out, (int) System.currentTimeMillis(), false);
+      DataTools.writeInt(out, 1, false); // track id
+      DataTools.writeInt(out, 0, false); // reserved
+
+      DataTools.writeInt(out, duration, false); // duration
+      DataTools.writeInt(out, 0, false); // reserved
+      DataTools.writeInt(out, 0, false); // reserved
+      DataTools.writeShort(out, 0, false); // reserved
+
+      DataTools.writeInt(out, 0, false); // unknown
+
+      // 3x3 matrix - tells reader how to rotate the image
+
+      DataTools.writeInt(out, 1, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 1, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 0, false);
+      DataTools.writeInt(out, 16384, false);
+
+      DataTools.writeInt(out, width, false); // image width
+      DataTools.writeInt(out, height, false); // image height
+      DataTools.writeShort(out, 0, false); // reserved
+
+      // -- write edts atom --
+
+      DataTools.writeInt(out, 36, false);
+      DataTools.writeString(out, "edts");
+
+      // -- write elst atom --
+
+      DataTools.writeInt(out, 28, false);
+      DataTools.writeString(out, "elst");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, 1, false); // number of entries in the table
+      DataTools.writeInt(out, duration, false); // duration
+      DataTools.writeShort(out, 0, false); // time
+      DataTools.writeInt(out, 1, false); // rate
+      DataTools.writeShort(out, 0, false); // unknown
+
+      // -- write mdia atom --
+
+      atomLength -= 136;
+      DataTools.writeInt(out, atomLength, false);
+      DataTools.writeString(out, "mdia");
+
+      // -- write mdhd atom --
+
+      DataTools.writeInt(out, 32, false);
+      DataTools.writeString(out, "mdhd");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, created, false); // creation time
+      DataTools.writeInt(out, (int) System.currentTimeMillis(), false);
+      DataTools.writeInt(out, timeScale, false); // time scale
+      DataTools.writeInt(out, duration, false); // duration
+      DataTools.writeShort(out, 0, false); // language
+      DataTools.writeShort(out, 0, false); // quality
+
+      // -- write hdlr atom --
+
+      DataTools.writeInt(out, 58, false);
+      DataTools.writeString(out, "hdlr");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeString(out, "mhlr");
+      DataTools.writeString(out, "vide");
+      DataTools.writeString(out, "appl");
+      out.write(new byte[] {16, 0, 0, 0, 0, 1, 1, 11, 25});
+      DataTools.writeString(out, "Apple Video Media Handler");
+
+      // -- write minf atom --
+
+      atomLength -= 98;
+      DataTools.writeInt(out, atomLength, false);
+      DataTools.writeString(out, "minf");
+
+      // -- write vmhd atom --
+
+      DataTools.writeInt(out, 20, false);
+      DataTools.writeString(out, "vmhd");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 1, false); // flags
+      DataTools.writeShort(out, 64, false); // graphics mode
+      DataTools.writeShort(out, 32768, false);  // opcolor 1
+      DataTools.writeShort(out, 32768, false);  // opcolor 2
+      DataTools.writeShort(out, 32768, false);  // opcolor 3
+
+      // -- write hdlr atom --
+
+      DataTools.writeInt(out, 57, false);
+      DataTools.writeString(out, "hdlr");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeString(out, "dhlr");
+      DataTools.writeString(out, "alis");
+      DataTools.writeString(out, "appl");
+      out.write(new byte[] {16, 0, 0, 1, 0, 1, 1, 31, 24});
+      DataTools.writeString(out, "Apple Alias Data Handler");
+
+      // -- write dinf atom --
+
+      DataTools.writeInt(out, 36, false);
+      DataTools.writeString(out, "dinf");
+
+      // -- write dref atom --
+
+      DataTools.writeInt(out, 28, false);
+      DataTools.writeString(out, "dref");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeShort(out, 0, false); // version 2
+      DataTools.writeShort(out, 1, false); // flags 2
+      out.write(new byte[] {0, 0, 0, 12});
+      DataTools.writeString(out, "alis");
+      DataTools.writeShort(out, 0, false); // version 3
+      DataTools.writeShort(out, 1, false); // flags 3
+
+      // -- write stbl atom --
+
+      atomLength -= 121;
+      DataTools.writeInt(out, atomLength, false);
+      DataTools.writeString(out, "stbl");
+
+      // -- write stsd atom --
+
+      DataTools.writeInt(out, 118, false);
+      DataTools.writeString(out, "stsd");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, 1, false); // number of entries in the table
+      out.write(new byte[] {0, 0, 0, 102});
+      DataTools.writeString(out, "raw "); // codec
+      out.write(new byte[] {0, 0, 0, 0, 0, 0});  // reserved
+      DataTools.writeShort(out, 1, false); // data reference
+      DataTools.writeShort(out, 1, false); // version
+      DataTools.writeShort(out, 1, false); // revision
+      DataTools.writeString(out, "appl");
+      DataTools.writeInt(out, 0, false); // temporal quality
+      DataTools.writeInt(out, 768, false); // spatial quality
+      DataTools.writeShort(out, width, false); // image width
+      DataTools.writeShort(out, height, false); // image height
+      out.write(new byte[] {0, 72, 0, 0}); // horizontal dpi
+      out.write(new byte[] {0, 72, 0, 0}); // vertical dpi
+      DataTools.writeInt(out, 0, false); // data size
+      DataTools.writeShort(out, 1, false); // frames per sample
+      DataTools.writeShort(out, 12, false); // length of compressor name
+      DataTools.writeString(out, "Uncompressed"); // compressor name
+      DataTools.writeInt(out, bitsPerPixel, false); // unknown
+      DataTools.writeInt(out, bitsPerPixel, false); // unknown
+      DataTools.writeInt(out, bitsPerPixel, false); // unknown
+      DataTools.writeInt(out, bitsPerPixel, false); // unknown
+      DataTools.writeInt(out, bitsPerPixel, false); // unknown
+      DataTools.writeShort(out, bitsPerPixel, false); // bits per pixel
+      DataTools.writeInt(out, 65535, false); // ctab ID
+      out.write(new byte[] {12, 103, 97, 108}); // gamma
+      out.write(new byte[] {97, 1, -52, -52, 0, 0, 0, 0}); // unknown
+
+      // -- write stts atom --
+
+      DataTools.writeInt(out, 24, false);
+      DataTools.writeString(out, "stts");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, 1, false); // number of entries in the table
+      DataTools.writeInt(out, numWritten, false); // number of planes
+      DataTools.writeInt(out, (timeScale / fps), false); // frames per second
+
+      // -- write stsc atom --
+
+      DataTools.writeInt(out, 28, false);
+      DataTools.writeString(out, "stsc");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, 1, false); // number of entries in the table
+      DataTools.writeInt(out, 1, false); // chunk
+      DataTools.writeInt(out, 1, false); // samples
+      DataTools.writeInt(out, 1, false); // id
+
+      // -- write stsz atom --
+
+      DataTools.writeInt(out, 20 + 4*numWritten, false);
+      DataTools.writeString(out, "stsz");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, 0, false); // sample size
+      DataTools.writeInt(out, numWritten, false); // number of planes
+      for (int i=0; i<numWritten; i++) {
+        // sample size
+        DataTools.writeInt(out, channels*height*(width+pad)*bytesPerPixel,
+          false);
+      }
+
+      // -- write stco atom --
+
+      DataTools.writeInt(out, 16 + 4*numWritten, false);
+      DataTools.writeString(out, "stco");
+
+      DataTools.writeShort(out, 0, false); // version
+      DataTools.writeShort(out, 0, false); // flags
+      DataTools.writeInt(out, numWritten, false); // number of planes
+      for (int i=0; i<numWritten; i++) {
+        // write the plane offset
+        DataTools.writeInt(out, ((Integer) offsets.get(i)).intValue(), false);
+      }
+
+      out.close();
+    }
+  }
+
+  /* @see loci.formats.IFormatWriter#canDoStacks() */
+  public boolean canDoStacks() { return true; }
+
+  /* @see loci.formats.IFormatWriter#getPixelTypes(String) */
+  public int[] getPixelTypes() {
+    return new int[] {FormatTools.UINT8, FormatTools.UINT16};
+  }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    if (out != null) out.close();
+    out = null;
+    numWritten = 0;
+    byteCountOffset = 0;
+    numBytes = 0;
+    created = 0;
+    offsets = null;
+    currentId = null;
+    initialized = false;
+  }
+
+  // -- Helper methods --
+
+  private void setCodec() {
+    if (compression == null) return;
+    if (compression.equals("Uncompressed")) codec = CODEC_RAW;
+    // NB: Writing to Motion JPEG-B with QTJava seems to be broken.
+    else if (compression.equals("Motion JPEG-B")) codec = CODEC_MOTION_JPEG_B;
+    else if (compression.equals("Cinepak")) codec = CODEC_CINEPAK;
+    else if (compression.equals("Animation")) codec = CODEC_ANIMATION;
+    else if (compression.equals("H.263")) codec = CODEC_H_263;
+    else if (compression.equals("Sorenson")) codec = CODEC_SORENSON;
+    else if (compression.equals("Sorenson 3")) codec = CODEC_SORENSON_3;
+    else if (compression.equals("MPEG 4")) codec = CODEC_MPEG_4;
+  }
+
+}
diff --git a/loci/formats/out/TiffWriter.java b/loci/formats/out/TiffWriter.java
new file mode 100644
index 0000000..172c53e
--- /dev/null
+++ b/loci/formats/out/TiffWriter.java
@@ -0,0 +1,171 @@
+//
+// TiffWriter.java
+//
+
+/*
+LOCI Bio-Formats package for reading and converting biological file formats.
+Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+Eric Kjellman and Brian Loranger.
+
+This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+package loci.formats.out;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.util.*;
+import loci.formats.*;
+
+/**
+ * TiffWriter is the file format writer for TIFF files.
+ *
+ * <dl><dt><b>Source code:</b></dt>
+ * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/out/TiffWriter.java">Trac</a>,
+ * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/out/TiffWriter.java">SVN</a></dd></dl>
+ */
+public class TiffWriter extends FormatWriter {
+
+  // -- Fields --
+
+  /** The last offset written to. */
+  protected int lastOffset;
+
+  /** Current output stream. */
+  protected BufferedOutputStream out;
+
+  /** Image counts for each open series. */
+  protected Vector imageCounts;
+
+  // -- Constructors --
+
+  public TiffWriter() {
+    this("Tagged Image File Format", new String[] {"tif", "tiff"});
+  }
+
+  public TiffWriter(String format, String[] exts) {
+    super(format, exts);
+    lastOffset = 0;
+    compressionTypes = new String[] {"Uncompressed", "LZW"};
+  }
+
+  // -- TiffWriter API methods --
+
+  /**
+   * Saves the given image to the specified (possibly already open) file.
+   * The IFD hashtable allows specification of TIFF parameters such as bit
+   * depth, compression and units.  If this image is the last one in the file,
+   * the last flag must be set.
+   */
+  public void saveImage(Image image, Hashtable ifd, boolean last)
+    throws IOException, FormatException
+  {
+    saveImage(image, ifd, 0, last, last);
+  }
+
+  /**
+   * Saves the given image to the specified series in the current file.
+   * The IFD hashtable allows specification of TIFF parameters such as bit
+   * depth, compression and units. If this image is the last one in the series,
+   * the lastInSeries flag must be set. If this image is the last one in the
+   * file, the last flag must be set.
+   */
+  public void saveImage(Image image, Hashtable ifd, int series,
+    boolean lastInSeries, boolean last) throws IOException, FormatException
+  {
+    if (!initialized) {
+      imageCounts = new Vector();
+      initialized = true;
+      out =
+        new BufferedOutputStream(new FileOutputStream(currentId, true), 4096);
+
+      RandomAccessStream tmp = new RandomAccessStream(currentId);
+      if (tmp.length() == 0) {
+        DataOutputStream dataOut = new DataOutputStream(out);
+        dataOut.writeByte(TiffTools.BIG);
+        dataOut.writeByte(TiffTools.BIG);
+        dataOut.writeShort(TiffTools.MAGIC_NUMBER);
+        dataOut.writeInt(8); // offset to first IFD
+        lastOffset = 8;
+      }
+      else {
+        // compute the offset to the last IFD
+        TiffTools.checkHeader(tmp);
+        long offset = TiffTools.getFirstOffset(tmp);
+        long ifdMax = (tmp.length() - 8) / 18;
+
+        for (long ifdNum=0; ifdNum<ifdMax; ifdNum++) {
+          TiffTools.getIFD(tmp, ifdNum, offset);
+          offset = tmp.readInt();
+          if (offset <= 0 || offset >= tmp.length()) break;
+        }
+        lastOffset = (int) offset;
+      }
+      tmp.close();
+    }
+
+    BufferedImage img = (cm == null) ?
+      ImageTools.makeBuffered(image) : ImageTools.makeBuffered(image, cm);
+
+    lastOffset += TiffTools.writeImage(img, ifd, out, lastOffset, last);
+    if (last) close();
+  }
+
+  // -- IFormatWriter API methods --
+
+  /* @see loci.formats.IFormatWriter#saveImage(Image, boolean) */
+  public void saveImage(Image image, boolean last)
+    throws FormatException, IOException
+  {
+    saveImage(image, 0, last, last);
+  }
+
+  /* @see loci.formats.IFormatWriter#saveImage(Image, int, boolean, boolean) */
+  public void saveImage(Image image, int series, boolean lastInSeries,
+    boolean last) throws FormatException, IOException
+  {
+    Hashtable h = new Hashtable();
+    if (compression == null) compression = "";
+    h.put(new Integer(TiffTools.COMPRESSION), compression.equals("LZW") ?
+      new Integer(TiffTools.LZW) : new Integer(TiffTools.UNCOMPRESSED));
+    saveImage(image, h, series, lastInSeries, last);
+  }
+
+  /* @see loci.formats.IFormatWriter#canDoStacks(String) */
+  public boolean canDoStacks() { return true; }
+
+  // -- IFormatHandler API methods --
+
+  /* @see loci.formats.IFormatHandler#close() */
+  public void close() throws IOException {
+    if (out != null) out.close();
+    out = null;
+    currentId = null;
+    initialized = false;
+    lastOffset = 0;
+  }
+
+  // -- Deprecated API methods --
+
+  /** @deprecated Replaced by {@link #saveImage(Image, Hashtable, boolean)} */
+  public void saveImage(String id, Image image, Hashtable ifd, boolean last)
+    throws IOException, FormatException
+  {
+    setId(id);
+    saveImage(image, ifd, last);
+  }
+
+}
diff --git a/loci/formats/package.html b/loci/formats/package.html
new file mode 100644
index 0000000..c69d3bf
--- /dev/null
+++ b/loci/formats/package.html
@@ -0,0 +1,4 @@
+<html><body>
+<b>Bio-Formats</b>: a library for reading and writing popular microscopy file
+formats.
+</body></html>
diff --git a/loci/formats/readers.txt b/loci/formats/readers.txt
new file mode 100644
index 0000000..11972b3
--- /dev/null
+++ b/loci/formats/readers.txt
@@ -0,0 +1,94 @@
+#
+# readers.txt
+#
+
+# LOCI Bio-Formats package for reading and converting biological file formats.
+# Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+# Eric Kjellman and Brian Loranger.
+#
+# This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+# This document is a configuration file identifying all file format readers
+# available to Bio-Formats, and the order in which they should be used.
+# Please do not edit unless you know what you are doing (see reader-guide.txt).
+
+# javax.imageio readers
+loci.formats.in.PNGReader          # png [javax.imageio]
+loci.formats.in.JPEGReader         # jpg, jpeg [javax.imageio]
+
+# standalone readers with unique file extensions
+loci.formats.in.PGMReader          # pgm
+loci.formats.in.FitsReader         # fits
+loci.formats.in.GIFReader          # gif
+loci.formats.in.BMPReader          # bmp
+loci.formats.in.DicomReader        # dcm, dicom
+loci.formats.in.BioRadReader       # pic
+loci.formats.in.IPLabReader        # ipl
+loci.formats.in.DeltavisionReader  # dv, r3d
+loci.formats.in.MRCReader          # mrc
+loci.formats.in.GatanReader        # dm3
+loci.formats.in.ImarisReader       # ims
+loci.formats.in.OpenlabRawReader   # raw
+loci.formats.in.OMEXMLReader       # ome
+loci.formats.in.LIFReader          # lif
+loci.formats.in.AVIReader          # avi
+loci.formats.in.QTReader           # mov
+loci.formats.in.PictReader         # pict, pct
+loci.formats.in.SDTReader          # sdt
+loci.formats.in.EPSReader          # eps, epsi
+loci.formats.in.SlidebookReader    # sld
+loci.formats.in.AliconaReader      # al3d
+loci.formats.in.MNGReader          # mng
+loci.formats.in.NRRDReader         # nrrd, nhdr
+loci.formats.in.KhorosReader       # xv
+loci.formats.in.VisitechReader     # html, xys
+loci.formats.in.LIMReader          # lim
+loci.formats.in.PSDReader          # psd
+
+# multi-extension messes
+loci.formats.in.ICSReader          # ics, ids
+loci.formats.in.OIFReader          # oif, various
+loci.formats.in.PerkinElmerReader  # rec, ano, csv, htm, tim, zpo, 2, 3, 4, ...
+
+# readers requiring third-party libraries
+loci.formats.in.ZeissZVIReader     # zvi [POIFS]
+loci.formats.in.OIBReader          # oib [POIFS]
+loci.formats.in.IPWReader          # ipw [POIFS]
+loci.formats.in.ND2Reader          # nd2, jp2 [JAI-ImageIO]
+loci.formats.in.PCIReader          # cxd [POIFS]
+
+# TIFF-based readers with unique file extensions
+loci.formats.in.MetamorphReader    # stk
+loci.formats.in.ZeissLSMReader     # lsm
+loci.formats.in.SEQReader          # seq
+loci.formats.in.GelReader          # gel
+loci.formats.in.ImarisTiffReader   # ims
+loci.formats.in.FlexReader         # flex
+
+# TIFF-based readers with slow isThisType
+loci.formats.in.LeicaReader        # lei, tif
+loci.formats.in.NikonReader        # nef, tif
+loci.formats.in.FluoviewReader     # tif
+loci.formats.in.PrairieReader      # xml, cfg, tif
+loci.formats.in.MicromanagerReader # txt, tif
+loci.formats.in.ImprovisionTiffReader # tif
+loci.formats.in.TCSReader # tif
+loci.formats.in.OMETiffReader # tif
+
+# standard TIFF reader must go last (it accepts any TIFF)
+loci.formats.in.TiffReader         # tif, tiff
+
+# non-TIFF readers with slow isThisType
+loci.formats.in.OpenlabReader      # liff
diff --git a/loci/formats/writers.txt b/loci/formats/writers.txt
new file mode 100644
index 0000000..de28c4c
--- /dev/null
+++ b/loci/formats/writers.txt
@@ -0,0 +1,33 @@
+#
+# writers.txt
+#
+
+# LOCI Bio-Formats package for reading and converting biological file formats.
+# Copyright (C) 2005- at year@ Melissa Linkert, Curtis Rueden, Chris Allan,
+# Eric Kjellman and Brian Loranger.
+#
+# This program 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 program is distributed in the hope that 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 program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+# This document is a configuration file identifying all file format writers
+# available to Bio-Formats, and the order in which they should be used.
+# Please do not edit unless you know what you are doing.
+
+loci.formats.out.OMETiffWriter # ome.tif, ome.tiff
+loci.formats.out.TiffWriter # tif, tiff
+loci.formats.out.JPEGWriter # jpg, jpeg
+loci.formats.out.PNGWriter  # png
+loci.formats.out.AVIWriter  # avi
+loci.formats.out.QTWriter   # mov
+loci.formats.out.EPSWriter  # eps
diff --git a/ncsa/hdf/hdf5lib/H5.java b/ncsa/hdf/hdf5lib/H5.java
new file mode 100644
index 0000000..d1e9ef5
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/H5.java
@@ -0,0 +1,3795 @@
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib;
+
+import java.io.*;
+import java.net.*;
+import java.lang.*;
+import java.util.*;
+import ncsa.hdf.hdf5lib.exceptions.*;
+
+
+/**
+ *  <hr>
+ *  <p>
+ *  <center>
+ *  <b>This class is the Java interface for the HDF5 library</b>
+ *  </center>
+ *  <p>
+ *  This code is the called by Java programs to access the
+ *  entry points of the HDF5 1.2 library.
+ *  Each routine wraps a single HDF5 entry point, generally with the
+ *  arguments and return codes analogous to the C interface.
+ *  <p>
+ *  For details of the HDF5 library, see the HDF5 Documentation at:
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/">http://hdf.ncsa.uiuc.edu/HDF5/</a>
+ *  <hr>
+ *  <p>
+ *  <b>Mapping of arguments for Java</b>
+ *
+ *  <p>
+ *  In general, arguments to the HDF Java API are straightforward
+ *  translations from the 'C' API described in the HDF Reference
+ *  Manual.
+ *  <p>
+ *
+ *  <center>
+ *  <table border=2 cellpadding=2>
+ *  <caption><b>HDF-5 C types to Java types</b>   </caption>
+ *  <tr><td> <b>HDF-5</b>       </td><td> <b>Java</b>    </td></tr>
+ *  <tr><td> H5T_NATIVE_INT     </td><td> int, Integer   </td></tr>
+ *  <tr><td> H5T_NATIVE_SHORT   </td><td> short, Short   </td></tr>
+ *  <tr><td> H5T_NATIVE_FLOAT   </td><td> float, Float   </td></tr>
+ *  <tr><td> H5T_NATIVE_DOUBLE  </td><td> double, Double </td></tr>
+ *  <tr><td> H5T_NATIVE_CHAR    </td><td> byte, Byte     </td></tr>
+ *  <tr><td> H5T_C_S1           </td><td> java.lang.String    </td></tr>
+ *  <tr><td> void * <BR>(i.e., pointer to `Any')     </td><td> Special -- see HDFArray </td></tr>
+ *  </table>
+ *  </center>
+ *  <p>
+ *  <center>
+ *  <b>General Rules for Passing Arguments and Results</b>
+ *  </center>
+ *  <p>
+ *  In general, arguments passed <b>IN</b> to Java are the analogous basic types, as above.
+ *  The exception is for arrays, which are discussed below.
+ *  <p>
+ *  The <i>return value</i> of Java methods is also the analogous type, as above.
+ *  A major exception to that rule is that all HDF functions that
+ *  return SUCCEED/FAIL are declared <i>boolean</i> in the Java version, rather than
+ *  <i>int</i> as in the C.
+ *  Functions that return a value or else FAIL are declared
+ *  the equivalent to the C function.
+ *  However, in most cases the Java method will raise an exception instead
+ *  of returning an error code.  See 
+ *  <a href="#ERRORS">Errors and Exceptions</a> below.
+ *  <p>
+ *  Java does not support pass by reference of arguments, so
+ *  arguments that are returned through <b>OUT</b> parameters
+ *  must be wrapped in an object or array.
+ *  The Java API for HDF consistently wraps arguments in
+ *  arrays.
+ *  <p>
+ *  For instance, a function that returns two integers is
+ *  declared:
+ *  <p>
+ *  <pre>
+ *       h_err_t HDF5dummy( int *a1, int *a2)
+ *  </pre>
+ *  For the Java interface, this would be declared:
+ *  <p>
+ *  <pre>
+ *       public static native int HDF5dummy( int args[] );
+ *  </pre>
+ *  where <i>a1</i> is <i>args[0]</i>
+ *  and <i>a2</i> is <i>args[1]</i>, and would be invoked:
+ *  <p>
+ *  <pre>
+ *       H5.HDF5dummy( a );
+ *  </pre>
+ *  <p>
+ *  All the routines where this convention is used will have
+ *  specific documentation of the details, given below.
+ *  <p>
+ *  <a NAME="ARRAYS">
+ *  <b>Arrays</b>
+ *  </a>
+ *  <p>
+ *  HDF5 needs to read and write multi-dimensional arrays
+ *  of any number type (and records).
+ *  The HDF5 API describes the layout of the source and destination, 
+ *  and the data for the array passed as a block of bytes, for instance,
+ *  <p>
+ *  <pre>
+ *      herr_t H5Dread(int fid, int filetype, int memtype, int memspace, 
+ *      void * data);
+ *  </pre>
+ *  <p>
+ *  where ``void *'' means that the data may be any valid numeric
+ *  type, and is a contiguous block of bytes that is the data
+ *  for a multi-dimensional array.  The other parameters describe the
+ *  dimensions, rank, and datatype of the array on disk (source) and
+ *  in memory (destination).  
+ *  <p>
+ *  For Java, this ``ANY'' is a problem, as the type of data must
+ *  always be declared.  Furthermore, multidimensional arrays
+ *  are definitely <i>not</i> layed out contiguously
+ *  in memory.
+ *  It would be infeasible to declare a separate routine for
+ *  every combination of number type and dimensionality.
+ *  For that reason, the 
+ *  <a href="./ncsa.hdf.hdf5lib.HDFArray.html><b>HDFArray</b></a> 
+ *  class is used to
+ *  discover the type, shape, and size of the data array
+ *  at run time, and to convert to and from a contiguous array
+ *  of bytes in static native C order.
+ *  <p>
+ *  The upshot is that any Java array of numbers (either primitive
+ *  or sub-classes of type <b>Number</b>) can be passed as 
+ *  an ``Object'', and the Java API will translate to and from 
+ *  the appropriate packed array of bytes needed by the C library.
+ *  So the function above would be declared:
+ *  <p>
+ *  <pre>
+ *      public static native int H5Dread(int fid, int filetype, 
+ *          int memtype, int memspace, Object data);
+ *  </pre>
+ *  and the parameter <i>data</i> can be any multi-dimensional
+ *  array of numbers, such as float[][], or int[][][], or Double[][].
+ *  <p>
+ *  <a NAME="CONSTANTS">
+ *  <b>HDF-5 Constants</b>
+ *  <p>
+ *  The HDF-5 API defines a set of constants and enumerated values.
+ *  Most of these values are available to Java programs via the class 
+ *  <a href="./ncsa.hdf.hdf5lib.HDF5Constants.html">
+ *  <b>HDF5Constants</b></a>.
+ *  For example, the parameters for the h5open() call include two
+ *  numeric values, <b><i>HDFConstants.H5F_ACC_RDWR</i></b> and 
+ *  <b><i>HDF5Constants.H5P_DEFAULT</i></b>.  As would be expected, 
+ *  these numbers correspond to the C constants <b><i>H5F_ACC_RDWR</i></b>
+ *  and <b><i>H5P_DEFAULT</i></b>.
+ *  <p>
+ *  The HDF-5 API defines a set of values that describe number types and
+ *  sizes, such as "H5T_NATIVE_INT" and "hsize_t". These values are
+ *  determined at run time by the HDF-5 C library. 
+ *  To support these parameters,
+ *  the Java class 
+ *  <a href="./ncsa.hdf.hdf5lib.HDF5CDataTypes.html">
+ *  <b>HDF5CDataTypes</b></a> looks up the values when 
+ *  initiated.  The values can be accessed as public variables of the 
+ *  Java class, such as:
+ *  <pre> int data_type = HDF5CDataTypes.JH5T_NATIVE_INT;</pre>
+ *  The Java application uses both types of constants the same way, the only
+ *  difference is that the <b><i>HDF5CDataTypes</i></b> may have different
+ *  values on different platforms.
+ *  <p>
+ *  <a NAME="ERRORS">
+ *  <b>Error handling and Exceptions</b>
+ *  <p>
+ *  The HDF5 error API (H5E) manages the behavior of the error stack in
+ *  the HDF-5 library. This API is omitted from the JHI5. Errors
+ *  are converted into Java exceptions. This is totally different from the
+ *  C interface, but is very natural for Java programming.
+ *  <p>
+ *  The exceptions of the JHI5 are organized as sub-classes of the class
+ *  <a href="./ncsa.hdf.hdf5lib.exceptions.HDF5Exception.html">
+ *  <b>HDF5Exception</b></a>.  There are two subclasses of 
+ *  <b>HDF5Exception</b>, 
+ *  <a href="./ncsa.hdf.hdf5lib.exceptions.HDF5LibraryException.html">
+ *  <b>HDF5LibraryException</b></a>
+ *  and 
+ *  <a href="./ncsa.hdf.hdf5lib.exceptions.HDF5JavaException.html">
+ *  <b>HDF5JavaException</b></a>. The sub-classes of the former
+ *  represent errors from the HDF-5 C library, while sub-classes of the latter
+ *  represent errors in the JHI5 wrapper and support code.
+ *  <p>
+ *  The super-class <b><i>HDF5LibraryException</i></b> implements the method
+ *  '<b><i>printStackTrace()</i></b>', which prints out the HDF-5 error stack,
+ *  as described in the HDF-5 C API <i><b>H5Eprint()</b>.</i> This may
+ *  be used by Java exception handlers to print out the HDF-5 error stack.
+ *  <hr>
+ *
+ *  @version HDF5 1.2 <BR>
+ *  <b>See also:
+ *  <a href ="./ncsa.hdf.hdf5lib.HDFArray.html">
+ *  </b> ncsa.hdf.hdf5lib.HDFArray</a><BR>
+ *  <a href ="./ncsa.hdf.hdf5lib.HDF5Constants.html">
+ *  </b> ncsa.hdf.hdf5lib.HDF5Constants</a><BR>
+ *  <a href ="./ncsa.hdf.hdf5lib.HDF5CDataTypes.html">
+ *  </b> ncsa.hdf.hdf5lib.HDF5CDataTypes</a><BR>
+ *  <a href ="./ncsa.hdf.hdf5lib.HDF5Exception.html">
+ *  ncsa.hdf.hdf5lib.HDF5Exception<BR>
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/">
+ *  http://hdf.ncsa.uiuc.edu/HDF5"</a>
+**/
+public class H5 {
+	public final static String H5PATH_PROPERTY_KEY = "ncsa.hdf.hdf5lib.H5.hdf5lib";
+
+	static 
+	{
+		String filename = null;
+		filename = System.getProperty(H5PATH_PROPERTY_KEY,null);
+		if ((filename != null) && (filename.length() > 0))
+		{
+			File h5dll = new File(filename);
+			if (h5dll.exists() && h5dll.canRead() && h5dll.isFile()) {
+				System.load(filename);
+			} else {
+				throw (new UnsatisfiedLinkError("Invalid HDF5 library, "+filename));
+			}
+		}
+		else {
+			System.loadLibrary("jhdf5");
+		}
+
+		/* Important!  Exit quietly */
+		try {
+		H5.H5dont_atexit();
+		} catch (HDF5LibraryException e) {
+			System.exit(1);
+		}
+
+		/* Important!  Disable error output to C stdout */
+		H5.H5error_off();  
+
+		/*  Optional:  confirm the version 
+                 *     This will crash immediately if not the
+                 *     specified version.
+		 */
+		Integer majnum = Integer.getInteger("ncsa.hdf.hdf5lib.H5.hdf5maj",null);
+		Integer minnum = Integer.getInteger("ncsa.hdf.hdf5lib.H5.hdf5min",null);
+		Integer relnum = Integer.getInteger("ncsa.hdf.hdf5lib.H5.hdf5rel",null);
+		if ((majnum != null) && (minnum != null) && (relnum != null)) {
+			H5.H5check_version(majnum.intValue(),minnum.intValue(),relnum.intValue());
+		}
+
+	}
+
+       //////////////////////////////////////////////////////////////////
+
+	/**
+	 *  J2C converts a Java constant to an HDF5 constant determined at runtime
+	 *
+	 *  @param java_constant The value of Java constant
+	 *  @return the value of an HDF5 constant determined at runtime
+	**/
+	public static native int J2C(int java_constant);
+
+	/** Turn off error handling 
+          * By default, the C library prints the error stack
+          * of the HDF-5 C library on stdout.  This behavior
+          * may be disabled by calling H5error_off().
+          */
+	private static native int H5error_off();
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//             H5: General Library Functions                //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+
+	/**
+	 *  H5open initialize the library.
+	 *
+	 *  @return a non-negative value if successful 
+	 *
+	 *  @exception HDF5LibraryException 
+	 *  - Error from the HDF-5 Library.
+	 **/
+	public static native int H5open() throws HDF5LibraryException;
+
+
+	/**
+	 *  H5close flushes all data to disk, closes all file 
+	 *  identifiers, and cleans up all memory used by the library.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException 
+	 *  - Error from the HDF-5 Library.
+	 **/
+	public static native int H5close() throws HDF5LibraryException;
+
+
+	/**
+	 *  H5dont_atexit indicates to the library that an atexit() 
+         *  cleanup routine should not be installed.  In order to be 
+         *  effective, this routine must be called before any other HDF
+	 *  function calls, and must be called each time the library 
+         *  is loaded/linked into the application (the first time and 
+         *  after it's been un-loaded).
+	 *  <P>
+	 *  This is called by the static initializer, so this should
+         *  never need to be explicitly called by a Java program.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	private static native int H5dont_atexit() throws HDF5LibraryException;
+
+	/**
+	 *  H5get_libversion retrieves the major, minor, and release 
+         *  numbers of the version of the HDF library which is linked 
+         *  to the application.
+	 *
+	 *  @param libversion The version information of the HDF library.
+	 *    <pre>
+	 *      libversion[0] = The major version of the library.
+	 *      libversion[1] = The minor version of the library.
+	 *      libversion[2] = The release number of the library.
+	 *    </pre>
+	 *  @return a non-negative value if successful, along with
+	 *  the version information. 
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5get_libversion(int[] libversion) throws HDF5LibraryException;
+
+
+	/**
+	 *  H5check_version verifies that the arguments match the version numbers
+	 *  compiled into the library.
+	 *
+	 *  @param majnum The major version of the library.
+	 *  @param minnum The minor version of the library.
+	 *  @param relnum The release number of the library.
+	 *  @param patnum The patch number of the library.
+	 *  @return a non-negative value if successful. 
+         *  Upon failure (when the versions do not match), this function
+	 *  causes the application to abort (i.e., crash)
+	 *
+	 *  @see C API function: herr_t H5check_version()
+	 **/
+	public static native int H5check_version(int majnum, int minnum, int relnum);
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//             H5E: Error Stack                             //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *   H5Eclear clears the error stack for the current thread.
+	 *   H5Eclear can fail if there are problems initializing the 
+	 *   library.
+	 *   <p>
+	 *   This may be used by exception handlers to assure that
+	 *   the error condition in the HDF-5 library has been reset.
+	 * 
+	 *   @return Returns a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Eclear() throws HDF5LibraryException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//             H5A: Attribute Interface Functions           //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Acreate creates an attribute which is attached to the 
+         *  object specified with loc_id.
+	 *
+	 *  @param loc_id IN: Object (dataset, group, or named datatype) to be attached to.
+	 *  @param name IN: Name of attribute to create.
+	 *  @param type_id IN: Identifier of datatype for attribute.
+	 *  @param space_id IN: Identifier of dataspace for attribute.
+	 *  @param create_plist IN: Identifier of creation property 
+	 *  list (currently not used).
+	 *
+	 *  @return an attribute identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Acreate(int loc_id, String name, 
+		int type_id, int space_id, int create_plist)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Aopen_name opens an attribute specified by its name, 
+	 *  name, which is attached to the object specified with loc_id.
+	 *
+	 *  @param loc_id  IN: Identifier of a group, dataset, or named datatype atttribute
+	 *  @param name IN: Attribute name.
+	 *
+	 *  @return attribute identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Aopen_name(int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Aopen_idx opens an attribute which is attached to the 
+	 *  object specified with loc_id.  The location object may 
+	 *  be either a group, dataset, or named datatype, all of 
+	 *  which may have any sort of attribute.
+	 *
+	 *  @param loc_id IN: Identifier of the group, dataset, or 
+	 *  named datatype attribute
+	 *  @param idx IN: Index of the attribute to open.
+	 *
+	 *  @return attribute identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Aopen_idx(int loc_id, int idx)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Awrite writes an attribute, specified with attr_id. The 
+	 *  attribute's memory datatype is specified with mem_type_id. 
+	 *  The entire attribute is written from buf to the file.
+	 *
+	 *  @param attr_id  IN: Identifier of an attribute to write.
+	 *  @param mem_type_id IN: Identifier of the attribute datatype 
+	 *  (in memory).
+	 *  @param buf IN: Data to be written.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - data is null.
+	 **/
+	public static native int H5Awrite(int attr_id, int mem_type_id, 
+		byte[] buf)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Awrite writes an attribute, specified with attr_id. The 
+	 *  attribute's memory datatype is specified with mem_type_id. 
+	 *  The entire attribute is written from data object to the file.
+	 *
+	 *  @param attr_id  IN: Identifier of an attribute to write.
+	 *  @param mem_type_id IN: Identifier of the attribute datatype 
+	 *  (in memory).
+	 *  @param obj IN: Data object to be written.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - data object is null.
+	 *  @see public static native int H5Awrite(int attr_id, int mem_type_id, byte[] buf);
+	 **/
+	public static int H5Awrite(int attr_id, int mem_type_id,
+	 Object obj )
+	 throws HDF5Exception, NullPointerException
+	{
+		HDFArray theArray = new HDFArray(obj);
+		byte[] buf = theArray.byteify();
+
+		int retVal = H5Awrite(attr_id, mem_type_id, buf);
+		buf = null;
+		theArray = null;
+		return retVal;
+	}
+
+
+	/**
+	 *  H5Aread reads an attribute, specified with attr_id. The 
+	 *  attribute's memory datatype is specified with mem_type_id. 
+	 *  The entire attribute is read into buf from the file.
+	 *
+	 *  @param attr_id IN: Identifier of an attribute to read.
+	 *  @param mem_type_id IN: Identifier of the attribute datatype 
+	 *  (in memory).
+	 *  @param buf IN: Buffer for data to be read.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - data buffer is null.
+	 **/
+	public static native int H5Aread(int attr_id, int mem_type_id, byte[] buf)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Aread reads an attribute, specified with attr_id. The 
+	 *  attribute's memory datatype is specified with mem_type_id. 
+	 *  The entire attribute is read into data object from the file.
+	 *
+	 *  @param attr_id IN: Identifier of an attribute to read.
+	 *  @param mem_type_id IN: Identifier of the attribute datatype 
+	 *  (in memory).
+	 *  @param obj IN: Object for data to be read.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - data buffer is null.
+	 *  @see public static native int H5Aread( )
+	**/
+	public static int H5Aread(int attr_id, int mem_type_id, Object obj)
+	 throws HDF5Exception, NullPointerException
+	{
+		HDFArray theArray = new HDFArray(obj);
+		byte[] buf = theArray.emptyBytes();
+
+		//  This will raise an exception if there is an error
+		int status = H5Aread(attr_id, mem_type_id, buf);
+
+		// No exception:  status really ought to be OK
+ 		if (status >= 0) {
+			obj = theArray.arrayify( buf);
+		}
+
+		return status;
+	}
+
+	/**
+	 *  H5Aget_space retrieves a copy of the dataspace for an 
+         *  attribute.
+	 *
+	 *  @param attr_id IN: Identifier of an attribute.
+	 *
+	 *  @return attribute dataspace identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Aget_space(int attr_id) throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Aget_type retrieves a copy of the datatype for an attribute.
+	 *
+	 *  @param attr_id  IN: Identifier of an attribute.
+	 *
+	 *  @return a datatype identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Aget_type(int attr_id) throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Aget_name retrieves the name of an attribute specified by 
+	 *  the identifier, attr_id.
+	 *
+	 *  @param attr_id  IN: Identifier of the attribute.
+	 *  @param buf_size  IN: The size of the buffer to store the 
+ 	 *  name in.
+	 *  @param name OUT: Buffer to store name in.
+	 *
+	 *  @exception ArrayIndexOutOfBoundsException  JNI error writing 
+	 *  back array
+	 *  @exception ArrayStoreException   JNI error writing back array
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 *  @exception IllegalArgumentException - bub_size <= 0.
+	 *
+	 *  @return the length of the attribute's name if successful.
+	 **/
+	public static native long H5Aget_name(int attr_id, long buf_size, 
+		String[] name)
+		throws ArrayIndexOutOfBoundsException, 
+		ArrayStoreException, 
+		HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  H5Aget_num_attrs returns the number of attributes attached 
+	 *  to the object specified by its identifier, loc_id.
+	 *
+	 *  @param loc_id  IN: Identifier of a group, dataset, or named 
+	 *  datatype.
+	 *
+	 *  @return the number of attributes if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Aget_num_attrs(int loc_id)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Adelete removes the attribute specified by its name, name, 
+	 *  from a dataset, group, or named datatype.
+	 *
+	 *  @param loc_id  IN: Identifier of the dataset, group, or 
+	 *  named datatype.
+	 *  @param name  IN: Name of the attribute to delete.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Adelete(int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Aclose terminates access to the attribute specified by 
+	 *  its identifier, attr_id.
+	 *
+	 *  @param attr_id  IN: Attribute to release access to.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Aclose(int attr_id)
+		throws HDF5LibraryException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//             H5D: Datasets Interface Functions            //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Dcreate creates a data set with a name, name, in the file 
+	 *  or in the  group specified by the identifier loc_id.
+	 *
+	 *  @param loc_id Identifier of the file or group to create the 
+	 *  dataset within.
+	 *  @param name The name of the dataset to create.
+	 *  @param type_id Identifier of the datatype to use when 
+	 *  creating the dataset.
+	 *  @param space_id Identifier of the dataspace to use when 
+	 *  creating the dataset.
+	 *  @param create_plist_id Identifier of the set creation 
+	 *  property list.
+	 *
+	 *  @return a dataset identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Dcreate(int loc_id, String name, 
+		int type_id, int space_id, int create_plist_id)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Dopen opens an existing dataset for access in the file or 
+	 *  group specified in loc_id.
+	 *
+	 *  @param loc_id  Identifier of the dataset to open or the file 
+	 *  or group
+	 *  @param name  The name of the dataset to access.
+	 *
+	 *  @return a dataset identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	**/
+	public static native int H5Dopen(int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Dget_space returns an identifier for a copy of the 
+	 *  dataspace for a dataset.
+	 *
+	 *  @param dataset_id Identifier of the dataset to query.
+	 *
+	 *  @return a dataspace identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Dget_space(int dataset_id) 
+		throws HDF5LibraryException ;
+
+
+	/**
+	 *  H5Dget_type returns an identifier for a copy of the datatype 
+	 *  for a dataset.
+	 *
+	 *  @param dataset_id Identifier of the dataset to query.
+	 *
+	 *  @return a datatype identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Dget_type(int dataset_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Dget_create_plist returns an identifier for a copy of the 
+	 *  dataset creation property list for a dataset.
+	 *
+	 *  @param dataset_id Identifier of the dataset to query.
+	 *  @return a dataset creation property list identifier
+	 *  if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Dget_create_plist(int dataset_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Dread reads a (partial) dataset, specified by its 
+	 *  identifier dataset_id, from the file into the application 
+	 *  memory buffer buf.
+	 *
+	 *  @param dataset_id  Identifier of the dataset read from.
+	 *  @param mem_type_id  Identifier of the memory datatype.
+	 *  @param mem_space_id  Identifier of the memory dataspace.
+	 *  @param file_space_id  Identifier of the dataset's dataspace 
+	 *  in the file.
+	 *  @param xfer_plist_id  Identifier of a transfer property 
+	 *  list for this I/O operation.
+	 *  @param buf Buffer to store data read from the file.
+	 * 
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - data buffer is null.
+	 **/
+	public static native int H5Dread(int dataset_id, int mem_type_id, 
+		int mem_space_id, int file_space_id, int xfer_plist_id, 
+		byte[] buf)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Dread reads a (partial) dataset, specified by its 
+	 *  identifier dataset_id, from the file into the application 
+	 *  data object.
+	 *
+	 *  @param dataset_id  Identifier of the dataset read from.
+	 *  @param mem_type_id  Identifier of the memory datatype.
+	 *  @param mem_space_id  Identifier of the memory dataspace.
+	 *  @param file_space_id  Identifier of the dataset's dataspace 
+	 *  in the file.
+	 *  @param xfer_plist_id  Identifier of a transfer property 
+	 *  list for this I/O operation.
+	 *  @param obj Object to store data read from the file.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5Exception - Failure in the data conversion.
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - data object is null.
+	 **/
+	public static int H5Dread(int dataset_id, int mem_type_id, 
+		int mem_space_id, int file_space_id, 
+		int xfer_plist_id, Object obj )
+		throws HDF5Exception, 
+		HDF5LibraryException, 
+		NullPointerException
+	{
+		/*  Create a data buffer to hold
+			the data into a Java Array */
+		HDFArray theArray = new HDFArray(obj);
+		byte[] buf = theArray.emptyBytes();
+
+		/*  will raise exception if read fails */
+		int status = H5Dread(dataset_id, mem_type_id, mem_space_id,
+			file_space_id, xfer_plist_id, buf);
+		if (status >= 0) {
+			/*  convert the data into a Java Array */
+			obj = theArray.arrayify( buf);
+		}
+		/* clean up these:  assign 'null' as hint to gc() */
+		buf = null;
+		theArray = null;
+		return status;
+	}
+
+
+	/**
+	 *  H5Dwrite writes a (partial) dataset, specified by its 
+	 *  identifier dataset_id, from the application memory buffer 
+	 *  buf into the file.
+	 *
+	 *  @param dataset_id  Identifier of the dataset read from.
+	 *  @param mem_type_id  Identifier of the memory datatype.
+	 *  @param mem_space_id  Identifier of the memory dataspace.
+	 *  @param file_space_id  Identifier of the dataset's dataspace 
+	 *  in the file.
+	 *  @param xfer_plist_id  Identifier of a transfer property 
+	 *  list for this I/O operation.
+	 *  @param buf Buffer with data to be written to the file.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Dwrite(int dataset_id, 
+		int mem_type_id, int mem_space_id,
+		int file_space_id, int xfer_plist_id, byte[] buf)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Dwrite writes a (partial) dataset, specified by its 
+	 *  identifier dataset_id, from the application memory data 
+	 *  object into the file.
+	 *
+	 *  @param dataset_id  Identifier of the dataset read from.
+	 *  @param mem_type_id  Identifier of the memory datatype.
+	 *  @param mem_space_id  Identifier of the memory dataspace.
+	 *  @param file_space_id  Identifier of the dataset's dataspace 
+	 *  in the file.
+	 *  @param xfer_plist_id  Identifier of a transfer property 
+	 *  list for this I/O operation.
+	 *  @param obj Object with data to be written to the file.
+	 * 
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5Exception - Failure in the data conversion.
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - data object is null.
+	 **/
+	public static int H5Dwrite(int dataset_id, int mem_type_id, int mem_space_id,
+		int file_space_id, int xfer_plist_id, Object obj )
+		throws HDF5Exception, 
+		HDF5LibraryException, 
+		NullPointerException
+	{
+		HDFArray theArray = new HDFArray(obj);
+		byte[] buf = theArray.byteify();
+
+		/* will raise exception on error */
+		int status = H5Dwrite(dataset_id, mem_type_id, 
+			mem_space_id, file_space_id, xfer_plist_id, buf);
+
+		/* clean up these:  assign 'null' as hint to gc() */
+		buf = null;
+		theArray = null;
+		return status;
+	}
+
+
+	/**
+	 *  H5Dextend verifies that the dataset is at least of size size.
+	 *
+	 *  @param dataset_id  Identifier of the dataset.
+	 *  @param size Array containing the new magnitude of each 
+	 *  dimension.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - size array is null.
+	 **/
+	public static native int H5Dextend(int dataset_id, long[] size)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Dclose ends access to a dataset specified by dataset_id and
+	 *  releases resources used by it.
+	 *
+	 *  @param dataset_id  Identifier of the dataset to finish 
+	 *  access to.
+	 *  
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Dclose(int dataset_id) 
+		throws HDF5LibraryException;
+
+	// following static native functions are missing from HDF5 RM version 1.0.1
+
+	/** H5Dget_storage_size returns the amount of storage that is 
+	 *  required for the dataset.
+	 *  
+	 *  @param dataset_id  Identifier of the dataset in question 
+	 *  
+	 *  @return he amount of storage space allocated for the dataset.
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native long H5Dget_storage_size(int dataset_id)
+		throws HDF5LibraryException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//             H5F: File Interface Functions                //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Fopen opens an existing file and is the primary function 
+	 *  for accessing existing HDF5 files.
+	 *
+	 *  @param name Name of the file to access.
+	 *  @param flags File access flags.
+	 *  @param access_id  Identifier for the file access properties 
+	 *  list.
+	 *
+	 *  @return a file identifier if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Fopen(String name, int flags, 
+		int access_id)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Fcreate is the primary function for creating HDF5 files.
+	 *
+	 *  @param name Name of the file to access.
+	 *  @param flags File access flags. Possible values include:
+	 *  <UL>
+	 *  <LI>
+	 *      H5F_ACC_RDWR Allow read and write access to file.
+	 *  </LI>
+	 *  <LI>
+	 *      H5F_ACC_RDONLY Allow read-only access to file.
+	 *  </LI>
+	 *  <LI>
+	 *      H5F_ACC_TRUNC Truncate file, if it already exists, 
+	 *      erasing all data previously stored in the file.
+	 *  </LI>
+	 *  <LI>
+	 *      H5F_ACC_EXCL Fail if file already exists.
+	 *  </LI>
+	 *  <LI>
+	 *      H5F_ACC_DEBUG Print debug information.
+	 *  </LI>
+	 *  <LI>
+	 *      H5P_DEFAULT Apply default file access and creation 
+	 *   properties.
+	 *  </LI>
+	 *  </UL>
+	 *  
+	 *  @param create_id  File creation property list identifier, 
+	 *  used when modifying default file meta-data.
+ 	 *  Use H5P_DEFAULT for default access properties.
+	 *  @param access_id File access property list identifier. 
+	 *  If parallel file access is desired,
+	 *  this is a collective call according to the communicator 
+	 *  stored in the access_id (not supported in Java). 
+ 	 *  Use H5P_DEFAULT for default access properties.
+ 	 *  
+	 *  @return a file identifier if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Fcreate(String name, int flags, 
+		int create_id, int access_id)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Fflush causes all buffers associated with a file or 
+	 *  object to be immediately flushed (written) to disk 
+	 *  without removing the data from the (memory) cache.  
+	 *  <P>
+	 *  After this call completes, the file (or object) is in 
+	 *  a consistent state and all data written to date is 
+	 *  assured to be permanent.
+	 *
+	 *  @param object_id  Identifier of object used to identify 
+	 *  the file.  <b>object_id</b> can be any object associated 
+	 *  with the file, including the file itself, a dataset, 
+	 *  a group, an attribute, or a named data type. 
+
+    scope specifies whether the scope of the flushing action is global or local. Valid values are 
+                    H5F_SCOPE_GLOBAL
+                                        
+                                       Flushes the entire virtual file.
+                    H5F_SCOPE_LOCAL
+                                       Flushes only the specified file.
+	 *  @param H5F_scope_t scope Specifies the scope of the 
+	 *  flushing action, in the case that the HDF-5 file is not 
+	 *  a single physical file.
+	 *  <P>
+	 *  Valid values are:
+	 *  <UL>
+	 *  <LI>
+         *           H5F_SCOPE_GLOBAL
+         *                               Flushes the entire virtual file.
+	 *  </LI>
+	 *  <LI>
+         *          H5F_SCOPE_LOCAL
+         *                             Flushes only the specified file.
+	 *  </LI>
+	 *  </UL>
+	 *  
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Fflush(int object_id, int scope) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Fis_hdf5 determines whether a file is in the HDF5 format.
+	 *
+	 *  @param name File name to check format.
+	 *
+	 *  @return true if is HDF-5, false if not.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native boolean H5Fis_hdf5(String name) 
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Fget_create_plist returns a file creation property list 
+	 *  identifier identifying the creation properties used to 
+	 *  create this file.
+	 *
+	 *  @param file_id  Identifier of the file to get creation 
+	 *  property list
+	 *
+	 *  @return a file creation property list identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Fget_create_plist(int file_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Fget_access_plist returns the file access property list 
+	 *  identifier of the specified file.
+	 *
+	 *  @param file_id  Identifier of file to get access property 
+	 *  list of
+	 *  
+	 *  @return a file access property list identifier if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Fget_access_plist(int file_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Fclose terminates access to an HDF5 file.
+	 *
+	 *  @param file_id  Identifier of a file to terminate access to.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Fclose(int file_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Fmount mounts the file specified by child_id onto the 
+	 *  group specified by loc_id and name using the mount 
+	 *  properties plist_id.
+	 *
+	 *  @param loc_id The identifier for the group onto which 
+	 *  the file specified by child_id is to be mounted.
+	 *  @param name  The name of the group onto which the file 
+	 *  specified by child_id is to be mounted.
+	 *  @param child_id The identifier of the file to be mounted.
+	 *  @param plist_id The identifier of the property list to be used.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Fmount(int loc_id, String name, 
+		int child_id, int plist_id)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  Given a mount point, H5Funmount dissassociates the mount 
+	 *  point's file from the file mounted there.
+	 *
+	 *  @param loc_id  The identifier for the location at which 
+	 *  the specified file is to be unmounted.
+	 *  @param name  The name of the file to be unmounted.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Funmount(int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Freopen reopens an HDF5 file.
+	 *
+	 *  @param file_id  Identifier of a file to terminate and
+	 *  reopen access to.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @return a new file identifier if successful
+	 **/
+	public static native int H5Freopen(int file_id) 
+		throws HDF5LibraryException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//             H5G: Group Interface Functions               //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Gcreate creates a new group with the specified name at 
+	 *  the specified location, loc_id.
+	 *
+	 *  @param loc_id The file or group identifier.
+	 *  @param name The absolute or relative name of the new group.
+	 *  @param size_hint An optional parameter indicating the 
+	 *  number of bytes to reserve for the names that will appear 
+	 *  in the group.
+	 *  
+	 *  @return a valid group identifier for the open group if 
+	 *  successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Gcreate(int loc_id, String name, 
+		int size_hint)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Gopen opens an existing group with the specified name 
+	 *  at the specified location, loc_id.
+	 *
+	 *  @param loc_id File or group identifier within which group 
+	 *  is to be open.
+	 *  @param name Name of group to open.
+	 *
+	 *  @return a valid group identifier if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Gopen(int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Gclose releases resources used by a group which was opened 
+	 *  by a call to H5Gcreate() or H5Gopen().
+	 *
+	 *  @param group_id  Group identifier to release.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Gclose(int group_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Glink creates a new name for an already existing object.
+	 *
+	 *  @param loc_id  File, group, dataset, or datatype identifier.
+	 *  @param H5G_link_t link_type Link type. Possible values are:
+	 *  <UL>
+	 *  <LI> 
+	 *  H5G_LINK_HARD 
+	 *  </LI>
+	 *  <LI>
+	 *  H5G_LINK_SOFT.
+	 *  </LI>
+	 *  </UL>
+	 *  @param current_name A name of the existing object if link is 
+	 *  a hard link. Can be anything for the soft link.
+	 *  @param new_name New name for the object.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - current_name or name is null.
+	 **/
+	public static native int H5Glink(int loc_id, int link_type,
+		String current_name, String new_name)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Gunlink removes an association between a name and an object.
+	 *
+	 *  @param loc_id  Identifier of the file containing the object.
+	 *  @param name Name of the object to unlink.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Gunlink(int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+	// extensions to the standard interface:  not in the Ref. Man.
+
+	/**
+	 *  @name H5Gn_members  report the number of objects in
+	 *        a Group.  The 'objects' include everything that
+	 *        will be visited by H5Giterate.  Each link is
+ 	 *        returned, so objects with multiple links will
+ 	 *        be counted once for each link.
+ 	 *        
+	 *  @param loc_id  file or group ID.
+	 *  @param name   name of the group to iterate, relative to 
+	 *  the loc_id
+ 	 *        
+	 *  @returns the number of members in the group or -1 if error.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 */
+	public static native int H5Gn_members( int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  @name H5Gget_obj_info_idx   report the name and type of
+	 *        object with index 'idx' in a Group.  The 'idx'
+	 *        corresponds to the index maintained by H5Giterate.  
+	 *	  Each link is returned, so objects with multiple 
+	 *        links will be counted once for each link.
+ 	 *        
+	 *  @param loc_id  IN:  file or group ID.
+	 *  @param name   IN:  name of the group to iterate, 
+	 *   relative to the loc_id
+	 *  @param idx   IN:  the index of the object to iterate.
+	 *  @param oname  the name of the object [OUT]
+	 *  @param type   the type of the object [OUT]
+	 *
+	 *  @returns non-negative if successful, -1 if not.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 */
+	public static native int H5Gget_obj_info_idx( int loc_id, 
+		String name, int idx, String[] oname, int[]type) 
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Gmove renames an object within an HDF5 file. 
+	 *  The original name, src, is unlinked from the group graph 
+	 *  and the new name, dst, is inserted as an atomic operation. 
+	 *  Both names are interpreted relative to loc_id, which is
+	 *  either a file or a group identifier.
+	 *
+	 *  @param loc_id File or group identifier.
+	 *  @paramsrc Object's original name.
+	 *  @paramdst Object's new name.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - src or dst is null.
+	 **/
+	public static native int H5Gmove(int loc_id, String src, 
+		String dst)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Gget_objinfo returns information about the specified 
+	 *  object.
+	 *
+	 *  @param loc_id  IN: File, group, dataset, or datatype 
+	 *  identifier.
+	 *  @param name  IN: Name of the object for which status is 
+	 *  being sought.
+	 *  @param follow_link  IN: Link flag.
+	 *  @param  fileno  OUT: file id numbers.
+	 *  @param  objno  OUT: object id numbers.
+	 *  @param  link_info  OUT: link information.
+	 *      <pre>
+	 *          link_info[0] = nlink
+	 *          link_info[1] = type
+	 *          link_info[2] = linklen
+	 *      </pre>
+	 *  @param  mtime  OUT: modification time
+	 *
+	 *  @return a non-negative value if successful, with the 
+	 *  fields of link_info and mtime  (if non-null) initialized.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name or array is null.
+	 *  @exception IllegalArgumentException - bad argument.
+	 **/
+	public static native int H5Gget_objinfo(int loc_id, String name, 
+		boolean follow_link, long[] fileno, long[] objno, 
+		int[] link_info, long[] mtime)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  H5Gget_objinfo returns information about the specified 
+	 *  object in an HDF5GroupInfo object.
+	 *
+	 *  @param loc_id  IN: File, group, dataset, or datatype 
+	 *  identifier.
+	 *  @param name  IN: Name of the object for which status 
+	 *  is being sought.
+	 *  @param follow_link  IN: Link flag.
+	 *  @param  info OUT: the HDF5GroupInfo object to store the 
+	 *  object infomation
+	 *
+	 *  @return a non-negative value if successful, with the 
+	 *  fields of HDF5GroupInfo object (if non-null) initialized. 
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 *
+	 *  @see ncsa.hdf.hdf5lib.HDF5GroupInfo
+	 *  @see public static native int H5Gget_objinfo();
+	 **/
+	public static int H5Gget_objinfo(int loc_id, String name, 
+		boolean follow_link,
+		HDF5GroupInfo info) 
+		throws HDF5LibraryException, NullPointerException
+	{
+		int status = -1;
+		long[] fileno = new long[2];
+		long[] objno = new long[2];
+		int[] link_info = new int[3];
+		long[] mtime = new long[1];
+
+		status = H5Gget_objinfo(loc_id, name, follow_link,
+			fileno, objno, link_info, mtime);
+
+		if (status >=0 ) {
+			info.setGroupInfo(fileno, objno, link_info[0],
+			link_info[1], mtime[0], link_info[2]);
+		}
+		return status;
+	}
+
+	/**
+	 *  H5Gget_linkval returns size characters of the link value 
+	 *  through the value argument if loc_id (a file or group 
+	 *  identifier) and name specify a symbolic link.
+	 *
+	 *  @param loc_id  IN: Identifier of the file, group, dataset, 
+	 *  or datatype.
+	 *  @param name  IN: Name of the object whose link value is to 
+	 *  be checked.
+	 *  @param size  IN: Maximum number of characters of value to 
+	 *  be returned.
+	 *  @param char *value  OUT: Link value.
+	 *  
+	 *  @return a non-negative value, with the link value in value, 
+	 *  if successful.
+	 *
+	 *  @exception ArrayIndexOutOfBoundsException   Copy back failed
+	 *  @exception ArrayStoreException  Copy back failed
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 *  @exception IllegalArgumentException - size is invalid
+	 **/
+	public static native int H5Gget_linkval(int loc_id, String name, 
+		int size, String[] value)
+		throws ArrayIndexOutOfBoundsException, 
+		ArrayStoreException,
+		HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	/**
+	 *  H5Gset_comment sets the comment for the the object name 
+	 *  to comment.   Any previously existing comment is overwritten.
+	 *
+	 *  @param loc_id  IN: Identifier of the file, group, dataset, 
+	 *  or datatype.
+	 *  @param name  IN: Name of the object whose comment is to 
+	 *  be set or reset.
+	 *  @param comment  IN: The new comment.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name or comment is null.
+	 **/
+	public static native int H5Gset_comment(int loc_id, String name, 
+		String comment)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Gget_comment retrieves the comment for the the object name. 
+	 *  The comment is returned in the buffer comment.
+	 *
+	 *  @param loc_id  IN: Identifier of the file, group, dataset, 
+	 *  or datatype.
+	 *  @param name  IN: Name of the object whose comment is to be 
+	 *  set or reset.
+	 *  @param bufsize  IN: Anticipated size of the buffer required 
+	 *  to hold comment.
+	 *  @param comment  OUT: The comment.
+	 *  @return the number of characters in the comment, counting 
+	 *  the null terminator, if successful
+	 * 
+	 *  @exception ArrayIndexOutOfBoundsException - JNI error writing
+	 *  back data
+	 *  @exception ArrayStoreException - JNI error writing
+	 *  back data
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 *  @exception IllegalArgumentException - size < 1, 
+	 *  comment is invalid.
+	 **/
+	public static native int H5Gget_comment(int loc_id, String name, 
+		int bufsize,
+		String[] comment)
+		throws ArrayIndexOutOfBoundsException, 
+		ArrayStoreException,
+		HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//           H5I: Identifier Interface Functions            //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Iget_type retrieves the type of the object identified 
+	 *  by obj_id.
+	 *
+	 *  @param obj_id  IN: Object identifier whose type is to be 
+	 *  determined.
+	 *
+	 *  @return the object type if successful; otherwise H5I_BADID.
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Iget_type(int obj_id) 
+		throws HDF5LibraryException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//         H5P: Property List Interface Functions           //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Pcreate creates a new property as an instance of some 
+	 *  property list class.
+	 *
+	 *  @param H5P_class_t type  IN: The type of property list 
+	 *  to create.
+	 *
+	 *  @return a property list identifier (plist) if successful; 
+	 *  otherwise Fail (-1).
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pcreate(int type) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pclose terminates access to a property list.
+	 *
+	 *  @param plist  IN: Identifier of the property list to 
+	 *  terminate access to.
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pclose(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_class returns the property list class for the 
+	 *  property list identified by the plist parameter.
+	 *
+	 *  @param plist  IN: Identifier of property list to query.
+	 *  @return a property list class if successful. Otherwise 
+	 *  returns H5P_NO_CLASS (-1).
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pget_class(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pcopy copies an existing property list to create a 
+	 *  new property list.
+	 *
+	 *  @param plist  IN: Identifier of property list to duplicate.
+	 *
+	 *  @return a property list identifier if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pcopy(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_version retrieves the version information of various 
+	 *  objects for a file creation property list.
+	 *
+	 *  @param plist  IN: Identifier of the file creation property 
+	 *  list.
+	 *  @param version_info OUT: version information.
+	 *  <pre>
+	 *      version_info[0] = boot  // boot block version number
+	 *      version_info[1] = freelist  // global freelist version 
+	 *      version_info[2] = stab  // symbol tabl version number
+	 *      version_info[3] = shhdr  // hared object header version 
+	 *  </pre>
+	 *  @return a non-negative value, with the values of version_info
+	 *  initialized, if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - version_info is null.
+	 *  @exception IllegalArgumentException - version_info is illegal.
+	 **/
+	public static native int H5Pget_version(int plist, 
+		int[] version_info)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  H5Pset_userblock sets the user block size of a file 
+	 *  creation property list.
+	 *
+	 *  @param plist  IN: Identifier of property list to modify.
+	 *  @param size  IN: Size of the user-block in bytes.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_userblock(int plist, long size)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_userblock retrieves the size of a user block in a 
+	 *  file creation property list.
+	 *
+	 *  @param plist  IN: Identifier for property list to query.
+	 *  @param size  OUT: Pointer to location to return user-block 
+	 *  size.
+	 *
+	 *  @return a non-negative value and the size of the user block;
+	 *  if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - size is null.
+	 **/
+	public static native int H5Pget_userblock(int plist, long[] size)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Pset_sizes sets the byte size of the offsets and lengths 
+	 *  used to address objects in an HDF5 file.
+	 *
+	 *  @param plist  IN: Identifier of property list to modify.
+	 *  @param sizeof_addr  IN: Size of an object offset in bytes.
+	 *  @param sizeof_size  IN: Size of an object length in bytes.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_sizes(int plist, int sizeof_addr, 
+		int sizeof_size)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_sizes retrieves the size of the offsets and lengths 
+	 *  used in an HDF5 file. This function is only valid for file 
+	 *  creation property lists.
+	 *
+	 *  @param plist  IN: Identifier of property list to query.
+	 *  @param size  OUT: the size of the offsets and length.
+	 *  <pre>
+	 *      size[0] = sizeof_addr // offset size in bytes
+	 *      size[1] = sizeof_size // length size in bytes
+	 *  </pre>
+	 *  @return a non-negative value with the sizes initialized; 
+	 *  if successful;
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - size is null.
+	 *  @exception IllegalArgumentException - size is invalid.
+	 **/
+	public static native int H5Pget_sizes(int plist, int[] size) 
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  H5Pset_sym_k sets the size of parameters used to control the 
+	 *  symbol table nodes.
+	 *
+	 *  @param plist  IN: Identifier for property list to query.
+	 *  @param ik  IN: Symbol table tree rank.
+	 *  @param lk  IN: Symbol table node size.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_sym_k(int plist, int ik, int lk) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_sym_k retrieves the size of the symbol table 
+	 *  B-tree 1/2 rank and the symbol table leaf node 1/2 size.
+	 *
+	 *  @param plist  IN: Property list to query.
+	 *  @param size  OUT: the symbol table's B-tree 1/2 rank 
+	 *  and leaf node 1/2 size.
+	 *  <pre>
+	 *      size[0] = ik // the symbol table's B-tree 1/2 rank
+	 *      size[1] = lk // leaf node 1/2 size
+	 *  </pre>
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - size is null.
+	 *  @exception IllegalArgumentException - size is invalid.
+	 **/
+	public static native int H5Pget_sym_k(int plist, int[] size) 
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	/**
+	 *  H5Pset_istore_k sets the size of the parameter used to 
+	 *  control the B-trees for indexing chunked datasets.
+	 *
+	 *  @param plist  IN: Identifier of property list to query.
+	 *  @param ik  IN: 1/2 rank of chunked storage B-tree.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_istore_k(int plist, int ik)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_istore_k queries the 1/2 rank of an indexed 
+	 *  storage B-tree.
+	 *
+	 *  @param plist  IN: Identifier of property list to query.
+	 *  @param ik  OUT: Pointer to location to return the chunked 
+	 *  storage B-tree 1/2 rank.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - ik array is null.
+	 **/
+	public static native int H5Pget_istore_k(int plist, int[] ik)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+
+	/**
+	 *  H5Pset_layout sets the type of storage used store the 
+	 *  raw data for a dataset.
+	 *
+	 *  @param plist  IN: Identifier of property list to query.
+	 *  @param layout  IN: Type of storage layout for raw data.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_layout(int plist, int layout)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_layout returns the layout of the raw data for a dataset.
+	 *
+	 *  @param plist  IN: Identifier for property list to query.
+	 *
+	 *  @return the layout type of a dataset creation property 
+	 *  list if successful.
+	 *  Otherwise returns H5D_LAYOUT_ERROR (-1).
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pget_layout(int plist) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pset_chunk sets the size of the chunks used to store a 
+	 *  chunked layout dataset.
+	 *
+	 *  @param plist  IN: Identifier for property list to query.
+	 *  @param ndims  IN: The number of dimensions of each chunk.
+	 *  @param dim  IN: An array containing the size of each chunk.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - dims array is null.
+	 *  @exception IllegalArgumentException - dims <=0
+	**/
+	public static native int H5Pset_chunk(int plist, int ndims, 
+		long[] dim)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	/**
+	 *  H5Pget_chunk retrieves the size of chunks for the raw 
+	 *  data of a chunked layout dataset.
+	 *
+	 *  @param plist  IN: Identifier of property list to query.
+	 *  @param max_ndims  OUT: Size of the dims array.
+	 *  @param dims  OUT: Array to store the chunk dimensions.
+	 *
+	 *  @return chunk dimensionality successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - dims array is null.
+	 *  @exception IllegalArgumentException - max_ndims <=0
+	 **/
+	public static native int H5Pget_chunk(int plist, int max_ndims, 
+		long[] dims)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	/**
+	 *  H5Pset_alignment sets the alignment properties of a 
+	 *  file access property list so that any file object >= 
+	 *  THRESHOLD bytes will be aligned on an address which 
+	 *  is a multiple of ALIGNMENT.
+	 *
+	 *  @param plist  IN: Identifier for a file access property list.
+	 *  @param threshold  IN: Threshold value.
+	 *  @param alignment  IN: Alignment value.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_alignment(int plist, 
+		long threshold, long alignment)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_alignment retrieves the current settings for 
+	 *  alignment properties from a file access property list.
+	 *
+	 *  @param plist  IN: Identifier of a file access property list.
+	 *  @param alignment  OUT: threshold value and alignment value.
+	 *  <pre>
+	 *      alignment[0] = threshold // threshold value
+	 *      alignment[1] = alignment // alignment value
+	 *  </pre>
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - aligment array is null.
+	 *  @exception IllegalArgumentException - aligment array is 
+	 *  invalid.
+	 **/
+	public static native int H5Pget_alignment(int plist, 
+		long[] alignment)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  H5Pset_external adds an external file to the list of 
+	 *  external files.
+	 *
+	 *  @param plist  IN: Identifier of a dataset creation property 
+	 *  list.
+	 *  @param name  IN: Name of an external file.
+	 *  @param offset  IN: Offset, in bytes, from the beginning 
+	 *  of the file to the location in the file where the 
+	 *  data starts.
+	 *  @param size  IN: Number of bytes reserved in the file for 
+	 *  the data.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Pset_external(int plist, String name, 
+		long offset, long size)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Pget_external_count returns the number of external 
+	 *  files for the specified dataset.
+	 *
+	 *  @param plist  IN: Identifier of a dataset creation property 
+	 *  list.
+	 *
+	 *  @return the number of external files if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pget_external_count(int plist) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_external returns information about an external file.
+	 *
+	 *  @param plist  IN: Identifier of a dataset creation property 
+	 *  list.
+	 *  @param idx  IN: External file index.
+	 *  @param name_size  IN: Maximum length of name array.
+	 *  @param name  OUT: Name of the external file.
+	 *  @param size  OUT: the offset value and the size of 
+	 *  the external file data.
+	 *  <pre>
+	 *      size[0] = offset // a location to return an offset value
+	 *      size[1] = size // a location to return the size of 
+	 *                // the external file data.
+	 *  </pre>
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception ArrayIndexOutOfBoundsException  Fatal 
+	 *  error on Copyback 
+	 *  @exception ArrayStoreException  Fatal error on Copyback
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name or size is null.
+	 *  @exception IllegalArgumentException - name_size <= 0 .
+	 *
+	 **/
+	public static native int H5Pget_external(int plist, int idx, 
+		int name_size, String[] name, long[] size)
+		throws ArrayIndexOutOfBoundsException, 
+		ArrayStoreException,
+		HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	/**
+	 *  H5Pset_fill_value sets the fill value for a dataset creation 
+	 *  property list.  <b>NOT IMPLEMENTED YET </b>
+	 *
+	 *  @param plist_id  IN: Property list identifier.
+	 *  @param type_id,  IN: The datatype identifier of value.
+	 *  @param value  IN: The fill value.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5Exception - Error converting data array
+	 **/
+	public static native int H5Pset_fill_value(int plist_id, 
+		int type_id, byte[] value)
+		throws HDF5Exception;
+
+	/**
+	 *  H5Pset_fill_value sets the fill value for a dataset creation 
+	 *  property list.
+	 *
+	 *  @param plist_id  IN: Property list identifier.
+	 *  @param type_id,  IN: The datatype identifier of value.
+	 *  @param obj  IN: The fill value.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5Exception - Error converting data array
+	 **/
+	public static int H5Pset_fill_value(int plist_id, 
+		int type_id, Object obj)
+		throws HDF5Exception
+	{
+		HDFArray theArray = new HDFArray(obj);
+		byte[] buf = theArray.byteify();
+
+		int retVal = H5Pset_fill_value(plist_id, type_id, buf);
+
+		buf = null;
+		theArray = null;
+		return retVal;
+	}
+
+	/**
+	 *  H5Pget_fill_value queries the fill value property of a dataset
+	 *  creation property list. <b>NOT IMPLEMENTED YET</B>
+	 *
+	 *  @param plist_id IN: Property list identifier.
+	 *  @param type_id IN: The datatype identifier of value.
+	 *  @param value  IN: The fill value.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 **/
+	public static native int H5Pget_fill_value(int plist_id, 
+		int type_id, byte[] value)
+		throws HDF5Exception;
+
+	/**
+	 *  H5Pget_fill_value queries the fill value property of a dataset
+	 *  creation property list. <b>NOT IMPLEMENTED YET</B>
+	 *
+	 *  @param plist_id IN: Property list identifier.
+	 *  @param type_id IN: The datatype identifier of value.
+	 *  @param obj  IN: The fill value.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 **/
+	public static int H5Pget_fill_value(int plist_id, int type_id, Object obj)
+		throws HDF5Exception
+	{
+		HDFArray theArray = new HDFArray(obj);
+		byte[] buf = theArray.emptyBytes();
+
+		int status = H5Pget_fill_value(plist_id, type_id, buf);
+		if (status >= 0) obj = theArray.arrayify( buf);
+
+		return status;
+	}
+
+
+	/**
+	 *  H5Pset_filter adds the specified filter and corresponding
+	 *  properties to the end of an output filter pipeline.
+	 *
+	 *  @param plist IN: Property list identifier.
+	 *  @param_t filter IN: Filter to be added to the pipeline.
+	 *  @param flags IN: Bit vector specifying certain general 
+	 *  properties of the filter.
+	 *  @param cd_nelmts IN: Number of elements in cd_values
+	 *  @param cd_values[] IN: Auxiliary data for the filter.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_filter(int plist, int filter, 
+		int flags, int cd_nelmts, int[] cd_values) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_nfilters returns the number of filters defined in 
+	 *  the filter pipeline associated with the property list plist.
+	 *
+	 *  @param plist IN: Property list identifier.
+	 *
+	 *  @return the number of filters in the pipeline if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pget_nfilters(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_filter returns information about a filter, specified 
+	 *  by its filter number, in a filter pipeline, specified by 
+	 *  the property list with which it is associated.
+	 *
+	 *  @param plist IN: Property list identifier.
+	 *  @param filter_number IN: Sequence number within the 
+	 *  filter pipeline of the filter for which information is sought.
+	 *  @param flags OUT: Bit vector specifying certain general 
+	 *  properties of the filter.
+	 *  @param cd_nelmts IN/OUT: Number of elements in cd_values
+	 *  @param cd_values OUT: Auxiliary data for the filter.
+	 *  @param namelen IN: Anticipated number of characters in name.
+	 *  @param name[] OUT: Name of the filter.
+	 *
+	 *  @return the filter identification number if successful. Otherwise
+	 *    returns H5Z_FILTER_ERROR (-1).
+	 *
+	 *  @exception ArrayIndexOutOfBoundsException  
+	 *  Fatal error on Copyback 
+	 *  @exception ArrayStoreException  Fatal error on Copyback
+	 *  @exception NullPointerException - name or an array is null.
+	 *
+	 **/
+	public static native int H5Pget_filter(int plist, 
+		int filter_number, int[] flags,
+		int[] cd_nelmts, int[] cd_values, 
+		int namelen, String[] name)
+		throws ArrayIndexOutOfBoundsException, 
+		ArrayStoreException,
+		HDF5LibraryException, 
+		NullPointerException ;
+
+
+	/**
+	 *  H5Pget_driver returns the identifier of the low-level 
+	 *  file driver.
+	 *  <p>
+	 *  Valid identifiers are:
+	 *  <UL>
+	 *  <LI>
+	 *       H5F_LOW_STDIO (0)
+	 *  </LI>
+	 *  <LI>
+	 *       H5F_LOW_SEC2 (1)
+	 *  </LI>
+	 *  <LI>
+	 *       H5F_LOW_MPIO (2)
+	 *  </LI>
+	 *  <LI>
+	 *       H5F_LOW_CORE (3)
+	 *  </LI>
+	 *  <LI>
+	 *       H5F_LOW_SPLIT (4)
+	 *  </LI>
+	 *  <LI>
+	 *       H5F_LOW_FAMILY (5)
+	 *  </LI>
+	 *  </UL>
+	 *
+	 *  @param plist IN: Identifier of a file access property list.
+	 *
+	 *  @return a low-level driver identifier if successful. Otherwise returns
+	 *  H5F_LOW_ERROR (-1).
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pget_driver(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pset_stdio sets the low level file driver to use the 
+	 *  functions declared in the stdio.h file: fopen(), fseek() 
+	 *  or fseek64(), fread(), fwrite(), and fclose().
+	 *
+	 *  @param plist IN: Identifier of a file access property list.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 **/
+	public static native int H5Pset_stdio(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_stdio checks to determine whether the file access
+	 *  property list is set to the stdio driver.
+	 *
+	 *  @param plist IN: Identifier of a file access property list.
+	 *  @return true if the file access property list is set to
+	 *    the stdio driver. Otherwise returns a negative value.
+	 *
+	 **/
+	public static native boolean H5Pget_stdio(int plist);
+
+	/**
+	 *  H5Pset_sec2 sets the low-level file driver to use the 
+	 *  functions declared in the unistd.h file: open(), lseek() 
+	 *  or lseek64(), read(), write(), and close().
+	 *
+	 *  @param plist IN: Identifier of a file access property list.
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_sec2(int plist) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_sec2 checks to determine whether the file access 
+	 *  property list is set to the sec2 driver.
+	 *
+	 *  @param plist IN: Identifier of a file access property list.
+	 *  @return true if the file access property list is set to
+	 *  the sec2 driver. Otherwise returns a negative value.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native boolean H5Pget_sec2(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pset_core sets the low-level file driver to use malloc() and
+	 *  free().
+	 *
+	 *  @param plist IN: Identifier of a file access property list.
+	 *  @param increment IN: File block size in bytes.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_core(int plist, int increment)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_core checks to determine whether the file access 
+	 *  property list is set to the core driver.
+	 *
+	 *  @param plist IN: Identifier of the file access property list.
+	 *  @param increment OUT: A location to return the file block size
+	 *  @return true if the file access property list is set to
+	 *    the core driver.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native boolean H5Pget_core(int plist, 
+		int[] increment)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pset_split sets the low-level driver to split meta data 
+	 *  from raw data, storing meta data in one file and raw data 
+	 *  in another file.
+	 *
+	 *  @param plist  IN: Identifier of the file access property list.
+	 *  @param meta_ext  IN: Name of the extension for the metafile 
+	 *  filename.   Recommended default value: <i>.meta</i>.
+	 *  @param meta_plist  IN: Identifier of the meta file access 
+	 *  property list.
+	 *  @param raw_ext  IN: Name extension for the raw file filename. 
+	 *  Recommended default value: <i>.raw</i>.
+	 *  @param raw_plist  IN: Identifier of the raw file access 
+	 *  property list.
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - a string is null.
+	 **/
+	public static native int H5Pset_split(int plist, String meta_ext, 
+		int meta_plist, String raw_ext, int raw_plist)
+		throws HDF5LibraryException,
+		NullPointerException;
+
+
+	/**
+	 *  H5Pget_split checks to determine whether the file access
+	 *  property list is set to the split driver.
+	 *
+	 *  @param plist  IN: Identifier of the file access property list.
+	 *  @param meta_ext_size  IN: Number of characters of the 
+	 *  meta file extension to be copied to the meta_ext buffer.
+	 *  @param meta_ext  IN: Meta file extension.
+	 *  @param *meta_properties OUT: A copy of the meta file 
+	 *  access property list.
+	 *  @param raw_ext_size  IN: Number of characters of the 
+	 *  raw file extension to be copied to the raw_ext buffer.
+	 *  @param raw_ext OUT: Raw file extension.
+	 *  @param *raw_properties OUT: A copy of the raw file 
+	 *  access property list.
+	 *
+	 *  @return true if the file access property list is set to
+	 *      the split driver.
+	 *
+         *  @exception ArrayIndexOutOfBoundsException  JNI error 
+	 *  writing back array
+         *  @exception ArrayStoreException   JNI error writing back array
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - a string or array is null.
+	 **/
+	public static native boolean H5Pget_split(int plist, 
+		int meta_ext_size, String[] meta_ext,
+		int[] meta_properties, int raw_ext_size, 
+		String[] raw_ext, int[] raw_properties) 
+		throws ArrayIndexOutOfBoundsException,  
+		ArrayStoreException, 
+		HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Pset_family sets the file access properties to use the 
+	 *  family driver; any previously defined driver properties 
+	 *  are erased from the  property list. 
+	 *
+	 *  @param plist  IN: Identifier of the file access property list.
+	 *  @param memb_size  IN: Logical size, in bytes, of each 
+	 *  family member.
+	 *  @param memb_plist  IN: Identifier of the file access 
+	 *  property list for each member of the family.
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_family(int plist, long memb_size, 
+		int memb_plist)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_family checks to determine whether the file access
+	 *  property list is set to the family driver.
+	 *
+	 *  @param plist  IN: Identifier of the file access property list.
+	 *  @param memb_size OUT: Logical size, in bytes, of each 
+	 *  family member.
+	 *  @param *memb_plist OUT: Identifier of the file access 
+	 *  property list for each member of the family.
+	 *
+	 *  @return a non-negative value if the file access property 
+	 *  list is set to the family driver.
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - an array is null.
+	 **/
+	public static native int H5Pget_family(int tid, long[] memb_size, 
+		int[] memb_plist)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Pset_cache sets the number of elements (objects) in the meta
+	 *  data cache and the total number of bytes in the raw data chunk
+	 *  cache.
+	 *
+	 *  @param plist  IN: Identifier of the file access property list.
+	 *  @param mdc_nelmts  IN: Number of elements (objects) in the 
+	 *  meta data cache.
+	 *  @param rdcc_nbytes  IN: Total size of the raw data chunk 
+	 *  cache, in bytes.
+	 *  @param rdcc_w0  IN: Preemption policy.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_cache(int plist, int mdc_nelmts,
+		int rdcc_nelmts, int rdcc_nbytes, double rdcc_w0)
+		throws HDF5LibraryException;
+
+	/**
+	 *  Retrieves the maximum possible number of elements in the meta
+	 *  data cache and the maximum possible number of bytes and the
+	 *  RDCC_W0 value in the raw data chunk cache.
+	 *
+	 *  @param plist  IN: Identifier of the file access property list.
+	 *  @param mdc_nelmts IN/OUT: Number of elements (objects) in 
+	 *  the meta data cache.
+	 *  @param rdcc_nbytes IN/OUT: Total size of the raw data 
+	 *  chunk cache, in bytes.
+	 *  @param rdcc_w0 IN/OUT: Preemption policy.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - an array is null.
+	 **/
+	public static native int H5Pget_cache(int plist, int[] mdc_nelmts,
+		int[] rdcc_nelmts, int[] rdcc_nbytes, double[] rdcc_w0)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Pset_preserve sets the dataset transfer property list 
+	 *  status to TRUE or FALSE.
+	 *
+	 *  @param plist  IN: Identifier for the dataset transfer 
+	 *  property list.
+	 *  @param status  IN: Status of for the dataset transfer 
+	 *  property list.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception IllegalArgumentException - plist is invalid.
+	 **/
+	public static native int H5Pset_preserve(int plist, 
+		boolean status) 
+		throws HDF5LibraryException, IllegalArgumentException;
+
+
+	/**
+	 *  H5Pget_preserve checks the status of the dataset transfer
+	 *  property list.
+	 *
+	 *  @param plist  IN: Identifier for the dataset transfer 
+	 *  property list.
+	 *
+	 *  @return TRUE or FALSE if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pget_preserve(int plist) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pset_deflate sets the compression method for a dataset.
+	 *
+	 *  @param plist  IN: Identifier for the dataset creation 
+	 *  property list.
+	 *  @param level  IN: Compression level.
+	 *
+	 *  @return true if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native boolean H5Pset_deflate(int plist, int level)
+		throws HDF5LibraryException;
+
+	// The following API functions were added since HDF5 version 1.0.1
+	// The java wrappers are only partly tested.
+	/**
+	 * H5Pset_gc_references Sets the flag for garbage collecting 
+	 * references for the file.  Default value for garbage 
+	 * collecting references is off.
+	 * 
+	 *  @param fapl_id  IN File access property list
+	 *  @param gc_ref  IN set GC on  (true) or off (false)
+	 * 
+	 *  @return non-negative if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int  H5Pset_gc_references(int fapl_id, 
+		boolean gc_ref)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Pget_gc_references Returns the current setting for the 
+	 *  garbage collection refernces property from a file 
+	 *  access property list.
+	 *
+	 *  @param fapl_id  IN File access property list
+	 *  @param gc_ref  OUT GC is on  (true) or off (false)
+	 * 
+	 *  @return non-negative if succeed
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - array is null.
+	 **/
+	public static native int H5Pget_gc_reference(int fapl_id, 
+		boolean[] gc_ref)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Pset_hyper_cache Indicates whether to cache hyperslab 
+	 *  blocks during I/O. 
+	 *  <P>
+	 *  Given a dataset transfer property list, 
+	 *  H5Pset_hyper_cache indicates whether to cache
+	 *  hyperslab blocks during I/O, a process which can 
+	 *  significantly increase I/O speeds. 
+	 *
+	 *  @param plist_id  IN Dataset transfer property list
+	 *  @param cache  IN  cache on (true)/off (false)
+	 *  @param limit  IN   Maximum size of the hyperslab block 
+	 *  to cache. 0 (zero) indicates no limit. 
+	 * 
+	 *  @return non-negative if succeed
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_hyper_cache(int plist_id, 
+		boolean cache, int limit)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_hyper_cache Find whether to hyperslab 
+	 *  blocks are cached during I/O. 
+	 *
+	 *  @param plist_id  IN Dataset transfer property list
+	 *  @param cache  OUT  cache on (true)/off (false)
+	 *  @param limit  OUT   Maximum size of the hyperslab block 
+	 *  to cache. 0 (zero) indicates no limit. 
+	 * 
+	 *  @return non-negative if succeed
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - an array is null.
+	 **/
+	public static native int H5Pget_hyper_cache(int plist_id, 
+		boolean[] cache, int[] limit)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Pset_btree_ratio Sets B-tree split ratios for a dataset 
+	 *  transfer property list. The split ratios determine what 
+	 *  percent of children go in the first node when a node splits.
+	 *
+	 *  @param plist_id  IN Dataset transfer property list
+	 *  @param left  IN  split ratio for leftmost nodes
+	 *  @param right  IN  split ratio for righttmost nodes
+	 *  @param middle  IN  split ratio for all other nodes
+	 * 
+	 *  @return non-negative if succeed
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Pset_btree_ratios(int plist_id, 
+		double left, double middle, double right)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Pget_btree_ratio Get the B-tree split ratios for a dataset 
+	 *  transfer property list. 
+	 *
+	 *  @param plist_id  IN Dataset transfer property list
+	 *  @param left  OUT  split ratio for leftmost nodes
+	 *  @param right  OUT  split ratio for righttmost nodes
+	 *  @param middle  OUT  split ratio for all other nodes
+	 * 
+	 *  @return non-negative if succeed
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - an input array is null.
+	 **/
+	public static native int H5Pget_btree_ratios(int plist_id, 
+		double[] left, double[] middle, double[] right)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//          H5R: Reference Interface Functions              //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	private static native int H5Rcreate(byte[] ref,
+		int loc_id, String name, int ref_type, int space_id)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  H5Rcreate creates the reference, ref, of the type specified in
+	 *  ref_type, pointing to the object name located at loc_id.
+	 *
+	 *  @param loc_id  IN: Location identifier used to locate 
+	 *  the object being pointed to.
+	 *  @param name  IN: Name of object at location loc_id.
+	 *  @param ref_type  IN: Type of reference.
+	 *  @param space_id  IN: Dataspace identifier with selection.
+	 *
+	 *  @return the reference (byte[]) if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - an input array is null.
+	 *  @exception IllegalArgumentException - an input array is 
+	 *  invalid.
+	 **/
+	public static byte[] H5Rcreate(
+		int loc_id, String name, int ref_type, int space_id)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException 
+	{
+		/*  These sizes are correct for HDF5.1.2 */
+		int ref_size = 8;
+		if (ref_type == HDF5Constants.H5R_DATASET_REGION) {
+			ref_size = 12;
+		}
+		byte rbuf[] = new byte[ref_size];
+
+		/*  will raise an exception if fails  */
+		H5Rcreate(rbuf, loc_id, name, ref_type, space_id);
+
+		return rbuf;
+	}
+
+	/**
+	 *  Given a reference to some object, H5Rdereference opens 
+	 *  that object and return an identifier.
+	 *
+	 *  @param dataset  IN: Dataset containing reference object.
+	 *  @paramete ref_type  IN: The reference type of ref.
+	 *  @param ref  IN: reference to an object 
+	 *
+	 *  @return valid identifier if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - output array is null.
+	 *  @exception IllegalArgumentException - output array is invalid.
+	**/
+	public static native int H5Rdereference(int dataset, 
+		int ref_type, byte[] ref)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  Given a reference to an object ref, H5Rget_region creates 
+	 *  a copy of the dataspace of the dataset pointed to and 
+	 *  defines a selection in the copy which is the region 
+	 *  pointed to.
+	 *
+	 *  @param loc_id,  IN: loc_id  of the reference object.
+	 *  @param ref_type,  IN: The reference type of ref.
+	 *  @param reference  OUT: the reference to the object and region
+	 *
+	 *  @return a valid identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - output array is null.
+	 *  @exception IllegalArgumentException - output array is invalid.
+	 **/
+	public static native int H5Rget_region(int loc_id, int ref_type, 
+		byte[] ref )
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  Given a reference to an object ref, H5Rget_object_type 
+	 *  returns the type of the object pointed to. 
+	 *
+	 *  @param loc_id,  IN: loc_id  of the reference object.
+	 *  @param ref  IN: the reference
+	 *
+	 *  @return a valid identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - array is null.
+	 *  @exception IllegalArgumentException - array is invalid.
+	 **/
+	public static native int H5Rget_object_type(int loc_id, 
+		byte ref[])
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//          H5S: Dataspace Interface Functions              //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Screate creates a new dataspace of a particular type.
+	 *
+	 *  @param type The type of dataspace to be created.
+	 *
+	 *  @return a dataspace identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Screate(int type) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Screate_simple creates a new simple data space and opens 
+	 *  it for access.
+	 *
+	 *  @param rank Number of dimensions of dataspace.
+	 *  @param dims An array of the size of each dimension.
+	 *  @param maxdims An array of the maximum size of each dimension.
+	 *
+	 *  @return a dataspace identifier if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - dims or maxdims is null.
+	 **/
+	public static native int H5Screate_simple(int rank, long[] dims, 
+		long[] maxdims)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Scopy creates a new dataspace which is an exact copy of the
+	 *  dataspace identified by space_id.
+	 *
+	 *  @param space_id  Identifier of dataspace to copy.
+	 *  @return a dataspace identifier if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Scopy(int space_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Sselect_elements selects array elements to be included in the
+	 *  selection for the space_id dataspace.
+	 *
+	 *  @param space_id  Identifier of the dataspace.
+	 *  @param op operator specifying how the new selection is 
+	 *  combined.
+	 *  @param num_elements Number of elements to be selected.
+	 *  @param coord A 2-dimensional array specifying the 
+	 *  coordinates of the elements.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	private static native int H5Sselect_elements(int space_id, int op, 
+		int num_elements, byte[] coord)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Sselect_elements selects array elements to be included in the
+	 *  selection for the space_id dataspace.
+	 *
+	 *  @param space_id  Identifier of the dataspace.
+	 *  @param op operator specifying how the new selection is 
+	 *  combined.
+	 *  @param num_elements Number of elements to be selected.
+	 *  @param coord A 2-dimensional array specifying the 
+	 *  coordinates of the elements.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5Exception - Error in the data conversion
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - cord array is
+	 **/
+	public static int H5Sselect_elements(int space_id, int op, 
+		int num_elements, long[][] coord2D)
+		throws HDF5Exception, 
+		HDF5LibraryException, 
+		NullPointerException
+	{
+		if (coord2D == null) return -1;
+
+		HDFArray theArray = new HDFArray((Object)coord2D);
+		byte[] coord = theArray.byteify();
+
+		int retVal = H5Sselect_elements(space_id, op, 
+			num_elements, coord);
+
+		coord = null;
+		theArray = null;
+		return retVal;
+	}
+
+	/**
+	 *  H5Sselect_all selects the entire extent of the 
+	 *  dataspace space_id.
+	 *
+	 *  @param space_id  IN: The identifier of the dataspace 
+	 *  to be selected.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sselect_all(int space_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Sselect_none resets the selection region for the 
+	 *  dataspace space_id to include no elements.
+	 *
+	 *  @param space_id  IN: The identifier of the dataspace to 
+	 *  be reset.
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sselect_none(int space_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Sselect_valid verifies that the selection for the dataspace.
+	 *
+	 *  @param space_id  The identifier for the dataspace in 
+	 *  which the selection is being reset.
+	 *
+	 *  @return true if the selection is contained within 
+	 *  the extent and FALSE if it is not or is an error. 
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native boolean H5Sselect_valid(int space_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Sget_simple_extent_npoints determines the number of elements
+	 *  in a dataspace.
+	 *
+	 *  @param space_id ID of the dataspace object to query
+	 *  @return the number of elements in the dataspace if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native long H5Sget_simple_extent_npoints(
+		int space_id)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Sget_select_npoints determines the number of elements in the
+	 *  current selection of a dataspace.
+	 *
+	 *  @param space_id Dataspace identifier.
+	 *
+	 *  @return the number of elements in the selection if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native long H5Sget_select_npoints(int space_id)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Sget_simple_extent_ndims determines the dimensionality 
+	 *  (or rank) of a dataspace.
+	 *
+	 *  @param space_id  Identifier of the dataspace
+	 *  @return the number of dimensions in the dataspace if 
+	 *  successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sget_simple_extent_ndims(int space_id)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Sget_simple_extent_dims returns the size and maximum sizes of
+	 *  each dimension of a dataspace through the dims and maxdims
+	 *  parameters.
+	 *
+	 *  @param space_id  IN: Identifier of the dataspace object to 
+	 *  query
+	 *  @param dims  OUT: Pointer to array to store the size of 
+	 *  each dimension.
+	 *  @param maxdims  OUT: Pointer to array to store the maximum 
+	 *  size of each dimension.
+	 *
+	 *  @return the number of dimensions in the dataspace if 
+	 *  successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - dims or maxdims is null.
+	 **/
+	public static native int H5Sget_simple_extent_dims(int space_id, 
+		long[] dims, long[] maxdims) 
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+
+	/**
+	 *  H5Sget_simple_extent_type queries a dataspace to determine the
+	 *  current class of a dataspace.
+	 *
+	 *  @param space_id  Dataspace identifier.
+	 *
+	 *  @return a dataspace class name if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sget_simple_extent_type(int space_id)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Sset_extent_simple sets or resets the size of an existing 
+	 *  dataspace.
+	 *
+	 *  @param space_id Dataspace identifier.
+	 *  @param rank Rank, or dimensionality, of the dataspace.
+	 *  @param current_size Array containing current size of dataspace.
+	 *  @param maximum_size Array containing maximum size of dataspace.
+	 *
+	 *  @return a dataspace identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sset_extent_simple(int space_id, 
+		int rank, long[] current_size, long[] maximum_size)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Sis_simple determines whether a dataspace is a simple 
+	 *  dataspace.
+	 *
+	 *  @param space_id  Identifier of the dataspace to query
+	 *
+	 *  @return true if is a simple dataspace
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native boolean H5Sis_simple(int space_id)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Soffset_simple sets the offset of a simple dataspace 
+	 *  space_id.
+	 *
+	 *  @param space_id  IN: The identifier for the dataspace 
+	 *  object to reset.
+	 *  @param offset  IN: The offset at which to position the 
+	 *  selection.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - offset array is null.
+	 **/
+	public static native int H5Soffset_simple(int space_id, 
+		long[] offset)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Sextent_copy copies the extent from source_space_id to
+	 *  dest_space_id. This action may change the type of the 
+	 *  dataspace.
+	 *
+	 *  @param dest_space_id  IN: The identifier for the dataspace 
+	 *  from which the extent is copied.
+	 *  @param source_space_id  IN: The identifier for the 
+	 *  dataspace to which the extent is copied.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sextent_copy(int dest_space_id, 
+		int source_space_id)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Sset_extent_none removes the extent from a dataspace and 
+	 *  sets the type to H5S_NONE.
+	 *
+	 *  @param space_id The identifier for the dataspace from 
+	 *  which the extent is to be removed.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sset_extent_none(int space_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Sselect_hyperslab selects a hyperslab region to add to 
+	 *  the current selected region for the dataspace specified 
+	 *  by space_id.  The start, stride, count, and block arrays 
+	 *  must be the same size as the rank of the dataspace.
+	 *
+	 *  @param space_id  IN: Identifier of dataspace selection 
+	 *  to modify
+	 *  @param op  IN: Operation to perform on current selection.
+	 *  @param start  IN: Offset of start of hyperslab
+	 *  @param count  IN: Number of blocks included in hyperslab.
+	 *  @param stride  IN: Hyperslab stride.
+	 *  @param block  IN: Size of block in hyperslab.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - an input array is null.
+	 *  @exception NullPointerException - an input array is invalid.
+	 **/
+	public static native int H5Sselect_hyperslab(int space_id, 
+		int op, long[] start,
+		long[] stride, long[] count, long[] block)
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+	/**
+	 *  H5Sclose releases a dataspace.
+	 *
+	 *  @param space_id  Identifier of dataspace to release.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Sclose(int space_id) 
+		throws HDF5LibraryException;
+
+//--//
+
+	// following static native functions are missing from HDF5 (version 1.0.1) RM
+
+	/**
+	 *  H5Sget_select_hyper_nblocks returns the number of 
+	 *  hyperslab blocks in the current dataspace selection. 
+	 *
+	 *  @param spaceid  Identifier of dataspace to release.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native long H5Sget_select_hyper_nblocks(int spaceid)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Sget_select_elem_npoints returns the number of 
+	 *  element points in the current dataspace selection. 
+	 *
+	 *  @param spaceid  Identifier of dataspace to release.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native long H5Sget_select_elem_npoints(int spaceid)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Sget_select_hyper_blocklist returns an array of
+	 *  hyperslab blocks. The block coordinates have the 
+	 *  same dimensionality (rank) as the dataspace they 
+	 *  are located within. The list of blocks is formatted 
+	 *  as follows: 
+	 *  <pre>
+         *    <"start" coordinate>, immediately followed by 
+         *    <"opposite" corner coordinate>, followed by 
+         *   the next "start" and "opposite" coordinates, 
+         *   etc. 
+    	 *   until all of the selected blocks have been listed. 
+	 * </pre>
+	 *
+	 *  @param spaceid  Identifier of dataspace to release.
+	 *  @param startblock  first block to retrieve
+	 *  @param numblock  number of blocks to retrieve
+	 *  @param buf  returns blocks startblock to startblock+num-1,
+	 *  each block is <i>rank</i> * 2 (corners) longs.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - buf is null.
+	 **/
+	public static native int H5Sget_select_hyper_blocklist(int spaceid,
+		long startblock, long numblocks, long[] buf)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Sget_select_elem_pointlist returns an array of
+	 *  of element points in the current dataspace selection. 
+	 *  The point coordinates have the same dimensionality 
+	 *  (rank) as the dataspace they are located within,
+	 *  one coordinate per point.
+	 *
+	 *  @param spaceid  Identifier of dataspace to release.
+	 *  @param startpoint  first point to retrieve
+	 *  @param numpoints  number of points to retrieve
+	 *  @param buf  returns points startblock to startblock+num-1,
+	 *  each points is <i>rank</i> longs.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - buf is null.
+	 **/
+	public static native int H5Sget_select_elem_pointlist(int spaceid, 
+		long startpoint, long numpoints, long[] buf)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Sget_select_bounds retrieves the coordinates of 
+	 *  the bounding box containing the current
+         *  selection and places them into user-supplied buffers. 
+	 *  <P>
+	 *  The start and end buffers must be large enough to 
+	 *  hold the dataspace rank number of coordinates. 
+	 *
+	 *  @param spaceid  Identifier of dataspace to release.
+	 *  @param start  coordinates of lowest corner of bounding box.
+	 *  @param end  coordinates of highest corner of bounding box.
+	 *
+	 *  @return a non-negative value if successful,with start and
+	 *  end initialized.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - start or end is null.
+	 **/
+	public static native int H5Sget_select_bounds(int spaceid, 
+		long[] start, long[] end)
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+
+	//////////////////////////////////////////////////////////////
+	//                                                          //
+	//           H5T: Datatype Interface Functions              //
+	//                                                          //
+	//////////////////////////////////////////////////////////////
+
+	/**
+	 *  H5Topen opens a named datatype at the location specified 
+	 *  by loc_id and return an identifier for the datatype.
+	 *
+	 *  @param loc_id A file, group, or datatype identifier.
+	 *  @param name  A datatype name.
+	 *
+	 *  @return a named datatype identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Topen(int loc_id, String name)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Tcommit commits a transient datatype (not immutable) 
+	 *  to a file, turned it into a named datatype.
+	 *
+	 *  @param loc_id A file or group identifier.
+	 *  @param name A datatype name.
+	 *  @param type A datatype identifier.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Tcommit(int loc_id, String name, 
+		int type)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Tcommitted queries a type to determine whether the type 
+	 *  specified by the type identifier is a named type or a 
+	 *  transient type.
+	 *
+	 *  @param type Datatype identifier.
+	 *
+	 *  @return true if successfully committed
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native boolean H5Tcommitted(int type) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tcreate creates a new dataype of the specified class with 
+	 *  the specified number of bytes.
+	 *
+	 *  @param dclass Class of datatype to create.
+	 *  @param size The number of bytes in the datatype to create.
+	 *
+	 *  @return datatype identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tcreate(int dclass, int size) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tcopy copies an existing datatype. The returned type is 
+	 *  always transient and unlocked.
+	 *
+	 *  @param type_id  Identifier of datatype to copy. Can 
+	 *  be a datatype identifier, a  predefined datatype 
+	 *  (defined in H5Tpublic.h), or a dataset Identifier.
+	 *
+	 *  @return a datatype identifier if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tcopy(int type_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Tequal determines whether two datatype identifiers refer 
+	 *  to the same datatype.
+	 *
+	 *  @param type_id1  Identifier of datatype to compare.
+	 *  @param type_id2  Identifier of datatype to compare.
+	 *
+	 *  @return true if the datatype identifiers refer to the 
+	 *  same datatype, else FALSE.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native boolean H5Tequal(int type_id1, int type_id2)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tlock locks the datatype specified by the type_id 
+	 *  identifier, making it read-only and non-destrucible.
+	 *
+	 *  @param type_id  Identifier of datatype to lock.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tlock(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_class returns the datatype class identifier.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return datatype class identifier if successful; 
+	 *  otherwise H5T_NO_CLASS (-1).
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_class(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_size returns the size of a datatype in bytes.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return the size of the datatype in bytes if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_size(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_size sets the total size in bytes, size, for an 
+	 *  atomic datatype (this operation is not permitted on 
+	 *  compound datatypes).
+	 *
+	 *  @param type_id  Identifier of datatype to change size.
+	 *  @param size Size in bytes to modify datatype.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_size(int type_id, int size) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_order returns the byte order of an atomic datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return a byte order constant if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_order(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_order sets the byte ordering of an atomic datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param order Byte ordering constant.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_order(int type_id, int order)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_precision returns the precision of an atomic datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return the number of significant bits if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_precision(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_precision sets the precision of an atomic datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param precision  Number of bits of precision for datatype.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_precision(int type_id, 
+		int precision)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_offset retrieves the bit offset of the first 
+	 *  significant bit.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *  @return a positive offset value if successful; otherwise 0.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_offset(int type_id) 
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Tset_offset sets the bit offset of the first 
+	 *  significant bit.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param offset  Offset of first significant bit.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_offset(int type_id, int offset)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_pad retrieves the padding type of the least and 
+	 *  most-significant bit padding.
+	 *
+	 *  @param type_id  IN: Identifier of datatype to query.
+	 *  @param pad  OUT: locations to return least-significant 
+	 *  and most-significant bit padding type.
+	 *  <pre>
+	 *      pad[0] = lsb // least-significant bit padding type
+	 *      pad[1] = msb // most-significant bit padding type
+	 *  </pre>
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - pad is null.
+	 **/
+	public static native int H5Tget_pad(int type_id, int[] pad)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Tset_pad sets the least and most-significant bits 
+	 *  padding types.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param lsb Padding type for least-significant bits.
+	 *  @param msb Padding type for most-significant bits.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_pad(int type_id, int lsb, int msb)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_sign retrieves the sign type for an integer type.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return a valid sign type if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_sign(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_sign sets the sign proprety for an integer type.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param sign Sign type.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_sign(int type_id, int sign)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_fields retrieves information about the locations of 
+	 *  the various bit fields of a floating point datatype.
+	 *
+	 *  @param type_id  IN: Identifier of datatype to query.
+	 *  @param fields  OUT: location of size and bit-position.
+	 *  <pre>
+	 *      fields[0] = spos  OUT: location to return size of in bits.
+	 *      fields[1] = epos  OUT: location to return exponent 
+	 *                  bit-position.
+	 *      fields[2] = esize  OUT: location to return size of 
+	 *                  exponent in bits.
+	 *      fields[3] = mpos  OUT: location to return mantissa 
+	 *                  bit-position.
+	 *      fields[4] = msize  OUT: location to return size of 
+	 *                  mantissa in bits.
+	 *  </pre>
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - fileds is null.
+	 *  @exception IllegalArgumentException - fileds array is invalid.
+	 **/
+	public static native int H5Tget_fields(int type_id, int[] fields) 
+		throws HDF5LibraryException, 
+		NullPointerException, 
+		IllegalArgumentException;
+
+
+	/**
+	 *  H5Tset_fields sets the locations and sizes of the various
+	 *  floating point bit fields.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param spos Size position.
+	 *  @param epos Exponent bit position.
+	 *  @param esize Size of exponent in bits.
+	 *  @param mpos Mantissa bit position.
+	 *  @param msize Size of mantissa in bits.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_fields(int type_id, int spos, 
+		int epos, int esize,
+		int mpos, int msize) throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_ebias retrieves the exponent bias of a 
+	 *  floating-point type.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return the bias if successful; otherwise 0.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_ebias(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_ebias sets the exponent bias of a floating-point type.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param ebias Exponent bias value.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_ebias(int type_id, int ebias)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_norm retrieves the mantissa normalization of a 
+	 *  floating-point datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return a valid normalization type if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_norm(int type_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_norm sets the mantissa normalization of a 
+	 *  floating-point datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to set.
+	 *  @param norm Mantissa normalization type.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_norm(int type_id, int norm) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_inpad retrieves the internal padding type for unused 
+	 *  bits in floating-point datatypes.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return a valid padding type if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_inpad(int type_id)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  If any internal bits of a floating point type are unused 
+	 *  (that is, those significant bits which are not part of 
+	 *  the sign, exponent, or mantissa), then  H5Tset_inpad will 
+	 *  be filled according to the value of the padding value
+	 *  property inpad.
+	 *
+	 *  @param type_id  Identifier of datatype to modify.
+	 *  @param inpad Padding type.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_inpad(int type_id, int inpad)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_cset retrieves the character set type of a 
+	 *  string datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return a valid character set type if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_cset(int type_id)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_cset the character set to be used.
+	 *
+	 *  @param type_id  Identifier of datatype to modify.
+	 *  @param cset Character set type.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_cset(int type_id, int cset)
+		throws HDF5LibraryException;
+
+
+	/**
+	 *  H5Tget_strpad retrieves the string padding method for 
+	 *  a string datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return a valid string padding type if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_strpad(int type_id)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_strpad defines the storage mechanism for the string.
+	 *
+	 *  @param type_id Identifier of datatype to modify.
+	 *  @param strpad String padding type.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_strpad(int type_id, int strpad)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_nmembers retrieves the number of fields a 
+	 *  compound datatype has.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *
+	 *  @return number of members datatype has if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_nmembers(int type_id)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_member_name retrieves the name of a field of a compound
+	 *  datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *  @param field_idx Field index (0-based) of the field name 
+	 *  to retrieve.
+	 *
+	 *  @return a valid pointer if successful; otherwise null.
+	 *
+	 **/
+	public static native String H5Tget_member_name(int type_id, 
+		int field_idx);
+
+	/**
+	 *  H5Tget_member_dims returns the dimensionality of the field.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *  @param field_idx  Field index (0-based) of the field dims 
+	 *  to retrieve.
+	 *  @param dims Pointer to buffer to store the dimensions of 
+	 *  the field.
+	 *  @param perm Pointer to buffer to store the permutation 
+	 *  vector of the field.
+	 *
+	 *  @return the number of dimensions, a number from 0 to 4, 
+	 *  if successful.
+	 *  Otherwise returns a negative value.
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_member_dims(int type_id, 
+		int field_idx, int[] dims, int[] perm) 
+		throws HDF5LibraryException, 
+		NullPointerException;
+
+	/**
+	 *  H5Tget_member_type returns the datatype of the specified 
+	 *  member.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *  @param field_idx Field index (0-based) of the field type to 
+	 *  retrieve.
+	 *
+	 *  @return the identifier of a copy of the datatype of the 
+	 *  field if successful;
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_member_type(int type_id, 
+		int field_idx)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_member_offset returns the byte offset of the 
+	 *  specified member of the compound datatype.  This
+	 *  is the byte offset in the HDF-5 file/library, NOT
+	 *  the offset of any Java object which might be mapped
+	 *  to this data item.
+	 *
+	 *  @param type_id  Identifier of datatype to query.
+	 *  @param field_idx Field index (0-based) of the field type to 
+	 *  retrieve.
+	 *
+	 *  @return the offset of the member.
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native long H5Tget_member_offset(int type_id, 
+		int membno)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tinsert adds another member to the compound datatype type_id.
+	 *
+	 *  @param type_id  Identifier of compound datatype to modify.
+	 *  @param name  Name of the field to insert.
+	 *  @param offset Offset in memory structure of the field to 
+	 *  insert.
+	 *  @param field_id  Datatype identifier of the field to insert.
+	 *
+	 *  @return a non-negative value if successful
+	 *  
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Tinsert(int type_id, String name, 
+		long offset, int field_id)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Tpack recursively removes padding from within a 
+	 *  compound datatype to make it more efficient (space-wise) 
+	 *  to store that data.  
+	 *  <P>
+	 *  <b>WARNING:</b> This call only affects the
+	 *  C-data, even if it succeeds, there may be no visible 
+	 *  effect on Java objects.
+	 *
+	 *  @param type_id  Identifier of datatype to modify.
+	 *
+	 *  @return a non-negative value if successful
+	 * 
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tpack(int type_id) 
+		throws HDF5LibraryException;
+
+
+ 	/**
+	 *  H5Tinsert_array adds a new member to the compound datatype
+	 *  parent_id.
+	 *
+	 *  @param parent_id  Identifier of the parent compound datatype.
+	 *  @param name Name of new member.
+	 *  @param offset Offset to start of new member within compound 
+	 *  datatype.
+	 *  @param ndims Dimensionality of new member.
+	 *  @param dim Size of new member array.
+	 *  @param perm Buffer to store the permutation vector of 
+	 *  the field.
+	 *  @param member_id  Identifier of the datatype of the new member.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Tinsert_array(int parent_id, 
+		String name, int offset,
+		int ndims, int[] dim, int[] perm, int member_id)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Tclose releases a datatype.
+	 *
+	 *  @param type_id  Identifier of datatype to release.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tclose(int type_id) 
+		throws HDF5LibraryException;
+
+	// following static native functions are missing from HDF5 (version 1.0.1) RM
+
+	/**
+	 *  H5Tenum_create creates a new enumeration datatype 
+	 *  based on the specified base datatype, parent_id, 
+	 *  which must be an integer type. 
+	 *
+	 *  @param base_id  Identifier of the parent datatype to release.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tenum_create(int base_id) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tenum_insert inserts a new enumeration datatype member 
+	 *  into an enumeration datatype. 
+	 *
+	 *  @param type  Identifier of datatype.
+	 *  @param name  The name of the member
+	 *  @param obj  The value of the member,  data of the correct
+	 *  type
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Tenum_insert(int type, String name, 
+		int[] value)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Tenum_nameof finds the symbol name that corresponds 
+	 *  to the specified value of the enumeration datatype type. 
+	 *
+	 *  @param type  IN: Identifier of datatype.
+	 *  @param obj  IN: The value of the member, data of the correct
+	 *  @param name  OUT: The name of the member
+	 *  @param size  IN:  The max length of the name
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Tenum_nameof(int type, 
+		int[] value, String[] name, int size)
+		throws HDF5LibraryException, NullPointerException;
+
+
+	/**
+	 *  H5Tenum_valueof finds the value that corresponds to 
+	 *  the specified name of the enumeration datatype type. 
+	 *
+	 *  @param type  IN: Identifier of datatype.
+	 *  @param name  IN: The name of the member
+	 *  @param value  OUT: The value of the member
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Tenum_valueof(int type, 
+		String name, int[] value)
+		throws HDF5LibraryException, NullPointerException;
+
+	/**
+	 *  H5Tvlen_create creates a new variable-length (VL) dataype. 
+	 *
+	 *  @param base_id_type  IN: Identifier of parent datatype.
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tvlen_create(int base_id)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tset_tag tags an opaque datatype type_id with a 
+	 *  unique ASCII identifier tag. 
+	 *
+	 *  @param type  IN: Identifier of parent datatype.
+	 *  @param tag  IN: Name of the tag (will be stored as
+	 *  ASCII)
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tset_tag(int type, String tag)
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_tag returns the tag associated with datatype type_id.
+	 *
+	 *  @param type  IN: Identifier of datatype.
+	 *
+	 *  @return the tag
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native String H5Tget_tag(int type) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_super returns the type from which TYPE is derived.
+	 *
+	 *  @param type  IN: Identifier of datatype.
+	 *
+	 *  @return the parent type
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 **/
+	public static native int H5Tget_super(int type) 
+		throws HDF5LibraryException;
+
+	/**
+	 *  H5Tget_member_value returns the value of the enumeration 
+	 *  datatype member memb_no. 
+	 *
+	 *  @param type_id  IN: Identifier of datatype.
+	 *  @param membno  IN: The name of the member
+	 *  @param value  OUT: The value of the member
+	 *
+	 *  @return a non-negative value if successful
+	 *
+	 *  @exception HDF5LibraryException - Error from the HDF-5 Library.
+	 *  @exception NullPointerException - name is null.
+	 **/
+	public static native int H5Tget_member_value(int type_id, 
+		int membno, int[] value)
+		throws HDF5LibraryException, NullPointerException;
+
+}
diff --git a/ncsa/hdf/hdf5lib/HDF5CDataTypes.java b/ncsa/hdf/hdf5lib/HDF5CDataTypes.java
new file mode 100644
index 0000000..953968b
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/HDF5CDataTypes.java
@@ -0,0 +1,161 @@
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib;
+
+/**
+ *  Class HDF5CDataTypes contains C constants and enumerated
+ *  types of HDF5 library which are set at runtime, the Java 
+ *  constants need to be converted to C constants with the
+ *  function call J2C(int) in HDF5Library class. Any constant 
+ *  which starts its name with "JH5" need to be converted. Any 
+ *  constant which start its name with "H5" has the same value 
+ *  as its C constant. For example,
+ *  <pre>
+ *  h5.H5Tcopy(h5.J2C(HDF5CDatatypes.JH5T_NATIVE_INT)); // convert Java value to C value
+ *  </pre>
+ *
+ *  <B>See also:</b> ncsa.hdf.hdf5lib.HDF5Library
+ *  <B>See also:</b> ncsa.hdf.hdf5lib.HDF5Constants
+ */
+public class HDF5CDataTypes
+{
+	// Constants need to be mapped into runtime C constants
+	// The values are arbitrary but MUST match the values in
+        // native/hdf5lib/h5Constants.h
+	public static final int JH5T_ALPHA_B16 = 1;
+	public static final int JH5T_ALPHA_B32 = 2;
+	public static final int JH5T_ALPHA_B64 = 3;
+	public static final int JH5T_ALPHA_B8 = 4;
+	public static final int JH5T_ALPHA_F32 = 5;
+	public static final int JH5T_ALPHA_F64 = 6;
+	public static final int JH5T_ALPHA_I16 = 7;
+	public static final int JH5T_ALPHA_I32 = 8;
+	public static final int JH5T_ALPHA_I64 = 9;
+	public static final int JH5T_ALPHA_I8 = 10;
+	public static final int JH5T_ALPHA_U16 = 11;
+	public static final int JH5T_ALPHA_U32 = 12;
+	public static final int JH5T_ALPHA_U64 = 13;
+	public static final int JH5T_ALPHA_U8 = 14;
+	public static final int JH5T_C_S1 = 15;
+	public static final int JH5T_FORTRAN_S1 = 16;
+	public static final int JH5T_IEEE_F32BE = 17;
+	public static final int JH5T_IEEE_F32LE = 18;
+	public static final int JH5T_IEEE_F64BE = 19;
+	public static final int JH5T_IEEE_F64LE = 20;
+	public static final int JH5T_INTEL_B16 = 21;
+	public static final int JH5T_INTEL_B32 = 22;
+	public static final int JH5T_INTEL_B64 = 23;
+	public static final int JH5T_INTEL_B8 = 24;
+	public static final int JH5T_INTEL_F32 = 25;
+	public static final int JH5T_INTEL_F64 = 26;
+	public static final int JH5T_INTEL_I16 = 27;
+	public static final int JH5T_INTEL_I32 = 28;
+	public static final int JH5T_INTEL_I64 = 29;
+	public static final int JH5T_INTEL_I8 = 30;
+	public static final int JH5T_INTEL_U16 = 31;
+	public static final int JH5T_INTEL_U32 = 32;
+	public static final int JH5T_INTEL_U64 = 33;
+	public static final int JH5T_INTEL_U8 = 34;
+	public static final int JH5T_MIPS_B16 = 35;
+	public static final int JH5T_MIPS_B32 = 36;
+	public static final int JH5T_MIPS_B64 = 37;
+	public static final int JH5T_MIPS_B8 = 38;
+	public static final int JH5T_MIPS_F32 = 39;
+	public static final int JH5T_MIPS_F64 = 40;
+	public static final int JH5T_MIPS_I16 = 41;
+	public static final int JH5T_MIPS_I32 = 42;
+	public static final int JH5T_MIPS_I64 = 43;
+	public static final int JH5T_MIPS_I8 = 44;
+	public static final int JH5T_MIPS_U16 = 45;
+	public static final int JH5T_MIPS_U32 = 46;
+	public static final int JH5T_MIPS_U64 = 47;
+	public static final int JH5T_MIPS_U8 = 48;
+	public static final int JH5T_NATIVE_B16 = 49;
+	public static final int JH5T_NATIVE_B32 = 50;
+	public static final int JH5T_NATIVE_B64 = 51;
+	public static final int JH5T_NATIVE_B8 = 52;
+	public static final int JH5T_NATIVE_CHAR = 53;
+	public static final int JH5T_NATIVE_DOUBLE = 54;
+	public static final int JH5T_NATIVE_FLOAT = 55;
+	public static final int JH5T_NATIVE_HBOOL = 56;
+	public static final int JH5T_NATIVE_HERR = 57;
+	public static final int JH5T_NATIVE_HSIZE = 58;
+	public static final int JH5T_NATIVE_HSSIZE = 59;
+	public static final int JH5T_NATIVE_INT = 60;
+	public static final int JH5T_NATIVE_INT_FAST16 = 61;
+	public static final int JH5T_NATIVE_INT_FAST32 = 62;
+	public static final int JH5T_NATIVE_INT_FAST64 = 63;
+	public static final int JH5T_NATIVE_INT_FAST8 = 64;
+	public static final int JH5T_NATIVE_INT_LEAST16 = 65;
+	public static final int JH5T_NATIVE_INT_LEAST32 = 66;
+	public static final int JH5T_NATIVE_INT_LEAST64 = 67;
+	public static final int JH5T_NATIVE_INT_LEAST8 = 68;
+	public static final int JH5T_NATIVE_INT16 = 69;
+	public static final int JH5T_NATIVE_INT32 = 70;
+	public static final int JH5T_NATIVE_INT64 = 71;
+	public static final int JH5T_NATIVE_INT8 = 72;
+	public static final int JH5T_NATIVE_LDOUBLE = 73;
+	public static final int JH5T_NATIVE_LLONG = 74;
+	public static final int JH5T_NATIVE_LONG = 75;
+	public static final int JH5T_NATIVE_OPAQUE = 76;
+	public static final int JH5T_NATIVE_SCHAR = 77;
+	public static final int JH5T_NATIVE_SHORT = 78;
+	public static final int JH5T_NATIVE_UCHAR = 79;
+	public static final int JH5T_NATIVE_UINT = 80;
+	public static final int JH5T_NATIVE_UINT_FAST16 = 81;
+	public static final int JH5T_NATIVE_UINT_FAST32 = 82;
+	public static final int JH5T_NATIVE_UINT_FAST64 = 83;
+	public static final int JH5T_NATIVE_UINT_FAST8 = 84;
+	public static final int JH5T_NATIVE_UINT_LEAST16 = 85;
+	public static final int JH5T_NATIVE_UINT_LEAST32 = 86;
+	public static final int JH5T_NATIVE_UINT_LEAST64 = 87;
+	public static final int JH5T_NATIVE_UINT_LEAST8 = 88;
+	public static final int JH5T_NATIVE_UINT16 = 89;
+	public static final int JH5T_NATIVE_UINT32 = 90;
+	public static final int JH5T_NATIVE_UINT64 = 91;
+	public static final int JH5T_NATIVE_UINT8 = 92;
+	public static final int JH5T_NATIVE_ULLONG = 93;
+	public static final int JH5T_NATIVE_ULONG = 94;
+	public static final int JH5T_NATIVE_USHORT = 95;
+	public static final int JH5T_NCSET = 96;
+	public static final int JH5T_NSTR = 97;
+	public static final int JH5T_STD_B16BE = 98;
+	public static final int JH5T_STD_B16LE = 99;
+	public static final int JH5T_STD_B32BE = 100;
+	public static final int JH5T_STD_B32LE = 101;
+	public static final int JH5T_STD_B64BE = 102;
+	public static final int JH5T_STD_B64LE = 103;
+	public static final int JH5T_STD_B8BE = 104;
+	public static final int JH5T_STD_B8LE = 105;
+	public static final int JH5T_STD_I16BE = 106;
+	public static final int JH5T_STD_I16LE = 107;
+	public static final int JH5T_STD_I32BE = 108;
+	public static final int JH5T_STD_I32LE = 109;
+	public static final int JH5T_STD_I64BE = 110;
+	public static final int JH5T_STD_I64LE = 111;
+	public static final int JH5T_STD_I8BE = 112;
+	public static final int JH5T_STD_I8LE = 113;
+	public static final int JH5T_STD_REF_DSETREG = 114;
+	public static final int JH5T_STD_REF_OBJ = 115;
+	public static final int JH5T_STD_U16BE = 116;
+	public static final int JH5T_STD_U16LE = 117;
+	public static final int JH5T_STD_U32BE = 118;
+	public static final int JH5T_STD_U32LE = 119;
+	public static final int JH5T_STD_U64BE = 120;
+	public static final int JH5T_STD_U64LE = 121;
+	public static final int JH5T_STD_U8BE = 122;
+	public static final int JH5T_STD_U8LE = 123;
+	public static final int JH5T_UNIX_D32BE = 124;
+	public static final int JH5T_UNIX_D32LE = 125;
+	public static final int JH5T_UNIX_D64BE = 126;
+	public static final int JH5T_UNIX_D64LE = 127;
+}
diff --git a/ncsa/hdf/hdf5lib/HDF5Constants.java b/ncsa/hdf/hdf5lib/HDF5Constants.java
new file mode 100644
index 0000000..819ac7b
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/HDF5Constants.java
@@ -0,0 +1,256 @@
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib;
+
+/**
+ *  Class HDF5Constants contains the C define constants and enumerated
+ *  types of HDF5 library, e.g., 
+ *  <pre>
+ *  HDF5Library.H5Pcreate(HDF5Constants.H5P_FILE_ACCESS); 
+ *  </pre>
+ *
+ *  <p>
+ *  These values are generated automatically by /etc/Makefile
+ *  <P>
+ *  <B>Do not edit this file!</b>
+ *
+ *  <b>See also:</b> ncsa.hdf.hdf5lib.HDF5Library
+ *  <br>
+ *  <b>See also:</b> ncsa.hdf.hdf5lib.HDF5CDataTypes
+ */
+public class HDF5Constants
+{
+	/*AUTOMATICALLY GENERATED CONSTANTS*/ 
+	final static public int H5D_CHUNKED = 2;
+	final static public int H5D_COMPACT = 0;
+	final static public int H5D_CONTIGUOUS = 1;
+	final static public int H5D_LAYOUT_ERROR = -1;
+	final static public int H5D_NLAYOUTS = 3;
+	final static public int H5E_ALIGNMENT = 39;
+	final static public int H5E_ALREADYINIT = 23;
+	final static public int H5E_ARGS = 1;
+	final static public int H5E_ATOM = 7;
+	final static public int H5E_ATTR = 18;
+	final static public int H5E_BADATOM = 24;
+	final static public int H5E_BADFILE = 14;
+	final static public int H5E_BADMESG = 40;
+	final static public int H5E_BADRANGE = 4;
+	final static public int H5E_BADTYPE = 3;
+	final static public int H5E_BADVALUE = 5;
+	final static public int H5E_BTREE = 9;
+	final static public int H5E_CACHE = 8;
+	final static public int H5E_CANTCOPY = 7;
+	final static public int H5E_CANTCREATE = 11;
+	final static public int H5E_CANTDECODE = 33;
+	final static public int H5E_CANTDELETE = 41;
+	final static public int H5E_CANTENCODE = 32;
+	final static public int H5E_CANTFLUSH = 26;
+	final static public int H5E_CANTFREE = 8;
+	final static public int H5E_CANTINIT = 22;
+	final static public int H5E_CANTINSERT = 35;
+	final static public int H5E_CANTLIST = 36;
+	final static public int H5E_CANTLOAD = 27;
+	final static public int H5E_CANTOPENFILE = 12;
+	final static public int H5E_CANTOPENOBJ = 42;
+	final static public int H5E_CANTREGISTER = 25;
+	final static public int H5E_CANTSPLIT = 34;
+	final static public int H5E_CLOSEERROR = 20;
+	final static public int H5E_COMPLEN = 43;
+	final static public int H5E_CWG = 44;
+	final static public int H5E_DATASET = 15;
+	final static public int H5E_DATASPACE = 14;
+	final static public int H5E_DATATYPE = 13;
+	final static public int H5E_EFL = 20;
+	final static public int H5E_EXISTS = 31;
+	final static public int H5E_FILE = 4;
+	final static public int H5E_FILEEXISTS = 9;
+	final static public int H5E_FILEOPEN = 10;
+	final static public int H5E_FUNC = 6;
+	final static public int H5E_HEAP = 11;
+	final static public int H5E_INTERNAL = 3;
+	final static public int H5E_IO = 5;
+	final static public int H5E_LINK = 45;
+	final static public int H5E_LINKCOUNT = 37;
+	final static public int H5E_MOUNT = 16;
+	final static public int H5E_MPI = 47;
+	final static public int H5E_NONE_MAJOR = 0;
+	final static public int H5E_NONE_MINOR = 0;
+	final static public int H5E_NOSPACE = 6;
+	final static public int H5E_NOTCACHED = 29;
+	final static public int H5E_NOTFOUND = 30;
+	final static public int H5E_NOTHDF5 = 13;
+	final static public int H5E_OHDR = 12;
+	final static public int H5E_OVERFLOW = 21;
+	final static public int H5E_PLINE = 19;
+	final static public int H5E_PLIST = 17;
+	final static public int H5E_PROTECT = 28;
+	final static public int H5E_RAGGED = 21;
+	final static public int H5E_READERROR = 18;
+	final static public int H5E_REFERENCE = 22;
+	final static public int H5E_RESOURCE = 2;
+	final static public int H5E_SEEKERROR = 17;
+	final static public int H5E_SLINK = 46;
+	final static public int H5E_STORAGE = 16;
+	final static public int H5E_SYM = 10;
+	final static public int H5E_TRUNCATED = 15;
+	final static public int H5E_UNINITIALIZED = 1;
+	final static public int H5E_UNSUPPORTED = 2;
+	final static public int H5E_VERSION = 38;
+	final static public int H5E_WALK_DOWNWARD = 1;
+	final static public int H5E_WALK_UPWARD = 0;
+	final static public int H5E_WRITEERROR = 19;
+	final static public int H5F_ACC_EXCL = 4;
+	final static public int H5F_ACC_RDONLY = 0;
+	final static public int H5F_ACC_RDWR = 1;
+	final static public int H5F_ACC_TRUNC = 2;
+	final static public int H5F_LOW_CORE = 3;
+	final static public int H5F_LOW_ERROR = -1;
+	final static public int H5F_LOW_FAMILY = 5;
+	final static public int H5F_LOW_MPIO = 2;
+	final static public int H5F_LOW_SEC2 = 1;
+	final static public int H5F_LOW_SPLIT = 4;
+	final static public int H5F_LOW_STDIO = 0;
+	final static public int H5F_SCOPE_DOWN = 2;
+	final static public int H5F_SCOPE_GLOBAL = 1;
+	final static public int H5F_SCOPE_LOCAL = 0;
+	final static public int H5F_UNLIMITED = -1;
+	final static public int H5G_DATASET = 2;
+	final static public int H5G_GROUP = 1;
+	final static public int H5G_LINK = 0;
+	final static public int H5G_LINK_ERROR = -1;
+	final static public int H5G_LINK_HARD = 0;
+	final static public int H5G_LINK_SOFT = 1;
+	final static public int H5G_TYPE = 3;
+	final static public int H5G_UNKNOWN = -1;
+	final static public int H5I_ATTR = 16;
+	final static public int H5I_BADID = -1;
+	final static public int H5I_DATASET = 15;
+	final static public int H5I_DATASPACE = 14;
+	final static public int H5I_DATATYPE = 13;
+	final static public int H5I_FILE = 1;
+	final static public int H5I_FILE_CLOSING = 2;
+	final static public int H5I_GROUP = 12;
+	final static public int H5I_NGROUPS = 20;
+	final static public int H5I_RAGGED = 18;
+	final static public int H5I_REFERENCE = 19;
+	final static public int H5I_TEMPBUF = 17;
+	final static public int H5I_TEMPLATE_0 = 3;
+	final static public int H5I_TEMPLATE_1 = 4;
+	final static public int H5I_TEMPLATE_2 = 5;
+	final static public int H5I_TEMPLATE_3 = 6;
+	final static public int H5I_TEMPLATE_4 = 7;
+	final static public int H5I_TEMPLATE_5 = 8;
+	final static public int H5I_TEMPLATE_6 = 9;
+	final static public int H5I_TEMPLATE_7 = 10;
+	final static public int H5P_DATASET_CREATE = 2;
+	final static public int H5P_DATASET_XFER = 3;
+	final static public int H5P_DEFAULT = -2;
+	final static public int H5P_FILE_ACCESS = 1;
+	final static public int H5P_FILE_CREATE = 0;
+	final static public int H5P_MOUNT = 4;
+	final static public int H5P_NCLASSES = 5;
+	final static public int H5P_NO_CLASS = -1;
+	final static public int H5R_BADTYPE = -1;
+	final static public int H5R_DATASET_REGION = 1;
+	final static public int H5R_INTERNAL = 2;
+	final static public int H5R_MAXTYPE = 3;
+	final static public int H5R_OBJECT = 0;
+	final static public int H5S_ALL = -2;
+	final static public int H5S_COMPLEX = 2;
+	final static public int H5S_MAX_RANK = 31;
+	final static public int H5S_NO_CLASS = -1;
+	final static public int H5S_SCALAR = 0;
+	final static public int H5S_SELECT_INVALID = 2;
+	final static public int H5S_SELECT_NOOP = -1;
+	final static public int H5S_SELECT_OR = 1;
+	final static public int H5S_SELECT_SET = 0;
+	final static public int H5S_SIMPLE = 1;
+	final static public int H5S_UNLIMITED = -1;
+	final static public int H5T_BITFIELD = 4;
+	final static public int H5T_BKG_NO = 0;
+	final static public int H5T_BKG_TEMP = 1;
+	final static public int H5T_BKG_YES = 2;
+	final static public int H5T_COMPOUND = 6;
+	final static public int H5T_CONV_CONV = 1;
+	final static public int H5T_CONV_FREE = 2;
+	final static public int H5T_CONV_INIT = 0;
+	final static public int H5T_CSET_ASCII = 0;
+	final static public int H5T_CSET_ERROR = -1;
+	final static public int H5T_CSET_RESERVED_1 = 1;
+	final static public int H5T_CSET_RESERVED_10 = 10;
+	final static public int H5T_CSET_RESERVED_11 = 11;
+	final static public int H5T_CSET_RESERVED_12 = 12;
+	final static public int H5T_CSET_RESERVED_13 = 13;
+	final static public int H5T_CSET_RESERVED_14 = 14;
+	final static public int H5T_CSET_RESERVED_15 = 15;
+	final static public int H5T_CSET_RESERVED_2 = 2;
+	final static public int H5T_CSET_RESERVED_3 = 3;
+	final static public int H5T_CSET_RESERVED_4 = 4;
+	final static public int H5T_CSET_RESERVED_5 = 5;
+	final static public int H5T_CSET_RESERVED_6 = 6;
+	final static public int H5T_CSET_RESERVED_7 = 7;
+	final static public int H5T_CSET_RESERVED_8 = 8;
+	final static public int H5T_CSET_RESERVED_9 = 9;
+	final static public int H5T_ENUM = 8;
+	final static public int H5T_FLOAT = 1;
+	final static public int H5T_INTEGER = 0;
+	final static public int H5T_NORM_ERROR = -1;
+	final static public int H5T_NORM_IMPLIED = 0;
+	final static public int H5T_NORM_MSBSET = 1;
+	final static public int H5T_NORM_NONE = 2;
+	final static public int H5T_NO_CLASS = -1;
+	final static public int H5T_NPAD = 3;
+	final static public int H5T_NSGN = 2;
+	final static public int H5T_OPAQUE = 5;
+	final static public int H5T_ORDER_BE = 1;
+	final static public int H5T_ORDER_ERROR = -1;
+	final static public int H5T_ORDER_LE = 0;
+	final static public int H5T_ORDER_NONE = 3;
+	final static public int H5T_ORDER_VAX = 2;
+	final static public int H5T_PAD_BACKGROUND = 2;
+	final static public int H5T_PAD_ERROR = -1;
+	final static public int H5T_PAD_ONE = 1;
+	final static public int H5T_PAD_ZERO = 0;
+	final static public int H5T_PERS_DONTCARE = -1;
+	final static public int H5T_PERS_HARD = 0;
+	final static public int H5T_PERS_SOFT = 1;
+	final static public int H5T_REFERENCE = 7;
+	final static public int H5T_SGN_2 = 1;
+	final static public int H5T_SGN_ERROR = -1;
+	final static public int H5T_SGN_NONE = 0;
+	final static public int H5T_STRING = 3;
+	final static public int H5T_STR_ERROR = -1;
+	final static public int H5T_STR_NULLPAD = 1;
+	final static public int H5T_STR_NULLTERM = 0;
+	final static public int H5T_STR_RESERVED_10 = 10;
+	final static public int H5T_STR_RESERVED_11 = 11;
+	final static public int H5T_STR_RESERVED_12 = 12;
+	final static public int H5T_STR_RESERVED_13 = 13;
+	final static public int H5T_STR_RESERVED_14 = 14;
+	final static public int H5T_STR_RESERVED_15 = 15;
+	final static public int H5T_STR_RESERVED_3 = 3;
+	final static public int H5T_STR_RESERVED_4 = 4;
+	final static public int H5T_STR_RESERVED_5 = 5;
+	final static public int H5T_STR_RESERVED_6 = 6;
+	final static public int H5T_STR_RESERVED_7 = 7;
+	final static public int H5T_STR_RESERVED_8 = 8;
+	final static public int H5T_STR_RESERVED_9 = 9;
+	final static public int H5T_STR_SPACEPAD = 2;
+	final static public int H5T_TIME = 2;
+	final static public int H5T_VLEN = 9;
+	final static public int H5Z_FILTER_DEFLATE = 1;
+	final static public int H5Z_FILTER_ERROR = -1;
+	final static public int H5Z_FILTER_NONE = 0;
+	final static public int H5_VERS_MAJOR = 1;
+	final static public int H5_VERS_MINOR = 2;
+	final static public int H5_VERS_RELEASE = 0;
+}
diff --git a/ncsa/hdf/hdf5lib/HDF5GroupInfo.java b/ncsa/hdf/hdf5lib/HDF5GroupInfo.java
new file mode 100644
index 0000000..a1b8dfb
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/HDF5GroupInfo.java
@@ -0,0 +1,107 @@
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib;
+
+/**
+ * <p>
+ *  This class is a container for the information reported about an HDF5 
+ *  Object from the H5Gget_obj_info() method.
+ * <p>
+ *  The fileno and objno fields contain four values which uniquely identify 
+ *  an object among those HDF5 files which are open: if all four values are 
+ *  the same between two objects, then the two objects are the same (provided 
+ *  both files are still open). The nlink field is the number of hard links 
+ *  to the object or zero when information is being returned about a symbolic 
+ *  link (symbolic links do not have hard links but all other objects always 
+ *  have at least one). The type field contains the type of the object, one 
+ *  of H5G_GROUP, H5G_DATASET, or H5G_LINK. The mtime field contains the 
+ *  modification time. If information is being returned about a symbolic link 
+ *  then linklen will be the length of the link value (the name of the pointed-to
+ *  object with the null terminator); otherwise linklen will be zero. Other 
+ *  fields may be added to this structure in the future.  
+ * <p>
+ *  For details of the HDF5 libraries, see the HDF5 Documentation at:
+ *     <a href="http://hdf.ncsa.uiuc.edu/HDF5/doc/">http://hdf.ncsa.uiuc.edu/HDF5/doc/</a>
+ */
+
+public class HDF5GroupInfo
+{
+	long[] fileno;
+	long[] objno;
+	int nlink;
+	int type;
+	long mtime; 
+	int linklen;
+
+	public HDF5GroupInfo()
+	{
+		fileno = new long[2];
+		objno = new long[2];
+		nlink = 0;
+		type = -1;
+		mtime = 0;
+		linklen = 0;
+	}
+
+	/**
+	 *  Sets the HDF5 group information.  Used by the
+         *  JHI5.
+	 *
+	 *  @param fn  File id number
+	 *  @param on  Object id number
+	 *  @param nl  Number of links
+	 *  @param t   Type of the object
+	 *  @param mt  Modification time
+	 *  @param len Length of link
+	**/
+	public void setGroupInfo(long[] fn, long[] on, int nl, int t, long mt, int len)
+	{
+		fileno = fn;
+		objno = on;
+		nlink = nl;
+		type = t;
+		mtime = mt;
+		linklen = len;
+	}
+
+	/* accessors */
+	public long[] getFileno() { return fileno; }
+	public long[] getObjno() { return objno; }
+	public int getType() { return type; }
+	public int getNlink() { return nlink; }
+	public long getMtime() { return mtime; }
+	public int getLinklen() { return linklen; }
+
+	/**
+	 * Converts this object to a String representation.
+	 * @return     a string representation of this object
+	 */
+	public String toString()
+	{
+		String fileStr="fileno=null";
+		String objStr="objno=null";
+
+		if (fileno != null)
+			fileStr = "fileno[0]="+fileno[0]+",fileno[1]="+fileno[1];
+
+		if (objno != null)
+			objStr = "objno[0]="+objno[0]+",objno[1]="+objno[1];
+
+		return getClass().getName() + "[" + fileStr +"," +objStr+
+			",type="+type+",nlink="+nlink+",mtime="+mtime+",linklen="+
+			linklen+"]";
+	}
+
+
+}
+
+
diff --git a/ncsa/hdf/hdf5lib/HDFArray.java b/ncsa/hdf/hdf5lib/HDFArray.java
new file mode 100644
index 0000000..acb7e56
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/HDFArray.java
@@ -0,0 +1,865 @@
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib;
+
+import java.lang.reflect.*;
+import ncsa.hdf.hdf5lib.exceptions.*;
+/**
+ *  This is a class for handling multidimensional arrays for
+ *  HDF.
+ *  <p>
+ *  The purpose is to allow the storage and retrieval of
+ *  arbitrary array types containing scientific data.
+ *  <p>
+ *  The methods support the conversion of an array to and
+ *  from Java to a one-dimensional array of bytes suitable
+ *  for I/O by the C library.
+ *  <p>
+ *  This class heavily uses the <a href="./ncsa.hdf.hdf5lib.HDFNativeData.html">HDFNativeData</a>
+ *  class to convert between Java and C representations.
+ */
+
+public class HDFArray {
+
+private Object _theArray = null;
+private ArrayDescriptor _desc = null;
+private byte [] _barray = null;
+
+//public HDFArray() {}
+
+/**
+ *  The input must be a Java Array (possibly multidimensional)
+ *  of primitive numbers or sub-classes of Number.
+ * <P>
+ *  The input is analysed to determine the number of dimensions
+ *  and size of each dimension, as well as the type of the elements.
+ * <P>
+ * The description is saved in private variables, and used to
+ * convert data.
+ *
+ *  @exception  ncsa.hdf.hdf5lib.exception.HDF5Exception  object is not an array.
+ */
+public HDFArray(Object anArray) throws HDF5Exception {
+
+	if (anArray == null) {
+		HDF5JavaException ex =
+		new HDF5JavaException("HDFArray: array is null?: ");
+	}
+	Class tc = anArray.getClass();
+        if (tc.isArray() == false) {
+                /* exception: not an array */
+		HDF5JavaException ex =
+		new HDF5JavaException("HDFArray: not an array?: ");
+		throw(ex);
+        }
+	_theArray = anArray;
+	_desc = new ArrayDescriptor( _theArray );
+
+	/* extra error checking -- probably not needed */
+	if (_desc == null ) {
+		HDF5JavaException ex =
+		new HDF5JavaException("HDFArray: internal error: array description failed?: ");
+		throw(ex);
+	}
+}
+
+/**
+ *  Allocate a one-dimensional array of bytes sufficient to store
+ *  the array.
+ *
+ *  @return  A one-D array of bytes, filled with zeroes.
+ *  The bytes are sufficient to hold the data of the Array 
+ *  passed to the constructor.
+ *  @exception ncsa.hdf.hdf5lib.exception.HDF5JavaException  Allocation failed.
+ */
+
+public byte[] emptyBytes()
+throws HDF5Exception
+{
+	byte[] b = new byte[_desc.totalSize];
+	if (b == null) {
+		HDF5JavaException ex =
+		new HDF5JavaException("HDFArray: emptyBytes: allocation failed");
+		throw(ex);
+	}
+	return (b);
+}
+
+/**
+ *  Given a Java array of numbers, convert it to a one-dimensional
+ *  array of bytes in correct native order.
+ *
+ *  @return  A one-D array of bytes, constructed from the Array
+ *  passed to the constructor.
+ *  @exception ncsa.hdf.hdf5lib.exception.HDF5Exception  thrown for errors in HDF5 
+ *  @exception ncsa.hdf.hdf5lib.exception.HDF5JavaException  the object not an array or other internal error.
+ */
+public byte[] byteify() throws HDF5Exception
+{
+
+	if (_barray != null) return _barray;
+    
+	if (_theArray == null) {
+         /* exception: not an array */
+         HDF5JavaException ex = new HDF5JavaException("HDFArray: byteify not an array?: ");
+         throw(ex);
+    }
+
+	if (_desc.dims == 1) {
+		/* special case */
+		if (_desc.NT == 'B') {
+			/* really special case! */
+			_barray = (byte [])_theArray;
+			return _barray;
+		} else {
+			try {
+			_barray = new byte[_desc.totalSize];
+
+			byte [] therow;
+			if (_desc.NT == 'I') {
+				therow = HDFNativeData.intToByte(0,_desc.dimlen[1],(int [])_theArray);
+			} else if (_desc.NT == 'S') {
+				therow = HDFNativeData.shortToByte(0,_desc.dimlen[1],(short [])_theArray);
+			} else if (_desc.NT == 'F') {
+				therow = HDFNativeData.floatToByte(0,_desc.dimlen[1],(float [])_theArray);
+			} else if (_desc.NT == 'J') {
+				therow = HDFNativeData.longToByte(0,_desc.dimlen[1],(long [])_theArray);
+			} else if (_desc.NT == 'D') {
+				therow = HDFNativeData.doubleToByte(0,_desc.dimlen[1],(double [])_theArray);
+			} else if (_desc.NT == 'L') {
+				if (_desc.className.equals("java.lang.Byte")) {
+					therow = ByteObjToByte((Byte[])_theArray);
+				} else if (_desc.className.equals("java.lang.Integer")) {
+					therow = IntegerToByte((Integer[])_theArray);
+				} else if (_desc.className.equals("java.lang.Short")) {
+					therow = ShortToByte((Short[])_theArray);
+				} else if (_desc.className.equals("java.lang.Float")) {
+					therow = FloatObjToByte((Float[])_theArray);
+				} else if (_desc.className.equals("java.lang.Double")) {
+					therow = DoubleObjToByte((Double[])_theArray);
+				} else if (_desc.className.equals("java.lang.Long")) {
+					therow = LongObjToByte((Long[])_theArray);
+				} else {
+					 HDF5JavaException ex =
+						new HDF5JavaException("HDFArray: unknown type of Object?");
+					 throw(ex);
+				}
+			} else {
+				 HDF5JavaException ex =
+					new HDF5JavaException("HDFArray: unknown type of data?");
+				 throw(ex);
+			}
+			System.arraycopy(therow,0,_barray,0,(_desc.dimlen[1] * _desc.NTsize));
+			return _barray;
+			} catch (OutOfMemoryError err) {
+				 HDF5JavaException ex =
+				new HDF5JavaException("HDFArray: byteify array too big?");
+				throw(ex);
+			}
+		}
+	}
+
+	try {
+		_barray = new byte[_desc.totalSize];
+        } catch (OutOfMemoryError err) {
+		HDF5JavaException ex =
+		new HDF5JavaException("HDFArray: byteify array too big?");
+                throw(ex);
+	}
+
+
+	Object oo = _theArray;
+	int n = 0;  /* the current byte */
+	int index = 0;
+	int i;
+	while ( n < _desc.totalSize ) {
+		oo = _desc.objs[0];
+		index = n / _desc.bytetoindex[0];
+                index %= _desc.dimlen[0];
+		for (i = 0 ; i < (_desc.dims); i++) {
+			index = n / _desc.bytetoindex[i];
+			index %= _desc.dimlen[i];
+
+			if (index == _desc.currentindex[i]) {
+				/* then use cached copy */
+				oo = _desc.objs[i];
+			} else {
+				/* check range of index */		
+				if (index > (_desc.dimlen[i] - 1)) {
+					throw new java.lang.IndexOutOfBoundsException("HDFArray: byteify index OOB?");
+				}
+				oo = java.lang.reflect.Array.get((Object) oo,index);
+				_desc.currentindex[i] = index;
+				_desc.objs[i] = oo;
+			}
+		}
+
+		/* byte-ify */
+		byte arow[];
+		try {
+		if (_desc.NT == 'J') {
+			arow = HDFNativeData.longToByte(0,_desc.dimlen[_desc.dims],(long [])_desc.objs[_desc.dims - 1]);
+			arow = HDFNativeData.longToByte(0,_desc.dimlen[_desc.dims],(long [])_desc.objs[_desc.dims - 1]);
+		} else if (_desc.NT == 'I') {
+			arow = HDFNativeData.intToByte(0,_desc.dimlen[_desc.dims],(int [])_desc.objs[_desc.dims - 1]);
+		} else if (_desc.NT == 'S') {
+			arow = HDFNativeData.shortToByte(0,_desc.dimlen[_desc.dims],(short [])_desc.objs[_desc.dims - 1]);
+		} else if (_desc.NT == 'B') {
+			arow = (byte [])_desc.objs[_desc.dims - 1];
+		} else if (_desc.NT == 'F') {
+			/* 32 bit float */
+			arow = HDFNativeData.floatToByte(0,_desc.dimlen[_desc.dims],(float [])_desc.objs[_desc.dims - 1]);
+		} else if (_desc.NT == 'D') {
+			/* 64 bit float */
+			arow = HDFNativeData.doubleToByte(0,_desc.dimlen[_desc.dims],(double [])_desc.objs[_desc.dims - 1]);
+		} else if (_desc.NT == 'L') {
+			if (_desc.className.equals("java.lang.Byte")) {
+				arow = ByteObjToByte((Byte[])_desc.objs[_desc.dims - 1]);
+			} else if (_desc.className.equals("java.lang.Integer")) {
+				arow = IntegerToByte((Integer[])_desc.objs[_desc.dims - 1]);
+			} else if (_desc.className.equals("java.lang.Short")) {
+				arow = ShortToByte((Short[])_desc.objs[_desc.dims - 1]);
+			} else if (_desc.className.equals("java.lang.Float")) {
+				arow = FloatObjToByte((Float[])_desc.objs[_desc.dims - 1]);
+			} else if (_desc.className.equals("java.lang.Double")) {
+				arow = DoubleObjToByte((Double[])_desc.objs[_desc.dims - 1]);
+			} else if (_desc.className.equals("java.lang.Long")) {
+				arow = LongObjToByte((Long[])_desc.objs[_desc.dims - 1]);
+			} else {
+				HDF5JavaException ex =
+				new HDF5JavaException("HDFArray: byteify Object type not implemented?");
+				throw(ex);
+			}
+		} else {
+			HDF5JavaException ex =
+			new HDF5JavaException("HDFArray: byteify unknown type not implemented?");
+			throw(ex);
+		}
+		System.arraycopy(arow,0,_barray,n,(_desc.dimlen[_desc.dims] * _desc.NTsize));
+		n += _desc.bytetoindex[_desc.dims - 1];
+		} catch (OutOfMemoryError err) {
+			HDF5JavaException ex =
+			new HDF5JavaException("HDFArray: byteify array too big?");
+			throw(ex);
+		}
+	}
+/* assert:  the whole array is completed--currentindex should == len - 1 */
+
+	/* error checks */
+
+	if (n < _desc.totalSize) {
+		throw new java.lang.InternalError(
+		new String("HDFArray::byteify: Panic didn't complete all input data: n=  "+n+" size = "+_desc.totalSize));
+	}
+	for (i = 0;i < _desc.dims; i++) {
+		if (_desc.currentindex[i] != _desc.dimlen[i] - 1) {
+			throw new java.lang.InternalError(
+				new String("Panic didn't complete all data: currentindex["+i+"] = "+_desc.currentindex[i]+" (should be "+(_desc.dimlen[i] - 1)+" ?)"));
+		}
+	}
+	return _barray;
+}
+
+/**
+ *  Given a one-dimensional array of bytes representing numbers, 
+ *  convert it to a java array of the shape and size passed to 
+ *  the constructor.
+ *
+ *  @param  bytes  The bytes to construct the Array.
+ *  @return  An Array (possibly multidimensional) of primitive or
+ *  number objects.
+ *  @exception ncsa.hdf.hdf5lib.exception.HDF5Exception  thrown for errors in HDF5 
+ *  @exception ncsa.hdf.hdf5lib.exception.HDF5JavaException  the object not an array or other internal error.
+ */
+public Object arrayify(byte[] bytes) throws HDF5Exception {
+
+	if (_theArray == null) {
+                /* exception: not an array */
+		HDF5JavaException ex = 
+		new HDF5JavaException("arrayify: not an array?: "); 
+		throw(ex); 
+	} 
+
+	if (java.lang.reflect.Array.getLength((Object) bytes) != _desc.totalSize) {
+	/* exception: array not right size */ 
+		HDF5JavaException ex = 
+		new HDF5JavaException("arrayify: array is wrong size?: "); 
+		throw(ex); 
+	} 
+	_barray = bytes; /* hope that the bytes are correct.... */ 
+	if (_desc.dims == 1) {
+		/* special case */
+		/* 2 data copies here! */
+		try {
+		if (_desc.NT == 'I') {
+			int [] x = (int [])HDFNativeData.byteToInt(_barray);
+			System.arraycopy(x,0,_theArray,0,_desc.dimlen[1]);
+			return _theArray;
+		} else if (_desc.NT == 'S') {
+			short [] x = HDFNativeData.byteToShort(_barray);
+			System.arraycopy(x,0,_theArray,0,_desc.dimlen[1]);
+			return _theArray;
+		} else if (_desc.NT == 'F') {
+			float x[] = HDFNativeData.byteToFloat(_barray);
+			System.arraycopy(x,0,_theArray,0,_desc.dimlen[1]);
+			return _theArray;
+		} else if (_desc.NT == 'J') {
+			long x[] = HDFNativeData.byteToLong(_barray);
+			System.arraycopy(x,0,_theArray,0,_desc.dimlen[1]);
+			return _theArray;
+		} else if (_desc.NT == 'D') {
+			double x[] = HDFNativeData.byteToDouble(_barray);
+			System.arraycopy(x,0,_theArray,0,_desc.dimlen[1]);
+			return _theArray;
+		} else if (_desc.NT == 'B') {
+			System.arraycopy(_barray,0,_theArray,0,_desc.dimlen[1]);
+			return _theArray;
+		} else if (_desc.NT == 'L') {
+			if (_desc.className.equals("java.lang.Byte")) {
+				Byte I[] = ByteToByteObj(_barray);
+				System.arraycopy(I,0,_theArray,0,_desc.dimlen[1]);
+				return _theArray;
+			} else if (_desc.className.equals("java.lang.Integer")) {
+				Integer I[] = ByteToInteger(_barray);
+				System.arraycopy(I,0,_theArray,0,_desc.dimlen[1]);
+				return _theArray;
+			} else if (_desc.className.equals("java.lang.Short")) {
+				Short I[] = ByteToShort(_barray);
+				System.arraycopy(I,0,_theArray,0,_desc.dimlen[1]);
+				return _theArray;
+			} else if (_desc.className.equals("java.lang.Float")) {
+				Float I[] = ByteToFloatObj(_barray);
+				System.arraycopy(I,0,_theArray,0,_desc.dimlen[1]);
+				return _theArray;
+			} else if (_desc.className.equals("java.lang.Double")) {
+				Double I[] = ByteToDoubleObj(_barray);
+				System.arraycopy(I,0,_theArray,0,_desc.dimlen[1]);
+				return _theArray;
+			} else if (_desc.className.equals("java.lang.Long")) {
+				Long I[] = ByteToLongObj(_barray);
+				System.arraycopy(I,0,_theArray,0,_desc.dimlen[1]);
+				return _theArray;
+			} else {
+			HDF5JavaException ex =
+			new HDF5JavaException("arrayify:  Object type not implemented yet...");
+			throw(ex); 
+			}
+		} else {
+			HDF5JavaException ex = 
+			new HDF5JavaException("arrayify:  unknown type not implemented yet...");
+			throw(ex); 
+		}
+		} catch (OutOfMemoryError err) {
+			HDF5JavaException ex =
+			new HDF5JavaException("HDFArray: arrayify array too big?");
+			throw(ex);
+		}
+	}
+	/* Assert dims >= 2 */
+
+	Object oo = _theArray;
+	int n = 0;  /* the current byte */
+	int index = 0;
+	int i;
+	while ( n < _desc.totalSize ) {
+		oo = _desc.objs[0];
+		index = n / _desc.bytetoindex[0];
+		index %= _desc.dimlen[0];
+		for (i = 0 ; i < (_desc.dims); i++) {
+			index = n / _desc.bytetoindex[i];
+			index %= _desc.dimlen[i];
+
+			if (index == _desc.currentindex[i]) {
+				/* then use cached copy */
+				oo = _desc.objs[i];
+			} else {
+				/* check range of index */		
+				if (index > (_desc.dimlen[i] - 1)) {
+					System.out.println("out of bounds?");
+					return null;
+				}
+				oo = java.lang.reflect.Array.get((Object) oo,index);
+				_desc.currentindex[i] = index;
+				_desc.objs[i] = oo;
+			}
+		}
+
+		/* array-ify */
+		try {
+		if (_desc.NT == 'J') {
+			long [] arow = HDFNativeData.byteToLong(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), (Object)arow);
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+		} else if (_desc.NT == 'I') {
+			int [] arow = HDFNativeData.byteToInt(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), (Object)arow);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+		} else if (_desc.NT == 'S') {
+			short [] arow = HDFNativeData.byteToShort(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), (Object)arow);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+		} else if (_desc.NT == 'B') {
+			System.arraycopy( _barray, n, _desc.objs[_desc.dims - 1], 0, _desc.dimlen[_desc.dims]);
+			n += _desc.bytetoindex[_desc.dims - 1];
+		} else if (_desc.NT == 'F') {
+			float arow[] = HDFNativeData.byteToFloat(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), (Object)arow);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+		} else if (_desc.NT == 'D') {
+			double [] arow = HDFNativeData.byteToDouble(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), (Object)arow);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+		} else if (_desc.NT == 'L') {
+			if (_desc.className.equals("java.lang.Byte")) {
+				Byte I[] = ByteToByteObj(n,_desc.dimlen[_desc.dims],_barray);
+		java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+			(_desc.currentindex[_desc.dims - 1]), 
+			(Object)I);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+			} else if (_desc.className.equals("java.lang.Integer")) {
+				Integer I[] = ByteToInteger(n,_desc.dimlen[_desc.dims],_barray);
+		java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+			(_desc.currentindex[_desc.dims - 1]), 
+			(Object)I);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+			} else if (_desc.className.equals("java.lang.Short")) {
+				Short I[] = ByteToShort(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), 
+				(Object)I);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+			} else if (_desc.className.equals("java.lang.Float")) {
+				Float I[] = ByteToFloatObj(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), 
+				(Object)I);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+			} else if (_desc.className.equals("java.lang.Double")) {
+				Double I[] = ByteToDoubleObj(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), 
+				(Object)I);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+			} else if (_desc.className.equals("java.lang.Long")) {
+				Long I[] = ByteToLongObj(n,_desc.dimlen[_desc.dims],_barray);
+			java.lang.reflect.Array.set(_desc.objs[_desc.dims - 2] ,
+				(_desc.currentindex[_desc.dims - 1]), 
+				(Object)I);
+
+			n += _desc.bytetoindex[_desc.dims - 1];
+			_desc.currentindex[_desc.dims - 1]++;
+			} else {
+			HDF5JavaException ex =
+			new HDF5JavaException("HDFArray: unsupported Object type: "+_desc.NT);
+			throw(ex);
+			}
+		} else {
+			HDF5JavaException ex =
+			new HDF5JavaException("HDFArray: unknown or unsupported type: "+_desc.NT);
+			throw(ex);
+		}
+		} catch (OutOfMemoryError err) {
+			HDF5JavaException ex =
+			new HDF5JavaException("HDFArray: arrayify array too big?");
+			throw(ex);
+		}
+
+	}
+
+/* assert:  the whole array is completed--currentindex should == len - 1 */
+
+	/* error checks */
+
+	if (n < _desc.totalSize) {
+		throw new java.lang.InternalError(
+		new String("HDFArray::byteify Panic didn't complete all input data: n=  "+n+" size = "+_desc.totalSize));
+	}
+	for (i = 0;i <= _desc.dims-2; i++) {
+		if (_desc.currentindex[i] != _desc.dimlen[i] - 1) {
+		throw new java.lang.InternalError(
+		new String("HDFArray::byteify Panic didn't complete all data: currentindex["+i+"] = "+_desc.currentindex[i]+" (should be "+(_desc.dimlen[i] - 1)+"?"));
+		}
+	}
+	if (_desc.NT != 'B') {
+	if (_desc.currentindex[_desc.dims - 1] != _desc.dimlen[_desc.dims - 1]) {
+		throw new java.lang.InternalError(
+		new String("HDFArray::byteify Panic didn't complete all data: currentindex["+i+"] = "+_desc.currentindex[i]+" (should be "+(_desc.dimlen[i])+"?"));
+	}
+	} else {
+	if (_desc.currentindex[_desc.dims - 1] != (_desc.dimlen[_desc.dims - 1] - 1)) {
+		throw new java.lang.InternalError(
+		new String("HDFArray::byteify Panic didn't complete all data: currentindex["+i+"] = "+_desc.currentindex[i]+" (should be "+(_desc.dimlen[i] - 1)+"?"));
+	}
+	}
+
+	return _theArray;
+}
+
+private byte[] IntegerToByte( Integer in[] ) {
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	int[] out = new int[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = in[i].intValue();
+	}
+	return  HDFNativeData.intToByte(0,nelems,out);
+}
+
+private Integer[] ByteToInteger( byte[] bin ) {
+	int in[] = (int [])HDFNativeData.byteToInt(bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Integer[] out = new Integer[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Integer(in[i]);
+	}
+	return  out;
+}
+
+private Integer[] ByteToInteger( int start, int len, byte[] bin ) {
+	int in[] = (int [])HDFNativeData.byteToInt(start,len,bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Integer[] out = new Integer[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Integer(in[i]);
+	}
+	return  out;
+}
+
+
+private byte[] ShortToByte( Short in[] ) {
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	short[] out = new short[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = in[i].shortValue();
+	}
+	return  HDFNativeData.shortToByte(0,nelems,out);
+}
+
+private Short[] ByteToShort( byte[] bin ) {
+	short in[] = (short [])HDFNativeData.byteToShort(bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Short[] out = new Short[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Short(in[i]);
+	}
+	return  out;
+}
+
+private Short[] ByteToShort( int start, int len, byte[] bin ) {
+	short in[] = (short [])HDFNativeData.byteToShort(start,len,bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Short[] out = new Short[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Short(in[i]);
+	}
+	return  out;
+}
+
+private byte[] ByteObjToByte( Byte in[] ) {
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	byte[] out = new byte[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = in[i].byteValue();
+	}
+	return out;
+}
+
+private Byte[] ByteToByteObj( byte[] bin ) {
+	int nelems = java.lang.reflect.Array.getLength((Object)bin);
+	Byte[] out = new Byte[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Byte(bin[i]);
+	}
+	return  out;
+}
+
+private Byte[] ByteToByteObj( int start, int len, byte[] bin ) {
+	Byte[] out = new Byte[len];
+
+	for (int i = 0; i < len; i++) {
+		out[i] = new Byte(bin[i]);
+	}
+	return  out;
+}
+
+private byte[] FloatObjToByte( Float in[] ) {
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	float[] out = new float[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = in[i].floatValue();
+	}
+	return  HDFNativeData.floatToByte(0,nelems,out);
+}
+
+private Float[] ByteToFloatObj( byte[] bin ) {
+	float in[] = (float [])HDFNativeData.byteToFloat(bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Float[] out = new Float[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Float(in[i]);
+	}
+	return  out;
+}
+
+private Float[] ByteToFloatObj( int start, int len, byte[] bin ) {
+	float in[] = (float [])HDFNativeData.byteToFloat(start,len,bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Float[] out = new Float[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Float(in[i]);
+	}
+	return  out;
+}
+
+private byte[] DoubleObjToByte( Double in[] ) {
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	double[] out = new double[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = in[i].doubleValue();
+	}
+	return  HDFNativeData.doubleToByte(0,nelems,out);
+}
+
+private Double[] ByteToDoubleObj( byte[] bin ) {
+	double in[] = (double [])HDFNativeData.byteToDouble(bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Double[] out = new Double[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Double(in[i]);
+	}
+	return  out;
+}
+
+private Double[] ByteToDoubleObj( int start, int len, byte[] bin ) {
+	double in[] = (double [])HDFNativeData.byteToDouble(start,len,bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Double[] out = new Double[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Double(in[i]);
+	}
+	return  out;
+}
+
+private byte[] LongObjToByte( Long in[] ) {
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	long[] out = new long[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = in[i].longValue();
+	}
+	return  HDFNativeData.longToByte(0,nelems,out);
+}
+
+private Long[] ByteToLongObj( byte[] bin ) {
+	long in[] = (long [])HDFNativeData.byteToLong(bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Long[] out = new Long[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Long(in[i]);
+	}
+	return  out;
+}
+
+private Long[] ByteToLongObj( int start, int len, byte[] bin ) {
+	long in[] = (long [])HDFNativeData.byteToLong(start,len,bin);
+	int nelems = java.lang.reflect.Array.getLength((Object)in);
+	Long[] out = new Long[nelems];
+
+	for (int i = 0; i < nelems; i++) {
+		out[i] = new Long(in[i]);
+	}
+	return  out;
+}
+}
+
+/**
+  * This private class is used by HDFArray to discover the 
+  * shape and type of an arbitrary array.
+  * <p>
+  * We use java.lang.reflection here.
+  */
+class ArrayDescriptor {
+
+	static String theType = "";
+	static Class theClass = null;
+        static int [] dimlen = null;
+        static int [] dimstart = null;
+        static int [] currentindex = null;
+        static int [] bytetoindex = null;
+	static int totalSize = 0;
+        static Object [] objs = null;
+	static char NT = ' ';  /*  must be B,S,I,L,F,D, else error */
+        static int NTsize = 0;
+	static int dims = 0;
+	static String className;
+
+	public ArrayDescriptor ( Object anArray ) throws HDF5Exception {
+
+		Class tc = anArray.getClass();
+		if (tc.isArray() == false) {
+			/* exception: not an array */
+			HDF5Exception ex =
+			new HDF5JavaException("ArrayDescriptor: not an array?: ");
+                        throw(ex);
+		}
+
+		theClass = tc;
+
+		/* parse the type descriptor to discover the
+			shape of the array */
+		String ss = tc.toString();
+		theType = ss;
+		int n = 6;
+		dims = 0;
+		char c = ' ';
+		while (n < ss.length()) {
+			c = ss.charAt(n);
+			n++;
+			if (c == '[') {
+				dims++;
+			}
+		}
+
+		String css = ss.substring(ss.lastIndexOf('[')+1);
+		Class compC = tc.getComponentType();
+		String cs = compC.toString();
+		/* To do:  extend to deal with Integer, Short, etc. */
+		NT = c;  /*  must be B,S,I,L,F,D, else error */
+		if (NT == 'B') {
+			NTsize = 1;
+		} else if (NT == 'S') {
+			NTsize = 2;
+		} else if ((NT == 'I') || (NT == 'F')) {
+			NTsize = 4;
+		} else if ((NT == 'J') || (NT == 'D')){
+			NTsize = 8;
+		} else if (css.startsWith("Ljava.lang.Byte")) {
+			NT='L';
+			className = "java.lang.Byte";
+			NTsize = 1;
+		} else if (css.startsWith("Ljava.lang.Short")) {
+			NT='L';
+			className = "java.lang.Short";
+			NTsize = 2;
+		} else if (css.startsWith("Ljava.lang.Integer")) {
+			NT='L';
+			className = "java.lang.Integer";
+			NTsize = 4;
+		} else if (css.startsWith("Ljava.lang.Float")) {
+			NT='L';
+			className = "java.lang.Float";
+			NTsize = 4;
+		} else if (css.startsWith("Ljava.lang.Double")) {
+			NT='L';
+			className = "java.lang.Double";
+			NTsize = 8;
+		} else if (css.startsWith("Ljava.lang.Long")) {
+			NT='L';
+			className = "java.lang.Long";
+			NTsize = 8;
+		} else if (css.startsWith("Ljava.lang.String")) {
+throw new HDF5JavaException(new String("ArrayDesciptor: Error:  String array not supported yet"));
+		} else {
+			/* exception:  not a numeric type */
+throw new HDF5JavaException(new String("ArrayDesciptor: Error:  array is not numeric (type is "+css+") ?"));
+		}
+
+		/* fill in the table */
+		dimlen = new int [dims+1];
+		dimstart = new int [dims+1];
+		currentindex = new int [dims+1];
+		bytetoindex = new int [dims+1];
+		objs = new Object [dims+1];
+
+		Object o = anArray;
+		objs[0] = o;
+		dimlen[0]= 1;
+		dimstart[0] = 0;
+		currentindex[0] = 0;
+		int i;
+		for ( i = 1; i <= dims; i++) {
+			dimlen[i]= java.lang.reflect.Array.getLength((Object) o);
+			o = java.lang.reflect.Array.get((Object) o,0);
+			objs [i] = o;
+			dimstart[i] = 0;
+			currentindex[i] = 0;
+		}
+
+		int j;
+		int dd;
+		bytetoindex[dims] = NTsize;
+		for ( i = dims; i >= 0; i--) {
+			dd = NTsize;
+			for (j = i; j < dims; j++) {
+				dd *= dimlen[j + 1];
+			}
+			bytetoindex[i] = dd;
+		}
+
+		totalSize = bytetoindex[0];
+	}
+
+	/**
+          *  Debug dump
+          */
+	public void dumpInfo()
+	{
+		System.out.println("Type: "+theType);
+		System.out.println("Class: "+theClass);
+		System.out.println("NT: "+NT+" NTsize: "+NTsize);
+		System.out.println("Array has "+dims+" dimensions ("+totalSize+" bytes)");
+		int i;
+		for (i = 0; i <= dims; i++) {
+			Class tc = objs[i].getClass();
+			String ss = tc.toString();
+			System.out.println(i+":  start "+dimstart[i]+": len "+dimlen[i]+" current "+currentindex[i]+" bytetoindex "+bytetoindex[i]+" object "+objs[i]+" otype "+ss);
+		}
+	}
+}
diff --git a/ncsa/hdf/hdf5lib/HDFNativeData.java b/ncsa/hdf/hdf5lib/HDFNativeData.java
new file mode 100644
index 0000000..b4965c0
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/HDFNativeData.java
@@ -0,0 +1,393 @@
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib;
+
+import ncsa.hdf.hdf5lib.exceptions.*;
+
+/**
+  * This class encapsulates native methods to deal with
+  * arrays of numbers, converting from numbers to bytes
+  * and bytes to numbers.
+  * <p>
+  * These routines are used by class <b>HDFArray</b> to
+  * pass data to and from the HDF-5 library.
+  * <p>
+  * Methods xxxToByte() convert a Java array of primitive
+  * numbers (int, short, ...) to a Java array of bytes.
+  * Methods byteToXxx() convert from a Java array of
+  * bytes into a Java array of primitive numbers (int, short, ...)
+  * <p>
+  * Variant interfaces convert a section of an array, and also
+  * can convert to sub-classes of Java <b>Number</b>.
+  * <P>
+  * <b>See also:</b> ncsa.hdf.hdf5lib.HDFArray.
+  */
+
+public class HDFNativeData
+{
+	static 
+	{
+		try { 
+			int[] libversion = new int[3];
+			H5.H5get_libversion(libversion);   // force the load if needed.
+		} catch ( HDF5Exception e ) {
+			System.out.println("HDFNative:  error loading library?");
+			System.exit(1);
+		}
+	}
+	/**
+          * Convert an array of bytes into an array of ints
+	  *
+	  *  @param data  The input array of bytes
+	  *  @returns an array of int
+          */
+	public static native int[] byteToInt( byte[] data );
+
+	/**
+          * Convert an array of bytes into an array of floats
+	  *
+	  *  @param data  The input array of bytes
+	  *  @returns an array of float
+          */
+	public static native float[] byteToFloat( byte[] data );
+
+	/**
+          * Convert an array of bytes into an array of shorts
+	  *
+	  *  @param data  The input array of bytes
+	  *  @returns an array of short
+          */
+	public static native short[] byteToShort( byte[] data );
+
+	/**
+          * Convert an array of bytes into an array of long
+	  *
+	  *  @param data  The input array of bytes
+	  *  @returns an array of long
+          */
+	/* does this really work?  C 'long' is 32 bits, Java 'long'
+           is 64-bits.  What does this routine actually do? */
+	public static native long[] byteToLong( byte[] data );
+
+	/**
+          * Convert an array of bytes into an array of double
+	  *
+	  *  @param data  The input array of bytes
+	  *  @returns an array of double
+          */
+	public static native double[] byteToDouble( byte[] data );
+
+	/**
+          * Convert a range from an array of bytes into an array of int
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param len  The number of 'int' to convert
+	  *  @param data  The input array of bytes
+	  *  @returns an array of 'len' int
+          */
+	public static native int[] byteToInt( int start, int len, byte[] data );
+	/**
+          * Convert 4 bytes from an array of bytes into a single int
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param data  The input array of bytes
+	  *  @returns The integer value of the bytes.
+          */
+	public static int byteToInt( byte[] data, int start)
+	{
+		int []ival = new int[1];
+		ival = byteToInt(start,1,data);
+		return(ival[0]);
+	}
+
+	/**
+          * Convert a range from an array of bytes into an array of short
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param len  The number of 'short' to convert
+	  *  @param data  The input array of bytes
+	  *  @returns an array of 'len' short
+          */
+	public static native short[] byteToShort( int start, int len, byte[] data );
+
+	/**
+          * Convert 2 bytes from an array of bytes into a single short
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param data  The input array of bytes
+	  *  @returns The short value of the bytes.
+          */
+	public static short byteToShort( byte[] data, int start)
+	{
+		short []sval = new short[1];
+		sval = byteToShort(start,1,data);
+		return(sval[0]);
+	}
+
+	/**
+          * Convert a range from an array of bytes into an array of float
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param len  The number of 'float' to convert
+	  *  @param data  The input array of bytes
+	  *  @returns an array of 'len' float
+          */
+	public static native float[] byteToFloat( int start, int len, byte[] data );
+
+	/**
+          * Convert 4 bytes from an array of bytes into a single float
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param data  The input array of bytes
+	  *  @returns The float value of the bytes.
+          */
+	public static float byteToFloat( byte[] data, int start)
+	{
+		float []fval = new float[1];
+		fval = byteToFloat(start,1,data);
+		return(fval[0]);
+	}
+
+	/**
+          * Convert a range from an array of bytes into an array of long
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param len  The number of 'long' to convert
+	  *  @param data  The input array of bytes
+	  *  @returns an array of 'len' long
+          */
+	public static native long[] byteToLong( int start, int len, byte[] data );
+	/**
+          * Convert 8(?) bytes from an array of bytes into a single long
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param data  The input array of bytes
+	  *  @returns The long value of the bytes.
+          */
+	public static long byteToLong( byte[] data, int start)
+	{
+		long []lval = new long[1];
+		lval = byteToLong(start,1,data);
+		return(lval[0]);
+	}
+
+	/**
+          * Convert a range from an array of bytes into an array of double
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param len  The number of 'double' to convert
+	  *  @param data  The input array of bytes
+	  *  @returns an array of 'len' double
+          */
+	public static native double[] byteToDouble( int start, int len, byte[] data );
+
+	/**
+          * Convert 8 bytes from an array of bytes into a single double
+	  *
+	  *  @param start  The position in the input array of bytes to start
+	  *  @param data  The input array of bytes
+	  *  @returns The double value of the bytes.
+          */
+	public static double byteToDouble( byte[] data, int start)
+	{
+		double []dval = new double[1];
+		dval = byteToDouble(start,1,data);
+		return(dval[0]);
+	}
+
+
+	/**
+          * Convert a range from an array of int into an array of bytes.
+	  *
+	  *  @param start  The position in the input array of int to start
+	  *  @param len  The number of 'int' to convert
+	  *  @param data  The input array of int
+	  *  @returns an array of bytes
+          */
+	public static native byte[] intToByte( int start, int len, int[] data);
+	/**
+          * Convert a range from an array of short into an array of bytes.
+	  *
+	  *  @param start  The position in the input array of int to start
+	  *  @param len  The number of 'short' to convert
+	  *  @param data  The input array of short
+	  *  @returns an array of bytes
+          */
+	public static native byte[] shortToByte( int start, int len, short[] data);
+	/**
+          * Convert a range from an array of float into an array of bytes.
+	  *
+	  *  @param start  The position in the input array of int to start
+	  *  @param len  The number of 'float' to convert
+	  *  @param data  The input array of float
+	  *  @returns an array of bytes
+          */
+	public static native byte[] floatToByte( int start, int len, float[] data);
+	/**
+          * Convert a range from an array of long into an array of bytes.
+	  *
+	  *  @param start  The position in the input array of int to start
+	  *  @param len  The number of 'long' to convert
+	  *  @param data  The input array of long
+	  *  @returns an array of bytes
+          */
+	public static native byte[] longToByte( int start, int len, long[] data);
+	/**
+          * Convert a range from an array of double into an array of bytes.
+	  *
+	  *  @param start  The position in the input array of double to start
+	  *  @param len  The number of 'double' to convert
+	  *  @param data  The input array of double
+	  *  @returns an array of bytes
+          */
+	public static native byte[] doubleToByte( int start, int len, double[] data);
+
+	/**
+          * Convert a single byte into an array of one byte.
+	  * <p>
+          * (This is a trivial method.)
+	  *
+	  *  @param data  The input byte
+	  *  @returns an array of bytes
+          */
+	public static native byte[] byteToByte( byte data);
+
+	/**
+          * Convert a single Byte object into an array of one byte.
+	  * <p>
+          * (This is an almost trivial method.)
+	  *
+	  *  @param data  The input Byte
+	  *  @returns an array of bytes
+          */
+	public static byte[] byteToByte( Byte data){return byteToByte(data.byteValue());}
+
+	/**
+          * Convert a single int into an array of 4 bytes.
+	  *
+	  *  @param data  The input int
+	  *  @returns an array of bytes
+	  */
+	public static native byte[] intToByte( int data);
+
+	/**
+          * Convert a single Integer object into an array of 4 bytes.
+	  *
+	  *  @param data  The input Integer
+	  *  @returns an array of bytes
+          */
+	public static byte[] intToByte( Integer data){return intToByte(data.intValue());}
+
+	/**
+          * Convert a single short into an array of 2 bytes.
+	  *
+	  *  @param data  The input short
+	  *  @returns an array of bytes
+	  */
+	public static native byte[] shortToByte(short data);
+
+	/**
+          * Convert a single Short object into an array of 2 bytes.
+	  *
+	  *  @param data  The input Short
+	  *  @returns an array of bytes
+          */
+	public static byte[] shortToByte( Short data){return shortToByte(data.shortValue());}
+
+	/**
+          * Convert a single float into an array of 4 bytes.
+	  *
+	  *  @param data  The input float
+	  *  @returns an array of bytes
+	  */
+	public static native byte[] floatToByte( float data );
+
+	/**
+          * Convert a single Float object into an array of 4 bytes.
+	  *
+	  *  @param data  The input Float
+	  *  @returns an array of bytes
+          */
+	public static byte[] floatToByte( Float data){return floatToByte(data.floatValue());};
+
+	/**
+          * Convert a single long into an array of 8 bytes.
+	  *
+	  *  @param data  The input long
+	  *  @returns an array of bytes
+	  */
+	public static native byte[] longToByte( long data);
+
+	/**
+          * Convert a single Long object into an array of 8(?) bytes.
+	  *
+	  *  @param data  The input Long
+	  *  @returns an array of bytes
+          */
+	public static byte[] longToByte(Long data){ return longToByte(data.longValue());}
+
+	/**
+          * Convert a single double into an array of 8 bytes.
+	  *
+	  *  @param data  The input double
+	  *  @returns an array of bytes
+	  */
+	public static native byte[] doubleToByte( double data);
+
+	/**
+          * Convert a single Double object into an array of 8 bytes.
+	  *
+	  *  @param data  The input Double
+	  *  @returns an array of bytes
+          */
+	public static byte[] doubleToByte( Double data){return doubleToByte(data.doubleValue());}
+
+	/**
+          * Create a Number object from an array of bytes.
+	  *
+	  *  @param barray  The bytes to be converted
+	  *  @param obj  Input object of the desired output class.  Must be a sub-class of Number.
+	  *  @returns A Object of the type  of obj.
+          */
+	public static Object byteToNumber( byte[] barray, Object obj)
+		throws HDF5Exception
+	{
+		Class theClass = obj.getClass();
+		String type = theClass.getName();
+		Object retobj = null;
+
+		if (type.equals("java.lang.Integer")) {
+			int[] i = ncsa.hdf.hdf5lib.HDFNativeData.byteToInt(0,1,barray);
+			retobj = new Integer(i[0]);
+		} else  if (type.equals("java.lang.Byte")) {
+			retobj = new Byte(barray[0]);
+		} else  if (type.equals("java.lang.Short")) {
+			short[] f = ncsa.hdf.hdf5lib.HDFNativeData.byteToShort(0,1,barray);
+			retobj = new Short(f[0]) ;
+		} else  if (type.equals("java.lang.Float")) {
+			float[] f = ncsa.hdf.hdf5lib.HDFNativeData.byteToFloat(0,1,barray);
+			retobj = new Float(f[0]) ;
+		} else  if (type.equals("java.lang.Long")) {
+			long[] f = ncsa.hdf.hdf5lib.HDFNativeData.byteToLong(0,1,barray);
+			retobj = new Long(f[0]) ;
+		} else  if (type.equals("java.lang.Double")) {
+			double[] f = ncsa.hdf.hdf5lib.HDFNativeData.byteToDouble(0,1,barray);
+			retobj = new Double(f[0] );
+		} else {
+			/* exception: unsupported type */
+			HDF5Exception ex =
+			(HDF5Exception)new HDF5JavaException("byteToNumber: setfield bad type: "+obj+" "+type);
+			throw(ex);
+		}
+		return(retobj);
+	}
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5AtomException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5AtomException.java
new file mode 100644
index 0000000..9004dab
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5AtomException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_ATOM</b>
+ */
+
+public class HDF5AtomException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5AtomException</code> with no 
+     * specified detail message.
+     */
+     public HDF5AtomException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5AtomException</code> with the 
+     * specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5AtomException(String s) {
+        super(s);
+    }
+
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5AttributeException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5AttributeException.java
new file mode 100644
index 0000000..732c3b1
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5AttributeException.java
@@ -0,0 +1,40 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_ATTR</b>
+ */
+public class HDF5AttributeException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5AttributeException</code> with 
+     * no specified detail message.
+     */
+     public HDF5AttributeException() {
+        super();
+     }
+
+    /**
+     * Constructs an <code>HDF5AttributeException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5AttributeException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5BtreeException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5BtreeException.java
new file mode 100644
index 0000000..644342f
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5BtreeException.java
@@ -0,0 +1,41 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_BTREE</b>
+ */
+public class HDF5BtreeException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5BtreeException</code> with 
+     * no specified detail message.
+     */
+     public HDF5BtreeException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5BtreeException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5BtreeException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5DataFiltersException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5DataFiltersException.java
new file mode 100644
index 0000000..5f59368
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5DataFiltersException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_PLINE</b>
+ */
+public class HDF5DataFiltersException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5DataFiltersException</code> with 
+     * no specified detail message.
+     */
+     public HDF5DataFiltersException() {
+        super();
+     }
+
+    /**
+     * Constructs an <code>HDF5DataFiltersException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5DataFiltersException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5DataStorageException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5DataStorageException.java
new file mode 100644
index 0000000..5c3ffe6
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5DataStorageException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_STORAGE</b>
+ */
+
+public class HDF5DataStorageException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5DataStorageExceptionn</code> with 
+     * no specified detail message.
+     */
+     public HDF5DataStorageException() {
+        super();
+     }
+
+    /**
+     * Constructs an <code>HDF5DataStorageException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5DataStorageException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5DatasetInterfaceException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5DatasetInterfaceException.java
new file mode 100644
index 0000000..f8347ea
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5DatasetInterfaceException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_DATASET</b>
+ */
+public class HDF5DatasetInterfaceException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5DatasetInterfaceException</code> with 
+     * no specified detail message.
+     */
+     public HDF5DatasetInterfaceException() {
+        super();
+     }
+
+    /**
+     * Constructs an <code>HDF5DatasetInterfaceException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5DatasetInterfaceException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5DataspaceInterfaceException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5DataspaceInterfaceException.java
new file mode 100644
index 0000000..40805a6
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5DataspaceInterfaceException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_DATASPACE</b>
+ */
+
+public class HDF5DataspaceInterfaceException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5DataspaceInterfaceException</code> with 
+     * no specified detail message.
+     */
+    public HDF5DataspaceInterfaceException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5DataspaceInterfaceException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5DataspaceInterfaceException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5DatatypeInterfaceException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5DatatypeInterfaceException.java
new file mode 100644
index 0000000..7d34855
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5DatatypeInterfaceException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_DATATYPE</b>
+ */
+
+public class HDF5DatatypeInterfaceException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5DatatypeInterfaceException</code> with 
+     * no specified detail message.
+     */
+    public HDF5DatatypeInterfaceException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5DatatypeInterfaceException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5DatatypeInterfaceException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5Exception.java b/ncsa/hdf/hdf5lib/exceptions/HDF5Exception.java
new file mode 100644
index 0000000..e7c77a3
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5Exception.java
@@ -0,0 +1,68 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  <p>
+ *  The class HDF5Exception returns errors from the 
+ *  Java HDF5 Interface.
+ *  <p>
+ *  Two sub-classes of HDF5Exception are defined:
+ *  <p>
+ *  <ol>
+ *  <li>
+ *   HDF5LibraryException -- errors raised the HDF5 library code
+ *  <li>
+ *   HDF5JavaException -- errors raised the HDF5 Java wrapper code
+ *  </ol>
+ *  <p>
+ *  These exceptions are sub-classed to represent specific
+ *  error conditions, as needed.  In particular, 
+ *  HDF5LibraryException has a sub-class for each major error
+ *  code returned by the HDF5 library.
+ * 
+ */
+public class HDF5Exception extends Exception {
+
+	
+    protected String detailMessage;
+
+    /**
+     * Constructs an <code>HDF5Exception</code> with no specified 
+     * detail message.
+     */
+     public HDF5Exception() {
+		super();
+     }
+
+    /**
+     * Constructs an <code>HDF5Exception</code> with the specified 
+     * detail message.
+     *
+     * @param   message   the detail message.
+     */
+    public HDF5Exception(String message) {
+		super();
+		detailMessage = message;
+    }
+
+    /**
+     * Returns the detail message of this exception
+     *
+     * @return  the detail message 
+     *          or <code>null</code> if this object does not
+     *          have a detail message.
+     */
+    public String getMessage() {
+	return detailMessage;
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5ExternalFileListException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5ExternalFileListException.java
new file mode 100644
index 0000000..da4c1a1
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5ExternalFileListException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_EFL</b>
+ */
+
+public class HDF5ExternalFileListException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5ExternalFileListException</code> with 
+     * no specified detail message.
+     */
+    public HDF5ExternalFileListException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5ExternalFileListException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5ExternalFileListException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5FileInterfaceException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5FileInterfaceException.java
new file mode 100644
index 0000000..1cf4781
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5FileInterfaceException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_FILE</b>
+ */
+
+public class HDF5FileInterfaceException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5FileInterfaceException</code> with 
+     * no specified detail message.
+     */
+    public HDF5FileInterfaceException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5FileInterfaceException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5FileInterfaceException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5FunctionArgumentException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5FunctionArgumentException.java
new file mode 100644
index 0000000..bd38fb4
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5FunctionArgumentException.java
@@ -0,0 +1,41 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_ARGS</b>
+ */
+
+public class HDF5FunctionArgumentException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5FunctionArgumentException</code> with 
+     * no specified detail message.
+     */
+    public HDF5FunctionArgumentException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5FunctionArgumentException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5FunctionArgumentException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5FunctionEntryExitException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5FunctionEntryExitException.java
new file mode 100644
index 0000000..f0e1820
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5FunctionEntryExitException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_FUNC</b>
+ */
+
+public class HDF5FunctionEntryExitException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5FunctionEntryExitException</code> with 
+     * no specified detail message.
+     */
+    public HDF5FunctionEntryExitException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5FunctionEntryExitException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5FunctionEntryExitException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5HeapException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5HeapException.java
new file mode 100644
index 0000000..55bd878
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5HeapException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_HEAP</b>
+ */
+
+public class HDF5HeapException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5HeapException</code> with 
+     * no specified detail message.
+     */
+    public HDF5HeapException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5HeapException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5HeapException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5InternalErrorException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5InternalErrorException.java
new file mode 100644
index 0000000..ae7d081
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5InternalErrorException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5 
+ *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_INTERNAL</b>
+ */
+
+public class HDF5InternalErrorException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5InternalErrorException</code> with 
+     * no specified detail message.
+     */
+    public HDF5InternalErrorException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5InternalErrorException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5InternalErrorException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5JavaException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5JavaException.java
new file mode 100644
index 0000000..b2c8480
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5JavaException.java
@@ -0,0 +1,41 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  <p>
+ *  The class HDF5JavaException returns errors from the Java
+ *  wrapper of theHDF5 library.
+ *  <p>
+ *  These errors include Java configuration errors, security
+ *  violations, and resource exhaustion.
+ */
+public class HDF5JavaException extends HDF5Exception {
+
+    /**
+     * Constructs an <code>HDF5JavaException</code> with no 
+     * specified detail message.
+     */
+    public HDF5JavaException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5JavaException</code> with the 
+     * specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5JavaException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5LibraryException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5LibraryException.java
new file mode 100644
index 0000000..d6d023d
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5LibraryException.java
@@ -0,0 +1,233 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.HDF5Constants;
+
+/**
+ *  <p>
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  Each major error code from the HDF-5 Library is represented
+ *  by a sub-class of this class, and by default the 'detailedMessage'
+ *  is set according to the minor error code from the HDF-5 Library.
+ *  <p>
+ *  For major and minor error codes, see <b>H5Epublic.h</b> in the HDF-5
+ *  library.
+ *  <p>
+ */
+
+
+public class HDF5LibraryException extends HDF5Exception {
+
+	/**
+	 * Constructs an <code>HDF5LibraryException</code> with 
+	 * no specified detail message.
+	 */
+	 public HDF5LibraryException() {
+		super();
+
+		// this code forces the loading of the HDF-5 library
+		// to assure that the native methods are available
+		try { H5.H5open(); } catch (Exception e) {};
+
+		detailMessage = getMinorError(getMinorErrorNumber());
+	}
+
+	/**
+	 * Constructs an <code>HDF5LibraryException</code> with 
+	 * the specified detail message.
+	 *
+	 * @param   s   the detail message.
+	 */
+	public HDF5LibraryException(String s) {
+		super(s);
+		// this code forces the loading of the HDF-5 library
+		// to assure that the native methods are available
+		try { H5.H5open(); } catch (Exception e) {};
+	}
+
+	/**
+	 * Get the major error number of the first error on the 
+	 * HDF5 library error stack.
+	 *
+	 * @return the major error number
+	 */
+	public native int getMajorErrorNumber();
+
+   	/**
+	 * Get the minor error number of the first error on the 
+	 * HDF5 library error stack.
+	 *
+	 * @return the minor error number
+	 */
+	public native int getMinorErrorNumber();
+
+    /**
+     *  Return a error message for the minor error number.
+     * <p>
+     *  These messages come from <b>H5Epublic.h</b>.
+     *
+     *  @param min_num the minor error number
+     *
+     *  @return the string of the minor error
+     */
+	public String getMinorError(int min_num)
+	{
+		switch (min_num)
+		{
+			case HDF5Constants.H5E_NONE_MINOR:
+				return "no error";
+			case HDF5Constants.H5E_UNINITIALIZED:
+				return "information is unitialized";
+			case HDF5Constants.H5E_UNSUPPORTED:
+				return "feature is unsupported";
+			case HDF5Constants.H5E_BADTYPE:
+				return "incorrect type found";
+			case HDF5Constants.H5E_BADRANGE:
+				return "argument out of range";
+			case HDF5Constants.H5E_BADVALUE:
+				return "bad value for argument";
+			case HDF5Constants.H5E_NOSPACE:
+				return "no space available for allocation";
+			case HDF5Constants.H5E_CANTCOPY:
+				return "unable to copy object";
+			case HDF5Constants.H5E_FILEEXISTS:
+				return "file already exists";
+			case HDF5Constants.H5E_FILEOPEN:
+				return "file already open";
+			case HDF5Constants.H5E_CANTCREATE:
+				return "Can't create file";
+			case HDF5Constants.H5E_CANTOPENFILE:
+				return "Can't open file";
+			case HDF5Constants.H5E_NOTHDF5:
+				return "not an HDF5 format file";
+			case HDF5Constants.H5E_BADFILE:
+				return "bad file ID accessed";
+			case HDF5Constants.H5E_TRUNCATED:
+				return "file has been truncated";
+			case HDF5Constants.H5E_MOUNT:
+				return "file mount error";
+			case HDF5Constants.H5E_SEEKERROR:
+				return "seek failed";
+			case HDF5Constants.H5E_READERROR:
+				return "read failed";
+			case HDF5Constants.H5E_WRITEERROR:
+				return "write failed";
+			case HDF5Constants.H5E_CLOSEERROR:
+				return "close failed";
+			case HDF5Constants.H5E_OVERFLOW:
+				return "address overflowed";
+			case HDF5Constants.H5E_CANTINIT:
+				return "Can't initialize";
+			case HDF5Constants.H5E_ALREADYINIT:
+				return "object already initialized";
+			case HDF5Constants.H5E_BADATOM:
+				return "Can't find atom information";
+			case HDF5Constants.H5E_CANTREGISTER:
+				return "Can't register new atom";
+			case HDF5Constants.H5E_CANTFLUSH:
+				return "Can't flush object from cache";
+			case HDF5Constants.H5E_CANTLOAD:
+				return "Can't load object into cache";
+			case HDF5Constants.H5E_PROTECT:
+				return "protected object error";
+			case HDF5Constants.H5E_NOTCACHED:
+				return "object not currently cached";
+			case HDF5Constants.H5E_NOTFOUND:
+				return "object not found";
+			case HDF5Constants.H5E_EXISTS:
+				return "object already exists";
+			case HDF5Constants.H5E_CANTENCODE:
+				return "Can't encode value";
+			case HDF5Constants.H5E_CANTDECODE:
+				return "Can't decode value";
+			case HDF5Constants.H5E_CANTSPLIT:
+				return "Can't split node";
+			case HDF5Constants.H5E_CANTINSERT:
+				return "Can't insert object";
+			case HDF5Constants.H5E_CANTLIST:
+				return "Can't list node";
+			case HDF5Constants.H5E_LINKCOUNT:
+				return "bad object header link count";
+			case HDF5Constants.H5E_VERSION:
+				return "wrong version number";
+			case HDF5Constants.H5E_ALIGNMENT:
+				return "alignment error";
+			case HDF5Constants.H5E_BADMESG:
+				return "unrecognized message";
+			case HDF5Constants.H5E_CANTDELETE:
+				return "Can't delete message";
+			case HDF5Constants.H5E_CANTOPENOBJ:
+				return "Can't open object";
+			case HDF5Constants.H5E_COMPLEN:
+				return "name component is too long";
+			case HDF5Constants.H5E_CWG:
+				return "problem with current working group";
+			case HDF5Constants.H5E_LINK:
+				return "link count failure";
+			case HDF5Constants.H5E_SLINK:
+				return "symbolic link error";
+			case HDF5Constants.H5E_MPI:
+				return "some MPI function failed";
+			default:
+				return "undefined error";
+		}
+	}
+
+
+	/**
+	 * Prints this <code>HDF5LibraryException</code>,
+	 * the HDF-5 Library error stack, and
+	 * and the Java stack trace to the standard error stream.
+	 */
+	public void printStackTrace() {
+		System.err.println(this);
+		printStackTrace0(null); // the HDF-5 Library error stack
+		super.printStackTrace(); // the Java stack trace
+	}
+
+	/**
+	 * Prints this <code>HDF5LibraryException</code> 
+	 * the HDF-5 Library error stack, and
+	 * and the Java stack trace to the 
+	 * specified print stream. 
+	 *
+	 */
+	public void printStackTrace(java.io.File f) {
+		if (f==null || !f.exists() || f.isDirectory() || !f.canWrite())
+			printStackTrace();
+		else
+		{
+		    try {
+			java.io.FileOutputStream o = new java.io.FileOutputStream(f);
+			java.io.PrintWriter p = new java.io.PrintWriter(o);
+			p.println(this);
+			p.close();
+		    } catch (Exception ex) {
+			System.err.println(this);
+		    };
+		    // the HDF-5 Library error stack
+		    printStackTrace0(f.getPath()); 
+		    super.printStackTrace(); // the Java stack trace
+		}
+	}
+
+	/*
+	 *  This private method calls the HDF-5 library to extract
+	 *  the error codes and error stack.
+	 */
+	private native void printStackTrace0(String s);
+
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5LowLevelIOException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5LowLevelIOException.java
new file mode 100644
index 0000000..e86b8cc
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5LowLevelIOException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_IO</b>
+ */
+
+public class HDF5LowLevelIOException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5LowLevelIOException</code> with 
+     * no specified detail message.
+     */
+    public HDF5LowLevelIOException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5LowLevelIOException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5LowLevelIOException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5MetaDataCacheException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5MetaDataCacheException.java
new file mode 100644
index 0000000..6aa340e
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5MetaDataCacheException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_CACHE</b>
+ */
+
+public class HDF5MetaDataCacheException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5MetaDataCacheException</code> with 
+     * no specified detail message.
+     */
+    public HDF5MetaDataCacheException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5MetaDataCacheException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5MetaDataCacheException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5ObjectHeaderException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5ObjectHeaderException.java
new file mode 100644
index 0000000..567f8e6
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5ObjectHeaderException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_OHDR</b>
+ */
+
+public class HDF5ObjectHeaderException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5ObjectHeaderException</code> with 
+     * no specified detail message.
+     */
+    public HDF5ObjectHeaderException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5ObjectHeaderException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5ObjectHeaderException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5PropertyListInterfaceException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5PropertyListInterfaceException.java
new file mode 100644
index 0000000..88d1cf1
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5PropertyListInterfaceException.java
@@ -0,0 +1,41 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_PLIST</b>
+ */
+
+public class HDF5PropertyListInterfaceException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5PropertyListInterfaceException</code> 
+     * with no specified detail message.
+     */
+    public HDF5PropertyListInterfaceException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5PropertyListInterfaceException</code> 
+     * with the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5PropertyListInterfaceException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5ReferenceException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5ReferenceException.java
new file mode 100644
index 0000000..1cdaf0f
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5ReferenceException.java
@@ -0,0 +1,34 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+public class HDF5ReferenceException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5ReferenceException</code> with 
+     * no specified detail message.
+     */
+    public HDF5ReferenceException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5ReferenceException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5ReferenceException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5ResourceUnavailableException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5ResourceUnavailableException.java
new file mode 100644
index 0000000..2bfd0d9
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5ResourceUnavailableException.java
@@ -0,0 +1,41 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_RESOURCE</b>
+ */
+
+public class HDF5ResourceUnavailableException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5ResourceUnavailableException</code> 
+     * with no specified detail message.
+     */
+    public HDF5ResourceUnavailableException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5FunctionArgumentException</code> 
+     * with the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5ResourceUnavailableException(String s) {
+        super(s);
+    }
+}
diff --git a/ncsa/hdf/hdf5lib/exceptions/HDF5SymbolTableException.java b/ncsa/hdf/hdf5lib/exceptions/HDF5SymbolTableException.java
new file mode 100644
index 0000000..ad428d7
--- /dev/null
+++ b/ncsa/hdf/hdf5lib/exceptions/HDF5SymbolTableException.java
@@ -0,0 +1,42 @@
+/****************************************************************************
+ * NCSA HDF5                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package ncsa.hdf.hdf5lib.exceptions;
+
+
+/**
+ *  The class HDF5LibraryException returns errors raised by the HDF5
+ *  library.
+ *  <p>
+ *  This sub-class represents HDF-5 major error code
+ *       <b>H5E_SYM</b>
+ */
+
+public class HDF5SymbolTableException extends HDF5LibraryException {
+
+    /**
+     * Constructs an <code>HDF5SymbolTableException</code> with no 
+     * specified detail message.
+     */
+    public HDF5SymbolTableException() {
+        super();
+    }
+
+    /**
+     * Constructs an <code>HDF5SymbolTableException</code> with 
+     * the specified detail message.
+     *
+     * @param   s   the detail message.
+     */
+    public HDF5SymbolTableException(String s) {
+        super(s);
+    }
+}
diff --git a/nom/tam/fits/A3DTableHDU.java b/nom/tam/fits/A3DTableHDU.java
new file mode 100644
index 0000000..af95d28
--- /dev/null
+++ b/nom/tam/fits/A3DTableHDU.java
@@ -0,0 +1,51 @@
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+
+
+import nom.tam.util.ArrayFuncs;
+import nom.tam.util.BufferedDataInputStream;
+
+/** FITS binary table header/data unit */
+public class A3DTableHDU
+      extends BinaryTableHDU
+{
+  /** Create a binary table header/data unit.
+    * @param header the template specifying the binary table.
+    * @exception FitsException if there was a problem with the header.
+    * @deprecated
+    */
+  public A3DTableHDU(Header header)
+	throws FitsException
+  {
+    super(header);
+    if (!isHeader()) {
+      throw new FitsException("Not a valid A3D table header");
+    }
+  }
+
+  /** Check that this is a valid binary table header.
+    * @param header to validate.
+    * @return <CODE>true</CODE> if this is a binary table header.
+    * @deprecated
+    */
+  public static boolean isHeader(Header header)
+  {
+    String card0 = header.getCard(0);
+    return (card0 != null && card0.startsWith("XTENSION= 'A3DTABLE'"));
+  }
+
+  /** Check that this HDU has a valid header.
+    * @return <CODE>true</CODE> if this HDU has a valid header.
+    */
+  public boolean isHeader()
+  {
+    return isHeader(myHeader);
+  }
+}
diff --git a/nom/tam/fits/AsciiTableHDU.java b/nom/tam/fits/AsciiTableHDU.java
new file mode 100644
index 0000000..165877c
--- /dev/null
+++ b/nom/tam/fits/AsciiTableHDU.java
@@ -0,0 +1,85 @@
+package nom.tam.fits;
+
+import java.io.IOException;
+import nom.tam.util.BufferedDataInputStream;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  *
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+/** FITS ASCII table header/data unit
+  * ASCII tables are not currently suppoted.
+  */
+public class AsciiTableHDU
+	extends TableHDU
+{
+ /** Create an ascii table header/data unit.
+   * @param header the template specifying the ascii table.
+   * @exception FitsException if there was a problem with the header.
+   */
+   public AsciiTableHDU(Header header)
+  	throws FitsException
+    {
+      super(header);
+      if (!isHeader()) {
+        throw new FitsException("Not a valid ascii table header");
+      }
+    }
+
+
+  /** Check that this is a valid ascii table header.
+    * @param header to validate.
+    * @return <CODE>true</CODE> if this is an ascii table header.
+    */
+  public static boolean isHeader(Header header)
+  {
+    String card0 = header.getCard(0);
+    return (card0 != null && card0.startsWith("XTENSION= 'TABLE   '"));
+  }
+
+  /** Check that this HDU has a valid header.
+    * @return <CODE>true</CODE> if this HDU has a valid header.
+    */
+  public boolean isHeader()
+  {
+    return isHeader(myHeader);
+  }
+
+  /** Create a Data object to correspond to the header description.
+      * @return An unfilled Data object which can be used to read
+      *         in the data for this HDU.
+      * @exception FitsException if the Data object could not be created
+      *				from this HDU's Header
+      */
+    Data manufactureData() throws FitsException {
+      throw new FitsException("ASCII tables are currently not supported");
+    }
+
+    /** Skip the ASCII table and throw an exception.
+      * @param stream the stream from which the data is read.
+      * @return nothing since an exception is always thrown.
+      * @exception FitsException because ASCII tables are not yet supported.
+      */
+    public void readData(BufferedDataInputStream stream)
+  	throws FitsException
+    {
+      try {
+        skipData(stream);
+      } catch (IOException e) {
+     }
+
+      throw new FitsException("ASCII tables are currently not supported");
+    }
+
+    public void info() {
+      System.out.println("ASCII Table: unimplemented");
+    }
+}
+
diff --git a/nom/tam/fits/BadHeaderException.java b/nom/tam/fits/BadHeaderException.java
new file mode 100644
index 0000000..3aa2623
--- /dev/null
+++ b/nom/tam/fits/BadHeaderException.java
@@ -0,0 +1,22 @@
+package nom.tam.fits;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  *
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+/** This exception indicates that an error
+  * was detected while parsing a FITS header record.
+  */
+public class BadHeaderException
+	extends FitsException
+{
+   public BadHeaderException () { super(); }
+   public BadHeaderException (String msg) { super(msg); }
+}
diff --git a/nom/tam/fits/BasicHDU.java b/nom/tam/fits/BasicHDU.java
new file mode 100644
index 0000000..de96f69
--- /dev/null
+++ b/nom/tam/fits/BasicHDU.java
@@ -0,0 +1,397 @@
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+
+import java.io.IOException;
+
+import nom.tam.util.BufferedDataInputStream;
+import nom.tam.util.BufferedDataOutputStream;
+
+import java.util.Date;
+
+/** This abstract class is the parent of all HDU types.
+  * It provides basic functionality for an HDU.
+  */
+public abstract class BasicHDU
+{
+  public static final int BITPIX_BYTE  =	 8;
+  public static final int BITPIX_SHORT =	16;
+  public static final int BITPIX_INT =  	32;
+  public static final int BITPIX_LONG =  	64;
+  public static final int BITPIX_FLOAT =	-32;
+  public static final int BITPIX_DOUBLE =	-64;
+
+  /** The associated header. */
+  Header myHeader = null;
+
+  /** The associated data unit. */
+  Data myData = null;
+
+  /** Create an HDU with the specified Header and an empty Data section
+    * @param header the Header
+    */
+  public BasicHDU(Header header)
+  {
+    myHeader = header;
+    if (myHeader != null) {
+      myHeader.unsetMark();
+    }
+  }
+
+  /** Create a Data object to correspond to the header description.
+    * @return An unfilled Data object which can be used to read
+    *         in the data for this HDU.
+    * @exception FitsException if the Data object could not be created
+    *				from this HDU's Header
+    */
+  abstract Data manufactureData() throws FitsException;
+
+  /** Skip the Data object immediately after the given Header object on
+    * the given stream object.
+    * @param stream the stream which contains the data.
+    * @param Header template indicating length of Data section
+    * @exception IOException if the Data object could not be skipped.
+    */
+  public static void skipData(BufferedDataInputStream stream, Header hdr)
+	throws IOException
+  {
+    stream.skipBytes(hdr.paddedDataSize());
+  }
+
+  /** Skip the Data object for this HDU.
+    * @param stream the stream which contains the data.
+    * @exception IOException if the Data object could not be skipped.
+    */
+  public void skipData(BufferedDataInputStream stream)
+	throws IOException
+  {
+    skipData(stream, myHeader);
+  }
+
+  /** Read in the Data object for this HDU.
+    * @param stream the stream from which the data is read.
+    * @exception FitsException if the Data object could not be created
+    *				from this HDU's Header
+    */
+  public void readData(BufferedDataInputStream stream)
+	throws FitsException
+  {
+    myData = null;
+    try {
+      myData = manufactureData();
+    } finally {
+      // if we can't build a Data object, skip this section
+      if (myData == null) {
+	try {
+	  skipData(stream, myHeader);
+	} catch (Exception e) {
+	}
+      }
+    }
+
+    myData.read(stream);
+  }
+
+  /** Get the associated header */
+  public Header getHeader() {
+    return myHeader;
+  }
+
+  /** Get the associated Data object*/
+  public Data getData() {
+    return myData;
+  }
+
+  /** Get the total size in bytes of the HDU.
+    * @return The size in bytes.
+    */
+  public int getSize() {
+    int size = 0;
+
+    if (myHeader != null) {
+      size += myHeader.headerSize();
+    }
+    if (myData != null) {
+      size += myData.getPaddedSize();
+    }
+    return size;
+  }
+
+  /** Check that this is a valid header for the HDU.
+    * @param header to validate.
+    * @return <CODE>true</CODE> if this is a valid header.
+    */
+  public static boolean isHeader(Header header) { return false; }
+
+  /** Print out some information about this HDU.
+    */
+  public abstract void info();
+
+  /** Check if a field is present and if so print it out.
+    * @param The header keyword.
+    * @param Was it found in the header?
+    */
+  boolean checkField(String name) {
+    String value = myHeader.getStringValue(name);
+    if (value == null) {
+      return false;
+    }
+
+    System.out.print(" "+name+"="+value+";");
+    return true;
+  }
+
+  /* Write out the HDU
+   * @param stream The data stream to be written to.
+   */
+  public void write(BufferedDataOutputStream stream)
+	throws FitsException
+  {
+    if (myHeader != null) {
+      myHeader.write(stream);
+    }
+    if (myData != null) {
+      myData.write(stream);
+    }
+    try {
+      stream.flush();
+    } catch (java.io.IOException e) {
+      throw new FitsException("Error flushing at end of HDU: " +
+			      e.getMessage());
+    }
+  }
+
+  /**
+    * Get the String value associated with <CODE>keyword</CODE>.
+    * @param hdr	the header piece of an HDU
+    * @param keyword	the FITS keyword
+    * @return	either <CODE>null</CODE> or a String with leading/trailing
+    * 		blanks stripped.
+    */
+  public String getTrimmedString(String keyword)
+  {
+    String s = myHeader.getStringValue(keyword);
+    if (s != null) {
+      s = s.trim();
+    }
+    return s;
+  }
+
+  public int getBitPix()
+	throws FitsException
+  {
+    int bitpix = myHeader.getIntValue("BITPIX", -1);
+    switch (bitpix) {
+    case BITPIX_BYTE:
+    case BITPIX_SHORT:
+    case BITPIX_INT:
+    case BITPIX_FLOAT:
+    case BITPIX_DOUBLE:
+      break;
+    default:
+      throw new FitsException("Unknown BITPIX type " + bitpix);
+    }
+
+    return bitpix;
+  }
+
+  public int[] getAxes()
+	throws FitsException
+  {
+    int nAxis = myHeader.getIntValue("NAXIS", 0);
+    if (nAxis < 0) {
+      throw new FitsException("Negative NAXIS value " + nAxis);
+    }
+    if (nAxis > 999) {
+      throw new FitsException("NAXIS value " + nAxis + " too large");
+    }
+
+    if (nAxis == 0) {
+      return null;
+    }
+
+    int[] axes = new int[nAxis];
+    for (int i = 1; i <= nAxis; i++) {
+      axes[nAxis - i] = myHeader.getIntValue("NAXIS" + i, 0);
+    }
+
+    return axes;
+  }
+
+  public int getParameterCount()
+  {
+    return myHeader.getIntValue("PCOUNT", 0);
+  }
+
+  public int getGroupCount()
+  {
+    return myHeader.getIntValue("GCOUNT", 1);
+  }
+
+  public double getBScale()
+  {
+    return myHeader.getDoubleValue("BSCALE", 1.0);
+  }
+
+  public double getBZero()
+  {
+    return myHeader.getDoubleValue("BZERO", 0.0);
+  }
+
+  public String getBUnit()
+  {
+    return getTrimmedString("BUNIT");
+  }
+
+  public int getBlankValue()
+	throws FitsException
+  {
+    if (!myHeader.containsKey("BLANK")) {
+      throw new FitsException("BLANK undefined");
+    }
+    return myHeader.getIntValue("BLANK");
+  }
+
+  /**
+    * Get the FITS file creation date as a <CODE>Date</CODE> object.
+    * @return	either <CODE>null</CODE> or a Date object
+    */
+  public Date getCreationDate()
+  {
+    try {
+      return new FitsDate(myHeader.getStringValue("DATE")).toDate();
+    } catch (FitsException e) {
+      return null;
+    }
+  }
+
+  /**
+    * Get the FITS file observation date as a <CODE>Date</CODE> object.
+    * @return	either <CODE>null</CODE> or a Date object
+    */
+  public Date getObservationDate()
+  {
+    try {
+      return new FitsDate(myHeader.getStringValue("DATE-OBS")).toDate();
+    } catch (FitsException e) {
+      return null;
+    }
+  }
+
+  /**
+    * Get the name of the organization which created this FITS file.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public String getOrigin()
+  {
+    return getTrimmedString("ORIGIN");
+  }
+
+  /**
+    * Get the name of the telescope which was used to acquire the data in
+    * this FITS file.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public String getTelescope()
+  {
+    return getTrimmedString("TELESCOP");
+  }
+
+  /**
+    * Get the name of the instrument which was used to acquire the data in
+    * this FITS file.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public String getInstrument()
+  {
+    return getTrimmedString("INSTRUME");
+  }
+
+  /**
+    * Get the name of the person who acquired the data in this FITS file.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public String getObserver()
+  {
+    return getTrimmedString("OBSERVER");
+  }
+
+  /**
+    * Get the name of the observed object in this FITS file.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public String getObject()
+  {
+    return getTrimmedString("OBJECT");
+  }
+
+  /**
+    * Get the equinox in years for the celestial coordinate system in which
+    * positions given in either the header or data are expressed.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public double getEquinox()
+  {
+    return myHeader.getDoubleValue("EQUINOX", -1.0);
+  }
+
+  /**
+    * Get the equinox in years for the celestial coordinate system in which
+    * positions given in either the header or data are expressed.
+    * @return	either <CODE>null</CODE> or a String object
+    * @deprecated	Replaced by getEquinox
+    * @see	#getEquinox()
+    */
+  public double getEpoch()
+  {
+    return myHeader.getDoubleValue("EPOCH", -1.0);
+  }
+
+  /**
+    * Return the name of the person who compiled the information in
+    * the data associated with this header.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public String getAuthor()
+  {
+    return getTrimmedString("AUTHOR");
+  }
+
+  /**
+    * Return the citation of a reference where the data associated with
+    * this header are published.
+    * @return	either <CODE>null</CODE> or a String object
+    */
+  public String getReference()
+  {
+    return getTrimmedString("REFERENC");
+  }
+
+  /**
+    * Return the minimum valid value in the array.
+    * @return	minimum value.
+    */
+  public double getMaximumValue()
+  {
+    return myHeader.getDoubleValue("DATAMAX");
+  }
+
+  /**
+    * Return the minimum valid value in the array.
+    * @return	minimum value.
+    */
+  public double getMinimumValue()
+  {
+    return myHeader.getDoubleValue("DATAMIN");
+  }
+}
diff --git a/nom/tam/fits/BinaryTable.java b/nom/tam/fits/BinaryTable.java
new file mode 100644
index 0000000..2450ec7
--- /dev/null
+++ b/nom/tam/fits/BinaryTable.java
@@ -0,0 +1,728 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import java.io.*;
+import nom.tam.util.*;
+import java.lang.reflect.Array;
+import java.util.Vector;
+
+/** This class defines the methods for accessing FITS binary table data.
+  */
+
+public class BinaryTable extends Data {
+
+    /** This is the area in which variable length column data lives.
+      */
+    byte[] hashArea = new byte[0];
+
+    /** Pointer to the next available byte in the hashArea.
+      */
+    int hashPtr = 0;
+
+    /** The columns of data in the binary table.
+      */
+    Vector columns;
+
+    /** The sizes of each column (in number of entries per row)
+      */
+    int[] sizes;
+
+    /** The dimensions of each column.
+      */
+    int[][] dimens;
+
+    /** An example of the structure of a row
+      */
+    Object[] modelRow;
+
+    /** Where the data is actually stored.
+      */
+    ColumnTable table;
+
+    /** Create a null binary table data segment.
+      */
+    public BinaryTable() throws FitsException {
+         columns = new Vector();
+         try {
+             table = new ColumnTable(new Object[0], new int[0]);
+         } catch (TableException e) {
+             throw new FitsException("Unable to create table:"+e);
+         }
+         dataArray = table;
+         dimens = new int[0][];
+         sizes = new int[0];
+         modelRow = new Object[0];
+    }
+
+    /** Create a binary table from given header information.
+      *
+      * @param header	A header describing what the binary
+      *                 table should look like.
+      */
+    public BinaryTable(Header myHeader) throws FitsException {
+
+      BinaryTableHeaderParser parser = new BinaryTableHeaderParser(myHeader);
+
+      modelRow     = parser.getModelRow();
+      int naxis2   = myHeader.getIntValue("NAXIS2");
+      int nfields  = myHeader.getIntValue("TFIELDS");
+
+      columns = new Vector(nfields);
+      sizes   = new int[nfields];
+      dimens  = new int[nfields][];
+
+      modelRow = parser.getModelRow();
+
+      int size = useModelRow(modelRow, naxis2);
+
+      Object[] arrCol= new Object[nfields];
+      columns.copyInto(arrCol);
+
+      try {
+          table = new ColumnTable(arrCol, sizes);
+      } catch (TableException e) {
+          throw new FitsException("Unable to create table:"+e);
+      }
+
+
+      int hsize = myHeader.trueDataSize();
+
+      // If the computed size of the data array is less than the size that
+      // the header indicates for the table, then there must be a hash
+      // area for variable length data.
+
+      if (size < hsize) {
+          hashArea = new byte[hsize-size];
+          hashPtr  = hashArea.length;
+
+     } else if (size > hsize) {
+
+          // This implies an error in the user's value of NAXIS1 (or a bug!)
+          throw new FitsException("Size inconsistency in header and data: Check NAXIS1 and PCOUNT");
+     }
+    }
+
+    /** Use an example row to determine what the table
+      * should look like.
+      * @param model An example of a row.  Each element of model should
+      *              have the structure of the corresponding element of the row.
+      * @param nrow  The number of rows in the table.
+      * @return The size of the table in bytes.
+      */
+    protected int useModelRow(Object[] model, int nrow) {
+
+      int totalsize = 0;
+
+      for (int col=0; col < model.length; col += 1) {
+
+           Class base = ArrayFuncs.getBaseClass(model[col]);
+           dimens[col] = ArrayFuncs.getDimensions(model[col]);
+
+           int size = 1;
+           for (int dim=0; dim < dimens[col].length; dim += 1) {
+               size *= dimens[col][dim];
+           }
+           sizes[col] = size;
+
+
+           Object array = Array.newInstance(base, size*nrow);
+           columns.addElement(array);
+
+           totalsize += size*nrow*ArrayFuncs.getBaseLength(model[col]);
+       }
+       return totalsize;
+    }
+
+    /** Create a binary table from existing data in row order.
+      *
+      * @param data The data used to initialize the binary table.
+      */
+    public BinaryTable(Object[][] data) throws FitsException {
+
+        modelRow = data[0];
+
+        dimens = new int[modelRow.length][];
+        sizes = new int[modelRow.length];
+        columns = new Vector(modelRow.length);
+
+        useModelRow(modelRow, data.length);
+        hashPtr = 0;
+
+        // We've set up the structure but now we need to go through and
+        // move information from data to the arrays in
+        // column.
+        rowToColumn(data);
+        Object[] ocols = new Object[columns.size()];
+        columns.copyInto(ocols);
+        try {
+            table = new ColumnTable(ocols, sizes);
+        } catch (TableException e) {
+            throw new FitsException("Error creating ColumnTable");
+        }
+    }
+
+    /** Convert the data from a row to a flattened column format.
+      * @param data The table data in row/column format.
+      */
+    private void rowToColumn(Object[][] data) {
+
+        for (int col=0; col<modelRow.length; col += 1) {
+
+            Object column = columns.elementAt(col);
+            for (int row=0; row<data.length; row += 1) {
+                System.arraycopy(
+                   ArrayFuncs.flatten(data[row][col]), 0,
+                   column, row*sizes[col], sizes[col]
+                );
+            }
+        }
+    }
+
+    /** Get a given row
+      * @param row The index of the row to be returned.
+      * @return A row of data.
+      */
+    public Object[] getRow(int row) throws FitsException {
+
+        if (!validRow(row)) {
+            throw new FitsException("Invalid row");
+        }
+
+        Object[] data = new Object[modelRow.length];
+        for (int col=0; col<modelRow.length; col += 1) {
+             data[col] = ArrayFuncs.curl(table.getElement(row,col), dimens[col]);
+        }
+        return data;
+    }
+
+    /** Replace a row in the table.
+      * @param row  The index of the row to be replaced.
+      * @param data The new values for the row.
+      * @exception FitsException Thrown if the new row cannot
+      *                          match the existing data.
+      */
+    public void setRow(int row, Object data[]) throws FitsException {
+
+
+         if (data.length != getNcol()) {
+             throw new FitsException("Updated row size does not agree with table");
+         }
+
+         Object[] ydata = new Object[data.length];
+
+         for (int col=0; col<data.length; col += 1) {
+              ydata[col] = ArrayFuncs.flatten(data[col]);
+         }
+         try {
+             table.setRow(row, ydata);
+         } catch (TableException e) {
+             throw new FitsException("Error modifying table: "+e);
+         }
+     }
+
+     /** Replace a column in the table.
+       * @param col The index of the column to be replaced.
+       * @param xcol The new data for the column
+       * @exception FitsException Thrown if the data does not match
+       *                          the current column description.
+       */
+     public void setColumn(int col, Object[] xcol) throws FitsException {
+
+
+         int nrow = xcol.length;
+         if (nrow != getNrow()) {
+             throw new FitsException("Replacement column had wrong number of rows");
+         }
+
+         int[] dims = ArrayFuncs.getDimensions(xcol[0]);
+         int size = 1;
+         for (int dim=0; dim<dims.length; dim += 1) {
+             size *= dims[dim];
+         }
+
+         if (size != sizes[col]) {
+             throw new FitsException("Replacement column has size mismatch");
+         }
+
+         Object x = columns.elementAt(col);
+         if (ArrayFuncs.getBaseClass(xcol) != ArrayFuncs.getBaseClass(x)) {
+             throw new FitsException("Replactment column has type mismatch");
+         }
+
+         for (int row=0; row < nrow;  row += 1) {
+
+             System.arraycopy(ArrayFuncs.flatten(xcol[row]), 0,
+                              x, row*size, size);
+         }
+
+     }
+
+
+     /** Set a column with the data already flattened.
+       *
+       * @param col  The index of the column to be replaced.
+       * @param data The new data array.  This should be a one-d
+       *             primitive array.
+       * @exception FitsException Thrown if the type of length of
+       *                         the replacement data differs from the
+       *                         original.
+       */
+      public void setFlattenedColumn (int col, Object data) throws FitsException {
+
+          Object x = columns.elementAt(col);
+          if ((x.getClass() != data.getClass()) || (Array.getLength(x) != Array.getLength(data))) {
+              throw new FitsException("Replacement column mismatch");
+          }
+
+          // Copy the new data into the array.
+          System.arraycopy(data, 0, x, 0, sizes[col]);
+      }
+
+
+
+
+    /** Get a given column
+      * @param col The index of the column.
+      */
+    public Object[] getColumn(int col) throws FitsException {
+
+         if (!validColumn(col)) {
+             throw new FitsException("Invalid column");
+         }
+
+         int[] dims = new int[dimens[col].length+1];
+         System.arraycopy(dimens[col], 0, dims, 0, dimens[col].length);
+         dims[dimens[col].length] = getNrow();
+         return (Object[]) ArrayFuncs.curl(columns.elementAt(col),dims);
+
+     }
+
+     /** Get a column in flattened format.
+       * For large tables getting a column in standard format can be
+       * inefficient because a separate object is needed for
+       * each row.  Leaving the data in flattened format means
+       * that only a single object is created.
+       * @param col
+       */
+
+     public Object getFlattenedColumn(int col) throws FitsException {
+         if (!validColumn(col) ) {
+             throw new FitsException("Invalid column");
+         }
+
+         return columns.elementAt(col);
+     }
+
+     /** Get a particular element from the table.
+       * @param i The row of the element.
+       * @param j The column of the element.
+       */
+     public Object getElement(int i, int j) throws FitsException {
+
+         if (!validRow(i) || !validColumn(j)) {
+             throw new FitsException("No such element");
+         }
+         return table.getElement(i,j);
+     }
+
+     /** Add a row at the end of the table.
+       * @param o An array of objects instantiating the data.  These
+       *          should have the same structure as any existing rows.
+       */
+     public void addRow(Object[] o) throws FitsException {
+
+         Vector newColumns = new Vector(columns.size());
+         for (int col=0; col<columns.size(); col += 1) {
+             Object oldArray = columns.elementAt(col);
+             int olen = Array.getLength(oldArray);
+             Class obase = ArrayFuncs.getBaseClass(oldArray);
+             Object newArray = Array.newInstance(obase,olen+sizes[col]);
+             System.arraycopy(oldArray, 0, newArray, 0, olen);
+             System.arraycopy(
+                ArrayFuncs.flatten(o[col]), 0, newArray, olen, sizes[col]
+             );
+
+         }
+
+         columns = newColumns;
+         Object[] arrCol = new Object[columns.size()];
+         columns.copyInto(arrCol);
+         try {
+             table = new ColumnTable(arrCol, sizes);
+         } catch (TableException e) {
+             throw new FitsException("Unable to modify table:"+e);
+         }
+
+     }
+
+     /** Add a column to the end of a table.
+       * @param o An array of identically structured objects with the
+       *          same number of elements as other columns in the table.
+       */
+     public void addColumn(Object[] o) throws FitsException {
+
+         int[] dims = ArrayFuncs.getDimensions(o[0]);
+         addFlattenedColumn(ArrayFuncs.flatten(o), dims);
+
+     }
+
+     /** Add a column where the data is already flattened.
+       * @param o      The new column data.  This should be a one-dimensional
+       *               primitive array.
+       * @param dimens The dimensions of one row of the column.
+       */
+
+
+
+     public void addFlattenedColumn(Object o, int[] dims)  throws FitsException {
+
+         int[] newsizes = new int[sizes.length + 1];
+         int[][] newdimens = new int[dimens.length + 1][];
+         Object[] newmodel = new Object[modelRow.length + 1];
+
+         int size = 1;
+         for (int dim=0; dim < dims.length; dim += 1) {
+             size *= dims[dim];
+         }
+
+         for (int col=0; col< sizes.length; col += 1) {
+              newsizes[col] = sizes[col];
+              newdimens[col] = dimens[col];
+              newmodel[col] = modelRow[col];
+         }
+
+
+         sizes    = newsizes;
+         dimens   = newdimens;
+         modelRow = newmodel;
+
+         sizes[sizes.length-1]    = size;
+         dimens[sizes.length-1]   = dims;
+         modelRow[sizes.length-1] = Array.newInstance(ArrayFuncs.getBaseClass(o), dims);
+
+         columns.addElement(o);
+         Object[] arrCol = new Object[columns.size()];
+         columns.copyInto(arrCol);
+
+         try {
+             table = new ColumnTable(arrCol, sizes);
+         } catch (TableException e) {
+              throw new FitsException("Unable to modify table:"+e);
+         }
+         dataArray  = table;
+
+     }
+
+     /** Get the number of rows in the table
+       */
+     public int getNrow() {
+         return table.getNrow();
+     }
+
+     /** Get the number of columns in the table.
+       */
+     public int getNcol() {
+         return table.getNcol();
+     }
+
+     /** Check to see if this is a valid row.
+       * @param i The Java index (first=0) of the row to check.
+       */
+     protected boolean validRow(int i) {
+         if (getNrow() > 0 && i >= 0 && i <getNrow()) {
+             return true;
+         } else {
+             return false;
+         }
+     }
+
+     /** Check if the column number is valid.
+       *
+       * @param j The Java index (first=0) of the column to check.
+       */
+     protected boolean validColumn(int j) {
+         return (j >= 0 && j < getNcol());
+     }
+
+     /** Replace a single element within the table.
+       *
+       * @param i The row of the data.
+       * @param j The column of the data.
+       * @param o The replacement data.
+       */
+    public void setElement(int i, int j, Object o) throws FitsException{
+
+        try {
+            table.setElement(i, j, ArrayFuncs.flatten(o));
+        } catch (TableException e) {
+            throw new FitsException("Error modifying table:"+e);
+        }
+    }
+
+    /** This routine makes sure the hash area is large
+      * enough to fill a given request.  If not it reallocates
+      * the hash area, and copies the old data into the new
+      * area.
+      * @param need The number of bytes needed for the current
+      *             hash area request.
+      */
+    protected void expandHashArea(int need) {
+
+        if (hashPtr+need > hashArea.length) {
+            int newlen = (int)((hashPtr+need)*1.5);
+            if (newlen < 16384) {
+                newlen = 16384;
+            }
+            byte[] newHash = new byte[newlen];
+            System.arraycopy(hashArea, 0, newHash, 0, hashPtr);
+            hashArea = newHash;
+        }
+    }
+
+    /** Write the data including any hash data.
+      *
+      * @param o The output stream.
+      */
+    protected void writeTrueData(BufferedDataOutputStream o) throws FitsException {
+
+        try {
+            table.write(o);
+            o.write(hashArea, 0, hashPtr);
+        } catch (IOException e) {
+            throw new FitsException("Error writing binary table data:"+ e);
+        }
+    }
+
+    public void read(BufferedDataInputStream i) throws FitsException {
+
+
+    /** Read the data associated with the HDU including the hash area if present.
+      * @param i The input stream
+      */
+        readTrueData(i);
+    }
+
+    protected void readTrueData(BufferedDataInputStream i) throws FitsException {
+
+         int len;
+
+         try {
+             len = table.read(i);
+
+             if (hashArea.length > 0) {
+                 i.readPrimitiveArray(hashArea);
+             }
+             len += hashArea.length;
+
+
+             if (len %2880 != 0) {
+                 byte[] padding = new byte[2880-len%2880];
+                 i.readPrimitiveArray(padding);
+             }
+
+         } catch (IOException e) {
+             throw new FitsException("Error reading binary table data:"+e);
+         }
+    }
+
+    /** Get the size of the data in the HDU sans padding.
+      */
+    public int getTrueSize() {
+        return super.getTrueSize() + hashPtr;
+    }
+
+
+    /** Add a variable length column to the data.
+      *
+      * @param data    The data comprising the variable length column.
+      *                This should be a two dimensional primitive array
+      *                (3D for complex data).
+      *                Note that it is declared as a one dimensional object
+      *                array to make access convenient.  Any 2-d array can
+      *                be cast to a 1-d array of objects.
+      * @return        A column describing the variable format data.
+      *                This column can then be added to the table using
+      *                other functions.
+      */
+
+    public Column addVarData(Object[] data) throws FitsException {
+
+        int size = ArrayFuncs.computeSize(data);
+        int baseLength = ArrayFuncs.getBaseLength(data);
+
+        // Check if the data is complex.  If so then the third dimension
+        // should be two.
+        if (data instanceof Object[][]) {
+            baseLength *= 2;
+        }
+
+        int offset = hashPtr;
+        expandHashArea(size);
+        ByteArrayOutputStream bo = new ByteArrayOutputStream(size);
+
+        try {
+             BufferedDataOutputStream o = new BufferedDataOutputStream(bo);
+             o.writePrimitiveArray(data);
+             o.flush();
+             o.close();
+        } catch (IOException e) {
+             throw new FitsException("Unable to write variable column length data");
+        }
+
+        System.arraycopy(bo.toByteArray(), 0, hashArea, hashPtr, size);
+        hashPtr += size;
+
+        int nrow = data.length;
+        int[][] pointers = new int[nrow][2];
+        int myMax = 0;
+
+        for (int i=0; i<nrow; i += 1) {
+             int rowLength = Array.getLength(data[i]);
+             pointers[i][0] = rowLength;
+             pointers[i][1] = offset;
+             offset += rowLength*baseLength;
+             if (rowLength > myMax) {
+                 myMax = rowLength;
+             }
+        }
+
+        Column newColumn = new Column();
+        newColumn.setData(pointers);
+        return newColumn;
+
+    }
+
+    /** Get the data from a variable length column as a two-d primitive array.
+      *
+      * @param col       The  index of the column to be returned.
+      * @param newArray  The array to be filled with variable length data.
+      *                  This is passed to the BinaryTable class rather than
+      *                  created here so that we can handle complex data properly.
+      *                  This will be a two or three dimensional array where the
+      *                  first dimension is the number of rows in the table.
+      * @param baseClass The base class of the array.  It should be one
+      *               of the primitive types, e.g, Integer.TYPE.
+      */
+
+    public Object getVarData(int col, Class baseClass, boolean complex) throws FitsException {
+
+        if (col < 0 || col >= getNcol()) {
+             throw new FitsException("Invalid column specified for variable length extraction");
+        }
+
+        int[] dims;
+
+        if (complex) {
+            dims = new int[3];
+            dims[2] = 0;
+        } else {
+            dims = new int[2];
+        }
+
+        dims[0] = getNrow();
+        dims[1] = 0;
+
+        Object[] newArray = (Object[])Array.newInstance(baseClass, dims);
+        int baseLength = ArrayFuncs.getBaseLength(newArray);
+        if (complex) {
+            baseLength *= 2;
+        }
+
+        int offset = 0;
+        BufferedDataInputStream inp = new BufferedDataInputStream(
+                                          new ByteArrayInputStream(hashArea));
+
+        int[] ptrs = (int[])table.getColumn(col);
+
+        for (int i=0; i<getNrow(); i += 1) {
+
+             if (ptrs[2*i+1] < offset) {
+                 inp = new BufferedDataInputStream(new ByteArrayInputStream(hashArea));
+                 offset = 0;
+             }
+             try {
+
+                 inp.skipBytes(ptrs[2*i+1]-offset);
+                 int[] xdims;
+                 if (complex) {
+                     xdims = new int[2];
+                     xdims[0] = ptrs[2*i];
+                     xdims[1] = 2;
+                 } else {
+                     xdims = new int[1];
+                     xdims[0] = ptrs[2*i];
+                 }
+                 newArray[i] = Array.newInstance(baseClass,xdims);
+                 inp.readPrimitiveArray(newArray[i]);
+
+                 offset += baseLength * ptrs[2*i];
+
+             } catch (IOException e) {
+                 throw new FitsException("Error decoding hash area at offset="+offset+
+                    ".  Exception: Exception "+e);
+             }
+        }
+        return newArray;
+    }
+
+    public void write(BufferedDataOutputStream os) throws FitsException {
+      int len;
+      try {
+
+        // First write the table.
+        len = table.write(os);
+
+        // Now any variable length data.
+        if (hashPtr> 0) {
+            os.write(hashArea);
+
+        }
+        len += hashPtr;
+
+        // Check and see if any padding needs to be appended.
+        if (len%2880  != 0) {
+            byte[] pad = new byte[2880 - len%2880];
+            for (int i=0; i<pad.length; i += 1) {
+                pad[i] = 0;
+            }
+            os.write(pad);
+        }
+      } catch (IOException e) {
+          throw new FitsException("Unable to write table:"+e);
+      }
+
+
+    }
+
+    public int getHeapSize() {
+        return hashPtr;
+    }
+
+    public int[][] getDimens() {
+        return dimens;
+    }
+
+
+    public Class[] getBases() {
+        return table.getBases();
+    }
+
+    public char[] getTypes() {
+        return table.getTypes();
+    }
+
+    public int[] getSizes() {
+        return sizes;
+    }
+
+
+}
diff --git a/nom/tam/fits/BinaryTableHDU.java b/nom/tam/fits/BinaryTableHDU.java
new file mode 100644
index 0000000..2795e79
--- /dev/null
+++ b/nom/tam/fits/BinaryTableHDU.java
@@ -0,0 +1,520 @@
+package nom.tam.fits;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  *
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+
+import nom.tam.util.ArrayFuncs;
+import nom.tam.util.BufferedDataInputStream;
+
+/** FITS binary table header/data unit */
+public class BinaryTableHDU
+      extends TableHDU
+{
+  /** Create a binary table header/data unit.
+    * @param header the template specifying the binary table.
+    * @exception FitsException if there was a problem with the header.
+    */
+
+  public BinaryTableHDU(Header header)
+	throws FitsException
+  {
+    super(header);
+    if (!isHeader()) {
+      throw new BadHeaderException("Not a valid binary table header");
+    }
+  }
+
+  /** Build a binary table HDU from the supplied data.
+    * @param table the array used to build the binary table.
+    * @exception FitsException if there was a problem with the data.
+    */
+  public BinaryTableHDU(Object[][] table)
+	throws FitsException
+  {
+    super(null);
+
+
+    if (table == null) {
+      myData = new BinaryTable();
+    } else {
+      myData = new BinaryTable(table);
+    }
+    myHeader = BinaryTableHeaderParser.pointToTable((BinaryTable)myData);
+
+    setColumnStrings();
+
+  }
+
+  /** Build an empty binary table HDU.
+    * @exception FitsException if there was a problem building the empty HDU.
+    */
+  public BinaryTableHDU()
+	throws FitsException
+  {
+    this((Object[][] )null);
+  }
+
+  /** Check that this is a valid binary table header.
+    * @param header to validate.
+    * @return <CODE>true</CODE> if this is a binary table header.
+    */
+  public static boolean isHeader(Header header)
+  {
+    String card0 = header.getCard(0);
+
+    // Note that characters after the first 8 aren't significant.
+    // BINTABLE may be followed by one or more blanks.
+    return (card0 != null && card0.startsWith("XTENSION= 'BINTABLE'"));
+  }
+
+  /** Check that this HDU has a valid header.
+    * @return <CODE>true</CODE> if this HDU has a valid header.
+    */
+  public boolean isHeader()
+  {
+    return isHeader(myHeader);
+  }
+
+  /** Set the default base keys which are expected to be associated
+    * with a binary table.
+    * The user can add to these by calling addColumnString directly.
+    */
+  protected void setColumnStrings() {
+    addColumnString("TTYPE");
+    addColumnString("TFORM");
+    addColumnString("TDIM");
+    addColumnString("TSCAL");
+    addColumnString("TZERO");
+  }
+
+  /** Add a column without any associated header information.
+    *
+    * @param data The column data to be added.  Data should be an Object[] where
+    *             type of all of the constituents is identical.  The length
+    *             of data should match the other columns.  <b> Note:</b> It is
+    *             valid for data to be a 2 or higher dimensionality primitive
+    *             array.  In this case the column index is the first (in Java speak)
+    *             index of the array.  E.g., if called with int[30][20][10], the
+    *             number of rows in the table should be 30 and this column
+    *             will have elements which are 2-d integer arrays with TDIM = (10,20).
+    * @exception FitsException the column could not be added.
+    */
+  public void addColumn(Object[] data)
+	throws FitsException
+  {
+    BinaryTable myData = (BinaryTable) this.myData;
+    myData.addColumn(data);
+
+    // Make sure we can point to an appropriate place in the Header.
+
+    int ncol = myData.getNcol();
+
+    if (ncol > 1) {
+        int lastMark = -2;
+        for (int j=0; j<columnStrings.size(); j += 1) {
+            String key = (String)columnStrings.elementAt(j) + (ncol-1);
+            myHeader.findKey(key);
+            if (myHeader.getMark() > lastMark) {
+               lastMark = myHeader.getMark();
+            }
+        }
+        myHeader.setMark(lastMark);
+    } else {
+        myHeader.findKey("TFIELDS");
+        int lastMark=myHeader.getMark();
+        int j=1;
+
+        while (true) {
+             String card = myHeader.getCard(lastMark+j);
+
+             if (card == null) {
+                 myHeader.unsetMark();
+                 break;
+             } else if ( !(card.substring(0,8).equals("COMMENT ") ||
+                           card.substring(0,8).equals("        "))) {
+                 myHeader.setMark(lastMark+j);
+                 break;
+             }
+             j += 1;
+        }
+    }
+
+
+    BinaryTableHeaderParser.addColumn(ncol-1, data, myHeader);
+  }
+
+  /** Find the column which has the given name (i.e., TTYPE)
+    * @param name The desired name.
+    * @return The Fits index of the column (first column = 1);
+    */
+  public int findColumn(String name)
+  {
+    for (int i=1; i <= myHeader.getIntValue("TFIELDS", 0); i += 1) {
+      String tform = myHeader.getStringValue("TTYPE"+i);
+      if (tform != null && tform.equals(name)) {
+	return i-1;
+      }
+    }
+
+    return -1;
+  }
+
+
+  /** Get the header and data information for a given column.
+    * @param name The name (TTYPE) of  column desired.
+    * @return A Column object with the desired information or
+    *         null if the column could not be found.
+    * @exception FitsException if <CODE>colNumber</CODE> could not be deleted.
+    */
+  public Column getColumn(String name)
+	throws FitsException
+  {
+    int col = findColumn(name);
+    if (col < 0) {
+      return null;
+    }
+
+    return getColumn(col);
+  }
+
+  /** Get the header and data associated with the given column.
+    * @param colNumber The Fits (first=1) index of the desired column.
+    * @return The associated information.
+    * @exception FitsException if <CODE>colNumber</CODE> could not be found.
+    */
+  public Column getColumn(int colNumber)
+	throws FitsException
+  {
+
+    Column thisCol = new Column();
+    Object[] col = (Object[])((BinaryTable)myData).getColumn(colNumber);
+    thisCol.setData(col);
+
+    for (int i=0; i<columnStrings.size(); i += 1) {
+      String card = myHeader.findKey((String)columnStrings.elementAt(i)+colNumber);
+      if (card != null) {
+	thisCol.addKey(card);
+      }
+    }
+    return thisCol;
+  }
+
+
+  /** Add a column to tabular data.
+    * @param col The column to be added.  It should have the same
+    *          dimension as all of the other columns.
+    * @exception FitsException if <CODE>col</CODE> could not be added.
+    */
+  public void addColumn(Column col)
+	throws FitsException
+  {
+    BinaryTable myData = (BinaryTable) this.myData;
+
+    addColumn(col.getData());
+    int ncol = myData.getNcol();
+
+    String[] keys = col.getKeys(ncol);
+
+    // Now add the pointers that were stored in this column.
+    // Set the mark to the TFORMn keyword before we start.
+
+    // Note that this will override the TFORM value for
+    // the variable length column we had.
+
+    myHeader.deleteKey("TDIMS"+ncol);
+    myHeader.getStringValue("TFORM"+ncol);
+
+    for (int i=0; i<keys.length; i += 1) {
+      if (keys[i].substring(0,5).equals("TFORM") ) {
+          HeaderCard card = new HeaderCard(keys[i]);
+          myHeader.addStringValue(card.getKey(), card.getValue(), card.getComment());
+      } else {
+          myHeader.addLine(keys[i]);
+      }
+    }
+  }
+
+  /** Create a variable column from the supplied data.
+    * @param data The column of data to be added.  It should have the same
+    *          dimension as all of the other columns.
+    * @exception FitsException if column could not be added.
+    */
+  public Column makeVarColumn(Object [] data)
+	throws FitsException
+  {
+    return makeVarColumn(data, null, null);
+  }
+
+  /** Create a variable column from the supplied data.
+    * @param data The column of data to be added.  It should have the same
+    *          dimension as all of the other columns.
+    * @param type The Fits type for this column (S, L, B, etc.)
+    * @exception FitsException if column could not be added.
+    */
+  public Column makeVarColumn(Object[] data, String type)
+	throws FitsException
+  {
+    return makeVarColumn(data, type, null);
+  }
+
+  /** Create a variable column from the supplied data.
+    * @param data The column of data to be added.  It should have the same
+    *          dimension as all of the other columns.
+    * @param type The Fits type for this column (S, L, B, etc.)
+    * @param keys The list of keys for this column (may be null).
+    * @exception FitsException if column could not be added.
+    */
+  public Column makeVarColumn(Object[] data, String type, String [] keys)
+	throws FitsException
+  {
+    Class baseClass = ArrayFuncs.getBaseClass(data);
+    char classChar;
+
+    // Byte type data can have several kinds of data encoded.
+    if (baseClass == Byte.TYPE) {
+      if (type != null && type.equals("S")) {
+	classChar = 'S';
+      } else if (type != null && type.equals("L")) {
+	classChar = 'L';
+      } else {
+	classChar = 'B';
+      }
+    } else if (baseClass == Integer.TYPE) {
+      classChar = 'J';
+    } else if (baseClass == Short.TYPE) {
+      classChar = 'I';
+    } else if (baseClass == Float.TYPE) {
+      if (type != null && type.equals("C")) {
+	classChar = 'C';
+      } else {
+	classChar = 'E';
+      }
+    } else if (baseClass == Double.TYPE) {
+      if (type != null && type.equals("M")) {
+	classChar = 'M';
+      } else {
+	classChar = 'D';
+      }
+    } else {
+      throw new FitsException("Invalid Base class for variable column");
+    }
+
+    Column varColumn = ((BinaryTable)myData).addVarData(data);
+    varColumn.addKey(myHeader.formatFields(
+      "TFORM", "'1P"+classChar+"     '", "VariableLength Column"));
+
+
+    // Adjust the PCOUNT variable to indicate the existence of a heap.
+    myHeader.addIntValue("PCOUNT", ((BinaryTable)myData).getHeapSize(), "Size of Heap Area");
+
+    if (keys != null) {
+      for (int i=0; i<keys.length; i += 1) {
+        varColumn.addKey(keys[i]);
+      }
+    }
+
+    return varColumn;
+  }
+
+  /** Return a variable column.
+    * @param name The name of the column to fetch.
+    * @return either null if <CODE>name</CODE> was not found, or an
+    *		array of data (as an Object).
+    * @exception FitsException if the column could not be found or returned.
+    */
+  public Object getVarData(String name)
+	throws FitsException
+  {
+    int colNum = findColumn(name);
+    if (colNum < 0) {
+      return null;
+    }
+
+    return getVarData(colNum);
+  }
+
+  /** Return a variable column.
+    * @param col The column number to fetch.
+    * @return an array of data (as an Object).
+    * @exception FitsException if <CODE>col</CODE> was not a valid column
+    *				number, was not a variable column, or had
+    *				an invalid Fits type.
+    */
+  public Object getVarData(int col)
+	throws FitsException
+  {
+    String tform = myHeader.getStringValue("TFORM"+(col+1));
+    if (tform == null)  {
+      throw new FitsException("TFORM not found for column(0 indexed):"+col);
+    }
+
+    char  typeChar;
+    Class baseClass;
+
+    if (tform.substring(0,2).equals("1P") ) {
+      typeChar = tform.charAt(2);
+    } else if (tform.charAt(0) == 'P') {
+      typeChar = tform.charAt(1);
+    } else {
+      throw new FitsException("Requested column does not seem to be variable: TFORM="+tform);
+    }
+
+    boolean complex = false;
+    switch (typeChar) {
+    case 'L':
+    case 'B':
+    case 'S':
+      baseClass = Byte.TYPE;
+      break;
+    case 'I':
+      baseClass = Short.TYPE;
+      break;
+    case 'J':
+      baseClass = Integer.TYPE;
+      break;
+    case 'K':
+      baseClass = Long.TYPE;
+      break;
+    case 'E':
+      baseClass = Float.TYPE;
+      break;
+    case 'D':
+      baseClass = Double.TYPE;
+      break;
+    case 'C':
+      complex = true;
+      baseClass = Float.TYPE;
+      break;
+    case 'M':
+      complex = true;
+      baseClass = Double.TYPE;
+      break;
+    default:
+      throw new FitsException("Unable to understand variable column format:"+tform);
+    }
+
+    return ((BinaryTable)myData).getVarData(col, baseClass, complex);
+  }
+
+  /** Create a Data object to correspond to the header description.
+    * @return An unfilled Data object which can be used to read
+    *         in the data for this HDU.
+    * @exception FitsException if the binary table could not be created.
+    */
+  public Data manufactureData()
+	throws FitsException
+  {
+    setColumnStrings();
+    return new BinaryTable(myHeader);
+  }
+
+  /** Get the number of columns for this table
+    * @return The number of columns in the table.
+    */
+  public int getNumColumns()
+  {
+    return myHeader.getIntValue("TFIELDS", 0);
+  }
+
+  /** Get the number of rows for this table
+    * @return The number of rows in the table.
+    */
+  public int getNumRows()
+  {
+    return myHeader.getIntValue("NAXIS2", 0);
+  }
+
+  /** Get the name of a column in the table.
+    * @return The column name.
+    * @exception FitsException if an invalid index was requested.
+    */
+  public String getColumnName(int index)
+	throws FitsException
+  {
+    int flds = myHeader.getIntValue("TFIELDS", 0);
+    if (index < 0 || index >= flds) {
+      throw new FitsException("Bad column index " + index + " (only " + flds +
+			      " columns)");
+    }
+
+    return getTrimmedString("TTYPE" + (index + 1));
+  }
+
+  /** Get the FITS type of a column in the table.
+    * @return The FITS type.
+    * @exception FitsException if an invalid index was requested.
+    */
+  public String getColumnFITSType(int index)
+	throws FitsException
+  {
+    int flds = myHeader.getIntValue("TFIELDS", 0);
+    if (index < 0 || index >= flds) {
+      throw new FitsException("Bad column index " + index + " (only " + flds +
+			      " columns)");
+    }
+
+    return getTrimmedString("TFORM" + (index + 1));
+  }
+
+  /** Print out some information about this HDU.
+    */
+  public void info() {
+
+    BinaryTable myData = (BinaryTable) this.myData;
+
+    System.out.println("  Binary Table");
+    System.out.println("      Header Information:");
+
+    int nhcol = myHeader.getIntValue("TFIELDS", -1);
+    int nrow = myHeader.getIntValue("NAXIS2", -1);
+    int rowsize = myHeader.getIntValue("NAXIS1", -1);
+    System.out.print("          "+nhcol+" fields");
+    System.out.println(", "+nrow+" rows of length "+rowsize);
+    for (int i=1; i <= nhcol; i += 1) {
+      System.out.print("           "+i+":");
+      checkField("TTYPE"+i);
+      checkField("TFORM"+i);
+      checkField("TDIM"+i);
+      System.out.println(" ");
+    }
+
+    System.out.println("      Data Information:");
+    if (myData == null ||
+        myData.getNrow() == 0 || myData.getNcol() == 0) {
+        System.out.println("         No data present");
+        if (myData.getHeapSize() > 0) {
+            System.out.println("         Heap size is: "+myData.getHeapSize()+" bytes");
+        }
+    } else {
+
+      System.out.println("         Number of rows="+myData.getNrow());
+      System.out.println("         Number of columns="+myData.getNcol());
+      if (myData.getHeapSize() > 0) {
+          System.out.println("         Heap size is: "+myData.getHeapSize()+" bytes");
+      }
+
+      int[][] dimens = myData.getDimens();
+      char[]  types = myData.getTypes();
+
+      for (int i=0; i<myData.getNcol(); i += 1) {
+	  System.out.print("         "+(i+1)+
+			   ":"+types[i] + " [");
+          char comma= ' ';
+          for (int dim=0; dim < dimens[i].length; dim += 1) {
+               System.out.print(""+comma+dimens[i][dim]);
+               comma = ',';
+          }
+          System.out.println(" ]");
+      }
+    }
+  }
+}
diff --git a/nom/tam/fits/BinaryTableHeaderParser.java b/nom/tam/fits/BinaryTableHeaderParser.java
new file mode 100644
index 0000000..f327ebf
--- /dev/null
+++ b/nom/tam/fits/BinaryTableHeaderParser.java
@@ -0,0 +1,455 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+*/
+
+import nom.tam.util.ArrayFuncs;
+
+/** This class defines the methods for accessing FITS binary table header
+  * information.
+  */
+
+public class BinaryTableHeaderParser {
+
+    Header	myHeader;
+
+    /** Parse an existing header.
+      * @param The existing header -- likely read from a file.
+      */
+    public BinaryTableHeaderParser(Header myHeader) throws FitsException {
+
+	int naxis1 = myHeader.getIntValue ("NAXIS1", 0);
+      checkLT0(naxis1, "NAXIS1 < 0 for binary table");
+
+	int naxis2 = myHeader.getIntValue ("NAXIS2", 0);
+      checkLT0(naxis2, "NAXIS2 < 0 for binary table");
+
+	int nfields =    myHeader.getIntValue("TFIELDS",0);
+      checkLT0(nfields, "NFIELDS < 0 for binary table");
+
+      this.myHeader = myHeader;
+    }
+
+    /** Create a model row for a binary table given a
+      * describing header.
+      * @return A model row for the table.
+      */
+    public Object[] getModelRow() throws FitsException {
+
+      int nfields = myHeader.getIntValue("TFIELDS");
+      Object[] row = new Object[nfields];
+
+	for (int i=0; i < nfields; i += 1) {
+	    Object column = getColumnDef(i+1);
+	    if (column == null) {
+		throw new FitsException("Invalid TFORM for column "+(i+1));
+	    } else {
+		row[i] = column;
+	    }
+	}
+      return row;
+    }
+
+
+
+    /** Check if a value is less than 0 and throw an error if so.
+      * @param i The value to be checked.
+      * @param errmst The message to be associated with the FitsException thrown.
+      */
+    protected void checkLT0(int i, String errmsg) throws FitsException {
+        if (i < 0) {
+            throw new FitsException(errmsg);
+        }
+    }
+
+    /** Get the format for a given column
+      * @param col      the column being examined.
+      * @return         an object of the type described in the column
+      *                 header info.  Note that only the TFORM and TDIM keywords
+      *                 are examined.
+      */
+    protected Object getColumnDef(int col) throws FitsException{
+
+	int i;
+	Class baseType;
+	int arrsiz;
+
+	String format = myHeader.getStringValue("TFORM"+col);
+
+	if (format == null) {
+	    throw new FitsException("No TFORM for column "+col);
+	}
+
+	// Skip initial white space
+	for (i=0; i<format.length(); i += 1) {
+	    if (!Character.isSpaceChar(format.charAt(i))){
+	        break;
+	    }
+	}
+	// Skip numbers
+	for ( ;i<format.length(); i += 1) {
+	    if (!Character.isDigit(format.charAt(i))) {
+		break;
+	    }
+	}
+
+      boolean complex = false;
+      boolean bitData = false;
+      boolean varData = false;
+	if (i >= format.length() ) {
+          throw new FitsException("Invalid TFORM value for column "+col);
+      }
+
+      if (i > 0) {
+	    arrsiz = Integer.parseInt(format.substring(0,i));
+	} else {
+          arrsiz = 1;
+	}
+
+	switch (format.charAt(i)){
+	     case 'X':
+		baseType = Byte.TYPE;
+		bitData = true;
+		break;
+	     case 'B':
+	     case 'A':
+	     case 'L':
+		baseType = Byte.TYPE;
+		break;
+	     case 'I':
+		baseType = Short.TYPE;
+		break;
+	     case 'J':
+		baseType = Integer.TYPE;
+		break;
+           case 'K':
+            baseType = Long.TYPE;
+            break;
+	     case 'E':
+		baseType = Float.TYPE;
+		break;
+	     case 'D':
+		baseType = Double.TYPE;
+		break;
+	     case 'C':
+		baseType = Float.TYPE;
+		complex = true;
+		break;
+	     case 'M':
+		baseType = Double.TYPE;
+		complex = true;
+		break;
+	     case 'P':
+		baseType = Integer.TYPE;
+            varData = true;
+     		if (arrsiz > 0) {
+		    arrsiz = 2;
+		} else {
+		    arrsiz = 0;
+		}
+            break;
+	     default:
+		throw new FitsException("Invalid TFORM code '"+format.charAt(i)+"' for column "+col);
+	}
+
+      String tdims = myHeader.getStringValue("TDIM"+col);
+      int[] dims;
+
+      if (tdims != null && !varData && !bitData) {
+          dims = getTDims(tdims, arrsiz);
+      } else {
+          if (bitData) arrsiz /= 8;
+          dims = new int[1];
+          dims[0] = arrsiz;
+      }
+
+      // Add in a dimension for complex data.
+      if (complex) {
+          int[] ndims = new int[dims.length+1];
+          ndims[0] = 2;
+          for (i=1; i<=dims.length; i += 1) {
+              ndims[i] = dims[i-1];
+          }
+          dims = ndims;
+      }
+
+      try {
+          return java.lang.reflect.Array.newInstance(baseType, dims);
+      } catch (IllegalArgumentException e) {
+          throw new FitsException("Invalid datatype");
+      } catch (NegativeArraySizeException e) {
+          throw new FitsException("Negative dimensions");
+      }
+    }
+
+    /** Parse the TDIMS value.
+      *
+      * If the TDIMS value cannot be deciphered a one-d
+      * array with the size given in arrsiz is returned.
+      *
+      * @param tdims   The value of the TDIMSn card.
+      * @param arrsiz  The size field found on the TFORMn card.
+      * @return        An int array of the desired dimensions.
+      *                Note that the order of the tdims is the inverser
+      *                of the order in the TDIMS key.
+      */
+    public static int[] getTDims(String tdims, int arrsiz) {
+
+        // The TDIMS value should be of the form: "(iiii,jjjj,kkk,...)"
+
+        int[] backup = {arrsiz};
+
+        // Count the commas in the tdims field.
+        int ncomma = 0;
+        for (int i=0; i<tdims.length(); i += 1) {
+             if (tdims.charAt(i) == ',') {
+                  ncomma += 1;
+             }
+        }
+
+        int[] dims = new int[ncomma+1];
+
+        int starter = tdims.indexOf('(') + 1;
+        if (starter < 0) {
+            return backup;
+        }
+
+        int ender;
+        for (int i=0; i < ncomma; i += 1) {
+            ender= tdims.indexOf(',', starter);
+            if (ender < 0) {
+                return backup;
+            }
+            dims[i] = Integer.parseInt(tdims.substring(starter,ender));
+            starter = ender + 1;
+        }
+
+        ender = tdims.indexOf(')', starter);
+        if (ender < 0) {
+            return backup;
+        }
+
+        dims[ncomma] = Integer.parseInt(tdims.substring(starter,ender));
+
+        // Now invert the order of the tdims.
+
+        int[] newdims = new int[dims.length];
+        for (int i=0; i<dims.length; i += 1) {
+            newdims[i] = dims[dims.length-i-1];
+        }
+
+        return newdims;
+    }
+
+     /** Make the header describe the a table where we give only.
+       * a single row of the table and the number of rows.
+       *
+       * @exception FitsException if the table was not valid.
+       */
+     public static Header pointToTable(BinaryTable table) throws FitsException {
+
+         if (table == null) {
+             throw new FitsException("Cannot create header for null table");
+         }
+
+         Header myHeader = new Header();
+         return pointToTable(table, myHeader);
+    }
+
+
+    /** Make the header describe a specified table and included
+      * existing header information.
+      * @param table    The binary table data.
+      * @param myHeader An existing header for this data.  It will be modified
+      *                 as needed, but excess keywords will not be pruned.
+      */
+    public static Header pointToTable(BinaryTable table, Header myHeader)
+                         throws FitsException {
+
+         myHeader.setXtension("BINTABLE");
+         myHeader.setBitpix(8);
+         myHeader.setNaxes(2);
+
+         myHeader.setNaxis(1, 0);     // This is just a place holder
+         myHeader.setNaxis(2, table.getNrow());
+
+         myHeader.setPcount(0);
+         myHeader.setGcount(1);
+
+         int[][] dimens = table.getDimens();
+         int[] sizes = table.getSizes();
+         char[] types = table.getTypes();
+
+         myHeader.addIntValue("TFIELDS", dimens.length, "Number of fields in table");
+
+         int mark = myHeader.getMark();
+
+         String card = myHeader.getCard(mark);
+
+         if (card != null && (card.substring(0,8).equals("COMMENT ") ||
+                              card.substring(0,8).equals("        ") )) {
+             while(card.substring(0,8).equals("COMMENT ") ||
+                   card.substring(0,8).equals("        ")) {
+                  mark += 1;
+                  card = myHeader.getCard(mark);
+             }
+         } else {
+
+	     myHeader.insertCommentStyle("","");
+             myHeader.insertComment("End of required structural keywords");
+             myHeader.insertCommentStyle("","");
+         }
+
+         int rowsize = 0;
+         for (int col=0; col < dimens.length; col += 1) {
+             pointToCol(myHeader, col, sizes[col], dimens[col], types[col]);
+             int colsiz = sizes[col];
+             if (types[col] == 'S') {
+                 colsiz *= 2;
+             } else if (types[col] == 'I' ||
+                types[col] == 'F') {
+                 colsiz *= 4;
+             } else if (types[col] == 'L'  ||
+                types[col] == 'D') {
+                 colsiz *= 8;
+             }
+             rowsize += colsiz;
+         }
+
+         // Overwrite previous values.
+         myHeader.addIntValue("NAXIS1", rowsize, "Number of bytes in row");
+         return myHeader;
+     }
+
+     /** Add a column to the header information.
+       *
+       * @param column   The column index for the new column.
+       * @param col      The column data.
+       * @param myHeader The existing header for the table.
+       */
+     public static void addColumn(int column, Object[] col, Header myHeader) throws FitsException {
+
+         int size;
+         int[] dimens = ArrayFuncs.getDimensions(col[0]);
+         if (dimens.length == 0) {
+             size = 1;
+             dimens = new int[1];
+             dimens[0] = 1;
+         } else {
+             size = 1;
+             for (int i=0; i<dimens.length; i += 1) {
+                 size *= dimens[i];
+             }
+         }
+
+         char type;
+         int bsize;
+         Class base = ArrayFuncs.getBaseClass(col[0]);
+         if (base == Boolean.TYPE) {
+             bsize = 1;
+             type = 'Z';
+         } else if (base == Byte.TYPE) {
+             bsize = 1;
+             type = 'B';
+         } else if (base == Short.TYPE || base == Character.TYPE) {
+             type = 'S';
+             bsize = 2;
+         } else if (base == Integer.TYPE) {
+             type = 'I';
+             bsize=4;
+         } else if (base == Long.TYPE) {
+             type = 'J';
+             bsize=8;
+         } else if (base == Float.TYPE) {
+             type = 'F';
+             bsize=4;
+         } else if (base == Double.TYPE) {
+             type = 'D';
+             bsize = 8;
+         } else {
+             throw new FitsException("Invalid Column type");
+         }
+
+         pointToCol(myHeader, column, size, dimens, type);
+         myHeader.addIntValue("TFIELDS", myHeader.getIntValue("TFIELDS")+1, "Number of columns");
+         myHeader.addIntValue("NAXIS1", myHeader.getIntValue("NAXIS1")+bsize*size, "Bytes per row");
+         if (column == 0) {
+             myHeader.addIntValue("NAXIS2", col.length, "Number of rows");
+         }
+    }
+
+
+
+    /** Add information in the header to describe a single column.
+      *
+      * @param myHeader The header to be updated.
+      * @param col      The column index.
+      * @param size     The number of elements in the column per row
+      * @param dimens   The dimensions of the column.
+      * @param type     A character indicating the type of data.
+      */
+    static void pointToCol(Header myHeader, int col, int size,
+                   int[] dimens, char type)
+                   throws FitsException {
+
+         char desc;
+
+         switch(type) {
+           case 'Z':
+               desc = 'L';
+               break;
+           case 'B':
+               desc = 'B';
+               break;
+           case 'I':
+               desc = 'J';
+               break;
+           case 'J':
+               desc = 'K';
+               break;
+           case 'S':
+               desc = 'I';
+               break;
+           case 'F':
+               desc = 'E';
+               break;
+           case 'D':
+               desc = 'D';
+               break;
+           default:
+               throw new FitsException("Invalid data type at column:"+col);
+         }
+
+         StringBuffer tdim = new StringBuffer("(");
+         for (int i=0; i < dimens.length; i += 1) {
+             int dim = dimens[dimens.length-i-1];
+             if (i > 0) {
+                 tdim.append(",");
+             }
+             tdim.append(dim);
+         }
+         tdim.append(")");
+
+         // Don't overwrite a variable length column...
+
+         if (size == 2 &&  desc == 'J') {
+             String colTform = myHeader.getStringValue("TFORM"+(col+1));
+             if (colTform != null &&
+                ( (colTform.length() > 1 && colTform.charAt(0) == 'P') ||
+                  (colTform.length() > 2 && colTform.substring(0,2).equals("1P") ) ) ){
+                 return;
+             }
+         }
+
+         myHeader.addStringValue("TFORM"+(col+1), ""+size+desc, null);
+         myHeader.addStringValue("TDIM"+(col+1),  new String(tdim), null);
+
+    }
+}
diff --git a/nom/tam/fits/Column.java b/nom/tam/fits/Column.java
new file mode 100644
index 0000000..498fa73
--- /dev/null
+++ b/nom/tam/fits/Column.java
@@ -0,0 +1,89 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+/** Keep header and data information for a column from a FITS table.
+  */
+public class Column {
+
+    private Object[] columnData=null;
+    private java.util.Vector headerKeys = new java.util.Vector(5);
+
+
+    /** Create an empty column */
+    public Column() {}
+
+    /** Initialize the data segment.  Each element of data should be an N-dimensional
+      * primitive array.
+      * @param data The column data.
+      */
+    public void setData(Object[] data) {
+         columnData = data;
+    }
+
+    /** Return the data.
+      */
+    public Object[] getData() {
+         return columnData;
+    }
+
+    /** Set the FITS keywords associated with this column.
+      * These may have just the base of the keyword (e.g, "TFORM"), or
+      * have a column number appended.  The correct column number
+      * will be put in by getKeys when the header information is retrieved.
+      */
+    public void setKeys(String[] keys) {
+        for (int i=0; i<keys.length; i += 1) {
+            addKey(keys[i]);
+        }
+    }
+
+    /** Get the keywords associated with the column.
+      *
+      * @param colNumber the FITS column number that will be associated
+      *                  with this column.
+      */
+    public String[] getKeys (int colNumber) {
+
+         if (headerKeys.size() <= 0) {
+             return null;
+         }
+         String[] keys = new String[headerKeys.size()];
+
+         for(int i=0; i<headerKeys.size(); i += 1) {
+              String card = (String) headerKeys.elementAt(i);
+              StringBuffer newKey = new StringBuffer();
+
+              for (int j=0; j<8; j += 1) {
+
+                  char c = card.charAt(j);
+                  if (!Character.isDigit(c) && c != ' ') {
+                      newKey.append(card.charAt(j));
+                  } else {
+                      break;
+                  }
+              }
+              newKey.append(colNumber);
+              newKey.append("        ");
+              keys[i] = newKey.toString().substring(0,8) + card.substring(8);
+         }
+
+         return keys;
+    }
+
+    /** Add a key to the keys associated with this column.
+      * @param key The new key.
+      */
+    public void addKey(String key) {
+         headerKeys.addElement(key);
+    }
+
+}
diff --git a/nom/tam/fits/Data.java b/nom/tam/fits/Data.java
new file mode 100644
index 0000000..4a93f3f
--- /dev/null
+++ b/nom/tam/fits/Data.java
@@ -0,0 +1,135 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import java.io.*;
+import nom.tam.util.*;
+
+/** This class provides methods to access the data segment of an
+  * HDU.
+  */
+
+public abstract class Data {
+
+    /** This is the object which contains the actual data for the HDU.
+      * <ul>
+      *  <li> For images and primary data this is a simple (but possibly
+      *       multi-dimensional) primitive array.  When group data is
+      *       supported it will be a possibly multidimensional array
+      *       of group objects.
+      *  <li> For ASCII data it is a two dimensional Object array where
+      *       each of the constituent objects is a primitive array of length 1.
+      *  <li> For Binary data it is a two dimensional Object array where
+      *       each of the constituent objects is a primitive array of arbitrary
+      *       (more or less) dimensionality.
+      *  </ul>
+      */
+    protected Object	dataArray;
+
+    /** Write the data -- including any buffering needed
+      * @param o  The output stream on which to write the data.
+      */
+    public void write(BufferedDataOutputStream o) throws FitsException {
+
+         this.writeTrueData(o);
+         byte[] padding = new byte[getPadding()];
+         try {
+             o.writePrimitiveArray(padding);
+         } catch (IOException e) {
+             throw new FitsException ("Error writing padding: "+e);
+         }
+
+    }
+
+    /** Read a data array into the current object and if needed position
+      * to the beginning of the next FITS block.
+      * @param i The input data stream
+      */
+    public void read(BufferedDataInputStream i) throws FitsException {
+
+         readTrueData(i);
+         int pad = getPadding();
+         try {
+             byte[] buf = new byte[pad];
+             while(pad > 0) {
+                 int len = i.read(buf, 0, pad);
+                 if (len == 0) {
+                     throw new FitsException("Data Padding EOF");
+                 }
+                 pad -= len;
+             }
+         //    i.skipBytes(getPadding());
+         } catch (EOFException e) {
+	   // ignore EOF messages while reading padded data
+         } catch (IOException e) {
+             throw new FitsException("Error skipping padding:"+e);
+         }
+
+    }
+
+    /** Write only the actual data.
+      * @param o  The output stream on which to write the data.
+      */
+    protected void writeTrueData(BufferedDataOutputStream o) throws FitsException {
+
+        try {
+            o.writePrimitiveArray(dataArray);
+        } catch (IOException e) {
+            throw new FitsException("FITS Output Error: "+e);
+        }
+    }
+
+    /** Read in the actual data portion.  This method needs to be
+      * overriden for ASCII tables and for binary tables with
+      * variable length data.
+      * @param i The input stream.
+      */
+    protected void readTrueData(BufferedDataInputStream i) throws FitsException {
+        try {
+            i.readPrimitiveArray(dataArray);
+        } catch (IOException e) {
+            throw new FitsException("FITS Input Error: "+e);
+        }
+    }
+
+    /** Get the amount of padding needed to fill in or skip to the beginning
+      * of the next FITS block.
+      */
+    public int getPadding() {
+
+        int len = getTrueSize() % 2880;
+
+        if (len == 0) {
+            return 0;
+        }
+
+        return 2880 - len;
+    }
+
+    /** Get the size of the actual data elements.
+      */
+    public int getTrueSize() {
+         int len = ArrayFuncs.computeSize(dataArray);
+         return len;
+    }
+
+    /** Get the size of the entire data area including any padding.
+      */
+    public int getPaddedSize() {
+         return getTrueSize() + getPadding();
+    }
+
+    /** Return the data array object.
+      */
+    public Object getData() {
+         return dataArray;
+    }
+
+}
diff --git a/nom/tam/fits/ExtensionHDU.java b/nom/tam/fits/ExtensionHDU.java
new file mode 100644
index 0000000..69b4d96
--- /dev/null
+++ b/nom/tam/fits/ExtensionHDU.java
@@ -0,0 +1,57 @@
+package nom.tam.fits;
+
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+
+/** Generic FITS extension methods */
+public abstract class ExtensionHDU
+	extends BasicHDU
+{
+  /** Create an HDU with the specified Header and an empty Data section
+    * @param header the Header
+    */
+  public ExtensionHDU(Header header)
+  {
+    super(header);
+  }
+
+  public String getExtensionType()
+	throws FitsException
+  {
+    String xStr = myHeader.getStringValue("XTENSION");
+    if (xStr == null) {
+      throw new FitsException("Missing EXTENDed FITS file type");
+    }
+
+    xStr = xStr.trim();
+    if (xStr.length() < 1) {
+      throw new FitsException("Empty EXTENDed FITS file type");
+    }
+
+    return xStr;
+  }
+
+  public String getExtensionName()
+  {
+    return getTrimmedString("EXTNAME");
+  }
+
+  public int getExtensionVersion()
+  {
+    return myHeader.getIntValue("EXTVER", 1);
+  }
+
+  public int getExtensionLevel()
+  {
+    return myHeader.getIntValue("EXTLEVEL", 1);
+  }
+}
diff --git a/nom/tam/fits/Fits.java b/nom/tam/fits/Fits.java
new file mode 100644
index 0000000..7e62d50
--- /dev/null
+++ b/nom/tam/fits/Fits.java
@@ -0,0 +1,650 @@
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+import nom.tam.util.*;
+
+
+abstract class FauxHDU
+	extends BasicHDU
+{
+  public FauxHDU() { super(null); }
+  public abstract void throwException() throws FitsException;
+  Data manufactureData() throws FitsException { throwException(); return null; }
+}
+
+class SkippedHDU
+	extends FauxHDU
+{
+  public SkippedHDU() { }
+  public void throwException()
+	throws FitsException
+  {
+    throw new FitsException("Skipped HDU");
+  }
+  public void info() { System.out.println("Skipped HDU"); }
+}
+
+class BadHDU
+	extends FauxHDU
+{
+  private FitsException exception;
+
+  public BadHDU(FitsException e) { exception = e; }
+  public void throwException()
+	throws FitsException
+  {
+    throw exception;
+  }
+  public void info() {
+    System.out.println("Bad HDU: " + exception.getMessage());
+  }
+}
+
+/** This class provides access to routines to allow users
+  * to read and write FITS files.
+  * <p>
+  * @version 0.6  March 22, 1998
+  *
+  * This version of the Java FITS library incorporates many changes
+  * made by Dave Glowacki who greatly enhanced the handling of
+  * header records and created the hierarchical organization of
+  * FITS HDU types.
+  * <p>
+  * <b> Description of the Package </b>
+  * <p>
+  * This FITS package attempts to make using FITS files easy,
+  * but does not do exhaustive error checking.  Users should
+  * not assume that just because a FITS file can be read
+  * and written that it is necessarily legal FITS.
+  *
+  *
+  * <ul>
+  * <li> The Fits class provides capabilities to
+  *      read and write data at the HDU level, and to
+  *      add and delete HDU's from the current  Fits object.
+  *      A large number of constructors are provided which
+  *      allow users to associate the Fits object with
+  *      some form of external data.  This external
+  *      data may be in a compressed format.
+  * <li> The HDU class is a factory class which is used to
+  *      create HDUs.  HDU's can be of a number of types
+  *      derived from the abstract class BasicHDU.
+  *      The hierarchy of HDUs is:
+  *      <ul>
+  *       <li>BasicHDU
+  *           <ul>
+  *           <li> PrimaryHDU
+  *           <li> RandomGroupsHDU
+  *           <li> ExtensionHDU
+  *                <ul>
+  *                <li> ImageHDU
+  *                <li> TableHDU
+  *                    <ul>
+  *                    <li> BinaryTableHDU
+  *                    <li> AsciiTableHDU (unimplemented)
+  *                    </ul>
+  *                </ul>
+  *           <ul>
+  *       </ul>
+  *
+  * <li> The Header class provides many functions to
+  *      add, delete and read header keywords in a variety
+  *      of formats.
+  * <li> The HeaderCard class provides access to the structure
+  *      of a FITS header card.
+  * <li> The Data class is an abstract class which provides
+  *      the basic methods for reading and writing FITS data.
+  *      Users will likely only be interested in the getData
+  *      method which returns that actual FITS data.
+  * <li> The BinaryTable class provides a large number of
+  *      methods to access and modify information in Binary
+  *      tables.  Modifications to columns are best done
+  *      done using the methods of HDU, but row manipulations
+  *      are reasonably done directly using the BinaryTable
+  *      class.  General users may find it convenient to
+  *      use the getElement, Row and Column methods.
+  * <li> The Column class
+  *      combines the Header information and Data corresponding to
+  *      a given column.
+  * </ul>
+  *
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  *
+  */
+public class Fits {
+
+    /** The input stream associated with this Fits object.
+      */
+    private BufferedDataInputStream dataStr;
+
+    /** A vector of HDUs that have been added to this
+      * Fits object.
+      */
+    private Vector HDUList = new Vector(10);
+
+    /** Has the input stream reached the EOF?
+      */
+    private boolean atEOF;
+
+    /** Indicate the version of these classes */
+    public static String version() {
+
+         // Version 0.1: Original test FITS classes -- 9/96
+         // Version 0.2: Pre-alpha release 10/97
+         //              Complete rewrite using BufferedData*** and
+         //              ArrayFuncs utilities.
+         // Version 0.3: Pre-alpha release  1/98
+         //              Incorporation of HDU hierarchy developed
+         //              by Dave Glowacki and various bug fixes.
+         // Version 0.4: Alpha-release 2/98
+         //              BinaryTable classes revised to use
+         //              ColumnTable classes.
+         // Version 0.5: Random Groups Data 3/98
+         // Version 0.6: Handling of bad/skipped FITS, FitsDate (D. Glowacki) 3/98
+
+         return "0.6";
+    }
+
+    /** Create an empty Fits object which is not
+      * associated with an input stream.
+      */
+    public Fits() {}
+
+    /** Create a Fits object associated with
+      * the given uncompressed data stream.
+      * @param str The data stream.
+      */
+    public Fits(InputStream str) throws FitsException {
+        this(str, false);
+    }
+
+    /** Create a Fits object associated with a possibly
+      * compressed data stream.
+      * @param str The data stream.
+      * @param compressed Is the stream compressed?
+      */
+    public Fits(InputStream str, boolean compressed)
+                throws FitsException {
+        streamInit(str, compressed);
+    }
+
+    /** Do the stream initialization.
+      *
+      * @param str The input stream.  The Fits class
+      *            uses a BufferedDataInputStream internally
+      *            and will use this instance if
+      *            it's already of that class.
+      * @param compressed Is this data compressed?  If so,
+      *            then the GZIPInputStream class will be
+      *            used to inflate it.
+      */
+    protected void streamInit(InputStream str, boolean compressed)
+                              throws FitsException {
+
+      if (str == null) {
+          throw new FitsException("Null input stream");
+      }
+
+      if (compressed) {
+          try {
+              str = new GZIPInputStream(str);
+          } catch (IOException e) {
+              throw new FitsException("Cannot inflate input stream"+e);
+          }
+      }
+
+      if (str instanceof BufferedDataInputStream) {
+          dataStr = (BufferedDataInputStream) str;
+      } else {
+          dataStr = new BufferedDataInputStream(str);
+      }
+    }
+
+    /** Associate the Fits object with a File
+      * @param myFile The File object.
+      * @param compressed Is the data compressed?
+      */
+    public Fits(File myFile, boolean compressed) throws FitsException {
+        fileInit(myFile, compressed);
+    }
+
+    /** Associate FITS object with an uncompressed File
+      * @param myFile The File object.
+      */
+    public Fits(File myFile) throws FitsException {
+        this(myFile, false);
+    }
+
+    /** Get a stream from the file and then use the stream initialization.
+      * @param myFile  The File to be associated.
+      * @param compressed Is the data compressed?
+      */
+    protected void fileInit(File myFile, boolean compressed) throws FitsException {
+
+        try {
+            FileInputStream str = new FileInputStream(myFile);
+            streamInit(str, compressed);
+        } catch (IOException e) {
+              throw new FitsException("Unable to create Input Stream from File: "+myFile);
+        }
+    }
+
+    private static boolean isCompressed(String filename)
+    {
+      int len = filename.length();
+      return (len > 2 && (filename.substring(len-3).equalsIgnoreCase(".gz")));
+    }
+
+    /** Associate the FITS object with a file or URL.
+      *
+      * The string is assumed to be a URL if it begins with
+      * http:  otherwise it is treated as a file name.
+      * If the string ends in .gz it is assumed that
+      * the data is in a compressed format.
+      * All string comparisons are case insensitive.
+      *
+      * @param filename  The name of the file or URL to be processed.
+      * @exception FitsException Thrown if unable to find or open
+      *                          a file or URL from the string given.
+      **/
+    public Fits(String filename) throws FitsException {
+
+      InputStream inp;
+
+      if (filename == null) {
+          throw new FitsException("Null FITS Identifier String");
+      }
+
+      boolean compressed = isCompressed(filename);
+
+      int len = filename.length();
+      if (len > 4 && filename.substring(0,5).equalsIgnoreCase("http:") ) {
+          // This seems to be a URL.
+          URL myURL;
+          try {
+               myURL = new URL(filename);
+          } catch (IOException e) {
+               throw new FitsException ("Unable to convert string to URL: "+filename);
+          }
+          try {
+              InputStream is = myURL.openStream();
+              streamInit(is, compressed);
+          } catch (IOException e) {
+              throw new FitsException ("Unable to open stream from URL:"+filename+" Exception="+e);
+          }
+      } else {
+          fileInit(new File(filename), compressed);
+
+      }
+
+    }
+
+    /** Associate the FITS object with a given URL
+      * @param myURL  The URL to be associated with the FITS file.
+      * @param compressed Is the data compressed?
+      * @exception FitsException Thrown if unable to find or open
+      *                          a file or URL from the string given.
+      */
+    public Fits (URL myURL, boolean compressed) throws FitsException {
+	  try {
+            streamInit(myURL.openStream(), compressed);
+        } catch (IOException e) {
+            throw new FitsException("Unable to open input from URL:"+myURL);
+        }
+    }
+
+    /** Associate the FITS object with a given uncompressed URL
+      * @param myURL  The URL to be associated with the FITS file.
+      * @exception FitsException Thrown if unable to use the specified URL.
+      */
+    public Fits (URL myURL) throws FitsException {
+        this(myURL, isCompressed(myURL.getFile()));
+    }
+
+    /** Return all HDUs for the Fits object.   If the
+      * FITS file is associated with an external stream make
+      * sure that we have exhausted the stream.
+      * @return an array of all HDUs in the Fits object.
+      */
+
+    public BasicHDU[] read() throws FitsException {
+
+      readToEnd();
+
+      int size = currentSize();
+      if (size == 0) {
+          return null;
+      }
+
+      BasicHDU[] hdus = new BasicHDU[size];
+      HDUList.copyInto(hdus);
+      return hdus;
+    }
+
+    /** Read the next HDU on the default input stream.
+      * @return The HDU read, or null if an EOF was detected.
+      * Note that null is only returned when the EOF is detected immediately
+      * at the beginning of reading the HDU (i.e., the first card image in the header).
+      */
+    public BasicHDU readHDU() throws FitsException, IOException {
+
+      if (dataStr == null || atEOF) {
+          return null;
+      }
+
+      BasicHDU nextHDU;
+      try {
+	nextHDU = HDU.readHDU(dataStr);
+      } catch (FitsException e) {
+	nextHDU = new BadHDU(e);
+      }
+      if (nextHDU == null) {
+          atEOF = true;
+      } else {
+          HDUList.addElement(nextHDU);
+      }
+
+      if (nextHDU instanceof FauxHDU) {
+	((FauxHDU )nextHDU).throwException();
+      }
+      return nextHDU;
+    }
+
+    /** Skip HDUs on the associate input stream.
+      * @param n The number of HDUs to be skipped.
+      */
+    public void skipHDU(int n) throws FitsException, IOException {
+        for (int i=0; i<n; i += 1) {
+            skipHDU();
+        }
+    }
+
+    /** Skip the next HDU on the default input stream.
+      */
+    public void skipHDU() throws FitsException, IOException {
+
+        if (!atEOF && !HDU.skipHDU(dataStr)) {
+            atEOF = true;
+        } else {
+            HDUList.addElement(new SkippedHDU());
+        }
+    }
+
+   /** Return the n'th HDU.
+     * If the HDU is already read simply return a pointer to the
+     * cached data.  Otherwise read the associated stream
+     * until the n'th HDU is read.
+     * @param n The index of the HDU to be read.  The primary HDU is index 0.
+     * @return The n'th HDU or null if it could not be found.
+     */
+    public BasicHDU getHDU(int n) throws FitsException, IOException {
+
+      int size = currentSize();
+      if (size > n) {
+	 BasicHDU hdu;
+         try {
+             hdu = (BasicHDU) HDUList.elementAt(n);
+         } catch (NoSuchElementException e) {
+             throw new FitsException("Internal Error: Vector mismatch");
+         }
+	 if (hdu instanceof FauxHDU) {
+	     ((FauxHDU )hdu).throwException();
+	 }
+	 return hdu;
+      }
+
+      for (int i=size; i <= n; i += 1) {
+	  BasicHDU hdu;
+	  try {
+	    hdu = readHDU();
+	  } catch (FitsException e) {
+	    hdu = new BadHDU(e);
+	  }
+          if (hdu == null) {
+              return null;
+          }
+      }
+
+      try {
+          return (BasicHDU) HDUList.elementAt(n);
+      } catch (NoSuchElementException e) {
+          throw new FitsException("Internal Error: HDUList build failed");
+      }
+    }
+
+    /** Read to the end of the associated input stream */
+    private void readToEnd() throws FitsException {
+      while (dataStr != null && !atEOF) {
+          try {
+	      if (readHDU() == null) {
+                  break;
+              }
+          } catch (IOException e) {
+              throw new FitsException("IO error: "+e);
+          }
+      }
+    }
+
+
+    /** Return the number of HDUs in the Fits object.   If the
+      * FITS file is associated with an external stream make
+      * sure that we have exhausted the stream.
+      * @return number of HDUs.
+      */
+    public int size() throws FitsException {
+      readToEnd();
+      return currentSize();
+    }
+
+    /** Add an HDU to the Fits object.  Users may intermix
+      * calls to functions which read HDUs from an associated
+      * input stream with the addHDU and insertHDU calls,
+      * but should be careful to understand the consequences.
+      *
+      * @param myHDU  The HDU to be added to the end of the FITS object.
+      */
+    public void addHDU(BasicHDU myHDU)
+	throws FitsException
+    {
+
+      if (myHDU == null) {
+          return;
+      }
+
+      if (currentSize() == 0) {
+          if (myHDU instanceof ImageHDU) {
+	    myHDU = new PrimaryHDU((ImageHDU )myHDU);
+	  } else if (!(myHDU instanceof PrimaryHDU)) {
+	    HDUList.addElement(new PrimaryHDU());
+	  }
+      } else if (myHDU instanceof PrimaryHDU) {
+	  myHDU = new ImageHDU((PrimaryHDU )myHDU);
+      }
+
+      HDUList.addElement(myHDU);
+    }
+
+    /** Insert a FITS object into the list of HDUs.
+      *
+      * @param myHDU The HDU to be inserted into the list of HDUs.
+      * @param n     The location at which the HDU is to be inserted.
+      *              If n is 0, then the previous initial HDU will
+      *              be converted into an IMAGE extension.
+      */
+
+    public void insertHDU(BasicHDU myHDU, int n)
+	throws FitsException
+    {
+
+      if (myHDU == null) {
+          return;
+      }
+
+      if (n < 0 || n >= currentSize()) {
+          throw new FitsException("Attempt to insert HDU at invalid location: "+n);
+      }
+      try {
+          HDUList.insertElementAt(myHDU, n);
+          if (n == 0) {
+	      PrimaryHDU old = (PrimaryHDU )HDUList.elementAt(1);
+              HDUList.setElementAt(new ImageHDU(old), 1);
+          }
+      } catch (NoSuchElementException e) {
+          throw new FitsException("Internal Error: HDUList Vector Inconsistency");
+      }
+    }
+
+    /** Delete an HDU from the HDU list.
+      *
+      * @param n  The index of the HDU to be deleted.
+      *           If n is 0 and there is more than one HDU present, then
+      *           the next HDU will be converted from an image to
+      *           primary HDU if possible.  If not a dummy header HDU
+      *           will then be inserted.
+      */
+    public void deleteHDU(int n) throws FitsException  {
+      int size = currentSize();
+      if (n < 0 || n >= size) {
+          throw new FitsException("Attempt to delete non-existent HDU:"+n);
+      }
+      try {
+          HDUList.removeElementAt(n);
+          if (n == 0 && size > 0) {
+              if (! (HDUList.elementAt(0) instanceof PrimaryHDU)) {
+                  insertHDU(new PrimaryHDU(), 0);
+              }
+          }
+      } catch (NoSuchElementException e) {
+          throw new FitsException("Internal Error: HDUList Vector Inconsitency");
+      }
+    }
+
+    /** Write a Fits Object to an external Stream.
+      *
+      * @param dos  A DataOutput stream.
+      */
+    public void write(OutputStream os) throws FitsException {
+
+      BufferedDataOutputStream obs;
+
+      if (os instanceof BufferedDataOutputStream) {
+          obs = (BufferedDataOutputStream) os;
+      } else {
+          obs = new BufferedDataOutputStream(os);
+      }
+
+	BasicHDU  hh;
+	for (int i=0; i<currentSize(); i += 1) {
+	    try {
+		hh = (BasicHDU) HDUList.elementAt(i);
+	      hh.write(obs);
+	    } catch (ArrayIndexOutOfBoundsException e) {
+		throw new FitsException("Internal Error: Vector Inconsistency");
+	    }
+	}
+
+    }
+
+    /** Read a FITS file from an InputStream object.
+      *
+      * @param is The InputStream stream whence the FITS information
+      *            is found.
+      */
+    public void read(InputStream is) throws FitsException, IOException {
+
+      if (is instanceof BufferedDataInputStream) {
+	    dataStr = (BufferedDataInputStream) is;
+      } else {
+          dataStr = new BufferedDataInputStream(is);
+      }
+      read();
+    }
+
+   /** Get the current number of HDUs in the Fits object.
+     * @return The number of HDU's in the object.
+     */
+    public int currentSize() {
+        return HDUList.size();
+    }
+
+    /** Get the data stream used for the Fits Data.
+      * @return The associated data stream.  Users may wish to
+      *         call this function after opening a Fits object when
+      *         they wish detailed control for writing some part of the FITS file.
+      */
+
+    public BufferedDataInputStream getStream() {
+        return dataStr;
+    }
+
+    /** Set the data stream to be used for future input.
+      *
+      * @param stream The data stream to be used.
+      */
+    public void setStream(BufferedDataInputStream stream) {
+        dataStr = stream;
+        atEOF = false;
+    }
+
+    public static void main(String args[])
+	throws FitsException
+    {
+      if (args.length != 1) {
+	System.err.println("Usage: Fits file");
+	System.exit(1);
+	return;
+      }
+
+      Fits fits = new Fits(args[0]);
+
+      try {
+	System.out.println("Fits: " + fits);
+      } catch (Exception e) {
+	System.err.println(args[0] + " print threw " + e.getMessage());
+	e.printStackTrace(System.err);
+	System.exit(1);
+	return;
+      }
+
+      for (int n = 0; true; n++) {
+	BasicHDU hdu;
+	try {
+	  hdu = fits.getHDU(n);
+	  if (hdu == null) {
+	    break;
+	  }
+	} catch (Exception e) {
+	  System.err.println(args[0] + "#" + n + " fetch threw " +
+			     e.getMessage());
+	  e.printStackTrace(System.err);
+	  System.exit(1);
+	  return;
+	}
+	try {
+	  System.out.println("Fits: " + args[0] + "#" + n + "= " + hdu);
+	} catch (Exception e) {
+	  System.err.println(args[0] + "#" + n + " print threw " +
+			     e.getMessage());
+	  e.printStackTrace(System.err);
+	  System.exit(1);
+	  return;
+	}
+      }
+    }
+}
diff --git a/nom/tam/fits/FitsDate.java b/nom/tam/fits/FitsDate.java
new file mode 100644
index 0000000..e2bc64d
--- /dev/null
+++ b/nom/tam/fits/FitsDate.java
@@ -0,0 +1,300 @@
+package nom.tam.fits;
+
+/*
+ * Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ *
+ * This class was contributed by D. Glowacki.
+ */
+
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+public class FitsDate
+{
+  private int year = -1;
+  private int month = -1;
+  private int mday = -1;
+  private int hour = -1;
+  private int minute = -1;
+  private int second = -1;
+  private int millisecond = -1;
+  private Date date = null;
+
+  /**
+    * Convert a FITS date string to a Java <CODE>Date</CODE> object.
+    * @param dStr	the FITS date
+    * @return	either <CODE>null</CODE> or a Date object
+    * @exception FitsException	if <CODE>dStr</CODE> does not
+    *					contain a valid FITS date.
+    */
+  public FitsDate(String dStr)
+	throws FitsException
+  {
+    // if the date string is null, we're done
+    if (dStr == null) {
+      return;
+    }
+
+    // if the date string is empty, we're done
+    dStr = dStr.trim();
+    if (dStr.length() == 0) {
+      return;
+    }
+
+    // if string contains at least 8 characters...
+    int len = dStr.length();
+    if (len >= 8) {
+      int first;
+
+      // ... and there's a "/" in the string...
+      first = dStr.indexOf('-');
+      if (first == 4 && first < len) {
+
+	// ... this must be an new-style date
+	buildNewDate(dStr, first, len);
+
+      // no "/" found; maybe it's an old-style date...
+      } else {
+
+	first = dStr.indexOf('/');
+	if (first > 1 && first < len) {
+
+	  // ... this must be an old-style date
+	  buildOldDate(dStr, first, len);
+	}
+      }
+    }
+
+    if (year == -1) {
+      throw new FitsException("Bad FITS date string \"" + dStr + '"');
+    }
+  }
+
+  private void buildOldDate(String dStr, int first, int len)
+  {
+    int middle = dStr.indexOf('/', first + 1);
+    if (middle > first + 2 && middle < len) {
+
+      try {
+	year = Integer.parseInt(dStr.substring(middle+1)) + 1900;
+	month = Integer.parseInt(dStr.substring(first+1,middle));
+	mday = Integer.parseInt(dStr.substring(0, first));
+      } catch (NumberFormatException e) {
+	year = month = mday = -1;
+      }
+    }
+  }
+
+  private void parseTime(String tStr)
+	throws FitsException
+  {
+    int first = tStr.indexOf(':');
+    if (first < 0) {
+      throw new FitsException("Bad time");
+    }
+
+    int len = tStr.length();
+
+    int middle = tStr.indexOf(':', first + 1);
+    if (middle > first + 2 && middle < len) {
+
+      if (middle + 3 < len && tStr.charAt(middle + 3) == '.') {
+	double d = Double.valueOf(tStr.substring(middle+3)).doubleValue();
+	millisecond = (int )(d * 1000);
+
+	len = middle + 3;
+      }
+
+      try {
+	hour = Integer.parseInt(tStr.substring(0, first));
+	minute = Integer.parseInt(tStr.substring(first+1, middle));
+	second = Integer.parseInt(tStr.substring(middle+1, len));
+      } catch (NumberFormatException e) {
+	hour = minute = second = millisecond = -1;
+      }
+    }
+  }
+
+  private void buildNewDate(String dStr, int first, int len)
+	throws FitsException
+  {
+    // find the middle separator
+    int middle = dStr.indexOf('-', first + 1);
+    if (middle > first + 2 && middle < len) {
+
+      try {
+
+	// if this date string includes a time...
+	if (middle + 3 < len && dStr.charAt(middle + 3) == 'T') {
+
+	  // ... try to parse the time
+	  try {
+	    parseTime(dStr.substring(middle+4));
+	  } catch (FitsException e) {
+	    throw new FitsException("Bad time in FITS date string \"" +
+				    dStr + "\"");
+	  }
+
+	  // we got the time; mark the end of the date string
+	  len = middle + 3;
+	}
+
+	// parse date string
+	year = Integer.parseInt(dStr.substring(0, first));
+	month = Integer.parseInt(dStr.substring(first+1, middle));
+	mday = Integer.parseInt(dStr.substring(middle+1, len));
+      } catch (NumberFormatException e) {
+
+	// yikes, something failed; reset everything
+	year = month = mday = hour = minute = second = millisecond = -1;
+      }
+    }
+  }
+
+  public Date toDate()
+  {
+    if (date == null && year != -1) {
+      TimeZone tz = TimeZone.getTimeZone("GMT");
+      GregorianCalendar cal = new GregorianCalendar(tz);
+
+      cal.set(Calendar.YEAR, year);
+      cal.set(Calendar.MONTH, month - 1);
+      cal.set(Calendar.DAY_OF_MONTH, mday);
+      if (hour == -1) {
+	cal.set(Calendar.HOUR_OF_DAY, 0);
+	cal.set(Calendar.MINUTE, 0);
+	cal.set(Calendar.SECOND, 0);
+	cal.set(Calendar.MILLISECOND, 0);
+      } else {
+	cal.set(Calendar.HOUR_OF_DAY, hour);
+	cal.set(Calendar.MINUTE, minute);
+	cal.set(Calendar.SECOND, second);
+	if (millisecond == -1) {
+	  cal.set(Calendar.MILLISECOND, 0);
+	} else {
+	  cal.set(Calendar.MILLISECOND, millisecond);
+	}
+      }
+
+      date = cal.getTime();
+    }
+
+    return date;
+  }
+
+  public String toString()
+  {
+    if (year == -1) {
+      return "";
+    }
+
+    StringBuffer buf = new StringBuffer(23);
+    buf.append(year);
+    buf.append('-');
+    if (month < 10) {
+      buf.append('0');
+    }
+    buf.append(month);
+    buf.append('-');
+    if (mday < 10) {
+      buf.append('0');
+    }
+    buf.append(mday);
+
+    if (hour != -1) {
+      buf.append('T');
+      if (hour < 10) {
+	buf.append('0');
+      }
+      buf.append(hour);
+      buf.append(':');
+      if (minute < 10) {
+	buf.append('0');
+      }
+      buf.append(minute);
+      buf.append(':');
+      if (second < 10) {
+	buf.append('0');
+      }
+      buf.append(second);
+
+      if (millisecond != -1) {
+	buf.append('.');
+	if (millisecond < 100) {
+	  if (millisecond < 10) {
+	    buf.append("00");
+	  } else {
+	    buf.append('0');
+	  }
+	}
+	buf.append(millisecond);
+      }
+    }
+
+    return buf.toString();
+  }
+
+  public static void testArgs(String args[])
+  {
+    for (int i = 0; i < args.length; i++) {
+      try {
+	FitsDate fd = new FitsDate(args[i]);
+	System.out.println("\"" + args[i] + "\" => " + fd + " => " +
+			   fd.toDate());
+      } catch (Exception e) {
+	System.err.println("Date \"" + args[i] + "\" threw " +
+			   e.getClass().getName() + "(" + e.getMessage() +
+			   ")");
+      }
+    }
+  }
+
+  public static void autotest()
+  {
+    String[] good = new String[6];
+    good[0] = "20/09/79";
+    good[1] = "1997-07-25";
+    good[2] = "1987-06-05T04:03:02.01";
+    good[3] = "1998-03-10T16:58:34";
+    good[4] = null;
+    good[5] = "        ";
+    testArgs(good);
+
+    String[] badOld = new String[4];
+    badOld[0] = "20/09/";
+    badOld[1] = "/09/79";
+    badOld[2] = "09//79";
+    badOld[3] = "20/09/79/";
+    testArgs(badOld);
+
+    String[] badNew = new String[4];
+    badNew[0] = "1997-07";
+    badNew[1] = "-07-25";
+    badNew[2] = "1997--07-25";
+    badNew[3] = "1997-07-25-";
+    testArgs(badNew);
+
+    String[] badMisc = new String[4];
+    badMisc[0] = "5-Aug-1992";
+    badMisc[1] = "28/02/91 16:32:00";
+    badMisc[2] = "18-Feb-1993";
+    badMisc[3] = "nn/nn/nn";
+    testArgs(badMisc);
+  }
+
+  public static void main(String args[])
+  {
+    if (args.length == 0) {
+      autotest();
+    } else {
+      testArgs(args);
+    }
+  }
+}
diff --git a/nom/tam/fits/FitsException.java b/nom/tam/fits/FitsException.java
new file mode 100644
index 0000000..61cf440
--- /dev/null
+++ b/nom/tam/fits/FitsException.java
@@ -0,0 +1,24 @@
+package nom.tam.fits;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+
+public class FitsException extends Exception {
+
+    public FitsException () {
+        super();
+    }
+
+    public FitsException (String msg) {
+        super(msg);
+    }
+
+}
diff --git a/nom/tam/fits/HDU.java b/nom/tam/fits/HDU.java
new file mode 100644
index 0000000..da6ed22
--- /dev/null
+++ b/nom/tam/fits/HDU.java
@@ -0,0 +1,153 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+
+import java.io.*;
+import java.util.*;
+import nom.tam.util.*;
+
+/** Methods to read/write FITS Header/Data unit (HDU).
+  * This class is generally used either to get access to the Header and Data
+  * objects, or to perform manipulations which affect both the Header and Data.
+  */
+public class HDU
+{
+  /** Create an HDU which points to the given object.  This may be either
+    * a primitive array or an 2-d array of objects.
+    * @param x The data to which the HDU points.
+    * @return the appropriate HDU object
+    * @exception FitsException if the HDU could not be created.
+    */
+  public static BasicHDU create(Object x)
+	throws FitsException
+  {
+    String className = x.getClass().getName();
+    if (className.equals("[[Ljava.lang.Object;") ) {
+      return new BinaryTableHDU((Object[][] )x);
+    }
+
+    if (className.startsWith("[")) {
+      return new ImageHDU(x);
+    }
+
+    throw new FitsException("Expected an array of some sort, not " +
+			    className);
+  }
+
+  public static RandomGroupsHDU createRandomGroups(Object[][] x)
+                                throws FitsException
+  {
+      return new RandomGroupsHDU(x);
+  }
+
+  public static AsciiTableHDU createAsciiTable(Object[][] x)
+                                throws FitsException
+  {
+      throw new FitsException("ASCII tables not yet supported");
+  }
+
+  /** Create an HDU from the supplied Header object.
+    * @param header the Header for the HDU to be created.
+    * @return the appropriate HDU object
+    * @exception FitsException if the HDU could not be created.
+    */
+  public static BasicHDU create(Header header)
+    throws FitsException
+  {
+    BasicHDU hdu;
+    if (PrimaryHDU.isHeader(header)) {
+       return new PrimaryHDU(header);
+    }
+
+    if (ImageHDU.isHeader(header)) {
+       return new ImageHDU(header);
+    }
+
+    if (BinaryTableHDU.isHeader(header)) {
+       return new BinaryTableHDU(header);
+    }
+
+    if (AsciiTableHDU.isHeader(header)) {
+       return new AsciiTableHDU(header);
+    }
+
+    if (RandomGroupsHDU.isHeader(header)) {
+       return new RandomGroupsHDU(header);
+    }
+
+    if (A3DTableHDU.isHeader(header)) {
+      return new A3DTableHDU(header);
+    }
+
+    throw new BadHeaderException("Unknown FITS header: Card 1=" +
+      header.getCard(0).trim() + ')');
+  }
+
+  /** Read an HDU.
+    * This is the usual method by which the Fits class reads an HDU.
+    * @param stream The data stream the FITS data is to be found on.
+    * @return The HDU that has been read.
+    * @exception FitsException if there was a problem with the data.
+    */
+  public static BasicHDU readHDU(BufferedDataInputStream stream)
+	throws FitsException, IOException
+  {
+    // Read the FITS header.  If we are at EOF, just return null.
+    Header hdr = Header.readHeader(stream);
+    if (hdr == null || !hdr.isValidHeader()) {
+      return null;
+    }
+
+    // create the appropriate HDU object for this Header
+    BasicHDU hdu;
+
+    try {
+      hdu = create(hdr);
+    } catch (BadHeaderException e) {
+      try {
+        BasicHDU.skipData(stream, hdr);
+        // Rethrow the original error after skipping data.
+        throw e;
+      } catch (FitsException fe) {
+        // Tell the user we failed to skip data.
+        throw new BadHeaderException(
+          "Could not skip Data section of unknown FITS header (Card 1="+
+          hdr.getCard(0).trim() + ").  Error is: " + fe.getMessage());
+      }
+    }
+
+    hdu.readData(stream);
+
+    return hdu;
+  }
+
+
+  /** Skip an HDU
+    * @return true if the HDU was skipped.
+    * @exception FitsException if the data could not be skipped.
+    */
+  public static boolean skipHDU(BufferedDataInputStream input)
+	throws FitsException, IOException
+  {
+    Header nextHeader = Header.readHeader(input);
+    if (nextHeader == null || !nextHeader.isValidHeader()) {
+      return false;
+    }
+
+    try {
+      BasicHDU.skipData(input, nextHeader);
+    } catch (IOException e) {
+      throw new FitsException("Error skipping data section:" + e.getMessage());
+    }
+
+    return true;
+  }
+}
diff --git a/nom/tam/fits/Header.java b/nom/tam/fits/Header.java
new file mode 100644
index 0000000..7b1a60c
--- /dev/null
+++ b/nom/tam/fits/Header.java
@@ -0,0 +1,1455 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import java.io.*;
+import java.util.*;
+import nom.tam.util.*;
+
+/** This class is used to maintain linked lists
+  * of headers cards with the same keyword.
+  */
+class KeyChain
+{
+  HeaderCard card;
+  KeyChain next;
+
+  public KeyChain(HeaderCard card)
+  {
+    this.card = card;
+    next = null;
+  }
+}
+
+
+/* This class is used to search efficiently for
+ * cards using a hash table.
+ */
+class KeyHash
+{
+  /** The hashed keys
+    */
+  private Hashtable hash = new Hashtable();
+
+  /** Initialize a null KeyHash. */
+  public KeyHash()
+  {
+  }
+
+  /** Add a card to the hash.
+    * @param card The new card.
+    */
+  public final void add(HeaderCard card)
+  {
+    // try to get the key for this card
+    String key = card.getKey();
+    if (key == null) {
+      // must not have a key, so there's nothing to add
+      return;
+    }
+    key = key.toUpperCase();
+
+    // create a wrapper for this card
+    KeyChain kc = new KeyChain(card);
+
+    // find this key entry
+    KeyChain front = (KeyChain )hash.get(key);
+    if (front == null) {
+      // add the new key
+      hash.put(key, kc);
+    } else {
+      // find the end of the chain
+      while (front.next != null) {
+	front = front.next;
+      }
+
+      // add this card to the end of the chain
+      front.next = kc;
+    }
+  }
+
+  /* Delete a card from the hash.
+   * @param card The card to be deleted.
+   */
+  public final void delete(HeaderCard card)
+  {
+    // try to get the key for this card
+    String key = card.getKey();
+    if (key == null) {
+      // must not have a key, so there's nothing to delete
+      return;
+    }
+    key = key.toUpperCase();
+
+    // find this key entry
+    KeyChain front = (KeyChain )hash.get(key);
+    if (front != null) {
+
+      // search through the chain
+      KeyChain prev = null;
+      while (front != null) {
+
+	// if we found a match...
+	if (front.card.equals(card)) {
+	  if (prev != null) {
+
+	    // take this card out of the chain
+	    prev.next = front.next;
+	  } else if (front.next != null) {
+
+	    // there's a new first card in the chain
+	    hash.put(key, front.next);
+	  } else {
+
+	    // this must have been the only card for this key
+	    hash.remove(key);
+	  }
+
+	  // removed the card
+	  return;
+	}
+
+	// move on down the chain
+	prev = front;
+	front = front.next;
+      }
+    }
+
+    // hmmm ... shouldn't have gotten here
+    return;
+  }
+
+  /** Replace one card with another.
+    * @param oldCard The card to be replaced.
+    * @param newCard The new card to take to its place.
+    */
+  public final void replace(HeaderCard oldCard, HeaderCard newCard)
+  {
+    String oldKey = oldCard.getKey();
+    if (oldKey == null) {
+      add(newCard);
+      return;
+    }
+
+    String newKey = newCard.getKey();
+    if (newKey == null) {
+      delete(oldCard);
+      return;
+    }
+
+    if (!oldKey.equalsIgnoreCase(newKey)) {
+      delete(oldCard);
+      add(newCard);
+      return;
+    }
+
+    // find the old key entry
+    String key = oldKey.toUpperCase();
+    KeyChain front = (KeyChain )hash.get(key);
+    if (front == null) {
+      // hmmm ... didn't find the old card ... start a new chain
+      hash.put(key, new KeyChain(newCard));
+      return;
+    }
+
+    // find the end of the chain
+    while (true) {
+
+      // if we found the old card, replace it with the new card
+      if (front.card.equals(oldCard)) {
+	front.card = newCard;
+	return;
+      }
+
+      // if this is the end of the chain...
+      if (front.next == null) {
+	break;
+      }
+
+      // try the next card in the chain
+      front = front.next;
+    }
+
+    // hmmm .. didn't find the old card ... add new card at the end
+    front.next = new KeyChain(newCard);
+  }
+
+  /** Find a given card by its key.  If more than one
+    * is in the header, just return the first.
+    * @param key The FITS keyword.
+    * @return The matching card or <code> null </code> if not found.
+    */
+  public final HeaderCard find(String key)
+  {
+    if (key != null) {
+      KeyChain kc = (KeyChain )hash.get(key.trim().toUpperCase());
+      if (kc != null) {
+	return kc.card;
+      }
+    }
+
+    return null;
+  }
+
+  public final boolean contains(String key)
+  {
+    if (key != null) {
+      if (hash.containsKey(key.toUpperCase())) {
+	return true;
+      }
+    }
+
+    return false;
+  }
+}
+
+
+/** This class stores a set of FITS header cards.
+  */
+class CardTable
+{
+  /** The actual cards stored as a Vector of HeaderCards
+    */
+  private Vector cards;
+
+  /** The hashed keys
+    */
+  private KeyHash keymap;
+
+  /** Create an empty table to hold FITS cards.
+    * @param initialCapacity the initial capacity of the table.
+    * @param capacityIncrement the amount by which the capacity is
+    *				increased when the table overflows.
+    */
+  public CardTable(int initialCapacity, int capacityIncrement)
+  {
+    cards = new Vector(initialCapacity, capacityIncrement);
+    keymap = new KeyHash();
+  }
+
+  /** Create an empty table to hold FITS cards.
+    */
+  public CardTable()
+  {
+    this(360,180);
+  }
+
+  /** Add the specified card to the end of this table.
+    * @param card the card to be added.
+    */
+  public final void addElement(HeaderCard card)
+  {
+    cards.addElement(card);
+    keymap.add(card);
+  }
+
+  /** Return the card found at the specified index.
+    * @param index an index into this table.
+    * @throws ArrayIndexOutOfBoundsException if an invalid index was given.
+    */
+  public final HeaderCard elementAt(int index)
+  {
+    return (HeaderCard )cards.elementAt(index);
+  }
+
+  /** Searches for the first occurence of the given card.
+    * @param card the card be found.
+    * @return the index of the first occurrence of the card;
+    *		returns -1 if the object is not found.
+    */
+  public final int indexOf(HeaderCard card)
+  {
+    return cards.indexOf(card);
+  }
+
+  /** Inserts the specified card at the specified index.
+    *
+    * The index must be greater than or equal to 0 and less than or equal to
+    * the current size of the vector.
+    * @param card the card be inserted.
+    * @param index where the card should be inserted.
+    * @throws ArrayIndexOutOfBoundsException if the index was invalid.
+    */
+  public final void insertElementAt(HeaderCard card, int index)
+  {
+    cards.insertElementAt(card, index);
+    keymap.add(card);
+  }
+
+  /** Removes the card at the specified index.
+    *
+    * The index must be greater than or equal to 0 and less than or equal to
+    * the current size of the vector.
+    * @param index of the card to be removed.
+    * @throws ArrayIndexOutOfBoundsException if the index was invalid.
+    */
+  public final void removeElementAt(int index)
+  {
+    HeaderCard oldCard = (HeaderCard )cards.elementAt(index);
+    cards.removeElementAt(index);
+    keymap.delete(oldCard);
+  }
+
+  /** Replace the card at the specified index with the specified card.
+    *
+    * The index must be greater than or equal to 0 and less than or equal to
+    * the current size of the vector.
+    * @param index of the card to be replaced.
+    * @throws ArrayIndexOutOfBoundsException if the index was invalid.
+    */
+  public final void setElementAt(HeaderCard card, int index)
+  {
+    HeaderCard oldCard = (HeaderCard )cards.elementAt(index);
+    cards.setElementAt(card, index);
+    keymap.replace(oldCard, card);
+  }
+
+  /** Returns the number of cards in this table.
+    * @return the number of cards.
+    */
+  public final int size()
+  {
+    return cards.size();
+  }
+
+  /** Finds the first occurence of the specified key.
+    * @param key the keyword to be found.
+    * @return the first card matching this keyword;
+    *		returns -1 if the keyword was not found.
+    */
+  public final HeaderCard findKey(String key)
+  {
+    return keymap.find(key);
+  }
+
+  /** Tests if the specified keyword is present in this table.
+    * @param key the keyword to be found.
+    * @return <CODE>true<CODE> if the specified keyword is present in this
+    *		table; <CODE>false<CODE> otherwise.
+    */
+  public final boolean containsKey(String key)
+  {
+    return keymap.contains(key);
+  }
+}
+
+/** This class describes methods to access and manipulate the header
+  * for a FITS HDU.
+  */
+public class Header extends Object {
+
+    /** The actual header data stored as a Vector of character strings
+      */
+    private CardTable  cards = new CardTable();
+
+    /** The mark is used to describe where actions on the header
+      * should take place.  The mark points to the 'current' card
+      * in the header.  A value of -1 is used to indicate
+      * that insertions should occur at the beginning of the
+      * header.
+      */
+    private int mark = -2;
+
+    /** Create a Header with no card images.
+      */
+    public Header (){
+    }
+
+    public int size() {
+        return cards.size();
+    }
+
+    /** Create a header and populate it from the input stream
+      * @param is  The input stream where header information is expected.
+      */
+    public Header(BufferedDataInputStream is)
+	throws TruncatedFileException, IOException
+    {
+	read(is);
+    }
+
+    /** Create a header and initialize it with a vector of strings.
+      * @param newCards Card images to be placed in the header.
+      */
+    public Header(String[] newCards) {
+
+      for (int i=0;  i < newCards.length; i += 1) {
+           cards.addElement(new HeaderCard(newCards[i]));
+      }
+
+    }
+
+
+    /** Calculate the unpadded size of the data segment from
+      * the header information.  Note that this algorithm is
+      * not correct for Random Groups format data.
+      * @return the unpadded data segment size.
+      */
+    public int trueDataSize() {
+	if (!isValidHeader()) {
+          return 0;
+	}
+
+	int size = 1;
+
+	int naxis = getIntValue("NAXIS", 0);
+	for (int axis = 1; axis <= naxis; axis += 1) {
+	    int nval = getIntValue("NAXIS"+axis, 0);
+	    if (axis != 1 || nval != 0) {
+	        size *= nval;
+	    }
+	}
+
+	size += getIntValue("PCOUNT", 0);
+	size *= getIntValue("GCOUNT", 1);
+	size *= Math.abs(getIntValue("BITPIX", 0))/8;
+
+        return size;
+    }
+
+    /** Return the size of the data including any needed padding.
+      * @return the data segment size including any needed padding.
+      */
+
+    public int paddedDataSize() {
+	  return ((trueDataSize() + 2879)/2880)*2880;
+    }
+
+    /** Return the size of the header data including padding.
+      * @return the header size including any needed padding.
+      */
+    public int headerSize() {
+       if (!isValidHeader()) {
+           return 0;
+       }
+
+       return ((cards.size()*80 + 2879)/2880) * 2880;
+    }
+
+
+    /** Is this a valid header.  This routine provides only
+      * minimal checking currently.
+      * @return <CODE>true</CODE> for a valid header,
+      *		<CODE>false</CODE> otherwise.
+      */
+    public boolean isValidHeader() {
+	// Probably should do something more sophisticated than this...
+	return (cards != null && cards.size() >= 5);
+    }
+
+
+    /** Get the n'th card image in the header
+      * @return the card image; return <CODE>null</CODE> if the n'th card
+      *		does not exist.
+      */
+    public String getCard(int n) {
+        try {
+	    if (n >= 0 && n < cards.size()) {
+	      return ((HeaderCard) cards.elementAt(n)).toString();
+	    }
+        } catch (NoSuchElementException e) {
+	}
+
+        return null;
+    }
+
+    /** Get the n'th key in the header.
+      * @return the card image; return <CODE>null</CODE> if the n'th key
+      *		does not exist.
+      */
+    public String getKey(int n) {
+
+        String card = getCard(n);
+        if (card == null) {
+            return null;
+        }
+
+        String key = card.substring(0,8);
+        if (key.charAt(0) == ' ') {
+           return "";
+        }
+
+
+        if (key.indexOf(' ') >= 1) {
+            key = key.substring(0,key.indexOf(' '));
+        }
+        return key;
+    }
+
+    /** Get the <CODE>long</CODE> value associated with the given key.
+      * @param key   The header key.
+      * @param dft   The default value to be returned if the key cannot be found.
+      * @return the associated value.
+      */
+    public long getLongValue(String key, long dft) {
+
+	HeaderCard fcard = findCard(key);
+	if (fcard == null) {
+	  return dft;
+	}
+
+	try {
+	    String v = fcard.getValue();
+	    if (v != null) {
+	      return Long.parseLong(v);
+	    }
+	} catch (NumberFormatException e) {
+	}
+
+	return dft;
+    }
+
+    /** Get the <CODE>double</CODE> value associated with the given key.
+      * @param key The header key.
+      * @param dft The default value to return if the key cannot be found.
+      * @return the associated value.
+      */
+    public double  getDoubleValue(String key, double dft) {
+
+	HeaderCard fcard = findCard(key);
+	if (fcard == null) {
+	  return dft;
+	}
+
+	try {
+	    String v = fcard.getValue();
+	    if (v != null) {
+	      return new Double(v).doubleValue();
+	    }
+	} catch (NumberFormatException e) {
+	}
+
+	return dft;
+    }
+
+    /** Get the <CODE>boolean</CODE> value associated with the given key.
+      * @param key The header key.
+      * @param dft The value to be returned if the key cannot be found
+      *            or if the parameter does not seem to be a boolean.
+      * @return the associated value.
+      */
+    public boolean getBooleanValue(String key, boolean dft) {
+
+	HeaderCard fcard = findCard(key);
+	if (fcard == null) {
+	  return dft;
+	}
+
+	String val = fcard.getValue();
+	if (val == null) {
+	  return dft;
+	}
+
+	if (val.equals("T")) {
+	    return true;
+	} else if (val.equals("F")) {
+	    return false;
+	} else {
+          return dft;
+      }
+    }
+
+    /** Get the <CODE>long</CODE> value associated with the given key.
+      * @param key The header key.
+      * @return The associated value or 0 if not found.
+      */
+    public long getLongValue(String key) {
+	return getLongValue(key, 0L);
+    }
+
+    /** Get the <CODE>double</CODE> value associated with the given key.
+      * @param key The header key.
+      * @return The associated value or 0.0 if not found.
+      */
+    public double getDoubleValue(String key) {
+	return getDoubleValue(key, 0.);
+    }
+
+    /** Get the <CODE>boolean</CODE> value associated with the given key.
+      * @param The header key.
+      * @return The value found, or false if not found or if the
+      *         keyword is not a logical keyword.
+      */
+    public boolean getBooleanValue(String key) {
+	return getBooleanValue(key, false);
+    }
+
+    /** Get the value associated with the key as an int.
+      * @param key The header key.
+      * @param dft The value to be returned if the key is not found.
+      */
+    public int getIntValue(String key, int dft) {
+        return (int) getLongValue(key, (long) dft);
+    }
+
+    /** Get the <CODE>int</CODE> value associated with the given key.
+      * @param key The header key.
+      * @return The associated value or 0 if not found.
+      */
+    public int getIntValue(String key) {
+        return (int) getLongValue(key);
+    }
+
+    /** Get the <CODE>float</CODE> value associated with the given key.
+      * @param key The header key.
+      * @param dft The value to be returned if the key is not found.
+      */
+    public float getFloatValue(String key, float dft) {
+        return (float) getDoubleValue(key, dft);
+    }
+
+    /** Get the <CODE>float</CODE> value associated with the given key.
+      * @param key The header key.
+      * @return The associated value or 0.0 if not found.
+      */
+    public float getFloatValue(String key) {
+        return (float) getDoubleValue(key);
+    }
+
+    /** Get the <CODE>String</CODE> value associated with the given key.
+      * @param key The header key.
+      * @return The associated value or null if not found or if the value is not a string.
+      */
+    public String  getStringValue(String key) {
+
+	HeaderCard fcard = findCard(key);
+	if (fcard == null || !fcard.isStringValue()) {
+	  return null;
+	}
+
+	return fcard.getValue();
+    }
+
+    /** Add a card image to the header after the mark if set.
+      * @param fcard The card to be added.
+      */
+    protected void addLine(HeaderCard fcard) {
+
+        if (fcard != null) {
+
+            if (markSet() && getMark() < cards.size()-1) {
+
+                cards.insertElementAt(fcard, getMark()+1);
+                setMark(getMark() + 1);
+
+            } else {
+                cards.addElement(fcard);
+            }
+        }
+    }
+
+
+    /** Add a card image to the header after the mark if set.
+      * @param card The card to be added.
+      * @exception HeaderCardException If the card is not valid.
+      */
+    protected void addLine(String card)
+	throws HeaderCardException
+    {
+      addLine(new HeaderCard(card));
+    }
+
+    /** Create a header by reading the information from the input stream.
+      * @param dis The input stream to read the data from.
+      * @return <CODE>null</CODE> if there was a problem with the header;
+      *		otherwise return the header read from the input stream.
+      */
+    public static Header readHeader(BufferedDataInputStream dis)
+	throws TruncatedFileException, IOException
+    {
+	Header myHeader = new Header();
+        try {
+            myHeader.read(dis);
+        } catch (EOFException e) {
+            // An EOF exception is thrown only if the EOF was detected
+            // when reading the first card.  In this case we want
+            // to return a null.
+            return null;
+        }
+        return myHeader;
+    }
+
+    /** Read a stream for header data.
+      * @param dis The input stream to read the data from.
+      * @return <CODE>null</CODE> if there was a problem with the header;
+      *		otherwise return the header read from the input stream.
+      */
+
+    public void read(BufferedDataInputStream dis)
+	throws TruncatedFileException, IOException
+    {
+	byte[] buffer = new byte[80];
+
+	boolean firstCard = true;
+	while (true) {
+
+	  int len;
+          int need=80;
+          try {
+              while (need > 0) {
+                  len = dis.read(buffer, 80-need, need);
+                  if (len == 0) {
+                    throw new TruncatedFileException();
+                  }
+                  need -= len;
+              }
+	  } catch (EOFException e) {
+              // Rethrow the EOF if we're at the beginning of the header,
+              // otherwise we have a FITS error.
+	      if (firstCard) {
+		  throw e;
+	      }
+	      throw new TruncatedFileException(e.getMessage());
+	  }
+
+	    HeaderCard fcard = new HeaderCard(new String(buffer));
+	    if (firstCard) {
+	      String key = fcard.getKey();
+	      if (key == null ||
+		  (!key.equals("SIMPLE") && !key.equals("XTENSION")))
+	      {
+		throw new IOException("Not a FITS file");
+	      }
+	    }
+
+	    // save card
+	    addLine(fcard);
+	    if (!fcard.isKeyValuePair()) {
+	      String endKey = fcard.getKey();
+	      if (endKey != null && endKey.equals("END") ){
+		break;
+	      }
+	    }
+
+	    // we're past the first card now
+	    firstCard = false;
+	  }
+
+      // Read to the end of the current FITS block.
+	int blanks = 36 - cards.size() % 36;
+	if (blanks != 36) {
+	    while (blanks>0) {
+	        int len;
+              int need=80;
+              try {
+                  while (need > 0) {
+                      len = dis.read(buffer, 80-need, need);
+                      if (len == 0) {
+			  throw new TruncatedFileException();
+                      }
+                      need -= len;
+                  }
+	        } catch (EOFException e) {
+		    throw new TruncatedFileException(e.getMessage());
+	        }
+              blanks -= 1;
+          }
+      }
+    }
+
+    /** Find the card associated with a given key.
+      * If found this sets the mark to the card, otherwise it
+      * unsets the mark.
+      * @param key The header key.
+      * @return <CODE>null</CODE> if the keyword could not be found;
+      *		return the HeaderCard object otherwise.
+      */
+    protected HeaderCard findCard(String key) {
+
+      HeaderCard card = cards.findKey(key);
+      if (card == null) {
+	unsetMark();
+	return null;
+      }
+
+      int newMark = cards.indexOf(card);
+      setMark(newMark);
+      return card;
+    }
+
+    /** Find the card associated with a given key.
+      * If found this sets the mark to the card, otherwise it
+      * unsets the mark.
+      * @param key The header key.
+      * @return <CODE>null</CODE> if the keyword could not be found;
+      *		return the card image otherwise.
+      */
+    public String findKey(String key) {
+      HeaderCard card = findCard(key);
+      if (card == null) {
+	return null;
+      }
+
+      return card.toString();
+    }
+
+    /** Replace the key with a new key.  Typically this is used
+      * when deleting or inserting columns so that TFORMx -> TFORMx-1
+      * @param oldKey The old header keyword.
+      * @param newKey the new header keyword.
+      * @return <CODE>true</CODE> if the card was replaced.
+      * @exception HeaderCardException If <CODE>newKey</CODE> is not a
+      *            valid FITS keyword.
+      */
+    boolean replaceKey(String oldKey, String newKey)
+	throws HeaderCardException
+    {
+
+        HeaderCard oldCard = findCard(oldKey);
+        if (oldCard == null) {
+            return false;
+        }
+
+        String v = oldCard.getValue();
+        if (v != null && oldCard.isStringValue()) {
+          v = "'" + v + "'";
+        }
+
+        String c = oldCard.getComment();
+
+        HeaderCard newCard = new HeaderCard(newKey, v, c);
+        cards.setElementAt(newCard,getMark());
+
+        return true;
+    }
+
+    /** Write the current header (including any needed padding) to the
+      * output stream.
+      * @param dos The output stream to which the data is to be written.
+      * @exception FitsException if the header could not be written.
+      */
+    public void write (BufferedDataOutputStream dos) throws FitsException {
+
+      checkEnd();
+      if (cards.size() <= 0) {
+          return;
+      }
+
+      String[] header = new String[cards.size()];
+      for (int i = 0; i < cards.size(); i++) {
+	header[i] = ((HeaderCard )cards.elementAt(i)).toString();
+      }
+
+      try {
+          dos.writePrimitiveArray(header);
+
+	    int pad = 36 - cards.size()%36;
+	    if (pad != 36) {
+                String blankBuffer =
+"                                                                                ";
+	        for (int i=0; i<pad; i += 1) {
+		      dos.writeBytes(blankBuffer);
+              }
+	    }
+      } catch (IOException e) {
+          throw new FitsException("IO Error writing header: " + e);
+      }
+
+    }
+
+    /** Add or replace a key with the given boolean value and comment.
+      * @param key     The header key.
+      * @param val     The boolean value.
+      * @param comment A comment to append to the card.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public void addBooleanValue(String key, boolean val, String comment)
+	throws HeaderCardException
+    {
+        String tf;
+        if (val) {
+            tf = "T";
+        } else {
+            tf = "F";
+        }
+        replaceCard(key,tf,comment);
+    }
+
+    /** Add or replace a key with the given float value and comment.
+      * @param key     The header key.
+      * @param val     The float value.
+      * @param comment A comment to append to the card.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public void addFloatValue(String key, float val, String comment)
+	throws HeaderCardException
+    {
+        String sval = ""+val;
+        replaceCard(key,sval,comment);
+    }
+
+    /** Add or replace a key with the given double value and comment.
+      * @param key     The header key.
+      * @param val     The double value.
+      * @param comment A comment to append to the card.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public void addDoubleValue(String key, double val, String comment)
+	throws HeaderCardException
+    {
+        String sval = ""+val;
+        replaceCard(key, sval, comment);
+    }
+
+    /** Add or replace a key with the given string value and comment.
+      * @param key     The header key.
+      * @param val     The string value.
+      * @param comment A comment to append to the card.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+
+    public void addStringValue(String key, String val, String comment)
+	throws HeaderCardException
+    {
+        if (val == null) {
+             val = "";
+        }
+        if (val.length() < 8) {
+             val = (val+"        ").substring(0,8);
+        } else if (val.length() > 67) {
+             val = val.substring(0,67);
+        }
+        val = "'"+val+"'";
+        replaceCard(key, val, comment);
+    }
+
+    /** Add or replace a key using the preformatted value.  If the
+      * key is not found, then add the card after the current mark or at
+      * the end if the mark is not set.
+      * @param key     The header key.
+      * @param val     The string which will follow the "= " on the
+      *                card.  This routine is called by the various
+      *                addXXXValue routines after they have formatted the
+      *                value as a string.
+      * @param comment A comment to append to the card.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public void replaceCard(String key, String val, String comment)
+	throws HeaderCardException
+    {
+
+	HeaderCard fcard = new HeaderCard(key, val, comment);
+
+        int oldMark = getMark();
+        findCard(key);
+
+        if (markSet() ) {
+            cards.setElementAt(fcard, getMark());
+        } else {
+            setMark(oldMark);
+            insertCard(fcard);
+        }
+    }
+
+    /** Add or replace a key with the given int value and comment.
+      * @param key     The header key.
+      * @param val     The int value.
+      * @param comment A comment to append to the card.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public void addIntValue(String key, int val, String comment)
+	throws HeaderCardException
+    {
+         addLongValue(key, (long) val, comment);
+    }
+
+    /** Add or replace a key with the given long value and comment.
+      * @param key     The header key.
+      * @param val     The long value.
+      * @param comment A comment to append to the card.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public void addLongValue(String key, long val, String comment)
+	throws HeaderCardException
+    {
+         String sval = ""+val;
+         replaceCard(key, sval, comment);
+    }
+
+    /** insert the given card either at the current mark or at the end of
+      * the header.
+      * @param fcard the card to insert.
+      */
+    private void insertCard(HeaderCard fcard) {
+         if (markSet()  && getMark() < cards.size()-1) {
+             cards.insertElementAt(fcard, getMark()+1);
+             mark += 1;
+         } else {
+             cards.addElement(fcard);
+             unsetMark();
+         }
+     }
+
+    /** Format the key, value and comment fields for the FITS data.
+      * @param key The header keyword.
+      * @param val The value associated with the key expressed as a string.
+      * @param comment A comment to put on the field.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public static String formatFields(String key, String val, String comment)
+	throws HeaderCardException
+    {
+      return new HeaderCard(key,val,comment).toString();
+    }
+
+    /** Insert or add a card to the header.  Insert after the mark
+      * if set, or at the end of the header if not set.
+      * @param card   The card to be inserted.
+      */
+    public void insertCard(String card) {
+         insertCard(new HeaderCard(card));
+    }
+
+    /** Add a line to the header using the COMMENT style, i.e., no '='
+      * in column 9.
+      * @param header The comment style header.
+      * @param value  A string to follow the header.
+      * @exception HeaderCardException If the parameters cannot build a
+      *            valid FITS card.
+      */
+    public void insertCommentStyle(String header, String value)
+	throws HeaderCardException
+    {
+         insertCard(new HeaderCard(header, null, value));
+    }
+
+    /** Add a COMMENT line.
+      * @param value The comment.
+      * @exception HeaderCardException If the parameter is not a
+      *            valid FITS comment.
+      */
+
+    public void insertComment(String value)
+	throws HeaderCardException
+    {
+         insertCommentStyle("COMMENT", value);
+    }
+
+    /** Add a HISTORY line.
+      * @param value The history record.
+      * @exception HeaderCardException If the parameter is not a
+      *            valid FITS comment.
+      */
+    public void insertHistory(String value)
+	throws HeaderCardException
+    {
+         insertCommentStyle("HISTORY", value);
+    }
+
+    /** Is the mark set?
+      * @return <CODE>true</CODE> if the mark is set.
+      */
+    public boolean markSet() {
+        return mark >= -1;
+    }
+
+    /** Get the current mark.
+      * A value of -2 indicates that the mark is not set and new cards are
+      * appended to the end of the header.
+      * A value of -1 indicates that the mark is set such that the next card
+      * should be inserted at the beginning of the header.  Otherwise cards
+      * should be inserted after the mark.  The mark is typically set to
+      * a card whenever an operation (i.e., insert or modify) is made on
+      * the card.  Thus a series of inserts will result in cards
+      * a sequential series of cards in the same order.
+      * @return the current mark.
+      */
+    public int getMark() {
+        return mark;
+    }
+
+    /** Set the mark to the given value.
+      * @param The index of the card the mark is to be set to.
+      */
+    public void setMark(int newMark) {
+        mark = newMark;
+    }
+
+    /** Unset the mark.  Inserts should now be done as appends
+      * to the end of the header.
+      */
+    public void unsetMark() {
+        mark = -2;
+    }
+
+    /** Delete the card associated with the given key.
+      * Nothing occurs if the key is not found, though
+      * this will unset the mark.  The mark is left pointing
+      * at the following card if successful (or unset if this
+      * was the last card).
+      *
+      * @param key The header key.
+      */
+    public void deleteKey(String key) {
+
+        findCard(key);
+
+        if (markSet()) {
+            cards.removeElementAt(getMark());
+        }
+
+        // After a delete we want to point to the card after the deleted cards
+        // so that an immediately following insert will replace the original
+        // card.  This means just leave the mark alone except that we have
+        // to check if we just deleted the last card.
+
+        if (getMark() >= cards.size() ) {
+            unsetMark();
+        }
+    }
+
+    /** Remove the card at the given index.  The mark is left pointing
+      * at the next card or unset if this was the last card.
+      *
+      * @param i The index of the card to be removed.
+      */
+    public void removeCardAt(int i) {
+         if (i < cards.size() && i >= 0) {
+             cards.removeElementAt(i);
+         }
+         if (i < cards.size()) {
+             setMark(i);
+         } else {
+             unsetMark();
+         }
+    }
+
+    /** Tests if the specified keyword is present in this table.
+      * @param key the keyword to be found.
+      * @return <CODE>true<CODE> if the specified keyword is present in this
+      *		table; <CODE>false<CODE> otherwise.
+      */
+    public final boolean containsKey(String key)
+    {
+	return cards.containsKey(key);
+    }
+
+    /** Create keywords such that this Header describes the given
+      * data.
+      * @param o  The data object to be described.
+      * @exception FitsException if the data was not valid for this header.
+      */
+    public void pointToData(Data o) throws FitsException {
+
+        if (o instanceof ImageData) {
+            pointToImage(o.getData());
+        } else {
+            throw new FitsException("Cannot point to class:"+o.getClass().getName());
+        }
+    }
+
+    /** Create keywords such that this header describes the given
+      * image data.
+      * @param o The image to be described.
+      * @exception FitsException if the object does not contain
+      *		valid image data.
+      */
+    protected void pointToImage(Object o) throws FitsException {
+
+        if (o == null) {
+            nullImage();
+        }
+
+        String classname = o.getClass().getName();
+
+        int[] dimens = ArrayFuncs.getDimensions(o);
+        if (dimens == null || dimens.length == 0) {
+            throw new FitsException("Image data object not array");
+        }
+
+        int bitpix;
+        switch (classname.charAt(dimens.length)) {
+          case 'B':
+            bitpix = 8;
+            break;
+          case 'S':
+            bitpix = 16;
+            break;
+          case 'I':
+            bitpix = 32;
+            break;
+          case 'J':
+            bitpix = 64;
+            break;
+          case 'F':
+            bitpix = -32;
+            break;
+          case 'D':
+            bitpix = -64;
+            break;
+          default:
+            throw new FitsException("Invalid Object Type for FITS data");
+        }
+
+	// if this is neither a primary header nor an image extension,
+	//  make it a primary header
+        if (!getBooleanValue("SIMPLE")) {
+            String str = getStringValue("XTENSION");
+            if (str == null || !str.equals("IMAGE") || getMark() != 0) {
+                setSimple(true);
+            }
+        }
+
+        setBitpix(bitpix);
+        setNaxes(dimens.length);
+
+        for (int i=1; i<=dimens.length; i += 1) {
+            if (dimens[i-1] == -1) {
+                throw new FitsException("Unfilled array for dimension: "+i);
+            }
+            setNaxis(i, dimens[i-1]);
+        }
+        setPcount(0);
+        setGcount(1);
+        setExtend(true);
+    }
+
+
+    /** Create a header for a null image.
+      */
+    void nullImage() {
+
+        setSimple(true);
+        setBitpix(8);
+        setNaxes(0);
+        setPcount(0);
+        setGcount(0);
+        setExtend(true);
+
+        // Get rid of any NAXIS junk that's around.
+        for (int i=1; i<9; i += 1) {
+            deleteKey("NAXIS"+i);
+        }
+    }
+
+    /** Set the SIMPLE keyword to the given value.
+      * @param val The boolean value -- Should be true for FITS data.
+      */
+    void setSimple(boolean val) {
+        deleteKey("SIMPLE");
+        deleteKey("XTENSION");
+        if (cards.size() >= 0) {
+            setMark(-1);
+        }
+        try {
+	  addBooleanValue("SIMPLE", val, "Java FITS: " + new Date());
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Set the XTENSION keyword to the given value.
+      * @param val The name of the extension. "IMAGE" and "BINTABLE" are supported.
+      */
+    void setXtension(String val) {
+        deleteKey("SIMPLE");
+        deleteKey("XTENSION");
+        if (cards.size() >= 0) {
+            setMark(-1);
+        }
+        try {
+	  addStringValue("XTENSION", val, "Java FITS: " + new Date());
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Set the BITPIX value for the header.
+      * @param val.  The following values are permitted by FITS conventions:
+      * <ul>
+      * <li> 8  -- signed bytes data.  Also used for tables.
+      * <li> 16 -- signed short data.
+      * <li> 32 -- signed int data.
+      * <li> -32 -- IEEE 32 bit floating point numbers.
+      * <li> -64 -- IEEE 64 bit floating point numbers.
+      * </ul>
+      * These Fits classes also support BITPIX=64 in which case data
+      * is signed 64 bit long data.
+      */
+    void setBitpix(int val) {
+        if (cards.size() > 1) {
+           setMark(0);
+        }
+        try {
+	  addIntValue("BITPIX", val, null);
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Set the value of the NAXIS keyword
+      * @param val The dimensionality of the data.
+      */
+    void setNaxes(int val) {
+        if (cards.size() > 2) {
+           setMark(1);
+        }
+        try {
+	  addIntValue("NAXIS", val, "Dimensionality");
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Ensure that the header has exactly one END keyword in
+      * the appropriate location.
+      */
+    void checkEnd() {
+
+        // Get rid of any END keywords that are not at the end
+        // of the header.
+	HeaderCard blankCard = null;
+        for (int i=0; i<cards.size(); i += 1) {
+            HeaderCard card = (HeaderCard) cards.elementAt(i);
+	    try {
+	      if (!card.isKeyValuePair() && card.getKey().equals("END")) {
+                if (i == cards.size() - 1) {
+                    return;
+                } else {
+                    if (blankCard == null) {
+		      blankCard = new HeaderCard(null, null, null);
+		    }
+		    cards.setElementAt(blankCard, i);
+                }
+	      }
+	    } catch (HeaderCardException e) {
+            }
+        }
+        unsetMark();
+        try {
+	  addLine("END");
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Set the NAXISn keywords.
+      * @param dim The dimension being set.
+      * @param val The length along that dimension.
+      */
+    void setNaxis(int dim, int val) {
+        if (dim <= 0) {
+            return;
+        }
+        if (cards.size() > 2+dim) {
+            setMark(1+dim);
+        }
+        try {
+	  addIntValue("NAXIS"+dim, val, null);
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Set the group count (GCOUNT) keyword.
+      * This should be 1 except for the unsupported random-groups data.
+      * @param val the number of groups in the data.
+      */
+    void setGcount(int val) {
+        try {
+	  addIntValue("GCOUNT", val, "Number of Groups");
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Set the Parameter count (PCOUNT) keyword.
+      * This is normally 0 except when random-groups data is used or when
+      * the variable length columns convention is used with binary tables.
+      * @param val The number of parameters.
+      */
+    void setPcount(int val) {
+        try {
+	  addIntValue("PCOUNT", val, "Group params/Variable cols buffer");
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** Set the EXTEND keyword.  This is only placed in the
+      * primary header and should always be <CODE>true</CODE>.
+      * Just in case we do provide for setting it to <CODE>false</CODE>.
+      * @param val the value assigned to the EXTEND keyword.
+      */
+    void setExtend(boolean val) {
+        try {
+	  addBooleanValue("EXTEND", val, "Can there be extensions?");
+	} catch (HeaderCardException e) {
+	  throw new RuntimeException("Impossible error: " + e.getMessage());
+	}
+    }
+
+    /** See if the current header is an array and if so turn
+      * it into an IMAGE extension.
+      *
+      * @return whether the transformation could be done.
+      */
+    protected boolean primaryToImage() {
+
+        if (getBooleanValue("SIMPLE") && getMark() == 0) {
+            setXtension("IMAGE");
+            return true;
+        }
+
+        String str = getStringValue("XTENSION");
+        if (str == null) {
+            setXtension("IMAGE");
+            return true;
+        }
+
+        if (str.equals("IMAGE")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /** See if the current header is for an an array and if so
+      * turn it into a primary array.
+      *
+      * @return whether the transformation could be done.
+      */
+
+    protected boolean imageToPrimary() {
+        if (getBooleanValue("SIMPLE") && getMark() == 0) {
+            return true;
+        }
+
+        String str = getStringValue("XTENSION");
+        if (str == null) {
+            setSimple(true);
+            return true;
+        }
+
+        if (str.equals("IMAGE") && getMark() == 0) {
+            setSimple(true);
+            return true;
+        }
+
+        return false;
+    }
+
+    /** Dump the header to a given stream.
+      * @param ps the stream to which the card images are dumped.
+      */
+    protected void dumpHeader(PrintStream ps) {
+        for (int i=0; i<cards.size(); i += 1) {
+            ps.println(cards.elementAt(i));
+        }
+    }
+}
diff --git a/nom/tam/fits/HeaderCard.java b/nom/tam/fits/HeaderCard.java
new file mode 100644
index 0000000..a27864b
--- /dev/null
+++ b/nom/tam/fits/HeaderCard.java
@@ -0,0 +1,280 @@
+package nom.tam.fits;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes -- including
+  * this class.
+  */
+
+
+/** This class describes methods to access and manipulate the individual
+  * cards for a FITS Header.
+  */
+public class HeaderCard
+{
+  /** The keyword part of the card (set to null if there's no keyword) */
+  String key;
+
+  /** The value part of the card (set to null if there's no value) */
+  String value;
+
+  /** The comment part of the card (set to null if there's no comment) */
+  String comment;
+
+  /** A flag indicating whether or not this is a string value */
+  boolean isString;
+
+  /** Maximum length of a FITS keyword field */
+  private static final int MAX_KEYWORD_LENGTH = 8;
+
+  /** Maximum length of a FITS value field */
+  private static final int MAX_VALUE_LENGTH = 70;
+
+  /** padding for building card images */
+  private String space40 = "                                        ";
+
+  /** Create a HeaderCard from its component parts
+    * @param key keyword (null for a comment)
+    * @param value value (null for a comment or keyword without an '=')
+    * @param comment comment
+    * @exception HeaderCardException for any invalid keyword or value
+    */
+  public HeaderCard(String key, String value, String comment)
+	throws HeaderCardException
+  {
+    if (key == null && value != null) {
+      throw new HeaderCardException("Null keyword with non-null value");
+    }
+
+    if (key != null && key.length() > MAX_KEYWORD_LENGTH) {
+      throw new HeaderCardException("Keyword too long");
+    }
+
+    if (value != null) {
+      value = value.trim();
+
+      if (value.length() > MAX_VALUE_LENGTH) {
+	throw new HeaderCardException("Value too long");
+      }
+
+      if (value.charAt(0) == '\'') {
+	if (value.charAt(value.length()-1) != '\'') {
+	  throw new HeaderCardException("Missing end quote in string value");
+	}
+
+	value = value.substring(1,value.length()-1).trim();
+
+	isString = true;
+      }
+    }
+
+    this.key = key;
+    this.value = value;
+    this.comment = comment;
+  }
+
+  /** Create a HeaderCard from a FITS card image
+    * @param card the 80 character card image
+    */
+  public HeaderCard(String card)
+  {
+    key = null;
+    value = null;
+    comment = null;
+    isString = false;
+
+
+    // We are going to assume that the value has no blanks in
+    // it unless it is enclosed in quotes.  Also, we assume that
+    // a / terminates the string (except inside quotes)
+
+    // treat short lines as special keywords
+    if (card.length() < 9) {
+      key = card;
+      return;
+    }
+
+    // extract the key
+    key = card.substring(0, 8).trim();
+
+    // if it's an empty key, assume the remainder of the card is a comment
+    if (key.length() == 0) {
+      key = "";
+      comment = card.substring(8);
+      return;
+    }
+
+    // Non-key/value pair lines are treated as keyed comments
+    if (!card.substring(8,10).equals("= ")) {
+      comment = card.substring(8);
+      return;
+    }
+
+    // extract the value/comment part of the string
+    String valcom = card.substring(10).trim();
+
+    // if there's no value/comment part, we're done
+    if (valcom.length() == 0) {
+      value = "";
+      return;
+    }
+
+    int vend = -1;
+    boolean quote = false;
+
+    // If we have a ' then find the matching quote.
+    if (valcom.charAt(0) == '\'') {
+
+      int offset = 1;
+      while (offset < valcom.length()) {
+
+	// look for next single-quote character
+	vend = valcom.indexOf("'", offset);;
+
+	// if the quote character is the last character on the line...
+	if (vend == valcom.length()-1) {
+	  break;
+	}
+
+	// if we didn't find a matching single-quote...
+	if (vend == -1) {
+	  // pretend this is a comment card
+	  key = null;
+	  comment = card;
+	  return;
+	}
+
+	// if this isn't an escaped single-quote, we're done
+	if (valcom.charAt(vend+1) != '\'') {
+	  break;
+	}
+
+	// skip past escaped single-quote
+	offset = vend+2;
+      }
+
+      // break apart character string
+      value = valcom.substring(1, vend).trim();
+      isString = true;
+    }
+
+    // look for a / to terminate the field.
+    int slashLoc = valcom.indexOf('/');
+    if (slashLoc != -1) {
+      comment = valcom.substring(slashLoc+1).trim();
+      valcom = valcom.substring(0, slashLoc).trim();
+    }
+
+    // if we didn't already save a string value, do it now
+    if (!isString) {
+      value = valcom;
+    }
+  }
+
+  /** Does this card contain a string value?
+    */
+  public boolean isStringValue()
+  {
+    return isString;
+  }
+
+  /** Is this a key/value card?
+    */
+  public boolean isKeyValuePair()
+  {
+    return (key != null && value != null);
+  }
+
+  /** Return the keyword from this card
+    */
+  public String getKey()
+  {
+    return key;
+  }
+
+  /** Return the value from this card
+    */
+  public String getValue()
+  {
+    return value;
+  }
+
+  /** Return the comment from this card
+    */
+  public String getComment()
+  {
+    return comment;
+  }
+
+  /** Return the 80 character card image
+    */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer(80);
+
+    // start with the keyword, if there is one
+    if (key != null) {
+      buf.append(key);
+    }
+
+    // fill keyword field with blanks
+    while (buf.length() < 8) {
+      buf.append(' ');
+    }
+
+    if (value != null) {
+      buf.append("= ");
+
+      if (isString) {
+	// left justify the string inside the quotes
+	buf.append('\'');
+	buf.append(value);
+	while (buf.length() < 19) {
+	  buf.append(' ');
+	}
+	buf.append('\'');
+      } else {
+	int offset = buf.length();
+	buf.append(value);
+
+	// right justify the value field to column 30
+	while (buf.length() < 30) {
+	  buf.insert(offset, ' ');
+	}
+      }
+
+      // if there's a comment, add a comment delimiter
+      if (comment != null) {
+	buf.append(" / ");
+      }
+    } else if (comment != null && comment.startsWith("= ")) {
+      buf.append("  ");
+    }
+
+    // finally, add any comment
+    if (comment != null) {
+      buf.append(comment);
+    }
+
+    // make sure the final string is exactly 80 characters long
+    if (buf.length() > 80) {
+      buf.setLength(80);
+    } else {
+
+      if (buf.length() < 40) {
+	buf.append(space40);
+      }
+
+      if (buf.length() < 80) {
+	buf.append(space40.substring(0, 80 - buf.length()));
+      }
+    }
+
+    return buf.toString();
+  }
+}
diff --git a/nom/tam/fits/HeaderCardException.java b/nom/tam/fits/HeaderCardException.java
new file mode 100644
index 0000000..905f185
--- /dev/null
+++ b/nom/tam/fits/HeaderCardException.java
@@ -0,0 +1,21 @@
+package nom.tam.fits;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+/* This class was contributed by David Glowacki */
+
+public class HeaderCardException
+	extends FitsException
+{
+  public HeaderCardException() { super(); }
+  public HeaderCardException(String s) { super(s); }
+}
+
diff --git a/nom/tam/fits/ImageData.java b/nom/tam/fits/ImageData.java
new file mode 100644
index 0000000..2001358
--- /dev/null
+++ b/nom/tam/fits/ImageData.java
@@ -0,0 +1,101 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+
+/** This class instantiates FITS primary HDU and IMAGE extension data.
+  * Essentially these data are a primitive multi-dimensional array.
+  */
+public class ImageData extends Data {
+
+    /** Create an array from a header description.
+      * This is typically how data will be created when reading
+      * FITS data from a file where the header is read first.
+      * This creates an empty array.
+      * @param h header to be used as a template.
+      * @exception FitsException if there was a problem with the header description.
+      */
+    public ImageData(Header h) throws FitsException {
+
+ 	int bitpix;
+    	int type;
+	int ndim;
+	int[] dims;
+
+	long trueSize;
+	int i;
+
+      Class baseClass;
+
+	int gCount = h.getIntValue("GCOUNT",1);
+	int pCount = h.getIntValue("PCOUNT",0);
+	if (gCount > 1  || pCount != 0) {
+              throw new FitsException("Currently unable to handle GROUPed data"+
+                "  GCOUNT="+gCount+"; PCOUNT="+pCount);
+      }
+
+	bitpix = (int) h.getIntValue("BITPIX", 0);
+	if (bitpix == 8) {
+	    baseClass = Byte.TYPE;
+	} else if (bitpix == 16) {
+	    baseClass = Short.TYPE;
+	} else if (bitpix == 32) {
+	    baseClass = Integer.TYPE;
+	} else if (bitpix == 64) {  /* This isn't a standard for FITS yet...*/
+	    baseClass = Long.TYPE;
+	} else if (bitpix == -32) {
+	    baseClass = Float.TYPE;
+	} else if (bitpix == -64) {
+	    baseClass = Double.TYPE;
+	} else {
+	    throw new FitsException("Invalid BITPIX:"+bitpix);
+	}
+
+	ndim = h.getIntValue("NAXIS   ", 0) ;
+	dims = new int[ndim];
+
+
+     	// Note that we have to invert the order of the axes
+	// for the FITS file to get the order in the array we
+      // are generating.
+
+	for (i=0; i<ndim; i += 1) {
+	    long cdim = h.getIntValue("NAXIS"+(i+1), 0);
+          if (cdim < 0) {
+              throw new FitsException("Invalid array dimension:"+cdim);
+          }
+	    dims[ndim-i-1] = (int) cdim;
+	}
+
+      if (ndim > 0) {
+          // dataArray is inherited from Data
+          dataArray = java.lang.reflect.Array.newInstance(baseClass, dims);
+      } else {
+          int[] dim = new int[1];
+          dim[0] = 0;
+          dataArray = java.lang.reflect.Array.newInstance(baseClass, dim);
+      }
+    }
+
+    /** Create the equivalent of a null data element.
+      */
+    public ImageData() {
+        dataArray = new byte[0];
+    }
+
+    /** Create an ImageData object using the specified object to
+      * initialize the data array.
+      * @param x The initial data array.  This should be a primitive
+      *          array but this is not checked currently.
+      */
+    public ImageData(Object x) {
+        dataArray = x;
+    }
+}
diff --git a/nom/tam/fits/ImageHDU.java b/nom/tam/fits/ImageHDU.java
new file mode 100644
index 0000000..4c6b998
--- /dev/null
+++ b/nom/tam/fits/ImageHDU.java
@@ -0,0 +1,127 @@
+package nom.tam.fits;
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import nom.tam.util.ArrayFuncs;
+import nom.tam.util.BufferedDataInputStream;
+
+/** FITS image header/data unit */
+public class ImageHDU
+	extends ExtensionHDU
+{
+  /** Create an image header/data unit.
+    * @param header the template specifying the image.
+    * @exception FitsException if there was a problem with the header.
+    */
+  public ImageHDU(Header header)
+	throws FitsException
+  {
+    super(header);
+    if (!isHeader()) {
+      throw new BadHeaderException("Not a valid image header");
+    }
+  }
+
+  /** Build an image HDU from a RandomGroupsHDU.
+    * @param primary the RandomGroupsHDU containing the image data.
+    * @exception FitsException if there was a problem with the data.
+    */
+  public ImageHDU(PrimaryHDU primary)
+	throws FitsException
+  {
+    // this is currently a hack; should clone RandomGroupsHDU header and data
+
+    super(primary.myHeader);
+
+    if (myHeader != null) {
+      if (!myHeader.primaryToImage()) {
+	throw new FitsException("Couldn't create ImageHDU from PrimaryHDU");
+      }
+    }
+
+    if (!isHeader()) {
+      throw new FitsException("Header was not converted to a valid image header");
+    }
+
+    myData = primary.myData;
+  }
+
+  /** Build an image HDU using the supplied data.
+    * @param obj the data used to build the image.
+    * @exception FitsException if there was a problem with the data.
+    */
+  public ImageHDU(Object obj)
+	throws FitsException
+  {
+    super(new Header());
+
+    myData = new ImageData(obj);
+    myHeader.pointToData(myData);
+
+    if (!myHeader.primaryToImage()) {
+      throw new FitsException("Default header was not converted to a valid image header");
+    }
+  }
+
+  /** Check that this is a valid image extension header.
+    * @param header to validate.
+    * @return <CODE>true</CODE> if this is an image extension header.
+    */
+  public static boolean isHeader(Header header)
+  {
+    String card0 = header.getCard(0);
+    return (card0 != null && card0.startsWith("XTENSION= 'IMAGE   '"));
+  }
+
+  /** Check that this HDU has a valid header.
+    * @return <CODE>true</CODE> if this HDU has a valid header.
+    */
+  public boolean isHeader()
+  {
+    return isHeader(myHeader);
+  }
+
+  /** Create a Data object to correspond to the header description.
+    * @return An unfilled Data object which can be used to read
+    *         in the data for this HDU.
+    * @exception FitsException if the image extension could not be created.
+    */
+  public Data manufactureData()
+	throws FitsException
+  {
+    return new ImageData(myHeader);
+  }
+
+  /** Print out some information about this HDU.
+    */
+  public void info() {
+    if (isHeader()) {
+      System.out.println("  Image Extension");
+    } else {
+      System.out.println("  Image Extension (bad header)");
+    }
+
+    System.out.println("      Header Information:");
+    System.out.println("         BITPIX="+myHeader.getIntValue("BITPIX",-1));
+    int naxis = myHeader.getIntValue("NAXIS", -1);
+    System.out.println("         NAXIS="+naxis);
+    for (int i=1; i<=naxis; i += 1) {
+      System.out.println("         NAXIS"+i+"="+
+			 myHeader.getIntValue("NAXIS"+i,-1));
+    }
+
+    System.out.println("      Data information:");
+    if (myData.getData() == null) {
+      System.out.println("        No Data");
+    } else {
+      System.out.println("         "+
+			 ArrayFuncs.arrayDescription(myData.getData()));
+    }
+  }
+}
diff --git a/nom/tam/fits/PrimaryHDU.java b/nom/tam/fits/PrimaryHDU.java
new file mode 100644
index 0000000..f9de365
--- /dev/null
+++ b/nom/tam/fits/PrimaryHDU.java
@@ -0,0 +1,137 @@
+package nom.tam.fits;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+
+import nom.tam.util.ArrayFuncs;
+import nom.tam.util.BufferedDataInputStream;
+
+/** FITS primary array header/data unit */
+public class PrimaryHDU
+	extends BasicHDU
+{
+  /** Create a primary array unit.
+    * @param header the template specifying the primary array.
+    * @exception FitsException if there was a problem with the header.
+    */
+  public PrimaryHDU(Header header)
+	throws FitsException
+  {
+    super(header);
+    if (!isHeader()) {
+      throw new BadHeaderException("Not a valid primary header or uses random groups");
+    }
+  }
+
+  /** Build a primary HDU from an image HDU.
+    * @param img the ImageHDU containing the data.
+    * @exception FitsException if there was a problem with the data.
+    */
+  public PrimaryHDU(ImageHDU img)
+	throws FitsException
+  {
+
+    super(img.myHeader);
+
+    if (myHeader != null) {
+      if (!myHeader.imageToPrimary()) {
+	throw new FitsException("Couldn't create PrimaryHDU from ImageHDU");
+      }
+    }
+
+    myData = img.myData;
+  }
+
+  /** Build an empty primary HDU.
+    * @exception FitsException if there was a problem creating the HDU.
+    */
+  public PrimaryHDU()
+	throws FitsException
+  {
+    super(new Header());
+
+    myData = new ImageData();
+    myHeader.pointToData(myData);
+  }
+
+  /** Build a primary HDU using the supplied data.
+    * @param obj the data used to build the primary HDU.
+    * @exception FitsException if there was a problem with the data.
+    */
+  public PrimaryHDU(Object obj)
+	throws FitsException
+  {
+    super(new Header());
+
+    myData = new ImageData(obj);
+    myHeader.pointToData(myData);
+  }
+
+  /** Check that this is a valid primary/non-random groups header.
+    * @param header to validate.
+    * @return <CODE>true</CODE> if this is a simple primary header.
+    */
+  public static boolean isHeader(Header header)
+  {
+    // We don't handle random-groups format.
+    if (header.getBooleanValue("GROUPS")) {
+	if (header.getIntValue("GCOUNT",1) > 1 ||
+	    header.getIntValue("PCOUNT",0) != 0)
+	{
+	    return false;
+	}
+    }
+
+    String card0 = header.getCard(0);
+    return (card0 != null && card0.startsWith("SIMPLE  "));
+  }
+
+  /** Check that this HDU has a valid header.
+    * @return <CODE>true</CODE> if this HDU has a valid header.
+    */
+  public boolean isHeader()
+  {
+    return isHeader(myHeader);
+  }
+
+  /** Create a Data object to correspond to the header description.
+    * @return An unfilled Data object which can be used to read
+    *         in the data for this HDU.
+    * @exception FitsException if the image data could not be created.
+    */
+  public Data manufactureData()
+	throws FitsException
+  {
+    return new ImageData(myHeader);
+  }
+
+  /** Print out some information about this HDU.
+    */
+  public void info() {
+    System.out.println("  Primary Image");
+
+    System.out.println("      Header Information:");
+    System.out.println("         BITPIX="+myHeader.getIntValue("BITPIX",-1));
+    int naxis = myHeader.getIntValue("NAXIS", -1);
+    System.out.println("         NAXIS="+naxis);
+    for (int i=1; i<=naxis; i += 1) {
+      System.out.println("         NAXIS"+i+"="+
+			 myHeader.getIntValue("NAXIS"+i,-1));
+    }
+
+    System.out.println("      Data information:");
+    if (myData.getData() == null) {
+      System.out.println("        No Data");
+    } else {
+      System.out.println("         "+
+			 ArrayFuncs.arrayDescription(myData.getData()));
+    }
+  }
+}
diff --git a/nom/tam/fits/RandomGroupsData.java b/nom/tam/fits/RandomGroupsData.java
new file mode 100644
index 0000000..1e2fa59
--- /dev/null
+++ b/nom/tam/fits/RandomGroupsData.java
@@ -0,0 +1,145 @@
+package nom.tam.fits;
+/* Copyright: Thomas McGlynn 1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ * Many thanks to David Glowacki (U. Wisconsin) for substantial
+ * improvements, enhancements and bug fixes.
+ */
+
+import nom.tam.fits.*;
+
+/** This class instantiates FITS Random Groups data.
+  * Random groups are instantiated as a two-dimensional
+  * array of objects.  The first dimension of the array
+  * is the number of groups.  The second dimension is 2.
+  * The first object in every row is a one dimensional
+  * parameter array.  The second element is the n-dimensional
+  * data array.
+  */
+public class RandomGroupsData extends Data {
+
+    /** Create a random groups array from a header description.
+      * This is typically how data will be created when reading
+      * FITS data from a file where the header is read first.
+      * This creates an empty array.
+      * @param h header to be used as a template.
+      * @exception FitsException if there was a problem with the header description.
+      */
+    public RandomGroupsData(Header h) throws FitsException {
+
+
+    Class baseClass;
+
+    int gcount = h.getIntValue("GCOUNT", -1);
+    int pcount = h.getIntValue("PCOUNT", -1);
+
+    if (!h.getBooleanValue("GROUPS") ||
+         h.getIntValue("NAXIS1", -1) != 0 ||
+         gcount < 0 || pcount < 0  ||
+         h.getIntValue("NAXIS")< 2) {
+        throw new FitsException("Invalid Random Groups Parameters");
+    }
+
+	// Allocate the object.
+	if (gcount > 0) {
+	    dataArray = new Object[gcount][2];
+	} else {
+	    dataArray = new Object[0][];
+	}
+
+	Object[] sampleRow = generateSampleRow(h);
+	for (int i=0; i<gcount; i += 1) {
+	    ((Object[])dataArray)[i] = nom.tam.util.ArrayFuncs.deepClone(sampleRow);
+	}
+}
+
+public static Object[] generateSampleRow (Header h)
+                                         throws FitsException {
+
+	int ndim = h.getIntValue("NAXIS", 0) - 1;
+	int[] dims = new int[ndim];
+
+	int bitpix =  h.getIntValue("BITPIX", 0);
+	Class baseClass;
+
+	if (bitpix == 8) {
+	    baseClass = Byte.TYPE;
+	} else if (bitpix == 16) {
+	    baseClass = Short.TYPE;
+	} else if (bitpix == 32) {
+	    baseClass = Integer.TYPE;
+	} else if (bitpix == 64) {  /* This isn't a standard for FITS yet...*/
+	    baseClass = Long.TYPE;
+	} else if (bitpix == -32) {
+	    baseClass = Float.TYPE;
+	} else if (bitpix == -64) {
+	    baseClass = Double.TYPE;
+	} else {
+	    throw new FitsException("Invalid BITPIX:"+bitpix);
+	}
+
+    // Note that we have to invert the order of the axes
+	// for the FITS file to get the order in the array we
+    // are generating.  Also recall that NAXIS1=0, so that
+    // we have an 'extra' dimension.
+
+	for (int i=0; i<ndim; i += 1) {
+	    long cdim = h.getIntValue("NAXIS"+(i+2), 0);
+        if (cdim < 0) {
+            throw new FitsException("Invalid array dimension:"+cdim);
+        }
+	    dims[ndim-i-1] = (int) cdim;
+	}
+
+	Object[] sample = new Object[2];
+	sample[0] = java.lang.reflect.Array.newInstance(baseClass, h.getIntValue("PCOUNT"));
+	sample[1] = java.lang.reflect.Array.newInstance(baseClass, dims);
+	return sample;
+
+}
+
+
+
+/** Create the equivalent of a null data element.
+  */
+public RandomGroupsData() {
+    dataArray = new Object[0][];
+}
+
+/** Create a RandomGroupsData object using the specified object to
+  * initialize the data array.
+  * @param x The initial data array.  This should a two-d
+  *          array of objects as described above.
+  */
+public RandomGroupsData(Object[][] x) {
+    dataArray = x;
+}
+
+public int getPadding() {
+
+    Object par = ((Object[][]) dataArray)[0][0];
+    Object dat = ((Object[][]) dataArray)[0][1];
+
+    int nbyte = nom.tam.util.ArrayFuncs.getBaseLength(par);
+    int npar = java.lang.reflect.Array.getLength(par);
+    int[] ndims = nom.tam.util.ArrayFuncs.getDimensions(dat);
+    int ngrp = ((Object[][])dataArray).length;
+
+    int total = 1;
+    for (int i=0; i < ndims.length; i += 1) {
+        total *= ndims[i];
+    }
+
+    total += npar;
+    total = nbyte*ngrp*total;
+
+    if (total % 2880 == 0) {
+        return 0;
+    } else {
+        return 2880 - total%2880;
+    }
+}
+
+}
diff --git a/nom/tam/fits/RandomGroupsHDU.java b/nom/tam/fits/RandomGroupsHDU.java
new file mode 100644
index 0000000..7aae5cc
--- /dev/null
+++ b/nom/tam/fits/RandomGroupsHDU.java
@@ -0,0 +1,159 @@
+package nom.tam.fits;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+
+/** Random groups HDUs.  Note that the internal storage of random
+  * groups is a Object[ngroup][2] array.  The first element of
+  * each group is the parameter data from that group.  The second element
+  * is the data.  The parameters should be a one dimensional array
+  * of the primitive types byte, short, int, long, float or double.
+  * The second element is a n-dimensional array of the same type.
+  * When analyzing group data structure only the first group is examined,
+  * but for a valid FITS file all groups must have the same structure.
+  */
+public class RandomGroupsHDU extends BasicHDU {
+
+
+    public RandomGroupsHDU(Header myHeader)  throws FitsException {
+        super(myHeader);
+        myData = manufactureData();
+    }
+
+    public RandomGroupsHDU(Object[][] data)
+        throws FitsException {
+        super(new Header());
+        myData = new RandomGroupsData(data);
+        pointToData(myHeader, data);
+    }
+
+    /** Make a header point to the given object.
+      * @param h The header to be modified.
+      * @param data The random groups data the header should describe.
+      */
+    static void pointToData(Header h, Object[][] data)
+                            throws FitsException {
+
+        if (data.length <= 0 || data[0].length != 2) {
+            throw new FitsException("Data not conformable to Random Groups");
+        }
+
+        int gcount = data.length;
+        Object paraSamp = data[0][0];
+        Object dataSamp = data[0][1];
+
+        Class pbase = nom.tam.util.ArrayFuncs.getBaseClass(paraSamp);
+        Class dbase = nom.tam.util.ArrayFuncs.getBaseClass(dataSamp);
+
+        if (pbase != dbase) {
+            throw new FitsException("Data and parameters do not agree in type for random group");
+        }
+
+        int[] pdims = nom.tam.util.ArrayFuncs.getDimensions(paraSamp);
+        int[] ddims = nom.tam.util.ArrayFuncs.getDimensions(dataSamp);
+
+        if (pdims.length != 1) {
+            throw new FitsException("Parameters are not 1 d array for random groups");
+        }
+
+        // We've now got the information we need to build the
+        // header.
+
+        h.setSimple(true);
+        if (dbase == byte.class) {
+            h.setBitpix(8);
+        } else if (dbase == short.class) {
+            h.setBitpix(16);
+        } else if (dbase == int.class) {
+            h.setBitpix(32);
+        } else if (dbase == long.class) { // Non-standard
+            h.setBitpix(64);
+        } else if (dbase == float.class) {
+            h.setBitpix(-32);
+        } else if (dbase == double.class) {
+            h.setBitpix(-64);
+        } else {
+            throw new FitsException("Data type:"+dbase+" not supported for random groups");
+        }
+
+        h.setNaxes(ddims.length+1);
+        h.addIntValue("NAXIS1", 0, "");
+        for (int i=2; i<=ddims.length+1; i += 1) {
+            h.addIntValue("NAXIS"+i, ddims[i-2], "");
+        }
+
+        h.addBooleanValue("GROUPS", true, "");
+        h.addIntValue("GCOUNT", data.length, "");
+        h.addIntValue("PCOUNT", pdims[0], "");
+    }
+
+    /** Is the a random groups header?
+      * @param myHeader The header to be tested.
+      */
+    public static boolean isHeader(Header myHeader) {
+
+        return (myHeader.getBooleanValue("SIMPLE") &
+                myHeader.getBooleanValue("GROUPS")   ) ;
+
+    }
+
+    /** Check that this HDU has a valid header.
+      * @return <CODE>true</CODE> if this HDU has a valid header.
+      */
+    public boolean isHeader() { return isHeader(myHeader); }
+
+    /** Create a FITS Data object corresponding to
+      * this HDU header.
+      */
+    public Data manufactureData() throws FitsException {
+        if (myHeader != null) {
+            return new RandomGroupsData(myHeader);
+        } else {
+            return null;
+        }
+
+    }
+
+    /** Display structural information about the current HDU.
+      */
+    public void info() {
+
+        System.out.println("Random Groups HDU");
+        if (myHeader != null) {
+            System.out.println("   HeaderInformation:");
+            System.out.println("     Ngroups:"+myHeader.getIntValue("GCOUNT"));
+            System.out.println("     Npar:   "+myHeader.getIntValue("PCOUNT"));
+            System.out.println("     BITPIX: "+myHeader.getIntValue("BITPIX"));
+            System.out.println("     NAXIS:  "+myHeader.getIntValue("NAXIS"));
+            for (int i=0; i<myHeader.getIntValue("NAXIS"); i += 1) {
+                System.out.println("      NAXIS"+(i+1)+"= "+
+                   myHeader.getIntValue("NAXIS"+(i+1)));
+            }
+        } else {
+            System.out.println("    No Header Information");
+        }
+
+
+        Object[][] data = null;
+        if (myData != null) {
+            data = (Object[][]) myData.getData();
+        }
+
+        if (data == null || data.length < 1 || data[0].length != 2 ) {
+            System.out.println("    Invalid/unreadable data");
+        } else {
+            System.out.println("    Number of groups:"+data.length);
+            System.out.println("    Parameters: "+nom.tam.util.ArrayFuncs.arrayDescription(data[0][0]));
+            System.out.println("    Data:"+nom.tam.util.ArrayFuncs.arrayDescription(data[0][1]));
+        }
+    }
+
+}
diff --git a/nom/tam/fits/TableHDU.java b/nom/tam/fits/TableHDU.java
new file mode 100644
index 0000000..16d0634
--- /dev/null
+++ b/nom/tam/fits/TableHDU.java
@@ -0,0 +1,65 @@
+package nom.tam.fits;
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+import java.util.Vector;
+
+import nom.tam.util.BufferedDataInputStream;
+
+/** Generic FITS table methods */
+public abstract class TableHDU
+	extends ExtensionHDU
+{
+  /** An array containing the base keys that a user may anticipate
+    * being associated with a given column.  This array is used
+    * to find and extract keys that belong to a given column.
+    */
+  Vector columnStrings = new Vector();
+
+  /** Build a table from the specified FITS header.
+    * @param header to use as a template.
+    * @exception FitsException if the header was not valid.
+    */
+  public TableHDU(Header header)
+	throws FitsException
+  {
+    super(header);
+  }
+
+  /** Add a base keyword to the keys to be looked for
+    * in describing a column.  E.g., if a user wishes to
+    * make sure the TLMINn keywords are associated with columns
+    * he/she might call addColumnString("TLMIN");
+    * This does not mean that the software will generate these
+    * columns, only that they will be seen as associated
+    * with the appropriate table data.
+    */
+  public void addColumnString(String keyBase) {
+    columnStrings.addElement(keyBase);
+  }
+
+  /** Ensure that keywords for the current column are placed after
+    * the keywords for the last column.
+    * @param The minimum index to be used.
+    * @param The column that is being added (so we are looking for the keywords
+    *        from the previous column.
+    */
+  void setLastMark (int lastMark, int colNumber) {
+
+    for (int i=0; i<columnStrings.size(); i += 1) {
+      myHeader.findKey((String)columnStrings.elementAt(i) + (colNumber-1));
+      if (myHeader.markSet() && myHeader.getMark() > lastMark) {
+	lastMark = myHeader.getMark();
+      }
+    }
+
+    myHeader.setMark(lastMark);
+  }
+}
diff --git a/nom/tam/fits/TruncatedFileException.java b/nom/tam/fits/TruncatedFileException.java
new file mode 100644
index 0000000..eea8caf
--- /dev/null
+++ b/nom/tam/fits/TruncatedFileException.java
@@ -0,0 +1,21 @@
+package nom.tam.fits;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+/** This exception is thrown when an EOF is detected in the middle
+  * of an HDU.
+  */
+public class TruncatedFileException
+	extends FitsException
+{
+  public TruncatedFileException() { super(); }
+  public TruncatedFileException(String msg) { super(msg); }
+}
diff --git a/nom/tam/fits/package.html b/nom/tam/fits/package.html
new file mode 100644
index 0000000..398b2d1
--- /dev/null
+++ b/nom/tam/fits/package.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+The <B>F</B>lexible <B>I</B>mage <B>T</B>ransport <B>S</B>ystem, used
+primarily for exchanging astronomical data.
+
+</body>
+</html>
+
diff --git a/nom/tam/test/FitsTester.java b/nom/tam/test/FitsTester.java
new file mode 100644
index 0000000..f7f929f
--- /dev/null
+++ b/nom/tam/test/FitsTester.java
@@ -0,0 +1,523 @@
+package nom.tam.test;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  * Many thanks to David Glowacki (U. Wisconsin) for substantial
+  * improvements, enhancements and bug fixes.
+  */
+
+
+
+import nom.tam.fits.*;
+import nom.tam.util.*;
+import java.util.Date;
+import java.io.*;
+
+
+/** This class comprises a set of static methods to test the Java FITS implementation.
+   */
+public class FitsTester {
+
+    public static void main(String[] args) {
+      testSimpleWrite();
+      testSimpleRead();
+      testNetRead();
+      testSkipAndRead();
+      testReadByRow();
+      testWriteByRow();
+      testBuildByColumn();
+      testVarCols();
+      testRandomGroups();
+    }
+
+    static void testRandomGroups() {
+
+      start("Write and read a random groups data set");
+      try {
+
+         short[] pararr = new short[5];
+         short[][] dataArr = new short[50][50];
+
+         Object[][] test = new Object[10][2];
+         // Only fill in the first row so we
+         // can write the data group by group.
+
+         test[0][0] = pararr;
+         test[0][1] = dataArr;
+
+         RandomGroupsHDU hdu = new RandomGroupsHDU(test);
+
+         BufferedDataOutputStream os =
+            new BufferedDataOutputStream(
+               new FileOutputStream("test6.fits")
+            );
+
+         int padding = hdu.getData().getPadding();
+
+         hdu.getHeader().write(os);
+
+         for (int i=0; i<10; i += 1) {
+             pararr[2] = (short) i;
+             pararr[3] = (short) (i*i);
+             dataArr[i][i] = (short)(i*i*i);
+             os.writePrimitiveArray(pararr);
+             os.writePrimitiveArray(dataArr);
+         }
+         byte[] pad = new byte[padding];
+         os.write(pad);
+         os.flush();
+         os.close();
+         os = null;
+
+     } catch (Exception e) {
+         System.out.println("Error writing random groups data");
+         e.printStackTrace(System.out);
+         return;
+     }
+
+     // Read the data back in.
+     try {
+
+         Fits rg = new Fits("Test6.fits");
+         BasicHDU[] HDUs = rg.read();
+         HDUs[0].info();
+         Object[][] data = (Object[][]) HDUs[0].getData().getData();
+         for (int i=0; i<10; i += 1) {
+
+             short[] par = (short[]) data[i][0];
+             System.out.println("    Group:"+(i+1)+" params2,3= "+par[2]+" "+par[3]);
+             short[][] arr = (short[][])data[i][1];
+             System.out.println("           Data[i][i] = "+arr[i][i]);
+         }
+     } catch (Exception e) {
+         System.out.println("Error reading random groups data");
+         e.printStackTrace(System.out);
+     }
+     end("Test Random Groups");
+    }
+    static void testVarCols() {
+
+      start("Build and read variable length columns");
+      try {
+        Fits myFits = new Fits();
+
+        myFits.addHDU(new PrimaryHDU());
+        myFits.addHDU(HDU.create(genTable()));
+
+        BasicHDU[] myHDUs = myFits.read();
+
+        BinaryTableHDU table = (BinaryTableHDU )myHDUs[1];
+
+        int[][] varData = new int[8][];
+        for (int i=0; i<8; i += 1) {
+            varData[i] = new int[i+1];
+            for (int j=0; j <= i; j += 1) {
+                varData[i][j] = 2*j;
+            }
+        }
+        System.out.println("    Written to FITS:");
+        for (int i=0; i<varData.length; i += 1) {
+            System.out.print("       "+i+":");
+            for (int j=0; j<varData[i].length; j += 1) {
+               System.out.print(" "+j+":"+varData[i][j]);
+            }
+            System.out.println("");
+        }
+
+        Column newCol = table.makeVarColumn(varData, "J");
+        newCol.addKey(Header.formatFields("TTYPE", "'TestVar '", "Name of variable column"));
+        table.addColumn(newCol);
+
+        BufferedDataOutputStream obs = new BufferedDataOutputStream(
+                                        new FileOutputStream("test5.fits"));
+
+        myFits.write(obs);
+        obs.flush();
+        obs.close();
+      } catch (Exception e) {
+        System.out.println("Exception writing test5.fits:"+e);
+        e.printStackTrace(System.out);
+      }
+
+      try {
+
+        Fits myFits = new Fits("test5.fits");
+        BasicHDU[] myHDUs = myFits.read();
+
+	if (myHDUs == null) {
+	   System.out.println("Error: test5.fits doesn't seem to have any HDUs!");
+	   return;
+	}
+
+        BinaryTableHDU table = (BinaryTableHDU )myHDUs[1];
+        table.info();
+        int[][] varcol = (int[][]) table.getVarData("TestVar");
+	if (varcol == null) {
+	   System.out.println("Error: test5.fits TestVar data not found");
+	   return;
+	}
+        System.out.println("    Read from FITS:");
+        for (int i=0; i<varcol.length; i += 1) {
+            System.out.print("       "+i+":");
+            for (int j=0; j<varcol[i].length; j += 1) {
+               System.out.print(" "+j+":"+varcol[i][j]);
+            }
+            System.out.println("");
+        }
+     } catch (Exception e) {
+        System.out.println("Caught exception reading test5.fits: " + e);
+        e.printStackTrace(System.out);
+     }
+
+     end("Test Var columns");
+    }
+
+    static void testBuildByColumn () {
+
+      start("Build by Column");
+
+      try {
+        Fits myFits = new Fits();
+        myFits.addHDU(new PrimaryHDU());
+
+        BinaryTableHDU myHDU = new BinaryTableHDU();
+
+        int[][][] column = new int[8][][];
+
+        for (int i=0; i<8; i += 1) {
+            column[i] = new int[4][3];
+            column[i][0][0] = i;
+        }
+
+        myHDU.addColumn(column);
+
+        float[][] column2 = new float[8][10];
+        for (int i=0; i<8; i += 1) {
+            column2[i][0] = 100*i;
+        }
+
+        myHDU.addColumn(column2);
+
+        double[][] column3 = new double[8][4];
+        myHDU.addColumn(column3);
+
+        myFits.addHDU(myHDU);
+
+        BufferedDataOutputStream obs = new BufferedDataOutputStream(
+                                           new FileOutputStream("test4.fits"));
+        myFits.write(obs);
+
+        obs.flush();
+        obs.close();
+        obs = null;
+
+        myFits = new Fits("test4.fits");
+
+        BasicHDU[] myHDUs = myFits.read();
+
+	if (myHDUs == null) {
+	   System.out.println("Error: test4.fits doesn't seem to have any HDUs!");
+	   return;
+	}
+
+        for (int i=0; i<myHDUs.length; i += 1) {
+             myHDUs[i].info();
+        }
+      } catch (Exception e) {
+        System.out.println("Caught exception writing/reading test4.fits: "+e);
+        e.printStackTrace(System.out);
+      }
+
+      end("Build by column: ");
+    }
+
+    static void testWriteByRow() {
+
+      start("Write data row by row");
+
+      Object[] aRow = new Object[3];
+      aRow[0] = new int[30];
+      aRow[1] = new float[10][10];
+      aRow[2] = new double[2][3][4];
+      String[] names = new String[3];
+      names[0]= "RandomName1"; names[1]= "RandomName2"; names[2]= "RandomName3";
+
+      int nrows = 20;
+
+      try {
+          Object[][] testTable = new Object[1][];
+          testTable[0] = aRow;
+          BinaryTableHDU dummy = new BinaryTableHDU(testTable);
+          Header myHeader = dummy.getHeader();
+          myHeader.addIntValue("NAXIS2", nrows, "Number of rows");
+          for (int i=0; i<3; i += 1) {
+              myHeader.findKey("TFORM"+(i+1));
+              myHeader.addStringValue("TTYPE"+(i+1), names[i], "");
+          }
+
+          BufferedDataOutputStream obs = new BufferedDataOutputStream(
+                                             new FileOutputStream("test2.fits"));
+
+          PrimaryHDU rg = new PrimaryHDU();
+          rg.write(obs);
+          myHeader.write(obs);
+          for (int i=0; i<nrows; i += 1) {
+              ((int[])aRow[0])[0] = i;
+              obs.writePrimitiveArray(aRow);
+          }
+
+          // Add in padding to make legal FITS.
+
+          int paddingSize = myHeader.paddedDataSize() - myHeader.trueDataSize();
+          byte[] pad = new byte[paddingSize];
+          obs.write(pad);
+          obs.flush();
+          obs.close();
+
+      } catch (Exception e) {
+          System.out.println("Caught exception writing test2.fits: " +e);
+          return;
+      }
+
+      try {
+          Fits myFits = new Fits("test2.fits");
+          BasicHDU[] myHDUs = myFits.read();
+	  if (myHDUs == null) {
+	     System.out.println("Error: test2.fits doesn't seem to have any HDUs!");
+	     return;
+	  }
+          for (int i=0; i<myHDUs.length; i += 1) {
+              myHDUs[i].info();
+          }
+
+           BinaryTable data = (BinaryTable) myHDUs[1].getData();
+           for (int i=0; i < data.getNrow(); i += 1) {
+               int[] col0 = (int[])data.getElement(i,0);
+               System.out.println("    Row marker is:"+col0[0]);
+           }
+       } catch (Exception e) {
+           System.out.println("Caught exception reading test2.fits:"+e);
+	   e.printStackTrace(System.out);
+       }
+       end("Writing data row by row");
+    }
+
+    static void testReadByRow() {
+      start("Read row by row");
+
+      try {
+      Fits myFits = new Fits("test1.fits");
+      myFits.skipHDU(2);
+
+      BufferedDataInputStream ibs = myFits.getStream();
+      Header myHeader = Header.readHeader(ibs);
+      if (myHeader == null) {
+	  System.out.println("Third HDU from test1.fits is null!");
+	  return;
+      }
+
+      Object[] aRow = new BinaryTableHeaderParser(myHeader).getModelRow();
+
+      int nrows = myHeader.getIntValue("NAXIS2");
+
+      byte[] col1 = (byte[]) aRow[0];
+      for (int i=0; i<nrows; i += 1) {
+          ibs.readPrimitiveArray(aRow);
+          System.out.println("    Reading row:"+(i+1)+" with marker:"+col1[0]);
+      }
+
+      // We don't need to do this here, but it shows how you might do to get
+      // to the start of the next HDU.
+      ibs.skipBytes(myHeader.paddedDataSize() - myHeader.trueDataSize());
+    } catch (Exception e) {
+      System.out.println("Caught exception reading by rows:"+e);
+      e.printStackTrace(System.out);
+    }
+    end("Reading row by row");
+
+    }
+
+    static void testSkipAndRead() {
+
+      start("Skip to third extension");
+      try {
+        Fits myFits = new Fits("test1.fits");
+        myFits.skipHDU(2);
+        BasicHDU[] myHDUs = myFits.read();
+	if (myHDUs == null) {
+	   System.out.println("Error: test1.fits third extension doesn't exist!");
+	   return;
+	}
+        for (int i=0; i<myHDUs.length; i += 1) {
+            myHDUs[i].info();
+        }
+      } catch (Exception e) {
+        System.out.println("Caught exception in skip and read:"+e);
+      }
+      end("Skip to third extension");
+    }
+
+    static void testSimpleWrite() {
+
+      Fits myFits;
+      start("Write a FITS file");
+      try {
+        // First create a null FITS object.
+        myFits = new Fits();
+
+        // Now create three extensions.
+
+        int[] dims1 = {20,20,20};
+        myFits.addHDU(HDU.create(ArrayFuncs.generateArray(Float.TYPE, dims1)));
+
+
+        int[] dims2 = {2,2,2,8,16};
+        myFits.addHDU(HDU.create(ArrayFuncs.generateArray(Integer.TYPE, dims2)));
+
+        myFits.addHDU(HDU.create(genTable()));
+
+        java.io.FileOutputStream fo = new java.io.FileOutputStream("test1.fits");
+        BufferedDataOutputStream o = new BufferedDataOutputStream(fo);
+        myFits.write(o);
+     } catch (Exception e) {
+        System.err.println("Exception thrown:"+e);
+        e.printStackTrace(System.out);
+        return;
+     }
+
+     end("Write a FITS file");
+   }
+
+   static void testSimpleRead() {
+
+     Fits myFits;
+     BasicHDU[] myHDUs;
+
+     start("Read a FITS file");
+
+     try {
+
+       myFits = new Fits("test1.fits");
+       myHDUs = myFits.read();
+
+    } catch (Exception e) {
+
+       System.out.println("Caught an exception reading test1.fits:"+e);
+       e.printStackTrace(System.out);
+       return;
+    }
+
+    if (myHDUs == null) {
+       System.out.println("Error: test1.fits doesn't seem to have any HDUs!");
+       return;
+    }
+
+    for (int i=0; i < myHDUs.length; i += 1) {
+       try {
+          if (myHDUs[i] == null) {
+	    System.out.println("test1.fits HDU#" + i + " is null");
+	  } else {
+	    myHDUs[i].info();
+	  }
+       } catch (Exception e) {
+
+          System.out.println("Caught an exception examining test1.fits HDU#"+i+":"+e);
+          e.printStackTrace(System.out);
+       }
+    }
+
+    end("Read a FITS file");
+
+  }
+
+static void testNetRead () {
+
+    Fits myFits;
+    BasicHDU[] myHDUs;
+
+    start("Read compressed FITS file over the network");
+
+    String testURL=
+    "http://legacy.gsfc.nasa.gov/FTP/compton/data/egret/phase01/pnt_0010/counts_vp0010_g001.fits.gz";
+
+    try {
+
+      myFits = new Fits(testURL);
+      myHDUs = myFits.read();
+    } catch (Exception e) {
+      System.out.println("Caught an exception reading over the net:"+e);
+      e.printStackTrace(System.out);
+      return;
+    }
+
+    if (myHDUs == null) {
+       System.out.println("Error: net file doesn't seem to have any HDUs!");
+       return;
+    }
+
+    for (int i=0; i<myHDUs.length; i += 1) {
+        try {
+	    myHDUs[i].info();
+	} catch (Exception e) {
+	  System.out.println("Caught an exception examining net HDU#"+i+":"+e);
+	  e.printStackTrace(System.out);
+	  return;
+	}
+    }
+    end("Read over net");
+}
+
+static Object[][] genTable() {
+
+         Object[] row1 = new Object[6];
+
+         int[] dims0 = {10};
+         row1[0] = ArrayFuncs.generateArray(Byte.TYPE, dims0);
+
+         int[] dims1 = {5,5};
+         row1[1] = ArrayFuncs.generateArray(Short.TYPE, dims1);
+
+         int[] dims2 = {5,2};
+         row1[2] = ArrayFuncs.generateArray(Integer.TYPE, dims2);
+
+         int[] dims3 = {3,3,2};
+         row1[3] = ArrayFuncs.generateArray(Integer.TYPE, dims3);
+
+         int[] dims4 = {4,3,2};
+         row1[4] = ArrayFuncs.generateArray(Float.TYPE, dims4);
+
+         int[] dims5 = {6,7};
+         row1[5] = ArrayFuncs.generateArray(Double.TYPE, dims5);
+
+         Object[][] table = new Object[8][6];
+
+         for (int i=0; i<8; i += 1) {
+
+             table[i] = (Object[]) ArrayFuncs.deepClone(row1);
+
+             // Mark each row.
+             byte[] col0 = (byte[])  table[i][0];
+             col0[0] = (byte)i;
+
+         }
+         return table;
+    }
+
+    static void start(String msg) {
+        System.out.println("***************************************");
+        System.out.println("Start:  "+ msg + " @ " + new Date());
+    }
+
+    static void end(String msg) {
+        System.out.println("End:    "+ msg + " @ " + new Date());
+        System.out.println("");
+        System.out.println("");
+        System.out.println("");
+    }
+
+
+}
diff --git a/nom/tam/test/package.html b/nom/tam/test/package.html
new file mode 100644
index 0000000..af5ccb0
--- /dev/null
+++ b/nom/tam/test/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Classes used for regression testing the main FITS classes.
+
+</body>
+</html>
+
diff --git a/nom/tam/util/ArrayFuncs.java b/nom/tam/util/ArrayFuncs.java
new file mode 100644
index 0000000..78d8036
--- /dev/null
+++ b/nom/tam/util/ArrayFuncs.java
@@ -0,0 +1,1050 @@
+
+// Member of the utility package.
+
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+
+
+
+import java.lang.reflect.*;
+
+
+/** This is a package of static functions which perform
+  * computations on arrays.  Generally these routines attempt
+  * to complete without throwing errors by ignoring data
+  * they cannot understand.
+  */
+
+public class ArrayFuncs {
+
+    /** Compute the size of an object.  Note that this only handles
+      * arrays or scalars of the primitive objects and Strings.  It
+      * returns 0 for any object array element it does not understand.
+      *
+      * @param o The object whose size is desired.
+      */
+
+    public static int computeSize(Object o) {
+
+        if (o == null) {
+             return 0;
+        }
+
+        int size = 0;
+        String classname = o.getClass().getName();
+        if (classname.substring(0, 2).equals("[[") ||
+            classname.equals("[Ljava.lang.String;") ||
+            classname.equals("[Ljava.lang.Object;")) {
+
+            for (int i=0; i<((Object[])o).length; i += 1) {
+                 size += computeSize( ((Object[]) o)[i] );
+            }
+            return size;
+        }
+
+        if (classname.charAt(0) == '['  && classname.charAt(1) != 'L') {
+            switch (classname.charAt(1)) {
+                 case 'I': return ((int[])o).length  * 4;
+                 case 'J': return ((long[])o).length * 8;
+                 case 'F': return ((float[])o).length* 4;
+                 case 'D': return ((double[])o).length*8;
+                 case 'B': return ((byte[])o).length;
+                 case 'C': return ((char[])o).length * 2;
+                 case 'S': return ((short[])o).length* 2;
+                 case 'Z': return ((boolean[])o).length;
+                 default: return 0;
+            }
+        }
+
+        if (classname.substring(0,10).equals("java.lang.") ) {
+            classname = classname.substring(10, classname.length()-1);
+
+	      if (classname.equals("Int") || classname.equals("Float")) {
+                return 4;
+            } else if (classname.equals("Double") || classname.equals("Long")) {
+                return 8;
+            } else if (classname.equals("Short") || classname.equals("Char")) {
+                return 2;
+            } else if (classname.equals("Byte") || classname.equals("Boolean")) {
+                return 1;
+            } else if (classname.equals("String") ) {
+                return ((String)o).length();
+            } else {
+                return 0;
+            }
+        }
+        return 0;
+    }
+
+/** Try to create a deep clone of an Array or a standard clone of a scalar.
+  * The object may comprise arrays of
+  * any primitive type or any Object type which implements Cloneable.
+  * However, if the Object is some kind of collection, e.g., a Vector
+  * then only a shallow copy of that object is made.  I.e., deep refers
+  * only to arrays.
+  *
+  * @param o The object to be copied.
+  */
+    public static Object deepClone(Object o) {
+
+        if (o == null) {
+            return null;
+        }
+
+        String classname = o.getClass().getName();
+
+        // Is this an array?
+        if (classname.charAt(0) != '[') {
+            return genericClone(o);
+        }
+
+        // Check if this is a 1D primitive array.
+        if (classname.charAt(1) != '[' && classname.charAt(1) != 'L') {
+          try {
+            // Some compilers (SuperCede, e.g.) still
+            // think you have to catch this...
+            if (false) throw new CloneNotSupportedException();
+            switch( classname.charAt(1) ){
+                 case 'B': return ((byte[])o).clone();
+                 case 'Z': return ((boolean[])o).clone();
+                 case 'C': return ((char[])o).clone();
+                 case 'S': return ((short[])o).clone();
+                 case 'I': return ((int[])o).clone();
+                 case 'J': return ((long[])o).clone();
+                 case 'F': return ((float[])o).clone();
+                 case 'D': return ((double[])o).clone();
+                 default: System.err.println("Unknown primtive array class:"+classname);
+                          return null;
+
+            }
+          } catch (CloneNotSupportedException e) {}
+        }
+
+        // Get the base type.
+        int ndim = 1;
+        while (classname.charAt(ndim) == '[') {
+            ndim += 1;
+        }
+        Class baseClass;
+        if (classname.charAt(ndim) != 'L') {
+            baseClass = getBaseClass(o);
+        } else {
+              try {
+                  baseClass = Class.forName(classname.substring(ndim+1, classname.length()-1));
+              } catch (ClassNotFoundException e) {
+                  System.err.println("Internal error: class definition inconsistency: "+classname);
+                  return null;
+              }
+        }
+
+        // Allocate the array but make all but the first dimension 0.
+        int[] dims = new int[ndim];
+        dims[0] = Array.getLength(o);
+        for (int i=1; i<ndim; i += 1) {
+            dims[i] = 0;
+        }
+
+
+        Object copy = Array.newInstance(baseClass, dims);
+
+        // Now fill in the next level down by recursion.
+        for (int i=0; i<dims[0]; i += 1) {
+             Array.set(copy, i, deepClone(Array.get(o, i)));
+        }
+
+        return copy;
+    }
+
+    /** Clone an Object if possible.
+     *
+     * This method returns an Object which is a clone of the
+     * input object.  It checks if the method implements the
+     * Cloneable interface and then uses reflection to invoke
+     * the clone method.  This can't be done directly since
+     * as far as the compiler is concerned the clone method for
+     * Object is protected and someone could implement Cloneable but
+     * leave the clone method protected.  The cloning can fail in a
+     * variety of ways which are trapped so that it returns null instead.
+     * This method will generally create a shallow clone.  If you
+     * wish a deep copy of an array the method deepClone should be used.
+     *
+     * @param o The object to be cloned.
+     */
+    public static Object genericClone(Object o) {
+
+        if (! (o instanceof Cloneable) ) {
+            return null;
+        }
+
+        Class[] argTypes = new Class[0];
+        Object[] args = new Object[0];
+
+        try {
+            return o.getClass().getMethod("clone", argTypes).invoke(o,args);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /** Copy one array into another.
+      * This function copies the contents of one array
+      * into a previously allocated array.
+      * The arrays must agree in type and size.
+      * @param original The array to be copied.
+      * @param copy     The array to be copied into.  This
+      *                 array must already be fully allocated.
+      */
+     public static void copyArray(Object original, Object copy) {
+         String oname = original.getClass().getName();
+         String cname = copy.getClass().getName();
+
+         if (! oname.equals(cname)) {
+             return;
+         }
+
+         if (oname.charAt(0) != '[') {
+             return;
+         }
+
+         if (oname.charAt(1) == '[') {
+             Object[] x = (Object[]) original;
+             Object[] y = (Object[]) copy;
+             if (x.length != y.length) {
+                 return;
+             }
+             for (int i=0; i<x.length; i += 1) {
+                 copyArray(x,y);
+             }
+         }
+         int len = Array.getLength(original);
+
+         System.arraycopy(original, 0, copy, 0, len);
+     }
+
+
+
+
+    /** Find the dimensions of an object.
+      *
+      * This method returns an integer array with the dimensions
+      * of the object o which should usually be an array.
+      *
+      * It returns an array of dimension 0 for scalar objects
+      * and it returns -1 for dimension which have not been allocated,
+      *  e.g., int[][][] x = new int[100][][]; should return [100,-1,-1].
+      *
+      * @param o The object to get the dimensions of.
+      */
+
+    public static int[] getDimensions(Object o) {
+
+        if (o == null) {
+            return null;
+        }
+
+        String classname = o.getClass().getName();
+
+        int ndim=0;
+
+        while (classname.charAt(ndim) == '[') {
+            ndim += 1;
+        }
+
+        int[] dimens = new int[ndim];
+
+        for (int i=0; i<ndim; i += 1) {
+             dimens[i] = -1;  // So that we can distinguish a null from a 0 length.
+        }
+
+        for (int i=0; i < ndim; i += 1) {
+            dimens[i] = java.lang.reflect.Array.getLength(o);
+            if (dimens[i] == 0) {
+                return dimens;
+            }
+            if (i != ndim-1) {
+                o  = ((Object[])o)[0];
+                if (o == null) {
+                    return dimens;
+                }
+            }
+        }
+        return dimens;
+    }
+
+    /** This routine returns the base class of an object.  This is just
+      * the class of the object for non-arrays.
+      */
+    public static Class getBaseClass(Object o) {
+
+         if (o == null) {
+             return Void.TYPE;
+         }
+
+         String className = o.getClass().getName();
+
+         int dims = 0;
+         while (className.charAt(dims) == '[') {
+             dims += 1;
+         }
+
+         if (dims == 0) {
+             return o.getClass();
+         }
+
+         switch (className.charAt(dims) ) {
+
+            case 'Z': return Boolean.TYPE;
+            case 'B': return Byte.TYPE;
+            case 'S': return Short.TYPE;
+            case 'C': return Character.TYPE;
+            case 'I': return Integer.TYPE;
+            case 'J': return Long.TYPE;
+            case 'F': return Float.TYPE;
+            case 'D': return Double.TYPE;
+            case 'L':
+                try {
+                    return Class.forName(className.substring(dims+1, className.length()-1));
+                } catch (ClassNotFoundException e) {
+                    return null;
+                }
+            default:  return null;
+         }
+    }
+
+    /** This routine returns the size of the base element of an array.
+      * @param o The array object whose base length is desired.
+      * @return the size of the object in bytes, 0 if null, or
+      * -1 if not a primitive array.
+      */
+    public static int getBaseLength(Object o) {
+
+         if (o == null) {
+             return 0;
+         }
+
+         String className = o.getClass().getName();
+
+         int dims = 0;
+
+         while (className.charAt(dims) == '[') {
+             dims += 1;
+         }
+
+         if (dims == 0) {
+             return -1;
+         }
+
+         switch (className.charAt(dims) ) {
+
+            case 'Z': return 1;
+            case 'B': return 1;
+            case 'S': return 2;
+            case 'C': return 2;
+            case 'I': return 4;
+            case 'J': return 8;
+            case 'F': return 4;
+            case 'D': return 8;
+            default: return -1;
+         }
+    }
+
+
+    /** Create an array and populate it with a test pattern.
+      *
+      * @param baseType  The base type of the array.  This is expected to
+      *                  be a numeric type, but this is not checked.
+      * @param dims      The desired dimensions.
+      * @return An array object populated with a simple test pattern.
+      */
+
+    public static Object generateArray(Class baseType, int[] dims) {
+
+        // Generate an array and populate it with a test pattern of
+        // data.
+
+        Object x = java.lang.reflect.Array.newInstance(baseType, dims);
+        testPattern(x,(byte)0);
+        return x;
+    }
+
+    /** Just create a simple pattern cycling through valid byte values.
+      * We use bytes because they can be cast to any other numeric type.
+      * @param o      The array in which the test pattern is to be set.
+      * @param start  The value for the first element.
+      */
+    public static byte testPattern(Object o, byte start) {
+
+        int[] dims = getDimensions(o);
+        if (dims.length > 1) {
+            for (int i=0; i < ((Object[])o).length; i += 1) {
+                start = testPattern(((Object[]) o)[i], start);
+            }
+
+        } else if (dims.length == 1) {
+            for (int i=0; i < dims[0]; i += 1) {
+                java.lang.reflect.Array.setByte(o, i, start);
+                start += 1;
+            }
+        }
+        return start;
+    }
+
+    /** Generate a description of an array (presumed rectangular).
+      * @param o The array to be described.
+      */
+
+    public static String arrayDescription(Object o) {
+
+          Class base = getBaseClass(o);
+          if (base == Void.TYPE) {
+              return "NULL";
+          }
+
+          int[] dims = getDimensions(o);
+
+          StringBuffer desc = new StringBuffer();
+
+          // Note that all instances Class describing a given class are
+          // the same so we can use == here.
+
+          if (base == Float.TYPE) {
+              desc.append("float");
+          } else if (base == Integer.TYPE) {
+              desc.append("int");
+          } else if (base == Double.TYPE) {
+              desc.append("double");
+          } else if (base == Short.TYPE) {
+              desc.append("short");
+          } else if (base == Long.TYPE) {
+              desc.append("long");
+          } else if (base == Character.TYPE) {
+              desc.append("char");
+          } else if (base == Byte.TYPE) {
+              desc.append("byte");
+          } else if (base == Boolean.TYPE) {
+              desc.append("boolean");
+          } else {
+              desc.append(base.getName());
+          }
+
+          if (dims != null) {
+              desc.append("[");
+              for (int i=0; i<dims.length; i += 1) {
+                  desc.append(""+dims[i]);
+                  if (i < dims.length-1) {
+                      desc.append(",");
+                  }
+              }
+              desc.append("]");
+          }
+          return new String(desc);
+     }
+
+     /** Examine the structure of an array in detail.
+       * @param o The array to be examined.
+       */
+     public static void examinePrimitiveArray(Object o) {
+         String className = o.getClass().getName();
+
+         // If we have a two-d array, or if the array is a one-d array
+         // of Objects, then recurse over the next dimension.  We handle
+         // Object specially because each element could itself be an array.
+         if (className.substring(0,2).equals("[[") ||
+             className.equals("[Ljava.lang.Object;") ) {
+             System.out.println("[");
+             for (int i=0; i< ((Object[])o).length; i += 1) {
+                 examinePrimitiveArray(((Object[])o)[i]);
+             }
+             System.out.print("]");
+         } else if (className.charAt(0) != '[') {
+             System.out.println(className);
+         } else {
+             System.out.println("["+java.lang.reflect.Array.getLength(o)+"]"+
+                                className.substring(1));
+         }
+     }
+
+     /** Given an array of arbitrary dimensionality return
+       * the array flattened into a single dimension.
+       * @param input The input array.
+       */
+
+     public static Object flatten(Object input) {
+
+         int[] dimens = getDimensions(input);
+         if (dimens.length <= 1) {
+             return input;
+         }
+         int size=1;
+         for (int i=0; i<dimens.length; i += 1) {
+             size *= dimens[i];
+         }
+
+         Object flat = Array.newInstance(getBaseClass(input), size);
+
+         if (size == 0) {
+             return flat;
+         }
+
+         int offset = 0;
+         doFlatten(input, flat, offset);
+         return flat;
+    }
+
+    /** This routine does the actually flattening of multi-dimensional
+      * arrays.
+      * @param input  The input array to be flattened.
+      * @param output The flattened array.
+      * @param offset The current offset within the output array.
+      * @return       The number of elements within the array.
+      */
+    protected static int doFlatten(Object input, Object output, int offset) {
+
+        String classname = input.getClass().getName();
+        if (classname.charAt(0) != '[') {
+            throw new RuntimeException("Attempt to flatten non-array");
+        }
+        int size = Array.getLength(input);
+
+        if (classname.charAt(1) != '[') {
+            System.arraycopy(input, 0, output, offset, size);
+            return size;
+        }
+        int total = 0;
+        Object[] xx = (Object[]) input;
+        for(int i=0; i < size; i += 1) {
+            int len = doFlatten(xx[i], output, offset+total);
+            total += len;
+        }
+        return total;
+    }
+
+    /** Curl an input array up into a multi-dimensional array.
+      *
+      * @param input The one dimensional array to be curled.
+      * @param dimens The desired dimensions
+      * @return The curled array.
+      */
+    public static Object curl(Object input, int[] dimens) {
+
+        String classname= input.getClass().getName();
+        if (classname.charAt(0) != '['  || classname.charAt(1) == '[') {
+            throw new RuntimeException("Attempt to curl non-1D array");
+        }
+
+        int size = Array.getLength(input);
+
+        int test = 1;
+        for (int i=0; i<dimens.length; i += 1) {
+            test *= dimens[i];
+        }
+
+        if (test != size) {
+            throw new RuntimeException("Curled array does not fit desired dimensions");
+        }
+
+        Class base = getBaseClass(input);
+
+        Object newArray = Array.newInstance(base, dimens);
+
+        int offset = 0;
+
+        doCurl(input, newArray, dimens, offset);
+        return newArray;
+
+    }
+
+    /** Do the curling of the 1-d to multi-d array.
+      * @param input  The 1-d array to be curled.
+      * @param output The multi-dimensional array to be filled.
+      * @param dimens The desired output dimensions.
+      * @param offset The current offset in the input array.
+      * @return       The number of elements curled.
+      */
+
+    protected static int doCurl(Object input, Object output,
+                                int[] dimens, int offset) {
+
+        if (dimens.length == 1) {
+            System.arraycopy(input, offset, output, 0, dimens[0]);
+            return dimens[0];
+        }
+
+        int total = 0;
+        int[] xdimens = new int[dimens.length-1];
+        for (int i=1; i<dimens.length; i +=1) {
+            xdimens[i-1] = dimens[i];
+        }
+
+        for (int i=0; i<dimens[0]; i += 1) {
+            total += doCurl(input, ((Object[]) output)[i], xdimens, offset+total);
+        }
+        return total;
+    }
+
+    /** Convert an array to a specified type.  This method supports conversions
+      * only among the primitive numeric types.
+      * @param array   A possibly multidimensional array to be converted.
+      * @param newType The desired output type.  This should be one of the
+      *                class descriptors for primitive numeric data, e.g., double.type.
+      */
+
+    public static Object convertArray(Object array, Class newType) {
+
+        String classname = array.getClass().getName();
+        if (classname.charAt(0) != '[') {
+            return null;
+        }
+        int dims = 1;
+        while (classname.charAt(dims) == '[') {
+            dims += 1;
+        }
+
+        Object converted;
+        if (dims > 1) {
+
+            Object[] xarray = (Object[]) array;
+            int[] dimens = new int[dims];
+            dimens[0] = xarray.length;  // Leave other dimensions at 0.
+
+
+            converted = Array.newInstance(newType, dimens);
+
+            for (int i=0; i<xarray.length; i += 1) {
+		Object temp  = convertArray(xarray[i], newType);
+                ((Object[])converted)[i] = temp;
+            }
+
+        } else {
+
+            // We have to handle 49 cases below.  Only
+            // the logical primitive type is ignored.
+
+            byte[] xbarr;
+            short[] xsarr;
+            char[] xcarr;
+            int[] xiarr;
+            long[] xlarr;
+            float[] xfarr;
+            double[] xdarr;
+            converted = null;
+
+            Class base = getBaseClass(array);
+
+            if (base == byte.class) {
+                byte[] barr = (byte[]) array;
+
+                if (newType == byte.class) {
+                    xbarr = new byte[barr.length];
+                    converted = xbarr;
+                    for (int i=0; i<barr.length; i += 1) xbarr[i] = barr[i];
+
+                } else if (newType == short.class) {
+                    xsarr = new short[barr.length];
+                    converted = xsarr;
+                    for (int i=0; i<barr.length; i += 1) xsarr[i] = barr[i];
+
+                } else if (newType == char.class) {
+                    xcarr = new char[barr.length];
+                    converted = xcarr;
+                    for (int i=0; i<barr.length; i += 1) xcarr[i] = (char) barr[i];
+
+                } else if (newType == int.class) {
+                    xiarr = new int[barr.length];
+                    converted = xiarr;
+                    for (int i=0; i<barr.length; i += 1) xiarr[i] = barr[i];
+
+                } else if (newType == long.class) {
+                    xlarr = new long[barr.length];
+                    converted = xlarr;
+                    for (int i=0; i<barr.length; i += 1) xlarr[i] = barr[i];
+
+                } else if (newType == float.class) {
+                    xfarr = new float[barr.length];
+                    converted = xfarr;
+                    for (int i=0; i<barr.length; i += 1) xfarr[i] = barr[i];
+
+                } else if (newType == double.class) {
+                    xdarr = new double[barr.length];
+                    converted = xdarr;
+                    for (int i=0; i<barr.length; i += 1) xdarr[i] = barr[i];
+                }
+
+            } else if (base == short.class) {
+                short[] sarr = (short[]) array;
+
+                if (newType == byte.class) {
+                    xbarr = new byte[sarr.length];
+                    converted = xbarr;
+                    for (int i=0; i<sarr.length; i += 1) xbarr[i] = (byte) sarr[i];
+
+                } else if (newType == short.class) {
+                    xsarr = new short[sarr.length];
+                    converted = xsarr;
+                    for (int i=0; i<sarr.length; i += 1) xsarr[i] = sarr[i];
+
+                } else if (newType == char.class) {
+                    xcarr = new char[sarr.length];
+                    converted = xcarr;
+                    for (int i=0; i<sarr.length; i += 1) xcarr[i] = (char) sarr[i];
+
+                } else if (newType == int.class) {
+                    xiarr = new int[sarr.length];
+                    converted = xiarr;
+                    for (int i=0; i<sarr.length; i += 1) xiarr[i] = sarr[i];
+
+                } else if (newType == long.class) {
+                    xlarr = new long[sarr.length];
+                    converted = xlarr;
+                    for (int i=0; i<sarr.length; i += 1) xlarr[i] = sarr[i];
+
+                } else if (newType == float.class) {
+                    xfarr = new float[sarr.length];
+                    converted = xfarr;
+                    for (int i=0; i<sarr.length; i += 1) xfarr[i] = sarr[i];
+
+                } else if (newType == double.class) {
+                    xdarr = new double[sarr.length];
+                    converted = xdarr;
+                    for (int i=0; i<sarr.length; i += 1) xdarr[i] = sarr[i];
+                }
+
+            } else if (base == char.class) {
+                char[] carr = (char[]) array;
+
+                if (newType == byte.class) {
+                    xbarr = new byte[carr.length];
+                    converted = xbarr;
+                    for (int i=0; i<carr.length; i += 1) xbarr[i] = (byte) carr[i];
+
+                } else if (newType == short.class) {
+                    xsarr = new short[carr.length];
+                    converted = xsarr;
+                    for (int i=0; i<carr.length; i += 1) xsarr[i] = (short) carr[i];
+
+                } else if (newType == char.class) {
+                    xcarr = new char[carr.length];
+                    converted = xcarr;
+                    for (int i=0; i<carr.length; i += 1) xcarr[i] = carr[i];
+
+                } else if (newType == int.class) {
+                    xiarr = new int[carr.length];
+                    converted = xiarr;
+                    for (int i=0; i<carr.length; i += 1) xiarr[i] = carr[i];
+
+                } else if (newType == long.class) {
+                    xlarr = new long[carr.length];
+                    converted = xlarr;
+                    for (int i=0; i<carr.length; i += 1) xlarr[i] = carr[i];
+
+                } else if (newType == float.class) {
+                    xfarr = new float[carr.length];
+                    converted = xfarr;
+                    for (int i=0; i<carr.length; i += 1) xfarr[i] = carr[i];
+
+                } else if (newType == double.class) {
+                    xdarr = new double[carr.length];
+                    converted = xdarr;
+                    for (int i=0; i<carr.length; i += 1) xdarr[i] = carr[i];
+                }
+
+            } else if (base == int.class) {
+                int[] iarr = (int[]) array;
+
+                if (newType == byte.class) {
+                    xbarr = new byte[iarr.length];
+                    converted = xbarr;
+                    for (int i=0; i<iarr.length; i += 1) xbarr[i] = (byte) iarr[i];
+
+                } else if (newType == short.class) {
+                    xsarr = new short[iarr.length];
+                    converted = xsarr;
+                    for (int i=0; i<iarr.length; i += 1) xsarr[i] = (short) iarr[i];
+
+                } else if (newType == char.class) {
+                    xcarr = new char[iarr.length];
+                    converted = xcarr;
+                    for (int i=0; i<iarr.length; i += 1) xcarr[i] = (char) iarr[i];
+
+                } else if (newType == int.class) {
+                    xiarr = new int[iarr.length];
+                    converted = xiarr;
+                    for (int i=0; i<iarr.length; i += 1) xiarr[i] = iarr[i];
+
+                } else if (newType == long.class) {
+                    xlarr = new long[iarr.length];
+                    converted = xlarr;
+                    for (int i=0; i<iarr.length; i += 1) xlarr[i] = iarr[i];
+
+                } else if (newType == float.class) {
+                    xfarr = new float[iarr.length];
+                    converted = xfarr;
+                    for (int i=0; i<iarr.length; i += 1) xfarr[i] = iarr[i];
+
+                } else if (newType == double.class) {
+                    xdarr = new double[iarr.length];
+                    converted = xdarr;
+                    for (int i=0; i<iarr.length; i += 1) xdarr[i] = iarr[i];
+                }
+
+
+            } else if (base == long.class) {
+                long[] larr = (long[]) array;
+
+                if (newType == byte.class) {
+                    xbarr = new byte[larr.length];
+                    converted = xbarr;
+                    for (int i=0; i<larr.length; i += 1) xbarr[i] = (byte) larr[i];
+
+                } else if (newType == short.class) {
+                    xsarr = new short[larr.length];
+                    converted = xsarr;
+                    for (int i=0; i<larr.length; i += 1) xsarr[i] = (short) larr[i];
+
+                } else if (newType == char.class) {
+                    xcarr = new char[larr.length];
+                    converted = xcarr;
+                    for (int i=0; i<larr.length; i += 1) xcarr[i] = (char) larr[i];
+
+                } else if (newType == int.class) {
+                    xiarr = new int[larr.length];
+                    converted = xiarr;
+                    for (int i=0; i<larr.length; i += 1) xiarr[i] = (int) larr[i];
+
+                } else if (newType == long.class) {
+                    xlarr = new long[larr.length];
+                    converted = xlarr;
+                    for (int i=0; i<larr.length; i += 1) xlarr[i] = larr[i];
+
+                } else if (newType == float.class) {
+                    xfarr = new float[larr.length];
+                    converted = xfarr;
+                    for (int i=0; i<larr.length; i += 1) xfarr[i] = (float) larr[i];
+
+                } else if (newType == double.class) {
+                    xdarr = new double[larr.length];
+                    converted = xdarr;
+                    for (int i=0; i<larr.length; i += 1) xdarr[i] = (double) larr[i];
+                }
+
+            } else if (base == float.class) {
+                float[] farr = (float[]) array;
+
+                if (newType == byte.class) {
+                    xbarr = new byte[farr.length];
+                    converted = xbarr;
+                    for (int i=0; i<farr.length; i += 1) xbarr[i] = (byte) farr[i];
+
+                } else if (newType == short.class) {
+                    xsarr = new short[farr.length];
+                    converted = xsarr;
+                    for (int i=0; i<farr.length; i += 1) xsarr[i] = (short) farr[i];
+
+                } else if (newType == char.class) {
+                    xcarr = new char[farr.length];
+                    converted = xcarr;
+                    for (int i=0; i<farr.length; i += 1) xcarr[i] = (char) farr[i];
+
+                } else if (newType == int.class) {
+                    xiarr = new int[farr.length];
+                    converted = xiarr;
+                    for (int i=0; i<farr.length; i += 1) xiarr[i] = (int) farr[i];
+
+                } else if (newType == long.class) {
+                    xlarr = new long[farr.length];
+                    converted = xlarr;
+                    for (int i=0; i<farr.length; i += 1) xlarr[i] = (long) farr[i];
+
+                } else if (newType == float.class) {
+                    xfarr = new float[farr.length];
+                    converted = xfarr;
+                    for (int i=0; i<farr.length; i += 1) xfarr[i] = farr[i];
+
+                } else if (newType == double.class) {
+                    xdarr = new double[farr.length];
+                    converted = xdarr;
+                    for (int i=0; i<farr.length; i += 1) xdarr[i] = farr[i];
+                }
+
+
+            } else if (base == double.class) {
+                double[] darr = (double[]) array;
+
+                if (newType == byte.class) {
+                    xbarr = new byte[darr.length];
+                    converted = xbarr;
+                    for (int i=0; i<darr.length; i += 1) xbarr[i] = (byte) darr[i];
+
+                } else if (newType == short.class) {
+                    xsarr = new short[darr.length];
+                    converted = xsarr;
+                    for (int i=0; i<darr.length; i += 1) xsarr[i] = (short) darr[i];
+
+                } else if (newType == char.class) {
+                    xcarr = new char[darr.length];
+                    converted = xcarr;
+                    for (int i=0; i<darr.length; i += 1) xcarr[i] = (char) darr[i];
+
+                } else if (newType == int.class) {
+                    xiarr = new int[darr.length];
+                    converted = xiarr;
+                    for (int i=0; i<darr.length; i += 1) xiarr[i] = (int) darr[i];
+
+                } else if (newType == long.class) {
+                    xlarr = new long[darr.length];
+                    converted = xlarr;
+                    for (int i=0; i<darr.length; i += 1) xlarr[i] = (long) darr[i];
+
+                } else if (newType == float.class) {
+                    xfarr = new float[darr.length];
+                    converted = xfarr;
+                    for (int i=0; i<darr.length; i += 1) xfarr[i] = (float) darr[i];
+
+                } else if (newType == double.class) {
+                    xdarr = new double[darr.length];
+                    converted = xdarr;
+                    for (int i=0; i<darr.length; i += 1) xdarr[i] = darr[i];
+                }
+            }
+        }
+        return converted;
+
+    }
+
+
+    /** Test and demonstrate the ArrayFuncs methods.
+      * @param args Unused.
+      */
+    public static void main(String[] args) {
+
+        int[][][]   test1 = new int[10][9][8];
+        boolean[][] test2 = new boolean[4][];
+        test2[0] = new boolean[5];
+        test2[1] = new boolean[4];
+        test2[2] = new boolean[3];
+        test2[3] = new boolean[2];
+
+        double[][] test3 = new double[10][20];
+        StringBuffer[][] test4 = new StringBuffer[3][2];
+
+        System.out.println("getBaseClass: test1: Base type of integer array is:"+ getBaseClass(test1));
+        System.out.println("getBaseLength: test1: "+getBaseLength(test1));
+        System.out.println("arrayDescription: test1: "+arrayDescription(test1));
+        System.out.println("computeSize: test1:   "+computeSize(test1));
+        System.out.println("getBaseClass: test2: Base type of boolean array is:"+ getBaseClass(test2));
+        System.out.println("getBaseLength: test2: "+getBaseLength(test2));
+        System.out.println("arrayDescription: test2: "+arrayDescription(test2));
+        System.out.println("computeSize: test2:   "+computeSize(test2));
+        System.out.println("getBaseClass: test3: Base type of double array is: "+ getBaseClass(test3));
+        System.out.println("getBaseLength: test3: "+getBaseLength(test3));
+        System.out.println("arrayDescription: test1: "+arrayDescription(test3));
+        System.out.println("computeSize: test3:   "+computeSize(test3));
+        System.out.println("getBaseClass: test4: Base type of StringBuffer array is: "+getBaseClass(test4));
+        System.out.println("getBaseLength: test4: "+getBaseLength(test4));
+        System.out.println("arrayDescription: test1: "+arrayDescription(test4));
+        System.out.println("computeSize: test4:   "+computeSize(test4));
+
+        System.out.println("");
+
+        System.out.println("arrayDescription: test1: Print a simple description of an array:"+arrayDescription(test1));
+        System.out.println("arrayDescription: test2: doesn't handle non-rectangular arrays:"+arrayDescription(test2));
+        System.out.println("");
+
+        System.out.println("examinePrimitiveArray: test1");
+        examinePrimitiveArray(test1);
+        System.out.println("");
+        System.out.println("examinePrimitiveArray: test2");
+        examinePrimitiveArray(test2);
+        System.out.println("");
+        System.out.println("    NOTE: this should show that test2 is not a rectangular array");
+        System.out.println("");
+
+        System.out.println("Using aggregates:");
+        Object[] agg = new Object[4];
+        agg[0] = test1;
+	agg[1] = test2;
+	agg[2] = test3;
+        agg[3] = test4;
+
+        System.out.println("getBaseClass: agg: Base class of aggregate is:"+getBaseClass(agg));
+        System.out.println("Size of aggregate is:" + computeSize(agg));
+        System.out.println("This ignores the array of StringBuffers");
+
+        testPattern(test1,(byte)0);
+        System.out.println("testPattern:");
+        for (int i=0; i < test1.length; i += 1) {
+            for (int j=0; j <test1[0].length; j += 1) {
+                for(int k=0; k<test1[0][0].length; k += 1) {
+                    System.out.print(" "+test1[i][j][k]);
+                }
+                System.out.println("");
+             }
+             System.out.println(""); // Double space....
+        }
+
+
+        int[][][] test5 = (int[][][]) deepClone(test1);
+        System.out.println("deepClone: copied array");
+        for (int i=0; i < test5.length; i += 1) {
+            for (int j=0; j <test5[0].length; j += 1) {
+                for(int k=0; k<test5[0][0].length; k += 1) {
+                    System.out.print(" "+test5[i][j][k]);
+                }
+                System.out.println("");
+             }
+             System.out.println(""); // Double space....
+        }
+
+
+        test5[2][2][2] = 99;
+        System.out.println("Demonstrating that this is a deep clone:"
+           +test5[2][2][2]+" "+test1[2][2][2]);
+
+
+
+        System.out.println("Flatten an array:");
+        int[] test6 = (int[]) flatten(test1);
+        System.out.println("    arrayDescription: test6:"+arrayDescription(test6));
+        for (int i=0; i<test6.length; i += 1) {
+             System.out.print(" "+test6[i]);
+             if (i > 0 && i%10 == 0) System.out.println("");
+        }
+        System.out.println("");
+
+        System.out.println("Curl an array, we'll reformat test1's data");
+        int[] newdims = {8,9,10};
+
+        int[][][] test7 = (int[][][]) curl(test6, newdims);
+
+        for (int i=0; i < test5.length; i += 1) {
+            for (int j=0; j <test5[0].length; j += 1) {
+                for(int k=0; k<test5[0][0].length; k += 1) {
+                    System.out.print(" "+test5[i][j][k]);
+                }
+                System.out.println("");
+             }
+             System.out.println(""); // Double space....
+        }
+
+        System.out.println("");
+        System.out.println("Test array conversions");
+
+        byte[][][] xtest1 = (byte[][][]) convertArray(test1, byte.class);
+        System.out.println("  xtest1 is of type:"+arrayDescription(xtest1));
+        System.out.println("   test1[3][3][3]="+test1[3][3][3]+"  xtest1="+xtest1[3][3][3]);
+
+        System.out.println("Converting float[700][700] to byte at:"+new java.util.Date() );
+        float[][] big=new float[700][700];
+        byte[][]  img = (byte[][]) convertArray(big, byte.class);
+        System.out.println("  img="+arrayDescription(img)+" at "+ new java.util.Date()) ;
+
+        System.out.println("End of tests");
+    }
+
+}
diff --git a/nom/tam/util/BufferedDataInputStream.java b/nom/tam/util/BufferedDataInputStream.java
new file mode 100644
index 0000000..adbc49d
--- /dev/null
+++ b/nom/tam/util/BufferedDataInputStream.java
@@ -0,0 +1,732 @@
+/** This class is intended for high performance I/O in scientific applications.
+  * It combines the functionality of the BufferedInputStream and the
+  * DataInputStream as well as more efficient handling of arrays.
+  * This minimizes the number of method calls that are required to
+  * read data.  Informal tests of this method show that it can
+  * be as much as 10 times faster than using a DataInputStream layered
+  * on a BufferedInputStream for writing large arrays.  The performance
+  * gain on scalars or small arrays will be less but there should probably
+  * never be substantial degradation of performance.
+  *
+  * One routine is added to the public interface of DataInput, readPrimitiveArray.
+  * This routine provides efficient protocols for writing arrays.  Note that
+  * they will create temporaries of a size equal to the array (if the array
+  * is one dimensional).
+  *
+  * Note that there is substantial duplication of code to minimize method
+  * invocations.  E.g., the floating point read routines read the data
+  * as integer values and then convert to float.  However the integer
+  * code is duplicated rather than invoked.
+  */
+
+// Member of the utilities package.
+
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+
+// What do we use in here?
+
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.EOFException;
+
+public class BufferedDataInputStream
+               extends BufferedInputStream
+               implements DataInput {
+
+
+private long bufferOffset=0;
+private int primitiveArrayCount;
+
+/** Use the BufferedInputStream constructor
+  */
+public BufferedDataInputStream(InputStream o) {
+    super(o);
+}
+/** Use the BufferedInputStream constructor
+  */
+public BufferedDataInputStream(InputStream o, int bufLength) {
+    super(o, bufLength);
+}
+
+
+public int read(byte[] buf, int offset, int len) throws IOException {
+
+
+    int total = 0;
+
+    // Ensure that the entire buffer is read.
+    while (len > 0) {
+        int xlen= super.read(buf, offset+total, len);
+        if (xlen <= 0) {
+            if (total == 0) {
+                throw new EOFException();
+            } else {
+                return total;
+            }
+        } else {
+            len -= xlen;
+            total += xlen;
+        }
+    }
+    return total;
+
+}
+
+public int read() throws IOException {
+    return super.read();
+}
+
+public long skip(long offset) throws IOException {
+
+    long total = 0;
+
+    while (offset > 0) {
+        long xoff = super.skip(offset);
+        if (xoff == 0) {
+            return total;
+        }
+        offset -= xoff;
+        total += xoff;
+    }
+    return total;
+}
+
+/** Read a boolean value
+  * @param b  The value to be written.  Externally true is represented as
+  *           a byte of 1 and false as a byte value of 0.
+  */
+public boolean readBoolean() throws IOException {
+
+    int b = read();
+    if (b == 1) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+public byte readByte() throws IOException {
+    return (byte) read();
+}
+
+public int readUnsignedByte() throws IOException {
+    return read() | 0x00ff;
+}
+
+public int readInt() throws IOException {
+    byte[] b = new byte[4];
+
+    if (read(b, 0, 4) < 4 ) {
+        throw new EOFException();
+    }
+    int i = b[0] << 24  | (b[1]&0xFF) << 16 | (b[2]&0xFF) << 8 | (b[3]&0xFF);
+    return i;
+}
+
+public short readShort() throws IOException {
+    byte[] b = new byte[2];
+
+    if (read(b, 0, 2) < 2) {
+        throw new EOFException();
+    }
+
+    short s = (short) (b[0] << 8 | (b[1]&0xFF));
+    return s;
+}
+
+public int readUnsignedShort() throws IOException {
+    byte[] b = new byte[2];
+
+    if (read(b,0,2) < 2) {
+        throw new EOFException();
+    }
+
+    return (b[0]&0xFF) << 8  |  (b[1]&0xFF);
+}
+
+
+public char readChar() throws IOException {
+    byte[] b = new byte[2];
+
+    if (read(b, 0, 2)  <  2) {
+        throw new EOFException();
+    }
+
+    char c = (char) (b[0] << 8 | (b[1]&0xFF));
+    return c;
+}
+
+public long readLong() throws IOException {
+    byte[] b = new byte[8];
+
+    // Let's use two int's as intermediarys so that we don't
+    // have a lot of casts of bytes to longs...
+    if (read(b, 0, 8) < 8) {
+        throw new EOFException();
+    }
+    int i1 =  b[0] << 24 | (b[1]&0xFF) << 16 | (b[2]&0xFF) << 8 | (b[3]&0xFF);
+    int i2 =  b[4] << 24 | (b[5]&0xFF) << 16 | (b[6]&0xFF) << 8 | (b[7]&0xFF);
+    return  (((long) i1) << 32) | (((long)i2)&0x00000000ffffffffL);
+}
+
+public float readFloat() throws IOException {
+
+    byte[] b = new byte[4];   // Repeat this from readInt to save method call.
+    if (read(b, 0, 4) < 4) {
+        throw new EOFException();
+    }
+
+    int i = b[0] << 24  | (b[1]&0xFF) << 16 | (b[2]&0xFF) << 8 | (b[3]&0xFF);
+    return Float.intBitsToFloat(i);
+
+}
+
+public double readDouble() throws IOException {
+
+    byte[] b = new byte[8];
+    if (read(b, 0, 8) < 8) {
+        throw new EOFException();
+    }
+
+    int i1 =  b[0] << 24 | (b[1]&0xFF) << 16 | (b[2]&0xFF) << 8 | (b[3]&0xFF);
+    int i2 =  b[4] << 24 | (b[5]&0xFF) << 16 | (b[6]&0xFF) << 8 | (b[7]&0xFF);
+
+    return Double.longBitsToDouble( ((long) i1) << 32 | ((long)i2&0x00000000ffffffffL) );
+}
+
+public void readFully(byte[] b) throws IOException {
+    readFully(b, 0, b.length);
+}
+
+public void readFully(byte[] b, int off, int len) throws IOException {
+
+    if (off < 0 || len < 0 || off+len > b.length) {
+        throw new IOException("Attempt to read outside byte array");
+    }
+
+    if (read(b, off, len) < len) {
+        throw new EOFException();
+    }
+}
+
+public int skipBytes(int toSkip) throws IOException {
+
+    if (skip(toSkip) < toSkip) {
+        throw new EOFException();
+    } else {
+        return toSkip;
+    }
+}
+
+
+public String readUTF() throws IOException{
+
+    // Punt on this one and use DataInputStream routines.
+    DataInputStream d = new DataInputStream(this);
+    return d.readUTF();
+
+}
+
+/** This routine uses the deprecated DataInputStream.readLine() method.
+  * However, we really want to simulate the behavior of that
+  * method so that's what we used.
+  * @deprecated
+  */
+public String readLine() throws IOException {
+    // Punt on this and use DataInputStream routines.
+    DataInputStream d = new DataInputStream(this);
+    return d.readLine();
+}
+
+/** This routine provides efficient reading of arrays of any primitive type.
+  * It is an error to invoke this method with an object that is not an array
+  * of some primitive type.  Note that there is no corresponding capability
+  * to writePrimitiveArray in BufferedDataOutputStream to read in an
+  * array of Strings.
+  *
+  * @param o  The object to be read.  It must be an array of a primitive type,
+  *           or an array of Object's.
+  */
+
+public int readPrimitiveArray(Object o) throws IOException {
+
+    // Note that we assume that only a single thread is
+    // doing a primitive Array read at any given time.  Otherwise
+    // primitiveArrayCount can be wrong and also the
+    // input data can be mixed up.  If this assumption isn't
+    // true we need to synchronize this call.
+
+    primitiveArrayCount = 0;
+    return primitiveArrayRecurse(o);
+}
+
+protected int primitiveArrayRecurse(Object o) throws IOException {
+
+    if (o == null) {
+	return primitiveArrayCount;
+    }
+
+    String className = o.getClass().getName();
+
+    if (className.charAt(0) != '[') {
+        throw new IOException("Invalid object passed to BufferedDataInputStream.readArray:"+className);
+    }
+
+    // Is this a multidimensional array?  If so process recursively.
+    if (className.charAt(1) == '[') {
+        for (int i=0; i < ((Object[])o).length; i += 1) {
+            primitiveArrayRecurse(((Object[])o)[i]);
+        }
+    } else {
+
+        // This is a one-d array.  Process it using our special functions.
+        switch (className.charAt(1)) {
+        case 'Z':
+             primitiveArrayCount += readBooleanArray((boolean[])o);
+             break;
+        case 'B':
+             int len=read((byte[])o, 0, ((byte[])o).length);
+             if (len < ((byte[])o).length){
+                 primitiveArrayCount += len;
+                 primitiveEOFThrower();
+             }
+             primitiveArrayCount += len;
+             break;
+        case 'C':
+             primitiveArrayCount += readCharArray((char[])o);
+             break;
+        case 'S':
+             primitiveArrayCount += readShortArray((short[])o);
+             break;
+        case 'I':
+             primitiveArrayCount += readIntArray((int[])o);
+             break;
+        case 'J':
+             primitiveArrayCount += readLongArray((long[])o);
+             break;
+        case 'F':
+             primitiveArrayCount += readFloatArray((float[])o);
+             break;
+        case 'D':
+             primitiveArrayCount += readDoubleArray((double[])o);
+             break;
+        case 'L':
+
+             // Handle an array of Objects by recursion.  Anything
+             // else is an error.
+             if (className.equals("[Ljava.lang.Object;") ) {
+                 for (int i=0; i < ((Object[])o).length; i += 1) {
+                      primitiveArrayRecurse( ((Object[]) o)[i] );
+                 }
+             } else {
+                 throw new IOException("Invalid object passed to BufferedDataInputStream.readArray: "+className);
+             }
+             break;
+        default:
+             throw new IOException("Invalid object passed to BufferedDataInputStream.readArray: "+className);
+        }
+    }
+    return primitiveArrayCount;
+}
+
+protected int readBooleanArray(boolean[] b) throws IOException {
+    byte[] bx = new byte[b.length];
+
+    if (read(bx, 0, bx.length) < bx.length) {
+        primitiveEOFThrower();
+    }
+
+    for (int i=0; i < b.length; i += 1) {
+        if (bx[i] == 1) {
+             b[i] = true;
+        } else {
+             b[i] = false;
+        }
+    }
+    return bx.length;
+}
+
+protected int readShortArray(short[] s) throws IOException {
+
+    if (s.length == 0) {
+      return 0;
+    }
+
+    byte[] b = new byte[2*s.length];
+
+    if (read(b, 0, b.length) < b.length) {
+        primitiveEOFThrower();
+    }
+    char c = (char) (b[0] << 8 | b[1]);
+
+    for(int i=0; i<s.length; i += 1) {
+        s[i] = (short) (b[2*i] << 8 | (b[2*i+1]&0xFF));
+    }
+    return b.length;
+}
+
+protected int readCharArray(char[] c) throws IOException {
+    byte[] b = new byte[2*c.length];
+    if (read(b, 0, b.length) < b.length) {
+        primitiveEOFThrower();
+    }
+
+    for(int i=0; i<c.length; i += 1) {
+        c[i] = (char) (b[2*i] << 8 | (b[2*i+1]&0xFF));
+    }
+    return b.length;
+}
+
+protected int readIntArray(int[] i) throws IOException {
+    byte[] b = new byte[4*i.length];
+
+    if (read(b, 0, b.length) < b.length) {
+        primitiveEOFThrower();
+    }
+
+
+    for (int ii=0; ii<i.length; ii += 1) {
+        i[ii] = b[4*ii] << 24 | (b[4*ii+1]&0xFF) << 16 | (b[4*ii+2]&0xFF) << 8 | (b[4*ii+3]&0xFF);
+    }
+    return b.length;
+}
+
+protected int readLongArray(long[] l) throws IOException {
+    byte[] b = new byte[8*l.length];
+
+    if (read(b, 0, b.length) < b.length) {
+        primitiveEOFThrower();
+    }
+
+    for (int i=0; i<l.length; i += 1) {
+         int i1  = b[8*i]   << 24 | (b[8*i+1]&0xFF) << 16 | (b[8*i+2]&0xFF) << 8 | (b[8*i+3]&0xFF);
+         int i2  = b[8*i+4] << 24 | (b[8*i+5]&0xFF) << 16 | (b[8*i+6]&0xFF) << 8 | (b[8*i+7]&0xFF);
+         l[i] = ( (long) i1) << 32 | ((long)i2&0x00000000FFFFFFFFL);
+    }
+    return b.length;
+}
+
+protected int readFloatArray(float[] f) throws IOException {
+
+    byte[] b = new byte[4*f.length];
+
+    if (read(b, 0, b.length) < b.length) {
+        primitiveEOFThrower();
+    }
+
+    for (int i=0; i<f.length; i += 1) {
+        int t = b[4*i] << 24 |
+               (b[4*i+1]&0xFF) << 16 |
+               (b[4*i+2]&0xFF) <<  8 |
+               (b[4*i+3]&0xFF);
+        f[i] = Float.intBitsToFloat(t);
+    }
+    return b.length;
+}
+
+protected int readDoubleArray(double[] d) throws IOException {
+    byte[] b = new byte[8*d.length];
+
+    if(read(b, 0, b.length) < b.length) {
+         primitiveEOFThrower();
+    }
+
+    for (int i=0; i<d.length; i += 1) {
+         int i1  = b[8*i]   << 24 | (b[8*i+1]&0xFF) << 16 | (b[8*i+2]&0xFF) << 8 | (b[8*i+3]&0xFF);
+         int i2  = b[8*i+4] << 24 | (b[8*i+5]&0xFF) << 16 | (b[8*i+6]&0xFF) << 8 | (b[8*i+7]&0xFF);
+         d[i] = Double.longBitsToDouble(
+                ((long) i1) << 32 | ((long)i2&0x00000000FFFFFFFFL));
+    }
+    return b.length;
+}
+
+protected void primitiveEOFThrower() throws EOFException {
+    throw new EOFException("EOF on primitive array read after "+primitiveArrayCount+" bytes.");
+}
+
+public void printStatus() {
+
+    System.out.println("BufferedDataInputStream:");
+    System.out.println("    count="+count);
+    System.out.println("      pos="+pos);
+}
+
+public String toString() {
+    return "BufferedDataInputStream[count="+count+",pos="+pos+"]";
+}
+
+
+/** This method is used to test and time the buffered data methods.
+  * Note that the BufferedDataOutputStream.main simply calls
+  * this method which is used to test both classes in conjunction.
+  */
+public static void main(String args[]) throws Exception {
+
+    // Test data.
+    boolean booleanScalar = true;
+    byte    byteScalar    = (byte) 0x12;
+    short   shortScalar   = (short) 0x1234;
+    char    charScalar    = 'p';
+    int     intScalar     = 0x12345678;
+    long    longScalar    = 0x1234567890abcdeL;
+    float   floatScalar   = (float)1.1;
+    double  doubleScalar  = 1.2;
+    String  stringScalar  = "This is a string";
+
+    boolean[] booleanArray = new boolean[50];
+    byte[][]  byteArray    = new byte[50][50];
+    short[]   shortArray   = new short[50];
+    char[][]  charArray    = new char[50][50];
+    int[][][] intArray     = new int[50][50][50];
+    long[]    longArray    = new long[50];
+    float[]   floatArray   = new float[50];
+    double[][]doubleArray  = new double[50][50];
+
+
+    for (int i=0; i<50; i += 1) {
+        int sign = 1;
+        if (i%2 > 0) {
+            sign = -1;
+        }
+        booleanArray[i] = (i%2 == 1);
+        shortArray[i] = (short) (sign * i|0x1234);
+        longArray[i] = (long) sign * (i|0x1234567890abcdeL);
+        floatArray[i] = i+sign*(float)2.33;
+        for (int j=0; j<50; j += 1) {
+            byteArray[i][j] = (byte) (i-j);
+            charArray[i][j] = (char) (i+j);
+            doubleArray[i][j] = sign*i*j*3.97;
+            for (int k=0; k<50; k += 1) {
+                intArray[i][j][k] = sign*(i*j + i*k + j*k + 0x1234567);
+            }
+        }
+    }
+
+    // Write and read back data.
+
+    BufferedDataOutputStream o = new nom.tam.util.BufferedDataOutputStream (
+                                     new java.io.FileOutputStream("BufferedData.test") );
+
+    o.writeBoolean(booleanScalar);
+    o.writePrimitiveArray(booleanArray);
+
+    o.writeByte(byteScalar);
+
+    // Write the byte array three different ways.
+    o.writePrimitiveArray(byteArray);
+    for (int i=0; i<50; i+= 1) {
+        o.write(byteArray[i]);
+    }
+
+    for(int i=0; i<50; i += 1) {
+        o.write(byteArray[i],  0, 25);
+        o.write(byteArray[i], 25, 25);
+    }
+
+    o.writeShort(shortScalar);
+    o.writePrimitiveArray(shortArray);
+
+    o.writeChar(charScalar);
+    o.writePrimitiveArray(charArray);
+
+    o.writeInt(intScalar);
+    o.writePrimitiveArray(intArray);
+
+    o.writeLong(longScalar);
+    o.writePrimitiveArray(longArray);
+
+    o.writeFloat(floatScalar);
+    o.writePrimitiveArray(floatArray);
+
+    o.writeDouble(doubleScalar);
+    o.writePrimitiveArray(doubleArray);
+
+    o.flush();
+    o.close();
+
+    o = null;
+
+
+    BufferedDataInputStream in = new BufferedDataInputStream(
+             new java.io.FileInputStream("BufferedData.test") );
+
+
+
+    System.out.println("Functionality tests (Note String I/O not checked)");
+    System.out.println("");
+    passes(booleanScalar == in.readBoolean(), "boolean scalar");
+    boolean[] ba = new boolean[50];
+    in.readPrimitiveArray(ba);
+    passes(ba[0] == booleanArray[0], "boolean array (start)");
+    passes(ba[49] == booleanArray[49], "boolean array (end)");
+    passes(ba[22] == booleanArray[22], "boolean array (middle)");
+
+    passes(byteScalar == in.readByte(), "byte scalar");
+
+
+    byte[][] binp1 = new byte[50][50];
+    byte[][] binp2 = new byte[50][50];
+    byte[][] binp3 = new byte[50][50];
+    // read the byte array three different ways --
+    // we deliberately do this differently than above.
+    for(int i=0; i<50; i += 1) {
+        in.read(binp3[i],  0, 25);
+        in.read(binp3[i], 25, 25);
+    }
+    for (int i=0; i<50; i+= 1) {
+        in.readFully(binp2[i]);
+    }
+    in.readPrimitiveArray(binp1);
+
+    passes(binp1[0][0] == byteArray[0][0], "byte array(start-method1)");
+    passes(binp1[49][49] == byteArray[49][49], "byte array(end-method1)");
+    passes(binp1[22][22] == byteArray[22][22], "byte array(middle-method1");
+    passes(binp2[0][0] == byteArray[0][0], "byte array(start-method2)");
+    passes(binp2[49][49] == byteArray[49][49], "byte array(end-method2)");
+    passes(binp2[22][22] == byteArray[22][22], "byte array(middle-method2");
+    passes(binp3[0][0] == byteArray[0][0], "byte array(start-method3)");
+    passes(binp3[49][49] == byteArray[49][49], "byte array(end-method3)");
+    passes(binp3[22][22] == byteArray[22][22], "byte array(middle-method3");
+
+
+    passes(shortScalar == in.readShort(), "short scalar");
+
+    short[] sa = new short[50];
+    in.readPrimitiveArray(sa);
+
+    passes(sa[0] == shortArray[0], "short array (start)");
+    passes(sa[49] == shortArray[49], "short array (end)");
+    passes(sa[22] == shortArray[22], "short array (middle)");
+
+    passes(charScalar == in.readChar(), "char scalar");
+
+    char[][] ca = new char[50][50];
+    in.readPrimitiveArray(ca);
+
+    passes(ca[0][0] == charArray[0][0], "char array (start)");
+    passes(ca[49][49] == charArray[49][49], "char array (end)");
+    passes(ca[22][22] == charArray[22][22], "char array (middle)");
+
+    passes(intScalar == in.readInt(), "int scalar");
+    int[][][] ia = new int[50][50][50];
+    in.readPrimitiveArray(ia);
+    passes(ia[0][0][0] == intArray[0][0][0], "int array (start)");
+    passes(ia[49][49][49] == intArray[49][49][49], "int array (end)");
+    passes(ia[22][22][22] == intArray[22][22][22], "int array (middle)");
+
+
+    passes (longScalar == in.readLong(), "long scalar");
+    long[] la = new long[50];
+    in.readPrimitiveArray(la);
+    passes(la[0] == longArray[0], "long array (start)");
+    passes(la[49] == longArray[49], "long array (end)");
+    passes(la[22] == longArray[22], "long array (middle)");
+
+    passes (floatScalar == in.readFloat(), "float scalar");
+    float[] fa = new float[50];
+    in.readPrimitiveArray(fa);
+    passes(fa[0] == floatArray[0], "float array (start)");
+    passes(fa[49] == floatArray[49], "float array (end)");
+    passes(fa[22] == floatArray[22], "float array (middle)");
+
+    passes(doubleScalar == in.readDouble(), "double scalar");
+    double[][] da = new double[50][50];
+    in.readPrimitiveArray(da);
+    passes(da[0][0] == doubleArray[0][0], "double array (start)");
+    passes(da[49][49] == doubleArray[49][49], "double array (end)");
+    passes(da[22][22] == doubleArray[22][22], "double array (middle)");
+
+    in = null;
+
+    System.out.println("");
+    System.out.println("Timing test:  Write and read an 800x800 int array");
+    System.out.println("");
+    System.out.println("Initializing array");
+    int[][] data = new int[800][800];
+    int[][] indata = new int[800][800];
+
+    for (int i=0; i<data.length; i += 1) {
+         for (int j=0; j<data[0].length; j += 1) {
+              data[i][j] = i*j * (i-j);
+         }
+    }
+
+
+    System.out.println("");
+
+    System.out.println("Using DataXputStream(BufferedXputStream) at "+new java.util.Date());
+    java.io.DataOutputStream ds = new java.io.DataOutputStream(
+                              new java.io.BufferedOutputStream(
+                                  new java.io.FileOutputStream("test_std.data")) );
+    for (int i=0; i<800; i += 1) {
+        for (int j=0; j<800; j += 1) {
+             ds.writeInt(data[i][j]);
+        }
+    }
+
+    ds.flush();
+    ds.close();
+
+    ds = null;
+
+    System.out.println("                          Finished write at:"+new java.util.Date());
+
+    java.io.DataInputStream is =  new java.io.DataInputStream(
+                              new BufferedInputStream(
+                                  new java.io.FileInputStream("test_std.data")) );
+
+    for (int i=0; i<800; i += 1) {
+        for (int j=0; j<800; j += 1) {
+            indata[i][j] = is.readInt();
+        }
+    }
+    is = null;
+    System.out.println("                          Finished read at: "+new java.util.Date());
+
+    System.out.println("");
+    System.out.println("Using BufferedDataXputStream at             "+new java.util.Date());
+
+    BufferedDataOutputStream ob = new nom.tam.util.BufferedDataOutputStream(
+                                      new java.io.FileOutputStream("test_bd.data"));
+
+    ob.writePrimitiveArray(data);
+    ob.flush();
+    ob.close();
+
+    ob = null;
+
+    System.out.println("                          Finished write at:"+new java.util.Date());
+
+
+    BufferedDataInputStream ib = new BufferedDataInputStream(
+                                     new java.io.FileInputStream("test_bd.data"));
+    ib.readPrimitiveArray(indata);
+    ib = null;
+    System.out.println("                          Finished read at: " + new java.util.Date());
+
+}
+
+private static void passes (boolean status, String msg) {
+
+     System.out.print(msg+":");
+     if (msg.length() < 30) {
+         System.out.print("                              ".substring(0,30-msg.length()));
+     }
+     if (status) {
+          System.out.println(" passes");
+     } else {
+          System.out.println(" fails");
+     }
+}
+
+}
diff --git a/nom/tam/util/BufferedDataOutputStream.java b/nom/tam/util/BufferedDataOutputStream.java
new file mode 100644
index 0000000..55707b4
--- /dev/null
+++ b/nom/tam/util/BufferedDataOutputStream.java
@@ -0,0 +1,393 @@
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+
+// What do we use in here?
+
+import java.io.OutputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/** This class is intended for high performance I/O in scientific applications.
+  * It combines the functionality of the BufferedOutputStream and the
+  * DataOutputStream as well as more efficient handling of arrays.
+  * This minimizes the number of method calls that are required to
+  * write data.  Informal tests of this method show that it can
+  * be as much as 10 times faster than using a DataOutputStream layered
+  * on a BufferedOutputStream for writing large arrays.  The performance
+  * gain on scalars or small arrays will be less but there should probably
+  * never be substantial degradation of performance.
+  *
+  * One routine is added to the public interface of DataOutput, writePrimitiveArray.
+  * This routine provides efficient protocols for writing arrays.
+  *
+  * Note that there is substantial duplication of code to minimize method
+  * invocations.
+  */
+
+public class BufferedDataOutputStream
+               extends BufferedOutputStream
+               implements DataOutput {
+
+/** Use the BufferedOutputStream constructor
+  * @param o An open output stream.
+  */
+public BufferedDataOutputStream(OutputStream o) {
+    super(o);
+}
+/** Use the BufferedOutputStream constructor
+  * @param o           An open output stream.
+  * @param bufLength   The buffer size.
+  */
+public BufferedDataOutputStream(OutputStream o, int bufLength) {
+    super(o, bufLength);
+}
+
+
+/** Write a boolean value
+  * @param b  The value to be written.  Externally true is represented as
+  *           a byte of 1 and false as a byte value of 0.
+  */
+public void writeBoolean(boolean b) throws IOException {
+
+    if (b) {
+        write( (byte) 1);
+    } else {
+        write( (byte) 0);
+    }
+}
+
+/** Write a byte value.
+  */
+public void writeByte(int b) throws IOException {
+    write((byte) b);
+}
+
+/** Write an integer value.
+  */
+public void writeInt(int i) throws IOException {
+    byte[] b = new byte[4];
+
+    b[0] = (byte) (i >>> 24);
+    b[1] = (byte) (i >>> 16);
+    b[2] = (byte) (i >>>  8);
+    b[3] = (byte)  i;
+    write(b, 0, 4);
+}
+
+/** Write a short value.
+  */
+public void writeShort(int s) throws IOException {
+    byte[] b = new byte[2];
+
+    b[0] = (byte) (s >>> 8);
+    b[1] = (byte)  s;
+
+    write(b, 0, 2);
+}
+
+/** Write a char value.
+  */
+public void writeChar(int c) throws IOException {
+    byte[] b = new byte[2];
+    b[0] = (byte) (c >>> 8);
+    b[1] = (byte)  c;
+
+    write(b, 0, 2);
+}
+
+/** Write a long value.
+  */
+public void writeLong(long l) throws IOException {
+    byte[] b = new byte[8];
+
+    b[0] = (byte) (l >>> 56);
+    b[1] = (byte) (l >>> 48);
+    b[2] = (byte) (l >>> 40);
+    b[3] = (byte) (l >>> 32);
+    b[4] = (byte) (l >>> 24);
+    b[5] = (byte) (l >>> 16);
+    b[6] = (byte) (l >>>  8);
+    b[7] = (byte)  l;
+
+    write(b, 0, 8);
+}
+
+/** Write a float value.
+  */
+public void writeFloat(float f) throws IOException {
+
+    int i = Float.floatToIntBits(f);
+
+    byte[] b = new byte[4];   // Repeat this from writeInt to save method call.
+
+    b[0] = (byte) (i >>> 24);
+    b[1] = (byte) (i >>> 16);
+    b[2] = (byte) (i >>>  8);
+    b[3] = (byte)  i;
+
+    write(b, 0, 4);
+}
+
+/** Write a double value.
+  */
+public void writeDouble(double d) throws IOException {
+
+    long l = Double.doubleToLongBits(d);
+    byte[] b = new byte[8];
+
+    b[0] = (byte) (l >>> 56);
+    b[1] = (byte) (l >>> 48);
+    b[2] = (byte) (l >>> 40);
+    b[3] = (byte) (l >>> 32);
+    b[4] = (byte) (l >>> 24);
+    b[5] = (byte) (l >>> 16);
+    b[6] = (byte) (l >>>  8);
+    b[7] = (byte)  l;
+
+    write(b, 0, 8);
+}
+
+/** Write a string using the local protocol to convert char's to bytes.
+  *
+  * @param s   The string to be written.
+  */
+public void writeBytes(String s) throws IOException {
+    write(s.getBytes(),0,s.length());
+}
+
+/** Write a string as an array of chars.
+  */
+public void writeChars(String s) throws IOException {
+
+    int len = s.length();
+    char c;
+    byte[] b = new byte[2*len];
+
+    for (int i=0; i<len; i += 1) {
+        c = s.charAt(i);
+        b[2*i] = (byte) (c>>8);
+        b[2*i+1] = (byte) c;
+    }
+
+    write(b, 0, 2*len);
+}
+
+/** Write a string as a UTF.  Note that this class does not
+  * handle this situation efficiently since it creates
+  * new DataOutputStream to handle each call.
+  */
+public void writeUTF(String s) throws IOException{
+
+    // Punt on this one and use standard routines.
+    DataOutputStream d = new DataOutputStream(this);
+    d.writeUTF(s);
+    d.flush();
+}
+
+/** This routine provides efficient writing of arrays of any primitive type.
+  * The String class is also handled but it is an error to invoke this
+  * method with an object that is not an array of these types.  If the
+  * array is multidimensional, then it calls itself recursively to write
+  * the entire array.  Strings are written using the standard
+  * 1 byte format (i.e., as in writeBytes).
+  *
+  * If the array is an array of objects, then writePrimitiveArray will
+  * be called for each element of the array.
+  *
+  * @param o  The object to be written.  It must be an array of a primitive
+  *           type, Object, or String.
+  */
+public void writePrimitiveArray(Object o) throws IOException {
+    String className = o.getClass().getName();
+
+    if (className.charAt(0) != '[') {
+        throw new IOException("Invalid object passed to BufferedDataOutputStream.writeArray:"+className);
+    }
+
+    // Is this a multidimensional array?  If so process recursively.
+    if (className.charAt(1) == '[') {
+        for (int i=0; i < ((Object[])o).length; i += 1) {
+            writePrimitiveArray(((Object[])o)[i]);
+        }
+    } else {
+
+        // This is a one-d array.  Process it using our special functions.
+        switch (className.charAt(1)) {
+        case 'Z': writeBooleanArray((boolean[])o);
+             break;
+        case 'B': write((byte[])o, 0, ((byte[])o).length);
+             break;
+        case 'C': writeCharArray((char[])o);
+             break;
+        case 'S': writeShortArray((short[])o);
+             break;
+        case 'I': writeIntArray((int[])o);
+             break;
+        case 'J': writeLongArray((long[])o);
+             break;
+        case 'F': writeFloatArray((float[])o);
+             break;
+        case 'D': writeDoubleArray((double[])o);
+             break;
+        case 'L':
+
+             // Handle two exceptions: an array of strings, or an
+             // array of objects. .
+             if (className.equals("[Ljava.lang.String;") ) {
+                 writeStringArray((String[])o);
+             } else if (className.equals("[Ljava.lang.Object;")) {
+                 for (int i=0; i< ((Object[])o).length; i += 1) {
+                     writePrimitiveArray(((Object[])o)[i]);
+                 }
+             } else {
+                 throw new IOException("Invalid object passed to BufferedDataOutputStream.writeArray: "+className);
+             }
+             break;
+        default:
+             throw new IOException("Invalid object passed to BufferedDataOutputStream.writeArray: "+className);
+        }
+    }
+
+}
+
+/** Write an array of booleans.
+  */
+protected void writeBooleanArray(boolean[] b) throws IOException {
+    byte[] bx = new byte[b.length];
+    for (int i=0; i<b.length; i += 1) {
+        if (b[i]) {
+             bx[i] = 1;
+        } else {
+             bx[i] = 0;
+        }
+    }
+    write(bx, 0, bx.length);
+}
+
+/** Write an array of shorts.
+  */
+protected void writeShortArray(short[] s) throws IOException {
+    byte[] b = new byte[2*s.length];
+
+    for(int i=0; i<s.length; i += 1) {
+        int t = s[i];
+        b[2*i] = (byte) (t>>8);
+        b[2*i + 1] = (byte) t;
+    }
+    write(b, 0, b.length);
+}
+
+/** Write an array of char's.
+  */
+protected void writeCharArray(char[] c) throws IOException {
+    byte[] b = new byte[2*c.length];
+
+    for(int i=0; i<c.length; i += 1) {
+        int t = c[i];
+        b[2*i] = (byte) (t>>8);
+        b[2*i + 1] = (byte) t;
+    }
+    write(b, 0, b.length);
+}
+
+/** Write an array of int's.
+  */
+protected void writeIntArray(int[] i) throws IOException {
+    byte[] b = new byte[4*i.length];
+
+    for (int ii=0; ii<i.length; ii += 1) {
+        int t = i[ii];
+        b[4*ii]   = (byte)(t >>> 24);
+        b[4*ii+1] = (byte)(t >>> 16);
+        b[4*ii+2] = (byte)(t >>>  8);
+        b[4*ii+3] = (byte) t;
+    }
+
+    write(b, 0, b.length);
+}
+
+/** Write an array of longs.
+  */
+protected void writeLongArray(long[] l) throws IOException {
+    byte[] b = new byte[8*l.length];
+
+    for (int i=0; i<l.length; i += 1) {
+         long t = l[i];
+         b[8*i]   = (byte)(t >>> 56);
+         b[8*i+1] = (byte)(t >>> 48);
+         b[8*i+2] = (byte)(t >>> 40);
+         b[8*i+3] = (byte)(t >>> 32);
+         b[8*i+4] = (byte)(t >>> 24);
+         b[8*i+5] = (byte)(t >>> 16);
+         b[8*i+6] = (byte)(t >>>  8);
+         b[8*i+7] = (byte) t;
+    }
+    write(b, 0, b.length);
+}
+
+/** Write an array of floats.
+  */
+protected void writeFloatArray(float[] f) throws IOException {
+
+    byte[] b = new byte[4*f.length];
+
+    for (int i=0; i<f.length; i += 1) {
+        int t = Float.floatToIntBits(f[i]);
+        b[4*i]   = (byte)(t >>> 24);
+        b[4*i+1] = (byte)(t >>> 16);
+        b[4*i+2] = (byte)(t >>>  8);
+        b[4*i+3] = (byte) t;
+    }
+
+    write(b, 0, b.length);
+}
+
+/** Write an array of doubles.
+  */
+protected void writeDoubleArray(double[] d) throws IOException {
+    byte[] b = new byte[8*d.length];
+
+    for (int i=0; i<d.length; i += 1) {
+         long t = Double.doubleToLongBits(d[i]);
+         b[8*i]   = (byte)(t >>> 56);
+         b[8*i+1] = (byte)(t >>> 48);
+         b[8*i+2] = (byte)(t >>> 40);
+         b[8*i+3] = (byte)(t >>> 32);
+         b[8*i+4] = (byte)(t >>> 24);
+         b[8*i+5] = (byte)(t >>> 16);
+         b[8*i+6] = (byte)(t >>>  8);
+         b[8*i+7] = (byte) t;
+    }
+    write(b, 0, b.length);
+}
+
+/** Write an array of Strings -- equivalent to calling writeBytes for each string.
+  */
+protected void writeStringArray(String[] s) throws IOException {
+
+    // Don't worry about buffering this specially since the
+    // strings may be of differing lengths.
+
+    for (int i=0; i<s.length; i += 1) {
+        writeBytes(s[i]);
+    }
+}
+
+
+/** Test this class */
+
+public static void main(String[] args) throws Exception {
+
+    // Call the test routines in BufferedDataInputStream.main.
+    BufferedDataInputStream.main(args);
+}
+
+}
diff --git a/nom/tam/util/ColumnTable.java b/nom/tam/util/ColumnTable.java
new file mode 100644
index 0000000..7ae2a4f
--- /dev/null
+++ b/nom/tam/util/ColumnTable.java
@@ -0,0 +1,793 @@
+package nom.tam.util;
+
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  */
+import  java.io.*;
+import  java.lang.reflect.Array;
+
+/** A data table is conventionally considered to consist of rows and
+  * columns, where the structure within each column is constant, but
+  * different columns may have different structures.  I.e., structurally
+  * columns may differ but rows are identical.
+  * Typically tabular data is usually stored in row order which can
+  * make it extremely difficult to access efficiently using Java.
+  * This class provides efficient
+  * access to data which is stored in row order and allows users to
+  * get and set the elements of the table.
+  * The table can consist only of arrays of primitive types.
+  * Data stored in column order can
+  * be efficiently read and written using the
+  * BufferedDataXputStream classes.
+  *
+  * The table is represented entirely as a set of one-dimensional primitive
+  * arrays.  For a given column, a row consists of some number of
+  * contiguous elements of the array.  Each column is required to have
+  * the same number of rows.
+  */
+
+public class ColumnTable implements DataTable {
+
+
+    /** The columns to be read/written */
+    private Object[] arrays;
+
+    /** The number of elements in a row for each column */
+    private int[] sizes;
+
+    /** The number of rows */
+    private int nrow;
+
+    /** The number or rows to read/write in one I/O. */
+    private int chunk;
+
+    /** The size of a row in bytes */
+    private int rowSize;
+
+    /** The base type of each row (using the second character
+      * of the [x class names of the arrays.
+      */
+    private char[] types;
+    private Class[] bases;
+
+    // The following arrays are used to avoid having to check
+    // casts during the I/O loops.
+    // They point to elements of arrays.
+    private byte[][]      bytePointers;
+    private short[][]     shortPointers;
+    private int[][]       intPointers;
+    private long[][]      longPointers;
+    private float[][]     floatPointers;
+    private double[][]    doublePointers;
+    private char[][]      charPointers;
+    private boolean[][]   booleanPointers;
+
+
+    /** Create the object after checking consistency.
+      * @param arrays  An array of one-d primitive arrays.
+      * @param sizes   The number of elements in each row
+      *                for the corresponding column
+      */
+    public ColumnTable(Object[] arrays, int[] sizes) throws TableException {
+        setup(arrays, sizes);
+    }
+
+    /** Actually perform the initialization.
+      */
+    protected void setup(Object[] arrays, int[] sizes) throws TableException {
+
+        checkArrayConsistency(arrays, sizes);
+        getNumberOfRows();
+        initializePointers();
+
+    }
+
+    /** Get the number of rows in the table.
+      */
+    public int getNrow() {
+        return nrow;
+    }
+
+    /** Get the number of columns in the table.
+      */
+    public int getNcol() {
+        return arrays.length;
+    }
+
+
+    /** Get a particular column.
+      * @param col The column desired.
+      * @return an object containing the column data desired.
+      *         This will be an instance of a 1-d primitive array.
+      */
+    public Object getColumn(int col) {
+        return arrays[col];
+    }
+
+    /** Set the values in a particular column.
+      * The new values must match the old in length but not necessarily in type.
+      * @param col The column to modify.
+      * @param newColumn The new column data.  This should be a primitive array.
+      * @exception TableException Thrown when the new data is not commenserable with
+      *                           informaiton in the table.
+      */
+    public void setColumn(int col, Object newColumn) throws TableException {
+        arrays[col] = newColumn;
+        setup(arrays, sizes);
+    }
+
+    /** Get a element of the table.
+      * @param row The row desired.
+      * @param col The column desired.
+      * @return A primitive array containing the information.  Note
+      *         that an array will be returned even if the element
+      *         is a scalar.
+      */
+    public Object getElement(int row, int col) {
+
+        Object x = Array.newInstance(bases[col], sizes[col]);
+        System.arraycopy(arrays[col], sizes[col]*row, x, 0, sizes[col]);
+        return x;
+    }
+
+    /** Modify an element of the table.
+      * @param row The row containing the element.
+      * @param col The column containing the element.
+      * @param x   The new datum.  This should be 1-d primitive
+      *            array.
+      * @exception TableException Thrown when the new data
+      *                           is not of the same type as
+      *                           the data it replaces.
+      */
+    public void setElement(int row, int col, Object x)
+                           throws TableException {
+
+        String classname = x.getClass().getName();
+
+        if (!classname.equals("["+types[col])) {
+            throw new TableException("setElement: Incompatible element type");
+        }
+
+        if (Array.getLength(x) != sizes[col]) {
+            throw new TableException("setElement: Incompatible element size");
+        }
+
+        System.arraycopy(x, 0, arrays[col], sizes[col]*row, sizes[col]);
+    }
+
+    /** Get a row of data.
+      * @param The row desired.
+      * @return An array of objects each containing a primitive array.
+      */
+    public Object getRow(int row) {
+
+        Object[] x = new Object[arrays.length];
+        for (int col=0; col<arrays.length; col += 1) {
+             x[col] = getElement(row, col);
+        }
+        return x;
+    }
+
+    /** Modify a row of data.
+      * @param row The row to be modified.
+      * @param x   The data to be modified.  This should be an
+      *            array of objects.  It is described as an Object
+      *            here since other table implementations may
+      *            use other methods to store the data (e.g.,
+      *            @see ColumnTable.getColumn.
+      */
+    public void setRow(int row, Object x) throws TableException {
+
+        if (! (x instanceof Object[])) {
+            throw new TableException("setRow: Incompatible row");
+        }
+
+        for (int col=0; col<arrays.length; col += 1) {
+            setElement(row, col, ((Object[]) x)[col]);
+        }
+    }
+
+    /** Check that the columns and sizes are consistent.
+      * Inconsistencies include:
+      * <ul>
+      * <li> arrays and sizes have different lengths.
+      * <li> an element of arrays is not a primitive array.
+      * <li> the size of an array is not divisible by the sizes entry.
+      * <li> the number of rows differs for the columns.
+      * </ul>
+      * @param arrays The arrays defining the columns.
+      * @param sizes  The number of elements in each row for the column.
+      */
+    protected void checkArrayConsistency(Object[] arrays, int[] sizes)
+                                         throws TableException {
+
+        // This routine throws an error if it detects an inconsistency
+        // between the arrays being read in.
+
+        // First check that the lengths of the two arrays are the same.
+        if (arrays.length != sizes.length) {
+          throw new TableException ("readArraysAsColumns: Incompatible arrays and sizes.");
+        }
+
+        // Now check that we'll fill up all of the arrays exactly.
+        int ratio = 0;
+        int rowSize = 0;
+
+        this.types = new char[arrays.length];
+        this.bases = new Class[arrays.length];
+
+        // Check for a null table.
+        boolean nullTable = true;
+
+        for (int i=0; i<arrays.length; i += 1) {
+            if (Array.getLength(arrays[i]) > 0) {
+                nullTable = false;
+                break;
+            }
+        }
+
+
+        for (int i=0; i<arrays.length; i += 1) {
+
+            String classname = arrays[i].getClass().getName();
+
+            if (classname.charAt(0) != '['  || classname.length() != 2) {
+                throw new TableException("Non-primitive array");
+            }
+
+            int thisSize = Array.getLength(arrays[i]);
+            if (thisSize == 0 && sizes[i] == 0) {
+                continue;   // It's allowed to have zero length arrays if we don't
+                            // ask to put any data in them.
+            }
+
+            // ...but if both are not 0, then neither can be 0 individually.
+            if ( (thisSize == 0 || sizes[i] <= 0) && !nullTable) {
+                throw new TableException("Invalid size for array: index="+i);
+            }
+
+            // The row size must evenly divide the size of the array.
+            if (thisSize % sizes[i] != 0) {
+                throw new TableException("Row size does not divide array: index="+i);
+            }
+
+            // Finally the ratio of sizes must be the same for all rows.
+            if (sizes[i] > 0) {
+                int thisRatio = thisSize/sizes[i];
+
+                if (ratio != 0 && (thisRatio != ratio)) {
+                    throw new TableException("Different number of rows in different columns");
+                }
+
+                ratio = thisRatio;
+            }
+
+            rowSize += sizes[i]*ArrayFuncs.getBaseLength(arrays[i]);
+            types[i] = classname.charAt(1);
+            bases[i] = ArrayFuncs.getBaseClass(arrays[i]);
+        }
+
+        this.nrow = ratio;
+        this.rowSize = rowSize;
+        this.arrays = arrays;
+        this.sizes = sizes;
+    }
+
+    /** Calculate the number of rows to read/write at a time.
+      * @param rowSize The size of a row in bytes.
+      * @param nrows   The number of rows in the table.
+      */
+    protected void getNumberOfRows() {
+
+        int bufSize=65536;
+
+        // If a row is larger than bufSize, then read one row at a time.
+        if (rowSize == 0) {
+            this.chunk = 0;
+
+        } else if (rowSize > bufSize) {
+            this.chunk = 1;
+
+        // If the entire set isn't too big, just read it all.
+        } else if (bufSize/rowSize >= nrow) {
+            this.chunk = nrow;
+        } else {
+            this.chunk = bufSize/rowSize + 1;
+        }
+
+    }
+
+    /** Set the pointer arrays for the eight primitive types
+      * to point to the appropriate elements of arrays.
+      */
+    protected void initializePointers() {
+
+        int nbyte, nshort, nint, nlong, nfloat, ndouble, nchar, nboolean;
+
+        // Count how many of each type we have.
+        nbyte=0; nshort=0; nint = 0; nlong = 0;
+        nfloat = 0; ndouble=0; nchar = 0; nboolean = 0;
+
+        for (int col=0; col<arrays.length; col += 1) {
+            switch (types[col]) {
+
+               case 'B':
+                   nbyte += 1;
+                   break;
+               case 'S':
+                   nshort += 1;
+                   break;
+               case 'I':
+                   nint += 1;
+                   break;
+               case 'L':
+                   nlong += 1;
+                   break;
+               case 'F':
+                   nfloat += 1;
+                   break;
+               case 'D':
+                   ndouble += 1;
+                   break;
+               case 'C':
+                   nchar += 1;
+                   break;
+               case 'Z':
+                   nboolean += 1;
+                   break;
+            }
+        }
+
+        // Allocate the pointer arrays.  Note that many will be
+        // zero-length.
+
+        bytePointers     = new byte[nbyte][];
+        shortPointers    = new short[nshort][];
+        intPointers      = new int[nint][];
+        longPointers     = new long[nlong][];
+        floatPointers    = new float[nfloat][];
+        doublePointers   = new double[ndouble][];
+        charPointers     = new char[nchar][];
+        booleanPointers  = new boolean[nboolean][];
+
+        // Now set the pointers.
+        nbyte=0; nshort=0; nint = 0; nlong = 0;
+        nfloat = 0; ndouble=0; nchar = 0; nboolean = 0;
+
+        for (int col=0; col<arrays.length; col += 1) {
+            switch (types[col]) {
+
+               case 'B':
+                   bytePointers[nbyte] = (byte[]) arrays[col];
+                   nbyte += 1;
+                   break;
+               case 'S':
+                   shortPointers[nshort] = (short[]) arrays[col];
+                   nshort += 1;
+                   break;
+               case 'I':
+                   intPointers[nint] = (int[]) arrays[col];
+                   nint += 1;
+                   break;
+               case 'L':
+                   longPointers[nlong] = (long[]) arrays[col];
+                   nlong += 1;
+                   break;
+               case 'F':
+                   floatPointers[nfloat] = (float[]) arrays[col];
+                   nfloat += 1;
+                   break;
+               case 'D':
+                   doublePointers[ndouble] = (double[]) arrays[col];
+                   ndouble += 1;
+                   break;
+               case 'C':
+                   charPointers[nchar] = (char[]) arrays[col];
+                   nchar += 1;
+                   break;
+               case 'Z':
+                   booleanPointers[nboolean] = (boolean[]) arrays[col];
+                   nboolean += 1;
+                   break;
+            }
+        }
+    }
+
+
+    /** Read a table.
+      * @param is The input stream to read from.
+      */
+    public int read(InputStream is) throws IOException {
+
+        byte[] buffer = new byte[chunk*rowSize];
+
+        if (rowSize == 0) {
+            return 0;
+        }
+        int currRow = 0;
+
+        // While we haven't finished reading the table..
+        while (currRow < nrow) {
+
+            // The last chunk might not have as many rows.
+            int drow = chunk;
+            if (currRow+drow > nrow) {
+                drow = nrow - currRow;
+            }
+
+            // Read in the entire buffer.  The loop is not needed
+            // for the BufferedDataInputStream, but checking
+            // allows us to use any kind of stream.
+
+            int need = drow * rowSize;
+            int got = 0;
+
+            while (need > 0) {
+                int len = is.read(buffer, got, need);
+                if (len <= 0) {
+                    throw new EOFException("EOF reached in ColumnarIO.read");
+                }
+                need -= len;
+                got += len;
+            }
+            int bufOffset = 0;
+
+            // Loop over the rows in this buffer
+            for (int row=currRow; row<currRow+drow; row += 1) {
+
+              int ibyte=0;
+              int ishort = 0;
+              int iint = 0;
+              int ilong = 0;
+              int ichar = 0;
+              int ifloat = 0;
+              int idouble = 0;
+              int iboolean = 0;
+
+              // Loop over the columns within the row.
+              for (int col=0; col < arrays.length; col += 1) {
+
+                int arrOffset = sizes[col]*row;
+                int size = sizes[col];
+                int i,i1,i2,tmp;
+
+                switch(types[col]) {
+                  // In anticpated order of use.
+                  case 'I':
+                    int[] ia = intPointers[iint];
+                    iint += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+                      ia[i] =   buffer[bufOffset]         << 24 |
+                               (buffer[bufOffset+1]&0xFF) << 16 |
+                               (buffer[bufOffset+2]&0xFF) <<  8 |
+                               (buffer[bufOffset+3]&0xFF);
+                      bufOffset += 4;
+                    }
+                    break;
+
+                  case 'S':
+                    short[] s = shortPointers[ishort];
+                    ishort += 1;
+
+                    for(i=arrOffset; i<arrOffset+size; i += 1) {
+                      s[i] = (short) (buffer[bufOffset] << 8 |
+                                     (buffer[bufOffset+1]&0xFF) );
+                      bufOffset += 2;
+                    }
+                    break;
+
+                  case 'B':
+                    byte[] b = bytePointers[ibyte];
+                    ibyte += 1;
+
+                    for(i=arrOffset; i<arrOffset+size; i += 1) {
+                       b[i] = buffer[bufOffset];
+                       bufOffset += 1;
+                    }
+                    break;
+
+                  case 'F':
+                    float[] f = floatPointers[ifloat];
+                    ifloat += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+                      tmp = buffer[bufOffset]         << 24 |
+                               (buffer[bufOffset+1]&0xFF) << 16 |
+                               (buffer[bufOffset+2]&0xFF) <<  8 |
+                               (buffer[bufOffset+3]&0xFF);
+                      f[i] = Float.intBitsToFloat(tmp);
+                      bufOffset += 4;
+                    }
+                    break;
+
+                  case 'D':
+                    double[] d = doublePointers[idouble];
+                    idouble += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+                      i1  = buffer[bufOffset]         << 24 |
+                               (buffer[bufOffset+1]&0xff) << 16 |
+                               (buffer[bufOffset+2]&0xff) <<  8 |
+                               (buffer[bufOffset+3]&0xff);
+
+                      bufOffset += 4;
+                      i2  = buffer[bufOffset]         << 24 |
+                               (buffer[bufOffset+1]&0xff) << 16 |
+                               (buffer[bufOffset+2]&0xff) <<  8 |
+                               (buffer[bufOffset+3]&0xff);
+                      bufOffset += 4;
+
+                      d[i] = Double.longBitsToDouble(
+                              ((long) i1)                   << 32 |
+                              ((long) i2&0x00000000ffffffffL)    );
+                    }
+
+                    break;
+                  case 'C':
+                    char[] c = charPointers[ichar];
+                    ichar += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+
+                      tmp = ((buffer[bufOffset]         << 8 ) |
+                             (buffer[bufOffset+1]&0xFF       ) );
+                      c[i] = (char) tmp;
+                      bufOffset += 2;
+                    }
+                    break;
+
+                  case 'L':
+                    long[] l = longPointers[ilong];
+                    ilong += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+                      i1  = buffer[bufOffset]         << 24 |
+                               (buffer[bufOffset+1]&0xff) << 16 |
+                               (buffer[bufOffset+2]&0xff) <<  8 |
+                               (buffer[bufOffset+3]&0xff);
+
+                      bufOffset += 4;
+                      i2  = buffer[bufOffset]         << 24 |
+                               (buffer[bufOffset+1]&0xff) << 16 |
+                               (buffer[bufOffset+2]&0xff) <<  8 |
+                               (buffer[bufOffset+3]&0xff);
+                      bufOffset += 4;
+
+                      l[i] = (( (long) i1) << 32) |
+                             ((long)i2&0x00000000ffffffffL);
+                    }
+
+                    break;
+                  case 'Z':
+
+                    boolean[] bool = booleanPointers[iboolean];
+                    iboolean += 1;
+
+                    for (i=arrOffset; i < arrOffset+size; i += 1) {
+                      if (buffer[bufOffset] == 1) {
+                         bool[i] = true;
+                      } else {
+                         bool[i] = false;
+                      }
+                      bufOffset += 1;
+                    }
+                    break;
+                }
+              }
+            }
+
+            currRow += drow;
+        }
+
+        // All done if we get here...
+        return rowSize*nrow;
+    }
+
+    /** Write a table.
+      * @param os the output stream to write to.
+      */
+    public int write(OutputStream os) throws IOException {
+
+        byte[] buffer = new byte[chunk*rowSize];
+
+        if (rowSize == 0) {
+            return 0;
+        }
+        int currRow = 0;
+
+        // While we haven't finished writing the table..
+        while (currRow < nrow) {
+
+            // The last chunk might not have as many rows.
+            int drow = chunk;
+            if (currRow+drow > nrow) {
+                drow = nrow - currRow;
+            }
+
+            int bufOffset = 0;
+
+            // Loop over the rows in this buffer
+            for (int row=currRow; row<currRow+drow; row += 1) {
+
+              int ibyte=0;
+              int ishort = 0;
+              int iint = 0;
+              int ilong = 0;
+              int ichar = 0;
+              int ifloat = 0;
+              int idouble = 0;
+              int iboolean = 0;
+
+              // Loop over the columns within the row.
+              for (int col=0; col < arrays.length; col += 1) {
+
+                int arrOffset = sizes[col]*row;
+                int size = sizes[col];
+                int i,i1,i2,tmp;
+
+                switch(types[col]) {
+                  // In anticpated order of use.
+                  case 'I':
+                    int[] ia = intPointers[iint];
+                    iint += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+
+                      buffer[bufOffset]  = (byte) (ia[i]>>>24);
+                      buffer[bufOffset+1]= (byte) (ia[i]>>>16);
+                      buffer[bufOffset+2]= (byte) (ia[i]>>> 8);
+                      buffer[bufOffset+3]= (byte) (ia[i]);
+                      bufOffset += 4;
+                    }
+                    break;
+
+                  case 'S':
+                    short[] s = shortPointers[ishort];
+                    ishort += 1;
+
+                    for(i=arrOffset; i<arrOffset+size; i += 1) {
+                      buffer[bufOffset]  = (byte) (s[i]>>>8);
+                      buffer[bufOffset+1]= (byte) (s[i]);
+                      bufOffset += 2;
+                    }
+                    break;
+
+                  case 'B':
+                    byte[] b = bytePointers[ibyte];
+                    ibyte += 1;
+
+                    System.arraycopy(b,arrOffset,buffer,bufOffset, size);
+                    bufOffset += size;
+
+                    break;
+
+                  case 'F':
+                    float[] f = floatPointers[ifloat];
+                    ifloat += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+                      tmp = Float.floatToIntBits(f[i]);
+                      buffer[bufOffset]   = (byte) (tmp >>> 24);
+                      buffer[bufOffset+1] = (byte) (tmp >>> 16);
+                      buffer[bufOffset+2] = (byte) (tmp >>>  8);
+                      buffer[bufOffset+3] = (byte) (tmp);
+                      bufOffset += 4;
+                    }
+                    break;
+
+                  case 'D':
+                    double[] d = doublePointers[idouble];
+                    idouble += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+
+                      long lng = Double.doubleToLongBits(d[i]);
+                      i1 = (int) (lng >>> 32);
+                      i2 = (int) (lng);
+
+                      buffer[bufOffset]   = (byte) (i1 >>> 24);
+                      buffer[bufOffset+1] = (byte) (i1 >>> 16);
+                      buffer[bufOffset+2] = (byte) (i1 >>>  8);
+                      buffer[bufOffset+3] = (byte) (i1);
+
+                      bufOffset += 4;
+
+                      buffer[bufOffset]   = (byte) (i2 >>> 24);
+                      buffer[bufOffset+1] = (byte) (i2 >>> 16);
+                      buffer[bufOffset+2] = (byte) (i2 >>>  8);
+                      buffer[bufOffset+3] = (byte) (i2);
+
+                      bufOffset += 4;
+                    }
+
+                    break;
+
+                  case 'C':
+                    char[] c = charPointers[ichar];
+                    ichar += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+
+                      buffer[bufOffset]  = (byte) (c[i] >>> 8);
+                      buffer[bufOffset+1]= (byte) (c[i]);
+
+                      bufOffset += 2;
+                    }
+                    break;
+
+                  case 'L':
+                    long[] l = longPointers[ilong];
+                    ilong += 1;
+
+                    for (i=arrOffset; i<arrOffset+size; i += 1) {
+
+                      i1 = (int) (l[i] >>> 32);
+                      i2 = (int) (l[i]);
+
+                      buffer[bufOffset]   = (byte) (i1 >>> 24);
+                      buffer[bufOffset+1] = (byte) (i1 >>> 16);
+                      buffer[bufOffset+2] = (byte) (i1 >>>  8);
+                      buffer[bufOffset+3] = (byte) (i1);
+
+                      bufOffset += 4;
+
+                      buffer[bufOffset]   = (byte) (i2 >>> 24);
+                      buffer[bufOffset+1] = (byte) (i2 >>> 16);
+                      buffer[bufOffset+2] = (byte) (i2 >>>  8);
+                      buffer[bufOffset+3] = (byte) (i2);
+
+                      bufOffset += 4;
+                    }
+
+                    break;
+                  case 'Z':
+
+                    boolean[] bool = booleanPointers[iboolean];
+                    iboolean += 1;
+
+                    for (i=arrOffset; i < arrOffset+size; i += 1) {
+                      if (bool[i]) {
+                         buffer[bufOffset] = 1;
+                      } else {
+                         buffer[bufOffset] = 0;
+                      }
+                      bufOffset += 1;
+                    }
+                    break;
+                }
+              }
+            }
+
+            // Write the entire buffer.
+            os.write(buffer, 0, rowSize*drow);
+
+            currRow += drow;
+        }
+
+        // All done if we get here...
+        return rowSize*nrow;
+    }
+
+    /** Get the base classes of the columns.
+      * @return An array of Class objects, one for each column.
+      */
+    public Class[] getBases() {
+        return bases;
+    }
+
+    /** Get the characters describing the base classes of the columns.
+      * @return An array of char's, one for each column.
+      */
+    public char[] getTypes() {
+        return types;
+    }
+
+
+}
+
+
diff --git a/nom/tam/util/DataTable.java b/nom/tam/util/DataTable.java
new file mode 100644
index 0000000..3d446cf
--- /dev/null
+++ b/nom/tam/util/DataTable.java
@@ -0,0 +1,32 @@
+package nom.tam.util;
+
+/* Copyright: Thomas McGlynn 1997-1998.
+ * This code may be used for any purpose, non-commercial
+ * or commercial so long as this copyright notice is retained
+ * in the source code or included in or referred to in any
+ * derived software.
+ */
+
+
+/** This interface defines the properties that
+  * a generic table should have.
+  */
+
+public interface DataTable {
+
+    void   setRow(int row, Object newRow)
+        throws TableException;
+    Object getRow(int row);
+
+    void   setColumn(int column, Object newColumn)
+        throws TableException;
+    Object getColumn(int column);
+
+    void   setElement(int row, int col, Object newElement)
+        throws TableException;
+    Object getElement(int row, int col);
+
+    int getNrow();
+    int getNcol();
+
+}
diff --git a/nom/tam/util/TableException.java b/nom/tam/util/TableException.java
new file mode 100644
index 0000000..8803118
--- /dev/null
+++ b/nom/tam/util/TableException.java
@@ -0,0 +1,19 @@
+package nom.tam.util;
+ /*
+  * Copyright: Thomas McGlynn 1997-1998.
+  * This code may be used for any purpose, non-commercial
+  * or commercial so long as this copyright notice is retained
+  * in the source code or included in or referred to in any
+  * derived software.
+  */
+
+public class TableException extends Exception {
+
+    public TableException() {
+        super();
+    }
+
+    public TableException(String msg) {
+        super(msg);
+    }
+}
diff --git a/nom/tam/util/package.html b/nom/tam/util/package.html
new file mode 100644
index 0000000..8bcfc0f
--- /dev/null
+++ b/nom/tam/util/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Classes which may possibly be of more general interest that the FITS classes.
+
+</body>
+</html>
+
diff --git a/ucar/multiarray/AbstractAccessor.java b/ucar/multiarray/AbstractAccessor.java
new file mode 100644
index 0000000..5b2f47e
--- /dev/null
+++ b/ucar/multiarray/AbstractAccessor.java
@@ -0,0 +1,357 @@
+// $Id: AbstractAccessor.java,v 1.2 2002-05-29 20:32:38 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.io.IOException;
+
+/**
+ * This abstract class provides a skeletal implementation
+ * of the Accessor interface.
+ * <p>
+ * A minimal concrete implementation
+ * would provide concrete implementations
+ * <code>Object get(int [] index)</code>,
+ * <code>Object copyout(int [] origin, int [] shape)</code>,
+ * <code>set(int [] index, Object value)</code>.
+ * <code>copyin(int [] index, MultiArray value)</code>.
+ * 
+ * @see Accessor
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:38 $
+ */
+
+public abstract class
+AbstractAccessor
+	implements Accessor
+{
+	/* NOTUSED, not correct?
+	 * Analogous to System.arraycopy,
+	 * copy elements from one Accessor to another.
+	 * <p>
+	 * The destination limits control the iteration.
+	 * If the source limits are such that there is less
+	 * data available in the source than requested in the
+	 * destination, the source IndexIterator will silently
+	 * "roll over", providing data from the beginning of
+	 * the source.
+	 *
+	 * @param src the data source
+	 * @param src_pos starting position in the data source
+	 * @param src_limits limits on the source IndexIterator
+	 *	typically < ((MultiArray)src).getLengths()
+	 * @param dst the destination, values here are modified
+	 * @param dst_pos starting position in the data source
+	 * @param dst_limits limits on the destination IndexIterator,
+	 *	typically < ((MultiArray)dst).getLengths()
+	 * 
+	static public void
+	copy(Accessor src, int [] src_pos, int [] src_limits,
+		Accessor dst, int [] dst_pos, int [] dst_limits)
+			throws IOException
+	{
+		IndexIterator src_odo = new IndexIterator(src_pos, src_limits);
+		IndexIterator dst_odo = new IndexIterator(dst_pos, dst_limits);
+
+		while(dst_odo.notDone())
+		{
+			dst.set(dst_odo.value(), src.get(src_odo.value()));
+			src_odo.incr();
+			dst_odo.incr();
+		}
+	}
+	 */
+
+	/**
+	 * Used to implement copyin.
+	 *
+	 * @param src the data source
+	 * @param src_limits limits on the source IndexIterator
+	 *	typically < ((MultiArray)src).getLengths()
+	 * @param dst the destination, values here are modified
+	 * @param dst_pos starting position in the data source
+	 * 
+	 */
+	static public void
+	copy(Accessor src, int [] src_limits,
+		Accessor dst, int [] dst_pos )
+			throws IOException
+	{
+		for(OffsetDualIndexIterator odo =
+			new OffsetDualIndexIterator(dst_pos, src_limits);
+				odo.notDone(); odo.incr())
+		{
+			dst.set(odo.offsetValue(), src.get(odo.value()));
+		}
+	}
+
+	/**
+	 * Used to implement copyout.
+	 *
+	 * @param src the data source
+	 * @param src_pos starting position in the data source
+	 * @param dst the destination, values here are modified
+	 * @param dst_limits limits on the source IndexIterator
+	 *	typically < ((MultiArray)dst).getLengths()
+	 * 
+	 */
+	static public void
+	copyO(Accessor src, int [] src_pos,
+		Accessor dst, int [] dst_limits )
+			throws IOException
+	{
+		for(OffsetDualIndexIterator odo =
+			new OffsetDualIndexIterator(src_pos, dst_limits);
+				odo.notDone(); odo.incr())
+		{
+			dst.set(odo.value(), src.get(odo.offsetValue()));
+		}
+	}
+
+ /* Begin MultiArray read access methods */
+
+	abstract public Object
+	get(int [] index)
+		throws IOException;
+
+	public boolean
+	getBoolean(int [] index)
+		throws IOException
+	{
+		final Boolean nn = (Boolean) get(index);
+		return nn.booleanValue();
+	}
+
+	public char
+	getChar(int [] index)
+		throws IOException
+	{
+		final Character nn = (Character) get(index);
+		return nn.charValue();
+	}
+
+	public byte
+	getByte(int [] index)
+		throws IOException
+	{
+		final Number nn = (Number) get(index);
+		return nn.byteValue();
+	}
+
+	public short
+	getShort(int [] index)
+		throws IOException
+	{
+		final Number nn = (Number) get(index);
+		return nn.shortValue();
+	}
+
+	public int
+	getInt(int [] index)
+		throws IOException
+	{
+		final Number nn = (Number) get(index);
+		return nn.intValue();
+	}
+
+	public long
+	getLong(int [] index)
+		throws IOException
+	{
+		final Number nn = (Number) get(index);
+		return nn.longValue();
+	}
+
+	public float
+	getFloat(int [] index)
+		throws IOException
+	{
+		final Number nn = (Number) get(index);
+		return nn.floatValue();
+	}
+
+	public double
+	getDouble(int [] index)
+		throws IOException
+	{
+		final Number nn = (Number) get(index);
+		return nn.doubleValue();
+	}
+
+ /* End MultiArray read access methods */
+ /* Begin MultiArray write access methods */
+
+    	abstract public void
+	set(int [] index, Object value)
+			throws IOException;
+
+	public void
+	setBoolean(int [] index, boolean value)
+		throws IOException
+	{
+		set(index, new Boolean(value));
+	}
+
+	public void
+	setChar(int [] index, char value)
+		throws IOException
+	{
+		set(index, new Character(value));
+	}
+
+	public void
+	setByte(int [] index, byte value)
+		throws IOException
+	{
+		set(index, new Byte(value));
+	}
+
+	public void
+	setShort(int [] index, short value)
+		throws IOException
+	{
+		set(index, new Short(value));
+	}
+
+	public void
+	setInt(int [] index, int value)
+		throws IOException
+	{
+		set(index, new Integer(value));
+	}
+
+	public void
+	setLong(int [] index, long value)
+		throws IOException
+	{
+		set(index, new Long(value));
+	}
+
+	public void
+	setFloat(int [] index, float value)
+		throws IOException
+	{
+		set(index, new Float(value));
+	}
+
+	public void
+	setDouble(int [] index, double value)
+		throws IOException
+	{
+		set(index, new Double(value));
+	}
+
+ /* End MultiArray write access methods */
+
+	abstract public MultiArray
+	copyout(int [] origin, int [] shape)
+			throws IOException;
+	/*
+	{
+		final MultiArrayImpl data = new MultiArrayImpl(
+			componentType,
+			shape);
+		for(OffsetDualIndexIterator odo =
+			new OffsetDualIndexIterator(origin, data.getLengths());
+				odo.notDone(); odo.incr())
+		{
+			data.set(odo.value(), get(odo.offsetValue()));
+		}
+		return data;
+	}
+	*/
+
+	/**
+	 * You almost always want to override this
+	 */
+	public void
+	copyin(int [] origin, MultiArray data)
+		throws IOException
+	{
+		for(OffsetDualIndexIterator odo =
+			new OffsetDualIndexIterator(origin, data.getLengths());
+				odo.notDone(); odo.incr())
+		{
+			set(odo.offsetValue(), data.get(odo.value()));
+		}
+	}
+
+	abstract public Object
+	toArray()
+		throws IOException;
+
+	abstract public Object
+	toArray(Object dst, int [] origin, int [] shape)
+		throws IOException;
+}
+
+
+final class
+OffsetDualIndexIterator
+	extends IndexIterator
+{
+	OffsetDualIndexIterator(int [] theOffset, int [] theLimits)
+	{
+		super(theLimits);
+		offset = theOffset; // N.B. Not a copy
+		offsetCounter = (int []) offset.clone();
+	}
+
+	/**
+	 * Increment the odometer
+	 */
+	public void
+	incr()
+	{
+		int digit = counter.length -1;
+		if(digit < 0)
+		{
+			// counter is zero length array <==> scalar
+			ncycles++;
+			return;
+		}
+
+		while(digit >= 0)
+		{
+			offsetCounter[digit]++;
+			counter[digit]++;
+			if(counter[digit] < limits[digit])
+			{
+				break; // normal exit
+			}
+			// else, carry
+			counter[digit] = 0;
+			offsetCounter[digit] = offset[digit];
+			if(digit == 0)
+			{
+				ncycles++; // rolled over
+				break;
+			}
+			// else
+			digit--;
+		}
+	}
+
+	public int []
+	offsetValue() { return offsetCounter; }
+
+	private final int[] offset;
+	private final int[] offsetCounter;
+}
diff --git a/ucar/multiarray/Accessor.java b/ucar/multiarray/Accessor.java
new file mode 100644
index 0000000..b385448
--- /dev/null
+++ b/ucar/multiarray/Accessor.java
@@ -0,0 +1,340 @@
+// $Id: Accessor.java,v 1.3 2003-02-03 20:09:03 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.io.IOException;
+
+/**
+ *  Interface for multidimensional array data access.
+ *  Given an index (array of integers), get or set the value
+ *  at index.
+ *  <p>
+ *  Netcdf Variables implement this, but more general objects,
+ *  such as java arrays, can be simply wrapped to provide
+ *  this interface.
+ *  <p>
+ *  For lack of a better model, we use use naming conventions
+ *  from java.lang.reflect.Array.
+ *  In particular, we name the primitive specific "set" functions by type,
+ *  rather than using overloading.
+ *  This is symmetric with the "get" operations.
+ *  <p>
+ *  The primitive specific get and set methods are useful only if the
+ *  the componentType is primitive (like java.lang.Double.TYPE).
+ *  <p>
+ *  Like java.lang.reflect.Array, classes that implement this
+ *  interface should permit widening conversions to occur during a
+ *  get or set operation, and throw IllegalArgumentException otherwise.
+ *  Classes which implement this interface may be more lenient, however,
+ *  only throwing the exception for narrowing conversions if
+ *  the unconverted value is out of range for the target type.
+ *  Implementations may throw UnsupportedOperationException,
+ *	IllegalArgumentException, or ? for conversions to primitive
+ *  which don't make sense.
+ *  <p>
+ *  The implementations may be file based or remote,
+ *  so the methods throw java.io.IOException.
+ *
+ * @see AbstractAccessor
+ * @see MultiArray
+ * @see RemoteAccessor
+ * @author $Author: donm $
+ * @version $Revision: 1.3 $ $Date: 2003-02-03 20:09:03 $
+ */
+public interface
+Accessor
+{
+	/**
+	 * Get (read) the array element at index.
+	 * The returned value is wrapped in an object if it
+	 * has a primitive type.
+	 * Length of index must be greater than or equal to the rank of this.
+	 * Values of index components must be less than corresponding
+	 * values from getLengths().
+	 * @param index MultiArray index
+	 * @return Object value at <code>index</code>
+	 * @exception NullPointerException If the argument is null.
+	 * @exception IllegalArgumentException If the array length of index is
+	 *	 too small
+	 * @exception ArrayIndexOutOfBoundsException If an index component
+	 *  argument is negative, or if it is greater than or equal to the
+	 *  corresponding dimension length.
+	 */
+	public Object
+	get(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as a boolean.
+	 * @see Accessor#get
+	 */
+	public boolean
+	getBoolean(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as a char.
+	 * @see Accessor#get
+	 */
+	public char
+	getChar(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as a byte.
+	 * @see Accessor#get
+	 */
+	public byte
+	getByte(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as a short.
+	 * @see Accessor#get
+	 */
+	public short
+	getShort(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as an int.
+	 * @see Accessor#get
+	 */
+	public int
+	getInt(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as a long.
+	 * @see Accessor#get
+	 */
+	public long
+	getLong(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as a float.
+	 * @see Accessor#get
+	 */
+	public float
+	getFloat(int [] index)
+		throws IOException;
+
+	/**
+	 * Get the array element at index, as a double.
+	 * @see Accessor#get
+	 */
+	public double
+	getDouble(int [] index)
+		throws IOException;
+
+	/**
+	 * Set (modify, write) the array element at index
+	 * to the specified value.
+	 * If the array has a primitive component type, the value may
+	 * be unwrapped.
+	 * Values of index components must be less than corresponding
+	 * values from getLengths().
+	 * @param index MultiArray index
+	 * @param value the new value.
+	 * @exception NullPointerException If the index argument is null, or
+	 * if the array has a primitive component type and the value argument is
+	 * null
+	 * @exception IllegalArgumentException If the array length of index is
+	 *	 too small
+	 * @exception ArrayIndexOutOfBoundsException If an index component
+	 *  argument is negative, or if it is greater than or equal to the
+	 *  corresponding dimension length.
+	 */
+	public void
+	set(int [] index, Object value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified boolean value.
+	 * @see Accessor#set
+	 */
+	public void
+	setBoolean(int [] index, boolean value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified char value.
+	 * @see Accessor#set
+	 */
+	public void
+	setChar(int [] index, char value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified byte value.
+	 * @see Accessor#set
+	 */
+	public void
+	setByte(int [] index, byte value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified short value.
+	 * @see Accessor#set
+	 */
+	public void
+	setShort(int [] index, short value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified int value.
+	 * @see Accessor#set
+	 */
+	public void
+	setInt(int [] index, int value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified long value.
+	 * @see Accessor#set
+	 */
+	public void
+	setLong(int [] index, long value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified float value.
+	 * @see Accessor#set
+	 */
+	public void
+	setFloat(int [] index, float value)
+		throws IOException;
+
+	/**
+	 * Set the array element at index to the specified double value.
+	 * @see Accessor#set
+	 */
+	public void
+	setDouble(int [] index, double value)
+		throws IOException;
+
+	/**
+	 * Aggregate read access.
+	 * Return a new MultiArray of the
+	 * same componentType as this, and with shape as specified,
+	 * which is initialized to the values of this, as 
+	 * clipped to (origin, origin + shape).
+	 * <p>
+	 * It is easier to implement than to specify :-).
+	 * <p>
+	 * The main reason to implement this instead of using
+	 * the equivalent proxy is for remote or file access.
+	 * <p>
+	 * <code>assert(origin[ii] + shape[ii] <= lengths[ii]);</code>
+	 *
+	 * @param origin int array specifying the starting index.
+	 * @param shape  int array specifying the extents in each
+	 *	dimension. This becomes the shape of the return.
+	 * @return the MultiArray with the specified shape
+	 */
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+			throws IOException;
+
+	/**
+	 * Aggregate write access.
+	 * Given a MultiArray, copy it into this at the specified starting index.
+	 * TODO: clearer specification.
+	 * <p>
+	 * Hopefully this member can be optimized in various situations.
+	 * <p>
+	 * <code>assert(origin[ii] + (source.getLengths())[ii]
+		<= (getLengths())[ii]);</code>
+	 *
+	 * @param origin int array specifying the starting index.
+	 * @param source  MultiArray with the same componentType as
+	 *      this and shape smaller than
+	 *	<code>this.getLengths() - origin</code>
+	 */
+	public void
+	copyin(int [] origin, MultiArray source)
+		throws IOException;
+
+	/**
+	 * Returns a new array containing all of the elements in this
+	 * MultiArray. The returned array is one dimensional.
+	 * The order of the elements in the result is natural,
+	 * as if we used an IndexIterator to step through the elements
+	 * of this MultiArray. The component type of the result is
+	 * the same as this.
+	 * <p>
+     	 * This method acts as bridge between array-based and MultiArray-based
+	 * APIs.
+	 * <p>
+	 * This method is functionally equivalent to
+	 * <pre>
+		Object anArray = Array.newInstance(getComponentType(), 1);
+		int [] origin = new int[getRank()]
+		int [] shape = getDimensions();
+		return toArray(anArray, origin, shape);
+	 * </pre>
+	 *
+	 * @return a one dimensional Array containing all the elements
+	 * in this MultiArray
+	 */
+	public Object
+	toArray()
+		throws IOException;
+	
+
+
+
+
+	
+
+	/**
+	 * Returns an array containing elements of this
+	 * MultiArray specified by origin and shape,
+	 * possibly converting the component type.
+	 * The returned array is one dimensional.
+	 * The order of the elements in the result is natural,
+	 * as if we used an IndexIterator to step through the elements
+	 * of this MultiArray.
+	 * <p>
+	 * The anArray argument should be an array.
+	 * If it is large enough to contain the output,
+	 * it is used and no new storage is allocated.
+	 * Otherwise, new storage is allocated with the
+	 * same component type as the argument, and the data
+	 * is copied into it.
+	 * <p>
+     	 * This method acts as bridge between array-based and MultiArray-based
+	 * APIs.
+	 * <p>
+	 * This method is similar to copyout(origin, shape).toArray(),
+	 * but avoids a copy operation and (potentially) an allocation.
+	 * <p>
+	 * NOTE: Implementation of type conversion is deferred until
+	 * JDK 1.2. Currently, the componentType of <code>anArray</code>
+	 * must be the same as <code>this</code>
+	 *
+	 * @return a one dimensional Array containing the specified elements
+	 */
+	public Object
+	toArray(Object anArray, int [] origin, int [] shape)
+		throws IOException;
+
+}
diff --git a/ucar/multiarray/ArrayMultiArray.java b/ucar/multiarray/ArrayMultiArray.java
new file mode 100644
index 0000000..cacc880
--- /dev/null
+++ b/ucar/multiarray/ArrayMultiArray.java
@@ -0,0 +1,747 @@
+// $Id: ArrayMultiArray.java,v 1.3 2003-02-03 20:09:03 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+import java.io.IOException;
+
+/**
+ * MultiArray implementation which is an adapter for java language arrays.
+ * If you have a java array and want to wrap it
+ * in a MultiArray interface, use this class.
+ * Rank of these is always > 0, use ScalarMultiArray
+ * for scalars.
+ * <p>
+ * The set, setXXX, get, getXXX methods use the
+ * corresponding methods from java.lang.reflect.Array,
+ * the conversion and exception characteristics of the methods
+ * here are like the ones found there.
+ *
+ * @see java.lang.reflect.Array
+ * @see MultiArray
+ * @see ScalarMultiArray
+ *
+ * @author $Author: donm $
+ * @version $Revision: 1.3 $ $Date: 2003-02-03 20:09:03 $
+ */
+/*
+ * Implementation note:
+ * It has been suggested that we "factor common code in the get/set
+ * methods". 
+ *
+ *   public int getInt(int[] index)
+ *   {
+ *	return get(index).intValue();
+ *   }
+ *
+ * This is probably desireable from a maintenance or clarity
+ * point of view.
+ * One would need to prove that this is not desireable from
+ * from a performance point of view to justify leaving it
+ * the way it is.
+ * For now, it seems to work the way it is, so I'm leaving it.
+ * Think of it as having been machine generated.
+ */
+public class
+ArrayMultiArray
+	implements MultiArray
+{
+
+	/**
+	 * Package private constructor which avoids some of the
+	 * protections of the public constructor below.
+	 */ 
+	ArrayMultiArray(Object aro, int theRank, Class componentType)
+	{
+		jla = aro;
+		rank = theRank;
+		this.componentType = componentType;
+	}
+
+	/**
+	 * Given a java Object, typically an array of primitive
+	 * (or an array of array of primitive ...), Provide a MultiArray
+	 * interface to the provided object.
+	 * The wrapper provided does not copy the object, so it remains
+	 * accessable via the language [] notation
+	 * and the java.lang.reflect.Array methods.
+	 * Synchronization not provided by this interface.
+	 * @param aro a (multi-dimensional) array of primitives.
+	 */
+	public
+	ArrayMultiArray(Object aro) {
+		int rank_ = 0;
+		Class componentType_ = aro.getClass();
+		while(componentType_.isArray())
+		{
+			rank_++;
+			componentType_ = componentType_.getComponentType();
+		}
+		if(rank_ == 0)
+			 throw new IllegalArgumentException();
+		jla = aro;
+		rank = rank_;
+		componentType = componentType_;
+	}
+
+	/**
+	 * Create a new MultiArray of the given componentType and shape.
+	 * Storage for the values is allocated and owned by this with
+	 * default initialization.
+	 * @param componentType Class of the primitives or objects to
+	 * be contained.
+	 * @param dimensions the shape of the MultiArray.
+	 * dimensions.length determines the rank of the new MultiArray.
+	 */
+	public
+	ArrayMultiArray(Class componentType, int [] dimensions)
+	{
+		rank = dimensions.length;
+		if(rank == 0)
+			 throw new IllegalArgumentException();
+		this.componentType = componentType;
+		jla = Array.newInstance(componentType, dimensions);
+	}
+
+	/**
+	 * A copy constructor.
+	 * <p>
+	 * Create a new MultiArray with the same componentType and shape
+	 * as the argument
+	 * @param ma the MultiArray to copy.
+	 */
+	public
+	ArrayMultiArray(MultiArray ma)
+		throws IOException
+	{
+		rank = ma.getRank();
+		if(rank == 0)
+			 throw new IllegalArgumentException();
+		componentType = ma.getComponentType();
+		final int [] lengths = ma.getLengths();
+		jla = Array.newInstance(componentType, lengths);
+
+		IndexIterator odo = new IndexIterator(lengths);
+		for(; odo.notDone(); odo.incr())
+		{
+			final int [] index = odo.value();	
+			this.set(index, ma.get(index));
+		}
+	}
+
+ /* Begin MultiArray Inquiry methods from MultiArrayInfo */
+
+	/**
+	 * Returns the Class object representing the component
+	 * type of the wrapped array. If the rank is greater than
+	 * 1, this will be the component type of the leaf (rightmost)
+	 * nested array.
+	 * @see MultiArrayInfo#getComponentType
+	 * @return Class the component type
+	 */
+	public Class
+	getComponentType() { return componentType; }
+
+	/**
+	 * @see MultiArrayInfo#getRank
+	 * @return int number of dimensions of the array
+	 */
+	public int
+	getRank() { return rank;}
+
+	/**
+	 * As if java.lang.reflect.Array.getLength() were called recursively
+	 * on the wrapped object, return the dimension lengths.
+	 * @see MultiArrayInfo#getLengths
+	 *
+	 * @return int array whose length is the rank of this
+	 * MultiArray and whose elements represent the
+	 * length of each of it's dimensions
+	 */
+	public int []
+	getLengths() {
+		int [] lengths = new int[rank];
+		Object oo = jla;
+		for(int ii = 0; ii < rank; ii++) {
+			lengths[ii] = Array.getLength(oo);
+			oo = Array.get(oo, 0);
+		}
+		return lengths;
+	}
+
+	/**
+	 * Returns <code>true</code> if and only if the effective dimension
+	 * lengths can change. Always returns <code>false</code> for this class.
+	 * @see MultiArrayInfo#isUnlimited
+	 * @return boolean <code>false</code>
+	 */
+	public boolean
+	isUnlimited() { return false; }
+
+	/**
+	 * Always returns false for this class.
+	 * @see MultiArrayInfo#isScalar
+	 * @return false
+	 */
+	public boolean
+	isScalar() { return rank == 0; }
+
+ /* End MultiArrayInfo */
+ /* Begin MultiArray Access methods from Accessor */
+
+	/**
+	 * @see Accessor#get
+	 */
+	public Object
+	get(int [] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.get(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getBoolean
+	 */
+	public boolean
+	getBoolean(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getBoolean(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getChar
+	 */
+	public char
+	getChar(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getChar(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getByte
+	 */
+	public byte
+	getByte(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getByte(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getShort
+	 */
+	public short
+	getShort(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getShort(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getInt
+	 */
+	public int
+	getInt(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getInt(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getLong
+	 */
+	public long
+	getLong(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getLong(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getFloat
+	 */
+	public float
+	getFloat(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getFloat(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#getDouble
+	 */
+	public double
+	getDouble(int[] index)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.getDouble(oo, index[end]);
+	}
+
+	/**
+	 * @see Accessor#set
+	 */
+	public void
+	set(int [] index, Object value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.set(oo, index[end], value);
+		return;
+	}
+
+	/**
+	 * @see Accessor#setBoolean
+	 */
+	public void
+	setBoolean(int [] index, boolean value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setBoolean(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#setChar
+	 */
+	public void
+	setChar(int [] index, char value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setChar(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#setByte
+	 */
+	public void
+	setByte(int [] index, byte value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setByte(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#setShort
+	 */
+	public void
+	setShort(int [] index, short value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setShort(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#setInt
+	 */
+	public void
+	setInt(int [] index, int value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setInt(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#setLong
+	 */
+	public void
+	setLong(int [] index, long value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setLong(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#setFloat
+	 */
+	public void
+	setFloat(int [] index, float value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setFloat(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#setDouble
+	 */
+	public void
+	setDouble(int[] index, double value)
+	{
+		if(index.length < rank)
+			 throw new IllegalArgumentException();
+		final int end = rank -1;
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		Array.setDouble(oo, index[end], value);
+	}
+
+	/**
+	 * @see Accessor#copyout
+	 */
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+	{
+		if(origin.length != rank
+				|| shape.length != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+		final int [] shp = (int []) shape.clone();
+		final int [] pducts = new int[shp.length];
+		final int product = MultiArrayImpl.numberOfElements(shp,
+				pducts);
+		final Object dst = Array.newInstance(getComponentType(),
+				product);
+		int ji = rank -1;
+		int src_pos = origin[ji];
+		if(ji == 0)
+		{
+			// rank == 1
+			// No loop required
+			System.arraycopy(jla, src_pos,
+				dst, 0, product);
+		}
+		else
+		{
+			ji--;
+			final int contig = pducts[ji];
+			final OffsetIndexIterator odo =
+				new OffsetIndexIterator(truncCopy(origin),
+					getTruncLengths());
+			for(int dst_pos = 0; dst_pos < product;
+				dst_pos += contig)
+			{
+				System.arraycopy(getLeaf(odo.value()), src_pos,
+					dst, dst_pos, contig);
+				odo.incr();
+			}
+		}
+
+		return new MultiArrayImpl(shp, pducts,
+			dst);
+	}
+
+	/* TODO: specialize & optimise? */
+	/**
+	 * @see Accessor#copyin
+	 */
+	public void
+	copyin(int [] origin, MultiArray data)
+		throws IOException
+	{
+		if(origin.length != rank
+				|| data.getRank() != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// else
+		if(data.getComponentType() != componentType)
+			throw new ArrayStoreException();
+		// else
+		AbstractAccessor.copy(data, data.getLengths(), this, origin);
+	}
+
+	/**
+	 * @see Accessor#toArray
+	 */
+	public Object
+	toArray()
+	{
+		return this.toArray(null, null, null);
+	}
+
+	public Object getStorage () {
+	    return jla;
+	}
+
+	/**
+	 * @see Accessor#toArray
+	 */
+	public Object
+	toArray(Object dst, int [] origin, int [] shape)
+	{
+		if(origin == null)
+			origin = new int[rank];
+		else if(origin.length != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+
+		int [] shp = null;
+		if(shape == null)
+			shp = getLengths();
+		else if(shape.length == rank)
+			shp = (int []) shape.clone();
+		else
+			throw new IllegalArgumentException("Rank Mismatch");
+
+		final int [] pducts = new int[shp.length];
+		final int product = MultiArrayImpl.numberOfElements(shp,
+				pducts);
+		dst = MultiArrayImpl.fixDest(dst, product, componentType);
+
+		int ji = rank -1;
+		int src_pos = origin[ji];
+		if(ji == 0)
+		{
+			// rank == 1
+			// No loop required
+			System.arraycopy(jla, src_pos,
+				dst, 0, product);
+		}
+		else
+		{
+			ji--;
+			final int contig = pducts[ji];
+			final OffsetIndexIterator odo =
+				new OffsetIndexIterator(truncCopy(origin),
+					getTruncLengths());
+			for(int dst_pos = 0; dst_pos < product;
+				dst_pos += contig)
+			{
+				System.arraycopy(getLeaf(odo.value()),
+					src_pos,
+					dst, dst_pos, contig);
+				odo.incr();
+			}
+		}
+		return dst;
+	}
+
+ /* End Accessor */
+
+	static int []
+	truncCopy(int [] src)
+	{
+		final int len = src.length -1;
+		int [] dst = new int [len];
+		System.arraycopy(src, 0, dst, 0, len);
+		return dst;
+	}
+
+	/**
+	 * Peel the array by fixing the leftmost index value
+	 * to the argument.  Reduces rank by 1.
+	 * If the result would be of primitive type,
+	 * it is appropriately wrapped.
+	 * @return Object value at <code>index</code>
+	 */
+	public Object
+	get(int index)
+	{
+		if(rank == 1)
+			return Array.get(jla, index);
+		// else
+		return new ArrayMultiArray(Array.get(jla, index),
+			rank -1,
+			componentType);
+	}
+
+	/**
+	 * Get the leaf array at Index.
+	 */
+	public Object
+	getLeaf(int [] index)
+	{
+		final int end = rank -2;
+		if(index.length <= end)
+			 throw new IllegalArgumentException();
+		Object oo = jla;
+		for(int ii = 0 ; ii < end; ii++)
+			oo = Array.get(oo, index[ii]);
+		return Array.get(oo, index[end]);
+	}
+
+	/**
+	 * @return int array whose length is the rank of this minus one
+	 * MultiArray and whose elements represent the
+	 * length of each of it's leading dimensions
+	 */
+	private int []
+	getTruncLengths() {
+		final int containRank = rank - 1;
+		int [] lengths = new int[containRank];
+		Object oo = jla;
+		for(int ii = 0; ii < containRank; ii++) {
+			lengths[ii] = Array.getLength(oo);
+			oo = Array.get(oo, 0);
+		}
+		return lengths;
+	}
+
+
+	/**
+	 * The java language array which this adapts.
+	 */
+	public final Object jla;
+	private final int rank;
+	private final Class componentType;
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+			System.out.println(">>  " + System.currentTimeMillis());
+		final int [] shape = {48, 64};
+		MultiArrayImpl init =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(init.storage,
+					ii, ii);
+
+		}
+
+		ArrayMultiArray src = (ArrayMultiArray) null;
+		try {
+			src = new ArrayMultiArray(init);
+		}
+		catch (java.io.IOException ee) {}
+
+		int [] clip = new int[] {32, 64};
+		int [] origin = new int[] {8, 0};
+		MultiArray ma = src.copyout(origin, clip);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0}));
+			System.out.println(ma.getInt(new int[] {1, 0}));
+			System.out.println(ma.getInt(new int[] {lengths[0] -1,								 lengths[1] -1}));
+		}
+		catch (java.io.IOException ee) {}
+
+		clip = new int[] {48, 48};
+		origin = new int[] {0, 8};
+		ma = src.copyout(origin, clip);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0}));
+			System.out.println(ma.getInt(new int[] {1, 0}));
+			System.out.println(ma.getInt(new int[] {lengths[0] -1,								 lengths[1] -1}));
+		}
+		catch (java.io.IOException ee) {}
+
+		ArrayMultiArray dest =
+			new ArrayMultiArray(Integer.TYPE, shape);
+		try {
+			dest.copyin(origin, ma);
+			System.out.println("***Rank  " + dest.getRank());
+			int [] lengths = dest.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(dest.getInt(new int[] {0, 0}));
+			System.out.println(dest.getInt(new int[] {0, 7}));
+			System.out.println(dest.getInt(new int[] {0, 8}));
+			System.out.println(dest.getInt(new int[] {47, 55}));
+			System.out.println(dest.getInt(new int[] {47, 56}));
+			System.out.println(dest.getInt(new int[] {47, 63}));
+		}
+		catch (java.io.IOException ee) {}
+
+	}
+ /* End Test */
+}
diff --git a/ucar/multiarray/ClipMap.java b/ucar/multiarray/ClipMap.java
new file mode 100644
index 0000000..9f739d6
--- /dev/null
+++ b/ucar/multiarray/ClipMap.java
@@ -0,0 +1,151 @@
+// $Id: ClipMap.java,v 1.2 2002-05-29 20:32:38 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * Use with MultiArrayProxy to limit the bounds of an
+ * index to the delegate on a given dimension.
+ * <p>
+ * You could "clip" a 2d MultiArray to a window using
+ * 2 of these.
+ *
+ * @see IndexMap
+ * @see MultiArrayProxy
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:38 $
+ */
+public class
+ClipMap
+		extends ConcreteIndexMap
+{
+	/**
+	 * Create an IndexMap which clips along a specific dimension.
+	 * Using this in an MultiArrayProxy will result in the
+	 * the length of dimension <code>position</code> appearing
+	 * as <code>extent</code>.
+	 *
+	 * @param position the dimension number to clip along
+	 * @param low the minimum value. 0 will map to this
+	 * @param extent the new dimension length at position
+	 */
+	public
+	ClipMap(int position, int low, int extent)
+	{
+		init(new IMap(),
+			new LengthsMap());
+		position_ = position;
+		low_ = low;
+		extent_ = extent;
+	}
+
+	/**
+	 * Create an IndexMap which clips along a specific dimension
+	 * and is functionally composed with another IndexMap.
+	 * Using this in an MultiArrayProxy will result in the
+	 * the length of dimension <code>position</code> appearing
+	 * as <code>extent</code>.
+	 *
+	 * @param prev IndexMap to be composed with this.
+	 * @param position the dimension number to clip along
+	 * @param low the minimum value. 0 will map to this
+	 * @param extent the new dimension length at position
+	 */
+	public
+	ClipMap(ConcreteIndexMap prev, int position, int low, int extent)
+	{
+		link(prev, new IMap(),
+			new LengthsMap());
+		position_ = position;
+		low_ = low;
+		extent_ = extent;
+	}
+
+	private class
+	IMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			if(key == position_)
+			{
+				return super.get(key) + low_;
+			}
+			// else
+			return super.get(key);
+		}
+
+	}
+	
+	private class
+	LengthsMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			if(key == position_)
+			{
+				return extent_;
+			}
+			// else
+			return super.get(key);
+		}
+	}
+
+	/* WORKAROUND: Inner class & blank final initialize compiler bug */
+	private /* final */ int position_;
+	private /* final */ int low_;
+	private /* final */ int extent_;
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+		final int [] shape = {48, 64};
+		MultiArrayImpl delegate =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(delegate.storage,
+					ii, ii);
+
+		}
+		IndexMap im = new ClipMap(0, 8, 32);
+		MultiArray ma = new MultiArrayProxy(delegate, im);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {1, 0}));
+		}
+		catch (java.io.IOException ee) {}
+	}
+ /* Test output java ucar.multiarray.ClipMap
+Rank  2
+Shape { 32, 64 }
+576
+  */
+ /* End Test */
+}
diff --git a/ucar/multiarray/ConcreteIndexMap.java b/ucar/multiarray/ConcreteIndexMap.java
new file mode 100644
index 0000000..ac71c28
--- /dev/null
+++ b/ucar/multiarray/ConcreteIndexMap.java
@@ -0,0 +1,461 @@
+// $Id: ConcreteIndexMap.java,v 1.3 2002-05-29 20:32:38 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array; // used by ZZMap
+
+/**
+ * Base class which provides framework for implementations of
+ * IndexMap. This class contains two instances of the inner class ZZMap,
+ * which are used to implement the two transformations required by IndexMap.
+ * <p>
+ * This class also supports a functional composition framework via
+ * the link() initializer.
+ * <p>
+ * All methods except contructors are final.
+ * Specialization in subclasses occurs by subclassing
+ * the ZZMap inner class.
+ * Subclasses provide different ZZMaps during construction.
+ * <p>
+ * This specialization strategy results in an implementation oddity.
+ * When instances of a subclass of this are being constructed,
+ * this class's constructor (as super(...)) must complete before
+ * instances of the subclass inner class may be created.
+ * "Can't reference this before the superclass constructor
+ *  has been called." So, rather that initializing the ZZMap
+ *  members contained by this in the constructors, we provide
+ *  init() and link() members functions. One or the other of
+ *  these should be called by every subclass constructor.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.3 $ $Date: 2002-05-29 20:32:38 $
+ */
+public class
+ConcreteIndexMap
+	implements IndexMap
+{
+
+/**
+ * An Map of int by int key. (Z is the math symbol for integers).
+ * A Map maps keys to values.
+ * A Map cannot contain duplicate keys;
+ * each key can map to at most one value.
+ * For this implementation, the keys are restricted to non-negative
+ * integers, and we only use non-negative integers as values.
+ * <p>
+ * A ZZMap is like a readonly 1-d array of int.
+ * The <code>size()</code> method returns the array length.
+ * The <code>get(int ii)</code> method returns the int stored at
+ * position <code>ii</code>;
+ * <p>
+ * This class also supports a functional composition framework via
+ * the setPrev() method. The implementation of get(int) and size()
+ * provided here is simply the identity composed with whatever.
+ * Subclasses will override this functionality.
+ *
+ */
+protected class
+ZZMap
+{
+	/**
+	 */
+	protected
+	ZZMap()
+	{
+		rebind((int []) null);
+	}
+
+	/**
+	 * Construct a ZZMap form the functional composition
+	 * of the new Map with another.
+	 * rebind(int [] range) calls prev.rebind(range),
+	 * get(int key) is composed as get(prev.get(key))
+	 * @param prev ZZMap this is composed with.
+	 */
+	protected
+	ZZMap(ZZMap prev)
+	{
+		setPrev(prev);
+	}
+
+	/**
+	 * Returns the value to which this Map maps the specified key.
+	 * If you think of this as a 1-d  array of int, then
+	 * ia.get(ii) is like ia[ii].
+	 * @param key int
+	 * @return int value
+	 */
+	synchronized int
+	get(int key)
+	{
+		if(prev_ instanceof ZZMap)
+			return ((ZZMap)prev_).get(key);
+		// else
+		try {
+			return Array.getInt(prev_, key);
+		}
+		catch (RuntimeException ex) {
+			/*
+			 * rebind() was not called (ex isa NullPointerException)
+			 * or called with a value which doesn't make sense for
+			 * the key (ex isa ArrayIndexOutOfBoundsException)
+			 */
+			throw new IllegalArgumentException("Improper Binding");
+		}
+	}
+
+	/**
+	 * Rebind (redefine) the range of get(int)
+	 * @param range int array which defines the get(int)
+	 * member.
+	 */
+	synchronized final void
+	rebind(int [] range)
+	{
+		if(prev_ instanceof ZZMap)
+		{
+			((ZZMap)prev_).rebind(range);
+			return;
+		}
+		// else
+		prev_ = range;
+	}
+
+	/**
+	 * Returns the number of key-value mappings in this Map.
+	 * If you think of this as a 1-d  array of int, then
+	 * ia.size() is like ia.length.
+	 * @return int size
+	 */
+	synchronized int
+	size()
+	{
+		if(prev_ instanceof ZZMap)
+			return ((ZZMap)prev_).size();
+		// else
+		try {
+			return Array.getLength(prev_);
+		}
+		catch (NullPointerException npe) {
+			return 0;
+		}
+	}
+
+	/**
+	 * Form the functional composition
+	 * of this Map with another.
+	 * rebind(int [] range) calls prev.rebind(range),
+	 * get(int key) is composed as get(prev.get(key))
+	 * @param prev ZZMap this is composed with.
+	 */
+	synchronized final void
+	setPrev(ZZMap prev)
+	{
+		if(prev_ instanceof ZZMap)
+		{
+			((ZZMap)prev_).setPrev(prev);
+			return;
+		}
+		// else
+		prev_ = prev;
+	}
+
+	/**
+	 * The range of the get(int) function.
+	 * Either an array of ints or another ZZMap.
+	 */
+	private Object prev_;
+
+	public String
+	toString()
+	{
+		StringBuffer buf = new StringBuffer(
+	    		super.toString()
+		);
+		final int sz = size();
+		buf.append(" [");
+		buf.append(sz);
+		buf.append("]");
+		buf.append(" {");
+		final int last = sz -1;
+		for(int ii = 0; ii < sz ; ii++)
+		{
+			buf.append(get(ii));
+			if(ii == last)
+				break; // normal loop exit
+			buf.append(", ");
+		}
+		buf.append("}");
+		return buf.toString();
+	}
+
+} /* End Inner Class ZZMap */
+
+	/**
+	 * Only constructor is protected.
+	 * This is a base class, clients only
+	 * create instances of the subclasses.
+	 */
+	protected
+	ConcreteIndexMap() {}
+
+
+	/**
+	 * Called by subclass constructors to initialize.
+	 * Used for standalone or "leaf" instances.
+	 * See "implementation oddity" above.
+	 * @param iMap ZZMap defining the forward transform.
+	 * @param lengthsMap ZZMap defining the reverse transform.
+	 */
+	protected final void
+	init(ZZMap iMap, ZZMap lengthsMap)
+	{
+		iMap_ = iMap;
+		lengthsMap_ = lengthsMap;
+	}
+
+	/**
+	 * Called by subclass constructors to initialize.
+	 * Used for standalone or "leaf" instances when
+	 * the reverse transformation (lengthsMap) is the
+	 * identity.
+	 * See "implementation oddity" above.
+	 * @param iMap ZZMap defining the forward transform.
+	 */
+	protected final void
+	init(ZZMap iMap)
+	{
+		init(iMap, new ZZMap());
+	}
+
+	/**
+	 * Called by subclass constructors to initialize.
+	 * Used when nested constructors are used to form
+	 * functional composition of IndexMaps.
+	 * See "implementation oddity" above.
+	 * @param prev ConcreteIndexMap this is composed with.
+	 * @param iMap ZZMap defining the forward transform.
+	 * @param lengthsMap ZZMap defining the reverse transform.
+	 */
+	protected final void
+	link(ConcreteIndexMap prev, ZZMap iMap, ZZMap lengthsMap)
+	{
+		iMap_ = prev.iMap_;
+		lengthsMap_ = lengthsMap;
+
+		iMap_.setPrev(iMap);
+		lengthsMap_.setPrev(prev.lengthsMap_);
+	}
+
+	/**
+	 * Called by subclass constructors to initialize.
+	 * Used when nested constructors are used to form
+	 * functional composition of IndexMaps.
+	 * This form is used when the reverse transform (lengthsMap)
+	 * is the identity
+	 * See "implementation oddity" above.
+	 * @param prev ConcreteIndexMap this is composed with.
+	 * @param iMap ZZMap defining the forward transform.
+	 */
+	protected final void
+	link(ConcreteIndexMap prev, ZZMap iMap)
+	{
+		link(prev, iMap,
+			 new ZZMap()); // TODO: can we use prev.lengthsMap_?
+	}
+
+/* Begin IndexMap impl */
+
+	public final synchronized int
+	getOutputLength()
+	{
+		return iMap_.size();
+	}
+
+	public final synchronized void
+	setInput(int [] input)
+	{
+		iMap_.rebind(input);
+	}
+
+	public final synchronized int []
+	getTransformed(int [] output)
+	{
+		final int sz = getOutputLength();
+		for(int ii = 0; ii < sz; ii++)
+			output[ii] = iMap_.get(ii);
+		return output;
+	}
+
+	public final synchronized int []
+	transform(int [] output, int [] input)
+	{
+		setInput(input);
+		return getTransformed(output);
+	}
+
+	public final synchronized int
+	getRank()
+	{
+		return lengthsMap_.size();
+	}
+
+	public final synchronized void
+	setLengths(int [] lengths)
+	{
+		lengthsMap_.rebind(lengths);
+		if(getRank() < 0)
+			throw new IllegalArgumentException("rank < 0");
+	}
+
+	public final synchronized int []
+	getLengths(int [] output)
+	{
+		final int sz = lengthsMap_.size();
+		for(int ii = 0; ii < sz; ii++)
+			output[ii] = lengthsMap_.get(ii);
+		return output;
+	}
+
+ /* End IndexMap Impl */
+
+	public String
+	toString()
+	{
+		StringBuffer buf = new StringBuffer(
+	    		super.toString() + "\n\t"
+		);
+		buf.append(iMap_.toString() + "\n\t");
+		buf.append(lengthsMap_.toString());
+		return buf.toString();
+	}
+
+	/*
+	 * Implementation note. See "implementation oddity" above.
+         * "Can't reference this before the superclass constructor
+	 *  has been called."
+	 * ==> Can't be final.
+	 */
+	/**
+	 * Supports the forward tranform.
+	 */
+	protected /* final */ ZZMap iMap_;
+	/**
+	 * Supports the reverse tranform.
+	 */
+	protected /* final */ ZZMap lengthsMap_;
+
+ /* Begin Test */
+	private static void
+	testZZMap()
+	{
+		System.out.println("Testing Inner Class ZZMap");
+		ConcreteIndexMap im = new ConcreteIndexMap();
+
+		System.out.println("Unbound:");
+		ZZMap zm = im. new ZZMap();
+		System.out.println("\t" + zm);
+		ZZMap next = im. new ZZMap(zm);
+		System.out.println("\t" + next);
+
+		System.out.println("Bernoulli");
+		int [] ia = {1, 1, 2, 3, 5, 8, 13};
+		zm.rebind(ia);
+		System.out.println("\t" + zm);
+		System.out.println("\t" + next);
+
+		System.out.println("Rebound");
+		int [] ia2 = {1, 2, 4, 8};
+		next.rebind(ia2);
+		System.out.println("\t" + zm);
+		System.out.println("\t" + next);
+
+		System.out.println("End ZZMap Test");
+	}
+
+	private static void
+	testInit()
+	{
+		System.out.println("Testing init() and link() ");
+		ConcreteIndexMap im = new ConcreteIndexMap();
+		im.init(im. new ZZMap(), im. new ZZMap());
+		System.out.println("Unbound:        " + im);
+		ConcreteIndexMap next = new ConcreteIndexMap();
+		next.link(im, next. new ZZMap(), next .new ZZMap());
+		System.out.println("Next Unbound:   " + next);
+
+		int [] ia = {1, 1, 2, 3, 5, 8, 13};
+		int [] ia2 = {1, 2, 4, 8};
+		next.setInput(ia);
+		next.setLengths(ia2);
+		System.out.println("forward  :      " + im);
+		System.out.println("Next forward  : " + next);
+
+		next.setInput(ia2);
+		next.setLengths(ia);
+		System.out.println("reversed:       " + im);
+		System.out.println("Next reversed:  " + next);
+
+		System.out.println("End init(), link() test");
+	}
+
+	public static void
+	main(String[] args)
+	{
+		testZZMap();
+		testInit();
+		// TODO more complete
+	}
+
+ /* Test output java ucar.multiarray.ConcreteIndexMap
+Testing Inner Class ZZMap
+Unbound:
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce7bc [0] {}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce7ec [0] {}
+Bernoulli
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce7bc [7] {1, 1, 2, 3, 5, 8, 13}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce7ec [7] {1, 1, 2, 3, 5, 8, 13}
+Rebound
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce7bc [4] {1, 2, 4, 8}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce7ec [4] {1, 2, 4, 8}
+End ZZMap Test
+Testing init() and link()
+Unbound:        ucar.multiarray.ConcreteIndexMap at 8ce890
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88f [0] {}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88e [0] {}
+Next Unbound:   ucar.multiarray.ConcreteIndexMap at 8ce8ac
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88f [0] {}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce8aa [0] {}
+forward  :      ucar.multiarray.ConcreteIndexMap at 8ce890
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88f [7] {1, 1, 2, 3, 5, 8, 13}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88e [4] {1, 2, 4, 8}
+Next forward  : ucar.multiarray.ConcreteIndexMap at 8ce8ac
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88f [7] {1, 1, 2, 3, 5, 8, 13}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce8aa [4] {1, 2, 4, 8}
+reversed:       ucar.multiarray.ConcreteIndexMap at 8ce890
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88f [4] {1, 2, 4, 8}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88e [7] {1, 1, 2, 3, 5, 8, 13}
+Next reversed:  ucar.multiarray.ConcreteIndexMap at 8ce8ac
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce88f [4] {1, 2, 4, 8}
+	ucar.multiarray.ConcreteIndexMap$ZZMap at 8ce8aa [7] {1, 1, 2, 3, 5, 8, 13}
+End init(), link() test
+  */
+ /* End Test */
+}
diff --git a/ucar/multiarray/DecimateMap.java b/ucar/multiarray/DecimateMap.java
new file mode 100644
index 0000000..63a2b6e
--- /dev/null
+++ b/ucar/multiarray/DecimateMap.java
@@ -0,0 +1,190 @@
+// $Id: DecimateMap.java,v 1.2 2002-05-29 20:32:38 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * Use with MultiArrayProxy to reduce the length along a particular
+ * dimension by sampling the domain according to a (repeated) pattern.
+ *
+ * @see IndexMap
+ * @see MultiArrayProxy
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:38 $
+ */
+public class
+DecimateMap
+		extends ConcreteIndexMap
+{
+	/**
+	 * Create an ConcreteIndexMap which decimates along
+	 * a specific dimension.
+	 * Using this in an MultiArrayProxy will result in the
+	 * the length of dimension <code>position</code> appearing
+	 * smaller. The values which will show through are selected
+	 * by the pattern argument.
+	 *
+	 * @param position the dimension number to clip along
+	 * @param pattern index values along the dimension will
+	 *	show through where pattern is set to <code>true</code>.
+	 *	If pattern.length is less than the source dimension length,
+	 *	the pattern is repeated.
+	 */
+	public
+	DecimateMap(int position, boolean [] pattern)
+	{
+		init(new IMap(),
+			new LengthsMap());
+		position_ = position;
+		pattern_ = (boolean []) pattern.clone();
+		nset_ = nbset(pattern_, pattern_.length);
+	}
+
+	/**
+	 * Create an ConcreteIndexMap which decimates along
+	 * a specific dimension.
+	 * Using this in an MultiArrayProxy will result in the
+	 * the length of dimension <code>position</code> appearing
+	 * smaller. The values which will show through are selected
+	 * by the pattern argument.
+	 *
+	 * @param prev ConcreteIndexMap to be composed with this.
+	 * @param position the dimension number to clip along
+	 * @param pattern index values along the dimension will
+	 *	show through where pattern is set to <code>true</code>.
+	 *	If pattern.length is less than the source dimension length,
+	 *	the pattern is repeated.
+	 */
+	public
+	DecimateMap(ConcreteIndexMap prev, int position, boolean [] pattern)
+	{
+		link(prev, new IMap(),
+			new LengthsMap());
+		position_ = position;
+		pattern_ = (boolean []) pattern.clone();
+		nset_ = nbset(pattern_, pattern_.length);
+	}
+
+	private class
+	IMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			if(key == position_)
+			{
+				// TODO: better algorithm ?
+				final int input = super.get(key);
+				final int nstrides = input / nset_;
+				final int which = nset_ > 1 ? input % nset_ : 0;
+				int offset = 0;
+				for(int nhits = 0; offset < pattern_.length;
+						offset++)
+				{
+					if(pattern_[offset])
+					{
+						nhits++;
+						if(nhits > which)
+							break; // normal exit
+					}
+				}
+				return nstrides * pattern_.length + offset;
+			}
+			// else
+			return super.get(key);
+			
+		}
+	
+	}
+	
+	private class
+	LengthsMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			final int plen = super.get(key);
+			if(key != position_)
+				return plen;
+			// else
+			final int len = (plen / pattern_.length) * nset_;
+			final int rem = plen % pattern_.length;
+			if(rem == 0)
+				return len;
+			// else
+			return len + nbset(pattern_, rem);
+		}
+	}
+
+	/**
+	 * Compute the number of <code>true</code>
+	 * elements in <code>apattern</code>.
+	 */
+	static private int
+	nbset(boolean [] apattern, int len)
+	{
+		int nhits = 0;
+		for(int ii = 0; ii < len; ii++)
+			if(apattern[ii])
+				nhits++;
+		return nhits;
+	}
+
+	/* WORKAROUND: Inner class & blank final initialize compiler bug */
+	private /* final */ int position_;
+	private /* final */ boolean [] pattern_;
+	private /* final */ int nset_;
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+		final int [] shape = {48, 64};
+		MultiArrayImpl delegate =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(delegate.storage,
+					ii, ii);
+
+		}
+		boolean [] pattern = new boolean [] { true, false, true };
+		IndexMap im = new DecimateMap(0, pattern);
+		MultiArray ma = new MultiArrayProxy(delegate, im);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {1, 0}));
+		}
+		catch (java.io.IOException ee) {}
+	}
+ /* Test output java ucar.multiarray.DecimateMap
+Rank  2
+Shape { 32, 64 }
+128
+  */
+ /* End Test */
+}
diff --git a/ucar/multiarray/FlattenMap.java b/ucar/multiarray/FlattenMap.java
new file mode 100644
index 0000000..d68f2d4
--- /dev/null
+++ b/ucar/multiarray/FlattenMap.java
@@ -0,0 +1,184 @@
+// $Id: FlattenMap.java,v 1.2 2002-05-29 20:32:39 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * Use with MultiArrayProxy to reduce apparent rank by
+ * merging adjacent dimensions. The total number of elements
+ * remains constant.
+ * <p>
+ * This framework doesn't really support this operation
+ * very well. See caveats in <code>get()</code>
+ *
+ * @see IndexMap
+ * @see MultiArrayProxy
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:39 $
+ */
+public class
+FlattenMap
+		extends ConcreteIndexMap
+{
+	/**
+	 * Create an ConcreteIndexMap which merges two adjacent dimensions.
+	 * Using this in an MultiArrayProxy will result in the
+	 * a MultiArray of one less rank.
+	 * @param position this dimension and
+	 *	dimension <code>(position +1)</code>
+	 *	will appear as a single dimension.
+	 */
+	public
+	FlattenMap(int position)
+	{
+		init(new IMap(),
+			new LengthsMap());
+		position_ = position;
+	}
+
+	/**
+	 * Create an ConcreteIndexMap which merges two adjacent dimensions.
+	 * Using this in an MultiArrayProxy will result in the
+	 * a MultiArray of one less rank.
+	 * @param prev ConcreteIndexMap to be composed with this.
+	 * @param position this dimension and
+	 *	dimension <code>(position +1)</code>
+	 */
+	public
+	FlattenMap(ConcreteIndexMap prev, int position)
+	{
+		link(prev, new IMap(),
+			new LengthsMap());
+		position_ = position;
+	}
+
+	private class
+	IMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			if(key < position_)
+				return super.get(key);
+			if(key == position_)
+			{
+				length_ = ((LengthsMap)lengthsMap_)
+						.superGet(key + 1);
+				final int got = super.get(key);
+				value_ = got % length_;
+				return got / length_;
+			}
+			if(key == position_ +1)
+				return value_;
+			// else
+			return super.get(key - 1);
+			
+		}
+	
+		public synchronized int
+		size()
+		{
+			return super.size() +1;
+		}
+
+		private int length_;
+		private int value_;
+	}
+
+	private class
+	LengthsMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			if(key < position_)
+				return super.get(key);
+			// else
+			if(key == position_)
+				return super.get(key) *
+					super.get(key + 1);
+			// else
+				return super.get(key +1);
+		}
+
+		public synchronized int
+		size()
+		{
+			return super.size() -1;
+		}
+
+		int
+		superGet(int key)
+		{
+			return super.get(key);
+		}
+
+	}
+
+ /**/
+	/* WORKAROUND: Inner class & blank final initialize compiler bug */
+	private /* final */ int position_;
+
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+		final int [] shape = {32, 48, 64};
+		MultiArrayImpl delegate =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(delegate.storage,
+					ii, ii);
+
+		}
+		ConcreteIndexMap im = new FlattenMap(1);
+		MultiArray ma = new MultiArrayProxy(delegate, im);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0]
+					 + ", " + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0}));
+			System.out.println(ma.getInt(new int[] {0, 1}));
+			System.out.println(ma.getInt(new int[] {0, 63}));
+			System.out.println(ma.getInt(new int[] {0, 64}));
+			System.out.println(ma.getInt(new int[] {0, 3071}));
+			System.out.println(ma.getInt(new int[] {1, 0}));
+		}
+		catch (java.io.IOException ee) {}
+	}
+ /* Test output java ucar.multiarray.FlattenMap
+Rank  2
+Shape { 32, 3072 }
+0
+1
+63
+64
+3071
+3072
+  */
+ /* End Test */
+}
diff --git a/ucar/multiarray/FlipMap.java b/ucar/multiarray/FlipMap.java
new file mode 100644
index 0000000..dd91f4e
--- /dev/null
+++ b/ucar/multiarray/FlipMap.java
@@ -0,0 +1,119 @@
+// $Id: FlipMap.java,v 1.2 2002-05-29 20:32:39 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * Use with MultiArrayProxy to flip (invert) the
+ * indexing along a particular dimension.
+ * Maps {0, 1, ..., N-1} to {N-1, N-2, ..., 0} where
+ * N is the length of the dimension.
+ *
+ * @see IndexMap
+ * @see MultiArrayProxy
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:39 $
+ */
+public class
+FlipMap
+		extends ConcreteIndexMap
+{
+	/**
+	 * Create an IndexMap which flips the indexing
+	 * for a particular dimension.
+	 *
+	 * @param position the dimension number where the index
+	 * is to be flipped.
+	 */
+	public
+	FlipMap(int position)
+	{
+		init(new IMap());
+		position_ = position;
+	}
+
+	/**
+	 * Create an IndexMap which flips the indexing
+	 * for a particular dimension and is functionally composed
+	 * with another IndexMap.
+	 *
+	 * @param prev ConcreteIndexMap to be composed with this.
+	 * @param position the dimension number where the index
+	 * is to be flipped.
+	 */
+	public
+	FlipMap(ConcreteIndexMap prev, int position)
+	{
+		link(prev, new IMap());
+		position_ = position;
+	}
+
+	private class
+	IMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			final int value = super.get(key);
+			if(key == position_)
+				return (lengthsMap_.get(key) -1 - value);
+			// else
+			return value;
+		}
+	}
+
+	/* WORKAROUND: Inner class & blank final initialize compiler bug */
+	private /* final */ int position_;
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+		final int [] shape = {48, 64};
+		MultiArrayImpl delegate =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(delegate.storage,
+					ii, ii);
+
+		}
+		IndexMap im = new FlipMap(1);
+		MultiArray ma = new MultiArrayProxy(delegate, im);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0]
+					 + " " + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0}));
+		}
+		catch (java.io.IOException ee) {}
+	}
+ /* Test output java ucar.multiarray.FlipMap
+Rank  2
+Shape { 48 64 }
+63
+  */
+ /* End Test */
+}
diff --git a/ucar/multiarray/IndexIterator.java b/ucar/multiarray/IndexIterator.java
new file mode 100644
index 0000000..fb50f3e
--- /dev/null
+++ b/ucar/multiarray/IndexIterator.java
@@ -0,0 +1,234 @@
+// $Id: IndexIterator.java,v 1.2 2002-05-29 20:32:39 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+
+/**
+ * An IndexIterator is a helper class used for stepping through the
+ * index values of a MultiArray.
+ * <p>
+ * This is like an odometer. The number of columns or rings on the odometer
+ * is the length of the constructor argument. The number of values on
+ * each ring of the odometer is specified in the limits argument of
+ * the constructor.
+ * <p>
+ * Currently no synchronized methods.
+ * 
+ * @see MultiArray
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:39 $
+ */
+public class IndexIterator {
+
+	/**
+	 * Return <code>true</code> iff the argument
+	 * is the zero index.
+	 */
+	static public boolean
+	isZero(int [] iv)
+	{
+		for(int ii = 0; ii < iv.length; ii++)
+			if(iv[ii] != 0)
+				return false;
+		return true;
+	}
+
+	/**
+	 * Return <code>true</code> iff the arguments have
+	 * same values.
+	 */
+	static public boolean
+	equals(int [] lhs, int [] rhs)
+	{
+		if(lhs == rhs)
+			return true;
+		// else
+		if(lhs.length != rhs.length)
+			return false;
+		// else
+		for(int ii = 0; ii < lhs.length; ii++)
+			if(lhs[ii] != rhs[ii])
+				return false;
+		return true;
+	}
+
+	/**
+         * Creates a new IndexIterator whose variation is bounded by the
+	 * component values of the argument.
+	 * @param theLimits typically <code>ma.getLengths()</code>
+	 * for some MultiArray <code>ma</code>
+	 */
+	public
+	IndexIterator(int [] theLimits)
+	{
+		counter = new int[theLimits.length];
+		limits = theLimits; // N.B. Not a copy
+		ncycles = 0;
+	}
+
+	/**
+         * Creates a new IndexIterator with initial counter value,
+	 * whose variation is bounded by the
+	 * component values of the <code>limits</code> argument.
+	 * @param initCounter the initial value.
+	 * @param theLimits typically <code>ma.getLengths()</code>
+	 * for some MultiArray <code>ma</code>
+	 */
+	public
+	IndexIterator(int [] initCounter, int [] theLimits)
+	{
+		if(initCounter == null)
+			counter = new int[theLimits.length];
+		else
+			counter = (int []) initCounter.clone();
+		limits = theLimits; // N.B. Not a copy
+		ncycles = 0;
+	}
+
+	/**
+	 * If the IndexIterator has not yet "rolled over",
+	 * return <code>true</code>.
+	 * Useful for loop end detection.
+	 */
+    	public boolean
+	notDone()
+	{
+		if(ncycles > 0)
+			return false;
+		return true;
+	}
+
+	/**
+	 * Return the current counter value.
+	 * N.B. Not a copy!
+	 */
+    	public int []
+	value()
+	{
+		return counter;
+	}
+
+	/**
+	 * Increment the counter value
+	 */
+	public void
+	incr()
+	{
+		int digit = counter.length -1;
+		if(digit < 0)
+		{
+			// counter is zero length array <==> scalar
+			ncycles++;
+			return;
+		}
+		while(digit >= 0)
+		{
+			counter[digit]++;
+			if(counter[digit] < limits[digit])
+				break; // normal exit
+			// else, carry
+			counter[digit] = 0;
+			if(digit == 0)
+			{
+				ncycles++; // rolled over
+				break;
+			}
+			// else
+			digit--;
+		}
+	}
+
+	/**
+         * Increment the counter value 
+	 * @param nsteps the number of times to increment the value.
+	 */
+	public void
+	advance(int nsteps)
+	{
+		// TODO: make this smarter and faster;
+		while(nsteps-- > 0)
+			incr();
+	}
+
+	public String
+	toString()
+	{
+		StringBuffer buf = new StringBuffer();
+		final int last = counter.length -1;
+		for(int ii = 0; ii <= last; ii++)  
+		{
+			buf.append(counter[ii]);
+			if(ii == last)
+				break; // normal loop exit
+			buf.append(" ");
+		}
+		return buf.toString();
+	}
+
+	/**
+	 * Test
+	 */
+	public static void
+	main (String args[])
+	{
+		/*
+		 * Translate the command line args into an array of int.
+		 */
+		int [] edges = new int[args.length];
+		int ii;
+		for(ii = 0; ii < args.length; ii++)
+		{
+			final Integer av = new Integer(args[ii]);
+			edges[ii] = av.intValue();
+		}
+		System.out.println(edges);
+
+		
+		/*
+		 * Example usage
+		 */
+		IndexIterator odo = new IndexIterator(edges);
+		for(ii = 0; odo.notDone(); odo.incr(), ii++)
+		{
+			System.out.println(odo);
+		}
+		System.out.print("\t");
+		System.out.println(ii);
+	}
+
+	/**
+	 * The counter value. Initialized to zero. The length
+	 * is the same as limits.length.
+         */
+	protected final int[] counter;
+
+	/**
+	 * (Reference to) the constructor argument which determines
+	 * counter value variation.
+	 * <code>for 0 <= ii < limits.length, counter[ii] < limits[ii]</code>
+	 */
+	protected final int[] limits;
+
+	/**
+	 * A "carry" indicator,
+	 * the number of times the counter value has rolled over.
+	 */
+	protected int ncycles;
+}
diff --git a/ucar/multiarray/IndexMap.java b/ucar/multiarray/IndexMap.java
new file mode 100644
index 0000000..9355bae
--- /dev/null
+++ b/ucar/multiarray/IndexMap.java
@@ -0,0 +1,137 @@
+// $Id: IndexMap.java,v 1.2 2002-05-29 20:32:39 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+
+/**
+ * This interface defines the services required by
+ * MultiArrayProxy to manipulate indexes and the dimensions
+ * of a MultiArray. There are two transformations represented.
+ * Each goes from <code>int []</code> to <code>int []</code>.
+ * (The <code>int</code> values should actually be non-negative,
+ * as array indexes or array sizes.)
+ * <p>
+ * The transform most often used takes a MultiArrayProxy index
+ * and returns an index suitable for accessing the hidden 'backing'
+ * MultiArray. The
+ * <code>int [] transform(int [] output, int [] input)</code>
+ * does this. Refer to this as the forward transform.
+ * The <code>setInput(int [] input)</code> procedure binds the
+ * input array reference to the forward transformation. The transformation
+ * of values in the input array is obtained by calling
+ * <code>int [] getTransformed(int [] output)</code>.
+ * Note that the reference to input is bound, not a copy.
+ * This allows changes in values of the input array to be
+ * reflected in subsequent calls to <code>getTransformed</code>
+ * with needing to call <code>setInput</code> again.
+ * <p>
+ * The other transform is used to determine the proxy shape.
+ * It takes the shape of the backing MultiArray as input.
+ * It goes in the opposite direction as the forward transform;
+ * refer to this as the reverse transform. (Note: it is not
+ * an inverse.) The
+ * <code>setLengths(int [] lengths)</code>
+ * is analogous to <code>setInput()</code> above. It is typically
+ * called once during MultiArrayProxy initialization.
+ * The function 
+ * <code>int [] getLengths(int [] output)</code> is analogous to
+ * <code>getTransformed</code>. It is used to implement the
+ * proxy getLengths() method.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:39 $
+ */
+
+public interface
+IndexMap
+{
+	/**
+	 * Return the length needed for an output vector.
+	 * Will throw an exception if called before <code>setInput()</code>.
+	 */
+	public int
+	getOutputLength();
+
+	/**
+	 * Rebind the domain of <code>getTransformed()</code>
+	 * @param input int array domain reference member.
+	 */
+	void
+	setInput(int [] input);
+
+	/**
+	 * Transform the current input, placing the results in
+	 * <code>output</output>.
+	 * @param output int array storage for the result.
+	 * 	The elements of <code>output</output> are usually
+	 *	modified by this call.
+	 * @return output
+	 */
+	int []
+	getTransformed(int [] output);
+
+	/**
+	 * Perform the forward transform.
+	 * <p>
+	 * This function is equivalent to
+	 * <code>
+	 *	setInput(input);
+	 *	return getTransformed(output);
+	 * </code>
+	 *
+	 * @param output int array storage for the result.
+	 * 	The elements of <code>output</output> are usually
+	 *	modified by this call.
+	 * @param input int array which is the index to be transformed.
+	 * @return output
+	 */ 
+	int []
+	transform(int [] output, int [] input);
+
+	/**
+	 * Return the length of input vectors.
+	 * Will throw an exception if called before <code>setLengths()</code>.
+	 */
+	int
+	getRank();
+
+	/**
+	 * Initialize or reinitialize the IndexMap.
+	 * Binds the domain of <code>getLengths()</code>,
+	 * <code>getRank()</code>.
+	 * @param lengths int array representing the shape on the forward
+	 * transform output.
+	 */
+	void
+	setLengths(int [] lengths);
+
+	/**
+	 * Reverse transform the lengths, placing the results in
+	 * <code>output</output>.
+	 * Will throw an exception if called before <code>setLengths()</code>.
+	 *
+	 * @param output int array storage for the result.
+	 * 	The elements of <code>output</output> are usually
+	 *	modified by this call.
+	 * @return output
+	 */
+	int []
+	getLengths(int [] output);
+}
diff --git a/ucar/multiarray/IntArrayAdapter.java b/ucar/multiarray/IntArrayAdapter.java
new file mode 100644
index 0000000..222b8f4
--- /dev/null
+++ b/ucar/multiarray/IntArrayAdapter.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 1997, University Corporation for Atmospheric Research
+ * See COPYRIGHT file for copying and redistribution conditions.
+ */
+
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * Apply the "Adapter" pattern to
+ * convert the interface of Class (int []) to interface IntMap.
+ * <p>
+ * Instances of this class are constructed automatically
+ * for you when no 'next' argument is provided to the other
+ * IntMap constructors.
+ *
+ * @author $Author: dglo $
+ * @version $Revision: 1.1.1.1 $ $Date: 2000-08-28 21:42:24 $
+ */
+public class
+IntArrayAdapter
+		implements IntMap
+{
+	public
+	IntArrayAdapter()
+		{ /*EMPTY*/ }
+
+	/**
+	 * Returns the value to which this Map maps the specified key.
+	 * @return int adaptee[key];
+	 */
+	public int
+	get(int key)
+		{ return adaptee[key]; }
+
+	/**
+	 * Returns the number of key-value mappings in this Map.
+	 * @return adaptee.length
+	 */
+	public int
+	size()
+		{ return adaptee.length; }
+
+	/**
+	 * Instances of this class are always
+	 * the tail of an IntMap chain.
+	 * Initialize the prev member.
+	 * Call <code>this.rebind(new int[rank]);</code>
+	 * @return this
+	 */
+	public IntArrayAdapter
+	tail(int rank, Object prev)
+	{
+		this.prev = prev;
+		rebind(new int[rank]);
+		return this;
+	}
+
+	/**
+         * Traverse the inverse mapping chain to
+         * retrieve the dimension length at ii.
+	 */
+	public int
+	getLength(int ii)
+	{
+		if(prev instanceof IntMap)
+			return ((IntMap)prev).getLength(ii);
+		return Array.getInt(prev, ii);
+	}
+
+	/**
+	 * Reset the adaptee converted by this.
+	 */
+	public void
+	rebind(int [] newAdaptee)
+	{
+		adaptee = newAdaptee;
+	}
+
+ /* */
+	/**
+	 * Either an IntMap delegate for getLength(int)
+	 * or an array of ints which can answer the
+	 * question directly.
+	 */
+	private Object prev;
+
+	private int [] adaptee;
+}
diff --git a/ucar/multiarray/IntMap.java b/ucar/multiarray/IntMap.java
new file mode 100644
index 0000000..babc15a
--- /dev/null
+++ b/ucar/multiarray/IntMap.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 1997, University Corporation for Atmospheric Research
+ * See COPYRIGHT file for copying and redistribution conditions.
+ */
+
+package ucar.multiarray;
+
+/**
+ * An immutable Map of int by int key.
+ * An Map maps keys to values.
+ * A Map cannot contain duplicate keys;
+ * each key can map to at most one value.
+ * <p>
+ * An IntMap is like a readonly 1-d array of int.
+ * The <code>size()</code> method returns the array length.
+ * The <code>get(int ii)</code> method returns the int stored at
+ * position <code>ii</code>;
+ * <p>
+ * MultiArray uses array of int for as index (key) values.
+ * This interface is an abstraction of those, so that we can
+ * implement transformations on them. Beyond the <code>get()</code> and
+ * <code>size()</code> methods of the map abstraction, methods used in the
+ * context of MultiArrayProxy are present to support connecting the
+ * reverse chain of a linked list of IntMap and to traverse the inverse
+ * map to discover the shape.
+ * 
+ * @see ClipMap
+ * @see DecimateMap
+ * @see FlattenMap
+ * @see SliceMap
+ * @see TransposeMap
+ * @see MultiArrayProxy
+ * @see IntArrayAdapter
+ *
+ * @author $Author: dglo $
+ * @version $Revision: 1.1.1.1 $ $Date: 2000-08-28 21:42:24 $
+ */
+public interface
+IntMap
+{
+	/**
+	 * Returns the value to which this Map maps the specified key.
+	 * If you think of this as a 1-d  array of int, then 
+	 * ia.get(ii) is like ia[ii].
+	 * @param key int
+	 * @return int value
+	 */
+	public int
+	get(int key);
+
+	/**
+	 * Returns the number of key-value mappings in this Map.
+	 * If you think of this as a 1-d  array of int, then 
+	 * ia.size() is like ia.length.
+	 * @return int size
+	 */
+	public int
+	size();
+
+	/**
+	 * Return the tail of a chain of IntMap.
+	 * As side effects, connect the prev members and
+	 * initialize the rank at the tail.
+	 */
+	public IntArrayAdapter
+	tail(int rank, Object prev);
+
+	/**
+	 * Traverse the inverse mapping chain to
+	 * retrieve the dimension length at ii.
+	 */
+	public int
+	getLength(int ii);
+}
diff --git a/ucar/multiarray/MultiArray.java b/ucar/multiarray/MultiArray.java
new file mode 100644
index 0000000..934d9b6
--- /dev/null
+++ b/ucar/multiarray/MultiArray.java
@@ -0,0 +1,58 @@
+// $Id: MultiArray.java,v 1.3 2003-02-03 20:09:04 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.io.IOException;
+
+/**
+ *  Interface for multidimensional arrays.
+ *  Includes introspection by extending MultiArrayInfo and
+ *  data access by extending Accessor.
+ *  <p>
+ *  These are more general and abstract than Netcdf Variables.
+ *  Netcdf Variables implement this, but more general objects,
+ *  such as java arrays, can be simply wrapped to provide
+ *  this interface.
+ *
+ * @see MultiArrayInfo
+ * @see Accessor
+ * @see ucar.netcdf.Variable
+ * @see MultiArrayImpl
+ * @see ArrayMultiArray
+ * @see ScalarMultiArray
+ * @see MultiArrayProxy
+ * @author $Author: donm $
+ * @version $Revision: 1.3 $ $Date: 2003-02-03 20:09:04 $
+ */
+public interface
+MultiArray
+	extends MultiArrayInfo, Accessor
+{
+	/* The super interfaces say it all */
+	/**
+	 * @return a the original one dimensional Array containing all the elements
+	 * in this MultiArray
+	 */
+	public Object
+	    getStorage();
+
+
+
+}
diff --git a/ucar/multiarray/MultiArrayImpl.java b/ucar/multiarray/MultiArrayImpl.java
new file mode 100644
index 0000000..c87c0fc
--- /dev/null
+++ b/ucar/multiarray/MultiArrayImpl.java
@@ -0,0 +1,701 @@
+// $Id: MultiArrayImpl.java,v 1.3 2003-02-03 20:09:04 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * A concrete, space efficent implementation of the MultiArray interface.
+ *
+ * @see MultiArray
+ *
+ * @author $Author: donm $
+ * @version $Revision: 1.3 $ $Date: 2003-02-03 20:09:04 $
+ */
+public class
+MultiArrayImpl
+	implements MultiArray, Cloneable, Serializable
+{
+
+	/**
+	 * Used to figure out how storage is required for a
+	 * given shape.
+	 * Compute the right to left multiplicative product of the
+	 * argument.
+	 * @return int product of the dimensions.
+	 * @param dimensions the shape.
+	 */
+	static public int
+	    numberOfElements(int [] dimensions)
+	{
+		int product = 1;
+		for(int ii = dimensions.length -1; ii >= 0; ii--)
+		{
+			product *= dimensions[ii];
+		}
+		return product;
+	}
+
+	/**
+	 * Used to figure out how storage is required for a
+	 * given shape, retaining intermediate products.
+	 * Compute the right to left multiplicative product of the first
+	 * argument, modifying the second argument so that
+	 * it contains the intermediate products.
+	 * @return int product of the dimensions.
+	 * @param dimensions the shape.
+	 * @param products modified upon return to contain
+	 *	the intermediate products
+	 */
+	static public int
+	numberOfElements(int [] dimensions, int [] products)
+	{
+		int product = 1;
+		for(int ii = dimensions.length -1; ii >= 0; ii--)
+		{
+			final int thisDim = dimensions[ii];
+			if(thisDim < 0)
+				throw new NegativeArraySizeException();
+			products[ii] = product;
+			product *= thisDim;
+		}
+		return product;
+	}
+
+	/**
+	 * Create a new MultiArray of the given componentType and shape.
+	 * Storage for the values is allocated and owned by this with
+	 * default initialization.
+	 * @param componentType Class of the primitives or objects to
+	 * be contained.
+	 * @param dimensions the shape of the MultiArray.
+	 * dimensions.length determines the rank of the new MultiArray.
+	 */
+	public
+	MultiArrayImpl(Class componentType, int [] dimensions)
+	{
+		lengths = (int []) dimensions.clone();
+		products = new int[dimensions.length];
+		int product = numberOfElements(dimensions, products);
+		/*
+		 * Use array of length 1 for scalar storage
+		 */
+		if(product == 0)
+			product = 1; 
+		storage = Array.newInstance(componentType, product);
+	}
+
+
+	/**
+	 * A copy constructor.
+	 * <p>
+	 * Create a new MultiArray with the same componentType and shape
+	 * as the argument
+	 * Storage for values is allocated and owned by this, and the values
+	 * are initialized to the values of the argument.
+	 * @param ma the MultiArray to copy.
+	 */
+	public
+	MultiArrayImpl(MultiArray ma)
+		throws IOException
+	{
+		lengths = (int []) ma.getLengths().clone();
+		products = new int[lengths.length];
+		int product = numberOfElements(lengths, products);
+		/*
+		 * Use array of length 1 for scalar storage
+		 */
+		if(product == 0)
+			product = 1; 
+		storage = Array.newInstance(ma.getComponentType(), product);
+
+		IndexIterator odo = new IndexIterator(lengths);
+		for(; odo.notDone(); odo.incr())
+		{
+			final int [] index = odo.value();	
+			this.set(index, ma.get(index));
+		}
+	}
+
+	/**
+	 * Create a new MultiArrayImpl of the given shape accessing
+	 * externally created storage. It is up to the client to
+	 * to mitigate conflicting access to the external storage.
+	 * 
+	 * @param lengths the shape of the MultiArray.
+	 * @param storage array Object which is storage
+	 */
+	public
+	MultiArrayImpl(int [] lengths, Object storage)
+	{
+		this.lengths = lengths;
+		this.products = new int[lengths.length];
+		final int length = numberOfElements(this.lengths,
+				this.products);
+		if(length > Array.getLength(storage))
+			throw new IllegalArgumentException(
+				"Inadequate storage");
+		this.storage = storage;
+	}
+
+	/**
+	 * Create a new MultiArrayImple of the given shape accessing
+	 * externally created storage. It is up to the client to
+	 * to mitigate conflicting access to the external storage.
+	 * Should be a protected constructor?
+	 * @param lengths describing the shape of the MultiArray.
+	 * @param products right-to-left accumulated sizes.
+	 * @param storage array Object which is storage
+	 */
+	public
+	MultiArrayImpl(int [] lengths, int [] products, Object storage)
+	{
+		this.lengths = lengths;
+		this.products = products;
+		this.storage = storage;
+	}
+
+
+ /* MultiArray Inquiry methods from MultiArrayInfo */
+
+	/**
+	 * @see MultiArrayInfo#getComponentType
+	 */
+	public Class getComponentType()
+	{
+		 return storage.getClass().getComponentType();
+	}
+
+	/**
+	 * @see  MultiArrayInfo#getRank
+	 */
+	public int getRank() { return lengths.length;}
+
+	/**
+	 * @see MultiArrayInfo#getLengths
+	 */
+	public int [] getLengths() {
+		return (int []) lengths.clone();
+	}
+
+	/**
+	 * @see MultiArrayInfo#isUnlimited
+	 */
+	public boolean isUnlimited() { return false; }
+
+	/**
+	 * @see MultiArrayInfo#isScalar
+	 */
+	public boolean isScalar() { return 0 == getRank(); }
+
+ /* End MultiArrayInfo */
+ /* MultiArray Access methods from Accessor */
+
+	/**
+	 * @see Accessor#get
+	 */
+	public Object get(int [] index)
+	{
+		return Array.get(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getBoolean
+	 */
+	public boolean getBoolean(int[] index)
+	{
+		return Array.getBoolean(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getChar
+	 */
+	public char getChar(int[] index)
+	{
+		return Array.getChar(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getByte
+	 */
+	public byte getByte(int[] index)
+	{
+		return Array.getByte(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getShort
+	 */
+	public short getShort(int[] index)
+	{
+		return Array.getShort(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getInt
+	 */
+	public int getInt(int[] index)
+	{
+		return Array.getInt(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getLong
+	 */
+	public long getLong(int[] index)
+	{
+		return Array.getLong(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getFloat
+	 */
+	public float getFloat(int[] index)
+	{
+		return Array.getFloat(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#getDouble
+	 */
+	public double getDouble(int[] index)
+	{
+		return Array.getDouble(storage, indexMap(index));
+	}
+
+	/**
+	 * @see Accessor#set
+	 */
+	public void set(int [] index, Object value)
+	{
+		Array.set(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setBoolean
+	 */
+	public void setBoolean(int [] index, boolean value)
+	{
+		Array.setBoolean(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setChar
+	 */
+	public void setChar(int [] index, char value)
+	{
+		Array.setChar(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setByte
+	 */
+	public void setByte(int [] index, byte value)
+	{
+		Array.setByte(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setShort
+	 */
+	public void setShort(int [] index, short value)
+	{
+		Array.setShort(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setInt
+	 */
+	public void setInt(int [] index, int value)
+	{
+		Array.setInt(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setLong
+	 */
+	public void setLong(int [] index, long value)
+	{
+		Array.setLong(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setFloat
+	 */
+	public void setFloat(int [] index, float value)
+	{
+		Array.setFloat(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#setDouble
+	 */
+	public void setDouble(int[] index, double value)
+	{
+		Array.setDouble(storage, indexMap(index), value);
+	}
+
+	/**
+	 * @see Accessor#copyout
+	 */
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+	{
+		if(origin.length != lengths.length
+				|| shape.length != lengths.length)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// else
+		int ji = lengths.length -1 ;
+		for(; ji >= 0; ji--)
+		{
+			if(origin[ji] != 0 || shape[ji] != lengths[ji])
+				break;
+		}
+		if(ji < 0)
+		{
+			// origin is zero vector && target shape same as this
+			return (MultiArrayImpl) this.clone();
+		}
+		// else
+		// ji is the index where a maximum contiguous copy can occur
+		final int [] shp = (int []) shape.clone();
+		final int [] pducts = new int[shp.length];
+		final int product = numberOfElements(shp, pducts);
+		final Object dst = Array.newInstance(getComponentType(),
+				product);
+		int src_pos = indexMap(origin);
+		if(ji == 0)
+		{
+			// No loop required
+			System.arraycopy(storage, src_pos,
+				dst, 0, product);
+		}
+		else
+		{
+			ji--;
+			final int step = products[ji];
+			final int contig = pducts[ji];
+			for(int dst_pos = 0; dst_pos < product;
+				dst_pos += contig)
+			{
+				System.arraycopy(storage, src_pos,
+					dst, dst_pos, contig);
+				src_pos += step;
+			}
+		}
+
+		return new MultiArrayImpl(shp, pducts,
+			dst);
+	}
+
+	/**
+	 * Version <code>copyin</code> specialized and optimized for
+	 * MultiArrayImpl.
+	 * 
+	 * @see Accessor#copyin
+	 */
+	public void
+	copyin(int [] origin, MultiArrayImpl src)
+	{
+		if(origin.length != lengths.length
+				|| src.getRank() != lengths.length)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// else
+		int ji = lengths.length -1 ;
+		for(; ji >= 0; ji--)
+		{
+			if(origin[ji] != 0 || src.lengths[ji] != lengths[ji])
+				break;
+		}
+		if(ji < 0)
+		{
+			// origin is zero vector && src shape same as this
+			System.arraycopy(src.storage, 0,
+				storage, 0,
+				Array.getLength(storage));
+			return;
+		}
+		// else
+		// ji is the index where a maximum contiguous copy can occur
+		int dst_pos = indexMap(origin);
+		if(ji == 0)
+		{
+			// No loop required
+			System.arraycopy(src.storage, 0,
+				storage, dst_pos,
+				Array.getLength(storage) - dst_pos);
+			return;
+		}
+		// else
+		{
+			ji--;
+			final int step = products[ji];
+			final int contig = src.products[ji];
+			final int src_length = Array.getLength(src.storage);
+			for(int src_pos = 0; src_pos < src_length;
+				src_pos += contig)
+			{
+				System.arraycopy(src.storage, src_pos,
+					storage, dst_pos, contig);
+				dst_pos += step;
+			}
+		}
+	}
+
+	/**
+	 * @see Accessor#copyin
+	 */
+	public void
+	copyin(int [] origin, MultiArray data)
+		throws IOException
+	{
+		if(data instanceof MultiArrayImpl)
+		{
+			copyin(origin, (MultiArrayImpl)data);
+			return;
+		}
+		// else
+		if(origin.length != lengths.length
+				|| data.getRank() != lengths.length)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// else
+		if(data.getComponentType() != getComponentType())
+			throw new ArrayStoreException();
+		// else
+		AbstractAccessor.copy(data, data.getLengths(), this, origin);
+	}
+
+	static public Object
+	fixDest(Object dst, int lengthNeeded, Class defaultComponentType)
+	{
+		if(dst == null || Array.getLength(dst) < lengthNeeded)
+		{
+			final Class ct = (dst == null ?
+					defaultComponentType
+					: dst.getClass().getComponentType());
+			dst = Array.newInstance(
+				ct, lengthNeeded);
+		}
+		return dst;
+	}
+
+	
+	/**
+	 * @see Accessor#getStorage
+	 */
+	public Object
+	    getStorage() {
+	    return storage;
+	}
+
+
+
+
+
+
+	/**
+	 * @see Accessor#toArray
+	 */
+	public Object
+	toArray()
+	{
+		// Clone storage. Would 
+		// storage.getClass().getDeclaredMethod("clone", new Class [0])			//	.invoke(storage, new Object [0])
+		// be better?
+		final int length = Array.getLength(storage);
+		final Object dst = Array.newInstance(getComponentType(),
+			length);
+		System.arraycopy(storage, 0, dst, 0, length);
+		return dst;
+	}
+
+	/**
+	 * @see Accessor#toArray
+	 */
+	public Object
+	toArray(Object dst, int [] origin, int [] shape)
+	{
+		if(origin.length != lengths.length
+				|| shape.length != lengths.length)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// else
+		int ji = lengths.length -1 ;
+		for(; ji >= 0; ji--)
+		{
+			if(origin[ji] != 0 || shape[ji] != lengths[ji])
+				break;
+		}
+		if(ji < 0)
+		{
+			final int length = Array.getLength(storage);
+			dst = fixDest(dst, length, getComponentType());
+			System.arraycopy(storage, 0,
+				dst, 0, length);
+			return dst;
+		}
+
+		// else
+		// ji is the index where a maximum contiguous copy can occur
+		final int [] shp = (int []) shape.clone();
+		final int [] pducts = new int[shp.length];
+		final int product = numberOfElements(shp, pducts);
+		dst = fixDest(dst, product, getComponentType());
+
+		int src_pos = indexMap(origin);
+		if(ji == 0)
+		{
+			// No loop required
+			System.arraycopy(storage, src_pos,
+				dst, 0, product);
+			return dst;
+		}
+		// else
+		ji--;
+		final int step = products[ji];
+		final int contig = pducts[ji];
+		for(int dst_pos = 0; dst_pos < product;
+			dst_pos += contig)
+		{
+			System.arraycopy(storage, src_pos,
+				dst, dst_pos, contig);
+			src_pos += step;
+		}
+		return dst;
+	}
+
+ /* End Accessor */
+
+	/**
+	 * @see java.lang.Object#clone
+	 */
+	public Object
+	clone()
+	{
+		return new MultiArrayImpl((int [])lengths.clone(),
+			(int [])products.clone(), toArray());
+	}
+
+ /****/
+
+	/**
+	 * Convert index vector into integer index into storage.
+	 */
+	public int
+	indexMap(int [] index)
+	{
+		int value = 0;
+		for(int ii = 0; ii < lengths.length; ii++)
+		{
+			final int thisIndex = index[ii];
+			if( thisIndex < 0 || thisIndex >= lengths[ii])
+				 throw new ArrayIndexOutOfBoundsException();
+			value += thisIndex * products[ii];
+		}
+		return value;
+	}
+
+
+	/**
+	 * The actual storage. An array of componentType.
+	 * This member is exposed so that System.arraycopy(), etc
+	 * can be used directly on the storage.
+	 * @serial
+	 */
+	public final Object storage;
+
+
+	/**
+	 * Right to left products used in indexMap() to compute
+	 * offset into the array.
+	 * When incrementing index[ii], one jumps through storage by
+	 * products[ii].
+	 * @serial
+	 */
+	private final int[] products;
+	/**
+	 * @serial
+	 */
+	private final int[] lengths;
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+		final int [] shape = {48, 64};
+		MultiArrayImpl src =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(src.storage,
+					ii, ii);
+
+		}
+		int [] clip = new int[] {32, 64};
+		int [] origin = new int[] {8, 0};
+		MultiArray ma = src.copyout(origin, clip);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0}));
+			System.out.println(ma.getInt(new int[] {1, 0}));
+			System.out.println(ma.getInt(new int[] {lengths[0] -1,								 lengths[1] -1}));
+		}
+		catch (java.io.IOException ee) {}
+
+		clip = new int[] {48, 48};
+		origin = new int[] {0, 8};
+		ma = src.copyout(origin, clip);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0}));
+			System.out.println(ma.getInt(new int[] {1, 0}));
+			System.out.println(ma.getInt(new int[] {lengths[0] -1,								 lengths[1] -1}));
+		}
+		catch (java.io.IOException ee) {}
+
+		MultiArrayImpl dest =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		try {
+			dest.copyin(origin, ma);
+			System.out.println("***Rank  " + dest.getRank());
+			int [] lengths = dest.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(dest.getInt(new int[] {0, 0}));
+			System.out.println(dest.getInt(new int[] {0, 7}));
+			System.out.println(dest.getInt(new int[] {0, 8}));
+			System.out.println(dest.getInt(new int[] {47, 55}));
+			System.out.println(dest.getInt(new int[] {47, 56}));
+			System.out.println(dest.getInt(new int[] {47, 63}));
+		}
+		catch (java.io.IOException ee) {}
+	}
+ /* End Test */
+}
diff --git a/ucar/multiarray/MultiArrayInfo.java b/ucar/multiarray/MultiArrayInfo.java
new file mode 100644
index 0000000..d40e7c5
--- /dev/null
+++ b/ucar/multiarray/MultiArrayInfo.java
@@ -0,0 +1,72 @@
+// $Id: MultiArrayInfo.java,v 1.2 2002-05-29 20:32:40 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+
+/**
+ *  Inquiry or introspection interface for abstract
+ *  multidimensional arrays. The MultiArray interface
+ *  extends this by adding data access operations.
+ *
+ * @see MultiArray
+ * @see ucar.netcdf.ProtoVariable
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:40 $
+ */
+public interface MultiArrayInfo {
+    /**
+     * Returns the Class object representing the component
+     * type of the array.
+     * @return Class the component type
+     * @see java.lang.Class#getComponentType
+     */
+    public Class getComponentType();
+
+    /**
+     * Returns the number of dimensions of the array.
+     * @return int number of dimensions of the array
+     */
+    public int getRank();
+
+    /**
+     * Discover the dimensions of this MultiArray.
+     *
+     * @return int array whose length is the rank of this
+     * MultiArray and whose elements represent the
+     * length of each of it's dimensions
+     */
+    public int [] getLengths();
+
+    /**
+     * Returns <code>true</code> if and only if the effective dimension
+     * lengths can change. For example, if this were implemented by
+     * a java.util.Vector.
+     * @return boolean <code>true</code> iff this can grow
+     */
+    public boolean isUnlimited();
+
+    /**
+     * Convenience interface; return <code>true</code>
+     * if and only if the rank is zero.
+     * @return boolean <code>true</code> iff rank == 0
+     */
+    public boolean isScalar();
+
+}
diff --git a/ucar/multiarray/MultiArrayProxy.java b/ucar/multiarray/MultiArrayProxy.java
new file mode 100644
index 0000000..542455f
--- /dev/null
+++ b/ucar/multiarray/MultiArrayProxy.java
@@ -0,0 +1,433 @@
+// $Id: MultiArrayProxy.java,v 1.3 2003-02-03 20:09:07 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.io.IOException;
+
+/**
+ * This MultiArray implementation wraps another MultiArray
+ * and an IndexMap to provide a different view of the
+ * wrapped MultiArray. Indices passed to access methods
+ * are passed through a chain of mappings.
+ *
+ * @see MultiArray
+ * @see IndexMap
+ * @author $Author: donm $
+ * @version $Revision: 1.3 $ $Date: 2003-02-03 20:09:07 $
+ */
+public class MultiArrayProxy implements MultiArray {
+
+	/**
+	 * Construct a new proxy.
+	 * @param delegate MultiArray backing the proxy view provided.
+	 * @param im IndexMap defining the proxy view.
+	 */
+	public
+	MultiArrayProxy(MultiArray delegate, IndexMap im)
+	{
+		delegate_ = delegate;
+		im_ = im;
+		dlengths_ = delegate_.getLengths();
+		im_.setLengths(dlengths_);
+	}
+
+ /* Begin MultiArrayInfo */
+
+	/**
+	 * Returns the Class object representing the component
+	 * type of the array.
+	 * @return Class The componentType
+	 * @see java.lang.Class#getComponentType
+	 */
+	public Class
+	getComponentType()
+		{ return delegate_.getComponentType(); }
+
+	/**
+	 * Returns the number of dimensions of the backing MultiArray,
+	 * as transformed by the <code>rankInverseMap()</code> method of
+	 * the IndexMap.
+	 * @return int number of dimensions
+	 */
+	public int
+	getRank()
+		{ return im_.getRank(); }
+
+	/**
+	 * Returns the shape of the backing MultiArray as transformed
+	 * by the <code>dimensionsInverseMap()</code> method of
+	 * the IndexMap.
+	 * @return int array whose length is the rank of this
+	 * MultiArray and whose elements represent the
+	 * length of each of it's dimensions
+	 */
+	public int []
+	getLengths()
+	{
+		if(isUnlimited())
+		{
+			// The delegate lengths might have changed.
+			// This could better be handle by an event.
+			System.arraycopy(delegate_.getLengths(), 0,
+				dlengths_, 0, dlengths_.length);
+		}
+		final int [] lengths = new int[getRank()];
+		return im_.getLengths(lengths);
+	}
+
+	/**
+	 * Returns <code>true</code> if and only if the effective dimension
+	 * lengths can change.
+	 */
+	public boolean
+	isUnlimited()
+		{ return delegate_.isUnlimited(); }
+
+	/**
+	 * Convenience interface; return <code>true</code>
+	 * if and only if the rank is zero.
+	 * @return boolean <code>true</code> iff rank == 0
+	 */
+	public boolean
+	isScalar()
+	{
+		return 0 == this.getRank();
+	}
+
+ /* End MultiArrayInfo */
+ /* Begin Accessor */
+
+	/**
+	 * @return Object value at <code>index</code>
+	 * Length of index must equal rank() of this.
+	 * Values of index components must be less than corresponding
+	 * values from getLengths().
+	 */
+	public Object
+	get(int [] index)
+		throws IOException
+	{
+		return delegate_.get(map(index));
+	}
+
+	public boolean
+	getBoolean(int[] index)
+		 throws IOException
+	{
+		return delegate_.getBoolean(map(index));
+	}
+
+	public char
+	getChar(int[] index)
+		 throws IOException
+	{
+		return delegate_.getChar(map(index));
+	}
+
+	public byte
+	getByte(int[] index)
+		 throws IOException
+	{
+		return delegate_.getByte(map(index));
+	}
+
+	public short
+	getShort(int[] index)
+		 throws IOException
+	{
+		return delegate_.getShort(map(index));
+	}
+
+	public int
+	getInt(int[] index)
+		 throws IOException
+	{
+		return delegate_.getInt(map(index));
+	}
+
+	public long
+	getLong(int[] index)
+		 throws IOException
+	{
+		return delegate_.getLong(map(index));
+	}
+
+	public float
+	getFloat(int[] index)
+		 throws IOException
+	{
+		return delegate_.getFloat(map(index));
+	}
+
+	public double
+	getDouble(int[] index)
+		 throws IOException
+	{
+		return delegate_.getDouble(map(index));
+	}
+
+	/**
+	 * Length of index must equal rank() of this.
+	 * Values of index components must be less than corresponding
+	 * values from getLengths().
+	 */
+	public void
+	set(int [] index, Object value)
+		throws IOException
+	{
+		delegate_.set(map(index), value);
+	}
+
+	public void
+	setBoolean(int [] index, boolean value)
+		 throws IOException
+	{
+		delegate_.setBoolean(map(index), value);
+	}
+
+	public void
+	setChar(int [] index, char value)
+		 throws IOException
+	{
+		delegate_.setChar(map(index), value);
+	}
+
+	public void
+	setByte(int [] index, byte value)
+		 throws IOException
+	{
+		delegate_.setByte(map(index), value);
+	}
+
+	public void
+	setShort(int [] index, short value)
+		 throws IOException
+	{
+		delegate_.setShort(map(index), value);
+	}
+
+	public void
+	setInt(int [] index, int value)
+		 throws IOException
+	{
+		delegate_.setInt(map(index), value);
+	}
+
+	public void
+	setLong(int [] index, long value)
+		 throws IOException
+	{
+		delegate_.setLong(map(index), value);
+	}
+
+	public void
+	setFloat(int [] index, float value)
+		 throws IOException
+	{
+		delegate_.setFloat(map(index), value);
+	}
+
+	public void
+	setDouble(int[] index, double value)
+		 throws IOException
+	{
+		delegate_.setDouble(map(index), value);
+	}
+
+	/**
+	 * @see Accessor#copyout
+	 */
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+			throws IOException
+	{
+		final int rank = getRank();
+		if(origin.length != rank
+				|| shape.length != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+		final MultiArrayImpl data = new MultiArrayImpl(
+			getComponentType(),
+			shape);
+		AbstractAccessor.copyO(this, origin, data, shape);
+		return data;
+	}
+
+	/**
+	 * @see Accessor#copyin
+	 */
+	public void
+	copyin(int [] origin, MultiArray data)
+		throws IOException
+	{
+		final int rank = getRank();
+		if(origin.length != rank
+				|| data.getRank() != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// else
+		if(data.getComponentType() != getComponentType())
+			throw new ArrayStoreException();
+		// else
+		AbstractAccessor.copy(data, data.getLengths(), this, origin);
+	}
+
+    public Object getStorage () {
+	return delegate_.getStorage ();
+    }
+
+
+	/**
+	 * @see Accessor#toArray
+	 * TODO: optimize?
+	 */
+	public Object
+	toArray()
+		throws IOException
+	{
+		return this.toArray(null, null, null);
+	}
+
+	/**
+	 * @see Accessor#toArray
+	 * TODO: optimize?
+	 */
+	public Object
+	toArray(Object dst, int [] origin, int [] shape)
+		throws IOException
+	{
+		final int rank = getRank();
+		if(origin == null)
+			origin = new int[rank];
+		else if(origin.length != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+
+		int [] shp = null;
+		if(shape == null)
+			shp = getLengths();
+		else if(shape.length == rank)
+			shp = (int []) shape.clone();
+		else
+			throw new IllegalArgumentException("Rank Mismatch");
+
+		final int [] products = new int[rank];
+		final int length = MultiArrayImpl.numberOfElements(shp,
+			products);
+		dst = MultiArrayImpl.fixDest(dst, length, getComponentType());
+		final MultiArrayImpl data = new MultiArrayImpl(shp, products,
+			dst);
+		AbstractAccessor.copyO(this, origin, data, shp);
+		return dst;
+	}
+
+ /* End Accessor */
+
+	private synchronized int []
+	map(int [] index)
+	{
+		// TODO speedup?
+		// safe inline im_.transform(converted_, index);
+		im_.setInput(index);
+		return im_.getTransformed(new int[im_.getOutputLength()]);
+	}
+
+	private final MultiArray delegate_;
+	private final IndexMap im_;
+	/**
+	 * Storage of delegate dimension lengths
+	 */
+	private final int [] dlengths_;
+
+ // TODO better test
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+			System.out.println(">>  " + System.currentTimeMillis());
+		final int [] shape = {48, 64};
+		MultiArrayImpl delegate =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(delegate.storage,
+					ii, ii);
+
+		}
+		IndexMap im = new ClipMap(0, 4, 40);
+		MultiArray src = new MultiArrayProxy(delegate, im);
+
+		int [] clip = new int[] {32, 64};
+		int [] origin = new int[] {4, 0};
+		MultiArray ma = (MultiArray) null;
+
+		try {
+			ma = src.copyout(origin, clip);
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0}));
+			System.out.println(ma.getInt(new int[] {1, 0}));
+			System.out.println(ma.getInt(new int[] {lengths[0] -1,								 lengths[1] -1}));
+		}
+		catch (java.io.IOException ee) {}
+
+		MultiArrayImpl destD =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		im = new ClipMap(0, 8, 36);
+		MultiArray dest = new MultiArrayProxy(destD, im);
+		try {
+			origin = new int[] {0, 0};
+			dest.copyin(origin, ma);
+			System.out.println("***Rank  " + dest.getRank());
+			int [] lengths = dest.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + " }");
+			System.out.println(destD.getInt(new int[] {0, 0}));
+			System.out.println(destD.getInt(new int[] {7, 63}));
+			System.out.println(destD.getInt(new int[] {8, 0}));
+			System.out.println(destD.getInt(new int[] {8, 63}));
+			System.out.println(destD.getInt(new int[] {9, 0}));
+			System.out.println(destD.getInt(new int[] {39, 0}));
+			System.out.println(destD.getInt(new int[] {40, 0}));
+			System.out.println(destD.getInt(new int[] {47, 63}));
+				
+		}
+		catch (java.io.IOException ee) {}
+
+	}
+ /* Test output java ucar.multiarray.MultiArrayProxy
+Rank  2
+Shape { 32, 64 }
+512
+576
+2559
+***Rank  2
+Shape { 36, 64 }
+0
+0
+512
+575
+576
+2496
+0
+0
+  */
+  /* End Test */
+}
diff --git a/ucar/multiarray/OffsetIndexIterator.java b/ucar/multiarray/OffsetIndexIterator.java
new file mode 100644
index 0000000..2217281
--- /dev/null
+++ b/ucar/multiarray/OffsetIndexIterator.java
@@ -0,0 +1,78 @@
+// $Id: OffsetIndexIterator.java,v 1.2 2002-05-29 20:32:40 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * An IndexIterator where the lower bound is non-zero.
+ *
+ * @see IndexIterator
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:40 $
+ */
+
+public class
+OffsetIndexIterator
+	extends IndexIterator
+{
+
+	public
+	OffsetIndexIterator(int [] theOffset, int [] theLimits)
+	{
+		super(theOffset, theLimits);
+		offset = theOffset; // N.B. Not a copy
+	}
+
+	/**
+	 * Increment the odometer
+	 */
+	public void
+	incr()
+	{
+		int digit = counter.length -1;
+		if(digit < 0)
+		{
+			// counter is zero length array <==> scalar
+			ncycles++;
+			return;
+		}
+		while(digit >= 0)
+		{
+			counter[digit]++;
+			if(counter[digit] < limits[digit])
+			{
+				break; // normal exit
+			}
+			// else, carry
+			counter[digit] = offset[digit];
+			if(digit == 0)
+			{
+				ncycles++; // rolled over
+				break;
+			}
+			// else
+			digit--;
+		}
+	}
+
+	private final int[] offset;
+}
diff --git a/ucar/multiarray/RemoteAccessor.java b/ucar/multiarray/RemoteAccessor.java
new file mode 100644
index 0000000..f645dfb
--- /dev/null
+++ b/ucar/multiarray/RemoteAccessor.java
@@ -0,0 +1,138 @@
+// $Id: RemoteAccessor.java,v 1.2 2002-05-29 20:32:40 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+/**
+ * This interface is the same as Accessor, just
+ * tagged as java.rmi.Remote. This intervening
+ * layer is necessary so that the rmi compiler
+ * interpretes concrete multiarrays like MultiArrayImpl
+ * as return by value. Otherwise, if Accessor itself
+ * were remote, rmic would generate stubs for MultiArrayImpl
+ * to be a remote reference.
+ * <p>
+ * As of this writing (jdk1.1),
+ * the rmi compiler <code>rmic</code> is braindead in the
+ * sense that it doesn't recognize that java.rmi.RemoteException isa
+ * java.io.IOException. Hence, we reproduce each method declaration
+ * from Accessor, narrowing the throws specification.
+ * 
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:40 $
+ */
+
+public interface
+RemoteAccessor
+	extends Accessor, Remote
+{
+	public Object
+	get(int [] index)
+		throws RemoteException;
+
+	public boolean
+	getBoolean(int [] index)
+		throws RemoteException;
+
+	public char
+	getChar(int [] index)
+		throws RemoteException;
+
+	public byte
+	getByte(int [] index)
+		throws RemoteException;
+
+	public short
+	getShort(int [] index)
+		throws RemoteException;
+
+	public int
+	getInt(int [] index)
+		throws RemoteException;
+
+	public long
+	getLong(int [] index)
+		throws RemoteException;
+
+	public float
+	getFloat(int [] index)
+		throws RemoteException;
+
+	public double
+	getDouble(int [] index)
+		throws RemoteException;
+
+	public void
+	set(int [] index, Object value)
+		throws RemoteException;
+
+	public void
+	setBoolean(int [] index, boolean value)
+		throws RemoteException;
+
+	public void
+	setChar(int [] index, char value)
+		throws RemoteException;
+
+	public void
+	setByte(int [] index, byte value)
+		throws RemoteException;
+
+	public void
+	setShort(int [] index, short value)
+		throws RemoteException;
+
+	public void
+	setInt(int [] index, int value)
+		throws RemoteException;
+
+	public void
+	setLong(int [] index, long value)
+		throws RemoteException;
+
+	public void
+	setFloat(int [] index, float value)
+		throws RemoteException;
+
+	public void
+	setDouble(int [] index, double value)
+		throws RemoteException;
+
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+			throws RemoteException;
+
+	public void
+	copyin(int [] origin, MultiArray source)
+		throws RemoteException;
+
+	public Object
+	toArray()
+		throws RemoteException;
+	
+
+	public Object
+	toArray(Object anArray, int [] origin, int [] shape)
+		throws RemoteException;
+
+}
diff --git a/ucar/multiarray/ScalarMultiArray.java b/ucar/multiarray/ScalarMultiArray.java
new file mode 100644
index 0000000..08845fe
--- /dev/null
+++ b/ucar/multiarray/ScalarMultiArray.java
@@ -0,0 +1,515 @@
+// $Id: ScalarMultiArray.java,v 1.3 2003-02-03 20:09:07 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * MultiArray implementation which can only contain single values,
+ * aka scalars.
+ * If you have a single object and want to wrap it
+ * in a MultiArray interface, use this class.
+ * Rank of these is always zero.
+ * The index argument to the set, setXXX, get, and getXXX is ignored.
+ * <p>
+ * When the component type is primitive, this class is an adapter for
+ * the appropriate java.lang primitive wrapper (Double, Float, and so on).
+ * One of the purposes of this class is to substitute for
+ * the wrappers in the MultiArray context, providing continuity of access
+ * method signature between arrays and scalars. Contrast this with the the
+ * discontinuity between java.util.reflect.Array.getDouble()
+ * and java.lang.Number.doubleValue().
+ *
+ * @see MultiArray
+ * @see ArrayMultiArray
+ *
+ * @author $Author: donm $
+ * @version $Revision: 1.3 $ $Date: 2003-02-03 20:09:07 $
+ */
+public class
+ScalarMultiArray
+	implements MultiArray
+{
+
+	/**
+	 * Construct a new ScalarMultiArray with the specified component
+	 * type. This form of constructor would typically be used for
+	 * primitive component types such as java.lang.Double.TYPE.
+	 * @param theComponentType the Class object representing
+	 * the component type of the new MultiArray 
+	 */
+	public
+	ScalarMultiArray(Class theComponentType)
+	{
+		componentType = theComponentType;
+	}
+
+	/**
+	 * Construct a new ScalarMultiArray with the specified component
+	 * type and initialize it to the value given.
+	 * This form of constructor would typically be used for
+	 * primitive component types such as java.lang.Double.TYPE,
+	 * value would be a wrapped primitive of compatible class.
+	 * @param theComponentType the Class object representing
+	 * the component type of the new MultiArray 
+	 * @param value the initial value
+	 */
+	public
+	ScalarMultiArray(Class theComponentType, Object value)
+	{
+		componentType = theComponentType;
+		this.set((int [])null, value);
+	}
+
+	/**
+	 * Construct a new ScalarMultiArray with component
+	 * type value.getClass() and initialize it to the value given.
+	 * @param value the initial value
+	 */
+	public
+	ScalarMultiArray(Object value)
+	{
+		componentType = value.getClass();
+		obj = value;
+	}
+
+ /* Begin MultiArray Inquiry methods from MultiArrayInfo */
+
+	/**
+	 * @see MultiArrayInfo#getComponentType
+	 */
+	public Class
+	getComponentType() {
+		return componentType;
+	}
+
+	/**
+	 * Always returns zero for members of this class.
+	 * @see MultiArrayInfo#getRank
+	 * @return int 0
+	 */
+	public int
+	getRank() { return 0;}
+
+	/**
+	 * Always returns empty array for members of this class.
+	 * @see MultiArrayInfo#getLengths
+	 * @return int array of length zero.
+	 */
+	public int []
+	getLengths() {
+		int [] lengths = new int[0];
+		return lengths;
+	}
+
+	/**
+	 * Always returns <code>false</code> for members of this class.
+	 * @see MultiArrayInfo#isUnlimited
+	 * @return boolean <code>false</code>
+	 */
+	public boolean
+	isUnlimited() { return false; }
+
+	/**
+	 * Alway <code>true</code> for members of this class.
+	 * @see MultiArrayInfo#isUnlimited
+	 * @return boolean <code>true</code>
+	 */
+	public boolean
+	isScalar() { return true; }
+
+ /* End MultiArrayInfo */
+ /* Begin MultiArray Access methods from Accessor */
+
+	/**
+	 * Retrieve the object in this container.
+	 * @see Accessor#get
+	 * @param index ignored
+	 * @return the Object contained herein
+	 */
+	public Object
+	get(int [] index)
+	{
+		return obj;
+	}
+
+	/**
+	 * As if <code>(((Character)this.get(index)).charValue();</code>	
+	 * were called.
+	 * @see Accessor#getChar
+	 * @see #get
+	 * @see java.lang.Character#charValue()
+	 */
+	public char
+	getChar(int[] index)
+	{
+		return ((Character)obj).charValue();
+	}
+
+	/**
+	 * As if <code>(((Boolean)this.get(index)).booleanValue();</code>	
+	 * were called.
+	 * @see Accessor#getBoolean
+	 * @see #get
+	 * @see java.lang.Boolean#booleanValue()
+	 */
+	public boolean
+	getBoolean(int[] index)
+	{
+		return ((Boolean)obj).booleanValue();
+	}
+
+	/**
+	 * As if <code>(((Number)this.get(index)).byteValue();</code>	
+	 * were called.
+	 * @see Accessor#getByte
+	 * @see #get
+	 * @see java.lang.Number#byteValue
+	 */
+	public byte
+	getByte(int[] index)
+	{
+		return ((Number)obj).byteValue();
+	}
+
+	/**
+	 * As if <code>(((Number)this.get(index)).shortValue();</code>	
+	 * were called.
+	 * @see Accessor#getShort
+	 * @see #get
+	 * @see java.lang.Number#shortValue
+	 */
+	public short
+	getShort(int[] index)
+	{
+		return ((Number)obj).shortValue();
+	}
+
+	/**
+	 * As if <code>(((Number)this.get(index)).intValue();</code>	
+	 * were called.
+	 * @see Accessor#getInt
+	 * @see #get
+	 * @see java.lang.Number#intValue
+	 */
+	public int
+	getInt(int[] index)
+	{
+		return ((Number)obj).intValue();
+	}
+
+	/**
+	 * As if <code>(((Number)this.get(index)).longValue();</code>	
+	 * were called.
+	 * @see Accessor#getLong
+	 * @see #get
+	 * @see java.lang.Number#longValue
+	 */
+	public long
+	getLong(int[] index)
+	{
+		return ((Number)obj).longValue();
+	}
+
+	/**
+	 * As if <code>(((Number)this.get(index)).floatValue();</code>	
+	 * were called.
+	 * @see Accessor#getFloat
+	 * @see #get
+	 * @see java.lang.Number#floatValue
+	 */
+	public float
+	getFloat(int[] index)
+	{
+		return ((Number)obj).floatValue();
+	}
+
+	/**
+	 * As if <code>(((Number)this.get(index)).doubleValue();</code>	
+	 * were called.
+	 * @see Accessor#getDouble
+	 * @see #get
+	 * @see java.lang.Number#doubleValue
+	 */
+	public double
+	getDouble(int[] index)
+	{
+		return ((Number)obj).doubleValue();
+	}
+
+	/* TODO: ArrayStoreException vs IllegalArgumentException */
+	/**
+	 * Set the object contained here to value.
+	 * The index argument is ignored.
+	 * @see Accessor#set
+	 * @param index ignored
+	 * @param value the replacement contents
+	 */
+	public void
+	set(int [] index, Object value)
+	{
+		final Class xcls = value.getClass();
+
+		if(componentType.isPrimitive())
+		{
+			try {
+				if(componentType ==
+					(Class) xcls.getDeclaredField("TYPE").
+						get(value))
+				{
+					// value is a wrapper for componentType
+					obj = value;
+					return;
+				}
+			}
+			catch (NoSuchFieldException ee)
+			{
+				// continue
+			}
+			catch (IllegalAccessException ee)
+			{
+				// continue
+			}
+			// Value is not the specific wrapper.
+			// Maybe it is assignable...
+
+			Method tValueMethod;
+			try {
+				tValueMethod = xcls.getMethod(
+					componentType.getName()+"Value",
+					new Class[0]);
+			}
+			catch (NoSuchMethodException nsme)
+			{
+				// Can't convert
+				throw new IllegalArgumentException();
+			}
+			// else
+			try {
+				obj = tValueMethod.invoke(value, new Object[0]);
+				return;
+			}
+			catch (IllegalAccessException iae)
+			{
+				// assert(wrapper.xxxValue() is accessable);
+				throw new Error();
+			}
+			catch (InvocationTargetException ite)
+			{
+				throw (RuntimeException)
+					ite.getTargetException();
+			}
+		}
+		// else
+
+		if(!componentType.isAssignableFrom(xcls))
+		{
+			// Can't convert
+			throw new IllegalArgumentException();
+		}
+		// else
+		obj = value;
+	}
+
+	/**
+	 * Set the object contained here to a boolean value.
+	 * The index argument is ignored.
+	 * @see Accessor#setBoolean
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setBoolean(int [] index, boolean value)
+	{
+		this.set(index, new Boolean(value));
+	}
+
+	/**
+	 * Set the object contained here to a char value.
+	 * The index argument is ignored.
+	 * @see Accessor#setChar
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setChar(int [] index, char value)
+	{
+		this.set(index, new Character(value));
+	}
+
+	/**
+	 * Set the object contained here to a byte value.
+	 * The index argument is ignored.
+	 * @see Accessor#setByte
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setByte(int [] index, byte value)
+	{
+		this.set(index, new Byte(value));
+	}
+
+	/**
+	 * Set the object contained here to a short value.
+	 * The index argument is ignored.
+	 * @see Accessor#setShort
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setShort(int [] index, short value)
+	{
+		this.set(index, new Short(value));
+	}
+
+	/**
+	 * Set the object contained here to a int value.
+	 * The index argument is ignored.
+	 * @see Accessor#setInt
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setInt(int [] index, int value)
+	{
+		this.set(index, new Integer(value));
+	}
+
+	/**
+	 * Set the object contained here to a long value.
+	 * The index argument is ignored.
+	 * @see Accessor#setLong
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setLong(int [] index, long value)
+	{
+		this.set(index, new Long(value));
+	}
+
+	/**
+	 * Set the object contained here to a float value.
+	 * The index argument is ignored.
+	 * @see Accessor#setFloat
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setFloat(int [] index, float value)
+	{
+		this.set(index, new Float(value));
+	}
+
+	/**
+	 * Set the object contained here to a double value.
+	 * The index argument is ignored.
+	 * @see Accessor#setDouble
+	 * @param index ignored
+	 * @param value the new value
+	 */
+	public void
+	setDouble(int[] index, double value)
+	{
+		this.set(index, new Double(value));
+	}
+
+	/**
+	 * @see Accessor#copyout
+	 */
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+	{
+		if(origin != null && origin.length != 0
+				|| shape != null && shape.length != 0)
+			throw new IllegalArgumentException("Rank Mismatch");
+		try {
+			return new MultiArrayImpl(this);
+		}
+		catch (IOException ie)
+		{
+			// Can't happen: reading this won't generate i/o
+			throw new Error();
+		}
+	}
+
+	/**
+	 * @see Accessor#copyin
+	 */
+	public void
+	copyin(int [] origin, MultiArray src)
+		throws IOException
+	{
+		if(origin != null && origin.length != 0
+				|| src.getRank() != 0)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// We know src isScalar, but we don't know if it ignores
+		// the index arg...
+		final int [] nonarg = new int[] {};
+		set(nonarg, src.get(nonarg));
+	}
+
+	
+	/**
+	 * TODO: fishy semantics.
+	 * @see Accessor#toArray
+	 */
+	 public Object
+	toArray()
+	{
+		return obj;
+	}
+	/**
+	 * TODO: fishy semantics.
+	 * @see Accessor#toArray
+	 */
+	 public Object
+	     getStorage()
+	{
+		return obj;
+	}
+
+
+	/**
+	 * TODO: fishy semantics.
+	 * @see Accessor#toArray
+	 */
+	public Object
+	toArray(Object oo, int [] origin, int [] shape)
+	{
+		if(oo.getClass().isAssignableFrom(componentType))
+		{
+			return obj;
+		}
+		// TODO, numeric promotion
+
+		// else, Can't convert
+		throw new IllegalArgumentException();
+	}
+		
+ /* End Accessor */
+
+    private final Class componentType;
+    private Object obj;
+}
diff --git a/ucar/multiarray/SliceMap.java b/ucar/multiarray/SliceMap.java
new file mode 100644
index 0000000..6a98be2
--- /dev/null
+++ b/ucar/multiarray/SliceMap.java
@@ -0,0 +1,154 @@
+// $Id: SliceMap.java,v 1.2 2002-05-29 20:32:40 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * Use with MultiArrayProxy to reduce the apparent rank of
+ * the delegate by fixing an index at particular value.
+ *
+ * @see IndexMap
+ * @see MultiArrayProxy
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:40 $
+ */
+public class
+SliceMap
+		extends ConcreteIndexMap
+{
+	/**
+	 * Create an ConcreteIndexMap which fixes the key for a particular
+	 * dimension at a particular value.
+	 *
+	 * @param position the dimension number on which to fix the key.
+	 * @param value the value at which to fix the key.
+	 */
+	public
+	SliceMap(int position, int value)
+	{
+		init(new IMap(),
+			new LengthsMap());
+		position_ = position;
+		value_ = value;
+	}
+
+	/**
+	 * Create an ConcreteIndexMap which fixes the key for a particular
+	 * dimension at a particular value and is functionally composed
+	 * with another ConcreteIndexMap.
+	 *
+	 * @param prev ConcreteIndexMap to be composed with this.
+	 * @param position the dimension number on which to fix the key.
+	 * @param value the value at which to fix the key.
+	 */
+	public
+	SliceMap(ConcreteIndexMap prev, int position, int value)
+	{
+		link(prev, new IMap(),
+			new LengthsMap());
+		position_ = position;
+		value_ = value;
+	}
+
+	private class
+	IMap extends ZZMap
+	{
+
+		public synchronized int
+		get(int key)
+		{
+		/*
+			return key < position_
+				? super.get(key) : key == position_
+					? value_ : super.get(key -1);
+		*/
+			if(key < position_)
+				return super.get(key);
+			// else
+			if(key == position_)
+				return value_;
+			// else
+			return super.get(key -1);
+			
+		}
+	
+		public synchronized int
+		size()
+		{
+			return super.size() +1;
+		}
+	}
+
+	private class
+	LengthsMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			final int adjust = key < position_ ? key : key + 1;
+			return super.get(adjust);
+		}
+	
+		public synchronized int
+		size()
+		{
+			return super.size() -1;
+		}
+	}
+
+	/* WORKAROUND: Inner class & blank final initialize compiler bug */
+	private /* final */ int position_;
+	private /* final */ int value_;
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+		final int [] shape = {48, 64};
+		MultiArrayImpl delegate =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(delegate.storage,
+					ii, ii);
+
+		}
+		IndexMap im = new SliceMap(1, 1);
+		MultiArray ma = new MultiArrayProxy(delegate, im);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0]
+					 + " }");
+			System.out.println(ma.getInt(new int[] {1}));
+		}
+		catch (java.io.IOException ee) {}
+	}
+ /* Test output java ucar.multiarray.SliceMap
+Rank  1
+Shape { 48 }
+65
+  */
+ /* End Test */
+}
diff --git a/ucar/multiarray/StringCharAdapter.java b/ucar/multiarray/StringCharAdapter.java
new file mode 100644
index 0000000..165eb96
--- /dev/null
+++ b/ucar/multiarray/StringCharAdapter.java
@@ -0,0 +1,446 @@
+// $Id: StringCharAdapter.java,v 1.3 2003-02-03 20:09:07 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.io.IOException;
+
+/**
+ * This MultiArray implementation wraps another MultiArray
+ * of Character componentType to produce a MultiArray of
+ * one less rank with String componentType.
+ *
+ * @see MultiArray
+ * @author $Author: donm $
+ * @version $Revision: 1.3 $ $Date: 2003-02-03 20:09:07 $
+ */
+public class StringCharAdapter implements MultiArray {
+
+	/**
+	 * Construct a new proxy.
+	 * @param delegate MultiArray of Character componentType
+	 * @param fillValue char which used as terminator and fill value
+	 */
+	public
+	StringCharAdapter(MultiArray delegate, char fillValue)
+	{
+		if(delegate.getComponentType() != Character.TYPE)
+			throw new IllegalArgumentException(
+				"Not a Character Array");
+		delegate_ = delegate;
+		fillValue_ = fillValue;
+		lengths_ = new int [delegate_.getRank() -1];
+		final int [] dlengths = delegate_.getLengths();
+		System.arraycopy(dlengths, 0,
+			lengths_, 0, lengths_.length);
+		maxStringLen_ = dlengths[lengths_.length];
+	}
+	
+	public char
+	getFillValue()
+		{ return fillValue_; }
+
+ /* Begin MultiArrayInfo */
+
+	/**
+	 * Returns the Class object representing the component
+	 * type of the array.
+	 * @return Class The componentType
+	 * @see java.lang.Class#getComponentType
+	 */
+	public Class
+	getComponentType()
+	{
+		// There has got to be a better way to do this.
+		try {
+			return Class.forName("java.lang.String");
+		}
+		catch (ClassNotFoundException cnfe) {
+			throw new RuntimeException("Implementation problem");
+		}
+	}
+
+	/**
+	 * Returns the number of dimensions of the backing MultiArray,
+	 * as transformed by the <code>rankInverseMap()</code> method of
+	 * the IndexMap.
+	 * @return int number of dimensions
+	 */
+	public int
+	getRank()
+		{ return lengths_.length; }
+
+	/**
+	 * Returns the shape of the backing MultiArray as transformed
+	 * by the <code>dimensionsInverseMap()</code> method of
+	 * the IndexMap.
+	 * @return int array whose length is the rank of this
+	 * MultiArray and whose elements represent the
+	 * length of each of it's dimensions
+	 */
+	public int []
+	getLengths()
+	{
+		if(isUnlimited())
+		{
+			// The delegate lengths might have changed.
+			// This could better be handle by an event.
+			System.arraycopy(delegate_.getLengths(), 0,
+				lengths_, 0, lengths_.length);
+		}
+		return (int []) lengths_.clone();
+	}
+
+	/**
+	 * Returns <code>true</code> if and only if the effective dimension
+	 * lengths can change.
+	 */
+	public boolean
+	isUnlimited()
+		{ return delegate_.isUnlimited(); }
+
+	/**
+	 * Convenience interface; return <code>true</code>
+	 * if and only if the rank is zero.
+	 * @return boolean <code>true</code> iff rank == 0
+	 */
+	public boolean
+	isScalar()
+	{
+		return 0 == this.getRank();
+	}
+
+ /* End MultiArrayInfo */
+ /* Begin Accessor */
+
+	/**
+	 * @return Object value at <code>index</code>
+	 * Length of index must equal rank() of this.
+	 * Values of index components must be less than corresponding
+	 * values from getLengths().
+	 */
+	public Object
+	get(int [] index)
+		throws IOException
+	{
+		final int [] dIndex = new int [lengths_.length +1];
+		System.arraycopy(index, 0,
+				dIndex, 0, lengths_.length);
+
+		final char [] buf = new char[maxStringLen_];
+		int ii = 0;
+		for(; ii < maxStringLen_; ii++)
+		{
+			dIndex[lengths_.length] = ii;
+			buf[ii] = delegate_.getChar(dIndex);
+			if(buf[ii] == fillValue_)
+				break;
+		}
+
+		return new String(buf, 0, ii);
+	}
+
+	public boolean
+	getBoolean(int[] index)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public char
+	getChar(int[] index)
+		throws IOException
+	{
+		if(index.length > lengths_.length)
+			return delegate_.getChar(index);
+		throw new IllegalArgumentException();
+	}
+
+	public byte
+	getByte(int[] index)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public short
+	getShort(int[] index)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public int
+	getInt(int[] index)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public long
+	getLong(int[] index)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public float
+	getFloat(int[] index)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public double
+	getDouble(int[] index)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	/**
+	 * Length of index must equal rank() of this.
+	 * Values of index components must be less than corresponding
+	 * values from getLengths().
+	 */
+	public void
+	set(int [] index, Object value)
+		throws IOException
+	{
+		if( value instanceof String)
+		{
+			final int [] dIndex = new int [lengths_.length +1];
+			System.arraycopy(index, 0,
+				dIndex, 0, lengths_.length);
+			final String sValue = (String) value;
+			final int stringLen = ((String)value).length();
+			for(int ii = 0; ii < maxStringLen_; ii++)
+			{
+				dIndex[lengths_.length] = ii;
+				if(ii >= stringLen)
+				{
+					delegate_.setChar(dIndex,
+						fillValue_);
+					continue;
+				}
+				// else
+				delegate_.setChar(dIndex,
+						((String)value).charAt(ii));
+			}
+			return;
+		}
+		// else
+		throw new IllegalArgumentException();
+
+	}
+
+	public void
+	setBoolean(int [] index, boolean value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public void
+	setChar(int [] index, char value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public void
+	setByte(int [] index, byte value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public void
+	setShort(int [] index, short value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public void
+	setInt(int [] index, int value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public void
+	setLong(int [] index, long value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public void
+	setFloat(int [] index, float value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	public void
+	setDouble(int[] index, double value)
+	{
+		throw new IllegalArgumentException();
+	}
+
+	/**
+	 * @see Accessor#copyout
+	 */
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+			throws IOException
+	{
+		final int rank = getRank();
+		if(origin.length != rank
+				|| shape.length != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+		final MultiArrayImpl data = new MultiArrayImpl(
+			getComponentType(),
+			shape);
+		AbstractAccessor.copyO(this, origin, data, shape);
+		return data;
+	}
+
+	/**
+	 * @see Accessor#copyin
+	 */
+	public void
+	copyin(int [] origin, MultiArray data)
+		throws IOException
+	{
+		final int rank = getRank();
+		if(origin.length != rank
+				|| data.getRank() != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+		// else
+		if(data.getComponentType() != getComponentType())
+			throw new ArrayStoreException();
+		// else
+		AbstractAccessor.copy(data, data.getLengths(), this, origin);
+	}
+
+	/**
+	 * @see Accessor#toArray
+	 * TODO: optimize?
+	 */
+	public Object
+	toArray()
+		throws IOException
+	{
+		return this.toArray(null, null, null);
+	}
+
+
+	/**
+	 * @see Accessor#toArray
+	 * TODO: optimize?
+	 */
+	public Object
+	    getStorage ()
+	{
+	    return delegate_.getStorage ();
+	}
+
+	/**
+	 * @see Accessor#toArray
+	 * TODO: optimize?
+	 */
+	public Object
+	toArray(Object dst, int [] origin, int [] shape)
+		throws IOException
+	{
+		final int rank = getRank();
+		if(origin == null)
+			origin = new int[rank];
+		else if(origin.length != rank)
+			throw new IllegalArgumentException("Rank Mismatch");
+
+		int [] shp = null;
+		if(shape == null)
+			shp = getLengths();
+		else if(shape.length == rank)
+			shp = (int []) shape.clone();
+		else
+			throw new IllegalArgumentException("Rank Mismatch");
+
+		final int [] products = new int[rank];
+		final int length = MultiArrayImpl.numberOfElements(shp,
+			products);
+		dst = MultiArrayImpl.fixDest(dst, length, getComponentType());
+		final MultiArrayImpl data = new MultiArrayImpl(shp, products,
+			dst);
+		AbstractAccessor.copyO(this, origin, data, shp);
+		return dst;
+	}
+
+ /* End Accessor */
+
+	private final MultiArray delegate_;
+	private final char fillValue_;
+	private final int [] lengths_;
+	private final int maxStringLen_;
+
+ /* Begin Test */
+	private static String
+	MultiArrayToString(MultiArray ma) {
+		StringBuffer buf = new StringBuffer();
+		final int rank = ma.getRank();
+		if (rank > 0)
+		{
+			buf.append("{\n\t");
+			final int [] dims = ma.getLengths();
+			final int last = dims[0] -1;
+			for(int ii = 0; ii <= last; ii++)
+			{
+				final MultiArray inner =
+					new MultiArrayProxy(ma,
+						new SliceMap(0, ii));
+				buf.append(MultiArrayToString(inner));
+				if(ii != last)
+					buf.append(", ");
+			}
+			buf.append("\n}");
+		}
+		else
+		{
+			try {
+			buf.append(ma.get((int [])null));
+			} catch (IOException ee) {}
+		}
+		return buf.toString();
+	}
+
+	public static void
+	main(String[] args)
+	{
+			
+		MultiArray cha = new MultiArrayImpl(Character.TYPE, 
+			new int[]{4, 5});
+		MultiArray sta = new StringCharAdapter(cha, (char)0);
+		int [] index = {0};
+		try {
+			sta.set(index, "KDEN");
+			index[0]++;
+			sta.set(index, "KBOU");
+			index[0]++;
+			sta.set(index, "KABQ");
+			index[0]++;
+			sta.set(index, "KPHX");
+			System.out.println(MultiArrayToString(sta));
+			System.out.println(MultiArrayToString(cha));
+		}
+		// catch (java.io.IOException ee) {}
+		catch (Exception ee) {}
+
+	}
+
+  /* End Test */
+}
diff --git a/ucar/multiarray/TransposeMap.java b/ucar/multiarray/TransposeMap.java
new file mode 100644
index 0000000..14debfe
--- /dev/null
+++ b/ucar/multiarray/TransposeMap.java
@@ -0,0 +1,147 @@
+// $Id: TransposeMap.java,v 1.2 2002-05-29 20:32:41 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.multiarray;
+import java.lang.reflect.Array;
+
+/**
+ * Use with MultiArrayProxy to transpose two dimensions.
+ *
+ * @see IndexMap
+ * @see MultiArrayProxy
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 20:32:41 $
+ */
+public class
+TransposeMap
+		extends ConcreteIndexMap
+{
+	/**
+	 * Create an IndexMap which swaps two dimensions.
+	 *
+	 * @param aa specifies one of the dimensions to swap
+	 * @param bb specifies the other dimension to swap
+	 */
+	public
+	TransposeMap(int aa, int bb)
+	{
+		init(new IMap(),
+			new LengthsMap());
+		aa_ = aa;
+		bb_ = bb;
+	}
+
+	/**
+	 * Create an IndexMap which swaps two dimensions.
+	 *
+	 * @param prev IndexMap to be composed with this.
+	 * @param aa specifies one of the dimensions to swap
+	 * @param bb specifies the other dimension to swap
+	 */
+	public
+	TransposeMap(ConcreteIndexMap prev, int aa, int bb)
+	{
+		link(prev, new IMap(),
+			new LengthsMap());
+		aa_ = aa;
+		bb_ = bb;
+	}
+
+	private class
+	IMap extends ZZMap
+	{
+		public synchronized int
+		get(int key)
+		{
+			if(key == aa_)
+			{
+				return super.get(bb_);
+			}
+			// else
+			if(key == bb_)
+			{
+				return super.get(aa_);
+			}
+			// else
+			return super.get(key);
+			
+		}
+	}
+
+	private class
+	LengthsMap extends ZZMap
+	{
+		public int
+		get(int key)
+		{
+			if(key == aa_)
+				return super.get(bb_);
+			// else
+			if(key == bb_)
+				return super.get(aa_);
+			// else
+			return super.get(key);
+		}
+	}
+
+ /**/
+
+	/* WORKAROUND: Inner class & blank final initialize compiler bug */
+	private /* final */ int aa_;
+	private /* final */ int bb_;
+
+ /* Begin Test */
+	public static void
+	main(String[] args)
+	{
+		final int [] shape = {32, 48, 64};
+		MultiArrayImpl delegate =
+			new MultiArrayImpl(Integer.TYPE, shape);
+		{
+			final int size = MultiArrayImpl.numberOfElements(shape);
+			for(int ii = 0; ii < size; ii++)
+				java.lang.reflect.Array.setInt(delegate.storage,
+					ii, ii);
+
+		}
+		IndexMap im = new TransposeMap(0, 2);
+		MultiArray ma = new MultiArrayProxy(delegate, im);
+
+		try {
+			System.out.println("Rank  " + ma.getRank());
+			int [] lengths = ma.getLengths();
+			System.out.println("Shape { " + lengths[0] + ", "
+					 + lengths[1] + ", "
+					 + lengths[2] + " }");
+			System.out.println(ma.getInt(new int[] {0, 0, 1}));
+			System.out.println(ma.getInt(new int[] {0, 1, 0}));
+			System.out.println(ma.getInt(new int[] {1, 0, 0}));
+		}
+		catch (java.io.IOException ee) {}
+	}
+ /* Test output java ucar.multiarray.TransposeMap
+Rank  3
+Shape { 64, 48, 32 }
+3072
+64
+1
+ /* End Test */
+}
diff --git a/ucar/multiarray/overview.html b/ucar/multiarray/overview.html
new file mode 100644
index 0000000..7f6aaad
--- /dev/null
+++ b/ucar/multiarray/overview.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML PUBLIC "-//Netscape Comm. Corp.//DTD HTML//EN">
+<HTML>
+<HEAD>
+    <!-- SGI_COMMENT COSMOCREATE -->
+    <!-- SGI_COMMENT VERSION NUMBER="1.0" -->
+    <!-- $Id: overview.html,v 1.1.1.1 2000-08-28 21:42:24 dglo Exp $ -->
+    <TITLE>UCAR MultiArray API Overview</TITLE>
+</HEAD>
+<BODY>
+<CENTER><H2 ALIGN="CENTER">
+UCAR MultiArray API Overview</H2>
+</CENTER><P>
+The MultiArray API provides an abstraction for multidimensional array 
+access and some concrete implementations. By use of the MultiArrayProxy 
+and IndexMaps, it also provides techniques by which a MultiArray can be 
+viewed as if it had different structure.</P>
+<P>
+This discussion is made somewhat confusing by imprecise usage in our 
+field. When we say "array", or "vector", it isn't 
+clear whether we are talking about constructs in a given programming 
+language or mathematical abstractions. In the following discussion, we 
+will use the unadorned term "array" to mean the Java language 
+construct, The term "multidimensional array" for a 
+mathematical abstraction which is modeled by the MultiArray class in 
+this package.</P>
+<P>
+A <I>multidimensional array</I> is a collection of elements which are 
+accessed by <I>index</I>. The elements may be objects or primitives. 
+The index is a one dimensional array of integers. The number of 
+dimensions of a multidimensional array is called its <I>rank</I>. Each 
+of the dimensions has a length, which determines the possible values of 
+the corresponding index element. A multidimensional array of rank 1 is 
+often referred to as a <I>vector</I>. A multidimensional array of rank 
+0 is referred to as a <I>scalar</I>.</P>
+<P>
+The Java language, like C and C++, provides an array primitive which is 
+actually a vector. Also like C and C++, this primitive is used in the 
+language to build up multidimensional arrays as vectors of vectors. 
+Numerical programs in C and C++ rarely use the higher dimensional array 
+construct for several reasons. (TODO: more detail on why?) One purpose 
+of this API is to codify that practice into a clear object framework.</P>
+<P>
+The MultiArray API consists of:</P>
+<UL>
+    <LI>
+    <B><A HREF="ucar.multiarray.MultiArrayInfo.html">MultiArrayInfo</A>
+     Interface </B>- A reflection or introspection interface for 
+    multidimensional arrays. Methods to discover the rank, shape and 
+    component type. 
+    <LI>
+    <B><A HREF="ucar.multiarray.Accessor.html">Accessor</A> Interface</B>
+     - Methods to set and get single values of a multidimensional array. 
+    Also methods for aggregate copy in and out of the array. 
+    <LI>
+    <B><A HREF="ucar.multiarray.MultiArray.html">MultiArray</A>
+     Interface</B> - The union of MultiArrayInfo and Accessor 
+    interfaces. This is interface that client code should use. The 
+    artificial factorization into MultiArrayInfo and Accessor interfaces is 
+    only needed for the convenience of some implementations, notably 
+    ucar.netcdf.Variable. 
+    <LI>
+    <B>MultiArray Implementations</B>- Wrappers for Java arrays (<A
+     HREF="ucar.multiarray.ArrayMultiArray.html">ArrayMultiArray</A>) 
+    and scalar objects (<A HREF="ucar.multiarray.ScalarMultiArray.html">ScalarMultiArray</A>), 
+    as well as a space efficient implementation (<A
+     HREF="ucar.multiarray.MultiArrayImpl.html">MultiArrayImpl</A>). 
+    <LI>
+    <B><A HREF="ucar.multiarray.IndexIterator.html">IndexIterator</A>
+     utility class</B> - Used to step through the possible index 
+    values. 
+    <LI>
+    <B><A HREF="ucar.multiarray.MultiArrayProxy.html">MultiArrayProxy</A>
+     / <A HREF="ucar.multiarray.IndexMap.html">IndexMap</A> Interface 
+    Framework</B> - MultiArrayProxy uses an IndexMapping to provide a 
+    different view of some MultiArray. The new view typically has a 
+    different structure than the backing MultiArray. For example, we may 
+    wish to reduce the rank by one, fixing the index for a given dimension 
+    at a specific value. 
+    <LI>
+    <B>Concrete IndexMap classes</B> - Concrete IndexMapping primitives 
+    which are used with MultiArrayProxy to <A
+     HREF="ucar.multiarray.SliceMap.html">slice</A>, <A
+     HREF="ucar.multiarray.ClipMap.html">clip</A>, <A
+     HREF="ucar.multiarray.DecimateMap.html">subsample</A>, <A
+     HREF="ucar.multiarray.TransposeMap.html">transpose</A>, or <A
+     HREF="ucar.multiarray.FlattenMap.html">flatten</A> a MultArray. 
+    These may be functionally composed to form complex operations by using 
+    nested construction.
+    <LI>
+    <B><A HREF="ucar.multiarray.AbstractAccessor.html">AbstractAccessor</A>
+     abstract class </B>- A partial implementation of the Accessor 
+    Interface which handles primitive type conversions. 
+</UL>
+<P>
+The MultiArray methods to get() and set() values operate on single 
+values. To grab a slice, clipped region or some other aggregate out of 
+a MultiArray, use MultiArrayProxy and the appropriate concrete 
+IndexMappings to create that view of the MultiArray. The concrete 
+MultiArrayImpl provides a copy constructor for which the view can be 
+used as initializer. The copyin() and copyout() methods of MultiArray 
+may also be used for simple clippings.</P>
+</BODY>
+</HTML>
diff --git a/ucar/multiarray/package.html b/ucar/multiarray/package.html
new file mode 100644
index 0000000..5ae2083
--- /dev/null
+++ b/ucar/multiarray/package.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<HEAD>
+    <!-- SGI_COMMENT COSMOCREATE -->
+    <!-- SGI_COMMENT VERSION NUMBER="1.0.2" -->
+    <!-- $Id: package.html,v 1.1.1.3 2000-08-28 21:45:44 dglo Exp $ -->
+    <TITLE>UCAR MultiArray API Overview</TITLE>
+</HEAD>
+<BODY>
+Provides an abstraction for multidimensional array 
+access, some concrete implementations, and ways to view a MultiArray
+as if it had a different structure. The MultiArrayProxy 
+and IndexMaps provide techniques to make any MultiArray appear as if
+it were transposed, flipped, flattened, clipped, sliced,
+subsampled, or any composition of these.
+</P>
+
+<P>
+In the following, we 
+use the unadorned term "array" to mean the Java language 
+construct, and the term "multidimensional array" for the
+mathematical abstraction modeled by the MultiArray class in 
+this package.</P>
+<P>
+A <I>multidimensional array</I> is a collection of elements that are 
+accessed by <I>index</I>. The elements may be objects or primitives. 
+The index is a one dimensional array of integers. The number of 
+dimensions of a multidimensional array is called its <I>rank</I>. Each 
+of the dimensions has a length, which determines the possible values of 
+the corresponding index element. A multidimensional array of rank 1 is 
+often referred to as a <I>vector</I>. A multidimensional array of rank 
+0 is referred to as a <I>scalar</I>.</P>
+<P>
+Like C and C++, the Java programming language provides an array
+primitive which is actually a (fixed length) vector. Also like C and
+C++, this primitive is used in the language to build up
+multidimensional arrays as vectors of vectors. For example, a two
+dimensional M by N array would be constructed as a vector of M
+references to M vectors each of length N. This strategy wastes some
+storage, and the waste increases for higher dimensioned arrays. In
+random access patterns, the added level of indirection for each
+dimension incurs a performance penalty as well. Numerical programs in
+C or C++ often avoid direct use of the language construct for
+multidimensional arrays. The availability of pointers and pointer
+arithmetic can be used to trade off against the costs, but these
+features are not available in Java. This suggests a need for Java
+library classes that abstract the notion of multidimensional array and
+implement commonly used array operations. These are included in this
+ package.</P>
+<P>
+The MultiArray API consists of:</P>
+<UL>
+    <LI>
+    <A HREF="MultiArrayInfo.html"><B>MultiArrayInfo</B></A><B>
+     Interface </B>- A reflection or introspection interface for 
+    multidimensional arrays. Methods to discover the rank, shape and 
+    component type. 
+    <LI>
+    <A HREF="Accessor.html"><B>Accessor</B></A><B>
+     Interface</B> - Methods to set and get single values of a 
+    multidimensional array. Also methods for aggregate copy in and out of 
+    the array. 
+    <LI>
+    <A HREF="MultiArray.html"><B>MultiArray</B></A><B>
+     Interface</B> - The union of MultiArrayInfo and Accessor 
+    interfaces. This is the interface that client code should use. The 
+    artificial factorization into MultiArrayInfo and Accessor interfaces is 
+    only needed for the convenience of some implementations, notably 
+    ucar.netcdf.Variable. 
+    <LI>
+    <B>MultiArray Implementations</B>- Wrappers for Java arrays (<A
+     HREF="ArrayMultiArray.html">ArrayMultiArray</A>) 
+    and scalar objects (<A HREF="ScalarMultiArray.html">ScalarMultiArray</A>), 
+    as well as a space efficient implementation (<A
+     HREF="MultiArrayImpl.html">MultiArrayImpl</A>). 
+    <LI>
+    <A HREF="IndexIterator.html"><B>IndexIterator</B></A><B>
+     utility class</B> - Used to step through the possible index 
+    values. 
+    <LI>
+    <A HREF="MultiArrayProxy.html"><B>MultiArrayProxy</B></A><B>
+     / <A HREF="IndexMap.html">IndexMap</A> Interface 
+    Framework</B> - MultiArrayProxy uses an IndexMap to provide a 
+    different view of some MultiArray. The new view typically has a 
+    different structure than the backing MultiArray. For example, we may 
+    wish to reduce the rank by one, fixing the index for a given dimension 
+    at a specific value. 
+    <LI>
+    <B>Concrete IndexMap classes</B> - Concrete IndexMap primitives 
+    that are used with MultiArrayProxy to <A
+     HREF="SliceMap.html">slice</A>, <A
+     HREF="ClipMap.html">clip</A>, <A
+     HREF="DecimateMap.html">subsample</A>, <A
+     HREF="FlipMap.html">flip</A>, <A
+     HREF="TransposeMap.html">transpose</A>, or <A
+     HREF="FlattenMap.html">flatten</A> a MultiArray. 
+    These may be functionally composed to form complex operations by using 
+    nested construction. 
+    <LI>
+    <A HREF="AbstractAccessor.html"><B>AbstractAccessor</B></A><B>
+     abstract class </B>- A partial implementation of the Accessor 
+    Interface that handles primitive type conversions. 
+    <LI>
+    <A HREF="StringCharAdapter.html"><B>StringCharAdapter</B></A><B>
+     class</B> - An adapter for MultiArray of <TT>char</TT> component 
+    type that presents a MultiArray of String interface. 
+</UL>
+<P>
+The MultiArray methods to get() and set() values operate on single 
+values. To grab a slice, clipped region or some other aggregate out of 
+a MultiArray, use MultiArrayProxy and the appropriate concrete 
+IndexMaps to create that view of the MultiArray. The concrete 
+MultiArrayImpl provides a copy constructor for which the view can be 
+used as initializer. The copyin() and copyout() methods of MultiArray 
+may also be used for simple clippings.</P>
+<P>
+There is an example program, <A
+ HREF="http://www.unidata.ucar.edu/packages/netcdf/java/examples/DemoMultiArrays.java">DemoMultiArrays.java</A>, which 
+shows some ways to use the interface.</P>
+</BODY>
+</HTML>
diff --git a/ucar/netcdf/AbstractNetcdf.java b/ucar/netcdf/AbstractNetcdf.java
new file mode 100644
index 0000000..0a93695
--- /dev/null
+++ b/ucar/netcdf/AbstractNetcdf.java
@@ -0,0 +1,425 @@
+// $Id: AbstractNetcdf.java,v 1.4 2002-05-29 18:31:32 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.multiarray.Accessor;
+import java.util.Hashtable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * This abstract class provides a skeletal implementation
+ * of the Netcdf interface.
+ * <p>
+ * A minimal concrete implementation
+ * would provide a concrete implementation of method
+ * <code>Accessor ioFactory(ProtoVariable proto)</code>.
+ * It would also provide a constructor which takes a Schema
+ * argument and calls super(Schema) to get this class to
+ * hook everything up.
+ * <p>
+ * TODO: There is a lot more to be said.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:32 $
+ */
+
+public abstract class
+AbstractNetcdf
+	implements Netcdf
+{
+ /* Begin Constructors */
+
+	/**
+	 * Create an empty instance.
+	 * This may be incrementally populated using the protected
+	 * put methods below. Use this constructor when you don't
+	 * have all the ProtoVariables, Dimensions, and global
+	 * Attributes available initially, such as when constructing
+	 * from a stream.
+	 */
+	protected
+	AbstractNetcdf()
+	{
+		ctor = VariableCtor();
+		delegate = new Schema();
+		variables = new Hashtable();
+	}
+
+	/**
+	 * Create an empty instance to be populated with instances
+	 * of some subclass of Variable.
+	 *
+	 * @param varClass  Class object for some subclass of Variable.
+	 * 	The class must implement a constructor of the form
+	 *	<code>myVar(ProtoVariable proto, Accessor io)</code>
+	 *  or NoSuchMethodException will be thrown.
+	 */
+	protected
+	AbstractNetcdf(Class varClass)
+		throws NoSuchMethodException
+	{
+		ctor = varClass.getDeclaredConstructor(
+				varCtorParameterTypes()
+			);
+		delegate = new Schema();
+		variables = new Hashtable();
+	}
+
+	/**
+	 * Create an instance populated with instances
+	 * of Variable.
+	 *
+	 * @param sc   the Schema to use. N.B. Not a copy.
+	 *   May be empty, shouldn't be null.
+	 *
+	 * @param init if true, call initHashtable()
+	 */
+	protected
+	AbstractNetcdf(Schema sc, boolean init)
+	{
+		ctor = VariableCtor();
+		delegate = sc;
+		variables = new Hashtable(delegate.size());
+		if(init)
+		{
+			try {
+				initHashtable();
+			}
+			catch (InstantiationException ie)
+			{
+				// Can't happen: Variable is concrete
+				throw new Error();
+			}
+			catch (IllegalAccessException iae)
+			{
+				// Can't happen: Variable is accessable
+				throw new Error();
+			}
+			catch (InvocationTargetException ite)
+			{
+				// all the possible target exceptions are
+				// RuntimeException
+				throw (RuntimeException)
+					ite.getTargetException();
+			}
+		}
+	}
+
+
+	/**
+	 * Create an instance populated with instances
+	 * of some subclass of Variable.
+	 *
+	 * @param sc  the Schema used as a template.
+	 *   May be empty, shouldn't be null.
+	 *
+	 * @param init if true, call initHashtable()
+	 *
+	 * @param varClass  Class object for some subclass of Variable.
+	 * 	The class must implement a constructor of the form
+	 *	<code>myVar(ProtoVariable proto, Accessor io)</code>
+	 *  or NoSuchMethodException will be thrown.
+	 */
+	protected
+	AbstractNetcdf(Schema sc, boolean init, Class varClass)
+		throws NoSuchMethodException,
+			 InstantiationException,
+			 InvocationTargetException,
+			 IllegalAccessException
+	{
+		ctor = varClass.getDeclaredConstructor(
+				varCtorParameterTypes()
+			);
+		delegate = new Schema(sc);
+		variables = new Hashtable(delegate.size());
+		if(init)
+			initHashtable();
+	}
+
+ /* End Constructors */
+
+	/**
+	 * Returns the number of variables
+	 * @return int number of variables
+	 */
+	public int
+	size()
+	{
+		// assert(delegate.size() == variables.size();
+		return variables.size();
+	}
+
+	/**
+	 * Returns VariableIterator for the elements.
+	 * @return VariableIterator for the elements.
+	 * @see VariableIterator
+	 */
+	public VariableIterator
+	iterator()
+	{
+		return new VariableIterator() {
+			final ProtoVariableIterator iter = delegate.iterator();
+			
+	    		public boolean hasNext() {
+				return iter.hasNext();
+	    		}
+	
+			public Variable next() {
+				return (Variable) variables.get(
+					iter.next().getName());
+			}
+	
+		};
+	}
+
+	/**
+	 * Retrieve the variable associated with the specified name.
+	 * @param name String which identifies the desired variable
+	 * @return the variable, or null if not found
+	 */
+	public Variable
+	get(String name)
+	{
+		return (Variable) variables.get(name);
+	}
+	
+	
+	/**
+	 * Tests if the Variable identified by <code>name</code>
+	 * is in this set.
+	 * @param name String which identifies the desired variable
+	 * @return <code>true</code> if and only if this set contains
+	 * the named variable.
+	 */
+	public boolean
+	contains(String name)
+	{
+		/*
+		 * assert(delegate.contains(name)
+		 *	 == variables.containsKey(name)();
+		 */
+		return variables.containsKey(name);
+	}
+
+	/**
+	 * Tests if the argument is in this set.
+	 * @param oo some Object
+	 * @return <code>true</code> if and only if this set contains
+	 * <code>oo</code>
+	 */
+	public boolean
+	contains(Object oo)
+	{
+		return variables.contains(oo);
+	}
+
+	/**
+	 * Returns the set of dimensions associated with this, 
+	 * the union of those used by each of the variables.
+	 *
+	 * @return DimensionSet containing dimensions used
+	 * by any of the variables. May be empty. Won't be null.
+	 */
+	public DimensionSet
+	getDimensions()
+		{ return delegate.getDimensions(); }
+
+	/**
+	 * Returns the set of attributes associated with this, 
+	 * also know as the "global" attributes.
+	 * 
+	 * @return AttributeSet. May be empty. Won't be null.
+	 */
+	public AttributeSet
+	getAttributes()
+		{ return delegate.getAttributes(); }
+
+	/**
+	 * Convenience function; look up global Attribute by name.
+	 *
+	 * @param name the name of the attribute
+	 * @return the attribute, or null if not found
+	 */
+	public Attribute
+	getAttribute(String name)
+		{ return delegate.getAttribute(name); }
+
+	/**
+	 * Format as CDL.
+	 * @param buf StringBuffer into which to write
+	 */
+	public void
+	toCdl(StringBuffer buf)
+		{ delegate.toCdl(buf); }
+		
+	/**
+	 * @return a CDL string of this.
+	 */
+	public String
+	toString()
+	{
+	 	StringBuffer buf = new StringBuffer();
+		toCdl(buf);
+		return buf.toString();
+	}
+
+	/**
+	 * Used to compute 'dimension index' needed 
+	 * in netcdf version 1 files.
+	 */
+	int
+	indexOf(Dimension dim)
+		{ return delegate.indexOf(dim); }
+
+ /* implementation */
+
+	/**
+	 * Used when creating variables to populate this.
+	 * Override in your implementation to provide the
+	 * correct i/o functionality.
+	 */
+	protected abstract Accessor
+	ioFactory(ProtoVariable proto)
+		throws InvocationTargetException;
+
+	/**
+	 * Used for incremental initialization.
+	 * Add a Dimension to the Netcdf.
+	 */
+	protected void
+	putDimension(Dimension dim)
+		{ delegate.putDimension(dim); }
+
+	/**
+	 * Used for incremental initialization.
+	 * Add a (global) attribute to the Netcdf.
+	 */
+	protected void
+	putAttribute(Attribute attr) {
+		delegate.putAttribute(attr);
+	}
+
+	/**
+	 * Used for incremental initialization.
+	 * Add a variable to the Netcdf.
+	protected void
+	put(ProtoVariable proto, Variable var)
+	{
+		if(!proto.getName().equals(var.getName()))
+			throw new IllegalArgumentException(
+					proto.getName()
+					+ " != " + var.getName());
+		delegate.put(proto);	
+		variables.put(var.getName(),var);
+	}
+	 */
+
+	/**
+	 * Used for incremental initialization.
+	 * Add a variable to the Netcdf.
+	 */
+	protected void
+	add(ProtoVariable proto, Accessor io)
+		throws InstantiationException,
+			InvocationTargetException,
+			IllegalAccessException
+	{
+		delegate.put(proto);	
+		final Object [] args = {proto, io};
+		final Variable var = (Variable) ctor.newInstance(args);
+		variables.put(var.getName(),var);
+	}
+
+ /* */
+	/**
+	 * These are the parameter types used for the
+	 * Variable constructor in <code>initHashtable(Class)</code>
+	 */
+	static final Class []
+	varCtorParameterTypes()
+	{
+		try {
+			Class [] parameterTypes = {
+				Class.forName("ucar.netcdf.ProtoVariable"),
+				Class.forName("ucar.multiarray.Accessor")
+			};
+			return parameterTypes;
+		}
+		catch (ClassNotFoundException cnfe)
+		{
+			// Shouldn't happen
+			throw new Error(
+				"ucar.netcdf implementation error");
+		}
+	}
+
+	protected void
+	initHashtable()
+		throws InstantiationException,
+			InvocationTargetException,
+			IllegalAccessException
+	{
+		for(ProtoVariableIterator iter = delegate.iterator();
+				iter.hasNext();)
+		{
+			final ProtoVariable proto = iter.next();
+			final Accessor io = ioFactory(proto);
+			final Object [] args = {proto, io};
+			final Variable var = (Variable) ctor.newInstance(args);
+			// assert(var.getName() == proto.getName());
+			if(variables.put(var.getName(), var) != null)
+				throw new IllegalArgumentException(
+					"Duplicate variable name");
+		}
+		// assert(delegate.size() == variables.size();
+	}
+
+	static private Constructor
+	VariableCtor()
+	{
+		try {
+			final Class vc = Class.forName("ucar.netcdf.Variable");
+			return vc.getDeclaredConstructor(
+				varCtorParameterTypes()
+			);
+			
+		}
+		catch (ClassNotFoundException cnfe)
+		{
+			// Can't happen: ucar.netcdf.Variable exists
+			throw new Error();
+		}
+		catch (NoSuchMethodException cnfe)
+		{
+			// Can't happen: ucar.netcdf.Variable has this ctor
+			throw new Error();
+		}
+	}
+
+
+	/* package */ Schema
+	getSchema()
+		{ return delegate; }
+	
+	final private Constructor ctor;
+        final private Schema delegate;
+	final private Hashtable variables;
+}
diff --git a/ucar/netcdf/Attribute.java b/ucar/netcdf/Attribute.java
new file mode 100644
index 0000000..7fdce2f
--- /dev/null
+++ b/ucar/netcdf/Attribute.java
@@ -0,0 +1,563 @@
+// $Id: Attribute.java,v 1.4 2002-05-29 18:31:32 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package ucar.netcdf;
+import java.io.Serializable;
+import java.lang.String;
+import java.lang.reflect.Array;
+
+/**
+ * Attributes are similar to "IndexedProperties" in the lingo
+ * of java beans. They have a name, type and an array of
+ * values. The array is often of length 1, degenerating into
+ * a simple property. The array should never be length 0.
+ * An Attribute object is used to contain netcdf "metadata",
+ * like units of a measurable quantity or its valid range.
+ * <p>
+ * These attributes have fixed values over their lifetime;
+ * no setValue() methods are provided.
+ * <p>
+ * Instances which have same name and same value elements are equal.
+ * We override hashCode() and equals() to be consistent with
+ * this semantic.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:32 $
+ */
+/*
+ * Implementation Notes:
+ * <p>
+ * We factor the value into two cases, a String value and
+ * an array of (numeric) primitive. Things would have been
+ * completely symmetric (eliminating this layer) if we just copied
+ * strings into array of char and copied out to string. Seems like a
+ * waste when given an immutable object to start with.
+ * <p>
+ * The members are immutable. The value is built by copying
+ * in. So, we can safely use the default clone() method.
+ */
+public class
+Attribute
+	implements Named, Serializable, Cloneable
+{
+	/**
+	 * Construct simple numeric attribute.
+	 *
+	 * @param name  String which is to be the name of this Attribute
+	 * @param value A Number to be the Attribute value
+	 */
+	public
+	Attribute(String name, Number value)
+	{
+		final Class componentType = primitiveClass(value);
+		if(!ProtoVariable.checkComponentType(componentType))
+			throw new IllegalArgumentException("Invalid Type: "
+				+ componentType);
+		this.name = name;
+		this.value = new NumericAttrVal(value, componentType);
+	}
+
+	/**
+	 * Construct simple numeric attribute.
+	 *
+	 * @param name  String which is to be the name of this Attribute
+	 * @param value A double to be the Attribute value
+	 */
+	public
+	Attribute(String name, double value)
+	{
+		this.name = name;
+		final double [] darray = {value};
+		this.value = new NumericAttrVal(darray);
+	}
+
+	/**
+	 * Construct a string valued attribute.
+	 * This will be seen to have component type Character.TYPE
+	 *
+	 * @param name  String which is to be the name of this Attribute
+	 * @param theValue  The value
+	 */
+	public
+	Attribute(String name, String theValue)
+	{
+		this.name = name;
+		this.value = new StringAttrVal(theValue);
+	}
+
+	/**
+	 * Construct an array valued Attribute.
+	 * Not often used.
+	 *
+	 * @param name  String which is to be the name of this Attribute
+	 * @param theValue  The value, an array of primitives. The primitive
+	 * 	type must be netcdf encodeable.
+	 */
+	public
+	Attribute(String name, Object theValue)
+	{
+		/*
+		 * check that arg is an array
+		 */
+		final Class aClass = theValue.getClass();
+		if(!aClass.isArray())
+			throw new IllegalArgumentException("Not an Array");
+		// else
+
+		/*
+		 * Check that the array componentType is netcdf encodeable.
+		 */
+		final Class componentType = aClass.getComponentType();
+		if(!ProtoVariable.checkComponentType(componentType))
+			throw new IllegalArgumentException("Invalid Type: "
+				+ componentType);
+		// else
+
+		this.name = name;
+		if(componentType ==  Character.TYPE)
+		{
+			this.value = new StringAttrVal((char [])theValue);
+		}
+		else
+		{
+			/* make a private copy of the array */
+			this.value = new NumericAttrVal(arrayClone(theValue));
+		}
+	}
+
+ /* Begin Overrides */
+
+	/**
+	 * Instances which have same name and same value elements are equal.
+	 * Overrides Object.equals() to be consistent with
+	 * this semantic.
+	 *
+	 * @return the hash code value for this Attribute
+	 */
+	public int
+	hashCode()
+	{
+		return (name.hashCode() ^ value.hashCode());
+	}
+
+	/**
+	 * Instances which have same name and same value elements are equal.
+	 * Overrides Object.equals() to be consistent with
+	 * this semantic.
+	 * TODO: test me.
+	 */
+	public boolean
+	equals(Object oo)
+	{
+		if(this == oo) return true;
+		if(oo instanceof Attribute)
+		{
+			final Attribute aa = (Attribute) oo;
+			if(name.equals(aa.getName()))
+			{
+				return value.equals(aa.value);
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @return a string representation of this
+	 */
+	public String
+	toString() {
+		StringBuffer buf = new StringBuffer();
+		toCdl(buf);
+		return buf.toString();
+	}
+
+ /* End Overrides */
+
+	/**
+	 * Returns the name of this Attribute.
+	 * @return String which identifies this Attribute.
+	 */
+	public final
+	String getName()
+	{
+		return name;
+	}
+
+	/**
+	 * Retrieve the value in its most general form.
+	 * @return Object which is either a java.lang.String or
+	 *   a 1-dimensional array of primitives.
+	 * @see Attribute#isString
+	 */
+	public Object
+	getValue()
+		{ return value.getValue(); }
+
+	/**
+	 * Retrieve String value.
+	 * @return String if this is a String valued attribute.
+	 * @throws ClassCastException if this is not String valued.
+	 * @see Attribute#isString
+	 */
+	public String
+	getStringValue()
+		{ return (String) value.getValue(); }
+
+	/**
+	 * Retrieve indexed value.
+	 * @param index int which is the index into the value array.
+	 * @return Number <code>value[index]</code>
+	 */
+	public Object
+	get(int index)
+		{ return value.get(index); }
+
+	/**
+	 * Retrieve indexed numeric value.
+	 * @param index int which is the index into the value array.
+	 * @return Number <code>value[index]</code>
+	 */
+	public Number
+	getNumericValue(int index)
+		{ return value.getNumericValue(index); }
+
+	/**
+	 * Retrieve simple numeric value.
+	 * Equivalent to <code>getNumericValue(0)</code>
+	 * @return Number the first element of the value array
+	 */
+	public Number
+	getNumericValue()
+		{ return value.getNumericValue(); }
+
+	/**
+	 * If the value is an instance of String, return <code>true</code>
+	 * otherwise returns <code>false</code>
+	 * @return boolean value instanceof String
+	 */
+	public boolean
+	isString()
+	{
+		return value instanceof StringAttrVal;
+	}
+
+	/**
+	 * If the value represents an array type, returns the Class
+	 * object representing the component type of the array; otherwise
+	 * returns null.
+	 * @return Class the component type
+	 * @see java.lang.Class#getComponentType
+	 */
+	public Class
+	getComponentType()
+		{ return value.getComponentType(); }
+
+	/**
+	 * If the value represents an array type, returns the length
+	 * of the value array;
+	 * otherwise return String.length of the String value.
+	 * @return int length of the value
+	 */
+	public int
+	getLength()
+		{ return value.getLength(); }
+
+	/**
+	 * Format as CDL.
+	 * @param buf StringBuffer into which to write
+	 */
+	public void
+	toCdl(StringBuffer buf)
+	{
+		buf.append(":");
+		buf.append(this.getName());
+		buf.append(" = ");
+		value.toCdl(buf);
+		buf.append(" ;");
+	}
+
+	/**
+	 * Why isn't this in java.lang.reflect.Array as native?
+	 */
+	static Object
+	arrayClone(Object src)
+	{
+		final int length = Array.getLength(src);
+		Object aa = Array.newInstance(src.getClass().getComponentType(),
+			length);
+		System.arraycopy(src, 0, aa, 0, length);
+		return aa;
+	}
+
+	/**
+	 * Use reflection to find out the TYPE (primitive class)
+	 * corresponding to a Number.
+	 */
+	static Class
+	primitiveClass(Number nn)
+	{
+		try {
+			return (Class) nn.getClass().getDeclaredField("TYPE").
+				get(nn);
+		}
+		catch (NoSuchFieldException ee)
+		{
+			// this shouldn't happen, since Numbers have TYPE
+			throw new Error();
+		}
+		catch (IllegalAccessException ee)
+		{
+			// this shouldn't happen, since TYPE is public
+			throw new Error();
+		}
+	}
+
+
+	/**
+	 * @serial
+	 */
+	private final String name;
+	/**
+	 * @serial
+	 */
+	private final AttrVal value;
+}
+
+abstract class
+AttrVal
+	implements Serializable, Cloneable
+{
+	abstract Object
+	getValue();
+
+	abstract Object
+	get(int index);
+
+	abstract Number
+	getNumericValue(int index);
+
+	abstract Number
+	getNumericValue();
+
+	abstract Class
+	getComponentType();
+
+	abstract int
+	getLength();
+
+	abstract void
+	toCdl(StringBuffer buf);
+}
+
+
+final class
+NumericAttrVal
+	extends AttrVal
+{
+
+	NumericAttrVal(Number nn, Class componentType)
+	{
+		data = Array.newInstance(componentType, 1);
+		Array.set(data, 0, nn);
+	}
+
+
+	NumericAttrVal(Object data)
+	{
+		/* sanity checking and copy in semantics done in caller */
+		this.data = data;
+	}
+
+	public int
+	hashCode()
+	{
+		int h = 0;
+		final int len = getLength();
+		for(int ii = 0; ii < len; ii++)
+			h = (h * 13) + get(ii).hashCode();
+		return h;
+	}
+
+	public boolean
+	equals(Object oo)
+	{
+		if(oo instanceof NumericAttrVal)
+		{
+			final NumericAttrVal aa = (NumericAttrVal) oo;
+			if(getComponentType() == aa.getComponentType())
+			{
+				// potentially equal
+				final int length = getLength();
+				if(length == aa.getLength())
+				{
+					for(int ii = 0; ii < length; ii++)
+						if(!get(ii).equals(aa.get(ii)))
+							return false;
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	Object
+	getValue()
+	{
+		return Attribute.arrayClone(data);
+	}
+
+	Object
+	get(int index)
+	{
+		return Array.get(data, index);
+	}
+
+	Number
+	getNumericValue(int index)
+	{
+		return (Number)	Array.get(data, index);
+	}
+
+	Number
+	getNumericValue() {
+		return this.getNumericValue(0);
+	}
+
+	Class
+	getComponentType()
+	{
+		return data.getClass().getComponentType();
+	}
+
+	int
+	getLength()
+	{
+		return Array.getLength(data);
+	}
+
+	void
+	toCdl(StringBuffer buf)
+	{
+		final int last = Array.getLength(data) - 1;
+		for(int ii = 0; ii <= last; ii++) {
+			buf.append(Array.get(data, ii));
+			if(ii < last)
+				buf.append(", ");
+		}
+	}
+
+	/**
+	 * An array of primitives
+	 */
+	private final Object data;
+
+}
+
+final class StringAttrVal extends AttrVal {
+
+  StringAttrVal(String str) {
+    if (str.length() == 0) {  // empty string test added -dwd
+      data = "";
+      return;
+    }
+    if (str.charAt(str.length()-1) == 0) { // trailing null test - dwd
+      data = str.substring(0, str.length()-1);
+    } else
+      data = str;
+    }
+
+
+  StringAttrVal(char [] charArray) {
+    int len = charArray.length;
+    if (len == 0) {  // empty string test added - dwd
+      data="";
+      return;
+    }
+    if (charArray[len-1] == 0) {  // trailing null test - dwd
+      data = new String(charArray, 0, len-1);
+    } else
+      data = new String(charArray);
+    }
+
+	public int
+	hashCode()
+	{
+		return data.hashCode();
+	}
+
+	public boolean
+	equals(Object oo)
+	{
+		if(oo instanceof StringAttrVal)
+		{
+			final String other = (String)((StringAttrVal)oo).data;
+			if(data == other)
+				return true;
+			// else
+			return data.equals(other);
+		}
+		return false;
+	}
+
+	Object
+	getValue()
+	{
+		return data;
+	}
+
+	Object
+	get(int index)
+	{
+		return new Character(data.charAt(index));
+	}
+
+	Number
+	getNumericValue(int index)
+	{
+		return new Integer(data.charAt(index));
+	}
+
+	Number
+	getNumericValue() {
+		return this.getNumericValue(0);
+	}
+
+	Class
+	getComponentType()
+	{
+		return Character.TYPE;
+	}
+
+	int
+	getLength()
+	{
+		return data.length();
+	}
+
+	void
+	toCdl(StringBuffer buf)
+	{
+		buf.append("\"");
+		buf.append(data);
+		buf.append("\"");
+	}
+
+	private final String data;
+}
diff --git a/ucar/netcdf/AttributeDictionary.java b/ucar/netcdf/AttributeDictionary.java
new file mode 100644
index 0000000..9987e50
--- /dev/null
+++ b/ucar/netcdf/AttributeDictionary.java
@@ -0,0 +1,202 @@
+// $Id: AttributeDictionary.java,v 1.4 2002-05-29 18:31:33 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import java.io.Serializable;
+
+/**
+ * AttributeDictionary is the machinery to implement AttributeSet.
+ * It wraps NamedDictionary so that the NamedDictionary can only
+ * contain Attributes. Note that AttributeDictionary is mutable,
+ * it has methods for adding new elements and removing elements.
+ * These methods should be exposed in the ProtoVariable and Schema
+ * implementations, and _not_ exposed in the Variable and Netcdf
+ * implementations.
+ * <p>
+ * Note: no public constructor.
+ *
+ * @see AttributeSet
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:33 $
+ */
+
+class AttributeDictionary implements AttributeSet, Serializable {
+
+    AttributeDictionary() {
+	this.attributes = new NamedDictionary(0);
+    }
+
+    AttributeDictionary(Attribute [] attrArray) {
+	this.attributes = new NamedDictionary(attrArray);
+    }
+
+    AttributeDictionary(final AttributeSet ss) {
+	this.attributes = new NamedDictionary(ss.size(),
+		// The cost of type safety at the interface level.
+		new java.util.Enumeration () {
+			final AttributeIterator ee = ss.iterator();
+	    		public boolean hasMoreElements() {
+				return ee.hasNext();
+	    		}
+			public Object nextElement() {
+				return (Object) ee.next();
+			}
+		}
+	);
+    }
+
+    /**
+     * Returns the number of elements contained within the Dictionary. 
+     */
+    public int size() {
+	return attributes.size();
+    }
+
+    /**
+     * Returns an iterator for the elements. Use the Iterator methods 
+     * on the returned object to fetch the elements sequentially.
+     * @see java.util.Iterator
+     */
+    public AttributeIterator iterator() {
+	return new AttributeIterator() {
+		final java.util.Enumeration ee = attributes.elements();
+		
+    		public boolean hasNext() {
+			return ee.hasMoreElements();
+    		}
+
+		public Attribute next() {
+			return (Attribute) ee.nextElement();
+		}
+
+	};
+    }
+
+    /**
+     * @return a new Array containing the elements of this set.
+     */
+    public Attribute [] toArray() {
+	final Attribute [] aa = new Attribute[this.size()];
+	final AttributeIterator ee = this.iterator();
+	for(int ii = 0; ee.hasNext(); ii++)
+		aa[ii] = ee.next();
+	return aa;
+    }
+
+    /**
+     * Gets the attribute associated with the specified name.
+     * @param name the name of the attribute
+     * @return the attribute, or null if not found
+     */
+    public Attribute get(String name) {
+	return (Attribute) attributes.get(name);
+    }
+
+    /**
+     * Tests if the Attribute identified by <code>name</code>
+     * is in this set.
+     * @param name String which identifies the desired attribute
+     * @return <code>true</code> if and only if this set contains
+     * the named Attribute.
+     */
+    public boolean contains(String name) {
+	return attributes.contains(name);
+    }
+
+    /**
+     * Tests if the argument is in this set.
+     * @param oo some Object
+     * @return <code>true</code> if and only if this set contains
+     * <code>oo</code>
+     */
+    public boolean contains(Object oo) {
+	return attributes.contains(oo);
+    }
+
+// Begin Methods used when mutable
+
+    /**
+     * Ensures that this set contains the specified Attribute.
+     * If a different Attribute with the same name, was in the set,
+     * it is returned, otherwise null is returned.
+     *
+     * @param attr the Attribute to be added to this set.
+     * @return Attribute replaced or null if not a replacement
+     */
+    public Attribute put(Attribute attr) {
+	return (Attribute) attributes.put(attr);
+    }
+
+    
+    /**
+     * Delete the Attribute specified by name from this set.
+     *
+     * @param name String identifying the Attribute to be removed.
+     * @return true if the Set changed as a result of this call.
+     */
+    public boolean remove(String name) {
+	final Named oo = attributes.remove(name);
+	return oo != null ? true : false;
+    }
+
+
+    /**
+     * Delete the Attribute specified from this set.
+     *
+     * @param oo Attribute to be removed.
+     * @return true if the Set changed as a result of this call.
+     */
+    public boolean remove(Object oo) {
+	if(this.contains(oo))
+	{
+		return this.remove(((Named)oo).getName());
+	}
+	return false;
+    }
+
+// End Methods used when mutable
+
+    /**
+     * Format as CDL.
+     * @param buf StringBuffer into which to write
+     */
+    public void
+    toCdl(StringBuffer buf)
+    {
+	for (AttributeIterator iter = this.iterator();
+			iter.hasNext() ;) {
+		buf.append("\t\t");
+		iter.next().toCdl(buf);
+		buf.append("\n");
+	}
+    }
+
+    /**
+     * @return a CDL string of this.
+     */
+    public String toString() {
+	StringBuffer buf = new StringBuffer();
+	toCdl(buf);
+	return buf.toString();
+    }
+
+    protected final NamedDictionary attributes;
+}
diff --git a/ucar/netcdf/AttributeIterator.java b/ucar/netcdf/AttributeIterator.java
new file mode 100644
index 0000000..0ca71ef
--- /dev/null
+++ b/ucar/netcdf/AttributeIterator.java
@@ -0,0 +1,41 @@
+// $Id: AttributeIterator.java,v 1.4 2002-05-29 18:31:33 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * Type specific Iterator.
+ * Use the Iterator methods to fetch elements sequentially.
+ * @see java.util.Iterator
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:33 $
+ */
+public interface AttributeIterator {
+    /**
+     * Returns <code>true</code> if there are more elements.
+     */
+    boolean hasNext();
+    /**
+     * Returns the next element. Calls to this
+     * method will step through successive elements.
+     * @exception java.util.NoSuchElementException If no more elements exist.
+     */
+    Attribute next();
+}
diff --git a/ucar/netcdf/AttributeSet.java b/ucar/netcdf/AttributeSet.java
new file mode 100644
index 0000000..f5096eb
--- /dev/null
+++ b/ucar/netcdf/AttributeSet.java
@@ -0,0 +1,126 @@
+// $Id: AttributeSet.java,v 1.4 2002-05-29 18:31:33 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * AttributeSet is an inquiry interface
+ * for a collection of Attributes.
+ * Uses naming conventions of Collection framework.
+ * @see java.util.Collection
+ * @see Attribute
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:33 $
+ */
+public interface AttributeSet {
+
+    /**
+     * Returns the number of elements in the set
+     * @return int number of elements in the set
+     */
+    int size();
+
+    /**
+     * Returns AttributeIterator for the elements.
+     * @return AttributeIterator for the elements.
+     * @see AttributeIterator
+     */
+    AttributeIterator iterator();
+
+    /**
+     * Returns a new Array containing the elements of this set.
+     * @return a new Array containing the elements of this set.
+     */
+    Attribute [] toArray();
+
+    /**
+     * Retrieve the attribute associated with the specified name.
+     * @param name String which identifies the desired attribute
+     * @return the attribute, or null if not found
+     */
+    public Attribute get(String name);
+    
+    /**
+     * Tests if the Attribute identified by <code>name</code>
+     * is in this set.
+     * @param name String which identifies the desired attribute
+     * @return <code>true</code> if and only if this set contains
+     * the named Attribute.
+     */
+    public boolean contains(String name);
+
+    /**
+     * Tests if the argument is in this set.
+     * @param oo some Object
+     * @return <code>true</code> if and only if this set contains
+     * <code>oo</code>
+     */
+    public boolean contains(Object oo);
+
+    /**
+     * Ensures that this set contains the specified Attribute.
+     * If a different Attribute with the same name, was in the set,
+     * it is returned, otherwise null is returned.
+     * <p>
+     * This is an "optional operation" in the sense of the Collections
+     * framework. In the context of this package, this method will throw
+     * UnsupportedOperationException when the set is unmodifiable.
+     * This will be the case when this AttributeSet is associated with
+     * a Netcdf or a Variable. The AttributeSet will be modifiable when
+     * it is associated with a Schema or ProtoVariable.
+     *
+     * @param attr the Attribute to be added to this set.
+     * @return Attribute replaced or null if not a replacement
+     */
+    public Attribute put(Attribute attr);
+
+    
+    /**
+     * Delete the Attribute specified by name from this set.
+     * <p>
+     * This is an "optional operation" in the sense of the Collections
+     * framework. In the context of this package, this method will throw
+     * UnsupportedOperationException when the set is unmodifiable.
+     * This will be the case when this AttributeSet is associated with
+     * a Netcdf or a Variable. The AttributeSet will be modifiable when
+     * it is associated with a Schema or ProtoVariable.
+     *
+     * @param name String identifying the Attribute to be removed.
+     * @return true if the Set changed as a result of this call.
+     */
+    public boolean remove(String name);
+
+
+    /**
+     * Delete the Attribute specified from this set.
+     * <p>
+     * This is an "optional operation" in the sense of the Collections
+     * framework. In the context of this package, this method will throw
+     * UnsupportedOperationException when the Set is unmodifiable.s
+     * This will be the case when this AttributeSet is associated with
+     * a Netcdf or a Variable. The AttributeSet will be modifiable when
+     * it is associated with a Schema or ProtoVariable.
+     *
+     * @param oo Attribute to be removed.
+     * @return true if the Set changed as a result of this call.
+     */
+    public boolean remove(Object oo);
+}
diff --git a/ucar/netcdf/Dimension.java b/ucar/netcdf/Dimension.java
new file mode 100644
index 0000000..3c425e6
--- /dev/null
+++ b/ucar/netcdf/Dimension.java
@@ -0,0 +1,154 @@
+// $Id: Dimension.java,v 1.4 2002-05-29 18:31:33 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import java.io.Serializable;
+
+/**
+ * A Dimension object is used to contain an array length which is
+ * named for use in multiple netcdf variables.
+ * <p>
+ * This class supports construction, retrieval of the name and retrieval 
+ * of the length value. The name is constant over the lifetime of the object.
+ * Also note that change of the dimension length value is not
+ * allowed. In the subclass UnlimitedDimension, the length may be increased.
+ * <p>
+ * Instances which have same name and same value are equal.
+ * We override hashCode() and equals() to be consistent with
+ * this semantic.
+ *
+ * @see UnlimitedDimension
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:33 $
+ */
+
+public class
+Dimension
+	implements Named, Serializable, Cloneable
+{
+
+ /* Begin Constructors */
+
+	/**
+	 * @param name  String which is to be the name of this Dimension
+	 * @param length  int length of this Dimension
+	 */
+	public
+	Dimension(String name, int length) {
+		this.name = name;
+		this.length = length;
+	}
+
+ /* End Constructors */
+ /* Begin Overrides */
+
+	/**
+	 * Instances which have same name and same value are equal.
+	 * Overrides Object.hashCode() to be consistent with this semantic.
+	 */
+	public int
+	hashCode()
+	{
+		return (name.hashCode() ^ length);
+	}
+
+	/**
+	 * Instances which have same name and same value are equal.
+	 * Overrides Object.hashCode() to be consistent with this semantic.
+	 */
+	public boolean
+	equals(Object oo)
+	{
+		if(this == oo) return true;
+		if((oo != null) && (oo instanceof Dimension)
+			&& !(oo instanceof UnlimitedDimension))
+		{
+			final Dimension aDim = (Dimension)oo;
+			return ((length == aDim.getLength())
+				&& name.equals(aDim.getName()));
+		}
+		return false;
+	}
+	
+	public Object
+	clone()
+	{
+		/*
+		 * Since this is immutable, just return it.
+		 * Overridden in mutable subclass.
+		 */
+		return this;
+	}
+
+	/**
+	 * @return a string representation of the object.
+	 */
+	public String
+	toString() {
+		StringBuffer buf = new StringBuffer();
+		toCdl(buf);
+		return buf.toString();
+	}
+
+ /* End Overrides */
+
+	/**
+	 * Returns the name of this Dimension.
+	 * @return String which identifies this Dimension.
+	 */
+	public final String
+	getName() {
+		return name;
+	}
+
+	/**
+	 * Retrieve the length.
+	 * @return int which is the length of this Dimension
+	 */
+	public final int
+	getLength() {
+		return length;
+	}
+
+	/**
+	 * Format as CDL.
+	 * @param buf StringBuffer into which to write
+	 */
+	public void
+	toCdl(StringBuffer buf)
+	{
+		buf.append(getName());
+		buf.append(" = ");
+		buf.append(getLength());
+		buf.append(" ;");
+	}
+
+	/**
+	 * The length. Immutable in this class.
+	 * @serial
+	 */
+	protected int length;
+
+	/**
+	 * @serial
+	 */
+	private final String name;
+}
diff --git a/ucar/netcdf/DimensionDictionary.java b/ucar/netcdf/DimensionDictionary.java
new file mode 100644
index 0000000..85c6665
--- /dev/null
+++ b/ucar/netcdf/DimensionDictionary.java
@@ -0,0 +1,243 @@
+// $Id: DimensionDictionary.java,v 1.4 2002-05-29 18:31:33 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import java.io.Serializable;
+
+/**
+ * DimensionDictionary is package private implementation of DimensionSet.
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:33 $
+ */
+
+class
+DimensionDictionary
+	implements DimensionSet, Serializable
+{
+	/**
+	 */
+	DimensionDictionary()
+	{
+		this.dimensions = new NamedDictionary(0);
+	}
+
+	/**
+	 */
+	DimensionDictionary(final DimensionSet ss)
+	{
+		dimensions = new NamedDictionary(ss.size());
+		final DimensionIterator iter = ss.iterator();
+		while(iter.hasNext())
+		{
+			put(iter.next());
+		}
+	}
+
+	/**
+	 */
+	DimensionDictionary(ProtoVariable [] varArray)
+	{
+		// varArray synchronized in caller
+		this.dimensions = new NamedDictionary(0);
+		for(int jj = 0; jj < varArray.length; jj++)
+		{
+			varArray[jj].connectDims(this);
+		}
+	}
+
+	/**
+	 * Returns the number of elements contained within the Dictionary. 
+	 */
+	public int
+	size()
+	{
+		return dimensions.size();
+	}
+
+	/**
+	 * Returns an iterator of the elements. Use the Iterator methods 
+	 * on the returned object to fetch the elements sequentially.
+	 * @see java.util.Iterator
+	 */
+	public DimensionIterator
+	iterator()
+	{
+		return new DimensionIterator() {
+			final java.util.Enumeration ee = dimensions.elements();
+			
+			public boolean hasNext() {
+				return ee.hasMoreElements();
+			}
+
+			public Dimension next() {
+				return (Dimension) ee.nextElement();
+			}
+
+		};
+	}
+
+	/**
+	 * @return a new Array containing (clones of) elements of this set.
+	 */
+	synchronized public Dimension []
+	toArray() {
+		final Dimension [] aa = new Dimension[this.size()];
+		final DimensionIterator ee = this.iterator();
+		for(int ii = 0; ee.hasNext(); ii++)
+		{
+			final Dimension dim = ee.next();
+			aa[ii] = (Dimension) dim.clone();
+		}
+		return aa;
+	}
+
+	/**
+	 * Gets the dimension associated with the specified name.
+	 * @param name the name of the dimension
+	 * @return the dimension, or null if not found
+	 */
+	public Dimension
+	get(String name) {
+		return (Dimension) dimensions.get(name);
+	}
+
+	/**
+	 * Tests if the Dimension identified by <code>name</code>
+	 * is in this set.
+	 * @param name String which identifies the desired dimension
+	 * @return <code>true</code> if and only if this set contains
+	 * the named Dimension.
+	 */
+	public boolean
+	contains(String name) {
+		return dimensions.contains(name);
+	}
+
+	/**
+	 * Tests if the argument is in this set.
+	 * @param oo some Object
+	 * @return <code>true</code> if and only if this set contains
+	 * <code>oo</code>
+	 */
+	public boolean
+	contains(Object oo) {
+		return dimensions.contains(oo);
+	}
+
+	/**
+	 * Format as CDL.
+	 * @param buf StringBuffer into which to write
+	 */
+	public void
+	toCdl(StringBuffer buf)
+	{
+		buf.append("dimensions:\n");
+		for (DimensionIterator iter = this.iterator();
+				iter.hasNext() ;) {
+			buf.append("\t");
+			iter.next().toCdl(buf);
+			buf.append("\n");
+		}
+	}
+
+	/**
+	 * @return a CDL string of this.
+	 */
+	public String toString() {
+		StringBuffer buf = new StringBuffer();
+		toCdl(buf);
+		return buf.toString();
+	}
+
+	/**
+	 * Ensures that this set contains a Dimension which is
+	 * equal() to the argument.
+	 * If such an element exists, it is returned.
+	 * Otherwise, a clone is created, added to the dictionary,
+	 * and the clone is returned.
+	 * NOTE: this is different than the usual container.put() 
+	 * return!
+         * <p>
+	 * If a different (not equal()) Dimension with the same name
+	 * was in the set, throw IllegalArgumentException.
+	 *
+	 * @param dim the Dimension to be added to this set.
+	 * @return Dimension added or matching member from the set.
+	 */
+	synchronized Dimension
+	put(Dimension dim) {
+		final String dname = dim.getName();
+		final Dimension found = get(dname);
+		if(found != null)
+		{
+			if(found.equals(dim))
+				return found; // Normal return
+			// else
+			throw new IllegalArgumentException(
+					"Duplicate dimension name"); 
+		}
+		// else
+		final Dimension copy = (Dimension) dim.clone();
+		dimensions.put(copy);
+		return copy;
+	}
+
+	/**
+	 * Add a Dimension instance to this dictionary.
+	 * <>
+	 * Use this form when initializing from an existing
+	 * data set and you want instances (and thus UnlimitedDimension
+	 * values) preserved.
+	 */
+	void
+	initialPut(Dimension dim)
+	{
+		if(contains(dim.getName()))
+			throw new IllegalArgumentException(
+					"Duplicate dimension name \""
+				+ dim.getName() + "\""); 
+		// else
+		dimensions.put(dim);
+	}
+
+	/**
+	 * Delete the Dimension specified by name from this set.
+	 *
+	 * @param name String identifying the Dimension to be removed.
+	 * @return true if the Set changed as a result of this call.
+	 */
+	boolean
+	remove(String name) {
+		final Named oo = dimensions.remove(name);
+		return oo != null ? true : false;
+	}
+
+	/**
+	 * Searches for the specified object, starting from the first position
+	 * and returns an index to it.
+	 * @param elem the desired element
+	 * @return the index of the element, or -1 if it was not found.
+	 */
+	int
+	indexOf(Dimension elem) {
+		return dimensions.indexOf(elem);
+	}
+
+	private final NamedDictionary dimensions;
+}
diff --git a/ucar/netcdf/DimensionIterator.java b/ucar/netcdf/DimensionIterator.java
new file mode 100644
index 0000000..08708f9
--- /dev/null
+++ b/ucar/netcdf/DimensionIterator.java
@@ -0,0 +1,41 @@
+// $Id: DimensionIterator.java,v 1.4 2002-05-29 18:31:33 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * Type specific Iterator.
+ * Use the Iterator methods to fetch elements sequentially.
+ * @see java.util.Iterator
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:33 $
+ */
+public interface DimensionIterator {
+    /**
+     * Returns <code>true</code> if there are more elements.
+     */
+    boolean hasNext();
+    /**
+     * Returns the next element. Calls to this
+     * method will step through successive elements.
+     * @exception java.util.NoSuchElementException If no more elements exist.
+     */
+    Dimension next();
+}
diff --git a/ucar/netcdf/DimensionSet.java b/ucar/netcdf/DimensionSet.java
new file mode 100644
index 0000000..e6f5a7d
--- /dev/null
+++ b/ucar/netcdf/DimensionSet.java
@@ -0,0 +1,77 @@
+// $Id: DimensionSet.java,v 1.4 2002-05-29 18:31:33 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * DimensionSet is an inquiry interface
+ * for a collection of Dimensions.
+ * Uses naming conventions of Collection framework.
+ * @see java.util.Collection
+ * @see Dimension
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:33 $
+ */
+public interface DimensionSet {
+
+    /**
+     * Returns the number of elements in the set
+     * @return int number of elements in the set
+     */
+    int size();
+
+    /**
+     * Returns DimensionIterator for the elements.
+     * @return DimensionIterator for the elements.
+     * @see DimensionIterator
+     */
+    DimensionIterator iterator();
+
+    /**
+     * Returns a new Array containing the elements of this set.
+     * @return a new Array containing the elements of this set.
+     */
+    Dimension [] toArray();
+
+    /**
+     * Retrieve the dimension associated with the specified name.
+     * @param name String which identifies the desired dimension
+     * @return the dimension, or null if not found
+     */
+    public Dimension get(String name);
+
+    /**
+     * Tests if the Dimension identified by <code>name</code>
+     * is in this set.
+     * @param name String which identifies the desired dimension
+     * @return <code>true</code> if and only if this set contains
+     * the named Dimension.
+     */
+    public boolean contains(String name);
+
+    /**
+     * Tests if the argument is in this set.
+     * @param oo some Object
+     * @return <code>true</code> if and only if this set contains
+     * <code>oo</code>
+     */
+    public boolean contains(Object oo);
+}
diff --git a/ucar/netcdf/HTTPRandomAccessFile.java b/ucar/netcdf/HTTPRandomAccessFile.java
new file mode 100644
index 0000000..7109be9
--- /dev/null
+++ b/ucar/netcdf/HTTPRandomAccessFile.java
@@ -0,0 +1,127 @@
+// $Id: HTTPRandomAccessFile.java,v 1.4 2002-05-29 18:31:34 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * HTTPRandomAccessFile.java.
+ * @author John Caron, based on work by Donald Denbo
+ */
+
+package ucar.netcdf;
+
+import HTTPClient.*;
+
+import java.io.FileNotFoundException;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * HTTPRandomAccessFile.java.
+ * @author John Caron, based on work by Donald Denbo
+ */
+
+public class HTTPRandomAccessFile extends RandomAccessFile {
+
+  private long total_length = 0;
+  private HTTPConnection conn;
+  private String path;
+  private NVPair[] form = null;
+  private NVPair[] header = new NVPair[2];
+
+  public HTTPRandomAccessFile(URL url) throws IOException {
+    this( url, defaultBufferSize);
+  }
+
+  public HTTPRandomAccessFile(URL url, int bufferSize) throws IOException {
+    super( bufferSize);
+    file = null;
+
+    path = url.getFile();	// not "getPath()" to accomodate JDK 1.2
+    conn = new HTTPConnection(url);
+    try {
+      HTTPResponse test = conn.Head(path);
+      if(test.getStatusCode() == 404)
+        throw new FileNotFoundException(test.getReasonLine());
+      if(test.getStatusCode() >= 300) {
+        throw new IOException(test.getReasonLine());
+      }
+      total_length = test.getHeaderAsInt("Content-Length");
+    } catch (ModuleException me) {
+      me.printStackTrace();
+      throw new IOException();
+    }
+    header[0] = new NVPair("User-Agent", "HTTPnetCDF;");
+  }
+
+  protected int read_(long pos, byte[] buff, int off, int len) throws IOException {
+    long end = pos + len - 1;
+    if (end >= total_length)
+      end = total_length - 1;
+
+    byte[] data = null;
+    header[1] = new NVPair("Range", "bytes="+pos+"-"+end);
+    //System.out.print(" want = "+pos+"-"+end+": ");
+
+    try {
+      HTTPResponse res = conn.Get(path, form, header);
+      if(res.getStatusCode() >= 300) {
+        System.out.println(new String(res.getData()));
+        throw new IOException(res.getReasonLine());
+      }
+      data = res.getData();
+      //System.out.println(res.getHeader("Content-Range"));
+    } catch (ModuleException me) {
+      me.printStackTrace();
+      throw new IOException(me.getMessage());
+    }
+
+    // copy to output buffer
+    int reslen = Math.min( len, data.length);
+    System.arraycopy( data, 0, buff, off, reslen );
+
+    return reslen;
+  }
+
+  public long length( ) throws IOException {
+    long fileLength = total_length;
+    if( fileLength < dataEnd )
+      return dataEnd;
+    else
+      return fileLength;
+  }
+
+
+  /**
+   * override the rest of the RandomAccessFile public methods
+   */
+  public void close() {
+  }
+
+  public FileDescriptor getFD() {
+    return null;
+  }
+
+  /**
+   * implement HTTP access.
+   */
+
+}
+
diff --git a/ucar/netcdf/Named.java b/ucar/netcdf/Named.java
new file mode 100644
index 0000000..6eb47d9
--- /dev/null
+++ b/ucar/netcdf/Named.java
@@ -0,0 +1,41 @@
+// $Id: Named.java,v 1.4 2002-05-29 18:31:34 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * An interface for Named objects in netcdf.
+ * <p>
+ * It supports retrieval of the name.
+ * In the classes which implement this interface,
+ * the private name data is final; the name is the constant
+ * for the lifetime of an object.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:34 $
+ */
+
+interface Named {
+    /**
+     * returns the name which identifies this thing.
+     * @return String which identifies this thing.
+     */
+    public String getName();
+}
diff --git a/ucar/netcdf/NamedDictionary.java b/ucar/netcdf/NamedDictionary.java
new file mode 100644
index 0000000..e0fbba3
--- /dev/null
+++ b/ucar/netcdf/NamedDictionary.java
@@ -0,0 +1,221 @@
+// $Id: NamedDictionary.java,v 1.4 2002-05-29 18:31:34 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import java.io.Serializable;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+
+/**
+ * NamedDictionary is a collection of Named things.
+ * This is used in the implementation of the other netcdf collections.
+ * Note: no public methods, all are package scope or more private.
+ * <p>
+ * It turns out that people think that the order of these things is
+ * important. So, we implement as a Vector, with an aux Hashtable for
+ * lookup by name.
+ * <p>
+ * For serialization, we only put the Vector part over the wire.
+ * The Hashtable is reconstructed at the other end.
+ * In fact, we encode the vector as an array for transport, saving
+ * a few more bytes.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $
+ */
+
+class
+NamedDictionary
+		implements Serializable
+{
+
+	private void
+	init(Named [] na)
+	{
+		if(na == null) {
+			vector = new Vector(0);
+		}
+		else synchronized (na) {
+			vector = new Vector(na.length);
+			for(int ii = 0; ii < na.length; ii++) {
+				vector.addElement(na[ii]);
+			}
+		}
+		// TODO: Dont use Hashtable unless size is worth it?
+		int size = vector.size();
+		if(size < 3)
+			size = 3;
+		size *= 4;
+		size /= 3;
+		table = new Hashtable(size, .75f + Float.MIN_VALUE);
+		Enumeration ee = vector.elements();
+		while ( ee.hasMoreElements() ) {
+			final Named value = (Named) ee.nextElement();
+			table.put(value.getName(), value);
+		}
+	}
+
+	NamedDictionary(Named [] na)
+	{
+		init(na);
+	}
+
+	NamedDictionary(int size, Enumeration ee)
+	{
+		vector = new Vector(size);
+		table = new Hashtable(((size < 3 ? 3 : size) * 4)/3,
+			 .75f + Float.MIN_VALUE);
+		while ( ee.hasMoreElements() ) {
+			final Named value = (Named) ee.nextElement();
+			this.put(value);
+		}
+	}
+
+	/**
+	 */
+	NamedDictionary(int size)
+	{
+		vector = new Vector(size);
+		table = new Hashtable(((size < 3 ? 3 : size) * 4)/3,
+			 .75f + Float.MIN_VALUE);
+	}
+
+	/**
+	 * Returns the number of elements contained within the Dictionary. 
+	 */
+	int
+	size()
+		{ return vector.size(); }
+
+	/**
+	 * Returns an enumeration of the elements. Use the Enumeration methods 
+	 * on the returned object to fetch the elements sequentially.
+	 * @see java.util.Enumeration
+	 */
+	Enumeration
+	elements()
+		{ return vector.elements(); }
+
+	/**
+	 * Gets the object associated with the specified name.
+	 * @param name the name of the dimension
+	 * @return the dimension, or null if not found
+	 * @see NamedDictionary#put
+	 */
+	Named
+	get(String name)
+		{ return (Named) table.get(name); }
+
+	/**
+	 * Puts the specified element into the Dictionary, using the its name as
+	 * key.  The element may be retrieved by doing a get() with the same 
+	 * name.  The element cannot be null.
+	 * @param value the specified element 
+	 * @return the old value of the key, or null if it did not have one.
+	 * @exception NullPointerException If the value of the specified
+	 * element is null.
+	 * @see NamedDictionary#get
+	 */
+	synchronized Named
+	put(Named value)
+	{
+		String	name = value.getName();
+		Named	prev = get(name);
+		if (prev != null)
+		    vector.remove(prev);
+		vector.addElement(value);
+		return (Named) table.put(name, value);
+	}
+
+	/**
+	 * Removes the element corresponding to the key. Does nothing if the
+	 * key is not present.
+	 * @param name the name of the Named that needs to be removed
+	 * @return the Named, or null if no match.
+	 *
+	 */
+	synchronized Named
+	remove(String name)
+	{
+		vector.removeElement(get(name));
+		return (Named) table.remove(name);
+	}
+
+	/**
+	 * Searches for the specified object, starting from the first position
+	 * and returns an index to it. Only used by DimensionDictionary.
+	 * @param elem the desired element
+	 * @return the index of the element, or -1 if it was not found.
+	 */
+	int
+	indexOf(Named elem)
+		{ return vector.indexOf(elem); }
+
+	/**
+	 * Tests if the Named identified by <code>name</code>
+	 * is in this set.
+	 * @param name String which identifies the desired object
+	 * @return <code>true</code> if and only if this set contains
+	 * the Named
+	 */
+	boolean
+	contains(String name)
+		{ return table.containsKey(name); }
+
+	/**
+	 * Tests if the argument is in this set.
+	 * @param oo some Object
+	 * @return <code>true</code> if and only if this set contains
+	 * <code>oo</code>
+	 * TODO? typecheck?
+	 */
+	boolean
+	contains(Object oo)
+		{ return table.contains(oo); }
+
+	private void
+	writeObject(ObjectOutputStream out)
+     		throws IOException
+	{
+		final int sz = size();
+		final Named [] na =  new Named[sz];
+		int ii = 0;
+		Enumeration ee = vector.elements();
+		while ( ee.hasMoreElements() ) {
+			na[ii++] = (Named) ee.nextElement();
+		}
+		out.writeObject(na);
+	}
+
+	private void
+	readObject(ObjectInputStream in)
+     		throws IOException, ClassNotFoundException
+	{
+		Named [] na = (Named []) in.readObject();
+		init(na);
+	}
+
+	private Vector vector; // encoded over the wire as an array.
+	private transient Hashtable table;
+}
diff --git a/ucar/netcdf/Netcdf.java b/ucar/netcdf/Netcdf.java
new file mode 100644
index 0000000..52adcc1
--- /dev/null
+++ b/ucar/netcdf/Netcdf.java
@@ -0,0 +1,108 @@
+// $Id: Netcdf.java,v 1.4 2002-05-29 18:31:34 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * This is the interface for Netcdf objects.
+ * A Netcdf is seen as a set of Variables, the DimensionSet
+ * which is the union of the Dimensions used by the Variables,
+ * and any global Attributes. The Variable interface has
+ * data i/o (get/set) functionality.
+ * <p>
+ * This set, the associated DimensionSet and AttributeSet are immutable.
+ * <p>
+ * The portions of this interface which do not have to
+ * do with i/o capable Variables are available in Schema.
+ * 
+ * @see Variable
+ * @see DimensionSet
+ * @see AttributeSet
+ * @see Schema
+ * @see java.util.Collection
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:34 $
+ */
+
+public interface Netcdf {
+
+    /**
+     * Returns the number of variables
+     * @return int number of variables
+     */
+    int size();
+
+    /**
+     * Returns VariableIterator for the elements.
+     * @return VariableIterator for the elements.
+     * @see VariableIterator
+     */
+    VariableIterator iterator();
+
+    /**
+     * Retrieve the variable associated with the specified name.
+     * @param name String which identifies the desired variable
+     * @return the variable, or null if not found
+     */
+    Variable get(String name);
+    
+    /**
+     * Tests if the Variable identified by <code>name</code>
+     * is in this set.
+     * @param name String which identifies the desired variable
+     * @return <code>true</code> if and only if this set contains
+     * the named variable.
+     */
+    boolean contains(String name);
+
+    /**
+     * Tests if the argument is in this set.
+     * @param oo some Object
+     * @return <code>true</code> if and only if this set contains
+     * <code>oo</code>
+     */
+    boolean contains(Object oo);
+
+    /**
+     * Returns the set of dimensions associated with this, 
+     * the union of those used by each of the variables.
+     *
+     * @return DimensionSet containing dimensions used
+     * by any of the variables. May be empty. Won't be null.
+     */
+    public DimensionSet getDimensions();
+
+    /**
+     * Returns the set of attributes associated with this, 
+     * also know as the "global" attributes.
+     * 
+     * @return AttributeSet. May be empty. Won't be null.
+     */
+    public AttributeSet getAttributes();
+
+    /**
+     * Convenience function; look up global Attribute by name.
+     *
+     * @param name the name of the attribute
+     * @return the attribute, or null if not found
+     */
+    public Attribute getAttribute(String name);
+}
diff --git a/ucar/netcdf/NetcdfFile.java b/ucar/netcdf/NetcdfFile.java
new file mode 100644
index 0000000..e6e8874
--- /dev/null
+++ b/ucar/netcdf/NetcdfFile.java
@@ -0,0 +1,1896 @@
+// $Id: NetcdfFile.java,v 1.9 2003-03-14 16:29:05 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package ucar.netcdf;
+
+import ucar.multiarray.Accessor;
+import ucar.multiarray.AbstractAccessor;
+import ucar.multiarray.MultiArray;
+import ucar.multiarray.MultiArrayImpl;
+import ucar.multiarray.IndexIterator;
+import ucar.multiarray.OffsetIndexIterator;
+
+import java.lang.reflect.Array;
+import java.lang.Math;
+
+import java.io.File;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+
+// use buffered ucar.netcdf.RandomAccessFile instead
+// import java.io.RandomAccessFile;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A concrete implementation of the Netcdf interface,
+ * this class provides connection to NetCDF version 1 files.
+ * <p>
+ * Constructors for creating new files and opening existing
+ * ones.
+ *
+ * @see Netcdf
+ * @author $Author: donm $
+ * @version $Revision: 1.9 $ $Date: 2003-03-14 16:29:05 $
+ */
+public class NetcdfFile	extends AbstractNetcdf {
+
+ /* Begin constructors */
+
+    /**
+     * Create a new netcdf version 1 file from a Schema template.
+     *
+     * @param file       the file name as File object
+     * @param clobber    if <code>true</code>, overwrite existing
+     * @param fill       if <code>false</code>, suppress variable pre fill
+     * @param template   the Schema used as construction template. May be empty,
+     *                     shouldn't be null.
+     *
+     * @see #setFill
+     * @see Netcdf
+     */
+    public
+    NetcdfFile(File file, boolean clobber, boolean fill,
+                        Schema template)
+                throws IOException
+    {
+        super(new Schema(template), true);
+        if(!clobber && file.exists())
+        {
+                // TODO: netcdf exception?
+                throw new SecurityException(file.getName() + " exists");
+        }
+        // else
+        this.file = file;
+        this.raf = new RandomAccessFile(file, "rw");
+        this.doFill = fill;
+
+        compileBegins();
+        initRecSize();
+        writeV1();
+        fillerup();
+
+        url = null;
+    }
+
+    /**
+     * Create a new netcdf version 1 file from a Schema template.
+     *
+     * @param path       the file name as a String
+     * @param clobber    if <code>true</code>, overwrite existing
+     * @param fill       if <code>false</code>, suppress variable pre fill
+     * @param template   the Schema used as construction template. May be empty,
+     *                     shouldn't be null.
+     *
+     * @see #setFill
+     * @see Netcdf
+     */
+    public
+    NetcdfFile(String path, boolean clobber, boolean fill,
+                        Schema template)
+                throws IOException
+    {
+        this(new File(path), clobber, fill, template);
+    }
+
+    /**
+     * Open existing netcdf version 1 file.
+     *
+     * @param file       the file name as File object
+     * @param readonly	 if <code>true</code>, open read only,
+     *			else open for read and write.
+     */
+    public
+    NetcdfFile(File file, boolean readonly) throws IOException {
+        super();
+        this.file = file;
+        raf = new RandomAccessFile(file, readonly ? "r" : "rw");
+        readV1(raf);
+        initRecSize();
+        this.doFill = true;
+
+        url = null;
+    }
+
+    /**
+     * Open existing netcdf version 1 file.
+     *
+     * @param path       the file name as a String
+     * @param readonly	 if <code>true</code>, open read only,
+     *			else open for read and write.
+     */
+    public
+    NetcdfFile(String path, boolean ro)
+                throws IOException
+    {
+        this(new File(path), ro);
+    }
+
+    /**
+     * Open existing, read-only netcdf file through a URL. This may use either the
+     * file: or http: protocol. If it uses the file protocol, it will be opened as a
+     * read-only file using url.getFile(). If it uses the http protocol, it will be
+     * read over http using HTTPRandomAccessFile. The query
+     * component of the URL is ignored
+     *
+     * <p>Modified from ncBrowse (Donald Denbo).</p>
+     *
+     * @param url                    the URL of the netCDF dataset.
+     * @throws FileNotFoundException if the URL specifies a file that doesn't
+     *                               exist.
+     * @throws IOException           if an I/O failure occurs.
+     */
+  public NetcdfFile(URL url) throws FileNotFoundException, IOException {
+    super();
+    String path = url.getFile();
+    int    i = path.indexOf('?');
+    if (i != -1)
+	path = path.substring(0, i);
+    if (url.getProtocol().equalsIgnoreCase("file"))
+    {
+	/*
+	 * URL.getPath() isn't used in order to accomodate JDK 1.2.
+	 */
+        this.url = null;
+        file = new File(path);
+        raf = new RandomAccessFile (path, "r", 204800);
+    }   else   {
+	/* Defensive copy */
+	this.url =
+	    new URL(url.getProtocol(), url.getHost(), url.getPort(), path);
+	file = null;
+	//Use a pretty big buffer to reduce the number of seeks
+	raf = new HTTPRandomAccessFile(this.url, 204800);
+	//raf = new HTTPRandomAccessFile(this.url);
+    }
+    readV1(raf);
+    initRecSize();
+    this.doFill = true;
+  }
+
+ /* End constructors */
+ /* Begin Public */
+
+    /**
+     * Close this netcdf file.
+     * The inquiry interface calls will continue to be available,
+     * but I/O accesses will fail after this call.
+     * @see RandomAccessFile#close
+     */
+    public void
+    close() throws IOException
+    {
+        raf.close();
+    }
+
+    /**
+     * Flush anything written to disk.
+     * @see RandomAccessFile#flush
+     */
+    public void
+    flush() throws IOException
+    {
+        raf.flush();
+    }
+
+    /**
+     * Useful for identifying this instance among others.
+     * @return File object this was opened or created as.
+     */
+    public final File getFile() {
+        return file;
+    }
+
+    /**
+     * Useful for identifying this instance among others.
+     * @return File object this was opened or created as.
+     */
+    public final String getName() {
+        return (file != null) ? file.getPath() : url.toString();
+    }
+
+    /**
+     * Sets the "fill mode" to the argument.
+     * If true (the default), new storage is prefilled with the
+     * appropriate fill value. Otherwise, this activity is suppressed
+     * and the programmer should initialize all values.
+     * @see #getFill
+     * @param pleaseFill true to fill.
+     */
+    public synchronized void
+    setFill(boolean pleaseFill)
+    {
+        doFill = pleaseFill;
+    }
+
+    /**
+     * Get the current "fill mode".
+     * @see #setFill
+     * @return true iff we are prefilling new storage
+     * with the appropriate fill value.
+     */
+    public final boolean
+    getFill()
+    {
+        return doFill;
+    }
+
+    /**
+     * If this has an unlimited Dimension,
+     * return it, otherwise null.
+     * Note that this specific to NetcdfFile, not
+     * part of the Netcdf interface. Other implementations
+     * may support multiple unlimited dimensions.
+     * @deprecated
+     * @return UnlimitedDimension the unlimited dimension
+     */
+    public final UnlimitedDimension
+    unlimitedDimension()
+    {
+        return recDim;
+    }
+
+    /**
+     * Format as CDL.
+     * @param buf StringBuffer into which to write
+     */
+    public void
+    toCdl(StringBuffer buf)
+    {
+        buf.append("netcdf ");
+        if (file != null)
+          buf.append(file.getName());
+        else
+          buf.append(url.toString());
+        buf.append(" ");
+        super.toCdl(buf);
+    }
+
+ /* End Public */
+ /* Begin Constants */
+
+    /*
+     * netcdf file format version 1 "magic number"
+     */
+    final static int v1magic = 0x43444601;
+    /*
+     * tag for signed 1 byte integer
+     */
+    final static int NC_BYTE = 1;
+    /*
+     * tag for ISO/ASCII characters
+     */
+    final static int NC_CHAR = 2;
+    /*
+     * tag for signed 2 byte integer
+     */
+    final static int NC_SHORT = 3;
+    /*
+     * tag for signed 4 byte integer
+     */
+    final static int NC_INT = 4;
+    /*
+     * tag for single precision floating point number
+     */
+    final static int NC_FLOAT =	5;
+    /*
+     * tag for double precision floating point number
+     */
+    final static int NC_DOUBLE = 6;
+    /*
+     * tag for array of netcdf Dimensions
+     */
+    final static int NC_DIMENSION = 10;
+    /*
+     * tag for array of netcdf Variables
+     */
+    final static int NC_VARIABLE = 11;
+    /*
+     * tag for array of netcdf attributes
+     */
+    final static int NC_ATTRIBUTE = 12;
+
+    /*
+     * External representation aligns to 4 byte boundaries.
+     * a.k.a. BYTES_PER_XDR_UNIT
+     */
+    final static int X_ALIGN = 4;
+
+    /*
+     * External size of char.
+     * Netcdf v1 file format uses 8 bit ASCII
+     */
+    final static int X_SIZEOF_CHAR = 1;
+    /*
+     * External size of byte
+     */
+    final static int X_SIZEOF_BYTE = 1;
+    /*
+     * External size of short
+     */
+    final static int X_SIZEOF_SHORT = 2;
+    /*
+     * External size of int
+     */
+    final static int X_SIZEOF_INT = 4;
+    /*
+     * External size of float
+     */
+    final static int X_SIZEOF_FLOAT = 4;
+    /*
+     * External size of DOUBLE
+     */
+    final static int X_SIZEOF_DOUBLE = 8;
+
+    /*
+     * Reserved name of Fill attribute
+     */
+    static final String _FillValue = "_FillValue";
+
+    static final byte NC_FILL_BYTE = -127;
+    static final byte NC_FILL_CHAR = 0;
+    static final short NC_FILL_SHORT = -32767;
+    static final int NC_FILL_INT = -2147483647;
+    static final float NC_FILL_FLOAT = 9.9692099683868690e+36F;
+    static final double NC_FILL_DOUBLE = 9.9692099683868690e+36;
+
+ /* End Constants */
+
+    private int
+    padsz(int xsz) {
+        int rem = xsz % X_ALIGN;
+        if(rem == 0)
+              return 0;
+        return (X_ALIGN - rem);
+    }
+
+    private int
+    rndup(int xsz) {
+        return (xsz + padsz(xsz));
+    }
+
+    private int
+    xszofV1String(String str) {
+        int xsz = X_SIZEOF_INT;
+        xsz += rndup(str.length());
+        return xsz;
+    }
+
+    private void
+    writeV1String(String str)
+                throws IOException {
+        int rndup = str.length() % X_ALIGN;
+        if(rndup != 0)
+                rndup = X_ALIGN - rndup;
+        raf.writeInt(str.length());
+        raf.writeBytes(str);
+        while(rndup != 0) {
+                raf.writeByte(0);
+                rndup--;
+        }
+
+    }
+
+    private String
+    readV1String(DataInput hgs, int size)
+                throws IOException {
+        int rndup = size % X_ALIGN;
+        if(rndup != 0)
+                rndup = X_ALIGN - rndup;
+        byte [] bytes = new byte[size];
+        hgs.readFully(bytes); // TODO: premature EOF detection provided?
+        hgs.skipBytes(rndup);
+        return new String(bytes).intern();
+    }
+
+    private String
+    readV1String(DataInput hgs)
+                throws IOException {
+        return readV1String(hgs, hgs.readInt());
+    }
+
+    private void
+    writeV1Bytes(byte [] bytes)
+                throws IOException {
+        int rndup = bytes.length % X_ALIGN;
+        if(rndup != 0)
+                rndup = X_ALIGN - rndup;
+        raf.writeInt(bytes.length);
+        raf.write(bytes);
+        while(rndup != 0) {
+                raf.writeByte(0);
+                rndup--;
+        }
+
+    }
+
+    private int
+    v1TypeEncode(Class componentType) {
+        if(componentType.isPrimitive()) {
+                if(componentType.equals(Character.TYPE))
+                        return NC_CHAR;
+                if(componentType.equals(Byte.TYPE))
+                        return NC_BYTE;
+                if(componentType.equals(Short.TYPE))
+                        return NC_SHORT;
+                if(componentType.equals(Integer.TYPE))
+                        return NC_INT;
+                if(componentType.equals(Float.TYPE))
+                        return NC_FLOAT;
+                if(componentType.equals(Double.TYPE))
+                        return NC_DOUBLE;
+        }
+        throw new IllegalArgumentException("Not a V1 type: " + componentType);
+    }
+
+    private Class
+    v1TypeDecode(int v1type) {
+        switch (v1type) {
+        case NC_CHAR:
+                return Character.TYPE;
+        case NC_BYTE:
+                return Byte.TYPE;
+        case NC_SHORT:
+                return Short.TYPE;
+        case NC_INT:
+                return Integer.TYPE;
+        case NC_FLOAT:
+                return Float.TYPE;
+        case NC_DOUBLE:
+                return Double.TYPE;
+        }
+        return null;
+    }
+
+    private int
+    xszofElement(Class componentType) {
+        if(componentType.equals(Short.TYPE))
+                return 2;
+        if(componentType.equals(Integer.TYPE))
+                return 4;
+        if(componentType.equals(Float.TYPE))
+                return 4;
+        if(componentType.equals(Double.TYPE))
+                return 8;
+        return 1;
+    }
+
+    /*
+     * Used to calculate V1Io.vsize and this.recsize
+     */
+    private int
+    initVsize(DimensionIterator ee, int xsz) {
+        int size = 1;
+        while ( ee.hasNext() ) {
+                final Dimension dim = ee.next();
+                if(dim instanceof UnlimitedDimension) {
+                        continue;
+                }
+                // else
+                size *= dim.getLength();
+        }
+
+        size *= xsz;
+        return size; // N.B. not rounded up
+   }
+
+        private void
+        writeV1(Dimension dim)
+                        throws IOException {
+                writeV1String(dim.getName());
+                if(dim instanceof UnlimitedDimension)
+                        raf.writeInt(0);
+                else
+                        raf.writeInt(dim.getLength());
+        }
+
+        private int
+        xszof(Dimension dim) {
+                int xsz = xszofV1String(dim.getName());
+                xsz += X_SIZEOF_INT;
+                return xsz;
+        }
+
+        private void
+        writeV1(Attribute attr) throws IOException {
+                writeV1String(attr.getName());
+                if(attr.isString())
+                {
+                        raf.writeInt(NC_CHAR); // type
+                        writeV1String((String)attr.getValue());
+                        return;
+                }
+                // else
+                final int v1type = v1TypeEncode(attr.getComponentType());
+                raf.writeInt(v1type); // type
+                if(v1type == NC_CHAR)
+                {
+                        writeV1String((String)attr.getValue());
+                        return;
+                }
+                if(v1type == NC_BYTE)
+                {
+                        writeV1Bytes((byte [])attr.getValue());
+                        return;
+                }
+                // else
+                final int length = Array.getLength(attr.getValue());
+                raf.writeInt(length); // nelems
+                // TODO: invert so loop is inside switch?
+                for(int ii = 0; ii < length; ii++) {
+                        switch (v1type) {
+                        case NC_SHORT:
+                                raf.writeShort(((short[])attr.getValue())[ii]);
+                                if(length % 2 != 0)
+                                        raf.writeShort(0); // pad to X_ALIGN
+                                break;
+                        case NC_INT:
+                                raf.writeInt(((int[])attr.getValue())[ii]);
+                                break;
+                        case NC_FLOAT:
+                                raf.writeFloat(((float[])attr.getValue())[ii]);
+                                break;
+                        case NC_DOUBLE:
+                                raf.writeDouble(((double[])attr.getValue())[ii]);
+                                break;
+                        }
+                }
+        }
+
+        private int
+        xszof(Attribute attr) {
+                int xsz = xszofV1String(attr.getName());
+                xsz += X_SIZEOF_INT; // tag
+                if(attr.isString()) {
+                        return xsz + xszofV1String((String)attr.getValue());
+                }
+                // else
+                xsz += X_SIZEOF_INT; // nelems
+                final int v1type = v1TypeEncode(attr.getComponentType());
+                final int length = Array.getLength(attr.getValue());
+                switch (v1type) {
+                case NC_BYTE:
+                case NC_CHAR:
+                        xsz += rndup(length);
+                        break;
+                case NC_SHORT:
+                        xsz += rndup(length * X_SIZEOF_SHORT);
+                        break;
+                case NC_INT:
+                        xsz += length * X_SIZEOF_INT;
+                        break;
+                case NC_FLOAT:
+                        xsz += length * X_SIZEOF_FLOAT;
+                        break;
+                case NC_DOUBLE:
+                        xsz += length * X_SIZEOF_DOUBLE;
+                        break;
+                }
+                return xsz;
+        }
+
+
+        private Dimension []
+        readV1DimensionArray(DataInput hgs)
+                        throws IOException {
+                final int numrecs = hgs.readInt();
+                final int tag = hgs.readInt();
+                if(tag != NC_DIMENSION && tag != 0)
+                        throw new IllegalArgumentException(
+                                "Not a netcdf file (dimensions)");
+                final int ndims = hgs.readInt();
+                Dimension [] dimArray = new Dimension[ndims];
+                for(int ii = 0; ii < ndims; ii++) {
+                        final String name = readV1String(hgs);
+                        final int length = hgs.readInt();
+                        if(length == 0) {
+                                if(this.recDim != null)
+                                        throw new IllegalArgumentException(
+                                                "Multiple UnlimitedDimensions");
+                                this.recDim =
+                                        new UnlimitedDimension(name, numrecs);
+                                dimArray[ii] = this.recDim;
+                        }
+                        else
+                                dimArray[ii] = new Dimension(name, length);
+                }
+                return dimArray;
+        }
+
+        private void
+        writeV1(DimensionSet ds)
+                throws IOException
+        {
+                writeV1numrecs();
+                final int size = ds.size();
+                if(size != 0)
+                        raf.writeInt(NC_DIMENSION);
+                else
+                        raf.writeInt(0); // bit for bit backward compat.
+                raf.writeInt(size);
+                final DimensionIterator ee = ds.iterator();
+                while(ee.hasNext()) {
+                        writeV1(ee.next());
+                }
+        }
+
+        private int
+        xszof(DimensionSet ds) {
+                int xsz = X_SIZEOF_INT; // numrecs
+                xsz += X_SIZEOF_INT; // tag
+                xsz += X_SIZEOF_INT; // nelems
+                final DimensionIterator ee = ds.iterator();
+                while(ee.hasNext()) {
+                        xsz += xszof(ee.next());
+                }
+                return xsz;
+        }
+
+        private void
+        writeV1numrecs()
+                throws IOException
+        {
+                if(recDim == null)
+                        raf.writeInt(0);
+                else
+                        raf.writeInt(recDim.getLength());
+        }
+
+        private void
+        writeV1(AttributeSet as)
+                throws IOException
+        {
+                final int size = as.size();
+                if(size != 0)
+                        raf.writeInt(NC_ATTRIBUTE);
+                else
+                        raf.writeInt(0); // bit for bit backward compat.
+                raf.writeInt(size);
+                final AttributeIterator ee = as.iterator();
+                while (ee.hasNext()) {
+                                writeV1(ee.next());
+                }
+        }
+
+        private Object
+        readV1AttrVal(DataInput hgs) throws IOException {
+                final int v1type = hgs.readInt();
+                final int nelems = hgs.readInt();
+
+                switch (v1type) {
+                case NC_CHAR:
+                {
+                        int rndup = nelems % X_ALIGN;
+                        if(rndup != 0)
+                                rndup = X_ALIGN - rndup;
+                        char [] values = new char[nelems];
+                        for(int ii = 0; ii < nelems; ii++)
+                                values[ii] = (char) hgs.readUnsignedByte();
+                        hgs.skipBytes(rndup);
+                        return values;
+                }
+                case NC_BYTE:
+                {
+                        int rndup = nelems % X_ALIGN;
+                        if(rndup != 0)
+                                rndup = X_ALIGN - rndup;
+                        byte [] values = new byte[nelems];
+                        hgs.readFully(values); // TODO: premature EOF detection?
+                        hgs.skipBytes(rndup);
+                        return values;
+                }
+                case NC_SHORT:
+                {
+                        short [] values = new short[nelems];
+                        for(int ii = 0; ii < nelems; ii++)
+                                values[ii] = hgs.readShort();
+                        if(nelems % 2 != 0)
+                                hgs.skipBytes(2); // pad to X_ALIGN
+                        return values;
+                }
+                case NC_INT:
+                {
+                        int [] values = new int[nelems];
+                        for(int ii = 0; ii < nelems; ii++)
+                                values[ii] = hgs.readInt();
+                        return values;
+                }
+                case NC_FLOAT:
+                {
+                        float [] values = new float[nelems];
+                        for(int ii = 0; ii < nelems; ii++)
+                                values[ii] = hgs.readFloat();
+                        return values;
+                }
+                case NC_DOUBLE:
+                {
+                        double [] values = new double[nelems];
+                        for(int ii = 0; ii < nelems; ii++)
+                                values[ii] = hgs.readDouble();
+                        return values;
+                }
+                } // end switch
+                /*NOTREACHED*/
+                return null;
+        }
+
+        private Attribute []
+        readV1AttributeArray(DataInput hgs)
+                        throws IOException {
+                final int tag = hgs.readInt();
+                if(tag != NC_ATTRIBUTE && tag != 0)
+                        throw new IllegalArgumentException(
+                                "Not a netcdf file (attributes)");
+                final int nelems = hgs.readInt();
+                Attribute [] attrArray = new Attribute[nelems];
+                for(int ii = 0; ii < nelems; ii++) {
+                        final String name = readV1String(hgs);
+                        final Object value = readV1AttrVal(hgs);
+                        attrArray[ii] = new Attribute(name, value);
+                }
+                return attrArray;
+        }
+
+        private int
+        xszof(AttributeSet as) {
+                int xsz = X_SIZEOF_INT; // tag
+                xsz += X_SIZEOF_INT; // nelems
+                final AttributeIterator ee = as.iterator();
+                while (ee.hasNext()) {
+                        xsz += xszof(ee.next());
+                }
+                return xsz;
+        }
+
+    abstract class
+    V1Io extends AbstractAccessor {
+
+        /*
+         * This form of constructor used when creating.
+         */
+        protected
+        V1Io(ProtoVariable proto) {
+                meta = proto;
+                lengths = proto.getLengths();
+                initFillValue(proto);
+                // TODO
+                this.vsize = rndup(initVsize(proto.getDimensionIterator(),
+                                xszofElement(proto.getComponentType())));
+                this.begin = 0;
+                isUnlimited = proto.isUnlimited();
+                this.dsizes = compileDsizes(proto.getLengths());
+                this.xsz = xszofElement(proto.getComponentType());
+        }
+
+        abstract /* protected */ void
+        readArray(long offset, Object dst, int dst_position, int nelems)
+                        throws IOException;
+
+        private final int
+        iocount(int [] origin, int [] shape)
+        {
+                int product = 1;
+                int minIndex = 0;
+                if(isUnlimited)
+                        minIndex = 1;
+                for(int ii = shape.length -1; ii >= minIndex; ii--)
+                {
+                        final int si = shape[ii];
+                        product *= si;
+                        if(origin[ii] != 0 || si < lengths[ii] )
+                                break;
+                }
+                return product;
+        }
+
+        public MultiArray
+        copyout(int [] origin, int [] shape)
+                        throws IOException {
+                final int [] dimensions = (int []) shape.clone();
+                final int [] products = new int[dimensions.length];
+                final int product = MultiArrayImpl.numberOfElements(dimensions,           products);
+                final Object storage = Array.newInstance (
+                        meta.getComponentType(),
+                        product);
+                final int contig = iocount(origin, shape);
+
+                // convert dimensions to limits
+                final int [] limits = (int []) dimensions.clone();
+                for(int ii = 0; ii < limits.length; ii++)
+                        limits[ii] += origin[ii];
+
+                final OffsetIndexIterator odo = new OffsetIndexIterator(origin, limits);
+
+
+		int cnt = 0;
+
+
+                for(int begin = 0; 
+		    odo.notDone (); 
+		    odo.advance (contig), begin += contig)
+                {
+                        final long offset = computeOffset (odo.value());
+                        readArray(offset, storage, begin, contig);
+			cnt++;
+                }
+
+		MultiArray result = new MultiArrayImpl(dimensions, products,  storage);
+
+
+                return result;
+        }
+
+        /*
+         * @param data Array of byte which can be
+	 *     	 *     	 *     	 *     	 *     	 *     	 *     	 *	contiguously written.
+         */
+        abstract void
+        writeArray(long offset, Object from, int begin, int nelems)
+                throws IOException;
+
+        public void
+        copyin(int [] origin, MultiArray data)
+                throws IOException
+        {
+                /*
+                 * The switch on subclass here is justified
+                 * to make the specialized optimization available here
+                 * without adding specializations to Accessor,
+                 * AbstractAccessor, and Variable.
+                 */
+                if(data instanceof MultiArrayImpl)
+                {
+                        this.copyin(origin, (MultiArrayImpl) data);
+                }
+                else
+                {
+                        // TODO checkfill
+                        if(isUnlimited)
+                                checkfill(origin[0] + (data.getLengths())[0]);
+                        super.copyin(origin, data); // AbstractAccessor.
+                }
+        }
+
+        public void
+        copyin(int [] origin, MultiArrayImpl data)
+                throws IOException
+        {
+                final int [] dimensions = data.getLengths();
+                final int contig = iocount(origin, dimensions);
+                // convert dimensions to limits
+                for(int ii = 0; ii < dimensions.length; ii++)
+                        dimensions[ii] += origin[ii];
+                if(isUnlimited)
+                        checkfill(dimensions[0]);
+                final Object storage = data.storage;
+                final OffsetIndexIterator odo = new OffsetIndexIterator(origin,
+                        dimensions);
+                for(int begin = 0; odo.notDone(); odo.advance(contig),
+                         begin += contig)
+                {
+                        final long offset = computeOffset(odo.value());
+                        writeArray(offset, storage, begin, contig);
+
+                }
+        }
+
+        public Object
+        toArray()
+                throws IOException
+        {
+                return this.toArray(null, null, null);
+        }
+
+        public Object
+        toArray(Object dst, int [] origin, int [] shape)
+                throws IOException
+        {
+                final int rank = getRank();
+                if(origin == null)
+                        origin = new int[rank];
+                else if(origin.length != rank)
+                        throw new IllegalArgumentException("Rank Mismatch");
+
+                int [] shp = null;
+                if(shape == null)
+                        shp = (int []) lengths.clone();
+                else if(shape.length == rank)
+                        shp = (int []) shape.clone();
+                else
+                        throw new IllegalArgumentException("Rank Mismatch");
+
+                final int product = MultiArrayImpl.numberOfElements(shp);
+                dst = MultiArrayImpl.fixDest(dst, product,
+                        meta.getComponentType());
+                final int contig = iocount(origin, shp);
+
+                // convert dimensions to limits
+                final int [] limits = (int []) shp.clone();
+                for(int ii = 0; ii < limits.length; ii++)
+                        limits[ii] += origin[ii];
+
+                final OffsetIndexIterator odo = new OffsetIndexIterator(origin,
+                        limits);
+                for(int begin = 0; odo.notDone(); odo.advance(contig),
+                         begin += contig)
+                {
+                        final long offset = computeOffset(odo.value());
+                        readArray(offset, dst, begin, contig);
+
+                }
+
+                return dst;
+        }
+
+  /**/
+        private final int []
+        compileDsizes(int [] shape)
+        {
+                final int [] ds = new int [shape.length];
+                int product = 1;
+                for(int ii = shape.length - 1; ii >= 0; ii--)
+                {
+                        if(!(ii == 0 && isUnlimited))
+                                product *= shape[ii];
+                        ds[ii] = product;
+                }
+                return ds;
+        }
+
+        public final void
+        checkfill(int newLength)
+                        throws IOException {
+                synchronized(recDim) {
+                        int length = recDim.getLength();
+                        if(newLength > length)
+                        {
+                                if(doFill)
+                                {
+                                        for(; length < newLength; length++)
+                                        {
+                                                fillRec(length);
+                                        }
+                                }
+                                recDim.setLength(newLength);
+                                // TODO? allow caching? (NC_NSYNC)
+                                raf.seek(((long)4)); // NC_NUMRECS_OFFSET
+                                // writeV1numrecs();
+                                raf.writeInt(recDim.getLength());
+                        }
+                }
+
+        }
+
+        /* TODO */
+        final int getRank() { return dsizes.length; }
+        final boolean isScalar() { return 0 == getRank(); }
+
+        final long
+        computeOffset(int [] origin) {
+                if(isScalar())
+                        return begin;
+                // else
+                if(getRank() == 1) {
+                        if(isUnlimited) {
+                                return (begin + origin[0] * recsize);
+                        }
+                        // else
+                        return (begin + origin[0] * this.xsz);
+                }
+                // else
+                final int end = dsizes.length -1;
+                int lcoord = origin[end];
+                int index  = 0;
+                if(isUnlimited) {
+                        index++;
+                }
+                for(; index < end ; index++) {
+                        lcoord += dsizes[index +1] * origin[index];
+                }
+                lcoord *= this.xsz;
+                if(isUnlimited)
+                        lcoord += origin[0] * recsize;
+                lcoord += begin;
+                return lcoord;
+        }
+
+        abstract void
+        fill(DataOutput dos, int nbytes, Attribute fAttr)
+                        throws IOException;
+
+        private final void
+        initFillValue(Attribute fAttr) {
+                final int nbytes = 32; // tune here
+                ByteArrayOutputStream bos = new ByteArrayOutputStream(nbytes);
+                DataOutputStream dos = new DataOutputStream(bos);
+                try {
+                        this.fill(dos, nbytes, fAttr);
+                        dos.flush();
+                } catch (IOException ioe) {
+                        // assert(cant happen);
+                }
+                fillbytes = bos.toByteArray();
+
+        }
+
+        private final void
+        initFillValue(ProtoVariable proto)
+        {
+                initFillValue(proto.getAttribute(_FillValue));
+        }
+
+        private final void
+        initFillValue(Attribute [] attrArray)
+        {
+                Attribute fAttr = null;
+                for(int ii = 0; ii < attrArray.length; ii++)
+                {
+                        if(attrArray[ii].getName() == _FillValue)
+                                fAttr = attrArray[ii];
+                }
+                initFillValue(fAttr);
+        }
+
+        void
+        fillO(long offset)
+                throws IOException {
+                raf.seek(offset);
+                int remainder = vsize;
+                for(; remainder >= fillbytes.length;
+                                remainder -= fillbytes.length)
+                        raf.write(fillbytes);
+                // handle any remainder;
+                if(remainder > 0)
+                        for(int ii = 0; ii < remainder; ii++)
+                                raf.write(fillbytes[ii]);
+        }
+
+        void
+        fill(int recno)
+                throws IOException {
+                long offset = begin;
+                if(isUnlimited)
+                        offset +=  (long)recno * recsize;
+                this.fillO(offset);
+        }
+
+        private final ProtoVariable meta; // sibling within the Variable.
+        private final int [] lengths; // cache of meta.getLengths()
+        byte [] fillbytes;
+        int vsize;
+        int begin;
+        final boolean isUnlimited; // TODO factor this!!
+        final int [] dsizes;
+        int xsz; // TODO: Is this member needed?
+    }
+
+ /* Begin IWISHWEHADTEMPLATES or a macro preprocessor */
+
+    private final class
+    V1ByteIo extends V1Io {
+
+        V1ByteIo(ProtoVariable var) {
+                super(var);
+        }
+
+        void
+        readArray(long offset, Object into, int begin, int nelems)
+                        throws IOException {
+                final byte [] values = (byte []) into;
+                raf.seek(offset);
+                raf.read(values, begin, nelems);
+        }
+
+        public byte
+        getByte(int [] index)
+                throws IOException
+        {
+                raf.seek(computeOffset(index));
+                return raf.readByte();
+        }
+
+        public Object
+        get(int [] index)
+                throws IOException
+        {
+                return new Byte(this.getByte(index));
+        }
+
+        void
+        writeArray(long offset, Object from, int begin, int nelems)
+                        throws IOException {
+                byte [] values = (byte []) from;
+                raf.seek(offset);
+                raf.write(values, begin, nelems);
+        }
+
+        public void
+        setByte(int [] index, byte value)
+                throws IOException
+        {
+                if(isUnlimited)
+                        checkfill(index[0] +1);
+                raf.seek(computeOffset(index));
+                raf.writeByte(value);
+        }
+
+        public void
+        set(int [] index, Object value)
+                        throws IOException
+        {
+                this.setByte(index, ((Number)value).byteValue());
+        }
+
+        final void
+        fill(DataOutput dos, int nbytes, Attribute fAttr)
+                        throws IOException {
+                byte fv = NC_FILL_BYTE;
+                if(fAttr != null)
+                        fv = fAttr.getNumericValue().byteValue();
+                for(int ii = 0; ii < nbytes; ii++)
+                                dos.write(fv);
+        }
+    }
+
+    private final class
+    V1CharacterIo extends V1Io {
+
+        V1CharacterIo(ProtoVariable var) {
+                super(var);
+        }
+
+        void
+        readArray(long offset, Object into, int begin, int nelems)
+                        throws IOException {
+                final char [] values = (char []) into;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        values[ii] = (char) raf.readUnsignedByte();
+                }
+        }
+
+        public char
+        getChar(int [] index)
+                throws IOException
+        {
+                raf.seek(computeOffset(index));
+                return (char) raf.readUnsignedByte();
+        }
+
+        public Object
+        get(int [] index)
+                throws IOException
+        {
+                return new Character(this.getChar(index));
+        }
+
+        void
+        writeArray(long offset, Object from, int begin, int nelems)
+                        throws IOException {
+                final char [] values = (char []) from;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        raf.writeByte((byte) values[ii]);
+                }
+        }
+
+        public void
+        setChar(int [] index, char value)
+                throws IOException
+        {
+                if(isUnlimited)
+                        checkfill(index[0] +1);
+                raf.seek(computeOffset(index));
+                raf.writeByte((byte) value);
+        }
+
+        public void
+        set(int [] index, Object value)
+                        throws IOException
+        {
+                this.setChar(index, ((Character)value).charValue());
+        }
+
+        final void
+        fill(DataOutput dos, int nbytes, Attribute fAttr)
+                        throws IOException {
+                byte fv = NC_FILL_CHAR;
+                if(fAttr != null)
+                        fv = fAttr.getLength() == 0
+                            ? 0  // because Atribute strips trailing NUL
+                            : fAttr.getNumericValue().byteValue();
+                for(int ii = 0; ii < nbytes; ii++)
+                                dos.write(fv);
+        }
+    }
+
+    private final class
+    V1ShortIo extends V1Io {
+
+        V1ShortIo(ProtoVariable var) {
+                super(var);
+        }
+
+        void
+        readArray(long offset, Object into, int begin, int nelems)
+                        throws IOException {
+                final short [] values = (short []) into;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        values[ii] = raf.readShort();
+                }
+        }
+
+        public short
+        getShort(int [] index)
+                throws IOException
+        {
+                raf.seek(computeOffset(index));
+                return raf.readShort();
+        }
+
+        public Object
+        get(int [] index)
+                throws IOException
+        {
+                return new Short(this.getShort(index));
+        }
+
+        void
+        writeArray(long offset, Object from, int begin, int nelems)
+                        throws IOException {
+                final short [] values = (short []) from;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        raf.writeShort(values[ii]);
+                }
+        }
+
+        public void
+        setShort(int [] index, short value)
+                throws IOException
+        {
+                if(isUnlimited)
+                        checkfill(index[0] +1);
+                raf.seek(computeOffset(index));
+                raf.writeShort(value);
+        }
+
+        public void
+        set(int [] index, Object value)
+                        throws IOException
+        {
+                this.setShort(index, ((Number)value).shortValue());
+        }
+
+        final void
+        fill(DataOutput dos, int nbytes, Attribute fAttr)
+                        throws IOException {
+                short fv = NC_FILL_SHORT;
+                if(fAttr != null)
+                        fv = fAttr.getNumericValue().shortValue();
+                for(int ii = 0; ii < nbytes; ii++)
+                                dos.writeShort(fv);
+        }
+    }
+
+    private final class
+    V1IntegerIo extends V1Io {
+
+        V1IntegerIo(ProtoVariable var) {
+                super(var);
+        }
+
+        void
+        readArray(long offset, Object into, int begin, int nelems)
+                        throws IOException {
+                final int [] values = (int []) into;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        values[ii] = raf.readInt();
+                }
+        }
+
+        public int
+        getInt(int [] index)
+                throws IOException
+        {
+                raf.seek(computeOffset(index));
+                return raf.readInt();
+        }
+
+        public Object
+        get(int [] index)
+                throws IOException
+        {
+                return new Integer(this.getInt(index));
+        }
+
+        void
+        writeArray(long offset, Object from, int begin, int nelems)
+                        throws IOException {
+                final int [] values = (int []) from;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        raf.writeInt(values[ii]);
+                }
+        }
+
+        public void
+        setInt(int [] index, int value)
+                throws IOException
+        {
+                if(isUnlimited)
+                        checkfill(index[0] +1);
+                raf.seek(computeOffset(index));
+                raf.writeInt(value);
+        }
+
+        public void
+        set(int [] index, Object value)
+                        throws IOException
+        {
+                this.setInt(index, ((Number)value).intValue());
+        }
+
+        final void
+        fill(DataOutput dos, int nbytes, Attribute fAttr)
+                        throws IOException {
+                int fv = NC_FILL_INT;
+                if(fAttr != null)
+                        fv = fAttr.getNumericValue().intValue();
+                for(int ii = 0; ii < nbytes; ii++)
+                                dos.writeInt(fv);
+        }
+    }
+
+    private final class
+    V1FloatIo extends V1Io {
+
+        V1FloatIo(ProtoVariable var) {
+                super(var);
+        }
+
+        void  readArray(long offset, Object into, int begin, int nelems)
+	    throws IOException {
+	    //	    if (begin+nelems>100)
+	    float [] values = (float []) into;
+	    raf.seek (offset);
+	    final int end = begin + nelems;
+	    for(int ii = begin; ii < end; ii++) {
+		values[ii] = raf.readFloat();
+	    }
+
+        }
+
+        public float
+        getFloat(int [] index)
+                throws IOException
+        {
+                raf.seek(computeOffset(index));
+                return raf.readFloat();
+        }
+
+        public Object
+        get(int [] index)
+                throws IOException
+        {
+                return new Float(this.getFloat(index));
+        }
+
+        void
+        writeArray(long offset, Object from, int begin, int nelems)
+                        throws IOException {
+                final float [] values = (float []) from;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        raf.writeFloat(values[ii]);
+                }
+        }
+
+        public void
+        setFloat(int [] index, float value)
+                throws IOException
+        {
+                if(isUnlimited)
+                        checkfill(index[0] +1);
+                raf.seek(computeOffset(index));
+                raf.writeFloat(value);
+        }
+
+        public void
+        set(int [] index, Object value)
+                        throws IOException
+        {
+                this.setFloat(index, ((Number)value).floatValue());
+        }
+
+        final void
+        fill(DataOutput dos, int nbytes, Attribute fAttr)
+                        throws IOException {
+                float fv = NC_FILL_FLOAT;
+                if(fAttr != null)
+                        fv = fAttr.getNumericValue().floatValue();
+                for(int ii = 0; ii < nbytes; ii++)
+                                dos.writeFloat(fv);
+        }
+    }
+
+    private final class
+    V1DoubleIo extends V1Io {
+
+        V1DoubleIo(ProtoVariable var) {
+                super(var);
+        }
+
+        void
+        readArray(long offset, Object into, int begin, int nelems)
+                        throws IOException {
+                final double [] values = (double []) into;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        values[ii] = raf.readDouble();
+                }
+        }
+
+        public double
+        getDouble(int [] index)
+                throws IOException
+        {
+                raf.seek(computeOffset(index));
+                return raf.readDouble();
+        }
+
+        public Object
+        get(int [] index)
+                throws IOException
+        {
+                return new Double(this.getDouble(index));
+        }
+
+        void
+        writeArray(long offset, Object from, int begin, int nelems)
+                        throws IOException {
+                final double [] values = (double []) from;
+                raf.seek(offset);
+                final int end = begin + nelems;
+                for(int ii = begin; ii < end; ii++) {
+                        raf.writeDouble(values[ii]);
+                }
+        }
+
+        public void
+        setDouble(int [] index, double value)
+                throws IOException
+        {
+                if(isUnlimited)
+                        checkfill(index[0] +1);
+                raf.seek(computeOffset(index));
+                raf.writeDouble(value);
+        }
+
+        public void
+        set(int [] index, Object value)
+                        throws IOException
+        {
+                this.setDouble(index, ((Number)value).doubleValue());
+        }
+
+        final void
+        fill(DataOutput dos, int nbytes, Attribute fAttr)
+                        throws IOException {
+                double fv = NC_FILL_DOUBLE;
+                if(fAttr != null)
+                        fv = fAttr.getNumericValue().doubleValue();
+                for(int ii = 0; ii < nbytes; ii++)
+                                dos.writeDouble(fv);
+        }
+    }
+
+ /* End IWISHWEHADTEMPLATES or a macro preprocessor */
+
+
+        private V1Io
+        V1IoFactory(ProtoVariable proto)
+        {
+                final Class componentType = proto.getComponentType();
+                V1Io io = null;
+
+                if(componentType.equals(Character.TYPE)) {
+                        io =  new V1CharacterIo(proto);
+                }
+                else if (componentType.equals(Byte.TYPE)) {
+                        io =  new V1ByteIo(proto);
+                }
+                else if (componentType.equals(Short.TYPE)) {
+                        io =  new V1ShortIo(proto);
+                }
+                else if (componentType.equals(Integer.TYPE)) {
+                        io =  new V1IntegerIo(proto);
+                }
+                else if (componentType.equals(Float.TYPE)) {
+                        io =  new V1FloatIo(proto);
+                }
+                else if (componentType.equals(Double.TYPE)) {
+                        io =  new V1DoubleIo(proto);
+                }
+
+                return io;
+        }
+
+        protected Accessor
+        ioFactory(ProtoVariable proto)
+                { return V1IoFactory(proto); }
+
+        private void
+        writeV1(Variable var)
+                throws IOException
+        {
+                writeV1String(var.getName());
+                raf.writeInt(var.getRank());
+
+                DimensionIterator ee = var.getDimensionIterator();
+                while(ee.hasNext())
+                {
+                        raf.writeInt(indexOf(ee.next()));
+                }
+
+                writeV1(var.getAttributes());
+                raf.writeInt(v1TypeEncode(var.getComponentType()));
+                final V1Io io = (V1Io) var.io;
+                raf.writeInt(io.vsize);
+                raf.writeInt(io.begin);
+        }
+
+        private int
+        xszof(Variable var) {
+                int xsz = xszofV1String(var.getName());
+                xsz += X_SIZEOF_INT; // dimArray.length
+                xsz += var.getRank() * X_SIZEOF_INT; // dim indexes
+                xsz += xszof(var.getAttributes());
+                xsz += X_SIZEOF_INT; // tag
+                xsz += X_SIZEOF_INT; // vsize
+                xsz += X_SIZEOF_INT; // begin
+                return xsz;
+        }
+
+        private void
+        readV1VarArray(DataInput hgs, Dimension [] allDims)
+                        throws IOException {
+                int tag = hgs.readInt();
+                if(tag != NC_VARIABLE && tag != 0)
+                        throw new IllegalArgumentException(
+                                "Not a netcdf file (variables)");
+                int nelems = hgs.readInt();
+                for(int ii = 0; ii < nelems; ii++) {
+                        final String name = readV1String(hgs);
+                        final int ndims = hgs.readInt();
+                        final Dimension [] dimArray = new Dimension[ndims];
+                        for(int jj = 0; jj < ndims; jj++)
+                                dimArray[jj] = allDims[hgs.readInt()];
+                        final Attribute [] attrArray =
+                                readV1AttributeArray(hgs);
+                        final Class type =  v1TypeDecode(hgs.readInt());
+                        final ProtoVariable proto = new ProtoVariable(
+                                name, type, dimArray, attrArray
+                                );
+
+                        final V1Io io = V1IoFactory(proto);
+                        io.vsize =  hgs.readInt();
+                        io.begin =  hgs.readInt();
+
+                        try {
+                                add(proto, io);
+                        }
+                        catch (InstantiationException ie)
+                        {
+                                // Can't happen: Variable is concrete
+                                throw new Error();
+                        }
+                        catch (IllegalAccessException iae)
+                        {
+                                // Can't happen: Variable is accessable
+                                throw new Error();
+                        }
+                        catch (InvocationTargetException ite)
+                        {
+                                // all the possible target exceptions are
+                                // RuntimeException
+                                throw (RuntimeException)
+                                        ite.getTargetException();
+                        }
+                }
+        }
+
+        private void
+        writeV1(int size, VariableIterator ee)
+                throws IOException
+        {
+                if(size != 0)
+                        raf.writeInt(NC_VARIABLE);
+                else
+                        raf.writeInt(0); // bit for bit backward compat.
+                raf.writeInt(size);
+                while( ee.hasNext()) {
+                        writeV1(ee.next());
+                }
+        }
+
+        private int
+        xszof(VariableIterator ee) {
+                int xsz = X_SIZEOF_INT; // tag
+                xsz += X_SIZEOF_INT; // nelems
+                while(ee.hasNext()) {
+                        xsz += xszof(ee.next());
+                }
+                return xsz;
+        }
+
+    private void
+    writeV1()
+                throws IOException
+    {
+        raf.writeInt(v1magic);
+        writeV1(getDimensions());
+        writeV1(getAttributes());
+        writeV1(size(), iterator());
+    }
+
+    private int
+    xszof() {
+        int xsz = X_SIZEOF_INT; // magic number
+        xsz += xszof(getDimensions());
+        xsz += xszof(getAttributes());
+        xsz += xszof(iterator());
+        return xsz;
+    }
+
+    private void
+    readV1(DataInput hgs)
+                throws IOException {
+
+        final int magic = hgs.readInt();
+        if(magic != v1magic)
+                throw new IllegalArgumentException("Not a netcdf file");
+
+        final Dimension [] dimArray = readV1DimensionArray(hgs);
+        for(int ii = 0; ii < dimArray.length; ii++)
+                putDimension(dimArray[ii]);
+
+        {
+                final Attribute [] gAttrArray =
+                        readV1AttributeArray(hgs);
+                for(int ii = 0; ii < gAttrArray.length; ii++)
+                        putAttribute(gAttrArray[ii]);
+        }
+
+        readV1VarArray(hgs, dimArray);
+    }
+
+    /*
+     * In the C interface this is called NC_begins();
+     */
+    private void
+    compileBegins() {
+
+        int index = xszof();
+        /* loop thru vars, first pass is for the 'non-record' vars */
+        {
+        final VariableIterator ee = iterator();
+        while(ee.hasNext())
+        {
+                final Variable var = ee.next();
+                if(var.isUnlimited())
+                        continue;
+                // else
+                final V1Io io = (V1Io) var.io;
+                io.begin = index;
+                index += io.vsize;
+        }
+        }
+
+        {
+        /* loop thru vars, 2nd pass is for the 'record' vars */
+        final VariableIterator ee = iterator();
+        while(ee.hasNext())
+        {
+                final Variable var = ee.next();
+                if(!var.isUnlimited())
+                        continue;
+                if(recDim == null)
+                {
+                        final Dimension dim0 =
+                                 var.getDimensionIterator().next();
+                        if(!(dim0 instanceof UnlimitedDimension))
+                                throw new IllegalArgumentException(
+                                        "Unlimited Dim not leftmost"
+                                );
+                        recDim = (UnlimitedDimension)dim0;
+                }
+                final V1Io io = (V1Io) var.io;
+                io.begin = index;
+                index += io.vsize;
+        }
+        }
+    }
+
+    private void
+    initRecSize() {
+        recsize = 0;
+        /* loop thru vars, 2nd pass is for the 'record' vars */
+        final VariableIterator ee = iterator();
+        while(ee.hasNext())
+        {
+                final Variable var = ee.next();
+                if(!var.isUnlimited())
+                        continue;
+                final V1Io io = (V1Io) var.io;
+                if(recsize == 0 && !ee.hasNext())
+                {
+                        // special case exactly one record variable
+                        // pack value
+                        recsize = initVsize(var.getDimensionIterator(),
+                                io.xsz);
+                        break;
+                }
+                // else
+                recsize += io.vsize;
+        }
+    }
+
+    // can't be private and still visible in inner class V1Var?
+    void
+    fillRec(int recno) throws IOException {
+        // synchronized in caller
+        // "only call when doFill set" checked in caller
+        final VariableIterator ee = iterator();
+        while(ee.hasNext())
+        {
+                final Variable var = ee.next();
+                if(!var.isUnlimited())
+                        continue;
+                // else
+                // var.fill(recno);
+                final V1Io io = (V1Io) var.io;
+                final long offset = (long)io.begin + (long)recno * recsize;
+                io.fillO(offset);
+        }
+    }
+
+    private void
+    fillerup()
+                 throws IOException {
+        if(!this.doFill)
+                return;
+        final VariableIterator ee = iterator();
+        while(ee.hasNext())
+        {
+                final Variable var = ee.next();
+                if(var.isUnlimited())
+                        continue;
+                // else
+                final V1Io io = (V1Io) var.io;
+                io.fillO((long)io.begin);
+        }
+        if(this.recDim != null) {
+            final int nrecs = recDim.getLength();
+            for(int recno = 0; recno < nrecs; recno++)
+                fillRec(recno);
+        }
+    }
+
+    /**
+     * Ensures that the close method of this file is called when
+     * there are no more
+     * references to it.
+     * @exception Throwable The lack of covariance for exception specifications
+     * dictates the specificed type;
+     * it can actually only be <code>IOException</code> thrown
+     * by <code>RandomAccessFile.close</code>.
+     * @see NetcdfFile#close
+     */
+    protected void
+    finalize() throws Throwable
+    {
+        super.finalize();
+        close();
+    }
+
+    private URL url;	// not "final" to accomodate JDK 1.2
+    private File file;	// not "final" to accomodate JDK 1.2
+    private RandomAccessFile raf; // unidata.netcdf version, not java.io
+    private UnlimitedDimension recDim;
+    private int recsize;
+    private boolean doFill; // set to false to suppress data prefill.
+
+}
+
+/* Change History:
+   $Log: not supported by cvs2svn $
+   Revision 1.15  2003/03/04 22:26:32  jeffmc
+   Add a new ctor that takes buffer size and cleanup some old debug in netcdffile
+
+   Revision 1.14  2003/03/04 22:23:22  jeffmc
+   Create the httprandomaccess file with a largish buffer size
+
+   Revision 1.13  2003/01/21 21:24:05  jeffmc
+   Add a getStorage method that returns the raw multiarray object
+
+   Revision 1.12  2002/07/15 21:39:17  steve
+   Changed use of _FillValue attribute.  If zero-length, then use byte-value 0.
+
+   Revision 1.11  2002/05/24 00:06:06  caron
+   add flush()
+
+   Revision 1.10  2001/09/14 21:29:28  caron
+   minor doc improvements
+
+   Revision 1.9  2001/09/10 20:37:12  steve
+   Improved constructor NetcdfFile(URL):
+       Replaced URL.getPath() with URL.getFile() to accomodate JDK 1.2.
+       Made protocol comparison case-insensitive.
+       Added defensive copying of modifiable URL argument.
+       Added FileNotFoundException.
+       Added ignoring of query component of URL.
+       Enhanced JavaDoc.
+
+   Revision 1.8  2001/08/28 16:59:59  steve
+   Added support for "file" protocol to constructor NetcdfFile(URL).
+
+   Revision 1.7  2001/05/17 15:15:09  steve
+   Modified to accomodate JDK 1.2.
+
+   Revision 1.6  2001/05/01 15:06:02  caron
+   add netcdf HTTP access
+
+ */
diff --git a/ucar/netcdf/NetcdfRemoteProxy.java b/ucar/netcdf/NetcdfRemoteProxy.java
new file mode 100644
index 0000000..19a7591
--- /dev/null
+++ b/ucar/netcdf/NetcdfRemoteProxy.java
@@ -0,0 +1,72 @@
+// $Id: NetcdfRemoteProxy.java,v 1.4 2002-05-29 18:31:35 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.multiarray.RemoteAccessor;
+import java.rmi.RemoteException;
+import java.rmi.Remote;
+
+/**
+ * This interface wraps a single instance of Netcdf to
+ * provide Remote services required in the construction
+ * of an instance of RemoteNetcdf.
+ * <p>
+ * This interface is only needed by directory services like NetcdfService
+ * to bootstrap instances of RemoteNetcdf.
+ * It could be considered package or implementation private.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:35 $
+ */
+
+public interface
+NetcdfRemoteProxy
+		extends Remote
+{
+	/**
+	 * @return a Schema for the Netcdf this
+	 * represents.
+	 */
+	public Schema
+	getSchema()
+		throws RemoteException;
+
+	/**
+	 * Get an Accessor for a Variable, by name.
+	 * Given the Accessor and the ProtoVariable
+	 * obtained indirectly from getSchema() above,
+	 * RemoteNetcdf can create a remote proxy for the Variable.
+	 * @param varName String which names a Variable in the
+	 * Netcdf this represents.
+	 * @return a (Remote)Accessor for the Variable.
+	 */
+	public RemoteAccessor
+	getAccessor(String varName)
+		throws  RemoteException;
+
+	/**
+	 * Indicate that you are done with this
+	 * Netcdf data set. Allows the service to free
+	 * resources (close the data set).
+	 */
+	public void
+	release()
+		throws RemoteException;
+}
diff --git a/ucar/netcdf/NetcdfRemoteProxyImpl.java b/ucar/netcdf/NetcdfRemoteProxyImpl.java
new file mode 100644
index 0000000..b86787e
--- /dev/null
+++ b/ucar/netcdf/NetcdfRemoteProxyImpl.java
@@ -0,0 +1,141 @@
+// $Id: NetcdfRemoteProxyImpl.java,v 1.4 2002-05-29 18:31:35 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.multiarray.RemoteAccessor;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.UnicastRemoteObject;
+import java.rmi.server.Unreferenced;
+
+/**
+ * This class provides implementation of the interface
+ * NetcdfRemoteProxy. It wraps a single instance of Netcdf
+ * provide Remote services required in the construction
+ * of an instance of RemoteNetcdf.
+ *
+ * @see NetcdfRemoteProxy
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:35 $
+ */
+
+public class
+NetcdfRemoteProxyImpl
+		extends RemoteObject
+		implements NetcdfRemoteProxy, Unreferenced {
+
+        /**
+         * Construct a RemoteObject which acts as
+	 * a NetcdfRemoteProxy for a single Netcdf.
+	 * @param svr NetcdfServer which owns this.
+	 * @param key String by which svr knows us.
+	 * @param nc Netcdf  which this will represent.
+	 *
+	 */
+	public
+	NetcdfRemoteProxyImpl(NetcdfServer svr, String key, AbstractNetcdf nc)
+		throws RemoteException
+	{
+		super();
+		svr_ = svr;
+		key_ = key;
+		nc_ = nc;
+	}
+
+/* Begin NetcdfRemoteProxy */	
+
+	public Schema
+	getSchema()
+		throws RemoteException
+	{
+		return nc_.getSchema();
+	}
+
+	public RemoteAccessor
+	getAccessor(String varName)
+		throws RemoteException
+	{
+		if(svr_ != null)
+			return (RemoteAccessor) svr_.exportObject(
+				new RemoteAccessorImpl(this, nc_.get(varName)));
+		// else
+		return (RemoteAccessor) UnicastRemoteObject.exportObject(
+				new RemoteAccessorImpl(this, nc_.get(varName)));
+	}
+
+	public void
+	release()
+		throws RemoteException
+	{
+		_release();
+	}
+
+/* End NetcdfRemoteProxy */	
+/* Begin java.rmi.server.Unreferenced */	
+
+	/**
+	 * Equivalent to release(), called automatically by
+	 * the runtime system.
+	 * @see java.rmi.server.Unreferenced#unreferenced
+	 * @see NetcdfRemoteProxy#release
+	 */
+	public void
+	unreferenced()
+	{
+		if(svr_ != null && svr_.logger_ != null)
+			svr_.logger_.logDebug(this + ".unreferenced()");
+		_release();
+	}
+/* End java.rmi.server.Unreferenced */	
+
+	protected void
+	finalize()
+		throws Throwable
+	{ 
+		if(svr_ != null && svr_.logger_ != null)
+			svr_.logger_.logDebug(this + ".finalize()");
+		super.finalize();
+		_release();
+	}
+
+	protected void
+	_release()
+	{
+		if(nc_ != null)
+		{
+			svr_._release(key_);
+			nc_ = null;
+		}
+	}
+
+	/**
+	 * @serial
+	 */
+	private final NetcdfServer svr_;
+	/**
+	 * @serial
+	 */
+	private final String key_;
+	/**
+	 * @serial
+	 */
+	private /* final */ AbstractNetcdf nc_;
+}
diff --git a/ucar/netcdf/NetcdfServer.java b/ucar/netcdf/NetcdfServer.java
new file mode 100644
index 0000000..5bf8cc1
--- /dev/null
+++ b/ucar/netcdf/NetcdfServer.java
@@ -0,0 +1,378 @@
+// $Id: NetcdfServer.java,v 1.4 2002-05-29 18:31:35 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.util.Logger;
+import ucar.util.RMILogger;
+import java.io.File;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.rmi.AccessException;
+import java.rmi.ServerException;
+import java.rmi.RMISecurityManager;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+
+import java.rmi.ConnectException;
+import java.rmi.AlreadyBoundException;
+import java.rmi.NotBoundException;
+import java.rmi.registry.Registry;
+import java.rmi.registry.LocateRegistry;
+
+/**
+ * A UnicastRemoteObject implementation of NetcdfService.
+ * @note NetcdfService is a placeholder directory service
+ * for a more elaborate directory service,
+ * hopefully to be provided later on.
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:35 $
+ */
+
+public class
+NetcdfServer
+	extends UnicastRemoteObject implements NetcdfService {
+	
+	public static void
+	setLog(OutputStream out)
+	{
+		if(out != null)
+		{
+			logger_.logUpTo(Logger.DEBUG);
+			logger_.setLog(out);
+		}
+		else
+		{
+			logger_.logUpTo(Logger.NOTICE);
+			logger_.setLog(System.err);
+			UnicastRemoteObject.setLog(out);
+		}
+	}
+
+	public
+	NetcdfServer(String [] exports, Registry registry)
+		throws RemoteException, AlreadyBoundException
+	{
+		super();
+		byName_ = new Hashtable();
+		for(int ii = 0; ii < exports.length; ii++)
+			export(exports[ii]);
+		if(byName_.size() == 0)
+			throw new IllegalArgumentException("No exports");
+		if(registry != null)
+		{
+			registry_ = registry;
+			registry.bind(SVC_NAME, this);
+                        logger_.logNotice(SVC_NAME
+				+ " bound in registry");
+		}
+	}
+
+	public int
+	ping()
+		throws RemoteException
+	{
+		return 0;
+	}
+	
+	public NetcdfRemoteProxy
+	lookup(String dataSetName)
+		throws RemoteException
+	{
+		final Entry entry = get(dataSetName);
+		if(entry == null)
+			throw new AccessException(dataSetName +
+				" not available");
+		AbstractNetcdf nc = null;
+		try {
+			nc = entry.getNetcdfFile();
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("lookup", ioe);
+		}
+		return (NetcdfRemoteProxy) exportObject(
+			new NetcdfRemoteProxyImpl(this, dataSetName, nc));
+	}
+
+	public String []
+	list()
+		throws RemoteException
+	{
+		String [] ret = new String [byName_.size()];
+		Enumeration ee = byName_.keys();
+		for(int ii = 0; ee.hasMoreElements(); ii++)
+			ret[ii] = (String) ee.nextElement();
+		return ret;
+	}
+
+	public void
+	export(File ff)
+	{
+		if(!ff.isFile())
+			throw new IllegalArgumentException(ff.getPath()
+				+ " not a File");
+		Entry entry = new Entry(ff);
+		String keyval = entry.keyValue();
+		logger_.logDebug("Exporting " + ff + " as "
+			+ keyval);
+		put(keyval, entry);
+	}
+
+	public void
+	export(String path)
+	{
+		export(new File(path));
+	}
+
+	protected void
+	finalize()
+		throws Throwable
+	{
+		super.finalize();
+		if(registry_ != null)
+		{
+			try {
+				registry_.unbind(SVC_NAME);
+			} catch (Exception ee) {
+				// we tried.
+                        	logger_.logError( "unbind: " + ee.getMessage());
+			}
+		}
+		registry_ = null;
+	}
+
+	public static Registry
+	startRegistry()
+			throws RemoteException
+	{
+		logger_.logNotice("No registry, starting one");
+		return LocateRegistry.createRegistry(
+			Registry.REGISTRY_PORT);
+	}
+
+	public static Registry
+	checkRegistry(Registry regis, int tryagain)
+			throws RemoteException
+	{
+		if(regis == null)
+			regis = startRegistry(); 
+
+		NetcdfService existing = (NetcdfService) null;
+
+                try {
+			existing = (NetcdfService) regis.lookup(SVC_NAME);
+                }
+		catch (ConnectException ce) {
+			if(--tryagain > 0)
+			{
+				return checkRegistry(startRegistry(),
+					tryagain);
+			}
+			throw ce;
+		}
+		catch (NotBoundException nbe) {
+			return regis;	// Normal return
+		}
+		// else, AlreadyBound. Is it bogus?
+		try {
+			existing.ping();
+		}
+		catch (ConnectException ce) { // ?? any RemoteException
+			// bogus
+			try {
+				logger_.logNotice(
+					"unbinding dead registry entry");
+				regis.unbind(SVC_NAME);
+			} catch (NotBoundException nbe) {
+				// Race condition.
+				// Ignore here and catch it later.
+			}
+		}
+		return regis;
+	}
+
+	public static void
+	main(String args[])
+	{
+		System.setSecurityManager(new RMISecurityManager());
+		// setLog(System.err);
+		
+		Registry regis = (Registry) null;
+                try {
+			regis = checkRegistry(LocateRegistry.getRegistry(), 2);
+                }
+                catch (Exception ee) {
+			PrintStream ps = getLog();
+			if(ps == null)
+				ps = System.err;
+                        ps.println(
+				"NetcdfServer: error getting registry: "
+				 + ee.getMessage());
+                        ee.printStackTrace(ps);
+			System.exit(1);
+                }
+
+                try {
+                	NetcdfServer svc = new NetcdfServer(args, regis);
+                } catch (Throwable ee) {
+			PrintStream ps = getLog();
+			if(ps == null)
+				ps = System.err;
+                        ps.println("NetcdfServer err: "
+				 + ee.getMessage());
+                        ee.printStackTrace(ps);
+			System.exit(1);
+                }
+	}
+
+/**/
+	/* package */ synchronized void
+	_release(String keyval)
+	{
+		final Entry entry = (Entry) byName_.get(keyval);
+		if(entry != null)
+			entry.releaseNetcdfFile();
+	}
+
+	/**
+	 * Gets the Entry associated with the specified name.
+	 * @param dataSetName the name 
+	 * @return the Entry, or null if not found
+	 */
+	private Entry
+	get(String dataSetName)
+		{ return (Entry) byName_.get(dataSetName); }
+
+	/**
+	 * Puts the specified element into the Dictionary, using its
+	 * keyValue() as key. 
+	 * The element may be retrieved by doing a get() with the key value.
+	 * name.  The element cannot be null.
+	 * @param entry the new entry;
+	 * @exception NullPointerException If the value of the specified
+	 * element is null.
+	 */
+	synchronized private void
+	put(String keyval, Entry entry)
+	{
+		byName_.put(keyval, entry);
+	}
+
+	/**
+	 * @serial
+	 */
+	private Hashtable byName_;
+	/**
+	 * @serial
+	 */
+	private Registry registry_;
+	static /* package */ final RMILogger logger_ = new RMILogger();
+
+class Entry
+{
+	final File dirent;
+	NetcdfFile nc;
+	int refcount;
+	
+	Entry(File ff)
+	{
+		this.dirent = ff;
+		nc = (NetcdfFile) null;
+		refcount = 0;
+	}
+
+	String
+	keyValue()
+	{
+		// Strip leading path
+		final String name = dirent.getName();
+		// Strip extension
+		final int index = name.indexOf('.');
+		return name.substring(0, index).intern();
+	}
+
+	/**
+	 * Open entry.nc.
+	 */
+	synchronized private void
+	open(boolean readonly)
+		throws IOException
+	{
+		if(nc != null)
+			throw new IllegalArgumentException("dataSet "
+				+ keyValue() + " already open");
+		nc = new NetcdfFile(dirent, readonly);
+	}
+
+	/**
+	 * Return entry.nc, opening if necessary.
+	 * Increments reference count.
+	 */
+	synchronized NetcdfFile
+	getNetcdfFile()
+		throws IOException
+	{
+		if(nc == null)
+			open(true); // all access readonly for now
+		refcount++;
+		logger_.logDebug("refcount: " + refcount);
+		return nc;
+	}
+
+	/**
+	 * Close entry.nc and delete any references to it,
+	 * making it available for garbage collection.
+	 */
+	synchronized private void
+	close()
+	{
+		if(nc != null)
+		{
+			logger_.logDebug("closing: " +  nc.getFile());
+			try {
+				nc.close();
+			}
+			catch (IOException ioe) {
+				// TODO: what?
+			}
+			nc = (NetcdfFile) null;
+			refcount = 0;
+		}
+		
+	}
+
+	/**
+	 * Decrement the reference count and close if no
+	 * more references.
+	 */
+	synchronized void
+	releaseNetcdfFile()
+	{
+		if(refcount > 0)
+			refcount--;
+		if(refcount == 0) // assert (refcount >= 0)
+			close();
+	}
+}
+}
diff --git a/ucar/netcdf/NetcdfService.java b/ucar/netcdf/NetcdfService.java
new file mode 100644
index 0000000..71a694a
--- /dev/null
+++ b/ucar/netcdf/NetcdfService.java
@@ -0,0 +1,73 @@
+// $Id: NetcdfService.java,v 1.4 2002-05-29 18:31:35 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.rmi.Remote;
+
+/**
+ * This service provides a way to 'open' remote Netcdf data sets
+ * by name. It is a placeholder for a more elaborate
+ * directory service, hopefully to be provided later on.
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:35 $
+ */
+
+public interface
+NetcdfService
+		extends Remote
+{
+	/**
+	 * The string identifing this service in the rmi registry.
+	 */
+	public static final String SVC_NAME = "NetcdfService";
+
+	/**
+	 * Test if the service is alive.
+	 * Used by the automatic registration feature of the
+	 * NetcdfServer implementation.
+	 * @return 0
+	 */
+	public int
+	ping()
+		throws RemoteException;
+
+	/**
+	 * Connect to (open) a remote Netcdf dataSet by name.
+	 * If the name is not the same as one obtainable from
+	 * the list() opteration on this service, then this method
+	 * will fail.
+	 * @param dataSetName String name of the remote Netcdf
+	 * @return NetcdfRemoteProxy which can be used to create
+	 * an instance of RemoteNetcdf.
+	 */
+	public NetcdfRemoteProxy
+	lookup(String dataSetName)
+		throws RemoteException;
+
+	/**
+	 * List the names of exported data sets.
+	 */
+	public String []
+	list()
+		throws RemoteException;
+}
diff --git a/ucar/netcdf/NetcdfWrapper.java b/ucar/netcdf/NetcdfWrapper.java
new file mode 100644
index 0000000..72f0098
--- /dev/null
+++ b/ucar/netcdf/NetcdfWrapper.java
@@ -0,0 +1,143 @@
+// $Id: NetcdfWrapper.java,v 1.2 2002-05-29 18:31:35 steve Exp $
+/*
+ * Copyright 2001 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+
+package ucar.netcdf;
+
+/**
+ * Abstract "decorator" class for wrapping a {@link Netcdf} object.  All
+ * method invocations of the {@link Netcdf} API are forwarded to a contained
+ * {@link Netcdf} object.  This class is designed to be extended.
+ *
+ * @author Steven R. Emmerson
+ * @version $Revision: 1.2 $ $Date: 2002-05-29 18:31:35 $
+ */
+public abstract class NetcdfWrapper
+    implements Netcdf
+{
+    /**
+     * The wrapped, {@link Netcdf} object.
+     */
+    private final Netcdf netcdf;
+
+    /**
+     * Constructs from a netCDF object.
+     * @param netcdf               The netCDF dataset to be wrapped.
+     * @param NullPointerException if the argument is <code>null</code>.
+     */
+    protected NetcdfWrapper(Netcdf netcdf)
+        throws NullPointerException
+    {
+        if (netcdf == null)
+            throw new NullPointerException();
+        this.netcdf = netcdf;
+    }
+
+    /**
+     * Returns the wrapped {@link Netcdf} object.
+     * @return                    The wrapped, {@link Netcdf} object.
+     */
+    public final Netcdf getNetcdf()
+    {
+        return netcdf;
+    }
+
+    /**
+     * Returns the number of variables.
+     * @return                    The number of variables
+     */
+    public int size()
+    {
+        return netcdf.size();
+    }
+
+    /**
+     * Returns an iterator over the variables.
+     * @return                    An iterator over the variables.
+     */
+    public VariableIterator iterator()
+    {
+        return netcdf.iterator();
+    }
+
+    /**
+     * Retrieve the variable associated with a name.  If no such variable 
+     * exists, then <code>null</code> is returned.
+     * @param name                Name of the desired variable.
+     * @return                    The variable or <code>null</code>.
+     */
+    public Variable get(String name)
+    {
+        return netcdf.get(name);
+    }
+    
+    /**
+     * Tests if the Variable identified by <code>name</code> is in this dataset.
+     * @param name                Name of the desired variable.
+     * @return                    <code>true</code> if and only if this dataset
+     *                            contains the named variable.
+     */
+    public boolean contains(String name)
+    {
+        return netcdf.contains(name);
+    }
+
+    /**
+     * Tests an object is in this dataset.
+     * @param oo                  An object.
+     * @return                    <code>true</code> if and only if this dataset
+     *                            contains <code>oo</code>.
+     */
+    public boolean contains(Object oo)
+    {
+        return netcdf.contains(oo);
+    }
+
+    /**
+     * Returns all the netCDF dimensions in this dataset.
+     * @return                    The union of all dimensions of all variables.
+     *                            May be empty.
+     */
+    public DimensionSet getDimensions()
+    {
+        return netcdf.getDimensions();
+    }
+
+    /**
+     * Returns the set of global, netCDF attributes in this dataset. 
+     * @return                    All global attributes in this dataset.  May be
+     *                            empty.
+     */
+    public AttributeSet getAttributes()
+    {
+        return netcdf.getAttributes();
+    }
+
+    /**
+     * Returns a global, netCDF attribute by name.  If no such attribute exists,
+     * then <code>null</code> is returned.
+     * @param                    The name of the attribute.
+     * @return                   The attribute or <code>null</code>.
+     */
+    public Attribute getAttribute(String name)
+    {
+        return netcdf.getAttribute(name);
+    }
+}
diff --git a/ucar/netcdf/ProtoVariable.java b/ucar/netcdf/ProtoVariable.java
new file mode 100644
index 0000000..f91b7a3
--- /dev/null
+++ b/ucar/netcdf/ProtoVariable.java
@@ -0,0 +1,560 @@
+// $Id: ProtoVariable.java,v 1.4 2002-05-29 18:31:35 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.multiarray.MultiArrayInfo;
+import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Array;
+import java.io.ObjectInputStream;
+import java.io.InvalidClassException;
+
+/**
+ * Prototype for Netcdf Variable.
+ * Instances of ProtoVariable provide the description of a Netcdf Variable
+ * without data i/o functionality. Instances of this class are used in a Schema,
+ * which is used when we create a new Netcdf. An instance has a name and a shape
+ * specified by an array of Dimensions.
+ * It may also have descriptive attributes. The attribute set is 
+ * modifiable.
+ * <p>
+ * The data logically contained in a Netcdf Variable is not accessed
+ * through this object.
+ * <p>
+ * Although there is no explicit relationship between this class and
+ * and Variable, they share common method signatures and semantics where
+ * appropriate.
+ *
+ * @see Variable
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:35 $
+ */
+
+
+public class
+ProtoVariable
+	implements Named, MultiArrayInfo, Serializable, Cloneable
+{
+
+	/**
+	 * Check if the given Class corresponds to a netcdf type.
+	 * This method is used to check Attributes as well...
+	 *
+	 * @param componentType Class to check
+	 * @return <code>true</code> if Okay, <code>false</code> otherwise.
+	 */
+	static final boolean
+	checkComponentType(Class componentType)
+	{
+		if(!componentType.isPrimitive()
+				|| componentType.equals(Long.TYPE)
+				|| componentType.equals(Boolean.TYPE))
+			return false;
+		return true;
+	}
+	
+ /* Begin Constructors */
+
+	/**
+	 * The usual constructor, used when you are going to
+	 * add the attributes after construction.
+	 *
+	 * @param name  String which is to be the name of this Variable
+	 * @param componentType  Class (primitive type) contained herein.
+	 * 	One of
+	 *     	<code>Character.Type</code>,
+	 *		<code>Byte.TYPE</code>,
+	 *		<code>Short.TYPE</code>,
+	 *		<code>Integer.TYPE</code>,
+	 *		<code>Float.TYPE</code>,
+	 *	or
+	 * 		<code>Double.Type</code>.
+	 * @param dimArray  The dimensions which define the
+	 *	shape of this Variable.	If null or zero length array,
+	 *	this is a scalar variable.
+	 */
+	public
+	ProtoVariable(String name, Class componentType, Dimension [] dimArray)
+	{
+		this.name = name;
+		if(!checkComponentType(componentType))
+			throw new IllegalArgumentException("Invalid Type");
+		this.componentType = componentType;
+		if(dimArray == null) {
+			this.dimArray = new Dimension[0];
+		}
+		else synchronized (dimArray) {
+			this.dimArray = new Dimension[dimArray.length];
+			for(int ii = 0; ii < dimArray.length; ii++) {
+				final Dimension dim = dimArray[ii];
+				if(dim instanceof UnlimitedDimension) {
+					if(ii > 0)
+	throw new IllegalArgumentException(
+		"UnlimitedDimension not is leftmost position");
+				} else if (dim.getLength() == 0) {
+	throw new IllegalArgumentException(
+		"Zero length dimension"); 
+				}
+				this.dimArray[ii] = dim;
+			}
+		}
+		this.attributes = new AttributeDictionary();
+	}
+
+	/**
+	 * Convenience constructor for 1-dimensional Variables, often
+	 * used for coordinate variables. Typically attributes would
+	 * be added after construction when this constructor is used.
+	 *
+	 * @param name  String which is to be the name of this Variable
+	 * @param componentType  Class (primitive type) contained herein.
+	 * 	One of
+	 *     	<code>Character.Type</code>,
+	 *		<code>Byte.TYPE</code>,
+	 *		<code>Short.TYPE</code>,
+	 *		<code>Integer.TYPE</code>,
+	 *		<code>Float.TYPE</code>,
+	 *	or
+	 * 		<code>Double.Type</code>.
+	 * @param dimension  A single dimension to define the array.
+	 */
+	public
+	ProtoVariable(String name, Class componentType,
+			 Dimension dimension)
+	{
+		this.name = name;
+		if(!checkComponentType(componentType))
+			throw new IllegalArgumentException("Invalid Type");
+		this.componentType = componentType;
+		this.dimArray = new Dimension[1];
+		this.dimArray[0] = dimension;
+		this.attributes = new AttributeDictionary();
+	}
+
+
+	/**
+	 * More general constructor. Initializes attribute set
+	 * during construction.
+	 *
+	 * @param name  String which is to be the name of this Variable
+	 * @param componentType  Class (primitive type) contained herein.
+	 * 	One of
+	 *     	<code>Character.Type</code>,
+	 *		<code>Byte.TYPE</code>,
+	 *		<code>Short.TYPE</code>,
+	 *		<code>Integer.TYPE</code>,
+	 *		<code>Float.TYPE</code>,
+	 *	or
+	 * 		<code>Double.Type</code>.
+	 * @param dimArray  The dimensions which define the shape
+	 *	of this Variable. If null or zero length array,
+	 *	this is a scalar variable.
+	 * @param attrArray  Attributes associated with this Variable.
+	 *	May be null or a zero length array.
+	 */
+	public
+	ProtoVariable(String name, Class componentType,
+			Dimension [] dimArray, Attribute [] attrArray)
+	{
+		this.name = name;
+		if(!checkComponentType(componentType))
+			throw new IllegalArgumentException("Invalid Type");
+		this.componentType = componentType;
+		if(dimArray == null) {
+			this.dimArray = new Dimension[0];
+		}
+		else synchronized (dimArray) {
+			this.dimArray = new Dimension[dimArray.length];
+			for(int ii = 0; ii < dimArray.length; ii++) {
+				final Dimension dim = dimArray[ii];
+				if(dim instanceof UnlimitedDimension) {
+					if(ii > 0)
+	throw new IllegalArgumentException(
+		"UnlimitedDimension not is leftmost position");
+				} else if (dim.getLength() == 0){
+	throw new IllegalArgumentException(
+		"Zero length dimension"); 
+				}
+				this.dimArray[ii] = dim;
+			}
+		}
+		this.attributes = new AttributeDictionary(attrArray);
+	}
+
+	/**
+	 * copy constructor.
+	 */ 
+	ProtoVariable(ProtoVariable pv)
+	{
+		name = pv.getName();
+		componentType = pv.getComponentType();
+		pv.copyVolatile(this);
+	}
+
+	/**
+	 * Conversion constructor.
+	 */
+	public
+	ProtoVariable(Variable var)
+	{
+		/*
+		 * Why ask why? Would prefer to say:
+		 * this(var.meta); 
+		 *  ==> Blank final xxx may not have been initialized.
+		 */
+		name = var.meta.getName();
+		componentType = var.meta.getComponentType();
+		var.meta.copyVolatile(this);
+	}
+
+ /* End Constructors */
+
+	/**
+	 * Factor common code used
+	 * to implement copy constructor and clone() method.
+	 * Copy modifiable portions of this to dest.
+	 */
+	private synchronized void
+	copyVolatile(ProtoVariable dest)
+	{
+		dest.dimArray = new Dimension[dimArray.length];
+		for(int ii = 0; ii < dimArray.length; ii++)
+		{
+			dest.dimArray[ii] = (Dimension) dimArray[ii].clone();
+		}
+		dest.attributes = new AttributeDictionary(attributes);
+	}
+
+	/**
+	 * Returns a clone of this
+	 */
+	public Object
+	clone()
+	{
+		try {
+			final ProtoVariable pv = (ProtoVariable) super.clone();
+			copyVolatile(pv);
+			return pv;
+		}
+		catch (CloneNotSupportedException e)
+		{
+			// this shouldn't happen, since we are Cloneable
+			throw new Error();
+		}
+	}
+
+	/**
+	 * Returns the name of this Variable.
+	 * @return String which identifies this Variable.
+	 */
+	public final
+	String getName()
+	{
+		return name;
+	}
+
+	/**
+	 * Returns the Class object representing the component
+	 * type of the Variable.
+	 * @return Class The componentType
+	 * @see java.lang.Class#getComponentType
+	 */
+	public final Class
+	getComponentType()
+	{
+		return componentType;
+	}
+
+	/**
+	 * Returns the number of dimensions of the variable.
+	 * @return int number of dimensions of the variable
+	 */
+	public final int
+	getRank()
+	{
+		return dimArray.length;
+	}
+
+	/**
+	 * Return an array whose length is the rank of this
+	 * and whose elements represent the
+	 * length of each of its dimensions.
+	 *
+	 * @return int array whose length is the rank of this
+	 * and whose elements represent the
+	 * length of each of its dimensions
+	 */
+	public final int []
+	getLengths()
+	{
+		int [] lengths = new int[dimArray.length];
+		for(int ii = 0; ii < dimArray.length; ii++)
+				lengths[ii] = dimArray[ii].getLength();
+		
+		return lengths;
+	}
+
+	/**
+	 * Returns <code>true</code> if and only if the this variable can grow.
+	 * This is equivalent to saying
+	 * at least one of its dimensions is unlimited.
+	 * In the current implementation, exactly one dimension, the most
+	 * slowly varying (leftmost), can be unlimited.
+	 * @return boolean <code>true</code> iff this can grow
+	 */
+	public final boolean
+	isUnlimited()
+	{
+		return ((dimArray.length > 0)
+			 && dimArray[0] instanceof UnlimitedDimension);
+	}
+
+	/**
+	 * Convenience interface; return <code>true</code>
+	 * if and only if the rank is zero.
+	 * @return boolean <code>true</code> iff rank == 0
+	 */
+	public final boolean
+	isScalar()
+	{
+		return (dimArray.length == 0);
+	}
+
+	/**
+	 * Returns a DimensionIterator of the dimensions
+	 * used by this variable. The most slowly varying (leftmost
+	 * for java and C programmers) dimension is first.
+	 * For scalar variables, the set has no elements and the iteration
+	 * is empty.
+	 * @return DimensionIterator of the elements.
+	 * @see DimensionIterator
+	 */
+	public DimensionIterator
+	getDimensionIterator()
+	{
+		return new DimensionIterator() {
+			int position = 0;
+			
+			public boolean hasNext() {
+				return position < dimArray.length;
+			}
+
+			public Dimension next() {
+				return dimArray[position++];
+			}
+
+		};
+	}
+
+	/**
+	 * Convenience function; look up Attribute by name.
+	 *
+	 * @param name the name of the attribute
+	 * @return the attribute, or null if not found
+	 */
+	public Attribute
+	getAttribute(String name)
+	{
+		return attributes.get(name);
+	}
+
+	/**
+	 * Returns the (modifiable) set of attributes
+	 * associated with this. 
+	 * 
+	 * @return AttributeSet. May be empty. Won't be null.
+	 */
+	public AttributeSet
+	getAttributes()
+	{
+		return (AttributeSet) attributes;
+	}
+
+	/**
+	 * Convenience function; add attribute.
+	 * @see AttributeSet#put
+	 * @param attr the Attribute to be added to this set.
+	 * @return Attribute replaced or null if not a replacement
+	 */
+	public Attribute
+	putAttribute(Attribute attr)
+	{
+		return attributes.put(attr);
+	}
+
+	/**
+	 * Format as CDL.
+	 * @param buf StringBuffer into which to write
+	 */
+	public void
+	toCdl(StringBuffer buf)
+	{
+		buf.append(this.getComponentType());
+		buf.append(" ");
+		buf.append(this.getName());
+		buf.append("(");
+		for(DimensionIterator iter = this.getDimensionIterator();
+				iter.hasNext() ;) {
+			buf.append( iter.next().getName() );
+			if(!iter.hasNext())
+				break;
+			buf.append(", ");
+		}
+		buf.append(") ;\n");
+		
+		/*
+		 * We need to tag each attribute with this variable's name.
+		 */
+		for (AttributeIterator iter = this.getAttributes().iterator();
+				iter.hasNext() ;) {
+			buf.append("\t\t");
+			buf.append(this.getName());
+			iter.next().toCdl(buf);
+			buf.append("\n");
+		}
+	}
+
+	/**
+	 * @return a string representation of this
+	 */
+	public String
+	toString()
+	{
+		StringBuffer buf = new StringBuffer();
+		toCdl(buf);
+		return buf.toString();
+	}
+
+	/**
+	 * Ensure that the dimensions referenced by this
+	 * are members of the specified Dictionary.
+	 * This may modify dimArray elements to reference
+	 * different (equivalent) dimension instances.
+	 *
+	 * package private
+	 *
+	 */
+	synchronized void
+	connectDims(DimensionDictionary dimensions)
+	{
+		for(int ii = 0; ii < dimArray.length; ii++)
+		{
+			dimArray[ii] = dimensions.put(dimArray[ii]);
+		}
+	}
+
+
+	/**
+	 * Because members of Class Class can't be serialized, we have
+	 * to come up with our own encoding of the Class field 'componentType'.
+	 * <p>
+	 * The intent of this function is to use the same encoding as
+	 * 'prim_typecode' from the object serialization stream spec.
+	 *
+	 */
+	private int
+	encodeComponentType() 
+	{
+		if(componentType.isPrimitive())
+		{
+			if(componentType.equals(Byte.TYPE))
+				return (byte) 'B';
+			if(componentType.equals(Character.TYPE))
+				return (byte) 'C';
+			if(componentType.equals(Double.TYPE))
+				return (byte) 'D';
+			if(componentType.equals(Float.TYPE))
+				return (byte) 'F';
+			if(componentType.equals(Integer.TYPE))
+				return (byte) 'I';
+			if(componentType.equals(Long.TYPE))
+				return (byte) 'J';
+			if(componentType.equals(Short.TYPE))
+				return (byte) 'S';
+			if(componentType.equals(Boolean.TYPE))
+				return (byte) 'Z';
+		}
+		throw new IllegalArgumentException(componentType.toString());
+	}
+
+	static private Class
+	decodeComponentType(int typecode)
+			throws InvalidClassException
+	{
+		switch (typecode) {
+		case (byte) 'B':
+			return Byte.TYPE;
+		case (byte) 'C':
+			return Character.TYPE;
+		case (byte) 'D':
+			return Double.TYPE;
+		case (byte) 'F':
+			return Float.TYPE;
+		case (byte) 'I':
+			return Integer.TYPE;
+		case (byte) 'J':
+			return Long.TYPE;
+		case (byte) 'S':
+			return Short.TYPE;
+		case (byte) 'Z':
+			return Boolean.TYPE;
+		}
+		throw new InvalidClassException(Integer.toHexString(typecode));
+	}
+
+	private void
+	writeObject(ObjectOutputStream out)
+     		throws IOException
+	{
+		out.writeObject(name);
+		out.write(encodeComponentType());
+		out.writeObject(dimArray);
+		out.writeObject(attributes);
+	}
+
+	private void
+	readObject(ObjectInputStream in)
+     		throws IOException, ClassNotFoundException
+	{
+		name = (String) in.readObject();
+		final int typecode = in.read();
+		componentType = decodeComponentType(typecode);
+		dimArray = (Dimension []) in.readObject();
+		attributes = (AttributeDictionary) in.readObject();
+	}
+
+	/**
+	 * @serial
+	 */
+	private /* final */ String name;
+	/**
+	 * @serial
+	 */
+	private /* final */ Class componentType;
+	/**
+	 * @serial
+	 */
+	private /* final */ Dimension [] dimArray; 
+	/**
+	 * @serial
+	 */
+	private /* final */ AttributeDictionary attributes; 
+}
diff --git a/ucar/netcdf/ProtoVariableIterator.java b/ucar/netcdf/ProtoVariableIterator.java
new file mode 100644
index 0000000..cd7302c
--- /dev/null
+++ b/ucar/netcdf/ProtoVariableIterator.java
@@ -0,0 +1,41 @@
+// $Id: ProtoVariableIterator.java,v 1.4 2002-05-29 18:31:36 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * Type specific Iterator.
+ * Use the Iterator methods to fetch elements sequentially.
+ * @see java.util.Iterator
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:36 $
+ */
+public interface ProtoVariableIterator {
+    /**
+     * Returns <code>true</code> if there are more elements.
+     */
+    boolean hasNext();
+    /**
+     * Returns the next element. Calls to this
+     * method will step through successive elements.
+     * @exception java.util.NoSuchElementException If no more elements exist.
+     */
+    ProtoVariable next();
+}
diff --git a/ucar/netcdf/RandomAccessFile.java b/ucar/netcdf/RandomAccessFile.java
new file mode 100644
index 0000000..a9cd194
--- /dev/null
+++ b/ucar/netcdf/RandomAccessFile.java
@@ -0,0 +1,1631 @@
+// $Id: RandomAccessFile.java,v 1.7 2005-01-13 20:36:10 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+/*
+ * RandomAccessFile.java.  By Russ Rew, based on
+ * BufferedRandomAccessFile by Alex McManus, based on Sun's source code
+ * for java.io.RandomAccessFile.  For Alex McManus version from which
+ * this derives, see his <a href="http://www.aber.ac.uk/~agm/Java.html">
+ * Freeware Java Classes</a>.
+ */
+
+package ucar.netcdf;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UTFDataFormatException;
+
+import java.util.Random; // used in test method
+import java.util.Date;
+
+/**
+ * A buffered drop-in replacement for java.io.RandomAccessFile.
+ * Instances of this class realise substantial speed increases over
+ * java.io.RandomAccessFile through the use of buffering. This is a
+ * subclass of Object, as it was not possible to subclass
+ * java.io.RandomAccessFile because many of the methods are
+ * final. However, if it is necessary to use RandomAccessFile and
+ * java.io.RandomAccessFile interchangeably, both classes implement the
+ * DataInput and DataOutput interfaces.
+ *
+ * @author Alex McManus
+ * @author Russ Rew
+ * @version $Id: RandomAccessFile.java,v 1.7 2005-01-13 20:36:10 donm Exp $
+ * @see DataInput
+ * @see DataOutput
+ * @see java.io.RandomAccessFile */
+public class RandomAccessFile extends Object
+implements DataInput, DataOutput {
+
+   /** Read from the file. This is always implied. */
+   public static final int READ = 1;
+
+   /** Write to the file. */
+   public static final int WRITE = 2;
+
+   /** Create the file rather than overwriting it. This is ignored if mode is
+    *  not also WRITE. */
+   public static final int CREATE = 4;
+
+   /** The default buffer size, in bytes. */
+   protected static final int defaultBufferSize = 4096;
+
+   /** The underlying java.io.RandomAccessFile. */
+   protected java.io.RandomAccessFile file;
+
+   /** The offset in bytes from the file start, of the next read or
+    *  write operation. */
+   protected long filePosition;
+
+   /** The buffer used to load the data. */
+   protected byte buffer[];
+
+   /** The offset in bytes of the start of the buffer, from the start
+    *  of the file. */
+   protected long bufferStart;
+
+   /** The offset in bytes of the end of the data in the buffer, from
+    *  the start of the file. This can be calculated from
+    *  <code>bufferStart + dataSize</code>, but it is cached to speed
+    *  up the read( ) method. */
+   protected long dataEnd;
+
+   /** The size of the data stored in the buffer, in bytes. This may be
+    *  less than the size of the buffer.*/
+   protected int dataSize;
+
+   /** True if we are at the end of the file. */
+   protected boolean endOfFile;
+
+   /** The access mode of the file. This is a logical OR of READ,
+    *  WRITE and CREATE. */
+   protected int mode;
+
+   /** True if the data in the buffer has been modified. */
+   boolean bufferModified = false;
+
+     // subclasses only
+   protected RandomAccessFile(int bufferSize) {
+    // Initialise the buffer
+    bufferStart = 0;
+    dataEnd = 0;
+    dataSize = 0;
+    filePosition = 0;
+    buffer = new byte[bufferSize];
+    endOfFile = false;
+  }
+
+   /**
+    * Create a new buffered random-access file with a default buffer size.
+    * Note that the mode CREATE implies WRITE.
+    *
+    * @param filename  the name of the file.
+    * @param mode      how the file is to be opened. This may be a
+    *                  combination (logical OR) of CREATE, WRITE, and READ.
+    * @exception IOException        if an I/O error occurrs.
+    * @exception SecurityException  if a security manager exists, its checkRead
+    *                               method is called with the name argument to
+    *                               see if the application is allowed read
+    *                               access to the file. If the mode argument is
+    *                               WRITE, its checkWrite method also is called
+    *                               with the name argument to see if the
+    *                               application is allowed write access to the
+    *                               file. Either of these may result in a
+    *                               security exception.
+    */
+   public RandomAccessFile( String filename, int mode )
+   throws IOException {
+      this( filename, mode, defaultBufferSize );
+   }
+
+    /**
+     * Creates a random access file stream to read from, and optionally
+     * to write to, a file with the specified name.
+     * <p>
+     * The mode argument must either be equal to <code>"r"</code> or
+     * <code>"rw"</code>, indicating that the file is to be opened for
+     * input only or for both input and output, respectively.  If the
+     * mode is <code>"rw"</code> and the
+     * file does not exist, then an attempt is made to create it.
+     *
+     * @param      filename     the system-dependent filename.
+     * @param      modeString   the access mode.
+     * @exception  IllegalArgumentException  if the mode argument is not equal
+     *               to <code>"r"</code> or to <code>"rw"</code>.
+     * @exception  IOException               if an I/O error occurs.
+     * @exception  SecurityException         if a security manager exists, its
+     *               <code>checkRead</code> method is called with the name
+     *               argument to see if the application is allowed read access
+     *               to the file. If the mode argument is equal to
+     *               <code>"rw"</code>, its <code>checkWrite</code> method also
+     *               is called with the name argument to see if the application
+     *               is allowed write access to the file. Either of these may
+     *               result in a security exception.
+     * @see        java.lang.SecurityException
+     * @see        java.lang.SecurityManager#checkRead(java.lang.String)
+     */
+   public RandomAccessFile( String filename, String modeString )
+       throws IOException {
+       this (filename, modeString, defaultBufferSize);
+   }
+
+
+    /**
+     * Creates a random access file stream to read from, and optionally
+     * to write to, a file with the specified name.
+     * <p>
+     * The mode argument must either be equal to <code>"r"</code> or
+     * <code>"rw"</code>, indicating that the file is to be opened for
+     * input only or for both input and output, respectively.  If the
+     * mode is <code>"rw"</code> and the
+     * file does not exist, then an attempt is made to create it.
+     *
+     * @param      filename     the system-dependent filename.
+     * @param      modeString   the access mode.
+     * @exception  IllegalArgumentException  if the mode argument is not equal
+     *               to <code>"r"</code> or to <code>"rw"</code>.
+     * @exception  IOException               if an I/O error occurs.
+     * @exception  SecurityException         if a security manager exists, its
+     *               <code>checkRead</code> method is called with the name
+     *               argument to see if the application is allowed read access
+     *               to the file. If the mode argument is equal to
+     *               <code>"rw"</code>, its <code>checkWrite</code> method also
+     *               is called with the name argument to see if the application
+     *               is allowed write access to the file. Either of these may
+     *               result in a security exception.
+     * @see        java.lang.SecurityException
+     * @see        java.lang.SecurityManager#checkRead(java.lang.String)
+     */
+   public RandomAccessFile( String filename, String modeString, int bufferSize )
+       throws IOException {
+       this( filename,
+             modeString.equals("r")? READ :
+             (modeString.equals("rw")? WRITE | READ : 0),
+             bufferSize );
+   }
+
+    /**
+     * Creates a random access file stream to read from, and optionally
+     * to write to, the file specified by the <code>File</code> argument.
+     * A new {@link FileDescriptor} object is created to represent
+     * this file connection.
+     * <p>
+     * The mode argument must either be equal to <code>"r"</code> or
+     * <code>"rw"</code>, indicating that the file is to be opened for
+     * input only or for both input and output, respectively. The
+     * write methods on this object will always throw an
+     * <code>IOException</code> if the file is opened with a mode of
+     * <code>"r"</code>. If the mode is <code>"rw"</code> and the
+     * file does not exist, then an attempt is made to create it.
+     *
+     * @param      file         the file object.
+     * @param      modeString   the access mode.
+     * @exception  IllegalArgumentException  if the mode argument is not equal
+     *               to <code>"r"</code> or to <code>"rw"</code>.
+     * @exception  IOException               if an I/O error occurs.
+     * @exception  SecurityException         if a security manager exists, its
+     *               <code>checkRead</code> method is called with the pathname
+     *               of the <code>File</code> argument to see if the
+     *               application is allowed read access to the file. If the
+     *               mode argument is equal to <code>"rw"</code>, its
+     *               <code>checkWrite</code> method also is called with the
+     *               pathname to see if the application is allowed write access
+     *               to the file.
+     * @see        java.io.File#getPath()
+     * @see        java.lang.SecurityManager#checkRead(java.lang.String)
+     */
+    public RandomAccessFile(File file, String modeString) throws IOException {
+        this(file.getPath(), modeString);
+    }
+
+   /**
+    * Create a new buffered random-access file with a specified buffer
+    * size. Note that the mode CREATE implies WRITE, and the READ is
+    * always implied.
+    *
+    * @param filename    the name of the file.
+    * @param mode        how the file is to be opened. This may be a
+                         combination (logical OR) of CREATE, WRITE, and READ.
+    * @param bufferSize  the size of the temporary buffer, in bytes.
+    * @exception FileNotFoundException
+    *                               if the access is readonly and the file
+    *                               doesn't exist.
+    * @exception IOException        if an I/O error occurrs.
+    * @exception SecurityException  if a security manager exists, its checkRead
+    *                               method is called with the name argument to
+    *                               see if the application is allowed read
+    *                               access to the file. If the mode argument is
+    *                               WRITE, its checkWrite method also is called
+    *                               with the name argument to see if the
+    *                               application is allowed write access to the
+    *                               file. Either of these may result in a
+    *                               security exception.
+    */
+   public RandomAccessFile( String filename, int mode, int bufferSize )
+   throws FileNotFoundException, IOException {
+      this.mode = mode;
+
+      // If we are CREATEing a file, we must also WRITE. READ is always
+      // set.
+      mode |= READ;
+      if( (this.mode & CREATE) > 0 )
+         this.mode |= WRITE;
+
+      // To match java.io.RandomAccessFile semantics, if we want to write
+      // a nonexistant file, create it first (even if CREATE not set)
+      File checkfile = new File( filename );
+      if((this.mode & WRITE) > 0 && !checkfile.exists( ) ) {
+          mode |= CREATE;
+      }
+
+      // If a new file is to be created, delete any existing file with the same name.
+      if( (this.mode & CREATE) > 0 ) {
+         if( checkfile.exists( ) ) {
+            if( !checkfile.delete( ) )
+               throw new IOException( "Failed to delete " + filename );
+         }
+      }
+
+      // If only reading, check that the file exists.
+      if( this.mode == READ && !(new File( filename )).exists( ) )
+         throw new FileNotFoundException( filename );
+
+      // Create the underlying file object.
+      String modeString = ((this.mode & WRITE) > 0) ? "rw" : "r";
+      file = new java.io.RandomAccessFile( filename, modeString );
+
+      // Initialise the buffer;
+      bufferStart = 0;
+      dataEnd = 0;
+      dataSize = 0;
+      filePosition = 0;
+      buffer = new byte[bufferSize];
+      endOfFile = false;
+   }
+
+   /**
+    * Close the file, and release any associated system resources.
+    *
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public void close()
+   throws IOException {
+
+      // If we are writing and the buffer has been modified, flush the contents
+      // of the buffer.
+      if( (mode | WRITE) > 0 && bufferModified ) {
+         file.seek( bufferStart );
+         file.write( buffer, 0, (int)dataSize );
+      }
+
+      // Close the underlying file object.
+      file.close( );
+   }
+
+   /**
+    * Set the position in the file for the next read or write.
+    *
+    * @param pos  the offset (in bytes) from the start of the file.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public void seek( long pos ) throws IOException {
+
+      // If the seek is into the buffer, just update the file pointer.
+      if( pos >= bufferStart && pos < dataEnd ) {
+         filePosition = pos;
+         return;
+      }
+
+      // If the current buffer is modified, write it to disk.
+      if( bufferModified )
+         flush( );
+
+      // need new buffer
+      bufferStart = pos;
+      filePosition = pos;
+
+      dataSize = read_( pos, buffer, 0, buffer.length);
+      if( dataSize < 0 ) {
+         dataSize = 0;
+         endOfFile = true;
+      } else {
+         endOfFile = false;
+      }
+
+      // Cache the position of the buffer end.
+      dataEnd = bufferStart + dataSize;
+   }
+
+   /**
+    * Returns the current position in the file, where the next read or
+    * write will occur.
+    *
+    * @return the offset from the start of the file in bytes.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public long getFilePointer() throws IOException {
+      return filePosition;
+   }
+
+   /**
+    * Get the length of the file. The data in the buffer (which may not
+    * have been written the disk yet) is taken into account.
+    *
+    * @return the length of the file in bytes.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public long length( ) throws IOException {
+      long fileLength = file.length( );
+      if( fileLength < dataEnd )
+         return dataEnd;
+      else
+         return fileLength;
+   }
+
+    /**
+     * Returns the opaque file descriptor object associated with this file.
+     *
+     * @return the file descriptor object associated with this file.
+     * @exception IOException if an I/O error occurs.
+     */
+    public FileDescriptor getFD()
+        throws IOException {
+        return file.getFD();
+    }
+
+   /**
+    * Copy the contents of the buffer to the disk.
+    *
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public void flush( ) throws IOException {
+     if (bufferModified) {
+       file.seek( bufferStart );
+       file.write( buffer, 0, dataSize );
+       bufferModified = false;
+     }
+   }
+
+   //
+   // Read primitives.
+   //
+
+   /**
+    * Read a byte of data from the file, blocking until data is
+    * available.
+    *
+    * @return the next byte of data, or -1 if the end of the file is
+    *         reached.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public final int read()
+   throws IOException {
+
+      // If the file position is within the data, return the byte...
+      if( filePosition < dataEnd ) {
+         return (int)(buffer[(int)(filePosition++ - bufferStart)] & 0xff);
+
+      // ...or should we indicate EOF...
+      } else if( endOfFile ) {
+         return -1;
+
+      // ...or seek to fill the buffer, and try again.
+      } else {
+         seek( filePosition );
+         return read( );
+      }
+   }
+
+   /**
+    * Read up to <code>len</code> bytes into an array, at a specified
+    * offset. This will block until at least one byte has been read.
+    *
+    * @param b    the byte array to receive the bytes.
+    * @param off  the offset in the array where copying will start.
+    * @param len  the number of bytes to copy.
+    * @return the actual number of bytes read, or -1 if there is not
+    *         more data due to the end of the file being reached.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   private int readBytes( byte b[], int off, int len ) throws IOException {
+
+      // Check for end of file.
+      if( endOfFile )
+         return -1;
+
+      // See how many bytes are available in the buffer - if none,
+      // seek to the file position to update the buffer and try again.
+      int bytesAvailable = (int)(dataEnd - filePosition);
+      if( bytesAvailable < 1 ) {
+         seek( filePosition );
+         return readBytes( b, off, len );
+      }
+
+      // Copy as much as we can.
+      int copyLength = (bytesAvailable >= len) ? len : bytesAvailable;
+      System.arraycopy( buffer, (int)(filePosition - bufferStart),
+                        b, off, copyLength );
+      filePosition += copyLength;
+
+      // If there is more to copy...
+      if( copyLength < len ) {
+         int extraCopy = len - copyLength;
+
+         // If the amount remaining is more than a buffer's length, read it
+         // directly from the file.
+         if( extraCopy > buffer.length ) {
+            extraCopy = read_( filePosition, b, off + copyLength, len - copyLength );
+
+         // ...or read a new buffer full, and copy as much as possible...
+         } else {
+            seek( filePosition );
+            if( ! endOfFile ) {
+               extraCopy = (extraCopy > dataSize) ? dataSize : extraCopy;
+               System.arraycopy( buffer, 0, b, off + copyLength, extraCopy );
+            } else {
+               extraCopy = -1;
+            }
+         }
+
+         // If we did manage to copy any more, update the file position and
+         // return the amount copied.
+         if( extraCopy > 0 ) {
+            filePosition += extraCopy;
+            return copyLength + extraCopy;
+         }
+      }
+
+      // Return the amount copied.
+      return copyLength;
+   }
+
+   // read directly, without going through the buffer
+   protected int read_( long pos, byte[] b, int offset, int len) throws IOException {
+
+     file.seek( pos );
+     int n = file.read( b, offset, len );
+
+     //System.out.println(" want = "+len+" at "+pos+"; got = "+n);
+     return n;
+  }
+
+   /**
+    * Read up to <code>len</code> bytes into an array, at a specified
+    * offset. This will block until at least one byte has been read.
+    *
+    * @param b    the byte array to receive the bytes.
+    * @param off  the offset in the array where copying will start.
+    * @param len  the number of bytes to copy.
+    * @return the actual number of bytes read, or -1 if there is not
+    *         more data due to the end of the file being reached.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public int read( byte b[], int off, int len ) throws IOException {
+      return readBytes( b, off, len );
+   }
+
+   /**
+    * Read up to <code>b.length( )</code> bytes into an array. This
+    * will block until at least one byte has been read.
+    *
+    * @param b  the byte array to receive the bytes.
+    * @return the actual number of bytes read, or -1 if there is not
+    *         more data due to the end of the file being reached.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public int read( byte b[] ) throws IOException {
+      return readBytes( b, 0, b.length );
+   }
+
+   /**
+    * Reads <code>b.length</code> bytes from this file into the byte
+    * array. This method reads repeatedly from the file until all the
+    * bytes are read. This method blocks until all the bytes are read,
+    * the end of the stream is detected, or an exception is thrown.
+    *
+    * @param b  the buffer into which the data is read.
+    * @exception EOFException  if this file reaches the end before reading
+    *                          all the bytes.
+    * @exception IOException   if an I/O error occurs.
+    */
+   public final void readFully( byte b[] )
+   throws IOException {
+      readFully(b, 0, b.length);
+   }
+
+   /**
+    * Reads exactly <code>len</code> bytes from this file into the byte
+    * array. This method reads repeatedly from the file until all the
+    * bytes are read. This method blocks until all the bytes are read,
+    * the end of the stream is detected, or an exception is thrown.
+    *
+    * @param      b     the buffer into which the data is read.
+    * @param      off   the start offset of the data.
+    * @param      len   the number of bytes to read.
+    * @exception  EOFException  if this file reaches the end before reading
+    *                           all the bytes.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final void readFully( byte b[], int off, int len )
+   throws IOException {
+      int n = 0;
+      while (n < len) {
+         int count = this.read(b, off + n, len - n);
+         if (count < 0)
+            throw new EOFException();
+         n += count;
+      }
+   }
+
+   /**
+    * Skips exactly <code>n</code> bytes of input.
+    * This method blocks until all the bytes are skipped, the end of
+    * the stream is detected, or an exception is thrown.
+    *
+    * @param      n   the number of bytes to be skipped.
+    * @return the number of bytes skipped, which is always <code>n</code>.
+    * @exception  EOFException  if this file reaches the end before skipping
+    *                           all the bytes.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public int skipBytes( int n )
+   throws IOException {
+      seek(getFilePointer() + n);
+      return n;
+   }
+
+   /**
+    * Unread the last byte read.
+    * This method should not be used more than once
+    * between reading operations, or strange things might happen.
+    */
+   public final void unread( ) {
+      filePosition--;
+   }
+
+   //
+   // Write primitives.
+   //
+
+   /**
+    * Write a byte to the file. If the file has not been opened for
+    * writing, an IOException will be raised only when an attempt is
+    * made to write the buffer to the file.
+    * <p>
+    * Caveat: the effects of seek( )ing beyond the end of the file are
+    *         undefined.
+    *
+    * @exception IOException if an I/O error occurrs.
+    */
+   public final void write( int b )
+   throws IOException {
+
+      // If the file position is within the block of data...
+      if( filePosition < dataEnd ) {
+         buffer[(int)(filePosition++ - bufferStart)] = (byte)b;
+         bufferModified = true;
+
+      // ...or (assuming that seek will not allow the file pointer
+      // to move beyond the end of the file) get the correct block of
+      // data...
+      } else {
+
+         // If there is room in the buffer, expand it...
+         if( dataSize != buffer.length ) {
+            buffer[(int)(filePosition++ - bufferStart)] = (byte)b;
+            bufferModified = true;
+            dataSize++;
+            dataEnd++;
+
+         // ...or do another seek to get a new buffer, and start again...
+         } else {
+            seek( filePosition );
+            write( b );
+         }
+      }
+   }
+
+   /**
+    * Write <code>len</code> bytes from an array to the file.
+    *
+    * @param b    the array containing the data.
+    * @param off  the offset in the array to the data.
+    * @param len  the length of the data.
+    * @exception IOException  if an I/O error occurrs.
+    */
+   public final void writeBytes( byte b[], int off, int len )
+   throws IOException {
+
+      // If the amount of data is small (less than a full buffer)...
+      if( len < buffer.length ) {
+
+         // If any of the data fits within the buffer...
+         int spaceInBuffer = 0;
+         int copyLength = 0;
+         if( filePosition >= bufferStart )
+            spaceInBuffer = (int)((bufferStart + buffer.length) - filePosition);
+         if( spaceInBuffer > 0 ) {
+
+            // Copy as much as possible to the buffer.
+            copyLength = (spaceInBuffer > len) ? len : spaceInBuffer;
+            System.arraycopy( b, off, buffer,
+                              (int)(filePosition - bufferStart), copyLength );
+            bufferModified = true;
+            long myDataEnd = filePosition + copyLength;
+            dataEnd = myDataEnd > dataEnd ? myDataEnd : dataEnd;
+            dataSize = (int)(dataEnd - bufferStart);
+            filePosition += copyLength;
+         }
+
+         // If there is any data remaining, move to the new position and copy to
+         // the new buffer.
+         if( copyLength < len ) {
+            seek( filePosition );
+            System.arraycopy( b, off + copyLength, buffer,
+                              (int)(filePosition - bufferStart),
+                              len - copyLength );
+            bufferModified = true;
+            long myDataEnd = filePosition + (len - copyLength);
+            dataEnd = myDataEnd > dataEnd ? myDataEnd : dataEnd;
+            dataSize = (int)(dataEnd - bufferStart);
+            filePosition += (len - copyLength);
+         }
+
+      // ...or write a lot of data...
+      } else {
+
+         // Flush the current buffer, and write this data to the file.
+         if( bufferModified ) {
+            flush( );
+            bufferStart = dataEnd = dataSize = 0;
+            file.seek(filePosition); // JC added Oct 21, 2004
+         }
+         file.write( b, off, len );
+         filePosition += len;
+      }
+   }
+
+   /**
+    * Writes <code>b.length</code> bytes from the specified byte array
+    * starting at offset <code>off</code> to this file.
+    *
+    * @param b   the data.
+    * @exception IOException  if an I/O error occurs.
+    */
+   public void write(byte b[]) throws IOException {
+      writeBytes(b, 0, b.length);
+   }
+
+   /**
+    * Writes <code>len</code> bytes from the specified byte array
+    * starting at offset <code>off</code> to this file.
+    *
+    * @param b    the data.
+    * @param off  the start offset in the data.
+    * @param len  the number of bytes to write.
+    * @exception IOException  if an I/O error occurs.
+    */
+   public void write(byte b[], int off, int len) throws IOException {
+      writeBytes(b, off, len);
+   }
+
+   //
+   // DataInput methods.
+   //
+
+   /**
+    * Reads a <code>boolean</code> from this file. This method reads a
+    * single byte from the file. A value of <code>0</code> represents
+    * <code>false</code>. Any other value represents <code>true</code>.
+    * This method blocks until the byte is read, the end of the stream
+    * is detected, or an exception is thrown.
+    *
+    * @return     the <code>boolean</code> value read.
+    * @exception  EOFException  if this file has reached the end.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final boolean readBoolean() throws IOException {
+      int ch = this.read();
+      if (ch < 0)
+         throw new EOFException();
+      return (ch != 0);
+   }
+
+   /**
+    * Reads a signed 8-bit value from this file. This method reads a
+    * byte from the file. If the byte read is <code>b</code>, where
+    * <code>0 <= b <= 255</code>,
+    * then the result is:
+    * <ul><code>
+    *     (byte)(b)
+    *</code></ul>
+    * <p>
+    * This method blocks until the byte is read, the end of the stream
+    * is detected, or an exception is thrown.
+    *
+    * @return     the next byte of this file as a signed 8-bit
+    *             <code>byte</code>.
+    * @exception  EOFException  if this file has reached the end.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final byte readByte() throws IOException {
+      int ch = this.read();
+      if (ch < 0)
+         throw new EOFException();
+      return (byte)(ch);
+   }
+
+   /**
+    * Reads an unsigned 8-bit number from this file. This method reads
+    * a byte from this file and returns that byte.
+    * <p>
+    * This method blocks until the byte is read, the end of the stream
+    * is detected, or an exception is thrown.
+    *
+    * @return     the next byte of this file, interpreted as an unsigned
+    *             8-bit number.
+    * @exception  EOFException  if this file has reached the end.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final int readUnsignedByte() throws IOException {
+      int ch = this.read();
+      if (ch < 0)
+         throw new EOFException();
+      return ch;
+   }
+
+   /**
+    * Reads a signed 16-bit number from this file. The method reads 2
+    * bytes from this file. If the two bytes read, in order, are
+    * <code>b1</code> and <code>b2</code>, where each of the two values is
+    * between <code>0</code> and <code>255</code>, inclusive, then the
+    * result is equal to:
+    * <ul><code>
+    *     (short)((b1 << 8) | b2)
+    * </code></ul>
+    * <p>
+    * This method blocks until the two bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     the next two bytes of this file, interpreted as a signed
+    *             16-bit number.
+    * @exception  EOFException  if this file reaches the end before reading
+    *               two bytes.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final short readShort() throws IOException {
+      int ch1 = this.read();
+      int ch2 = this.read();
+      if ((ch1 | ch2) < 0)
+         throw new EOFException();
+      return (short)((ch1 << 8) + (ch2 << 0));
+   }
+
+   /**
+    * Reads an unsigned 16-bit number from this file. This method reads
+    * two bytes from the file. If the bytes read, in order, are
+    * <code>b1</code> and <code>b2</code>, where
+    * <code>0 <= b1, b2 <= 255</code>,
+    * then the result is equal to:
+    * <ul><code>
+    *     (b1 << 8) | b2
+    * </code></ul>
+    * <p>
+    * This method blocks until the two bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     the next two bytes of this file, interpreted as an unsigned
+    *             16-bit integer.
+    * @exception  EOFException  if this file reaches the end before reading
+    *               two bytes.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final int readUnsignedShort() throws IOException {
+      int ch1 = this.read();
+      int ch2 = this.read();
+      if ((ch1 | ch2) < 0)
+         throw new EOFException();
+      return (ch1 << 8) + (ch2 << 0);
+   }
+
+   /**
+    * Reads a Unicode character from this file. This method reads two
+    * bytes from the file. If the bytes read, in order, are
+    * <code>b1</code> and <code>b2</code>, where
+    * <code>0 <= b1, b2 <= 255</code>,
+    * then the result is equal to:
+    * <ul><code>
+    *     (char)((b1 << 8) | b2)
+    * </code></ul>
+    * <p>
+    * This method blocks until the two bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     the next two bytes of this file as a Unicode character.
+    * @exception  EOFException  if this file reaches the end before reading
+    *               two bytes.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final char readChar() throws IOException {
+      int ch1 = this.read();
+      int ch2 = this.read();
+      if ((ch1 | ch2) < 0)
+         throw new EOFException();
+      return (char)((ch1 << 8) + (ch2 << 0));
+   }
+
+   /**
+    * Reads a signed 32-bit integer from this file. This method reads 4
+    * bytes from the file. If the bytes read, in order, are <code>b1</code>,
+    * <code>b2</code>, <code>b3</code>, and <code>b4</code>, where
+    * <code>0 <= b1, b2, b3, b4 <= 255</code>,
+    * then the result is equal to:
+    * <ul><code>
+    *     (b1 << 24) | (b2 << 16) + (b3 << 8) + b4
+    * </code></ul>
+    * <p>
+    * This method blocks until the four bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     the next four bytes of this file, interpreted as an
+    *             <code>int</code>.
+    * @exception  EOFException  if this file reaches the end before reading
+    *               four bytes.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final int readInt() throws IOException {
+      int ch1 = this.read();
+      int ch2 = this.read();
+      int ch3 = this.read();
+      int ch4 = this.read();
+      if ((ch1 | ch2 | ch3 | ch4) < 0)
+         throw new EOFException();
+      return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
+   }
+
+   /**
+    * Reads a signed 64-bit integer from this file. This method reads eight
+    * bytes from the file. If the bytes read, in order, are
+    * <code>b1</code>, <code>b2</code>, <code>b3</code>,
+    * <code>b4</code>, <code>b5</code>, <code>b6</code>,
+    * <code>b7</code>, and <code>b8,</code> where:
+    * <ul><code>
+    *     0 <= b1, b2, b3, b4, b5, b6, b7, b8 <=255,
+    * </code></ul>
+    * <p>
+    * then the result is equal to:
+    * <p><blockquote><pre>
+    *     ((long)b1 << 56) + ((long)b2 << 48)
+    *     + ((long)b3 << 40) + ((long)b4 << 32)
+    *     + ((long)b5 << 24) + ((long)b6 << 16)
+    *     + ((long)b7 << 8) + b8
+    * </pre></blockquote>
+    * <p>
+    * This method blocks until the eight bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     the next eight bytes of this file, interpreted as a
+    *             <code>long</code>.
+    * @exception  EOFException  if this file reaches the end before reading
+    *               eight bytes.
+    * @exception  IOException   if an I/O error occurs.
+    */
+   public final long readLong() throws IOException {
+      return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
+   }
+
+   /**
+    * Reads a <code>float</code> from this file. This method reads an
+    * <code>int</code> value as if by the <code>readInt</code> method
+    * and then converts that <code>int</code> to a <code>float</code>
+    * using the <code>intBitsToFloat</code> method in class
+    * <code>Float</code>.
+    * <p>
+    * This method blocks until the four bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     the next four bytes of this file, interpreted as a
+    *             <code>float</code>.
+    * @exception  EOFException  if this file reaches the end before reading
+    *             four bytes.
+    * @exception  IOException   if an I/O error occurs.
+    * @see        java.io.RandomAccessFile#readInt()
+    * @see        java.lang.Float#intBitsToFloat(int)
+    */
+   public final float readFloat() throws IOException {
+      return Float.intBitsToFloat(readInt());
+   }
+
+   /**
+    * Reads a <code>double</code> from this file. This method reads a
+    * <code>long</code> value as if by the <code>readLong</code> method
+    * and then converts that <code>long</code> to a <code>double</code>
+    * using the <code>longBitsToDouble</code> method in
+    * class <code>Double</code>.
+    * <p>
+    * This method blocks until the eight bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     the next eight bytes of this file, interpreted as a
+    *             <code>double</code>.
+    * @exception  EOFException  if this file reaches the end before reading
+    *             eight bytes.
+    * @exception  IOException   if an I/O error occurs.
+    * @see        java.io.RandomAccessFile#readLong()
+    * @see        java.lang.Double#longBitsToDouble(long)
+    */
+   public final double readDouble() throws IOException {
+      return Double.longBitsToDouble(readLong());
+   }
+
+   /**
+    * Reads the next line of text from this file. This method
+    * successively reads bytes from the file until it reaches the end of
+    * a line of text.
+    * <p>
+    * A line of text is terminated by a carriage-return character
+    * (<code>'\r'</code>), a newline character (<code>'\n'</code>), a
+    * carriage-return character immediately followed by a newline
+    * character, or the end of the input stream. The line-terminating
+    * character(s), if any, are included as part of the string returned.
+    * <p>
+    * This method blocks until a newline character is read, a carriage
+    * return and the byte following it are read (to see if it is a
+    * newline), the end of the stream is detected, or an exception is thrown.
+    *
+    * @return     the next line of text from this file.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final String readLine() throws IOException {
+      StringBuffer input = new StringBuffer();
+      int c;
+
+      while (((c = read()) != -1) && (c != '\n')) {
+         input.append((char)c);
+      }
+      if ((c == -1) && (input.length() == 0)) {
+         return null;
+      }
+      return input.toString();
+   }
+
+   /**
+    * Reads in a string from this file. The string has been encoded
+    * using a modified UTF-8 format.
+    * <p>
+    * The first two bytes are read as if by
+    * <code>readUnsignedShort</code>. This value gives the number of
+    * following bytes that are in the encoded string, not
+    * the length of the resulting string. The following bytes are then
+    * interpreted as bytes encoding characters in the UTF-8 format
+    * and are converted into characters.
+    * <p>
+    * This method blocks until all the bytes are read, the end of the
+    * stream is detected, or an exception is thrown.
+    *
+    * @return     a Unicode string.
+    * @exception  EOFException            if this file reaches the end before
+    *               reading all the bytes.
+    * @exception  IOException             if an I/O error occurs.
+    * @exception  UTFDataFormatException  if the bytes do not represent
+    *               valid UTF-8 encoding of a Unicode string.
+    * @see        java.io.RandomAccessFile#readUnsignedShort()
+    */
+   public final String readUTF() throws IOException {
+      return DataInputStream.readUTF(this);
+   }
+
+   //
+   // DataOutput methods.
+   //
+
+   /**
+    * Writes a <code>boolean</code> to the file as a 1-byte value. The
+    * value <code>true</code> is written out as the value
+    * <code>(byte)1</code>; the value <code>false</code> is written out
+    * as the value <code>(byte)0</code>.
+    *
+    * @param      v   a <code>boolean</code> value to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeBoolean(boolean v) throws IOException {
+      write(v ? 1 : 0);
+   }
+
+   /**
+    * Writes a <code>byte</code> to the file as a 1-byte value.
+    *
+    * @param      v   a <code>byte</code> value to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeByte(int v) throws IOException {
+      write(v);
+   }
+
+   /**
+    * Writes a <code>short</code> to the file as two bytes, high byte first.
+    *
+    * @param      v   a <code>short</code> to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeShort(int v) throws IOException {
+      write((v >>> 8) & 0xFF);
+      write((v >>> 0) & 0xFF);
+   }
+
+   /**
+    * Writes a <code>char</code> to the file as a 2-byte value, high
+    * byte first.
+    *
+    * @param      v   a <code>char</code> value to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeChar(int v) throws IOException {
+      write((v >>> 8) & 0xFF);
+      write((v >>> 0) & 0xFF);
+    }
+
+   /**
+    * Writes an <code>int</code> to the file as four bytes, high byte first.
+    *
+    * @param      v   an <code>int</code> to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeInt(int v) throws IOException {
+      write((v >>> 24) & 0xFF);
+      write((v >>> 16) & 0xFF);
+      write((v >>>  8) & 0xFF);
+      write((v >>>  0) & 0xFF);
+   }
+
+   /**
+    * Writes a <code>long</code> to the file as eight bytes, high byte first.
+    *
+    * @param      v   a <code>long</code> to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeLong(long v) throws IOException {
+      write((int)(v >>> 56) & 0xFF);
+      write((int)(v >>> 48) & 0xFF);
+      write((int)(v >>> 40) & 0xFF);
+      write((int)(v >>> 32) & 0xFF);
+      write((int)(v >>> 24) & 0xFF);
+      write((int)(v >>> 16) & 0xFF);
+      write((int)(v >>>  8) & 0xFF);
+      write((int)(v >>>  0) & 0xFF);
+   }
+
+   /**
+    * Converts the float argument to an <code>int</code> using the
+    * <code>floatToIntBits</code> method in class <code>Float</code>,
+    * and then writes that <code>int</code> value to the file as a
+    * 4-byte quantity, high byte first.
+    *
+    * @param      v   a <code>float</code> value to be written.
+    * @exception  IOException  if an I/O error occurs.
+    * @see        java.lang.Float#floatToIntBits(float)
+    */
+   public final void writeFloat(float v) throws IOException {
+      writeInt(Float.floatToIntBits(v));
+   }
+
+   /**
+    * Converts the double argument to a <code>long</code> using the
+    * <code>doubleToLongBits</code> method in class <code>Double</code>,
+    * and then writes that <code>long</code> value to the file as an
+    * 8-byte quantity, high byte first.
+    *
+    * @param      v   a <code>double</code> value to be written.
+    * @exception  IOException  if an I/O error occurs.
+    * @see        java.lang.Double#doubleToLongBits(double)
+    */
+   public final void writeDouble(double v) throws IOException {
+      writeLong(Double.doubleToLongBits(v));
+   }
+
+   /**
+    * Writes the string to the file as a sequence of bytes. Each
+    * character in the string is written out, in sequence, by discarding
+    * its high eight bits.
+    *
+    * @param      s   a string of bytes to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeBytes(String s) throws IOException {
+      int len = s.length();
+      for (int i = 0 ; i < len ; i++) {
+         write((byte)s.charAt(i));
+      }
+   }
+
+   /**
+    * Writes the character array to the file as a sequence of bytes. Each
+    * character in the string is written out, in sequence, by discarding
+    * its high eight bits.
+    *
+    * @param b    a character array of bytes to be written.
+    * @param off  the index of the first character to write.
+    * @param len  the number of characters to write.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeBytes( char b[], int off, int len ) throws IOException {
+      for( int i = off; i < len; i++ ) {
+         write( (byte)b[i] );
+      }
+   }
+
+   /**
+    * Writes a string to the file as a sequence of characters. Each
+    * character is written to the data output stream as if by the
+    * <code>writeChar</code> method.
+    *
+    * @param      s   a <code>String</code> value to be written.
+    * @exception  IOException  if an I/O error occurs.
+    * @see        java.io.RandomAccessFile#writeChar(int)
+    */
+   public final void writeChars(String s) throws IOException {
+      int len = s.length();
+      for (int i = 0 ; i < len ; i++) {
+         int v = s.charAt(i);
+         write((v >>> 8) & 0xFF);
+         write((v >>> 0) & 0xFF);
+      }
+   }
+
+   /**
+    * Writes a string to the file using UTF-8 encoding in a
+    * machine-independent manner.
+    * <p>
+    * First, two bytes are written to the file as if by the
+    * <code>writeShort</code> method giving the number of bytes to
+    * follow. This value is the number of bytes actually written out,
+    * not the length of the string. Following the length, each character
+    * of the string is output, in sequence, using the UTF-8 encoding
+    * for each character.
+    *
+    * @param      str   a string to be written.
+    * @exception  IOException  if an I/O error occurs.
+    */
+   public final void writeUTF(String str) throws IOException {
+      int strlen = str.length();
+      int utflen = 0;
+
+      for (int i = 0 ; i < strlen ; i++) {
+         int c = str.charAt(i);
+         if ((c >= 0x0001) && (c <= 0x007F)) {
+            utflen++;
+         } else if (c > 0x07FF) {
+            utflen += 3;
+         } else {
+            utflen += 2;
+         }
+      }
+      if (utflen > 65535)
+         throw new UTFDataFormatException();
+
+      write((utflen >>> 8) & 0xFF);
+      write((utflen >>> 0) & 0xFF);
+      for (int i = 0 ; i < strlen ; i++) {
+         int c = str.charAt(i);
+         if ((c >= 0x0001) && (c <= 0x007F)) {
+            write(c);
+         } else if (c > 0x07FF) {
+            write(0xE0 | ((c >> 12) & 0x0F));
+            write(0x80 | ((c >>  6) & 0x3F));
+            write(0x80 | ((c >>  0) & 0x3F));
+         } else {
+            write(0xC0 | ((c >>  6) & 0x1F));
+            write(0x80 | ((c >>  0) & 0x3F));
+         }
+      }
+   }
+
+   /**
+    * Create a string representation of this object.
+    *
+    * @return a string representation of the state of the object.
+    */
+   public String toString( ) {
+      return "fp=" + filePosition + ", bs=" + bufferStart +
+             ", de=" + dataEnd + ", ds=" + dataSize +
+             ", bl=" + buffer.length + ", m=" + mode +
+             ", bm=" + bufferModified;
+   }
+
+   /**
+    * Test the byte operations of the RandomAccessFile class. These are
+    * the methods that read/write on a byte-by-byte basis. The following checks
+    * are made:
+    * <ul>
+    * <li>Writing random bytes to a file.
+    * <li>Checking the size of the file is correct.
+    * <li>Checking that EOF is correctly raised.
+    * <li>Reading the file back in and verifying its contents.
+    * </ul>
+    * The test file is 4.5 times the size of the buffer, in order to test
+    * paging between buffers, and using files that end in the middle of a
+    * buffer. A constant seed value is used for the random number generator,
+    * to ensure any bugs are reproduceable.
+    *
+    * @param filename    the name of the test file to generate.
+    * @param bufferSize  the size of the buffer to use.
+    */
+   public static void testBytes( String filename, int bufferSize ) {
+
+      System.out.println( "\nTesting byte operations..." );
+      int newFileSize = (int)(bufferSize * 4.5 );
+
+      try {
+
+         // Create a test file.
+         RandomAccessFile outFile = new RandomAccessFile( filename,
+            RandomAccessFile.WRITE |
+            RandomAccessFile.CREATE, bufferSize );
+         try {
+            Random random = new Random( 0 );
+            byte b = 0;
+            for( int i = 0; i < newFileSize; i++ ) {
+               b = (byte)(random.nextInt( ) % 256);
+               outFile.writeByte( b );
+            }
+         } finally {
+            outFile.close( );
+         }
+
+         // Check that the file length is correct.
+         if( (new File( filename )).length( ) == newFileSize )
+            System.out.println( ". File size correct (" + newFileSize + ")." );
+         else
+            System.out.println( "X New file size incorrect (should be " + newFileSize +
+                                ", but is " + (new File( filename )).length( ) + ")." );
+
+         // Read the file, verify and modify its contents.
+         RandomAccessFile inoutFile = new RandomAccessFile( filename,
+            RandomAccessFile.READ |
+            RandomAccessFile.WRITE, bufferSize );
+
+         boolean verified = true;
+         int byteNo = 0;
+         try {
+
+            // Read each byte in the file.
+            Random random = new Random( 0 );
+            byte b = 0;
+            for( byteNo = 0; byteNo < newFileSize; byteNo++ ) {
+               b = (byte)(random.nextInt( ) % 256);
+               byte currentByte = inoutFile.readByte( );
+
+               // Check the value is correct.
+               if( currentByte != b )
+                  verified = false;
+
+               // Modify selected values.
+               if( currentByte >=128 ) {
+                  inoutFile.seek( inoutFile.getFilePointer( ) - 1 );
+                  inoutFile.writeByte( 0 );
+               }
+            }
+
+            // Check the EOF is correctly trapped.
+            boolean foundEOF = false;
+            try {
+               inoutFile.readByte( );
+            } catch( EOFException e ) {
+               foundEOF = true;
+            }
+            if( foundEOF )
+               System.err.println( ". EOF found correctly" );
+            else
+               System.err.println( "X No EOF found." );
+
+         // Trace a premature EOF.
+         } catch( EOFException e ) {
+            e.printStackTrace( );
+            System.err.println( "    At byte " + byteNo );
+         } finally {
+            inoutFile.close( );
+         }
+
+         // Check that the read was verified.
+         if( verified )
+            System.out.println( ". Read/Write verified" );
+         else
+            System.out.println( "X Read/Write verification failed" );
+
+         // Read the file and verify contents.
+         RandomAccessFile inFile = new RandomAccessFile( filename,
+            RandomAccessFile.READ, bufferSize );
+
+         verified = true;
+         byteNo = 0;
+         try {
+
+            // Read each byte in the file.
+            Random random = new Random( 0 );
+            byte b = 0;
+            for( byteNo = 0; byteNo < newFileSize; byteNo++ ) {
+               b = (byte)(random.nextInt( ) % 256);
+               byte currentByte = inFile.readByte( );
+
+               // Account for the modification.
+               if( currentByte >= 128 )
+                  currentByte = 0;
+
+               // Check the byte's value.
+               if( currentByte != b )
+                  verified = false;
+            }
+
+         // Trap a premature EOF.
+         } catch( EOFException e ) {
+            e.printStackTrace( );
+            System.err.println( "    At byte " + byteNo );
+         } finally {
+            inFile.close( );
+         }
+
+         // Check that the read was verified.
+         if( verified )
+            System.out.println( ". Update verified" );
+         else
+            System.out.println( "X Update verification failed" );
+
+      } catch( Exception e ) {
+         e.printStackTrace( );
+      }
+   }
+
+   /**
+    * Test the block operations of the RandomAccessFile class. These
+    * are the methods that read/write blocks of data. The following checks
+    * are made:
+    * <ul>
+    * <li>Writing blocks of data that are smaller than the buffer size.
+    * <li>Writing blocks of data that are larger than the buffer size.
+    * <li>Checking the size of the file is correct.
+    * <li>Reading small blocks of the file back in and verifying its contents.
+    * <li>Reading large blocks of the file back in and verifying its contents.
+    * </ul>
+    *
+    * @param filename    the name of the test file to generate.
+    */
+   public static void testBlocks( String filename ) {
+
+      System.err.println( "\nTesting block operations..." );
+
+      // Generate the data.
+      int bufferSize = 10;
+      byte data[] = new byte[256];
+      for( int i = 0; i < data.length; i++ )
+         data[i] = (byte)(i % 256);
+
+      try {
+
+         // Write the data in small and large blocks.
+         RandomAccessFile outFile = new RandomAccessFile(
+                               filename, RandomAccessFile.WRITE |
+                               RandomAccessFile.CREATE, bufferSize );
+         for( int i = 0; i < data.length; ) {
+            int blockSize = (i < data.length / 2) ? 3 :
+                                                    13 ;
+            blockSize = (i + blockSize >= data.length) ? (data.length - i) :
+                                                         blockSize;
+            outFile.write( data, i, blockSize );
+            i += blockSize;
+         }
+
+         outFile.close( );
+
+         // Check that the file length is correct.
+         if( (new File( filename )).length( ) != data.length )
+            System.out.println( "X New file size incorrect (should be " + data.length +
+                                ", but is " + (new File( filename )).length( ) + ")." );
+         else
+            System.out.println( ". File size correct (" + data.length + ")." );
+
+         // Reopen the file for reading.
+         RandomAccessFile inFile = new RandomAccessFile(
+                        filename, RandomAccessFile.READ, bufferSize );
+
+         // Read and check random small blocks of data.
+         boolean verified = true;
+         int firstFailure = 256;
+         Random random = new Random( 0 );
+         byte block[] = new byte[(int)(bufferSize * 0.5)];
+         for( int i = 0; i < 100; i++ ) {
+            int index = Math.abs( random.nextInt( ) ) % (data.length - block.length);
+            inFile.seek( index );
+            inFile.read( block );
+
+            // Verify the block of data.
+            for( int j = 0; j < block.length; j++ ) {
+               if( block[j] != data[index + j] ) {
+                  verified = false;
+                  if( index + j < firstFailure )
+                     firstFailure = index + j;
+               }
+            }
+         }
+         if( verified )
+            System.err.println( ". Reading small blocks verified." );
+         else
+            System.err.println( "X Reading small blocks failed (byte " + firstFailure + ")." );
+
+         // Read and check random large (bigger than the bufferSize) blocks
+         // of data.
+         verified = true;
+         random = new Random( 0 );
+         block = new byte[(int)(bufferSize * 1.5)];
+         for( int i = 0; i < 100; i++ ) {
+            int index = Math.abs( random.nextInt( ) ) % (data.length - block.length);
+            inFile.seek( index );
+            inFile.read( block );
+
+            // Verify the block of data.
+            for( int j = 0; j < block.length; j++ ) {
+               if( block[j] != data[j + index] )
+                  verified = false;
+            }
+         }
+         if( verified )
+            System.err.println( ". Reading large blocks verified." );
+         else
+            System.err.println( "X Reading large blocks failed." );
+
+         // Close the input file.
+         inFile.close( );
+
+      } catch( Exception e ) {
+         e.printStackTrace( );
+      }
+
+   }
+
+   /**
+    * Benchmark the performance of the new RandomAccessFile
+    * class. Its speed is compared to that of a
+    * java.io.RandomAccessFile, based on reading and writing a test
+    * file, byte by byte.
+    *
+    * @param filename    the name of the test file.
+    * @param bufferSize the buffer size to use.  */
+   public static void benchmark( String filename, int bufferSize ) {
+      System.out.println( "\nBenchmarking..." );
+
+      // Start the clock, and open a file for reading and a file for writing.
+      long time = (new Date( )).getTime( );
+      try {
+         RandomAccessFile inFile = new RandomAccessFile( filename,
+            RandomAccessFile.READ, bufferSize );
+         RandomAccessFile outFile = new RandomAccessFile( "temp.data",
+            RandomAccessFile.WRITE |
+            RandomAccessFile.CREATE, bufferSize );
+
+         // Copy one file to the other.
+         try {
+
+            while( true ) {
+               outFile.writeByte( inFile.readByte( ) );
+            }
+
+         } catch( EOFException e ) {
+         } catch( IOException e ) {
+            e.printStackTrace( );
+         } finally {
+            inFile.close( );
+            outFile.close( );
+         }
+         System.out.println( ". RandomAccessFile elapsed time=" +
+                             ((new Date( )).getTime( ) - time) );
+
+         // Restart the clock, and open RandomAccessFiles for reading and writing.
+         time = (new Date( )).getTime( );
+         java.io.RandomAccessFile inFile2 = new java.io.RandomAccessFile( filename, "r" );
+         java.io.RandomAccessFile outFile2 = new java.io.RandomAccessFile( "temp.data", "rw" );
+
+         // Copy one file to the other.
+         try {
+
+            while( true ) {
+               outFile2.writeByte( inFile2.readByte( ) );
+            }
+
+         } catch( EOFException e ) {
+         } catch( IOException e ) {
+            e.printStackTrace( );
+         } finally {
+            inFile2.close( );
+            outFile2.close( );
+         }
+
+      } catch( Exception e ) {
+         e.printStackTrace( );
+      }
+      System.out.println( ". java.io.RandomAccessFile elapsed time=" + ((new Date( )).getTime( ) - time) );
+   }
+
+   /**
+    * Test the RandomAccessFile class. This involves testing the byte
+    * methods, the block methods, and benchmarking the performance. By appending
+    * 'test' or 'benchmark' to the command-line, it can be limited to the tests
+    * or benchmarking alone. The test filename is only used for the benchmarking,
+    * the other tests create a file called "temp.data" in the current directory.
+    * Note that the size of the buffer determines the size of the test file
+    * (which is 4.5 times the size of the buffer).
+    *
+    * @param argv  Usage: <testFilename> [bufferSize] [test | benchmark]
+    * @see #testBytes(String filename, int bufferSize)
+    * @see #testBlocks(String filename)
+    * @see #benchmark(String filename, int bufferSize)
+    */
+   public static void main( String argv[] ) {
+
+      int defaultPageSize = 4096;
+
+      // Parse the command-line arguments.
+      String filename = null;
+      int bufferSize = 0;
+      boolean test = true;
+      boolean benchmark = true;
+      if( argv.length < 1 ) {
+         System.err.println( "Usage: RandomAccessFile <filename> [buffer.length] [benchmark | test]" );
+         System.exit( -1 );
+      } else if( argv.length < 2 ) {
+         filename = argv[0];
+         bufferSize = defaultPageSize;
+      } else if( argv.length < 3 ) {
+         filename = argv[0];
+         bufferSize = Integer.parseInt( argv[1] );
+      } else {
+         filename = argv[0];
+         bufferSize = Integer.parseInt( argv[1] );
+         if( argv[2].equals( "benchmark" ) )
+            test = false;
+         else if( argv[2].equals( "test" ) )
+            benchmark = false;
+      }
+
+      System.out.println( "\nRandomAccessFile\n" +
+                            "========================" );
+      System.out.println( "filename=" + filename +
+                          ", bufferSize=" + bufferSize );
+      System.out.println( "totalMemory=" +
+         (Runtime.getRuntime( ).totalMemory( ) / 1000) + "k" +
+         " freeMemory=" + (Runtime.getRuntime( ).freeMemory( ) / 1000) + "k" );
+
+      if( test ) {
+         RandomAccessFile.testBytes( "temp.data", bufferSize );
+         RandomAccessFile.testBlocks( "temp.data" );
+      }
+      if( benchmark ) {
+         RandomAccessFile.benchmark( filename, bufferSize );
+      }
+
+      System.out.println( "\nEND" );
+   }
+
+}
+
diff --git a/ucar/netcdf/RemoteAccessorImpl.java b/ucar/netcdf/RemoteAccessorImpl.java
new file mode 100644
index 0000000..9c142d8
--- /dev/null
+++ b/ucar/netcdf/RemoteAccessorImpl.java
@@ -0,0 +1,360 @@
+// $Id: RemoteAccessorImpl.java,v 1.4 2002-05-29 18:31:36 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.multiarray.Accessor;
+import ucar.multiarray.RemoteAccessor;
+import java.io.IOException;
+import ucar.multiarray.MultiArray;
+import java.rmi.RemoteException;
+import java.rmi.ServerException;
+import java.rmi.server.RemoteObject;
+import java.rmi.server.LogStream;
+
+
+/**
+ * RemoteAccessorImpl is a UnicastRemoteObject (RMI service)
+ * which implements ucar.multiarray.RemoteAccessor using the proxy
+ * pattern. Accessor methods are forwarded to the adaptee and
+ * adaptee exceptions are wrapped in java.rmi.ServerException.
+ * 
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:36 $
+ */
+
+public class
+RemoteAccessorImpl
+	extends RemoteObject
+	implements RemoteAccessor
+{
+        /**
+         * Construct a UnicastRemoteObject which acts as
+	 * an Accessor proxy.
+	 * @param svr NetcdfRemoteProxyImpl which owns this.
+	 * 	May be null.
+	 * @param adaptee Accessor to which the Accessor
+	 * methods of this are forwarded.
+	 *
+	 */
+	public
+	RemoteAccessorImpl(NetcdfRemoteProxyImpl svr, Accessor adaptee)
+			throws RemoteException
+	{
+		adaptee_ = adaptee;
+		svr_ = svr;
+	}
+
+	public Object
+	get(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.get(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public boolean
+	getBoolean(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getBoolean(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public char
+	getChar(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getChar(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public byte
+	getByte(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getByte(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public short
+	getShort(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getShort(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public int
+	getInt(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getInt(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public long
+	getLong(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getLong(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public float
+	getFloat(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getFloat(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+	public double
+	getDouble(int [] index)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.getDouble(index);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("get", ioe);
+		}
+	}
+
+
+	public void
+	set(int [] index, Object value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.set(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setBoolean(int [] index, boolean value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setBoolean(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setChar(int [] index, char value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setChar(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setByte(int [] index, byte value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setByte(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setShort(int [] index, short value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setShort(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setInt(int [] index, int value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setInt(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setLong(int [] index, long value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setLong(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setFloat(int [] index, float value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setFloat(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public void
+	setDouble(int [] index, double value)
+			throws RemoteException
+	{
+		try {
+			adaptee_.setDouble(index, value);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("set", ioe);
+		}
+	}
+
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+			throws RemoteException
+	{
+		try {
+			return adaptee_.copyout(origin, shape);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("copyout", ioe);
+		}
+	}
+
+	public void
+	copyin(int [] origin, MultiArray source)
+			throws RemoteException
+	{
+		try {
+			adaptee_.copyin(origin, source);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("copyin", ioe);
+		}
+	}
+
+	public Object
+	toArray()
+			throws RemoteException
+	{
+		try {
+			return adaptee_.toArray();
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("toArray", ioe);
+		}
+	}
+
+	public Object
+	toArray(Object dst, int [] origin, int [] shape)
+			throws RemoteException
+	{
+		// TODO: Avoid sending big dst over wire
+		try {
+			return adaptee_.toArray(dst, origin, shape);
+		}
+		catch (IOException ioe)
+		{
+			throw new ServerException("toArray", ioe);
+		}
+	}
+
+	/**
+	 * @serial
+	 */
+	private final Accessor adaptee_;
+	/**
+	 * @serial
+	 */
+	private final NetcdfRemoteProxyImpl svr_;
+}
diff --git a/ucar/netcdf/RemoteNetcdf.java b/ucar/netcdf/RemoteNetcdf.java
new file mode 100644
index 0000000..8e159b4
--- /dev/null
+++ b/ucar/netcdf/RemoteNetcdf.java
@@ -0,0 +1,187 @@
+// $Id: RemoteNetcdf.java,v 1.4 2002-05-29 18:31:36 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.multiarray.Accessor;
+import ucar.multiarray.MultiArray;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.rmi.Naming;
+import java.lang.reflect.InvocationTargetException;
+
+
+/**
+ * A concrete implementation of the Netcdf interface,
+ * this class uses java rmi to access a remote Netcdf.
+ * <p>
+ * 
+ * @see Netcdf
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:36 $
+ */
+public class
+RemoteNetcdf
+	extends AbstractNetcdf
+{
+
+	/**
+	 * Get remote dataset directory
+	 * service from a given host.
+	 * Convience function which wraps java.rmi.Naming.lookup().
+	 * @param remoteHostName String host name or dotted quad
+	 * @return NetcdfService
+	 */
+	static public NetcdfService
+	getNetcdfService(String remoteHostName)
+			throws RemoteException,
+				java.rmi.NotBoundException,
+				java.net.MalformedURLException
+	{
+		String svcName = "//" + remoteHostName + "/"
+			+ NetcdfService.SVC_NAME;
+		return (NetcdfService) Naming.lookup(svcName);
+	}
+
+	/**
+	 * Given a NetcdfRemoteProxy, construct a RemoteNetcdf.
+	 * The NetcdfRemoteProxy would be obtained from a directory
+	 * service like NetcdfService.
+	 */
+	public
+	RemoteNetcdf(NetcdfRemoteProxy remote)
+			throws RemoteException
+	{
+		super(remote.getSchema(), false);
+		this.remote = remote;
+		try {
+			super.initHashtable();
+		}
+		catch (InstantiationException ie)
+		{
+			// Can't happen: Variable is concrete
+			throw new Error();
+		}
+		catch (IllegalAccessException iae)
+		{
+			// Can't happen: Variable is accessable
+			throw new Error();
+		}
+		catch (InvocationTargetException ite)
+		{
+			// all the possible target exceptions are
+			// RuntimeException
+			throw (RuntimeException)
+				ite.getTargetException();
+		}
+	}
+
+	/**
+	 * Open up a remote Netcdf by name.
+	 * The remote host needs to be running a NetcdfService
+	 * which exports the data set.
+	 * @param remoteHostName String host name or dotted quad
+	 * @param dataSetName String name of the remote Netcdf
+	 */
+	public
+	RemoteNetcdf(String remoteHostName,
+		String dataSetName)
+			throws RemoteException,
+				java.rmi.NotBoundException,
+				java.net.MalformedURLException
+	{
+		this(getNetcdfService(remoteHostName).lookup(dataSetName));
+	}
+
+	/**
+	 * Indicate that you are done with this remote Netcdf.
+	 * Allows the service to free resources.
+	 * We name this method close for symmetry with NetcdfFile.
+	 * You do not have to call this. RMI runtime will
+	 * eventually (~10 minutes?) call NetcdfRemoteProxyImpl.unreferenced()
+	 * and accomplish the same thing.
+	 * @see NetcdfRemoteProxy#release
+	 */
+	public void
+	close()
+		throws RemoteException
+	{
+		remote.release();
+	}
+
+	protected Accessor
+	ioFactory(ProtoVariable proto)
+			throws InvocationTargetException
+	{
+		try {
+			return remote.getAccessor(proto.getName());
+		}
+		catch (IOException ee)
+		{
+			throw new InvocationTargetException(ee);
+		}
+	}
+	
+	/**
+	 * Ensures that the remote resources associated with this are
+	 * released when there are no more references to it. 
+	 * @see #close()
+	 */
+	protected void
+	finalize() throws Throwable
+	{
+		super.finalize();
+		close();
+	}
+
+	private /* final */ NetcdfRemoteProxy remote;
+
+	public static void
+	main(String[] args)
+	{
+		if(args.length < 1)
+		{
+			System.out.println("test Usage: RemoteNetcdf nc_name");
+			System.exit(1);
+		}
+		final String name = args[0];
+		// else
+		try {
+			RemoteNetcdf rnc = new RemoteNetcdf("localhost", name);
+			System.out.println(rnc);
+			VariableIterator vi = rnc.iterator();
+			while(vi.hasNext())
+			{
+				Variable v = vi.next();
+				System.out.print(v.getName() + "[0, ...]: ");
+				MultiArray ma = v.copyout(new int[v.getRank()],
+					v.getLengths());
+				System.out.println(ma.get(
+					new int[ma.getRank()]));
+			}
+			rnc.close();
+		}
+		catch (Exception ee)
+		{
+			System.out.println(ee);
+			System.exit(1);
+		}
+		System.exit(0);
+	}
+}
diff --git a/ucar/netcdf/Schema.java b/ucar/netcdf/Schema.java
new file mode 100644
index 0000000..77a21a9
--- /dev/null
+++ b/ucar/netcdf/Schema.java
@@ -0,0 +1,382 @@
+// $Id: Schema.java,v 1.5 2002-05-29 18:31:37 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package ucar.netcdf;
+import java.io.Serializable;
+
+/**
+ * Schema collects the metadata which describes or defines a Netcdf.
+ * This is a set of ProtoVariable, the DimensionSet which is
+ * the union of the Dimensions used, and any global Attributes.
+ * <p>
+ * Instances are used as templates in creation of new Netcdf datasets.
+ * <p>
+ * Variable descriptions in form of ProtoVariable instances can be
+ * added to, overwritten, or deleted from a Schema. The associated set of
+ * global attributes may also be modified.
+ *
+ * @see ProtoVariable
+ * @see DimensionSet
+ * @see AttributeSet
+ * @see Netcdf
+ * @see java.util.Collection
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.5 $ $Date: 2002-05-29 18:31:37 $
+ */
+
+public class Schema implements java.io.Serializable {
+
+    /**
+     * Create an empty Schema
+     */
+    public
+    Schema()
+    {
+	this.variables = new NamedDictionary(0);
+	this.dimensions = new DimensionDictionary();
+	this.attributes = new AttributeDictionary();
+    }
+
+    /**
+     * Copy constructor.
+     *  @param sc Schema to copy from. May be empty, shouldn't be null.
+     */
+    public
+    Schema(Schema sc)
+    {
+	// Use clone instead??
+	synchronized (sc)
+	{
+		this.dimensions = new DimensionDictionary(sc.getDimensions());
+		this.variables = new NamedDictionary(sc.size());
+		for(ProtoVariableIterator iter = sc.iterator(); iter.hasNext();)
+		{
+			if(this.put(new ProtoVariable(iter.next())) != null)
+				throw new IllegalArgumentException(
+					"Duplicate variable name");
+		}
+		this.attributes = new AttributeDictionary(sc.getAttributes());
+	}
+    }
+
+    /**
+     * Create a Schema initialized by an existing Netcdf.
+     * <p>
+     * This would be the first step in making a copy of
+     * a Netcdf, making a partial copy, or using and existing
+     * Netcdf basis for another.
+     * @param nc Netcdf from which to generate the Schema.
+     */
+    public
+    Schema(Netcdf nc)
+    {
+	this.dimensions = new DimensionDictionary(nc.getDimensions());
+	this.variables = new NamedDictionary(nc.size());
+	for(VariableIterator iter = nc.iterator(); iter.hasNext();)
+	{
+		if(this.put(new ProtoVariable(iter.next())) != null)
+			throw new IllegalArgumentException(
+				"Duplicate variable name");
+	}
+	this.attributes = new AttributeDictionary(nc.getAttributes());
+    }
+
+    /**
+     * Create a Schema initialized by an array of ProtoVariable
+     * and an array of Attributes.
+     * @param varArray ProtoVariable [] to initialize the Schema.
+     *   May be null or length 0.
+     * @param attrArray ProtoVariable [] to initialize the (global) Attributes.
+     *   May be null or length 0.
+     */
+    public
+    Schema(ProtoVariable [] varArray, Attribute [] attrArray)
+    {
+	if(varArray == null) {
+		this.variables = new NamedDictionary(0);
+		this.dimensions = new DimensionDictionary();
+	}
+	else synchronized (varArray) {
+		this.dimensions = new DimensionDictionary(varArray);
+		this.variables = new NamedDictionary(varArray);
+	}
+	this.attributes = new AttributeDictionary(attrArray);
+    }
+
+/* Begin ProtoVariableSet */
+
+    /**
+     * Returns the number of ProtoVariable objects in this set
+     * @return int number of elements in the set
+     */
+    public int
+    size() {
+	return variables.size();
+    }
+
+    /**
+     * Returns ProtoVariableIterator for the elements.
+     * @return ProtoVariableIterator for the elements.
+     * @see ProtoVariableIterator
+     */
+    public ProtoVariableIterator
+    iterator() {
+	return new ProtoVariableIterator() {
+		final java.util.Enumeration ee = variables.elements();
+
+    		public boolean hasNext() {
+			return ee.hasMoreElements();
+    		}
+
+		public ProtoVariable next() {
+			return (ProtoVariable) ee.nextElement();
+		}
+
+	};
+    }
+
+    /**
+     * Returns a new Array containing the elements of this set.
+     * @return a new Array containing the elements of this set.
+     */
+    public ProtoVariable []
+    toArray() {
+	final ProtoVariable [] aa = new ProtoVariable[this.size()];
+	final ProtoVariableIterator ee = this.iterator();
+	for(int ii = 0; ee.hasNext(); ii++)
+		aa[ii] = ee.next();
+	return aa;
+    }
+
+    /**
+     * Retrieve the variable associated with the specified name.
+     * @param name String which identifies the desired variable
+     * @return the variable, or null if not found
+     */
+    public ProtoVariable
+    get(String name) {
+	return (ProtoVariable) variables.get(name);
+    }
+
+    /**
+     * Tests if the ProtoVariable identified by <code>name</code>
+     * is in this set.
+     * @param name String which identifies the desired variable
+     * @return <code>true</code> if and only if this set contains
+     * the named ProtoVariable.
+     */
+    public boolean
+    contains(String name) {
+	return variables.contains(name);
+    }
+
+    /**
+     * Tests if the argument is in this set.
+     * @param oo some Object
+     * @return <code>true</code> if and only if this set contains
+     * <code>oo</code>
+     */
+    public boolean
+    contains(Object oo) {
+	return variables.contains(oo);
+    }
+
+    /**
+     * Ensures that this set contains the specified ProtoVariable.
+     * If a different ProtoVariable with the same name, was in the set,
+     * it is returned, otherwise null is returned.
+     *
+     * @param var the ProtoVariable to be added to this set.
+     * @return ProtoVariable replaced or null if not a replacement
+     */
+    public synchronized ProtoVariable
+    put(ProtoVariable var) {
+	final ProtoVariable hit = this.get(var.getName());
+	if(hit != null)
+	{
+		if(hit == var)
+			return null; // Nothing to do
+		// else
+		this.remove(hit);
+	}
+	variables.put(var);
+	var.connectDims(dimensions);
+	return hit;
+    }
+
+
+    /**
+     * Delete the ProtoVariable specified by name from this set.
+     *
+     * @param name String identifying the ProtoVariable to be removed.
+     * @return true if the Set changed as a result of this call.
+     */
+    public synchronized boolean remove(String name) {
+	final ProtoVariable var = (ProtoVariable) variables.remove(name);
+	if(var == null)
+		return false; // not found
+	// else, reconcile dimensions
+	// TODO: better algorithm
+	for(DimensionIterator checkiter = var.getDimensionIterator();
+		checkiter.hasNext();)
+	{
+		boolean hit = false;
+		final Dimension check = checkiter.next();
+		final String checkStr = check.getName();
+		for(ProtoVariableIterator viter = this.iterator();
+				viter.hasNext();)
+		{
+			for(DimensionIterator diter
+				 = viter.next().getDimensionIterator();
+					diter.hasNext();)
+			{
+				final Dimension ref = diter.next();
+				if(ref.getName().equals(checkStr))
+				{
+					hit = true;
+					break;
+				}
+			}
+			if(hit)
+				break;
+		}
+		if(!hit)
+			dimensions.remove(checkStr);
+	}
+	return true;
+    }
+
+
+    /**
+     * Delete the ProtoVariable specified from this set.
+     *
+     * @param oo ProtoVariable to be removed.
+     * @return true if the Set changed as a result of this call.
+     */
+    public boolean remove(Object oo) {
+	if(this.contains(oo))
+	{
+		return this.remove(((Named)oo).getName());
+	}
+	return false;
+    }
+
+/* End ProtoVariableSet */
+/* Begin VSMixIn */
+
+    /**
+     * Returns the set of dimensions associated with this,
+     * the union of those used by each of the variables.
+     *
+     * @return DimensionSet containing dimensions used
+     * by any of the variables. May be empty. Won't be null.
+     */
+    public DimensionSet
+    getDimensions() {
+	return (DimensionSet) dimensions;
+    }
+
+    /**
+     * Returns the set of attributes associated with this,
+     * also know as the "global" attributes.
+     *
+     * @return AttributeSet. May be empty. Won't be null.
+     */
+    public AttributeSet
+    getAttributes() {
+	return (AttributeSet) attributes;
+    }
+
+    /**
+     * Convenience function; look up (global) Attribute by name.
+     *
+     * @param name the name of the attribute
+     * @return the attribute, or null if not found
+     */
+    public Attribute getAttribute(String name) {
+	return attributes.get(name);
+    }
+
+/* End VSMixIn */
+
+    /**
+     * Convenience function; add global attribute.
+     * @see AttributeSet#put
+     * @param attr the Attribute to be added to this set.
+     * @return Attribute replaced or null if not a replacement
+     */
+    public Attribute
+    putAttribute(Attribute attr) {
+	return attributes.put(attr);
+    }
+
+    /**
+     * Format as CDL.
+     * @param buf StringBuffer into which to write
+     */
+    public void
+    toCdl(StringBuffer buf)
+    {
+	buf.append("{\n");
+	dimensions.toCdl(buf);
+	buf.append("variables:\n");
+	for (ProtoVariableIterator iter = this.iterator();
+			iter.hasNext() ;) {
+		buf.append("\t");
+		iter.next().toCdl(buf);
+	}
+	if(attributes.size() > 0) {
+		buf.append("\n// global attributes:\n");
+		attributes.toCdl(buf);
+	}
+	buf.append("\n}");
+    }
+
+    /**
+     * @return a CDL string of this.
+     */
+    public String toString() {
+	StringBuffer buf = new StringBuffer();
+	toCdl(buf);
+	return buf.toString();
+    }
+
+    int
+    indexOf(Dimension dim)
+	{ return dimensions.indexOf(dim); }
+
+    void
+    putDimension(Dimension dim)
+	{ dimensions.initialPut(dim); }
+
+    /**
+     * @serial
+     */
+    private final DimensionDictionary dimensions;
+    /**
+     * @serial
+     */
+    private final AttributeDictionary attributes;
+    /**
+     * @serial
+     */
+    private final NamedDictionary variables;
+}
diff --git a/ucar/netcdf/UnlimitedDimension.java b/ucar/netcdf/UnlimitedDimension.java
new file mode 100644
index 0000000..f97cf3c
--- /dev/null
+++ b/ucar/netcdf/UnlimitedDimension.java
@@ -0,0 +1,138 @@
+// $Id: UnlimitedDimension.java,v 1.4 2002-05-29 18:31:37 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * A Dimension object is used to contain an array length which is
+ * named for use in multiple netcdf variables.
+ * For UnlimitedDimensions, the array length value may grow.
+ * An UnlimitedDimension may appear as the most slowly varying dimension
+ * of a Variable.
+ * <p>
+ * This class supports construction, retrieval of the name, retrieval 
+ * of the length value, and increase of the length value.
+ * <p>
+ * Instances are only distiguished by name.
+ * Override hashCode() and equals() to create this semantic.
+ * <p>
+ * @see Dimension
+ *
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:37 $
+ */
+
+public class
+UnlimitedDimension
+	extends Dimension
+{
+ /* Begin Constructors */
+
+	/**
+	 * The usual constructor defaults initial length to 0.
+	 *
+	 * @param name  String which is to be the
+	 *	name of this UnlimitedDimension
+	 */
+	public 
+	UnlimitedDimension(String name)
+	{
+		this(name, 0);
+	}
+
+	/**
+	 * Constructing from other data sets with initial length.
+	 * Package private.
+	 * 
+	 * @param name  String which is to be the
+	 *	name of this UnlimitedDimension
+	 * @param length  int which is the initial length
+	 */
+	UnlimitedDimension(String name, int length)
+	{
+		super(name, length);
+	}
+
+ /* End Constructors */
+ /* Begin Overrides */
+
+	/**
+	 * Instances are only distiguished by name.
+	 * Override super.hashCode() to be consistent with this semantic.
+	 */
+	public int
+	hashCode()
+	{
+		return getName().hashCode();
+	}
+
+	/**
+	 * Instances are only distiguished by name.
+	 * Override super.equals() to to be consistent with this semantic.
+	 */
+	public boolean
+	equals(Object oo)
+	{
+		if(this == oo) return true;
+		if((oo != null) && (oo instanceof UnlimitedDimension))
+			return getName().equals(
+				((UnlimitedDimension)oo).getName());
+		return false;
+	}
+
+	public Object
+	clone()
+	{
+		return new UnlimitedDimension(getName());
+	}
+
+	/**
+	 * Format as CDL.
+	 * @param buf StringBuffer into which to write
+	 */
+	public void
+	toCdl(StringBuffer buf)
+	{
+		buf.append(this.getName());
+		buf.append(" = UNLIMITED ;");
+		buf.append(" // (");
+		buf.append(this.getLength());
+		buf.append(" currently)");
+	}
+
+ /* End Overrides */
+
+	/**
+	 * Set the length to be at least newLength
+	 * Should be Package private.
+	 *
+	 * @param newLength int which is the minimum new length
+	 * @return int amount by which this grew to satisfy the request.
+	 */
+	synchronized public int
+	setLength(int newLength)
+	{
+		final int diff = newLength - length;
+		if(diff <= 0)
+			return 0;
+		this.length = newLength;
+		return diff;
+	}
+}
diff --git a/ucar/netcdf/Variable.java b/ucar/netcdf/Variable.java
new file mode 100644
index 0000000..e7a8980
--- /dev/null
+++ b/ucar/netcdf/Variable.java
@@ -0,0 +1,356 @@
+// $Id: Variable.java,v 1.6 2003-03-14 16:29:05 donm Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+import ucar.multiarray.Accessor;
+import ucar.multiarray.MultiArray;
+import java.io.IOException;
+
+/**
+ * Variable is a (potentially large) multi-dimensional
+ * array of primitives. The dimensions are named, allowing
+ * relationships between Variables to be expressed. Variables have names and
+ * may have descriptive attributes.
+ * <p>
+ * Objects which implement this interface exist in the context of a
+ * particular Netcdf data set. If you factor out the data access
+ * methods of this interface, leaving the descriptive "meta" information,
+ * what remains is a ProtoVariable.
+ * <p>
+ * Although there is no explicit relationship between this interface and
+ * class ProtoVariable, they share common method signatures and
+ * semantics where appropriate.
+ *
+ * @see ProtoVariable
+ * @see MultiArray
+ * @author $Author: donm $
+ * @version $Revision: 1.6 $ $Date: 2003-03-14 16:29:05 $
+ */
+
+public class
+Variable
+	implements MultiArray, Named
+{
+	
+ /* Begin Constructors */
+
+	/**
+	 * The usual constructor. Think of it as package private or
+	 * protected, let AbstactNetcdf call it for you.
+	 *
+	 * @param proto   the ProtoVariable used as metadata storage.
+	 *   Shouldn't be null.
+         *   It should be immutable over the lifetime of this object.
+         *   (If in doubt, hand this its own private copy.)
+	 */
+	public
+	Variable(ProtoVariable proto, Accessor io)
+	{
+		if(proto == null || io == null)
+			throw new NullPointerException();
+		meta = proto;
+		this.io = io;
+	}
+
+ /* End Constructors */
+ /* Begin MultiArrayInfo */
+
+	/**
+	 * Returns the Class object representing the component
+	 * type of the array.
+	 * @see ucar.multiarray.MultiArray#getComponentType
+	 * @return Class of the component type
+	 */
+	public Class
+	getComponentType()
+		{ return meta.getComponentType(); }
+
+	/**
+	 * Returns the number of dimensions.
+	 * @see ucar.multiarray.MultiArray#getRank
+	 * @return int number of dimensions 
+	 */
+	public int
+	getRank()
+		{ return meta.getRank(); }
+
+	/**
+	 * @see ucar.multiarray.MultiArray#getLengths
+	 * @return int array whose length is the rank of this
+	 * and whose elements represent the
+	 * length of each of its dimensions
+	 */
+	public int []
+	getLengths()
+		{ return meta.getLengths(); }
+
+	/**
+	 * Returns <code>true</code> if and only if the this variable can grow.
+	 * This is equivalent to saying
+	 * at least one of its dimensions is unlimited.
+	 * In the current implementation, exactly one dimension, the most
+	 * slowly varying (leftmost), can be unlimited.
+	 * @return boolean <code>true</code> iff this can grow
+	 */
+	public boolean
+	isUnlimited()
+		{ return meta.isUnlimited(); }
+
+	/**
+	 * Convenience interface; return <code>true</code>
+	 * if and only if the rank is zero.
+	 * @return boolean <code>true</code> iff rank == 0
+	 */
+	public boolean
+	isScalar()
+		{ return meta.isScalar(); }
+
+ /* End MultiArrayInfo */
+ /* Begin Variable Introspection */
+
+	/**
+	 * Returns the name of this Variable.
+	 * @return String which identifies this Variable.
+	 */
+	public String
+	getName()
+		{ return meta.getName(); }
+
+	/**
+	 * Returns a DimensionIterator of the dimensions
+	 * used by this variable. The most slowly varying (leftmost
+	 * for java and C programmers) dimension is first.
+	 * For scalar variables, the set has no elements and the iteration
+	 * is empty.
+	 * @return DimensionIterator of the elements.
+	 * @see DimensionIterator
+	 */
+	public DimensionIterator
+	getDimensionIterator() 
+		{ return meta.getDimensionIterator(); }
+
+	/**
+	 * Returns the set of attributes
+	 * associated with this. 
+	 * 
+	 * @return AttributeSet. May be empty. Won't be null.
+	 */
+	public AttributeSet
+	getAttributes()
+		{ return meta.getAttributes(); }
+
+	/**
+	 * Convenience function; look up Attribute by name.
+	 *
+	 * @param name the name of the attribute
+	 * @return the attribute, or null if not found
+	 */
+	public Attribute
+	getAttribute(String name)
+		{ return meta.getAttribute(name); }
+
+ /* End Variable Introspection */
+ /* Begin Accessor read access methods */
+
+	public Object
+	get(int [] index)
+			throws IOException
+		{ return io.get(index); }
+
+	public boolean
+	getBoolean(int [] index)
+			throws IOException
+		{ return io.getBoolean(index); }
+
+	public char
+	getChar(int [] index)
+			throws IOException
+		{ return io.getChar(index); }
+
+	public byte
+	getByte(int [] index)
+			throws IOException
+		{ return io.getByte(index); }
+
+	public short
+	getShort(int [] index)
+			throws IOException
+		{ return io.getShort(index); }
+
+	public int
+	getInt(int [] index)
+			throws IOException
+		{ return io.getInt(index); }
+
+	public long
+	getLong(int [] index)
+			throws IOException
+		{ return io.getLong(index); }
+
+	public float
+	getFloat(int [] index)
+			throws IOException
+		{ return io.getFloat(index); }
+
+	public double
+	getDouble(int [] index)
+			throws IOException
+		{ return io.getDouble(index); }
+
+ /* End Accessor read access methods */
+ /* Begin Accessor write access methods */
+
+    	public void
+	set(int [] index, Object value)
+			throws IOException
+		{ io.set(index, value); }
+
+	public void
+	setBoolean(int [] index, boolean value)
+			throws IOException
+		{ io.setBoolean(index, value); }
+
+	public void
+	setChar(int [] index, char value)
+			throws IOException
+		{ io.setChar(index, value); }
+
+	public void
+	setByte(int [] index, byte value)
+			throws IOException
+		{ io.setByte(index, value); }
+
+	public void
+	setShort(int [] index, short value)
+			throws IOException
+		{ io.setShort(index, value); }
+
+	public void
+	setInt(int [] index, int value)
+			throws IOException
+		{ io.setInt(index, value); }
+
+	public void
+	setLong(int [] index, long value)
+			throws IOException
+		{ io.setLong(index, value); }
+
+	public void
+	setFloat(int [] index, float value)
+			throws IOException
+		{ io.setFloat(index, value); }
+
+	public void
+	setDouble(int [] index, double value)
+			throws IOException
+		{ io.setDouble(index, value); }
+
+ /* End Accessor write access methods */
+ /* Begin Variable aggregate access */
+
+	public MultiArray
+	copyout(int [] origin, int [] shape)
+			throws IOException
+	{
+		if(shape.length != getRank())
+			throw new IllegalArgumentException("rank mismatch");
+		// TODO vet shape elements
+//		ucar.unidata.util.LogUtil.call1 ("Variable.copyout", " io=" + io.getClass().getName());
+		MultiArray ma = io.copyout(origin, shape);
+		//		ucar.unidata.util.LogUtil.call2 ("Variable.copyout");
+		return ma;
+	}
+
+	public void
+	copyin(int [] origin, MultiArray data)
+			throws IOException
+	{
+		if(data.getRank() != getRank())
+			throw new IllegalArgumentException(data.getRank()+" != "+getRank());
+
+		if( data.getComponentType() != getComponentType())
+			throw new IllegalArgumentException(data.getComponentType().getName()+" != "+getComponentType().getName());
+
+		// TODO vet shape elements
+		io.copyin(origin, data);
+	}
+
+	public Object
+	toArray()
+			throws IOException
+	{
+		return io.toArray();
+	}
+
+	public Object
+	    getStorage ()
+	{
+	    try {
+		return toArray ();
+	    } catch (IOException ioe) {
+		System.err.println ("Error:" + ioe);
+	    }
+	    return null;
+	}
+
+	public Object
+	toArray(Object dst, int [] origin, int [] shape)
+			throws IOException
+	{
+		return io.toArray(dst, origin, shape);
+	}
+
+ /* End Variable aggregate access */
+
+	/**
+	  * Format as CDL.
+	  * @param buf StringBuffer into which to write
+	  */
+	public void
+	toCdl(StringBuffer buf)
+		{ meta.toCdl(buf); }
+
+	/**
+	  * @return a CDL string of this.
+	  */
+	public String
+	toString()
+	{
+		return meta.toString(); // TODO
+	}
+    
+	/**
+	 * Ensure that the dimensions referenced by this
+	 * are members of the specified Dictionary.
+	 * This may modify dimArray elements to reference
+	 * different (equivalent) dimension instances.
+	 *
+	 * package private
+	 *
+	 */
+	void
+	connectDims(DimensionDictionary dimensions)
+		{ meta.connectDims(dimensions); }
+	
+ /* implementation */
+
+        final ProtoVariable meta;
+        final Accessor io;
+}
diff --git a/ucar/netcdf/VariableIterator.java b/ucar/netcdf/VariableIterator.java
new file mode 100644
index 0000000..689bc11
--- /dev/null
+++ b/ucar/netcdf/VariableIterator.java
@@ -0,0 +1,41 @@
+// $Id: VariableIterator.java,v 1.4 2002-05-29 18:31:37 steve Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.netcdf;
+
+/**
+ * Type specific Iterator.
+ * Use the Iterator methods to fetch elements sequentially.
+ * @see java.util.Iterator
+ * @author $Author: steve $
+ * @version $Revision: 1.4 $ $Date: 2002-05-29 18:31:37 $
+ */
+public interface VariableIterator {
+    /**
+     * Returns <code>true</code> if there are more elements.
+     */
+    boolean hasNext();
+    /**
+     * Returns the next element. Calls to this
+     * method will step through successive elements.
+     * @exception java.util.NoSuchElementException If no more elements exist.
+     */
+    Variable next();
+}
diff --git a/ucar/netcdf/overview.html b/ucar/netcdf/overview.html
new file mode 100644
index 0000000..665fecc
--- /dev/null
+++ b/ucar/netcdf/overview.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML PUBLIC "-//Netscape Comm. Corp.//DTD HTML//EN">
+<HTML>
+<HEAD>
+    <!-- SGI_COMMENT COSMOCREATE -->
+    <!-- SGI_COMMENT VERSION NUMBER="1.0" -->
+    <!-- $Id: overview.html,v 1.1.1.1 2000-08-28 21:42:24 dglo Exp $ -->
+    <TITLE>UCAR Netcdf API Overview</TITLE>
+</HEAD>
+<BODY>
+<CENTER><H2 ALIGN="CENTER">
+UCAR Netcdf API Overview</H2>
+</CENTER><P>
+The Netcdf API provides an abstraction for sampled functions between 
+multidimensional spaces. Samplings of the domain and range are 
+represented as multidimensional arrays of values called variables (see 
+ucar.multiarray). The functional relationship between the elements of 
+the domain and the range is maintained through the use of shared named 
+dimensions. When two variables share a dimension, there exists a 
+mapping between them.</P>
+<P>
+Perhaps an example would be illuminating. Suppose we wish to model a 
+curve in the plane with N samples. We would create a dimension named, 
+say "samples" whose length is N. We would then create two 
+variables, say "x" and "y" which each have a single 
+dimension, "samples". The relationship between elements of x 
+and those of y is made explicit through the shared dimension.</P>
+<P>
+An important feature of Netcdf is that the individual variables may 
+have descriptive metadata, called attributes, associated with them. 
+This might include characteristics such as the unit of measurement (as 
+a string), identification of values which are to have special 
+interpretation, or specifying the precision of data. The dataset has a 
+whole may also have attributes.</P>
+<P>
+The Netcdf API includes the following basic elements</P>
+<UL>
+    <LI>
+    <B>Class <A HREF="ucar.netcdf.Dimension.html">Dimension</A></B><A
+     HREF="ucar.netcdf.Dimension.html"></A> - A named integer value. 
+    Used in the construction of ProtoVariable; specifies the shape of 
+    Variables and the functional relationship between Variables 
+    <LI>
+    <B>Class <A HREF="ucar.netcdf.Attribute.html">Attribute</A></B><A
+     HREF="ucar.netcdf.Attribute.html"></A> - A name value pair. 
+    Contains the metadata 
+    <LI>
+    <B>Class <A HREF="ucar.netcdf.ProtoVariable.html">ProtoVariable</A> </B>- 
+    Used as prototype in the construction of Variables. Has a name, type 
+    specification, an AttributeSet, and an array of Dimensions which 
+    specify shape 
+    <LI>
+    <B>Class <A HREF="ucar.netcdf.Variable.html">Variable</A></B><A
+     HREF="ucar.netcdf.Variable.html"></A> - A named MultiArray with 
+    Attributes. Has an AttributeSet, and a DimensionIterator (see below). 
+    Instances with working i/o functionality generally exist in the context 
+    of a Netcdf data set.
+</UL>
+<P>
+Type specific containers and iterators of these are visible as the 
+following interfaces. Each of these have similar interfaces except for 
+type of the object contained or iterated. It might be useful to think 
+of these as analogous to template class instanciations.</P>
+<UL>
+    <LI>
+    <B>Interface <A HREF="ucar.netcdf.DimensionSet.html">DimensionSet</A>, <A
+     HREF="ucar.netcdf.DimensionIterator.html">DimensionIterator</A></B><A
+     HREF="ucar.netcdf.DimensionIterator.html"></A> 
+    <LI>
+    <B>Interface <A HREF="ucar.netcdf.AttributeSet.html">AttributeSet</A>, <A
+     HREF="ucar.netcdf.AttributeIterator.html">AttributeIterator</A></B><A
+     HREF="ucar.netcdf.AttributeIterator.html"></A> 
+    <LI>
+    <B><A HREF="ucar.netcdf.ProtoVariable.html">ProtoVariableIterator</A></B><A
+     HREF="ucar.netcdf.ProtoVariable.html"> </A>
+    <LI>
+    <B><A HREF="ucar.netcdf.VariableInfoIterator.html">VariableIterator</A></B><A
+     HREF="ucar.netcdf.VariableInfoIterator.html"> </A>
+</UL>
+<P>
+At the highest level we have two concrete collections and an interface.</P>
+<UL>
+    <LI>
+    <B>Class <A HREF="ucar.netcdf.Schema.html">Schema</A> </B>- 
+    Implements set of ProtoVariable, has a DimensionSet and an 
+    AttributeSet. Used as a prototype in the construction of concrete 
+    Netcdf datasets. 
+    <LI>
+    <B>Interface <A HREF="ucar.netcdf.Netcdf.html">Netcdf</A></B><A
+     HREF="ucar.netcdf.Netcdf.html"></A> - Extends set of Variable, has 
+    a DimensionSet and an AttributeSet. The top level interface of this API.
+    <LI>
+    <B>Abstract Class <A HREF="ucar.netcdf.AbstractNetcdf.html">AbstractNetcdf</A>
+     </B>- Partial implemention of interface Netcdf. For use by 
+    implementors, not most clients.
+    <LI>
+    <B>Class <A HREF="ucar.netcdf.NetcdfFile.html">NetcdfFile</A></B><A
+     HREF="ucar.netcdf.NetcdfFile.html"></A> - Extends AbstractNetcdf. 
+    Provides connnection to Netcdf version 1 files. 
+</UL>
+</BODY>
+</HTML>
diff --git a/ucar/netcdf/package.html b/ucar/netcdf/package.html
new file mode 100644
index 0000000..8ba02c1
--- /dev/null
+++ b/ucar/netcdf/package.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<HEAD>
+    <!-- SGI_COMMENT COSMOCREATE -->
+    <!-- SGI_COMMENT VERSION NUMBER="1.0.2" -->
+    <!-- $Id: package.html,v 1.3 2001-08-28 19:12:51 steve Exp $ -->
+    <TITLE>UCAR NetCDF API Overview</TITLE>
+</HEAD>
+<BODY>
+
+Provides an abstraction for sampled functions between 
+multidimensional spaces. Samplings of the domain and range are 
+represented as multidimensional arrays of values called variables (see 
+ucar.multiarray). The functional relationship between the elements of 
+the domain and the range is maintained through the use of shared named 
+dimensions.</P>
+<P>
+For example, suppose we wish to model a 
+curve in the plane with N samples. We might create a dimension named
+"samples" whose length is N. We would then create two 
+variables, say "x" and "y" which each have a single 
+dimension, "samples". The relationship between elements of x 
+and those of y is made explicit through the shared dimension.</P>
+<P>
+An important feature of netCDF is that the individual variables may 
+have descriptive metadata, called attributes, associated with them. 
+This might include characteristics such as the unit of measurement (as 
+a string), identification of values which are to have special 
+interpretation, or specifying the precision of data. The dataset as a 
+whole may also have attributes, such as a string describing the
+pedigree of the data.</P>
+<P>
+The ucar.netcdf includes the following basic elements</P>
+<UL>
+    <LI>
+    <B>Class <A HREF="Dimension.html">Dimension</A></B> - A 
+    named integer value. Used in the construction of ProtoVariable; 
+    specifies the shape of Variables and functional relationships
+    among Variables 
+    <LI>
+    <B>Class <A HREF="Attribute.html">Attribute</A></B> - A 
+    name value pair. Contains the metadata 
+    <LI>
+    <B>Class <A HREF="ProtoVariable.html">ProtoVariable</A> </B>- 
+    Used as prototype in the construction of Variables. Has a name, type 
+    specification, an AttributeSet, and an array of Dimensions that
+    specify its shape 
+    <LI>
+    <B>Class <A HREF="Variable.html">Variable</A></B> - A 
+    named MultiArray with Attributes. Has an AttributeSet, and a 
+    DimensionIterator (see below). Instances with working I/O functionality 
+    generally exist in the context of a Netcdf data set. 
+</UL>
+<P>
+Type specific containers and iterators of these are visible as the 
+following interfaces. Each of these have similar interfaces except for 
+type of the object contained or iterated. It might be useful to think 
+of these as analogous to template class instantiations.</P>
+<UL>
+    <LI>
+    <B>Interface <A HREF="DimensionSet.html">DimensionSet</A>, <A
+     HREF="DimensionIterator.html">DimensionIterator</A></B>
+     
+    <LI>
+    <B>Interface <A HREF="AttributeSet.html">AttributeSet</A>, <A
+     HREF="AttributeIterator.html">AttributeIterator</A></B>
+     
+    <LI>
+    <A HREF="ProtoVariableIterator.html"><B>ProtoVariableIterator</B>
+     </A>
+    <LI>
+    <A HREF="VariableIterator.html"><B>VariableIterator</B>
+     </A>
+</UL>
+<P>
+At the highest level we have two concrete collections and an interface.</P>
+<UL>
+    <LI>
+    <B>Class <A HREF="Schema.html">Schema</A> </B>- 
+    Implements set of ProtoVariable, has a DimensionSet and an 
+    AttributeSet. Used as a prototype in the construction of 
+    Netcdf datasets. 
+    <LI>
+    <B>Interface <A HREF="Netcdf.html">Netcdf</A></B> - 
+    Extends set of Variable, has a DimensionSet and an AttributeSet. The 
+    top level interface of this API. 
+    <LI>
+    <B>Abstract Class <A HREF="AbstractNetcdf.html">AbstractNetcdf</A>
+     </B>- Partial implementation of interface Netcdf. For use by 
+    implementors, not most clients. 
+    <LI>
+    <B>Class <A HREF="NetcdfFile.html">NetcdfFile</A></B> - 
+    Extends AbstractNetcdf. Provides connection to netCDF version 1 files. 
+</UL>
+<P>
+Annotated <A
+ HREF="http://www.unidata.ucar.edu/packages/netcdf/java/examples/">examples</A>
+ are available from our web site.</P>
+</BODY>
+</HTML>
diff --git a/ucar/tests/TestNetcdf.java b/ucar/tests/TestNetcdf.java
new file mode 100644
index 0000000..f689bc9
--- /dev/null
+++ b/ucar/tests/TestNetcdf.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 1997, University Corporation for Atmospheric Research
+ * See COPYRIGHT file for copying and redistribution conditions.
+ */
+
+
+package ucar.tests;
+import ucar.netcdf.*;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import ucar.multiarray.MultiArray;
+import ucar.multiarray.MultiArrayImpl;
+import ucar.multiarray.ArrayMultiArray;
+import ucar.multiarray.MultiArrayProxy;
+import ucar.multiarray.SliceMap;
+
+
+/**
+ * @author $Author: dglo $
+ * @version $Revision: 1.2 $ $Date: 2000-08-30 18:55:52 $
+ */
+
+public class TestNetcdf {
+
+public static String
+MultiArrayToString(MultiArray ma) {
+	StringBuffer buf = new StringBuffer();
+	final int rank = ma.getRank();
+	if (rank > 0)
+	{
+		buf.append("{\n\t");
+		final int [] dims = ma.getLengths();
+		final int last = dims[0] -1;
+		for(int ii = 0; ii <= last; ii++)
+		{
+			final MultiArray inner =
+				new MultiArrayProxy(ma,
+					new SliceMap(0, ii));
+			buf.append(MultiArrayToString(inner));
+			if(ii != last)
+				buf.append(", ");
+		}
+		buf.append("\n}");
+	}
+	else
+	{
+		try {
+		buf.append(ma.get((int [])null));
+		} catch (IOException ee) {}
+	}
+	return buf.toString();
+}
+
+
+public static void
+main(String[] args) {
+
+	String fname;
+	if(args.length == 0) {
+		fname = new String("t.nc");
+	}
+	else
+	{
+		fname = args[args.length -1];
+	}
+	System.out.print("path: ");
+	System.out.println(fname);
+
+	try {
+		Schema schema = new Schema();
+
+		// scalar variable
+		schema.put( new ProtoVariable("t1",
+			Integer.TYPE,
+			(Dimension [])null));
+ 
+
+		UnlimitedDimension timeD = new UnlimitedDimension("time");
+		Dimension latD = new Dimension("lat", 45);
+		Dimension lonD = new Dimension("lon", 90);
+		Dimension hgtD = new Dimension("level", 12);
+
+		schema.put( new ProtoVariable(timeD.getName(), Double.TYPE,
+			 timeD));
+		schema.put( new ProtoVariable(latD.getName(), Byte.TYPE,
+			 latD));
+		schema.put( new ProtoVariable(lonD.getName(), Short.TYPE,
+			 lonD));
+		schema.put(new ProtoVariable(hgtD.getName(), Integer.TYPE,
+			 hgtD));
+
+		ProtoVariable temperatureV;
+		{
+		Dimension[] Tdims = new Dimension[4];
+		Tdims[0] = timeD;
+		Tdims[1] = hgtD;
+		Tdims[2] = latD;
+		Tdims[3] = lonD;
+
+		temperatureV = new ProtoVariable("temperature",
+				Float.TYPE,
+				Tdims);
+
+		AttributeSet Vattrs = temperatureV.getAttributes();
+		Vattrs.put( new Attribute("units", "degree_Celsius"));
+		double [] vr = { -100., 200.};
+		Vattrs.put( new Attribute("valid_range", vr));
+		Vattrs.put( new Attribute("missing_value", new Double(-999)));
+		Vattrs.put( new Attribute("_FillValue", -9999));
+
+		schema.put(temperatureV);
+		}
+
+		schema.putAttribute(new Attribute("conventions", "none"));
+
+		NetcdfFile nc =
+			 new NetcdfFile(fname, true, true, schema);
+	/*	System.out.println(nc); */
+
+		{
+			final int nlats = latD.getLength();
+			byte [] lats = new byte[nlats];
+			byte lat = (byte)(-nlats/2);
+			for(int ii = 0; ii < nlats; ii++, lat++)
+				lats[ii] = lat;
+			int [] origin = {0};
+			nc.get(latD.getName()).copyin(origin,
+				new ArrayMultiArray(lats));
+		}
+		{
+			final int nlons = lonD.getLength();
+			short [] lons = new short[nlons];
+			short lon = (short)(-(nlons/2 -1));
+			for(int ii = 0; ii < nlons; ii++, lon++)
+				lons[ii] = lon;
+			int [] origin = {0};
+			nc.get(lonD.getName()).copyin(origin,
+				new ArrayMultiArray(lons));
+		}
+		{
+			int [] hgts = {1000, 925, 850, 700, 500,
+				 400, 300, 250, 200, 150, 100, 50};
+			int [] origin = {0};
+			nc.get(hgtD.getName()).copyin(origin,
+				new ArrayMultiArray(hgts));
+		}
+
+		{
+			Variable tao = nc.get(timeD.getName());
+			double time = 0.;
+			int [] index = { 0, 0, 0, 0};
+			for(; index[0] < 5; index[0]++, time += 60.) {
+				if(index[0] == 1) // don't fill record 1
+					nc.setFill(false);
+				tao.setDouble(index, time);
+				if(index[0] == 1)
+					nc.setFill(true);
+			}
+		}
+
+		nc.close();
+
+
+	} catch (Exception ee) {
+		System.out.println(ee);
+	}
+
+	try {
+		NetcdfFile nc =
+			 new NetcdfFile(fname, false);
+		System.out.println(nc);
+
+		Variable timeV = nc.get("time");
+		int[] origin = new int[timeV.getRank()];
+		int[] extent  = timeV.getLengths();
+		MultiArray times = timeV.copyout(origin, extent);
+		System.out.println(MultiArrayToString(times));
+
+		nc.close();
+
+	} catch (Exception ee) {
+		System.out.println(ee);
+	}
+
+// System.exit(0);
+
+	try {
+	fname = new String("test.nc");
+		NetcdfFile nc =
+			 new NetcdfFile(fname, false);
+	System.out.println(nc);
+
+	
+		Variable ma = nc.get("Float");
+		int [] origin = {1, 1, 1};
+		int [] extent = {2, 3, 6};
+		MultiArray flts = ma.copyout(origin, extent);
+// System.out.println(MultiArrayToString(flts));
+
+		nc.close();
+		
+		final Schema sc2 = new Schema(nc);
+		final NetcdfFile clone = new NetcdfFile("clone.nc", true, true,
+				sc2);
+		ma = clone.get(ma.getName());
+		ma.copyin(origin, flts);
+		MultiArray cflts = ma.copyout(origin, extent);
+System.out.println(MultiArrayToString(cflts));
+		MultiArray cflts2 = new MultiArrayImpl(extent,
+			ma.toArray(new float[36], origin, extent));
+System.out.println(MultiArrayToString(cflts2));
+
+	} catch (Exception ee) {
+		System.out.println(ee);
+	}
+
+}
+}
diff --git a/ucar/tests/clone.nc b/ucar/tests/clone.nc
new file mode 100644
index 0000000..ac39adf
Binary files /dev/null and b/ucar/tests/clone.nc differ
diff --git a/ucar/tests/t.nc b/ucar/tests/t.nc
new file mode 100644
index 0000000..6a40b02
Binary files /dev/null and b/ucar/tests/t.nc differ
diff --git a/ucar/tests/test.nc b/ucar/tests/test.nc
new file mode 100644
index 0000000..3a83947
Binary files /dev/null and b/ucar/tests/test.nc differ
diff --git a/ucar/tests/test.out b/ucar/tests/test.out
new file mode 100644
index 0000000..178b7bd
--- /dev/null
+++ b/ucar/tests/test.out
@@ -0,0 +1,119 @@
+path: t.nc
+netcdf t.nc {
+dimensions:
+	time = UNLIMITED ; // (5 currently)
+	lat = 45 ;
+	lon = 90 ;
+	level = 12 ;
+variables:
+	int t1() ;
+	double time(time) ;
+	byte lat(lat) ;
+	short lon(lon) ;
+	int level(level) ;
+	float temperature(time, level, lat, lon) ;
+		temperature:units = "degree_Celsius" ;
+		temperature:valid_range = -100.0, 200.0 ;
+		temperature:missing_value = -999.0 ;
+		temperature:_FillValue = -9999.0 ;
+
+// global attributes:
+		:conventions = "none" ;
+
+}
+{
+	0.0, 60.0, 120.0, 180.0, 240.0
+}
+netcdf test.nc {
+dimensions:
+	record = UNLIMITED ; // (8 currently)
+	ixx = 7 ;
+	iyy = 8 ;
+variables:
+	byte Byte(record, ixx) ;
+		Byte:UNITS = "ones" ;
+		Byte:VALIDMIN = -127.0 ;
+		Byte:VALIDMAX = 127.0 ;
+		Byte:SCALEMIN = -127.0 ;
+		Byte:SCALEMAX = 127.0 ;
+		Byte:FIELDNAM = "Byte sized integer variable" ;
+	char Char(record, iyy) ;
+		Char:UNITS = "(unitless)" ;
+		Char:VALIDMIN = -1.0 ;
+		Char:VALIDMAX = -1.0 ;
+		Char:SCALEMIN = -1.0 ;
+		Char:SCALEMAX = -1.0 ;
+		Char:FIELDNAM = "char (string) variable" ;
+	short Short(record, iyy) ;
+		Short:UNITS = "ones" ;
+		Short:VALIDMIN = -32767.0 ;
+		Short:VALIDMAX = 32767.0 ;
+		Short:SCALEMIN = -32767.0 ;
+		Short:SCALEMAX = 32767.0 ;
+		Short:FIELDNAM = "Short variable" ;
+	int Long(ixx, iyy) ;
+		Long:UNITS = "ones" ;
+		Long:VALIDMIN = -2.147483647E9 ;
+		Long:VALIDMAX = 2.147483647E9 ;
+		Long:SCALEMIN = -2.147483647E9 ;
+		Long:SCALEMAX = 2.147483647E9 ;
+		Long:FIELDNAM = "Long Integer variable" ;
+		Long:_FillValue = -1 ;
+	float Float(record, ixx, iyy) ;
+		Float:UNITS = "flots" ;
+		Float:VALIDMIN = -2.147483647E9 ;
+		Float:VALIDMAX = 2.147483647E9 ;
+		Float:SCALEMIN = -2.147483647E9 ;
+		Float:SCALEMAX = 2.147483647E9 ;
+		Float:FIELDNAM = "Single Precision Floating Point variable" ;
+	double Double(record, ixx, iyy) ;
+		Double:UNITS = "dflots" ;
+		Double:VALIDMIN = -2.147483647E9 ;
+		Double:VALIDMAX = 2.147483647E9 ;
+		Double:SCALEMIN = -2.147483647E9 ;
+		Double:SCALEMAX = 2.147483647E9 ;
+		Double:FIELDNAM = "Double Precision Floating Point variable" ;
+		Double:_FillValue = -9999.0 ;
+
+// global attributes:
+		:TITLE = "test.nc" ;
+
+}
+{
+	{
+	{
+	65.0, 66.0, 67.0, 68.0, 69.0, 70.0
+}, {
+	73.0, 74.0, 2.7182817, 76.0, 77.0, 78.0
+}, {
+	81.0, 82.0, 83.0, 84.0, 85.0, 86.0
+}
+}, {
+	{
+	121.0, 122.0, 123.0, 124.0, 125.0, 126.0
+}, {
+	129.0, 130.0, 131.0, 132.0, 133.0, 134.0
+}, {
+	137.0, 138.0, 139.0, 140.0, 141.0, 142.0
+}
+}
+}
+{
+	{
+	{
+	65.0, 66.0, 67.0, 68.0, 69.0, 70.0
+}, {
+	73.0, 74.0, 2.7182817, 76.0, 77.0, 78.0
+}, {
+	81.0, 82.0, 83.0, 84.0, 85.0, 86.0
+}
+}, {
+	{
+	121.0, 122.0, 123.0, 124.0, 125.0, 126.0
+}, {
+	129.0, 130.0, 131.0, 132.0, 133.0, 134.0
+}, {
+	137.0, 138.0, 139.0, 140.0, 141.0, 142.0
+}
+}
+}
diff --git a/ucar/util/AbstractLogger.java b/ucar/util/AbstractLogger.java
new file mode 100644
index 0000000..994d1ed
--- /dev/null
+++ b/ucar/util/AbstractLogger.java
@@ -0,0 +1,114 @@
+// $Id: AbstractLogger.java,v 1.1.1.2 2000-08-28 21:54:47 dglo Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.util;
+import java.io.IOException;
+
+/**
+ * Partial implementation of Logger.
+ * Implements the shorthand <code>logXXXX(message)</code> methods in terms
+ * of the primitive <code>log(XXXX, message)</code> method.
+ * @see Logger
+ */
+public abstract class
+AbstractLogger
+		implements Logger
+{
+	/**
+	 * Implementation hook to deal with internal exceptions.
+	 * This implementation does nothing.	
+	 * Override this method to do something
+	 * about <code>IOException</code> in the <code>Logger</code>
+	 * shorthand methods implemented here.
+	 */
+	protected void
+	logLogException(IOException ioe, String message)
+	{
+	}
+
+
+	/**
+	 * Log the <code>message</code>
+	 * at priority <code>Logger.ERR</code>.
+	 * @see Logger#ERR
+	 */
+	public void
+	logError(String message)
+	{
+		try {
+			log(Logger.ERR, message);
+		}
+		catch (IOException ioe)
+		{
+			logLogException(ioe, message);
+		}
+	}
+
+	/**
+	 * Log the <code>message</code>
+	 * at priority <code>Logger.NOTICE</code>.
+	 * @see Logger#NOTICE
+	 */
+	public void
+	logNotice(String message)
+	{
+		try {
+			log(Logger.NOTICE, message);
+		}
+		catch (IOException ioe)
+		{
+			logLogException(ioe, message);
+		}
+	}
+
+	/**
+	 * Log the <code>message</code>
+	 * at priority <code>Logger.INFO</code>.
+	 * @see Logger#INFO
+	 */
+	public void
+	logInfo(String message)
+	{
+		try {
+			log(Logger.INFO, message);
+		}
+		catch (IOException ioe)
+		{
+			logLogException(ioe, message);
+		}
+	}
+
+	/**
+	 * Log the <code>message</code>
+	 * at priority <code>Logger.DEBUG</code>.
+	 * @see Logger#DEBUG
+	 */
+	public void
+	logDebug(String message)
+	{
+		try {
+			log(Logger.DEBUG, message);
+		}
+		catch (IOException ioe)
+		{
+			logLogException(ioe, message);
+		}
+	}
+}
diff --git a/ucar/util/Logger.java b/ucar/util/Logger.java
new file mode 100644
index 0000000..7b90167
--- /dev/null
+++ b/ucar/util/Logger.java
@@ -0,0 +1,151 @@
+// $Id: Logger.java,v 1.1.1.3 2000-08-28 21:54:47 dglo Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.util;
+import java.io.IOException;
+
+/**
+ * This interface provides logging functions for daemon applications
+ * such as servers. It looks a lot like UCAR's <code>ulog(3)</code>
+ * C language interface, which in turn looks like the UNIX
+ * <code>syslog(3C)</code> client interface.
+ * <p>	
+ * Log messages are tagged with a numeric logging level,
+ * selected from the ordered list of constants below.
+ * Higher levels are more verbose. An implementation would
+ * use the level of a message to decide where and whether to
+ * write the message.
+ * <p>
+ * The three lowest logging levels,
+ * 	<code>EMERG</code>,
+ *	<code>ALERT</code>, and
+ *	<code>CRIT</code>,
+ * should probably never be
+ * assigned by user (non-kernel or non JVM?) code.
+ * <p>
+ * Note: By default, the shorthand methods are silent
+ * in the face of internal exceptions.
+ *
+ * @author $Author: dglo $
+ * @version $Revision: 1.1.1.3 $ $Date: 2000-08-28 21:54:47 $
+ */
+public interface
+Logger
+{
+	/**
+	 * Log level for messages indicating that
+	 * the system is unusable.
+	 * Included only for syslog compatiblity.
+	 */
+	public static final int EMERG     = 0;
+	/**
+	 * Log level for messages indicating that
+	 * action must be taken immediately.
+	 * Included only for syslog compatiblity.
+	 */
+	public static final int ALERT     = 1;
+	/**
+	 * Log level for messages indicating
+	 * critical conditions.
+	 * Included only for syslogd compatiblity.
+	 */
+	public static final int CRIT      = 2;
+	/**
+	 * Log level for error messages.
+	 * Included only for syslog compatiblity.
+	 */
+	public static final int ERR       = 3;
+	/**
+	 * Log level for warnings.
+	 */
+	public static final int WARNING   = 4;
+	/**
+	 * Log level for messages indicating a
+	 * normal but significant condition.
+	 */
+	public static final int NOTICE    = 5;
+	/**
+	 * Log level for informational (verbose) messages.
+	 */
+	public static final int INFO      = 6;
+	/**
+	 * Log level for debug messages
+	 */
+	public static final int DEBUG     = 7;
+
+	/**
+	 * Control the verbosity of the implementation.
+	 * Messages tagged with level above
+	 * <code>maxLevel</code> may be discarded.
+	 */
+	public void
+	logUpTo(int maxLevel);
+
+	/**
+	 * Arrange to log the <code>message</code>
+	 * at the given <code>level</code>.
+	 *
+	 * @param level Int value which is one of
+	 * 	<code>EMERG</code>,
+	 *	<code>ALERT</code>,
+	 *	<code>CRIT</code>,
+	 *	<code>ERR</code>,
+	 *	<code>WARNING</code>,
+	 *	<code>NOTICE</code>,
+	 *	<code>INFO</code>, or
+	 *	<code>DEBUG</code>.
+	 * @param String message to be logged.
+	 */
+	public void
+	log(int level, String message)
+			throws IOException;
+
+	/**
+	 * Shorthand for <code>log(Logger.ERR, message)</code>.
+	 * @see #ERR
+	 * @see #log
+	 */
+	public void
+	logError(String message);
+
+	/**
+	 * Shorthand for <code>log(Logger.NOTICE, message)</code>.
+	 * @see #NOTICE
+	 * @see #log
+	 */
+	public void
+	logNotice(String message);
+
+	/**
+	 * Shorthand for <code>log(Logger.INFO, message)</code>.
+	 * @see #INFO
+	 * @see #log
+	 */
+	public void
+	logInfo(String message);
+
+	/**
+	 * Shorthand for <code>log(Logger.DEBUG, message)</code>.
+	 * @see #DEBUG
+	 * @see #log
+	 */
+	public void
+	logDebug(String message);
+}
diff --git a/ucar/util/RMILogger.java b/ucar/util/RMILogger.java
new file mode 100644
index 0000000..00bb326
--- /dev/null
+++ b/ucar/util/RMILogger.java
@@ -0,0 +1,119 @@
+// $Id: RMILogger.java,v 1.1.1.2 2000-08-28 21:54:47 dglo Exp $
+/*
+ * Copyright 1997-2000 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ * 
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
+ */
+package ucar.util;
+import java.io.IOException;
+import java.rmi.server.RemoteServer;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * This is a concrete implementation of the <code>Logger</code> interface
+ * which retains consistancy and interoperability with the logging
+ * done by <code>java.rmi.server.RemoteServer</code>
+ * <p>
+ * If the log level of this is set to a value greater than
+ * <code>Logger.NOTICE</code>, then rmi server logging is turned on,
+ * directed to the same output stream.
+ */
+public class
+RMILogger
+	extends AbstractLogger
+		implements Logger // redundant spec helps javadoc
+{
+	/**
+	 * Construct a logger that prints messages of
+	 * priority up to <code>maxLevel</code> on <code>logStream</code>.
+	 */
+	public
+	RMILogger(int maxLevel, OutputStream logStream)
+	{
+		maxLevel_ = maxLevel;
+		setLog(logStream);
+	}
+
+	/**
+	 * Default construct prints messages of
+	 * priority up to <code>Logger.NOTICE</code>
+	 * on <code>System.err</code>.
+	 */
+	public
+	RMILogger()
+	{
+		this(Logger.NOTICE, System.err);
+	}
+
+	private void
+	makeConsistent()
+	{
+		if(maxLevel_ > Logger.NOTICE
+				&& RemoteServer.getLog() != logStream_)
+			RemoteServer.setLog(logStream_);
+	}
+
+	/**
+	 * Set the OutputStream where log messages will be printed.
+	 * If the log level is greater than <code>Logger.NOTICE</code>,
+	 * then <code>java.rmi.server.RemoteServer.setLog(logStream)</code>
+	 * is called.
+	 * @see java.rmi.server.RemoteServer#setLog
+	 */
+	public synchronized void
+	setLog(OutputStream logStream)
+	{
+		if(logStream instanceof PrintStream)
+			logStream_ = (PrintStream) logStream;
+		else
+			logStream_ = new PrintStream(logStream);
+		makeConsistent();
+	}
+
+	/**
+	 * Control the verbosity of this Logger.
+	 * Messages tagged with level above
+	 * <code>maxLevel</code> are discarded.
+	 */
+	public synchronized void
+	logUpTo(int maxLevel)
+	{
+		maxLevel_ = maxLevel;
+		makeConsistent();
+	}
+
+	/**
+	 * Arrange to log the <code>message</code>
+	 * at the given <code>level</code>.
+	 */
+	public synchronized void
+	log(int level, String message)
+			throws IOException
+	{
+		if(level > maxLevel_)
+			return;
+		// else
+		PrintStream ps = RemoteServer.getLog();
+		if(ps == null)
+			ps = logStream_;
+		ps.println(message);
+	}
+	
+	private int maxLevel_;
+	private PrintStream logStream_;
+}
diff --git a/visad/AVControl.java b/visad/AVControl.java
new file mode 100644
index 0000000..c94cc2d
--- /dev/null
+++ b/visad/AVControl.java
@@ -0,0 +1,68 @@
+//
+// AVControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+   AVControl is the VisAD interface for AnimationControl
+   and ValueControl.<P>
+*/
+public interface AVControl {
+
+  /**
+   * remove all references to SwitchSet objects involving re
+   * @param re - DataRenderer used to select SwitchSet objects
+   */
+  void clearSwitches(DataRenderer re);
+
+  /**
+   * in future, notify listener of changes in this AVControl
+   * @param listener - ControlListener to notify
+   */
+  void addControlListener(ControlListener listener);
+
+  /**
+   * stop notifying listener of changes in this AVControl
+   * @param listener - ControlListener to stop notifying
+   */
+  void removeControlListener(ControlListener listener);
+
+  /**
+   * @return String representation of this AVControl
+   */
+  String getSaveString();
+
+  /**
+   * reconstruct this AVControl using the specified save string
+   * @param save - String representation for reconstruction
+   */
+  void setSaveString(String save)
+    throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/Action.java b/visad/Action.java
new file mode 100644
index 0000000..819a2e4
--- /dev/null
+++ b/visad/Action.java
@@ -0,0 +1,86 @@
+//
+// Action.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+   Action is the VisAD interface for runnable threads that need to be
+   notified when ThingReference objects change.  For example, this may
+   be used for a Data display or for a spreadsheet cell.<P>
+*/
+public interface Action extends ThingChangedListener {
+
+  /**
+   * Creates a link to a ThingReference.  Note that this method causes this
+   * object to register itself with the ThingReference.
+   * @param ref                   The ThingReference to which to create
+   *                              the link.  Subsequent invocation of
+   *                              <code>thingChanged(ThingChangedEvent)</code>
+   *                              causes invocation of
+   *                              <code>ref.acknowledgeThingChanged(this)</code>
+   *                              .  This method invokes <code>
+   *                              ref.addThingChangedListener(this, ...)</code>.
+   * @throws RemoteVisADException if the reference isn't a {@link
+   *                              ThingReferenceImpl}.
+   * @throws ReferenceException   if the reference has already been added.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   * @see #thingChanged(ThingChangedEvent)
+   * @see ThingReference#addThingChangedListener(ThingChangedListener, long)
+   */
+  void addReference(ThingReference ref)
+         throws VisADException, RemoteException;
+
+  /**
+   * <p>Removes a link to a ThingReference.</p>
+   *
+   * @param ref                   The reference to be removed.
+   * @throws RemoteVisADException if the reference isn't a {@link
+   *                              ThingReferenceImpl}.
+   * @throws ReferenceException   if the reference isn't a part of this
+   *                              instance.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  void removeReference(ThingReference ref)
+         throws VisADException, RemoteException;
+
+  /**
+    * delete all links to ThingReferences
+    */
+  void removeAllReferences()
+         throws VisADException, RemoteException;
+
+  /**
+    * @return String name of this Action
+    */
+  String getName()
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/ActionImpl.java b/visad/ActionImpl.java
new file mode 100644
index 0000000..4a1001f
--- /dev/null
+++ b/visad/ActionImpl.java
@@ -0,0 +1,801 @@
+//
+// ActionImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import visad.util.ThreadPool;
+
+/*
+Action - ThingReference event logic
+
+ActionImpl has Vector of ReferenceActionLinks, one per linked ThingReference
+ThingReferenceImpl has Vector of ThingChangedLinks, one per linked Action
+
+
+
+call stacks:
+  // create and send ThingChangedEvent
+  ThingReferenceImpl.incTick() calls
+    ThingChangedLink.queueThingChangedEvent('new' ThingChangedEvent e) calls
+      Action.thingChanged(e) calls
+        ReferenceActionLink.acknowledgeThingChangedEvent(e.getTick())
+
+  // get queued ThingChangedEvent
+  ActionImpl.run() calls
+    ReferenceActionLink.getThingChangedEvent() calls
+      ThingReference.acknowledgeThingChanged(Action a) calls
+        ThingChangedLink.acknowledgeThingChangedEvent()
+    ActionImpl.thingChanged(ThingChangedEvent e) calls
+      ReferenceActionLink.acknowledgeThingChangedEvent(e.getTick())
+
+  // peek at queued ThingChangedEvent
+  ActionImpl.run() calls
+    ReferenceActionLink.peekThingChangedEvent() calls
+      ThingReference.peekThingChanged() calls
+        ThingChangedLink.peekThingChangedEvent()
+*/
+
+/**
+ * ActionImpl is the abstract superclass for runnable threads that
+ * need to be notified when ThingReference objects change.<P>
+ *
+ * ActionImpl is the superclass of DisplayImpl and CellImpl.<P>
+ *
+ * ActionImpl is not Serializable and should not be copied
+ * between JVMs.<P>
+ */
+public abstract class ActionImpl implements Action, Runnable {
+
+
+
+  /**
+   * Indicates whether we print out the trace from where an action is invoked and print its run  time
+   */
+  public static final boolean TRACE_TIME =
+    Boolean.parseBoolean(System.getProperty("visad.actionimpl.tracetime",
+                                            "false"));
+
+  /**           */
+  public static final boolean TRACE_STACK =
+    Boolean.parseBoolean(System.getProperty("visad.actionimpl.tracestack",
+                                            "false"));
+
+
+  /**           */
+  private String stackTrace;
+
+
+  /** thread pool and its lock */
+  private transient static ThreadPool pool = null;
+
+  /**           */
+  private static Object poolLock = new Object();
+
+  /**           */
+  private boolean enabled = true;
+
+  /**           */
+  private Object lockEnabled = new Object();
+
+  /**           */
+  private boolean peek = false;
+
+  /**           */
+  private Thread currentActionThread = null;
+
+  /** String name, used only for debugging */
+  private String Name;
+
+  // WLH 17 Dec 2001 - get it off Thread stack
+
+  /**           */
+  private Enumeration run_links = null;
+
+  /**
+   * Vector of ReferenceActionLink-s;
+   *   ActionImpl is not Serializable, but mark as transient anyway 
+   */
+  private transient Vector LinkVector = new Vector();
+
+  /**
+   * counter used to give a unique id to each ReferenceActionLink
+   *   in LinkVector 
+   */
+  private long link_id;
+
+  /**           */
+  private boolean requeue = false;
+
+  /**
+   * construct an ActionImpl
+   * @param name - String name, used only for debugging
+   */
+  public ActionImpl(String name) {
+    // if the thread pool hasn't been initialized...
+    if (pool == null) {
+      startThreadPool();
+    }
+
+    Name = name;
+    link_id = 0;
+  }
+
+  /** used internally to create the shared Action thread pool */
+  private static void startThreadPool() {
+    synchronized (poolLock) {
+      if (pool == null) {
+        // ...fill the pool; die if pool wasn't created
+        try {
+          pool = new ThreadPool("ActionThread");
+        }
+        catch (Exception e) {
+          System.err.println(e.getClass().getName() + ": " + e.getMessage());
+          System.exit(1);
+        }
+      }
+    }
+  }
+
+
+  /**
+   * return the number of tasks in the threadpool queue
+   * @return number of queued and active tasks
+   */
+  public static int getTaskCount() {
+    if (pool == null) return 0;
+    return pool.getTaskCount();
+  }
+
+
+  /**
+   * 
+   */
+  public static void printPool() {
+    if (pool != null) {
+      pool.printPool();
+    }
+  }
+
+
+  /**
+   * destroy all threads after they've drained the job queue
+   */
+  public static void stopThreadPool() {
+    if (pool != null) {
+      pool.stopThreads();
+      pool = null;
+    }
+  }
+
+  /**
+   * increase the maximum number of Threads allowed in the ThreadPool
+   * @param num - new maximum number of Threads in ThreadPool
+   * @throws Exception - num is less than previous maximum
+   */
+  public static void setThreadPoolMaximum(int num) throws Exception {
+    if (pool == null) {
+      startThreadPool();
+    }
+    pool.setThreadMaximum(num);
+  }
+
+  /**
+   * stop activity in this ActionImpl
+   */
+  public void stop() {
+    if (LinkVector == null) return;
+    synchronized (LinkVector) {
+      Enumeration links = LinkVector.elements();
+      while(links.hasMoreElements()) {
+        ReferenceActionLink link = (ReferenceActionLink)links.nextElement();
+        try {
+          link.getThingReference().removeThingChangedListener(
+            link.getAction());
+        }
+        catch (RemoteException e) {
+        }
+        catch (VisADException e) {
+        }
+      }
+      LinkVector.removeAllElements();
+    }
+
+    // WLH 17 Dec 2001
+    if (pool != null && !pool.isTerminated()) {
+      pool.queue(this);
+    }
+    run_links = null;
+
+  }
+
+  /**
+   * @return long value of long counter used to give a unique id to
+   *         each linked ReferenceActionLink
+   */
+  synchronized long getLinkId() {
+    long i = link_id;
+    link_id++;
+    return i;
+  }
+
+  /**
+   * call setTicks() for each linked ReferenceActionLink
+   * which saves boolean flag indicating whether its incTick()
+   * has been called since last setTicks()
+   */
+  private void setTicks() {
+    synchronized (LinkVector) {
+      Enumeration links = LinkVector.elements();
+      while(links.hasMoreElements()) {
+        ReferenceActionLink link = (ReferenceActionLink)links.nextElement();
+        link.setTicks();
+      }
+    }
+  }
+
+  /**
+   * @return boolean that is disjunction (or) of flags saved
+   * in setTicks() calls to each linked ReferenceActionLink
+   */
+  public boolean checkTicks() {
+    boolean doIt = false;
+    synchronized (LinkVector) {
+      Enumeration links = LinkVector.elements();
+      while(links.hasMoreElements()) {
+        ReferenceActionLink link = (ReferenceActionLink)links.nextElement();
+        doIt |= link.checkTicks();
+      }
+    }
+    return doIt;
+  }
+
+  /**
+   * call resetTicks() for each linked ReferenceActionLink
+   * which resets boolean flag indicating whether its incTick()
+   * has been called since last setTicks()
+   */
+  private void resetTicks() {
+    synchronized (LinkVector) {
+      Enumeration links = LinkVector.elements();
+      while(links.hasMoreElements()) {
+        ReferenceActionLink link = (ReferenceActionLink)links.nextElement();
+        link.resetTicks();
+      }
+    }
+  }
+
+  /**
+   * enable activity in this ActionImpl and trigger any pending activity
+   */
+  public void enableAction() {
+// System.out.println("enableAction " + getName());
+    if (!enabled) peek = true;
+    enabled = true;
+    notifyAction();
+  }
+
+  /**
+   * disable activity in this ActionImpl and if necessary wait
+   * for end of current doAction() call
+   */
+  public void disableAction() {
+// System.out.println("disableAction " + getName());
+    enabled = false;
+    // wait for possible current run() invocation to finish
+    synchronized (lockEnabled) {
+      enabled = false; // probably not necessary, just don't trust a nop
+    }
+  }
+
+  /**
+   * Set the "enabled" state of this action.  This may be used in code like the
+   * following to ensure that the action has the same "enabled" state on leaving
+   * the code as it did on entering it:
+   * <BLOCKQUOTE>
+   * <PRE><CODE>
+   * ActionImpl action = ...;
+   * boolean wasEnabled = action.setEnabled(false);
+   * ...
+   * action.setEnabled(wasEnabled);
+   * </CODE></PRE>
+   * </BLOCKQUOTE>
+   * @param enable              The new "enabled" state for this action.
+   * @return                    The previous "enabled" state of this action.
+   */
+  public boolean setEnabled(boolean enable) {
+    boolean wasEnabled;
+    synchronized (lockEnabled) {
+      wasEnabled = enabled;
+      if (enable && !wasEnabled) {
+        enableAction();
+      }
+      else if (!enable && wasEnabled) {
+        disableAction();
+      }
+    }
+    return wasEnabled;
+  }
+
+  /**
+   * return Thread currently active in run() method of this
+   * ActionImpl, or null is run() is not active
+   *
+   * @return currently active thread or null
+   */
+  
+  public Thread getCurrentActionThread() {
+    return currentActionThread;
+  }
+
+  /**
+   * remove linked ReferenceActionLink
+   * @param link - linked ReferenceActionLink to remove
+   */
+  void handleRunDisconnectException(ReferenceActionLink link) {
+    LinkVector.removeElement(link);
+  }
+
+
+
+
+  /**
+   * invoked by a Thread from the ThreadPool whenever
+   * there is a request for activity in this ActionImpl
+   */
+  public void run() {
+
+    // Save the current thread so we can prohibit it from calling
+    // getImage.  This is thread-safe, because only one ActionImpl
+    // thread can be running at a time.
+    currentActionThread = Thread.currentThread();
+
+    synchronized (lockEnabled) {
+// if (getName() != null) System.out.println("ENABLED = " + enabled + " " + getName());
+      if (enabled) {
+        try {
+          if (peek) {
+            // WLH 17 Dec 2001 - keep run_links off Thread stack
+            synchronized (LinkVector) {
+              run_links = ((Vector)LinkVector.clone()).elements();
+            }
+            while(run_links.hasMoreElements()) {
+              ReferenceActionLink link =
+                (ReferenceActionLink)run_links.nextElement();
+
+              try {
+                link.peekThingChangedEvent();
+              }
+              catch (RemoteException re) {
+                if (!visad.collab.CollabUtil.isDisconnectException(re)) {
+                  throw re;
+                }
+
+                // remote side has died
+                handleRunDisconnectException(link);
+              }
+            }
+            run_links = null;
+            peek = false;
+          } // end if (peek)
+
+          setTicks();
+          if (checkTicks()) {
+// if (getName() != null) System.out.println("RUN " + getName());
+            long t1 = System.currentTimeMillis();
+            doAction();
+            long t2 = System.currentTimeMillis();
+            //If it took longer than 10 milliseconds then do the trace
+            if ((t2 - t1) > 10) {
+              if (TRACE_TIME) {
+                System.out.println(
+                  "Action:" + getClass().getName() + " time:" + (t2 - t1));
+              }
+              if (TRACE_STACK) {
+                String[] lines = stackTrace.split("\n");
+                for (int i = 0; i < lines.length && i < 30; i++) {
+                  if (i > 1) {
+                    System.out.println(lines[i]);
+                  }
+                }
+              }
+            }
+          }
+          // WLH 17 Dec 2001 - keep run_links off Thread stack
+          synchronized (LinkVector) {
+            run_links = ((Vector)LinkVector.clone()).elements();
+          }
+          while(run_links.hasMoreElements()) {
+            ReferenceActionLink link =
+              (ReferenceActionLink)run_links.nextElement();
+
+            ThingChangedEvent e;
+            try {
+              e = link.getThingChangedEvent();
+            }
+            catch (RemoteException re) {
+              if (!visad.collab.CollabUtil.isDisconnectException(re)) {
+                throw re;
+              }
+
+              // remote side has died
+              handleRunDisconnectException(link);
+              e = null;
+            }
+
+            if (e != null) {
+              thingChanged(e);
+            }
+          }
+          run_links = null;
+          resetTicks();
+        }
+        catch (VisADException v) {
+          v.printStackTrace();
+          throw new VisADError("Action.run: " + v.toString());
+        }
+        catch (RemoteException v) {
+          v.printStackTrace();
+          throw new VisADError("Action.run: " + v.toString());
+        }
+      } // end if (enabled)
+
+      // if there's more to do, add this to the end of the task list
+      if (requeue) {
+        if (pool != null) {
+// if (getName() != null) System.out.println("requeue " + getName());
+          pool.queue(this);
+        }
+        requeue = false;
+      }
+
+    } // end synchronized (lockEnabled)
+    currentActionThread = null;
+  }
+
+  /**
+   * abstract method that implements activity of this ActionImpl
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract void doAction() throws VisADException, RemoteException;
+
+  /**
+   * a linked ThingReference has changed, requesting activity
+   * in this ActionImpl
+   * @param e ThingChangedEvent for change to ThingReference
+   *
+   * @return true if the ThingReference changed
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  
+  public boolean thingChanged(ThingChangedEvent e)
+          throws VisADException, RemoteException {
+    long id = e.getId();
+    ReferenceActionLink link = findLink(id);
+
+    boolean changed = true;
+    if (link != null) {
+      link.acknowledgeThingChangedEvent(e.getTick());
+      notifyAction();
+      changed = false;
+    }
+
+    return changed;
+  }
+
+  /**
+   * add a link to a ReferenceActionLink (and via it
+   * link to a ThingReference)
+   * @param link ReferenceActionLink to link to
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void addLink(ReferenceActionLink link)
+          throws VisADException, RemoteException {
+    ThingReference ref = link.getThingReference();
+    if (findReference(ref) != null) {
+      throw new ReferenceException("Action.addLink: link to " +
+                                   "ThingReference already exists");
+    }
+
+// WLH 4 Dec 98 - moved this above LinkVector stuff
+    ref.addThingChangedListener(link.getAction(), link.getId());
+
+    if (LinkVector == null) LinkVector = new Vector();
+    synchronized (LinkVector) {
+      LinkVector.addElement(link);
+    }
+  }
+
+  /**
+   * trigger activity in this ActionImpl
+   */
+  void notifyAction() {
+// if (getName() != null) DisplayImpl.printStack("notifyAction " + getName());
+    requeue = true;
+    if (pool == null) {
+      startThreadPool();
+    }
+    if (TRACE_STACK) {
+      stackTrace = visad.util.Util.getStackTrace();
+    }
+    pool.queue(this);
+  }
+
+  /**
+   * wait for all queued tasks in ThreadPool to finish
+   */
+  public void waitForTasks() {
+    if (pool != null) {
+      pool.waitForTasks();
+    }
+  }
+
+  /**
+   * Creates a link to a ThingReference.  Note that this method causes this
+   * object to register itself with the ThingReference.
+   * @param ref                   The ThingReference to which to create
+   *                              the link.  Subsequent invocation of
+   *                              <code>thingChanged(ThingChangedEvent)</code>
+   *                              causes invocation of
+   *                              <code>ref.acknowledgeThingChanged(this)</code>
+   *                              .  This method invokes <code>
+   *                              ref.addThingChangedListener(this, ...)</code>.
+   * @throws RemoteVisADException if the reference isn't a {@link
+   *                              ThingReferenceImpl}.
+   * @throws ReferenceException   if the reference has already been added.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   * @see #thingChanged(ThingChangedEvent)
+   * @see ThingReference#addThingChangedListener(ThingChangedListener, long)
+   */
+  public void addReference(ThingReference ref)
+          throws ReferenceException, RemoteVisADException, VisADException,
+                 RemoteException {
+    if (!(ref instanceof ThingReferenceImpl)) {
+      throw new RemoteVisADException("ActionImpl.addReference: requires " +
+                                     "ThingReferenceImpl");
+    }
+    if (findReference(ref) != null) {
+      throw new ReferenceException("ActionImpl.addReference: " +
+                                   "link already exists");
+    }
+    addLink(new ReferenceActionLink(ref, this, this, getLinkId()));
+    notifyAction();
+  }
+
+  /**
+   * does essentially the same thing as addReference(), but is
+   * called by the addReference() method of any RemoteActionImpl
+   * that adapts this ActionImpl
+   * @param ref RemoteThingReference being linked
+   * @param action RemoteActionImpl adapting this ActionImpl
+   * @throws ReferenceException   if the reference has already been added.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  void adaptedAddReference(RemoteThingReference ref, Action action)
+          throws VisADException, RemoteException {
+    if (findReference(ref) != null) {
+      throw new ReferenceException("ActionImpl.adaptedAddReference: " +
+                                   "link already exists");
+    }
+    addLink(new ReferenceActionLink(ref, this, action, getLinkId()));
+    notifyAction();
+  }
+
+  /**
+   * <p>Removes a link to a ThingReference.</p>
+   *
+   * <p>This implementation invokes {@link #findReference(ThingReference)}.</p>
+   *
+   * @param ref                   The reference to be removed.
+   * @throws RemoteVisADException if the reference isn't a {@link
+   *                              ThingReferenceImpl}.
+   * @throws ReferenceException   if the reference isn't a part of this
+   *                              instance.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  public void removeReference(ThingReference ref)
+          throws VisADException, RemoteException {
+    ReferenceActionLink link = null;
+    if (!(ref instanceof ThingReferenceImpl)) {
+      throw new RemoteVisADException("ActionImpl.removeReference: requires " +
+                                     "ThingReferenceImpl");
+    }
+    if (LinkVector != null) {
+      synchronized (LinkVector) {
+        link = findReference(ref);
+        if (link == null) {
+          throw new ReferenceException("ActionImpl.removeReference: " +
+                                       "ThingReference not linked");
+        }
+        LinkVector.removeElement(link);
+      }
+    }
+    if (link != null) ref.removeThingChangedListener(link.getAction());
+    notifyAction();
+  }
+
+  /**
+   * does essentially the same thing as removeReference(), but is
+   * called by the removeReference() method of any RemoteActionImpl
+   * that adapts this ActionImpl
+   * @param ref RemoteThingReference being removed
+   * @throws ReferenceException   if the reference is not linked.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  void adaptedRemoveReference(RemoteThingReference ref)
+          throws VisADException, RemoteException {
+    ReferenceActionLink link = null;
+    if (LinkVector != null) {
+      synchronized (LinkVector) {
+        link = findReference(ref);
+        if (link == null) {
+          throw new ReferenceException("ActionImpl.adaptedRemoveReference: " +
+                                       "ThingReference not linked");
+        }
+        LinkVector.removeElement(link);
+      }
+    }
+    if (link != null) ref.removeThingChangedListener(link.getAction());
+    notifyAction();
+  }
+
+  /**
+   *  delete all links to ThingReferences
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public void removeAllReferences() throws VisADException, RemoteException {
+    Vector cloneLink = null;
+    if (LinkVector != null) {
+      synchronized (LinkVector) {
+        cloneLink = (Vector)LinkVector.clone();
+        LinkVector.removeAllElements();
+      }
+    }
+    if (cloneLink != null) {
+      Enumeration links = cloneLink.elements();
+      while(links.hasMoreElements()) {
+        ReferenceActionLink link = (ReferenceActionLink)links.nextElement();
+        ThingReference ref = link.getThingReference();
+        ref.removeThingChangedListener(link.getAction());
+      }
+    }
+    notifyAction();
+  }
+
+  /**
+   * called by DisplayImpl.removeReference and
+   * DisplayImpl.adaptedDisplayRemoveReference to remove links
+   * @param links array of ReferenceActionLinks to remove
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  void removeLinks(ReferenceActionLink[] links)
+          throws VisADException, RemoteException {
+    if (LinkVector != null) {
+      synchronized (LinkVector) {
+        for (int i = 0; i < links.length; i++) {
+          if (!LinkVector.removeElement(links[i])) links[i] = null;
+        }
+      }
+    }
+    for (int i = 0; i < links.length; i++) {
+      if (links[i] != null) {
+        ThingReference ref = links[i].getThingReference();
+        try {
+          ref.removeThingChangedListener(links[i].getAction());
+        }
+        catch (RemoteException re) {
+          // don't throw exception if the other side has died
+          if (!visad.collab.CollabUtil.isDisconnectException(re)) {
+            throw re;
+          }
+        }
+      }
+    }
+    notifyAction();
+  }
+
+  /**
+   * returns a linked ReferenceActionLink with the given id
+   * @param id value to search for
+   * @return linked ReferenceActionLink with given id
+   * @throws VisADException       if a VisAD failure occurs.
+   */
+  ReferenceActionLink findLink(long id) throws VisADException {
+    if (LinkVector == null) return null;
+    synchronized (LinkVector) {
+      Enumeration links = LinkVector.elements();
+      while(links.hasMoreElements()) {
+        ReferenceActionLink link = (ReferenceActionLink)links.nextElement();
+        if (id == link.getId()) return link;
+      }
+    }
+    return null;
+  }
+
+
+  /**
+   * Returns the link associated with a ThingReference.
+   *
+   * @param ref                 The reference to find.
+   * @return                    The link associated with the reference.
+   * @throws ReferenceException if the argument is <code>null</code>.
+   * @throws VisADException     if the argument is <code>null</code>.
+   */
+  public ReferenceActionLink findReference(ThingReference ref)
+          throws VisADException {
+    if (ref == null) {
+      throw new ReferenceException("ActionImpl.findReference: " +
+                                   "ThingReference cannot be null");
+    }
+    if (LinkVector == null) return null;
+    synchronized (LinkVector) {
+      Enumeration links = LinkVector.elements();
+      while(links.hasMoreElements()) {
+        ReferenceActionLink link = (ReferenceActionLink)links.nextElement();
+        if (ref.equals(link.getThingReference())) return link;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * @return Vector of linked ReferenceActionLinks
+   */
+  public Vector getLinks() {
+    return (Vector)LinkVector.clone();
+  }
+
+  /**
+   *  @return String name of this Action
+   */
+  public String getName() {
+    return Name;
+  }
+
+  /**
+   * change the name of this Action
+   * @param name new String name
+   */
+  public void setName(String name) {
+    Name = name;
+  }
+
+}
+
diff --git a/visad/ActivityHandler.java b/visad/ActivityHandler.java
new file mode 100644
index 0000000..4b8c3c2
--- /dev/null
+++ b/visad/ActivityHandler.java
@@ -0,0 +1,64 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Display activity handler supplied to
+ * {@link DisplayImpl#addActivityHandler(ActivityHandler)
+ *   DisplayImpl.addActivityHandler}.<br>
+ * <br>
+ * A trivial implementation which toggles between two Data objects
+ * in a Display would be:<br>
+ * <br>
+ * <code><pre>
+ *    class SwitchGIFs implements ActivityHandler
+ *    {
+ *      SwitchGIFs(LocalDisplay d) { toggle(d, true); }
+ *      public void busyDisplay(LocalDisplay d) { toggle(d, false); }
+ *      public void idleDisplay(LocalDisplay d) { toggle(d, true); }
+ *      private void toggle(LocalDisplay d, boolean first) {
+ *        java.util.Vector v = d.getRenderers();
+ *        ((DataRenderer )v.get(0)).toggle(first);
+ *        ((DataRenderer )v.get(1)).toggle(!first);
+ *      }
+ *    }
+ * </pre></code>
+ */
+public interface ActivityHandler
+{
+
+  /**
+   * Method called when the Display becomes busy.
+   *
+   * @param dpy Busy Display.
+   */
+  void busyDisplay(LocalDisplay dpy);
+
+  /**
+   * Method called after the Display has been idle long enough.
+   *
+   * @param dpy Idle Display.
+   */
+  void idleDisplay(LocalDisplay dpy);
+
+}
diff --git a/visad/AnimationControl.java b/visad/AnimationControl.java
new file mode 100644
index 0000000..1b3a432
--- /dev/null
+++ b/visad/AnimationControl.java
@@ -0,0 +1,220 @@
+//
+// AnimationControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   AnimationControl is the VisAD interface for controlling Animation
+   display scalars. Current implementations also implement Runnable<P>
+*/
+public interface AnimationControl extends AVControl {
+
+  /**
+   * stop activity in this AnimationControl
+   */
+  void stop();
+
+  /**
+   * a single invocation implements anmation behavior
+   * until stop() is called
+   */
+  void run();
+
+  /**
+   * set the current ordinal step number
+   * @param c - value for current ordinal step number
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  void setCurrent(int c)
+         throws VisADException, RemoteException;
+
+  /**
+   * set the current step by the value of the RealType mapped to
+   * Display.Animation
+   * @param value - RealType value that is converted to an
+   *                ordinal step number
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  void setCurrent(double value)
+         throws VisADException, RemoteException;
+
+  /**
+   * @return the current ordinal step number
+   */
+  int getCurrent();
+
+  /**
+   * Set the animation direction.
+   *
+   * @param    dir     true for forward, false for backward
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           direction remains unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  void setDirection(boolean dir)
+         throws VisADException, RemoteException;
+
+  /** Get the animation direction.
+   *
+   *  @return	true for forward, false for backward
+   */
+  boolean getDirection();
+
+  /**
+   * @return the dwell time for the current step (in ms)
+   */
+  long getStep();
+
+  /**
+   * @return an array of the dwell times for all the steps (in ms)
+   */
+  long[] getSteps();
+
+  /**
+   * Set the dwell rate between animation steps to a constant value
+   *
+   * @param  st   dwell time in milliseconds
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           dwell time remains unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  void setStep(int st)
+         throws VisADException, RemoteException;
+
+  /**
+   * set the dwell time for individual steps.
+   *
+   * @param   steps   an array of dwell times in milliseconds for each
+   *                  step in the animation.
+   *                  If the length of the array is less than the number of
+   *                  frames in the animation, the subsequent step values will
+   *                  be set to the value of the last step.
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           dwell times remain unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  void setSteps(int[] steps)
+         throws VisADException, RemoteException;
+
+  /**
+   * advance one step (forward or backward)
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           step is taken.
+   * @throws  RemoteException  Java RMI exception
+   */
+  void takeStep()
+         throws VisADException, RemoteException;
+
+  /**
+   * actually set Switches (Java3D) or VisADSwitches (Java2D) to
+   * child nodes corresponding to current ordinal step number
+   * @throws VisADException - a VisAD error occurred.
+   */
+  void init() throws VisADException;
+
+  /**
+   * @return Set of RealType values for animation steps, in RealType
+   * mapped to Animation
+   */
+  Set getSet();
+
+  /**
+   * <p>Sets the set of times in this animation control, in RealType
+   * mapped to Animation. If the argument set is equal to the current
+   * set, then nothing is done.</p>
+   *
+   * @param s                     The set of times.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  void setSet(Set s)
+         throws VisADException, RemoteException;
+
+  /**
+   * <p>Sets the set of times in this animation control, in RealType
+   * mapped to Animation. If the argument set is equal to the current
+   * set, then nothing is done.</p>
+   *
+   * @param s                     The set of times.
+   * @param noChange              changeControl(!noChange) to not trigger 
+   *                              re-transform, used by ScalarMap.setRange
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  void setSet(Set s, boolean noChange)
+         throws VisADException, RemoteException;
+
+  /**
+   * @return true if automatic stepping is on, false otherwise
+   */
+  boolean getOn();
+
+  /**
+   * Set automatic stepping on or off.
+   *
+   * @param  o  true = turn stepping on, false = turn stepping off
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           change in automatic stepping occurs.
+   * @throws  RemoteException  Java RMI exception
+   */
+  void setOn(boolean o)
+         throws VisADException, RemoteException;
+
+  /**
+   * toggle automatic stepping between off and on
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           change in automatic stepping occurs.
+   * @throws  RemoteException  Java RMI exception
+   */
+  void toggle()
+         throws VisADException, RemoteException;
+
+  /**
+   * Set the flag to automatically compute the animation set if it is
+   * null
+   * @param compute   false to allow application to control set computation
+   *                  if set is null.
+   */
+  void setComputeSet(boolean compute);
+
+  /**
+   * Get the flag to automatically compute the animation set if it is
+   * null
+   * 
+   * @return true if should compute
+   */
+  boolean getComputeSet();
+}
diff --git a/visad/AnimationSetControl.java b/visad/AnimationSetControl.java
new file mode 100644
index 0000000..12b55c3
--- /dev/null
+++ b/visad/AnimationSetControl.java
@@ -0,0 +1,241 @@
+//
+// AnimationSetControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   AnimationSetControl is the VisAD class for sampling Animation
+   steps.<P>
+*/
+public class AnimationSetControl extends Control {
+
+  private Set set;
+  private transient AnimationControl parent;
+  private boolean isManual;
+
+  /**
+   * construct an AnimationSetControl for the given DisplayImpl
+   * and AnimationControl
+   * @param d - DisplayImpl this AnimationSetControl is associated with
+   * @param p - parent AnimationControl of this AnimationSetControl
+   */
+  public AnimationSetControl(DisplayImpl d, AnimationControl p) {
+    super(d);
+    parent = p;
+    set = null;
+    isManual = false;
+  }
+
+  /** 
+   * @return Set of RealType values for animation steps, in RealType
+   * mapped to Animation
+   */
+  public Set getSet() {
+    return set;
+  }
+
+  /**
+   * @return value of current clipped to limits defined by this
+   * AnimationSetControl
+   * @param current value to clip
+   * @throws VisADException if a VisAD error occurs
+   */
+  public int clipCurrent(int current) throws VisADException {
+    if (set == null || current >= set.getLength()) {
+      current = 0;
+    }
+    else if (current < 0) {
+      current = set.getLength() - 1;
+    }
+    return current;
+  }
+
+  /**
+   * @return current step converted to value of RealType
+   * mapped to Animation
+   * @param current index of current step
+   * @throws VisADException if a VisAD error occurs
+   */
+  public double getValue(int current) throws VisADException {
+    int[] indices = new int[1];
+    indices[0] = clipCurrent(current);
+    if (set == null) {
+      return Double.NaN;
+    }
+    else {
+      double[][] values = set.indexToDouble(indices);
+      return values[0][0];
+    }
+  }
+
+  /**
+   * @return animation step ordinal corresponding to value
+   * of RealType mapped to Animation
+   * @param value - RealType value
+   * @throws VisADException if a VisAD error occurs
+   */
+  public int getIndex(double value) throws VisADException {
+    if (set == null) {
+      return 0;
+    }
+    else {
+      double[][] values = new double[][] {{value}};
+      int[] indices = set.doubleToIndex(values);
+      return ((indices[0] < 0) ? 0 : indices[0]);
+    }
+  }
+
+  /** 
+   * set Set of Animation value
+   * @param s - Set of RealType values for Animation steps
+   * @throws VisADException if a VisAD error occurs
+   * @throws RemoteException if an RMI error occurs
+   */
+  public void setSet(Set s)
+         throws VisADException, RemoteException {
+    setSet(s, false);
+  }
+
+  /**
+   * set Set of Animation value
+   * @param s - Set of RealType values for Animation steps
+   * @param noChange = true to not trigger changeControl (used by
+      ScalarMap.setRange())
+   * @throws VisADException if a VisAD error occurs
+   * @throws RemoteException if an RMI error occurs
+   */
+  public void setSet(Set s, boolean noChange)
+         throws VisADException, RemoteException {
+    if (set != null && set.equals(s) || 
+        set == null && s == null) {
+        return;
+    }
+
+    if (noChange) {
+      // don't auto-scale is a previous non-auto-scale call
+      if (isManual) return;
+    }
+    else {
+      // a non-auto-scale call
+      isManual = true;
+    }
+
+    set = s;
+    if (parent != null) {
+      parent.setCurrent(clipCurrent(parent.getCurrent()));
+    }
+    changeControl(!noChange);
+  }
+
+  /**
+   * @return String representation of this AnimationSetControl
+   */
+  public String getSaveString() {
+    return null;
+  }
+
+  /**
+   * reconstruct this AnimationSetControl using the specified save string
+   * @param save - String representation for reconstruction
+   * @throws VisADException if a VisAD error occurs
+   * @throws RemoteException if an RMI error occurs
+   */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    throw new UnimplementedException(
+      "Cannot setSaveString on this type of control");
+  }
+
+  /**
+   * copy the state of a remote control to this control
+   * @param rmt remote Control whose state is copied
+   * @throws VisADException if a VisAD error occurs
+   */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof AnimationSetControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    AnimationSetControl asc = (AnimationSetControl )rmt;
+
+    boolean changeSet = false;
+
+    if (set == null) {
+      if (asc.set != null) {
+        changeSet = true;
+      }
+    } else if (asc.set == null) {
+      changeSet = true;
+    } else if (!set.equals(asc.set)) {
+      changeSet = true;
+    }
+
+    if (changeSet) {
+      try {
+        setSet(asc.set);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not set Set: " + re.getMessage());
+      }
+    }
+  }
+
+  /**
+   * @return true if o is identical with this
+   * @param o - Object tested for equality with this
+   */
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    AnimationSetControl asc = (AnimationSetControl )o;
+
+    if (set == null) {
+      if (asc.set != null) {
+        return false;
+      }
+    } else if (asc.set == null) {
+      return false;
+    } else if (!set.equals(asc.set)) {
+      return false;
+    }
+
+    return true;
+  }
+
+}
diff --git a/visad/AxisScale.java b/visad/AxisScale.java
new file mode 100644
index 0000000..e709339
--- /dev/null
+++ b/visad/AxisScale.java
@@ -0,0 +1,1756 @@
+//
+// AxisScale.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Color;
+import java.util.*;
+import java.awt.Font;
+import visad.util.HersheyFont;
+import java.text.*;
+
+/**
+ * Class which defines the scales displayed along the spatial axes
+ * of a display.  Each ScalarMap that has a DisplayScalar of the
+ * X, Y, or Z axis will have a non-null AxisScale.
+ * @see ScalarMap#getAxisScale()
+ * @author Don Murray
+ */
+public class AxisScale implements java.io.Serializable
+{
+
+  private static final long serialVersionUID = 1L;
+  /** X_AXIS identifier */
+  public static final int X_AXIS = 0;
+  /** Y_AXIS identifier */
+  public static final int Y_AXIS = 1;
+  /** Z_AXIS identifier */
+  public static final int Z_AXIS = 2;
+  /** identifier for primary label side of axis*/
+  public static final int PRIMARY = 0;
+  /** identifier for secondary label side of axis*/
+  public static final int SECONDARY = 1;
+  /** identifier for tertiary label side of axis*/
+  public static final int TERTIARY = 2;
+  /** identifier for quaternary label side of axis*/
+  public static final int QUATERNARY = 3;
+
+  // WLH 12 July 2001
+  // true indicates axis is stationary relative to screen
+  // rather than graphics coordinates
+  private boolean screenBased = false;
+  private boolean gridLinesVisible = false;
+  private boolean ticksVisible = true;
+  private boolean labelBothSides = false;
+
+  private VisADLineArray scaleArray;
+  private VisADTriangleArray labelArray;
+  private ScalarMap scalarMap;
+  private Color myColor = Color.white;
+  private double[] dataRange = new double[2];
+  private int myAxis = -1;
+  private int axisOrdinal = -1;
+  private String myTitle;
+  private Hashtable labelTable;
+  private double[] majorTicks = null;
+  private double[] minorTicks = null;
+  private double majorTickSpacing = 0.0;
+  private double minorTickSpacing = 0.0;
+  private double tickBase = 0.0;
+  private boolean autoComputeTicks = true;
+  private boolean baseLineVisible = true;
+  private boolean snapToBox = false;
+  private boolean userLabels = false;
+  private boolean visibility = true;
+  private boolean labelAllTicks = false;  // label major ticks
+  private Object labelFont = null;
+  private int labelSize = 12;
+  private int axisSide = PRIMARY;
+  private int tickOrient = PRIMARY;
+  private static final double TICKSIZE = .5;  // major ticks are 1/2 char ht.
+  private NumberFormat labelFormat = null;
+  
+  /** Is the label angled away from the axis or is it "flat"*/
+  private boolean labelRelief = true;
+
+  /**
+   * Construct a new AxisScale for the given ScalarMap
+   * @param map  ScalarMap to monitor.  Must be mapped to one of
+   *       Display.XAxis, Display.YAxis, Display.ZAxis
+   * @throws  VisADException  bad ScalarMap or other VisAD problem
+   */
+  public AxisScale(ScalarMap map)
+    throws VisADException
+  {
+    scalarMap = map;
+    DisplayRealType displayScalar = scalarMap.getDisplayScalar();
+    if (!displayScalar.equals(Display.XAxis) &&
+      !displayScalar.equals(Display.YAxis) &&
+      !displayScalar.equals(Display.ZAxis)) {
+        throw new DisplayException("AxisSale: DisplayScalar " +
+                                   "must be XAxis, YAxis or ZAxis");
+    }
+    myAxis = (displayScalar.equals(Display.XAxis)) ? X_AXIS :
+       (displayScalar.equals(Display.YAxis)) ? Y_AXIS : Z_AXIS;
+    myTitle = scalarMap.getScalarName();
+    visibility = scalarMap.getScaleEnable();
+    labelTable = new Hashtable();
+    DisplayImpl display = scalarMap.getDisplay();
+    if (display != null) {
+      DisplayRenderer displayRenderer = display.getDisplayRenderer();
+      if (displayRenderer != null) {
+        float[] rgb = displayRenderer.getRendererControl().getForegroundColor();
+        myColor = new Color(rgb[0], rgb[1], rgb[2]);
+        boolean ok = makeScale();
+      }
+    }
+  }
+
+  /**
+   * Get the position of this AxisScale on the Axis (first, second, third).
+   *
+   * @return  position from the axis (first = 0, second = 1, etc)
+   */
+  public int getAxisOrdinal()
+  {
+    return axisOrdinal;
+  }
+
+  /**
+   * Set the position of this AxisScale on the axis.  Should only
+   * be called by ScalarMap
+   * @param  ordinalValue  axis position (0 = first, 1 = second, etc)
+   */
+  void setAxisOrdinal(int ordinalValue)
+  {
+    axisOrdinal = ordinalValue;
+  }
+
+  /**
+   * @deprecated
+   * Set the label to be used for this axis.  The default is the
+   * ScalarName of the ScalarMap.
+   * @param  label  label to be used
+   * @see #setTitle(String)
+   */
+  public void setLabel(String label)
+  {
+    setTitle(label);
+  }
+
+  /**
+   * @deprecated
+   * Get the label of the AxisScale.
+   * @return label
+   * @see #getTitle()
+   */
+  public String getLabel()
+  {
+    return getTitle();
+  }
+
+  /**
+   * Set the title to be used for this axis.  The default is the
+   * ScalarName of the ScalarMap.
+   * @param  title  title to be used
+   */
+  public void setTitle(String title)
+  {
+    String oldTitle = myTitle;
+    myTitle = title;
+    if (!myTitle.equals(oldTitle) ) {
+      try {
+        // check for case where this was called from scalarmap.setScalarName()
+        if ( !myTitle.equals(scalarMap.getScalarName()) )
+        {
+          scalarMap.setScalarName(myTitle);
+        }
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Get the title of the AxisScale.
+   * @return title
+   */
+  public String getTitle()
+  {
+    return myTitle;
+  }
+
+  /**
+   * Get axis that the scale will be displayed on.
+   * @return  axis  (X_AXIS, Y_AXIS or Z_AXIS)
+   */
+  public int getAxis()
+  {
+    return myAxis;
+  }
+
+  /**
+   * Get the Scale to pass to the renderer.
+   * @return  VisADLineArray representing the scale
+   */
+  public VisADLineArray getScaleArray()
+  {
+    return scaleArray;
+  }
+
+  /**
+   * Get the labels rendered with a font to pass to the renderer.
+   * @return  VisADTriangleArray representing the labels
+   */
+  public VisADTriangleArray getLabelArray()
+  {
+    return labelArray;
+  }
+
+  /**
+   * set screenBased mode
+   *   true indicates axis is stationary relative to screen
+  */
+  public void setScreenBased(boolean sb) {
+    screenBased = sb;
+  }
+
+  /**
+   * return screenBased mode
+   * @return  true if axis is stationary relative to screen
+  */
+  public boolean getScreenBased() {
+    return screenBased;
+  }
+
+  /**
+   * Create the scale for screen based.
+   * @return  true if scale was successfully created, otherwise false
+   */
+  public boolean makeScreenBasedScale(double xmin, double ymin,
+                                      double xmax, double ymax,
+                                      double XTMIN, double YTMIN,
+                                      double XTMAX, double YTMAX)
+         throws VisADException {
+    DisplayImpl display = scalarMap.getDisplay();
+    if (display == null) return false;
+    DisplayRenderer displayRenderer = display.getDisplayRenderer();
+    if (displayRenderer == null) return false;
+    if (axisOrdinal < 0) return false;
+    dataRange = scalarMap.getRange();
+    // boolean twoD = displayRenderer.getMode2D();
+    if (!displayRenderer.getMode2D()) return false;
+    boolean twoD = true;
+
+    ProjectionControl pcontrol = display.getProjectionControl();
+    double[] aspect = pcontrol.getAspectCartesian();
+    double oldMax = 1.0;
+    double oldMin = -1.0;
+    double newMax = 1.0;
+    double newMin = -1.0;
+    if (myAxis == X_AXIS) {
+      oldMax = aspect[0];
+      oldMin = -aspect[0];
+      newMax = XTMAX;
+      newMin = XTMIN;
+    }
+    else if (myAxis == Y_AXIS) {
+      oldMax = aspect[1];
+      oldMin = -aspect[1];
+      newMax = YTMAX;
+      newMin = YTMIN;
+    }
+
+    double mult = (dataRange[1] - dataRange[0]) / (oldMax - oldMin);
+    double d1 = (newMax - oldMin) * mult + dataRange[0];
+    double d0 = (newMin - oldMin) * mult + dataRange[0];
+    double[] dr = {d0, d1};
+
+    double zmin = 0.0;
+    double zmax = -zmin;
+
+    // set scale according to labelSize
+    double scale =  labelSize/200.;
+    double offset = 1.05;
+
+    // WLH 20 Feb 2003 keep screen-based YAxis label in Frame
+    scale *= 0.6; // hack size for screen based
+    // scale *= 0.8; // hack size for screen based
+
+    // Add 16-APR-2001 DRM
+    int position = 0;
+    int myPosition = 0;
+    // Snap to the box edge instead of being offset
+    if (snapToBox) {
+      offset = 1.0;
+    }
+    else
+    {
+      for (Enumeration e = display.getMapVector().elements();
+            e.hasMoreElements();)
+      {
+        ScalarMap map = (ScalarMap) e.nextElement();
+        if (map.getDisplayScalar().equals(scalarMap.getDisplayScalar()))
+        {
+          if (getSide() == map.getAxisScale().getSide()) 
+          {
+            if (!map.equals(scalarMap)) // same side someone else
+            {
+              position++;
+            } else {
+              myPosition = position;
+            }
+          }
+        }
+      }
+    }
+    /*
+    System.out.println(scalarMap + "is at position " + (myPosition+1) +
+                       " out of " + (position + 1));
+    */
+    // End Add 16-APR-2001 DRM
+
+    // position of baseline for this scale
+    double line = 4.0 * myPosition * scale;  // DRM 17-APR-2001
+
+    return makeScale(twoD, xmin, ymin, zmin, xmax, ymax, zmax,
+                     scale, offset, line, dr);
+  }
+
+  /**
+   * Create the scale.
+   * @return  true if scale was successfully created, otherwise false
+   */
+  public boolean makeScale()
+      throws VisADException {
+    DisplayImpl display = scalarMap.getDisplay();
+    if (display == null) return false;
+    DisplayRenderer displayRenderer = display.getDisplayRenderer();
+    if (displayRenderer == null) return false;
+    if (axisOrdinal < 0) {
+      axisOrdinal = displayRenderer.getAxisOrdinal(myAxis);
+    }
+    dataRange = scalarMap.getRange();
+    boolean twoD = displayRenderer.getMode2D();
+
+  // now create scale along axis at axisOrdinal position in array
+  // twoD may help define orientation
+
+// WLH 24 Nov 2000
+    ProjectionControl pcontrol = display.getProjectionControl();
+    double[] aspect = pcontrol.getAspectCartesian();
+
+    double xmin = -aspect[0];
+    double ymin = -aspect[1];
+    double zmin = -aspect[2];
+
+    double xmax = -xmin;
+    double ymax = -ymin;
+    double zmax = -zmin;
+
+    // set scale according to labelSize
+    double scale =  labelSize/200.;
+    double offset = 1.05;
+
+    // Add 16-APR-2001 DRM
+    int position = 0;
+    int myPosition = 0;
+    // Snap to the box edge instead of being offset
+    if (snapToBox) {
+      offset = 1.0;
+    }
+    else
+    {
+      for (Enumeration e = display.getMapVector().elements();
+            e.hasMoreElements();)
+      {
+        ScalarMap map = (ScalarMap) e.nextElement();
+        if (map.getDisplayScalar().equals(scalarMap.getDisplayScalar()))
+        {
+          if (getSide() == map.getAxisScale().getSide()) {
+            if (!map.equals(scalarMap)) // same side someone else
+            {
+              position++;
+            } else {
+              myPosition = position;// this is me
+            }
+          }
+        }
+      }
+    }
+    /*
+    System.out.println(scalarMap + "is at position " + (myPosition+1) +
+                       " out of " + (position + 1));
+    */
+    // End Add 16-APR-2001 DRM
+
+    // position of baseline for this scale
+    double line = 4.0 * myPosition * scale;  // DRM 17-APR-2001
+
+    /*  Remove 16-APR-2001
+    double one = 1.0;
+    if (dataRange[0] > dataRange[1]) one = -1.0; // inverted range
+    int position = axisOrdinal;
+    if (snapToBox) {
+      offset = 1.0;
+      position = 0;
+    }
+    double line = 2.0 * position * scale;
+    */
+
+
+    return makeScale(twoD, xmin, ymin, zmin, xmax, ymax, zmax,
+                     scale, offset, line, dataRange);
+  }
+
+  /** inner logic of makeScale with no references to display, displayRenderer
+      or scalarMap, allwoing more flexible placement of scales */
+  public boolean makeScale(boolean twoD, double xmin, double ymin, double zmin,
+                           double xmax, double ymax, double zmax,
+                           double scale, double offset, double line,
+                           double[] dataRange)
+         throws VisADException {
+
+// start new method here
+// no references to display, scalarMap, or displayRenderer past this point
+
+
+    // compute graphics positions
+    // these are {x, y, z} vectors
+    double[] base = null; // vector from one character to another
+    double[] up = null; // vector from bottom of character to top
+    double[] startn = null; // -1.0 position along axis
+    double[] startp = null; // +1.0 position along axis
+    double[] gridstartn = null; // -1.0 position along axis
+    double[] gridstartp = null; // +1.0 position along axis
+
+    int numSides = (getLabelBothSides()) ? 2 : 1;
+    Vector lineArrayVector = new Vector(4*numSides);
+    Vector labelArrayVector = new Vector();
+
+    double one = 1.0;
+    if (dataRange[0] > dataRange[1]) one = -1.0; // inverted range
+
+    for (int l = 0; l < numSides; l++) {
+      int side = getSide();
+      side = (side + l) % (twoD ? 2 : 4);
+      // set up the defaults for each of the axes.  startp and startn are the
+      // endpoints of the axis line.  base and up determine which way the
+      // tick marks are drawn along that line.  For 2-D, base and up are changed
+      // later on so that the labels are right side up. DRM 16-APR-2001
+      if (myAxis == X_AXIS) {
+        if (side == PRIMARY) {
+          base = new double[] {scale, 0.0, 0.0};
+          up = new double[] {0.0, scale, scale};
+          startp = new double[] {one * xmax,
+                                 ymin - ((offset - 1.0) + line),
+                                 zmin - ((offset - 1.0) + line)};
+          startn = new double[] {one * xmin,
+                                 ymin - ((offset - 1.0) + line),
+                                 zmin - ((offset - 1.0) + line)};
+          gridstartp = new double[] {one * xmax, ymin, zmin};
+          gridstartn = new double[] {one * xmin, ymin, zmin};
+        }
+        else if (side == SECONDARY) {
+          base = new double[] {-scale, 0.0, 0.0};
+          up = new double[] {0.0, -scale, scale};
+          startp = new double[] {one * xmax,
+                                 ymax + ((offset - 1.0) + line),
+                                 zmin - ((offset - 1.0) + line)};
+          startn = new double[] {one * xmin,
+                                 ymax + ((offset - 1.0) + line),
+                                 zmin - ((offset - 1.0) + line)};
+          gridstartp = new double[] {one * xmax, ymax, zmin};
+          gridstartn = new double[] {one * xmin, ymax, zmin};
+        }
+        else if (side == TERTIARY) {
+          base = new double[] {scale, 0.0, 0.0};
+          up = new double[] {0.0, scale, -scale};
+          startp = new double[] {one * xmax,
+                                 ymin - ((offset - 1.0) + line),
+                                 zmax + ((offset - 1.0) + line)};
+          startn = new double[] {one * xmin,
+                                 ymin - ((offset - 1.0) + line),
+                                 zmax + ((offset - 1.0) + line)};
+          gridstartp = new double[] {one * xmax, ymin, zmax};
+          gridstartn = new double[] {one * xmin, ymin, zmax};
+        }
+        else { // side == QUATERNARY
+          base = new double[] {-scale, 0.0, 0.0};
+          up = new double[] {0.0, -scale, -scale};
+          startp = new double[] {one * xmax,
+                                 ymax + ((offset - 1.0) + line),
+                                 zmax + ((offset - 1.0) + line)};
+          startn = new double[] {one * xmin,
+                                 ymax + ((offset - 1.0) + line),
+                                 zmax + ((offset - 1.0) + line)};
+          gridstartp = new double[] {one * xmax, ymax, zmax};
+          gridstartn = new double[] {one * xmin, ymax, zmax};
+        }
+      }
+      else if (myAxis == Y_AXIS) {
+        if (side == PRIMARY) {
+          base = new double[] {0.0, -scale, 0.0};
+          up = new double[] {scale, 0.0, scale};
+          startp = new double[] {xmin - ((offset - 1.0) + line),
+                                 one * ymax,
+                                 zmin - ((offset - 1.0) + line)};
+          startn = new double[] {xmin - ((offset - 1.0) + line),
+                                 one * ymin,
+                                 zmin - ((offset - 1.0) + line)};
+          gridstartp = new double[] {xmin, one * ymax, zmin};
+          gridstartn = new double[] {xmin, one * ymin, zmin};
+        }
+        else if (side == SECONDARY) {
+          base = new double[] {0.0, scale, 0.0};
+          up = new double[] {-scale, 0.0, scale};
+          startp = new double[] {xmax + ((offset - 1.0) + line),
+                                 one * ymax,
+                                 zmin - ((offset - 1.0) + line)};
+          startn = new double[] {xmax + ((offset - 1.0) + line),
+                                 one * ymin,
+                                 zmin - ((offset - 1.0) + line)};
+          gridstartp = new double[] {xmax, one * ymax, zmin};
+          gridstartn = new double[] {xmax, one * ymin, zmin};
+        }
+        else if (side == TERTIARY) {
+          base = new double[] {0.0, -scale, 0.0};
+          up = new double[] {scale, 0.0, -scale};
+          startp = new double[] {xmin - ((offset - 1.0) + line),
+                                 one * ymax,
+                                 zmax + ((offset - 1.0) + line)};
+          startn = new double[] {xmin - ((offset - 1.0) + line),
+                                 one * ymin,
+                                 zmax + ((offset - 1.0) + line)};
+          gridstartp = new double[] {xmin, one * ymax, zmax};
+          gridstartn = new double[] {xmin, one * ymin, zmax};
+        }
+        else { // side == QUATERNARY
+          base = new double[] {0.0, scale, 0.0};
+          up = new double[] {-scale, 0.0, -scale};
+          startp = new double[] {xmax + ((offset - 1.0) + line),
+                                 one * ymax,
+                                 zmax + ((offset - 1.0) + line)};
+          startn = new double[] {xmax + ((offset - 1.0) + line),
+                                 one * ymin,
+                                 zmax + ((offset - 1.0) + line)};
+          gridstartp = new double[] {xmax, one * ymax, zmax};
+          gridstartn = new double[] {xmax, one * ymin, zmax};
+        }
+      }
+      else if (myAxis == Z_AXIS) {
+        if (side == PRIMARY) {
+          base = new double[] {0.0, 0.0, -scale};
+          up = new double[] {scale, scale, 0.0};
+          startp = new double[] {xmin - ((offset - 1.0) + line),
+                                 ymin - ((offset - 1.0) + line),
+                                 one * zmax};
+          startn = new double[] {xmin - ((offset - 1.0) + line),
+                                 ymin - ((offset - 1.0) + line),
+                                 one * zmin};
+          gridstartp = new double[] {xmin, ymin, one * zmax};
+          gridstartn = new double[] {xmin, ymin, one * zmin};
+        }
+        else if (side == SECONDARY) {
+          base = new double[] {0.0, 0.0, scale};
+          up = new double[] {-scale, scale, 0.0};
+          startp = new double[] {xmax + ((offset - 1.0) + line),
+                                 ymin - ((offset - 1.0) + line),
+                                 one * zmax};
+          startn = new double[] {xmax + ((offset - 1.0) + line),
+                                 ymin - ((offset - 1.0) + line),
+                                 one * zmin};
+          gridstartp = new double[] {xmax, ymin, one * zmax};
+          gridstartn = new double[] {xmax, ymin, one * zmin};
+        }
+        else if (side == TERTIARY) {
+          base = new double[] {0.0, 0.0, -scale};
+          up = new double[] {scale, -scale, 0.0};
+          startp = new double[] {xmin - ((offset - 1.0) + line),
+                                 ymax + ((offset - 1.0) + line),
+                                 one * zmax};
+          startn = new double[] {xmin - ((offset - 1.0) + line),
+                                 ymax + ((offset - 1.0) + line),
+                                 one * zmin};
+          gridstartp = new double[] {xmin, ymax, one * zmax};
+          gridstartn = new double[] {xmin, ymax, one * zmin};
+        }
+        else { // side == QUATERNARY
+          base = new double[] {0.0, 0.0, scale};
+          up = new double[] {-scale, -scale, 0.0};
+          startp = new double[] {xmax + ((offset - 1.0) + line),
+                                 ymax + ((offset - 1.0) + line),
+                                 one * zmax};
+          startn = new double[] {xmax + ((offset - 1.0) + line),
+                                 ymax + ((offset - 1.0) + line),
+                                 one * zmin};
+          gridstartp = new double[] {xmax, ymax, one * zmax};
+          gridstartn = new double[] {xmax, ymax, one * zmin};
+        }
+      }
+  
+      if (twoD) {
+        if (myAxis == Z_AXIS) return false;  // can't have Z in 2D
+        // zero out z coordinates
+        base[2] = 0.0;
+        up[2] = 0.0;
+        startn[2] = 0.0;
+        startp[2] = 0.0;
+      }
+      
+      if (!labelRelief ) {
+        up[2] = 0.0;        
+      }
+  
+      // VisADLineArray coordinates have three entries for (x, y, z) of each point
+      // two points determine a line segment,
+      // hence 6 coordinates entries per segment
+  
+      // base line for axis
+      if (baseLineVisible) // draw base line
+      {
+        VisADLineArray baseLineArray = new VisADLineArray();
+        float[] lineCoordinates = new float[6];
+        for (int i=0; i<3; i++) { // loop over x, y & z coordinates
+          lineCoordinates[i] = (float) startn[i];
+          lineCoordinates[3 + i] = (float) startp[i];
+        }
+        baseLineArray.vertexCount = 2;
+        baseLineArray.coordinates = lineCoordinates;
+        lineArrayVector.add(baseLineArray);
+      }
+  
+      double range = Math.abs(dataRange[1] - dataRange[0]);
+      double min = Math.min(dataRange[0], dataRange[1]);
+      double max = Math.max(dataRange[0], dataRange[1]);
+      //System.out.println(
+      //  "range = " + range + " min = " + min + " max = " + max);
+  
+      // compute tick mark values
+      double tens = 1.0;
+      if (range < tens) {
+        tens /= 10.0;
+        while (range < tens) tens /= 10.0;
+      }
+      else {
+        while (10.0 * tens <= range) tens *= 10.0;
+      }
+      // now tens <= range < 10.0 * tens;
+      if (autoComputeTicks || majorTickSpacing <= 0)
+      {
+        double ratio = range / tens;
+        if (ratio < 2.0) {
+          tens = tens/5.0;
+        }
+        else if (ratio < 4.0) {
+          tens = tens/2.0;
+        }
+        majorTickSpacing = tens;
+      }
+      // now tens = interval between major tick marks (majorTickSpacing)
+      //System.out.println("computed ticks " + majorTickSpacing);
+
+      //double[] hilo = computeTicks(max, min, tickBase, majorTickSpacing);
+      double[] hilo;
+      if (majorTicks == null) {
+        hilo = computeTicks(max, min, tickBase, majorTickSpacing);
+      } else {
+    	hilo = computeTicks(max,min,majorTicks);
+      }
+  
+      // firstValue is the first Tick mark value
+      //double firstValue = hilo[0];
+      //double botval = hilo[0];
+      //double topval = hilo[hilo.length-1];
+  
+      // draw major tick marks
+      VisADLineArray majorTickArray = new VisADLineArray();
+      //int nticks = (int) ((topval-botval)/majorTickSpacing) + 1;
+      int nticks = hilo.length;
+      float[] majorCoordinates = new float[6 * nticks];
+      double[] tickup = up;
+      if (getTickOrientation() != PRIMARY)
+      {
+        if (myAxis == X_AXIS) {
+          tickup = new double[] {up[0], -up[1], -up[2]};
+        }
+        else if (myAxis == Y_AXIS) {
+          tickup = new double[] {-up[0], up[1], -up[2]};
+        }
+        else if (myAxis == Z_AXIS) {
+          tickup = new double[] {-up[0], -up[1], up[2]};
+        }
+      }
+      // initialize some stuff
+      int k = 0;
+      if (ticksVisible) {
+        for (int j = 0; j< nticks; j++) //Change DRM 21-Feb-2001
+        {
+          //double value = firstValue + (j * majorTickSpacing);
+          double value = hilo[j];
+          double a = (value - min) / (max - min);
+          for (int i=0; i<3; i++) {
+            if ((k + 3 + i) < majorCoordinates.length) {
+              // guard against error that cannot happen, but was seen?
+              majorCoordinates[k + i] =
+                (float) ((1.0 - a) * startn[i] + a * startp[i]);
+              majorCoordinates[k + 3 + i] =
+                (float) (majorCoordinates[k + i] - TICKSIZE * tickup[i]);
+            }
+          }
+          k += 6;
+        }
+  
+        majorTickArray.vertexCount = 2 * (nticks);
+        majorTickArray.coordinates = majorCoordinates;
+        lineArrayVector.add(majorTickArray);
+      }
+  
+      if (gridLinesVisible && l == 0) {
+        VisADLineArray gridArray = new VisADLineArray();
+        float[] gridCoordinates = new float[6 * nticks];
+        // initialize some stuff
+        k = 0;
+        double[] gridup = null;
+        double gridLength = 1.0;
+        if (myAxis == X_AXIS) {
+          gridup = new double[] {0, up[1], 0};
+          gridLength = (ymax-ymin)/scale;
+        }
+        else if (myAxis == Y_AXIS) {
+          gridup = new double[] {up[0], 0, 0};
+          gridLength = (xmax-xmin)/scale;
+        }
+        else if (myAxis == Z_AXIS) {
+          gridup = new double[] {up[0], 0, 0};
+          gridLength = (xmax-xmin)/scale;
+        }
+        for (int j = 0; j< nticks; j++) //Change DRM 21-Feb-2001
+        {
+          //double value = firstValue + (j * majorTickSpacing);
+          double value = hilo[j];
+          double a = (value - min) / (max - min);
+          for (int i=0; i<3; i++) {
+            if ((k + 3 + i) < gridCoordinates.length) {
+              // guard against error that cannot happen, but was seen?
+              gridCoordinates[k + i] =
+                (float) ((1.0 - a) * gridstartn[i] + a * gridstartp[i]);
+              gridCoordinates[k + 3 + i] =
+                (float) (gridCoordinates[k + i] + gridLength*gridup[i]);
+            }
+          }
+          k += 6;
+        }
+        gridArray.vertexCount = 2 * (nticks);
+        gridArray.coordinates = gridCoordinates;
+        lineArrayVector.add(gridArray);
+      }
+  
+      // create an array for the minor ticks
+      if ((getMinorTickSpacing() > 0 || minorTicks != null) && ticksVisible)  
+      {
+        //hilo = computeTicks(max, min, tickBase, minorTickSpacing);
+        double[] minorTicksToDraw;
+    	if (minorTicks == null) {
+    	  minorTicksToDraw = computeTicks(max, min, tickBase, minorTickSpacing);
+    	} else {
+          minorTicksToDraw = computeTicks(max, min, minorTicks);
+    	}
+        // now lower * minorTickSpacing = value of lowest tick mark, and
+        // upper * minorTickSpacing = values of highest tick mark
+  
+        VisADLineArray minorTickArray = new VisADLineArray();
+        // Change DRM 21-Feb-2001
+        //nticks = (int) ((hilo[hilo.length-1]-hilo[0])/minorTickSpacing) + 1;
+        nticks = minorTicksToDraw.length;
+        float[] minorCoordinates = new float[6 * nticks];
+  
+        // draw tick marks
+        k = 0;
+        //for (long j=lower; j<=upper; j++) {  // Change DRM 21-Feb-2001
+        for (int j = 0; j < nticks; j++)
+        {
+          //double val = hilo[0] + (j * minorTickSpacing);
+          double val = minorTicksToDraw[j];
+          double a = (val - min) / (max - min);
+          for (int i=0; i<3; i++) {
+            if ((k + 3 + i) < minorCoordinates.length) {
+              // guard against error that cannot happen, but was seen?
+              minorCoordinates[k + i] =
+                (float) ((1.0 - a) * startn[i] + a * startp[i]);
+              // minor ticks are half the size of the major ticks
+              minorCoordinates[k + 3 + i] =
+                (float) (minorCoordinates[k + i] - TICKSIZE/2 * tickup[i]);
+            }
+          }
+          k += 6;
+        }
+        minorTickArray.vertexCount = 2 * (nticks);
+        minorTickArray.coordinates = minorCoordinates;
+        lineArrayVector.add(minorTickArray);
+      }
+  
+      // Title and labels
+      // by default, all labels rendered centered
+       TextControl.Justification justification =
+         TextControl.Justification.CENTER;
+  
+      // PlotText is controlled by the initial starting point, base (controls
+      // direction) and up (which way is up).  We handle 2D and 3D differently.
+      // In 2-D, titles are drawn along the positive direction of the axis.
+      // Labels are drawn in the Y-positive direction.
+  
+  
+      // Labels first
+      if (twoD) {
+        if (myAxis == X_AXIS) {
+           up = new double[] {0.0, scale, 0.0};
+        }
+        else if (myAxis == Y_AXIS) {
+           up = new double[] {-scale, 0.0, 0.0};
+        }
+      }
+  
+      // Draw the labels.  If user hasn't defined their own, make defaults.
+      if (!userLabels) {
+        //createStandardLabels(topval, botval, botval, 
+        //                     (labelAllTicks == false)
+        //                        ?(topval - botval):majorTickSpacing, 
+        //                     false);
+    	createLabels(hilo, false);
+      }
+  
+      double dist = 1.0 + TICKSIZE;   // dist from the line in the up direction;
+      double[] updir = (twoD != true) ? up : new double[] {0.0, scale, 0.0};
+      if (twoD) {
+        base = new double[] {scale, 0.0, 0.0};
+        if (myAxis == X_AXIS) {
+           dist = (side == PRIMARY)
+             ? (1.0 + TICKSIZE + .15)
+             : -(TICKSIZE + .15);
+        }
+        else if (myAxis == Y_AXIS) {
+           dist = (side == PRIMARY)
+             ? -(TICKSIZE + .15)
+             : (TICKSIZE + .15);
+           justification =
+             (side == PRIMARY)
+                 ? TextControl.Justification.RIGHT
+                 : TextControl.Justification.LEFT;
+        }
+      }
+  
+      // Added by Luke Catania on 05/07/2002
+      // Added maximumYAxisTickLabelSize & yAxisLabelLength to calculate 
+      // offset for Y-Axis label.
+      //
+      int maximumYAxisTickLabelSize = 1;
+      int yAxisLabelLength=0;
+      Hashtable localTable;
+      synchronized(labelTable) {
+          localTable = new Hashtable(labelTable);
+      }
+      for (Enumeration e = localTable.keys(); e.hasMoreElements();)
+      {
+        Double value;
+        try {
+          value = (Double) e.nextElement();
+        } catch (ClassCastException cce) {
+          throw new VisADException("Invalid keys in label hashtable");
+        }
+        double test = value.doubleValue();
+        if (test > max || test < min) continue; // don't draw labels beyond range
+  
+        // Added by Luke Catania on 05/07/2002 - mods by DRM 28-Oct-2002
+        // For Y-Axis only, calculate offset for axis label, so it does 
+        // not overlap the tick labels.
+        if (myAxis == Y_AXIS) {
+          yAxisLabelLength = ((String) localTable.get(value)).length();
+          if (yAxisLabelLength > maximumYAxisTickLabelSize)
+            maximumYAxisTickLabelSize = yAxisLabelLength;
+        }
+        double val = (test - min) / (max - min);
+        // center label on tick if Y axis and 2D
+        if ((myAxis == Y_AXIS) && (twoD == true)) val -= .2 * scale; // HACK!!!!!
+  
+        double[] point = new double[3];
+        for (int j=0; j < 3; j++) {
+          point[j] = (1.0 - val) * startn[j] + val * startp[j] - dist * up[j];
+  
+  //        if (myAxis == Y_AXIS) System.out.println("Axis & Tick Label Position for " + test + ": " + startn[j] + ":" + startp[j] + ":" + point[j]);
+        }
+  
+        /*
+        System.out.println("For label = " + value.doubleValue() + "(" + val + "), point is (" + point[0] + "," + point[1] + "," + point[2] + ")");
+        */
+  
+        if (labelFont == null)
+        {
+          VisADLineArray label =
+            PlotText.render_label((String) localTable.get(value), point, base, updir, justification);
+          lineArrayVector.add(label);
+        }
+        else if (labelFont instanceof Font)
+        {
+          VisADTriangleArray label =
+            PlotText.render_font(
+                (String) localTable.get(value), (Font) labelFont, point, base,
+                updir, justification);
+          labelArrayVector.add(label);
+  
+        } else if (labelFont instanceof HersheyFont) {
+          VisADLineArray label =
+            PlotText.render_font(
+                (String) localTable.get(value), (HersheyFont) labelFont,
+                   point, base, updir, justification);
+          lineArrayVector.add(label);
+        }
+      }
+  
+      // Title
+      double[] startlabel = new double[3];
+      dist = 2.0 + TICKSIZE;   // dist from the line in the up direction;
+      justification =
+         TextControl.Justification.CENTER;
+      if (twoD) {
+        if (myAxis == X_AXIS) {
+           base = new double[] {scale, 0.0, 0.0};
+           up = new double[] {0.0, scale, 0.0};
+           dist = (side == PRIMARY)
+             ? 2.5 + TICKSIZE
+             : -(1.5 + TICKSIZE - .05);
+        }
+        else if (myAxis == Y_AXIS) {
+           base = new double[] {0.0, scale, 0.0};
+           up = new double[] {-scale, 0.0, 0.0};
+           dist = (side == PRIMARY)
+             ? -(.5 + TICKSIZE + maximumYAxisTickLabelSize)
+             : (.5 + TICKSIZE + maximumYAxisTickLabelSize) ;
+        }
+      }
+      for (int i=0; i<3; i++) {
+        startlabel[i] = 0.5 * (startn[i] + startp[i]) - dist * up[i];
+      }
+      /*
+      System.out.println("For title, point is (" +
+        startlabel[0] + "," + startlabel[1] + "," + startlabel[2] + ")");
+      */
+  
+      if (labelFont == null)
+      {
+        VisADLineArray plotArray =
+          PlotText.render_label(myTitle, startlabel, base, up, justification);
+        lineArrayVector.add(plotArray);
+      }
+      else if (labelFont instanceof java.awt.Font)
+      {
+        VisADTriangleArray nameArray =
+          PlotText.render_font(myTitle, (Font) labelFont,
+                               startlabel, base, up, justification);
+        labelArrayVector.add(nameArray);
+      } else if (labelFont instanceof visad.util.HersheyFont) {
+        VisADLineArray plotArray =
+          PlotText.render_font(myTitle, (HersheyFont) labelFont,
+                               startlabel, base, up, justification);
+        lineArrayVector.add(plotArray);
+      }
+  
+      // merge the line arrays
+      VisADLineArray[] arrays =
+          (VisADLineArray[]) lineArrayVector.toArray(
+            new VisADLineArray[lineArrayVector.size()]);
+      scaleArray = VisADLineArray.merge(arrays);
+  
+      // merge the label arrays
+      labelArray = new VisADTriangleArray();
+      if ( !(labelArrayVector.isEmpty()) )
+      {
+        VisADTriangleArray[] labelArrays =
+            (VisADTriangleArray[]) labelArrayVector.toArray(
+              new VisADTriangleArray[labelArrayVector.size()]);
+        labelArray = VisADTriangleArray.merge(labelArrays);
+        // set the color for the label arrays
+        float[] rgb = myColor.getColorComponents(null);
+        byte red = ShadowType.floatToByte(rgb[0]);
+        byte green = ShadowType.floatToByte(rgb[1]);
+        byte blue = ShadowType.floatToByte(rgb[2]);
+        int n = 3 * labelArray.vertexCount;
+        byte[] colors = new byte[n];
+        for (int i=0; i<n; i+=3) {
+          colors[i] = red;
+          colors[i+1] = green;
+          colors[i+2] = blue;
+        }
+        labelArray.colors = colors;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Get the color of this axis scale.
+   *
+   * @return  Color of the scale.
+   */
+  public Color getColor()
+  {
+    return myColor;
+  }
+
+  /**
+   * Set the color of this axis scale.
+   * @param  color  Color to use
+   */
+  public void setColor(Color color)
+  {
+    Color oldColor = myColor;
+    myColor = color;
+    if (myColor != null && !myColor.equals(oldColor)) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Set the color of this axis scale.
+   * @param   color   array of red, green, and blue values in
+   *          the range (0.0 - 1.0). color must be float[3].
+   */
+  public void setColor(float[] color)
+  {
+    setColor(new Color(color[0], color[1], color[2]));
+  }
+
+  /**
+   * Clone the properties of this AxisScale.  Should only be used
+   * by ScalarMap and map should have the same DisplayScalar as
+   * this scalar's
+   * @param map  map to use for creating the new Axis
+   * @throws VisADException  display scalars are not equal
+   */
+  AxisScale clone(ScalarMap map)
+    throws VisADException
+  {
+    AxisScale newScale = new AxisScale(map);
+    if (!(map.getDisplayScalar().equals(scalarMap.getDisplayScalar())))
+      throw new VisADException(
+        "AxisScale: DisplayScalar for map is not" +
+          scalarMap.getDisplayScalar());
+    newScale.myColor = myColor;
+    newScale.axisOrdinal = axisOrdinal;
+    newScale.myAxis = myAxis;
+    newScale.myTitle = myTitle;
+    newScale.labelTable = (Hashtable) labelTable.clone();
+    newScale.majorTickSpacing = majorTickSpacing;
+    newScale.minorTickSpacing = minorTickSpacing;
+    newScale.autoComputeTicks = autoComputeTicks;
+    newScale.baseLineVisible = baseLineVisible;
+    newScale.snapToBox = snapToBox;
+    newScale.labelFont = labelFont;
+    newScale.labelSize = labelSize;
+    newScale.axisSide = axisSide;
+    newScale.tickOrient = tickOrient;
+    newScale.userLabels = userLabels;
+    newScale.labelAllTicks = labelAllTicks;
+    newScale.gridLinesVisible = gridLinesVisible;
+    newScale.ticksVisible = ticksVisible;
+    newScale.labelBothSides = labelBothSides;
+    return newScale;
+  }
+
+  /**
+   * Set major tick mark spacing. The number that is passed-in represents
+   * the distance, measured in values, between each major tick mark. If you
+   * have a ScalarMap with a range from 0 to 50 and the major tick spacing
+   * is set to 10, you will get major ticks next to the following values:
+   * 0, 10, 20, 30, 40, 50.  This value will always be used unless
+   * you call {@link #setAutoComputeTicks(boolean) setAutoComputeTicks}
+   * with a <CODE>true</CODE> value.
+   * @param spacing  spacing between major tick marks (must be > 0)
+   * @see #getMajorTickSpacing
+   * @see #setAutoComputeTicks
+   */
+  public void setMajorTickSpacing(double spacing)
+  {
+    double oldValue = majorTickSpacing;
+    majorTickSpacing = Math.abs(spacing);
+    autoComputeTicks = false;
+    if (majorTickSpacing != oldValue) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Set major tick marks.  Tick marks will be placed at the values on 
+   * the axis.
+   * @param majorTicks  the tick values
+   */
+  public void setMajorTicks(double[] majorTicks)
+  {
+    this.majorTicks = majorTicks;
+    autoComputeTicks = false;
+    try {
+      scalarMap.makeScale();  // update the display
+    }
+    catch (VisADException ve) {;}
+  }
+
+  /**
+   * Set minor tick marks.  Tick marks will be placed at the values on 
+   * the axis.
+   * @param minorTicks  the tick values
+   */
+  public void setMinorTicks(double[] minorTicks)
+  {
+    this.minorTicks = minorTicks;
+    autoComputeTicks = false;
+    try {
+      scalarMap.makeScale();  // update the display
+    }
+    catch (VisADException ve) {;}
+  }
+
+  /**
+   * This method returns the major tick spacing.  The number that is returned
+   * represents the distance, measured in values, between each major tick mark.
+   *
+   * @return the number of values between major ticks
+   * @see #setMajorTickSpacing
+   */
+  public double getMajorTickSpacing() {
+    return majorTickSpacing;
+  }
+
+  /**
+   * Set minor tick mark spacing. The number that is passed-in represents
+   * the distance, measured in values, between each minor tick mark. If you
+   * have a ScalarMap with a range from 0 to 50 and the minor tick spacing
+   * is set to 10, you will get minor ticks next to the following values:
+   * 0, 10, 20, 30, 40, 50.  This value will always be used unless
+   * you call {@link #setAutoComputeTicks(boolean) setAutoComputeTicks}
+   * with a <CODE>true</CODE> value.
+   * @param spacing  spacing between minor tick marks (must be > 0)
+   * @see #getMinorTickSpacing
+   * @see #setAutoComputeTicks
+   */
+  public void setMinorTickSpacing(double spacing)
+  {
+    double oldValue = minorTickSpacing;
+    minorTickSpacing = Math.abs(spacing);
+    if (minorTickSpacing != oldValue) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * This method returns the minor tick spacing.  The number that is returned
+   * represents the distance, measured in values, between each minor tick mark.
+   *
+   * @return the number of values between minor ticks
+   * @see #setMinorTickSpacing
+   */
+  public double getMinorTickSpacing() {
+    return minorTickSpacing;
+  }
+
+  /**
+   * Allow the AxisScale to automatically compute the desired majorTickSpacing
+   * based on the range of the ScalarMap.
+   * @param b  if true, have majorTickSpacing automatically computed.
+   */
+  public void setAutoComputeTicks(boolean b)
+  {
+    boolean oldValue = autoComputeTicks;
+    autoComputeTicks = b;
+    if (autoComputeTicks != oldValue) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Creates a hashtable that will draw text labels starting at the
+   * starting point specified using the increment field.
+   * If you call createStandardLabels(100, 0, 2.0, 10.0), then it will
+   * make labels for the values 2, 12, 22, 32, etc.
+   *
+   * @see #setLabelTable
+   * @throws IllegalArgumentException  if min > max, or increment is
+   *                                   greater than max-min
+   */
+  public void createStandardLabels(
+    double max, double min, double base, double increment)
+  {
+    if (min > max) {
+      throw new IllegalArgumentException("max must be greater than min");
+    }
+    if (increment > (max-min)) {
+      throw new IllegalArgumentException(
+        "increment must be less than or equal to range (max-min)");
+    }
+    createStandardLabels(max, min, base, increment, true);
+  }
+
+  /**
+   * private copy to allow program to create table, but not remake scale
+   */
+  private void createStandardLabels(
+    double max, double min, double base, double increment, boolean byuser)
+  {
+    synchronized(labelTable) {
+       labelTable.clear();
+       double[] values = computeTicks(max, min, base, increment);
+       if (values != null) {
+          for (int i = 0; i < values.length; i++) {
+            labelTable.put(new Double(values[i]), createLabelString(values[i]));
+          }
+        }
+    }
+    if (byuser) {
+      try {
+        userLabels = true;
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * private copy to allow program to create table, but not remake scale
+   */
+  private void createLabels(double[] values, boolean byuser)
+  {
+    synchronized(labelTable) {
+       labelTable.clear();
+       if (values != null) {
+    	 if (getLabelAllTicks()) {
+            for (int i = 0; i < values.length; i++) {
+              labelTable.put(new Double(values[i]), createLabelString(values[i]));
+            }
+         } else{
+            labelTable.put(new Double(values[0]), createLabelString(values[0]));
+            labelTable.put(new Double(values[values.length-1]), createLabelString(values[values.length-1]));
+         }
+       }
+    }
+    if (byuser) {
+      try {
+        userLabels = true;
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Used to specify what label will be drawn at any given value.
+   * The key-value pairs are of this format:
+   *     <B>{ Double value, java.lang.String}</B>
+   *
+   * @param  labels  map of value/label pairs
+   * @throws VisADException  invalid hashtable
+   * @see #getLabelTable
+   */
+  public void setLabelTable( Hashtable labels )
+    throws VisADException
+  {
+    Map oldTable = labelTable;
+    labelTable = labels;
+    if (labels != oldTable) {
+      userLabels = true;
+      scalarMap.makeScale();  // update the display
+    }
+  }
+
+  /**
+   * Get the Hashtable used for labels
+   */
+  public Hashtable getLabelTable()
+  {
+    return labelTable;
+  }
+
+  /**
+   * Set the font used for rendering the labels
+   * @param font  new font to use
+   */
+  public void setFont(Font font)
+  {
+    Object oldFont = labelFont;
+    labelFont = font;
+    if ((labelFont == null && oldFont != null) || 
+    	(labelFont != null && !labelFont.equals(oldFont)))
+    //if (labelFont != null && !labelFont.equals(oldFont))
+    {
+      if (labelFont != null && labelFont instanceof java.awt.Font) {
+    	labelSize = ((Font) labelFont).getSize();
+      }
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Set the font used for rendering the labels
+   * @param font  new font to use
+   */
+  public void setFont(HersheyFont font)
+  {
+    Object oldFont = labelFont;
+    labelFont = font;
+    if ((labelFont == null && oldFont != null) || 
+    	(labelFont != null && !labelFont.equals(oldFont)))
+    //if (labelFont != null && !labelFont.equals(oldFont))
+    {
+      labelSize = 12;
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Get the font used for rendering the labels
+   * @return  font use or null if using default text plot
+   */
+  public Font getFont()
+  {
+    return (labelFont instanceof Font) ? (Font)labelFont : null;
+  }
+
+  /**
+   * Set visibility of base line.
+   * @param  visible   true to display (default), false to turn off
+   */
+  public void setBaseLineVisible(boolean visible)
+  {
+    boolean oldValue = baseLineVisible;
+    baseLineVisible = visible;
+    if (baseLineVisible != oldValue) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Determine whether the base line for the scale should be visible
+   * @return  true if line is visible, otherwise false;
+   */
+  public boolean getBaseLineVisible()
+  {
+    return baseLineVisible;
+  }
+
+  /**
+   * Toggle whether the scale is along the box edge or not
+   * @param b   true to snap to the box
+   */
+  public void setSnapToBox(boolean b)
+  {
+    boolean oldValue = snapToBox;
+    snapToBox = b;
+    if (snapToBox != oldValue) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Determine whether this property is set.
+   * @return  true if property is set, otherwise false;
+   */
+  public boolean getSnapToBox()
+  {
+    return snapToBox;
+  }
+
+  /**
+   * Sets the size of the labels.  You can use this to change the label
+   * size when a <CODE>Font</CODE> is not being used.  If a <CODE>Font</CODE>
+   * is being used and you call setLabelSize(), a new <CODE>Font</CODE> is
+   * created using the old <CODE>Font</CODE> name and style, but with the
+   * new size.
+   * @param  size  font size to use
+   * @see #setFont
+   */
+  public void setLabelSize(int size)
+  {
+    int oldSize = labelSize;
+    labelSize = size;
+    if (labelSize != oldSize) {
+      if (labelFont != null) {
+        if (labelFont instanceof java.awt.Font) labelFont =
+            new Font( ((Font)labelFont).getName(),
+                      ((Font)labelFont).getStyle(), labelSize);
+      }
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Gets the size of the labels.
+   * @return  relative size of labels
+   */
+  public int getLabelSize()
+  {
+    return labelSize;
+  }
+
+  /**
+   * Sets the base value for tick marks.  This only applies when
+   * <CODE>setMajorTickSpacing</CODE> or <CODE>setMinorTickSpacing</CODE>
+   * have been called.
+   * @param  base  base value for drawing tick marks.  For example, if
+   *               your scale ranges from -4 to 18 and you set the
+   *               major tick spacing to 5, you will get ticks at
+   *               -4, 1, 6, 11, and 16 by default.  If you set the tick
+   *               base value to 0, you will get ticks at 0, 5, 10, 15.
+   */
+  public void setTickBase(double base)
+  {
+    double oldBase = tickBase;
+    tickBase = base;
+    if (tickBase != oldBase) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Set side for axis (PRIMARY, SECONDARY)
+   * @param side side for axis to appear on
+   */
+  public void setSide(int side)
+  {
+    // sanity check
+    if (side != PRIMARY && side != SECONDARY &&
+      side != TERTIARY && side != QUATERNARY)
+    {
+      return;
+    }
+    if (axisSide == side) return;
+    axisSide = side;
+    try {
+      scalarMap.makeScale();  // update the display
+    }
+    catch (VisADException ve) {;}
+  }
+
+  /**
+   * Get the alignment for the axis
+   * @return  axis alignment (PRIMARY or SECONDARY)
+   */
+  public int getSide()
+  {
+    return axisSide;
+  }
+
+  /**
+   * Set orientation of tick marks along the axis line.
+   * @param orient (PRIMARY or SECONDARY)
+   */
+  public void setTickOrientation(int orient)
+  {
+    double oldOrient = tickOrient;
+    tickOrient =
+      (orient == SECONDARY) ? SECONDARY : PRIMARY;  // sanity check
+    if (tickOrient != oldOrient) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Get the orientation for the ticks along the axis
+   * @return  tick orientation (PRIMARY or SECONDARY)
+   */
+  public int getTickOrientation()
+  {
+    return tickOrient;
+  }
+
+  /**
+   * Set the formatting for all labels
+   * @param format  format string
+   */
+  public void setNumberFormat(NumberFormat format)
+  {
+    labelFormat = format;
+  }
+
+  /**
+   * Get the formatting for labels.  May be null (if not set)
+   * @return format used for labeling
+   */
+  public NumberFormat getNumberFormat() { return labelFormat; }
+
+  /**
+   * Set the visibility of the AxisScale
+   * @param visible  true to display the AxisScale
+   */
+  public void setVisible(boolean visible) {
+    boolean oldVisibility = visibility;
+    visibility = visible;
+    if (!(oldVisibility == visibility) ) {
+      try {
+        // check for case if this was called from scalarmap.setScaleEnable()
+        if ( !(visible == scalarMap.getScaleEnable()) ) {
+          scalarMap.setScaleEnable(visible);
+	}
+        scalarMap.makeScale();  // update the display
+      } catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Get the visibility of the AxisScale
+   * @return true if AxisScale is being rendered
+   */
+  public boolean isVisible() {
+    return scalarMap.getScaleEnable();
+  }
+
+  /**
+   * Set the visibility of the grid lines; Grid lines are placed
+   * at major tick marks.
+   * @param show  true to display the grid lines
+   */
+  public void setGridLinesVisible(boolean show) {
+    boolean oldShow = gridLinesVisible;
+    gridLinesVisible = show;
+    if (!(oldShow == show) ) {
+      try {
+        scalarMap.makeScale();  // update the display
+      } catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Get the visibility of the grid lines
+   * @return true if grid lines are being rendered
+   */
+  public boolean getGridLinesVisible() {
+    return gridLinesVisible;
+  }
+
+  /**
+   * Set whether both sides are labeled.
+   * @param both  true to label both sides
+   */
+  public void setLabelBothSides(boolean both) {
+    boolean oldBoth = labelBothSides;
+    labelBothSides = both;
+    if (!(oldBoth == both) ) {
+      try {
+        scalarMap.makeScale();  // update the display
+      } catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * See if both sides are labeled
+   * @return true if labelling is on both sides
+   */
+  public boolean getLabelBothSides() {
+    return labelBothSides;
+  }
+
+
+  /**
+   * Set whether ticks are visible
+   * @param visible  true to show ticks
+   */
+  public void setTicksVisible(boolean visible) {
+    boolean oldValue = ticksVisible;
+    ticksVisible = visible;
+    if (!(oldValue == visible) ) {
+      try {
+        scalarMap.makeScale();  // update the display
+      } catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * See if ticks are visible
+   * @return true if labeling is on both sides
+   */
+  public boolean getTicksVisible() {
+    return ticksVisible;
+  }
+
+
+  /**
+   * Set whether all major ticks should be labeled.  The default is
+   * to only label the first and last major tick.  This setting is 
+   * ignored if user labels are being used or if user manually
+   * calls {@link #createStandardLabels(double, double, double, double) 
+   * createStandardLabels} or {@link #setLabelTable(Hashtable) 
+   * setLabelTable}
+   * @see #getLabelAllTicks()
+   * @see #createStandardLabels(double, double, double, double)
+   * @see #setLabelTable(Hashtable)
+   *
+   * @param labelAll  true to label all (major) ticks.  Overridden
+   */
+  public void setLabelAllTicks(boolean labelAll) {
+    boolean oldValue = labelAllTicks;
+    labelAllTicks = labelAll;
+    if (labelAllTicks != oldValue) {
+      try {
+        scalarMap.makeScale();  // update the display
+      }
+      catch (VisADException ve) {;}
+    }
+  }
+
+  /**
+   * Return whether all major ticks are to be labeled.
+   * @return true if ticks are to be labeled.
+   */
+  public boolean getLabelAllTicks() {
+     return labelAllTicks;
+  }
+
+  /** compute the tick mark values */
+  private double[] computeTicks(double high, double low,
+                                double base, double interval)
+  {
+    double[] vals = null;
+
+    // compute nlo and nhi, for low and high contour values in the box
+    long nlo = Math.round((Math.ceil((low - base) / Math.abs(interval))));
+    long nhi = Math.round((Math.floor((high - base) / Math.abs(interval))));
+
+    // how many contour lines are needed.
+    int numc = (int) (nhi - nlo) + 1;
+    if (numc < 1) return new double[] {low, high};
+
+    vals = new double[numc];
+
+    for(int i = 0; i < numc; i++) {
+      vals[i] = base + (nlo + i) * interval;
+    }
+
+    return vals;
+  }
+
+  /** compute the tick mark values that are between max and min */
+  private double[] computeTicks(double max, double min,
+                                double[] ticks)
+  {
+    double[] vals = new double[ticks.length];
+    Arrays.sort(ticks);
+    int numTicks = 0;
+    for (int i = 0; i < ticks.length; i++) {
+      double tick = ticks[i];
+      if (tick <= max && tick >= min) { 
+        vals[numTicks++] = tick;
+      }
+    }
+    if (numTicks < ticks.length) {
+       double[] newVals = new double[numTicks];
+       System.arraycopy(vals,0,newVals,0,numTicks);
+       return newVals;
+    }
+    return vals;
+  }
+
+  /** create the default string for a value */
+  private String createLabelString(double value)
+  {
+    String     label = null;
+    ScalarType sType = scalarMap.getScalar();
+    if (sType instanceof RealType)
+    {
+      RealType rType = (RealType)sType;
+      Unit     unit = rType.getDefaultUnit();
+      if (Unit.canConvert(CommonUnit.secondsSinceTheEpoch, unit) &&
+        !unit.getAbsoluteUnit().equals(unit))
+      {
+         label = new Real(rType, value).toValueString();
+      }
+      else
+      {
+        label =
+          (labelFormat != null)
+            ? labelFormat.format(value)
+            : PlotText.shortString(value);
+      }
+    }
+    else
+    {
+      label =
+        (labelFormat != null)
+          ? labelFormat.format(value)
+          : PlotText.shortString(value);
+    }
+    return label;
+  }
+
+  /**
+   * Checks if is label has relief.
+   *
+   * @return true, if is label has relief
+   */
+  public boolean isLabelRelief() {
+    return labelRelief;
+  }
+
+  /**
+   * Sets the label relief.
+   *
+   * @param labelRelief the new label relief
+   */
+  public void setLabelRelief(boolean labelRelief) {
+    this.labelRelief = labelRelief;
+  }
+
+}
diff --git a/visad/BadDirectManipulationException.java b/visad/BadDirectManipulationException.java
new file mode 100644
index 0000000..18cb323
--- /dev/null
+++ b/visad/BadDirectManipulationException.java
@@ -0,0 +1,47 @@
+//
+// BadDirectManipulationException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   BadDirectManipulationException is an exception for an illegal
+   DirectManipulation with a VisAD display.<P>
+*/
+public class BadDirectManipulationException extends BadMappingException {
+
+  /**
+   * construct a BadDirectManipulationException with no message
+   */
+  public BadDirectManipulationException() { super(); }
+
+  /**
+   * construct a BadDirectManipulationException with given message
+   * @param s - message String
+   */
+  public BadDirectManipulationException(String s) { super(s); }
+
+}
+
diff --git a/visad/BadMappingException.java b/visad/BadMappingException.java
new file mode 100644
index 0000000..7fce9a3
--- /dev/null
+++ b/visad/BadMappingException.java
@@ -0,0 +1,47 @@
+//
+// BadMappingException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   BadMappingException is an exception for an error with
+   ScalarMaps in a VisAD display.<P>
+*/
+public class BadMappingException extends DisplayException {
+
+  /**
+   * construct a BadMappingException with no message
+   */
+  public BadMappingException() { super(); }
+
+  /**
+   * construct a BadMappingException with given message
+   * @param s - message String
+   */
+  public BadMappingException(String s) { super(s); }
+
+}
+
diff --git a/visad/BaseColorControl.java b/visad/BaseColorControl.java
new file mode 100644
index 0000000..3c3a5e8
--- /dev/null
+++ b/visad/BaseColorControl.java
@@ -0,0 +1,941 @@
+//
+// BaseColorControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.StringTokenizer;
+
+import visad.browser.Convert;
+import visad.util.Util;
+
+/**
+   BaseColorControl is the VisAD class for controlling N-component Color
+   DisplayRealType-s.<P>
+*/
+public class BaseColorControl
+  extends Control
+{
+
+  /** The index of the color red */
+  public static final int RED = 0;
+  /** The index of the color green */
+  public static final int GREEN = 1;
+  /** The index of the color blue */
+  public static final int BLUE = 2;
+  /**
+   * The index of the alpha channel.
+   * <P>
+   * <B>NOTE:</B> ALPHA will always be the last index.
+   */
+  public static final int ALPHA = 3;
+
+  /** The default number of colors */
+  public final static int DEFAULT_NUMBER_OF_COLORS = 256;
+
+  // color map represented by either table or function
+  private float[][] table;
+  private int tableLength; // = table[0].length - 1
+  private Function function;
+  private transient RealTupleType functionDomainType;
+  private transient CoordinateSystem functionCoordinateSystem;
+  private transient Unit[] functionUnits;
+
+  private transient Object lock = new Object();
+
+  private final int components;
+
+  /**
+   * Create a basic color control.
+   *
+   * @param d The display with which this control is associated.
+   * @param components Either 3 (if this is a red/green/blue control)
+   *        or 4 (if there is also an alpha component).
+   */
+  public BaseColorControl(DisplayImpl d, int components)
+  {
+    super(d);
+
+    // constrain number of components to known range
+    if (components < 3) {
+      components = 3;
+    } else if (components > 4) {
+      components = 4;
+    }
+    this.components = components;
+
+    tableLength = DEFAULT_NUMBER_OF_COLORS;
+    table = initTableVis5D(new float[components][tableLength]);
+  }
+
+  /**
+   * Initialize table to a grey wedge.
+   *
+   * @param table Table to be initialized.
+   *
+   * @return the initialized table.
+   */
+
+  public static float[][] initTableGreyWedge(float[][] table) 
+  {
+    return initTableGreyWedge(table, false);
+  }
+
+  public static float[][] initTableGreyWedge(float[][] table, boolean invert)
+  {
+    if (table == null || table[0] == null) {
+      return null;
+    }
+
+    boolean hasAlpha = table.length > 3;
+
+    final int numColors = table[0].length;
+    float scale = (float) (1.0f / (float) (numColors - 1));
+    for (int i=0; i<numColors; i++) {
+      int idx = invert ? (numColors-1)-i : i;
+      table[RED][idx]   = scale * i;
+      table[GREEN][idx] = scale * i;
+      table[BLUE][idx]  = scale * i;
+      if (hasAlpha) {
+        table[ALPHA][idx] = scale * i;
+      }
+    }
+
+    return table;
+  }
+
+
+  /**
+   * Initialize the colormap to a grey wedge
+   */
+  public void initGreyWedge()
+  {
+    initTableGreyWedge(table);
+  }
+
+  public void initGreyWedge(boolean invert)
+  { 
+    initTableGreyWedge(table, invert);
+  }
+
+  /**
+   * Initialize table to the Vis5D colormap (opaque
+   *   blue-green-red rainbow).
+   *
+   * @param table Table to be initialized.
+   *
+   * @return the initialized table.
+   */
+  public static float[][] initTableVis5D(float[][] table)
+  {
+    if (table == null || table[0] == null) {
+      return null;
+    }
+
+    boolean hasAlpha = table.length > 3;
+
+    float curve = 1.4f;
+    float bias = 1.0f;
+    float rfact = 0.5f * bias;
+
+    final int numColors = table[0].length;
+    for (int i=0; i<numColors; i++) {
+
+      /* compute s in [0,1] */
+      float s = (float) i / (float) (numColors-1);
+      float t = curve * (s - rfact);   /* t in [curve*-0.5,curve*0.5) */
+
+      table[RED][i] = (float) (0.5 + 0.5 * Math.atan( 7.0*t ) / 1.57);
+      table[GREEN][i] = (float) (0.5 + 0.5 * (2 * Math.exp(-7*t*t) - 1));
+      table[BLUE][i] = (float) (0.5 + 0.5 * Math.atan( -7.0*t ) / 1.57);
+      if (hasAlpha) {
+        table[ALPHA][i] = 1.0f;
+      }
+    }
+
+    return table;
+  }
+
+  /**
+   * Initialize the colormap to the VisAD sine waves
+   */
+  public void initVis5D()
+  {
+    initTableVis5D(table);
+  }
+
+  /**
+   * Initialize table to the Hue-Saturation-Value colormap.
+   *
+   * @param table Table to be initialized.
+   *
+   * @return the initialized table.
+   */
+  public static float[][] initTableHSV(float[][] table)
+  {
+    if (table == null || table[0] == null) {
+      return null;
+    }
+
+    boolean hasAlpha = table.length > 3;
+
+    float s = 1;
+    float v = 1;
+
+    final int numColors = table[0].length;
+    for (int i=0; i<numColors; i++) {
+
+      float h = i * 6 / (float )(numColors - 1);
+
+      int hFloor = (int )Math.floor(h);
+      float hPart = h - hFloor;
+
+      // if hFloor is even
+      if ((hFloor & 1) == 0) {
+        hPart = 1 - hPart;
+      }
+
+      float m = v * (1 - s);
+      float n = v * (1 - s*hPart);
+
+      switch (hFloor) {
+      case 0:
+      case 6:
+        table[RED][i] = v;
+        table[GREEN][i] = n;
+        table[BLUE][i] = m;
+        break;
+      case 1:
+        table[RED][i] = n;
+        table[GREEN][i] = v;
+        table[BLUE][i] = m;
+        break;
+      case 2:
+        table[RED][i] = m;
+        table[GREEN][i] = v;
+        table[BLUE][i] = n;
+        break;
+      case 3:
+        table[RED][i] = m;
+        table[GREEN][i] = n;
+        table[BLUE][i] = v;
+        break;
+      case 4:
+        table[RED][i] = n;
+        table[GREEN][i] = m;
+        table[BLUE][i] = v;
+        break;
+      case 5:
+        table[RED][i] = v;
+        table[GREEN][i] = m;
+        table[BLUE][i] = n;
+        break;
+      }
+
+      if (hasAlpha) {
+        table[ALPHA][i] = 1.0f;
+      }
+    }
+
+    return table;
+  }
+
+  /**
+   * Initialize the colormap to Hue-Saturation-Value
+   */
+  public void initHSV() {
+    initTableHSV(table);
+  }
+
+  /**
+   * Get the number of components of the range.
+   *
+   * @return Either 3 or 4
+   */
+  public int getNumberOfComponents() { return components; }
+
+  /**
+   * Get the number of colors in the table.
+   *
+   * @return The number of colors in the colormap.
+   */
+  public int getNumberOfColors() { return tableLength; }
+
+  /**
+   * Define the color lookup by a <CODE>Function</CODE>, whose
+   * <CODE>MathType</CODE> must have a 1-D domain and a 3-D or
+   * 4-D <CODE>RealTupleType</CODE> range; the domain and range
+   * <CODE>Real</CODE>s must vary over the range (0.0, 1.0)
+   *
+   * @param func The new <CODE>Function</CODE>.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception VisADException If there was a problem with the function.
+   */
+  public void setFunction(Function func)
+    throws RemoteException, VisADException
+  {
+    FunctionType baseType;
+    if (components == 4) {
+      baseType = FunctionType.REAL_1TO4_FUNCTION;
+    } else {
+      baseType = FunctionType.REAL_1TO3_FUNCTION;
+    }
+    if (func == null ||
+        !func.getType().equalsExceptName(baseType)) {
+      throw new DisplayException("BaseColorControl.setFunction: " +
+                                 "function must be 1D-to-" + components + "D");
+    }
+    synchronized (lock) {
+      function = func;
+      functionDomainType = ((FunctionType) function.getType()).getDomain();
+      functionCoordinateSystem = function.getDomainCoordinateSystem();
+      functionUnits = function.getDomainUnits();
+      table = null;
+    }
+    changeControl(true);
+  }
+
+  /**
+   * Return the color lookup <CODE>Function</CODE>.
+   *
+   * @return The function which defines this object's colors.
+   */
+  public Function getFunction() { return function; }
+
+  /**
+   * Define the color lookup by an array of <CODE>float</CODE>s
+   * which must have the form <CODE>float[components][table_length]</CODE>;
+   * values should be in the range (0.0, 1.0)
+   *
+   * @param t The new table of colors.
+   *
+   * @exception RemoteException If there was a problem changing the control.
+   * @exception VisADException If there is a problem with the table.
+   */
+  public void setTable(float[][] t)
+    throws RemoteException, VisADException
+  {
+    if (t == null || t[0] == null) {
+      throw new DisplayException(getClass().getName() + ".setTable: " +
+                                 "Null table");
+    }
+
+    if (t.length != components) {
+      if (t[0].length == components) {
+        throw new DisplayException(getClass().getName() + ".setTable: " +
+                                   " Table may be inverted");
+      }
+      throw new DisplayException(getClass().getName() + ".setTable: " +
+                                 "Unusable table [" + t.length + "][" +
+                                 t[0].length + "], expected [" + components +
+                                 "][]");
+    }
+
+    if (t[RED] == null || t[GREEN] == null || t[BLUE] == null ||
+        (t.length > ALPHA && t[ALPHA] == null))
+    {
+      throw new DisplayException(getClass().getName() + ".setTable: " +
+                                 "One or more component lists is null");
+    }
+
+    if (t[RED].length != t[GREEN].length || t[RED].length != t[BLUE].length ||
+        (components > ALPHA && t[RED].length != t[ALPHA].length))
+    {
+      throw new DisplayException("BaseColorControl.setTable: " +
+                                 "Inconsistent table lengths");
+    }
+
+    synchronized (lock) {
+      tableLength = t[0].length;
+      table = new float[components][tableLength];
+      for (int j=0; j<components; j++) {
+        System.arraycopy(t[j], 0, table[j], 0, tableLength);
+      }
+      function = null;
+    }
+    changeControl(true);
+  }
+
+  /**
+   * Get the table of colors.
+   *
+   * @return The color table.
+   */
+  public float[][] getTable()
+  {
+    if (table == null) return null;
+    float[][] t = new float[components][tableLength];
+    for (int j=0; j<components; j++) {
+      System.arraycopy(table[j], 0, t[j], 0, tableLength);
+    }
+    return t;
+  }
+
+  /**
+   * If the colors are defined using a color table, get a
+   * <CODE>String</CODE> that can be used to reconstruct this
+   * object later. If the colors are defined using a
+   * <CODE>Function</CODE>, return null.
+   *
+   * @return The save string describing this object.
+   */
+  public String getSaveString()
+  {
+    if (table == null) return null;
+    int len = table.length;
+    int len0 = table[0].length;
+    StringBuffer sb = new StringBuffer(15 * len * len0);
+    sb.append(len);
+    sb.append(" x ");
+    sb.append(len0);
+    sb.append('\n');
+    for (int j=0; j<len0; j++) {
+      sb.append(table[RED][j]);
+      for (int i=1; i<len; i++) {
+        sb.append(' ');
+        sb.append(table[i][j]);
+      }
+      sb.append('\n');
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Reconstruct this control using the specified save string.
+   *
+   * @param save The save string.
+   *
+   * @exception VisADException If the save string is not valid.
+   * @exception RemoteException If there was a problem setting the table.
+   */
+  public void setSaveString(String save)
+    throws RemoteException, VisADException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save);
+    int numTokens = st.countTokens();
+    if (numTokens < 3) throw new VisADException("Invalid save string");
+
+    // get table size
+    int len = Convert.getInt(st.nextToken());
+    if (len < 1) {
+      throw new VisADException("First dimension is not positive");
+    }
+    if (!st.nextToken().equalsIgnoreCase("x")) {
+      throw new VisADException("Invalid save string");
+    }
+    int len0 = Convert.getInt(st.nextToken());
+    if (len0 < 1) {
+      throw new VisADException("Second dimension is not positive");
+    }
+    if (numTokens < 3 + len * len0) {
+      throw new VisADException("Not enough table entries");
+    }
+
+    // get table entries
+    float[][] t = new float[len][len0];
+    for (int j=0; j<len0; j++) {
+      for (int i=0; i<len; i++) t[i][j] = Convert.getFloat(st.nextToken());
+    }
+    setTable(t);
+  }
+
+  /**
+   * Return a list of colors for specified values.
+   *
+   * @param values		The values to look up.  It is expected that
+   *				they nominally lie in the range of 0 through 1.
+   *				Values outside this range will be assigned the
+   *				color of the nearest end.  NaN values will be
+   *				assigned NaN colors.
+   * @return			The list of colors.  Element <code>[i][j]</code>
+   *				is the value of the <code>i</code>-th color
+   *				component for <code>values[j]</code>, where
+   *				<code>i</code> is {@link #RED}, {@link #GREEN},
+   *				{@link #BLUE}, or {@link #ALPHA}.  A component
+   *				value is in the range from 0 through 1, or is
+   *				NaN.
+   * @throws RemoteException	If there was an RMI-related problem.
+   * @throws VisADException	If the function encountered a problem.
+   */
+  public float[][] lookupValues(float[] values)
+    throws RemoteException, VisADException
+  {
+    if (values == null) {
+      return null;
+    }
+
+    final int tblEnd = tableLength - 1;
+    final int valLen = values.length;
+
+    float[][] colors = null;
+    synchronized (lock) {
+      if (table != null) {
+        colors = new float[components][valLen];
+        float scale = (float) tableLength;
+        try {
+          for (int i=0; i<valLen; i++) {
+            if (values[i] != values[i]) {
+              colors[RED][i] = Float.NaN;
+              colors[GREEN][i] = Float.NaN;
+              colors[BLUE][i] = Float.NaN;
+              if (components > ALPHA) {
+                colors[ALPHA][i] = Float.NaN;
+              }
+            }
+            else {
+              int j = (int) (scale * values[i]);
+              // note actual table length is tableLength + 1
+              // extend first and last table entries to 'infinity'
+              if (j < 0) {
+                colors[RED][i] = table[RED][0];
+                colors[GREEN][i] = table[GREEN][0];
+                colors[BLUE][i] = table[BLUE][0];
+                if (components > ALPHA) {
+                  colors[ALPHA][i] = table[ALPHA][0];
+                }
+              }
+              else if (tableLength <= j) {
+                colors[RED][i] = table[RED][tblEnd];
+                colors[GREEN][i] = table[GREEN][tblEnd];
+                colors[BLUE][i] = table[BLUE][tblEnd];
+                if (components > ALPHA) {
+                  colors[ALPHA][i] = table[ALPHA][tblEnd];
+                }
+              }
+              else {
+                colors[RED][i] = table[RED][j];
+                colors[GREEN][i] = table[GREEN][j];
+                colors[BLUE][i] = table[BLUE][j];
+                if (components > ALPHA) {
+                  colors[ALPHA][i] = table[ALPHA][j];
+                }
+              }
+            }
+          } // end for (int i=0; i<valLen; i++)
+        }
+        catch (ArrayIndexOutOfBoundsException e) {
+        }
+      }
+      else if (function != null) {
+        List1DSet set = new List1DSet(values, functionDomainType,
+                                      functionCoordinateSystem,
+                                      functionUnits);
+        Field field =
+          function.resample(set, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+        colors = Set.doubleToFloat(field.getValues());
+      }
+    }
+    return colors;
+  }
+
+  /**
+   * Return a list of colors for the specified range.
+   */
+  public float[][] lookupRange(int left, int right)
+    throws VisADException, RemoteException
+  {
+    if (left < 0 || right >= tableLength || left > right) {
+      throw new VisADException("Bad left/right value");
+    }
+
+    final int tblEnd = tableLength - 1;
+    final int valLen = (right - left) + 1;
+
+    float[][] colors = null;
+    synchronized (lock) {
+      if (table != null) {
+        colors = new float[components][valLen];
+        for (int i=0; i<valLen; i++) {
+          colors[RED][i] = table[RED][i+left];
+          colors[GREEN][i] = table[GREEN][i+left];
+          colors[BLUE][i] = table[BLUE][i+left];
+          if (components > ALPHA) {
+            colors[ALPHA][i] = table[ALPHA][i+left];
+          }
+        }
+      } else if (function != null) {
+        double scale = (double) tableLength;
+        Linear1DSet set = new Linear1DSet(functionDomainType,
+                                          (double ) (left / scale),
+                                          (double ) (right / scale), valLen,
+                                          functionCoordinateSystem,
+                                          functionUnits, null);
+        Field field =
+          function.resample(set, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+        colors = Set.doubleToFloat(field.getValues());
+      }
+    }
+    return colors;
+  }
+
+  /**
+   * Set the specified range to the specified colors.
+   */
+  public void setRange(int left, int right, float[][] colors)
+    throws VisADException, RemoteException
+  {
+    if (left < 0 || right >= tableLength || left > right) {
+      throw new VisADException("Bad left/right value");
+    }
+
+    if (colors == null || colors.length != components ||
+        colors[RED] == null || colors[GREEN] == null ||
+        colors[BLUE] == null ||
+        (colors.length > ALPHA && colors[ALPHA] == null))
+    {
+      throw new VisADException("Bad range table!");
+    }
+
+    if (table == null) {
+      throw new VisADException("Cannot set values for function!");
+    }
+
+    final int valLen = (right - left) + 1;
+
+    if (colors[RED].length != valLen || colors[GREEN].length != valLen ||
+        colors[BLUE].length != valLen ||
+        (colors.length > ALPHA && colors[ALPHA].length != valLen))
+    {
+      throw new VisADException("Array does not contain " + valLen +
+                               " colors!");
+    }
+
+    synchronized (lock) {
+      for (int i=0; i<valLen; i++) {
+        table[RED][i+left] = colors[RED][i];
+        table[GREEN][i+left] = colors[GREEN][i];
+        table[BLUE][i+left] = colors[BLUE][i];
+        if (components > ALPHA) {
+          table[ALPHA][i+left] = colors[ALPHA][i];
+        }
+      }
+    }
+    changeControl(true);
+  }
+
+  /**
+   * Compare the specified table to this object's table.
+   *
+   * @param newTable Table to compare.
+   *
+   * @return <CODE>true</CODE> if <CODE>newTable</CODE> is the
+   *         same as this object's table.
+   */
+  private boolean tableEquals(float[][] newTable)
+  {
+    if (table == null) {
+      if (newTable != null) {
+        return false;
+      }
+    } else if (newTable == null) {
+      return false;
+    } else if (table != newTable) {
+      if (table.length != newTable.length) {
+        return false;
+      } else {
+        int i;
+        for (i = 0; i < table.length; i++) {
+          if (table[i].length != newTable[i].length) {
+            return false;
+          }
+        }
+        for (i = 0; i < table.length; i++) {
+          for (int j = 0; j < table[i].length; j++) {
+            if (!Util.isApproximatelyEqual(table[i][j], newTable[i][j])) {
+              return false;
+            }
+          }
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Compare the specified function to this object's function.
+   *
+   * @param newFunc Function to compare.
+   *
+   * @return <CODE>true</CODE> if <CODE>newFunc</CODE> is the
+   *         same as this object's function.
+   */
+  private boolean functionEquals(Function newFunc)
+  {
+    if (function == null) {
+      if (newFunc != null) {
+        return false;
+      }
+    } else if (newFunc == null) {
+      return false;
+    } else if (!function.equals(newFunc)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Copy the state of a remote control to this control.
+   *
+   * @param rmt The control to be copied.
+   *
+   * @exception VisADException If the remote control cannot be copied.
+   */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof BaseColorControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    BaseColorControl bcc = (BaseColorControl )rmt;
+
+    boolean changed = false;
+
+    boolean tableChanged = !tableEquals(bcc.table);
+    boolean functionChanged = !functionEquals(bcc.function);
+
+    if (tableChanged) {
+      if (bcc.table == null) {
+        if (functionChanged ? bcc.function == null : function == null) {
+          throw new VisADException("BaseColorControl has null Table," +
+                                   " but no Function");
+        }
+
+        table = null;
+      } else {
+        if (bcc.table.length != components) {
+          throw new VisADException("Table must be float[" + components +
+                                   "][], not float[" + bcc.table.length +
+                                   "][]");
+        }
+        synchronized (lock) {
+          tableLength = bcc.table[0].length;
+          for (int i = 0; i < components; i++) {
+            if (table[i].length != bcc.table[i].length) {
+              table[i] = new float[bcc.table[i].length];
+            }
+            System.arraycopy(bcc.table[i], 0, table[i], 0,
+                             bcc.table[i].length);
+          }
+          tableLength = table[0].length;
+          function = null;
+        }
+        try {
+          changeControl(true);
+        } catch (RemoteException re) {
+          throw new VisADException("Could not indicate that control" +
+                                   " changed: " + re.getMessage());
+        }
+      }
+    }
+    if (functionChanged) {
+      if (bcc.function == null) {
+        if (table == null) {
+          throw new VisADException("ColorControl has null Function," +
+                                   " but no Table");
+        }
+
+        function = null;
+      } else {
+        try {
+          setFunction(bcc.function);
+        } catch (RemoteException re) {
+          throw new VisADException("Could not set function: " +
+                                   re.getMessage());
+        }
+      }
+    }
+  }
+
+  /**
+   * Return <CODE>true</CODE> if this object is "equal" to the parameter.
+   *
+   * @param o Object to compare.
+   *
+   * @return <CODE>true</CODE> if this object "equals" <CODE>o</CODE>.
+   */
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    BaseColorControl bcc = (BaseColorControl )o;
+
+    if (tableLength != bcc.tableLength) {
+      return false;
+    }
+    if (!tableEquals(bcc.table)) {
+      return false;
+    }
+    if (!functionEquals(bcc.function)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  public Object clone()
+  {
+    BaseColorControl bcc = (BaseColorControl )super.clone();
+    if (table != null) {
+      bcc.table = new float[table.length][];
+      for (int i = table.length - 1; i >= 0; i--) {
+        bcc.table[i] = (float[] )table[i].clone();
+      }
+    }
+
+    return bcc;
+  }
+
+  private static char dirChar(int down, int same, int up)
+  {
+    char ch;
+
+    if (down == 0 || same == 0 || up == 0) {
+      if (down > 0) {
+        if (up > 0) {
+          if (down > up) {
+            return 'v';
+          }
+
+          return '^';
+        }
+
+        if (same > 0) {
+          return '~';
+        }
+
+        return '\\';
+      } else if (up > 0) {
+        if (same > 0) {
+          return '~';
+        }
+
+        return '/';
+      } else {
+        return '_';
+      }
+    }
+
+    if (down > same) {
+      if (down > (same + up)) {
+        return '\\';
+      }
+
+      if (up > (down + same)) {
+        return '/';
+      }
+
+      if (up > same) {
+        return '^';
+      }
+    }
+
+    if (up > same) {
+      if (up > (down + same)) {
+        return '/';
+      }
+
+      if (down > (same + up)) {
+        return '\\';
+      }
+    }
+
+    if (same > (down + up)) {
+      return '-';
+    }
+
+    return '~';
+  }
+
+  public String toString()
+  {
+    int binLen = tableLength;
+    int binSize = 1;
+    while (binLen > 32) {
+      binLen >>= 1;
+      binSize <<= 1;
+    }
+
+    String className = getClass().getName();
+    int dot = className.lastIndexOf('.');
+    if (dot >= 0) {
+      className = className.substring(dot+1);
+    }
+
+    StringBuffer buf = new StringBuffer(className);
+    buf.append('[');
+
+    String colorInitial = "RGBA";
+    for (int c = 0; c < components; c++) {
+      if (c > 0) {
+        buf.append(',');
+      }
+      buf.append(colorInitial.charAt(c));
+      buf.append('=');
+
+      float prev = table[c][0];
+
+      int tot = 0;
+      while (tot < tableLength) {
+        int trendDown, trendSame, trendUp;
+        trendDown = trendSame = trendUp = 0;
+
+        for (int i = 0; i < binSize; i++) {
+          float curr = table[c][tot+i];
+
+          if (Math.abs(curr - prev) <= 0.0001) {
+            trendSame++;
+          } else if (curr < prev) {
+            trendDown++;
+          } else {
+            trendUp++;
+          }
+
+          prev = curr;
+        }
+
+        buf.append(dirChar(trendDown, trendSame, trendUp));
+
+        tot += binSize;
+      }
+    }
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/BaseQuantity.java b/visad/BaseQuantity.java
new file mode 100644
index 0000000..fcad3b4
--- /dev/null
+++ b/visad/BaseQuantity.java
@@ -0,0 +1,302 @@
+//
+// BaseQuantity.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.io.Serializable;
+import java.util.AbstractList;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Vector;
+
+
+/**
+ * This class represents a base quantity (e.g. "length").
+ *
+ * This class is mutable but monotonic: new base quantities can be added but
+ * not removed.
+ */
+public final class BaseQuantity
+  implements	Serializable
+{
+  /**
+   * The name/quantity map-view of the database.
+   */
+  private static final Map		nameDB = new TreeMap();
+
+  /**
+   * The alias/quantity map-view of the database.
+   */
+  private static final Map		aliasDB = new TreeMap();
+
+  /**
+   * The index/quantity map-view of the database.
+   */
+  private static final AbstractList	indexDB = new Vector(8);
+
+  /**
+   * The name of this base quantity.
+   */
+  private final String			name;
+
+  /**
+   * The index of this base quantity.
+   */
+  private final int			index;
+
+
+  /*
+   * Initialize the database with standard base quantities.
+   */
+  static
+  {
+    try
+    {
+      add("electric current",
+	new String[] {SI.ampere.quantityName(), "current"});
+      add("luminous intensity", SI.candela.quantityName());
+      add("thermodynamic temperature",
+	new String[] {SI.kelvin.quantityName(), "temperature"});
+      add("mass", SI.kilogram.quantityName());
+      add("length", SI.meter.quantityName());
+      add("time", SI.second.quantityName());
+      add("amount of substance", SI.mole.quantityName());
+      add("plane angle", new String[] {SI.radian.quantityName(), "angle"});
+      add("solid angle", SI.steradian.quantityName());
+    }
+    catch (VisADException e)
+    {
+      /*
+       * With our godlike powers of observation, we know that this
+       * VisADException can't occur -- so we ignore it.
+       */
+    }
+  }
+
+
+  /**
+   * Construct a base quantity.  Private to ensure use of the get...()
+   * methods.
+   *
+   * @param name	The name of the base qantity.
+   */
+  private BaseQuantity(String name, int index)
+  {
+    this.name = name;
+    this.index = index;
+  }
+
+
+  /**
+   * Add a base quantity to the database.
+   *
+   * @param name		The name of the base quantity.
+   * @precondition		<code>name</code> isn't already in the database.
+   * @exception VisADException	Attempt to redefine an existing base quantity.
+   */
+  public static synchronized BaseQuantity add(String name)
+    throws VisADException
+  {
+    String	key = key(name);
+
+    if (nameDB.containsKey(key))
+      throw new VisADException("Attempt to redefine existing base quantity \"" +
+	name + "\"");
+
+    BaseQuantity	q = new BaseQuantity(name, indexDB.size());
+
+    nameDB.put(key, q);
+    indexDB.add(q);
+
+    return q;
+  }
+
+
+  /**
+   * Add a base quantity with an alias to the database.
+   *
+   * @param name		The name of the base quantity being added to
+   *				the database (e.g. "plane angle", "foobility").
+   * @param alias		An alias for the base quantity (e.g. "angle").
+   * @precondition              Neither <code>name</code> nor <code>alias</code>
+   *				is in the database.
+   * @return			A reference to the new base quantity in the
+   *				database.
+   * @exception VisADException  Attempt to redefine an existing base quantity
+   *				or alias.  If thrown, then the database is
+   *				unmodified.
+   */
+  public static synchronized BaseQuantity add(String name, String alias)
+    throws VisADException
+  {
+    return add(name, new String[] {alias});
+  }
+
+
+  /**
+   * Add a base quantity with aliases to the database.
+   *
+   * @param name		The name of the base quantity being added to
+   *				the database (e.g. "plane angle", "foobility").
+   * @param aliases		Aliases for the base quantity (e.g. "angle").
+   * @precondition		Neither <code>name</code> nor any name in
+   *				<code>aliases</code> is in the database.
+   * @postcondition		<code>size()</code> will return one greater
+   *				than on entry.
+   * @return			A reference to the new base quantity in the
+   *				database.
+   * @exception VisADException	Attempt to redefine an existing base quantity
+   *				or alias.  If thrown, then the database is
+   *				unmodified.
+   */
+  public static synchronized BaseQuantity add(String name, String[] aliases)
+    throws VisADException
+  {
+    for (int i = 0; i < aliases.length; ++i)
+    {
+      if (aliasDB.containsKey(key(aliases[i])))
+	throw new VisADException(
+	  "Attempt to redefine existing base quantity alias \"" +
+	  aliases[i] + "\"");
+    }
+
+    BaseQuantity	q = add(name);
+
+    for (int i = 0; i < aliases.length; ++i)
+      aliasDB.put(key(aliases[i]), q);
+
+    return q;
+  }
+
+
+  /**
+   * Convert the given name into a database key.
+   *
+   * @param name		The name or alias of the base quantity.
+   * @return			The database key.
+   */
+  private static String key(String name)
+  {
+    return name.toLowerCase();
+  }
+
+
+  /**
+   * Return the number of base qantities in the database.
+   *
+   * @return			The current number of base quantities in the
+   *				database.  This number is strictly monotonic:
+   *				it will increase by one each time a new base
+   *				quantity is added to the database.
+   */
+  public static synchronized int size()
+  {
+    return indexDB.size();
+  }
+
+
+  /**
+   * Return the name of this base quantity.
+   *
+   * @return			The name of this base quantity.
+   */
+  public String getName()
+  {
+    return name;
+  }
+
+
+  /**
+   * Return the index of this base quantity.
+   *
+   * @return			The index of this base quantity.
+   */
+  public int getIndex()
+  {
+    return index;
+  }
+
+
+  /**
+   * Retrieve a base quantity from the database based on a
+   * match of the name.
+   *
+   * @param name		The name of the base quantity to be retrieved.
+   * @return			The base quantity in the database corresponding
+   *				to <code>name</code> or <code>null</code> if
+   *				no such quantity exists.
+   */
+  public static synchronized BaseQuantity getByName(String name)
+  {
+    return (BaseQuantity)nameDB.get(key(name));
+  }
+
+
+  /**
+   * Retrieve a base quantity from the database based on a
+   * match of an alias.
+   *
+   * @param name		An alias of the base quantity to be retrieved.
+   * @return			The base quantity in the database corresponding
+   *				to <code>name</code> or <code>null</code> if
+   *				no such quantity exists.
+   */
+  public static synchronized BaseQuantity getByAlias(String name)
+  {
+    return (BaseQuantity)aliasDB.get(key(name));
+  }
+
+
+  /**
+   * Retrieve a base quantity from the database based on a
+   * match of either the name or an alias.  Try the name first.
+   *
+   * @param name		The name or an alias of the base quantity to
+   *				be retrieved.
+   * @return			The base quantity in the database corresponding
+   *				to <code>name</code> or <code>null</code> if
+   *				no such quantity exists.
+   */
+  public static synchronized BaseQuantity get(String name)
+  {
+    BaseQuantity	q = getByName(name);
+
+    return q != null ? q : getByAlias(name);
+  }
+
+
+  /**
+   * Retrieve the base quantity associated with a given index.
+   *
+   * @param i			The origin-0 index of the base quantity.
+   * @precondition		<code>i >= 0 && i < size()</code>.
+   * @return			The base quantity at index <code>i</code>.
+   */
+  public static BaseQuantity get(int i)
+  {
+    return (BaseQuantity)indexDB.get(i);
+  }
+}
diff --git a/visad/BaseUnit.java b/visad/BaseUnit.java
new file mode 100644
index 0000000..d9cc5cf
--- /dev/null
+++ b/visad/BaseUnit.java
@@ -0,0 +1,924 @@
+//
+// BaseUnit.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Vector;
+
+/**
+ * A class that represents the base units of a system of units.
+ * 
+ * Instances are immutable.
+ * 
+ * @author Steven R. Emmerson
+ * 
+ *         This is part of Steve Emerson's Unit package that has been
+ *         incorporated into VisAD.
+ */
+public final class BaseUnit extends Unit implements Serializable {
+    private static final long               serialVersionUID    = 1L;
+
+    /**
+     * Name of the unit (e.g. "meter").
+     */
+    private final String                    unitName;
+
+    /**
+     * Quantity of the unit (e.g. "Length").
+     */
+    private final String                    quantityName;
+
+    /**
+     * Derived unit associated with base unit (for computational efficiency).
+     */
+    final DerivedUnit                       derivedUnit;
+
+    final boolean                           isDimless;
+
+    /**
+     * Global database of base units (to prevent multiple base units for the
+     * same quantity).
+     */
+    private static final Vector<BaseUnit>   baseUnits           = new Vector<BaseUnit>(
+                                                                        9);
+
+    /**
+     * Constructs a base unit from the names for the quantity and unit, a the
+     * unit abbreviation, and whether or not the unit is dimensionless.
+     * 
+     * @param unitName
+     *            Name of the unit (e.g. "meter").
+     * @param abbreviation
+     *            The abbreviation for the unit (e.g. "m").
+     * @param quantityName
+     *            Name of the quantity (e.g. "Length").
+     * @param isDimless
+     *            Whether or not the unit is dimensionless.
+     * @throws UnitException
+     *             Name, abbreviation, or quantity name is <code>
+     *                          null</code>
+     *             .
+     */
+    private BaseUnit(final String unitName, final String abbreviation,
+            final String quantityName, final boolean isDimless)
+            throws UnitException {
+        super(abbreviation);
+        if (unitName == null || abbreviation == null || quantityName == null) {
+            throw new UnitException(
+                    "Base unit name, abbreviation, or quantity name is null");
+        }
+        this.unitName = unitName;
+        this.quantityName = quantityName;
+        baseUnits.addElement(this);
+        derivedUnit = new DerivedUnit(this);
+        this.isDimless = isDimless;
+    }
+
+    /**
+     * <p>
+     * Indicates if this instance is dimensionless. A unit is dimensionless if
+     * it is a measure of a dimensionless quantity like angle or concentration.
+     * Examples of dimensionless base units include radian, degree, and
+     * steradian.
+     * </p>
+     * 
+     * @return True if an only if this unit is dimensionless.
+     */
+    @Override
+    public boolean isDimensionless() {
+        return isDimless;
+    }
+
+    @Override
+    public Unit scale(final double amount) throws UnitException {
+        return ScaledUnit.getInstance(amount, this);
+    }
+
+    @Override
+    public Unit shift(final double offset) throws UnitException {
+        return OffsetUnit.getInstance(offset, this);
+    }
+
+    @Override
+    public Unit log(final double base) {
+        return derivedUnit.log(base);
+    }
+
+    /**
+     * Raise a base unit to a power.
+     * 
+     * @param power
+     *            The power to raise this unit by.
+     * @return The unit resulting from raising this unit to <code>power</code>.
+     * @promise This unit has not been modified.
+     */
+    @Override
+    public Unit pow(final int power) {
+        return derivedUnit.pow(power);
+    }
+
+    /**
+     * Raise a unit to a power.
+     * 
+     * @param power
+     *            The power to raise this unit by. The value must be integral or
+     *            reciprocal integral.
+     * @return The unit resulting from raising this unit to <code>power</code>.
+     * @throws IllegalArgumentException
+     *             <code>power</code> has a non-integral or non-reciprocal
+     *             integral value.
+     * @promise The unit has not been modified.
+     */
+    @Override
+    public Unit pow(final double power) throws IllegalArgumentException {
+        return derivedUnit.pow(power);
+    }
+
+    /**
+     * Returns the N-th root of this unit.
+     * 
+     * @param root
+     *            The root to take (e.g. 2 means square root). May not be zero.
+     * @return The unit corresponding to the <code>root</code>-th root of this
+     *         unit.
+     * @promise This unit has not been modified.
+     * @throws IllegalArgumentException
+     *             The root value is zero or the resulting unit would have a
+     *             non-integral unit dimension.
+     */
+    @Override
+    public Unit root(final int root) throws IllegalArgumentException {
+        return derivedUnit.root(root);
+    }
+
+    /**
+     * Return the name of this unit.
+     * 
+     * @return The name of this unit (e.g. "meter").
+     */
+    public String unitName() {
+        return unitName;
+    }
+
+    /**
+     * Return the symbol of this unit. This is the same as the identifier.
+     * 
+     * @return The symbol of this unit (e.g. "m").
+     */
+    public String unitSymbol() {
+        return getIdentifier();
+    }
+
+    /**
+     * Return the name of the quantity associated with this unit.
+     * 
+     * @return The name this units quantity (e.g. "Length").
+     */
+    public String quantityName() {
+        return quantityName;
+    }
+
+    /**
+     * Create a new base unit from the name of a quantity and the name of a
+     * unit. The unit abbreviation will be the same as the unit name. The unit
+     * will not be dimensionless.
+     * 
+     * @param quantityName
+     *            The name of the associated quantity (e.g. "Length").
+     * @param unitName
+     *            The name for the unit (e.g. "meter").
+     * @return A new base unit or the previously created one with the same
+     *         names.
+     * @require The arguments are non-null. The quantity name has not been used
+     *          before or the unit name is the same as before.
+     * @promise The new quantity and unit has been added to the database.
+     * @throws UnitException
+     *             Name, abbreviation, or quantity name is <code>
+     *                          null</code>
+     *             or attempt to redefine the base unit associated with
+     *             <code>quantityName</code>.
+     */
+    public static BaseUnit addBaseUnit(final String quantityName,
+            final String unitName) throws UnitException {
+        return addBaseUnit(quantityName, unitName, unitName);
+    }
+
+    /**
+     * Create a new base unit from from the name of a quantity, the name of a
+     * unit, and the unit's abbreviation. The unit will not be dimensionless.
+     * 
+     * @param quantityName
+     *            The name of the associated quantity (e.g. "Length").
+     * @param unitName
+     *            The name for the unit (e.g. "meter").
+     * @param abbreviation
+     *            The abbreviation for the unit (e.g. "m").
+     * @return A new base unit or the previously created one with the same
+     *         names.
+     * @require The arguments are non-null. The quantity name has not been used
+     *          before or the unit name is the same as before.
+     * @promise The new quantity and unit has been added to the database.
+     * @throws UnitException
+     *             Name, abbreviation, or quantity name is <code>
+     *                          null</code>
+     *             or attempt to redefine the base unit associated with
+     *             <code>quantityName</code>.
+     */
+    public static synchronized BaseUnit addBaseUnit(final String quantityName,
+            final String unitName, final String abbreviation)
+            throws UnitException {
+        return addBaseUnit(quantityName, unitName, abbreviation, false);
+    }
+
+    /**
+     * Create a new base unit from from the name of a quantity, the name of a
+     * unit, the unit's abbreviation, and whether or not the unit is
+     * dimensionless.
+     * 
+     * @param quantityName
+     *            The name of the associated quantity (e.g. "Length").
+     * @param unitName
+     *            The name for the unit (e.g. "meter").
+     * @param abbreviation
+     *            The abbreviation for the unit (e.g. "m").
+     * @param isDimless
+     *            Whether or not the unit is dimensionless.
+     * @return A new base unit or the previously created one with the same
+     *         names.
+     * @require The arguments are non-null. The quantity name has not been used
+     *          before or the unit name is the same as before.
+     * @promise The new quantity and unit has been added to the database.
+     * @throws UnitException
+     *             Name, abbreviation, or quantity name is <code>
+     *                          null</code>
+     *             or attempt to redefine the base unit associated with
+     *             <code>quantityName</code>.
+     */
+    public static synchronized BaseUnit addBaseUnit(final String quantityName,
+            final String unitName, final String abbreviation,
+            final boolean isDimless) throws UnitException {
+        final BaseUnit baseUnit = quantityNameToUnit(quantityName);
+
+        if (baseUnit == null) {
+            return new BaseUnit(unitName, abbreviation, quantityName, isDimless);
+        }
+
+        if (baseUnit.unitName.equals(unitName)
+                && baseUnit.getIdentifier().equals(abbreviation)
+                && baseUnit.isDimless == isDimless) {
+            return baseUnit;
+        }
+
+        throw new UnitException("Attempt to redefine quantity \""
+                + quantityName + "\" base unit from \"" + baseUnit.unitName
+                + "(" + baseUnit.getIdentifier() + ")" + "\" to \"" + unitName
+                + "(" + abbreviation + ")" + "\"");
+    }
+
+    /**
+     * Find the base unit with the given name.
+     * 
+     * @param unitName
+     *            The name of the unit (e.g. "meter").
+     * @return The existing base unit with the given name or <code>null</code>
+     *         if no such units exists.
+     * @require The argument is non-null.
+     */
+    public static synchronized BaseUnit unitNameToUnit(final String unitName) {
+        for (int i = 0; i < baseUnits.size(); ++i) {
+            final BaseUnit baseUnit = baseUnits.elementAt(i);
+
+            if (baseUnit.unitName.equals(unitName)) {
+                return baseUnit;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Find the base unit for the given quantity.
+     * 
+     * @param quantityName
+     *            The name of the quantity (e.g. "Length").
+     * @return The existing base unit for the given quantity or
+     *         <code>null</code> if no such unit exists.
+     * @require The argument is non-null.
+     */
+    public static synchronized BaseUnit quantityNameToUnit(
+            final String quantityName) {
+        for (int i = 0; i < baseUnits.size(); ++i) {
+            final BaseUnit baseUnit = baseUnits.elementAt(i);
+
+            if (baseUnit.quantityName.equals(quantityName)) {
+                return baseUnit;
+            }
+        }
+
+        return null;
+    }
+
+    private static void myAssert(final boolean assertion) {
+        if (!assertion) {
+            throw new AssertionError();
+        }
+    }
+
+    private static void myAssert(final String have, final String expect) {
+        if (!have.equals(expect)) {
+            throw new AssertionError(have + " != " + expect);
+        }
+    }
+
+    private static void myAssert(final Unit have, final Unit expect) {
+        if (!have.equals(expect)) {
+            throw new AssertionError(have.toString() + " != " + expect);
+        }
+    }
+
+    private static void myAssert(final double have, final double expect) {
+        if (have != expect) {
+            throw new AssertionError("" + have + " != " + expect);
+        }
+    }
+
+    private static void myAssert(final double[] have, final double[] expect) {
+        if (!Arrays.equals(have, expect)) {
+            throw new AssertionError("" + have + " != " + expect);
+        }
+    }
+
+    /**
+     * Test this class.
+     * 
+     * @param args
+     *            Arguments (ignored).
+     * @throws UnitException
+     *             A problem occurred.
+     */
+    public static void main(final String[] args) throws UnitException {
+        final BaseUnit meter = BaseUnit.addBaseUnit("Length", "meter", "m");
+
+        myAssert(meter, meter);
+        myAssert(meter.isConvertible(meter));
+        myAssert(meter.toString(), "m");
+        myAssert(meter.pow(2), meter.multiply(meter));
+        myAssert(meter.pow(2).sqrt(), meter);
+
+        final BaseUnit second = BaseUnit.addBaseUnit("Time", "second", "s");
+
+        myAssert(!meter.equals(second));
+        myAssert(!meter.isConvertible(second));
+
+        Unit unit = meter.multiply(second);
+        myAssert(unit, second.multiply(meter));
+        unit = meter.divide(second);
+        myAssert(unit, second.divide(meter).pow(-1));
+        myAssert(!unit.equals(meter));
+        myAssert(!unit.equals(second));
+
+        myAssert(meter.toThis(5, meter), 5);
+        myAssert(meter.toThat(5, meter), 5);
+
+        final double[] values = { 1, 2 };
+        myAssert(meter.toThis(values, meter), values);
+        myAssert(meter.toThat(values, meter), values);
+
+        System.out.println("Checking exceptions:");
+        try {
+            meter.toThis(5, second);
+            throw new AssertionError();
+        }
+        catch (final UnitException e) {
+            System.out.println(e.getMessage());
+        }
+        try {
+            meter.toThat(5, second);
+            throw new AssertionError();
+        }
+        catch (final UnitException e) {
+            System.out.println(e.getMessage());
+        }
+        try {
+            BaseUnit.addBaseUnit("Length", "foot", "ft");
+            throw new AssertionError();
+        }
+        catch (final UnitException e) {
+            System.out.println(e.getMessage());
+        }
+
+        System.out.println("Done");
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * @require The units are convertible.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    double[] toThis(final double[] values, final BaseUnit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * @require The units are convertible.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    float[] toThis(final float[] values, final BaseUnit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from a base unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    double[] toThis(final double[] values, final BaseUnit that,
+            final boolean copy) throws UnitException {
+        if (equals(that)) {
+            final double[] newValues = (copy)
+                    ? (double[]) values.clone()
+                    : values;
+            return newValues;
+        }
+
+        throw new UnitException("Attempt to convert from unit \"" + that
+                + "\" to unit \"" + this + "\"");
+    }
+
+    /**
+     * Convert values to this unit from a base unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    float[] toThis(final float[] values, final BaseUnit that, final boolean copy)
+            throws UnitException {
+        if (equals(that)) {
+            final float[] newValues = (copy)
+                    ? (float[]) values.clone()
+                    : values;
+
+            return newValues;
+        }
+
+        throw new UnitException("Attempt to convert from unit \"" + that
+                + "\" to unit \"" + this + "\"");
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * @require The units are convertible.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * @require The units are convertible.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return that.toThat(values, derivedUnit, copy);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return that.toThat(values, derivedUnit, copy);
+    }
+
+    /**
+     * Convert values from this unit to a base unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    double[] toThat(final double[] values, final BaseUnit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to a base unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    float[] toThat(final float[] values, final BaseUnit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to a base unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    double[] toThat(final double[] values, final BaseUnit that,
+            final boolean copy) throws UnitException {
+        if (equals(that)) {
+            final double[] newValues = (copy)
+                    ? (double[]) values.clone()
+                    : values;
+            return newValues;
+        }
+
+        throw new UnitException("Attempt to convert from unit \"" + this
+                + "\" to unit \"" + that + "\"");
+    }
+
+    /**
+     * Convert values from this unit to a base unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    float[] toThat(final float[] values, final BaseUnit that, final boolean copy)
+            throws UnitException {
+        if (equals(that)) {
+            final float[] newValues = (copy)
+                    ? (float[]) values.clone()
+                    : values;
+            return newValues;
+        }
+
+        throw new UnitException("Attempt to convert from unit \"" + this
+                + "\" to unit \"" + that + "\"");
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double[] values, final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float[] values, final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return that.toThis(values, derivedUnit, copy);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * @require The units are identical.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return that.toThis(values, derivedUnit, copy);
+    }
+
+    /**
+     * Returns the definition of this unit. The definition of a BaseUnit is the
+     * same as the BaseUnit's identifier.
+     * 
+     * @return The definition of this unit. Won't be <code>null
+     *                  </code>
+     *         but may be empty.
+     */
+    @Override
+    public String getDefinition() {
+        return getIdentifier();
+    }
+
+    /**
+     * Clones this unit, changing the identifier. This method always throws an
+     * exception because base units may not be cloned.
+     * 
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * @return A unit equal this this instance but with the given identifier.
+     * @throws UnitException
+     *             Base units may not be cloned. Always thrown.
+     */
+    @Override
+    protected Unit protectedClone(final String identifier) throws UnitException {
+        throw new UnitException("Base units may not be cloned");
+    }
+
+    /**
+     * Indicates whether or not this instance equals a unit.
+     * 
+     * @param unit
+     *            A unit.
+     * @return <code>true</code> if and only if this instance is equal to the
+     *         unit.
+     */
+    @Override
+    public boolean equals(final Unit unit) {
+        if (this == unit) {
+            return true;
+        }
+        if (!(unit instanceof BaseUnit)) {
+            return  derivedUnit.equals(unit);
+        }
+        final BaseUnit that = (BaseUnit) unit;
+        return unitName.equals(that.unitName)
+                && quantityName.equals(that.quantityName)
+                && isDimless == that.isDimless;
+    }
+
+    /**
+     * Returns the hash code of this instance. {@link Object#hashCode()} should
+     * be overridden whenever {@link Object#equals(Object)} is.
+     * 
+     * @return The hash code of this instance (includes the values).
+     */
+    @Override
+    public int hashCode() {
+        if (hashCode == 0) {
+            hashCode = unitName.hashCode() ^ quantityName.hashCode()
+                    ^ Boolean.valueOf(isDimless).hashCode();
+        }
+        return hashCode;
+    }
+
+    /**
+     * Multiply this unit by another unit.
+     * 
+     * @param that
+     *            The unit with which to multiply this unit.
+     * @return The product of the two units.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             Meaningless operation.
+     */
+    @Override
+    public Unit multiply(final Unit that) throws UnitException {
+        return derivedUnit.multiply(that);
+    }
+
+    /**
+     * Divide this unit by another unit.
+     * 
+     * @param that
+     *            The unit to divide into this unit.
+     * @return The quotient of the two units.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             Meaningless operation.
+     */
+    @Override
+    public Unit divide(final Unit that) throws UnitException {
+        return derivedUnit.divide(that);
+    }
+
+    /**
+     * Divide this unit into another unit.
+     * 
+     * @param that
+     *            The unit to divided this unit.
+     * @return The quotient of the two units.
+     * @promise Neither unit has been modified.
+     * @throws UnitException
+     *             Meaningless operation.
+     */
+    @Override
+    protected Unit divideInto(final Unit that) throws UnitException {
+        return derivedUnit.divideInto(that);
+    }
+
+    /**
+     * Indicate whether this unit is convertible with another unit. If one unit
+     * is convertible with another, then the <code>toThis(...)</code>/ and
+     * <code>toThat(...)</code> methods will not throw a UnitException. Unit A
+     * is convertible with unit B if and only if unit B is convertible with unit
+     * A; hence, calling-order is irrelevant.
+     * 
+     * @param unit
+     *            The other unit.
+     * @return True if and only if this unit is convertible with the other unit.
+     */
+    @Override
+    public boolean isConvertible(final Unit unit) {
+        return unit == null
+                ? false
+                : derivedUnit.isConvertible(unit);
+    }
+
+    @Override
+    public DerivedUnit getDerivedUnit() {
+        return derivedUnit;
+    }
+}
diff --git a/visad/CMYCoordinateSystem.java b/visad/CMYCoordinateSystem.java
new file mode 100644
index 0000000..9a3614b
--- /dev/null
+++ b/visad/CMYCoordinateSystem.java
@@ -0,0 +1,175 @@
+//
+// CMYCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   CMYCoordinateSystem is the VisAD CoordinateSystem class for
+   (Cyan, Magenta, Yellow) with Reference (Red, Green, Blue).
+   Algorithm from Foley and van Dam.<P>
+*/
+public class CMYCoordinateSystem extends CoordinateSystem {
+
+  private static final long serialVersionUID = 1L;
+  private static Unit[] coordinate_system_units = {null, null, null};
+
+  /**
+   * construct a CMYCoordinateSystem with given reference
+   * @param reference - reference RealTupleType
+   */
+  public CMYCoordinateSystem(RealTupleType reference) throws VisADException {
+    super(reference, coordinate_system_units);
+  }
+
+  /**
+   * trusted constructor for initializers (does not throw
+   * any declared Exceptions)
+   * @param reference - reference RealTupleType
+   * @param b - dummy argument for trusted constructor signature
+   */
+  CMYCoordinateSystem(RealTupleType reference, boolean b) {
+    super(reference, coordinate_system_units, b);
+  }
+
+  /**
+   *  Convert RealTuple values to Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  double[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is double[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.
+   *  @param  tuples  array of values assumed to be in coordinateSystem
+   *                 units. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of double values in reference coordinates and Unit-s.
+   *  @throws VisADException  if problem with conversion.
+   */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("CMYCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    for (int i=0; i<len ;i++) {
+      value[0][i] = 1.0 - tuples[0][i];
+      value[1][i] = 1.0 - tuples[1][i];
+      value[2][i] = 1.0 - tuples[2][i];
+    }
+    return value;
+  }
+
+  /**
+   *  Convert RealTuple values from Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  double[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is double[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.
+   *  @param  tuples  array of values assumed to be in reference
+   *                 Unit-s. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of double values in CoordinateSystem Unit-s.
+   *  @throws VisADException  if problem with conversion.
+   */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("CMYCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    for (int i=0; i<len ;i++) {
+      value[0][i] = 1.0 - tuples[0][i];
+      value[1][i] = 1.0 - tuples[1][i];
+      value[2][i] = 1.0 - tuples[2][i];
+    }
+    return value;
+  }
+
+  /**
+   *  Convert RealTuple values to Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  float[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is float[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.
+   *  @param  tuples  array of values assumed to be in coordinateSystem
+   *                 units. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of float values in reference coordinates and Unit-s.
+   *  @throws VisADException  if problem with conversion.
+   */
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("CMYCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    for (int i=0; i<len ;i++) {
+      value[0][i] = 1.0f - tuples[0][i];
+      value[1][i] = 1.0f - tuples[1][i];
+      value[2][i] = 1.0f - tuples[2][i];
+    }
+    return value;
+  }
+
+  /**
+   *  Convert RealTuple values from Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  float[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is float[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.
+   *  @param  tuples  array of values assumed to be in reference
+   *                 Unit-s. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of float values in CoordinateSystem Unit-s.
+   *  @throws VisADException  if problem with conversion.
+   */
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("CMYCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    for (int i=0; i<len ;i++) {
+      value[0][i] = 1.0f - tuples[0][i];
+      value[1][i] = 1.0f - tuples[1][i];
+      value[2][i] = 1.0f - tuples[2][i];
+    }
+    return value;
+  }
+
+  /**
+   * Indicates whether or not this instance is equal to an object.
+   * @param cs - the object in question.
+   * @return <code>true</code> if and only if this instance equals cs.
+   */
+  public boolean equals(Object cs) {
+    return (cs instanceof CMYCoordinateSystem);
+  }
+
+}
+
diff --git a/visad/CachingCoordinateSystem.java b/visad/CachingCoordinateSystem.java
new file mode 100644
index 0000000..fdbccf0
--- /dev/null
+++ b/visad/CachingCoordinateSystem.java
@@ -0,0 +1,238 @@
+//
+// CachingCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+
+import java.util.Arrays;
+
+import visad.util.Util;
+
+import visad.data.ArrayCache;
+
+
+/**
+ * A wrapper class for CoordinateSystems that will cache the last
+ * values input and output values of the toReference and fromReference
+ * methods.  If the inputs are the same as the last time these
+ * methods were called, the previously calculated values are returned.
+ *
+ * @author Don Murray
+ * @version $Revision: 1.11 $ $Date: 2009-12-07 12:16:19 $
+ */
+public class CachingCoordinateSystem extends CoordinateSystem {
+
+  /** The coordinate system I wrap */
+  private CoordinateSystem myCS = null;
+
+  /**  Does the actual caching         */
+  private ArrayCache arrayCache = new ArrayCache();
+
+  /** Show time to transform           */
+  public static boolean debugTime = 
+    Boolean.parseBoolean(System.getProperty("visad.cachingcoordinatesystem.debugtime",
+                                            "false"));
+
+  /**  counter to show which object this is         */
+  private static int cnt = 0;
+
+  /**  counter to show which object this is         */
+  private int mycnt = cnt++;
+
+
+  /**
+   * Construct a new CachingCoordinateSystem that wraps around the input.
+   * @param cs CoordinateSystem to wrap
+   *
+   * @throws VisADException 
+   */
+  public CachingCoordinateSystem(CoordinateSystem cs) throws VisADException {
+    super(cs.getReference(), cs.getCoordinateSystemUnits());
+    myCS = cs;
+  }
+
+
+  /**
+   * Wrapper around the toReference method of the input CoordinateSystem.
+   * If the inputs are the same as the last time this method was called,
+   * the previously computed outputs will be returned, otherwise the
+   * toReference method of the wrapped CS is called and it's output
+   * is returned
+   *
+   * @param   inputs  values to transform
+   * @return  transformed input values.
+   * @throws  VisADException  when wrapped CS does
+   */
+  public double[][] toReference(double[][] inputs) throws VisADException {
+    if (inputs == null) return inputs;
+    long t1 = System.currentTimeMillis();
+    boolean hit = true;
+    String key = "toReferenceD";
+    ArrayCache.DoubleResult results = arrayCache.get(key, inputs);    
+    if (results.values == null) {
+      double[][] tmp = results.cloneForCache(inputs);
+      results.values = myCS.toReference(inputs);
+      arrayCache.put(key, tmp, results);
+      hit = false;
+    }
+    if(debugTime  && results.getShouldCache())
+        debugTime(inputs[0].length, key +" hit?" + hit, t1,System.currentTimeMillis());
+    //    System.err.println (Util.getStackTrace());
+
+    return results.values;
+  }
+
+    private void debugTime(int size, String msg, long t1, long t2) {
+        if(size>100 && debugTime && t1!=t2) {
+            System.err.println("CCS #" +cnt + " size:" + size+ " " +  msg+" time:" + (t2 - t1));
+        }
+    }
+
+
+  /**
+   * Wrapper around the fromReference method of the input CoordinateSystem.
+   * If the inputs are the same as the last time this method was called,
+   * the previously computed outputs will be returned, otherwise the
+   * fromReference method of the wrapped CS is called and it's output
+   * is returned
+   *
+   * @param   inputs  values to transform
+   * @return  transformed input values.
+   * @throws  VisADException  when wrapped CS does
+   */
+  public double[][] fromReference(double[][] inputs) throws VisADException {
+    if (inputs == null) return inputs;
+
+    long t1 = System.currentTimeMillis();
+    boolean hit = true;
+    String key = "fromReferenceD";
+    ArrayCache.DoubleResult results = arrayCache.get(key, inputs);
+    if (results.values == null) {
+      double[][] tmp = results.cloneForCache(inputs);
+      results.values = myCS.fromReference(inputs);
+      arrayCache.put(key, tmp, results);
+      hit = false;
+    }
+    if(debugTime  && results.getShouldCache())
+        debugTime(inputs[0].length,key +" hit?" + hit, t1,System.currentTimeMillis());
+    return results.values;
+
+  }
+
+
+
+
+
+  /**
+   * Wrapper around the toReference method of the input CoordinateSystem.
+   * If the inputs are the same as the last time this method was called,
+   * the previously computed outputs will be returned, otherwise the
+   * toReference method of the wrapped CS is called and it's output
+   * is returned
+   *
+   * @param   inputs  values to transform
+   * @return  transformed input values.
+   * @throws  VisADException  when wrapped CS does
+   */
+  public float[][] toReference(float[][] inputs) throws VisADException {
+    if (inputs == null) return inputs;
+
+    long t1 = System.currentTimeMillis();
+    boolean hit = true;
+    String key = "toReferenceF";
+
+    ArrayCache.FloatResult results = arrayCache.get(key, inputs);
+    if (results.values == null) {
+      float[][] tmp = results.cloneForCache(inputs);
+      results.values = myCS.toReference(inputs);
+      arrayCache.put(key, tmp, results);
+      hit = false;
+    }
+
+    if(debugTime  && results.getShouldCache())
+        debugTime(inputs[0].length,key +" hit?" + hit, t1,System.currentTimeMillis());
+    return results.values;
+  }
+
+
+  /**
+   * Wrapper around the fromReference method of the input CoordinateSystem.
+   * If the inputs are the same as the last time this method was called,
+   * the previously computed outputs will be returned, otherwise the
+   * fromReference method of the wrapped CS is called and it's output
+   * is returned
+   *
+   * @param   inputs  values to transform
+   * @return  transformed input values.
+   * @throws  VisADException  when wrapped CS does
+   */
+  public float[][] fromReference(float[][] inputs) throws VisADException {
+    if (inputs == null) return inputs;
+    long t1 = System.currentTimeMillis();
+    boolean hit = true;
+    String key = "fromReferenceF";
+    ArrayCache.FloatResult results = arrayCache.get(key, inputs);
+    if (results.values==null) {
+      float[][] tmp = results.cloneForCache(inputs);
+      results.values = myCS.fromReference(inputs);
+      arrayCache.put(key, tmp, results);
+      hit = false;
+    }
+    if(debugTime  && results.getShouldCache())
+        debugTime(inputs[0].length,key +" hit?" + hit, t1,System.currentTimeMillis());
+    return results.values;
+  }
+
+  /**
+   * Check for equality of CoordinateSystem objects
+   * @param  obj  other object in question
+   * @return  true if the object in question is a CachingCoordinateSystem
+   *          and it's CS is equal this object's CS
+   */
+  public boolean equals(Object obj) {
+    if (!(obj instanceof CachingCoordinateSystem)) return false;
+    CachingCoordinateSystem that = (CachingCoordinateSystem)obj;
+    return that.myCS.equals(myCS);
+  }
+
+  /**
+   * Access to the "cached" CS
+   * @return  cached CoordinateSystem
+   */
+  public CoordinateSystem getCachedCoordinateSystem() {
+    return myCS;
+  }
+
+  /**
+   * A decriptive string of this CS.
+   * @return a descriptive String
+   */
+  public String toString() {
+    return "Cached CS: " + myCS.toString();
+  }
+
+}
+
diff --git a/visad/CartesianProductCoordinateSystem.java b/visad/CartesianProductCoordinateSystem.java
new file mode 100644
index 0000000..2a44d31
--- /dev/null
+++ b/visad/CartesianProductCoordinateSystem.java
@@ -0,0 +1,325 @@
+//
+// $Id: CartesianProductCoordinateSystem.java,v 1.13 2009-03-02 23:35:41 curtis Exp $
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+
+import java.util.Vector;
+
+
+/**
+ * <P>A class for creating a new CoordinateSystem that is the product of
+ * two or more CoordinateSystems.  This is useful
+ * for creating a new CoordinateSystem when combining values from different
+ * Data objects with different CoordinateSystems into a new Data object with a
+ * new CoordinateSystem that combines the CoordinateSystems from the original
+ * Data objects.  An example would be where one CoordinateSystem
+ * transforms (row,col) -> (lat,lon) and another CoordinateSystem transforms
+ * (pressure) -> (altitude).  The resulting CartesianProductCoordinateSystem
+ * would transform (row, col, pressure) -> (lat, lon, alt).</P>
+ *
+ * <P>The resulting CartesianProductCoordinateSystem will have a dimension
+ * of the sum of the dimensions of the individual CoordinateSystems and a
+ * Reference RealTupleType that is the composite of the references of
+ * each CoordinateSystem.  The CoordinateSystem Units are a composite of the
+ * Units of each of the CoordinateSystems as well.</P>
+ */
+public class CartesianProductCoordinateSystem extends CoordinateSystem {
+
+  /** array of coordinate systems */
+  private CoordinateSystem[] csArray;
+
+  /**
+   * Construct a CartesianProductCoordinateSystem from two other
+   * CoordinateSystems.
+   *
+   * @param   a   first non-null CoordinateSystem
+   * @param   b   second non-null CoordinateSystem
+   * @throws  VisADException  a or b are null or VisAD object can't be created
+   */
+  public CartesianProductCoordinateSystem(CoordinateSystem a,
+                                          CoordinateSystem b)
+          throws VisADException {
+    this(new CoordinateSystem[] {a, b});
+  }
+
+  /**
+   * Construct a CartesianProductCoordinateSystem from an array of
+   * CoordinateSystems.
+   *
+   * @param  csArray  non-null array of non-null CoordinateSystems
+   * @throws VisADException  an element is null or VisAD object can't be
+   *                         created.
+   */
+  public CartesianProductCoordinateSystem(CoordinateSystem[] csArray)
+          throws VisADException {
+    super(getProductReference(csArray), getProductUnits(csArray));
+    this.csArray = csArray;
+  }
+
+  /**
+   * Get the arrays of CoordinateSystems being used in this product
+   * @return array of CoordinateSystems
+   */
+  public CoordinateSystem[] getCoordinateSystems() {
+    return csArray;
+  }
+
+  /**
+   * Get a particular CoordinateSystem
+   * @param  index  index into the array
+   * @return CoordinateSystem from array
+   * @throws ArrayIndexOutOfBoundsException (no need to declare)
+   *         if index out of bounds
+   */
+  public CoordinateSystem getCoordinateSystem(int index) {
+    return csArray[index];
+  }
+
+  /**
+   * create a RealTupleType that concatenates the references of
+   * the CoordinateSystems in csArray (used by constructor)
+   * @param csArray - array of CoordinateSystems
+   * @return concatenated reference RealTupleType
+   * @throws VisADException
+   */
+  static RealTupleType getProductReference(CoordinateSystem[] csArray)
+          throws VisADException {
+    if (csArray == null)
+      throw new VisADException("CoordinateSystem array can't be null");
+    if (csArray.length < 2)
+      throw new VisADException(
+        "CoordinateSystem array must have more than one element");
+    Vector typeVector = new Vector();
+    for (int i = 0; i < csArray.length; i++) {
+      CoordinateSystem cs = csArray[i];
+      if (cs == null)
+        throw new VisADException(
+          "CoordinateSystem array can't have null members "+i);
+      RealType[] reals = cs.getReference().getRealComponents();
+      for (int j = 0; j < reals.length; j++) {
+        typeVector.add(reals[j]);
+      }
+    }
+
+    return new RealTupleType(
+      (RealType[]) typeVector.toArray(new RealType[typeVector.size()]));
+  }
+
+  /**
+   * create an array of Units that concatenates the Unit arrays
+   * of the CoordinateSystems in csArray (used by constructor)
+   * @param csArray - array of CoordinateSystems
+   * @return concatenated array of Units
+   * @throws VisADException
+   */
+  static Unit[] getProductUnits(CoordinateSystem[] csArray)
+          throws VisADException {
+    if (csArray == null)
+      throw new VisADException("CoordinateSystem array can't be null");
+    if (csArray.length < 2)
+      throw new VisADException(
+        "CoordinateSystem array must have more than one element");
+    Vector unitVector = new Vector();
+    for (int i = 0; i < csArray.length; i++) {
+      Unit[] units = csArray[i].getCoordinateSystemUnits();
+      for (int j = 0; j < units.length; j++) {
+        unitVector.add(units[j]);
+      }
+    }
+
+    return (Unit[]) unitVector.toArray(new Unit[unitVector.size()]);
+  }
+
+  /**
+   * Convert input array to reference coordinates.
+   *
+   * @param input   input array
+   * @return  array of values in reference space
+   * @throws VisADException  input array has the wrong dimension or is null
+   */
+  public double[][] toReference(double[][] input) throws VisADException {
+    if (input.length != getDimension() || input == null)
+      throw new VisADException("input has wrong dimension");
+    int numElements = input[0].length;
+    double[][] output = new double[getDimension()][];
+    int pointer = 0;
+    for (int i = 0; i < csArray.length; i++) {
+      int dimension = csArray[i].getDimension();
+      double[][] temp = new double[dimension][];
+      // assign input values
+      for (int j = 0; j < dimension; j++) {
+        temp[j] = (double[]) input[pointer+j];
+      }
+
+      // do the transformation
+      temp = csArray[i].toReference(temp);
+
+      // assign values to the output array
+      for (int j = 0; j < dimension; j++) {
+        output[pointer+j] = temp[j];
+      }
+
+      pointer += dimension;
+    }
+
+    return output;
+  }
+
+  /**
+   * Convert array of reference valeus from Reference coordinates.  Input
+   * values are modified in this transaction.
+   *
+   * @param refTuple   reference tuple array
+   * @return  array of values in non-reference space
+   * @throws VisADException  input array has the wrong dimension or is null
+   */
+  public double[][] fromReference(double[][] refTuple) throws VisADException {
+    if (refTuple.length != getDimension() || refTuple == null)
+      throw new VisADException("refTuple has wrong dimension");
+    int numElements = refTuple[0].length;
+    double[][] output = new double[getDimension()][];
+    int pointer = 0;
+    for (int i = 0; i < csArray.length; i++) {
+      int dimension = csArray[i].getDimension();
+      double[][] temp = new double[dimension][];
+      // assign over the input values
+      for (int j = 0; j < dimension; j++) {
+        temp[j] = (double[]) refTuple[pointer+j];
+      }
+
+      // do the transformation
+      temp = csArray[i].fromReference(temp);
+
+      // assign values to the output array
+      for (int j = 0; j < dimension; j++) {
+        output[pointer+j] = temp[j];
+      }
+
+      pointer += dimension;
+    }
+
+    return output;
+  }
+
+  /**
+   * Convert input array to reference coordinates.
+   *
+   * @param input   input array
+   * @return  array of values in reference space
+   * @throws VisADException  input array has the wrong dimension or is null
+   */
+  public float[][] toReference(float[][] input) throws VisADException {
+    if (input.length != getDimension() || input == null)
+      throw new VisADException("input has wrong dimension");
+    int numElements = input[0].length;
+    float[][] output = new float[getDimension()][];
+    int pointer = 0;
+    for (int i = 0; i < csArray.length; i++) {
+      int dimension = csArray[i].getDimension();
+      float[][] temp = new float[dimension][];
+      // assign input values
+      for (int j = 0; j < dimension; j++) {
+        temp[j] = input[pointer+j];
+      }
+
+      // do the transformation
+      temp = csArray[i].toReference(temp);
+
+      // assign values to the output array
+      for (int j = 0; j < dimension; j++) {
+        output[pointer+j] = temp[j];
+      }
+
+      pointer += dimension;
+    }
+
+    return output;
+  }
+
+  /**
+   * Convert array of reference valeus from Reference coordinates.  Input
+   * values are modified in this transaction.
+   *
+   * @param refTuple   reference tuple array
+   * @return  array of values in non-reference space
+   * @throws VisADException  input array has the wrong dimension or is null
+   */
+  public float[][] fromReference(float[][] refTuple) throws VisADException {
+    if (refTuple.length != getDimension() || refTuple == null)
+      throw new VisADException("refTuple has wrong dimension");
+    int numElements = refTuple[0].length;
+    float[][] output = new float[getDimension()][];
+    int pointer = 0;
+    for (int i = 0; i < csArray.length; i++) {
+      int dimension = csArray[i].getDimension();
+      float[][] temp = new float[dimension][];
+      // assign over the input values
+      for (int j = 0; j < dimension; j++) {
+        temp[j] = (float[]) refTuple[pointer+j];
+      }
+
+      // do the transformation
+      temp = csArray[i].fromReference(temp);
+
+      // assign values to the output array
+      for (int j = 0; j < dimension; j++) {
+        output[pointer+j] = temp[j];
+      }
+
+      pointer += dimension;
+    }
+
+    return output;
+  }
+
+  /**
+   * Check to see if the object in question is equal to this
+   * CartesianProductCoordinateSystem.
+   *
+   * @param  o  object in question
+   * @return  true if the the object in question is composed of equal
+   *          CoordinateSystems.
+   */
+  public boolean equals(Object o) {
+    if (o != null && o instanceof CartesianProductCoordinateSystem) {
+      CoordinateSystem[] ocsa =
+        ((CartesianProductCoordinateSystem) o).csArray;
+      int n = csArray.length;
+      if (n != ocsa.length) return false;
+
+      for (int i = 0; i < n; i++) {
+        if (!csArray[i].equals(ocsa[i])) return false;
+      }
+
+      return true;
+    }
+
+    return false;
+  }
+
+}
+
diff --git a/visad/Cell.java b/visad/Cell.java
new file mode 100644
index 0000000..0230df4
--- /dev/null
+++ b/visad/Cell.java
@@ -0,0 +1,63 @@
+//
+// Cell.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   Cell is the VisAD interface for computations.  It has a set of
+   'triggering' DataReferences and access to a set of non-triggering
+   DataReferences. <P>
+*/
+public interface Cell extends Action {
+
+  /**
+   * set a non-triggering link to a DataReference; this is
+   * used to give the Cell access to Data without triggering
+   * the Cell's doAction whenever the Data changes;
+   * these 'other' DataReferences are identified by their
+   * integer index
+   * @param index - identifier of DataReference
+   * @param ref - DataReference to be linked
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  void setOtherReference(int index, DataReference ref)
+         throws VisADException, RemoteException;
+
+  /**
+   * @return the non-triggering link to a DataReference
+   * identified by index
+   * @param index - identifier of DataReference to return
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  DataReference getOtherReference(int index)
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/CellImpl.java b/visad/CellImpl.java
new file mode 100644
index 0000000..0875a96
--- /dev/null
+++ b/visad/CellImpl.java
@@ -0,0 +1,146 @@
+//
+// CellImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   CellImpl is the abstract superclass for computations.  It has a
+   set of input DataReferences and an output DataReference, which
+   updates whenever an input changes.  Cell is runnable.<P>
+
+   CellImpl is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public abstract class CellImpl extends ActionImpl implements Cell {
+
+  /**
+   * references to Data use in computation, other than
+   * those in ReferenceActionLink-s
+   */
+  DataReference[] otherReferences = null;
+
+  /**
+   * construct a CellImpl with null name
+   */
+  public CellImpl() {
+    this(null);
+  }
+
+  /**
+   * construct a CellImpl
+   * @param name - String useful for debugging
+   */
+  public CellImpl(String name) {
+    super(name);
+    otherReferences = null;
+  }
+
+  /**
+   * subclasses of CellImpl implement doAction to execute
+   * triggered computation
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  public abstract void doAction() throws VisADException, RemoteException;
+
+  /**
+   * set a non-triggering link to a DataReference; this is
+   * used to give the Cell access to Data without triggering
+   * the Cell's doAction whenever the Data changes;
+   * these 'other' DataReferences are identified by their
+   * integer index
+   * @param index - identifier of DataReference
+   * @param ref - DataReference to be linked
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  public void setOtherReference(int index, DataReference ref)
+         throws VisADException, RemoteException {
+    if (!(ref instanceof DataReferenceImpl)) {
+      throw new RemoteVisADException("CellImpl.addReference: requires " +
+                                     "DataReferenceImpl");
+    }
+    setOtherReferences(index, ref);
+  }
+
+  /**
+   * called by RemoteCellImpl.setOtherReference()
+   * set a non-triggering link to a RemoteDataReference; this
+   * is used to give the Cell access to Data without triggering
+   * the Cell's doAction whenever the Data changes;
+   * these 'other' DataReferences are identified by their
+   * integer index
+   * @param index - identifier of DataReference
+   * @param ref - DataReference to be linked
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  void adaptedSetOtherReference(int index, RemoteDataReference ref) {
+    setOtherReferences(index, ref);
+  }
+
+  /**
+   * @return the non-triggering link to a DataReference
+   * identified by index
+   * @param index - identifier of DataReference to return
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  public synchronized DataReference getOtherReference(int index)
+         throws VisADException, RemoteException {
+    if (otherReferences == null ||
+        index < 0 || index >= otherReferences.length) {
+      return null;
+    }
+    else {
+      return otherReferences[index];
+    }
+  }
+
+  private synchronized void setOtherReferences(int index, DataReference ref) {
+    if (otherReferences == null) {
+      otherReferences = new DataReference[index];
+      for (int i=0; i<index; i++) {
+        otherReferences[i] = null;
+      }
+    }
+    else if (index >= otherReferences.length) {
+      DataReference[] os = new DataReference[index];
+      for (int i=0; i<otherReferences.length; i++) {
+        os[i] = otherReferences[i];
+      }
+      for (int i=otherReferences.length; i<index; i++) {
+        os[i] = null;
+      }
+      otherReferences = os;
+    }
+    otherReferences[index] = ref;
+  }
+
+}
+
diff --git a/visad/ColorAlphaControl.java b/visad/ColorAlphaControl.java
new file mode 100644
index 0000000..3247455
--- /dev/null
+++ b/visad/ColorAlphaControl.java
@@ -0,0 +1,43 @@
+//
+// ColorAlphaControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   ColorAlphaControl is the VisAD class for controlling 4-component Color
+   DisplayRealType-s (e.g., RGBA).<P>
+*/
+public class ColorAlphaControl
+  extends BaseColorControl
+{
+
+  /**
+   * construct a ColorAlphaControl for given DisplayImpl
+   * @param d - DisplayImpl
+   */
+  public ColorAlphaControl(DisplayImpl d) { super(d, 4); }
+
+}
diff --git a/visad/ColorControl.java b/visad/ColorControl.java
new file mode 100644
index 0000000..b24f037
--- /dev/null
+++ b/visad/ColorControl.java
@@ -0,0 +1,43 @@
+//
+// ColorControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   ColorControl is the VisAD class for controlling 3-component Color
+   DisplayRealType-s (e.g., RGB, HSV, CMY).<P>
+*/
+public class ColorControl
+  extends BaseColorControl
+{
+
+  /**
+   * construct a ColorAlphaControl for given DisplayImpl
+   * @param d - DisplayImpl
+   */
+  public ColorControl(DisplayImpl d) { super(d, 3); }
+
+}
diff --git a/visad/CommonUnit.java b/visad/CommonUnit.java
new file mode 100644
index 0000000..28ccc8a
--- /dev/null
+++ b/visad/CommonUnit.java
@@ -0,0 +1,103 @@
+//
+// CommonUnit.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   CommonUnit is a class for commonly used Units
+*/
+public class CommonUnit extends Object {
+
+  /** CommonUnit for plane angle, not temperature */
+  public static Unit degree;
+
+  /** CommonUnit for plane angle */
+  public static Unit radian = SI.radian;
+
+  /** CommonUnit for time */
+  public static Unit second = SI.second;
+
+  /** CommonUnit for length */
+  public static Unit meter = SI.meter;
+
+  /** CommonUnit for speed */
+  public static Unit meterPerSecond =
+    new DerivedUnit(new BaseUnit[] {SI.meter, SI.second},
+                    new int[] {1, -1});
+
+  /** CommonUnit for seconds since the Epoch (i.e. 1970-01-01 00:00:00Z) */
+  public static Unit secondsSinceTheEpoch =
+        new OffsetUnit(
+            visad.data.units.UnitParser.encodeTimestamp(
+                1970, 1, 1, 0, 0, 0, 0),
+            SI.second);
+
+  /** CommonUnit for all BaseUnits with exponent = zero */
+  public static Unit dimensionless = new DerivedUnit();
+
+  /** promiscuous is compatible with any Unit; useful for constants;
+      not the same as null Unit, which is only compatible with
+      other null Units; not the same as dimensionless, which is not
+      compatible with other Units for addition and subtraction */
+  public static Unit promiscuous = PromiscuousUnit.promiscuous;
+
+  /**
+   * static initializer to catch impossible but declared Exception
+   */
+  static {
+    try {
+      degree = SI.radian.scale(Math.PI/180.0, true).clone("deg");
+    }
+    catch (UnitException e) {}		// can't happen
+  }
+
+    /**
+     * Test this class.
+     *
+     * @param args		Arguments (ignored).
+     * @exception UnitException	A problem occurred.
+     */
+    public static void main(String[] args)
+	throws UnitException
+    {
+	System.out.println(
+	  "new ScaledUnit(1.0).equals(dimensionless)=" +
+	  new ScaledUnit(1.0).equals(dimensionless));
+	System.out.println(
+	  "dimensionless.equals(new ScaledUnit(1.0))=" +
+	  dimensionless.equals(new ScaledUnit(1.0)));
+	System.out.println(
+	  "CommonUnit.dimensionless.isConvertible(SI.radian) = " +
+	   CommonUnit.dimensionless.isConvertible(SI.radian));
+	System.out.println(
+	  "CommonUnit.dimensionless.isConvertible(CommonUnit.degree) = " +
+	   CommonUnit.dimensionless.isConvertible(CommonUnit.degree));
+	System.out.println(
+	  "CommonUnit.degree.isConvertible(SI.radian) = " +
+	   CommonUnit.degree.isConvertible(SI.radian));
+    }
+}
+
diff --git a/visad/ConstantMap.java b/visad/ConstantMap.java
new file mode 100644
index 0000000..8bf90b7
--- /dev/null
+++ b/visad/ConstantMap.java
@@ -0,0 +1,213 @@
+//
+// ConstantMap.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+import visad.util.Util;
+
+/**
+   mapping from constant to DisplayRealType
+*/
+public class ConstantMap extends ScalarMap {
+
+  // no Scalar, control or function for ConstantMap
+  private double Constant;
+
+  // WLH 24 Aug 2001
+  // flag to allow multiple use of a ConstantMap
+  private static boolean allowMultipleUseKludge = false;
+
+  /**
+   * construct a ConstantMap with a double constant
+   * @param constant - double equated to DisplayRealType
+   * @param display_scalar - DisplayRealType set to constant
+   *   (may not be Animation, SelectValue, SelectRange or IsoContour)
+   * @throws VisADException - a VisAD error occurred
+   */
+  public ConstantMap(double constant, DisplayRealType display_scalar)
+                     throws VisADException {
+    super(null, display_scalar, false); // no Scalar for ConstantMap
+    if (Double.isNaN(constant) || Double.isInfinite(constant)) {
+      throw new DisplayException("ConstantMap: constant is missing (NaN) " +
+                                 "or infinity");
+    }
+    if (display_scalar.equals(Display.Animation) ||
+        display_scalar.equals(Display.SelectValue) ||
+        display_scalar.equals(Display.SelectRange) ||
+        display_scalar.equals(Display.IsoContour) ||
+        display_scalar.equals(Display.Text)) {
+      throw new DisplayException("ConstantMap: illegal for " + display_scalar);
+    }
+    if (isScaled &&
+        !display_scalar.equals(Display.XAxis) &&
+        !display_scalar.equals(Display.YAxis) &&
+        !display_scalar.equals(Display.ZAxis) &&
+        (constant < displayRange[0] || constant > displayRange[1])) {
+      throw new DisplayException("ConstantMap: constant is out of range");
+    }
+    Constant = constant;
+  }
+
+  /**
+   * construct a ConstantMap with a Real constant;
+   * @param constant - Real whose value is equated to DisplayRealType
+   * @param display_scalar - DisplayRealType set to constant
+   *   (may not be Animation, SelectValue, SelectRange or IsoContour)
+   * @throws VisADException - a VisAD error occurred
+   */
+  public ConstantMap(Real constant, DisplayRealType display_scalar)
+                     throws VisADException {
+    this(constant.getValue(), display_scalar);
+  }
+
+  /**
+   * allow ConstantMaps to be used in multiple calls to
+   * DisplayImpl.addReference() or addReferences()
+   * such multiple use was not checked in the early releases of
+   * VisAD, and this method allows applications to easily avoid
+   * more extensive changes
+   * @param k - true to allow multiple use
+   */
+  public static void setAllowMultipleUseKludge(boolean k) {
+    allowMultipleUseKludge = k;
+  }
+
+  /**
+   * @return boolean indicating whether ConstantMaps may be
+   * used in multiple calls to DisplayImpl.addReference() or
+   * addReferences()
+   */ 
+  public static boolean getAllowMultipleUseKludge() {
+    return allowMultipleUseKludge;
+  }
+
+  /**
+   * do nothing over-ride of ScalarMap method
+   */
+  void setControl() throws VisADException, RemoteException {
+    return;
+  }
+
+  /**
+   * @return constant value mapped to DisplayRealType
+   */
+  public double getConstant() {
+    return Constant;
+  }
+
+  /**
+   * Indicates whether or not this instance equals an Object.
+   * @param o - A object.
+   * @return <code>true</code> if and only if this instance is equal to o.
+   */
+  public boolean equals(Object o)
+  {
+    boolean	equals;
+    if (!(o instanceof ConstantMap)) {
+      equals = false;
+    }
+    else {
+      ConstantMap cm = (ConstantMap )o;
+      equals = this == cm || (this.compareTo(cm) == 0);
+    }
+    return equals;
+  }
+
+  /**
+   * Compares this instance to another object.
+   * @param obj		The other object.
+   * @return            A value that is negative, zero, or positive depending on
+   *                    whether this instance is considered less than, equal
+   *                    to, or greater than the other object, respectively.
+   */
+  public int compareTo(Object obj)
+  {
+    return
+      obj instanceof ConstantMap
+	? compareTo((ConstantMap)obj)
+	: compareTo((ScalarMap)obj);
+  }
+
+  /**
+   * Compares this instance to another instance.
+   * @param that	The other instance.
+   * @return            A value that is negative, zero, or positive depending on
+   *                    whether this instance is considered less than, equal
+   *                    to, or greater than the other instance, respectively.
+   */
+  protected int compareTo(ConstantMap that)
+  {
+    int	comp = getDisplayScalar().compareTo(that.getDisplayScalar());
+    if (comp == 0) {
+      comp =
+	Util.isApproximatelyEqual(Constant, that.Constant)
+	  ? 0
+	  : ((Constant - that.Constant) < 0 ? -1 : 1);
+    }
+    return comp;
+  }
+
+  /**
+   * Compares this instance to a ScalarMap.
+   * @param that	The ScalarMap.
+   * @return            -1 always.  ConstantMap-s are considered less than
+   *			true ScalarMap-s.
+   */
+  protected int compareTo(ScalarMap that)
+  {
+    return -1;
+  }
+
+  public Object clone()
+  {
+    try {
+      ConstantMap cm = new ConstantMap(Constant, getDisplayScalar());
+      copy(cm);
+      return cm;
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  public String toString(String pre) {
+    return pre + "ConstantMap: " + Constant +
+           " -> " + getDisplayScalar().toString() + "\n";
+  }
+
+  /**
+   * do nothing over-ride of ScalarMap method
+   * @param aspect ratios; 3 elements for Java3D, 2 for Java2D
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void setAspectCartesian(double[] aspect)
+       throws VisADException, RemoteException {
+  }
+
+}
+
diff --git a/visad/Contour2D.java b/visad/Contour2D.java
new file mode 100644
index 0000000..01656c2
--- /dev/null
+++ b/visad/Contour2D.java
@@ -0,0 +1,6062 @@
+//
+// Contour2D.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import visad.util.Trace;
+import visad.util.HersheyFont;
+import java.awt.Font;
+import java.text.DecimalFormat;
+
+/**
+ * Contour2D is a class equipped with a 2-D contouring function.
+ * <P>
+ */
+
+public class Contour2D {
+
+	/**           */
+	protected Contour2D con;
+
+	/**           */
+	protected int whichlabels = 0;
+
+	/**           */
+	protected boolean showgrid;
+
+	/**           */
+	protected int rows, cols, scale;
+
+	/**           */
+	protected int[] num1, num2, num3, num4;
+
+	/**           */
+	protected float[][] vx1, vy1;
+
+	/**           */
+	public static final int DIFFICULTY_THRESHOLD = 600000;
+
+	/**           */
+	public static final float DIMENSION_THRESHOLD = 5.0E5f;
+
+	public static final byte SIDE_LEFT = 3;
+	public static final byte SIDE_RIGHT = 1;
+	public static final byte SIDE_TOP = 2;
+	public static final byte SIDE_BOTTOM = 0;
+	public static final byte SIDE_NONE = -1;
+	public static final byte CLOCKWISE = -1;
+	public static final byte CNTRCLOCKWISE = 1;
+
+	/**
+	 * Compute contour lines for a 2-D array. If the interval is negative, then
+	 * contour lines less than base will be drawn as dashed lines. The contour
+	 * lines will be computed for all V such that:
+	 * 
+	 * <pre>
+	 * lowlimit <= V <= highlimit
+	 * </pre>
+	 * 
+	 * and
+	 * 
+	 * <pre>
+	 * V = base + n*interval  for some integer n
+	 * </pre>
+	 * 
+	 * Note that the input array, g, should be in column-major (FORTRAN) order.
+	 * 
+	 * @param g
+	 *            the 2-D array to contour.
+	 * @param nr
+	 *            size of 2-D array in rows
+	 * @param nc
+	 *            size of 2-D array in columns.
+	 * @param interval
+	 *            contour interval
+	 * @param lowlimit
+	 *            the lower limit on values to contour.
+	 * @param highlimit
+	 *            the upper limit on values to contour.
+	 * @param base
+	 *            base value to start contouring at.
+	 * @param vx1
+	 *            array to put contour line vertices (x value)
+	 * @param vy1
+	 *            array to put contour line vertices (y value)
+	 * @param numv1
+	 *            pointer to int to return number of vertices in vx1,vy1
+	 * @param auxValues
+	 *            colors corresponding to grid points
+	 * @param swap
+	 * @param fill
+	 *            true if filling contours
+	 * @param grd_normals
+	 * @param interval_colors
+	 * @param lbl_vv
+	 *            values for label line segments
+	 * @param lbl_cc
+	 *            label color triples
+	 * @param lbl_loc
+	 *            center points for label locations
+	 * @param scale_ratio
+	 * @param label_size
+	 * @param labelColor
+	 * @param spatial_set
+	 * 
+	 * @throws VisADException
+	 */
+	
+	public static void contour(float g[], int nr, int nc, float interval,
+			float lowlimit, float highlimit, float base, float vx1[][],
+			float vy1[][], int[] numv1, byte[][] auxValues,
+			boolean[] swap, boolean fill, float[][][] grd_normals,
+			byte[][] interval_colors, float[][][][] lbl_vv,
+			byte[][][][] lbl_cc, float[][][] lbl_loc, double[] scale, double scale_ratio,
+			int label_freq, int label_line_skip,
+			double label_size, boolean labelAlign, byte[] labelColor,
+			Object labelFont, boolean sphericalDisplayCS,
+			Gridded3DSet spatial_set) throws VisADException {
+		boolean[] dashes = { false };
+		float[] intervals = intervalToLevels(interval, lowlimit, highlimit,
+				base, dashes);
+		boolean dash = dashes[0];
+
+		contour(g, nr, nc, intervals, lowlimit, highlimit, base, dash,
+				auxValues, swap, fill, grd_normals, interval_colors, scale,
+				scale_ratio, label_freq, label_line_skip,
+				label_size, labelAlign, labelColor, labelFont,
+				sphericalDisplayCS, spatial_set);
+	}
+
+	/**
+	 * Returns an array of contour values and an indication on whether to use
+	 * dashed lines below the base value.
+	 * 
+	 * @param interval
+	 *            The contouring interval. Must be non-zero. If the interval is
+	 *            negative, then contour lines less than the base will be drawn
+	 *            as dashed lines. Must not be NaN.
+	 * @param low
+	 *            The minimum contour value. The returned array will not contain
+	 *            a value below this. Must not be NaN.
+	 * @param high
+	 *            The maximum contour value. The returned array will not contain
+	 *            a value above this. Must not be NaN.
+	 * @param ba
+	 *            The base contour value. The returned values will be integer
+	 *            multiples of the interval away from this this value. Must not
+	 *            be NaN. dash Whether or not contour lines less than the base
+	 *            should be drawn as dashed lines. This is a computed and
+	 *            returned value.
+	 * @param dash
+	 * 
+	 * @return Levels array
+	 * @throws VisADException
+	 *             The contour interval is zero or too small.
+	 */
+	
+	public static float[] intervalToLevels(float interval, float low,
+			float high, float ba, boolean[] dash) throws VisADException {
+		float[] levs = null;
+
+		if (interval == 0.0) {
+			throw new VisADException("Contour interval cannot be zero");
+		}
+
+		dash[0] = false;
+		if (interval < 0) {
+			dash[0] = true;
+			interval = -interval;
+		}
+
+		// compute list of contours
+		// compute nlo and nhi, for low and high contour values in the box
+		long nlo = Math.round((Math.ceil((low - ba) / Math.abs(interval))));
+		long nhi = Math.round((Math.floor((high - ba) / Math.abs(interval))));
+
+		// how many contour lines are needed.
+		int numc = (int) (nhi - nlo) + 1;
+		if (numc < 1)
+			return levs;
+		if (numc > 4000) {
+			throw new VisADException("Contour interval " + interval
+					+ " too small for range " + low + "," + high);
+		}
+
+		try {
+			levs = new float[numc];
+		} catch (OutOfMemoryError e) {
+			throw new VisADException("Contour interval too small");
+		}
+
+		for (int i = 0; i < numc; i++) {
+			levs[i] = ba + (nlo + i) * interval;
+		}
+
+		return levs;
+	}
+
+	/**           */
+	public static int vertexCnt = 0;
+
+	/**           */
+	public static boolean TRUEVALUE = true;
+
+	public static ContourOutput contour(float g[], int nr, int nc,
+			float[] values, float lowlimit, float highlimit, float base,
+			boolean dash, byte[][] auxValues, boolean[] swap, boolean fill,
+			float[][][] grd_normals, byte[][] interval_colors, double[] scale,
+			double scale_ratio, int label_freq, int label_line_skip,
+			double label_size, boolean labelAlign,
+			byte[] labelColor, Object labelFont, boolean sphericalDisplayCS,
+			Gridded3DSet spatial_set) throws VisADException {
+
+		dash = fill ? false : dash;
+		int ir, ic;
+		int nrm, ncm;
+		int numc, il;
+		int lr, lc, lc2, lrr, lr2, lcc;
+		float xd, yd, xx, yy;
+		float xdd, ydd;
+		float gg;
+
+		// these are just estimates
+		// int est = 2 * Length; WLH 14 April 2000
+		double dest = Math.sqrt((double) spatial_set.getLength());
+		int est = (int) (dest * Math.sqrt(dest));
+		if (est < 1000)
+			est = 1000;
+		int maxsize = (2 * 2 * est) + est;
+
+		// setup colors arrays
+		int interval_length = (interval_colors.length > 0) ? interval_colors[0].length : 0;
+
+		// setup display coordinate arrays
+		float[] vx = new float[maxsize];
+		float[] vy = new float[maxsize];
+
+		int numv;
+
+		// JDM:Find the max and min values of the data
+		float maxValue = Float.NEGATIVE_INFINITY;
+		float minValue = Float.POSITIVE_INFINITY;
+		for (int i = 0; i < g.length; i++) {
+			if (g[i] > maxValue)
+				maxValue = g[i];
+			if (g[i] < minValue)
+				minValue = g[i];
+		}
+
+		/* DRM 1999-05-18, CTR 29 Jul 1999: values could be null */
+		float[] myvals = null;
+		boolean debug = false;
+		if (values != null && (minValue < maxValue) /* grid was not all missing */) {
+			myvals = (float[]) values.clone();
+
+			// Sort the values. Get the original indidices
+			int[] indices = QuickSort.sort(myvals);
+
+			// JDM: Now, change the order of the colors to reflect the new sort
+			// order
+			// BMF 2009-03-17: don't assume there are interval colors
+			byte[][] tmpColors = new byte[interval_colors.length][interval_length];
+			for (int colorIdx = 0; colorIdx < interval_length; colorIdx++) {
+				for (int rgbIdx = 0; rgbIdx < interval_colors.length; rgbIdx++) {
+					tmpColors[rgbIdx][indices[colorIdx]] = interval_colors[rgbIdx][colorIdx];
+				}
+			}
+			interval_colors = tmpColors;
+
+			// JDM: Clip the myvals array to only use values within the range of
+			// the
+			// data
+			int minIdx = 0;
+			int maxIdx = myvals.length - 1;
+
+			for (int i = 0; i < myvals.length; i++) {
+				if (minValue <= myvals[i]) {
+					break;
+				}
+				minIdx = i;
+			}
+			for (int i = myvals.length - 1; i >= 0; i--) {
+				if (maxValue >= myvals[i]) {
+					break;
+				}
+				maxIdx = i;
+			}
+			// debug =true;
+			int newSize = maxIdx - minIdx + 1;
+			int newIdx = 0;
+			if (debug) {
+				System.err.println("min: " + minValue + " max:" + maxValue
+						+ " idx:" + minIdx + "-" + maxIdx + " new size:"
+						+ newSize);
+				System.err.print("original values: ");
+				for (int i = 0; i < myvals.length; i++) {
+					System.err.print(myvals[i] + ",");
+				}
+				System.err.println("");
+			}
+
+			float[] tmpValues = new float[newSize];
+			tmpColors = new byte[interval_colors.length][newSize];
+			for (int i = minIdx; i <= maxIdx; i++) {
+				for (int rgbIdx = 0; rgbIdx < interval_colors.length; rgbIdx++) {
+					tmpColors[rgbIdx][newIdx] = interval_colors[rgbIdx][i];
+				}
+				tmpValues[newIdx] = myvals[i];
+				newIdx++;
+			}
+
+			if (debug) {
+				System.err.print("new values: ");
+				for (int i = 0; i < tmpValues.length; i++) {
+					System.err.print(tmpValues[i] + ",");
+				}
+				System.err.println("");
+			}
+
+			myvals = tmpValues;
+			interval_colors = tmpColors;
+		}
+
+		int low;
+		int hi;
+
+		int t;
+
+		byte[][] auxLevels = null;
+		int naux = (auxValues != null) ? auxValues.length : 0;
+		byte[] auxa = null;
+		byte[] auxb = null;
+		byte[] auxc = null;
+		byte[] auxd = null;
+		if (naux > 0) {
+			for (int i = 0; i < naux; i++) {
+				if (auxValues[i].length != g.length) {
+					throw new SetException("Contour2D.contour: "
+							+ "auxValues lengths don't match");
+				}
+			}
+			auxa = new byte[naux];
+			auxb = new byte[naux];
+			auxc = new byte[naux];
+			auxd = new byte[naux];
+			auxLevels = new byte[naux][maxsize];
+		}
+
+		if (values == null)
+			return null; // WLH 24 Aug 99
+
+		// JDM: if we have no values then return
+		if (myvals == null || myvals.length == 0)
+			return null;
+
+		// flags for each level indicating dashed rendering
+		boolean[] dashFlags = new boolean[myvals.length];
+
+		int numLevels = myvals.length;
+		float minLevelValue = myvals[0];
+		float maxLevelValue = myvals[numLevels - 1];
+
+		/*
+		 * DRM: 1999-05-19 - Not needed since dash is a boolean // check for bad
+		 * contour interval if (interval==0.0) { throw new
+		 * DisplayException("Contour2D.contour: interval cannot be 0"); } if
+		 * (!dash) { // draw negative contour lines as dashed lines interval =
+		 * -interval; idash = 1; } else { idash = 0; }
+		 */
+
+		nrm = nr - 1;
+		ncm = nc - 1;
+
+		xdd = ((nr - 1) - 0.0f) / (nr - 1.0f); // = 1.0
+		ydd = ((nc - 1) - 0.0f) / (nc - 1.0f); // = 1.0
+
+		/**
+		 * -TDR xd = xdd - 0.0001f; yd = ydd - 0.0001f; gap too big *
+		 */
+		xd = xdd - 0.00002f;
+		yd = ydd - 0.00002f;
+
+		/*
+		 * set up mark array mark= 0 if avail for label center, 2 if in label,
+		 * and 1 if not available and not in label
+		 * 
+		 * lr and lc give label size in grid boxes lrr and lcc give unavailable
+		 * radius
+		 */
+		if (swap[0]) {
+			lr = 1 + (nr - 2) / 10;
+			lc = 1 + (nc - 2) / 50;
+		} else {
+			lr = 1 + (nr - 2) / 50;
+			lc = 1 + (nc - 2) / 10;
+		}
+		lc2 = lc / 2;
+		lr2 = lr / 2;
+		lrr = 1 + (nr - 2) / 8;
+		lcc = 1 + (nc - 2) / 8;
+
+		// allocate mark array
+		char[] mark = new char[nr * nc];
+
+		// set top and bottom rows to 1
+		float max_g = -Float.MAX_VALUE;
+		float min_g = Float.MAX_VALUE;
+		for (ic = 0; ic < nc; ic++) {
+			for (ir = 0; ir < lr; ir++) {
+				mark[(ic) * nr + (ir)] = 1;
+				mark[(ic) * nr + (nr - ir - 2)] = 1;
+				float val = g[(ic) * nr + (ir)];
+				if (val > max_g)
+					max_g = val;
+				if (val < min_g)
+					min_g = val;
+			}
+		}
+
+		// set left and right columns to 1
+		for (ir = 0; ir < nr; ir++) {
+			for (ic = 0; ic < lc; ic++) {
+				mark[(ic) * nr + (ir)] = 1;
+				mark[(nc - ic - 2) * nr + (ir)] = 1;
+			}
+		}
+		numv = 0;
+
+		// - color fill arrays
+		byte[][] color_bin = null;
+		byte[][][] o_flags = null;
+		short[][] n_lines = null;
+		short[][] ctrLow = null;
+
+		if (fill) {
+			color_bin = interval_colors;
+			o_flags = new byte[nrm][ncm][];
+			n_lines = new short[nrm][ncm];
+			ctrLow = new short[nrm][ncm];
+		}
+
+		ContourStripSet ctrSet = new ContourStripSet(myvals, swap,
+				scale_ratio, label_freq, label_line_skip, label_size, nr, nc, spatial_set);
+
+		visad.util.Trace.call1("Contour2d.loop", " nrm=" + nrm + " ncm=" + ncm
+				+ " naux=" + naux + " myvals.length=" + myvals.length);
+
+		// compute contours
+		for (ic = 0; ic < ncm; ic++) {
+			int ic_plus1 = ic + 1;
+			yy = ydd * ic + 0.0f; // = ic
+			for (ir = 0; ir < nrm; ir++) {
+				int ir_plus1 = ir + 1;
+				xx = xdd * ir + 0.0f; // = ir
+
+				int ic_times_nr = ic * nr;
+				int ic_plus1_times_nr = ic_plus1 * nr;
+
+				float ga, gb, gc, gd;
+				float gAvg, gMin, gMax;
+				float tmp1, tmp2;
+
+				// WLH 21 April 2000
+				// if (numv+8 >= maxsize || nump+4 >= 2*maxsize) {
+				if (numv + 8 >= maxsize) {
+					// allocate more space
+					maxsize = 2 * maxsize;
+					/*
+					 * WLH 21 April 2000 int[] tt = ipnt; ipnt = new int[2
+					 * maxsize]; System.arraycopy(tt, 0, ipnt, 0, nump);
+					 */
+					float[] tx = vx;
+					float[] ty = vy;
+					vx = new float[maxsize];
+					vy = new float[maxsize];
+					System.arraycopy(tx, 0, vx, 0, numv);
+					System.arraycopy(ty, 0, vy, 0, numv);
+					tx = null;
+					ty = null;
+					if (naux > 0) {
+						byte[][] ta = auxLevels;
+						auxLevels = new byte[naux][maxsize];
+						for (int i = 0; i < naux; i++) {
+							System.arraycopy(ta[i], 0, auxLevels[i], 0, numv);
+						}
+						ta = null;
+					}
+				}
+
+				// save index of first vertex in this grid box
+				// JDM: ipnt[nump++] = numv;
+
+				/*
+				 * ga = ( g[ (ic) nr + (ir) ] ); gb = ( g[ (ic) nr + (ir+1) ] );
+				 * gc = ( g[ (ic+1) nr + (ir) ] ); gd = ( g[ (ic+1) nr + (ir+1)
+				 * ] ); boolean miss = false; if (ga != ga || gb != gb || gc !=
+				 * gc || gd != gd) { miss = true; System.out.println("ic, ir = "
+				 * + ic + "  " + ir + " gabcd = " + ga + " " + gb + " " + gc +
+				 * " " + gd); }
+				 */
+
+				/*
+				 * if (ga != ga || gb != gb || gc != gc || gd != gd) { if
+				 * (!anymissing) { anymissing = true;
+				 * System.out.println("missing"); } } else { if (!anynotmissing)
+				 * { anynotmissing = true; System.out.println("notmissing"); } }
+				 */
+				// get 4 corner values, skip box if any are missing
+				//
+				// [c, (x,y+ydd)]-------[d, (x+xdd,y+ydd)]
+				// | |
+				// | |
+				// | |
+				// [a, (x,y)]------------[b, (x+xdd,y)]
+				//
+				// ------------------------------
+				ga = g[ic_times_nr + ir];
+       			if (Float.isNaN(ga))
+         			continue;
+				gb = g[ic_times_nr + ir_plus1];
+       			if (Float.isNaN(gb))
+         			continue;
+				gc = g[ic_plus1_times_nr + ir];
+       			if (Float.isNaN(gc))
+         			continue;
+				gd = g[ic_plus1_times_nr + ir_plus1];
+       			if (Float.isNaN(gd))
+         			continue;
+
+				/*
+				 * DRM move outside the loop byte[] auxa = null; byte[] auxb =
+				 * null; byte[] auxc = null; byte[] auxd = null; if (naux > 0) {
+				 * auxa = new byte[naux]; auxb = new byte[naux]; auxc = new
+				 * byte[naux]; auxd = new byte[naux];
+				 */
+				if (naux > 0) {
+					for (int i = 0; i < naux; i++) {
+						byte[] auxValues_i = auxValues[i];
+						auxa[i] = auxValues_i[ic_times_nr + ir];
+						auxb[i] = auxValues_i[ic_times_nr + ir_plus1];
+						auxc[i] = auxValues_i[ic_plus1_times_nr + ir];
+						auxd[i] = auxValues_i[ic_plus1_times_nr + ir_plus1];
+					}
+				}
+
+				// find average, min, and max of 4 corner values
+				gAvg = (ga + gb + gc + gd) / 4.0f;
+
+				// gMin = MIN4(ga,gb,gc,gd);
+				tmp1 = ((ga) < (gb) ? (ga) : (gb));
+				tmp2 = ((gc) < (gd) ? (gc) : (gd));
+				gMin = ((tmp1) < (tmp2) ? (tmp1) : (tmp2));
+
+				// gMax = MAX4(ga,gb,gc,gd);
+				tmp1 = ((ga) > (gb) ? (ga) : (gb));
+				tmp2 = ((gc) > (gd) ? (gc) : (gd));
+				gMax = ((tmp1) > (tmp2) ? (tmp1) : (tmp2));
+
+				/*
+				 * remove for new signature, replace with code below // compute
+				 * clow and chi, low and high contour values in the box tmp1 =
+				 * (gMin-base) / interval; clow = base + interval (( (tmp1) >= 0
+				 * ? (int) ((tmp1) + 0.5) : (int) ((tmp1)-0.5) )-1); while
+				 * (clow<gMin) { clow += interval; }
+				 * 
+				 * tmp1 = (gMax-base) / interval; chi = base + interval ((
+				 * (tmp1) >= 0 ? (int) ((tmp1) + 0.5) : (int) ((tmp1)-0.5) )+1);
+				 * while (chi>gMax) { chi -= interval; }
+				 * 
+				 * // how many contour lines in the box: tmp1 = (chi-clow) /
+				 * interval; numc = 1+( (tmp1) >= 0 ? (int) ((tmp1) + 0.5) :
+				 * (int) ((tmp1)-0.5) );
+				 * 
+				 * // gg is current contour line value gg = clow;
+				 */
+
+				low = 0;
+				hi = numLevels - 1;
+				if (gMax < minLevelValue || gMin > maxLevelValue) {
+					// no contours
+					numc = 1;
+				} else {
+					// some inside the box
+					// JDM: Instead of iterating through the whole list just do
+					// a
+					// binarySearch
+					/*
+					 * for (int i = 0; i < myvals.length; i++) { if (i == 0 &&
+					 * myvals[i] >= gn) { low = i; } else if (myvals[i] >= gn &&
+					 * myvals[i-1] < gn) { low = i; } if (i == 0 && myvals[i] >=
+					 * gx) { hi = i; } else if (myvals[i] >= gx && myvals[i-1] <
+					 * gx) { hi = i; } }
+					 */
+					hi = java.util.Arrays.binarySearch(myvals, gMax);
+					if (hi < 0)
+						hi = (-hi) - 1;
+					if (hi >= myvals.length)
+						hi = myvals.length - 1;
+					low = java.util.Arrays.binarySearch(myvals, gMin);
+					if (low < 0)
+						low = (-low) - 1;
+
+					numc = hi - low + 1;
+				}
+
+				// gg = myvals[low];
+				/*
+				 * if (!any && numc > 0) { System.out.println("gMin = " + gMin +
+				 * " gMax = " + gMax + " gAvg = " + gAvg);
+				 * System.out.println("numc = " + numc + " clow = " +
+				 * myvals[low] + " chi = " + myvals[hi]); any = true; }
+				 */
+				if (fill) {
+					o_flags[ir][ic] = new byte[2 * numc]; // - case flags
+					n_lines[ir][ic] = 0; // - number of contour line segments
+					ctrLow[ir][ic] = (short) hi;
+				}
+
+				for (il = 0; il < numc; il++) {
+					if ((low + il) >= myvals.length) {
+						System.err.println("bad range: myvals.length=" + myvals
+								+ " il=" + il + " low=" + low + " high=" + hi);
+					}
+					gg = myvals[low + il];
+
+					// WLH 21 April 2000
+					// if (numv+8 >= maxsize || nump+4 >= 2*maxsize) {
+					if (numv + 8 >= maxsize) {
+						// allocate more space
+						maxsize = 2 * maxsize;
+						/*
+						 * WLH 21 April 2000 int[] tt = ipnt; ipnt = new int[2
+						 * maxsize]; System.arraycopy(tt, 0, ipnt, 0, nump);
+						 */
+						float[] tx = vx;
+						float[] ty = vy;
+						vx = new float[maxsize];
+						vy = new float[maxsize];
+						System.arraycopy(tx, 0, vx, 0, numv);
+						System.arraycopy(ty, 0, vy, 0, numv);
+						tx = null;
+						ty = null;
+						if (naux > 0) {
+							byte[][] ta = auxLevels;
+							auxLevels = new byte[naux][maxsize];
+							for (int i = 0; i < naux; i++) {
+								System.arraycopy(ta[i], 0, auxLevels[i], 0,
+										numv);
+							}
+							ta = null;
+						}
+					}
+
+					// make sure gg is within contouring limits
+					if (gg < gMin)
+						continue;
+					if (gg > gMax)
+						break;
+					if (gg < lowlimit)
+						continue;
+					if (gg > highlimit)
+						break;
+
+					// compute orientation of lines inside box
+					int ii = 0;
+					if (gg > ga)
+						ii = 1;
+					if (gg > gb)
+						ii += 2;
+					if (gg > gc)
+						ii += 4;
+					if (gg > gd)
+						ii += 8;
+					if (ii > 7)
+						ii = 15 - ii;
+					if (ii <= 0)
+						continue;
+
+					if (fill) {
+						if ((low + il) < ctrLow[ir][ic])
+							ctrLow[ir][ic] = (short) (low + il);
+					}
+
+					// DO LABEL HERE
+					if ((mark[(ic) * nr + (ir)]) == 0) {
+						int kc, kr, mc, mr, jc, jr;
+
+						// Insert a label
+
+						// BOX TO AVOID
+						kc = ic - lc2 - lcc;
+						kr = ir - lr2 - lrr;
+						mc = kc + 2 * lcc + lc - 1;
+						mr = kr + 2 * lrr + lr - 1;
+						// OK here
+						for (jc = kc; jc <= mc; jc++) {
+							if (jc >= 0 && jc < nc) {
+								for (jr = kr; jr <= mr; jr++) {
+									if (jr >= 0 && jr < nr) {
+										if ((mark[(jc) * nr + (jr)]) != 2) {
+											mark[(jc) * nr + (jr)] = 1;
+										}
+									}
+								}
+							}
+						}
+
+						// BOX TO HOLD LABEL
+						kc = ic - lc2;
+						kr = ir - lr2;
+						mc = kc + lc - 1;
+						mr = kr + lr - 1;
+						for (jc = kc; jc <= mc; jc++) {
+							if (jc >= 0 && jc < nc) {
+								for (jr = kr; jr <= mr; jr++) {
+									if (jr >= 0 && jr < nr) {
+										mark[(jc) * nr + (jr)] = 2;
+									}
+								}
+							}
+						}
+					}
+
+					float gba, gca, gdb, gdc;
+					switch (ii) {
+					case 1:
+						gba = gb - ga;
+						gca = gc - ga;
+
+						if (naux > 0) {
+							float ratioba = (gg - ga) / gba;
+							float ratioca = (gg - ga) / gca;
+							for (int i = 0; i < naux; i++) {
+								t = (int) ((1.0f - ratioba)
+										* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+												: ((float) auxa[i])) + ratioba
+										* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+												: ((float) auxb[i])));
+								auxLevels[i][numv] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								t = (int) ((1.0f - ratioca)
+										* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+												: ((float) auxa[i])) + ratioca
+										* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+												: ((float) auxc[i])));
+								auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv] = auxa[i] +
+								 * (auxb[i]-auxa[i]) ratioba;
+								 * auxLevels[i][numv+1] = auxa[i] +
+								 * (auxc[i]-auxa[i]) ratioca;
+								 */
+							}
+						}
+
+						if (((gba) < 0 ? -(gba) : (gba)) < 0.0000001) {
+							vx[numv] = xx;
+						} else {
+							vx[numv] = xx + xd * (gg - ga) / gba;
+						}
+						vy[numv] = yy;
+						numv++;
+						if (((gca) < 0 ? -(gca) : (gca)) < 0.0000001) {
+							vy[numv] = yy;
+						} else {
+							vy[numv] = yy + yd * (gg - ga) / gca;
+						}
+						vx[numv] = xx;
+						numv++;
+						if (fill) {
+							o_flags[ir][ic][n_lines[ir][ic]] = (byte) ii;
+							n_lines[ir][ic]++;
+						}
+						if (vx[numv - 2] == vx[numv - 1]
+								|| vy[numv - 2] == vy[numv - 1]) {
+							vx[numv - 2] += 0.00001f;
+							vy[numv - 1] += 0.00001f;
+						}
+						break;
+
+					case 2:
+						gba = gb - ga;
+						gdb = gd - gb;
+
+						if (naux > 0) {
+							float ratioba = (gg - ga) / gba;
+							float ratiodb = (gg - gb) / gdb;
+							for (int i = 0; i < naux; i++) {
+								t = (int) ((1.0f - ratioba)
+										* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+												: ((float) auxa[i])) + ratioba
+										* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+												: ((float) auxb[i])));
+								auxLevels[i][numv] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								t = (int) ((1.0f - ratiodb)
+										* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+												: ((float) auxb[i])) + ratiodb
+										* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+												: ((float) auxd[i])));
+								auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv] = auxa[i] +
+								 * (auxb[i]-auxa[i]) ratioba;
+								 * auxLevels[i][numv+1] = auxb[i] +
+								 * (auxd[i]-auxb[i]) ratiodb;
+								 */
+							}
+						}
+
+						if (((gba) < 0 ? -(gba) : (gba)) < 0.0000001)
+							vx[numv] = xx;
+						else
+							vx[numv] = xx + xd * (gg - ga) / gba;
+						vy[numv] = yy;
+						numv++;
+						if (((gdb) < 0 ? -(gdb) : (gdb)) < 0.0000001)
+							vy[numv] = yy;
+						else
+							vy[numv] = yy + yd * (gg - gb) / gdb;
+						vx[numv] = xx + xd;
+						numv++;
+						if (fill) {
+							o_flags[ir][ic][n_lines[ir][ic]] = (byte) ii;
+							n_lines[ir][ic]++;
+						}
+						if (vx[numv - 2] == vx[numv - 1]
+								|| vy[numv - 2] == vy[numv - 1]) {
+							vx[numv - 2] -= 0.00001f;
+							vy[numv - 1] += 0.00001f;
+						}
+						break;
+
+					case 3:
+						gca = gc - ga;
+						gdb = gd - gb;
+
+						if (naux > 0) {
+							float ratioca = (gg - ga) / gca;
+							float ratiodb = (gg - gb) / gdb;
+							for (int i = 0; i < naux; i++) {
+								t = (int) ((1.0f - ratioca)
+										* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+												: ((float) auxa[i])) + ratioca
+										* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+												: ((float) auxc[i])));
+								auxLevels[i][numv] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								t = (int) ((1.0f - ratiodb)
+										* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+												: ((float) auxb[i])) + ratiodb
+										* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+												: ((float) auxd[i])));
+								auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv] = auxa[i] +
+								 * (auxc[i]-auxa[i]) ratioca;
+								 * auxLevels[i][numv+1] = auxb[i] +
+								 * (auxd[i]-auxb[i]) ratiodb;
+								 */
+							}
+						}
+
+						if (((gca) < 0 ? -(gca) : (gca)) < 0.0000001)
+							vy[numv] = yy;
+						else
+							vy[numv] = yy + yd * (gg - ga) / gca;
+						vx[numv] = xx;
+						numv++;
+						if (((gdb) < 0 ? -(gdb) : (gdb)) < 0.0000001)
+							vy[numv] = yy;
+						else
+							vy[numv] = yy + yd * (gg - gb) / gdb;
+						vx[numv] = xx + xd;
+						numv++;
+						if (fill) {
+							o_flags[ir][ic][n_lines[ir][ic]] = (byte) ii;
+							n_lines[ir][ic]++;
+						}
+						break;
+
+					case 4:
+						gca = gc - ga;
+						gdc = gd - gc;
+
+						if (naux > 0) {
+							float ratioca = (gg - ga) / gca;
+							float ratiodc = (gg - gc) / gdc;
+							for (int i = 0; i < naux; i++) {
+								t = (int) ((1.0f - ratioca)
+										* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+												: ((float) auxa[i])) + ratioca
+										* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+												: ((float) auxc[i])));
+								auxLevels[i][numv] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								t = (int) ((1.0f - ratiodc)
+										* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+												: ((float) auxc[i])) + ratiodc
+										* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+												: ((float) auxd[i])));
+								auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv] = auxa[i] +
+								 * (auxc[i]-auxa[i]) ratioca;
+								 * auxLevels[i][numv+1] = auxc[i] +
+								 * (auxd[i]-auxc[i]) ratiodc;
+								 */
+							}
+						}
+
+						if (((gca) < 0 ? -(gca) : (gca)) < 0.0000001)
+							vy[numv] = yy;
+						else
+							vy[numv] = yy + yd * (gg - ga) / gca;
+						vx[numv] = xx;
+						numv++;
+						if (((gdc) < 0 ? -(gdc) : (gdc)) < 0.0000001)
+							vx[numv] = xx;
+						else
+							vx[numv] = xx + xd * (gg - gc) / gdc;
+						vy[numv] = yy + yd;
+						numv++;
+						if (fill) {
+							o_flags[ir][ic][n_lines[ir][ic]] = (byte) ii;
+							n_lines[ir][ic]++;
+						}
+						if (vx[numv - 2] == vx[numv - 1]
+								|| vy[numv - 2] == vy[numv - 1]) {
+							vx[numv - 1] += 0.00001f;
+							vy[numv - 2] -= 0.00001f;
+						}
+						break;
+
+					case 5:
+						gba = gb - ga;
+						gdc = gd - gc;
+
+						if (naux > 0) {
+							float ratioba = (gg - ga) / gba;
+							float ratiodc = (gg - gc) / gdc;
+							for (int i = 0; i < naux; i++) {
+								t = (int) ((1.0f - ratioba)
+										* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+												: ((float) auxa[i])) + ratioba
+										* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+												: ((float) auxb[i])));
+								auxLevels[i][numv] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								t = (int) ((1.0f - ratiodc)
+										* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+												: ((float) auxc[i])) + ratiodc
+										* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+												: ((float) auxd[i])));
+								auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv] = auxa[i] +
+								 * (auxb[i]-auxa[i]) ratioba;
+								 * auxLevels[i][numv+1] = auxc[i] +
+								 * (auxd[i]-auxc[i]) ratiodc;
+								 */
+							}
+						}
+
+						if (((gba) < 0 ? -(gba) : (gba)) < 0.0000001)
+							vx[numv] = xx;
+						else
+							vx[numv] = xx + xd * (gg - ga) / gba;
+						vy[numv] = yy;
+						numv++;
+						if (((gdc) < 0 ? -(gdc) : (gdc)) < 0.0000001)
+							vx[numv] = xx;
+						else
+							vx[numv] = xx + xd * (gg - gc) / gdc;
+						vy[numv] = yy + yd;
+						numv++;
+						if (fill) {
+							o_flags[ir][ic][n_lines[ir][ic]] = (byte) ii;
+							n_lines[ir][ic]++;
+						}
+						break;
+
+					case 6:
+						gba = gb - ga;
+						gdc = gd - gc;
+						gca = gc - ga;
+						gdb = gd - gb;
+
+						if (naux > 0) {
+							float ratioba = (gg - ga) / gba;
+							float ratiodc = (gg - gc) / gdc;
+							float ratioca = (gg - ga) / gca;
+							float ratiodb = (gg - gb) / gdb;
+							for (int i = 0; i < naux; i++) {
+								t = (int) ((1.0f - ratioba)
+										* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+												: ((float) auxa[i])) + ratioba
+										* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+												: ((float) auxb[i])));
+								auxLevels[i][numv] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv] = auxa[i] +
+								 * (auxb[i]-auxa[i]) ratioba;
+								 */
+								if ((gg > gAvg) ^ (ga < gb)) {
+									t = (int) ((1.0f - ratioca)
+											* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+													: ((float) auxa[i])) + ratioca
+											* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+													: ((float) auxc[i])));
+									auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+											: ((t > 255) ? -1 : ((t < 128) ? t
+													: t - 256)));
+									t = (int) ((1.0f - ratiodb)
+											* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+													: ((float) auxb[i])) + ratiodb
+											* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+													: ((float) auxd[i])));
+									auxLevels[i][numv + 2] = (byte) ((t < 0) ? 0
+											: ((t > 255) ? -1 : ((t < 128) ? t
+													: t - 256)));
+									/*
+									 * MEM_WLH auxLevels[i][numv+1] = auxa[i] +
+									 * (auxc[i]-auxa[i]) ratioca;
+									 * auxLevels[i][numv+2] = auxb[i] +
+									 * (auxd[i]-auxb[i]) ratiodb;
+									 */
+								} else {
+									t = (int) ((1.0f - ratiodb)
+											* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+													: ((float) auxb[i])) + ratiodb
+											* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+													: ((float) auxd[i])));
+									auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+											: ((t > 255) ? -1 : ((t < 128) ? t
+													: t - 256)));
+									t = (int) ((1.0f - ratioca)
+											* ((auxa[i] < 0) ? ((float) auxa[i]) + 256.0f
+													: ((float) auxa[i])) + ratioca
+											* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+													: ((float) auxc[i])));
+									auxLevels[i][numv + 2] = (byte) ((t < 0) ? 0
+											: ((t > 255) ? -1 : ((t < 128) ? t
+													: t - 256)));
+									/*
+									 * MEM_WLH auxLevels[i][numv+1] = auxb[i] +
+									 * (auxd[i]-auxb[i]) ratiodb;
+									 * auxLevels[i][numv+2] = auxa[i] +
+									 * (auxc[i]-auxa[i]) ratioca;
+									 */
+								}
+								t = (int) ((1.0f - ratiodc)
+										* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+												: ((float) auxc[i])) + ratiodc
+										* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+												: ((float) auxd[i])));
+								auxLevels[i][numv + 3] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv+3] = auxc[i] +
+								 * (auxd[i]-auxc[i]) ratiodc;
+								 */
+							}
+						}
+
+						if (((gba) < 0 ? -(gba) : (gba)) < 0.0000001)
+							vx[numv] = xx;
+						else
+							vx[numv] = xx + xd * (gg - ga) / gba;
+						vy[numv] = yy;
+						numv++;
+						// here's a brain teaser
+						if ((gg > gAvg) ^ (ga < gb)) { // (XOR)
+							if (((gca) < 0 ? -(gca) : (gca)) < 0.0000001)
+								vy[numv] = yy;
+							else
+								vy[numv] = yy + yd * (gg - ga) / gca;
+							vx[numv] = xx;
+							numv++;
+							if (fill) {
+								o_flags[ir][ic][n_lines[ir][ic]] = (byte) 1
+										+ (byte) 32;
+								n_lines[ir][ic]++;
+							}
+							if (((gdb) < 0 ? -(gdb) : (gdb)) < 0.0000001)
+								vy[numv] = yy;
+							else
+								vy[numv] = yy + yd * (gg - gb) / gdb;
+							vx[numv] = xx + xd;
+							if (fill) {
+								o_flags[ir][ic][n_lines[ir][ic]] = (byte) 7
+										+ (byte) 32;
+								n_lines[ir][ic]++;
+							}
+							numv++;
+						} else {
+							if (((gdb) < 0 ? -(gdb) : (gdb)) < 0.0000001)
+								vy[numv] = yy;
+							else
+								vy[numv] = yy + yd * (gg - gb) / gdb;
+							vx[numv] = xx + xd;
+							numv++;
+							if (fill) {
+								o_flags[ir][ic][n_lines[ir][ic]] = (byte) 2
+										+ (byte) 32;
+								n_lines[ir][ic]++;
+							}
+							if (((gca) < 0 ? -(gca) : (gca)) < 0.0000001)
+								vy[numv] = yy;
+							else
+								vy[numv] = yy + yd * (gg - ga) / gca;
+							vx[numv] = xx;
+							numv++;
+							if (fill) {
+								o_flags[ir][ic][n_lines[ir][ic]] = (byte) 4
+										+ (byte) 32;
+								n_lines[ir][ic]++;
+							}
+						}
+						if (((gdc) < 0 ? -(gdc) : (gdc)) < 0.0000001)
+							vx[numv] = xx;
+						else
+							vx[numv] = xx + xd * (gg - gc) / gdc;
+						vy[numv] = yy + yd;
+						numv++;
+						break;
+
+					case 7:
+						gdb = gd - gb;
+						gdc = gd - gc;
+
+						if (naux > 0) {
+							float ratiodb = (gg - gb) / gdb;
+							float ratiodc = (gg - gc) / gdc;
+							for (int i = 0; i < naux; i++) {
+								t = (int) ((1.0f - ratiodb)
+										* ((auxb[i] < 0) ? ((float) auxb[i]) + 256.0f
+												: ((float) auxb[i])) + ratiodb
+										* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+												: ((float) auxd[i])));
+								auxLevels[i][numv] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								t = (int) ((1.0f - ratiodc)
+										* ((auxc[i] < 0) ? ((float) auxc[i]) + 256.0f
+												: ((float) auxc[i])) + ratiodc
+										* ((auxd[i] < 0) ? ((float) auxd[i]) + 256.0f
+												: ((float) auxd[i])));
+								auxLevels[i][numv + 1] = (byte) ((t < 0) ? 0
+										: ((t > 255) ? -1 : ((t < 128) ? t
+												: t - 256)));
+								/*
+								 * MEM_WLH auxLevels[i][numv] = auxb[i] +
+								 * (auxb[i]-auxb[i]) ratiodb;
+								 * auxLevels[i][numv+1] = auxc[i] +
+								 * (auxd[i]-auxc[i]) ratiodc;
+								 */
+							}
+						}
+
+						if (((gdb) < 0 ? -(gdb) : (gdb)) < 0.0000001)
+							vy[numv] = yy;
+						else
+							vy[numv] = yy + yd * (gg - gb) / gdb;
+						vx[numv] = xx + xd;
+						numv++;
+						if (((gdc) < 0 ? -(gdc) : (gdc)) < 0.0000001)
+							vx[numv] = xx;
+						else
+							vx[numv] = xx + xd * (gg - gc) / gdc;
+						vy[numv] = yy + yd;
+						numv++;
+						if (fill) {
+							o_flags[ir][ic][n_lines[ir][ic]] = (byte) ii;
+							n_lines[ir][ic]++;
+						}
+						if (vx[numv - 2] == vx[numv - 1]
+								|| vy[numv - 2] == vy[numv - 1]) {
+							vx[numv - 1] -= 0.00001f;
+							vy[numv - 2] -= 0.00001f;
+						}
+						break;
+					} // switch
+
+					// If contour level is negative, make dashed line
+					if (gg < base && dash) { /* DRM: 1999-05-19 */
+						dashFlags[low + il] = true;
+					}
+					/*
+					 * if ((20.0 <= vy[numv-2] && vy[numv-2] < 22.0) || (20.0 <=
+					 * vy[numv-1] && vy[numv-1] < 22.0)) {
+					 * System.out.println("vy = " + vy[numv-1] + " " +
+					 * vy[numv-2] + " ic, ir = " + ic + " " + ir); }
+					 */
+
+					if (ii == 6) { // - add last two pairs
+                                                ctrSet.add(vx, vy, numv - 4, numv - 3, low + il);
+                                                ctrSet.add(vx, vy, numv - 2, numv - 1, low + il);
+					} else {
+                                                ctrSet.add(vx, vy, numv - 2, numv - 1, low + il);
+					}
+
+				} // for il -- NOTE: gg incremented in for statement
+			} // for ic
+		} // for ir
+
+		// System.err.println ("ii:" + ii1 + " " +ii2 + " " +ii3 + " " +ii4 +
+		// " "
+		// +ii5 + " " +ii6);
+		visad.util.Trace.call2("Contour2d.loop");
+
+		/** ------------------- Color Fill ------------------------- */
+		TriangleStripBuilder triStripBldr = null;
+
+		if (fill) {
+			triStripBldr = new TriangleStripBuilder(ncm, nrm, color_bin.length);
+			fillGridBox(g, n_lines, vx, vy, xd, xdd, yd, ydd, nr, nrm, nc, ncm,
+					ctrLow, o_flags, myvals, color_bin, grd_normals,
+					triStripBldr);
+			// BMF 2006-10-04 do not return, ie. draw labels on filled contours
+			// for now, just return because we don't need to do labels
+			// return;
+		}
+
+		// ---TDR, build Contour Strips
+
+		Trace.call1("Contour2d.getLineColorArrays");
+		ctrSet.getLineColorArrays(vx, vy, auxLevels, labelColor, labelFont,
+				labelAlign, sphericalDisplayCS, dashFlags);
+		Trace.call2("Contour2d.getLineColorArrays");
+
+		return new ContourOutput(ctrSet, triStripBldr);
+	}
+
+	/**
+	 * 
+	 * @param g
+	 * @param n_lines
+	 * @param vx
+	 * @param vy
+	 * @param xd
+	 * @param xdd
+	 * @param yd
+	 * @param ydd
+	 * @param nr
+	 * @param nrm
+	 * @param nc
+	 * @param ncm
+	 * @param ctrLow
+	 * @param o_flags
+	 * @param values
+	 * @param color_bin
+	 * @param grd_normals
+	 * @param triStripBldr
+	 */
+	
+	private static void fillGridBox(float[] g, short[][] n_lines, float[] vx,
+			float[] vy, float xd, float xdd, float yd, float ydd, int nr,
+			int nrm, int nc, int ncm, short[][] ctrLow, byte[][][] o_flags,
+			float[] values, byte[][] color_bin, float[][][] grd_normals,
+			TriangleStripBuilder triStripBldr) {
+		float xx, yy;
+		int[] numv = new int[1];
+		numv[0] = 0;
+
+		for (int ic = 0; ic < ncm; ic++) {
+			yy = ydd * ic + 0.0f;
+			for (int ir = 0; ir < nrm; ir++) {
+				triStripBldr.setGridBox(ic, ir);
+				float ga, gb, gc, gd;
+				xx = xdd * ir + 0.0f;
+
+				// get 4 corner values, skip box if any are missing
+				ga = (g[(ic) * nr + (ir)]);
+       			// test for missing
+       			if (Float.isNaN(ga))
+         			continue;
+				gb = (g[(ic) * nr + (ir + 1)]);
+       			// test for missing
+       			if (Float.isNaN(gb))
+         			continue;
+				gc = (g[(ic + 1) * nr + (ir)]);
+       			// test for missing
+       			if (Float.isNaN(gc))
+         			continue;
+				gd = (g[(ic + 1) * nr + (ir + 1)]);
+       			// test for missing
+       			if (Float.isNaN(gd ))
+         			continue;
+
+				numv[0] += n_lines[ir][ic] * 2;
+
+				fillGridBox(new float[] { ga, gb, gc, gd }, n_lines[ir][ic],
+						vx, vy, xx, yy, xd, yd, ic, ir, ctrLow[ir][ic],
+						numv[0], o_flags[ir][ic], values, color_bin,
+						grd_normals, triStripBldr);
+			}
+		}
+	}
+
+	/**
+	 * 
+	 * @param corners
+	 * @param numc
+	 * @param vx
+	 * @param vy
+	 * @param xx
+	 * @param yy
+	 * @param xd
+	 * @param yd
+	 * @param nc
+	 * @param nr
+	 * @param ctrLow
+	 * @param numv
+	 * @param o_flags
+	 * @param values
+	 * @param color_bin
+	 * @param grd_normals
+	 * @param triStripBldr
+	 */
+	
+	private static void fillGridBox(float[] corners, int numc, float[] vx,
+			float[] vy, float xx, float yy, float xd, float yd, int nc, int nr,
+			short ctrLow, int numv, byte[] o_flags, float[] values,
+			byte[][] color_bin, float[][][] grd_normals,
+			TriangleStripBuilder triStripBldr) {
+
+		int il = 0;
+		int color_length = color_bin.length;
+		float[] vv1 = new float[2];
+		float[] vv2 = new float[2];
+		float[] vv1_last = new float[2];
+		float[] vv2_last = new float[2];
+		float[][] vv = new float[2][2];
+		float[][] vv_last = new float[2][2];
+
+		int dir = 1;
+		int start = numv - 2;
+		int o_start = numc - 1;
+		int o_idx = 0;
+		byte o_flag = o_flags[o_idx];
+		int[] closed = { 0 };
+		boolean up;
+		boolean right;
+
+		int v_idx = start + dir * il * 2;
+
+		int cc_start = (dir > 0) ? (ctrLow - 1) : (ctrLow + (numc - 1));
+
+		// -- color level at corners
+		// ------------------------------
+		byte[][] crnr_color = new byte[4][color_length];
+		int[] crnrLevelIdx = new int[4];
+		boolean[] crnr_out = new boolean[] { true, true, true, true };
+		boolean all_out = true;
+		for (int tt = 0; tt < corners.length; tt++) {
+			int cc = 0;
+			int kk = 0;
+			for (kk = 0; kk < (values.length - 1); kk++) {
+				if ((corners[tt] >= values[kk])
+						&& (corners[tt] < values[kk + 1])) {
+					cc = kk;
+					all_out = false;
+					crnr_out[tt] = false;
+				}
+			}
+			for (int ii = 0; ii < color_length; ii++) {
+				crnr_color[tt][ii] = color_bin[ii][cc];
+			}
+			crnrLevelIdx[tt] = cc;
+		}
+
+		dir = 1;
+		start = numv - numc * 2;
+		o_start = 0;
+		v_idx = start + dir * il * 2;
+		up = false;
+		right = false;
+		float[] x_avg = new float[2];
+		float[] y_avg = new float[2];
+
+		if (numc > 1) { // -- first/next ctr line midpoints
+			int idx = v_idx;
+			x_avg[0] = (vx[idx] + vx[idx + 1]) / 2;
+			y_avg[0] = (vy[idx] + vy[idx + 1]) / 2;
+			idx = v_idx + 2;
+			x_avg[1] = (vx[idx] + vx[idx + 1]) / 2;
+			y_avg[1] = (vy[idx] + vy[idx + 1]) / 2;
+			if ((x_avg[1] - x_avg[0]) > 0)
+				up = true;
+			if ((y_avg[1] - y_avg[0]) > 0)
+				right = true;
+		} else if (numc == 1) { // - default values for logic below
+			x_avg[0] = 0f;
+			y_avg[0] = 0f;
+			x_avg[1] = 1f;
+			y_avg[1] = 1f;
+		} else if (numc == 0) // - empty grid box (no contour lines)
+		{
+			if (all_out)
+				return;
+
+			float[][] tri = new float[2][4];
+			float[][] normals = new float[3][4];
+			byte[] color = new byte[color_length];
+			for (int ii = 0; ii < color_length; ii++) {
+				color[ii] = crnr_color[0][ii];
+			}
+
+			normals[0][0] = grd_normals[nc][nr][0];
+			normals[1][0] = grd_normals[nc][nr][1];
+			normals[2][0] = grd_normals[nc][nr][2];
+			tri[0][0] = xx;
+			tri[1][0] = yy;
+
+			normals[0][1] = grd_normals[nc + 1][nr][0];
+			normals[1][1] = grd_normals[nc + 1][nr][1];
+			normals[2][1] = grd_normals[nc + 1][nr][2];
+			tri[0][1] = xx;
+			tri[1][1] = yy + yd;
+
+			normals[0][2] = grd_normals[nc][nr + 1][0];
+			normals[1][2] = grd_normals[nc][nr + 1][1];
+			normals[2][2] = grd_normals[nc][nr + 1][2];
+			tri[0][2] = xx + xd;
+			tri[1][2] = yy;
+
+			normals[0][3] = grd_normals[nc + 1][nr + 1][0];
+			normals[1][3] = grd_normals[nc + 1][nr + 1][1];
+			normals[2][3] = grd_normals[nc + 1][nr + 1][2];
+			tri[0][3] = xx + xd;
+			tri[1][3] = yy + yd;
+
+			byte first_tri_orient = CLOCKWISE;
+			byte last_tri_orient = CNTRCLOCKWISE;
+			byte first_strp_side = SIDE_LEFT;
+			byte last_strp_side = SIDE_RIGHT;
+
+			triStripBldr.addVerticies(crnrLevelIdx[0], tri, normals, color,
+					first_strp_side, first_tri_orient, last_strp_side,
+					last_tri_orient);
+
+			return;
+		} // -- end no contour lines
+
+		// If any case 6 (saddle point), handle all contour lines with special
+		// logic.
+		for (int iii = 0; iii < o_flags.length; iii++) {
+			if (o_flags[iii] > 32) {
+				fillCaseSix(xx, yy, xd, yd, v_idx, dir, o_flags, ctrLow, vx,
+						vy, nc, nr, crnr_color, crnrLevelIdx, crnr_out,
+						color_bin, color_length, grd_normals, closed,
+						triStripBldr);
+				return;
+			}
+		}
+
+		// -- start making triangles for color fill
+		// ---------------------------------------------
+
+		if (o_flag == 1 || o_flag == 4 || o_flag == 2 || o_flag == 7) {
+			boolean opp = false;
+			float dy = 0;
+			float dx = 0;
+			float dist_0 = 0;
+			float dist_1 = 0;
+
+			/**
+			 * compare midpoints distances for first/next contour lines
+			 * -------------------------------------------------
+			 */
+			if (o_flag == 1) {
+				dy = (y_avg[1] - (yy));
+				dx = (x_avg[1] - (xx));
+				dist_1 = dy * dy + dx * dx;
+				dy = (y_avg[0] - (yy));
+				dx = (x_avg[0] - (xx));
+				dist_0 = dy * dy + dx * dx;
+			}
+			if (o_flag == 2) {
+				dy = (y_avg[1] - (yy));
+				dx = (x_avg[1] - (xx + xd));
+				dist_1 = dy * dy + dx * dx;
+				dy = (y_avg[0] - (yy));
+				dx = (x_avg[0] - (xx + xd));
+				dist_0 = dy * dy + dx * dx;
+			}
+			if (o_flag == 4) {
+				dy = (y_avg[1] - (yy + yd));
+				dx = (x_avg[1] - (xx));
+				dist_1 = dy * dy + dx * dx;
+				dy = (y_avg[0] - (yy + yd));
+				dx = (x_avg[0] - (xx));
+				dist_0 = dy * dy + dx * dx;
+			}
+			if (o_flag == 7) {
+				dy = (y_avg[1] - (yy + yd));
+				dx = (x_avg[1] - (xx + xd));
+				dist_1 = dy * dy + dx * dx;
+				dy = (y_avg[0] - (yy + yd));
+				dx = (x_avg[0] - (xx + xd));
+				dist_0 = dy * dy + dx * dx;
+			}
+			if (dist_1 < dist_0)
+				opp = true;
+			if (opp) {
+				fillToOppCorner(xx, yy, xd, yd, v_idx, o_flag, dir, vx, vy, nc,
+						nr, crnr_color, crnrLevelIdx, crnr_out, grd_normals,
+						closed, triStripBldr);
+			} else {
+				fillToNearCorner(xx, yy, xd, yd, v_idx, o_flag, dir, vx, vy,
+						nc, nr, crnr_color, crnrLevelIdx, crnr_out,
+						grd_normals, closed, triStripBldr);
+			}
+		} else if (o_flags[o_idx] == 3) {
+			int flag = 1;
+			if (right)
+				flag = -1;
+			fillToSide(xx, yy, xd, yd, v_idx, o_flag, flag, dir, vx, vy, nc,
+					nr, crnr_color, crnrLevelIdx, crnr_out, grd_normals,
+					closed, triStripBldr);
+		} else if (o_flags[o_idx] == 5) {
+			int flag = 1;
+			if (!up)
+				flag = -1;
+			fillToSide(xx, yy, xd, yd, v_idx, o_flag, flag, dir, vx, vy, nc,
+					nr, crnr_color, crnrLevelIdx, crnr_out, grd_normals,
+					closed, triStripBldr);
+		}
+
+		byte last_o = o_flags[o_idx];
+
+		// - move to next contour line
+		// --------------------------------
+		il++;
+		for (il = 1; il < numc; il++) { // - iterate over contour line box
+										// intersection segments
+			v_idx = start + dir * il * 2;
+			o_idx = o_start + dir * il;
+			int v_idx_last = v_idx - 2 * dir;
+			int cc = cc_start + dir * il;
+
+			if (o_flags[o_idx] != last_o) // - contour line case change
+			{
+				// - box side of line segment verticies, 0->v_idx,
+				// 1->v_idx->v_idx+1
+				byte[] side_s = new byte[2];
+
+				// - box side of last line segment verticies, 0->v_idx_last,
+				// 1->v_idx->v_idx_last+1
+				byte[] last_side_s = new byte[2];
+
+				boolean flip;
+
+				getBoxSide(vx, vy, xx, xd, yy, yd, v_idx, dir, o_flags[o_idx],
+						side_s);
+				getBoxSide(vx, vy, xx, xd, yy, yd, v_idx_last, dir, last_o,
+						last_side_s);
+
+				// -1: no line segment points lie on the same box side
+				// 0:
+				// 1:
+				int same_side_idx = -1;
+
+				if (side_s[0] == last_side_s[0]) {
+					flip = false;
+					same_side_idx = 0;
+				} else if (side_s[0] == last_side_s[1]) {
+					flip = true;
+					same_side_idx = 1;
+				} else if (side_s[1] == last_side_s[0]) {
+					flip = true;
+					same_side_idx = 0;
+				} else if (side_s[1] == last_side_s[1]) {
+					flip = false;
+					same_side_idx = 1;
+				} else {
+					if (((side_s[0] + last_side_s[0]) & 1) == 1) { // flip so
+																	// 0idx and
+																	// 1idx on
+																	// opposite
+																	// sides
+						flip = false;
+					} else {
+						flip = true;
+					}
+				}
+
+				// - flip only (v_idx, v_idx+1) line segment, not 'last', ie.
+				// (v_idx_last, v_idx_last+1)
+				if (!flip) {
+					vv1[0] = vx[v_idx];
+					vv1[1] = vy[v_idx];
+					vv2[0] = vx[v_idx + dir];
+					vv2[1] = vy[v_idx + dir];
+
+					vv[0][0] = vx[v_idx];
+					vv[1][0] = vy[v_idx];
+					vv[0][1] = vx[v_idx + dir];
+					vv[1][1] = vy[v_idx + dir];
+				} else { // do the flip
+					vv1[0] = vx[v_idx + dir];
+					vv1[1] = vy[v_idx + dir];
+					vv2[0] = vx[v_idx];
+					vv2[1] = vy[v_idx];
+
+					vv[0][0] = vx[v_idx + dir];
+					vv[1][0] = vy[v_idx + dir];
+					vv[0][1] = vx[v_idx];
+					vv[1][1] = vy[v_idx];
+					// - reflect that the flipped occurred.
+					byte tmp = side_s[0];
+					side_s[0] = side_s[1];
+					side_s[1] = tmp;
+				}
+				vv1_last[0] = vx[v_idx_last];
+				vv1_last[1] = vy[v_idx_last];
+				vv2_last[0] = vx[v_idx_last + dir];
+				vv2_last[1] = vy[v_idx_last + dir];
+
+				vv_last[0][0] = vx[v_idx_last];
+				vv_last[1][0] = vy[v_idx_last];
+				vv_last[0][1] = vx[v_idx_last + dir];
+				vv_last[1][1] = vy[v_idx_last + dir];
+
+				fillCaseChange(xx, yy, xd, yd, nc, nr, vv1, vv2, vv1_last,
+						vv2_last, color_bin, cc, color_length, grd_normals,
+						closed, same_side_idx, side_s, last_side_s,
+						triStripBldr);
+			} else {
+				vv1[0] = vx[v_idx];
+				vv1[1] = vy[v_idx];
+				vv2[0] = vx[v_idx + dir];
+				vv2[1] = vy[v_idx + dir];
+				vv1_last[0] = vx[v_idx_last];
+				vv1_last[1] = vy[v_idx_last];
+				vv2_last[0] = vx[v_idx_last + dir];
+				vv2_last[1] = vy[v_idx_last + dir];
+
+				fillToLast(xx, yy, xd, yd, nc, nr, vv1, vv2, vv1_last,
+						vv2_last, o_flags[o_idx], color_bin, cc, color_length,
+						grd_normals, -1, triStripBldr);
+			}
+			last_o = o_flags[o_idx];
+		} // ---- contour lines loop
+
+		/*- last or first/last contour line
+		------------------------------------*/
+		int flag_set = 0;
+		if ((last_o == 1) || (last_o == 2) || (last_o == 4) || (last_o == 7)) {
+			if (last_o == 1)
+				flag_set = (closed[0] & 1);
+			if (last_o == 2)
+				flag_set = (closed[0] & 2);
+			if (last_o == 4)
+				flag_set = (closed[0] & 4);
+			if (last_o == 7)
+				flag_set = (closed[0] & 8);
+
+			if (flag_set > 0) {
+				fillToOppCorner(xx, yy, xd, yd, v_idx, last_o, dir, vx, vy, nc,
+						nr, crnr_color, crnrLevelIdx, crnr_out, grd_normals,
+						closed, triStripBldr);
+			} else {
+				fillToNearCorner(xx, yy, xd, yd, v_idx, last_o, dir, vx, vy,
+						nc, nr, crnr_color, crnrLevelIdx, crnr_out,
+						grd_normals, closed, triStripBldr);
+			}
+		} else if (last_o == 3) {
+			int flag = -1;
+			if (closed[0] == 3)
+				flag = 1;
+			fillToSide(xx, yy, xd, yd, v_idx, last_o, flag, dir, vx, vy, nc,
+					nr, crnr_color, crnrLevelIdx, crnr_out, grd_normals,
+					closed, triStripBldr);
+		} else if (last_o == 5) {
+			int flag = 1;
+			if (closed[0] == 5)
+				flag = -1;
+			fillToSide(xx, yy, xd, yd, v_idx, last_o, flag, dir, vx, vy, nc,
+					nr, crnr_color, crnrLevelIdx, crnr_out, grd_normals,
+					closed, triStripBldr);
+		}
+
+	} // --- end fillGridBox
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param xx
+	 * @param xd
+	 * @param yy
+	 * @param yd
+	 * @param v_idx
+	 * @param dir
+	 * @param o_flag
+	 * @param side
+	 */
+	
+	private static void getBoxSide(float[] vx, float[] vy, float xx, float xd,
+			float yy, float yd, int v_idx, int dir, byte o_flag, byte[] side) {
+		/*
+		 * if (vy[v_idx] == yy) side[0] = 0; // a-b if (vy[v_idx] == (yy + yd))
+		 * side[0] = 2; // c-d if (vx[v_idx] == xx) side[0] = 3; // a-c if
+		 * (vx[v_idx] == (xx + xd)) side[0] = 1; // b-d
+		 */
+
+		for (int kk = 0; kk < 2; kk++) {
+			int ii = v_idx + kk * dir;
+			switch (o_flag) {
+			case 1:
+				side[kk] = 3;
+				if (vy[ii] == yy)
+					side[kk] = 0;
+				break;
+			case 2:
+				side[kk] = 1;
+				if (vy[ii] == yy)
+					side[kk] = 0;
+				break;
+			case 4:
+				side[kk] = 3;
+				if (vy[ii] == (yy + yd))
+					side[kk] = 2;
+				break;
+			case 7:
+				side[kk] = 1;
+				if (vy[ii] == (yy + yd))
+					side[kk] = 2;
+				break;
+			case 3:
+				side[kk] = 1;
+				if (vx[ii] == xx)
+					side[kk] = 3;
+				break;
+			case 5:
+				side[kk] = 0;
+				if (vy[ii] == (yy + yd))
+					side[kk] = 2;
+				break;
+			}
+		}
+		// - check for degenerate corner case, ie both intersection points on a
+		// corner
+		switch (o_flag) {
+		case 1:
+			if (side[0] == side[1]) {
+				side[0] = 0;
+				side[1] = 3;
+			}
+			break;
+		case 2:
+			if (side[0] == side[1]) {
+				side[0] = 0;
+				side[1] = 1;
+			}
+			break;
+		case 4:
+			if (side[0] == side[1]) {
+				side[0] = 3;
+				side[1] = 2;
+			}
+			break;
+		case 7:
+			if (side[0] == side[1]) {
+				side[0] = 1;
+				side[1] = 2;
+			}
+			break;
+		}
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param xx
+	 * @param yy
+	 * @param nc
+	 * @param nr
+	 * @param xd
+	 * @param yd
+	 * @param grd_normals
+	 * @param n_idx
+	 * @param tri_normals
+	 */
+
+	private static void interpNormals(float[] vx, float[] vy, float xx,
+			float yy, int nc, int nr, float xd, float yd,
+			float[][][] grd_normals, float[][] tri_normals) {
+
+		int n_verts = vx.length;
+		float[][] tmp = new float[3][1];
+
+		for (int k = 0; k < n_verts; k++) {
+			interpNormals(vx[k], vy[k], xx, yy, nc, nr, xd, yd, grd_normals,
+					tmp);
+			tri_normals[0][k] = tmp[0][0];
+			tri_normals[1][k] = tmp[1][0];
+			tri_normals[2][k] = tmp[2][0];
+		}
+	}
+
+	private static void interpNormals(float vx, float vy, float xx, float yy,
+			int nc, int nr, float xd, float yd, float[][][] grd_normals,
+			float[][] tri_normals) {
+
+		int[] n_idx = new int[] { 0 };
+		float[] temp = new float[3];
+
+		interpNormals(vx, vy, xx, yy, nc, nr, xd, yd, grd_normals, n_idx, temp);
+		tri_normals[0][0] = temp[0];
+		tri_normals[1][0] = temp[1];
+		tri_normals[2][0] = temp[2];
+	}
+
+	private static void interpNormals(float vx, float vy, float xx, float yy,
+			int nc, int nr, float xd, float yd, float[][][] grd_normals,
+			int[] n_idx, float[] tri_normals) {
+		int side = -1;
+		float[] nn = new float[3];
+
+		if (vy == yy)
+			side = 0; // a-b
+		if (vy == (yy + yd))
+			side = 2; // c-d
+		if (vx == xx)
+			side = 3; // a-c
+		if (vx == (xx + xd))
+			side = 1; // b-d
+
+		float dx = vx - xx;
+		float dy = vy - yy;
+
+		switch (side) {
+		case 0:
+			nn[0] = ((grd_normals[nc][nr + 1][0] - grd_normals[nc][nr][0]) / xd)
+					* dx + grd_normals[nc][nr][0];
+			nn[1] = ((grd_normals[nc][nr + 1][1] - grd_normals[nc][nr][1]) / xd)
+					* dx + grd_normals[nc][nr][1];
+			nn[2] = ((grd_normals[nc][nr + 1][2] - grd_normals[nc][nr][2]) / xd)
+					* dx + grd_normals[nc][nr][2];
+			break;
+		case 3:
+			nn[0] = ((grd_normals[nc + 1][nr][0] - grd_normals[nc][nr][0]) / yd)
+					* dy + grd_normals[nc][nr][0];
+			nn[1] = ((grd_normals[nc + 1][nr][1] - grd_normals[nc][nr][1]) / yd)
+					* dy + grd_normals[nc][nr][1];
+			nn[2] = ((grd_normals[nc + 1][nr][2] - grd_normals[nc][nr][2]) / yd)
+					* dy + grd_normals[nc][nr][2];
+			break;
+		case 1:
+			nn[0] = ((grd_normals[nc + 1][nr + 1][0] - grd_normals[nc][nr + 1][0]) / yd)
+					* dy + grd_normals[nc][nr + 1][0];
+			nn[1] = ((grd_normals[nc + 1][nr + 1][1] - grd_normals[nc][nr + 1][1]) / yd)
+					* dy + grd_normals[nc][nr + 1][1];
+			nn[2] = ((grd_normals[nc + 1][nr + 1][2] - grd_normals[nc][nr + 1][2]) / yd)
+					* dy + grd_normals[nc][nr + 1][2];
+			break;
+		case 2:
+			nn[0] = ((grd_normals[nc + 1][nr + 1][0] - grd_normals[nc + 1][nr][0]) / xd)
+					* dx + grd_normals[nc + 1][nr][0];
+			nn[1] = ((grd_normals[nc + 1][nr + 1][1] - grd_normals[nc + 1][nr][1]) / xd)
+					* dx + grd_normals[nc + 1][nr][1];
+			nn[2] = ((grd_normals[nc + 1][nr + 1][2] - grd_normals[nc + 1][nr][2]) / xd)
+					* dx + grd_normals[nc + 1][nr][2];
+			break;
+		default:
+			System.out.println("interpNormals, bad side: " + side);
+		}
+		// - re-normalize
+		float mag = (float) Math.sqrt(nn[0] * nn[0] + nn[1] * nn[1] + nn[2]
+				* nn[2]);
+		nn[0] /= mag;
+		nn[1] /= mag;
+		nn[2] /= mag;
+		tri_normals[n_idx[0]++] = nn[0];
+		tri_normals[n_idx[0]++] = nn[1];
+		tri_normals[n_idx[0]++] = nn[2];
+	}
+
+	/**
+	 * 
+	 * @param xx
+	 * @param yy
+	 * @param xd
+	 * @param yd
+	 * @param nc
+	 * @param nr
+	 * @param vv1
+	 * @param vv2
+	 * @param vv1_last
+	 * @param vv2_last
+	 * @param kase
+	 * @param color_bin
+	 * @param cc
+	 * @param color_length
+	 * @param grd_normals
+	 * @param same_side_idx
+	 * @param triStripBldr
+	 */
+	
+	private static void fillToLast(float xx, float yy, float xd, float yd,
+			int nc, int nr, float[] vv1, float[] vv2, float[] vv1_last,
+			float[] vv2_last, byte kase, byte[][] color_bin, int cc,
+			int color_length, float[][][] grd_normals, int same_side_idx,
+			TriangleStripBuilder triStripBldr) {
+		float[][] tri = new float[2][4];
+		float[][] normals = new float[3][4];
+		byte[] side_s = new byte[2];
+		byte[] last_side_s = new byte[2];
+		byte[] strp_sides = new byte[4];
+		int startIdx = 0;
+
+		float[] vx = new float[2];
+		float[] vy = new float[2];
+
+		vx[0] = vv1[0];
+		vx[1] = vv2[0];
+		vy[0] = vv1[1];
+		vy[1] = vv2[1];
+		getBoxSide(vx, vy, xx, xd, yy, yd, 0, 1, kase, side_s);
+
+		vx[0] = vv1_last[0];
+		vx[1] = vv2_last[0];
+		vy[0] = vv1_last[1];
+		vy[1] = vv2_last[1];
+		getBoxSide(vx, vy, xx, xd, yy, yd, 0, 1, kase, last_side_s);
+
+		fillToLast(xx, yy, xd, yd, nc, nr, vv1, vv2, vv1_last, vv2_last,
+				color_bin, cc, color_length, grd_normals, same_side_idx,
+				side_s, last_side_s, strp_sides, tri, normals, startIdx,
+				triStripBldr);
+
+	}
+
+	private static void fillToLast(float xx, float yy, float xd, float yd,
+			int nc, int nr, float[] vv1, float[] vv2, float[] vv1_last,
+			float[] vv2_last, byte[][] color_bin, int cc, int color_length,
+			float[][][] grd_normals, int same_side_idx, byte[] side_s,
+			byte[] last_side_s, byte[] strp_sides, float[][] tri,
+			float[][] normals, int startIdx, TriangleStripBuilder triStripBldr) {
+		float x0, y0, x1, y1, x2, y2, x3, y3;
+
+		float[][] tmp = new float[3][1];
+		byte[] color = new byte[color_length];
+
+		for (int ii = 0; ii < color_length; ii++) {
+			color[ii] = color_bin[ii][cc];
+		}
+
+		if (same_side_idx <= 0) {
+			x0 = vv1[0];
+			y0 = vv1[1];
+			x1 = vv1_last[0];
+			y1 = vv1_last[1];
+			x2 = vv2[0];
+			y2 = vv2[1];
+			x3 = vv2_last[0];
+			y3 = vv2_last[1];
+			strp_sides[0] = side_s[0];
+			strp_sides[1] = last_side_s[0];
+			strp_sides[2] = side_s[1];
+			strp_sides[3] = last_side_s[1];
+		} else {
+			x0 = vv2[0];
+			y0 = vv2[1];
+			x1 = vv2_last[0];
+			y1 = vv2_last[1];
+			x2 = vv1[0];
+			y2 = vv1[1];
+			x3 = vv1_last[0];
+			y3 = vv1_last[1];
+			strp_sides[0] = side_s[1];
+			strp_sides[1] = last_side_s[1];
+			strp_sides[2] = side_s[0];
+			strp_sides[3] = last_side_s[0];
+		}
+
+		int idx = startIdx + 0;
+		tri[0][idx] = x0;
+		tri[1][idx] = y0;
+		interpNormals(tri[0][idx], tri[1][idx], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][idx] = tmp[0][0];
+		normals[1][idx] = tmp[1][0];
+		normals[2][idx] = tmp[2][0];
+
+		idx = startIdx + 1;
+		tri[0][idx] = x1;
+		tri[1][idx] = y1;
+		interpNormals(tri[0][idx], tri[1][idx], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][idx] = tmp[0][0];
+		normals[1][idx] = tmp[1][0];
+		normals[2][idx] = tmp[2][0];
+
+		idx = startIdx + 2;
+		tri[0][idx] = x2;
+		tri[1][idx] = y2;
+		interpNormals(tri[0][idx], tri[1][idx], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][idx] = tmp[0][0];
+		normals[1][idx] = tmp[1][0];
+		normals[2][idx] = tmp[2][0];
+
+		idx = startIdx + 3;
+		tri[0][idx] = x3;
+		tri[1][idx] = y3;
+		interpNormals(tri[0][idx], tri[1][idx], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][idx] = tmp[0][0];
+		normals[1][idx] = tmp[1][0];
+		normals[2][idx] = tmp[2][0];
+
+		if (triStripBldr != null) {
+			byte first_tri_orient = 0;
+			byte last_tri_orient = 0;
+			byte kase = strp_sides[0];
+
+			byte first_strp_side = strp_sides[0];
+			byte last_strp_side = strp_sides[3];
+
+			switch (kase) {
+			case 1:
+				if ((tri[0][1] - tri[0][0]) >= 0) {
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				} else {
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+				break;
+			case 2:
+				if ((tri[0][1] - tri[0][0]) >= 0) {
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				} else {
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+				break;
+			case 4:
+				if ((tri[1][1] - tri[1][0]) >= 0) {
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				} else {
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				}
+				break;
+			case 7:
+				if ((tri[1][1] - tri[1][0]) >= 0) {
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				} else {
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+				break;
+			case 3:
+				if ((tri[1][1] - tri[1][0]) >= 0) {
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				} else {
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				}
+				break;
+			case 5:
+				if ((tri[0][1] - tri[0][0]) >= 0) {
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				} else {
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+				break;
+			}
+
+			triStripBldr.addVerticies(cc, tri, normals, color, first_strp_side,
+					first_tri_orient, last_strp_side, last_tri_orient);
+		}
+	}
+
+	private static void fillCaseChange(float xx, float yy, float xd, float yd,
+			int nc, int nr, float[] vv1, float[] vv2, float[] vv1_last,
+			float[] vv2_last, byte[][] color_bin, int cc, int color_length,
+			float[][][] grd_normals, int[] closed, int same_side_idx,
+			byte[] side_s, byte[] last_side_s, TriangleStripBuilder triStripBldr) {
+		float[][] tri = null;
+		float[][] normals = null;
+		byte first_tri_orient = 0;
+		byte last_tri_orient = 0;
+		byte first_strp_side = SIDE_NONE;
+		byte last_strp_side = SIDE_NONE;
+		byte[] strp_sides = new byte[4];
+
+		byte[] color = new byte[color_length];
+		for (int k = 0; k < color_length; k++)
+			color[k] = color_bin[k][cc];
+
+		/*
+		 * no line segments points are on the same box side. Close off (2)
+		 * opposite corners
+		 */
+		if (same_side_idx == -1) {
+			tri = new float[2][6];
+			normals = new float[3][6];
+
+			// - use box side of first point in both contour segments.
+			byte side = side_s[0];
+			byte last_s = last_side_s[0];
+			byte[] cornersToAdd = new byte[2];
+
+			fillToLast(xx, yy, xd, yd, nc, nr, vv1, vv2, vv1_last, vv2_last,
+					color_bin, cc, color_length, grd_normals, same_side_idx,
+					side_s, last_side_s, strp_sides, tri, normals, 1, null);
+
+			if ((side == 0 && last_s == 1) || (side == 3 && last_s == 2)
+					|| (side == 2 && last_s == 3) || (side == 1 && last_s == 0)) {
+				if (strp_sides[0] == 0 && strp_sides[1] == 1) {
+					cornersToAdd[0] = 1;
+					cornersToAdd[1] = 2;
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+
+				if (strp_sides[0] == 1 && strp_sides[1] == 0) {
+					cornersToAdd[0] = 1;
+					cornersToAdd[1] = 2;
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				}
+
+				if (strp_sides[0] == 3 && strp_sides[1] == 2) {
+					cornersToAdd[0] = 2;
+					cornersToAdd[1] = 1;
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				}
+
+				if (strp_sides[0] == 2 && strp_sides[1] == 3) {
+					cornersToAdd[0] = 2;
+					cornersToAdd[1] = 1;
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+			}
+
+			if ((side == 0 && last_s == 3) || (side == 1 && last_s == 2)
+					|| (side == 3 && last_s == 0) || (side == 2 && last_s == 1)) {
+				if (strp_sides[0] == 0 && strp_sides[1] == 3) {
+					cornersToAdd[0] = 0;
+					cornersToAdd[1] = 3;
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				}
+
+				if (strp_sides[0] == 3 && strp_sides[1] == 0) {
+					cornersToAdd[0] = 0;
+					cornersToAdd[1] = 3;
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+
+				if (strp_sides[0] == 1 && strp_sides[1] == 2) {
+					cornersToAdd[0] = 3;
+					cornersToAdd[1] = 0;
+					first_tri_orient = -1;
+					last_tri_orient = 1;
+				}
+
+				if (strp_sides[0] == 2 && strp_sides[1] == 1) {
+					cornersToAdd[0] = 3;
+					cornersToAdd[1] = 0;
+					first_tri_orient = 1;
+					last_tri_orient = -1;
+				}
+			}
+
+			first_strp_side = strp_sides[0];
+			last_strp_side = strp_sides[3];
+			addCorner(xx, yy, xd, yd, nc, nr, cornersToAdd[0], grd_normals,
+					closed, 0, tri, normals);
+			addCorner(xx, yy, xd, yd, nc, nr, cornersToAdd[1], grd_normals,
+					closed, 5, tri, normals);
+
+			triStripBldr.addVerticies(cc, tri, normals, color, first_strp_side,
+					first_tri_orient, last_strp_side, last_tri_orient);
+		} else { // - 2 line segment end points have the same box side
+			byte side, last_s;
+			int indx = (same_side_idx == 0) ? 1 : 0;
+			side = side_s[indx];
+			last_s = last_side_s[indx];
+
+			byte kase = 0;
+
+			if ((side == 0 && last_s == 3) || (side == 3 && last_s == 0))
+				kase = 1;
+			if ((side == 0 && last_s == 1) || (side == 1 && last_s == 0))
+				kase = 2;
+			if ((side == 2 && last_s == 3) || (side == 3 && last_s == 2))
+				kase = 4;
+			if ((side == 2 && last_s == 1) || (side == 1 && last_s == 2))
+				kase = 7;
+			if ((side == 1 && last_s == 3) || (side == 3 && last_s == 1))
+				kase = 3;
+			if ((side == 2 && last_s == 0) || (side == 0 && last_s == 2))
+				kase = 5;
+
+			if (kase == 1 || kase == 2 || kase == 4 || kase == 7) { // close off
+																	// (1)
+																	// corner
+				byte cornerId = 127;
+				tri = new float[2][5];
+				normals = new float[3][5];
+
+				fillToLast(xx, yy, xd, yd, nc, nr, vv1, vv2, vv1_last,
+						vv2_last, color_bin, cc, color_length, grd_normals,
+						same_side_idx, side_s, last_side_s, strp_sides, tri,
+						normals, 0, null);
+
+				if (kase == 1) {
+					cornerId = 0;
+					last_strp_side = strp_sides[3];
+					first_strp_side = strp_sides[0];
+					if (last_strp_side == 3) {
+						last_tri_orient = 1;
+						first_tri_orient = 1;
+					} else if (last_strp_side == 0) {
+						last_tri_orient = -1;
+						first_tri_orient = -1;
+					}
+				}
+				if (kase == 2) {
+					cornerId = 1;
+					last_strp_side = strp_sides[3];
+					first_strp_side = strp_sides[0];
+					if (last_strp_side == 1) {
+						last_tri_orient = -1;
+						first_tri_orient = -1;
+					} else if (last_strp_side == 0) {
+						last_tri_orient = 1;
+						first_tri_orient = 1;
+					}
+				}
+				if (kase == 4) {
+					cornerId = 2;
+					last_strp_side = strp_sides[3];
+					first_strp_side = strp_sides[0];
+					if (last_strp_side == 2) {
+						last_tri_orient = 1;
+						first_tri_orient = 1;
+					} else if (last_strp_side == 3) {
+						last_tri_orient = -1;
+						first_tri_orient = -1;
+					}
+				}
+				if (kase == 7) {
+					cornerId = 3;
+					last_strp_side = strp_sides[3];
+					first_strp_side = strp_sides[0];
+					if (last_strp_side == 2) {
+						last_tri_orient = -1;
+						first_tri_orient = -1;
+					} else if (last_strp_side == 1) {
+						last_tri_orient = 1;
+						first_tri_orient = 1;
+					}
+				}
+				addCorner(xx, yy, xd, yd, nc, nr, cornerId, grd_normals,
+						closed, 4, tri, normals);
+			} else if (kase == 5) { // close off (2) adjacent corners
+				byte[] oppCorners = new byte[] { 127, 127 };
+				byte same_side = side_s[same_side_idx];
+				tri = new float[2][6];
+				normals = new float[3][6];
+				fillToLast(xx, yy, xd, yd, nc, nr, vv1, vv2, vv1_last,
+						vv2_last, color_bin, cc, color_length, grd_normals,
+						same_side_idx, side_s, last_side_s, strp_sides, tri,
+						normals, 0, null);
+
+				if (same_side == 1) {
+					if (strp_sides[3] == 2) {
+						oppCorners = new byte[] { 0, 2 };
+						first_tri_orient = 1;
+						last_tri_orient = -1;
+						first_strp_side = 1;
+						last_strp_side = 3;
+					} else if (strp_sides[3] == 0) {
+						oppCorners = new byte[] { 2, 0 };
+						first_tri_orient = -1;
+						last_tri_orient = 1;
+						first_strp_side = 1;
+						last_strp_side = 3;
+					}
+				}
+				if (same_side == 3) {
+					if (strp_sides[3] == 2) {
+						oppCorners = new byte[] { 1, 3 };
+						first_tri_orient = -1;
+						last_tri_orient = 1;
+						first_strp_side = 3;
+						last_strp_side = 1;
+					} else if (strp_sides[3] == 0) {
+						oppCorners = new byte[] { 3, 1 };
+						first_tri_orient = 1;
+						last_tri_orient = -1;
+						first_strp_side = 3;
+						last_strp_side = 1;
+					}
+				}
+				addCorner(xx, yy, xd, yd, nc, nr, oppCorners[0], grd_normals,
+						closed, 4, tri, normals);
+				addCorner(xx, yy, xd, yd, nc, nr, oppCorners[1], grd_normals,
+						closed, 5, tri, normals);
+			} else if (kase == 3) { // - close off (2) adjacent corners
+				byte[] oppCorners = new byte[] { 127, 127 };
+				byte same_side = side_s[same_side_idx];
+				tri = new float[2][6];
+				normals = new float[3][6];
+				fillToLast(xx, yy, xd, yd, nc, nr, vv1, vv2, vv1_last,
+						vv2_last, color_bin, cc, color_length, grd_normals,
+						same_side_idx, side_s, last_side_s, strp_sides, tri,
+						normals, 0, null);
+
+				if (same_side == 0) {
+					if (strp_sides[3] == 3) {
+						oppCorners = new byte[] { 3, 2 };
+						first_tri_orient = -1;
+						last_tri_orient = 1;
+						first_strp_side = 0;
+						last_strp_side = 2;
+					} else if (strp_sides[3] == 1) {
+						oppCorners = new byte[] { 2, 3 };
+						first_tri_orient = 1;
+						last_tri_orient = -1;
+						first_strp_side = 0;
+						last_strp_side = 2;
+					}
+				}
+				if (same_side == 2) {
+					if (strp_sides[3] == 3) {
+						oppCorners = new byte[] { 1, 0 };
+						first_tri_orient = 1;
+						last_tri_orient = -1;
+						first_strp_side = 2;
+						last_strp_side = 0;
+					} else if (strp_sides[3] == 1) {
+						oppCorners = new byte[] { 0, 1 };
+						first_tri_orient = -1;
+						last_tri_orient = 1;
+						first_strp_side = 2;
+						last_strp_side = 0;
+					}
+				}
+				addCorner(xx, yy, xd, yd, nc, nr, oppCorners[0], grd_normals,
+						closed, 4, tri, normals);
+				addCorner(xx, yy, xd, yd, nc, nr, oppCorners[1], grd_normals,
+						closed, 5, tri, normals);
+			}
+
+			triStripBldr.addVerticies(cc, tri, normals, color, first_strp_side,
+					first_tri_orient, last_strp_side, last_tri_orient);
+		}
+	}
+
+	/**
+	 *  
+	 * @param xx
+	 * @param yy
+	 * @param xd
+	 * @param yd
+	 * @param v_idx
+	 * @param dir
+	 * @param o_flags
+	 * @param ctrLow
+	 * @param vx
+	 * @param vy
+	 * @param nc
+	 * @param nr
+	 * @param crnr_color
+	 * @param crnrLevelIdx
+	 * @param crnr_out
+	 * @param color_bin
+	 * @param color_length
+	 * @param grd_normals
+	 * @param closed
+	 * @param triStripBldr
+	 */
+	
+	private static void fillCaseSix(float xx, float yy, float xd, float yd,
+			int v_idx, int dir, byte[] o_flags, short ctrLow, float[] vx,
+			float[] vy, int nc, int nr, byte[][] crnr_color,
+			int[] crnrLevelIdx, boolean[] crnr_out, byte[][] color_bin,
+			int color_length, float[][][] grd_normals, int[] closed,
+			TriangleStripBuilder triStripBldr) {
+
+		float[][] tri = null;
+
+		int n1 = 0; // - number of case1 line segments
+		int n2 = 0; // - case2
+		int n4 = 0; // - case4
+		int n7 = 0; // - case7
+
+		for (int kk = 0; kk < o_flags.length; kk++) {
+			if ((o_flags[kk] - 32) == 1 || o_flags[kk] == 1)
+				n1++;
+			if ((o_flags[kk] - 32) == 2 || o_flags[kk] == 2)
+				n2++;
+			if ((o_flags[kk] - 32) == 4 || o_flags[kk] == 4)
+				n4++;
+			if ((o_flags[kk] - 32) == 7 || o_flags[kk] == 7)
+				n7++;
+		}
+		// - all case coords in separate arrays
+		float[][] vv1 = new float[2][n1 * 2]; // - all case1 coords in one array
+		float[][] vv2 = new float[2][n2 * 2]; // 2
+		float[][] vv4 = new float[2][n4 * 2]; // 4
+		float[][] vv7 = new float[2][n7 * 2]; // 7
+		int[] clr_idx1 = new int[n1];
+		int[] clr_idx2 = new int[n2];
+		int[] clr_idx4 = new int[n4];
+		int[] clr_idx7 = new int[n7];
+		float[] vvv1 = new float[2];
+		float[] vvv2 = new float[2];
+		float[] vvv1_last = new float[2];
+		float[] vvv2_last = new float[2];
+
+		n1 = 0;
+		n2 = 0;
+		n4 = 0;
+		n7 = 0;
+		int ii = v_idx;
+		int cc = ctrLow - 1;
+		int cnt = 0;
+		int[] cases = { 1, 2, 7, 4 }; // - corner cases, counter-clockwise
+										// around box
+
+		for (int kk = 0; kk < o_flags.length; kk++) {
+			if (o_flags[kk] > 32)
+				cnt++;
+
+			if ((o_flags[kk] - 32) == 1 || o_flags[kk] == 1) {
+				clr_idx1[n1] = cc;
+				vv1[0][2 * n1] = vx[ii];
+				vv1[1][2 * n1] = vy[ii];
+				vv1[0][2 * n1 + 1] = vx[ii + 1];
+				vv1[1][2 * n1 + 1] = vy[ii + 1];
+				n1++;
+			} else if ((o_flags[kk] - 32) == 2 || o_flags[kk] == 2) {
+				clr_idx2[n2] = cc;
+				vv2[0][2 * n2] = vx[ii];
+				vv2[1][2 * n2] = vy[ii];
+				vv2[0][2 * n2 + 1] = vx[ii + 1];
+				vv2[1][2 * n2 + 1] = vy[ii + 1];
+				n2++;
+			} else if ((o_flags[kk] - 32) == 4 || o_flags[kk] == 4) {
+				clr_idx4[n4] = cc;
+				vv4[0][2 * n4] = vx[ii];
+				vv4[1][2 * n4] = vy[ii];
+				vv4[0][2 * n4 + 1] = vx[ii + 1];
+				vv4[1][2 * n4 + 1] = vy[ii + 1];
+				n4++;
+			} else if ((o_flags[kk] - 32) == 7 || o_flags[kk] == 7) {
+				clr_idx7[n7] = cc;
+				vv7[0][2 * n7] = vx[ii];
+				vv7[1][2 * n7] = vy[ii];
+				vv7[0][2 * n7 + 1] = vx[ii + 1];
+				vv7[1][2 * n7 + 1] = vy[ii + 1];
+				n7++;
+			}
+			if (o_flags[kk] < 32) {
+				cc += 1;
+			} else if (cnt == 2) {
+				cnt = 0;
+				cc++;
+			}
+			ii += 2;
+		}
+
+		int[] clr_idx = null;
+		float[] vvx = null;
+		float[] vvy = null;
+		float[] x_avg = new float[2];
+		float[] y_avg = new float[2];
+		float dist_0 = 0;
+		float dist_1 = 0;
+		float xxx = 0;
+		float yyy = 0;
+		float dx = 0;
+		float dy = 0;
+		int nn = 0;
+		int pt = 0;
+		int n_pt = 0;
+		int s_idx = 0;
+		int ns_idx = 0;
+		byte[] tmp = null;
+		byte[] cntr_color = null;
+		int cntr_clr = Integer.MIN_VALUE;
+
+		float[][] edge_points = new float[2][8];
+		boolean[] edge_point_a_corner = { false, false, false, false, false,
+				false, false, false };
+		boolean[] edge_point_out = { false, false, false, false, false, false,
+				false, false };
+		boolean this_crnr_out = false;
+		int n_crnr_out = 0;
+
+		int[] which_corner = new int[2]; // - use case values
+		int num_corners = 0;
+
+		/*
+		 * Letters are the grid corners consistent with previous definition.
+		 * Numbers are indexes into the edge_points array. Note, if there is no
+		 * corner line segement, the adjacent indexes both refer to the corner:
+		 * 4,3 -> D, 6,5 -> C, 0,7 -> A, 1,2 -> B, otherwise they are the
+		 * contour line segment intersection points.
+		 * 
+		 * C------(5)-------(4)------D | | | | | | (6) (3) | | | | | | (7) (2) |
+		 * | | | | | A------(0)-------(1)------B
+		 */
+
+		/*
+		 * Iterate through cases, they're all corners, and create the fill
+		 * geometry. When finished the final edge points are used below to fill
+		 * the remainder of the grid box.
+		 */
+		for (int kk = 0; kk < cases.length; kk++) {
+			switch (cases[kk]) {
+			case 1:
+				nn = n1;
+				clr_idx = clr_idx1;
+				vvx = vv1[0];
+				vvy = vv1[1];
+				xxx = xx;
+				yyy = yy;
+				pt = 0;
+				n_pt = 7;
+				s_idx = 0;
+				ns_idx = 1;
+				tmp = crnr_color[0];
+				this_crnr_out = crnr_out[0];
+				break;
+			case 2:
+				nn = n2;
+				clr_idx = clr_idx2;
+				vvx = vv2[0];
+				vvy = vv2[1];
+				xxx = xx + xd;
+				yyy = yy;
+				pt = 1;
+				n_pt = 2;
+				s_idx = 0;
+				ns_idx = 1;
+				tmp = crnr_color[1];
+				this_crnr_out = crnr_out[1];
+				break;
+			case 4:
+				nn = n4;
+				clr_idx = clr_idx4;
+				vvx = vv4[0];
+				vvy = vv4[1];
+				xxx = xx;
+				yyy = yy + yd;
+				pt = 5;
+				n_pt = 6;
+				s_idx = 1;
+				ns_idx = 0;
+				tmp = crnr_color[2];
+				this_crnr_out = crnr_out[2];
+				break;
+			case 7:
+				nn = n7;
+				clr_idx = clr_idx7;
+				vvx = vv7[0];
+				vvy = vv7[1];
+				xxx = xx + xd;
+				yyy = yy + yd;
+				pt = 3;
+				n_pt = 4;
+				s_idx = 0;
+				ns_idx = 1;
+				tmp = crnr_color[3];
+				this_crnr_out = crnr_out[3];
+				break;
+			}
+
+			if (nn == 0) {
+				edge_points[0][pt] = xxx;
+				edge_points[1][pt] = yyy;
+				edge_points[0][n_pt] = xxx;
+				edge_points[1][n_pt] = yyy;
+				cntr_color = tmp;
+				edge_point_a_corner[pt] = true;
+				edge_point_a_corner[n_pt] = true;
+				edge_point_out[pt] = this_crnr_out;
+				edge_point_out[n_pt] = this_crnr_out;
+				which_corner[num_corners] = cases[kk];
+				num_corners++;
+				if (this_crnr_out)
+					n_crnr_out++;
+			} else if (nn == 1) {
+				fillToNearCorner(xx, yy, xd, yd, 0, (byte) cases[kk], dir, vvx,
+						vvy, nc, nr, crnr_color, crnrLevelIdx, crnr_out,
+						grd_normals, closed, triStripBldr);
+				edge_points[0][pt] = vvx[s_idx];
+				edge_points[1][pt] = vvy[s_idx];
+				edge_points[0][n_pt] = vvx[ns_idx];
+				edge_points[1][n_pt] = vvy[ns_idx];
+				if (clr_idx[0] > cntr_clr)
+					cntr_clr = clr_idx[0];
+			} else {
+				int il = 0;
+				int idx = 0;
+				x_avg[0] = (vvx[idx] + vvx[idx + 1]) / 2;
+				y_avg[0] = (vvy[idx] + vvy[idx + 1]) / 2;
+				idx = idx + 2;
+				x_avg[1] = (vvx[idx] + vvx[idx + 1]) / 2;
+				y_avg[1] = (vvy[idx] + vvy[idx + 1]) / 2;
+
+				dy = (y_avg[1] - (yyy));
+				dx = (x_avg[1] - (xxx));
+				dist_1 = dy * dy + dx * dx;
+				dy = (y_avg[0] - (yyy));
+				dx = (x_avg[0] - (xxx));
+				dist_0 = dy * dy + dx * dx;
+
+				boolean cornerFirst = false;
+				if (dist_1 > dist_0)
+					cornerFirst = true;
+
+				if (cornerFirst) {
+					fillToNearCorner(xx, yy, xd, yd, 0, (byte) cases[kk], dir,
+							vvx, vvy, nc, nr, crnr_color, crnrLevelIdx,
+							crnr_out, grd_normals, closed, triStripBldr);
+				} else {
+					edge_points[0][pt] = vvx[s_idx];
+					edge_points[1][pt] = vvy[s_idx];
+					edge_points[0][n_pt] = vvx[ns_idx];
+					edge_points[1][n_pt] = vvy[ns_idx];
+					if (clr_idx[0] > cntr_clr)
+						cntr_clr = clr_idx[0];
+				}
+				for (il = 1; il < nn; il++) {
+					idx = dir * il * 2;
+					int idx_last = idx - 2 * dir;
+
+					vvv1[0] = vvx[idx];
+					vvv1[1] = vvy[idx];
+					vvv2[0] = vvx[idx + dir];
+					vvv2[1] = vvy[idx + dir];
+					vvv1_last[0] = vvx[idx_last];
+					vvv1_last[1] = vvy[idx_last];
+					vvv2_last[0] = vvx[idx_last + dir];
+					vvv2_last[1] = vvy[idx_last + dir];
+
+					fillToLast(xx, yy, xd, yd, nc, nr, vvv1, vvv2, vvv1_last,
+							vvv2_last, (byte) cases[kk], color_bin,
+							clr_idx[il], color_length, grd_normals, -1,
+							triStripBldr);
+
+					if (!cornerFirst && il == (nn - 1)) {
+						fillToNearCorner(xx, yy, xd, yd, idx, (byte) cases[kk],
+								dir, vvx, vvy, nc, nr, crnr_color,
+								crnrLevelIdx, crnr_out, grd_normals, closed,
+								triStripBldr);
+					}
+					if (cornerFirst && il == (nn - 1)) {
+						edge_points[0][pt] = vvx[idx + s_idx];
+						edge_points[1][pt] = vvy[idx + s_idx];
+						edge_points[0][n_pt] = vvx[idx + ns_idx];
+						edge_points[1][n_pt] = vvy[idx + ns_idx];
+						if (clr_idx[il] > cntr_clr)
+							cntr_clr = clr_idx[il];
+					}
+				}
+			}
+		}
+
+		/*
+		 * Now use edge_points to create the triangle strip to fill the
+		 * remainder of the grid box, ie. the center region.
+		 */
+
+		if (n_crnr_out == 2) { // - don't fill center region
+			return;
+		}
+
+		if (cntr_color == null) { // - All corners were closed off
+			cntr_color = new byte[color_length];
+			for (int c = 0; c < color_length; c++) {
+				cntr_color[c] = color_bin[c][cntr_clr];
+			}
+		}
+
+		byte first_tri_orient = 0;
+		byte last_tri_orient = 0;
+		byte first_strp_side = SIDE_NONE;
+		byte last_strp_side = SIDE_NONE;
+
+		float[][] normals = null;
+		if (num_corners == 0) {
+			tri = new float[2][8];
+			normals = new float[3][8];
+			tri[0][0] = edge_points[0][7];
+			tri[1][0] = edge_points[1][7];
+			tri[0][1] = edge_points[0][6];
+			tri[1][1] = edge_points[1][6];
+			tri[0][2] = edge_points[0][0];
+			tri[1][2] = edge_points[1][0];
+			tri[0][3] = edge_points[0][5];
+			tri[1][3] = edge_points[1][5];
+			tri[0][4] = edge_points[0][1];
+			tri[1][4] = edge_points[1][1];
+			tri[0][5] = edge_points[0][4];
+			tri[1][5] = edge_points[1][4];
+			tri[0][6] = edge_points[0][2];
+			tri[1][6] = edge_points[1][2];
+			tri[0][7] = edge_points[0][3];
+			tri[1][7] = edge_points[1][3];
+
+			first_strp_side = 3;
+			last_strp_side = 1;
+			first_tri_orient = -1;
+			last_tri_orient = 1;
+
+			interpNormals(tri[0], tri[1], xx, yy, nc, nr, xd, yd, grd_normals,
+					normals);
+			triStripBldr.addVerticies(cntr_clr, tri, normals, cntr_color,
+					first_strp_side, first_tri_orient, last_strp_side,
+					last_tri_orient);
+		} else if (num_corners == 1) {
+			tri = new float[2][7];
+			normals = new float[3][7];
+			if (which_corner[0] == 1) {
+				tri[0][0] = edge_points[0][6];
+				tri[1][0] = edge_points[1][6];
+				tri[0][1] = edge_points[0][0];
+				tri[1][1] = edge_points[1][0];
+				tri[0][2] = edge_points[0][5];
+				tri[1][2] = edge_points[1][5];
+				tri[0][3] = edge_points[0][1];
+				tri[1][3] = edge_points[1][1];
+				tri[0][4] = edge_points[0][4];
+				tri[1][4] = edge_points[1][4];
+				tri[0][5] = edge_points[0][2];
+				tri[1][5] = edge_points[1][2];
+				tri[0][6] = edge_points[0][3];
+				tri[1][6] = edge_points[1][3];
+				first_strp_side = 3;
+				last_strp_side = 1;
+				first_tri_orient = 1;
+				last_tri_orient = 1;
+			} else if (which_corner[0] == 2) {
+				tri[0][0] = edge_points[0][6];
+				tri[1][0] = edge_points[1][6];
+				tri[0][1] = edge_points[0][7];
+				tri[1][1] = edge_points[1][7];
+				tri[0][2] = edge_points[0][5];
+				tri[1][2] = edge_points[1][5];
+				tri[0][3] = edge_points[0][0];
+				tri[1][3] = edge_points[1][0];
+				tri[0][4] = edge_points[0][4];
+				tri[1][4] = edge_points[1][4];
+				tri[0][5] = edge_points[0][1];
+				tri[1][5] = edge_points[1][1];
+				tri[0][6] = edge_points[0][3];
+				tri[1][6] = edge_points[1][3];
+				first_strp_side = 3;
+				last_strp_side = 1;
+				first_tri_orient = 1;
+				last_tri_orient = 1;
+			} else if (which_corner[0] == 7) {
+				tri[0][0] = edge_points[0][7];
+				tri[1][0] = edge_points[1][7];
+				tri[0][1] = edge_points[0][6];
+				tri[1][1] = edge_points[1][6];
+				tri[0][2] = edge_points[0][0];
+				tri[1][2] = edge_points[1][0];
+				tri[0][3] = edge_points[0][5];
+				tri[1][3] = edge_points[1][5];
+				tri[0][4] = edge_points[0][1];
+				tri[1][4] = edge_points[1][1];
+				tri[0][5] = edge_points[0][4];
+				tri[1][5] = edge_points[1][4];
+				tri[0][6] = edge_points[0][2];
+				tri[1][6] = edge_points[1][2];
+				first_strp_side = 3;
+				last_strp_side = 1;
+				first_tri_orient = -1;
+				last_tri_orient = -1;
+			} else if (which_corner[0] == 4) {
+				tri[0][0] = edge_points[0][7];
+				tri[1][0] = edge_points[1][7];
+				tri[0][1] = edge_points[0][6];
+				tri[1][1] = edge_points[1][6];
+				tri[0][2] = edge_points[0][0];
+				tri[1][2] = edge_points[1][0];
+				tri[0][3] = edge_points[0][4];
+				tri[1][3] = edge_points[1][4];
+				tri[0][4] = edge_points[0][1];
+				tri[1][4] = edge_points[1][1];
+				tri[0][5] = edge_points[0][3];
+				tri[1][5] = edge_points[1][3];
+				tri[0][6] = edge_points[0][2];
+				tri[1][6] = edge_points[1][2];
+				first_strp_side = 3;
+				last_strp_side = 1;
+				first_tri_orient = -1;
+				last_tri_orient = -1;
+			}
+
+			interpNormals(tri[0], tri[1], xx, yy, nc, nr, xd, yd, grd_normals,
+					normals);
+			triStripBldr.addVerticies(cntr_clr, tri, normals, cntr_color,
+					first_strp_side, first_tri_orient, last_strp_side,
+					last_tri_orient);
+		} else if (num_corners == 2) {
+			tri = new float[2][6];
+			normals = new float[3][6];
+			int flag = ((which_corner[0] == 1 && which_corner[1] == 7) || (which_corner[0] == 7 && which_corner[1] == 1)) ? 1
+					: 4;
+			if (flag == 4) {
+				tri[0][0] = edge_points[0][6];
+				tri[1][0] = edge_points[1][6];
+				tri[0][1] = edge_points[0][7];
+				tri[1][1] = edge_points[1][7];
+				tri[0][2] = edge_points[0][4];
+				tri[1][2] = edge_points[1][4];
+				tri[0][3] = edge_points[0][0];
+				tri[1][3] = edge_points[1][0];
+				tri[0][4] = edge_points[0][3];
+				tri[1][4] = edge_points[1][3];
+				tri[0][5] = edge_points[0][1];
+				tri[1][5] = edge_points[1][1];
+				first_strp_side = 3;
+				last_strp_side = 1;
+				first_tri_orient = 1;
+				last_tri_orient = -1;
+			} else if (flag == 1) {
+				tri[0][0] = edge_points[0][6];
+				tri[1][0] = edge_points[1][6];
+				tri[0][1] = edge_points[0][7];
+				tri[1][1] = edge_points[1][7];
+				tri[0][2] = edge_points[0][5];
+				tri[1][2] = edge_points[1][5];
+				tri[0][3] = edge_points[0][1];
+				tri[1][3] = edge_points[1][1];
+				tri[0][4] = edge_points[0][3];
+				tri[1][4] = edge_points[1][3];
+				tri[0][5] = edge_points[0][2];
+				tri[1][5] = edge_points[1][2];
+				first_strp_side = 3;
+				last_strp_side = 1;
+				first_tri_orient = 1;
+				last_tri_orient = -1;
+			}
+
+			interpNormals(tri[0], tri[1], xx, yy, nc, nr, xd, yd, grd_normals,
+					normals);
+			triStripBldr.addVerticies(cntr_clr, tri, normals, cntr_color,
+					first_strp_side, first_tri_orient, last_strp_side,
+					last_tri_orient);
+		}
+	}
+
+	/**
+	 * 
+	 * @param xx
+	 * @param yy
+	 * @param xd
+	 * @param yd
+	 * @param v_idx
+	 * @param o_flag
+	 * @param cnt
+	 * @param dir
+	 * @param vx
+	 * @param vy
+	 * @param nc
+	 * @param nr
+	 * @param crnr_color
+	 * @param crnrLevelIdx
+	 * @param crnr_out
+	 * @param grd_normals
+	 * @param closed
+	 * @param triStripBldr
+	 */
+
+	private static void fillToNearCorner(float xx, float yy, float xd,
+			float yd, int v_idx, byte o_flag, int dir, float[] vx, float[] vy,
+			int nc, int nr, byte[][] crnr_color, int[] crnrLevelIdx,
+			boolean[] crnr_out, float[][][] grd_normals, int[] closed,
+			TriangleStripBuilder triStripBldr) {
+		float cx = 0;
+		float cy = 0;
+		int cc = 0;
+
+		int color_length = crnr_color[0].length;
+
+		int vidx_0 = 0; // first segment index maps to this in outgoing strip
+						// array
+		int vidx_1 = 0; // next "                                       "
+		int crn = 0; // corner index "                             "
+		float[][] normals = new float[3][3];
+		float[][] tri = new float[2][3];
+		float[][] tmp = new float[3][1];
+		byte[] color = new byte[color_length];
+
+		byte first_tri_orient = 0;
+		byte last_tri_orient = 0;
+		byte first_strp_side = SIDE_NONE;
+		byte last_strp_side = SIDE_NONE;
+
+		switch (o_flag) {
+		case 1:
+			cc = 0;
+			closed[0] = closed[0] | 1;
+			if (crnr_out[cc])
+				return;
+			cx = xx;
+			cy = yy;
+			vidx_0 = 2;
+			vidx_1 = 1;
+			crn = 0;
+			normals[0][crn] = grd_normals[nc][nr][0];
+			normals[1][crn] = grd_normals[nc][nr][1];
+			normals[2][crn] = grd_normals[nc][nr][2];
+			first_tri_orient = -1;
+			last_tri_orient = -1;
+			first_strp_side = 3;
+			last_strp_side = 0;
+			break;
+		case 4:
+			cc = 2;
+			closed[0] = closed[0] | 4;
+			if (crnr_out[cc])
+				return;
+			cx = xx;
+			cy = yy + yd;
+			vidx_0 = 0;
+			vidx_1 = 2;
+			crn = 1;
+			normals[0][crn] = grd_normals[nc + 1][nr][0];
+			normals[1][crn] = grd_normals[nc + 1][nr][1];
+			normals[2][crn] = grd_normals[nc + 1][nr][2];
+			first_tri_orient = -1;
+			last_tri_orient = -1;
+			first_strp_side = 3;
+			last_strp_side = 2;
+			break;
+		case 2:
+			cc = 1;
+			closed[0] = closed[0] | 2;
+			if (crnr_out[cc])
+				return;
+			cx = xx + xd;
+			cy = yy;
+			vidx_0 = 0;
+			vidx_1 = 2;
+			crn = 1;
+			normals[0][crn] = grd_normals[nc][nr + 1][0];
+			normals[1][crn] = grd_normals[nc][nr + 1][1];
+			normals[2][crn] = grd_normals[nc][nr + 1][2];
+			first_tri_orient = 1;
+			last_tri_orient = 1;
+			first_strp_side = 0;
+			last_strp_side = 1;
+			break;
+		case 7:
+			cc = 3;
+			closed[0] = closed[0] | 8;
+			if (crnr_out[cc])
+				return;
+			cx = xx + xd;
+			cy = yy + yd;
+			vidx_0 = 2;
+			vidx_1 = 0;
+			crn = 1;
+			normals[0][crn] = grd_normals[nc + 1][nr + 1][0];
+			normals[1][crn] = grd_normals[nc + 1][nr + 1][1];
+			normals[2][crn] = grd_normals[nc + 1][nr + 1][2];
+			first_tri_orient = -1;
+			last_tri_orient = -1;
+			first_strp_side = 2;
+			last_strp_side = 1;
+			break;
+		}
+
+		for (int ii = 0; ii < color_length; ii++) {
+			color[ii] = crnr_color[cc][ii];
+		}
+		int levIdx = crnrLevelIdx[cc];
+
+		tri[0][crn] = cx;
+		tri[1][crn] = cy;
+
+		tri[0][vidx_0] = vx[v_idx];
+		tri[1][vidx_0] = vy[v_idx];
+		interpNormals(tri[0][vidx_0], tri[1][vidx_0], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][vidx_0] = tmp[0][0];
+		normals[1][vidx_0] = tmp[1][0];
+		normals[2][vidx_0] = tmp[2][0];
+
+		tri[0][vidx_1] = vx[v_idx + dir];
+		tri[1][vidx_1] = vy[v_idx + dir];
+		interpNormals(tri[0][vidx_1], tri[1][vidx_1], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][vidx_1] = tmp[0][0];
+		normals[1][vidx_1] = tmp[1][0];
+		normals[2][vidx_1] = tmp[2][0];
+
+		triStripBldr.addVerticies(levIdx, tri, normals, color, first_strp_side,
+				first_tri_orient, last_strp_side, last_tri_orient);
+	}
+
+	/**
+	 * 
+	 * @param xx
+	 * @param yy
+	 * @param xd
+	 * @param yd
+	 * @param v_idx
+	 * @param o_flag
+	 * @param dir
+	 * @param vx
+	 * @param vy
+	 * @param nc
+	 * @param nr
+	 * @param crnr_color
+	 * @param crnrLevelIdx
+	 * @param crnr_out
+	 * @param grd_normals
+	 * @param closed
+	 * @param triStripBldr
+	 */
+	
+	private static void fillToOppCorner(float xx, float yy, float xd, float yd,
+			int v_idx, byte o_flag, int dir, float[] vx, float[] vy, int nc,
+			int nr, byte[][] crnr_color, int[] crnrLevelIdx,
+			boolean[] crnr_out, float[][][] grd_normals, int[] closed,
+			TriangleStripBuilder triStripBldr) {
+
+		float cx1 = 0;
+		float cx2 = 0;
+		float cx3 = 0;
+		float cy1 = 0;
+		float cy2 = 0;
+		float cy3 = 0;
+		int cc = 0;
+		int[][] grd = new int[3][2];
+		int color_length = crnr_color[0].length;
+
+		float[][] tri = new float[2][5];
+		float[][] normals = new float[3][5];
+		float[][] tmp = new float[3][1];
+		byte[] color = new byte[color_length];
+
+		int vidx_0 = 0; // first segment index maps to this in outgoing strip
+						// array
+		int vidx_1 = 0; // next "                                       "
+		int crn_1 = 0; // corner's index into outgoing strip array
+		int crn_2 = 0; // "                "
+		int crn_3 = 0; // "              "
+
+		byte first_tri_orient = 0;
+		byte last_tri_orient = 0;
+		byte first_strp_side = SIDE_NONE;
+		byte last_strp_side = SIDE_NONE;
+
+		switch (o_flag) {
+		case 1:
+			closed[0] = closed[0] | 14;
+			if (crnr_out[1] || crnr_out[2] || crnr_out[3])
+				return;
+			cx1 = xx + xd;
+			cy1 = yy;
+			cx2 = xx + xd;
+			cy2 = yy + yd;
+			cx3 = xx;
+			cy3 = yy + yd;
+			cc = 3;
+			grd[0][0] = 1;
+			grd[0][1] = 0;
+			grd[1][0] = 1;
+			grd[1][1] = 1;
+			grd[2][0] = 0;
+			grd[2][1] = 1;
+			vidx_0 = 2;
+			vidx_1 = 0;
+			crn_1 = 4;
+			crn_2 = 3;
+			crn_3 = 1;
+			first_tri_orient = -1;
+			last_tri_orient = -1;
+			first_strp_side = 3;
+			last_strp_side = 1;
+			break;
+		case 2:
+			closed[0] = closed[0] | 13;
+			if (crnr_out[0] || crnr_out[2] || crnr_out[3])
+				return;
+			cx1 = xx;
+			cy1 = yy;
+			cx2 = xx;
+			cy2 = yy + yd;
+			cx3 = xx + xd;
+			cy3 = yy + yd;
+			cc = 2;
+			grd[0][0] = 0;
+			grd[0][1] = 0;
+			grd[1][0] = 0;
+			grd[1][1] = 1;
+			grd[2][0] = 1;
+			grd[2][1] = 1;
+			vidx_0 = 2;
+			vidx_1 = 4;
+			crn_1 = 0;
+			crn_2 = 1;
+			crn_3 = 3;
+			first_tri_orient = -1;
+			last_tri_orient = -1;
+			first_strp_side = 3;
+			last_strp_side = 1;
+			break;
+		case 4:
+			closed[0] = closed[0] | 11;
+			if (crnr_out[0] || crnr_out[1] || crnr_out[3])
+				return;
+			cx1 = xx;
+			cy1 = yy;
+			cx2 = xx + xd;
+			cy2 = yy;
+			cx3 = xx + xd;
+			cy3 = yy + yd;
+			cc = 1;
+			grd[0][0] = 0;
+			grd[0][1] = 0;
+			grd[1][0] = 1;
+			grd[1][1] = 0;
+			grd[2][0] = 1;
+			grd[2][1] = 1;
+			vidx_0 = 1;
+			vidx_1 = 3;
+			crn_1 = 0;
+			crn_2 = 2;
+			crn_3 = 4;
+			first_tri_orient = -1;
+			last_tri_orient = -1;
+			first_strp_side = 3;
+			last_strp_side = 2;
+			break;
+		case 7:
+			closed[0] = closed[0] | 7;
+			if (crnr_out[0] || crnr_out[1] || crnr_out[2])
+				return;
+			cx1 = xx + xd;
+			cy1 = yy;
+			cx2 = xx;
+			cy2 = yy;
+			cx3 = xx;
+			cy3 = yy + yd;
+			cc = 0;
+			grd[0][0] = 1;
+			grd[0][1] = 0;
+			grd[1][0] = 0;
+			grd[1][1] = 0;
+			grd[2][0] = 0;
+			grd[2][1] = 1;
+			vidx_0 = 4;
+			vidx_1 = 3;
+			crn_1 = 2;
+			crn_2 = 0;
+			crn_3 = 1;
+			first_tri_orient = -1;
+			last_tri_orient = -1;
+			first_strp_side = 3;
+			last_strp_side = 1;
+			break;
+		}
+
+		for (int ii = 0; ii < color_length; ii++) {
+			color[ii] = crnr_color[cc][ii];
+		}
+		int levIdx = crnrLevelIdx[cc];
+
+		tri[0][crn_1] = cx1;
+		tri[1][crn_1] = cy1;
+		normals[0][crn_1] = grd_normals[nc + grd[0][1]][nr + grd[0][0]][0];
+		normals[1][crn_1] = grd_normals[nc + grd[0][1]][nr + grd[0][0]][1];
+		normals[2][crn_1] = grd_normals[nc + grd[0][1]][nr + grd[0][0]][2];
+
+		tri[0][crn_2] = cx2;
+		tri[1][crn_2] = cy2;
+		normals[0][crn_2] = grd_normals[nc + grd[1][1]][nr + grd[1][0]][0];
+		normals[1][crn_2] = grd_normals[nc + grd[1][1]][nr + grd[1][0]][1];
+		normals[2][crn_2] = grd_normals[nc + grd[1][1]][nr + grd[1][0]][2];
+
+		tri[0][crn_3] = cx3;
+		tri[1][crn_3] = cy3;
+		normals[0][crn_3] = grd_normals[nc + grd[2][1]][nr + grd[2][0]][0];
+		normals[1][crn_3] = grd_normals[nc + grd[2][1]][nr + grd[2][0]][1];
+		normals[2][crn_3] = grd_normals[nc + grd[2][1]][nr + grd[2][0]][2];
+
+		tri[0][vidx_0] = vx[v_idx];
+		tri[1][vidx_0] = vy[v_idx];
+		interpNormals(tri[0][vidx_0], tri[1][vidx_0], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][vidx_0] = tmp[0][0];
+		normals[1][vidx_0] = tmp[1][0];
+		normals[2][vidx_0] = tmp[2][0];
+
+		tri[0][vidx_1] = vx[v_idx + dir];
+		tri[1][vidx_1] = vy[v_idx + dir];
+		interpNormals(tri[0][vidx_1], tri[1][vidx_1], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][vidx_1] = tmp[0][0];
+		normals[1][vidx_1] = tmp[1][0];
+		normals[2][vidx_1] = tmp[2][0];
+
+		triStripBldr.addVerticies(levIdx, tri, normals, color, first_strp_side,
+				first_tri_orient, last_strp_side, last_tri_orient);
+	}
+
+	/**
+	 * 
+	 * @param xx
+	 * @param yy
+	 * @param xd
+	 * @param yd
+	 * @param v_idx
+	 * @param o_flag
+	 * @param flag
+	 * @param cnt
+	 * @param dir
+	 * @param vx
+	 * @param vy
+	 * @param nc
+	 * @param nr
+	 * @param crnr_color
+	 * @param crnrLevelIdx
+	 * @param crnr_out
+	 * @param grd_normals
+	 * @param closed
+	 * @param triStripBldr
+	 */
+
+	private static void fillToSide(float xx, float yy, float xd, float yd,
+			int v_idx, byte o_flag, int flag, int dir, float[] vx, float[] vy,
+			int nc, int nr, byte[][] crnr_color, int[] crnrLevelIdx,
+			boolean[] crnr_out, float[][][] grd_normals, int[] closed,
+			TriangleStripBuilder triStripBldr) {
+
+		float[][] tri = new float[2][4];
+		float[][] normals = new float[3][4];
+
+		fillToSide(xx, yy, xd, yd, v_idx, o_flag, flag, dir, vx, vy, nc, nr,
+				crnr_color, crnrLevelIdx, crnr_out, grd_normals, closed, tri,
+				normals, triStripBldr);
+
+	}
+
+	private static void fillToSide(float xx, float yy, float xd, float yd,
+			int v_idx, byte o_flag, int flag, int dir, float[] vx, float[] vy,
+			int nc, int nr, byte[][] crnr_color, int[] crnrLevelIdx,
+			boolean[] crnr_out, float[][][] grd_normals, int[] closed,
+			float[][] strpverts, float[][] strpnrmls,
+			TriangleStripBuilder triStripBldr) {
+
+		float cx1 = 0;
+		float cy1 = 0;
+		float cx2 = 0;
+		float cy2 = 0;
+		int cc = 0;
+		int[][] grd = new int[2][2];
+		int color_length = crnr_color[0].length;
+
+		float[][] tri = new float[2][4];
+		float[][] normals = new float[3][4];
+		float[][] tmp = new float[3][1];
+		byte[] color = new byte[color_length];
+		int vidx_0 = 0;
+		int vidx_1 = 0;
+		int crn_1 = 0;
+		int crn_2 = 0;
+
+		byte first_tri_orient = 0;
+		byte last_tri_orient = 0;
+		byte first_strp_side = SIDE_NONE;
+		byte last_strp_side = SIDE_NONE;
+
+		switch (o_flag) {
+		case 3:
+			switch (flag) {
+			case 1:
+				closed[0] = closed[0] | 12;
+				if (crnr_out[2] || crnr_out[3])
+					return;
+				cx1 = xx;
+				cy1 = yy + yd;
+				cx2 = xx + xd;
+				cy2 = yy + yd;
+				cc = 3;
+				grd[0][0] = 0;
+				grd[0][1] = 1;
+				grd[1][0] = 1;
+				grd[1][1] = 1;
+				crn_1 = 1;
+				crn_2 = 3;
+				vidx_0 = 0;
+				vidx_1 = 2;
+				first_tri_orient = -1;
+				last_tri_orient = 1;
+				first_strp_side = 3;
+				last_strp_side = 1;
+				break;
+			case -1:
+				closed[0] = closed[0] | 3;
+				if (crnr_out[0] || crnr_out[1])
+					return;
+				cx1 = xx;
+				cy1 = yy;
+				cx2 = xx + xd;
+				cy2 = yy;
+				cc = 0;
+				grd[0][0] = 0;
+				grd[0][1] = 0;
+				grd[1][0] = 1;
+				grd[1][1] = 0;
+				crn_1 = 0;
+				crn_2 = 2;
+				vidx_0 = 1;
+				vidx_1 = 3;
+				first_tri_orient = -1;
+				last_tri_orient = 1;
+				first_strp_side = 3;
+				last_strp_side = 1;
+				break;
+			}
+			break;
+
+		case 5:
+			switch (flag) {
+			case 1:
+				closed[0] = closed[0] | 5;
+				if (crnr_out[0] || crnr_out[2])
+					return;
+				cx1 = xx;
+				cy1 = yy;
+				cx2 = xx;
+				cy2 = yy + yd;
+				cc = 0;
+				grd[0][0] = 0;
+				grd[0][1] = 0;
+				grd[1][0] = 0;
+				grd[1][1] = 1;
+				crn_1 = 0;
+				crn_2 = 1;
+				vidx_0 = 2;
+				vidx_1 = 3;
+				first_tri_orient = -1;
+				last_tri_orient = 1;
+				first_strp_side = 3;
+				last_strp_side = -1;
+				break;
+			case -1:
+				closed[0] = closed[0] | 10;
+				if (crnr_out[1] || crnr_out[3])
+					return;
+				cx1 = xx + xd;
+				cy1 = yy;
+				cx2 = xx + xd;
+				cy2 = yy + yd;
+				grd[0][0] = 1;
+				grd[0][1] = 0;
+				grd[1][0] = 1;
+				grd[1][1] = 1;
+				cc = 3;
+				crn_1 = 2;
+				crn_2 = 3;
+				vidx_0 = 0;
+				vidx_1 = 1;
+				first_tri_orient = -1;
+				last_tri_orient = 1;
+				first_strp_side = -1;
+				last_strp_side = 1;
+				break;
+			}
+			break;
+		}
+
+		for (int ii = 0; ii < color_length; ii++) {
+			color[ii] = crnr_color[cc][ii];
+		}
+		int levIdx = crnrLevelIdx[cc];
+
+		tri[0][crn_1] = cx1;
+		tri[1][crn_1] = cy1;
+		int i = grd[0][0];
+		int j = grd[0][1];
+		normals[0][crn_1] = grd_normals[nc + j][nr + i][0];
+		normals[1][crn_1] = grd_normals[nc + j][nr + i][1];
+		normals[2][crn_1] = grd_normals[nc + j][nr + i][2];
+
+		tri[0][crn_2] = cx2;
+		tri[1][crn_2] = cy2;
+		i = grd[1][0];
+		j = grd[1][1];
+		normals[0][crn_2] = grd_normals[nc + j][nr + i][0];
+		normals[1][crn_2] = grd_normals[nc + j][nr + i][1];
+		normals[2][crn_2] = grd_normals[nc + j][nr + i][2];
+
+		tri[0][vidx_0] = vx[v_idx];
+		tri[1][vidx_0] = vy[v_idx];
+		interpNormals(tri[0][vidx_0], tri[1][vidx_0], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][vidx_0] = tmp[0][0];
+		normals[1][vidx_0] = tmp[1][0];
+		normals[2][vidx_0] = tmp[2][0];
+
+		tri[0][vidx_1] = vx[v_idx + dir];
+		tri[1][vidx_1] = vy[v_idx + dir];
+		interpNormals(tri[0][vidx_1], tri[1][vidx_1], xx, yy, nc, nr, xd, yd,
+				grd_normals, tmp);
+		normals[0][vidx_1] = tmp[0][0];
+		normals[1][vidx_1] = tmp[1][0];
+		normals[2][vidx_1] = tmp[2][0];
+
+		if (triStripBldr != null) {
+			triStripBldr.addVerticies(levIdx, tri, normals, color,
+					first_strp_side, first_tri_orient, last_strp_side,
+					last_tri_orient);
+		}
+	}
+
+	private static void addCorner(float xx, float yy, float xd, float yd,
+			int nc, int nr, byte cornerID, float[][][] grd_normals,
+			int[] closed, int strpIdx, float[][] strpverts, float[][] strpnrmls) {
+
+		float cx = 0;
+		float cy = 0;
+		int i = 0; // int offsets for corners
+		int j = 0;
+
+		if (cornerID == 0) {
+			closed[0] = closed[0] | 1;
+			cx = xx;
+			cy = yy;
+			i = 0;
+			j = 0;
+		} else if (cornerID == 1) {
+			closed[0] = closed[0] | 2;
+			cx = xx + xd;
+			cy = yy;
+			i = 1;
+			j = 0;
+		} else if (cornerID == 2) {
+			closed[0] = closed[0] | 4;
+			cx = xx;
+			cy = yy + yd;
+			i = 0;
+			j = 1;
+		} else if (cornerID == 3) {
+			closed[0] = closed[0] | 8;
+			cx = xx + xd;
+			cy = yy + yd;
+			i = 1;
+			j = 1;
+		}
+
+		strpverts[0][strpIdx] = cx;
+		strpverts[1][strpIdx] = cy;
+		strpnrmls[0][strpIdx] = grd_normals[nc + j][nr + i][0];
+		strpnrmls[1][strpIdx] = grd_normals[nc + j][nr + i][1];
+		strpnrmls[2][strpIdx] = grd_normals[nc + j][nr + i][2];
+	}
+
+	public static int[] getTriOrientation(float[][] verts) {
+		/* note: doesn't deal with cross-product == 0 */
+		int len = verts[0].length;
+		float xa = verts[0][1] - verts[0][0];
+		float ya = verts[1][1] - verts[1][0];
+		float xb = verts[0][2] - verts[0][0];
+		float yb = verts[1][2] - verts[1][0];
+
+		float first = xa * yb - xb * ya;
+
+		xa = verts[0][len - 2] - verts[0][len - 3];
+		ya = verts[1][len - 2] - verts[1][len - 3];
+		xb = verts[0][len - 1] - verts[0][len - 3];
+		yb = verts[1][len - 1] - verts[1][len - 3];
+
+		float last = xa * yb - xb * ya;
+
+		int firstOrient = (first < 0) ? CLOCKWISE : CNTRCLOCKWISE;
+		int lastOrient = (last < 0) ? CLOCKWISE : CNTRCLOCKWISE;
+
+		return new int[] { firstOrient, lastOrient };
+	}
+
+	static final class ContourOutput {
+
+		public final ContourStripSet stripSet;
+		public final TriangleStripBuilder triStripBldr;
+
+		ContourOutput(ContourStripSet set, TriangleStripBuilder tsb) {
+			stripSet = set;
+			triStripBldr = tsb;
+		}
+
+		boolean isLineStyled(int lvl) {
+			return stripSet.isLevelStyled(lvl);
+		}
+
+		List<float[][][]> getLineStripCoordinates(int lvl) {
+			return stripSet.getLineStripCoordinates(lvl);
+		}
+
+		List<byte[][][]> getLineStripColors(int lvl) {
+			return stripSet.getLineStripColors(lvl);
+		}
+
+		int getIntervalCount() {
+			return stripSet.vecArray.length;
+		}
+
+		List<ContourStrip> getStrips(int lvl) {
+			return stripSet.vecArray[lvl];
+		}
+
+		int[] getLabelIndexes(int lvlIdx) {
+			return stripSet.labelIndexes[lvlIdx];
+		}
+
+		float[] getLevels() {
+			return stripSet.levels;
+		}
+	}
+} // end class
+
+/**
+ * Class ContourQuadSet
+ * 
+ */
+
+class ContourQuadSet {
+
+	/**           */
+	int nx = 1;
+
+	/**           */
+	int ny = 1;
+
+	/**           */
+	int npx;
+
+	/**           */
+	int npy;
+
+	/**           */
+	int nc;
+
+	/**           */
+	int nr;
+
+	/**           */
+	int lev_idx;
+
+	/**           */
+	int numv = 0;
+
+	/**           */
+	ContourStripSet css = null;
+
+	/**           */
+	ContourQuad[][] qarray = null;
+
+	/**           */
+	int snumv = 0;
+
+	/**           */
+	public Map<CachedArrayDimension, CachedArray> subGridMap = new HashMap<CachedArrayDimension, CachedArray>();
+
+	/**           */
+	public Map<CachedArrayDimension, CachedArray> subGrid2Map = new HashMap<CachedArrayDimension, CachedArray>();
+
+	/**           */
+	public Map<CachedArrayDimension, CachedArray> markGridMap = new HashMap<CachedArrayDimension, CachedArray>();
+
+	/**           */
+	public Map<CachedArrayDimension, CachedArray> markGrid2Map = new HashMap<CachedArrayDimension, CachedArray>();
+
+	/**
+	 * 
+	 * @param nr
+	 * @param nc
+	 * @param lev_idx
+	 * @param css
+	 */
+	
+	ContourQuadSet(int nr, int nc, int lev_idx, ContourStripSet css) {
+		this.nc = nc;
+		this.nr = nr;
+		this.lev_idx = lev_idx;
+		this.css = css;
+
+		npy = (int) (nr / ny);
+		npx = (int) (nc / nx);
+
+		qarray = new ContourQuad[ny][nx];
+		// JDM: Only make the ContourQuad objects when we need them
+		/*
+		 * for (int j=0; j<ny; j++) { for (int i=0; i<nx; i++) { qarray[j][i] =
+		 * makeContourQuad(j,i); } }
+		 */
+	}
+
+	/**
+	 * 
+	 * @param j
+	 * @param i
+	 * 
+	 * @return
+	 */
+	
+	private ContourQuad makeContourQuad(int j, int i) {
+		int lenx = npx;
+		int leny = npy;
+		if (j == ny - 1)
+			leny = nr - (ny - 1) * npy;
+		if (i == nx - 1)
+			lenx = nc - (nx - 1) * npx;
+		return new ContourQuad(this, j * npy, i * npx, leny, lenx);
+	}
+
+	/**
+	 * 
+	 * @param idx0
+	 * @param ir
+	 * @param ic
+	 */
+	
+	public void add(int idx0, int ir, int ic) {
+		int ix = (int) (ic / npx);
+		int iy = (int) (ir / npy);
+		if (ix >= nx)
+			ix = nx - 1;
+		if (iy >= ny)
+			iy = ny - 1;
+		if (qarray[iy][ix] == null) {
+			qarray[iy][ix] = makeContourQuad(iy, ix);
+		}
+		qarray[iy][ix].add(idx0, ir, ic);
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 */
+	
+	public void get(float[] vx, float[] vy) {
+		numv = 0;
+		snumv = 0;
+		for (int j = 0; j < ny; j++) {
+			for (int i = 0; i < nx; i++) {
+				if (qarray[j][i] == null)
+					continue;
+				ContourStrip[] c_strps = qarray[j][i].getContourStrips(vx, vy);
+				numv += qarray[j][i].numv;
+				snumv += qarray[j][i].stripCnt;
+				if (c_strps != null) {
+					css.vecArray[lev_idx].add(c_strps[0]);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param auxLevels
+	 * @param vx1
+	 * @param vy1
+	 * @param vz1
+	 * @param colors
+	 * @param spatial_set
+	 * 
+	 * @throws VisADException
+	 */
+	
+	public void getArrays(float[] vx, float[] vy, byte[][] auxLevels,
+			float[][] vx1, float[][] vy1, float[][] vz1, byte[][] colors,
+			Gridded3DSet spatial_set) throws VisADException {
+
+		float[][] arrays = new float[2][2 * numv];
+		int clr_dim = 3;
+		if (auxLevels != null) {
+			clr_dim = auxLevels.length;
+			colors[0] = new byte[2 * numv];
+			colors[1] = new byte[2 * numv];
+			colors[2] = new byte[2 * numv];
+			colors[3] = new byte[2 * numv];
+		}
+
+		int cnt = 0;
+		for (int j = 0; j < ny; j++) {
+			for (int i = 0; i < nx; i++) {
+				if (qarray[j][i] == null)
+					continue;
+				for (int k = 0; k < qarray[j][i].numv; k++) {
+					int vidx = qarray[j][i].vert_indices[k];
+					if (vidx >= 0) {
+						arrays[0][cnt] = vx[vidx];
+						arrays[1][cnt] = vy[vidx];
+						if (auxLevels != null) {
+							colors[0][cnt] = auxLevels[0][vidx];
+							colors[1][cnt] = auxLevels[1][vidx];
+							colors[2][cnt] = auxLevels[2][vidx];
+							if (clr_dim == 4)
+								colors[3][cnt] = auxLevels[3][vidx];
+						}
+						cnt++;
+						arrays[0][cnt] = vx[vidx + 1];
+						arrays[1][cnt] = vy[vidx + 1];
+						if (auxLevels != null) {
+							colors[0][cnt] = auxLevels[0][vidx + 1];
+							colors[1][cnt] = auxLevels[1][vidx + 1];
+							colors[2][cnt] = auxLevels[2][vidx + 1];
+							if (clr_dim == 4)
+								colors[3][cnt] = auxLevels[3][vidx];
+						}
+						cnt++;
+					}
+				}
+			}
+		}
+
+		float[][] tmp = new float[2][cnt];
+		System.arraycopy(arrays[0], 0, tmp[0], 0, cnt);
+		System.arraycopy(arrays[1], 0, tmp[1], 0, cnt);
+
+		float[][] tmp3D = spatial_set.gridToValue(tmp);
+		vx1[0] = tmp3D[0];
+		vy1[0] = tmp3D[1];
+		vz1[0] = tmp3D[2];
+		arrays = null;
+		colors = null;
+	}
+}
+
+/**
+ * Class ContourQuad
+ * 
+ */
+
+class ContourQuad {
+
+	/**           */
+	int[] vert_indices;
+
+	/**           */
+	int[] vert_indices_save = null;
+
+	/**           */
+	int[] grid_indices;
+
+	/**           */
+	int maxnumv;
+
+	/**           */
+	int numv;
+
+	/**           */
+	ContourQuadSet qs;
+
+	/**           */
+	int nc;
+
+	/**           */
+	int nr;
+
+	/**           */
+	int strty;
+
+	/**           */
+	int strtx;
+
+	/**           */
+	int leny;
+
+	/**           */
+	int lenx;
+
+	/**           */
+	int lev_idx;
+
+	/**           */
+	int[][] sub_grid = null;
+
+	/**           */
+	int[][] sub_grid_2 = null;
+
+	/**           */
+	int[][] mark_grid = null;
+
+	/**           */
+	int[][] mark_grid_2 = null;
+
+	/**           */
+	ContourStripSet css = null;
+
+	/**           */
+	int[] stripVert_indices;
+
+	/**           */
+	int stripCnt = 0;
+
+	/**
+	 * 
+	 * @param qs
+	 * @param strty
+	 * @param strtx
+	 * @param leny
+	 * @param lenx
+	 */
+	
+	ContourQuad(ContourQuadSet qs, int strty, int strtx, int leny, int lenx) {
+		maxnumv = 100;
+		numv = 0;
+		vert_indices = new int[maxnumv]; // - indices into vx,vy
+		grid_indices = new int[maxnumv]; // - location on the grid
+		this.qs = qs;
+		this.nc = qs.nc;
+		this.nr = qs.nr;
+		this.strty = strty;
+		this.strtx = strtx;
+		this.leny = leny;
+		this.lenx = lenx;
+		css = qs.css;
+		lev_idx = qs.lev_idx;
+	}
+
+	/**
+	 * 
+	 * @param idx0
+	 * @param gy
+	 * @param gx
+	 */
+	
+	public void add(int idx0, int gy, int gx) {
+		if (numv < maxnumv - 2) {
+			vert_indices[numv] = idx0;
+			grid_indices[numv] = gy * nc + gx;
+			numv++;
+		} else {
+			maxnumv += 50;
+			int[] tmpA = vert_indices;
+			int[] tmpB = grid_indices;
+			vert_indices = new int[maxnumv];
+			grid_indices = new int[maxnumv];
+			System.arraycopy(tmpA, 0, vert_indices, 0, numv);
+			System.arraycopy(tmpB, 0, grid_indices, 0, numv);
+			tmpA = null;
+			tmpB = null;
+			vert_indices[numv] = idx0;
+			grid_indices[numv] = gy * nc + gx;
+			numv++;
+		}
+	}
+
+	/**
+	 * 
+	 * @param leny
+	 * @param lenx
+	 * 
+	 * @return
+	 */
+	
+	public int[][][] getWorkArrays(int leny, int lenx) {
+		Object key;
+
+		java.util.Set<CachedArrayDimension> keySet = qs.subGridMap.keySet();
+
+		key = null;
+		for (CachedArrayDimension obj : keySet) {
+			if (obj.equals(new CachedArrayDimension(leny, lenx))) {
+				key = obj;
+				break;
+			}
+		}
+
+		int[][] subgrid = null;
+		int[][] subgrid2 = null;
+		int[][] markgrid = null;
+		int[][] markgrid2 = null;
+
+		if (key != null) {
+			subgrid = ((CachedArray) qs.subGridMap.get(key)).getArray();
+			subgrid2 = ((CachedArray) qs.subGrid2Map.get(key)).getArray();
+			markgrid = ((CachedArray) qs.markGridMap.get(key)).getArray();
+			markgrid2 = ((CachedArray) qs.markGrid2Map.get(key)).getArray();
+		} else {
+			subgrid = new int[leny][lenx];
+			subgrid2 = new int[leny][lenx];
+			markgrid = new int[leny][lenx];
+			markgrid2 = new int[leny][lenx];
+			CachedArrayDimension newKey = new CachedArrayDimension(leny, lenx);
+			qs.subGridMap.put(newKey, new CachedArray(subgrid));
+			qs.subGrid2Map.put(newKey, new CachedArray(subgrid2));
+			qs.markGridMap.put(newKey, new CachedArray(markgrid));
+			qs.markGrid2Map.put(newKey, new CachedArray(markgrid2));
+		}
+		return new int[][][] { subgrid, subgrid2, markgrid, markgrid2 };
+	}
+
+	/**
+	 *
+	 */
+	
+	public void get() {
+		int ix_i = -1;
+		int iy_i = -1;
+		int[][][] workarrays = getWorkArrays(leny, lenx);
+		sub_grid = workarrays[0];
+		sub_grid_2 = workarrays[1];
+		mark_grid = workarrays[2];
+		mark_grid_2 = workarrays[3];
+
+		for (int j = 0; j < leny; j++) {
+			for (int i = 0; i < lenx; i++) {
+				sub_grid[j][i] = 0;
+				sub_grid_2[j][i] = 0;
+			}
+		}
+
+		if (vert_indices_save == null) {
+			vert_indices_save = new int[vert_indices.length];
+			System.arraycopy(vert_indices, 0, vert_indices_save, 0,
+					vert_indices.length);
+		} else {
+			System.arraycopy(vert_indices_save, 0, vert_indices, 0,
+					vert_indices.length);
+		}
+
+		for (int ii = 0; ii < numv; ii++) {
+			int kk = grid_indices[ii];
+			int iy = (int) kk / nc;
+			int ix = kk - iy * nc;
+			sub_grid[iy - strty][ix - strtx] = vert_indices[ii];
+			mark_grid[iy - strty][ix - strtx] = ii;
+			if (ix_i == ix && iy_i == iy) {
+				sub_grid_2[iy - strty][ix - strtx] = vert_indices[ii];
+				mark_grid_2[iy - strty][ix - strtx] = ii;
+			}
+			ix_i = ix;
+			iy_i = iy;
+		}
+	}
+
+	/**
+	 *
+	 */
+	
+	public void reset() {
+		for (int j = 0; j < leny; j++) {
+			for (int i = 0; i < lenx; i++) {
+				if (sub_grid[j][i] < 0) {
+					sub_grid[j][i] *= -1;
+				}
+			}
+		}
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * 
+	 * @return
+	 */
+	
+	public ContourStrip[] getContourStrips(float[] vx, float[] vy) {
+
+		get();
+		stripVert_indices = new int[200];
+		int n_segs = 0;
+		int[][] udrl = { { 1, -1, 0, 0 }, { 0, 0, 1, -1 } }; // -up/down,
+																// right/left
+
+		// - find starting point
+		int[] start = getStartPoint();
+		if (start == null) {
+			return null;
+		}
+		int iy = start[0];
+		int ix = start[1];
+
+		int idx0 = sub_grid[iy][ix];
+		int idx1 = idx0 + 1;
+
+		ContourStrip c_strp = new ContourStrip(lev_idx, idx0, idx1, css);
+
+		int idxA = idx0;
+		int idxB = idx0;
+		int idxA_2;
+		int idxB_2;
+
+		int ix_t, iy_t;
+		int ix_a = ix;
+		int iy_a = iy;
+		int ix_b = ix;
+		int iy_b = iy;
+
+		int cnt = 0;
+		stripVert_indices[cnt++] = idx0;
+		sub_grid[iy][ix] *= -1;
+
+		int test_cnt = 0;
+		while ((n_segs < 100) && (test_cnt < 300)) {
+			test_cnt++;
+
+			// - A
+			for (int k = 0; k < 4; k++) {
+				ix_t = ix_a + udrl[0][k];
+				iy_t = iy_a + udrl[1][k];
+				if ((iy_t >= 0 && iy_t < leny) && (ix_t >= 0 && ix_t < lenx)) {
+
+					idxA = sub_grid[iy_t][ix_t];
+					idxA_2 = sub_grid_2[iy_t][ix_t];
+
+					if (idxA > 0) {
+						if (c_strp.addPair(vx, vy, idxA, idxA + 1)) {
+							sub_grid[iy_t][ix_t] *= -1;
+							stripVert_indices[cnt++] = idxA;
+							ix_a = ix_t;
+							iy_a = iy_t;
+							vert_indices[mark_grid[iy_t][ix_t]] = -1;
+							n_segs++;
+							break;
+						}
+					}
+					if (idxA_2 > 0) {
+						if (c_strp.addPair(vx, vy, idxA_2, idxA_2 + 1)) {
+							sub_grid_2[iy_t][ix_t] *= -1;
+							stripVert_indices[cnt++] = idxA_2;
+							ix_a = ix_t;
+							iy_a = iy_t;
+							vert_indices[mark_grid_2[iy_t][ix_t]] = -1;
+							n_segs++;
+							break;
+						}
+					}
+				}
+			}
+
+			// - B
+			for (int k = 0; k < 4; k++) {
+				ix_t = ix_b + udrl[0][k];
+				iy_t = iy_b + udrl[1][k];
+				if ((iy_t >= 0 && iy_t < leny) && (ix_t >= 0 && ix_t < lenx)) {
+
+					idxB = sub_grid[iy_t][ix_t];
+					idxB_2 = sub_grid_2[iy_t][ix_t];
+
+					if (idxB > 0) {
+						if (c_strp.addPair(vx, vy, idxB, idxB + 1)) {
+							sub_grid[iy_t][ix_t] *= -1;
+							stripVert_indices[cnt++] = idxB;
+							ix_b = ix_t;
+							iy_b = iy_t;
+							vert_indices[mark_grid[iy_t][ix_t]] = -1;
+							n_segs++;
+							break;
+						}
+					}
+					if (idxB_2 > 0) {
+						if (c_strp.addPair(vx, vy, idxB_2, idxB_2 + 1)) {
+							sub_grid_2[iy_t][ix_t] *= -1;
+							stripVert_indices[cnt++] = idxB_2;
+							ix_b = ix_t;
+							iy_b = iy_t;
+							vert_indices[mark_grid_2[iy_t][ix_t]] = -1;
+							n_segs++;
+							break;
+						}
+					}
+				}
+			}
+		}
+		stripCnt = cnt;
+
+		sub_grid = null;
+		sub_grid_2 = null;
+		return new ContourStrip[] { c_strp };
+
+	}
+
+	/**
+	 * getStartPoint
+	 * 
+	 * @return Starting point as two-element int array
+	 */
+	
+	public int[] getStartPoint() {
+		int n_trys = 20;
+		float nn = (float) lenx * leny;
+		java.util.Random rnd = new java.util.Random();
+
+		for (int tt = 0; tt < n_trys; tt++) {
+			int kk = (int) (nn * rnd.nextFloat());
+			int j = (int) kk / lenx;
+			int i = kk - j * lenx;
+			if (sub_grid[j][i] != 0) {
+				return new int[] { j, i };
+			}
+		}
+		return null;
+	}
+}
+
+/**
+ * Class CachedArray
+ * 
+ */
+
+class CachedArray {
+
+	/**           */
+	int[][] array;
+
+	/**
+	 * 
+	 * 
+	 * @param array
+	 */
+	public CachedArray(int[][] array) {
+		this.array = array;
+	}
+
+	/**
+	 * 
+	 * 
+	 * @return
+	 */
+	int[][] getArray() {
+		return array;
+	}
+}
+
+/**
+ * Class CachedArrayDimension
+ * 
+ */
+
+class CachedArrayDimension {
+
+	/**           */
+	int lenx;
+
+	/**           */
+	int leny;
+
+	/**
+	 * 
+	 * 
+	 * @param leny
+	 * @param lenx
+	 */
+	CachedArrayDimension(int leny, int lenx) {
+		this.leny = leny;
+		this.lenx = lenx;
+	}
+
+	/**
+	 * 
+	 * 
+	 * @param obj
+	 * 
+	 * @return
+	 */
+	public boolean equals(CachedArrayDimension obj) {
+		return (lenx == obj.lenx && leny == obj.leny);
+	}
+}
+
+/**
+ * ContourStripSet is used internally by Contour2D
+ */
+
+class ContourStripSet {
+
+	// value for dash algm.
+
+	/**           */
+	static final int DEFAULT_DASH_VALUE = 2;
+
+	/**           */
+	static final int DISABLE_DASH_VALUE = -1;
+
+	/**           */
+	float[] levels;
+
+	int[][] labelIndexes;
+	
+	int labelFreq = ContourControl.LABEL_FREQ_LO;
+
+	/**           */
+	int n_levs;
+
+	/**           */
+	int nr;
+
+	/**           */
+	int nc;
+
+	/**           */
+	Gridded3DSet spatial_set;
+
+	/** Contour strips by level. */
+	List<ContourStrip>[] vecArray;
+
+	/**           */
+	List<ContourStrip> vec;
+
+        /** Closed strips by level. */
+	List<ContourStrip>[] closedStripArray;
+
+        List<ContourStrip> closedStripList;
+
+	/**           */
+	boolean[] swap;
+
+	/**           */
+	ContourQuadSet[] qSet;
+
+	/** Grid X coordinates. */
+	private float[] gridX;
+	/** Grid Y coordinates. */
+	private float[] gridY;
+	/** Colors corresponding to grid values. */
+	private byte[][] gridColors;
+
+	ArrayList<ContourLabelGeometry> labels = new ArrayList<ContourLabelGeometry>();
+	ArrayList<VisADLineStripArray> fillLines = new ArrayList<VisADLineStripArray>();
+	ArrayList<VisADLineStripArray> fillLinesStyled = new ArrayList<VisADLineStripArray>();
+	ArrayList<VisADLineStripArray> cntrLines = new ArrayList<VisADLineStripArray>();
+	ArrayList<VisADLineStripArray> cntrLinesStyled = new ArrayList<VisADLineStripArray>();
+
+	double labelScale;
+
+	public int labelLineSkip = ContourControl.EVERY_NTH_DEFAULT;
+
+	/**
+	 * 
+	 * @param levels
+	 * @param swap
+	 * @param scale_ratio
+	 * @param label_size
+	 * @param nr
+	 * @param nc
+	 * @param spatial_set
+	 * @param contourDifficulty
+	 * 
+	 * @throws VisADException
+	 */
+
+	ContourStripSet(float[] levels, boolean[] swap, double scale_ratio,
+			int label_freq, int label_line_skip, double label_size, int nr, int nc,
+			Gridded3DSet spatial_set) throws VisADException {
+
+		this.levels = levels;
+		n_levs = levels.length;
+		labelIndexes = new int[n_levs][];
+		
+		labelFreq = label_freq;
+		labelLineSkip = label_line_skip;
+		
+		vecArray = new List[n_levs];
+                closedStripArray = new List[n_levs];
+		labelScale = ((0.062 * (1.0 / scale_ratio)) * label_size);
+		this.nr = nr;
+		this.nc = nc;
+		this.swap = swap;
+		this.spatial_set = spatial_set;
+
+		for (int kk = 0; kk < n_levs; kk++) {
+			vecArray[kk] = new ArrayList<ContourStrip>();
+			closedStripArray[kk] = new ArrayList<ContourStrip>();
+		}
+
+		qSet = new ContourQuadSet[n_levs];
+		for (int kk = 0; kk < n_levs; kk++) {
+			qSet[kk] = new ContourQuadSet(nr, nc, kk, this);
+		}
+	}
+
+	/**
+	 * Set the grid coordinates used to contruct <code>ContourStrip</code>s
+	 * contained in this set.
+	 * 
+	 * @param gx
+	 * @param gy
+	 */
+	
+	void setGridValues(float[] gx, float[] gy) {
+		gridX = gx;
+		gridY = gy;
+	}
+
+	void setGridColors(byte[][] colors) {
+		gridColors = colors;
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param idx0
+	 * @param idx1
+	 * @param lev_idx
+	 * @param ir
+	 * @param ic
+	 */
+	
+	void add(float[] vx, float[] vy, int idx0, int idx1, int lev_idx, int ir,
+			int ic) {
+		qSet[lev_idx].add(idx0, ir, ic);
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param idx0
+	 * @param idx1
+	 * @param level
+	 */
+	
+	void add(float[] vx, float[] vy, int idx0, int idx1, float level) {
+		int lev_idx = 0;
+		for (int kk = 0; kk < n_levs; kk++) {
+			if (level == levels[kk])
+				lev_idx = kk;
+		}
+		add(vx, vy, idx0, idx1, lev_idx);
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param idx0
+	 * @param idx1
+	 * @param lev_idx
+	 */
+	
+	void add(float[] vx, float[] vy, int idx0, int idx1, int lev_idx) {
+                float delx = vx[idx1] - vx[idx0];
+                float dely = vy[idx1] - vy[idx0];
+                // skip really small segments
+                if ((delx <= 0.0001 && delx >= -0.0001) && (dely <= 0.0001 && dely >= -0.0001)) {
+                    return;
+                }
+
+		vec = vecArray[lev_idx];
+                closedStripList = closedStripArray[lev_idx];
+		int n_strip = vec.size();
+
+		if (n_strip == 0) {
+			ContourStrip c_strp = new ContourStrip(lev_idx, idx0, idx1, this);
+			vec.add(c_strp);
+		} else {
+			int[] found_array = new int[3];
+			int found = 0;
+			for (int kk = 0; kk < n_strip; kk++) {
+				ContourStrip c_strp = vec.get(kk);
+				if (c_strp.addPair(vx, vy, idx0, idx1)) {
+					found_array[found] = kk;
+					found++;
+                                        if (c_strp.closed) { // take off main list, add to closed (done) list.
+                                           vec.remove(c_strp);
+                                           closedStripList.add(c_strp);
+                                           break;
+                                        }
+					// exit loop if we hit threshold value
+					if (found == 3) break;
+				}
+			}
+			if (found == 3) {
+				ContourStrip c_strp = new ContourStrip(lev_idx, idx0,
+						idx1, this);
+				vec.add(c_strp);
+
+			} else if (found == 2) {
+				ContourStrip c_strpA = vec.get(found_array[0]);
+				ContourStrip c_strpB = vec.get(found_array[1]);
+				c_strpA.merge(c_strpB);
+				vec.remove(found_array[1]);
+
+			} else if (found == 0) {
+				ContourStrip c_strp = new ContourStrip(lev_idx, idx0,
+						idx1, this);
+				vec.add(c_strp);
+			}
+		}
+	}
+
+	/**
+         * Iterates over list of ContourStrips for each contour level index.
+         *
+	 * 
+	 * @param vx
+	 *            Grid coordinate values.
+	 * @param vy
+	 *            Grid coordinate values.
+	 * @param colors
+	 *            Colors for grid coordinate values.
+	 * @param labelColor
+	 *            Color for labels if filling.
+	 * @param lev_idx
+	 *            Index of the level to process.
+	 * @param out_vv
+	 *            Output line display coords for basic lines.
+	 * @param out_bb
+	 *            Output colors for basic lines.
+	 * @param out_vvL
+	 *            Output line display coords for labels.
+	 * @param out_bbL
+	 *            Output colors for label lines.
+	 * @param out_loc
+	 *            Output location coords for labels.
+	 * @param dashed
+	 *            Flags indicating which levels to dash.
+	 * @throws VisADException
+	 */
+	
+	void getLineColorArraysAtCntrLevel(float[] vx, float[] vy, byte[][] colors,
+			byte[] labelColor, Object labelFont, boolean labelAlign,
+			boolean sphericalDisplayCS, int lev_idx, boolean[] dashed)
+			throws VisADException {
+
+                // open strips (must end on grid boundary)
+		int n_strips = vecArray[lev_idx].size();
+		for (int kk = 0; kk < n_strips; kk++) {
+			ContourStrip cs = vecArray[lev_idx].get(kk);
+			cs.isDashed = dashed[lev_idx];
+			cs.getLabeledLineColorArray(vx, vy, colors, labelColor, labelFont,
+					labelAlign, sphericalDisplayCS);
+		}
+
+                // closed strips
+                n_strips = closedStripArray[lev_idx].size();
+                for (int kk = 0; kk < n_strips; kk++) {
+                        ContourStrip cs = closedStripArray[lev_idx].get(kk);
+                        cs.isDashed = dashed[lev_idx];
+                        cs.getLabeledLineColorArray(vx, vy, colors, labelColor, labelFont,
+                                        labelAlign, sphericalDisplayCS);
+                }
+
+
+	}
+
+	/**
+         * Called just after the grid walking is complete.
+         *
+	 * @param vx
+	 * @param vy
+	 * @param colors
+	 *            shared colors
+	 * @param labelColor
+	 *            RGB label color byte array
+	 * @param out_vv
+	 *            output vector verticie array {{ X }, { Y }}
+	 * @param out_bb
+	 * @param out_vvL
+	 * @param out_bbL
+	 * @param out_loc
+	 * @param dashFlags
+	 * @param contourDifficulty
+	 * @throws VisADException
+	 */
+	
+	void getLineColorArrays(float[] vx, float[] vy, byte[][] colors,
+			byte[] labelColor, Object labelFont, boolean labelAlign,
+			boolean sphericalDisplayCS, boolean[] dashFlags)
+			throws VisADException {
+              
+                /* Don't use the tiling logic for now.
+		makeContourStrips(vx, vy);
+                */
+
+		// set the line and color arrays for each level
+		for (int kk = 0; kk < n_levs; kk++) {
+			getLineColorArraysAtCntrLevel(vx, vy, colors, labelColor, labelFont,
+					labelAlign, sphericalDisplayCS, kk, dashFlags);
+		}
+
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 */
+	
+	void makeContourStrips(float[] vx, float[] vy) {
+		for (int kk = 0; kk < n_levs; kk++) {
+			int nx = qSet[kk].nx;
+			int ny = qSet[kk].ny;
+			for (int j = 0; j < ny; j++) {
+				for (int i = 0; i < nx; i++) {
+					if (qSet[kk].qarray[j][i] == null)
+						continue;
+					int[] vert_indices = qSet[kk].qarray[j][i].vert_indices;
+					int len = qSet[kk].qarray[j][i].numv;
+					for (int q = 0; q < len; q++) {
+						int idx = vert_indices[q];
+						add(vx, vy, idx, idx + 1, kk);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get grid coordinates representing the data at the level specified.
+	 * 
+	 * @param lvlIdx
+	 *            The level for which to generate an array.
+	 * @return An list of in line strip format, an emtpy list if none.
+	 * @see {@link VisADLineStripArray}
+	 */
+	
+	List<float[][][]> getLineStripCoordinates(int lvlIdx) {
+		if (lvlIdx > vecArray.length - 1) {
+			return new ArrayList<float[][][]>(0);
+		}
+		List<ContourStrip> strips = vecArray[lvlIdx];
+		List<float[][][]> stripValues = new ArrayList<float[][][]>();
+		for (ContourStrip strip : strips) {
+			stripValues.add(strip.getLineStripArrays(gridX, gridY));
+		}
+		return stripValues;
+	}
+
+	/**
+	 * Get colors corresponding to the grid coordinates for a level.
+	 * 
+	 * @param lvlIdx
+	 *            The level for which to get colors.
+	 * @return A list of arrays for the strips that make up the level, an empty
+	 *         list if none.
+	 */
+	
+	List<byte[][][]> getLineStripColors(int lvlIdx) {
+		if (lvlIdx > vecArray.length - 1) {
+			return new ArrayList<byte[][][]>(0);
+		}
+		List<ContourStrip> strips = vecArray[lvlIdx];
+		List<byte[][][]> stripColors = new ArrayList<byte[][][]>();
+		for (ContourStrip strip : strips) {
+			;
+			stripColors.add(strip.getColorStripArrays(gridColors));
+		}
+		return stripColors;
+	}
+
+	/**
+	 * Are we using line style for a level.
+	 * 
+	 * @param lvl
+	 *            The index of the the level.
+	 * @return True if the first strip is using line style, false otherwise.
+	 *         There is an assumption that if the first level is styled they all
+	 *         are.
+	 */
+	
+	boolean isLevelStyled(int lvl) {
+		if (vecArray.length > lvl + 1 && vecArray[lvl] != null) {
+			if (vecArray[lvl].size() > 0) {
+				return vecArray[lvl].get(0).isDashed;
+			}
+		}
+		return false;
+	}
+
+	boolean isLabeled(int lvl) {
+		return vecArray[lvl].get(0).isLabeled();
+	}
+
+} //--------  ContourStripSet -------------------
+
+/**
+ * ContourStrip is used internally by Contour2D to track the indexes associated
+ * with a strip. Indexes are in line strip format and not line array format.
+ */
+
+class ContourStrip {
+
+  /** Default label Font */
+	private static final HersheyFont TIMESR_FONT = new HersheyFont("timesr");
+
+  /** Minimum number of points for which to perform label algm */
+	static final int LBL_ALGM_THRESHHOLD = 20;
+
+	/**
+	 * Array of indexes to values in the grid coordinate arrays that make up
+	 * this strip.
+	 */
+	IndexPairList idxs = new IndexPairList();
+
+	/** Index to the level for this strip in the intervals array. */
+	int lev_idx;
+
+	private boolean isLabeled = false;
+	/** First index which starts the break for the label. */
+	private int start_break;
+	/** Last index which ends the break for the label. */
+	private int stop_break;
+	/** Number of indexes that make up the break for the label. */
+	private int n_skip;
+
+	/** Number of labels on this strip. */
+	int numLabels;
+	
+	/** Label every Nth line */
+	int numSkipLines;
+
+	boolean isDashed = false;
+
+	/**           */
+	PlotDigits plot;
+
+	/**           */
+	ContourStripSet css;
+
+    boolean closed = false;
+
+	/**
+	 * 
+	 * @param lev_idx
+	 * @param idx0
+	 * @param idx1
+	 * @param plot
+	 * @param css
+	 */
+	
+	ContourStrip(int lev_idx, int idx0, int idx1,
+			ContourStripSet css) {
+		this.lev_idx = lev_idx;
+
+		idxs.addFirst(idx0, idx1);
+
+		this.css = css;
+		numLabels = css.labelFreq;
+		numSkipLines = css.labelLineSkip;
+	}
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param idx0
+	 * @param idx1
+	 * 
+	 * @return
+	 */
+	
+	boolean addPair(float[] vx, float[] vy, int idx0, int idx1) {
+
+		// test for closed strip, bail out early if found
+                if (closed) return false;
+ 
+                float delta = 0.001f;
+		   
+		float vx0 = vx[idx0];
+		float vy0 = vy[idx0];
+		float vx1 = vx[idx1];
+		float vy1 = vy[idx1];
+
+		float vx_s = vx[idxs.first.idx0];
+		float vy_s = vy[idxs.first.idx0];
+
+                float delx = vx0 - vx_s;
+                float dely = vy0 - vy_s;
+                if ((delx > -delta && delx < delta) && (dely > -delta && dely < delta)) {
+			idxs.addFirst(idx1, idx0);
+                        setIsClosed(vx, vy);
+			return true;
+		}
+                delx = vx1 - vx_s;
+                dely = vy1 - vy_s;
+                if ((delx > -delta && delx < delta) && (dely > -delta && dely < delta)) {
+			idxs.addFirst(idx0, idx1);
+                        setIsClosed(vx, vy);
+			return true;
+		}
+
+		vx_s = vx[idxs.last.idx1];
+		vy_s = vy[idxs.last.idx1];
+                delx = vx0 - vx_s;
+                dely = vy0 - vy_s;
+                if ((delx > -delta && delx < delta) && (dely > -delta && dely < delta)) {
+			idxs.addLast(idx0, idx1);
+                        setIsClosed(vx, vy);
+			return true;
+		}
+                delx = vx1 - vx_s;
+                dely = vy1 - vy_s;
+                if ((delx > -delta && delx < delta) && (dely > -delta && dely < delta)) {
+			idxs.addLast(idx1, idx0);
+                        setIsClosed(vx, vy);
+			return true;
+		}
+
+		return false;
+	}
+
+        /** Check if endpoints are equal and set the closed flag.
+         */
+        void setIsClosed(float[] vx, float[] vy) {
+           float delta = 0.001f;
+           float delx = vx[idxs.first.idx0] - vx[idxs.last.idx1];
+           float dely = vy[idxs.first.idx0] - vy[idxs.last.idx1];
+           closed = ((idxs.numIndices > 2) && (delx > -delta && delx < delta) && (dely > -delta && dely < delta));
+        }
+
+
+	/**
+	 * 
+	 * @param vx
+	 * @param vy
+	 * @param colors
+	 * @param labelColor
+	 * @param out_vv
+	 * @param out_colors
+	 * @param out_vvL
+	 * @param out_colorsL
+	 * @param lbl_loc
+	 * @throws VisADException
+	 */
+	
+	void getLabeledLineColorArray(float[] vx, float[] vy, byte[][] colors,
+			byte[] labelColor, Object labelFont, boolean labelAlign,
+			boolean sphericalDisplayCS) throws VisADException {
+
+                boolean hasColors = (colors != null);
+
+		int linArrLen = idxs.getNumIndices();
+           
+		// break up each line into chunks according to label frequency
+		// Below heuristic can be tweaked if desired.  Just provides a
+		// label freq 1 to 9 mapping to point count for repeating the label
+
+                int labelRepeat = linArrLen;
+		switch (numLabels) {
+			case 1:
+				labelRepeat = linArrLen;
+				break;
+			case 3:
+				labelRepeat = 200;
+				break;
+			case 5:
+				labelRepeat = 150;
+				break;
+			case 7:
+				labelRepeat = 100;
+				break;
+			case 9:
+				labelRepeat = 50;
+				break;
+			default: 
+				labelRepeat = linArrLen;
+				break;
+		}
+		int labelCount = linArrLen / labelRepeat;
+		int labelRemain = linArrLen % labelRepeat;
+
+                if (labelRemain <= 4 && labelRemain > 0 && labelCount > 0) {
+                   labelCount -= 1;
+                   labelRemain += labelRepeat;
+                }
+
+		int labelsDone = 0;
+
+		for (int i = 0; i < labelCount; i++) {
+
+                        int start = i*labelRepeat/2;
+                        int stop = start + labelRepeat/2 - 1;
+
+                        float[][] vvTmp = getLineArray(vx, vy, start, stop);
+                        byte[][] bbTmp = null;
+                        if (hasColors) {
+                           bbTmp = getColorArray(colors, start, stop);
+                        }
+
+			processLineArrays(vvTmp, bbTmp, labelColor, labelFont, labelAlign,
+					sphericalDisplayCS);
+			labelsDone++;
+		}
+		
+		if (labelRemain > 0) {
+
+                        int start = labelsDone*labelRepeat/2;
+                        int stop = start + labelRemain/2 - 1;
+
+                        float[][] vvTmp = getLineArray(vx, vy, start, stop);
+                        byte[][] bbTmp = null;
+                        if (hasColors) {
+                           bbTmp = getColorArray(colors, start, stop);
+                        }
+
+			processLineArrays(vvTmp, bbTmp, labelColor, labelFont, labelAlign,
+					sphericalDisplayCS);
+		}
+	}
+
+	/**
+	 * Common line array code
+	 * 
+	 * @param vv_grid grid coordinates..
+	 * 
+	 * @param bb grid color values.
+	 * 
+	 * @param labelColor RGB label color byte array
+	 * 
+	 * @param out_vv
+	 * 
+	 * @param out_colors
+	 * 
+	 * @param out_vvL
+	 * 
+	 * @param out_colorsL
+	 * 
+	 * @param lbl_loc
+	 */
+	
+	private void processLineArrays(float[][] vv_grid, byte[][] bb,
+			byte[] labelColor, Object labelFont, boolean labelAlign,
+			boolean sphericalDisplayCS) throws VisADException {
+
+		float[][] vv = css.spatial_set.gridToValue(vv_grid);
+
+		int clr_dim = 0;
+		if (bb != null)
+			clr_dim = bb.length;
+
+		int totalPts = vv[0].length / 2;
+		int loc = 0;
+		int pos = 0;
+
+		VisADGeometryArray label = null;
+
+		DecimalFormat numFormat = new DecimalFormat();
+		numFormat.setMaximumFractionDigits(1);
+		numFormat.setGroupingUsed(false);
+		String numStr = numFormat.format((double) css.levels[lev_idx]);
+
+		float lbl_half = 0.1f;
+		isLabeled = false;
+		boolean labelThisLine = false;
+
+		// label every Nth line, user can adjust this
+		if ((lev_idx % css.labelLineSkip) == 0) {
+			labelThisLine = true;
+		}
+                
+		if ((totalPts > LBL_ALGM_THRESHHOLD) && (labelThisLine)) {
+			isLabeled = true;
+			loc = (vv[0].length) / 2; // - start at half-way pt.
+			int n_pairs_b = 1;
+			int n_pairs_f = 1;
+			boolean found = false;
+			float ctr_dist;
+			pos = loc;
+
+			// - get a unit vector parallel to the contour line at the label
+			// - position.
+			float del_x;
+			float del_y;
+			float del_z;
+			del_z = vv[2][pos + 1] - vv[2][pos - 1];
+			del_y = vv[1][pos + 1] - vv[1][pos - 1];
+			del_x = vv[0][pos + 1] - vv[0][pos - 1];
+			float mag = (float) Math.sqrt(del_y * del_y + del_x * del_x + del_z
+					* del_z);
+			float[] ctr_u = new float[] { del_x / mag, del_y / mag, del_z / mag };
+
+			if (ctr_u[0] < 0) {
+				ctr_u[0] = -ctr_u[0];
+				ctr_u[1] = -ctr_u[1];
+				ctr_u[2] = -ctr_u[2];
+			}
+
+			float ctr_u_dot_lbl = ctr_u[0] * 1f + ctr_u[1] * 0f + ctr_u[2] * 0f;
+
+			if (labelFont instanceof Font) {
+				label = PlotText.render_font(numStr, (Font) labelFont,
+						new double[] { vv[0][loc], vv[1][loc], vv[2][loc] },
+						new double[] { 1.0, 0.0, 0.0 }, new double[] { 0.0,
+								1.0, 0.0 }, TextControl.Justification.CENTER,
+						TextControl.Justification.CENTER, 0.0, css.labelScale,
+						null);
+			} else if (labelFont instanceof HersheyFont) {
+				label = PlotText.render_font(numStr, (HersheyFont) labelFont,
+						new double[] { vv[0][loc], vv[1][loc], vv[2][loc] },
+						new double[] { 1.0, 0.0, 0.0 }, new double[] { 0.0,
+								1.0, 0.0 }, TextControl.Justification.CENTER,
+						TextControl.Justification.CENTER, 0.0, css.labelScale,
+						null);
+			} else if (labelFont == null) {
+				label = PlotText.render_font(numStr, TIMESR_FONT,
+						new double[] { vv[0][loc], vv[1][loc], vv[2][loc] },
+						new double[] { 1.0, 0.0, 0.0 }, new double[] { 0.0,
+								1.0, 0.0 }, TextControl.Justification.CENTER,
+						TextControl.Justification.CENTER, 0.0, css.labelScale,
+						null);
+			} else {
+				label = PlotText.render_font(numStr, TIMESR_FONT,
+						new double[] { vv[0][loc], vv[1][loc], vv[2][loc] },
+						new double[] { 1.0, 0.0, 0.0 }, new double[] { 0.0,
+								1.0, 0.0 }, TextControl.Justification.CENTER,
+						TextControl.Justification.CENTER, 0.0, css.labelScale,
+						null);
+			}
+
+			float x_min = Float.MAX_VALUE;
+			float x_max = -Float.MAX_VALUE;
+			float y_min = Float.MAX_VALUE;
+			float y_max = -Float.MAX_VALUE;
+			float z_min = Float.MAX_VALUE;
+			float z_max = -Float.MAX_VALUE;
+
+			for (int k = 0; k < label.vertexCount; k++) {
+				int i = 3 * k;
+				float x = label.coordinates[i];
+				float y = label.coordinates[i + 1];
+				float z = label.coordinates[i + 2];
+				if (x > x_max)
+					x_max = x;
+				if (y > y_max)
+					y_max = y;
+				if (z > z_max)
+					z_max = z;
+				if (x < x_min)
+					x_min = x;
+				if (y < y_min)
+					y_min = y;
+				if (z < z_min)
+					z_min = z;
+			}
+
+			if (labelAlign) {
+				lbl_half = (x_max - x_min) / 2;
+			} else {
+				if (ctr_u_dot_lbl > 0.5) {
+					lbl_half = (x_max - x_min) / 2;
+				} else {
+					lbl_half = (y_max - y_min);
+				}
+			}
+
+			lbl_half += lbl_half * 0.08;
+
+			// - compute distance between label location (loc) and points
+			// - on each side, when greater than lbl_half - stop. This
+			// - assumes that around the label the contour line is fairly
+			// - linear, seems good approx almost all of time.
+
+			while (!found) { // - go backwards, ie decreasing index val
+				pos -= 2;
+				if (pos < 0 || pos > (vv[0].length - 1))
+					return;
+				float dx = vv[0][pos] - vv[0][loc];
+				float dy = vv[1][pos] - vv[1][loc];
+				float dz = vv[2][pos] - vv[2][loc];
+				ctr_dist = (float) Math.sqrt((double) (dx * dx + dy * dy + dz
+						* dz));
+				if (ctr_dist > (float) Math.abs((double) lbl_half)) {
+					found = true;
+				} else {
+					n_pairs_b++;
+				}
+			}
+
+			pos = loc;
+			found = false;
+			while (!found) { // - go fowards, ie increasing index val
+				pos += 2;
+				if (pos < 0 || pos > (vv[0].length - 1))
+					return;
+				float dx = vv[0][pos] - vv[0][loc];
+				float dy = vv[1][pos] - vv[1][loc];
+				float dz = vv[2][pos] - vv[2][loc];
+				ctr_dist = (float) Math.sqrt((double) (dx * dx + dy * dy + dz
+						* dz));
+				if (ctr_dist > (float) Math.abs((double) lbl_half)) {
+					found = true;
+				} else {
+					n_pairs_f++;
+				}
+			}
+
+			// - total number of points skipped (removed)
+			n_skip = (n_pairs_b + n_pairs_f) * 2;
+			// always start_break on even (1st in pair), stop_break on odd index
+			// (2nd in pair)
+			if ((loc & 1) == 1) { // - odd
+				start_break = loc - (1 + (n_pairs_b - 1) * 2);
+				stop_break = loc + (2 + (n_pairs_f - 1) * 2);
+			} else { // -even
+				start_break = loc - (2 + (n_pairs_b - 1) * 2);
+				stop_break = loc + (1 + (n_pairs_f - 1) * 2);
+			}
+
+		}
+
+		boolean doLabel = false;
+		// - check if label blocks out too may points
+		if (start_break >= 4 && stop_break <= totalPts * 2 - 3)
+			doLabel = true;
+
+		if (doLabel && isLabeled) {
+
+			
+			
+			/*-------LABEL START --------------------*/
+			float[] ctr_u = null;
+			float[] norm_x_ctr = null;
+
+			// - get a unit vector perpendicular to display coordinate grid
+			// - at this label location (loc)
+			float[][] norm = null;
+			Gridded3DSet cg3d = (Gridded3DSet) css.spatial_set;
+
+			norm = cg3d.getNormals(new float[][] { { vv_grid[0][loc] },
+					{ vv_grid[1][loc] } });
+
+			/*
+			 * test if (norm[2][0] < 0) { norm[0][0] = -norm[0][0]; norm[1][0] =
+			 * -norm[1][0]; norm[2][0] = -norm[2][0]; }
+			 */
+
+			float[] labelBase = null;
+			float[] labelUp = null;
+
+			if (labelAlign) { // - align labels with contours
+				// - get a unit vector parallel to the contour line at the label
+				// - position.
+				float del_x;
+				float del_y;
+				float del_z;
+
+				del_z = vv[2][stop_break] - vv[2][start_break];
+				del_y = vv[1][stop_break] - vv[1][start_break];
+				del_x = vv[0][stop_break] - vv[0][start_break];
+
+				float mag = (float) Math.sqrt(del_y * del_y + del_x * del_x
+						+ del_z * del_z);
+
+				ctr_u = new float[] { del_x / mag, del_y / mag, del_z / mag };
+
+				if (ctr_u[0] < 0) {
+					ctr_u[0] = -ctr_u[0];
+					ctr_u[1] = -ctr_u[1];
+					ctr_u[2] = -ctr_u[2];
+				}
+
+				if (sphericalDisplayCS) {
+					float[] newNorm = SphericalCoordinateSystem
+							.getNormal(new float[] { vv[0][pos], vv[1][pos],
+									vv[2][pos] });
+
+					norm[0][0] = newNorm[0];
+					norm[1][0] = newNorm[1];
+					norm[2][0] = newNorm[2];
+
+					float[] unitI = SphericalCoordinateSystem
+							.getUnitI(new float[] { vv[0][pos], vv[1][pos],
+									vv[2][pos] });
+
+					float ctr_u_dot_unitI = ctr_u[0] * unitI[0] + ctr_u[1]
+							* unitI[1] + ctr_u[2] * unitI[2];
+					if (ctr_u_dot_unitI < 0) {
+						ctr_u[0] = -ctr_u[0];
+						ctr_u[1] = -ctr_u[1];
+						ctr_u[2] = -ctr_u[2];
+					}
+				}
+
+				// - get a vector perpendicular to contour line, and in the
+				// local
+				// - tangent plane. norm_x_ctr: cross-product of local norm and
+				// - unit vector parallel to contour line at label location.
+				norm_x_ctr = new float[] {
+						norm[1][0] * ctr_u[2] - norm[2][0] * ctr_u[1],
+						-(norm[0][0] * ctr_u[2] - norm[2][0] * ctr_u[0]),
+						norm[0][0] * ctr_u[1] - norm[1][0] * ctr_u[0] };
+
+				mag = (float) Math.sqrt(norm_x_ctr[0] * norm_x_ctr[0]
+						+ norm_x_ctr[1] * norm_x_ctr[1] + norm_x_ctr[2]
+						* norm_x_ctr[2]);
+
+				// - normalize vector
+				norm_x_ctr[0] = norm_x_ctr[0] / mag;
+				norm_x_ctr[1] = norm_x_ctr[1] / mag;
+				norm_x_ctr[2] = norm_x_ctr[2] / mag;
+
+				if (!sphericalDisplayCS) {
+					if (Math.abs((double) norm[2][0]) <= 0.00001) {
+						if (norm_x_ctr[2] < 0) {
+							norm_x_ctr[0] = -norm_x_ctr[0];
+							norm_x_ctr[1] = -norm_x_ctr[1];
+							norm_x_ctr[2] = -norm_x_ctr[2];
+						}
+					} else {
+						if (norm_x_ctr[1] < 0) {
+							norm_x_ctr[0] = -norm_x_ctr[0];
+							norm_x_ctr[1] = -norm_x_ctr[1];
+							norm_x_ctr[2] = -norm_x_ctr[2];
+						}
+					}
+				}
+
+				labelBase = ctr_u;
+				labelUp = norm_x_ctr;
+			} else {
+				float a = norm[0][0];
+				float b = norm[1][0];
+				float c = norm[2][0];
+
+				float[] unitI = null;
+
+				if (sphericalDisplayCS) {
+					float[] newNorm = SphericalCoordinateSystem
+							.getNormal(new float[] { vv[0][pos], vv[1][pos],
+									vv[2][pos] });
+
+					unitI = SphericalCoordinateSystem.getUnitI(new float[] {
+							vv[0][pos], vv[1][pos], vv[2][pos] });
+
+					a = newNorm[0];
+					b = newNorm[1];
+					c = newNorm[2];
+				}
+
+				if (visad.util.Util.isApproximatelyEqual(a, 0)
+						&& visad.util.Util.isApproximatelyEqual(b, 0)) {
+					labelBase = new float[] { 1, 0, 0 };
+					labelUp = new float[] { 0, 1, 0 };
+				} else {
+					float D = -(a * vv[0][loc] + b * vv[1][loc] + c
+							* vv[2][loc]);
+					float K = -D - c * vv[2][loc];
+
+					float xLine = vv[0][loc] + 0.5f;
+					float yLine = (K - a * xLine) / b;
+
+					float delX = xLine - vv[0][loc];
+					float delY = yLine - vv[1][loc];
+
+					float mag = (float) Math.sqrt(delX * delX + delY * delY);
+
+					float[] uLine = null;
+
+					if (sphericalDisplayCS) {
+						uLine = new float[] { unitI[0], unitI[1], unitI[2] };
+					} else {
+						uLine = new float[] { delX / mag, delY / mag, 0 };
+					}
+
+					float[] norm_x_uLine = new float[] {
+							(b * uLine[2] - c * uLine[1]),
+							-(a * uLine[2] - c * uLine[0]),
+							(a * uLine[1] - b * uLine[0]) };
+
+					if (norm_x_uLine[2] < 0) {
+						norm_x_uLine[2] = 1f;
+						norm_x_uLine[1] = -norm_x_uLine[1];
+						norm_x_uLine[0] = -norm_x_uLine[0];
+					}
+
+					labelBase = uLine;
+					labelUp = norm_x_uLine;
+
+				}
+			}
+
+			// -- translate to label plot location --------------
+
+			if (labelFont instanceof Font) {
+				label = PlotText
+						.render_font(numStr, (Font) labelFont, new double[] {
+								vv[0][loc], vv[1][loc], vv[2][loc] },
+								new double[] { labelBase[0], labelBase[1],
+										labelBase[2] }, new double[] {
+										labelUp[0], labelUp[1], labelUp[2] },
+								TextControl.Justification.CENTER,
+								TextControl.Justification.CENTER, 0.0,
+								css.labelScale, null);
+			} else if (labelFont instanceof HersheyFont) {
+				label = PlotText
+						.render_font(numStr, (HersheyFont) labelFont,
+								new double[] { vv[0][loc], vv[1][loc],
+										vv[2][loc] }, new double[] {
+										labelBase[0], labelBase[1],
+										labelBase[2] }, new double[] {
+										labelUp[0], labelUp[1], labelUp[2] },
+								TextControl.Justification.CENTER,
+								TextControl.Justification.CENTER, 0.0,
+								css.labelScale, null);
+			} else if (labelFont == null) {
+				label = PlotText
+						.render_font(numStr, TIMESR_FONT,
+								new double[] { vv[0][loc], vv[1][loc],
+										vv[2][loc] }, new double[] {
+										labelBase[0], labelBase[1],
+										labelBase[2] }, new double[] {
+										labelUp[0], labelUp[1], labelUp[2] },
+								TextControl.Justification.CENTER,
+								TextControl.Justification.CENTER, 0.0,
+								css.labelScale, null);
+			} else {
+				label = PlotText
+						.render_font(numStr, TIMESR_FONT,
+								new double[] { vv[0][loc], vv[1][loc],
+										vv[2][loc] }, new double[] {
+										labelBase[0], labelBase[1],
+										labelBase[2] }, new double[] {
+										labelUp[0], labelUp[1], labelUp[2] },
+								TextControl.Justification.CENTER,
+								TextControl.Justification.CENTER, 0.0,
+								css.labelScale, null);
+			}
+
+			// no lighting/shading for labels
+			label.normals = null;
+
+			// set the color arrays for label, can be null
+			byte[] lblClr = null;
+			if (labelColor != null) {
+				int clrDim = labelColor.length;
+				lblClr = new byte[clrDim * label.vertexCount];
+				for (int kk = 0; kk < label.vertexCount; kk++) {
+					lblClr[kk * clrDim] = labelColor[0];
+					lblClr[kk * clrDim + 1] = labelColor[1];
+					lblClr[kk * clrDim + 2] = labelColor[2];
+					if (clrDim == 4)
+						lblClr[kk * clrDim + 3] = labelColor[3];
+				}
+			} else if (bb != null) {
+				lblClr = new byte[clr_dim * label.vertexCount];
+				for (int kk = 0; kk < label.vertexCount; kk++) {
+					lblClr[kk * clr_dim] = bb[0][loc];
+					lblClr[kk * clr_dim + 1] = bb[1][loc];
+					lblClr[kk * clr_dim + 2] = bb[2][loc];
+					if (clr_dim == 4)
+						lblClr[kk * clr_dim + 3] = bb[3][loc];
+				}
+			}
+			label.colors = lblClr;
+
+			VisADLineArray labelAnchor = new VisADLineArray();
+
+			SampledSet.setGeometryArray(labelAnchor, new float[][] {
+					{ vv[0][loc] }, { vv[1][loc] }, { vv[2][loc] } }, clr_dim,
+					null);
+
+			/*-------- LABEL DONE -------------------*/
+
+			// - this sections creates the contour gap for the label
+
+			int s_pos = 0;
+			int d_pos = 0;
+			int cnt = start_break;
+
+			// - make indexed
+			float[] lineCoords = new float[3 * ((start_break / 2 + 1)
+					+ (((totalPts * 2 - start_break) - n_skip) / 2) + 1)];
+			byte[] lineColors = new byte[clr_dim
+					* ((start_break / 2 + 1)
+							+ (((totalPts * 2 - start_break) - n_skip) / 2) + 1)];
+			float[] fillLineCoords = new float[3 * (n_skip / 2 + 1)];
+			byte[] fillLineColors = new byte[clr_dim * (n_skip / 2 + 1)];
+
+			int lineClrCnt = 0;
+			int lineCnt = 0;
+			lineCoords[lineCnt++] = vv[0][0];
+			lineCoords[lineCnt++] = vv[1][0];
+			lineCoords[lineCnt++] = vv[2][0];
+			for (int cc = 0; cc < clr_dim; cc++) {
+				lineColors[lineClrCnt++] = bb[cc][0];
+			}
+
+			for (int t = 1; t < cnt; t += 2) {
+				lineCoords[lineCnt++] = vv[0][t];
+				lineCoords[lineCnt++] = vv[1][t];
+				lineCoords[lineCnt++] = vv[2][t];
+				for (int c = 0; c < clr_dim; c++) {
+					lineColors[lineClrCnt++] = bb[c][t];
+				}
+			}
+
+			// label fill line
+			s_pos = start_break;
+			d_pos = 0;
+			cnt = n_skip;
+
+			fillLineCoords[0] = vv[0][s_pos];
+			fillLineCoords[1] = vv[1][s_pos];
+			fillLineCoords[2] = vv[2][s_pos];
+			for (int cc = 0; cc < clr_dim; cc++) {
+				fillLineColors[cc] = bb[cc][s_pos];
+			}
+			int kk = 3;
+			int nn = clr_dim;
+			for (int t = 1; t < n_skip; t += 2) {
+				fillLineCoords[kk++] = vv[0][s_pos + t];
+				fillLineCoords[kk++] = vv[1][s_pos + t];
+				fillLineCoords[kk++] = vv[2][s_pos + t];
+				for (int cc = 0; cc < clr_dim; cc++) {
+					fillLineColors[nn++] = bb[cc][s_pos + t];
+				}
+			}
+
+			VisADLineStripArray fillLineArray = new VisADLineStripArray();
+			fillLineArray.stripVertexCounts = new int[] { (n_skip / 2) + 1 };
+			fillLineArray.vertexCount = (n_skip / 2) + 1;
+			fillLineArray.coordinates = fillLineCoords;
+			if (fillLineColors.length > 0)
+				fillLineArray.colors = fillLineColors;
+			if (isDashed) {
+				css.fillLinesStyled.add(fillLineArray);
+			} else {
+				css.fillLines.add(fillLineArray);
+			}
+
+			// -- end label fill line;
+
+			s_pos = stop_break + 1;
+			d_pos = start_break;
+			cnt = vv[0].length - s_pos;
+
+			// - make indexed
+			lineCoords[lineCnt++] = vv[0][s_pos];
+			lineCoords[lineCnt++] = vv[1][s_pos];
+			lineCoords[lineCnt++] = vv[2][s_pos];
+			for (int cc = 0; cc < clr_dim; cc++) {
+				lineColors[lineClrCnt++] = bb[cc][s_pos];
+			}
+
+			for (int t = 1; t < ((totalPts * 2 - start_break) - n_skip); t += 2) {
+				lineCoords[lineCnt++] = vv[0][s_pos + t];
+				lineCoords[lineCnt++] = vv[1][s_pos + t];
+				lineCoords[lineCnt++] = vv[2][s_pos + t];
+				for (int c = 0; c < clr_dim; c++) {
+					lineColors[lineClrCnt++] = bb[c][s_pos + t];
+				}
+			}
+
+			VisADLineStripArray lineArray = new VisADLineStripArray();
+			lineArray.stripVertexCounts = new int[] { (start_break / 2) + 1,
+					(((totalPts * 2 - start_break) - n_skip) / 2) + 1 };
+			lineArray.vertexCount = lineArray.stripVertexCounts[0]
+					+ lineArray.stripVertexCounts[1];
+			lineArray.coordinates = lineCoords;
+			if (lineColors.length > 0) {
+				lineArray.colors = lineColors;
+			}
+			if (isDashed) {
+				css.cntrLinesStyled.add(lineArray);
+			} else {
+				css.cntrLines.add(lineArray);
+			}
+
+			// --- end label gap code
+
+			// --- expanding/contracting left-right segments
+
+			// - left
+			s_pos = start_break;
+			d_pos = 0;
+			cnt = 2;
+
+			// - unit left
+			float dx = vv[0][loc] - vv[0][s_pos];
+			float dy = vv[1][loc] - vv[1][s_pos];
+			float dz = vv[2][loc] - vv[2][s_pos];
+			float dd = (float) Math
+					.sqrt((double) (dx * dx + dy * dy + dz * dz));
+			dx = dx / dd;
+			dy = dy / dd;
+			dz = dz / dd;
+			float mm = dd - (float) Math.abs((double) lbl_half);
+			dx *= mm;
+			dy *= mm;
+			dz *= mm;
+			byte[][] segColors = new byte[clr_dim][2];
+			if (bb != null) {
+				for (int cc = 0; cc < clr_dim; cc++) {
+					System.arraycopy(bb[cc], s_pos, segColors[cc], d_pos, cnt);
+				}
+			}
+
+			VisADLineArray expSegLeft = new VisADLineArray();
+			VisADLineArray segLeftAnchor = new VisADLineArray();
+			SampledSet.setGeometryArray(expSegLeft, new float[][] {
+					{ vv[0][s_pos], vv[0][s_pos] + dx },
+					{ vv[1][s_pos], vv[1][s_pos] + dy },
+					{ vv[2][s_pos], vv[2][s_pos] + dz } }, clr_dim, segColors);
+			SampledSet.setGeometryArray(segLeftAnchor, new float[][] {
+					{ vv[0][s_pos] }, { vv[1][s_pos] }, { vv[2][s_pos] } },
+					clr_dim, null);
+
+			float[] segLeftScaleInfo = new float[] { lbl_half, dd };
+
+			// - right
+			s_pos = stop_break - 1;
+			d_pos = 0;
+			cnt = 2;
+
+			// - unit right
+			dx = vv[0][loc] - vv[0][stop_break];
+			dy = vv[1][loc] - vv[1][stop_break];
+			dz = vv[2][loc] - vv[2][stop_break];
+			dd = (float) Math.sqrt((double) (dx * dx + dy * dy + dz * dz));
+			dx = dx / dd;
+			dy = dy / dd;
+			dz = dz / dd;
+			mm = dd - (float) Math.abs((double) lbl_half);
+			dx *= mm;
+			dy *= mm;
+			dz *= mm;
+			segColors = new byte[clr_dim][2];
+			if (bb != null) {
+				for (int cc = 0; cc < clr_dim; cc++) {
+					System.arraycopy(bb[cc], s_pos, segColors[cc], d_pos, cnt);
+				}
+			}
+
+			VisADLineArray expSegRight = new VisADLineArray();
+			SampledSet.setGeometryArray(expSegRight, new float[][] {
+					{ vv[0][stop_break], vv[0][stop_break] + dx },
+					{ vv[1][stop_break], vv[1][stop_break] + dy },
+					{ vv[2][stop_break], vv[2][stop_break] + dz } }, clr_dim,
+					segColors);
+			VisADLineArray segRightAnchor = new VisADLineArray();
+			SampledSet.setGeometryArray(segRightAnchor, new float[][] {
+					{ vv[0][stop_break] }, { vv[1][stop_break] },
+					{ vv[2][stop_break] } }, clr_dim, null);
+
+			float[] segRightScaleInfo = new float[] { lbl_half, dd };
+
+			// ----- end expanding/contracting line segments
+
+			ContourLabelGeometry ctrLabel = new ContourLabelGeometry(label,
+					labelAnchor, expSegLeft, segLeftAnchor, segLeftScaleInfo,
+					expSegRight, segRightAnchor, segRightScaleInfo);
+			ctrLabel.isStyled = isDashed;
+			css.labels.add(ctrLabel);
+		} else { // no label
+			float[] lineCoords = new float[3 * (totalPts + 1)];
+			byte[] lineColors = new byte[clr_dim * (totalPts + 1)];
+
+			int lineClrCnt = 0;
+			int lineCnt = 0;
+			lineCoords[lineCnt++] = vv[0][0];
+			lineCoords[lineCnt++] = vv[1][0];
+			lineCoords[lineCnt++] = vv[2][0];
+			for (int cc = 0; cc < clr_dim; cc++) {
+				lineColors[lineClrCnt++] = bb[cc][0];
+			}
+
+			for (int t = 1; t < totalPts * 2; t += 2) {
+				lineCoords[lineCnt++] = vv[0][t];
+				lineCoords[lineCnt++] = vv[1][t];
+				lineCoords[lineCnt++] = vv[2][t];
+				for (int c = 0; c < clr_dim; c++) {
+					lineColors[lineClrCnt++] = bb[c][t];
+				}
+			}
+
+			VisADLineStripArray lineArray = new VisADLineStripArray();
+			lineArray.stripVertexCounts = new int[] { (totalPts) + 1 };
+			lineArray.vertexCount = lineArray.stripVertexCounts[0];
+			lineArray.coordinates = lineCoords;
+			if (lineColors.length > 0) {
+				lineArray.colors = lineColors;
+			}
+			if (totalPts >= 2) {
+				if (isDashed) {
+					css.cntrLinesStyled.add(lineArray);
+				} else {
+					css.cntrLines.add(lineArray);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get a line array using this instances cached indexes.
+	 * 
+	 * @param vx
+	 *            X values to apply cached indexes to.
+	 * @param vy
+	 *            Y values to apply cached indexes to.
+	 * @see {@link VisADLineArray}
+	 * @return
+	 */
+	
+	float[][] getLineArray(float[] vx, float[] vy) {
+		if (vx == null || vy == null) {
+			return null;
+		}
+		int[] idx_array = idxs.toArray();
+
+		float[] vvx = new float[idx_array.length];
+		float[] vvy = new float[vvx.length];
+
+		for (int ii = 0; ii < idx_array.length; ii++) {
+			vvx[ii] = vx[idx_array[ii]];
+			vvy[ii] = vy[idx_array[ii]];
+		}
+		return new float[][] { vvx, vvy };
+	}
+
+        /**
+         * Get a line array using this instances cached indexes from start to stop (0-based, inclusive).
+         * 
+         * @param vx
+         *            X values to apply cached indexes to.
+         * @param vy
+         *            Y values to apply cached indexes to.
+         * @param start
+         *            start pair index.
+         * @param stop
+         *            stop pair index.
+         * @see {@link VisADLineArray}
+         *
+         * @return
+         */
+        float[][] getLineArray(float[] vx, float[] vy, int start, int stop) {
+                if (vx == null || vy == null) {
+                        return null;
+                }
+                int[] idx_array = idxs.toArray(start, stop);
+
+                float[] vvx = new float[idx_array.length];
+                float[] vvy = new float[vvx.length];
+
+                for (int ii = 0; ii < idx_array.length; ii++) {
+                        vvx[ii] = vx[idx_array[ii]];
+                        vvy[ii] = vy[idx_array[ii]];
+                }
+                return new float[][] { vvx, vvy };
+        }
+
+
+	/**
+	 * Get line strip arrays for this strip.
+	 * 
+	 * @param vx
+	 *            X grid coords to apply cached indexes to.
+	 * @param vy
+	 *            Y grid coords to apply cached indexes to.
+	 * @return If this strip is has a label the first dim will be 2 arrays, one
+	 *         for before the label and one for after. Otherwise the first
+	 *         dimension will be a single array for the entire strip.
+	 */
+	
+	float[][][] getLineStripArrays(float[] vx, float[] vy) {
+
+		int[] idx_array = idxs.toArray();
+		int count = idx_array.length;
+
+		int lenBefore = start_break / 2 + 1;
+		int lenAfter = (count - stop_break + 1) / 2;
+
+		// if we're labeling and there are enough points before
+		// and after to make at least 1 line
+		if (isLabeled && (lenBefore >= 2 && lenAfter >= 2)) {
+			float[][] vvBefore = new float[2][lenBefore];
+
+			// the first point in the array
+			vvBefore[0][0] = vx[idx_array[0]];
+			vvBefore[1][0] = vy[idx_array[0]];
+
+			// every other point up to the start of the break
+			int kk = 1;
+			for (int ii = 1; ii < vvBefore[0].length; kk += 2, ii++) {
+				vvBefore[0][ii] = vx[idx_array[kk]];
+				vvBefore[1][ii] = vy[idx_array[kk]];
+			}
+
+			// skip to stop_break
+			kk += n_skip - 1;
+
+			float[][] vvAfter = new float[2][lenAfter];
+			vvAfter[0][0] = vx[idx_array[kk]];
+			vvAfter[1][0] = vy[idx_array[kk]];
+
+			// every other point to the end
+			kk++;
+			for (int ii = 1; ii < vvAfter[0].length; kk += 2, ii++) {
+				vvAfter[0][ii] = vx[idx_array[kk]];
+				vvAfter[1][ii] = vy[idx_array[kk]];
+			}
+
+			// return new float[][][]{vvAfter};
+			return new float[][][] { vvBefore, vvAfter };
+		}
+
+		// no label
+		float[][] vv = null;
+		if (count == 2) { // can't have less than 2 verticies
+			vv = new float[2][count];
+		} else {
+			vv = new float[2][count / 2 + 1];
+		}
+
+		vv[0][0] = vx[idx_array[0]];
+		vv[1][0] = vy[idx_array[0]];
+
+		for (int kk = 1, ii = 1; ii < vv[0].length; kk += 2, ii++) {
+			vv[0][ii] = vx[idx_array[kk]];
+			vv[1][ii] = vy[idx_array[kk]];
+		}
+
+		return new float[][][] { vv };
+	}
+
+	/**
+	 * Get the array of colors corresponding to cached indexes.
+	 * 
+	 * @param colors
+	 *            Line array formatted colors where the first dimension is the
+	 *            color dimension and the second the color values.
+	 * @see {@link VisADLineArray}
+	 * @return Array of colors in line array format.
+	 */
+	
+	byte[][] getColorArray(byte[][] colors) {
+		if (colors == null)
+			return null;
+		int clr_dim = colors.length;
+		int[] idx_array = idxs.toArray();
+		byte[][] new_colors = new byte[clr_dim][idx_array.length];
+
+		for (int ii = 0; ii < idx_array.length; ii++) {
+			for (int cc = 0; cc < clr_dim; cc++) {
+				new_colors[cc][ii] = colors[cc][idx_array[ii]];
+			}
+		}
+		return new_colors;
+	}
+
+        /**
+         * Get the array of colors corresponding to cached indexes from start pair to stop pair (0-based, inclusive).
+         * 
+         * @param colors
+         *            Line array formatted colors where the first dimension is the
+         *            color dimension and the second the color values.
+         * @see {@link VisADLineArray}
+         * @return Array of colors in line array format.
+         */
+        byte[][] getColorArray(byte[][] colors, int start, int stop) {
+                if (colors == null)
+                        return null;
+                int clr_dim = colors.length;
+                int[] idx_array = idxs.toArray(start, stop);
+                byte[][] new_colors = new byte[clr_dim][idx_array.length];
+
+                for (int ii = 0; ii < idx_array.length; ii++) {
+                        for (int cc = 0; cc < clr_dim; cc++) {
+                                new_colors[cc][ii] = colors[cc][idx_array[ii]];
+                        }
+                }
+                return new_colors;
+        }
+
+
+	/**
+	 * Get the array of colors corresponding to cached indexes.
+	 * 
+	 * @param colors
+	 *            Line array formatted colors where the first dimension is the
+	 *            color dimension and the second the color values.
+	 * @see {@link VisADStripLineArray}
+	 * @return Array of colors in line strip array format.
+	 */
+	
+	byte[][][] getColorStripArrays(byte[][] colors) {
+
+		int clrDim = colors.length;
+		int[] idx_array = idxs.toArray();
+		int count = idx_array.length;
+
+		int lenBefore = start_break / 2 + 1;
+		int lenAfter = (count - stop_break + 1) / 2;
+
+		if (isLabeled && (lenBefore >= 2 && lenAfter >= 2)) {
+			byte[][] colorsBefore = new byte[clrDim][start_break / 2 + 1];
+
+			// first point
+			for (int cc = 0; cc < clrDim; cc++) {
+				colorsBefore[cc][0] = colors[cc][idx_array[0]];
+			}
+
+			// every other redundant point
+			int kk = 1;
+			for (int ii = 1; ii < colorsBefore[0].length; kk += 2, ii++) {
+				for (int cc = 0; cc < clrDim; cc++) {
+					colorsBefore[cc][ii] = colors[cc][idx_array[kk]];
+				}
+			}
+
+			kk += n_skip - 1;
+
+			byte[][] colorsAfter = new byte[clrDim][(count - stop_break + 1) / 2];
+			for (int cc = 0; cc < clrDim; cc++) {
+				colorsAfter[cc][0] = colors[cc][idx_array[kk]];
+			}
+
+			// every other redundant point
+			kk++;
+			for (int ii = 1; ii < colorsAfter[0].length; kk += 2, ii++) {
+				for (int cc = 0; cc < clrDim; cc++) {
+					colorsAfter[cc][ii] = colors[cc][idx_array[kk]];
+				}
+			}
+
+			// return new byte[][][]{colorsAfter};
+			return new byte[][][] { colorsBefore, colorsAfter };
+		}
+
+		// no label
+		byte[][] bb = null;
+		if (count == 2) {
+			bb = new byte[clrDim][count];
+		} else {
+			bb = new byte[clrDim][count / 2 + 1];
+		}
+
+		for (int cc = 0; cc < clrDim; cc++) {
+			bb[cc][0] = colors[cc][idx_array[0]];
+		}
+
+		for (int ii = 1, kk = 1; kk < idx_array.length; kk += 2, ii++) {
+			for (int cc = 0; cc < clrDim; cc++) {
+				bb[cc][ii] = colors[cc][idx_array[kk]];
+			}
+		}
+
+		return new byte[][][] { bb };
+	}
+
+	boolean isLabeled() {
+		return isLabeled;
+	}
+
+	/**
+	 * 
+	 * @param c_strp
+	 * 
+	 * @return
+	 */
+	
+	void merge(ContourStrip that) {
+		if (this.lev_idx != that.lev_idx) {
+			System.out.println("Contour2D.ContourStrip.merge: !BIG ATTENTION!");
+		}
+
+		int[] thisLo = new int[2];
+		int[] thisHi = new int[2];
+		int[] thatLo = new int[2];
+		int[] thatHi = new int[2];
+
+		thisLo[0] = this.idxs.first.idx0;
+		thisLo[1] = this.idxs.first.idx1;
+		thisHi[0] = this.idxs.last.idx1;
+		thisHi[1] = this.idxs.last.idx0;
+
+		thatLo[0] = that.idxs.first.idx0;
+		thatLo[1] = that.idxs.first.idx1;
+		thatHi[0] = that.idxs.last.idx1;
+		thatHi[1] = that.idxs.last.idx0;
+
+		/*
+		 * THAT THIS H----------------------L L---------------------H
+		 */
+		if (((thisLo[0] == thatLo[0]) || (thisLo[0] == thatLo[1]))
+				|| ((thisLo[1] == thatLo[0]) || (thisLo[1] == thatLo[1]))) {
+
+			IndexPairList.Node n = that.idxs.first.next; // skip redundant point
+															// idxs
+			while (n != null) {
+				this.idxs.addFirst(n.idx1, n.idx0);
+				n = n.next;
+			}
+
+			/*
+			 * THAT THIS L----------------------H L---------------------H
+			 */
+		} else if (((thisLo[0] == thatHi[0]) || (thisLo[0] == thatHi[1]))
+				|| ((thisLo[1] == thatHi[0]) || (thisLo[1] == thatHi[1]))) {
+
+			this.idxs.first.prev = that.idxs.last.prev; // skip redundant point
+														// idxs
+			this.idxs.first.prev.next = this.idxs.first;
+			this.idxs.first = that.idxs.first;
+			this.idxs.numIndices = this.idxs.numIndices + that.idxs.numIndices
+					- 2;
+
+			/*
+			 * THIS THAT L----------------------H H---------------------L
+			 */
+		} else if (((thisHi[0] == thatHi[0]) || (thisHi[0] == thatHi[1]))
+				|| ((thisHi[1] == thatHi[0]) || (thisHi[1] == thatHi[1]))) {
+
+			IndexPairList.Node n = that.idxs.last.prev; // skip redundant point
+														// idxs
+			while (n != null) {
+				this.idxs.addLast(n.idx1, n.idx0);
+				n = n.prev;
+			}
+
+			/*
+			 * THIS THAT L----------------------H L---------------------H
+			 */
+		} else if (((thisHi[0] == thatLo[0]) || (thisHi[0] == thatLo[1]))
+				|| ((thisHi[1] == thatLo[0]) || (thisHi[1] == thatLo[1]))) {
+
+			this.idxs.last.next = that.idxs.first.next; // skip redundant point
+														// idxs
+			this.idxs.last.next.prev = this.idxs.last;
+			this.idxs.last = that.idxs.last;
+			this.idxs.numIndices = this.idxs.numIndices + that.idxs.numIndices
+					- 2;
+
+		}
+	}
+
+	/**
+	 * 
+	 * @return
+	 */
+	
+	public String toString() {
+		return "<" + this.getClass().getName() + "(" + idxs.first.idx0 + ","
+				+ idxs.first.idx1 + "), (" + idxs.last.idx0 + ","
+				+ idxs.first.idx1 + ")>";
+	}
+
+} //---------  ContourStrip ------------------/ 
+
+/**
+ * A double ended list for pairs of integers implemented as a doubly linked
+ * list.
+ */
+
+class IndexPairList {
+
+	/**
+	 * Node object of a pair of indices.
+	 */
+	static final class Node {
+		Node prev;
+		Node next;
+		final int idx0;
+		final int idx1;
+
+		Node(int idx0, int idx1) {
+			this.idx0 = idx0;
+			this.idx1 = idx1;
+		}
+	}
+
+	/**
+	 * Total number of indices which will always be the number of nodes divided
+	 * by 2.
+	 */
+	int numIndices = 0;
+	/** Last pair node in list. */
+	Node last;
+	/** First pair node in list. */
+	Node first;
+
+	/**
+	 * Create a node for the pair of indices and add to the beginning of this
+	 * list.
+	 * 
+	 * @param i0
+	 * @param i1
+	 */
+	
+	void addFirst(int i0, int i1) {
+		addFirst(new Node(i0, i1));
+	}
+
+	/**
+	 * Add a node the the beginning of this list.
+	 * 
+	 * @param n
+	 */
+	
+	private void addFirst(Node n) {
+		n.next = null;
+		n.prev = null;
+		if (numIndices == 0) {
+			first = n;
+			last = n;
+		} else {
+			first.prev = n;
+			n.next = first;
+			first = n;
+		}
+		numIndices += 2;
+	}
+
+	/**
+	 * Create a node for the pair of indices and add to the end of this list.
+	 * 
+	 * @param i0
+	 * @param i1
+	 */
+	
+	void addLast(int i0, int i1) {
+		addLast(new Node(i0, i1));
+	}
+
+	/**
+	 * Add the Node to the end of this list.
+	 * 
+	 * @param n
+	 */
+	
+	private void addLast(Node n) {
+		n.next = null;
+		n.prev = null;
+		if (numIndices == 0) {
+			last = n;
+			first = n;
+		} else {
+			last.next = n;
+			n.prev = last;
+			last = n;
+		}
+		numIndices += 2;
+	}
+
+	/**
+	 * Clear the list.
+	 * <p>
+	 * NOTE: We do not need to null out all the node objects because the garbage
+	 * collector is <u>supposed</u> to collect even cyclic references.
+	 */
+	
+	void clear() {
+		first = null;
+		last = null;
+		numIndices = 0;
+	}
+
+        int getNumIndices() {
+              return numIndices;
+        }
+
+	/**
+	 * Return array of this lists indices. Each nodes idx0 precedes it's idx1
+	 * with a total array length of <code>numIndices</code>.
+	 * 
+	 * @return
+	 */
+	
+	int[] toArray() {
+		int[] idxs = new int[numIndices];
+		int idx = 0;
+		Node n = first;
+		while (n != null) {
+			idxs[idx++] = n.idx0;
+			idxs[idx++] = n.idx1;
+			n = n.next;
+		}
+		return idxs;
+	}
+
+        /**
+         * Return array of this list's indices from start pair to stop pair index (0-based and inclusive). 
+         *
+         * @param start index of first pair.
+         * @param stop index of last pair.
+         * 
+         * @return array of length num of pairs (inclusive) times two.
+         */
+        int[] toArray(int start, int stop) {
+                int[] idxs = new int[(stop - start + 1)*2];
+                int pairIdx = 0;
+                Node n = first;
+                int cnt = 0;
+                while (pairIdx <= stop && n != null) {
+                     if (pairIdx >= start) {
+                        idxs[cnt++] = n.idx0;
+                        idxs[cnt++] = n.idx1;
+                     }
+                     n = n.next;
+                     pairIdx += 1;
+                }
+                return idxs;
+        }
+}
+
+
diff --git a/visad/ContourControl.java b/visad/ContourControl.java
new file mode 100644
index 0000000..ee7b1c2
--- /dev/null
+++ b/visad/ContourControl.java
@@ -0,0 +1,1239 @@
+//
+// ContourControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+import java.util.StringTokenizer;
+
+import visad.browser.Convert;
+import visad.util.Util;
+
+/**
+ * ContourControl is the VisAD class for controlling IsoContour display scalars.
+ */
+
+public class ContourControl extends Control {
+
+  private static final long serialVersionUID = 1L;
+  
+  // number below is from GUI control, it is approximately
+  // how many times a contour line will (attempt to) be labeled
+  public static final int LABEL_FREQ_LO = 1;
+  public static final int LABEL_FREQ_MED = 5;
+  public static final int LABEL_FREQ_HI = 9;
+  
+  // default and (somewhat arbitrary) values for labeling every Nth line
+  public static final int EVERY_NTH_MIN = 0;
+  public static final int EVERY_NTH_DEFAULT = 2;
+  public static final int EVERY_NTH_MAX = 100;
+  
+  private boolean mainContours;
+  // for 3-D mainContours
+  private float surfaceValue;
+  // for 2-D mainContours
+  // these are the 'old' descriptors for 2-D contour lines
+  private float contourInterval;
+  private float lowLimit;
+  private float hiLimit;
+  private float base;
+  private boolean labels;
+  private int labelFreq = LABEL_FREQ_LO;
+  private int everyNth = EVERY_NTH_DEFAULT;
+
+  private boolean public_set = false; // application called setLevels()
+
+  //
+  // these are the 'new' descriptors for 2-D contour lines
+  // includes lowLimit, hiLimit and base from the 'old' descriptors
+  // true if contourInterval is valid
+  private boolean arithmeticProgression = true;
+  // contour line levels
+  private float[] levels = null;
+  private int lineStyle = GraphicsModeControl.SOLID_STYLE;
+  private boolean dash = false;
+
+  private boolean horizontalContourSlice;
+  private boolean verticalContourSlice;
+
+  private float horizontalSliceLow;
+  private float horizontalSliceHi;
+  private float horizontalSliceStep;
+  private float verticalSliceLow;
+  private float verticalSliceHi;
+  private float verticalSliceStep;
+
+  boolean contourFill;
+
+  private static double init_scale = Double.NaN;
+  private static double default_init_scale = 0.40;
+  private boolean autoSizeLabels = true; // BMF 2009-03-05 change default to true
+  private boolean alignLabels = true;
+  private double labelSizeFactor = 1;
+  private transient ZoomDoneListener zoom;
+  private ProjectionControl pcntrl;
+  private ControlListener projListener;
+  private double ratio = 1.20;
+  
+  // label color defaults to white
+  private byte[] labelColor = null;
+  private boolean colorSet = false;
+
+  private Object labelFont = null;
+  //private Object labelFont = new java.awt.Font("Dialog", java.awt.Font.PLAIN, 14);
+
+  
+  /**
+   * Construct a new ContourControl for the display
+   * @param d    Display to associate with this
+   */
+  public ContourControl(DisplayImpl d) {
+    super(d);
+    mainContours = true;
+    labels = false;
+    surfaceValue = Float.NaN;
+    contourInterval = Float.NaN;
+    lowLimit = Float.NaN;
+    hiLimit = Float.NaN;
+    base = Float.NaN;
+
+    horizontalContourSlice = false;
+    verticalContourSlice = false;
+
+    horizontalSliceLow = Float.NaN;
+    horizontalSliceHi = Float.NaN;
+    horizontalSliceStep = Float.NaN;
+    verticalSliceLow = Float.NaN;
+    verticalSliceHi = Float.NaN;
+    verticalSliceStep = Float.NaN;
+
+    contourFill = false;
+
+    pcntrl = d.getProjectionControl();
+    double[] matrix          = pcntrl.getMatrix();
+    double[] rot             = new double[3];
+    double[] trans           = new double[3];
+    double[] scale           = new double[1];
+    MouseBehavior mouse      = d.getMouseBehavior();
+
+    double projScale;
+    if (mouse != null) {
+      mouse.instance_unmake_matrix(rot, scale, trans, matrix);
+      if (init_scale != init_scale) init_scale = scale[0];
+      projScale = scale[0];
+    }
+    else {
+      init_scale = default_init_scale;
+      projScale = init_scale;
+    }
+
+    zoom = new ZoomDoneListener(this, pcntrl, mouse, projScale);
+    d.addDisplayListener(zoom);
+  }
+
+  /**
+   * set parameters for IsoContour depictions, if not already set
+   * @param  bvalues   must be dimensioned boolean[2], where
+   *         bvalues[0]  enable contours
+   *         bvalues[1]  enable labels (if applicable)
+   * @param  fvalues   must be dimensioned float[5], where
+   *         fvalues[0]  level for iso-surface
+   *         fvalues[1]  interval for iso-lines
+   *         fvalues[2]  low limit for iso-lines
+   *         fvalues[3]  high limit for iso-lines
+   *         fvalues[4]  base for iso-lines
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  void setMainContours(boolean[] bvalues, float[] fvalues)
+         throws VisADException, RemoteException {
+    setMainContours(bvalues, fvalues, false, false);
+  }
+
+  /**
+   * set parameters for IsoContour depictions, if not already set
+   * @param  bvalues   must be dimensioned boolean[2], where
+   *         bvalues[0]  enable contours
+   *         bvalues[1]  enable labels (if applicable)
+   * @param  fvalues   must be dimensioned float[5], where
+   *         fvalues[0]  level for iso-surface
+   *         fvalues[1]  interval for iso-lines
+   *         fvalues[2]  low limit for iso-lines
+   *         fvalues[3]  high limit for iso-lines
+   *         fvalues[4]  base for iso-lines
+   * @param  noChange  true to not trigger re-transform (false for
+   *                   auto-scale)
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  void setMainContours(boolean[] bvalues, float[] fvalues, boolean noChange)
+         throws VisADException, RemoteException {
+    setMainContours(bvalues, fvalues, noChange, false);
+  }
+
+  /**
+   * set parameters for IsoContour depictions
+   * @param  bvalues   must be dimensioned boolean[2], where
+   *         bvalues[0]  enable contours
+   *         bvalues[1]  enable labels (if applicable)
+   * @param  fvalues   must be dimensioned float[5], where
+   *         fvalues[0]  level for iso-surface
+   *         fvalues[1]  interval for iso-lines
+   *         fvalues[2]  low limit for iso-lines
+   *         fvalues[3]  high limit for iso-lines
+   *         fvalues[4]  base for iso-lines
+   * @param  noChange  true to not trigger re-transform (false for
+   *                   auto-scale)
+   * @param  override  true to set float values even if already set
+   *                   (i.e., not NaNs)
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  void setMainContours(boolean[] bvalues, float[] fvalues, boolean noChange,
+                       boolean override)
+         throws VisADException, RemoteException {
+    if (fvalues == null || fvalues.length != 5 ||
+        bvalues == null || bvalues.length != 2) {
+      throw new DisplayException("ContourControl.setMainContours: " +
+                                 "bad array length");
+    }
+    boolean setLevels = false;
+    float[] levs = null;
+    boolean[] dashes = null;
+    float myBase = 0;
+    synchronized(this) {
+      mainContours = bvalues[0];
+      labels = bvalues[1];
+
+      // WLH 13 Sept 2000
+      if (override) {
+        surfaceValue = fvalues[0];
+        contourInterval = fvalues[1];
+        lowLimit = fvalues[2];
+        hiLimit = fvalues[3];
+        base = fvalues[4];
+      }
+      else {
+        if (surfaceValue != surfaceValue) surfaceValue = fvalues[0];
+        if (contourInterval != contourInterval) contourInterval = fvalues[1];
+        if (lowLimit != lowLimit) lowLimit = fvalues[2];
+        if (hiLimit != hiLimit) hiLimit = fvalues[3];
+        if (base != base) base = fvalues[4];
+      }
+      // adapt to 'new' descriptors
+      if (arithmeticProgression) {
+        if (contourInterval == contourInterval && base == base &&
+            lowLimit == lowLimit && hiLimit == hiLimit) {
+          dashes = new boolean[] {false};
+          levs =
+            Contour2D.intervalToLevels(contourInterval, lowLimit, hiLimit, base, dashes);
+          myBase = base;
+          setLevels = true;
+        }
+        else {
+          dash = false;
+          levels = null;
+        }
+      }
+    }
+
+    /**
+     * The following methods are "alien" because they are outside the control of
+     * this class.  If they were invoked in a synchronized block, then deadlock
+     * could occur if one of the methods waits on a thread that it creates that
+     * calls back into this class and tries to obtain a lock (it's happened
+     * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's
+     * "Effective Java" for more information.
+     */
+    if (setLevels)
+      setLevels(levs, myBase, dashes[0], false);
+
+    changeControl(!noChange);
+  }
+
+  /** 
+   * Set level for iso-surfaces 
+   * @param value   value of the iso-surface to display
+   * @throws VisADException     VisAD error
+   * @throws RemoteException    Java RMI failure.
+   */
+  public void setSurfaceValue(float value)
+         throws VisADException, RemoteException {
+    setSurfaceValue(value, false);
+  }
+
+  /** 
+   * Set level for iso-surfaces 
+   * @param value   value of the iso-surface to display
+   * @param setLevels  true if this should be used for contour levels
+   * @throws VisADException     VisAD error
+   * @throws RemoteException    Java RMI failure.
+   */
+  public void setSurfaceValue(float value, boolean setLevels)
+         throws VisADException, RemoteException {
+    boolean change;
+    synchronized(this) {
+      change = !Util.isApproximatelyEqual(surfaceValue, value);
+      surfaceValue = value;
+      if (setLevels) levels = new float[] {value};
+    }
+    if (change) {
+      /**
+       * The following method is "alien" because it is outside the control of
+       * this class.  If it were invoked in a synchronized block, then deadlock
+       * could occur if the method waits on a thread that it creates that
+       * calls back into this class and tries to obtain a lock (it's happened
+       * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua
+       * Bloch's "Effective Java" for more information.
+       */
+      changeControl(true);
+    }
+  }
+
+  /**
+   * Sets the parameters for contour iso-lines.  This method invokes the
+   * {@link ControlListener#controlChanged(ControlEvent)} method of all
+   * registered listeners;
+   *
+   * @param interval            The contour interval.  Must be non-zero.  If
+   *                            negative, then contour lines below the base will
+   *                            be dashed.  Must not be NaN.
+   * @param low                 The minimum contour value.  No contour line less
+   *                            than this value will be drawn.  Must not be NaN.
+   * @param hi                  The maximum contour value.  No contour line
+   *                            greater than this value will be drawn.  Must not
+   *                            be NaN.
+   * @param ba                  The base contour value.  The contour lines will
+   *                            be integer multiples of the interval away from
+   *                            this value.  Must not be NaN.
+   * @throws VisADException     The interval is zero or too small.
+   * @throws RemoteException    Java RMI failure.
+   */
+  public void setContourInterval(float interval, float low,
+                                 float hi, float ba)
+         throws VisADException, RemoteException {
+    public_set = true;
+    float[] levs;
+    float myBase;
+    boolean[] dashes = {false};
+    boolean change;
+    synchronized(this) {
+      change = (contourInterval != interval) || (base != ba) ||
+               !Util.isApproximatelyEqual(lowLimit, low) ||
+               !Util.isApproximatelyEqual(hiLimit, hi);
+      contourInterval = interval;
+      lowLimit = low;
+      hiLimit = hi;
+      myBase = base = ba;
+
+      // adapt to 'new' descriptors
+      levs =
+        Contour2D.intervalToLevels(contourInterval, lowLimit, hiLimit, base, dashes);
+      arithmeticProgression = true;
+    }
+    /**
+     * The following method is "alien" because it is outside the control of this
+     * class.  If it were invoked in a synchronized block, then deadlock could
+     * occur if the method waits on a thread that it creates that calls back
+     * into this class and tries to obtain a lock (it's happened before).  See
+     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
+     * Java" for more information.
+     */
+    setLevels(levs, myBase, dashes[0], change);
+  }
+
+  /** 
+   * Set low and high iso-line levels 
+   * @param low                 The minimum contour value.  No contour line less
+   *                            than this value will be drawn.  Must not be NaN.
+   * @param hi                  The maximum contour value.  No contour line
+   *                            greater than this value will be drawn.  Must not
+   *                            be NaN.
+   * @throws VisADException     VisAD error
+   * @throws RemoteException    Java RMI failure.
+   */
+  public void setContourLimits(float low, float hi)
+         throws VisADException, RemoteException {
+    public_set = true;
+    boolean change;
+    boolean setLevels;
+    float[] levs = null;
+    float myBase = 0;
+    boolean[] dashes = null;
+    synchronized(this) {
+      change = !Util.isApproximatelyEqual(lowLimit, low) ||
+               !Util.isApproximatelyEqual(hiLimit, hi);
+      lowLimit = low;
+      hiLimit = hi;
+      // adapt to 'new' descriptors
+      if (arithmeticProgression) {
+        setLevels = true;
+        dashes = new boolean[] {false};
+        levs =
+          Contour2D.intervalToLevels(contourInterval, lowLimit, hiLimit, base, dashes);
+        myBase = base;
+      }
+      else {
+        setLevels = false;
+        int n = 0;
+        for (int i=0; i<levels.length; i++) {
+          if (lowLimit < levels[i] && levels[i] < hiLimit) n++;
+        }
+        if (n != levels.length) {
+          levs = new float[n];
+          int k = 0;
+          for (int i=0; i<levels.length; i++) {
+            if (lowLimit < levels[i] && levels[i] < hiLimit) levs[k++] = levels[i];
+          }
+          levels = levs;
+        }
+        else {
+          change = false;
+        }
+      }
+    }
+
+    /**
+     * The following methods are "alien" because they are outside the control of
+     * this class.  If they were invoked in a synchronized block, then deadlock
+     * could occur if one of the methods waits on a thread that it creates that
+     * calls back into this class and tries to obtain a lock (it's happened
+     * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's
+     * "Effective Java" for more information.
+     */
+    if (setLevels)
+      setLevels(levs, myBase, dashes[0], false);
+
+    if (change) changeControl(true);
+  }
+
+  /**
+   * @return boolean indicating whether levels have
+   *                 been set by other than auto-scale
+   */
+  public boolean getPublicSet() {
+    return public_set;
+  }
+
+  /** 
+   * Set arbitrary levels for 2-D contour lines;
+   * levels below base are dashed if dash == true 
+   * @param levels              An array of contour values to display.
+   * @param base                The base contour value for dashing.  Levels
+   *                            below base are dashed if dash is true
+   * @param dash                flag for making dashed contours below the
+   *                            base contour value.
+   * @throws VisADException     VisAD error
+   * @throws RemoteException    Java RMI failure.
+   */
+  public void setLevels(float[] levels, float base, boolean dash)
+         throws VisADException, RemoteException {
+    public_set = true;
+    setLevels(levels, base, dash, true);
+  }
+
+  private void setLevels(float[] levs, float ba, boolean da,
+                         boolean by_user)
+          throws VisADException, RemoteException {
+    if (levs == null) return;
+    float[] newLevels = new float[levs.length];
+    float min = Float.MAX_VALUE;
+    float max = -Float.MAX_VALUE;
+    for (int i=0; i<levs.length; i++) {
+      if (levs[i] < min) min = levs[i];
+      if (levs[i] > max) max = levs[i];
+      newLevels[i] = levs[i];
+    }
+    synchronized(this) {
+      levels = newLevels;
+      dash = da;
+      base = ba;
+      if (by_user) {
+        lowLimit = min - Math.abs(.01f * min);  // DRM 25-APR-2001
+        hiLimit  = max + Math.abs(.01f * max);  // DRM 25-APR-2001
+      }
+    }
+    if (by_user) {
+      /**
+       * The following method is "alien" because it is outside the control of
+       * this class.  If it were invoked in a synchronized block, then deadlock
+       * could occur if the method waits on a thread that it creates that
+       * calls back into this class and tries to obtain a lock (it's happened
+       * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua
+       * Bloch's "Effective Java" for more information.
+       */
+      changeControl(true);
+    }
+  }
+
+  /** get 'new' descriptors for 2-D contour lines
+   * @param lowhibase   must be dimensioned float[3], where
+   *        lowhibase[0]  used to return low limit
+   *        lowhibase[1]  used to return high limit
+   *        lowhibase[2]  used to return base
+   * @param dashes      must be dimensioned boolean[1], where
+   *        dashed[0]     used to return dash enable
+   * @return float[] array of levels for contour curves
+   */
+  public synchronized float[] getLevels(float[] lowhibase, boolean[] dashes) {
+    float[] levs = null;
+    if (levels != null) {
+      levs = new float[levels.length];
+      System.arraycopy(levels, 0, levs, 0, levels.length);
+    }
+    lowhibase[0] = lowLimit;
+    lowhibase[1] = hiLimit;
+    lowhibase[2] = base;
+    dashes[0] = dash;
+    return levs;
+  }
+
+  /**
+   * set label enable
+   * @param on  new value for label enable
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public void enableLabels(boolean on)
+         throws VisADException, RemoteException {
+    boolean change;
+    synchronized(this) {
+      change = (labels != on);
+      labels = on;
+    }
+    /**
+     * The following method is "alien" because it is outside the control of this
+     * class.  If it were invoked in a synchronized block, then deadlock could
+     * occur if the method waits on a thread that it creates that calls back
+     * into this class and tries to obtain a lock (it's happened before).  See
+     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
+     * Java" for more information.
+     */
+    if (change) changeControl(true);
+  }
+
+  /** 
+   * set contour enable 
+   * @param on  new value for contour enable
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public void enableContours(boolean on)
+         throws VisADException, RemoteException {
+    boolean change;
+    synchronized(this) {
+      change = (mainContours != on);
+      mainContours = on;
+    }
+    /**
+     * The following method is "alien" because it is outside the control of this
+     * class.  If it were invoked in a synchronized block, then deadlock could
+     * occur if the method waits on a thread that it creates that calls back
+     * into this class and tries to obtain a lock (it's happened before).  See
+     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
+     * Java" for more information.
+     */
+    if (change) changeControl(true);
+  }
+
+  /**
+   * get parameters for IsoContour depictions
+   * @param  bvalues   must be dimensioned boolean[2], where
+   *         bvalues[0]  used to return contour enable
+   *         bvalues[1]  used to return label enable
+   * @param  fvalues   must be dimensioned float[5], where
+   *         fvalues[0]  used to return level for iso-surface
+   *         fvalues[1]  used to return interval for iso-lines
+   *         fvalues[2]  used to return low limit for iso-lines
+   *         fvalues[3]  used to return high limit for iso-lines
+   *         fvalues[4]  used to return base for iso-lines
+   * @throws VisADException  a VisAD error occurred
+   */
+  public void getMainContours(boolean[] bvalues, float[] fvalues)
+         throws VisADException {
+    if (fvalues == null || fvalues.length != 5 ||
+        bvalues == null || bvalues.length != 2) {
+      throw new DisplayException("ContourControl.getMainContours: " +
+                                 "bad array length");
+    }
+    synchronized(this) {
+      bvalues[0] = mainContours;
+      bvalues[1] = labels;
+      fvalues[0] = surfaceValue;
+      fvalues[1] = contourInterval;
+      fvalues[2] = lowLimit;
+      fvalues[3] = hiLimit;
+      fvalues[4] = base;
+    }
+  }
+
+  public void setContourFill(boolean flag)
+         throws VisADException, RemoteException {
+    
+    if (flag) {
+      if (!colorSet)
+        labelColor = new byte[]{(byte)255, (byte)255, (byte)255, (byte)255};
+    
+    } else {
+      if (!colorSet)
+        labelColor = null;
+    }
+        
+    synchronized(this) {
+      contourFill = flag;
+    }
+    /**
+     * The following method is "alien" because it is outside the control of this
+     * class.  If it were invoked in a synchronized block, then deadlock could
+     * occur if the method waits on a thread that it creates that calls back
+     * into this class and tries to obtain a lock (it's happened before).  See
+     * Item 49 (Avoid Excessive Synchronization) in Joshua Bloch's "Effective
+     * Java" for more information.
+     */
+    changeControl(true);
+  }
+
+  /**
+   * @return contourFill enable
+   */
+  public synchronized boolean contourFilled() {
+    return contourFill;
+  }
+
+  /**
+   * @return initial scale for label auto-size
+   */
+  public static double getInitScale() {
+    return init_scale;
+  }
+
+  /**
+   * set enable for label auto-size
+   * @param flag  new value for label auto-size enable
+   */
+  public void setAutoScaleLabels(boolean flag) {
+    synchronized(this) {
+      autoSizeLabels = flag;
+    }
+  }
+
+  /**
+   * @return label auto-size enable
+   */
+  public boolean getAutoSizeLabels() {
+    return autoSizeLabels;
+  }
+
+  /**
+   * Set the contour label alignment policy
+   * @param flag true: labels follow the contour line, false: labels are horizontal, default: true
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public void setAlignLabels(boolean flag) throws RemoteException, VisADException {
+    synchronized(this) {
+      alignLabels = flag;
+    }
+    changeControl(true);
+  }
+
+  public boolean getAlignLabels() {
+    return alignLabels;
+  }
+  
+  //BMF 2006-10-04
+  /** 
+   * Sets the color for label.
+   * @param color RGB color array, if null label takes contour color
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public void setLabelColor(byte[] color) throws RemoteException, VisADException {
+    setLabelColor(color, true);
+  }
+
+  //BMF 2006-10-04
+  /**
+   * Sets the label color. 
+   * @param color RGB color array, if null label takes contour color
+   * @param change If false, no {@link visad.ControlEvent} is fired.
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  public void setLabelColor(byte[] color, boolean change) throws RemoteException, VisADException {
+    labelColor = color;
+    colorSet = true;
+    changeControl(change);
+  }
+  
+  //BMF 2006-10-04
+  /**
+   * Gets the label color.
+   * @return label color as RGB array, null if not set
+   */
+  public byte[] getLabelColor() {
+    return labelColor;
+  }
+
+  /**
+   * Set the contour label Font
+   * @param font can be java.awt.Font or visad.util.HersheyFont.  The former are rendered as
+   *             filled polygons which can exhibit co-planar artifacts, the latter as lines
+   *             which will always be visible, front or back, on co-planar surfaces.  Can be
+   *             <code>null</code>, the default,  which results in the Times Roman HersheyFont. 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  
+  public void setLabelFont(Object font) throws RemoteException, VisADException {
+    synchronized(this) {
+      labelFont = font;
+    }
+    changeControl(true);
+  }
+
+  /**
+   * Get contour label font
+   * @return label font
+   */
+  
+  public Object getLabelFont() {
+    return labelFont;
+  }
+
+  /**
+   * set label frequency 
+   * @param freq  how many labels to attempt per line
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  
+  public void setLabelFreq(int freq)
+  throws VisADException, RemoteException {
+	  synchronized (this) {
+		  // bounds check the setter value, if out of range set to default
+		  if ((labelFreq < LABEL_FREQ_LO) || (labelFreq > LABEL_FREQ_HI)) {
+			  labelFreq = LABEL_FREQ_LO;
+		  } else {
+			  labelFreq = freq;
+		  }
+	  }
+	  changeControl(true);
+  }
+
+  /**
+   * @return label frequency (number per line)
+   */
+  
+  public int getLabelFreq() {
+    return labelFreq;
+  }
+  
+  /**
+   * @return the everyNth
+   */
+  
+  public int getEveryNth() {
+	  return everyNth;
+  }
+
+  /**
+   * Set how often we label lines 
+   * @param lineCount  how many lines to skip before next labeled line
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  
+  public void setEveryNth(int lineCount) throws VisADException, RemoteException {
+	  synchronized (this) {
+		  // bounds check the setter value, if out of range set to bounds
+		  if (lineCount < 1) {
+			  everyNth = EVERY_NTH_DEFAULT;
+		  } else if (lineCount > EVERY_NTH_MAX) {
+			  everyNth = EVERY_NTH_MAX;
+		  } else {
+			  everyNth = lineCount;
+		  }
+	  }
+	  changeControl(true);
+  }
+
+/**
+   * set size for label auto-size
+   * @param factor  new size for label auto-size
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  
+  public void setLabelSize(double factor)
+  throws VisADException, RemoteException {
+	  synchronized (this) {
+		  labelSizeFactor = factor;
+	  }
+	  changeControl(true);
+  }
+
+  /**
+   * @return size for label auto-size
+   */
+  public double getLabelSize() {
+    return labelSizeFactor;
+  }
+  
+  /**
+   * Get the line style for lines that are styled.
+   * @return One of the line style constants from <code>GraphicsModeControl</code>.
+   */
+  public int getDashedStyle() {
+	  return lineStyle;
+  }
+  
+  /**
+   * Set the line style to apply to dashed lines.
+   * @param style One of the line style constants from <code>GraphicsModeControl</code>.
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  public void setDashedStyle(int style) 
+  	throws RemoteException, VisADException {
+  	lineStyle = style;
+  	changeControl(true);
+  }
+
+  /**
+   * @return String representation of this ContourControl
+   */
+  public synchronized String getSaveString() {
+    return mainContours + " " + labels + " " + surfaceValue + " " +
+      contourInterval + " " + lowLimit + " " + hiLimit + " " + base;
+  }
+
+  /**
+   * reconstruct this ContourControl using the specified save string
+   * @param save - String representation for reconstruction
+   * @throws VisADException if a VisAD error occurs
+   * @throws RemoteException if an RMI error occurs
+   */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save);
+    if (st.countTokens() < 7) throw new VisADException("Invalid save string");
+    boolean[] b = new boolean[2];
+    float[] f = new float[5];
+    for (int i=0; i<2; i++) b[i] = Convert.getBoolean(st.nextToken());
+    for (int i=0; i<5; i++) f[i] = Convert.getFloat(st.nextToken());
+    setMainContours(b, f, false, true);
+  }
+
+  /**
+   * remove previous projListener from pcntrl, and save cl as
+   * new projListener
+   * @param cl  new ControlListener for projListener
+   * @param pcntrl ProjectionControl
+   */
+  public void addProjectionControlListener(ControlListener cl,
+                                           ProjectionControl pcntrl)
+  {
+    pcntrl.removeControlListener(projListener);
+    projListener = cl;
+  }
+
+  /**
+   * copy the state of a remote control to this control
+   * @param rmt remote Control whose state is copied
+   * @throws VisADException if a VisAD error occurs
+   */
+  public void syncControl(Control rmt)
+        throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof ContourControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    ContourControl cc = (ContourControl )rmt;
+
+    boolean changed = false;
+
+    synchronized(this) {
+      synchronized(cc) {
+        if (mainContours != cc.mainContours) {
+          changed = true;
+          mainContours = cc.mainContours;
+        }
+        if (!Util.isApproximatelyEqual(surfaceValue, cc.surfaceValue)) {
+          changed = true;
+          surfaceValue = cc.surfaceValue;
+        }
+
+        if (!Util.isApproximatelyEqual(contourInterval, cc.contourInterval)) {
+          changed = true;
+          contourInterval = cc.contourInterval;
+        }
+        if (!Util.isApproximatelyEqual(lowLimit, cc.lowLimit)) {
+          changed = true;
+          lowLimit = cc.lowLimit;
+        }
+        if (!Util.isApproximatelyEqual(hiLimit, cc.hiLimit)) {
+          changed = true;
+          hiLimit = cc.hiLimit;
+        }
+        if (!Util.isApproximatelyEqual(base, cc.base)) {
+          changed = true;
+          base = cc.base;
+        }
+
+        if (labels != cc.labels) {
+          changed = true;
+          labels = cc.labels;
+        }
+        if (arithmeticProgression != cc.arithmeticProgression) {
+          changed = true;
+          arithmeticProgression = cc.arithmeticProgression;
+        }
+
+        if (cc.levels == null) {
+          if (levels != null) {
+            changed = true;
+            levels = null;
+          }
+        } else {
+          // make sure array lengths match
+          if (levels == null || levels.length != cc.levels.length) {
+            changed = true;
+            levels = new float[cc.levels.length];
+            for (int i = 0; i < levels.length; i++) {
+              levels[i] = 0;
+            }
+          }
+          // copy remote values
+          for (int i = 0; i < levels.length; i++) {
+            if (!Util.isApproximatelyEqual(levels[i], cc.levels[i])) {
+              changed = true;
+              levels[i] = cc.levels[i];
+            }
+          }
+        }
+
+        if (dash != cc.dash) {
+          changed = true;
+          dash = cc.dash;
+        }
+
+        if (horizontalContourSlice != cc.horizontalContourSlice) {
+          changed = true;
+          horizontalContourSlice = cc.horizontalContourSlice;
+        }
+        if (verticalContourSlice != cc.verticalContourSlice) {
+          changed = true;
+          verticalContourSlice = cc.verticalContourSlice;
+        }
+
+        if (!Util.isApproximatelyEqual(horizontalSliceLow,
+                                       cc.horizontalSliceLow))
+        {
+          changed = true;
+          horizontalSliceLow = cc.horizontalSliceLow;
+        }
+        if (!Util.isApproximatelyEqual(horizontalSliceHi, cc.horizontalSliceHi)) {
+          changed = true;
+          horizontalSliceHi = cc.horizontalSliceHi;
+        }
+        if (!Util.isApproximatelyEqual(horizontalSliceStep,
+                                       cc.horizontalSliceStep))
+        {
+          changed = true;
+          horizontalSliceStep = cc.horizontalSliceStep;
+        }
+        if (!Util.isApproximatelyEqual(verticalSliceLow, cc.verticalSliceLow)) {
+          changed = true;
+          verticalSliceLow = cc.verticalSliceLow;
+        }
+        if (!Util.isApproximatelyEqual(verticalSliceHi, cc.verticalSliceHi)) {
+          changed = true;
+          verticalSliceHi = cc.verticalSliceHi;
+        }
+        if (!Util.isApproximatelyEqual(verticalSliceStep, cc.verticalSliceStep)) {
+          changed = true;
+          verticalSliceStep = cc.verticalSliceStep;
+        }
+
+        if (contourFill != cc.contourFill) {
+          changed = true;
+          contourFill = cc.contourFill;
+        }
+
+        if (autoSizeLabels != cc.autoSizeLabels) {
+          changed = true;
+          autoSizeLabels = cc.autoSizeLabels;
+        }
+
+        if (labelSizeFactor != cc.labelSizeFactor) {
+          changed = true;
+          labelSizeFactor = cc.labelSizeFactor;
+        }
+
+        if (alignLabels != cc.alignLabels) {
+          changed = true;
+          alignLabels = cc.alignLabels;
+        }
+
+        if (labelFont != cc.labelFont) {
+          changed = true;
+          labelFont = cc.labelFont;
+        }
+
+      }
+    }
+
+    if (changed) {
+      try {
+      /**
+       * The following method is "alien" because it is outside the control of
+       * this class.  If it were invoked in a synchronized block, then deadlock
+       * could occur if the method waits on a thread that it creates that
+       * calls back into this class and tries to obtain a lock (it's happened
+       * before).  See Item 49 (Avoid Excessive Synchronization) in Joshua
+       * Bloch's "Effective Java" for more information.
+       */
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  /**
+   * Indicates whether or not this instance equals an Object
+   * @param o  an Object
+   * @return true if and only if this instance is equal to o
+   */
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    ContourControl cc = (ContourControl )o;
+
+    synchronized(this) {
+      synchronized(cc) {
+        if (mainContours != cc.mainContours) {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(surfaceValue, cc.surfaceValue)) {
+          return false;
+        }
+
+        if (!Util.isApproximatelyEqual(contourInterval, cc.contourInterval)) {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(lowLimit, cc.lowLimit)) {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(hiLimit, cc.hiLimit)) {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(base, cc.base)) {
+          return false;
+        }
+
+        if (labels != cc.labels) {
+          return false;
+        }
+        if (arithmeticProgression != cc.arithmeticProgression) {
+          return false;
+        }
+
+        if (levels == null) {
+          if (cc.levels != null) {
+            return false;
+          }
+        } else {
+          // make sure array lengths match
+          if (cc.levels == null || levels.length != cc.levels.length) {
+            return false;
+          }
+          // copy remote values
+          for (int i = 0; i < levels.length; i++) {
+            if (!Util.isApproximatelyEqual(levels[i], cc.levels[i])) {
+              return false;
+            }
+          }
+        }
+
+        if (dash != cc.dash) {
+          return false;
+        }
+
+        if (horizontalContourSlice != cc.horizontalContourSlice) {
+          return false;
+        }
+        if (verticalContourSlice != cc.verticalContourSlice) {
+          return false;
+        }
+
+        if (!Util.isApproximatelyEqual(horizontalSliceLow,
+                                       cc.horizontalSliceLow))
+        {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(horizontalSliceHi, cc.horizontalSliceHi)) {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(horizontalSliceStep,
+                                       cc.horizontalSliceStep))
+        {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(verticalSliceLow, cc.verticalSliceLow)) {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(verticalSliceHi, cc.verticalSliceHi)) {
+          return false;
+        }
+        if (!Util.isApproximatelyEqual(verticalSliceStep, cc.verticalSliceStep)) {
+          return false;
+        }
+
+        if (contourFill != cc.contourFill) {
+          return false;
+        }
+
+        if (autoSizeLabels != cc.autoSizeLabels) {
+          return false;
+        }
+
+        if (alignLabels != cc.alignLabels) {
+          return false;
+        }
+
+        if (labelFont != cc.labelFont) {
+          return false;
+        }
+
+        if (!Util.isApproximatelyEqual(labelSizeFactor, cc.labelSizeFactor)) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * @return a copy of this ContourControl
+   */
+  public synchronized Object clone()
+  {
+    ContourControl cc = (ContourControl )super.clone();
+    if (levels != null) {
+      cc.levels = (float[] )levels.clone();
+    }
+    cc.lineStyle = lineStyle;
+
+    return cc;
+  }
+
+  //BMF 2006-10-04 added contourFill condition
+  /**
+   * If zoom scale has changed sufficiently, re-transform in
+   * order to recompute labels. 
+   * 
+   * No action is taken if {@link #setContourFill(boolean)} has been set.
+   *  
+   * @throws VisADException if a VisAD error occurs
+   * @throws RemoteException if an RMI error occurs
+   */
+  public void reLabel() throws VisADException, RemoteException {   
+    if (zoom == null || contourFill) return;
+    zoom.reLabel(ratio);
+  }
+
+  class ZoomDoneListener implements DisplayListener
+  {
+    ContourControl    c_cntrl;
+    ProjectionControl p_cntrl;
+    MouseBehavior     mouse;
+    double            last_scale;
+
+    ZoomDoneListener(ContourControl c_cntrl, ProjectionControl p_cntrl,
+                      MouseBehavior mouse, double scale) {
+      this.c_cntrl = c_cntrl;
+      this.p_cntrl = p_cntrl;
+      this.mouse   = mouse;
+      last_scale   = scale;
+    }
+
+    public void displayChanged(DisplayEvent de)
+           throws VisADException, RemoteException
+    {
+      if (de.getId() == DisplayEvent.MOUSE_RELEASED_LEFT ||
+          de.getId() == DisplayEvent.MOUSE_RELEASED_RIGHT) {
+        reLabel(ratio);
+      }
+    }
+     
+    public void reLabel(double ratio) 
+           throws VisADException, RemoteException {
+      if (!c_cntrl.contourFilled() && autoSizeLabels) {
+        double[] matrix        = p_cntrl.getMatrix();
+        double[] rot           = new double[3];
+        double[] trans         = new double[3];
+        double[] scale         = new double[1];
+        mouse.instance_unmake_matrix(rot, scale, trans, matrix);
+        if (scale[0]/last_scale > ratio ||
+            scale[0]/last_scale < 1/ratio) { //- re-label
+          if (labels) c_cntrl.changeControl(true);
+          last_scale = scale[0];
+        }
+      }
+    }
+  }
+
+
+  /**
+   * End this control (called by ScalarMap.nullDisplay()). Override
+   * to remove zoom control listener.
+   */
+  public void nullControl() {
+    if (projListener != null) {
+      pcntrl.removeControlListener(projListener);
+    }
+    getDisplay().removeDisplayListener(zoom);
+    zoom = null;
+    super.nullControl();
+  }
+
+}
diff --git a/visad/ContourLabelGeometry.java b/visad/ContourLabelGeometry.java
new file mode 100644
index 0000000..857e67f
--- /dev/null
+++ b/visad/ContourLabelGeometry.java
@@ -0,0 +1,102 @@
+//
+// ContourLabelGeometry.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+
+/**
+   VisADGeometryArray stands in for j3d.GeometryArray
+   and is Serializable.<P>
+*/
+public class ContourLabelGeometry extends VisADGeometryArray
+       implements Cloneable {
+
+  public VisADGeometryArray label;
+  public VisADLineArray labelAnchor;
+  public VisADLineArray expSegLeft;
+  public VisADLineArray segLeftAnchor;
+  public float[] segLeftScaleInfo;
+  public VisADLineArray expSegRight;
+  public VisADLineArray segRightAnchor;
+  public float[] segRightScaleInfo;
+
+  public boolean isStyled = false;
+
+  public ContourLabelGeometry(VisADGeometryArray label, VisADLineArray labelAnchor,
+                    VisADLineArray expSegLeft, VisADLineArray segLeftAnchor, float[] segLeftScaleInfo,
+                    VisADLineArray expSegRight, VisADLineArray segRightAnchor, float[] segRightScaleInfo) {
+    this.label = label;
+    this.labelAnchor = labelAnchor;
+    this.expSegLeft = expSegLeft;
+    this.segLeftAnchor = segLeftAnchor;
+    this.segLeftScaleInfo = segLeftScaleInfo;
+    this.expSegRight = expSegRight;
+    this.segRightAnchor = segRightAnchor;
+    this.segRightScaleInfo = segRightScaleInfo;
+  }
+
+
+  /** eliminate any vectors or triangles crossing seams of
+      map projections, defined by display-side CoordinateSystems;
+      this default implementation does nothing */
+  public ContourLabelGeometry adjustSeam(DataRenderer renderer)
+         throws VisADException {
+    ContourLabelGeometry cntr = new ContourLabelGeometry(label.adjustSeam(renderer), labelAnchor,
+            (VisADLineArray)expSegLeft.adjustSeam(renderer), segLeftAnchor, segLeftScaleInfo,
+            (VisADLineArray)expSegRight.adjustSeam(renderer), segRightAnchor, segRightScaleInfo);
+    cntr.isStyled = isStyled;
+    return cntr;
+  }
+
+  /** split any vectors or triangles crossing crossing longitude
+      seams when Longitude is mapped to a Cartesian display axis;
+      default implementation: rotate if necessary, then return points */
+  public ContourLabelGeometry adjustLongitude(DataRenderer renderer)
+         throws VisADException {
+    ContourLabelGeometry cntr = new  ContourLabelGeometry(label.adjustLongitude(renderer), labelAnchor,
+              (VisADLineArray)expSegLeft.adjustLongitude(renderer),
+              segLeftAnchor, segLeftScaleInfo,
+              (VisADLineArray)expSegRight.adjustLongitude(renderer),
+              segRightAnchor, segRightScaleInfo);
+    cntr.isStyled = isStyled;
+    return cntr;
+  }
+
+  public ContourLabelGeometry removeMissing() {
+    ContourLabelGeometry cntr = new  ContourLabelGeometry(label.removeMissing(), labelAnchor,
+              (VisADLineArray)expSegLeft.removeMissing(),
+              segLeftAnchor, segLeftScaleInfo,
+              (VisADLineArray)expSegRight.removeMissing(),
+              segRightAnchor, segRightScaleInfo);
+    cntr.isStyled = isStyled;
+    return cntr;
+  }
+
+  public Object clone() {
+    return this;
+  }
+}
diff --git a/visad/Control.java b/visad/Control.java
new file mode 100644
index 0000000..afb307a
--- /dev/null
+++ b/visad/Control.java
@@ -0,0 +1,392 @@
+//
+// Control.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   Control is the abstract VisAD superclass for controls for
+   DisplayRealTypes.<P>
+*/
+public abstract class Control extends Object
+       implements Cloneable, java.io.Serializable {
+
+  /** incremented by incTick */
+  private long NewTick;
+  /** value of NewTick at last setTicks call */
+  private long OldTick;
+  /** set by setTicks if OldTick < NewTick; cleared by resetTicks */
+  private boolean tickFlag;
+  /** flag to indicate after setTicks and bfore resetTicks */
+  private boolean isSet = false;
+
+  /** unique Display this Control is part of */
+  transient DisplayImpl display;
+  transient DisplayRenderer displayRenderer;
+  /** index of this in display.ControlVector */
+  private int Index;
+  /** instance of this in display.ControlVector */
+  private int Instance;
+
+  /** Vector of ControlListeners */
+  private transient Vector ListenerVector = new Vector();
+
+  /**
+   * construct a Control for the given DisplayImpl
+   * @param d - DisplayImpl this Control is associated with
+   */
+  public Control(DisplayImpl d) {
+    OldTick = Long.MIN_VALUE;
+    NewTick = Long.MIN_VALUE + 1;
+    tickFlag = false;
+    display = d;
+    Index = Instance = -1;
+    if (display != null) displayRenderer = display.getDisplayRenderer();
+  }
+
+  /**
+   * @return DisplayRenderer assciated with this Control
+   */
+  public DisplayRenderer getDisplayRenderer() {
+    return displayRenderer;
+  }
+
+  /**
+   * invoked every time values of this Control change
+   * @param tick  true to notify the Display for possible re-transform
+   */
+  public void changeControl(boolean tick)
+         throws VisADException, RemoteException {
+    if (tick) incTick();
+    if (ListenerVector != null) {
+      Vector clv = null;
+      synchronized (ListenerVector) {
+        clv = (Vector) ListenerVector.clone();
+      }
+      Enumeration listeners = clv.elements();
+      while (listeners.hasMoreElements()) {
+        ControlListener listener =
+          (ControlListener) listeners.nextElement();
+        listener.controlChanged(new ControlEvent(this));
+      }
+    }
+  }
+
+  /**
+   * add a ControlListener
+   * @param listener  ControlListener to add
+   */
+  public void addControlListener(ControlListener listener) {
+    ListenerVector.addElement(listener);
+  }
+
+  /**
+   * remove a ControlListener
+   * @param listener  ControlListener to remove
+   */
+  public void removeControlListener(ControlListener listener) {
+    if (listener != null) {
+      ListenerVector.removeElement(listener);
+    }
+  }
+
+  /**
+   * end this control (called by ScalarMap.nullDisplay())
+   */
+  public void nullControl() {
+    ListenerVector.removeAllElements();
+  }
+
+  /**
+   * increment long counter NewTick: NewTick > OldTick indicates
+   * that there is event in this Control that the DisplayImpl
+   * must process;
+   * this method is invoked every time Control changes
+   * @return incremented value of NewTick counter
+   */
+  public long incTick() {
+    NewTick += 1;
+    if (NewTick == Long.MAX_VALUE) NewTick = Long.MIN_VALUE + 1;
+    if (display != null) display.controlChanged();
+// System.out.println(getClass().getName() + "  set  NewTick = " + NewTick);
+    return NewTick;
+  }
+
+  /**
+   * set tickFlag if NewTick > OldTick, and reset OldTick = NewTick
+   * also invoke subSetTicks() to propagate to any sub-Controls
+   */
+  public synchronized void setTicks() {
+    if (isSet) return; // WLH 22 Aug 99
+    isSet = true;
+    tickFlag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+// if (tickFlag) System.out.println(getClass().getName() + "  set  tickFlag = " + tickFlag);
+    OldTick = NewTick;
+    subSetTicks();
+  }
+
+  /**
+   * peek at future value of checkTicks()
+   * @param r DataRenderer to check if changes to this Control
+   *          require re-transform
+   * @param link DataDisplayLink involved in decision whether
+   *             changes to this Control require re-transform
+   * @return true if checkTicks() will return true after next setTicks()
+   */
+  public synchronized boolean peekTicks(DataRenderer r, DataDisplayLink link) {
+/*
+boolean flag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+System.out.println(getClass().getName() + "  peek  flag = " + flag +
+                   " trans = " + r.isTransformControl(this, link) + " sub = " +
+                   subPeekTicks(r, link));
+*/
+    return ((OldTick < NewTick || (NewTick < 0 && 0 < OldTick)) &&
+            r.isTransformControl(this, link)) || subPeekTicks(r, link);
+  }
+
+  /**
+   * check if this Control changed and requires re-Transform
+   * @param r DataRenderer to check if changes to this Control 
+   *          require re-transform
+   * @param link DataDisplayLink involved in decision whether
+   *             changes to this Control require re-transform
+   * @return true if Control changed and requires re-Transform
+   */
+  public synchronized boolean checkTicks(DataRenderer r, DataDisplayLink link) {
+/*
+boolean flag = (tickFlag && r.isTransformControl(this, link)) || subCheckTicks(r, link);
+if (tickFlag) {
+  System.out.println(getClass().getName() + "  check  tickFlag = " + tickFlag +
+                   " trans = " + r.isTransformControl(this, link) + " sub = " +
+                   subCheckTicks(r, link));
+}
+*/
+    return (tickFlag && r.isTransformControl(this, link)) || subCheckTicks(r, link);
+  }
+
+  /**
+   * reset tickFlag and propagate to sub-Controls
+   */
+  public synchronized void resetTicks() {
+// if (tickFlag) System.out.println(getClass().getName() + "  reset");
+    tickFlag = false;
+    subResetTicks();
+    isSet = false;
+  }
+
+  /**
+   * run setTicks on any sub-Controls;
+   * this default for no sub-Controls
+   */
+  public void subSetTicks() {
+  }
+
+  /**
+   * run checkTicks on any sub-Controls
+   * this default for no sub-Controls
+   * @param r DataRenderer to check if changes to this Control
+   *          require re-transform
+   * @param link DataDisplayLink involved in decision whether
+   *             changes to this Control require re-transform
+   * @return 'logical or' of values from checkTicks on sub-Controls
+   */
+  public boolean subCheckTicks(DataRenderer r, DataDisplayLink link) {
+    return false;
+  }
+
+  /**
+   * run peekTicks on any sub-Controls
+   * this default for no sub-Controls
+   * @param r DataRenderer to check if changes to this Control
+   *          require re-transform
+   * @param link DataDisplayLink involved in decision whether
+   *             changes to this Control require re-transform
+   * @return 'logical or' of values from peekTicks on sub-Controls
+   */
+  public boolean subPeekTicks(DataRenderer r, DataDisplayLink link) {
+    return false;
+  }
+
+  /**
+   * run resetTicks on any sub-Controls
+   * this default for no sub-Controls
+   */
+  public void subResetTicks() {
+  }
+
+  /**
+   * build String representation of current animation step
+   * and pass it to DisplayRenderer.setAnimationString()
+   * called by java3d.AnimationControlJ3D and java2d.AnimationControlJ2D
+   * @param real - RealType mapped to Display.Animation
+   * @param set - Set from AnimationSetControl
+   * @param value - real value associated with current animation step
+   * @param current - index of current animation step
+   * @throws VisADException a VisAD error occurred
+   */
+  public void animation_string(RealType real, Set set, double value,
+              int current) throws VisADException {
+    if (set != null) {
+      Unit[] units = set.getSetUnits();
+
+      // WLH 31 Aug 2000
+      Vector tmap = display.getMapVector();
+      Unit overrideUnit = null;
+      for (int i=0; i<tmap.size(); i++) {
+        ScalarMap map = (ScalarMap) tmap.elementAt(i);
+        Control c = map.getControl();
+        if (this.equals(c)) {
+          overrideUnit = map.getOverrideUnit();
+        }
+      }
+      // units not part of Time string
+      if (overrideUnit != null && units != null &&
+          !overrideUnit.equals(units[0]) && 
+          (!Unit.canConvert(units[0], CommonUnit.secondsSinceTheEpoch) ||
+           units[0].getAbsoluteUnit().equals(units[0]))) {
+        value = overrideUnit.toThis(value, units[0]);
+        units[0] = overrideUnit;
+      }
+  
+      String s = real.getName() + " = " +
+        new Real(real, value, units == null ? null : units[0]).toValueString();
+      String t = Integer.toString(current+1) + " of " +
+                 Integer.toString(set.getLength());
+      getDisplayRenderer().setAnimationString(new String[] {s, t});
+    } else { // null set
+      getDisplayRenderer().setAnimationString(new String[] {null, null});
+    }
+  }
+
+  /**
+   * set index of this Control in display.ControlVector
+   * @param index new value to index to set
+   */
+  void setIndex(int index) {
+    Index = index;
+  }
+
+  /**
+   * @return index of this Control in display.ControlVector
+   */
+  int getIndex() {
+    return Index;
+  }
+
+  /**
+   * set 'instance number' (index + 1 ?) of this Control
+   * in display.ControlVector
+   * @param instance new value to 'instance number' to set
+   */
+  void setInstanceNumber(int instance) {
+    Instance = instance;
+  }
+
+  /**
+   * @return 'instance number' (index + 1 ?) of this Control
+   *         in display.ControlVector
+   */
+  public int getInstanceNumber() {
+    return Instance;
+  }
+
+  /**
+   * @return DisplayImpl associated with this Control
+   */
+  public DisplayImpl getDisplay() {
+    return display;
+  }
+
+  /**
+   * @return String representation of this Control
+   */
+  public abstract String getSaveString();
+
+  /**
+   * reconstruct this Control using the specified save string
+   * @param save - String representation for reconstruction
+   * @throws VisADException if a VisAD error occurs
+   * @throws RemoteException if an RMI error occurs
+   */
+  public abstract void setSaveString(String save)
+    throws VisADException, RemoteException;
+
+  /**
+   * copy the state of a remote control to this control
+   * @param rmt remote Control whose state is copied
+   * @throws VisADException if a VisAD error occurs
+   */
+  public abstract void syncControl(Control rmt)
+    throws VisADException;
+
+  /**
+   * @return a copy of this Control
+   */
+  public Object clone()
+  {
+    try {
+      return super.clone();
+    } catch (CloneNotSupportedException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Indicates whether or not this instance equals an Object
+   * @param o  an Object
+   * @return true if and only if this instance is equal to o
+   */
+  public boolean equals(Object o)
+  {
+    if (o == null || !getClass().isInstance(o)) {
+      return false;
+    }
+
+    return (Instance == ((Control )o).Instance);
+  }
+
+  /**
+   * @return a simple String representation of this Control
+   */
+  public String toString()
+  {
+    String cn = getClass().getName();
+    int pt = cn.lastIndexOf('.');
+    final int ds = cn.lastIndexOf('$');
+    if (ds > pt) {
+      pt = ds;
+    }
+    if (pt == -1) {
+      pt = 0;
+    } else {
+      pt++;
+    }
+    return cn.substring(pt) + "@" + Index + "#" + Instance;
+  }
+
+}
diff --git a/visad/ControlEvent.java b/visad/ControlEvent.java
new file mode 100644
index 0000000..22c5769
--- /dev/null
+++ b/visad/ControlEvent.java
@@ -0,0 +1,67 @@
+//
+// ControlEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   ControlEvent is the VisAD class for changes in Control
+   objects.  They are sourced by Control objects and
+   received by ControlListener objects.<P>
+*/
+public class ControlEvent extends VisADEvent {
+
+  private Control control; // source of event
+
+  /**
+   * construct a ControlEvent for Control c and LOCAL_SOURCE
+   * @param c Control associated with this ControlEvent
+   */
+  public ControlEvent(Control c) {
+    this(c, LOCAL_SOURCE);
+  }
+
+  /**
+   * construct a ControlEvent for Control c and remoteId
+   * @param c Control associated with this ControlEvent
+   * @param remoteId ID for this ControlEvent
+   */
+  public ControlEvent(Control c, int remoteId) {
+    // don't pass control as the source, since source
+    // is transient inside Event
+    super(null, 0, null, remoteId);
+    control = c;
+  }
+
+  /**
+   * @return the Control that sent this ControlEvent (or a copy
+   *         if the Control was on a different JVM)
+   */
+  public Control getControl() {
+    return control;
+  }
+
+}
+
diff --git a/visad/ControlListener.java b/visad/ControlListener.java
new file mode 100644
index 0000000..d05b682
--- /dev/null
+++ b/visad/ControlListener.java
@@ -0,0 +1,43 @@
+//
+// ControlListener.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.EventListener;
+import java.rmi.*;
+
+/**
+   ControlListener is the EventListener interface for
+   ControlEvents.<P>
+*/
+public interface ControlListener extends EventListener {
+
+  /** send a ControlEvent to this ControlListener */
+  void controlChanged(ControlEvent e)
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/CoordinateSystem.java b/visad/CoordinateSystem.java
new file mode 100644
index 0000000..51a5642
--- /dev/null
+++ b/visad/CoordinateSystem.java
@@ -0,0 +1,961 @@
+//
+// CoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   CoordinateSystem is the VisAD abstract superclass for coordinate systems for
+   vectors in R^n for n>0.  Specific coordinate systems are defined by
+   extending this class and providing coordinate transformation logic in
+   the toReference and fromReference methods.<P>
+
+   CoordinateSystem objects should be immutable.<P>
+*/
+public abstract class CoordinateSystem extends Object
+       implements java.io.Serializable {
+
+  /** reference coordinate system (e.g., (Latitude, Longitude, Radius) ) */
+  private final RealTupleType Reference;
+
+  private final int DomainDimension;
+
+  /** not required to be convertable with Reference.DefaultUnits */
+  private final Unit[] CoordinateSystemUnits;
+
+  /**
+   * Constructs from the type of the reference coordinate system and units for
+   * values in this coordinate system.  Subclasses must supply reference type
+   * and units.
+   * @param reference           The type of the reference coordinate
+   *                            system.  Numeric values in the reference
+   *                            coordinate system shall be in units of
+   *                            <code>reference.getDefaultUnits()</code>
+   *                            unless specified otherwise.
+   * @param units               The default units for this coordinate system.
+   *                            Numeric values in this coordinate system shall
+   *                            be in units of <code>units</code> unless
+   *                            specified otherwise.  May be <code>null</code>
+   *                            or an array of <code>null</code>s.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public CoordinateSystem(RealTupleType reference, Unit[] units)
+         throws VisADException {
+    if (reference == null) {
+      throw new CoordinateSystemException(
+        "CoordinateSystem: Reference may not be null");
+    }
+    if (reference.getCoordinateSystem() != null) {
+      throw new CoordinateSystemException(
+        "CoordinateSystem: Reference may not have a DefaultCoordinateSystem");
+    }
+    Reference = reference;
+    DomainDimension = Reference.getDimension();
+    if (units != null && DomainDimension != units.length) {
+      throw new UnitException("CoordinateSystem: units dimension does not match");
+    }
+    CoordinateSystemUnits = new Unit[DomainDimension];
+    if (units != null) {
+      for (int i=0; i<DomainDimension; i++) CoordinateSystemUnits[i] = units[i];
+    }
+  }
+
+  /**
+   * trusted constructor for initializers (does not throw 
+   * any declared Exceptions)
+   * @param reference           The type of the reference coordinate
+   *                            system.  Numeric values in the reference
+   *                            coordinate system shall be in units of
+   *                            <code>reference.getDefaultUnits()</code>
+   *                            unless specified otherwise.
+   * @param units               The default units for this coordinate system.
+   *                            Numeric values in this coordinate system shall
+   *                            be in units of <code>units</code> unless
+   *                            specified otherwise.  May be <code>null</code>
+   *                            or an array of <code>null</code>s.
+   * @param b dummy argument for trusted constructor signature
+   */
+  CoordinateSystem(RealTupleType reference, Unit[] units, boolean b) {
+    Reference = reference;
+    DomainDimension = Reference.getDimension();
+    CoordinateSystemUnits = new Unit[DomainDimension];
+    if (units != null) {
+      for (int i=0; i<DomainDimension; i++) CoordinateSystemUnits[i] = units[i];
+    }
+  }
+
+  /**
+   * Return the reference RealTupleType for this CoordinateSystem.
+   * @return reference RealTupleType
+   */
+  public RealTupleType getReference() {
+    return Reference;
+  }
+
+  /**
+   * Return the number of components in the reference RealTupleType.
+   * @return dimension of the reference.
+   */
+  public int getDimension() {
+    return DomainDimension;
+  }
+
+  /**
+   * Return the Units for this CoordinateSystem's reference 
+   * RealTupleType.  These are the units of the return values from 
+   * {@link #toReference}.
+   * @return  copy of the Units array used at construction.
+   */
+  public Unit[] getReferenceUnits() {
+    return Reference.getDefaultUnits();
+  }
+
+  /**
+   * Return the Units for this CoordinateSystem.  The Units
+   * are what's expected for the input data for the 
+   * {@link #toReference} method.
+   * @return  copy of the Units array used at construction.
+   */
+  public Unit[] getCoordinateSystemUnits() {
+    return Unit.copyUnitsArray(CoordinateSystemUnits);
+  }
+
+  /** 
+   *  Convert RealTuple values to Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  double[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is double[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.
+   *  @param  value  array of values assumed to be in coordinateSystem
+   *                 units. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of double values in reference coordinates and Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public abstract double[][] toReference(double[][] value) throws VisADException;
+
+  /** 
+   *  Convert RealTuple values from Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  double[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is double[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.
+   *  @param  value  array of values assumed to be in reference
+   *                 Units. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of double values in CoordinateSystem Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public abstract double[][] fromReference(double[][] value) throws VisADException;
+
+  /** 
+   *  Convert RealTuple values to Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  float[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is float[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.  This implementation
+   *  converts the input array to doubles and calls {@link
+   *  #toReference(double[][])} and then returns that converted 
+   *  double array back as a float array.  For efficiency, subclasses 
+   *  should override this implementation.
+   *  @param  value  array of values assumed to be in coordinateSystem
+   *                 units. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of float values in reference coordinates and Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public float[][] toReference(float[][] value) throws VisADException {
+    double[][] val = Set.floatToDouble(value);
+    val = toReference(val);
+    return Set.doubleToFloat(val);
+  }
+
+  /** 
+   *  Convert RealTuple values from Reference coordinates;
+   *  for efficiency, input and output values are passed as
+   *  float[][] arrays rather than RealTuple[] arrays; the array
+   *  organization is float[tuple_dimension][number_of_tuples];
+   *  can modify and return argument array.  This implementation
+   *  converts the input array to doubles and calls {@link
+   *  #toReference(double[][])} and then returns that converted 
+   *  double array back as a float array.  For efficiency, subclasses 
+   *  should override this implementation.
+   *  @param  value  array of values assumed to be in reference
+   *                 Units. Input array is not guaranteed to be immutable
+   *                 and could be used for return.
+   *  @return array of float values in this CoordinateSystem Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public float[][] fromReference(float[][] value) throws VisADException {
+    double[][] val = Set.floatToDouble(value);
+    val = fromReference(val);
+    return Set.doubleToFloat(val);
+  }
+
+  /**
+   * Check to see if a conversion can be done between values
+   * of one RealTupleType and another given the CoordinateSystems
+   * supplied.
+   * @param out              The output {@link RealTupleType}.
+   * @param coord_out        The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param in               The input {@link RealTupleType}.
+   * @param coord_in         The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   *
+   * @return true if conversion is possible.
+   */
+  public static boolean canConvert(RealTupleType out, CoordinateSystem coord_out,
+                                   RealTupleType in, CoordinateSystem coord_in) {
+    if (out == null) return (in == null);
+    if (out.equals(in)) return true;
+    RealTupleType ref_out = out;
+    if (coord_out != null) ref_out = coord_out.getReference();
+    RealTupleType ref_in = in;
+    if (coord_in != null) ref_in = coord_in.getReference();
+    return ref_out.equals(ref_in);
+  }
+
+  /**
+   * <p>Transforms double-valued coordinates between two {@link RealTupleType}s.
+   * Unit conversion is always performed even if no coordinate transformation
+   * is done.</p>
+   *
+   * <p>This implementation uses {@link #transformCoordinatesFreeUnits} to do
+   * most of the transformation.</p>
+   *
+   * <p>If both {@link RealTupleType}s have a reference coordinate system, then
+   * this implementation <em>always</em> transforms the input domain values by
+   * first transforming them according to the input reference coordinate system
+   * and then inverse transforming them according to the output reference
+   * coordinate system -- even if the input and output {@link RealTupleType}s
+   * are equal.</p>
+   *
+   * @param out              The output {@link RealTupleType}.
+   * @param coord_out        The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_out        The output units.
+   * @param errors_out       The output error estimates or <code>null</code>.
+   * @param in               The input {@link RealTupleType}.
+   * @param coord_in         The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_in         The input units or <code>null</code>.
+   * @param errors_in        The input error estimates or <code>null</code>.
+   * @param value            The input coordinate values.  <code>value[i][j]
+   *                         </code> is the <code>j</code>-th sample of the
+   *                         <code>i</code>-th component.  The values might
+   *                         be modified upon return from this method.
+   * @return                 The transformed coordinate values not in the input
+   *                         array.
+   * @throws VisADException  if a VisAD failure occurs.
+   * @throws NullPointerException if <code>units_out</code> is 
+   *                         <code>null</code>.
+   */
+  public static double[][] transformCoordinates(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, ErrorEstimate[] errors_in,
+                        double[][] value) throws VisADException {
+    return transformCoordinates(out, coord_out, units_out, errors_out,
+                                in, coord_in, units_in, errors_in,
+                                value, true);
+  }
+
+  /**
+   * <p>Transforms double-valued coordinates between two {@link RealTupleType}s.
+   * Unit conversion is always performed even if no coordinate transformation
+   * is done.</p>
+   *
+   * <p>This implementation uses {@link #transformCoordinatesFreeUnits} to do
+   * most of the transformation.</p>
+   *
+   * <p>If both {@link RealTupleType}s have a reference coordinate system, then
+   * this implementation <em>always</em> transforms the input domain values by
+   * first transforming them according to the input reference coordinate system
+   * and then inverse transforming them according to the output reference
+   * coordinate system -- even if the input and output {@link RealTupleType}s
+   * are equal.</p>
+   *
+   * @param out              The output {@link RealTupleType}.
+   * @param coord_out        The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_out        The output units.
+   * @param errors_out       The output error estimates or <code>null</code>.
+   * @param in               The input {@link RealTupleType}.
+   * @param coord_in         The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_in         The input units or <code>null</code>.
+   * @param errors_in        The input error estimates or <code>null</code>.
+   * @param value            The input coordinate values.  <code>value[i][j]
+   *                         </code> is the <code>j</code>-th sample of the
+   *                         <code>i</code>-th component.  The values might
+   *                         be modified upon return from this method.
+   * @param copy             if false, the underlying <code>value</code> array
+   *                         transformations may be done in place.
+   * @return                 The transformed coordinate values not in the input
+   *                         array, if copy is true.
+   * @throws VisADException  if a VisAD failure occurs.
+   * @throws NullPointerException if <code>units_out</code> is 
+   *                         <code>null</code>.
+   */
+  public static double[][] transformCoordinates(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, ErrorEstimate[] errors_in,
+                        double[][] value, boolean copy) throws VisADException {
+    int n = out.getDimension();
+    Unit[] units_free = new Unit[n];
+    double[][] old_value = value;
+    value =
+      transformCoordinatesFreeUnits(out, coord_out, units_free, errors_out,
+                                    in, coord_in, units_in, errors_in, value);
+    // WLH 21 Nov 2001
+    if (value == old_value) {
+      value = new double[n][];
+      for (int i=0; i<n; i++) value[i] = old_value[i];
+    }
+
+    ErrorEstimate[] sub_errors_out = new ErrorEstimate[1];
+    if (errors_out == null) {
+      for (int i=0; i<n; i++) {
+        value[i] = Unit.transformUnits(units_out[i], sub_errors_out, units_free[i],
+                                       null, value[i], copy);
+      }
+    }
+    else {
+      for (int i=0; i<n; i++) {
+        value[i] = Unit.transformUnits(units_out[i], sub_errors_out, units_free[i],
+                                       errors_out[i], value[i], copy);
+        errors_out[i] = sub_errors_out[0];
+      }
+    }
+    return value;
+  }
+
+  /**
+   * <p>Transforms double-valued coordinates between two {@link RealTupleType}s.
+   * This is just like {@link #transformCoordinates}, except that final Unit
+   * conversion to <code>units_out</code> is not done; rather, 
+   * <code>units_out[i]</code> is set to the final {@link Unit} of 
+   * <code>value[i]</code>.</p>
+   *
+   * <p>If both {@link RealTupleType}s have a reference coordinate system, then
+   * this implementation <em>always</em> transforms the input domain values by
+   * first transforming them according to the input reference coordinate system
+   * and then inverse transforming them according to the output reference
+   * coordinate system -- even if the input and output {@link RealTupleType}s
+   * are equal.</p>
+   *
+   * @param out              The output {@link RealTupleType}.
+   * @param coord_out        The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_out        The output units or <code>null</code>.
+   * @param errors_out       The output error estimates or <code>null</code>.
+   * @param in               The input {@link RealTupleType}.
+   * @param coord_in         The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_in         The input units or <code>null</code>.
+   * @param errors_in        The input error estimates or <code>null</code>.
+   * @param value            The input coordinate values.  <code>value[i][j]
+   *                         </code> is the <code>j</code>-th sample of the
+   *                         <code>i</code>-th component.
+   * @return                 The transformed coordinate values. Possibly
+   *                         in the input array.
+   * @throws VisADException  if a VisAD failure occurs.
+   */
+  public static double[][] transformCoordinatesFreeUnits(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, ErrorEstimate[] errors_in,
+                        double[][] value) throws VisADException {
+
+    int n = in.getDimension();
+
+    // prepare for error calculations, if any
+    double[][] error_values = new double[1][1];
+    boolean any_transform = false;
+    boolean any_errors = false;
+    if (errors_in != null && errors_out != null) {
+      any_errors = true;
+      for (int i=0; i<n; i++) {
+        if (errors_in[i] == null) any_errors = false;
+      }
+    }
+    if (errors_out != null) {
+      // set default errors_out in case no transform
+      if (errors_in != null) {
+        for (int i=0; i<n; i++) errors_out[i] = errors_in[i];
+      }
+      else {
+        for (int i=0; i<n; i++) errors_out[i] = null;
+      }
+    }
+
+    // prepare for Unit calculations
+    Unit[] units = Unit.copyUnitsArray(units_in);
+    if (units == null) units = new Unit[n];
+    Unit[] error_units = Unit.copyUnitsArray(units);
+
+    if (units_out != null) {
+      // set default units_out in case no transform
+      for (int i=0; i<n; i++) units_out[i] = units[i];
+    }
+
+    // WLH 28 March 2000
+    // ensure coord_out and coord_in include any RealTupleType defaults
+    if (coord_out == null) coord_out = out.getCoordinateSystem();
+    if (coord_in == null) coord_in = in.getCoordinateSystem();
+
+    if (out.equals(in)) {
+      if (coord_in == null && coord_out == null) return value;
+      if (coord_in == null || coord_out == null) {
+        throw new CoordinateSystemException(
+          "CoordinateSystem.transformCoordinates: inconsistency");
+      }
+      if (!coord_in.equals(coord_out)) {
+        if (any_errors) {
+          if (!any_transform) {
+            error_values = ErrorEstimate.init_error_values(errors_in);
+          }
+          any_transform = true;
+          error_values = coord_in.toReference(error_values, error_units);
+          error_values = coord_out.fromReference(error_values, error_units);
+        }
+        value = coord_in.toReference(value, units);
+        value = coord_out.fromReference(value, units);
+      }
+    }
+    else { // !out.equals(in)
+      RealTupleType ref_out = out;
+      if (coord_out != null) {
+        ref_out = coord_out.getReference();
+        // WLH - this check for testing only - may eliminate later
+        if (out.getCoordinateSystem() == null ||
+            !out.getCoordinateSystem().getReference().equals(ref_out)) {
+          throw new CoordinateSystemException(
+            "CoordinateSystem.transformCoordinates: out References don't match");
+        }
+      }
+
+      RealTupleType ref_in = in;
+      if (coord_in != null) {
+        ref_in = coord_in.getReference();
+        // WLH - this check for testing only - may eliminate later
+        if (in.getCoordinateSystem() == null ||
+            !in.getCoordinateSystem().getReference().equals(ref_in)) {
+          throw new CoordinateSystemException(
+            "CoordinateSystem.transformCoordinates: in References don't match");
+        }
+      }
+
+      if (ref_out.equals(ref_in)) {
+        if (!in.equals(ref_in)) {
+          if (any_errors) {
+            if (!any_transform) {
+              error_values = ErrorEstimate.init_error_values(errors_in);
+            }
+            any_transform = true;
+            error_values = coord_in.toReference(error_values, error_units);
+          }
+          value = coord_in.toReference(value, units);
+        }
+        if (!out.equals(ref_out)) {
+          if (any_errors) {
+            if (!any_transform) {
+              error_values = ErrorEstimate.init_error_values(errors_in);
+            }
+            any_transform = true;
+            error_values = coord_out.fromReference(error_values, error_units);
+          }
+          value = coord_out.fromReference(value, units);
+        }
+      }
+      else { // !(ref_out.equals(ref_in)
+// WLH 4 July 2000 - should throw an Exception here -
+//                   but breaks too many things, so don't do it
+      }
+    } // end if (!out.equals(in))
+
+    // set return Units
+    if (units_out != null) {
+      for (int i=0; i<n; i++) units_out[i] = units[i];
+    }
+    // set return ErrorEstimates
+    if (any_errors && any_transform) {
+      for (int i=0; i<n; i++) {
+        double error = Math.abs( error_values[i][2 * i + 1] -
+                                 error_values[i][2 * i] );
+        errors_out[i] = new ErrorEstimate(value[i], error, units_out[i]);
+      }
+    }
+    return value;
+  }
+
+  /**
+   * <p>Transforms float-valued coordinates between two {@link RealTupleType}s.
+   * Unit conversion is always performed even if no coordinate transformation
+   * is done.</p>
+   *
+   * <p>This implementation uses {@link #transformCoordinatesFreeUnits} to do
+   * most of the transformation.</p>
+   *
+   * <p>If both {@link RealTupleType}s have a reference coordinate system, then
+   * this implementation <em>always</em> transforms the input domain values by
+   * first transforming them according to the input reference coordinate system
+   * and then inverse transforming them according to the output reference
+   * coordinate system -- even if the input and output {@link RealTupleType}s
+   * are equal.</p>
+   *
+   * @param out              The output {@link RealTupleType}.
+   * @param coord_out        The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_out        The output units.
+   * @param errors_out       The output error estimates or <code>null</code>.
+   * @param in               The input {@link RealTupleType}.
+   * @param coord_in         The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_in         The input units or <code>null</code>.
+   * @param errors_in        The input error estimates or <code>null</code>.
+   * @param value            The input coordinate values.  <code>value[i][j]
+   *                         </code> is the <code>j</code>-th sample of the
+   *                         <code>i</code>-th component.  The values might
+   *                         be modified upon return from this method.
+   * @return                 The transformed coordinate values not in the input
+   *                         array.
+   * @throws VisADException  if a VisAD failure occurs.
+   * @throws NullPointerException if <code>units_out</code> is 
+   *                         <code>null</code>.
+   */
+  public static float[][] transformCoordinates(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, ErrorEstimate[] errors_in,
+                        float[][] value) throws VisADException {
+    return transformCoordinates(out, coord_out, units_out, errors_out,
+                                in, coord_in, units_in, errors_in,
+                                value, true);
+  }
+
+
+  /**
+   * <p>Transforms float-valued coordinates between two {@link RealTupleType}s.
+   * Unit conversion is always performed even if no coordinate transformation
+   * is done.</p>
+   *
+   * <p>This implementation uses {@link #transformCoordinatesFreeUnits} to do
+   * most of the transformation.</p>
+   *
+   * <p>If both {@link RealTupleType}s have a reference coordinate system, then
+   * this implementation <em>always</em> transforms the input domain values by
+   * first transforming them according to the input reference coordinate system
+   * and then inverse transforming them according to the output reference
+   * coordinate system -- even if the input and output {@link RealTupleType}s
+   * are equal.</p>
+   *
+   * @param out              The output {@link RealTupleType}.
+   * @param coord_out        The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_out        The output units.
+   * @param errors_out       The output error estimates or <code>null</code>.
+   * @param in               The input {@link RealTupleType}.
+   * @param coord_in         The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_in         The input units or <code>null</code>.
+   * @param errors_in        The input error estimates or <code>null</code>.
+   * @param value            The input coordinate values.  <code>value[i][j]
+   *                         </code> is the <code>j</code>-th sample of the
+   *                         <code>i</code>-th component.  The values might
+   *                         be modified upon return from this method.
+   * @param copy             if false, the underlying <code>value</code> array
+   *                         transformations may be done in place.
+   * @return                 The transformed coordinate values not in the input
+   *                         array, if copy is true.
+   * @throws VisADException  if a VisAD failure occurs.
+   * @throws NullPointerException if <code>units_out</code> is 
+   *                         <code>null</code>.
+   */
+  public static float[][] transformCoordinates(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, ErrorEstimate[] errors_in,
+                        float[][] value, boolean copy) throws VisADException {
+
+    int n = out.getDimension();
+    Unit[] units_free = new Unit[n];
+    float[][] old_value = value;
+    value =
+      transformCoordinatesFreeUnits(out, coord_out, units_free, errors_out,
+                                    in, coord_in, units_in, errors_in, value);
+    // WLH 21 Nov 2001
+    if (value == old_value) {
+      value = new float[n][];
+      for (int i=0; i<n; i++) value[i] = old_value[i];
+    }
+
+    ErrorEstimate[] sub_errors_out = new ErrorEstimate[1];
+    if (errors_out == null) {
+      for (int i=0; i<n; i++) {
+        value[i] = Unit.transformUnits(units_out[i], sub_errors_out, units_free[i],
+                                       null, value[i], copy);
+      }
+    }
+    else {
+      for (int i=0; i<n; i++) {
+        value[i] = Unit.transformUnits(units_out[i], sub_errors_out, units_free[i],
+                                       errors_out[i], value[i], copy);
+        errors_out[i] = sub_errors_out[0];
+      }
+    }
+    return value;
+  }
+
+  /**
+   * <p>Transforms float-valued coordinates between two {@link RealTupleType}s.
+   * This is just like {@link #transformCoordinates}, except that final Unit
+   * conversion to <code>units_out</code> is not done; rather, 
+   * <code>units_out[i]</code> is set to the final {@link Unit} of 
+   * <code>value[i]</code>.</p>
+   *
+   * <p>If both {@link RealTupleType}s have a reference coordinate system, then
+   * this implementation <em>always</em> transforms the input domain values by
+   * first transforming them according to the input reference coordinate system
+   * and then inverse transforming them according to the output reference
+   * coordinate system -- even if the input and output {@link RealTupleType}s
+   * are equal.</p>
+   *
+   * @param out              The output {@link RealTupleType}.
+   * @param coord_out        The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_out        The output units or <code>null</code>.
+   * @param errors_out       The output error estimates or <code>null</code>.
+   * @param in               The input {@link RealTupleType}.
+   * @param coord_in         The coordinate system transformation associated
+   *                         with the output {@link RealTupleType} or <code>
+   *                         null</code>.
+   * @param units_in         The input units or <code>null</code>.
+   * @param errors_in        The input error estimates or <code>null</code>.
+   * @param value            The input coordinate values.  <code>value[i][j]
+   *                         </code> is the <code>j</code>-th sample of the
+   *                         <code>i</code>-th component.
+   * @return                 The transformed coordinate values. Possibly
+   *                         in the input array.
+   * @throws VisADException  if a VisAD failure occurs.
+   */
+  public static float[][] transformCoordinatesFreeUnits(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, ErrorEstimate[] errors_in,
+                        float[][] value) throws VisADException {
+    int n = in.getDimension();
+
+    // prepare for error calculations, if any
+    double[][] error_values = new double[1][1];
+    boolean any_transform = false;
+    boolean any_errors = false;
+    if (errors_in != null && errors_out != null) {
+      any_errors = true;
+      for (int i=0; i<n; i++) {
+        if (errors_in[i] == null) any_errors = false;
+      }
+    }
+    if (errors_out != null) {
+      // set default errors_out in case no transform
+      if (errors_in != null) {
+        for (int i=0; i<n; i++) errors_out[i] = errors_in[i];
+      }
+      else {
+        for (int i=0; i<n; i++) errors_out[i] = null;
+      }
+    }
+
+    // prepare for Unit calculations
+    Unit[] units = Unit.copyUnitsArray(units_in);
+    if (units == null) units = new Unit[n];
+    Unit[] error_units = Unit.copyUnitsArray(units);
+
+    if (units_out != null) {
+      // set default units_out in case no transform
+      for (int i=0; i<n; i++) units_out[i] = units[i];
+    }
+
+    // WLH 28 March 2000
+    // ensure coord_out and coord_in include any RealTupleType defaults
+    if (coord_out == null) coord_out = out.getCoordinateSystem();
+    if (coord_in == null) coord_in = in.getCoordinateSystem();
+
+    if (out.equals(in)) {
+      if (coord_in == null && coord_out == null) return value;
+      if (coord_in == null || coord_out == null) {
+        throw new CoordinateSystemException(
+          "CoordinateSystem.transformCoordinates: inconsistency");
+      }
+      if (!coord_in.equals(coord_out)) {
+        if (any_errors) {
+          if (!any_transform) {
+            error_values = ErrorEstimate.init_error_values(errors_in);
+          }
+          any_transform = true;
+          error_values = coord_in.toReference(error_values, error_units);
+          error_values = coord_out.fromReference(error_values, error_units);
+        }
+        value = coord_in.toReference(value, units);
+        value = coord_out.fromReference(value, units);
+      }
+    }
+    else { // !out.equals(in)
+      RealTupleType ref_out = out;
+      if (coord_out != null) {
+        ref_out = coord_out.getReference();
+        // WLH - this check for testing only - may eliminate later
+        if (out.getCoordinateSystem() == null ||
+            !out.getCoordinateSystem().getReference().equals(ref_out)) {
+          throw new CoordinateSystemException(
+            "CoordinateSystem.transformCoordinates: out References don't match");
+        }
+      }
+
+      RealTupleType ref_in = in;
+      if (coord_in != null) {
+        ref_in = coord_in.getReference();
+        // WLH - this check for testing only - may eliminate later
+        if (in.getCoordinateSystem() == null ||
+            !in.getCoordinateSystem().getReference().equals(ref_in)) {
+          throw new CoordinateSystemException(
+            "CoordinateSystem.transformCoordinates: in References don't match");
+        }
+      }
+
+      if (ref_out.equals(ref_in)) {
+        if (!in.equals(ref_in)) {
+          if (any_errors) {
+            if (!any_transform) {
+              error_values = ErrorEstimate.init_error_values(errors_in);
+            }
+            any_transform = true;
+            error_values = coord_in.toReference(error_values, error_units);
+          }
+          value = coord_in.toReference(value, units);
+        }
+        if (!out.equals(ref_out)) {
+          if (any_errors) {
+            if (!any_transform) {
+              error_values = ErrorEstimate.init_error_values(errors_in);
+            }
+            any_transform = true;
+            error_values = coord_out.fromReference(error_values, error_units);
+          }
+          value = coord_out.fromReference(value, units);
+        }
+      }
+      else { // !(ref_out.equals(ref_in)
+// WLH 4 July 2000 - should throw an Exception here -
+//                   but breaks too many things, so don't do it
+      }
+    } // end if (!out.equals(in))
+
+    // set return Units
+    if (units_out != null) {
+      for (int i=0; i<n; i++) units_out[i] = units[i];
+    }
+    // set return ErrorEstimates
+    if (any_errors && any_transform) {
+      for (int i=0; i<n; i++) {
+        double error = Math.abs( error_values[i][2 * i + 1] -
+                                 error_values[i][2 * i] );
+        errors_out[i] = new ErrorEstimate(value[i], error, units_out[i]);
+      }
+    }
+    return value;
+  }
+
+  /** 
+   *  Convert values in Units specified to Reference coordinates.
+   *  If units are non-null, they are both the Unit[] of input value,
+   *  and a holder for Unit[] of output.  
+   *  @param  value  array of values assumed to be in the Units
+   *                 specified or CoordinateSystem units if null.
+   *  @param  units  Units of input values.  If non-null, input values
+   *                 are converted to CoordinateSystem Units (if they
+   *                 are non-null) before calling 
+   *                 {@link #toReference(double[][])}.
+   *  @return array of double values in reference coordinates and Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public double[][] toReference(double[][] value, Unit[] units)
+         throws VisADException {
+    int n = value.length;
+    if (CoordinateSystemUnits != null) {
+      for (int i=0; i<n; i++) {
+        if (CoordinateSystemUnits[i] != null) {
+          if (units[i] != null && !CoordinateSystemUnits[i].equals(units[i])) {
+            value[i] = CoordinateSystemUnits[i].toThis(value[i], units[i], false);
+          }
+        }
+      }
+    }
+    Unit[] us = Reference.getDefaultUnits();
+    if (us != null) {
+      for (int i=0; i<n; i++) units[i] = us[i];
+    }
+    else {
+      for (int i=0; i<n; i++) units[i] = null;
+    }
+    return toReference(value);
+  }
+
+  /** 
+   *  Convert values in Units specified to Reference coordinates.
+   *  If units are non-null, they are both the Unit[] of input value,
+   *  and a holder for Unit[] of output.  
+   *  @param  value  array of values assumed to be in the Units
+   *                 specified or CoordinateSystem units if null.
+   *  @param  units  Units of input values.  If non-null, input values
+   *                 are converted to CoordinateSystem Units (if they
+   *                 are non-null) before calling 
+   *                 {@link #toReference(float[][])}.
+   *  @return array of float values in reference coordinates and Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public float[][] toReference(float[][] value, Unit[] units)
+         throws VisADException {
+    int n = value.length;
+    if (CoordinateSystemUnits != null) {
+      for (int i=0; i<n; i++) {
+        if (CoordinateSystemUnits[i] != null) {
+          if (units[i] != null && !CoordinateSystemUnits[i].equals(units[i])) {
+            value[i] = CoordinateSystemUnits[i].toThis(value[i], units[i], false);
+          }
+        }
+      }
+    }
+    Unit[] us = Reference.getDefaultUnits();
+    if (us != null) {
+      for (int i=0; i<n; i++) units[i] = us[i];
+    }
+    else {
+      for (int i=0; i<n; i++) units[i] = null;
+    }
+    return toReference(value);
+  }
+
+  /** 
+   *  Convert values in Units specified to this CoordinateSystem's
+   *  Units. If units are non-null, they are both the Unit[] of input value,
+   *  and a holder for Unit[] of output.  
+   *  @param  value  array of values assumed to be in the Units
+   *                 specified or Reference units if null.
+   *  @param  units  Units of input values.  If non-null, input values
+   *                 are converted to Reference Units (if they
+   *                 are non-null) before calling 
+   *                 {@link #fromReference(double[][])}.
+   *  @return array of double values in CoordinateSystem Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public double[][] fromReference(double[][] value, Unit[] units)
+         throws VisADException {
+    int n = value.length;
+    Unit[] us = Reference.getDefaultUnits();
+    if (us != null) {
+      for (int i=0; i<n; i++) {
+        if (us[i] != null) {
+          if (units[i] != null && !us[i].equals(units[i])) {
+            value[i] = us[i].toThis(value[i], units[i], false);
+          }
+        }
+      }
+    }
+    if (CoordinateSystemUnits != null) {
+      for (int i=0; i<n; i++) units[i] = CoordinateSystemUnits[i];
+    }
+    else {
+      for (int i=0; i<n; i++) units[i] = null;
+    }
+    return fromReference(value);
+  }
+
+  /** 
+   *  Convert values in Units specified to this CoordinateSystem's
+   *  Units. If units are non-null, they are both the Unit[] of input value,
+   *  and a holder for Unit[] of output.  
+   *  @param  value  array of values assumed to be in the Units
+   *                 specified or Reference units if null.
+   *  @param  units  Units of input values.  If non-null, input values
+   *                 are converted to Reference Units (if they
+   *                 are non-null) before calling 
+   *                 {@link #fromReference(float[][])}.
+   *  @return array of float values in CoordinateSystem Units.  
+   *  @throws VisADException  if problem with conversion.
+   */
+  public float[][] fromReference(float[][] value, Unit[] units)
+         throws VisADException {
+    int n = value.length;
+    Unit[] us = Reference.getDefaultUnits();
+    if (us != null) {
+      for (int i=0; i<n; i++) {
+        if (us[i] != null) {
+          if (units[i] != null && !us[i].equals(units[i])) {
+            value[i] = us[i].toThis(value[i], units[i], false);
+          }
+        }
+      }
+    }
+    if (CoordinateSystemUnits != null) {
+      for (int i=0; i<n; i++) units[i] = CoordinateSystemUnits[i];
+    }
+    else {
+      for (int i=0; i<n; i++) units[i] = null;
+    }
+    return fromReference(value);
+  }
+
+  /**
+   * Indicates whether or not this instance is equal to an object
+   * (note must test for cs == null).
+   * @param cs the object in question.
+   * @return <code>true</code> if and only if this instance equals cs.
+   */
+  public abstract boolean equals(Object cs);
+
+}
+
diff --git a/visad/CoordinateSystemException.java b/visad/CoordinateSystemException.java
new file mode 100644
index 0000000..64f0fcb
--- /dev/null
+++ b/visad/CoordinateSystemException.java
@@ -0,0 +1,59 @@
+//
+// CoordinateSystemException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   CoordinateSystemException is an exception for a bad VisAD CoordinateSystem.<P>
+*/
+public class CoordinateSystemException extends VisADException {
+
+  /**
+   * construct a CoordinateSystemException with no message
+   */
+  public CoordinateSystemException() { super(); }
+
+  /**
+   * construct a CoordinateSystemException with given message
+   * @param s - message String
+   */
+  public CoordinateSystemException(String s) { super(s); }
+
+  /**
+   * construct a CoordinateSystemException for unequal
+   * CoordinateSystems
+   * @param cs1 - first CoordinateSystem
+   * @param cs2 - second CoordinateSystem
+   */
+  public CoordinateSystemException(CoordinateSystem cs1, CoordinateSystem cs2) {
+
+    this("Coordinate system mismatch: " +
+      (cs1 == null ? "null" : cs1.getReference().toString()) + " != " +
+      (cs2 == null ? "null" : cs2.getReference().toString()));
+  }
+
+}
+
diff --git a/visad/CylindricalCoordinateSystem.java b/visad/CylindricalCoordinateSystem.java
new file mode 100644
index 0000000..e0b1632
--- /dev/null
+++ b/visad/CylindricalCoordinateSystem.java
@@ -0,0 +1,243 @@
+//
+// CylindricalCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * CylindricalCoordinateSystem is the VisAD CoordinateSystem class
+ * for (CylRadius, CylAzimuth, CylZAxis) with a Cartesian Reference,
+ * (XAxis, YAxis, ZAxis) and with CylAzimuth in degrees.<P>
+ */
+public class CylindricalCoordinateSystem
+    extends CoordinateSystem
+{
+
+    private static Unit[] coordinate_system_units =
+        {null, CommonUnit.degree, null};
+
+    /** construct a CoordinateSystem for (radius, azimuth, zaxis) relative
+     *  to a 3-D Cartesian reference; this constructor supplies units =
+     *  {null, CommonUnit.Degree, null} to the super
+     *  constructor, in order to ensure Unit compatibility with its
+     *  use of trigonometric functions
+     *
+     * @param    reference    Cartesian reference in the order of x, y, z
+     * @throws   VisADException  necessary VisAD object could not be created
+     */
+    public CylindricalCoordinateSystem(RealTupleType reference)
+        throws VisADException
+    {
+        super(reference, coordinate_system_units);
+    }
+
+    /** construct a CoordinateSystem for (radius, azimuth, zaxis) relative
+     *  to a 3-D Cartesian reference; this constructor supplies units =
+     *  {null, CommonUnit.Degree, null} to the super
+     *  constructor, in order to ensure Unit compatibility with its
+     *  use of trigonometric functions
+     *
+     * @param    reference    Cartesian reference in the order of x, y, z
+     * @param    b            boolean argument indicating this is the
+     *                        trusted constructor for initializers (does
+     *                        not declare Exceptions)
+     */
+    CylindricalCoordinateSystem(RealTupleType reference, boolean b)
+    {
+        super(reference, coordinate_system_units, b);
+    }
+
+    /**
+     * Convert cylindrical coordinates (radius, azimuth, z) to
+     * Cartesian coordinates (x, y, z).  Input array must have
+     * a length of 3 and be in the correct order.
+     *
+     * @param  tuples  double array containing the radius, azimuth and z values.
+     *
+     * @return  double array in Cartesian coordinates ordered as x, y, z
+     *
+     * @throws  CoordinateSystemException  if input array is null or wrong
+     *                                     dimension.
+     */
+    public double[][] toReference(double[][] tuples)
+        throws CoordinateSystemException
+    {
+        if (tuples == null || tuples.length != 3)
+        {
+            throw new CoordinateSystemException(
+                "CylindricalCoordinateSystem.toReference: " +
+                    "tuples wrong dimension");
+        }
+        int len = tuples[0].length;
+        double[][] value = new double[3][len];
+        for (int i = 0; i < len; i++)
+        {
+            if (tuples[0][i] < 0.0)   // radius < 0
+            {
+                value[0][i] = Double.NaN;
+                value[1][i] = Double.NaN;
+                value[2][i] = Double.NaN;
+            }
+            else
+            {
+                double cosaz =
+                    Math.cos(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+                double sinaz =
+                    Math.sin(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+                value[0][i] = tuples[0][i] * cosaz;
+                value[1][i] = tuples[0][i] * sinaz;
+                value[2][i] = tuples[2][i];
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Convert Cartesian coordinates (x, y, z) to
+     * cylindrical coordinates (radius, azimuth, z).  Input array must have
+     * a length of 3 and be in the correct order.
+     *
+     * @param  tuples   double array in Cartesian coordinates ordered as x, y, z
+     *
+     * @return  double array containing the radius, azimuth and z values.
+     *
+     * @throws  CoordinateSystemException  if input array is null or wrong
+     *                                     dimension.
+     */
+    public double[][] fromReference(double[][] tuples)
+        throws CoordinateSystemException
+    {
+        if (tuples == null || tuples.length != 3)
+        {
+            throw new CoordinateSystemException(
+                "CylindricalCoordinateSystem.fromReference: " +
+                    "tuples wrong dimension");
+        }
+        int len = tuples[0].length;
+        double[][] value = new double[3][len];
+        for (int i = 0; i < len; i++)
+        {
+            value[0][i] = Math.sqrt(tuples[0][i] * tuples[0][i] +
+                                    tuples[1][i] * tuples[1][i]);
+            value[1][i] =
+                Data.RADIANS_TO_DEGREES *
+                    Math.atan2(tuples[1][i], tuples[0][i]);
+            if (value[1][i] < 0.0) value[1][i] += 360.0;
+            value[2][i] = tuples[2][i];
+        }
+        return value;
+    }
+
+    /**
+     * Convert cylindrical coordinates (radius, azimuth, z) to
+     * Cartesian coordinates (x, y, z).  Input array must have
+     * a length of 3 and be in the correct order.
+     *
+     * @param  tuples  float array containing the radius, azimuth and z values.
+     *
+     * @return  float array in Cartesian coordinates ordered as x, y, z
+     *
+     * @throws  CoordinateSystemException  if input array is null or wrong
+     *                                     dimension.
+     */
+    public float[][] toReference(float[][] tuples)
+        throws CoordinateSystemException
+    {
+        if (tuples == null || tuples.length != 3)
+        {
+            throw new CoordinateSystemException(
+                "CylindricalCoordinateSystem.toReference: " +
+                    "tuples wrong dimension");
+        }
+        int len = tuples[0].length;
+        float[][] value = new float[3][len];
+        for (int i=0; i<len ;i++)
+        {
+            if (tuples[0][i] < 0.0)   // radius < 0
+            {
+                value[0][i] = Float.NaN;
+                value[1][i] = Float.NaN;
+                value[2][i] = Float.NaN;
+            }
+            else
+            {
+                float cosaz =
+                    (float) Math.cos(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+                float sinaz =
+                    (float) Math.sin(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+                value[0][i] = tuples[0][i] * cosaz;
+                value[1][i] = tuples[0][i] * sinaz;
+                value[2][i] = tuples[2][i];
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Convert Cartesian coordinates (x, y, z) to
+     * cylindrical coordinates (radius, azimuth, z).  Input array must have
+     * a length of 3 and be in the correct order.
+     *
+     * @param  tuples   float array in Cartesian coordinates ordered as x, y, z
+     *
+     * @return  float array containing the radius, azimuth and z values.
+     *
+     * @throws  CoordinateSystemException  if input array is null or wrong
+     *                                     dimension.
+     */
+    public float[][] fromReference(float[][] tuples)
+        throws CoordinateSystemException
+    {
+        if (tuples == null || tuples.length != 3)
+        {
+            throw new CoordinateSystemException(
+                "CylindricalCoordinateSystem.fromReference: " +
+                    "tuples wrong dimension");
+        }
+        int len = tuples[0].length;
+        float[][] value = new float[3][len];
+        for (int i=0; i<len ;i++)
+        {
+            value[0][i] = (float) Math.sqrt(tuples[0][i] * tuples[0][i] +
+                                            tuples[1][i] * tuples[1][i]);
+            value[1][i] = (float)
+                (Data.RADIANS_TO_DEGREES *
+                    Math.atan2(tuples[1][i], tuples[0][i]));
+            if (value[1][i] < 0.0f) value[1][i] += 360.0f;
+            value[2][i] = tuples[2][i];
+        }
+        return value;
+    }
+
+    /** determine if the CoordinateSystem in question is a Cylindrical one
+     *
+     * @param cs the CoordinateSystem in question
+     *
+     */
+    public boolean equals(Object cs)
+    {
+        return (cs instanceof CylindricalCoordinateSystem);
+    }
+}
diff --git a/visad/DEDICATION b/visad/DEDICATION
new file mode 100644
index 0000000..756e13e
--- /dev/null
+++ b/visad/DEDICATION
@@ -0,0 +1 @@
+for John
diff --git a/visad/Data.java b/visad/Data.java
new file mode 100644
index 0000000..27bc286
--- /dev/null
+++ b/visad/Data.java
@@ -0,0 +1,1080 @@
+//
+// Data.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+   Data is the top-level interface of the VisAD data hierarchy.
+   See the DataImpl class for more information.<P>
+*/
+public interface Data extends Thing {
+
+  /** NEAREST_NEIGHBOR resampling mode */
+  int NEAREST_NEIGHBOR = 100;
+  /** WEIGHTED_AVERAGE resampling Mode */
+  int WEIGHTED_AVERAGE = 101;
+
+  /** INDEPENDENT error estimation Mode */
+  int INDEPENDENT = 200;
+  /** DEPENDENT error estimation Mode */
+  int DEPENDENT = 201;
+  /** NO_ERRORS error estimation Mode */
+  int NO_ERRORS = 202;
+
+  /** constants for various binary arithmetic operations */
+  int ADD = 1;
+  int SUBTRACT = 2; // this - operand
+  int INV_SUBTRACT = 3; // operand - this
+  int MULTIPLY = 4;
+  int DIVIDE = 5; // this / operand
+  int INV_DIVIDE = 6; // operand / this
+  int POW = 7; // this ** operand
+  int INV_POW = 8; // operand ** this
+  int MAX = 9;
+  int MIN = 10;
+  int ATAN2 = 11; // atan2(this, operand)
+  int ATAN2_DEGREES = 12; // atan2(this, operand)
+  int INV_ATAN2 = 13; // atan2(operand, this)
+  int INV_ATAN2_DEGREES = 14; // atan2(operand, this)
+  int REMAINDER = 15; // this % operand
+  int INV_REMAINDER = 16; // operand % this
+
+  /** constants for various unary arithmetic operations */
+  int ABS = 21;
+  int ACOS = 22;
+  int ACOS_DEGREES = 23;
+  int ASIN = 24;
+  int ASIN_DEGREES = 25;
+  int ATAN = 26;
+  int ATAN_DEGREES = 27;
+  int CEIL = 28;
+  int COS = 29;
+  int COS_DEGREES = 30;
+  int EXP = 31;
+  int FLOOR = 32;
+  int LOG = 33;
+  int RINT = 34;
+  int ROUND = 35;
+  int SIN = 36;
+  int SIN_DEGREES = 37;
+  int SQRT = 38;
+  int TAN = 39;
+  int TAN_DEGREES = 40;
+  int NEGATE = 41;
+  int NOP = 42;
+
+  /** constants for angle Unit conversions */
+  double RADIANS_TO_DEGREES = 180.0 / Math.PI;
+  double DEGREES_TO_RADIANS = Math.PI / 180.0;
+
+  /**
+   * @return a local copy if remote (i.e., this is RemoteData),
+   *         else return this if local (i.e., this is DataImpl)
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  DataImpl local() throws VisADException, RemoteException;
+
+  /**
+   * @return MathType of this Data
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  MathType getType() throws VisADException, RemoteException;
+
+  /**
+   * @return flag indicating whether this Data has a missing value
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  boolean isMissing()
+         throws VisADException, RemoteException;
+
+  /**
+   * Pointwise binary operation between this and data. Applies
+   * to Reals, Tuples (recursively to components), and to Field
+   * ranges (Field domains implicitly resampled if necessary).
+   * Does not apply to Field domains or Sets (regarded as domains
+   * of Fields wthout ranges). Data.ADD is only op defined for
+   * Text, interpreted as concatenate. MathTypes of this and data
+   * must match, or one may match the range of the other if it is
+   * a FunctionType.
+   * @param data other Data operand for binary operation
+   * @param op may be Data.ADD, Data.SUBTRACT, etc; these include all
+   *             binary operations defined for Java primitive data types
+   * @param sampling_mode may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation, which takes the MathType of this unless
+   *         the default Units of that MathType conflict with Units of
+   *         the result, in which case a generic MathType with appropriate
+   *         Units is constructed
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  Data binary(Data data, int op, int sampling_mode,
+         int error_mode) throws VisADException, RemoteException;
+
+  /**
+   * Pointwise binary operation between this and data. Applies
+   * to Reals, Tuples (recursively to components), and to Field 
+   * ranges (Field domains implicitly resampled if necessary). 
+   * Does not apply to Field domains or Sets (regarded as domains
+   * of Fields wthout ranges). Data.ADD is only op defined for
+   * Text, interpreted as concatenate. MathTypes of this and data
+   * must match, or one may match the range of the other if it is
+   * a FunctionType.
+   * @param data other Data operand for binary operation
+   * @param op may be Data.ADD, Data.SUBTRACT, etc; these include all
+   *             binary operations defined for Java primitive data types
+   * @param new_type MathType of the result
+   * @param sampling_mode may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, with MathType = new_type
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  Data binary(Data data, int op, MathType new_type,
+                              int sampling_mode, int error_mode )
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to add data to this, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data add(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to subtract data from this, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data subtract(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to multiply this by data, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data multiply(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to divide this by data, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data divide(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to raise this to data power, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data pow(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the max of this and data, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data max(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the min of this and data, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data min(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing radian Units, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atan2(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing degree Units, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atan2Degrees(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the remainder of this divided by
+   * data, using default modes for sampling 
+   * (Data.NEAREST_NEIGHBOR) and error estimation (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data remainder(Data data)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to add data to this
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data add(Data data, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to subtract data from this
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data subtract(Data data, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to multiply this by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data multiply(Data data, int sampling_mode,
+         int error_mode) throws VisADException, RemoteException;
+
+  /**
+   * call binary() to divide this by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data divide(Data data, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to raise this to data power
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data pow(Data data, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the max of this and data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data max(Data data, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the min of this and data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data min(Data data, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing radian Units
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atan2(Data data, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing degree Units
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atan2Degrees(Data data, int sampling_mode,
+         int error_mode) throws VisADException, RemoteException;
+
+  /**
+   * call binary() to take the remainder of this divided by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data remainder(Data data, int sampling_mode,
+         int error_mode) throws VisADException, RemoteException;
+
+  /**
+   * Pointwise unary operation applied to this. Applies
+   * to Reals, Tuples (recursively to components), and to Field 
+   * ranges (Field domains implicitly resampled if necessary). 
+   * Does not apply to Field domains, Sets (regarded as domains
+   * of Fields wthout ranges) or Text.
+   * @param op  may be Data.ABS, Data.ACOS, etc; these include all
+   *             unary operations defined for Java primitive data types
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation, which takes the MathType of this unless
+   *         the default Units of that MathType conflict with Units of
+   *         the result, in which case a generic MathType with appropriate
+   *         Units is constructed
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data unary(int op, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * Pointwise unary operation applied to this. Applies
+   * to Reals, Tuples (recursively to components), and to Field 
+   * ranges (Field domains implicitly resampled if necessary). 
+   * Does not apply to Field domains, Sets (regarded as domains
+   * of Fields wthout ranges) or Text.
+   * @param op  may be Data.ABS, Data.ACOS, etc; these include all
+   *             unary operations defined for Java primitive data types
+   * @param new_type  MathType of the result
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, with MathType = new_type
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data unary(int op, MathType new_type, int sampling_mode,
+                             int error_mode )
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to clone this except with a new MathType
+   * @param new_type  MathType of returned Data object
+   * @return clone of this Data object except with new MathType
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data changeMathType(MathType new_type)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the absolute value of this, using
+   * default modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data abs() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arccos of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data acos() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arccos of this producing
+   * degree Units, using default modes for sampling 
+   * (Data.NEAREST_NEIGHBOR) and error estimation 
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data acosDegrees() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data asin() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data asinDegrees() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arctan of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atan() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arctan of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atanDegrees() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the ceiling of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data ceil() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the cos of this assuming radian
+   * Units unless this actual Units are degrees,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data cos() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the cos of this assuming degree 
+   * Units unless this actual Units are radians, 
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data cosDegrees() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the exponent of this, using default 
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and 
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data exp() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the floor of this, using default 
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and 
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data floor() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the log of this, using default 
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and 
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data log() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the rint (essentially round)
+   * of this, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data rint() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the round of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error
+   * estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data round() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the sin of this assuming radian 
+   * Units unless this actual Units are degrees, 
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data sin() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the sin of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data sinDegrees() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the square root of this, using default 
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error 
+   * estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data sqrt() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the tan of this assuming radian 
+   * Units unless this actual Units are degrees, 
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data tan() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the tan of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data tanDegrees() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to negate this, using default modes for
+   * sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data negate() throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the absolute value of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data abs(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arccos of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data acos(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arccos of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data acosDegrees(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data asin(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data asinDegrees(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arctan of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atan(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the arctan of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data atanDegrees(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the ceiling of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data ceil(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the cos of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data cos(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the cos of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data cosDegrees(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the exponent of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data exp(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the floor of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data floor(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the log of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data log(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the rint (essentially round)
+   * of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data rint(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the round of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data round(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the sin of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data sin(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the sin of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data sinDegrees(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the square root of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data sqrt(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the tan of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data tan(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to take the tan of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data tanDegrees(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * call unary() to negate this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  Data negate(int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * compute ranges of values in this of given RealTypes, using
+   * a dummy DisplayImplJ2D
+   * @param reals array of RealTypes whose value ranges to compute
+   * @return double[reals.length][2] giving the low and high value
+   *         in this for each RealType in reals
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException;
+
+  /**
+   * Compute ranges of values for each of 'n' RealTypes in
+   * DisplayImpl.RealTypeVector. Called from DataRenderer
+   * with n = DisplayImpl.getScalarCount().
+   * @param type ShadowType generated for MathType of this
+   * @param n number of RealTypes in DisplayImpl.RealTypeVector
+   * @return DataShadow instance containing double[][] array
+   *         of RealType ranges, and an animation sampling Set
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException;
+
+  /** 
+   * Recursive version of computeRanges(), called down through
+   * Data object tree.
+   * @param type ShadowType generated for MathType of this
+   * @param shadow DataShadow instance whose contained double[][]
+   *               array and animation sampling Set are modified
+   *               according to RealType values in this, and used
+   *               as return value
+   * @return DataShadow instance containing double[][] array
+   *         of RealType ranges, and an animation sampling Set
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException;
+
+  /**
+   * return a clone of this, except with ErrorEstimates
+   * combined with values in error, according to error_mode
+   * @param error
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return clone of this, except with ErrorEstimates set
+   *         according to values in error
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException;
+
+  /**
+   * @return a longer String than returned by toString()
+   */
+  String longString()
+         throws VisADException, RemoteException;
+
+  /** 
+   * @param pre String added to start of each line
+   * @return a longer String than returned by toString(),
+   *         indented by pre (a string of blanks)
+   */
+  String longString(String pre)
+         throws VisADException, RemoteException;
+
+  /** 
+   * A VisAD adaptation of clone that works for local or remote Data.
+   * Catches CloneNotSupportedException and throws message in a
+   * RuntimeException.
+   * @return for DataImpl return clone(), and for RemoteDataImpl
+   *         return clone() inherited from UnicastRemoteObject
+   */
+  Object dataClone() throws RemoteException;
+}
+
diff --git a/visad/DataDisplayLink.java b/visad/DataDisplayLink.java
new file mode 100644
index 0000000..e1a75a6
--- /dev/null
+++ b/visad/DataDisplayLink.java
@@ -0,0 +1,470 @@
+//
+// DataDisplayLink.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   DataDisplayLink objects define connections between DataReference
+   objects and Display objects. It extends ReferenceActionLink which
+   is the more general link between ThingReference (extended by
+   DataReference) and Action (extended by Display).<P>
+*/
+public class DataDisplayLink extends ReferenceActionLink {
+
+  /** ShadowType created for data */
+  private ShadowType shadow;
+
+  /** cached copy of linked Data, used by prepareData() */
+  private Data data;
+
+  /** ConstantMaps specific to this Data */
+  private Vector ConstantMapVector = new Vector();
+
+  /** DataRenderer associated with this Data
+      (may be multiple Data per DataRenderer) */
+  private DataRenderer renderer;
+
+  /** Vector of ScalarMaps applying to this Data */
+  private Vector SelectedMapVector = new Vector();
+
+  /** default values for DisplayRealTypes, determined by:
+      1. this.ConstantMapVector
+      2. Display.ConstantMapVector
+      3. DisplayRealType.DefaultValue */
+  private float[] defaultValues;
+
+  /** flag per Control to indicate need for transform when
+      Control changes, index by Control.getIndex() */
+  boolean[] isTransform;
+
+  /** value of System.currentTimeMillis() when doTransform() started */
+  public long start_time;
+
+  /** flag indicating current doTransform() has taken more than 500 ms */
+  public boolean time_flag;
+
+  /**
+   * construct a DataDisplayLink linking a DataReference to a Display
+   * @param ref the DataReference to link
+   * @param local_d if d is DisplayImpl, then d; if d is RemoteDisplay, then
+   *                its adapted DisplayImpl
+   * @param d the Display
+   * @param constant_maps array of ConstantMaps specific to this Data
+   * @param rend DataRenderer that creates Data depictions
+   * @param jd - unique ID among ReferenceActionLinks attached to Action
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DataDisplayLink(DataReference ref, DisplayImpl local_d, Display d,
+                  ConstantMap[] constant_maps, DataRenderer rend, long jd)
+                  throws VisADException, RemoteException {
+    super(ref, local_d, d, jd);
+    renderer = rend;
+    setConstantMaps(constant_maps, true);
+  }
+
+  /**
+   * Change ConstantMaps[] array specific to this DataDisplayLink
+   * Note this call should occur between
+   * display.disableAction()
+   * and
+   * display.enableAction()
+   *
+   * there are two ways for an application to get a DataDisplayLink:
+   * given a DisplayImpl and a DataReference:
+   *  DataDisplayLink link = (DataDisplayLink) display.findReference(ref);
+   * given a DataRenderer (assuming it has only one DataReference):
+   *  DataDisplayLink link = renderer.getLinks()[0];
+   *
+   * @param constant_maps array of ConstantMaps specific to this Data
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void setConstantMaps(ConstantMap[] constant_maps)
+                  throws VisADException, RemoteException {
+    setConstantMaps(constant_maps, false);
+  }
+
+  private void setConstantMaps(ConstantMap[] constant_maps, boolean init)
+                  throws VisADException, RemoteException {
+    Enumeration maps;
+
+    synchronized (ConstantMapVector) {
+      if (!init) {
+        maps = ConstantMapVector.elements();
+        while(maps.hasMoreElements()) {
+          ConstantMap map = (ConstantMap) maps.nextElement();
+          map.nullDisplay();
+        }
+        ConstantMapVector.removeAllElements();
+      }
+
+      DisplayImpl local_d = (DisplayImpl) getLocalAction();
+      Display d = (Display) getAction();
+  
+      if (constant_maps != null) {
+        for (int i=0; i<constant_maps.length; i++) {
+          maps = ((Vector) ConstantMapVector.clone()).elements();
+          while(maps.hasMoreElements()) {
+            ScalarMap map = (ScalarMap) maps.nextElement();
+            if (map.getDisplayScalar().equals(constant_maps[i].getDisplayScalar())) {
+              throw new DisplayException("DataDisplayLink: two ConstantMaps have" +
+                                         " the same DisplayScalar");
+            }
+          }
+  
+          if (constant_maps[i].getDisplay() != null &&
+              !ConstantMap.getAllowMultipleUseKludge()) {
+            throw new DisplayException(constant_maps[i] + " already has a display\n" +
+                        "If this Exception breaks an existing app add a call to:\n" +
+                        "ConstantMap.setAllowMultipleUseKludge(true) at the " +
+                        "start of your app \n  OR you can stop reusing ConstantMaps");
+          }
+  
+          constant_maps[i].setDisplay(local_d);
+          ConstantMapVector.addElement(constant_maps[i]);
+          local_d.addDisplayScalar(constant_maps[i]);
+        }
+      }
+      if (!init) {
+        getThingReference().incTick();
+      }
+    }
+  }
+
+  /**
+   * @return the local DisplayImpl for the linked Display
+   */
+  public DisplayImpl getDisplay() {
+    return (DisplayImpl) local_action;
+  }
+
+  /**
+   * @return the DataRenderer that creates Data depictions
+   */
+  public DataRenderer getRenderer() {
+    return renderer;
+  }
+
+  /**
+   * @return a clone of Vector of ScalarMaps applying to this Data
+   */
+  public Vector getSelectedMapVector() {
+    return (Vector) SelectedMapVector.clone();
+  }
+
+  /**
+   * add a ScalarMap applying to this Data
+   * @param map ScalarMap to add
+   */
+  public void addSelectedMapVector(ScalarMap map) {
+    if (renderer == null) return;
+    // 'synchronized' unnecessary
+    // (since prepareData is a single Thread, but ...)
+    synchronized (SelectedMapVector) {
+      if (!SelectedMapVector.contains(map)) {
+        SelectedMapVector.addElement(map);
+      }
+    }
+  }
+
+  /**
+   * clear Vectors of ScalarMaps applying to this Data and
+   * of ConstantMaps; also clear other instance variables
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void clearMaps()
+    throws RemoteException, VisADException
+  {
+    Enumeration maps;
+
+    synchronized (ConstantMapVector) {
+      maps = ConstantMapVector.elements();
+      while(maps.hasMoreElements()) {
+        ConstantMap map = (ConstantMap) maps.nextElement();
+        map.nullDisplay();
+      }
+      ConstantMapVector.removeAllElements();
+
+      SelectedMapVector.removeAllElements();
+      shadow = null;
+      data = null;
+      renderer = null;
+    }
+  }
+
+  /**
+   * Prepare to render data (include feasibility check);
+   * @return false if infeasible
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public boolean prepareData()
+         throws VisADException, RemoteException {
+    if (renderer == null) return false;
+    int[] indices;
+    int[] display_indices;
+    int[] value_indices;
+    int levelOfDifficulty;
+
+    data = ((DataReference) ref).getData();
+    if (data == null) {
+      renderer.clearExceptions();
+      renderer.addException(
+        // new DisplayException("Data is null: DataDisplayLink.prepareData"));
+        new DisplayException("Data is null"));
+      return false;
+    }
+    MathType type = data.getType();
+
+    SelectedMapVector.removeAllElements();
+
+    // calculate default values for DisplayRealTypes
+    // lowest priority: DisplayRealType.DefaultValue
+    int n = ((DisplayImpl) local_action).getDisplayScalarCount();
+    defaultValues = new float[n];
+    GraphicsModeControl mode =
+      ((DisplayImpl) local_action).getGraphicsModeControl();
+    for (int i=0; i<n; i++) {
+      DisplayRealType dreal =
+        (DisplayRealType) ((DisplayImpl) local_action).getDisplayScalar(i);
+      defaultValues[i] = (float) dreal.getDefaultValue();
+      if (Display.PointSize.equals(dreal)) {
+        defaultValues[i] = mode.getPointSize();
+      }
+      else if (Display.LineWidth.equals(dreal)) {
+        defaultValues[i] = mode.getLineWidth();
+      }
+      else if (Display.LineStyle.equals(dreal)) {
+        defaultValues[i] = mode.getLineStyle();
+      }
+      else if (Display.PolygonMode.equals(dreal)) {
+        defaultValues[i] = mode.getPolygonMode();
+      }
+      else if (Display.PolygonOffset.equals(dreal)) {
+        defaultValues[i] = mode.getPolygonOffset();
+      }
+      else if (Display.PolygonOffsetFactor.equals(dreal)) {
+        defaultValues[i] = mode.getPolygonOffsetFactor();
+      }
+      else if (Display.ColorMode.equals(dreal)) {
+        defaultValues[i] = mode.getColorMode();
+      }
+      else if (Display.CurvedSize.equals(dreal)) {
+        defaultValues[i] = mode.getCurvedSize();
+      }
+      else if (Display.MissingTransparent.equals(dreal)) {
+        defaultValues[i] = (mode.getMissingTransparent()) ? 1 : -1;
+      }
+      else if (Display.TextureEnable.equals(dreal)) {
+        defaultValues[i] = (mode.getTextureEnable()) ? 1 : -1;
+      }
+      else if (Display.AdjustProjectionSeam.equals(dreal)) {
+        defaultValues[i] = (mode.getAdjustProjectionSeam()) ? 1 : -1;
+      } 
+      else if (Display.Texture3DMode.equals(dreal)) {
+        defaultValues[i] = mode.getTexture3DMode();
+      }
+      else if (Display.CacheAppearances.equals(dreal)) {
+        defaultValues[i] = (mode.getCacheAppearances()) ? 1 : -1;
+      }
+      else if (Display.MergeGeometries.equals(dreal)) {
+        defaultValues[i] = (mode.getMergeGeometries()) ? 1 : -1;
+      }
+      else if (Display.PointMode.equals(dreal)) {
+        defaultValues[i] = (mode.getPointMode()) ? 1 : -1;
+      }
+/* WLH 21 Aug 98
+      defaultValues[i] = (float) (((DisplayRealType)
+        ((DisplayImpl) local_action).getDisplayScalar(i)).getDefaultValue());
+*/
+    }
+    // middle priority: DisplayImpl.ConstantMapVector
+    Vector temp =
+      (Vector) ((DisplayImpl) local_action).getConstantMapVector().clone();
+    Enumeration maps = temp.elements();
+/* WLH 13 July 98
+    Enumeration maps =
+      ((DisplayImpl) local_action).getConstantMapVector().elements();
+*/
+    while(maps.hasMoreElements()) {
+      ConstantMap map = (ConstantMap) maps.nextElement();
+      defaultValues[map.getDisplayScalarIndex()] = (float) map.getConstant();
+
+    }
+    // highest priority: this.ConstantMapVector
+    // WLH 13 July 98
+    maps =((Vector) ConstantMapVector.clone()).elements();
+    while(maps.hasMoreElements()) {
+      ConstantMap map = (ConstantMap) maps.nextElement();
+      // WLH 10 Aug 2001
+      int index = map.getDisplayScalarIndex();
+      if (index >= 0) defaultValues[index] = (float) map.getConstant();
+      // defaultValues[map.getDisplayScalarIndex()] = (float) map.getConstant();
+    }
+
+    try {
+      renderer.clearExceptions();
+
+      DisplayImpl local_dpy = (DisplayImpl )local_action;
+
+      shadow = type.buildShadowType(this, null);
+      ShadowType adaptedShadow = shadow.getAdaptedShadowType();
+      indices = ShadowType.zeroIndices(local_dpy.getScalarCount());
+      display_indices = ShadowType.zeroIndices(
+                  local_dpy.getDisplayScalarCount());
+      value_indices = ShadowType.zeroIndices(local_dpy.getValueArrayLength());
+      final int numControls = local_dpy.getNumberOfControls();
+      isTransform = new boolean[numControls];
+      for (int i=0; i<numControls; i++) isTransform[i] = false;
+      levelOfDifficulty =
+        shadow.checkIndices(indices, display_indices, value_indices,
+                              isTransform, ShadowType.NOTHING_MAPPED);
+      if (levelOfDifficulty == ShadowType.LEGAL) {
+        // every Control isTransform for merely LEGAL
+        // (i.e., the 'dots') rendering
+        for (int i=0; i<numControls; i++) isTransform[i] = true;
+      }
+      renderer.checkDirect();
+    }
+    catch (BadMappingException e) {
+      data = null;
+      renderer.addException(e);
+      return false;
+    }
+    catch (UnimplementedException e) {
+      data = null;
+      renderer.addException(e);
+      return false;
+    }
+    catch (RemoteException e) {
+      data = null;
+      renderer.addException(e);
+      return false;
+    }
+    // can now render data
+    return true;
+  }
+
+  /**
+   * @return ShadowType generated from MathType of linked Data
+   */
+  public ShadowType getShadow() {
+    return shadow;
+  }
+
+  /**
+   * @return linked Data (note Data is cached until
+   *         clearData() is called)
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public Data getData()
+         throws VisADException, RemoteException {
+    if (renderer == null) return null;
+    Data data_copy = data;
+    if (data_copy == null) {
+      data_copy = ((DataReference) ref).getData();
+    }
+    data = data_copy;
+    return data_copy;
+  }
+
+  /**
+   * clear cached copy of linked Data
+   */
+  public void clearData() {
+    data = null;
+  }
+
+  /**
+   * @return MathType of linked Data
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public MathType getType()
+         throws VisADException, RemoteException {
+    Data d = getData();
+    return (d == null) ? null : d.getType();
+  }
+
+  /**
+   * @return default values for DisplayRealTypes
+   */
+  public float[] getDefaultValues() {
+    return defaultValues;
+  }
+
+  /**
+   * @return linked DataReference
+   */
+  public DataReference getDataReference() {
+    return (DataReference) getThingReference();
+  }
+
+  /**
+   * @return Vector of ConstantMaps specific to this Data
+   */
+  public Vector getConstantMaps()
+  {
+    return ConstantMapVector;
+  }
+
+  /**
+   * @return Vector of ScalarMaps that apply to this Data
+   */
+  public Vector getScalarMaps()
+  {
+    return SelectedMapVector;
+  }
+
+  /**
+   * Indicates whether or not this instance is equal to an object
+   * @param o the object in question.
+   * @return <code>true</code> if and only if this instance equals o.
+   */
+  public boolean equals(Object o)
+  {
+    if (!(o instanceof DataDisplayLink)) {
+      return false;
+    }
+
+    DataDisplayLink ddl = (DataDisplayLink )o;
+    if (!getDataReference().equals(ddl.getDataReference())) {
+      return false;
+    }
+    if (!getDisplay().equals(ddl.getDisplay())) {
+      return false;
+    }
+
+    return true;
+  }
+}
+
diff --git a/visad/DataImpl.java b/visad/DataImpl.java
new file mode 100644
index 0000000..6909494
--- /dev/null
+++ b/visad/DataImpl.java
@@ -0,0 +1,1645 @@
+//
+// DataImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+import java.util.Vector;
+
+import visad.java2d.DisplayImplJ2D;
+
+/**
+   DataImpl is the superclass for VisAD's data hierarchy, inheriting
+   the Data interface.  Data objects are immutable except for the range
+   values of Field objects.<p>
+
+   VisAD Data objects are finite approximations to math objects
+   that include scalars, tuples (i.e., n-dimensional vectors), functions,
+   and certain forms of sets.  Hence, all Data objects possess a MathType,
+   which identifies the corresponding concept and is <b>not</b> a synonym
+   for the Data class, even though the class names for a Data object and
+   its corresponding MathType object (Set and SetType, e.g.) may be
+   similar.  In order to approximate their corresponding mathematical
+   entities, Data objects may use text strings or finite representations
+   of real numbers.  Also, any Data object may take the value 'missing',
+   and any sub-object of a Data object may take the value 'missing'.<p>
+
+   All of the Java arithmetical operations are defined for Data objects,
+   to the extent that they make sense for the types involved.<p>
+*/
+public abstract class DataImpl extends ThingImpl
+       implements Data {
+
+  private static final long serialVersionUID = 1L;
+
+  /** each VisAD data object has a VisAD mathematical type */
+  MathType Type;
+
+  /** parent is used to propogate notifyReferences;
+      parent DataImpl object if parent is local;
+      null if parent is remote;
+      i.e., notifyReferences does not propogate to remote parents;
+      only a single parent is supported - multiple parents are
+      not correctly notified of data changes */
+  private transient DataImpl parent;
+
+  /**
+   * construct a DataImpl with given MathType
+   * @param type  MathType
+   */
+  public DataImpl(MathType type) {
+    Type = type;
+    parent = null;
+  }
+
+  /**
+   * @return this (returns a local copy for RemoteData)
+   */
+  public DataImpl local() {
+    return this;
+  }
+
+  /**
+   * set the parent (i.e., containing Tuple or Field) of this DataImpl
+   * @param p  parent DataImpl
+   */
+  void setParent(DataImpl p) {
+    parent = p;
+  }
+
+  /**
+   * @return MathType of this Data
+   */
+  public MathType getType() {
+    return Type;
+  }
+
+  /**
+   * notify local DataReferenceImpl-s that this DataImpl has changed;
+   * incTick in RemoteDataImpl for RemoteDataReferenceImpl-s;
+   * declared public because it is defined in the Data interface
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public void notifyReferences()
+         throws VisADException, RemoteException {
+    super.notifyReferences();
+    // recursively propogate data change to parent
+    if (parent != null) parent.notifyReferences();
+  }
+
+  /**
+   * Pointwise binary operation between this and data. Applies
+   * to Reals, Tuples (recursively to components), and to Field 
+   * ranges (Field domains implicitly resampled if necessary). 
+   * Does not apply to Field domains or Sets (regarded as domains
+   * of Fields wthout ranges). Data.ADD is only op defined for
+   * Text, interpreted as concatenate. MathTypes of this and data
+   * must match, or one may match the range of the other if it is
+   * a FunctionType.
+   * @param data  other Data operand for binary operation
+   * @param op  may be Data.ADD, Data.SUBTRACT, etc; these include all
+   *             binary operations defined for Java primitive data types
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, which takes the MathType of this unless the default
+   *         Units of that MathType conflict with Units of the result,
+   *         in which case a generic MathType with appropriate Units is
+   *         constructed 
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    MathType dtype = data.getType();
+
+    MathType new_type = Type.binary( dtype, op, new Vector() );
+    return binary( data, op, new_type, sampling_mode, error_mode );
+  }
+
+  /**
+   * Pointwise binary operation between this and data. Applies
+   * to Reals, Tuples (recursively to components), and to Field 
+   * ranges (Field domains implicitly resampled if necessary). 
+   * Does not apply to Field domains or Sets (regarded as domains
+   * of Fields wthout ranges). Data.ADD is only op defined for
+   * Text, interpreted as concatenate. MathTypes of this and data
+   * must match, or one may match the range of the other if it is
+   * a FunctionType.
+   * @param data  other Data operand for binary operation
+   * @param op  may be Data.ADD, Data.SUBTRACT, etc; these include all
+   *             binary operations defined for Java primitive data types
+   * @param new_type  MathType of the result
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, with MathType = new_type
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data binary( Data data, int op, MathType new_type,
+                      int sampling_mode, int error_mode )
+              throws VisADException, RemoteException {
+    throw new TypeException("DataImpl.binary");
+  }
+
+  /**
+   * call binary() to add data to this, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data add(Data data) throws VisADException, RemoteException {
+    return binary(data, ADD, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to subtract data from this, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data subtract(Data data) throws VisADException, RemoteException {
+    return binary(data, SUBTRACT, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to multiply this by data, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data multiply(Data data) throws VisADException, RemoteException {
+    return binary(data, MULTIPLY, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to divide this by data, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data divide(Data data) throws VisADException, RemoteException {
+    return binary(data, DIVIDE, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to raise this to data power, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data pow(Data data) throws VisADException, RemoteException {
+    return binary(data, POW, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the max of this and data, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data max(Data data) throws VisADException, RemoteException {
+    return binary(data, MAX, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the min of this and data, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data min(Data data) throws VisADException, RemoteException {
+    return binary(data, MIN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing radian Units, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2(Data data) throws VisADException, RemoteException {
+    return binary(data, ATAN2, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing degree Units, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2Degrees(Data data) throws VisADException, RemoteException {
+    return binary(data, ATAN2_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the remainder of this divided by
+   * data, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data remainder(Data data) throws VisADException, RemoteException {
+    return binary(data, REMAINDER, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to add data to this
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data add(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, ADD, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to subtract data from this
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data subtract(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, SUBTRACT, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to multiply this by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data multiply(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, MULTIPLY, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to divide this by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data divide(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, DIVIDE, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to raise this to data power
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data pow(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, POW, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the max of this and data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data max(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, MAX, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the min of this and data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data min(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, MIN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing radian Units
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, ATAN2, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing degree Units
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2Degrees(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, ATAN2_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the remainder of this divided by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data remainder(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, REMAINDER, sampling_mode, error_mode);
+  }
+
+  /**
+   * Pointwise unary operation applied to this. Applies
+   * to Reals, Tuples (recursively to components), and to Field 
+   * ranges (Field domains implicitly resampled if necessary). 
+   * Does not apply to Field domains, Sets (regarded as domains
+   * of Fields wthout ranges) or Text.
+   * @param op  may be Data.ABS, Data.ACOS, etc; these include all
+   *             unary operations defined for Java primitive data types
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation, which takes the MathType of this unless
+   *         the default Units of that MathType conflict with Units of
+   *         the result, in which case a generic MathType with appropriate
+   *         Units is constructed
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data unary(int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    MathType new_type = Type.unary( op, new Vector() );
+    return unary( op, new_type, sampling_mode, error_mode );
+  }
+
+  /**
+   * Pointwise unary operation applied to this. Applies
+   * to Reals, Tuples (recursively to components), and to Field 
+   * ranges (Field domains implicitly resampled if necessary). 
+   * Does not apply to Field domains, Sets (regarded as domains
+   * of Fields wthout ranges) or Text.
+   * @param op  may be Data.ABS, Data.ACOS, etc; these include all
+   *             unary operations defined for Java primitive data types
+   * @param new_type  MathType of the result
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, with MathType = new_type
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data unary( int op, MathType new_type, int sampling_mode,
+                     int error_mode )
+              throws VisADException, RemoteException {
+    throw new TypeException("DataImpl: unary");
+  }
+
+  /** 
+   * call unary() to clone this except with a new MathType
+   * @param new_type  MathType of returned Data object
+   * @return clone of this Data object except with new MathType
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data changeMathType(MathType new_type)
+         throws VisADException, RemoteException {
+    return unary(NOP, new_type, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the absolute value of this, using
+   * default modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data abs() throws VisADException, RemoteException {
+    return unary(ABS, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acos() throws VisADException, RemoteException {
+    return unary(ACOS, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acosDegrees() throws VisADException, RemoteException {
+    return unary(ACOS_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asin() throws VisADException, RemoteException {
+    return unary(ASIN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asinDegrees() throws VisADException, RemoteException {
+    return unary(ASIN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan() throws VisADException, RemoteException {
+    return unary(ATAN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atanDegrees() throws VisADException, RemoteException {
+    return unary(ATAN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the ceiling of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data ceil() throws VisADException, RemoteException {
+    return unary(CEIL, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming radian
+   * Units unless this actual Units are degrees,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cos() throws VisADException, RemoteException {
+    return unary(COS, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cosDegrees() throws VisADException, RemoteException {
+    return unary(COS_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the exponent of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data exp() throws VisADException, RemoteException {
+    return unary(EXP, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the floor of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data floor() throws VisADException, RemoteException {
+    return unary(FLOOR, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the log of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data log() throws VisADException, RemoteException {
+    return unary(LOG, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the rint (essentially round)
+   * of this, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data rint() throws VisADException, RemoteException {
+    return unary(RINT, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the round of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error
+   * estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data round() throws VisADException, RemoteException {
+    return unary(ROUND, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming radian
+   * Units unless this actual Units are degrees,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sin() throws VisADException, RemoteException {
+    return unary(SIN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sinDegrees() throws VisADException, RemoteException {
+    return unary(SIN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the square root of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error
+   * estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sqrt() throws VisADException, RemoteException {
+    return unary(SQRT, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming radian
+   * Units unless this actual Units are degrees,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tan() throws VisADException, RemoteException {
+    return unary(TAN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tanDegrees() throws VisADException, RemoteException {
+    return unary(TAN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to negate this, using default modes for
+   * sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data negate() throws VisADException, RemoteException {
+    return unary(NEGATE, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the absolute value of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data abs(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ABS, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acos(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ACOS, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acosDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ACOS_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asin(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ASIN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asinDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ASIN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ATAN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atanDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ATAN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the ceiling of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data ceil(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(CEIL, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cos(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(COS, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cosDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(COS_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the exponent of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data exp(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(EXP, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the floor of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data floor(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(FLOOR, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the log of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data log(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(LOG, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the rint (essentially round)
+   * of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data rint(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(RINT, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the round of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data round(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ROUND, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sin(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(SIN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sinDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(SIN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the square root of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sqrt(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(SQRT, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tan(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(TAN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tanDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(TAN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to negate this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data negate(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(NEGATE, sampling_mode, error_mode);
+  }
+
+  /**
+   * returns new binary operator equivalent to op with
+   * order of operands reversed
+   * @param op an integer interpreted as an 'op' argument
+   *           to binary()
+   * @return an 'op' argument to binary() with order of
+   *         operands reversed        
+   * @throws VisADException  a VisAD error occurred
+   */
+  static int invertOp(int op) throws VisADException {
+    switch(op) {
+      case ADD:
+        return ADD;
+      case SUBTRACT:
+        return INV_SUBTRACT;
+      case INV_SUBTRACT:
+        return SUBTRACT;
+      case MULTIPLY:
+        return MULTIPLY;
+      case DIVIDE:
+        return INV_DIVIDE;
+      case INV_DIVIDE:
+        return DIVIDE;
+      case POW:
+        return INV_POW;
+      case INV_POW:
+        return POW;
+      case MAX:
+        return MAX;
+      case MIN:
+        return MIN;
+      case ATAN2:
+        return INV_ATAN2;
+      case ATAN2_DEGREES:
+        return INV_ATAN2_DEGREES;
+      case INV_ATAN2:
+        return ATAN2;
+      case INV_ATAN2_DEGREES:
+        return ATAN2_DEGREES;
+      case REMAINDER:
+        return INV_REMAINDER;
+      case INV_REMAINDER:
+        return REMAINDER;
+    }
+    throw new ArithmeticException("DataImpl.invertOp: illegal operation");
+  }
+
+  /** dummy display for computeRanges() */
+  private static DisplayImplJ2D rdisplay = null;
+  private static Object lock = new Object();
+
+  /** class used to synchronize with TRANSFORM_DONE events
+      from dummy DisplayImplJ2D used by computeRanges() */
+  public class Syncher extends Object implements DisplayListener {
+
+    /**
+     * construct Syncher, add as DisplayListener to dummy
+     * DisplayImplJ2D, enableAction() and wait for TRANSFORM_DONE
+     */
+    Syncher() {
+      try {
+        synchronized (this) {
+          rdisplay.addDisplayListener(this);
+          rdisplay.enableAction();
+          this.wait();
+        }
+      }
+      catch(InterruptedException e) {
+      }
+
+      rdisplay.removeDisplayListener(this);
+    }
+
+    /**
+     * look for TRANSFORM_DONE event from dummy DisplayImplJ2D
+     * @param e DisplayEvent from dummy DisplayImplJ2D
+     * @throws VisADException a VisAD error occurred
+     * @throws RemoteException an RMI error occurred
+     */
+    public void displayChanged(DisplayEvent e)
+           throws VisADException, RemoteException {
+      if (e.getId() == DisplayEvent.TRANSFORM_DONE) {
+        synchronized (this) {
+          this.notify();
+        }
+      }
+    }
+  }
+
+  /**
+   * compute ranges of values in this of given RealTypes, using
+   * a dummy DisplayImplJ2D
+   * @param reals array of RealTypes whose value ranges to compute
+   * @return double[reals.length][2] giving the low and high value
+   *         in this for each RealType in reals
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    synchronized (lock) {
+
+      if (rdisplay == null) {
+        // construct offscreen dummy display
+        rdisplay = new DisplayImplJ2D("dummy", 4, 4);
+      }
+
+      if (reals == null || reals.length == 0) return null;
+      int n = reals.length;
+      ScalarMap[] maps = new ScalarMap[n];
+      for (int i=0; i<n; i++) {
+        maps[i] = new ScalarMap(reals[i], Display.Shape);
+        rdisplay.addMap(maps[i]);
+      }
+      rdisplay.disableAction();
+      DataReference ref = new DataReferenceImpl("dummy");
+      ref.setData(this);
+      rdisplay.reAutoScale();
+      rdisplay.addReference(ref);
+      new Syncher(); // wait for TRANSFORM_DONE
+      double[][] ranges = new double[n][];
+      for (int i=0; i<n; i++) {
+        ranges[i] = maps[i].getRange();
+      }
+      rdisplay.removeReference(ref);
+      rdisplay.clearMaps();
+      return ranges;
+    }
+  }
+
+  /** 
+   * Compute ranges of values for each of 'n' RealTypes in
+   * DisplayImpl.RealTypeVector. Called from DataRenderer 
+   * with n = DisplayImpl.getScalarCount().
+   * @param type ShadowType generated for MathType of this
+   * @param n number of RealTypes in DisplayImpl.RealTypeVector
+   * @return DataShadow instance containing double[][] array
+   *         of RealType ranges, and an animation sampling Set
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    double[][] ranges = new double[2][n];
+    for (int i=0; i<n; i++) {
+      ranges[0][i] = Double.MAX_VALUE; // init minimums
+      ranges[1][i] = -Double.MAX_VALUE; // init maximums
+    }
+    DataShadow shadow = new DataShadow(ranges);
+    return computeRanges(type, shadow);
+  }
+
+  /**
+   * called by computeRanges() from RealTuple, SampledSet and
+   * FlatField (used for range RealTuple in FlatField, domain is
+   * handled by recursive domain Set call computeRanges()) to
+   * recursively compute ranges of reference RealTupleType
+   * @param shad_type ShadowRealTupleType of refering RealTuple
+   *                  (refering RealTuple is implicit in cases of
+   *                  SampledSet and FlatField)
+   * @param coord_in CoordinateSystem connecting refering and
+   *                 reference RealTuples
+   * @param units_in default Units for RealTypes in refering RealTuple
+   * @param shadow DataShadow instance whose contained double[][] 
+   *               array and animation sampling Set are modified
+   *               according to RealType values in reference
+   *               RealTupleType, and used as return value
+   * @param shad_ref ShadowRealTupleType of reference RealTupleType
+   * @param ranges RealType value ranges in refering RealTuple
+   * @return DataShadow instance containing double[][] array
+   *         of RealType ranges, and an animation sampling Set
+   * @throws VisADException a VisAD error occurred
+   */
+  DataShadow computeReferenceRanges(
+             ShadowRealTupleType shad_type, CoordinateSystem coord_in,
+             Unit[] units_in, DataShadow shadow,
+             ShadowRealTupleType shad_ref, double[][] ranges)
+             throws VisADException {
+    RealTupleType type = (RealTupleType) shad_type.Type;
+    RealTupleType ref = (RealTupleType) shad_ref.Type;
+    int n = ranges[0].length;
+    int len = 1;
+    // indices is a 'base-5 integer' with n quints
+    int[] indices = new int[n];
+    for (int i=0; i<n; i++) {
+      len = 5 * len;
+      indices[i] = 0;
+    }
+    // len = 5 ^ n;
+    double[][] vals = new double[n][len];
+    for (int j=0; j<len; j++) {
+      for (int i=0; i<n; i++) {
+        switch(indices[i]) {
+          case 0:
+            vals[i][j] = ranges[0][i];
+            break;
+          case 1:
+            vals[i][j] = 0.75 * ranges[0][i] + 0.25 * ranges[1][i];
+            break;
+          case 2:
+            vals[i][j] = 0.5 * (ranges[0][i] + ranges[1][i]);
+            break;
+          case 3:
+            vals[i][j] = 0.25 * ranges[0][i] + 0.75 * ranges[1][i];
+            break;
+          case 4:
+            vals[i][j] = ranges[1][i];
+            break;
+        }
+      }
+      // increment 'base-5 integer' in indices array
+      for (int i=0; i<n; i++) {
+        indices[i]++;
+        if (indices[i] == 5) {
+          indices[i] = 0;
+        }
+        else {
+          break;
+        }
+      }
+    }
+
+    // vals are the vertices of the n-dimensional box defined by ranges;
+    // tranform them
+    vals = CoordinateSystem.transformCoordinates(
+                   ref, ref.getCoordinateSystem(), ref.getDefaultUnits(), null,
+                   type, coord_in, units_in, null, vals);
+    // mix vals into shadow.ranges
+    for (int i=0; i<n; i++) {
+      double min = Double.MAX_VALUE; // init minimum
+      double max = -Double.MAX_VALUE; // init maximum
+      for (int j=0; j<len; j++) {
+        double val = vals[i][j];
+        if (val == val) {
+          min = Math.min(min, val);
+          max = Math.max(max, val);
+        }
+      }
+      int index = ((ShadowRealType) shad_ref.getComponent(i)).getIndex();
+      if (index >= 0) {
+        if (min == min) {
+          shadow.ranges[0][index] = Math.min(shadow.ranges[0][index], min);
+        }
+        if (max == max) {
+          shadow.ranges[1][index] = Math.max(shadow.ranges[1][index], max);
+        }
+      }
+    }
+    return shadow;
+  }
+
+  /**
+   * return a clone of this, except with ErrorEstimates
+   * combined with values in error, according to error_mode
+   * @param error
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return clone of this, except with ErrorEstimates set
+   *         according to values in error
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    return this;
+  }
+
+  /**
+   * A wrapper around {@link #add(Data) add} for JPython
+   */
+  public Data __add__(Data data) throws VisADException, RemoteException {
+    return add(data);
+  }
+
+  /**
+   * A wrapper around {@link #subtract(Data) subtract} for JPython
+   */
+  public Data __sub__(Data data) throws VisADException, RemoteException {
+    return subtract(data);
+  }
+
+  /**
+   * A wrapper around {@link #multiply(Data) multiply} for JPython
+   */
+  public Data __mul__(Data data) throws VisADException, RemoteException {
+    return multiply(data);
+  }
+
+  /**
+   * A wrapper around {@link #divide(Data) divide} for JPython
+   */
+  public Data __div__(Data data) throws VisADException, RemoteException {
+    return divide(data);
+  }
+
+  /**
+   * A wrapper around {@link #pow(Data) pow} for JPython
+   */
+  public Data __pow__(Data data) throws VisADException, RemoteException {
+    return pow(data);
+  }
+
+  /**
+   * A wrapper around {@link #remainder(Data) remainder} for JPython
+   */
+  public Data __mod__(Data data) throws VisADException, RemoteException {
+    return remainder(data);
+  }
+
+  /**
+   * A wrapper around {@link #negate() negate} for JPython
+   */
+  public Data __neg__() throws VisADException, RemoteException {
+    return negate();
+  }
+
+  /**
+   * A wrapper around {@link #__add__(Data) __add__} for JPython
+   */
+  public Data __add__(double data) throws VisADException, RemoteException {
+    return add(new Real(data));
+  }
+
+  /**
+   * A wrapper around {@link #__add__(Data) __add__} for JPython
+   */
+  public Data __radd__(double data) throws VisADException, RemoteException {
+    return add(new Real(data));
+  }
+
+  /**
+   * A wrapper around {@link #__sub__(Data) __sub__} for JPython
+   */
+  public Data __sub__(double data) throws VisADException, RemoteException {
+    return subtract(new Real(data));
+  }
+
+  /**
+   * A wrapper around {@link #__sub__(Data) __sub__} for JPython
+   */
+  public Data __rsub__(double data) throws VisADException, RemoteException {
+    return (new Real(data)).subtract(this);
+  }
+
+
+  /**
+   * A wrapper around {@link #__mul__(Data) __mul__} for JPython
+   */
+  public Data __mul__(double data) throws VisADException, RemoteException {
+    return multiply(new Real(data));
+  }
+
+  /**
+   * A wrapper around {@link #__mul__(Data) __mul__} for JPython
+   */
+  public Data __rmul__(double data) throws VisADException, RemoteException {
+    return multiply(new Real(data));
+  }
+
+
+  /**
+   * A wrapper around {@link #__div__(Data) __div__} for JPython
+   */
+  public Data __div__(double data) throws VisADException, RemoteException {
+    return divide(new Real(data));
+  }
+
+  /**
+   * A wrapper around {@link #__div__(Data) __div__} for JPython
+   */
+  public Data __rdiv__(double data) throws VisADException, RemoteException {
+    return (new Real(data)).divide(this);
+  }
+
+
+  /**
+   * A wrapper around {@link #__pow__(Data) __pow__} for JPython
+   * For low powers, do the multiply directly to preserve units.
+   */
+  public Data __pow__(double data) throws VisADException, RemoteException {
+    if(data == 2.0) return multiply(this);
+    if(data == 3.0) return multiply( multiply(this) );
+    if(data == 4.0) return multiply( multiply( multiply(this) ) );
+    return pow(new Real(data));
+  }
+  
+  /**
+   * A wrapper around {@link #__pow__(Data) __pow__} for JPython
+   */
+  public Data __rpow__(double data) throws VisADException, RemoteException {
+    return (new Real(data)).pow(this);
+  }
+
+
+  /**
+   * A wrapper around {@link #__mod__(Data) __mod__} for JPython
+   */
+  public Data __mod__(double data) throws VisADException, RemoteException {
+    return remainder(new Real(data));
+  }
+
+  /**
+   * A wrapper around {@link #__mod__(Data) __mod__} for JPython
+   */
+  public Data __rmod__(double data) throws VisADException, RemoteException {
+    return (new Real(data)).remainder(this);
+  }
+
+
+  /**
+   * A VisAD adaptation of clone that works for local or remote Data.
+   * Catches CloneNotSupportedException and throws message in a
+   * RuntimeException.
+   * @return for DataImpl return clone(), and for RemoteDataImpl
+   *         return clone() inherited from UnicastRemoteObject
+   */
+  public Object dataClone() {
+    try {
+      return clone();
+    }
+    catch (CloneNotSupportedException ex) {
+      throw new RuntimeException(ex.toString());
+    }
+  }
+
+  /**
+   * <p>Clones this instance.  Information on the parent object of this instance
+   * is not cloned, so -- following the general contract of the <code>clone()
+   * </code> method -- subclasses should not test for equality of the parent
+   * object in any <code>equals(Object)</code> method.</p>
+   *
+   * <p>This implementation never throws {@link CloneNotSupportedException}.</p>
+   *
+   * @return                            A clone of this instance.
+   * @throws CloneNotSupportedException if cloning isn't supported.
+   */
+  public Object clone() throws CloneNotSupportedException {
+    DataImpl clone = (DataImpl)super.clone();
+
+    clone.parent = null;
+    clone.rdisplay = null;
+    clone.lock = new Object();
+
+    return clone;
+  }
+
+  /**
+   * @return a String representation of this
+   */
+  public String toString() {
+    try {
+      return longString("");
+    }
+    catch(VisADException e) {
+      return e.toString();
+    }
+    catch(RemoteException e) {
+      return e.toString();
+    }
+  }
+
+  /** 
+   * @return a longer String than returned by toString() 
+   */
+  public String longString()
+         throws VisADException, RemoteException {
+    return longString("");
+  }
+
+  /** 
+   * @param pre String added to start of each line
+   * @return a longer String than returned by toString(),
+   *         indented by pre (a string of blanks)
+   */
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    throw new TypeException("DataImpl.longString");
+  }
+
+  /**
+   * Simple DataImpl test, invoked as 'java visad.DataImpl'.
+   * @param args array of command line arguments (not used)
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    RealType[] types3d = {RealType.Latitude, RealType.Longitude, RealType.Radius};
+    RealTupleType earth_location3d = new RealTupleType(types3d);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType grid_tuple = new FunctionType(earth_location3d, radiance);
+
+    int size3d = 6;
+    float level = 2.5f;
+    FlatField grid3d = FlatField.makeField(grid_tuple, size3d, false);
+
+    RealType[] types = {RealType.Latitude, RealType.Longitude, RealType.Radius,
+                        vis_radiance, ir_radiance, RealType.Time};
+    double[][] ranges = grid3d.computeRanges(types);
+    for (int i=0; i<ranges.length; i++) {
+      System.out.println(types[i] + ": " + ranges[i][0] + " to " + ranges[i][1]);
+    }
+    System.out.println(" ");
+
+    FunctionType func = new FunctionType(radiance, RealType.Time);
+    Integer2DSet fset = new Integer2DSet(2, 2);
+    FlatField ff = new FlatField(func, fset);
+    ff.setSamples(new float[][] {{0.0f, -1.0f, 1.0f, 2.0f}});
+    ranges = ff.computeRanges(types);
+    for (int i=0; i<ranges.length; i++) {
+      System.out.println(types[i] + ": " + ranges[i][0] + " to " + ranges[i][1]);
+    }
+    System.exit(0);
+  }
+
+}
+
diff --git a/visad/DataReference.java b/visad/DataReference.java
new file mode 100644
index 0000000..ce237e7
--- /dev/null
+++ b/visad/DataReference.java
@@ -0,0 +1,82 @@
+//
+// DataReference.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   DataReference is the VisAD interface for named holders for data
+   objects.  It can provide a symbol table binding between a name and
+   a variable for a user interface that includes a formula interpreter,
+   or a full language interpreter (e.g., a Java interpreter).<P>
+
+   During computations the Data object referenced by a DataReference
+   may change.  DataReference objects are passed to Display objects, so
+   that a display may depict the changing values of named variables.<P>
+
+   DataReference is a source of ThingChangedEvent-s, and thus defines
+   addThingChangedListener and removeThingChangedListener.<P>
+
+   DataReference objects may be local (DataReferenceImpl) or
+   remote (RemoteDataReferenceImpl).<P>
+*/
+public interface DataReference extends ThingReference {
+
+  /**
+   * set reference to data, replacing any currently referenced
+   * Data object; if this is local (i.e., an instance of
+   * DataReferenceImpl) then the Data argument must also be
+   * local (i.e., an instance of DataImpl);
+   * if this is Remote (i.e., an instance of RemoteDataReference)
+   * then a local Data argument (i.e., an instance of DataImpl)
+   * will be passed by copy and a remote Data argument (i.e., an
+   * instance of RemoteData) will be passed by remote reference;
+   * invokes d.addReference(DataReference r)
+   * @param d Data object to be set
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void setData(Data d) throws VisADException, RemoteException;
+
+  /**
+   * @return referenced Data object, or null if none
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  Data getData() throws VisADException, RemoteException;
+
+  /**
+   * this is more efficient than getData().getType() for
+   * RemoteDataReferences
+   * @return the MathType of referenced Data object, or null if none;
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  MathType getType() throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/DataReferenceImpl.java b/visad/DataReferenceImpl.java
new file mode 100644
index 0000000..9bd589c
--- /dev/null
+++ b/visad/DataReferenceImpl.java
@@ -0,0 +1,114 @@
+//
+// DataReferenceImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   DataReferenceImpl is a local implementation of DataReference.<P>
+
+   DataReferenceImpl is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public class DataReferenceImpl extends ThingReferenceImpl
+       implements DataReference {
+  /**
+   * Constructs from a name for the instance.
+   *
+   * @param name                   The name for this instance.
+   * @throws VisADException        if the name is <code>null</code>.
+   */
+  public DataReferenceImpl(String name) throws VisADException {
+    super(name);
+  }
+
+  /** 
+   * @return referenced Data object, or null if none 
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public Data getData() {
+    return (Data) getThing();
+  }
+
+  /** 
+   * this is more efficient than getData().getType() for 
+   * RemoteDataReferences
+   * @return the MathType of referenced Data object, or null if none;
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public MathType getType()
+         throws VisADException, RemoteException {
+    Data data = getData();
+    return (data == null) ? null : data.getType();
+  }
+
+  /**
+   * Sets the Data object to which this instance refers.
+   *
+   * @param d                     The data object.
+   * @throws ReferenceException   if the data object is <code>null</code>.
+   * @throws RemoteVisADException if the data object is a {@link RemoteData}.
+   */
+  public void setData(Data d)
+         throws VisADException, RemoteException {
+    setThing(d);
+  }
+
+  /**
+   * method for use by setData() method of the RemoteDataReferenceImpl
+   * that adapts this DataReferenceImpl
+   * @param d RemoteData being set
+   * @param r RemoteDataReference adapting this DataReferenceImpl
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void adaptedSetData(RemoteData d, RemoteDataReference r)
+               throws VisADException, RemoteException {
+    adaptedSetThing(d, r);
+  }
+
+  /**
+   * Indicates whether or not this instance is equal to an object
+   * @param obj the object in question.
+   * @return <code>true</code> if and only if this instance equals o.
+   */
+  public boolean equals(Object obj) {
+    if (!(obj instanceof DataReference)) return false;
+    return obj == this;
+  }
+
+  /**
+   * @return String representation of this
+   */
+  public String toString() {
+    return "DataReference " + Name;
+  }
+
+}
+
diff --git a/visad/DataRenderer.java b/visad/DataRenderer.java
new file mode 100644
index 0000000..80b2c85
--- /dev/null
+++ b/visad/DataRenderer.java
@@ -0,0 +1,2705 @@
+//
+// DataRenderer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   DataRenderer is the VisAD abstract super-class for graphics rendering
+   algorithms.  These transform Data objects into 3-D (or 2-D)
+   depictions in a Display window.<P>
+
+   DataRenderer is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public abstract class DataRenderer extends Object implements Cloneable {
+
+  /** DisplayImpl this is associated with */
+  private DisplayImpl display = null;
+
+  /** used to insert output into scene graph */
+  private DisplayRenderer displayRenderer = null;
+
+  /** links to Data to be renderer by this */
+  private transient DataDisplayLink[] Links = null;
+
+  /** flag from DataDisplayLink.prepareData() */
+  private boolean[] feasible; // it's a miracle if this is correct
+
+  private boolean[] is_null; // WLH 7 May 2001
+
+  /** flag to indicate that DataDisplayLink.prepareData() was invoked */
+  private boolean[] changed;
+
+  private boolean any_changed;
+  private boolean all_feasible;
+  private boolean any_transform_control;
+
+  /** a Vector of BadMappingException and UnimplementedException
+      Strings generated during the last invocation of doAction() */
+  private Vector exceptionVector = new Vector();
+
+  /** flag indicating whether to suppress Exceptions
+      generated during doAction() */
+  private boolean suppress_exceptions = false;
+
+  /** flag for visibility of Data depictions */
+  protected boolean enabled = true;
+
+  /**
+   * construct a DataRenderer
+   */
+  public DataRenderer() {
+    Links = null;
+    display = null;
+  }
+
+  /**
+   * clear Vector of Exceptions generated during doAction()
+   */
+  public void clearExceptions() {
+    exceptionVector.removeAllElements();
+  }
+
+  /**
+   * set a flag indicating whether to suppress Exceptions
+   * generated during doAction()
+   */
+  public void suppressExceptions(boolean suppress) {
+    suppress_exceptions = suppress;
+  }
+    
+  /**
+   * add a BadMappingException or UnimplementedException to
+   * Vector of Exceptions generated during doAction()
+   * @param error Exception to add
+   */
+  public void addException(Exception error) {
+    if (display == null) return;
+    exceptionVector.addElement(error);
+    // System.out.println(error.getMessage());
+  }
+
+  /**
+   * there is no need to over-ride this method, but it may be invoked
+   * by DisplayRenderer; gets a clone of exceptionVector to avoid
+   * concurrent access by Display thread
+   * @return a Vector of Strings from the BadMappingExceptions
+   * and UnimplementedExceptions generated during the last invocation
+   * of this DataRenderer's doAction method;
+   */
+  public Vector getExceptionVector() {
+    return (suppress_exceptions ? new Vector() : (Vector) exceptionVector.clone());
+  }
+
+  /**
+   * @return flag indicating whether depiction generation is feasible
+   * for all linked Data
+   */
+  public boolean get_all_feasible() {
+    return all_feasible;
+  }
+
+  /**
+   * @return flag indicating whether any linked Data have changed
+   * since last invocation of prepareAction()
+   */
+  public boolean get_any_changed() {
+    return any_changed;
+  }
+
+  /**
+   * @return flag indicating whether any Controls associated with
+   * ScalarMaps applying to any linked Data have changed and require
+   * re-transform
+   */
+  public boolean get_any_transform_control() {
+    return any_transform_control;
+  }
+
+  /**
+   * set flag indicating whether depiction generation is feasible
+   * for all linked Data
+   * @param b value to set in flag
+   */
+  public void set_all_feasible(boolean b) {
+    all_feasible = b;
+  }
+
+  /**
+   * set DataDisplayLinks for linked Data, and set associated DisplayImpl
+   * @param links array of DataDisplayLinks to set
+   * @param d associated DisplayImpl to set
+   * @throws VisADException a VisAD error occurred
+   */
+  public abstract void setLinks(DataDisplayLink[] links, DisplayImpl d)
+           throws VisADException;
+
+  /**
+   * Sets the visibility of the data being rendered by this instance.
+   *
+   * @param on                 Whether or not to render the data.
+   */
+  public void toggle(boolean on) {
+    enabled = on;
+  }
+
+  /**
+   * Returns the visibility of the data being rendered by this instance.
+   *
+   * @return                   Whether or not the data is being rendered.
+   */
+  public boolean getEnabled() {
+    return enabled;
+  }
+
+  /**
+   * set DataDisplayLinks for linked Data, including constructing
+   * arrays of booleans associated with DataDisplayLinks; called by
+   * setLinks(DataDisplayLink[], DisplayImpl)
+   * @param links array of DataDisplayLinks to set
+   */
+  public synchronized void setLinks(DataDisplayLink[] links) {
+    if (display == null) return;
+    if (links == null || links.length == 0) return;
+    Links = links;
+    feasible = new boolean[Links.length];
+    is_null = new boolean[Links.length];
+    changed = new boolean[Links.length];
+    for (int i=0; i<Links.length; i++) {
+      feasible[i] = false;
+      is_null[i] = true;
+    }
+  }
+
+  /**
+   * @return an array of DataDisplayLinks to Data objects to be rendered
+   * (Data objects are accessed by DataDisplayLink.getData())
+   */
+  public DataDisplayLink[] getLinks() {
+    return Links;
+  }
+
+  /**
+   * @return DisplayImpl associated with this DataRenderer
+   */
+  public DisplayImpl getDisplay() {
+    return display;
+  }
+
+  /**
+   * set DisplayImpl associated with this DataRenderer
+   * @param d DisplayImpl to set
+   */
+  public void setDisplay(DisplayImpl d) {
+    display = d;
+  }
+
+  /**
+   * @return  associated with this DataRenderer
+   */
+  public DisplayRenderer getDisplayRenderer() {
+    return displayRenderer;
+  }
+
+  /**
+   * set DisplayRenderer associated with this DataRenderer
+   * @param r DisplayRenderer to set
+   */
+  public void setDisplayRenderer(DisplayRenderer r) {
+    displayRenderer = r;
+  }
+
+  /**
+   * @return flag indicating whether there is any pending need
+   * for re-transform for this DataRenderer
+   */
+  public boolean checkAction() {
+    if (display == null) return false;
+    for (int i=0; i<Links.length; i++) {
+      if (Links[i].checkTicks() || !feasible[i]) {
+        return true;
+      }
+
+      // check if this Data includes any changed Controls
+      Enumeration maps = Links[i].getSelectedMapVector().elements();
+      while(maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        if (map.checkTicks(this, Links[i])) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * check if re-transform is needed; if initialize is true then
+   * compute ranges for RealTypes and Animation sampling
+   * @param go flag indicating that re-transform is required for
+   *           at least one DataRenderer linked to DisplayImpl
+   * @param initialize flag indicating that initialization (i.e.,
+   *                   auto-scaling) is required
+   * @param shadow DataShadow shared by prepareAction() method of
+   *               all DataRenderers linked to DisplayImpl
+   * @return DataShadow containing ranges and animation sampling
+   *         Set (return null if no need for initialization)
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DataShadow prepareAction(boolean go, boolean initialize,
+                                  DataShadow shadow)
+         throws VisADException, RemoteException {
+    if (display == null) return null;
+    any_changed = false;
+    all_feasible = true;
+    any_transform_control = false;
+
+    for (int i=0; i<Links.length; i++) {
+      changed[i] = false;
+      DataReference ref = Links[i].getDataReference();
+      // test for changed Controls that require doTransform
+
+      boolean do_prepare = Links[i].checkTicks() || !feasible[i] || go;
+      if (feasible[i] && !do_prepare) {
+        // check if this Data includes any changed Controls
+        Enumeration maps = Links[i].getSelectedMapVector().elements();
+        while(maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) maps.nextElement();
+          if (map.checkTicks(this, Links[i])) {
+            do_prepare = true;
+          }
+        }
+      }
+/*
+System.out.println("prepareAction " + display.getName() + " " +
+                   Links[i].getThingReference().getName() +
+                   " Links[" + i + "].checkTicks() = " + Links[i].checkTicks() +
+                   " feasible[" + i + "] = " + feasible[i] + " go = " + go +
+                   " do_prepare = " + do_prepare);
+*/
+      if (do_prepare) {
+        // data has changed - need to re-display
+        changed[i] = true;
+        any_changed = true;
+
+        // create ShadowType for data, classify data for display
+        try {
+          feasible[i] = Links[i].prepareData();
+          is_null[i] = (Links[i].getData() == null); // WLH 7 May 2001
+        } catch (RemoteException re) {
+          if (visad.collab.CollabUtil.isDisconnectException(re)) {
+            getDisplay().connectionFailed(this, Links[i]);
+            removeLink(Links[i]);
+            i--;
+            continue;
+          }
+          throw re;
+        }
+
+        if (!feasible[i]) {
+          all_feasible = false;
+          clearBranch();
+        }
+        if (initialize && feasible[i]) {
+          // compute ranges of RealTypes and Animation sampling
+          ShadowType type = Links[i].getShadow().getAdaptedShadowType();
+          Data data;
+          try {
+            data = Links[i].getData();
+          } catch (RemoteException re) {
+            if (visad.collab.CollabUtil.isDisconnectException(re)) {
+              getDisplay().connectionFailed(this, Links[i]);
+              removeLink(Links[i]);
+              i--;
+              continue;
+            }
+            throw re;
+          }
+
+          shadow = computeRanges(data, type, shadow);
+        }
+      } // end if (Links[i].checkTicks() || !feasible[i] || go)
+
+      if (feasible[i]) {
+        // check if this Data includes any changed Controls
+        Enumeration maps = Links[i].getSelectedMapVector().elements();
+        while(maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) maps.nextElement();
+          if (map.checkTicks(this, Links[i])) {
+            any_transform_control = true;
+          }
+        }
+      } // end if (feasible[i])
+
+    } // end for (int i=0; i<Links.length; i++)
+/*
+System.out.println("any_changed = " + any_changed +
+                   " all_feasible = " + all_feasible +
+                   " any_transform_control = " + any_transform_control);
+*/
+    return shadow;
+  }
+
+  /**
+   * Compute ranges of values for each RealType in
+   * DisplayImpl.RealTypeVector.
+   * @param data Data object in which to compute ranges of RealType values
+   * @param type ShadowType generated for MathType of data
+   * @param shadow DataShadow instance whose contained double[][]
+   *               array and animation sampling Set are modified
+   *               according to RealType values in data, and used
+   *               as return value
+   * @return DataShadow instance containing double[][] array
+   *         of RealType ranges, and an animation sampling Set
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DataShadow computeRanges(Data data, ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    if (display == null) return null;
+    if (shadow == null) {
+      shadow =
+        data.computeRanges(type, display.getScalarCount());
+    }
+    else {
+      shadow = data.computeRanges(type, shadow);
+    }
+    return shadow;
+  }
+
+  /**
+   * clear part of Display scene graph generated by this DataRenderer
+   */
+  public abstract void clearBranch();
+
+  /**
+   * transform linked Data objects into a scene graph depiction,
+   * if any Data object values have changed or relevant Controls
+   * have changed; DataRenderers that assume the default
+   * implementation of DisplayImpl.doAction can determine
+   * whether re-transform is needed by:
+   *   (get_all_feasible() &&
+   *    (get_any_changed() || get_any_transform_control()))
+   * these flags are computed by the default DataRenderer
+   * implementation of prepareAction()
+   * @return flag indicating if the transform was done successfully
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract boolean doAction() throws VisADException, RemoteException;
+
+  /**
+   * @return flag indicating whether initialization (i.e.,
+   *         auto-scale) is needed on next re-transform
+   */
+  public boolean getBadScale(boolean anyBadMap) {
+    if (display == null) return false;
+    boolean badScale = false;
+    for (int i=0; i<Links.length; i++) {
+      if (!feasible[i] && (anyBadMap || !is_null[i])) {
+/*
+try {
+  System.out.println("getBadScale not feasible " +
+                     Links[i].getThingReference().getName());
+}
+catch (VisADException e) {}
+catch (RemoteException e) {}
+*/
+        return true;
+      }
+      Enumeration maps = Links[i].getSelectedMapVector().elements();
+      while(maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        badScale |= map.badRange();
+/*
+if (map.badRange()) {
+  System.out.println("getBadScale: " + map.getScalar().getName() + " -> " +
+                     map.getDisplayScalar().getName());
+}
+*/
+      }
+    }
+// System.out.println("getBadScale return " + badScale);
+    return badScale;
+  }
+
+  /**
+   * clear any scene graph created by this DataRenderer, and
+   * clear all instance variables
+   */
+  public void clearScene() {
+// test for display == null in methods
+    display = null;
+    displayRenderer = null;
+    Links = null;
+    exceptionVector.removeAllElements();
+
+// clear flow rendering and direct manipulation variables
+    shadow_data_out = null;
+    data_out = null;
+    data_units_out = null;
+    shadow_data_in = null;
+    data_in = null;
+    data_units_in = null;
+    data_coord_in = null;
+    sdo_maps = null;
+    sdi_maps = null;
+    rvts = new RealVectorType[] {null, null};
+    display_coordinate_system = null;
+    spatial_tuple = null;
+    lat_map = null;
+    lon_map = null;
+
+    link = null;
+    ref = null;
+    type = null;
+    shadow = null;
+    directMap = new ScalarMap[] {null, null, null};
+    tuple = null;
+  }
+
+  /**
+   * clear all information associated with AnimationControls
+   * and ValueControls created by this DataRenderer
+   */
+  public void clearAVControls() {
+    if (display == null) return;
+    Enumeration controls = display.getControls(AVControl.class).elements();
+    while (controls.hasMoreElements()) {
+      ((AVControl )controls.nextElement()).clearSwitches(this);
+    }
+
+    // a convenient place to throw this in
+    ProjectionControl control = display.getProjectionControl();
+    control.clearSwitches(this);
+
+    // a convenient place to throw this in
+    lat_index = -1;
+    lon_index = -1;
+  }
+
+  /**
+   * factory method for constructing a subclass of ShadowType appropriate
+   * for the graphics API, that also adapts ShadowFunctionType;
+   * ShadowType trees are constructed that 'shadow' the MathType trees of
+   * Data to be depicted, via recursive calls to buildShadowType() methods
+   * of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
+   * to Shadow*Type constructors, then back to buildShadowType() methods;
+   * the recursive call chain is initiated by DataDisplayLink.prepareData()
+   * calls to buildShadowType() methods of MathType sub-classes;
+   * @param type FunctionType that returned ShadowType will shadow
+   * @param link DataDisplayLink linking Data to be depicted
+   * @param parent parent in ShadowType tree structure
+   * @return constructed ShadowType
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException;
+
+  /**
+   * factory for constructing a subclass of ShadowType appropriate
+   * for the graphics API, that also adapts ShadowRealTupleType;
+   * ShadowType trees are constructed that 'shadow' the MathType trees of
+   * Data to be depicted, via recursive calls to buildShadowType() methods 
+   * of MathType sub-classes, to DataRenderer.makeShadow*Type() methods, 
+   * to Shadow*Type constructors, then back to buildShadowType() methods;
+   * the recursive call chain is initiated by DataDisplayLink.prepareData()
+   * calls to buildShadowType() methods of MathType sub-classes;
+   * @param type FunctionType that returned ShadowType will shadow
+   * @param link DataDisplayLink linking Data to be depicted
+   * @param parent parent in ShadowType tree structure
+   * @return constructed ShadowType
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException;
+
+  /**
+   * factory for constructing a subclass of ShadowType appropriate
+   * for the graphics API, that also adapts ShadowRealType;
+   * ShadowType trees are constructed that 'shadow' the MathType trees of
+   * Data to be depicted, via recursive calls to buildShadowType() methods
+   * of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
+   * to Shadow*Type constructors, then back to buildShadowType() methods;
+   * the recursive call chain is initiated by DataDisplayLink.prepareData()
+   * calls to buildShadowType() methods of MathType sub-classes;
+   * @param type FunctionType that returned ShadowType will shadow
+   * @param link DataDisplayLink linking Data to be depicted
+   * @param parent parent in ShadowType tree structure
+   * @return constructed ShadowType 
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException;
+
+  /**
+   * factory for constructing a subclass of ShadowType appropriate
+   * for the graphics API, that also adapts ShadowSetType;
+   * ShadowType trees are constructed that 'shadow' the MathType trees of
+   * Data to be depicted, via recursive calls to buildShadowType() methods
+   * of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
+   * to Shadow*Type constructors, then back to buildShadowType() methods;
+   * the recursive call chain is initiated by DataDisplayLink.prepareData()
+   * calls to buildShadowType() methods of MathType sub-classes;
+   * @param type FunctionType that returned ShadowType will shadow
+   * @param link DataDisplayLink linking Data to be depicted
+   * @param parent parent in ShadowType tree structure
+   * @return constructed ShadowType 
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException;
+
+  /**
+   * factory for constructing a subclass of ShadowType appropriate
+   * for the graphics API, that also adapts ShadowTextType;
+   * ShadowType trees are constructed that 'shadow' the MathType trees of
+   * Data to be depicted, via recursive calls to buildShadowType() methods
+   * of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
+   * to Shadow*Type constructors, then back to buildShadowType() methods;
+   * the recursive call chain is initiated by DataDisplayLink.prepareData()
+   * calls to buildShadowType() methods of MathType sub-classes;
+   * @param type FunctionType that returned ShadowType will shadow
+   * @param link DataDisplayLink linking Data to be depicted
+   * @param parent parent in ShadowType tree structure
+   * @return constructed ShadowType 
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract ShadowType makeShadowTextType(
+         TextType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException;
+
+  /**
+   * factory for constructing a subclass of ShadowType appropriate
+   * for the graphics API, that also adapts ShadowTupleType;
+   * ShadowType trees are constructed that 'shadow' the MathType trees of
+   * Data to be depicted, via recursive calls to buildShadowType() methods
+   * of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
+   * to Shadow*Type constructors, then back to buildShadowType() methods;
+   * the recursive call chain is initiated by DataDisplayLink.prepareData()
+   * calls to buildShadowType() methods of MathType sub-classes;
+   * @param type FunctionType that returned ShadowType will shadow
+   * @param link DataDisplayLink linking Data to be depicted
+   * @param parent parent in ShadowType tree structure
+   * @return constructed ShadowType 
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public abstract ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException;
+
+  /**
+   * DataRenderer-specific decision about which Controls require
+   * re-transform; may be over-ridden by DataRenderer sub-classes;
+   * this decision may use some values computed by link.prepareData()
+   * @param control Control being judged whether it needs re-transform
+   * @param link DataDisplayLink possibly involved in decision
+   * @return flag indicating whether re-transform is needed
+   */
+  public boolean isTransformControl(Control control, DataDisplayLink link) {
+    if (display == null) return false;
+    if (control instanceof ProjectionControl ||
+        control instanceof ToggleControl) {
+      return false;
+    }
+/* WLH 1 Nov 97 - temporary hack -
+   RangeControl changes always require Transform
+   ValueControl and AnimationControl never do
+
+    if (control instanceof AnimationControl ||
+        control instanceof ValueControl ||
+        control instanceof RangeControl) {
+      return link.isTransform[control.getIndex()];
+*/
+    if (control instanceof AnimationControl ||
+        control instanceof ValueControl) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * used by ShadowFunctionOrSetType for transform time-out hack
+   * @return single DataDisplayLink (over-ridden by sub-classes)
+   */
+  public DataDisplayLink getLink() {
+    return null;
+  }
+
+  /**
+   * @return flag indicating whether texture mapping is legal
+   *         for this DataRenderer
+   */
+  public boolean isLegalTextureMap() {
+    return true;
+  }
+
+  /* ********************** */
+  /*  flow rendering stuff  */
+  /* ********************** */
+
+  // value array (display_values) indices
+  //   ((ScalarMap) MapVector.elementAt(valueToMap[index]))
+  // can get these indices through shadow_data_out or shadow_data_in
+
+
+  /** true if lat and lon in data_in & shadow_data_in is allSpatial
+      or if lat and lon in data_in & lat_lon_in_by_coord */
+  boolean lat_lon_in = false;
+  /** true if lat_lon_in and shadow_data_out is allSpatial, i.e.,
+      map from lat, lon to display is through data CoordinateSystem */
+  boolean lat_lon_in_by_coord = false;
+  /** true if lat and lon in data_out & shadow_data_out is allSpatial */
+  boolean lat_lon_out = false;
+  /** true if lat_lon_out and shadow_data_in is allSpatial, i.e.,
+      map from lat, lon to display is inverse via data CoordinateSystem */
+  boolean lat_lon_out_by_coord = false;
+
+  /** earth dimension, either 2, 3 or -1 (for none) */
+  int lat_lon_dimension = -1;
+
+  /** Shadow of reference RealTupleType in Data */
+  ShadowRealTupleType shadow_data_out = null;
+  /** reference RealTupleType in Data */
+  RealTupleType data_out = null;
+  /** Units of reference RealTupleType in Data */
+  Unit[] data_units_out = null;
+  // CoordinateSystem data_coord_out is always null
+
+  /** Shadow of RealTupleType with reference in Data */
+  ShadowRealTupleType shadow_data_in = null;
+  /** RealTupleType with reference in Data */
+  RealTupleType data_in = null;
+  /** Units of RealTupleType with reference in Data */
+  Unit[] data_units_in = null;
+  /** CoordinateSystems relating data_in to data_out,
+      usually just one, but may be one per point */
+  CoordinateSystem[] data_coord_in = null;
+
+  /** spatial ScalarMaps for allSpatial shadow_data_out */
+  ScalarMap[] sdo_maps = null;
+  /** spatial ScalarMaps for allSpatial shadow_data_in */
+  ScalarMap[] sdi_maps = null;
+  /** indices in DisplayTupleType of DisplayRealTypes in sdo_maps */
+  int[] sdo_spatial_index = null;
+  /** indices in DisplayTupleType of DisplayRealTypes in sdi_maps */
+  int[] sdi_spatial_index = null;
+
+  /** index of RealType.Latitude
+      if lat_lon_in then index in data_in
+      if lat_lon_out then index in data_out
+      if lat_lon_spatial then values index */
+  int lat_index = -1;
+  /** index of RealType.Longitude
+      if lat_lon_in then index in data_in
+      if lat_lon_out then index in data_out
+      if lat_lon_spatial then values indices */
+  int lon_index = -1;
+  /** other index if lat & lon in a RealTupleType of length 3,
+      otherwise -1 */
+  int other_index = -1;
+  /** true if other_index Units convertable to meter */
+  boolean other_meters = false;
+
+  /** RealVectorTypes (extends RealTupleType) mapped to flow1 and
+      flow2, to be tested for being instances of EarthVectorType;
+      values will be either data_in, data_out or null */
+  RealVectorType[] rvts = {null, null};
+
+  /**
+   * @param index 0 or 1 for flow1 and flow2
+   * @return RealVectorType (extends RealTupleType) mapped to flow1 or
+   *         flow2 (values will be either data_in, data_out or null)
+   */
+  public RealVectorType getRealVectorTypes(int index) {
+    if (index == 0 || index == 1) return rvts[index];
+    else return null;
+  }
+
+  /**
+   * @return indices of RealType.Latitude and RealType.Longitude
+   *         in data_in, data_out, or just spatial value array
+   */
+  public int[] getLatLonIndices() {
+    return new int[] {lat_index, lon_index};
+  }
+
+  /**
+   * @param indices indices of RealType.Latitude and RealType.Longitude
+   *                in data_in, data_out, or just spatial value array
+   */
+  public void setLatLonIndices(int[] indices) {
+    lat_index = indices[0];
+    lon_index = indices[1];
+  }
+
+  /**
+   * @return earth dimension, either 2, 3 or -1 (for none)
+   */
+  public int getEarthDimension() {
+    return lat_lon_dimension;
+  }
+
+  /**
+   * @return Units of earth coordinates used in Data
+   */
+  public Unit[] getEarthUnits() {
+    if (display == null) return null;
+    Unit[] units = null;
+    if (lat_lon_in) {
+      units = data_units_in;
+    }
+    else if (lat_lon_out) {
+      units = data_units_out;
+    }
+    else if (lat_lon_spatial) {
+      units = new Unit[] {RealType.Latitude.getDefaultUnit(),
+                          RealType.Longitude.getDefaultUnit()};
+    }
+    else {
+      units = null;
+    }
+    int lat = lat_index;
+    int lon = lon_index;
+    int other = other_index;
+    if (units == null) {
+      return null;
+    }
+    else if (units.length == 2) {
+      return new Unit[] {lat >= 0 ? units[lat] : null,
+                         lon >= 0 ? units[lon] : null};
+    }
+    else if (units.length == 3) {
+      return new Unit[] {lat >= 0 ? units[lat] : null,
+                         lon >= 0 ? units[lon] : null,
+                         other >= 0 ? units[other] : null};
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * @return maximum of display ranges of latitude and longitude
+   */
+  public float getLatLonRange() {
+    if (display == null) return 1.0f;
+    double[] rlat = null;
+    double[] rlon = null;
+    int lat = lat_index;
+    int lon = lon_index;
+    if ((lat_lon_out && !lat_lon_out_by_coord) ||
+        (lat_lon_in && lat_lon_in_by_coord)) {
+      rlat = lat >= 0 ? sdo_maps[lat].getRange() :
+                        new double[] {Double.NaN, Double.NaN};
+      rlon = lon >= 0 ? sdo_maps[lon].getRange() :
+                        new double[] {Double.NaN, Double.NaN};
+    }
+    else if ((lat_lon_in && !lat_lon_in_by_coord) ||
+             (lat_lon_out && lat_lon_out_by_coord)) {
+      rlat = lat >= 0 ? sdi_maps[lat].getRange() :
+                        new double[] {Double.NaN, Double.NaN};
+      rlon = lon >= 0 ? sdi_maps[lon].getRange() :
+                        new double[] {Double.NaN, Double.NaN};
+    }
+    else if (lat_lon_spatial) {
+      rlat = lat_map.getRange();
+      rlon = lon_map.getRange();
+    }
+    else {
+      return 1.0f;
+    }
+    double dlat = Math.abs(rlat[1] - rlat[0]);
+    double dlon = Math.abs(rlon[1] - rlon[0]);
+    if (dlat != dlat) dlat = 1.0f;
+    if (dlon != dlon) dlon = 1.0f;
+    return (dlat > dlon) ? (float) dlat : (float) dlon;
+  }
+
+  /**
+   * convert (lat, lon) or (lat, lon, other) values to display (x, y, z)
+   * @param locs (lat, lon) or (lat, lon, other) coordinates
+   * @param vert vertical flow component (if non-null, used to
+   *             adjust non-lat/lon spatial_locs
+   * @return display (x, y, z) coordinates
+   * @throws VisADException a VisAD error occurred
+   */
+  public float[][] earthToSpatial(float[][] locs, float[] vert)
+         throws VisADException {
+    if (display == null) return null;
+    return earthToSpatial(locs, vert, null);
+  }
+
+  /**
+   * convert (lat, lon) or (lat, lon, other) values to display (x, y, z)
+   * @param locs (lat, lon) or (lat, lon, other) coordinates
+   * @param vert vertical flow component (if non-null, used to
+   *        adjust non-lat/lon spatial_locs
+   * @param base_spatial_locs saved spatial_locs argument from
+   *        spatialToEarth() call used to fill in any null members
+   *        of return array
+   * @return display (x, y, z) coordinates
+   * @throws VisADException a VisAD error occurred
+   */
+  public float[][] earthToSpatial(float[][] locs, float[] vert,
+                                  float[][] base_spatial_locs)
+         throws VisADException {
+    if (display == null) return null;
+    int lat = lat_index;
+    int lon = lon_index;
+    int other = other_index;
+    if (lat_index < 0 || lon_index < 0) return null;
+
+    int size = locs[0].length;
+    if (locs.length < lat_lon_dimension) {
+      // extend locs to lat_lon_dimension with zero fill
+      float[][] temp = locs;
+      locs = new float[lat_lon_dimension][];
+      for (int i=0; i<locs.length; i++) {
+        locs[i] = temp[i];
+      }
+      float[] zero = new float[size];
+      for (int j=0; j<size; j++) zero[j] = 0.0f;
+      for (int i=locs.length; i<lat_lon_dimension; i++) {
+        locs[i] = zero;
+      }
+    }
+    else if (locs.length > lat_lon_dimension) {
+      // truncate locs to lat_lon_dimension
+      float[][] temp = locs;
+      locs = new float[lat_lon_dimension][];
+      for (int i=0; i<lat_lon_dimension; i++) {
+        locs[i] = temp[i];
+      }
+    }
+
+    // permute (lat, lon, other) to data RealTupleType
+    float[][] tuple_locs = new float[lat_lon_dimension][];
+    float[][] spatial_locs = new float[3][];
+    tuple_locs[lat] = locs[0];
+    tuple_locs[lon] = locs[1];
+    if (lat_lon_dimension == 3) tuple_locs[other] = locs[2];
+
+    int vert_index = -1; // non-lat/lon index for lat_lon_dimension = 2
+
+    if (lat_lon_in) {
+      if (lat_lon_in_by_coord) {
+        // transform 'RealTupleType data_in' to 'RealTupleType data_out'
+        if (data_coord_in.length == 1) {
+          // one data_coord_in applies to all data points
+          tuple_locs = CoordinateSystem.transformCoordinates(data_out, null,
+                           data_units_out, null, data_in, data_coord_in[0],
+                           data_units_in, null, tuple_locs);
+        }
+        else {
+          // one data_coord_in per data point
+          float[][] temp = new float[lat_lon_dimension][1];
+          for (int j=0; j<size; j++) {
+            for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
+              temp = CoordinateSystem.transformCoordinates(data_out, null,
+                             data_units_out, null, data_in, data_coord_in[j],
+                             data_units_in, null, temp);
+            for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
+          }
+        }
+        // map data_out to spatial DisplayRealTypes
+        for (int i=0; i<lat_lon_dimension; i++) {
+          spatial_locs[sdo_spatial_index[i]] =
+            sdo_maps[i].scaleValues(tuple_locs[i]);
+        }
+        if (lat_lon_dimension == 2) {
+          vert_index = 3 - (sdo_spatial_index[0] + sdo_spatial_index[1]);
+        }
+      }
+      else {
+        // map data_in to spatial DisplayRealTypes
+        for (int i=0; i<lat_lon_dimension; i++) {
+          spatial_locs[sdi_spatial_index[i]] =
+            sdi_maps[i].scaleValues(tuple_locs[i]);
+        }
+        if (lat_lon_dimension == 2) {
+          vert_index = 3 - (sdi_spatial_index[0] + sdi_spatial_index[1]);
+        }
+      }
+    }
+    else if (lat_lon_out) {
+      if (lat_lon_out_by_coord) {
+        // transform 'RealTupleType data_out' to 'RealTupleType data_in'
+        if (data_coord_in.length == 1) {
+          // one data_coord_in applies to all data points
+          tuple_locs = CoordinateSystem.transformCoordinates(data_in,
+                           data_coord_in[0], data_units_in, null, data_out,
+                           null, data_units_out, null, tuple_locs);
+        }
+        else {
+          // one data_coord_in per data point
+          float[][] temp = new float[lat_lon_dimension][1];
+          for (int j=0; j<size; j++) {
+            for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
+              temp = CoordinateSystem.transformCoordinates(data_in,
+                             data_coord_in[j], data_units_in, null, data_out,
+                             null, data_units_out, null, temp);
+            for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
+          }
+        }
+        // map data_in to spatial DisplayRealTypes
+        for (int i=0; i<lat_lon_dimension; i++) {
+          spatial_locs[sdi_spatial_index[i]] =
+            sdi_maps[i].scaleValues(tuple_locs[i]);
+        }
+        if (lat_lon_dimension == 2) {
+          vert_index = 3 - (sdi_spatial_index[0] + sdi_spatial_index[1]);
+        }
+      }
+      else {
+        // map data_out to spatial DisplayRealTypes
+        for (int i=0; i<lat_lon_dimension; i++) {
+          spatial_locs[sdo_spatial_index[i]] =
+            sdo_maps[i].scaleValues(tuple_locs[i]);
+        }
+        if (lat_lon_dimension == 2) {
+          vert_index = 3 - (sdo_spatial_index[0] + sdo_spatial_index[1]);
+        }
+      }
+    }
+    else if (lat_lon_spatial) {
+      // map lat & lon, not in allSpatial RealTupleType, to
+      // spatial DisplayRealTypes
+      spatial_locs[lat_spatial_index] =
+        lat_map.scaleValues(tuple_locs[lat]);
+      spatial_locs[lon_spatial_index] =
+        lon_map.scaleValues(tuple_locs[lon]);
+      vert_index = 3 - (lat_spatial_index + lon_spatial_index);
+    }
+    else {
+      // should never happen
+      return null;
+    }
+
+    // WLH 9 Dec 99
+    // fill any empty spatial DisplayRealTypes with default values
+    for (int i=0; i<3; i++) {
+      if (spatial_locs[i] == null) {
+        if (base_spatial_locs != null &&  base_spatial_locs[i] != null) {
+          spatial_locs[i] = base_spatial_locs[i]; // copy not necessary
+        }
+        else {
+          spatial_locs[i] = new float[size];
+          float def = default_spatial_in[i]; // may be non-Cartesian
+          for (int j=0; j<size; j++) spatial_locs[i][j] = def;
+        }
+      }
+    }
+
+    // adjust non-lat/lon spatial_locs by vertical flow component
+/* WLH 28 July 99
+    if (vert != null && vert_index > -1) {
+      for (int j=0; j<size; j++) spatial_locs[vert_index][j] += vert[j];
+    }
+*/
+    if (vert != null && vert_index > -1 && spatial_locs[vert_index] != null) {
+      for (int j=0; j<size; j++) spatial_locs[vert_index][j] += vert[j];
+    }
+
+    if (display_coordinate_system != null) {
+      // transform non-Cartesian spatial DisplayRealTypes to Cartesian
+      if (spatial_locs != null && spatial_locs.length > 0 &&
+          spatial_locs[0] != null && spatial_locs[0].length > 0) {
+        // DRM 14 Apr 2003 - could do transform in place
+        //spatial_locs = display_coordinate_system.toReference(spatial_locs);
+        spatial_locs = 
+          display_coordinate_system.toReference(Set.copyFloats(spatial_locs));
+      }
+    }
+    return spatial_locs;
+  }
+
+  /**
+   * convert display (x, y, z) to (lat, lon) or (lat, lon, other) values
+   * @param spatial_locs display (x, y, z) coordinates
+   * @return (lat, lon) or (lat, lon, other) coordinates
+   * @throws VisADException a VisAD error occurred
+   */
+  public float[][] spatialToEarth(float[][] spatial_locs)
+         throws VisADException {
+    if (display == null) return null;
+    float[][] base_spatial_locs = new float[3][];
+    return spatialToEarth(spatial_locs, base_spatial_locs);
+  }
+
+  /**
+   * convert display (x, y, z) to (lat, lon) or (lat, lon, other) values
+   * @param spatial_locs display (x, y, z) coordinates
+   * @param base_spatial_locs float[3][] array used to return member
+   *        arrays of spatial_locs argument
+   * @return (lat, lon) or (lat, lon, other) coordinates
+   * @throws VisADException a VisAD error occurred
+   */
+  public float[][] spatialToEarth(float[][] spatial_locs,
+                                  float[][] base_spatial_locs)
+         throws VisADException {
+    if (display == null) return null;
+    int lat = lat_index;
+    int lon = lon_index;
+    int other = other_index;
+    if (lat_index < 0 || lon_index < 0) return null;
+    if (spatial_locs.length != 3) return null;
+
+    int size = 0;
+    for (int i=0; i<3; i++) {
+      if (spatial_locs[i] != null && spatial_locs[i].length > size) {
+        size = spatial_locs[i].length;
+      }
+    }
+    if (size == 0) return null;
+
+    // fill any empty spatial DisplayRealTypes with default values
+    for (int i=0; i<3; i++) {
+      if (spatial_locs[i] == null) {
+        spatial_locs[i] = new float[size];
+        // defaults for Cartesian spatial DisplayRealTypes = 0.0f
+        for (int j=0; j<size; j++) spatial_locs[i][j] = 0.0f;
+      }
+    }
+    if (display_coordinate_system != null) {
+      // transform Cartesian spatial DisplayRealTypes to non-Cartesian
+      // DRM 14 Apr 2003 - could do transform in place
+      //spatial_locs = display_coordinate_system.fromReference(spatial_locs);
+      spatial_locs = 
+          display_coordinate_system.fromReference(Set.copyFloats(spatial_locs));
+    }
+    base_spatial_locs[0] = spatial_locs[0];
+    base_spatial_locs[1] = spatial_locs[1];
+    base_spatial_locs[2] = spatial_locs[2];
+
+    float[][] tuple_locs = new float[lat_lon_dimension][];
+
+    if (lat_lon_in) {
+      if (lat_lon_in_by_coord) {
+        // map spatial DisplayRealTypes to data_out
+        for (int i=0; i<lat_lon_dimension; i++) {
+          tuple_locs[i] =
+            sdo_maps[i].inverseScaleValues(spatial_locs[sdo_spatial_index[i]]);
+        }
+        // transform 'RealTupleType data_out' to 'RealTupleType data_in'
+        if (data_coord_in.length == 1) {
+          // one data_coord_in applies to all data points
+          tuple_locs = CoordinateSystem.transformCoordinates(data_in,
+                           data_coord_in[0], data_units_in, null, data_out,
+                           null, data_units_out, null, tuple_locs);
+        }
+        else {
+          // one data_coord_in per data point
+          float[][] temp = new float[lat_lon_dimension][1];
+          for (int j=0; j<size; j++) {
+            for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
+              temp = CoordinateSystem.transformCoordinates(data_in,
+                             data_coord_in[j], data_units_in, null, data_out,
+                             null, data_units_out, null, temp);
+            for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
+          }
+        }
+      }
+      else {
+        // map spatial DisplayRealTypes to data_in
+        for (int i=0; i<lat_lon_dimension; i++) {
+          tuple_locs[i] =
+            sdi_maps[i].inverseScaleValues(spatial_locs[sdi_spatial_index[i]]);
+        }
+      }
+    }
+    else if (lat_lon_out) {
+      if (lat_lon_out_by_coord) {
+        // map spatial DisplayRealTypes to data_in
+        for (int i=0; i<lat_lon_dimension; i++) {
+          tuple_locs[i] =
+            sdi_maps[i].inverseScaleValues(spatial_locs[sdi_spatial_index[i]]);
+        }
+        // transform 'RealTupleType data_in' to 'RealTupleType data_out'
+        if (data_coord_in.length == 1) {
+          // one data_coord_in applies to all data points
+          tuple_locs = CoordinateSystem.transformCoordinates(data_out, null,
+                           data_units_out, null, data_in, data_coord_in[0],
+                           data_units_in, null, tuple_locs);
+        }
+        else {
+          // one data_coord_in per data point
+          float[][] temp = new float[lat_lon_dimension][1];
+          for (int j=0; j<size; j++) {
+            for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
+              temp = CoordinateSystem.transformCoordinates(data_out, null,
+                             data_units_out, null, data_in, data_coord_in[j],
+                             data_units_in, null, temp);
+            for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
+          }
+        }
+      }
+      else {
+        // map spatial DisplayRealTypes to data_out
+        for (int i=0; i<lat_lon_dimension; i++) {
+          tuple_locs[i] =
+            sdo_maps[i].inverseScaleValues(spatial_locs[sdo_spatial_index[i]]);
+        }
+      }
+    }
+    else if (lat_lon_spatial) {
+      // map spatial DisplayRealTypes to lat & lon, not in
+      // allSpatial RealTupleType
+      tuple_locs[lat] =
+        lat_map.inverseScaleValues(spatial_locs[lat_spatial_index]);
+      tuple_locs[lon] =
+        lon_map.inverseScaleValues(spatial_locs[lon_spatial_index]);
+    }
+    else {
+      // should never happen
+      return null;
+    }
+
+    // permute data RealTupleType to (lat, lon, other)
+    float[][] locs = new float[lat_lon_dimension][];
+    locs[0] = tuple_locs[lat];
+    locs[1] = tuple_locs[lon];
+    if (lat_lon_dimension == 3) locs[2] = tuple_locs[other];
+
+    return locs;
+  }
+
+  /**
+   * save information about relation between earth and display
+   * spatial coordinates, IF the arguments do define the relation
+   * @param s_d_i candidate shadow_data_in
+   *              (Shadow of RealTupleType with reference in Data)
+   * @param s_d_o candidate shadow_data_out
+   *              (Shadow of reference RealTupleType in Data)
+   * @param d_o candidate data_out
+   *            (reference RealTupleType in Data)
+   * @param d_u_o candidate data_units_out
+   *              (Units of reference RealTupleType in Data)
+   * @param d_i candidate data_in
+   *            (RealTupleType with reference in Data)
+   * @param d_c_i candidate data_coord_in
+   *              (CoordinateSystems relating data_in to data_out)
+   * @param d_u_i candidate data_units_in
+   *              (Units of RealTupleType with reference in Data)
+   * @throws VisADException a VisAD error occurred
+   */
+  public void setEarthSpatialData(ShadowRealTupleType s_d_i,
+                    ShadowRealTupleType s_d_o, RealTupleType d_o,
+                    Unit[] d_u_o, RealTupleType d_i,
+                    CoordinateSystem[] d_c_i, Unit[] d_u_i)
+         throws VisADException {
+    if (display == null) return;
+
+    // first check for VectorRealType components mapped to flow
+    // TO_DO:  check here for flow mapped via CoordinateSystem
+    if (d_o != null && d_o instanceof RealVectorType) {
+      ScalarMap[] maps = new ScalarMap[3];
+      int k = getFlowMaps(s_d_o, maps);
+      if (k > -1) rvts[k] = (RealVectorType) d_o;
+    }
+    if (d_i != null && d_i instanceof RealVectorType) {
+      ScalarMap[] maps = new ScalarMap[3];
+      int k = getFlowMaps(s_d_i, maps);
+      if (k > -1) rvts[k] = (RealVectorType) d_i;
+    }
+
+    int lat_index_local = -1;
+    int lon_index_local = -1;
+    int other_index_local = -1;
+    int n = 0;
+    int m = 0;
+    if (d_i != null) {
+      n = d_i.getDimension();
+      for (int i=0; i<n; i++) {
+        RealType real = (RealType) d_i.getComponent(i);
+        if (RealType.Latitude.equals(real)) lat_index_local = i;
+        if (RealType.Longitude.equals(real)) lon_index_local = i;
+      }
+    }
+    if (lat_index_local > -1 && lon_index_local > -1 && (n == 2 || n == 3)) {
+      if (s_d_i != null && s_d_i.getAllSpatial() &&
+          !s_d_i.getSpatialReference()) {
+        lat_lon_in_by_coord = false;
+        sdi_spatial_index = new int[s_d_i.getDimension()];
+        sdi_maps = getSpatialMaps(s_d_i, sdi_spatial_index);
+        if (sdi_maps == null) {
+          throw new DisplayException("sdi_maps null A");
+        }
+      }
+      else if (s_d_o != null && s_d_o.getAllSpatial() &&
+               !s_d_o.getSpatialReference()) {
+        lat_lon_in_by_coord = true;
+        sdo_spatial_index = new int[s_d_o.getDimension()];
+        sdo_maps = getSpatialMaps(s_d_o, sdo_spatial_index);
+        if (sdo_maps == null) {
+          throw new DisplayException("sdo_maps null A");
+        }
+      }
+      else {
+        // do not update lat_index & lon_index
+        return;
+      }
+      lat_lon_in = true;
+      lat_lon_out = false;
+      lat_lon_out_by_coord = false;
+      lat_lon_spatial = false;
+      lat_lon_dimension = n;
+      if (n == 3) {
+        other_index_local = 3 - (lat_index_local + lon_index_local);
+        if (Unit.canConvert(d_u_i[other_index_local], CommonUnit.meter)) {
+          other_meters = true;
+        }
+      }
+    }
+    else { // if( !(lat & lon in di, di dimension = 2 or 3) )
+      lat_index_local = -1;
+      lon_index_local = -1;
+      other_index_local = -1;
+      if (d_o != null) {
+        m = d_o.getDimension();
+        for (int i=0; i<m; i++) {
+          RealType real = (RealType) d_o.getComponent(i);
+          if (RealType.Latitude.equals(real)) lat_index_local = i;
+          if (RealType.Longitude.equals(real)) lon_index_local = i;
+        }
+      }
+      if (lat_index_local < 0 || lon_index_local < 0 || !(m == 2 || m == 3)) {
+        // do not update lat_index & lon_index
+        return;
+      }
+      if (s_d_o != null && s_d_o.getAllSpatial() &&
+          !s_d_o.getSpatialReference()) {
+        lat_lon_out_by_coord = false;
+        sdo_spatial_index = new int[s_d_o.getDimension()];
+        sdo_maps = getSpatialMaps(s_d_o, sdo_spatial_index);
+        if (sdo_maps == null) {
+          throw new DisplayException("sdo_maps null B");
+        }
+      }
+      else if (s_d_i != null && s_d_i.getAllSpatial() &&
+               !s_d_i.getSpatialReference()) {
+        lat_lon_out_by_coord = true;
+        sdi_spatial_index = new int[s_d_i.getDimension()];
+        sdi_maps = getSpatialMaps(s_d_i, sdi_spatial_index);
+        if (sdi_maps == null) {
+          throw new DisplayException("sdi_maps null B");
+        }
+      }
+      else {
+        // do not update lat_index & lon_index
+        return;
+      }
+
+      lat_lon_out = true;
+      lat_lon_in = false;
+      lat_lon_in_by_coord = false;
+      lat_lon_spatial = false;
+      lat_lon_dimension = m;
+      if (m == 3) {
+        other_index_local = 3 - (lat_index_local + lon_index_local);
+        if (Unit.canConvert(d_u_i[other_index_local], CommonUnit.meter)) {
+          other_meters = true;
+        }
+      }
+    }
+    shadow_data_out = s_d_o;
+    data_out = d_o;
+    data_units_out = d_u_o;
+    shadow_data_in = s_d_i;
+    data_in = d_i;
+    data_units_in = d_u_i;
+    data_coord_in = d_c_i; // may be one per point
+    lat_index = lat_index_local;
+    lon_index = lon_index_local;
+    other_index = other_index_local;
+    return;
+  }
+
+  /**
+   * get information about spatial ScalarMaps of components in srt
+   * @param srt tuple of ShadowRealTypes
+   * @param spatial_index array with length equal to number of
+   *        components in srt, used to return indices in
+   *        DisplayTupleTypes of DisplayRealType mapped from
+   *        RealTypes of corresponding components in srt
+   * @return array of spatial ScalarMaps for components in srt (or null)
+   */
+  private ScalarMap[] getSpatialMaps(ShadowRealTupleType srt,
+                                     int[] spatial_index) {
+    if (display == null) return null;
+    int n = srt.getDimension();
+    ScalarMap[] maps = new ScalarMap[n];
+    for (int i=0; i<n; i++) {
+      ShadowRealType real = (ShadowRealType) srt.getComponent(i);
+      Enumeration ms = real.getSelectedMapVector().elements();
+      while (ms.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) ms.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+        if (tuple != null &&
+            (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+             (tuple.getCoordinateSystem() != null &&
+              tuple.getCoordinateSystem().getReference().equals(
+                  Display.DisplaySpatialCartesianTuple)))) {
+          maps[i] = map;
+          spatial_index[i] = dreal.getTupleIndex();
+          break;
+        }
+      }
+      if (maps[i] == null) {
+        return null;
+      }
+    }
+    return maps;
+  }
+
+  /**
+   * determine whether Flow1 or Flow2 is used by given ShadowRealTupleType
+   * @param srt tuple of ShadowRealTypes
+   * @param maps array with length equal to number of components in srt,
+   *             used to return ScalarMaps to Flow from RealTypes of
+   *             corresponding components in srt
+   * @return 0 for Flow1, 1 for Flow2, or -1 for neither (or both - error)
+   */
+  private int getFlowMaps(ShadowRealTupleType srt, ScalarMap[] maps) {
+    if (display == null) return -1;
+    int n = srt.getDimension();
+    maps[0] = null;
+    maps[1] = null;
+    maps[2] = null;
+    DisplayTupleType ftuple = null;
+    for (int i=0; i<n; i++) {
+      ShadowRealType real = (ShadowRealType) srt.getComponent(i);
+      Enumeration ms = real.getSelectedMapVector().elements();
+      while (ms.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) ms.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+        if (Display.DisplayFlow1Tuple.equals(tuple) ||
+            Display.DisplayFlow2Tuple.equals(tuple)) {
+          if (ftuple != null && !ftuple.equals(tuple)) return -1;
+          ftuple = tuple;
+          maps[i] = map;
+          break;
+        }
+      }
+      if (maps[i] == null) return -1;
+    }
+    return Display.DisplayFlow1Tuple.equals(ftuple) ? 0 : 1;
+  }
+
+
+  /** if non-null, float[][] new_spatial_values =
+      display_coordinate_system.toReference(spatial_values); */
+  CoordinateSystem display_coordinate_system = null;
+
+  /** spatial DisplayTupleType; set whether
+      display_coordinate_system is null or not */
+  DisplayTupleType spatial_tuple = null;
+
+  /** map from spatial_tuple tuple_index to value array indices;
+      set whether display_coordinate_system is null or not */
+  int[] spatial_value_indices = {-1, -1, -1};
+
+  /** default values for spatial DisplayRealTypes */
+  float[] default_spatial_in = {0.0f, 0.0f, 0.0f};
+
+  /** true if lat and lon mapped directly to spatial */
+  boolean lat_lon_spatial = false;
+
+  /** ScalarMap from RealType.Latitude */
+  ScalarMap lat_map = null;
+
+  /** ScalarMap from RealType.Longitude */
+  ScalarMap lon_map = null;
+
+  /** index of lat_map DisplayRealType in DisplayTupleType */
+  int lat_spatial_index = -1;
+
+  /** index of lon_map DisplayRealType in DisplayTupleType */
+  int lon_spatial_index = -1;
+
+  /** array of normalized (i.e., max = 1.0) ranges for spatial
+      ScalarMaps, for flow adjustment */
+  double[] ranges = null;
+
+  /**
+   * @return array of normalized (i.e., max = 1.0) ranges for spatial 
+   *         ScalarMaps, for flow adjustment
+   */
+  public double[] getRanges() {
+    return ranges;
+  }
+
+  /**
+   * @return CoordinateSystem for spatial DisplayTupleType
+   *         (null if DisplaySpatialCartesianTuple)
+   */
+  public CoordinateSystem getDisplayCoordinateSystem() {
+    return display_coordinate_system ;
+  }
+
+  /**
+   * save information from ShadowType.assembleSpatial() about
+   * relation between earth and display spatial coordinates
+   * @param coord CoordinateSystem for spatial DisplayTupleType
+   *              (null if DisplaySpatialCartesianTuple)
+   * @param t spatial DisplayTupleType
+   * @param display the DisplayImpl
+   * @param indices indices in display_values array for 3 spatial
+   *                coordinates
+   * @param default_values default values for 3 spatial coordinates
+   * @param r double[3] array of normalized (i.e., max = 1.0)
+   *          ranges for spatial ScalarMaps, for flow adjustment
+   * @throws VisADException a VisAD error occurred
+   */
+  public void setEarthSpatialDisplay(CoordinateSystem coord,
+           DisplayTupleType t, DisplayImpl display, int[] indices,
+           float[] default_values, double[] r)
+         throws VisADException {
+    if (display == null) return;
+    display_coordinate_system = coord;
+    spatial_tuple = t;
+    System.arraycopy(indices, 0, spatial_value_indices, 0, 3);
+    ranges = r;
+    for (int i=0; i<3; i++) {
+      int default_index = display.getDisplayScalarIndex(
+              ((DisplayRealType) t.getComponent(i)) );
+      default_spatial_in[i] = default_values[default_index];
+    }
+
+    if (lat_index > -1 && lon_index > -1) return;
+
+    lat_index = -1;
+    lon_index = -1;
+    other_index = -1;
+
+    int valueArrayLength = display.getValueArrayLength();
+    int[] valueToScalar = display.getValueToScalar();
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    for (int i=0; i<valueArrayLength; i++) {
+      ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+      ScalarType real = map.getScalar();
+      DisplayRealType dreal = map.getDisplayScalar();
+      DisplayTupleType tuple = dreal.getTuple();
+      if (tuple != null &&
+          (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+           (tuple.getCoordinateSystem() != null &&
+            tuple.getCoordinateSystem().getReference().equals(
+                Display.DisplaySpatialCartesianTuple)))) {
+        if (RealType.Latitude.equals(real)) {
+          lat_index = 0;
+          lat_map = map;
+          lat_spatial_index = dreal.getTupleIndex();
+        }
+        if (RealType.Longitude.equals(real)) {
+          lon_index = 1;
+          lon_map = map;
+          lon_spatial_index = dreal.getTupleIndex();
+        }
+      }
+    }
+    if (lat_index > -1 && lon_index > -1) {
+      lat_lon_spatial = true;
+      lat_lon_dimension = 2;
+      lat_lon_out = false;
+      lat_lon_in_by_coord = false;
+      lat_lon_in = false;
+    }
+    else {
+      lat_lon_spatial = false;
+      lat_index = -1;
+      lon_index = -1;
+    }
+  }
+
+
+
+  /* *************************** */
+  /*  direct manipulation stuff  */
+  /* *************************** */
+
+
+  private float[][] spatialValues = null;
+
+  /** if Function, last domain index and range values */
+  private int lastIndex = -1;
+  private double[] lastD = null;
+  private float[] lastX = new float[6];
+
+  /** index into spatialValues found by checkClose */
+  private int closeIndex = -1;
+
+  /** pick error offset, communicated from checkClose() to drag_direct() */
+  private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
+  /** count down to decay offset to 0.0 */
+  private int offset_count = 0;
+  /** initial offset_count */
+  private static final int OFFSET_COUNT_INIT = 30;
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  // private transient ShadowTypeJ3D type = null;
+  private transient DataReference ref = null;
+  private transient MathType type = null;
+  private transient ShadowType shadow = null;
+
+
+  /** point on direct manifold line or plane */
+  private float point_x, point_y, point_z;
+  /** normalized direction of line or perpendicular to plane */
+  private float line_x, line_y, line_z;
+
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+  private float[][] value = new float[1][1];
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  /** mapping from spatial axes to tuple component */
+  private int[] axisToComponent = {-1, -1, -1};
+  /** mapping from spatial axes to ScalarMaps */
+  private ScalarMap[] directMap = {null, null, null};
+  /** spatial axis for Function domain */
+  private int domainAxis = -1;
+  /** dimension of direct manipulation
+      (including any Function domain) */
+  private int directManifoldDimension = 0;
+  /** spatial DisplayTupleType other than
+      DisplaySpatialCartesianTuple */
+  private DisplayTupleType tuple = null;
+
+  /** possible values for whyNotDirect */
+  private final static String notRealFunction =
+    "FunctionType must be Real";
+  private final static String notSimpleField =
+    "not simple field";
+  private final static String notSimpleTuple =
+    "not simple tuple";
+  private final static String multipleMapping =
+    "RealType with multiple mappings";
+  private final static String multipleSpatialMapping =
+    "RealType with multiple spatial mappings";
+  private final static String nonSpatial =
+    "no spatial mapping";
+  private final static String viaReference =
+    "spatial mapping through Reference";
+  private final static String domainDimension =
+    "domain dimension must be 1";
+  private final static String domainNotSpatial =
+    "domain must be mapped to spatial";
+  private final static String rangeType =
+    "range must be RealType or RealTupleType";
+  private final static String rangeNotSpatial =
+    "range must be mapped to spatial";
+  private final static String domainSet =
+    "domain Set must be Gridded1DSet";
+  private final static String tooFewSpatial =
+    "Function without spatial domain";
+  private final static String functionTooFew =
+    "Function directManifoldDimension < 2";
+  private final static String badCoordSysManifoldDim =
+    "bad directManifoldDimension with spatial CoordinateSystem";
+  private final static String lostConnection =
+    "lost connection to Data server";
+
+  private boolean stop = false;
+
+  private int LastMouseModifiers = 0;
+
+  /**
+   * determine if direct manipulation is feasible for the Data
+   * objects rendered by this, and for the ScalarMaps linked to
+   * the associated DisplayImpl;
+   * "returns" its result by calls to setIsDirectManipulation()
+   * called by checkDirect() method of DirectManipulationRendererJ2D
+   * and DirectManipulationRendererJ3D, basically just to share
+   * code between those two classes
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public synchronized void realCheckDirect()
+         throws VisADException, RemoteException {
+    if (display == null) return;
+    setIsDirectManipulation(false);
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    shadow = link.getShadow().getAdaptedShadowType();
+    type = link.getType();
+    tuple = null;
+
+    if (type instanceof FunctionType) {
+      ShadowRealTupleType domain =
+        ((ShadowFunctionType) shadow).getDomain();
+      ShadowType range =
+        ((ShadowFunctionType) shadow).getRange();
+      tuple = domain.getDisplaySpatialTuple();
+      // there is some redundancy among these conditions
+      if (!((FunctionType) type).getReal()) {
+        whyNotDirect = notRealFunction;
+        return;
+      }
+      else if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_FIELD) {
+        whyNotDirect = notSimpleField;
+        return;
+      }
+      else if (shadow.getMultipleSpatialDisplayScalar()) {
+        whyNotDirect = multipleSpatialMapping;
+        return;
+      }
+      else if (domain.getDimension() != 1) {
+        whyNotDirect = domainDimension;
+        return;
+      }
+      else if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
+                (tuple != null &&
+                 tuple.getCoordinateSystem().getReference().equals(
+                 Display.DisplaySpatialCartesianTuple)) )) {
+        whyNotDirect = domainNotSpatial;
+        return;
+      }
+      else if (domain.getSpatialReference()) {
+        whyNotDirect = viaReference;
+        return;
+      }
+      DisplayTupleType rtuple = null;
+      if (range instanceof ShadowRealTupleType) {
+        rtuple = ((ShadowRealTupleType) range).getDisplaySpatialTuple();
+      }
+      else if (range instanceof ShadowRealType) {
+        rtuple = ((ShadowRealType) range).getDisplaySpatialTuple();
+      }
+      else {
+        whyNotDirect = rangeType;
+        return;
+      }
+      if (!tuple.equals(rtuple)) {
+        whyNotDirect = rangeNotSpatial;
+        return;
+      }
+      else if (range instanceof ShadowRealTupleType &&
+               ((ShadowRealTupleType) range).getSpatialReference()) {
+        whyNotDirect = viaReference;
+        return;
+      }
+      else {
+        Data data;
+        try {
+          data = link.getData();
+        } catch (RemoteException re) {
+          if (visad.collab.CollabUtil.isDisconnectException(re)) {
+            getDisplay().connectionFailed(this, link);
+            removeLink(link);
+            link = null;
+            whyNotDirect = lostConnection;
+            return;
+          }
+          throw re;
+        }
+
+        if (!(data instanceof Field) ||
+            !(((Field) data).getDomainSet() instanceof Gridded1DSet))
+        {
+          whyNotDirect = domainSet;
+          return;
+        }
+      }
+      if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+        tuple = null;
+      }
+
+      domainAxis = -1;
+      for (int i=0; i<3; i++) {
+        axisToComponent[i] = -1;
+        directMap[i] = null;
+      }
+
+      directManifoldDimension =
+        setDirectMap((ShadowRealType) domain.getComponent(0), -1, true);
+      if (range instanceof ShadowRealType) {
+        directManifoldDimension +=
+          setDirectMap((ShadowRealType) range, 0, false);
+      }
+      else if (range instanceof ShadowRealTupleType) {
+        ShadowRealTupleType r = (ShadowRealTupleType) range;
+        for (int i=0; i<r.getDimension(); i++) {
+          directManifoldDimension +=
+            setDirectMap((ShadowRealType) r.getComponent(i), i, false);
+        }
+      }
+
+      if (domainAxis == -1) {
+        whyNotDirect = tooFewSpatial;
+        return;
+      }
+      if (directManifoldDimension < 2) {
+        whyNotDirect = functionTooFew;
+        return;
+      }
+      boolean twod = displayRenderer.getMode2D();
+      if (tuple != null &&
+          (!twod && directManifoldDimension != 3 ||
+           twod && directManifoldDimension != 2) ) {
+        whyNotDirect = badCoordSysManifoldDim;
+        return;
+      }
+      setIsDirectManipulation(true);
+    }
+    else if (type instanceof RealTupleType) {
+      //
+      // TO_DO
+      // allow for any Flat ShadowTupleType
+      //
+      tuple = ((ShadowRealTupleType) shadow).getDisplaySpatialTuple();
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_TUPLE) {
+        whyNotDirect = notSimpleTuple;
+        return;
+      }
+      else if (shadow.getMultipleSpatialDisplayScalar()) {
+        whyNotDirect = multipleSpatialMapping;
+        return;
+      }
+      else if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
+                (tuple != null &&
+                 tuple.getCoordinateSystem().getReference().equals(
+                 Display.DisplaySpatialCartesianTuple)) )) {
+        whyNotDirect = nonSpatial;
+        return;
+      }
+      else if (((ShadowRealTupleType) shadow).getSpatialReference()) {
+        whyNotDirect = viaReference;
+        return;
+      }
+      if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+        tuple = null;
+      }
+
+      domainAxis = -1;
+      for (int i=0; i<3; i++) {
+        axisToComponent[i] = -1;
+        directMap[i] = null;
+      }
+
+      directManifoldDimension = 0;
+      for (int i=0; i<((ShadowRealTupleType) shadow).getDimension(); i++) {
+        directManifoldDimension += setDirectMap((ShadowRealType)
+                  ((ShadowRealTupleType) shadow).getComponent(i), i, false);
+      }
+      boolean twod = displayRenderer.getMode2D();
+      if (tuple != null &&
+          (!twod && directManifoldDimension != 3 ||
+           twod && directManifoldDimension != 2) ) {
+        whyNotDirect = badCoordSysManifoldDim;
+        return;
+      }
+      setIsDirectManipulation(true);
+    }
+    else if (type instanceof RealType) {
+      tuple = ((ShadowRealType) shadow).getDisplaySpatialTuple();
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_TUPLE) {
+        whyNotDirect = notSimpleTuple;
+        return;
+      }
+      else if (shadow.getMultipleSpatialDisplayScalar()) {
+        whyNotDirect = multipleSpatialMapping;
+        return;
+      }
+      else if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
+                (tuple != null &&
+                 tuple.getCoordinateSystem().getReference().equals(
+                 Display.DisplaySpatialCartesianTuple)) )) {
+        whyNotDirect = nonSpatial;
+        return;
+      }
+      if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+        tuple = null;
+      }
+
+      domainAxis = -1;
+      for (int i=0; i<3; i++) {
+        axisToComponent[i] = -1;
+        directMap[i] = null;
+      }
+      directManifoldDimension =
+        setDirectMap((ShadowRealType) shadow, 0, false);
+      boolean twod = displayRenderer.getMode2D();
+      if (tuple != null &&
+          (!twod && directManifoldDimension != 3 ||
+           twod && directManifoldDimension != 2) ) {
+        whyNotDirect = badCoordSysManifoldDim;
+        return;
+      }
+      setIsDirectManipulation(true);
+    } // end else if (type instanceof RealType)
+  }
+
+  /**
+   * set directMap and axisToComponent (domain = false) or domainAxis
+   * (domain = true) from real; called by realCheckDirect()
+   * @param real shadow of RealType in a ScalarMap
+   * @param component index of real in a ShadowRealTupleType
+   *                  -1 if real is a domain, 0 if range not in tuple
+   * @param domain true if real occurs in a Function domain
+   * @return direct manifold dimension (i.e., degrees of freedom
+   *         of manipulation)
+   */
+  synchronized int setDirectMap(ShadowRealType real, int component,
+                                boolean domain) {
+    if (display == null) return 0;
+    Enumeration maps = real.getSelectedMapVector().elements();
+    while (maps.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      DisplayRealType dreal = map.getDisplayScalar();
+      DisplayTupleType tuple = dreal.getTuple();
+      if (Display.DisplaySpatialCartesianTuple.equals(tuple) ||
+          (tuple != null && tuple.getCoordinateSystem() != null &&
+           Display.DisplaySpatialCartesianTuple.equals(
+             tuple.getCoordinateSystem().getReference() )) ) {
+        int index = dreal.getTupleIndex();
+        if (domain) {
+          domainAxis = index;
+        }
+        else {
+          axisToComponent[index] = component;
+        }
+        directMap[index] = map;
+        return 1;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * @return direct manifold dimension (i.e., degrees of freedom
+   *         of manipulation)
+   */
+  private int getDirectManifoldDimension() {
+    return directManifoldDimension;
+  }
+
+  /**
+   * @return String with reason MathType and ScalarMaps do not
+   *         qualify for direct manipulation
+   */
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  /**
+   * @param index of a spatial axis
+   * @return index of tuple component for spatial axis i
+   */
+  private int getAxisToComponent(int i) {
+    return axisToComponent[i];
+  }
+
+  /**
+   * @param index of a spatial axis
+   * @return ScalarMap for spatial axis i
+   */
+  private ScalarMap getDirectMap(int i) {
+    return directMap[i];
+  }
+
+  /**
+   * @return spatial axis for Function domain
+   */
+  private int getDomainAxis() {
+    return domainAxis;
+  }
+
+  /**
+   * set spatial values for Data depiction; used to detect when
+   * direct manipulation mouse selects a point of a Data depiction
+   * @param spatial_values float[3][number_of_points] of 3-D locations
+   *                       of depiction points
+   */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // these are X, Y, Z values
+    spatialValues = spatial_values;
+  }
+
+  /**
+   * find minimum distance from ray to spatialValues; save index of
+   * point with minimum distance in closeIndex; reset lastIndex to -1
+   * (Field domain index of Field range value last modified by
+   *  drag_direct())
+   * @param origin 3-D origin of ray
+   * @param direction 3-D direction of ray
+   * @return minimum distance of ray to any point in spatialValues
+   *         (spatial values for Data depiction)
+   */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    float distance = Float.MAX_VALUE;
+    if (display == null) return distance;
+    lastIndex = -1;
+    if (spatialValues == null) return distance;
+    float o_x = (float) origin[0];
+    float o_y = (float) origin[1];
+    float o_z = (float) origin[2];
+    float d_x = (float) direction[0];
+    float d_y = (float) direction[1];
+    float d_z = (float) direction[2];
+/*
+System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
+System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
+*/
+    for (int i=0; i<spatialValues[0].length; i++) {
+      float x = spatialValues[0][i] - o_x;
+      float y = spatialValues[1][i] - o_y;
+      float z = spatialValues[2][i] - o_z;
+      float dot = x * d_x + y * d_y + z * d_z;
+      x = x - dot * d_x;
+      y = y - dot * d_y;
+      z = z - dot * d_z;
+      float d = (float) Math.sqrt(x * x + y * y + z * z);
+      if (d < distance) {
+        distance = d;
+        closeIndex = i;
+        offsetx = x;
+        offsety = y;
+        offsetz = z;
+      }
+/*
+System.out.println("spatialValues["+i+"] = " + spatialValues[0][i] + " " +
+spatialValues[1][i] + " " + spatialValues[2][i] + " d = " + d);
+*/
+    }
+/*
+System.out.println("checkClose: distance = " + distance);
+*/
+    return distance;
+  }
+
+  /**
+   * called when mouse button is released ending direct manipulation;
+   * intended to be over-ridden by DataRenderer extensions that need
+   * to act on this event
+   */
+  public synchronized void release_direct() {
+  }
+
+  /**
+   * discontinue manipulating Data values for current mouse drag;
+   * (this only applies to the current mouse drag and is not a
+   *  general disable)
+   */
+  public void stop_direct() {
+    stop = true;
+  }
+
+  /**
+   * @return value of InputEvent.getModifiers() from most recent
+   *         direct manipulation mouse click
+   */
+  public int getLastMouseModifiers() {
+    return LastMouseModifiers;
+  }
+
+  /**
+   * called by MouseHelper.processEvent() to set LastMouseModifiers
+   * @param mouseModifiers value of InputEvent.getModifiers() from
+   *                       last direct manipulation mouse click
+   */
+  public void setLastMouseModifiers(int mouseModifiers) {
+    LastMouseModifiers = mouseModifiers;
+  }
+
+  /**
+   * modify Data values based on direct manipulation mouse actions
+   * @param ray 3-D graphics coordinates of ray corresponding to
+   *            mouse screen location
+   * @param first flag if this is first call (for MouseEvent.MOUSE_PRESSED,
+   *              not for MouseEvent.MOUSE_DRAGGED)
+   * @param mouseModifiers value of InputEvent.getModifiers() from
+   *                       most recent direct manipulation mouse click
+   */
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    if (display == null) return;
+    // System.out.println("drag_direct " + first + " " + type);
+    if (spatialValues == null || ref == null || shadow == null ||
+        link == null) return;
+
+    if (first) {
+      stop = false;
+    }
+    else {
+      if (stop) return;
+    }
+
+    float o_x = (float) ray.position[0];
+    float o_y = (float) ray.position[1];
+    float o_z = (float) ray.position[2];
+    float d_x = (float) ray.vector[0];
+    float d_y = (float) ray.vector[1];
+    float d_z = (float) ray.vector[2];
+
+    if (pickCrawlToCursor) {
+      if (first) {
+        offset_count = OFFSET_COUNT_INIT;
+      }
+      else {
+        if (offset_count > 0) offset_count--;
+      }
+      if (offset_count > 0) {
+        float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
+        o_x += mult * offsetx;
+        o_y += mult * offsety;
+        o_z += mult * offsetz;
+      }
+    }
+
+    if (first) {
+      point_x = spatialValues[0][closeIndex];
+      point_y = spatialValues[1][closeIndex];
+      point_z = spatialValues[2][closeIndex];
+      int lineAxis = -1;
+      if (getDirectManifoldDimension() == 3) {
+        // coord sys ok
+        line_x = d_x;
+        line_y = d_y;
+        line_z = d_z;
+      }
+      else {
+        if (getDirectManifoldDimension() == 2) {
+          if (displayRenderer.getMode2D()) {
+            // coord sys ok
+            lineAxis = 2;
+          }
+          else {
+            for (int i=0; i<3; i++) {
+              if (getAxisToComponent(i) < 0 && getDomainAxis() != i) {
+                lineAxis = i;
+              }
+            }
+          }
+        }
+        else if (getDirectManifoldDimension() == 1) {
+          for (int i=0; i<3; i++) {
+            if (getAxisToComponent(i) >= 0) {
+              lineAxis = i;
+            }
+          }
+        }
+        line_x = (lineAxis == 0) ? 1.0f : 0.0f;
+        line_y = (lineAxis == 1) ? 1.0f : 0.0f;
+        line_z = (lineAxis == 2) ? 1.0f : 0.0f;
+      }
+    } // end if (first)
+
+    float[] x = new float[3]; // x marks the spot
+    if (getDirectManifoldDimension() == 1) {
+      // find closest point on line to ray
+      // logic from vis5d/cursor.c
+      // line o_, d_ to line point_, line_
+      float ld = d_x * line_x + d_y * line_y + d_z * line_z;
+      float od = o_x * d_x + o_y * d_y + o_z * d_z;
+      float pd = point_x * d_x + point_y * d_y + point_z * d_z;
+      float ol = o_x * line_x + o_y * line_y + o_z * line_z;
+      float pl = point_x * line_x + point_y * line_y + point_z * line_z;
+      if (ld * ld == 1.0f) return;
+      float t = ((pl - ol) - (ld * (pd - od))) / (ld * ld - 1.0f);
+      // x is closest point
+      x[0] = point_x + t * line_x;
+      x[1] = point_y + t * line_y;
+      x[2] = point_z + t * line_z;
+    }
+    else { // getDirectManifoldDimension() = 2 or 3
+      // intersect ray with plane
+      float dot = (point_x - o_x) * line_x +
+                  (point_y - o_y) * line_y +
+                  (point_z - o_z) * line_z;
+      float dot2 = d_x * line_x + d_y * line_y + d_z * line_z;
+      if (dot2 == 0.0) return;
+      dot = dot / dot2;
+      // x is intersection
+      x[0] = o_x + dot * d_x;
+      x[1] = o_y + dot * d_y;
+      x[2] = o_z + dot * d_z;
+    }
+
+    //
+    //jeffmc: new call so a derived class can constrain the position of the drag point
+    //
+    constrainDragPoint(x);
+
+    //
+    // TO_DO
+    // might estimate errors from pixel resolution on screen
+    //
+
+    try {
+      float[] xx = {x[0], x[1], x[2]};
+      if (tuple != null) {
+        float[][] cursor = {{x[0]}, {x[1]}, {x[2]}};
+        float[][] new_cursor =
+          tuple.getCoordinateSystem().fromReference(cursor);
+        x[0] = new_cursor[0][0];
+        x[1] = new_cursor[1][0];
+        x[2] = new_cursor[2][0];
+      }
+      Data newData = null;
+      Data data;
+      try {
+        data = link.getData();
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          link = null;
+          return;
+        }
+        throw re;
+      }
+
+
+      if (type instanceof RealType) {
+        addPoint(xx);
+        for (int i=0; i<3; i++) {
+          if (getAxisToComponent(i) >= 0) {
+            f[0] = x[i];
+            d = getDirectMap(i).inverseScaleValues(f);
+            // RealType rtype = (RealType) data.getType();
+            RealType rtype = (RealType) type;
+            newData = new Real(rtype, (double) d[0], rtype.getDefaultUnit(), null);
+
+            // create location string
+            Vector vect = new Vector();
+            Real r = new Real(rtype, d[0]);
+            Unit overrideUnit = getDirectMap(i).getOverrideUnit();
+            Unit rtunit = rtype.getDefaultUnit();
+            // units not part of Time string
+            if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+                (!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
+                 rtunit.getAbsoluteUnit().equals(rtunit))) {
+              double dval =  overrideUnit.toThis((double) d[0], rtunit);
+              r = new Real(rtype, dval, overrideUnit);
+            }
+            String valueString = r.toValueString();
+            vect.addElement(rtype.getName() + " = " + valueString);
+
+            getDisplayRenderer().setCursorStringVector(vect);
+            break;
+          }
+        }
+        ref.setData(newData);
+        link.clearData();
+      }
+      else if (type instanceof RealTupleType) {
+
+        addPoint(xx);
+        int n = ((RealTuple) data).getDimension();
+        Real[] reals = new Real[n];
+        Vector vect = new Vector();
+        for (int i=0; i<3; i++) {
+          int j = getAxisToComponent(i);
+          if (j >= 0) {
+            f[0] = x[i];
+            d = getDirectMap(i).inverseScaleValues(f);
+            Real c = (Real) ((RealTuple) data).getComponent(j);
+            RealType rtype = (RealType) c.getType();
+            reals[j] = new Real(rtype, (double) d[0], rtype.getDefaultUnit(), null);
+
+            // create location string
+            Real r = new Real(rtype, d[0]);
+            Unit overrideUnit = getDirectMap(i).getOverrideUnit();
+            Unit rtunit = rtype.getDefaultUnit();
+            // units not part of Time string
+            if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+                (!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
+                 rtunit.getAbsoluteUnit().equals(rtunit))) {
+              double dval = overrideUnit.toThis((double) d[0], rtunit);
+              r = new Real(rtype, dval, overrideUnit);
+            }
+            String valueString = r.toValueString();
+            vect.addElement(rtype.getName() + " = " + valueString);
+
+          }
+        }
+        getDisplayRenderer().setCursorStringVector(vect);
+        for (int j=0; j<n; j++) {
+          if (reals[j] == null) {
+            reals[j] = (Real) ((RealTuple) data).getComponent(j);
+          }
+        }
+        newData = new RealTuple((RealTupleType) type, reals,
+                                ((RealTuple) data).getCoordinateSystem());
+        ref.setData(newData);
+        link.clearData();
+      }
+      else if (type instanceof FunctionType) {
+        Vector vect = new Vector();
+        if (first) lastIndex = -1;
+        int k = getDomainAxis();
+        f[0] = x[k];
+        d = getDirectMap(k).inverseScaleValues(f);
+        RealType rtype = (RealType) getDirectMap(k).getScalar();
+
+        // first, save value in default Unit
+        double dsave = d[0];
+
+        // WLH 4 Jan 99
+        // convert d from default Unit to actual domain Unit of data
+        Unit[] us = ((Field) data).getDomainUnits();
+        if (us != null && us[0] != null) {
+          d[0] = (float) us[0].toThis((double) d[0], rtype.getDefaultUnit());
+        }
+
+        // create location string
+        Real r = new Real(rtype, dsave);
+        Unit overrideUnit = getDirectMap(k).getOverrideUnit();
+        Unit rtunit = rtype.getDefaultUnit();
+        // units not part of Time string
+        if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+            (!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
+             rtunit.getAbsoluteUnit().equals(rtunit))) {
+          dsave = overrideUnit.toThis(dsave, rtunit);
+          r = new Real(rtype, dsave, overrideUnit);
+        }
+        String valueString = r.toValueString();
+        vect.addElement(rtype.getName() + " = " + valueString);
+
+        // convert domain value to domain index
+        Gridded1DSet set = (Gridded1DSet) ((Field) data).getDomainSet();
+        value[0][0] = (float) d[0];
+        int[] indices = set.valueToIndex(value);
+        int thisIndex = indices[0];
+        if (thisIndex < 0) {
+          lastIndex = -1;
+          return;
+        }
+
+        if (lastIndex < 0) {
+          addPoint(xx);
+        }
+        else {
+          lastX[3] = xx[0];
+          lastX[4] = xx[1];
+          lastX[5] = xx[2];
+          addPoint(lastX);
+        }
+        lastX[0] = xx[0];
+        lastX[1] = xx[1];
+        lastX[2] = xx[2];
+
+        int n;
+        MathType range = ((FunctionType) type).getRange();
+        if (range instanceof RealType) {
+          n = 1;
+        }
+        else {
+          n = ((RealTupleType) range).getDimension();
+        }
+        double[] thisD = new double[n];
+        boolean[] directComponent = new boolean[n];
+        for (int j=0; j<n; j++) {
+          thisD[j] = Double.NaN;
+          directComponent[j] = false;
+        }
+        for (int i=0; i<3; i++) {
+          int j = getAxisToComponent(i);
+          if (j >= 0) {
+            f[0] = x[i];
+            d = getDirectMap(i).inverseScaleValues(f);
+            // create location string
+            rtype = (RealType) getDirectMap(i).getScalar();
+
+            r = new Real(rtype, d[0]);
+            overrideUnit = getDirectMap(i).getOverrideUnit();
+            rtunit = rtype.getDefaultUnit();
+            // units not part of Time string
+            if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+                (!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
+                 rtunit.getAbsoluteUnit().equals(rtunit))) {
+              double dval = overrideUnit.toThis((double) d[0], rtunit);
+              r = new Real(rtype, dval, overrideUnit);
+            }
+            valueString = r.toValueString();
+            vect.addElement(rtype.getName() + " = " + valueString);
+
+            thisD[j] = d[0];
+            directComponent[j] = true;
+          }
+        }
+        getDisplayRenderer().setCursorStringVector(vect);
+        if (lastIndex < 0) {
+          lastIndex = thisIndex;
+          lastD = new double[n];
+          for (int j=0; j<n; j++) {
+            lastD[j] = thisD[j];
+          }
+        }
+        Real[] reals = new Real[n];
+        int m = Math.abs(lastIndex - thisIndex) + 1;
+        indices = new int[m];
+        int index = thisIndex;
+        int inc = (lastIndex >= thisIndex) ? 1 : -1;
+        for (int i=0; i<m; i++) {
+          indices[i] = index;
+          index += inc;
+        }
+        float[][] values = set.indexToValue(indices);
+        double coefDiv = values[0][m-1] - values[0][0];
+        for (int i=0; i<m; i++) {
+          index = indices[i];
+          double coef = (i == 0 || coefDiv == 0.0) ? 0.0 :
+                          (values[0][i] - values[0][0]) / coefDiv;
+          Data tuple = ((Field) data).getSample(index);
+          if (tuple instanceof Real) {
+            if (directComponent[0]) {
+              rtype = (RealType) tuple.getType();
+              tuple = new Real(rtype, thisD[0] + coef * (lastD[0] - thisD[0]),
+                               rtype.getDefaultUnit(), null);
+            }
+          }
+          else {
+            for (int j=0; j<n; j++) {
+              Real c = (Real) ((RealTuple) tuple).getComponent(j);
+              if (directComponent[j]) {
+                rtype = (RealType) c.getType();
+                reals[j] = new Real(rtype, thisD[j] + coef * (lastD[j] - thisD[j]),
+                                    rtype.getDefaultUnit(), null);
+              }
+              else {
+                reals[j] = c;
+              }
+            }
+            tuple = new RealTuple(reals);
+          }
+          ((Field) data).setSample(index, tuple);
+        } // end for (int i=0; i<m; i++)
+        if (ref instanceof RemoteDataReference &&
+            !(data instanceof RemoteData)) {
+          // ref is Remote and data is local, so we have only modified
+          // a local copy and must send it back to ref
+          ref.setData(data);
+          link.clearData(); // WLH 27 July 99
+        }
+        // set last index to this, and component values
+        lastIndex = thisIndex;
+        for (int j=0; j<n; j++) {
+          lastD[j] = thisD[j];
+        }
+      } // end else if (type instanceof FunctionType)
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * jeffmc: new method that provides a hook so derived classes can easily constrain the position of a drag point
+   *
+   * @param dragPoint The position of the drag point
+   */
+  public void constrainDragPoint(float[]dragPoint) {}
+
+
+  /**
+   * add point for temporary rendering; intended to be
+   * over-ridden by graphics-API-specific extensions of
+   * DataRenderer
+   * @param x 3-D graphics coordinates of point to render
+   * @throws VisADException a VisAD error occurred
+   */
+  public void addPoint(float[] x) throws VisADException {
+  }
+
+  /** flag indicating whether DirectManipulationRenderer is valid
+      for this ShadowType */
+  private boolean isDirectManipulation;
+
+  /**
+   * set isDirectManipulation = true if this DataRenderer supports
+   * direct manipulation for the MathType of its linked Data, and
+   * for its ScalarMaps; intended to be over-ridden by extensions of
+   * DataRenderer
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void checkDirect() throws VisADException, RemoteException {
+    isDirectManipulation = false;
+  }
+
+  /**
+   * set value of isDirectManipulation flag (indicating whether this
+   * DataRenderer supports direct manipulation for its MathType and
+   * ScalarMaps)
+   * @param b value to set in isDirectManipulation
+   */
+  public void setIsDirectManipulation(boolean b) {
+    isDirectManipulation = b;
+  }
+
+  /**
+   * @return value of isDirectManipulation flag (indicating whether this
+   *         DataRenderer supports direct manipulation for its MathType
+   *         and ScalarMaps)
+   */
+  public boolean getIsDirectManipulation() {
+    return isDirectManipulation;
+  }
+
+  /** flag indicating whether points affected by direct manipulation should
+      "crawl" toward the cursor instead of jumping to it immediately. */
+  protected boolean pickCrawlToCursor = true;
+
+  /**
+   * set pickCrawlToCursor flag indicating whether Data points being
+   * manipulated should "crawl" toward the cursor instead of jumping
+   * to it immediately
+   * @param b value to set in pickCrawlToCursor
+   */
+  public void setPickCrawlToCursor(boolean b) {
+    pickCrawlToCursor = b;
+  }
+
+  /**
+   * @return pickCrawlToCursor flag indicating whether Data points being
+   *         manipulated should "crawl" toward the cursor instead of
+   *         jumping to it immediately
+   */
+  public boolean getPickCrawlToCursor() {
+    return pickCrawlToCursor;
+  }
+
+  private float ray_pos; // save last ray_pos as first guess for next
+  private static final int HALF_GUESSES = 200;
+  private static final int GUESSES = 2 * HALF_GUESSES + 1;
+  private static final float RAY_POS_INC = 0.1f;
+  private static final int TRYS = 10;
+  private static final double EPS = 0.001f;
+
+  /**
+   * find intersection of a ray and a 2-D manifold, using Newton's method
+   * @param first flag requesting to generate a first guess ray position
+   *              by brute force search
+   * @param origin 3-D graphics coordinates of ray origin
+   * @param direction 3-D graphics coordinates of ray direction
+   * @param tuple spatial DisplayTupleType used to define 2-D manifold
+   *              (manifold is defined by fixing value of one component
+   *               of 3-D tuple)
+   * @param otherindex index of tuple component to be fixed
+   * @param othervalue value at which to fix otherindex tuple component
+   * @return parameter of point along ray of ray-manifold intersection
+   *        (point = origin + parameter * direction)
+   * @throws VisADException a VisAD error occurred
+   */
+  public float findRayManifoldIntersection(boolean first, double[] origin,
+                             double[] direction, DisplayTupleType tuple,
+                             int otherindex, float othervalue)
+          throws VisADException {
+    ray_pos = Float.NaN;
+    if (display == null) return ray_pos;
+    if (otherindex < 0) return ray_pos;
+    CoordinateSystem tuplecs = null;
+    if (tuple != null) tuplecs = tuple.getCoordinateSystem();
+    if (tuple == null || tuplecs == null) {
+      ray_pos = (float)
+        ((othervalue - origin[otherindex]) / direction[otherindex]);
+    }
+    else { // tuple != null
+      double adjust = Double.NaN;
+      if (Display.DisplaySpatialSphericalTuple.equals(tuple)) {
+        if (otherindex == 1) adjust = 360.0;
+      }
+      if (first) {
+        // generate a first guess ray_pos by brute force
+        ray_pos = Float.NaN;
+        float[][] guesses = new float[3][GUESSES];
+        for (int i=0; i<GUESSES; i++) {
+          float rp = (i - HALF_GUESSES) * RAY_POS_INC;
+          guesses[0][i] = (float) (origin[0] + rp * direction[0]);
+          guesses[1][i] = (float) (origin[1] + rp * direction[1]);
+          guesses[2][i] = (float) (origin[2] + rp * direction[2]);
+          if (adjust == adjust) {
+            guesses[otherindex][i] = (float)
+              (((othervalue + 0.5 * adjust + guesses[otherindex][i]) % adjust) -
+               (othervalue + 0.5 * adjust));
+          }
+        }
+        guesses = tuplecs.fromReference(guesses);
+        double distance = Double.MAX_VALUE;
+        float lastg = 0.0f;
+        for (int i=0; i<GUESSES; i++) {
+          float g = othervalue - guesses[otherindex][i];
+          // first, look for nearest zero crossing and interpolate
+          if (i > 0 && ((g < 0.0f && lastg >= 0.0f) || (g >= 0.0f && lastg < 0.0f))) {
+            float r = (float)
+              (i - (Math.abs(g) / (Math.abs(lastg) + Math.abs(g))));
+            ray_pos = (r - HALF_GUESSES) * RAY_POS_INC;
+            break;
+          }
+          lastg = g;
+
+          // otherwise look for closest to zero
+          double d = Math.abs(othervalue - guesses[otherindex][i]);
+          if (d < distance) {
+            distance = d;
+            ray_pos = (i - HALF_GUESSES) * RAY_POS_INC;
+          }
+        } // end for (int i=0; i<GUESSES; i++)
+      }
+      if (ray_pos == ray_pos) {
+        // use Newton's method to refine first guess
+        // double error_limit = 10.0 * EPS;
+        double error_limit = EPS;
+        double r = ray_pos;
+        double error = 1.0f;
+        double[][] guesses = new double[3][3];
+        int itry;
+// System.out.println("\nothervalue = " + (float) othervalue + " r = " + (float) r);
+        for (itry=0; (itry<TRYS && r == r); itry++) {
+          double rp = r + EPS;
+          double rm = r - EPS;
+          guesses[0][0] = origin[0] + rp * direction[0];
+          guesses[1][0] = origin[1] + rp * direction[1];
+          guesses[2][0] = origin[2] + rp * direction[2];
+          guesses[0][1] = origin[0] + r * direction[0];
+          guesses[1][1] = origin[1] + r * direction[1];
+          guesses[2][1] = origin[2] + r * direction[2];
+          guesses[0][2] = origin[0] + rm * direction[0];
+          guesses[1][2] = origin[1] + rm * direction[1];
+          guesses[2][2] = origin[2] + rm * direction[2];
+// System.out.println(" guesses = " + (float) guesses[0][1] + " " +
+//                    (float) guesses[1][1] + " " + (float) guesses[2][1]);
+          guesses = tuplecs.fromReference(guesses);
+// System.out.println(" transformed = " + (float) guesses[0][1] + " " +
+//                    (float) guesses[1][1] + " " + (float) guesses[2][1]);
+          if (adjust == adjust) {
+            guesses[otherindex][0] =
+              ((othervalue + 0.5 * adjust + guesses[otherindex][0]) % adjust) -
+               (othervalue + 0.5 * adjust);
+            guesses[otherindex][1] =
+              ((othervalue + 0.5 * adjust + guesses[otherindex][1]) % adjust) -
+               (othervalue + 0.5 * adjust);
+            guesses[otherindex][2] =
+              ((othervalue + 0.5 * adjust + guesses[otherindex][2]) % adjust) -
+               (othervalue + 0.5 * adjust);
+          }
+// System.out.println(" adjusted = " + (float) guesses[0][1] + " " +
+//                    (float) guesses[1][1] + " " + (float) guesses[2][1]);
+          double g = othervalue - guesses[otherindex][1];
+          error = Math.abs(g);
+          if (error <= EPS) break;
+          double gp = othervalue - guesses[otherindex][0];
+          double gm = othervalue - guesses[otherindex][2];
+          double dg = (gp - gm) / (EPS + EPS);
+// System.out.println("r = " + (float) r + " g = " + (float) g + " gm = " +
+//                    (float) gm + " gp = " + (float) gp + " dg = " + (float) dg);
+          r = r - g / dg;
+        }
+        if (error < error_limit) {
+          ray_pos = (float) r;
+        }
+        else {
+          // System.out.println("error = " + error + " itry = " + itry);
+          ray_pos = Float.NaN;
+        }
+      }
+    } // end (tuple != null)
+    return ray_pos;
+  }
+
+  /**
+   * <b>WARNING!</b>
+   * Do <b>NOT</b> use this routine unless you know what you are doing!
+   * remove link from Links[] array when remote connection fails
+   * @param link DataDisplayLink to remove
+   */
+  public void removeLink(DataDisplayLink link)
+  {
+    if (display == null) return;
+    final int newLen = Links.length - 1;
+    if (newLen < 0) {
+      // give up if the Links array is already empty
+      return;
+    }
+
+    DataDisplayLink[] newLinks = new DataDisplayLink[newLen];
+
+    int n = 0;
+    for (int i = 0; i <= newLen; i++) {
+      if (Links[i] == link) {
+        // skip the specified link
+      } else {
+        if (n == newLen) {
+          // yikes!   Obviously didn't find this link in the list!
+          return;
+        }
+        newLinks[n++] = Links[i];
+      }
+    }
+
+    if (n < newLen) {
+      // Hmmm ... seem to have removed multiple instances of 'link'!
+      DataDisplayLink[] newest = new DataDisplayLink[n];
+      System.arraycopy(newLinks, 0, newest, 0, n);
+      newLinks = newest;
+    }
+
+    Links = newLinks;
+  }
+
+  /**
+   * @return a copy of this DataRenderer
+   */
+  public abstract Object clone() throws CloneNotSupportedException;
+
+}
+
diff --git a/visad/DataShadow.java b/visad/DataShadow.java
new file mode 100644
index 0000000..e847ab2
--- /dev/null
+++ b/visad/DataShadow.java
@@ -0,0 +1,106 @@
+//
+// DataShadow.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DataShadow is the VisAD class for gathering RealType value
+   ranges and Animation sampling Sets for auto-scaling Displays.<P>
+*/
+public class DataShadow extends Object implements java.io.Serializable {
+
+  /** ranges of RealType values, dimensioned [2][num_RealTypes] where
+      first index = 0 for min, = 1 for max */
+  double[][] ranges;
+
+  /** default Set for Animation sampling, from Field domains in data */
+  Set animationSampling;
+
+  /**
+   * construct a new DataShadow with given ranges array and
+   * null animationSampling
+   * @param r ranges array
+   */
+  DataShadow(double[][] r) {
+    ranges = r;
+    animationSampling = null;
+  }
+
+  /**
+   * @param domain flag indicating if this call is for Field domain
+   * @return flag indicating that animationSampling is non-null
+   *         and this call is not for a Field domain
+   */
+  boolean isAnimationSampling(boolean domain) {
+    if (domain) {
+      return false;
+    }
+    else {
+      return (animationSampling != null);
+    }
+  }
+
+  /**
+   * set or merge a Set into animationSampling, as long as Set is
+   * neither FloatSet nor DoubleSet, and this call is for a Field
+   * domain
+   * @param set Set to merge
+   * @param domain flag indicating if this call is for Field domain
+   * @throws VisADException a VisAD error occurred
+   */
+  void setAnimationSampling(Set set, boolean domain)
+       throws VisADException {
+    if (set instanceof DoubleSet || set instanceof FloatSet) return;
+    if (domain) {
+      if (animationSampling == null) {
+        animationSampling = set;
+      }
+      else {
+        animationSampling = animationSampling.merge1DSets(set);
+      }
+    }
+  }
+
+  /**
+   * merge argument DataShadow into this DataShadow
+   * @param shadow DataShadow to merge
+   * @throws VisADException a VisAD error occurred
+   */
+  public void merge(DataShadow shadow) throws VisADException {
+    int n = ranges[0].length;
+    for (int i=0; i<n; i++) {
+      if (shadow.ranges[0][i] < ranges[0][i]) {
+        ranges[0][i] = shadow.ranges[0][i];
+      }
+      if (shadow.ranges[1][i] > ranges[1][i]) {
+        ranges[1][i] = shadow.ranges[1][i];
+      }
+    }
+    setAnimationSampling(shadow.animationSampling, true);
+  }
+
+}
+
diff --git a/visad/DataSourceListener.java b/visad/DataSourceListener.java
new file mode 100644
index 0000000..bf9e7b4
--- /dev/null
+++ b/visad/DataSourceListener.java
@@ -0,0 +1,31 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * @deprecated Use {@link RemoteSourceListener} instead.
+ */
+public interface DataSourceListener
+  extends RemoteSourceListener
+{
+}
diff --git a/visad/DateTime.java b/visad/DateTime.java
new file mode 100644
index 0000000..d807f31
--- /dev/null
+++ b/visad/DateTime.java
@@ -0,0 +1,688 @@
+//
+// DateTime.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.text.ParseException;
+import java.text.FieldPosition;
+
+/**
+ * DateTime is a class of objects for holding date and time information.
+ * DateTime objects are immutable.<P>
+ *
+ * Internally, the object uses seconds since the epoch
+ * (1970-01-01 00:00:00Z) as the temporal reference.
+ * @see java.lang.System#currentTimeMillis()
+ *
+ */
+public class
+DateTime
+    extends     Real
+{
+
+    /**
+	 * default for serializable classes
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/** This is around so we can use a different date formatter */
+    private static Class dateFormatClass;
+
+    /**
+     * Default Time Format Pattern (yyyy-MM-dd HH:mm:ss)
+     */
+    public static final String DEFAULT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * Default Time Zone (GMT)
+     */
+    public static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone("GMT");
+
+    // Time related variables
+    private static String formatPattern = DEFAULT_TIME_FORMAT;
+    private static TimeZone timeZone = DEFAULT_TIMEZONE;
+
+    //Don't create this right away now
+    private GregorianCalendar utcCalendar;
+
+    private static final double secondsPerDay = (double) (24 * 60 * 60);
+
+    /**
+     * Construct a DateTime object and initialize it using a VisAD Real.
+     * Unless the units of the Real specify otherwise, the Real's value
+     * is assumed to be seconds since the Epoch (i.e. 1970-01-01 00:00:00Z).
+     *
+     * @param   real            Real value in a temporal unit.
+     *
+     * @throws  VisADException  unit conversion problem
+     */
+    public DateTime(Real real)
+            throws VisADException
+    {
+        super( RealType.Time,
+               real.getValue(CommonUnit.secondsSinceTheEpoch),
+               CommonUnit.secondsSinceTheEpoch,null);
+
+        //We create and set the utcCalendar when we need it
+        // set up in terms of java date
+	//        utcCalendar.setTime(new Date(Math.round(getValue()*1000.)));
+    }
+
+    /**
+     * Construct a DateTime object and initialize it with the seconds since
+     * January 1, 1970 00:00:00Z.
+     *
+     * @param  seconds  number of seconds since 1970-01-01 00:00:00Z.
+     *
+     * @throws  VisADException  unit conversion problem
+     */
+    public DateTime(double seconds)
+            throws VisADException
+    {
+        super(RealType.Time,
+              seconds,
+              CommonUnit.secondsSinceTheEpoch,null, false);
+        //        this(seconds, CommonUnit.secondsSinceTheEpoch);
+    }
+
+    /**
+     * Construct a DateTime object from a tim value and a Unit
+     * @param  timeValue  value of time in timeUnits
+     * @param  timeUnits  units of value
+     */
+     public DateTime(double timeValue, Unit timeUnits) 
+            throws VisADException {
+         this(new Real(RealType.Time, timeValue, timeUnits));
+     }
+
+
+    /**
+     * Construct a DateTime object and initialize it with a Java date.
+     *
+     * @param  date  date object
+     *
+     * @throws  VisADException  unit conversion problem
+     */
+    public DateTime(Date date)
+            throws VisADException
+    {
+	//Just go directly to the super with the Date value
+        super( RealType.Time,
+	       date.getTime()/1000.,
+               CommonUnit.secondsSinceTheEpoch, null, false);
+    }
+
+    /**
+     * Construct a DateTime object and initialize it to the current date/time.
+     *
+     * @throws  VisADException  unit conversion problem
+     */
+    public DateTime()
+            throws VisADException
+    {
+        this(new Real(RealType.Time,
+                      System.currentTimeMillis()/1000.,
+                      CommonUnit.secondsSinceTheEpoch));
+
+    }
+
+    /**
+     * Construct a DateTime object initialized with a year, day of
+     * the year, and seconds in the day.
+     *
+     * @param   year            year - use negative year to indicated BC
+     * @param   day             day of the year
+     * @param   seconds         seconds in the day
+     *
+     * @throws  VisADException  invalid day or seconds.  Days must be
+     *                          greater than zero and seconds must be greater
+     *                          than zero and less than or equal to the
+     *                          seconds in a day.
+     */
+    public DateTime(int year, int day, double seconds)
+            throws VisADException
+    {
+        this(fromYearDaySeconds(year, day, seconds));
+    }
+
+
+
+
+    /**
+     * Return a Real object whose value is the seconds since the Epoch
+     * initialized with a year, day of the year, and seconds in the day.
+     *
+     * @param   year            year - use negative year to indicated BC
+     * @param   day             day of the year
+     * @param   seconds         seconds in the day
+     *
+     * @throws  VisADException  invalid day or seconds.  Days must be
+     *                          greater than zero and seconds must be greater
+     *                          than zero and less than or equal to the
+     *                          seconds in a day.
+     */
+    public static Real fromYearDaySeconds(int year, int day, double seconds)
+            throws VisADException
+    {
+
+/*  Comment out for now - may want to revisit  DRM - 1999-05-06
+    Handle in trusted method to allow for BC years (year <= 0).
+        if (year < 1)
+        {
+            throw new VisADException(
+                              "DateTime.fromYearDaySeconds: invalid year");
+        }
+        int dayLimit = getCalendar().isLeapYear(year) ? 366 : 365;
+*/
+
+        // require positive day
+        if (day < 1)
+        {
+            throw new VisADException(
+                              "DateTime.fromYearDaySeconds: invalid day");
+        }
+
+        // require positive seconds and no more than are in a day.
+        if (seconds > secondsPerDay || seconds < 0.0) {
+            throw new VisADException(
+                              "DateTime.fromYearDaySeconds: invalid seconds");
+        }
+        return fromYearDaySecondsTrusted(year, day, seconds);
+    }
+
+    /** trusted method for initializers */
+    private static Real fromYearDaySecondsTrusted(int year,
+                                                  int day,
+                                                  double seconds)
+             throws VisADException
+    {
+        GregorianCalendar cal = new GregorianCalendar();
+        cal.clear();
+        cal.setTimeZone(DEFAULT_TIMEZONE);        // use GMT as default
+        if (year == 0) year = -1;                // set to 1 BC
+        cal.set(Calendar.ERA, year < 0
+                                ? GregorianCalendar.BC
+                                : GregorianCalendar.AD);
+        cal.set(Calendar.YEAR, Math.abs(year));
+
+        /*
+           allow us to specify # of days since the year began without having
+           worry about leap years and seconds since the day began, instead
+           of in the minute.  Saves on some calculations.
+        */
+        cal.setLenient(true);
+
+        cal.set(Calendar.DAY_OF_YEAR, day);
+        int temp = (int) Math.round(seconds * 1000);
+        int secs = temp/1000;
+        int millis = temp%1000;
+        cal.set(Calendar.SECOND, secs);
+        cal.set(Calendar.MILLISECOND, millis);
+
+        return new Real( RealType.Time,
+                         cal.getTime().getTime()/1000.,
+                         CommonUnit.secondsSinceTheEpoch );
+    }
+
+    /**
+     * Get a Real representing the number of seconds since * the epoch.
+     *
+     * @return this object
+     */
+    public Real getReal()
+    {
+        return this;
+    }
+
+
+    /** 
+     * Create, if needed, and return the utcCalendar 
+     *
+     * @returns The Calendar object to use
+     */
+    private Calendar getCalendar() 
+    {
+	if(utcCalendar == null) {
+	    utcCalendar =new GregorianCalendar(DEFAULT_TIMEZONE);
+	    utcCalendar.setTime(new Date(Math.round(getValue()*1000.)));
+	}	    
+	return utcCalendar;
+    }
+
+
+    /**
+     * Return a string representation of this DateTime.  Unless the
+     * setFormatPattern() and/or setTimeZone() methods were used, the
+     * default it the ISO 8601 complete date plus hours, minutes and seconds
+     * and a time zone of UTC.
+     * See <a href="http://www.w3.org/TR/NOTE-datetime">
+     *      http://www.w3.org/TR/NOTE-datetime</a>
+     * @see #setFormatPattern
+     * @see #setFormatTimeZone
+     *
+     * @return  String representing the date/time.  Default is
+     *          <nobr>yyyy-MM-dd HH:mm:ssZ</nobr> (ex: 1999-05-04 15:27:08Z)
+     */
+    public String toString()
+    {
+        String pat = formatPattern;
+        if (formatPattern.equals(DEFAULT_TIME_FORMAT)) {
+            if (timeZone.equals(DEFAULT_TIMEZONE)) {
+                pat = DEFAULT_TIME_FORMAT+"'Z'";
+            } else {
+                pat = pat + " z";
+            }
+            if (getCalendar().get(Calendar.ERA) == GregorianCalendar.BC) {
+                pat = pat + " 'BCE'";
+            }
+        }
+        return formattedString(pat, timeZone);
+    }
+
+    /**
+     * Gets a string that represents just the value portion of this
+     * DateTime -- but with full semantics.
+     *
+     * @return  String representing the date/time in the form
+     *          <nobr>yyyy-MM-dd HH:mm:ssZ</nobr> (ex: 1999-05-04 15:27:08Z)
+     */
+    public String toValueString() {
+        return toString();
+    }
+
+    /**
+     * Return a string representation of this DateTime from a user
+     * specified format.  The pattern uses the time format syntax
+     * of java.text.SimpleDateFormat and the time zone is any of the
+     * valid java.util.TimeZone values.
+     * @see java.text.SimpleDateFormat
+     * @see java.util.TimeZone
+     *
+     * @param   pattern         time format string
+     * @param   timezone        time zone to use
+     * @return  String representing the date/time in the form specified
+     *          by the pattern.
+     */
+    public String formattedString(String pattern, TimeZone timezone)
+    {
+        StringBuffer buf = new StringBuffer();
+        DateFormat sdf = null;
+        if(dateFormatClass!=null) {
+            try {
+                sdf = (DateFormat) dateFormatClass.newInstance();
+            } catch(Exception ie) {
+                throw new IllegalStateException("Error creating a DateFormat from:" + dateFormatClass.getName() +" " + ie);
+            }
+        } else {
+            sdf = new SimpleDateFormat();
+        }
+        sdf.setTimeZone(timezone);
+        if (pattern != null&& sdf instanceof SimpleDateFormat) {
+
+            ((SimpleDateFormat)sdf).applyPattern(pattern);
+        }
+        return (sdf.format(getCalendar().getTime(), buf,
+                                new FieldPosition(0))).toString();
+    }
+
+    /**
+     * Return a string representing the "date" portion of this DateTime
+     *
+     * @return  String representing the date (UTC) in the form
+     *          yyyy-MM-dd (ex: 1999-05-04).
+     */
+    public String dateString()
+    {
+        String pattern =
+                  (getCalendar().get(Calendar.ERA) == GregorianCalendar.BC)
+                            ? "yyyy-MM-dd 'BCE'"
+                            : "yyyy-MM-dd";
+        return formattedString(pattern, DEFAULT_TIMEZONE);
+    }
+
+    /**
+     * Return a string representing the "time" portion of this DateTime
+     *
+     * @return  String representing the time (UTC) in the form
+     *          HH:mm:ssZ (ex: 15:27:08Z)
+     */
+    public String timeString()
+    {
+        return formattedString("HH:mm:ss'Z'", DEFAULT_TIMEZONE);
+    }
+
+    /** 
+     * You can override the DateFormat by specifying a class. 
+     *
+     * @param dateFormatClass The class. This must be derived from java.text.DateFormat 
+     */
+    public static void setDateFormatClass(Class dateFormatClass) {
+        if(!java.text.DateFormat.class.isAssignableFrom(dateFormatClass)) {
+            throw new IllegalArgumentException("Not a DateFormat class: " + dateFormatClass.getName());
+        }
+        DateTime.dateFormatClass = dateFormatClass;
+    }
+
+    /**
+     * Set the format of the output of the toString() method.  All DateTime
+     * objects created in the JVM once this is set will use the new format
+     * so be very careful in your use of this method.
+     *
+     * The pattern uses the time format syntax of java.text.SimpleDateFormat.
+     * @see java.text.SimpleDateFormat
+     * If you want to use a time zone other than the default,
+     * @see #setFormatTimeZone
+     *
+     * @param   pattern         time format string
+     */
+    public static void setFormatPattern(String pattern)
+    {
+        formatPattern = pattern;
+    }
+
+    /**
+     * Return the format pattern used in the output of the toString() method.
+     * The pattern uses the time format syntax of java.text.SimpleDateFormat.
+     * @see java.text.SimpleDateFormat
+     *
+     * @return  time format pattern
+     */
+    public static String getFormatPattern()
+    {
+        return formatPattern;
+    }
+
+    /**
+     * Set the TimeZone of the output of the toString() method.  All DateTime
+     * objects created in the JVM once this is set will use the new TimeZone
+     * so be very careful in your use of this method.
+     *
+     * The time zone is any of the valid java.util.TimeZone values.
+     * @see java.util.TimeZone
+     *
+     * @param   tz              time zone
+     */
+    public static void setFormatTimeZone(TimeZone tz)
+    {
+        timeZone = tz;
+    }
+
+    /**
+     * Return the TimeZone used in the output of the toString() method.
+     *
+     * @return  time zone
+     */
+    public static TimeZone getFormatTimeZone()
+    {
+        return timeZone;
+    }
+
+    /**
+     * Reset the format of the output of the toString() method to the default -
+     * <nobr>yyyy-MM-dd HH:mm:ssZ</nobr> (ex: 1999-05-04 15:27:08Z) and
+     * the TimeZone to UTC.
+     */
+    public static void resetFormat()
+    {
+        formatPattern = DEFAULT_TIME_FORMAT;
+        timeZone = DEFAULT_TIMEZONE;
+    }
+
+    /**
+     * Create a DateTime object from a string specification
+     * @param  dateString  date string specification in format pattern
+     *                     defined for DateTime in this JVM
+     *
+     * @throws  VisADException  formatting problem
+     * @see #setFormatPattern
+     */
+    public static DateTime createDateTime(String dateString)
+        throws VisADException
+    {
+        return createDateTime(dateString, formatPattern, DEFAULT_TIMEZONE);
+    }
+
+    /**
+     * Create a DateTime object from a string specification using the
+     * supplied pattern and default timezone.
+     * @param  dateString  date string specification
+     * @param  format string
+     *
+     * @throws  VisADException  formatting problem
+     */
+    public static DateTime createDateTime(String dateString, String format)
+        throws VisADException
+    {
+        return createDateTime(dateString, format, DEFAULT_TIMEZONE);
+    }
+
+    /**
+     * Create a DateTime object from a string specification using the
+     * supplied pattern and timezone.
+     * @param  dateString     date string specification
+     * @param  format		  format string
+     * @param  timezone       TimeZone to use
+     *
+     * @throws  VisADException  formatting problem
+     */
+    public static DateTime createDateTime(String dateString, 
+                                        String format, 
+                                        TimeZone timezone)
+        throws VisADException
+    {
+        Date d;
+        try {
+          SimpleDateFormat sdf = new SimpleDateFormat();
+          sdf.setTimeZone(timezone);
+          sdf.applyPattern(format);
+          d = sdf.parse(dateString);
+        } catch (ParseException pe) {
+            throw new VisADException("invalid date string: " + dateString);
+        }
+        return new DateTime(d);
+    }
+
+    /**
+     *  Implement Comparable interface
+     *
+     * @param   oo      Object for comparison - should be DateTime
+     */
+    public int compareTo(Object oo)
+    {
+        return super.compareTo(oo);
+    }
+
+    /**
+     * Create a Gridded1DDoubleSet from an array of DateTimes
+     *
+     * @param  times  array of DateTimes.  Array cannot be null or only
+     *                have one entry.
+     *
+     * @return Gridded1DDouble set representing the array
+     * @throws VisADException  couldn't create the GriddedDoubleSet
+     */
+    public static Gridded1DDoubleSet makeTimeSet(DateTime[] times)
+        throws VisADException
+    {
+        Arrays.sort(times);
+        double[][] timeValues = new double[1][times.length];
+        for (int i = 0; i < times.length; i++) 
+            timeValues[0][i] = times[i].getValue(CommonUnit.secondsSinceTheEpoch);
+        return new Gridded1DDoubleSet(RealType.Time, timeValues, times.length);
+    }
+
+    /**
+     * Create a Gridded1DDoubleSet from an array of doubles of seconds
+     * since the epoch.
+     *
+     * @param  times  array of times in seconds since the epoch. Array 
+     *                cannot be null or only have one entry.
+     *
+     * @return set representing the array as a Gridded1DDoubleSet
+     * @throws VisADException  couldn't create the GriddedDoubleSet
+     */
+    public static Gridded1DDoubleSet makeTimeSet(double[] times)
+        throws VisADException
+    {
+        Arrays.sort(times);
+        double[][] alltimes = new double[1][times.length];
+        for (int i = 0; i < times.length; i++) alltimes[0][i] = times[i];
+        return new Gridded1DDoubleSet(RealType.Time, alltimes, times.length);
+    }
+
+    /**
+     * Create an array of DateTimes from a Gridded1DSet of times.
+     *
+     * @param  timeSet   Gridded1DSet of times
+     *
+     * @throws VisADException  invalid time set or couldn't create DateTimes
+     */
+    public static DateTime[] timeSetToArray(Gridded1DSet timeSet)
+        throws VisADException
+    {
+        Unit unit = timeSet.getSetUnits()[0];
+        if (!Unit.canConvert(unit, CommonUnit.secondsSinceTheEpoch)) {
+            throw new VisADException( "Invalid Units for timeSet");
+        }
+        double[][] values;
+        if (!unit.equals(CommonUnit.secondsSinceTheEpoch)) 
+            values = Unit.convertTuple(timeSet.getDoubles(), new Unit[] {unit},
+                              new Unit[] {CommonUnit.secondsSinceTheEpoch}, false);
+        else
+            values = timeSet.getDoubles();
+        DateTime[] times = new DateTime[timeSet.getLength()];
+
+        for (int i = 0; i < timeSet.getLength(); i++)
+            times[i] = new DateTime(values[0][i]);
+        return times;
+    }
+
+    /**
+     * run 'java visad.DateTime' to test the DateTime class
+     */
+    public static void main(String args[]) throws VisADException {
+
+        Real r;
+        DateTime a;
+
+        System.out.println(
+                    "\nInitialized using DateTime(1959, 284, 36600.):");
+        a = new DateTime(1959, 284, 36600.);
+
+        System.out.println(
+                      "\n\ttoString()        = " + a +
+                      "\n\tdateString()      = " + a.dateString() +
+                      "\n\ttimeString()      = " + a.timeString() +
+                      "\n\tformattedString() = " +
+                      a.formattedString("(EEE) dd-MMM-yy hh:mm:SS.sss z",
+                                               TimeZone.getTimeZone("EST")) +
+                      "\n\t  (using pattern " +
+                      "'(EEE) dd-MMM-yy hh:mm:SS.sss z' and timezone 'EST')");
+
+        // test using DateTime(Real r) where r has correct units and type
+        System.out.println("\nIncrementing 5 times by 20 days each time:\n");
+        for (int i = 0; i < 5; i++)
+        {
+              r = new Real(RealType.Time,
+                           a.getValue() + 20 * secondsPerDay,
+                           CommonUnit.secondsSinceTheEpoch);
+              a = new DateTime(r);
+              System.out.println("\t" + a);
+         }
+
+        // test for backward compatibility
+        System.out.println("\nInitialized using Real of RealType.Time" +
+                           " but no Unit (backward compatibility):");
+        r = new Real(RealType.Time, a.getValue() + secondsPerDay);
+        a = new DateTime(r);
+        System.out.println("\n\t" + a);
+
+        //  try BC date
+        System.out.println(
+                    "\nInitialized with a BCE date DateTime(-5, 196, 24493.):");
+        a = new DateTime(-5, 193, 24493.);
+        System.out.println(
+                      "\n\ttoString()        = " + a +
+                      "\n\tdateString()      = " + a.dateString() +
+                      "\n\ttimeString()      = " + a.timeString());
+
+        // test using Date() values
+        Date date = new Date();
+        a = new DateTime(date);
+        System.out.println("\nInitialized with current Date(): " + a);
+        a = new DateTime(date.getTime()/1000.);
+        System.out.println(
+                  "\nInitialized with current seconds since the epoch: "
+                            + a + "\n");
+        a = DateTime.createDateTime(a.toString());
+        System.out.println("\nUsing createDateTime with string of current Date(): " + a);
+
+    }
+
+/* Here's the output:
+
+java visad.DateTime
+
+Initialized using DateTime(1959, 284, 36600.):
+
+        toString()        = 1959-10-11 10:10:00Z
+        dateString()      = 1959-10-11
+        timeString()      = 10:10:00Z
+        formattedString() = (Sun) 11-Oct-59 06:10:00.000 EDT
+          (using pattern '(EEE) dd-MMM-yy hh:mm:SS.sss z' and timezone 'EST')
+
+Incrementing 5 times by 20 days each time:
+
+        1959-10-31 10:10:00Z
+        1959-11-20 10:10:00Z
+        1959-12-10 10:10:00Z
+        1959-12-30 10:10:00Z
+        1960-01-19 10:10:00Z
+
+Initialized using Real of RealType.Time but no Unit (backward compatibility):
+
+        1960-01-20 10:10:00Z
+
+Initialized with a BCE date DateTime(-5, 196, 24493.):
+
+        toString()        = 0005-07-11 06:48:13Z BCE
+        dateString()      = 0005-07-11 BCE
+        timeString()      = 06:48:13Z
+
+Initialized with current Date(): 1999-05-06 23:01:41Z
+
+Initialized with current seconds since the epoch: 1999-05-06 23:01:41Z
+
+*/
+
+}
diff --git a/visad/Delaunay.java b/visad/Delaunay.java
new file mode 100644
index 0000000..edabd5f
--- /dev/null
+++ b/visad/Delaunay.java
@@ -0,0 +1,989 @@
+//
+// Delaunay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Delaunay represents an abstract class for calculating an
+   N-dimensional Delaunay triangulation, that can be extended
+   to allow for various triangulation algorithms.<P>
+*/
+public abstract class Delaunay implements java.io.Serializable {
+
+  /**
+   * triangles/tetrahedra --> vertices.<p>
+   * Tri = new int[ntris][dim + 1]
+   *
+   * <p>This is the key output, a list of triangles (in two dimensions,
+   * tetrahedra in three dimensions, etc). ntris is the number of triangles.
+   *
+   * <p>In 2-D, Tri[i] is an array of 3 integers, which are three indices into
+   * the samples[0] and samples[1] arrays to get the x and y values of the
+   * three vertices of the triangle.
+   *
+   * <p>In 3-D, Tri[i] is an array of 4 integers, which are four indices into
+   * the samples[0], samples[1] and samples[2] arrays to get the x, y and z
+   * values of the four vertices of the tetrahedron.
+   *
+   * <p>This pattern continues for higher dimensionalities.
+   */
+  public int[][] Tri;
+
+  /**
+   * vertices --> triangles/tetrahedra.<p>
+   * Vertices = new int[nrs][nverts[i]]
+   *
+   * <p>nrs is the number of samples (the length of the samples[0] and
+   * samples[1] arrays. For sample i, Vertices[i] is a (variable length) list
+   * of indices into the Tri array above, giving the indices of the triangles
+   * that include vertex i.
+   *
+   * <p>nverts is an array as the second index of the Vertices array since
+   * different vertices may be part of different numbers of triangles.
+   *
+   * <p>You can use Tri and Vertices together to traverse the triangulation.
+   * If you don't need to traverse, then you can probably ignore all arrays
+   * except Tri.
+   */
+  public int[][] Vertices;
+
+  /**
+   * triangles/tetrahedra --> triangles/tetrahedra.<p>
+   * Walk = new int[ntris][dim + 1]
+   *
+   * <p>Also useful for traversing the triangulation, in this case giving the
+   * indices of triangles that share edges with the current triangle.
+   */
+  public int[][] Walk;
+
+  /**
+   * tri/tetra edges --> global edge number.<p>
+   * Edges = new int[ntris][3 * (dim - 1)];
+   *
+   * <p>'global edge number' is the number of an edge that is unique among the
+   * whole triangulation. This number is not an index into any array, but will
+   * match for a shared edge between two triangles.
+   */
+  public int[][] Edges;
+
+  /** number of unique global edge numbers */
+  public int NumEdges;
+
+  private boolean nonConvex = false;
+
+  /**
+   * The abstract constructor initializes the class's data arrays.
+   * @throws VisADException a VisAD error occurred
+   */
+  public Delaunay() throws VisADException {
+    Tri = null;
+    Vertices = null;
+    Walk = null;
+    Edges = null;
+    NumEdges = 0;
+  }
+
+  /**
+   * set flag indicating this Delaunay topology is non-convex
+   */
+  public void setNonConvex() {
+    nonConvex = true;
+  }
+
+  /**
+   * @return flag indicating whether this Delaunay topology is non-convex
+   */
+  public boolean getNonConvex() {
+    return nonConvex;
+  }
+
+  /**
+   * @return clone of this Delaunay as a DelaunayCustom
+   */
+  public Object clone() {
+    try {
+      return new DelaunayCustom(null, Tri, Vertices, Walk, Edges, NumEdges);
+    }
+    catch (VisADException e) {
+      throw new VisADError("Delaunay.clone: " + e.toString());
+    }
+  }
+
+  /**
+   * The factory class method heuristically decides which extension
+   * to the Delaunay abstract class to use in order to construct the
+   * fastest triangulation, and calls that extension, returning the
+   * finished triangulation. The method chooses from among the Fast,
+   * Clarkson, and Watson methods.
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param exact flag indicating need for exact Delaunay triangulation
+   * @return a topology using an appropriate sub-class of Delaunay
+   * @throws VisADException a VisAD error occurred
+   */
+  public static Delaunay factory(float[][] samples, boolean exact)
+                                                  throws VisADException {
+
+    /* Note: Clarkson doesn't work well for very closely clumped site values,
+             since the algorithm rounds each value to the nearest integer
+             before computing the triangulation.  This fact should probably
+             be taken into account in this factory algorithm, but as of yet
+             is not.  In other words, if you need an exact triangulation
+             and have more than 3000 data sites, and they have closely
+             clumped values, be sure to scale them up before calling the
+             factory method. */
+
+    /* Note: The factory method will not take new Delaunay extensions into
+             account unless it is extended as well. */
+
+    int choice;
+    int FAST = 0;
+    int CLARKSON = 1;
+    int WATSON = 2;
+
+    int dim = samples.length;
+    if (dim < 2) throw new VisADException("Delaunay.factory: "
+                                         +"dimension must be 2 or higher");
+
+    // only Clarkson can handle triangulations in high dimensions
+    if (dim > 3) {
+      choice = CLARKSON;
+    }
+    else {
+      int nrs = samples[0].length;
+      for (int i=1; i<dim; i++) {
+        nrs = Math.min(nrs, samples[i].length);
+      }
+      if (dim == 2 && !exact && nrs > 10000) {
+        // use fast in 2-D with a very large set and exact not required
+        choice = FAST;
+      }
+      else if (nrs > 3000) {
+        // use Clarkson for large sets
+        choice = CLARKSON;
+      }
+      else {
+        choice = WATSON;
+      }
+    }
+
+    try {
+      if (choice == FAST) {
+        // triangulate with the Fast method and one improvement pass
+        DelaunayFast delan = new DelaunayFast(samples);
+        delan.improve(samples, 1);
+        return (Delaunay) delan;
+      }
+      if (choice == CLARKSON) {
+        // triangulate with the Clarkson method
+        DelaunayClarkson delan = new DelaunayClarkson(samples);
+        return (Delaunay) delan;
+      }
+      if (choice == WATSON) {
+        // triangulate with the Watson method
+        DelaunayWatson delan = new DelaunayWatson(samples);
+        return (Delaunay) delan;
+      }
+    }
+    catch (Exception e) {
+      if (choice != CLARKSON) {
+        try {
+          // triangulate with the Clarkson method
+          DelaunayClarkson delan = new DelaunayClarkson(samples);
+          return (Delaunay) delan;
+        }
+        catch (Exception ee) {
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * alters the values of the samples by multiplying them by
+   * the mult factor
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param mult multiplication factor
+   * @param copy specifies whether scale should modify and return the
+   *             argument samples array or a copy
+   * @return array of scaled values
+   */
+  public static float[][] scale(float[][] samples, float mult,
+                                boolean copy) {
+    int dim = samples.length;
+    int nrs = samples[0].length;
+    for (int i=1; i<dim; i++) {
+      if (samples[i].length < nrs) nrs = samples[i].length;
+    }
+
+    // make a copy if needed
+    float[][] samp = copy ? Set.copyFloats(samples) : samples;
+
+    // scale points
+    for (int i=0; i<dim; i++) {
+      for (int j=0; j<nrs; j++) {
+        samp[i][j] *= mult;
+      }
+    }
+
+    return samp;
+  }
+
+  /**
+   * increments samples coordinates by random numbers between -epsilon
+   * and epsilon, in order to eliminate triangulation problems such as
+   * co-linear and co-located points
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param epsilon size limit on random perturbations
+   * @param copy specifies whether perturb should modify and return the
+   *             argument samples array or a copy
+   * @return array of perturbed values
+   */
+  public static float[][] perturb(float[][] samples, float epsilon,
+                                  boolean copy) {
+    int dim = samples.length;
+    int nrs = samples[0].length;
+    for (int i=1; i<dim; i++) {
+      if (samples[i].length < nrs) nrs = samples[i].length;
+    }
+
+    // make a copy if needed
+    float[][] samp = copy ? Set.copyFloats(samples) : samples;
+
+    // perturb points
+    for (int i=0; i<dim; i++) {
+      for (int j=0; j<nrs; j++) {
+        samp[i][j] += (float)(2*epsilon*(Math.random()-0.5));
+      }
+    }
+
+    return samp;
+  }
+
+  /**
+   * check this triangulation in various ways to make sure it is
+   * constructed correctly. This method is expensive, provided
+   * mainly for debugging purposes.
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @return flag that is false to indicate there are problems with
+   *         the triangulation
+   */
+  public boolean test(float[][] samples) {
+    return test(samples, false);
+  }
+
+  public boolean test(float[][] samples, boolean printErrors) {
+
+    int dim = samples.length;
+    int dim1 = dim+1;
+    int ntris = Tri.length;
+    int nrs = samples[0].length;
+    for (int i=1; i<dim; i++) {
+      nrs = Math.min(nrs, samples[i].length);
+    }
+
+    // verify triangulation dimension
+    for (int i=0; i<ntris; i++) {
+      if (Tri[i].length < dim1) {
+        if (printErrors) {
+          System.err.println("Delaunay.test: invalid triangulation " +
+            "dimension (Tri[" + i + "].length=" + Tri[i].length +
+            "; dim1=" + dim1 + ")");
+        }
+        return false;
+      }
+    }
+
+    // verify no illegal triangle vertices
+    for (int i=0; i<ntris; i++) {
+      for (int j=0; j<dim1; j++) {
+        if (Tri[i][j] < 0 || Tri[i][j] >= nrs) {
+          if (printErrors) {
+            System.err.println("Delaunay.test: illegal triangle vertex (" +
+              "Tri[" + i + "][" + j + "]=" + Tri[i][j] + "; nrs=" + nrs + ")");
+          }
+          return false;
+        }
+      }
+    }
+
+    // verify that all points are in at least one triangle
+    int[] nverts = new int[nrs];
+    for (int i=0; i<nrs; i++) nverts[i] = 0;
+    for (int i=0; i<ntris; i++) {
+      for (int j=0; j<dim1; j++) nverts[Tri[i][j]]++;
+    }
+    for (int i=0; i<nrs; i++) {
+      if (nverts[i] == 0) {
+        if (printErrors) {
+          System.err.println("Delaunay.test: point not in triangle (" +
+            "nverts[" + i + "]=0)");
+        }
+        return false;
+      }
+    }
+
+    // test for duplicate triangles
+    for (int i=0; i<ntris; i++) {
+      for (int j=i+1; j<ntris; j++) {
+        boolean[] m = new boolean[dim1];
+        for (int mi=0; mi<dim1; mi++) m[mi] = false;
+        for (int k=0; k<dim1; k++) {
+          for (int l=0; l<dim1; l++) {
+            if (Tri[i][k] == Tri[j][l] && !m[l]) {
+              m[l] = true;
+            }
+          }
+        }
+        boolean mtot = true;
+        for (int k=0; k<dim1; k++) {
+          if (!m[k]) mtot = false;
+        }
+        if (mtot) {
+          if (printErrors) {
+            System.err.println("Delaunay.test: duplicate triangles (i=" + i +
+              "; j=" + j + ")");
+          }
+          return false;
+        }
+      }
+    }
+
+    // test for errors in Walk array
+    for (int i=0; i<ntris; i++) {
+      for (int j=0; j<dim1; j++) {
+        if (Walk[i][j] != -1) {
+          boolean found = false;
+          for (int k=0; k<dim1; k++) {
+            if (Walk[Walk[i][j]][k] == i) found = true;
+          }
+          if (!found) {
+            if (printErrors) {
+              System.err.println("Delaunay.test: error in Walk array (i=" + i +
+                "; j=" + j + ")");
+            }
+            return false;
+          }
+
+          // make sure two walk'ed triangles share dim vertices
+          int sb = 0;
+          for (int k=0; k<dim1; k++) {
+            for (int l=0; l<dim1; l++) {
+              if (Tri[i][k] == Tri[Walk[i][j]][l]) sb++;
+            }
+          }
+          if (sb != dim) {
+            if (printErrors) {
+              System.err.println("Delaunay.test: error in Walk array (i=" + i +
+                "; j=" + j + "; sb=" + sb + "; dim=" + dim + ")");
+            }
+            return false;
+          }
+        }
+      }
+    }
+
+    // Note: Another test that could be performed is one that
+    //       makes sure, given a triangle T, all points in the
+    //       triangulation that are not part of T are located
+    //       outside the bounds of T.  This test would verify
+    //       that there are no overlapping triangles.
+
+    // all tests passed
+    return true;
+  }
+
+  /**
+   * use edge-flipping to bring the current triangulation closer
+   * to the true Delaunay triangulation.
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param pass the number of passes the algorithm should take over
+   *             all edges (however, the algorithm terminates if no
+   *             edges are flipped for an entire pass). 
+   * @throws VisADException a VisAD error occurred
+   */
+  public void improve(float[][] samples, int pass) throws VisADException {
+    int dim = samples.length;
+    int dim1 = dim+1;
+    if (Tri[0].length != dim1) {
+      throw new SetException("Delaunay.improve: samples dimension " +
+                             "does not match");
+    }
+    // only 2-D triangulations supported for now
+    if (dim > 2) {
+      throw new UnimplementedException("Delaunay.improve: dimension " +
+                                       "must be 2!");
+    }
+    int ntris = Tri.length;
+    int nrs = samples[0].length;
+    for (int i=1; i<dim; i++) {
+      nrs = Math.min(nrs, samples[i].length);
+    }
+    float[] samp0 = samples[0];
+    float[] samp1 = samples[1];
+
+    // go through entire triangulation pass times
+    boolean eflipped = false;
+    for (int p=0; p<pass; p++) {
+      eflipped = false;
+
+      // edge keeps track of which edges have been checked
+      boolean[] edge = new boolean[NumEdges];
+      for (int i=0; i<NumEdges; i++) edge[i] = true;
+
+      // check every edge of every triangle
+      for (int t=0; t<ntris; t++) {
+        int[] trit = Tri[t];
+        int[] walkt = Walk[t];
+        int[] edgest = Edges[t];
+        for (int e=0; e<2; e++) {
+          int curedge = edgest[e];
+          // only check the edge if it hasn't been checked yet
+          if (edge[curedge]) {
+            int t2 = walkt[e];
+
+            // only check edge if it is not part of the outer hull
+            if (t2 >= 0) {
+              int[] trit2 = Tri[t2];
+              int[] walkt2 = Walk[t2];
+              int[] edgest2 = Edges[t2];
+
+              // check if the diagonal needs to be flipped
+              int f = (walkt2[0] == t) ? 0 :
+                      (walkt2[1] == t) ? 1 : 2;
+              int A = (e + 2) % 3;
+              int B = (A + 1) % 3;
+              int C = (B + 1) % 3;
+              int D = (f + 2) % 3;
+              float ax = samp0[trit[A]];
+              float ay = samp1[trit[A]];
+              float bx = samp0[trit[B]];
+              float by = samp1[trit[B]];
+              float cx = samp0[trit[C]];
+              float cy = samp1[trit[C]];
+              float dx = samp0[trit2[D]];
+              float dy = samp1[trit2[D]];
+              float abx = ax - bx;
+              float aby = ay - by;
+              float acx = ax - cx;
+              float acy = ay - cy;
+              float dbx = dx - bx;
+              float dby = dy - by;
+              float dcx = dx - cx;
+              float dcy = dy - cy;
+              float Q = abx*acx + aby*acy;
+              float R = dbx*abx + dby*aby;
+              float S = acx*dcx + acy*dcy;
+              float T = dbx*dcx + dby*dcy;
+              boolean QD = abx*acy - aby*acx >= 0;
+              boolean RD = dbx*aby - dby*abx >= 0;
+              boolean SD = acx*dcy - acy*dcx >= 0;
+              boolean TD = dcx*dby - dcy*dbx >= 0;
+              boolean sig = (QD ? 1 : 0) + (RD ? 1 : 0)
+                          + (SD ? 1 : 0) + (TD ? 1 : 0) < 2;
+              boolean d;
+              if (QD == sig) d = true;
+              else if (RD == sig) d = false;
+              else if (SD == sig) d = false;
+              else if (TD == sig) d = true;
+              else if (Q < 0 && T < 0 || R > 0 && S > 0) d = true;
+              else if (R < 0 && S < 0 || Q > 0 && T > 0) d = false;
+              else if ((Q < 0 ? Q : T) < (R < 0 ? R : S)) d = true;
+              else d = false;
+              if (d) {
+                // diagonal needs to be swapped
+                eflipped = true;
+                int n1 = trit[A];
+                int n2 = trit[B];
+                int n3 = trit[C];
+                int n4 = trit2[D];
+                int w1 = walkt[A];
+                int w2 = walkt[C];
+                int e1 = edgest[A];
+                int e2 = edgest[C];
+                int w3, w4, e3, e4;
+                if (trit2[(D+1)%3] == trit[C]) {
+                  w3 = walkt2[D];
+                  w4 = walkt2[(D+2)%3];
+                  e3 = edgest2[D];
+                  e4 = edgest2[(D+2)%3];
+                }
+                else {
+                  w3 = walkt2[(D+2)%3];
+                  w4 = walkt2[D];
+                  e3 = edgest2[(D+2)%3];
+                  e4 = edgest2[D];
+                }
+
+                // update Tri array
+                trit[0] = n1;
+                trit[1] = n2;
+                trit[2] = n4;
+                trit2[0] = n1;
+                trit2[1] = n4;
+                trit2[2] = n3;
+
+                // update Walk array
+                walkt[0] = w1;
+                walkt[1] = w4;
+                walkt[2] = t2;
+                walkt2[0] = t;
+                walkt2[1] = w3;
+                walkt2[2] = w2;
+                if (w2 >= 0) {
+                  int val = (Walk[w2][0] == t) ? 0
+                          : (Walk[w2][1] == t) ? 1 : 2;
+                  Walk[w2][val] = t2;
+                }
+                if (w4 >= 0) {
+                  int val = (Walk[w4][0] == t2) ? 0
+                          : (Walk[w4][1] == t2) ? 1 : 2;
+                  Walk[w4][val] = t;
+                }
+
+                // update Edges array
+                edgest[0] = e1;
+                edgest[1] = e4;
+                // Edges[t][2] and Edges[t2][0] stay the same
+                edgest2[1] = e3;
+                edgest2[2] = e2;
+
+                // update Vertices array
+                int[] vertn1 = Vertices[n1];
+                int[] vertn2 = Vertices[n2];
+                int[] vertn3 = Vertices[n3];
+                int[] vertn4 = Vertices[n4];
+                int ln1 = vertn1.length;
+                int ln2 = vertn2.length;
+                int ln3 = vertn3.length;
+                int ln4 = vertn4.length;
+                int[] tn1 = new int[ln1 + 1];  // Vertices[n1] adds t2
+                int[] tn2 = new int[ln2 - 1];  // Vertices[n2] loses t2
+                int[] tn3 = new int[ln3 - 1];  // Vertices[n3] loses t
+                int[] tn4 = new int[ln4 + 1];  // Vertices[n4] adds t
+                System.arraycopy(vertn1, 0, tn1, 0, ln1);
+                tn1[ln1] = t2;
+                int c = 0;
+                for (int i=0; i<ln2; i++) {
+                  if (vertn2[i] != t2) tn2[c++] = vertn2[i];
+                }
+                c = 0;
+                for (int i=0; i<ln3; i++) {
+                  if (vertn3[i] != t) tn3[c++] = vertn3[i];
+                }
+                System.arraycopy(vertn4, 0, tn4, 0, ln4);
+                tn4[ln4] = t;
+                Vertices[n1] = tn1;
+                Vertices[n2] = tn2;
+                Vertices[n3] = tn3;
+                Vertices[n4] = tn4;
+              }
+            }
+
+            // the edge has now been checked
+            edge[curedge] = false;
+          }
+        }
+      }
+
+      // if no edges have been flipped this pass, then stop
+      if (!eflipped) break;
+    }
+  }
+
+  /**
+   * calculate a triangulation's helper arrays, Walk and Edges, if the
+   * triangulation algorithm hasn't calculated them already. Any
+   * extension to the Delaunay class should call finish_triang() at
+   * the end of its triangulation constructor.
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @throws VisADException a VisAD error occurred
+   */
+  public void finish_triang(float[][] samples) throws VisADException {
+    int mdim = Tri[0].length - 1;
+    int mdim1 = mdim + 1;
+    int dim = samples.length;
+    int dim1 = dim+1;
+    int ntris = Tri.length;
+    int nrs = samples[0].length;
+    for (int i=1; i<dim; i++) {
+      nrs = Math.min(nrs, samples[i].length);
+    }
+
+    if (Vertices == null) {
+      // build Vertices component
+      Vertices = new int[nrs][];
+      int[] nverts = new int[nrs];
+      for (int i=0; i<ntris; i++) {
+        for (int j=0; j<mdim1; j++) nverts[Tri[i][j]]++;
+      }
+      for (int i=0; i<nrs; i++) {
+        Vertices[i] = new int[nverts[i]];
+        nverts[i] = 0;
+      }
+      for (int i=0; i<ntris; i++) {
+        for (int j=0; j<mdim1; j++) {
+          Vertices[Tri[i][j]][nverts[Tri[i][j]]++] = i;
+        }
+      }
+    }
+
+    if (Walk == null && mdim <= 3) {
+      // build Walk component
+      Walk = new int[ntris][mdim1];
+      for (int i=0; i<ntris; i++) {
+      WalkDim:
+        for (int j=0; j<mdim1; j++) {
+          int v1 = j;
+          int v2 = (v1+1)%mdim1;
+          Walk[i][j] = -1;
+          for (int k=0; k<Vertices[Tri[i][v1]].length; k++) {
+            int temp = Vertices[Tri[i][v1]][k];
+            if (temp != i) {
+              for (int l=0; l<Vertices[Tri[i][v2]].length; l++) {
+                if (mdim == 2) {
+                  if (temp == Vertices[Tri[i][v2]][l]) {
+                    Walk[i][j] = temp;
+                    continue WalkDim;
+                  }
+                }
+                else {    // mdim == 3
+                  int temp2 = Vertices[Tri[i][v2]][l];
+                  int v3 = (v2+1)%mdim1;
+                  if (temp == temp2) {
+                    for (int m=0; m<Vertices[Tri[i][v3]].length; m++) {
+                      if (temp == Vertices[Tri[i][v3]][m]) {
+                        Walk[i][j] = temp;
+                        continue WalkDim;
+                      }
+                    }
+                  }
+                } // end if (mdim == 3)
+              } // end for (int l=0; l<Vertices[Tri[i][v2]].length; l++)
+            } // end if (temp != i)
+          } // end for (int k=0; k<Vertices[Tri[i][v1]].length; k++)
+        } // end for (int j=0; j<mdim1; j++)
+      } // end for (int i=0; i<Tri.length; i++)
+    } // end if (Walk == null && mdim <= 3)
+
+    if (Edges == null && mdim <= 3) {
+      // build Edges component
+
+      // initialize all edges to "not yet found"
+      int edim = 3*(mdim-1);
+      Edges = new int[ntris][edim];
+      for (int i=0; i<ntris; i++) {
+        for (int j=0; j<edim; j++) Edges[i][j] = -1;
+      }
+
+      // calculate global edge values
+      NumEdges = 0;
+      if (mdim == 2) {
+        for (int i=0; i<ntris; i++) {
+          for (int j=0; j<3; j++) {
+            if (Edges[i][j] < 0) {
+              // this edge doesn't have a "global edge number" yet
+              int othtri = Walk[i][j];
+              if (othtri >= 0) {
+                int cside = -1;
+                for (int k=0; k<3; k++) {
+                  if (Walk[othtri][k] == i) cside = k;
+                }
+                if (cside != -1) {
+                  Edges[othtri][cside] = NumEdges;
+                }
+                else {
+                  throw new SetException("Delaunay.finish_triang: " +
+                                         "error in triangulation!");
+                }
+              }
+              Edges[i][j] = NumEdges++;
+            }
+          }
+        }
+      }
+      else {    // mdim == 3
+        int[] ptlook1 = {0, 0, 0, 1, 1, 2};
+        int[] ptlook2 = {1, 2, 3, 2, 3, 3};
+        for (int i=0; i<ntris; i++) {
+          for (int j=0; j<6; j++) {
+            if (Edges[i][j] < 0) {
+              // this edge doesn't have a "global edge number" yet
+
+              // search through the edge's two end points
+              int endpt1 = Tri[i][ptlook1[j]];
+              int endpt2 = Tri[i][ptlook2[j]];
+
+              // create an intersection of two sets
+              int[] set = new int[Vertices[endpt1].length];
+              int setlen = 0;
+              for (int p1=0; p1<Vertices[endpt1].length; p1++) {
+                int temp = Vertices[endpt1][p1];
+                for (int p2=0; p2<Vertices[endpt2].length; p2++) {
+                  if (temp == Vertices[endpt2][p2]) {
+                    set[setlen++] = temp;
+                    break;
+                  }
+                }
+              }
+
+              // assign global edge number to all members of set
+              for (int kk=0; kk<setlen; kk++) {
+                int k = set[kk];
+                for (int l=0; l<edim; l++) {
+                  if ((Tri[k][ptlook1[l]] == endpt1
+                    && Tri[k][ptlook2[l]] == endpt2)
+                   || (Tri[k][ptlook1[l]] == endpt2
+                    && Tri[k][ptlook2[l]] == endpt1)) {
+                    Edges[k][l] = NumEdges;
+                  }
+                }
+              }
+              Edges[i][j] = NumEdges++;
+            } // end if (Edges[i][j] < 0)
+          } // end for (int j=0; j<6; j++)
+        } // end for (int i=0; i<ntris; i++)
+      } // end if (mdim == 3)
+    } // end if (Edges == null && mdim <= 3)
+  }
+/*
+  public void finish_triang(float[][] samples) throws VisADException {
+    int dim = samples.length;
+    int dim1 = dim+1;
+    int ntris = Tri.length;
+    int nrs = samples[0].length;
+    for (int i=1; i<dim; i++) {
+      nrs = Math.min(nrs, samples[i].length);
+    }
+
+    if (Vertices == null) {
+      // build Vertices component
+      Vertices = new int[nrs][];
+      int[] nverts = new int[nrs];
+      for (int i=0; i<ntris; i++) {
+        for (int j=0; j<dim1; j++) nverts[Tri[i][j]]++;
+      }
+      for (int i=0; i<nrs; i++) {
+        Vertices[i] = new int[nverts[i]];
+        nverts[i] = 0;
+      }
+      for (int i=0; i<ntris; i++) {
+        for (int j=0; j<dim1; j++) {
+          Vertices[Tri[i][j]][nverts[Tri[i][j]]++] = i;
+        }
+      }
+    }
+
+    if (Walk == null && dim <= 3) {
+      // build Walk component
+      Walk = new int[ntris][dim1];
+      for (int i=0; i<Tri.length; i++) {
+      WalkDim:
+        for (int j=0; j<dim1; j++) {
+          int v1 = j;
+          int v2 = (v1+1)%dim1;
+          Walk[i][j] = -1;
+          for (int k=0; k<Vertices[Tri[i][v1]].length; k++) {
+            int temp = Vertices[Tri[i][v1]][k];
+            if (temp != i) {
+              for (int l=0; l<Vertices[Tri[i][v2]].length; l++) {
+                if (dim == 2) {
+                  if (temp == Vertices[Tri[i][v2]][l]) {
+                    Walk[i][j] = temp;
+                    continue WalkDim;
+                  }
+                }
+                else {    // dim == 3
+                  int temp2 = Vertices[Tri[i][v2]][l];
+                  int v3 = (v2+1)%dim1;
+                  if (temp == temp2) {
+                    for (int m=0; m<Vertices[Tri[i][v3]].length; m++) {
+                      if (temp == Vertices[Tri[i][v3]][m]) {
+                        Walk[i][j] = temp;
+                        continue WalkDim;
+                      }
+                    }
+                  }
+                } // end if (dim == 3)
+              } // end for (int l=0; l<Vertices[Tri[i][v2]].length; l++)
+            } // end if (temp != i)
+          } // end for (int k=0; k<Vertices[Tri[i][v1]].length; k++)
+        } // end for (int j=0; j<dim1; j++)
+      } // end for (int i=0; i<Tri.length; i++)
+    } // end if (Walk == null && dim <= 3)
+
+    if (Edges == null && dim <= 3) {
+      // build Edges component
+
+      // initialize all edges to "not yet found"
+      int edim = 3*(dim-1);
+      Edges = new int[ntris][edim];
+      for (int i=0; i<ntris; i++) {
+        for (int j=0; j<edim; j++) Edges[i][j] = -1;
+      }
+
+      // calculate global edge values
+      NumEdges = 0;
+      if (dim == 2) {
+        for (int i=0; i<ntris; i++) {
+          for (int j=0; j<3; j++) {
+            if (Edges[i][j] < 0) {
+              // this edge doesn't have a "global edge number" yet
+              int othtri = Walk[i][j];
+              if (othtri >= 0) {
+                int cside = -1;
+                for (int k=0; k<3; k++) {
+                  if (Walk[othtri][k] == i) cside = k;
+                }
+                if (cside != -1) {
+                  Edges[othtri][cside] = NumEdges;
+                }
+                else {
+                  throw new SetException("Delaunay.finish_triang: " +
+                                         "error in triangulation!");
+                }
+              }
+              Edges[i][j] = NumEdges++;
+            }
+          }
+        }
+      }
+      else {    // dim == 3
+        int[] ptlook1 = {0, 0, 0, 1, 1, 2};
+        int[] ptlook2 = {1, 2, 3, 2, 3, 3};
+        for (int i=0; i<ntris; i++) {
+          for (int j=0; j<6; j++) {
+            if (Edges[i][j] < 0) {
+              // this edge doesn't have a "global edge number" yet
+
+              // search through the edge's two end points
+              int endpt1 = Tri[i][ptlook1[j]];
+              int endpt2 = Tri[i][ptlook2[j]];
+
+              // create an intersection of two sets
+              int[] set = new int[Vertices[endpt1].length];
+              int setlen = 0;
+              for (int p1=0; p1<Vertices[endpt1].length; p1++) {
+                int temp = Vertices[endpt1][p1];
+                for (int p2=0; p2<Vertices[endpt2].length; p2++) {
+                  if (temp == Vertices[endpt2][p2]) {
+                    set[setlen++] = temp;
+                    break;
+                  }
+                }
+              }
+
+              // assign global edge number to all members of set
+              for (int kk=0; kk<setlen; kk++) {
+                int k = set[kk];
+                for (int l=0; l<edim; l++) {
+                  if ((Tri[k][ptlook1[l]] == endpt1
+                    && Tri[k][ptlook2[l]] == endpt2)
+                   || (Tri[k][ptlook1[l]] == endpt2
+                    && Tri[k][ptlook2[l]] == endpt1)) {
+                    Edges[k][l] = NumEdges;
+                  }
+                }
+              }
+              Edges[i][j] = NumEdges++;
+            } // end if (Edges[i][j] < 0)
+          } // end for (int j=0; j<6; j++)
+        } // end for (int i=0; i<ntris; i++)
+      } // end if (dim == 3)
+    } // end if (Edges == null && dim <= 3)
+  }
+*/
+
+  /**
+   * @return a String representation of this
+   */
+  public String toString() {
+    return sampleString(null);
+  }
+
+  /**
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points] - may be null
+   * @return a String representation of this, including samples if
+   *         it is non-null
+   */
+  public String sampleString(float[][] samples) {
+    StringBuffer s = new StringBuffer("");
+    if (samples != null) {
+      s.append("\nsamples " + samples[0].length + "\n");
+      for (int i=0; i<samples[0].length; i++) {
+        s.append("  " + i + " -> " + samples[0][i] + " " +
+                 samples[1][i] + " " + samples[2][i] + "\n");
+      }
+      s.append("\n");
+    }
+
+    s.append("\nTri (triangles -> vertices) " + Tri.length + "\n");
+    for (int i=0; i<Tri.length; i++) {
+      s.append("  " + i + " -> ");
+      for (int j=0; j<Tri[i].length; j++) {
+        s.append(" " + Tri[i][j]);
+      }
+      s.append("\n");
+    }
+
+    s.append("\nVertices (vertices -> triangles) " + Vertices.length + "\n");
+    for (int i=0; i<Vertices.length; i++) {
+      s.append("  " + i + " -> ");
+      for (int j=0; j<Vertices[i].length; j++) {
+        s.append(" " + Vertices[i][j]);
+      }
+      s.append("\n");
+    }
+
+    s.append("\nWalk (triangles -> triangles) " + Walk.length + "\n");
+    for (int i=0; i<Walk.length; i++) {
+      s.append("  " + i + " -> ");
+      for (int j=0; j<Walk[i].length; j++) {
+        s.append(" " + Walk[i][j]);
+      }
+      s.append("\n");
+    }
+
+    s.append("\nEdges (triangles -> global edges) " + Edges.length + "\n");
+    for (int i=0; i<Edges.length; i++) {
+      s.append("  " + i + " -> ");
+      for (int j=0; j<Edges[i].length; j++) {
+        s.append(" " + Edges[i][j]);
+      }
+      s.append("\n");
+    }
+    return s.toString();
+  }
+
+}
+
diff --git a/visad/Delaunay2D.txt b/visad/Delaunay2D.txt
new file mode 100644
index 0000000..869ac5d
--- /dev/null
+++ b/visad/Delaunay2D.txt
@@ -0,0 +1,8 @@
+2 6
+
+397 22
+169 105
+425 321
+159 281
+520 173
+333 146
diff --git a/visad/Delaunay3D.txt b/visad/Delaunay3D.txt
new file mode 100644
index 0000000..f7a26a6
--- /dev/null
+++ b/visad/Delaunay3D.txt
@@ -0,0 +1,7 @@
+3 5
+
+1 1 1
+5 1 1
+3 7 1
+3 1 10
+3 -5 1
diff --git a/visad/DelaunayClarkson.java b/visad/DelaunayClarkson.java
new file mode 100644
index 0000000..755ea1f
--- /dev/null
+++ b/visad/DelaunayClarkson.java
@@ -0,0 +1,1260 @@
+//
+// DelaunayClarkson.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/* The Delaunay triangulation algorithm in this class
+ * is originally from hull by Ken Clarkson:
+ *
+ * Ken Clarkson wrote this.  Copyright (c) 1995 by AT&T..
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+
+/**
+   DelaunayClarkson represents an O(N*logN) method
+   with high overhead to find the Delaunay triangulation
+   of a set of samples of R^DomainDimension.<P>
+*/
+public class DelaunayClarkson extends Delaunay {
+
+/* ******* BEGINNING OF CONVERTED HULL CODE ******* */
+
+  // <<<< Constants >>>>
+  private static final double DBL_MANT_DIG = 53;
+  private static final double FLT_RADIX = 2;
+  private static final double DBL_EPSILON = 2.2204460492503131E-16;
+  private static final double ln2 = Math.log(2);
+
+
+  // <<<< Variables >>>>
+  /* we need to have two indices for every pointer into basis_s and
+     simplex arrays, because they are two-dimensional arrays of
+     blocks.  ( _bn = block number ) */
+
+  // for the pseudo-pointers
+  private static final int INFINITY = -2;      // replaces infinity
+  private static final int NOVAL = -1;         // replaces null
+
+  private float[][] site_blocks;        // copy of samples array
+  private int[][]   a3s;                // output array
+  private int       a3size;             // output array maximum size
+  private int       nts = 0;            // # output objects
+
+  private static final int max_blocks = 10000; // max # basis/simplex blocks
+  private static final int Nobj = 10000;
+  private static final int MAXDIM = 8;         // max dimension
+
+  private int    dim;
+  private int    p;
+  private long   pnum;
+  private int    rdim,          // region dimension
+                 cdim;          // # sites currently specifying region
+  private int    exact_bits;
+  private double b_err_min,
+                 b_err_min_sq;
+  private double ldetbound = 0;
+  private int    failcount = 0;          // static: reduce_inner
+  private int    lscale;                 // static: reduce_inner
+  private double max_scale;              // static: reduce_inner
+  private float  Sb;                     // static: reduce_inner
+  private int    nsb = 0;                // # simplex blocks
+  private int    nbb = 0;                // # basis_s blocks
+  private int    ss = MAXDIM;            // static: search
+  private int    ss2 = 2000;             // static: visit_triang
+  private long   vnum = -1;              // static: visit_triang
+  private int    p_neigh_vert = NOVAL;   // static: main
+
+  // "void stuff" -- dummy variables to hold unused return information
+  private int[] voidp = new int[1];
+  private int[] voidp_bn = new int[1];
+
+  // basis_s stuff
+  private int[][]      bbt_next = new int[max_blocks][];
+  private int[][]      bbt_next_bn = new int[max_blocks][];
+  private int[][]      bbt_ref_count = new int[max_blocks][];
+  private int[][]      bbt_lscale = new int[max_blocks][];
+  private double[][]   bbt_sqa = new double[max_blocks][];
+  private double[][]   bbt_sqb = new double[max_blocks][];
+  private double[][][] bbt_vecs = new double[max_blocks][][];
+
+  private int ttbp;
+  private int ttbp_bn;
+  private int ib;
+  private int ib_bn;
+  private int basis_s_list = NOVAL;
+  private int basis_s_list_bn;
+  private int pnb = NOVAL;
+  private int pnb_bn;
+  private int b = NOVAL;              // static: sees
+  private int b_bn;
+
+  // simplex stuff
+  private int[][]   sbt_next = new int[max_blocks][];
+  private int[][]   sbt_next_bn = new int[max_blocks][];
+  private long[][]  sbt_visit = new long[max_blocks][];
+  private short[][] sbt_mark = new short[max_blocks][];
+  private int[][]   sbt_normal = new int[max_blocks][];
+  private int[][]   sbt_normal_bn = new int[max_blocks][];
+  private int[][]   sbt_peak_vert = new int[max_blocks][];
+  private int[][]   sbt_peak_simp = new int[max_blocks][];
+  private int[][]   sbt_peak_simp_bn = new int[max_blocks][];
+  private int[][]   sbt_peak_basis = new int[max_blocks][];
+  private int[][]   sbt_peak_basis_bn = new int[max_blocks][];
+  private int[][][] sbt_neigh_vert = new int[max_blocks][][];
+  private int[][][] sbt_neigh_simp = new int[max_blocks][][];
+  private int[][][] sbt_neigh_simp_bn = new int[max_blocks][][];
+  private int[][][] sbt_neigh_basis = new int[max_blocks][][];
+  private int[][][] sbt_neigh_basis_bn = new int[max_blocks][][];
+
+  private int   simplex_list = NOVAL;
+  private int   simplex_list_bn;
+  private int   ch_root;
+  private int   ch_root_bn;
+  private int   ns;                            // static: make_facets
+  private int   ns_bn;
+  private int[] st = new int[ss+MAXDIM+1];    // static: search
+  private int[] st_bn = new int[ss+MAXDIM+1];
+  private int[] st2 = new int[ss2+MAXDIM+1];    // static: visit_triang
+  private int[] st2_bn = new int[ss2+MAXDIM+1];
+
+
+  // <<<< Functions >>>>
+  private int new_block_basis_s() {
+    bbt_next[nbb] = new int[Nobj];
+    bbt_next_bn[nbb] = new int[Nobj];
+    bbt_ref_count[nbb] = new int[Nobj];
+    bbt_lscale[nbb] = new int[Nobj];
+    bbt_sqa[nbb] = new double[Nobj];
+    bbt_sqb[nbb] = new double[Nobj];
+    bbt_vecs[nbb] = new double[2*rdim][];
+    for (int i=0; i<2*rdim; i++) bbt_vecs[nbb][i] = new double[Nobj];
+    for (int i=0; i<Nobj; i++) {
+      bbt_next[nbb][i] = i+1;
+      bbt_next_bn[nbb][i] = nbb;
+      bbt_ref_count[nbb][i] = 0;
+      bbt_lscale[nbb][i] = 0;
+      bbt_sqa[nbb][i] = 0;
+      bbt_sqb[nbb][i] = 0;
+      for (int j=0; j<2*rdim; j++) bbt_vecs[nbb][j][i] = 0;
+    }
+    bbt_next[nbb][Nobj-1] = NOVAL;
+    basis_s_list = 0;
+    basis_s_list_bn = nbb;
+    nbb++;
+    return basis_s_list;
+  }
+
+  private int reduce_inner(int v, int v_bn, int s, int s_bn, int k) {
+    int q, q_bn;
+    double dd,
+           Sb = 0;
+    double scale;
+
+    bbt_sqa[v_bn][v] = 0;
+    for (int i=0; i<rdim; i++) {
+      bbt_sqa[v_bn][v] += bbt_vecs[v_bn][i][v] * bbt_vecs[v_bn][i][v];
+    }
+    bbt_sqb[v_bn][v] = bbt_sqa[v_bn][v];
+    if (k <= 1) {
+      for (int i=0; i<rdim; i++) {
+        bbt_vecs[v_bn][i][v] = bbt_vecs[v_bn][rdim+i][v];
+      }
+      return 1;
+    }
+    for (int j=0; j<250; j++) {
+      int    xx = rdim;
+      double labound;
+
+      for (int i=0; i<rdim; i++) {
+        bbt_vecs[v_bn][i][v] = bbt_vecs[v_bn][rdim+i][v];
+      }
+      for (int i=k-1; i>0; i--) {
+        q = sbt_neigh_basis[s_bn][i][s];
+        q_bn = sbt_neigh_basis_bn[s_bn][i][s];
+        dd = 0;
+        for (int l=0; l<rdim; l++) {
+          dd -= bbt_vecs[q_bn][l][q] * bbt_vecs[v_bn][l][v];
+        }
+        dd /= bbt_sqb[q_bn][q];
+        for (int l=0; l<rdim; l++) {
+          bbt_vecs[v_bn][l][v] += dd * bbt_vecs[q_bn][rdim+l][q];
+        }
+      }
+      bbt_sqb[v_bn][v] = 0;
+      for (int i=0; i<rdim; i++) {
+        bbt_sqb[v_bn][v] += bbt_vecs[v_bn][i][v] * bbt_vecs[v_bn][i][v];
+      }
+      bbt_sqa[v_bn][v] = 0;
+      for (int i=0; i<rdim; i++) {
+        bbt_sqa[v_bn][v] += bbt_vecs[v_bn][rdim+i][v]
+                          * bbt_vecs[v_bn][rdim+i][v];
+      }
+
+      if (2*bbt_sqb[v_bn][v] >= bbt_sqa[v_bn][v]) return 1;
+
+      // scale up vector
+      if (j < 10) {
+        labound = Math.floor(Math.log(bbt_sqa[v_bn][v])/ln2) / 2;
+        max_scale = exact_bits-labound-0.66*(k-2)-1;
+        if (max_scale < 1) max_scale = 1;
+
+        if (j == 0) {
+
+          ldetbound = 0;
+          Sb = 0;
+          for (int l=k-1; l>0; l--) {
+            q = sbt_neigh_basis[s_bn][l][s];
+            q_bn = sbt_neigh_basis_bn[s_bn][l][s];
+            Sb += bbt_sqb[q_bn][q];
+            ldetbound += Math.floor(Math.log(bbt_sqb[q_bn][q])/ln2) / 2 + 1;
+            ldetbound -= bbt_lscale[q_bn][q];
+          }
+        }
+      }
+      if (ldetbound - bbt_lscale[v_bn][v]
+        + Math.floor(Math.log(bbt_sqb[v_bn][v])/ln2) / 2 + 1 < 0) {
+        scale = 0;
+      }
+      else {
+        lscale = (int) (Math.log(2*Sb/(bbt_sqb[v_bn][v]
+                                     + bbt_sqa[v_bn][v]*b_err_min))/ln2) / 2;
+        if (lscale > max_scale) lscale = (int) max_scale;
+        else if (lscale < 0) lscale = 0;
+        bbt_lscale[v_bn][v] += lscale;
+        scale = (lscale < 20) ? 1 << lscale : Math.pow(2, lscale);
+      }
+
+      while (xx < 2*rdim) bbt_vecs[v_bn][xx++][v] *= scale;
+
+      for (int i=k-1; i>0; i--) {
+        q = sbt_neigh_basis[s_bn][i][s];
+        q_bn = sbt_neigh_basis_bn[s_bn][i][s];
+        dd = 0;
+        for (int l=0; l<rdim; l++) {
+          dd -= bbt_vecs[q_bn][l][q] * bbt_vecs[v_bn][rdim+l][v];
+        }
+        dd /= bbt_sqb[q_bn][q];
+        dd = Math.floor(dd+0.5);
+        for (int l=0; l<rdim; l++) {
+          bbt_vecs[v_bn][rdim+l][v] += dd * bbt_vecs[q_bn][rdim+l][q];
+        }
+      }
+    }
+    if (failcount++ < 10) System.out.println("reduce_inner failed!");
+    return 0;
+  }
+
+  private int reduce(int[] v, int[] v_bn, int rp, int s, int s_bn, int k) {
+    if (v[0] == NOVAL) {
+      v[0] = basis_s_list != NOVAL ? basis_s_list : new_block_basis_s();
+      v_bn[0] = basis_s_list_bn;
+      basis_s_list = bbt_next[v_bn[0]][v[0]];
+      basis_s_list_bn = bbt_next_bn[v_bn[0]][v[0]];
+      bbt_ref_count[v_bn[0]][v[0]] = 1;
+    }
+    else bbt_lscale[v_bn[0]][v[0]] = 0;
+    if (rp == INFINITY) {
+      bbt_next[v_bn[0]][v[0]] = bbt_next[ib_bn][ib];
+      bbt_next_bn[v_bn[0]][v[0]] = bbt_next_bn[ib_bn][ib];
+      bbt_ref_count[v_bn[0]][v[0]] = bbt_ref_count[ib_bn][ib];
+      bbt_lscale[v_bn[0]][v[0]] = bbt_lscale[ib_bn][ib];
+      bbt_sqa[v_bn[0]][v[0]] = bbt_sqa[ib_bn][ib];
+      bbt_sqb[v_bn[0]][v[0]] = bbt_sqb[ib_bn][ib];
+      for (int i=0; i<2*rdim; i++) {
+        bbt_vecs[v_bn[0]][i][v[0]] = bbt_vecs[ib_bn][i][ib];
+      }
+    }
+    else {
+      double sum = 0;
+      int sbt_nv = sbt_neigh_vert[s_bn][0][s];
+      if (sbt_nv == INFINITY) {
+        for (int i=0; i<dim; i++) {
+          bbt_vecs[v_bn[0]][i+rdim][v[0]] = bbt_vecs[v_bn[0]][i][v[0]]
+            = (double) site_blocks[i][rp];
+        }
+      }
+      else {
+        for (int i=0; i<dim; i++) {
+          bbt_vecs[v_bn[0]][i+rdim][v[0]] = bbt_vecs[v_bn[0]][i][v[0]]
+            = (double) (site_blocks[i][rp] - site_blocks[i][sbt_nv]);
+        }
+      }
+      for (int i=0; i<dim; i++) {
+        sum += bbt_vecs[v_bn[0]][i][v[0]] * bbt_vecs[v_bn[0]][i][v[0]];
+      }
+      bbt_vecs[v_bn[0]][2*rdim-1][v[0]] = sum;
+      bbt_vecs[v_bn[0]][rdim-1][v[0]] = sum;
+    }
+    return reduce_inner(v[0], v_bn[0], s, s_bn, k);
+  }
+
+  private void get_basis_sede(int s, int s_bn) {
+    int   k=1;
+    int   q, q_bn;
+    int[] curt = new int[1];
+    int[] curt_bn = new int[1];
+
+    if (sbt_neigh_vert[s_bn][0][s] == INFINITY && cdim > 1) {
+      int t_vert, t_simp, t_simp_bn, t_basis, t_basis_bn;
+      t_vert = sbt_neigh_vert[s_bn][0][s];
+      t_simp = sbt_neigh_simp[s_bn][0][s];
+      t_simp_bn = sbt_neigh_simp_bn[s_bn][0][s];
+      t_basis = sbt_neigh_basis[s_bn][0][s];
+      t_basis_bn = sbt_neigh_basis_bn[s_bn][0][s];
+      sbt_neigh_vert[s_bn][0][s] = sbt_neigh_vert[s_bn][k][s];
+      sbt_neigh_simp[s_bn][0][s] = sbt_neigh_simp[s_bn][k][s];
+      sbt_neigh_simp_bn[s_bn][0][s] = sbt_neigh_simp_bn[s_bn][k][s];
+      sbt_neigh_basis[s_bn][0][s] = sbt_neigh_basis[s_bn][k][s];
+      sbt_neigh_basis_bn[s_bn][0][s] = sbt_neigh_basis_bn[s_bn][k][s];
+      sbt_neigh_vert[s_bn][k][s] = t_vert;
+      sbt_neigh_simp[s_bn][k][s] = t_simp;
+      sbt_neigh_simp_bn[s_bn][k][s] = t_simp_bn;
+      sbt_neigh_basis[s_bn][k][s] = t_basis;
+      sbt_neigh_basis_bn[s_bn][k][s] = t_basis_bn;
+
+      q = sbt_neigh_basis[s_bn][0][s];
+      q_bn = sbt_neigh_basis_bn[s_bn][0][s];
+      if ((q != NOVAL) && --bbt_ref_count[q_bn][q] == 0) {
+        bbt_next[q_bn][q] = basis_s_list;
+        bbt_next_bn[q_bn][q] = basis_s_list_bn;
+        bbt_ref_count[q_bn][q] = 0;
+        bbt_lscale[q_bn][q] = 0;
+        bbt_sqa[q_bn][q] = 0;
+        bbt_sqb[q_bn][q] = 0;
+        for (int j=0; j<2*rdim; j++) bbt_vecs[q_bn][j][q] = 0;
+        basis_s_list = q;
+        basis_s_list_bn = q_bn;
+      }
+
+      sbt_neigh_basis[s_bn][0][s] = ttbp;
+      sbt_neigh_basis_bn[s_bn][0][s] = ttbp_bn;
+      bbt_ref_count[ttbp_bn][ttbp]++;
+    }
+    else {
+      if (sbt_neigh_basis[s_bn][0][s] == NOVAL) {
+        sbt_neigh_basis[s_bn][0][s] = ttbp;
+        sbt_neigh_basis_bn[s_bn][0][s] = ttbp_bn;
+        bbt_ref_count[ttbp_bn][ttbp]++;
+      } else while (k < cdim && sbt_neigh_basis[s_bn][k][s] != NOVAL) k++;
+    }
+    while (k < cdim) {
+      q = sbt_neigh_basis[s_bn][k][s];
+      q_bn = sbt_neigh_basis_bn[s_bn][k][s];
+      if (q != NOVAL && --bbt_ref_count[q_bn][q] == 0) {
+        bbt_next[q_bn][q] = basis_s_list;
+        bbt_next_bn[q_bn][q] = basis_s_list_bn;
+        bbt_ref_count[q_bn][q] = 0;
+        bbt_lscale[q_bn][q] = 0;
+        bbt_sqa[q_bn][q] = 0;
+        bbt_sqb[q_bn][q] = 0;
+        for (int j=0; j<2*rdim; j++) bbt_vecs[q_bn][j][q] = 0;
+        basis_s_list = q;
+        basis_s_list_bn = q_bn;
+      }
+      sbt_neigh_basis[s_bn][k][s] = NOVAL;
+      curt[0] = sbt_neigh_basis[s_bn][k][s];
+      curt_bn[0] = sbt_neigh_basis_bn[s_bn][k][s];
+      reduce(curt, curt_bn, sbt_neigh_vert[s_bn][k][s], s, s_bn, k);
+      sbt_neigh_basis[s_bn][k][s] = curt[0];
+      sbt_neigh_basis_bn[s_bn][k][s] = curt_bn[0];
+      k++;
+    }
+  }
+
+  private int sees(int rp, int s, int s_bn) {
+    double  dd, dds;
+    int     q, q_bn, q1, q1_bn, q2, q2_bn;
+    int[]   curt = new int[1];
+    int[]   curt_bn = new int[1];
+
+    if (b == NOVAL) {
+      b = (basis_s_list != NOVAL) ? basis_s_list : new_block_basis_s();
+      b_bn = basis_s_list_bn;
+      basis_s_list = bbt_next[b_bn][b];
+      basis_s_list_bn = bbt_next_bn[b_bn][b];
+    }
+    else bbt_lscale[b_bn][b] = 0;
+    if (cdim==0) return 0;
+    if (sbt_normal[s_bn][s] == NOVAL) {
+      get_basis_sede(s, s_bn);
+      if (rdim==3 && cdim==3) {
+        sbt_normal[s_bn][s] = basis_s_list != NOVAL ? basis_s_list
+                                                    : new_block_basis_s();
+        sbt_normal_bn[s_bn][s] = basis_s_list_bn;
+        q = sbt_normal[s_bn][s];
+        q_bn = sbt_normal_bn[s_bn][s];
+        basis_s_list = bbt_next[q_bn][q];
+        basis_s_list_bn = bbt_next_bn[q_bn][q];
+        q1 = sbt_neigh_basis[s_bn][1][s];
+        q1_bn = sbt_neigh_basis_bn[s_bn][1][s];
+        q2 = sbt_neigh_basis[s_bn][2][s];
+        q2_bn = sbt_neigh_basis_bn[s_bn][2][s];
+        bbt_ref_count[q_bn][q] = 1;
+        bbt_vecs[q_bn][0][q] = bbt_vecs[q1_bn][1][q1]
+                  *bbt_vecs[q2_bn][2][q2]
+             - bbt_vecs[q1_bn][2][q1]
+                  *bbt_vecs[q2_bn][1][q2];
+        bbt_vecs[q_bn][1][q] = bbt_vecs[q1_bn][2][q1]
+                  *bbt_vecs[q2_bn][0][q2]
+             - bbt_vecs[q1_bn][0][q1]
+                  *bbt_vecs[q2_bn][2][q2];
+        bbt_vecs[q_bn][2][q] = bbt_vecs[q1_bn][0][q1]
+                  *bbt_vecs[q2_bn][1][q2]
+             - bbt_vecs[q1_bn][1][q1]
+                  *bbt_vecs[q2_bn][0][q2];
+        bbt_sqb[q_bn][q] = 0;
+        for (int i=0; i<rdim; i++) bbt_sqb[q_bn][q] += bbt_vecs[q_bn][i][q]
+                                                     * bbt_vecs[q_bn][i][q];
+        for (int i=cdim+1; i>0; i--) {
+          int m = (i > 1) ? sbt_neigh_vert[ch_root_bn][i-2][ch_root]
+                          : INFINITY;
+          int j;
+          for (j=0; j<cdim && m != sbt_neigh_vert[s_bn][j][s]; j++);
+          if (j < cdim) continue;
+          if (m == INFINITY) {
+            if (bbt_vecs[q_bn][2][q] > -b_err_min) continue;
+          }
+          else {
+            if (sees(m, s, s_bn) == 0) {
+              continue;
+            }
+          }
+          bbt_vecs[q_bn][0][q] = -bbt_vecs[q_bn][0][q];
+          bbt_vecs[q_bn][1][q] = -bbt_vecs[q_bn][1][q];
+          bbt_vecs[q_bn][2][q] = -bbt_vecs[q_bn][2][q];
+          break;
+        }
+      }
+      else {
+        for (int i=cdim+1; i>0; i--) {
+          int m = (i > 1) ? sbt_neigh_vert[ch_root_bn][i-2][ch_root]
+                          : INFINITY;
+          int j;
+          for (j=0; j<cdim && m != sbt_neigh_vert[s_bn][j][s]; j++);
+          if (j < cdim) continue;
+          curt[0] = sbt_normal[s_bn][s];
+          curt_bn[0] = sbt_normal_bn[s_bn][s];
+          reduce(curt, curt_bn, m, s, s_bn, cdim);
+          q = sbt_normal[s_bn][s] = curt[0];
+          q_bn = sbt_normal_bn[s_bn][s] = curt_bn[0];
+          if (bbt_sqb[q_bn][q] != 0) break;
+        }
+      }
+
+      for (int i=0; i<cdim; i++) {
+        q = sbt_neigh_basis[s_bn][i][s];
+        q_bn = sbt_neigh_basis_bn[s_bn][i][s];
+        if (q != NOVAL && --bbt_ref_count[q_bn][q] == 0) {
+          bbt_next[q_bn][q] = basis_s_list;
+          bbt_next_bn[q_bn][q] = basis_s_list_bn;
+          bbt_ref_count[q_bn][q] = 0;
+          bbt_lscale[q_bn][q] = 0;
+          bbt_sqa[q_bn][q] = 0;
+          bbt_sqb[q_bn][q] = 0;
+          for (int l=0; l<2*rdim; l++) bbt_vecs[q_bn][l][q] = 0;
+          basis_s_list = q;
+          basis_s_list_bn = q_bn;
+        }
+        sbt_neigh_basis[s_bn][i][s] = NOVAL;
+      }
+    }
+    if (rp == INFINITY) {
+      bbt_next[b_bn][b] = bbt_next[ib_bn][ib];
+      bbt_next_bn[b_bn][b] = bbt_next_bn[ib_bn][ib];
+      bbt_ref_count[b_bn][b] = bbt_ref_count[ib_bn][ib];
+      bbt_lscale[b_bn][b] = bbt_lscale[ib_bn][ib];
+      bbt_sqa[b_bn][b] = bbt_sqa[ib_bn][ib];
+      bbt_sqb[b_bn][b] = bbt_sqb[ib_bn][ib];
+      for (int i=0; i<2*rdim; i++) {
+        bbt_vecs[b_bn][i][b] = bbt_vecs[ib_bn][i][ib];
+      }
+    }
+    else {
+      double sum = 0;
+      int sbt_nv = sbt_neigh_vert[s_bn][0][s];
+      if (sbt_nv == INFINITY) {
+        for (int l=0; l<dim; l++) {
+          bbt_vecs[b_bn][l+rdim][b] = bbt_vecs[b_bn][l][b]
+            = (double) site_blocks[l][rp];
+        }
+      }
+      else {
+        for (int l=0; l<dim; l++) {
+          bbt_vecs[b_bn][l+rdim][b] = bbt_vecs[b_bn][l][b]
+            = (double) (site_blocks[l][rp] - site_blocks[l][sbt_nv]);
+        }
+      }
+      for (int l=0; l<dim; l++) {
+        sum += bbt_vecs[b_bn][l][b] * bbt_vecs[b_bn][l][b];
+      }
+      bbt_vecs[b_bn][2*rdim-1][b] = bbt_vecs[b_bn][rdim-1][b] = sum;
+    }
+    q = sbt_normal[s_bn][s];
+    q_bn = sbt_normal_bn[s_bn][s];
+    for (int i=0; i<3; i++) {
+      double sum = 0;
+      dd = 0;
+      for (int l=0; l<rdim; l++) {
+        dd += bbt_vecs[b_bn][l][b] * bbt_vecs[q_bn][l][q];
+      }
+      if (dd == 0.0) return 0;
+      for (int l=0; l<rdim; l++) {
+        sum += bbt_vecs[b_bn][l][b] * bbt_vecs[b_bn][l][b];
+      }
+      dds = dd*dd/bbt_sqb[q_bn][q]/sum;
+      if (dds > b_err_min_sq) return (dd < 0 ? 1 : 0);
+      get_basis_sede(s, s_bn);
+      reduce_inner(b, b_bn, s, s_bn, cdim);
+    }
+    return 0;
+  }
+
+  private int new_block_simplex() {
+    sbt_next[nsb] = new int[Nobj];
+    sbt_next_bn[nsb] = new int[Nobj];
+    sbt_visit[nsb] = new long[Nobj];
+    sbt_mark[nsb] = new short[Nobj];
+    sbt_normal[nsb] = new int[Nobj];
+    sbt_normal_bn[nsb] = new int[Nobj];
+    sbt_peak_vert[nsb] = new int[Nobj];
+    sbt_peak_simp[nsb] = new int[Nobj];
+    sbt_peak_simp_bn[nsb] = new int[Nobj];
+    sbt_peak_basis[nsb] = new int[Nobj];
+    sbt_peak_basis_bn[nsb] = new int[Nobj];
+    sbt_neigh_vert[nsb] = new int[rdim][];
+    sbt_neigh_simp[nsb] = new int[rdim][];
+    sbt_neigh_simp_bn[nsb] = new int[rdim][];
+    sbt_neigh_basis[nsb] = new int[rdim][];
+    sbt_neigh_basis_bn[nsb] = new int[rdim][];
+    for (int i=0; i<rdim; i++) {
+      sbt_neigh_vert[nsb][i] = new int[Nobj];
+      sbt_neigh_simp[nsb][i] = new int[Nobj];
+      sbt_neigh_simp_bn[nsb][i] = new int[Nobj];
+      sbt_neigh_basis[nsb][i] = new int[Nobj];
+      sbt_neigh_basis_bn[nsb][i] = new int[Nobj];
+    }
+    for (int i=0; i<Nobj; i++) {
+      sbt_next[nsb][i] = i+1;
+      sbt_next_bn[nsb][i] = nsb;
+      sbt_visit[nsb][i] = 0;
+      sbt_mark[nsb][i] = 0;
+      sbt_normal[nsb][i] = NOVAL;
+      sbt_peak_vert[nsb][i] = NOVAL;
+      sbt_peak_simp[nsb][i] = NOVAL;
+      sbt_peak_basis[nsb][i] = NOVAL;
+      for (int j=0; j<rdim; j++) {
+        sbt_neigh_vert[nsb][j][i] = NOVAL;
+        sbt_neigh_simp[nsb][j][i] = NOVAL;
+        sbt_neigh_basis[nsb][j][i] = NOVAL;
+      }
+    }
+    sbt_next[nsb][Nobj-1] = NOVAL;
+    simplex_list = 0;
+    simplex_list_bn = nsb;
+
+    nsb++;
+    return simplex_list;
+  }
+
+  /**
+     starting at s, visit simplices t such that test(s,i,0) is true,
+     and t is the i'th neighbor of s;
+     apply visit function to all visited simplices;
+     when visit returns nonnull, exit and return its value.
+  */
+  private void visit_triang_gen(int s, int s_bn, int whichfunc,
+                                int[] ret, int[] ret_bn) {
+    int v;
+    int v_bn;
+    int t;
+    int t_bn;
+    int tms = 0;
+
+    vnum--;
+    if (s != NOVAL) {
+      st2[tms] = s;
+      st2_bn[tms] = s_bn;
+      tms++;
+    }
+    while (tms != 0) {
+      if (tms > ss2) {
+        // JAVA: efficiency issue: how much is this stack hammered?
+        ss2 += ss2;
+        int[] newst2 = new int[ss2+MAXDIM+1];
+        int[] newst2_bn = new int[ss2+MAXDIM+1];
+        System.arraycopy(st2, 0, newst2, 0, st2.length);
+        System.arraycopy(st2_bn, 0, newst2_bn, 0, st2_bn.length);
+        st2 = newst2;
+        st2_bn = newst2_bn;
+      }
+      tms--;
+      t = st2[tms];
+      t_bn = st2_bn[tms];
+      if (t == NOVAL || sbt_visit[t_bn][t] == vnum) continue;
+      sbt_visit[t_bn][t] = vnum;
+      if (whichfunc == 1) {
+        if (sbt_peak_vert[t_bn][t] == NOVAL) {
+          v = t;
+          v_bn = t_bn;
+        }
+        else {
+          v = NOVAL;
+          v_bn = NOVAL;
+        }
+        if (v != NOVAL) {
+          ret[0] = v;
+          ret_bn[0] = v_bn;
+          return;
+        }
+      }
+      else {
+        int[] vfp = new int[cdim];
+
+        if (t != NOVAL) {
+          for (int j=0; j<cdim; j++) vfp[j] = sbt_neigh_vert[t_bn][j][t];
+          for (int j=0; j<cdim; j++) {
+            a3s[j][nts] = (vfp[j] == INFINITY) ? -1 : vfp[j];
+          }
+          nts++;
+          if (nts > a3size) {
+            // JAVA: efficiency issue, hammering an array
+            a3size += a3size;
+            int[][] newa3s = new int[rdim][a3size+MAXDIM+1];
+            for (int i=0; i<rdim; i++) {
+              System.arraycopy(a3s[i], 0, newa3s[i], 0, a3s[i].length);
+            }
+            a3s = newa3s;
+          }
+        }
+      }
+      for (int i=0; i<cdim; i++) {
+        int j = sbt_neigh_simp[t_bn][i][t];
+        int j_bn = sbt_neigh_simp_bn[t_bn][i][t];
+        if ((j != NOVAL) && sbt_visit[j_bn][j] != vnum) {
+          st2[tms] = j;
+          st2_bn[tms] = j_bn;
+          tms++;
+        }
+      }
+    }
+    ret[0] = NOVAL;
+  }
+
+  /**
+     make neighbor connections between newly created simplices incident to p.
+  */
+  private void connect(int s, int s_bn) {
+    int xb, xf;
+    int sb, sb_bn;
+    int sf, sf_bn;
+    int tf, tf_bn;
+    int ccj, ccj_bn;
+    int xfi;
+
+    if (s == NOVAL) return;
+    for (int i=0; (sbt_neigh_vert[s_bn][i][s] != p) && (i<cdim); i++);
+    if (sbt_visit[s_bn][s] == pnum) return;
+    sbt_visit[s_bn][s] = pnum;
+    ccj = sbt_peak_simp[s_bn][s];
+    ccj_bn = sbt_peak_simp_bn[s_bn][s];
+    for (xfi=0; (sbt_neigh_simp[ccj_bn][xfi][ccj] != s
+              || sbt_neigh_simp_bn[ccj_bn][xfi][ccj] != s_bn)
+                     && (xfi<cdim); xfi++);
+    for (int i=0; i<cdim; i++) {
+      int l;
+      if (p == sbt_neigh_vert[s_bn][i][s]) continue;
+      sb = sbt_peak_simp[s_bn][s];
+      sb_bn = sbt_peak_simp_bn[s_bn][s];
+      sf = sbt_neigh_simp[s_bn][i][s];
+      sf_bn = sbt_neigh_simp_bn[s_bn][i][s];
+      xf = sbt_neigh_vert[ccj_bn][xfi][ccj];
+      if (sbt_peak_vert[sf_bn][sf] == NOVAL) {  // are we done already?
+        for (l=0; (sbt_neigh_vert[ccj_bn][l][ccj]
+                != sbt_neigh_vert[s_bn][i][s]) && (l<cdim); l++);
+        sf = sbt_neigh_simp[ccj_bn][l][ccj];
+        sf_bn = sbt_neigh_simp_bn[ccj_bn][l][ccj];
+        if (sbt_peak_vert[sf_bn][sf] != NOVAL) continue;
+      } else do {
+        xb = xf;
+        for (l=0; (sbt_neigh_simp[sf_bn][l][sf] != sb
+                || sbt_neigh_simp_bn[sf_bn][l][sf] != sb_bn)
+                && l<cdim; l++);
+        xf = sbt_neigh_vert[sf_bn][l][sf];
+        sb = sf;
+        sb_bn = sf_bn;
+        for (l=0; (sbt_neigh_vert[sb_bn][l][sb] != xb) && (l<cdim); l++);
+        tf = sbt_neigh_simp[sf_bn][l][sf];
+        tf_bn = sbt_neigh_simp_bn[sf_bn][l][sf];
+        sf = tf;
+        sf_bn = tf_bn;
+      } while (sbt_peak_vert[sf_bn][sf] != NOVAL);
+
+      sbt_neigh_simp[s_bn][i][s] = sf;
+      sbt_neigh_simp_bn[s_bn][i][s] = sf_bn;
+      for (l=0; (sbt_neigh_vert[sf_bn][l][sf] != xf) && (l<cdim); l++);
+      sbt_neigh_simp[sf_bn][l][sf] = s;
+      sbt_neigh_simp_bn[sf_bn][l][sf] = s_bn;
+
+      connect(sf, sf_bn);
+    }
+
+  }
+
+  /**
+     visit simplices s with sees(p,s), and make a facet for every neighbor
+     of s not seen by p.
+  */
+  private void make_facets(int seen, int seen_bn, int[] ret, int[] ret_bn) {
+    int n, n_bn;
+    int q, q_bn;
+    int j;
+
+    if (seen == NOVAL) {
+      ret[0] = NOVAL;
+      return;
+    }
+    sbt_peak_vert[seen_bn][seen] = p;
+
+    for (int i=0; i<cdim; i++) {
+      n = sbt_neigh_simp[seen_bn][i][seen];
+      n_bn = sbt_neigh_simp_bn[seen_bn][i][seen];
+
+      if (pnum != sbt_visit[n_bn][n]) {
+        sbt_visit[n_bn][n] = pnum;
+        if (sees(p, n, n_bn) != 0) make_facets(n, n_bn, voidp, voidp_bn);
+      }
+      if (sbt_peak_vert[n_bn][n] != NOVAL) continue;
+
+      ns = (simplex_list != NOVAL) ? simplex_list : new_block_simplex();
+      ns_bn = simplex_list_bn;
+      simplex_list = sbt_next[ns_bn][ns];
+      simplex_list_bn = sbt_next_bn[ns_bn][ns];
+      sbt_next[ns_bn][ns] = sbt_next[seen_bn][seen];
+      sbt_next_bn[ns_bn][ns] = sbt_next_bn[seen_bn][seen];
+      sbt_visit[ns_bn][ns] = sbt_visit[seen_bn][seen];
+      sbt_mark[ns_bn][ns] = sbt_mark[seen_bn][seen];
+      sbt_normal[ns_bn][ns] = sbt_normal[seen_bn][seen];
+      sbt_normal_bn[ns_bn][ns] = sbt_normal_bn[seen_bn][seen];
+      sbt_peak_vert[ns_bn][ns] = sbt_peak_vert[seen_bn][seen];
+      sbt_peak_simp[ns_bn][ns] = sbt_peak_simp[seen_bn][seen];
+      sbt_peak_simp_bn[ns_bn][ns] = sbt_peak_simp_bn[seen_bn][seen];
+      sbt_peak_basis[ns_bn][ns] = sbt_peak_basis[seen_bn][seen];
+      sbt_peak_basis_bn[ns_bn][ns] = sbt_peak_basis_bn[seen_bn][seen];
+      for (j=0; j<rdim; j++) {
+        sbt_neigh_vert[ns_bn][j][ns] = sbt_neigh_vert[seen_bn][j][seen];
+        sbt_neigh_simp[ns_bn][j][ns] = sbt_neigh_simp[seen_bn][j][seen];
+        sbt_neigh_simp_bn[ns_bn][j][ns]
+                       = sbt_neigh_simp_bn[seen_bn][j][seen];
+        sbt_neigh_basis[ns_bn][j][ns] = sbt_neigh_basis[seen_bn][j][seen];
+        sbt_neigh_basis_bn[ns_bn][j][ns]
+                        = sbt_neigh_basis_bn[seen_bn][j][seen];
+      }
+
+      for (j=0; j<cdim; j++) {
+        q = sbt_neigh_basis[seen_bn][j][seen];
+        q_bn = sbt_neigh_basis_bn[seen_bn][j][seen];
+        if (q != NOVAL) bbt_ref_count[q_bn][q]++;
+      }
+
+      sbt_visit[ns_bn][ns] = 0;
+      sbt_peak_vert[ns_bn][ns] = NOVAL;
+      sbt_normal[ns_bn][ns] = NOVAL;
+      sbt_peak_simp[ns_bn][ns] = seen;
+      sbt_peak_simp_bn[ns_bn][ns] = seen_bn;
+
+      q = sbt_neigh_basis[ns_bn][i][ns];
+      q_bn = sbt_neigh_basis_bn[ns_bn][i][ns];
+      if (q != NOVAL && --bbt_ref_count[q_bn][q] == 0) {
+        bbt_next[q_bn][q] = basis_s_list;
+        bbt_next_bn[q_bn][q] = basis_s_list_bn;
+        bbt_ref_count[q_bn][q] = 0;
+        bbt_lscale[q_bn][q] = 0;
+        bbt_sqa[q_bn][q] = 0;
+        bbt_sqb[q_bn][q] = 0;
+        for (int l=0; l<2*rdim; l++) bbt_vecs[q_bn][l][q] = 0;
+        basis_s_list = q;
+        basis_s_list_bn = q_bn;
+      }
+      sbt_neigh_basis[ns_bn][i][ns] = NOVAL;
+
+      sbt_neigh_vert[ns_bn][i][ns] = p;
+      for (j=0; (sbt_neigh_simp[n_bn][j][n] != seen
+                  || sbt_neigh_simp_bn[n_bn][j][n] != seen_bn)
+                  && j<cdim; j++);
+      sbt_neigh_simp[seen_bn][i][seen] = sbt_neigh_simp[n_bn][j][n] = ns;
+      sbt_neigh_simp_bn[seen_bn][i][seen] = ns_bn;
+      sbt_neigh_simp_bn[n_bn][j][n] = ns_bn;
+    }
+    ret[0] = ns;
+    ret_bn[0] = ns_bn;
+  }
+
+  /**
+     p lies outside flat containing previous sites;
+     make p a vertex of every current simplex, and create some new simplices.
+  */
+  private void extend_simplices(int s, int s_bn, int[] ret, int[] ret_bn) {
+    int q, q_bn;
+    int ns, ns_bn;
+
+    if (sbt_visit[s_bn][s] == pnum) {
+      if (sbt_peak_vert[s_bn][s] != NOVAL) {
+        ret[0] = sbt_neigh_simp[s_bn][cdim-1][s];
+        ret_bn[0] = sbt_neigh_simp_bn[s_bn][cdim-1][s];
+      }
+      else {
+        ret[0] = s;
+        ret_bn[0] = s_bn;
+      }
+      return;
+    }
+    sbt_visit[s_bn][s] = pnum;
+    sbt_neigh_vert[s_bn][cdim-1][s] = p;
+    q = sbt_normal[s_bn][s];
+    q_bn = sbt_normal_bn[s_bn][s];
+    if (q != NOVAL && --bbt_ref_count[q_bn][q] == 0) {
+      bbt_next[q_bn][q] = basis_s_list;
+      bbt_next_bn[q_bn][q] = basis_s_list_bn;
+      bbt_ref_count[q_bn][q] = 0;
+      bbt_lscale[q_bn][q] = 0;
+      bbt_sqa[q_bn][q] = 0;
+      bbt_sqb[q_bn][q] = 0;
+      for (int j=0; j<2*rdim; j++) bbt_vecs[q_bn][j][q] = 0;
+      basis_s_list = q;
+      basis_s_list_bn = q_bn;
+    }
+    sbt_normal[s_bn][s] = NOVAL;
+
+    q = sbt_neigh_basis[s_bn][0][s];
+    q_bn = sbt_neigh_basis_bn[s_bn][0][s];
+    if (q != NOVAL && --bbt_ref_count[q_bn][q] == 0) {
+      bbt_next[q_bn][q] = basis_s_list;
+      bbt_ref_count[q_bn][q] = 0;
+      bbt_lscale[q_bn][q] = 0;
+      bbt_sqa[q_bn][q] = 0;
+      bbt_sqb[q_bn][q] = 0;
+      for (int j=0; j<2*rdim; j++) bbt_vecs[q_bn][j][q] = 0;
+
+      basis_s_list = q;
+      basis_s_list_bn = q_bn;
+    }
+    sbt_neigh_basis[s_bn][0][s] = NOVAL;
+
+    if (sbt_peak_vert[s_bn][s] == NOVAL) {
+      int[] esretp = new int[1];
+      int[] esretp_bn = new int[1];
+      extend_simplices(sbt_peak_simp[s_bn][s],
+                       sbt_peak_simp_bn[s_bn][s], esretp, esretp_bn);
+      sbt_neigh_simp[s_bn][cdim-1][s] = esretp[0];
+      sbt_neigh_simp_bn[s_bn][cdim-1][s] = esretp_bn[0];
+      ret[0] = s;
+      ret_bn[0] = s_bn;
+      return;
+    }
+    else {
+      ns = (simplex_list != NOVAL) ? simplex_list : new_block_simplex();
+      ns_bn = simplex_list_bn;
+      simplex_list = sbt_next[ns_bn][ns];
+      simplex_list_bn = sbt_next_bn[ns_bn][ns];
+      sbt_next[ns_bn][ns] = sbt_next[s_bn][s];
+      sbt_next_bn[ns_bn][ns] = sbt_next_bn[s_bn][s];
+      sbt_visit[ns_bn][ns] = sbt_visit[s_bn][s];
+      sbt_mark[ns_bn][ns] = sbt_mark[s_bn][s];
+      sbt_normal[ns_bn][ns] = sbt_normal[s_bn][s];
+      sbt_normal_bn[ns_bn][ns] = sbt_normal_bn[s_bn][s];
+      sbt_peak_vert[ns_bn][ns] = sbt_peak_vert[s_bn][s];
+      sbt_peak_simp[ns_bn][ns] = sbt_peak_simp[s_bn][s];
+      sbt_peak_simp_bn[ns_bn][ns] = sbt_peak_simp_bn[s_bn][s];
+      sbt_peak_basis[ns_bn][ns] = sbt_peak_basis[s_bn][s];
+      sbt_peak_basis_bn[ns_bn][ns] = sbt_peak_basis_bn[s_bn][s];
+      for (int j=0; j<rdim; j++) {
+        sbt_neigh_vert[ns_bn][j][ns] = sbt_neigh_vert[s_bn][j][s];
+        sbt_neigh_simp[ns_bn][j][ns] = sbt_neigh_simp[s_bn][j][s];
+        sbt_neigh_simp_bn[ns_bn][j][ns] = sbt_neigh_simp_bn[s_bn][j][s];
+        sbt_neigh_basis[ns_bn][j][ns] = sbt_neigh_basis[s_bn][j][s];
+        sbt_neigh_basis_bn[ns_bn][j][ns] = sbt_neigh_basis_bn[s_bn][j][s];
+      }
+
+      for (int j=0; j<cdim; j++) {
+        q = sbt_neigh_basis[s_bn][j][s];
+        q_bn = sbt_neigh_basis_bn[s_bn][j][s];
+        if (q != NOVAL) bbt_ref_count[q_bn][q]++;
+      }
+
+      sbt_neigh_simp[s_bn][cdim-1][s] = ns;
+      sbt_neigh_simp_bn[s_bn][cdim-1][s] = ns_bn;
+      sbt_peak_vert[ns_bn][ns] = NOVAL;
+      sbt_peak_simp[ns_bn][ns] = s;
+      sbt_peak_simp_bn[ns_bn][ns] = s_bn;
+      sbt_neigh_vert[ns_bn][cdim-1][ns] = sbt_peak_vert[s_bn][s];
+      sbt_neigh_simp[ns_bn][cdim-1][ns] = sbt_peak_simp[s_bn][s];
+      sbt_neigh_simp_bn[ns_bn][cdim-1][ns] = sbt_peak_simp_bn[s_bn][s];
+      sbt_neigh_basis[ns_bn][cdim-1][ns] = sbt_peak_basis[s_bn][s];
+      sbt_neigh_basis_bn[ns_bn][cdim-1][ns] = sbt_peak_basis_bn[s_bn][s];
+      q = sbt_peak_basis[s_bn][s];
+      q_bn = sbt_peak_basis_bn[s_bn][s];
+      if (q != NOVAL) bbt_ref_count[q_bn][q]++;
+      for (int i=0; i<cdim; i++) {
+        int[] esretp = new int[1];
+        int[] esretp_bn = new int[1];
+        extend_simplices(sbt_neigh_simp[ns_bn][i][ns],
+                         sbt_neigh_simp_bn[ns_bn][i][ns], esretp, esretp_bn);
+        sbt_neigh_simp[ns_bn][i][ns] = esretp[0];
+        sbt_neigh_simp_bn[ns_bn][i][ns] = esretp_bn[0];
+      }
+    }
+    ret[0] = ns;
+    ret_bn[0] = ns_bn;
+    return;
+  }
+
+  /**
+     return a simplex s that corresponds to a facet of the
+     current hull, and sees(p, s).
+  */
+  private void search(int root, int root_bn, int[] ret, int[] ret_bn) {
+    int s, s_bn;
+    int tms = 0;
+
+    st[tms] = sbt_peak_simp[root_bn][root];
+    st_bn[tms] = sbt_peak_simp_bn[root_bn][root];
+    tms++;
+    sbt_visit[root_bn][root] = pnum;
+    if (sees(p, root, root_bn) == 0) {
+      for (int i=0; i<cdim; i++) {
+        st[tms] = sbt_neigh_simp[root_bn][i][root];
+        st_bn[tms] = sbt_neigh_simp_bn[root_bn][i][root];
+        tms++;
+      }
+    }
+    while (tms != 0) {
+      if (tms > ss) {
+        // JAVA: efficiency issue: how much is this stack hammered?
+        ss += ss;
+        int[] newst = new int[ss+MAXDIM+1];
+        int[] newst_bn = new int[ss+MAXDIM+1];
+        System.arraycopy(st, 0, newst, 0, st.length);
+        System.arraycopy(st_bn, 0, newst_bn, 0, st_bn.length);
+        st = newst;
+        st_bn = newst_bn;
+      }
+      tms--;
+      s = st[tms];
+      s_bn = st_bn[tms];
+      if (sbt_visit[s_bn][s] == pnum) continue;
+      sbt_visit[s_bn][s] = pnum;
+      if (sees(p, s, s_bn) == 0) continue;
+      if (sbt_peak_vert[s_bn][s] == NOVAL) {
+        ret[0] = s;
+        ret_bn[0] = s_bn;
+        return;
+      }
+      for (int i=0; i<cdim; i++) {
+        st[tms] = sbt_neigh_simp[s_bn][i][s];
+        st_bn[tms] = sbt_neigh_simp_bn[s_bn][i][s];
+        tms++;
+      }
+    }
+    ret[0] = NOVAL;
+    return;
+  }
+
+
+  /**
+   * construct a Delaunay triangulation of the points in the
+   * samples array using Clarkson's algorithm
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @throws VisADException a VisAD error occurred
+   */
+  public DelaunayClarkson(float[][] samples) throws VisADException {
+    int s, s_bn, q, q_bn;
+    int root, root_bn;
+    int k=0;
+    int[] retp = new int[1];
+    int[] retp_bn = new int[1];
+    int[] ret2p = new int[1];
+    int[] ret2p_bn = new int[1];
+    int[] curt = new int[1];
+    int[] curt_bn = new int[1];
+    int s_num = 0;
+    int nrs;
+
+    // Start of main hull triangulation algorithm
+    dim = samples.length;
+    nrs = samples[0].length;
+    for (int i=1; i<dim; i++) nrs = Math.min(nrs, samples[i].length);
+
+    if (nrs <= dim) throw new SetException("DelaunayClarkson: "
+                                          +"not enough samples");
+    if (dim > MAXDIM) throw new SetException("DelaunayClarkson: "
+                               +"dimension bound MAXDIM exceeded");
+
+    // copy samples
+    site_blocks = new float[dim][nrs];
+    for (int j=0; j<dim; j++) {
+      System.arraycopy(samples[j], 0, site_blocks[j], 0, nrs);
+    }
+
+/* WLH 29 Jan 98 - scale samples values as discussed in Delaunay.factory
+    for (int j=0; j<dim; j++) {
+      for (int kk=0; kk<nrs; kk++) {
+        site_blocks[j][kk] = 100.0f * samples[j][kk];
+      }
+    }
+*/
+
+    exact_bits = (int) (DBL_MANT_DIG*Math.log(FLT_RADIX)/ln2);
+    b_err_min = DBL_EPSILON*MAXDIM*(1<<MAXDIM)*MAXDIM*3.01;
+    b_err_min_sq = b_err_min * b_err_min;
+
+    cdim = 0;
+    rdim = dim+1;
+    if (rdim > MAXDIM) throw new SetException(
+              "dimension bound MAXDIM exceeded; rdim="+rdim+"; dim="+dim);
+
+    pnb = basis_s_list != NOVAL ? basis_s_list : new_block_basis_s();
+    pnb_bn = basis_s_list_bn;
+    basis_s_list = bbt_next[pnb_bn][pnb];
+    basis_s_list_bn = bbt_next_bn[pnb_bn][pnb];
+    bbt_next[pnb_bn][pnb] = NOVAL;
+
+    ttbp = basis_s_list != NOVAL ? basis_s_list : new_block_basis_s();
+    ttbp_bn = basis_s_list_bn;
+    basis_s_list = bbt_next[ttbp_bn][ttbp];
+    basis_s_list_bn = bbt_next_bn[ttbp_bn][ttbp];
+    bbt_next[ttbp_bn][ttbp] = NOVAL;
+    bbt_ref_count[ttbp_bn][ttbp] = 1;
+    bbt_lscale[ttbp_bn][ttbp] = -1;
+    bbt_sqa[ttbp_bn][ttbp] = 0;
+    bbt_sqb[ttbp_bn][ttbp] = 0;
+    for (int j=0; j<2*rdim; j++) bbt_vecs[ttbp_bn][j][ttbp] = 0;
+
+    root = NOVAL;
+    p = INFINITY;
+    ib = (basis_s_list != NOVAL) ? basis_s_list : new_block_basis_s();
+    ib_bn = basis_s_list_bn;
+    basis_s_list = bbt_next[ib_bn][ib];
+    basis_s_list_bn = bbt_next_bn[ib_bn][ib];
+    bbt_ref_count[ib_bn][ib] = 1;
+    bbt_vecs[ib_bn][2*rdim-1][ib] = bbt_vecs[ib_bn][rdim-1][ib] = 1;
+    bbt_sqa[ib_bn][ib] = bbt_sqb[ib_bn][ib] = 1;
+
+    root = (simplex_list != NOVAL) ? simplex_list : new_block_simplex();
+    root_bn = simplex_list_bn;
+    simplex_list = sbt_next[root_bn][root];
+    simplex_list_bn = sbt_next_bn[root_bn][root];
+
+    ch_root = root;
+    ch_root_bn = root_bn;
+
+    s = (simplex_list != NOVAL) ? simplex_list : new_block_simplex();
+    s_bn = simplex_list_bn;
+    simplex_list = sbt_next[s_bn][s];
+    simplex_list_bn = sbt_next_bn[s_bn][s];
+    sbt_next[s_bn][s] = sbt_next[root_bn][root];
+    sbt_next_bn[s_bn][s] = sbt_next_bn[root_bn][root];
+    sbt_visit[s_bn][s] = sbt_visit[root_bn][root];
+    sbt_mark[s_bn][s] = sbt_mark[root_bn][root];
+    sbt_normal[s_bn][s] = sbt_normal[root_bn][root];
+    sbt_normal_bn[s_bn][s] = sbt_normal_bn[root_bn][root];
+    sbt_peak_vert[s_bn][s] = sbt_peak_vert[root_bn][root];
+    sbt_peak_simp[s_bn][s] = sbt_peak_simp[root_bn][root];
+    sbt_peak_simp_bn[s_bn][s] = sbt_peak_simp_bn[root_bn][root];
+    sbt_peak_basis[s_bn][s] = sbt_peak_basis[root_bn][root];
+    sbt_peak_basis_bn[s_bn][s] = sbt_peak_basis_bn[root_bn][root];
+    for (int i=0; i<rdim; i++) {
+      sbt_neigh_vert[s_bn][i][s] = sbt_neigh_vert[root_bn][i][root];
+      sbt_neigh_simp[s_bn][i][s] = sbt_neigh_simp[root_bn][i][root];
+      sbt_neigh_simp_bn[s_bn][i][s] = sbt_neigh_simp_bn[root_bn][i][root];
+      sbt_neigh_basis[s_bn][i][s] = sbt_neigh_basis[root_bn][i][root];
+      sbt_neigh_basis_bn[s_bn][i][s] = sbt_neigh_basis_bn[root_bn][i][root];
+    }
+    for (int i=0;i<cdim;i++) {
+      q = sbt_neigh_basis[root_bn][i][root];
+      q_bn = sbt_neigh_basis_bn[root_bn][i][root];
+      if (q != NOVAL) bbt_ref_count[q_bn][q]++;
+    }
+    sbt_peak_vert[root_bn][root] = p;
+    sbt_peak_simp[root_bn][root] = s;
+    sbt_peak_simp_bn[root_bn][root] = s_bn;
+    sbt_peak_simp[s_bn][s] = root;
+    sbt_peak_simp_bn[s_bn][s] = root_bn;
+    while (cdim < rdim) {
+      int oof = 0;
+
+      if (s_num == 0) p = 0;
+      else p++;
+      for (int i=0; i<dim; i++) {
+        site_blocks[i][p] = (float) Math.floor(site_blocks[i][p]+0.5);
+      }
+      s_num++;
+      pnum = (s_num*dim-1)/dim + 2;
+
+      cdim++;
+      sbt_neigh_vert[root_bn][cdim-1][root] = sbt_peak_vert[root_bn][root];
+
+      q = sbt_neigh_basis[root_bn][cdim-1][root];
+      q_bn = sbt_neigh_basis_bn[root_bn][cdim-1][root];
+      if (q != NOVAL && --bbt_ref_count[q_bn][q] == 0) {
+        bbt_next[q_bn][q] = basis_s_list;
+        bbt_next_bn[q_bn][q] = basis_s_list_bn;
+        bbt_ref_count[q_bn][q] = 0;
+        bbt_lscale[q_bn][q] = 0;
+        bbt_sqa[q_bn][q] = 0;
+        bbt_sqb[q_bn][q] = 0;
+        for (int l=0; l<2*rdim; l++) bbt_vecs[q_bn][l][q] = 0;
+
+        basis_s_list = q;
+        basis_s_list_bn = q_bn;
+      }
+      sbt_neigh_basis[root_bn][cdim-1][root] = NOVAL;
+
+      get_basis_sede(root, root_bn);
+      if (sbt_neigh_vert[root_bn][0][root] == INFINITY) oof = 1;
+      else {
+        curt[0] = pnb;
+        curt_bn[0] = pnb_bn;
+        reduce(curt, curt_bn, p, root, root_bn, cdim);
+        pnb = curt[0];
+        pnb_bn = curt_bn[0];
+        if (bbt_sqa[pnb_bn][pnb] != 0) oof = 1;
+        else cdim--;
+      }
+      if (oof != 0) extend_simplices(root, root_bn, voidp, voidp_bn);
+      else {
+        search(root, root_bn, retp, retp_bn);
+        make_facets(retp[0], retp_bn[0], ret2p, ret2p_bn);
+        connect(ret2p[0], ret2p_bn[0]);
+      }
+    }
+
+    for (int i=s_num; i<nrs; i++) {
+      p++;
+      s_num++;
+      for (int j=0; j<dim; j++) {
+        site_blocks[j][p] = (float) Math.floor(site_blocks[j][p]+0.5);
+      }
+      pnum = (s_num*dim-1)/dim + 2;
+      search(root, root_bn, retp, retp_bn);
+      make_facets(retp[0], retp_bn[0], ret2p, ret2p_bn);
+      connect(ret2p[0], ret2p_bn[0]);
+    }
+
+    a3size = rdim*nrs;
+    a3s = new int[rdim][a3size+MAXDIM+1];
+    visit_triang_gen(root, root_bn, 1, retp, retp_bn);
+    visit_triang_gen(retp[0], retp_bn[0], 0, voidp, voidp_bn);
+
+    // deallocate memory
+    /* NOTE: If this deallocation is not performed, more points
+       could theoretically be added to the triangulation later */
+    site_blocks = null;
+    st = null;
+    st_bn = null;
+    st2 = null;
+    st2_bn = null;
+    sbt_next = null;
+    sbt_next_bn = null;
+    sbt_visit = null;
+    sbt_mark = null;
+    sbt_normal = null;
+    sbt_normal_bn = null;
+    sbt_peak_vert = null;
+    sbt_peak_simp = null;
+    sbt_peak_simp_bn = null;
+    sbt_peak_basis = null;
+    sbt_peak_basis_bn = null;
+    sbt_neigh_vert = null;
+    sbt_neigh_simp = null;
+    sbt_neigh_simp_bn = null;
+    sbt_neigh_basis = null;
+    sbt_neigh_basis_bn = null;
+    bbt_next = null;
+    bbt_next_bn = null;
+    bbt_ref_count = null;
+    bbt_lscale = null;
+    bbt_sqa = null;
+    bbt_sqb = null;
+    bbt_vecs = null;
+
+/* ********** END OF CONVERTED HULL CODE ********** */
+/*          (but still inside constructor)          */
+
+    // compute number of triangles or tetrahedra
+    int[] nverts = new int[nrs];
+    for (int i=0; i<nrs; i++) nverts[i] = 0;
+    int ntris = 0;
+    boolean positive;
+    for (int i=0; i<nts; i++) {
+      positive = true;
+      for (int j=0; j<rdim; j++) {
+        if (a3s[j][i] < 0) positive = false;
+      }
+      if (positive) {
+        ntris++;
+        for (int j=0; j<rdim; j++) nverts[a3s[j][i]]++;
+      }
+    }
+    Vertices = new int[nrs][];
+    for (int i=0; i<nrs; i++) Vertices[i] = new int[nverts[i]];
+    for (int i=0; i<nrs; i++) nverts[i] = 0;
+
+    // build Tri & Vertices components
+    Tri = new int[ntris][rdim];
+    int a, b, c, d;
+    int itri = 0;
+    for (int i=0; i<nts; i++) {
+      positive = true;
+      for (int j=0; j<rdim; j++) {
+        if (a3s[j][i] < 0) positive = false;
+      }
+      if (positive) {
+        for (int j=0; j<rdim; j++) {
+          Vertices[a3s[j][i]][nverts[a3s[j][i]]++] = itri;
+          Tri[itri][j] = a3s[j][i];
+        }
+        itri++;
+      }
+    }
+
+    // Deallocate remaining helper information
+    a3s = null;
+
+    // call more generic method for constructing Walk and Edges arrays
+    finish_triang(samples);
+  }
+
+}
+
diff --git a/visad/DelaunayCustom.java b/visad/DelaunayCustom.java
new file mode 100644
index 0000000..0313fd6
--- /dev/null
+++ b/visad/DelaunayCustom.java
@@ -0,0 +1,1148 @@
+//
+// DelaunayCustom.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/*
+for polygon algorithms: http://www.exaflop.org/docs/cgafaq/cga2.html
+*/
+
+/**
+   DelaunayCustom is a set of constructors to create an instance
+   of Delaunay by passing in a pre-computed triangulation.
+   DelaunayCustom is useful for creating instances of Delaunay
+   that can be passed into IrregularSet.  If you want
+   to perform consistency checks on your triangulation, call
+   Delaunay.test() on your DelaunayCustom object after it is
+   constructed.<P>
+*/
+public class DelaunayCustom extends Delaunay {
+
+  /**
+   * construct a Delaunay from point locations and a list of
+   * triangles; call finish_triang() to fill in helper arrays
+   * (vertices, walk and edges); copy arguments
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param tri list of triangles - dimensioned int[ntris][dim + 1]
+   *            tris values are indices into second index of samples
+   * @throws VisADException a VisAD error occurred
+   */
+  public DelaunayCustom(float[][] samples, int[][] tri)
+                                           throws VisADException {
+    this(samples, tri, null, null, null, 0, true);
+  }
+
+  /**
+   * construct a Delaunay from point locations, a list of triangles,
+   * and helper arrays (vertices, walk and edges); copy arguments
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param tri list of triangles - dimensioned int[ntris][dim + 1]
+   *            tris values are indices into second index of samples
+   * @param vertices links from vertices to triangles/tetrahedra -
+   *                 dimensioned int[number_of_points][nverts[i]]
+   * @param walk links from triangles/tetrahedra to neighboring
+   *             triangles/tetrahedra - dimensioned int[ntris][dim + 1]
+   * @param edges links from tri/tetra edges to global edge numbers -
+   *              dimensioned int[ntris][3 * (dim - 1)]
+   * @param num_edges number of global edges
+   * @throws VisADException a VisAD error occurred
+   */
+  public DelaunayCustom(float[][] samples, int[][] tri, int[][] vertices,
+                        int[][] walk, int[][] edges, int num_edges)
+                        throws VisADException {
+    this(samples, tri, vertices, walk, edges, num_edges, true);
+  }
+
+  /**
+   * construct a Delaunay from point locations, a list of triangles,
+   * and helper arrays (vertices, walk and edges); copy arguments
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param tri list of triangles - dimensioned int[ntris][dim + 1]
+   *            tris values are indices into second index of samples
+   * @param vertices links from vertices to triangles/tetrahedra -
+   *                 dimensioned int[number_of_points][nverts[i]]
+   * @param walk links from triangles/tetrahedra to neighboring
+   *             triangles/tetrahedra - dimensioned int[ntris][dim + 1]
+   * @param edges links from tri/tetra edges to global edge numbers -
+   *              dimensioned int[ntris][3 * (dim - 1)]
+   * @param num_edges number of global edges
+   * @param copy flag indicating whether to copy arrays
+   * @throws VisADException a VisAD error occurred
+   */
+  public DelaunayCustom(float[][] samples, int[][] tri, int[][] vertices,
+                        int[][] walk, int[][] edges, int num_edges,
+                        boolean copy) throws VisADException {
+    if (tri == null) {
+      throw new VisADException("DelaunayCustom: "
+                              +"Tri array must be specified!");
+    }
+    if (samples == null && vertices == null) {
+      throw new VisADException("DelaunayCustom: Cannot construct "
+                              +"Vertices without samples!");
+    }
+    if (samples == null && walk == null) {
+      throw new VisADException("DelaunayCustom: Cannot construct "
+                              +"Walk without samples!");
+    }
+    if (samples == null && edges == null) {
+      throw new VisADException("DelaunayCustom: Cannot construct "
+                              +"Edges without samples!");
+    }
+
+    // copy data into Delaunay arrays
+    if (copy) {
+      Tri = new int[tri.length][];
+      for (int i=0; i<tri.length; i++) {
+        Tri[i] = new int[tri[i].length];
+        System.arraycopy(tri[i], 0, Tri[i], 0, tri[i].length);
+      }
+      if (vertices != null) {
+        Vertices = new int[vertices.length][];
+        for (int i=0; i<vertices.length; i++) {
+          Vertices[i] = new int[vertices[i].length];
+          System.arraycopy(vertices[i], 0,
+                           Vertices[i], 0, vertices[i].length);
+        }
+      }
+      if (walk != null) {
+        Walk = new int[walk.length][];
+        for (int i=0; i<walk.length; i++) {
+          Walk[i] = new int[walk[i].length];
+          System.arraycopy(walk[i], 0, Walk[i], 0, walk[i].length);
+        }
+      }
+      if (edges != null) {
+        Edges = new int[edges.length][];
+        for (int i=0; i<edges.length; i++) {
+          Edges[i] = new int[edges[i].length];
+          System.arraycopy(edges[i], 0, Edges[i], 0, edges[i].length);
+        }
+      }
+    }
+    else {
+      Tri = tri;
+      Vertices = vertices;
+      Walk = walk;
+      Edges = edges;
+    }
+    NumEdges = num_edges;
+
+    // call more generic method for constructing any remaining null arrays
+    if (samples != null) super.finish_triang(samples);
+  }
+
+  /**
+   * determine if a closed path self-intersects
+   * @param set Gridded2DSet with manifold dimension = 1
+   * @return true if closed path in set self-intersects
+   * @throws VisADException a VisAD error occurred
+   */
+  public static boolean checkSelfIntersection(Gridded2DSet set)
+         throws VisADException {
+    if (set == null) return false;
+    if (set.getManifoldDimension() != 1) {
+      throw new SetException("Gridded2DSet musy have manifold dimension = 1");
+    }
+    return checkSelfIntersection(set.getSamples());
+  }
+
+  /**
+   * determine if a closed path self-intersects
+   * @param samples locations of points on closed path - dimensioned
+   *                float[2][number_of_points]
+   * @return true if closed path in samples self-intersects
+   * @throws VisADException a VisAD error occurred
+   */
+  public static boolean checkSelfIntersection(float[][] samples)
+         throws VisADException {
+    if (samples == null) return false;
+    if (samples.length != 2 || samples[0].length != samples[1].length) {
+      throw new VisADException("samples argument bad dimensions");
+    }
+    int n = samples[0].length;
+
+        // build circular boundary list
+    int[] next = new int[n];
+    for (int i=0; i<n-1; i++) {
+      next[i] = i+1;
+    }
+    next[n-1] = 0;
+    for (int i=0; i<n; i++) {
+      for (int j=0; j<n; j++) {
+        if (i == j || i == next[j] || next[i] == j) continue;
+        float a0 = samples[0][i];
+        float a1 = samples[1][i];
+        float b0 = samples[0][next[i]];
+        float b1 = samples[1][next[i]];
+        float c0 = samples[0][j];
+        float c1 = samples[1][j];
+        float d0 = samples[0][next[j]];
+        float d1 = samples[1][next[j]];
+        float det = (b0 - a0) * (c1 - d1) - (b1 - a1) * (c0 - d0);
+        if (Math.abs(det) < 0.0000001) continue;
+        float x = ((b0 - a0) * (c1 - a1) - (b1 - a1) * (c0 - a0)) / det;
+        float y = ((c0 - a0) * (c1 - d1) - (c1 - a1) * (c0 - d0)) / det;
+        // if (0.0f <= x && x <= 1.0f && 0.0f <= y && y <= 1.0f) {
+        if (0.0f < x && x < 1.0f && 0.0f < y && y < 1.0f) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  // private final static float SELF = 0.999f;
+  private final static float SELF = 0.9999f;
+  private final static float PULL = 1.0f - SELF;
+  private final static float PULL2 = 0.5f * (1.0f - SELF);
+
+  /**
+   * determine if a closed path self-intersects, and remove
+   * consecutive identical points
+   * @param samples locations of points on closed path - dimensioned
+   *                float[2][number_of_points] - may be modified on
+   *                return
+   * @return true if closed path in samples self-intersects
+   * @throws VisADException a VisAD error occurred
+   */
+  public static boolean checkAndFixSelfIntersection(float[][] samples)
+         throws VisADException {
+    if (samples == null) return false;
+    if (samples.length != 2 || samples[0].length != samples[1].length) {
+      throw new VisADException("samples argument bad dimensions");
+    }
+    int n = samples[0].length;
+    boolean intersect = false;
+
+    // build circular boundary list
+    int[] next = new int[n];
+    for (int i=0; i<n-1; i++) {
+      next[i] = i+1;
+    }
+    next[n-1] = 0;
+
+    // remove consecutive identical points
+    float[][] new_samples = new float[2][n];
+    int k = 0;
+    for (int i=0; i<n; i++) {
+      if (samples[0][i] != samples[0][next[i]] ||
+          samples[1][i] != samples[1][next[i]]) {
+        new_samples[0][k] = samples[0][i];
+        new_samples[1][k] = samples[1][i];
+        k++;
+      }
+    }
+    if (k != n) {
+      samples[0] = new float[k];
+      samples[1] = new float[k];
+      if (k == 0) return false;
+      System.arraycopy(new_samples[0], 0, samples[0], 0, k);
+      System.arraycopy(new_samples[1], 0, samples[1], 0, k);
+      n = k;
+      next = new int[n];
+      for (int i=0; i<n-1; i++) {
+        next[i] = i+1;
+      }
+      next[n-1] = 0;
+    }
+
+    int[] prev = new int[n];
+    for (int i=1; i<n; i++) {
+      prev[i] = i-1;
+    }
+    prev[0] = n-1;
+
+    for (int i=0; i<n; i++) {
+      if (samples[0][i] == samples[0][next[i]] &&
+          samples[1][i] == samples[1][next[i]]) {
+        System.out.println("equal consecutive points");
+      }
+      for (int j=0; j<n; j++) {
+        if (i == j || i == next[j] || next[i] == j) continue;
+        float a0 = samples[0][i];
+        float a1 = samples[1][i];
+        float b0 = samples[0][next[i]];
+        float b1 = samples[1][next[i]];
+        float c0 = samples[0][j];
+        float c1 = samples[1][j];
+        float d0 = samples[0][next[j]];
+        float d1 = samples[1][next[j]];
+        float det = (b0 - a0) * (c1 - d1) - (b1 - a1) * (c0 - d0);
+        if (Math.abs(det) < 0.0000001) continue;
+        float x = ((b0 - a0) * (c1 - a1) - (b1 - a1) * (c0 - a0)) / det;
+        float y = ((c0 - a0) * (c1 - d1) - (c1 - a1) * (c0 - d0)) / det;
+        if (0.0f < x && x < 1.0f && 0.0f < y && y < 1.0f) {
+          intersect = true;
+        }
+        else if ((0.0f == x || x == 1.0f) && (0.0f == y || y == 1.0f)) {
+          // System.out.println("fix  x = " + x + " y = " + y);
+          if (x == 0.0f) {
+            samples[0][i] = SELF * a0 + PULL2 * (b0 + samples[0][prev[i]]);
+            samples[1][i] = SELF * a1 + PULL2 * (b1 + samples[1][prev[i]]);
+          }
+          else if (x == 1.0f) {
+            k = next[i];
+            samples[0][k] = SELF * b0 + PULL2 * (a0 + samples[0][next[k]]);
+            samples[1][k] = SELF * b1 + PULL2 * (a1 + samples[1][next[k]]);
+          }
+          if (y == 0.0f) {
+            samples[0][j] = SELF * c0 + PULL2 * (d0 + samples[0][prev[j]]);
+            samples[1][j] = SELF * c1 + PULL2 * (d1 + samples[1][prev[j]]);
+          }
+          else if (y == 1.0f) {
+            k = next[j];
+            samples[0][k] = SELF * d0 + PULL2 * (c0 + samples[0][next[k]]);
+            samples[1][k] = SELF * d1 + PULL2 * (c1 + samples[1][next[k]]);
+          }
+        }
+      }
+    }
+    return intersect;
+  }
+
+  /**
+   * compute the area inside a set of closed paths
+   * @param set UnionSet of Gridded2DSets with manifold dimension = 1,
+   *            interpreted as a set of closed paths
+   * @return sum of areas inside closed paths
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static float computeArea(UnionSet set) throws VisADException {
+    if (set == null) return 0.0f;
+    if (set.getManifoldDimension() != 1) {
+      throw new SetException("UnionSet must have manifold dimension = 1");
+    }
+    SampledSet[] sets = set.getSets();
+    if (sets == null) return 0.0f;
+    int n = sets.length;
+    if (n == 0) return 0.0f;
+    float[][][] ss = new float[n][][];
+    int k = 0;
+    for (int i=0; i<n; i++) {
+      if (!(sets[i] instanceof Gridded2DSet)) {
+        throw new SetException("UnionSet must contain only Gridded2DSets");
+      }
+      ss[k] = sets[i].getSamples();
+      if (ss[k] != null && ss[k].length == 2 && ss[k][0].length > 2) {
+        k++;
+      }
+    }
+    if (k == 0) return 0.0f;
+    float[][][] new_ss = new float[k][][];
+    System.arraycopy(ss, 0, new_ss, 0, k);
+    ss = new_ss;
+    float[][] samples = link(ss);
+    if (samples == null) return 0.0f;
+    return computeArea(samples);
+  }
+
+  /**
+   * compute the area inside a set of closed paths
+   * @param set Gridded2DSet with manifold dimension = 1, interpreted
+   *            as a closed path
+   * @return area inside closed path
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static float computeArea(Gridded2DSet set) throws VisADException {
+    if (set == null) return 0.0f;
+    if (set.getManifoldDimension() != 1) {
+      throw new SetException("Gridded2DSet musy have manifold dimension = 1");
+    }
+    return computeArea(set.getSamples());
+  }
+
+  /**
+   * compute the area inside a set of closed paths
+   * @param samples locations of points on closed path - dimensioned
+   *                float[2][number_of_points]
+   * @return area inside closed path
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static float computeArea(float[][] samples) throws VisADException {
+    if (samples == null) return 0.0f;
+    if (samples.length != 2 || samples[0].length != samples[1].length) {
+      throw new VisADException("samples argument bad dimensions");
+    }
+    if (samples[0].length < 3) return 0.0f;
+    if (checkSelfIntersection(samples)) {
+      throw new VisADException("path self intersects");
+    }
+    int n = samples[0].length;
+
+    // build circular boundary list
+    int[] next = new int[n];
+    for (int i=0; i<n-1; i++) {
+      next[i] = i+1;
+    }
+    next[n-1] = 0;
+
+    float area = 0.0f;
+    for (int i=0; i<n; i++) {
+      area +=
+        samples[0][i] * samples[1][next[i]] - samples[0][next[i]] * samples[1][i];
+    }
+    return (float) Math.abs(0.5 * area);
+  }
+
+  /**
+   * check that set describes the boundary of a simply connected plane
+   * region; return a decomposition of that region into triangles whose
+   * vertices are all boundary points from samples, as an Irregular2DSet
+   * @param set Gridded2DSet with manifold dimension = 1, interpreted
+   *            as a closed path boundary
+   * @return Irregular2DSet whose triangles form the interior of the
+   *         region enclosed by set
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static Irregular2DSet fill(Gridded2DSet set) throws VisADException {
+    return fillCheck(set, true);
+  }
+
+  /**
+   * check that set describes the boundary of a simply connected plane
+   * region; return a decomposition of that region into triangles whose
+   * vertices are all boundary points from samples, as an Irregular2DSet
+   * @param set Gridded2DSet with manifold dimension = 1, interpreted
+   *            as a closed path boundary
+   * @param check if true then throw a VisADException if path self-
+   *              intersects, else just return null
+   * @return Irregular2DSet whose triangles form the interior of the
+   *         region enclosed by set
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static Irregular2DSet fillCheck(Gridded2DSet set, boolean check)
+         throws VisADException {
+    if (set == null) return null;
+    if (set.getManifoldDimension() != 1) {
+      throw new SetException("Gridded2DSet musy have manifold dimension = 1");
+    }
+    float[][] samples = set.getSamples();
+    int[][] tris = fillCheck(samples, check);
+    if (tris == null || tris[0].length == 0) return null;
+    DelaunayCustom delaunay = new DelaunayCustom(samples, tris);
+    if (delaunay == null) return null;
+    return new Irregular2DSet(set.getType(), samples,
+                              null, null, null, delaunay);
+  }
+
+  /**
+   * check that samples describes the boundary of a simply connected
+   * plane region; return a decomposition of that region into triangles
+   * whose vertices are all boundary points from samples; the trick is
+   * that the region may not be convex, but the triangles must all lie
+   * inside the region
+   * @param samples locations of points on closed path - dimensioned
+   *                float[2][number_of_points]
+   * @return triangles that form the interior of the region enclosed by
+   *         samples - dimensioned int[ntris][dim + 1]
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static int[][] fill(float[][] samples) throws VisADException {
+    return fillCheck(samples, true);
+  }
+
+  /**
+   * check that samples describes the boundary of a simply connected
+   * plane region; return a decomposition of that region into triangles
+   * whose vertices are all boundary points from samples; the trick is
+   * that the region may not be convex, but the triangles must all lie
+   * inside the region
+   * @param samples locations of points on closed path - dimensioned
+   *                float[2][number_of_points]
+   * @param check if true then throw a VisADException if path self-
+   *              intersects, else just return null
+   * @return triangles that form the interior of the region enclosed by
+   *         samples - dimensioned int[ntris][dim + 1]
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static int[][] fillCheck(float[][] samples, boolean check)
+         throws VisADException {
+    if (samples == null) return null;
+    if (samples.length != 2 || samples[0].length != samples[1].length) {
+      throw new VisADException("samples argument bad dimensions");
+    }
+    if (samples[0].length < 3) return null;
+    if (checkAndFixSelfIntersection(samples)) {
+      if (check) throw new VisADException("path self intersects");
+    }
+    if (samples == null || samples[0].length < 3) {
+      System.out.println("fillCheck return null");
+      return null;
+    }
+    int n = samples[0].length;
+
+    // build circular boundary list
+    int[] next = new int[n];
+    for (int i=0; i<n-1; i++) {
+      next[i] = i+1;
+    }
+    next[n-1] = 0;
+    int[] prev = new int[n];
+    for (int i=1; i<n; i++) {
+      prev[i] = i-1;
+    }
+    prev[0] = n-1;
+
+    // compute area of region, to get orientation (positive or negative)
+    float area = 0.0f;
+    for (int i=0; i<n; i++) {
+      area +=
+        samples[0][i] * samples[1][next[i]] - samples[0][next[i]] * samples[1][i];
+    }
+    boolean pos = (area > 0.0);
+
+    int[][] tris = new int[n-2][]; // will be n-2 triangles
+    int i = 0; // current candidate for triangle
+    int t = 0; // next triangle, boundary length = n - t
+    int bad = 0;
+    boolean bug = false;
+    while ((n - t) > 2) {
+      int j = next[i];
+      int k = next[j];
+      float a0 = samples[0][j] - samples[0][i];
+      float a1 = samples[1][j] - samples[1][i];
+      float b0 = samples[0][k] - samples[0][j];
+      float b1 = samples[1][k] - samples[1][j];
+      boolean in = (((a0 * b1 - b0 * a1) > 0.0) == pos);
+// if (bug && !in) System.out.println("bug " + i + " tri orient in = " + in);
+      if (in && i != next[k]) {
+        float ik1 = samples[1][i] - samples[1][k];
+        float ik0 = samples[0][i] - samples[0][k];
+        float ik = samples[1][k] * ik0 - samples[0][k] * ik1;
+
+        float ji1 = samples[1][j] - samples[1][i];
+        float ji0 = samples[0][j] - samples[0][i];
+        float ji = samples[1][i] * ji0 - samples[0][i] * ji1;
+
+        float kj1 = samples[1][k] - samples[1][j];
+        float kj0 = samples[0][k] - samples[0][j];
+        float kj = samples[1][j] * kj0 - samples[0][j] * kj1;
+
+        int kn = next[k];
+        float kn0 = samples[0][kn];
+        float kn1 = samples[1][kn];
+        if (((ik + kn0 * ik1 - kn1 * ik0) > 0.0) != pos &&
+            ((kj + kn0 * kj1 - kn1 * kj0) > 0.0) != pos) {
+          in = false;
+        }
+        int ip = prev[i];
+        float ip0 = samples[0][ip];
+        float ip1 = samples[1][ip];
+        if (((ik + ip0 * ik1 - ip1 * ik0) > 0.0) != pos &&
+            ((ji + ip0 * ji1 - ip1 * ji0) > 0.0) != pos) {
+          in = false;
+        }
+// if (bug && !in) System.out.println("bug " + i + " adjacent in = " + in);
+        if (in) {
+          int p = next[k];
+          int q = next[p];
+          a0 = samples[0][i];
+          a1 = samples[1][i];
+          b0 = samples[0][k];
+          b1 = samples[1][k];
+          while (q != i) {
+            float c0 = samples[0][p];
+            float c1 = samples[1][p];
+            float d0 = samples[0][q];
+            float d1 = samples[1][q];
+            float det = (b0 - a0) * (c1 - d1) - (b1 - a1) * (c0 - d0);
+            p = q;
+            q = next[p];
+            // if (Math.abs(det) < 0.0000001) continue;
+            if (Math.abs(det) == 0.0) continue;
+            float x = ((b0 - a0) * (c1 - a1) - (b1 - a1) * (c0 - a0)) / det;
+            float y = ((c0 - a0) * (c1 - d1) - (c1 - a1) * (c0 - d0)) / det;
+            if (0.0f <= x && x <= 1.0f && 0.0f <= y && y <= 1.0f) {
+              in = false;
+              break;
+            }
+          } // end while (q != i)
+// if (bug && !in) System.out.println("bug " + i + " intersect in = " + in);
+        } // end if (in)
+      } // end if (in && i != next[k])
+      if (in) {
+        tris[t++] = new int[] {i, j, k}; // add triangle to tris
+        next[i] = k; // and remove j from boundary
+        prev[k] = i;
+        bad = 0;
+      }
+      else {
+        i = j; // try next point along boundary
+        if (bad++ > (n - t)) {
+          if (bug) {
+            // throw new VisADException("bad samples");
+            // System.out.println("bad samples t = " + t + " n = " + n);
+            if (t > 0) {
+              int[][] new_tris = new int[t][];
+              System.arraycopy(tris, 0, new_tris, 0, t);
+              return new_tris;
+            }
+            else {
+              return null;
+            }
+          }
+          else {
+            bug = true;
+            bad = 0;
+            pos = !pos; // WLH 16 May 2003
+          }
+        }
+      }
+    }
+    return tris;
+  }
+
+  /**
+   * check that set describes the boundary of a simply connected plane
+   * region; return a decomposition of that region into triangles whose
+   * vertices are all boundary points from samples, as an Irregular2DSet
+   * @param set UnionSet of Gridded2DSets with manifold dimension = 1,
+   *            interpreted as a set of closed paths
+   * @return Irregular2DSet whose triangles form the interior of the
+   *         region enclosed by set
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static Irregular2DSet fill(UnionSet set) throws VisADException {
+    return fillCheck(set, true);
+  }
+
+  /**
+   * check that set describes the boundary of a simply connected plane
+   * region; return a decomposition of that region into triangles whose
+   * vertices are all boundary points from samples, as an Irregular2DSet
+   * @param set UnionSet of Gridded2DSets with manifold dimension = 1,
+   *            interpreted as a set of closed paths
+   * @param check if true then throw a VisADException if path self-
+   *              intersects, else just return null
+   * @return Irregular2DSet whose triangles form the interior of the
+   *         region enclosed by set
+   * @throws VisADException path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static Irregular2DSet fillCheck(UnionSet set, boolean check)
+         throws VisADException {
+    if (set == null) return null;
+    if (set.getManifoldDimension() != 1) {
+      throw new SetException("UnionSet must have manifold dimension = 1");
+    }
+    SampledSet[] sets = set.getSets();
+    if (sets == null) return null;
+    int n = sets.length;
+    if (n == 0) return null;
+    float[][][] ss = new float[n][][];
+    int k = 0;
+    for (int i=0; i<n; i++) {
+      if (!(sets[i] instanceof Gridded2DSet)) {
+        throw new SetException("UnionSet must contain only Gridded2DSets");
+      }
+      ss[k] = sets[i].getSamples();
+      if (ss[k] != null && ss[k].length == 2 && ss[k][0].length > 2) {
+        k++;
+      }
+    }
+    if (k == 0) return null;
+    float[][][] new_ss = new float[k][][];
+    System.arraycopy(ss, 0, new_ss, 0, k);
+    ss = new_ss;
+    float[][] samples = link(ss);
+    int[][] tris = fillCheck(samples, check);
+    if (tris == null || tris[0].length == 0) return null;
+    DelaunayCustom delaunay = new DelaunayCustom(samples, tris);
+    if (delaunay == null) return null;
+    return new Irregular2DSet(set.getType(), samples,
+                              null, null, null, delaunay);
+  }
+
+  /**
+   * link multiple paths into a single path; this assumes that the
+   * paths in ss don't intersect each other but does test for
+   * self-intersection by each path
+   * @param ss a set of paths - dimensioned
+   *           float[number_of_paths][2][number_of_points_for_path]
+   * @return a path dimensioned float [2][number_of_points]
+   * @throws VisADException a path self intersects or a VisAD error
+   *                        occurred
+   */
+  public static float[][] link(float[][][] ss) throws VisADException {
+    if (ss == null || ss.length == 0) return null;
+    int nn = ss.length;
+    for (int ii=0; ii<nn; ii++) {
+      if (ss[ii].length != 2 || ss[ii][0].length != ss[ii][1].length) {
+        throw new VisADException("samples argument bad dimensions");
+      }
+      if (checkAndFixSelfIntersection(ss[ii])) {
+        throw new VisADException("path self intersects");
+      }
+    }
+    float[][][] new_ss = new float[nn][][];
+    int k = 0;
+    for (int ii=0; ii<nn; ii++) {
+      if (ss[ii][0].length > 2) {
+        new_ss[k] = ss[ii];
+        k++;
+      }
+    }
+    if (k == 0) return null;
+    if (k == 1) return new_ss[0];
+    ss = new float[k][][];
+    System.arraycopy(new_ss, 0, ss, 0, k);
+    nn = k;
+
+    // compute which paths are inside other paths
+    // this assumes that paths do not intersect
+    boolean[][] in = new boolean[nn][nn];
+    for (int ii=0; ii<nn; ii++) {
+      for (int jj=0; jj<nn; jj++) {
+        if (ii == jj) {
+          in[ii][jj] = false;
+        }
+        else {
+          in[ii][jj] = inside(ss[ii], ss[jj][0][0], ss[jj][1][0]);
+        }
+      }
+    }
+
+    // sort paths so no early path is inside a later path
+    for (int ii=0; ii<nn; ii++) {
+      boolean any_outer = false;
+      for (int jj=ii; jj<nn; jj++) {
+        boolean in_any = false;
+        for (int kk=ii; kk<nn; kk++) {
+          if (in[kk][jj]) {
+            in_any = true;
+            break;
+          }
+        }
+        if (!in_any) {
+          any_outer = true;
+          if (ii != jj) {
+            float[][] tss = ss[ii];
+            ss[ii] = ss[jj];
+            ss[jj] = tss;
+            boolean[] tin = in[ii];
+            in[ii] = in[jj];
+            in[jj] = tin;
+            for (int kk=0; kk<nn; kk++) {
+              boolean tb = in[kk][ii];
+              in[kk][ii] = in[kk][jj];
+              in[kk][jj] = tb;
+            }
+          }
+          break;
+        }
+      } // end for (int jj=ii; jj<nn; jj++)
+      if (!any_outer) {
+        // this should never happen and indicates paths must
+        // intersect, but don't throw an Exception
+        // just muddle through
+      }
+    } // end for (int ii=0; ii<nn; ii++)
+
+    // compute orientations of paths
+    boolean[] orient = new boolean[nn];
+    for (int ii=0; ii<nn; ii++) {
+      float area = 0.0f;
+      float[][] t = ss[ii];
+      int m = t[0].length;
+      for (int i=0; i<m-1; i++) {
+        area += t[0][i] * t[1][i+1] - t[0][i+1] * t[1][i];
+      }
+      area += t[0][m-1] * t[1][0] - t[0][0] * t[1][m-1];
+      orient[ii] = (area > 0.0);
+    }
+
+    // now merge paths
+    float[][] s = ss[0];
+    for (int ii=1; ii<nn; ii++) {
+      float[][] t = ss[ii];
+      if (t[0].length < 3) continue;
+      int n = s[0].length;
+      int m = t[0].length;
+
+      // find closest points between paths in s and t
+      float distance = Float.MAX_VALUE;
+      int near_i = -1;
+      int near_j = -1;
+      for (int i=0; i<n; i++) {
+        for (int j=0; j<m; j++) {
+          float d = (s[0][i] - t[0][j]) * (s[0][i] - t[0][j]) +
+                    (s[1][i] - t[1][j]) * (s[1][i] - t[1][j]);
+          if (d < distance) {
+            distance = d;
+            near_i = i;
+            near_j = j;
+          }
+        }
+      }
+      if (near_i < 0) continue;
+
+      // decide if need to flip order of traversing path in t
+      boolean inn = in[00][ii];
+      boolean flip = ((orient[0] == orient[ii]) == inn);
+
+      // link paths in s and t at nearest points
+      int new_n = n + m + 2;
+      float[][] new_s = new float[2][new_n];
+      k = 0;
+      System.arraycopy(s[0], 0, new_s[0], k, near_i + 1);
+      System.arraycopy(s[1], 0, new_s[1], k, near_i + 1);
+      k += near_i + 1;
+      int b1 = k - 1;
+      int a1 = k;
+      if (flip) {
+        for (int j=near_j; j>=0; j--) {
+          new_s[0][k] = t[0][j];
+          new_s[1][k] = t[1][j];
+          k++;
+        }
+        for (int j=m-1; j>=near_j; j--) {
+          new_s[0][k] = t[0][j];
+          new_s[1][k] = t[1][j];
+          k++;
+        }
+      }
+      else {
+        System.arraycopy(t[0], near_j, new_s[0], k, m - near_j);
+        System.arraycopy(t[1], near_j, new_s[1], k, m - near_j);
+        k += m - near_j;
+        System.arraycopy(t[0], 0, new_s[0], k, near_j + 1);
+        System.arraycopy(t[1], 0, new_s[1], k, near_j + 1);
+        k += near_j + 1;
+      }
+      int b2 = k - 1;
+      int a2 = k;
+      System.arraycopy(s[0], near_i, new_s[0], k, n - near_i);
+      System.arraycopy(s[1], near_i, new_s[1], k, n - near_i);
+      s[0] = new_s[0];
+      s[1] = new_s[1];
+
+      // nudge link points away from each other
+      int b1m = (b1 > 0) ? b1 - 1 : new_n - 1;
+      int a1p = (a1 < new_n - 1) ? a1 + 1 : 0;
+      int b2m = (b2 > 0) ? b2 - 1 : new_n - 1;
+      int a2p = (a2 < new_n - 1) ? a2 + 1 : 0;
+      new_s[0][b1] = SELF * new_s[0][b1] + PULL * new_s[0][b1m];
+      new_s[1][b1] = SELF * new_s[1][b1] + PULL * new_s[1][b1m];
+      new_s[0][a1] = SELF * new_s[0][a1] + PULL * new_s[0][a1p];
+      new_s[1][a1] = SELF * new_s[1][a1] + PULL * new_s[1][a1p];
+      new_s[0][b2] = SELF * new_s[0][b2] + PULL * new_s[0][b2m];
+      new_s[1][b2] = SELF * new_s[1][b2] + PULL * new_s[1][b2m];
+      new_s[0][a2] = SELF * new_s[0][a2] + PULL * new_s[0][a2p];
+      new_s[1][a2] = SELF * new_s[1][a2] + PULL * new_s[1][a2p];
+
+    } // end for (int ii=1; ii<nn; ii++)
+    return s;
+  }
+
+  /**
+   * determine if a point is inside a closed path
+   * @param s locations of points on closed path - dimensioned
+   *          float[2][number_of_points]
+   * @param x first coordinate of point
+   * @param y second coordinate of point
+   * @return true if point (x, y) is inside the path s
+   * @throws VisADException a VisAD error occurred
+   */
+  public static boolean inside(float[][] s, float x, float y)
+         throws VisADException {
+    if (s == null) return false;
+    if (s.length != 2 || s[0].length != s[1].length) {
+      throw new VisADException("samples argument bad dimensions");
+    }
+    int n = s[0].length;
+    double angle = 0.0;
+    for (int i=0; i<n; i++) {
+      int ip = (i < n - 1) ? i + 1 : 0;
+      double a = Math.atan2(s[0][i] - x, s[1][i] - y) -
+                 Math.atan2(s[0][ip] - x, s[1][ip] - y);
+      if (a < -Math.PI) a += Math.PI;
+      if (a > Math.PI) a -= Math.PI;
+      angle += a;
+    }
+    return (Math.abs(angle) > 0.5);
+  }
+
+  /**
+   * clip the topology (samples, tris) against the half-plane
+   * xc * x + yc * y <= v and return the clipped topology
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param tris list of triangles - dimensioned int[ntris][dim + 1]
+   *            tris values are indices into second index of samples
+   * @param xc x coefficient in half-plane inequality
+   * @param yc y coefficient in half-plane inequality
+   * @param v constant in half-plane inequality
+   * @param outs array dimensioned float[1][][] to take samples
+   *             of clipped topology - on output dimensioned
+   *             float[1][2][number_of_output_samples]
+   * @param outt array dimensioned int[1][][] to take tris
+   *             of clipped topology - on output dimensioned
+   *             int[1][number_of_output_triangles][3]
+   * @throws VisADException a VisAD error occurred
+   */
+  public static void clip(float[][] samples, int[][] tris,
+                          float xc, float yc, float v,
+                          float[][][] outs, int[][][] outt)
+         throws VisADException {
+    float[][] s = null;
+    int[][] t = null;
+    if (samples == null || tris == null) {
+      outs[0] = s;
+      outt[0] = t;
+      return;
+    }
+    int nsamples = samples[0].length;
+    int ns = 0; // count of new samples
+    s = new float[2][2 * nsamples]; // temp holder for new samples
+    int ntris = tris.length;
+    int nt = 0; // count of new tris
+    t = new int[2 * ntris][]; // temp holder for new tris
+    int[] smap = new int[nsamples]; // map from old to new samples
+    int[][] sother = new int[nsamples][6]; // other ends of clipped edges
+    int[][] snew = new int[nsamples][6]; // mid points of clipped edges
+    int[] minus1s = {-1, -1, -1, -1, -1, -1};
+    for (int i=0; i<nsamples; i++) {
+      System.arraycopy(minus1s, 0, sother[i], 0, 6);
+      if (xc * samples[0][i] + yc * samples[1][i] <= v) {
+        s[0][ns] = samples[0][i];
+        s[1][ns] = samples[1][i];
+        smap[i] = ns;
+        ns++;
+      }
+      else {
+        smap[i] = -1;
+      }
+    }
+    int nskept = ns;
+
+    int[] incounts = { 0, 1, 1, 2, 1, 2, 2, 3};
+    int[] firsts =   {-1, 0, 1, 0, 2, 0, 1, 0};
+    int[] seconds =  {-1, 1, 0, 1, 0, 2, 2, 1};
+    int[] thirds =   {-1, 2, 2, 2, 1, 1, 0, 2};
+    for (int i=0; i<ntris; i++) {
+      int a = tris[i][0];
+      int b = tris[i][1];
+      int c = tris[i][2];
+      int flags = (smap[a] < 0) ? 0 : 1;
+      flags += (smap[b] < 0) ? 0 : 2;
+      flags += (smap[c] < 0) ? 0 : 4;
+      switch (incounts[flags]) {
+        case 0:
+          break;
+        case 3:
+          t[nt] = new int[]
+            {smap[tris[i][0]], smap[tris[i][1]], smap[tris[i][2]]};
+          nt++;
+          break;
+        case 1:
+          int ao = tris[i][firsts[flags]];
+          int bo = tris[i][seconds[flags]];
+          int co = tris[i][thirds[flags]];
+          float av = v - (xc * samples[0][ao] + yc * samples[1][ao]);
+          float bv = v - (xc * samples[0][bo] + yc * samples[1][bo]);
+          float cv = v - (xc * samples[0][co] + yc * samples[1][co]);
+          float bw = av / (av - bv);
+          float bwm = 1.0f - bw;
+          float cw = av / (av - cv);
+          float cwm = 1.0f - cw;
+          float[] sb = {bwm * samples[0][ao] + bw * samples[0][bo],
+                        bwm * samples[1][ao] + bw * samples[1][bo]};
+          float[] sc = {cwm * samples[0][ao] + cw * samples[0][co],
+                        cwm * samples[1][ao] + cw * samples[1][co]};
+          int sbi = -1;
+          int sci = -1;
+          int jmax = -1;
+          for (int j=0; j<6; j++) {
+            if (sother[ao][j] < 0) break;
+            jmax = j;
+            if (sother[ao][j] == bo) {
+              sbi = snew[ao][j];
+            }
+            if (sother[ao][j] == co) {
+              sci = snew[ao][j];
+            }
+          }
+          if (sbi < 0) {
+            s[0][ns] = sb[0];
+            s[1][ns] = sb[1];
+            if (jmax < 5) {
+              jmax++;
+              sother[ao][jmax] = bo;
+              snew[ao][jmax] = ns;
+            }
+            sbi = ns;
+            ns++;
+          }
+          if (sci < 0) {
+            s[0][ns] = sc[0];
+            s[1][ns] = sc[1];
+            if (jmax < 5) {
+              jmax++;
+              sother[ao][jmax] = co;
+              snew[ao][jmax] = ns;
+            }
+            sci = ns;
+            ns++;
+          }
+          t[nt] = new int[] {smap[ao], sbi, sci};
+          nt++;
+          break;
+        case 2:
+          ao = tris[i][firsts[flags]];
+          bo = tris[i][seconds[flags]];
+          co = tris[i][thirds[flags]];
+          av = v - (xc * samples[0][ao] + yc * samples[1][ao]);
+          bv = v - (xc * samples[0][bo] + yc * samples[1][bo]);
+          cv = v - (xc * samples[0][co] + yc * samples[1][co]);
+          float aw = av / (av - cv);
+          float awm = 1.0f - aw;
+          bw = bv / (bv - cv);
+          bwm = 1.0f - bw;
+          float[] sa = {awm * samples[0][ao] + aw * samples[0][co],
+                        awm * samples[1][ao] + aw * samples[1][co]};
+          sb = new float[] {bwm * samples[0][bo] + bw * samples[0][co],
+                            bwm * samples[1][bo] + bw * samples[1][co]};
+          int sai = -1;
+          sbi = -1;
+          int jamax = -1;
+          for (int j=0; j<6; j++) {
+            if (sother[ao][j] < 0) break;
+            jamax = j;
+            if (sother[ao][j] == co) {
+              sai = snew[ao][j];
+            }
+          }
+          int jbmax = -1;
+          for (int j=0; j<6; j++) {
+            if (sother[bo][j] < 0) break;
+            jbmax = j;
+            if (sother[bo][j] == co) {
+              sbi = snew[bo][j];
+            }
+          }
+          if (sai < 0) {
+            s[0][ns] = sa[0];
+            s[1][ns] = sa[1];
+            if (jamax < 5) {
+              jamax++;
+              sother[ao][jamax] = co;
+              snew[ao][jamax] = ns;
+            }
+            sai = ns;
+            ns++;
+          }
+          if (sbi < 0) {
+            s[0][ns] = sb[0];
+            s[1][ns] = sb[1];
+            if (jbmax < 5) {
+              jbmax++;
+              sother[bo][jbmax] = co;
+              snew[bo][jbmax] = ns;
+            }
+            sbi = ns;
+            ns++;
+          }
+          t[nt] = new int[] {smap[ao], smap[bo], sai};
+          nt++;
+          t[nt] = new int[] {smap[bo], sai, sbi};
+          nt++;
+          break;
+      }
+    }
+
+    if (ns == 0 || nt == 0) {
+      outs[0] = null;
+      outt[0] = null;
+    }
+    else {
+      float[][] ss = new float[2][ns];
+      System.arraycopy(s[0], 0, ss[0], 0, ns);
+      System.arraycopy(s[1], 0, ss[1], 0, ns);
+      int[][] tt = new int[nt][];
+      System.arraycopy(t, 0, tt, 0, nt);
+      outs[0] = ss;
+      outt[0] = tt;
+    }
+/*
+if (ss != null) {
+  int nn = samples[0].length;
+  for (int ii=0; ii<nn; ii++) {
+    System.out.println("samples[][" + ii + "] = " + samples[0][ii] +
+                       " " + samples[1][ii]);
+  }
+  if (tris != null) {
+    nn = tris.length;
+    for (int ii=0; ii<nn; ii++) {
+       System.out.println("tris[" + ii + "] = " + tris[ii][0] +
+                          " " + tris[ii][1] + " " + tris[ii][2]);
+    }
+  }
+  System.out.println("nskept = " + nskept + " ns = " + ns + " nt = " + nt);
+  nn = ss[0].length;
+  for (int ii=0; ii<nn; ii++) {
+    System.out.println("ss[][" + ii + "] = " + ss[0][ii] +
+                       " " + ss[1][ii]);
+  }
+  if (tt != null) {
+    nn = tt.length;
+    for (int ii=0; ii<nn; ii++) {
+       System.out.println("tt[" + ii + "] = " + tt[ii][0] +
+                          " " + tt[ii][1] + " " + tt[ii][2]);
+    }
+  }
+  for (int ii=0; ii<smap.length; ii++) {
+    System.out.println("smap[" + ii + "] = " + smap[ii]);
+  }
+  for (int ii=0; ii<sother.length; ii++) {
+    System.out.println("sother[" + ii + "] = " + sother[ii][0] + " " +
+                       sother[ii][1] + " " + sother[ii][2] + " " +
+                       sother[ii][3] + " " + sother[ii][4] + " " +
+                       sother[ii][5]);
+    System.out.println("snew[" + ii + "] = " + snew[ii][0] + " " +
+                       snew[ii][1] + " " + snew[ii][2] + " " +
+                       snew[ii][3] + " " + snew[ii][4] + " " +
+                       snew[ii][5]);
+  }
+}
+else {
+  System.out.println("ss is null");
+}
+*/
+  }
+
+}
+
diff --git a/visad/DelaunayFast.java b/visad/DelaunayFast.java
new file mode 100644
index 0000000..952ec67
--- /dev/null
+++ b/visad/DelaunayFast.java
@@ -0,0 +1,670 @@
+//
+// DelaunayFast.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DelaunayFast is a method of finding an imperfect triangulation
+   or tetrahedralization of a set of samples of R^2 or R^3.
+   It provides a substantial speed increase over
+   the true Delaunay triangulation algorithms.<P>
+*/
+public class DelaunayFast extends Delaunay {
+
+  // <<< Modified quick sort routine >>>
+  private final void qsort(int[] array, float[][] samples,
+                           int sIndex, int lo, int hi) {
+    if (lo < hi) {
+      int pivot = (lo + hi)/2;
+      int swap = array[lo];
+      array[lo] = array[pivot];
+      array[pivot] = swap;
+
+      pivot = lo;
+      for (int i=lo+1; i<=hi; i++)
+        if (samples[sIndex][array[i]] < samples[sIndex][array[lo]]) {
+          swap = array[i];
+          array[i] = array[++pivot];
+          array[pivot] = swap;
+        }
+
+      swap = array[lo];
+      array[lo] = array[pivot];
+      array[pivot] = swap;
+
+      if (lo < pivot-1) qsort(array, samples, sIndex, lo, pivot-1);
+      if (pivot+1 < hi) qsort(array, samples, sIndex, pivot+1, hi);
+    }
+  }
+
+  /** Number of radians to rotate points before triangulating */
+  public static final double ROTATE = Math.PI/18;   // (10 degrees)
+
+  /**
+   * construct an approximate Delaunay triangulation of the points
+   * in the samples array using Curtis Rueden's algorithm
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @throws VisADException a VisAD error occurred
+   */
+  public DelaunayFast(float[][] samples) throws VisADException {
+    if (samples.length < 2 || samples.length > 3) {
+      throw new VisADException("DelaunayFast: dimension must be 2 or 3");
+    }
+    if (samples.length == 3) {
+      throw new UnimplementedException("DelaunayFast: "
+                                      +"only two dimensions for now");
+    }
+    int numpts = Math.min(samples[0].length, samples[1].length);
+    if (numpts < 3) {
+      throw new VisADException("DelaunayFast: triangulation is "
+                              +"futile with less than 3 samples");
+    }
+    float[][] samp = new float[2][numpts];
+    System.arraycopy(samples[0], 0, samp[0], 0, numpts);
+    System.arraycopy(samples[1], 0, samp[1], 0, numpts);
+    float[] samp0 = samp[0];
+    float[] samp1 = samp[1];
+
+    // rotate samples by ROTATE radians to avoid colinear axis-parallel points
+    double cosrot = Math.cos(ROTATE);
+    double sinrot = Math.sin(ROTATE);
+    for (int i=0; i<numpts; i++) {
+      double x = samp0[i];
+      double y = samp1[i];
+      samp0[i] = (float) (x*cosrot - y*sinrot);
+      samp1[i] = (float) (y*cosrot + x*sinrot);
+    }
+
+    // misc. variables
+    int ntris = 0;
+    int tsize = (int) (2f/3f*numpts) + 10;
+    int[][][] tris = new int[tsize][3][];
+    int tp = 0;
+    int[] nverts = new int[numpts];
+    for (int i=0; i<numpts; i++) nverts[i] = 0;
+
+    // set up the stack
+    int ssize = 20;                         // "stack size"
+    int[] ss = new int[ssize+2];            // "stack start"
+    int[] se = new int[ssize+2];            // "stack end"
+    boolean[] vh = new boolean[ssize+2];    // "vertical/horizontal"
+    boolean[] mp = new boolean[ssize+2];    // "merge points"
+    int sp = 0;                             // "stack pointer"
+    int hsize = 10;                         // "hull stack size"
+    int[][] hs = new int[hsize+2][];        // "hull stack"
+    int hsp = 0;                            // "hull stack pointer"
+
+    // set up initial conditions
+    int[] indices = new int[numpts];
+    for (int i=0; i<numpts; i++) indices[i] = i;
+
+    // add initial conditions to stack
+    sp++;
+    ss[0] = 0;
+    se[0] = numpts-1;
+    vh[0] = false;
+    mp[0] = false;
+
+    // stack loop variables
+    int css;
+    int cse;
+    boolean cvh;
+    boolean cmp;
+
+    // stack loop
+    while (sp != 0) {
+      if (hsp > hsize) {
+        // expand hull stack if necessary
+        hsize += hsize;
+        int newhs[][] = new int[hsize+2][];
+        System.arraycopy(hs, 0, newhs, 0, hs.length);
+        hs = newhs;
+      }
+      if (sp > ssize) {
+        // expand stack if necessary
+        ssize += ssize;
+        int[] newss = new int[ssize+2];
+        int[] newse = new int[ssize+2];
+        boolean[] newvh = new boolean[ssize+2];
+        boolean[] newmp = new boolean[ssize+2];
+        System.arraycopy(ss, 0, newss, 0, ss.length);
+        System.arraycopy(se, 0, newse, 0, se.length);
+        System.arraycopy(vh, 0, newvh, 0, vh.length);
+        System.arraycopy(mp, 0, newmp, 0, mp.length);
+        ss = newss;
+        se = newse;
+        vh = newvh;
+        mp = newmp;
+      }
+
+      // pop action from stack
+      sp--;
+      css = ss[sp];
+      cse = se[sp];
+      cvh = vh[sp];
+      cmp = mp[sp];
+
+      if (!cmp) {
+        // division step
+        if (cse - css >= 3) {
+          // sort step
+          qsort(indices, samp, cvh ? 0 : 1, css, cse);
+
+          // push merge action onto stack
+          ss[sp] = css;
+          se[sp] = cse;
+          vh[sp] = cvh;
+          mp[sp] = true;
+          sp++;
+
+          // divide, and push two halves onto stack
+          int mid = (css + cse)/2;
+          ss[sp] = css;
+          se[sp] = mid;
+          vh[sp] = !cvh;
+          mp[sp] = false;
+          sp++;
+          ss[sp] = mid+1;
+          se[sp] = cse;
+          vh[sp] = !cvh;
+          mp[sp] = false;
+          sp++;
+        }
+        else {
+          // connect step, also push hulls onto hull stack
+          int[] hull;
+          if (cse - css + 1 == 3) {
+            hull = new int[3];
+            hull[0] = indices[css];
+            hull[1] = indices[css+1];
+            hull[2] = indices[cse];
+            float a0x = samp0[hull[0]];
+            float a0y = samp1[hull[0]];
+            if ( (samp0[hull[1]]-a0x)*(samp1[hull[2]]-a0y)
+               - (samp1[hull[1]]-a0y)*(samp0[hull[2]]-a0x) > 0) {
+              // adjust step, hull must remain clockwise
+              hull[1] = indices[cse];
+              hull[2] = indices[css+1];
+            }
+            tris[tp][0] = new int[1];
+            tris[tp][1] = new int[1];
+            tris[tp][2] = new int[1];
+            tris[tp][0][0] = hull[0];
+            tris[tp][1][0] = hull[1];
+            tris[tp][2][0] = hull[2];
+            tp++;
+            ntris++;
+            nverts[indices[css]]++;
+            nverts[indices[cse]]++;
+            nverts[indices[css+1]]++;
+          }
+          else {
+            hull = new int[2];
+            hull[0] = indices[css];
+            hull[1] = indices[cse];
+          }
+          hs[hsp++] = hull;
+        }
+      }
+      else {
+        // merge step
+        int coord = cvh ? 1 : 0;
+
+        // pop hull arrays from stack
+        int[] hull1, hull2;
+        hsp -= 2;
+        hull2 = cvh ? hs[hsp+1] : hs[hsp];
+        hull1 = cvh ? hs[hsp] : hs[hsp+1];
+        hs[hsp+1] = null;
+        hs[hsp] = null;
+
+        // find upper and lower convex hull additions
+        int upp1 = 0;
+        int upp2 = 0;
+        int low1 = 0;
+        int low2 = 0;
+
+        // find initial upper and lower hull indices for later optimization
+        for (int i=1; i<hull1.length; i++) {
+          if (samp[coord][hull1[i]] > samp[coord][hull1[upp1]]) upp1 = i;
+          if (samp[coord][hull1[i]] < samp[coord][hull1[low1]]) low1 = i;
+        }
+        for (int i=1; i<hull2.length; i++) {
+          if (samp[coord][hull2[i]] > samp[coord][hull2[upp2]]) upp2 = i;
+          if (samp[coord][hull2[i]] < samp[coord][hull2[low2]]) low2 = i;
+        }
+
+        // hull sweep must be performed thrice to ensure correctness
+        for (int t=0; t<3; t++) {
+          // optimize upp1
+          int bob = (upp1+1)%hull1.length;
+          float ax = samp0[hull2[upp2]];
+          float ay = samp1[hull2[upp2]];
+          float bamx = samp0[hull1[bob]] - ax;
+          float bamy = samp1[hull1[bob]] - ay;
+          float camx = samp0[hull1[upp1]] - ax;
+          float camy = samp1[hull1[upp1]] - ay;
+          float u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                          : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+          float v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                          : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          boolean plus_dir = (u < v);
+          if (!plus_dir) {
+            bob = upp1;
+            u = 0;
+            v = 1;
+          }
+          while (u < v) {
+            upp1 = bob;
+            bob = plus_dir ? (upp1+1)%hull1.length
+                           : (upp1+hull1.length-1)%hull1.length;
+            bamx = samp0[hull1[bob]] - ax;
+            bamy = samp1[hull1[bob]] - ay;
+            camx = samp0[hull1[upp1]] - ax;
+            camy = samp1[hull1[upp1]] - ay;
+            u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                      : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+            v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                      : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          }
+
+          // optimize upp2
+          bob = (upp2+1)%hull2.length;
+          ax = samp0[hull1[upp1]];
+          ay = samp1[hull1[upp1]];
+          bamx = samp0[hull2[bob]] - ax;
+          bamy = samp1[hull2[bob]] - ay;
+          camx = samp0[hull2[upp2]] - ax;
+          camy = samp1[hull2[upp2]] - ay;
+          u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                    : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+          v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                    : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          plus_dir = (u < v);
+          if (!plus_dir) {
+            bob = upp2;
+            u = 0;
+            v = 1;
+          }
+          while (u < v) {
+            upp2 = bob;
+            bob = plus_dir ? (upp2+1)%hull2.length
+                           : (upp2+hull2.length-1)%hull2.length;
+            bamx = samp0[hull2[bob]] - ax;
+            bamy = samp1[hull2[bob]] - ay;
+            camx = samp0[hull2[upp2]] - ax;
+            camy = samp1[hull2[upp2]] - ay;
+            u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                      : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+            v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                      : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          }
+
+          // optimize low1
+          bob = (low1+1)%hull1.length;
+          ax = samp0[hull2[low2]];
+          ay = samp1[hull2[low2]];
+          bamx = samp0[hull1[bob]] - ax;
+          bamy = samp1[hull1[bob]] - ay;
+          camx = samp0[hull1[low1]] - ax;
+          camy = samp1[hull1[low1]] - ay;
+          u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                    : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+          v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                    : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          plus_dir = (u > v);
+          if (!plus_dir) {
+            bob = low1;
+            u = 1;
+            v = 0;
+          }
+          while (u > v) {
+            low1 = bob;
+            bob = plus_dir ? (low1+1)%hull1.length
+                           : (low1+hull1.length-1)%hull1.length;
+            bamx = samp0[hull1[bob]] - ax;
+            bamy = samp1[hull1[bob]] - ay;
+            camx = samp0[hull1[low1]] - ax;
+            camy = samp1[hull1[low1]] - ay;
+            u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                      : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+            v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                      : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          }
+
+          // optimize low2
+          bob = (low2+1)%hull2.length;
+          ax = samp0[hull1[low1]];
+          ay = samp1[hull1[low1]];
+          bamx = samp0[hull2[bob]] - ax;
+          bamy = samp1[hull2[bob]] - ay;
+          camx = samp0[hull2[low2]] - ax;
+          camy = samp1[hull2[low2]] - ay;
+          u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                    : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+          v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                    : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          plus_dir = (u > v);
+          if (!plus_dir) {
+            bob = low2;
+            u = 1;
+            v = 0;
+          }
+          while (u > v) {
+            low2 = bob;
+            bob = plus_dir ? (low2+1)%hull2.length
+                           : (low2+hull2.length-1)%hull2.length;
+            bamx = samp0[hull2[bob]] - ax;
+            bamy = samp1[hull2[bob]] - ay;
+            camx = samp0[hull2[low2]] - ax;
+            camy = samp1[hull2[low2]] - ay;
+            u = (cvh) ? (float) (bamy/Math.sqrt(bamx*bamx + bamy*bamy))
+                      : (float) (bamx/Math.sqrt(bamx*bamx + bamy*bamy));
+            v = (cvh) ? (float) (camy/Math.sqrt(camx*camx + camy*camy))
+                      : (float) (camx/Math.sqrt(camx*camx + camy*camy));
+          }
+        }
+
+        // calculate number of points in inner hull
+        int nih1, nih2;
+        int noh1, noh2;
+        int h1ups, h2ups;
+        if (low1 == upp1) {
+          nih1 = hull1.length;
+          noh1 = 1;
+          h1ups = 0;
+        }
+        else {
+          nih1 = low1-upp1+1;
+          if (nih1 <= 0) nih1 += hull1.length;
+          noh1 = hull1.length-nih1+2;
+          h1ups = 1;
+        }
+        if (low2 == upp2) {
+          nih2 = hull2.length;
+          noh2 = 1;
+          h2ups = 0;
+        }
+        else {
+          nih2 = upp2-low2+1;
+          if (nih2 <= 0) nih2 += hull2.length;
+          noh2 = hull2.length-nih2+2;
+          h2ups = 1;
+        }
+
+        // copy hull1 & hull2 info into merged hull array
+        int[] hull = new int[noh1+noh2];
+        int hullnum = 0;
+        int spot;
+
+        // go clockwise until upp1 is reached
+        for (spot=low1; spot!=upp1; hullnum++, spot=(spot+1)%hull1.length) {
+          hull[hullnum] = hull1[spot];
+        }
+
+        // append upp1
+        hull[hullnum++] = hull1[upp1];
+
+        // go clockwise until low2 is reached
+        for (spot=upp2; spot!=low2; hullnum++, spot=(spot+1)%hull2.length) {
+          hull[hullnum] = hull2[spot];
+        }
+
+        // append low2
+        hull[hullnum++] = hull2[low2];
+
+        // now push the new, completed hull onto the hull stack
+        hs[hsp++] = hull;
+
+        // stitch a connection between the two triangulations
+        int base1 = low1;
+        int base2 = low2;
+        int oneUp1 = (base1+hull1.length-1)%hull1.length;
+        int oneUp2 = (base2+1)%hull2.length;
+
+        // when both sides reach the top the merge is complete
+        int ntd = (noh1 == 1 || noh2 == 1) ? nih1+nih2-1 : nih1+nih2-2;
+        tris[tp][0] = new int[ntd];
+        tris[tp][1] = new int[ntd];
+        tris[tp][2] = new int[ntd];
+        for (int t=0; t<ntd; t++) {
+
+          // special case if side 1 has reached the top
+          if (h1ups == nih1) {
+            oneUp2 = (base2+1)%hull2.length;
+            tris[tp][0][t] = hull2[base2];
+            tris[tp][1][t] = hull1[base1];
+            tris[tp][2][t] = hull2[oneUp2];
+            ntris++;
+            nverts[hull1[base1]]++;
+            nverts[hull2[base2]]++;
+            nverts[hull2[oneUp2]]++;
+            base2 = oneUp2;
+            h2ups++;
+          }
+
+          // special case if side 2 has reached the top
+          else if (h2ups == nih2) {
+            oneUp1 = (base1+hull1.length-1)%hull1.length;
+            tris[tp][0][t] = hull2[base2];
+            tris[tp][1][t] = hull1[base1];
+            tris[tp][2][t] = hull1[oneUp1];
+            ntris++;
+            nverts[hull1[base1]]++;
+            nverts[hull2[base2]]++;
+            nverts[hull1[oneUp1]]++;
+            base1 = oneUp1;
+            h1ups++;
+          }
+
+          // neither side has reached the top yet
+          else {
+            boolean d;
+            int hb1 = hull1[base1];
+            int ho1 = hull1[oneUp1];
+            int hb2 = hull2[base2];
+            int ho2 = hull2[oneUp2];
+            float ax = samp0[ho2];
+            float ay = samp1[ho2];
+            float bx = samp0[hb2];
+            float by = samp1[hb2];
+            float cx = samp0[ho1];
+            float cy = samp1[ho1];
+            float dx = samp0[hb1];
+            float dy = samp1[hb1];
+            float abx = ax - bx;
+            float aby = ay - by;
+            float acx = ax - cx;
+            float acy = ay - cy;
+            float dbx = dx - bx;
+            float dby = dy - by;
+            float dcx = dx - cx;
+            float dcy = dy - cy;
+            float Q = abx*acx + aby*acy;
+            float R = dbx*abx + dby*aby;
+            float S = acx*dcx + acy*dcy;
+            float T = dbx*dcx + dby*dcy;
+            boolean QD = abx*acy - aby*acx >= 0;
+            boolean RD = dbx*aby - dby*abx >= 0;
+            boolean SD = acx*dcy - acy*dcx >= 0;
+            boolean TD = dcx*dby - dcy*dbx >= 0;
+            boolean sig = (QD ? 1 : 0) + (RD ? 1 : 0) + (SD ? 1 : 0) + (TD ? 1 : 0) < 2;
+            if (QD == sig) d = true;
+            else if (RD == sig) d = false;
+            else if (SD == sig) d = false;
+            else if (TD == sig) d = true;
+            else if (Q < 0 && T < 0 || R > 0 && S > 0) d = true;
+            else if (R < 0 && S < 0 || Q > 0 && T > 0) d = false;
+            else if ((Q < 0 ? Q : T) < (R < 0 ? R : S)) d = true;
+            else d = false;
+            if (d) {
+              tris[tp][0][t] = hull2[base2];
+              tris[tp][1][t] = hull1[base1];
+              tris[tp][2][t] = hull2[oneUp2];
+              ntris++;
+              nverts[hull1[base1]]++;
+              nverts[hull2[base2]]++;
+              nverts[hull2[oneUp2]]++;
+
+              // use diagonal (base1, oneUp2) as new base
+              base2 = oneUp2;
+              h2ups++;
+              oneUp2 = (base2+1)%hull2.length;
+            }
+            else {
+              tris[tp][0][t] = hull2[base2];
+              tris[tp][1][t] = hull1[base1];
+              tris[tp][2][t] = hull1[oneUp1];
+              ntris++;
+              nverts[hull1[base1]]++;
+              nverts[hull2[base2]]++;
+              nverts[hull1[oneUp1]]++;
+
+              // use diagonal (base2, oneUp1) as new base
+              base1 = oneUp1;
+              h1ups++;
+              oneUp1 = (base1+hull1.length-1)%hull1.length;
+            }
+          }
+        }
+        tp++;
+      }
+    }
+
+    // build Tri component
+    Tri = new int[ntris][3];
+    int tr = 0;
+    for (int i=0; i<tp; i++) {
+      for (int j=0; j<tris[i][0].length; j++) {
+        Tri[tr][0] = tris[i][0][j];
+        Tri[tr][1] = tris[i][1][j];
+        Tri[tr][2] = tris[i][2][j];
+        tr++;
+      }
+    }
+
+    // build Vertices component
+    Vertices = new int[numpts][];
+    for (int i=0; i<numpts; i++) {
+      Vertices[i] = new int[nverts[i]];
+      nverts[i] = 0;
+    }
+    int a, b, c;
+    for (int i=0; i<ntris; i++) {
+      a = Tri[i][0];
+      b = Tri[i][1];
+      c = Tri[i][2];
+      Vertices[a][nverts[a]++] = i;
+      Vertices[b][nverts[b]++] = i;
+      Vertices[c][nverts[c]++] = i;
+    }
+
+    // call more generic method for constructing Walk and Edges arrays
+    finish_triang(samples);
+  }
+
+  /**
+   * Illustrates the speed increase over other Delaunay algorithms
+   * @param argv command line arguments
+   * @throws VisADException a VisAD error occurred
+   */
+  public static void main(String[] argv) throws VisADException {
+    boolean problem = false;
+    int points = 0;
+    if (argv.length < 1) problem = true;
+    else {
+      try {
+        points = Integer.parseInt(argv[0]);
+        if (points < 3) problem = true;
+      }
+      catch (NumberFormatException exc) {
+        problem = true;
+      }
+    }
+    if (problem) {
+      System.out.println("Usage:\n" +
+                         "   java visad.DelaunayFast points\n" +
+                         "points = the number of points to triangulate.\n");
+      System.exit(1);
+    }
+    System.out.println("Generating " + points + " random points...");
+    float[][] samples = new float[2][points];
+    float[] samp0 = samples[0];
+    float[] samp1 = samples[1];
+    for (int i=0; i<points; i++) {
+      samp0[i] = (float) (500 * Math.random());
+      samp1[i] = (float) (500 * Math.random());
+    }
+    System.out.println("\nTriangulating points with Clarkson algorithm...");
+    long start1 = System.currentTimeMillis();
+    DelaunayClarkson dc = new DelaunayClarkson(samples);
+    long end1 = System.currentTimeMillis();
+    float time1 = (end1 - start1) / 1000f;
+    System.out.println("Triangulation took " + time1 + " seconds.");
+    System.out.println("\nTriangulating points with Watson algorithm...");
+    long start2 = System.currentTimeMillis();
+    DelaunayWatson dw = new DelaunayWatson(samples);
+    long end2 = System.currentTimeMillis();
+    float time2 = (end2 - start2) / 1000f;
+    System.out.println("Triangulation took " + time2 + " seconds.");
+    System.out.println("\nTriangulating points with Fast algorithm...");
+    long start3 = System.currentTimeMillis();
+    DelaunayFast df = new DelaunayFast(samples);
+    long end3 = System.currentTimeMillis();
+    float time3 = (end3 - start3) / 1000f;
+    System.out.println("Triangulation took " + time3 + " seconds.");
+    float ratio1 = (time1 / time3);
+    System.out.println("\nAt " + points + " points:");
+    System.out.println("  Fast is " + ratio1 + " times faster than Clarkson.");
+    float ratio2 = (time2 / time3);
+    System.out.println("  Fast is " + ratio2 + " times faster than Watson.");
+  }
+
+/* Here's the output of the main method:
+
+C:\java\visad>java visad.DelaunayFast 10000
+Generating 10000 random points...
+
+Triangulating points with Clarkson algorithm...
+Triangulation took 11.406 seconds.
+
+Triangulating points with Watson algorithm...
+Triangulation took 63.063 seconds.
+
+Triangulating points with Fast algorithm...
+Triangulation took 1.234 seconds.
+
+At 10000 points:
+  Fast is 9.243113 times faster than Clarkson.
+  Fast is 51.104538 times faster than Watson.
+
+C:\java\visad>
+
+*/
+
+}
+
diff --git a/visad/DelaunayOverlap.java b/visad/DelaunayOverlap.java
new file mode 100644
index 0000000..2ab2284
--- /dev/null
+++ b/visad/DelaunayOverlap.java
@@ -0,0 +1,2199 @@
+//
+// DelaunayOverlap.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+// AWT is used in main method, for testing
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+   DelaunayOverlap quickly constructs a triangulation of a series
+   of partially overlapping two-dimensional gridded sets.<P>
+
+   Grids should be aligned so that they overlap in a roughly
+   vertical column (i.e., as samples[*][1] increases or decreases,
+   the grid number increases or decreases respectively).
+   DelaunayOverlap does not handle grids which overlap across a
+   horizontal row.<P>
+*/
+public class DelaunayOverlap extends Delaunay {
+
+  /* This method could result in overlapping triangles in the
+     triangulation in the intersecting section of two grids.
+     For "bow-tie" shaped data, this problem is unlikely to
+     occur, but for data with the left and right bowed inwards,
+     overlapping triangles are more likely.  The latter type of
+     data is also more likely to have a greater number of
+     Type 3 lost points. */
+
+  // topology signature of grid boxes
+  private boolean sig;
+
+  /**
+   * Construct a Delaunay triangulation of the points in the
+   * samples array, which are a sequence of 2-D grids of size
+   * lenx * leny, and which may overlap with each other. This
+   * situation can arise when each grid is a scan from a polar
+   * orbiting satellite and the scans are perpendicular to the
+   * direction of travel of the satellite. The scan grids widen
+   * away from the satellite sub-point and can overlap.
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @param lenx x size of scan grid
+   * @param leny y size of scan grid
+   * @throws VisADException a VisAD error occurred
+   */
+  public DelaunayOverlap(float[][] samples, int lenx, int leny)
+                                            throws VisADException {
+    if (samples.length < 2) {
+      throw new SetException("DelaunayOverlap: bad dimension");
+    }
+    if (lenx < 2 || leny < 2) {
+      throw new SetException("DelaunayOverlap: must have at least "
+                            +"two points per dimension");
+    }
+    if (samples[0].length < lenx*leny) {
+      throw new SetException("DelaunayOverlap: not enough samples");
+    }
+    int lenp = lenx*leny;
+    int leng = (lenx-1)*(leny-1);
+    int numgrids = samples[0].length/lenp;
+    if (numgrids < 2) {
+      throw new SetException("DelaunayOverlap: not enough grids "
+                            +"(try Gridded2DSet instead)");
+    }
+
+    // samples consistency check for each grid
+    sig = ( (samples[0][1]-samples[0][0])
+           *(samples[1][lenx+1]-samples[1][1])
+          - (samples[1][1]-samples[1][0])
+           *(samples[0][lenx+1]-samples[0][1]) > 0);
+    for (int curgrid=0; curgrid<numgrids; curgrid++) {
+      for (int gy=0; gy<leny-1; gy++) {
+        for (int gx=0; gx<lenx-1; gx++) {
+          float v00x, v00y, v10x, v10y, v01x, v01y, v11x, v11y;
+          int q = curgrid*lenp+gy*lenx+gx;
+          v00x = samples[0][q];
+          v00y = samples[1][q];
+          v10x = samples[0][q+1];
+          v10y = samples[1][q+1];
+          v01x = samples[0][q+lenx];
+          v01y = samples[1][q+lenx];
+          v11x = samples[0][q+lenx+1];
+          v11y = samples[1][q+lenx+1];
+          if (  ( (v10x-v00x)*(v11y-v10y)
+                - (v10y-v00y)*(v11x-v10x) > 0 != sig)
+             || ( (v11x-v10x)*(v01y-v11y)
+                - (v11y-v10y)*(v01x-v11x) > 0 != sig)
+             || ( (v01x-v11x)*(v00y-v01y)
+                - (v01y-v11y)*(v00x-v01x) > 0 != sig)
+             || ( (v00x-v01x)*(v10y-v00y)
+                - (v00y-v01y)*(v10x-v00x) > 0 != sig)  ) {
+            throw new SetException("DelaunayOverlap: samples from grid "
+                                   +curgrid+" do not form a valid grid "
+                                   +"("+gx+","+gy+")");
+          }
+        }
+      }
+    }
+
+    // arrays for keeping track of which points fall in which grid boxes
+    int ngrid = (numgrids-1)*(lenx-1)*(leny-1);
+    int[][] grid = new int[ngrid][5];
+    int[] gsize = new int[ngrid];
+    int[] glen = new int[ngrid];
+    for (int g=0; g<numgrids-1; g++) {
+      for (int gy=0; gy<leny-1; gy++) {
+        for (int gx=0; gx<lenx-1; gx++) {
+          int qg = leng*g + (lenx-1)*gy + gx;
+          gsize[qg] = 4;
+          glen[qg] = 0;
+        }
+      }
+    }
+
+    // Broken grid boxes (severed from a previous grid by a line)
+    //   broken[*][0] = left edge break point
+    //   broken[*][X] = break point of X'th grid box column
+    //   broken[*][lenx] = right edge break point
+    int[][] broken = new int[numgrids][lenx+1];
+    for (int g=0; g<numgrids; g++) {
+      for (int pix=0; pix<lenx+1; pix++) {
+        broken[g][pix] = -1;
+      }
+    }
+
+    // left and right outer hulls
+    int[] leftedge = new int[numgrids*leny];
+    int ledges = 0;
+    int[] rightedge = new int[numgrids*leny];
+    int redges = 0;
+
+    // the trinum arrays keep track of the triangle
+    // numbers that border the edge arrays;
+    // the triedge arrays keep track of the edge numbers
+    // of the triangles that border the edge arrays
+    int[] ltrinum = new int[numgrids*leny];
+    int[] ltriedge = new int[numgrids*leny];
+    int[] rtrinum = new int[numgrids*leny];
+    int[] rtriedge = new int[numgrids*leny];
+
+    // arrays for saving previous values of edge information
+    int[] old_leftedge;
+    int[] old_rightedge;
+    int old_ledges;
+    int old_redges;
+    int[] old_ltrinum;
+    int[] old_ltriedge;
+    int[] old_rtrinum;
+    int[] old_rtriedge;
+
+    // boolean matrix of whether each point fell into a grid
+    boolean[] ptfell = new boolean[numgrids*lenx*leny];
+    for (int g=0; g<numgrids; g++) {
+      for (int line=0; line<leny; line++) {
+        for (int pix=0; pix<lenx; pix++) {
+          ptfell[lenp*g+lenx*line+pix] = false;
+        }
+      }
+    }
+
+    // walk construction helper arrays, for use during box triangulation
+    int[] bottom = new int[lenx-1];
+    for (int i=0; i<lenx-1; i++) bottom[i] = -1;
+    // array for saving previous values of bottom
+    int[] old_bottom;
+
+    // more walk helper arrays, for use during the zipping algorithm.
+    //   tri[*][0] = right line of grid box
+    //   tri[*][1] = bottom line of grid box
+    //   tri[*][2] = left line of grid box
+    //   tri[*][istwo[boxnum] ? 2 : 0] = top line of grid box
+    //   tlr[*][0] = triangle containing top line
+    //   tlr[*][1] = triangle containing left line
+    //   tlr[*][2] = triangle containing right line
+    int[][] tlr = new int[leng][3];
+    boolean[] istwo = new boolean[leng];
+
+    // expandable triangle storage arrays
+    int[][] tri;
+    int[][] walk;
+    int trisize = 0;
+    int trilen = 0;
+
+    int curgrid;
+    int prevgrid = 0;
+
+
+    // ******************** MAIN ALGORITHM ******************** //
+
+    // *** PHASE 1: *** Pre-triangulation preparation
+
+    // *** PHASE 1a: *** Figure out where all the points lie
+    //                   (i.e., locate containing grid boxes).
+
+    // array of pointers to last correct grid box of each grid
+    int[] foundx = new int[numgrids];
+    int[] foundy = new int[numgrids];
+
+    // center grid box of a grid
+    int cenx = lenx/2 - 1;
+    int ceny = leny/2 - 1;
+
+    // current point being examined in 1-D rasterization
+    int curpt;
+
+    // examine all grids except the first one
+    for (curgrid=1; curgrid<numgrids; curgrid++) {
+
+      // initialize found arrays to center of each grid
+      for (int g=0; g<numgrids; g++) {
+        foundx[g] = cenx;
+        foundy[g] = ceny;
+      }
+
+      // keeps track of whether entire scan line is outside previous grid
+      int lfound;
+      // keeps track of whether entire grid is outside previous grid
+      int gfound;
+
+      // examine every point in the current grid
+      gfound = curgrid;
+      for (int line=0; line<leny; line++) {
+        lfound = curgrid;
+        for (int pix=0; pix<lenx; pix++) {
+
+          // the point to be located
+          curpt = lenp*curgrid + lenx*line + pix;
+          float x = samples[0][curpt];
+          float y = samples[1][curpt];
+
+          // look through all applicable previous grids until box found
+          boolean found = false;
+          for (int g=prevgrid; g<curgrid; g++) {
+
+            // search grid #g for containing grid box of current point
+            int gx = foundx[g];
+            int gy = foundy[g];
+            int ogx, ogy;
+            int q, qg;
+            float ax, ay, bx, by, cx, cy, dx, dy;
+
+            // marching boxes--find correct grid box
+            while (!found) {
+              q = lenp*g + lenx*gy + gx;       // index into samples
+              ax = samples[0][q];
+              ay = samples[1][q];
+              bx = samples[0][q+1];
+              by = samples[1][q+1];
+              cx = samples[0][q+lenx];
+              cy = samples[1][q+lenx];
+              dx = samples[0][q+lenx+1];
+              dy = samples[1][q+lenx+1];
+              ogx = gx;
+              ogy = gy;
+
+              // determine marching direction
+              switch (((ax - bx)*(y - by)
+                     - (ay - by)*(x - bx) < 0 == sig ? 0 : 1)
+                    + ((bx - dx)*(y - dy)
+                     - (by - dy)*(x - dx) < 0 == sig ? 0 : 2)
+                    + ((dx - cx)*(y - cy)
+                     - (dy - cy)*(x - cx) < 0 == sig ? 0 : 4)
+                    + ((cx - ax)*(y - ay)
+                     - (cy - ay)*(x - ax) < 0 == sig ? 0 : 8)) {
+
+                case 0: // inside this box
+                        qg = leng*g + (lenx-1)*gy + gx;  // index into grid
+                        found = true;
+                        if (lfound > g) lfound = g;
+                        if (gfound > g) gfound = g;
+                        foundx[g] = gx;
+                        foundy[g] = gy;
+
+                        // mark grid box as containing current point
+                        grid[qg][glen[qg]++] = curpt;
+
+                        // expand grid array as necessary
+                        if (glen[qg] > gsize[qg]) {
+                          int[] newg = new int[gsize[q]+gsize[q]+1];
+                          System.arraycopy(grid[qg], 0, newg, 0, gsize[qg]);
+                          grid[qg] = newg;
+                          gsize[qg] += gsize[qg];
+                        }
+
+                        // mark point as being found
+                        ptfell[curpt] = true;
+
+                        // break box to the lower left of point
+                        if (broken[curgrid][pix] < line) {
+                          broken[curgrid][pix] = line;
+                        }
+
+                        // break box to the lower right of point
+                        if (broken[curgrid][pix+1] < line) {
+                          broken[curgrid][pix+1] = line;
+                        }
+                        break;
+
+                case 1: case 11: // up from this box
+                        gy--;
+                        break;
+                case 2: case 7: // right from this box
+                        gx++;
+                        break;
+                case 3: // up and right from this box
+                        gx++;
+                        gy--;
+                        break;
+                case 4: case 14: // down from this box
+                        gy++;
+                        break;
+                case 6: // down and right from this box
+                        gx++;
+                        gy++;
+                        break;
+                case 8: case 13: // left from this box
+                        gx--;
+                        break;
+                case 9: // up and left from this box
+                        gx--;
+                        gy--;
+                        break;
+                case 12: // down and left from this box
+                        gx--;
+                        gy++;
+                        break;
+
+                default: // theoretically, should never occur
+                        throw new SetException("DelaunayOverlap: "
+                                              +"pathological grid");
+              }
+
+              if (gx > lenx-2) gx = lenx-2;
+              if (gx < 0) gx = 0;
+              if (gy > leny-2) gy = leny-2;
+              if (gy < 0) gy = 0;
+
+              // check if point is outside the grid
+              if (ogx == gx && ogy == gy && !found) break;
+            }
+
+            // no need to check remaining grids if a grid box was located
+            if (found) break;
+          }
+        }
+
+        // if entire scan line wasn't in a previous grid, advance prevgrid
+        prevgrid = lfound;
+      }
+      // if entire grid wasn't in a previous grid, advance prevgrid
+      prevgrid = gfound;
+    }
+
+    // *** PHASE 1b: *** Break boxes that contain a point from
+    //                   the bottom line of the previous grid.
+    //                   Search logic from above is duplicated
+    //                   to maximize efficiency of the search.
+
+    // offset of the bottom scan line of a grid
+    int z = lenp-lenx;
+
+    // re-initialize arrays to center of each grid (old values are
+    // irrelevant now; center is actually a better starting guess)
+    for (int g=0; g<numgrids; g++) {
+      foundx[g] = cenx;
+      foundy[g] = ceny;
+    }
+
+    // search all grids but the last one
+    for (curgrid=0; curgrid<numgrids-1; curgrid++) {
+
+      // search every point in the bottom scan line of curgrid
+      for (int pix=0; pix<lenx; pix++) {
+
+        // the point to be located
+        curpt = lenp*curgrid + z + pix;
+        float x = samples[0][curpt];
+        float y = samples[1][curpt];
+        int g = curgrid+1;
+
+        // search grid #g for containing grid box of current point
+        int gx = foundx[g];
+        int gy = foundy[g];
+        int ogx, ogy, q;
+        boolean found = false;
+        float ax, ay, bx, by, cx, cy, dx, dy;
+
+        // marching boxes--find correct grid box
+        while (!found) {
+          q = lenp*g + lenx*gy + gx;
+          ax = samples[0][q];
+          ay = samples[1][q];
+          bx = samples[0][q+1];
+          by = samples[1][q+1];
+          cx = samples[0][q+lenx];
+          cy = samples[1][q+lenx];
+          dx = samples[0][q+lenx+1];
+          dy = samples[1][q+lenx+1];
+          ogx = gx;
+          ogy = gy;
+
+          // determine marching direction
+          switch (((ax - bx)*(y - by)
+                 - (ay - by)*(x - bx) < 0 == sig ? 0 : 1)
+                + ((bx - dx)*(y - dy)
+                 - (by - dy)*(x - dx) < 0 == sig ? 0 : 2)
+                + ((dx - cx)*(y - cy)
+                 - (dy - cy)*(x - cx) < 0 == sig ? 0 : 4)
+                + ((cx - ax)*(y - ay)
+                 - (cy - ay)*(x - ax) < 0 == sig ? 0 : 8)) {
+
+            case 0: // inside this box
+                    found = true;
+                    foundx[g] = gx;
+                    foundy[g] = gy;
+
+                    // break containing box
+                    if (broken[g][gx+1] < gy) broken[g][gx+1] = gy;
+                    break;
+
+            case 1: case 11: // up from this box
+                    gy--;
+                    break;
+            case 2: case 7: // right from this box
+                    gx++;
+                    break;
+            case 3: // up and right from this box
+                    gx++;
+                    gy--;
+                    break;
+            case 4: case 14: // down from this box
+                    gy++;
+                    break;
+            case 6: // down and right from this box
+                    gx++;
+                    gy++;
+                    break;
+            case 8: case 13: // left from this box
+                    gx--;
+                    break;
+            case 9: // up and left from this box
+                    gx--;
+                    gy--;
+                    break;
+            case 12: // down and left from this box
+                    gx--;
+                    gy++;
+                    break;
+
+            default: // theoretically, should never occur
+                    throw new SetException("DelaunayOverlap: "
+                                          +"pathological grid");
+          }
+          if (gx > lenx-2) gx = lenx-2;
+          if (gx < 0) gx = 0;
+          if (gy > leny-2) gy = leny-2;
+          if (gy < 0) gy = 0;
+
+          // check if point is outside the grid
+          if (ogx == gx && ogy == gy && !found) break;
+        }
+      }
+    }
+
+    // *** PHASE 2: *** triangulate points inside grid boxes
+
+    // set leftedge to the first column of grid 0
+    leftedge[0] = 0;
+    for (int i=1; i<leny; i++) {
+      leftedge[i] = leftedge[i-1] + lenx;
+    }
+    ledges += leny;
+
+    // set rightedge to the last column of grid 0
+    rightedge[0] = lenx-1;
+    for (int i=1; i<leny; i++) {
+      rightedge[i] = rightedge[i-1] + lenx;
+    }
+    redges += leny;
+
+    // determine necessary tri array size
+    // Note: This memory allocation strategy could be more precise.
+    //       It should loop through only unbroken boxes, and figure
+    //       out the number of triangles in the zipped sections and
+    //       the number of lost points.  If these steps were
+    //       implemented, then the tri and walk arrays would not be
+    //       needed, and triangulation data could be stored directly
+    //       in the Tri and Walk arrays.
+    for (curgrid=0; curgrid<numgrids; curgrid++) {
+      for (int gy=0; gy<leny-1; gy++) {
+        for (int gx=0; gx<lenx-1; gx++) {
+          int npts = (curgrid == numgrids-1)
+                   ? 0 : glen[leng*curgrid + (lenx-1)*gy + gx];
+          trisize += 2*npts+2;
+        }
+      }
+    }
+    // probably won't be more than 2*leny Type 2 lost points per grid
+    trisize += 2*leny*numgrids;
+
+    // allocate memory
+    tri = new int[trisize+2][3];
+    walk = new int[trisize+2][3];
+    for (int i=0; i<trisize+2; i++) {
+      walk[i][0] = -1;
+      walk[i][1] = -1;
+      walk[i][2] = -1;
+    }
+
+    // examine every grid
+    for (curgrid=0; curgrid<numgrids; curgrid++) {
+
+      // save old bottom values
+      old_bottom = bottom;
+      bottom = new int[lenx-1];
+      for (int i=0; i<lenx-1; i++) bottom[i] = -1;
+
+      // triangulate all unbroken boxes in this grid
+      for (int gx=0; gx<lenx-1; gx++) {
+        for (int gy=broken[curgrid][gx+1]+1; gy<leny-1; gy++) {
+
+          int q = lenp*curgrid + lenx*gy + gx;
+          int qmg = (lenx-1)*gy + gx;
+          int qg = leng*curgrid + qmg;
+          int A = q;
+          int B = q + 1;
+          int C = q + lenx;
+          int D = q + lenx + 1;
+
+          float ax, ay, bx, by, cx, cy, dx, dy;
+          int G1, G2, G3;
+
+          switch (curgrid == numgrids-1 ? 0 : glen[qg]) {
+            case 0: // find the best diagonal of the box
+                    ax = samples[0][A];
+                    ay = samples[1][A];
+                    bx = samples[0][B];
+                    by = samples[1][B];
+                    cx = samples[0][C];
+                    cy = samples[1][C];
+                    dx = samples[0][D];
+                    dy = samples[1][D];
+                    float abx = ax - bx;
+                    float aby = ay - by;
+                    float acx = ax - cx;
+                    float acy = ay - cy;
+                    float dbx = dx - bx;
+                    float dby = dy - by;
+                    float dcx = dx - cx;
+                    float dcy = dy - cy;
+                    float Q = abx*acx + aby*acy;
+                    float R = dbx*abx + dby*aby;
+                    float S = acx*dcx + acy*dcy;
+                    float T = dbx*dcx + dby*dcy;
+                    boolean diag;
+                    if (Q < 0 && T < 0 || R > 0 && S > 0) diag = true;
+                    else if (R < 0 && S < 0 || Q > 0 && T > 0) diag = false;
+                    else if ((Q < 0 ? Q : T) < (R < 0 ? R : S)) diag = true;
+                    else diag = false;
+
+                    if (diag) {
+                      tri[trilen][0] = B;
+                      tri[trilen][1] = D;
+                      tri[trilen][2] = A;
+                      walk[trilen][2] = bottom[gx];
+                      if (bottom[gx] >= 0) walk[bottom[gx]][1] = trilen;
+                      walk[trilen][1] = trilen+1;
+                      trilen++;
+                      tri[trilen][0] = A;
+                      tri[trilen][1] = D;
+                      tri[trilen][2] = C;
+                      if (gx > 0 && broken[curgrid][gx] < gy) {
+                        walk[trilen][2] = tlr[qmg-1][2];
+                        walk[tlr[qmg-1][2]][0] = trilen;
+                      }
+                      else walk[trilen][2] = -1;
+                      walk[trilen][0] = trilen-1;
+                      trilen++;
+                      bottom[gx] = trilen-1;
+                      tlr[qmg][0] = trilen-2;
+                      tlr[qmg][1] = trilen-1;
+                      tlr[qmg][2] = trilen-2;
+                      istwo[qmg] = true;
+                    }
+                    else {
+                      tri[trilen][0] = A;
+                      tri[trilen][1] = B;
+                      tri[trilen][2] = C;
+                      walk[trilen][0] = bottom[gx];
+                      if (bottom[gx] >= 0) walk[bottom[gx]][1] = trilen;
+                      if (gx > 0 && broken[curgrid][gx] < gy) {
+                        walk[trilen][2] = tlr[qmg-1][2];
+                        walk[tlr[qmg-1][2]][0] = trilen;
+                      }
+                      else walk[trilen][2] = -1;
+                      walk[trilen][1] = trilen+1;
+                      trilen++;
+                      tri[trilen][0] = B;
+                      tri[trilen][1] = D;
+                      tri[trilen][2] = C;
+                      walk[trilen][2] = trilen-1;
+                      trilen++;
+                      bottom[gx] = trilen-1;
+                      tlr[qmg][0] = trilen-2;
+                      tlr[qmg][1] = trilen-2;
+                      tlr[qmg][2] = trilen-1;
+                      istwo[qmg] = false;
+                    }
+                    break;
+
+            case 1: // draw the four triangles of the point
+                    G1 = grid[qg][0];
+                    tri[trilen][0] = B;
+                    tri[trilen][1] = G1;
+                    tri[trilen][2] = A;
+                    walk[trilen][2] = bottom[gx];
+                    if (bottom[gx] >= 0) walk[bottom[gx]][1] = trilen;
+                    walk[trilen][0] = trilen+1;
+                    walk[trilen][1] = trilen+3;
+                    trilen++;
+                    tri[trilen][0] = B;
+                    tri[trilen][1] = D;
+                    tri[trilen][2] = G1;
+                    walk[trilen][1] = trilen+1;
+                    walk[trilen][2] = trilen-1;
+                    trilen++;
+                    tri[trilen][0] = G1;
+                    tri[trilen][1] = D;
+                    tri[trilen][2] = C;
+                    walk[trilen][0] = trilen-1;
+                    walk[trilen][2] = trilen+1;
+                    trilen++;
+                    tri[trilen][0] = A;
+                    tri[trilen][1] = G1;
+                    tri[trilen][2] = C;
+                    if (gx > 0 && broken[curgrid][gx] < gy) {
+                      walk[trilen][2] = tlr[qmg-1][2];
+                      walk[tlr[qmg-1][2]][0] = trilen;
+                    }
+                    else walk[trilen][2] = -1;
+                    walk[trilen][0] = trilen-3;
+                    walk[trilen][1] = trilen-1;
+                    trilen++;
+                    bottom[gx] = trilen-2;
+                    tlr[qmg][0] = trilen-4;
+                    tlr[qmg][1] = trilen-1;
+                    tlr[qmg][2] = trilen-3;
+                    istwo[qmg] = true;
+                    break;
+
+           default: // triangulate first two points
+                    // use a diagonal comparison
+                    G1 = grid[qg][0];
+                    G2 = grid[qg][1];
+                    float Gdx = samples[0][G2] - samples[0][G1];
+                    float Gdy = samples[1][G2] - samples[1][G1];
+                    ax = samples[0][A];
+                    ay = samples[1][A];
+                    bx = samples[0][B];
+                    by = samples[1][B];
+                    cx = samples[0][C];
+                    cy = samples[1][C];
+                    dx = samples[0][D];
+                    dy = samples[1][D];
+                    float val1 = Gdx*(ax-dx) + Gdy*(ay-dy);
+                    if (val1 < 0) val1 = -val1;
+                    float val2 = Gdx*(bx-cx) + Gdy*(by-cy);
+                    if (val2 < 0) val2 = -val2;
+
+                    if (val1 > val2) {
+                      float Qx1 = ax - samples[0][G1];
+                      float Qy1 = ay - samples[1][G1];
+                      float Qx2 = ax - samples[0][G2];
+                      float Qy2 = ay - samples[1][G2];
+                      if (Qx1*Qx1 + Qy1*Qy1 < Qx2*Qx2 + Qy2*Qy2) {
+                        // G1 is closer to A than G2
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = A;
+                        walk[trilen][2] = bottom[gx];
+                        if (bottom[gx] >= 0) walk[bottom[gx]][1] = trilen;
+                        walk[trilen][0] = trilen+5;
+                        walk[trilen][1] = trilen+1;
+                        trilen++;
+                        tri[trilen][0] = A;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = C;
+                        if (gx > 0 && broken[curgrid][gx] < gy) {
+                          walk[trilen][2] = tlr[qmg-1][2];
+                          walk[tlr[qmg-1][2]][0] = trilen;
+                        }
+                        else walk[trilen][2] = -1;
+                        walk[trilen][0] = trilen-1;
+                        walk[trilen][1] = trilen+3;
+                        trilen++;
+                        tri[trilen][0] = G2;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = C;
+                        walk[trilen][0] = trilen+1;
+                        walk[trilen][2] = trilen+2;
+                        trilen++;
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = G2;
+                        walk[trilen][1] = trilen-1;
+                        walk[trilen][2] = trilen+2;
+                        trilen++;
+                        tri[trilen][0] = C;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = G2;
+                        walk[trilen][0] = trilen-3;
+                        walk[trilen][1] = trilen+1;
+                        walk[trilen][2] = trilen-2;
+                        trilen++;
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = G1;
+                        walk[trilen][0] = trilen-2;
+                        walk[trilen][1] = trilen-1;
+                        walk[trilen][2] = trilen-5;
+                        trilen++;
+                        bottom[gx] = trilen-4;
+                        tlr[qmg][0] = trilen-6;
+                        tlr[qmg][1] = trilen-5;
+                        tlr[qmg][2] = trilen-3;
+                        istwo[qmg] = true;
+                      }
+                      else {
+                        // G2 is closer to A than G1
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = A;
+                        walk[trilen][2] = bottom[gx];
+                        if (bottom[gx] >= 0) walk[bottom[gx]][1] = trilen;
+                        walk[trilen][0] = trilen+5;
+                        walk[trilen][1] = trilen+1;
+                        trilen++;
+                        tri[trilen][0] = A;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = C;
+                        if (gx > 0 && broken[curgrid][gx] < gy) {
+                          walk[trilen][2] = tlr[qmg-1][2];
+                          walk[tlr[qmg-1][2]][0] = trilen;
+                        }
+                        else walk[trilen][2] = -1;
+                        walk[trilen][0] = trilen-1;
+                        walk[trilen][1] = trilen+3;
+                        trilen++;
+                        tri[trilen][0] = G1;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = C;
+                        walk[trilen][0] = trilen+1;
+                        walk[trilen][2] = trilen+2;
+                        trilen++;
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = G1;
+                        walk[trilen][1] = trilen-1;
+                        walk[trilen][2] = trilen+2;
+                        trilen++;
+                        tri[trilen][0] = C;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = G1;
+                        walk[trilen][0] = trilen-3;
+                        walk[trilen][1] = trilen+1;
+                        walk[trilen][2] = trilen-2;
+                        trilen++;
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = G2;
+                        walk[trilen][0] = trilen-2;
+                        walk[trilen][1] = trilen-1;
+                        walk[trilen][2] = trilen-5;
+                        trilen++;
+                        bottom[gx] = trilen-4;
+                        tlr[qmg][0] = trilen-6;
+                        tlr[qmg][1] = trilen-5;
+                        tlr[qmg][2] = trilen-3;
+                        istwo[qmg] = true;
+                      }
+                    }
+                    else {
+                      float Qx1 = bx - samples[0][G1];
+                      float Qy1 = by - samples[1][G1];
+                      float Qx2 = bx - samples[0][G2];
+                      float Qy2 = by - samples[1][G2];
+                      if (Qx1*Qx1 + Qy1*Qy1 < Qx2*Qx2 + Qy2*Qy2) {
+                        // G1 is closer to B than G2
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = A;
+                        walk[trilen][2] = bottom[gx];
+                        if (bottom[gx] >= 0) walk[bottom[gx]][1] = trilen;
+                        walk[trilen][0] = trilen+3;
+                        walk[trilen][1] = trilen+4;
+                        trilen++;
+                        tri[trilen][0] = A;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = C;
+                        if (gx > 0 && broken[curgrid][gx] < gy) {
+                          walk[trilen][2] = tlr[qmg-1][2];
+                          walk[tlr[qmg-1][2]][0] = trilen;
+                        }
+                        else walk[trilen][2] = -1;
+                        walk[trilen][0] = trilen+3;
+                        walk[trilen][1] = trilen+1;
+                        trilen++;
+                        tri[trilen][0] = G2;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = C;
+                        walk[trilen][0] = trilen+3;
+                        walk[trilen][2] = trilen-1;
+                        trilen++;
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = G1;
+                        walk[trilen][1] = trilen+2;
+                        walk[trilen][2] = trilen-3;
+                        trilen++;
+                        tri[trilen][0] = A;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = G2;
+                        walk[trilen][0] = trilen-4;
+                        walk[trilen][1] = trilen+1;
+                        walk[trilen][2] = trilen-3;
+                        trilen++;
+                        tri[trilen][0] = D;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = G1;
+                        walk[trilen][0] = trilen-3;
+                        walk[trilen][1] = trilen-1;
+                        walk[trilen][2] = trilen-2;
+                        trilen++;
+                        bottom[gx] = trilen-4;
+                        tlr[qmg][0] = trilen-6;
+                        tlr[qmg][1] = trilen-5;
+                        tlr[qmg][2] = trilen-3;
+                        istwo[qmg] = true;
+                      }
+                      else {
+                        // G2 is closer to B than G1
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = A;
+                        walk[trilen][2] = bottom[gx];
+                        if (bottom[gx] >= 0) walk[bottom[gx]][1] = trilen;
+                        walk[trilen][0] = trilen+3;
+                        walk[trilen][1] = trilen+4;
+                        trilen++;
+                        tri[trilen][0] = A;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = C;
+                        if (gx > 0 && broken[curgrid][gx] < gy) {
+                          walk[trilen][2] = tlr[qmg-1][2];
+                          walk[tlr[qmg-1][2]][0] = trilen;
+                        }
+                        else walk[trilen][2] = -1;
+                        walk[trilen][0] = trilen+3;
+                        walk[trilen][1] = trilen+1;
+                        trilen++;
+                        tri[trilen][0] = G1;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = C;
+                        walk[trilen][0] = trilen+3;
+                        walk[trilen][2] = trilen-1;
+                        trilen++;
+                        tri[trilen][0] = B;
+                        tri[trilen][1] = D;
+                        tri[trilen][2] = G2;
+                        walk[trilen][1] = trilen+2;
+                        walk[trilen][2] = trilen-3;
+                        trilen++;
+                        tri[trilen][0] = A;
+                        tri[trilen][1] = G2;
+                        tri[trilen][2] = G1;
+                        walk[trilen][0] = trilen-4;
+                        walk[trilen][1] = trilen+1;
+                        walk[trilen][2] = trilen-3;
+                        trilen++;
+                        tri[trilen][0] = D;
+                        tri[trilen][1] = G1;
+                        tri[trilen][2] = G2;
+                        walk[trilen][0] = trilen-3;
+                        walk[trilen][1] = trilen-1;
+                        walk[trilen][2] = trilen-2;
+                        trilen++;
+                        bottom[gx] = trilen-4;
+                        tlr[qmg][0] = trilen-6;
+                        tlr[qmg][1] = trilen-5;
+                        tlr[qmg][2] = trilen-3;
+                        istwo[qmg] = true;
+                      }
+                    }
+
+                    // subtriangulate remaining points
+                    int starttri = trilen-1;
+                    int maxit = 2*glen[qg]+2;
+                    for (int i=2; i<glen[qg]; i++) {
+                      int pt = grid[qg][i];
+
+                      // find containing triangle of point pt
+                      int curtri = starttri;
+                      int p0 = -1;
+                      int p1 = -1;
+                      int p2 = -1;
+                      int w0 = -1;
+                      int w1 = -1;
+                      int w2 = -1;
+                      boolean found = false;
+                      int itnum;
+                      for (itnum=0; itnum<maxit && !found; itnum++) {
+                        p0 = tri[curtri][0];
+                        p1 = tri[curtri][1];
+                        p2 = tri[curtri][2];
+                        w0 = walk[curtri][0];
+                        w1 = walk[curtri][1];
+                        w2 = walk[curtri][2];
+                        float ptx = samples[0][pt];
+                        float pty = samples[1][pt];
+                        float p0x = samples[0][p0];
+                        float p0y = samples[1][p0];
+                        float p1x = samples[0][p1];
+                        float p1y = samples[1][p1];
+                        float p2x = samples[0][p2];
+                        float p2y = samples[1][p2];
+                        float p01x = p0x-p1x;
+                        float p01y = p0y-p1y;
+                        float p02x = p0x-p2x;
+                        float p02y = p0y-p2y;
+                        float p12x = p1x-p2x;
+                        float p12y = p1y-p2y;
+                        float c0 = p01x*(p0y-pty) - p01y*(p0x-ptx);
+                        float c1 = p12x*(p1y-pty) - p12y*(p1x-ptx);
+                        float c2 = p02x*(pty-p2y) - p02y*(ptx-p2x);
+                        boolean t0 = ( c0 == 0 ||
+                                       c0 > 0 == p01x*p02y - p01y*p02x > 0 );
+                        boolean t1 = ( c1 == 0 ||
+                                       c1 > 0 == p12x*p01y - p12y*p01x > 0 );
+                        boolean t2 = ( c2 == 0 ||
+                                       c2 > 0 == p02x*p12y - p02y*p12x > 0 );
+
+                        if (!t0 && !t1 && !t2) {
+                          throw new SetException("DelaunayOverlap: "
+                                           +"subtriangulation error");
+                        }
+                        else if (!t0) {
+                          if (curtri != tlr[qmg][2]) curtri = w0;
+                          else throw new SetException("DelaunayOverlap: "
+                                                +"subtriangulation error");
+                        }
+                        else if (!t1) {
+                          if (curtri != bottom[gx]) curtri = w1;
+                          else throw new SetException("DelaunayOverlap: "
+                                                +"subtriangulation error");
+                        }
+                        else if (!t2) {
+                          if (curtri != tlr[qmg][0] && curtri != tlr[qmg][1]) {
+                            curtri = w2;
+                          }
+                          else throw new SetException("DelaunayOverlap: "
+                                                +"subtriangulation error");
+                        }
+                        else found = true;
+                      }
+
+                      // should never happen
+                      if (!found) throw new SetException("DelaunayOverlap: "
+                                                   +"subtriangulation error");
+                      else if (curtri == bottom[gx]) {
+                        // curtri is on bottom edge of grid box
+
+                        // find adjoining triangles' connecting edges
+                        int we0 = -1;
+                        int we2 = -1;
+                        for (int w=0; w<3; w++) {
+                          if (walk[w0][w] == curtri) we0 = w;
+                          if (walk[w2][w] == curtri) we2 = w;
+                        }
+                        if (we0 < 0 || we2 < 0) {
+                          throw new SetException("DelaunayOverlap: "
+                                           +"subtriangulation error");
+                        }
+
+                        // define three new triangles
+                        // the first new triangle overwrites the old triangle
+                        tri[curtri][0] = pt;
+                        // tri[curtri][1] = p1;             <-- already true
+                        // tri[curtri][2] = p2;             <-- already true
+                        walk[curtri][0] = trilen;
+                        // walk[curtri][1] = w1;            <-- already true
+                        // walk[w1][we1] = curtri;          <-- already true
+                        walk[curtri][2] = trilen+1;
+
+                        tri[trilen][0] = p1;
+                        tri[trilen][1] = pt;
+                        tri[trilen][2] = p0;
+                        walk[trilen][0] = curtri;
+                        walk[trilen][1] = trilen+1;
+                        walk[trilen][2] = w0;
+                        walk[w0][we0] = trilen;
+                        trilen++;
+
+                        tri[trilen][0] = p0;
+                        tri[trilen][1] = pt;
+                        tri[trilen][2] = p2;
+                        walk[trilen][0] = trilen-1;
+                        walk[trilen][1] = curtri;
+                        walk[trilen][2] = w2;
+                        walk[w2][we2] = trilen;
+                        trilen++;
+                      }
+                      else if (curtri == tlr[qmg][0] || curtri == tlr[qmg][1]) {
+                        // tlr[qmg][0]: curtri is on top edge of grid box
+                        // tlr[qmg][1]: curtri is on left edge of grid box
+
+                        // find adjoining triangles' connecting edges
+                        int we0 = -1;
+                        int we1 = -1;
+                        for (int w=0; w<3; w++) {
+                          if (walk[w0][w] == curtri) we0 = w;
+                          if (walk[w1][w] == curtri) we1 = w;
+                        }
+                        if (we0 < 0 || we1 < 0) {
+                          throw new SetException("DelaunayOverlap: "
+                                           +"subtriangulation error");
+                        }
+
+                        // define three new triangles
+                        // the first new triangle overwrites the old triangle
+                        // tri[curtri][0] = p0;             <-- already true
+                        tri[curtri][1] = pt;
+                        // tri[curtri][2] = p2;             <-- already true
+                        walk[curtri][0] = trilen;
+                        walk[curtri][1] = trilen+1;
+                        // walk[curtri][2] = w2;            <-- already true
+                        // walk[w2][we2] = curtri;          <-- already true
+
+                        tri[trilen][0] = p0;
+                        tri[trilen][1] = p1;
+                        tri[trilen][2] = pt;
+                        walk[trilen][0] = w0;
+                        walk[w0][we0] = trilen;
+                        walk[trilen][1] = trilen+1;
+                        walk[trilen][2] = curtri;
+                        trilen++;
+
+                        tri[trilen][0] = pt;
+                        tri[trilen][1] = p1;
+                        tri[trilen][2] = p2;
+                        walk[trilen][0] = trilen-1;
+                        walk[trilen][1] = w1;
+                        walk[w1][we1] = trilen;
+                        walk[trilen][2] = curtri;
+                        trilen++;
+                      }
+                      else {
+                        // if curtri == tlr[qmg][2],
+                        // then curtri is on right edge of grid box,
+                        // otherwise, curtri is not on border of grid box
+
+                        // find adjoining triangles' connecting edges
+                        int we1 = -1;
+                        int we2 = -1;
+                        for (int w=0; w<3; w++) {
+                          if (walk[w1][w] == curtri) we1 = w;
+                          if (walk[w2][w] == curtri) we2 = w;
+                        }
+                        if (we1 < 0 || we2 < 0) {
+                          throw new SetException("DelaunayOverlap: "
+                                           +"subtriangulation error");
+                        }
+
+                        // define three new triangles
+                        // the first new triangle overwrites the old triangle
+                        // tri[curtri][0] = p0;             <-- already true
+                        // tri[curtri][1] = p1;             <-- already true
+                        tri[curtri][2] = pt;
+                        // walk[curtri][0] = w0;            <-- already true
+                        // walk[w0][we0] = curtri;          <-- already true
+                        walk[curtri][1] = trilen;
+                        walk[curtri][2] = trilen+1;
+
+                        tri[trilen][0] = pt;
+                        tri[trilen][1] = p1;
+                        tri[trilen][2] = p2;
+                        walk[trilen][0] = curtri;
+                        walk[trilen][1] = w1;
+                        walk[w1][we1] = trilen;
+                        walk[trilen][2] = trilen+1;
+                        trilen++;
+
+                        tri[trilen][0] = p0;
+                        tri[trilen][1] = pt;
+                        tri[trilen][2] = p2;
+                        walk[trilen][0] = curtri;
+                        walk[trilen][1] = trilen-1;
+                        walk[trilen][2] = w2;
+                        walk[w2][we2] = trilen;
+                        trilen++;
+                      }
+                    }
+                    break;
+          }
+
+        }
+      }
+
+      // *** PHASE 3: *** triangulate connection between grids
+
+      if (curgrid == 0) {
+        // construct ltrinum, ltriedge, rtrinum, and rtriedge
+        for (int i=0; i<ledges-1; i++) {
+          ltrinum[i] = tlr[i*(lenx-1)][1];
+          ltriedge[i] = 2;
+        }
+        for (int i=0; i<redges-1; i++) {
+          rtrinum[i] = tlr[(i+1)*(lenx-1)-1][2];
+          rtriedge[i] = 0;
+        }
+      }
+      else {
+        // starting and ending indices of grid connection
+        int start1;    // index into samples (left edge of curgrid)
+        int start2;    // index into leftedge
+        int end1;      // index into samples (right edge of curgrid)
+        int end2;      // index into rightedge
+
+        // find start1 and start2
+        int tmup = curgrid*lenp + lenx*(broken[curgrid][0]+1);
+        float tx = samples[0][tmup];
+        float ty = samples[1][tmup];
+        boolean sig = samples[1][leftedge[0]]
+                    < samples[1][leftedge[ledges-1]];
+
+        if (samples[1][leftedge[ledges-1]] < ty != sig
+         && samples[1][leftedge[ledges-1]] != ty) {
+          // use a binary search to find prospective start2
+          int min = 0;
+          int max = ledges-1;
+          int sg = (min+max)/2;
+          int itnum = 0;
+          while (samples[1][leftedge[sg+1]] < ty
+                == samples[1][leftedge[sg]] < ty && itnum < ledges) {
+            if (samples[1][leftedge[sg]] < ty == sig) {
+              // point is closer to ledges-1
+              min = sg;
+            }
+            else {
+              // point is closer to 0
+              if (sg == 0) {
+                throw new SetException("DelaunayOverlap: "
+                                      +"illegal grid overlap");
+              }
+              max = sg;
+            }
+            sg = (min+max)/2;
+            if (sg == ledges-1) {
+              throw new SetException("DelaunayOverlap: "
+                                    +"pathological grid overlap");
+            }
+            itnum++;
+          }
+          if (itnum >= ledges) {
+            throw new SetException("DelaunayOverlap: corrupt "
+                                  +"left edge structure");
+          }
+          int clep = leftedge[sg];
+          float cx = samples[0][clep];
+          float cy = samples[1][clep];
+          int clep1 = leftedge[sg+1];
+          float c1x = samples[0][clep1];
+          float c1y = samples[1][clep1];
+
+          // perform cross-product test to verify that points are correct
+          int pt = curgrid*lenp - lenx + 1;
+          float px = samples[0][pt];
+          float py = samples[1][pt];
+
+          if ( (tx-cx)*(ty-c1y) - (ty-cy)*(tx-c1x) > 0
+            == (px-cx)*(py-c1y) - (py-cy)*(px-c1x) > 0 ) {
+            // inverted overlap, must adopt slightly different strategy
+            start2 = ledges-1;
+
+            // use a binary search to find start1
+            min = 0;
+            max = leny-1;
+            sg = (min+max)/2;
+            itnum = 0;
+            float ll = samples[1][leftedge[ledges-1]];
+            int offst = curgrid*lenp;
+            int offst2 = offst+lenx;
+            while (samples[1][lenx*sg+offst] < ll
+                == samples[1][lenx*sg+offst2] < ll && itnum < leny) {
+              if (ll > samples[1][lenx*sg+offst]) {
+                // point is closer to leny-1
+                min = sg;
+              }
+              else {
+                // point is closer to 0
+                if (sg == 0) {
+                  throw new SetException("DelaunayOverlap: "
+                                        +"illegal grid overlap");
+                }
+                max = sg;
+              }
+              sg = (min+max)/2;
+              if (sg == leny-1) {
+                throw new SetException("DelaunayOverlap: "
+                                      +"pathological grid overlap");
+              }
+              itnum++;
+            }
+            if (itnum >= leny) {
+              throw new SetException("DelaunayOverlap:  corrupt "
+                                    +"grid structure");
+            }
+            start1 = lenx*sg+offst2;
+          }
+          else {
+            // normal overlap
+            start1 = tmup;
+            start2 = sg;
+          }
+        }
+        else {
+          // tmup is outside range of leftedge, use last leftedge pt
+          start1 = tmup;
+          start2 = ledges-1;
+        }
+
+        // find end1 and end2
+        tmup = curgrid*lenp + lenx*(broken[curgrid][lenx]+2) - 1;
+        tx = samples[0][tmup];
+        ty = samples[1][tmup];
+        sig = samples[1][rightedge[0]]
+            < samples[1][rightedge[redges-1]];
+
+        if (samples[1][rightedge[redges-1]] < ty != sig
+         && samples[1][rightedge[redges-1]] != ty) {
+          // use a binary search to find prospective end2
+          int min = 0;
+          int max = redges-1;
+          int sg = (min+max)/2;
+          int itnum = 0;
+          while (samples[1][rightedge[sg+1]] < ty
+                == samples[1][rightedge[sg]] < ty && itnum < redges) {
+            if (samples[1][rightedge[sg]] < ty == sig) {
+              // point is closer to redges-1
+              min = sg;
+            }
+            else {
+              // point is closer to 0
+              if (sg == 0) {
+                throw new SetException("DelaunayOverlap: "
+                                      +"illegal grid overlap");
+              }
+              max = sg;
+            }
+            sg = (min+max)/2;
+            if (sg == redges-1) {
+              throw new SetException("DelaunayOverlap: "
+                                    +"pathological grid overlap");
+            }
+            itnum++;
+          }
+          if (itnum >= redges) {
+            throw new SetException("DelaunayOverlap: corrupt "
+                                  +"right edge structure");
+          }
+          int crep = rightedge[sg];
+          float cx = samples[0][crep];
+          float cy = samples[1][crep];
+          int crep1 = rightedge[sg+1];
+          float c1x = samples[0][crep1];
+          float c1y = samples[1][crep1];
+
+          // perform cross-product test to verify that points are correct
+          int pt = curgrid*lenp - 2;
+          float px = samples[0][pt];
+          float py = samples[1][pt];
+
+          if ( (tx-cx)*(ty-c1y) - (ty-cy)*(tx-c1x) > 0
+            == (px-cx)*(py-c1y) - (py-cy)*(px-c1x) > 0 ) {
+            // inverted overlap, must adopt slightly different strategy
+            end2 = redges-1;
+
+            // use a binary search to find end1
+            min = 0;
+            max = leny-1;
+            sg = (min+max)/2;
+            itnum = 0;
+            float ll = samples[1][rightedge[redges-1]];
+            int offst = curgrid*lenp+lenx-1;
+            int offst2 = offst+lenx;
+            while (samples[1][lenx*sg+offst] < ll
+                == samples[1][lenx*sg+offst2] < ll && itnum < leny) {
+              if (ll > samples[1][lenx*sg+offst]) {
+                // point is closer to leny-1
+                min = sg;
+              }
+              else {
+                // point is closer to 0
+                if (sg == 0) {
+                  throw new SetException("DelaunayOverlap: "
+                                        +"illegal grid overlap");
+                }
+                max = sg;
+              }
+              sg = (min+max)/2;
+              if (sg == leny-1) {
+                throw new SetException("DelaunayOverlap: "
+                                      +"pathological grid overlap");
+              }
+              itnum++;
+            }
+            if (itnum >= leny) {
+              throw new SetException("DelaunayOverlap:  corrupt "
+                                    +"grid structure");
+            }
+            end1 = lenx*sg+offst2;
+          }
+          else {
+            // normal overlap
+            end1 = tmup;
+            end2 = sg;
+          }
+        }
+        else {
+          // tmup is outside range of rightedge, use last rightedge pt
+          end1 = tmup;
+          end2 = redges-1;
+        }
+
+        // save old edge values
+        old_leftedge = leftedge;
+        old_rightedge = rightedge;
+        old_ledges = ledges;
+        old_redges = redges;
+        old_ltrinum = ltrinum;
+        old_ltriedge = ltriedge;
+        old_rtrinum = rtrinum;
+        old_rtriedge = rtriedge;
+
+        // update leftedge and rightedge arrays
+        leftedge = new int[numgrids*leny];
+        ltrinum = new int[numgrids*leny];
+        ltriedge = new int[numgrids*leny];
+        ledges = 0;
+        rightedge = new int[numgrids*leny];
+        rtrinum = new int[numgrids*leny];
+        rtriedge = new int[numgrids*leny];
+        redges = 0;
+        System.arraycopy(old_leftedge, 0, leftedge, 0, start2+1);
+        System.arraycopy(old_ltrinum, 0, ltrinum, 0, start2);
+        System.arraycopy(old_ltriedge, 0, ltriedge, 0, start2);
+        ledges += start2+1;
+        System.arraycopy(old_rightedge, 0, rightedge, 0, end2+1);
+        System.arraycopy(old_rtrinum, 0, rtrinum, 0, end2);
+        System.arraycopy(old_rtriedge, 0, rtriedge, 0, end2);
+        redges += end2+1;
+        for (int i=start1; i<=(curgrid+1)*lenp-lenx; i+=lenx) {
+          leftedge[ledges++] = i;
+        }
+        for (int i=end1; i<(curgrid+1)*lenp; i+=lenx) {
+          rightedge[redges++] = i;
+        }
+        int curledge = start2;
+        int curredge = redges - leny + broken[curgrid][lenx-1];
+
+        // indices into current quadrilateral's points
+        int base1, oneUp1;
+        int base2, oneUp2;
+
+        // x and y coordinates of base1 and oneUp1
+        int base1x, base1y, oneUp1x, oneUp1y;
+
+        // flag indicating which array base2 & oneUp2 reference
+        // b2lbr = 0  -->  base2 & oneUp2 reference old_leftedge array
+        // b2lbr = 1  -->  base2 & oneUp2 reference samples array
+        //                                          (bottom of curgrid-1)
+        // b2lbr = 2  -->  base2 & oneUp2 reference old_rightedge array
+        int b2lbr = 0;
+
+        // initialize base1 and base2
+        base1 = start1;
+        base2 = start2;
+        if (base2 == old_ledges-1) {
+          base2 = curgrid*lenp - lenx;
+          b2lbr = 1;
+        }
+        base1x = 0;
+        base1y = base1 % lenp / lenx;
+        boolean down = false;
+
+        // initialize oneUp1 and oneUp2
+        if (base1y > 0 && broken[curgrid][0] < base1y - 1) {
+          // move up
+          oneUp1x = 0;
+          oneUp1y = base1y - 1;
+        }
+        else if (base1y == leny-1 || broken[curgrid][1] < base1y) {
+          // move right
+          oneUp1x = 1;
+          oneUp1y = base1y;
+        }
+        else {
+          // move down
+          oneUp1x = 0;
+          oneUp1y = base1y + 1;
+          down = true;
+        }
+        oneUp1 = curgrid*lenp + oneUp1y*lenx + oneUp1x;
+        oneUp2 = base2 + 1;
+
+        boolean diag = false;
+        boolean firsttri = true;
+        while (base1 != end1 || base2 != end2 || b2lbr != 2) {
+
+          // determine which diagonal to take
+          if (base1 == end1) diag = true;
+          else if (base2 == end2 && b2lbr == 2) diag = false;
+          else {
+            // set up variables
+            float ax = samples[0][base1];
+            float ay = samples[1][base1];
+            float cx = samples[0][oneUp1];
+            float cy = samples[1][oneUp1];
+            float bx, by, dx, dy;
+            if (b2lbr == 0) {
+              bx = samples[0][old_leftedge[base2]];
+              by = samples[1][old_leftedge[base2]];
+              dx = samples[0][old_leftedge[oneUp2]];
+              dy = samples[1][old_leftedge[oneUp2]];
+            }
+            else if (b2lbr == 1) {
+              bx = samples[0][base2];
+              by = samples[1][base2];
+              dx = samples[0][oneUp2];
+              dy = samples[1][oneUp2];
+            }
+            else {
+              bx = samples[0][old_rightedge[base2]];
+              by = samples[1][old_rightedge[base2]];
+              dx = samples[0][old_rightedge[oneUp2]];
+              dy = samples[1][old_rightedge[oneUp2]];
+            }
+
+            // perform diagonal test
+            float abx = ax - bx;
+            float aby = ay - by;
+            float acx = ax - cx;
+            float acy = ay - cy;
+            float dbx = dx - bx;
+            float dby = dy - by;
+            float dcx = dx - cx;
+            float dcy = dy - cy;
+            float Q = abx*acx + aby*acy;
+            float R = dbx*abx + dby*aby;
+            float S = acx*dcx + acy*dcy;
+            float T = dbx*dcx + dby*dcy;
+            boolean QD = abx*acy - aby*acx > 0;
+            boolean RD = dbx*aby - dby*abx > 0;
+            boolean SD = acx*dcy - acy*dcx > 0;
+            boolean TD = dcx*dby - dcy*dbx > 0;
+            boolean sigD = (QD ? 1 : 0) + (RD ? 1 : 0)
+                         + (SD ? 1 : 0) + (TD ? 1 : 0) < 2;
+            if (QD == sigD) diag = true;
+            else if (RD == sigD || SD == sigD) diag = false;
+            else if (TD == sigD) diag = true;
+            else if (Q < 0 && T < 0 || R > 0 && S > 0) diag = true;
+            else if (R < 0 && S < 0 || Q > 0 && T > 0) diag = false;
+            else if ((Q < 0 ? Q : T) < (R < 0 ? R : S)) diag = true;
+            else diag = false;
+          }
+
+          // walk[*][0]             -->  NEXT
+          // walk[*][diag ? 1 : 2]  -->  PREVIOUS
+          // walk[*][diag ? 2 : 1]  -->  OUTSIDE
+
+          if (diag) {  // diagonal goes from base1 to oneUp2
+
+            // update tri array
+            if (b2lbr == 0) {
+              tri[trilen][0] = old_leftedge[oneUp2];
+              tri[trilen][1] = base1;
+              tri[trilen][2] = old_leftedge[base2];
+            }
+            else if (b2lbr == 1) {
+              tri[trilen][0] = oneUp2;
+              tri[trilen][1] = base1;
+              tri[trilen][2] = base2;
+            }
+            else {
+              tri[trilen][0] = old_rightedge[oneUp2];
+              tri[trilen][1] = base1;
+              tri[trilen][2] = old_rightedge[base2];
+            }
+
+            // update walk array
+
+            // current --> next
+            walk[trilen][0] = -1;
+
+            // current <--> previous
+            if (firsttri) {
+              ltrinum[curledge] = trilen;
+              ltriedge[curledge] = 2;
+              curledge++;
+              firsttri = false;
+            }
+            else {
+              walk[trilen][1] = trilen-1;
+              walk[trilen-1][0] = trilen;
+            }
+
+            // current <--> outside
+            if (b2lbr == 0) {
+              int x = old_ltrinum[base2];
+              walk[trilen][2] = x;
+              walk[x][old_ltriedge[base2]] = trilen;
+            }
+            else if (b2lbr == 1) {
+              int x = old_bottom[base2 % lenx];
+              walk[trilen][2] = x;
+              walk[x][1] = trilen;
+            }
+            else {
+              int x = old_rtrinum[oneUp2];
+              walk[trilen][2] = x;
+              walk[x][old_rtriedge[oneUp2]] = trilen;
+            }
+
+            // update base2
+            base2 = oneUp2;
+            if (b2lbr == 0 && base2 == old_ledges-1) {
+              b2lbr = 1;
+              base2 = curgrid*lenp - lenx;
+            }
+            if (b2lbr == 1 && base2 == curgrid*lenp - 1) {
+              b2lbr = 2;
+              base2 = old_redges-1;
+            }
+
+            // update oneUp2
+            oneUp2 = base2 + (b2lbr == 2 ? -1 : 1);
+          }
+          else {       // diagonal goes from base2 to oneUp1
+
+            // update tri array
+            if (b2lbr == 0) {
+              tri[trilen][0] = old_leftedge[base2];
+              tri[trilen][1] = oneUp1;
+              tri[trilen][2] = base1;
+            }
+            else if (b2lbr == 1) {
+              tri[trilen][0] = base2;
+              tri[trilen][1] = oneUp1;
+              tri[trilen][2] = base1;
+            }
+            else {
+              tri[trilen][0] = old_rightedge[base2];
+              tri[trilen][1] = oneUp1;
+              tri[trilen][2] = base1;
+            }
+
+            // update walk array
+
+            // current --> next
+            walk[trilen][0] = -1;
+
+            // current <--> previous
+            if (firsttri) {
+              ltrinum[curledge] = trilen;
+              ltriedge[curledge] = 1;
+              curledge++;
+              firsttri = false;
+            }
+            else {
+              walk[trilen][2] = trilen-1;
+              walk[trilen-1][0] = trilen;
+            }
+
+            // current <--> outside
+            if (oneUp1 - base1 == -lenx) {
+              // base1 -> oneUp1 = up
+              if (base1x < lenx-1) {
+                int x = tlr[(lenx-1)*oneUp1y+oneUp1x][1];
+                walk[trilen][1] = x;
+                walk[x][2] = trilen;
+              }
+              else {
+                // base1 is on the right edge of current grid
+                walk[trilen][1] = -1;
+
+                // update right edge triangle structure
+                rtrinum[curredge] = trilen;
+                rtriedge[curredge] = 1;
+                curredge--;
+              }
+            }
+            else if (oneUp1 - base1 == 1) {
+              // base1 -> oneUp1 = right
+              if (base1y < leny-1) {
+                int inx = (lenx-1)*base1y+base1x;
+                int x = tlr[inx][0];
+                walk[trilen][1] = x;
+                walk[x][istwo[inx] ? 2 : 0] = trilen;
+              }
+              else {
+                // base1 is on the bottom edge of current grid
+                walk[trilen][1] = -1;
+                bottom[base1x] = trilen;
+              }
+            }
+            else {
+              // base1 -> oneUp1 = down
+              if (base1x > 0) {
+                int x = tlr[(lenx-1)*base1y+base1x-1][2];
+                walk[trilen][1] = x;
+                walk[x][0] = trilen;
+              }
+              else {
+                // base1 is on the left edge of current grid
+                walk[trilen][1] = -1;
+                ltrinum[curledge] = trilen;
+                ltriedge[curledge] = 1;
+                curledge++;
+              }
+            }
+
+            // update base1
+            base1 = oneUp1;
+            base1x = oneUp1x;
+            base1y = oneUp1y;
+
+            // update oneUp1
+            if (broken[curgrid][base1x == 0 ? 0 : base1x+1] < base1y - 1
+                                       && base1y > 0 && !down) {
+              // move up
+              oneUp1x = base1x;
+              oneUp1y = base1y - 1;
+            }
+            else if ( base1y == leny-1 || (base1x < lenx-1
+                             && broken[curgrid][base1x+1] < base1y) ) {
+              // move right
+              oneUp1x = base1x+1;
+              oneUp1y = base1y;
+              down = false;
+            }
+            else {
+              // move down
+              oneUp1x = base1x;
+              oneUp1y = base1y + 1;
+              down = true;
+            }
+            oneUp1 = curgrid*lenp + oneUp1y*lenx + oneUp1x;
+          }
+
+          trilen++;
+
+          // expand tri array if necessary
+          if (trilen > trisize) {
+            trisize += trisize;
+            int[][] newtri = new int[trisize+2][3];
+            int[][] newwalk = new int[trisize+2][3];
+            for (int i=0; i<trilen; i++) {
+              newtri[i][0] = tri[i][0];
+              newtri[i][1] = tri[i][1];
+              newtri[i][2] = tri[i][2];
+              newwalk[i][0] = walk[i][0];
+              newwalk[i][1] = walk[i][1];
+              newwalk[i][2] = walk[i][2];
+            }
+            for (int i=trilen; i<trisize+2; i++) {
+              newwalk[i][0] = -1;
+              newwalk[i][1] = -1;
+              newwalk[i][2] = -1;
+            }
+            tri = newtri;
+            walk = newwalk;
+          }
+        }
+
+        // add triangle number (trilen-1) to rtrinum and rtriedge
+        rtrinum[curredge] = trilen-1;
+        rtriedge[curredge] = (diag ? 2 : 1);
+        curredge = redges - leny + broken[curgrid][lenx-1] + 1;
+
+        // finish updating ltrinum, ltriedge, rtrinum, and rtriedge
+        int x = broken[curgrid][1]+1;
+        for (int i=x; i<leny-1; i++) {
+          ltrinum[curledge] = tlr[(lenx-1)*i][1];
+          ltriedge[curledge] = 2;
+          curledge++;
+        }
+        x = broken[curgrid][lenx-1]+1;
+        for (int i=x; i<leny-1; i++) {
+          rtrinum[curredge] = tlr[(lenx-1)*(i+1)-1][2];
+          rtriedge[curredge] = 0;
+          curredge++;
+        }
+      }
+    }
+
+    // *** PHASE 4: *** sub-triangulate lost grid points
+
+    for (curgrid=0; curgrid<numgrids; curgrid++) {
+
+      // *** PHASE 4a: *** sub-triangulate Type 1 lost grid points
+
+      // find all Type 1 lost grid points and deal with them.
+      // Type 1 lost grid points are those that fall into a broken box
+
+      // The final grid's boxes cannot contain any points, and
+      // thus cannot contain any Type 1 lost points
+      if (curgrid < numgrids-1) {
+        int curtri = trilen/2;   // a reasonable starting guess
+        for (int gx=0; gx<lenx-1; gx++) {
+          for (int gy=0; gy<=broken[curgrid][gx+1]; gy++) {
+
+            // subtriangulate all points within broken grid boxes
+            int qg = leng*curgrid + (lenx-1)*gy + gx;
+
+            if (curtri < 0) curtri = trilen/2;
+            for (int pt=0; pt<glen[qg]; pt++) {
+
+              // point (px, py) is broken
+              float Px = samples[0][grid[qg][pt]];
+              float Py = samples[1][grid[qg][pt]];
+
+              // locate containing triangle of (px, py)
+              boolean located = false;
+              int itnum;
+              for (itnum=0; itnum<trilen && !located; itnum++) {
+                // define data
+                int t0 = tri[curtri][0];
+                int t1 = tri[curtri][1];
+                int t2 = tri[curtri][2];
+                float Ax = samples[0][t0];
+                float Ay = samples[1][t0];
+                float Bx = samples[0][t1];
+                float By = samples[1][t1];
+                float Cx = samples[0][t2];
+                float Cy = samples[1][t2];
+
+                // test whether point is contained in current triangle
+                float tval0 = (Bx-Ax)*(Py-Ay) - (By-Ay)*(Px-Ax);
+                float tval1 = (Cx-Bx)*(Py-By) - (Cy-By)*(Px-Bx);
+                float tval2 = (Ax-Cx)*(Py-Cy) - (Ay-Cy)*(Px-Cx);
+                boolean test0 = (tval0 == 0) || ( (tval0 > 0) == (
+                                (Bx-Ax)*(Cy-Ay) - (By-Ay)*(Cx-Ax) > 0) );
+                boolean test1 = (tval1 == 0) || ( (tval1 > 0) == (
+                                (Cx-Bx)*(Ay-By) - (Cy-By)*(Ax-Bx) > 0) );
+                boolean test2 = (tval2 == 0) || ( (tval2 > 0) == (
+                                (Ax-Cx)*(By-Cy) - (Ay-Cy)*(Bx-Cx) > 0) );
+
+                // figure out which triangle to go to next
+                if (!test0 && !test1 && !test2) {
+                  throw new SetException("DelaunayOverlap: corrupt "
+                                        +"triangle structure");
+                }
+                if (!test0 && !test1) {
+                  int tri0 = walk[curtri][0];
+                  int tri1 = walk[curtri][1];
+                  if (tri0 == -1) curtri = tri1;
+                  else curtri = tri0;
+                }
+                else if (!test0 && !test2) {
+                  int tri0 = walk[curtri][0];
+                  int tri2 = walk[curtri][2];
+                  if (tri0 == -1) curtri = tri2;
+                  else curtri = tri0;
+                }
+                else if (!test1 && !test2) {
+                  int tri1 = walk[curtri][1];
+                  int tri2 = walk[curtri][2];
+                  if (tri1 == -1) curtri = tri2;
+                  else curtri = tri1;
+                }
+                else if (!test0) curtri = walk[curtri][0];
+                else if (!test1) curtri = walk[curtri][1];
+                else if (!test2) curtri = walk[curtri][2];
+                else located = true;
+
+                // point is outside current triangulation
+                if (curtri < 0) itnum = trilen;
+              }
+
+              // if itnum == trilen, the point is permanently lost
+              if (itnum < trilen) {
+                // subtriangulate point grid[qg][pt]
+                // (curtri is the containing triangle)
+                // get curtri's point information
+                int q = grid[qg][pt];
+                int ct0 = tri[curtri][0];
+                int ct1 = tri[curtri][1];
+                int ct2 = tri[curtri][2];
+
+                // get curtri's walk information
+                int T0 = walk[curtri][0];
+                int T1 = walk[curtri][1];
+                int T2 = walk[curtri][2];
+                int TE0, TE1, TE2;
+                if (T0 == -1) TE0 = -1;
+                else if (walk[T0][0] == curtri) TE0 = 0;
+                else if (walk[T0][1] == curtri) TE0 = 1;
+                else if (walk[T0][2] == curtri) TE0 = 2;
+                else throw new SetException("DelaunayOverlap: corrupt "
+                                         +"walk structure");
+                if (T1 == -1) TE1 = -1;
+                else if (walk[T1][0] == curtri) TE1 = 0;
+                else if (walk[T1][1] == curtri) TE1 = 1;
+                else if (walk[T1][2] == curtri) TE1 = 2;
+                else throw new SetException("DelaunayOverlap: corrupt "
+                                         +"walk structure");
+                if (T2 == -1) TE2 = -1;
+                else if (walk[T2][0] == curtri) TE2 = 0;
+                else if (walk[T2][1] == curtri) TE2 = 1;
+                else if (walk[T2][2] == curtri) TE2 = 2;
+                else throw new SetException("DelaunayOverlap: corrupt "
+                                         +"walk structure");
+
+                // define three new triangles
+                // the first new triangle overwrites the old triangle
+                // tri[curtri][0] = ct0;                  <-- already true
+                // tri[curtri][1] = ct1;                  <-- already true
+                tri[curtri][2] = q;
+                // walk[curtri][0] = T0;                  <-- already true
+                // if (TE0 >= 0) walk[T0][TE0] = curtri;  <-- already true
+                walk[curtri][1] = trilen;
+                walk[curtri][2] = trilen+1;
+
+                tri[trilen][0] = q;
+                tri[trilen][1] = ct1;
+                tri[trilen][2] = ct2;
+                walk[trilen][0] = curtri;
+                walk[trilen][1] = T1;
+                if (TE1 >= 0) walk[T1][TE1] = trilen;
+                walk[trilen][2] = trilen+1;
+                trilen++;
+
+                tri[trilen][0] = ct0;
+                tri[trilen][1] = q;
+                tri[trilen][2] = ct2;
+                walk[trilen][0] = curtri;
+                walk[trilen][1] = trilen-1;
+                walk[trilen][2] = T2;
+                if (TE2 >= 0) walk[T2][TE2] = trilen;
+                trilen++;
+
+                // expand tri array if necessary
+                if (trilen > trisize) {
+                  trisize += trisize;
+                  int[][] newtri = new int[trisize+2][3];
+                  int[][] newwalk = new int[trisize+2][3];
+                  for (int i=0; i<trilen; i++) {
+                    newtri[i][0] = tri[i][0];
+                    newtri[i][1] = tri[i][1];
+                    newtri[i][2] = tri[i][2];
+                    newwalk[i][0] = walk[i][0];
+                    newwalk[i][1] = walk[i][1];
+                    newwalk[i][2] = walk[i][2];
+                  }
+                  for (int i=trilen; i<trisize+2; i++) {
+                    newwalk[i][0] = -1;
+                    newwalk[i][1] = -1;
+                    newwalk[i][2] = -1;
+                  }
+                  tri = newtri;
+                  walk = newwalk;
+                }
+
+              }
+            }
+          }
+        }
+      }
+
+      // *** PHASE 4b: *** sub-triangulate Type 2 lost grid points
+
+      // find all Type 2 lost grid points and deal with them.
+      // Type 2 lost grid points are those that did not fall into any
+      // box, but which have both grid boxes below them broken.
+      int curtri = trilen/2;
+      for (int line=0; line<leny-1; line++) {
+        for (int pix=1; pix<lenx-1; pix++) {
+
+          // find out if point is lost
+          int q = lenp*curgrid + lenx*line + pix;
+          if (!ptfell[q] && broken[curgrid][pix] >= line
+                         && broken[curgrid][pix+1] >= line) {
+
+            // point (px, py) is lost
+            float Px = samples[0][q];
+            float Py = samples[1][q];
+
+            // locate containing triangle of (px, py)
+            boolean located = false;
+            int itnum;
+            for (itnum=0; itnum<trilen && !located; itnum++) {
+              // define data
+              int t0 = tri[curtri][0];
+              int t1 = tri[curtri][1];
+              int t2 = tri[curtri][2];
+              float Ax = samples[0][t0];
+              float Ay = samples[1][t0];
+              float Bx = samples[0][t1];
+              float By = samples[1][t1];
+              float Cx = samples[0][t2];
+              float Cy = samples[1][t2];
+
+              // test whether point is contained in current triangle
+              float tval0 = (Bx-Ax)*(Py-Ay) - (By-Ay)*(Px-Ax);
+              float tval1 = (Cx-Bx)*(Py-By) - (Cy-By)*(Px-Bx);
+              float tval2 = (Ax-Cx)*(Py-Cy) - (Ay-Cy)*(Px-Cx);
+              boolean test0 = (tval0 == 0) || ( (tval0 > 0) == (
+                              (Bx-Ax)*(Cy-Ay) - (By-Ay)*(Cx-Ax) > 0) );
+              boolean test1 = (tval1 == 0) || ( (tval1 > 0) == (
+                              (Cx-Bx)*(Ay-By) - (Cy-By)*(Ax-Bx) > 0) );
+              boolean test2 = (tval2 == 0) || ( (tval2 > 0) == (
+                              (Ax-Cx)*(By-Cy) - (Ay-Cy)*(Bx-Cx) > 0) );
+
+              // figure out which triangle to go to next
+              if (!test0 && !test1 && !test2) {
+                throw new SetException("DelaunayOverlap: corrupt "
+                                      +"triangle structure");
+              }
+              if (!test0 && !test1) {
+                int tri0 = walk[curtri][0];
+                int tri1 = walk[curtri][1];
+                if (tri0 == -1) curtri = tri1;
+                else curtri = tri0;
+              }
+              else if (!test0 && !test2) {
+                int tri0 = walk[curtri][0];
+                int tri2 = walk[curtri][2];
+                if (tri0 == -1) curtri = tri2;
+                else curtri = tri0;
+              }
+              else if (!test1 && !test2) {
+                int tri1 = walk[curtri][1];
+                int tri2 = walk[curtri][2];
+                if (tri1 == -1) curtri = tri2;
+                else curtri = tri1;
+              }
+              else if (!test0) curtri = walk[curtri][0];
+              else if (!test1) curtri = walk[curtri][1];
+              else if (!test2) curtri = walk[curtri][2];
+              else located = true;
+
+              // point is outside current triangulation
+              // (this point is permanently lost)
+              if (curtri < 0) itnum = trilen;
+            }
+
+            // if itnum == trilen, the point is permanently lost
+            if (itnum < trilen) {
+              // subtriangulate point q
+              // (curtri is the containing triangle)
+              // get curtri's point information
+              int ct0 = tri[curtri][0];
+              int ct1 = tri[curtri][1];
+              int ct2 = tri[curtri][2];
+
+              // get curtri's walk information
+              int T0 = walk[curtri][0];
+              int T1 = walk[curtri][1];
+              int T2 = walk[curtri][2];
+              int TE0, TE1, TE2;
+              if (T0 == -1) TE0 = -1;
+              else if (walk[T0][0] == curtri) TE0 = 0;
+              else if (walk[T0][1] == curtri) TE0 = 1;
+              else if (walk[T0][2] == curtri) TE0 = 2;
+              else throw new SetException("DelaunayOverlap: corrupt "
+                                         +"walk structure");
+              if (T1 == -1) TE1 = -1;
+              else if (walk[T1][0] == curtri) TE1 = 0;
+              else if (walk[T1][1] == curtri) TE1 = 1;
+              else if (walk[T1][2] == curtri) TE1 = 2;
+              else throw new SetException("DelaunayOverlap: corrupt "
+                                         +"walk structure");
+              if (T2 == -1) TE2 = -1;
+              else if (walk[T2][0] == curtri) TE2 = 0;
+              else if (walk[T2][1] == curtri) TE2 = 1;
+              else if (walk[T2][2] == curtri) TE2 = 2;
+              else throw new SetException("DelaunayOverlap: corrupt "
+                                         +"walk structure");
+
+              // define three new triangles
+              // the first new triangle overwrites the old triangle
+              // tri[curtri][0] = ct0;                  <-- already true
+              // tri[curtri][1] = ct1;                  <-- already true
+              tri[curtri][2] = q;
+              // walk[curtri][0] = T0;                  <-- already true
+              // if (TE0 >= 0) walk[T0][TE0] = curtri;  <-- already true
+              walk[curtri][1] = trilen;
+              walk[curtri][2] = trilen+1;
+
+              tri[trilen][0] = q;
+              tri[trilen][1] = ct1;
+              tri[trilen][2] = ct2;
+              walk[trilen][0] = curtri;
+              walk[trilen][1] = T1;
+              if (TE1 >= 0) walk[T1][TE1] = trilen;
+              walk[trilen][2] = trilen+1;
+              trilen++;
+
+              tri[trilen][0] = ct0;
+              tri[trilen][1] = q;
+              tri[trilen][2] = ct2;
+              walk[trilen][0] = curtri;
+              walk[trilen][1] = trilen-1;
+              walk[trilen][2] = T2;
+              if (TE2 >= 0) walk[T2][TE2] = trilen;
+              trilen++;
+
+              // expand tri array if necessary
+              if (trilen > trisize) {
+                trisize += trisize;
+                int[][] newtri = new int[trisize+2][3];
+                int[][] newwalk = new int[trisize+2][3];
+                for (int i=0; i<trilen; i++) {
+                  newtri[i][0] = tri[i][0];
+                  newtri[i][1] = tri[i][1];
+                  newtri[i][2] = tri[i][2];
+                  newwalk[i][0] = walk[i][0];
+                  newwalk[i][1] = walk[i][1];
+                  newwalk[i][2] = walk[i][2];
+                }
+                for (int i=trilen; i<trisize+2; i++) {
+                  newwalk[i][0] = -1;
+                  newwalk[i][1] = -1;
+                  newwalk[i][2] = -1;
+                }
+                tri = newtri;
+                walk = newwalk;
+              }
+
+            }
+          }
+        }
+      }
+
+      // There is a third type of lost grid point.  If a point on
+      // the left or right edge of a grid is cut off from the rest
+      // of its grid by a zigzagging grid edge from a previous grid,
+      // that point is Type 3 lost and will not be incorporated into
+      // the triangulation.  This case is not yet handled by
+      // DelaunayOverlap's algorithm, but will be added to the
+      // constructor if data sets exhibit properties which cause
+      // the loss of many points.
+    }
+
+    // *** PHASE 5: *** finish the triangulation
+    // copy information into Tri and Walk arrays
+    Tri = new int[trilen][3];
+    Walk = new int[trilen][3];
+    for (int i=0; i<trilen; i++) {
+      Tri[i][0] = tri[i][0];
+      Tri[i][1] = tri[i][1];
+      Tri[i][2] = tri[i][2];
+      Walk[i][0] = walk[i][0];
+      Walk[i][1] = walk[i][1];
+      Walk[i][2] = walk[i][2];
+    }
+
+    // call more generic method for constructing Vertices and Edges arrays
+    finish_triang(samples);
+  }
+
+  private static final float[][] m_samples = {  // grid 1 x-coordinates
+                               { 65, 142, 215, 315, 373, 435,  39, 118,
+                                202, 320, 373, 455,  40, 114, 206, 307,
+                                384, 457,  66, 128, 208, 308, 380, 436,
+                                // grid 2 x-coordinates
+                                 83, 144, 201, 293, 354, 433,  60, 135,
+                                202, 285, 355, 456,  59, 136, 204, 284,
+                                362, 456,  75, 138, 207, 285, 363, 438,
+                                // grid 3 x-coordinates
+                                 90, 153, 217, 292, 358, 441,  61, 145,
+                                216, 292, 366, 452,  55, 143, 213, 295,
+                                373, 463,  80, 148, 217, 295, 375, 444},
+                                // grid 1 y-coordinates
+                               { 67,  87, 103, 104,  72,  42, 122, 148,
+                                160, 165, 156, 109, 212, 211, 203, 200,
+                                204, 211, 282, 263, 248, 243, 256, 287,
+                                // grid 2 y-coordinates
+                                166, 187, 201, 207, 185, 155, 230, 235,
+                                240, 241, 235, 210, 283, 275, 270, 267,
+                                273, 280, 338, 310, 299, 297, 305, 331,
+                                // grid 3 y-coordinates
+                                247, 270, 277, 276, 265, 233, 295, 319,
+                                325, 322, 306, 281, 368, 371, 368, 362,
+                                372, 376, 464, 431, 417, 418, 424, 455} };
+
+  private static final int m_lenx = 6;
+  private static final int m_leny = 4;
+  private static final int m_numgrids = 3;
+
+  private static final Color[] m_col =
+    {Color.black, Color.gray, Color.blue};
+
+  private static DelaunayOverlap delO = null;
+
+  /**
+   * run 'java visad.DelaunayOverlap' to test the DelaunayOverlap class
+   * @param argv command line arguments
+   * @throws VisADException a VisAD error occurred
+   */
+  public static void main(String[] argv) throws VisADException {
+
+    Frame frame = new Frame("DelaunayOverlap");
+    WindowListener l = new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    };
+    frame.addWindowListener(l);
+    frame.setSize(500, 600);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - 250,
+                      screenSize.height/2 - 300);
+
+    Panel big_panel = new Panel();
+    big_panel.setLayout(new GridLayout(1, 1));
+    frame.add(big_panel);
+
+    Canvas gcan = new Canvas() {
+      public void paint(Graphics gr) {
+        // draw triangulation
+        if (delO != null) {
+          gr.setColor(Color.red);
+          for (int t=0; t<delO.Tri.length; t++) {
+            for (int e=0; e<3; e++) {
+              int gx1 = (int)m_samples[0][delO.Tri[t][e]];
+              int gy1 = (int)m_samples[1][delO.Tri[t][e]];
+              int gx2 = (int)m_samples[0][delO.Tri[t][(e+1)%3]];
+              int gy2 = (int)m_samples[1][delO.Tri[t][(e+1)%3]];
+              gr.drawLine(gx1, gy1, gx2, gy2);
+            }
+          }
+        }
+
+        // plot points
+        int colnum = 0;
+        for (int p=0; p<m_numgrids*m_lenx*m_leny; p++) {
+          int x = (int)m_samples[0][p];
+          int y = (int)m_samples[1][p];
+          if (p % (m_lenx*m_leny) == 0) {
+            gr.setColor(m_col[colnum++]);
+          }
+          gr.fillRect(x-2, y-2, 4, 4);
+        }
+      }
+    };
+    big_panel.add(gcan);
+
+    System.out.println("Constructing triangulation...");
+    long startTime = System.currentTimeMillis();
+    delO = new DelaunayOverlap(m_samples, m_lenx, m_leny);
+    long endTime = System.currentTimeMillis();
+    frame.setVisible(true);
+    System.out.println("Algorithm completed successfully.");
+    float algTime = (float)(endTime-startTime) / 1000;
+    System.out.println("Triangulation took "+algTime+" seconds.");
+
+    // Test the triangulation for errors
+    System.out.println("Testing triangulation...");
+    boolean good = delO.test(m_samples);
+    if (!good) System.out.println("Errors in triangulation!");
+    else System.out.println("Triangulation was constructed correctly.");
+  }
+
+}
+
diff --git a/visad/DelaunayWatson.java b/visad/DelaunayWatson.java
new file mode 100644
index 0000000..1ca2860
--- /dev/null
+++ b/visad/DelaunayWatson.java
@@ -0,0 +1,367 @@
+//
+// DelaunayWatson.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+
+/* The Delaunay triangulation/tetrahedralization algorithm in this class is
+ * originally from nnsort.c by David F. Watson:
+ *
+ * nnsort() finds the Delaunay triangulation of the two- or three-component
+ * vectors in 'data_list' and returns a list of simplex vertices in
+ * 'vertices' with the corresponding circumcentre and squared radius in the
+ * rows of 'circentres'.  nnsort() also can be used to find the ordered
+ * convex hull of the two- or three-component vectors in 'data_list' and
+ * returns a list of (d-1)-facet vertices in 'vertices' (dummy filename for
+ * 'circentres' must be used).
+ * nnsort() was written by Dave Watson and uses the algorithm described in -
+ *    Watson, D.F., 1981, Computing the n-dimensional Delaunay tessellation
+ *          with application to Voronoi polytopes:
+ *                                      The Computer J., 24(2), p. 167-172.
+ *
+ * additional information about this algorithm can be found in -
+ *    CONTOURING: A guide to the analysis and display of spatial data,
+ *    by David F. Watson, Pergamon Press, 1992, ISBN 0 08 040286 0
+ *                                                                        */
+
+/**
+   DelaunayWatson represents an O(N^2) method with low overhead
+   to find the Delaunay triangulation or tetrahedralization of
+   a set of samples of R^2 or R^3.<P>
+*/
+public class DelaunayWatson extends Delaunay {
+
+  private static final float BIGNUM = (float) 1E37;
+  private static final float EPSILON = 0.00001f;
+  // temporary storage size factor
+  private static final int TSIZE = 75;
+  // factor (>=1) for radius of control points
+  private static final float RANGE = 10.0f;
+
+  /**
+   * construct a Delaunay triangulation of the points in the
+   * samples array using Watson's algorithm
+   * @param samples locations of points for topology - dimensioned
+   *                float[dimension][number_of_points]
+   * @throws VisADException a VisAD error occurred
+   */
+  public DelaunayWatson(float[][] samples) throws VisADException {
+    int dim = samples.length;
+    int nrs = samples[0].length;
+
+    float xx, yy, bgs;
+    int i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i11;
+    int[] ii = new int[3];
+    int dm, dim1, nts, tsz;
+
+    float[][] mxy = new float[2][dim];
+    for (i0=0; i0<dim; i0++) mxy[0][i0] = - (mxy[1][i0] = BIGNUM);
+    dim1 = dim + 1;
+    float[][] wrk = new float[dim][dim1];
+    for (i0=0; i0<dim; i0++) for (i1=0; i1<dim1; i1++) wrk[i0][i1] = -RANGE;
+    for (i0=0; i0<dim; i0++) wrk[i0][i0] = RANGE * (3 * dim - 1);
+
+    float[][] pts = new float[nrs + dim1][dim];
+    for (i0=0; i0<nrs; i0++) {
+      if (dim < 3) {
+        pts[i0][0] = samples[0][i0];
+        pts[i0][1] = samples[1][i0];
+      }
+      else {
+        pts[i0][0] = samples[0][i0];
+        pts[i0][1] = samples[1][i0];
+        pts[i0][2] = samples[2][i0];
+      }
+      // compute bounding box
+      for (i1=0; i1<dim; i1++) {
+        if (mxy[0][i1] < pts[i0][i1]) mxy[0][i1] = pts[i0][i1]; // max
+        if (mxy[1][i1] > pts[i0][i1]) mxy[1][i1] = pts[i0][i1]; // min
+      }
+    }
+
+    for (bgs=0, i0=0; i0<dim; i0++)  {
+      mxy[0][i0] -= mxy[1][i0];
+      if (bgs < mxy[0][i0]) bgs = mxy[0][i0];
+    }
+    // now bgs = largest range
+    // add random perturbations to points
+    bgs *= EPSILON;
+
+    Random rand = new Random(367);
+    for (i0=0; i0<nrs; i0++) for (i1=0; i1<dim; i1++) {
+      // random numbers [0, 1]
+      pts[i0][i1] += bgs * (0.5 - rand.nextDouble());
+    }
+    for (i0=0; i0<dim1; i0++) for (i1=0; i1<dim; i1++) {
+      pts[nrs+i0][i1] = mxy[1][i1] + wrk[i1][i0] * mxy[0][i1];
+    }
+    for (i1=1, i0=2; i0<dim1; i0++) i1 *= i0;
+    tsz = TSIZE * i1;
+    int[][] tmp = new int[tsz + 1][dim];
+    // storage allocation - increase value of `i1' for 3D if necessary
+    i1 *= (nrs + 50 * i1);
+/* WLH 4 Nov 97 */
+    if (dim == 3) i1 *= 10;
+/* end WLH 4 Nov 97 */
+    int[] id = new int[i1];
+    for (i0=0; i0<i1; i0++) id[i0] = i0;
+    int[][] a3s = new int[i1][dim1];
+    float[][] ccr = new float[i1][dim1];
+    for (a3s[0][0]=nrs, i0=1; i0<dim1; i0++) a3s[0][i0] = a3s[0][i0-1] + 1;
+    for (ccr[0][dim]=BIGNUM, i0=0; i0<dim; i0++) ccr[0][i0] = 0;
+    nts = i4 = 1;
+    dm = dim - 1;
+    for (i0=0; i0<nrs; i0++) {
+      i1 = i7 = -1;
+      i9 = 0;
+Loop3:
+      for (i11=0; i11<nts; i11++) {
+        i1++;
+        while (a3s[i1][0] < 0) i1++;
+        xx = ccr[i1][dim];
+        for (i2=0; i2<dim; i2++) {
+          xx -= (pts[i0][i2] - ccr[i1][i2]) * (pts[i0][i2] - ccr[i1][i2]);
+          if (xx<0) continue Loop3;
+        }
+        i9--;
+        i4--;
+        id[i4] = i1;
+Loop2:
+        for (i2=0; i2<dim1; i2++) {
+          ii[0] = 0;
+          if (ii[0] == i2) ii[0]++;
+          for (i3=1; i3<dim; i3++) {
+            ii[i3] = ii[i3-1] + 1;
+            if (ii[i3] == i2) ii[i3]++;
+          }
+          if (i7>dm) {
+            i8 = i7;
+Loop1:
+            for (i3=0; i3<=i8; i3++) {
+              for (i5=0; i5<dim; i5++) {
+                if (a3s[i1][ii[i5]] != tmp[i3][i5]) continue Loop1;
+              }
+              for (i6=0; i6<dim; i6++) tmp[i3][i6] = tmp[i8][i6];
+              i7--;
+              continue Loop2;
+            }
+          }
+          if (++i7 > tsz) {
+            int newtsz = 2 * tsz;
+            int[][] newtmp = new int[newtsz + 1][dim];
+            System.arraycopy(tmp, 0, newtmp, 0, tsz);
+            tsz = newtsz;
+            tmp = newtmp;
+            // WLH 23 july 97
+            // throw new VisADException(
+            //                "DelaunayWatson: Temporary storage exceeded");
+          }
+          for (i3=0; i3<dim; i3++) tmp[i7][i3] = a3s[i1][ii[i3]];
+        }
+        a3s[i1][0] = -1;
+      }
+      for (i1=0; i1<=i7; i1++) {
+        for (i2=0; i2<dim; i2++) {
+          for (wrk[i2][dim]=0, i3=0; i3<dim; i3++) {
+            wrk[i2][i3] = pts[tmp[i1][i2]][i3] - pts[i0][i3];
+            wrk[i2][dim] += wrk[i2][i3] * (pts[tmp[i1][i2]][i3]
+                                        + pts[i0][i3]) / 2;
+          }
+        }
+        if (dim < 3) {
+          xx = wrk[0][0] * wrk[1][1] - wrk[1][0] * wrk[0][1];
+          ccr[id[i4]][0] = (wrk[0][2] * wrk[1][1]
+                         - wrk[1][2] * wrk[0][1]) / xx;
+          ccr[id[i4]][1] = (wrk[0][0] * wrk[1][2]
+                         - wrk[1][0] * wrk[0][2]) / xx;
+        }
+        else {
+          xx = (wrk[0][0] * (wrk[1][1] * wrk[2][2] - wrk[2][1] * wrk[1][2]))
+             - (wrk[0][1] * (wrk[1][0] * wrk[2][2] - wrk[2][0] * wrk[1][2]))
+             + (wrk[0][2] * (wrk[1][0] * wrk[2][1] - wrk[2][0] * wrk[1][1]));
+          ccr[id[i4]][0] = ((wrk[0][3] * (wrk[1][1] * wrk[2][2]
+                           - wrk[2][1] * wrk[1][2]))
+                          - (wrk[0][1] * (wrk[1][3] * wrk[2][2]
+                           - wrk[2][3] * wrk[1][2]))
+                          + (wrk[0][2] * (wrk[1][3] * wrk[2][1]
+                           - wrk[2][3] * wrk[1][1]))) / xx;
+          ccr[id[i4]][1] = ((wrk[0][0] * (wrk[1][3] * wrk[2][2]
+                           - wrk[2][3] * wrk[1][2]))
+                          - (wrk[0][3] * (wrk[1][0] * wrk[2][2]
+                           - wrk[2][0] * wrk[1][2]))
+                          + (wrk[0][2] * (wrk[1][0] * wrk[2][3]
+                           - wrk[2][0] * wrk[1][3]))) / xx;
+          ccr[id[i4]][2] = ((wrk[0][0] * (wrk[1][1] * wrk[2][3]
+                           - wrk[2][1] * wrk[1][3]))
+                          - (wrk[0][1] * (wrk[1][0] * wrk[2][3]
+                           - wrk[2][0] * wrk[1][3]))
+                          + (wrk[0][3] * (wrk[1][0] * wrk[2][1]
+                           - wrk[2][0] * wrk[1][1]))) / xx;
+        }
+        for (ccr[id[i4]][dim]=0, i2=0; i2<dim; i2++) {
+          ccr[id[i4]][dim] += (pts[i0][i2] - ccr[id[i4]][i2])
+                            * (pts[i0][i2] - ccr[id[i4]][i2]);
+          a3s[id[i4]][i2] = tmp[i1][i2];
+        }
+        a3s[id[i4]][dim] = i0;
+        i4++;
+        i9++;
+      }
+      nts += i9;
+    }
+
+/* OUTPUT is in a3s ARRAY
+   needed output is:
+     Tri      - array of pointers from triangles or tetrahedra to their
+                corresponding vertices
+     Vertices - array of pointers from vertices to their
+                corresponding triangles or tetrahedra
+     Walk     - array of pointers from triangles or tetrahedra to neighboring
+                triangles or tetrahedra
+     Edges    - array of pointers from each triangle or tetrahedron's edges
+                to their corresponding triangles or tetrahedra
+
+   helpers:
+     nverts - number of triangles or tetrahedra per vertex
+*/
+
+    // compute number of triangles or tetrahedra
+    int[] nverts = new int[nrs];
+    for (int i=0; i<nrs; i++) nverts[i] = 0;
+    int ntris = 0;
+    i0 = -1;
+    for (i11=0; i11<nts; i11++) {
+      i0++;
+      while (a3s[i0][0] < 0) i0++;
+      if (a3s[i0][0] < nrs) {
+        ntris++;
+        if (dim < 3) {
+          nverts[a3s[i0][0]]++;
+          nverts[a3s[i0][1]]++;
+          nverts[a3s[i0][2]]++;
+        }
+        else {
+          nverts[a3s[i0][0]]++;
+          nverts[a3s[i0][1]]++;
+          nverts[a3s[i0][2]]++;
+          nverts[a3s[i0][3]]++;
+        }
+      }
+    }
+    Vertices = new int[nrs][];
+    for (int i=0; i<nrs; i++) Vertices[i] = new int[nverts[i]];
+    for (int i=0; i<nrs; i++) nverts[i] = 0;
+
+    // build Tri & Vertices components
+    Tri = new int[ntris][dim1];
+    int a, b, c, d;
+    int itri = 0;
+    i0 = -1;
+    for (i11=0; i11<nts; i11++) {
+      i0++;
+      while (a3s[i0][0] < 0) i0++;
+      if (a3s[i0][0] < nrs) {
+        if (dim < 3) {
+          a = a3s[i0][0];
+          b = a3s[i0][1];
+          c = a3s[i0][2];
+          Vertices[a][nverts[a]] = itri;
+          nverts[a]++;
+          Vertices[b][nverts[b]] = itri;
+          nverts[b]++;
+          Vertices[c][nverts[c]] = itri;
+          nverts[c]++;
+          Tri[itri][0] = a;
+          Tri[itri][1] = b;
+          Tri[itri][2] = c;
+        }
+        else {
+          a = a3s[i0][0];
+          b = a3s[i0][1];
+          c = a3s[i0][2];
+          d = a3s[i0][3];
+          Vertices[a][nverts[a]] = itri;
+          nverts[a]++;
+          Vertices[b][nverts[b]] = itri;
+          nverts[b]++;
+          Vertices[c][nverts[c]] = itri;
+          nverts[c]++;
+          Vertices[d][nverts[d]] = itri;
+          nverts[d]++;
+          Tri[itri][0] = a;
+          Tri[itri][1] = b;
+          Tri[itri][2] = c;
+          Tri[itri][3] = d;
+        }
+        itri++;
+      }
+    }
+
+    // call more generic method for constructing Walk and Edges arrays
+    finish_triang(samples);
+  }
+
+/*
+  DO NOT DELETE THIS COMMENTED CODE
+  IT CONTAINS ALGORITHM DETAILS NOT CAST INTO JAVA (YET?)
+  i0 = -1;
+  for (i11=0; i11<nts; i11++) {
+    i0++;
+    while (a3s[i0][0] < 0) i0++;
+    if (a3s[i0][0] < nrs) {
+      for (i1=0; i1<dim; i1++) for (i2=0; i2<dim; i2++) {
+        wrk[i1][i2] = pts[a3s[i0][i1]][i2] - pts[a3s[i0][dim]][i2];
+      }
+      if (dim < 3) {
+        xx = wrk[0][0] * wrk[1][1] - wrk[0][1] * wrk[1][0];
+        if (fabs(xx) > EPSILON) {
+          if (xx < 0)
+            fprintf(afile,"%d %d %d\n",a3s[i0][0],a3s[i0][2],a3s[i0][1]);
+          else fprintf(afile,"%d %d %d\n",a3s[i0][0],a3s[i0][1],a3s[i0][2]);
+          fprintf(bfile,"%e %e %e\n",ccr[i0][0],ccr[i0][1],ccr[i0][2]);
+        }
+      }
+      else {
+        xx = ((wrk[0][0] * (wrk[1][1] * wrk[2][2] - wrk[2][1] * wrk[1][2]))
+           -  (wrk[0][1] * (wrk[1][0] * wrk[2][2] - wrk[2][0] * wrk[1][2]))
+           +  (wrk[0][2] * (wrk[1][0] * wrk[2][1] - wrk[2][0] * wrk[1][1])));
+        if (fabs(xx) > EPSILON) {
+          if (xx < 0)
+            fprintf(afile,"%d %d %d %d\n",
+                    a3s[i0][0],a3s[i0][1],a3s[i0][3],a3s[i0][2]);
+          else fprintf(afile,"%d %d %d %d\n",
+                       a3s[i0][0],a3s[i0][1],a3s[i0][2],a3s[i0][3]);
+          fprintf(bfile,"%e %e %e %e\n",
+                  ccr[i0][0],ccr[i0][1],ccr[i0][2],ccr[i0][3]);
+        }
+      }
+    }
+  }
+*/
+
+}
+
diff --git a/visad/DerivedUnit.java b/visad/DerivedUnit.java
new file mode 100644
index 0000000..37fb4c4
--- /dev/null
+++ b/visad/DerivedUnit.java
@@ -0,0 +1,1249 @@
+//
+// DerivedUnit.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.io.Serializable;
+import java.text.ChoiceFormat;
+import java.util.Arrays;
+import java.util.Vector;
+
+/**
+ * A class that represents a unit consisting of zero or more base units.
+ * 
+ * Instances are immutable.
+ * 
+ * @author Steven R. Emmerson
+ * 
+ *         This is part of Steve Emmerson's Unit package that has been
+ *         incorporated into VisAD.
+ */
+public final class DerivedUnit extends Unit implements Serializable {
+	private static final long	serialVersionUID	= 1L;
+	/**
+	 * The array of individual base-unit factors that make up this unit.
+	 * 
+	 * The following is effectively "final" in that it is only set in
+	 * constructors and never altered. Unfortunately, the JDK 1.1.2 javac(1)
+	 * compiler on our SunOS 5.5 systems doesn't recognize this fact; hence, the
+	 * "final" is commented-out.
+	 */
+	/* final */Factor[]			factors;
+
+	/**
+	 * Construct a dimensionless derived unit. The identifier of the unit will
+	 * be empty.
+	 */
+	public DerivedUnit() {
+		this(new BaseUnit[] {}, new int[] {}, "");
+	}
+
+	/**
+	 * Construct a dimensionless derived unit with an identifier.
+	 * 
+	 * @param identifier
+	 *            Name or abbreviation for the unit. May be <code>null</code> or
+	 *            empty.
+	 */
+	public DerivedUnit(final String identifier) {
+		this(new BaseUnit[] {}, new int[] {}, identifier);
+	}
+
+	/**
+	 * Construct a derived unit from a base unit. The identifier of the unit
+	 * will be that of the base unit.
+	 * 
+	 * @param baseUnit
+	 *            The base unit.
+	 */
+	public DerivedUnit(final BaseUnit baseUnit) {
+		this(new BaseUnit[] { baseUnit }, new int[] { 1 }, baseUnit
+				.getIdentifier());
+	}
+
+	/**
+	 * Construct a derived unit from an array base units and powers. The
+	 * identifier of the unit with be that of the base unit if there's only one
+	 * base unit; otherwise, the identifier will be <code>null</code>.
+	 * 
+	 * @param baseUnits
+	 *            The array of base units (e.g. {m, s}).
+	 * @param powers
+	 *            The array of powers (e.g. {1, -1} to create a m/s unit).
+	 */
+	public DerivedUnit(final BaseUnit[] baseUnits, final int[] powers) {
+		this(newFactors(baseUnits, powers), baseUnits.length == 1
+				? baseUnits[0].getIdentifier()
+				: null);
+	}
+
+	/**
+	 * Construct a derived unit from an array base units, powers, and an
+	 * identifier.
+	 * 
+	 * @param baseUnits
+	 *            The array of base units (e.g. {m, s}).
+	 * @param powers
+	 *            The array of powers (e.g. {1, -1} to create a m/s unit).
+	 * @param identifier
+	 *            Name or abbreviation for the unit. May be <code>null</code> or
+	 *            empty.
+	 */
+	public DerivedUnit(final BaseUnit[] baseUnits, final int[] powers,
+			final String identifier) {
+		this(newFactors(baseUnits, powers), identifier);
+	}
+
+	/**
+	 * Creates an array of Factor-s from arrays of base units and powers.
+	 * 
+	 * @param baseUnits
+	 *            The array of base units (e.g. {m, s}).
+	 * @param powers
+	 *            The array of powers (e.g. {1, -1} to create a m/s unit).
+	 * @return An array of {@link Factor}s equivalent to the given base units
+	 *         and powers.
+	 */
+	protected static Factor[] newFactors(final BaseUnit[] baseUnits,
+			final int[] powers) {
+		final Factor[] factors = new Factor[baseUnits.length];
+		for (int i = 0; i < baseUnits.length; ++i) {
+			factors[i] = new Factor(baseUnits[i], powers[i]);
+		}
+		return factors;
+	}
+
+	/**
+	 * Construct a derived unit from a derived unit and an identifier.
+	 * 
+	 * @param that
+	 *            The derived unit.
+	 * @param identifier
+	 *            Name or abbreviation for the unit. May be <code>null</code> or
+	 *            empty.
+	 */
+	public DerivedUnit(final DerivedUnit that, final String identifier) {
+		this(that.factors, identifier);
+	}
+
+	/**
+	 * Constructs from an array of factors. The identifier of the unit will be
+	 * <code>null</code>.
+	 * 
+	 * @param facts
+	 *            The factors for the DerivedUnit. Every factor with a power of
+	 *            zero will be ignored.
+	 */
+	private DerivedUnit(final Factor[] facts) {
+		this(facts, null);
+	}
+
+	/**
+	 * Constructs from an array of factors and an identifier.
+	 * 
+	 * @param facts
+	 *            The factors for the DerivedUnit. Every factor with a power of
+	 *            zero will be ignored.
+	 * @param identifier
+	 *            Name or abbreviation for the unit. May be <code>null</code> or
+	 *            empty.
+	 */
+	private DerivedUnit(final Factor[] facts, final String identifier) {
+		super(identifier);
+		int n = 0;
+		for (int i = 0; i < facts.length; ++i) {
+			final Factor fact = facts[i];
+			if (fact != null && fact.power != 0) {
+				n++;
+			}
+		}
+		factors = new Factor[n];
+		n = 0;
+		for (int i = 0; i < facts.length; ++i) {
+			final Factor fact = facts[i];
+			if (fact != null && fact.power != 0) {
+				factors[n++] = fact;
+			}
+		}
+	}
+
+	/**
+	 * <p>
+	 * Indicates if this instance is dimensionless. A unit is dimensionless if
+	 * it is a measure of a dimensionless quantity like angle or concentration.
+	 * Examples of dimensionless units include radian, degree, steradian, and
+	 * "g/kg".
+	 * </p>
+	 * 
+	 * @return True if an only if this unit is dimensionless.
+	 */
+	@Override
+	public boolean isDimensionless() {
+		for (int i = 0; i < factors.length; i++) {
+			if (factors[i].power != 0 && !factors[i].baseUnit.isDimensionless()) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Clones this unit, changing the identifier.
+	 * 
+	 * @param identifier
+	 *            The name or abbreviation for the cloned unit. May be
+	 *            <code>null</code> or empty.
+	 * @return A unit equal to this unit but with the given identifier.
+	 */
+	@Override
+	protected Unit protectedClone(final String identifier) {
+		return new DerivedUnit(this, identifier);
+	}
+
+	/**
+	 * Scale this unit by an amount.
+	 * 
+	 * @param amount
+	 *            The amount by which to scale this unit. E.g. Unit yard =
+	 *            meter.scale(0.9144);
+	 * @return A unit equal this this unit scaled by the given amount.
+	 * @exception UnitException
+	 *                This unit cannot be scaled.
+	 */
+	@Override
+	public Unit scale(final double amount) throws UnitException {
+		return ScaledUnit.getInstance(amount, this);
+	}
+
+	/**
+	 * Shift this unit by an amount.
+	 * 
+	 * @param offset
+	 *            The amount by which to shift this unit. E.g. Unit celsius =
+	 *            kelvin.shift(273.15);
+	 * @return A unit equal to this unit with the origin shifted to the given
+	 *         point.
+	 * @exception UnitException
+	 *                The unit subclass is unknown.
+	 */
+	@Override
+	public Unit shift(final double offset) throws UnitException {
+		return OffsetUnit.getInstance(offset, this);
+	}
+
+	@Override
+	public Unit log(final double base) {
+		return LogarithmicUnit.getInstance(base, this);
+	}
+
+	/**
+	 * Raise a derived unit to a power.
+	 * 
+	 * @param power
+	 *            The power to raise this unit by.
+	 * @return The unit resulting from raising this unit to <code>power</code>.
+	 * @promise The unit has not been modified.
+	 */
+	@Override
+	public Unit pow(final int power) {
+		DerivedUnit result;
+		if (power == 1) {
+			result = this;
+		}
+		else {
+			final Factor[] newFactors = new Factor[factors.length];
+			for (int i = 0; i < factors.length; ++i) {
+				final Factor factor = factors[i];
+				newFactors[i] = new Factor(factor.baseUnit, factor.power
+						* power);
+			}
+			result = new DerivedUnit(newFactors);
+		}
+		return result;
+	}
+
+	/**
+	 * Returns the N-th root of this unit.
+	 * 
+	 * @param root
+	 *            The root to take (e.g. 2 means square root). May not be zero.
+	 * @return The unit corresponding to the <code>root</code>-th root of this
+	 *         unit.
+	 * @promise The unit has not been modified.
+	 * @throws IllegalArgumentException
+	 *             <code>root</code> is zero or the result would have a
+	 *             non-integral unit dimension.
+	 */
+	@Override
+	public Unit root(final int root) throws IllegalArgumentException {
+		if (root == 0) {
+			throw new IllegalArgumentException(getClass().getName()
+					+ ".root(int): zero root");
+		}
+		DerivedUnit result;
+		if (root == 1) {
+			result = this;
+		}
+		else {
+			final Factor[] newFactors = new Factor[factors.length];
+			for (int i = 0; i < factors.length; ++i) {
+				final Factor factor = factors[i];
+				if (factor.power % root != 0) {
+					throw new IllegalArgumentException(getClass().getName()
+							+ ".root(int): "
+							+ "Non-integral resulting dimension");
+				}
+				newFactors[i] = new Factor(factor.baseUnit, factor.power / root);
+			}
+			result = new DerivedUnit(newFactors);
+		}
+		return result;
+	}
+
+	/**
+	 * Raise a derived unit to a power.
+	 * 
+	 * @param power
+	 *            The power to raise this unit by. If this unit is not
+	 *            dimensionless, then the value must be an integer or the
+	 *            reciprocal of an integer.
+	 * @return The unit resulting from raising this unit to <code>power</code>.
+	 * @throws IllegalArgumentException
+	 *             This unit is not dimensionless and <code>power
+     *                          </code>
+	 *             is neither an integer nor the reciprocal of an integer.
+	 * @promise The unit has not been modified.
+	 */
+	@Override
+	public Unit pow(final double power) throws IllegalArgumentException {
+		Unit result;
+		if (factors.length == 0) {
+			result = this;
+		}
+		else {
+			if (Math.abs(power) > 1) {
+				final double intVal = Math.rint(power);
+				if (power < ChoiceFormat.previousDouble(intVal)
+						|| power > ChoiceFormat.nextDouble(intVal)) {
+					throw new IllegalArgumentException(this.getClass()
+							.getName()
+							+ ".pow(double): " + "Non-integral power");
+				}
+				result = pow((int) intVal);
+			}
+			else {
+				final double root = 1. / power;
+				final double intVal = Math.rint(root);
+				if (root < ChoiceFormat.previousDouble(intVal)
+						|| root > ChoiceFormat.nextDouble(intVal)) {
+					throw new IllegalArgumentException(this.getClass()
+							.getName()
+							+ ".pow(double): "
+							+ "Non-integral reciprocal power");
+				}
+				result = root((int) intVal);
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Return the definition of this unit.
+	 * 
+	 * @return The definition of this unit (e.g. "m.s-1").
+	 */
+	@Override
+	public String getDefinition() {
+		String definition;
+
+		if (factors == null) {
+			/* Probably exception thrown during construction */
+			definition = "<unconstructed DerivedUnit>";
+		}
+		else {
+			final StringBuffer buf = new StringBuffer(80);
+
+			for (int i = 0; i < factors.length; ++i) {
+				if (factors[i].power == 1) {
+					buf.append(factors[i].baseUnit.toString() + ".");
+				}
+				else if (factors[i].power != 0) {
+					buf.append(factors[i].baseUnit.toString()
+							+ factors[i].power + ".");
+				}
+			}
+
+			if (buf.length() > 0) {
+				buf.setLength(buf.length() - 1);
+			}
+
+			definition = buf.toString();
+		}
+		return definition;
+	}
+
+	private static void myAssert(final boolean assertion) {
+		if (!assertion) {
+			throw new AssertionError();
+		}
+	}
+
+	private static void myAssert(final Unit have, final Unit expect) {
+		if (!have.equals(expect)) {
+			throw new AssertionError(have.toString() + " != " + expect);
+		}
+	}
+
+	private static void myAssert(final double have, final double expect) {
+		if (have != expect) {
+			throw new AssertionError("" + have + " != " + expect);
+		}
+	}
+
+	private static void myAssert(final double[] have, final double[] expect) {
+		if (!Arrays.equals(have, expect)) {
+			throw new AssertionError("" + have + " != " + expect);
+		}
+	}
+
+	/**
+	 * Test this class.
+	 * 
+	 * @param args
+	 *            Arguments (ignored).
+	 * @exception UnitException
+	 *                A problem occurred.
+	 */
+	public static void main(final String[] args) throws UnitException {
+		final BaseUnit meter = SI.meter;
+		final BaseUnit second = SI.second;
+		final DerivedUnit speed = new DerivedUnit(new BaseUnit[] { meter,
+				second }, new int[] { 1, -1 });
+		final Unit speed2 = speed.pow(2);
+
+		myAssert(!speed.equals(meter));
+		myAssert(!speed.equals(second));
+		myAssert(!speed.isConvertible(meter));
+		myAssert(!speed.isConvertible(second));
+
+		myAssert(speed, meter.divide(second));
+		myAssert(!speed.equals(speed2));
+		myAssert(!speed.isConvertible(speed2));
+		myAssert(speed, speed2.sqrt());
+		myAssert(speed, speed2.root(2));
+		myAssert(speed.pow(2.0 + Math.ulp(2.0)), speed2);
+		myAssert(speed2.pow(1 / (2.0 + Math.ulp(2.0))), speed);
+
+		myAssert(meter.divide(speed), second);
+		myAssert(speed.divide(meter), second.pow(-1));
+
+		myAssert(speed.toThis(5, speed), 5);
+		myAssert(speed.toThat(5, speed), 5);
+
+		final double[] values = { 1, 2 };
+
+		myAssert(speed.toThis(values, speed), values);
+		myAssert(speed.toThat(values, speed), values);
+
+		final DerivedUnit joule = (DerivedUnit) speed.pow(2).multiply(
+				SI.kilogram);
+
+		myAssert(joule.equals(joule));
+		myAssert(joule.isConvertible(joule));
+		myAssert(!joule.equals(meter));
+		myAssert(!joule.isConvertible(meter));
+
+		System.out.println("Checking exceptions:");
+		try {
+			speed.toThis(5, joule);
+			throw new AssertionError();
+		}
+		catch (final UnitException e) {
+			System.out.println(e.getMessage());
+		}
+		try {
+			System.out.println("speed.pow(2+2*ULP)=\""
+					+ speed.pow(ChoiceFormat.nextDouble(ChoiceFormat
+							.nextDouble(2.0))) + "\"");
+			throw new AssertionError();
+		}
+		catch (final IllegalArgumentException e) {
+			System.out.println(e.getMessage());
+		}
+		try {
+			System.out.println("speed2.pow(1/(2+2*ULP))=\""
+					+ speed2.pow(1 / ChoiceFormat.nextDouble(ChoiceFormat
+							.nextDouble(2.0))) + "\"");
+			throw new AssertionError();
+		}
+		catch (final IllegalArgumentException e) {
+			System.out.println(e.getMessage());
+		}
+		System.out.println("Done");
+	}
+
+	/*
+	 * PROMISE: None of the returned factors will have a power of zero and input
+	 * dimensionless factors will be ignored and not appear in the output.
+	 */
+	private Vector[] common(final DerivedUnit that) {
+		final Vector[] vector = new Vector[3];
+		final int max = factors.length + that.factors.length;
+
+		vector[0] = new Vector(max);
+		vector[1] = new Vector(max);
+		vector[2] = new Vector(max);
+
+		for (int i = 0; i < factors.length; ++i) {
+			if (factors[i].power != 0 && !factors[i].baseUnit.isDimensionless()) {
+				int j;
+
+				for (j = 0; j < that.factors.length; ++j) {
+					if (that.factors[j].power != 0
+							&& factors[i].baseUnit
+									.equals(that.factors[j].baseUnit)) {
+						vector[2].addElement(new Factor[] { factors[i],
+								that.factors[j] });
+						break;
+					}
+				}
+				if (j == that.factors.length) {
+					vector[0].addElement(factors[i]);
+				}
+			}
+		}
+
+		for (int j = 0; j < that.factors.length; ++j) {
+			if (that.factors[j].power != 0
+					&& !that.factors[j].baseUnit.isDimensionless()) {
+				int i;
+
+				for (i = 0; i < factors.length; ++i) {
+					if (factors[i].power != 0
+							&& factors[i].baseUnit
+									.equals(that.factors[j].baseUnit)) {
+						break;
+					}
+				}
+				if (i == factors.length) {
+					vector[1].addElement(that.factors[j]);
+				}
+			}
+		}
+
+		return vector;
+	}
+
+	static abstract class Op {
+		public DerivedUnit multOp(final DerivedUnit d1, final DerivedUnit d2) {
+			final Vector[] comm = d1.common(d2);
+			final int n0 = comm[0].size();
+			final int n1 = comm[1].size();
+			final int n2 = comm[2].size();
+			final Factor[] factors = new Factor[n0 + n1 + n2];
+			int k = 0;
+
+			for (int i = 0; i < n0; ++i) {
+				factors[k++] = (Factor) comm[0].elementAt(i);
+			}
+
+			for (int i = 0; i < n1; ++i) {
+				factors[k++] = op((Factor) comm[1].elementAt(i));
+			}
+
+			for (int i = 0; i < n2; ++i) {
+				final Factor[] facts = (Factor[]) comm[2].elementAt(i);
+				factors[k++] = op(facts[0], facts[1]);
+			}
+
+			return new DerivedUnit(factors);
+		}
+
+		protected abstract Factor op(Factor factor);
+
+		protected abstract Factor op(Factor f1, Factor f2);
+	}
+
+	private static final class AddPow extends Op {
+		@Override
+		protected Factor op(final Factor factor) {
+			return factor;
+		}
+
+		@Override
+		protected Factor op(final Factor f1, final Factor f2) {
+			return new Factor(f1.baseUnit, f1.power + f2.power);
+		}
+	}
+
+	private static final class SubPow extends Op {
+		@Override
+		protected Factor op(final Factor factor) {
+			return new Factor(factor.baseUnit, -factor.power);
+		}
+
+		@Override
+		protected Factor op(final Factor f1, final Factor f2) {
+			return new Factor(f1.baseUnit, f1.power - f2.power);
+		}
+	}
+
+	/**
+	 * Instantiations of the inner "helper" classes.
+	 */
+	private static AddPow	addPow	= new AddPow();
+	private static SubPow	subPow	= new SubPow();
+
+	/**
+	 * Multiply a derived unit by another unit.
+	 * 
+	 * @param that
+	 *            The unit with which to multiply this unit.
+	 * @return The product of the two units.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             Meaningless operation.
+	 */
+	@Override
+	public Unit multiply(final Unit that) throws UnitException {
+		return that instanceof DerivedUnit
+				? multiply((DerivedUnit) that)
+				: that.multiply(this);
+	}
+
+	/**
+	 * Multiply a derived unit by a derived unit.
+	 * 
+	 * @param that
+	 *            The derived unit with which to multiply this unit.
+	 * @return The product of the two units.
+	 * @promise Neither unit has been modified.
+	 */
+	public Unit multiply(final DerivedUnit that) {
+		return addPow.multOp(this, that);
+	}
+
+	/**
+	 * Divide a derived unit by another unit.
+	 * 
+	 * @param that
+	 *            The unit to divide into this unit.
+	 * @return The quotient of the two units.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             Meaningless operation.
+	 */
+	@Override
+	public Unit divide(final Unit that) throws UnitException {
+		return that instanceof DerivedUnit
+				? divide((DerivedUnit) that)
+				: that.divideInto(this);
+	}
+
+	/**
+	 * Divide a derived unit by a derived unit.
+	 * 
+	 * @param that
+	 *            The derived unit to divide into this unit.
+	 * @return The quotient of the two units.
+	 * @promise Neither unit has been modified.
+	 */
+	Unit divide(final DerivedUnit that) {
+		return subPow.multOp(this, that);
+	}
+
+	/**
+	 * Divide a derived unit into another unit.
+	 * 
+	 * @param that
+	 *            The unit to be divided by this unit.
+	 * @return The quotient of the two units.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             Meaningless operation.
+	 */
+	@Override
+	protected Unit divideInto(final Unit that) throws UnitException {
+		return that.divide(this);
+	}
+
+	/**
+	 * Indicate whether or not this unit has the same dimensionality as a
+	 * derived unit.
+	 * 
+	 * @param that
+	 *            The derived unit.
+	 * @return <code>true</code> if and only if both units have the same
+	 *         dimensionality (e.g. Length/Time).
+	 */
+	boolean sameDimensionality(final DerivedUnit that) {
+		final Vector[] comm = common(that);
+
+		if (comm[0].size() != 0 || comm[1].size() != 0) {
+			return false;
+		}
+
+		final int n2 = comm[2].size();
+
+		for (int i = 0; i < n2; ++i) {
+			final Factor[] factors = (Factor[]) comm[2].elementAt(i);
+
+			if (factors[0].power != factors[1].power) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Indicate whether or not this unit has the reciprocal dimensionality of a
+	 * derived unit.
+	 * 
+	 * @param that
+	 *            The derived unit.
+	 * @return <code>true</code> if and only if the unit dimensionalities are
+	 *         reciprocals of each other (e.g. Length/Time and Time/Length).
+	 */
+	boolean reciprocalDimensionality(final DerivedUnit that) {
+		final Vector[] comm = common(that);
+
+		if (comm[0].size() != 0 || comm[1].size() != 0) {
+			return false;
+		}
+
+		final int n2 = comm[2].size();
+
+		for (int i = 0; i < n2; ++i) {
+			final Factor[] factors = (Factor[]) comm[2].elementAt(i);
+
+			if (factors[0].power != -factors[1].power) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Convert values to this unit from another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public double[] toThis(final double[] values, final Unit that)
+			throws UnitException {
+		return toThis(values, that, true);
+	}
+
+	/**
+	 * Convert values to this unit from another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public float[] toThis(final float[] values, final Unit that)
+			throws UnitException {
+		return toThis(values, that, true);
+	}
+
+	/**
+	 * Convert values to this unit from another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public double[] toThis(final double[] values, final Unit that,
+			final boolean copy) throws UnitException {
+		if (that instanceof PromiscuousUnit) {
+			final double[] newValues = (copy)
+					? (double[]) values.clone()
+					: values;
+			return newValues;
+		}
+		return that instanceof DerivedUnit
+				? toThis(values, (DerivedUnit) that, copy)
+				: that.toThat(values, this);
+	}
+
+	/**
+	 * Convert values to this unit from another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public float[] toThis(final float[] values, final Unit that,
+			final boolean copy) throws UnitException {
+		if (that instanceof PromiscuousUnit) {
+			final float[] newValues = (copy)
+					? (float[]) values.clone()
+					: values;
+			return newValues;
+		}
+		return that instanceof DerivedUnit
+				? toThis(values, (DerivedUnit) that, copy)
+				: that.toThat(values, this);
+	}
+
+	/**
+	 * Convert values to this unit from a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	double[] toThis(final double[] values, final DerivedUnit that)
+			throws UnitException {
+		return toThis(values, that, true);
+	}
+
+	/**
+	 * Convert values to this unit from a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	float[] toThis(final float[] values, final DerivedUnit that)
+			throws UnitException {
+		return toThis(values, that, true);
+	}
+
+	/**
+	 * Convert values to this unit from a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	double[] toThis(final double[] values, final DerivedUnit that,
+			final boolean copy) throws UnitException {
+		double[] newValues;
+
+		if (sameDimensionality(that)) {
+			newValues = (copy)
+					? (double[]) values.clone()
+					: values;
+
+		}
+		else if (reciprocalDimensionality(that)) {
+			newValues = (copy)
+					? (double[]) values.clone()
+					: values;
+
+			for (int i = 0; i < values.length; ++i) {
+				if (values[i] == values[i]) {
+					newValues[i] = 1.0 / values[i];
+				}
+				else {
+					newValues[i] = Double.NaN;
+				}
+			}
+		}
+		else {
+			throw new UnitException("Attempt to convert from unit \"" + that
+					+ "\" to unit \"" + this + "\"");
+		}
+
+		return newValues;
+	}
+
+	/**
+	 * Convert values to this unit from a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted.
+	 * @param that
+	 *            The unit of <code>values</code>.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values in units of this unit.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	float[] toThis(final float[] values, final DerivedUnit that,
+			final boolean copy) throws UnitException {
+		float[] newValues;
+
+		if (sameDimensionality(that)) {
+			newValues = (copy)
+					? (float[]) values.clone()
+					: values;
+		}
+		else if (reciprocalDimensionality(that)) {
+			newValues = (copy)
+					? (float[]) values.clone()
+					: values;
+
+			for (int i = 0; i < values.length; ++i) {
+				newValues[i] = 1.0f / values[i];
+				if (values[i] == values[i]) {
+					newValues[i] = 1.0f / values[i];
+				}
+				else {
+					newValues[i] = Float.NaN;
+				}
+			}
+		}
+		else {
+			throw new UnitException("Attempt to convert from unit \"" + that
+					+ "\" to unit \"" + this + "\"");
+		}
+
+		return newValues;
+	}
+
+	/**
+	 * Convert values from this unit to another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public double[] toThat(final double values[], final Unit that)
+			throws UnitException {
+		return toThat(values, that, true);
+	}
+
+	/**
+	 * Convert values from this unit to another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public float[] toThat(final float values[], final Unit that)
+			throws UnitException {
+		return toThat(values, that, true);
+	}
+
+	/**
+	 * Convert values from this unit to a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	double[] toThat(final double values[], final DerivedUnit that)
+			throws UnitException {
+		return that.toThis(values, this, true);
+	}
+
+	/**
+	 * Convert values from this unit to a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	float[] toThat(final float values[], final DerivedUnit that)
+			throws UnitException {
+		return that.toThis(values, this, true);
+	}
+
+	/**
+	 * Convert values from this unit to another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public double[] toThat(final double values[], final Unit that,
+			final boolean copy) throws UnitException {
+		if (that instanceof PromiscuousUnit) {
+			final double[] newValues = (copy)
+					? (double[]) values.clone()
+					: values;
+			return newValues;
+		}
+		return that instanceof DerivedUnit
+				? toThat(values, (DerivedUnit) that, copy)
+				: that.toThis(values, this, copy);
+	}
+
+	/**
+	 * Convert values from this unit to another unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	@Override
+	public float[] toThat(final float values[], final Unit that,
+			final boolean copy) throws UnitException {
+		if (that instanceof PromiscuousUnit) {
+			final float[] newValues = (copy)
+					? (float[]) values.clone()
+					: values;
+			return newValues;
+		}
+		return that instanceof DerivedUnit
+				? toThat(values, (DerivedUnit) that, copy)
+				: that.toThis(values, this, copy);
+	}
+
+	/**
+	 * Convert values from this unit to a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	double[] toThat(final double values[], final DerivedUnit that,
+			final boolean copy) throws UnitException {
+		return that.toThis(values, this, copy);
+	}
+
+	/**
+	 * Convert values from this unit to a derived unit.
+	 * 
+	 * @param values
+	 *            The values to be converted in units of this unit.
+	 * @param that
+	 *            The unit to which to convert the values.
+	 * @param copy
+	 *            if false and <code>that</code> equals this, return
+	 *            <code>values</code>, else return a new array
+	 * @return The converted values.
+	 * @require The units are convertible.
+	 * @promise Neither unit has been modified.
+	 * @throws UnitException
+	 *             The units are not convertible.
+	 */
+	float[] toThat(final float values[], final DerivedUnit that,
+			final boolean copy) throws UnitException {
+		return that.toThis(values, this, copy);
+	}
+
+	/**
+	 * Indicate whether this unit is convertible with another unit. If one unit
+	 * is convertible with another, then the <code>toThis(...)</code>/ and
+	 * <code>toThat(...)</code> methods will not throw a UnitException. Unit A
+	 * is convertible with unit B if and only if unit B is convertible with unit
+	 * A; hence, calling-order is irrelevant.
+	 * 
+	 * @param unit
+	 *            The other unit.
+	 * @return True if and only if this unit is convertible with the other unit.
+	 */
+	@Override
+	public boolean isConvertible(final Unit unit) {
+		boolean isConvertible;
+		if (unit == null) {
+			isConvertible = false;
+		}
+		else {
+			if (unit instanceof DerivedUnit) {
+				final DerivedUnit that = (DerivedUnit) unit;
+				isConvertible = sameDimensionality(that)
+						|| reciprocalDimensionality(that);
+			}
+			else {
+				isConvertible = unit.isConvertible(this);
+			}
+		}
+		return isConvertible;
+	}
+
+	// added by WLH 11 Feb 98
+	/**
+	 * Indicates if this instance equals a unit.
+	 * 
+	 * @param unit
+	 *            The unit.
+	 * @return <code>true</code> if and only if this instance equals the unit.
+	 */
+	@Override
+	public boolean equals(final Unit unit) {
+		if (this == unit) {
+			return true;
+		}
+		if (!(unit instanceof DerivedUnit)) {
+			return unit == null ? false : unit.equals(this);
+		}
+		final int n = factors.length;
+		if (n != ((DerivedUnit) unit).factors.length) {
+			return false;
+		}
+		final boolean[] mark = new boolean[n];
+		for (int j = 0; j < n; j++) {
+			mark[j] = false;
+		}
+		for (int i = 0; i < n; i++) {
+			for (int j = 0; j < n; j++) {
+				if (!mark[j]) {
+					if (factors[i].equals(((DerivedUnit) unit).factors[j])) {
+						mark[j] = true;
+						break;
+					}
+				}
+			}
+		}
+		for (int j = 0; j < n; j++) {
+			if (!mark[j]) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Returns the hash code of this instance. {@link Object#hashCode()} should
+	 * be overridden whenever {@link Object#equals(Object)} is.
+	 * 
+	 * @return The hash code of this instance (includes the values).
+	 */
+	@Override
+	public int hashCode() {
+		if (hashCode == 0) {
+			for (int i = 0; i < factors.length; i++) {
+				hashCode ^= factors[i].hashCode();
+			}
+		}
+		return hashCode;
+	}
+
+	@Override
+	public DerivedUnit getDerivedUnit() {
+		return this;
+	}
+}
diff --git a/visad/Display.java b/visad/Display.java
new file mode 100644
index 0000000..cefda79
--- /dev/null
+++ b/visad/Display.java
@@ -0,0 +1,591 @@
+//
+// Display.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   Display is the VisAD interface for displays.  It is runnable.<P>
+
+   The display architecture is based on three goals:<P>
+<OL>
+
+<LI>Display data according to a set of mappings from RealType's
+    (e.g., Latitude, Time, Pressure) to DisplayRealTypes (e.g.,
+    XAxis, RGB, Animation).<P>
+
+<LI>Allow user extensions, to define new DisplayRealTypes,
+    new DisplayTupleTypes (and hence new display
+    CoordinateSystems), and new rendering algorithms.<P>
+
+<LI>Support direct manipulation, where users modify Data
+    values by re-drawing their depictions.<P>
+
+</OL>
+*/
+public interface Display extends Action {
+
+  // system intrinsic DisplayRealType objects
+
+  /** display spatial Cartesian X axis coordinate */
+  DisplayRealType XAxis =
+    new DisplayRealType("XAxis", true, -1.0, 1.0, 0.0, true);
+
+  /** display spatial Cartesian Y axis coordinate */
+  DisplayRealType YAxis =
+    new DisplayRealType("YAxis", true, -1.0, 1.0, 0.0, true);
+
+  /** display spatial Cartesian Z axis coordinate */
+  DisplayRealType ZAxis =
+    new DisplayRealType("ZAxis", true, -1.0, 1.0, 0.0, true);
+
+
+  /** display spatial spherical Latitude coordinate */
+  DisplayRealType Latitude =
+    new DisplayRealType("Latitude", true, -90.0, 90.0, 0.0,
+                        CommonUnit.degree, true);
+
+  /** display spatial spherical Longitude coordinate */
+  DisplayRealType Longitude =
+    new DisplayRealType("Longitude", true, 0.0, 360.0, 0.0,
+                        CommonUnit.degree, true);
+
+  /** display spatial spherical Radius coordinate */
+  DisplayRealType Radius =
+    new DisplayRealType("Radius", true, 0.01, 2.0, 1.0, true);
+
+
+  /** display spatial cylindrical radius coordinate */
+  DisplayRealType CylRadius =
+    new DisplayRealType("CylRadius", true, 0.01, 2.0, 1.0, true);
+
+  /** display spatial cylindrical azimuth coordinate */
+  DisplayRealType CylAzimuth =
+    new DisplayRealType("CylAzimuth", true, 0.0, 360.0, 0.0,
+                        CommonUnit.degree, true);
+
+  /** display spatial cylindrical Z axis coordinate */
+  DisplayRealType CylZAxis =
+    new DisplayRealType("CylZAxis", true, -1.0, 1.0, 0.0, true);
+
+
+  /** display spatial offset Cartesian X axis coordinate */
+  DisplayRealType XAxisOffset =
+    new DisplayRealType("XAxisOffset", false, -1.0, 1.0, 0.0, null, true);
+
+  /** display spatial offset Cartesian Y axis coordinate */
+  DisplayRealType YAxisOffset =
+    new DisplayRealType("YAxisOffset", false, -1.0, 1.0, 0.0, null, true);
+
+  /** display spatial offset Cartesian Z axis coordinate */
+  DisplayRealType ZAxisOffset =
+    new DisplayRealType("ZAxisOffset", false, -1.0, 1.0, 0.0, null, true);
+
+
+  /** list display scalar (not used) */
+  DisplayRealType List =
+    new DisplayRealType("List", false, 0.0, true);
+
+  /** display color red coordinate (in RGB) */
+  DisplayRealType Red =
+    new DisplayRealType("Red", false, 0.0, 1.0, 1.0, null, true);
+
+  /** display color green coordinate (in RGB) */
+  DisplayRealType Green =
+    new DisplayRealType("Green", false, 0.0, 1.0, 1.0, null, true);
+
+  /** display color blue coordinate (in RGB) */
+  DisplayRealType Blue =
+    new DisplayRealType("Blue", false, 0.0, 1.0, 1.0, null, true);
+
+
+  /** display color RGB lookup table index */
+  DisplayRealType RGB =
+    new DisplayRealType("RGB", false, 0.0, 1.0, 0.0, true);
+
+
+  /** display color RGBA lookup table index */
+  DisplayRealType RGBA =
+    new DisplayRealType("RGBA", false, 0.0, 1.0, 0.0, true);
+
+
+  /** display color hue coordinate (in HSV) */
+  DisplayRealType Hue =
+    new DisplayRealType("Hue", false, 0.0, 360.0, 0.0,
+                       CommonUnit.degree, true);
+
+  /** display color saturation coordinate (in HSV) */
+  DisplayRealType Saturation =
+    new DisplayRealType("Saturation", false, 0.0, 1.0, 0.0, null, true);
+
+  /** display color value coordinate (in HSV) */
+  DisplayRealType Value =
+    new DisplayRealType("Value", false, 0.0, 1.0, 1.0, null, true);
+
+
+  /** display color HSV lookup table index */
+  DisplayRealType HSV =
+    new DisplayRealType("HSV", false, 0.0, 1.0, 0.0, true);
+
+
+  /** display color cyan coordinate (in CMY) */
+  DisplayRealType Cyan =
+    new DisplayRealType("Cyan", false, 0.0, 1.0, 1.0, null, true);
+
+  /** display color magenta coordinate (in CMY) */
+  DisplayRealType Magenta =
+    new DisplayRealType("Magenta", false, 0.0, 1.0, 1.0, null, true);
+
+  /** display color yellow coordinate (in CMY) */
+  DisplayRealType Yellow =
+    new DisplayRealType("Yellow", false, 0.0, 1.0, 1.0, null, true);
+
+
+  /** display color CMY lookup table index */
+  DisplayRealType CMY =
+    new DisplayRealType("CMY", false, 0.0, 1.0, 0.0, true);
+
+
+  /** display alpha (transparency) */
+  DisplayRealType Alpha =
+    new DisplayRealType("Alpha", false, 0.0, 1.0, 1.0, null, true);
+
+
+  /** display animation */
+  DisplayRealType Animation =
+    new DisplayRealType("Animation", true, 0.0, true);
+
+
+  /** display scalar for selecting by a single value */
+  DisplayRealType SelectValue =
+    new DisplayRealType("SelectValue", false, 0.0, true);
+
+
+  /** display scalar for selecting by a range of values */
+  DisplayRealType SelectRange =
+    new DisplayRealType("SelectRange", false, 0.0, true);
+
+
+  /** display iso-contour */
+  DisplayRealType IsoContour =
+    new DisplayRealType("IsoContour", false, 0.0, true);
+
+
+  /** display flow set 1 Cartesian X axis coordinate */
+  DisplayRealType Flow1X =
+    new DisplayRealType("Flow1X", true, -1.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+  /** display flow set 1 Cartesian Y axis coordinate */
+  DisplayRealType Flow1Y =
+    new DisplayRealType("Flow1Y", true, -1.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+  /** display flow set 1 Cartesian Z axis coordinate */
+  DisplayRealType Flow1Z =
+    new DisplayRealType("Flow1Z", true, -1.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+
+  /** display flow set 1 spherical elevation coordinate */
+  DisplayRealType Flow1Elevation =
+    new DisplayRealType("Flow1Elevation", true, -90.0, 90.0, 0.0,
+                        CommonUnit.degree, true);
+
+  /** display flow set 1 spherical azimuth coordinate */
+  DisplayRealType Flow1Azimuth =
+    new DisplayRealType("Flow1Azimuth", true, 0.0, 360.0, 0.0,
+                        CommonUnit.degree, true);
+
+  /** display flow set 1 spherical radial coordinate */
+  DisplayRealType Flow1Radial =
+    new DisplayRealType("Flow1Radial", true, 0.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+
+  /** display flow set 2 Cartesian X axis coordinate */
+  DisplayRealType Flow2X =
+    new DisplayRealType("Flow2X", true, -1.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+  /** display flow set 2 Cartesian Y axis coordinate */
+  DisplayRealType Flow2Y =
+    new DisplayRealType("Flow2Y", true, -1.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+  /** display flow set 2 Cartesian Z axis coordinate */
+  DisplayRealType Flow2Z =
+    new DisplayRealType("Flow2Z", true, -1.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+
+  /** display flow set 2 spherical elevation coordinate */
+  DisplayRealType Flow2Elevation =
+    new DisplayRealType("Flow2Elevation", true, -90.0, 90.0, 0.0,
+                        CommonUnit.degree, true);
+
+  /** display flow set 2 spherical azimuth coordinate */
+  DisplayRealType Flow2Azimuth =
+    new DisplayRealType("Flow2Azimuth", true, 0.0, 360.0, 0.0,
+                        CommonUnit.degree, true);
+
+  /** display flow set 2 spherical radial coordinate */
+  DisplayRealType Flow2Radial =
+    new DisplayRealType("Flow2Radial", true, 0.0, 1.0, 0.0,
+                        CommonUnit.meterPerSecond, true);
+
+
+  /** index into a set of display shapes */
+  DisplayRealType Shape =
+    new DisplayRealType("Shape", false, 0.0, true);
+
+  /** scale for display shapes */
+  DisplayRealType ShapeScale =
+    new DisplayRealType("ShapeScale", true, 0.01, 1.0, 1.0, true);
+
+
+  /** display scalar for text */
+  DisplayRealType Text =
+    new DisplayRealType("Text", true, true);
+
+
+  /** line width - ConstantMap only */
+  DisplayRealType LineWidth =
+    new DisplayRealType("LineWidth", true, 1.0, true);
+
+  /** point size - ConstantMap only */
+  DisplayRealType PointSize =
+    new DisplayRealType("PointSize", true, 1.0, true);
+
+  /** line style - ConstantMap only */
+  DisplayRealType LineStyle =
+    new DisplayRealType("LineStyle", true, 1.0, true);
+
+  /** texture enable - ConstantMap only */
+  DisplayRealType TextureEnable =
+    new DisplayRealType("TextureEnable", true, -1.0, true);
+
+  /** missing transparent - ConstantMap only */
+  DisplayRealType MissingTransparent =
+    new DisplayRealType("MissingTransparent", true, -1.0, true);
+
+  /** polygon mode - ConstantMap only */
+  DisplayRealType PolygonMode =
+    new DisplayRealType("PolygonMode", true, -1.0, true);
+
+  /** curved size - ConstantMap only, values must be > 0 */
+  DisplayRealType CurvedSize =
+    new DisplayRealType("CurvedSize", true, 0.0, true);
+
+  /** color mode - ConstantMap only, values must be > 0 */
+  DisplayRealType ColorMode =
+    new DisplayRealType("ColorMode", true, -1.0, true);
+
+  /** Polygon offset - ConstantMap only */
+  DisplayRealType PolygonOffset =
+    new DisplayRealType("PolygonOffset", true, Double.NaN, true);
+
+  /** Polygon offset factor - ConstantMap only */
+  DisplayRealType PolygonOffsetFactor =
+    new DisplayRealType("PolygonOffsetFactor", true, Double.NaN, true);
+
+  /** Adjust along projection seams - ConstantMap only */
+  DisplayRealType AdjustProjectionSeam =
+    new DisplayRealType("AdjustProjectionSeam", true, -1.0, true);
+
+  /** texture 3D mode - ConstantMap only, values must be > 0 */
+  DisplayRealType Texture3DMode =
+    new DisplayRealType("Texture3DMode", true, -1.0, true);
+
+  /** cache appearances - ConstantMap only */
+  DisplayRealType CacheAppearances =
+    new DisplayRealType("CacheAppearances", true, -1.0, true);
+
+  /** cache appearances - ConstantMap only */
+  DisplayRealType MergeGeometries =
+    new DisplayRealType("MergeGeometries", true, -1.0, true);
+
+  /** point mode - ConstantMap only */
+  DisplayRealType PointMode =
+    new DisplayRealType("PointMode", true, -1.0, true);
+
+  /** array of system intrinsic DisplayRealTypes */
+  DisplayRealType[] DisplayRealArray =
+    {XAxis, YAxis, ZAxis, Latitude, Longitude, Radius, List, Red, Green, Blue,
+     RGB, RGBA, Hue, Saturation, Value, HSV, Cyan, Magenta, Yellow, CMY, Alpha,
+     Animation, SelectValue, SelectRange, IsoContour, Flow1X, Flow1Y, Flow1Z,
+     Flow2X, Flow2Y, Flow2Z, XAxisOffset, YAxisOffset, ZAxisOffset, Shape,
+     Text, ShapeScale, LineWidth, PointSize, CylRadius, CylAzimuth, CylZAxis,
+     Flow1Elevation, Flow1Azimuth, Flow1Radial,
+     Flow2Elevation, Flow2Azimuth, Flow2Radial, 
+     LineStyle, TextureEnable, MissingTransparent, 
+     PolygonMode, CurvedSize, ColorMode, 
+     PolygonOffset, PolygonOffsetFactor, 
+     AdjustProjectionSeam, Texture3DMode,
+     CacheAppearances, MergeGeometries, PointMode
+     };
+
+  // system intrinsic DisplayTupleType objects
+
+  /** array of 3D Cartesian spatial coordinates */
+  DisplayRealType[] components3c =
+          {Display.XAxis, Display.YAxis, Display.ZAxis};
+
+  /** system intrinsic DisplayTupleType for 3D Cartesian
+      spatial coordinates */
+  DisplayTupleType DisplaySpatialCartesianTuple =
+    new DisplayTupleType(components3c, true);
+
+
+  /** CoordinateSystem for DisplaySpatialSphericalTuple, with
+      reference DisplaySpatialCartesianTuple */
+  CoordinateSystem DisplaySphericalCoordSys =
+    new SphericalCoordinateSystem(DisplaySpatialCartesianTuple, true);
+
+  /** array of 3D spherical spatial coordinates */
+  DisplayRealType[] components3s =
+          {Latitude, Longitude, Radius};
+
+  /** system intrinsic DisplayTupleType for 3D spherical
+      spatial coordinates */
+  DisplayTupleType DisplaySpatialSphericalTuple =
+    new DisplayTupleType(components3s, DisplaySphericalCoordSys, true);
+
+
+  /** CoordinateSystem for DisplaySpatialCylindricalTuple, with
+      reference DisplaySpatialCartesianTuple */
+  CoordinateSystem DisplayCylindricalCoordSys =
+    new CylindricalCoordinateSystem(DisplaySpatialCartesianTuple, true);
+
+  /** array of 3D cylindrical Coordinates */
+  DisplayRealType[] componentscyl =
+          {CylRadius, CylAzimuth, CylZAxis};
+
+  /** system intrinsic DisplayTupleType for 3D cylindrical
+      spatial coordinates */
+  DisplayTupleType DisplaySpatialCylindricalTuple =
+    new DisplayTupleType(componentscyl, DisplayCylindricalCoordSys, true);
+
+
+  /** array of 3D RGB coordinates */
+  DisplayRealType[] componentsrgb = {Red, Green, Blue};
+
+  /** system intrinsic DisplayTupleType for RGB color coordinates */
+  DisplayTupleType DisplayRGBTuple =
+    new DisplayTupleType(componentsrgb, true);
+
+
+  /** CoordinateSystem for DisplayHSVTuple, with reference
+      DisplayRGBTuple */
+  CoordinateSystem DisplayHSVCoordSys =
+    new HSVCoordinateSystem(DisplayRGBTuple, true);
+
+  /** array of 3D HSV coordinates */
+  DisplayRealType[] componentshsv = {Hue, Saturation, Value};
+
+  /** system intrinsic DisplayTupleType for HSV color coordinates */
+  DisplayTupleType DisplayHSVTuple =
+    new DisplayTupleType(componentshsv, DisplayHSVCoordSys, true);
+
+
+  /** CoordinateSystem for DisplayCMYTuple, with reference
+      DisplayRGBTuple */
+  CoordinateSystem DisplayCMYCoordSys =
+    new CMYCoordinateSystem(DisplayRGBTuple, true);
+
+  /** array of 3D CMY coordinates */
+  DisplayRealType[] componentscmy =
+          {Cyan, Magenta, Yellow};
+
+  /** system intrinsic DisplayTupleType for CMY color coordinates */
+  DisplayTupleType DisplayCMYTuple =
+    new DisplayTupleType(componentscmy, DisplayCMYCoordSys, true);
+
+  /** array of 3D Cartesian flow set 1 coordinates */
+  DisplayRealType[] componentsflow1 = {Flow1X, Flow1Y, Flow1Z};
+
+  /** system intrinsic DisplayTupleType for 3D Cartesian
+      flow set 1 coordinates */
+  DisplayTupleType DisplayFlow1Tuple =
+    new DisplayTupleType(componentsflow1, true);
+
+  /** array of 3D Cartesian flow set 2 coordinates */
+  DisplayRealType[] componentsflow2 = {Flow2X, Flow2Y, Flow2Z};
+
+  /** system intrinsic DisplayTupleType for 3D Cartesian
+      flow set 2 coordinates */
+  DisplayTupleType DisplayFlow2Tuple =
+    new DisplayTupleType(componentsflow2, true);
+
+
+  /** CoordinateSystem for DisplayFlow1SphericalTuple, with reference
+      DisplayFlow1Tuple */
+  CoordinateSystem DisplayFlow1SphericalCoordSys =
+    new FlowSphericalCoordinateSystem(DisplayFlow1Tuple, true);
+
+  /** array of 3D spherical flow set 1 coordinates */
+  DisplayRealType[] componentsflow1s =
+          {Flow1Elevation, Flow1Azimuth, Flow1Radial};
+
+  /** system intrinsic DisplayTupleType for 3D spherical
+      flow set 1 coordinates */
+  DisplayTupleType DisplayFlow1SphericalTuple =
+    new DisplayTupleType(componentsflow1s, DisplayFlow1SphericalCoordSys,
+                         true);
+
+
+  /** CoordinateSystem for DisplayFlow2SphericalTuple, with reference
+      DisplayFlow2Tuple */
+  CoordinateSystem DisplayFlow2SphericalCoordSys =
+    new FlowSphericalCoordinateSystem(DisplayFlow2Tuple, true);
+
+  /** array of 3D spherical flow set 2 coordinates */
+  DisplayRealType[] componentsflow2s =
+          {Flow2Elevation, Flow2Azimuth, Flow2Radial};
+
+  /** system intrinsic DisplayTupleType for 3D spherical
+      flow set 2 coordinates */
+  DisplayTupleType DisplayFlow2SphericalTuple =
+    new DisplayTupleType(componentsflow2s, DisplayFlow2SphericalCoordSys,
+                         true);
+
+
+  /** array of 3D Cartesian spatial offset coordinates */
+  DisplayRealType[] componentsso =
+          {XAxisOffset, YAxisOffset, ZAxisOffset};
+
+  /** system intrinsic DisplayTupleType for 3D Cartesian
+      spatial offset coordinates */
+  DisplayTupleType DisplaySpatialOffsetTuple =
+    new DisplayTupleType(componentsso, true);
+
+
+  /**
+   * create link to DataReference, with ConstantMaps;
+   * invokes ref.addThingChangedListener(ThingChangedListener l, long id)
+   * @param ref DataReference to link to
+   * @param constant_maps array of ConstantMaps applied to linked Data
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void addReference(DataReference ref,
+         ConstantMap[] constant_maps) throws VisADException, RemoteException;
+
+  /** 
+   * create link to DataReference, with ConstantMaps and DataRenderer;
+   * invokes ref.addThingChangedListener(ThingChangedListener l, long id) 
+   * @param renderer DataRenderer used to depict linked Data
+   * @param ref DataReference to link to
+   * @param constant_maps array of ConstantMaps applied to this Data
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void addReferences(DataRenderer renderer, DataReference ref,
+                            ConstantMap[] constant_maps)
+         throws VisADException, RemoteException;
+
+  /**
+   * link a ScalarMap (may be a ConstantMap) to this Display
+   * @param map ScalarMap to link
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void addMap(ScalarMap map)
+         throws VisADException, RemoteException;
+
+  /** 
+   * remove a ScalarMap (may be a ConstantMap) from this Display 
+   * @param map ScalarMap to remove
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void removeMap(ScalarMap map)
+         throws VisADException, RemoteException;
+
+  /** 
+   * remove all ScalarMaps (and ConstantMaps) from this Display
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void clearMaps() throws VisADException, RemoteException;
+
+  /**
+   * destroy this display: break all links, stop Threads and
+   * clear references for garbage collection
+   */
+  void destroy() throws VisADException, RemoteException;
+
+  /**
+   * @return Vector of linked ConstantMaps
+   */
+  Vector getConstantMapVector()
+         throws VisADException, RemoteException;
+
+  /**
+   * Send a message to all </tt>MessageListener</tt>s.
+   *
+   * @param msg Message being sent.
+   * @throws RemoteException an RMI error occurred
+   */
+  void sendMessage(MessageEvent msg)
+    throws RemoteException;
+
+  /**
+   * link a slave display to this display
+   * @param display RemoteSlaveDisplay to link
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void addSlave(RemoteSlaveDisplay display)
+        throws VisADException, RemoteException;
+
+  /** 
+   * remove a slave display from this display
+   * @param display RemoteSlaveDisplay to remove
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void removeSlave(RemoteSlaveDisplay display)
+        throws VisADException, RemoteException;
+
+  /** 
+   * remove all slave displays from this display
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void removeAllSlaves() throws VisADException, RemoteException;
+
+  /**
+   * @return true if this display has any slave displays
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  boolean hasSlaves() throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/DisplayActivity.java b/visad/DisplayActivity.java
new file mode 100644
index 0000000..6e765cf
--- /dev/null
+++ b/visad/DisplayActivity.java
@@ -0,0 +1,245 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.util.ArrayList;
+
+import javax.swing.Timer;
+
+/** Manage busy/idle handlers for a Display */
+public class DisplayActivity
+{
+  private static final int DEFAULT_IDLE_MILLISECONDS = 250;
+
+  private static final boolean DEBUG = false;
+
+  private transient DisplayImpl dpy;
+  private int interval;
+
+  private long lastBusyEvt;
+  private boolean isBusy;
+  private transient Timer busyTimer;
+  private transient BusyAction busyAction;
+
+  private transient ArrayList handlerList;
+
+  /**
+   * Manage busy/idle handlers for a Display, using a default
+   * idle time of 0.25 second.
+   *
+   * @param dpy Display to manage.
+   */
+  public DisplayActivity(DisplayImpl dpy)
+  {
+    this(dpy, DEFAULT_IDLE_MILLISECONDS);
+  }
+
+  /**
+   * Manage busy/idle handlers for a Display, using the specified
+   * idle time, with the minimum idle time being 100 milliseconds.
+   *
+   * @param dpy Display to manage.
+   * @param milliseconds Number of milliseconds to wait before
+   *                     the Display is considered idle.
+   */
+  public DisplayActivity(DisplayImpl dpy, int milliseconds)
+  {
+    this.dpy = dpy;
+    if (milliseconds < 100) {
+      this.interval = 100;
+    } else {
+      this.interval = milliseconds;
+    }
+
+    lastBusyEvt = System.currentTimeMillis() - this.interval;
+    isBusy = false;
+
+    busyAction = new BusyAction();
+    busyTimer = new Timer(this.interval, busyAction);
+
+    busyTimer.removeActionListener(busyAction);
+    busyAction = null;
+
+    handlerList = new ArrayList();
+  }
+
+  /**
+   * Stop the idle timer and any running tasks.
+   */
+  public void destroy()
+  {
+    synchronized (busyTimer) {
+      if (busyAction != null) {
+        busyTimer.removeActionListener(busyAction);
+        busyAction = null;
+      }
+
+      busyTimer.stop();
+    }
+  }
+
+  /**
+   * Add a new activity handler.
+   * @param ah ActivityHandler to add
+   * @throws VisADException no handler list
+   */
+  public void addHandler(ActivityHandler ah)
+    throws VisADException
+  {
+    if (handlerList == null) {
+      throw new VisADException("No handler list found; " +
+                               "was this object serialized?");
+    }
+
+    if (!busyTimer.isRunning()) {
+      busyTimer.restart();
+    }
+
+    handlerList.add(ah);
+  }
+
+  /**
+   * Remove an activity handler.
+   * @param ah ActivityHandler to remove
+   * @throws VisADException no handler list
+   */
+  public void removeHandler(ActivityHandler ah)
+    throws VisADException
+  {
+    if (handlerList == null) {
+      throw new VisADException("No handler list found; " +
+                               "was this object serialized?");
+    }
+
+    handlerList.remove(ah);
+
+    if (handlerList.size() == 0 && busyTimer.isRunning()) {
+      busyTimer.stop();
+    }
+  }
+
+  /**
+   * Notify all handlers of a transition from idle to busy or vice versa.
+   *
+   * @param isBusy <tt>true</tt> if the Display is now busy.
+   */
+  private void notifyList(boolean isBusy)
+  {
+    if (handlerList != null && busyTimer != null) {
+      synchronized (handlerList) {
+        final int len = handlerList.size();
+        for (int i = 0; i < len; i++) {
+          ActivityHandler ah = (ActivityHandler )handlerList.get(i);
+          if (isBusy) {
+            ah.busyDisplay(dpy);
+          } else {
+            ah.idleDisplay(dpy);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Notify the activity monitor that work has been done.
+   */
+  public void updateBusyStatus()
+  {
+    final long now = System.currentTimeMillis();
+
+    if (!isBusy && (now < lastBusyEvt || now > lastBusyEvt + interval)) {
+      // Display is doing something...
+      isBusy = true;
+
+      if (busyAction == null && busyTimer != null) {
+        synchronized (busyTimer) {
+          if (busyAction == null) {
+            // create a busy task
+            busyAction = new BusyAction();
+
+            // have busy action called at the specified interval
+            busyTimer.addActionListener(busyAction);
+            if (DEBUG) System.err.println("STARTED");
+          }
+        }
+      }
+
+      if (DEBUG) System.err.println("BUSY");
+
+      // let handlers know that Display is busy
+      notifyList(true);
+    }
+
+    // remember when Display was last busy
+    lastBusyEvt = now;
+  }
+
+  private class BusyAction
+    implements ActionListener
+  {
+    private static final int MAX_IDLE = 10;
+
+    private int idleCount = 0;
+
+    BusyAction() { }
+
+    public void actionPerformed(ActionEvent evt)
+    {
+      if (!isBusy) {
+        // another interval has passed where display wasn't busy
+        idleCount++;
+
+        // if it's been long enough...
+        if (idleCount > MAX_IDLE) {
+
+          // ... and there's a timer object...
+          if (busyTimer != null) {
+
+            // ...stop the timer
+            synchronized (busyTimer) {
+              busyTimer.removeActionListener(busyAction);
+              busyAction = null;
+              if (DEBUG) System.err.println("CANCELLED");
+            }
+          }
+        }
+      } else {
+        final long now = System.currentTimeMillis();
+
+        if (lastBusyEvt + interval <= now) {
+          // if we're past the waiting period, we must be idle
+          isBusy = false;
+          idleCount = 0;
+
+          if (DEBUG) System.err.println("IDLE");
+
+          // let handlers know that Display is idle
+          notifyList(false);
+        }
+      }
+    }
+  }
+}
diff --git a/visad/DisplayEvent.java b/visad/DisplayEvent.java
new file mode 100644
index 0000000..3e5acc9
--- /dev/null
+++ b/visad/DisplayEvent.java
@@ -0,0 +1,508 @@
+//
+// DisplayEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Component;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+   DisplayEvent is the VisAD class for Events from Display
+   objects.  They are sourced by Display objects and
+   received by DisplayListener objects.<P>
+*/
+public class DisplayEvent extends VisADEvent {
+
+  // If you add more events, be sure to add them to the Javadoc for
+  // DisplayEvent.getId(), DisplayImpl.enableEvent(), and
+  // DisplayImpl.disableEvent() and DisplayImpl.eventStatus. Also 
+  // update DisplayEvent.main()'s ids array.
+
+  /**
+   * The "mouse pressed" event.  This event occurs when any
+   * of the mouse buttons is pressed inside the display.  Other
+   * MOUSE_PRESSED event positions (LEFT, CENTER, RIGHT) are based
+   * on a right-handed mouse configuration.
+   */
+  public final static int MOUSE_PRESSED = 1;
+
+  /** The "transform done" event. This even occurs when a DisplayImpl
+      finishes transforming Data into depictions. */
+  public final static int TRANSFORM_DONE = 2;
+
+  /** The "frame done" event. This even occurs when Data depictions
+      have been rendered to the screen by the graphics API. */
+  public final static int FRAME_DONE = 3;
+
+  /**
+   * The "center mouse button pressed" event.  This event occurs when
+   * the center mouse button is pressed inside the display.
+   */
+  public final static int MOUSE_PRESSED_CENTER = 4;
+
+  /**
+   * The "left mouse button pressed" event.  This event occurs when
+   * the left mouse button is pressed inside the display.
+   */
+  public final static int MOUSE_PRESSED_LEFT = 5;
+
+  /**
+   * The "right mouse button pressed" event.  This event occurs when
+   * the right mouse button is pressed inside the display.
+   */
+  public final static int MOUSE_PRESSED_RIGHT = 6;
+
+  /**
+   * The "mouse released" event.  This event occurs when any
+   * of the mouse buttons is released after one of the MOUSE_PRESSED
+   * events. Other MOUSE_RELEASED event positions (LEFT, CENTER, RIGHT)
+   * are based on a right-handed mouse configuration.
+   */
+  public final static int MOUSE_RELEASED = 7;
+
+  /**
+   * The "center mouse button released" event.  This event occurs when
+   * the center mouse button is released after a MOUSE_PRESSED or
+   * MOUSE_PRESSED_CENTER event.
+   */
+  public final static int MOUSE_RELEASED_CENTER = 8;
+
+  /**
+   * The "left mouse button released" event.  This event occurs when
+   * the left mouse button is released after a MOUSE_PRESSED or
+   * MOUSE_PRESSED_LEFT event.
+   */
+  public final static int MOUSE_RELEASED_LEFT = 9;
+
+  /**
+   * The "right mouse button released" event.  This event occurs when
+   * the right mouse button is released after a MOUSE_PRESSED or
+   * MOUSE_PRESSED_RIGHT event.
+   */
+  public final static int MOUSE_RELEASED_RIGHT = 10;
+
+  /**
+   * The "map added" event.  This event occurs when
+   * a ScalarMap is added to the display.
+   */
+  public final static int MAP_ADDED = 11;
+
+  /**
+   * The "maps cleared" event.  This event occurs when
+   * all ScalarMaps are removed from the display.
+   */
+  public final static int MAPS_CLEARED = 12;
+
+  /**
+   * The "reference added" event.  This event occurs when
+   * a DataReference is added to the display.
+   */
+  public final static int REFERENCE_ADDED = 13;
+
+  /**
+   * The "reference removed" event.  This event occurs when
+   * a DataReference is removed from the display.
+   */
+  public final static int REFERENCE_REMOVED = 14;
+
+  /**
+   * The "display destroyed" event.  This event occurs when
+   * a display's destroy() method is called.
+   */
+  public final static int DESTROYED = 15;
+
+  /**
+   * The "key pressed" event.  This event occurs when the display
+   * has the focus and a key on the keyboard is pressed.
+   *
+   * Note that a KeyboardBehavior must be attached to the display
+   * before this type of event will be reported.
+   */
+  public final static int KEY_PRESSED = 16;
+
+  /**
+   * The "key released" event.  This event occurs when the display
+   * has the focus and a key on the keyboard is released.
+   *
+   * Note that a KeyboardBehavior must be attached to the display
+   * before this type of event will be reported.
+   */
+  public final static int KEY_RELEASED = 17;
+
+  /**
+   * The "mouse dragged" event.  This event occurs when
+   * the mouse is dragged across the display.
+   *
+   * Note that you must call
+   * DisplayImpl.enableEvent(DisplayEvent.MOUSE_DRAGGED)
+   * to enable reporting of this type of event.
+   */
+  public final static int MOUSE_DRAGGED = 18;
+
+  /**
+   * The "mouse entered" event.  This event occurs when
+   * the mouse cursor enters the region of the display.
+   *
+   * Note that you must call
+   * DisplayImpl.enableEvent(DisplayEvent.MOUSE_ENTERED)
+   * to enable reporting of this type of event.
+   */
+  public final static int MOUSE_ENTERED = 19;
+
+  /**
+   * The "mouse exited" event.  This event occurs when
+   * the mouse cursor leaves the region of the display.
+   *
+   * Note that you must call
+   * DisplayImpl.enableEvent(DisplayEvent.MOUSE_EXITED)
+   * to enable reporting of this type of event.
+   */
+  public final static int MOUSE_EXITED = 20;
+
+  /**
+   * The "mouse moved" event.  This event occurs when
+   * the mouse is moved across the display.
+   *
+   * Note that you must call
+   * DisplayImpl.enableEvent(DisplayEvent.MOUSE_MOVED)
+   * to enable reporting of this type of event.
+   */
+  public final static int MOUSE_MOVED = 21;
+
+  /**
+   * The "display wait on" event.  This event occurs when
+   * a display's renderer is told to "wait"
+   *
+   * Note that you must call
+   * DisplayImpl.enableEvent(DisplayEvent.WAIT_ON)
+   * to enable reporting of this type of event.
+   */
+  public final static int WAIT_ON = 22;
+
+  /**
+   * The "display wait off" event.  This event occurs when
+   * a display's renderer is told to "stop waiting"
+   *
+   * Note that you must call
+   * DisplayImpl.enableEvent(DisplayEvent.WAIT_OFF)
+   * to enable reporting of this type of event.
+   */
+  public final static int WAIT_OFF = 23;
+
+  /**
+   * The "map removed" event.  This event occurs when
+   * a ScalarMap is removed from the display.
+   */
+  public final static int MAP_REMOVED = 24;
+
+  /**
+   * The "component resized" event.  This event occurs when
+   * a the display's component is resized.
+   *
+   * Note that you must call
+   * DisplayImpl.enableEvent(DisplayEvent.COMPONENT_RESIZED)
+   * to enable reporting of this type of event.
+   */
+  public final static int COMPONENT_RESIZED = 25;
+
+  private final static String[] ids = {
+      "?", "MOUSE_PRESSED", "TRANSFORM_DONE", "FRAME_DONE",
+      "MOUSE_PRESSED_CENTER", "MOUSE_PRESSED_LEFT",  "MOUSE_PRESSED_RIGHT",
+      "MOUSE_RELEASED", "MOUSE_RELEASED_CENTER", "MOUSE_RELEASED_LEFT",
+      "MOUSE_RELEASED_RIGHT", "MAP_ADDED", "MAPS_CLEARED", "REFERENCE_ADDED",
+      "REFERENCE_REMOVED", "DESTROYED", "KEY_PRESSED", "KEY_RELEASED",
+      "MOUSE_DRAGGED", "MOUSE_ENTERED", "MOUSE_EXITED", "MOUSE_MOVED",
+      "WAIT_ON", "WAIT_OFF", "MAP_REMOVED", "COMPONENT_RESIZED"
+    };
+
+  /** Dummy AWT component. */
+  // private static final Component DUMMY = new JPanel();
+  private static Component DUMMY = null;
+  static {
+    try {
+      DUMMY = new JPanel();
+    }
+    catch (Throwable t) {
+    }
+  }
+
+  private int id = 0;
+
+  /** InputEvent corresponding to the DisplayEvent, if any */
+  private InputEvent input_event = null;
+
+  /** source of event */
+  private Display display;
+
+  /**
+   * Constructs a DisplayEvent object with the specified source display,
+   * and type of event.
+   *
+   * @param  d  display that sends the event
+   * @param  id_d  type of DisplayEvent that is sent
+   */
+  public DisplayEvent(Display d, int id_d) {
+    this(d, id_d, LOCAL_SOURCE);
+  }
+
+  /**
+   * Constructs a DisplayEvent object with the specified source display,
+   * and type of event.
+   *
+   * @param  d  display that sends the event
+   * @param  id_d  type of DisplayEvent that is sent
+   * @param  remoteId  ID of remote source
+   */
+  public DisplayEvent(Display d, int id_d, int remoteId) {
+    // don't pass display as the source, since source
+    // is transient inside Event
+    super(null, 0, null, remoteId);
+    display = d;
+    id = id_d;
+  }
+
+  /**
+   * Constructs a DisplayEvent object with the specified source display,
+   * type of event, and mouse positions where event occurred.
+   *
+   * @param  d  display that sends the event
+   * @param  id_d  type of DisplayEvent that is sent
+   * @param  x  the horizontal x coordinate for the mouse location in
+   *            the display component
+   * @param  y  the vertical y coordinate for the mouse location in
+   *            the display component
+   */
+  public DisplayEvent(Display d, int id_d, int x, int y) {
+    this(d, id_d, x, y, LOCAL_SOURCE);
+  }
+
+  /**
+   * Constructs a DisplayEvent object with the specified source display,
+   * type of event, and mouse event describing mouse details.
+   *
+   * @param  d  display that sends the event
+   * @param  id_d  type of DisplayEvent that is sent
+   * @param  e  the InputEvent describing this MOUSE type DisplayEvent
+   */
+  public DisplayEvent(Display d, int id_d, InputEvent e) {
+    this(d, id_d, e, LOCAL_SOURCE);
+  }
+
+  /**
+   * get the AWT (including Swing) Component of a Display
+   * @param d the Display
+   * @return the Component of d (may be null
+   */
+  protected static Component getDisplayComponent(Display d) {
+    if (!(d instanceof DisplayImpl)) return DUMMY;
+    DisplayImpl di = (DisplayImpl) d;
+    Component c = di.getComponent();
+    return c == null ? DUMMY : c;
+  }
+
+  /**
+   * Constructs a DisplayEvent object with the specified source display,
+   * type of event, mouse positions where event occurred, and
+   * remote flag indicating whether event came from a remote source.
+   *
+   * @param  d  display that sends the event
+   * @param  id_d  type of DisplayEvent that is sent
+   * @param  x  the horizontal x coordinate for the mouse location in
+   *            the display component
+   * @param  y  the vertical y coordinate for the mouse location in
+   *            the display component
+   * @param  remoteId  ID of remote source
+   */
+  public DisplayEvent(Display d, int id_d, int x, int y, int remoteId) {
+    this(d, id_d, makeInputEvent(d, x, y), remoteId);
+    // this(d, id_d, new MouseEvent(getDisplayComponent(d), 0,
+    //   System.currentTimeMillis(), 0, x, y, 1, false), remoteId);
+  }
+
+  /**
+   * Constructs a DisplayEvent object with the specified source display,
+   * type of event, mouse event describing mouse details, and remote
+   * flag indicating whether event came from a remote source.
+   *
+   * @param  d  display that sends the event
+   * @param  id_d  type of DisplayEvent that is sent
+   * @param  e  the InputEvent describing this MOUSE type DisplayEvent
+   * @param  remoteId  ID of remote source
+   */
+  public DisplayEvent(Display d, int id_d, InputEvent e, int remoteId) {
+    // don't pass display as the source, since source
+    // is transient inside Event
+    super(null, 0, null, remoteId);
+    display = d;
+    id = id_d;
+    input_event = e;
+  }
+
+  private static InputEvent makeInputEvent(Display d, int x, int y) {
+    Component c = getDisplayComponent(d);
+    if (c != null) {
+      return new MouseEvent(c, 0, System.currentTimeMillis(), 0, x, y, 1, false);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * @return a new DisplayEvent which is a copy of this event,
+   *         but which uses the specified source display
+   */
+  public DisplayEvent cloneButDisplay(Display dpy)
+  {
+    return new DisplayEvent(dpy, id, input_event, getRemoteId());
+  }
+
+  /**
+   * @return the DisplayImpl that sent this DisplayEvent (or
+   *         a RemoteDisplay reference to it if the Display was
+   *         on a different JVM)
+   */
+  public Display getDisplay() {
+    return display;
+  }
+
+  /**
+   * Get the ID type of this event
+   *
+   * @return  DisplayEvent type.  Valid types are:
+   *          <UL>
+   *          <LI>DisplayEvent.FRAME_DONE
+   *          <LI>DisplayEvent.TRANSFORM_DONE
+   *          <LI>DisplayEvent.MOUSE_PRESSED
+   *          <LI>DisplayEvent.MOUSE_PRESSED_LEFT
+   *          <LI>DisplayEvent.MOUSE_PRESSED_CENTER
+   *          <LI>DisplayEvent.MOUSE_PRESSED_RIGHT
+   *          <LI>DisplayEvent.MOUSE_RELEASED_LEFT
+   *          <LI>DisplayEvent.MOUSE_RELEASED_CENTER
+   *          <LI>DisplayEvent.MOUSE_RELEASED_RIGHT
+   *          <LI>DisplayEvent.MAP_ADDED
+   *          <LI>DisplayEvent.MAPS_CLEARED
+   *          <LI>DisplayEvent.REFERENCE_ADDED
+   *          <LI>DisplayEvent.REFERENCE_REMOVED
+   *          <LI>DisplayEvent.DESTROYED
+   *          <LI>DisplayEvent.KEY_PRESSED
+   *          <LI>DisplayEvent.KEY_RELEASED
+   *          <LI>DisplayEvent.MOUSE_DRAGGED
+   *          <LI>DisplayEvent.MOUSE_ENTERED
+   *          <LI>DisplayEvent.MOUSE_EXITED
+   *          <LI>DisplayEvent.MOUSE_MOVED
+   *          <LI>DisplayEvent.WAIT_ON
+   *          <LI>DisplayEvent.WAIT_OFF
+   *          <LI>DisplayEvent.MAP_REMOVED
+   *          <LI>DisplayEvent.COMPONENT_RESIZED
+   *          </UL>
+   */
+  public int getId() {
+    return id;
+  }
+
+  /**
+   * Get the horizontal x coordinate for the mouse location.  Only valid
+   * for MOUSE type events.
+   *
+   * @return  horizontal x coordinate for the mouse location in
+   *          the display component, or -1 if not a mouse event
+   */
+  public int getX() {
+    return input_event == null || !(input_event instanceof MouseEvent) ?
+      -1 : ((MouseEvent) input_event).getX();
+  }
+
+  /**
+   * Get the vertical y coordinate for the mouse location.  Only valid
+   * for MOUSE type events.
+   *
+   * @return  vertical y coordinate for the mouse location in
+   *          the display component, or -1 if not a mouse event
+   */
+  public int getY() {
+    return input_event == null || !(input_event instanceof MouseEvent) ?
+      -1 : ((MouseEvent) input_event).getY();
+  }
+
+  /**
+   * Get the key code for the pressed or released key.  Only valid
+   * for KEY type events.
+   *
+   * @return  key code for key pressed or released in the
+   *          display component, or -1 if not a key event
+   */
+  public int getKeyCode() {
+    return input_event == null || !(input_event instanceof KeyEvent) ?
+      -1 : ((KeyEvent) input_event).getKeyCode();
+  }
+
+  /**
+   * Get the keyboard modifiers (such as whether SHIFT or CTRL was
+   * being held during the event).  Only valid for MOUSE and KEY type events.
+   *
+   * @return  keyboard modifier bit field, or -1 if not a mouse event
+   */
+  public int getModifiers() {
+    return input_event == null ? -1 : input_event.getModifiers();
+  }
+
+  /**
+   * Get the InputEvent associated with this DisplayEvent.
+   * Only valid for MOUSE and KEY type events.
+   *
+   * @return  associated InputEvent, or null if not a mouse event
+   */
+  public InputEvent getInputEvent() { return input_event; }
+
+  /**
+   * String representation of the event.
+   *
+   * @return descriptive info about the event
+   */
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("DisplayEvent: ");
+    try {
+      String display = getDisplay().getName();
+      buf.append("Display=");
+      buf.append(display);
+      buf.append(", ");
+    } catch (Exception ve) { }
+    buf.append("Id=");
+    buf.append(ids[getId()]);
+    buf.append(", X=");
+    buf.append(getX());
+    buf.append(", Y=");
+    buf.append(getY());
+    buf.append(", remoteId=");
+    buf.append(getRemoteId());
+    return buf.toString();
+  }
+
+}
+
diff --git a/visad/DisplayException.java b/visad/DisplayException.java
new file mode 100644
index 0000000..91ec85f
--- /dev/null
+++ b/visad/DisplayException.java
@@ -0,0 +1,46 @@
+//
+// DisplayException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DisplayException is an exception for an error with a VisAD display.<P>
+*/
+public class DisplayException extends VisADException {
+
+  /**
+   * construct a DisplayException with null message String
+   */
+  public DisplayException() { super(); }
+
+  /** 
+   * construct a DisplayException with given message String
+   * @param s message String
+   */
+  public DisplayException(String s) { super(s); }
+
+}
+
diff --git a/visad/DisplayImpl.java b/visad/DisplayImpl.java
new file mode 100644
index 0000000..1b8a2f1
--- /dev/null
+++ b/visad/DisplayImpl.java
@@ -0,0 +1,3308 @@
+//
+// DisplayImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.image.BufferedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.UnmarshalException;
+
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.Vector;
+
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import visad.browser.Convert;
+import visad.browser.Divider;
+import visad.collab.ControlMonitorEvent;
+import visad.collab.DisplayMonitor;
+import visad.collab.DisplayMonitorImpl;
+import visad.collab.DisplaySync;
+import visad.collab.DisplaySyncImpl;
+import visad.collab.MonitorEvent;
+import visad.util.AnimationWidget;
+import visad.util.ContourWidget;
+import visad.util.GMCWidget;
+import visad.util.LabeledColorWidget;
+import visad.util.RangeWidget;
+import visad.util.SelectRangeWidget;
+import visad.util.VisADSlider;
+
+/**
+   DisplayImpl is the abstract VisAD superclass for display
+   implementations.  It is runnable.<P>
+
+   DisplayImpl is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public abstract class DisplayImpl extends ActionImpl implements LocalDisplay {
+
+  /** instance variables */
+
+  /**
+   * a Vector of ScalarMap objects;
+   *   does not include ConstantMap objects 
+   */
+  private Vector MapVector = new Vector();
+
+  /** a Vector of ConstantMap objects */
+  private Vector ConstantMapVector = new Vector();
+
+  /**
+   * a Vector of RealType (and TextType) objects occuring
+   *   in MapVector 
+   */
+  private Vector RealTypeVector = new Vector();
+
+  /** a Vector of DisplayRealType objects occuring in MapVector */
+  private Vector DisplayRealTypeVector = new Vector();
+
+  /**
+   * list of Control objects linked to ScalarMap objects in MapVector;
+   *   the Control objects may be linked to UI widgets, or just computed 
+   */
+  private Vector ControlVector = new Vector();
+
+  /** ordered list of DataRenderer objects that render Data objects */
+  private Vector RendererVector = new Vector();
+
+  /**
+   * list of objects interested in learning when DataRenderers
+   *   are deleted from this Display 
+   */
+  private Vector RendererSourceListeners = new Vector();
+
+  /**
+   * list of objects interested in learning when Data objects
+   *   are deleted from this Display 
+   */
+  private Vector RmtSrcListeners = new Vector();
+
+  /**
+   * list of objects interested in receiving messages
+   *   from this Display 
+   */
+  private Vector MessageListeners = new Vector();
+
+  /** DisplayRenderer object for background and metadata rendering */
+  private DisplayRenderer displayRenderer;
+
+  /**
+   * Component where data depictions are rendered;
+   *   must be set by concrete subclass constructor;
+   *   may be null for off-screen displays 
+   */
+  Component component;
+
+  /**           */
+  private ComponentChangedListener componentListener = null;
+
+  /**
+   * set to indicate need to compute ranges of RealType-s
+   *   and sampling for Animation 
+   */
+  private boolean initialize = true;
+
+  /**
+   * set to indicate that ranges should be auto-scaled
+   *   every time data are displayed 
+   */
+  private boolean always_initialize = false;
+
+  /** set to re-display all linked Data */
+  private boolean redisplay_all = false;
+
+
+  /**
+   * length of ValueArray of distinct DisplayRealType values;
+   *   one per Single DisplayRealType that occurs in a ScalarMap,
+   *   plus one per ScalarMap per non-Single DisplayRealType;
+   *   ScalarMap.valueIndex is an index into ValueArray 
+   */
+  private int valueArrayLength;
+
+  /** mapping from ValueArray to DisplayScalar */
+  private int[] valueToScalar;
+
+  /** mapping from ValueArray to MapVector */
+  private int[] valueToMap;
+
+  /** Vector of DisplayListeners */
+  private final transient Vector ListenerVector = new Vector();
+
+  /**           */
+  private Object mapslock = new Object();
+
+  // WLH 16 March 99
+
+  /**           */
+  private MouseBehavior mouse = null;
+
+  // objects which monitor and synchronize with remote displays
+
+  /**           */
+  private transient DisplayMonitor displayMonitor = null;
+
+  /**           */
+  private transient DisplaySync displaySync = null;
+
+  // activity monitor
+
+  /**           */
+  private transient DisplayActivity displayActivity = null;
+
+  // Support for printing
+
+  /**           */
+  private Printable printer;
+
+  /** has this display been destroyed           */
+  private boolean destroyed = false;
+
+  /**
+   * construct a DisplayImpl with given name and DisplayRenderer
+   * @param name String name for DisplayImpl (used for debugging)
+   * @param renderer DisplayRenderer that controls aspects of the
+   *                 display not specific to any particular Data
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DisplayImpl(String name, DisplayRenderer renderer)
+          throws VisADException, RemoteException {
+    super(name);
+    // put system intrinsic DisplayRealType-s in DisplayRealTypeVector
+    for (int i = 0; i < DisplayRealArray.length; i++) {
+      DisplayRealTypeVector.addElement(DisplayRealArray[i]);
+    }
+
+    displayMonitor = new DisplayMonitorImpl(this);
+    displaySync = new DisplaySyncImpl(this);
+
+    if (renderer != null) {
+      displayRenderer = renderer;
+    }
+    else {
+      displayRenderer = getDefaultDisplayRenderer();
+    }
+    displayRenderer.setDisplay(this);
+
+    // initialize ScalarMap's, ShadowDisplayReal's and Control's
+    clearMaps();
+  }
+
+  /**
+   * construct a DisplayImpl collaborating with the given RemoteDisplay,
+   * and with the given DisplayRenderer
+   * @param rmtDpy RemoteDisplay to collaborate with
+   * @param renderer DisplayRenderer that controls aspects of the
+   *                 display not specific to any particular Data
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DisplayImpl(RemoteDisplay rmtDpy, DisplayRenderer renderer)
+          throws VisADException, RemoteException {
+    // this(rmtDpy, renderer, false);
+
+
+    super(rmtDpy.getName() + ".remote"); // WLH 11 April 2001
+
+    // get class used for remote display
+    String className = rmtDpy.getDisplayClassName();
+    Class rmtClass;
+    try {
+      rmtClass = Class.forName(className);
+    }
+    catch (ClassNotFoundException cnfe) {
+      throw new DisplayException("Cannot find remote display class " +
+                                 className);
+    }
+
+    // make sure this display class
+    // is compatible with the remote display class
+    if (!rmtClass.isInstance(this)) {
+      throw new DisplayException("Cannot construct " + getClass().getName() +
+                                 " from remote " + className);
+    }
+
+    // put system intrinsic DisplayRealType-s in DisplayRealTypeVector
+    for (int i = 0; i < DisplayRealArray.length; i++) {
+      DisplayRealTypeVector.addElement(DisplayRealArray[i]);
+    }
+
+    displayMonitor = new DisplayMonitorImpl(this);
+    displaySync = new DisplaySyncImpl(this);
+
+    if (renderer != null) {
+      displayRenderer = renderer;
+    }
+    else {
+      try {
+        String name = rmtDpy.getDisplayRendererClassName();
+        Object obj = Class.forName(name).newInstance();
+        displayRenderer = (DisplayRenderer)obj;
+      }
+      catch (Exception e) {
+        displayRenderer = getDefaultDisplayRenderer();
+      }
+    }
+    displayRenderer.setDisplay(this);
+
+    // initialize ScalarMap's, ShadowDisplayReal's and Control's
+    clearMaps();
+  }
+
+  // suck in any remote ScalarMaps
+
+  /**
+   * 
+   *
+   * @param rmtDpy 
+   */
+  void copyScalarMaps(RemoteDisplay rmtDpy) {
+    Vector m;
+    try {
+      m = rmtDpy.getMapVector();
+    }
+    catch (Exception e) {
+      System.err.println("Couldn't copy ScalarMaps");
+      return;
+    }
+
+    Enumeration me = m.elements();
+    while(me.hasMoreElements()) {
+      ScalarMap sm = (ScalarMap)me.nextElement();
+      try {
+        addMap((ScalarMap)sm.clone());
+      }
+      catch (DisplayException de) {
+        try {
+          addMap(new ScalarMap(sm.getScalar(), sm.getDisplayScalar()));
+        }
+        catch (Exception e) {
+          System.err.println("Couldn't copy remote ScalarMap " + sm);
+        }
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * copy ConstantMaps from RemoteDisplay to this
+   * @param rmtDpy RemoteDisplay to get ConstantMaps from
+   */
+  void copyConstantMaps(RemoteDisplay rmtDpy) {
+    Vector c;
+    try {
+      c = rmtDpy.getConstantMapVector();
+    }
+    catch (Exception e) {
+      System.err.println("Couldn't copy ConstantMaps");
+      return;
+    }
+
+    Enumeration ce = c.elements();
+    while(ce.hasMoreElements()) {
+      ConstantMap cm = (ConstantMap)ce.nextElement();
+      try {
+        addMap((ConstantMap)cm.clone());
+      }
+      catch (DisplayException de) {
+        try {
+          addMap(new ConstantMap(cm.getConstant(), cm.getDisplayScalar()));
+        }
+        catch (Exception e) {
+          System.err.println("Couldn't copy remote ConstantMap " + cm);
+        }
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * copy GraphicsModeControl settings from RemoteDisplay to this
+   * @param rmtDpy RemoteDisplay to get GraphicsModeControl settings from
+   */
+  void copyGraphicsModeControl(RemoteDisplay rmtDpy) {
+    GraphicsModeControl rc;
+    try {
+      getGraphicsModeControl().syncControl(rmtDpy.getGraphicsModeControl());
+    }
+    catch (UnmarshalException ue) {
+      System.err.println("Couldn't copy remote GraphicsModeControl");
+      return;
+    }
+    catch (java.rmi.ConnectException ce) {
+      System.err.println("Couldn't copy remote GraphicsModeControl");
+      return;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return;
+    }
+
+  }
+
+  /**
+   * copy DataReferences from RemoteDisplay to this
+   * @param rmtDpy RemoteDisplay to get DataReferences from
+   * @param localRefs array of DataReferences: don't get any
+   *                  DataReference from rmtDpy that has the same
+   *                  name as a DataReference in localRefs
+   */
+  private void copyRefLinks(RemoteDisplay rmtDpy, DataReference[] localRefs) {
+
+    Vector ml;
+    if (rmtDpy == null) return;
+    try {
+      ml = rmtDpy.getReferenceLinks();
+    }
+    catch (UnmarshalException ue) {
+      System.err.println("Couldn't copy remote DataReferences");
+      return;
+    }
+    catch (java.rmi.ConnectException ce) {
+      System.err.println("Couldn't copy remote DataReferences");
+      return;
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      return;
+    }
+
+    String[] refNames;
+    if (localRefs == null) {
+      refNames = null;
+    }
+    else {
+      refNames = new String[localRefs.length];
+      for (int i = 0; i < refNames.length; i++) {
+        try {
+          refNames[i] = localRefs[i].getName();
+        }
+        catch (VisADException ve) {
+          refNames[i] = null;
+        }
+        catch (RemoteException re) {
+          refNames[i] = null;
+        }
+      }
+    }
+
+    Enumeration mle = ml.elements();
+    if (mle.hasMoreElements()) {
+
+      DataRenderer dr = displayRenderer.makeDefaultRenderer();
+      String defaultClass = dr.getClass().getName();
+
+      while(mle.hasMoreElements()) {
+        RemoteReferenceLink link = (RemoteReferenceLink)mle.nextElement();
+
+        // get reference to Data object
+        RemoteDataReference ref;
+        try {
+          ref = link.getReference();
+        }
+        catch (Exception e) {
+          System.err.println("Couldn't copy remote DataReference");
+          ref = null;
+        }
+
+        if (ref != null && refNames != null) {
+          String rName;
+          try {
+            rName = ref.getName();
+          }
+          catch (VisADException ve) {
+            System.err.println("Couldn't get DataReference name");
+            rName = null;
+          }
+          catch (RemoteException re) {
+            System.err.println("Couldn't get remote DataReference name");
+            rName = null;
+          }
+
+          if (rName != null) {
+            for (int i = 0; i < refNames.length; i++) {
+              if (rName.equals(refNames[i])) {
+                ref = null;
+                break;
+              }
+            }
+          }
+        }
+
+        if (ref != null) {
+
+          // build array of ConstantMap values
+          ConstantMap[] cm = null;
+          try {
+            Vector v = link.getConstantMapVector();
+            int len = v.size();
+            if (len > 0) {
+              cm = new ConstantMap[len];
+              for (int i = 0; i < len; i++) {
+                cm[i] = (ConstantMap)v.elementAt(i);
+              }
+            }
+          }
+          catch (Exception e) {
+            System.err.println(
+              "Couldn't copy ConstantMaps" + " for remote DataReference");
+          }
+
+          // get proper DataRenderer
+          DataRenderer renderer;
+          try {
+            String newClass = link.getRendererClassName();
+            if (newClass.equals(defaultClass)) {
+              renderer = null;
+            }
+            else {
+              Object obj = Class.forName(newClass).newInstance();
+              renderer = (DataRenderer)obj;
+            }
+          }
+          catch (Exception e) {
+            System.err.println(
+              "Couldn't copy remote DataRenderer name" + "; using " +
+              defaultClass);
+            renderer = null;
+          }
+
+          // build RemoteDisplayImpl to which reference is attached
+          try {
+            RemoteDisplayImpl rd = new RemoteDisplayImpl(this);
+
+            // if this reference uses the default renderer...
+            if (renderer == null) {
+              rd.addReference(ref, cm);
+            }
+            else {
+              rd.addReferences(renderer, ref, cm);
+            }
+          }
+          catch (Exception e) {
+            System.err.println("Couldn't add remote DataReference " + ref);
+          }
+        }
+      }
+    }
+
+  }
+
+  /**
+   * copy Data from RemoteDisplay to this
+   * @param rmtDpy RemoteDisplay to get Data from
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  protected void syncRemoteData(RemoteDisplay rmtDpy)
+          throws VisADException, RemoteException {
+    copyScalarMaps(rmtDpy);
+    copyConstantMaps(rmtDpy);
+    copyGraphicsModeControl(rmtDpy);
+    copyRefLinks(rmtDpy, null);
+
+    notifyAction();
+
+    waitForTasks();
+
+    // only add remote display as listener *after* we've synced
+    displayMonitor.addRemoteListener(rmtDpy);
+    initializeControls();
+  }
+
+  // get current state of all controls from remote display(s)
+
+  /**
+   * 
+   */
+  private void initializeControls() {
+    ListIterator iter = ControlVector.listIterator();
+    while(iter.hasNext()) {
+      try {
+        Control ctl = (Control)iter.next();
+        ControlMonitorEvent evt;
+        evt = new ControlMonitorEvent(MonitorEvent.CONTROL_INIT_REQUESTED,
+                                      (Control)ctl.clone());
+        displayMonitor.notifyListeners(evt);
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+    }
+  }
+
+  /** RemoteDisplayImpl to this for use with remote DisplayListeners */
+  private RemoteDisplayImpl rd = null;
+
+  /**
+   * Notify this instance's {@link DisplayListener}s.
+   *
+   * @param  id  type of DisplayEvent that is to be sent
+   * @param  x  the horizontal x coordinate for the mouse location in
+   *            the display component
+   * @param  y  the vertical y coordinate for the mouse location in
+   *            the display component
+   * @throws VisADException     if a VisAD failure occurs.
+   * @throws RemoteException    if a Java RMI failure occurs.
+   */
+  public void notifyListeners(int id, int x, int y)
+          throws VisADException, RemoteException {
+    notifyListeners(null, id, x, y);
+    //    notifyListeners(new DisplayEvent(this, id, x, y));
+  }
+
+  /**
+   * Notify this instance's {@link DisplayListener}s.
+   *
+   * @param evt                 The {@link DisplayEvent} to be passed to the
+   *                            {@link DisplayListener}s.
+   * @throws VisADException     if a VisAD failure occurs.
+   * @throws RemoteException    if a Java RMI failure occurs.
+   */
+  public void notifyListeners(DisplayEvent evt)
+          throws VisADException, RemoteException {
+
+    synchronized (eventStatus) {
+      if (!eventStatus[evt.getId()]) return; // ignore disabled events
+    }
+    notifyListeners(evt, 0, 0, 0);
+  }
+
+
+
+  /**
+   * Notify this instance's {@link DisplayListener}s. This method creates a runnable that actually
+   * does the notification. It invokes the runnable directly if its in the Swing event dispatch thread.
+   * Else, it invokes the runnable in a separate thread.
+   *
+   * @param evt                 The {@link DisplayEvent} to be passed to the
+   *                            {@link DisplayListener}s. If this is null then construct the
+   *                             event from the other parameters
+   * @param  id  type of DisplayEvent that is to be sent
+   * @param  x  the horizontal x coordinate for the mouse location in
+   *            the display component
+   * @param  y  the vertical y coordinate for the mouse location in
+   *            the display component
+   */
+  private void notifyListeners(final DisplayEvent evt, final int id,
+                               final int x, final int y) {
+    Runnable runnable = new Runnable() {
+      public void run() {
+        try {
+          DisplayEvent displayEvent = evt;
+          if (displayEvent == null) {
+            displayEvent = new DisplayEvent(DisplayImpl.this, id, x, y);
+          }
+          for (Enumeration listeners =
+                  ((Vector)ListenerVector.clone()).elements();
+                  listeners.hasMoreElements(); ) {
+            DisplayListener listener =
+              (DisplayListener)listeners.nextElement();
+            if (listener instanceof Remote) {
+              if (rd == null) {
+                rd = new RemoteDisplayImpl(DisplayImpl.this);
+              }
+              listener.displayChanged(displayEvent.cloneButDisplay(rd));
+            }
+            else {
+              listener.displayChanged(
+                displayEvent.cloneButDisplay(DisplayImpl.this));
+            }
+          }
+        }
+        catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    };
+
+    if (SwingUtilities.isEventDispatchThread()) {
+      runnable.run();
+    }
+    else {
+      SwingUtilities.invokeLater(runnable);
+    }
+  }
+
+
+  /**
+   * add a DisplayListener
+   * @param listener DisplayListener to add
+   */
+  public void addDisplayListener(DisplayListener listener) {
+    ListenerVector.addElement(listener);
+  }
+
+  /**
+   * remove a DisplayListener
+   * @param listener DisplayListener to remove
+   */
+  public void removeDisplayListener(DisplayListener listener) {
+    ListenerVector.removeElement(listener);
+  }
+
+  /**
+   * @return the java.awt.Component (e.g., JPanel or AppletPanel)
+   *         this DisplayImpl uses; returns null for an offscreen
+   *         DisplayImpl
+   */
+  public Component getComponent() {
+    return component;
+  }
+
+  /**
+   * set the java.awt.Component this DisplayImpl uses
+   * @param c Component to set
+   */
+  public void setComponent(Component c) {
+    if (c != null) {
+      // lazy initialization
+      if (componentListener == null) {
+        componentListener = new ComponentChangedListener(this);
+      }
+      c.addComponentListener(componentListener);
+    }
+    // in case setComponent is called multiple times
+    if (component != null) {
+      if (componentListener != null) {
+        component.removeComponentListener(componentListener);
+      }
+    }
+    component = c;
+  }
+
+  /**
+   * request auto-scaling of ScalarMap ranges the next time
+   * Data are transformed into scene graph elements
+   */
+  public void reAutoScale() {
+    initialize = true;
+// printStack("reAutoScale");
+  }
+
+  /**
+   * if auto is true, re-apply auto-scaling of ScalarMap ranges
+   * every time Display is triggered
+   * @param a flag indicating whether to always re-apply auto-scaling
+   */
+  public void setAlwaysAutoScale(boolean a) {
+    always_initialize = a;
+  }
+
+  /**
+   * request all linked Data to be re-transformed into scene graph
+   * elements
+   */
+  public void reDisplayAll() {
+    redisplay_all = true;
+// printStack("reDisplayAll");
+    notifyAction();
+  }
+
+
+  // CTR - begin code for slaved displays
+
+  /** Internal list of slaves linked to this display. */
+  private Vector Slaves = new Vector();
+
+  /**
+   * link a slave display to this
+   * @param display RemoteSlaveDisplay to link
+   */
+  public void addSlave(RemoteSlaveDisplay display) {
+    if (!Slaves.contains(display)) Slaves.add(display);
+  }
+
+  /**
+   * remove a link between a slave display and this
+   * @param display RemoteSlaveDisplay to remove
+   */
+  public void removeSlave(RemoteSlaveDisplay display) {
+    if (Slaves.contains(display)) Slaves.remove(display);
+  }
+
+  /**
+   * remove all links to slave displays
+   */
+  public void removeAllSlaves() {
+    Slaves.removeAllElements();
+  }
+
+  /**
+   * @return flag indicating whether there are any slave displays
+   *         linked to this display
+   */
+  public boolean hasSlaves() {
+    return !Slaves.isEmpty();
+  }
+
+  /**
+   * update all linked slave displays with the given image
+   * @param img BufferedImage to send to all linked slave displays
+   */
+  public void updateSlaves(BufferedImage img) {
+    // extract pixels from image
+    int width = img.getWidth();
+    int height = img.getHeight();
+    int type = img.getType();
+    int[] pixels = new int[width * height];
+    img.getRGB(0, 0, width, height, pixels, 0, width);
+
+    // encode pixels with RLE
+    int[] encoded = Convert.encodeRLE(pixels);
+
+    synchronized (Slaves) {
+      // send encoded pixels to each slave
+      for (int i = 0; i < Slaves.size(); i++) {
+        RemoteSlaveDisplay d = (RemoteSlaveDisplay)Slaves.elementAt(i);
+        try {
+          d.sendImage(encoded, width, height, type);
+        }
+        catch (java.rmi.ConnectException exc) {
+          // remote slave client has died; remove it from list
+          Slaves.remove(i--);
+        }
+        catch (RemoteException exc) {
+        }
+      }
+    }
+  }
+
+  /**
+   * update all linked slave display with the given message
+   * @param message String to send to all linked slave displays
+   */
+  public void updateSlaves(String message) {
+    synchronized (Slaves) {
+      // send message to each slave
+      for (int i = 0; i < Slaves.size(); i++) {
+        RemoteSlaveDisplay d = (RemoteSlaveDisplay)Slaves.elementAt(i);
+        try {
+          d.sendMessage(message);
+        }
+        catch (java.rmi.ConnectException exc) {
+          // remote slave client has died; remove it from list
+          Slaves.remove(i--);
+        }
+        catch (RemoteException exc) {
+        }
+      }
+    }
+  }
+
+  // CTR - end code for slaved displays
+
+
+  /** Enabled status flag for each DisplayEvent type. */
+  private final boolean[] eventStatus = {
+    true, // (not used)
+    true, // MOUSE_PRESSED
+    true, // FRAME_DONE
+    true, // TRANSFORM_DONE
+    true, // MOUSE_PRESSED_LEFT
+    true, // MOUSE_PRESSED_CENTER
+    true, // MOUSE_PRESSED_RIGHT
+    true, // MOUSE_RELEASED
+    true, // MOUSE_RELEASED_LEFT
+    true, // MOUSE_RELEASED_CENTER
+    true, // MOUSE_RELEASED_RIGHT
+    true, // MAP_ADDED
+    true, // MAPS_CLEARED
+    true, // REFERENCE_ADDED
+    true, // REFERENCE_REMOVED
+    true, // DESTROYED
+    true, // KEY_PRESSED
+    true, // KEY_RELEASED
+    false, // MOUSE_DRAGGED
+    false, // MOUSE_ENTERED
+    false, // MOUSE_EXITED
+    false, // MOUSE_MOVED
+    false, // WAIT_ON
+    false, // WAIT_OFF
+    true, // MAP_REMOVED
+    false, // COMPONENT_RESIZED
+  };
+
+  /**
+   * Enables reporting of a DisplayEvent of a given type
+   * when it occurs in this display.
+   *
+   * @param id DisplayEvent type to enable.  Valid types are:
+   *          <UL>
+   *          <LI>DisplayEvent.FRAME_DONE
+   *          <LI>DisplayEvent.TRANSFORM_DONE
+   *          <LI>DisplayEvent.MOUSE_PRESSED
+   *          <LI>DisplayEvent.MOUSE_PRESSED_LEFT
+   *          <LI>DisplayEvent.MOUSE_PRESSED_CENTER
+   *          <LI>DisplayEvent.MOUSE_PRESSED_RIGHT
+   *          <LI>DisplayEvent.MOUSE_RELEASED_LEFT
+   *          <LI>DisplayEvent.MOUSE_RELEASED_CENTER
+   *          <LI>DisplayEvent.MOUSE_RELEASED_RIGHT
+   *          <LI>DisplayEvent.MAP_ADDED
+   *          <LI>DisplayEvent.MAPS_CLEARED
+   *          <LI>DisplayEvent.REFERENCE_ADDED
+   *          <LI>DisplayEvent.REFERENCE_REMOVED
+   *          <LI>DisplayEvent.DESTROYED
+   *          <LI>DisplayEvent.KEY_PRESSED
+   *          <LI>DisplayEvent.KEY_RELEASED
+   *          <LI>DisplayEvent.MOUSE_DRAGGED
+   *          <LI>DisplayEvent.MOUSE_ENTERED
+   *          <LI>DisplayEvent.MOUSE_EXITED
+   *          <LI>DisplayEvent.MOUSE_MOVED
+   *          <LI>DisplayEvent.WAIT_ON
+   *          <LI>DisplayEvent.WAIT_OFF
+   *          <LI>DisplayEvent.MAP_REMOVED
+   *          <LI>DisplayEvent.COMPONENT_RESIZED
+   *          </UL>
+   */
+  public void enableEvent(int id) {
+
+    if (id < 1 || id >= eventStatus.length) return;
+
+    synchronized (eventStatus) {
+      eventStatus[id] = true;
+    }
+  }
+
+  /**
+   * Disables reporting of a DisplayEvent of a given type
+   * when it occurs in this display.
+   *
+   * @param id DisplayEvent type to disable.  Valid types are:
+   *          <UL>
+   *          <LI>DisplayEvent.FRAME_DONE
+   *          <LI>DisplayEvent.TRANSFORM_DONE
+   *          <LI>DisplayEvent.MOUSE_PRESSED
+   *          <LI>DisplayEvent.MOUSE_PRESSED_LEFT
+   *          <LI>DisplayEvent.MOUSE_PRESSED_CENTER
+   *          <LI>DisplayEvent.MOUSE_PRESSED_RIGHT
+   *          <LI>DisplayEvent.MOUSE_RELEASED_LEFT
+   *          <LI>DisplayEvent.MOUSE_RELEASED_CENTER
+   *          <LI>DisplayEvent.MOUSE_RELEASED_RIGHT
+   *          <LI>DisplayEvent.MAP_ADDED
+   *          <LI>DisplayEvent.MAPS_CLEARED
+   *          <LI>DisplayEvent.REFERENCE_ADDED
+   *          <LI>DisplayEvent.REFERENCE_REMOVED
+   *          <LI>DisplayEvent.DESTROYED
+   *          <LI>DisplayEvent.KEY_PRESSED
+   *          <LI>DisplayEvent.KEY_RELEASED
+   *          <LI>DisplayEvent.MOUSE_DRAGGED
+   *          <LI>DisplayEvent.MOUSE_ENTERED
+   *          <LI>DisplayEvent.MOUSE_EXITED
+   *          <LI>DisplayEvent.MOUSE_MOVED
+   *          <LI>DisplayEvent.WAIT_ON
+   *          <LI>DisplayEvent.WAIT_OFF
+   *          <LI>DisplayEvent.MAP_REMOVED
+   *          <LI>DisplayEvent.COMPONENT_RESIZED
+   *          </UL>
+   */
+  public void disableEvent(int id) {
+
+    if (id < 1 || id >= eventStatus.length) return;
+
+    synchronized (eventStatus) {
+      eventStatus[id] = false;
+    }
+  }
+
+  /**
+   * @param id DisplayEvent type
+   * @return flag indicating whether a DisplayEvent of a given
+   *         type is eported when it occurs in this display.
+   */
+  public boolean isEventEnabled(int id) {
+
+    if (id < 1 || id >= eventStatus.length) {
+      return false;
+    }
+    else {
+      synchronized (eventStatus) {
+        return eventStatus[id];
+      }
+    }
+  }
+
+  /**
+   * Link a reference to this Display.
+   * This method may only be invoked after all links to
+   * {@link visad.ScalarMap ScalarMaps}
+   * have been made.
+   *
+   * @param ref data reference
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   */
+  public void addReference(ThingReference ref)
+          throws VisADException, RemoteException {
+    if (!(ref instanceof DataReference)) {
+      throw new ReferenceException("DisplayImpl.addReference: ref " +
+                                   "must be DataReference");
+    }
+    if (displayRenderer == null) return;
+    addReference((DataReference)ref, null);
+  }
+
+  /**
+   * Replace remote reference with local reference.
+   *
+   * @param rDpy Remote display.
+   * @param ref Local reference which will replace the previous
+   *            reference.
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see visad.DisplayImpl#addReference(visad.ThingReference)
+   */
+  public void replaceReference(RemoteDisplay rDpy, ThingReference ref)
+          throws VisADException, RemoteException {
+    if (!(ref instanceof DataReference)) {
+      throw new ReferenceException("DisplayImpl.replaceReference: ref " +
+                                   "must be DataReference");
+    }
+    if (displayRenderer == null) return;
+    replaceReference(rDpy, (DataReference)ref, null);
+  }
+
+  /**
+   * Add a link to a DataReference object
+   *
+   * @param link The link to the DataReference.
+   *
+   * @exception VisADException If referenced data is null
+   *                           or if a link already exists.
+   * @exception RemoteException If a link could not be made
+   *                            within the remote display.
+   */
+  void addLink(DataDisplayLink link) throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+    addLink(link, true);
+  }
+
+  /**
+   * Add a link to a DataReference object
+   *
+   * @param link The link to the DataReference.
+   * @param syncRemote <tt>true</tt> if this is not just
+   *                   a local link.
+   *
+   * @exception VisADException If referenced data is null
+   *                           or if a link already exists.
+   * @exception RemoteException If a link could not be made
+   *                            within the remote display.
+   */
+  private void addLink(DataDisplayLink link, boolean syncRemote)
+          throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+// System.out.println("addLink " + getName() + " " +
+//                    link.getData().getType()); // IDV
+    super.addLink((ReferenceActionLink)link);
+    if (syncRemote) {
+      notifyListeners(
+        new DisplayReferenceEvent(this, DisplayEvent.REFERENCE_ADDED, link));
+    }
+  }
+
+  /**
+   * Link a reference to this Display.
+   * <tt>ref</tt> must be a local
+   * {@link visad.DataReferenceImpl DataReferenceImpl}.
+   * The {@link visad.ConstantMap ConstantMap} array applies only
+   * to the rendering reference.
+   *
+   * @param ref data reference
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with the data reference
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  public void addReference(DataReference ref, ConstantMap[] constant_maps)
+          throws VisADException, RemoteException {
+    if (!(ref instanceof DataReferenceImpl)) {
+      throw new RemoteVisADException("DisplayImpl.addReference: requires " +
+                                     "DataReferenceImpl");
+    }
+    if (displayRenderer == null) return;
+    if (findReference(ref) != null) {
+      throw new TypeException("DisplayImpl.addReference: link already exists");
+    }
+    DataRenderer renderer = displayRenderer.makeDefaultRenderer();
+    DataDisplayLink[] links = {new DataDisplayLink(ref, this, this,
+                                constant_maps, renderer, getLinkId())};
+    addLink(links[0]);
+    renderer.setLinks(links, this);
+    synchronized (mapslock) {
+      RendererVector.addElement(renderer);
+    }
+
+    initialize |= computeInitialize();
+
+// printStack("addReference");
+    notifyAction();
+  }
+
+  /**
+   * Replace remote reference with local reference.
+   *
+   * @param rDpy Remote display.
+   * @param ref Local reference which will replace the previous
+   *            reference.
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with the data reference
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see visad.DisplayImpl#addReference(visad.DataReference, visad.ConstantMap[])
+   */
+  public void replaceReference(RemoteDisplay rDpy, DataReference ref,
+                               ConstantMap[] constant_maps)
+          throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+    replaceReferences(
+      rDpy, null, new DataReference[] {ref}, new ConstantMap[][] {
+      constant_maps
+    });
+  }
+
+  /**
+   * decide whether an autoscale is needed 
+   *
+   * @return 
+   */
+  private boolean computeInitialize() {
+    boolean init = false;
+    for (Iterator iter = ((java.util.List)MapVector.clone()).iterator();
+            !init && iter.hasNext();
+            init |= ((ScalarMap)iter.next()).doInitialize()) {}
+    if (!init) {
+      AnimationControl control =
+        (AnimationControl)getControl(AnimationControl.class);
+      if (control != null) {
+        init |= (control.getSet() == null && control.getComputeSet());
+      }
+    }
+    return init;
+  }
+
+  /**
+   * Link a RemoteDataReference to this Display.
+   * The {@link visad.ConstantMap ConstantMap} array applies only
+   * to the rendering reference.
+   * For use by addReference() method of RemoteDisplay that adapts this.
+   *
+   * @param ref remote data reference
+   * @param display RemoteDisplay adapting this
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with the data reference
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  void adaptedAddReference(RemoteDataReference ref, RemoteDisplay display,
+                           ConstantMap[] constant_maps)
+          throws VisADException, RemoteException {
+    if (findReference(ref) != null) {
+      throw new TypeException("DisplayImpl.adaptedAddReference: " +
+                              "link already exists");
+    }
+    if (displayRenderer == null) return;
+    DataRenderer renderer = displayRenderer.makeDefaultRenderer();
+    DataDisplayLink[] links = {new DataDisplayLink(ref, this, display,
+                                constant_maps, renderer, getLinkId())};
+    addLink(links[0]);
+    renderer.setLinks(links, this);
+    synchronized (mapslock) {
+      RendererVector.addElement(renderer);
+    }
+
+    initialize |= computeInitialize();
+
+// printStack("adaptedAddReference");
+    notifyAction();
+  }
+
+  /**
+   * Link a reference to this Display using a non-default renderer.
+   * <tt>ref</tt> must be a local
+   * {@link visad.DataReferenceImpl DataReferenceImpl}.
+   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
+   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
+   * than {@link visad.Display Display}
+   *
+   * @param renderer logic to render this data
+   * @param ref data reference
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  public void addReferences(DataRenderer renderer, DataReference ref)
+          throws VisADException, RemoteException {
+    addReferences(renderer, new DataReference[] {ref}, null);
+  }
+
+  /**
+   * Replace remote reference with local reference using
+   * non-default renderer.
+   *
+   * @param rDpy Remote display.
+   * @param renderer logic to render this data
+   * @param ref Local reference which will replace the previous
+   *            reference.
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference)
+   */
+  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
+                                DataReference ref)
+          throws VisADException, RemoteException {
+    replaceReferences(rDpy, renderer, new DataReference[] {ref}, null);
+  }
+
+  /**
+   * Link a reference to this Display using a non-default renderer.
+   * <tt>ref</tt> must be a local
+   * {@link visad.DataReferenceImpl DataReferenceImpl}.
+   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
+   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
+   * than {@link visad.Display Display}
+   *
+   * @param renderer logic to render this data
+   * @param ref data reference
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with the data reference
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  public void addReferences(DataRenderer renderer, DataReference ref,
+                            ConstantMap[] constant_maps)
+          throws VisADException, RemoteException {
+    addReferences(renderer, new DataReference[] {ref}, new ConstantMap[][] {
+      constant_maps
+    });
+  }
+
+  /**
+   * Replace remote reference with local reference using
+   * non-default renderer.
+   *
+   * @param rDpy Remote display.
+   * @param renderer logic to render this data
+   * @param ref Local reference which will replace the previous
+   *            reference.
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with the data reference
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data reference to the remote display.
+   *
+   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference, visad.ConstantMap[])
+   */
+  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
+                                DataReference ref,
+                                ConstantMap[] constant_maps)
+          throws VisADException, RemoteException {
+    replaceReferences(
+      rDpy, renderer, new DataReference[] {ref}, new ConstantMap[][] {
+      constant_maps
+    });
+  }
+
+  /**
+   * Link references to this display using a non-default renderer.
+   * <tt>refs</tt> must be local
+   * {@link visad.DataReferenceImpl DataReferenceImpls}.
+   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
+   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
+   * than {@link visad.Display Display}
+   *
+   * @param renderer logic to render this data
+   * @param refs array of data references
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data references to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  public void addReferences(DataRenderer renderer, DataReference[] refs)
+          throws VisADException, RemoteException {
+    addReferences(renderer, refs, null);
+  }
+
+  /**
+   * Replace remote references with local references.
+   *
+   * @param rDpy Remote display.
+   * @param renderer logic to render this data
+   * @param refs array of local data references
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data references to the remote display.
+   *
+   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference[])
+   */
+  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
+                                DataReference[] refs)
+          throws VisADException, RemoteException {
+    replaceReferences(rDpy, renderer, refs, null);
+  }
+
+  /**
+   * Link references to this display using the non-default renderer.
+   * <tt>refs</tt> must be local
+   * {@link visad.DataReferenceImpl DataReferenceImpls}.
+   * The <tt>maps[i]</tt> array applies only to rendering <tt>refs[i]</tt>.
+   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
+   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
+   * than {@link visad.Display Display}
+   *
+   * @param renderer logic to render this data
+   * @param refs array of data references
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with data references
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data references to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  public void addReferences(DataRenderer renderer, DataReference[] refs,
+                            ConstantMap[][] constant_maps)
+          throws VisADException, RemoteException {
+    addReferences(renderer, refs, constant_maps, true);
+  }
+
+  /**
+   * Link references to this display using the non-default renderer.
+   * <tt>refs</tt> must be local
+   * {@link visad.DataReferenceImpl DataReferenceImpls}.
+   * The <tt>maps[i]</tt> array applies only to rendering <tt>refs[i]</tt>.
+   * This is a method of {@link visad.DisplayImpl DisplayImpl} and
+   * {@link visad.RemoteDisplayImpl RemoteDisplayImpl} rather
+   * than {@link visad.Display Display}
+   *
+   * @param renderer logic to render this data
+   * @param refs array of data references
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with data references.
+   * @param syncRemote <tt>true</tt> if this data should be forwarded
+   *                   to the remote display.
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters
+   * @exception RemoteException if there was a problem adding the
+   *                            data references to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  private void addReferences(DataRenderer renderer, DataReference[] refs,
+                             ConstantMap[][] constant_maps,
+                             boolean syncRemote)
+          throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+    // N.B. This method is called by all replaceReference() methods
+    if (refs.length < 1) {
+      throw new DisplayException("DisplayImpl.addReferences: must have at " +
+                                 "least one DataReference");
+    }
+    if (constant_maps != null && refs.length != constant_maps.length) {
+      throw new DisplayException("DisplayImpl.addReferences: constant_maps " +
+                                 "length must match refs length");
+    }
+    if (!displayRenderer.legalDataRenderer(renderer)) {
+      throw new DisplayException("DisplayImpl.addReferences: illegal " +
+                                 "DataRenderer class");
+    }
+    DataDisplayLink[] links = new DataDisplayLink[refs.length];
+    for (int i = 0; i < refs.length; i++) {
+      if (!(refs[i] instanceof DataReferenceImpl)) {
+        throw new RemoteVisADException("DisplayImpl.addReferences: requires " +
+                                       "DataReferenceImpl");
+      }
+      if (findReference(refs[i]) != null) {
+        throw new TypeException("DisplayImpl.addReferences: link already exists");
+      }
+      if (constant_maps == null) {
+        links[i] = new DataDisplayLink(refs[i], this, this, null, renderer,
+                                       getLinkId());
+      }
+      else {
+        links[i] = new DataDisplayLink(refs[i], this, this, constant_maps[i],
+                                       renderer, getLinkId());
+      }
+      addLink(links[i], syncRemote);
+    }
+    renderer.setLinks(links, this);
+    synchronized (mapslock) {
+      RendererVector.addElement(renderer);
+    }
+
+    initialize |= computeInitialize();
+
+// printStack("addReferences");
+    notifyAction();
+  }
+
+  /**
+   * Replace remote references with local references.
+   *
+   * @param rDpy Remote display.
+   * @param renderer logic to render this data
+   * @param refs array of data references
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with data references.
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters.
+   * @exception RemoteException if there was a problem adding the
+   *                            data references to the remote display.
+   *
+   * @see visad.DisplayImpl#addReferences(visad.DataRenderer, visad.DataReference[], visad.ConstantMap[][])
+   */
+  public void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
+                                DataReference[] refs,
+                                ConstantMap[][] constant_maps)
+          throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+    if (renderer == null) {
+      renderer = displayRenderer.makeDefaultRenderer();
+    }
+
+    removeAllReferences();
+    addReferences(renderer, refs, constant_maps, false);
+    copyRefLinks(rDpy, refs);
+  }
+
+  /**
+   * Link references to this display using the non-default renderer.
+   * <tt>refs</tt> may be a mix of local
+   * {@link visad.DataReferenceImpl DataReferenceImpls} and
+   * {@link visad.RemoteDataReference RemoteDataReferences}.
+   * The <tt>maps[i]</tt> array applies only to rendering <tt>refs[i]</tt>.
+   * For use by addReferences() method of RemoteDisplay that adapts this.
+   *
+   * @param renderer logic to render this data
+   * @param refs array of data references
+   * @param display RemoteDisplay adapting this
+   * @param constant_maps array of {@link visad.ConstantMap ConstantMaps}
+   *                      associated with data references.
+   *
+   * @exception VisADException if there was a problem with one or more
+   *                           parameters
+   * @exception RemoteException if there was a problem adding the
+   *                            data references to the remote display.
+   *
+   * @see <a href="http://www.ssec.wisc.edu/~billh/guide.html#6.1">Section 6.1 of the Developer's Guide</a>
+   */
+  void adaptedAddReferences(DataRenderer renderer, DataReference[] refs,
+                            RemoteDisplay display,
+                            ConstantMap[][] constant_maps)
+          throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+    if (refs.length < 1) {
+      throw new DisplayException("DisplayImpl.addReferences: must have at " +
+                                 "least one DataReference");
+    }
+    if (constant_maps != null && refs.length != constant_maps.length) {
+      throw new DisplayException("DisplayImpl.addReferences: constant_maps " +
+                                 "length must match refs length");
+    }
+    if (!displayRenderer.legalDataRenderer(renderer)) {
+      throw new DisplayException("DisplayImpl.addReferences: illegal " +
+                                 "DataRenderer class");
+    }
+    DataDisplayLink[] links = new DataDisplayLink[refs.length];
+    for (int i = 0; i < refs.length; i++) {
+      if (findReference(refs[i]) != null) {
+        throw new TypeException("DisplayImpl.addReferences: link already exists");
+      }
+      if (refs[i] instanceof DataReferenceImpl) {
+        // refs[i] is local
+        if (constant_maps == null) {
+          links[i] = new DataDisplayLink(refs[i], this, this, null, renderer,
+                                         getLinkId());
+        }
+        else {
+          links[i] = new DataDisplayLink(refs[i], this, this,
+                                         constant_maps[i], renderer,
+                                         getLinkId());
+        }
+      }
+      else {
+        // refs[i] is remote
+        if (constant_maps == null) {
+          links[i] = new DataDisplayLink(refs[i], this, display, null,
+                                         renderer, getLinkId());
+        }
+        else {
+          links[i] = new DataDisplayLink(refs[i], this, display,
+                                         constant_maps[i], renderer,
+                                         getLinkId());
+        }
+      }
+      addLink(links[i]);
+    }
+    renderer.setLinks(links, this);
+    synchronized (mapslock) {
+      RendererVector.addElement(renderer);
+    }
+
+    initialize |= computeInitialize();
+
+// printStack("adaptedAddReferences");
+    notifyAction();
+  }
+
+  /**
+   * remove link to ref, which must be a local DataReferenceImpl;
+   * if ref was added as part of a DataReference  array passed to
+   * addReferences(), remove links to all of them
+   * @param ref ThingReference to remove
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void removeReference(ThingReference ref)
+          throws VisADException, RemoteException {
+    if (!(ref instanceof DataReferenceImpl)) {
+      throw new RemoteVisADException("ActionImpl.removeReference: requires " +
+                                     "DataReferenceImpl");
+    }
+    adaptedDisplayRemoveReference((DataReference)ref);
+    notifyListeners(new DisplayEvent(this, DisplayEvent.REFERENCE_REMOVED));
+  }
+
+  /**
+   * remove DataDisplayLinks from this DisplayImpl
+   * @param links array of DataDisplayLinks to remove
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void removeLinks(DataDisplayLink[] links)
+          throws RemoteException, VisADException {
+    if (displayRenderer == null) return;
+    for (int i = links.length - 1; i >= 0; i--) {
+      if (links[i] != null) {
+        links[i].clearMaps();
+      }
+    }
+
+    super.removeLinks(links);
+  }
+
+  /**
+   * remove link to a DataReference;
+   * uses by removeReference() method of RemoteActionImpl that
+   * adapts this ActionImpl;
+   * because DataReference array input to adaptedAddReferences
+   * may be a mix of local and remote, we tolerate either here
+   * @param ref DataReference to remove
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void adaptedDisplayRemoveReference(DataReference ref)
+          throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+    DataDisplayLink link = (DataDisplayLink)findReference(ref);
+    // don't throw an Exception if link is null: users may try to
+    // remove all DataReferences added by a call to addReferences
+    if (link == null) return;
+    DataRenderer renderer = link.getRenderer();
+    DataDisplayLink[] links = renderer.getLinks();
+    synchronized (mapslock) {
+      renderer.clearAVControls();
+      renderer.clearScene();
+      RendererVector.removeElement(renderer);
+    }
+    removeLinks(links);
+  }
+
+  /**
+   * remove all links to DataReferences.
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void removeAllReferences() throws VisADException, RemoteException {
+
+    if (displayRenderer == null) return;
+    Vector temp = (Vector)RendererVector.clone();
+
+    synchronized (mapslock) {
+      Iterator renderers = temp.iterator();
+      while(renderers.hasNext()) {
+        DataRenderer renderer = (DataRenderer)renderers.next();
+        renderer.clearAVControls();
+        DataDisplayLink[] links = renderer.getLinks();
+        renderers.remove();
+        removeLinks(links);
+        renderer.clearScene();
+      }
+      RendererVector.removeAllElements();
+
+      initialize = true;
+// printStack("removeAllReferences");
+      
+      notifyListeners(new DisplayEvent(this, DisplayEvent.REFERENCE_REMOVED));
+    }
+  }
+
+  /**
+   * trigger possible re-transform of linked Data
+   * used by Controls to notify this DisplayImpl that they
+   * have changed
+   */
+  public void controlChanged() {
+    notifyAction();
+  }
+
+  /**
+   * over-ride ActionImpl.checkTicks() to always return true,
+   * since DisplayImpl always runs doAction to find out if any
+   * linked Data needs to be re-transformed
+   * @return true
+   */
+  public boolean checkTicks() {
+    return true;
+  }
+
+  /**
+   * has this display been destroyed
+   *
+   * @return  has this display been destroyed
+   */
+  public boolean isDestroyed() {
+    return destroyed;
+  }
+
+  /**
+   * destroy this display: clear all references to objects
+   * (so they can be garbage collected), stop all Threads
+   * and remove all links
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void destroy() throws VisADException, RemoteException {
+    if (destroyed) return;
+    destroyed = true;
+    VisADException thrownVE = null;
+    RemoteException thrownRE = null;
+
+    if (mapslock == null) mapslock = new Object();
+    synchronized (mapslock) {
+      stop();
+
+      if (displayActivity != null) {
+        displayActivity.destroy();
+      }
+
+      // tell everybody we're going away
+      notifyListeners(new DisplayEvent(this, DisplayEvent.DESTROYED));
+
+      // remove all listeners
+      synchronized (ListenerVector) {
+        ListenerVector.removeAllElements();
+      }
+
+      try {
+        removeAllReferences();
+      }
+      catch (RemoteException re) {
+        thrownRE = re;
+      }
+      catch (VisADException ve) {
+        thrownVE = ve;
+      }
+
+      try {
+        clearMaps();
+      }
+      catch (RemoteException re) {
+        thrownRE = re;
+      }
+      catch (VisADException ve) {
+        thrownVE = ve;
+      }
+
+      AnimationControl control =
+        (AnimationControl)getControl(AnimationControl.class);
+      if (control != null) {
+        control.stop();
+      }
+
+      if (thrownVE != null) {
+        throw thrownVE;
+      }
+      if (thrownRE != null) {
+        throw thrownRE;
+      }
+
+      // get rid of dangling references
+      /* done in clearMaps()
+          verify (RendererVector == null)
+          MapVector.removeAllElements();
+          ConstantMapVector.removeAllElements();
+          RealTypeVector.removeAllElements();
+      */
+      DisplayRealTypeVector.removeAllElements();
+      ControlVector.removeAllElements();
+      RendererSourceListeners.removeAllElements();
+      RmtSrcListeners.removeAllElements();
+      MessageListeners.removeAllElements();
+      ListenerVector.removeAllElements();
+      Slaves.removeAllElements();
+      displayRenderer = null; // this disables most DisplayImpl methods
+      if (component != null) {
+        component.removeComponentListener(componentListener);
+      }
+      componentListener = null;
+      component = null;
+      mouse = null;
+      displayMonitor = null;
+      displaySync = null;
+      displayActivity = null;
+      printer = null;
+      rd = null;
+      widgetPanel = null;
+    } // end synchronized (mapslock)
+  }
+
+  /**
+   * Check if any Data need re-transform, and if so, do it.
+   * Check if auto-scaling is needed for any ScalarMaps, and
+   * if so, do it. This method does the real work of DisplayImpl.
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void doAction() throws VisADException, RemoteException {
+
+    if (displayRenderer == null) return;
+    if (mapslock == null) return;
+    // put a try/finally block around the setWaitFlag(true), so that we unset
+    // the flag before exiting even if an Exception or Error is thrown
+    try {
+// System.out.println("DisplayImpl call setWaitFlag(true)");
+      displayRenderer.setWaitFlag(true);
+      synchronized (mapslock) {
+        if (RendererVector == null || displayRenderer == null) {
+// System.out.println("DisplayImpl call setWaitFlag(false)");
+          if (displayRenderer != null) displayRenderer.setWaitFlag(false);
+          return;
+        }
+        // set tickFlag-s in changed Control-s
+        // clone MapVector to avoid need for synchronized access
+        Vector tmap = (Vector)MapVector.clone();
+        Enumeration maps = tmap.elements();
+        while(maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap)maps.nextElement();
+          map.setTicks();
+        }
+
+        // set ScalarMap.valueIndex-s and valueArrayLength
+        int n = getDisplayScalarCount();
+        int[] scalarToValue = new int[n];
+        for (int i = 0; i < n; i++)
+          scalarToValue[i] = -1;
+        valueArrayLength = 0;
+        maps = tmap.elements();
+        while(maps.hasMoreElements()) {
+          ScalarMap map = ((ScalarMap)maps.nextElement());
+          DisplayRealType dreal = map.getDisplayScalar();
+          map.setValueIndex(valueArrayLength);
+          valueArrayLength++;
+        }
+
+        // set valueToScalar and valueToMap arrays
+        valueToScalar = new int[valueArrayLength];
+        valueToMap = new int[valueArrayLength];
+        for (int i = 0; i < tmap.size(); i++) {
+          ScalarMap map = (ScalarMap)tmap.elementAt(i);
+          DisplayRealType dreal = map.getDisplayScalar();
+          valueToScalar[map.getValueIndex()] = getDisplayScalarIndex(dreal);
+          valueToMap[map.getValueIndex()] = i;
+        }
+
+        // invoke each DataRenderer (to prepare associated Data objects
+        // for transformation)
+        // clone RendererVector to avoid need for synchronized access
+        Vector temp = ((Vector)RendererVector.clone());
+        Enumeration renderers = temp.elements();
+        boolean go = false;
+        if (initialize) {
+          renderers = temp.elements();
+          while(!go && renderers.hasMoreElements()) {
+            DataRenderer renderer = (DataRenderer)renderers.nextElement();
+            go |= renderer.checkAction();
+          }
+        }
+/*
+System.out.println("initialize = " + initialize + " go = " + go +
+                     " redisplay_all = " + redisplay_all);
+*/
+        if (redisplay_all) {
+          go = true;
+// System.out.println("redisplay_all = " + redisplay_all + " go = " + go);
+          redisplay_all = false;
+        }
+
+        if (!initialize || go) {
+          boolean lastinitialize = initialize;
+          displayRenderer.prepareAction(temp, tmap, go, initialize);
+
+          // WLH 10 May 2001
+          boolean anyBadMap = false;
+          maps = tmap.elements();
+          while(maps.hasMoreElements()) {
+            ScalarMap map = ((ScalarMap)maps.nextElement());
+            if (map.badRange()) {
+              anyBadMap = true;
+              // System.out.println("badRange " + map);
+            }
+          }
+
+          renderers = temp.elements();
+          boolean badScale = false;
+          while(renderers.hasMoreElements()) {
+            DataRenderer renderer = (DataRenderer)renderers.nextElement();
+            boolean badthis = renderer.getBadScale(anyBadMap);
+            badScale |= badthis;
+/*
+            if (badthis) {
+              DataDisplayLink[] links = renderer.getLinks();
+              System.out.println("badthis " +
+                                 links[0].getThingReference().getName());
+            }
+*/
+          }
+          initialize = badScale;
+          if (always_initialize) initialize = true;
+
+          if (initialize && !lastinitialize) {
+            displayRenderer.prepareAction(temp, tmap, go, initialize);
+          }
+
+          boolean transform_done = false;
+
+// System.out.println("DisplayImpl.doAction transform");
+// int i = 0;
+          boolean any_exceptions = false;
+          renderers = temp.elements();
+          while(renderers.hasMoreElements()) {
+// System.out.println("DisplayImpl invoke renderer.doAction " + i);
+// i++;
+            DataRenderer renderer = (DataRenderer)renderers.nextElement();
+
+            boolean this_transform = renderer.doAction();
+            transform_done |= this_transform;
+            any_exceptions |= !renderer.getExceptionVector().isEmpty();
+/*
+            if (this_transform) {
+              DataDisplayLink[] links = renderer.getLinks();
+              System.out.println("transform " + getName() + " " +
+                                 links[0].getThingReference().getName());
+            }
+*/
+          }
+          if (transform_done) {
+// System.out.println(getName() + " invoked " + i + " renderers");
+            AnimationControl control =
+              (AnimationControl)getControl(AnimationControl.class);
+            if (control != null) {
+              control.init();
+            }
+            synchronized (ControlVector) {
+              Enumeration controls = ControlVector.elements();
+              while(controls.hasMoreElements()) {
+                Control cont = (Control)controls.nextElement();
+                if (ValueControl.class.isInstance(cont)) {
+                  ((ValueControl)cont).init();
+                }
+              }
+            }
+          }
+
+          if (transform_done || any_exceptions) {
+            notifyListeners(DisplayEvent.TRANSFORM_DONE, 0, 0);
+          }
+
+        }
+
+        // clear tickFlag-s in Control-s
+        maps = tmap.elements();
+        while(maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap)maps.nextElement();
+          map.resetTicks();
+        }
+      } // end synchronized (mapslock)
+    }
+    finally {
+// System.out.println("DisplayImpl call setWaitFlag(false)");
+      if (displayRenderer != null) displayRenderer.setWaitFlag(false);
+    }
+
+  }
+
+  /**
+   * @return the default DisplayRenderer for this DisplayImpl
+   */
+  protected abstract DisplayRenderer getDefaultDisplayRenderer();
+
+  /**
+   * @return the DisplayRenderer associated with this DisplayImpl
+   */
+  public DisplayRenderer getDisplayRenderer() {
+    return displayRenderer;
+  }
+
+  /**
+   * Returns a clone of the list of DataRenderer-s.  A clone is returned
+   * to avoid concurrent access problems by the Display thread.
+   * @return                    A clone of the list of DataRenderer-s.
+   * @see #getRenderers()
+   */
+  public Vector getRendererVector() {
+    return (Vector)RendererVector.clone();
+  }
+
+  /**
+   * @return the number of DisplayRealTypes in ScalarMaps
+   *         linked to this DisplayImpl
+   */
+  public int getDisplayScalarCount() {
+    return DisplayRealTypeVector.size();
+  }
+
+  /**
+   * get the DisplayRealType with the given index
+   * @param index index into Vector of DisplayRealTypes
+   * @return the indexed DisplayRealType
+   */
+  public DisplayRealType getDisplayScalar(int index) {
+    return (DisplayRealType)DisplayRealTypeVector.elementAt(index);
+  }
+
+  /**
+   * get the index for the given DisplayRealType
+   * @param dreal DisplayRealType to search for
+   * @return the index of dreal in Vector of DisplayRealTypes
+   */
+  public int getDisplayScalarIndex(DisplayRealType dreal) {
+    int dindex;
+    synchronized (DisplayRealTypeVector) {
+      DisplayTupleType tuple = dreal.getTuple();
+      if (tuple != null) {
+        int n = tuple.getDimension();
+        for (int i = 0; i < n; i++) {
+          try {
+            DisplayRealType ereal = (DisplayRealType)tuple.getComponent(i);
+            int eindex = DisplayRealTypeVector.indexOf(ereal);
+            if (eindex < 0) {
+              DisplayRealTypeVector.addElement(ereal);
+            }
+          }
+          catch (VisADException e) {
+          }
+        }
+      }
+      dindex = DisplayRealTypeVector.indexOf(dreal);
+      if (dindex < 0) {
+        DisplayRealTypeVector.addElement(dreal);
+        dindex = DisplayRealTypeVector.indexOf(dreal);
+      }
+    }
+    return dindex;
+  }
+
+  /**
+   * @return the number of ScalarTypes in ScalarMaps
+   *         linked to this DisplayImpl
+   */
+  public int getScalarCount() {
+    return RealTypeVector.size();
+  }
+
+  /**
+   * get the ScalarType with the given index
+   * @param index index into Vector of ScalarTypes
+   * @return the indexed ScalarType
+   */
+  public ScalarType getScalar(int index) {
+    return (ScalarType)RealTypeVector.elementAt(index);
+  }
+
+  /**
+   * get the index for the given ScalarType
+   * @param real ScalarType to search for
+   * @return the index of real in Vector of ScalarTypes
+   * @throws RemoteException an RMI error occurred
+   */
+  public int getScalarIndex(ScalarType real) throws RemoteException {
+    return RealTypeVector.indexOf(real);
+  }
+
+  /**
+   * add a ScalarMap to this Display, assuming a local source
+   * @param map ScalarMap to add
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void addMap(ScalarMap map) throws VisADException, RemoteException {
+    addMap(map, VisADEvent.LOCAL_SOURCE);
+  }
+
+  /**
+   * add a ScalarMap to this Display
+   * @param map ScalarMap to add
+   * @param remoteId remote source for collab, or VisADEvent.LOCAL_SOURCE
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void addMap(ScalarMap map, int remoteId)
+          throws VisADException, RemoteException {
+
+    if (displayRenderer == null) return;
+    synchronized (mapslock) {
+      int index;
+      if (!RendererVector.isEmpty()) {
+        ScalarType st = map.getScalar();
+        if (st != null) {
+          Vector temp = (Vector)RendererVector.clone();
+          Iterator renderers = temp.iterator();
+          while(renderers.hasNext()) {
+            DataRenderer renderer = (DataRenderer)renderers.next();
+            DataDisplayLink[] links = renderer.getLinks();
+            for (int i = 0; i < links.length; i++) {
+              if (MathType.findScalarType(links[i].getType(), st)) {
+/* WLH relax addMap() & clearMap() 17 Dec 2002
+                throw new DisplayException("DisplayImpl.addMap(): " +
+                            "ScalarType may not occur in any DataReference");
+*/
+                DataReference ref = links[i].getDataReference();
+                if (ref != null) ref.incTick();
+              }
+            }
+          }
+        }
+      }
+      DisplayRealType type = map.getDisplayScalar();
+      if (!displayRenderer.legalDisplayScalar(type)) {
+        throw new BadMappingException("DisplayImpl.addMap: " +
+                                      map.getDisplayScalar() +
+                                      " illegal for this DisplayRenderer");
+      }
+      if ((Display.LineWidth.equals(type) ||
+           Display.PointSize.equals(type) ||
+           Display.PointMode.equals(type) ||
+           Display.LineStyle.equals(type) ||
+           Display.TextureEnable.equals(type) ||
+           Display.MissingTransparent.equals(type) ||
+           Display.PolygonMode.equals(type) ||
+           Display.CurvedSize.equals(type) ||
+           Display.ColorMode.equals(type) ||
+           Display.PolygonOffset.equals(type) ||
+           Display.PolygonOffsetFactor.equals(type) ||
+           Display.AdjustProjectionSeam.equals(type) ||
+           Display.Texture3DMode.equals(type) ||
+           Display.CacheAppearances.equals(type) ||
+           Display.MergeGeometries.equals(type)) && !(map
+           instanceof ConstantMap)) {
+        throw new BadMappingException("DisplayImpl.addMap: " +
+                                      map.getDisplayScalar() +
+                                      " for ConstantMap only");
+      }
+// System.out.println("addMap " + getName() + " " + map.getScalar() +
+//                    " -> " + map.getDisplayScalar()); // IDV
+      map.setDisplay(this);
+
+      if (map instanceof ConstantMap) {
+        synchronized (ConstantMapVector) {
+          Enumeration maps = ConstantMapVector.elements();
+          while(maps.hasMoreElements()) {
+            ConstantMap map2 = (ConstantMap)maps.nextElement();
+            if (map2.getDisplayScalar().equals(map.getDisplayScalar())) {
+              throw new BadMappingException("Display.addMap: two ConstantMaps " +
+                                            "have the same DisplayScalar");
+            }
+          }
+          ConstantMapVector.addElement(map);
+        }
+        if (!RendererVector.isEmpty()) {
+          reDisplayAll(); // WLH 2 April 2002
+        }
+      }
+      else { // !(map instanceof ConstantMap)
+        // add to RealTypeVector and set ScalarIndex
+        ScalarType real = map.getScalar();
+        DisplayRealType dreal = map.getDisplayScalar();
+        synchronized (MapVector) {
+          Enumeration maps = MapVector.elements();
+          while(maps.hasMoreElements()) {
+            ScalarMap map2 = (ScalarMap)maps.nextElement();
+            if (real.equals(map2.getScalar()) &&
+                dreal.equals(map2.getDisplayScalar()) &&
+                !dreal.equals(Display.Shape)) {
+              throw new BadMappingException("Display.addMap: two ScalarMaps " +
+                      "with the same RealType & DisplayRealType");
+            }
+            if (dreal.equals(Display.Animation) &&
+                map2.getDisplayScalar().equals(Display.Animation)) {
+              throw new BadMappingException("Display.addMap: two RealTypes " +
+                                            "are mapped to Animation");
+            }
+          }
+          MapVector.addElement(map);
+          needWidgetRefresh = true;
+        }
+        synchronized (RealTypeVector) {
+          index = RealTypeVector.indexOf(real);
+          if (index < 0) {
+            RealTypeVector.addElement(real);
+            index = RealTypeVector.indexOf(real);
+          }
+        }
+        map.setScalarIndex(index);
+        map.setControl();
+        // WLH 18 June 2002
+        if (!RendererVector.isEmpty() && map.doInitialize()) {
+          reAutoScale();
+        }
+      } // end !(map instanceof ConstantMap)
+      addDisplayScalar(map);
+      notifyListeners(
+        new DisplayMapEvent(this, DisplayEvent.MAP_ADDED, map, remoteId));
+
+      // make sure we monitor all changes to this ScalarMap
+      map.addScalarMapListener(displayMonitor);
+    }
+
+  }
+
+  /**
+   * remove a ScalarMap from this Display, assuming a local source
+   * @param map ScalarMap to remove
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void removeMap(ScalarMap map)
+          throws VisADException, RemoteException {
+    removeMap(map, VisADEvent.LOCAL_SOURCE);
+  }
+
+  /**
+   * remove a ScalarMap from this Display
+   * @param map ScalarMap to add
+   * @param remoteId remote source for collab, or VisADEvent.LOCAL_SOURCE
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void removeMap(ScalarMap map, int remoteId)
+          throws VisADException, RemoteException {
+
+    if (displayRenderer == null) return;
+// System.out.println("removeMap " + getName() + " " + map.getScalar() +
+//                    " -> " + map.getDisplayScalar()); // IDV
+    synchronized (mapslock) {
+      // can have multiple equals() maps to Shape, so test for ==
+      int index = MapVector.indexOf(map);
+      while(index >= 0 && map != MapVector.elementAt(index)) {
+        index = MapVector.indexOf(map, index + 1);
+      }
+      if (index < 0) {
+        throw new BadMappingException("Display.removeMap: " + map + " not " +
+                                      "in Display " + getName());
+      }
+
+      //Remove the control from the ControlVector
+      Control control = map.getControl();
+      synchronized (ControlVector) {
+        if (control != null && ControlVector.contains(control)) {
+          ControlVector.remove(control);
+          control.removeControlListener((ControlListener)displayMonitor);
+          control.setInstanceNumber(-1);
+          control.setIndex(-1);
+          for (int i = 0; i < ControlVector.size(); i++) {
+            Control ctl = (Control)ControlVector.get(i);
+            ctl.setIndex(i);
+          }
+        }
+      }
+
+      MapVector.removeElementAt(index);
+      ScalarType real = map.getScalar();
+      if (real != null) {
+        Enumeration maps = MapVector.elements();
+        boolean any = false;
+        while(maps.hasMoreElements()) {
+          ScalarMap map2 = (ScalarMap)maps.nextElement();
+          if (real.equals(map2.getScalar())) any = true;
+        }
+        if (!any) {
+          // if real is not used by any other ScalarMap, remove it
+          // and adjust ScalarIndex of all other ScalarMaps
+          RealTypeVector.removeElement(real);
+
+          maps = MapVector.elements();
+          while(maps.hasMoreElements()) {
+            ScalarMap map2 = (ScalarMap)maps.nextElement();
+            ScalarType real2 = map2.getScalar();
+            int index2 = RealTypeVector.indexOf(real2);
+            if (index2 < 0) {
+              throw new BadMappingException("Display.removeMap: impossible 1");
+            }
+            map2.setScalarIndex(index2);
+          }
+        } // end if (!any)
+      } // end if (real != null)
+
+      // trigger events
+      if (map instanceof ConstantMap) {
+        if (!RendererVector.isEmpty()) {
+          reDisplayAll();
+        }
+      }
+      else { // !(map instanceof ConstantMap)
+        if (!RendererVector.isEmpty()) {
+          ScalarType st = map.getScalar();
+          if (st != null) { // not necessary for !(map instanceof ConstantMap)
+            Vector temp = (Vector)RendererVector.clone();
+            Iterator renderers = temp.iterator();
+            while(renderers.hasNext()) {
+              DataRenderer renderer = (DataRenderer)renderers.next();
+              DataDisplayLink[] links = renderer.getLinks();
+              for (int i = 0; i < links.length; i++) {
+                if (MathType.findScalarType(links[i].getType(), st)) {
+                  DataReference ref = links[i].getDataReference();
+                  if (ref != null) ref.incTick();
+                }
+              }
+            }
+          }
+        }
+        // add DRM 2003-02-21
+        if (map.getAxisScale() != null) {
+          DisplayRenderer displayRenderer = getDisplayRenderer();
+          displayRenderer.clearScale(map.getAxisScale());
+
+          Enumeration maps = MapVector.elements();
+          while(maps.hasMoreElements()) {
+            ScalarMap map2 = (ScalarMap)maps.nextElement();
+            AxisScale axisScale = map2.getAxisScale();
+            if (axisScale != null) {
+              displayRenderer.clearScale(axisScale);
+              axisScale.setAxisOrdinal(-1);
+            }
+          }
+          maps = MapVector.elements();
+          while(maps.hasMoreElements()) {
+            ScalarMap map2 = (ScalarMap)maps.nextElement();
+            AxisScale axisScale = map2.getAxisScale();
+            if (axisScale != null) {
+              map2.makeScale();
+            }
+          }
+
+
+        }
+        needWidgetRefresh = true;
+      } // end !(map instanceof ConstantMap)
+      notifyListeners(
+        new DisplayMapEvent(this, DisplayEvent.MAP_REMOVED, map, remoteId));
+      map.nullDisplay(); // ??
+    } // end synchronized (mapslock)
+
+  }
+
+  /**
+   * add a ScalarType from a ScalarMap from this Display
+   * @param map ScalarMap whose ScalarType to add
+   */
+  void addDisplayScalar(ScalarMap map) {
+    int index;
+
+    if (displayRenderer == null) return;
+    DisplayRealType dreal = map.getDisplayScalar();
+    synchronized (DisplayRealTypeVector) {
+      DisplayTupleType tuple = dreal.getTuple();
+      if (tuple != null) {
+        int n = tuple.getDimension();
+        for (int i = 0; i < n; i++) {
+          try {
+            DisplayRealType ereal = (DisplayRealType)tuple.getComponent(i);
+            int eindex = DisplayRealTypeVector.indexOf(ereal);
+            if (eindex < 0) {
+              DisplayRealTypeVector.addElement(ereal);
+            }
+          }
+          catch (VisADException e) {
+          }
+        }
+      }
+      index = DisplayRealTypeVector.indexOf(dreal);
+      if (index < 0) {
+        DisplayRealTypeVector.addElement(dreal);
+        index = DisplayRealTypeVector.indexOf(dreal);
+      }
+    }
+    map.setDisplayScalarIndex(index);
+  }
+
+  /**
+   * remove all ScalarMaps linked this display;
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void clearMaps() throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+// System.out.println("clearMaps " + getName() + "\n"); // IDV
+    synchronized (mapslock) {
+      if (!RendererVector.isEmpty()) {
+/* WLH relax addMap() & clearMap() 17 Dec 2002
+        throw new DisplayException("DisplayImpl.clearMaps: RendererVector " +
+                                   "must be empty");
+*/
+        reDisplayAll();
+      }
+
+      Enumeration maps;
+
+      synchronized (MapVector) {
+        maps = MapVector.elements();
+        while(maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap)maps.nextElement();
+          map.nullDisplay();
+          map.removeScalarMapListener(displayMonitor);
+        }
+        MapVector.removeAllElements();
+        needWidgetRefresh = true;
+      }
+      synchronized (ConstantMapVector) {
+        maps = ConstantMapVector.elements();
+        while(maps.hasMoreElements()) {
+          ConstantMap map = (ConstantMap)maps.nextElement();
+          map.nullDisplay();
+          map.removeScalarMapListener(displayMonitor);
+        }
+        ConstantMapVector.removeAllElements();
+      }
+
+      synchronized (ControlVector) {
+        // clear Control-s associated with this Display
+        maps = ControlVector.elements();
+        while(maps.hasMoreElements()) {
+          Control ctl = (Control)maps.nextElement();
+          ctl.removeControlListener((ControlListener)displayMonitor);
+          ctl.setInstanceNumber(-1);
+        }
+        ControlVector.removeAllElements();
+        // one each GraphicsModeControl and ProjectionControl always exists
+        Control control = (Control)getGraphicsModeControl();
+        if (control != null) addControl(control);
+        control = (Control)getProjectionControl();
+        if (control != null) addControl(control);
+        // don't forget RendererControl
+        control = (Control)displayRenderer.getRendererControl();
+        if (control != null) addControl(control);
+      }
+      // clear RealType-s from RealTypeVector
+      // removeAllElements is synchronized
+      RealTypeVector.removeAllElements();
+      synchronized (DisplayRealTypeVector) {
+        // clear DisplayRealType-s from DisplayRealTypeVector
+        DisplayRealTypeVector.removeAllElements();
+        // put system intrinsic DisplayRealType-s in DisplayRealTypeVector
+        for (int i = 0; i < DisplayRealArray.length; i++) {
+          DisplayRealTypeVector.addElement(DisplayRealArray[i]);
+        }
+      }
+      displayRenderer.clearAxisOrdinals();
+      displayRenderer.setAnimationString(new String[] {null, null});
+    }
+
+    notifyListeners(new DisplayEvent(this, DisplayEvent.MAPS_CLEARED));
+  }
+
+  /**
+   * @return clone of Vector of ScalarMaps linked to this DisplayImpl
+   *         (doesn't include ConstantMaps)
+   */
+  public Vector getMapVector() {
+    return (Vector)MapVector.clone();
+  }
+
+  /**
+   * @return clone of Vector of ConstantMaps linked to this DisplayImpl
+   */
+  public Vector getConstantMapVector() {
+    return (Vector)ConstantMapVector.clone();
+  }
+
+  /**
+   * Get the instance number of this <CODE>Control</CODE>
+   * in the internal <CODE>ControlVector</CODE>.
+   *
+   * @param ctl <CODE>Control</CODE> to look for.
+   *
+   * @return Instance number (<CODE>-1</CODE> if not found.)
+   */
+  private int getInstanceNumber(Control ctl) {
+    Class ctlClass = ctl.getClass();
+    int num = 0;
+    Enumeration en = ControlVector.elements();
+    while(en.hasMoreElements()) {
+      Control c = (Control)en.nextElement();
+      if (ctlClass.isInstance(c)) {
+        if (ctl == c) {
+          return num;
+        }
+        num++;
+      }
+    }
+
+    return -1;
+  }
+
+  /**
+   * Return the ID used to identify the collaborative connection to
+   * the specified remote display.<br>
+   * <br>
+   * <b>WARNING!</b>  Due to limitations in the Java RMI implementation,
+   * this only works with an exact copy of the RemoteDisplay used to
+   * create the collaboration link.
+   *
+   * @param rmtDpy the specified remote display.
+   * @return <tt>DisplayMonitor.UNKNOWN_LISTENER_ID</tt> if not found;
+   *         otherwise, returns the ID.
+   * @throws RemoteException an RMI error occurred
+   */
+  public int getConnectionID(RemoteDisplay rmtDpy) throws RemoteException {
+    if (displayMonitor == null) return DisplayMonitor.UNKNOWN_LISTENER_ID;
+    return displayMonitor.getConnectionID(rmtDpy);
+  }
+
+  /**
+   * add a Control to this DisplayImpl
+   * @param control Control to add
+   */
+  public void addControl(Control control) {
+    if (displayRenderer == null) return;
+    if (control != null && !ControlVector.contains(control)) {
+      ControlVector.addElement(control);
+      control.setIndex(ControlVector.indexOf(control));
+      control.setInstanceNumber(getInstanceNumber(control));
+      control.addControlListener((ControlListener)displayMonitor);
+    }
+  }
+
+  /**
+   * get a linked Control with the given Class;
+   * only called for Control objects associated with 'single'
+   * DisplayRealTypes
+   * @param c sub-Class of Control to search for
+   * @return linked Control with Class c, or null
+   */
+  public Control getControl(Class c) {
+    return getControl(c, 0);
+  }
+
+  /**
+   * get ordinal instance of linked Control object of the
+   * specified class
+   * @param c sub-Class of Control to search for
+   * @param inst ordinal instance number
+   * @return linked Control with Class c, or null
+   */
+  public Control getControl(Class c, int inst) {
+    return getControls(c, null, inst);
+  }
+
+  /**
+   * get all linked Control objects of the specified Class
+   * @param c sub-Class of Control to search for
+   * @return Vector of linked Controls with Class c
+   */
+  public Vector getControls(Class c) {
+    Vector v = new Vector();
+    getControls(c, v, -1);
+    return v;
+  }
+
+  /**
+   * Internal method which does the bulk of the work for both
+   * <CODE>getControl()</CODE> and <CODE>getControls()</CODE>.
+   * If <CODE>v</CODE> is non-null, adds all <CODE>Control</CODE>s
+   *  of the specified <CODE>Class</CODE> to that <CODE>Vector</CODE>.
+   * Otherwise, returns <CODE>inst</CODE> instance of the
+   *  <CODE>Control</CODE> matching the specified <CODE>Class</CODE>,
+   *  or <CODE>null</CODE> if no <CODE>Control</CODE> matching the
+   *  criteria is found.
+   *
+   * @param ctlClass 
+   * @param v 
+   * @param inst 
+   *
+   * @return 
+   */
+  private Control getControls(Class ctlClass, Vector v, int inst) {
+    if (displayRenderer == null) return null;
+    if (ctlClass == null) {
+      return null;
+    }
+
+    GraphicsModeControl gmc = getGraphicsModeControl();
+    if (ctlClass.isInstance(gmc)) {
+      if (v == null) {
+        return gmc;
+      }
+      v.addElement(gmc);
+    }
+    else {
+      synchronized (ControlVector) {
+        Enumeration en = ControlVector.elements();
+        while(en.hasMoreElements()) {
+          Control c = (Control)en.nextElement();
+          if (ctlClass.isInstance(c)) {
+            if (v != null) {
+              v.addElement(c);
+            }
+            else if (c.getInstanceNumber() == inst) {
+              return c;
+            }
+          }
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * @return the total number of controls used by this display
+   */
+  public int getNumberOfControls() {
+    return ControlVector.size();
+  }
+
+  /**
+   * @return clone of Vector of Controls linked to this DisplayImpl
+   * @deprecated - DisplayImpl shouldn't expose itself at this level
+   */
+  public Vector getControlVector() {
+    return (Vector)ControlVector.clone();
+  }
+
+  /** whether the Control widget panel needs to be reconstructed */
+  private boolean needWidgetRefresh = true;
+
+  /** this Display's associated panel of Control widgets */
+  private JPanel widgetPanel = null;
+
+  /**
+   * get a GUI component containing this Display's Control widgets;
+   * create the widgets as necessary
+   * @return Container of widget panel
+   */
+  public Container getWidgetPanel() {
+    if (displayRenderer == null) return null;
+    if (needWidgetRefresh) {
+      synchronized (MapVector) {
+        // construct widget panel if needed
+        if (widgetPanel == null) {
+          widgetPanel = new JPanel();
+          widgetPanel.setLayout(new BoxLayout(widgetPanel, BoxLayout.Y_AXIS));
+        }
+        else
+          widgetPanel.removeAll();
+
+        if (getLinks().size() > 0) {
+          // GraphicsModeControl widget
+          GMCWidget gmcw = new GMCWidget(getGraphicsModeControl());
+          addToWidgetPanel(gmcw, false);
+        }
+
+        for (int i = 0; i < MapVector.size(); i++) {
+          ScalarMap sm = (ScalarMap)MapVector.elementAt(i);
+
+          DisplayRealType drt = sm.getDisplayScalar();
+          try {
+            double[] a = new double[2];
+            double[] b = new double[2];
+            double[] c = new double[2];
+            boolean scale = sm.getScale(a, b, c);
+            if (scale) {
+              // ScalarMap range widget
+              RangeWidget rw = new RangeWidget(sm);
+              addToWidgetPanel(rw, true);
+            }
+          }
+          catch (VisADException exc) {
+          }
+          try {
+            if (drt.equals(Display.RGB) || drt.equals(Display.RGBA)) {
+              // ColorControl widget
+              try {
+                LabeledColorWidget lw = new LabeledColorWidget(sm);
+                addToWidgetPanel(lw, true);
+              }
+              catch (VisADException exc) {
+              }
+              catch (RemoteException exc) {
+              }
+            }
+            else if (drt.equals(Display.SelectValue)) {
+              // ValueControl widget
+              VisADSlider vs = new VisADSlider(sm);
+              vs.setAlignmentX(JPanel.CENTER_ALIGNMENT);
+              addToWidgetPanel(vs, true);
+            }
+            else if (drt.equals(Display.SelectRange)) {
+              // RangeControl widget
+              SelectRangeWidget srw = new SelectRangeWidget(sm);
+              addToWidgetPanel(srw, true);
+            }
+            else if (drt.equals(Display.IsoContour)) {
+              // ContourControl widget
+              ContourWidget cw = new ContourWidget(sm);
+              addToWidgetPanel(cw, true);
+            }
+            else if (drt.equals(Display.Animation)) {
+              // AnimationControl widget
+              AnimationWidget aw = new AnimationWidget(sm);
+              addToWidgetPanel(aw, true);
+            }
+          }
+          catch (VisADException exc) {
+          }
+          catch (RemoteException exc) {
+          }
+        }
+      }
+      needWidgetRefresh = false;
+    }
+    return widgetPanel;
+  }
+
+  /**
+   * add a component to the widget panel 
+   *
+   * @param c 
+   * @param divide 
+   */
+  private void addToWidgetPanel(Component c, boolean divide) {
+    if (displayRenderer == null) return;
+    if (divide) widgetPanel.add(new Divider());
+    widgetPanel.add(c);
+  }
+
+  /**
+   * @return length of valueArray passed to ShadowType.doTransform()
+   */
+  public int getValueArrayLength() {
+    return valueArrayLength;
+  }
+
+  /**
+   * @return int[] array mapping from valueArray indices to
+   *         ScalarType Vector indices
+   */
+  public int[] getValueToScalar() {
+    return valueToScalar;
+  }
+
+  /**
+   * @return int[] array mapping from valueArray indices to
+   *         ScalarMap Vector indices
+   */
+  public int[] getValueToMap() {
+    return valueToMap;
+  }
+
+  /**
+   * @return the ProjectionControl associated with this DisplayImpl
+   */
+  public abstract ProjectionControl getProjectionControl();
+
+  /**
+   * @return the GraphicsModeControl associated with this DisplayImpl
+   */
+  public abstract GraphicsModeControl getGraphicsModeControl();
+
+  /**
+   * wait for millis milliseconds
+   * @param millis number of milliseconds to wait
+   * @deprecated Use <CODE>new visad.util.Delay(millis)</CODE> instead.
+   */
+  public static void delay(int millis) {
+    new visad.util.Delay(millis);
+  }
+
+  /**
+   * print a stack dump with the given message
+   * @param message String to print with stack dump
+   */
+  public static void printStack(String message) {
+    try {
+      throw new DisplayException("printStack: " + message);
+    }
+    catch (DisplayException e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * test for equality between this and the given Object
+   * given their complexity, its reasonable that DisplayImpl
+   * objects are only equal to themselves
+   * @param obj Object to test for equality with this
+   * @return flag indicating whether this is equal to obj
+   */
+  public boolean equals(Object obj) {
+    return (obj == this);
+  }
+
+  /**
+   * Returns the list of DataRenderer-s.  NOTE: The actual list is returned
+   * rather than a copy.  If a copy is desired, then use
+   * <code>getRendererVector()</code>.
+   * @return                    The list of DataRenderer-s.
+   * @see #getRendererVector()
+   */
+  public Vector getRenderers() {
+    return (Vector)RendererVector.clone();
+  }
+
+  /**
+   * Return the API used for this display
+   *
+   * @return  the mode being used (UNKNOWN, JPANEL, APPLETFRAME,
+   *                               OFFSCREEN, TRANSFORM_ONLY)
+   * @throws  VisADException
+   */
+  public int getAPI() throws VisADException {
+    throw new VisADException("No API specified");
+  }
+
+  /**
+   * @return the <CODE>DisplayMonitor</CODE> associated with this
+   * <CODE>Display</CODE>.
+   */
+  public DisplayMonitor getDisplayMonitor() {
+    return displayMonitor;
+  }
+
+  /**
+   * @return the <CODE>DisplaySync</CODE> associated with this
+   * <CODE>Display</CODE>.
+   */
+  public DisplaySync getDisplaySync() {
+    return displaySync;
+  }
+
+  /**
+   * set given MouseBehavior
+   * @param m MouseBehavior to set
+   */
+  public void setMouseBehavior(MouseBehavior m) {
+    mouse = m;
+  }
+
+  /**
+   * @return the MouseBehavior used for this Display
+   */
+  public MouseBehavior getMouseBehavior() {
+    return mouse;
+  }
+
+  /**
+   * make projection matrix from given arguments
+   * @param rotx rotation about x axis
+   * @param roty rotation about y axis
+   * @param rotz rotation about z axis
+   * @param scale linear scale factor
+   * @param transx translation along x axis
+   * @param transy translation along y axis
+   * @param transz translation along z axis
+   *
+   * @return projection matrix
+   */
+  
+  public double[] make_matrix(double rotx, double roty, double rotz,
+                              double scale, double transx, double transy,
+                              double transz) {
+    if (mouse != null) {
+      return mouse.make_matrix(
+               rotx, roty, rotz, scale, transx, transy, transz);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * multiply matrices
+   * @param a first operand matrix
+   * @param b second operand matrix
+   * @return product matrix
+   */
+  public double[] multiply_matrix(double[] a, double[] b) {
+    if (mouse != null && a != null && b != null) {
+      return mouse.multiply_matrix(a, b);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * get a BufferedImage of this Display, without synchronizing
+   * (assume the application has made sure Data have been
+   * transformed and rendered)
+   * @return a captured image of this Display
+   */
+  public BufferedImage getImage() {
+    return getImage(false);
+  }
+
+  /**
+   * get a BufferedImage of this Display
+   * @param sync if true, ensure that all linked Data have been
+   *        transformed and rendered
+   * @return a captured image of this Display
+   */
+  public BufferedImage getImage(boolean sync) {
+    if (displayRenderer == null) return null;
+    Thread thread = Thread.currentThread();
+    String name = thread.getName();
+    if (thread.equals(getCurrentActionThread()) ||
+        name.startsWith("J3D-Renderer") ||
+        name.startsWith("AWT-EventQueue")) {
+      throw new VisADError("cannot call getImage() from Thread: " + name);
+    }
+    if (sync) new Syncher(this);
+    return displayRenderer.getImage();
+  }
+
+  /**
+   * @return a String representation of this Display
+   */
+  public String toString() {
+    return toString("");
+  }
+
+  /**
+   * @param pre String added to start of each line
+   * @return a String representation of this Display
+   *         indented by pre (a string of blanks)
+   */
+  public String toString(String pre) {
+    String s = pre + "Display\n";
+    Enumeration maps = MapVector.elements();
+    while(maps.hasMoreElements()) {
+      ScalarMap map = (ScalarMap)maps.nextElement();
+      s = s + map.toString(pre + "    ");
+    }
+    maps = ConstantMapVector.elements();
+    while(maps.hasMoreElements()) {
+      ConstantMap map = (ConstantMap)maps.nextElement();
+      s = s + map.toString(pre + "    ");
+    }
+    return s;
+  }
+
+  /**
+   * 
+   *
+   * @throws Throwable 
+   */
+  protected void finalize() throws Throwable {
+    if (!destroyed) destroy();
+  }
+
+  /**
+   * Class used to ensure that all linked Data have been
+   *   transformed and rendered, used by getImage() 
+   */
+  public class Syncher extends Object implements DisplayListener {
+
+    /**           */
+    private ProjectionControl control;
+
+    /**           */
+    int count;
+
+    /**
+     * construct a Syncher for the given DisplayImpl
+     * @param display DisplayImpl for this Syncher
+     */
+    Syncher(DisplayImpl display) {
+      try {
+        synchronized (this) {
+          control = display.getProjectionControl();
+          count = -1;
+          display.disableAction();
+          display.addDisplayListener(this);
+          display.reDisplayAll();
+          display.enableAction();
+          this.wait();
+        }
+      }
+      catch (InterruptedException e) {
+      }
+      display.removeDisplayListener(this);
+    }
+
+    /**
+     * process DisplayEvent
+     * @param e DisplayEvent to process
+     *
+     * @throws RemoteException 
+     * @throws VisADException 
+     */
+    public void displayChanged(DisplayEvent e)
+            throws VisADException, RemoteException {
+      if (e.getId() == DisplayEvent.TRANSFORM_DONE) {
+        count = 2;
+        control.setMatrix(control.getMatrix());
+      }
+      else if (e.getId() == DisplayEvent.FRAME_DONE) {
+        if (count > 0) {
+          control.setMatrix(control.getMatrix());
+          count--;
+        }
+        else if (count == 0) {
+          synchronized (this) {
+            this.notify();
+          }
+          count--;
+        }
+      }
+    }
+  }
+
+  /**
+   * Return the Printable object to be used by a PrinterJob.  This can
+   * be used as follows:
+   * <pre>
+   *    PrinterJob printJob = PrinterJob.getPrinterJob();
+   *    PageFormat pf = printJob.defaultPage();
+   *    printJob.setPrintable(display.getPrintable(), pf);
+   *    if (printJob.printDialog()) {
+   *        try {
+   *            printJob.print();
+   *        }
+   *        catch (Exception pe) {
+   *            pe.printStackTrace();
+   *        }
+   *    }
+   * </pre>
+   *
+   * @return printable object
+   */
+  public Printable getPrintable() {
+    if (printer == null) printer = new Printable() {
+      public int print(Graphics g, PageFormat pf, int pi)
+              throws PrinterException {
+        if (pi >= 1) {
+          return Printable.NO_SUCH_PAGE;
+        }
+        BufferedImage image = DisplayImpl.this.getImage();
+        g.drawImage(
+          image, (int)pf.getImageableX(), (int)pf.getImageableY(),
+          DisplayImpl.this.component);
+        return Printable.PAGE_EXISTS;
+      }
+    };
+    return printer;
+  }
+
+  /**
+   * handle DisconnectException for the given ReferenceActionLink
+   * @param raLink ReferenceActionLink with DisconnectException
+   */
+  void handleRunDisconnectException(ReferenceActionLink raLink) {
+    if (!(raLink instanceof DataDisplayLink)) {
+      return;
+    }
+
+    DataDisplayLink link = (DataDisplayLink)raLink;
+  }
+
+  /**
+   * Notify this Display that a connection to a remote server has failed
+   * @param renderer DataRenderer with failure
+   * @param link DataDisplayLink with failure
+   */
+  public void connectionFailed(DataRenderer renderer, DataDisplayLink link) {
+    try {
+      removeLinks(new DataDisplayLink[] {link});
+    }
+    catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+    catch (RemoteException re) {
+      re.printStackTrace();
+    }
+
+    if (renderer != null) {
+      DataDisplayLink[] links = renderer.getLinks();
+      if (links.length <= 1) {
+        deleteRenderer(renderer);
+      }
+    }
+
+    Enumeration en = RmtSrcListeners.elements();
+    while(en.hasMoreElements()) {
+      RemoteSourceListener l = (RemoteSourceListener)en.nextElement();
+      l.dataSourceLost(link.getName());
+    }
+  }
+
+  /**
+   * Inform <tt>listener</tt> of deleted {@link DataRenderer}s.
+   *
+   * @param listener Object to add.
+   */
+  public void addRendererSourceListener(RendererSourceListener listener) {
+    RendererSourceListeners.addElement(listener);
+  }
+
+  /**
+   * Remove <tt>listener</tt> from the {@link DataRenderer} deletion list.
+   *
+   * @param listener Object to remove.
+   */
+  public void removeRendererSourceListener(RendererSourceListener listener) {
+    RendererSourceListeners.removeElement(listener);
+  }
+
+  /**
+   * Stop using a {@link DataRenderer}.
+   *
+   * @param renderer Renderer to delete
+   */
+  private void deleteRenderer(DataRenderer renderer) {
+    RendererVector.removeElement(renderer);
+
+    Enumeration en = RendererSourceListeners.elements();
+    while(en.hasMoreElements()) {
+      ((RendererSourceListener)en.nextElement()).rendererDeleted(renderer);
+    }
+  }
+
+  /**
+   * @deprecated
+   *
+   * @param listener 
+   */
+  public void addDataSourceListener(RemoteSourceListener listener) {
+    addRemoteSourceListener(listener);
+  }
+
+  /**
+   * @deprecated
+   *
+   * @param listener 
+   */
+  public void removeDataSourceListener(RemoteSourceListener listener) {
+    removeRemoteSourceListener(listener);
+  }
+
+  /**
+   * Inform <tt>listener</tt> of changes in the availability
+   * of remote data/collaboration sources.
+   *
+   * @param listener Object to send change notifications.
+   */
+  public void addRemoteSourceListener(RemoteSourceListener listener) {
+    RmtSrcListeners.addElement(listener);
+  }
+
+  /**
+   * Remove <tt>listener</tt> from the remote source notification list.
+   *
+   * @param listener Object to be removed.
+   */
+  public void removeRemoteSourceListener(RemoteSourceListener listener) {
+    RmtSrcListeners.removeElement(listener);
+  }
+
+  /**
+   * Inform {@link RemoteSourceListener}s that the specified collaborative
+   * connection has been lost.<br>
+   * <br>
+   * <b>WARNING!</b>  This should only be called from within the
+   * visad.collab package!
+   *
+   * @param id ID of lost connection.
+   */
+  public void lostCollabConnection(int id) {
+    Enumeration en = RmtSrcListeners.elements();
+    while(en.hasMoreElements()) {
+      ((RemoteSourceListener)en.nextElement()).collabSourceLost(id);
+    }
+  }
+
+  /**
+   * Forward messages to the specified <tt>listener</tt>
+   *
+   * @param listener New message receiver.
+   */
+  public void addMessageListener(MessageListener listener) {
+    MessageListeners.addElement(listener);
+  }
+
+  /**
+   * Remove <tt>listener</tt> from the message list.
+   *
+   * @param listener Object to remove.
+   */
+  public void removeMessageListener(MessageListener listener) {
+    MessageListeners.removeElement(listener);
+  }
+
+  /**
+   * Send a message to all </tt>MessageListener</tt>s.
+   *
+   * @param msg Message being sent.
+   *
+   * @throws RemoteException 
+   */
+  public void sendMessage(MessageEvent msg) throws RemoteException {
+    RemoteException exception = null;
+    Enumeration en = MessageListeners.elements();
+    while(en.hasMoreElements()) {
+      MessageListener l = (MessageListener)en.nextElement();
+      try {
+        l.receiveMessage(msg);
+      }
+      catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          // remote side disconnected; forget about it
+          MessageListeners.removeElement(l);
+        }
+        else {
+          // save this exception for later
+          exception = re;
+        }
+      }
+    }
+
+    if (exception != null) {
+      throw exception;
+    }
+  }
+
+  /**
+   * set aspect ratio of XAxis, YAxis & ZAxis in ScalarMaps rather
+   * than matrix (i.e., don't distort text fonts); called by
+   * ProjectionControl.setAspectCartesian()
+   * @param aspect ratios; 3 elements for Java3D, 2 for Java2D
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void setAspectCartesian(double[] aspect)
+          throws VisADException, RemoteException {
+    if (displayRenderer == null) return;
+    if (mapslock == null) return;
+    synchronized (mapslock) {
+      // clone MapVector to avoid need for synchronized access
+      Vector tmap = (Vector)MapVector.clone();
+      Enumeration maps = tmap.elements();
+      while(maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap)maps.nextElement();
+        map.setAspectCartesian(aspect);
+      }
+
+      tmap = (Vector)ConstantMapVector.clone();
+      maps = tmap.elements();
+      while(maps.hasMoreElements()) {
+        ConstantMap map = (ConstantMap)maps.nextElement();
+        map.setAspectCartesian(aspect);
+      }
+
+      // resize box
+      getDisplayRenderer().setBoxAspect(aspect);
+
+      // reAutoScale(); ??
+      reDisplayAll();
+    } // end synchronized (mapslock)
+  }
+
+  /**
+   * Add a busy/idle activity handler.
+   *
+   * @param ah Activity handler.
+   *
+   * @throws VisADException If the handler couldn't be added.
+   */
+  public void addActivityHandler(ActivityHandler ah) throws VisADException {
+    if (displayRenderer == null) return;
+    if (displayActivity == null) {
+      displayActivity = new DisplayActivity(this);
+    }
+
+    displayActivity.addHandler(ah);
+  }
+
+  /**
+   * Remove a busy/idle activity handler.
+   *
+   * @param ah Activity handler.
+   *
+   * @throws VisADException If the handler couldn't be removed.
+   */
+  public void removeActivityHandler(ActivityHandler ah)
+          throws VisADException {
+    if (displayRenderer == null) return;
+    if (displayActivity == null) {
+      displayActivity = new DisplayActivity(this);
+    }
+
+    displayActivity.removeHandler(ah);
+  }
+
+  /**
+   * Indicate to activity monitor that the Display is busy.
+   */
+  public void updateBusyStatus() {
+    if (displayActivity != null) {
+      displayActivity.updateBusyStatus();
+    }
+  }
+
+  /** Class for listening to component events */
+  private class ComponentChangedListener extends ComponentAdapter {
+
+    /** the listener's display */
+    DisplayImpl display;
+
+    /**
+     * Create a listener for the display
+     *
+     * @param d 
+     */
+    public ComponentChangedListener(DisplayImpl d) {
+      display = d;
+    }
+
+    /**
+     * Invoked when the component has been resized.
+     * @param ce  ComponentEvent fired.
+     */
+    public void componentShown(ComponentEvent ce) {}
+
+    /**
+     * Invoked when the component has been made invisible.
+     * @param ce  ComponentEvent fired.
+     */
+    public void componentHidden(ComponentEvent ce) {}
+
+    /**
+     * Invoked when the component has been moved.
+     * @param ce  ComponentEvent fired.
+     */
+    public void componentMoved(ComponentEvent ce) {}
+
+    /**
+     * Invoked when the component has been resized.
+     * @param ce  ComponentEvent fired.
+     */
+    public void componentResized(ComponentEvent ce) {
+      Component component = ce.getComponent();
+      Dimension d = component.getSize();
+      try {
+        notifyListeners(
+          new DisplayEvent(display, DisplayEvent.COMPONENT_RESIZED, d.width,
+                           d.height));
+      }
+      catch (VisADException ve) {
+        System.err.println("Couldn't notify listeners of resize event");
+      }
+      catch (RemoteException re) {
+        System.err.println(
+          "Couldn't notify listeners of remote resize event");
+      }
+    }
+
+  }
+}
+
diff --git a/visad/DisplayInterruptException.java b/visad/DisplayInterruptException.java
new file mode 100644
index 0000000..28933bc
--- /dev/null
+++ b/visad/DisplayInterruptException.java
@@ -0,0 +1,47 @@
+//
+// DisplayInterruptException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DisplayInterruptException is an exception for interrupting
+   data transformation.<P>
+*/
+public class DisplayInterruptException extends DisplayException {
+
+  /**
+   * construct a DisplayInterruptException with no message
+   */
+  public DisplayInterruptException() { super(); }
+
+  /**
+   * construct a DisplayInterruptException with given message
+   * @param s - message String
+   */
+  public DisplayInterruptException(String s) { super(s); }
+
+}
+
diff --git a/visad/DisplayListener.java b/visad/DisplayListener.java
new file mode 100644
index 0000000..36ba33a
--- /dev/null
+++ b/visad/DisplayListener.java
@@ -0,0 +1,48 @@
+//
+// DisplayListener.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.EventListener;
+import java.rmi.*;
+
+/**
+   DisplayListener is the EventListener interface for
+   DisplayEvents.<P>
+*/
+public interface DisplayListener extends EventListener {
+
+  /**
+   * send a DisplayEvent to this DisplayListener
+   * @param e DisplayEvent to send
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void displayChanged(DisplayEvent e)
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/DisplayMapEvent.java b/visad/DisplayMapEvent.java
new file mode 100644
index 0000000..78df8e0
--- /dev/null
+++ b/visad/DisplayMapEvent.java
@@ -0,0 +1,81 @@
+//
+// DisplayMapEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DisplayMapEvent is the VisAD class for Events from Display
+   ScalarMap and ConstantMap objects.  They are sourced by Display
+   objects and received by DisplayListener objects.<P>
+*/
+public class DisplayMapEvent extends DisplayEvent {
+
+  private ScalarMap map;
+
+  /**
+   * Constructs a DisplayMapEvent object with the specified
+   * source display, type of event, and ScalarMap.
+   *
+   * @param  d  display that sends the event
+   * @param  id  type of DisplayMapEvent that is sent
+   * @param m ScalarMap referenced by this event
+   */
+  public DisplayMapEvent(Display d, int id, ScalarMap m) {
+    this(d, id, m, LOCAL_SOURCE);
+  }
+
+  /**
+   * Constructs a DisplayMapEvent object with the specified
+   * source display, type of event, and ScalarMap.
+   *
+   * @param  d  display that sends the event
+   * @param  id  type of DisplayMapEvent that is sent
+   * @param  m  ScalarMap referenced by this event
+   * @param  remoteId  ID of remote source
+   */
+  public DisplayMapEvent(Display d, int id, ScalarMap m, int remoteId) {
+    super(d, id, remoteId);
+    map = m;
+  }
+
+  /**
+   * Return a new DisplayMapEvent which is a copy of this event,
+   * but which uses the specified source display
+   *
+   * @param dpy Display to use for the new DisplayMapEvent
+   * @return a clone of this, except with the given source Display
+   */
+  public DisplayEvent cloneButDisplay(Display dpy)
+  {
+    return new DisplayMapEvent(dpy, getId(), map, getRemoteId());
+  }
+
+  /** get the ScalarMap referenced by this DisplayMapEvent */
+  public ScalarMap getMap() {
+    return map;
+  }
+
+}
diff --git a/visad/DisplayRealType.java b/visad/DisplayRealType.java
new file mode 100644
index 0000000..b54b1b2
--- /dev/null
+++ b/visad/DisplayRealType.java
@@ -0,0 +1,274 @@
+//
+// DisplayRealType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+
+/**
+   DisplayRealType is the class for display real scalar types.
+   ScalarMaps map ScalarTypes to DisplayRealType.
+   A fixed set is defined by the system, users may add others.
+*/
+public class DisplayRealType extends RealType {
+
+  private boolean range;          // true if [LowValue, HiValue] range is used
+  private double LowValue;        // [LowValue, HiValue] is range of values
+  private double HiValue;         //   for this display scalar
+  private double DefaultValue;    // default value for this display scalar
+
+  private DisplayTupleType tuple; // tuple to which DisplayRealType belongs, or null
+  private int tupleIndex;         // index within tuple
+  private boolean Single;   // true if only one instance allowed in a display type
+
+  private boolean system;   // true if this is a system intrinsic
+
+  // true if this is actually a text type, false if it is really real
+  private boolean text;     // admitedly a kludge
+  private boolean circular;
+
+  /**
+   * trusted constructor for intrinsic DisplayRealType's created by system
+   * without range or Unit
+   * @param name String name for this DisplayRealType
+   * @param single if true, this may occur at most once at a terminal
+   *               node in the ShadowType tree; see
+   *  <a href="http://www.ssec.wisc.edu/~billh/guide.html#Appendix A">Appendix A of the Developer's Guide</a>
+   * @param def default value
+   * @param b dummy argument indicating that this is a trusted constructor
+   *        (does not throw VisADExceptions)
+   */
+  DisplayRealType(String name, boolean single, double def, boolean b) {
+    this(name, single, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+         def, null, b);
+  }
+
+  /**
+   * trusted constructor for intrinsic DisplayRealType's created by system
+   * without Unit
+   * @param name String name for this DisplayRealType
+   * @param single if true, this may occur at most once at a terminal
+   *               node in the ShadowType tree; see
+   *  <a href="http://www.ssec.wisc.edu/~billh/guide.html#Appendix A">Appendix A of the Developer's Guide</a>
+   * @param low start of range of values for scaling
+   * @param hi end of range of values for scaling
+   * @param def default value
+   * @param b dummy argument indicating that this is a trusted constructor
+   *        (does not throw VisADExceptions)
+   */
+  DisplayRealType(String name, boolean single, double low, double hi,
+                  double def, boolean b) {
+    this(name, single, low, hi, def, null, b);
+  }
+
+  /**
+   * trusted constructor for intrinsic DisplayRealType's created by system
+   * with Unit
+   * @param name String name for this DisplayRealType
+   * @param single if true, this may occur at most once at a terminal
+   *               node in the ShadowType tree; see
+   *  <a href="http://www.ssec.wisc.edu/~billh/guide.html#Appendix A">Appendix A of the Developer's Guide</a>
+   * @param low start of range of values for scaling
+   * @param hi end of range of values for scaling
+   * @param def default value
+   * @param unit Unit for this DisplayRealType
+   * @param b dummy argument indicating that this is a trusted constructor
+   *        (does not throw VisADExceptions)
+   */
+  DisplayRealType(String name, boolean single, double low, double hi,
+                  double def, Unit unit, boolean b) {
+    super("Display" + name, unit, b);
+    system = true;
+    Single = single;
+    LowValue = low;
+    HiValue = hi;
+    range = !(Double.isInfinite(low) || Double.isNaN(low) ||
+              Double.isInfinite(hi) || Double.isNaN(hi));
+    DefaultValue = def;
+    tuple = null;
+    tupleIndex = -1;
+    text = false;
+    circular = false;
+  }
+
+  /**
+   * trusted constructor for intrinsic text DisplayRealType
+   * @param name String name for this DisplayRealType
+   * @param single if true, this may occur at most once at a terminal
+   *               node in the ShadowType tree; see
+   *  <a href="http://www.ssec.wisc.edu/~billh/guide.html#Appendix A">Appendix A of the Developer's Guide</a>
+   * @param b dummy argument indicating that this is a trusted constructor
+   *        (does not throw VisADExceptions)
+   */
+  DisplayRealType(String name, boolean single,  boolean b) {
+    super("Display" + name, null, b);
+    system = true;
+    Single = single;
+    text = true;
+    circular = false;
+  }
+
+  /**
+   * construct a DisplayRealType
+   * @param name String name for this DisplayRealType
+   * @param single if true, this may occur at most once at a terminal
+   *               node in the ShadowType tree; see
+   *  <a href="http://www.ssec.wisc.edu/~billh/guide.html#Appendix A">Appendix A of the Developer's Guide</a>
+   * @param low start of range of values for scaling
+   * @param hi end of range of values for scaling
+   * @param def default value
+   * @param unit Unit for this DisplayRealType
+   * @throws VisADException a VisAD error occurred
+   */
+  public DisplayRealType(String name, boolean single, double low, double hi,
+                         double def, Unit unit)
+         throws VisADException {
+    super("Display" + name, unit, false);
+    system = false;
+    Single = single;
+    LowValue = low;
+    HiValue = hi;
+    range = !(Double.isInfinite(low) || Double.isNaN(low) ||
+              Double.isInfinite(hi) || Double.isNaN(hi));
+    DefaultValue = def;
+    tuple = null;
+    tupleIndex = -1;
+    text = false;
+    circular = false;
+  }
+
+  /**
+   * construct a DisplayRealType whose values are not scaled
+   * @param name String name for this DisplayRealType
+   * @param single if true, this may occur at most once at a terminal
+   *               node in the ShadowType tree; see
+   *  <a href="http://www.ssec.wisc.edu/~billh/guide.html#Appendix A">Appendix A of the Developer's Guide</a>
+   * @param def default value
+   * @param unit Unit for this DisplayRealType
+   * @throws VisADException a VisAD error occurred
+   */
+  public DisplayRealType(String name, boolean single, double def,
+                         Unit unit) throws VisADException {
+    this(name, single, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
+         def, unit);
+  }
+
+  private static DisplayRealType getDisplayRealTypeByName(String name) {
+    ScalarType scalarType = getScalarTypeByName(name);
+    if (scalarType instanceof DisplayRealType) {
+      return (DisplayRealType) scalarType;
+    }
+    return null;
+  }
+
+  /**
+   * @return the unique DisplayTupleType that this DisplayRealType
+   *         is a component of, or return null if it is not a
+   *         component of any DisplayTupleType
+   */
+  public DisplayTupleType getTuple() {
+    return tuple;
+  }
+
+  /**
+   * @return index of this as component of a DisplayTupleType
+   *         (-1 if it isn't a component of any DisplayTupleType)
+   */
+  public int getTupleIndex() {
+    return tupleIndex;
+  }
+
+  /**
+   * Sets the DisplayTupleType to which this DisplayRealType will belong.
+   * @param t The DisplayTupleType of which this DisplayRealType
+   *          will be a component.
+   * @param i The 0-based index for this as a component of t.
+   * @param c Flag indicating whether t has a CoordinateSystem whose
+   *          toReference() is periodic in this with period 2*pi
+   *          (or equivalent according to unit).
+   * @throws VisADException a VisAD error occurred.
+   */
+  public void setTuple(DisplayTupleType t, int i, boolean c)
+         throws VisADException {
+    if (tuple != null) {
+      throw new DisplayException("DisplayRealType " + getName() +
+        " already has DisplayTupleType " + tuple);
+    }
+    tuple = t;
+    tupleIndex = i;
+    circular = c;
+  }
+
+  /**
+   * @return flag indicating whether this is 'single'
+   */
+  public boolean isSingle() {
+    return Single;
+  }
+
+  /**
+   * @return default value for this DisplayRealType
+   */
+  public double getDefaultValue() {
+    return DefaultValue;
+  }
+
+  /**
+   * get the range of values is defined for this
+   * @param range_values double[2] array used to return low
+   *                     and hi values of the range (if this
+   *                     DisplayRealType has a range)
+   * @return flag indicating whether this has a range (i.e.,
+   *         is scaled)
+   */
+  public boolean getRange(double[] range_values) {
+    if (range) {
+      range_values[0] = LowValue;
+      range_values[1] = HiValue;
+    }
+    return range;
+  }
+
+  /**
+   * @return flag indicating whether this DisplayRealType is
+   *         really a text type
+   */
+  public boolean getText() {
+    return text;
+  }
+
+  /**
+   * @return flag indicating whether this has a DisplayTupleType,
+   *         which has a CoordinateSystem whose toReference() is
+   *         periodic in this with period 2*pi (or equivalent
+   *         according to unit)
+   */
+  public boolean getCircular() {
+    return circular;
+  }
+
+}
+
diff --git a/visad/DisplayReferenceEvent.java b/visad/DisplayReferenceEvent.java
new file mode 100644
index 0000000..e1031d6
--- /dev/null
+++ b/visad/DisplayReferenceEvent.java
@@ -0,0 +1,69 @@
+//
+// DisplayReferenceEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DisplayReferenceEvent is the VisAD class for Events from Display
+   DataReference obkects.  They are sourced by Display objects and
+   received by DisplayListener objects.<P>
+*/
+public class DisplayReferenceEvent extends DisplayEvent {
+
+  private DataDisplayLink link;
+
+  /**
+   * Constructs a DisplayReferenceEvent object with the specified
+   * source display, type of event, and DataReference connection.
+   *
+   * @param  d  display that sends the event
+   * @param  id  type of DisplayReferenceEvent that is sent
+   * @param link DataReference link
+   */
+  public DisplayReferenceEvent(Display d, int id, DataDisplayLink link) {
+    super(d, id);
+    this.link = link;
+  }
+
+  /**
+   * Return a new DisplayReferenceEvent which is a copy of this event,
+   * but which uses the specified source display
+   *
+   * @param dpy Display to use for the new DisplayReferenceEvent
+   */
+  public DisplayEvent cloneButDisplay(Display dpy)
+  {
+    return new DisplayReferenceEvent(dpy, getId(), link);
+  }
+
+  /**
+   * @return the DataDisplayLink referenced by this
+   */
+  public DataDisplayLink getDataDisplayLink() {
+    return link;
+  }
+
+}
diff --git a/visad/DisplayRenderer.java b/visad/DisplayRenderer.java
new file mode 100644
index 0000000..8612d37
--- /dev/null
+++ b/visad/DisplayRenderer.java
@@ -0,0 +1,935 @@
+//
+// DisplayRenderer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import java.rmi.RemoteException;
+
+import java.util.*;
+
+
+/**
+ * <CODE>DisplayRenderer</CODE> is the VisAD abstract super-class for
+ * background and metadata rendering algorithms.  These complement
+ * depictions of <CODE>Data</CODE> objects created by
+ * <CODE>DataRenderer</CODE> objects.<P>
+ *
+ * <CODE>DisplayRenderer</CODE> also manages the overall relation of
+ * <CODE>DataRenderer</CODE> output to the graphics library.<P>
+ *
+ * <CODE>DisplayRenderer</CODE> is not <CODE>Serializable</CODE> and
+ * should not be copied between JVMs.<P>
+ */
+public abstract class DisplayRenderer
+  implements ControlListener
+{
+
+  /** DisplayImpl this renderer is attached to. */
+  private transient DisplayImpl display;
+
+  /** RendererControl holds the shared renderer data */
+  private transient RendererControl rendererControl = null;
+
+  /** Vector of Strings describing cursor location */
+  private Vector cursorStringVector = new Vector();
+
+  /** Strings to display during next frame of animation. */
+  String[] animationString = {null, null};
+
+  /** Number of scales allocated on each axis. */
+  private int[] axisOrdinals = {-1, -1, -1};
+
+  /** Set to true when the wait message should be displayed. */
+  private boolean waitFlag = false;
+
+  /** Set to true if the cursor location Strings should be displayed. */
+  private boolean cursor_string = true;
+
+  /** threshhold for direct manipulation picking */
+  private float pickThreshhold = 0.05f;
+
+  /** Set to true to make animation Strings visible on the display */
+  private boolean aniStringVisible = true;
+
+  /** Set to true to make please wait Strings visible on the display */
+  private boolean waitMessageVisible = true;
+
+  /** Does the mouse and keyboard rotation scale the rotation angle by the zoom factor */
+  private boolean scaleRotation = false;
+
+ /** when we rotate via the key or mouse do we rotate about the center of the xyz box */
+  private boolean rotateAboutCenter = false;
+
+
+  /**
+   * Construct a new <CODE>DisplayRenderer</CODE>.
+   */
+  public DisplayRenderer () {
+  }
+
+  /**
+   * @return the distance threshhold for picking in direct manipulation
+   *         (distance in XAxis, YAxis, ZAxis coordinates)
+   */
+  public float getPickThreshhold() {
+    return pickThreshhold;
+  }
+
+  /**
+   * set the distance threshhold for picking in direct manipulation
+   * @param pt distance (in XAxis, YAxis, ZAxis coordinates)
+   */
+  public void setPickThreshhold(float pt) {
+    pickThreshhold = pt;
+  }
+
+  /**
+   * set the aspect for the containing box
+   * aspect double[3] array used to scale x, y and z box sizes
+   */
+  public abstract void setBoxAspect(double[] aspect);
+
+  /**
+   * Specify <CODE>DisplayImpl</CODE> to be rendered.
+   * @param d <CODE>Display</CODE> to render.
+   * @exception VisADException If a <CODE>DisplayImpl</CODE> has already
+   *                           been specified.
+   */
+  public void setDisplay(DisplayImpl d) throws VisADException {
+    if (display != null) {
+      throw new DisplayException("DisplayRenderer.setDisplay: " +
+                                 "display already set");
+    }
+    display = d;
+
+    // reinitialize rendererControl
+    if (rendererControl == null) {
+      rendererControl = new RendererControl(display);
+      initControl(rendererControl);
+    } else {
+      RendererControl rc = new RendererControl(display);
+      rc.syncControl(rendererControl);
+      rendererControl = rc;
+    }
+    rendererControl.addControlListener(this);
+    display.addControl(rendererControl);
+  }
+
+  /**
+   * Internal method used to initialize newly created
+   * <CODE>RendererControl</CODE> with current renderer settings
+   * before it is actually connected to the renderer.  This
+   * means that changes will not generate <CODE>MonitorEvent</CODE>s.
+   * @param ctl RendererControl to initialize
+   */
+  public abstract void initControl(RendererControl ctl);
+
+  /**
+   * Get the <CODE>Display</CODE> associated with this renderer.
+   * @return The Display being rendered.
+   */
+  public DisplayImpl getDisplay() {
+    return display;
+  }
+
+  /**
+   * Get the <CODE>Control</CODE> which holds the "shared" data
+   * for this renderer.
+   * @return The renderer <CODE>Control</CODE>.
+   */
+  public RendererControl getRendererControl()
+  {
+    return rendererControl;
+  }
+
+  /**
+   * Set the <I>wait flag</I> to the specified value.
+   * (When the <I>wait flag</I> is enabled, the user is informed
+   *  that the application is busy, typically by displaying a
+   *  <B><TT>Please wait . . .</TT></B> message at the bottom of
+   *  the <CODE>Display</CODE>.)  DisplayEvent.WAIT_ON and
+   *  DisplayEvent.WAIT_OFF events are fired based on value of b.
+   * @param b Boolean value to which <I>wait flag</I> is set.
+   */
+  public void setWaitFlag(boolean b) {
+    waitFlag = b;
+    try {
+      DisplayEvent e = new DisplayEvent(display,
+        (b == true) ? DisplayEvent.WAIT_ON 
+                    : DisplayEvent.WAIT_OFF);
+      display.notifyListeners(e);
+    } 
+    catch (VisADException e) { }
+    catch (RemoteException e) { }
+  }
+
+  /**
+   *  Get the <I>wait flag</I> state.
+   * @return <CODE>true</CODE> if the <I>wait flag</I> is enabled.
+   */
+  public boolean getWaitFlag() {
+    return waitFlag;
+  }
+
+  /**
+   * Get a new ordinal number for this axis.
+   * @param axis Axis for which ordinal is returned
+   *        (XAxis=0, YAxis=1, ZAxis=2).
+   * @return The new ordinal number.
+   */
+  int getAxisOrdinal(int axis) {
+    synchronized (axisOrdinals) {
+      axisOrdinals[axis]++;
+      return axisOrdinals[axis];
+    }
+  }
+
+  /**
+   * Reset all the axis ordinals and remove all scales.
+   */
+  void clearAxisOrdinals() {
+    synchronized (axisOrdinals) {
+      axisOrdinals[0] = -1;
+      axisOrdinals[1] = -1;
+      axisOrdinals[2] = -1;
+    }
+    clearScales();
+  }
+
+  /**
+   * Get a snapshot of the displayed image.
+   * @return The current image being displayed.
+   */
+  public abstract BufferedImage getImage();
+
+  /**
+   * Set an axis scale.
+   * @param  axisScale  AxisScale to set (it knows what axis
+   *                    it is for)
+   * @throws  VisADException  couldn't set the scale
+   */
+  public abstract void setScale(AxisScale axisScale)
+         throws VisADException;
+
+  /**
+   * Set an axis scale.
+   * @param  axis  axis for this scale (0 = XAxis, 1 = YAxis, 2 = ZAxis)
+   * @param  axis_ordinal  position along the axis
+   * @param  array   <CODE>VisADLineArray</CODE> representing the scale plot
+   * @param  scale_color   float[3] array representing the red, green and
+   *                       blue color values.
+   * @throws  VisADException  couldn't set the scale
+   */
+  public abstract void setScale(int axis, int axis_ordinal,
+                  VisADLineArray array, float[] scale_color)
+         throws VisADException;
+
+  /**
+   * Set an axis scale.
+   * @param  axis  axis for this scale (0 = XAxis, 1 = YAxis, 2 = ZAxis)
+   * @param  axis_ordinal  position along the axis
+   * @param  array   <CODE>VisADLineArray</CODE> representing the scale plot
+   * @param  labels  <CODE>VisADTriangleArray</CODE> representing the labels
+   *                 created using a font (can be null)
+   * @param  scale_color   float[3] array representing the red, green and 
+   *                       blue color values.
+   * @throws  VisADException  couldn't set the scale
+   */
+  public abstract void setScale(int axis, int axis_ordinal,
+                  VisADLineArray array, VisADTriangleArray labels, 
+                  float[] scale_color)
+         throws VisADException;
+
+  /**
+   * Remove all the scales being rendered.
+   */
+  public abstract void clearScales();
+
+  /**
+   * Remove a particular scale being rendered.
+   * @param axisScale  scale to remove
+   */
+  public abstract void clearScale(AxisScale axisScale);
+
+  /**
+   * Enable scales to be displayed if they are set.  This should
+   * not be called programmatically, since it does not update
+   * collaborative displays.
+   * Applications should use
+   * {@link GraphicsModeControl#setScaleEnable(boolean)
+   *  GraphicsModeControl.setScaleEnable}
+   * instead of this method.
+   * @param  on   true to turn them on, false to set them invisible
+   */
+  public abstract void setScaleOn(boolean on);
+
+  /**
+   * Return <CODE>true</CODE> if this is a 2-D <CODE>DisplayRenderer</CODE>.
+   * @return <CODE>true</CODE> if this is a 2-D renderer.
+   */
+  public boolean getMode2D() {
+    return false;
+  }
+
+  /**
+   * Set the background color.
+   * @param color background color
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setBackgroundColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setBackgroundColor(r, g, b);
+  }
+
+  /**
+   * Set the background color.  All specified values should be in the
+   * range <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setBackgroundColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    if (rendererControl == null) {
+      throw new VisADException("DisplayRenderer not yet assigned to a Display");
+    }
+    rendererControl.setBackgroundColor(r, g, b);
+  }
+
+  /**
+   * Set the foreground color (box, cursor and scales).
+   * @param color foreground color
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setForegroundColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setForegroundColor(r, g, b);
+  }
+
+  /**
+   * Set the foreground color (box, cursor and scales).  All specified 
+   * values should be in the range <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setForegroundColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    if (rendererControl == null) {
+      throw new VisADException("DisplayRenderer not yet assigned to a Display");
+    }
+    rendererControl.setForegroundColor(r, g, b);
+  }
+
+  /**
+   * Get the box visibility.
+   * @return <CODE>true</CODE> if the box is visible.
+   */
+  public boolean getBoxOn()
+    throws RemoteException, VisADException
+  {
+    if (rendererControl == null) {
+      throw new VisADException("DisplayRenderer not yet assigned to a Display");
+    }
+    return rendererControl.getBoxOn();
+  }
+
+  /**
+   * Set the box color.
+   * @param color box color
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setBoxColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setBoxColor(r, g, b);
+  }
+
+  /**
+   * Set the box color.  All specified values should be in the range
+   * <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setBoxColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    if (rendererControl == null) {
+      throw new VisADException("DisplayRenderer not yet assigned to a Display");
+    }
+    rendererControl.setBoxColor(r, g, b);
+  }
+
+  /**
+   * Set the box visibility.
+   * @param on <CODE>true</CODE> if the box should be visible.
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setBoxOn(boolean on)
+    throws RemoteException, VisADException
+  {
+    if (rendererControl == null) {
+      throw new VisADException("DisplayRenderer not yet assigned to a Display");
+    }
+    rendererControl.setBoxOn(on);
+  }
+
+  /**
+   * Get the cursor color.
+   * @return A 3 element array of <CODE>float</CODE> values
+   *         in the range <CODE>[0.0f - 1.0f]</CODE>
+   *         in the order <I>(Red, Green, Blue)</I>.
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public float[] getCursorColor()
+    throws RemoteException, VisADException
+  {
+    if (rendererControl == null) {
+      throw new VisADException("DisplayRenderer not yet assigned to a Display");
+    }
+    return rendererControl.getCursorColor();
+  }
+
+  /**
+   * Set the cursor color.
+   * @param color cursor color
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setCursorColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setCursorColor(r, g, b);
+  }
+
+  /**
+   * Set the cursor color.  All specified values should be in the range
+   * <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   * @exception RemoteException If there was a problem making this change
+   *                            in a remote collaborative
+   *                            <CODE>DisplayRenderer</CODE>.
+   * @exception VisADException If this renderer as not yet been assigned
+   *                           to a <CODE>Display</CODE>.
+   */
+  public void setCursorColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    if (rendererControl == null) {
+      throw new VisADException("DisplayRenderer not yet assigned to a Display");
+    }
+    rendererControl.setCursorColor(r, g, b);
+  }
+
+  /**
+   * Factory for constructing a subclass of <CODE>Control</CODE>
+   * appropriate for the graphics API and for this
+   * <CODE>DisplayRenderer</CODE>; invoked by <CODE>ScalarMap</CODE>
+   * when it is <CODE>addMap()</CODE>ed to a <CODE>Display</CODE>.
+   * @param map The <CODE>ScalarMap</CODE> for which a <CODE>Control</CODE>
+   *            should be built.
+   * @return The appropriate <CODE>Control</CODE>.
+   */
+  public abstract Control makeControl(ScalarMap map);
+
+  /**
+   * Factory for constructing the default subclass of
+   * <CODE>DataRenderer</CODE> for this <CODE>DisplayRenderer</CODE>.
+   * @return The default <CODE>DataRenderer</CODE>.
+   */
+  public abstract DataRenderer makeDefaultRenderer();
+
+  /**
+   * determine whether a DataRenderer is legal for this DisplayRenderer
+   * @param renderer DisplayRenderer to test for legality
+   * @return true if renderer is legal
+   */
+  public abstract boolean legalDataRenderer(DataRenderer renderer);
+
+  /**
+   * Set whether the animation info should be visible in the display
+   * or not.
+   * @param  visible  true to show the animation info
+   */
+  public void setAnimationStringVisible(boolean visible) {
+    aniStringVisible = visible;
+  }
+
+  /**
+   * Return whether the animation info should be visible in the display
+   * or not.
+   * @return   true if the animation info should be shown
+   */
+  public boolean getAnimationStringVisible() {
+    return aniStringVisible;
+  }
+
+  /**
+   * Set whether the "please wait" info should be visible in the display
+   * or not.
+   * @param  visible  true to show the please wait info
+   */
+  public void setWaitMessageVisible(boolean visible) {
+    waitMessageVisible = visible;
+  }
+
+  /**
+   * Return whether the please wait info should be visible in the display
+   * or not.
+   * @return   true if the "please wait" info should be shown
+   */
+  public boolean getWaitMessageVisible() {
+    return waitMessageVisible;
+  }
+
+  /**
+   * Return Array of <CODE>String</CODE>s describing the
+   * animation sequence
+   * @return The animation description
+   */
+  public String[] getAnimationString() {
+    if (aniStringVisible) {
+      return animationString;
+    }
+    else {
+      return new String[] {null, null};
+    }
+  }
+
+  /**
+   * Set Array of <CODE>String</CODE>s describing the
+   * animation sequence
+   * @param animation a String[2] array describing the
+   *                  animation sequence
+   */
+  public void setAnimationString(String[] animation) {
+    animationString[0] = animation[0];
+    animationString[1] = animation[1];
+  }
+
+  /**
+   * Return an array giving the cursor location as
+   * <I>(XAxis, YAxis, ZAxis)</I> coordinates
+   * @return 3 element <CODE>double</CODE> array of cursor coordinates.
+   */
+  public abstract double[] getCursor();
+
+  /**
+   * set flag indicating whether the cursor should be displayed.
+   * @param on value of flag to set
+   */
+  public abstract void setCursorOn(boolean on);
+
+  /**
+   * set a VisADRay along which to drag cursor in depth (in and out
+   * of screen)
+   * @param ray VisADRay to set
+   */
+  public abstract void depth_cursor(VisADRay ray);
+
+  /**
+   * drag cursor parallel to plane of screen
+   * @param ray VisADRay that goes through new cursor location
+   * @param first true to indicate this is first call to drag_cursor()
+   *        for this drag
+   */
+  public abstract void drag_cursor(VisADRay ray, boolean first);
+
+  /**
+   * set flag indicating whether direct manipulation is active
+   * @param on value of flag to set
+   */
+  public abstract void setDirectOn(boolean on);
+
+  /**
+   * drag cursor in depth (in and out of screen)
+   * @param diff amount to move cursor in depth (0.0 corresponds
+   *             to no movement)
+   */
+  public abstract void drag_depth(float diff);
+
+  /**
+   * @return flag indicating whether there are any direct manipulation
+   *         DataRenderers linked to Display
+   */
+  public abstract boolean anyDirects();
+
+  /**
+   * @return the MouseBehavior for this display
+   */
+  public abstract MouseBehavior getMouseBehavior();
+
+  /**
+   * Returns a direct manipulation renderer if one is close to
+   * the specified ray (within pick threshold).
+   * @param ray The ray used to look for a nearby direct manipulation
+   *            renderer.
+   * @param mouseModifiers Value of InputEvent.getModifiers().
+   * @return DataRenderer or <CODE>null</CODE>.
+   */
+  public abstract DataRenderer findDirect(VisADRay ray, int mouseModifiers);
+
+  /**
+   * set flag indicating whether the cursor location String
+   * should be displayed.
+   * @param on value of flag to set
+   */
+  public void setCursorStringOn(boolean on) {
+    cursor_string = on;
+  }
+
+  /**
+   * Return <CODE>Vector</CODE> of <CODE>String</CODE>s describing the
+   * cursor location, if cursor location display is enabled.
+   * @return The cursor location description as a Vector of Strings
+   *         (an empty Vector if cursor location display is disabled).
+   */
+  public Vector getCursorStringVector() {
+    if (cursor_string) {
+      return (Vector) cursorStringVector.clone();
+    }
+    else {
+      return new Vector();
+    }
+  }
+
+  /**
+   * Return <CODE>Vector</CODE> of <CODE>String</CODE>s describing the
+   * cursor location, regardless of whether cursor location display is
+   * enabled.
+   * @return The cursor location description as a Vector of Strings.
+   */
+  public Vector getCursorStringVectorUnconditional() {
+    return (Vector) cursorStringVector.clone();
+  }
+
+  /**
+   * get the value of a RealType if it is included in the
+   * cursor location
+   * @param type RealType whose value to get
+   * @return value of type, or NaN if it is not included in
+   *         the cursor location
+   */
+  public double getDirectAxisValue(RealType type) {
+    return getDirectAxisValue(type.getName());
+  }
+
+  /**
+   * get the value of a named RealType if it is included in
+   * the cursor location
+   * @param name String name of RealType whose value to get
+   * @return value of named RealType, or NaN if it is not
+   *         included in the cursor location
+   */
+  public double getDirectAxisValue(String name) { 
+    synchronized (cursorStringVector) {
+      if (cursorStringVector != null) { 
+        Enumeration strings = cursorStringVector.elements();
+        while(strings.hasMoreElements()) {
+          String s = (String) strings.nextElement();
+          if (s.startsWith(name)) {
+            String t = s.substring(s.indexOf("=") + 2);
+            int i = t.indexOf(" ");
+            if (i >= 0) t = t.substring(0, i);
+            try {
+              double v = Double.valueOf(t).doubleValue();
+              return v;
+            }
+            catch (NumberFormatException e) {
+              return Double.NaN;
+            }
+          }
+        }
+      }
+    }
+    return Double.NaN;
+  } 
+
+  /**
+   * Set <CODE>Vector</CODE> of <CODE>String</CODE>s describing the
+   * cursor location by copy; this is invoked by direct manipulation
+   * renderers.
+   * @param vect String descriptions of cursor location.
+   */
+  public void setCursorStringVector(Vector vect) {
+    synchronized (cursorStringVector) {
+      cursorStringVector.removeAllElements();
+      if (vect != null) {
+        Enumeration strings = vect.elements();
+        while(strings.hasMoreElements()) {
+          cursorStringVector.addElement(strings.nextElement());
+        }
+      }
+    }
+    render_trigger();
+  }
+
+  /**
+   * Set <CODE>Vector</CODE> of <CODE>String</CODE>s describing the
+   * cursor location from the cursor location; this is invoked when the
+   * cursor location changes or the cursor display status changes
+   */
+  public void setCursorStringVector() {
+    synchronized (cursorStringVector) {
+      cursorStringVector.removeAllElements();
+      float[][] cursor = new float[3][1];
+      double[] cur = getCursor();
+      cursor[0][0] = (float) cur[0];
+      cursor[1][0] = (float) cur[1];
+      cursor[2][0] = (float) cur[2];
+      Enumeration maps = display.getMapVector().elements();
+      while(maps.hasMoreElements()) {
+        try {
+          ScalarMap map = (ScalarMap) maps.nextElement();
+          DisplayRealType dreal = map.getDisplayScalar();
+          DisplayTupleType tuple = dreal.getTuple();
+          int index = dreal.getTupleIndex();
+          if (tuple != null &&
+              (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+               (tuple.getCoordinateSystem() != null &&
+                tuple.getCoordinateSystem().getReference().equals(
+                Display.DisplaySpatialCartesianTuple)))) {
+            float[] fval = new float[1];
+            if (tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+              fval[0] = cursor[index][0];
+            }
+            else {
+              float[][] new_cursor =
+                tuple.getCoordinateSystem().fromReference(cursor);
+              fval[0] = new_cursor[index][0];
+            }
+            float[] dval = map.inverseScaleValues(fval);
+            RealType real = (RealType) map.getScalar();
+
+            // WLH 31 Aug 2000
+            Real r = new Real(real, dval[0]);
+            Unit overrideUnit = map.getOverrideUnit();
+            Unit rtunit = real.getDefaultUnit();
+            // units not part of Time string
+            // DRM 2003-08-19: don't check for equality since toString
+            // may be different
+            if (overrideUnit != null && //!overrideUnit.equals(rtunit) &&
+                (!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
+                 rtunit.getAbsoluteUnit().equals(rtunit))) {
+              dval[0] = (float)
+                overrideUnit.toThis((double) dval[0], rtunit);
+              r = new Real(real, dval[0], overrideUnit);
+            }
+            String valueString = r.toValueString();
+
+            // WLH 27 Oct 2000
+            String s = map.getScalarName() + " = " + valueString;
+            // String s = real.getName() + " = " + valueString;
+
+            cursorStringVector.addElement(s);
+          } // end if (tuple != null && ...)
+        }
+        catch (VisADException e) {
+        }
+      } // end while(maps.hasMoreElements())
+    } // end synchronized (cursorStringVector)
+    render_trigger();
+  }
+
+  /**
+   * trigger the graphics API to render the scene graph to the screen;
+   * intended to be over-ridden by graphics-API-specific extensions of
+   * DisplayRenderer
+   */
+  public void render_trigger() {
+  }
+
+  /**
+   * Return <CODE>true</CODE> if <CODE>type</CODE> is legal for this
+   * <CODE>DisplayRenderer</CODE>; for example, 2-D
+   * <CODE>DisplayRenderer</CODE>s use this to disallow mappings to
+   * <I>ZAxis</I> and <I>Latitude</I>.
+   * @param type The mapping type to check.
+   * @return <CODE>true</CODE> if <CODE>type</CODE> is legal.
+   */
+  public boolean legalDisplayScalar(DisplayRealType type) {
+    // First check to see if it is a member of the default list
+    for (int i=0; i<Display.DisplayRealArray.length; i++) {
+      if (Display.DisplayRealArray[i].equals(type)) return true;
+    }
+    // if we get here, it's not one of the defaults.  See if it has
+    // a CS that transforms to a default that we know how to handle
+    if (type.getTuple() != null && 
+        type.getTuple().getCoordinateSystem() != null) 
+    {
+        RealTupleType ref = 
+          type.getTuple().getCoordinateSystem().getReference();
+        if (ref.equals(Display.DisplaySpatialCartesianTuple) ||
+            ref.equals(Display.DisplayRGBTuple) ||
+            ref.equals(Display.DisplayFlow1Tuple) ||
+            ref.equals(Display.DisplayFlow2Tuple)) return true;
+    }
+    return false;
+  }
+
+  /**
+   * prepare for transforming Data into scene graph depictions,
+   * including possible auto-scaling of ScalarMaps
+   * @param temp Vector of DataRenderers
+   * @param tmap Vector of ScalarMaps
+   * @param go flag indicating whether Data transforms are requested
+   * @param initialize flag indicating whether auto-scaling is
+   *        requested
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public void prepareAction(Vector temp, Vector tmap, boolean go,
+                            boolean initialize)
+         throws VisADException, RemoteException {
+    DataShadow shadow = null;
+    Enumeration renderers = temp.elements();
+    while (renderers.hasMoreElements()) {
+      DataRenderer renderer = (DataRenderer) renderers.nextElement();
+      shadow = renderer.prepareAction(go, initialize, shadow);
+    }
+
+    if (shadow != null) {
+      // apply RealType ranges and animationSampling
+      Enumeration maps = tmap.elements();
+      while(maps.hasMoreElements()) {
+        ScalarMap map = ((ScalarMap) maps.nextElement());
+        map.setRange(shadow);
+      }
+    }
+
+    ScalarMap.equalizeFlow(tmap, Display.DisplayFlow1Tuple);
+    ScalarMap.equalizeFlow(tmap, Display.DisplayFlow2Tuple);
+  }
+
+  public abstract int getTextureWidthMax() 
+         throws VisADException; 
+
+  public abstract int getTextureHeightMax()
+         throws VisADException; 
+
+
+
+
+    /**
+       Set the ScaleRotation property.
+
+       @param value The new value for ScaleRotation
+    **/
+    public void setScaleRotation (boolean value) {
+	scaleRotation = value;
+    }
+
+    /**
+       Get the ScaleRotation property.
+
+       @return The ScaleRotation
+    **/
+    public boolean getScaleRotation () {
+	return scaleRotation;
+    }
+
+
+    /**
+       Set the RotateAboutCenter property.
+
+       @param value The new value for RotateAboutCenter
+    **/
+    public void setRotateAboutCenter (boolean value) {
+	rotateAboutCenter = value;
+    }
+
+    /**
+       Get the RotateAboutCenter property.
+
+       @return The RotateAboutCenter
+    **/
+    public boolean getRotateAboutCenter () {
+	return rotateAboutCenter;
+    }
+
+
+
+}
diff --git a/visad/DisplayTupleType.java b/visad/DisplayTupleType.java
new file mode 100644
index 0000000..ecce802
--- /dev/null
+++ b/visad/DisplayTupleType.java
@@ -0,0 +1,198 @@
+//
+// DisplayTupleType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DisplayTupleType is the class for tuples of DisplayRealType's.<P>
+*/
+public class DisplayTupleType extends RealTupleType {
+
+  /**
+   * construct a DisplayTupleType with null CoordinateSystem
+   * @param types array of DisplayRealType to be components
+   *              note a DisplayRealType may not be a component
+   *              of more than one DisplayTupleType
+   * @throws VisADException a VisAD error occurred
+   */
+  public DisplayTupleType(DisplayRealType[] types) throws VisADException {
+    this(types, null);
+  }
+
+  /** 
+   * construct a DisplayTupleType
+   * @param types array of DisplayRealType to be components
+   *              note a DisplayRealType may not be a component
+   *              of more than one DisplayTupleType
+   * @param coord_sys CoordinateSystem; if non-null, its reference
+   *                  must be another DisplayTupleType
+   * @throws VisADException a VisAD error occurred
+   */
+  public DisplayTupleType(DisplayRealType[] types, CoordinateSystem coord_sys)
+         throws VisADException {
+    super(types, coord_sys, null);
+    if (coord_sys != null) {
+      RealTupleType ref = coord_sys.getReference();
+      if (!(ref instanceof DisplayTupleType)) {
+        throw new CoordinateSystemException("DisplayTupleType: " +
+                    "CoordinateSystem.Reference must be a DisplayTupleType");
+      }
+      else if (Display.DisplaySpatialOffsetTuple.equals(ref)) {
+        throw new CoordinateSystemException("DisplayTupleType: " +
+         "CoordinateSystem.Reference cannot be DisplaySpatialOffsetTuple");
+      }
+      Unit[] default_units = getDefaultUnits();
+      Unit[] coord_sys_units = coord_sys.getCoordinateSystemUnits();
+      int n = default_units.length;
+      boolean match = true;
+      for (int i=0; i<n; i++) {
+        if (default_units[i] == null) {
+          if (coord_sys_units[i] != null) match = false;
+        }
+        else {
+          if (!default_units[i].equals(coord_sys_units[i])) match = false;
+        }
+      }
+      if (!match) {
+        throw new UnitException("RealTupleType: CoordinateSystem Units " +
+                                "must equal default Units");
+      }
+    } // end if (coord_sys != null)
+    setTuples(types, coord_sys);
+  }
+
+  /**
+   * construct a DisplayTupleType with null CoordinateSystem
+   * @param types array of DisplayRealType to be components
+   *              note a DisplayRealType may not be a component
+   *              of more than one DisplayTupleType
+   * @param b argument indicating this is a trusted constructor
+   *          for initializers
+   */
+  DisplayTupleType(DisplayRealType[] types, boolean b) {
+    this(types, null, b);
+  }
+
+  /**
+   * construct a DisplayTupleType
+   * @param types array of DisplayRealType to be components
+   *              note a DisplayRealType may not be a component
+   *              of more than one DisplayTupleType
+   * @param coord_sys CoordinateSystem; if non-null, its reference
+   *                  must be another DisplayTupleType
+   * @param b argument indicating this is a trusted constructor
+   *          for initializers
+   */
+  DisplayTupleType(DisplayRealType[] types, CoordinateSystem coord_sys,
+                   boolean b) {
+    super(types, coord_sys, b);
+    try {
+      setTuples(types, coord_sys);
+    }
+    catch (VisADException e) {
+      System.out.println(e);
+    }
+  }
+
+  private void setTuples(DisplayRealType[] types, CoordinateSystem coord_sys)
+          throws VisADException {
+    int n = types.length;
+    boolean[] circulars = new boolean[n];
+    for (int i=0; i<n; i++) circulars[i] = false;
+    if (coord_sys != null && coord_sys.getReference().equals(
+          Display.DisplaySpatialCartesianTuple)) {
+      double[] defaults = new double[n];
+      for (int i=0; i<n; i++) {
+        defaults[i] = types[i].getDefaultValue();
+      }
+      for (int i=0; i<n; i++) {
+        Unit u = types[i].getDefaultUnit();
+        if (u != null && Unit.canConvert(CommonUnit.degree, u)) {
+// System.out.println(types[i] + " unit " + u);
+          double[][] test = new double[n][37];
+          for (int j=0; j<n; j++) {
+            if (j == i) {
+              for (int k=0; k<37; k++) {
+                test[j][k] = u.toThis(10.0 * k, CommonUnit.degree);
+              }
+            }
+            else {
+              for (int k=0; k<37; k++) test[j][k] = defaults[j];
+            }
+          }
+// System.out.println(test[i][0] + " " + test[i][18] + " " + test[i][36]);
+          double[][] tt = coord_sys.toReference(test);
+          double diff180 = Math.sqrt(
+            (tt[0][18] - tt[0][0]) * (tt[0][18] - tt[0][0]) +
+            (tt[1][18] - tt[1][0]) * (tt[1][18] - tt[1][0]) +
+            (tt[2][18] - tt[2][0]) * (tt[2][18] - tt[2][0]));
+          double diff360 = Math.sqrt(
+            (tt[0][36] - tt[0][0]) * (tt[0][36] - tt[0][0]) +
+            (tt[1][36] - tt[1][0]) * (tt[1][36] - tt[1][0]) +
+            (tt[2][36] - tt[2][0]) * (tt[2][36] - tt[2][0]));
+          if (diff360 < 0.01 * diff180) {
+            circulars[i] = true;
+            double diff0 = 0.0;
+            double difflast = 0.0;
+            for (int k=0; k<37; k++) {
+              if (k == 36) {
+                if (difflast < 0.1 * diff0 ||
+                    diff0 < 0.1 * difflast) {
+                  circulars[i] = false;
+                  break;
+                }
+              }
+              else {
+                double diff = Math.sqrt(
+                  (tt[0][k+1] - tt[0][k]) * (tt[0][k+1] - tt[0][k]) +
+                  (tt[1][k+1] - tt[1][k]) * (tt[1][k+1] - tt[1][k]) +
+                  (tt[2][k+1] - tt[2][k]) * (tt[2][k+1] - tt[2][k]));
+                if (k == 0) {
+                  diff0 = diff;
+                }
+                else {
+                  if (difflast < 0.1 * diff ||
+                      diff < 0.1 * difflast) {
+                    circulars[i] = false;
+                    break;
+                  }
+                }
+                difflast = diff;
+              }
+            }
+          }
+// System.out.println("diff180 = " + diff180 + " diff360 = " + diff360 +
+//                    " " + circulars[i]);
+        } // end if (u != null && Unit.canConvert(CommonUnit.degree, u))
+      } // end for (int i=0; i<n; i++)
+    }
+    for (int i=0; i<n; i++) {
+      types[i].setTuple(this, i, circulars[i]);
+    }
+  }
+
+}
+
diff --git a/visad/DomainException.java b/visad/DomainException.java
new file mode 100644
index 0000000..267c346
--- /dev/null
+++ b/visad/DomainException.java
@@ -0,0 +1,47 @@
+//
+// DomainException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Supports exceptions for bad or invalid or inappropriate domains
+ * of Fields.<P>
+ */
+public class DomainException extends FieldException {
+
+  /**
+   * construct a DomainException with null message String
+   */
+  public DomainException() { super(); }
+
+  /**
+   * construct a DomainException with given message String
+   * @param s message String
+   */
+  public DomainException(String s) { super(s); }
+
+}
+
diff --git a/visad/DoubleSet.java b/visad/DoubleSet.java
new file mode 100644
index 0000000..7e2e11c
--- /dev/null
+++ b/visad/DoubleSet.java
@@ -0,0 +1,163 @@
+//
+// DoubleSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   DoubleSet represents the finite (but large) set of samples of
+   R^dimension made by vectors of IEEE double precision floating
+   point numbers.  DoubleSet objects are immutable.<P>
+
+   DoubleSet cannot be used for the domain sampling of a Field.<P>
+*/
+public class DoubleSet extends SimpleSet {
+
+  /**
+   * construct a DoubleSet with null CoordinateSystem and Units
+   * @param type MathType for this DoubleSet, must be SetType,
+   *             RealTupleType or RealType
+   * @throws VisADException a VisAD error occurred
+   */
+  public DoubleSet(MathType type) throws VisADException {
+    this(type, null, null);
+  }
+
+  /** 
+   * construct a DoubleSet with null CoordinateSystem and Units 
+   * @param type MathType for this DoubleSet, must be SetType,
+   *             RealTupleType or RealType
+   * @param coord_sys CoordinateSystem for Set domain, must be
+   *                  compatible with default for type
+   * @param units array of Units for Real values in Set domain,
+   *              must be compatible with defaults for type
+   * @throws VisADException a VisAD error occurred
+   */
+  public DoubleSet(MathType type, CoordinateSystem coord_sys, Unit[] units)
+         throws VisADException {
+    super(type, coord_sys, units, null); // no ErrorEstimate for DoubleSet
+  }
+
+  /**
+   * for DoubleSet, this always throws a SetException
+   * @param index array of integer indices
+   * @return float[domain_dimension][indices.length] array of
+   *         Set values (but always throws SetException instead)
+   * @throws VisADException a VisAD error occurred
+   */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    throw new SetException("DoubleSet.indexToValue");
+  }
+
+  /**
+   * for DoubleSet, this always throws a SetException
+   * @param value float[domain_dimension][number_of_values] array of
+   *        Set values
+   * @return array of integer indices (but always throws SetException
+   *         instead)
+   * @throws VisADException a VisAD error occurred
+   */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    throw new SetException("DoubleSet.valueToIndex");
+  }
+
+  /**
+   * for DoubleSet, this always throws a SetException
+   * @param value float[domain_dimension][number_of_values] array of
+   *        Set values
+   * @param indices int[number_of_values][] array for returning Set
+   *                indices
+   * @param weights float[number_of_values][] array for returning
+   *                weights
+   * @throws VisADException a VisAD error occurred
+   */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float weights[][]) throws VisADException {
+    throw new SetException("DoubleSet.valueToInterp");
+  }
+
+  /**
+   * for DoubleSet, this always throws a SetException
+   * @return length of Set (but always throws SetException instead)
+   * @throws VisADException a VisAD error occurred
+   */
+  public int getLength() throws VisADException {
+    throw new SetException("DoubleSet.getLength");
+  }
+
+  /**
+   * Indicates whether or not this instance is equal to an object
+   * @param set the object in question.
+   * @return <code>true</code> if and only if this instance equals set.
+   */
+  public boolean equals(Object set) {
+    if (!(set instanceof DoubleSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    return (DomainDimension == ((DoubleSet) set).getDimension());
+  }
+
+  /**
+   * @return false (a DoubleSet is never missing)
+   */
+  public boolean isMissing() {
+    return false;
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                      A clone of this instance.
+   */
+  public final Object clone() {
+      /*
+       * I (Steve Emmerson) believe that this implementation should return
+       * "this" to reduce the memory footprint but Bill believes that doing so
+       * would be counter-intuitive and might harm applications.
+       */
+      return super.clone();
+  }
+
+  /**
+   * Clones this instance with a different MathType.
+   *
+   * @param type MathType for returned DoubleSet
+   * @return                      A clone of this instance.
+   */
+  public Object cloneButType(MathType type) throws VisADException {
+    return new DoubleSet(type, DomainCoordinateSystem, SetUnits);
+  }
+
+  /**
+   * @param pre String added to start of each line
+   * @return a longer String than returned by toString(),
+   *         indented by pre (a string of blanks)
+   */
+  public String longString(String pre) throws VisADException {
+    return pre + "DoubleSet: Dimension = " + DomainDimension + "\n";
+  }
+
+}
+
diff --git a/visad/DoubleStringTuple.java b/visad/DoubleStringTuple.java
new file mode 100644
index 0000000..e80a100
--- /dev/null
+++ b/visad/DoubleStringTuple.java
@@ -0,0 +1,299 @@
+// DoubleStringTuple.java
+//
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray,
+Tommy Jasmin and Jeff McWhirter.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+
+import java.rmi.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * This provides a LoCal Tuple that can hold numeric and string values without
+ * taking the hit that having slots and lots of Real and Text objects around.
+ *
+ * @author MetApps Development Team
+ * @version $Revision: 1.6 $ $Date: 2009-12-01 14:59:41 $
+ */
+public class DoubleStringTuple extends Tuple {
+
+  private Data[] prototypes; 
+
+  /** The string values */
+  String[] strings;
+
+  /** The numeric values */
+  double[] doubles;
+
+  /** The tuple type */
+  TupleType tt;
+
+  /** The units for the numeric values */
+  Unit[] units;
+
+  /** Holds the components as we create them */
+  Data[] components;
+
+  /** my size */
+  int size;
+
+
+  /**
+   * Construct a DoubleStringTuple
+   *
+   * @param type The type
+   * @param units The units for the reals
+   * @param doubles The reals
+   * @param strings The strings
+   */
+  public DoubleStringTuple(TupleType type, double[] doubles,
+                           String[] strings, Unit[] units) {
+      this(type, null,doubles, strings, units);
+  }
+
+  public DoubleStringTuple(TupleType type, Data[] prototypes,double[] doubles,
+                           String[] strings, Unit[] units) {
+    super(type);
+    this.tt = type;
+    this.prototypes = prototypes;
+    this.units = units;
+    this.doubles = doubles;
+    this.strings = strings;
+    size = 0;
+
+    if (doubles != null) {
+      size += doubles.length;
+    }
+
+    if (strings != null) {
+      size += strings.length;
+    }
+  }
+
+
+  /**
+   * Check if there is no Data in this Tuple.
+   * @return true if there is no data.
+   */
+  public boolean isMissing() {
+      return doubles == null && strings == null;
+  }
+
+
+  /**
+   * Make a tuple type from lists of scalar types
+   *
+   * @param numericTypes   List of RealTypes
+   * @param stringTypes   List of TextTypes
+   *
+   * @return TupleType of the lists
+   *
+   * @throws RemoteException   Java RMI problem
+   * @throws VisADException  unable to create TupleType
+   */
+  public static TupleType makeTupleType(List numericTypes, List stringTypes)
+          throws VisADException {
+    List allTypes = new ArrayList();
+    allTypes.addAll(numericTypes);
+    allTypes.addAll(stringTypes);
+    MathType[] tmp =
+      (MathType[]) allTypes.toArray(new MathType[allTypes.size()]);
+
+    return new TupleType(tmp);
+  }
+
+
+  /**
+   * Get the i'th component. This creates it if needed and
+   * stores it in the components array
+   *
+   * @param i Which one
+   *
+   * @return The component
+   *
+   * @throws RemoteException   Java RMI problem
+   * @throws VisADException  unable to create TupleType
+   */
+  public Data getComponent(int i) throws VisADException, RemoteException {
+      //    System.err.println ("DoubleStringTuple.getComponent:" + i);
+    if (components == null) {
+	components = new Data[size];
+    }
+    if (0 <= i && i < getDimension()) {
+      if (components[i] == null) {
+	  components[i] = getComponentInner(i);
+	  if (components[i] != null) {
+	      ((DataImpl) components[i]).setParent(this);
+	  }
+      }
+      return components[i];
+    }
+    else {
+      throw new TypeException("Tuple: component index out of range: "+i);
+    }
+
+  }
+
+
+  /**
+   * Actually get the component
+   *
+   * @param i i
+   *
+   * @return The component
+   *
+   * @throws RemoteException On badness
+   * @throws VisADException
+   */
+  private Data getComponentInner(int i)
+          throws VisADException, RemoteException {
+      //      System.err.println ("get component:" + i +"  doubles:" + doubles.length);
+    //System.err.println ("get component:" +tt.getComponent(i));
+    if ((doubles != null) && (i < doubles.length)) {
+       if(prototypes!=null) {
+	   return ((Real)prototypes[i]).cloneButValue(doubles[i]);
+      }
+      if ((units == null) || (units[i] == null)) {
+        return new Real((RealType) tt.getComponent(i), doubles[i]);
+      }
+      else {
+        return new Real((RealType) tt.getComponent(i), doubles[i], units[i]);
+      }
+    }
+
+    int idx = i-doubles.length;
+
+    return new Text((TextType) tt.getComponent(i), strings[idx]);
+
+  }
+
+
+  /**
+   * Create, if needed, and return the component array.
+   *
+   * @return components
+   */
+ public Data[] getComponents(boolean copy) {
+    try {
+	//       System.err.println ("DoubleStringTuple.getComponents");
+      //Create the array and populate it if needed
+      if (components == null) {
+	  components = new Data[size];
+      }
+      for (int i = 0; i < size; i++) {
+        components[i] = getComponent(i);
+      }
+
+      return components;
+    } catch (Exception exc) {
+      exc.printStackTrace();
+
+      throw new IllegalStateException("Error making component array:"+exc);
+    }
+  }
+
+
+  /**
+   * Indicates if this Tuple is identical to an object.
+   *
+   * @param obj         The object.
+   * @return            <code>true</code> if and only if the object is
+   *                    a Tuple and both Tuple-s have identical component
+   *                    sequences.
+   */
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (!(obj instanceof DoubleStringTuple)) {
+      return false;
+    }
+
+    DoubleStringTuple that = (DoubleStringTuple) obj;
+
+    return Arrays.equals(doubles, that.doubles)
+           && Arrays.equals(strings, that.strings) && tt.equals(that.tt)
+           && Arrays.equals(units, that.units);
+
+  }
+
+
+  /**
+   * Returns the hash code of this object.
+   * @return            The hash code of this object.
+   */
+  public int hashCode() {
+    int hashCode = 0;
+    if (doubles != null) {
+      hashCode ^= doubles.hashCode();
+    }
+
+    if (strings != null) {
+      hashCode ^= strings.hashCode();
+    }
+
+    hashCode ^= tt.hashCode();
+
+    return hashCode;
+  }
+
+  /**
+   * run 'java visad.DoubleStringTuple' to test the RealTuple class
+   *
+   * @param args  ignored
+   *
+   * @throws RemoteException  Java RMI problem
+   * @throws VisADException   Unable to create the VisAD objects
+   */
+  public static void main(String args[])
+          throws VisADException, RemoteException {
+    List reals = new ArrayList();
+    reals.add(RealType.Latitude);
+    reals.add(RealType.Longitude);
+    reals.add(RealType.Altitude);
+    List strings = new ArrayList();
+    strings.add(TextType.getTextType("id"));
+    double[] vals = new double[] {40, -105, 5337};
+    String[] svals = new String[] {"Boulder"};
+    Unit foot = CommonUnit.meter.scale(.3048);  // approximate
+    try {
+      foot = visad.data.units.Parser.parse("foot");
+    } catch (visad.data.units.ParseException pe) {}
+
+    Unit[] units = new Unit[] {CommonUnit.degree, CommonUnit.degree, foot};
+    DoubleStringTuple dst =
+      new DoubleStringTuple(DoubleStringTuple.makeTupleType(reals, strings),
+                            vals, svals, units);
+    System.out.println(dst);
+    System.out.println("serializable? "+
+                       visad.util.DataUtility.isSerializable(dst));
+  }
+
+}
diff --git a/visad/DoubleTuple.java b/visad/DoubleTuple.java
new file mode 100644
index 0000000..521f6f8
--- /dev/null
+++ b/visad/DoubleTuple.java
@@ -0,0 +1,356 @@
+// DoubleTuple.java
+//
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, 
+Tommy Jasmin and Jeff McWhirter.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+
+import visad.*;
+
+import java.rmi.RemoteException;
+
+import java.util.Arrays;
+
+
+/**
+ * This provides a LoCal RealTuple that can hold numeric values without
+ * taking the hit that having lots and lots of Real objects around.
+ *
+ * @author MetApps Development Team
+ * @version $Revision: 1.5 $ $Date: 2009-12-01 03:39:21 $
+ */
+public class DoubleTuple extends RealTuple {
+
+  private Data[] prototypes; 
+
+  /** The numeric values */
+  double[] doubles;
+
+  /** The tuple type */
+  RealTupleType tt;
+
+  /** The units for the numeric values */
+  Unit[] units;
+
+  /** Holds the components as we create them */
+  Data[] components;
+
+  /**
+   * Construct a new DoubleTuple of generic values
+   *
+   * @param doubles  the values
+   *
+   * @throws VisADException  problem creating MathType
+   */
+  public DoubleTuple(double[] doubles) throws VisADException {
+    this(makeGenericTypes(doubles.length), doubles, null);
+  }
+
+  /**
+   * Construct a new DoubleTuple
+   *
+   * @param type The type
+   * @param doubles The reals
+   */
+  public DoubleTuple(RealTupleType type, double[] doubles) {
+    this(type, doubles, null);
+  }
+
+  /**
+   * Construct a new DoubleTuple
+   *
+   * @param type The type
+   * @param doubles The reals
+   * @param units The units for the reals (may be null)
+   */
+  public DoubleTuple(RealTupleType type, double[] doubles, Unit[] units) {
+      this(type, null, doubles, units);
+  }
+
+ public DoubleTuple(RealTupleType type, Data[]prototypes, double[] doubles, Unit[] units) {
+    super(type);
+    this.tt = type;
+    this.prototypes = prototypes;
+    this.doubles = doubles;
+    this.units = units;
+
+    if (units == null) {
+      this.units = type.getDefaultUnits();
+    }
+  }
+
+  /**
+   * Make a RealTupleType of RealType.Generic for num elements
+   *
+   * @param num  number of elements
+   *
+   * @return corresponding RealTupleType
+   *
+   * @throws VisADException   unable to create the RealTupleType
+   */
+  private static RealTupleType makeGenericTypes(int num)
+          throws VisADException {
+    RealType[] types = new RealType[num];
+    Arrays.fill(types, RealType.Generic);
+    return new RealTupleType(types);
+  }
+
+
+  /**
+   * Check if there is no Data in this Tuple.
+   * @return true if there is no data.
+   */
+  public boolean isMissing() {
+      return doubles == null;
+  }
+
+  /**
+   * Get the i'th component. This creates it if needed and stores
+   * it in the components array
+   *
+   * @param i Which one
+   *
+   * @return The component
+   *
+   * @throws RemoteException On badness
+   * @throws VisADException On badness
+   */
+  public Data getComponent(int i) throws VisADException, RemoteException {
+    if (components == null) {
+      components = new Data[getDimension()];
+    }
+
+    if (0 <= i && i < getDimension()) {
+      if (components[i] == null) {
+        components[i] = getComponentInner(i);
+        if (components[i] != null) {
+          ((DataImpl) components[i]).setParent(this);
+        }
+      }
+      return components[i];
+    }
+    else {
+      throw new TypeException("Tuple: component index out of range: "+i);
+    }
+
+  }
+
+  /**
+   * Actually get the component
+   *
+   * @param i index
+   *
+   * @return The component
+   *
+   * @throws RemoteException On badness
+   * @throws VisADException
+   */
+  private Data getComponentInner(int i)
+          throws VisADException, RemoteException {
+    //System.err.println ("get component:" +tt.getComponent(i));
+    if ((doubles != null)) {
+      if(prototypes!=null) {
+	    return ((Real) prototypes[i]).cloneButValue(doubles[i]);
+      }
+      if ((units == null) || (units[i] == null)) {
+        return new Real((RealType) tt.getComponent(i), doubles[i]);
+      }
+      else {
+        return new Real((RealType) tt.getComponent(i), doubles[i], units[i]);
+      }
+    }
+
+    return null;
+  }
+
+
+  /**
+   * Create, if needed, and return the component array.
+   *
+   * @return components
+   */
+  public Data[] getComponents(boolean copy) {
+    try {
+      //Create the array and populate it if needed
+      if (components == null) {
+	  components = new Data[getDimension()];
+      }
+      for (int i = 0; i < getDimension(); i++) {
+	  components[i] = getComponent(i);
+      }
+      return components;
+    } catch (Exception exc) {
+      throw new IllegalStateException("Error making component array:"+exc);
+    }
+  }
+
+  /**
+   * Get Units of Real components
+   *
+   * @return the units of the components
+   */
+  public Unit[] getTupleUnits() {
+    return Unit.copyUnitsArray(units);
+  }
+
+  /**
+   * Get the values of the Real components
+   * @return double array of the values of each Real component
+   */
+  public double[] getValues() {
+    if (doubles == null) {
+      return null;
+    }
+
+    return (double[]) doubles.clone();
+  }
+
+
+  /**
+   * Indicates if this Tuple is identical to an object.
+   *
+   * @param obj         The object.
+   * @return            <code>true</code> if and only if the object is
+   *                    a Tuple and both Tuple-s have identical component
+   *                    sequences.
+   */
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (!(obj instanceof DoubleTuple)) {
+      return false;
+    }
+
+    DoubleTuple that = (DoubleTuple) obj;
+
+    return Arrays.equals(doubles, that.doubles) && tt.equals(that.tt)
+           && Arrays.equals(units, that.units);
+
+  }
+
+
+  /**
+   * Returns the hash code of this object.
+   * @return            The hash code of this object.
+   */
+  public int hashCode() {
+    int hashCode = 0;
+    if (doubles != null) {
+      hashCode ^= doubles.hashCode();
+    }
+
+    hashCode ^= tt.hashCode();
+
+    return hashCode;
+  }
+
+
+  /**
+   * run 'java visad.DoubleTuple' to test the RealTuple class
+   *
+   * @param args  ignored
+   *
+   * @throws RemoteException  Java RMI problem
+   * @throws VisADException   Unable to create the VisAD objects
+   */
+  public static void main(String args[])
+          throws VisADException, RemoteException {
+
+    byte b = 10;
+    Real w = new Real(b);
+
+    Real[] reals1 = {new Real(1), new Real(2), new Real(3)};
+    //RealTuple rt1    = new RealTuple(reals1);
+    DoubleTuple rt1 = new DoubleTuple(new double[] {1, 2, 3});
+    DoubleTuple rt2 = new DoubleTuple(new double[] {6, 5, 4});
+
+    System.out.println("rt1 = "+rt1+"\nrt2 = "+rt2);
+
+    System.out.println("rt1 + rt2 = "+rt1.add(rt2));
+    System.out.println("rt1 - rt2 = "+rt1.subtract(rt2));
+    System.out.println("rt1 * rt2 = "+rt1.multiply(rt2));
+    System.out.println("rt1 / rt2 = "+rt1.divide(rt2));
+    System.out.println("sqrt(rt1) = "+rt1.sqrt());
+
+    System.out.println("rt1 + w = "+rt1.add(w));
+    System.out.println("rt1 - w = "+rt1.subtract(w));
+    System.out.println("rt1 * w = "+rt1.multiply(w));
+    System.out.println("rt1 / w = "+rt1.divide(w));
+
+    System.out.println("w + rt2 = "+w.add(rt2));
+    System.out.println("w - rt2 = "+w.subtract(rt2));
+    System.out.println("w * rt2 = "+w.multiply(rt2));
+    System.out.println("w / rt2 = "+w.divide(rt2));
+
+    RealTupleType tt = RealTupleType.LatitudeLongitudeAltitude;
+    Unit foot = CommonUnit.meter.scale(.3048);  // approximate
+    try {
+      foot = visad.data.units.Parser.parse("foot");
+    } catch (visad.data.units.ParseException pe) {}
+
+    DoubleTuple one = new DoubleTuple(tt, new double[] {40.5, -105.0, 5338},
+                                      new Unit[] {CommonUnit.degree,
+            CommonUnit.degree, foot});
+    System.out.println("one = "+one.toString());
+    visad.util.DataUtility.isSerializable(one, true);
+    visad.util.Util.printArray("one.values", one.getValues());
+    visad.util.Util.printArray("one.units", one.getTupleUnits());
+    System.out.println("one.cs = "+one.getCoordinateSystem());
+    DoubleTuple two = new DoubleTuple(tt, new double[] {45, -75.0, 400});
+    System.out.println("two = "+two.toString());
+    System.out.println("one - two = "+one.subtract(two));
+    System.out.println("one + two = "+one.add(two));
+  }
+
+  /* Here's the output:
+
+  rt1 = (1.0, 2.0, 3.0)
+  rt2 = (6.0, 5.0, 4.0)
+  rt1 + rt2 = (7.0, 7.0, 7.0)
+  rt1 - rt2 = (-5.0, -3.0, -1.0)
+  rt1 * rt2 = (6.0, 10.0, 12.0)
+  rt1 / rt2 = (0.16666666666666666, 0.4, 0.75)
+  sqrt(rt1) = (1.0, 1.4142135623730951, 1.7320508075688772)
+  rt1 + w = (11.0, 12.0, 13.0)
+  rt1 - w = (-9.0, -8.0, -7.0)
+  rt1 * w = (10.0, 20.0, 30.0)
+  rt1 / w = (0.1, 0.2, 0.3)
+  w + rt2 = (16.0, 15.0, 14.0)
+  w - rt2 = (4.0, 5.0, 6.0)
+  w * rt2 = (60.0, 50.0, 40.0)
+  w / rt2 = (1.6666666666666667, 2.0, 2.5)
+  one = (40.5, -105.0, 5338.0)
+  one.values: [0]: 40.5 [1]: -105.0 [2]: 5338.0
+  one.units: [0]: deg [1]: deg [2]: international foot
+  one.cs = null
+  two = (45.0, -75.0, 400.0)
+  one - two = (-4.5, -30.0, 4025.6640419947507)
+  one + two = (85.5, -180.0, 6650.335958005249)
+  */
+
+}
+
diff --git a/visad/EarthVectorType.java b/visad/EarthVectorType.java
new file mode 100644
index 0000000..00698a7
--- /dev/null
+++ b/visad/EarthVectorType.java
@@ -0,0 +1,144 @@
+//
+// EarthVectorType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/* 5 May 99: TMW says this is the standard.  He also says that
+winds not on a grid have components in earth coordinates */
+
+/**
+   EarthVectorType is the VisAD data type for 2-D and 3-D wind
+   or current vectors in Units convertable with meter / second
+   whose first component is parallel to latitude lines, positive
+   east, and whose second component is parallel to longitude lines,
+   positive north.  It assumes vertical coordinates transform
+   nearly flat, so the optional third vertical wind component
+   does not transform. <P>
+*/
+public class EarthVectorType extends RealVectorType {
+
+  public EarthVectorType(RealType[] types) throws VisADException {
+    this(types, null);
+  }
+
+  public EarthVectorType(RealType[] types, CoordinateSystem coord_sys)
+         throws VisADException {
+    super(types, coord_sys);
+    if (types.length != 2 && types.length != 3) {
+      throw new TypeException("EarthVectorType must be 2-D or 3-D: " + types.length);
+    }
+    for (int i=0; i<types.length; i++) {
+      if (!Unit.canConvert(CommonUnit.meterPerSecond, types[i].getDefaultUnit())) {
+        throw new TypeException("EarthVectorType components must be convertable " +
+                                "with meter / second: " + types[i].getDefaultUnit());
+      }
+    }
+  }
+
+  public EarthVectorType(RealType a) throws VisADException {
+    super(a);
+  }
+
+  public EarthVectorType(RealType a, RealType b) throws VisADException {
+    super(a, b);
+  }
+
+  public EarthVectorType(RealType a, RealType b, RealType c)
+         throws VisADException {
+    super(a, b, c);
+  }
+
+  public EarthVectorType(RealType a, RealType b, RealType c, RealType d)
+         throws VisADException {
+    super(a, b, c, d);
+  }
+
+  /** transform an array of vector values from a field, based on a
+      coordinate transform of the field domain.  This may use the
+      Jacobean of the coordinate transform, but may be more complex.
+      For example, vectors in m/s would not transform for a simple
+      rescaling transform.  Or the transform may be to a moving
+      coordinate system.
+
+      out, coord_out, units_out, in, coord_in, units_in are the
+      arguments to the corresponding call to transformCoordinates;
+      loc_errors_out are the ErrorEstimates for loc from that call;
+      inloc and outloc contain the input and output values from the
+      corresponding call to transformCoordinates;
+      coord_vector and errors_in are the CoordinateSystem and ErrorEstimates
+      associated with values;
+      value are the vector values (already resampled at loc);
+      return new value array;
+      return transformed ErrorEstimates in errors_out array */
+  public double[][] transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        ErrorEstimate[] errors_in,
+                        ErrorEstimate[] errors_out,
+                        double[][] inloc, double[][] outloc,
+                        double[][] value)
+         throws VisADException, RemoteException {
+    return value;
+  }
+
+  /** transform an array of vector values from a field, based on a
+      coordinate transform of the field domain.  This may use the
+      Jacobean of the coordinate transform, but may be more complex.
+      For example, vectors in m/s would not transform for a simple
+      rescaling transform.  Or the transform may be to a moving
+      coordinate system.
+
+      out, coord_out, units_out, in, coord_in, units_in are the
+      arguments to the corresponding call to transformCoordinates;
+      loc_errors_out are the ErrorEstimates for loc from that call;
+      inloc and outloc contain the input and output values from the
+      corresponding call to transformCoordinates;
+      coord_vector and errors_in are the CoordinateSystem and ErrorEstimates
+      associated with values;
+      value are the vector values (already resampled at loc);
+      return new value array;
+      return transformed ErrorEstimates in errors_out array */
+  public float[][] transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        ErrorEstimate[] errors_in,
+                        ErrorEstimate[] errors_out,
+                        float[][] inloc, float[][] outloc,
+                        float[][] value)
+         throws VisADException, RemoteException {
+    return value;
+  }
+
+// ShadowType.java: need to account for spatial setRange scaling in flow
+
+}
+
diff --git a/visad/EmpiricalCoordinateSystem.java b/visad/EmpiricalCoordinateSystem.java
new file mode 100644
index 0000000..f546423
--- /dev/null
+++ b/visad/EmpiricalCoordinateSystem.java
@@ -0,0 +1,432 @@
+//
+// EmpiricalCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+import visad.util.Util;
+
+/**
+ * Provides support for empirically-defined CoordinateSystem-s.  This is useful
+ * for data-dependent coordinate transformations that must be determined
+ * empirically rather than analytically (e.g. pressure <-> height).</p>
+ *
+ * <p>Coordinates in this system are termed "world" coordinates and coordinates
+ * in the reference coordinate system are termed "reference" coordinates.</p>
+ *
+ * <p>Instances of this class are immutable.
+ *
+ * @author Steven R. Emmerson
+ */
+public final class
+EmpiricalCoordinateSystem
+  extends       CoordinateSystem
+  implements    java.io.Serializable
+{
+  /**
+   * The world/grid coordinate system.
+   * @serial
+   */
+  private final GridCoordinateSystem    worldCS;
+
+  /**
+   * The reference/grid coordinate system.
+   * @serial
+   */
+  private final GridCoordinateSystem    referenceCS;
+
+  /**
+   * Constructs from two GriddedSet-s.  The dimensionality (i.e. rank and
+   * lengths) of the two sets must be identical.  The RealTupleType of the
+   * reference will be that of the reference set.  The units of the world
+   * coordinates will be the actual units of the world set.
+   * @param world               A set of world coordinates.
+   *                            <code>world.getLengths()</code> shall equal
+   *                            <code>reference.getLengths()</code>.  Determines
+   *                            the default units of world coordinates:
+   *                            the default units will be those of <code>
+   *                            world.getSetUnits()</code>.
+   * @param reference           A set of reference coordinates.  Determines
+   *                            the reference RealTupleType.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public
+  EmpiricalCoordinateSystem(GriddedSet world, GriddedSet reference)
+    throws VisADException
+  {
+    this(world, reference, true, true);
+  }
+
+  /**
+   * Constructs from two GriddedSet-s.  The dimensionality (i.e. rank and
+   * lengths) of the two sets must be identical.  The RealTupleType of the
+   * reference will be that of the reference set.  The units of the world
+   * coordinates will be the actual units of the world set.
+   * @param world               A set of world coordinates.
+   *                            <code>world.getLengths()</code> shall equal
+   *                            <code>reference.getLengths()</code>.  Determines
+   *                            the default units of world coordinates:
+   *                            the default units will be those of <code>
+   *                            world.getSetUnits()</code>.
+   * @param reference           A set of reference coordinates.  Determines
+   *                            the reference RealTupleType.
+   * @param copy                copy samples if clone is made
+   * @param check               check whether samples form a valid grid
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public
+  EmpiricalCoordinateSystem(GriddedSet world, GriddedSet reference, boolean copy, boolean check)
+    throws VisADException
+  {
+    super(((SetType)reference.getType()).getDomain(), world.getSetUnits());
+    worldCS = new GridCoordinateSystem(ensureNoCoordinateSystem(world, copy, check));
+    referenceCS = new GridCoordinateSystem(ensureNoCoordinateSystem(reference, copy, check));
+  }
+
+  /**
+   * Ensures that a GriddedSet doesn't have a default CoordinateSystem.
+   *
+   * @param griddedSet          The GriddedSet to not have a CoordinateSystem.
+   * @return                    The GriddedSet without a CoordinateSystem.
+   *                            May be the original GriddedSet.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  protected static GriddedSet
+  ensureNoCoordinateSystem(GriddedSet griddedSet)
+    throws VisADException
+  {
+      return ensureNoCoordinateSystem(griddedSet, true, true);
+  }
+
+  /**
+   * Ensures that a GriddedSet doesn't have a default CoordinateSystem.
+   *
+   * @param griddedSet          The GriddedSet to not have a CoordinateSystem.
+   * @return                    The GriddedSet without a CoordinateSystem.
+   *                            May be the original GriddedSet.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  protected static GriddedSet
+  ensureNoCoordinateSystem(GriddedSet griddedSet, boolean copy, boolean check)
+    throws VisADException
+  {
+    if (griddedSet.getCoordinateSystem() != null)
+    {
+      /*
+       * The GriddedSet has a CoordinateSystem.  Clone the GriddedSet without
+       * the CoordinateSystem.
+       */
+      SetType           setType = (SetType)griddedSet.getType();
+      RealTupleType     realTupleType = setType.getDomain();
+      if (realTupleType.getCoordinateSystem() != null)
+        setType =
+          new SetType(new RealTupleType(realTupleType.getRealComponents()));
+      griddedSet =
+        GriddedSet.create(
+          setType,
+          griddedSet.getSamples(),
+          griddedSet.getLengths(),
+          (CoordinateSystem)null,
+          griddedSet.getSetUnits(),
+          griddedSet.getSetErrors(),
+          copy, check);
+    }
+    return griddedSet;
+  }
+
+  /**
+   * Constructs an EmpiricalCoordinateSystem from a Field.  The
+   * <code>toReference()</code> method of the resulting CoordinateSystem will
+   * transform numeric values in actual units of the domain of the field to
+   * numeric values in default units of the range of the field.  Similarly,
+   * the <code>fromReference()</code> method will transform numeric values in
+   * default units of the range of the field to numeric values in the actual
+   * units of the domain of the field.
+   *
+   * @param field               The FlatField comprising a coordinate system
+   *                            transformation from the domain to the range.
+   *                            The rank of the domain and range shall be the
+   *                            same.  The domain set of the field shall be a
+   *                            GriddedSet.
+   * @throws SetException       The field's domain set isn't a GriddedSet.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   * @throws RemoteException    Java RMI failure.
+   */
+  public static EmpiricalCoordinateSystem
+  create(Field field)
+    throws SetException, VisADException, RemoteException
+  {
+    Set         domainSet = field.getDomainSet();
+    float[][]   samples = field.getFloats(false);       // in default units
+    if (!(domainSet instanceof GriddedSet))
+      throw new SetException("Domain set must be GriddedSet");
+    return
+      new EmpiricalCoordinateSystem(
+        (GriddedSet)domainSet,
+        GriddedSet.create(
+          ((FunctionType)field.getType()).getFlatRange(),
+          samples,
+          new int[] {samples[0].length},
+          (CoordinateSystem)null,
+          (Unit[])null,
+          (ErrorEstimate[])null));
+  }
+
+  /**
+   * Constructs an EmpiricalCoordinateSystem from a Field.  The
+   * <code>toReference()</code> method of the resulting CoordinateSystem will
+   * transform numeric values in actual units of the range of the field to
+   * numeric values in default units of the domain of the field.  Similarly,
+   * the <code>fromReference()</code> method will transform numeric values in
+   * default units of the domain of the field to numeric values in the actual
+   * units of the range of the field.  The coordinate system transformation
+   * created by this method is the inverse of that created by {@link
+   * #create(Field)}.
+   *
+   * @param field               The FlatField comprising a coordinate system
+   *                            transformation from the range to the domain.
+   *                            The rank of the domain and range shall be the
+   *                            same.  The domain set of the field shall be a
+   *                            GriddedSet.
+   * @throws SetException       The field's domain set isn't a GriddedSet.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   * @throws RemoteException    Java RMI failure.
+   */
+  public static EmpiricalCoordinateSystem
+  inverseCreate(Field field)
+    throws SetException, VisADException, RemoteException
+  {
+    Set         domainSet = field.getDomainSet();
+    float[][]   samples = field.getFloats(false);       // in default units
+    if (!(domainSet instanceof GriddedSet))
+      throw new SetException("Domain set must be GriddedSet");
+    return
+      new EmpiricalCoordinateSystem(
+        GriddedSet.create(
+          ((FunctionType)field.getType()).getFlatRange(),
+          samples,
+          new int[] {samples[0].length},
+          (CoordinateSystem)null,
+          (Unit[])null,
+          (ErrorEstimate[])null),
+        (GriddedSet)domainSet);
+  }
+
+  /**
+   * Returns the Set of world coordinates.
+   * @return                    The Set of world coordinates.
+   */
+  public GriddedSet
+  getWorldSet()
+  {
+    return worldCS.getGriddedSet();
+  }
+
+  /**
+   * Returns the Set of reference coordinates.
+   * @return                    The Set of reference coordinates.
+   */
+  public GriddedSet
+  getReferenceSet()
+  {
+    return referenceCS.getGriddedSet();
+  }
+
+  /**
+   * Converts reference coordinates to world coordinates.
+   * @param values              Numeric reference coordinates in default units
+   *                            of the reference RealTupleType to be
+   *                            converted to numeric world coordinates.
+   *                            <code>values.length</code> shall
+   *                            equal <code>getDimension()</code> and
+   *                            <code>values[i].length</code> shall be the same
+   *                            for all <code>i</code>.
+   * @return                    <code>values</code> which now contains
+   *                            world coordinates.  Values which could
+   *                            not be converted will have the value
+   *                            <code>Double.NaN</code>.
+   * @throws SetException       Mismatch between input values and field domain.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public double[][]
+  fromReference(double[][] values)
+    throws SetException, VisADException
+  {
+    return worldCS.toReference(referenceCS.fromReference(values));
+  }
+
+  /**
+   * Convert world coordinates to reference coordinates.
+   * @param values              Numeric world coordinates to be converted
+   *                            to numeric reference coordinates in default
+   *                            units of the reference RealTupleType.
+   *                            <code>values.length</code> shall
+   *                            equal <code>getDimension()</code> and
+   *                            <code>values[i].length</code> shall be the same
+   *                            for all <code>i</code>.
+   * @return                    <code>values</code> which now contains
+   *                            world coordinates.  Values which could
+   *                            not be converted will have the value
+   *                            <code>Double.NaN</code>.
+   * @throws SetException       Mismatch between input values and field domain.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public double[][]
+  toReference(double[][] values)
+    throws SetException, VisADException
+  {
+    GriddedSet set = worldCS.getGriddedSet();
+    double[][] samples = set.getDoubles(false);
+    if (set.getLength() == values[0].length) {
+    int[] lens = set.getLengths();
+    int dim = lens.length;
+    double[][] tvalues = new double[values.length][values[0].length];
+    int[] save_index  = new int[values[0].length];
+    int start = 0;
+    int cnt = 0;
+    for (int i = 0; i < values[0].length; i++) {
+        boolean all = true;
+        for (int k = 0; k < dim; k++) {
+          if (!(Util.isApproximatelyEqual(values[k][i], samples[k][i]))) {
+            all = false;
+          }
+        }
+        if (all) {
+          int k = i;
+          int[] indexI = new int[dim];
+          for (int j=0; j<dim-1; j++) {
+            tvalues[j][i] = k % lens[j];
+            k = k / lens[j];
+          }
+          tvalues[dim-1][i] = k;
+        }
+        else {
+          return referenceCS.toReference(worldCS.fromReference(values));
+        }
+    }
+    return referenceCS.toReference(tvalues);
+    }
+    else {
+      return referenceCS.toReference(worldCS.fromReference(values));
+    }
+  }
+
+  /**
+   * Converts reference coordinates to world coordinates.
+   * @param values              Numeric reference coordinates in default units
+   *                            of the reference RealTupleType to be
+   *                            converted to numeric world coordinates.
+   *                            <code>values.length</code> shall
+   *                            equal <code>getDimension()</code> and
+   *                            <code>values[i].length</code> shall be the same
+   *                            for all <code>i</code>.
+   * @return                    <code>values</code> which now contains
+   *                            world coordinates.  Values which could
+   *                            not be converted will have the value
+   *                            <code>Double.NaN</code>.
+   * @throws SetException       Mismatch between input values and field domain.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public float[][]
+  fromReference(float[][] values)
+    throws SetException, VisADException
+  {
+    return worldCS.toReference(referenceCS.fromReference(values));
+  }
+
+  /**
+   * Convert world coordinates to reference coordinates.
+   * @param values              Numeric world coordinates to be converted
+   *                            to numeric reference coordinates in default
+   *                            units of the reference RealTupleType.
+   *                            <code>values.length</code> shall
+   *                            equal <code>getDimension()</code> and
+   *                            <code>values[i].length</code> shall be the same
+   *                            for all <code>i</code>.
+   * @return                    <code>values</code> which now contains
+   *                            world coordinates.  Values which could
+   *                            not be converted will have the value
+   *                            <code>Double.NaN</code>.
+   * @throws SetException       Mismatch between input values and field domain.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public float[][]
+  toReference(float[][] values)
+    throws SetException, VisADException
+  {
+    GriddedSet set = worldCS.getGriddedSet();
+    float[][] samples = set.getSamples(false);
+    if (set.getLength() == values[0].length) {
+    int[] lens = set.getLengths();
+    int dim = lens.length;
+    float[][] tvalues = new float[values.length][values[0].length];
+    int start = 0;
+    int cnt = 0;
+    for (int i = 0; i < values[0].length; i++) {
+        boolean all = true;
+        for (int k = 0; k < dim; k++) {
+          if (!(Util.isApproximatelyEqual(values[k][i], samples[k][i]))) {
+            all = false;
+          }
+        }
+        if (all) {
+          int k = i;
+          int[] indexI = new int[dim];
+          for (int j=0; j<dim-1; j++) {
+            tvalues[j][i] = k % lens[j];
+            k = k / lens[j];
+          }
+          tvalues[dim-1][i] = k;
+        }
+        else {
+          return referenceCS.toReference(worldCS.fromReference(values));
+        }
+    }
+    return referenceCS.toReference(tvalues);
+    }
+    else {
+      return referenceCS.toReference(worldCS.fromReference(values));
+    }
+  }
+
+  /**
+   * Indicates if this coordinate system is semantically identical to an
+   * object.
+   * @param object              The object in question.
+   * @return                    <code>true</code> if and only if this
+   *                            coordinate system is semantically identical to
+   *                            <code>object</code>.
+   */
+  public boolean
+  equals(Object object)
+  {
+    if (!(object instanceof EmpiricalCoordinateSystem))
+      return false;
+    EmpiricalCoordinateSystem   that = (EmpiricalCoordinateSystem)object;
+    return
+      worldCS.equals(that.worldCS) && referenceCS.equals(that.referenceCS);
+  }
+}
diff --git a/visad/ErrorEstimate.java b/visad/ErrorEstimate.java
new file mode 100644
index 0000000..cd9b989
--- /dev/null
+++ b/visad/ErrorEstimate.java
@@ -0,0 +1,679 @@
+//
+// ErrorEstimate.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   ErrorEstimate is the immutable VisAD class for statistics about a value
+   or array of values.<P>
+*/
+public class ErrorEstimate extends Object implements java.io.Serializable, Comparable {
+
+  final double Error;
+  final double Mean;
+  final long NumberNotMissing;
+  final Unit unit;
+
+  /** these are bounds used in estimates of derivatives;
+      they should be reciprocals;
+      they are not applied in all cases, or uniformly */
+  private static final double DERIVATIVE_LOW_LIMIT = 0.01;
+  private static final double DERIVATIVE_HI_LIMIT = 1.0 / DERIVATIVE_LOW_LIMIT;
+
+  /** construct an error distribution of number values with
+      given mean and error (variance), in Unit unit
+      <br><br><em>Note that the <code>mean</code> and <code>error</code>
+      parameters are reversed in this method</em>
+  */
+  public ErrorEstimate(double error, double mean, long number, Unit u) {
+    unit = u;
+    if (Double.isNaN(error) || Double.isNaN(mean) || number <= 0) {
+      Error = Double.NaN;
+      Mean = Double.NaN;
+      NumberNotMissing = 0;
+    }
+    else {
+      Error = error;
+      Mean = mean;
+      NumberNotMissing = number;
+    }
+  }
+
+  /** construct an error distribution of 1 value with
+      given mean and error (variance), in Unit unit */
+  public ErrorEstimate(double mean, double error, Unit u) {
+    unit = u;
+    if (Double.isNaN(mean)) {
+      NumberNotMissing = 0;
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+    else {
+      NumberNotMissing = 1;
+      Mean = mean;
+      Error = error;
+    }
+  }
+
+  /** construct an ErrorEstimate from a Field ErrorEstimate, a sample
+      ErrorEstimate, the sample value, and an increment for NumberNotMissing;
+      used by FlatField.setSample */
+  public ErrorEstimate(ErrorEstimate field_error, ErrorEstimate sample_error,
+                       double val, int inc) throws VisADException {
+    double ef;
+    double mf;
+    long nf;
+    Unit uf;
+    if (field_error == null) {
+      ef = Double.NaN;
+      mf = Double.NaN;
+      nf = 0;
+      uf = null;
+    }
+    else {
+      ef = field_error.Error;
+      mf = field_error.Mean;
+      nf = field_error.NumberNotMissing;
+      uf = field_error.unit;
+    }
+
+    double es;
+    double ms;
+    long ns = Double.isNaN(val) ? 0 : 1;
+    Unit us;
+    if (sample_error == null) {
+      us = null;
+      es = Double.NaN;
+      ms = Double.NaN;
+    }
+    else {
+      us = sample_error.unit;
+      if (uf == null || uf == us) {
+        es = sample_error.Error;
+        ms = val;
+      }
+      else {
+        es = uf.toThis(sample_error.Error, us);
+        ms = uf.toThis(val, us);
+      }
+    }
+
+    unit = (field_error != null) ? uf : us;
+    long number = nf + inc;
+    if (number > 0) {
+      double mean = 0.0;
+      double error = 0.0;
+      if (!Double.isNaN(ef)) error += (number - ns) * ef;
+      if (!Double.isNaN(mf)) mean += (number - ns) * mf;
+
+      if (!Double.isNaN(es)) error += ns * es;
+      if (!Double.isNaN(ms)) mean += ns * ms;
+      NumberNotMissing = number;
+//if(NumberNotMissing==1800)Thread.dumpStack();
+      Error = error / NumberNotMissing;
+      Mean = mean / NumberNotMissing;
+    }
+    else {
+      NumberNotMissing = 0;
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+  }
+
+  /** construct an ErrorEstimate for a value that is the result of a
+      binary operator; a and b are the ErrorEstimate-s for the operands */
+  public ErrorEstimate(double value, Unit u, int op, ErrorEstimate a,
+                       ErrorEstimate b, int error_mode)
+         throws VisADException {
+    unit = u;
+    if (Double.isNaN(value)) {
+      NumberNotMissing = 0;
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+    else {
+      NumberNotMissing = 1;
+      Mean = value;
+      Error = binary(op, a, b, error_mode);
+    }
+  }
+
+  /** construct an ErrorEstimate for a value that is the result of a
+      unary operator; a is the ErrorEstimate for the operand */
+  public ErrorEstimate(double value, Unit u, int op, ErrorEstimate a,
+                       int error_mode) throws VisADException {
+    unit = u;
+    if (Double.isNaN(value)) {
+      NumberNotMissing = 0;
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+    else {
+      NumberNotMissing = 1;
+      Mean = value;
+      Error = unary(op, a, error_mode);
+    }
+  }
+
+  /** construct an ErrorEstimate for an array of values with an error */
+  public ErrorEstimate(double[] value, double error, Unit u) {
+    unit = u;
+    int number = 0;
+    double sum = 0.0;
+    for (int i=0; i<value.length; i++) {
+      if (!Double.isNaN(value[i])) {
+        number++;
+        sum += value[i];
+      }
+    }
+    NumberNotMissing = number;
+    if (NumberNotMissing > 0) {
+      Mean = sum / NumberNotMissing;
+      Error = error;
+    }
+    else {
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+  }
+
+  /** construct an ErrorEstimate for an array of values with an error */
+  public ErrorEstimate(float[] value, double error, Unit u) {
+    unit = u;
+    int number = 0;
+    double sum = 0.0;
+    for (int i=0; i<value.length; i++) {
+      if (!Float.isNaN(value[i])) {
+        number++;
+        sum += value[i];
+      }
+    }
+    NumberNotMissing = number;
+    if (NumberNotMissing > 0) {
+      Mean = sum / NumberNotMissing;
+      Error = error;
+    }
+    else {
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+  }
+
+  /** construct Error for an array of values that is the result of a binary
+      operator; a and b are the ErrorEstimate-s for the operands */
+  public ErrorEstimate(double[] value, Unit u, int op, ErrorEstimate a,
+                       ErrorEstimate b, int error_mode)
+         throws VisADException {
+    unit = u;
+    int number = 0;
+    double sum = 0.0;
+    for (int i=0; i<value.length; i++) {
+      if (!Double.isNaN(value[i])) {
+        number++;
+        sum += value[i];
+      }
+    }
+    NumberNotMissing = number;
+    if (NumberNotMissing > 0) {
+      Mean = sum / NumberNotMissing;
+      Error = binary(op, a, b, error_mode);
+    }
+    else {
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+  }
+
+  /** construct Error for an array of values that is the result of a binary
+      operator; a and b are the ErrorEstimate-s for the operands */
+  public ErrorEstimate(float[] value, Unit u, int op, ErrorEstimate a,
+                       ErrorEstimate b, int error_mode)
+         throws VisADException {
+    unit = u;
+    int number = 0;
+    double sum = 0.0;
+    for (int i=0; i<value.length; i++) {
+      if (!Float.isNaN(value[i])) {
+        number++;
+        sum += value[i];
+      }
+    }
+    NumberNotMissing = number;
+    if (NumberNotMissing > 0) {
+      Mean = sum / NumberNotMissing;
+      Error = binary(op, a, b, error_mode);
+    }
+    else {
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+  }
+
+  /** construct Error for an array of values that is the result of a unary
+      operator; a is the ErrorEstimate for the operand */
+  public ErrorEstimate(double[] value, Unit u, int op, ErrorEstimate a,
+                       int error_mode) throws VisADException {
+    unit = u;
+    int number = 0;
+    double sum = 0.0;
+    for (int i=0; i<value.length; i++) {
+      if (!Double.isNaN(value[i])) {
+        number++;
+        sum += value[i];
+      }
+    }
+    NumberNotMissing = number;
+    if (NumberNotMissing > 0) {
+      Mean = sum / NumberNotMissing;
+      Error = unary(op, a, error_mode);
+    }
+    else {
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+  }
+
+  /** construct Error for an array of values that is the result of a unary
+      operator; a is the ErrorEstimate for the operand */
+  public ErrorEstimate(float[] value, Unit u, int op, ErrorEstimate a,
+                       int error_mode) throws VisADException {
+    unit = u;
+    int number = 0;
+    double sum = 0.0;
+    for (int i=0; i<value.length; i++) {
+      if (!Float.isNaN(value[i])) {
+        number++;
+        sum += value[i];
+      }
+    }
+    NumberNotMissing = number;
+    if (NumberNotMissing > 0) {
+      Mean = sum / NumberNotMissing;
+      Error = unary(op, a, error_mode);
+    }
+    else {
+      Mean = Double.NaN;
+      Error = Double.NaN;
+    }
+  }
+
+  /** copy a ErrorEstimate[] array; this is a helper for Set,
+      FlatField, etc */
+  public static ErrorEstimate[] copyErrorsArray(ErrorEstimate[] errors) {
+    if (errors == null) return null;
+    int n = errors.length;
+    ErrorEstimate[] ret_errors = new ErrorEstimate[n];
+    for (int i=0; i<n; i++) ret_errors[i] = errors[i];
+    return ret_errors;
+  }
+
+  /** estimate Error for a binary operator with operands a & b;
+      actually, more of a WAG than an estimate;
+      these formulas are a bit of a hack and
+      suggestions for improvements are welcome */
+  private double binary(int op, ErrorEstimate a, ErrorEstimate b,
+                        int error_mode) throws VisADException {
+    double am, bm, factor;
+    double error = Double.NaN;
+    if (a.isMissing() || b.isMissing() ||
+        error_mode == Data.NO_ERRORS) return error;
+    Unit u = null;
+    switch (op) {
+      case Data.ADD:
+      case Data.SUBTRACT:
+      case Data.INV_SUBTRACT:
+      case Data.MAX:
+      case Data.MIN:
+        if (unit != null && a.unit != null && !unit.equals(a.unit)) {
+          // apply Unit conversion to a Error and Mean
+          am = Math.abs(unit.toThis(a.Mean + 0.5 * a.Error, a.unit) -
+                            unit.toThis(a.Mean - 0.5 * a.Error, a.unit));
+        }
+        else {
+          am = a.Error;
+        }
+        if (unit != null && b.unit != null && !unit.equals(b.unit)) {
+          // apply Unit conversion to a Error and Mean
+          bm = Math.abs(unit.toThis(b.Mean + 0.5 * b.Error, b.unit) -
+                            unit.toThis(b.Mean - 0.5 * b.Error, b.unit));
+        }
+        else {
+          bm = b.Error;
+        }
+
+        am = a.Error;
+        bm = b.Error;
+        break;
+      case Data.MULTIPLY:
+        if (a.unit != null && b.unit != null) {
+          u = a.unit.multiply(b.unit);
+        }
+        am = a.Error * b.Mean;
+        bm = b.Error * a.Mean;
+        break;
+      case Data.DIVIDE:
+        if (a.unit != null && b.unit != null) {
+          u = a.unit.divide(b.unit);
+        }
+        factor = Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(b.Mean));
+        am = a.Error / factor;
+        bm = b.Error * Mean / factor;
+        break;
+      case Data.INV_DIVIDE:
+        if (a.unit != null && b.unit != null) {
+          u = b.unit.divide(a.unit);
+        }
+        factor = Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(a.Mean));
+        bm = a.Error * Mean / factor;
+        am = b.Error / factor;
+        break;
+      case Data.POW:
+        am = a.Error * Math.abs(Mean) * (b.Mean /
+                        Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(a.Mean)));
+        factor = Math.log(Math.abs(a.Mean));
+        if (Double.isNaN(factor)) factor = 1.0;
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, factor));
+        bm = b.Error * Math.abs(Mean) * factor;
+        break;
+      case Data.INV_POW:
+        factor = Math.log(Math.abs(b.Mean));
+        if (Double.isNaN(factor)) factor = 1.0;
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, factor));
+        am = a.Error * Math.abs(Mean) * factor;
+        bm = b.Error * Math.abs(Mean) * (a.Mean /
+             Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(b.Mean)));
+        break;
+      case Data.ATAN2:
+        factor = Math.min(DERIVATIVE_HI_LIMIT, 1.0 + Mean * Mean) /
+                 Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(b.Mean));
+        am = a.Error * factor;
+        bm = b.Error * Mean * factor;
+        break;
+      case Data.ATAN2_DEGREES:
+        factor = Math.min(DERIVATIVE_HI_LIMIT, 1.0 + Mean * Mean) /
+                 Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(b.Mean));
+        am = a.Error * factor;
+        bm = b.Error * Mean * factor;
+        break;
+      case Data.INV_ATAN2:
+        factor = Math.min(DERIVATIVE_HI_LIMIT, 1.0 + Mean * Mean) /
+                 Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(a.Mean));
+        am = a.Error * Mean * factor;
+        bm = b.Error * factor;
+        break;
+      case Data.INV_ATAN2_DEGREES:
+        factor = Math.min(DERIVATIVE_HI_LIMIT, 1.0 + Mean * Mean) /
+                 Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(a.Mean));
+        am = a.Error * Mean * factor;
+        bm = b.Error * factor;
+        break;
+      case Data.REMAINDER:
+        am = a.Error;
+        bm = b.Error * a.Mean /
+             Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(b.Mean));
+        break;
+      case Data.INV_REMAINDER:
+        am = a.Error * b.Mean /
+             Math.max(DERIVATIVE_LOW_LIMIT, Math.abs(a.Mean));
+        bm = b.Error;
+        break;
+      default:
+        throw new ArithmeticException("ErrorEstimate.binary: illegal " +
+                                      "operation");
+    }
+    if (error_mode == Data.INDEPENDENT) {
+      error = Math.sqrt(am * am + bm * bm);
+    }
+    else {
+      error = Math.abs(am) + Math.abs(bm);
+    }
+    if (unit != null && u != null && !unit.equals(u)) {
+      // apply Unit conversion to a Error and Mean
+      error = Math.abs(unit.toThis(Mean + 0.5 * error, u) -
+                       unit.toThis(Mean - 0.5 * error, u));
+    }
+    return error;
+  }
+
+  /** estimate Error for a unary operator with operand a;
+      actually, more of a WAG than an estimate;
+      these formulas are a bit of a hack and
+      suggestions for improvements are welcome */
+  private double unary(int op, ErrorEstimate a, int error_mode)
+          throws UnitException {
+    double factor;
+    double error = Double.NaN;
+    if (a.isMissing() || error_mode == Data.NO_ERRORS) return error;
+    // no difference between Data.INDEPENDENT and Data.DEPENDENT for unary
+
+    switch (op) {
+      case Data.ABS:
+      case Data.CEIL: // least int greater, represented as a double
+      case Data.FLOOR: // greatest int less, represented as a double
+      case Data.RINT: // nearest int, represented as a double
+      case Data.ROUND: // round double to long
+      case Data.NEGATE:
+      case Data.NOP:
+        if (unit != null && a.unit != null && !unit.equals(a.unit)) {
+          // apply Unit conversion to a Error and Mean
+          error = Math.abs(unit.toThis(a.Mean + 0.5 * a.Error, a.unit) -
+                            unit.toThis(a.Mean - 0.5 * a.Error, a.unit));
+        }
+        else {
+          error = a.Error;
+        }
+        break;
+      case Data.ACOS:
+      case Data.ASIN:
+        factor = Math.sqrt(1.0 - a.Mean * a.Mean);
+        if (Double.isNaN(factor)) factor = 1.0;
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, factor));
+        error = a.Error / factor;
+        break;
+      case Data.ACOS_DEGREES:
+      case Data.ASIN_DEGREES:
+        factor = Math.sqrt(1.0 - a.Mean * a.Mean);
+        if (Double.isNaN(factor)) factor = 1.0;
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, factor));
+        error = a.Error * Data.RADIANS_TO_DEGREES / factor;
+        break;
+      case Data.ATAN:
+        error = a.Error / Math.min(DERIVATIVE_HI_LIMIT, 1.0 + a.Mean * a.Mean);
+        break;
+      case Data.ATAN_DEGREES:
+        error = a.Error * Data.RADIANS_TO_DEGREES /
+                Math.min(DERIVATIVE_HI_LIMIT, 1.0 + a.Mean * a.Mean);
+        break;
+      case Data.COS:
+      case Data.SIN:
+        factor = Math.sqrt(1.0 - Mean * Mean);
+        if (Double.isNaN(factor)) factor = 1.0;
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, factor));
+        error = a.Error * factor;
+        break;
+      case Data.COS_DEGREES:
+      case Data.SIN_DEGREES:
+        factor = Math.sqrt(1.0 - Mean * Mean);
+        if (Double.isNaN(factor)) factor = 1.0;
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, factor));
+        error = a.Error * Data.DEGREES_TO_RADIANS * factor;
+        break;
+      case Data.EXP:
+        error = a.Error * Math.abs(Mean);
+        break;
+      case Data.LOG:
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, Math.abs(a.Mean)));
+        error = a.Error / factor;
+        break;
+      case Data.SQRT:
+        factor = Math.max(DERIVATIVE_LOW_LIMIT,
+                          Math.min(DERIVATIVE_HI_LIMIT, 2.0 * Math.abs(Mean)));
+        error = a.Error / factor;
+        break;
+      case Data.TAN:
+        error = a.Error * Math.min(DERIVATIVE_HI_LIMIT, 1.0 + Mean * Mean);
+        break;
+      case Data.TAN_DEGREES:
+        error = a.Error * Data.DEGREES_TO_RADIANS *
+                Math.min(DERIVATIVE_HI_LIMIT, 1.0 + Mean * Mean);
+        break;
+      default:
+        throw new ArithmeticException("ErrorEstimate.unary: illegal " +
+                                      "operation");
+    }
+    return error;
+  }
+
+  public boolean isMissing() {
+    return Double.isNaN(Error);
+  }
+
+  /**
+   * Get the mean value for this error distribution
+   */
+  public double getMean() {
+    return Mean;
+  }
+
+  /**
+   * Get the variance of this error distribution
+   */
+  public double getErrorValue() {
+    return Error;
+  }
+
+  /**
+   * Get the number of values in this error distribution
+   */
+  public long getNumberNotMissing() {
+    return NumberNotMissing;
+  }
+
+  /**
+   * Get the Unit for this error distribution.
+   */
+  public Unit getUnit() {
+    return unit;
+  }
+
+  /** initialize matrix for applying (approximate) Jacobean to errors_in */
+  static double[][] init_error_values(ErrorEstimate[] errors_in) {
+    int n = errors_in.length;
+    double[] means = new double[n];
+    for (int j=0; j<n; j++) {
+      means[j] = errors_in[j].getMean();
+    }
+    return init_error_values(errors_in, means);
+  }
+
+  /** initialize matrix for applying (approximate) Jacobean to errors_in */
+  static double[][] init_error_values(ErrorEstimate[] errors_in,
+                                      double[] means) {
+    int n = errors_in.length;
+    double[][] error_values = new double[n][2 * n];
+    for (int j=0; j<n; j++) {
+      double mean = means[j];
+      double error = 0.5 * errors_in[j].getErrorValue();
+      for (int i=0; i<n; i++) {
+        if (i == j) {
+          error_values[j][2 * i] = mean - error;
+          error_values[j][2 * i + 1] = mean + error;
+        }
+        else {
+          error_values[j][2 * i] = mean;
+          error_values[j][2 * i + 1] = mean;
+        }
+      }
+    }
+    return error_values;
+  }
+
+  public String toString() {
+    return
+      "NumberNotMissing = " + NumberNotMissing + "  Error = " +
+      (Double.isNaN(Error) ? "missing" : Double.toString(Error)) +
+      "  Mean = " +
+      (Double.isNaN(Mean) ? "missing" : Double.toString(Mean));
+  }
+
+  /**
+   * Compares this error estimate to another.
+   * @param obj			The other error estimate.  May be <code>null
+   *				</code>.
+   * @return                    A negative integer, zero, or a positive integer
+   *                            depending on whether this ErrorEstimate is
+   *                            considered less than, equal to, or greater than
+   *                            the other ErrorEstimate, respectively.  If
+   *                            <code>obj == null</code>, then a positive value
+   *                            is returned.  An ErrorEstimate with no unit is
+   *                            considered less than an ErrorEstimate with a
+   *                            unit.
+   */
+  public int compareTo(Object obj) {
+    int		comp;
+    if (obj == null) {
+      comp = 1;		// null ErrorEstimate considered smallest
+    }
+    else {
+      ErrorEstimate	that = (ErrorEstimate)obj;
+      if (unit == null)
+      {
+	if (that.unit == null) {
+	  comp = new Double(Error).compareTo(new Double(that.Error));
+	}
+	else {
+	  comp = -1;	// no-unit ErrorEstimate considered smaller than
+			// one with unit
+	}
+      }
+      else {
+	if (that.unit == null) {
+	  comp = 1;	// no-unit ErrorEstimate considered smaller than
+			// one with unit
+	}
+	else {
+	  try {
+	    comp = new Double(Error).compareTo(
+	      new Double(unit.toThis(that.Error, that.unit)));
+	  }
+	  catch (UnitException e) {
+	    comp = +1;	// put problem ErrorEstimate-s at the end
+	  }
+	}
+      }
+    }
+    return comp;
+  }
+
+}
+
diff --git a/visad/Factor.java b/visad/Factor.java
new file mode 100644
index 0000000..22c2946
--- /dev/null
+++ b/visad/Factor.java
@@ -0,0 +1,115 @@
+//
+// Factor.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.io.Serializable;
+
+/**
+ * A class that represents a factor in the dimensionality of a unit.
+ * A factor is a base unit and a power.
+ * @author Steven R. Emmerson
+ *
+ * This is part of Steve Emerson's Unit package that has been
+ * incorporated into VisAD.
+ */
+final class Factor
+    implements Serializable
+{
+    /**
+     * The power of the base unit.
+     */
+    final int           power;
+
+    /**
+     * The base unit.
+     */
+    final BaseUnit      baseUnit;
+
+    private transient int hashCode = 0;
+
+    /**
+     * Construct a factor from a base unit and a power.
+     *
+     * @param baseUnit  The base unit.
+     * @param power     The power to raise the base unit by.
+     */
+    Factor(BaseUnit baseUnit, int power)
+    {
+        this.power = power;
+        this.baseUnit = baseUnit;
+    }
+
+    /**
+     * Return a string representation of this factor.
+     *
+     * @return  A string representation of this factor (e.g. "m-2").
+     */
+    public String toString()
+    {
+        return power == 1
+                ? baseUnit.toString()
+                : baseUnit.toString() + power;
+    }
+
+  public boolean equals(Factor factor) {
+    return baseUnit.equals(factor.baseUnit) &&
+           (power == factor.power);
+  }
+
+  /**
+   * Indicates whether or not this instance is equal to an object.
+   *
+   * @param that               The object in question.
+   * @return                  <code>true</code> if and only if this instance
+   *                          equals the unit.
+   */
+  public boolean equals (Object that) {
+    if (this == that)
+        return true;
+    if (!(that instanceof Factor)) {
+      return false;
+    }
+    return equals ((Factor)that);
+  }
+
+  /**
+   * Returns the hash code of this instance. {@link Object#hashCode()} should be
+   * overridden whenever {@link Object#equals(Object)} is.
+   * @return                    The hash code of this instance (includes the
+   *                            values).
+   */
+  public int hashCode()
+  {
+    if (hashCode == 0)
+    {
+      hashCode = baseUnit.hashCode() ^ Integer.valueOf(power).hashCode();
+    }
+    return hashCode;
+  }
+
+}
+
diff --git a/visad/Field.java b/visad/Field.java
new file mode 100644
index 0000000..23b7330
--- /dev/null
+++ b/visad/Field.java
@@ -0,0 +1,185 @@
+//
+// Field.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   Field is the VisAD interface for finite samplings of functions
+   from R^n to a range type, where  n>0.<P>
+
+   A Field domain type may be either a RealType (for a function with
+   domain = R) or a RealTupleType (for a function with domain = R^n
+   for n > 0).<P>
+*/
+public interface Field extends Function {
+
+  /** set the range samples of the function; the order of range samples
+      must be the same as the order of domain indices in the DomainSet;
+      copy range objects if copy is true;
+      should use same MathType object in each Data object in range array */
+  void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException;
+
+  /** set range array as range values of this Field;
+      this must have a Flat range; the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet */
+  void setSamples(double[][] range)
+         throws VisADException, RemoteException;
+
+  /** set range array as range values of this Field;
+      this must have a Flat range; the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet */
+  void setSamples(float[][] range)
+         throws VisADException, RemoteException;
+
+  /** get the domain Set */
+  Set getDomainSet() throws VisADException, RemoteException;
+
+  /** get number of samples */
+  int getLength() throws VisADException, RemoteException;
+
+  /** get the range value at the index-th sample */
+  Data getSample(int index)
+         throws VisADException, RemoteException;
+
+  /** set the range value at the sample nearest to domain */
+  void setSample(RealTuple domain, Data range)
+         throws VisADException, RemoteException;
+
+  /** set the range value at the sample nearest to domain */
+  void setSample(RealTuple domain, Data range, boolean copy)
+         throws VisADException, RemoteException;
+
+  /** set the range value at the index-th sample */
+  void setSample(int index, Data range)
+         throws VisADException, RemoteException;
+
+  /** set the range value at the index-th sample */
+  void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException;
+
+  /** assumes the range type of this is a Tuple and returns
+      a Field with the same domain as this, but whose range
+      samples consist of the specified Tuple component of the
+      range samples of this; in shorthand, this[].component */
+  Field extract(int component)
+         throws VisADException, RemoteException;
+
+  /** combine domains of two outermost nested Fields into a single
+      domain and Field */
+  Field domainMultiply()
+         throws VisADException, RemoteException;
+
+  /** combine domains to depth, if possible */
+  Field domainMultiply(int depth)
+         throws VisADException, RemoteException;
+
+  /** factor Field domain into domains of two nested Fields */
+  Field domainFactor( RealType factor )
+         throws VisADException, RemoteException;
+
+  /** invokes getValues(true) */
+  double[][] getValues()
+         throws VisADException, RemoteException;
+
+  /** get the 'Flat' components of this Field's range values
+      in their default range Units (as defined by the range of
+      the Field's FunctionType); if the range type is a RealType
+      it is a 'Flat' component, if the range type is a TupleType
+      its RealType components and RealType components of its
+      RealTupleType components are all 'Flat' components; the
+      return array is dimensioned:
+      double[number_of_flat_components][number_of_range_samples];
+      return a copy if copy == true */
+  double[][] getValues(boolean copy)
+         throws VisADException, RemoteException;
+
+  /** invokes getFloats(true) */
+  float[][] getFloats()
+         throws VisADException, RemoteException;
+
+  /** get the 'Flat' components of this Field's range values
+      in their default range Units (as defined by the range of
+      the Field's FunctionType); if the range type is a RealType
+      it is a 'Flat' component, if the range type is a TupleType
+      its RealType components and RealType components of its
+      RealTupleType components are all 'Flat' components; the
+      return array is dimensioned:
+      float[number_of_flat_components][number_of_range_samples];
+      return a copy if copy == true */
+  float[][] getFloats(boolean copy)
+         throws VisADException, RemoteException;
+
+  /** get String values for Text components */
+  String[][] getStringValues()
+         throws VisADException, RemoteException;
+
+  /** get default range Unit-s for 'Flat' components */
+  Unit[] getDefaultRangeUnits()
+         throws VisADException, RemoteException;
+
+  /** get range Unit-s for 'Flat' components;
+      second index may enumerate samples, if they differ */
+  Unit[][] getRangeUnits()
+         throws VisADException, RemoteException;
+
+  /** get range CoordinateSystem for 'RealTuple' range;
+      index may enumerate samples, if they differ */
+  CoordinateSystem[] getRangeCoordinateSystem()
+         throws VisADException, RemoteException;
+
+  /** get range CoordinateSystem for 'RealTuple' components;
+      index may enumerate samples, if they differ */
+  CoordinateSystem[] getRangeCoordinateSystem(int component)
+         throws VisADException, RemoteException;
+
+  /** return true if this a FlatField or a RemoteField adapting a FlatField */
+  boolean isFlatField()
+         throws VisADException, RemoteException;
+
+
+/**
+<PRE>
+   Here's how to use this:
+
+   for (Enumeration e = field.domainEnumeration() ; e.hasMoreElements(); ) {
+     RealTuple domain_sample = (RealTuple) e.nextElement();
+     Data range = field.evaluate(domain_sample);
+   }
+</PRE>
+*/
+  Enumeration domainEnumeration()
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/FieldException.java b/visad/FieldException.java
new file mode 100644
index 0000000..c93dd27
--- /dev/null
+++ b/visad/FieldException.java
@@ -0,0 +1,38 @@
+//
+// FieldException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   FieldException is an exception for an error with a VisAD field.<P>
+*/
+public class FieldException extends VisADException {
+
+  public FieldException() { super(); }
+  public FieldException(String s) { super(s); }
+
+}
+
diff --git a/visad/FieldImpl.java b/visad/FieldImpl.java
new file mode 100644
index 0000000..f558b58
--- /dev/null
+++ b/visad/FieldImpl.java
@@ -0,0 +1,3853 @@
+//
+// FieldImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+import java.util.Vector;
+
+/**
+   FieldImpl is the VisAD class for finite samplings of functions
+   from R^n to a range type, where  n>0.  The DomainSet, DomainUnits
+   and DomainCoordinateSystem variables of FieldImpl are immutable.<P>
+
+   A FieldImpl domain type may be either a RealType (for a function with
+   domain = R) or a RealTupleType (for a function with domain = R^n
+   for n > 0).<P>
+*/
+public class FieldImpl extends FunctionImpl implements Field {
+
+  private static final long serialVersionUID = 1L;
+
+  /** the sampling of the function domain R^n */
+  Set DomainSet;
+
+  /** this is DomainSet.DomainCoordinateSystem */
+  CoordinateSystem DomainCoordinateSystem;
+
+  /** this is DomainSet.SetUnits */
+  Unit[] DomainUnits;
+
+  /** the number of samples */
+  int Length;
+
+  /** the array of function values */
+    //jeffm: Change the name of Range to MyRange 
+    //so methods that use Range can simply call:
+    //Data[]Range = getRange ();
+  private Data[] MyRange;  // won't be null though elements might be
+
+
+  // This is used to synchronize access to the Range (which might be null)
+  // use VisADRay since it is Serializable and small
+  private VisADRay RangeLock = new VisADRay();
+
+  private boolean MissingFlag;
+
+  /** construct a FieldImpl from type;
+      use default Set of FunctionType domain;
+      initial values are missing */
+  public FieldImpl(FunctionType type) throws VisADException {
+    this(type, null);
+  }
+
+
+  /**
+   * Constructs from the type of function and a set of domain points.
+   *
+   * @param type                The type of function.
+   * @param set                 The set of domain points.  Defines the units
+   *                            and any coordinate system transformation
+   *                            for the domain of the funciton.  May be
+   *                            <code>null</code>, in which case the default
+   *                            domain set ({@link FunctionType#getDomain()}) is
+   *                            used.  May <em>not</em> be {@link FloatSet} or
+   *                            {@link DoubleSet}.  If non-<code>null</code>, 
+   *                            then must be compatible with the domain of the
+   *                            FunctionType.
+   * @throws CoordinateSystemException
+   *                            if the {@link CoordinateSystem} of the
+   *                            domain set is <code>null</code> but the
+   *                            {@link CoordinateSystem} of the domain of
+   *                            the FunctionType} is not; or if both {@link
+   *                            CoordinateSystem}s are non-<code>null</code>
+   *                            and do not have the same reference {@link
+   *                            RealTupleType}.
+   * @throws VisADException     if a VisAD failure occurs.
+   */
+  public FieldImpl(FunctionType type, Set set) throws VisADException {
+      //Turn around and call the other ctor - the true says to go ahead 
+      //and create the  Range array
+      this (type, set, true);
+  }
+
+  /**
+   * Trusted constructor for subclasses that don't need to have the
+   * Range array instantiated (i.e., FlatField).
+   *
+   * @param type                The type of function.
+   * @param set                 The set of domain points.  Defines the units
+   *                            and any coordinate system transformation
+   *                            for the domain of the funciton.  May be
+   *                            <code>null</code>, in which case the default
+   *                            domain set ({@link FunctionType#getDomain()}) is
+   *                            used.  May <em>not</em> be {@link FloatSet} or
+   *                            {@link DoubleSet}.  If non-<code>null</code>, 
+   *                            then must be compatible with the domain of the
+   *                            FunctionType.
+   * @param createRangeArray    If true then the Range array is allocated.
+   * @throws CoordinateSystemException
+   *                            if the {@link CoordinateSystem} of the
+   *                            domain set is <code>null</code> but the
+   *                            {@link CoordinateSystem} of the domain of
+   *                            the FunctionType} is not; or if both {@link
+   *                            CoordinateSystem}s are non-<code>null</code>
+   *                            and do not have the same reference {@link
+   *                            RealTupleType}.
+   * @throws VisADException     if a VisAD failure occurs.
+   */
+  protected FieldImpl(FunctionType type, Set set, boolean createRangeArray) 
+    throws VisADException {
+
+    super(type);
+
+    RealTupleType DomainType = type.getDomain();
+
+    if (set == null) set = DomainType.getDefaultSet();
+    if (set == null) {
+      throw new SetException("FieldImpl: set cannot be null");
+    }
+
+    if (set instanceof DoubleSet || set instanceof FloatSet) {
+      throw new SetException("FieldImpl: set may not be DoubleSet " +
+                             "or FloatSet");
+    }
+    if (DomainType.getDimension() != set.getDimension()) {
+      throw new SetException("FieldImpl: set dimension " + set.getDimension() +
+                             " and type dimension " +
+                             DomainType.getDimension() + " don't match");
+    }
+    // force DomainSet Type to match DomainType
+    if (DomainType.equals(((SetType) set.getType()).getDomain())) {
+      DomainSet = set;
+    }
+    else {
+      DomainSet = (Set) set.cloneButType(new SetType(DomainType));
+    }
+    DomainCoordinateSystem = DomainSet.getCoordinateSystem();
+    CoordinateSystem domTypeCs = DomainType.getCoordinateSystem();
+    if (domTypeCs == null 
+          ? (DomainCoordinateSystem != null)
+          : (DomainCoordinateSystem != null && !domTypeCs.getReference().equals(
+              DomainCoordinateSystem.getReference()))) {
+      throw new CoordinateSystemException(domTypeCs, DomainCoordinateSystem);
+    }
+    DomainUnits = DomainSet.getSetUnits();
+    Length = DomainSet.getLength();
+    if (createRangeArray) {
+        MyRange = new Data[Length];
+    }
+    MissingFlag = true;
+  }
+
+    /**
+       This  creates (if it has not been created already)  and returns the MyRange  data member
+     **/
+    private Data[] getRange () {
+        synchronized (RangeLock) {
+            if (MyRange == null) {
+                MyRange = new Data[getLength()];
+            }
+        }
+        return  MyRange;
+    }
+
+  /** 
+   * Set the range samples of the function; the order of range samples
+   * must be the same as the order of domain indices in the DomainSet;
+   * copy range objects if copy is true;
+   * @param range The range values 
+   * @param copy should the range values be copied
+   */
+  public void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException {
+      setSamples(range, copy, true);
+  }
+
+  /** 
+   * Set the range samples of the function; the order of range samples
+   * must be the same as the order of domain indices in the DomainSet;
+   * copy range objects if copy is true;
+   * should use same MathType object in each Data object in range array 
+   * @param range The range values 
+   * @param copy should the range values be copied
+   * @param checkAllRangeTypes  If true then ensure that the MathType of 
+   *                            each element in the range matches the type 
+   *                            of this field. If false then only check the 
+   *                            first range element.  This is the "trust 
+   *                            the calling method" flag. If you pass in false 
+   *                            and there are elements in the range whose type 
+   *                            does not match the type of this field then 
+   *                            this may result in hard-to-track-down bugs.
+   */
+   public void setSamples(Data[] range, boolean copy, boolean checkAllRangeTypes)
+         throws VisADException, RemoteException {
+
+    if (range == null) {
+        MyRange = null;
+        MissingFlag = true;
+        return;
+    }
+
+    if (range.length != getLength()) {
+      throw new FieldException("FieldImpl.setSamples: bad array length");
+    }
+
+    Data[]Range = getRange ();
+
+
+    synchronized (RangeLock) {
+      MissingFlag = false;
+      MathType t = ((FunctionType) Type).getRange();
+      for (int i=0; i<getLength(); i++) {
+        if (range[i] != null) {
+          if(i==0 || checkAllRangeTypes) {
+              if(!t.equals(range[i].getType())) {
+                throw new TypeException("FieldImpl.setSamples: sample#" + i +
+                                        " type " + range[i].getType() +
+                                       " doesn't match field type " + t);
+              }
+          }
+          if (copy) Range[i] = (Data) range[i].dataClone();
+          else Range[i] = range[i];
+        }
+        else Range[i] = null;
+      }
+
+      for (int i=0; i<getLength(); i++) {
+        if (Range[i] instanceof DataImpl) {
+          ((DataImpl) Range[i]).setParent(this);
+        }
+      }
+    }
+    notifyReferences();
+  }
+
+  /**
+   * <p>Returns the domain-set of this instance.  The actual set is returned:
+   * it is not a copy or a clone.</p>
+   *
+   * <p> Note that it is possible to simultaneously modify the domain-set of
+   * both this instance and of a clone by modifying the values in the array
+   * returned by invoking <code>getSamples(false)</code> on the domain-set of
+   * either this instance or the clone.  Don't do this unless you enjoy 
+   * debugging.</p>
+   *
+   * @return                      The actual domain-set of this instance.
+   */
+  public Set getDomainSet() {
+    return DomainSet;
+  }
+
+  /** get number of samples */
+  public int getLength() {
+    return Length;
+  }
+
+  /**
+   * Returns the units of the values in the domain set.  The units may differ
+   * from the default units of the underlying MathType of the domain, but will
+   * be convertible with them.
+   * @return                    The units of the values in the domain set.
+   */
+  public Unit[] getDomainUnits() {
+    return DomainUnits;
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem() {
+    return DomainCoordinateSystem;
+  }
+
+  /** get range values for Text components; the return array is dimensioned
+      double[number_of_range_components][number_of_range_samples] */
+  public String[][] getStringValues()
+         throws VisADException, RemoteException {
+    TextType[] textComponents = ((FunctionType) Type).getTextComponents();
+    if (textComponents == null) return null;
+    int[] textIndices = ((FunctionType) Type).getTextIndices();
+    int n = textComponents.length;
+    int len = getLength();
+    String[][] values = new String[n][len];
+    if (isMissing()) {
+      for (int k=0; k<n; k++) {
+        for (int i=0; i<len; i++) values[k][i] = "";
+      }
+      return values;
+    }
+
+    MathType RangeType = ((FunctionType) Type).getRange();
+
+    synchronized (RangeLock) {
+      for (int i=0; i<len; i++) {
+        Data range = (MyRange==null?null:MyRange[i]);
+        if (range == null || range.isMissing()) {
+          for (int k=0; k<n; k++) values[k][i] = "";
+        }
+        else {
+          if (RangeType instanceof TextType) {
+            values[0][i] = ((Text) range).getValue();
+          }
+          else if (RangeType instanceof TupleType) {
+            for (int k=0; k<n; k++) {
+              Text t = (Text) ((TupleIface) range).getComponent(textIndices[k]);
+              values[k][i] = t.getValue();
+            }
+          }
+        }
+      } // end for (int i=0; i<len; i++)
+    }
+    return values;
+  }
+
+  public float[][] getFloats()
+         throws VisADException, RemoteException {
+    return getFloats(true);
+  }
+
+  /** get range values for 'Flat' components in their default range
+      Units (as defined by the range of this FieldImpl's
+      FunctionType); the return array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      copy is ignored for FieldImpl */
+  public float[][] getFloats(boolean copy)
+         throws VisADException, RemoteException {
+    return Set.doubleToFloat(getValues(copy));
+  }
+
+  public double[][] getValues()
+         throws VisADException, RemoteException {
+    return getValues(true);
+  }
+
+  /** get range values for 'Flat' components in their default range
+      Units (as defined by the range of this FieldImpl's
+      FunctionType); the return array is dimensioned
+      double[number_of_range_components][number_of_range_samples];
+      copy is ignored for FieldImpl */
+  public double[][] getValues(boolean copy)
+         throws VisADException, RemoteException {
+    RealType[] realComponents = ((FunctionType) Type).getRealComponents();
+    if (realComponents == null) return null;
+    int n = realComponents.length;
+    Unit[] units = getDefaultRangeUnits();
+    int len = getLength();
+    double[][] values = new double[n][len];
+
+    if (isMissing()) {
+      for (int k=0; k<n; k++) {
+        for (int i=0; i<len; i++) values[k][i] = Double.NaN;
+      }
+      return values;
+    }
+
+    MathType RangeType = ((FunctionType) Type).getRange();
+
+    synchronized (RangeLock) {
+      for (int i=0; i<len; i++) {
+        Data range = (MyRange == null? null: MyRange[i]);
+        if (range == null || range.isMissing()) {
+          for (int k=0; k<n; k++) values[k][i] = Double.NaN;
+        }
+        else {
+          if (RangeType instanceof RealType) {
+            values[0][i] = ((Real) range).getValue(units[0]);
+          }
+          else if (RangeType instanceof TupleType) {
+            int k = 0;
+            for (int j=0; j<((TupleType) RangeType).getDimension(); j++) {
+              MathType component_type = ((TupleType) RangeType).getComponent(j);
+              Data component = ((TupleIface) range).getComponent(j);
+              if (component_type instanceof RealType) {
+                values[k][i] = ((Real) component).getValue(units[k]);
+                k++;
+              }
+              else if (component_type instanceof RealTupleType) {
+                for (int m=0; m<((TupleType) component_type).getDimension(); m++) {
+                  Data comp_comp = ((TupleIface) component).getComponent(m);
+                  values[k][i] = ((Real) comp_comp).getValue(units[k]);
+                  k++;
+                }
+              }
+            }
+          }
+        }
+      } // end for (int i=0; i<len; i++)
+    }
+    return values;
+  }
+
+  /** set range array as range values of this FieldImpl;
+      this must have a Flat range; the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet */
+  public void setSamples(double[][] range)
+         throws VisADException, RemoteException {
+    RealType[] realComponents = ((FunctionType) Type).getRealComponents();
+    if (!((FunctionType) Type).getFlat()) {
+      throw new FieldException("FieldImpl.setSamples: not Flat range");
+    }
+    if (realComponents == null) {
+      throw new FieldException("FieldImpl.setSamples: no Real components");
+    }
+    int n = realComponents.length;
+    int len = getLength();
+    if (range == null || range.length != n) {
+      throw new FieldException("FieldImpl.setSamples: bad tuple length");
+    }
+    if (range[0] == null || range[0].length != len) {
+      throw new FieldException("FieldImpl.setSamples: bad array length");
+    }
+    Unit[] units = getDefaultRangeUnits();
+
+    MathType RangeType = ((FunctionType) Type).getRange();
+
+    synchronized (RangeLock) {
+      MissingFlag = false;
+      Data[]Range = getRange ();
+      if (RangeType instanceof RealType) {
+        for (int i=0; i<len; i++) {
+          Range[i] = new Real((RealType) RangeType, range[0][i], units[0]);
+        }
+      }
+      else if (RangeType instanceof RealTupleType) {
+        int ntup = ((RealTupleType) RangeType).getDimension();
+        Real[] reals = new Real[ntup];
+        for (int i=0; i<len; i++) {
+          for (int j=0; j<ntup; j++) {
+            RealType type = (RealType)
+              ((RealTupleType) RangeType).getComponent(j);
+            reals[j] = new Real(type, range[j][i], units[j]);
+          }
+          Range[i] = new RealTuple(reals);
+        }
+      }
+      else if (RangeType instanceof TupleType) {
+        int ntup = ((TupleType) RangeType).getDimension();
+        Data[] data = new Real[ntup];
+        MathType[] types = new MathType[ntup];
+        for (int j=0; j<ntup; j++) {
+          types[j] = ((TupleType) RangeType).getComponent(j);
+        }
+        for (int i=0; i<len; i++) {
+          int k = 0;
+          for (int j=0; j<ntup; j++) {
+            if (types[j] instanceof RealType) {
+              data[j] = new Real((RealType) types[j], range[k][i], units[k]);
+              k++;
+            }
+            else { // types[j] instanceof RealTupleType
+              int mtup = ((RealTupleType) types[j]).getDimension();
+              Real[] reals = new Real[mtup];
+              for (int m=0; m<mtup; m++) {
+                RealType type = (RealType)
+                  ((RealTupleType) types[j]).getComponent(m);
+                reals[m] = new Real(type, range[k][i], units[k]);
+                k++;
+              }
+              data[j] = new RealTuple(reals);
+            }
+          } // end for (int j=0; j<ntup; j++)
+          Range[i] = new Tuple(data);
+        } // end for (int i=0; i<len; i++)
+      }
+    }
+    return;
+  }
+
+  /** set range array as range values of this FieldImpl;
+      this must have a Flat range; the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet */
+  public void setSamples(float[][] range)
+         throws VisADException, RemoteException {
+    setSamples(Set.floatToDouble(range));
+  }
+
+  /** return array of Units associated with each RealType
+      component of range; these may differ from default
+      Units of range RealTypes, but must be convertable;
+      the second index enumerates samples since Units may
+      differ between samples */
+  public Unit[][] getRangeUnits()
+         throws VisADException, RemoteException {
+    RealType[] realComponents = ((FunctionType) Type).getRealComponents();
+    if (realComponents == null) return null;
+    int n = realComponents.length;
+    Unit[][] units = new Unit[n][getLength()];
+    Unit[] default_units = getDefaultRangeUnits();
+
+    MathType RangeType = ((FunctionType) Type).getRange();
+
+    for (int i=0; i<getLength(); i++) {
+      Data range = (MyRange==null?null:MyRange[i]);
+      if (range == null || range.isMissing()) {
+        for (int k=0; k<n; k++) units[k][i] = default_units[k];
+      }
+      else {
+        if (RangeType instanceof RealType) {
+          units[0][i] = ((Real) range).getUnit();
+        }
+        else if (RangeType instanceof TupleType) {
+          int k = 0;
+          for (int j=0; j<((TupleType) RangeType).getDimension(); j++) {
+            MathType component_type = ((TupleType) RangeType).getComponent(i);
+            Data component = ((TupleIface) range).getComponent(j);
+            if (component_type instanceof RealType) {
+              units[k][i] = ((Real) component).getUnit();
+              k++;
+            }
+            else if (component_type instanceof RealTupleType) {
+              for (int m=0; m<((TupleType) component_type).getDimension(); m++) {
+                Data comp_comp = ((TupleIface) component).getComponent(m);
+                units[k][i] = ((Real) comp_comp).getUnit();
+                k++;
+              }
+            }
+          }
+        }
+      }
+    }
+    return units;
+  }
+
+  /** 
+   * Get range CoordinateSystem for 'RealTuple' range;
+   * second index enumerates samples.
+   * @return range CoordinateSystem assuming range type is
+   * a RealTupleType (throws a TypeException if its not);
+   * this may differ from default CoordinateSystem of
+   * range RealTupleType, but must be convertable;
+   * the index enumerates samples since Units may
+   * differ between samples 
+   */
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws VisADException, RemoteException {
+    MathType RangeType = ((FunctionType) Type).getRange();
+    if (!(RangeType instanceof RealTupleType)) {
+      throw new TypeException("FieldImpl.getRangeCoordinateSystem: " +
+        "Range is not RealTupleType");
+    }
+
+    CoordinateSystem[] cs = new CoordinateSystem[getLength()];
+    CoordinateSystem default_cs =
+      ((RealTupleType) RangeType).getCoordinateSystem();
+
+    for (int i=0; i<getLength(); i++) {
+      Data range = (MyRange==null?null:MyRange[i]);
+      if (range == null || range.isMissing()) {
+        cs[i] = default_cs;
+      }
+      else {
+        cs[i] = ((RealTuple) range).getCoordinateSystem();
+      }
+    }
+    return cs;
+  }
+
+  /** get range CoordinateSystem for 'RealTuple' components;
+      second index enumerates samples */
+  public CoordinateSystem[] getRangeCoordinateSystem(int component)
+         throws VisADException, RemoteException {
+    MathType RangeType = ((FunctionType) Type).getRange();
+    if ( (!(RangeType instanceof TupleType)) ||
+         (RangeType instanceof RealTupleType) ) {
+      throw new TypeException("FieldImpl.getRangeCoordinateSystem: " +
+        "Range must be TupleType but not RealTupleType");
+    }
+
+    MathType component_type =
+      ((TupleType) RangeType).getComponent(component);
+
+    if (!(component_type instanceof RealTupleType)) {
+      throw new TypeException("FieldImpl.getRangeCoordinateSystem: " +
+        "selected Range component must be RealTupleType");
+    }
+
+    CoordinateSystem[] cs = new CoordinateSystem[getLength()];
+
+    CoordinateSystem default_cs =
+      ((RealTupleType) component_type).getCoordinateSystem();
+
+    for (int i=0; i<getLength(); i++) {
+      Data range = (MyRange==null?null:MyRange[i]);
+      if (range == null || range.isMissing()) {
+        cs[i] = default_cs;
+      }
+      else {
+        Data comp = ((TupleIface) range).getComponent(component);
+        if (comp == null || comp.isMissing()) {
+          cs[i] = default_cs;
+        }
+        else {
+          cs[i] = ((RealTuple) comp).getCoordinateSystem();
+        }
+      }
+    }
+    return cs;
+  }
+
+  /** get default range Unit-s for 'Flat' components */
+  public Unit[] getDefaultRangeUnits() {
+    RealType[] realComponents = ((FunctionType) Type).getRealComponents();
+    if (realComponents == null) return null;
+    int n = realComponents.length;
+    Unit[] units = new Unit[n];
+    for (int i=0; i<n; i++) {
+      units[i] = realComponents[i].getDefaultUnit();
+    }
+    return units;
+  }
+
+  /**
+   * <p>Get the range value at the index-th sample.  The actual range value
+   * is returned -- not a copy.</p>
+   *
+   * <p>This implementation uses {@link #getSample(int, boolean)}.</p>
+   *
+   * @param index index of requested range sample
+   * @return range value at sample index
+   */
+  
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    return getSample(index, false);
+  }
+
+  /**
+   * Get the metadata for the range value at the index-th sample.  If the range
+   * value is also requested, then the actual range value is returned -- not a
+   * copy.</p>
+   *
+   * @param index index of requested range sample
+   * @param metadataOnly <tt>true</tt> if only the metadata is needed,
+   *                     <tt>false</ff> if both metadata and data are
+   *                     desired.
+   */
+  public Data getSample(int index, boolean metadataOnly)
+         throws VisADException, RemoteException {
+    synchronized (RangeLock) {
+      if (MyRange == null || isMissing() || index < 0 || index >= getLength() || MyRange[index] == null) {
+        return ((FunctionType) Type).getRange().missingData();
+      }
+      else return MyRange[index];
+    }
+  }
+
+  /** set the range value at the sample nearest to domain */
+  public void setSample(RealTuple domain, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    if (getDomainSet() == null) {
+      throw new FieldException("FieldImpl.setSample: DomainSet undefined");
+    }
+    // WLH 9 Dec 99
+    // if (!((FunctionType) Type).getDomain().equalsExceptName(domain.getType())) {
+    if (!((FunctionType) Type).getDomain().equals(domain.getType())) {
+      throw new TypeException("FieldImpl.setSample: bad domain type");
+    }
+
+    int dimension = getDomainSet().getDimension();
+    double[][] vals = new double[dimension][1];
+    for (int j=0; j<dimension; j++) {
+      vals[j][0] = ((Real) ((RealTuple) domain).getComponent(j)).getValue();
+    }
+    // always use simple resampling for set
+    int[] indices = getDomainSet().doubleToIndex(vals);
+    setSample(indices[0], range, copy);
+  }
+
+  public void setSample(RealTuple domain, Data range)
+         throws VisADException, RemoteException {
+    setSample(domain, range, true);
+  }
+
+  /** 
+   * Set the range value at the index-th sample; makes a local copy
+   * @param  index  index in domain
+   * @param  range  sample at that index
+   */
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    setSample(index, range, true);
+  }
+
+  /** 
+   * Set the range value at the index-th sample 
+   * @param  index  index in domain
+   * @param  range  sample at that index
+   * @param  copy   true to make a copy
+   */
+  public void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    setSample(index, range, copy, true);
+  }
+
+  /** 
+   * Set the range value at the index-th sample 
+   * @param  index  index in domain
+   * @param  range  sample at that index
+   * @param  copy   true to make a copy
+   * @param  checkRangeType  setting to false will not check to make
+   *                         sure that the sample MathType is the same as
+   *                         this FieldImpl's range.  This saves on time
+   *                         at the expense of accuracy.  Use this only
+   *                         if you like shooting yourself in the foot.
+   */
+  public void setSample(int index, Data range, boolean copy, boolean checkRangeType)
+         throws VisADException, RemoteException {
+    if (getDomainSet() == null) {
+      throw new FieldException("FieldImpl.setSample: DomainSet undefined");
+    }
+    if (range != null && checkRangeType &&
+        !((FunctionType) Type).getRange().equals(range.getType())) {
+      throw new TypeException("FieldImpl.setSample: bad range type");
+    }
+    if (index >= 0 && index < getLength()) {
+      Data[]Range = getRange ();
+      synchronized (RangeLock) {
+        MissingFlag = false;
+        if (range != null) {
+          if (copy) {
+            Range[index] = (Data) range.dataClone();
+          }
+          else {
+            Range[index] = range;
+          }
+          if (Range[index] instanceof DataImpl) {
+            ((DataImpl) Range[index]).setParent(this);
+          }
+        }
+        else {
+          Range[index] = null;
+        }
+      }
+    }
+    notifyReferences();
+  }
+
+  /** test whether Field value is missing */
+  public boolean isMissing() {
+    synchronized (RangeLock) {
+      return MissingFlag;
+    }
+  }
+
+  /** return new Field with value 'this op data';
+      test for various relations between types of this and data */
+  /*- TDR  May 1998
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+   */
+  public Data binary(Data data, int op, MathType new_type,
+                     int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    boolean field_flag; // true if this and data have same type
+    if ( new_type == null ) {
+      throw new TypeException("binary: new_type may not be null" );
+    }
+    if (Type.equalsExceptName(data.getType())) {
+      /*-  TDR  May 1998  */
+      if ( !Type.equalsExceptName( new_type )) {
+        throw new TypeException("binary: new_type doesn't match return type");
+      }
+      /*- end  */
+      // type of this and data match, so normal Field operation
+      field_flag = true;
+      if (((Field) data).isFlatField()) {
+        // force (data instanceof FlatField) to be true
+        data = data.local();
+        // this and data have same type, but data is Flat and this is not
+        data = ((FlatField) data).convertToField();
+      }
+    }
+    else if (data instanceof Real ||
+             ((FunctionType) Type).getRange().equalsExceptName(data.getType())) {
+      // data is real or matches range type of this
+      field_flag = false;
+      /*-  TDR May 1998  */
+      if ( !Type.equalsExceptName( new_type )) {
+        throw new TypeException("binary: new_type doesn't match return type");
+      }
+      /*-  end  */
+    }
+    else if (data instanceof Field &&
+             ((FunctionType) data.getType()).getRange().equalsExceptName(Type)) {
+
+      /*- TDR  May 1998 */
+      if ( !((FunctionType) data.getType()).getRange().equalsExceptName(new_type)) {
+        throw new TypeException("binary: new_type doesn't match return type");
+      }
+      /*- end */
+      // this matches range type of data
+      // note invertOp to reverse order of operands
+      /*- TDR  May 1998
+      return data.binary(this, invertOp(op), sampling_mode, error_mode);
+       */
+      return data.binary(this, invertOp(op), new_type, sampling_mode, error_mode);
+    }
+    else {
+      throw new TypeException("FieldImpl.binary: types don't match");
+    }
+    // create (initially missing) Field for return
+    Field new_field = new FieldImpl((FunctionType) new_type, getDomainSet());
+    if (isMissing() || data.isMissing()) return new_field;
+    Data[] range = new Data[getLength()];
+    /*- TDR  May 1998  */
+    MathType m_type = ((FunctionType)new_type).getRange();
+
+
+    if (field_flag) {
+      // resample data if needed
+      data = ((Field) data).resample(getDomainSet(), sampling_mode, error_mode);
+      // apply operation to each range object
+      for (int i=0; i<getLength(); i++) {
+        synchronized (RangeLock) {
+          range[i] = ((MyRange==null||MyRange[i] == null)) ? null :
+                    /*-  TDR May 1998
+                     Range[i].binary(((Field) data).getSample(i), op,
+                                     sampling_mode, error_mode);
+                     */
+                     MyRange[i].binary(((Field) data).getSample(i), op, m_type,
+                                     sampling_mode, error_mode);
+
+        }
+      }
+    }
+    else { // !field_flag
+      for (int i=0; i<getLength(); i++) {
+        synchronized (RangeLock) {
+          range[i] = ((MyRange==null||MyRange[i] == null)) ? null :
+                     /*- TDR  May 1998
+                     MyRange[i].binary(data, op, sampling_mode, error_mode);
+                      */
+                     MyRange[i].binary(data, op, m_type, sampling_mode, error_mode);
+        }
+      }
+    }
+    new_field.setSamples(range, false);
+    return new_field;
+  }
+
+
+  /** return new Field with value 'op this' */
+  /*- TDR  July  1998
+  public Data unary(int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+  */
+  public Data unary(int op, MathType new_type, int sampling_mode,
+                    int error_mode )
+              throws VisADException, RemoteException {
+    if ( new_type == null ) {
+      throw new TypeException("unary: new_type may not be null");
+    }
+    if ( !Type.equalsExceptName(new_type)) {
+      throw new TypeException("unary: new_type doesn't match return type");
+    }
+    MathType m_type = ((FunctionType)new_type).getRange();
+    // create (initially missing) Field for return
+/* WLH 17 Jan 2000
+    Field new_field = new FieldImpl((FunctionType) Type, getDomainSet());
+*/
+/* WLH 3 April 2003
+    Field new_field = new FieldImpl((FunctionType) new_type, getDomainSet());
+*/
+    RealTupleType d_type = ((FunctionType)new_type).getDomain();
+    Set new_set = null;
+    if (!d_type.equals( ((FunctionType) getType()).getDomain() )) {
+      new_set = (Set) getDomainSet().cloneButType(d_type);
+    }
+    else {
+      new_set = getDomainSet();
+    }
+    Field new_field = new FieldImpl((FunctionType) new_type, new_set);
+
+    if (isMissing()) return new_field;
+    Data[] range = new Data[getLength()];
+
+
+    // apply operation to each range object
+    for (int i=0; i<getLength(); i++) {
+      synchronized (RangeLock) {
+        range[i] = ((MyRange==null||MyRange[i] == null)) ? null :
+                   MyRange[i].unary(op, m_type, sampling_mode, error_mode);
+      }
+    }
+    new_field.setSamples(range, false);
+    return new_field;
+  }
+
+  /** 
+   * Resample all elements of the fields array to the domain
+   * set of fields[0], then return a Field whose range samples
+   * are Tuples merging the corresponding range samples from
+   * each element of fields; if the range of fields[i] is a
+   * Tuple without a RangeCoordinateSystem, then each Tuple
+   * component of a range sample of fields[i] becomes a
+   * Tuple component of a range sample of the result -
+   * otherwise a range sample of fields[i] becomes a Tuple
+   * component of a range sample of the result; this assumes
+   * all elements of the fields array have the same domain
+   * dimension; use default sampling_mode (Data.NEAREST_NEIGHBOR)
+   * and default error_mode (Data.NO_ERRORS) 
+   *
+   * @param fields  fields to combine
+   * @return  combined fields
+   */
+  public static Field combine( Field[] fields )
+                throws VisADException, RemoteException
+  {
+    return combine( fields, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS, true);
+  }
+
+  /** 
+   * Resample all elements of the fields array to the domain
+   * set of fields[0], then return a Field whose range samples
+   * are Tuples merging the corresponding range samples from
+   * each element of fields.  If <code>flatten</code> is true and 
+   * if the range of fields[i] is a
+   * Tuple without a RangeCoordinateSystem, then each Tuple
+   * component of a range sample of fields[i] becomes a
+   * Tuple component of a range sample of the result -
+   * otherwise a range sample of fields[i] becomes a Tuple
+   * component of a range sample of the result; this assumes
+   * all elements of the fields array have the same domain
+   * dimension; use default sampling_mode (Data.NEAREST_NEIGHBOR)
+   * and default error_mode (Data.NO_ERRORS) 
+   *
+   * @param fields  fields to combine
+   * @param flatten   true to flatten range tuples with no CoordinateSystem
+   * @return  combined fields
+   */
+  public static Field combine( Field[] fields, boolean flatten )
+                throws VisADException, RemoteException
+  {
+    return combine( fields, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS, flatten );
+  }
+
+  /** resample all elements of the fields array to the domain
+      set of fields[0], then return a Field whose range samples
+      are Tuples merging the corresponding range samples from
+      each element of fields; if the range of fields[i] is a
+      Tuple without a RangeCoordinateSystem, then each Tuple
+      component of a range sample of fields[i] becomes a
+      Tuple component of a range sample of the result -
+      otherwise a range sample of fields[i] becomes a Tuple
+      component of a range sample of the result; this assumes
+      all elements of the fields array have the same domain
+      dimension */
+  public static Field combine( Field[] fields, int sampling_mode, int error_mode )
+                throws VisADException, RemoteException
+  {
+    return combine( fields, sampling_mode, error_mode, true);
+  }
+
+  /** 
+   * Resample all elements of the fields array to the domain
+   * set of fields[0], then return a Field whose range samples
+   * are Tuples merging the corresponding range samples from
+   * each element of fields.  If <code>flatten</code> is true and 
+   * if the range of fields[i] is a
+   * Tuple without a RangeCoordinateSystem, then each Tuple
+   * component of a range sample of fields[i] becomes a
+   * Tuple component of a range sample of the result -
+   * otherwise a range sample of fields[i] becomes a Tuple
+   * component of a range sample of the result; this assumes
+   * all elements of the fields array have the same domain
+   * dimension.
+   *
+   * @param fields  fields to combine
+   * @param sampling_mode   sampling mode to use (e.g., Data.NEAREST_NEIGHBOR)
+   * @param error_mode   error mode to use (e.g., Data.NO_ERRORS)
+   * @param flatten   true to flatten range tuples with no CoordinateSystem
+   *
+   * @return  combined fields
+   */
+  public static Field combine( Field[] fields, int sampling_mode, int error_mode , boolean flatten)
+                throws VisADException, RemoteException
+  {
+    return combine(fields, sampling_mode, error_mode, flatten, true);
+  }
+
+  /** 
+   * Resample all elements of the fields array to the domain
+   * set of fields[0], then return a Field whose range samples
+   * are Tuples merging the corresponding range samples from
+   * each element of fields.  If <code>flatten</code> is true and 
+   * if the range of fields[i] is a
+   * Tuple without a RangeCoordinateSystem, then each Tuple
+   * component of a range sample of fields[i] becomes a
+   * Tuple component of a range sample of the result -
+   * otherwise a range sample of fields[i] becomes a Tuple
+   * component of a range sample of the result; this assumes
+   * all elements of the fields array have the same domain
+   * dimension.
+   *
+   * @param fields  fields to combine
+   * @param sampling_mode   sampling mode to use (e.g., Data.NEAREST_NEIGHBOR)
+   * @param error_mode   error mode to use (e.g., Data.NO_ERRORS)
+   * @param flatten   true to flatten range tuples with no CoordinateSystem
+   * @param copy   true to copy the values from the original fields
+   *
+   * @return  combined fields
+   */
+  public static Field combine( Field[] fields, int sampling_mode, int error_mode , boolean flatten, boolean copy)
+                throws VisADException, RemoteException
+  {
+    visad.util.Trace.call1("combine, copy = " + copy);
+    int ii, jj, kk, n_fields;
+    int domainDim = 0;                 //- domain dimension of field_0.
+    Set domainSet_0;                   //- domain set of field_0.
+    RealTupleType domainType_0;        //- domain type of field_0.
+    int n_samples_0;                   //- number of samples in field_0.
+    Field new_field;                   //- return field.
+    boolean allFlat = true;            //- all fields in array are FlatFields.
+    Field field_0 = fields[0];         //- first field in array, "this".
+    FunctionType new_type;             //- type of result.
+    n_fields = fields.length;
+    MathType[] fieldRange_s = new MathType[ n_fields ];
+    MathType[][] rangeComp_s = new MathType[ n_fields ][];
+    FunctionType[] fieldType_s = new FunctionType[ n_fields ];
+    MathType new_range, range;
+    MathType m_type;
+    MathType fieldRange;
+    FlatField f_field;
+    float[][] valuesF = null;
+    float[][] new_valuesF = null;
+    double[][] valuesD = null;
+    double[][] new_valuesD = null;
+    int n_comps;
+    int n_dims;
+    int length;
+    int cnt = 0;
+
+    n_fields = fields.length;
+    n_comps = 0;
+
+    for ( ii = 0; ii < n_fields; ii++ )
+    {
+      if ( ii == 0 )
+      {
+        domainDim = field_0.getDomainDimension();
+      }
+      else if ( domainDim != fields[ii].getDomainDimension() )
+      {
+        throw new VisADException( "FieldImpl.combine: domain dimensions of input"+
+                                  "fields must match" );
+      }
+
+      if ( !(fields[ii].isFlatField()) )
+      {
+        allFlat = false;
+      }
+
+      fieldType_s[ii] = (FunctionType) fields[ii].getType();
+      fieldRange_s[ii] = ((FunctionType)fields[ii].getType()).getRange();
+      range = fieldRange_s[ii];
+
+      if ( range instanceof RealType )
+      {
+        rangeComp_s[ii] = new MathType[ 1 ];
+        rangeComp_s[ii][0] = range;
+        n_comps++;
+      }
+      else if ( range instanceof RealTupleType )
+      {
+        rangeComp_s[ii] = new MathType[ 1];
+        rangeComp_s[ii][0] = range;
+        n_comps++;
+      }
+      else if ( range instanceof TupleType )
+      {
+        n_dims = ((TupleType)range).getDimension();
+        rangeComp_s[ii] = new MathType[ n_dims ];
+        for ( jj = 0; jj < n_dims; jj++ ) {
+          rangeComp_s[ii][jj] = ((TupleType)range).getComponent(jj);
+        }
+        n_comps += n_dims;
+      }
+      else
+      {
+        rangeComp_s[ii] = new MathType[1];
+        rangeComp_s[ii][0] = range;
+        n_comps++;
+      }
+    }
+
+    domainSet_0 = field_0.getDomainSet();
+    domainType_0 = ((FunctionType)field_0.getType()).getDomain();
+    n_samples_0 = domainSet_0.getLength();
+
+    if ( allFlat )
+    {
+      int n_sets;
+      int n_sys;
+      CoordinateSystem[] rangeCoordSys_s;
+      Field field;
+
+      boolean allReal = true;
+      int tupleDim = 0;
+      cnt = 0;
+
+      Vector coordsys_s = new Vector();
+      Vector m_types = new Vector();
+      Vector r_types = new Vector();
+
+      for ( ii = 0; ii < n_fields; ii++ )
+      {
+        field = fields[ii];
+        fieldRange = fieldRange_s[ii];
+
+        if ( fieldRange instanceof RealType )
+        {
+          m_types.add( fieldRange );
+          r_types.add( fieldRange );
+          rangeCoordSys_s = field.getRangeCoordinateSystem();
+          coordsys_s.add( rangeCoordSys_s[0] );
+          tupleDim++;
+        }
+        else if ( fieldRange instanceof RealTupleType )
+        {
+          n_dims = ((RealTupleType)fieldRange).getDimension();
+          tupleDim += n_dims;
+          rangeCoordSys_s = field.getRangeCoordinateSystem();
+          if ( rangeCoordSys_s[0] == null && flatten)
+          {
+            for ( jj = 0; jj < n_dims; jj++ )
+            {
+              m_type = ((RealTupleType)fieldRange).getComponent(jj);
+              m_types.add( m_type );
+              r_types.add( m_type );
+              coordsys_s.add( null );
+            }
+          }
+          else
+          {
+            m_types.add( fieldRange );
+            coordsys_s.add( rangeCoordSys_s[0] );
+            allReal = false;
+          }
+        }
+        else //- must be Flat TupleType   -*
+        {
+          for ( jj = 0; jj < rangeComp_s[ii].length; jj++ )
+          {
+            rangeCoordSys_s = field.getRangeCoordinateSystem(jj);
+            m_type = rangeComp_s[ii][jj];
+
+            if ( m_type instanceof RealType )
+            {
+              r_types.add( m_type );
+              m_types.add( m_type );
+              coordsys_s.add( rangeCoordSys_s[0] );
+              tupleDim++;
+            }
+            else if ( m_type instanceof RealTupleType )
+            {
+              n_dims = ((RealTupleType)m_type).getDimension();
+              tupleDim += n_dims;
+              if ( rangeCoordSys_s[0] == null && flatten)
+              {
+                for ( kk = 0; kk < n_dims; kk++ )
+                {
+                  m_types.add( ((RealTupleType)m_type).getComponent(kk) );
+                  r_types.add( ((RealTupleType)m_type).getComponent(kk) );
+                  coordsys_s.add( null );
+                }
+              }
+              else
+              {
+                m_types.add( m_type );
+                coordsys_s.add( rangeCoordSys_s[0] );
+                allReal = false;
+              }
+            }
+          }
+        }
+      }
+
+      if ( allReal )
+      {
+        length = r_types.size();
+        RealType[] r_array =  new RealType[ length ];
+        for ( ii = 0; ii < length; ii++ ) {
+          r_array[ii] = (RealType) r_types.elementAt(ii);
+        }
+        new_range = new RealTupleType( r_array );
+      }
+      else
+      {
+        length = m_types.size();
+        MathType[] m_array =  new MathType[ length ];
+        for ( ii = 0; ii < length; ii++ ) {
+          m_array[ii] = (MathType) m_types.elementAt(ii);
+        }
+        new_range = new TupleType( m_array );
+      }
+
+      new_type = new FunctionType( domainType_0, new_range );
+
+      length = coordsys_s.size();
+      CoordinateSystem[] all_rangeCoordSys_s = new CoordinateSystem[ length ];
+      for ( ii = 0; ii < length; ii++ )
+      {
+        all_rangeCoordSys_s[ii] = (CoordinateSystem) coordsys_s.elementAt(ii);
+      }
+
+      Set[] rangeSets;
+      Set[] new_rangeSets = new Set[ tupleDim ];
+      Unit[][] rangeUnits;
+      Unit[] new_rangeUnits = new Unit[ tupleDim ];
+      Unit[] sub_units;
+
+      int cnt_a = 0;
+      int cnt_b = 0;
+      int cnt_c = 0;
+      int cnt_u = 0;
+      int n_coordsys = 0;
+      int n_units;
+      boolean allFloat = true;  // default set for FlatField
+      FlatField[] resampledFields = new FlatField[n_fields];
+
+      for ( ii = 0; ii < n_fields; ii++ )
+      {
+        f_field = (FlatField) fields[ii].local();
+
+        if ( ii > 0 ) //- don't resample field to itself even though this would
+        {             //- probably be no-op anyway.
+
+          f_field = (FlatField) f_field.resample( domainSet_0, sampling_mode, error_mode );
+        }
+        resampledFields[ii] = f_field;
+
+        //-  collect rangeSets for each tuple dimension  -*
+
+        rangeSets = f_field.getRangeSets();
+        n_sets = rangeSets.length;
+        // check for float sets
+        if (allFloat) {
+          for (int s = 0; s < n_sets; s++) {
+            if (!(rangeSets[s] instanceof FloatSet)) {
+              allFloat = false;
+              break;
+            }
+          }
+        }
+
+        System.arraycopy( rangeSets, 0, new_rangeSets, cnt_a, n_sets );
+        cnt_a += n_sets;
+
+        /* Don't need to do this because getSamples/Values uses default units)
+        //-  collect rangeUnits for each tuple dimension  -*
+        rangeUnits = f_field.getRangeUnits();
+        n_units = rangeUnits.length;
+        sub_units = new Unit[ n_units ];
+        for ( jj = 0; jj < n_units; jj++ ) {
+          sub_units[jj] = rangeUnits[jj][0];
+        }
+        System.arraycopy( sub_units, 0, new_rangeUnits, cnt_u, n_units );
+        cnt_u += n_units;
+        */
+      }
+      // metadata collected.  Now get Data
+
+      if (allFloat) {
+         new_valuesF = new float[ tupleDim ][];
+      } 
+      else {
+         new_valuesD = new double[ tupleDim ][];
+      }
+
+      for (int f = 0; f < n_fields; f++) {
+        
+        //-  get range values for each field, and combine into one array  -*
+        if (allFloat) {
+          valuesF = resampledFields[f].getFloats(copy);
+          for ( jj = 0; jj < valuesF.length; jj++ )
+          {
+            new_valuesF[cnt_b++] = valuesF[jj];
+          }
+        }
+        else {
+          valuesD = resampledFields[f].getValues(copy);
+          for ( jj = 0; jj < valuesD.length; jj++ )
+          {
+            new_valuesD[cnt_b++] = valuesD[jj];
+          }
+        }
+      }
+
+      //- data and metadata collected.  Make new flatfield  -*
+
+      if ( new_type.getReal() )
+      {
+        new_field = new FlatField( new_type, domainSet_0, null, null,
+                                   new_rangeSets, null );
+      }
+      else
+      {
+        new_field = new FlatField( new_type, domainSet_0, all_rangeCoordSys_s,
+                                   new_rangeSets, null );
+      }
+
+      //- set range values for new flatfield  -*
+      if (allFloat) {
+        ((FlatField)new_field).setSamples( new_valuesF, false );
+      } 
+      else {
+        ((FlatField)new_field).setSamples( new_valuesD, false );
+      }
+    }
+    else  //- not all FlatField(s)  -*
+    {
+      Vector sub_types = new Vector();
+      Field field;
+      Data[] data_s;
+      Data data;
+      Data rangeData;
+      boolean allReal = true;
+      RealTuple R_tuple;
+
+      Vector[] v_array = new Vector[ n_samples_0 ];
+      for ( ii = 0; ii < n_samples_0; ii++ ) {
+         v_array[ii] = new Vector();
+      }
+
+      for ( ii = 0; ii < n_fields; ii++ )
+      {
+        if ( ii == 0 ) //- resample fields to domain of first
+        {
+          field = (Field) fields[ii].local();
+        }
+        else
+        {
+          field = fields[ii].resample( domainSet_0, sampling_mode, error_mode );
+        }
+
+        fieldRange = fieldRange_s[ii];
+        if ( fieldRange instanceof RealType )
+        {
+          sub_types.add( fieldRange );
+          for ( kk = 0; kk < n_samples_0; kk++ ) {
+            v_array[kk].add( field.getSample(kk) );
+          }
+        }
+        else if ( fieldRange instanceof RealTupleType )
+        {
+          if ( ((RealTupleType)fieldRange).getCoordinateSystem() != null )
+          {
+            sub_types.add( fieldRange );
+            allReal = false;
+            for ( kk = 0; kk < n_samples_0; kk++ ) {
+              v_array[kk].add( field.getSample(kk) );
+            }
+          }
+          else
+          {
+            n_dims = ((RealTupleType)fieldRange).getDimension();
+            for ( jj = 0; jj < n_dims; jj++ )
+            {
+              sub_types.add( ((RealTupleType)fieldRange).getComponent(jj) );
+            }
+            for ( kk = 0; kk < n_samples_0; kk++ ) {
+              rangeData = field.getSample(kk);
+              for ( jj = 0; jj < n_dims; jj++ ) {
+                 v_array[kk].add( ((RealTuple)rangeData).getComponent(jj) );
+              }
+            }
+          }
+        }
+        else if( (fieldRange instanceof TupleType) &&
+                !(fieldRange instanceof RealTupleType) )
+        {
+          if ( !(((TupleType)fieldRange).getFlat()) )
+          {
+            sub_types.add( fieldRange );
+            for ( kk = 0; kk < n_samples_0; kk++ ) {
+              v_array[kk].add( field.getSample(kk) );
+            }
+            allReal = false;
+          }
+          else
+          {
+            n_dims = ((TupleType)fieldRange).getDimension();
+            for ( ii = 0; ii < n_dims; ii++ )
+            {
+              m_type = ((TupleType)fieldRange).getComponent(ii);
+              if ( m_type instanceof RealType )
+              {
+                sub_types.add( m_type );
+                for ( kk = 0; kk < n_samples_0; kk++ ) {
+                  v_array[kk].add( ((TupleIface)field.getSample(kk)).getComponent(ii));
+                }
+              }
+              else if ( m_type instanceof RealTupleType )
+              {
+                if ( ((RealTupleType)m_type).getCoordinateSystem() != null )
+                {
+                  sub_types.add( m_type );
+                  allReal = false;
+                  for ( kk = 0; kk < n_samples_0; kk++ ) {
+                    v_array[kk].add( ((TupleIface)field.getSample(kk)).getComponent(ii));
+                  }
+                }
+                else
+                {
+                  for ( jj = 0; jj < ((RealTupleType)m_type).getDimension(); jj++ )
+                  {
+                    sub_types.add( ((RealTupleType)m_type).getComponent(jj) );
+                  }
+                  for ( kk = 0; kk < n_samples_0; kk++ ) {
+                    R_tuple = (RealTuple)((TupleIface)field.getSample(kk)).getComponent(ii);
+                    for ( jj = 0; jj < ((RealTupleType)m_type).getDimension(); jj++ ) {
+                      v_array[kk].add( R_tuple.getComponent(jj));
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+        else if ( fieldRange instanceof FunctionType )
+        {
+          sub_types.add( fieldRange );
+          for ( kk = 0; kk < n_samples_0; kk++ ) {
+            v_array[kk].add( field.getSample(kk) );
+          }
+          allReal = false;
+        }
+      } //- all fields loop -*
+
+      int size = sub_types.size();
+
+      if ( allReal )
+      {
+        RealType[] r_types = new RealType[ size ];
+        for ( ii = 0; ii < size; ii++ ) {
+          r_types[ii] = (RealType) sub_types.elementAt(ii);
+        }
+        new_range = new RealTupleType( r_types );
+      }
+      else
+      {
+        MathType[] m_types = new MathType[ size ];
+        for ( ii = 0; ii < size; ii++ ) {
+          m_types[ii] = (MathType) sub_types.elementAt(ii);
+        }
+        new_range = new TupleType( m_types );
+      }
+
+      new_type = new FunctionType( domainType_0, new_range );
+
+      new_field = new FieldImpl( new_type, domainSet_0 );
+
+      for ( ii = 0; ii < n_samples_0; ii++ )
+      {
+        size = v_array[ii].size();
+        data_s = new Data[ size ];
+        for ( jj = 0; jj < size; jj++ )
+        {
+          data_s[jj] = (Data) v_array[ii].elementAt( jj );
+        }
+        data = new Tuple( data_s );
+        new_field.setSample( ii, data, copy );
+      }
+    }
+
+    visad.util.Trace.call2("combine, copy = " + copy);
+    return new_field;
+  }
+
+  /** extract Field from this.component using the MathType
+  * of one of the range componenets
+  */
+
+  public Field extract(MathType type) throws VisADException, RemoteException {
+    int index = -1;
+    MathType rangeType = ((FunctionType)Type).getRange();
+    if (!(rangeType instanceof TupleType)) {
+      throw new VisADException("FieldImpl.extract: range must be a TupleType");
+    }
+    int n_comps = ((TupleType)rangeType).getDimension();
+    for (int i=0; i<n_comps; i++) {
+      MathType test_comp = ((TupleType)rangeType).getComponent(i);
+      if (test_comp.equals(type) ) {
+        index = i;
+        break;
+      }
+    }
+
+    if (index != -1) {
+      return extract(index);
+    } else {
+      return null; //?
+    }
+  }
+
+  /** extract Field from this.component using the name
+  * of one of the range componenets
+  */
+
+  public Field extract(String name) throws VisADException, RemoteException {
+    int index = -1;
+    MathType rangeType = ((FunctionType)Type).getRange();
+    if (!(rangeType instanceof TupleType)) {
+      throw new VisADException("FieldImpl.extract: range must be a TupleType");
+    }
+    int n_comps = ((TupleType)rangeType).getDimension();
+    for (int i=0; i<n_comps; i++) {
+      String test_comp = ((TupleType)rangeType).getComponent(i).toString();
+      if (test_comp.equals(name) || test_comp.equals("("+name+")")) {
+        index = i;
+        break;
+      }
+    }
+    if (index != -1) {
+      return extract(index);
+    } else {
+      return null; //?
+    }
+  }
+
+  /** extract field from this[].component */
+  public Field extract(int component)
+         throws VisADException, RemoteException
+  {
+    Set domainSet = getDomainSet();
+    int n_samples = domainSet.getLength();
+    MathType rangeType = ((FunctionType)Type).getRange();
+    MathType domainType = ((FunctionType)Type).getDomain();
+    Data rangeData;
+    Data new_rangeData;
+    Unit[] new_unit;
+    Unit unit;
+    double[][] values, t_values;
+    double value;
+    CoordinateSystem coord_sys, coord_in, coord_out;
+    MathType m_type;
+    RealTupleType rt_type;
+    RealType r_type;
+    Real real;
+    RealTuple r_tuple;
+    FieldImpl new_field;
+    int ii, jj, kk, t_dim, dim, n_coordsys;
+
+    if (!( rangeType instanceof TupleType) )
+    {
+      throw new VisADException("extract: range type must be TupleType");
+    }
+    int n_comps = ((TupleType)rangeType).getDimension();
+    if (component == 0 && n_comps == 1) return this;
+
+    if ( (component + 1) > n_comps )
+    {
+      throw new VisADException("extract: component selection too large");
+    }
+
+    MathType new_range = ((TupleType)rangeType).getComponent( component );
+    FunctionType new_type = new FunctionType( domainType, new_range);
+
+    if ( new_range instanceof RealType )
+    {
+      new_unit = new Unit[1];
+      new_unit[0] = ((RealType)new_range).getDefaultUnit();
+      new_field = new FlatField( new_type, domainSet, null, null, null, new_unit );
+      values = new double[1][n_samples];
+
+      for ( ii = 0; ii < n_samples; ii++ )
+      {
+        real = (Real)
+          ((TupleIface) getSample(ii)).getComponent( component );
+        value = real.getValue();
+
+        if (new_unit[0] != null) {
+          unit = real.getUnit();
+          values[0][ii] = new_unit[0].toThis( value, unit );
+        }
+        else {
+          values[0][ii] = value;
+        }
+      }
+
+      ((FlatField)new_field).setSamples( values, false );
+    }
+    else if ( new_range instanceof RealTupleType )
+    {
+      coord_out = ((RealTupleType)new_range).getCoordinateSystem();
+      dim = ((RealTupleType)new_range).getDimension();
+      Unit[] unit_out = new Unit[dim];
+      Unit[] unit_in = new Unit[dim];
+      unit_out = ((RealTupleType)new_range).getDefaultUnits();
+      new_field = new FlatField( new_type, domainSet, coord_out, null, unit_out );
+      values = new double[ dim ][ n_samples ];
+      t_values = new double[ dim ][ 1];
+
+      for ( ii = 0; ii < n_samples; ii++ )
+      {
+        r_tuple = (RealTuple)
+          ((TupleIface) getSample(ii)).getComponent( component );
+        coord_in = r_tuple.getCoordinateSystem();
+        unit_in = r_tuple.getTupleUnits();
+        for ( jj = 0; jj < dim; jj++ )
+        {
+          t_values[jj][0] = ((Real)r_tuple.getComponent(jj)).getValue();
+        }
+
+        t_values = CoordinateSystem.transformCoordinates(
+                        (RealTupleType)new_range, coord_out, unit_out, null,
+                        (RealTupleType)new_range, coord_in, unit_in, null, t_values );
+
+        for ( jj = 0; jj < dim; jj++ )
+        {
+          values[jj][ii] = t_values[jj][0];
+        }
+      }
+
+      ((FlatField)new_field).setSamples( values, false );
+    }
+    else if (( new_range instanceof TupleType) && ( ((TupleType)new_range).getFlat()) )
+    {
+      new_field = new FlatField( new_type, domainSet );
+      dim = ((TupleType)new_range).getDimension();
+
+      t_dim = 0;
+      n_coordsys = 0;
+      for ( ii = 0; ii < dim; ii++ )
+      {
+        m_type = ((TupleType)new_range).getComponent(ii);
+        if ( m_type instanceof RealType )
+        {
+          t_dim++;
+        }
+        else if ( m_type instanceof RealTupleType )
+        {
+          rt_type = (RealTupleType)m_type;
+          t_dim += (rt_type).getDimension();
+          if ( rt_type.getCoordinateSystem() != null )
+          {
+            n_coordsys++;
+          }
+        }
+      }
+
+      values = new double[ t_dim ][ n_samples ];
+
+
+      t_dim = 0;
+      for ( ii = 0; ii < n_samples; ii++ )
+      {
+        rangeData = getSample(ii);
+        m_type = rangeData.getType();
+
+        for ( jj = 0; jj < dim; jj++ )
+        {
+          if ( m_type instanceof RealType )
+          {
+            values[t_dim][ii] =
+              ((Real)((TupleIface)rangeData).getComponent(jj)).getValue();
+            t_dim++;
+          }
+          else
+          {
+            r_tuple = (RealTuple) ((TupleIface)rangeData).getComponent(jj);
+            for ( kk = 0; kk < ((RealTupleType)m_type).getDimension(); kk++ )
+            {
+              values[t_dim][ii] = ((Real)r_tuple.getComponent(kk)).getValue();
+              t_dim++;
+            }
+          }
+        }
+      }
+      ((FlatField)new_field).setSamples( values, false );
+    }
+    else
+    {
+      new_field = new FieldImpl( new_type, domainSet );
+
+      for ( ii = 0; ii < n_samples; ii++ )
+      {
+        rangeData = getSample(ii);
+        new_rangeData = ((TupleIface)rangeData).getComponent( component );
+        new_field.setSample( ii, new_rangeData, false );
+      }
+    }
+
+    return new_field;
+  }
+
+  /**
+   * Factors this instance into a (nested) field-of-fields.  The type of the
+   * domain of the outer field will be the type specified.  The type of the
+   * domains of the inner fields will be the remainder of the original domain
+   * after the outer domain is factored out.  Range data is not copied for
+   * FieldImpls, but will be for FlatFields.
+   * @see #domainFactor(RealType, boolean) for copy information
+   *
+   * @param factor              The type of the domain for the outer field.
+   * @return                    The field-of-fields realization of this
+   *                            instance.
+   * @throws DomainException    The domain of this instance cannot be factored
+   *                            as requested.
+   * @throws VisADException     VisAD failure.
+   * @throws RemoteException    Java RMI failure.
+   */
+  public Field domainFactor( RealType factor )
+         throws DomainException, VisADException, RemoteException
+  {
+    return domainFactor(factor, false);
+  }
+
+  /**
+   * Factors this instance into a (nested) field-of-fields.  The type of the
+   * domain of the outer field will be the type specified.  The type of the
+   * domains of the inner fields will be the remainder of the original domain
+   * after the outer domain is factored out.  Range data is copied if
+   *
+   * @param factor              The type of the domain for the outer field.
+   * @param copy                true to make copies of the data objects
+   * @return                    The field-of-fields realization of this
+   *                            instance.
+   * @throws DomainException    The domain of this instance cannot be factored
+   *                            as requested.
+   * @throws VisADException     VisAD failure.
+   * @throws RemoteException    Java RMI failure.
+   */
+  public Field domainFactor( RealType factor, boolean copy )
+         throws DomainException, VisADException, RemoteException
+  {
+    int factorIndex;
+    Set factor_domain = null;
+    int length;
+    int[] lengths = null;
+    int[] new_lengths = null;
+    int[] dim_lengths = null;
+    int[] dim_product = null;
+    int[] sub_domain = null;
+    Set new_domain = null;
+    RealTupleType new_domain_type = null;
+    FieldImpl factor_field = null;
+    FunctionType new_type = null;
+    Data range;
+    Data[] new_range_data = null;
+
+    int ii, jj, kk, cnt;
+    int mm, nn, index;
+
+    RealTupleType domainType = ((FunctionType)Type).getDomain();
+    MathType rangeType = ((FunctionType)Type).getRange();
+    int domainDim = domainType.getDimension();
+    RealType[] r_types = new RealType[domainDim - 1];
+
+    if ((factorIndex = domainType.getIndex((MathType)factor)) < 0 ) {
+      throw new DomainException(
+        "domainFactor: factor not element of domain");
+    }
+    cnt = 0;
+    for ( ii = 0; ii < domainDim; ii++ ) {
+      if ( ii != factorIndex ) {
+        r_types[cnt++] = (RealType) domainType.getComponent(ii);
+      }
+    }
+    new_domain_type = new RealTupleType(r_types);
+    Set domainSet = getDomainSet();
+
+    if ( domainSet instanceof LinearSet )
+    {
+      factor_domain = ((LinearSet)domainSet).getLinear1DComponent( factorIndex );
+      dim_lengths = ((GriddedSet)domainSet).getLengths();
+
+      Linear1DSet[] L1D_sets = new Linear1DSet[domainDim - 1];
+      new_lengths = new int[ domainDim - 1 ];
+      sub_domain = new int[domainDim - 1];
+      cnt = 0;
+      for ( ii = 0; ii < domainDim; ii++ ) {
+        if ( ii != factorIndex ) {
+          L1D_sets[cnt] = ((LinearSet)domainSet).getLinear1DComponent(ii);
+          new_lengths[cnt] = L1D_sets[cnt].LengthX;
+          sub_domain[cnt] = ii;
+          cnt++;
+        }
+      }
+      new_domain = new LinearNDSet( new_domain_type, L1D_sets );
+      new_type = new FunctionType( new_domain_type, rangeType );
+    }
+    else if ( domainSet instanceof GriddedSet )
+    {
+      //- check for R^N aligned set?  If aligned then should
+      //- be created as a ProductSet for efficiency
+      throw new DomainException(
+        "domainFactor: DomainSet is GriddedSet, if aligned use ProductSet" );
+    }
+    else if ( domainSet instanceof ProductSet )
+    {
+      ProductSet prod_set = (ProductSet)((ProductSet)domainSet).product();
+      SampledSet[] sets = prod_set.Sets;
+      int n_sets = sets.length;
+      SampledSet[] sub_sets = new SampledSet[n_sets - 1];
+      SampledSet factor_set = null;
+      SampledSet fin_factor_set = null;
+      int sub_factor_index = -1;
+      int fac_set_idx = -1;
+      int fac_set_len = -1;
+      int[] sub_set_idx = new int[n_sets - 1];
+      int[] sub_set_lengths = new int[n_sets - 1];
+      cnt = 0;
+      for ( kk = 0; kk < n_sets; kk++ ) {
+        SetType s_type = (SetType) sets[kk].getType();
+        if ((sub_factor_index = s_type.getDomain().getIndex((MathType)factor)) >= 0 ) {
+          factor_set = sets[kk];
+          fac_set_idx = kk;
+          fac_set_len = factor_set.getLength();
+        }
+        else {
+          sub_sets[cnt] = sets[kk];
+          sub_set_idx[cnt] = kk;
+          sub_set_lengths[cnt] = sets[kk].getLength();
+          cnt++;
+        }
+      }
+
+      int factor_set_dim = factor_set.getDimension();
+      int n_sub_sets = sub_sets.length;
+
+      if ( factor_set_dim == 1 )
+      {
+        fin_factor_set = factor_set;
+        new_lengths = new int[n_sub_sets];
+        sub_domain = new int[n_sub_sets];
+        dim_lengths = new int[n_sets];
+        if ( n_sub_sets > 1 ) {
+          new_domain = (Set) new ProductSet(sub_sets);
+        }
+        else {
+          new_domain = (Set) sub_sets[0];
+        }
+        factor_domain = factor_set;
+        System.arraycopy(sub_set_lengths, 0, new_lengths, 0, n_sub_sets);
+        System.arraycopy(sub_set_idx, 0, sub_domain, 0, n_sub_sets);
+        for ( ii = 0; ii < n_sub_sets; ii++ ) {
+          dim_lengths[sub_set_idx[ii]] = sub_set_lengths[ii];
+        }
+        dim_lengths[fac_set_idx] = fac_set_len;
+        new_type =
+          new FunctionType(
+            ((SetType)new_domain.getType()).getDomain(), rangeType );
+      }
+      else if (!(factor_set instanceof LinearNDSet ))
+      {
+        throw new DomainException(
+          "cannot factor into "+factor_set.getClass());
+      }
+      else
+      {
+        MathType n_type = null;
+        new_lengths = new int[n_sub_sets + 1];
+        sub_domain = new int[n_sub_sets + 1];
+        dim_lengths = new int[n_sets + 1];
+        Linear1DSet[] L1D_sets = new Linear1DSet[factor_set_dim - 1];
+        cnt = 0;
+        for ( ii = 0; ii < factor_set_dim; ii++ ) {
+          if ( ii != sub_factor_index ) {
+            L1D_sets[cnt] = ((LinearSet)domainSet).getLinear1DComponent(ii);
+            cnt++;
+          }
+          else {
+            fin_factor_set = ((LinearSet)domainSet).getLinear1DComponent(ii);
+          }
+        }
+        Set new_set = new LinearNDSet(n_type, L1D_sets);
+        cnt = 0;
+        System.arraycopy(sub_set_lengths, 0, new_lengths, 0, fac_set_idx);
+        System.arraycopy(sub_set_idx, 0, sub_domain, 0, fac_set_idx);
+        cnt += fac_set_idx;
+        new_lengths[fac_set_idx] = new_set.getLength();
+        cnt += 1;
+        System.arraycopy(sub_set_lengths, fac_set_idx, new_lengths, cnt,
+                         (n_sub_sets - fac_set_idx));
+        System.arraycopy(sub_set_idx, fac_set_idx, sub_domain, cnt,
+                         (n_sub_sets - fac_set_idx));
+        new_type =
+          new FunctionType(
+            ((SetType)new_set.getType()).getDomain(), rangeType );
+        new_domain = new_set;
+      }
+    }
+    else if ( domainSet instanceof UnionSet )
+    {
+      throw new UnimplementedException(
+        "domainFactor: DomainSet is UnionSet" );
+    }
+    else if ( domainSet instanceof IrregularSet )
+    {
+      throw new DomainException(
+        "domainFactor: DomainSet is IrregularSet, can't factor" );
+    }
+
+    length = factor_domain.getLength();
+    new_range_data = new Data[ length ];
+
+    dim_product = new int[domainDim];
+    for ( kk = 0; kk < domainDim; kk++ ) {
+      dim_product[kk] = 1;
+      for ( mm = 0; mm < kk; mm++ ) {
+        dim_product[kk] *= dim_lengths[mm];
+      }
+    }
+
+    int s_dims = new_lengths.length;
+    int[] indexes = new int[ s_dims ];
+    int product = 1;
+    for ( ii = 0; ii < s_dims; ii++ ) {
+      product *= new_lengths[ii];
+    }
+    int[] work = new int[product];
+
+    for ( int k = 0; k < product; k++ )
+    {
+      int k2 = k;
+      for ( int j = (s_dims-1); j >= 0; j-- ) {
+        int temp = 1;
+        for ( int m = 0; m < j; m++  ) {
+          temp *= new_lengths[m];
+        }
+        indexes[j] = k2/temp;
+        k2 -= temp*indexes[j];
+      }
+      for ( int t = 0; t < indexes.length; t++ ) {
+        work[k] += dim_product[sub_domain[t]]*indexes[t];
+      }
+    }
+
+    if ( isFlatField() )
+    {
+      double[][] old_range_values = getValues(false);
+      int tup_dim = old_range_values.length;
+
+      for ( ii = 0; ii < length; ii++ )
+      {
+        double[][] new_range_values = new double[tup_dim][work.length];
+        FlatField new_field = new FlatField( new_type, new_domain );
+        for ( jj = 0; jj < work.length; jj++ ) {
+          index = 0;
+          index = ii*dim_product[factorIndex];
+          index += work[jj];
+          for ( kk = 0; kk < tup_dim; kk++ ) {
+            new_range_values[kk][jj] = old_range_values[kk][ index ];
+          }
+        }
+        ((FlatField)new_field).setSamples( new_range_values, false );
+        new_range_data[ii] = new_field;
+      }
+    }
+    else
+    {
+      for ( ii = 0; ii < length; ii++ )
+      {
+        FieldImpl new_field = new FieldImpl( new_type, new_domain );
+        for ( jj = 0; jj < work.length; jj++ ) {
+          index = 0;
+          index = ii*dim_product[factorIndex];
+          index += work[jj];
+          new_range_data[jj] = this.getSample(index);
+        }
+        new_field.setSamples( new_range_data, false, false );
+        new_range_data[ii] = new_field;
+      }
+    }
+    factor_field = new FieldImpl( new FunctionType( factor, new_type),
+                                  factor_domain );
+    factor_field.setSamples( new_range_data, copy, false );
+    return factor_field;
+  }
+
+  /** 
+   * Combine domains of two outermost nested Fields into a single
+   * domain and Field.  If the domains each have <code>
+   * CoordinateSystem</code>-s the new domain will have
+   * a <code>CartesianProductCoordinateSystem</code> of 
+   * Set-s CoordinateSystems
+   * 
+   * @throws VisADException  unable to collapse domains
+   * @throws RemoteException  unable to collapse domains of remote data
+   */
+  public Field domainMultiply()
+         throws VisADException, RemoteException
+  {
+    return domainMultiply(1, null);
+  }
+
+  /** 
+   * Combine domains of two outermost nested Fields into a single
+   * domain and Field.  The supplied <code>resultCS</code> would be used
+   * for the new domain
+   * @param resultCS  CoordinateSystem to use for the new domain set
+   * @throws VisADException  unable to collapse domains
+   * @throws RemoteException  unable to collapse domains of remote data
+   */
+  public Field domainMultiply(CoordinateSystem resultCS)
+         throws VisADException, RemoteException
+  {
+    return domainMultiply(1, resultCS);
+  }
+
+  /** 
+   * Combine domains of <code>collapse_depth</code> if possible.
+   * @param collapse_depth  depth to collapse to
+   * @throws VisADException  unable to collapse domains
+   * @throws RemoteException  unable to collapse domains of remote data
+   */
+  public Field domainMultiply(int collapse_depth)
+         throws VisADException, RemoteException
+  {
+     return domainMultiply(collapse_depth, null);
+  }
+
+  /** 
+   * Combine domains of <code>collapse_depth</code> if possible.
+   * Use <code>resultCS</code> as the CoordinateSystem for the new domain.
+   * @param collapse_depth  depth to collapse to
+   * @param resultCS  CoordinateSystem to use for the new domain set
+   * @throws VisADException  unable to collapse domains
+   * @throws RemoteException  unable to collapse domains of remote data
+   */
+  public Field domainMultiply(int collapse_depth, CoordinateSystem resultCS)
+         throws VisADException, RemoteException
+  {
+    class Helper
+    {
+      int cnt = 0;
+      int n_samples;
+      int depth;
+      int depth_max;
+      boolean flat;
+      MathType range_type;
+      MathType new_range_type;
+      SampledSet[] last_set;
+      SampledSet[] fac_sets;
+      Data[] collapse_array;
+
+      public Helper(Data data, int col_depth)
+             throws VisADException, RemoteException
+      {
+        MathType m_type = data.getType();
+
+        depth = 0;
+        flat = false;
+        depth_max = checkType( m_type );
+
+        if ( depth_max == 0 )
+        {
+          throw new FieldException("MathType "+m_type.prettyString());
+        }
+        else if ( depth_max >= col_depth )
+        {
+          depth_max = col_depth;
+        }
+
+
+        flat = false;
+        for (int kk = 0; kk < depth_max; kk++) {
+          if (m_type instanceof FunctionType) {
+            m_type = ((FunctionType)m_type).getRange();
+          }
+        }
+        if (m_type instanceof FunctionType)  {
+          flat = ((FunctionType)m_type).getFlat();
+          new_range_type = ((FunctionType)m_type).getRange();
+        }
+         
+
+        last_set = new SampledSet[depth_max + 1];
+        depth = 0;
+        if ( !(setsEqual((Field)data)) )
+        {
+          throw new FieldException("sets not equal");
+        }
+
+        int length = 1;
+        if (flat) {
+          for ( int kk = 0; kk < depth_max; kk++ ) {
+            length *= last_set[kk].getLength();
+          }
+        }
+        else {
+          for ( int kk = 0; kk < depth_max+1; kk++ ) {
+            length *= last_set[kk].getLength();
+          }
+        }
+ 
+        collapse_array = new Data[length];
+
+        depth = 0;
+        collapse( data );
+      }
+
+      public SampledSet[] getSets() {
+        int length = last_set.length;
+        fac_sets = new SampledSet[length];
+        for ( int ii = 0; ii < length; ii++ ) {
+          fac_sets[(length-1) - ii] = last_set[ii];
+        }
+        return fac_sets;
+      }
+
+      public Data[] getRangeArray() {
+        return collapse_array;
+      }
+
+      public MathType getRangeType() {
+        return new_range_type;
+      }
+
+      public int checkType(MathType m_type)  //-- analyze Data hierarchy
+             throws VisADException, RemoteException
+      {
+        if ( m_type instanceof FunctionType )
+        {
+          if (((FunctionType)m_type).getFlat())
+          {
+            flat = true;
+            new_range_type = ((FunctionType)m_type).getRange();
+            return depth;
+          }
+          else
+          {
+            range_type = ((FunctionType)m_type).getRange();
+            depth++;
+            return checkType(range_type);
+          }
+        }
+        else
+        {
+          new_range_type = m_type;
+          return depth;
+        }
+      }
+
+      public void collapse( Data data )
+             throws VisADException, RemoteException
+      {
+        if ( depth == depth_max )
+        {
+          if (flat)
+          {
+            collapse_array[cnt++] = ((FieldImpl)data);
+          }
+          else
+          {
+            for ( int ii = 0; ii < ((FieldImpl)data).getLength(); ii++) {
+              collapse_array[cnt++] = ((FieldImpl)data).getSample(ii);
+            }
+          }
+        }
+        else
+        {
+          int n_samples = (((Field)data).getDomainSet()).getLength();
+          for ( int ii = 0; ii < n_samples; ii++ ) {
+            depth++;
+            collapse(((FieldImpl)data).getSample(ii));
+            depth--;
+          }
+        }
+      }
+
+      public boolean setsEqual( Field field )
+             throws VisADException, RemoteException
+      {
+        Set domainSet = field.getDomainSet();
+        int n_samples = domainSet.getLength();
+
+        if ( depth == 0 ) {
+          last_set[depth] = (SampledSet)domainSet;
+        }
+
+        depth++;
+
+        if (last_set[depth] == null ) {
+          last_set[depth] = (SampledSet)((Field)(field.getSample(0))).getDomainSet();
+        }
+
+        for ( int ii = 0; ii < n_samples; ii++ )
+        {
+          Field range_data = (Field) field.getSample(ii);
+          Set range_set = range_data.getDomainSet();
+
+          if ( !(last_set[depth].equals(range_set)) )
+          {
+            return false;
+          }
+          if ( !(depth == (depth_max)) )
+          {
+            if ( !(setsEqual(range_data)) )
+            {
+              return false;
+            }
+            depth--;
+          }
+        }
+
+        return true;
+      }
+    } //- end helper class
+
+    int cnt;
+    SampledSet new_set = null;
+
+    int n_irregular = 0;
+    int n_linear = 0;
+    int new_domainDim = 0;
+    int new_manifoldDim = 0;
+
+    Helper helper = new Helper(this, collapse_depth);
+    SampledSet[] fac_sets = helper.getSets();
+    int n_sets = fac_sets.length;
+    Data[] new_range = helper.getRangeArray();
+    MathType new_range_type = helper.getRangeType();
+
+    SetType[] set_types = new SetType[n_sets];
+    int new_length = 1;
+    for ( int kk = 0; kk < n_sets; kk++ )
+    {
+      new_length *= fac_sets[kk].getLength();
+      new_domainDim += fac_sets[kk].getDimension();
+      new_manifoldDim += fac_sets[kk].getManifoldDimension();
+      set_types[kk] = (SetType) fac_sets[kk].getType();
+
+      if ( fac_sets[kk] instanceof IrregularSet ) {
+        n_irregular++;
+      }
+      else if ( fac_sets[kk] instanceof LinearSet ) {
+        n_linear++;
+      }
+    }
+    RealType[] r_types = new RealType[new_domainDim];
+
+    cnt = 0;
+    boolean any_are_null = false;
+    CoordinateSystem[] coord_sys = new CoordinateSystem[n_sets];
+
+    for ( int kk = 0; kk < n_sets; kk++ ) {
+      RealTupleType domain = set_types[kk].getDomain();
+      //- TDR: May, 2003
+      CoordinateSystem cs = domain.getCoordinateSystem();
+      if (cs == null) {
+        any_are_null = true;
+      }
+      coord_sys[kk] = cs;
+
+      for ( int j = 0; j < domain.getDimension(); j++ ) {
+        r_types[cnt++] = (RealType) domain.getComponent(j);
+      }
+    }
+    //- TDR: May, 2003
+    CoordinateSystem new_cs = resultCS;
+    if (!any_are_null && new_cs == null) {
+      new_cs = coord_sys[0];
+      for ( int kk = 0; kk < (coord_sys.length - 1); kk++ ) {
+        new_cs = new CartesianProductCoordinateSystem(new_cs, coord_sys[kk+1]);
+      }
+    }
+
+    RealTupleType new_domain_type = new RealTupleType( r_types, new_cs, null );
+    FunctionType new_function_type = new FunctionType( new_domain_type,
+                                                       new_range_type );
+
+    if ( n_irregular > 0 ) //- if any irregular sets --> ProductSet
+    {
+      new_set = new ProductSet(fac_sets);
+    }
+    else if ( n_linear == n_sets )  //- all Linear sets
+    {
+      Linear1DSet[] L1D_sets = new Linear1DSet[new_domainDim];
+      cnt = 0;
+      for ( int kk = 0; kk < n_sets; kk++ ) {
+        for ( int jj = 0; jj < fac_sets[kk].getDimension(); jj++ ) {
+          L1D_sets[cnt++] = ((LinearSet)fac_sets[kk]).getLinear1DComponent(jj);
+        }
+      }
+      new_set = new LinearNDSet(new_domain_type, L1D_sets);
+    }
+    else  //- some Linear, some Gridded --> GriddedNDSet
+    {
+      Set set;
+      int sub_manifoldDim;
+      int domainDim;
+      int[] lengths;
+      float[][] samples;
+      float[][] new_samples = new float[new_domainDim][new_length];
+      float[][] sub_samples = new float[new_domainDim][];
+      Unit[] newUnits = new Unit[new_domainDim];
+      ErrorEstimate[] newErrors = new ErrorEstimate[new_domainDim];
+
+      int[][] manifoldLengths = new int[new_domainDim][];
+      int[][] manifoldIndexes = new int[new_domainDim][];
+      int[] new_lengths = new int[new_manifoldDim];
+      cnt = 0;
+      int cnt_m = 0;
+      int manifoldDimension = 0;
+      for ( int kk = 0; kk < n_sets; kk++ )
+      {
+        set = fac_sets[kk];
+        samples = ((SampledSet)set).getSamples();
+        domainDim = set.getDimension();
+        sub_manifoldDim = set.getManifoldDimension();
+        lengths = ((GriddedSet)set).getLengths();
+        Unit[] units = set.getSetUnits();
+        ErrorEstimate[] errors = set.getSetErrors();
+        for ( int ii = 0; ii < domainDim; ii++ ) {
+          sub_samples[cnt] = samples[ii];
+          manifoldLengths[cnt] = lengths;
+          manifoldIndexes[cnt] = new int[sub_manifoldDim];
+          for ( int jj = 0; jj < sub_manifoldDim; jj++ ) {
+            manifoldIndexes[cnt][jj] = jj + manifoldDimension;
+          }
+          newUnits[cnt] = units[ii];
+          newErrors[cnt] = errors[ii];
+          cnt++;
+        }
+        for ( int ii = 0; ii < sub_manifoldDim; ii++ ) {
+          new_lengths[cnt_m++] = lengths[ii];
+        }
+        manifoldDimension += sub_manifoldDim;
+      }
+
+      int[] indexes = new int[new_manifoldDim];
+      for ( int k = 0; k < new_length; k++ )
+      {
+        int k2 = k;
+        for ( int j = (new_manifoldDim - 1); j >= 0; j-- ) {
+          int temp = 1;
+          for ( int m = 0; m < j; m++  ) {
+            temp *= new_lengths[m];
+          }
+          indexes[j] = k2/temp;
+          k2 -= temp*indexes[j];
+        }
+
+        for ( int ii = 0; ii < new_domainDim; ii++ )
+        {
+          int sub_index = 0;
+          for ( int mm = (manifoldIndexes[ii].length - 1); mm >= 0; mm-- ) {
+            int product = 1;
+            for ( int nn = 0; nn < mm; nn++ ) {
+              product *= manifoldLengths[ii][nn];
+            }
+            product *= indexes[manifoldIndexes[ii][mm]];
+            sub_index += product;
+          }
+          new_samples[ii][k] = sub_samples[ii][sub_index];
+        }
+      }
+
+      // DRM 02-Feb-2003
+      //new_set = new GriddedSet(new_domain_type, new_samples, new_lengths );
+      //new_set = GriddedSet.create(new_domain_type, new_samples, new_lengths );
+      new_set = GriddedSet.create(new_domain_type, new_samples, new_lengths,
+          null,  // CS null, because already defined in new_domain_type
+          newUnits, newErrors);
+    }
+    Field new_field;
+    if ( helper.flat )
+    {
+      new_field = new FlatField( new_function_type, new_set );
+      int tup_dim =
+        (new_function_type.getFlatRange()).getDimension();
+      double[][] new_values =
+        new double[tup_dim][new_length];
+
+      cnt = 0;
+      double[][] sub_range;
+      for ( int ii = 0; ii < new_range.length; ii++ ) {
+        sub_range = ((FieldImpl) new_range[ii]).getValues(false);
+        int len = sub_range[0].length;
+        for ( int jj = 0; jj < tup_dim; jj++ ) {
+          System.arraycopy(sub_range[jj], 0, new_values[jj], cnt, len);
+        }
+        cnt += len;
+      }
+      // still a problem if a factor Set is doubles (e.g., Time)
+      // WLH 15 Jan 2000
+      ((FlatField) new_field).setSamples(new_values, false);
+    }
+    else
+    {
+      new_field = new FieldImpl( new_function_type, new_set );
+      for ( int ii = 0; ii < new_length; ii++ ){
+        new_field.setSample(ii, new_range[ii]);
+      }
+    }
+
+    return new_field;
+  }
+
+  public Data derivative( RealTuple location, RealType[] d_partial_s,
+                          MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException
+  {
+    int ii, jj, kk, dd, rr, tt, pp, ss;
+    Set domainSet = this.getDomainSet();
+    int domainDim = domainSet.getDimension();
+    int manifoldDimension = domainSet.getManifoldDimension();
+    int n_samples = domainSet.getLength();
+    CoordinateSystem d_coordsys = this.getDomainCoordinateSystem();
+    RealTupleType d_reference = (d_coordsys == null) ? null : d_coordsys.getReference();
+    MathType m_type = null;
+    MathType[] m_types = null;
+    RealType r_type = null;
+    RealType[] r_types = null;
+    TupleType t_type = null;
+    boolean thisDomainFlag = true;
+
+    if ( manifoldDimension != domainDim )
+    {
+      throw new SetException("derivative: manifoldDimension must equal "+
+                             "domain dimension" );
+    }
+    error_mode = Data.NO_ERRORS;
+    int sampling_mode = Data.WEIGHTED_AVERAGE;
+
+    if ( location != null )
+    {
+      thisDomainFlag = false;
+    }
+
+    RealTupleType domainType = ((FunctionType)Type).getDomain();
+    RealType[] r_comps = domainType.getRealComponents();
+    RealType[] r_compsRef = (d_reference == null) ? null : d_reference.getRealComponents();
+
+    MathType RangeType = ((FunctionType)Type).getRange();
+
+    int n_partials;  // number of partial derivatives to compute -*
+
+  //- get all components for this function's domain -*
+    if ( d_partial_s == null )
+    {
+      n_partials = domainDim;
+      d_partial_s = r_comps;
+    }
+    else
+    {
+      n_partials = d_partial_s.length;
+      if ( n_partials > domainDim ) {
+        throw new VisADException("derivative: too many d_partial components");
+      }
+    }
+
+    int[] u_index = new int[n_partials];
+    double[][] u_vectors = new double[n_partials][domainDim];
+
+  //- verify that input RealType-s match the Function's domain -*
+  //- create unit vectors for the d_partial RealTypes -*
+    int found = 0;
+    int foundRef = 0;
+    for ( ii = 0; ii < n_partials; ii++ )
+    {
+      for ( jj = 0; jj < domainDim; jj++ )
+      {
+        u_vectors[ii][jj] = 0d;
+        if ( r_comps[jj].equals(d_partial_s[ii]) )
+        {
+          u_index[ii] = jj;
+          u_vectors[ii][jj] = 1d;
+          found++;
+        }
+        else if ( d_reference != null )
+        {
+          if ( r_compsRef[jj].equals(d_partial_s[ii]) )
+          {
+            u_index[ii] = jj;
+            u_vectors[jj][ii] = 1d;
+            foundRef++;
+          }
+        }
+      }
+    }
+
+    boolean transform;  //- flag indicating coordinate transform is required  --*
+
+    if ( found == 0 )
+    {
+      if ( foundRef == 0 )
+      {
+         throw new VisADException("derivative: d_partial_s not in domain or reference");
+      }
+      else if ( 0 < foundRef && foundRef < n_partials )
+      {
+        throw new VisADException("derivative: d_partial_s must ALL be in function's "+
+                                             "domain or ALL in domain's reference");
+      }
+      else
+      {
+        transform = true;
+      }
+    }
+    else if ( 0 < found && found < n_partials )
+    {
+      throw new VisADException("derivative: d_partial_s must ALL be in function's "+
+                                           "domain or ALL in domain's reference");
+    }
+    else
+    {
+      transform = false;
+    }
+
+    String[][] derivNames = null;
+    Unit[] D_units;
+    MathType[] new_range = new MathType[ n_partials ];
+    MathType[] new_types = new MathType[ n_partials ];
+
+    if ( !transform ) {
+      D_units = domainSet.getSetUnits();
+    }
+    else {
+      D_units = d_reference.getDefaultUnits();
+    }
+
+    if ( derivType_s == null )
+    {
+      for ( ii = 0; ii < n_partials; ii++ )
+      {
+        MathType M_type = Type.cloneDerivative( d_partial_s[ii] );
+        if ( thisDomainFlag ) {
+          new_types[ii] = M_type;
+        }
+        else {
+          new_types[ii] = ((FunctionType)M_type).getRange();
+        }
+      }
+      derivType_s = new_types;
+    }
+    else //- check supplied derivType-s for compatibility  -*
+    {
+      if ( derivType_s.length != n_partials ) {
+        throw new VisADException("derivative: must be a single MathType "+
+                                 "for each domain RealType");
+      }
+      for ( ii = 0; ii < n_partials; ii++ )
+      {
+        if ( thisDomainFlag ) {
+          if ( !Type.equalsExceptName(derivType_s[ii]) ) {
+            throw new TypeException("derivative: incompatible with function range");
+          }
+        }
+        else {
+          if ( !((((FunctionType)Type).getRange()).equalsExceptName(derivType_s[ii])) ) {
+            throw new TypeException("derivative: incompatible with function range");
+          }
+        }
+      }
+    }
+
+  //- compute derivative-s, return FlatField or Tuple of FlatFields, or Data --*
+
+    int[][] neighbors = null;
+    int n_points;
+    int n_index;
+    int m_index;
+    int index;
+    float distance;
+    float step;
+    float f_sum;
+    double d_sum;
+    Data[] p_derivatives = new Data[ n_partials ];
+    ErrorEstimate[] domainErrors = domainSet.getSetErrors();
+    Real deltaDomain;
+    FieldImpl[] new_fields = new FieldImpl[ n_partials ];
+    Data data_0;
+    Data data_1;
+    Data rangeDiff;
+    Data newRange;
+    Data[] rangeValues = null;
+
+    for ( pp = 0; pp < n_partials; pp++ ) {
+      new_fields[pp] = new FieldImpl( (FunctionType)derivType_s[pp], domainSet );
+    }
+
+    if ( isMissing() )   //- Bypass computations and return missing field -*
+    {
+
+  //- Handle LinearSet case separately for efficiency   -*
+    if(( domainSet instanceof LinearSet )&&( thisDomainFlag ))
+    {
+      //- each partial derivative   -*
+      for ( kk = 0; kk < n_partials; kk++ )
+      {
+        RangeType = ((FunctionType)derivType_s[kk]).getRange();
+        //- get manifoldDimension index for this real axis ( LinearSet only )  -*
+        m_index = u_index[kk];
+
+        //- get neigbors and separation along this axis   -*
+        neighbors = domainSet.getNeighbors( m_index );
+        step = (float) (((LinearSet)domainSet).getLinear1DComponent(kk)).getStep();
+
+        //- compute derivative for each sample and each range component   -*
+        for ( ii = 0; ii < n_samples; ii++ )
+        {
+          if ( neighbors[ii][0] == -1) {
+            distance = step;
+            n_index = neighbors[ii][1];
+            index = ii;
+          }
+          else if ( neighbors[ii][1] == -1 ) {
+            distance = step;
+            n_index = ii;
+            index = neighbors[ii][0];
+          }
+          else {
+            distance = 2.f*step;
+            n_index = neighbors[ii][1];
+            index = neighbors[ii][0];
+          }
+
+          data_1 = getSample(n_index);
+          data_0 = getSample(index);
+          deltaDomain = new Real( d_partial_s[kk], distance, D_units[m_index] );
+
+          rangeDiff = data_1.binary( data_0, Data.SUBTRACT, sampling_mode, error_mode);
+          newRange = rangeDiff.binary( deltaDomain, Data.DIVIDE, RangeType,
+                                       sampling_mode, error_mode );
+
+          new_fields[kk].setSample( ii, newRange );
+        }
+      }
+    }
+    else  //- GriddedSet, IrregularSet    --*
+    {
+      float dotproduct;
+      float inv_dotproduct;
+      float[][] weights = null;
+      float sum_weights;
+      float[][] Samples;
+
+      //- compute derivative at this Set's sample locations  --*
+      if ( thisDomainFlag )
+      {
+        neighbors = new int[n_samples][];
+        weights = new float[n_samples][];
+        domainSet.getNeighbors( neighbors, weights );
+        if ( transform )
+        {
+          Samples = domainSet.getSamples(true);
+
+          Samples =
+          CoordinateSystem.transformCoordinates( d_reference, null, null, null,
+                           domainType, d_coordsys, null, null, Samples );
+        }
+        else
+        {
+          Samples = domainSet.getSamples(false);
+        }
+      }
+      //- compute derivative at selected ( probably interpolated locations )  --*
+      else
+      {
+        Data[] new_rangeValues;
+        int[][] new_neighbors;
+        n_samples = 1;
+        Field field;
+        float[][] new_Samples;
+        float[][] evalSamples;
+        float[][] org_Samples = domainSet.getSamples(false);
+
+        field = resample( new SingletonSet(location, null, null, null ),
+                          Data.WEIGHTED_AVERAGE, error_mode );
+
+        evalSamples = (field.getDomainSet()).getSamples(false);
+        neighbors = new int[n_samples][];
+        weights = new float[n_samples][];
+
+        ((SimpleSet)domainSet).valueToInterp( evalSamples, neighbors, weights );
+
+        n_points = neighbors[0].length;
+        new_neighbors = new int[n_samples][ n_points ];
+
+        new_rangeValues = new Data[ n_points + 1 ];
+        new_Samples = new float[ domainDim ][ n_points + 1 ];
+        for ( ii = 0; ii < domainDim; ii++ )
+        {
+          new_Samples[ii][0] = evalSamples[ii][0];
+        }
+        new_rangeValues[0] = field.getSample(0);
+        for ( kk = 0; kk < n_points; kk++ )
+        {
+          new_neighbors[0][kk] = kk + 1;
+          new_rangeValues[kk+1] = getSample( neighbors[0][kk] );
+          for ( ii = 0; ii < domainDim; ii++ )
+          {
+            new_Samples[ii][kk+1] = org_Samples[ii][ neighbors[0][kk] ];
+          }
+        }
+
+        neighbors = new_neighbors;
+        rangeValues = new_rangeValues;
+        Samples = new_Samples;
+        if ( transform )
+        {
+          Samples =
+          CoordinateSystem.transformCoordinates( d_reference, null, null, null,
+                           domainType, d_coordsys, null, null, Samples );
+        }
+      }
+
+      //- compute derivatives for each sample   --*
+      for ( ii = 0; ii < n_samples; ii++ )
+      {
+        n_points = neighbors[ii].length;
+        Data[] rangeDiff_s = new Data[ n_points ];
+        Data p_derivative = null;
+        double[][] uvecPoint = new double[ n_points ][ domainDim ];
+        data_0 = (thisDomainFlag) ? getSample( ii ) : rangeValues[ii];
+        float factor;
+
+        //- neighbors loop   -*
+        for ( kk = 0; kk < n_points; kk++ )
+        {
+          for ( dd = 0; dd < domainDim; dd++ ) {
+            uvecPoint[kk][dd] = Samples[dd][ neighbors[ii][kk] ] - Samples[dd][ii];
+          }
+
+            data_1 = (thisDomainFlag) ? getSample( neighbors[ii][kk] ) :
+                                        rangeValues[ neighbors[ii][kk] ];
+
+            rangeDiff_s[kk] = data_1.binary( data_0, Data.SUBTRACT, sampling_mode,
+                                             error_mode );
+        }
+
+        //- Interpolate for each partial derivative  -*
+        boolean first = true;
+        for ( pp = 0; pp < n_partials; pp++ )
+        {
+          m_index = u_index[pp];
+          RangeType = ((FunctionType)derivType_s[pp]).getRange();
+          sum_weights = 0f;
+          for ( kk = 0; kk < n_points; kk++ )
+          {
+            dotproduct = 0;
+
+            for ( dd = 0; dd < domainDim; dd++ )
+            {
+              dotproduct += uvecPoint[kk][dd]*u_vectors[pp][dd];
+            }
+
+            inv_dotproduct = 1f/dotproduct;
+
+            if ( ! Float.isInfinite(inv_dotproduct) )
+            {
+              sum_weights += weights[ii][kk];
+              factor = inv_dotproduct*weights[ii][kk];
+              rangeDiff_s[kk] = rangeDiff_s[kk].binary( new Real( factor), Data.MULTIPLY,
+                                                        sampling_mode, error_mode );
+              if ( first ) {
+                p_derivative = rangeDiff_s[kk];
+                first = false;
+              }
+              else {
+                p_derivative = p_derivative.binary( rangeDiff_s[kk], Data.ADD, sampling_mode,
+                                                    error_mode );
+              }
+            }
+          }
+          Real real = new Real( d_partial_s[pp], sum_weights, D_units[ m_index] );
+          p_derivative = p_derivative.binary( real, Data.DIVIDE,
+                                              RangeType, sampling_mode, error_mode);
+
+          new_fields[pp].setSample( ii, p_derivative );
+        }
+      }
+    }
+    } //- missing range branch --*
+
+    if ( n_partials == 1 )
+    {
+      return new_fields[0];
+    }
+    else
+    {
+      return new Tuple( new_fields );
+    }
+  }
+
+  public Data derivative( int error_mode )
+         throws VisADException, RemoteException
+  {
+    MathType[] derivType_s = null;
+    RealType[] d_partial_s = null;
+    return this.derivative( null, d_partial_s, derivType_s, error_mode );
+  }
+
+  public Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException
+  {
+    return this.derivative( null, null, derivType_s, error_mode );
+  }
+
+  public Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException
+  {
+    MathType[] derivType_s = null;
+    RealType[] d_partial_s = new RealType[1];
+    d_partial_s[0] = d_partial;
+
+    return (Function) this.derivative( null, d_partial_s, derivType_s, error_mode );
+  }
+
+  public Function derivative( RealType d_partial, MathType derivType, int error_mode)
+         throws VisADException, RemoteException
+  {
+    MathType[] derivType_s = new MathType[1];
+    RealType[] d_partial_s = new RealType[1];
+    derivType_s[0] = derivType;
+    d_partial_s[0] = d_partial;
+
+    return (Function) this.derivative( null, d_partial_s, derivType_s, error_mode );
+  }
+
+  /** 
+   * Resample range values of this Field to domain samples in set either
+   * byt nearest neighbor or multi-linear interpolation.
+   * NOTE: this code is very similar to resample in FlatField.java 
+   * @param set            finite sampling values for the function.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.  NOTE: may
+   *         return this (i.e., not a copy).
+   * @throws  VisADException   unable to resample function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+
+    /* NB: resampling is done in this method for a float domain.  If
+     * you make changes to this method, make the corresponding changes
+     * in resampleDouble if necessary.
+     */
+    Set domainSet = getDomainSet();
+
+    if (domainSet.equals(set)) {
+      // nothing to do
+      return this;
+    }
+
+    MathType range_type = ((FunctionType) Type).getRange();
+    RealTupleType domain_type = ((SetType) set.getType()).getDomain();
+    FunctionType func_type = new FunctionType(domain_type, range_type);
+    Field field = new FieldImpl(func_type, set);
+    // Field field = new FieldImpl((FunctionType) Type, set);
+    if (isMissing()) return field;
+
+    int dim = domainSet.getDimension();
+    if (dim != set.getDimension()) {
+      throw new SetException("FieldImpl.resample: bad Set Dimension");
+    }
+
+    // Resampling in this method is done if floats so if we have
+    // a double domain, call resampleDouble. 
+    if (domainSet instanceof GriddedDoubleSet) {
+      return resampleDouble(set, sampling_mode, error_mode);
+    }
+
+    CoordinateSystem coord_sys = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors =
+      (error_mode == NO_ERRORS) ? new ErrorEstimate[dim] : set.getSetErrors();
+
+    // create an array containing all indices of 'this'
+    int length = set.getLength();
+    int[] wedge = set.getWedge();
+
+    // array of Data objects to receive resampled Range objects
+    Data[] range = new Data[length];
+
+    // get values from wedge and possibly transform coordinates
+    float[][] vals = set.indexToValue(wedge);
+    // holder for sampling errors of transformed set - these are
+    // only useful to help estmate range errors due to resampling
+    ErrorEstimate[] errors_out = new ErrorEstimate[dim];
+    float[][] oldvals = vals;
+    try {  // this is only to throw a more meaningful message
+      vals = CoordinateSystem.transformCoordinates(
+                      ((FunctionType) Type).getDomain(), 
+                      getDomainCoordinateSystem(),
+                      getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(), coord_sys,
+                      units, errors, vals);
+    } catch (UnitException ue) {
+        throw new VisADException("Sampling set is not compatible with domain");
+    }
+
+    boolean coord_transform = !(vals == oldvals);
+
+    // check whether we need to do sampling error calculations
+    boolean sampling_errors = (error_mode != NO_ERRORS);
+    if (sampling_errors) {
+      for (int i=0; i<dim; i++) {
+        if (errors_out[i] == null) sampling_errors = false;
+      }
+    }
+    Data[] sampling_partials = new Data[dim];
+    float[][] error_values;
+    double[] means = new double[dim];
+
+    Data[]Range = getRange ();
+    if (sampling_mode == WEIGHTED_AVERAGE && domainSet instanceof SimpleSet) {
+      // resample by interpolation
+      int[][] indices = new int[length][];
+      float[][] coefs = new float[length][];
+      ((SimpleSet) domainSet).valueToInterp(vals, indices, coefs);
+      for (int i=0; i<length; i++) {
+        int len;
+        len = (indices[i] == null) ? 0 : indices[i].length;
+        if (len > 0) {
+          Data r = null;
+          // WLH
+          for (int k=0; k<len; k++) {
+            Data RangeIK;
+            synchronized (RangeLock) {
+              RangeIK = Range[indices[i][k]];
+            }
+            if (RangeIK != null) {
+              r = (r == null) ? RangeIK.multiply(new Real(coefs[i][k])) :
+                                r.add(RangeIK.multiply(new Real(coefs[i][k])));
+            }
+            else {
+              r = null;
+              break;
+            }
+          }
+
+          // SRE 2002-02-13
+          if (r != null) {
+            r = r.changeMathType(((FunctionType)Type).getRange());
+          }
+
+          range[wedge[i]] = r;
+        }
+        else {
+          // set range[wedge[i]] to a missing Data object
+          range[wedge[i]] = ((FunctionType) Type).getRange().missingData();
+        }
+
+        if (sampling_errors && !range[wedge[i]].isMissing()) {
+          for (int j=0; j<dim; j++) means[j] = vals[j][i];
+          error_values = Set.doubleToFloat(
+                           ErrorEstimate.init_error_values(errors_out, means) );
+          int[][] error_indices = new int[2 * dim][];
+          float[][] error_coefs = new float[2 * dim][];
+          coefs = new float[2 * dim][];
+          ((SimpleSet) domainSet).valueToInterp(error_values, error_indices,
+                                                error_coefs);
+
+          for (int j=0; j<dim; j++) {
+            Data a = null;
+            Data b = null;
+            len = error_indices[2*j].length;
+            if (len > 0) {
+              for (int k=0; k<len; k++) {
+                Data RangeIK;
+                synchronized (RangeLock) {
+                  RangeIK = Range[error_indices[2*j][k]];
+                }
+                if (RangeIK != null) {
+                  a = (a == null) ?
+                      RangeIK.multiply(new Real(error_coefs[2*j][k])) :
+                      a.add(RangeIK.multiply(new Real(error_coefs[2*j][k])));
+                }
+                else {
+                  a = null;
+                  break;
+                }
+              }
+            }
+            len = error_indices[2*j+1].length;
+            if (len > 0) {
+              for (int k=0; k<len; k++) {
+                Data RangeIK;
+                synchronized (RangeLock) {
+                  RangeIK = Range[error_indices[2*j+1][k]];
+                }
+                if (RangeIK != null) {
+                  b = (b == null) ?
+                      RangeIK.multiply(new Real(error_coefs[2*j+1][k])) :
+                      b.add(RangeIK.multiply(new Real(error_coefs[2*j+1][k])));
+                }
+                else {
+                  b = null;
+                  break;
+                }
+              }
+            }
+            if (a == null || b == null) {
+              sampling_partials[j] = null;
+            }
+            else {
+              sampling_partials[j] = b.subtract(a).abs();
+            }
+          }
+
+          Data error = null;
+          if (error_mode == Data.INDEPENDENT) {
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j].multiply(sampling_partials[j]);
+              error = (error == null) ? e : error.add(e);
+            }
+            error = error.sqrt();
+          }
+          else { // error_mode == Data.DEPENDENT
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j];
+              error = (error == null) ? e : error.add(e);
+            }
+          }
+          range[wedge[i]] =
+            range[wedge[i]].adjustSamplingError(error, error_mode);
+        } // end if (sampling_errors && !range[wedge[i]].isMissing())
+      } // end for (int i=0; i<length; i++)
+    }
+    else { // Mode is NEAREST_NEIGHBOR or set is not GriddedSet
+      // simple resampling
+      int[] indices = domainSet.valueToIndex(vals);
+      for (int i=0; i<length; i++) {
+        synchronized (RangeLock) {
+          range[wedge[i]] = (indices[i] >= 0 && Range[indices[i]] != null) ?
+                            Range[indices[i]] :
+                            ((FunctionType) Type).getRange().missingData();
+        }
+
+        if (sampling_errors && !range[wedge[i]].isMissing()) {
+          for (int j=0; j<dim; j++) means[j] = vals[j][i];
+          error_values = Set.doubleToFloat(
+                           ErrorEstimate.init_error_values(errors_out, means) );
+          int[] error_indices = domainSet.valueToIndex(error_values);
+          for (int j=0; j<dim; j++) {
+            synchronized (RangeLock) {
+              if (error_indices[2*j] < 0 || Range[error_indices[2*j]] == null ||
+                  error_indices[2*j+1] < 0 || Range[error_indices[2*j+1]] == null) {
+                sampling_partials[j] = null;
+              }
+              else {
+                sampling_partials[j] = Range[error_indices[2*j+1]].
+                                         subtract(Range[error_indices[2*j]]).abs();
+              }
+            }
+          }
+
+          Data error = null;
+          if (error_mode == Data.INDEPENDENT) {
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j].multiply(sampling_partials[j]);
+              error = (error == null) ? e : error.add(e);
+            }
+            error = error.sqrt();
+          }
+          else { // error_mode == Data.DEPENDENT
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j];
+              error = (error == null) ? e : error.add(e);
+            }
+          }
+          range[wedge[i]] =
+            range[wedge[i]].adjustSamplingError(error, error_mode);
+        } // end if (sampling_errors && !range[wedge[i]].isMissing())
+      } // end for (int i=0; i<length; i++)
+    }
+
+    if (coord_transform) {
+      // domain coordinates were transformed, so make corresponding
+      // vector transform to any RealVectorType-s in range
+      MathType RangeType = ((FunctionType) Type).getRange();
+      if (RangeType instanceof RealVectorType) {
+        int n = vals.length;
+        float[][] inloc = new float[n][1];
+        float[][] outloc = new float[n][1];
+        for (int i=0; i<length; i++) {
+          for (int k=0; k<n; k++) inloc[k][0] = oldvals[k][i];
+          for (int k=0; k<n; k++) outloc[k][0] = vals[k][i];
+          range[i] = ((RealVectorType) RangeType).transformVectors(
+                      ((FunctionType) Type).getDomain(),
+                      getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(),
+                      coord_sys, units,
+                      ((RealTuple) range[i]).getCoordinateSystem(),
+                      inloc, outloc, (RealTuple) range[i]);
+        }
+      }
+      else if (RangeType instanceof TupleType &&
+               !(RangeType instanceof RealTupleType)) {
+        int m = ((TupleType) RangeType).getDimension();
+        boolean any_vector = false;
+        for (int j=0; j<m; j++) {
+          if (((TupleType) RangeType).getComponent(j) instanceof RealVectorType) {
+            any_vector = true;
+          }
+        }
+        if (any_vector) {
+          int n = vals.length;
+          float[][] inloc = new float[n][1];
+          float[][] outloc = new float[n][1];
+          Data[] datums = new Data[m];
+          for (int i=0; i<length; i++) {
+            for (int k=0; k<n; k++) inloc[k][0] = oldvals[k][i];
+            for (int k=0; k<n; k++) outloc[k][0] = vals[k][i];
+            for (int j=0; j<m; j++) {
+              MathType comp_type = ((TupleType) RangeType).getComponent(j);
+              if (comp_type instanceof RealVectorType) {
+                RealTuple component =
+                  (RealTuple) ((TupleIface) range[i]).getComponent(j);
+                datums[j] = ((RealVectorType) comp_type).transformVectors(
+                            ((FunctionType) Type).getDomain(),
+                            getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                            ((SetType) set.getType()).getDomain(),
+                            coord_sys, units, component.getCoordinateSystem(),
+                            inloc, outloc, component);
+              }
+              else {
+                datums[j] = ((TupleIface) range[i]).getComponent(j);
+              }
+            }
+            range[i] = new Tuple(datums);
+
+          }
+        }
+      }
+    } // end if (coord_transform)
+    ((FieldImpl)field).setSamples(range, false, false);
+    return field;
+  }
+
+  /** 
+   * Resample range values of this Field to domain samples in set either
+   * byt nearest neighbor or multi-linear interpolation.
+   * NOTE: this code is very similar to resample in FlatField.java 
+   * @param set            finite sampling values for the function.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.  NOTE: may
+   *         return this (i.e., not a copy).
+   * @throws  VisADException   unable to resample function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public Field resampleDouble(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+
+    /* NB: resampling is done in this method for a double domain.  If
+     * you make changes to this method, make the corresponding changes
+     * in resample if necessary.
+     */
+    Set domainSet = getDomainSet();
+
+    if (domainSet.equals(set)) {
+      // nothing to do
+      return this;
+    }
+
+    MathType range_type = ((FunctionType) Type).getRange();
+    RealTupleType domain_type = ((SetType) set.getType()).getDomain();
+    FunctionType func_type = new FunctionType(domain_type, range_type);
+    Field field = new FieldImpl(func_type, set);
+    // Field field = new FieldImpl((FunctionType) Type, set);
+    if (isMissing()) return field;
+
+    int dim = domainSet.getDimension();
+    if (dim != set.getDimension()) {
+      throw new SetException("FieldImpl.resample: bad Set Dimension");
+    }
+
+    if (!(domainSet instanceof GriddedDoubleSet)) {
+      return resample(set, sampling_mode, error_mode);
+    }
+
+    CoordinateSystem coord_sys = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors =
+      (error_mode == NO_ERRORS) ? new ErrorEstimate[dim] : set.getSetErrors();
+
+    // create an array containing all indices of 'this'
+    int length = set.getLength();
+    int[] wedge = set.getWedge();
+
+    // array of Data objects to receive resampled Range objects
+    Data[] range = new Data[length];
+
+    // get values from wedge and possibly transform coordinates
+    double[][] vals = set.indexToDouble(wedge);
+    // holder for sampling errors of transformed set - these are
+    // only useful to help estmate range errors due to resampling
+    ErrorEstimate[] errors_out = new ErrorEstimate[dim];
+    double[][] oldvals = vals;
+    try {  // this is only to throw a more meaningful message
+      vals = CoordinateSystem.transformCoordinates(
+                      ((FunctionType) Type).getDomain(), 
+                      getDomainCoordinateSystem(),
+                      getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(), coord_sys,
+                      units, errors, vals);
+    } catch (UnitException ue) {
+        throw new VisADException("Sampling set is not compatible with domain");
+    }
+
+    boolean coord_transform = !(vals == oldvals);
+
+    // check whether we need to do sampling error calculations
+    boolean sampling_errors = (error_mode != NO_ERRORS);
+    if (sampling_errors) {
+      for (int i=0; i<dim; i++) {
+        if (errors_out[i] == null) sampling_errors = false;
+      }
+    }
+    Data[] sampling_partials = new Data[dim];
+    double[][] error_values;
+    double[] means = new double[dim];
+
+    Data[]Range = getRange ();
+    if (sampling_mode == WEIGHTED_AVERAGE) {
+      // resample by interpolation
+      int[][] indices = new int[length][];
+      double[][] coefs = new double[length][];
+      ((GriddedDoubleSet) domainSet).doubleToInterp(vals, indices, coefs);
+      for (int i=0; i<length; i++) {
+        int len;
+        len = (indices[i] == null) ? 0 : indices[i].length;
+        if (len > 0) {
+          Data r = null;
+          // WLH
+          for (int k=0; k<len; k++) {
+            Data RangeIK;
+            synchronized (RangeLock) {
+              RangeIK = Range[indices[i][k]];
+            }
+            if (RangeIK != null) {
+              r = (r == null) ? RangeIK.multiply(new Real(coefs[i][k])) :
+                                r.add(RangeIK.multiply(new Real(coefs[i][k])));
+            }
+            else {
+              r = null;
+              break;
+            }
+          }
+
+          // SRE 2002-02-13
+          if (r != null) {
+            r = r.changeMathType(((FunctionType)Type).getRange());
+          }
+
+          range[wedge[i]] = r;
+        }
+        else {
+          // set range[wedge[i]] to a missing Data object
+          range[wedge[i]] = ((FunctionType) Type).getRange().missingData();
+        }
+
+        if (sampling_errors && !range[wedge[i]].isMissing()) {
+          for (int j=0; j<dim; j++) means[j] = vals[j][i];
+          error_values = ErrorEstimate.init_error_values(errors_out, means);
+          int[][] error_indices = new int[2 * dim][];
+          double[][] error_coefs = new double[2 * dim][];
+          coefs = new double[2 * dim][];
+          ((GriddedDoubleSet) domainSet).doubleToInterp(error_values, error_indices,
+                                                error_coefs);
+
+          for (int j=0; j<dim; j++) {
+            Data a = null;
+            Data b = null;
+            len = error_indices[2*j].length;
+            if (len > 0) {
+              for (int k=0; k<len; k++) {
+                Data RangeIK;
+                synchronized (RangeLock) {
+                  RangeIK = Range[error_indices[2*j][k]];
+                }
+                if (RangeIK != null) {
+                  a = (a == null) ?
+                      RangeIK.multiply(new Real(error_coefs[2*j][k])) :
+                      a.add(RangeIK.multiply(new Real(error_coefs[2*j][k])));
+                }
+                else {
+                  a = null;
+                  break;
+                }
+              }
+            }
+            len = error_indices[2*j+1].length;
+            if (len > 0) {
+              for (int k=0; k<len; k++) {
+                Data RangeIK;
+                synchronized (RangeLock) {
+                  RangeIK = Range[error_indices[2*j+1][k]];
+                }
+                if (RangeIK != null) {
+                  b = (b == null) ?
+                      RangeIK.multiply(new Real(error_coefs[2*j+1][k])) :
+                      b.add(RangeIK.multiply(new Real(error_coefs[2*j+1][k])));
+                }
+                else {
+                  b = null;
+                  break;
+                }
+              }
+            }
+            if (a == null || b == null) {
+              sampling_partials[j] = null;
+            }
+            else {
+              sampling_partials[j] = b.subtract(a).abs();
+            }
+          }
+
+          Data error = null;
+          if (error_mode == Data.INDEPENDENT) {
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j].multiply(sampling_partials[j]);
+              error = (error == null) ? e : error.add(e);
+            }
+            error = error.sqrt();
+          }
+          else { // error_mode == Data.DEPENDENT
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j];
+              error = (error == null) ? e : error.add(e);
+            }
+          }
+          range[wedge[i]] =
+            range[wedge[i]].adjustSamplingError(error, error_mode);
+        } // end if (sampling_errors && !range[wedge[i]].isMissing())
+      } // end for (int i=0; i<length; i++)
+    }
+    else { // Mode is NEAREST_NEIGHBOR or set is not GriddedSet
+      // simple resampling
+      int[] indices = domainSet.doubleToIndex(vals);
+      for (int i=0; i<length; i++) {
+        synchronized (RangeLock) {
+          range[wedge[i]] = (indices[i] >= 0 && Range[indices[i]] != null) ?
+                            Range[indices[i]] :
+                            ((FunctionType) Type).getRange().missingData();
+        }
+
+        if (sampling_errors && !range[wedge[i]].isMissing()) {
+          for (int j=0; j<dim; j++) means[j] = vals[j][i];
+          error_values = ErrorEstimate.init_error_values(errors_out, means);
+          int[] error_indices = domainSet.doubleToIndex(error_values);
+          for (int j=0; j<dim; j++) {
+            synchronized (RangeLock) {
+              if (error_indices[2*j] < 0 || Range[error_indices[2*j]] == null ||
+                  error_indices[2*j+1] < 0 || Range[error_indices[2*j+1]] == null) {
+                sampling_partials[j] = null;
+              }
+              else {
+                sampling_partials[j] = Range[error_indices[2*j+1]].
+                                         subtract(Range[error_indices[2*j]]).abs();
+              }
+            }
+          }
+
+          Data error = null;
+          if (error_mode == Data.INDEPENDENT) {
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j].multiply(sampling_partials[j]);
+              error = (error == null) ? e : error.add(e);
+            }
+            error = error.sqrt();
+          }
+          else { // error_mode == Data.DEPENDENT
+            for (int j=0; j<dim; j++) {
+              Data e = sampling_partials[j];
+              error = (error == null) ? e : error.add(e);
+            }
+          }
+          range[wedge[i]] =
+            range[wedge[i]].adjustSamplingError(error, error_mode);
+        } // end if (sampling_errors && !range[wedge[i]].isMissing())
+      } // end for (int i=0; i<length; i++)
+    }
+
+    if (coord_transform) {
+      // domain coordinates were transformed, so make corresponding
+      // vector transform to any RealVectorType-s in range
+      MathType RangeType = ((FunctionType) Type).getRange();
+      if (RangeType instanceof RealVectorType) {
+        int n = vals.length;
+        double[][] inloc = new double[n][1];
+        double[][] outloc = new double[n][1];
+        for (int i=0; i<length; i++) {
+          for (int k=0; k<n; k++) inloc[k][0] = oldvals[k][i];
+          for (int k=0; k<n; k++) outloc[k][0] = vals[k][i];
+          range[i] = ((RealVectorType) RangeType).transformVectors(
+                      ((FunctionType) Type).getDomain(),
+                      getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(),
+                      coord_sys, units,
+                      ((RealTuple) range[i]).getCoordinateSystem(),
+                      inloc, outloc, (RealTuple) range[i]);
+        }
+      }
+      else if (RangeType instanceof TupleType &&
+               !(RangeType instanceof RealTupleType)) {
+        int m = ((TupleType) RangeType).getDimension();
+        boolean any_vector = false;
+        for (int j=0; j<m; j++) {
+          if (((TupleType) RangeType).getComponent(j) instanceof RealVectorType) {
+            any_vector = true;
+          }
+        }
+        if (any_vector) {
+          int n = vals.length;
+          double[][] inloc = new double[n][1];
+          double[][] outloc = new double[n][1];
+          Data[] datums = new Data[m];
+          for (int i=0; i<length; i++) {
+            for (int k=0; k<n; k++) inloc[k][0] = oldvals[k][i];
+            for (int k=0; k<n; k++) outloc[k][0] = vals[k][i];
+            for (int j=0; j<m; j++) {
+              MathType comp_type = ((TupleType) RangeType).getComponent(j);
+              if (comp_type instanceof RealVectorType) {
+                RealTuple component =
+                  (RealTuple) ((TupleIface) range[i]).getComponent(j);
+                datums[j] = ((RealVectorType) comp_type).transformVectors(
+                            ((FunctionType) Type).getDomain(),
+                            getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                            ((SetType) set.getType()).getDomain(),
+                            coord_sys, units, component.getCoordinateSystem(),
+                            inloc, outloc, component);
+              }
+              else {
+                datums[j] = ((TupleIface) range[i]).getComponent(j);
+              }
+            }
+            range[i] = new Tuple(datums);
+
+          }
+        }
+      }
+    } // end if (coord_transform)
+    ((FieldImpl)field).setSamples(range, false, false);
+    return field;
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    if (isMissing()) return shadow;
+
+    ShadowRealTupleType domain_type = ((ShadowFunctionType) type).getDomain();
+    int n = domain_type.getDimension();
+    double[][] ranges = new double[2][n];
+    // DomainSet.computeRanges handles Reference
+    shadow = getDomainSet().computeRanges(domain_type, shadow, ranges, true);
+    ShadowType rtype = ((ShadowFunctionType) type).getRange();
+
+
+    if (MyRange != null) {
+        for (int i=0; i<MyRange.length; i++) {
+            synchronized (RangeLock) {
+                if (MyRange[i] != null) shadow = MyRange[i].computeRanges(rtype, shadow);
+            }
+        }
+    }
+    return shadow;
+  }
+
+
+  /** return a Field that clones this, except its ErrorEstimate-s
+      are adjusted for sampling errors in error */
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    if (isMissing() || error == null || error.isMissing()) return this;
+    Field field = new FieldImpl((FunctionType) Type, getDomainSet());
+    if (isMissing()) return field;
+    Field new_error =
+      ((Field) error).resample(getDomainSet(), NEAREST_NEIGHBOR, NO_ERRORS);
+
+
+
+    //Only do this if we have a MyRange
+    Data[] range = new Data[getLength()];
+    for (int i=0; i<getLength(); i++) {
+        synchronized (RangeLock) {
+            if (MyRange != null && MyRange[i] != null) {
+                range[i] = MyRange[i].adjustSamplingError(new_error.getSample(i), error_mode);
+            } else {
+                range[i]=null;
+            }
+        }
+    }
+    field.setSamples(range, true);
+    return field;
+  }
+
+  public boolean isFlatField() {
+    return false;
+  }
+
+  /**
+   * A wrapper around {@link #getLength() getLength} for JPython.
+   */
+  public int __len__() throws VisADException, RemoteException {
+    return getLength();
+  }
+
+  /**
+   * A wrapper around {@link #getSample(int) getSample} for JPython.
+   */
+  public Data __getitem__(int index) throws VisADException, RemoteException {
+    return getSample(index);
+  }
+
+  /**
+   * A wrapper around {@link #setSample(int, Data) setSample} for JPython.
+   */
+  public void __setitem__(int index, Data data)
+         throws VisADException, RemoteException {
+    if (data instanceof Real && ( (Real)data).getUnit() == null) {
+      __setitem__(index, ( (Real)data).getValue());
+    } else {
+      setSample(index, data);
+    }
+  }
+
+  /**
+   * A wrapper around {@link #setSample(int, Data) setSample} for JPython.
+   */
+  public void __setitem__(int index, double data)
+         throws VisADException, RemoteException {
+    RealType real = null;
+    boolean tuple = false;
+    MathType range = ((FunctionType) getType()).getRange();
+    if (range instanceof RealType) {
+      real = (RealType) range;
+    }
+    else if (range instanceof RealTupleType) {
+      if (((RealTupleType) range).getDimension() == 1) {
+        real = (RealType) ((RealTupleType) range).getComponent(0);
+        tuple = true;
+      }
+    }
+    if (real != null) {
+      Real r = new Real(real, data);
+      if (tuple) {
+        setSample(index, new RealTuple(new Real[] {r}));
+      }
+      else {
+        setSample(index, r);
+      }
+    }
+    else {
+      System.out.println("FieldImpl.__setitem__ bad type");
+    }
+  }
+
+  /**
+   * <p>Clones this instance.  The {@link MathType}, domain {@link Set}, and
+   * {@link CoordinateSystem} are shallow copied.  Each range value, however,
+   * has its <code>clone()</code> method invoked.</p>
+   *
+   * <p> Note that it is possible to simultaneously modify the domain-set of
+   * both this instance and the clone by modifying the values in the array
+   * returned by invoking <code>getSamples(false)</code> on the domain-set of
+   * either this instance or the clone.  Don't do this unless you enjoy 
+   * debugging.</p>
+   *
+   * @return                            A clone of this instance.
+   * @throws CloneNotSupportedException if cloning isn't supported by a 
+   *                                    range-value.
+   */
+  public Object clone() throws CloneNotSupportedException {
+    FieldImpl clone = (FieldImpl)super.clone();
+
+    synchronized(RangeLock) {
+        if (MyRange != null) {
+            clone.MyRange = new Data[MyRange.length];
+            for (int i = 0; i < MyRange.length; i++) {
+                if (MyRange[i] != null) {
+                    try {
+                        /*
+                         * Data.dataClone() is invoked because 
+                         * Data.clone() doesn't and can't exist.
+                         */
+                        clone.MyRange[i] = (Data)MyRange[i].dataClone();
+                    }
+                    catch (RemoteException ex) {
+                        throw new RuntimeException(ex.toString());
+                    }
+                }
+            }
+        }
+    }
+
+    return clone;
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    StringBuffer s = new StringBuffer(pre + "FieldImpl\n" + pre + "  Type: " +
+                                      Type.toString() + "\n");
+    Set domainSet = getDomainSet();
+    if (domainSet != null) {
+      s.append(pre + "  DomainSet:\n" + domainSet.longString(pre + "    "));
+    }
+    else {
+      s.append(pre + "  DomainSet: undefined\n");
+    }
+    if (isMissing()) {
+      s.append("  missing\n");
+      return s.toString();
+    }
+
+    if (MyRange !=null) {
+      for (int i=0; i<getLength(); i++) {
+        s.append(pre + "  Range value " + i + ":\n" + ((MyRange[i] == null) ?
+                 (pre + "missing\n") : MyRange[i].longString(pre + "    ")));
+      }
+    }
+    return s.toString();
+  }
+
+  public boolean equals(Object obj) {
+    if (obj == null || !(obj instanceof FieldImpl)) {
+      return false;
+    }
+
+    FieldImpl fi = (FieldImpl )obj;
+
+    if (!getType().equals(fi.getType())) {
+      return false;
+    }
+
+    if (getLength() != fi.getLength()) {
+      return false;
+    }
+
+    if (MissingFlag != fi.MissingFlag) {
+      return false;
+    }
+    Set domainSet = getDomainSet();
+
+    if (domainSet == null || fi.getDomainSet() == null) {
+      if (domainSet != null || fi.getDomainSet() != null) {
+        return false;
+      }
+    } else {
+      if (!domainSet.equals(fi.getDomainSet())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+/**
+<PRE>
+   Here's how to use this:
+
+   for (Enumeration e = field.domainEnumeration() ; e.hasMoreElements(); ) {
+     RealTuple domain_sample = (RealTuple) e.nextElement();
+     Data range = field.evaluate(domain_sample);
+   }
+</PRE>
+*/
+  public Enumeration domainEnumeration()
+         throws VisADException, RemoteException {
+    return new FieldEnumerator(this);
+  }
+
+}
+
+class FieldEnumerator implements Enumeration {
+  Field field;
+  int[] index;
+  int dimension;
+  RealTupleType type;
+  CoordinateSystem coordinateSystem;
+  RealType[] types;
+  Unit[] units;
+
+  FieldEnumerator(Field f) throws VisADException, RemoteException {
+    field = f;
+    if (field.getDomainSet() == null) {
+      throw new FieldException("FieldImplEnumerator: DomainSet undefined");
+    }
+    index = new int[1];
+    index[0] = 0;
+    dimension = field.getDomainSet().getDimension();
+    type = ((FunctionType) field.getType()).getDomain();
+    /*
+     * The setting of the CoordinateSytem is non-trivial because a the default
+     * CoordinateSystem of the domain of a FieldImpl can be null while the
+     * CoordinateSystem of the domain set of the FieldImpl can be non-null --
+     * giving rise to the possibility of returning a domain RealTuple with a
+     * non-null CoordinateSystem even though the corresponding RealTupleType
+     * doesn't have one. (see nextElement() below.)
+     */
+    coordinateSystem = type.getCoordinateSystem();
+    if (coordinateSystem != null)
+      coordinateSystem = field.getDomainCoordinateSystem();
+    types = new RealType[dimension];
+    units = field.getDomainUnits();
+    for (int j=0; j<dimension; j++) {
+      types[j] = (RealType) type.getComponent(j);
+    }
+  }
+
+  public boolean hasMoreElements() {
+    try {
+      return index[0] < field.getLength();
+    }
+    catch (RemoteException e) {
+      return false;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /*
+   * According to the semantics for java.util.Enumeration, this method can only
+   * throw a NoSuchElementException; consequently, all other exceptions must be
+   * trapped and converted.
+   */
+  public Object nextElement() throws NoSuchElementException {
+    try {
+      if (index[0] < field.getLength()) {
+        float[][] vals = field.getDomainSet().indexToValue(index);
+        index[0]++;
+        Real[] reals = new Real[dimension];
+        for (int j=0; j<dimension; j++) {
+          reals[j] = new Real(types[j], (double) vals[j][0], units[j]);
+        }
+        /*
+         * The actual CoordinateSystem of the domain set is used only if the
+         * domain's RealTupleType has a non-null CoordinateSystem; otherwise,
+         * an exception would be thrown as of version 1.34.
+         */
+        return new RealTuple(type, reals, coordinateSystem);
+      }
+      else {
+        throw new NoSuchElementException(
+          "FieldImplEnumerator.nextElement: no more elements");
+      }
+    }
+    catch (VisADException e) {
+      throw new NoSuchElementException("FieldImplEnumerator.nextElement: " + e);
+    }
+    catch (RemoteException e) {
+      throw new NoSuchElementException("FieldImplEnumerator.nextElement: " + e);
+    }
+  }
+}
+
+/**
+  class NDhelper
+  {
+    int n_dims;
+    int d = 0;
+    int cnt = 0;
+    int[] lengths;
+    int[] dim_product;
+    int[] sub_domain;
+    int[] indexes;
+    int[][] all;
+    int[] work;
+    int product;
+
+    NDhelper( int[] lengths, int[] dim_product, int[] sub_domain )
+    {
+      this.lengths = lengths;
+      this.n_dims = lengths.length;
+      this.indexes = new int[ n_dims ];
+      this.dim_product = dim_product;
+      this.sub_domain = sub_domain;
+      product = 1;
+      for ( int i = 0; i < n_dims; i++ ) {
+        product *= lengths[i];
+      }
+      work = new int[product];
+
+      permute();
+    }
+    void permute()
+    {
+      if ( d == indexes.length )
+      {
+        work[cnt] = 0;
+        for ( int k = 0; k < indexes.length; k++ ) {
+          work[cnt] += dim_product[sub_domain[k]]*indexes[(n_dims-1) - k];
+        }
+        cnt++;
+      }
+      else
+      {
+        for ( int i = 0; i < lengths[(n_dims-1) - d]; i++ )
+        {
+          indexes[d] = i;
+          d++;
+          permute();
+          d--;
+        }
+      }
+    }
+    public int[] getPermutations()
+    {
+      return work;
+    }
+
+    void permute()
+    {
+      for ( int k = 0; k < product; k++ )
+      {
+        int k2 = k;
+        for ( int j = (n_dims-1); j == 0; j-- ) {
+          int temp = 1;
+          for ( int m = 0; m < j; m++  ) {
+            temp *= lengths[m];
+          }
+          indexes[j] = k2/temp;
+          k2 -= temp*indexes[j];
+        }
+        for ( int t = 0; t < indexes.length; t++ ) {
+          work[k] += dim_product[sub_domain[t]]*indexes[(n_dims-1) - t];
+        }
+      }
+    }
+  }
+ **/
diff --git a/visad/FlatField.java b/visad/FlatField.java
new file mode 100644
index 0000000..5c60480
--- /dev/null
+++ b/visad/FlatField.java
@@ -0,0 +1,6265 @@
+//
+// FlatField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+   FlatField is the VisAD class for finite samplings of functions whose
+   range type and range coordinate systems are simple enough to allow
+   efficient representation.  The DomainSet, DomainCoordinateSystem,
+   RangeSet, RangeCoordinateSystem and RangeCoordinateSystems variables
+   of FlatField are immutable.<P>
+
+   A FlatField range type may be either a RealType (for a function with
+   range = R), a RealTupleType (for a function with range = R^n for n > 0),
+   or a TupleType of RealType-s and RealTupleType-s..<P>
+
+   VisAD avoids invoking methods once per datum through the use of
+   FlatField's.  These are logically Field's of Tuple's of RealType's
+   and RealTupleType's.  Internally FlatField's are stored as arrays of
+   numerical values, rather than the arrays of data objects stored in
+   Field's.  Many of the methods in the FlatField class and in other
+   classes (e.g., CoordinateTransform, Set, Unit) process data in the
+   form double[Dimension][Length] where Length is the number of samples
+   in a Field and Dimension is the number of Tuple elements in the
+   Field range values.  Note that the order of the Length and Dimension
+   indices are reversed as array indices.  This allows efficient
+   processing of long columns of Field value components.  For example,
+   if Latitude is one component of Field values, then any computation
+   involving Latitude can be applied in a tight loop to all Latitude's
+   in the Field.<P>
+
+   FlatField's support range types more general than RealTuple's.  To
+   understand the motive, consider a set of observations that include
+   Latitude, Longitude, Altitude, Pressure, Temperature, etc.  We can
+   organize these as a Field whose range values have the Tuple type:<P>
+ <PRE>
+
+     (Latitude, Longitude, Altitude, Pressure, Temperature, ...)
+
+</PRE>
+   However, in order to declare that (Latitude, Longitude, Altitude)
+   is a coordinate system with coordinate transform functions to other
+   spatial coordinate systems, we need to organize:<P>
+<PRE>
+
+     (Latitude, Longitude, Altitude)
+
+</PRE>
+   as a RealTupleType.  Hence the range type of the Field of observations
+   must be:<P>
+<PRE>
+
+     ((Latitude, Longitude, Altitude), Pressure, Temperature, ...)
+
+</PRE>
+   which is not a RealTupleType (since one of its components is a
+   RealTupleType).  In order to process such data efficiently, FlatField's
+   must support range types that are Tuple's of RealType's and
+   RealTupleType's.<P>
+*/
+public class FlatField extends FieldImpl implements FlatFieldIface {
+
+  protected int TupleDimension; // dimension of Type.getFlatRange()
+  private Set RangeSet[]; // one 1-D Set per range components
+  private int RangeMode[]; // DOUBLE, FLOAT, INT, SHORT or BYTE
+ // coordinate system of the function range R^n
+  protected CoordinateSystem RangeCoordinateSystem; // used if Type.Real
+  protected CoordinateSystem[] RangeCoordinateSystems; // used for Flat, not for Real
+  private boolean MissingFlag;
+
+  protected Unit[] RangeUnits;
+
+  // RangeErrors, like range values, are not immutable
+  private ErrorEstimate[] RangeErrors;
+
+  // for tuple memeber i, only one of these is not null, depending on RangeSet[i]
+  private double[][] DoubleRange;
+  private float[][] FloatRange;
+  private long[][] LongRange; // not currently used because array
+                            // indices are int's
+  private int[][] IntRange;
+  private short[][] ShortRange;
+  private byte[][] ByteRange;
+
+  private static final int MISSING1 = Byte.MIN_VALUE;      // least byte
+  private static final int MISSING2 = Short.MIN_VALUE;     // least short
+  private static final int MISSING4 = Integer.MIN_VALUE;   // least int
+  // private static final int MISSING8 = Long.MIN_VALUE;   // least long
+
+  private static final int DOUBLE = 1;
+  private static final int FLOAT = 2;
+  private static final int LONG = 3; // not currently used because array
+                                     // indices are int's
+  private static final int INT = 4;
+  private static final int SHORT = 5;
+  private static final int BYTE = 6;
+
+  /**
+   * Constructs a FlatField from a function type.  The domain Set is the
+   * default Set of the function domain.
+   * @param type                The type of the function.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public FlatField(FunctionType type) throws VisADException {
+    this(type, type.getDomain().getDefaultSet(), null, null, null, null);
+  }
+
+  /**
+   * Constructs a FlatField from a function type and a (non-default) domain
+   * Set.
+   * @param type                The type of the function.
+   * @param domain_set          The sampling set of the domain.  May be <code>
+   *                            null</code>, in which case the sampling set is
+   *                            the default Set of the function domain.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public FlatField(FunctionType type, Set domain_set) throws VisADException {
+    this(type, domain_set, null, null, null, null);
+  }
+
+  /**
+   * Constructs a FlatField from a function type, a sampling set of the domain,
+   * a coordinate system for the range, sampling sets for the range components,
+   * and units for the range components.
+   * @param type                The type of the function.
+   * @param domain_set          The sampling set of the domain.  May be
+   *                            <code> null</code>, in which case the
+   *                            sampling set is set to the default sampling
+   *                            set of the function domain (i.e. <code>
+   *                            type.getDomain().getDefaultSet()</code>).
+   * @param range_coord_sys     Optional coordinate system for the range.
+   *                            May be <code>null</code>.  If non-<code>null
+   *                            </code>, then the range of the function shall
+   *                            be a RealTuple (i.e. <code>type.getRange()
+   *                            instanceof RealTuple</code> shall be
+   *                            true) and the reference coordinate system
+   *                            of the range coordinate system shall be
+   *                            the same as the reference coordiinate
+   *                            system of the function range (i.e.
+   *                            <code>range_coord_sys().getReference().equals(
+   *                            ((RealTuple)type.getRange()).getReference())
+   *                            </code> shall be true).
+   * @param range_sets          The sampling sets of the (flat) range
+   *                            components.  May be <code>null</code>, in which
+   *                            case the default sampling sets of the range
+   *                            component RealType-s are used (i.e. <code>
+   *                            ((RealType)type.getFlatRange().getComponent(i))
+   *                            .getDefaultSet()</code> for all <code>i</code>
+   *                            in the flat range).  If non-<code>null</code>,
+   *                            then the <code> i</code>th flat range
+   *                            component values are stored in bytes
+   *                            if <code>range_sets[i].getLength() <
+   *                            256</code>, stored in shorts if <code>
+   *                            range_sets[i].getLength() < 65536</code>, etc.
+   * @param units               The units of the (flat) range components.  May
+   *                            be <code>null</code>, in which case the default
+   *                            units of the flat range RealType-s are used.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public FlatField(FunctionType type, Set domain_set,
+                   CoordinateSystem range_coord_sys, Set[] range_sets,
+                   Unit[] units) throws VisADException {
+    this(type, domain_set, range_coord_sys, null, range_sets, units);
+  }
+
+  /**
+   * Constructs a FlatField from a function type, a sampling set of the domain,
+   * coordinate systems for the range components, sampling sets for the range
+   * components, and units for the range components.
+   * @param type                The type of the function.
+   * @param domain_set          The sampling set of the domain.  May be null, in
+   *                            which case the sampling set is set to the
+   *                            default sampling set of the function domain
+   *                            (i.e. <code>
+   *                            type.getDomain().getDefaultSet()</code>).
+   * @param range_coord_syses   Optional coordinate systems for the range
+   *                            components.  May be <code>null</code>.
+   *                            If the <code>i</code>th component
+   *                            of the range is a RealTuple, then
+   *                            <code>range_coord_syses[i]</code> may be
+   *                            non-<code>null</code> and, if so, is the
+   *                            coordinate system for that component and shall
+   *                            have the same reference coordinate system as
+   *                            the corresponding component in the range of the
+   *                            function type.
+   * @param range_sets          The sampling sets of the (flat) range
+   *                            components.  May be <code>null</code>, in which
+   *                            case the default sampling sets of the range
+   *                            component RealType-s are used (i.e. <code>
+   *                            ((RealType)type.getFlatRange().getComponent(i))
+   *                            .getDefaultSet()</code> for all <code>i</code>
+   *                            in the flat range).  If non-<code>null</code>,
+   *                            then the <code> i</code>th flat range
+   *                            component values are stored in bytes
+   *                            if <code>range_sets[i].getLength() <
+   *                            256</code>, stored in shorts if <code>
+   *                            range_sets[i].getLength() < 65536</code>, etc.
+   * @param units               The units of the (flat) range components.  May
+   *                            be <code>null</code>, in which case the default
+   *                            units of the flat range RealType-s are used.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public FlatField(FunctionType type, Set domain_set,
+                   CoordinateSystem[] range_coord_syses, Set[] range_sets,
+                   Unit[] units) throws VisADException {
+    this(type, domain_set, null, range_coord_syses, range_sets, units);
+  }
+
+  /**
+   * Constructs a FlatField from a function type, a sampling set of the domain,
+   * a coordinate system for the range, coordinate systems for the range
+   * components, sampling sets for the range components, and units for the range
+   * components.  This is the most general constructor.
+   * @param type                The type of the function.
+   * @param domain_set          The sampling set of the domain.  May be null, in
+   *                            which case the sampling set is set to the
+   *                            default sampling set of the function domain
+   *                            (i.e. <code>
+   *                            type.getDomain().getDefaultSet()</code>).
+   * @param range_coord_sys     Optional coordinate system for the range.
+   *                            May be <code>null</code>.  If non-<code>null
+   *                            </code>, then the range of the function shall
+   *                            be a RealTuple (i.e. <code>type.getRange()
+   *                            instanceof RealTuple</code> shall be
+   *                            true) and the reference coordinate system
+   *                            of the range coordinate system shall be
+   *                            the same as the reference coordiinate
+   *                            system of the function range (i.e.
+   *                            <code>range_coord_sys().getReference().equals(
+   *                            ((RealTuple)type.getRange()).getReference())
+   *                            </code> shall be true).
+   * @param range_coord_syses   Optional coordinate systems for the range
+   *                            components.  May be <code>null</code>.
+   *                            If the <code>i</code>th component
+   *                            of the range is a RealTuple, then
+   *                            <code>range_coord_syses[i]</code> may be
+   *                            non-<code>null</code> and, if so, is the
+   *                            coordinate system for that component and shall
+   *                            have the same reference coordinate system as
+   *                            the corresponding component in the range of the
+   *                            function type.
+   * @param range_sets          The sampling sets of the (flat) range
+   *                            components.  May be <code>null</code>, in which
+   *                            case the default sampling sets of the range
+   *                            component RealType-s are used (i.e. <code>
+   *                            ((RealType)type.getFlatRange().getComponent(i))
+   *                            .getDefaultSet()</code> for all <code>i</code>
+   *                            in the flat range).  If non-<code>null</code>,
+   *                            then the <code> i</code>th flat range
+   *                            component values are stored in bytes
+   *                            if <code>range_sets[i].getLength() <
+   *                            256</code>, stored in shorts if <code>
+   *                            range_sets[i].getLength() < 65536</code>, etc.
+   * @param units               The units of the (flat) range components.  May
+   *                            be <code>null</code>, in which case the default
+   *                            units of the flat range RealType-s are used.
+   * @throws SetException       if <code>range_sets</code> is non-<code>null
+   *                            </code> and either one of its elements is
+   *                            <code>null</code> or an element's dimension is
+   *                            not one.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public FlatField(FunctionType type, Set domain_set,
+                   CoordinateSystem range_coord_sys,
+                   CoordinateSystem[] range_coord_syses,
+                   Set[] range_sets, Unit[] units)
+          throws VisADException {
+   //Call FieldImpl.ctor passing false to tell it not to create the Range array
+    super(type, domain_set, false);
+    pr ("ctor");
+
+    if (!type.getFlat()) {
+      throw new FieldException("FlatField: FunctionType must be Flat");
+    }
+    MathType RangeType = type.getRange();
+    RealTupleType FlatRange = type.getFlatRange();
+    TupleDimension = FlatRange.getDimension();
+    DoubleRange = new double[TupleDimension][];
+
+    // set RangeSet
+    RangeSet = new Set[TupleDimension];
+    RangeMode = new int[TupleDimension];
+    if (range_sets == null) {
+      // set the default range sampling; if no default, use double
+      for (int i=0; i<TupleDimension; i++) {
+        RangeSet[i] = ((RealType) FlatRange.getComponent(i)).getDefaultSet();
+        if (RangeSet[i] == null) {
+          RangeSet[i] = new FloatSet(new SetType(FlatRange.getComponent(i)));
+          // WLH 1 Feb 98
+          // RangeSet[i] = new DoubleSet(new SetType(FlatRange.getComponent(i)));
+        }
+      }
+    }
+    else {
+      // set explicit range sets
+      if (TupleDimension != range_sets.length) {
+        throw new SetException("FlatField: range set dimensions don't match");
+      }
+      for (int i=0; i<TupleDimension; i++) {
+        if (range_sets[i] == null || range_sets[i].getDimension() != 1) {
+          throw new SetException("FlatField: each range set dimension must be 1");
+        }
+      }
+      // force RangeSet Type-s to match FlatRange
+      for (int i=0; i<TupleDimension; i++) {
+        if (FlatRange.getComponent(i).equals(
+            ((SetType) range_sets[i].getType()).getDomain())) {
+          RangeSet[i] = range_sets[i];
+        }
+        else {
+          RangeSet[i] = (Set) range_sets[i].cloneButType(
+                        new SetType(FlatRange.getComponent(i)));
+        }
+      }
+    }
+    nullRanges();
+
+    // set RangeCoordinateSystem or RangeCoordinateSystems
+    // also set RangeUnits
+    if (type.getReal()) {
+      // only one RangeCoordinateSystem
+      Unit[] type_units;
+      if (range_coord_syses != null) {
+        throw new CoordinateSystemException("FlatField: Real Function" +
+               " Type requires single range coordinate system");
+      }
+      RangeCoordinateSystems = null;
+      RangeCoordinateSystem = FlatRange.getCoordinateSystem();
+      if (range_coord_sys != null) {
+        if (!(RangeType instanceof RealTupleType)) {
+          throw new CoordinateSystemException("FlatField: " +
+                    "range_coord_sys supplied, but RangeType is not RealTupleType");
+        }
+        if (RangeCoordinateSystem == null ||
+            !RangeCoordinateSystem.getReference().equals(
+             range_coord_sys.getReference())) {
+          throw new CoordinateSystemException("FlatField: range_coord_sys " +
+                                            (range_coord_sys == null ? null :
+                                             range_coord_sys.getReference()) +
+                                            " must match" +
+                                            " default range CoordinateSystem " +
+                                            (RangeCoordinateSystem == null ? null :
+                                             RangeCoordinateSystem.getReference()));
+        }
+        RangeCoordinateSystem = range_coord_sys;
+      }
+      if (units == null) {
+        RangeUnits = (RangeCoordinateSystem == null) ?
+                     FlatRange.getDefaultUnits() :
+                     RangeCoordinateSystem.getCoordinateSystemUnits();
+      }
+      else {
+        if (units.length != TupleDimension) {
+          throw new UnitException("FlatField: units dimension does not match");
+        }
+        RangeUnits = new Unit[TupleDimension];
+        for (int i=0; i<TupleDimension; i++) {
+          RealType componentType = (RealType)FlatRange.getComponent(i);
+          Unit componentUnit = units[i];
+          RangeUnits[i] =
+            componentUnit == null || !componentType.isInterval()
+              ? componentUnit
+              : componentUnit.getAbsoluteUnit();
+        }
+      }
+      if (RangeType instanceof RealTupleType) {
+        type_units = ((RealTupleType) RangeType).getDefaultUnits();
+      }
+      else {
+        type_units = new Unit[1];
+        type_units[0] = ((RealType) RangeType).getDefaultUnit();
+      }
+      if (RangeCoordinateSystem != null &&
+          !Unit.canConvertArray(RangeCoordinateSystem.getCoordinateSystemUnits(),
+                                type_units)) {
+        throw new UnitException("FlatField: RangeCoordinateSystem Units must be " +
+                                "convertable with RangeType default Units");
+      }
+      if (RangeCoordinateSystem != null &&
+          !Unit.canConvertArray(RangeCoordinateSystem.getCoordinateSystemUnits(),
+                                RangeUnits)) {
+        throw new UnitException("FlatField: RangeUnits must be convertable " +
+                                "with RangeCoordinateSystem Units");
+      }
+      if (!Unit.canConvertArray(type_units, RangeUnits)) {
+        throw new UnitException("FlatField: RangeUnits must be convertable " +
+                                "with RangeType default Units");
+      }
+    }
+    else { // !type.getReal()
+      // multiple RangeCoordinateSystems
+      Unit[] sub_range_units;
+      Unit[] sub_type_units;
+      if (range_coord_sys != null) {
+        throw new CoordinateSystemException("FlatField: non-Real Function" +
+               " Type requires multiple range coordinate systems");
+      }
+      RangeCoordinateSystem = null;
+      int n = ((TupleType) RangeType).getDimension();
+      RangeCoordinateSystems = new CoordinateSystem[n];
+      for (int i=0; i<n; i++) {
+        MathType component = ((TupleType) RangeType).getComponent(i);
+        if (component instanceof RealTupleType) {
+          RangeCoordinateSystems[i] =
+            ((RealTupleType) component).getCoordinateSystem();
+          if (range_coord_syses != null && range_coord_syses[i] != null) {
+            if (RangeCoordinateSystems[i] == null ||
+                RangeCoordinateSystems[i].getReference() !=
+                range_coord_syses[i].getReference()) {
+              throw new TypeException("FlatField: range_coord_syses must" +
+                                      " match Range DefaultCoordinateSystem");
+            }
+            RangeCoordinateSystems[i] = range_coord_syses[i];
+          }
+        }
+        else {
+          RangeCoordinateSystems[i] = null;
+        }
+      }
+      if (units == null) {
+        RangeUnits = FlatRange.getDefaultUnits();
+        int j = 0;
+        for (int i=0; i<n; i++) {
+          if (RangeCoordinateSystems[i] != null) {
+            sub_range_units =
+              RangeCoordinateSystems[i].getCoordinateSystemUnits();
+            for (int k=0; k<sub_range_units.length; k++) {
+              RangeUnits[j + k] = sub_range_units[k];
+            }
+          }
+          MathType component = ((TupleType) RangeType).getComponent(i);
+          if (component instanceof RealType) {
+            j++;
+          }
+          else if (component instanceof RealTupleType) {
+            j += ((RealTupleType) component).getDimension();
+          }
+/* WLH 117 April 99
+          j += n;
+*/
+        }
+      }
+      else {
+        if (units.length != TupleDimension) {
+          throw new UnitException("FlatField: units dimension does not match");
+        }
+        RangeUnits = new Unit[TupleDimension];
+        for (int i=0; i<TupleDimension; i++) {
+          RealType componentType = (RealType)FlatRange.getComponent(i);
+          Unit componentUnit = units[i];
+          RangeUnits[i] =
+            componentUnit == null || !componentType.isInterval()
+              ? componentUnit
+              : componentUnit.getAbsoluteUnit();
+        }
+      }
+
+      int j = 0;
+      for (int i=0; i<n; i++) {
+        int m;
+        MathType component = ((TupleType) RangeType).getComponent(i);
+        if (component instanceof RealTupleType) {
+          sub_type_units = ((RealTupleType) component).getDefaultUnits();
+          m = ((RealTupleType) component).getDimension();
+          sub_range_units = new Unit[m];
+          for (int k=0; k<m; k++) {
+            sub_range_units[k] = RangeUnits[j + k];
+          }
+        }
+        else {
+          sub_type_units = new Unit[1];
+          sub_type_units[0] = ((RealType) component).getDefaultUnit();
+          m = 1;
+          sub_range_units = new Unit[1];
+          sub_range_units[0] = RangeUnits[j];
+        }
+
+        if (RangeCoordinateSystems[i] != null &&
+            !Unit.canConvertArray(sub_type_units,
+                  RangeCoordinateSystems[i].getCoordinateSystemUnits())) {
+          throw new UnitException("FlatField: RangeCoordinateSystems Units must " +
+                                  "be convertable with RangeType default Units");
+        }
+        if (RangeCoordinateSystems[i] != null &&
+            !Unit.canConvertArray(sub_range_units,
+                  RangeCoordinateSystems[i].getCoordinateSystemUnits())) {
+          throw new UnitException("FlatField: RangeUnits must be convertable " +
+                                  "with RangeCoordinateSystems Units");
+        }
+        if (!Unit.canConvertArray(sub_type_units, sub_range_units)) {
+          throw new UnitException("FlatField: RangeUnits must be convertable " +
+                                  "with RangeType default Units");
+        }
+        j += m;
+      }
+    } // end !type.getReal()
+
+    if (RangeUnits == null) RangeUnits = new Unit[TupleDimension];
+
+
+    // initialize RangeErrors to all null
+    RangeErrors = new ErrorEstimate[TupleDimension];
+
+    // initially all values are missing
+    MissingFlag = true;
+  }
+
+  /**
+   * Returns the range CoordinateSystem assuming that the range type is
+   * a RealTupleType (and throws a TypeException if its not).  This may
+   * differ from default CoordinateSystem of range RealTupleType, but must be
+   * convertable; the index has length = 1 (since all samples have the same
+   * Units).
+   *
+   * @return                    The CoordinateSystem of the RealTuple range.
+   *                            Will not be <code>null</code> and will be of
+   *                            length 1.
+   * @throws TypeException      The type of the range is neither RealType nor
+   *                            RealTupleType.
+   */
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws TypeException {
+    MathType RangeType = ((FunctionType) Type).getRange();
+    if (!((FunctionType) Type).getReal()) {
+      throw new TypeException("FlatField.getRangeCoordinateSystem: " +
+        "Range is not Real, need DefaultCoordinateSystem index");
+    }
+    CoordinateSystem[] cs = {RangeCoordinateSystem};
+    return cs;
+  }
+
+  /**
+   * Returns the sampling set of each flat component.
+   * @return            The sampling set of each component in the flat range.
+   */
+  public Set[] getRangeSets() {
+    Set[] sets = new Set[RangeSet.length];
+    System.arraycopy(RangeSet, 0, sets, 0, sets.length);
+    return sets;
+  }
+
+  /**
+   * Returns the CoordinateSystem of a component of the range.  The MathType
+   * of the range shall be a TupleType.
+   *
+   * @param i                   The index of the component.  The value shall be
+   *                            greater than or equal to zero and less that the
+   *                            number of components in the TupleType of the
+   *                            range.
+   * @return                    The CoordinateSystem of the <code>i</code>-th
+   *                            component.  Won't be <code>null</code> and will
+   *                            be of length 1.  The single element might be
+   *                            <code>null</code>.
+   * @throws TypeException      The type of the range is either RealType or
+   *                            RealTupleType.
+   */
+  public CoordinateSystem[] getRangeCoordinateSystem(int i)
+         throws TypeException {
+    if (((FunctionType) Type).getReal()) {
+      throw new TypeException("FlatField.getRangeCoordinateSystem: " +
+        "Range is Real, cannot specify CoordinateSystem index");
+    }
+    CoordinateSystem[] cs = {RangeCoordinateSystems[i]};
+    return cs;
+  }
+
+  /** return array of Units associated with each RealType
+      component of range; these may differ from default
+      Units of range RealTypes, but must be convertable;
+      the second index has length = 1 (since all samples
+      have the same Units) */
+  public Unit[][] getRangeUnits() {
+    Unit[][] units = new Unit[RangeUnits.length][1];
+    for (int i=0; i<RangeUnits.length; i++) {
+      units[i][0] = RangeUnits[i];
+    }
+    return units;
+  }
+
+  /** return array of ErrorEstimates associated with each
+      RealType component of range; each ErrorEstimate is a
+      mean error for all samples of a range RealType
+      component */
+  public ErrorEstimate[] getRangeErrors() {
+    synchronized (RangeErrors) {
+      return ErrorEstimate.copyErrorsArray(RangeErrors);
+    }
+  }
+
+  /**
+   * Sets the ErrorEstimates associated with each RealType component of the
+   * range.  <code>errors[i]</code> is the {@link ErrorEstimate} for the 
+   * <code>i</code>-th {@link RealType} component.
+   *
+   * @param errors          The error estimates for the range values.
+   * @throws FieldException if <code>errors</code> is non-<code>null</code> and
+   *                        <code>errors.length != getRangeDimension()</code>.
+   */
+  public void setRangeErrors(ErrorEstimate[] errors) throws FieldException {
+    synchronized (RangeErrors) {
+      if (errors == null) {
+        for (int i=0; i<TupleDimension; i++) {
+          RangeErrors[i] = null;
+        }
+      }
+      else {
+        if (errors.length != TupleDimension) {
+          throw new FieldException("FlatField.setRangeErrors: errors " +
+                                   "dimension does not match");
+        }
+        for (int i=0; i<TupleDimension; i++) {
+          RangeErrors[i] = errors[i];
+        }
+      }
+    }
+  }
+
+  /** set the range values of the function; the order of range values
+      must be the same as the order of domain indices in the DomainSet;
+      copy argument included for consistency with Field, but ignored */
+  public void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException {
+    if (range == null || range.length != getLength()) {
+      throw new FieldException("setSamples: bad Data[] length");
+    }
+    for (int i=0; i<getLength(); i++) {
+      setSample(i, range[i]);
+    }
+  }
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      double[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet */
+  public void setSamples(double[][] range)
+         throws VisADException, RemoteException {
+    setSamples(range, null, true);
+  }
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet */
+  public void setSamples(float[][] range)
+         throws VisADException, RemoteException {
+    setSamples(range, null, true);
+  }
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      double[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet; copy array if copy flag is true */
+  public void setSamples(double[][] range, boolean copy)
+         throws VisADException, RemoteException {
+    setSamples(range, null, copy);
+  }
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet; copy array if copy flag is true */
+  public void setSamples(float[][] range, boolean copy)
+         throws VisADException, RemoteException {
+    setSamples(range, null, copy);
+  }
+
+  /** set the range values of the function including ErrorEstimate-s;
+      the order of range values must be the same as the order of
+      domain indices in the DomainSet */
+  public void setSamples(double[][] range, ErrorEstimate[] errors,
+              boolean copy) throws VisADException, RemoteException {
+    if(range.length != TupleDimension ||
+       (errors != null && errors.length != TupleDimension)) {
+      throw new FieldException("FlatField.setSamples: bad tuple length");
+    }
+
+    for (int i=0; i<TupleDimension; i++) {
+      if (range[i].length != getLength()) {
+        throw new FieldException("setSamples: bad array length");
+      }
+    }
+    packValues(range, copy);
+    setRangeErrors(errors);
+    notifyReferences();
+  }
+
+    /**
+       This returns true if any of the RangeMode values == DOUBLE. We use this
+       so in operations (e.g., unary, binary) that deal with the data we try
+       to keep it to be floats if we can (for size efficiency).
+     **/
+    private boolean  shouldBeDouble () {
+      for (int i=0; i<TupleDimension; i++) {
+          if (RangeMode[i] == DOUBLE) {
+              return true;
+          }
+      }
+      return false;
+    }
+
+
+  /** update a subset of a FlatField's range samples, where
+      start is the index of the first sample to update and
+      range[0].length is the number of samples to update;
+      the array is dimensioned
+      double[number_of_range_components][number_of_range_samples] */
+  public void setSamples(int start, double[][] range)
+         throws VisADException, RemoteException
+  {
+    if(range.length != TupleDimension ) {
+      throw new FieldException("FlatField.setSamples: bad tuple length");
+    }
+
+    for (int i=0; i<TupleDimension; i++) {
+      if (range[i].length + start > getLength()) {
+        throw new FieldException("setSamples: bad array length");
+      }
+    }
+    
+    boolean copy = false;
+    int length = range[0].length;
+
+  //--  packValues starts here:
+
+    // NOTE INVERTED ORDER OF range ARRAY INDICES !!!
+    int[] index;
+    synchronized (DoubleRange) {
+      for (int i=0; i<TupleDimension; i++) {
+        double[] rangeI = range[i];
+        double[][] range1 = new double[1][];
+        range1[0] = rangeI;
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            if (DoubleRange[i] == null) {
+              DoubleRange[i] = new double[getLength()];
+            }
+            double[] DoubleRangeI = DoubleRange[i];
+            System.arraycopy(rangeI, 0, DoubleRangeI, start, length);
+            break;
+          case FLOAT:
+            if (FloatRange[i] == null) {
+              FloatRange[i] = new float[getLength()];
+              for (int j=0; j<getLength(); j++) FloatRange[i][j] = Float.NaN;
+            }
+            float[] FloatRangeI = FloatRange[i];
+            for (int j=0; j<length; j++) FloatRangeI[start + j] = (float) rangeI[j];
+            break;
+          case BYTE:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            if (ByteRange[i] == null) {
+              ByteRange[i] = new byte[getLength()];
+              for (int j=0; j<getLength(); j++) ByteRange[i][j] = (byte) MISSING1;
+            }
+            byte[] ByteRangeI = ByteRange[i];
+            for (int j=0; j<length; j++) {
+              ByteRangeI[start + j] = (byte) (index[j] + MISSING1 + 1);
+            }
+            break;
+          case SHORT:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            if (ShortRange[i] == null) {
+              ShortRange[i] = new short[getLength()];
+              for (int j=0; j<getLength(); j++) ShortRange[i][j] = (short) MISSING2;
+            }
+            short[] ShortRangeI = ShortRange[i];
+            for (int j=0; j<length; j++) {
+              ShortRangeI[start + j] = (short) (index[j] + MISSING2 + 1);
+            }
+            break;
+          case INT:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            if (IntRange[i] == null) {
+              IntRange[i] = new int[getLength()];
+              for (int j=0; j<getLength(); j++) IntRange[i][j] = (int) MISSING4;
+            }
+            int[] IntRangeI = IntRange[i];
+            for (int j=0; j<length; j++) {
+              IntRangeI[start + j] = index[j] + MISSING4 + 1;
+            }
+            break;
+          default:
+            throw new SetException("FlatField.packValues: bad RangeMode");
+        }
+      }
+      clearMissing();
+    }
+  //-- End packValues
+
+    setRangeErrors(null);
+    notifyReferences();
+  }
+
+  public void setSamples(int[] indices, double[][] range)
+         throws VisADException, RemoteException
+  {
+      pr ("setSamples");
+
+
+    int length = indices.length;
+
+    if(range.length != TupleDimension ) {
+      throw new FieldException("FlatField.setSamples: bad tuple length");
+    }
+    if ( length > getLength() ) {
+      throw new FieldException("setSamples: indices array too long");
+    }
+    for (int i=0; i<TupleDimension; i++) {
+      if (range[i].length != length) {
+        throw new FieldException("setSamples: bad data array length");
+      }
+    }
+
+    boolean copy = false;
+
+  //--  packValues starts here:
+
+    // NOTE INVERTED ORDER OF range ARRAY INDICES !!!
+    int[] index;
+    synchronized (DoubleRange) {
+      for (int i=0; i<TupleDimension; i++) {
+        double[] rangeI = range[i];
+        double[][] range1 = new double[1][];
+        range1[0] = rangeI;
+
+
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            if (DoubleRange[i] == null) {
+              DoubleRange[i] = new double[getLength()];
+            }
+            double[] DoubleRangeI = DoubleRange[i];
+            for (int j = 0; j < length; j++) {
+              DoubleRangeI[indices[j]] = rangeI[j];
+            }
+            break;
+          case FLOAT:
+            if (FloatRange[i] == null) {
+              FloatRange[i] = new float[getLength()];
+              for (int j=0; j<getLength(); j++) FloatRange[i][j] = Float.NaN;
+            }
+            float[] FloatRangeI = FloatRange[i];
+            for (int j=0; j<length; j++) FloatRangeI[indices[j]] = (float) rangeI[j];
+            break;
+          case BYTE:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            if (ByteRange[i] == null) {
+              ByteRange[i] = new byte[getLength()];
+              for (int j=0; j<getLength(); j++) ByteRange[i][j] = (byte) MISSING1;
+            }
+            byte[] ByteRangeI = ByteRange[i];
+            for (int j=0; j<length; j++) {
+              ByteRangeI[indices[j]] = (byte) (index[j] + MISSING1 + 1);
+            }
+            break;
+          case SHORT:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            if (ShortRange[i] == null) {
+              ShortRange[i] = new short[getLength()];
+              for (int j=0; j<getLength(); j++) ShortRange[i][j] = (short) MISSING2;
+            }
+            short[] ShortRangeI = ShortRange[i];
+            for (int j=0; j<length; j++) {
+              ShortRangeI[indices[j]] = (short) (index[j] + MISSING2 + 1);
+            }
+            break;
+          case INT:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            if (IntRange[i] == null) {
+              IntRange[i] = new int[getLength()];
+              for (int j=0; j<getLength(); j++) IntRange[i][j] = (int) MISSING4;
+            }
+            int[] IntRangeI = IntRange[i];
+            for (int j=0; j<length; j++) {
+              IntRangeI[indices[j]] = index[j] + MISSING4 + 1;
+            }
+            break;
+          default:
+            throw new SetException("FlatField.packValues: bad RangeMode");
+        }
+      }
+      clearMissing();
+    }
+  //-- End packValues
+
+    setRangeErrors(null);
+    notifyReferences();
+  }
+
+  /** set the range values of the function including ErrorEstimate-s;
+      the order of range values must be the same as the order of
+      domain indices in the DomainSet */
+  public void setSamples(float[][] range, ErrorEstimate[] errors,
+              boolean copy) throws VisADException, RemoteException {
+    if(range.length != TupleDimension ||
+       (errors != null && errors.length != TupleDimension)) {
+      throw new FieldException("FlatField.setSamples: bad tuple length");
+    }
+
+    for (int i=0; i<TupleDimension; i++) {
+      if (range[i].length != getLength()) {
+        throw new FieldException("setSamples: bad array length");
+      }
+    }
+    packValues(range, copy);
+    setRangeErrors(errors);
+    notifyReferences();
+  }
+
+    protected void pr (String message) {
+//              System.err.println ( hashCode () + " " + getClass().getName () + "  " + message);
+    }
+
+  /** pack an array of doubles into field sample values according to the
+      RangeSet-s; copies data */
+  private void packValues(double[][] range, boolean copy)
+          throws VisADException {
+    // NOTE INVERTED ORDER OF range ARRAY INDICES !!!
+    int[] index;
+    pr ("packValuesD");
+
+
+
+    synchronized (DoubleRange) {
+      nullRanges();
+      for (int i=0; i<TupleDimension; i++) {
+        double[] rangeI = range[i];
+        double[][] range1 = new double[1][];
+        range1[0] = rangeI;
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            if (copy) {
+              DoubleRange[i] = new double[getLength()];
+              double[] DoubleRangeI = DoubleRange[i];
+              System.arraycopy(rangeI, 0, DoubleRangeI, 0, getLength());
+              // for (int j=0; j<getLength(); j++) DoubleRangeI[j] = rangeI[j];
+            }
+            else {
+              DoubleRange[i] = rangeI;
+            }
+            break;
+          case FLOAT:
+            FloatRange[i] = new float[getLength()];
+            float[] FloatRangeI = FloatRange[i];
+            for (int j=0; j<getLength(); j++) FloatRangeI[j] = (float) rangeI[j];
+            break;
+          case BYTE:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            ByteRange[i] = new byte[getLength()];
+            byte[] ByteRangeI = ByteRange[i];
+            for (int j=0; j<getLength(); j++) {
+              ByteRangeI[j] = (byte) (index[j] + MISSING1 + 1);
+            }
+            break;
+          case SHORT:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            ShortRange[i] = new short[getLength()];
+            short[] ShortRangeI = ShortRange[i];
+            for (int j=0; j<getLength(); j++) {
+              ShortRangeI[j] = (short) (index[j] + MISSING2 + 1);
+            }
+            break;
+          case INT:
+            index = RangeSet[i].valueToIndex(Set.doubleToFloat(range1));
+            IntRange[i] = new int[getLength()];
+            int[] IntRangeI = IntRange[i];
+            for (int j=0; j<getLength(); j++) {
+              IntRangeI[j] = index[j] + MISSING4 + 1;
+            }
+            break;
+          default:
+            throw new SetException("FlatField.packValues: bad RangeMode");
+        }
+      }
+      clearMissing();
+    }
+  }
+
+  /**
+   * Pack an array of floats into field sample values according to the
+   * RangeSet-s; copies data.
+   *
+   * @throws VisADException if {@link #nullRanges()} fails.
+   */
+  private void packValues(float[][] range, boolean copy)
+          throws VisADException {
+
+    // NOTE INVERTED ORDER OF range ARRAY INDICES !!!
+    int[] index;
+    synchronized (DoubleRange) {
+      nullRanges();
+      for (int i=0; i<TupleDimension; i++) {
+        float[] rangeI = range[i];
+        float[][] range1 = new float[1][];
+        range1[0] = rangeI;
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            DoubleRange[i] = new double[getLength()];
+            double[] DoubleRangeI = DoubleRange[i];
+            for (int j=0; j<getLength(); j++) DoubleRangeI[j] = rangeI[j];
+            break;
+          case FLOAT:
+            if (copy) {
+              FloatRange[i] = new float[getLength()];
+              float[] FloatRangeI = FloatRange[i];
+              System.arraycopy(rangeI, 0, FloatRangeI, 0, getLength());
+              // for (int j=0; j<getLength(); j++) FloatRangeI[j] = (float) rangeI[j];
+            }
+            else {
+              FloatRange[i] = rangeI;
+            }
+            break;
+          case BYTE:
+            index = RangeSet[i].valueToIndex(range1);
+            ByteRange[i] = new byte[getLength()];
+            byte[] ByteRangeI = ByteRange[i];
+            int offset = MISSING1+1;
+            for (int j=0; j<getLength(); j++) {
+              ByteRangeI[j] = (byte) (index[j] + offset);
+            }
+            break;
+          case SHORT:
+            index = RangeSet[i].valueToIndex(range1);
+            ShortRange[i] = new short[getLength()];
+            short[] ShortRangeI = ShortRange[i];
+            for (int j=0; j<getLength(); j++) {
+              ShortRangeI[j] = (short) (index[j] + MISSING2 + 1);
+            }
+            break;
+          case INT:
+            index = RangeSet[i].valueToIndex(range1);
+            IntRange[i] = new int[getLength()];
+            int[] IntRangeI = IntRange[i];
+            for (int j=0; j<getLength(); j++) {
+              IntRangeI[j] = index[j] + MISSING4 + 1;
+            }
+            break;
+          default:
+            throw new SetException("FlatField.packValues: bad RangeMode");
+        }
+      }
+      clearMissing();
+    }
+
+  }
+
+  public byte[][] grabBytes() {
+    return ByteRange;
+  }
+
+  /** unpack an array of doubles from field sample values according to the
+      RangeSet-s; returns a copy */
+  public double[][] unpackValues() throws VisADException {
+    return unpackValues(true);
+  }
+
+
+  /**
+   * Unpacks an array of doubles from field sample values according to the
+   * RangeSet-s; returns a copy if copy == true.
+   *
+   * @param copy            Whether or not to return a copy.
+   * @throws SetException   if an element of {@link #RangeMode} contains an
+   *                        unknown value.
+   * @throws VisADException if {@link Set#indexToValue(int [])} on a range set
+   *                        fails.
+   */
+    protected double[][] unpackValues(boolean copy)
+        throws SetException, VisADException {
+        double[][] range;
+        synchronized (DoubleRange) {
+            if (isMissing()) {
+                range = new double[TupleDimension][getLength()];
+                for (int i=0; i<TupleDimension; i++) {
+                    for (int j=0; j<getLength(); j++) {
+                        range[i][j] = Double.NaN;
+                    }
+                }
+                return range;
+            }
+
+            int[] index;
+            range = new double[TupleDimension][];
+            double[][] range0;
+            double[] rangeI;
+            for (int i=0; i<TupleDimension; i++) {
+                switch (RangeMode[i]) {
+                case DOUBLE:
+                    if (copy) {
+                        range[i] = new double[getLength()];
+                        rangeI = range[i];
+                        double[] DoubleRangeI = DoubleRange[i];
+                        System.arraycopy(DoubleRangeI, 0, rangeI, 0, getLength());
+                        // for (int j=0; j<getLength(); j++) rangeI[j] = DoubleRangeI[j];
+                    }
+                    else {
+                        range[i] = DoubleRange[i];
+                    }
+                    break;
+                case FLOAT:
+                    range[i] = new double[getLength()];
+                    rangeI = range[i];
+                    float[] FloatRangeI = FloatRange[i];
+                    for (int j=0; j<getLength(); j++) {
+                        rangeI[j] = (double) FloatRangeI[j];
+                    }
+                    break;
+                case BYTE:
+                    index = new int[getLength()];
+                    byte[] ByteRangeI = ByteRange[i];
+                    for (int j=0; j<getLength(); j++) {
+                        index[j] = ((int) ByteRangeI[j]) - MISSING1 - 1;
+                    }
+                    range0 = Set.floatToDouble(RangeSet[i].indexToValue(index));
+                    range[i] = range0[0];
+                    break;
+                case SHORT:
+                    index = new int[getLength()];
+                    short[] ShortRangeI = ShortRange[i];
+                    for (int j=0; j<getLength(); j++) {
+                        index[j] = ((int) ShortRangeI[j]) - MISSING2 - 1;
+                    }
+                    range0 = Set.floatToDouble(RangeSet[i].indexToValue(index));
+                    range[i] = range0[0];
+                    break;
+                case INT:
+                    index = new int[getLength()];
+                    int[] IntRangeI = IntRange[i];
+                    for (int j=0; j<getLength(); j++) {
+                        index[j] = ((int) IntRangeI[j]) - MISSING4 - 1;
+                    }
+                    range0 = Set.floatToDouble(RangeSet[i].indexToValue(index));
+                    range[i] = range0[0];
+                    break;
+                default:
+                    throw new SetException("FlatField.unpackValues: bad RangeMode");
+                }
+            }
+        }
+        return range;
+    }
+
+
+  /** unpack an array of floats from field sample values according to the
+      RangeSet-s; returns a copy */
+  public float[][] unpackFloats() throws VisADException {
+      return unpackFloats (true);
+  }
+
+  /** unpack an array of floats from field sample values according to the
+      RangeSet-s; returns a copy if copy == true */
+ protected float[][] unpackFloats(boolean copy) throws VisADException {
+    float[][] range;
+    synchronized (DoubleRange) {
+      if (isMissing()) {
+        range = new float[TupleDimension][getLength()];
+        for (int i=0; i<TupleDimension; i++) {
+          for (int j=0; j<getLength(); j++) {
+            range[i][j] = Float.NaN;
+          }
+        }
+        return range;
+      }
+      int[] index;
+      range = new float[TupleDimension][];
+      float[][] range0;
+      float[] rangeI;
+
+      for (int i=0; i<TupleDimension; i++) {
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            range[i] = new float[getLength()];
+            rangeI = range[i];
+            double[] DoubleRangeI = DoubleRange[i];
+            for (int j=0; j<getLength(); j++) {
+              rangeI[j] = (float) DoubleRangeI[j];
+            }
+            break;
+          case FLOAT:
+            if (copy) {
+              range[i] = new float[getLength()];
+              System.arraycopy (FloatRange[i], 0, range[i], 0, getLength());
+            }  else {
+              range[i] = FloatRange[i];
+            }
+            break;
+          case BYTE:
+              index = new int[getLength()];
+              byte[] ByteRangeI = ByteRange[i];
+              for (int j=0; j<getLength(); j++) {
+                  index[j] = ((int) ByteRangeI[j]) - MISSING1 - 1;
+              }
+              range0 = RangeSet[i].indexToValue(index);
+              range[i] = range0[0];
+              break;
+          case SHORT:
+            index = new int[getLength()];
+            short[] ShortRangeI = ShortRange[i];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) ShortRangeI[j]) - MISSING2 - 1;
+            }
+            range0 = RangeSet[i].indexToValue(index);
+            range[i] = range0[0];
+            break;
+          case INT:
+            index = new int[getLength()];
+            int[] IntRangeI = IntRange[i];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) IntRangeI[j]) - MISSING4 - 1;
+            }
+            range0 = RangeSet[i].indexToValue(index);
+            range[i] = range0[0];
+            break;
+          default:
+            throw new SetException("FlatField.unpackFloats: bad RangeMode");
+        }
+      }
+    }
+    return range;
+  }
+
+  /**
+   * Unpack one range component, makes a copy.
+   *
+   * @param comp	component index
+   *
+   * @return  array of the values
+   * @throws VisADException  bad range mode
+   */
+  protected double[] unpackOneRangeComp(int comp) throws VisADException {
+    return unpackOneRangeComp(comp, true);
+  }
+
+  /**
+   * Unpack one range component.
+   *
+   * @param comp	component index
+   * @param copy  true to make a copy
+   *
+   * @return  array of the values
+   * @throws VisADException  bad range mode
+   */
+  protected double[] unpackOneRangeComp(int comp, boolean copy) throws VisADException {
+    double[] range = null;
+    synchronized (DoubleRange) {
+      if (isMissing()) {
+        range = new double[getLength()];
+        for (int j=0; j<getLength(); j++) {
+          range[j] = Double.NaN;
+        }
+        return range;
+      }
+      int[] index;
+      double[][] range0;
+      for (int i=0; i<TupleDimension; i++) {
+        switch (RangeMode[comp]) {
+          case DOUBLE:
+            if (copy) {
+              range = new double[getLength()];
+              System.arraycopy (DoubleRange[comp], 0, range, 0, getLength());
+            }  else {
+              range = DoubleRange[comp];
+            }
+            break;
+          case FLOAT:
+            range = new double[getLength()];
+            float[] FloatRangeI = FloatRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              range[j] = (double) FloatRangeI[j];
+            }
+            break;
+          case BYTE:
+            index = new int[getLength()];
+            byte[] ByteRangeI = ByteRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) ByteRangeI[j]) - MISSING1 - 1;
+            }
+            range0 = Set.floatToDouble(RangeSet[comp].indexToValue(index));
+            range = range0[0];
+            break;
+          case SHORT:
+            index = new int[getLength()];
+            short[] ShortRangeI = ShortRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) ShortRangeI[j]) - MISSING2 - 1;
+            }
+            range0 = Set.floatToDouble(RangeSet[comp].indexToValue(index));
+            range = range0[0];
+            break;
+          case INT:
+            index = new int[getLength()];
+            int[] IntRangeI = IntRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) IntRangeI[j]) - MISSING4 - 1;
+            }
+            range0 = Set.floatToDouble(RangeSet[comp].indexToValue(index));
+            range = range0[0];
+            break;
+          default:
+            throw new SetException("FlatField.unpackValues: bad RangeMode");
+        }
+      }
+    }
+    return range;
+  }
+
+  /**
+   * Unpack one range component, makes a copy.
+   *
+   * @param comp	component index
+   *
+   * @return  array of the values
+   * @throws VisADException  bad range mode
+   */
+  protected float[] unpackOneFloatRangeComp(int comp) throws VisADException {
+    return unpackOneFloatRangeComp(comp, true);
+  }
+
+  /**
+   * Unpack one range component.
+   *
+   * @param comp	component index
+   * @param copy  true to make a copy
+   *
+   * @return  array of the values
+   * @throws VisADException  bad range mode
+   */
+  protected float[] unpackOneFloatRangeComp(int comp, boolean copy) throws VisADException {
+    float[] range = null;
+    synchronized (FloatRange) {
+      if (isMissing()) {
+        range = new float[getLength()];
+        for (int j=0; j<getLength(); j++) {
+          range[j] = Float.NaN;
+        }
+        return range;
+      }
+      int[] index;
+      float[][] range0;
+      for (int i=0; i<TupleDimension; i++) {
+        switch (RangeMode[comp]) {
+          case DOUBLE:
+            range = new float[getLength()];
+            double[] DoubleRangeI = DoubleRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              range[j] = (float) DoubleRangeI[j];
+            }
+            break;
+          case FLOAT:
+            if (copy) {
+              range = new float[getLength()];
+              System.arraycopy (FloatRange[comp], 0, range, 0, getLength());
+            }  else {
+              range = FloatRange[comp];
+            }
+            break;
+          case BYTE:
+            index = new int[getLength()];
+            byte[] ByteRangeI = ByteRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) ByteRangeI[j]) - MISSING1 - 1;
+            }
+            range0 = RangeSet[comp].indexToValue(index);
+            range = range0[0];
+            break;
+          case SHORT:
+            index = new int[getLength()];
+            short[] ShortRangeI = ShortRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) ShortRangeI[j]) - MISSING2 - 1;
+            }
+            range0 = RangeSet[comp].indexToValue(index);
+            range = range0[0];
+            break;
+          case INT:
+            index = new int[getLength()];
+            int[] IntRangeI = IntRange[comp];
+            for (int j=0; j<getLength(); j++) {
+              index[j] = ((int) IntRangeI[j]) - MISSING4 - 1;
+            }
+            range0 = RangeSet[comp].indexToValue(index);
+            range = range0[0];
+            break;
+          default:
+            throw new SetException("FlatField.unpackValues: bad RangeMode");
+        }
+      }
+    }
+    return range;
+  }
+
+  /** 
+   * Unpack the double value at the sample index.
+   * @param s_index  sample index
+   * @return array of values at that index
+   * @throws VisADException  unable to unpack values
+   */
+  protected double[] unpackValues( int s_index ) throws VisADException {
+    double[] range;
+    synchronized (DoubleRange) {
+      if (isMissing()) {
+        range = new double[TupleDimension];
+        for (int i=0; i<TupleDimension; i++) {
+          range[i] = Double.NaN;
+        }
+        return range;
+      }
+      int[] index;
+      range = new double[TupleDimension];
+      double[][] range0;
+      double[] rangeI;
+      for (int i=0; i<TupleDimension; i++) {
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            range[i] = DoubleRange[i][s_index];
+            break;
+          case FLOAT:
+            range[i] = (double) FloatRange[i][s_index];
+            break;
+          case BYTE:
+            index = new int[1];
+            byte[] ByteRangeI = ByteRange[i];
+            index[0] = ((int) ByteRangeI[s_index]) - MISSING1 - 1;
+            range0 = Set.floatToDouble(RangeSet[i].indexToValue(index));
+            range[i] = range0[0][0];
+            break;
+          case SHORT:
+            index = new int[1];
+            short[] ShortRangeI = ShortRange[i];
+            index[0] = ((int) ShortRangeI[s_index]) - MISSING2 - 1;
+            range0 = Set.floatToDouble(RangeSet[i].indexToValue(index));
+            range[i] = range0[0][0];
+            break;
+          case INT:
+            index = new int[1];
+            int[] IntRangeI = IntRange[i];
+            index[0] = ((int) IntRangeI[s_index]) - MISSING4 - 1;
+            range0 = Set.floatToDouble(RangeSet[i].indexToValue(index));
+            range[i] = range0[0][0];
+            break;
+          default:
+            throw new SetException("FlatField.unpackValues: bad RangeMode");
+        }
+      }
+    }
+    return range;
+  }
+
+  /** 
+   * Unpack the floats at the sample index.
+   * @param s_index  sample index
+   * @return array of values at that index
+   * @throws VisADException  unable to unpack floats
+   */
+  protected float[] unpackFloats( int s_index ) throws VisADException {
+    float[] range;
+    synchronized (FloatRange) {
+      if (isMissing()) {
+        range = new float[TupleDimension];
+        for (int i=0; i<TupleDimension; i++) {
+          range[i] = Float.NaN;
+        }
+        return range;
+      }
+      int[] index;
+      range = new float[TupleDimension];
+      float[][] range0;
+      float[] rangeI;
+      for (int i=0; i<TupleDimension; i++) {
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            range[i] = (float) DoubleRange[i][s_index];
+            break;
+          case FLOAT:
+            range[i] = FloatRange[i][s_index];
+            break;
+          case BYTE:
+            index = new int[1];
+            byte[] ByteRangeI = ByteRange[i];
+            index[0] = ((int) ByteRangeI[s_index]) - MISSING1 - 1;
+            range0 = RangeSet[i].indexToValue(index);
+            range[i] = range0[0][0];
+            break;
+          case SHORT:
+            index = new int[1];
+            short[] ShortRangeI = ShortRange[i];
+            index[0] = ((int) ShortRangeI[s_index]) - MISSING2 - 1;
+            range0 = RangeSet[i].indexToValue(index);
+            range[i] = range0[0][0];
+            break;
+          case INT:
+            index = new int[1];
+            int[] IntRangeI = IntRange[i];
+            index[0] = ((int) IntRangeI[s_index]) - MISSING4 - 1;
+            range0 = RangeSet[i].indexToValue(index);
+            range[i] = range0[0][0];
+            break;
+          default:
+            throw new SetException("FlatField.unpackFloats: bad RangeMode");
+        }
+      }
+    }
+    return range;
+  }
+
+  /**
+   * Returns the range values in their default units as floats.
+   *
+   * @return                    The range values in their default units
+   *                            as determined by the {@link MathType} of
+   *                            the range.  Element <code>[i][j]</code> is
+   *                            the <code>j</code>th sample value of the
+   *                            <code>i</code>th component of the range.
+   * @throws VisADException     if a VisAD object couldn't be created.
+   */
+  public float[][] getFloats() throws VisADException {
+    return getFloats(true);
+  }
+
+  /**
+   * Returns the range values in their default units as floats.
+   *
+   * @param copy                Whether or not the returned array might be the
+   *                            actual range array.  If <code>true</code>, then
+   *                            the returned array will not be the actual range
+   *                            array.
+   * @return                    The range values in their default units
+   *                            as determined by the {@link MathType} of
+   *                            the range.  Element <code>[i][j]</code> is
+   *                            the <code>j</code>th sample value of the
+   *                            <code>i</code>th component of the range.
+   * @throws VisADException     if a VisAD object couldn't be created.
+   */
+  public float[][] getFloats (boolean copy) throws VisADException {
+      pr ("getFloats(" + copy + ")");
+      float[][] values = unpackFloats(copy);
+
+      Unit[] units_out =  ((FunctionType) Type).getFlatRange().getDefaultUnits();
+      //Only converty the data if the output units not equal to the input units
+      if (!Arrays.equals (units_out, RangeUnits)) {
+          values =  Unit.convertTuple(values, RangeUnits, units_out);
+      }
+      return values;
+  }
+
+  /**
+   * Returns the range values in their default units as doubles.
+   *
+   * @return                    The range values in their default units
+   *                            as determined by the {@link MathType} of
+   *                            the range.  Element <code>[i][j]</code> is
+   *                            the <code>j</code>th sample value of the
+   *                            <code>i</code>th component of the range.
+   * @throws VisADException     if a VisAD object couldn't be created.
+   */
+  public double[][] getValues() throws VisADException {
+    return getValues(true);
+  }
+
+  /**
+   * Returns the range values in their default units as doubles.
+   *
+   * @param copy                Whether or not the returned array might be the
+   *                            actual range array.  If <code>true</code>, then
+   *                            the returned array will not be the actual range
+   *                            array.
+   * @return                    The range values in their default units
+   *                            as determined by the {@link MathType} of
+   *                            the range.  Element <code>[i][j]</code> is
+   *                            the <code>j</code>th sample value of the
+   *                            <code>i</code>th component of the range.
+   * @throws VisADException     if a VisAD object couldn't be created.
+   */
+  public double[][] getValues(boolean copy) throws VisADException {
+      pr ("getValues(" + copy + ")");
+      double[][] values = unpackValues(copy);
+      Unit[] units_out =
+          ((FunctionType) Type).getFlatRange().getDefaultUnits();
+      double[][]result =  Unit.convertTuple(values, RangeUnits, units_out);
+      return result;
+  }
+
+  /** 
+   * Get String values for Text components 
+   * @return null (there are none for FlatFields) 
+   * @throws VisADException     but doesn't happen
+   * @throws RemoteException    but doesn't happen
+   */
+  public String[][] getStringValues()
+         throws VisADException, RemoteException {
+    return null;
+  }
+
+  /** 
+   * Get values for 'Flat' components in default range Unit-s. 
+   * @return                    The range values in their default units
+   *                            as determined by the {@link MathType} of
+   *                            the range.  Element <code>[i]</code> is
+   *                            the value of the <code>i</code>th component 
+   *                            of the flattened range.
+   * @throws VisADException     if a VisAD object couldn't be created.
+   */
+  /*- TDR June 1998  */
+  public double[] getValues( int s_index ) throws VisADException {
+    double[] values = new double[TupleDimension];
+    values = unpackValues( s_index );
+    double[][] n_values = new double[TupleDimension][1];
+    for ( int ii = 0; ii < TupleDimension; ii++ ) {
+      n_values[ii][0] = values[ii];
+    }
+    Unit[] units_out =
+      ((FunctionType) Type).getFlatRange().getDefaultUnits();
+    double[][] r_values = Unit.convertTuple(n_values, RangeUnits, units_out);
+    for ( int ii = 0; ii < values.length; ii++ ) {
+      values[ii] = r_values[ii][0];
+    }
+    return values;
+  }
+
+  /** 
+   * Get default range Unit-s for 'Flat' components.
+   * @return array of the default Units for each of the RealTypes 
+   *         components in the flattened range. 
+   */
+  public Unit[] getDefaultRangeUnits() {
+    return ((FunctionType) Type).getFlatRange().getDefaultUnits();
+  }
+
+  /** 
+   * Get the range value at the index-th sample.
+   * FlatField does not override evaluate, but the correctness
+   * of FlatField.evaluate depends on overriding getSample 
+   * @return Data object (Real, RealTuple, or Tuple) corresponding to
+   *         the range at the index-th sample.
+   * @throws VisADException  problem getting data
+   * @throws RemoteException problem getting data from remote object
+   */
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    int[] inds;
+    if (isMissing() || index < 0 || index >= getLength()) {
+      return ((FunctionType) Type).getRange().missingData();
+    }
+    double[][] range = new double[TupleDimension][1];
+    double[][] range1;
+    synchronized (DoubleRange) {
+      for (int i=0; i<TupleDimension; i++) {
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            range[i][0] = DoubleRange[i][index];
+            break;
+          case FLOAT:
+            range[i][0] = (double) FloatRange[i][index];
+            break;
+          case BYTE:
+            inds = new int[1];
+            inds[0] = ((int) ByteRange[i][index]) - MISSING1 - 1;
+            range1 = Set.floatToDouble(RangeSet[i].indexToValue(inds));
+            range[i] = range1[0];
+            break;
+          case SHORT:
+            inds = new int[1];
+            inds[0] = ((int) ShortRange[i][index]) - MISSING2 - 1;
+            range1 = Set.floatToDouble(RangeSet[i].indexToValue(inds));
+            range[i] = range1[0];
+            break;
+          case INT:
+            inds = new int[1];
+            inds[0] = ((int) IntRange[i][index]) - MISSING4 - 1;
+            range1 = Set.floatToDouble(RangeSet[i].indexToValue(inds));
+            range[i] = range1[0];
+            break;
+          default:
+            throw new SetException("FlatField.getSample: bad RangeMode");
+        }
+      }
+    }
+
+    MathType RangeType = ((FunctionType) Type).getRange();
+    if (RangeType instanceof RealType) {
+      return new Real((RealType) RangeType, range[0][0],
+                      RangeUnits[0], RangeErrors[0]);
+    }
+    else if (RangeType instanceof RealTupleType) {
+      Real[] reals = new Real[TupleDimension];
+      for (int j=0; j<TupleDimension; j++) {
+        MathType type = ((RealTupleType) RangeType).getComponent(j);
+        reals[j] = new Real((RealType) type, range[j][0],
+                            RangeUnits[j], RangeErrors[j]);
+      }
+      return new RealTuple((RealTupleType) RangeType, reals,
+                           RangeCoordinateSystem);
+    }
+    else { // RangeType is a Flat TupleType
+      int n = ((TupleType) RangeType).getDimension();
+      int j = 0;
+      Data[] datums = new Data[n];
+      for (int i=0; i<n; i++) {
+        MathType type = ((TupleType) RangeType).getComponent(i);
+        if (type instanceof RealType) {
+          datums[i] = new Real((RealType) type, range[j][0],
+                               RangeUnits[j], RangeErrors[j]);
+          j++;
+        }
+        else { // type instanceof RealTupleType
+          int m = ((RealTupleType) type).getDimension();
+          Real[] reals = new Real[m];
+          for (int k=0; k<m; k++) {
+            RealType ctype = (RealType) ((RealTupleType) type).getComponent(k);
+            reals[k] = new Real(ctype, range[j][0],
+                                RangeUnits[j], RangeErrors[j]);
+            j++;
+          }
+          datums[i] = new RealTuple((RealTupleType) type, reals,
+                                    RangeCoordinateSystems[i]);
+        }
+      }
+      return new Tuple(datums, false);
+    }
+  }
+
+  /**
+   * A stub routine which simply invokes 
+   * {@link #getSample(int) getSample} to override 
+   * {@link FieldImpl#getSample(int, boolean) FieldImpl.getSample}
+   * @param index index of requested range sample
+   * @throws VisADException     if a VisAD object couldn't be created.
+   * @throws RemoteException    if the Remote object couldn't be created.
+   */
+  public Data getSample(int index, boolean metadataOnly)
+         throws VisADException, RemoteException {
+    return getSample(index);
+  }
+
+  /** 
+   * Set the range value at the index-th sample 
+   * @param  index   index to set
+   * @param  range   range value to set
+   * @param  copy    flag to copy values - meaningless for FlatField
+   * @throws VisADException     if range's MathType is incompatible or
+   *                            some other error.
+   * @throws RemoteException    if the Remote object couldn't be created.
+   */
+  public void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    setSample(index, range); // copy flag meaningless for FlatField
+  }
+
+  /** 
+   * Set the range value at the index-th sample 
+   * @param  index   index to set
+   * @param  range   range value to set
+   * @throws VisADException     if range's MathType is incompatible or
+   *                            some other error.
+   * @throws RemoteException    if the Remote object couldn't be created.
+   */
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+      pr ("setSample");
+
+
+    double[][] values;
+    int[] indices;
+    if (getDomainSet() == null) {
+      throw new FieldException("Field.setSample: DomainSet undefined");
+    }
+    if (range != null &&
+        !((FunctionType) Type).getRange().equals(range.getType())) {
+      throw new TypeException("Field.setSample: sample range type " +
+                              range.getType() +
+                              " does not match expected type " +
+                              ((FunctionType) Type).getRange());
+    }
+    if (index < 0 || index >= getLength()) return;
+
+    // disect range into doubles
+    double[] vals = new double[TupleDimension];
+    // holder for errors of transformed values;
+    ErrorEstimate[] errors_out = new ErrorEstimate[TupleDimension];
+    if (range == null) {
+      for (int j=0; j<TupleDimension; j++) {
+        vals[j] = Double.NaN;
+      }
+    }
+    else if (range instanceof Real) {
+      vals[0] = ((Real) range).getValue();
+      vals = Unit.transformUnits (
+                        RangeUnits[0], errors_out,
+                        ((Real) range).getUnit(), ((Real) range).getError(),
+                        vals);
+    }
+    else if (range instanceof RealTuple) {
+      double[][] value = new double[TupleDimension][1];
+      for (int j=0; j<TupleDimension; j++) {
+        value[j][0] = ((Real) ((RealTuple) range).getComponent(j)).getValue();
+      }
+      value = CoordinateSystem.transformCoordinates(
+                        (RealTupleType) ((FunctionType) Type).getRange(),
+                        RangeCoordinateSystem, RangeUnits, errors_out,
+                        (RealTupleType) range.getType(),
+                        ((RealTuple) range).getCoordinateSystem(),
+                        ((RealTuple) range).getTupleUnits(),
+                        ((RealTuple) range).getErrors(), value);
+      for (int j=0; j<TupleDimension; j++) {
+        vals[j] = value[j][0];
+      }
+    }
+    else { // range is Flat Tuple
+      MathType RangeType = ((FunctionType) Type).getRange();
+      int n = ((TupleIface) range).getDimension();
+      int j = 0;
+      for (int i=0; i<n; i++) {
+        Data component = ((TupleIface) range).getComponent(i);
+        if (component instanceof Real) {
+          double[] value = new double[1];
+          value[0] = ((Real) component).getValue();
+          ErrorEstimate[] sub_errors_out = new ErrorEstimate[1];
+          value = Unit.transformUnits(
+                            RangeUnits[0], sub_errors_out,
+                            ((Real) component).getUnit(),
+                            ((Real) component).getError(),
+                            value);
+          vals[j] = value[0];
+          errors_out[j] = sub_errors_out[0];
+          j++;
+        }
+        else {
+          int m = ((RealTuple) component).getDimension();
+          double[][] value = new double[m][1];
+          Unit[] units_out = new Unit[m];
+          ErrorEstimate[] sub_errors_out = ((RealTuple) component).getErrors();
+          for (int k=0; k<m; k++) {
+            value[k][0] =
+              ((Real) ((RealTuple) component).getComponent(k)).getValue();
+            units_out[k] = RangeUnits[j + k];
+          }
+          value = CoordinateSystem.transformCoordinates(
+                        (RealTupleType) ((TupleType) RangeType).getComponent(i),
+                        RangeCoordinateSystems[i], units_out, sub_errors_out,
+                        (RealTupleType) component.getType(),
+                        ((RealTuple) component).getCoordinateSystem(),
+                        ((RealTuple) component).getTupleUnits(),
+                        ((RealTuple) component).getErrors(), value);
+          for (int k=0; k<m; k++) {
+            vals[j] = value[k][0];
+            errors_out[j] = sub_errors_out[k];
+            j++;
+          }
+        }
+      }
+    }
+    // now errors_out contains the transformed errors for the sample
+    // in range - these may be mixed with RangeErrors
+    // incs is counter for increase / decrease in NumberNotMissing
+    int[] incs = new int[TupleDimension];
+
+    synchronized (DoubleRange) {
+      for (int i=0; i<TupleDimension; i++) {
+        // test for missing
+        incs[i] = (vals[i] != vals[i]) ? 0 : 1;
+        switch (RangeMode[i]) {
+          case DOUBLE:
+            if (DoubleRange[i] == null) {
+              DoubleRange[i] = new double[getLength()];
+              for (int j=0; j<getLength(); j++) DoubleRange[i][j] = Double.NaN;
+            }
+            // test for missing
+            incs[i] -= (DoubleRange[i][index] != DoubleRange[i][index]) ? 0 : 1;
+            DoubleRange[i][index] = vals[i];
+            break;
+          case FLOAT:
+            if (FloatRange[i] == null) {
+              FloatRange[i] = new float[getLength()];
+              for (int j=0; j<getLength(); j++) FloatRange[i][j] = Float.NaN;
+            }
+            // test for missing
+            incs[i] -= (FloatRange[i][index] != FloatRange[i][index]) ? 0 : 1;
+            FloatRange[i][index] = (float) vals[i];
+            break;
+          case BYTE:
+            values = new double[1][1];
+            values[0][0] = vals[i];
+            indices = RangeSet[i].valueToIndex(Set.doubleToFloat(values));
+            if (ByteRange[i] == null) {
+              ByteRange[i] = new byte[getLength()];
+              for (int j=0; j<getLength(); j++) ByteRange[i][j] = (byte) MISSING1;
+            }
+            incs[i] -= (ByteRange[i][index] == (byte) MISSING1) ? 0 : 1;
+            ByteRange[i][index] = (byte) (indices[0] + MISSING1 + 1);
+            break;
+          case SHORT:
+            values = new double[1][1];
+            values[0][0] = vals[i];
+            indices = RangeSet[i].valueToIndex(Set.doubleToFloat(values));
+            if (ShortRange[i] == null) {
+              ShortRange[i] = new short[getLength()];
+              for (int j=0; j<getLength(); j++) ShortRange[i][j] = (short) MISSING2;
+            }
+            incs[i] -= (ShortRange[i][index] == (short) MISSING2) ? 0 : 1;
+            ShortRange[i][index] = (short) (indices[0] + MISSING2 + 1);
+            break;
+          case INT:
+            values = new double[1][1];
+            values[0][0] = vals[i];
+            indices = RangeSet[i].valueToIndex(Set.doubleToFloat(values));
+            if (IntRange[i] == null) {
+              IntRange[i] = new int[getLength()];
+              for (int j=0; j<getLength(); j++) IntRange[i][j] = MISSING4;
+            }
+            incs[i] -= (IntRange[i][index] == (int) MISSING4) ? 0 : 1;
+            IntRange[i][index] = indices[0] + MISSING4 + 1;
+            break;
+          default:
+            throw new SetException("FlatField.setSample: bad RangeMode");
+        }
+      }
+      synchronized (RangeErrors) {
+        for (int i=0; i<TupleDimension; i++) {
+          RangeErrors[i] = new ErrorEstimate(RangeErrors[i],
+                               errors_out[i], vals[i], incs[i]);
+        }
+      }
+    }
+    clearMissing();
+    notifyReferences();
+  }
+
+  /**
+   * Sets various arrays of range values to missing.
+   *
+   * @throws VisADException if a {@link Set#getLength()} invocation on a range
+   *                        set fails.
+   */
+  protected void nullRanges() throws VisADException {
+    synchronized (DoubleRange) {
+      // DoubleRange = new double[TupleDimension][];
+      for (int i=0; i<TupleDimension; i++) DoubleRange[i] = null; // WLH 12 Jan 2001
+
+
+      FloatRange = new float[TupleDimension][];
+      LongRange = new long[TupleDimension][];
+      IntRange = new int[TupleDimension][];
+      ShortRange = new short[TupleDimension][];
+      ByteRange = new byte[TupleDimension][];
+
+      for (int i=0; i<TupleDimension; i++) {
+        if (RangeSet[i] instanceof DoubleSet) {
+            RangeMode[i] = DOUBLE;
+        }
+        else if (RangeSet[i] instanceof FloatSet) {
+          RangeMode[i] = FLOAT;
+        }
+        else {
+          int SetLength = RangeSet[i].getLength();
+          if (SetLength < 256) {
+            RangeMode[i] = BYTE;
+          }
+          else if (SetLength < 65536) {
+            RangeMode[i] = SHORT;
+          }
+          else {
+            RangeMode[i] = INT;
+          }
+        }
+      }
+    }
+  }
+
+  /** 
+   * Test whether range values are missing 
+   * @return  true if the range values have not been set
+   */
+  public boolean isMissing() {
+    synchronized (DoubleRange) {
+      return MissingFlag;
+    }
+  }
+
+  /** 
+   * Mark this FlatField as non-missing 
+   */
+  public void clearMissing() {
+    synchronized (DoubleRange) {
+      MissingFlag = false;
+    }
+  }
+
+  /** 
+   * Return new Field with value 'this op data'.
+   * test for various relations between types of this and data;
+   * note return type may not be FlatField,
+   * in case data is a Field and this matches its range 
+   * @param data  object to operate on
+   * @param op  operation to perform (e.g. ADD, SUB, MULT)
+   * @param new_type  MathType of new object
+   * @param sampling_mode  sampling mode to use 
+   *                       (e.g., NEAREST_NEIGHBOR, WEIGHTED_AVERAGE)
+   * @param error_mode    error estimate mode (e.g., NO_ERROR, DEPENDENT,
+   *                      independent)
+   * @return new Field corresponding to the requested operation
+   * @throws VisADException   couldn't create new VisAD object
+   * @throws RemoteException  couldn't create new Remote object
+   */
+  /*- TDR May 1998
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+   */
+  public Data binary(Data data, int op, MathType new_type,
+                     int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if ( new_type == null ) {
+      throw new TypeException("binary: new_type may not be null");
+    }
+
+
+
+    if (data instanceof Field) {
+      /*- TDR June  1998 */
+      FunctionType data_type = (FunctionType) data.getType();
+      if ((data_type.getRange()).equalsExceptName(Type)) {
+        if ( !data_type.equalsExceptName(new_type)) {
+          throw new TypeException("binary: new_type doesn't match return type" );
+        }
+      /*- end   */
+        // this matches range type of data;
+        // note invertOp to reverse order of operands
+        /*- TDR June  1998
+        return data.binary(this, invertOp(op), sampling_mode, error_mode);
+        */
+        return data.binary(this, invertOp(op), new_type, sampling_mode, error_mode);
+      }
+      else if (!Type.equalsExceptName(data.getType())) {
+        throw new TypeException("FlatField.binary: types don't match");
+      }
+      /*- TDR June 1998 */
+      if ( !Type.equalsExceptName(new_type) ) {
+        throw new TypeException();
+      }
+      /*- end */
+      if (((Field) data).isFlatField()) {
+        // force (data instanceof FlatField) to be true
+        data = data.local();
+      }
+      else {
+        // this and data have same type, but this is Flat and data is not
+        /*- TDR June  1998
+        return convertToField().binary(data, op, sampling_mode, error_mode);
+        */
+        return convertToField().binary(data, op, new_type, sampling_mode, error_mode);
+      }
+
+      // use DoubleSet rather than RangeSet for intermediate computation results
+      if (isMissing() || data.isMissing()) return new_type.missingData();
+
+      // resample data if needed
+      data = ((FlatField) data).resample(getDomainSet(), sampling_mode, error_mode);
+
+      // get values from two FlatField's
+      double[][] thatValuesD = null;
+      double[][] thisValuesD = null;
+      float[][]  thatValuesF = null;
+      float[][]  thisValuesF = null;
+
+      //Should this operation use doubles or floats?
+      boolean binaryDouble = (shouldBeDouble () && ((FlatField)data).shouldBeDouble ());
+      if (binaryDouble) {
+          thatValuesD = ((FlatField) data).unpackValues ();
+          thisValuesD = unpackValues();
+      } else {
+          thatValuesF = ((FlatField) data).unpackFloats ();
+          thisValuesF = unpackFloats ();
+      }
+
+
+      // initialize for Unit and ErrorEstimate calculations
+      Unit[][] temp_units = ((FlatField) data).getRangeUnits();
+      Unit[] units_in = new Unit[temp_units.length];
+      for (int i=0; i<temp_units.length; i++) {
+        units_in[i] = temp_units[i][0];
+      }
+      ErrorEstimate[] errors_in = ((FlatField) data).getRangeErrors();
+      // substitute arrays of nulls for null arrays
+      if (units_in == null) units_in = new Unit[TupleDimension];
+      if (errors_in == null) errors_in = new ErrorEstimate[TupleDimension];
+      Unit[] units_out = new Unit[TupleDimension];
+      ErrorEstimate[] errors_out = new ErrorEstimate[TupleDimension];
+
+      // apply any range coordinate transformations
+      if (((FunctionType) Type).getReal()) {
+        // transformCoordinatesFreeUnits does not impose any
+        // particular Units on the final value2
+          CoordinateSystem[] cs = ((FlatField) data).getRangeCoordinateSystem();
+          if (thatValuesD != null) {
+              thatValuesD = CoordinateSystem.transformCoordinatesFreeUnits(
+                   ((FunctionType) Type).getFlatRange(),
+                   RangeCoordinateSystem, units_out, errors_out,
+                   ((FunctionType) data.getType()).getFlatRange(),
+                   cs[0], units_in, errors_in, thatValuesD);
+          } 
+          if (thatValuesF != null) {
+              thatValuesF = CoordinateSystem.transformCoordinatesFreeUnits(
+                   ((FunctionType) Type).getFlatRange(),
+                   RangeCoordinateSystem, units_out, errors_out,
+                   ((FunctionType) data.getType()).getFlatRange(),
+                   cs[0], units_in, errors_in, thatValuesF);
+          }
+
+      }      else if (RangeCoordinateSystems != null) {
+        TupleType rtype =
+          (TupleType) ((FunctionType) Type).getRange();
+        TupleType dtype =
+          (TupleType) ((FunctionType) data.getType()).getRange();
+        int n = rtype.getDimension();
+        int j = 0;
+        for (int i=0; i<n; i++) {
+          MathType crtype = rtype.getComponent(i);
+          MathType cdtype = dtype.getComponent(i);
+          if (crtype instanceof RealTupleType) {
+            int m = ((RealTupleType) crtype).getDimension();
+
+
+            double[][] tmpValuesD = null;
+            float [][] tmpValuesF = null;
+
+            if (thatValuesD != null) {
+                tmpValuesD = new double[m][];
+            } 
+            if (thatValuesF != null) {
+                tmpValuesF = new float[m][];
+            } 
+
+
+            Unit[] sub_units_out = new Unit[m];
+            Unit[] sub_units_in = new Unit[m];
+            ErrorEstimate[] sub_errors_out = new ErrorEstimate[m];
+            ErrorEstimate[] sub_errors_in = new ErrorEstimate[m]; 
+           for (int k=0; k<m; k++) {
+                if (tmpValuesD != null)
+                    tmpValuesD[k] = thatValuesD[j + k];
+                if (tmpValuesF!= null)
+                    tmpValuesF[k] = thatValuesF[j + k];
+              sub_units_in[k] = units_in[j + k];
+              sub_errors_in[k] = errors_in[j + k];
+            }
+            CoordinateSystem[] cs = ((FlatField) data).getRangeCoordinateSystem(i);
+            if (tmpValuesD != null) {
+                tmpValuesD = CoordinateSystem.transformCoordinatesFreeUnits(
+                       (RealTupleType) crtype, RangeCoordinateSystems[i],
+                       sub_units_out, sub_errors_out,
+                       (RealTupleType) cdtype,
+                       cs[0], sub_units_in, sub_errors_in, tmpValuesD);
+            } 
+            if (tmpValuesF != null) {
+                tmpValuesF = CoordinateSystem.transformCoordinatesFreeUnits(
+                       (RealTupleType) crtype, RangeCoordinateSystems[i],
+                       sub_units_out, sub_errors_out,
+                       (RealTupleType) cdtype,
+                       cs[0], sub_units_in, sub_errors_in, tmpValuesF);
+
+            }
+            for (int k=0; k<m; k++) {
+                if (thatValuesD !=null)
+                    thatValuesD[j + k] = tmpValuesD[k];
+                if (thatValuesF !=null)
+                    thatValuesF[j + k] = tmpValuesF[k];
+              errors_out[j + k] = sub_errors_out[k];
+              units_out[j + k] = sub_units_out[k];
+            }
+            j += m;
+          }
+          else {
+            errors_out[j] = errors_in[j];
+            units_out[j] = units_in[j];
+            j++;
+          }
+        }
+      }
+
+
+
+
+      /*
+       * Some variable renamings for clarity in the following:
+       */
+      Unit[]          thisUnits = (Unit[])RangeUnits.clone();
+      Unit[]          thatUnits = units_out;
+
+
+
+      ErrorEstimate[] thisErrs = (ErrorEstimate[])RangeErrors.clone();
+      ErrorEstimate[] thatErrs = errors_out;
+      /*
+       * Default values. The following switch might set "outUnits".
+       */
+      Unit[]          outUnits = new Unit[TupleDimension];
+
+      /*
+       * Set the output values.  NOTE: The input array (thisValues) is also
+       * used to store the output numeric values.
+       */
+
+      switch (op) {
+        case ADD:
+        case SUBTRACT:
+        case INV_SUBTRACT:
+        case MAX:
+        case MIN:
+          for (int j=0; j<TupleDimension; j++) {
+            if (thisUnits[j] == null || thatUnits[j] == null) {
+              outUnits[j] = null;
+            }
+            else if (thisUnits[j] == CommonUnit.promiscuous) {
+              outUnits[j] = thatUnits[j].getAbsoluteUnit();
+            }
+            else if (thatUnits[j] == CommonUnit.promiscuous) {
+              outUnits[j] = thisUnits[j].getAbsoluteUnit();
+            }
+            else {
+              try {
+                outUnits[j] = thisUnits[j].getAbsoluteUnit();
+                boolean convertThis = !outUnits[j].equals(thisUnits[j]);
+                boolean convertThat = !outUnits[j].equals(thatUnits[j]);
+
+                if (convertThis) {
+                    if (thisValuesD != null)
+                        thisValuesD[j] = outUnits[j].toThis (thisValuesD[j], thisUnits[j]);
+                    if (thisValuesF != null)
+                        thisValuesF[j] = outUnits[j].toThis (thisValuesF[j], thisUnits[j]);
+                }
+
+                if (convertThat) {
+                    if (thatValuesD!=null)
+                        thatValuesD[j] = outUnits[j].toThis (thatValuesD[j], thatUnits[j]);
+                    if (thatValuesF!=null)
+                        thatValuesF[j] = outUnits[j].toThis (thatValuesF[j], thatUnits[j]);
+                }
+
+                if (error_mode != NO_ERRORS &&
+                  thisErrs[j] != null && thatErrs[j] != null) {
+
+                  if (convertThis) {
+                    Unit        errUnit = thisErrs[j].getUnit();
+
+                    if (errUnit == null)
+                      errUnit = thisUnits[j];
+
+                    double err = 0.5 * thisErrs[j].getErrorValue();
+                    double mean = thisErrs[j].getMean();
+                    double a = outUnits[j].toThis(mean + err, errUnit);
+                    double b = outUnits[j].toThis(mean - err, errUnit);
+                    mean = (a + b) / 2;
+                    err = Math.abs(a - b);
+                    thisErrs[j] = new ErrorEstimate(mean, err, outUnits[j]);
+                  }
+
+                  if (convertThat) {
+                    Unit        errUnit = thatErrs[j].getUnit();
+
+                    if (errUnit == null)
+                      errUnit = thatUnits[j];
+
+                    double err = 0.5 * thatErrs[j].getErrorValue();
+                    double mean = thatErrs[j].getMean();
+                    double a = outUnits[j].toThis(mean + err, errUnit);
+                    double b = outUnits[j].toThis(mean - err, errUnit);
+                    mean = (a + b) / 2;
+                    err = Math.abs(a - b);
+                    thatErrs[j] = new ErrorEstimate(mean, err, outUnits[j]);
+                  }
+                }
+              }
+              catch (UnitException e) {         // inconvertible units
+                outUnits[j] = null;
+              }
+            }
+            double[]thisLineD = null;
+            double[]thatLineD = null;
+            float[]thisLineF = null;
+            float[]thatLineF = null;
+            switch (op) {
+              case ADD:
+                  if (thisValuesD != null) {
+                      double[]thisValuesLineD = thisValuesD[j];
+                      double[]thatValuesLineD = thatValuesD[j];
+                      for (int i=0; i<getLength(); i++) {
+                          thisValuesLineD[i] += thatValuesLineD[i];
+                      }
+                  }
+                  if (thisValuesF != null) {
+                      float[]thisValuesLineF = thisValuesF[j];
+                      float[]thatValuesLineF = thatValuesF[j];
+                      for (int i=0; i<getLength(); i++) {
+                          thisValuesLineF[i] += thatValuesLineF[i];
+                      }
+                  }
+
+                break;
+              case SUBTRACT:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++) 
+                          thisValuesD[j][i] -= thatValuesD[j][i];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++) 
+                          thisValuesF[j][i] -= thatValuesF[j][i];
+                break;
+              case INV_SUBTRACT:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++) 
+                          thisValuesD[j][i] = thatValuesD[j][i] - thisValuesD[j][i];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++) 
+                          thisValuesF[j][i] = thatValuesF[j][i] - thisValuesF[j][i];
+                break;
+              case MAX:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = Math.max(thisValuesD[j][i], thatValuesD[j][i]);
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = Math.max(thisValuesF[j][i], thatValuesF[j][i]);
+                break;
+              case MIN:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = Math.min (thisValuesD[j][i], thatValuesD[j][i]);
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = Math.min (thisValuesF[j][i], thatValuesF[j][i]);
+                break;
+            }
+          }
+          break;
+
+
+        case MULTIPLY:
+        case DIVIDE:
+        case INV_DIVIDE:
+          for (int j=0; j<TupleDimension; j++) {
+            if (thisUnits[j] != null) {
+              Unit absUnit = thisUnits[j].getAbsoluteUnit();
+              if (!absUnit.equals(thisUnits[j])) {
+                  if (thisValuesD != null)
+                      thisValuesD[j] = absUnit.toThis (thisValuesD[j], thisUnits[j]);
+                  if (thisValuesF != null)
+                      thisValuesF[j] = absUnit.toThis (thisValuesF[j], thisUnits[j]);
+                  thisUnits[j] = absUnit;
+              }
+            }
+            if (thatUnits[j] != null) {
+              Unit absUnit = thatUnits[j].getAbsoluteUnit();
+              if (!absUnit.equals(thatUnits[j])) {
+                  if (thatValuesD != null)
+                      thatValuesD[j] = absUnit.toThis(thatValuesD[j], thatUnits[j]);
+                  if (thatValuesF != null)
+                      thatValuesF[j] = absUnit.toThis(thatValuesF[j], thatUnits[j]);
+                  thatUnits[j] = absUnit;
+              }
+            }
+            if (thisUnits[j] == null || thatUnits[j] == null) {
+              outUnits[j] = null;
+            }
+            else {
+              switch(op) {
+                case MULTIPLY:
+                  outUnits[j] = 
+                    thisUnits[j].equals(CommonUnit.promiscuous)
+                      ? thatUnits[j]
+                      : thatUnits[j].equals(CommonUnit.promiscuous)
+                        ? thisUnits[j]
+                        : thisUnits[j].multiply(thatUnits[j]);
+                  break;
+                case DIVIDE:
+                  outUnits[j] =
+                    thatUnits[j].equals(CommonUnit.promiscuous)
+                      ? thisUnits[j]
+                      : thisUnits[j].divide(thatUnits[j]);
+                  break;
+                case INV_DIVIDE:
+                  outUnits[j] = 
+                    thisUnits[j].equals(CommonUnit.promiscuous)
+                      ? thatUnits[j]
+                      : thatUnits[j].divide(thisUnits[j]);
+                  break;
+              }
+            }
+            switch(op) {
+              case MULTIPLY:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] *= thatValuesD[j][i];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] *= thatValuesF[j][i];
+                break;
+              case DIVIDE:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] /= thatValuesD[j][i];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] /= thatValuesF[j][i];
+                break;
+              case INV_DIVIDE:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = thatValuesD[j][i] / thisValuesD[j][i];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = thatValuesF[j][i] / thisValuesF[j][i];
+                break;
+            }
+          }
+          break;
+
+        case POW:
+          for (int j=0; j<TupleDimension; j++) {
+            if (thisUnits[j] != null) {
+              Unit absUnit = thisUnits[j].getAbsoluteUnit();
+              if (!absUnit.equals(thisUnits[j])) {
+                  if (thisValuesD != null)
+                      thisValuesD[j] = absUnit.toThis(thisValuesD[j], thisUnits[j]);
+                  if (thisValuesF != null)
+                      thisValuesF[j] = absUnit.toThis(thisValuesF[j], thisUnits[j]);
+                  thisUnits[j] = absUnit;
+              }
+            }
+            if (thatUnits[j] != null && 
+                !CommonUnit.promiscuous.equals(thatUnits[j])) {
+                Unit absUnit = thatUnits[j].getAbsoluteUnit();
+                if (!absUnit.equals(thatUnits[j])) {
+                    if (thatValuesD != null)
+                        thatValuesD[j] = absUnit.toThis(thatValuesD[j], thatUnits[j]);
+                    if (thatValuesF != null)
+                        thatValuesF[j] = absUnit.toThis(thatValuesF[j], thatUnits[j]);
+                    thatUnits[j] = absUnit;
+              }
+            }
+            if (thisUnits[j] != null && (
+                thisUnits[j].equals(CommonUnit.promiscuous) ||
+                thisUnits[j].equals(CommonUnit.dimensionless))) {
+              outUnits[j] = thisUnits[j];
+            }
+            else {
+              outUnits[j] = null;
+            }
+            if (thisValuesD != null)
+                for (int i=0; i<getLength(); i++)
+                    thisValuesD[j][i] = (double)(Math.pow(thisValuesD[j][i], thatValuesD[j][i]));
+            if (thisValuesF != null)
+                for (int i=0; i<getLength(); i++)
+                    thisValuesF[j][i] = (float)(Math.pow(thisValuesF[j][i], thatValuesF[j][i]));
+          }
+          break;
+
+        case INV_POW:
+          for (int j=0; j<TupleDimension; j++) {
+            if (thatUnits[j] != null) {
+              Unit absUnit = thatUnits[j].getAbsoluteUnit();
+              if (!absUnit.equals(thatUnits[j])) {
+                  if (thatValuesD != null)
+                      thatValuesD[j] = absUnit.toThis(thatValuesD[j], thatUnits[j]);
+                  if (thatValuesF != null)
+                      thatValuesF[j] = absUnit.toThis(thatValuesF[j], thatUnits[j]);
+                  thatUnits[j] = absUnit;
+              }
+            }
+            if (thisUnits[j] != null && 
+                !CommonUnit.promiscuous.equals(thisUnits[j])) {
+              Unit absUnit = thisUnits[j].getAbsoluteUnit();
+              if (!absUnit.equals(thisUnits[j])) {
+                  if (thisValuesD != null)
+                      thisValuesD[j] = absUnit.toThis(thisValuesD[j], thisUnits[j]);
+                  if (thisValuesF != null)
+                      thisValuesF[j] = absUnit.toThis(thisValuesF[j], thisUnits[j]);
+                  thisUnits[j] = absUnit;
+              }
+            }
+            if (thatUnits[j] != null && (
+                thatUnits[j].equals(CommonUnit.promiscuous) ||
+                thatUnits[j].equals(CommonUnit.dimensionless))) {
+              outUnits[j] = thatUnits[j];
+            }
+            else {
+              outUnits[j] = null;
+            }
+            if (thisValuesD != null)
+                for (int i=0; i<getLength(); i++)
+                    thisValuesD[j][i] = (double)(Math.pow(thatValuesD[j][i], thisValuesD[j][i]));
+            if (thisValuesF != null)
+                for (int i=0; i<getLength(); i++)
+                    thisValuesF[j][i] = (float)(Math.pow(thatValuesF[j][i], thisValuesF[j][i]));
+          }
+          break;
+
+        case ATAN2:
+        case ATAN2_DEGREES:
+        case INV_ATAN2:
+        case INV_ATAN2_DEGREES:
+        case REMAINDER:
+        case INV_REMAINDER:
+          for (int j=0; j<TupleDimension; j++) {
+            if (thisUnits[j] != null && thatUnits[j] != null) {
+              Unit absUnit = thisUnits[j].getAbsoluteUnit();
+              if (!absUnit.equals(thisUnits[j])) {
+                  if (thisValuesD != null)
+                      thisValuesD[j] = absUnit.toThis(thisValuesD[j], thisUnits[j]);
+                  if (thisValuesF != null)
+                      thisValuesF[j] = absUnit.toThis(thisValuesF[j], thisUnits[j]);
+                  thisUnits[j] = absUnit;
+              }
+              if (!absUnit.equals(thatUnits[j])) {
+                  if (thatValuesD != null)
+                      thatValuesD[j] = absUnit.toThis(thatValuesD[j], thatUnits[j]);
+                  if (thatValuesF != null)
+                      thatValuesF[j] = absUnit.toThis(thatValuesF[j], thatUnits[j]);
+                  thatUnits[j] = absUnit;
+              }
+            }
+            switch(op) {
+              case ATAN2:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Math.atan2(thisValuesD[j][i], thatValuesD[j][i]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Math.atan2(thisValuesF[j][i], thatValuesF[j][i]));
+                  outUnits[j] = CommonUnit.radian;
+                  break;
+              case ATAN2_DEGREES:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Data.RADIANS_TO_DEGREES * Math.atan2(thisValuesD[j][i], thatValuesD[j][i]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Data.RADIANS_TO_DEGREES * Math.atan2(thisValuesF[j][i], thatValuesF[j][i]));
+                outUnits[j] = CommonUnit.degree;
+                break;
+              case INV_ATAN2:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Math.atan2(thatValuesD[j][i], thisValuesD[j][i]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Math.atan2(thatValuesF[j][i], thisValuesF[j][i]));
+                  outUnits[j] = CommonUnit.radian;
+                break;
+              case INV_ATAN2_DEGREES:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Data.RADIANS_TO_DEGREES * Math.atan2(thatValuesD[j][i], thisValuesD[j][i]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Data.RADIANS_TO_DEGREES * Math.atan2(thatValuesF[j][i], thisValuesF[j][i]));
+
+                  outUnits[j] = CommonUnit.degree;
+                  break;
+              case REMAINDER:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] %= thatValuesD[j][i];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] %= thatValuesF[j][i];
+                outUnits[j] = thisUnits[j];
+                break;
+              case INV_REMAINDER:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = thatValuesD[j][i] % thisValuesD[j][i];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = thatValuesF[j][i] % thisValuesF[j][i];
+                outUnits[j] = thatUnits[j];
+                break;
+            }
+          }
+          break;
+
+        default:
+          throw new ArithmeticException("FlatField.binary: illegal operation");
+      }
+
+      /*
+       * Compute ErrorEstimate-s for the result.
+       */
+      ErrorEstimate[] outErrs = new ErrorEstimate[TupleDimension];
+      for (int j=0; j<TupleDimension; j++) {
+        if (error_mode == NO_ERRORS ||
+            thisErrs[j] == null || thatErrs[j] == null) {
+          outErrs[j] = null;
+        }
+        else {
+            if (thisValuesD != null)
+                outErrs[j] =
+                    new ErrorEstimate(thisValuesD[j], outUnits[j], op, thisErrs[j],
+                                      thatErrs[j], error_mode);
+            if (thisValuesF != null)
+                outErrs[j] =
+                    new ErrorEstimate(thisValuesF[j], outUnits[j], op, thisErrs[j],
+                                      thatErrs[j], error_mode);
+        }
+      }
+
+      // create a FlatField for return
+      /*- TDR June  1998
+      FlatField new_field = cloneDouble(units_out, errors_out);
+      */
+
+      FlatField new_field=null;
+      if (thisValuesD!=null)
+          new_field = cloneDouble (new_type, outUnits, outErrs, thisValuesD);
+      if (thisValuesF!=null)
+          new_field = cloneFloat (new_type, outUnits, outErrs, thisValuesF);
+      new_field.clearMissing();
+      return new_field;
+    }
+    else if (data instanceof Real || data instanceof RealTuple ||
+             (data instanceof TupleIface &&
+              ((TupleType) data.getType()).getFlat())) {
+      MathType RangeType = ((FunctionType) Type).getRange();
+      /*- TDR July 1998
+      if (!RangeType.equalsExceptName(data.getType())) {
+        throw new TypeException("FlatField.binary: types don't match");
+      }
+      */
+      /*- TDR June 1998 */
+      if ( !Type.equalsExceptName(new_type)) {
+        throw new TypeException("binary: new_type doesn't match return type");
+      }
+      /*- end */
+
+      // use DoubleSet rather than RangeSet for intermediate computation results
+      if (isMissing() || data.isMissing()) return new_type.missingData();
+
+
+      // get data values and possibly apply coordinate transform
+      double[][] vals = new double[TupleDimension][1];
+
+      Unit[] units_out = new Unit[TupleDimension];
+      ErrorEstimate[] errors_out = new ErrorEstimate[TupleDimension];
+
+
+      if (data instanceof Real) {
+        // no need for Unit conversion - just pass Unit into binary ops
+        for (int j=0; j<TupleDimension; j++) {
+          vals[j][0] = ((Real) data).getValue();
+          units_out[j] = ((Real) data).getUnit();
+          errors_out[j] = ((Real) data).getError();
+        }
+      }
+      else if (data instanceof RealTuple) {
+        for (int j=0; j<TupleDimension; j++) {
+          vals[j][0] = ((Real) ((RealTuple) data).getComponent(j)).getValue();
+        }
+        vals = CoordinateSystem.transformCoordinatesFreeUnits(
+                        ((FunctionType) Type).getFlatRange(),
+                        RangeCoordinateSystem, units_out, errors_out,
+                        (RealTupleType) data.getType(),
+                        ((RealTuple) data).getCoordinateSystem(),
+                        ((RealTuple) data).getTupleUnits(),
+                        ((RealTuple) data).getErrors(), vals);
+      }
+      else { // (data instanceof TupleIface && !(data instanceof RealTuple))
+        int n = ((TupleIface) data).getDimension();
+        int j = 0;
+        for (int i=0; i<n; i++) {
+          Data component = ((TupleIface) data).getComponent(i);
+          if (component instanceof Real) {
+            // no need for Unit conversion - just pass Unit into binary ops
+            vals[j][0] = ((Real) component).getValue();
+            units_out[j] = ((Real) component).getUnit();
+            errors_out[j] = ((Real) component).getError();
+            j++;
+          }
+          else { // (component instanceof RealTuple)
+            int m = ((TupleIface) component).getDimension();
+            double[][] tvals = new double[m][1];
+            Unit[] sub_units_out = new Unit[m];
+            ErrorEstimate[] sub_errors_out = new ErrorEstimate[m];
+            for (int k=0; k<m; k++) {
+              tvals[k][0] =
+                ((Real) ((TupleIface) component).getComponent(k)).getValue();
+            }
+            tvals = CoordinateSystem.transformCoordinatesFreeUnits(
+                        (RealTupleType) ((TupleType) RangeType).getComponent(i),
+                        RangeCoordinateSystems[i], sub_units_out, sub_errors_out,
+                        (RealTupleType) component.getType(),
+                        ((RealTuple) component).getCoordinateSystem(),
+                        ((RealTuple) component).getTupleUnits(),
+                        ((RealTuple) component).getErrors(), tvals);
+            for (int k=0; k<m; k++) {
+              vals[j + k][0] = tvals[k][0];
+              units_out[j + k] = sub_units_out[k];
+              errors_out[j + k] = sub_errors_out[k];
+            }
+            j += m;
+          }
+        }
+      }
+
+      /*
+       * Some variable renamings for clarity in the following:
+       */
+      Unit[]          thisUnits = (Unit[])RangeUnits.clone();
+      Unit[]          thatUnits = units_out;
+
+
+      boolean binaryDouble = shouldBeDouble ();
+      double[][]      thisValuesD = null;
+      float [][]      thisValuesF = null;
+      if (binaryDouble) {
+          thisValuesD = unpackValues ();
+      } else {
+          thisValuesF = unpackFloats ();
+      }
+
+
+      double[]        thatValues = new double[vals.length];
+      ErrorEstimate[] thisErrs = (ErrorEstimate[])RangeErrors.clone();
+      ErrorEstimate[] thatErrs = errors_out;
+      /*
+       * Default (null) values. The following switch might set "outUnits".
+       */
+      Unit[]          outUnits = new Unit[TupleDimension];
+
+      for (int j = 0; j < vals.length; j++)
+          thatValues[j] = vals[j][0];
+
+      /*
+       * Set the output values.  NOTE: The input array (thisValuesD) is also
+       * used to store the output numeric values.
+       */
+      switch (op) {
+      case ADD:
+      case SUBTRACT:
+      case INV_SUBTRACT:
+      case MAX:
+      case MIN:
+          for (int j=0; j<TupleDimension; j++) {
+              if (thisUnits[j] == null || thatUnits[j] == null) {
+                  outUnits[j] = null;
+              }
+              else if (thisUnits[j] == CommonUnit.promiscuous) {
+                  outUnits[j] = thatUnits[j].getAbsoluteUnit();
+              }
+              else if (thatUnits[j] == CommonUnit.promiscuous) {
+                  outUnits[j] = thisUnits[j].getAbsoluteUnit();
+              }
+              else {
+                  try {
+                      outUnits[j] = thisUnits[j].getAbsoluteUnit();
+                      boolean convertThis = !outUnits[j].equals(thisUnits[j]);
+                      boolean convertThat = !outUnits[j].equals(thatUnits[j]);
+
+                      if (convertThis)
+                          if (thisValuesD != null)
+                              thisValuesD[j] = outUnits[j].toThis(thisValuesD[j], thisUnits[j]);
+                          if (thisValuesF != null)
+                              thisValuesF[j] = outUnits[j].toThis(thisValuesF[j], thisUnits[j]);
+
+                      if (convertThat)
+                          thatValues[j] =
+                              outUnits[j].toThis(thatValues[j], thatUnits[j]);
+
+                      if (error_mode != NO_ERRORS &&
+                          thisErrs[j] != null && thatErrs[j] != null) {
+
+                          if (convertThis) {
+                              Unit      errUnit = thisErrs[j].getUnit();
+
+                              if (errUnit == null)
+                                  errUnit = thisUnits[j];
+
+                              double err = 0.5 * thisErrs[j].getErrorValue();
+                              double mean = thisErrs[j].getMean();
+                              double a = outUnits[j].toThis(mean + err, errUnit);
+                              double b = outUnits[j].toThis(mean - err, errUnit);
+                              mean = (a + b) / 2;
+                              err = Math.abs(a - b);
+                              thisErrs[j] = new ErrorEstimate(mean, err, outUnits[j]);
+                          }
+
+                          if (convertThat) {
+                              Unit      errUnit = thatErrs[j].getUnit();
+
+                              if (errUnit == null)
+                                  errUnit = thatUnits[j];
+
+                              double err = 0.5 * thatErrs[j].getErrorValue();
+                              double mean = thatErrs[j].getMean();
+                              double a = outUnits[j].toThis(mean + err, errUnit);
+                              double b = outUnits[j].toThis(mean - err, errUnit);
+                              mean = (a + b) / 2;
+                              err = Math.abs(a - b);
+                              thatErrs[j] = new ErrorEstimate(mean, err, outUnits[j]);
+                          }
+                      }
+                  }
+                  catch (UnitException e) {             // inconvertible units
+                      outUnits[j] = null;
+                  }
+              }
+              switch (op) {
+              case ADD:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] += thatValues[j];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] += thatValues[j];
+                  break;
+              case SUBTRACT:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] -= thatValues[j];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] -= thatValues[j];
+                  break;
+              case INV_SUBTRACT:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(thatValues[j] - thisValuesD[j][i]);
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(thatValues[j] - thisValuesF[j][i]);
+                  break;
+              case MAX:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Math.max(thisValuesD[j][i], thatValues[j]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Math.max(thisValuesF[j][i], thatValues[j]));
+                  break;
+              case MIN:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Math.min(thisValuesD[j][i], thatValues[j]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Math.min(thisValuesF[j][i], thatValues[j]));
+                  break;
+              }
+          }
+          break;
+
+      case MULTIPLY:
+      case DIVIDE:
+      case INV_DIVIDE:
+          for (int j=0; j<TupleDimension; j++) {
+              if (thisUnits[j] != null) {
+                  Unit absUnit = thisUnits[j].getAbsoluteUnit();
+                  if (!absUnit.equals(thisUnits[j])) {
+                      if (thisValuesD != null) thisValuesD[j] = absUnit.toThis(thisValuesD[j], thisUnits[j]);
+                      if (thisValuesF != null) thisValuesF[j] = absUnit.toThis(thisValuesF[j], thisUnits[j]);
+                      thisUnits[j] = absUnit;
+                  }
+              }
+              if (thatUnits[j] != null) {
+                  Unit absUnit = thatUnits[j].getAbsoluteUnit();
+                  if (!absUnit.equals(thatUnits[j])) {
+                      thatValues[j] = absUnit.toThis(thatValues[j], thatUnits[j]);
+                      thatUnits[j] = absUnit;
+                  }
+              }
+              if (thisUnits[j] == null || thatUnits[j] == null) {
+                  outUnits[j] = null;
+              }
+              else {
+                  switch(op) {
+                  case MULTIPLY:
+                      outUnits[j] =
+                          thisUnits[j].equals(CommonUnit.promiscuous)
+                          ? thatUnits[j]
+                          : thatUnits[j].equals(CommonUnit.promiscuous)
+                          ? thisUnits[j]
+                          : thisUnits[j].multiply(thatUnits[j]);
+                      break;
+                  case DIVIDE:
+                      outUnits[j] =
+                          thatUnits[j].equals(CommonUnit.promiscuous)
+                          ? thisUnits[j]
+                          : thisUnits[j].divide(thatUnits[j]);
+                      break;
+                  case INV_DIVIDE:
+                      outUnits[j] =
+                          thisUnits[j].equals(CommonUnit.promiscuous)
+                          ? thatUnits[j]
+                          : thatUnits[j].divide(thisUnits[j]);
+                      break;
+                  }
+              }
+              switch(op) {
+              case MULTIPLY:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] *= thatValues[j];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] *= thatValues[j];
+                  break;
+              case DIVIDE:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] /= thatValues[j];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] /= thatValues[j];
+                  break;
+              case INV_DIVIDE:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(thatValues[j] / thisValuesD[j][i]);
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(thatValues[j] / thisValuesF[j][i]);
+                  break;
+              }
+          }
+          break;
+
+      case POW:
+          for (int j=0; j<TupleDimension; j++) {
+              if (thisUnits[j] != null) {
+                  Unit absUnit = thisUnits[j].getAbsoluteUnit();
+                  if (!absUnit.equals(thisUnits[j])) {
+                      if (thisValuesD != null) thisValuesD[j] = absUnit.toThis(thisValuesD[j], thisUnits[j]);
+                      if (thisValuesF != null) thisValuesF[j] = absUnit.toThis(thisValuesF[j], thisUnits[j]);
+                      thisUnits[j] = absUnit;
+                  }
+              }
+              if (thatUnits[j] != null && 
+                  !CommonUnit.promiscuous.equals(thatUnits[j])) {
+                  Unit absUnit = thatUnits[j].getAbsoluteUnit();
+                  if (!absUnit.equals(thatUnits[j])) {
+                      thatValues[j] = absUnit.toThis(thatValues[j], thatUnits[j]);
+                      thatUnits[j] = absUnit;
+                  }
+              }
+              if (thisUnits[j] != null && (
+                                           thisUnits[j].equals(CommonUnit.promiscuous) ||
+                                           thisUnits[j].equals(CommonUnit.dimensionless))) {
+                  outUnits[j] = thisUnits[j];
+              }
+              else {
+                  outUnits[j] = null;
+              }
+              if (thisValuesD != null)
+                  for (int i=0; i<getLength(); i++)
+                      thisValuesD[j][i] = (double)(Math.pow(thisValuesD[j][i], thatValues[j]));
+              if (thisValuesF != null)
+                  for (int i=0; i<getLength(); i++)
+                      thisValuesF[j][i] = (float)(Math.pow(thisValuesF[j][i], thatValues[j]));
+          }
+          break;
+
+      case INV_POW:
+          for (int j=0; j<TupleDimension; j++) {
+              if (thatUnits[j] != null) {
+                  Unit absUnit = thatUnits[j].getAbsoluteUnit();
+                  if (!absUnit.equals(thatUnits[j])) {
+                      thatValues[j] = absUnit.toThis(thatValues[j], thatUnits[j]);
+                      thatUnits[j] = absUnit;
+                  }
+              }
+              if (thisUnits[j] != null && 
+                  !CommonUnit.promiscuous.equals(thisUnits[j])) {
+                  Unit absUnit = thisUnits[j].getAbsoluteUnit();
+                  if (!absUnit.equals(thisUnits[j])) {
+                      if (thisValuesD != null)  thisValuesD[j] = absUnit.toThis(thisValuesD[j], thisUnits[j]);
+                      if (thisValuesF != null)  thisValuesF[j] = absUnit.toThis(thisValuesF[j], thisUnits[j]);
+                      thisUnits[j] = absUnit;
+                  }
+              }
+              if (thatUnits[j] != null && (
+                                           thatUnits[j].equals(CommonUnit.promiscuous) ||
+                                           thatUnits[j].equals(CommonUnit.dimensionless))) {
+                  outUnits[j] = thatUnits[j];
+              }
+              else {
+                  outUnits[j] = null;
+              }
+              if (thisValuesD != null)
+                  for (int i=0; i<getLength(); i++)
+                      thisValuesD[j][i] = (double)(Math.pow(thatValues[j], thisValuesD[j][i]));
+              if (thisValuesF != null)
+                  for (int i=0; i<getLength(); i++)
+                      thisValuesF[j][i] = (float)(Math.pow(thatValues[j], thisValuesF[j][i]));
+          }
+          break;
+
+      case ATAN2:
+      case ATAN2_DEGREES:
+      case INV_ATAN2:
+      case INV_ATAN2_DEGREES:
+      case REMAINDER:
+      case INV_REMAINDER:
+          for (int j=0; j<TupleDimension; j++) {
+              if (thisUnits[j] != null && thatUnits[j] != null) {
+                  Unit absUnit = thisUnits[j].getAbsoluteUnit();
+                  if (!absUnit.equals(thisUnits[j])) {
+                      if (thisValuesD != null)
+                          thisValuesD[j] = absUnit.toThis(thisValuesD[j], thisUnits[j]);
+                      if (thisValuesF != null)
+                          thisValuesF[j] = absUnit.toThis(thisValuesF[j], thisUnits[j]);
+                      thisUnits[j] = absUnit;
+                  }
+                  if (!absUnit.equals(thatUnits[j])) {
+                      thatValues[j] = absUnit.toThis(thatValues[j], thatUnits[j]);
+                      thatUnits[j] = absUnit;
+                  }
+              }
+              switch(op) {
+              case ATAN2:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Math.atan2(thisValuesD[j][i], thatValues[j]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Math.atan2(thisValuesF[j][i], thatValues[j]));
+                  outUnits[j] = CommonUnit.radian;
+                  break;
+              case ATAN2_DEGREES:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Data.RADIANS_TO_DEGREES * 
+                                                       Math.atan2(thisValuesD[j][i], thatValues[j]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Data.RADIANS_TO_DEGREES * 
+                                                       Math.atan2(thisValuesF[j][i], thatValues[j]));
+                  outUnits[j] = CommonUnit.degree;
+                  break;
+              case INV_ATAN2:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Math.atan2(thatValues[j], thisValuesD[j][i]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Math.atan2(thatValues[j], thisValuesF[j][i]));
+                  outUnits[j] = CommonUnit.radian;
+                  break;
+              case INV_ATAN2_DEGREES:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(Data.RADIANS_TO_DEGREES * 
+                                                       Math.atan2(thatValues[j], thisValuesD[j][i]));
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(Data.RADIANS_TO_DEGREES * 
+                                                       Math.atan2(thatValues[j], thisValuesF[j][i]));
+                  outUnits[j] = CommonUnit.degree;
+                  break;
+              case REMAINDER:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] %= thatValues[j];
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] %= thatValues[j];
+                  outUnits[j] = thisUnits[j];
+                  break;
+              case INV_REMAINDER:
+                  if (thisValuesD != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesD[j][i] = (double)(thatValues[j] % thisValuesD[j][i]);
+                  if (thisValuesF != null)
+                      for (int i=0; i<getLength(); i++)
+                          thisValuesF[j][i] = (float)(thatValues[j] % thisValuesF[j][i]);
+                  outUnits[j] = thatUnits[j];
+                  break;
+              }
+          }
+          break;
+
+      default:
+          throw new ArithmeticException("FlatField.binary: illegal operation");
+      }
+
+      /*
+       * Compute ErrorEstimate-s for the result.
+       */
+      ErrorEstimate[] outErrs = new ErrorEstimate[TupleDimension];
+      for (int j=0; j<TupleDimension; j++) {
+          if (error_mode == NO_ERRORS ||
+              thisErrs[j] == null || thatErrs[j] == null) {
+              outErrs[j] = null;
+          }
+          else {
+              if (thisValuesD != null)
+                  outErrs[j] = new ErrorEstimate(thisValuesD[j], outUnits[j], op, thisErrs[j],
+                                                 thatErrs[j], error_mode);
+              if (thisValuesF != null)
+                  outErrs[j] = new ErrorEstimate(thisValuesF[j], outUnits[j], op, thisErrs[j],
+                                                 thatErrs[j], error_mode);
+          }
+      }
+
+      // create a FlatField for return
+      /*- TDR June  1998
+        FlatField new_field = cloneDouble(units_out, errors_out);
+      */
+      FlatField new_field = null;
+      if (thisValuesD != null)
+          new_field = cloneDouble( new_type, outUnits, outErrs, thisValuesD);
+      if (thisValuesF != null)
+          new_field = cloneFloat( new_type, outUnits, outErrs, thisValuesF);
+
+      new_field.clearMissing();
+      return new_field;
+    }
+    else {
+        throw new TypeException("Field.binary");
+    }
+  }
+
+
+    /** 
+     * Return new FlatField with value 'this op'.
+     * @param op  operation to perform (e.g., NOP, ABS, COS)
+     * @param new_type  MathType of new object
+     * @param sampling_mode  sampling mode to use 
+     *                       (e.g., NEAREST_NEIGHBOR, WEIGHTED_AVERAGE)
+     * @param error_mode    error estimate mode (e.g., NO_ERROR, DEPENDENT,
+     *                      independent)
+     * @return new FlatField corresponding to the requested operation
+     * @throws VisADException   couldn't create new VisAD object
+     * @throws RemoteException  couldn't create new Remote object
+     */
+    public Data unary (int op, MathType new_type, int sampling_mode, int error_mode)
+        throws VisADException {
+        // use DoubleSet rather than RangeSet for intermediate computation results
+        //      if (isMissing()) return cloneDouble();
+        if (isMissing()) {
+            if (shouldBeDouble ()) {
+                return cloneDouble(new_type, RangeUnits, RangeErrors);
+            } else {
+                return cloneFloat(new_type, RangeUnits, RangeErrors);
+            }
+        }
+
+
+        /*- TDR July 1998  */
+        if ( new_type == null ) {
+            throw new TypeException("unary: new_type may not be null");
+        }
+
+        Unit[] units_out = new Unit[TupleDimension];
+
+        /*
+         * Ensure that the numeric values and units are in rational form, i.e. one
+         * in which ratios of data values make sense (e.g. Temperature values in
+         * kelvin rather than celsius).
+         */
+        Unit[]          units_in = (Unit[])RangeUnits.clone();
+        ErrorEstimate[] errors_in = (ErrorEstimate[])RangeErrors.clone();;
+
+
+        double[][] valuesD = null;
+        float [][] valuesF = null;
+
+        if (shouldBeDouble ()) {
+            valuesD = unpackValues ();
+            makeRational (valuesD, units_in, errors_in);
+        } else {
+            valuesF = unpackFloats ();
+            makeRational (valuesF, units_in, errors_in);
+        }
+
+
+        int i, j; // loop indices
+        double[] valuesDJ;
+        float[] valuesFJ;
+
+        switch (op) {
+        case ABS:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = Math.abs(valuesDJ[i]);
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = Math.abs(valuesFJ[i]);
+                    }
+                }
+                units_out[j] = units_in[j];
+            }
+            break;
+        case ACOS:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.acos(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.acos(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = CommonUnit.radian;
+            }
+            break;
+        case ACOS_DEGREES:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Data.RADIANS_TO_DEGREES * Math.acos(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Data.RADIANS_TO_DEGREES * Math.acos(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = CommonUnit.degree;
+            }
+            break;
+        case ASIN:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.asin(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.asin(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = CommonUnit.radian;
+            }
+            break;
+        case ASIN_DEGREES:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Data.RADIANS_TO_DEGREES * Math.asin(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Data.RADIANS_TO_DEGREES * Math.asin(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = CommonUnit.degree;
+            }
+            break;
+        case ATAN:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.atan(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.atan(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = CommonUnit.radian;
+            }
+            break;
+        case ATAN_DEGREES:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Data.RADIANS_TO_DEGREES * Math.atan(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Data.RADIANS_TO_DEGREES * Math.atan(valuesFJ[i]));
+                    }
+                }
+                units_out[j] = CommonUnit.degree;
+            }
+            break;
+        case CEIL:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.ceil(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.ceil(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = units_in[j];
+            }
+            break;
+        case COS:
+            // do cos in degrees, unless unit is radians
+            for (j=0; j<TupleDimension; j++) {
+                if (CommonUnit.degree.equals(units_in[j])) {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.cos(Data.DEGREES_TO_RADIANS * valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.cos(Data.DEGREES_TO_RADIANS * valuesFJ[i]));
+                        }
+                    }
+
+                }
+                else {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.cos(valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.cos(valuesFJ[i]));
+                        }
+                    }
+
+                }
+                units_out[j] = CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case COS_DEGREES:
+            for (j=0; j<TupleDimension; j++) {
+
+
+                if (CommonUnit.radian.equals(units_in[j])) {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.cos(valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.cos(valuesFJ[i]));
+                        }
+                    }
+
+                }
+                else {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.cos(Data.DEGREES_TO_RADIANS * valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.cos(Data.DEGREES_TO_RADIANS * valuesFJ[i]));
+                        }
+                    }
+
+                }
+                units_out[j] =
+                    CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case EXP:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.exp(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.exp(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] =
+                    CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case FLOOR:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.floor(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.floor(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = units_in[j];
+            }
+            break;
+        case LOG:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.log(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.log(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] =
+                    CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case RINT:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.rint(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.rint(valuesFJ[i]));
+                    }
+                }
+
+                units_out[j] = units_in[j];
+            }
+            break;
+        case ROUND:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = Math.round(valuesDJ[i]);
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = Math.round(valuesFJ[i]);
+                    }
+                }
+
+                units_out[j] = units_in[j];
+            }
+            break;
+        case SIN:
+            for (j=0; j<TupleDimension; j++) {
+                if (CommonUnit.degree.equals(units_in[j])) {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.sin(Data.DEGREES_TO_RADIANS * valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.sin(Data.DEGREES_TO_RADIANS * valuesFJ[i]));
+                        }
+                    }
+
+                }
+                else {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.sin(valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.sin(valuesFJ[i]));
+                        }
+                    }
+
+                }
+                units_out[j] =
+                    CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case SIN_DEGREES:
+            for (j=0; j<TupleDimension; j++) {
+
+                if (CommonUnit.radian.equals(units_in[j])) {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.sin(valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.sin(valuesFJ[i]));
+                        }
+                    }
+
+                }
+                else {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.sin(Data.DEGREES_TO_RADIANS * valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.sin(Data.DEGREES_TO_RADIANS * valuesFJ[i]));
+                        }
+                    }
+
+                }
+                units_out[j] =
+                    CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case SQRT:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = (double)(Math.sqrt(valuesDJ[i]));
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = (float)(Math.sqrt(valuesFJ[i]));
+                    }
+                }
+
+                // WLH 26 Nov 2001
+                // units_out[j] =
+                //   CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+                if (units_in[j] == null) {
+                    units_out[j] = null;
+                }
+                else {
+                    try {
+                        units_out[j] = units_in[j].sqrt();
+                    }
+                    catch (IllegalArgumentException e) {
+                        units_out[j] = null;
+                    }
+                    catch (UnitException e) {
+                        units_out[j] = null;
+                    }
+                }
+            }
+            break;
+        case TAN:
+            for (j=0; j<TupleDimension; j++) {
+                if (CommonUnit.degree.equals(units_in[j])) {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.tan(Data.DEGREES_TO_RADIANS * valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.tan(Data.DEGREES_TO_RADIANS * valuesFJ[i]));
+                        }
+                    }
+
+                }
+                else {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.tan(valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.tan(valuesFJ[i]));
+                        }
+                    }
+
+                }
+                units_out[j] =
+                    CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case TAN_DEGREES:
+            for (j=0; j<TupleDimension; j++) {
+                if (CommonUnit.radian.equals(units_in[j])) {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.tan(valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.tan(valuesFJ[i]));
+                        }
+                    }
+
+                }
+                else {
+                    if (valuesD != null) {
+                        valuesDJ = valuesD[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesDJ[i] = (double)(Math.tan(Data.DEGREES_TO_RADIANS * valuesDJ[i]));
+                        }
+                    }
+                    if (valuesF != null) {
+                        valuesFJ = valuesF[j];
+                        for (i=0; i<getLength(); i++) {
+                            valuesFJ[i] = (float)(Math.tan(Data.DEGREES_TO_RADIANS * valuesFJ[i]));
+                        }
+                    }
+
+                }
+                units_out[j] =
+                    CommonUnit.dimensionless.equals(units_in[j]) ? units_in[j] : null;
+            }
+            break;
+        case NEGATE:
+            for (j=0; j<TupleDimension; j++) {
+                if (valuesD != null) {
+                    valuesDJ = valuesD[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesDJ[i] = -valuesDJ[i];
+                    }
+                }
+                if (valuesF != null) {
+                    valuesFJ = valuesF[j];
+                    for (i=0; i<getLength(); i++) {
+                        valuesFJ[i] = -valuesFJ[i];
+                    }
+                }
+
+                units_out[j] = units_in[j];
+            }
+            break;
+        case NOP:
+            for (j=0; j<TupleDimension; j++) {
+                units_out[j] = units_in[j];
+            }
+            break;
+        }
+
+        // compute ErrorEstimates for result
+        ErrorEstimate[] errors_out = new ErrorEstimate[TupleDimension];
+        for (j=0; j<TupleDimension; j++) {
+            if (error_mode == NO_ERRORS || errors_in[j] == null) {
+                errors_out[j] = null;
+            }
+            else {
+                if (valuesD != null)
+                    errors_out[j] = new ErrorEstimate(valuesD[j], units_out[j], op,
+                                                      errors_in[j], error_mode);
+                if (valuesF != null)
+                    errors_out[j] = new ErrorEstimate(valuesF[j], units_out[j], op,
+                                                      errors_in[j], error_mode);
+            }
+        }
+
+        // create a FlatField for return
+        /*- TDR July 1998
+          FlatField new_field = cloneDouble(units_out, errors_out);
+        */
+
+        FlatField newField=null;
+        if (valuesD!=null) {
+            newField = cloneDouble (new_type, units_out, errors_out, valuesD);
+        }
+        if (valuesF!=null) {
+            newField = cloneFloat (new_type, units_out, errors_out, valuesF);
+        }
+
+
+
+        // new_field.DoubleRange = values;
+        newField.clearMissing();
+        return newField;
+    }
+
+
+
+
+
+
+
+    /**
+   * Ensure that numeric values and units are in rational form, i.e. one in
+   * which ratios of data values make sense (e.g. Temperature values in Kelvin
+   * rather than Celsius).  Additionally, if an input unit is a non-unity
+   * dimensionless unit, then the associated values will be converted to the
+   * dimensionless unity unit (this conditions the values for operations like
+   * exp() and pow()).  All conversions are done in-place.
+   *
+   * @param values              The numeric values.  On output,
+   *                            <code>values[i]</code> will have been replaced
+   *                            if necessary.
+   * @param units               The units for the values and error estimates.
+   *                            It's length shall be <code>values.length</code>.
+   *                            On output, <code>units[i]</code> will have been
+   *                            replaced with the absolute form of the input
+   *                            unit if necessary.
+   * @param errors              The error estimates.  It's length shall be
+   *                            <code>values.length</code>.  On output,
+   *                            <code>errors[i]</code> will have been replaced
+   *                            if necessary.
+   * @throws UnitException      Unit conversion error.
+   */
+    protected static void makeRational(double[][] values, Unit[] units,
+                                       ErrorEstimate[] errors) throws UnitException
+    {
+
+        for (int j=0; j<values.length; j++) {
+            Unit   inputUnit = units[j];
+            if (inputUnit != null && !(inputUnit instanceof PromiscuousUnit)) {
+                Unit    outputUnit = inputUnit.getAbsoluteUnit();
+                if (Unit.canConvert (outputUnit, CommonUnit.dimensionless)) {
+                    outputUnit = CommonUnit.dimensionless;
+                }
+                if (!outputUnit.equals(inputUnit)) {
+                    values[j] = outputUnit.toThis (values[j], inputUnit);
+                    if (errors[j] != null) {
+                        errors[j] =
+                            new ErrorEstimate(
+                                              outputUnit.toThis(errors[j].getMean(), inputUnit),
+                                              errors[j].getErrorValue(),
+                                              outputUnit);
+                    }
+                    units[j] = outputUnit;
+                }
+            }
+        }
+    }
+
+
+    protected static void makeRational(float[][] values, Unit[] units,
+                                       ErrorEstimate[] errors) throws UnitException
+    {
+
+        for (int j=0; j<values.length; j++) {
+            Unit        inputUnit = units[j];
+            if (inputUnit != null && !(inputUnit instanceof PromiscuousUnit)) {
+                Unit    outputUnit = inputUnit.getAbsoluteUnit();
+
+                if (Unit.canConvert (outputUnit, CommonUnit.dimensionless)) {
+                    outputUnit = CommonUnit.dimensionless;
+                }
+                if (!outputUnit.equals(inputUnit)) {
+                    values[j] = outputUnit.toThis (values[j], inputUnit);
+                    if (errors[j] != null) {
+                        errors[j] =
+                            new ErrorEstimate(
+                                              outputUnit.toThis(errors[j].getMean(), inputUnit),
+                                              errors[j].getErrorValue(),
+                                              outputUnit);
+                    }
+                    units[j] = outputUnit;
+                }
+            }
+        }
+    }
+
+  /** extract field from this[].component;
+      this is OK, when we get around to it */
+  public Field extract(int component)
+         throws VisADException, RemoteException
+  {
+    return extract(component, true);
+  }
+
+  /** extract field from this[].component;
+      this is OK, when we get around to it */
+  public Field extract(int component, boolean copy)
+         throws VisADException, RemoteException
+  {
+    Set domainSet = getDomainSet();
+    int n_samples = domainSet.getLength();
+    MathType rangeType = ((FunctionType)Type).getRange();
+    MathType domainType = ((FunctionType)Type).getDomain();
+    int n_comps;
+    CoordinateSystem coord_sys;
+    MathType m_type;
+    int ii, jj, compSize;
+
+    int[] flat_indices;
+
+    if ( rangeType instanceof RealType )
+    {
+      if ( component != 0 ) {
+        throw new VisADException("extract: component index must be zero");
+      }
+      else {
+        return this;
+      }
+    }
+    else
+    {
+      n_comps = ((TupleType)rangeType).getDimension();
+      if ( (component+1) > n_comps ) {
+        throw new VisADException("extract: component index too large");
+      }
+    }
+
+    MathType new_range = ((TupleType)rangeType).getComponent( component );
+    FunctionType new_type = new FunctionType( domainType, new_range);
+
+    int cnt = 0;
+    int t_cnt = 0;
+    for ( ii = 0; ii < component; ii++ )
+    {
+      m_type = ((TupleType)rangeType).getComponent(ii);
+      if ( m_type instanceof RealType )
+      {
+        cnt++;
+      }
+      else
+      {
+        cnt += ((RealTupleType)m_type).getDimension();
+        t_cnt++;
+      }
+    }
+
+    if ( new_range instanceof RealType )
+    {
+      compSize = 1;
+      flat_indices = new int[compSize];
+      flat_indices[0] = cnt;
+      coord_sys = null;
+    }
+    else
+    {
+      compSize = ((RealTupleType)new_range).getDimension();
+      flat_indices = new int[ compSize ];
+      for ( jj = 0; jj < compSize; jj++ )
+      {
+        flat_indices[jj] = cnt++;
+      }
+      coord_sys = RangeCoordinateSystems[t_cnt];
+    }
+
+    ErrorEstimate[] errors_out = new ErrorEstimate[ compSize ];
+    Unit[] units_out = new Unit[ compSize ];
+    Set[] rangeSet_out = new Set[ compSize ];
+
+    boolean needDoubles = false;
+    for ( ii = 0; ii < compSize; ii++ )
+    {
+      units_out[ii] = RangeUnits[ flat_indices[ii] ];
+      errors_out[ii] = RangeErrors[ flat_indices[ii] ];
+      rangeSet_out[ii] = RangeSet[ flat_indices[ii] ];
+      if (rangeSet_out[ii] instanceof DoubleSet) needDoubles = true;
+    }
+
+    FlatField new_field = new FlatField( new_type, domainSet, coord_sys, null,
+                                         rangeSet_out, units_out );
+    new_field.setRangeErrors( errors_out );
+
+    if (needDoubles) {
+   
+        double[][] new_values = new double[ compSize ][ n_samples ];
+    
+        for ( ii = 0; ii < compSize; ii++ )
+        {
+          new_values[ii] = unpackOneRangeComp( flat_indices[ii], copy);
+        }
+        new_field.setSamples( new_values, false );
+    } else {
+        float[][] new_floats = new float[ compSize ][ n_samples ];
+    
+        for ( ii = 0; ii < compSize; ii++ )
+        {
+          new_floats[ii] = unpackOneFloatRangeComp( flat_indices[ii], copy);
+        }
+        new_field.setSamples( new_floats, false );
+    }
+
+    return new_field;
+  }
+
+  public Data derivative( RealTuple location, RealType[] d_partial_s,
+                          MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException
+  {
+    int ii, jj, kk, dd, rr, tt, pp, ss;
+    Set domainSet = getDomainSet();
+    int domainDim = domainSet.getDimension();
+    int manifoldDimension = domainSet.getManifoldDimension();
+    int n_samples = domainSet.getLength();
+    CoordinateSystem d_coordsys = getDomainCoordinateSystem();
+    RealTupleType d_reference = (d_coordsys == null) ? null : d_coordsys.getReference();
+    MathType m_type = null;
+    MathType[] m_types = null;
+    RealType r_type = null;
+    RealType[] r_types = null;
+    TupleType t_type = null;
+    boolean thisDomainFlag = true;
+
+    if ( manifoldDimension != domainDim )
+    {
+      throw new SetException("derivative: manifoldDimension must equal "+
+                             "domain dimension" );
+    }
+    error_mode = Data.NO_ERRORS;
+    if ( location != null )
+    {
+      thisDomainFlag = false;
+    }
+
+    RealTupleType domainType = ((FunctionType)Type).getDomain();
+    RealType[] r_comps = domainType.getRealComponents();
+    RealType[] r_compsRange = (((FunctionType)Type).getFlatRange()).getRealComponents();
+    RealType[] r_compsRef = (d_reference == null) ? null : d_reference.getRealComponents();
+
+    MathType RangeType = ((FunctionType)Type).getRange();
+
+    int n_partials;  // number of partial derivatives to compute -*
+
+  //- get all components for this function's domain -*
+    if ( d_partial_s == null )
+    {
+      n_partials = domainDim;
+      d_partial_s = r_comps;
+    }
+    else
+    {
+      n_partials = d_partial_s.length;
+      if ( n_partials > domainDim ) {
+        throw new VisADException("derivative: too many d_partial components");
+      }
+    }
+
+    int[] u_index = new int[n_partials];
+    double[][] u_vectors = new double[n_partials][domainDim];
+
+  //- verify that input RealType-s match the Function's domain -*
+  //- create unit vectors for the d_partial RealTypes -*
+    int found = 0;
+    int foundRef = 0;
+    for ( ii = 0; ii < n_partials; ii++ )
+    {
+      for ( jj = 0; jj < domainDim; jj++ )
+      {
+        u_vectors[ii][jj] = 0d;
+        if ( r_comps[jj].equals(d_partial_s[ii]) )
+        {
+          u_index[ii] = jj;
+          u_vectors[ii][jj] = 1d;
+          found++;
+        }
+        else if ( d_reference != null )
+        {
+          if ( r_compsRef[jj].equals(d_partial_s[ii]) )
+          {
+            u_index[ii] = jj;
+            u_vectors[ii][jj] = 1d;
+            foundRef++;
+          }
+        }
+      }
+    }
+
+    boolean transform;  //- flag indicating coordinate transform is required  --*
+
+    if ( found == 0 )
+    {
+      if ( foundRef == 0 )
+      {
+         throw new VisADException("derivative: d_partial_s not in domain or reference");
+      }
+      else if ( 0 < foundRef && foundRef < n_partials )
+      {
+        throw new VisADException("derivative: d_partial_s must ALL be in function's "+
+                                             "domain or ALL in domain's reference");
+      }
+      else
+      {
+        transform = true;
+      }
+    }
+    else if ( 0 < found && found < n_partials )
+    {
+      throw new VisADException("derivative: d_partial_s must ALL be in function's "+
+                                           "domain or ALL in domain's reference");
+    }
+    else
+    {
+      transform = false;
+    }
+
+    Unit[] D_units = null;
+    Unit[][] R_units = getRangeUnits();
+    String[][] derivNames = null;
+    Unit[][] derivUnits = new Unit[ n_partials ][ TupleDimension ];
+    MathType[] new_range = new MathType[ n_partials ];
+    MathType[] new_types = new MathType[ n_partials ];
+
+    if ( !transform ) {
+      D_units = domainSet.getSetUnits();
+    }
+    else {
+      D_units = d_reference.getDefaultUnits();
+    }
+
+  //- Create derivative Units array   -*
+    for ( ii = 0; ii < n_partials; ii++ ) {
+      for ( jj = 0; jj < TupleDimension; jj++ ) {
+        if (( R_units == null)||( D_units == null ))
+        {
+          derivUnits[ii][jj] = null;
+        }
+        else if (( R_units[jj][0] == null )||( D_units[u_index[ii]] == null ))
+        {
+          derivUnits[ii][jj] = null;
+        }
+        else
+        {
+          derivUnits[ii][jj] = R_units[jj][0].divide( D_units[ u_index[ii] ] );
+        }
+
+      }
+    }
+
+    if ( derivType_s == null )
+    {
+      for ( ii = 0; ii < n_partials; ii++ )
+      {
+        MathType M_type = Type.cloneDerivative( d_partial_s[ii] );
+        if ( thisDomainFlag ) {
+          new_types[ii] = M_type;
+        }
+        else {
+          new_types[ii] = ((FunctionType)M_type).getRange();
+        }
+      }
+      derivType_s = new_types;
+    }
+    else //- check supplied derivType-s for compatibility  -*
+    {
+      if ( derivType_s.length != n_partials ) {
+        throw new VisADException("derivative: must be a single MathType "+
+                                 "for each domain RealType");
+      }
+      for ( ii = 0; ii < n_partials; ii++ )
+      {
+        if ( thisDomainFlag ) {
+          if ( !Type.equalsExceptName(derivType_s[ii]) ) {
+            throw new TypeException("derivative: incompatible with function range");
+          }
+        }
+        else {
+          if ( !((((FunctionType)Type).getRange()).equalsExceptName(derivType_s[ii])) ) {
+            throw new TypeException("derivative: incompatible with function range");
+          }
+        }
+      }
+    }
+
+    double[][][] p_derivatives = null;
+    DataImpl[] datums = new DataImpl[ n_partials ];
+    ErrorEstimate[][] rangeErrors_out = new ErrorEstimate[ n_partials ][ TupleDimension ];
+    if ( thisDomainFlag )
+    {
+      p_derivatives = new double[ n_partials ][ TupleDimension ][ n_samples ];
+      for ( ii = 0; ii < n_partials; ii++ ) {
+        datums[ii] = (DataImpl) cloneDouble( derivType_s[ii], derivUnits[ii], null );
+      }
+      if ( isMissing() ) {
+        if ( n_partials == 1 )
+        {
+          return datums[0];
+        }
+        else
+        {
+          return new Tuple( datums );
+        }
+      }
+    }
+    else
+    {
+      p_derivatives = new double[ n_partials ][ TupleDimension ][ 1 ];
+      if ( isMissing() ) {
+        for ( ii = 0; ii < n_partials; ii++ ) {
+          for ( jj = 0; jj < TupleDimension; jj++ ) {
+            p_derivatives[ii][jj][0] = Double.NaN;
+            rangeErrors_out[ii][jj] = null;
+          }
+        }
+      }
+    }
+
+    if ( !isMissing() )  //- skip computations. if thisDomainFlag is also set, then
+                         //- method would have already returned.
+    {
+
+  //- compute derivative-s, return FlatField or Tuple of FlatFields, or Data --*
+
+    double[][] rangeValues = null;
+    int[][] neighbors = null;
+    int n_points;
+    int n_index;
+    int m_index;
+    int index;
+    float distance;
+    float step;
+    float f_sum;
+    double d_sum;
+    ErrorEstimate[] domainErrors = domainSet.getSetErrors();
+
+
+  //- Handle LinearSet case separately for efficiency   -*
+    if(( domainSet instanceof LinearSet )&&( thisDomainFlag ))
+    {
+      rangeValues = getValues();
+
+      //- each partial derivative   -*
+      for ( kk = 0; kk < n_partials; kk++ )
+      {
+        //- get manifoldDimension index for this real axis ( LinearSet only )  -*
+        m_index = u_index[kk];
+
+        //- get neigbors and separation along this axis   -*
+        neighbors = domainSet.getNeighbors( m_index );
+        step = (float) (((LinearSet)domainSet).getLinear1DComponent(m_index)).getStep();
+
+        //- compute derivative for each sample and each range component   -*
+        for ( ii = 0; ii < n_samples; ii++ )
+        {
+          if ( neighbors[ii][0] == -1) {
+            distance = step;
+            n_index = neighbors[ii][1];
+            index = ii;
+          }
+          else if ( neighbors[ii][1] == -1 ) {
+            distance = step;
+            n_index = ii;
+            index = neighbors[ii][0];
+          }
+          else {
+            distance = 2.f*step;
+            n_index = neighbors[ii][1];
+            index = neighbors[ii][0];
+          }
+
+          for ( rr = 0; rr < TupleDimension; rr++ )
+          {
+            p_derivatives[kk][rr][ii] = ( rangeValues[rr][ n_index ] -
+                                          rangeValues[rr][ index ] )/distance;
+          }
+        }
+
+        if ( error_mode != Data.NO_ERRORS )
+        {
+          for ( rr = 0; rr < TupleDimension; rr++ ) {
+            double[] d_values = p_derivatives[kk][rr];
+            rangeErrors_out[kk][rr] = new ErrorEstimate( d_values, derivUnits[kk][rr],
+                                                     Data.DIVIDE, RangeErrors[rr],
+                                                     domainErrors[m_index],
+                                                     error_mode );
+          }
+        }
+      }
+
+      neighbors = null;
+      rangeValues = null;
+    }
+    else  //- GriddedSet, IrregularSet    --*
+    {
+      float dotproduct;
+      float inv_dotproduct;
+      float[][] weights = null;
+      float sum_weights;
+      float[][] Samples;
+
+      //- compute derivative at this Set's sample locations  --*
+      if ( thisDomainFlag )
+      {
+        rangeValues = getValues();
+        neighbors = new int[n_samples][];
+        weights = new float[n_samples][];
+        domainSet.getNeighbors( neighbors, weights );
+        if ( transform )
+        {
+          Samples = domainSet.getSamples(true);
+
+          Samples =
+          CoordinateSystem.transformCoordinates( d_reference, null, null, null,
+                           domainType, d_coordsys, null, null, Samples );
+        }
+        else
+        {
+          Samples = domainSet.getSamples(false);
+        }
+      }
+      //- compute derivative at selected ( probably interpolated locations )  --*
+      else
+      {
+        double[][] new_rangeValues;
+        double[] d_array;
+        int[][] new_neighbors;
+        n_samples = 1;
+        FlatField new_field;
+        float[][] new_Samples;
+        float[][] evalSamples;
+        float[][] org_Samples = domainSet.getSamples(false);
+
+        new_field = (FlatField) resample( new SingletonSet(location, null, null, null ),
+                                Data.WEIGHTED_AVERAGE, error_mode );
+
+        evalSamples = (new_field.getDomainSet()).getSamples(false);
+        neighbors = new int[n_samples][];
+        weights = new float[n_samples][];
+
+        ((SimpleSet)domainSet).valueToInterp( evalSamples, neighbors, weights );
+
+        n_points = neighbors[0].length;
+        new_neighbors = new int[n_samples][ n_points ];
+
+        new_rangeValues = new double[ TupleDimension ][ n_points + 1 ];
+        new_Samples = new float[ domainDim ][ n_points + 1 ];
+        for ( ii = 0; ii < domainDim; ii++ )
+        {
+          new_Samples[ii][0] = evalSamples[ii][0];
+        }
+        d_array = new_field.unpackValues(0);
+        for ( ii = 0; ii < TupleDimension; ii++ )
+        {
+          new_rangeValues[ii][0] = d_array[ii];
+        }
+        for ( kk = 0; kk < n_points; kk++ )
+        {
+          d_array = unpackValues( neighbors[0][kk] );
+          new_neighbors[0][kk] = kk + 1;
+          for ( ii = 0; ii < TupleDimension; ii++ )
+          {
+            new_rangeValues[ii][kk+1] = d_array[ii];
+          }
+          for ( ii = 0; ii < domainDim; ii++ )
+          {
+            new_Samples[ii][kk+1] = org_Samples[ii][ neighbors[0][kk] ];
+          }
+        }
+
+        neighbors = new_neighbors;
+        rangeValues = new_rangeValues;
+        Samples = new_Samples;
+        if ( transform )
+        {
+          Samples =
+          CoordinateSystem.transformCoordinates( d_reference, null, null, null,
+                           domainType, d_coordsys, null, null, Samples );
+        }
+      }
+
+      //- compute derivatives for each sample   --*
+      for ( ii = 0; ii < n_samples; ii++ )
+      {
+        n_points = neighbors[ii].length;
+        double[] distances = new double[ n_points ];
+        double[][] derivatives = new double[ n_points ][ TupleDimension ];
+        double[][] uvecPoint = new double[ n_points ][ domainDim ];
+
+        //- neighbors loop   -*
+        for ( kk = 0; kk < n_points; kk++ )
+        {
+          f_sum = 0;
+          for ( dd = 0; dd < domainDim; dd++ ) {
+            f_sum += (Samples[dd][ neighbors[ii][kk] ] - Samples[dd][ii])*
+                     (Samples[dd][ neighbors[ii][kk] ] - Samples[dd][ii]);
+            uvecPoint[kk][dd] = Samples[dd][ neighbors[ii][kk] ] - Samples[dd][ii];
+          }
+
+          distances[kk] = Math.sqrt((double)f_sum);
+
+          for ( rr = 0; rr < TupleDimension; rr++ )
+          {
+            derivatives[kk][rr] = rangeValues[rr][ neighbors[ii][kk] ] -
+                                  rangeValues[rr][ii];
+          }
+        }
+
+        //- Interpolate for each partial derivative  -*
+        for ( pp = 0; pp < n_partials; pp++ )
+        {
+          sum_weights = 0f;
+          for ( kk = 0; kk < n_points; kk++ )
+          {
+            dotproduct = 0;
+
+            for ( dd = 0; dd < domainDim; dd++ ) {
+              dotproduct += uvecPoint[kk][dd]*u_vectors[pp][dd];
+            }
+
+            inv_dotproduct = 1f/dotproduct;
+
+            if ( ! Float.isInfinite(inv_dotproduct) ) {
+              sum_weights += weights[ii][kk];
+              for ( rr = 0; rr < TupleDimension; rr++ ) {
+                p_derivatives[pp][rr][ii] += derivatives[kk][rr]*inv_dotproduct*weights[ii][kk];
+              }
+            }
+          }
+          for ( rr = 0; rr < TupleDimension; rr++ ) {
+            p_derivatives[pp][rr][ii] /= sum_weights;
+          }
+        }
+      }
+
+    }
+
+    } //-not missing branch  -*
+
+
+    double[][] s_samples = null;
+    for ( pp = 0; pp < n_partials; pp++ )
+    {
+      s_samples = p_derivatives[pp];
+      if ( thisDomainFlag )
+      {
+        ((FlatField)datums[pp]).setSamples( s_samples );
+        ((FlatField)datums[pp]).setRangeErrors( rangeErrors_out[pp] );
+      }
+      else
+      {
+        MathType M_type = derivType_s[pp];
+
+        if ( M_type instanceof RealType )
+        {
+          datums[pp] = new Real( (RealType) M_type, s_samples[0][0], derivUnits[pp][0],
+                                 rangeErrors_out[pp][0]);
+        }
+        else if ( M_type instanceof RealTupleType )
+        {
+          Real[] reals = new Real[ TupleDimension ];
+          for ( ii = 0; ii < TupleDimension; ii++ )
+          {
+            reals[ii] = new Real( (RealType)((TupleType)M_type).getComponent(ii),
+                                  s_samples[ii][0], derivUnits[pp][ii], rangeErrors_out[pp][0] );
+          }
+
+          datums[pp] = new RealTuple( (RealTupleType) M_type, reals, RangeCoordinateSystem );
+        }
+        else if ( M_type instanceof TupleType )
+        {
+          ss = 0;
+          tt = 0;
+          int n_comps = ((TupleType)M_type).getDimension();
+          Data[] s_datums = new Data[ n_comps ];
+          for ( ii = 0; ii < n_comps; ii++ )
+          {
+            m_type = ((TupleType)M_type).getComponent(ii);
+            if ( m_type instanceof RealType )
+            {
+              s_datums[ii] = new Real( (RealType)m_type, s_samples[tt][0], derivUnits[pp][ii],
+                                        rangeErrors_out[pp][ii] );
+              tt++;
+            }
+            else if ( m_type instanceof RealTupleType )
+            {
+              int n_compsI = ((TupleType)m_type).getDimension();
+              Real[] reals = new Real[ n_compsI ];
+              for ( jj = 0; jj < n_compsI; jj++ )
+              {
+                reals[jj] = new Real( (RealType)((TupleType)m_type).getComponent(jj),
+                                s_samples[tt][0], derivUnits[pp][jj], rangeErrors_out[pp][jj] );
+                tt++;
+              }
+              s_datums[ii] = new RealTuple( (RealTupleType) m_type, reals,
+                                             RangeCoordinateSystems[ss] );
+              ss++;
+            }
+          }
+
+          datums[pp] = new Tuple( (TupleType)M_type, s_datums );
+        }
+      }
+    }
+
+    if ( n_partials == 1 )
+    {
+      return datums[0];
+    }
+    else
+    {
+      return new Tuple( datums );
+    }
+  }
+
+  public Data derivative( int error_mode )
+         throws VisADException, RemoteException
+  {
+    MathType[] derivType_s = null;
+    RealType[] d_partial_s = null;
+    return derivative( null, d_partial_s, derivType_s, error_mode );
+  }
+
+  public Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException
+  {
+    return derivative( null, null, derivType_s, error_mode );
+  }
+
+  public Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException
+  {
+    MathType[] derivType_s = null;
+    RealType[] d_partial_s = new RealType[1];
+    d_partial_s[0] = d_partial;
+
+    return (Function) derivative( null, d_partial_s, derivType_s, error_mode );
+  }
+
+  public Function derivative( RealType d_partial, MathType derivType, int error_mode )
+         throws VisADException, RemoteException
+  {
+    MathType[] derivType_s = new MathType[1];
+    RealType[] d_partial_s = new RealType[1];
+    derivType_s[0] = derivType;
+    d_partial_s[0] = d_partial;
+
+    return (Function) derivative( null, d_partial_s, derivType_s, error_mode );
+  }
+
+  /**
+   * Resamples the range to domain samples of a given set.  Resampling is either
+   * by nearest neighbor or mulit-linear interpolation.  NOTE: This code is very
+   * similar to FieldImpl.resample(Set,int,int).
+   * @param set                 The set of points at which to resample this
+   *                            field.
+   * @param sampling_mode       Resampling mode: Data.NEAREST_NEIGHBOR or
+   *                            Data.WEIGHTED_AVERAGE
+   * @param error_mode          Error estimation mode: Data.DEPENDENT,
+   *                            Data.INDEPENDENT, or Data.NO_ERRORS.
+   * @return                    Field of resampled data.  RangeSet objects
+   *                            in result are set to DoubleSet.  NOTE: May
+   *                            return this (i.e., not a copy).
+   */
+  public Field resample (Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+
+    /* NB: resampling is done in this method for a float domain.  If
+     * you make changes to this method, make the corresponding changes
+     * in resampleDouble.
+     */
+    Set domainSet = getDomainSet();
+
+    visad.util.Trace.call1("FlatField.resample");
+    if (domainSet.equals(set)) {
+      // nothing to do
+      visad.util.Trace.call2("FlatField.resample", "sampling set==domain set");
+      return this;
+    }
+
+    int dim = domainSet.getDimension();
+    if (dim != set.getDimension()) {
+      throw new SetException("FlatField.resample: bad Set Dimension");
+    }
+
+    if (domainSet instanceof GriddedDoubleSet) {
+      return resampleDouble(set, sampling_mode, error_mode);
+    }
+
+    CoordinateSystem coord_sys = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors =
+      (error_mode == NO_ERRORS) ? new ErrorEstimate[dim] : set.getSetErrors();
+
+    // create (initially missing) FlatField for return
+    Set[] sets = new Set[TupleDimension];
+    for (int i=0; i<TupleDimension; i++) {
+      SetType set_type =
+        new SetType(((FunctionType) Type).getFlatRange().getComponent(i));
+      // WLH 26 Nov 2001
+      // sets[i] = new DoubleSet(set_type);
+      if (sampling_mode == Data.NEAREST_NEIGHBOR) {
+        sets[i] = RangeSet[i];
+      }
+      else {
+        sets[i] = new FloatSet(set_type);
+      }
+    }
+
+    MathType range_type = ((FunctionType) Type).getRange();
+    RealTupleType domain_type = ((SetType) set.getType()).getDomain();
+    FunctionType func_type = new FunctionType(domain_type, range_type);
+    FlatField new_field =
+      new FlatField(func_type, set, RangeCoordinateSystem,
+                    RangeCoordinateSystems, sets, RangeUnits);
+
+    if (isMissing()) return new_field;
+
+    ErrorEstimate[] range_errors_in =
+      (error_mode == NO_ERRORS) ? new ErrorEstimate[TupleDimension] :
+                                  RangeErrors;
+    ErrorEstimate[] range_errors_out = range_errors_in;
+
+    int i, j, k; // loop indices
+
+    // create an array containing all indices of 'this'
+    int length = set.getLength();
+    int[] wedge = set.getWedge();
+
+    // get values from wedge and possibly transform coordinates
+    float[][] vals = set.indexToValue(wedge);
+    // holder for sampling errors of transformed set; these are
+    // only useful to help estmate range errors due to resampling
+    ErrorEstimate[] errors_out = new ErrorEstimate[dim];
+    float[][] oldvals = vals;
+    visad.util.Trace.call1("FlatField.resample:transformCoords");
+    try {  // this is only to throw a more meaningful message
+      vals = CoordinateSystem.transformCoordinates(
+                      ((FunctionType) Type).getDomain(), 
+                      getDomainCoordinateSystem(),
+                      getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(), coord_sys,
+                      units, errors, vals, false);
+    } catch (UnitException ue) {
+        throw new VisADException("Sampling set is not compatible with domain");
+    }
+    visad.util.Trace.call2("FlatField.resample:transformCoords");
+    boolean coord_transform = !(vals == oldvals);
+
+    // check whether we need to do sampling error calculations
+    boolean sampling_errors = (error_mode != NO_ERRORS);
+    if (sampling_errors) {
+      for (i=0; i<dim; i++) {
+        if (errors_out[i] == null) sampling_errors = false;
+      }
+      boolean any_range_error = false;
+      for (i=0; i<TupleDimension; i++) {
+        if (range_errors_in[i] != null) any_range_error = true;
+      }
+      if (!any_range_error) sampling_errors = false;
+    }
+    float[][] sampling_partials = new float[TupleDimension][dim];
+    float[][] error_values = new float[1][1];
+    if (sampling_errors) {
+      error_values = Set.doubleToFloat(
+                     ErrorEstimate.init_error_values(errors_out) );
+    }
+
+    // WLH 20 July 2000
+    float[][] values = null;
+    if (sampling_errors || (10 * length > getLength()) || 
+        !shouldBeDouble() || sampling_mode == WEIGHTED_AVERAGE) {
+      values = unpackFloats(false);
+      // values = Set.doubleToFloat(unpackValues());
+    }
+
+    float[][] new_values = new float[TupleDimension][length];
+    float[] new_valuesJ;
+    float[] valuesJ;
+
+    if (sampling_mode == WEIGHTED_AVERAGE && domainSet instanceof SimpleSet) {
+      // resample by interpolation
+      int[][] indices = new int[length][];
+      float[][] coefs = new float[length][];
+      ((SimpleSet) domainSet).valueToInterp(vals, indices, coefs);
+
+/* DEBUG
+// System.out.println("DomainSet = " + domainSet);
+// System.out.println("set = " + set);
+
+// for (i=0; i<length; i++) {
+boolean pr = false;
+int ii = length;
+if (ii > 0) ii = 1;
+if (indices == null) ii = 0;
+for (i=0; i<ii; i++) {
+  if (indices[i] != null && coefs[i] != null) {
+    pr = true;
+    if (i == 0) {
+      System.out.println("DomainSet = " + domainSet);
+      System.out.println("set = " + set);
+    }
+    System.out.println("vals[0][" + i + "] = " + vals[0][i] +
+                      " vals[1][" + i + "] = " + vals[1][i]);
+    String s = "indices[" + i + "] = ";
+    for (j=0; j<indices[i].length; j++) s = s + indices[i][j] + " ";
+    System.out.println(s);
+    s = "coefs[" + i + "] = ";
+    for (j=0; j<coefs[i].length; j++) s = s + coefs[i][j] + " ";
+    System.out.println(s);
+  }
+}
+*/
+      // WLH 20 July 2000
+      if (values != null) {
+        for (j=0; j<TupleDimension; j++) {
+          valuesJ = values[j];
+          new_valuesJ = new_values[j];
+          for (i=0; i<length; i++) {
+            float v = Float.NaN;
+            int len = indices[i] == null ? 0 : indices[i].length;
+            if (len > 0) {
+              v = valuesJ[indices[i][0]] * coefs[i][0];
+              for (k=1; k<len; k++) {
+                v += valuesJ[indices[i][k]] * coefs[i][k];
+              }
+              new_valuesJ[wedge[i]] = v;
+            }
+            else { // values outside grid
+              new_valuesJ[wedge[i]] = Float.NaN;
+            }
+          }
+        }
+      }
+      else {
+        for (i=0; i<length; i++) {
+          int len = indices[i] == null ? 0 : indices[i].length;
+          if (len > 0) {
+            float[][] xvals = new float[len][];
+            for (k = 0; k<len; k++) {
+              xvals[k] = unpackFloats(indices[i][k]);
+            }
+            for (j=0; j<TupleDimension; j++) {
+              float v = xvals[0][j] * coefs[i][0];
+              for (k=1; k<len; k++) v += xvals[k][j] * coefs[i][k];
+              new_values[j][wedge[i]] = v;
+            }
+          }         
+          else { // values outside grid
+            for (j=0; j<TupleDimension; j++) {
+              new_values[j][wedge[i]] = Float.NaN;
+            }
+          }
+        }
+      }
+/* DEBUG
+if (pr) System.out.println("value = " + new_values[0][0]);
+*/
+
+      if (sampling_errors) {
+        int[][] error_indices = new int[2 * dim][];
+        float[][] error_coefs = new float[2 * dim][];
+        ((SimpleSet) domainSet).valueToInterp(error_values, error_indices,
+                                              error_coefs);
+
+        for (j=0; j<TupleDimension; j++) {
+          for (i=0; i<dim; i++) {
+            float a = Float.NaN;
+            float b = Float.NaN;;
+            int len = error_indices[2*i].length;
+            if (len > 0) {
+              a = values[j][error_indices[2*i][0]] * error_coefs[2*i][0];
+              for (k=1; k<len; k++) {
+                a += values[j][error_indices[2*i][k]] * error_coefs[2*i][k];
+              }
+            }
+            len = error_indices[2*i+1].length;
+            if (len > 0) {
+              b = values[j][error_indices[2*i+1][0]] * error_coefs[2*i+1][0];
+              for (k=1; k<len; k++) {
+                b += values[j][error_indices[2*i+1][k]] * error_coefs[2*i+1][k];
+              }
+            }
+            sampling_partials[j][i] = Math.abs(b - a);
+          }
+        }
+      }
+
+    }
+    else { // NEAREST_NEIGHBOR or set is not SimpleSet
+      // simple resampling
+      int[] indices = domainSet.valueToIndex(vals);
+/* DEBUG
+// System.out.println("DomainSet = " + domainSet);
+// System.out.println("set = " + set);
+
+// for (i=0; i<length; i++) {
+boolean pr = false;
+int ii = length;
+if (ii > 0) ii = 1;
+if (indices == null) ii = 0;
+for (i=0; i<ii; i++) {
+  if (indices[i] >= 0) {
+    pr = true;
+    if (i == 0) {
+      System.out.println("DomainSet = " + domainSet);
+      System.out.println("set = " + set);
+    }
+    System.out.println("NEAREST_NEIGHBOR indices[" + i + "] = " + indices[i]);
+  }
+}
+*/
+      // WLH 20 July 2000
+      if (values != null) {
+        for (j=0; j<TupleDimension; j++) {
+          valuesJ = values[j];
+          new_valuesJ = new_values[j];
+          for (i=0; i<length; i++) {
+            new_valuesJ[wedge[i]] =
+              ((indices[i] >= 0) ? valuesJ[indices[i]]: Float.NaN);
+          }
+        }
+      }
+      else {
+        for (i=0; i<length; i++) {
+          if (indices[i] >= 0) {
+            float[] xvals = unpackFloats(indices[i]);
+            for (j=0; j<TupleDimension; j++) {
+              new_values[j][wedge[i]] = xvals[j];
+            }
+          }
+          else { // values outside grid
+            for (j=0; j<TupleDimension; j++) {
+              new_values[j][wedge[i]] = Float.NaN;
+            }
+          }
+        }
+      }
+/* DEBUG
+if (pr) System.out.println("value = " + new_values[0][0]);
+*/
+
+      if (sampling_errors) {
+        int[] error_indices = domainSet.valueToIndex(error_values);
+        for (j=0; j<TupleDimension; j++) {
+          for (i=0; i<dim; i++) {
+            float a = (float) ((error_indices[2*i] >= 0) ?
+                       values[j][error_indices[2*i]]: Double.NaN);
+            float b = (float) ((error_indices[2*i+1] >= 0) ?
+                       values[j][error_indices[2*i+1]]: Double.NaN);
+            sampling_partials[j][i] = Math.abs(b - a);
+          }
+        }
+      }
+
+    }
+
+    if (sampling_errors) {
+      for (j=0; j<TupleDimension; j++) {
+        if (range_errors_in[j] != null) {
+          float error = (float) range_errors_in[j].getErrorValue();
+          if (error_mode == Data.INDEPENDENT) {
+            error = error * error;
+            for (i=0; i<dim; i++) {
+              error += sampling_partials[j][i] * sampling_partials[j][i];
+            }
+            error = (float) Math.sqrt(error);
+          }
+          else { // error_mode == Data.DEPENDENT
+            for (i=0; i<dim; i++) {
+              error += sampling_partials[j][i];
+            }
+          }
+          range_errors_out[j] =
+            new ErrorEstimate(new_values[j], error, RangeUnits[j]);
+        }
+      }
+    }
+    else if (error_mode != NO_ERRORS) {
+      for (j=0; j<TupleDimension; j++) {
+        if (range_errors_in[j] != null) {
+          range_errors_out[j] =
+            new ErrorEstimate(new_values[j], range_errors_in[j].getErrorValue(),
+                              RangeUnits[j]);
+        }
+      }
+    }
+
+    if (coord_transform) {
+      range_errors_in = range_errors_out;
+      MathType Range = ((FunctionType) Type).getRange();
+      if (Range instanceof RealVectorType) {
+        new_values = ((RealVectorType) Range).transformVectors(
+                      ((FunctionType) Type).getDomain(),
+                      getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(),
+                      coord_sys, units, RangeCoordinateSystem,
+                      range_errors_in, range_errors_out,
+                      oldvals, vals, new_values);
+      }
+      else if (Range instanceof TupleType && !(Range instanceof RealTupleType)) {
+        int offset = 0;
+        int m = ((TupleType) Range).getDimension();
+        for (j=0; j<m; j++) {
+          MathType comp_type = ((TupleType) Range).getComponent(j);
+          if (comp_type instanceof RealVectorType) {
+            int mm = ((RealVectorType) comp_type).getDimension();
+            float[][] comp_vals = new float[mm][];
+            for (int jj=0; jj<mm; jj++) {
+              comp_vals[jj] = new_values[offset + jj];
+            }
+            ErrorEstimate[] comp_errors_in = new ErrorEstimate[mm];
+            for (int jj=0; jj<mm; jj++) {
+              comp_errors_in[jj] = range_errors_in[offset + jj];
+            }
+            ErrorEstimate[] comp_errors_out = comp_errors_in;
+            comp_vals = ((RealVectorType) comp_type).transformVectors(
+                        ((FunctionType) Type).getDomain(),
+                        getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                        ((SetType) set.getType()).getDomain(), coord_sys, units,
+                        RangeCoordinateSystems[j],
+                        comp_errors_in, comp_errors_out,
+                        oldvals, vals, comp_vals);
+            for (int jj=0; jj<mm; jj++) {
+              new_values[offset + jj] = comp_vals[jj];
+            }
+            for (int jj=0; jj<mm; jj++) {
+              range_errors_out[offset + jj] = comp_errors_out[jj];
+            }
+          }
+          if (comp_type instanceof RealType) {
+            offset++;
+          }
+          else {
+            offset += ((RealTupleType) comp_type).getDimension();
+          }
+        }
+      }
+    } // end if (coord_transform)
+    new_field.packValues(new_values, false);
+    // new_field.DoubleRange = new_values;
+    new_field.setRangeErrors(range_errors_out);
+    new_field.clearMissing();
+    visad.util.Trace.call2("FlatField.resample");
+
+
+    return new_field;
+  }
+
+  /**
+   * Resamples the range to domain samples of a given double set.  Resampling is either
+   * by nearest neighbor or mulit-linear interpolation.  NOTE: This code is very
+   * similar to FieldImpl.resample(Set,int,int).
+   * @param set                 The set of points at which to resample this
+   *                            field.
+   * @param sampling_mode       Resampling mode: Data.NEAREST_NEIGHBOR or
+   *                            Data.WEIGHTED_AVERAGE
+   * @param error_mode          Error estimation mode: Data.DEPENDENT,
+   *                            Data.INDEPENDENT, or Data.NO_ERRORS.
+   * @return                    Field of resampled data.  RangeSet objects
+   *                            in result are set to DoubleSet.  NOTE: May
+   *                            return this (i.e., not a copy).
+   */
+  public Field resampleDouble(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+
+    /* NB: resampling is done in this method for a double domain.  If
+     * you make changes to this method, make the corresponding changes
+     * in resample if necessary.
+     */
+    Set domainSet = getDomainSet();
+    visad.util.Trace.call1("FlatField.resample");
+    if (domainSet.equals(set)) {
+      // nothing to do
+      visad.util.Trace.call2("FlatField.resample", "sampling set==domain set");
+      return this;
+    }
+
+    int dim = domainSet.getDimension();
+    if (dim != set.getDimension()) {
+      throw new SetException("FlatField.resample: bad Set Dimension");
+    }
+
+    if (!(domainSet instanceof GriddedDoubleSet)) {
+      return resample(set, sampling_mode, error_mode);
+    }
+
+    CoordinateSystem coord_sys = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors =
+      (error_mode == NO_ERRORS) ? new ErrorEstimate[dim] : set.getSetErrors();
+
+    // create (initially missing) FlatField for return
+    Set[] sets = new Set[TupleDimension];
+    for (int i=0; i<TupleDimension; i++) {
+      SetType set_type =
+        new SetType(((FunctionType) Type).getFlatRange().getComponent(i));
+      // WLH 26 Nov 2001
+      // sets[i] = new DoubleSet(set_type);
+      if (sampling_mode == Data.NEAREST_NEIGHBOR) {
+        sets[i] = RangeSet[i];
+      }
+      else {
+        sets[i] = new FloatSet(set_type);
+      }
+    }
+
+    MathType range_type = ((FunctionType) Type).getRange();
+    RealTupleType domain_type = ((SetType) set.getType()).getDomain();
+    FunctionType func_type = new FunctionType(domain_type, range_type);
+    FlatField new_field =
+      new FlatField(func_type, set, RangeCoordinateSystem,
+                    RangeCoordinateSystems, sets, RangeUnits);
+
+    if (isMissing()) return new_field;
+
+    ErrorEstimate[] range_errors_in =
+      (error_mode == NO_ERRORS) ? new ErrorEstimate[TupleDimension] :
+                                  RangeErrors;
+    ErrorEstimate[] range_errors_out = range_errors_in;
+
+    int i, j, k; // loop indices
+
+    // create an array containing all indices of 'this'
+    int length = set.getLength();
+    int[] wedge = set.getWedge();
+
+    // get values from wedge and possibly transform coordinates
+    double[][] vals = set.indexToDouble(wedge);
+    // holder for sampling errors of transformed set; these are
+    // only useful to help estmate range errors due to resampling
+    ErrorEstimate[] errors_out = new ErrorEstimate[dim];
+    double[][] oldvals = vals;
+    visad.util.Trace.call1("FlatField.resample:transformCoords");
+    try {  // this is only to throw a more meaningful message
+      vals = CoordinateSystem.transformCoordinates(
+                      ((FunctionType) Type).getDomain(), 
+                      getDomainCoordinateSystem(),
+                      getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(), coord_sys,
+                      units, errors, vals, false);
+    } catch (UnitException ue) {
+        throw new VisADException("Sampling set is not compatible with domain");
+    }
+    visad.util.Trace.call2("FlatField.resample:transformCoords");
+    boolean coord_transform = !(vals == oldvals);
+
+    // check whether we need to do sampling error calculations
+    boolean sampling_errors = (error_mode != NO_ERRORS);
+    if (sampling_errors) {
+      for (i=0; i<dim; i++) {
+        if (errors_out[i] == null) sampling_errors = false;
+      }
+      boolean any_range_error = false;
+      for (i=0; i<TupleDimension; i++) {
+        if (range_errors_in[i] != null) any_range_error = true;
+      }
+      if (!any_range_error) sampling_errors = false;
+    }
+    double[][] sampling_partials = new double[TupleDimension][dim];
+    double[][] error_values = new double[1][1];
+    if (sampling_errors) {
+      error_values = ErrorEstimate.init_error_values(errors_out);
+    }
+
+    // WLH 20 July 2000
+    float[][] values = null;
+    if (sampling_errors || (10 * length > getLength()) || 
+        !shouldBeDouble() || sampling_mode == WEIGHTED_AVERAGE) {
+      values = unpackFloats(false);
+      // values = Set.doubleToFloat(unpackValues());
+    }
+
+    double[][] new_values = new double[TupleDimension][length];
+    double[] new_valuesJ;
+    float[] valuesJ;
+
+    if (sampling_mode == WEIGHTED_AVERAGE) {
+      // resample by interpolation
+      int[][] indices = new int[length][];
+      double[][] coefs = new double[length][];
+      ((GriddedDoubleSet) domainSet).doubleToInterp(vals, indices, coefs);
+
+/* DEBUG
+// System.out.println("DomainSet = " + domainSet);
+// System.out.println("set = " + set);
+
+// for (i=0; i<length; i++) {
+boolean pr = false;
+int ii = length;
+if (ii > 0) ii = 1;
+if (indices == null) ii = 0;
+for (i=0; i<ii; i++) {
+  if (indices[i] != null && coefs[i] != null) {
+    pr = true;
+    if (i == 0) {
+      System.out.println("DomainSet = " + domainSet);
+      System.out.println("set = " + set);
+    }
+    System.out.println("vals[0][" + i + "] = " + vals[0][i] +
+                      " vals[1][" + i + "] = " + vals[1][i]);
+    String s = "indices[" + i + "] = ";
+    for (j=0; j<indices[i].length; j++) s = s + indices[i][j] + " ";
+    System.out.println(s);
+    s = "coefs[" + i + "] = ";
+    for (j=0; j<coefs[i].length; j++) s = s + coefs[i][j] + " ";
+    System.out.println(s);
+  }
+}
+*/
+      // WLH 20 July 2000
+      if (values != null) {
+        for (j=0; j<TupleDimension; j++) {
+          valuesJ = values[j];
+          new_valuesJ = new_values[j];
+          for (i=0; i<length; i++) {
+            double v = Double.NaN;
+            int len = indices[i] == null ? 0 : indices[i].length;
+            if (len > 0) {
+              v = valuesJ[indices[i][0]] * coefs[i][0];
+              for (k=1; k<len; k++) {
+                v += valuesJ[indices[i][k]] * coefs[i][k];
+              }
+              new_valuesJ[wedge[i]] = v;
+            }
+            else { // values outside grid
+              new_valuesJ[wedge[i]] = Float.NaN;
+            }
+          }
+        }
+      }
+      else {
+        for (i=0; i<length; i++) {
+          int len = indices[i] == null ? 0 : indices[i].length;
+          if (len > 0) {
+            float[][] xvals = new float[len][];
+            for (k = 0; k<len; k++) {
+              xvals[k] = unpackFloats(indices[i][k]);
+            }
+            for (j=0; j<TupleDimension; j++) {
+              double v = xvals[0][j] * coefs[i][0];
+              for (k=1; k<len; k++) v += xvals[k][j] * coefs[i][k];
+              new_values[j][wedge[i]] = v;
+            }
+          }         
+          else { // values outside grid
+            for (j=0; j<TupleDimension; j++) {
+              new_values[j][wedge[i]] = Float.NaN;
+            }
+          }
+        }
+      }
+/* DEBUG
+if (pr) System.out.println("value = " + new_values[0][0]);
+*/
+
+      if (sampling_errors) {
+        int[][] error_indices = new int[2 * dim][];
+        double[][] error_coefs = new double[2 * dim][];
+        ((GriddedDoubleSet) domainSet).doubleToInterp(error_values, error_indices,
+                                              error_coefs);
+
+        for (j=0; j<TupleDimension; j++) {
+          for (i=0; i<dim; i++) {
+            double a = Double.NaN;
+            double b = Double.NaN;;
+            int len = error_indices[2*i].length;
+            if (len > 0) {
+              a = values[j][error_indices[2*i][0]] * error_coefs[2*i][0];
+              for (k=1; k<len; k++) {
+                a += values[j][error_indices[2*i][k]] * error_coefs[2*i][k];
+              }
+            }
+            len = error_indices[2*i+1].length;
+            if (len > 0) {
+              b = values[j][error_indices[2*i+1][0]] * error_coefs[2*i+1][0];
+              for (k=1; k<len; k++) {
+                b += values[j][error_indices[2*i+1][k]] * error_coefs[2*i+1][k];
+              }
+            }
+            sampling_partials[j][i] = Math.abs(b - a);
+          }
+        }
+      }
+
+    }
+    else { // NEAREST_NEIGHBOR or set is not SimpleSet
+      // simple resampling
+      int[] indices = domainSet.doubleToIndex(vals);
+/* DEBUG
+// System.out.println("DomainSet = " + domainSet);
+// System.out.println("set = " + set);
+
+// for (i=0; i<length; i++) {
+boolean pr = false;
+int ii = length;
+if (ii > 0) ii = 1;
+if (indices == null) ii = 0;
+for (i=0; i<ii; i++) {
+  if (indices[i] >= 0) {
+    pr = true;
+    if (i == 0) {
+      System.out.println("DomainSet = " + domainSet);
+      System.out.println("set = " + set);
+    }
+    System.out.println("NEAREST_NEIGHBOR indices[" + i + "] = " + indices[i]);
+  }
+}
+*/
+      // WLH 20 July 2000
+      if (values != null) {
+        for (j=0; j<TupleDimension; j++) {
+          valuesJ = values[j];
+          new_valuesJ = new_values[j];
+          for (i=0; i<length; i++) {
+            new_valuesJ[wedge[i]] =
+              ((indices[i] >= 0) ? valuesJ[indices[i]]: Float.NaN);
+          }
+        }
+      }
+      else {
+        for (i=0; i<length; i++) {
+          if (indices[i] >= 0) {
+            float[] xvals = unpackFloats(indices[i]);
+            for (j=0; j<TupleDimension; j++) {
+              new_values[j][wedge[i]] = xvals[j];
+            }
+          }
+          else { // values outside grid
+            for (j=0; j<TupleDimension; j++) {
+              new_values[j][wedge[i]] = Float.NaN;
+            }
+          }
+        }
+      }
+/* DEBUG
+if (pr) System.out.println("value = " + new_values[0][0]);
+*/
+
+      if (sampling_errors) {
+        int[] error_indices = domainSet.doubleToIndex(error_values);
+        for (j=0; j<TupleDimension; j++) {
+          for (i=0; i<dim; i++) {
+            float a = (float) ((error_indices[2*i] >= 0) ?
+                       values[j][error_indices[2*i]]: Double.NaN);
+            float b = (float) ((error_indices[2*i+1] >= 0) ?
+                       values[j][error_indices[2*i+1]]: Double.NaN);
+            sampling_partials[j][i] = Math.abs(b - a);
+          }
+        }
+      }
+
+    }
+
+    if (sampling_errors) {
+      for (j=0; j<TupleDimension; j++) {
+        if (range_errors_in[j] != null) {
+          float error = (float) range_errors_in[j].getErrorValue();
+          if (error_mode == Data.INDEPENDENT) {
+            error = error * error;
+            for (i=0; i<dim; i++) {
+              error += sampling_partials[j][i] * sampling_partials[j][i];
+            }
+            error = (float) Math.sqrt(error);
+          }
+          else { // error_mode == Data.DEPENDENT
+            for (i=0; i<dim; i++) {
+              error += sampling_partials[j][i];
+            }
+          }
+          range_errors_out[j] =
+            new ErrorEstimate(new_values[j], error, RangeUnits[j]);
+        }
+      }
+    }
+    else if (error_mode != NO_ERRORS) {
+      for (j=0; j<TupleDimension; j++) {
+        if (range_errors_in[j] != null) {
+          range_errors_out[j] =
+            new ErrorEstimate(new_values[j], range_errors_in[j].getErrorValue(),
+                              RangeUnits[j]);
+        }
+      }
+    }
+
+    if (coord_transform) {
+      range_errors_in = range_errors_out;
+      MathType Range = ((FunctionType) Type).getRange();
+      if (Range instanceof RealVectorType) {
+        new_values = ((RealVectorType) Range).transformVectors(
+                      ((FunctionType) Type).getDomain(),
+                      getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                      ((SetType) set.getType()).getDomain(),
+                      coord_sys, units, RangeCoordinateSystem,
+                      range_errors_in, range_errors_out,
+                      oldvals, vals, new_values);
+      }
+      else if (Range instanceof TupleType && !(Range instanceof RealTupleType)) {
+        int offset = 0;
+        int m = ((TupleType) Range).getDimension();
+        for (j=0; j<m; j++) {
+          MathType comp_type = ((TupleType) Range).getComponent(j);
+          if (comp_type instanceof RealVectorType) {
+            int mm = ((RealVectorType) comp_type).getDimension();
+            double[][] comp_vals = new double[mm][];
+            for (int jj=0; jj<mm; jj++) {
+              comp_vals[jj] = new_values[offset + jj];
+            }
+            ErrorEstimate[] comp_errors_in = new ErrorEstimate[mm];
+            for (int jj=0; jj<mm; jj++) {
+              comp_errors_in[jj] = range_errors_in[offset + jj];
+            }
+            ErrorEstimate[] comp_errors_out = comp_errors_in;
+            comp_vals = ((RealVectorType) comp_type).transformVectors(
+                        ((FunctionType) Type).getDomain(),
+                        getDomainCoordinateSystem(), getDomainUnits(), errors_out,
+                        ((SetType) set.getType()).getDomain(), coord_sys, units,
+                        RangeCoordinateSystems[j],
+                        comp_errors_in, comp_errors_out,
+                        oldvals, vals, comp_vals);
+            for (int jj=0; jj<mm; jj++) {
+              new_values[offset + jj] = comp_vals[jj];
+            }
+            for (int jj=0; jj<mm; jj++) {
+              range_errors_out[offset + jj] = comp_errors_out[jj];
+            }
+          }
+          if (comp_type instanceof RealType) {
+            offset++;
+          }
+          else {
+            offset += ((RealTupleType) comp_type).getDimension();
+          }
+        }
+      }
+    } // end if (coord_transform)
+    new_field.packValues(new_values, false);
+    // new_field.DoubleRange = new_values;
+    new_field.setRangeErrors(range_errors_out);
+    new_field.clearMissing();
+    visad.util.Trace.call2("FlatField.resample");
+
+
+    return new_field;
+  }
+
+  /** convert this FlatField to a (non-Flat) FieldImpl */
+  public Field convertToField() throws VisADException, RemoteException {
+    Field new_field = new FieldImpl((FunctionType) Type, getDomainSet());
+    if (isMissing()) return new_field;
+    for (int i=0; i<getLength(); i++) {
+      new_field.setSample(i, getSample(i));
+    }
+    return new_field;
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException {
+    if (isMissing()) return shadow;
+
+    ShadowRealTupleType domain_type = ((ShadowFunctionType) type).getDomain();
+    int n = domain_type.getDimension();
+    double[][] ranges = new double[2][n];
+    // DomainSet.computeRanges handles Reference
+    shadow = getDomainSet().computeRanges(domain_type, shadow, ranges, true);
+    ShadowRealTupleType shad_ref;
+    // skip range if no range components are mapped
+    int[] indices = ((ShadowFunctionType) type).getRangeDisplayIndices();
+    boolean any_mapped = false;
+    for (int i=0; i<TupleDimension; i++) {
+      if (indices[i] >= 0) any_mapped = true;
+    }
+    if (!any_mapped) return shadow;
+
+    // check for any range coordinate systems
+    boolean anyRangeRef = (RangeCoordinateSystem != null);
+    if (RangeCoordinateSystems != null) {
+      for (int i=0; i<RangeCoordinateSystems.length; i++) {
+        anyRangeRef |= (RangeCoordinateSystems[i] != null);
+      }
+    }
+    ranges = anyRangeRef ? new double[2][TupleDimension] : null;
+
+    // get range values
+
+
+
+    double[][] valuesD = null;
+    float[][] valuesF = null;
+    if (shouldBeDouble ()) {
+        valuesD = unpackValues (false);
+    } else {
+        valuesF = unpackFloats (false);
+    }
+
+
+    for (int i=0; i<TupleDimension; i++) {
+        double[] valuesDI = null;
+        float [] valuesFI = null;
+        if (valuesD !=null)
+            valuesDI = valuesD[i];
+        if (valuesF !=null)
+            valuesFI = valuesF[i];
+      int k = indices[i];
+      if (k >= 0 || anyRangeRef) {
+        double min = Double.MAX_VALUE;
+        double max = -Double.MAX_VALUE;
+
+        if (valuesDI!=null) {
+            for (int j=0; j<getLength(); j++) {
+                if (valuesDI[j] == valuesDI[j]) {
+                    min = Math.min(min, valuesDI[j]);
+                    max = Math.max(max, valuesDI[j]);
+                }
+            }
+        }
+
+        if (valuesFI!=null) {
+            for (int j=0; j<getLength(); j++) {
+                if (valuesFI[j] == valuesFI[j]) {
+                    min = Math.min(min, valuesFI[j]);
+                    max = Math.max(max, valuesFI[j]);
+                }
+            }
+        }
+
+
+        Unit dunit = ((RealType)
+          ((FunctionType) Type).getFlatRange().getComponent(i)).getDefaultUnit();
+        if (dunit != null && !dunit.equals(RangeUnits[i])) {
+          min = dunit.toThis(min, RangeUnits[i]);
+          max = dunit.toThis(max, RangeUnits[i]);
+        }
+        if (anyRangeRef) {
+          ranges[0][i] = Math.min(ranges[0][i], min);
+          ranges[1][i] = Math.max(ranges[1][i], max);
+        }
+        if (k >= 0 && k < shadow.ranges[0].length) {
+          shadow.ranges[0][k] = Math.min(shadow.ranges[0][k], min);
+          shadow.ranges[1][k] = Math.max(shadow.ranges[1][k], max);
+        }
+      }
+    }
+    if (RangeCoordinateSystem != null) {
+      // computeRanges for Reference (relative to range) RealTypes
+      ShadowRealTupleType range_type =
+        (ShadowRealTupleType) ((ShadowFunctionType) type).getRange();
+      shad_ref = range_type.getReference();
+      shadow = computeReferenceRanges(range_type, RangeCoordinateSystem,
+                                      RangeUnits, shadow, shad_ref, ranges);
+    }
+    else if (RangeCoordinateSystems != null) {
+      TupleType RangeType = (TupleType) ((FunctionType) Type).getRange();
+      int j = 0;
+      for (int i=0; i<RangeCoordinateSystems.length; i++) {
+        MathType component = RangeType.getComponent(i);
+        if (component instanceof RealType) {
+          j++;
+        }
+        else { // (component instanceof RealTupleType)
+          int m = ((RealTupleType) component).getDimension();
+          if (RangeCoordinateSystems[i] != null) {
+            // computeRanges for Reference (relative to range
+            // component) RealTypes
+            double[][] sub_ranges = new double[2][m];
+            Unit[] sub_units = new Unit[m];
+            for (int k=0; k<m; k++) {
+              sub_ranges[0][k] = ranges[0][j];
+              sub_ranges[1][k] = ranges[1][j];
+              sub_units[k] = RangeUnits[j];
+              j++;
+            }
+            ShadowRealTupleType range_type = (ShadowRealTupleType)
+              ((ShadowTupleType) ((ShadowFunctionType) type).getRange()).
+                                                             getComponent(i);
+            shad_ref = range_type.getReference();
+            shadow = computeReferenceRanges(range_type, RangeCoordinateSystems[i],
+                                       sub_units, shadow, shad_ref, sub_ranges);
+          }
+          else { // (RangeCoordinateSystems[i] == null)
+            j += m;
+          }
+        } // end if (component instanceof RealTupleType)
+      } // end for (int i=0; i<RangeCoordinateSystems.length; i++)
+    } // end if (RangeCoordinateSystems != null)
+    return shadow;
+  }
+
+  /** return a FlatField that clones this, except its ErrorEstimate-s
+      are adjusted for sampling errors in error */
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    if (isMissing() || error == null || error.isMissing()) return this;
+    FlatField field =
+      new FlatField((FunctionType) Type, getDomainSet(), RangeCoordinateSystem,
+                    RangeCoordinateSystems, RangeSet, RangeUnits);
+    if (isMissing()) return field;
+    FlatField new_error = (FlatField)
+      ((FlatField) error).resample(getDomainSet(), NEAREST_NEIGHBOR, NO_ERRORS);
+
+    //TODO: check for float vs. double
+    double[][] values = unpackValues();
+    field.packValues(values, false);
+
+    ErrorEstimate[] errors = new ErrorEstimate[TupleDimension];
+    double[][] error_values = new_error.unpackValues();
+    for (int i=0; i<TupleDimension; i++) {
+      double a = 0.0;
+      for (int k=0; k<error_values[i].length; k++) {
+        a += error_values[i][k];
+      }
+      a = a / error_values.length;
+      double b = RangeErrors[i].getErrorValue();
+      double e = (error_mode == INDEPENDENT) ? Math.sqrt(a * a + b * b) :
+                                               Math.abs(a) + Math.abs(b);
+      errors[i] = new ErrorEstimate(values[i], e, RangeUnits[i]);
+    }
+    field.setRangeErrors(errors);
+    return field;
+  }
+
+  public boolean isFlatField() {
+    return true;
+  }
+
+  /** clone this FlatField, except substitute a DoubleSet for each
+      component of RangeSet */
+  private FlatField cloneDouble() throws VisADException {
+    return cloneDouble(RangeUnits, RangeErrors);
+  }
+
+  /** clone this FlatField, except substitute a DoubleSet for each
+      component of RangeSet, and substitute units and errors */
+  private FlatField cloneDouble(Unit[] units, ErrorEstimate[] errors)
+          throws VisADException {
+    return cloneDouble( null, units, errors );
+    /*- TDR June 1998
+    // create (initially missing) FlatField for return
+    // use DoubleSet rather than RangeSet for intermediate computation results
+    Set[] sets = new Set[TupleDimension];
+    for (int i=0; i<TupleDimension; i++) {
+      SetType set_type =
+        new SetType(((FunctionType) Type).getFlatRange().getComponent(i));
+      sets[i] = new DoubleSet(set_type);
+    }
+    FlatField field =
+      new FlatField((FunctionType) Type, getDomainSet(), RangeCoordinateSystem,
+                    RangeCoordinateSystems, sets, units);
+    double[][] values = unpackValues();
+    field.packValues(values, false);
+    // field.DoubleRange = values;
+    field.setRangeErrors(errors);
+    field.clearMissing();
+    return field;
+    */
+  }
+
+  /*- TDR June 1998  */
+  protected FlatField cloneDouble ( MathType f_type, Unit[] units,
+                                 ErrorEstimate[] errors )
+          throws VisADException
+  {
+      return cloneDouble ( f_type, units, errors, null);
+  }
+
+
+  /*- TDR June 1998  */
+  protected FlatField cloneDouble (MathType f_type, 
+                                 Unit[] units,
+                                 ErrorEstimate[] errors, 
+                                 double[][]newValues )
+      throws VisADException
+    {
+    MathType N_type = ((f_type == null) ? Type : f_type );
+
+    // create (initially missing) FlatField for return
+    // use DoubleSet rather than RangeSet for intermediate computation results
+    Set[] sets = new Set[TupleDimension];
+    for (int i=0; i<TupleDimension; i++) {
+      SetType set_type =
+        new SetType(((FunctionType) N_type).getFlatRange().getComponent(i));
+      sets[i] = new DoubleSet(set_type);
+    }
+/* WLH 3 April 2003
+    FlatField field =
+      new FlatField((FunctionType) N_type, 
+                    getDomainSet(), 
+                    RangeCoordinateSystem,
+                    RangeCoordinateSystems, 
+                    sets, 
+                    units);
+*/
+    RealTupleType d_type = ((FunctionType)N_type).getDomain();
+    Set new_set = null;
+    if (!d_type.equals( ((FunctionType) getType()).getDomain() )) {
+      new_set = (Set) getDomainSet().cloneButType(d_type);
+    }
+    else {
+      new_set = getDomainSet();
+    }
+    FlatField field =
+      new FlatField((FunctionType) N_type,
+                    new_set,
+                    RangeCoordinateSystem,
+                    RangeCoordinateSystems,
+                    sets,
+                    units);
+
+    if (newValues == null)
+        newValues = unpackValues ();
+    field.packValues(newValues, false);
+
+    // field.DoubleRange = values;
+    field.setRangeErrors(errors);
+    field.clearMissing();
+    return field;
+  }
+
+
+
+  protected FlatField cloneFloat (MathType f_type, 
+                                Unit[] units,
+                                ErrorEstimate[] errors)
+          throws VisADException
+  {
+      //Pass in null values array
+      return cloneFloat (f_type, units, errors, null);
+  }
+
+  /*- TDR June 1998  */
+  protected FlatField cloneFloat (MathType f_type, 
+                                Unit[] units,
+                                ErrorEstimate[] errors, 
+                                float[][]newValues )
+          throws VisADException
+  {
+      MathType N_type = ((f_type == null) ? Type : f_type );
+
+    // create (initially missing) FlatField for return
+    // use FloatSet rather than RangeSet for intermediate computation results
+    Set[] sets = new Set[TupleDimension];
+    for (int i=0; i<TupleDimension; i++) {
+      SetType set_type =
+        new SetType(((FunctionType) N_type).getFlatRange().getComponent(i));
+      sets[i] = new FloatSet(set_type);
+    }
+/* WLH 3 April 2003
+    FlatField field =
+      new FlatField((FunctionType) N_type, getDomainSet(), RangeCoordinateSystem,
+                    RangeCoordinateSystems, sets, units);
+*/
+    RealTupleType d_type = ((FunctionType)N_type).getDomain();
+    Set new_set = null;
+    if (!d_type.equals( ((FunctionType) getType()).getDomain() )) {
+      new_set = (Set) getDomainSet().cloneButType(d_type);
+    }
+    else {
+      new_set = getDomainSet();
+    }
+    FlatField field = 
+      new FlatField((FunctionType) N_type, new_set, RangeCoordinateSystem,
+                    RangeCoordinateSystems, sets, units);
+
+    if (newValues == null) {
+        //If we don't have the values array then copy this one.
+        newValues = unpackFloats (true);
+    }
+    field.packValues(newValues, false);
+
+    // field.DoubleRange = values;
+    field.setRangeErrors(errors);
+    field.clearMissing();
+    return field;
+  }
+
+
+  /** clone metadata but return missing values */
+  private FlatField cloneMissing() throws VisADException {
+    return new FlatField((FunctionType) Type, getDomainSet(), RangeCoordinateSystem,
+                         RangeCoordinateSystems, RangeSet, RangeUnits);
+  }
+
+  /**
+   * Clones this instance.  Immutable fields are shallow copied.  Range
+   * values, however, are deep copied.
+   *
+   * <p> Note that it is possible to simultaneously modify the domain-set of
+   * both this instance and the clone by modifying the values in the array
+   * returned by invoking <code>getSamples(false)</code> on the domain-set of
+   * either this instance or the clone.  Don't do this unless you enjoy 
+   * debugging.</p>
+   *
+   * @return                  A clone of this instance.
+   * @throws RuntimeException if a {@link VisADException} occurs.
+   */
+  public Object clone() {
+    FlatField clone;
+    
+    try {
+      clone = (FlatField)super.clone();
+    }
+    catch (CloneNotSupportedException ex) {
+      throw new Error("Assertion failure");  // can't happen
+    }
+
+    synchronized(DoubleRange) {
+      if (!MissingFlag) {
+        try {
+          // DRM 3 Oct 2006 - we need to recreate this otherwise 
+          // it's shared and it's mutable!
+          clone.DoubleRange = new double[TupleDimension][];
+          double[][] values  = unpackValues(true);
+          clone.packValues(values, false);
+        }
+        catch (VisADException ex) {
+          throw new RuntimeException(ex.toString());
+        }
+        try {
+          clone.setRangeErrors(RangeErrors);
+        }
+        catch (FieldException ex) {
+          throw new Error("Assertion failure");  // can't happen
+        }
+      }
+    }
+
+    return clone;
+  }
+
+  String valuesString() throws VisADException {
+    int rowlength;
+    StringBuffer s = new StringBuffer("");
+    int ncolumns = 8 / TupleDimension;
+    if (ncolumns < 1) ncolumns = 1;
+    if (getDomainSet() instanceof GriddedSet) {
+        // && ((GriddedSet) getDomainSet()).ManifoldDimension == 2) {
+      rowlength = ((GriddedSet) getDomainSet()).getLength(0);
+    }
+    else {
+      rowlength = getLength();
+    }
+    RealTupleType range = ((FunctionType) Type).getFlatRange();
+    RealType[] types = range.getRealComponents();
+    double[][] values = unpackValues();
+    int rl = rowlength;
+    int i = 0;
+    while (i<getLength()) {
+      int nc = Math.min(rl, Math.min(ncolumns, getLength()-i));
+      int ip = i + nc;
+      for (int k=i; k<ip; k++) {
+        if (k > i) s.append(", ");
+        if (TupleDimension == 1) {
+          s.append(new Real(types[0], values[0][k], RangeUnits[0]).toString());
+        }
+        else if (((FunctionType) Type).getReal()) {
+          String t = "(" + new Real(types[0], values[0][k], RangeUnits[0]);
+          for (int j=1; j<TupleDimension; j++) {
+            t = t + ", " + new Real(types[j], values[j][k], RangeUnits[j]);
+          }
+          t = t + ")";
+          s.append(t);
+        }
+        else { // Flat Tuple
+          TupleType RangeType = (TupleType) ((FunctionType) Type).getRange();
+          String t = "(";
+          int j = 0;
+          for (int l=0; l<RangeType.getDimension(); l++) {
+            if (j > 0) t = t + ", ";
+            MathType type = RangeType.getComponent(l);
+            if (type instanceof RealType) {
+              t = t + new Real(types[j], values[j][k], RangeUnits[j]);
+              j++;
+            }
+            else {
+              int mm = ((TupleType) type).getDimension();
+              t = t + "(" + new Real(types[j], values[j][k], RangeUnits[j]);
+              j++;
+              for (int kk=1; kk<mm; kk++) {
+                t = t + ", " + new Real(types[j], values[j][k], RangeUnits[j]);
+                j++;
+              }
+              t = t + ")";
+            }
+          }
+          t = t + ")";
+          s.append(t);
+        }
+      } // end for (int k=i; k<ip; k++)
+      s.append("\n");
+      i = ip;
+      rl -= nc;
+      if (rl <= 0) {
+        rl = rowlength;
+        s.append("\n");
+      }
+    } // end while (i<getLength())
+    return s.toString();
+  }
+
+  public String toString() {
+    try {
+      if (isMissing()) {
+        return "FlatField  missing\n";
+      }
+      else {
+        return "FlatField\n    " + Type + "\n" + valuesString();
+      }
+    }
+    catch (VisADException e) {
+      return e.toString();
+    }
+  }
+
+  public String longString(String pre) throws VisADException {
+    String t = pre + "FlatField\n" + pre + "  Type: " +
+               Type.toString() + "\n";
+    if (getDomainSet() != null) {
+      t = t + pre + "  DomainSet:\n" + getDomainSet().longString(pre + "    ");
+    }
+    else {
+      t = t + pre + "  DomainSet: undefined\n";
+    }
+    for (int i=0; i<TupleDimension; i++) {
+      if (RangeSet[i] != null) {
+        t = t + pre + "  RangeSet[" + i + "]:\n" + RangeSet[i].longString(pre + "    ");
+      }
+      else {
+        t = t + pre + "  RangeSet[" + i + "]: undefined\n";
+      }
+    }
+    if (isMissing()) {
+      return t + "  missing\n";
+    }
+    else {
+      return t + valuesString();
+    }
+  }
+
+  /**
+   * Gets the number of components in the "flat" range.
+   *
+   * @return                    The number of components in the "flat" range.
+   */
+  public int getRangeDimension() {
+     return TupleDimension;
+  }
+
+  public boolean equals(Object obj) {
+    if (obj == null || !(obj instanceof FlatField)) {
+      return false;
+    }
+
+    FlatField fld = (FlatField )obj;
+
+    if (RangeMode == null || fld.RangeMode == null) {
+      if (RangeMode != null || fld.RangeMode != null) {
+        return false;
+      }
+    } else if (RangeMode.length != fld.RangeMode.length) {
+      return false;
+    } else {
+      for (int i = 0; i < RangeMode.length; i++) {
+        if (RangeMode[i] != fld.RangeMode[i]) {
+          return false;
+        }
+      }
+    }
+
+    if (RangeSet == null || fld.RangeSet == null) {
+      if (RangeSet != null || fld.RangeSet != null) {
+        return false;
+      }
+    } else if (RangeSet.length != fld.RangeSet.length) {
+      return false;
+    } else {
+      for (int i = 0; i < RangeSet.length; i++) {
+        if (!RangeSet[i].equals(fld.RangeSet[i])) {
+          return false;
+        }
+      }
+    }
+
+    for (int i=0; i<TupleDimension; i++) {
+      switch (RangeMode[i]) {
+        case DOUBLE:
+          if (!Arrays.equals(DoubleRange[i], fld.DoubleRange[i])) {
+            return false;
+          }
+          break;
+        case FLOAT:
+          if (!Arrays.equals(FloatRange[i], fld.FloatRange[i])) {
+            return false;
+          }
+          break;
+        case BYTE:
+          if (!Arrays.equals(ByteRange[i], fld.ByteRange[i])) {
+            return false;
+          }
+          break;
+        case SHORT:
+          if (!Arrays.equals(ShortRange[i], fld.ShortRange[i])) {
+            return false;
+          }
+          break;
+        case INT:
+          if (!Arrays.equals(IntRange[i], fld.IntRange[i])) {
+            return false;
+          }
+          break;
+        default:
+          return false;
+      }
+    }
+
+    return super.equals(obj);
+  }
+
+  /** construct a FlatField of given type; used for testing */
+  public static FlatField makeField(FunctionType type, int length, boolean irregular)
+         throws VisADException, RemoteException {
+    double first = 0.0;
+    double last = length - 1.0;
+    double step = 1.0;
+    double half = 0.5 * last;
+    RealTupleType dtype = type.getDomain();
+    RealTupleType rtype = type.getFlatRange();
+    int domain_dim = dtype.getDimension();
+    int range_dim = rtype.getDimension();
+    SampledSet domain_set = null;
+    int dsize = 0;
+    Random random = new Random();
+    if (irregular) {
+      if (domain_dim == 1) {
+        dsize = length;
+        float[][] samples = new float[1][dsize];
+        for (int i=0; i<dsize; i++) {
+          samples[0][i] = (float) (last * random.nextFloat());
+        }
+        domain_set = new Irregular1DSet(dtype, samples);
+      }
+      else if (domain_dim == 2) {
+        dsize = length * length;
+        float[][] samples = new float[2][dsize];
+        for (int i=0; i<dsize; i++) {
+          samples[0][i] = (float) (last * random.nextFloat());
+          samples[1][i] = (float) (last * random.nextFloat());
+        }
+        domain_set = new Irregular2DSet(dtype, samples);
+      }
+      else if (domain_dim == 3) {
+        dsize = length * length * length;
+        float[][] samples = new float[3][dsize];
+
+        // random Irregular3DSet
+        for (int i=0; i<dsize; i++) {
+          samples[0][i] = (float) (last * random.nextFloat());
+          samples[1][i] = (float) (last * random.nextFloat());
+          samples[2][i] = (float) (last * random.nextFloat());
+        }
+/*
+        // jittered linear Irregular3DSet
+        Linear3DSet square_set = new Linear3DSet(dtype, first, last, length,
+                                                 first, last, length,
+                                                 first, last, length);
+        samples = square_set.getSamples();
+        for (int i=0; i<dsize; i++) {
+          samples[0][i] += 0.05 * random.nextFloat();
+          samples[1][i] += 0.05 * random.nextFloat();
+          samples[2][i] += 0.05 * random.nextFloat();
+        }
+*/
+
+        domain_set = new Irregular3DSet(dtype, samples);
+      }
+      else {
+        throw new FieldException("FlatField.makeField: bad domain dimension");
+      }
+    }
+    else { // if (!irregular)
+      if (domain_dim == 1) {
+        domain_set = new Linear1DSet(dtype, first, last, length);
+        dsize = length;
+      }
+      else if (domain_dim == 2) {
+        domain_set = new Linear2DSet(dtype, first, last, length,
+                                            first, last, length);
+        dsize = length * length;
+      }
+      else if (domain_dim == 3) {
+        domain_set = new Linear3DSet(dtype, first, last, length,
+                                            first, last, length,
+                                            first, last, length);
+        dsize = length * length * length;
+      }
+      else {
+        throw new FieldException("FlatField.makeField: bad domain dimension");
+      }
+    }
+    FlatField image = new FlatField(type, domain_set);
+    fillField(image, step, half);
+    return image;
+  }
+
+  public static void fillField(FlatField image, double step, double half)
+         throws VisADException, RemoteException {
+    Random random = new Random();
+    FunctionType type = (FunctionType) image.getType();
+    RealTupleType dtype = type.getDomain();
+    RealTupleType rtype = type.getFlatRange();
+    int domain_dim = dtype.getDimension();
+    int range_dim = rtype.getDimension();
+    SampledSet domain_set = (SampledSet) image.getDomainSet();
+    int dsize = domain_set.getLength();
+
+    double[][] data = new double[range_dim][dsize];
+    float[][] samples = domain_set.getSamples();
+    for (int k=0; k<range_dim; k++) {
+      if (domain_dim == 1) {
+        for (int i=0; i<dsize; i++) {
+          float x = samples[0][i];
+          if (k == 0) {
+            data[k][i] = (float) Math.abs(step * (x - half));
+          }
+          else if (k == 1) {
+            data[k][i] = x;
+          }
+          else {
+            data[k][i] = random.nextDouble();
+          }
+        }
+      }
+      else if (domain_dim == 2) {
+        for (int i=0; i<dsize; i++) {
+          float x = samples[0][i];
+          float y = samples[1][i];
+          if (k == 0) {
+            data[k][i] = (float) (step * Math.sqrt(
+              (x - half) * (x - half) +
+              (y - half) * (y - half)));
+          }
+          else if (k == 1) {
+            data[k][i] = x;
+          }
+          else if (k == 2) {
+            data[k][i] = y;
+          }
+          else {
+            data[k][i] = random.nextDouble();
+          }
+        }
+      }
+      else if (domain_dim == 3) {
+        for (int i=0; i<dsize; i++) {
+          float x = samples[0][i];
+          float y = samples[1][i];
+          float z = samples[2][i];
+          if (k == 0) {
+            data[k][i] = (float) (step * Math.sqrt(
+              (x - half) * (x - half) +
+              (y - half) * (y - half) +
+              (z - half) * (z - half)));
+          }
+          else if (k == 1) {
+            data[k][i] = x;
+          }
+          else if (k == 2) {
+            data[k][i] = y;
+          }
+          else if (k == 3) {
+            data[k][i] = z;
+          }
+          else {
+            data[k][i] = random.nextDouble();
+          }
+        }
+      }
+    }
+    image.setSamples(data);
+  }
+
+
+  /** construct a FlatField with a 2-D domain and a 1-D range;
+      used for testing */
+  public static FlatField makeField1(FunctionType type,
+                              double first1, double last1, int length1,
+                              double first2, double last2, int length2)
+          throws VisADException, RemoteException {
+
+    double step1 = (last1 - first1) / (length1 - 1);
+    double step2 = (last2 - first2) / (length2 - 1);
+
+    Linear2DSet imageset =
+      new Linear2DSet(type.getDomain(), first1, last1, length1,
+                                        first2, last2, length2);
+
+    FlatField image = new FlatField(type, imageset);
+
+    double[][] data = new double[1][length1 * length2];
+    for (int i=0; i<length1; i++) {
+      for (int j=0; j<length2; j++) {
+        data[0][i + length1 * j] =
+          (first1 + step1 * i) + (first2 + step2 * j);
+      }
+    }
+    image.setSamples(data);
+    return image;
+  }
+
+  /** construct a FlatField with a 2-D domain and a 2-D range;
+      used for testing */
+  public static FlatField makeField2(FunctionType type,
+                              double first1, double last1, int length1,
+                              double first2, double last2, int length2)
+          throws VisADException, RemoteException {
+
+    double step1 = (last1 - first1) / (length1 - 1);
+    double step2 = (last2 - first2) / (length2 - 1);
+
+    Linear2DSet imageset =
+      new Linear2DSet(type.getDomain(), first1, last1, length1,
+                                        first2, last2, length2);
+
+    FlatField image = new FlatField(type, imageset);
+
+    double[][] data = new double[2][length1 * length2];
+    for (int i=0; i<length1; i++) {
+      for (int j=0; j<length2; j++) {
+        data[0][i + length1 * j] = first1 + step1 * i;
+        data[1][i + length1 * j] = first2 + step2 * j;
+      }
+    }
+    image.setSamples(data);
+    return image;
+  }
+
+  /** construct a FlatField with a 2-D domain and a 2-D range
+      and random values; used for testing */
+  static FlatField makeRandomField2(FunctionType type,
+                                    double first1, double last1, int length1,
+                                    double first2, double last2, int length2)
+          throws VisADException, RemoteException {
+
+    double step1 = (last1 - first1) / (length1 - 1);
+    double step2 = (last2 - first2) / (length2 - 1);
+
+    Linear2DSet imageset =
+      new Linear2DSet(type.getDomain(), first1, last1, length1,
+                                        first2, last2, length2);
+
+    FlatField image = new FlatField(type, imageset);
+
+    Random random = new Random();
+    double[][] data = new double[2][length1 * length2];
+    for (int i=0; i<length1; i++) {
+      for (int j=0; j<length2; j++) {
+        data[0][i + length1 * j] = random.nextDouble();
+        data[1][i + length1 * j] = random.nextDouble();
+      }
+    }
+    image.setSamples(data);
+    return image;
+  }
+
+  /** run 'java visad.FlatField' to test the FlatField class */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    byte b = 10;
+    Real w = new Real(b);
+
+    RealType X = RealType.getRealType("X");
+    RealType Y = RealType.getRealType("Y");
+    RealType Z = RealType.getRealType("Z");
+
+    RealType A = RealType.getRealType("A");
+    RealType B = RealType.getRealType("B");
+
+    RealType[] domain2d = {X, Y};
+    RealTupleType Domain2d = new RealTupleType(domain2d, null, null);
+    Integer2DSet Domain2dSet = new Integer2DSet(Domain2d, 4, 4);
+    Domain2d.setDefaultSet(Domain2dSet);
+
+    RealType[] range2d = {A, B};
+    RealTupleType Range2d = new RealTupleType(range2d);
+
+    FunctionType Field2d1 = new FunctionType(Domain2d, A);
+    FunctionType Field2d2 = new FunctionType(Domain2d, Range2d);
+
+    double first11 = 0.0;
+    double last11 = 3.0;
+    int length11 = 4;
+    double first12 = 0.0;
+    double last12 = 3.0;
+    int length12 = 4;
+    FlatField image1 = makeField1(Field2d1, first11, last11, length11,
+                                            first12, last12, length12);
+    FlatField image3 = makeField2(Field2d2, first11, last11, length11,
+                                            first12, last12, length12);
+    Real[] reals = {new Real(X ,1.5), new Real(Y, 2.5)};
+    RealTuple val = new RealTuple(reals);
+
+    double first21 = 0.0;
+    double last21 = 3.0;
+    int length21 = 7;
+    double first22 = 0.0;
+    double last22 = 3.0;
+    int length22 = 7;
+    FlatField image2 = makeField1(Field2d1, first21, last21, length21,
+                                            first22, last22, length22);
+    FlatField image4 = makeField2(Field2d2, first21, last21, length21,
+                                            first22, last22, length22);
+
+    System.out.println("image1 = " + image1);
+    System.out.println("image2 = " + image2);
+    System.out.println("image3 = " + image3);
+    System.out.println("image4 = " + image4);
+
+    // do some computations in NEAREST_NEIGHBOR sampling mode
+    System.out.println("sampling mode is NEAREST_NEIGHBOR");
+    System.out.println("image3 + image4 = " + image3.add(image4));
+    System.out.println("image4 - image3 = " + image4.subtract(image3));
+    System.out.println("image3 * image4 = " + image3.multiply(image4));
+    System.out.println("image4 / image3 = " + image4.divide(image3));
+    System.out.println("sqrt(image3) = " + image3.sqrt());
+    System.out.println("val = " + val + " image1(val) = " +
+                       image1.evaluate(val));
+    System.out.println("val = " + val + " image3(val) = " +
+                       image3.evaluate(val) + "\n");
+    System.out.println("image3 + val = " + image3.add(val));
+    System.out.println("val - image3 = " + val.subtract(image3));
+    System.out.println("image3 * val = " + image3.multiply(val));
+    System.out.println("val / image3 = " + val.divide(image3));
+
+    // now do some computations in WEIGHTED_AVERAGE sampling mode
+    System.out.println("Field.Mode is WEIGHTED_AVERAGE");
+    System.out.println("image3 + image4 = " +
+                       image3.add(image4, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("image4 - image3 = " +
+                       image4.subtract(image3, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("image3 * image4 = " +
+                       image3.multiply(image4, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("image4 / image3 = " +
+                       image4.divide(image3, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("val = " + val + " image1(val) = " +
+                       image1.evaluate(val, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("val = " + val + " image3(val) = " +
+                       image3.evaluate(val, WEIGHTED_AVERAGE, INDEPENDENT) + "\n");
+    System.out.println("image3 + val = " +
+                       image3.add(val, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("val - image3 = " +
+                       val.subtract(image3, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("image3 * val = " +
+                       image3.multiply(val, WEIGHTED_AVERAGE, INDEPENDENT));
+    System.out.println("val / image3 = " +
+                       val.divide(image3, WEIGHTED_AVERAGE, INDEPENDENT));
+
+    // do some more computations in NEAREST_NEIGHBOR sampling mode
+    System.out.println("sampling mode is NEAREST_NEIGHBOR");
+
+    System.out.println("image1 + w = " + image1.add(w));
+    System.out.println("image1 - w = " + image1.subtract(w));
+    System.out.println("image1 * w = " + image1.multiply(w));
+    System.out.println("image1 / w = " + image1.divide(w));
+
+    System.out.println("w + image2 = " + w.add(image2));
+    System.out.println("w - image2 = " + w.subtract(image2));
+    System.out.println("w * image2 = " + w.multiply(image2));
+    System.out.println("w / image2 = " + w.divide(image2));
+
+    // test DateTime printing
+    RealType[] range2t = {A, RealType.Time};
+    RealTupleType Range2t = new RealTupleType(range2t);
+    FunctionType Field2t2 = new FunctionType(Domain2d, Range2t);
+    FlatField imaget = makeField2(Field2t2, first11, last11, length11,
+                                            first12, last12, length12);
+    System.out.println("imaget = " + imaget);
+
+  }
+
+/* Here's the output:
+
+iris 251% java visad.FlatField
+image1 = FlatField
+    FunctionType (Real): (X, Y) -> A
+0, 1, 2, 3
+
+1, 2, 3, 4
+
+2, 3, 4, 5
+
+3, 4, 5, 6
+
+
+image2 = FlatField
+    FunctionType (Real): (X, Y) -> A
+0, 0.5, 1, 1.5, 2, 2.5, 3
+
+0.5, 1, 1.5, 2, 2.5, 3, 3.5
+
+1, 1.5, 2, 2.5, 3, 3.5, 4
+
+. . .
+
+imaget = FlatField
+    FunctionType (Real): (X, Y) -> (A, Time)
+(0.0, 1970-01-01 00:00:00.000Z), (1.0, 1970-01-01 00:00:00.000Z), (2.0, 1970-01-01 00:00:00.000Z), (3.0, 1970-01-01 00:00:00.000Z)
+
+(0.0, 1970-01-01 00:00:01.000Z), (1.0, 1970-01-01 00:00:01.000Z), (2.0, 1970-01-01 00:00:01.000Z), (3.0, 1970-01-01 00:00:01.000Z)
+
+(0.0, 1970-01-01 00:00:02.000Z), (1.0, 1970-01-01 00:00:02.000Z), (2.0, 1970-01-01 00:00:02.000Z), (3.0, 1970-01-01 00:00:02.000Z)
+
+(0.0, 1970-01-01 00:00:03.000Z), (1.0, 1970-01-01 00:00:03.000Z), (2.0, 1970-01-01 00:00:03.000Z), (3.0, 1970-01-01 00:00:03.000Z)
+
+*/
+
+
+
+}
+
diff --git a/visad/FlatFieldIface.java b/visad/FlatFieldIface.java
new file mode 100644
index 0000000..6bd508d
--- /dev/null
+++ b/visad/FlatFieldIface.java
@@ -0,0 +1,163 @@
+//
+// FlatFieldIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   FlatField is the VisAD class for finite samplings of functions whose
+   range type and range coordinate systems are simple enough to allow
+   efficient representation.  The DomainSet, DomainCoordinateSystem,
+   RangeSet, RangeCoordinateSystem and RangeCoordinateSystems variables
+   of FlatField are immutable.<P>
+
+   A FlatField range type may be either a RealType (for a function with
+   range = R), a RealTupleType (for a function with range = R^n for n > 0),
+   or a TupleType of RealType-s and RealTupleType-s..<P>
+
+   VisAD avoids invoking methods once per datum through the use of
+   FlatField's.  These are logically Field's of Tuple's of RealType's
+   and RealTupleType's.  Internally FlatField's are stored as arrays of
+   numerical values, rather than the arrays of data objects stored in
+   Field's.  Many of the methods in the FlatField class and in other
+   classes (e.g., CoordinateTransform, Set, Unit) process data in the
+   form double[Dimension][Length] where Length is the number of samples
+   in a Field and Dimension is the number of Tuple elements in the
+   Field range values.  Note that the order of the Length and Dimension
+   indices are reversed as array indices.  This allows efficient
+   processing of long columns of Field value components.  For example,
+   if Latitude is one component of Field values, then any computation
+   involving Latitude can be applied in a tight loop to all Latitude's
+   in the Field.<P>
+
+   FlatField's support range types more general than RealTuple's.  To
+   understand the motive, consider a set of observations that include
+   Latitude, Longitude, Altitude, Pressure, Temperature, etc.  We can
+   organize these as a Field whose range values have the Tuple type:<P>
+ <PRE>
+
+     (Latitude, Longitude, Altitude, Pressure, Temperature, ...)
+
+</PRE>
+   However, in order to declare that (Latitude, Longitude, Altitude)
+   is a coordinate system with coordinate transform functions to other
+   spatial coordinate systems, we need to organize:<P>
+<PRE>
+
+     (Latitude, Longitude, Altitude)
+
+</PRE>
+   as a RealTupleType.  Hence the range type of the Field of observations
+   must be:<P>
+<PRE>
+
+     ((Latitude, Longitude, Altitude), Pressure, Temperature, ...)
+
+</PRE>
+   which is not a RealTupleType (since one of its components is a
+   RealTupleType).  In order to process such data efficiently, FlatField's
+   must support range types that are Tuple's of RealType's and
+   RealTupleType's.<P>
+*/
+public interface FlatFieldIface extends Field {
+
+  /**
+   * Returns the sampling set of each flat component.
+   * @return		The sampling set of each component in the flat range.
+   */
+  Set[] getRangeSets()
+    throws RemoteException, VisADException;
+
+  /** return array of ErrorEstimates associated with each
+      RealType component of range; each ErrorEstimate is a
+      mean error for all samples of a range RealType
+      component */
+  ErrorEstimate[] getRangeErrors()
+    throws RemoteException, VisADException;
+
+  /** set ErrorEstimates associated with each RealType
+      component of range */
+  void setRangeErrors(ErrorEstimate[] errors)
+    throws RemoteException, VisADException;
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      double[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet; copy array if copy flag is true */
+  void setSamples(double[][] range, boolean copy)
+    throws RemoteException, VisADException;
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet; copy array if copy flag is true */
+  void setSamples(float[][] range, boolean copy)
+    throws RemoteException, VisADException;
+
+  /** set the range values of the function including ErrorEstimate-s;
+      the order of range values must be the same as the order of
+      domain indices in the DomainSet */
+  void setSamples(double[][] range, ErrorEstimate[] errors, boolean copy)
+    throws RemoteException, VisADException;
+
+  void setSamples(int start, double[][] range)
+    throws RemoteException, VisADException;
+
+  /** set the range values of the function including ErrorEstimate-s;
+      the order of range values must be the same as the order of
+      domain indices in the DomainSet */
+  void setSamples(float[][] range, ErrorEstimate[] errors, boolean copy)
+    throws RemoteException, VisADException;
+
+  byte[][] grabBytes()
+    throws RemoteException, VisADException;
+
+
+  /** get values for 'Flat' components in default range Unit-s */
+  /*- TDR June 1998  */
+  double[] getValues(int s_index)
+    throws RemoteException, VisADException;
+
+  /** mark this FlatField as non-missing */
+  void clearMissing()
+    throws RemoteException, VisADException;
+
+  /** convert this FlatField to a (non-Flat) FieldImpl */
+  Field convertToField()
+    throws RemoteException, VisADException;
+
+  /**
+   * Gets the number of components in the "flat" range.
+   *
+   * @return The number of components in the "flat" range.
+   */
+  int getRangeDimension()
+    throws RemoteException, VisADException;
+}
+
diff --git a/visad/FloatSet.java b/visad/FloatSet.java
new file mode 100644
index 0000000..eeedffa
--- /dev/null
+++ b/visad/FloatSet.java
@@ -0,0 +1,104 @@
+//
+// FloatSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   FloatSet represents the finite (but large) set of samples of
+   R^dimension made by vectors of IEEE single precision floating
+   point numbers.  FloatSet objects are immutable.<P>
+
+   FloatSet cannot be used for the domain sampling of a Field.<P>
+*/
+public class FloatSet extends SimpleSet {
+
+  /** construct a FloatSet object with null CoordinateSystem and Units */
+  public FloatSet(MathType type) throws VisADException {
+    this(type, null, null);
+  }
+
+  /** the set of values representable by N floats;
+      type must be a RealType, a RealTupleType or a SetType;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null;
+      a FloatSet may not be used as a Field domain */
+  public FloatSet(MathType type, CoordinateSystem coord_sys, Unit[] units)
+         throws VisADException {
+    super(type, coord_sys, units, null); // no ErrorEstimate for FloatSet
+  }
+
+  public float[][] indexToValue(int[] index) throws VisADException {
+    throw new SetException("FloatSet.indexToValue");
+  }
+
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    throw new SetException("FloatSet.valueToIndex");
+  }
+
+  public void valueToInterp(float[][] value, int[][] indices, float weights[][])
+              throws VisADException {
+    throw new SetException("FloatSet.valueToInterp");
+  }
+
+  public int getLength() throws VisADException {
+    throw new SetException("FloatSet.getLength");
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof FloatSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    return (DomainDimension == ((FloatSet) set).getDimension());
+  }
+
+  public boolean isMissing() {
+    return false;
+  }
+  
+  /**
+   * Clones this instance.
+   *
+   * @return                      A clone of this instance.
+   */
+  public final Object clone() {
+      /*
+       * Steve Emmerson believes that this implementation should return
+       * "this" to reduce the memory-footprint but Bill believes that doing so
+       * would be counter-intuitive and might harm applications.
+       */
+      return super.clone();
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new FloatSet(type, DomainCoordinateSystem, SetUnits);
+  }
+
+  public String longString(String pre) throws VisADException {
+    return pre + "FloatSet: Dimension = " + DomainDimension + "\n";
+  }
+
+}
+
diff --git a/visad/Flow1Control.java b/visad/Flow1Control.java
new file mode 100644
index 0000000..3a8e6b7
--- /dev/null
+++ b/visad/Flow1Control.java
@@ -0,0 +1,39 @@
+//
+// Flow1Control.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Flow1Control is the VisAD class for controlling Flow1 display scalars.<P>
+*/
+public class Flow1Control extends FlowControl {
+
+  public Flow1Control(DisplayImpl d) {
+    super(d);
+  }
+
+}
+
diff --git a/visad/Flow2Control.java b/visad/Flow2Control.java
new file mode 100644
index 0000000..340dc48
--- /dev/null
+++ b/visad/Flow2Control.java
@@ -0,0 +1,39 @@
+//
+// Flow2Control.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Flow2Control is the VisAD class for controlling Flow2 display scalars.<P>
+*/
+public class Flow2Control extends FlowControl {
+
+  public Flow2Control(DisplayImpl d) {
+    super(d);
+  }
+
+}
+
diff --git a/visad/FlowControl.java b/visad/FlowControl.java
new file mode 100644
index 0000000..8d7df27
--- /dev/null
+++ b/visad/FlowControl.java
@@ -0,0 +1,708 @@
+//
+// FlowControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.StringTokenizer;
+
+import visad.browser.Convert;
+import visad.util.Util;
+
+/**
+   FlowControl is the VisAD abstract super-class for controlling
+   Flow display scalars.<P>
+*/
+public abstract class FlowControl extends Control {
+
+  float flowScale;
+
+  // DRM add 09-Sep-1999
+  /** Northern Hemisphere orientation for wind barbs */
+  public static final int NH_ORIENTATION = 0;
+  /** Southern Hemisphere orientation for wind barbs */
+  public static final int SH_ORIENTATION = 1;
+  int barbOrientation;
+  boolean adjustFlowToEarth = true;  
+
+  boolean HorizontalVectorSlice;
+  boolean VerticalVectorSlice;
+  boolean HorizontalStreamSlice;
+  boolean VerticalStreamSlice;
+  boolean[] TrajectorySet;
+
+  double HorizontalVectorSliceHeight;
+  double HorizontalStreamSliceHeight;
+
+  private boolean autoScale = false;
+  private ProjectionControlListener pcl = null;
+
+  /** Streamline flags
+  -------------------------------*/
+  boolean streamlinesEnabled;
+  float   streamlineDensity;
+  float   arrowScale;
+  float   stepFactor;
+  float   packingFactor;
+  float   cntrWeight;
+  int     n_pass;
+  float   reduction;
+
+  // WLH  need Vertical*Slice location parameters
+
+  /**
+   * Create a FlowControl
+   * @param  d  DisplayImpl that this is associated with.
+   */
+  public FlowControl(DisplayImpl d) {
+    super(d);
+    flowScale = 0.02f;
+    HorizontalVectorSlice = false;
+    VerticalVectorSlice = false;
+    HorizontalStreamSlice = false;
+    VerticalStreamSlice = false;
+    barbOrientation = SH_ORIENTATION;    // DRM 9-Sept-1999
+    TrajectorySet = null;
+
+    HorizontalVectorSliceHeight = 0.0;
+    HorizontalStreamSliceHeight = 0.0;
+
+    streamlinesEnabled = false;
+    streamlineDensity  = 1f;
+    arrowScale         = 1f;
+    stepFactor         = 2f;
+    packingFactor      = 1f;
+    cntrWeight         = 3f;
+    n_pass             = 0;
+    reduction          = 1f;
+    adjustFlowToEarth  = true;  
+    autoScale          = false;
+  }
+
+  /** 
+   * Set scale length for flow vectors (default is 0.02f) 
+   * @param scale  new scale
+   */
+  public void setFlowScale(float scale)
+         throws VisADException, RemoteException {
+    flowScale = scale;
+    changeControl(true);
+  }
+
+  /** 
+   * Get scale length for flow vectors 
+   * @return  scale length for flow vectors
+   */
+  public float getFlowScale() {
+    return flowScale;
+  }
+
+  /**
+   * Set barb orientation for wind barbs (default is southern hemisphere)
+   *
+   * @param  orientation   wind barb orientation
+   *                       (NH_ORIENTATION or SH_ORIENTATION);
+   */
+  public void setBarbOrientation(int orientation)
+         throws VisADException, RemoteException
+  {
+    // make sure it is one or the other
+    if (orientation == SH_ORIENTATION || orientation == NH_ORIENTATION)
+       barbOrientation = orientation;
+    else
+      throw new VisADException( "Invalid orientation value: " + orientation);
+    changeControl(true);
+  }
+
+  /**
+   * Get barb orientation for wind barbs
+   *
+   * @return orientation (false = northern hemisphere)
+   */
+  public int getBarbOrientation() {
+    return barbOrientation;
+  }
+
+  /**
+   * Get whether values should be adjusted to the earth 
+   *
+   * @param  adjust   true to adjust
+   * @throws VisADException  problem setting the value
+   * @throws RemoteException  problem setting the value on remote system
+   */
+  public void setAdjustFlowToEarth(boolean adjust)
+         throws VisADException, RemoteException
+  {
+    adjustFlowToEarth = adjust;
+    changeControl(true);
+  }
+
+  /**
+   * Get barb orientation for wind barbs
+   *
+   * @return orientation (false = northern hemisphere)
+   */
+  public boolean getAdjustFlowToEarth() {
+    return adjustFlowToEarth;
+  }
+
+  /**
+   * Enable/disable showing vectors as streamlines
+   *
+   * @param flag  true to display as streamlines
+   * @throws VisADException  problem enabling the streamlines
+   * @throws RemoteException  problem enabling the streamlines on remote system
+   */
+  public void enableStreamlines(boolean flag)
+         throws VisADException, RemoteException {
+    streamlinesEnabled = flag;
+    changeControl(true);
+  }
+
+  /**
+   * Set the streamline density
+   * @param density the density value
+   * @throws VisADException  problem setting the density
+   * @throws RemoteException  problem setting the density on remote system
+   */
+  public void setStreamlineDensity(float density)
+         throws VisADException, RemoteException {
+    streamlineDensity = density;
+    changeControl(true);
+  }
+
+  /**
+   * Set the streamline arrow size
+   * @param arrowScale the streamline arrow size
+   * @throws VisADException  problem setting the arrow scale
+   * @throws RemoteException  problem setting the arrow scale on remote system
+   */
+  public void setArrowScale(float arrowScale)
+         throws VisADException, RemoteException {
+    this.arrowScale = arrowScale;
+    changeControl(true);
+  }
+
+  /**
+   * Set the streamline step factor
+   * @param stepFactor the streamline step factor
+   * @throws VisADException  problem setting the step factor
+   * @throws RemoteException  problem setting the step factor on remote system
+   */
+  public void setStepFactor(float stepFactor)
+         throws VisADException, RemoteException {
+    this.stepFactor = stepFactor;
+    changeControl(true);
+  }
+
+  /**
+   * Set the streamline packing
+   * @param packing the streamline packing
+   * @throws VisADException  problem setting the packing
+   * @throws RemoteException  problem setting the packing on remote system
+   */
+  public void setStreamlinePacking(float packing) 
+         throws VisADException, RemoteException {
+    this.packingFactor = packing;
+    changeControl(true);
+  }
+
+  /**
+   * Set the streamline smoothing
+   * @param cntrWeight  the center weight
+   * @param n_pass  number of smoothing passes
+   * @throws VisADException  problem setting the smoothing
+   * @throws RemoteException  problem setting the smoothing on remote system
+   */
+  public void setStreamlineSmoothing(float cntrWeight, int n_pass)
+         throws VisADException, RemoteException {
+    this.cntrWeight = cntrWeight;
+    this.n_pass = n_pass;
+    changeControl(true);
+  }
+ 
+  /**
+   * Set the streamline reduction
+   * @param reduction the streamline reduction
+   * @throws VisADException  problem setting the reduction
+   * @throws RemoteException  problem setting the reduction on remote system
+   */
+  public void setStreamlineReduction(float reduction)
+         throws VisADException, RemoteException {
+    this.reduction = reduction;
+    changeControl(true);
+  }
+
+  /**
+   * Get the status of streamlines
+   * @return  true if streamlines are enabled.
+   */
+  public boolean streamlinesEnabled() {
+    return streamlinesEnabled;
+  }
+
+  /**
+   * Get the streamline density factor.
+   * @return  the streamline density factor.
+   */
+  public float getStreamlineDensity() {
+    return streamlineDensity;
+  }
+
+  /**
+   * Get the streamline arrow scale
+   * @return  the streamline arrow scale
+   */
+  public float getArrowScale() {
+    return arrowScale;
+  }
+
+  /**
+   * Get the streamline step factor
+   * @return  the streamline step factor
+   */
+  public float getStepFactor() {
+    return stepFactor;
+  }
+
+  /**
+   * Get the streamline packing value
+   * @return  the streamline packing value
+   */
+  public float getStreamlinePacking() {
+    return packingFactor;
+  }
+
+  /**
+   * Get the streamline smoothing value
+   * @return  the streamline smoothing value
+   */
+  public float[] getStreamlineSmoothing() {
+    return new float[] {cntrWeight, (float) n_pass};
+  }
+
+  /**
+   * Get the streamline reduction value
+   * @return  the streamline reduction value
+   */
+  public float getStreamlineReduction() {
+    return reduction;
+  }
+
+  /** 
+   * Get a string that can be used to reconstruct this control later 
+   * @return a string representation of this control
+   */
+  public String getSaveString() {
+    return "" + 
+           getFlowScale() + " " + 
+           getBarbOrientation() + " " +
+           streamlinesEnabled() + " " +
+           getStreamlineDensity() + " " +
+           getArrowScale() + " " +
+           getStepFactor() + " " +
+           getStreamlinePacking() + " " +
+           getStreamlineSmoothing()[0] + " " +
+           getStreamlineSmoothing()[1] + " " +
+           getStreamlineReduction() + " " +
+           getAdjustFlowToEarth() + " " +
+           getAutoScale();
+  }
+
+  /** 
+   * Reconstruct this control using the specified save string 
+   */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save);
+    if (st.countTokens() < 2) throw new VisADException("Invalid save string");
+    float scale = Convert.getFloat(st.nextToken());
+    int orientation = Convert.getInt(st.nextToken());
+    boolean es = st.hasMoreTokens() ? Convert.getBoolean(st.nextToken()) : streamlinesEnabled();
+    float sd = st.hasMoreTokens() ? Convert.getFloat(st.nextToken()) : getStreamlineDensity();
+    float as = st.hasMoreTokens() ? Convert.getFloat(st.nextToken()) : getArrowScale();
+    float sf = st.hasMoreTokens() ? Convert.getFloat(st.nextToken()) : getStepFactor();
+    float sp = st.hasMoreTokens() ? Convert.getFloat(st.nextToken()) : getStreamlinePacking();
+    float ssc = st.hasMoreTokens() ? Convert.getFloat(st.nextToken()) : getStreamlineSmoothing()[0];
+    float ssn = st.hasMoreTokens() ? Convert.getFloat(st.nextToken()) : getStreamlineSmoothing()[1];
+    float sr = st.hasMoreTokens() ? Convert.getFloat(st.nextToken()) : getStreamlineReduction();
+    boolean af = st.hasMoreTokens() ? Convert.getBoolean(st.nextToken()) : getAdjustFlowToEarth();
+    boolean asc = st.hasMoreTokens() ? Convert.getBoolean(st.nextToken()) : getAutoScale();
+
+    flowScale = scale;
+    barbOrientation = orientation;
+    streamlinesEnabled = es;
+    streamlineDensity = sd;
+    arrowScale = as;
+    stepFactor = sf;
+    packingFactor = sp;
+    cntrWeight = ssc;
+    n_pass= (int) ssn;
+    reduction = sr;
+    adjustFlowToEarth = af;
+    autoScale = asc;
+    changeControl(true);
+  }
+
+  /** 
+   * Copy the state of a remote control to this control 
+   */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof FlowControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    FlowControl fc = (FlowControl )rmt;
+
+    boolean changed = false;
+
+    if (!Util.isApproximatelyEqual(flowScale, fc.flowScale)) {
+      changed = true;
+      flowScale = fc.flowScale;
+    }
+
+    if (barbOrientation != fc.barbOrientation) {
+      changed = true;
+      barbOrientation = fc.barbOrientation;
+    }
+    if (HorizontalVectorSlice != fc.HorizontalVectorSlice) {
+      changed = true;
+      HorizontalVectorSlice = fc.HorizontalVectorSlice;
+    }
+    if (VerticalVectorSlice != fc.VerticalVectorSlice) {
+      changed = true;
+      VerticalVectorSlice = fc.VerticalVectorSlice;
+    }
+    if (HorizontalStreamSlice != fc.HorizontalStreamSlice) {
+      changed = true;
+      HorizontalStreamSlice = fc.HorizontalStreamSlice;
+    }
+    if (VerticalStreamSlice != fc.VerticalStreamSlice) {
+      changed = true;
+      VerticalStreamSlice = fc.VerticalStreamSlice;
+    }
+    if (TrajectorySet == null) {
+      if (fc.TrajectorySet != null) {
+        changed = true;
+        TrajectorySet = fc.TrajectorySet;
+      }
+    } else if (fc.TrajectorySet == null) {
+      changed = true;
+      TrajectorySet = null;
+    } else if (TrajectorySet.length != fc.TrajectorySet.length) {
+      changed = true;
+      TrajectorySet = fc.TrajectorySet;
+    } else {
+      for (int i = 0; i < TrajectorySet.length; i++) {
+        if (TrajectorySet[i] != fc.TrajectorySet[i]) {
+          changed = true;
+          TrajectorySet[i] = fc.TrajectorySet[i];
+        }
+      }
+    }
+
+    if (!Util.isApproximatelyEqual(HorizontalVectorSliceHeight,
+                                   fc.HorizontalVectorSliceHeight))
+    {
+      changed = true;
+      HorizontalVectorSliceHeight = fc.HorizontalVectorSliceHeight;
+    }
+    if (!Util.isApproximatelyEqual(HorizontalStreamSliceHeight,
+                                   fc.HorizontalStreamSliceHeight))
+    {
+      changed = true;
+      HorizontalStreamSliceHeight = fc.HorizontalStreamSliceHeight;
+    }
+
+    if (streamlinesEnabled != fc.streamlinesEnabled) {
+      changed = true;
+      streamlinesEnabled = fc.streamlinesEnabled;
+    }
+
+    if (!Util.isApproximatelyEqual(streamlineDensity, fc.streamlineDensity)) {
+      changed = true;
+      streamlineDensity = fc.streamlineDensity;
+    }
+
+    if (!Util.isApproximatelyEqual(arrowScale, fc.arrowScale)) {
+      changed = true;
+      arrowScale = fc.arrowScale;
+    }
+
+    if (!Util.isApproximatelyEqual(stepFactor, fc.stepFactor)) {
+      changed = true;
+      stepFactor = fc.stepFactor;
+    }
+
+    if (!Util.isApproximatelyEqual(packingFactor, fc.packingFactor)) {
+      changed = true;
+      packingFactor = fc.packingFactor;
+    }
+
+    if (!Util.isApproximatelyEqual(cntrWeight, fc.cntrWeight)) {
+      changed = true;
+      cntrWeight = fc.cntrWeight;
+    }
+
+    if (!Util.isApproximatelyEqual(n_pass, fc.n_pass)) {
+      changed = true;
+      n_pass = fc.n_pass;
+    }
+
+    if (!Util.isApproximatelyEqual(reduction, fc.reduction)) {
+      changed = true;
+      reduction = fc.reduction;
+    }
+
+    if (autoScale != fc.autoScale) {
+      // changed = true;
+      setAutoScale(fc.autoScale);
+    }
+
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  /**
+   * Set whether the vector/barb size should scale with display zoom.
+   * @param  auto  true to enable autoscaling.
+   * @throws VisADException  problem setting the autoscaling
+   */
+  public void setAutoScale(boolean auto)
+         throws VisADException {
+    if (auto == autoScale) return;
+    DisplayImpl display = getDisplay();
+    DisplayRenderer dr = display.getDisplayRenderer();
+    MouseBehavior mouse = dr.getMouseBehavior();
+    ProjectionControl pc = display.getProjectionControl();
+    if (auto) {
+      pcl = new ProjectionControlListener(mouse, this, pc);
+      pc.addControlListener(pcl);
+    }
+    else {
+      pc.removeControlListener(pcl);
+    }
+    autoScale = auto;
+    try {
+      changeControl(true);
+    }
+    catch (RemoteException e) {
+    }
+  }
+
+  /**
+   * Get whether the vector/barb size should scale with display zoom.
+   * @return  true if autoscaling is enabled.
+   */
+  public boolean getAutoScale() {
+    return autoScale;
+  }
+
+
+  /**
+   * Null the control.  Override superclass to remove the autoscaling listener.
+   */
+  public void nullControl() {
+    try {
+      setAutoScale(false);
+    }
+    catch (VisADException e) {
+    }
+    super.nullControl();
+  }
+
+  /**
+   * See if this control equals another
+   * @param o  object in question
+   * @return true if they are equal.
+   */
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    FlowControl fc = (FlowControl )o;
+
+    if (!Util.isApproximatelyEqual(flowScale, fc.flowScale)) {
+      return false;
+    }
+
+    if (barbOrientation != fc.barbOrientation) {
+      return false;
+    }
+    if (HorizontalVectorSlice != fc.HorizontalVectorSlice) {
+      return false;
+    }
+    if (VerticalVectorSlice != fc.VerticalVectorSlice) {
+      return false;
+    }
+    if (HorizontalStreamSlice != fc.HorizontalStreamSlice) {
+      return false;
+    }
+    if (VerticalStreamSlice != fc.VerticalStreamSlice) {
+      return false;
+    }
+    if (TrajectorySet == null) {
+      if (fc.TrajectorySet != null) {
+        return false;
+      }
+    } else if (fc.TrajectorySet == null) {
+      return false;
+    } else if (TrajectorySet.length != fc.TrajectorySet.length) {
+      return false;
+    } else {
+      for (int i = 0; i < TrajectorySet.length; i++) {
+        if (TrajectorySet[i] != fc.TrajectorySet[i]) {
+          return false;
+        }
+      }
+    }
+
+    if (!Util.isApproximatelyEqual(HorizontalVectorSliceHeight,
+                                   fc.HorizontalVectorSliceHeight))
+    {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(HorizontalStreamSliceHeight,
+                                   fc.HorizontalStreamSliceHeight))
+    {
+      return false;
+    }
+
+    if (streamlinesEnabled != fc.streamlinesEnabled) {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(streamlineDensity, fc.streamlineDensity))
+    {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(arrowScale, fc.arrowScale))
+    {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(stepFactor, fc.stepFactor))
+    {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(packingFactor, fc.packingFactor))
+    {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(cntrWeight, fc.cntrWeight))
+    {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(n_pass, fc.n_pass))
+    {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(reduction, fc.reduction))
+    {
+      return false;
+    }
+    if (autoScale != fc.autoScale) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Clone this control.
+   * @return a clone of this
+   */
+  public Object clone()
+  {
+    FlowControl fc = (FlowControl )super.clone();
+    if (TrajectorySet != null) {
+      fc.TrajectorySet = (boolean[] )TrajectorySet.clone();
+    }
+
+    return fc;
+  }
+
+  /**
+   * A class for listening to changes in the control.
+   */
+  class ProjectionControlListener implements ControlListener {
+    private boolean pfirst = true;
+    private MouseBehavior mouse;
+    private ProjectionControl pcontrol;
+    private FlowControl flowControl;
+    private double base_scale = 1.0;
+    private float last_cscale = 1.0f;
+    private double base_size = 1.0;
+
+    ProjectionControlListener(MouseBehavior m, FlowControl s,
+                              ProjectionControl p) {
+      mouse = m;
+      flowControl = s;
+      pcontrol = p;
+    }
+
+    public void controlChanged(ControlEvent e)
+           throws VisADException, RemoteException {
+      double[] matrix = pcontrol.getMatrix();
+      double[] rot = new double[3];
+      double[] scale = new double[3];
+      double[] trans = new double[3];
+      mouse.instance_unmake_matrix(rot, scale, trans, matrix);
+
+      if (pfirst) {
+        pfirst = false;
+        base_scale = scale[2];
+        last_cscale = 1.0f;
+        base_size = flowControl.getFlowScale();
+      }
+      else {
+        float cscale = (float) (base_scale / scale[2]);
+        float ratio = cscale / last_cscale;
+        if (ratio < 0.95f || 1.05f < ratio) { // 5% change
+          last_cscale = cscale;
+          flowControl.setFlowScale((float) base_size * cscale);
+        }
+      }
+    }
+  }
+}
diff --git a/visad/FlowSphericalCoordinateSystem.java b/visad/FlowSphericalCoordinateSystem.java
new file mode 100644
index 0000000..3a0752d
--- /dev/null
+++ b/visad/FlowSphericalCoordinateSystem.java
@@ -0,0 +1,154 @@
+//
+// FlowSphericalCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   FlowSphericalCoordinateSystem is the VisAD CoordinateSystem class
+   for (Elevation, Azimuth, Radial) with a Cartesian Reference,
+   with Elevation and Azimuth in degrees and Radial in meters
+   per second.  Note Elevation and Azimuth are direction that
+   wind is from.<P>
+*/
+public class FlowSphericalCoordinateSystem extends CoordinateSystem {
+
+  private static Unit[] coordinate_system_units =
+    {CommonUnit.degree, CommonUnit.degree, CommonUnit.meterPerSecond};
+
+  /** construct a CoordinateSystem for (elevation, azimuth,
+      radial) relative to a 3-D Cartesian reference;
+      this constructor supplies units =
+      {CommonUnit.Degree, CommonUnit.Degree, CommonUnit.meterPerSecond}
+      to the super constructor, in order to ensure Unit compatibility
+      with its use of trigonometric functions */
+  public FlowSphericalCoordinateSystem(RealTupleType reference)
+         throws VisADException {
+    super(reference, coordinate_system_units);
+  }
+
+  /** trusted constructor for initializers */
+  FlowSphericalCoordinateSystem(RealTupleType reference, boolean b) {
+    super(reference, coordinate_system_units, b);
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("FlowSphericalCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[2][i] < 0.0) {
+        value[0][i] = Double.NaN;
+        value[1][i] = Double.NaN;
+        value[2][i] = Double.NaN;
+      }
+      else {
+        double coslat = Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        double sinlat = Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        double coslon = Math.cos(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        double sinlon = Math.sin(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        value[0][i] = -tuples[2][i] * sinlon * coslat;
+        value[1][i] = -tuples[2][i] * coslon * coslat;
+        value[2][i] = -tuples[2][i] * sinlat;
+      }
+    }
+    return value;
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("FlowSphericalCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    for (int i=0; i<len ;i++) {
+      value[2][i] = Math.sqrt(tuples[0][i] * tuples[0][i] +
+                              tuples[1][i] * tuples[1][i] +
+                              tuples[2][i] * tuples[2][i]);
+      value[0][i] =
+        Data.RADIANS_TO_DEGREES * Math.asin(-tuples[2][i] / value[2][i]);
+      value[1][i] =
+        Data.RADIANS_TO_DEGREES * Math.atan2(-tuples[0][i], -tuples[1][i]);
+      if (value[1][i] < 0.0) value[1][i] += 360.0;
+    }
+    return value;
+  }
+
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("FlowSphericalCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[2][i] < 0.0) {
+        value[0][i] = Float.NaN;
+        value[1][i] = Float.NaN;
+        value[2][i] = Float.NaN;
+      }
+      else {
+        float coslat = (float) Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        float sinlat = (float) Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        float coslon = (float) Math.cos(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        float sinlon = (float) Math.sin(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        value[0][i] = -tuples[2][i] * sinlon * coslat;
+        value[1][i] = -tuples[2][i] * coslon * coslat;
+        value[2][i] = -tuples[2][i] * sinlat;
+      }
+    }
+    return value;
+  }
+
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("FlowSphericalCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    for (int i=0; i<len ;i++) {
+      value[2][i] = (float) Math.sqrt(tuples[0][i] * tuples[0][i] +
+                                      tuples[1][i] * tuples[1][i] +
+                                      tuples[2][i] * tuples[2][i]);
+      value[0][i] = (float)
+        (Data.RADIANS_TO_DEGREES * Math.asin(-tuples[2][i] / value[2][i]));
+      value[1][i] = (float)
+        (Data.RADIANS_TO_DEGREES * Math.atan2(-tuples[0][i], -tuples[1][i]));
+      if (value[1][i] < 0.0f) value[1][i] += 360.0f;
+    }
+    return value;
+  }
+
+  public boolean equals(Object cs) {
+    return (cs instanceof FlowSphericalCoordinateSystem);
+  }
+
+}
+
diff --git a/visad/Function.java b/visad/Function.java
new file mode 100644
index 0000000..3e23181
--- /dev/null
+++ b/visad/Function.java
@@ -0,0 +1,197 @@
+//
+// Function.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   Function is the interface for approximate implmentations
+   of mathematical function.<P>
+*/
+public interface Function extends Data {
+
+  /**
+   * Get the dimension (number of Real components) of this Function's domain
+   * @return  number of RealType components (n in R^n space)
+   */
+  int getDomainDimension()
+         throws VisADException, RemoteException;
+
+  /**
+   * Get the default Units of the Real components of the domain.
+   * @return  array of Unit-s in the same order as the RealTypes in the
+   *          domain.
+   */
+  Unit[] getDomainUnits()
+         throws VisADException, RemoteException;
+
+  /**
+   * Get the CoordinateSystem associated with the domain RealTuple
+   * @return CoordinateSystem of the domain
+   */
+  CoordinateSystem getDomainCoordinateSystem()
+         throws VisADException, RemoteException;
+
+  /** 
+   * Evaluate this Function at domain;  for 1-D domains
+   * use default modes for resampling (Data.WEIGHTED_AVERAGE) and 
+   * errors (Data.NO_ERRORS)
+   * @param domain         value to evaluate at.
+   * @return Data object corresponding to the function value at that domain.
+   *         may return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  Data evaluate(Real domain)
+         throws VisADException, RemoteException;
+
+  /** 
+   * Evaluate this Function, for 1-D domains, with non-default modes 
+   * for resampling and errors 
+   * @param domain         value to evaluate at.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.
+   *         May return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  Data evaluate(Real domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException;
+
+  /** 
+   * Evaluate this Function at domain; use default modes for resampling 
+   * (Data.WEIGHTED_AVERAGE) and errors (Data.NO_ERRORS)
+   * @param domain         value to evaluate at.
+   * @return Data object corresponding to the function value at that domain.
+   *         may return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  Data evaluate(RealTuple domain)
+         throws VisADException, RemoteException;
+
+  /** 
+   * Evaluate this Function with non-default modes for resampling and errors 
+   * @param domain         value to evaluate at.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.
+   *         May return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  Data evaluate(RealTuple domain, int sampling_mode,
+         int error_mode) throws VisADException, RemoteException;
+
+  /** 
+   * Return a Field of Function values at the samples in set
+   * using default sampling_mode (WEIGHTED_AVERAGE) and
+   * error_mode (NO_ERRORS);
+   * This combines unit conversions, coordinate transforms,
+   * resampling and interpolation 
+   *
+   * @param  set    finite sampling values for the function.
+   * @return Data object corresponding to the function value at that domain.
+   *         may return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to resample function
+   * @throws  RemoteException  Java RMI exception
+   */
+  Field resample(Set set) throws VisADException, RemoteException;
+
+  /** 
+   * Resample range values of this Function to domain samples in set;
+   * return a Field (i.e., a finite sampling of a Function).  Use
+   * the specified sampling_mode and error_mode.
+   * This combines unit conversions, coordinate transforms,
+   * resampling and interpolation 
+   * @param set            finite sampling values for the function.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.
+   *         May return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to resample function
+   * @throws  RemoteException  Java RMI exception
+   */
+  Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  /** return the derivative of this Function with respect to d_partial;
+      d_partial may occur in this Function's domain RealTupleType, or,
+      if the domain has a CoordinateSystem, in its Reference
+      RealTupleType; propogate errors according to error_mode */
+  Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException;
+
+  /** return the derivative of this Function with respect to d_partial;
+      set result MathType to derivType; d_partial may occur in this
+      Function's domain RealTupleType, or, if the domain has a
+      CoordinateSystem, in its Reference RealTupleType;
+      propogate errors according to error_mode */
+  Function derivative( RealType d_partial, MathType derivType,
+                                       int error_mode)
+         throws VisADException, RemoteException;
+
+  /** return the tuple of derivatives of this Function with respect to
+      all RealType components of its domain RealTuple;
+      propogate errors according to error_mode */
+  Data derivative( int error_mode )
+         throws VisADException, RemoteException;
+
+  /** return the tuple of derivatives of this Function with respect to
+      all RealType components of its domain RealTuple;
+      set result MathTypes of tuple components to derivType_s;
+      propogate errors according to error_mode */
+  Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException;
+
+  /** return the tuple of derivatives of this Function with respect
+      to the RealTypes in d_partial_s; the RealTypes in d_partial_s
+      may occur in this Function's domain RealTupleType, or, if the
+      domain has a CoordinateSystem, in its Reference RealTupleType;
+      set result MathTypes of tuple components to derivType_s;
+      propogate errors according to error_mode */
+  Data derivative( RealTuple location, RealType[] d_partial_s,
+                                   MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException;
+
+}
diff --git a/visad/FunctionImpl.java b/visad/FunctionImpl.java
new file mode 100644
index 0000000..1384f08
--- /dev/null
+++ b/visad/FunctionImpl.java
@@ -0,0 +1,245 @@
+//
+// FunctionImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   FunctionImpl is the abstract superclass for approximate
+   implmentations of mathematical functions.  It inherits
+   the Function interface.<P>
+*/
+public abstract class FunctionImpl extends DataImpl implements Function {
+
+  /**
+   * Construct a new FunctionImpl with the supplied FunctionType.
+   * @param type  FunctionType used to define the structure
+   */
+  public FunctionImpl(FunctionType type) {
+    super(type);
+  }
+
+  /**
+   * Get the dimension (number of RealType components) of this Function's domain
+   * @return  number of RealType components (n in R^n space)
+   */
+  public int getDomainDimension() {
+     return ((FunctionType) Type).getDomain().getDimension();
+  }
+
+  /**
+   * Get the default Units of the Real components of the domain.
+   * @return  array of Unit-s in the same order as the RealTypes in the
+   *          domain.
+   */
+  public Unit[] getDomainUnits() {
+     return ((FunctionType) Type).getDomain().getDefaultUnits();
+  }
+
+  /**
+   * Get the CoordinateSystem associated with the domain RealTuple
+   * @return CoordinateSystem of the domain
+   */
+  public CoordinateSystem getDomainCoordinateSystem() {
+    return ((FunctionType) Type).getDomain().getCoordinateSystem();
+  }
+
+  /** 
+   * Evaluate this Function at domain; use default modes for resampling 
+   * (Data.WEIGHTED_AVERAGE) and errors (Data.NO_ERRORS)
+   * @param domain         value to evaluate at.
+   * @return Data object corresponding to the function value at that domain.
+   *         may return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public Data evaluate(Real domain)
+         throws VisADException, RemoteException {
+    if (domain == null) {
+      return ((FunctionType) getType()).getRange().missingData();
+    }
+    CoordinateSystem domainCoordinateSystem = getDomainCoordinateSystem();
+    RealTuple domainPoint =
+      domainCoordinateSystem == null
+	? new RealTuple(new Real[] {domain})
+	: new RealTuple(
+	    new RealTupleType(
+	      (RealType)domain.getType(), domainCoordinateSystem, null),
+	    new Real[] {domain},
+	    (CoordinateSystem)null);
+    return evaluate(domainPoint, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+    // return evaluate(domainPoint, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+  }
+
+  /** 
+   * Evaluate this Function with non-default modes for resampling and errors 
+   * @param domain         value to evaluate at.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.
+   *         May return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public Data evaluate(Real domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if (domain == null) {
+      return ((FunctionType) getType()).getRange().missingData();
+    }
+    CoordinateSystem domainCoordinateSystem = getDomainCoordinateSystem();
+    RealTuple domainPoint =
+      domainCoordinateSystem == null
+	? new RealTuple(new Real[] {domain})
+	: new RealTuple(
+	    new RealTupleType(
+	      (RealType)domain.getType(), domainCoordinateSystem, null),
+	    new Real[] {domain},
+	    (CoordinateSystem)null);
+    return evaluate(domainPoint, sampling_mode, error_mode);
+  }
+
+  /** 
+   * Evaluate this Function at domain; use default modes for resampling 
+   * (Data.WEIGHTED_AVERAGE) and errors (Data.NO_ERRORS)
+   * @param domain         value to evaluate at.
+   * @return Data object corresponding to the function value at that domain.
+   *         may return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public Data evaluate(RealTuple domain)
+         throws VisADException, RemoteException {
+    if (domain == null) {
+      return ((FunctionType) getType()).getRange().missingData();
+    }
+    return evaluate(domain, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+    // return evaluate(domain, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+  }
+
+  /** 
+   * Evaluate this Function with non-default modes for resampling and errors 
+   * @param domain         value to evaluate at.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.
+   *         May return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to evaluate function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public Data evaluate(RealTuple domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if (domain == null) {
+      return ((FunctionType) getType()).getRange().missingData();
+    }
+    Field field = resample(new SingletonSet(domain, domain.getCoordinateSystem(),
+                           domain.getTupleUnits(), domain.getErrors()),
+                           sampling_mode, error_mode);
+    return field.getSample(0);
+  }
+
+  /** 
+   * Return a Field of Function values at the samples in set
+   * using default sampling_mode (WEIGHTED_AVERAGE) and
+   * error_mode (NO_ERRORS);
+   * This combines unit conversions, coordinate transforms,
+   * resampling and interpolation 
+   *
+   * @param  set    finite sampling values for the function.
+   * @return Data object corresponding to the function value at that domain.
+   *         may return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to resample function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public Field resample(Set set) throws VisADException, RemoteException {
+    return resample(set, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+  }
+
+  /** 
+   * Resample range values of this Function to domain samples in set;
+   * return a Field (i.e., a finite sampling of a Function).  Use
+   * the specified sampling_mode and error_mode.
+   * This combines unit conversions, coordinate transforms,
+   * resampling and interpolation 
+   * @param set            finite sampling values for the function.
+   * @param sampling_mode  type of interpolation to perform (e.g., 
+   *                       Data.WEIGHTED_AVERAGE, Data.NEAREST_NEIGHBOR)
+   * @param error_mode     type of error estimation to perform (e.g., 
+   *                       Data.INDEPENDENT, Data.DEPENDENT, Data.NO_ERRORS)
+   * @return Data object corresponding to the function value at that domain,
+   *         using the sampling_mode and error_modes specified.
+   *         May return a missing data object of the same type as the
+   *         Function's range.
+   * @throws  VisADException   unable to resample function
+   * @throws  RemoteException  Java RMI exception
+   */
+  public abstract Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  public abstract Data derivative( RealTuple location, RealType[] d_partial_s,
+                                   MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException;
+
+  public abstract Data derivative( int error_mode )
+         throws VisADException, RemoteException;
+
+  public abstract Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException;
+
+  public abstract Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException;
+
+  public abstract Function derivative( RealType d_partial, MathType derivType,
+                                       int error_mode )
+         throws VisADException, RemoteException;
+
+  /**
+   * A wrapper around {@link #evaluate(Real) evaluate} for JPython.
+   */
+  public Data __getitem__(Real domain) throws VisADException, RemoteException {
+    return evaluate(domain);
+  }
+
+  /**
+   * A wrapper around {@link #evaluate(RealTuple) evaluate} for JPython.
+   */
+  public Data __getitem__(RealTuple domain) throws VisADException, RemoteException {
+    return evaluate(domain);
+  }
+
+}
+
diff --git a/visad/FunctionType.java b/visad/FunctionType.java
new file mode 100644
index 0000000..c14d5a2
--- /dev/null
+++ b/visad/FunctionType.java
@@ -0,0 +1,332 @@
+//
+// FunctionType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.Vector;
+
+/**
+   FunctionType is the VisAD data type for functions.<P>
+
+   A Function domain type may be either a RealType (for a function with
+   domain = R) or a RealTupleType (for a function with domain = R^n for
+   n > 0).<P>
+*/
+public class FunctionType extends MathType {
+
+  private RealTupleType Domain;
+  private MathType Range;
+  private RealTupleType FlatRange;
+  private boolean Real;  // true if range is RealType or RealTupleType
+  private boolean Flat;  // true if Real or if range is Flat TupleType
+
+  /** this is an array of RealType-s that are RealType
+      components of Range or RealType components of
+      RealTupleType components of Range;
+      a non_realType and non-TupleType Range is marked by null;
+      components of a TupleType Range that are neither
+      RealType nor RealTupleType are ignored */
+  private RealType[] realComponents;
+
+  /** array of TextType Range components */
+  private TextType[] textComponents;
+  /** array of component indices of TextType Range components */
+  private int[] textIndices;
+
+  public final static FunctionType REAL_1TO1_FUNCTION =
+    new FunctionType(RealType.Generic, RealType.Generic, true);
+  private static RealType[] real3 =
+    {RealType.Generic, RealType.Generic, RealType.Generic};
+  public final static FunctionType REAL_1TO3_FUNCTION =
+    new FunctionType(RealType.Generic, new RealTupleType(real3, true), true);
+  private static RealType[] real4 =
+    {RealType.Generic, RealType.Generic, RealType.Generic, RealType.Generic};
+  public final static FunctionType REAL_1TO4_FUNCTION =
+    new FunctionType(RealType.Generic, new RealTupleType(real4, true), true);
+
+  /** domain must be a RealType or a RealTupleType;
+      range may be any MathType */
+  public FunctionType(MathType domain, MathType range) throws VisADException {
+    super();
+    if (domain == null) {
+      throw new TypeException("domain is null");
+    }
+    if (range == null) {
+      throw new TypeException("range is null");
+    }
+    if (!(domain instanceof RealTupleType || domain instanceof RealType)) {
+      throw new TypeException("FunctionType: domain must be RealTupleType" +
+                              " or RealType");
+    }
+    Domain = makeFlat(domain);
+
+    Real = range instanceof RealType ||
+           range instanceof RealTupleType;
+    Flat = Real ||
+           (range instanceof TupleType && ((TupleType) range).getFlat());
+    Range = range;
+    FlatRange = Flat ? makeFlat(range) : null;
+    realComponents = getComponents(Range);
+    makeTextComponents();
+  }
+
+  /** trusted constructor for initializers */
+  FunctionType(MathType domain, MathType range, boolean b) {
+    super(b);
+    Domain = makeFlatTrusted(domain);
+    Real = range instanceof RealType ||
+           range instanceof RealTupleType;
+    Flat = Real ||
+           (range instanceof TupleType && ((TupleType) range).getFlat());
+    Range = range;
+    FlatRange = Flat ? makeFlatTrusted(range) : null;
+    realComponents = getComponents(Range);
+    makeTextComponents();
+  }
+
+  private void makeTextComponents() {
+    int n = 0;
+    textComponents = null;
+    textIndices = null;
+    if (Range instanceof TextType) {
+      textComponents = new TextType[] {(TextType) Range};
+      textIndices = new int[] {0};
+      n = 1;
+    }
+    else if (Range instanceof TupleType) {
+      try {
+        for (int i=0; i<((TupleType) Range).getDimension(); i++) {
+          if (((TupleType) Range).getComponent(i) instanceof TextType) n++;
+        }
+        if (n == 0) return;
+        textComponents = new TextType[n];
+        textIndices = new int[n];
+        int j = 0;
+        for (int i=0; i<((TupleType) Range).getDimension(); i++) {
+          if (((TupleType) Range).getComponent(i) instanceof TextType) {
+            textComponents[j] = (TextType) ((TupleType) Range).getComponent(i);
+            textIndices[j] = i;
+            j++;
+          }
+        }
+      }
+      catch (VisADException e) {
+        textComponents = null;
+        textIndices = null;
+      }
+    }
+  }
+
+  public TextType[] getTextComponents() {
+    return textComponents;
+  }
+
+  public int[] getTextIndices() {
+    return textIndices;
+  }
+
+  private static RealType[] getComponents(MathType type) {
+    RealType[] reals;
+    if (type instanceof RealType) {
+      RealType[] r = {(RealType) type};
+      return r;
+    }
+    else if (type instanceof TupleType) {
+      return ((TupleType) type).getRealComponents();
+    }
+    else {
+      return null;
+    }
+  }
+
+  private static RealTupleType makeFlat(MathType type) throws VisADException {
+    if (type instanceof RealTupleType) {
+      return (RealTupleType) type;
+    }
+    else if (type instanceof RealType) {
+      RealType[] types = {(RealType) type};
+      return new RealTupleType(types, null, ((RealType) type).getDefaultSet());
+    }
+    else if (type instanceof TupleType && ((TupleType) type).getFlat()) {
+      return new RealTupleType(((TupleType) type).getRealComponents());
+    }
+    else {
+      throw new TypeException("FunctionType: illegal input to makeFlat");
+    }
+  }
+
+  private static RealTupleType makeFlatTrusted(MathType type) {
+    if (type instanceof RealTupleType) {
+      return (RealTupleType) type;
+    }
+    else if (type instanceof RealType) {
+      RealType[] types = {(RealType) type};
+      return new RealTupleType(types, true);
+    }
+    else if (type instanceof TupleType && ((TupleType) type).getFlat()) {
+      return new RealTupleType(((TupleType) type).getRealComponents(), true);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /** if the domain passed to constructor was a RealType,
+      getDomain returns a RealTupleType with that RealType
+      as its single component */
+  public RealTupleType getDomain() {
+    return Domain;
+  }
+
+  public MathType getRange() {
+    return Range;
+  }
+
+  public boolean getFlat() {
+    return Flat;
+  }
+
+  public boolean getReal() {
+    return Real;
+  }
+
+  public RealTupleType getFlatRange() {
+    return FlatRange;
+  }
+
+  public RealType[] getRealComponents() {
+    return realComponents;
+  }
+
+  public boolean equals(Object type) {
+    if (!(type instanceof FunctionType)) return false;
+    return (Domain.equals(((FunctionType) type).getDomain()) &&
+            Range.equals(((FunctionType) type).getRange()));
+  }
+
+  /**
+   * Returns the hash code of this instance.  If {@link #equals(Object type)},
+   * then <code>{@link #hashCode()} == type.hashCode()</code>.
+   *
+   * @return			The hash code of this instance.
+   */
+  public int hashCode()
+  {
+    return Domain.hashCode() ^ Range.hashCode();
+  }
+
+  public boolean equalsExceptName(MathType type) {
+    if (!(type instanceof FunctionType)) return false;
+    return (Domain.equalsExceptName(((FunctionType) type).getDomain()) &&
+            Range.equalsExceptName(((FunctionType) type).getRange()));
+  }
+
+  /*- TDR May 1998    */
+  public boolean equalsExceptNameButUnits(MathType type) throws VisADException {
+    if (!(type instanceof FunctionType)) return false;
+    return (Domain.equalsExceptNameButUnits(((FunctionType) type).getDomain()) &&
+            Range.equalsExceptNameButUnits(((FunctionType) type).getRange()));
+  }
+
+  /*- TDR June 1998   */
+  public MathType cloneDerivative( RealType d_partial )
+         throws VisADException
+  {
+    return (MathType) new FunctionType( Domain, Range.cloneDerivative(d_partial));
+  }
+
+  /*- TDR July 1998  */
+  public MathType binary( MathType type, int op, Vector names )
+         throws VisADException
+  {
+    if (type == null) {
+      throw new TypeException("FunctionType.binary: type may not be null" );
+    }
+    if (equalsExceptName(type)) {
+      return new FunctionType(Domain,
+             Range.binary(((FunctionType)type).getRange(), op, names));
+    }
+    else if (type instanceof RealType ||
+             getRange().equalsExceptName(type)) {
+      return new FunctionType(Domain, Range.binary(type, op, names));
+    }
+    else if (type instanceof FunctionType &&
+             ((FunctionType) type).getRange().equalsExceptName(this)) {
+      return new FunctionType(((FunctionType) type).getDomain(),
+        ((FunctionType) type).getRange().binary(this, DataImpl.invertOp(op), names));
+    }
+    else {
+      throw new TypeException("FunctionType.binary: types don't match");
+    }
+/* WLH 10 Sept 98
+    MathType m_type = (type instanceof FunctionType) ?
+                      ((FunctionType)type).getRange() : type;
+    return (MathType) new FunctionType( Domain, Range.binary( m_type, op, names ));
+*/
+  }
+
+  /*- TDR July 1998  */
+  public MathType unary( int op, Vector names )
+         throws VisADException
+  {
+    return (MathType) new FunctionType( Domain, Range.unary( op, names ));
+  }
+
+/* WLH 5 Jan 2000
+  public String toString() {
+    String t = Real ? " (Real): " : Flat ? " (Flat): " : ": ";
+    return "FunctionType" + t + Domain.toString() +
+             " -> " + Range.toString();
+  }
+*/
+
+  public String prettyString(int indent) {
+    String ds = "(" + Domain.prettyString(indent) + " -> ";
+    int n = ds.length();
+    String rs = Range.prettyString(indent + n) + ")";
+    return ds + rs;
+  }
+
+  public Data missingData() throws VisADException, RemoteException {
+    int n = Domain.getDimension();
+    double[] values = new double[n];
+    for (int i=0; i<n; i++) values[i] = 0.0;
+    RealTuple tuple = new RealTuple(Domain, values);
+    Set domainSet = new SingletonSet(tuple);
+    return getFlat()
+        ? new FlatField(this, domainSet)
+        : new FieldImpl(this, domainSet);
+  }
+
+  public ShadowType buildShadowType(DataDisplayLink link, ShadowType parent)
+             throws VisADException, RemoteException {
+    return (ShadowType)
+      link.getRenderer().makeShadowFunctionType(this, link, parent);
+  }
+
+}
+
diff --git a/visad/GraphicsModeControl.java b/visad/GraphicsModeControl.java
new file mode 100644
index 0000000..a424dac
--- /dev/null
+++ b/visad/GraphicsModeControl.java
@@ -0,0 +1,587 @@
+//
+// GraphicsModeControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+
+import java.rmi.*;
+
+import java.util.StringTokenizer;
+
+import visad.browser.Convert;
+
+
+/**
+ * GraphicsModeControl is the VisAD interface class for controlling various
+ * mode settings for rendering.<P>
+
+ * A GraphicsModeControl is not linked to any DisplayRealType or
+ * ScalarMap.  It is linked to a DisplayImpl.<P>
+ */
+public abstract class GraphicsModeControl extends Control implements Cloneable {
+
+  /** Solid line style for Display.LineStyle mapping */
+  public static final int SOLID_STYLE = 0;
+
+  /** Dash line style for Display.LineStyle mapping */
+  public static final int DASH_STYLE = 1;
+
+  /** Dot line style for Display.LineStyle mapping */
+  public static final int DOT_STYLE = 2;
+
+  /** Dash-Dot line style for Display.LineStyle mapping */
+  public static final int DASH_DOT_STYLE = 3;
+
+  /** Average color style for merging color maps */
+  public static final int AVERAGE_COLOR_MODE = 0;
+
+  /** Sum color style for merging color maps */
+  public static final int SUM_COLOR_MODE = 1;
+
+  /** 2D stack type for volume rendering */
+  public static final int STACK2D = 0;
+
+  /** 3D texture type for volume rendering */
+  public static final int TEXTURE3D = 1;
+
+  /**
+   * Create a GraphicsModeControl for the display.
+   *
+   * @param d  DisplayImpl to use
+   */
+  public GraphicsModeControl(DisplayImpl d) {
+    super(d);
+  }
+
+  /**
+   * Get the 2D mode of the display.
+   *
+   * @return true if display has a 2D mode.
+   */
+  public abstract boolean getMode2D();
+
+  /**
+   * Get the width of line rendering.
+   *
+   * @return line width
+   */
+  public abstract float getLineWidth();
+
+  /**
+   * Set the width of line rendering; this is over-ridden by
+   * ConstantMaps to Display.LineWidth.
+   *
+   * @param width      line width to use
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public abstract void setLineWidth(float width)
+    throws VisADException, RemoteException;
+
+  /**
+   * Set the width of line rendering, do not update the display.
+   *
+   * @param width      line width to use
+   * @param noChange   dummy flag
+   */
+  public abstract void setLineWidth(float width, boolean noChange);
+
+  /**
+   * Get the size for point rendering
+   *
+   * @return point size
+   */
+  public abstract float getPointSize();
+
+  /**
+   * Set the size for point rendering; this is over-ridden by
+   * ConstantMaps to Display.PointSize.
+   *
+   * @param size   point size
+   *
+   * @throws  VisADException   unable to set point size
+   * @throws  RemoteException  unable to set point size on remote display
+   */
+  public abstract void setPointSize(float size)
+    throws VisADException, RemoteException;
+
+  /**
+   * Set the size for point rendering, does not update the display.
+   *
+   * @param size   point size
+   * @param noChange   dummy flag
+   */
+  public abstract void setPointSize(float size, boolean noChange);
+
+  /**
+   * Get the line style
+   *
+   * @return line style
+   */
+  public abstract int getLineStyle();
+
+  /**
+   * set the style of line rendering; this is over-ridden by
+   * ConstantMaps to Display.LineStyle
+   *
+   * @param style  The line styles are:
+   *                        <ul>
+   *                        <li>GraphicsModeControl.SOLID_STYLE
+   *                        <li>GraphicsModeControl.DASH_STYLE
+   *                        <li>GraphicsModeControl.DOT_STYLE
+   *                        <li>GraphicsModeControl.DASH_DOT_STYLE
+   *                        </ul>
+   * @throws  VisADException   unable to set line style
+   * @throws  RemoteException  unable to set line style on remote display
+   */
+  public abstract void setLineStyle(int style)
+    throws VisADException, RemoteException;
+
+  /**
+   * Set the style of line rendering, does not update the display.
+   *
+   * @param style  The line styles are:
+   *                        <ul>
+   *                        <li>GraphicsModeControl.SOLID_STYLE
+   *                        <li>GraphicsModeControl.DASH_STYLE
+   *                        <li>GraphicsModeControl.DOT_STYLE
+   *                        <li>GraphicsModeControl.DASH_DOT_STYLE
+   *                        </ul>
+   * @param noChange   dummy flag
+   */
+  public abstract void setLineStyle(int style, boolean noChange);
+
+  /**
+   * Get the color mode.
+   * @return color mode
+   */
+  public abstract int getColorMode();
+
+  /**
+   * Set the mode for merging color mappings.
+   *
+   * @param  mode     The color modes are:
+   *                       <ul>
+   *                       <li>GraphicsModeControl.AVERAGE_COLOR_MODE
+   *                       <li>GraphicsModeControl.SUM_COLOR_MODE
+   *                       </ul>
+   * @throws  VisADException   unable to set color mode
+   * @throws  RemoteException  unable to set color mode on remote display
+   */
+  public abstract void setColorMode(int mode)
+    throws VisADException, RemoteException;
+
+  /**
+   * Get the point mode.
+   *
+   * @return point mode
+   */
+  public abstract boolean getPointMode();
+
+  /**
+   * Set the point rendering mode.
+   *
+   * @param mode  if true, this will cause some rendering as points
+   *              rather than lines or surfaces.
+   *
+   * @throws  VisADException   unable to enable point mode
+   * @throws  RemoteException  unable to enable point mode on remote display
+   */
+  public abstract void setPointMode(boolean mode)
+    throws VisADException, RemoteException;
+
+  /**
+   * Get the use of texture mapping.
+   * @return if true this the use of texture mapping is enabled
+   */
+  public abstract boolean getTextureEnable();
+
+  /**
+   * Set the use of texture mapping.
+   *
+   * @param enable  if true this will enable the use of texture
+   *                mapping, where appropriate
+   *
+   * @throws  VisADException   unable to enable texture mapping
+   * @throws  RemoteException  unable to enable texture mapping on remote display
+   */
+  public abstract void setTextureEnable(boolean enable)
+    throws VisADException, RemoteException;
+
+  /**
+   * Get the use of numerical scales along display axes.
+   *
+   * @return true if numerical scales are enabled along display spatial axes
+   */
+  public abstract boolean getScaleEnable();
+
+  /**
+   * Set the use of numerical scales along display axes.
+   *
+   * @param enable     if true, this will enable numerical
+   *                   scales along display spatial axes
+   *
+   * @throws  VisADException   unable to enable scales
+   * @throws  RemoteException  unable to enable scales on remote display
+   */
+  public abstract void setScaleEnable(boolean enable)
+    throws VisADException, RemoteException;
+
+  /**
+   * Gets the graphics-API-specific transparency mode (e.g.,
+   * SCREEN_DOOR, BLENDED) used in the display
+   *
+   * @return the graphics-API-specific transparency mode
+   */
+  public abstract int getTransparencyMode();
+
+  /**
+   * Sets a graphics-API-specific transparency mode (e.g.,
+   * SCREEN_DOOR, BLENDED) on the display.
+   *
+   * @param mode  graphics-API-specific transparency mode
+   *
+   * @throws  VisADException   Unable to change transparency mode
+   * @throws  RemoteException  can't change transparency mode on remote display
+   */
+  public abstract void setTransparencyMode(int mode)
+    throws VisADException, RemoteException;
+
+  /**
+   * Sets a graphics-API-specific projection policy (e.g.,
+   * PARALLEL_PROJECTION, PERSPECTIVE_PROJECTION) for the display.
+   *
+   * @param   policy      policy to be used
+   *
+   * @throws  VisADException   bad policy or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  change policy on remote display
+   */
+  public abstract void setProjectionPolicy(int policy)
+    throws VisADException, RemoteException;
+
+  /**
+   * Get the current graphics-API-specific projection policy for the display.
+   *
+   * @return  policy
+   */
+  public abstract int getProjectionPolicy();
+
+  /**
+   * Sets the graphics-API-specific polygon mode and updates the display
+   *
+   * @param  mode   the polygon mode to be used
+   *
+   * @throws  VisADException   bad mode or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  can't change mode on remote display
+   */
+  public abstract void setPolygonMode(int mode)
+    throws VisADException, RemoteException;
+
+  /**
+   * Sets the graphics-API-specific polygon mode.  Does not update the display.
+   *
+   * @param  mode   the polygon mode to be used
+   * @param noChange   dummy flag
+   *
+   * @throws  VisADException   bad mode or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  can't change mode on remote display
+   */
+  public abstract void setPolygonMode(int mode, boolean noChange)
+    throws VisADException, RemoteException;
+
+  /**
+   * Get the Polygon mode
+   *
+   * @return  the polygon mode
+   */
+  public abstract int getPolygonMode();
+
+  /**
+   * Sets the polygon offset and updates the display.
+   *
+   * @param  polygonOffset   the polygon offset to be used
+   *
+   * @throws  VisADException   Unable to change offset
+   * @throws  RemoteException  can't change offset on remote display
+   */
+  public abstract void setPolygonOffset(float polygonOffset)
+    throws VisADException, RemoteException;
+
+  /**
+   * Sets the polygon offset.  Does not update the display.
+   *
+   * @param  polygonOffset   the polygon offset to be used
+   * @param  noChange   dummy variable
+   */
+  public abstract void setPolygonOffset(float polygonOffset,
+                                        boolean noChange);
+
+  /**
+   * Get the current polygon offset.
+   *
+   * @return  offset
+   */
+  public abstract float getPolygonOffset();
+
+  /**
+   * Sets the polygon offset factor and updates the display.
+   *
+   * @param factor  the polygon offset factor to be used
+   *
+   * @throws  VisADException   Unable to change offset factor
+   * @throws  RemoteException  can't change offset factor on remote display
+   */
+  public abstract void setPolygonOffsetFactor(float factor)
+    throws VisADException, RemoteException;
+
+  /**
+   * Sets the polygon offset factor, does not update display.
+   *
+   * @param factor  the polygon offset to be used
+   * @param  noChange   dummy variable
+   */
+  public abstract void setPolygonOffsetFactor(float factor, boolean noChange);
+
+  /**
+   * Get the current polygon offset factor.
+   *
+   * @return  offset factor
+   */
+  public abstract float getPolygonOffsetFactor();
+
+  /**
+   * Set the transparency of missing values.
+   *
+   * @param  missing   true if missing values should be rendered transparent.
+   *
+   * @throws  VisADException   Unable to change missing transparent
+   * @throws  RemoteException  can't change missing transparent on remote display
+   */
+  public abstract void setMissingTransparent(boolean missing)
+    throws VisADException, RemoteException;
+
+  /**
+   * See whether missing values are rendered as transparent or not.
+   *
+   * @return  true if missing values are transparent.
+   */
+  public abstract boolean getMissingTransparent();
+
+  /**
+   * Set whether or not to call methods to adjust the projection seam
+   * (VisADGeometryArray.adjustLongitude/adjustSeam);
+   *
+   * @param  adjust   true if seams should be adjusted
+   *
+   * @throws  VisADException   Unable to change adjust seam property
+   * @throws  RemoteException  can't change adjust seam property on remote display
+   */
+  public abstract void setAdjustProjectionSeam(boolean adjust)
+    throws VisADException, RemoteException;
+
+  /**
+   * See whether or not to call methods to adjust the projection seam
+   * (VisADGeometryArray.adjustLongitude/adjustSeam);
+   *
+   * @return  true if adjust seam methods should be called
+   */
+  public abstract boolean getAdjustProjectionSeam();
+
+  /**
+   * Set the mode for Texture3D for volume rendering
+   *
+   * @param  mode   mode for Texture3D (STACK2D or TEXTURE3D)
+   *
+   * @throws  VisADException   Unable to change Texture3D mode
+   * @throws  RemoteException  can't change Texture3D mode on remote display
+   */
+  public abstract void setTexture3DMode(int mode)
+    throws VisADException, RemoteException;
+
+  /**
+   * Get the mode for Texture3D for volume rendering
+   *
+   * @return  mode for Texture3D (e.g., STACK2D or TEXTURE3D)
+   */
+  public abstract int getTexture3DMode();
+
+  /**
+   * Set the undersampling factor of surface shape for curved texture maps
+   *
+   * @param  curved_size  undersampling factor (default 10)
+   *
+   * @throws  VisADException   Unable to change curved size
+   * @throws  RemoteException  can't change curved size on remote display
+   */
+  public abstract void setCurvedSize(int curved_size);
+
+  /**
+   * Get the undersampling factor of surface shape for curved texture maps
+   *
+   * @return  undersampling factor (default 10)
+   */
+  public abstract int getCurvedSize();
+
+  /**
+   * Set whether the Appearances are reused
+   *
+   * @param  cache   true to cache and reuse appearances
+   */
+  public abstract void setCacheAppearances(boolean cache);
+
+  /**
+   * Get whether Appearances are cached or not
+   *
+   * @return  true if caching
+   */
+  public abstract boolean getCacheAppearances();
+
+  /**
+   * Set whether Geometries for shapes should be merged into Group if
+   * possible to reduce memory use.
+   *
+   * @param  merge   true to merge geometries if possible
+   */
+  public abstract void setMergeGeometries(boolean merge);
+
+  /**
+   * Set whether Geometries for shapes should be merged into Group
+   *
+   * @return  true if merging is used
+   */
+  public abstract boolean getMergeGeometries();
+
+  /**
+   * Get a string that can be used to reconstruct this control later
+   * @return save string
+   */
+  public String getSaveString() {
+    return "" + getLineWidth() + " " 
+           + getPointSize() + " " +
+           getPointMode() + " " + 
+           getTextureEnable() + " " +
+           getScaleEnable() + " " + 
+           getTransparencyMode() + " " +
+           getProjectionPolicy() + " " + 
+           getPolygonMode() + " " +
+           getMissingTransparent() + " " + 
+           getCurvedSize() + " " +
+           getLineStyle() + " " + 
+           getColorMode() + " " + 
+           getPolygonOffset() + " " + 
+           getPolygonOffsetFactor() + " " + 
+           getAdjustProjectionSeam() + " " + 
+           getCacheAppearances() + " " + 
+           getMergeGeometries();
+  }
+
+  /**
+   * Reconstruct this control using the specified save string
+   * @param save  save string
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public void setSaveString(String save)
+          throws VisADException, RemoteException {
+    if (save == null) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save);
+    int numTokens = st.countTokens();
+    if (numTokens < 10) throw new VisADException("Invalid save string");
+
+    // determine graphics mode settings
+    float lw = Convert.getFloat(st.nextToken());
+    float ps = Convert.getFloat(st.nextToken());
+    boolean pm = Convert.getBoolean(st.nextToken());
+    boolean te = Convert.getBoolean(st.nextToken());
+    boolean se = Convert.getBoolean(st.nextToken());
+    int tm = Convert.getInt(st.nextToken());
+    int pp = Convert.getInt(st.nextToken());
+    int pm2 = Convert.getInt(st.nextToken());
+    boolean mt = Convert.getBoolean(st.nextToken());
+    int cs = Convert.getInt(st.nextToken());
+
+    int ls = st.hasMoreTokens()
+             ? Convert.getInt(st.nextToken())
+             : SOLID_STYLE;
+    int cm = st.hasMoreTokens()
+             ? Convert.getInt(st.nextToken())
+             : 0;
+    float po = st.hasMoreTokens()
+               ? Convert.getFloat(st.nextToken())
+               : Float.NaN;
+    float pof = st.hasMoreTokens()
+                ? Convert.getFloat(st.nextToken())
+                : 0;
+
+    boolean as = st.hasMoreTokens()
+                 ? Convert.getBoolean(st.nextToken())
+                 : getAdjustProjectionSeam();
+    int t3dm = st.hasMoreTokens()
+               ? Convert.getInt(st.nextToken())
+               : 0;
+    boolean ca = st.hasMoreTokens()
+                 ? Convert.getBoolean(st.nextToken())
+                 : getCacheAppearances();
+    boolean mg = st.hasMoreTokens()
+                 ? Convert.getBoolean(st.nextToken())
+                 : getMergeGeometries();
+
+
+    // reset graphics mode settings
+    setLineWidth(lw);
+    setPointSize(ps);
+    setLineStyle(ls);
+    setPointMode(pm);
+    setTextureEnable(te);
+    setScaleEnable(se);
+    setTransparencyMode(tm);
+    setProjectionPolicy(pp);
+    setPolygonMode(pm2);
+    setMissingTransparent(mt);
+    setCurvedSize(cs);
+    setColorMode(cm);
+    setPolygonOffset(po);
+    setPolygonOffsetFactor(pof);
+    setAdjustProjectionSeam(as);
+    setTexture3DMode(t3dm);
+    setCacheAppearances(ca);
+    setMergeGeometries(mg);
+  }
+
+  /**
+   * A method to copy this object
+   *
+   * @return a copy of this object
+   */
+  public abstract Object clone();
+
+}
+
diff --git a/visad/GridCoordinateSystem.java b/visad/GridCoordinateSystem.java
new file mode 100644
index 0000000..3dc028c
--- /dev/null
+++ b/visad/GridCoordinateSystem.java
@@ -0,0 +1,146 @@
+//
+// GridCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   GridCoordinateSystem is the VisAD CoordinateSystem class
+   for grids defined by GriddedSets.<P>
+
+   It should be used as the CoordinateSystem of an IntegerSet
+   describing the set of grid values (so the dimensions of the
+   IntegerSet should match the dimensions of the GriddedSet
+   argument to the GridCoordinateSystem constructor) where
+   the reference RealTupleType describes the value space of the
+   GriddedSet.<P>
+*/
+public class GridCoordinateSystem extends CoordinateSystem {
+
+  private GriddedSet set;
+  private int dimension;
+
+  /** construct a CoordinateSystem for grid coordinates (e.g.,
+      (row, column, level) in 3-D) relative to the value space
+      of set; for example, if satellite pixel locations are
+      defined by explicit latitudes and longitude, these could
+      be used to construct a Gridded2DSet which could then be
+      used to construct a GridCoordinateSystem for (ImageLine,
+      ImageElement) coordinates relative to reference coordinates
+      (Latitude, Longitude) */
+  public GridCoordinateSystem(GriddedSet s) throws VisADException {
+    super(((SetType) s.getType()).getDomain(), null);
+    set = s;
+    dimension = set.getDimension();
+  }
+
+  /** Returns the GriddedSet associated with this instance.
+      @return			The GriddedSet associated with this instance.
+   */
+  public GriddedSet getGriddedSet() {
+    return set;
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("GridCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    double[][] values =
+      Set.floatToDouble(set.gridToValue(Set.doubleToFloat(tuples)));
+    Unit[] units_in = set.getSetUnits();
+    Unit[] units_out = getReference().getDefaultUnits();
+    ErrorEstimate[] errors_out = new ErrorEstimate[1];
+    for (int i=0; i<dimension; i++) {
+      values[i] =
+        Unit.transformUnits(units_out[i], errors_out, units_in[i],
+                            null, values[i],false);
+    }
+    return values;
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("GridCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    double[][] values = new double[dimension][];
+    for (int i=0; i<dimension; i++) values[i] = tuples[i];
+    Unit[] units_in = getReference().getDefaultUnits();
+    Unit[] units_out = set.getSetUnits();
+    ErrorEstimate[] errors_out = new ErrorEstimate[1];
+    for (int i=0; i<dimension; i++) {
+      values[i] =
+        Unit.transformUnits(units_out[i], errors_out, units_in[i],
+                            null, values[i]);
+    }
+    values =
+      Set.floatToDouble(set.valueToGrid(Set.doubleToFloat(values)));
+    return values;
+  }
+
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("GridCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    float[][] values = set.gridToValue(tuples);
+    Unit[] units_in = set.getSetUnits();
+    Unit[] units_out = getReference().getDefaultUnits();
+    ErrorEstimate[] errors_out = new ErrorEstimate[1];
+    for (int i=0; i<dimension; i++) {
+      values[i] =
+        Unit.transformUnits(units_out[i], errors_out, units_in[i],
+                            null, values[i],false);
+    }
+    return values;
+  }
+
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("GridCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    float[][] values = new float[dimension][];
+    for (int i=0; i<dimension; i++) values[i] = tuples[i];
+    Unit[] units_in = getReference().getDefaultUnits();
+    Unit[] units_out = set.getSetUnits();
+    ErrorEstimate[] errors_out = new ErrorEstimate[1];
+    for (int i=0; i<dimension; i++) {
+      values[i] =
+        Unit.transformUnits(units_out[i], errors_out, units_in[i],
+                            null, values[i]);
+    }
+    values = set.valueToGrid(values);
+    return values;
+  }
+
+  public boolean equals(Object cs) {
+    return (cs instanceof GridCoordinateSystem &&
+            set.equals(((GridCoordinateSystem) cs).set));
+  }
+
+}
+
diff --git a/visad/GridVectorType.java b/visad/GridVectorType.java
new file mode 100644
index 0000000..82248f3
--- /dev/null
+++ b/visad/GridVectorType.java
@@ -0,0 +1,136 @@
+//
+// GridVectorType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/* 5 May 99: TMW says this is the standard.  He also says that
+winds not on a grid have components in earth coordinates */
+
+/**
+   GridVectorType is the VisAD data type for 2-D and 3-D wind
+   or current vectors in Units convertable with meter / second
+   whose first component is parallel to grid rows, positive toward
+   increasing column, and whose second component is parallel to
+   grid columns, positive toward decreasing row.  It assumes
+   vertical coordinates transform nearly flat, so the optional
+   third vertical wind component does not transform. <P>
+*/
+public class GridVectorType extends RealVectorType {
+
+  public GridVectorType(RealType[] types) throws VisADException {
+    this(types, null);
+  }
+
+  public GridVectorType(RealType[] types, CoordinateSystem coord_sys)
+         throws VisADException {
+    super(types, coord_sys);
+    if (types.length != 2 && types.length != 3) {
+      throw new TypeException("GridVectorType must be 2-D or 3-D: " + types.length);
+    }
+    for (int i=0; i<types.length; i++) {
+      if (!Unit.canConvert(CommonUnit.meterPerSecond, types[i].getDefaultUnit())) {
+        throw new TypeException("GridVectorType components must be convertable " +
+                                "with meter / second: " + types[i].getDefaultUnit());
+      }
+    }
+  }
+
+  /** transform an array of vector values from a field, based on a
+      coordinate transform of the field domain.  This may use the
+      Jacobean of the coordinate transform, but may be more complex.
+      For example, vectors in m/s would not transform for a simple
+      rescaling transform.  Or the transform may be to a moving
+      coordinate system.
+
+      out, coord_out, units_out, in, coord_in, units_in are the
+      arguments to the corresponding call to transformCoordinates;
+      loc_errors_out are the ErrorEstimates for loc from that call;
+      inloc and outloc contain the input and output values from the
+      corresponding call to transformCoordinates;
+      coord_vector and errors_in are the CoordinateSystem and ErrorEstimates
+      associated with values;
+      value are the vector values (already resampled at loc);
+      return new value array;
+      return transformed ErrorEstimates in errors_out array */
+  public double[][] transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        ErrorEstimate[] errors_in,
+                        ErrorEstimate[] errors_out,
+                        double[][] inloc, double[][] outloc,
+                        double[][] value)
+         throws VisADException, RemoteException {
+    int n = value.length;
+    double[][] new_value = new double[n][];
+    if (n > 2) new_value[2] = value[2];
+
+
+    return new_value;
+  }
+
+  /** transform an array of vector values from a field, based on a
+      coordinate transform of the field domain.  This may use the
+      Jacobean of the coordinate transform, but may be more complex.
+      For example, vectors in m/s would not transform for a simple
+      rescaling transform.  Or the transform may be to a moving
+      coordinate system.
+
+      out, coord_out, units_out, in, coord_in, units_in are the
+      arguments to the corresponding call to transformCoordinates;
+      loc_errors_out are the ErrorEstimates for loc from that call;
+      inloc and outloc contain the input and output values from the
+      corresponding call to transformCoordinates;
+      coord_vector and errors_in are the CoordinateSystem and ErrorEstimates
+      associated with values;
+      value are the vector values (already resampled at loc);
+      return new value array;
+      return transformed ErrorEstimates in errors_out array */
+  public float[][] transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        ErrorEstimate[] errors_in,
+                        ErrorEstimate[] errors_out,
+                        float[][] inloc, float[][] outloc,
+                        float[][] value)
+         throws VisADException, RemoteException {
+    int n = value.length;
+    float[][] new_value = new float[n][];
+    if (n > 2) new_value[2] = value[2];
+
+
+    return new_value;
+  }
+
+// ShadowType.java: need to account for spatial setRange scaling in flow
+
+}
+
diff --git a/visad/Gridded1D.txt b/visad/Gridded1D.txt
new file mode 100644
index 0000000..3145e57
--- /dev/null
+++ b/visad/Gridded1D.txt
@@ -0,0 +1,22 @@
+20
+
+-40.548489 
+-39.462049 
+-29.183633 
+-20.616693 
+-19.486302 
+-17.525333 
+-16.789971 
+-5.960592 
+-4.536319 
+-3.581454 
+-2.901965 
+0.033919 
+2.333841 
+5.990025 
+8.204846 
+12.061084 
+22.243606 
+23.545682 
+26.026153 
+38.301201 
diff --git a/visad/Gridded1DDoubleSet.java b/visad/Gridded1DDoubleSet.java
new file mode 100644
index 0000000..92f3f14
--- /dev/null
+++ b/visad/Gridded1DDoubleSet.java
@@ -0,0 +1,761 @@
+//
+// Gridded1DDoubleSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+   Gridded1DDoubleSet is a Gridded1DSet with double-precision samples.<P>
+*/
+public class Gridded1DDoubleSet extends Gridded1DSet
+       implements GriddedDoubleSet {
+
+  double[] Low = new double[1];
+  double[] Hi = new double[1];
+  double LowX, HiX;
+  double[][] Samples;
+
+  /**
+   * A canonicalizing cache of previously-created instances.  Because instances
+   * are immutable, a cache can be used to reduce memory usage by ensuring
+   * that each instance is truly unique.  By implementing the cache using a
+   * {@link WeakHashMap}, this can be accomplished without the technique itself
+   * adversely affecting memory usage.
+   */
+  private static final WeakHashMap      cache = new WeakHashMap();
+
+
+  // Overridden Gridded1DSet constructors (float[][])
+
+  /** a 1-D sequence with no regular interval with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded1DDoubleSet(MathType type, float[][] samples, int lengthX)
+         throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, null, null, null, true);
+  }
+
+  public Gridded1DDoubleSet(MathType type, float[][] samples, int lengthX,
+                            CoordinateSystem coord_sys, Unit[] units,
+                            ErrorEstimate[] errors) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX,
+         coord_sys, units, errors, true);
+  }
+
+  /** a 1-D sorted sequence with no regular interval. samples array
+      is organized float[1][number_of_samples] where lengthX =
+      number_of_samples. samples must be sorted (either increasing
+      or decreasing). coordinate_system and units must be compatible
+      with defaults for type, or may be null. errors may be null */
+  public Gridded1DDoubleSet(MathType type, float[][] samples, int lengthX,
+                            CoordinateSystem coord_sys, Unit[] units,
+                            ErrorEstimate[] errors, boolean copy)
+                            throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX,
+      coord_sys, units, errors, copy);
+  }
+
+
+  // Corresponding Gridded1DDoubleSet constructors (double[][])
+
+  /** a 1-D sequence with no regular interval with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded1DDoubleSet(MathType type, double[][] samples, int lengthX)
+         throws VisADException {
+    this(type, samples, lengthX, null, null, null, true);
+  }
+
+  public Gridded1DDoubleSet(MathType type, double[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, coord_sys, units, errors, true);
+  }
+
+  /** a 1-D sorted sequence with no regular interval. samples array
+      is organized double[1][number_of_samples] where lengthX =
+      number_of_samples. samples must be sorted (either increasing
+      or decreasing). coordinate_system and units must be compatible
+      with defaults for type, or may be null. errors may be null */
+  public Gridded1DDoubleSet(MathType type, double[][] samples, int lengthX,
+                            CoordinateSystem coord_sys, Unit[] units,
+                            ErrorEstimate[] errors, boolean copy)
+                            throws VisADException {
+    super(type, null, lengthX, coord_sys, units, errors, copy);
+    if (samples == null) {
+      throw new SetException("Gridded1DDoubleSet: samples are null");
+    }
+    init_doubles(samples, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+
+    if (Samples != null && Lengths[0] > 1) {
+      // samples consistency test
+      for (int i=0; i<Length; i++) {
+        if (Samples[0][i] != Samples[0][i]) {
+          throw new SetException(
+           "Gridded1DDoubleSet: samples values may not be missing");
+        }
+      }
+      Ascending = (Samples[0][LengthX-1] > Samples[0][0]);
+      if (Ascending) {
+        for (int i=1; i<LengthX; i++) {
+          if (Samples[0][i] < Samples[0][i-1]) {
+            throw new SetException(
+             "Gridded1DDoubleSet: samples do not form a valid grid ("+i+")");
+          }
+        }
+      }
+      else { // !Ascending
+        for (int i=1; i<LengthX; i++) {
+          if (Samples[0][i] > Samples[0][i-1]) {
+            throw new SetException(
+             "Gridded1DDoubleSet: samples do not form a valid grid ("+i+")");
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns an instance of this class.  This method uses a weak cache of
+   * previously-created instances to reduce memory usage.
+   *
+   * @param type                The type of the set.  Must be a {@link 
+   *                            RealType} or a single-component {@link
+   *                            RealTupleType} or {@link SetType}.
+   * @param samples             The values in the set.
+   *                            <code>samples[i]</code> is the value of
+   *                            the ith sample point.  Must be sorted (either
+   *                            increasing or decreasing).  May be
+   *                            <code>null</code>.  The array is not copied, so
+   *                            either don't modify it or clone it first.
+   * @param coordSys           The coordinate system for this, particular, set.
+   *                            Must be compatible with the default coordinate
+   *                            system.  May be <code>null</code>.
+   * @param unit                The unit for the samples.  Must be compatible
+   *                            with the default unit.  May be 
+   *                            <code>null</code>.
+   * @param error               The error estimate of the samples.  May be
+   *                            <code>null</code>.
+   */
+  public static synchronized Gridded1DDoubleSet create(
+      MathType          type,
+      double[]          samples,
+      CoordinateSystem  coordSys,
+      Unit              unit,
+      ErrorEstimate     error)
+    throws VisADException
+  {
+    Gridded1DDoubleSet  newSet =
+      new Gridded1DDoubleSet(
+        type, new double[][] {samples}, samples.length, coordSys,
+        new Unit[] {unit}, new ErrorEstimate[] {error}, false);
+    WeakReference       ref = (WeakReference)cache.get(newSet);
+    if (ref == null)
+    {
+      /*
+       * The new instance is unique (any and all previously-created identical
+       * instances no longer exist).
+       *
+       * A WeakReference is used in the following because values of a
+       * WeakHashMap aren't "weak" themselves and must not strongly reference
+       * their associated keys either directly or indirectly.
+       */
+      cache.put(newSet, new WeakReference(newSet));
+    }
+    else
+    {
+      /*
+       * The new instance is a duplicate of a previously-created one.
+       */
+      Gridded1DDoubleSet        oldSet = (Gridded1DDoubleSet)ref.get();
+      if (oldSet == null)
+      {
+        /* The previous instance no longer exists.  Save the new instance. */
+        cache.put(newSet, new WeakReference(newSet));
+      }
+      else
+      {
+        /* The previous instance still exists.  Reuse it to save memory. */
+        newSet = oldSet;
+      }
+    }
+    return newSet;
+  }
+
+
+  // Overridden Gridded1DSet methods (float[][])
+
+  public float[][] getSamples() throws VisADException {
+    return getSamples(true);
+  }
+
+  public float[][] getSamples(boolean copy) throws VisADException {
+    return Set.doubleToFloat(Samples);
+  }
+
+  /** convert an array of 1-D indices to an array of values in
+      R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    return Set.doubleToFloat(indexToDouble(index));
+  }
+
+  /**
+   * Convert an array of values in R^DomainDimension to an array of
+   * 1-D indices.  This Gridded1DDoubleSet must have at least two points in the
+   * set.
+   * @param value       An array of coordinates.  <code>value[i][j]
+   *                    <code> contains the <code>i</code>th component of the
+   *                    <code>j</code>th point.
+   * @return            Indices of nearest points.  RETURN_VALUE<code>[i]</code>
+   *                    will contain the index of the point in the set closest
+   *                    to <code>value[][i]</code> or <code>-1</code> if
+   *                    <code>value[][i]</code> lies outside the set.
+   */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    return doubleToIndex(Set.floatToDouble(value));
+  }
+
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    return Set.doubleToFloat(gridToDouble(Set.floatToDouble(grid)));
+  }
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    return Set.doubleToFloat(doubleToGrid(Set.floatToDouble(value)));
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+    float[][] weights) throws VisADException
+  {
+    int len = weights.length;
+    double[][] w = new double[len][];
+    doubleToInterp(Set.floatToDouble(value), indices, w);
+    for (int i=0; i<len; i++) {
+      if (w[i] != null) {
+        weights[i] = new float[w[i].length];
+        for (int j=0; j<w[i].length; j++) {
+          weights[i][j] = (float) w[i][j];
+        }
+      }
+    }
+  }
+
+  public float getLowX() {
+    return (float) LowX;
+  }
+
+  public float getHiX() {
+    return (float) HiX;
+  }
+
+
+  // Corresponding Gridded1DDoubleSet methods (double[][])
+
+  public double[][] getDoubles() throws VisADException {
+    return getDoubles(true);
+  }
+
+  public double[][] getDoubles(boolean copy) throws VisADException {
+    return copy ? Set.copyDoubles(Samples) : Samples;
+  }
+
+  /** convert an array of 1-D indices to an array of values in
+      R^DomainDimension */
+  public double[][] indexToDouble(int[] index) throws VisADException {
+    int length = index.length;
+    if (Samples == null) {
+      // not used - over-ridden by Linear1DSet.indexToValue
+      double[][] grid = new double[ManifoldDimension][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          grid[0][i] = (double) index[i];
+        }
+        else {
+          grid[0][i] = -1;
+        }
+      }
+      return gridToDouble(grid);
+    }
+    else {
+      double[][] values = new double[1][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          values[0][i] = Samples[0][index[i]];
+        }
+        else {
+          values[0][i] = Double.NaN;
+        }
+      }
+      return values;
+    }
+  }
+
+  public int[] doubleToIndex(double[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded1DDoubleSet.doubleToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+
+    double[][] grid = doubleToGrid(value);
+    double[] grid0 = grid[0];
+    double g;
+    for (int i=0; i<length; i++) {
+      g = grid0[i];
+      index[i] = Double.isNaN(g) ? -1 : ((int) (g + 0.5));
+    }
+    return index;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public double[][] gridToDouble(double[][] grid) throws VisADException {
+    if (grid.length < DomainDimension) { 
+      throw new SetException("Gridded1DDoubleSet.gridToDouble: grid dimension " +
+                             grid.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    /* remove DRM: 2004-09-14
+    if (Lengths[0] < 2) {
+      throw new SetException("Gridded1DDoubleSet.gridToDouble: " +
+        "requires all grid dimensions to be > 1");
+    }
+    */
+    int length = grid[0].length;
+    double[][] value = new double[1][length];
+    for (int i=0; i<length; i++) {
+      // let g be the current grid coordinate
+      double g = grid[0][i];
+      if ( (g < -0.5) || (g > LengthX-0.5) ) {
+        value[0][i] = Double.NaN;
+      } else if (Length == 1) {
+        value[0][i] = Samples[0][0];
+      } else {
+        // calculate closest integer variable
+        int ig;
+        if (g < 0) ig = 0;
+        else if (g >= LengthX-1) ig = LengthX - 2;
+        else ig = (int) g;
+        double A = g - ig;  // distance variable
+        // do the linear interpolation
+        value[0][i] = (1-A)*Samples[0][ig] + A*Samples[0][ig+1];
+      }
+    }
+    return value;
+  }
+
+  // WLH 6 Dec 2001
+  //private int ig = -1;
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public double[][] doubleToGrid(double[][] value) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Gridded1DDoubleSet.doubleToGrid: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    /*
+    if (Lengths[0] < 2) {
+      throw new SetException("Gridded1DDoubleSet.doubleToGrid: " +
+        "requires all grid dimensions to be > 1");
+    }
+    */
+    double[] vals = value[0];
+    int length = vals.length;
+    double[] samps = Samples[0];
+    double[][] grid = new double[1][length];
+
+    int ig = (LengthX - 1)/2;
+/* WLH 6 Dec 2001
+    // use value from last call as first guess, if reasonable
+    if (ig < 0 || ig >= LengthX) {
+      ig = (LengthX - 1)/2;
+    }
+*/
+
+    for (int i=0; i<length; i++) {
+      if (Double.isNaN(vals[i])) {
+        grid[0][i] = Double.NaN;
+      } else if (Length == 1) {
+        grid[0][i] = 0;
+      } else {
+        int lower = 0;
+        int upper = LengthX-1;
+        while (lower < upper) {
+          if ((vals[i] - samps[ig]) * (vals[i] - samps[ig+1]) <= 0) break;
+          if (Ascending ? samps[ig+1] < vals[i] : samps[ig+1] > vals[i]) {
+            lower = ig+1;
+          }
+          else if (Ascending ? samps[ig] > vals[i] : samps[ig] < vals[i]) {
+            upper = ig;
+          }
+          if (lower < upper) ig = (lower + upper) / 2;
+        }
+        // Newton's method
+        double solv = ig + (vals[i] - samps[ig]) / (samps[ig+1] - samps[ig]);
+        if (solv > -0.5 && solv < LengthX - 0.5) grid[0][i] = solv;
+        else {
+          grid[0][i] = Double.NaN;
+          // next guess should be in the middle if previous value was missing
+          ig = (LengthX - 1)/2;
+        }
+      }
+    }
+    return grid;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void doubleToInterp(double[][] value, int[][] indices,
+    double[][] weights) throws VisADException
+  {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded1DDoubleSet.doubleToInterp: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length) {
+      throw new SetException("Gridded1DDoubleSet.doubleToInterp: indices length " +
+                             indices.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    if (weights.length != length) {
+      throw new SetException("Gridded1DDoubleSet.doubleToInterp: weights length " +
+                             weights.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    // convert value array to grid coord array
+    double[][] grid = doubleToGrid(value);
+
+    int i, j, k; // loop indices
+    int lis; // temporary length of is & cs
+    int length_is; // final length of is & cs, varies by i
+    int isoff; // offset along one grid dimension
+    double a, b; // weights along one grid dimension; a + b = 1.0
+    int[] is; // array of indices, becomes part of indices
+    double[] cs; // array of coefficients, become part of weights
+
+    int base; // base index, as would be returned by valueToIndex
+    int[] l = new int[ManifoldDimension]; // integer 'factors' of base
+    // fractions with l; -0.5 <= c <= 0.5
+    double[] c = new double[ManifoldDimension];
+
+    // array of index offsets by grid dimension
+    int[] off = new int[ManifoldDimension];
+    off[0] = 1;
+    for (j=1; j<ManifoldDimension; j++) off[j] = off[j-1] * Lengths[j-1];
+
+    for (i=0; i<length; i++) {
+      // compute length_is, base, l & c
+      length_is = 1;
+      if (Double.isNaN(grid[ManifoldDimension-1][i])) {
+        base = -1;
+      }
+      else {
+        l[ManifoldDimension-1] = (int) (grid[ManifoldDimension-1][i] + 0.5);
+        // WLH 23 Dec 99
+        if (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1]) {
+          l[ManifoldDimension-1]--;
+        }
+        c[ManifoldDimension-1] = grid[ManifoldDimension-1][i] -
+                                 ((double) l[ManifoldDimension-1]);
+        if (!((l[ManifoldDimension-1] == 0 && c[ManifoldDimension-1] <= 0.0) ||
+              (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1] - 1 &&
+               c[ManifoldDimension-1] >= 0.0))) {
+          // only interp along ManifoldDimension-1
+          // if between two valid grid coords
+          length_is *= 2;
+        }
+        base = l[ManifoldDimension-1];
+      }
+      for (j=ManifoldDimension-2; j>=0 && base>=0; j--) {
+        if (Double.isNaN(grid[j][i])) {
+          base = -1;
+        }
+        else {
+          l[j] = (int) (grid[j][i] + 0.5);
+          if (l[j] == Lengths[j]) l[j]--; // WLH 23 Dec 99
+          c[j] = grid[j][i] - ((double) l[j]);
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            length_is *= 2;
+          }
+          base = l[j] + Lengths[j] * base;
+        }
+      }
+
+      if (base < 0) {
+        // value is out of grid so return null
+        is = null;
+        cs = null;
+      }
+      else {
+        // create is & cs of proper length, and init first element
+        is = new int[length_is];
+        cs = new double[length_is];
+        is[0] = base;
+        cs[0] = 1.0f;
+        lis = 1;
+
+        for (j=0; j<ManifoldDimension; j++) {
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            if (c[j] >= 0.0) {
+              // grid coord above base
+              isoff = off[j];
+              a = 1.0f - c[j];
+              b = c[j];
+            }
+            else {
+              // grid coord below base
+              isoff = -off[j];
+              a = 1.0f + c[j];
+              b = -c[j];
+            }
+            // double is & cs; adjust new offsets; split weights
+            for (k=0; k<lis; k++) {
+              is[k+lis] = is[k] + isoff;
+              cs[k+lis] = cs[k] * b;
+              cs[k] *= a;
+            }
+            lis *= 2;
+          }
+        }
+      }
+      indices[i] = is;
+      weights[i] = cs;
+    }
+  }
+
+  public double getDoubleLowX() {
+    return LowX;
+  }
+
+  public double getDoubleHiX() {
+    return HiX;
+  }
+
+
+  // Miscellaneous Set methods that must be overridden
+  // (this code is duplicated throughout all *DoubleSet classes)
+
+  void init_doubles(double[][] samples, boolean copy)
+       throws VisADException {
+    if (samples.length != DomainDimension) {
+      throw new SetException("Gridded1DDoubleSet.init_doubles:" +
+                             " samples dimension " + samples.length +
+                             " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    if (Length == 0) {
+      // Length set in init_lengths, but not called for IrregularSet
+      Length = samples[0].length;
+    }
+    else {
+      if (Length != samples[0].length) {
+        throw new SetException("Gridded1DDoubleSet.init_doubles: samples[0] length " +
+                               samples[0].length +
+                               " doesn't match expected length " + Length);
+      }
+    }
+    // MEM
+    if (copy) {
+      Samples = new double[DomainDimension][Length];
+    }
+    else {
+      Samples = samples;
+    }
+    for (int j=0; j<DomainDimension; j++) {
+      if (samples[j].length != Length) {
+        throw new SetException("Gridded1DDoubleSet.init_doubles: samples[" + j +
+                               "] length " + samples[0].length +
+                               " doesn't match expected length " + Length);
+      }
+      double[] samplesJ = samples[j];
+      double[] SamplesJ = Samples[j];
+      if (copy) {
+        System.arraycopy(samplesJ, 0, SamplesJ, 0, Length);
+      }
+      Low[j] = Double.POSITIVE_INFINITY;
+      Hi[j] = Double.NEGATIVE_INFINITY;
+      double sum = 0.0f;
+      for (int i=0; i<Length; i++) {
+        if (SamplesJ[i] == SamplesJ[i] && !Double.isInfinite(SamplesJ[i])) {
+          if (SamplesJ[i] < Low[j]) Low[j] = SamplesJ[i];
+          if (SamplesJ[i] > Hi[j]) Hi[j] = SamplesJ[i];
+        }
+        else {
+          SamplesJ[i] = Double.NaN;
+        }
+        sum += SamplesJ[i];
+      }
+      if (SetErrors[j] != null ) {
+        SetErrors[j] =
+          new ErrorEstimate(SetErrors[j].getErrorValue(), sum / Length,
+                            Length, SetErrors[j].getUnit());
+      }
+      super.Low[j] = (float) Low[j];
+      super.Hi[j] = (float) Hi[j];
+    }
+  }
+
+  public void cram_missing(boolean[] range_select) {
+    int n = Math.min(range_select.length, Samples[0].length);
+    for (int i=0; i<n; i++) {
+      if (!range_select[i]) Samples[0][i] = Double.NaN;
+    }
+  }
+
+  public boolean isMissing() {
+    return (Samples == null);
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof Gridded1DDoubleSet) || set == null) return false;
+    if (this == set) return true;
+    if (testNotEqualsCache((Set) set)) return false;
+    if (testEqualsCache((Set) set)) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    try {
+      int i, j;
+      if (DomainDimension != ((Gridded1DDoubleSet) set).getDimension() ||
+          ManifoldDimension !=
+            ((Gridded1DDoubleSet) set).getManifoldDimension() ||
+          Length != ((Gridded1DDoubleSet) set).getLength()) return false;
+      for (j=0; j<ManifoldDimension; j++) {
+        if (Lengths[j] != ((Gridded1DDoubleSet) set).getLength(j)) {
+          return false;
+        }
+      }
+      // Sets are immutable, so no need for 'synchronized'
+      double[][] samples = ((Gridded1DDoubleSet) set).getDoubles(false);
+      if (Samples != null && samples != null) {
+        for (j=0; j<DomainDimension; j++) {
+          for (i=0; i<Length; i++) {
+            if (Samples[j][i] != samples[j][i]) {
+              addNotEqualsCache((Set) set);
+              return false;
+            }
+          }
+        }
+      }
+      else {
+        double[][] this_samples = getDoubles(false);
+        if (this_samples == null) {
+          if (samples != null) {
+            return false;
+          }
+        } else if (samples == null) {
+          return false;
+        } else {
+          for (j=0; j<DomainDimension; j++) {
+            for (i=0; i<Length; i++) {
+              if (this_samples[j][i] != samples[j][i]) {
+                addNotEqualsCache((Set) set);
+                return false;
+              }
+            }
+          }
+        }
+      }
+      addEqualsCache((Set) set);
+      return true;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Returns the hash code of this instance. {@link Object#hashCode()} should be
+   * overridden whenever {@link Object#equals(Object)} is.
+   * @return                    The hash code of this instance (includes the
+   *                            values).
+   */
+  public int hashCode()
+  {
+    if (!hashCodeSet)
+    {
+      hashCode = unitAndCSHashCode();
+      hashCode ^= DomainDimension ^ ManifoldDimension ^ Length;
+      for (int j=0; j<ManifoldDimension; j++)
+        hashCode ^= Lengths[j];
+      if (Samples != null)
+        for (int j=0; j<DomainDimension; j++)
+          for (int i=0; i<Length; i++)
+            hashCode ^= new Double(Samples[j][i]).hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                    A clone of this instance.
+   */
+  public Object clone() {
+    Gridded1DDoubleSet clone = (Gridded1DDoubleSet)super.clone();
+    
+    if (Samples != null) {
+      /*
+       * The Samples array is cloned because getDoubles(false) allows clients
+       * to manipulate the array and the general clone() contract forbids
+       * cross-clone contamination.
+       */
+      clone.Samples = (double[][])Samples.clone();
+      for (int i = 0; i < Samples.length; i++)
+        clone.Samples[i] = (double[])Samples[i].clone();
+    }
+    
+    return clone;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new Gridded1DDoubleSet(type, Samples, Length,
+      DomainCoordinateSystem, SetUnits, SetErrors);
+  }
+
+}
+
diff --git a/visad/Gridded1DSet.java b/visad/Gridded1DSet.java
new file mode 100644
index 0000000..71257a5
--- /dev/null
+++ b/visad/Gridded1DSet.java
@@ -0,0 +1,581 @@
+//
+// Gridded1DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.io.InputStreamReader;
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+   Gridded1DSet represents a finite set of samples of R.<P>
+*/
+public class Gridded1DSet extends GriddedSet implements Gridded1DSetIface {
+
+  int LengthX;
+  float LowX, HiX;
+
+  /** Whether this set is ascending or descending */
+  boolean Ascending;
+
+  /**
+   * A canonicalizing cache of previously-created instances.  Because instances
+   * are immutable, a cache can be used to reduce memory usage by ensuring
+   * that each instance is truly unique.  By implementing the cache using a
+   * {@link WeakHashMap}, this can be accomplished without the technique itself
+   * adversely affecting memory usage.
+   */
+  private static final WeakHashMap	cache = new WeakHashMap();
+
+  /**
+   * Constructs a 1-D sorted sequence with no regular interval.  The 
+   * coordinate system and units are the default from the set type.  The error
+   * estimate is null.
+   *
+   * @param type		The type of the set.  Must be a {@link 
+   *				RealType} or a single-component {@link
+   *				RealTupleType} or {@link SetType}.
+   * @param samples             The values in the set.
+   *                            <code>samples[0][i]</code> is the value of
+   *                            the ith sample point.  Must be sorted (either
+   *                            increasing or decreasing).  May be
+   *                            <code>null</code>.
+   * @param lengthX		The number of samples.
+   */
+  public Gridded1DSet(MathType type, float[][] samples, int lengthX)
+         throws VisADException {
+    this(type, samples, lengthX, null, null, null);
+  }
+
+  /**
+   * Constructs a 1-D sorted sequence with no regular interval.
+   *
+   * @param type		The type of the set.  Must be a {@link 
+   *				RealType} or a single-component {@link
+   *				RealTupleType} or {@link SetType}.
+   * @param samples             The values in the set.
+   *                            <code>samples[0][i]</code> is the value of
+   *                            the ith sample point.  Must be sorted (either
+   *                            increasing or decreasing).  May be
+   *                            <code>null</code>.
+   * @param lengthX		The number of samples.
+   * @param coord_sys           The coordinate system for this, particular, set.
+   *                            Must be compatible with the default coordinate
+   *                            system.  May be <code>null</code>.
+   * @param units               The units for the tuple components.  Only
+   *                            <code>units[0]</code> is meaningfull.  Must
+   *                            be compatible with the default unit.  May be
+   *                            <code>null</code>.
+   * @param errors		The error estimates of the tuple components.
+   *				Only <code>errors[0]</code> is meaningful.  May
+   *				be <code>null</code>.
+   */
+  public Gridded1DSet(MathType type, float[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, coord_sys, units, errors, true);
+  }
+
+  /**
+   * Constructs a 1-D sorted sequence with no regular interval.
+   *
+   * @param type		The type of the set.  Must be a {@link 
+   *				RealType} or a single-component {@link
+   *				RealTupleType} or {@link SetType}.
+   * @param samples             The values in the set.
+   *                            <code>samples[0][i]</code> is the value of
+   *                            the ith sample point.  Must be sorted (either
+   *                            increasing or decreasing).  May be
+   *                            <code>null</code>.
+   * @param lengthX		The number of samples.
+   * @param coord_sys           The coordinate system for this, particular, set.
+   *                            Must be compatible with the default coordinate
+   *                            system.  May be <code>null</code>.
+   * @param units               The units for the tuple components.  Only
+   *                            <code>units[0]</code> is meaningfull.  Must
+   *                            be compatible with the default unit.  May be
+   *                            <code>null</code>.
+   * @param errors		The error estimates of the tuple components.
+   *				Only <code>errors[0]</code> is meaningful.  May
+   *				be <code>null</code>.
+   * @param copy		Whether or not to copy the values array.
+   */
+  public Gridded1DSet(MathType type, float[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors, boolean copy)
+                      throws VisADException {
+    super(type, samples, make_lengths(lengthX), coord_sys, units,
+          errors, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+
+    float[][]mySamples = getMySamples();
+    if (mySamples != null && Lengths[0] > 1) {
+      // samples consistency test
+      for (int i=0; i<Length; i++) {
+        if (mySamples[0][i] != mySamples[0][i]) {
+          throw new SetException(
+           "Gridded1DSet: samples values may not be missing");
+        }
+      }
+      Ascending = (mySamples[0][LengthX-1] > mySamples[0][0]);
+      if (Ascending) {
+        for (int i=1; i<LengthX; i++) {
+          if (mySamples[0][i] < mySamples[0][i-1]) {
+            throw new SetException(
+             "Gridded1DSet: samples do not form a valid grid ("+i+")");
+          }
+        }
+      }
+      else { // !Pos
+        for (int i=1; i<LengthX; i++) {
+          if (mySamples[0][i] > mySamples[0][i-1]) {
+            throw new SetException(
+             "Gridded1DSet: samples do not form a valid grid ("+i+")");
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns an instance of this class.  This method uses a weak cache of
+   * previously-created instances to reduce memory usage.
+   *
+   * @param type		The type of the set.  Must be a {@link 
+   *				RealType} or a single-component {@link
+   *				RealTupleType} or {@link SetType}.
+   * @param samples             The values in the set.
+   *                            <code>samples[i]</code> is the value of
+   *                            the ith sample point.  Must be sorted (either
+   *                            increasing or decreasing).  May be
+   *                            <code>null</code>.  The array is not copied, so
+   *				either don't modify it or clone it first.
+   * @param coordSys            The coordinate system for this, particular, set.
+   *                            Must be compatible with the default coordinate
+   *                            system.  May be <code>null</code>.
+   * @param unit                The unit for the samples.  Must be compatible
+   *				with the default unit.  May be 
+   *				<code>null</code>.
+   * @param error		The error estimate of the samples.  May be
+   *				<code>null</code>.
+   */
+  
+  public static synchronized Gridded1DSet create(
+      MathType		type,
+      float[]		samples,
+      CoordinateSystem	coordSys,
+      Unit		unit,
+      ErrorEstimate	error)
+    throws VisADException
+  {
+    Gridded1DSet	newSet =
+      new Gridded1DSet(
+	type, new float[][] {samples}, samples.length, coordSys,
+	new Unit[] {unit}, new ErrorEstimate[] {error}, false);
+    WeakReference	ref = (WeakReference)cache.get(newSet);
+    if (ref == null)
+    {
+      /*
+       * The new instance is unique (any and all previously-created identical
+       * instances no longer exist).
+       *
+       * A WeakReference is used in the following because values of a
+       * WeakHashMap aren't "weak" themselves and must not strongly reference
+       * their associated keys either directly or indirectly.
+       */
+      cache.put(newSet, new WeakReference(newSet));
+    }
+    else
+    {
+      /*
+       * The new instance is a duplicate of a previously-created one.
+       */
+      Gridded1DSet	oldSet = (Gridded1DSet)ref.get();
+      if (oldSet == null)
+      {
+	/* The previous instance no longer exists.  Save the new instance. */
+	cache.put(newSet, new WeakReference(newSet));
+      }
+      else
+      {
+	/* The previous instance still exists.  Reuse it to save memory. */
+	newSet = oldSet;
+      }
+    }
+    return newSet;
+  }
+
+  static int[] make_lengths(int lengthX) {
+    int[] lens = new int[1];
+    lens[0] = lengthX;
+    return lens;
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int length = index.length;
+    float[][]mySamples = getMySamples();
+    if (mySamples == null) {
+      // not used - over-ridden by Linear1DSet.indexToValue
+      float[][] grid = new float[ManifoldDimension][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          grid[0][i] = (float) index[i];
+        }
+        else {
+          grid[0][i] = -1;
+        }
+      }
+      return gridToValue(grid);
+    }
+    else {
+      float[][] values = new float[1][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          values[0][i] = mySamples[0][index[i]];
+        }
+        else {
+          values[0][i] = Float.NaN;
+        }
+      }
+      return values;
+    }
+  }
+
+  /**
+   * Convert an array of values in R^DomainDimension to an array of
+   * 1-D indices.  This Gridded1DSet must have at least two points in the
+   * set.
+   * @param value	An array of coordinates.  <code>value[i][j]
+   *			</code> contains the <code>i</code>th component of the
+   *			<code>j</code>th point.
+   * @return		Indices of nearest points.  RETURN_VALUE<code>[i]</code>
+   *			will contain the index of the point in the set closest
+   *			to <code>value[][i]</code> or <code>-1</code> if
+   *			<code>value[][i]</code> lies outside the set.
+   */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded1DSet.valueToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+
+    float[][] grid = valueToGrid(value);
+    float[] grid0 = grid[0];
+    float g;
+    for (int i=0; i<length; i++) {
+      g = grid0[i];
+      index[i] = Float.isNaN(g) ? -1 : ((int) (g + 0.5));
+    }
+    return index;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    if (grid.length < DomainDimension) {
+      throw new SetException("Gridded1DSet.gridToValue: grid dimension " +
+                             grid.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    /* remove DRM 2004-09-14
+    if (Lengths[0] < 2) {
+      throw new SetException("Gridded1DSet.gridToValue: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    */
+    int length = grid[0].length;
+    float[][] value = new float[1][length];
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<length; i++) {
+      // let g be the current grid coordinate
+      float g = grid[0][i];
+      if ( (g < -0.5) || (g > LengthX-0.5) ) {
+        value[0][i] = Float.NaN;
+      } else if (Length == 1) {  // just return the value if that's all we have
+        value[0][i] = mySamples[0][0];
+      } else {
+        // calculate closest integer variable
+        int ig;
+        if (g < 0) ig = 0;
+        else if (g >= LengthX-1) ig = LengthX - 2;
+        else ig = (int) g;
+        float A = g - ig;  // distance variable
+        // do the linear interpolation
+        value[0][i] = (1-A)*mySamples[0][ig] + A*mySamples[0][ig+1];
+      }
+    }
+    return value;
+  }
+
+  // WLH 6 Dec 2001
+  //private int ig = -1;
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Gridded1DSet.valueToGrid: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    /* remove DRM 2004-09-14
+    if (Lengths[0] < 2) {
+      throw new SetException("Gridded1DSet.valueToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    */
+    float[] vals = value[0];
+    int length = vals.length;
+    float[][]mySamples = getMySamples();
+    float[] samps = mySamples[0];
+    float[][] grid = new float[1][length];
+
+
+    int ig = (LengthX - 1)/2;
+/* WLH 6 Dec 2001
+    // use value from last call as first guess, if reasonable
+    if (ig < 0 || ig >= LengthX) {
+      ig = (LengthX - 1)/2;
+    }
+*/
+
+    for (int i=0; i<length; i++) {
+      if (Float.isNaN(vals[i])) {
+        grid[0][i] = Float.NaN;
+      } else if (Length == 1) {  // just return 0 if that's all we have
+        grid[0][i] = 0;
+      } else {
+	int lower = 0;
+	int upper = LengthX-1;
+	while (lower < upper) {
+	  if ((vals[i] - samps[ig]) * (vals[i] - samps[ig+1]) <= 0) break;
+	  if (Ascending ? samps[ig+1] < vals[i] : samps[ig+1] > vals[i]) {
+	    lower = ig+1;
+          }
+	  else if (Ascending ? samps[ig] > vals[i] : samps[ig] < vals[i]) {
+	    upper = ig;
+          }
+	  if (lower < upper) ig = (lower + upper) / 2;
+	}
+        // Newton's method
+	float solv = ig + (vals[i] - samps[ig]) / (samps[ig+1] - samps[ig]);
+        if (solv > -0.5 && solv < LengthX - 0.5) grid[0][i] = solv;
+        else {
+          grid[0][i] = Float.NaN;
+          // next guess should be in the middle if previous value was missing
+          ig = (LengthX - 1)/2;
+        }
+      }
+    }
+    return grid;
+  }
+
+  public int getLengthX() {
+    return LengthX;
+  }
+
+  public float getLowX() {
+    return LowX;
+  }
+
+  public float getHiX() {
+    return HiX;
+  }
+
+  public boolean isAscending()
+  {
+    return Ascending;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new Gridded1DSet(type, getMySamples(), Length, DomainCoordinateSystem,
+                             SetUnits, SetErrors);
+  }
+
+  /* run 'java visad.Gridded1DSet < formatted_input_stream'
+     to test the Gridded1DSet class */
+  public static void main(String[] args) throws VisADException {
+
+    // Define input stream
+    InputStreamReader inStr = new InputStreamReader(System.in);
+
+    // Define temporary integer array
+    int[] ints = new int[80];
+    try {
+      ints[0] = inStr.read();
+    }
+    catch(Exception e) {
+      System.out.println("Gridded1DSet: "+e);
+    }
+    int l = 0;
+    while (ints[l] != 10) {
+      try {
+        ints[++l] = inStr.read();
+      }
+      catch (Exception e) {
+        System.out.println("Gridded1DSet: "+e);
+      }
+    }
+    // convert array of integers to array of characters
+    char[] chars = new char[l];
+    for (int i=0; i<l; i++) {
+      chars[i] = (char) ints[i];
+    }
+    int num_coords = Integer.parseInt(new String(chars));
+
+    // Define size of Samples array
+    float[][] samp = new float[1][num_coords];
+    System.out.println("num_dimensions = 1, num_coords = "+num_coords+"\n");
+
+    // Skip blank line
+    try {
+      ints[0] = inStr.read();
+    }
+    catch (Exception e) {
+      System.out.println("Gridded1DSet: "+e);
+    }
+
+    for (int c=0; c<num_coords; c++) {
+      l = 0;
+      try {
+        ints[0] = inStr.read();
+      }
+      catch (Exception e) {
+        System.out.println("Gridded1DSet: "+e);
+      }
+      while (ints[l] != 32) {
+        try {
+          ints[++l] = inStr.read();
+        }
+        catch (Exception e) {
+          System.out.println("Gridded1DSet: "+e);
+        }
+      }
+      chars = new char[l];
+      for (int i=0; i<l; i++) {
+        chars[i] = (char) ints[i];
+      }
+      samp[0][c] = (Float.valueOf(new String(chars))).floatValue();
+    }
+
+    // do EOF stuff
+    try {
+      inStr.close();
+    }
+    catch (Exception e) {
+      System.out.println("Gridded1DSet: "+e);
+    }
+
+    // Set up instance of Gridded1DSet
+    RealType vis_data = RealType.getRealType("vis_data");
+    RealType[] vis_array = {vis_data};
+    RealTupleType vis_tuple = new RealTupleType(vis_array);
+    Gridded1DSet gSet1D = new Gridded1DSet(vis_tuple, samp, num_coords);
+
+    System.out.println("Lengths = " + num_coords + " wedge = ");
+    int[] wedge = gSet1D.getWedge();
+    for (int i=0; i<wedge.length; i++) System.out.println(" " + wedge[i]);
+
+    // Print out Samples information
+    System.out.println("Samples ("+gSet1D.LengthX+"):");
+    for (int i=0; i<gSet1D.LengthX; i++) {
+	System.out.println("#"+i+":\t"+gSet1D.getMySamples()[0][i]);
+    }
+
+    // Test gridToValue function
+    System.out.println("\ngridToValue test:");
+    int myLength = gSet1D.LengthX+1;
+    float[][] myGrid = new float[1][myLength];
+    for (int i=0; i<myLength; i++) {
+      myGrid[0][i] = i-0.5f;
+    }
+    myGrid[0][0] += 0.1;          // don't let grid values get too
+    myGrid[0][myLength-1] -= 0.1; // close to interpolation limits
+    float[][] myValue = gSet1D.gridToValue(myGrid);
+    for (int i=0; i<myLength; i++) {
+        System.out.println("("+((float) Math.round(1000000
+                                        *myGrid[0][i]) /1000000)+")\t-->  "
+                              +((float) Math.round(1000000
+                                        *myValue[0][i]) /1000000));
+    }
+
+    // Test valueToGrid function
+    System.out.println("\nvalueToGrid test:");
+    float[][] gridTwo = gSet1D.valueToGrid(myValue);
+    for (int i=0; i<gridTwo[0].length; i++) {
+      System.out.println(((float) Math.round(1000000
+                                  *myValue[0][i]) /1000000)+"  \t-->  ("
+                        +((float) Math.round(1000000
+                                  *gridTwo[0][i]) /1000000)+")");
+    }
+    System.out.println();
+
+  }
+
+/* Here's the output with sample file Gridded1D.txt:
+
+iris 25% java visad.Gridded1DSet < Gridded1D.txt
+num_dimensions = 1, num_coords = 20
+
+Lengths = 20 wedge =
+ 0
+ 1
+. . .
+ 18
+ 19
+Samples (20):
+#0:     -40.54849
+#1:     -39.462048
+. . .
+#18:    26.026154
+#19:    38.3012
+
+gridToValue test:
+(-0.4)  -->  -40.983063
+(0.5)   -->  -40.00527
+. . .
+(18.5)  -->  32.163677
+(19.4)  -->  43.211212
+
+valueToGrid test:
+-40.983063      -->  (-0.399998)
+-40.00527       -->  (0.5)
+. . .
+32.163677       -->  (18.5)
+43.211212       -->  (19.4)
+
+iris 26%
+
+*/
+
+}
+
diff --git a/visad/Gridded1DSetIface.java b/visad/Gridded1DSetIface.java
new file mode 100644
index 0000000..10b1a6f
--- /dev/null
+++ b/visad/Gridded1DSetIface.java
@@ -0,0 +1,34 @@
+//
+// Gridded1DSetIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Gridded1DSetIface is the interface to a finite set of samples of R.<P>
+ */
+public interface Gridded1DSetIface
+  extends GriddedSetIface, Set1DIface
+{}
diff --git a/visad/Gridded2D.txt b/visad/Gridded2D.txt
new file mode 100644
index 0000000..de0f3f2
--- /dev/null
+++ b/visad/Gridded2D.txt
@@ -0,0 +1,22 @@
+20
+
+13.298374 40.239864
+19.182746 40.097643
+26.928734 41.238976
+33.394345 42.983207
+41.390867 41.984323
+12.908347 33.093846
+19.903287 33.127964
+25.329084 33.970834
+35.289376 34.457683
+42.576205 35.230984
+15.309867 24.349032
+22.039875 24.023975
+28.230987 25.290873
+37.230987 25.203987
+44.209873 26.289037
+12.908237 18.129836
+26.021734 17.723958
+36.732923 16.223498
+40.213987 19.230974
+46.293732 18.239872
diff --git a/visad/Gridded2DDoubleSet.java b/visad/Gridded2DDoubleSet.java
new file mode 100644
index 0000000..9902280
--- /dev/null
+++ b/visad/Gridded2DDoubleSet.java
@@ -0,0 +1,936 @@
+//
+// Gridded2DDoubleSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Gridded2DDoubleSet is a Gridded2DSet with double-precision samples.<P>
+*/
+public class Gridded2DDoubleSet extends Gridded2DSet
+       implements GriddedDoubleSet {
+
+  double[] Low = new double[2];
+  double[] Hi = new double[2];
+  double LowX, HiX, LowY, HiY;
+  double[][] Samples;
+
+
+  // Overridden Gridded2DSet constructors (float[][])
+
+  /** a 2-D set whose topology is a lengthX x lengthY grid, with
+      null errors, CoordinateSystem and Units are defaults from type */
+  public Gridded2DDoubleSet(MathType type, float[][] samples, int lengthX, int lengthY)
+         throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY, null, null, null, true);
+  }
+
+  /** a 2-D set whose topology is a lengthX x lengthY grid;
+      samples array is organized float[2][number_of_samples] where
+      lengthX * lengthY = number_of_samples; samples must form a
+      non-degenerate 2-D grid (no bow-tie-shaped grid boxes); the
+      X component increases fastest in the second index of samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded2DDoubleSet(MathType type, float[][] samples, int lengthX, int lengthY,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY, coord_sys,
+         units, errors, true);
+  }
+
+  Gridded2DDoubleSet(MathType type, float[][] samples, int lengthX, int lengthY,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY, coord_sys,
+         units, errors, copy);
+  }
+
+  /** a 2-D set with manifold dimension = 1, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded2DDoubleSet(MathType type, float[][] samples, int lengthX)
+         throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, null, null, null, true);
+  }
+
+  /** a 2-D set with manifold dimension = 1; samples array is
+      organized float[2][number_of_samples] where lengthX =
+      number_of_samples; no geometric constraint on samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded2DDoubleSet(MathType type, float[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, coord_sys, units, errors, true);
+  }
+
+  public Gridded2DDoubleSet(MathType type, float[][] samples, int lengthX,
+                            CoordinateSystem coord_sys, Unit[] units,
+                            ErrorEstimate[] errors, boolean copy)
+                            throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, coord_sys, units, errors, copy);
+  }
+
+
+  // Corresponding Gridded2DDoubleSet constructors (double[][])
+
+  /** a 2-D set whose topology is a lengthX x lengthY grid, with
+      null errors, CoordinateSystem and Units are defaults from type */
+  public Gridded2DDoubleSet(MathType type, double[][] samples, int lengthX, int lengthY)
+         throws VisADException {
+    this(type, samples, lengthX, lengthY, null, null, null, true);
+  }
+
+  /** a 2-D set whose topology is a lengthX x lengthY grid;
+      samples array is organized double[2][number_of_samples] where
+      lengthX * lengthY = number_of_samples; samples must form a
+      non-degenerate 2-D grid (no bow-tie-shaped grid boxes); the
+      X component increases fastest in the second index of samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded2DDoubleSet(MathType type, double[][] samples, int lengthX, int lengthY,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, lengthY, coord_sys, units, errors, true);
+  }
+
+  public Gridded2DDoubleSet(MathType type, double[][] samples, int lengthX, int lengthY,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors, boolean copy) throws VisADException {
+    this(type, samples, lengthX, lengthY, coord_sys, units, errors, copy, true);
+  }
+
+  public Gridded2DDoubleSet(MathType type, double[][] samples, int lengthX,
+                            int lengthY, CoordinateSystem coord_sys, Unit[] units,
+                            ErrorEstimate[] errors, boolean copy, boolean test)
+                            throws VisADException {
+    super(type, null, lengthX, lengthY, coord_sys, units, errors, copy);
+    if (samples == null) {
+      throw new SetException("Gridded2DDoubleSet: samples are null");
+    }
+    init_doubles(samples, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LengthY = Lengths[1];
+
+    if (Samples != null && Lengths[0] > 1 && Lengths[1] > 1) {
+      for (int i=0; i<Length; i++) {
+        if (Samples[0][i] != Samples[0][i]) {
+          throw new SetException(
+           "Gridded2DDoubleSet: samples values may not be missing");
+        }
+      }
+      // Samples consistency test
+/* CICERO
+      Pos = ( (Samples[0][1]-Samples[0][0])
+             *(Samples[1][LengthX+1]-Samples[1][1])
+            - (Samples[1][1]-Samples[1][0])
+             *(Samples[0][LengthX+1]-Samples[0][1]) > 0);
+*/
+// CICERO
+      double xpos = (Samples[0][1]-Samples[0][0])
+                    *(Samples[1][LengthX+1]-Samples[1][1])
+                   - (Samples[1][1]-Samples[1][0])
+                    *(Samples[0][LengthX+1]-Samples[0][1]);
+      Pos = (xpos > 0);
+
+      if (test) {
+
+      // CICERO
+      if (xpos == 0) {
+        throw new SetException(
+         "Gridded2DSet: samples do not form a valid grid");
+      }
+
+      double[] v00 = new double[2];
+      double[] v10 = new double[2];
+      double[] v01 = new double[2];
+      double[] v11 = new double[2];
+
+      for (int j=0; j<LengthY-1; j++) {
+        for (int i=0; i<LengthX-1; i++) {
+          for (int v=0; v<2; v++) {
+            v00[v] = Samples[v][j*LengthX+i];
+            v10[v] = Samples[v][j*LengthX+i+1];
+            v01[v] = Samples[v][(j+1)*LengthX+i];
+            v11[v] = Samples[v][(j+1)*LengthX+i+1];
+          }
+/* CICERO
+            if (  ( (v10[0]-v00[0])*(v11[1]-v10[1])
+                  - (v10[1]-v00[1])*(v11[0]-v10[0]) > 0 != Pos)
+               || ( (v11[0]-v10[0])*(v01[1]-v11[1])
+                  - (v11[1]-v10[1])*(v01[0]-v11[0]) > 0 != Pos)
+               || ( (v01[0]-v11[0])*(v00[1]-v01[1])
+                  - (v01[1]-v11[1])*(v00[0]-v01[0]) > 0 != Pos)
+               || ( (v00[0]-v01[0])*(v10[1]-v00[1])
+                  - (v00[1]-v01[1])*(v10[0]-v00[0]) > 0 != Pos)  ) {
+*/
+// CICERO
+            double w1 = ( (v10[0]-v00[0])*(v11[1]-v10[1])
+                        - (v10[1]-v00[1])*(v11[0]-v10[0]) );
+            double w2 = ( (v11[0]-v10[0])*(v01[1]-v11[1])
+                        - (v11[1]-v10[1])*(v01[0]-v11[0]) );
+            double w3 = ( (v01[0]-v11[0])*(v00[1]-v01[1])
+                        - (v01[1]-v11[1])*(v00[0]-v01[0]) );
+            double w4 = ( (v00[0]-v01[0])*(v10[1]-v00[1])
+                        - (v00[1]-v01[1])*(v10[0]-v00[0]) );
+            if ((w1 > 0 != Pos) || w1 == 0 ||
+                (w2 > 0 != Pos) || w2 == 0 ||
+                (w3 > 0 != Pos) || w3 == 0 ||
+                (w4 > 0 != Pos) || w4 == 0) {
+            throw new SetException(
+             "Gridded2DDoubleSet: samples do not form a valid grid ("+i+","+j+")");
+          }
+        }
+      }
+     } // end if (test)
+    }
+  }
+
+  /** a 2-D set with manifold dimension = 1, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded2DDoubleSet(MathType type, double[][] samples, int lengthX)
+         throws VisADException {
+    this(type, samples, lengthX, null, null, null);
+  }
+
+  /** a 2-D set with manifold dimension = 1; samples array is
+      organized double[2][number_of_samples] where lengthX =
+      number_of_samples; no geometric constraint on samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded2DDoubleSet(MathType type, double[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, coord_sys, units, errors, true);
+  }
+
+  public Gridded2DDoubleSet(MathType type, double[][] samples, int lengthX,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    super(type, null, lengthX, coord_sys, units, errors, copy);
+    if (samples == null) {
+      throw new SetException("Gridded2DDoubleSet: samples are null");
+    }
+    init_doubles(samples, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+
+    // no Samples consistency test
+  }
+
+//END
+
+
+  // Overridden Gridded2DSet methods (float[][])
+
+  public float[][] getSamples() throws VisADException {
+    return getSamples(true);
+  }
+
+  public float[][] getSamples(boolean copy) throws VisADException {
+    return Set.doubleToFloat(Samples);
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    return Set.doubleToFloat(indexToDouble(index));
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    return doubleToIndex(Set.floatToDouble(value));
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    return Set.doubleToFloat(gridToDouble(Set.floatToDouble(grid)));
+  }
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    return Set.doubleToFloat(doubleToGrid(Set.floatToDouble(value)));
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+    float[][] weights) throws VisADException
+  {
+    int len = weights.length;
+    double[][] w = new double[len][];
+    doubleToInterp(Set.floatToDouble(value), indices, w);
+    for (int i=0; i<len; i++) {
+      if (w[i] != null) {
+        weights[i] = new float[w[i].length];
+        for (int j=0; j<w[i].length; j++) {
+          weights[i][j] = (float) w[i][j];
+        }
+      }
+    }
+  }
+
+
+  // Corresponding Gridded2DDoubleSet methods (double[][])
+
+  public double[][] getDoubles() throws VisADException {
+    return getDoubles(true);
+  }
+
+  public double[][] getDoubles(boolean copy) throws VisADException {
+    return copy ? Set.copyDoubles(Samples) : Samples;
+  }
+
+  /** convert an array of 1-D indices to an array of values in
+      R^DomainDimension */
+  public double[][] indexToDouble(int[] index) throws VisADException {
+    int length = index.length;
+    if (Samples == null) {
+      // not used - over-ridden by Linear2DSet.indexToValue
+      int indexX, indexY;
+      double[][] grid = new double[ManifoldDimension][length];
+
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          indexX = index[i] % LengthX;
+          indexY = index[i] / LengthX;
+        }
+        else {
+          indexX = -1;
+          indexY = -1;
+        }
+        grid[0][i] = indexX;
+        grid[1][i] = indexY;
+      }
+      return gridToDouble(grid);
+    }
+    else {
+      double[][] values = new double[2][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          values[0][i] = Samples[0][index[i]];
+          values[1][i] = Samples[1][index[i]];
+        }
+        else {
+          values[0][i] = Double.NaN;
+          values[1][i] = Double.NaN;
+        }
+      }
+      return values;
+    }
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] doubleToIndex(double[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded2DDoubleSet.doubleToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+
+    double[][] grid = doubleToGrid(value);
+    double[] grid0 = grid[0];
+    double[] grid1 = grid[1];
+    double g0, g1;
+    for (int i=0; i<length; i++) {
+      g0 = grid0[i];
+      g1 = grid1[i];
+/* WLH 24 Oct 97
+      index[i] = (Double.isNaN(g0) || Double.isNaN(g1)) ? -1 :
+*/
+      // test for missing
+      index[i] = (g0 != g0 || g1 != g1) ? -1 :
+                 ((int) (g0 + 0.5)) + LengthX * ((int) (g1 + 0.5));
+    }
+    return index;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public double[][] gridToDouble(double[][] grid) throws VisADException {
+    if (grid.length != ManifoldDimension) {
+      throw new SetException("Gridded2DDoubleSet.gridToDouble: bad dimension");
+    }
+    if (ManifoldDimension < 2) {
+      throw new SetException("Gridded2DDoubleSet.gridToDouble: ManifoldDimension " +
+                             "must be 2");
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Gridded2DDoubleSet.gridToDouble: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(grid[0].length, grid[1].length);
+    double[][] value = new double[2][length];
+    for (int i=0; i<length; i++) {
+      // let gx and gy by the current grid values
+      double gx = grid[0][i];
+      double gy = grid[1][i];
+      if ( (gx < -0.5)        || (gy < -0.5) ||
+           (gx > LengthX-0.5) || (gy > LengthY-0.5) ) {
+        value[0][i] = value[1][i] = Double.NaN;
+      } else if (Length == 1) {
+        value[0][i] = Samples[0][0];
+        value[1][i] = Samples[1][0];
+      } else {
+        // calculate closest integer variables
+        int igx = (int) gx;
+        int igy = (int) gy;
+        if (igx < 0) igx = 0;
+        if (igx > LengthX-2) igx = LengthX-2;
+        if (igy < 0) igy = 0;
+        if (igy > LengthY-2) igy = LengthY-2;
+  
+        // set up conversion to 1D Samples array
+        int[][] s = { {LengthX*igy+igx,           // (0, 0)
+                       LengthX*(igy+1)+igx},      // (0, 1)
+                      {LengthX*igy+igx+1,         // (1, 0)
+                       LengthX*(igy+1)+igx+1} };  // (1, 1)
+        if (gx+gy-igx-igy-1 <= 0) {
+          // point is in LOWER triangle
+          for (int j=0; j<2; j++) {
+            value[j][i] = Samples[j][s[0][0]]
+              + (gx-igx)*(Samples[j][s[1][0]]-Samples[j][s[0][0]])
+              + (gy-igy)*(Samples[j][s[0][1]]-Samples[j][s[0][0]]);
+          }
+        }
+        else {
+          // point is in UPPER triangle
+          for (int j=0; j<2; j++) {
+            value[j][i] = Samples[j][s[1][1]]
+              + (1+igx-gx)*(Samples[j][s[0][1]]-Samples[j][s[1][1]])
+              + (1+igy-gy)*(Samples[j][s[1][0]]-Samples[j][s[1][1]]);
+          }
+        }
+      }
+    }
+    return value;
+  }
+
+  // WLH 6 Dec 2001
+  //private int gx = -1;
+  //private int gy = -1;
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public double[][] doubleToGrid(double[][] value) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Gridded2DDoubleSet.doubleToGrid: bad dimension");
+    }
+    if (ManifoldDimension < 2) {
+      throw new SetException("Gridded2DDoubleSet.doubleToGrid: ManifoldDimension " +
+                             "must be 2");
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Gridded2DDoubleSet.doubleToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = Math.min(value[0].length, value[1].length);
+    double[][] grid = new double[ManifoldDimension][length];
+
+    // (gx, gy) is the current grid box guess
+    int gx = (LengthX-1)/2;
+    int gy = (LengthY-1)/2;
+/* WLH 6 Dec 2001
+    // use value from last call as first guess, if reasonable
+    if (gx < 0 || gx >= LengthX || gy < 0 || gy >= LengthY) {
+      gx = (LengthX-1)/2;
+      gy = (LengthY-1)/2;
+    }
+*/
+
+    boolean lowertri = true;
+    for (int i=0; i<length; i++) {
+      // grid box guess starts at previous box unless there was no solution
+/* WLH 24 Oct 97
+      if ( (i != 0) && (Double.isNaN(grid[0][i-1])) )
+*/
+
+      if (Length == 1) {
+        if (Double.isNaN(value[0][i]) || Double.isNaN(value[1][i])) {
+           grid[0][i] = grid[1][i] = Double.NaN;
+        } else {
+           grid[0][i] = 0;
+           grid[1][i] = 0;
+        }
+        continue;
+      }
+
+      // test for missing
+      if ( (i != 0) && grid[0][i-1] != grid[0][i-1] ) {
+        gx = (LengthX-1)/2;
+        gy = (LengthY-1)/2;
+      }
+
+      // if the loop doesn't find the answer, the result should be NaN
+      grid[0][i] = grid[1][i] = Double.NaN;
+      for (int itnum=0; itnum<2*(LengthX+LengthY); itnum++) {
+        // define the four vertices of the current grid box
+        double[] v0 = {Samples[0][gy*LengthX+gx],
+                       Samples[1][gy*LengthX+gx]};
+        double[] v1 = {Samples[0][gy*LengthX+gx+1],
+                       Samples[1][gy*LengthX+gx+1]};
+        double[] v2 = {Samples[0][(gy+1)*LengthX+gx],
+                       Samples[1][(gy+1)*LengthX+gx]};
+        double[] v3 = {Samples[0][(gy+1)*LengthX+gx+1],
+                       Samples[1][(gy+1)*LengthX+gx+1]};
+
+        // Both cases use diagonal D-B and point distances P-B and P-D
+        double[] bd = {v2[0]-v1[0], v2[1]-v1[1]};
+        double[] bp = {value[0][i]-v1[0], value[1][i]-v1[1]};
+        double[] dp = {value[0][i]-v2[0], value[1][i]-v2[1]};
+
+        // check the LOWER triangle of the grid box
+        if (lowertri) {
+          double[] ab = {v1[0]-v0[0], v1[1]-v0[1]};
+          double[] da = {v0[0]-v2[0], v0[1]-v2[1]};
+          double[] ap = {value[0][i]-v0[0], value[1][i]-v0[1]};
+          double tval1 = ab[0]*ap[1]-ab[1]*ap[0];
+          double tval2 = bd[0]*bp[1]-bd[1]*bp[0];
+          double tval3 = da[0]*dp[1]-da[1]*dp[0];
+          boolean test1 = (tval1 == 0) || ((tval1 > 0) == Pos);
+          boolean test2 = (tval2 == 0) || ((tval2 > 0) == Pos);
+          boolean test3 = (tval3 == 0) || ((tval3 > 0) == Pos);
+          int ogx = gx;
+          int ogy = gy;
+          if (!test1 && !test2) {      // Go UP & RIGHT
+            gx++;
+            gy--;
+          }
+          else if (!test2 && !test3) { // Go DOWN & LEFT
+            gx--;
+            gy++;
+          }
+          else if (!test1 && !test3) { // Go UP & LEFT
+            gx--;
+            gy--;
+          }
+          else if (!test1) {           // Go UP
+            gy--;
+          }
+          else if (!test3) {           // Go LEFT
+            gx--;
+          }
+          // Snap guesses back into the grid
+          if (gx < 0) gx = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy < 0) gy = 0;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if ( (gx == ogx) && (gy == ogy) && (test2) ) {
+            // Found correct grid triangle
+            // Solve the point with the reverse interpolation
+            grid[0][i] = ((value[0][i]-v0[0])*(v2[1]-v0[1])
+                        + (v0[1]-value[1][i])*(v2[0]-v0[0]))
+                       / ((v1[0]-v0[0])*(v2[1]-v0[1])
+                        + (v0[1]-v1[1])*(v2[0]-v0[0])) + gx;
+            grid[1][i] = ((value[0][i]-v0[0])*(v1[1]-v0[1])
+                        + (v0[1]-value[1][i])*(v1[0]-v0[0]))
+                       / ((v2[0]-v0[0])*(v1[1]-v0[1])
+                        + (v0[1]-v2[1])*(v1[0]-v0[0])) + gy;
+            break;
+          }
+          else {
+            lowertri = false;
+          }
+        }
+
+        // check the UPPER triangle of the grid box
+        else {
+          double[] bc = {v3[0]-v1[0], v3[1]-v1[1]};
+          double[] cd = {v2[0]-v3[0], v2[1]-v3[1]};
+          double[] cp = {value[0][i]-v3[0], value[1][i]-v3[1]};
+          double tval1 = bc[0]*bp[1]-bc[1]*bp[0];
+          double tval2 = cd[0]*cp[1]-cd[1]*cp[0];
+          double tval3 = bd[0]*dp[1]-bd[1]*dp[0];
+          boolean test1 = (tval1 == 0) || ((tval1 > 0) == Pos);
+          boolean test2 = (tval2 == 0) || ((tval2 > 0) == Pos);
+          boolean test3 = (tval3 == 0) || ((tval3 < 0) == Pos);
+          int ogx = gx;
+          int ogy = gy;
+          if (!test1 && !test3) {      // Go UP & RIGHT
+            gx++;
+            gy--;
+          }
+          else if (!test2 && !test3) { // Go DOWN & LEFT
+            gx--;
+            gy++;
+          }
+          else if (!test1 && !test2) { // Go DOWN & RIGHT
+            gx++;
+            gy++;
+          }
+          else if (!test1) {           // Go RIGHT
+            gx++;
+          }
+          else if (!test2) {           // Go DOWN
+            gy++;
+          }
+          // Snap guesses back into the grid
+          if (gx < 0) gx = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy < 0) gy = 0;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if ( (gx == ogx) && (gy == ogy) && (test3) ) {
+            // Found correct grid triangle
+            // Solve the point with the reverse interpolation
+            grid[0][i] = ((v3[0]-value[0][i])*(v1[1]-v3[1])
+                        + (value[1][i]-v3[1])*(v1[0]-v3[0]))
+                       / ((v2[0]-v3[0])*(v1[1]-v3[1])
+                        - (v2[1]-v3[1])*(v1[0]-v3[0])) + gx + 1;
+            grid[1][i] = ((v2[1]-v3[1])*(v3[0]-value[0][i])
+                        + (v2[0]-v3[0])*(value[1][i]-v3[1]))
+                       / ((v1[0]-v3[0])*(v2[1]-v3[1])
+                        - (v2[0]-v3[0])*(v1[1]-v3[1])) + gy + 1;
+            break;
+          }
+          else {
+            lowertri = true;
+          }
+        }
+      }
+      if ( (grid[0][i] >= LengthX-0.5) || (grid[1][i] >= LengthY-0.5)
+        || (grid[0][i] <= -0.5) || (grid[1][i] <= -0.5) ) {
+        grid[0][i] = grid[1][i] = Double.NaN;
+      }
+    }
+    return grid;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void doubleToInterp(double[][] value, int[][] indices,
+    double[][] weights) throws VisADException
+  {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded2DDoubleSet.doubleToInterp: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length) {
+      throw new SetException("Gridded2DDoubleSet.valueToInterp: indices length " +
+                             indices.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    if (weights.length != length) {
+      throw new SetException("Gridded2DDoubleSet.valueToInterp: weights length " +
+                             weights.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    // convert value array to grid coord array
+    double[][] grid = doubleToGrid(value);
+
+    int i, j, k; // loop indices
+    int lis; // temporary length of is & cs
+    int length_is; // final length of is & cs, varies by i
+    int isoff; // offset along one grid dimension
+    double a, b; // weights along one grid dimension; a + b = 1.0
+    int[] is; // array of indices, becomes part of indices
+    double[] cs; // array of coefficients, become part of weights
+
+    int base; // base index, as would be returned by valueToIndex
+    int[] l = new int[ManifoldDimension]; // integer 'factors' of base
+    // fractions with l; -0.5 <= c <= 0.5
+    double[] c = new double[ManifoldDimension];
+
+    // array of index offsets by grid dimension
+    int[] off = new int[ManifoldDimension];
+    off[0] = 1;
+    for (j=1; j<ManifoldDimension; j++) off[j] = off[j-1] * Lengths[j-1];
+
+    for (i=0; i<length; i++) {
+      // compute length_is, base, l & c
+      length_is = 1;
+      if (Double.isNaN(grid[ManifoldDimension-1][i])) {
+        base = -1;
+      }
+      else {
+        l[ManifoldDimension-1] = (int) (grid[ManifoldDimension-1][i] + 0.5);
+        // WLH 23 Dec 99
+        if (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1]) {
+          l[ManifoldDimension-1]--;
+        }
+        c[ManifoldDimension-1] = grid[ManifoldDimension-1][i] -
+                                 ((double) l[ManifoldDimension-1]);
+        if (!((l[ManifoldDimension-1] == 0 && c[ManifoldDimension-1] <= 0.0) ||
+              (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1] - 1 &&
+               c[ManifoldDimension-1] >= 0.0))) {
+          // only interp along ManifoldDimension-1
+          // if between two valid grid coords
+          length_is *= 2;
+        }
+        base = l[ManifoldDimension-1];
+      }
+      for (j=ManifoldDimension-2; j>=0 && base>=0; j--) {
+        if (Double.isNaN(grid[j][i])) {
+          base = -1;
+        }
+        else {
+          l[j] = (int) (grid[j][i] + 0.5);
+          if (l[j] == Lengths[j]) l[j]--; // WLH 23 Dec 99
+          c[j] = grid[j][i] - ((double) l[j]);
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            length_is *= 2;
+          }
+          base = l[j] + Lengths[j] * base;
+        }
+      }
+
+      if (base < 0) {
+        // value is out of grid so return null
+        is = null;
+        cs = null;
+      }
+      else {
+        // create is & cs of proper length, and init first element
+        is = new int[length_is];
+        cs = new double[length_is];
+        is[0] = base;
+        cs[0] = 1.0f;
+        lis = 1;
+
+        for (j=0; j<ManifoldDimension; j++) {
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            if (c[j] >= 0.0) {
+              // grid coord above base
+              isoff = off[j];
+              a = 1.0f - c[j];
+              b = c[j];
+            }
+            else {
+              // grid coord below base
+              isoff = -off[j];
+              a = 1.0f + c[j];
+              b = -c[j];
+            }
+            // double is & cs; adjust new offsets; split weights
+            for (k=0; k<lis; k++) {
+              is[k+lis] = is[k] + isoff;
+              cs[k+lis] = cs[k] * b;
+              cs[k] *= a;
+            }
+            lis *= 2;
+          }
+        }
+      }
+      indices[i] = is;
+      weights[i] = cs;
+    }
+  }
+
+
+  // Miscellaneous Set methods that must be overridden
+  // (this code is duplicated throughout all *DoubleSet classes)
+
+  void init_doubles(double[][] samples, boolean copy)
+       throws VisADException {
+    if (samples.length != DomainDimension) {
+      throw new SetException("Gridded2DDoubleSet.init_doubles: samples " +
+                             " dimension " + samples.length +
+                             " not equal to domain dimension " +
+                             DomainDimension);
+    }
+    if (Length == 0) {
+      // Length set in init_lengths, but not called for IrregularSet
+      Length = samples[0].length;
+    }
+    else {
+      if (Length != samples[0].length) {
+        throw new SetException("Gridded2DDoubleSet.init_doubles: " +
+                               "samples[0] length " + samples[0].length +
+                               " doesn't match expected length " + Length);
+      }
+    }
+    // MEM
+    if (copy) {
+      Samples = new double[DomainDimension][Length];
+    }
+    else {
+      Samples = samples;
+    }
+    for (int j=0; j<DomainDimension; j++) {
+      if (samples[j].length != Length) {
+        throw new SetException("Gridded2DDoubleSet.init_doubles: " +
+                               "samples[" + j + "] length " +
+                               samples[0].length +
+                               " doesn't match expected length " + Length);
+      }
+      double[] samplesJ = samples[j];
+      double[] SamplesJ = Samples[j];
+      if (copy) {
+        System.arraycopy(samplesJ, 0, SamplesJ, 0, Length);
+      }
+      Low[j] = Double.POSITIVE_INFINITY;
+      Hi[j] = Double.NEGATIVE_INFINITY;
+      double sum = 0.0f;
+      for (int i=0; i<Length; i++) {
+        if (SamplesJ[i] == SamplesJ[i] && !Double.isInfinite(SamplesJ[i])) {
+          if (SamplesJ[i] < Low[j]) Low[j] = SamplesJ[i];
+          if (SamplesJ[i] > Hi[j]) Hi[j] = SamplesJ[i];
+        }
+        else {
+          SamplesJ[i] = Double.NaN;
+        }
+        sum += SamplesJ[i];
+      }
+      if (SetErrors[j] != null ) {
+        SetErrors[j] =
+          new ErrorEstimate(SetErrors[j].getErrorValue(), sum / Length,
+                            Length, SetErrors[j].getUnit());
+      }
+      super.Low[j] = (float) Low[j];
+      super.Hi[j] = (float) Hi[j];
+    }
+  }
+
+  public void cram_missing(boolean[] range_select) {
+    int n = Math.min(range_select.length, Samples[0].length);
+    for (int i=0; i<n; i++) {
+      if (!range_select[i]) Samples[0][i] = Double.NaN;
+    }
+  }
+
+  public boolean isMissing() {
+    return (Samples == null);
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof Gridded2DDoubleSet) || set == null) return false;
+    if (this == set) return true;
+    if (testNotEqualsCache((Set) set)) return false;
+    if (testEqualsCache((Set) set)) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    try {
+      int i, j;
+      if (DomainDimension != ((Gridded2DDoubleSet) set).getDimension() ||
+          ManifoldDimension !=
+            ((Gridded2DDoubleSet) set).getManifoldDimension() ||
+          Length != ((Gridded2DDoubleSet) set).getLength()) return false;
+      for (j=0; j<ManifoldDimension; j++) {
+        if (Lengths[j] != ((Gridded2DDoubleSet) set).getLength(j)) {
+          return false;
+        }
+      }
+      // Sets are immutable, so no need for 'synchronized'
+      double[][] samples = ((Gridded2DDoubleSet) set).getDoubles(false);
+      if (Samples != null && samples != null) {
+        for (j=0; j<DomainDimension; j++) {
+          for (i=0; i<Length; i++) {
+            if (Samples[j][i] != samples[j][i]) {
+              addNotEqualsCache((Set) set);
+              return false;
+            }
+          }
+        }
+      }
+      else {
+        double[][] this_samples = getDoubles(false);
+        if (this_samples == null) {
+          if (samples != null) {
+            return false;
+          }
+        } else if (samples == null) {
+          return false;
+        } else {
+          for (j=0; j<DomainDimension; j++) {
+            for (i=0; i<Length; i++) {
+              if (this_samples[j][i] != samples[j][i]) {
+                addNotEqualsCache((Set) set);
+                return false;
+              }
+            }
+          }
+        }
+      }
+      addEqualsCache((Set) set);
+      return true;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                    A clone of this instance.
+   */
+  public Object clone() {
+    Gridded2DDoubleSet clone = (Gridded2DDoubleSet)super.clone();
+    
+    if (Samples != null) {
+      /*
+       * The Samples array is cloned because getDoubles(false) allows clients
+       * to manipulate the array and the general clone() contract forbids
+       * cross-clone contamination.
+       */
+      clone.Samples = (double[][])Samples.clone();
+      for (int i = 0; i < Samples.length; i++)
+        clone.Samples[i] = (double[])Samples[i].clone();
+    }
+    
+    return clone;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    if (ManifoldDimension == 2) {
+      return new Gridded2DDoubleSet(type, Samples, LengthX, LengthY,
+                              DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+    else {
+      return new Gridded2DDoubleSet(type, Samples, LengthX,
+                              DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+  }
+/* WLH 3 April 2003
+  public Object cloneButType(MathType type) throws VisADException {
+    return new Gridded2DDoubleSet(type, Samples, Length,
+      DomainCoordinateSystem, SetUnits, SetErrors);
+  }
+*/
+}
+
diff --git a/visad/Gridded2DSet.java b/visad/Gridded2DSet.java
new file mode 100644
index 0000000..665c389
--- /dev/null
+++ b/visad/Gridded2DSet.java
@@ -0,0 +1,757 @@
+//
+// Gridded2DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.io.*;
+
+/**
+   Gridded2DSet represents a finite set of samples of R^2.<P>
+*/
+public class Gridded2DSet extends GriddedSet {
+
+  int LengthX, LengthY;
+  float LowX, HiX, LowY, HiY;
+
+  /** a 2-D set whose topology is a lengthX x lengthY grid, with
+      null errors, CoordinateSystem and Units are defaults from type */
+  public Gridded2DSet(MathType type, float[][] samples, int lengthX, int lengthY)
+         throws VisADException {
+    this(type, samples, lengthX, lengthY, null, null, null);
+  }
+
+  /** a 2-D set whose topology is a lengthX x lengthY grid;
+      samples array is organized float[2][number_of_samples] where
+      lengthX * lengthY = number_of_samples; samples must form a
+      non-degenerate 2-D grid (no bow-tie-shaped grid boxes); the
+      X component increases fastest in the second index of samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded2DSet(MathType type, float[][] samples, int lengthX, int lengthY,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, lengthY, coord_sys, units, errors,
+         true, true);
+  }
+
+  public Gridded2DSet(MathType type, float[][] samples, int lengthX, int lengthY,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    this(type, samples, lengthX, lengthY, coord_sys, units, errors,
+         copy, true);
+  }
+
+  public Gridded2DSet(MathType type, float[][] samples, int lengthX, int lengthY,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy, boolean test)
+               throws VisADException {
+    super(type, samples, make_lengths(lengthX, lengthY), coord_sys,
+          units, errors, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LengthY = Lengths[1];
+
+    float[][]mySamples = getMySamples();
+    if (mySamples != null && Lengths[0] > 1 && Lengths[1] > 1) {
+/* CICERO
+      Pos = ( (mySamples[0][1]-mySamples[0][0])
+               *(mySamples[1][LengthX+1]-mySamples[1][1])
+              - (mySamples[1][1]-mySamples[1][0])
+               *(mySamples[0][LengthX+1]-mySamples[0][1]) > 0);
+*/
+// CICERO
+      float xpos = (mySamples[0][1]-mySamples[0][0])
+                    *(mySamples[1][LengthX+1]-mySamples[1][1])
+                   - (mySamples[1][1]-mySamples[1][0])
+                    *(mySamples[0][LengthX+1]-mySamples[0][1]);
+      Pos = (xpos > 0);
+
+      if (test) {
+        // CICERO
+        if (xpos == 0) {
+          throw new SetException(
+           "Gridded2DSet: samples do not form a valid grid");
+        }
+
+        for (int i=0; i<Length; i++) {
+          if (mySamples[0][i] != mySamples[0][i]) {
+            throw new SetException(
+             "Gridded2DSet: samples value #" + i + " may not be missing");
+          }
+        }
+
+        float[] v00 = new float[2];
+        float[] v10 = new float[2];
+        float[] v01 = new float[2];
+        float[] v11 = new float[2];
+
+
+        // Samples consistency test
+        for (int j=0; j<LengthY-1; j++) {
+          for (int i=0; i<LengthX-1; i++) {
+            for (int v=0; v<2; v++) {
+              v00[v] = mySamples[v][j*LengthX+i];
+              v10[v] = mySamples[v][j*LengthX+i+1];
+              v01[v] = mySamples[v][(j+1)*LengthX+i];
+              v11[v] = mySamples[v][(j+1)*LengthX+i+1];
+            }
+/* CICERO
+            if (  ( (v10[0]-v00[0])*(v11[1]-v10[1])
+                  - (v10[1]-v00[1])*(v11[0]-v10[0]) > 0 != Pos)
+               || ( (v11[0]-v10[0])*(v01[1]-v11[1])
+                  - (v11[1]-v10[1])*(v01[0]-v11[0]) > 0 != Pos)
+               || ( (v01[0]-v11[0])*(v00[1]-v01[1])
+                  - (v01[1]-v11[1])*(v00[0]-v01[0]) > 0 != Pos)
+               || ( (v00[0]-v01[0])*(v10[1]-v00[1])
+                  - (v00[1]-v01[1])*(v10[0]-v00[0]) > 0 != Pos)  ) {
+*/
+// CICERO
+            float w1 = ( (v10[0]-v00[0])*(v11[1]-v10[1])
+                       - (v10[1]-v00[1])*(v11[0]-v10[0]) );
+            float w2 = ( (v11[0]-v10[0])*(v01[1]-v11[1])
+                       - (v11[1]-v10[1])*(v01[0]-v11[0]) );
+            float w3 = ( (v01[0]-v11[0])*(v00[1]-v01[1])
+                       - (v01[1]-v11[1])*(v00[0]-v01[0]) );
+            float w4 = ( (v00[0]-v01[0])*(v10[1]-v00[1])
+                       - (v00[1]-v01[1])*(v10[0]-v00[0]) );
+            if ((w1 > 0 != Pos) || w1 == 0 ||
+                (w2 > 0 != Pos) || w2 == 0 ||
+                (w3 > 0 != Pos) || w3 == 0 ||
+                (w4 > 0 != Pos) || w4 == 0) {
+/*
+System.out.println("mySamples[0][1] = " + mySamples[0][1] +
+                   " mySamples[0][0] = " + mySamples[0][0] +
+                   " mySamples[1][LengthX+1] = " + mySamples[1][LengthX+1] +
+                   " mySamples[1][1] = " + mySamples[1][1]);
+System.out.println("mySamples[1][1] = " + mySamples[1][1] +
+                   " mySamples[1][0] = " + mySamples[1][0] +
+                   " mySamples[0][LengthX+1] = " + mySamples[0][LengthX+1] +
+                   " mySamples[0][1] = " + mySamples[0][1]);
+System.out.println("v00[]=mySamples[]["+(j*LengthX+i)+"] " +
+                   "v10[]=mySamples[]["+(j*LengthX+i+1)+"] " +
+                   "v01[]=mySamples[]["+((j+1)*LengthX+i)+"] " +
+                   "v11[]=mySamples[]["+((j+1)*LengthX+i+1)+"]");
+System.out.println("Pos = " + Pos);
+System.out.println("1st = " + ( (v10[0]-v00[0])*(v11[1]-v10[1])
+                              - (v10[1]-v00[1])*(v11[0]-v10[0]) ) +
+                  " 2nd = " + ( (v11[0]-v10[0])*(v01[1]-v11[1])
+                              - (v11[1]-v10[1])*(v01[0]-v11[0]) ) +
+                  " 3rd = " + ( (v01[0]-v11[0])*(v00[1]-v01[1])
+                              - (v01[1]-v11[1])*(v00[0]-v01[0]) ) +
+                  " 4th = " + ( (v00[0]-v01[0])*(v10[1]-v00[1])
+                              - (v00[1]-v01[1])*(v10[0]-v00[0]) ) );
+*/
+              throw new SetException(
+               "Gridded2DSet: samples do not form a valid grid ("+i+","+j+")");
+            }
+          }
+        }
+      } // end if (test)
+    }
+  }
+
+  /** a 2-D set with manifold dimension = 1, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded2DSet(MathType type, float[][] samples, int lengthX)
+         throws VisADException {
+    this(type, samples, lengthX, null, null, null);
+  }
+
+  /** a 2-D set with manifold dimension = 1; samples array is
+      organized float[2][number_of_samples] where lengthX =
+      number_of_samples; no geometric constraint on samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded2DSet(MathType type, float[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, coord_sys, units, errors, true);
+  }
+
+  public Gridded2DSet(MathType type, float[][] samples, int lengthX,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    super(type, samples, Gridded1DSet.make_lengths(lengthX),
+          coord_sys, units, errors, copy);
+
+    if (DomainDimension != 2) {
+      throw new SetException("Gridded2DSet Domain dimension" +
+                             " should be 2, not " + DomainDimension);
+    }
+
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+
+    // no Samples consistency test
+  }
+
+  static int[] make_lengths(int lengthX, int lengthY) {
+    int[] lens = new int[2];
+    lens[0] = lengthX;
+    lens[1] = lengthY;
+    return lens;
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int length = index.length;
+    float[][]mySamples = getMySamples();
+    if (mySamples == null) {
+      // not used - over-ridden by Linear2DSet.indexToValue
+      int indexX, indexY;
+      float[][] grid = new float[ManifoldDimension][length];
+
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          indexX = index[i] % LengthX;
+          indexY = index[i] / LengthX;
+        }
+        else {
+          indexX = -1;
+          indexY = -1;
+        }
+        grid[0][i] = (float) indexX;
+        grid[1][i] = (float) indexY;
+      }
+      return gridToValue(grid);
+    }
+    else {
+      float[][] values = new float[2][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          values[0][i] = mySamples[0][index[i]];
+          values[1][i] = mySamples[1][index[i]];
+        }
+        else {
+          values[0][i] = Float.NaN;
+          values[1][i] = Float.NaN;
+        }
+      }
+      return values;
+    }
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded2DSet.valueToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+
+    float[][] grid = valueToGrid(value);
+    float[] grid0 = grid[0];
+    float[] grid1 = grid[1];
+    float g0, g1;
+    for (int i=0; i<length; i++) {
+      g0 = grid0[i];
+      g1 = grid1[i];
+/* WLH 24 Oct 97
+      index[i] = (Float.isNaN(g0) || Float.isNaN(g1)) ? -1 :
+*/
+      // test for missing
+      index[i] = (g0 != g0 || g1 != g1) ? -1 :
+                 ((int) (g0 + 0.5)) + LengthX * ((int) (g1 + 0.5));
+    }
+    return index;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    if (grid.length != ManifoldDimension) {
+      throw new SetException("Gridded2DSet.gridToValue: grid dimension " +
+                             grid.length +
+                             " not equal to Manifold dimension " +
+                             ManifoldDimension);
+    }
+    if (ManifoldDimension < 2) {
+      throw new SetException("Gridded2DSet.gridToValue: Manifold dimension " +
+                             "must be 2, not " + ManifoldDimension);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Gridded2DSet.gridToValue: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    float[][]mySamples = getMySamples();
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(grid[0].length, grid[1].length);
+    float[][] value = new float[2][length];
+    for (int i=0; i<length; i++) {
+      // let gx and gy by the current grid values
+      float gx = grid[0][i];
+      float gy = grid[1][i];
+      if ( (gx < -0.5)        || (gy < -0.5) ||
+           (gx > LengthX-0.5) || (gy > LengthY-0.5) ) {
+        value[0][i] = value[1][i] = Float.NaN;
+      } else if (Length == 1) {
+        value[0][i] = mySamples[0][0];
+        value[1][i] = mySamples[1][0];
+      } else {
+        // calculate closest integer variables
+        int igx = (int) gx;
+        int igy = (int) gy;
+        if (igx < 0) igx = 0;
+        if (igx > LengthX-2) igx = LengthX-2;
+        if (igy < 0) igy = 0;
+        if (igy > LengthY-2) igy = LengthY-2;
+  
+        // set up conversion to 1D Samples array
+        int[][] s = { {LengthX*igy+igx,           // (0, 0)
+                       LengthX*(igy+1)+igx},      // (0, 1)
+                      {LengthX*igy+igx+1,         // (1, 0)
+                       LengthX*(igy+1)+igx+1} };  // (1, 1)
+        if (gx+gy-igx-igy-1 <= 0) {
+          // point is in LOWER triangle
+          for (int j=0; j<2; j++) {
+            value[j][i] = mySamples[j][s[0][0]]
+              + (gx-igx)*(mySamples[j][s[1][0]]-mySamples[j][s[0][0]])
+              + (gy-igy)*(mySamples[j][s[0][1]]-mySamples[j][s[0][0]]);
+          }
+        }
+        else {
+          // point is in UPPER triangle
+          for (int j=0; j<2; j++) {
+            value[j][i] = mySamples[j][s[1][1]]
+              + (1+igx-gx)*(mySamples[j][s[0][1]]-mySamples[j][s[1][1]])
+              + (1+igy-gy)*(mySamples[j][s[1][0]]-mySamples[j][s[1][1]]);
+          }
+        }
+      }
+    }
+    return value;
+  }
+
+  // WLH 6 Dec 2001
+  //private int gx = -1;
+  //private int gy = -1;
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    float[][]mySamples = getMySamples();
+    if (value.length < DomainDimension) {
+      throw new SetException("Gridded2DSet.valueToGrid: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    if (ManifoldDimension < 2) {
+      throw new SetException("Gridded2DSet.valueToGrid: Manifold dimension " +
+                             "must be 2, not " + ManifoldDimension);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Gridded2DSet.valueToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = Math.min(value[0].length, value[1].length);
+    float[][] grid = new float[ManifoldDimension][length];
+
+    // (gx, gy) is the current grid box guess
+    int gx = (LengthX-1)/2;
+    int gy = (LengthY-1)/2;
+/* WLH 6 Dec 2001
+    // use value from last call as first guess, if reasonable
+    if (gx < 0 || gx >= LengthX || gy < 0 || gy >= LengthY) {
+      gx = (LengthX-1)/2;
+      gy = (LengthY-1)/2;
+    }
+*/
+
+    boolean lowertri = true;
+    for (int i=0; i<length; i++) {
+      // grid box guess starts at previous box unless there was no solution
+/* WLH 24 Oct 97
+      if ( (i != 0) && (Float.isNaN(grid[0][i-1])) )
+*/
+      if (Length == 1) {
+        if (Float.isNaN(value[0][i]) || Float.isNaN(value[1][i])) {
+           grid[0][i] = grid[1][i] = Float.NaN;
+        } else {
+           grid[0][i] = 0;
+           grid[1][i] = 0;
+        }
+        continue;
+      }
+
+      // test for missing
+      if ( (i != 0) && grid[0][i-1] != grid[0][i-1] ) {
+        gx = (LengthX-1)/2;
+        gy = (LengthY-1)/2;
+      }
+      // if the loop doesn't find the answer, the result should be NaN
+      grid[0][i] = grid[1][i] = Float.NaN;
+      for (int itnum=0; itnum<2*(LengthX+LengthY); itnum++) {
+        // define the four vertices of the current grid box
+        float[] v0 = {mySamples[0][gy*LengthX+gx],
+                       mySamples[1][gy*LengthX+gx]};
+        float[] v1 = {mySamples[0][gy*LengthX+gx+1],
+                       mySamples[1][gy*LengthX+gx+1]};
+        float[] v2 = {mySamples[0][(gy+1)*LengthX+gx],
+                       mySamples[1][(gy+1)*LengthX+gx]};
+        float[] v3 = {mySamples[0][(gy+1)*LengthX+gx+1],
+                       mySamples[1][(gy+1)*LengthX+gx+1]};
+
+        // Both cases use diagonal D-B and point distances P-B and P-D
+        float[] bd = {v2[0]-v1[0], v2[1]-v1[1]};
+        float[] bp = {value[0][i]-v1[0], value[1][i]-v1[1]};
+        float[] dp = {value[0][i]-v2[0], value[1][i]-v2[1]};
+
+        // check the LOWER triangle of the grid box
+        if (lowertri) {
+          float[] ab = {v1[0]-v0[0], v1[1]-v0[1]};
+          float[] da = {v0[0]-v2[0], v0[1]-v2[1]};
+          float[] ap = {value[0][i]-v0[0], value[1][i]-v0[1]};
+          float tval1 = ab[0]*ap[1]-ab[1]*ap[0];
+          float tval2 = bd[0]*bp[1]-bd[1]*bp[0];
+          float tval3 = da[0]*dp[1]-da[1]*dp[0];
+          boolean test1 = (tval1 == 0) || ((tval1 > 0) == Pos);
+          boolean test2 = (tval2 == 0) || ((tval2 > 0) == Pos);
+          boolean test3 = (tval3 == 0) || ((tval3 > 0) == Pos);
+          int ogx = gx;
+          int ogy = gy;
+          if (!test1 && !test2) {      // Go UP & RIGHT
+            gx++;
+            gy--;
+          }
+          else if (!test2 && !test3) { // Go DOWN & LEFT
+            gx--;
+            gy++;
+          }
+          else if (!test1 && !test3) { // Go UP & LEFT
+            gx--;
+            gy--;
+          }
+          else if (!test1) {           // Go UP
+            gy--;
+          }
+          else if (!test3) {           // Go LEFT
+            gx--;
+          }
+          // Snap guesses back into the grid
+          if (gx < 0) gx = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy < 0) gy = 0;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if ( (gx == ogx) && (gy == ogy) && (test2) ) {
+            // Found correct grid triangle
+            // Solve the point with the reverse interpolation
+            grid[0][i] = ((value[0][i]-v0[0])*(v2[1]-v0[1])
+                        + (v0[1]-value[1][i])*(v2[0]-v0[0]))
+                       / ((v1[0]-v0[0])*(v2[1]-v0[1])
+                        + (v0[1]-v1[1])*(v2[0]-v0[0])) + gx;
+            grid[1][i] = ((value[0][i]-v0[0])*(v1[1]-v0[1])
+                        + (v0[1]-value[1][i])*(v1[0]-v0[0]))
+                       / ((v2[0]-v0[0])*(v1[1]-v0[1])
+                        + (v0[1]-v2[1])*(v1[0]-v0[0])) + gy;
+            break;
+          }
+          else {
+            lowertri = false;
+          }
+        }
+
+        // check the UPPER triangle of the grid box
+        else {
+          float[] bc = {v3[0]-v1[0], v3[1]-v1[1]};
+          float[] cd = {v2[0]-v3[0], v2[1]-v3[1]};
+          float[] cp = {value[0][i]-v3[0], value[1][i]-v3[1]};
+          float tval1 = bc[0]*bp[1]-bc[1]*bp[0];
+          float tval2 = cd[0]*cp[1]-cd[1]*cp[0];
+          float tval3 = bd[0]*dp[1]-bd[1]*dp[0];
+          boolean test1 = (tval1 == 0) || ((tval1 > 0) == Pos);
+          boolean test2 = (tval2 == 0) || ((tval2 > 0) == Pos);
+          boolean test3 = (tval3 == 0) || ((tval3 < 0) == Pos);
+          int ogx = gx;
+          int ogy = gy;
+          if (!test1 && !test3) {      // Go UP & RIGHT
+            gx++;
+            gy--;
+          }
+          else if (!test2 && !test3) { // Go DOWN & LEFT
+            gx--;
+            gy++;
+          }
+          else if (!test1 && !test2) { // Go DOWN & RIGHT
+            gx++;
+            gy++;
+          }
+          else if (!test1) {           // Go RIGHT
+            gx++;
+          }
+          else if (!test2) {           // Go DOWN
+            gy++;
+          }
+          // Snap guesses back into the grid
+          if (gx < 0) gx = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy < 0) gy = 0;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if ( (gx == ogx) && (gy == ogy) && (test3) ) {
+            // Found correct grid triangle
+            // Solve the point with the reverse interpolation
+            grid[0][i] = ((v3[0]-value[0][i])*(v1[1]-v3[1])
+                        + (value[1][i]-v3[1])*(v1[0]-v3[0]))
+                       / ((v2[0]-v3[0])*(v1[1]-v3[1])
+                        - (v2[1]-v3[1])*(v1[0]-v3[0])) + gx + 1;
+            grid[1][i] = ((v2[1]-v3[1])*(v3[0]-value[0][i])
+                        + (v2[0]-v3[0])*(value[1][i]-v3[1]))
+                       / ((v1[0]-v3[0])*(v2[1]-v3[1])
+                        - (v2[0]-v3[0])*(v1[1]-v3[1])) + gy + 1;
+            break;
+          }
+          else {
+            lowertri = true;
+          }
+        }
+      }
+      if ( (grid[0][i] >= LengthX-0.5) || (grid[1][i] >= LengthY-0.5)
+        || (grid[0][i] <= -0.5) || (grid[1][i] <= -0.5) ) {
+        grid[0][i] = grid[1][i] = Float.NaN;
+      }
+    }
+    return grid;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    float[][]mySamples = getMySamples();
+    if (ManifoldDimension == 2) {
+      return new Gridded2DSet(type, mySamples, LengthX, LengthY,
+                              DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+    else {
+      return new Gridded2DSet(type, mySamples, LengthX,
+                              DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+  }
+
+  /* run 'java visad.Gridded2DSet < formatted_input_stream'
+     to test the Gridded2DSet class */
+  public static void main(String[] argv) throws VisADException {
+
+    // Define input stream
+    InputStreamReader inStr = new InputStreamReader(System.in);
+
+    // Define temporary integer array
+    int[] ints = new int[80];
+    try {
+      ints[0] = inStr.read();
+    }
+    catch(Exception e) {
+      System.out.println("Gridded2DSet: "+e);
+    }
+    int l = 0;
+    while (ints[l] != 10) {
+      try {
+        ints[++l] = inStr.read();
+      }
+      catch (Exception e) {
+        System.out.println("Gridded2DSet: "+e);
+      }
+    }
+    // convert array of integers to array of characters
+    char[] chars = new char[l];
+    for (int i=0; i<l; i++) {
+      chars[i] = (char) ints[i];
+    }
+    int num_coords = Integer.parseInt(new String(chars));
+
+    // num_coords should be a nice round number
+    if (num_coords % 4 != 0) {
+      System.out.println("Gridded2DSet: input coordinates must be divisible by 4"
+                                     +" for main function testing routines.");
+    }
+
+    // Define size of Samples array
+    float[][] samp = new float[2][num_coords];
+    System.out.println("num_dimensions = 2, num_coords = "+num_coords+"\n");
+
+    // Skip blank line
+    try {
+      ints[0] = inStr.read();
+    }
+    catch (Exception e) {
+      System.out.println("Gridded2DSet: "+e);
+    }
+
+    for (int c=0; c<num_coords; c++) {
+      for (int d=0; d<2; d++) {
+        l = 0;
+        try {
+          ints[0] = inStr.read();
+        }
+        catch (Exception e) {
+          System.out.println("Gridded2DSet: "+e);
+        }
+        while ( (ints[l] != 32) && (ints[l] != 10) ) {
+          try {
+            ints[++l] = inStr.read();
+          }
+          catch (Exception e) {
+            System.out.println("Gridded2DSet: "+e);
+          }
+        }
+        chars = new char[l];
+        for (int i=0; i<l; i++) {
+          chars[i] = (char) ints[i];
+        }
+        samp[d][c] = (Float.valueOf(new String(chars))).floatValue();
+      }
+    }
+
+    // do EOF stuff
+    try {
+      inStr.close();
+    }
+    catch (Exception e) {
+      System.out.println("Gridded2DSet: "+e);
+    }
+
+    // Set up instance of Gridded2DSet
+    RealType vis_xcoord = RealType.getRealType("xcoord");
+    RealType vis_ycoord = RealType.getRealType("ycoord");
+    RealType[] vis_array = {vis_xcoord, vis_ycoord};
+    RealTupleType vis_tuple = new RealTupleType(vis_array);
+    Gridded2DSet gSet2D = new Gridded2DSet(vis_tuple, samp, num_coords/4, 4);
+
+    System.out.println("Lengths = " + num_coords/4 + " 4 " + " wedge = ");
+    int[] wedge = gSet2D.getWedge();
+    for (int i=0; i<wedge.length; i++) System.out.println(" " + wedge[i]);
+
+
+    float[][]thatMySamples = gSet2D.getMySamples();
+    // print out Samples information
+    System.out.println("Samples ("+gSet2D.LengthX+" x "+gSet2D.LengthY+"):");
+    for (int i=0; i<gSet2D.LengthX*gSet2D.LengthY; i++) {
+      System.out.println("#"+i+":\t"+thatMySamples[0][i]+", "+thatMySamples[1][i]);
+    }
+
+    // Test gridToValue function
+    System.out.println("\ngridToValue test:");
+    int myLengthX = gSet2D.LengthX+1;
+    int myLengthY = gSet2D.LengthY+1;
+    float[][] myGrid = new float[2][myLengthX*myLengthY];
+    for (int j=0; j<myLengthY; j++) {
+      for (int i=0; i<myLengthX; i++) {
+        myGrid[0][j*myLengthX+i] = i-0.5f;
+        myGrid[1][j*myLengthX+i] = j-0.5f;
+        if (myGrid[0][j*myLengthX+i] < 0) {
+          myGrid[0][j*myLengthX+i] += 0.1;
+        }
+        if (myGrid[0][j*myLengthX+i] > gSet2D.LengthX-1) {
+          myGrid[0][j*myLengthX+i] -= 0.1;
+        }
+        if (myGrid[1][j*myLengthX+i] < 0) {
+          myGrid[1][j*myLengthX+i] += 0.1;
+        }
+        if (myGrid[1][j*myLengthX+i] > gSet2D.LengthY-1) {
+          myGrid[1][j*myLengthX+i] -= 0.1;
+        }
+      }
+    }
+    float[][] myValue = gSet2D.gridToValue(myGrid);
+    for (int i=0; i<myLengthX*myLengthY; i++) {
+      System.out.println("("+((float) Math.round(1000000
+                                      *myGrid[0][i]) /1000000)+", "
+                            +((float) Math.round(1000000
+                                      *myGrid[1][i]) /1000000)+")\t-->  "
+                            +((float) Math.round(1000000
+                                      *myValue[0][i]) /1000000)+", "
+                            +((float) Math.round(1000000
+                                      *myValue[1][i]) /1000000));
+    }
+
+    // Test valueToGrid function
+    System.out.println("\nvalueToGrid test:");
+    float[][] gridTwo = gSet2D.valueToGrid(myValue);
+    for (int i=0; i<gridTwo[0].length; i++) {
+      System.out.println(((float) Math.round(1000000
+                                  *myValue[0][i]) /1000000)+", "
+                        +((float) Math.round(1000000
+                                  *myValue[1][i]) /1000000)+"\t-->  ("
+                        +((float) Math.round(1000000
+                                  *gridTwo[0][i]) /1000000)+", "
+                        +((float) Math.round(1000000
+                                  *gridTwo[1][i]) /1000000)+")");
+    }
+    System.out.println();
+
+  }
+
+/* Here's the output with sample file Gridded2D.txt:
+
+iris 26% java visad.Gridded2DSet < Gridded2D.txt
+num_dimensions = 2, num_coords = 20
+
+Lengths = 5 4  wedge =
+ 0
+ 1
+ 2
+ 3
+ 4
+ 9
+ 8
+ 7
+ 6
+ 5
+. . .
+
+Samples (5 x 4):
+#0:     13.298374, 40.239864
+#1:     19.182746, 40.097643
+. . .
+#18:    40.213987, 19.230974
+#19:    46.293732, 18.239872
+
+gridToValue test:
+(-0.4, -0.4)    -->  11.100636, 43.15516
+(0.5, -0.4)     -->  16.396571, 43.027161
+. . .
+(3.5, 3.4)      -->  44.087403, 15.515757
+(4.4, 3.4)      -->  49.559174, 14.623765
+
+valueToGrid test:
+11.100636, 43.15516     -->  (-0.4, -0.4)
+16.396571, 43.027161    -->  (0.5, -0.4)
+. . .
+44.087403, 15.515757    -->  (3.5, 3.4)
+49.559174, 14.623765    -->  (4.4, 3.4)
+
+iris 27%
+
+*/
+
+}
+
diff --git a/visad/Gridded3D.txt b/visad/Gridded3D.txt
new file mode 100644
index 0000000..9838664
--- /dev/null
+++ b/visad/Gridded3D.txt
@@ -0,0 +1,29 @@
+27
+
+18.629837 8.529864 10.997844
+42.923097 10.123978 11.198275
+52.292387 14.732498 10.219875
+16.998732 26.529387 11.109785
+30.398237 26.118465 11.203785
+50.292378 28.029387 10.092372
+18.226378 41.382763 11.987344
+41.229874 37.882863 10.293744
+54.072386 39.192387 11.219876
+15.492887 10.692367 25.232305
+29.158762 12.363723 26.329765
+47.799027 18.700127 25.239785
+18.709874 23.444564 25.968781
+31.061957 24.132984 26.289374
+45.398732 25.721834 26.239232
+16.768732 36.323846 26.198575
+37.002398 36.776923 25.192865
+46.323333 34.441414 25.239755
+15.311778 14.100001 38.123944
+32.434874 14.123897 36.234987
+48.076345 12.590732 37.092375
+13.887234 25.440293 38.329875
+34.019256 27.623978 38.928372
+46.732499 23.187501 37.098637
+19.423903 36.298275 36.972349
+32.343298 39.600872 36.238975
+49.919754 40.119875 36.018752
diff --git a/visad/Gridded3DDoubleSet.java b/visad/Gridded3DDoubleSet.java
new file mode 100644
index 0000000..f57a042
--- /dev/null
+++ b/visad/Gridded3DDoubleSet.java
@@ -0,0 +1,2048 @@
+//
+// Gridded3DDoubleSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Gridded3DDoubleSet is a Gridded3DSet with double-precision samples.<P>
+*/
+public class Gridded3DDoubleSet extends Gridded3DSet
+       implements GriddedDoubleSet {
+
+  double[] Low = new double[3];
+  double[] Hi = new double[3];
+  double LowX, HiX, LowY, HiY, LowZ, HiZ;
+  double[][] Samples;
+
+
+  // Overridden Gridded3DSet constructors (float[][])
+
+  /** a 3-D set whose topology is a lengthX x lengthY x lengthZ
+      grid, with null errors, CoordinateSystem and Units are
+      defaults from type */
+  public Gridded3DDoubleSet(MathType type, float[][] samples, int lengthX,
+                      int lengthY, int lengthZ) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY, lengthZ,
+      null, null, null, true);
+  }
+
+  /** a 3-D set whose topology is a lengthX x lengthY x lengthZ
+      grid; samples array is organized float[3][number_of_samples]
+      where lengthX * lengthY * lengthZ = number_of_samples;
+      samples must form a non-degenerate 3-D grid (no bow-tie-shaped
+      grid cubes);  the X component increases fastest and the Z
+      component slowest in the second index of samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded3DDoubleSet(MathType type, float[][] samples,
+                      int lengthX, int lengthY, int lengthZ,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY, lengthZ,
+      coord_sys, units, errors, true);
+  }
+
+  public Gridded3DDoubleSet(MathType type, float[][] samples,
+               int lengthX, int lengthY, int lengthZ,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY, lengthZ,
+      coord_sys, units, errors, copy);
+  }
+
+  /** a 3-D set with manifold dimension = 2, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded3DDoubleSet(MathType type, float[][] samples, int lengthX,
+                      int lengthY) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY,
+      null, null, null, true);
+  }
+
+  /** a 3-D set with manifold dimension = 2; samples array is
+      organized float[3][number_of_samples] where lengthX * lengthY
+      = number_of_samples; no geometric constraint on samples; the
+      X component increases fastest in the second index of samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded3DDoubleSet(MathType type, float[][] samples,
+                      int lengthX, int lengthY,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY,
+      coord_sys, units, errors, true);
+  }
+
+  public Gridded3DDoubleSet(MathType type, float[][] samples,
+               int lengthX, int lengthY,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, lengthY,
+      coord_sys, units, errors, copy);
+  }
+
+  /** a 3-D set with manifold dimension = 1, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded3DDoubleSet(MathType type, float[][] samples, int lengthX)
+         throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX, null, null, null, true);
+  }
+
+  /** a 3-D set with manifold dimension = 1; samples array is
+      organized float[3][number_of_samples] where lengthX =
+      number_of_samples; no geometric constraint on samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded3DDoubleSet(MathType type, float[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX,
+      coord_sys, units, errors, true);
+  }
+
+  public Gridded3DDoubleSet(MathType type, float[][] samples, int lengthX,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    this(type, Set.floatToDouble(samples), lengthX,
+      coord_sys, units, errors, copy);
+  }
+
+
+  // Corresponding Gridded3DDoubleSet constructors (double[][])
+
+  /** a 3-D set whose topology is a lengthX x lengthY x lengthZ
+      grid, with null errors, CoordinateSystem and Units are
+      defaults from type */
+  public Gridded3DDoubleSet(MathType type, double[][] samples, int lengthX,
+                      int lengthY, int lengthZ) throws VisADException {
+    this(type, samples, lengthX, lengthY, lengthZ, null, null, null, true);
+  }
+
+  /** a 3-D set whose topology is a lengthX x lengthY x lengthZ
+      grid; samples array is organized double[3][number_of_samples]
+      where lengthX * lengthY * lengthZ = number_of_samples;
+      samples must form a non-degenerate 3-D grid (no bow-tie-shaped
+      grid cubes);  the X component increases fastest and the Z
+      component slowest in the second index of samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded3DDoubleSet(MathType type, double[][] samples,
+                      int lengthX, int lengthY, int lengthZ,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, lengthY, lengthZ,
+      coord_sys, units, errors, true);
+  }
+
+  public Gridded3DDoubleSet(MathType type, double[][] samples,
+               int lengthX, int lengthY, int lengthZ,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    this(type, samples, lengthX, lengthY, lengthZ, coord_sys, units,
+         errors, copy, true);
+  }
+
+  public Gridded3DDoubleSet(MathType type, double[][] samples,
+               int lengthX, int lengthY, int lengthZ,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy, boolean test)
+               throws VisADException {
+    super(type, null, lengthX, lengthY, lengthZ,
+      coord_sys, units, errors, copy);
+    if (samples == null) {
+      throw new SetException("Gridded3DDoubleSet: samples are null");
+    }
+    init_doubles(samples, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LengthY = Lengths[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+    LengthZ = Lengths[2];
+
+    if (Samples != null &&
+        Lengths[0] > 1 && Lengths[1] > 1 && Lengths[2] > 1) {
+      for (int i=0; i<Length; i++) {
+        if (Samples[0][i] != Samples[0][i]) {
+          throw new SetException(
+           "Gridded3DDoubleSet: samples values may not be missing");
+        }
+      }
+      // Samples consistency test
+      double[] t000 = new double[3];
+      double[] t100 = new double[3];
+      double[] t010 = new double[3];
+      double[] t001 = new double[3];
+      double[] t110 = new double[3];
+      double[] t101 = new double[3];
+      double[] t011 = new double[3];
+      double[] t111 = new double[3];
+      for (int v=0; v<3; v++) {
+        t000[v] = Samples[v][0];
+        t100[v] = Samples[v][1];
+        t010[v] = Samples[v][LengthX];
+        t001[v] = Samples[v][LengthY*LengthX];
+        t110[v] = Samples[v][LengthX+1];
+        t101[v] = Samples[v][LengthY*LengthX+1];
+        t011[v] = Samples[v][(LengthY+1)*LengthX];
+        t111[v] = Samples[v][(LengthY+1)*LengthX+1];
+      }
+/* CICERO
+      Pos = (  ( (t100[1]-t000[1])*(t101[2]-t100[2])
+               - (t100[2]-t000[2])*(t101[1]-t100[1]) )
+                *(t110[0]-t100[0])  )
+          + (  ( (t100[2]-t000[2])*(t101[0]-t100[0])
+               - (t100[0]-t000[0])*(t101[2]-t100[2]) )
+                *(t110[1]-t100[1])  )
+          + (  ( (t100[0]-t000[0])*(t101[1]-t100[1])
+               - (t100[1]-t000[1])*(t101[0]-t100[0]) )
+                *(t110[2]-t100[2])  ) > 0;
+*/
+      double xpos = (  ( (t100[1]-t000[1])*(t101[2]-t100[2])
+                       - (t100[2]-t000[2])*(t101[1]-t100[1]) )
+                        *(t110[0]-t100[0])  )
+                  + (  ( (t100[2]-t000[2])*(t101[0]-t100[0])
+                       - (t100[0]-t000[0])*(t101[2]-t100[2]) )
+                        *(t110[1]-t100[1])  )
+                  + (  ( (t100[0]-t000[0])*(t101[1]-t100[1])
+                       - (t100[1]-t000[1])*(t101[0]-t100[0]) )
+                        *(t110[2]-t100[2])  );
+      Pos = (xpos > 0);
+
+      if (test) {
+        double[] v000 = new double[3];
+        double[] v100 = new double[3];
+        double[] v010 = new double[3];
+        double[] v001 = new double[3];
+        double[] v110 = new double[3];
+        double[] v101 = new double[3];
+        double[] v011 = new double[3];
+        double[] v111 = new double[3];
+
+        for (int k=0; k<LengthZ-1; k++) {
+          for (int j=0; j<LengthY-1; j++) {
+            for (int i=0; i<LengthX-1; i++) {
+              for (int v=0; v<3; v++) {
+                int zadd = LengthY*LengthX;
+                int base = k*zadd + j*LengthX + i;
+                v000[v] = Samples[v][base];
+                v100[v] = Samples[v][base+1];
+                v010[v] = Samples[v][base+LengthX];
+                v001[v] = Samples[v][base+zadd];
+                v110[v] = Samples[v][base+LengthX+1];
+                v101[v] = Samples[v][base+zadd+1];
+                v011[v] = Samples[v][base+zadd+LengthX];
+                v111[v] = Samples[v][base+zadd+LengthX+1];
+              }
+/* CICERO
+              if (((  ( (v100[1]-v000[1])*(v101[2]-v100[2])    // test 1
+                      - (v100[2]-v000[2])*(v101[1]-v100[1]) )
+                       *(v110[0]-v100[0])  )
+                 + (  ( (v100[2]-v000[2])*(v101[0]-v100[0])
+                      - (v100[0]-v000[0])*(v101[2]-v100[2]) )
+                       *(v110[1]-v100[1])  )
+                 + (  ( (v100[0]-v000[0])*(v101[1]-v100[1])
+                      - (v100[1]-v000[1])*(v101[0]-v100[0]) )
+                       *(v110[2]-v100[2])  ) > 0 != Pos)
+               || ((  ( (v101[1]-v100[1])*(v001[2]-v101[2])    // test 2
+                      - (v101[2]-v100[2])*(v001[1]-v101[1]) )
+                       *(v111[0]-v101[0])  )
+                 + (  ( (v101[2]-v100[2])*(v001[0]-v101[0])
+                      - (v101[0]-v100[0])*(v001[2]-v101[2]) )
+                       *(v111[1]-v101[1])  )
+                 + (  ( (v101[0]-v100[0])*(v001[1]-v101[1])
+                      - (v101[1]-v100[1])*(v001[0]-v101[0]) )
+                       *(v111[2]-v101[2])  ) > 0 != Pos)
+               || ((  ( (v001[1]-v101[1])*(v000[2]-v001[2])    // test 3
+                      - (v001[2]-v101[2])*(v000[1]-v001[1]) )
+                       *(v011[0]-v001[0])  )
+                 + (  ( (v001[2]-v101[2])*(v000[0]-v001[0])
+                      - (v001[0]-v101[0])*(v000[2]-v001[2]) )
+                       *(v011[1]-v001[1])  )
+                 + (  ( (v001[0]-v101[0])*(v000[1]-v001[1])
+                      - (v001[1]-v101[1])*(v000[0]-v001[0]) )
+                       *(v011[2]-v001[2])  ) > 0 != Pos)
+               || ((  ( (v000[1]-v001[1])*(v100[2]-v000[2])    // test 4
+                      - (v000[2]-v001[2])*(v100[1]-v000[1]) )
+                       *(v010[0]-v000[0])  )
+                 + (  ( (v000[2]-v001[2])*(v100[0]-v000[0])
+                      - (v000[0]-v001[0])*(v100[2]-v000[2]) )
+                       *(v010[1]-v000[1])  )
+                 + (  ( (v000[0]-v001[0])*(v100[1]-v000[1])
+                      - (v000[1]-v001[1])*(v100[0]-v000[0]) )
+                       *(v010[2]-v000[2])  ) > 0 != Pos)
+               || ((  ( (v110[1]-v111[1])*(v010[2]-v110[2])    // test 5
+                      - (v110[2]-v111[2])*(v010[1]-v110[1]) )
+                       *(v100[0]-v110[0])  )
+                 + (  ( (v110[2]-v111[2])*(v010[0]-v110[0])
+                      - (v110[0]-v111[0])*(v010[2]-v110[2]) )
+                       *(v100[1]-v110[1])  )
+                 + (  ( (v110[0]-v111[0])*(v010[1]-v110[1])
+                      - (v110[1]-v111[1])*(v010[0]-v110[0]) )
+                       *(v100[2]-v110[2])  ) > 0 != Pos)
+               || ((  ( (v111[1]-v011[1])*(v110[2]-v111[2])    // test 6
+                      - (v111[2]-v011[2])*(v110[1]-v111[1]) )
+                       *(v101[0]-v111[0])  )
+                 + (  ( (v111[2]-v011[2])*(v110[0]-v111[0])
+                      - (v111[0]-v011[0])*(v110[2]-v111[2]) )
+                       *(v101[1]-v111[1])  )
+                 + (  ( (v111[0]-v011[0])*(v110[1]-v111[1])
+                      - (v111[1]-v011[1])*(v110[0]-v111[0]) )
+                       *(v101[2]-v111[2])  ) > 0 != Pos)
+               || ((  ( (v011[1]-v010[1])*(v111[2]-v011[2])    // test 7
+                      - (v011[2]-v010[2])*(v111[1]-v011[1]) )
+                       *(v001[0]-v011[0])  )
+                 + (  ( (v011[2]-v010[2])*(v111[0]-v011[0])
+                      - (v011[0]-v010[0])*(v111[2]-v011[2]) )
+                       *(v001[1]-v011[1])  )
+                 + (  ( (v011[0]-v010[0])*(v111[1]-v011[1])
+                      - (v011[1]-v010[1])*(v111[0]-v011[0]) )
+                       *(v001[2]-v011[2])  ) > 0 != Pos)
+               || ((  ( (v010[1]-v110[1])*(v011[2]-v010[2])    // test 8
+                      - (v010[2]-v110[2])*(v011[1]-v010[1]) )
+                       *(v000[0]-v010[0])  )
+                 + (  ( (v010[2]-v110[2])*(v011[0]-v010[0])
+                      - (v010[0]-v110[0])*(v011[2]-v010[2]) )
+                       *(v000[1]-v010[1])  )
+                 + (  ( (v010[0]-v110[0])*(v011[1]-v010[1])
+                      - (v010[1]-v110[1])*(v011[0]-v010[0]) )
+                       *(v000[2]-v010[2])  ) > 0 != Pos))
+*/
+// CICERO
+              double w1 = ((  ( (v100[1]-v000[1])*(v101[2]-v100[2])
+                              - (v100[2]-v000[2])*(v101[1]-v100[1]) )
+                               *(v110[0]-v100[0])  )
+                         + (  ( (v100[2]-v000[2])*(v101[0]-v100[0])
+                              - (v100[0]-v000[0])*(v101[2]-v100[2]) )
+                               *(v110[1]-v100[1])  )
+                         + (  ( (v100[0]-v000[0])*(v101[1]-v100[1])
+                              - (v100[1]-v000[1])*(v101[0]-v100[0]) )
+                               *(v110[2]-v100[2])  ));
+              double w2 = ((  ( (v101[1]-v100[1])*(v001[2]-v101[2])
+                              - (v101[2]-v100[2])*(v001[1]-v101[1]) )
+                               *(v111[0]-v101[0])  )
+                         + (  ( (v101[2]-v100[2])*(v001[0]-v101[0])
+                              - (v101[0]-v100[0])*(v001[2]-v101[2]) )
+                               *(v111[1]-v101[1])  )
+                         + (  ( (v101[0]-v100[0])*(v001[1]-v101[1])
+                              - (v101[1]-v100[1])*(v001[0]-v101[0]) )
+                               *(v111[2]-v101[2])  ));
+              double w3 = ((  ( (v001[1]-v101[1])*(v000[2]-v001[2])
+                              - (v001[2]-v101[2])*(v000[1]-v001[1]) )
+                               *(v011[0]-v001[0])  )
+                         + (  ( (v001[2]-v101[2])*(v000[0]-v001[0])
+                              - (v001[0]-v101[0])*(v000[2]-v001[2]) )
+                               *(v011[1]-v001[1])  )
+                         + (  ( (v001[0]-v101[0])*(v000[1]-v001[1])
+                              - (v001[1]-v101[1])*(v000[0]-v001[0]) )
+                               *(v011[2]-v001[2])  ));
+              double w4 = ((  ( (v000[1]-v001[1])*(v100[2]-v000[2])
+                              - (v000[2]-v001[2])*(v100[1]-v000[1]) )
+                               *(v010[0]-v000[0])  )
+                         + (  ( (v000[2]-v001[2])*(v100[0]-v000[0])
+                              - (v000[0]-v001[0])*(v100[2]-v000[2]) )
+                               *(v010[1]-v000[1])  )
+                         + (  ( (v000[0]-v001[0])*(v100[1]-v000[1])
+                              - (v000[1]-v001[1])*(v100[0]-v000[0]) )
+                               *(v010[2]-v000[2])  ));
+              double w5 = ((  ( (v110[1]-v111[1])*(v010[2]-v110[2])
+                              - (v110[2]-v111[2])*(v010[1]-v110[1]) )
+                               *(v100[0]-v110[0])  )
+                         + (  ( (v110[2]-v111[2])*(v010[0]-v110[0])
+                              - (v110[0]-v111[0])*(v010[2]-v110[2]) )
+                               *(v100[1]-v110[1])  )
+                         + (  ( (v110[0]-v111[0])*(v010[1]-v110[1])
+                              - (v110[1]-v111[1])*(v010[0]-v110[0]) )
+                               *(v100[2]-v110[2])  ));
+              double w6 = ((  ( (v111[1]-v011[1])*(v110[2]-v111[2])
+                              - (v111[2]-v011[2])*(v110[1]-v111[1]) )
+                               *(v101[0]-v111[0])  )
+                         + (  ( (v111[2]-v011[2])*(v110[0]-v111[0])
+                              - (v111[0]-v011[0])*(v110[2]-v111[2]) )
+                               *(v101[1]-v111[1])  )
+                         + (  ( (v111[0]-v011[0])*(v110[1]-v111[1])
+                              - (v111[1]-v011[1])*(v110[0]-v111[0]) )
+                               *(v101[2]-v111[2])  ));
+              double w7 = ((  ( (v011[1]-v010[1])*(v111[2]-v011[2])
+                              - (v011[2]-v010[2])*(v111[1]-v011[1]) )
+                               *(v001[0]-v011[0])  )
+                         + (  ( (v011[2]-v010[2])*(v111[0]-v011[0])
+                              - (v011[0]-v010[0])*(v111[2]-v011[2]) )
+                               *(v001[1]-v011[1])  )
+                         + (  ( (v011[0]-v010[0])*(v111[1]-v011[1])
+                              - (v011[1]-v010[1])*(v111[0]-v011[0]) )
+                               *(v001[2]-v011[2])  ));
+              double w8 = ((  ( (v010[1]-v110[1])*(v011[2]-v010[2])
+                              - (v010[2]-v110[2])*(v011[1]-v010[1]) )
+                               *(v000[0]-v010[0])  )
+                         + (  ( (v010[2]-v110[2])*(v011[0]-v010[0])
+                              - (v010[0]-v110[0])*(v011[2]-v010[2]) )
+                               *(v000[1]-v010[1])  )
+                         + (  ( (v010[0]-v110[0])*(v011[1]-v010[1])
+                              - (v010[1]-v110[1])*(v011[0]-v010[0]) )
+                               *(v000[2]-v010[2])  ));
+              if ((w1 > 0 != Pos) || w1 == 0 ||
+                  (w2 > 0 != Pos) || w2 == 0 ||
+                  (w3 > 0 != Pos) || w3 == 0 ||
+                  (w4 > 0 != Pos) || w4 == 0 ||
+                  (w5 > 0 != Pos) || w5 == 0 ||
+                  (w6 > 0 != Pos) || w6 == 0 ||
+                  (w7 > 0 != Pos) || w7 == 0 ||
+                  (w8 > 0 != Pos) || w8 == 0) {
+                throw new SetException("Gridded3DDoubleSet: samples do not "
+                                       +"form a valid grid ("
+                                       +i+","+j+","+k+")");
+              }
+            }
+          }
+        }
+      } // end if (test)
+    }
+  }
+
+  /** a 3-D set with manifold dimension = 2, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded3DDoubleSet(MathType type, double[][] samples, int lengthX,
+                      int lengthY) throws VisADException {
+    this(type, samples, lengthX, lengthY, null, null, null, true);
+  }
+
+  /** a 3-D set with manifold dimension = 2; samples array is
+      organized double[3][number_of_samples] where lengthX * lengthY
+      = number_of_samples; no geometric constraint on samples; the
+      X component increases fastest in the second index of samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded3DDoubleSet(MathType type, double[][] samples,
+                      int lengthX, int lengthY,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, lengthY, coord_sys, units, errors, true);
+  }
+
+  public Gridded3DDoubleSet(MathType type, double[][] samples,
+               int lengthX, int lengthY,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    super(type, null, lengthX, lengthY, coord_sys, units, errors, copy);
+    if (samples == null) {
+      throw new SetException("Gridded3DDoubleSet: samples are null");
+    }
+    init_doubles(samples, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LengthY = Lengths[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+
+    // no Samples consistency test
+  }
+
+  /** a 3-D set with manifold dimension = 1, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public Gridded3DDoubleSet(MathType type, double[][] samples, int lengthX)
+         throws VisADException {
+    this(type, samples, lengthX, null, null, null, true);
+  }
+
+  /** a 3-D set with manifold dimension = 1; samples array is
+      organized double[3][number_of_samples] where lengthX =
+      number_of_samples; no geometric constraint on samples;
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  public Gridded3DDoubleSet(MathType type, double[][] samples, int lengthX,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, coord_sys, units, errors, true);
+  }
+
+  public Gridded3DDoubleSet(MathType type, double[][] samples, int lengthX,
+               CoordinateSystem coord_sys, Unit[] units,
+               ErrorEstimate[] errors, boolean copy)
+               throws VisADException {
+    super(type, null, lengthX, coord_sys, units, errors, copy);
+    if (samples == null) {
+      throw new SetException("Gridded3DDoubleSet: samples are null");
+    }
+    init_doubles(samples, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+
+    // no Samples consistency test
+  }
+
+
+  // Overridden Gridded3DSet methods (float[][])
+
+  public float[][] getSamples() throws VisADException {
+    return getSamples(true);
+  }
+
+  public float[][] getSamples(boolean copy) throws VisADException {
+    return Set.doubleToFloat(Samples);
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    return Set.doubleToFloat(indexToDouble(index));
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    return doubleToIndex(Set.floatToDouble(value));
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    return Set.doubleToFloat(gridToDouble(Set.floatToDouble(grid)));
+  }
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    return Set.doubleToFloat(doubleToGrid(Set.floatToDouble(value)));
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+    float[][] weights) throws VisADException
+  {
+    int len = weights.length;
+    double[][] w = new double[len][];
+    doubleToInterp(Set.floatToDouble(value), indices, w);
+    for (int i=0; i<len; i++) {
+      if (w[i] != null) {
+        weights[i] = new float[w[i].length];
+        for (int j=0; j<w[i].length; j++) {
+          weights[i][j] = (float) w[i][j];
+        }
+      }
+    }
+  }
+
+
+  // Corresponding Gridded3DDoubleSet methods (double[][])
+
+  public double[][] getDoubles() throws VisADException {
+    return getDoubles(true);
+  }
+
+  public double[][] getDoubles(boolean copy) throws VisADException {
+    return copy ? Set.copyDoubles(Samples) : Samples;
+  }
+
+  /** convert an array of 1-D indices to an array of values in
+      R^DomainDimension */
+  public double[][] indexToDouble(int[] index) throws VisADException {
+    int length = index.length;
+    if (Samples == null) {
+      // not used - over-ridden by Linear3DSet.indexToValue
+      int indexX, indexY, indexZ;
+      int k;
+      double[][] grid = new double[ManifoldDimension][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          indexX = index[i] % LengthX;
+          k = index[i] / LengthX;
+          indexY = k % LengthY;
+          indexZ = k / LengthY;
+        }
+        else {
+          indexX = -1;
+          indexY = -1;
+          indexZ = -1;
+        }
+        grid[0][i] = indexX;
+        grid[1][i] = indexY;
+        grid[2][i] = indexZ;
+      }
+      return gridToDouble(grid);
+    }
+    else {
+      double[][] values = new double[3][length];
+      for (int i=0; i<length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          values[0][i] = Samples[0][index[i]];
+          values[1][i] = Samples[1][index[i]];
+          values[2][i] = Samples[2][index[i]];
+        }
+        else {
+          values[0][i] = Double.NaN;
+          values[1][i] = Double.NaN;
+          values[2][i] = Double.NaN;
+        }
+      }
+      return values;
+    }
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] doubleToIndex(double[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded3DDoubleSet.doubleToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+
+    double[][] grid = doubleToGrid(value);
+    double[] grid0 = grid[0];
+    double[] grid1 = grid[1];
+    double[] grid2 = grid[2];
+    double g0, g1, g2;
+    for (int i=0; i<length; i++) {
+      g0 = grid0[i];
+      g1 = grid1[i];
+      g2 = grid2[i];
+      // test for missing
+      index[i] = (g0 != g0 || g1 != g1 || g2 != g2) ? -1 :
+                 ((int) (g0 + 0.5)) + LengthX*( ((int) (g1 + 0.5)) +
+                  LengthY*((int) (g2 + 0.5)));
+    }
+    return index;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public double[][] gridToDouble(double[][] grid) throws VisADException {
+    if (grid.length != ManifoldDimension) {
+      throw new SetException("Gridded3DDoubleSet.gridToDouble: grid dimension " +
+                             grid.length +
+                             " not equal to Manifold dimension " +
+                             ManifoldDimension);
+    }
+    if (ManifoldDimension == 3) {
+      return gridToDouble3D(grid);
+    }
+    else if (ManifoldDimension == 2) {
+      return gridToDouble2D(grid);
+    }
+    else {
+      throw new SetException("Gridded3DDoubleSet.gridToDouble: ManifoldDimension " +
+                             "must be 2 or 3");
+    }
+  }
+
+  private double[][] gridToDouble2D(double[][] grid) throws VisADException {
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Gridded3DDoubleSet.gridToDouble: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(grid[0].length, grid[1].length);
+    double[][] value = new double[3][length];
+    for (int i=0; i<length; i++) {
+      // let gx and gy by the current grid values
+      double gx = grid[0][i];
+      double gy = grid[1][i];
+      if ( (gx < -0.5)        || (gy < -0.5) ||
+           (gx > LengthX-0.5) || (gy > LengthY-0.5) ) {
+        value[0][i] = value[1][i] = value[2][i] = Double.NaN;
+      } else if (Length == 1) {
+        value[0][i] = Samples[0][0];
+        value[1][i] = Samples[1][0];
+        value[2][i] = Samples[2][0];
+      } else {
+        // calculate closest integer variables
+        int igx = (int) gx;
+        int igy = (int) gy;
+        if (igx < 0) igx = 0;
+        if (igx > LengthX-2) igx = LengthX-2;
+        if (igy < 0) igy = 0;
+        if (igy > LengthY-2) igy = LengthY-2;
+  
+        // set up conversion to 1D Samples array
+        int[][] s = { {LengthX*igy+igx,           // (0, 0)
+                       LengthX*(igy+1)+igx},      // (0, 1)
+                      {LengthX*igy+igx+1,         // (1, 0)
+                       LengthX*(igy+1)+igx+1} };  // (1, 1)
+        if (gx+gy-igx-igy-1 <= 0) {
+          // point is in LOWER triangle
+          for (int j=0; j<3; j++) {
+            value[j][i] = Samples[j][s[0][0]]
+              + (gx-igx)*(Samples[j][s[1][0]]-Samples[j][s[0][0]])
+              + (gy-igy)*(Samples[j][s[0][1]]-Samples[j][s[0][0]]);
+          }
+        }
+        else {
+          // point is in UPPER triangle
+          for (int j=0; j<3; j++) {
+            value[j][i] = Samples[j][s[1][1]]
+              + (1+igx-gx)*(Samples[j][s[0][1]]-Samples[j][s[1][1]])
+              + (1+igy-gy)*(Samples[j][s[1][0]]-Samples[j][s[1][1]]);
+          }
+        }
+      }
+    }
+    return value;
+  }
+
+  private double[][] gridToDouble3D(double[][] grid) throws VisADException {
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2 || Lengths[2] < 2)) {
+      throw new SetException("Gridded3DDoubleSet.gridToDouble: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(grid[0].length, grid[1].length);
+    length = Math.min(length, grid[2].length);
+    double[][] value = new double[3][length];
+    double[] A = new double[3];
+    double[] B = new double[3];
+    double[] C = new double[3];
+    double[] D = new double[3];
+    double[] E = new double[3];
+    double[] F = new double[3];
+    double[] G = new double[3];
+    double[] H = new double[3];
+
+
+
+
+    for (int i=0; i<length; i++) {
+      // let gx, gy, and gz be the current grid values
+      double gx = grid[0][i];
+      double gy = grid[1][i];
+      double gz = grid[2][i];
+      if ( (gx < -0.5)        || (gy < -0.5)        || (gz < -0.5) ||
+           (gx > LengthX-0.5) || (gy > LengthY-0.5) || (gz > LengthZ-0.5) ) {
+        value[0][i] = value[1][i] = value[2][i] = Double.NaN;
+      } else if (Length == 1) {
+        value[0][i] = Samples[0][0];
+        value[1][i] = Samples[1][0];
+        value[2][i] = Samples[2][0];
+      } else {
+        // calculate closest integer variables
+        int igx, igy, igz;
+        if (gx < 0) igx = 0;
+        else if (gx > LengthX-2) igx = LengthX - 2;
+        else igx = (int) gx;
+        if (gy < 0) igy = 0;
+        else if (gy > LengthY-2) igy = LengthY - 2;
+        else igy = (int) gy;
+        if (gz < 0) igz = 0;
+        else if (gz > LengthZ-2) igz = LengthZ - 2;
+        else igz = (int) gz;
+  
+        // determine tetrahedralization type
+        boolean evencube = ((igx+igy+igz) % 2 == 0);
+  
+        // calculate distances from integer grid point
+        double s, t, u;
+        if (evencube) {
+          s = gx - igx;
+          t = gy - igy;
+          u = gz - igz;
+        }
+        else {
+          s = 1 + igx - gx;
+          t = 1 + igy - gy;
+          u = 1 + igz - gz;
+        }
+  
+        // Define vertices of grid box
+        int zadd = LengthY*LengthX;
+        int base = igz*zadd + igy*LengthX + igx;
+        int ai = base+zadd;            // 0, 0, 1
+        int bi = base+zadd+1;          // 1, 0, 1
+        int ci = base+zadd+LengthX+1;  // 1, 1, 1
+        int di = base+zadd+LengthX;    // 0, 1, 1
+        int ei = base;                 // 0, 0, 0
+        int fi = base+1;               // 1, 0, 0
+        int gi = base+LengthX+1;       // 1, 1, 0
+        int hi = base+LengthX;         // 0, 1, 0
+        if (evencube) {
+          A[0] = Samples[0][ai];
+          A[1] = Samples[1][ai];
+          A[2] = Samples[2][ai];
+          B[0] = Samples[0][bi];
+          B[1] = Samples[1][bi];
+          B[2] = Samples[2][bi];
+          C[0] = Samples[0][ci];
+          C[1] = Samples[1][ci];
+          C[2] = Samples[2][ci];
+          D[0] = Samples[0][di];
+          D[1] = Samples[1][di];
+          D[2] = Samples[2][di];
+          E[0] = Samples[0][ei];
+          E[1] = Samples[1][ei];
+          E[2] = Samples[2][ei];
+          F[0] = Samples[0][fi];
+          F[1] = Samples[1][fi];
+          F[2] = Samples[2][fi];
+          G[0] = Samples[0][gi];
+          G[1] = Samples[1][gi];
+          G[2] = Samples[2][gi];
+          H[0] = Samples[0][hi];
+          H[1] = Samples[1][hi];
+          H[2] = Samples[2][hi];
+        }
+        else {
+          G[0] = Samples[0][ai];
+          G[1] = Samples[1][ai];
+          G[2] = Samples[2][ai];
+          H[0] = Samples[0][bi];
+          H[1] = Samples[1][bi];
+          H[2] = Samples[2][bi];
+          E[0] = Samples[0][ci];
+          E[1] = Samples[1][ci];
+          E[2] = Samples[2][ci];
+          F[0] = Samples[0][di];
+          F[1] = Samples[1][di];
+          F[2] = Samples[2][di];
+          C[0] = Samples[0][ei];
+          C[1] = Samples[1][ei];
+          C[2] = Samples[2][ei];
+          D[0] = Samples[0][fi];
+          D[1] = Samples[1][fi];
+          D[2] = Samples[2][fi];
+          A[0] = Samples[0][gi];
+          A[1] = Samples[1][gi];
+          A[2] = Samples[2][gi];
+          B[0] = Samples[0][hi];
+          B[1] = Samples[1][hi];
+          B[2] = Samples[2][hi];
+        }
+  
+        // These tests determine which tetrahedron the point is in
+        boolean test1 = (1 - s - t - u >= 0);
+        boolean test2 = (s - t + u - 1 >= 0);
+        boolean test3 = (t - s + u - 1 >= 0);
+        boolean test4 = (s + t - u - 1 >= 0);
+  
+        // These cases handle grid coordinates off the grid
+        // (Different tetrahedrons must be chosen accordingly)
+        if ( (gx < 0) || (gx > LengthX-1)
+          || (gy < 0) || (gy > LengthY-1)
+          || (gz < 0) || (gz > LengthZ-1) ) {
+          boolean OX, OY, OZ, MX, MY, MZ, LX, LY, LZ;
+          OX = OY = OZ = MX = MY = MZ = LX = LY = LZ = false;
+          if (igx == 0) OX = true;
+          if (igy == 0) OY = true;
+          if (igz == 0) OZ = true;
+          if (igx == LengthX-2) LX = true;
+          if (igy == LengthY-2) LY = true;
+          if (igz == LengthZ-2) LZ = true;
+          if (!OX && !LX) MX = true;
+          if (!OY && !LY) MY = true;
+          if (!OZ && !LZ) MZ = true;
+          test1 = test2 = test3 = test4 = false;
+          // 26 cases
+          if (evencube) {
+            if (!LX && !LY && !LZ) test1 = true;
+            else if ( (LX && OY && MZ) || (MX && OY && LZ)
+                   || (LX && MY && LZ) || (LX && OY && LZ)
+                   || (MX && MY && LZ) || (LX && MY && MZ) ) test2 = true;
+            else if ( (OX && LY && MZ) || (OX && MY && LZ)
+                   || (MX && LY && LZ) || (OX && LY && LZ)
+                                       || (MX && LY && MZ) ) test3 = true;
+            else if ( (MX && LY && OZ) || (LX && MY && OZ)
+                   || (LX && LY && MZ) || (LX && LY && OZ) ) test4 = true;
+          }
+          else {
+            if (!OX && !OY && !OZ) test1 = true;
+            else if ( (OX && MY && OZ) || (MX && LY && OZ)
+                   || (OX && LY && MZ) || (OX && LY && OZ)
+                   || (MX && MY && OZ) || (OX && MY && MZ) ) test2 = true;
+            else if ( (LX && MY && OZ) || (MX && OY && OZ)
+                   || (LX && OY && MZ) || (LX && OY && OZ)
+                                       || (MX && OY && MZ) ) test3 = true;
+            else if ( (OX && OY && MZ) || (OX && MY && OZ)
+                   || (MX && OY && LZ) || (OX && OY && LZ) ) test4 = true;
+          }
+        }
+        if (test1) {
+          for (int j=0; j<3; j++) {
+            value[j][i] = E[j] + s*(F[j]-E[j])
+                               + t*(H[j]-E[j])
+                               + u*(A[j]-E[j]);
+          }
+        }
+        else if (test2) {
+          for (int j=0; j<3; j++) {
+            value[j][i] = B[j] + (1-s)*(A[j]-B[j])
+                                   + t*(C[j]-B[j])
+                               + (1-u)*(F[j]-B[j]);
+          }
+        }
+        else if (test3) {
+          for (int j=0; j<3; j++) {
+            value[j][i] = D[j]     + s*(C[j]-D[j])
+                               + (1-t)*(A[j]-D[j])
+                               + (1-u)*(H[j]-D[j]);
+          }
+        }
+        else if (test4) {
+          for (int j=0; j<3; j++) {
+            value[j][i] = G[j] + (1-s)*(H[j]-G[j])
+                               + (1-t)*(F[j]-G[j])
+                                   + u*(C[j]-G[j]);
+          }
+        }
+        else {
+          for (int j=0; j<3; j++) {
+            value[j][i] = (H[j]+F[j]+A[j]-C[j])/2 + s*(C[j]+F[j]-H[j]-A[j])/2
+                                                  + t*(C[j]-F[j]+H[j]-A[j])/2
+                                                  + u*(C[j]-F[j]-H[j]+A[j])/2;
+          }
+        }
+      }
+    }
+    return value;
+  }
+
+  // WLH 6 Dec 2001
+  //private int gx = -1;
+  //private int gy = -1;
+  //private int gz = -1;
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public double[][] doubleToGrid(double[][] value) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Gridded3DDoubleSet.doubleToGrid: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    if (ManifoldDimension < 3) {
+      throw new SetException("Gridded3DDoubleSet.doubleToGrid: ManifoldDimension " +
+                             "must be 3");
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2 || Lengths[2] < 2)) {
+      throw new SetException("Gridded3DDoubleSet.doubleToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    // Avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(value[0].length, value[1].length);
+    length = Math.min(length, value[2].length);
+    double[][] grid = new double[ManifoldDimension][length];
+
+    // (gx, gy, gz) is the current grid box guess
+    int gx = (LengthX-1)/2;
+    int gy = (LengthY-1)/2;
+    int gz = (LengthZ-1)/2;
+/* WLH 6 Dec 2001
+    // use value from last call as first guess, if reasonable
+    if (gx < 0 || gx >= LengthX || gy < 0 || gy >= LengthY ||
+        gz < 0 || gz >= LengthZ) {
+      gx = (LengthX-1)/2;
+      gy = (LengthY-1)/2;
+      gz = (LengthZ-1)/2;
+    }
+*/
+
+    double[] A = new double[3];
+    double[] B = new double[3];
+    double[] C = new double[3];
+    double[] D = new double[3];
+    double[] E = new double[3];
+    double[] F = new double[3];
+    double[] G = new double[3];
+    double[] H = new double[3];
+
+    double[] M = new double[3];
+    double[] N = new double[3];
+    double[] O = new double[3];
+    double[] P = new double[3];
+    double[] X = new double[3];
+    double[] Y = new double[3];
+    double[] Q = new double[3];
+
+
+    for (int i=0; i<length; i++) {
+
+      if (Length == 1) {
+        if (Double.isNaN(value[0][i]) || Double.isNaN(value[1][i]) || Double.isNaN(value[2][i])) {
+           grid[0][i] = grid[1][i] = grid[2][i] = Double.NaN;
+        } else {
+           grid[0][i] = 0;
+           grid[1][i] = 0;
+           grid[2][i] = 0;
+        }
+        continue;
+      }
+
+      // a flag indicating whether point is off the grid
+      boolean offgrid = false;
+      // the first guess should be the last box unless there was no solution
+      // test for missing
+      if ( (i != 0) && grid[0][i-1] != grid[0][i-1] ) {
+        gx = (LengthX-1)/2;
+        gy = (LengthY-1)/2;
+        gz = (LengthZ-1)/2;
+      }
+      int tetnum = 5;  // Tetrahedron number in which to start search
+      // if the iteration loop fails, the result should be NaN
+      grid[0][i] = grid[1][i] = grid[2][i] = Double.NaN;
+      for (int itnum=0; itnum<2*(LengthX+LengthY+LengthZ); itnum++) {
+        // determine tetrahedralization type
+        boolean evencube = ((gx+gy+gz) % 2 == 0);
+
+        // Define vertices of grid box
+        int zadd = LengthY*LengthX;
+        int base = gz*zadd + gy*LengthX + gx;
+        int ai = base+zadd;            // 0, 0, 1
+        int bi = base+zadd+1;          // 1, 0, 1
+        int ci = base+zadd+LengthX+1;  // 1, 1, 1
+        int di = base+zadd+LengthX;    // 0, 1, 1
+        int ei = base;                 // 0, 0, 0
+        int fi = base+1;               // 1, 0, 0
+        int gi = base+LengthX+1;       // 1, 1, 0
+        int hi = base+LengthX;         // 0, 1, 0
+        if (evencube) {
+          A[0] = Samples[0][ai];
+          A[1] = Samples[1][ai];
+          A[2] = Samples[2][ai];
+          B[0] = Samples[0][bi];
+          B[1] = Samples[1][bi];
+          B[2] = Samples[2][bi];
+          C[0] = Samples[0][ci];
+          C[1] = Samples[1][ci];
+          C[2] = Samples[2][ci];
+          D[0] = Samples[0][di];
+          D[1] = Samples[1][di];
+          D[2] = Samples[2][di];
+          E[0] = Samples[0][ei];
+          E[1] = Samples[1][ei];
+          E[2] = Samples[2][ei];
+          F[0] = Samples[0][fi];
+          F[1] = Samples[1][fi];
+          F[2] = Samples[2][fi];
+          G[0] = Samples[0][gi];
+          G[1] = Samples[1][gi];
+          G[2] = Samples[2][gi];
+          H[0] = Samples[0][hi];
+          H[1] = Samples[1][hi];
+          H[2] = Samples[2][hi];
+        }
+        else {
+          G[0] = Samples[0][ai];
+          G[1] = Samples[1][ai];
+          G[2] = Samples[2][ai];
+          H[0] = Samples[0][bi];
+          H[1] = Samples[1][bi];
+          H[2] = Samples[2][bi];
+          E[0] = Samples[0][ci];
+          E[1] = Samples[1][ci];
+          E[2] = Samples[2][ci];
+          F[0] = Samples[0][di];
+          F[1] = Samples[1][di];
+          F[2] = Samples[2][di];
+          C[0] = Samples[0][ei];
+          C[1] = Samples[1][ei];
+          C[2] = Samples[2][ei];
+          D[0] = Samples[0][fi];
+          D[1] = Samples[1][fi];
+          D[2] = Samples[2][fi];
+          A[0] = Samples[0][gi];
+          A[1] = Samples[1][gi];
+          A[2] = Samples[2][gi];
+          B[0] = Samples[0][hi];
+          B[1] = Samples[1][hi];
+          B[2] = Samples[2][hi];
+        }
+
+        // Compute tests and go to a new box depending on results
+        boolean test1, test2, test3, test4;
+        double tval1, tval2, tval3, tval4;
+        int ogx = gx;
+        int ogy = gy;
+        int ogz = gz;
+        if (tetnum==1) {
+          tval1 = ( (E[1]-A[1])*(F[2]-E[2]) - (E[2]-A[2])*(F[1]-E[1]) )
+                   *(value[0][i]-E[0])
+                + ( (E[2]-A[2])*(F[0]-E[0]) - (E[0]-A[0])*(F[2]-E[2]) )
+                   *(value[1][i]-E[1])
+                + ( (E[0]-A[0])*(F[1]-E[1]) - (E[1]-A[1])*(F[0]-E[0]) )
+                   *(value[2][i]-E[2]);
+          tval2 = ( (E[1]-H[1])*(A[2]-E[2]) - (E[2]-H[2])*(A[1]-E[1]) )
+                   *(value[0][i]-E[0])
+                + ( (E[2]-H[2])*(A[0]-E[0]) - (E[0]-H[0])*(A[2]-E[2]) )
+                   *(value[1][i]-E[1])
+                + ( (E[0]-H[0])*(A[1]-E[1]) - (E[1]-H[1])*(A[0]-E[0]) )
+                   *(value[2][i]-E[2]);
+          tval3 = ( (E[1]-F[1])*(H[2]-E[2]) - (E[2]-F[2])*(H[1]-E[1]) )
+                   *(value[0][i]-E[0])
+                + ( (E[2]-F[2])*(H[0]-E[0]) - (E[0]-F[0])*(H[2]-E[2]) )
+                   *(value[1][i]-E[1])
+                + ( (E[0]-F[0])*(H[1]-E[1]) - (E[1]-F[1])*(H[0]-E[0]) )
+                   *(value[2][i]-E[2]);
+          test1 = (tval1 == 0) || ((tval1 > 0) == (!evencube)^Pos);
+          test2 = (tval2 == 0) || ((tval2 > 0) == (!evencube)^Pos);
+          test3 = (tval3 == 0) || ((tval3 > 0) == (!evencube)^Pos);
+
+          // if a test failed go to a new box
+          int updown = (evencube) ? -1 : 1;
+          if (!test1) gy += updown; // UP/DOWN
+          if (!test2) gx += updown; // LEFT/RIGHT
+          if (!test3) gz += updown; // BACK/FORWARD
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off.
+          if (gx < 0) gx = 0;
+          if (gy < 0) gy = 0;
+          if (gz < 0) gz = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if (gz > LengthZ-2) gz = LengthZ-2;
+
+          // Detect if the point is off the grid entirely
+          if ( (gx == ogx) && (gy == ogy) && (gz == ogz)
+            && (!test1 || !test2 || !test3) && !offgrid ) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (  ( (gx == ogx) && (gy == ogy) && (gz == ogz) )
+                || offgrid) {
+            // solve point
+            for (int j=0; j<3; j++) {
+              M[j] = (F[j]-E[j])*(A[(j+1)%3]-E[(j+1)%3])
+                   - (F[(j+1)%3]-E[(j+1)%3])*(A[j]-E[j]);
+              N[j] = (H[j]-E[j])*(A[(j+1)%3]-E[(j+1)%3])
+                   - (H[(j+1)%3]-E[(j+1)%3])*(A[j]-E[j]);
+              O[j] = (F[(j+1)%3]-E[(j+1)%3])*(A[(j+2)%3]-E[(j+2)%3])
+                   - (F[(j+2)%3]-E[(j+2)%3])*(A[(j+1)%3]-E[(j+1)%3]);
+              P[j] = (H[(j+1)%3]-E[(j+1)%3])*(A[(j+2)%3]-E[(j+2)%3])
+                   - (H[(j+2)%3]-E[(j+2)%3])*(A[(j+1)%3]-E[(j+1)%3]);
+              X[j] = value[(j+2)%3][i]*(A[(j+1)%3]-E[(j+1)%3])
+                   - value[(j+1)%3][i]*(A[(j+2)%3]-E[(j+2)%3])
+                   + E[(j+1)%3]*A[(j+2)%3] - E[(j+2)%3]*A[(j+1)%3];
+              Y[j] = value[j][i]*(A[(j+1)%3]-E[(j+1)%3])
+                   - value[(j+1)%3][i]*(A[j]-E[j])
+                   + E[(j+1)%3]*A[j] - E[j]*A[(j+1)%3];
+            }
+            double s, t, u;
+            // these if statements handle skewed grids
+            double d0 = M[0]*P[0] - N[0]*O[0];
+            double d1 = M[1]*P[1] - N[1]*O[1];
+            double d2 = M[2]*P[2] - N[2]*O[2];
+            double ad0 = Math.abs(d0);
+            double ad1 = Math.abs(d1);
+            double ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = (N[0]*X[0] + P[0]*Y[0])/d0;
+              t = -(M[0]*X[0] + O[0]*Y[0])/d0;
+            }
+            else if (ad1 > ad2) {
+              s = (N[1]*X[1] + P[1]*Y[1])/d1;
+              t = -(M[1]*X[1] + O[1]*Y[1])/d1;
+            }
+            else {
+              s = (N[2]*X[2] + P[2]*Y[2])/d2;
+              t = -(M[2]*X[2] + O[2]*Y[2])/d2;
+            }
+            d0 = A[0]-E[0];
+            d1 = A[1]-E[1];
+            d2 = A[2]-E[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = ( value[0][i] - E[0] - s*(F[0]-E[0])
+                - t*(H[0]-E[0]) ) / d0;
+            }
+            else if (ad1 > ad2) {
+              u = ( value[1][i] - E[1] - s*(F[1]-E[1])
+                - t*(H[1]-E[1]) ) / d1;
+            }
+            else {
+              u = ( value[2][i] - E[2] - s*(F[2]-E[2])
+                - t*(H[2]-E[2]) ) / d2;
+            }
+            if (evencube) {
+              grid[0][i] = gx+s;
+              grid[1][i] = gy+t;
+              grid[2][i] = gz+u;
+            }
+            else {
+              grid[0][i] = gx+1-s;
+              grid[1][i] = gy+1-t;
+              grid[2][i] = gz+1-u;
+            }
+            break;
+          }
+        }
+        else if (tetnum==2) {
+          tval1 = ( (B[1]-C[1])*(F[2]-B[2]) - (B[2]-C[2])*(F[1]-B[1]) )
+                   *(value[0][i]-B[0])
+                + ( (B[2]-C[2])*(F[0]-B[0]) - (B[0]-C[0])*(F[2]-B[2]) )
+                   *(value[1][i]-B[1])
+                + ( (B[0]-C[0])*(F[1]-B[1]) - (B[1]-C[1])*(F[0]-B[0]) )
+                   *(value[2][i]-B[2]);
+          tval2 = ( (B[1]-A[1])*(C[2]-B[2]) - (B[2]-A[2])*(C[1]-B[1]) )
+                   *(value[0][i]-B[0])
+                + ( (B[2]-A[2])*(C[0]-B[0]) - (B[0]-A[0])*(C[2]-B[2]) )
+                   *(value[1][i]-B[1])
+                + ( (B[0]-A[0])*(C[1]-B[1]) - (B[1]-A[1])*(C[0]-B[0]) )
+                   *(value[2][i]-B[2]);
+          tval3 = ( (B[1]-F[1])*(A[2]-B[2]) - (B[2]-F[2])*(A[1]-B[1]) )
+                   *(value[0][i]-B[0])
+                + ( (B[2]-F[2])*(A[0]-B[0]) - (B[0]-F[0])*(A[2]-B[2]) )
+                   *(value[1][i]-B[1])
+                + ( (B[0]-F[0])*(A[1]-B[1]) - (B[1]-F[1])*(A[0]-B[0]) )
+                   *(value[2][i]-B[2]);
+          test1 = (tval1 == 0) || ((tval1 > 0) == (!evencube)^Pos);
+          test2 = (tval2 == 0) || ((tval2 > 0) == (!evencube)^Pos);
+          test3 = (tval3 == 0) || ((tval3 > 0) == (!evencube)^Pos);
+
+          // if a test failed go to a new box
+          if (!test1 &&  evencube) gx++; // RIGHT
+          if (!test1 && !evencube) gx--; // LEFT
+          if (!test2 &&  evencube) gz++; // FORWARD
+          if (!test2 && !evencube) gz--; // BACK
+          if (!test3 &&  evencube) gy--; // UP
+          if (!test3 && !evencube) gy++; // DOWN
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0) gx = 0;
+          if (gy < 0) gy = 0;
+          if (gz < 0) gz = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if (gz > LengthZ-2) gz = LengthZ-2;
+
+          // Detect if the point is off the grid entirely
+          if ( (gx == ogx) && (gy == ogy) && (gz == ogz)
+            && (!test1 || !test2 || !test3) && !offgrid ) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (  ( (gx == ogx) && (gy == ogy) && (gz == ogz) )
+                || offgrid) {
+            // solve point
+            for (int j=0; j<3; j++) {
+              M[j] = (A[j]-B[j])*(F[(j+1)%3]-B[(j+1)%3])
+                   - (A[(j+1)%3]-B[(j+1)%3])*(F[j]-B[j]);
+              N[j] = (C[j]-B[j])*(F[(j+1)%3]-B[(j+1)%3])
+                   - (C[(j+1)%3]-B[(j+1)%3])*(F[j]-B[j]);
+              O[j] = (A[(j+1)%3]-B[(j+1)%3])*(F[(j+2)%3]-B[(j+2)%3])
+                   - (A[(j+2)%3]-B[(j+2)%3])*(F[(j+1)%3]-B[(j+1)%3]);
+              P[j] = (C[(j+1)%3]-B[(j+1)%3])*(F[(j+2)%3]-B[(j+2)%3])
+                   - (C[(j+2)%3]-B[(j+2)%3])*(F[(j+1)%3]-B[(j+1)%3]);
+              X[j] = value[(j+2)%3][i]*(F[(j+1)%3]-B[(j+1)%3])
+                   - value[(j+1)%3][i]*(F[(j+2)%3]-B[(j+2)%3])
+                   + B[(j+1)%3]*F[(j+2)%3] - B[(j+2)%3]*F[(j+1)%3];
+              Y[j] = value[j][i]*(F[(j+1)%3]-B[(j+1)%3])
+                   - value[1][i]*(F[j]-B[j])
+                   + B[(j+1)%3]*F[j] - B[j]*F[(j+1)%3];
+            }
+            double s, t, u;
+            // these if statements handle skewed grids
+            double d0 = M[0]*P[0] - N[0]*O[0];
+            double d1 = M[1]*P[1] - N[1]*O[1];
+            double d2 = M[2]*P[2] - N[2]*O[2];
+            double ad0 = Math.abs(d0);
+            double ad1 = Math.abs(d1);
+            double ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = 1 - (N[0]*X[0] + P[0]*Y[0])/d0;
+              t = -(M[0]*X[0] + O[0]*Y[0])/d0;
+            }
+            else if (ad1 > ad2) {
+              s = 1 - (N[1]*X[1] + P[1]*Y[1])/d1;
+              t = -(M[1]*X[1] + O[1]*Y[1])/d1;
+            }
+            else {
+              s = 1 - (N[2]*X[2] + P[2]*Y[2])/d2;
+              t = -(M[2]*X[2] + O[2]*Y[2])/d2;
+            }
+            d0 = F[0]-B[0];
+            d1 = F[1]-B[1];
+            d2 = F[2]-B[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = 1 - ( value[0][i] - B[0] - (1-s)*(A[0]-B[0])
+                - t*(C[0]-B[0]) ) / d0;
+            }
+            else if (ad1 > ad2) {
+              u = 1 - ( value[1][i] - B[1] - (1-s)*(A[1]-B[1])
+                - t*(C[1]-B[1]) ) / d1;
+            }
+            else {
+              u = 1 - ( value[2][i] - B[2] - (1-s)*(A[2]-B[2])
+                - t*(C[2]-B[2]) ) / d2;
+            }
+            if (evencube) {
+              grid[0][i] = gx+s;
+              grid[1][i] = gy+t;
+              grid[2][i] = gz+u;
+            }
+            else {
+              grid[0][i] = gx+1-s;
+              grid[1][i] = gy+1-t;
+              grid[2][i] = gz+1-u;
+            }
+            break;
+          }
+        }
+        else if (tetnum==3) {
+          tval1 = ( (D[1]-A[1])*(H[2]-D[2]) - (D[2]-A[2])*(H[1]-D[1]) )
+                   *(value[0][i]-D[0])
+                + ( (D[2]-A[2])*(H[0]-D[0]) - (D[0]-A[0])*(H[2]-D[2]) )
+                   *(value[1][i]-D[1])
+                + ( (D[0]-A[0])*(H[1]-D[1]) - (D[1]-A[1])*(H[0]-D[0]) )
+                   *(value[2][i]-D[2]);
+          tval2 = ( (D[1]-C[1])*(A[2]-D[2]) - (D[2]-C[2])*(A[1]-D[1]) )
+                   *(value[0][i]-D[0])
+                + ( (D[2]-C[2])*(A[0]-D[0]) - (D[0]-C[0])*(A[2]-D[2]) )
+                   *(value[1][i]-D[1])
+                + ( (D[0]-C[0])*(A[1]-D[1]) - (D[1]-C[1])*(A[0]-D[0]) )
+                   *(value[2][i]-D[2]);
+          tval3 = ( (D[1]-H[1])*(C[2]-D[2]) - (D[2]-H[2])*(C[1]-D[1]) )
+                   *(value[0][i]-D[0])
+                + ( (D[2]-H[2])*(C[0]-D[0]) - (D[0]-H[0])*(C[2]-D[2]) )
+                   *(value[1][i]-D[1])
+                + ( (D[0]-H[0])*(C[1]-D[1]) - (D[1]-H[1])*(C[0]-D[0]) )
+                   *(value[2][i]-D[2]);
+          test1 = (tval1 == 0) || ((tval1 > 0) == (!evencube)^Pos);
+          test2 = (tval2 == 0) || ((tval2 > 0) == (!evencube)^Pos);
+          test3 = (tval3 == 0) || ((tval3 > 0) == (!evencube)^Pos);
+
+          // if a test failed go to a new box
+          if (!test1 &&  evencube) gx--; // LEFT
+          if (!test1 && !evencube) gx++; // RIGHT
+          if (!test2 &&  evencube) gz++; // FORWARD
+          if (!test2 && !evencube) gz--; // BACK
+          if (!test3 &&  evencube) gy++; // DOWN
+          if (!test3 && !evencube) gy--; // UP
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0) gx = 0;
+          if (gy < 0) gy = 0;
+          if (gz < 0) gz = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if (gz > LengthZ-2) gz = LengthZ-2;
+
+          // Detect if the point is off the grid entirely
+          if ( (gx == ogx) && (gy == ogy) && (gz == ogz)
+            && (!test1 || !test2 || !test3) && !offgrid ) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (  ( (gx == ogx) && (gy == ogy) && (gz == ogz) )
+                || offgrid) {
+            // solve point
+            for (int j=0; j<3; j++) {
+              M[j] = (C[j]-D[j])*(H[(j+1)%3]-D[(j+1)%3])
+                   - (C[(j+1)%3]-D[(j+1)%3])*(H[j]-D[j]);
+              N[j] = (A[j]-D[j])*(H[(j+1)%3]-D[(j+1)%3])
+                   - (A[(j+1)%3]-D[(j+1)%3])*(H[j]-D[j]);
+              O[j] = (C[(j+1)%3]-D[(j+1)%3])*(H[(j+2)%3]-D[(j+2)%3])
+                   - (C[(j+2)%3]-D[(j+2)%3])*(H[(j+1)%3]-D[(j+1)%3]);
+              P[j] = (A[(j+1)%3]-D[(j+1)%3])*(H[(j+2)%3]-D[(j+2)%3])
+                   - (A[(j+2)%3]-D[(j+2)%3])*(H[(j+1)%3]-D[(j+1)%3]);
+              X[j] = value[(j+2)%3][i]*(H[(j+1)%3]-D[(j+1)%3])
+                   - value[(j+1)%3][i]*(H[(j+2)%3]-D[(j+2)%3])
+                   + D[(j+1)%3]*H[(j+2)%3] - D[(j+2)%3]*H[(j+1)%3];
+              Y[j] = value[j][i]*(H[(j+1)%3]-D[(j+1)%3])
+                   - value[(j+1)%3][i]*(H[j]-D[j])
+                   + D[(j+1)%3]*H[j] - D[j]*H[(j+1)%3];
+            }
+            double s, t, u;
+            // these if statements handle skewed grids
+            double d0 = M[0]*P[0] - N[0]*O[0];
+            double d1 = M[1]*P[1] - N[1]*O[1];
+            double d2 = M[2]*P[2] - N[2]*O[2];
+            double ad0 = Math.abs(d0);
+            double ad1 = Math.abs(d1);
+            double ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = (N[0]*X[0] + P[0]*Y[0])/d0;
+              t = 1 + (M[0]*X[0] + O[0]*Y[0])/d0;
+            }
+            else if (ad1 > ad2) {
+              s = (N[1]*X[1] + P[1]*Y[1])/d1;
+              t = 1 + (M[1]*X[1] + O[1]*Y[1])/d1;
+            }
+            else {
+              s = (N[2]*X[2] + P[2]*Y[2])/d2;
+              t =  1 + (M[2]*X[2] + O[2]*Y[2])/d2;
+            }
+            d0 = H[0]-D[0];
+            d1 = H[1]-D[1];
+            d2 = H[2]-D[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = 1 - ( value[0][i] - D[0] - s*(C[0]-D[0])
+                - (1-t)*(A[0]-D[0]) ) / d0;
+            }
+            else if (ad1 > ad2) {
+              u = 1 - ( value[1][i] - D[1] - s*(C[1]-D[1])
+                - (1-t)*(A[1]-D[1]) ) / d1;
+            }
+            else {
+              u = 1 - ( value[2][i] - D[2] - s*(C[2]-D[2])
+                - (1-t)*(A[2]-D[2]) ) / d2;
+            }
+            if (evencube) {
+              grid[0][i] = gx+s;
+              grid[1][i] = gy+t;
+              grid[2][i] = gz+u;
+            }
+            else {
+              grid[0][i] = gx+1-s;
+              grid[1][i] = gy+1-t;
+              grid[2][i] = gz+1-u;
+            }
+            break;
+          }
+        }
+        else if (tetnum==4) {
+          tval1 = ( (G[1]-C[1])*(H[2]-G[2]) - (G[2]-C[2])*(H[1]-G[1]) )
+                   *(value[0][i]-G[0])
+                + ( (G[2]-C[2])*(H[0]-G[0]) - (G[0]-C[0])*(H[2]-G[2]) )
+                   *(value[1][i]-G[1])
+                + ( (G[0]-C[0])*(H[1]-G[1]) - (G[1]-C[1])*(H[0]-G[0]) )
+                   *(value[2][i]-G[2]);
+          tval2 = ( (G[1]-F[1])*(C[2]-G[2]) - (G[2]-F[2])*(C[1]-G[1]) )
+                   *(value[0][i]-G[0])
+                + ( (G[2]-F[2])*(C[0]-G[0]) - (G[0]-F[0])*(C[2]-G[2]) )
+                   *(value[1][i]-G[1])
+                + ( (G[0]-F[0])*(C[1]-G[1]) - (G[1]-F[1])*(C[0]-G[0]) )
+                   *(value[2][i]-G[2]);
+          tval3 = ( (G[1]-H[1])*(F[2]-G[2]) - (G[2]-H[2])*(F[1]-G[1]) )
+                   *(value[0][i]-G[0])
+                + ( (G[2]-H[2])*(F[0]-G[0]) - (G[0]-H[0])*(F[2]-G[2]) )
+                   *(value[1][i]-G[1])
+                + ( (G[0]-H[0])*(F[1]-G[1]) - (G[1]-H[1])*(F[0]-G[0]) )
+                   *(value[2][i]-G[2]);
+          test1 = (tval1 == 0) || ((tval1 > 0) == (!evencube)^Pos);
+          test2 = (tval2 == 0) || ((tval2 > 0) == (!evencube)^Pos);
+          test3 = (tval3 == 0) || ((tval3 > 0) == (!evencube)^Pos);
+
+          // if a test failed go to a new box
+          if (!test1 &&  evencube) gy++; // DOWN
+          if (!test1 && !evencube) gy--; // UP
+          if (!test2 &&  evencube) gx++; // RIGHT
+          if (!test2 && !evencube) gx--; // LEFT
+          if (!test3 &&  evencube) gz--; // BACK
+          if (!test3 && !evencube) gz++; // FORWARD
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0) gx = 0;
+          if (gy < 0) gy = 0;
+          if (gz < 0) gz = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if (gz > LengthZ-2) gz = LengthZ-2;
+
+          // Detect if the point is off the grid entirely
+          if ( (gx == ogx) && (gy == ogy) && (gz == ogz)
+            && (!test1 || !test2 || !test3) && !offgrid ) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (  ( (gx == ogx) && (gy == ogy) && (gz == ogz) )
+                || offgrid) {
+            // solve point
+            for (int j=0; j<3; j++) {
+              M[j] = (H[j]-G[j])*(C[(j+1)%3]-G[(j+1)%3])
+                   - (H[(j+1)%3]-G[(j+1)%3])*(C[j]-G[j]);
+              N[j] = (F[j]-G[j])*(C[(j+1)%3]-G[(j+1)%3])
+                   - (F[(j+1)%3]-G[(j+1)%3])*(C[j]-G[j]);
+              O[j] = (H[(j+1)%3]-G[(j+1)%3])*(C[(j+2)%3]-G[(j+2)%3])
+                   - (H[(j+2)%3]-G[(j+2)%3])*(C[(j+1)%3]-G[(j+1)%3]);
+              P[j] = (F[(j+1)%3]-G[(j+1)%3])*(C[(j+2)%3]-G[(j+2)%3])
+                   - (F[(j+2)%3]-G[(j+2)%3])*(C[(j+1)%3]-G[(j+1)%3]);
+              X[j] = value[(j+2)%3][i]*(C[(j+1)%3]-G[(j+1)%3])
+                   - value[(j+1)%3][i]*(C[(j+2)%3]-G[(j+2)%3])
+                   + G[(j+1)%3]*C[(j+2)%3] - G[(j+2)%3]*C[(j+1)%3];
+              Y[j] = value[j][i]*(C[(j+1)%3]-G[(j+1)%3])
+                   - value[(j+1)%3][i]*(C[j]-G[j])
+                   + G[(j+1)%3]*C[j] - G[j]*C[(j+1)%3];
+            }
+            double s, t, u;
+            // these if statements handle skewed grids
+            double d0 = M[0]*P[0] - N[0]*O[0];
+            double d1 = M[1]*P[1] - N[1]*O[1];
+            double d2 = M[2]*P[2] - N[2]*O[2];
+            double ad0 = Math.abs(d0);
+            double ad1 = Math.abs(d1);
+            double ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = 1 - (N[0]*X[0] + P[0]*Y[0])/d0;
+              t = 1 + (M[0]*X[0] + O[0]*Y[0])/d0;
+            }
+            else if (ad1 > ad2) {
+              s = 1 - (N[1]*X[1] + P[1]*Y[1])/d1;
+              t = 1 + (M[1]*X[1] + O[1]*Y[1])/d1;
+            }
+            else {
+              s = 1 - (N[2]*X[2] + P[2]*Y[2])/d2;
+              t = 1 + (M[2]*X[2] + O[2]*Y[2])/d2;
+            }
+            d0 = C[0]-G[0];
+            d1 = C[1]-G[1];
+            d2 = C[2]-G[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = ( value[0][i] - G[0] - (1-s)*(H[0]-G[0])
+                - (1-t)*(F[0]-G[0]) ) / d0;
+            }
+            else if (ad1 > ad2) {
+              u = ( value[1][i] - G[1] - (1-s)*(H[1]-G[1])
+                - (1-t)*(F[1]-G[1]) ) / d1;
+            }
+            else {
+              u = ( value[2][i] - G[2] - (1-s)*(H[2]-G[2])
+                - (1-t)*(F[2]-G[2]) ) / d2;
+            }
+            if (evencube) {
+              grid[0][i] = gx+s;
+              grid[1][i] = gy+t;
+              grid[2][i] = gz+u;
+            }
+            else {
+              grid[0][i] = gx+1-s;
+              grid[1][i] = gy+1-t;
+              grid[2][i] = gz+1-u;
+            }
+            break;
+          }
+        }
+        else {    // tetnum==5
+          tval1 = ( (F[1]-H[1])*(A[2]-F[2]) - (F[2]-H[2])*(A[1]-F[1]) )
+                   *(value[0][i]-F[0])
+                + ( (F[2]-H[2])*(A[0]-F[0]) - (F[0]-H[0])*(A[2]-F[2]) )
+                   *(value[1][i]-F[1])
+                + ( (F[0]-H[0])*(A[1]-F[1]) - (F[1]-H[1])*(A[0]-F[0]) )
+                   *(value[2][i]-F[2]);
+          tval2 = ( (C[1]-F[1])*(A[2]-C[2]) - (C[2]-F[2])*(A[1]-C[1]) )
+                   *(value[0][i]-C[0])
+                + ( (C[2]-F[2])*(A[0]-C[0]) - (C[0]-F[0])*(A[2]-C[2]) )
+                   *(value[1][i]-C[1])
+                + ( (C[0]-F[0])*(A[1]-C[1]) - (C[1]-F[1])*(A[0]-C[0]) )
+                   *(value[2][i]-C[2]);
+          tval3 = ( (C[1]-A[1])*(H[2]-C[2]) - (C[2]-A[2])*(H[1]-C[1]) )
+                   *(value[0][i]-C[0])
+                + ( (C[2]-A[2])*(H[0]-C[0]) - (C[0]-A[0])*(H[2]-C[2]) )
+                   *(value[1][i]-C[1])
+                + ( (C[0]-A[0])*(H[1]-C[1]) - (C[1]-A[1])*(H[0]-C[0]) )
+                   *(value[2][i]-C[2]);
+          tval4 = ( (F[1]-C[1])*(H[2]-F[2]) - (F[2]-C[2])*(H[1]-F[1]) )
+                   *(value[0][i]-F[0])
+                + ( (F[2]-C[2])*(H[0]-F[0]) - (F[0]-C[0])*(H[2]-F[2]) )
+                   *(value[1][i]-F[1])
+                + ( (F[0]-C[0])*(H[1]-F[1]) - (F[1]-C[1])*(H[0]-F[0]) )
+                   *(value[2][i]-F[2]);
+          test1 = (tval1 == 0) || ((tval1 > 0) == (!evencube)^Pos);
+          test2 = (tval2 == 0) || ((tval2 > 0) == (!evencube)^Pos);
+          test3 = (tval3 == 0) || ((tval3 > 0) == (!evencube)^Pos);
+          test4 = (tval4 == 0) || ((tval4 > 0) == (!evencube)^Pos);
+
+          // if a test failed go to a new tetrahedron
+          if (!test1 && test2 && test3 && test4) tetnum = 1;
+          if (test1 && !test2 && test3 && test4) tetnum = 2;
+          if (test1 && test2 && !test3 && test4) tetnum = 3;
+          if (test1 && test2 && test3 && !test4) tetnum = 4;
+          if ( (!test1 && !test2 && evencube)
+            || (!test3 && !test4 && !evencube) ) gy--; // GO UP
+          if ( (!test1 && !test3 && evencube)
+            || (!test2 && !test4 && !evencube) ) gx--; // GO LEFT
+          if ( (!test1 && !test4 && evencube)
+            || (!test2 && !test3 && !evencube) ) gz--; // GO BACK
+          if ( (!test2 && !test3 && evencube)
+            || (!test1 && !test4 && !evencube) ) gz++; // GO FORWARD
+          if ( (!test2 && !test4 && evencube)
+            || (!test1 && !test3 && !evencube) ) gx++; // GO RIGHT
+          if ( (!test3 && !test4 && evencube)
+            || (!test1 && !test2 && !evencube) ) gy++; // GO DOWN
+
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0) gx = 0;
+          if (gy < 0) gy = 0;
+          if (gz < 0) gz = 0;
+          if (gx > LengthX-2) gx = LengthX-2;
+          if (gy > LengthY-2) gy = LengthY-2;
+          if (gz > LengthZ-2) gz = LengthZ-2;
+
+          // Detect if the point is off the grid entirely
+          if (  ( (gx == ogx) && (gy == ogy) && (gz == ogz)
+               && (!test1 || !test2 || !test3 || !test4)
+               && (tetnum == 5)) || offgrid) {
+            offgrid = true;
+            boolean OX, OY, OZ, MX, MY, MZ, LX, LY, LZ;
+            OX = OY = OZ = MX = MY = MZ = LX = LY = LZ = false;
+            if (gx == 0) OX = true;
+            if (gy == 0) OY = true;
+            if (gz == 0) OZ = true;
+            if (gx == LengthX-2) LX = true;
+            if (gy == LengthY-2) LY = true;
+            if (gz == LengthZ-2) LZ = true;
+            if (!OX && !LX) MX = true;
+            if (!OY && !LY) MY = true;
+            if (!OZ && !LZ) MZ = true;
+            test1 = test2 = test3 = test4 = false;
+            // 26 cases
+            if (evencube) {
+              if (!LX && !LY && !LZ) tetnum = 1;
+              else if ( (LX && OY && MZ) || (MX && OY && LZ)
+                     || (LX && MY && LZ) || (LX && OY && LZ)
+                     || (MX && MY && LZ) || (LX && MY && MZ) ) tetnum = 2;
+              else if ( (OX && LY && MZ) || (OX && MY && LZ)
+                     || (MX && LY && LZ) || (OX && LY && LZ)
+                                         || (MX && LY && MZ) ) tetnum = 3;
+              else if ( (MX && LY && OZ) || (LX && MY && OZ)
+                     || (LX && LY && MZ) || (LX && LY && OZ) ) tetnum = 4;
+            }
+            else {
+              if (!OX && !OY && !OZ) tetnum = 1;
+              else if ( (OX && MY && OZ) || (MX && LY && OZ)
+                     || (OX && LY && MZ) || (OX && LY && OZ)
+                     || (MX && MY && OZ) || (OX && MY && MZ) ) tetnum = 2;
+              else if ( (LX && MY && OZ) || (MX && OY && OZ)
+                     || (LX && OY && MZ) || (LX && OY && OZ)
+                                         || (MX && OY && MZ) ) tetnum = 3;
+              else if ( (OX && OY && MZ) || (OX && MY && OZ)
+                     || (MX && OY && LZ) || (OX && OY && LZ) ) tetnum = 4;
+            }
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if ( (gx == ogx) && (gy == ogy) && (gz == ogz) && (tetnum == 5) ) {
+            // solve point
+
+            for (int j=0; j<3; j++) {
+              Q[j] = (H[j] + F[j] + A[j] - C[j])/2;
+            }
+
+            for (int j=0; j<3; j++) {
+              M[j] = (F[j]-Q[j])*(A[(j+1)%3]-Q[(j+1)%3])
+                   - (F[(j+1)%3]-Q[(j+1)%3])*(A[j]-Q[j]);
+              N[j] = (H[j]-Q[j])*(A[(j+1)%3]-Q[(j+1)%3])
+                   - (H[(j+1)%3]-Q[(j+1)%3])*(A[j]-Q[j]);
+              O[j] = (F[(j+1)%3]-Q[(j+1)%3])*(A[(j+2)%3]-Q[(j+2)%3])
+                   - (F[(j+2)%3]-Q[(j+2)%3])*(A[(j+1)%3]-Q[(j+1)%3]);
+              P[j] = (H[(j+1)%3]-Q[(j+1)%3])*(A[(j+2)%3]-Q[(j+2)%3])
+                   - (H[(j+2)%3]-Q[(j+2)%3])*(A[(j+1)%3]-Q[(j+1)%3]);
+              X[j] = value[(j+2)%3][i]*(A[(j+1)%3]-Q[(j+1)%3])
+                   - value[(j+1)%3][i]*(A[(j+2)%3]-Q[(j+2)%3])
+                   + Q[(j+1)%3]*A[(j+2)%3] - Q[(j+2)%3]*A[(j+1)%3];
+              Y[j] = value[j][i]*(A[(j+1)%3]-Q[(j+1)%3])
+                   - value[(j+1)%3][i]*(A[j]-Q[j])
+                   + Q[(j+1)%3]*A[j] - Q[j]*A[(j+1)%3];
+            }
+            double s, t, u;
+            // these if statements handle skewed grids
+            double d0 = M[0]*P[0] - N[0]*O[0];
+            double d1 = M[1]*P[1] - N[1]*O[1];
+            double d2 = M[2]*P[2] - N[2]*O[2];
+            double ad0 = Math.abs(d0);
+            double ad1 = Math.abs(d1);
+            double ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = (N[0]*X[0] + P[0]*Y[0])/d0;
+              t = -(M[0]*X[0] + O[0]*Y[0])/d0;
+            }
+            else if (ad1 > ad2) {
+              s = (N[1]*X[1] + P[1]*Y[1])/d1;
+              t = -(M[1]*X[1] + O[1]*Y[1])/d1;
+            }
+            else {
+              s = (N[2]*X[2] + P[2]*Y[2])/d2;
+              t = -(M[2]*X[2] + O[2]*Y[2])/d2;
+            }
+            d0 = A[0]-Q[0];
+            d1 = A[1]-Q[1];
+            d2 = A[2]-Q[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = ( value[0][i] - Q[0] - s*(F[0]-Q[0])
+                - t*(H[0]-Q[0]) ) / d0;
+            }
+            else if (ad1 > ad2) {
+              u = ( value[1][i] - Q[1] - s*(F[1]-Q[1])
+                - t*(H[1]-Q[1]) ) / d1;
+            }
+            else {
+              u = ( value[2][i] - Q[2] - s*(F[2]-Q[2])
+                - t*(H[2]-Q[2]) ) / d2;
+            }
+            if (evencube) {
+              grid[0][i] = gx+s;
+              grid[1][i] = gy+t;
+              grid[2][i] = gz+u;
+            }
+            else {
+              grid[0][i] = gx+1-s;
+              grid[1][i] = gy+1-t;
+              grid[2][i] = gz+1-u;
+            }
+            break;
+          }
+        }
+      }
+      // allow estimations up to 0.5 boxes outside of defined samples
+      if ( (grid[0][i] <= -0.5) || (grid[0][i] >= LengthX-0.5)
+        || (grid[1][i] <= -0.5) || (grid[1][i] >= LengthY-0.5)
+        || (grid[2][i] <= -0.5) || (grid[2][i] >= LengthZ-0.5) ) {
+        grid[0][i] = grid[1][i] = grid[2][i] = Double.NaN;
+      }
+    }
+    return grid;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void doubleToInterp(double[][] value, int[][] indices,
+    double[][] weights) throws VisADException
+  {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded3DDoubleSet.doubleToInterp: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length) {
+      throw new SetException("Gridded3DDoubleSet.doubleToInterp: indices length " +
+                             indices.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    if (weights.length != length) {
+      throw new SetException("Gridded3DDoubleSet.doubleToInterp: weights length " +
+                             weights.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    // convert value array to grid coord array
+    double[][] grid = doubleToGrid(value);
+
+    int i, j, k; // loop indices
+    int lis; // temporary length of is & cs
+    int length_is; // final length of is & cs, varies by i
+    int isoff; // offset along one grid dimension
+    double a, b; // weights along one grid dimension; a + b = 1.0
+    int[] is; // array of indices, becomes part of indices
+    double[] cs; // array of coefficients, become part of weights
+
+    int base; // base index, as would be returned by valueToIndex
+    int[] l = new int[ManifoldDimension]; // integer 'factors' of base
+    // fractions with l; -0.5 <= c <= 0.5
+    double[] c = new double[ManifoldDimension];
+
+    // array of index offsets by grid dimension
+    int[] off = new int[ManifoldDimension];
+    off[0] = 1;
+    for (j=1; j<ManifoldDimension; j++) off[j] = off[j-1] * Lengths[j-1];
+
+    for (i=0; i<length; i++) {
+      // compute length_is, base, l & c
+      length_is = 1;
+      if (Double.isNaN(grid[ManifoldDimension-1][i])) {
+        base = -1;
+      }
+      else {
+        l[ManifoldDimension-1] = (int) (grid[ManifoldDimension-1][i] + 0.5);
+        // WLH 23 Dec 99
+        if (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1]) {
+          l[ManifoldDimension-1]--;
+        }
+        c[ManifoldDimension-1] = grid[ManifoldDimension-1][i] -
+                                 ((double) l[ManifoldDimension-1]);
+        if (!((l[ManifoldDimension-1] == 0 && c[ManifoldDimension-1] <= 0.0) ||
+              (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1] - 1 &&
+               c[ManifoldDimension-1] >= 0.0))) {
+          // only interp along ManifoldDimension-1
+          // if between two valid grid coords
+          length_is *= 2;
+        }
+        base = l[ManifoldDimension-1];
+      }
+      for (j=ManifoldDimension-2; j>=0 && base>=0; j--) {
+        if (Double.isNaN(grid[j][i])) {
+          base = -1;
+        }
+        else {
+          l[j] = (int) (grid[j][i] + 0.5);
+          if (l[j] == Lengths[j]) l[j]--; // WLH 23 Dec 99
+          c[j] = grid[j][i] - ((double) l[j]);
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            length_is *= 2;
+          }
+          base = l[j] + Lengths[j] * base;
+        }
+      }
+
+      if (base < 0) {
+        // value is out of grid so return null
+        is = null;
+        cs = null;
+      }
+      else {
+        // create is & cs of proper length, and init first element
+        is = new int[length_is];
+        cs = new double[length_is];
+        is[0] = base;
+        cs[0] = 1.0f;
+        lis = 1;
+
+        for (j=0; j<ManifoldDimension; j++) {
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            if (c[j] >= 0.0) {
+              // grid coord above base
+              isoff = off[j];
+              a = 1.0f - c[j];
+              b = c[j];
+            }
+            else {
+              // grid coord below base
+              isoff = -off[j];
+              a = 1.0f + c[j];
+              b = -c[j];
+            }
+            // double is & cs; adjust new offsets; split weights
+            for (k=0; k<lis; k++) {
+              is[k+lis] = is[k] + isoff;
+              cs[k+lis] = cs[k] * b;
+              cs[k] *= a;
+            }
+            lis *= 2;
+          }
+        }
+      }
+      indices[i] = is;
+      weights[i] = cs;
+    }
+  }
+
+
+  // Miscellaneous Set methods that must be overridden
+  // (this code is duplicated throughout all *DoubleSet classes)
+
+  void init_doubles(double[][] samples, boolean copy)
+       throws VisADException {
+    if (samples.length != DomainDimension) {
+      throw new SetException("Gridded3DDoubleSet.init_doubles: samples dimension " +
+                             samples.length +
+                             " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    if (Length == 0) {
+      // Length set in init_lengths, but not called for IrregularSet
+      Length = samples[0].length;
+    }
+    else {
+      if (Length != samples[0].length) {
+        throw new SetException("Gridded3DDoubleSet.init_doubles: " +
+                               "samples[0] length " + samples[0].length +
+                               " doesn't match expected length " + Length);
+      }
+    }
+    // MEM
+    if (copy) {
+      Samples = new double[DomainDimension][Length];
+    }
+    else {
+      Samples = samples;
+    }
+    for (int j=0; j<DomainDimension; j++) {
+      if (samples[j].length != Length) {
+        throw new SetException("Gridded3DDoubleSet.init_doubles: " +
+                               "samples[" + j + "] length " +
+                               samples[0].length +
+                               " doesn't match expected length " + Length);
+      }
+      double[] samplesJ = samples[j];
+      double[] SamplesJ = Samples[j];
+      if (copy) {
+        System.arraycopy(samplesJ, 0, SamplesJ, 0, Length);
+      }
+      Low[j] = Double.POSITIVE_INFINITY;
+      Hi[j] = Double.NEGATIVE_INFINITY;
+      double sum = 0.0f;
+      for (int i=0; i<Length; i++) {
+        if (SamplesJ[i] == SamplesJ[i] && !Double.isInfinite(SamplesJ[i])) {
+          if (SamplesJ[i] < Low[j]) Low[j] = SamplesJ[i];
+          if (SamplesJ[i] > Hi[j]) Hi[j] = SamplesJ[i];
+        }
+        else {
+          SamplesJ[i] = Double.NaN;
+        }
+        sum += SamplesJ[i];
+      }
+      if (SetErrors[j] != null ) {
+        SetErrors[j] =
+          new ErrorEstimate(SetErrors[j].getErrorValue(), sum / Length,
+                            Length, SetErrors[j].getUnit());
+      }
+      super.Low[j] = (float) Low[j];
+      super.Hi[j] = (float) Hi[j];
+    }
+  }
+
+  public void cram_missing(boolean[] range_select) {
+    int n = Math.min(range_select.length, Samples[0].length);
+    for (int i=0; i<n; i++) {
+      if (!range_select[i]) Samples[0][i] = Double.NaN;
+    }
+  }
+
+  public boolean isMissing() {
+    return (Samples == null);
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof Gridded3DDoubleSet) || set == null) return false;
+    if (this == set) return true;
+    if (testNotEqualsCache((Set) set)) return false;
+    if (testEqualsCache((Set) set)) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    try {
+      int i, j;
+      if (DomainDimension != ((Gridded3DDoubleSet) set).getDimension() ||
+          ManifoldDimension !=
+            ((Gridded3DDoubleSet) set).getManifoldDimension() ||
+          Length != ((Gridded3DDoubleSet) set).getLength()) return false;
+      for (j=0; j<ManifoldDimension; j++) {
+        if (Lengths[j] != ((Gridded3DDoubleSet) set).getLength(j)) {
+          return false;
+        }
+      }
+      // Sets are immutable, so no need for 'synchronized'
+      double[][] samples = ((Gridded3DDoubleSet) set).getDoubles(false);
+      if (Samples != null && samples != null) {
+        for (j=0; j<DomainDimension; j++) {
+          for (i=0; i<Length; i++) {
+            if (Samples[j][i] != samples[j][i]) {
+              addNotEqualsCache((Set) set);
+              return false;
+            }
+          }
+        }
+      }
+      else {
+        double[][] this_samples = getDoubles(false);
+        if (this_samples == null) {
+          if (samples != null) {
+            return false;
+          }
+        } else if (samples == null) {
+          return false;
+        } else {
+          for (j=0; j<DomainDimension; j++) {
+            for (i=0; i<Length; i++) {
+              if (this_samples[j][i] != samples[j][i]) {
+                addNotEqualsCache((Set) set);
+                return false;
+              }
+            }
+          }
+        }
+      }
+      addEqualsCache((Set) set);
+      return true;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                    A clone of this instance.
+   */
+  public Object clone() {
+    Gridded3DDoubleSet clone = (Gridded3DDoubleSet)super.clone();
+    
+    if (Samples != null) {
+      /*
+       * The Samples array is cloned because getDoubles(false) allows clients
+       * to manipulate the array and the general clone() contract forbids
+       * cross-clone contamination.
+       */
+      clone.Samples = (double[][])Samples.clone();
+      for (int i = 0; i < Samples.length; i++)
+        clone.Samples[i] = (double[])Samples[i].clone();
+    }
+    
+    return clone;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    if (ManifoldDimension == 3) {
+      return new Gridded3DDoubleSet(type, Samples, LengthX, LengthY, LengthZ,
+                              DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+    else if (ManifoldDimension == 2) {
+      return new Gridded3DDoubleSet(type, Samples, LengthX, LengthY,
+                              DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+    else {
+      return new Gridded3DDoubleSet(type, Samples, LengthX,
+                              DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+  }
+/* WLH 3 April 2003
+  public Object cloneButType(MathType type) throws VisADException {
+    return new Gridded3DDoubleSet(type, Samples, Length,
+      DomainCoordinateSystem, SetUnits, SetErrors);
+  }
+*/
+}
+
diff --git a/visad/Gridded3DSet.java b/visad/Gridded3DSet.java
new file mode 100644
index 0000000..bd2c08d
--- /dev/null
+++ b/visad/Gridded3DSet.java
@@ -0,0 +1,5657 @@
+//
+// Gridded3DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.io.InputStreamReader;
+import java.util.Enumeration;
+
+/**
+ * Gridded3DSet represents a finite set of samples of R^3.
+ * <P>
+ */
+
+public class Gridded3DSet extends GriddedSet {
+
+  private static final long serialVersionUID = 1L;
+
+  int LengthX, LengthY, LengthZ;
+
+  float LowX, HiX, LowY, HiY, LowZ, HiZ;
+
+  /**
+   * a 3-D set whose topology is a lengthX x lengthY x lengthZ grid, with null
+   * errors, CoordinateSystem and Units are defaults from type
+   */
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      int lengthY, int lengthZ) throws VisADException {
+    this(type, samples, lengthX, lengthY, lengthZ, null, null, null);
+  }
+
+  /**
+   * a 3-D set whose topology is a lengthX x lengthY x lengthZ grid; samples
+   * array is organized float[3][number_of_samples] where lengthX * lengthY *
+   * lengthZ = number_of_samples; samples must form a non-degenerate 3-D grid
+   * (no bow-tie-shaped grid cubes); the X component increases fastest and the Z
+   * component slowest in the second index of samples; coordinate_system and
+   * units must be compatible with defaults for type, or may be null; errors may
+   * be null
+   */
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      int lengthY, int lengthZ, CoordinateSystem coord_sys, Unit[] units,
+      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, lengthY, lengthZ, coord_sys, units, errors,
+        true, true);
+  }
+
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      int lengthY, int lengthZ, CoordinateSystem coord_sys, Unit[] units,
+      ErrorEstimate[] errors, boolean copy) throws VisADException {
+    this(type, samples, lengthX, lengthY, lengthZ, coord_sys, units, errors,
+        copy, true);
+  }
+
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      int lengthY, int lengthZ, CoordinateSystem coord_sys, Unit[] units,
+      ErrorEstimate[] errors, boolean copy, boolean test) throws VisADException {
+    super(type, samples, make_lengths(lengthX, lengthY, lengthZ), coord_sys,
+        units, errors, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LengthY = Lengths[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+    LengthZ = Lengths[2];
+
+    float[][]mySamples = getMySamples();
+    if (mySamples != null && Lengths[0] > 1 && Lengths[1] > 1 && Lengths[2] > 1) {
+
+      // Samples consistency test
+      float[] t000 = new float[3];
+      float[] t100 = new float[3];
+      float[] t010 = new float[3];
+      float[] t001 = new float[3];
+      float[] t110 = new float[3];
+      float[] t101 = new float[3];
+      float[] t011 = new float[3];
+      float[] t111 = new float[3];
+      for (int v = 0; v < 3; v++) {
+        t000[v] = mySamples[v][0];
+        t100[v] = mySamples[v][1];
+        t010[v] = mySamples[v][LengthX];
+        t001[v] = mySamples[v][LengthY * LengthX];
+        t110[v] = mySamples[v][LengthX + 1];
+        t101[v] = mySamples[v][LengthY * LengthX + 1];
+        t011[v] = mySamples[v][(LengthY + 1) * LengthX];
+        t111[v] = mySamples[v][(LengthY + 1) * LengthX + 1];
+      }
+      /*
+       * CICERO Pos = ( ( (t100[1]-t000[1])(t101[2]-t100[2]) -
+       * (t100[2]-t000[2])(t101[1]-t100[1]) )(t110[0]-t100[0]) ) + ( (
+       * (t100[2]-t000[2])(t101[0]-t100[0]) - (t100[0]-t000[0])(t101[2]-t100[2])
+       * )(t110[1]-t100[1]) ) + ( ( (t100[0]-t000[0])(t101[1]-t100[1]) -
+       * (t100[1]-t000[1])(t101[0]-t100[0]) )(t110[2]-t100[2]) ) > 0;
+       */
+      float xpos = (((t100[1] - t000[1]) * (t101[2] - t100[2]) - (t100[2] - t000[2])
+          * (t101[1] - t100[1])) * (t110[0] - t100[0]))
+          + (((t100[2] - t000[2]) * (t101[0] - t100[0]) - (t100[0] - t000[0])
+              * (t101[2] - t100[2])) * (t110[1] - t100[1]))
+          + (((t100[0] - t000[0]) * (t101[1] - t100[1]) - (t100[1] - t000[1])
+              * (t101[0] - t100[0])) * (t110[2] - t100[2]));
+      Pos = (xpos > 0);
+
+      if (test) {
+        // CICERO
+        if (xpos == 0) {
+          throw new SetException(
+              "Gridded3DSet: samples do not form a valid grid");
+        }
+
+        for (int i = 0; i < Length; i++) {
+          if (mySamples[0][i] != mySamples[0][i]) {
+            throw new SetException(
+                "Gridded3DSet: samples values may not be missing");
+          }
+        }
+
+
+        float[] v000 = new float[3];
+        float[] v100 = new float[3];
+        float[] v010 = new float[3];
+        float[] v001 = new float[3];
+        float[] v110 = new float[3];
+        float[] v101 = new float[3];
+        float[] v011 = new float[3];
+        float[] v111 = new float[3];
+
+        for (int k = 0; k < LengthZ - 1; k++) {
+          for (int j = 0; j < LengthY - 1; j++) {
+            for (int i = 0; i < LengthX - 1; i++) {
+              for (int v = 0; v < 3; v++) {
+                int zadd = LengthY * LengthX;
+                int base = k * zadd + j * LengthX + i;
+                v000[v] = mySamples[v][base];
+                v100[v] = mySamples[v][base + 1];
+                v010[v] = mySamples[v][base + LengthX];
+                v001[v] = mySamples[v][base + zadd];
+                v110[v] = mySamples[v][base + LengthX + 1];
+                v101[v] = mySamples[v][base + zadd + 1];
+                v011[v] = mySamples[v][base + zadd + LengthX];
+                v111[v] = mySamples[v][base + zadd + LengthX + 1];
+              }
+              /*
+               * CICERO if ((( ( (v100[1]-v000[1])(v101[2]-v100[2]) // test 1 -
+               * (v100[2]-v000[2])(v101[1]-v100[1]) )(v110[0]-v100[0]) ) + ( (
+               * (v100[2]-v000[2])(v101[0]-v100[0]) -
+               * (v100[0]-v000[0])(v101[2]-v100[2]) )(v110[1]-v100[1]) ) + ( (
+               * (v100[0]-v000[0])(v101[1]-v100[1]) -
+               * (v100[1]-v000[1])(v101[0]-v100[0]) )(v110[2]-v100[2]) ) > 0 !=
+               * Pos) || (( ( (v101[1]-v100[1])(v001[2]-v101[2]) // test 2 -
+               * (v101[2]-v100[2])(v001[1]-v101[1]) )(v111[0]-v101[0]) ) + ( (
+               * (v101[2]-v100[2])(v001[0]-v101[0]) -
+               * (v101[0]-v100[0])(v001[2]-v101[2]) )(v111[1]-v101[1]) ) + ( (
+               * (v101[0]-v100[0])(v001[1]-v101[1]) -
+               * (v101[1]-v100[1])(v001[0]-v101[0]) )(v111[2]-v101[2]) ) > 0 !=
+               * Pos) || (( ( (v001[1]-v101[1])(v000[2]-v001[2]) // test 3 -
+               * (v001[2]-v101[2])(v000[1]-v001[1]) )(v011[0]-v001[0]) ) + ( (
+               * (v001[2]-v101[2])(v000[0]-v001[0]) -
+               * (v001[0]-v101[0])(v000[2]-v001[2]) )(v011[1]-v001[1]) ) + ( (
+               * (v001[0]-v101[0])(v000[1]-v001[1]) -
+               * (v001[1]-v101[1])(v000[0]-v001[0]) )(v011[2]-v001[2]) ) > 0 !=
+               * Pos) || (( ( (v000[1]-v001[1])(v100[2]-v000[2]) // test 4 -
+               * (v000[2]-v001[2])(v100[1]-v000[1]) )(v010[0]-v000[0]) ) + ( (
+               * (v000[2]-v001[2])(v100[0]-v000[0]) -
+               * (v000[0]-v001[0])(v100[2]-v000[2]) )(v010[1]-v000[1]) ) + ( (
+               * (v000[0]-v001[0])(v100[1]-v000[1]) -
+               * (v000[1]-v001[1])(v100[0]-v000[0]) )(v010[2]-v000[2]) ) > 0 !=
+               * Pos) || (( ( (v110[1]-v111[1])(v010[2]-v110[2]) // test 5 -
+               * (v110[2]-v111[2])(v010[1]-v110[1]) )(v100[0]-v110[0]) ) + ( (
+               * (v110[2]-v111[2])(v010[0]-v110[0]) -
+               * (v110[0]-v111[0])(v010[2]-v110[2]) )(v100[1]-v110[1]) ) + ( (
+               * (v110[0]-v111[0])(v010[1]-v110[1]) -
+               * (v110[1]-v111[1])(v010[0]-v110[0]) )(v100[2]-v110[2]) ) > 0 !=
+               * Pos) || (( ( (v111[1]-v011[1])(v110[2]-v111[2]) // test 6 -
+               * (v111[2]-v011[2])(v110[1]-v111[1]) )(v101[0]-v111[0]) ) + ( (
+               * (v111[2]-v011[2])(v110[0]-v111[0]) -
+               * (v111[0]-v011[0])(v110[2]-v111[2]) )(v101[1]-v111[1]) ) + ( (
+               * (v111[0]-v011[0])(v110[1]-v111[1]) -
+               * (v111[1]-v011[1])(v110[0]-v111[0]) )(v101[2]-v111[2]) ) > 0 !=
+               * Pos) || (( ( (v011[1]-v010[1])(v111[2]-v011[2]) // test 7 -
+               * (v011[2]-v010[2])(v111[1]-v011[1]) )(v001[0]-v011[0]) ) + ( (
+               * (v011[2]-v010[2])(v111[0]-v011[0]) -
+               * (v011[0]-v010[0])(v111[2]-v011[2]) )(v001[1]-v011[1]) ) + ( (
+               * (v011[0]-v010[0])(v111[1]-v011[1]) -
+               * (v011[1]-v010[1])(v111[0]-v011[0]) )(v001[2]-v011[2]) ) > 0 !=
+               * Pos) || (( ( (v010[1]-v110[1])(v011[2]-v010[2]) // test 8 -
+               * (v010[2]-v110[2])(v011[1]-v010[1]) )(v000[0]-v010[0]) ) + ( (
+               * (v010[2]-v110[2])(v011[0]-v010[0]) -
+               * (v010[0]-v110[0])(v011[2]-v010[2]) )(v000[1]-v010[1]) ) + ( (
+               * (v010[0]-v110[0])(v011[1]-v010[1]) -
+               * (v010[1]-v110[1])(v011[0]-v010[0]) )(v000[2]-v010[2]) ) > 0 !=
+               * Pos))
+               */
+              // CICERO
+              float w1 = ((((v100[1] - v000[1]) * (v101[2] - v100[2]) - (v100[2] - v000[2])
+                  * (v101[1] - v100[1])) * (v110[0] - v100[0]))
+                  + (((v100[2] - v000[2]) * (v101[0] - v100[0]) - (v100[0] - v000[0])
+                      * (v101[2] - v100[2])) * (v110[1] - v100[1])) + (((v100[0] - v000[0])
+                  * (v101[1] - v100[1]) - (v100[1] - v000[1])
+                  * (v101[0] - v100[0])) * (v110[2] - v100[2])));
+              float w2 = ((((v101[1] - v100[1]) * (v001[2] - v101[2]) - (v101[2] - v100[2])
+                  * (v001[1] - v101[1])) * (v111[0] - v101[0]))
+                  + (((v101[2] - v100[2]) * (v001[0] - v101[0]) - (v101[0] - v100[0])
+                      * (v001[2] - v101[2])) * (v111[1] - v101[1])) + (((v101[0] - v100[0])
+                  * (v001[1] - v101[1]) - (v101[1] - v100[1])
+                  * (v001[0] - v101[0])) * (v111[2] - v101[2])));
+              float w3 = ((((v001[1] - v101[1]) * (v000[2] - v001[2]) - (v001[2] - v101[2])
+                  * (v000[1] - v001[1])) * (v011[0] - v001[0]))
+                  + (((v001[2] - v101[2]) * (v000[0] - v001[0]) - (v001[0] - v101[0])
+                      * (v000[2] - v001[2])) * (v011[1] - v001[1])) + (((v001[0] - v101[0])
+                  * (v000[1] - v001[1]) - (v001[1] - v101[1])
+                  * (v000[0] - v001[0])) * (v011[2] - v001[2])));
+              float w4 = ((((v000[1] - v001[1]) * (v100[2] - v000[2]) - (v000[2] - v001[2])
+                  * (v100[1] - v000[1])) * (v010[0] - v000[0]))
+                  + (((v000[2] - v001[2]) * (v100[0] - v000[0]) - (v000[0] - v001[0])
+                      * (v100[2] - v000[2])) * (v010[1] - v000[1])) + (((v000[0] - v001[0])
+                  * (v100[1] - v000[1]) - (v000[1] - v001[1])
+                  * (v100[0] - v000[0])) * (v010[2] - v000[2])));
+              float w5 = ((((v110[1] - v111[1]) * (v010[2] - v110[2]) - (v110[2] - v111[2])
+                  * (v010[1] - v110[1])) * (v100[0] - v110[0]))
+                  + (((v110[2] - v111[2]) * (v010[0] - v110[0]) - (v110[0] - v111[0])
+                      * (v010[2] - v110[2])) * (v100[1] - v110[1])) + (((v110[0] - v111[0])
+                  * (v010[1] - v110[1]) - (v110[1] - v111[1])
+                  * (v010[0] - v110[0])) * (v100[2] - v110[2])));
+              float w6 = ((((v111[1] - v011[1]) * (v110[2] - v111[2]) - (v111[2] - v011[2])
+                  * (v110[1] - v111[1])) * (v101[0] - v111[0]))
+                  + (((v111[2] - v011[2]) * (v110[0] - v111[0]) - (v111[0] - v011[0])
+                      * (v110[2] - v111[2])) * (v101[1] - v111[1])) + (((v111[0] - v011[0])
+                  * (v110[1] - v111[1]) - (v111[1] - v011[1])
+                  * (v110[0] - v111[0])) * (v101[2] - v111[2])));
+              float w7 = ((((v011[1] - v010[1]) * (v111[2] - v011[2]) - (v011[2] - v010[2])
+                  * (v111[1] - v011[1])) * (v001[0] - v011[0]))
+                  + (((v011[2] - v010[2]) * (v111[0] - v011[0]) - (v011[0] - v010[0])
+                      * (v111[2] - v011[2])) * (v001[1] - v011[1])) + (((v011[0] - v010[0])
+                  * (v111[1] - v011[1]) - (v011[1] - v010[1])
+                  * (v111[0] - v011[0])) * (v001[2] - v011[2])));
+              float w8 = ((((v010[1] - v110[1]) * (v011[2] - v010[2]) - (v010[2] - v110[2])
+                  * (v011[1] - v010[1])) * (v000[0] - v010[0]))
+                  + (((v010[2] - v110[2]) * (v011[0] - v010[0]) - (v010[0] - v110[0])
+                      * (v011[2] - v010[2])) * (v000[1] - v010[1])) + (((v010[0] - v110[0])
+                  * (v011[1] - v010[1]) - (v010[1] - v110[1])
+                  * (v011[0] - v010[0])) * (v000[2] - v010[2])));
+              if ((w1 > 0 != Pos) || w1 == 0 || (w2 > 0 != Pos) || w2 == 0
+                  || (w3 > 0 != Pos) || w3 == 0 || (w4 > 0 != Pos) || w4 == 0
+                  || (w5 > 0 != Pos) || w5 == 0 || (w6 > 0 != Pos) || w6 == 0
+                  || (w7 > 0 != Pos) || w7 == 0 || (w8 > 0 != Pos) || w8 == 0) {
+                throw new SetException("Gridded3DSet: samples do not form "
+                    + "a valid grid (" + i + "," + j + "," + k + ")");
+              }
+            }
+          }
+        }
+      } // end if (test)
+    }
+  }
+
+  /**
+   * a 3-D set with manifold dimension = 2, with null errors, CoordinateSystem
+   * and Units are defaults from type
+   */
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX, int lengthY)
+      throws VisADException {
+    this(type, samples, lengthX, lengthY, null, null, null);
+  }
+
+  /**
+   * a 3-D set with manifold dimension = 2; samples array is organized
+   * float[3][number_of_samples] where lengthX * lengthY = number_of_samples; no
+   * geometric constraint on samples; the X component increases fastest in the
+   * second index of samples; coordinate_system and units must be compatible
+   * with defaults for type, or may be null; errors may be null
+   */
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      int lengthY, CoordinateSystem coord_sys, Unit[] units,
+      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengthX, lengthY, coord_sys, units, errors, true);
+  }
+
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      int lengthY, CoordinateSystem coord_sys, Unit[] units,
+      ErrorEstimate[] errors, boolean copy) throws VisADException {
+    super(type, samples, Gridded2DSet.make_lengths(lengthX, lengthY),
+        coord_sys, units, errors, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LengthY = Lengths[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+
+    // no Samples consistency test
+  }
+
+  /**
+   * a 3-D set with manifold dimension = 1, with null errors, CoordinateSystem
+   * and Units are defaults from type
+   */
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX)
+      throws VisADException {
+    this(type, samples, lengthX, null, null, null);
+  }
+
+  /**
+   * a 3-D set with manifold dimension = 1; samples array is organized
+   * float[3][number_of_samples] where lengthX = number_of_samples; no geometric
+   * constraint on samples; coordinate_system and units must be compatible with
+   * defaults for type, or may be null; errors may be null
+   */
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      CoordinateSystem coord_sys, Unit[] units, ErrorEstimate[] errors)
+      throws VisADException {
+    this(type, samples, lengthX, coord_sys, units, errors, true);
+  }
+
+  public Gridded3DSet(MathType type, float[][] samples, int lengthX,
+      CoordinateSystem coord_sys, Unit[] units, ErrorEstimate[] errors,
+      boolean copy) throws VisADException {
+    super(type, samples, Gridded1DSet.make_lengths(lengthX), coord_sys, units,
+        errors, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LengthX = Lengths[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+
+    // no Samples consistency test
+  }
+
+  static int[] make_lengths(int lengthX, int lengthY, int lengthZ) {
+    int[] lens = new int[3];
+    lens[0] = lengthX;
+    lens[1] = lengthY;
+    lens[2] = lengthZ;
+    return lens;
+  }
+
+  @Override
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int length = index.length;
+    float[][]mySamples = getMySamples();
+    if (mySamples == null) {
+      // not used - over-ridden by Linear3DSet.indexToValue
+      int indexX, indexY, indexZ;
+      int k;
+      float[][] grid = new float[ManifoldDimension][length];
+      for (int i = 0; i < length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          indexX = index[i] % LengthX;
+          k = index[i] / LengthX;
+          indexY = k % LengthY;
+          indexZ = k / LengthY;
+        } else {
+          indexX = -1;
+          indexY = -1;
+          indexZ = -1;
+        }
+        grid[0][i] = indexX;
+        grid[1][i] = indexY;
+        grid[2][i] = indexZ;
+      }
+      return gridToValue(grid);
+    } else {
+      float[][] values = new float[3][length];
+      for (int i = 0; i < length; i++) {
+        if (0 <= index[i] && index[i] < Length) {
+          values[0][i] = mySamples[0][index[i]];
+          values[1][i] = mySamples[1][index[i]];
+          values[2][i] = mySamples[2][index[i]];
+        } else {
+          values[0][i] = Float.NaN;
+          values[1][i] = Float.NaN;
+          values[2][i] = Float.NaN;
+        }
+      }
+      return values;
+    }
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  @Override
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("Gridded3DSet.valueToIndex: value dimension "
+          + value.length + " not equal to Domain dimension " + DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+
+    float[][] grid = valueToGrid(value);
+    float[] grid0 = grid[0];
+    float[] grid1 = grid[1];
+    float[] grid2 = grid[2];
+    float g0, g1, g2;
+    for (int i = 0; i < length; i++) {
+      g0 = grid0[i];
+      g1 = grid1[i];
+      g2 = grid2[i];
+      /*
+       * WLH 24 Oct 97 index[i] = (Float.isNaN(g0) || Float.isNaN(g1) ||
+       * Float.isNaN(g2)) ? -1 :
+       */
+      // test for missing
+      /*
+       * WLH 2 April 99 index[i] = (g0 != g0 || g1 != g1 || g2 != g2) ? 1 :
+       */
+      index[i] = (g0 != g0 || g1 != g1 || g2 != g2) ? -1 : ((int) (g0 + 0.5))
+          + LengthX * (((int) (g1 + 0.5)) + LengthY * ((int) (g2 + 0.5)));
+    }
+    return index;
+  }
+
+  /**
+   * Transform an array of non-integer grid coordinates to an array of values
+   * in R^DomainDimension.
+   * @param grid Grid values, where dim1 is the grid coordinate dimension
+   *  and dim2 is the length of the grid.
+   * @return R^n coordinates where dim1 is DomainDimension and dim2 is the
+   *  length of the grid.
+   * @throws visad.VisADException
+   */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    return gridToValue(grid, false);
+  }
+
+  /**
+   * Transform an array of non-integer grid coordinates to an array of values
+   * in R^DomainDimension.
+   * @param grid Grid values, where dim1 is the grid coordinate dimension
+   *  and dim2 is the length of the grid.
+   * @return R^n coordinates where dim1 is 1 and dim2 is gird_len*coord_dim.
+   * @throws visad.VisADException
+   */
+  public float[][] gridToValue(float[][] grid, boolean altFormat) throws VisADException {
+    if (grid.length != ManifoldDimension) {
+      throw new SetException("Gridded3DSet.gridToValue: grid dimension "
+          + grid.length + " not equal to Manifold dimension "
+          + ManifoldDimension);
+    }
+    if (ManifoldDimension == 3) {
+      return altFormat ? gridToValue3DAlt(grid) : gridToValue3D(grid);
+    } else if (ManifoldDimension == 2) {
+      return altFormat ? gridToValue2DAlt(grid) : gridToValue2D(grid);
+    } else {
+      throw new SetException("Gridded3DSet.gridToValue: ManifoldDimension "
+          + "must be 2 or 3");
+    }
+  }
+
+  private float[][] gridToValue2D(float[][] grid) throws VisADException {
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Gridded3DSet.gridToValue: requires all grid "
+          + "dimensions to be > 1");
+    }
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(grid[0].length, grid[1].length);
+    float[][] value = new float[3][length];
+    float[][]mySamples = getMySamples();
+    for (int i = 0; i < length; i++) {
+      // let gx and gy by the current grid values
+      float gx = grid[0][i];
+      float gy = grid[1][i];
+      if ((gx < -0.5) || (gy < -0.5) || (gx > LengthX - 0.5)
+          || (gy > LengthY - 0.5)) {
+        value[0][i] = value[1][i] = value[2][i] = Float.NaN;
+      } else if (Length == 1) {
+        value[0][i] = mySamples[0][0];
+        value[1][i] = mySamples[1][0];
+        value[2][i] = mySamples[2][0];
+      } else {
+        // calculate closest integer variables
+        int igx = (int) gx;
+        int igy = (int) gy;
+        if (igx < 0)
+          igx = 0;
+        if (igx > LengthX - 2)
+          igx = LengthX - 2;
+        if (igy < 0)
+          igy = 0;
+        if (igy > LengthY - 2)
+          igy = LengthY - 2;
+
+        // set up conversion to 1D Samples array
+        int[][] s = { { LengthX * igy + igx, // (0, 0)
+            LengthX * (igy + 1) + igx }, // (0, 1)
+            { LengthX * igy + igx + 1, // (1, 0)
+                LengthX * (igy + 1) + igx + 1 } }; // (1, 1)
+        if (gx + gy - igx - igy - 1 <= 0) {
+          // point is in LOWER triangle
+          for (int j = 0; j < 3; j++) {
+            value[j][i] = mySamples[j][s[0][0]] + (gx - igx)
+                * (mySamples[j][s[1][0]] - mySamples[j][s[0][0]]) + (gy - igy)
+                * (mySamples[j][s[0][1]] - mySamples[j][s[0][0]]);
+          }
+        } else {
+          // point is in UPPER triangle
+          for (int j = 0; j < 3; j++) {
+            value[j][i] = mySamples[j][s[1][1]] + (1 + igx - gx)
+                * (mySamples[j][s[0][1]] - mySamples[j][s[1][1]]) + (1 + igy - gy)
+                * (mySamples[j][s[1][0]] - mySamples[j][s[1][1]]);
+          }
+        }
+        /*
+         * for (int j=0; j<3; j++) { if (value[j][i] != value[j][i]) {
+         * System.out.println("gridToValue2D: bad Samples j = " + j +
+         * " gx, gy = " + gx + " " + gy + " " + s[0][0] + " " + s[0][1] + " " +
+         * s[1][0] + " " + s[1][1]); } }
+         */
+      }
+    }
+    return value;
+  }
+
+  private float[][] gridToValue2DAlt(float[][] grid) throws VisADException {
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Gridded3DSet.gridToValue: requires all grid "
+          + "dimensions to be > 1");
+    }
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int gridLength = Math.min(grid[0].length, grid[1].length);
+    float[] value = new float[gridLength * 3];
+    float[][]mySamples = getMySamples();
+    for (int i = 0; i < gridLength; i++) {
+      // let gx and gy by the current grid values
+      float gx = grid[0][i];
+      float gy = grid[1][i];
+      if ((gx < -0.5) || (gy < -0.5) || (gx > LengthX - 0.5)
+          || (gy > LengthY - 0.5)) {
+        value[3*i] = value[3*i+1] = value[3*i+2] = Float.NaN;
+      } else if (Length == 1) {
+        value[3*i] = mySamples[0][0];
+        value[3*i+1] = mySamples[1][0];
+        value[3*i+2] = mySamples[2][0];
+      } else {
+        // calculate closest integer variables
+        int igx = (int) gx;
+        int igy = (int) gy;
+        if (igx < 0)
+          igx = 0;
+        if (igx > LengthX - 2)
+          igx = LengthX - 2;
+        if (igy < 0)
+          igy = 0;
+        if (igy > LengthY - 2)
+          igy = LengthY - 2;
+
+        // set up conversion to 1D Samples array
+        int[][] s = { { LengthX * igy + igx, // (0, 0)
+            LengthX * (igy + 1) + igx }, // (0, 1)
+            { LengthX * igy + igx + 1, // (1, 0)
+                LengthX * (igy + 1) + igx + 1 } }; // (1, 1)
+        if (gx + gy - igx - igy - 1 <= 0) {
+          // point is in LOWER triangle
+          for (int j = 0; j < 3; j++) {
+            value[3*i+j] = mySamples[j][s[0][0]] + (gx - igx)
+                * (mySamples[j][s[1][0]] - mySamples[j][s[0][0]]) + (gy - igy)
+                * (mySamples[j][s[0][1]] - mySamples[j][s[0][0]]);
+          }
+        } else {
+          // point is in UPPER triangle
+          for (int j = 0; j < 3; j++) {
+            value[3*i+j] = mySamples[j][s[1][1]] + (1 + igx - gx)
+                * (mySamples[j][s[0][1]] - mySamples[j][s[1][1]]) + (1 + igy - gy)
+                * (mySamples[j][s[1][0]] - mySamples[j][s[1][1]]);
+          }
+        }
+        /*
+         * for (int j=0; j<3; j++) { if (value[i] != value[i]) {
+         * System.out.println("gridToValue2D: bad mySamples j = " + j +
+         * " gx, gy = " + gx + " " + gy + " " + s[0][0] + " " + s[0][1] + " " +
+         * s[1][0] + " " + s[1][1]); } }
+         */
+      }
+    }
+    return new float[][]{value};
+  }
+
+  private float[][] gridToValue3D(float[][] grid) throws VisADException {
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2 || Lengths[2] < 2)) {
+      throw new SetException("Gridded3DSet.gridToValue: requires all grid "
+          + "dimensions to be > 1");
+    }
+    float[][]mySamples = getMySamples();
+
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(grid[0].length, grid[1].length);
+    length = Math.min(length, grid[2].length);
+    float[][] value = new float[3][length];
+
+    float[] A = new float[3];
+    float[] B = new float[3];
+    float[] C = new float[3];
+    float[] D = new float[3];
+    float[] E = new float[3];
+    float[] F = new float[3];
+    float[] G = new float[3];
+    float[] H = new float[3];
+
+    for (int i = 0; i < length; i++) {
+      // let gx, gy, and gz be the current grid values
+      float gx = grid[0][i];
+      float gy = grid[1][i];
+      float gz = grid[2][i];
+      if ((gx < -0.5) || (gy < -0.5) || (gz < -0.5) || (gx > LengthX - 0.5)
+          || (gy > LengthY - 0.5) || (gz > LengthZ - 0.5)) {
+        value[0][i] = value[1][i] = value[2][i] = Float.NaN;
+      } else if (Length == 1) {
+        value[0][i] = mySamples[0][0];
+        value[1][i] = mySamples[1][0];
+        value[2][i] = mySamples[2][0];
+      } else {
+        // calculate closest integer variables
+        int igx, igy, igz;
+        if (gx < 0)
+          igx = 0;
+        else if (gx > LengthX - 2)
+          igx = LengthX - 2;
+        else
+          igx = (int) gx;
+        if (gy < 0)
+          igy = 0;
+        else if (gy > LengthY - 2)
+          igy = LengthY - 2;
+        else
+          igy = (int) gy;
+        if (gz < 0)
+          igz = 0;
+        else if (gz > LengthZ - 2)
+          igz = LengthZ - 2;
+        else
+          igz = (int) gz;
+
+        // determine tetrahedralization type
+        boolean evencube = ((igx + igy + igz) % 2 == 0);
+
+        // calculate distances from integer grid point
+        float s, t, u;
+        if (evencube) {
+          s = gx - igx;
+          t = gy - igy;
+          u = gz - igz;
+        } else {
+          s = 1 + igx - gx;
+          t = 1 + igy - gy;
+          u = 1 + igz - gz;
+        }
+
+        // Define vertices of grid box
+        int zadd = LengthY * LengthX;
+        int base = igz * zadd + igy * LengthX + igx;
+        int ai = base + zadd; // 0, 0, 1
+        int bi = base + zadd + 1; // 1, 0, 1
+        int ci = base + zadd + LengthX + 1; // 1, 1, 1
+        int di = base + zadd + LengthX; // 0, 1, 1
+        int ei = base; // 0, 0, 0
+        int fi = base + 1; // 1, 0, 0
+        int gi = base + LengthX + 1; // 1, 1, 0
+        int hi = base + LengthX; // 0, 1, 0
+        if (evencube) {
+          A[0] = mySamples[0][ai];
+          A[1] = mySamples[1][ai];
+          A[2] = mySamples[2][ai];
+          B[0] = mySamples[0][bi];
+          B[1] = mySamples[1][bi];
+          B[2] = mySamples[2][bi];
+          C[0] = mySamples[0][ci];
+          C[1] = mySamples[1][ci];
+          C[2] = mySamples[2][ci];
+          D[0] = mySamples[0][di];
+          D[1] = mySamples[1][di];
+          D[2] = mySamples[2][di];
+          E[0] = mySamples[0][ei];
+          E[1] = mySamples[1][ei];
+          E[2] = mySamples[2][ei];
+          F[0] = mySamples[0][fi];
+          F[1] = mySamples[1][fi];
+          F[2] = mySamples[2][fi];
+          G[0] = mySamples[0][gi];
+          G[1] = mySamples[1][gi];
+          G[2] = mySamples[2][gi];
+          H[0] = mySamples[0][hi];
+          H[1] = mySamples[1][hi];
+          H[2] = mySamples[2][hi];
+        } else {
+          G[0] = mySamples[0][ai];
+          G[1] = mySamples[1][ai];
+          G[2] = mySamples[2][ai];
+          H[0] = mySamples[0][bi];
+          H[1] = mySamples[1][bi];
+          H[2] = mySamples[2][bi];
+          E[0] = mySamples[0][ci];
+          E[1] = mySamples[1][ci];
+          E[2] = mySamples[2][ci];
+          F[0] = mySamples[0][di];
+          F[1] = mySamples[1][di];
+          F[2] = mySamples[2][di];
+          C[0] = mySamples[0][ei];
+          C[1] = mySamples[1][ei];
+          C[2] = mySamples[2][ei];
+          D[0] = mySamples[0][fi];
+          D[1] = mySamples[1][fi];
+          D[2] = mySamples[2][fi];
+          A[0] = mySamples[0][gi];
+          A[1] = mySamples[1][gi];
+          A[2] = mySamples[2][gi];
+          B[0] = mySamples[0][hi];
+          B[1] = mySamples[1][hi];
+          B[2] = mySamples[2][hi];
+        }
+
+        // These tests determine which tetrahedron the point is in
+        boolean test1 = (1 - s - t - u >= 0);
+        boolean test2 = (s - t + u - 1 >= 0);
+        boolean test3 = (t - s + u - 1 >= 0);
+        boolean test4 = (s + t - u - 1 >= 0);
+
+        // These cases handle grid coordinates off the grid
+        // (Different tetrahedrons must be chosen accordingly)
+        if ((gx < 0) || (gx > LengthX - 1) || (gy < 0) || (gy > LengthY - 1)
+            || (gz < 0) || (gz > LengthZ - 1)) {
+          boolean OX, OY, OZ, MX, MY, MZ, LX, LY, LZ;
+          OX = OY = OZ = MX = MY = MZ = LX = LY = LZ = false;
+          if (igx == 0)
+            OX = true;
+          if (igy == 0)
+            OY = true;
+          if (igz == 0)
+            OZ = true;
+          if (igx == LengthX - 2)
+            LX = true;
+          if (igy == LengthY - 2)
+            LY = true;
+          if (igz == LengthZ - 2)
+            LZ = true;
+          if (!OX && !LX)
+            MX = true;
+          if (!OY && !LY)
+            MY = true;
+          if (!OZ && !LZ)
+            MZ = true;
+          test1 = test2 = test3 = test4 = false;
+          // 26 cases
+          if (evencube) {
+            if (!LX && !LY && !LZ)
+              test1 = true;
+            else if ((LX && OY && MZ) || (MX && OY && LZ) || (LX && MY && LZ)
+                || (LX && OY && LZ) || (MX && MY && LZ) || (LX && MY && MZ))
+              test2 = true;
+            else if ((OX && LY && MZ) || (OX && MY && LZ) || (MX && LY && LZ)
+                || (OX && LY && LZ) || (MX && LY && MZ))
+              test3 = true;
+            else if ((MX && LY && OZ) || (LX && MY && OZ) || (LX && LY && MZ)
+                || (LX && LY && OZ))
+              test4 = true;
+          } else {
+            if (!OX && !OY && !OZ)
+              test1 = true;
+            else if ((OX && MY && OZ) || (MX && LY && OZ) || (OX && LY && MZ)
+                || (OX && LY && OZ) || (MX && MY && OZ) || (OX && MY && MZ))
+              test2 = true;
+            else if ((LX && MY && OZ) || (MX && OY && OZ) || (LX && OY && MZ)
+                || (LX && OY && OZ) || (MX && OY && MZ))
+              test3 = true;
+            else if ((OX && OY && MZ) || (OX && MY && OZ) || (MX && OY && LZ)
+                || (OX && OY && LZ))
+              test4 = true;
+          }
+        }
+        if (test1) {
+          for (int j = 0; j < 3; j++) {
+            value[j][i] = E[j] + s * (F[j] - E[j]) + t * (H[j] - E[j]) + u
+                * (A[j] - E[j]);
+          }
+        } else if (test2) {
+          for (int j = 0; j < 3; j++) {
+            value[j][i] = B[j] + (1 - s) * (A[j] - B[j]) + t * (C[j] - B[j])
+                + (1 - u) * (F[j] - B[j]);
+          }
+        } else if (test3) {
+          for (int j = 0; j < 3; j++) {
+            value[j][i] = D[j] + s * (C[j] - D[j]) + (1 - t) * (A[j] - D[j])
+                + (1 - u) * (H[j] - D[j]);
+          }
+        } else if (test4) {
+          for (int j = 0; j < 3; j++) {
+            value[j][i] = G[j] + (1 - s) * (H[j] - G[j]) + (1 - t)
+                * (F[j] - G[j]) + u * (C[j] - G[j]);
+          }
+        } else {
+          for (int j = 0; j < 3; j++) {
+            value[j][i] = (H[j] + F[j] + A[j] - C[j]) / 2 + s
+                * (C[j] + F[j] - H[j] - A[j]) / 2 + t
+                * (C[j] - F[j] + H[j] - A[j]) / 2 + u
+                * (C[j] - F[j] - H[j] + A[j]) / 2;
+          }
+        }
+      }
+    }
+    return value;
+  }
+
+  private float[][] gridToValue3DAlt(float[][] grid) throws VisADException {
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2 || Lengths[2] < 2)) {
+      throw new SetException("Gridded3DSet.gridToValue: requires all grid "
+          + "dimensions to be > 1");
+    }
+    float[][]mySamples = getMySamples();
+    // avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int gridLength = Math.min(grid[0].length, grid[1].length);
+    gridLength = Math.min(gridLength, grid[2].length);
+    float[] value = new float[gridLength * 3];
+
+    float[] A = new float[3];
+    float[] B = new float[3];
+    float[] C = new float[3];
+    float[] D = new float[3];
+    float[] E = new float[3];
+    float[] F = new float[3];
+    float[] G = new float[3];
+    float[] H = new float[3];
+
+    for (int i = 0; i < value.length; i++) {
+      // let gx, gy, and gz be the current grid values
+      float gx = grid[0][i];
+      float gy = grid[1][i];
+      float gz = grid[2][i];
+      if ((gx < -0.5) || (gy < -0.5) || (gz < -0.5) || (gx > LengthX - 0.5)
+          || (gy > LengthY - 0.5) || (gz > LengthZ - 0.5)) {
+        value[3*i] = value[3*i+1] = value[3*i+2] = Float.NaN;
+      } else if (Length == 1) {
+        value[3*i] = mySamples[0][0];
+        value[3*i+1] = mySamples[1][0];
+        value[3*i+2] = mySamples[2][0];
+      } else {
+        // calculate closest integer variables
+        int igx, igy, igz;
+        if (gx < 0)
+          igx = 0;
+        else if (gx > LengthX - 2)
+          igx = LengthX - 2;
+        else
+          igx = (int) gx;
+        if (gy < 0)
+          igy = 0;
+        else if (gy > LengthY - 2)
+          igy = LengthY - 2;
+        else
+          igy = (int) gy;
+        if (gz < 0)
+          igz = 0;
+        else if (gz > LengthZ - 2)
+          igz = LengthZ - 2;
+        else
+          igz = (int) gz;
+
+        // determine tetrahedralization type
+        boolean evencube = ((igx + igy + igz) % 2 == 0);
+
+        // calculate distances from integer grid point
+        float s, t, u;
+        if (evencube) {
+          s = gx - igx;
+          t = gy - igy;
+          u = gz - igz;
+        } else {
+          s = 1 + igx - gx;
+          t = 1 + igy - gy;
+          u = 1 + igz - gz;
+        }
+
+        // Define vertices of grid box
+        int zadd = LengthY * LengthX;
+        int base = igz * zadd + igy * LengthX + igx;
+        int ai = base + zadd; // 0, 0, 1
+        int bi = base + zadd + 1; // 1, 0, 1
+        int ci = base + zadd + LengthX + 1; // 1, 1, 1
+        int di = base + zadd + LengthX; // 0, 1, 1
+        int ei = base; // 0, 0, 0
+        int fi = base + 1; // 1, 0, 0
+        int gi = base + LengthX + 1; // 1, 1, 0
+        int hi = base + LengthX; // 0, 1, 0
+        if (evencube) {
+          A[0] = mySamples[0][ai];
+          A[1] = mySamples[1][ai];
+          A[2] = mySamples[2][ai];
+          B[0] = mySamples[0][bi];
+          B[1] = mySamples[1][bi];
+          B[2] = mySamples[2][bi];
+          C[0] = mySamples[0][ci];
+          C[1] = mySamples[1][ci];
+          C[2] = mySamples[2][ci];
+          D[0] = mySamples[0][di];
+          D[1] = mySamples[1][di];
+          D[2] = mySamples[2][di];
+          E[0] = mySamples[0][ei];
+          E[1] = mySamples[1][ei];
+          E[2] = mySamples[2][ei];
+          F[0] = mySamples[0][fi];
+          F[1] = mySamples[1][fi];
+          F[2] = mySamples[2][fi];
+          G[0] = mySamples[0][gi];
+          G[1] = mySamples[1][gi];
+          G[2] = mySamples[2][gi];
+          H[0] = mySamples[0][hi];
+          H[1] = mySamples[1][hi];
+          H[2] = mySamples[2][hi];
+        } else {
+          G[0] = mySamples[0][ai];
+          G[1] = mySamples[1][ai];
+          G[2] = mySamples[2][ai];
+          H[0] = mySamples[0][bi];
+          H[1] = mySamples[1][bi];
+          H[2] = mySamples[2][bi];
+          E[0] = mySamples[0][ci];
+          E[1] = mySamples[1][ci];
+          E[2] = mySamples[2][ci];
+          F[0] = mySamples[0][di];
+          F[1] = mySamples[1][di];
+          F[2] = mySamples[2][di];
+          C[0] = mySamples[0][ei];
+          C[1] = mySamples[1][ei];
+          C[2] = mySamples[2][ei];
+          D[0] = mySamples[0][fi];
+          D[1] = mySamples[1][fi];
+          D[2] = mySamples[2][fi];
+          A[0] = mySamples[0][gi];
+          A[1] = mySamples[1][gi];
+          A[2] = mySamples[2][gi];
+          B[0] = mySamples[0][hi];
+          B[1] = mySamples[1][hi];
+          B[2] = mySamples[2][hi];
+        }
+
+        // These tests determine which tetrahedron the point is in
+        boolean test1 = (1 - s - t - u >= 0);
+        boolean test2 = (s - t + u - 1 >= 0);
+        boolean test3 = (t - s + u - 1 >= 0);
+        boolean test4 = (s + t - u - 1 >= 0);
+
+        // These cases handle grid coordinates off the grid
+        // (Different tetrahedrons must be chosen accordingly)
+        if ((gx < 0) || (gx > LengthX - 1) || (gy < 0) || (gy > LengthY - 1)
+            || (gz < 0) || (gz > LengthZ - 1)) {
+          boolean OX, OY, OZ, MX, MY, MZ, LX, LY, LZ;
+          OX = OY = OZ = MX = MY = MZ = LX = LY = LZ = false;
+          if (igx == 0)
+            OX = true;
+          if (igy == 0)
+            OY = true;
+          if (igz == 0)
+            OZ = true;
+          if (igx == LengthX - 2)
+            LX = true;
+          if (igy == LengthY - 2)
+            LY = true;
+          if (igz == LengthZ - 2)
+            LZ = true;
+          if (!OX && !LX)
+            MX = true;
+          if (!OY && !LY)
+            MY = true;
+          if (!OZ && !LZ)
+            MZ = true;
+          test1 = test2 = test3 = test4 = false;
+          // 26 cases
+          if (evencube) {
+            if (!LX && !LY && !LZ)
+              test1 = true;
+            else if ((LX && OY && MZ) || (MX && OY && LZ) || (LX && MY && LZ)
+                || (LX && OY && LZ) || (MX && MY && LZ) || (LX && MY && MZ))
+              test2 = true;
+            else if ((OX && LY && MZ) || (OX && MY && LZ) || (MX && LY && LZ)
+                || (OX && LY && LZ) || (MX && LY && MZ))
+              test3 = true;
+            else if ((MX && LY && OZ) || (LX && MY && OZ) || (LX && LY && MZ)
+                || (LX && LY && OZ))
+              test4 = true;
+          } else {
+            if (!OX && !OY && !OZ)
+              test1 = true;
+            else if ((OX && MY && OZ) || (MX && LY && OZ) || (OX && LY && MZ)
+                || (OX && LY && OZ) || (MX && MY && OZ) || (OX && MY && MZ))
+              test2 = true;
+            else if ((LX && MY && OZ) || (MX && OY && OZ) || (LX && OY && MZ)
+                || (LX && OY && OZ) || (MX && OY && MZ))
+              test3 = true;
+            else if ((OX && OY && MZ) || (OX && MY && OZ) || (MX && OY && LZ)
+                || (OX && OY && LZ))
+              test4 = true;
+          }
+        }
+        if (test1) {
+          for (int j = 0; j < 3; j++) {
+            value[3*i+j] = E[j] + s * (F[j] - E[j]) + t * (H[j] - E[j]) + u
+                * (A[j] - E[j]);
+          }
+        } else if (test2) {
+          for (int j = 0; j < 3; j++) {
+            value[3*i+j] = B[j] + (1 - s) * (A[j] - B[j]) + t * (C[j] - B[j])
+                + (1 - u) * (F[j] - B[j]);
+          }
+        } else if (test3) {
+          for (int j = 0; j < 3; j++) {
+            value[3*i+j] = D[j] + s * (C[j] - D[j]) + (1 - t) * (A[j] - D[j])
+                + (1 - u) * (H[j] - D[j]);
+          }
+        } else if (test4) {
+          for (int j = 0; j < 3; j++) {
+            value[3*i+j] = G[j] + (1 - s) * (H[j] - G[j]) + (1 - t)
+                * (F[j] - G[j]) + u * (C[j] - G[j]);
+          }
+        } else {
+          for (int j = 0; j < 3; j++) {
+            value[3*i+j] = (H[j] + F[j] + A[j] - C[j]) / 2 + s
+                * (C[j] + F[j] - H[j] - A[j]) / 2 + t
+                * (C[j] - F[j] + H[j] - A[j]) / 2 + u
+                * (C[j] - F[j] - H[j] + A[j]) / 2;
+          }
+        }
+      }
+    }
+    return new float[][]{value};
+  }
+
+  // WLH 6 Dec 2001
+  //private int gx = -1;
+  //private int gy = -1;
+  //private int gz = -1;
+
+  /**
+   * transform an array of values in R^DomainDimension to an array of
+   * non-integer grid coordinates
+   */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+
+    float[][]mySamples = getMySamples();
+    if (value.length < DomainDimension) {
+      throw new SetException("Gridded3DSet.valueToGrid: value dimension "
+          + value.length + " not equal to Domain dimension " + DomainDimension);
+    }
+    if (ManifoldDimension < 3) {
+      throw new SetException("Gridded3DSet.valueToGrid: ManifoldDimension "
+          + "must be 3");
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2 || Lengths[2] < 2)) {
+      throw new SetException("Gridded3DSet.valueToGrid: requires all grid "
+          + "dimensions to be > 1");
+    }
+    // Avoid any ArrayOutOfBounds exceptions by taking the shortest length
+    int length = Math.min(value[0].length, value[1].length);
+    length = Math.min(length, value[2].length);
+    float[][] grid = new float[ManifoldDimension][length];
+
+    // (gx, gy, gz) is the current grid box guess
+    int gx = (LengthX-1)/2; 
+    int gy = (LengthY-1)/2; 
+    int gz = (LengthZ-1)/2;
+
+    /* WLH 6 Dec 2001 
+    // use value from last call as first guess, if reasonable
+    if (gx < 0 || gx >= LengthX || gy < 0 || gy >= LengthY || gz < 0
+        || gz >= LengthZ) {
+      gx = (LengthX - 1) / 2;
+      gy = (LengthY - 1) / 2;
+      gz = (LengthZ - 1) / 2;
+    }
+     */
+
+    float[] A = new float[3];
+    float[] B = new float[3];
+    float[] C = new float[3];
+    float[] D = new float[3];
+    float[] E = new float[3];
+    float[] F = new float[3];
+    float[] G = new float[3];
+    float[] H = new float[3];
+    float[] M = new float[3];
+    float[] N = new float[3];
+    float[] O = new float[3];
+    float[] P = new float[3];
+    float[] Q = new float[3];
+    float[] X = new float[3];
+    float[] Y = new float[3];
+
+    for (int i = 0; i < length; i++) {
+      // a flag indicating whether point is off the grid
+      boolean offgrid = false;
+      // the first guess should be the last box unless there was no solution
+      /*
+       * WLH 24 Oct 97 if ( (i != 0) && (Float.isNaN(grid[0][i-1])) )
+       */
+      if (Length == 1) {
+        if (Float.isNaN(value[0][i]) || Float.isNaN(value[1][i])
+            || Float.isNaN(value[2][i])) {
+          grid[0][i] = grid[1][i] = grid[2][i] = Float.NaN;
+        } else {
+          grid[0][i] = 0;
+          grid[1][i] = 0;
+          grid[2][i] = 0;
+        }
+        continue;
+      }
+      // test for missing
+      if ((i != 0) && grid[0][i - 1] != grid[0][i - 1]) {
+        // gx = (LengthX-1)/2;
+        // gy = (LengthY-1)/2;
+        // gz = (LengthZ-1)/2;
+      }
+      int tetnum = 5; // Tetrahedron number in which to start search
+      // if the iteration loop fails, the result should be NaN
+      grid[0][i] = grid[1][i] = grid[2][i] = Float.NaN;
+
+      // --TDR, don't let value and initial guess be too far apart
+      float v_x, v_y, v_z;
+      v_x = value[0][i];
+      v_y = value[1][i];
+      v_z = value[2][i];
+      int ii = LengthX * LengthY * LengthZ - 1;
+      int gii = (int) gz * LengthX * LengthY + gy * LengthX + gx;
+      float sx = mySamples[0][gii];
+      float sy = mySamples[1][gii];
+      float sz = mySamples[2][gii];
+      //GHANSHAM: Added this if condition. It tries to get start point
+      //when i = 0 (first time) or when the last guess is not a valid value
+      if (i == 0 || ((i != 0) && grid[0][i - 1] != grid[0][i - 1])) {
+        if (Math.abs(v_x-sx) > 0.4 *Math.abs(mySamples[0][0] - mySamples[0][ii]) ||
+            Math.abs(v_y-sy) > 0.4 *Math.abs(mySamples[1][0] - mySamples[1][ii]) ||
+            Math.abs(v_z-sz) > 0.4 *Math.abs(mySamples[2][0] - mySamples[2][ii])) {
+          float[] ginit = getStartPoint(value[0][i], value[1][i], value[2][i]);
+          gx = (int) ginit[0];
+          gy = (int) ginit[1];
+          gz = (int) ginit[2];
+        }
+      } else { //GHANSHAM: Use the last guest other wise
+          gx = (int) grid[0][i-1];
+          gy = (int) grid[1][i-1];
+          gz = (int) grid[2][i-1];
+          if (gx > LengthX - 2) {
+             gx = LengthX - 2;
+          }
+          if (gy > LengthY - 2) {
+             gy = LengthY - 2;
+          }
+          if (gz > LengthZ - 2) {
+             gz = LengthZ - 2;
+          }
+      }
+      // ----
+      for (int itnum = 0; itnum < 2 * (LengthX + LengthY + LengthZ); itnum++) {
+        // determine tetrahedralization type
+        boolean evencube = ((gx + gy + gz) % 2 == 0);
+
+        // Define vertices of grid box
+        int zadd = LengthY * LengthX;
+        int base = gz * zadd + gy * LengthX + gx;
+        int ai = base + zadd; // 0, 0, 1
+        int bi = base + zadd + 1; // 1, 0, 1
+        int ci = base + zadd + LengthX + 1; // 1, 1, 1
+        int di = base + zadd + LengthX; // 0, 1, 1
+        int ei = base; // 0, 0, 0
+        int fi = base + 1; // 1, 0, 0
+        int gi = base + LengthX + 1; // 1, 1, 0
+        int hi = base + LengthX; // 0, 1, 0
+
+        if (evencube) {
+          A[0] = mySamples[0][ai];
+          A[1] = mySamples[1][ai];
+          A[2] = mySamples[2][ai];
+          B[0] = mySamples[0][bi];
+          B[1] = mySamples[1][bi];
+          B[2] = mySamples[2][bi];
+          C[0] = mySamples[0][ci];
+          C[1] = mySamples[1][ci];
+          C[2] = mySamples[2][ci];
+          D[0] = mySamples[0][di];
+          D[1] = mySamples[1][di];
+          D[2] = mySamples[2][di];
+          E[0] = mySamples[0][ei];
+          E[1] = mySamples[1][ei];
+          E[2] = mySamples[2][ei];
+          F[0] = mySamples[0][fi];
+          F[1] = mySamples[1][fi];
+          F[2] = mySamples[2][fi];
+          G[0] = mySamples[0][gi];
+          G[1] = mySamples[1][gi];
+          G[2] = mySamples[2][gi];
+          H[0] = mySamples[0][hi];
+          H[1] = mySamples[1][hi];
+          H[2] = mySamples[2][hi];
+        } else {
+          G[0] = mySamples[0][ai];
+          G[1] = mySamples[1][ai];
+          G[2] = mySamples[2][ai];
+          H[0] = mySamples[0][bi];
+          H[1] = mySamples[1][bi];
+          H[2] = mySamples[2][bi];
+          E[0] = mySamples[0][ci];
+          E[1] = mySamples[1][ci];
+          E[2] = mySamples[2][ci];
+          F[0] = mySamples[0][di];
+          F[1] = mySamples[1][di];
+          F[2] = mySamples[2][di];
+          C[0] = mySamples[0][ei];
+          C[1] = mySamples[1][ei];
+          C[2] = mySamples[2][ei];
+          D[0] = mySamples[0][fi];
+          D[1] = mySamples[1][fi];
+          D[2] = mySamples[2][fi];
+          A[0] = mySamples[0][gi];
+          A[1] = mySamples[1][gi];
+          A[2] = mySamples[2][gi];
+          B[0] = mySamples[0][hi];
+          B[1] = mySamples[1][hi];
+          B[2] = mySamples[2][hi];
+        }
+
+        // Compute tests and go to a new box depending on results
+        boolean test1, test2, test3, test4;
+        float tval1, tval2, tval3, tval4;
+        int ogx = gx;
+        int ogy = gy;
+        int ogz = gz;
+        if (tetnum == 1) {
+          tval1 = ((E[1] - A[1]) * (F[2] - E[2]) - (E[2] - A[2])
+              * (F[1] - E[1]))
+              * (value[0][i] - E[0])
+              + ((E[2] - A[2]) * (F[0] - E[0]) - (E[0] - A[0]) * (F[2] - E[2]))
+              * (value[1][i] - E[1])
+              + ((E[0] - A[0]) * (F[1] - E[1]) - (E[1] - A[1]) * (F[0] - E[0]))
+              * (value[2][i] - E[2]);
+          tval2 = ((E[1] - H[1]) * (A[2] - E[2]) - (E[2] - H[2])
+              * (A[1] - E[1]))
+              * (value[0][i] - E[0])
+              + ((E[2] - H[2]) * (A[0] - E[0]) - (E[0] - H[0]) * (A[2] - E[2]))
+              * (value[1][i] - E[1])
+              + ((E[0] - H[0]) * (A[1] - E[1]) - (E[1] - H[1]) * (A[0] - E[0]))
+              * (value[2][i] - E[2]);
+          tval3 = ((E[1] - F[1]) * (H[2] - E[2]) - (E[2] - F[2])
+              * (H[1] - E[1]))
+              * (value[0][i] - E[0])
+              + ((E[2] - F[2]) * (H[0] - E[0]) - (E[0] - F[0]) * (H[2] - E[2]))
+              * (value[1][i] - E[1])
+              + ((E[0] - F[0]) * (H[1] - E[1]) - (E[1] - F[1]) * (H[0] - E[0]))
+              * (value[2][i] - E[2]);
+          test1 = (visad.util.Util.isApproximatelyEqual(tval1, 0.0, 1E-04))
+              || ((tval1 > 0) == (!evencube) ^ Pos);
+          test2 = (visad.util.Util.isApproximatelyEqual(tval2, 0.0, 1E-04))
+              || ((tval2 > 0) == (!evencube) ^ Pos);
+          test3 = (visad.util.Util.isApproximatelyEqual(tval3, 0.0, 1E-04))
+              || ((tval3 > 0) == (!evencube) ^ Pos);
+
+          // if a test failed go to a new box
+          int updown = (evencube) ? -1 : 1;
+          if (!test1)
+            gy += updown; // UP/DOWN
+          if (!test2)
+            gx += updown; // LEFT/RIGHT
+          if (!test3)
+            gz += updown; // BACK/FORWARD
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off.
+          if (gx < 0)
+            gx = 0;
+          if (gy < 0)
+            gy = 0;
+          if (gz < 0)
+            gz = 0;
+          if (gx > LengthX - 2)
+            gx = LengthX - 2;
+          if (gy > LengthY - 2)
+            gy = LengthY - 2;
+          if (gz > LengthZ - 2)
+            gz = LengthZ - 2;
+
+          // Detect if the point is off the grid entirely
+          if ((gx == ogx) && (gy == ogy) && (gz == ogz)
+              && (!test1 || !test2 || !test3) && !offgrid) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (((gx == ogx) && (gy == ogy) && (gz == ogz)) || offgrid) {
+            if ((value[0][i] == E[0]) && (value[1][i] == E[1])
+                && (value[2][i] == E[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            if ((value[0][i] == A[0]) && (value[1][i] == A[1])
+                && (value[2][i] == A[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            if ((value[0][i] == F[0]) && (value[1][i] == F[1])
+                && (value[2][i] == F[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            if ((value[0][i] == H[0]) && (value[1][i] == H[1])
+                && (value[2][i] == H[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            // solve point
+            for (int j = 0; j < 3; j++) {
+              M[j] = (F[j] - E[j]) * (A[(j + 1) % 3] - E[(j + 1) % 3])
+                  - (F[(j + 1) % 3] - E[(j + 1) % 3]) * (A[j] - E[j]);
+              N[j] = (H[j] - E[j]) * (A[(j + 1) % 3] - E[(j + 1) % 3])
+                  - (H[(j + 1) % 3] - E[(j + 1) % 3]) * (A[j] - E[j]);
+              O[j] = (F[(j + 1) % 3] - E[(j + 1) % 3])
+                  * (A[(j + 2) % 3] - E[(j + 2) % 3])
+                  - (F[(j + 2) % 3] - E[(j + 2) % 3])
+                  * (A[(j + 1) % 3] - E[(j + 1) % 3]);
+              P[j] = (H[(j + 1) % 3] - E[(j + 1) % 3])
+                  * (A[(j + 2) % 3] - E[(j + 2) % 3])
+                  - (H[(j + 2) % 3] - E[(j + 2) % 3])
+                  * (A[(j + 1) % 3] - E[(j + 1) % 3]);
+              X[j] = value[(j + 2) % 3][i] * (A[(j + 1) % 3] - E[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (A[(j + 2) % 3] - E[(j + 2) % 3])
+                  + E[(j + 1) % 3] * A[(j + 2) % 3] - E[(j + 2) % 3]
+                  * A[(j + 1) % 3];
+              Y[j] = value[j][i] * (A[(j + 1) % 3] - E[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (A[j] - E[j]) + E[(j + 1) % 3]
+                  * A[j] - E[j] * A[(j + 1) % 3];
+            }
+            float s, t, u;
+            // these if statements handle skewed grids
+            float d0 = M[0] * P[0] - N[0] * O[0];
+            float d1 = M[1] * P[1] - N[1] * O[1];
+            float d2 = M[2] * P[2] - N[2] * O[2];
+            float ad0 = Math.abs(d0);
+            float ad1 = Math.abs(d1);
+            float ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = (N[0] * X[0] + P[0] * Y[0]) / d0;
+              t = -(M[0] * X[0] + O[0] * Y[0]) / d0;
+            } else if (ad1 > ad2) {
+              s = (N[1] * X[1] + P[1] * Y[1]) / d1;
+              t = -(M[1] * X[1] + O[1] * Y[1]) / d1;
+            } else {
+              s = (N[2] * X[2] + P[2] * Y[2]) / d2;
+              t = -(M[2] * X[2] + O[2] * Y[2]) / d2;
+            }
+            /*
+             * WLH 5 April 99 if (M[0]P[0] != N[0]O[0]) { s = (N[0]X[0] +
+             * P[0]Y[0])/(M[0]P[0] - N[0]O[0]); t = (M[0]X[0] +
+             * O[0]Y[0])/(N[0]O[0] - M[0]P[0]); } else if (M[1]P[1] != N[1]O[1])
+             * { s = (N[1]X[1] + P[1]Y[1])/(M[1]P[1] - N[1]O[1]); t = (M[1]X[1]
+             * + O[1]Y[1])/(N[1]O[1] - M[1]P[1]); } else { s = (N[2]X[2] +
+             * P[2]Y[2])/(M[2]P[2] - N[2]O[2]); t = (M[2]X[2] +
+             * O[2]Y[2])/(N[2]O[2] - M[2]P[2]); }
+             */
+            d0 = A[0] - E[0];
+            d1 = A[1] - E[1];
+            d2 = A[2] - E[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = (value[0][i] - E[0] - s * (F[0] - E[0]) - t * (H[0] - E[0]))
+                  / d0;
+            } else if (ad1 > ad2) {
+              u = (value[1][i] - E[1] - s * (F[1] - E[1]) - t * (H[1] - E[1]))
+                  / d1;
+            } else {
+              u = (value[2][i] - E[2] - s * (F[2] - E[2]) - t * (H[2] - E[2]))
+                  / d2;
+            }
+            /*
+             * WLH 5 April 99 if (A[0] != E[0]) { u = ( value[0][i] - E[0] -
+             * s(F[0]-E[0]) - t(H[0]-E[0]) ) / (A[0]-E[0]); } else if (A[1] !=
+             * E[1]) { u = ( value[1][i] - E[1] - s(F[1]-E[1]) - t(H[1]-E[1]) )
+             * / (A[1]-E[1]); } else { u = ( value[2][i] - E[2] - s(F[2]-E[2]) -
+             * t(H[2]-E[2]) ) / (A[2]-E[2]); }
+             */
+            if (evencube) {
+              grid[0][i] = gx + s;
+              grid[1][i] = gy + t;
+              grid[2][i] = gz + u;
+            } else {
+              grid[0][i] = gx + 1 - s;
+              grid[1][i] = gy + 1 - t;
+              grid[2][i] = gz + 1 - u;
+            }
+
+            break;
+          }
+        } else if (tetnum == 2) {
+          tval1 = ((B[1] - C[1]) * (F[2] - B[2]) - (B[2] - C[2])
+              * (F[1] - B[1]))
+              * (value[0][i] - B[0])
+              + ((B[2] - C[2]) * (F[0] - B[0]) - (B[0] - C[0]) * (F[2] - B[2]))
+              * (value[1][i] - B[1])
+              + ((B[0] - C[0]) * (F[1] - B[1]) - (B[1] - C[1]) * (F[0] - B[0]))
+              * (value[2][i] - B[2]);
+          tval2 = ((B[1] - A[1]) * (C[2] - B[2]) - (B[2] - A[2])
+              * (C[1] - B[1]))
+              * (value[0][i] - B[0])
+              + ((B[2] - A[2]) * (C[0] - B[0]) - (B[0] - A[0]) * (C[2] - B[2]))
+              * (value[1][i] - B[1])
+              + ((B[0] - A[0]) * (C[1] - B[1]) - (B[1] - A[1]) * (C[0] - B[0]))
+              * (value[2][i] - B[2]);
+          tval3 = ((B[1] - F[1]) * (A[2] - B[2]) - (B[2] - F[2])
+              * (A[1] - B[1]))
+              * (value[0][i] - B[0])
+              + ((B[2] - F[2]) * (A[0] - B[0]) - (B[0] - F[0]) * (A[2] - B[2]))
+              * (value[1][i] - B[1])
+              + ((B[0] - F[0]) * (A[1] - B[1]) - (B[1] - F[1]) * (A[0] - B[0]))
+              * (value[2][i] - B[2]);
+          test1 = (visad.util.Util.isApproximatelyEqual(tval1, 0.0, 1E-04))
+              || ((tval1 > 0) == (!evencube) ^ Pos);
+          test2 = (visad.util.Util.isApproximatelyEqual(tval2, 0.0, 1E-04))
+              || ((tval2 > 0) == (!evencube) ^ Pos);
+          test3 = (visad.util.Util.isApproximatelyEqual(tval3, 0.0, 1E-04))
+              || ((tval3 > 0) == (!evencube) ^ Pos);
+
+          // if a test failed go to a new box
+          if (!test1 && evencube)
+            gx++; // RIGHT
+          if (!test1 && !evencube)
+            gx--; // LEFT
+          if (!test2 && evencube)
+            gz++; // FORWARD
+          if (!test2 && !evencube)
+            gz--; // BACK
+          if (!test3 && evencube)
+            gy--; // UP
+          if (!test3 && !evencube)
+            gy++; // DOWN
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0)
+            gx = 0;
+          if (gy < 0)
+            gy = 0;
+          if (gz < 0)
+            gz = 0;
+          if (gx > LengthX - 2)
+            gx = LengthX - 2;
+          if (gy > LengthY - 2)
+            gy = LengthY - 2;
+          if (gz > LengthZ - 2)
+            gz = LengthZ - 2;
+
+          // Detect if the point is off the grid entirely
+          if ((gx == ogx) && (gy == ogy) && (gz == ogz)
+              && (!test1 || !test2 || !test3) && !offgrid) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (((gx == ogx) && (gy == ogy) && (gz == ogz)) || offgrid) {
+            if ((value[0][i] == B[0]) && (value[1][i] == B[1])
+                && (value[2][i] == B[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            if ((value[0][i] == A[0]) && (value[1][i] == A[1])
+                && (value[2][i] == A[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            if ((value[0][i] == F[0]) && (value[1][i] == F[1])
+                && (value[2][i] == F[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            if ((value[0][i] == C[0]) && (value[1][i] == C[1])
+                && (value[2][i] == C[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            // solve point
+            for (int j = 0; j < 3; j++) {
+              M[j] = (A[j] - B[j]) * (F[(j + 1) % 3] - B[(j + 1) % 3])
+                  - (A[(j + 1) % 3] - B[(j + 1) % 3]) * (F[j] - B[j]);
+              N[j] = (C[j] - B[j]) * (F[(j + 1) % 3] - B[(j + 1) % 3])
+                  - (C[(j + 1) % 3] - B[(j + 1) % 3]) * (F[j] - B[j]);
+              O[j] = (A[(j + 1) % 3] - B[(j + 1) % 3])
+                  * (F[(j + 2) % 3] - B[(j + 2) % 3])
+                  - (A[(j + 2) % 3] - B[(j + 2) % 3])
+                  * (F[(j + 1) % 3] - B[(j + 1) % 3]);
+              P[j] = (C[(j + 1) % 3] - B[(j + 1) % 3])
+                  * (F[(j + 2) % 3] - B[(j + 2) % 3])
+                  - (C[(j + 2) % 3] - B[(j + 2) % 3])
+                  * (F[(j + 1) % 3] - B[(j + 1) % 3]);
+              X[j] = value[(j + 2) % 3][i] * (F[(j + 1) % 3] - B[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (F[(j + 2) % 3] - B[(j + 2) % 3])
+                  + B[(j + 1) % 3] * F[(j + 2) % 3] - B[(j + 2) % 3]
+                  * F[(j + 1) % 3];
+              Y[j] = value[j][i] * (F[(j + 1) % 3] - B[(j + 1) % 3])
+                  - value[1][i] * (F[j] - B[j]) + B[(j + 1) % 3] * F[j] - B[j]
+                  * F[(j + 1) % 3];
+            }
+            float s, t, u;
+            // these if statements handle skewed grids
+            float d0 = M[0] * P[0] - N[0] * O[0];
+            float d1 = M[1] * P[1] - N[1] * O[1];
+            float d2 = M[2] * P[2] - N[2] * O[2];
+            float ad0 = Math.abs(d0);
+            float ad1 = Math.abs(d1);
+            float ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = 1 - (N[0] * X[0] + P[0] * Y[0]) / d0;
+              t = -(M[0] * X[0] + O[0] * Y[0]) / d0;
+            } else if (ad1 > ad2) {
+              s = 1 - (N[1] * X[1] + P[1] * Y[1]) / d1;
+              t = -(M[1] * X[1] + O[1] * Y[1]) / d1;
+            } else {
+              s = 1 - (N[2] * X[2] + P[2] * Y[2]) / d2;
+              t = -(M[2] * X[2] + O[2] * Y[2]) / d2;
+            }
+            /*
+             * WLH 5 April 99 if (M[0]P[0] != N[0]O[0]) { s = 1 - (N[0]X[0] +
+             * P[0]Y[0])/(M[0]P[0] - N[0]O[0]); t = (M[0]X[0] +
+             * O[0]Y[0])/(N[0]O[0] - M[0]P[0]); } else if (M[1]P[1] != N[1]O[1])
+             * { s = 1 - (N[1]X[1] + P[1]Y[1])/(M[1]P[1] - N[1]O[1]); t =
+             * (M[1]X[1] + O[1]Y[1])/(N[1]O[1] - M[1]P[1]); } else { s = 1 -
+             * (N[2]X[2] + P[2]Y[2])/(M[2]P[2] - N[2]O[2]); t = (M[2]X[2] +
+             * O[2]Y[2])/(N[2]O[2] - M[2]P[2]); }
+             */
+            d0 = F[0] - B[0];
+            d1 = F[1] - B[1];
+            d2 = F[2] - B[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = 1
+                  - (value[0][i] - B[0] - (1 - s) * (A[0] - B[0]) - t
+                      * (C[0] - B[0])) / d0;
+            } else if (ad1 > ad2) {
+              u = 1
+                  - (value[1][i] - B[1] - (1 - s) * (A[1] - B[1]) - t
+                      * (C[1] - B[1])) / d1;
+            } else {
+              u = 1
+                  - (value[2][i] - B[2] - (1 - s) * (A[2] - B[2]) - t
+                      * (C[2] - B[2])) / d2;
+            }
+            /*
+             * WLH 5 April 99 if (F[0] != B[0]) { u = 1 - ( value[0][i] - B[0] -
+             * (1-s)(A[0]-B[0]) - t(C[0]-B[0]) ) / (F[0]-B[0]); } else if (F[1]
+             * != B[1]) { u = 1 - ( value[1][i] - B[1] - (1-s)(A[1]-B[1]) -
+             * t(C[1]-B[1]) ) / (F[1]-B[1]); } else { u = 1 - ( value[2][i] -
+             * B[2] - (1-s)(A[2]-B[2]) - t(C[2]-B[2]) ) / (F[2]-B[2]); }
+             */
+            if (evencube) {
+              grid[0][i] = gx + s;
+              grid[1][i] = gy + t;
+              grid[2][i] = gz + u;
+            } else {
+              grid[0][i] = gx + 1 - s;
+              grid[1][i] = gy + 1 - t;
+              grid[2][i] = gz + 1 - u;
+            }
+
+            break;
+          }
+        } else if (tetnum == 3) {
+          tval1 = ((D[1] - A[1]) * (H[2] - D[2]) - (D[2] - A[2])
+              * (H[1] - D[1]))
+              * (value[0][i] - D[0])
+              + ((D[2] - A[2]) * (H[0] - D[0]) - (D[0] - A[0]) * (H[2] - D[2]))
+              * (value[1][i] - D[1])
+              + ((D[0] - A[0]) * (H[1] - D[1]) - (D[1] - A[1]) * (H[0] - D[0]))
+              * (value[2][i] - D[2]);
+          tval2 = ((D[1] - C[1]) * (A[2] - D[2]) - (D[2] - C[2])
+              * (A[1] - D[1]))
+              * (value[0][i] - D[0])
+              + ((D[2] - C[2]) * (A[0] - D[0]) - (D[0] - C[0]) * (A[2] - D[2]))
+              * (value[1][i] - D[1])
+              + ((D[0] - C[0]) * (A[1] - D[1]) - (D[1] - C[1]) * (A[0] - D[0]))
+              * (value[2][i] - D[2]);
+          tval3 = ((D[1] - H[1]) * (C[2] - D[2]) - (D[2] - H[2])
+              * (C[1] - D[1]))
+              * (value[0][i] - D[0])
+              + ((D[2] - H[2]) * (C[0] - D[0]) - (D[0] - H[0]) * (C[2] - D[2]))
+              * (value[1][i] - D[1])
+              + ((D[0] - H[0]) * (C[1] - D[1]) - (D[1] - H[1]) * (C[0] - D[0]))
+              * (value[2][i] - D[2]);
+          test1 = (visad.util.Util.isApproximatelyEqual(tval1, 0.0, 1E-04))
+              || ((tval1 > 0) == (!evencube) ^ Pos);
+          test2 = (visad.util.Util.isApproximatelyEqual(tval2, 0.0, 1E-04))
+              || ((tval2 > 0) == (!evencube) ^ Pos);
+          test3 = (visad.util.Util.isApproximatelyEqual(tval3, 0.0, 1E-04))
+              || ((tval3 > 0) == (!evencube) ^ Pos);
+
+          // if a test failed go to a new box
+          if (!test1 && evencube)
+            gx--; // LEFT
+          if (!test1 && !evencube)
+            gx++; // RIGHT
+          if (!test2 && evencube)
+            gz++; // FORWARD
+          if (!test2 && !evencube)
+            gz--; // BACK
+          if (!test3 && evencube)
+            gy++; // DOWN
+          if (!test3 && !evencube)
+            gy--; // UP
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0)
+            gx = 0;
+          if (gy < 0)
+            gy = 0;
+          if (gz < 0)
+            gz = 0;
+          if (gx > LengthX - 2)
+            gx = LengthX - 2;
+          if (gy > LengthY - 2)
+            gy = LengthY - 2;
+          if (gz > LengthZ - 2)
+            gz = LengthZ - 2;
+
+          // Detect if the point is off the grid entirely
+          if ((gx == ogx) && (gy == ogy) && (gz == ogz)
+              && (!test1 || !test2 || !test3) && !offgrid) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (((gx == ogx) && (gy == ogy) && (gz == ogz)) || offgrid) {
+            if ((value[0][i] == H[0]) && (value[1][i] == H[1])
+                && (value[2][i] == H[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            if ((value[0][i] == A[0]) && (value[1][i] == A[1])
+                && (value[2][i] == A[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            if ((value[0][i] == D[0]) && (value[1][i] == D[1])
+                && (value[2][i] == D[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            if ((value[0][i] == C[0]) && (value[1][i] == C[1])
+                && (value[2][i] == C[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            // solve point
+            for (int j = 0; j < 3; j++) {
+              M[j] = (C[j] - D[j]) * (H[(j + 1) % 3] - D[(j + 1) % 3])
+                  - (C[(j + 1) % 3] - D[(j + 1) % 3]) * (H[j] - D[j]);
+              N[j] = (A[j] - D[j]) * (H[(j + 1) % 3] - D[(j + 1) % 3])
+                  - (A[(j + 1) % 3] - D[(j + 1) % 3]) * (H[j] - D[j]);
+              O[j] = (C[(j + 1) % 3] - D[(j + 1) % 3])
+                  * (H[(j + 2) % 3] - D[(j + 2) % 3])
+                  - (C[(j + 2) % 3] - D[(j + 2) % 3])
+                  * (H[(j + 1) % 3] - D[(j + 1) % 3]);
+              P[j] = (A[(j + 1) % 3] - D[(j + 1) % 3])
+                  * (H[(j + 2) % 3] - D[(j + 2) % 3])
+                  - (A[(j + 2) % 3] - D[(j + 2) % 3])
+                  * (H[(j + 1) % 3] - D[(j + 1) % 3]);
+              X[j] = value[(j + 2) % 3][i] * (H[(j + 1) % 3] - D[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (H[(j + 2) % 3] - D[(j + 2) % 3])
+                  + D[(j + 1) % 3] * H[(j + 2) % 3] - D[(j + 2) % 3]
+                  * H[(j + 1) % 3];
+              Y[j] = value[j][i] * (H[(j + 1) % 3] - D[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (H[j] - D[j]) + D[(j + 1) % 3]
+                  * H[j] - D[j] * H[(j + 1) % 3];
+            }
+            float s, t, u;
+            // these if statements handle skewed grids
+            float d0 = M[0] * P[0] - N[0] * O[0];
+            float d1 = M[1] * P[1] - N[1] * O[1];
+            float d2 = M[2] * P[2] - N[2] * O[2];
+            float ad0 = Math.abs(d0);
+            float ad1 = Math.abs(d1);
+            float ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = (N[0] * X[0] + P[0] * Y[0]) / d0;
+              t = 1 + (M[0] * X[0] + O[0] * Y[0]) / d0;
+            } else if (ad1 > ad2) {
+              s = (N[1] * X[1] + P[1] * Y[1]) / d1;
+              t = 1 + (M[1] * X[1] + O[1] * Y[1]) / d1;
+            } else {
+              s = (N[2] * X[2] + P[2] * Y[2]) / d2;
+              t = 1 + (M[2] * X[2] + O[2] * Y[2]) / d2;
+            }
+            /*
+             * WLH 5 April 99 if (M[0]P[0] != N[0]O[0]) { s = (N[0]X[0] +
+             * P[0]Y[0])/(M[0]P[0] - N[0]O[0]); t = 1 - (M[0]X[0] +
+             * O[0]Y[0])/(N[0]O[0] - M[0]P[0]); } else if (M[1]P[1] != N[1]O[1])
+             * { s = (N[1]X[1] + P[1]Y[1])/(M[1]P[1] - N[1]O[1]); t = 1 -
+             * (M[1]X[1] + O[1]Y[1])/(N[1]O[1] - M[1]P[1]); } else { s =
+             * (N[2]X[2] + P[2]Y[2])/(M[2]P[2] - N[2]O[2]); t = 1 - (M[2]X[2] +
+             * O[2]Y[2])/(N[2]O[2] - M[2]P[2]); }
+             */
+            d0 = H[0] - D[0];
+            d1 = H[1] - D[1];
+            d2 = H[2] - D[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = 1
+                  - (value[0][i] - D[0] - s * (C[0] - D[0]) - (1 - t)
+                      * (A[0] - D[0])) / d0;
+            } else if (ad1 > ad2) {
+              u = 1
+                  - (value[1][i] - D[1] - s * (C[1] - D[1]) - (1 - t)
+                      * (A[1] - D[1])) / d1;
+            } else {
+              u = 1
+                  - (value[2][i] - D[2] - s * (C[2] - D[2]) - (1 - t)
+                      * (A[2] - D[2])) / d2;
+            }
+            /*
+             * WLH 5 April 99 if (H[0] != D[0]) { u = 1 - ( value[0][i] - D[0] -
+             * s(C[0]-D[0]) - (1-t)(A[0]-D[0]) ) / (H[0]-D[0]); } else if (H[1]
+             * != D[1]) { u = 1 - ( value[1][i] - D[1] - s(C[1]-D[1]) -
+             * (1-t)(A[1]-D[1]) ) / (H[1]-D[1]); } else { u = 1 - ( value[2][i]
+             * - D[2] - s(C[2]-D[2]) - (1-t)(A[2]-D[2]) ) / (H[2]-D[2]); }
+             */
+            if (evencube) {
+              grid[0][i] = gx + s;
+              grid[1][i] = gy + t;
+              grid[2][i] = gz + u;
+            } else {
+              grid[0][i] = gx + 1 - s;
+              grid[1][i] = gy + 1 - t;
+              grid[2][i] = gz + 1 - u;
+            }
+
+            break;
+          }
+        } else if (tetnum == 4) {
+          tval1 = ((G[1] - C[1]) * (H[2] - G[2]) - (G[2] - C[2])
+              * (H[1] - G[1]))
+              * (value[0][i] - G[0])
+              + ((G[2] - C[2]) * (H[0] - G[0]) - (G[0] - C[0]) * (H[2] - G[2]))
+              * (value[1][i] - G[1])
+              + ((G[0] - C[0]) * (H[1] - G[1]) - (G[1] - C[1]) * (H[0] - G[0]))
+              * (value[2][i] - G[2]);
+          tval2 = ((G[1] - F[1]) * (C[2] - G[2]) - (G[2] - F[2])
+              * (C[1] - G[1]))
+              * (value[0][i] - G[0])
+              + ((G[2] - F[2]) * (C[0] - G[0]) - (G[0] - F[0]) * (C[2] - G[2]))
+              * (value[1][i] - G[1])
+              + ((G[0] - F[0]) * (C[1] - G[1]) - (G[1] - F[1]) * (C[0] - G[0]))
+              * (value[2][i] - G[2]);
+          tval3 = ((G[1] - H[1]) * (F[2] - G[2]) - (G[2] - H[2])
+              * (F[1] - G[1]))
+              * (value[0][i] - G[0])
+              + ((G[2] - H[2]) * (F[0] - G[0]) - (G[0] - H[0]) * (F[2] - G[2]))
+              * (value[1][i] - G[1])
+              + ((G[0] - H[0]) * (F[1] - G[1]) - (G[1] - H[1]) * (F[0] - G[0]))
+              * (value[2][i] - G[2]);
+          test1 = (visad.util.Util.isApproximatelyEqual(tval1, 0.0, 1E-04))
+              || ((tval1 > 0) == (!evencube) ^ Pos);
+          test2 = (visad.util.Util.isApproximatelyEqual(tval2, 0.0, 1E-04))
+              || ((tval2 > 0) == (!evencube) ^ Pos);
+          test3 = (visad.util.Util.isApproximatelyEqual(tval3, 0.0, 1E-04))
+              || ((tval3 > 0) == (!evencube) ^ Pos);
+
+          // if a test failed go to a new box
+          if (!test1 && evencube)
+            gy++; // DOWN
+          if (!test1 && !evencube)
+            gy--; // UP
+          if (!test2 && evencube)
+            gx++; // RIGHT
+          if (!test2 && !evencube)
+            gx--; // LEFT
+          if (!test3 && evencube)
+            gz--; // BACK
+          if (!test3 && !evencube)
+            gz++; // FORWARD
+          tetnum = 5;
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0)
+            gx = 0;
+          if (gy < 0)
+            gy = 0;
+          if (gz < 0)
+            gz = 0;
+          if (gx > LengthX - 2)
+            gx = LengthX - 2;
+          if (gy > LengthY - 2)
+            gy = LengthY - 2;
+          if (gz > LengthZ - 2)
+            gz = LengthZ - 2;
+
+          // Detect if the point is off the grid entirely
+          if ((gx == ogx) && (gy == ogy) && (gz == ogz)
+              && (!test1 || !test2 || !test3) && !offgrid) {
+            offgrid = true;
+            continue;
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if (((gx == ogx) && (gy == ogy) && (gz == ogz)) || offgrid) {
+            if ((value[0][i] == H[0]) && (value[1][i] == H[1])
+                && (value[2][i] == H[2])) {
+              if (evencube) {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            if ((value[0][i] == G[0]) && (value[1][i] == G[1])
+                && (value[2][i] == G[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            if ((value[0][i] == F[0]) && (value[1][i] == F[1])
+                && (value[2][i] == F[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              }
+              break;
+            }
+            if ((value[0][i] == C[0]) && (value[1][i] == C[1])
+                && (value[2][i] == C[2])) {
+              if (evencube) {
+                grid[0][i] = gx + 1;
+                grid[1][i] = gy + 1;
+                grid[2][i] = gz + 1;
+              } else {
+                grid[0][i] = gx;
+                grid[1][i] = gy;
+                grid[2][i] = gz;
+              }
+              break;
+            }
+            // solve point
+            for (int j = 0; j < 3; j++) {
+              M[j] = (H[j] - G[j]) * (C[(j + 1) % 3] - G[(j + 1) % 3])
+                  - (H[(j + 1) % 3] - G[(j + 1) % 3]) * (C[j] - G[j]);
+              N[j] = (F[j] - G[j]) * (C[(j + 1) % 3] - G[(j + 1) % 3])
+                  - (F[(j + 1) % 3] - G[(j + 1) % 3]) * (C[j] - G[j]);
+              O[j] = (H[(j + 1) % 3] - G[(j + 1) % 3])
+                  * (C[(j + 2) % 3] - G[(j + 2) % 3])
+                  - (H[(j + 2) % 3] - G[(j + 2) % 3])
+                  * (C[(j + 1) % 3] - G[(j + 1) % 3]);
+              P[j] = (F[(j + 1) % 3] - G[(j + 1) % 3])
+                  * (C[(j + 2) % 3] - G[(j + 2) % 3])
+                  - (F[(j + 2) % 3] - G[(j + 2) % 3])
+                  * (C[(j + 1) % 3] - G[(j + 1) % 3]);
+              X[j] = value[(j + 2) % 3][i] * (C[(j + 1) % 3] - G[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (C[(j + 2) % 3] - G[(j + 2) % 3])
+                  + G[(j + 1) % 3] * C[(j + 2) % 3] - G[(j + 2) % 3]
+                  * C[(j + 1) % 3];
+              Y[j] = value[j][i] * (C[(j + 1) % 3] - G[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (C[j] - G[j]) + G[(j + 1) % 3]
+                  * C[j] - G[j] * C[(j + 1) % 3];
+            }
+            float s, t, u;
+            // these if statements handle skewed grids
+            float d0 = M[0] * P[0] - N[0] * O[0];
+            float d1 = M[1] * P[1] - N[1] * O[1];
+            float d2 = M[2] * P[2] - N[2] * O[2];
+            float ad0 = Math.abs(d0);
+            float ad1 = Math.abs(d1);
+            float ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = 1 - (N[0] * X[0] + P[0] * Y[0]) / d0;
+              t = 1 + (M[0] * X[0] + O[0] * Y[0]) / d0;
+            } else if (ad1 > ad2) {
+              s = 1 - (N[1] * X[1] + P[1] * Y[1]) / d1;
+              t = 1 + (M[1] * X[1] + O[1] * Y[1]) / d1;
+            } else {
+              s = 1 - (N[2] * X[2] + P[2] * Y[2]) / d2;
+              t = 1 + (M[2] * X[2] + O[2] * Y[2]) / d2;
+            }
+            /*
+             * WLH 5 April 99 if (M[0]P[0] != N[0]O[0]) { s = 1 - (N[0]X[0] +
+             * P[0]Y[0])/(M[0]P[0] - N[0]O[0]); t = 1 - (M[0]X[0] +
+             * O[0]Y[0])/(N[0]O[0] - M[0]P[0]); } else if (M[1]P[1] != N[1]O[1])
+             * { s = 1 - (N[1]X[1] + P[1]Y[1])/(M[1]P[1] - N[1]O[1]); t = 1 -
+             * (M[1]X[1] + O[1]Y[1])/(N[1]O[1] - M[1]P[1]); } else { s = 1 -
+             * (N[2]X[2] + P[2]Y[2])/(M[2]P[2] - N[2]O[2]); t = 1 - (M[2]X[2] +
+             * O[2]Y[2])/(N[2]O[2] - M[2]P[2]); }
+             */
+            d0 = C[0] - G[0];
+            d1 = C[1] - G[1];
+            d2 = C[2] - G[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = (value[0][i] - G[0] - (1 - s) * (H[0] - G[0]) - (1 - t)
+                  * (F[0] - G[0]))
+                  / d0;
+            } else if (ad1 > ad2) {
+              u = (value[1][i] - G[1] - (1 - s) * (H[1] - G[1]) - (1 - t)
+                  * (F[1] - G[1]))
+                  / d1;
+            } else {
+              u = (value[2][i] - G[2] - (1 - s) * (H[2] - G[2]) - (1 - t)
+                  * (F[2] - G[2]))
+                  / d2;
+            }
+            /*
+             * WLH 5 April 99 if (C[0] != G[0]) { u = ( value[0][i] - G[0] -
+             * (1-s)(H[0]-G[0]) - (1-t)(F[0]-G[0]) ) / (C[0]-G[0]); } else if
+             * (C[1] != G[1]) { u = ( value[1][i] - G[1] - (1-s)(H[1]-G[1]) -
+             * (1-t)(F[1]-G[1]) ) / (C[1]-G[1]); } else { u = ( value[2][i] -
+             * G[2] - (1-s)(H[2]-G[2]) - (1-t)(F[2]-G[2]) ) / (C[2]-G[2]); }
+             */
+            if (evencube) {
+              grid[0][i] = gx + s;
+              grid[1][i] = gy + t;
+              grid[2][i] = gz + u;
+            } else {
+              grid[0][i] = gx + 1 - s;
+              grid[1][i] = gy + 1 - t;
+              grid[2][i] = gz + 1 - u;
+            }
+
+            break;
+          }
+        } else { // tetnum==5
+          tval1 = ((F[1] - H[1]) * (A[2] - F[2]) - (F[2] - H[2])
+              * (A[1] - F[1]))
+              * (value[0][i] - F[0])
+              + ((F[2] - H[2]) * (A[0] - F[0]) - (F[0] - H[0]) * (A[2] - F[2]))
+              * (value[1][i] - F[1])
+              + ((F[0] - H[0]) * (A[1] - F[1]) - (F[1] - H[1]) * (A[0] - F[0]))
+              * (value[2][i] - F[2]);
+          tval2 = ((C[1] - F[1]) * (A[2] - C[2]) - (C[2] - F[2])
+              * (A[1] - C[1]))
+              * (value[0][i] - C[0])
+              + ((C[2] - F[2]) * (A[0] - C[0]) - (C[0] - F[0]) * (A[2] - C[2]))
+              * (value[1][i] - C[1])
+              + ((C[0] - F[0]) * (A[1] - C[1]) - (C[1] - F[1]) * (A[0] - C[0]))
+              * (value[2][i] - C[2]);
+          tval3 = ((C[1] - A[1]) * (H[2] - C[2]) - (C[2] - A[2])
+              * (H[1] - C[1]))
+              * (value[0][i] - C[0])
+              + ((C[2] - A[2]) * (H[0] - C[0]) - (C[0] - A[0]) * (H[2] - C[2]))
+              * (value[1][i] - C[1])
+              + ((C[0] - A[0]) * (H[1] - C[1]) - (C[1] - A[1]) * (H[0] - C[0]))
+              * (value[2][i] - C[2]);
+          tval4 = ((F[1] - C[1]) * (H[2] - F[2]) - (F[2] - C[2])
+              * (H[1] - F[1]))
+              * (value[0][i] - F[0])
+              + ((F[2] - C[2]) * (H[0] - F[0]) - (F[0] - C[0]) * (H[2] - F[2]))
+              * (value[1][i] - F[1])
+              + ((F[0] - C[0]) * (H[1] - F[1]) - (F[1] - C[1]) * (H[0] - F[0]))
+              * (value[2][i] - F[2]);
+          test1 = (visad.util.Util.isApproximatelyEqual(tval1, 0.0, 1E-04))
+              || ((tval1 > 0) == (!evencube) ^ Pos);
+          test2 = (visad.util.Util.isApproximatelyEqual(tval2, 0.0, 1E-04))
+              || ((tval2 > 0) == (!evencube) ^ Pos);
+          test3 = (visad.util.Util.isApproximatelyEqual(tval3, 0.0, 1E-04))
+              || ((tval3 > 0) == (!evencube) ^ Pos);
+          test4 = (visad.util.Util.isApproximatelyEqual(tval4, 0.0, 1E-04))
+              || ((tval4 > 0) == (!evencube) ^ Pos);
+
+          // if a test failed go to a new tetrahedron
+          if (!test1 && test2 && test3 && test4)
+            tetnum = 1;
+          if (test1 && !test2 && test3 && test4)
+            tetnum = 2;
+          if (test1 && test2 && !test3 && test4)
+            tetnum = 3;
+          if (test1 && test2 && test3 && !test4)
+            tetnum = 4;
+          if ((!test1 && !test2 && evencube) || (!test3 && !test4 && !evencube))
+            gy--; // GO UP
+          if ((!test1 && !test3 && evencube) || (!test2 && !test4 && !evencube))
+            gx--; // GO LEFT
+          if ((!test1 && !test4 && evencube) || (!test2 && !test3 && !evencube))
+            gz--; // GO BACK
+          if ((!test2 && !test3 && evencube) || (!test1 && !test4 && !evencube))
+            gz++; // GO FORWARD
+          if ((!test2 && !test4 && evencube) || (!test1 && !test3 && !evencube))
+            gx++; // GO RIGHT
+          if ((!test3 && !test4 && evencube) || (!test1 && !test2 && !evencube))
+            gy++; // GO DOWN
+
+          // Snap coordinates back onto grid in case they fell off
+          if (gx < 0)
+            gx = 0;
+          if (gy < 0)
+            gy = 0;
+          if (gz < 0)
+            gz = 0;
+          if (gx > LengthX - 2)
+            gx = LengthX - 2;
+          if (gy > LengthY - 2)
+            gy = LengthY - 2;
+          if (gz > LengthZ - 2)
+            gz = LengthZ - 2;
+
+          // Detect if the point is off the grid entirely
+          if (((gx == ogx) && (gy == ogy) && (gz == ogz)
+              && (!test1 || !test2 || !test3 || !test4) && (tetnum == 5))
+              || offgrid) {
+            offgrid = true;
+            boolean OX, OY, OZ, MX, MY, MZ, LX, LY, LZ;
+            OX = OY = OZ = MX = MY = MZ = LX = LY = LZ = false;
+            if (gx == 0)
+              OX = true;
+            if (gy == 0)
+              OY = true;
+            if (gz == 0)
+              OZ = true;
+            if (gx == LengthX - 2)
+              LX = true;
+            if (gy == LengthY - 2)
+              LY = true;
+            if (gz == LengthZ - 2)
+              LZ = true;
+            if (!OX && !LX)
+              MX = true;
+            if (!OY && !LY)
+              MY = true;
+            if (!OZ && !LZ)
+              MZ = true;
+            test1 = test2 = test3 = test4 = false;
+            // 26 cases
+            if (evencube) {
+              if (!LX && !LY && !LZ)
+                tetnum = 1;
+              else if ((LX && OY && MZ) || (MX && OY && LZ) || (LX && MY && LZ)
+                  || (LX && OY && LZ) || (MX && MY && LZ) || (LX && MY && MZ))
+                tetnum = 2;
+              else if ((OX && LY && MZ) || (OX && MY && LZ) || (MX && LY && LZ)
+                  || (OX && LY && LZ) || (MX && LY && MZ))
+                tetnum = 3;
+              else if ((MX && LY && OZ) || (LX && MY && OZ) || (LX && LY && MZ)
+                  || (LX && LY && OZ))
+                tetnum = 4;
+            } else {
+              if (!OX && !OY && !OZ)
+                tetnum = 1;
+              else if ((OX && MY && OZ) || (MX && LY && OZ) || (OX && LY && MZ)
+                  || (OX && LY && OZ) || (MX && MY && OZ) || (OX && MY && MZ))
+                tetnum = 2;
+              else if ((LX && MY && OZ) || (MX && OY && OZ) || (LX && OY && MZ)
+                  || (LX && OY && OZ) || (MX && OY && MZ))
+                tetnum = 3;
+              else if ((OX && OY && MZ) || (OX && MY && OZ) || (MX && OY && LZ)
+                  || (OX && OY && LZ))
+                tetnum = 4;
+            }
+          }
+
+          // If all tests pass then this is the correct tetrahedron
+          if ((gx == ogx) && (gy == ogy) && (gz == ogz) && (tetnum == 5)) {
+            // solve point
+            for (int j = 0; j < 3; j++) {
+              Q[j] = (H[j] + F[j] + A[j] - C[j]) / 2;
+            }
+
+            for (int j = 0; j < 3; j++) {
+              M[j] = (F[j] - Q[j]) * (A[(j + 1) % 3] - Q[(j + 1) % 3])
+                  - (F[(j + 1) % 3] - Q[(j + 1) % 3]) * (A[j] - Q[j]);
+              N[j] = (H[j] - Q[j]) * (A[(j + 1) % 3] - Q[(j + 1) % 3])
+                  - (H[(j + 1) % 3] - Q[(j + 1) % 3]) * (A[j] - Q[j]);
+              O[j] = (F[(j + 1) % 3] - Q[(j + 1) % 3])
+                  * (A[(j + 2) % 3] - Q[(j + 2) % 3])
+                  - (F[(j + 2) % 3] - Q[(j + 2) % 3])
+                  * (A[(j + 1) % 3] - Q[(j + 1) % 3]);
+              P[j] = (H[(j + 1) % 3] - Q[(j + 1) % 3])
+                  * (A[(j + 2) % 3] - Q[(j + 2) % 3])
+                  - (H[(j + 2) % 3] - Q[(j + 2) % 3])
+                  * (A[(j + 1) % 3] - Q[(j + 1) % 3]);
+              X[j] = value[(j + 2) % 3][i] * (A[(j + 1) % 3] - Q[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (A[(j + 2) % 3] - Q[(j + 2) % 3])
+                  + Q[(j + 1) % 3] * A[(j + 2) % 3] - Q[(j + 2) % 3]
+                  * A[(j + 1) % 3];
+              Y[j] = value[j][i] * (A[(j + 1) % 3] - Q[(j + 1) % 3])
+                  - value[(j + 1) % 3][i] * (A[j] - Q[j]) + Q[(j + 1) % 3]
+                  * A[j] - Q[j] * A[(j + 1) % 3];
+            }
+            float s, t, u;
+            // these if statements handle skewed grids
+            float d0 = M[0] * P[0] - N[0] * O[0];
+            float d1 = M[1] * P[1] - N[1] * O[1];
+            float d2 = M[2] * P[2] - N[2] * O[2];
+            float ad0 = Math.abs(d0);
+            float ad1 = Math.abs(d1);
+            float ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              s = (N[0] * X[0] + P[0] * Y[0]) / d0;
+              t = -(M[0] * X[0] + O[0] * Y[0]) / d0;
+            } else if (ad1 > ad2) {
+              s = (N[1] * X[1] + P[1] * Y[1]) / d1;
+              t = -(M[1] * X[1] + O[1] * Y[1]) / d1;
+            } else {
+              s = (N[2] * X[2] + P[2] * Y[2]) / d2;
+              t = -(M[2] * X[2] + O[2] * Y[2]) / d2;
+            }
+            /*
+             * WLH 3 April 99 if (M[0]P[0] != N[0]O[0]) { s = (N[0]X[0] +
+             * P[0]Y[0])/(M[0]P[0] - N[0]O[0]); t = (M[0]X[0] +
+             * O[0]Y[0])/(N[0]O[0] - M[0]P[0]); } else if (M[1]P[1] != N[1]O[1])
+             * { s = (N[1]X[1] + P[1]Y[1])/(M[1]P[1] - N[1]O[1]); t = (M[1]X[1]
+             * + O[1]Y[1])/(N[1]O[1] - M[1]P[1]); } else { s = (N[2]X[2] +
+             * P[2]Y[2])/(M[2]P[2] - N[2]O[2]); t = (M[2]X[2] +
+             * O[2]Y[2])/(N[2]O[2] - M[2]P[2]); }
+             */
+            d0 = A[0] - Q[0];
+            d1 = A[1] - Q[1];
+            d2 = A[2] - Q[2];
+            ad0 = Math.abs(d0);
+            ad1 = Math.abs(d1);
+            ad2 = Math.abs(d2);
+            if (ad0 > ad1 && ad0 > ad2) {
+              u = (value[0][i] - Q[0] - s * (F[0] - Q[0]) - t * (H[0] - Q[0]))
+                  / d0;
+            } else if (ad1 > ad2) {
+              u = (value[1][i] - Q[1] - s * (F[1] - Q[1]) - t * (H[1] - Q[1]))
+                  / d1;
+            } else {
+              u = (value[2][i] - Q[2] - s * (F[2] - Q[2]) - t * (H[2] - Q[2]))
+                  / d2;
+            }
+            /*
+             * WLH 3 April 99 if (A[0] != Q[0]) { u = ( value[0][i] - Q[0] -
+             * s(F[0]-Q[0]) - t(H[0]-Q[0]) ) / (A[0]-Q[0]); } else if (A[1] !=
+             * Q[1]) { u = ( value[1][i] - Q[1] - s(F[1]-Q[1]) - t(H[1]-Q[1]) )
+             * / (A[1]-Q[1]); } else { u = ( value[2][i] - Q[2] - s(F[2]-Q[2]) -
+             * t(H[2]-Q[2]) ) / (A[2]-Q[2]); }
+             */
+            if (evencube) {
+              grid[0][i] = gx + s;
+              grid[1][i] = gy + t;
+              grid[2][i] = gz + u;
+            } else {
+              grid[0][i] = gx + 1 - s;
+              grid[1][i] = gy + 1 - t;
+              grid[2][i] = gz + 1 - u;
+            }
+
+            break;
+          }
+        }
+      }
+
+      // allow estimations up to 0.5 boxes outside of defined samples
+      if ((grid[0][i] <= -0.5) || (grid[0][i] >= LengthX - 0.5)
+          || (grid[1][i] <= -0.5) || (grid[1][i] >= LengthY - 0.5)
+          || (grid[2][i] <= -0.5) || (grid[2][i] >= LengthZ - 0.5)) {
+        grid[0][i] = grid[1][i] = grid[2][i] = Float.NaN;
+      }
+    }
+
+    return grid;
+  }
+
+  public float[] getStartPoint(float x, float y, float z) {
+    float[][]mySamples = getMySamples();
+    int factor = 4;
+    int nx = LengthX / factor;
+    int ny = LengthY / factor;
+    int nz = LengthZ / factor;
+    float gx, gy, gz, dist;
+    gx = gy = gz = 0;
+
+    float min_dist = Float.MAX_VALUE;
+    for (int k = 0; k < nz; k++) {
+      for (int j = 0; j < ny; j++) {
+        for (int i = 0; i < nx; i++) {
+          int idx = k * factor * (LengthX * LengthY) + j * factor * LengthX + i
+              * factor;
+          dist = (mySamples[0][idx] - x) * (mySamples[0][idx] - x)
+              + (mySamples[1][idx] - y) * (mySamples[1][idx] - y)
+              + (mySamples[2][idx] - z) * (mySamples[2][idx] - z);
+          if (dist < min_dist) {
+            min_dist = dist;
+            gx = i * factor;
+            gy = j * factor;
+            gz = k * factor;
+          }
+        }
+      }
+    }
+    return new float[] { gx, gy, gz };
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see visad.Set#makeIsoLines(float[], float, float, float, float[],
+   * byte[][], boolean[], boolean, boolean, visad.ScalarMap[], double, double,
+   * float[][][])
+   */
+  
+  public VisADGeometryArray[][] makeIsoLines(float[] intervals, float lowlimit,
+      float highlimit, float base, float[] fieldValues, byte[][] color_values,
+      boolean[] swap, boolean dash, boolean fill, ScalarMap[] smap,
+      double[] scale, double label_size, boolean sphericalDisplayCS)
+      throws VisADException {
+
+    
+    int ManifoldDimension = getManifoldDimension();
+    int[] Lengths = getLengths();
+    int LengthX = Lengths[0];
+    int LengthY = Lengths[1];
+
+    if (ManifoldDimension != 2) {
+      throw new DisplayException("Gridded3DSet.makeIsoLines: "
+          + "ManifoldDimension must be 2");
+    }
+
+    int nr = LengthX;
+    int nc = LengthY;
+    float[] g = fieldValues;
+
+    double scale_ratio = scale[0] / 0.5169703885552809;
+
+    /*
+     * memory use for temporaries, in bytes (for est = 2 Length): 12
+     * color_length Length + 64 Length + 48 Length + = (112 + 12 color_length)
+     * Length for color_length = 3 this is 148 Length
+     */
+    int color_length = (color_values != null) ? color_values.length : 0;
+    float[][][] grd_normals = null;
+    if (intervals == null) {
+      return null;
+    }
+    byte[][] interval_colors = new byte[color_length][intervals.length];
+
+    if (fill) { // - compute normals at grid points
+      float[][] samples = getSamples(false);
+      grd_normals = new float[nc][nr][3];
+      // calculate normals
+      int k3 = 0;
+      int ki, kj;
+      for (int i = 0; i < LengthY; i++) {
+        for (int j = 0; j < LengthX; j++) {
+          float c0 = samples[0][k3];
+          float c1 = samples[1][k3];
+          float c2 = samples[2][k3];
+          float n0 = 0.0f;
+          float n1 = 0.0f;
+          float n2 = 0.0f;
+          float n, m, m0, m1, m2;
+          for (int ip = -1; ip <= 1; ip += 2) {
+            for (int jp = -1; jp <= 1; jp += 2) {
+              int ii = i + ip;
+              int jj = j + jp;
+              if (0 <= ii && ii < LengthY && 0 <= jj && jj < LengthX) {
+                ki = k3 + ip * LengthX;
+                kj = k3 + jp;
+                m0 = (samples[2][kj] - c2) * (samples[1][ki] - c1)
+                    - (samples[1][kj] - c1) * (samples[2][ki] - c2);
+                m1 = (samples[0][kj] - c0) * (samples[2][ki] - c2)
+                    - (samples[2][kj] - c2) * (samples[0][ki] - c0);
+                m2 = (samples[1][kj] - c1) * (samples[0][ki] - c0)
+                    - (samples[0][kj] - c0) * (samples[1][ki] - c1);
+                m = (float) Math.sqrt(m0 * m0 + m1 * m1 + m2 * m2);
+                if (ip == jp) {
+                  n0 += m0 / m;
+                  n1 += m1 / m;
+                  n2 += m2 / m;
+                } else {
+                  n0 -= m0 / m;
+                  n1 -= m1 / m;
+                  n2 -= m2 / m;
+                }
+              }
+            }
+          }
+          n = (float) Math.sqrt(n0 * n0 + n1 * n1 + n2 * n2);
+          grd_normals[i][j][0] = n0 / n;
+          grd_normals[i][j][1] = n1 / n;
+          grd_normals[i][j][2] = n2 / n;
+          k3++;
+        }
+      }
+
+      // -- compute color at field contour intervals
+      float[] default_intervals = null;
+      Unit ounit = smap[0].getOverrideUnit();
+      if (ounit != null) {
+        default_intervals = (((RealType) smap[0].getScalar()).getDefaultUnit())
+            .toThis(intervals, ounit);
+      } else {
+        default_intervals = intervals;
+      }
+      float[] display_intervals = smap[0].scaleValues(default_intervals);
+      //TDR, 7-19-12: invert display intervals for inverted range 
+      double[] rng = smap[0].getRange();
+      float[] display_intervals_for_invert_range = new float[display_intervals.length];
+      if (rng[0] > rng[1]) {  // - inverted
+        for (int q=0; q<display_intervals.length-1; q++) {
+           display_intervals_for_invert_range[q] = display_intervals[q+1];
+        }
+        display_intervals_for_invert_range[display_intervals.length-1] = display_intervals[display_intervals.length-1];
+        display_intervals = display_intervals_for_invert_range;
+      }
+      BaseColorControl color_control = (BaseColorControl) smap[0].getControl();
+      float[][] temp = null;
+      try {
+        temp = color_control.lookupValues(display_intervals);
+      } catch (Exception e) {
+      }
+      for (int tt = 0; tt < temp.length; tt++) {
+        for (int kk = 0; kk < interval_colors[0].length; kk++) {
+          interval_colors[tt][kk] = ShadowType.floatToByte(temp[tt][kk]);
+        }
+      }
+    }
+
+    // BMF 2006-10-10 get label color from control for Contour2D.contour(...)
+    ContourControl ctrl = (ContourControl) smap[1].getControl();
+    boolean labelAlign = ctrl.getAlignLabels();
+    byte[] labelColor = ctrl.getLabelColor();
+    Object labelFont = ctrl.getLabelFont();
+    int labelFreq = ctrl.getLabelFreq();
+    int labelLineSkip = ctrl.getEveryNth();
+
+    Contour2D.ContourOutput contour = Contour2D.contour(g, nr, nc, intervals,
+        lowlimit, highlimit, base, dash, color_values, swap, fill, grd_normals,
+        interval_colors, scale, scale_ratio, labelFreq, labelLineSkip,
+        label_size, labelAlign, labelColor, labelFont, sphericalDisplayCS, this);
+    if (contour == null) return null;
+
+    VisADGeometryArray[][] basicLines = null;
+    VisADGeometryArray[] fillLines = null;
+    VisADGeometryArray[] labelLines = null;
+
+    int n_labels = contour.stripSet.labels.size();
+    if (n_labels > 0) {
+      labelLines = new VisADGeometryArray[n_labels];
+      for (int k=0; k< n_labels; k++) {
+        labelLines[k] = contour.stripSet.labels.get(k);
+      }
+    }
+
+    if (fill) {
+      VisADTriangleStripArray tsa = contour.triStripBldr.compile(this);
+      return new VisADGeometryArray[][] { 
+          new VisADGeometryArray[] {tsa}, null, labelLines, null
+      };
+    }
+
+    fillLines = new VisADLineStripArray[2];
+    Object[] objArray = contour.stripSet.fillLines.toArray();
+    if (objArray.length > 0 ) {
+      VisADLineStripArray[] arrs = new VisADLineStripArray[objArray.length];
+      for (int k=0; k< objArray.length; k++) {
+       arrs[k] = (VisADLineStripArray) objArray[k];
+      }
+      fillLines[0] = VisADLineStripArray.merge(arrs);
+    }
+
+    objArray = contour.stripSet.fillLinesStyled.toArray();
+    if (objArray.length > 0) {
+      VisADLineStripArray[] arrsStyled = new VisADLineStripArray[objArray.length];
+      for (int k=0; k< objArray.length; k++) {
+         arrsStyled[k] = (VisADLineStripArray) objArray[k];
+      }
+      fillLines[1] = VisADLineStripArray.merge(arrsStyled);
+    }
+
+    VisADLineStripArray[] arrs = null;
+    VisADLineStripArray[] arrsStyled = null;
+
+    objArray = contour.stripSet.cntrLines.toArray();
+    if (objArray.length > 0) {
+      arrs = new VisADLineStripArray[objArray.length];
+      for (int k=0; k<objArray.length; k++) {
+        arrs[k] = (VisADLineStripArray) objArray[k];
+      }
+    }
+
+    objArray = contour.stripSet.cntrLinesStyled.toArray();
+    if (objArray.length > 0) {
+      arrsStyled = new VisADLineStripArray[objArray.length];
+      for (int k=0; k<objArray.length; k++) {
+        arrsStyled[k] = (VisADLineStripArray) objArray[k];
+      }
+    }
+
+    basicLines = new VisADLineStripArray[][] {arrs, arrsStyled};
+
+    return new VisADGeometryArray[][]
+      { basicLines[0], fillLines, labelLines, basicLines[1] };
+  }
+
+  public float[][] getNormals(float[][] grid) throws VisADException {
+    int[] Lengths = getLengths();
+    int LengthX = Lengths[0];
+    int LengthY = Lengths[1];
+
+    float[][] samples = getSamples(false);
+    float[][] grd_normals = new float[3][grid[0].length];
+    // calculate normals
+    int k3 = 0;
+    int ki, kj;
+    for (int tt = 0; tt < grid[0].length; tt++) {
+      k3 = ((int) grid[1][tt]) * LengthX + (int) grid[0][tt];
+
+      int i = k3 / LengthX;
+      int j = k3 - i * LengthX;
+      float c0 = samples[0][k3];
+      float c1 = samples[1][k3];
+      float c2 = samples[2][k3];
+      float n0 = 0.0f;
+      float n1 = 0.0f;
+      float n2 = 0.0f;
+      float n, m, m0, m1, m2;
+      for (int ip = -1; ip <= 1; ip += 2) {
+        for (int jp = -1; jp <= 1; jp += 2) {
+          int ii = i + ip;
+          int jj = j + jp;
+          if (0 <= ii && ii < LengthY && 0 <= jj && jj < LengthX) {
+            ki = k3 + ip * LengthX;
+            kj = k3 + jp;
+            m0 = (samples[2][kj] - c2) * (samples[1][ki] - c1)
+                - (samples[1][kj] - c1) * (samples[2][ki] - c2);
+            m1 = (samples[0][kj] - c0) * (samples[2][ki] - c2)
+                - (samples[2][kj] - c2) * (samples[0][ki] - c0);
+            m2 = (samples[1][kj] - c1) * (samples[0][ki] - c0)
+                - (samples[0][kj] - c0) * (samples[1][ki] - c1);
+            m = (float) Math.sqrt(m0 * m0 + m1 * m1 + m2 * m2);
+            if (ip == jp) {
+              n0 += m0 / m;
+              n1 += m1 / m;
+              n2 += m2 / m;
+            } else {
+              n0 -= m0 / m;
+              n1 -= m1 / m;
+              n2 -= m2 / m;
+            }
+          }
+        }
+      }
+      n = (float) Math.sqrt(n0 * n0 + n1 * n1 + n2 * n2);
+      grd_normals[0][tt] = n0 / n;
+      grd_normals[1][tt] = n1 / n;
+      grd_normals[2][tt] = n2 / n;
+    }
+    return grd_normals;
+  }
+
+  /** constants for isosurface, etc */
+  static final int BIG_NEG = (int) -2e+9;
+  static final float EPS_0 = (float) 1.0e-5;
+
+  static final boolean TRUE = true;
+  static final boolean FALSE = false;
+  static final int MASK = 0x0F;
+  static final int MAX_FLAG_NUM = 317;
+  static final int SF_6B = 0;
+  static final int SF_6D = 6;
+  static final int SF_79 = 12;
+  static final int SF_97 = 18;
+  static final int SF_9E = 24;
+  static final int SF_B6 = 30;
+  static final int SF_D6 = 36;
+  static final int SF_E9 = 42;
+  static final int Zp = 0;
+  static final int Zn = 1;
+  static final int Yp = 2;
+  static final int Yn = 3;
+  static final int Xp = 4;
+  static final int Xn = 5;
+  static final int incZn = 0;
+  static final int incYn = 8;
+  static final int incXn = 16;
+
+  static final int pol_edges[][] = {
+      { 0x0, 0, 0x0, 0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0xe, 1, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0x32, 4, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x3c, 2, 4, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0xc4, 2, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0xca, 6, 1, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0xf6, 1, 4, 5, 2, 7, 6, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xf8, 4, 5, 3, 7, 6, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0x150, 6, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x15e, 4, 6, 8, 1, 3, 2, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x162, 1, 6, 8, 5, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x16c, 6, 8, 5, 3, 2, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x194, 4, 2, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x19a, 1, 3, 7, 8, 4, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1a6, 2, 7, 8, 5, 1, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf, 1, 0x4, 0x1a8, 5, 3, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0x608, 3, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x606, 10, 2, 1, 9, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x63a, 3, 9, 10, 1, 4, 5, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x634, 9, 10, 2, 4, 5, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x6cc, 2, 7, 6, 3, 9, 10, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x6c2, 7, 6, 1, 9, 10, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0x6fe, 1, 4, 5, 2, 7, 6, 3, 9, 10, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0x6f0, 5, 9, 10, 7, 6, 4, 0, 0, 0, 0, 0, 0 },
+      { 0x18, 2, 0x33, 0x758, 3, 9, 10, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x756, 1, 9, 10, 2, 4, 6, 8, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x76a, 1, 6, 8, 5, 3, 9, 10, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x764, 2, 6, 8, 5, 9, 10, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x79c, 7, 8, 4, 2, 10, 3, 9, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x792, 1, 9, 10, 7, 8, 4, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x7ae, 3, 9, 10, 2, 7, 8, 5, 1, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x7a0, 10, 7, 8, 5, 9, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0xa20, 9, 5, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0xa2e, 1, 3, 2, 5, 11, 9, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0xa12, 4, 11, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xa1c, 3, 2, 4, 11, 9, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x18, 2, 0x33, 0xae4, 5, 11, 9, 6, 2, 7, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0xaea, 3, 7, 6, 1, 9, 5, 11, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0xad6, 4, 11, 9, 1, 6, 2, 7, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0xad8, 3, 7, 6, 4, 11, 9, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0xb70, 5, 11, 9, 4, 6, 8, 0, 0, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0xb7e, 4, 6, 8, 1, 3, 2, 5, 11, 9, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xb42, 11, 9, 1, 6, 8, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0xb4c, 8, 11, 9, 3, 2, 6, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0xbb4, 4, 2, 7, 8, 5, 11, 9, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0xbba, 5, 11, 9, 1, 3, 7, 8, 4, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0xb86, 1, 2, 7, 8, 11, 9, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xb88, 9, 3, 7, 8, 11, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0xc28, 11, 10, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xc26, 5, 11, 10, 2, 1, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xc1a, 1, 4, 11, 10, 3, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf, 1, 0x4, 0xc14, 2, 4, 11, 10, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0xcec, 3, 5, 11, 10, 2, 7, 6, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0xce2, 10, 7, 6, 1, 5, 11, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0xcde, 2, 7, 6, 1, 4, 11, 10, 3, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xcd0, 6, 4, 11, 10, 7, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0xd78, 11, 10, 3, 5, 8, 4, 6, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0xd76, 4, 6, 8, 5, 11, 10, 2, 1, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0xd4a, 1, 6, 8, 11, 10, 3, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xd44, 8, 11, 10, 2, 6, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3c, 2, 0x44, 0xdbc, 4, 2, 7, 8, 5, 11, 10, 3, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0xdb2, 8, 11, 10, 7, 4, 1, 5, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0xd8e, 10, 7, 8, 11, 3, 1, 2, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0xd80, 10, 7, 8, 11, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0x1480, 10, 12, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x148e, 3, 2, 1, 10, 12, 7, 0, 0, 0, 0, 0, 0 },
+      { 0x18, 2, 0x33, 0x14b2, 7, 10, 12, 1, 4, 5, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x14bc, 2, 4, 5, 3, 7, 10, 12, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x1444, 2, 10, 12, 6, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x144a, 10, 12, 6, 1, 3, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x1476, 2, 10, 12, 6, 1, 4, 5, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x1478, 6, 4, 5, 3, 10, 12, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x15d0, 6, 8, 4, 7, 10, 12, 0, 0, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0x15de, 2, 1, 3, 6, 8, 4, 7, 10, 12, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x15e2, 8, 5, 1, 6, 12, 7, 10, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x15ec, 7, 10, 12, 6, 8, 5, 3, 2, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1514, 8, 4, 2, 10, 12, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0x151a, 3, 10, 12, 8, 4, 1, 0, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x1526, 2, 10, 12, 8, 5, 1, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1528, 12, 8, 5, 3, 10, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x1288, 7, 3, 9, 12, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1286, 2, 1, 9, 12, 7, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x12ba, 9, 12, 7, 3, 5, 1, 4, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x12b4, 9, 12, 7, 2, 4, 5, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x124c, 3, 9, 12, 6, 2, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf, 1, 0x4, 0x1242, 1, 9, 12, 6, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x127e, 1, 4, 5, 3, 9, 12, 6, 2, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1270, 5, 9, 12, 6, 4, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x13d8, 7, 3, 9, 12, 6, 8, 4, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x13d6, 6, 8, 4, 2, 1, 9, 12, 7, 0, 0, 0, 0 },
+      { 0x3c, 2, 0x44, 0x13ea, 1, 6, 8, 5, 3, 9, 12, 7, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x13e4, 12, 8, 5, 9, 7, 2, 6, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x131c, 2, 3, 9, 12, 8, 4, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1312, 4, 1, 9, 12, 8, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x132e, 5, 9, 12, 8, 1, 2, 3, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x1320, 5, 9, 12, 8, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x1ea0, 10, 12, 7, 9, 5, 11, 0, 0, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0x1eae, 3, 2, 1, 10, 12, 7, 9, 5, 11, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x1e92, 9, 1, 4, 11, 10, 12, 7, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x1e9c, 10, 12, 7, 3, 2, 4, 11, 9, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x1e64, 12, 6, 2, 10, 11, 9, 5, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x1e6a, 9, 5, 11, 10, 12, 6, 1, 3, 0, 0, 0, 0 },
+      { 0x3c, 2, 0x44, 0x1e56, 2, 10, 12, 6, 1, 4, 11, 9, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x1e58, 11, 12, 6, 4, 9, 3, 10, 0, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0x1ff0, 11, 9, 5, 12, 7, 10, 8, 4, 6, 0, 0, 0 },
+      { 0x69, 4, 0x3333, 0x1ffe, 1, 3, 2, 6, 8, 4, 9, 5, 11, 10, 12, 7 },
+      { 0x1e, 2, 0x53, 0x1fc2, 12, 7, 10, 11, 9, 1, 6, 8, 0, 0, 0, 0 },
+      { 0xe9, 3, 0x333, 0x1fcc, 12, 8, 11, 10, 9, 3, 7, 2, 6, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x1f34, 11, 9, 5, 8, 4, 2, 10, 12, 0, 0, 0, 0 },
+      { 0xe9, 3, 0x333, 0x1f3a, 5, 4, 1, 9, 3, 10, 11, 12, 8, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x1f06, 10, 9, 1, 2, 12, 8, 11, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x1f08, 9, 3, 10, 11, 12, 8, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x18a8, 12, 7, 3, 5, 11, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0x18a6, 1, 5, 11, 12, 7, 2, 0, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x189a, 11, 12, 7, 3, 1, 4, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1894, 7, 2, 4, 11, 12, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x186c, 12, 6, 2, 3, 5, 11, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1862, 11, 12, 6, 1, 5, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x185e, 6, 4, 11, 12, 2, 3, 1, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x1850, 11, 12, 6, 4, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x19f8, 8, 4, 6, 12, 7, 3, 5, 11, 0, 0, 0, 0 },
+      { 0xe9, 3, 0x333, 0x19f6, 6, 7, 2, 4, 1, 5, 8, 11, 12, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x19ca, 6, 7, 3, 1, 8, 11, 12, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x19c4, 8, 11, 12, 6, 7, 2, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x193c, 5, 4, 2, 3, 11, 12, 8, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x1932, 11, 12, 8, 5, 4, 1, 0, 0, 0, 0, 0, 0 },
+      { 0xe7, 2, 0x33, 0x190e, 3, 1, 2, 12, 8, 11, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0x1900, 11, 12, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1, 1, 0x3, 0x1900, 11, 8, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x18, 2, 0x33, 0x190e, 8, 12, 11, 2, 1, 3, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x1932, 11, 8, 12, 5, 1, 4, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x193c, 5, 3, 2, 4, 11, 8, 12, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x19c4, 8, 12, 11, 6, 2, 7, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x19ca, 6, 1, 3, 7, 8, 12, 11, 0, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0x19f6, 6, 2, 7, 4, 5, 1, 8, 12, 11, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x19f8, 8, 12, 11, 4, 5, 3, 7, 6, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x1850, 11, 4, 6, 12, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x185e, 6, 12, 11, 4, 2, 1, 3, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1862, 5, 1, 6, 12, 11, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x186c, 6, 12, 11, 5, 3, 2, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1894, 12, 11, 4, 2, 7, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x189a, 4, 1, 3, 7, 12, 11, 0, 0, 0, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0x18a6, 7, 12, 11, 5, 1, 2, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x18a8, 11, 5, 3, 7, 12, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x6, 2, 0x33, 0x1f08, 9, 10, 3, 11, 8, 12, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x1f06, 10, 2, 1, 9, 12, 11, 8, 0, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0x1f3a, 9, 10, 3, 11, 8, 12, 5, 1, 4, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x1f34, 11, 8, 12, 9, 10, 2, 4, 5, 0, 0, 0, 0 },
+      { 0x16, 3, 0x333, 0x1fcc, 10, 3, 9, 7, 6, 2, 12, 11, 8, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x1fc2, 12, 11, 8, 7, 6, 1, 9, 10, 0, 0, 0, 0 },
+      { 0x69, 4, 0x3333, 0x1ffe, 4, 5, 1, 2, 7, 6, 11, 8, 12, 9, 10, 3 },
+      { 0xe9, 3, 0x333, 0x1ff0, 11, 5, 9, 12, 10, 7, 8, 6, 4, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x1e58, 11, 4, 6, 12, 9, 10, 3, 0, 0, 0, 0, 0 },
+      { 0x3c, 2, 0x44, 0x1e56, 10, 2, 1, 9, 12, 11, 4, 6, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x1e6a, 9, 10, 3, 5, 1, 6, 12, 11, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x1e64, 12, 10, 2, 6, 11, 5, 9, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x1e9c, 10, 3, 9, 12, 11, 4, 2, 7, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x1e92, 9, 11, 4, 1, 10, 7, 12, 0, 0, 0, 0, 0 },
+      { 0xe9, 3, 0x333, 0x1eae, 10, 7, 12, 9, 11, 5, 3, 1, 2, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x1ea0, 11, 5, 9, 12, 10, 7, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0x1320, 12, 9, 5, 8, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x132e, 5, 8, 12, 9, 1, 3, 2, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1312, 8, 12, 9, 1, 4, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x131c, 4, 8, 12, 9, 3, 2, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0x13e4, 12, 9, 5, 8, 7, 6, 2, 0, 0, 0, 0, 0 },
+      { 0x3c, 2, 0x44, 0x13ea, 6, 1, 3, 7, 8, 12, 9, 5, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x13d6, 6, 2, 7, 8, 12, 9, 1, 4, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x13d8, 7, 12, 9, 3, 6, 4, 8, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1270, 4, 6, 12, 9, 5, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x127e, 1, 3, 2, 4, 6, 12, 9, 5, 0, 0, 0, 0 },
+      { 0xf, 1, 0x4, 0x1242, 9, 1, 6, 12, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x124c, 2, 6, 12, 9, 3, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x12b4, 12, 9, 5, 4, 2, 7, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x12ba, 9, 3, 7, 12, 5, 4, 1, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1286, 7, 12, 9, 1, 2, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x1288, 7, 12, 9, 3, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x1528, 10, 3, 5, 8, 12, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x1526, 5, 8, 12, 10, 2, 1, 0, 0, 0, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0x151a, 3, 1, 4, 8, 12, 10, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1514, 12, 10, 2, 4, 8, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x15ec, 7, 6, 2, 10, 3, 5, 8, 12, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x15e2, 8, 6, 1, 5, 12, 10, 7, 0, 0, 0, 0, 0 },
+      { 0xe9, 3, 0x333, 0x15de, 2, 3, 1, 6, 4, 8, 7, 12, 10, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x15d0, 6, 4, 8, 7, 12, 10, 0, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x1478, 12, 10, 3, 5, 4, 6, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x1476, 2, 6, 12, 10, 1, 5, 4, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x144a, 3, 1, 6, 12, 10, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x1444, 2, 6, 12, 10, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x14bc, 2, 3, 5, 4, 7, 12, 10, 0, 0, 0, 0, 0 },
+      { 0xe7, 2, 0x33, 0x14b2, 7, 12, 10, 1, 5, 4, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x148e, 3, 1, 2, 10, 7, 12, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0x1480, 10, 7, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x3, 1, 0x4, 0xd80, 10, 11, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0xd8e, 10, 11, 8, 7, 3, 2, 1, 0, 0, 0, 0, 0 },
+      { 0x19, 2, 0x34, 0xdb2, 8, 7, 10, 11, 4, 5, 1, 0, 0, 0, 0, 0 },
+      { 0x3c, 2, 0x44, 0xdbc, 2, 4, 5, 3, 7, 10, 11, 8, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xd44, 6, 2, 10, 11, 8, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0xd4a, 10, 11, 8, 6, 1, 3, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0xd76, 4, 5, 1, 6, 2, 10, 11, 8, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0xd78, 11, 5, 3, 10, 8, 6, 4, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xcd0, 7, 10, 11, 4, 6, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0xcde, 2, 1, 3, 7, 10, 11, 4, 6, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0xce2, 11, 5, 1, 6, 7, 10, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0xcec, 3, 10, 11, 5, 2, 6, 7, 0, 0, 0, 0, 0 },
+      { 0xf, 1, 0x4, 0xc14, 4, 2, 10, 11, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xc1a, 3, 10, 11, 4, 1, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xc26, 1, 2, 10, 11, 5, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0xc28, 3, 10, 11, 5, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0xb88, 11, 8, 7, 3, 9, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0xb86, 7, 2, 1, 9, 11, 8, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0xbba, 5, 1, 4, 11, 8, 7, 3, 9, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0xbb4, 4, 8, 7, 2, 5, 9, 11, 0, 0, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0xb4c, 9, 11, 8, 6, 2, 3, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xb42, 8, 6, 1, 9, 11, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xe9, 3, 0x333, 0xb7e, 4, 8, 6, 1, 2, 3, 5, 9, 11, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0xb70, 5, 9, 11, 4, 8, 6, 0, 0, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0xad8, 7, 3, 9, 11, 4, 6, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0xad6, 4, 1, 9, 11, 6, 7, 2, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0xaea, 3, 1, 6, 7, 9, 11, 5, 0, 0, 0, 0, 0 },
+      { 0xe7, 2, 0x33, 0xae4, 5, 9, 11, 6, 7, 2, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xa1c, 9, 11, 4, 2, 3, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0xa12, 4, 1, 9, 11, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0xa2e, 9, 11, 5, 3, 1, 2, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0xa20, 9, 11, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x7, 1, 0x5, 0x7a0, 9, 5, 8, 7, 10, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x1e, 2, 0x53, 0x7ae, 3, 2, 1, 9, 5, 8, 7, 10, 0, 0, 0, 0 },
+      { 0x1d, 1, 0x6, 0x792, 9, 1, 4, 8, 7, 10, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x79c, 7, 2, 4, 8, 10, 9, 3, 0, 0, 0, 0, 0 },
+      { 0x1b, 1, 0x6, 0x764, 8, 6, 2, 10, 9, 5, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x76a, 1, 5, 8, 6, 3, 10, 9, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x34, 0x756, 1, 2, 10, 9, 4, 8, 6, 0, 0, 0, 0, 0 },
+      { 0xe7, 2, 0x33, 0x758, 3, 10, 9, 4, 8, 6, 0, 0, 0, 0, 0, 0 },
+      { 0x17, 1, 0x6, 0x6f0, 10, 9, 5, 4, 6, 7, 0, 0, 0, 0, 0, 0 },
+      { 0xe9, 3, 0x333, 0x6fe, 1, 5, 4, 2, 6, 7, 3, 10, 9, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x6c2, 10, 9, 1, 6, 7, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x6cc, 2, 6, 7, 3, 10, 9, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x634, 5, 4, 2, 10, 9, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x63a, 5, 4, 1, 9, 3, 10, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x606, 1, 2, 10, 9, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0x608, 9, 3, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf, 1, 0x4, 0x1a8, 8, 7, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x1a6, 1, 5, 8, 7, 2, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x19a, 4, 8, 7, 3, 1, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x194, 4, 8, 7, 2, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0x16c, 2, 3, 5, 8, 6, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x162, 1, 5, 8, 6, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0x15e, 4, 8, 6, 1, 2, 3, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0x150, 6, 4, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf8, 1, 0x5, 0xf8, 6, 7, 3, 5, 4, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 2, 0x33, 0xf6, 1, 5, 4, 2, 6, 7, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0xca, 6, 7, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0xc4, 2, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfc, 1, 0x4, 0x3c, 2, 3, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0x32, 4, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xfe, 1, 0x3, 0xe, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0x0, 0, 0x0, 0x0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0xdb2, 8, 11, 10, 7, 4, 1, 5, 11, 8, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0xd8e, 10, 7, 8, 11, 3, 1, 2, 7, 10, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x13e4, 12, 8, 5, 9, 7, 2, 6, 8, 12, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x132e, 5, 9, 12, 8, 1, 2, 3, 9, 5, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x1e58, 11, 12, 6, 4, 9, 3, 10, 12, 11, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x1f06, 10, 9, 1, 2, 12, 8, 11, 9, 10, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x185e, 6, 4, 11, 12, 2, 3, 1, 4, 6, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x19ca, 6, 7, 3, 1, 8, 11, 12, 7, 6, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x193c, 5, 4, 2, 3, 11, 12, 8, 4, 5, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x1e64, 12, 10, 2, 6, 11, 5, 9, 10, 12, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x1e92, 9, 11, 4, 1, 10, 7, 12, 11, 9, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x13d8, 7, 12, 9, 3, 6, 4, 8, 12, 7, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x12ba, 9, 3, 7, 12, 5, 4, 1, 3, 9, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x15e2, 8, 6, 1, 5, 12, 10, 7, 6, 8, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x1476, 2, 6, 12, 10, 1, 5, 4, 6, 2, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x14bc, 2, 3, 5, 4, 7, 12, 10, 3, 2, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0xd78, 11, 5, 3, 10, 8, 6, 4, 5, 11, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0xcec, 3, 10, 11, 5, 2, 6, 7, 10, 3, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0xbb4, 4, 8, 7, 2, 5, 9, 11, 8, 4, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0xad6, 4, 1, 9, 11, 6, 7, 2, 1, 4, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0xaea, 3, 1, 6, 7, 9, 11, 5, 1, 3, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x79c, 7, 2, 4, 8, 10, 9, 3, 2, 7, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x76a, 1, 5, 8, 6, 3, 10, 9, 5, 1, 0, 0, 0 },
+      { 0xe6, 2, 0x54, 0x756, 1, 2, 10, 9, 4, 8, 6, 2, 1, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x1f08, 9, 3, 10, 12, 8, 11, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x19c4, 8, 11, 12, 7, 2, 6, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x1932, 11, 12, 8, 4, 1, 5, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x1ea0, 11, 5, 9, 10, 7, 12, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x15d0, 6, 4, 8, 12, 10, 7, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x148e, 3, 1, 2, 7, 12, 10, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0xb70, 5, 9, 11, 8, 6, 4, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0xa2e, 9, 11, 5, 1, 2, 3, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x6cc, 2, 6, 7, 10, 9, 3, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x63a, 5, 4, 1, 3, 10, 9, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0x15e, 4, 8, 6, 2, 3, 1, 0, 0, 0, 0, 0, 0 },
+      { 0xf9, 1, 0x6, 0xf6, 1, 5, 4, 6, 7, 2, 0, 0, 0, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1fcc, 12, 8, 11, 9, 3, 10, 7, 2, 6, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1f3a, 5, 4, 1, 3, 10, 9, 11, 12, 8, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x19f6, 6, 7, 2, 1, 5, 4, 8, 11, 12, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1ff0, 11, 5, 9, 10, 7, 12, 8, 6, 4, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1eae, 10, 7, 12, 11, 5, 9, 3, 1, 2, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x15de, 2, 3, 1, 4, 8, 6, 7, 12, 10, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0xb7e, 4, 8, 6, 2, 3, 1, 5, 9, 11, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x6fe, 1, 5, 4, 6, 7, 2, 3, 10, 9, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1fcc, 8, 11, 12, 7, 2, 6, 9, 3, 10, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1f3a, 4, 1, 5, 11, 12, 8, 3, 10, 9, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x19f6, 7, 2, 6, 8, 11, 12, 1, 5, 4, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1ff0, 5, 9, 11, 8, 6, 4, 10, 7, 12, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1eae, 7, 12, 10, 3, 1, 2, 11, 5, 9, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x15de, 3, 1, 2, 7, 12, 10, 4, 8, 6, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0xb7e, 8, 6, 4, 5, 9, 11, 2, 3, 1, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x6fe, 5, 4, 1, 3, 10, 9, 6, 7, 2, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1fcc, 2, 6, 7, 10, 9, 3, 12, 8, 11, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1f3a, 12, 8, 11, 9, 3, 10, 5, 4, 1, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x19f6, 11, 12, 8, 4, 1, 5, 6, 7, 2, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1ff0, 6, 4, 8, 12, 10, 7, 11, 5, 9, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x1eae, 1, 2, 3, 9, 11, 5, 10, 7, 12, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x15de, 12, 10, 7, 6, 4, 8, 2, 3, 1, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0xb7e, 9, 11, 5, 1, 2, 3, 4, 8, 6, 0, 0, 0 },
+      { 0xe9, 2, 0x36, 0x6fe, 10, 9, 3, 2, 6, 7, 1, 5, 4, 0, 0, 0 } };
+
+  static final int sp_cases[] = { 000, 000, 000, 000, 000, 000, 000, 000, 000,
+      000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000,
+      000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000,
+      000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000,
+      000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 256, 257, 000, 000,
+      000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000,
+      000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 258, 000,
+      000, 259, 000, 000, 000, 000, 000, 000, 000, 000, 260, 000, 000, 000,
+      292, 000, 293, 261, 280, 000, 000, 000, 000, 000, 000, 262, 000, 000,
+      294, 263, 281, 264, 282, 000, 000, 000, 000, 000, 000, 000, 000, 000,
+      000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000,
+      000, 000, 295, 000, 000, 000, 265, 000, 266, 296, 283, 000, 000, 000,
+      000, 000, 000, 000, 267, 000, 000, 000, 000, 000, 268, 000, 000, 000,
+      000, 000, 000, 000, 269, 297, 284, 000, 270, 000, 000, 271, 000, 285,
+      000, 000, 000, 000, 000, 000, 000, 000, 272, 000, 000, 000, 273, 000,
+      000, 000, 000, 000, 000, 000, 274, 000, 000, 298, 286, 000, 275, 276,
+      000, 000, 000, 287, 000, 000, 000, 000, 277, 000, 278, 279, 000, 000,
+      299, 000, 288, 000, 289, 000, 000, 000, 000, 000, 000, 000, 000, 290,
+      000, 000, 291, 000, 000, 000, 000, 000, 000 };
+
+  static final int case_E9[] = { Xn, Yp, Zp, incXn, incYn, incZn, Xp, Yn, Zp,
+      incYn, incZn, incXn, Xp, Yp, Zn, incXn, incYn, incZn, Xp, Yp, Zp, incYn,
+      incXn, incZn, Xn, Yn, Zp, incYn, incXn, incZn, Xn, Yp, Zp, incYn, incXn,
+      incZn, Xp, Yn, Zn, incYn, incXn, incZn, Xn, Yn, Zn, incXn, incYn, incZn };
+
+  static final int NTAB[] = { 0, 1, 2, 1, 2, 0, 2, 0, 1, 0, 1, 3, 2, 1, 2, 0,
+      3, 2, 3, 1, 0, 3, 0, 2, 1, 0, 1, 4, 2, 3, 1, 2, 0, 3, 4, 2, 3, 1, 4, 0,
+      3, 4, 2, 0, 1, 4, 0, 3, 1, 2, 0, 1, 5, 2, 4, 3, 1, 2, 0, 3, 5, 4, 2, 3,
+      1, 4, 0, 5, 3, 4, 2, 5, 1, 0, 4, 5, 3, 0, 2, 1, 5, 0, 4, 1, 3, 2 };
+
+  static final int ITAB[] = { 0, 2, 1, 1, 0, 2, 2, 1, 0, 0, 3, 1, 2, 1, 0, 2,
+      3, 2, 1, 3, 0, 3, 2, 0, 1, 0, 4, 1, 3, 2, 1, 0, 2, 4, 3, 2, 1, 3, 0, 4,
+      3, 2, 4, 1, 0, 4, 3, 0, 2, 1, 0, 5, 1, 4, 2, 3, 1, 0, 2, 5, 3, 4, 2, 1,
+      3, 0, 4, 5, 3, 2, 4, 1, 5, 0, 4, 3, 5, 2, 0, 1, 5, 4, 0, 3, 1, 2 };
+
+  static final int STAB[] = { 0, 9, 25, 50 };
+
+  public VisADGeometryArray makeIsoSurface(float isolevel, float[] fieldValues,
+      byte[][] color_values, boolean indexed) throws VisADException {
+    boolean debug = false;
+
+    int i;
+    int size_stripe;
+    int xdim_x_ydim, xdim_x_ydim_x_zdim;
+    int num_cubes, nvertex, npolygons;
+    int ix, iy, ii;
+    int nvertex_estimate;
+
+    if (ManifoldDimension != 3) {
+      throw new DisplayException("Gridded3DSet.makeIsoSurface: "
+          + "ManifoldDimension must be 3");
+    }
+
+    /* adapt isosurf algorithm to Gridded3DSet variables */
+    // NOTE X & Y swap
+    int xdim = LengthY;
+    int ydim = LengthX;
+    int zdim = LengthZ;
+
+    float[] ptGRID = fieldValues;
+
+    xdim_x_ydim = xdim * ydim;
+    xdim_x_ydim_x_zdim = xdim_x_ydim * zdim;
+    num_cubes = (xdim - 1) * (ydim - 1) * (zdim - 1);
+
+    int[] ptFLAG = new int[num_cubes];
+    int[] ptAUX = new int[xdim_x_ydim_x_zdim];
+    int[] pcube = new int[num_cubes + 1];
+
+    // System.out.println("pre-flags: isolevel = " + isolevel +
+    // " xdim, ydim, zdim = " + xdim + " " + ydim + " " + zdim);
+
+    npolygons = flags(isolevel, ptFLAG, ptAUX, pcube, ptGRID, xdim, ydim, zdim);
+
+    if (debug)
+      System.out.println("npolygons= " + npolygons);
+
+    if (npolygons == 0)
+      return null;
+
+    // take the garbage out
+    pcube = null;
+
+    nvertex_estimate = 4 * npolygons + 100;
+    ix = 9 * (nvertex_estimate + 50);
+    iy = 7 * npolygons;
+
+    float[][] VX = new float[1][nvertex_estimate];
+    float[][] VY = new float[1][nvertex_estimate];
+    float[][] VZ = new float[1][nvertex_estimate];
+
+    byte[][] color_temps = null;
+    if (color_values != null) {
+      color_temps = new byte[color_values.length][];
+    }
+
+    int[] Pol_f_Vert = new int[ix];
+    int[] Vert_f_Pol = new int[iy];
+    int[][] arg_Pol_f_Vert = new int[][] { Pol_f_Vert };
+
+    nvertex = isosurf(isolevel, ptFLAG, nvertex_estimate, npolygons, ptGRID,
+        xdim, ydim, zdim, VX, VY, VZ, color_values, color_temps,
+        arg_Pol_f_Vert, Vert_f_Pol);
+    Pol_f_Vert = arg_Pol_f_Vert[0];
+
+    if (nvertex == 0)
+      return null;
+
+    // take the garbage out
+    ptFLAG = null;
+    ptAUX = null;
+    /*
+     * for (int j=0; j<nvertex; j++) { System.out.println("iso vertex[" + j +
+     * "] " + VX[0][j] + " " + VY[0][j] + " " + VZ[0][j]); }
+     */
+    float[][] fieldVertices = new float[3][nvertex];
+    // NOTE - NO X & Y swap
+    System.arraycopy(VX[0], 0, fieldVertices[0], 0, nvertex);
+    System.arraycopy(VY[0], 0, fieldVertices[1], 0, nvertex);
+    System.arraycopy(VZ[0], 0, fieldVertices[2], 0, nvertex);
+    // take the garbage out
+    VX = null;
+    VY = null;
+    VZ = null;
+
+    byte[][] color_levels = null;
+    if (color_values != null) {
+      color_levels = new byte[color_values.length][nvertex];
+      System.arraycopy(color_temps[0], 0, color_levels[0], 0, nvertex);
+      System.arraycopy(color_temps[1], 0, color_levels[1], 0, nvertex);
+      System.arraycopy(color_temps[2], 0, color_levels[2], 0, nvertex);
+      if (color_temps.length > 3) {
+        System.arraycopy(color_temps[3], 0, color_levels[3], 0, nvertex);
+      }
+      // take the garbage out
+      color_temps = null;
+    }
+
+    if (debug)
+      System.out.println("nvertex= " + nvertex);
+
+    float[] NxA = new float[npolygons];
+    float[] NxB = new float[npolygons];
+    float[] NyA = new float[npolygons];
+    float[] NyB = new float[npolygons];
+    float[] NzA = new float[npolygons];
+    float[] NzB = new float[npolygons];
+
+    float[] Pnx = new float[npolygons];
+    float[] Pny = new float[npolygons];
+    float[] Pnz = new float[npolygons];
+
+    float[] NX = new float[nvertex];
+    float[] NY = new float[nvertex];
+    float[] NZ = new float[nvertex];
+
+    make_normals(fieldVertices[0], fieldVertices[1], fieldVertices[2], NX, NY,
+        NZ, nvertex, npolygons, Pnx, Pny, Pnz, NxA, NxB, NyA, NyB, NzA, NzB,
+        Pol_f_Vert, Vert_f_Pol);
+
+    // take the garbage out
+    NxA = NxB = NyA = NyB = NzA = NzB = Pnx = Pny = Pnz = null;
+
+    float[] normals = new float[3 * nvertex];
+    int j = 0;
+    for (i = 0; i < nvertex; i++) {
+      normals[j++] = (float) NX[i];
+      normals[j++] = (float) NY[i];
+      normals[j++] = (float) NZ[i];
+    }
+    // take the garbage out
+    NX = NY = NZ = null;
+
+    /* ----- Find PolyTriangle Stripe */
+    // temporary array to hold maximum possible polytriangle strip
+    int[] stripe = new int[6 * npolygons];
+    int[] vet_pol = new int[npolygons];
+    size_stripe = poly_triangle_stripe(vet_pol, stripe, nvertex, npolygons,
+        Pol_f_Vert, Vert_f_Pol);
+
+    // take the garbage out
+    Pol_f_Vert = null;
+    Vert_f_Pol = null;
+
+    if (indexed) {
+      VisADIndexedTriangleStripArray array = new VisADIndexedTriangleStripArray();
+
+      // set up indices
+      array.indexCount = size_stripe;
+      array.indices = new int[size_stripe];
+      System.arraycopy(stripe, 0, array.indices, 0, size_stripe);
+      array.stripVertexCounts = new int[1];
+      array.stripVertexCounts[0] = size_stripe;
+      // take the garbage out
+      stripe = null;
+
+      // set coordinates and colors
+      setGeometryArray(array, fieldVertices, 4, color_levels);
+      // take the garbage out
+      fieldVertices = null;
+      color_levels = null;
+
+      // array.vertexFormat |= NORMALS;
+      array.normals = normals;
+
+      if (debug) {
+        System.out.println("size_stripe= " + size_stripe);
+        for (ii = 0; ii < size_stripe; ii++)
+          System.out.println(+array.indices[ii]);
+      }
+      return array;
+    } else { // if (!indexed)
+      VisADTriangleStripArray array = new VisADTriangleStripArray();
+      array.stripVertexCounts = new int[] { size_stripe };
+      array.vertexCount = size_stripe;
+
+      array.normals = new float[3 * size_stripe];
+      int k = 0;
+      for (i = 0; i < 3 * size_stripe; i += 3) {
+        j = 3 * stripe[k];
+        array.normals[i] = normals[j];
+        array.normals[i + 1] = normals[j + 1];
+        array.normals[i + 2] = normals[j + 2];
+        k++;
+      }
+      normals = null;
+
+      array.coordinates = new float[3 * size_stripe];
+      k = 0;
+      for (i = 0; i < 3 * size_stripe; i += 3) {
+        j = stripe[k];
+        array.coordinates[i] = fieldVertices[0][j];
+        array.coordinates[i + 1] = fieldVertices[1][j];
+        array.coordinates[i + 2] = fieldVertices[2][j];
+        k++;
+      }
+      fieldVertices = null;
+
+      if (color_levels != null) {
+        int color_length = color_levels.length;
+        array.colors = new byte[color_length * size_stripe];
+        k = 0;
+        if (color_length == 4) {
+          for (i = 0; i < color_length * size_stripe; i += color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i + 1] = color_levels[1][j];
+            array.colors[i + 2] = color_levels[2][j];
+            array.colors[i + 3] = color_levels[3][j];
+            k++;
+          }
+        } else { // if (color_length == 3)
+          for (i = 0; i < color_length * size_stripe; i += color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i + 1] = color_levels[1][j];
+            array.colors[i + 2] = color_levels[2][j];
+            k++;
+          }
+        }
+      }
+      color_levels = null;
+      stripe = null;
+      return array;
+    } // end if (!indexed)
+  }
+
+  public VisADGeometryArray makeIsoSurfaceMissingSpatial(float isolevel,
+      float[] fieldValues, byte[][] color_values, boolean indexed,
+      ShadowRealTupleType Domain, ShadowRealTupleType domain_reference,
+      Unit[] domain_units, CoordinateSystem dataCoordinateSystem,
+      CoordinateSystem coord_sys, ShadowRealType[] DomainReferenceComponents,
+      DisplayTupleType spatial_tuple, float[][] spatial_offset_values)
+      throws VisADException {
+
+    boolean debug = false;
+    int i;
+    int size_stripe;
+    int xdim_x_ydim, xdim_x_ydim_x_zdim;
+    int num_cubes, nvertex, npolygons;
+    int ix, iy, ii;
+    int nvertex_estimate;
+
+    if (ManifoldDimension != 3) {
+      throw new DisplayException("Gridded3DSet.makeIsoSurface: "
+          + "ManifoldDimension must be 3");
+    }
+
+    /* adapt isosurf algorithm to Gridded3DSet variables */
+    // NOTE X & Y swap
+    int xdim = LengthY;
+    int ydim = LengthX;
+    int zdim = LengthZ;
+
+    float[] ptGRID = fieldValues;
+
+    xdim_x_ydim = xdim * ydim;
+    xdim_x_ydim_x_zdim = xdim_x_ydim * zdim;
+    num_cubes = (xdim - 1) * (ydim - 1) * (zdim - 1);
+
+    int[] ptFLAG = new int[num_cubes];
+    int[] ptAUX = new int[xdim_x_ydim_x_zdim];
+    int[] pcube = new int[num_cubes + 1];
+
+    // System.out.println("pre-flags: isolevel = " + isolevel +
+    // " xdim, ydim, zdim = " + xdim + " " + ydim + " " + zdim);
+
+    npolygons = flags(isolevel, ptFLAG, ptAUX, pcube, ptGRID, xdim, ydim, zdim);
+
+    if (debug)
+      System.out.println("npolygons= " + npolygons);
+
+    if (npolygons == 0)
+      return null;
+
+    // take the garbage out
+    pcube = null;
+
+    nvertex_estimate = 4 * npolygons + 100;
+    ix = 9 * (nvertex_estimate + 50);
+    iy = 7 * npolygons;
+
+    float[][] VX = new float[1][nvertex_estimate];
+    float[][] VY = new float[1][nvertex_estimate];
+    float[][] VZ = new float[1][nvertex_estimate];
+
+    byte[][] color_temps = null;
+    if (color_values != null) {
+      color_temps = new byte[color_values.length][];
+    }
+
+    int[] Pol_f_Vert = new int[ix];
+    int[] Vert_f_Pol = new int[iy];
+    int[][] arg_Pol_f_Vert = new int[][] { Pol_f_Vert };
+
+    nvertex = isosurf(isolevel, ptFLAG, nvertex_estimate, npolygons, ptGRID,
+        xdim, ydim, zdim, VX, VY, VZ, color_values, color_temps,
+        arg_Pol_f_Vert, Vert_f_Pol);
+    Pol_f_Vert = arg_Pol_f_Vert[0];
+
+    if (nvertex == 0)
+      return null;
+
+    // take the garbage out
+    ptFLAG = null;
+    ptAUX = null;
+    /*
+     * for (int j=0; j<nvertex; j++) { System.out.println("iso vertex[" + j +
+     * "] " + VX[0][j] + " " + VY[0][j] + " " + VZ[0][j]); }
+     */
+    // float[][] fieldVertices = new float[3][nvertex];
+    float[][] fieldVertices;
+    float[][] fieldVertices_tmp = new float[3][nvertex];
+    // NOTE - NO X & Y swap
+    System.arraycopy(VX[0], 0, fieldVertices_tmp[0], 0, nvertex);
+    System.arraycopy(VY[0], 0, fieldVertices_tmp[1], 0, nvertex);
+    System.arraycopy(VZ[0], 0, fieldVertices_tmp[2], 0, nvertex);
+    // take the garbage out
+    VX = null;
+    VY = null;
+    VZ = null;
+
+    int[] fieldVertices_indices = null;
+    if (spatial_offset_values[0] != null) {
+      int offset_len = spatial_offset_values[0].length;
+      if (offset_len > 1) {
+        fieldVertices_indices = this.valueToIndex(fieldVertices_tmp);
+      }
+    }
+
+    RealTupleType ref = (RealTupleType) domain_reference.getType();
+    fieldVertices = CoordinateSystem.transformCoordinates(ref, null, ref
+        .getDefaultUnits(), null, (RealTupleType) Domain.getType(),
+        dataCoordinateSystem, domain_units, null, fieldVertices_tmp);
+
+    float[][] farray = new float[3][fieldVertices.length];
+    RealType[] dspSpatialReals = spatial_tuple.getRealComponents();
+
+    for (i = 0; i < 3; i++) {
+      Enumeration maps = DomainReferenceComponents[i].getSelectedMapVector()
+          .elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType dspScalar = map.getDisplayScalar();
+        for (int k = 0; k < 3; k++) {
+          if (dspSpatialReals[k].equals(dspScalar)) {
+            farray[k] = map.scaleValues(fieldVertices[i]);
+          }
+        }
+      }
+    }
+
+    fieldVertices = coord_sys.toReference(farray);
+
+    // - apply spatial offsets?
+    if (spatial_offset_values[0] != null) {
+      if (spatial_offset_values[0].length == 1) {
+        for (i = 0; i < fieldVertices[0].length; i++) {
+          fieldVertices[0][i] += spatial_offset_values[0][0];
+          fieldVertices[1][i] += spatial_offset_values[1][0];
+          fieldVertices[2][i] += spatial_offset_values[2][0];
+        }
+      } else {
+        for (i = 0; i < fieldVertices[0].length; i++) {
+          fieldVertices[0][i] += spatial_offset_values[0][fieldVertices_indices[i]];
+          fieldVertices[1][i] += spatial_offset_values[1][fieldVertices_indices[i]];
+          fieldVertices[2][i] += spatial_offset_values[2][fieldVertices_indices[i]];
+        }
+      }
+    }
+
+    byte[][] color_levels = null;
+    if (color_values != null) {
+      color_levels = new byte[color_values.length][nvertex];
+      System.arraycopy(color_temps[0], 0, color_levels[0], 0, nvertex);
+      System.arraycopy(color_temps[1], 0, color_levels[1], 0, nvertex);
+      System.arraycopy(color_temps[2], 0, color_levels[2], 0, nvertex);
+      if (color_temps.length > 3) {
+        System.arraycopy(color_temps[3], 0, color_levels[3], 0, nvertex);
+      }
+      // take the garbage out
+      color_temps = null;
+    }
+
+    if (debug)
+      System.out.println("nvertex= " + nvertex);
+
+    float[] NxA = new float[npolygons];
+    float[] NxB = new float[npolygons];
+    float[] NyA = new float[npolygons];
+    float[] NyB = new float[npolygons];
+    float[] NzA = new float[npolygons];
+    float[] NzB = new float[npolygons];
+
+    float[] Pnx = new float[npolygons];
+    float[] Pny = new float[npolygons];
+    float[] Pnz = new float[npolygons];
+
+    float[] NX = new float[nvertex];
+    float[] NY = new float[nvertex];
+    float[] NZ = new float[nvertex];
+
+    make_normals(fieldVertices[0], fieldVertices[1], fieldVertices[2], NX, NY,
+        NZ, nvertex, npolygons, Pnx, Pny, Pnz, NxA, NxB, NyA, NyB, NzA, NzB,
+        Pol_f_Vert, Vert_f_Pol);
+
+    // take the garbage out
+    NxA = NxB = NyA = NyB = NzA = NzB = Pnx = Pny = Pnz = null;
+
+    float[] normals = new float[3 * nvertex];
+    int j = 0;
+    for (i = 0; i < nvertex; i++) {
+      normals[j++] = (float) NX[i];
+      normals[j++] = (float) NY[i];
+      normals[j++] = (float) NZ[i];
+    }
+    // take the garbage out
+    NX = NY = NZ = null;
+
+    /* ----- Find PolyTriangle Stripe */
+    // temporary array to hold maximum possible polytriangle strip
+    int[] stripe = new int[6 * npolygons];
+    int[] vet_pol = new int[npolygons];
+    size_stripe = poly_triangle_stripe(vet_pol, stripe, nvertex, npolygons,
+        Pol_f_Vert, Vert_f_Pol);
+
+    // take the garbage out
+    Pol_f_Vert = null;
+    Vert_f_Pol = null;
+
+    if (indexed) {
+      VisADIndexedTriangleStripArray array = new VisADIndexedTriangleStripArray();
+
+      // set up indices
+      array.indexCount = size_stripe;
+      array.indices = new int[size_stripe];
+      System.arraycopy(stripe, 0, array.indices, 0, size_stripe);
+      array.stripVertexCounts = new int[1];
+      array.stripVertexCounts[0] = size_stripe;
+      // take the garbage out
+      stripe = null;
+
+      // set coordinates and colors
+      setGeometryArray(array, fieldVertices, 4, color_levels);
+      // take the garbage out
+      fieldVertices = null;
+      color_levels = null;
+
+      // array.vertexFormat |= NORMALS;
+      array.normals = normals;
+
+      if (debug) {
+        System.out.println("size_stripe= " + size_stripe);
+        for (ii = 0; ii < size_stripe; ii++)
+          System.out.println(+array.indices[ii]);
+      }
+      return array;
+    } else { // if (!indexed)
+      VisADTriangleStripArray array = new VisADTriangleStripArray();
+      array.stripVertexCounts = new int[] { size_stripe };
+      array.vertexCount = size_stripe;
+
+      array.normals = new float[3 * size_stripe];
+      int k = 0;
+      for (i = 0; i < 3 * size_stripe; i += 3) {
+        j = 3 * stripe[k];
+        array.normals[i] = normals[j];
+        array.normals[i + 1] = normals[j + 1];
+        array.normals[i + 2] = normals[j + 2];
+        k++;
+      }
+      normals = null;
+
+      array.coordinates = new float[3 * size_stripe];
+      k = 0;
+      for (i = 0; i < 3 * size_stripe; i += 3) {
+        j = stripe[k];
+        array.coordinates[i] = fieldVertices[0][j];
+        array.coordinates[i + 1] = fieldVertices[1][j];
+        array.coordinates[i + 2] = fieldVertices[2][j];
+        k++;
+      }
+      fieldVertices = null;
+
+      if (color_levels != null) {
+        int color_length = color_levels.length;
+        array.colors = new byte[color_length * size_stripe];
+        k = 0;
+        if (color_length == 4) {
+          for (i = 0; i < color_length * size_stripe; i += color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i + 1] = color_levels[1][j];
+            array.colors[i + 2] = color_levels[2][j];
+            array.colors[i + 3] = color_levels[3][j];
+            k++;
+          }
+        } else { // if (color_length == 3)
+          for (i = 0; i < color_length * size_stripe; i += color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i + 1] = color_levels[1][j];
+            array.colors[i + 2] = color_levels[2][j];
+            k++;
+          }
+        }
+      }
+      color_levels = null;
+      stripe = null;
+      return array;
+    } // end if (!indexed)
+  }
+
+  public static int flags(float isovalue, int[] ptFLAG, int[] ptAUX,
+      int[] pcube, float[] ptGRID, int xdim, int ydim, int zdim) {
+    int ii, jj, ix, iy, iz, cb, SF, bcase;
+    int num_cubes, num_cubes_xy, num_cubes_y;
+    int xdim_x_ydim = xdim * ydim;
+    int xdim_x_ydim_x_zdim = xdim_x_ydim * zdim;
+    int npolygons;
+
+    num_cubes_y = ydim - 1;
+    num_cubes_xy = (xdim - 1) * num_cubes_y;
+    num_cubes = (zdim - 1) * num_cubes_xy;
+
+    /*
+     * 
+     * Big Attention
+     * 
+     * pcube must have the dimension "num_cubes+1" because in terms of eficiency
+     * the best loop to calculate "pcube" will use more one component to pcube.
+     */
+
+    /* Calculate the Flag Value of each Cube */
+    /*
+     * In order to simplify the Flags calculations, "pcube" will be used to
+     * store the number of the first vertex of each cube
+     */
+    ii = 0;
+    pcube[0] = 0;
+    cb = 0;
+    for (iz = 0; iz < (zdim - 1); iz++) {
+      for (ix = 0; ix < (xdim - 1); ix++) {
+        cb = pcube[ii];
+        for (iy = 1; iy < (ydim - 1); iy++)
+          /* Vectorized */
+          pcube[ii + iy] = cb + iy;
+        ii += ydim - 1;
+        pcube[ii] = pcube[ii - 1] + 2;
+      }
+      pcube[ii] += ydim;
+    }
+
+    /* Vectorized */
+    for (ii = 0; ii < xdim_x_ydim_x_zdim; ii++) {
+      /*
+       * WLH 24 Oct 97 if (ptGRID[ii] >= INVALID_VALUE) ptAUX[ii] = 0x1001; if
+       * (Float.isNaN(ptGRID[ii]) ptAUX[ii] = 0x1001;
+       */
+      // test for missing
+      if (ptGRID[ii] != ptGRID[ii])
+        ptAUX[ii] = 0x1001;
+      else if (ptGRID[ii] >= isovalue)
+        ptAUX[ii] = 1;
+      else
+        ptAUX[ii] = 0;
+    }
+
+    /* Vectorized */
+    for (ii = 0; ii < num_cubes; ii++) {
+      ptFLAG[ii] = ((ptAUX[pcube[ii]]) | (ptAUX[pcube[ii] + ydim] << 1)
+          | (ptAUX[pcube[ii] + 1] << 2) | (ptAUX[pcube[ii] + ydim + 1] << 3)
+          | (ptAUX[pcube[ii] + xdim_x_ydim] << 4)
+          | (ptAUX[pcube[ii] + ydim + xdim_x_ydim] << 5)
+          | (ptAUX[pcube[ii] + 1 + xdim_x_ydim] << 6) | (ptAUX[pcube[ii] + 1
+          + ydim + xdim_x_ydim] << 7));
+    }
+    /* After this Point it is not more used pcube */
+
+    /* Analyse Special Cases in FLAG */
+    ii = npolygons = 0;
+    while (TRUE) {
+      for (; ii < num_cubes; ii++) {
+        if (((ptFLAG[ii] != 0) && (ptFLAG[ii] != 0xFF))
+            && ptFLAG[ii] < MAX_FLAG_NUM)
+          break;
+      }
+
+      if (ii == num_cubes)
+        break;
+
+      bcase = pol_edges[ptFLAG[ii]][0];
+      if (bcase == 0xE6 || bcase == 0xF9) {
+        iz = ii / num_cubes_xy;
+        ix = ((ii - (iz * num_cubes_xy)) / num_cubes_y);
+        iy = ii - (iz * num_cubes_xy) - (ix * num_cubes_y);
+
+        /* == Z+ == */
+        if ((ptFLAG[ii] & 0xF0) == 0x90 || (ptFLAG[ii] & 0xF0) == 0x60) {
+          cb = (iz < (zdim - 1)) ? ii + num_cubes_xy : -1;
+        }
+        /* == Z- == */
+        else if ((ptFLAG[ii] & 0x0F) == 0x09 || (ptFLAG[ii] & 0x0F) == 0x06) {
+          cb = (iz > 0) ? ii - num_cubes_xy : -1;
+        }
+        /* == Y+ == */
+        else if ((ptFLAG[ii] & 0xCC) == 0x84 || (ptFLAG[ii] & 0xCC) == 0x48) {
+          cb = (iy < (ydim - 1)) ? ii + 1 : -1;
+        }
+        /* == Y- == */
+        else if ((ptFLAG[ii] & 0x33) == 0x21 || (ptFLAG[ii] & 0x33) == 0x12) {
+          cb = (iy > 0) ? ii - 1 : -1;
+        }
+        /* == X+ == */
+        else if ((ptFLAG[ii] & 0xAA) == 0x82 || (ptFLAG[ii] & 0xAA) == 0x28) {
+          cb = (ix < (xdim - 1)) ? ii + num_cubes_y : -1;
+        }
+        /* == X- == */
+        else if ((ptFLAG[ii] & 0x55) == 0x41 || (ptFLAG[ii] & 0x55) == 0x14) {
+          cb = (ix > 0) ? ii - num_cubes_y : -1;
+        }
+        /* == Map Special Case == */
+        if ((cb > -1 && cb < num_cubes) && ptFLAG[cb] < 316) /*
+                                                              * changed by BEP
+                                                              * on 7-20-92
+                                                              */
+        {
+          bcase = pol_edges[ptFLAG[cb]][0];
+          if (bcase == 0x06 || bcase == 0x16 || bcase == 0x19 || bcase == 0x1E
+              || bcase == 0x3C || bcase == 0x69) {
+            ptFLAG[ii] = sp_cases[ptFLAG[ii]];
+          }
+        }
+      } else if (bcase == 0xE9) {
+        iz = ii / num_cubes_xy;
+        ix = ((ii - (iz * num_cubes_xy)) / num_cubes_y);
+        iy = ii - (iz * num_cubes_xy) - (ix * num_cubes_y);
+
+        SF = 0;
+        if (ptFLAG[ii] == 0x6B)
+          SF = SF_6B;
+        else if (ptFLAG[ii] == 0x6D)
+          SF = SF_6D;
+        else if (ptFLAG[ii] == 0x79)
+          SF = SF_79;
+        else if (ptFLAG[ii] == 0x97)
+          SF = SF_97;
+        else if (ptFLAG[ii] == 0x9E)
+          SF = SF_9E;
+        else if (ptFLAG[ii] == 0xB6)
+          SF = SF_B6;
+        else if (ptFLAG[ii] == 0xD6)
+          SF = SF_D6;
+        else if (ptFLAG[ii] == 0xE9)
+          SF = SF_E9;
+        for (jj = 0; jj < 3; jj++) {
+          if (case_E9[jj + SF] == Zp) {
+            cb = (iz < (zdim - 1)) ? ii + num_cubes_xy : -1;
+          } else if (case_E9[jj + SF] == Zn) {
+            cb = (iz > 0) ? ii - num_cubes_xy : -1;
+          } else if (case_E9[jj + SF] == Yp) {
+            cb = (iy < (ydim - 1)) ? ii + 1 : -1;
+          } else if (case_E9[jj + SF] == Yn) {
+            cb = (iy > 0) ? ii - 1 : -1;
+          } else if (case_E9[jj + SF] == Xp) {
+            cb = (ix < (xdim - 1)) ? ii + num_cubes_y : -1;
+          } else if (case_E9[jj + SF] == Xn) {
+            cb = (ix > 0) ? ii - num_cubes_y : -1;
+          }
+          /*
+           * changed: if ((cb > -1 && cb < num_cubes)) to:
+           */
+          if ((cb > -1 && cb < num_cubes) && ptFLAG[cb] < 316)
+          /* changed by BEP on 7-20-92 */
+          {
+            bcase = pol_edges[ptFLAG[cb]][0];
+            if (bcase == 0x06 || bcase == 0x16 || bcase == 0x19
+                || bcase == 0x1E || bcase == 0x3C || bcase == 0x69) {
+              ptFLAG[ii] = sp_cases[ptFLAG[ii]] + case_E9[jj + SF + 3];
+              break;
+            }
+          }
+        }
+      }
+
+      /* Calculate the Number of Generated Triangles and Polygons */
+      npolygons += pol_edges[ptFLAG[ii]][1];
+      ii++;
+    }
+
+    /* npolygons2 = 2npolygons; */
+
+    return npolygons;
+  }
+
+  private int isosurf(float isovalue, int[] ptFLAG, int nvertex_estimate,
+      int npolygons, float[] ptGRID, int xdim, int ydim, int zdim,
+      float[][] VX, float[][] VY, float[][] VZ, byte[][] auxValues,
+      byte[][] auxLevels, int[][] Pol_f_Vert, int[] Vert_f_Pol)
+      throws VisADException {
+
+    int ix, iy, iz, caseA, above, bellow, front, rear, mm, nn;
+    int ii, jj, kk, ncube, cpl, pvp, pa, ve;
+    int[] calc_edge = new int[13];
+    int xx, yy, zz;
+    float cp;
+    float vnode0 = 0;
+    float vnode1 = 0;
+    float vnode2 = 0;
+    float vnode3 = 0;
+    float vnode4 = 0;
+    float vnode5 = 0;
+    float vnode6 = 0;
+    float vnode7 = 0;
+    int pt = 0;
+    int n_pol;
+    int aa;
+    int bb;
+    int temp;
+    int xdim_x_ydim = xdim * ydim;
+    int nvet;
+
+    int t;
+
+    float[][] samples = getSamples(false);
+
+    int naux = (auxValues != null) ? auxValues.length : 0;
+    if (naux > 0) {
+      if (auxLevels == null || auxLevels.length != naux) {
+        throw new SetException("Gridded3DSet.isosurf: " + "auxLevels length "
+            + auxLevels.length + " doesn't match expected " + naux);
+      }
+      for (int i = 0; i < naux; i++) {
+        if (auxValues[i].length != Length) {
+          throw new SetException("Gridded3DSet.isosurf: expected auxValues "
+              + " length#" + i + " to be " + Length + ", not "
+              + auxValues[i].length);
+        }
+      }
+    } else {
+      if (auxLevels != null) {
+        throw new SetException("Gridded3DSet.isosurf: "
+            + "auxValues null but auxLevels not null");
+      }
+    }
+
+    // temporary storage of auxLevels structure
+    byte[][] tempaux = (naux > 0) ? new byte[naux][nvertex_estimate] : null;
+
+    bellow = rear = 0;
+    above = front = 1;
+
+    /* Initialize the Auxiliar Arrays of Pointers */
+    /*
+     * WLH 25 Oct 97 ix = 9 (npolygons2 + 50); iy = 7 npolygons; ii = ix + iy;
+     */
+    for (jj = 0; jj < Pol_f_Vert[0].length; jj++) {
+      Pol_f_Vert[0][jj] = BIG_NEG;
+    }
+    for (jj = 8; jj < Pol_f_Vert[0].length; jj += 9) {
+      Pol_f_Vert[0][jj] = 0;
+    }
+    for (jj = 0; jj < Vert_f_Pol.length; jj++) {
+      Vert_f_Pol[jj] = BIG_NEG;
+    }
+    for (jj = 6; jj < Vert_f_Pol.length; jj += 7) {
+      Vert_f_Pol[jj] = 0;
+    }
+
+    /*
+     * Allocate the auxiliar edge vectors size ixPlane = (xdim - 1) ydim =
+     * xdim_x_ydim - ydim size iyPlane = (ydim - 1) xdim = xdim_x_ydim - xdim
+     * size izPlane = xdim
+     */
+
+    xx = xdim_x_ydim - ydim;
+    yy = xdim_x_ydim - xdim;
+    zz = ydim;
+    ii = 2 * (xx + yy + zz);
+
+    int[] P_array = new int[ii];
+
+    /*
+     * Calculate the Vertex of the Polygons which edges were calculated above
+     */
+    nvet = ncube = cpl = pvp = 0;
+
+    for (iz = 0; iz < zdim - 1; iz++) {
+
+      for (ix = 0; ix < xdim - 1; ix++) {
+
+        for (iy = 0; iy < ydim - 1; iy++) {
+          if ((ptFLAG[ncube] != 0 & ptFLAG[ncube] != 0xFF)) {
+
+            if (nvet + 12 > nvertex_estimate) {
+              // allocate more space
+              nvertex_estimate = 2 * (nvet + 12);
+              if (naux > 0) {
+                for (int i = 0; i < naux; i++) {
+                  byte[] tt = tempaux[i];
+                  tempaux[i] = new byte[nvertex_estimate];
+                  System.arraycopy(tt, 0, tempaux[i], 0, nvet);
+                }
+              }
+              float[] tt = VX[0];
+              VX[0] = new float[nvertex_estimate];
+              System.arraycopy(tt, 0, VX[0], 0, tt.length);
+              tt = VY[0];
+              VY[0] = new float[nvertex_estimate];
+              System.arraycopy(tt, 0, VY[0], 0, tt.length);
+              tt = VZ[0];
+              VZ[0] = new float[nvertex_estimate];
+              System.arraycopy(tt, 0, VZ[0], 0, tt.length);
+              int big_ix = 9 * (nvertex_estimate + 50);
+              int[] it = Pol_f_Vert[0];
+              Pol_f_Vert[0] = new int[big_ix];
+              for (jj = 0; jj < Pol_f_Vert[0].length; jj++) {
+                Pol_f_Vert[0][jj] = BIG_NEG;
+              }
+              for (jj = 8; jj < Pol_f_Vert[0].length; jj += 9) {
+                Pol_f_Vert[0][jj] = 0;
+              }
+              System.arraycopy(it, 0, Pol_f_Vert[0], 0, it.length);
+            }
+
+            /* WLH 2 April 99 */
+            vnode0 = ptGRID[pt];
+            vnode1 = ptGRID[pt + ydim];
+            vnode2 = ptGRID[pt + 1];
+            vnode3 = ptGRID[pt + ydim + 1];
+            vnode4 = ptGRID[pt + xdim_x_ydim];
+            vnode5 = ptGRID[pt + ydim + xdim_x_ydim];
+            vnode6 = ptGRID[pt + 1 + xdim_x_ydim];
+            vnode7 = ptGRID[pt + 1 + ydim + xdim_x_ydim];
+
+            if ((ptFLAG[ncube] < MAX_FLAG_NUM)) {
+              /* fill_Vert_f_Pol(ncube); */
+
+              kk = pol_edges[ptFLAG[ncube]][2];
+              aa = ptFLAG[ncube];
+              bb = 4;
+              pa = pvp;
+              n_pol = pol_edges[ptFLAG[ncube]][1];
+              for (ii = 0; ii < n_pol; ii++) {
+                Vert_f_Pol[pa + 6] = ve = kk & MASK;
+                ve += pa;
+                for (jj = pa; jj < ve && jj < pa + 6; jj++) {
+
+                  Vert_f_Pol[jj] = pol_edges[aa][bb];
+                  bb++;
+                  if (bb >= 16) {
+                    aa++;
+                    bb -= 16;
+                  }
+                }
+                kk >>= 4;
+                pa += 7;
+              }
+              /* end fill_Vert_f_Pol(ncube); */
+              /* */
+
+              /* find_vertex(); */
+              /*
+               * WLH 2 April 99 vnode0 = ptGRID[pt]; vnode1 = ptGRID[pt + ydim];
+               * vnode2 = ptGRID[pt + 1]; vnode3 = ptGRID[pt + ydim + 1]; vnode4
+               * = ptGRID[pt + xdim_x_ydim]; vnode5 = ptGRID[pt + ydim +
+               * xdim_x_ydim]; vnode6 = ptGRID[pt + 1 + xdim_x_ydim]; vnode7 =
+               * ptGRID[pt + 1 + ydim + xdim_x_ydim];
+               */
+
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0002) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 0-1
+                                                                    */
+                if ((iz != 0) || (iy != 0)) {
+                  calc_edge[1] = P_array[bellow * xx + ix * ydim + iy];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode1 - vnode0; cp = ( ( isovalue
+                   * - vnode0 ) / nodeDiff ) + ix; VX[0][nvet] = cp; VY[0][nvet]
+                   * = iy; VZ[0][nvet] = iz;
+                   */
+                  cp = ((isovalue - vnode0) / (vnode1 - vnode0));
+                  VX[0][nvet] = (float) cp * samples[0][pt + ydim]
+                      + (1.0f - cp) * samples[0][pt];
+                  VY[0][nvet] = (float) cp * samples[1][pt + ydim]
+                      + (1.0f - cp) * samples[1][pt];
+                  VZ[0][nvet] = (float) cp * samples[2][pt + ydim]
+                      + (1.0f - cp) * samples[2][pt];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + ydim] < 0) ? ((float) auxValues[j][pt
+                            + ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + ydim])) + (1.0f - cp)
+                        * ((auxValues[j][pt] < 0) ? ((float) auxValues[j][pt]) + 256.0f
+                            : ((float) auxValues[j][pt])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                     * ydim] + (1.0f-cp) auxValues[j][pt];
+                     */
+                  }
+
+                  calc_edge[1] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0004) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 0-2
+                                                                    */
+                if ((iz != 0) || (ix != 0)) {
+                  calc_edge[2] = P_array[2 * xx + bellow * yy + iy * xdim + ix];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode2 - vnode0; cp = ( ( isovalue
+                   * - vnode0 ) / nodeDiff ) + iy; VX[0][nvet] = ix; VY[0][nvet]
+                   * = cp; VZ[0][nvet] = iz;
+                   */
+                  cp = ((isovalue - vnode0) / (vnode2 - vnode0));
+                  VX[0][nvet] = (float) cp * samples[0][pt + 1] + (1.0f - cp)
+                      * samples[0][pt];
+                  VY[0][nvet] = (float) cp * samples[1][pt + 1] + (1.0f - cp)
+                      * samples[1][pt];
+                  VZ[0][nvet] = (float) cp * samples[2][pt + 1] + (1.0f - cp)
+                      * samples[2][pt];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + 1] < 0) ? ((float) auxValues[j][pt + 1]) + 256.0f
+                            : ((float) auxValues[j][pt + 1])) + (1.0f - cp)
+                        * ((auxValues[j][pt] < 0) ? ((float) auxValues[j][pt]) + 256.0f
+                            : ((float) auxValues[j][pt])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                     * 1] + (1.0f-cp) auxValues[j][pt];
+                     */
+                  }
+
+                  calc_edge[2] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0008) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 0-4
+                                                                    */
+                if ((ix != 0) || (iy != 0)) {
+                  calc_edge[3] = P_array[2 * xx + 2 * yy + rear * zz + iy];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode4 - vnode0; cp = ( ( isovalue
+                   * - vnode0 ) / nodeDiff ) + iz; VX[0][nvet] = ix; VY[0][nvet]
+                   * = iy; VZ[0][nvet] = cp;
+                   */
+                  cp = ((isovalue - vnode0) / (vnode4 - vnode0));
+                  VX[0][nvet] = (float) cp * samples[0][pt + xdim_x_ydim]
+                      + (1.0f - cp) * samples[0][pt];
+                  VY[0][nvet] = (float) cp * samples[1][pt + xdim_x_ydim]
+                      + (1.0f - cp) * samples[1][pt];
+                  VZ[0][nvet] = (float) cp * samples[2][pt + xdim_x_ydim]
+                      + (1.0f - cp) * samples[2][pt];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                            + xdim_x_ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + xdim_x_ydim])) + (1.0f - cp)
+                        * ((auxValues[j][pt] < 0) ? ((float) auxValues[j][pt]) + 256.0f
+                            : ((float) auxValues[j][pt])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                     * xdim_x_ydim] + (1.0f-cp) auxValues[j][pt];
+                     */
+                  }
+
+                  calc_edge[3] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0010) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 1-3
+                                                                    */
+                if ((iz != 0)) {
+                  calc_edge[4] = P_array[2 * xx + bellow * yy + iy * xdim
+                      + (ix + 1)];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode3 - vnode1; cp = ( ( isovalue
+                   * - vnode1 ) / nodeDiff ) + iy; VX[0][nvet] = ix+1;
+                   * VY[0][nvet] = cp; VZ[0][nvet] = iz;
+                   */
+                  cp = ((isovalue - vnode1) / (vnode3 - vnode1));
+                  VX[0][nvet] = (float) cp * samples[0][pt + ydim + 1]
+                      + (1.0f - cp) * samples[0][pt + ydim];
+                  VY[0][nvet] = (float) cp * samples[1][pt + ydim + 1]
+                      + (1.0f - cp) * samples[1][pt + ydim];
+                  VZ[0][nvet] = (float) cp * samples[2][pt + ydim + 1]
+                      + (1.0f - cp) * samples[2][pt + ydim];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + ydim + 1] < 0) ? ((float) auxValues[j][pt
+                            + ydim + 1]) + 256.0f
+                            : ((float) auxValues[j][pt + ydim + 1])) + (1.0f - cp)
+                        * ((auxValues[j][pt + ydim] < 0) ? ((float) auxValues[j][pt
+                            + ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + ydim])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                     * ydim + 1] + (1.0f-cp) auxValues[j][pt + ydim];
+                     */
+                  }
+
+                  calc_edge[4] = nvet;
+                  P_array[2 * xx + bellow * yy + iy * xdim + (ix + 1)] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0020) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 1-5
+                                                                    */
+                if ((iy != 0)) {
+                  calc_edge[5] = P_array[2 * xx + 2 * yy + front * zz + iy];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode5 - vnode1; cp = ( ( isovalue
+                   * - vnode1 ) / nodeDiff ) + iz; VX[0][nvet] = ix+1;
+                   * VY[0][nvet] = iy; VZ[0][nvet] = cp;
+                   */
+                  cp = ((isovalue - vnode1) / (vnode5 - vnode1));
+                  VX[0][nvet] = (float) cp
+                      * samples[0][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                      * samples[0][pt + ydim];
+                  VY[0][nvet] = (float) cp
+                      * samples[1][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                      * samples[1][pt + ydim];
+                  VZ[0][nvet] = (float) cp
+                      * samples[2][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                      * samples[2][pt + ydim];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                            + ydim + xdim_x_ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + ydim + xdim_x_ydim])) + (1.0f - cp)
+                        * ((auxValues[j][pt + ydim] < 0) ? ((float) auxValues[j][pt
+                            + ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + ydim])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                     * ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt + ydim];
+                     */
+                  }
+
+                  calc_edge[5] = nvet;
+                  P_array[2 * xx + 2 * yy + front * zz + iy] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0040) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 2-3
+                                                                    */
+                if ((iz != 0)) {
+                  calc_edge[6] = P_array[bellow * xx + ix * ydim + (iy + 1)];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode3 - vnode2; cp = ( ( isovalue
+                   * - vnode2 ) / nodeDiff ) + ix; VX[0][nvet] = cp; VY[0][nvet]
+                   * = iy+1; VZ[0][nvet] = iz;
+                   */
+                  cp = ((isovalue - vnode2) / (vnode3 - vnode2));
+                  VX[0][nvet] = (float) cp * samples[0][pt + ydim + 1]
+                      + (1.0f - cp) * samples[0][pt + 1];
+                  VY[0][nvet] = (float) cp * samples[1][pt + ydim + 1]
+                      + (1.0f - cp) * samples[1][pt + 1];
+                  VZ[0][nvet] = (float) cp * samples[2][pt + ydim + 1]
+                      + (1.0f - cp) * samples[2][pt + 1];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + ydim + 1] < 0) ? ((float) auxValues[j][pt
+                            + ydim + 1]) + 256.0f
+                            : ((float) auxValues[j][pt + ydim + 1])) + (1.0f - cp)
+                        * ((auxValues[j][pt + 1] < 0) ? ((float) auxValues[j][pt + 1]) + 256.0f
+                            : ((float) auxValues[j][pt + 1])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                     * ydim + 1] + (1.0f-cp) auxValues[j][pt + 1];
+                     */
+                  }
+
+                  calc_edge[6] = nvet;
+                  P_array[bellow * xx + ix * ydim + (iy + 1)] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0080) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 2-6
+                                                                    */
+                if ((ix != 0)) {
+                  calc_edge[7] = P_array[2 * xx + 2 * yy + rear * zz + (iy + 1)];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode6 - vnode2; cp = ( ( isovalue
+                   * - vnode2 ) / nodeDiff ) + iz; VX[0][nvet] = ix; VY[0][nvet]
+                   * = iy+1; VZ[0][nvet] = cp;
+                   */
+                  cp = ((isovalue - vnode2) / (vnode6 - vnode2));
+                  VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim]
+                      + (1.0f - cp) * samples[0][pt + 1];
+                  VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim]
+                      + (1.0f - cp) * samples[1][pt + 1];
+                  VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim]
+                      + (1.0f - cp) * samples[2][pt + 1];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                            + 1 + xdim_x_ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + 1 + xdim_x_ydim])) + (1.0f - cp)
+                        * ((auxValues[j][pt + 1] < 0) ? ((float) auxValues[j][pt + 1]) + 256.0f
+                            : ((float) auxValues[j][pt + 1])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt + 1
+                     * + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt + 1];
+                     */
+                  }
+
+                  calc_edge[7] = nvet;
+                  P_array[2 * xx + 2 * yy + rear * zz + (iy + 1)] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0100) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 3-7
+                                                                    */
+                /*
+                 * WLH 26 Oct 97 nodeDiff = vnode7 - vnode3; cp = ( ( isovalue -
+                 * vnode3 ) / nodeDiff ) + iz; VX[0][nvet] = ix+1; VY[0][nvet] =
+                 * iy+1; VZ[0][nvet] = cp;
+                 */
+                cp = ((isovalue - vnode3) / (vnode7 - vnode3));
+                VX[0][nvet] = (float) cp
+                    * samples[0][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[0][pt + ydim + 1];
+                VY[0][nvet] = (float) cp
+                    * samples[1][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[1][pt + ydim + 1];
+                VZ[0][nvet] = (float) cp
+                    * samples[2][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[2][pt + ydim + 1];
+
+                for (int j = 0; j < naux; j++) {
+                  t = (int) (cp
+                      * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                          + 1 + ydim + xdim_x_ydim]) + 256.0f
+                          : ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim])) + (1.0f - cp)
+                      * ((auxValues[j][pt + ydim + 1] < 0) ? ((float) auxValues[j][pt
+                          + ydim + 1]) + 256.0f
+                          : ((float) auxValues[j][pt + ydim + 1])));
+                  tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                      : ((t < 128) ? t : t - 256)));
+                  /*
+                   * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt + 1 +
+                   * ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt + ydim +
+                   * 1];
+                   */
+                }
+
+                calc_edge[8] = nvet;
+                P_array[2 * xx + 2 * yy + front * zz + (iy + 1)] = nvet;
+                nvet++;
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0200) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 4-5
+                                                                    */
+                if ((iy != 0)) {
+                  calc_edge[9] = P_array[above * xx + ix * ydim + iy];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode5 - vnode4; cp = ( ( isovalue
+                   * - vnode4 ) / nodeDiff ) + ix; VX[0][nvet] = cp; VY[0][nvet]
+                   * = iy; VZ[0][nvet] = iz+1;
+                   */
+                  cp = ((isovalue - vnode4) / (vnode5 - vnode4));
+                  VX[0][nvet] = (float) cp
+                      * samples[0][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                      * samples[0][pt + xdim_x_ydim];
+                  VY[0][nvet] = (float) cp
+                      * samples[1][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                      * samples[1][pt + xdim_x_ydim];
+                  VZ[0][nvet] = (float) cp
+                      * samples[2][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                      * samples[2][pt + xdim_x_ydim];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                            + ydim + xdim_x_ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + ydim + xdim_x_ydim])) + (1.0f - cp)
+                        * ((auxValues[j][pt + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                            + xdim_x_ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + xdim_x_ydim])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                     * ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt +
+                     * xdim_x_ydim];
+                     */
+                  }
+
+                  calc_edge[9] = nvet;
+                  P_array[above * xx + ix * ydim + iy] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0400) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 4-6
+                                                                    */
+                if ((ix != 0)) {
+                  calc_edge[10] = P_array[2 * xx + above * yy + iy * xdim + ix];
+                } else {
+                  /*
+                   * WLH 26 Oct 97 nodeDiff = vnode6 - vnode4; cp = ( ( isovalue
+                   * - vnode4 ) / nodeDiff ) + iy; VX[0][nvet] = ix; VY[0][nvet]
+                   * = cp; VZ[0][nvet] = iz+1;
+                   */
+                  cp = ((isovalue - vnode4) / (vnode6 - vnode4));
+                  VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim]
+                      + (1.0f - cp) * samples[0][pt + xdim_x_ydim];
+                  VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim]
+                      + (1.0f - cp) * samples[1][pt + xdim_x_ydim];
+                  VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim]
+                      + (1.0f - cp) * samples[2][pt + xdim_x_ydim];
+
+                  for (int j = 0; j < naux; j++) {
+                    t = (int) (cp
+                        * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                            + 1 + xdim_x_ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + 1 + xdim_x_ydim])) + (1.0f - cp)
+                        * ((auxValues[j][pt + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                            + xdim_x_ydim]) + 256.0f
+                            : ((float) auxValues[j][pt + xdim_x_ydim])));
+                    tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                        : ((t < 128) ? t : t - 256)));
+                    /*
+                     * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt + 1
+                     * + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt +
+                     * xdim_x_ydim];
+                     */
+                  }
+
+                  calc_edge[10] = nvet;
+                  P_array[2 * xx + above * yy + iy * xdim + ix] = nvet;
+                  nvet++;
+                }
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x0800) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 5-7
+                                                                    */
+                /*
+                 * WLH 26 Oct 97 nodeDiff = vnode7 - vnode5; cp = ( ( isovalue -
+                 * vnode5 ) / nodeDiff ) + iy; VX[0][nvet] = ix+1; VY[0][nvet] =
+                 * cp; VZ[0][nvet] = iz+1;
+                 */
+                cp = ((isovalue - vnode5) / (vnode7 - vnode5));
+                VX[0][nvet] = (float) cp
+                    * samples[0][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[0][pt + ydim + xdim_x_ydim];
+                VY[0][nvet] = (float) cp
+                    * samples[1][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[1][pt + ydim + xdim_x_ydim];
+                VZ[0][nvet] = (float) cp
+                    * samples[2][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[2][pt + ydim + xdim_x_ydim];
+
+                for (int j = 0; j < naux; j++) {
+                  t = (int) (cp
+                      * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                          + 1 + ydim + xdim_x_ydim]) + 256.0f
+                          : ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim])) + (1.0f - cp)
+                      * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                          + ydim + xdim_x_ydim]) + 256.0f
+                          : ((float) auxValues[j][pt + ydim + xdim_x_ydim])));
+                  tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                      : ((t < 128) ? t : t - 256)));
+                  /*
+                   * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt + 1 +
+                   * ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt + ydim +
+                   * xdim_x_ydim];
+                   */
+                }
+
+                calc_edge[11] = nvet;
+                P_array[2 * xx + above * yy + iy * xdim + (ix + 1)] = nvet;
+                nvet++;
+              }
+              if (((pol_edges[ptFLAG[ncube]][3] & 0x1000) != 0)) { /*
+                                                                    * cube
+                                                                    * vertex 6-7
+                                                                    */
+                /*
+                 * WLH 26 Oct 97 nodeDiff = vnode7 - vnode6; cp = ( ( isovalue -
+                 * vnode6 ) / nodeDiff ) + ix; VX[0][nvet] = cp; VY[0][nvet] =
+                 * iy+1; VZ[0][nvet] = iz+1;
+                 */
+                cp = ((isovalue - vnode6) / (vnode7 - vnode6));
+                VX[0][nvet] = (float) cp
+                    * samples[0][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[0][pt + 1 + xdim_x_ydim];
+                VY[0][nvet] = (float) cp
+                    * samples[1][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[1][pt + 1 + xdim_x_ydim];
+                VZ[0][nvet] = (float) cp
+                    * samples[2][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                    * samples[2][pt + 1 + xdim_x_ydim];
+
+                for (int j = 0; j < naux; j++) {
+                  t = (int) (cp
+                      * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                          + 1 + ydim + xdim_x_ydim]) + 256.0f
+                          : ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim])) + (1.0f - cp)
+                      * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                          + 1 + xdim_x_ydim]) + 256.0f
+                          : ((float) auxValues[j][pt + 1 + xdim_x_ydim])));
+                  tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                      : ((t < 128) ? t : t - 256)));
+                  /*
+                   * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt + 1 +
+                   * ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt + 1 +
+                   * xdim_x_ydim];
+                   */
+                }
+
+                calc_edge[12] = nvet;
+                P_array[above * xx + ix * ydim + (iy + 1)] = nvet;
+                nvet++;
+              }
+
+              /* end find_vertex(); */
+              /* update_data_structure(ncube); */
+              kk = pol_edges[ptFLAG[ncube]][2];
+              nn = pol_edges[ptFLAG[ncube]][1];
+              for (ii = 0; ii < nn; ii++) {
+                mm = pvp + (kk & MASK);
+                for (jj = pvp; jj < mm; jj++) {
+                  Vert_f_Pol[jj] = ve = calc_edge[Vert_f_Pol[jj]];
+                  // Pol_f_Vert[0][ve*9 + (Pol_f_Vert[0][ve*9 + 8])++] = cpl;
+                  temp = Pol_f_Vert[0][ve * 9 + 8];
+                  Pol_f_Vert[0][ve * 9 + temp] = cpl;
+                  Pol_f_Vert[0][ve * 9 + 8] = temp + 1;
+                }
+                kk >>= 4;
+                pvp += 7;
+                cpl++;
+              }
+              /* end update_data_structure(ncube); */
+            } else { // !(ptFLAG[ncube] < MAX_FLAG_NUM)
+              /* find_vertex_invalid_cube(ncube); */
+
+              ptFLAG[ncube] &= 0x1FF;
+              if ((ptFLAG[ncube] != 0 & ptFLAG[ncube] != 0xFF)) {
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0010) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 1-3
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (!(iz != 0 ) && vnode3 < INV_VAL && vnode1
+                 * < INV_VAL) { if (!(iz != 0 ) && !Float.isNaN(vnode3) &&
+                 * !Float.isNaN(vnode1))
+                 */
+                // test for not missing
+                {
+                  if (!(iz != 0) && vnode3 == vnode3 && vnode1 == vnode1) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode3 - vnode1; cp = ( (
+                     * isovalue - vnode1 ) / nodeDiff ) + iy; VX[0][nvet] =
+                     * ix+1; VY[0][nvet] = cp; VZ[0][nvet] = iz;
+                     */
+                    cp = ((isovalue - vnode1) / (vnode3 - vnode1));
+                    // WLH 4 Aug 2000 - replace Samples by samples
+                    VX[0][nvet] = (float) cp * samples[0][pt + ydim + 1]
+                        + (1.0f - cp) * samples[0][pt + ydim];
+                    VY[0][nvet] = (float) cp * samples[1][pt + ydim + 1]
+                        + (1.0f - cp) * samples[1][pt + ydim];
+                    VZ[0][nvet] = (float) cp * samples[2][pt + ydim + 1]
+                        + (1.0f - cp) * samples[2][pt + ydim];
+                    // end WLH 4 Aug 2000 - replace Samples by samples
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + ydim + 1] < 0) ? ((float) auxValues[j][pt
+                              + ydim + 1]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim + 1])) + (1.0f - cp)
+                          * ((auxValues[j][pt + ydim] < 0) ? ((float) auxValues[j][pt
+                              + ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * ydim + 1] + (1.0f-cp) auxValues[j][pt + ydim];
+                       */
+                    }
+
+                    P_array[2 * xx + bellow * yy + iy * xdim + (ix + 1)] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0020) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 1-5
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (!(iy != 0) && vnode5 < INV_VAL && vnode1
+                 * < INV_VAL) { if (!(iy != 0) && !Float.isNaN(vnode5) &&
+                 * !Float.isNaN(vnode1))
+                 */
+                // test for not missing
+                {
+                  if (!(iy != 0) && vnode5 == vnode5 && vnode1 == vnode1) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode5 - vnode1; cp = ( (
+                     * isovalue - vnode1 ) / nodeDiff ) + iz; VX[0][nvet] =
+                     * ix+1; VY[0][nvet] = iy; VZ[0][nvet] = cp;
+                     */
+                    cp = ((isovalue - vnode1) / (vnode5 - vnode1));
+                    VX[0][nvet] = (float) cp
+                        * samples[0][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[0][pt + ydim];
+                    VY[0][nvet] = (float) cp
+                        * samples[1][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[1][pt + ydim];
+                    VZ[0][nvet] = (float) cp
+                        * samples[2][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[2][pt + ydim];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + ydim + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim + xdim_x_ydim])) + (1.0f - cp)
+                          * ((auxValues[j][pt + ydim] < 0) ? ((float) auxValues[j][pt
+                              + ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt +
+                       * ydim];
+                       */
+                    }
+
+                    P_array[2 * xx + 2 * yy + front * zz + iy] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0040) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 2-3
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (!(iz != 0) && vnode3 < INV_VAL && vnode2
+                 * < INV_VAL) { if (!(iz != 0) && !Float.isNaN(vnode3) &&
+                 * !Float.isNaN(vnode2))
+                 */
+                // test for not missing
+                {
+                  if (!(iz != 0) && vnode3 == vnode3 && vnode2 == vnode2) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode3 - vnode2; cp = ( (
+                     * isovalue - vnode2 ) / nodeDiff ) + ix; VX[0][nvet] = cp;
+                     * VY[0][nvet] = iy+1; VZ[0][nvet] = iz;
+                     */
+                    cp = ((isovalue - vnode2) / (vnode3 - vnode2));
+                    VX[0][nvet] = (float) cp * samples[0][pt + ydim + 1]
+                        + (1.0f - cp) * samples[0][pt + 1];
+                    VY[0][nvet] = (float) cp * samples[1][pt + ydim + 1]
+                        + (1.0f - cp) * samples[1][pt + 1];
+                    VZ[0][nvet] = (float) cp * samples[2][pt + ydim + 1]
+                        + (1.0f - cp) * samples[2][pt + 1];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + ydim + 1] < 0) ? ((float) auxValues[j][pt
+                              + ydim + 1]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim + 1])) + (1.0f - cp)
+                          * ((auxValues[j][pt + 1] < 0) ? ((float) auxValues[j][pt + 1]) + 256.0f
+                              : ((float) auxValues[j][pt + 1])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * ydim + 1] + (1.0f-cp) auxValues[j][pt + 1];
+                       */
+                    }
+
+                    P_array[bellow * xx + ix * ydim + (iy + 1)] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0080) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 2-6
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (!(ix != 0) && vnode6 < INV_VAL && vnode2
+                 * < INV_VAL) { if (!(ix != 0) && !Float.isNaN(vnode6) &&
+                 * !Float.isNaN(vnode2))
+                 */
+                // test for not missing
+                {
+                  if (!(ix != 0) && vnode6 == vnode6 && vnode2 == vnode2) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode6 - vnode2; cp = ( (
+                     * isovalue - vnode2 ) / nodeDiff ) + iz; VX[0][nvet] = ix;
+                     * VY[0][nvet] = iy+1; VZ[0][nvet] = cp;
+                     */
+                    cp = ((isovalue - vnode2) / (vnode6 - vnode2));
+                    VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim]
+                        + (1.0f - cp) * samples[0][pt + 1];
+                    VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim]
+                        + (1.0f - cp) * samples[1][pt + 1];
+                    VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim]
+                        + (1.0f - cp) * samples[2][pt + 1];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + 1 + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + 1 + xdim_x_ydim])) + (1.0f - cp)
+                          * ((auxValues[j][pt + 1] < 0) ? ((float) auxValues[j][pt + 1]) + 256.0f
+                              : ((float) auxValues[j][pt + 1])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * 1 + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt + 1];
+                       */
+                    }
+
+                    P_array[2 * xx + 2 * yy + rear * zz + (iy + 1)] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0100) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 3-7
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (vnode7 < INV_VAL && vnode3 < INV_VAL) {
+                 * if (!Float.isNaN(vnode7) && !Float.isNaN(vnode3))
+                 */
+                // test for not missing
+                {
+                  if (vnode7 == vnode7 && vnode3 == vnode3) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode7 - vnode3; cp = ( (
+                     * isovalue - vnode3 ) / nodeDiff ) + iz; VX[0][nvet] =
+                     * ix+1; VY[0][nvet] = iy+1; VZ[0][nvet] = cp;
+                     */
+                    cp = ((isovalue - vnode3) / (vnode7 - vnode3));
+                    VX[0][nvet] = (float) cp
+                        * samples[0][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[0][pt + ydim + 1];
+                    VY[0][nvet] = (float) cp
+                        * samples[1][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[1][pt + ydim + 1];
+                    VZ[0][nvet] = (float) cp
+                        * samples[2][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[2][pt + ydim + 1];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + 1 + ydim + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + 1 + ydim
+                                  + xdim_x_ydim])) + (1.0f - cp)
+                          * ((auxValues[j][pt + ydim + 1] < 0) ? ((float) auxValues[j][pt
+                              + ydim + 1]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim + 1])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * 1 + ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt +
+                       * ydim + 1];
+                       */
+                    }
+
+                    P_array[2 * xx + 2 * yy + front * zz + (iy + 1)] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0200) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 4-5
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (!(iy != 0) && vnode5 < INV_VAL && vnode4
+                 * < INV_VAL) { if (!(iy != 0) && !Float.isNaN(vnode5) &&
+                 * !Float.isNaN(vnode4))
+                 */
+                // test for not missing
+                {
+                  if (!(iy != 0) && vnode5 == vnode5 && vnode4 == vnode4) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode5 - vnode4; cp = ( (
+                     * isovalue - vnode4 ) / nodeDiff ) + ix; VX[0][nvet] = cp;
+                     * VY[0][nvet] = iy; VZ[0][nvet] = iz+1;
+                     */
+                    cp = ((isovalue - vnode4) / (vnode5 - vnode4));
+                    VX[0][nvet] = (float) cp
+                        * samples[0][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[0][pt + xdim_x_ydim];
+                    VY[0][nvet] = (float) cp
+                        * samples[1][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[1][pt + xdim_x_ydim];
+                    VZ[0][nvet] = (float) cp
+                        * samples[2][pt + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[2][pt + xdim_x_ydim];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + ydim + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim + xdim_x_ydim])) + (1.0f - cp)
+                          * ((auxValues[j][pt + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + xdim_x_ydim])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt +
+                       * xdim_x_ydim];
+                       */
+                    }
+
+                    P_array[above * xx + ix * ydim + iy] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0400) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 4-6
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (!(ix != 0) && vnode6 < INV_VAL && vnode4
+                 * < INV_VAL) { if (!(ix != 0) && !Float.isNaN(vnode6) &&
+                 * !Float.isNaN(vnode4))
+                 */
+                // test for not missing
+                {
+                  if (!(ix != 0) && vnode6 == vnode6 && vnode4 == vnode4) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode6 - vnode4; cp = ( (
+                     * isovalue - vnode4 ) / nodeDiff ) + iy; VX[0][nvet] = ix;
+                     * VY[0][nvet] = cp; VZ[0][nvet] = iz+1;
+                     */
+                    cp = ((isovalue - vnode4) / (vnode6 - vnode4));
+                    VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim]
+                        + (1.0f - cp) * samples[0][pt + xdim_x_ydim];
+                    VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim]
+                        + (1.0f - cp) * samples[1][pt + xdim_x_ydim];
+                    VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim]
+                        + (1.0f - cp) * samples[2][pt + xdim_x_ydim];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + 1 + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + 1 + xdim_x_ydim])) + (1.0f - cp)
+                          * ((auxValues[j][pt + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + xdim_x_ydim])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * 1 + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt +
+                       * xdim_x_ydim];
+                       */
+                    }
+
+                    P_array[2 * xx + above * yy + iy * xdim + ix] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x0800) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 5-7
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (vnode7 < INV_VAL && vnode5 < INV_VAL) {
+                 * if (!Float.isNaN(vnode7) && !Float.isNaN(vnode5))
+                 */
+                // test for not missing
+                {
+                  if (vnode7 == vnode7 && vnode5 == vnode5) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode7 - vnode5; cp = ( (
+                     * isovalue - vnode5 ) / nodeDiff ) + iy; VX[0][nvet] =
+                     * ix+1; VY[0][nvet] = cp; VZ[0][nvet] = iz+1;
+                     */
+                    cp = ((isovalue - vnode5) / (vnode7 - vnode5));
+                    VX[0][nvet] = (float) cp
+                        * samples[0][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[0][pt + ydim + xdim_x_ydim];
+                    VY[0][nvet] = (float) cp
+                        * samples[1][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[1][pt + ydim + xdim_x_ydim];
+                    VZ[0][nvet] = (float) cp
+                        * samples[2][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[2][pt + ydim + xdim_x_ydim];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + 1 + ydim + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + 1 + ydim
+                                  + xdim_x_ydim])) + (1.0f - cp)
+                          * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + ydim + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + ydim + xdim_x_ydim])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * 1 + ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt +
+                       * ydim + xdim_x_ydim];
+                       */
+                    }
+
+                    P_array[2 * xx + above * yy + iy * xdim + (ix + 1)] = nvet;
+                    nvet++;
+                  }
+                }
+                if (((pol_edges[ptFLAG[ncube]][3] & 0x1000) != 0)) /*
+                                                                    * cube
+                                                                    * vertex 6-7
+                                                                    */
+                /*
+                 * WLH 24 Oct 97 { if (vnode7 < INV_VAL && vnode6 < INV_VAL) {
+                 * if (!Float.isNaN(vnode7) && !Float.isNaN(vnode6))
+                 */
+                // test for not missing
+                {
+                  if (vnode7 == vnode7 && vnode6 == vnode6) {
+                    /*
+                     * WLH 26 Oct 97 nodeDiff = vnode7 - vnode6; cp = ( (
+                     * isovalue - vnode6 ) / nodeDiff ) + ix; VX[0][nvet] = cp;
+                     * VY[0][nvet] = iy+1; VZ[0][nvet] = iz+1;
+                     */
+                    cp = ((isovalue - vnode6) / (vnode7 - vnode6));
+                    VX[0][nvet] = (float) cp
+                        * samples[0][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[0][pt + 1 + xdim_x_ydim];
+                    VY[0][nvet] = (float) cp
+                        * samples[1][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[1][pt + 1 + xdim_x_ydim];
+                    VZ[0][nvet] = (float) cp
+                        * samples[2][pt + 1 + ydim + xdim_x_ydim] + (1.0f - cp)
+                        * samples[2][pt + 1 + xdim_x_ydim];
+
+                    for (int j = 0; j < naux; j++) {
+                      t = (int) (cp
+                          * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + 1 + ydim + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + 1 + ydim
+                                  + xdim_x_ydim])) + (1.0f - cp)
+                          * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ? ((float) auxValues[j][pt
+                              + 1 + xdim_x_ydim]) + 256.0f
+                              : ((float) auxValues[j][pt + 1 + xdim_x_ydim])));
+                      tempaux[j][nvet] = (byte) ((t < 0) ? 0 : ((t > 255) ? -1
+                          : ((t < 128) ? t : t - 256)));
+                      /*
+                       * MEM_WLH tempaux[j][nvet] = (float) cp auxValues[j][pt +
+                       * 1 + ydim + xdim_x_ydim] + (1.0f-cp) auxValues[j][pt + 1
+                       * + xdim_x_ydim];
+                       */
+                    }
+
+                    P_array[above * xx + ix * ydim + (iy + 1)] = nvet;
+                    nvet++;
+                  }
+                }
+              }
+              /* end find_vertex_invalid_cube(ncube); */
+
+            }
+          } /* end if (exist_polygon_in_cube(ncube)) */
+          ncube++;
+          pt++;
+        } /* end for ( iy = 0; iy < ydim - 1; iy++ ) */
+        /* swap_planes(Z,rear,front); */
+        caseA = rear;
+        rear = front;
+        front = caseA;
+        pt++;
+        /* end swap_planes(Z,rear,front); */
+      } /* end for ( ix = 0; ix < xdim - 1; ix++ ) */
+      /* swap_planes(XY,bellow,above); */
+      caseA = bellow;
+      bellow = above;
+      above = caseA;
+      pt += ydim;
+      /* end swap_planes(XY,bellow,above); */
+    } /* end for ( iz = 0; iz < zdim - 1; iz++ ) */
+
+    // copy tempaux array into auxLevels array
+    for (int i = 0; i < naux; i++) {
+      auxLevels[i] = new byte[nvet];
+      System.arraycopy(tempaux[i], 0, auxLevels[i], 0, nvet);
+    }
+
+    return nvet;
+  }
+
+  public static void make_normals(float[] VX, float[] VY, float[] VZ,
+      float[] NX, float[] NY, float[] NZ, int nvertex, int npolygons,
+      float[] Pnx, float[] Pny, float[] Pnz, float[] NxA, float[] NxB,
+      float[] NyA, float[] NyB, float[] NzA, float[] NzB, int[] Pol_f_Vert,
+      int[] Vert_f_Pol) throws VisADException {
+
+    int i, k, n;
+    int max_vert_per_pol, swap_flag;
+    float x, y, z, minimum_area, len;
+
+    int iv[] = new int[3];
+
+    for (i = 0; i < nvertex; i++) {
+      NX[i] = 0;
+      NY[i] = 0;
+      NZ[i] = 0;
+    }
+
+    // WLH 12 Nov 2001
+    // minimum_area = (float) ((1.e-4 > EPS_0) ? 1.e-4 : EPS_0);
+    minimum_area = Float.MIN_VALUE;
+
+    /* Calculate maximum number of vertices per polygon */
+    k = 6;
+    n = 7 * npolygons;
+    while (TRUE) {
+      for (i = k + 7; i < n; i += 7)
+        if (Vert_f_Pol[i] > Vert_f_Pol[k])
+          break;
+      if (i >= n)
+        break;
+      k = i;
+    }
+    max_vert_per_pol = Vert_f_Pol[k];
+
+    /* Calculate the Normals vector components for each Polygon */
+    /* $dir vector */
+    for (i = 0; i < npolygons; i++) { /* Vectorized */
+      if (Vert_f_Pol[6 + i * 7] > 0) { /*
+                                        * check for valid polygon added by BEP
+                                        * 2-13-92
+                                        */
+        NxA[i] = VX[Vert_f_Pol[1 + i * 7]] - VX[Vert_f_Pol[0 + i * 7]];
+        NyA[i] = VY[Vert_f_Pol[1 + i * 7]] - VY[Vert_f_Pol[0 + i * 7]];
+        NzA[i] = VZ[Vert_f_Pol[1 + i * 7]] - VZ[Vert_f_Pol[0 + i * 7]];
+      }
+    }
+
+    swap_flag = 0;
+    for (k = 2; k < max_vert_per_pol; k++) {
+
+      if (swap_flag == 0) {
+        /* $dir no_recurrence *//* Vectorized */
+        for (i = 0; i < npolygons; i++) {
+          if (Vert_f_Pol[k + i * 7] >= 0) {
+            NxB[i] = VX[Vert_f_Pol[k + i * 7]] - VX[Vert_f_Pol[0 + i * 7]];
+            NyB[i] = VY[Vert_f_Pol[k + i * 7]] - VY[Vert_f_Pol[0 + i * 7]];
+            NzB[i] = VZ[Vert_f_Pol[k + i * 7]] - VZ[Vert_f_Pol[0 + i * 7]];
+            Pnx[i] = NyA[i] * NzB[i] - NzA[i] * NyB[i];
+            Pny[i] = NzA[i] * NxB[i] - NxA[i] * NzB[i];
+            Pnz[i] = NxA[i] * NyB[i] - NyA[i] * NxB[i];
+            NxA[i] = Pnx[i] * Pnx[i] + Pny[i] * Pny[i] + Pnz[i] * Pnz[i];
+            if (NxA[i] > minimum_area) {
+              Pnx[i] /= NxA[i];
+              Pny[i] /= NxA[i];
+              Pnz[i] /= NxA[i];
+            }
+          }
+        }
+      } else { /* swap_flag!=0 */
+        /* $dir no_recurrence *//* Vectorized */
+        for (i = 0; i < npolygons; i++) {
+          if (Vert_f_Pol[k + i * 7] >= 0) {
+            NxA[i] = VX[Vert_f_Pol[k + i * 7]] - VX[Vert_f_Pol[0 + i * 7]];
+            NyA[i] = VY[Vert_f_Pol[k + i * 7]] - VY[Vert_f_Pol[0 + i * 7]];
+            NzA[i] = VZ[Vert_f_Pol[k + i * 7]] - VZ[Vert_f_Pol[0 + i * 7]];
+            Pnx[i] = NyB[i] * NzA[i] - NzB[i] * NyA[i];
+            Pny[i] = NzB[i] * NxA[i] - NxB[i] * NzA[i];
+            Pnz[i] = NxB[i] * NyA[i] - NyB[i] * NxA[i];
+            NxB[i] = Pnx[i] * Pnx[i] + Pny[i] * Pny[i] + Pnz[i] * Pnz[i];
+            if (NxB[i] > minimum_area) {
+              Pnx[i] /= NxB[i];
+              Pny[i] /= NxB[i];
+              Pnz[i] /= NxB[i];
+            }
+          }
+        }
+      }
+
+      /* This Loop <CAN'T> be Vectorized */
+      for (i = 0; i < npolygons; i++) {
+        if (Vert_f_Pol[k + i * 7] >= 0) {
+          iv[0] = Vert_f_Pol[0 + i * 7];
+          iv[1] = Vert_f_Pol[(k - 1) + i * 7];
+          iv[2] = Vert_f_Pol[k + i * 7];
+          x = Pnx[i];
+          y = Pny[i];
+          z = Pnz[i];
+
+          // Update the origin vertex
+          NX[iv[0]] += x;
+          NY[iv[0]] += y;
+          NZ[iv[0]] += z;
+
+          // Update the vertex that defines the first vector
+          NX[iv[1]] += x;
+          NY[iv[1]] += y;
+          NZ[iv[1]] += z;
+
+          // Update the vertex that defines the second vector
+          NX[iv[2]] += x;
+          NY[iv[2]] += y;
+          NZ[iv[2]] += z;
+        }
+      }
+
+      swap_flag = ((swap_flag != 0) ? 0 : 1);
+    }
+
+    /* Normalize the Normals */
+    for (i = 0; i < nvertex; i++) /* Vectorized */
+    {
+      len = (float) Math.sqrt(NX[i] * NX[i] + NY[i] * NY[i] + NZ[i] * NZ[i]);
+      if (len > EPS_0) {
+        NX[i] /= len;
+        NY[i] /= len;
+        NZ[i] /= len;
+      }
+    }
+
+  }
+
+  public static int poly_triangle_stripe(int[] vet_pol, int[] Tri_Stripe,
+      int nvertex, int npolygons, int[] Pol_f_Vert, int[] Vert_f_Pol)
+      throws VisADException {
+    int i, j, k, m, ii, npol, cpol, idx, off, Nvt, vA, vB, ivA, ivB, iST, last_pol;
+    boolean f_line_conection = false;
+
+    last_pol = 0;
+    npol = 0;
+    iST = 0;
+    ivB = 0;
+
+    for (i = 0; i < npolygons; i++)
+      vet_pol[i] = 1; /* Vectorized */
+
+    while (TRUE) {
+      /* find_unselected_pol(cpol); */
+      for (cpol = last_pol; cpol < npolygons; cpol++) {
+        if ((vet_pol[cpol] != 0))
+          break;
+      }
+      if (cpol == npolygons) {
+        cpol = -1;
+      } else {
+        last_pol = cpol;
+      }
+      /* end find_unselected_pol(cpol); */
+
+      if (cpol < 0)
+        break;
+      /* update_polygon */
+      vet_pol[cpol] = 0;
+      /* end update_polygon */
+
+      /* get_vertices_of_pol(cpol,Vt,Nvt); { */
+      Nvt = Vert_f_Pol[(j = cpol * 7) + 6];
+      off = j;
+      /* } */
+      /* end get_vertices_of_pol(cpol,Vt,Nvt); { */
+
+      for (ivA = 0; ivA < Nvt; ivA++) {
+        ivB = (((ivA + 1) == Nvt) ? 0 : (ivA + 1));
+        /* get_pol_vert(Vt[ivA],Vt[ivB],npol) { */
+        npol = -1;
+        if (Vert_f_Pol[ivA + off] >= 0 && Vert_f_Pol[ivB + off] >= 0) {
+          i = Vert_f_Pol[ivA + off] * 9;
+          k = i + Pol_f_Vert[i + 8];
+          j = Vert_f_Pol[ivB + off] * 9;
+          m = j + Pol_f_Vert[j + 8];
+          while (i > 0 && j > 0 && i < k && j < m) {
+            if (Pol_f_Vert[i] == Pol_f_Vert[j] && (vet_pol[Pol_f_Vert[i]] != 0)) {
+              npol = Pol_f_Vert[i];
+              break;
+            } else if (Pol_f_Vert[i] < Pol_f_Vert[j])
+              i++;
+            else
+              j++;
+          }
+        }
+        /* } */
+        /* end get_pol_vert(Vt[ivA],Vt[ivB],npol) { */
+        if (npol >= 0)
+          break;
+      }
+      /* insert polygon alone */
+      if (npol < 0) { /* ptT = NTAB + STAB[Nvt-3]; */
+        idx = STAB[Nvt - 3];
+        if (iST > 0) {
+          Tri_Stripe[iST] = Tri_Stripe[iST - 1];
+          iST++;
+          Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx] + off];
+        } else
+          f_line_conection = true; /* WLH 3-9-95 added */
+        for (ii = 0; ii < ((Nvt < 6) ? Nvt : 6); ii++) {
+          Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx++] + off];
+        }
+        continue;
+      }
+
+      if (((ivB != 0) && ivA == (ivB - 1)) || (!(ivB != 0) && ivA == Nvt - 1)) {
+        /* ptT = ITAB + STAB[Nvt-3] + (ivB+1)Nvt; */
+        idx = STAB[Nvt - 3] + (ivB + 1) * Nvt;
+
+        if (f_line_conection) {
+          Tri_Stripe[iST] = Tri_Stripe[iST - 1];
+          iST++;
+          Tri_Stripe[iST++] = Vert_f_Pol[ITAB[idx - 1] + off];
+          f_line_conection = false;
+        }
+        for (ii = 0; ii < ((Nvt < 6) ? Nvt : 6); ii++) {
+          Tri_Stripe[iST++] = Vert_f_Pol[ITAB[--idx] + off];
+        }
+
+      } else {
+        /* ptT = NTAB + STAB[Nvt-3] + (ivB+1)Nvt; */
+        idx = STAB[Nvt - 3] + (ivB + 1) * Nvt;
+
+        if (f_line_conection) {
+          Tri_Stripe[iST] = Tri_Stripe[iST - 1];
+          iST++;
+          Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx - 1] + off];
+          f_line_conection = false;
+        }
+        for (ii = 0; ii < ((Nvt < 6) ? Nvt : 6); ii++) {
+          Tri_Stripe[iST++] = Vert_f_Pol[NTAB[--idx] + off];
+        }
+
+      }
+
+      vB = Tri_Stripe[iST - 1];
+      vA = Tri_Stripe[iST - 2];
+      cpol = npol;
+
+      while (TRUE) {
+        /* get_vertices_of_pol(cpol,Vt,Nvt) { */
+        Nvt = Vert_f_Pol[(j = cpol * 7) + 6];
+        off = j;
+        /* } */
+
+        /* update_polygon(cpol) */
+        vet_pol[cpol] = 0;
+        for (ivA = 0; ivA < Nvt && Vert_f_Pol[ivA + off] != vA; ivA++)
+          ;
+        for (ivB = 0; ivB < Nvt && Vert_f_Pol[ivB + off] != vB; ivB++)
+          ;
+        if (((ivB != 0) && ivA == (ivB - 1)) || (!(ivB != 0) && ivA == Nvt - 1)) {
+          /* ptT = NTAB + STAB[Nvt-3] + ivANvt + 2; */
+          idx = STAB[Nvt - 3] + ivA * Nvt + 2;
+
+          for (ii = 2; ii < ((Nvt < 6) ? Nvt : 6); ii++)
+            Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx++] + off];
+        } else {
+          /* ptT = ITAB + STAB[Nvt-3] + ivANvt + 2; */
+          idx = STAB[Nvt - 3] + ivA * Nvt + 2;
+
+          for (ii = 2; ii < ((Nvt < 6) ? Nvt : 6); ii++)
+            Tri_Stripe[iST++] = Vert_f_Pol[ITAB[idx++] + off];
+        }
+
+        vB = Tri_Stripe[iST - 1];
+        vA = Tri_Stripe[iST - 2];
+
+        /* get_pol_vert(vA,vB,cpol) { */
+        cpol = -1;
+        if (vA >= 0 && vB >= 0) {
+          i = vA * 9;
+          k = i + Pol_f_Vert[i + 8];
+          j = vB * 9;
+          m = j + Pol_f_Vert[j + 8];
+          while (i > 0 && j > 0 && i < k && j < m) {
+            if (Pol_f_Vert[i] == Pol_f_Vert[j] && (vet_pol[Pol_f_Vert[i]] != 0)) {
+              cpol = Pol_f_Vert[i];
+              break;
+            } else if (Pol_f_Vert[i] < Pol_f_Vert[j])
+              i++;
+            else
+              j++;
+          }
+        }
+        /* } */
+
+        if (cpol < 0) {
+
+          vA = Tri_Stripe[iST - 3];
+          /* get_pol_vert(vA,vB,cpol) { */
+          cpol = -1;
+          if (vA >= 0 && vB >= 0) {
+            i = vA * 9;
+            k = i + Pol_f_Vert[i + 8];
+            j = vB * 9;
+            m = j + Pol_f_Vert[j + 8];
+            while (i > 0 && j > 0 && i < k && j < m) {
+              if (Pol_f_Vert[i] == Pol_f_Vert[j]
+                  && (vet_pol[Pol_f_Vert[i]] != 0)) {
+                cpol = Pol_f_Vert[i];
+                break;
+              } else if (Pol_f_Vert[i] < Pol_f_Vert[j])
+                i++;
+              else
+                j++;
+            }
+          }
+
+          /* } */
+          if (cpol < 0) {
+            f_line_conection = true;
+            break;
+          } else {
+            // WLH 5 May 2004 - fix bug vintage 1990 or 91
+            if (iST > 0) {
+              Tri_Stripe[iST] = Tri_Stripe[iST - 1];
+              iST++;
+            }
+            Tri_Stripe[iST++] = vA;
+            i = vA;
+            vA = vB;
+            vB = i;
+          }
+        }
+      }
+    }
+
+    return iST;
+  }
+
+  public static float[] makeNormals(float[] coordinates, int LengthX,
+      int LengthY) {
+    int Length = LengthX * LengthY;
+
+    float[] normals = new float[3 * Length];
+    int k = 0;
+    int ki, kj;
+    int LengthX3 = 3 * LengthX;
+    for (int i = 0; i < LengthY; i++) {
+      for (int j = 0; j < LengthX; j++) {
+        float c0 = coordinates[k];
+        float c1 = coordinates[k + 1];
+        float c2 = coordinates[k + 2];
+        float n0 = 0.0f;
+        float n1 = 0.0f;
+        float n2 = 0.0f;
+        float n, m, m0, m1, m2, q0, q1, q2;
+        for (int ip = -1; ip <= 1; ip += 2) {
+          for (int jp = -1; jp <= 1; jp += 2) {
+            int ii = i + ip;
+            int jj = j + jp;
+            if (0 <= ii && ii < LengthY && 0 <= jj && jj < LengthX) {
+              ki = k + ip * LengthX3;
+              kj = k + jp * 3;
+              m0 = (coordinates[kj + 2] - c2) * (coordinates[ki + 1] - c1)
+                  - (coordinates[kj + 1] - c1) * (coordinates[ki + 2] - c2);
+              m1 = (coordinates[kj] - c0) * (coordinates[ki + 2] - c2)
+                  - (coordinates[kj + 2] - c2) * (coordinates[ki] - c0);
+              m2 = (coordinates[kj + 1] - c1) * (coordinates[ki] - c0)
+                  - (coordinates[kj] - c0) * (coordinates[ki + 1] - c1);
+              m = (float) Math.sqrt(m0 * m0 + m1 * m1 + m2 * m2);
+              if (ip == jp) {
+                q0 = m0 / m;
+                q1 = m1 / m;
+                q2 = m2 / m;
+              } else {
+                q0 = -m0 / m;
+                q1 = -m1 / m;
+                q2 = -m2 / m;
+              }
+              if (q0 == q0) {
+                n0 += q0;
+                n1 += q1;
+                n2 += q2;
+              } else {
+                /*
+                 * System.out.println("m = " + m + " " + m0 + " " + m1 + " " +
+                 * m2 + " " + n0 + " " + n1 + " " + n2 + " ip, jp = " + ip + " "
+                 * + jp); System.out.println("k = " + k + " " + ki + " " + kj);
+                 * System.out.println("c = " + c0 + " " + c1 + " " + c2);
+                 * System.out.println("coordinates[ki] = " + coordinates[ki] +
+                 * " " + coordinates[ki+1] + " " + coordinates[ki+2]); // == c
+                 * ?? System.out.println("coordinates[kj] = " + coordinates[kj]
+                 * + " " + coordinates[kj+1] + " " + coordinates[kj+2]);
+                 * System.out.println("LengthX = " + LengthX + " " + LengthY +
+                 * " " + LengthX3);
+                 */
+              }
+            }
+          }
+        }
+        n = (float) Math.sqrt(n0 * n0 + n1 * n1 + n2 * n2);
+        normals[k] = n0 / n;
+        normals[k + 1] = n1 / n;
+        normals[k + 2] = n2 / n;
+        if (normals[k] != normals[k]) {
+          normals[k] = 0.0f;
+          normals[k + 1] = 0.0f;
+          normals[k + 2] = -1.0f;
+        }
+        /*
+         * System.out.println("makeNormals " + k + " " + normals[k] + " " +
+         * normals[k+1] + " " + normals[k+2]);
+         */
+        k += 3;
+      } // end for (int j=0; j<LengthX; j++)
+    } // end for (int i=0; i<LengthY; i++)
+    return normals;
+  }
+
+  /** create a 2-D GeometryArray from this Set and color_values */
+  public VisADGeometryArray make2DGeometry(byte[][] color_values,
+      boolean indexed) throws VisADException {
+    if (DomainDimension != 3) {
+      throw new SetException("Gridded3DSet.make2DGeometry: "
+          + "DomainDimension must be 3");
+    }
+    if (ManifoldDimension != 2) {
+      throw new SetException("Gridded3DSet.make2DGeometry: "
+          + "ManifoldDimension must be 2");
+    }
+    if (LengthX < 2 || LengthY < 2) {
+      /*
+       * WLH 26 June 98 throw new SetException("Gridded3DSet.make2DGeometry: " +
+       * "LengthX and LengthY must be at least 2");
+       */
+      VisADPointArray array = new VisADPointArray();
+      setGeometryArray(array, 4, color_values);
+      return array;
+    }
+
+    if (indexed) {
+      VisADIndexedTriangleStripArray array = new VisADIndexedTriangleStripArray();
+
+      // set up indices into 2-D grid
+      array.indexCount = (LengthY - 1) * (2 * LengthX);
+      int[] indices = new int[array.indexCount];
+      array.stripVertexCounts = new int[LengthY - 1];
+      int k = 0;
+      for (int i = 0; i < LengthY - 1; i++) {
+        int m = i * LengthX;
+        array.stripVertexCounts[i] = 2 * LengthX;
+        for (int j = 0; j < LengthX; j++) {
+          indices[k++] = m;
+          indices[k++] = m + LengthX;
+          m++;
+        }
+      }
+      array.indices = indices;
+      // take the garbage out
+      indices = null;
+
+      // set coordinates and colors
+      setGeometryArray(array, 4, color_values);
+
+      // calculate normals
+      float[] coordinates = array.coordinates;
+      float[] normals = makeNormals(coordinates, LengthX, LengthY);
+      array.normals = normals;
+      return array;
+    } else { // if (!indexed)
+      VisADTriangleStripArray array = new VisADTriangleStripArray();
+      float[][] samples = getSamples(false);
+
+      array.stripVertexCounts = new int[LengthY - 1];
+      for (int i = 0; i < LengthY - 1; i++) {
+        array.stripVertexCounts[i] = 2 * LengthX;
+      }
+      int len = (LengthY - 1) * (2 * LengthX);
+      array.vertexCount = len;
+
+      // calculate normals
+      float[] normals = new float[3 * Length];
+      int k = 0;
+      int k3 = 0;
+      int ki, kj;
+      for (int i = 0; i < LengthY; i++) {
+        for (int j = 0; j < LengthX; j++) {
+          float c0 = samples[0][k3];
+          float c1 = samples[1][k3];
+          float c2 = samples[2][k3];
+          float n0 = 0.0f;
+          float n1 = 0.0f;
+          float n2 = 0.0f;
+          float n, m, m0, m1, m2;
+          boolean any = false;
+          for (int ip = -1; ip <= 1; ip += 2) {
+            for (int jp = -1; jp <= 1; jp += 2) {
+              int ii = i + ip;
+              int jj = j + jp;
+              ki = k3 + ip * LengthX;
+              kj = k3 + jp;
+              if (0 <= ii && ii < LengthY && 0 <= jj && jj < LengthX
+                  && samples[0][ki] == samples[0][ki]
+                  && samples[1][ki] == samples[1][ki]
+                  && samples[2][ki] == samples[2][ki]
+                  && samples[0][kj] == samples[0][kj]
+                  && samples[1][kj] == samples[1][kj]
+                  && samples[2][kj] == samples[2][kj]) {
+                any = true;
+                m0 = (samples[2][kj] - c2) * (samples[1][ki] - c1)
+                    - (samples[1][kj] - c1) * (samples[2][ki] - c2);
+                m1 = (samples[0][kj] - c0) * (samples[2][ki] - c2)
+                    - (samples[2][kj] - c2) * (samples[0][ki] - c0);
+                m2 = (samples[1][kj] - c1) * (samples[0][ki] - c0)
+                    - (samples[0][kj] - c0) * (samples[1][ki] - c1);
+                m = (float) Math.sqrt(m0 * m0 + m1 * m1 + m2 * m2);
+                if (ip == jp) {
+                  n0 += m0 / m;
+                  n1 += m1 / m;
+                  n2 += m2 / m;
+                } else {
+                  n0 -= m0 / m;
+                  n1 -= m1 / m;
+                  n2 -= m2 / m;
+                }
+              }
+            }
+          }
+          n = (float) Math.sqrt(n0 * n0 + n1 * n1 + n2 * n2);
+          if (any) {
+            normals[k] = n0 / n;
+            normals[k + 1] = n1 / n;
+            normals[k + 2] = n2 / n;
+          } else {
+            normals[k] = 0.0f;
+            normals[k + 1] = 0.0f;
+            normals[k + 2] = 1.0f;
+          }
+          k += 3;
+          k3++;
+        } // end for (int j=0; j<LengthX; j++)
+      } // end for (int i=0; i<LengthY; i++)
+
+      array.normals = new float[3 * len];
+
+      // shuffle normals into array.normals
+      k = 0;
+      int LengthX3 = 3 * LengthX;
+      for (int i = 0; i < LengthY - 1; i++) {
+        int m = i * LengthX3;
+        for (int j = 0; j < LengthX; j++) {
+          array.normals[k] = normals[m];
+          array.normals[k + 1] = normals[m + 1];
+          array.normals[k + 2] = normals[m + 2];
+          array.normals[k + 3] = normals[m + LengthX3];
+          array.normals[k + 4] = normals[m + LengthX3 + 1];
+          array.normals[k + 5] = normals[m + LengthX3 + 2];
+          k += 6;
+          m += 3;
+        }
+      }
+      normals = null;
+      /*
+       * int nmiss = 0; int nsmall = 0;
+       */
+      array.coordinates = new float[3 * len];
+      // shuffle samples into array.coordinates
+      k = 0;
+      for (int i = 0; i < LengthY - 1; i++) {
+        int m = i * LengthX;
+        for (int j = 0; j < LengthX; j++) {
+          array.coordinates[k] = samples[0][m];
+          array.coordinates[k + 1] = samples[1][m];
+          array.coordinates[k + 2] = samples[2][m];
+          array.coordinates[k + 3] = samples[0][m + LengthX];
+          array.coordinates[k + 4] = samples[1][m + LengthX];
+          array.coordinates[k + 5] = samples[2][m + LengthX];
+          /*
+           * if (samples[0][m] != samples[0][m] || samples[1][m] !=
+           * samples[1][m] || samples[2][m] != samples[2][m]) nmiss++; double
+           * size = Math.sqrt(samples[0][m] samples[0][m] + samples[1][m]
+           * samples[1][m] + samples[2][m] samples[2][m]); if (size < 0.2)
+           * nsmall++;
+           */
+          k += 6;
+          m++;
+        }
+      }
+
+      // System.out.println("make2DGeometry nmiss = " + nmiss + " nsmall = " +
+      // nsmall);
+
+      if (color_values != null) {
+        int color_length = color_values.length;
+        array.colors = new byte[color_length * len];
+        // shuffle samples into array.coordinates
+        k = 0;
+        if (color_length == 4) {
+          for (int i = 0; i < LengthY - 1; i++) {
+            int m = i * LengthX;
+            for (int j = 0; j < LengthX; j++) {
+              array.colors[k] = color_values[0][m];
+              array.colors[k + 1] = color_values[1][m];
+              array.colors[k + 2] = color_values[2][m];
+              array.colors[k + 3] = color_values[3][m];
+              k += color_length;
+              array.colors[k] = color_values[0][m + LengthX];
+              array.colors[k + 1] = color_values[1][m + LengthX];
+              array.colors[k + 2] = color_values[2][m + LengthX];
+              array.colors[k + 3] = color_values[3][m + LengthX];
+              k += color_length;
+              m++;
+            }
+          }
+        } else { // if (color_length == 3)
+          for (int i = 0; i < LengthY - 1; i++) {
+            int m = i * LengthX;
+            for (int j = 0; j < LengthX; j++) {
+              array.colors[k] = color_values[0][m];
+              array.colors[k + 1] = color_values[1][m];
+              array.colors[k + 2] = color_values[2][m];
+              k += color_length;
+              array.colors[k] = color_values[0][m + LengthX];
+              array.colors[k + 1] = color_values[1][m + LengthX];
+              array.colors[k + 2] = color_values[2][m + LengthX];
+              k += color_length;
+              m++;
+            }
+          }
+        }
+      }
+      return array;
+    } // end if (!indexed)
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    float[][]mySamples = getMySamples();
+    if (ManifoldDimension == 3) {
+      return new Gridded3DSet(type, mySamples, LengthX, LengthY, LengthZ,
+          DomainCoordinateSystem, SetUnits, SetErrors);
+    } else if (ManifoldDimension == 2) {
+      return new Gridded3DSet(type, mySamples, LengthX, LengthY,
+          DomainCoordinateSystem, SetUnits, SetErrors);
+    } else {
+      return new Gridded3DSet(type, mySamples, LengthX, DomainCoordinateSystem,
+          SetUnits, SetErrors);
+    }
+  }
+
+  /*
+   * run 'java visad.Gridded3DSet < formatted_input_stream' to test the
+   * Gridded3DSet class
+   */
+  public static void main(String[] argv) throws VisADException {
+
+    // Define input stream
+    InputStreamReader inStr = new InputStreamReader(System.in);
+
+    // Define temporary integer array
+    int[] ints = new int[80];
+    try {
+      ints[0] = inStr.read();
+    } catch (Exception e) {
+      throw new SetException("Gridded3DSet: " + e);
+    }
+    int l = 0;
+    while (ints[l] != 10) {
+      try {
+        ints[++l] = inStr.read();
+      } catch (Exception e) {
+        throw new SetException("Gridded3DSet: " + e);
+      }
+    }
+    // convert array of integers to array of characters
+    char[] chars = new char[l];
+    for (int i = 0; i < l; i++) {
+      chars[i] = (char) ints[i];
+    }
+    int num_coords = Integer.parseInt(new String(chars));
+
+    // num_coords should be a nice round number
+    if (num_coords % 9 != 0) {
+      throw new SetException("Gridded3DSet: input coordinates"
+          + " must be divisible by 9 for main function testing routines.");
+    }
+
+    // Define size of Samples array
+    float[][] samp = new float[3][num_coords];
+    System.out.println("num_dimensions = 3, num_coords = " + num_coords + "\n");
+
+    // Skip blank line
+    try {
+      ints[0] = inStr.read();
+    } catch (Exception e) {
+      throw new SetException("Gridded3DSet: " + e);
+    }
+
+    for (int c = 0; c < num_coords; c++) {
+      for (int d = 0; d < 3; d++) {
+        l = 0;
+        try {
+          ints[0] = inStr.read();
+        } catch (Exception e) {
+          throw new SetException("Gridded3DSet: " + e);
+        }
+        while ((ints[l] != 32) && (ints[l] != 10)) {
+          try {
+            ints[++l] = inStr.read();
+          } catch (Exception e) {
+            throw new SetException("Gridded3DSet: " + e);
+          }
+        }
+        chars = new char[l];
+        for (int i = 0; i < l; i++) {
+          chars[i] = (char) ints[i];
+        }
+        samp[d][c] = (Float.valueOf(new String(chars))).floatValue();
+      }
+    }
+
+    // do EOF stuff
+    try {
+      inStr.close();
+    } catch (Exception e) {
+      throw new SetException("Gridded3DSet: " + e);
+    }
+
+    // Set up instance of Gridded3DSet
+    RealType vis_xcoord = RealType.getRealType("xcoord");
+    RealType vis_ycoord = RealType.getRealType("ycoord");
+    RealType vis_zcoord = RealType.getRealType("zcoord");
+    RealType[] vis_array = { vis_xcoord, vis_ycoord, vis_zcoord };
+    RealTupleType vis_tuple = new RealTupleType(vis_array);
+    Gridded3DSet gSet3D = new Gridded3DSet(vis_tuple, samp, 3, 3,
+        num_coords / 9);
+
+    System.out.println("Lengths = 3 3 " + num_coords / 9 + " wedge = ");
+    int[] wedge = gSet3D.getWedge();
+    for (int i = 0; i < wedge.length; i++)
+      System.out.println(" " + wedge[i]);
+
+    float[][]thatMySamples = gSet3D.getMySamples();
+
+    // print out Samples information
+    System.out.println("Samples (" + gSet3D.LengthX + " x " + gSet3D.LengthY
+        + " x " + gSet3D.LengthZ + "):");
+    for (int i = 0; i < gSet3D.LengthX * gSet3D.LengthY * gSet3D.LengthZ; i++) {
+      System.out.println("#" + i + ":\t" + thatMySamples[0][i] + ", "
+          + thatMySamples[1][i] + ", " + thatMySamples[2][i]);
+    }
+
+    // Test gridToValue function
+    System.out.println("\ngridToValue test:");
+    int myLengthX = gSet3D.LengthX + 1;
+    int myLengthY = gSet3D.LengthY + 1;
+    int myLengthZ = gSet3D.LengthZ + 1;
+    float[][] myGrid = new float[3][myLengthX * myLengthY * myLengthZ];
+    for (int k = 0; k < myLengthZ; k++) {
+      for (int j = 0; j < myLengthY; j++) {
+        for (int i = 0; i < myLengthX; i++) {
+          int index = k * myLengthY * myLengthX + j * myLengthX + i;
+          myGrid[0][index] = i - 0.5f;
+          myGrid[1][index] = j - 0.5f;
+          myGrid[2][index] = k - 0.5f;
+          if (myGrid[0][index] < 0)
+            myGrid[0][index] += 0.1;
+          if (myGrid[1][index] < 0)
+            myGrid[1][index] += 0.1;
+          if (myGrid[2][index] < 0)
+            myGrid[2][index] += 0.1;
+          if (myGrid[0][index] > gSet3D.LengthX - 1)
+            myGrid[0][index] -= 0.1;
+          if (myGrid[1][index] > gSet3D.LengthY - 1)
+            myGrid[1][index] -= 0.1;
+          if (myGrid[2][index] > gSet3D.LengthZ - 1)
+            myGrid[2][index] -= 0.1;
+        }
+      }
+    }
+    float[][] myValue = gSet3D.gridToValue(myGrid);
+    for (int i = 0; i < myLengthX * myLengthY * myLengthZ; i++) {
+      System.out.println("("
+          + ((float) Math.round(1000000 * myGrid[0][i]) / 1000000) + ", "
+          + ((float) Math.round(1000000 * myGrid[1][i]) / 1000000) + ", "
+          + ((float) Math.round(1000000 * myGrid[2][i]) / 1000000)
+          + ")  \t-->  "
+          + ((float) Math.round(1000000 * myValue[0][i]) / 1000000) + ", "
+          + ((float) Math.round(1000000 * myValue[1][i]) / 1000000) + ", "
+          + ((float) Math.round(1000000 * myValue[2][i]) / 1000000));
+    }
+
+    // Test valueToGrid function
+    System.out.println("\nvalueToGrid test:");
+    float[][] gridTwo = gSet3D.valueToGrid(myValue);
+    for (int i = 0; i < gridTwo[0].length; i++) {
+      System.out.print(((float) Math.round(1000000 * myValue[0][i]) / 1000000)
+          + ", " + ((float) Math.round(1000000 * myValue[1][i]) / 1000000)
+          + ", " + ((float) Math.round(1000000 * myValue[2][i]) / 1000000)
+          + "\t-->  (");
+      if (Float.isNaN(gridTwo[0][i])) {
+        System.out.print("NaN, ");
+      } else {
+        System.out
+            .print(((float) Math.round(1000000 * gridTwo[0][i]) / 1000000)
+                + ", ");
+      }
+      if (Float.isNaN(gridTwo[1][i])) {
+        System.out.print("NaN, ");
+      } else {
+        System.out
+            .print(((float) Math.round(1000000 * gridTwo[1][i]) / 1000000)
+                + ", ");
+      }
+      if (Float.isNaN(gridTwo[2][i])) {
+        System.out.println("NaN)");
+      } else {
+        System.out
+            .println(((float) Math.round(1000000 * gridTwo[2][i]) / 1000000)
+                + ")");
+      }
+    }
+    System.out.println();
+  }
+
+  /*
+   * Here's the output with sample file Gridded3D.txt:
+   * 
+   * iris 28% java visad.Gridded3DSet < Gridded3D.txt num_dimensions = 3,
+   * num_coords = 27
+   * 
+   * Lengths = 3 3 3 wedge = 0 1 2 5 4 3 . . .
+   * 
+   * Samples (3 x 3 x 3): #0: 18.629837, 8.529864, 10.997844 #1: 42.923097,
+   * 10.123978, 11.198275 . . . #25: 32.343298, 39.600872, 36.238975 #26:
+   * 49.919754, 40.119875, 36.018752
+   * 
+   * gridToValue test: (-0.4, -0.4, -0.4) --> 10.819755, -0.172592, 5.179111
+   * (0.5, -0.4, -0.4) --> 32.683689, 1.262111, 5.359499 . . . (1.5, 2.4, 2.4)
+   * --> 43.844996, 48.904708, 40.008508 (2.4, 2.4, 2.4) --> 59.663807,
+   * 49.37181, 39.810308
+   * 
+   * valueToGrid test: 10.819755, -0.172592, 5.179111 --> (-0.4, -0.4, -0.4)
+   * 32.683689, 1.262111, 5.359499 --> (0.5, -0.4, -0.4) . . . 43.844996,
+   * 48.904708, 40.008508 --> (1.5, 2.4, 2.4) 59.663807, 49.37181, 39.810308 -->
+   * (2.4, 2.4, 2.4)
+   * 
+   * iris 29%
+   */
+
+}
diff --git a/visad/GriddedDoubleSet.java b/visad/GriddedDoubleSet.java
new file mode 100644
index 0000000..1b6df8a
--- /dev/null
+++ b/visad/GriddedDoubleSet.java
@@ -0,0 +1,64 @@
+//
+// GriddedDoubleSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   GriddedDoubleSet is an interface for GriddedSets that have
+   double-precision samples rather than single-precision samples.<P>
+*/
+public interface GriddedDoubleSet extends GriddedSetIface 
+{
+
+  /*
+  In addition to implementing all the methods given here, an implementation
+  of GriddedDoubleSet should also override the following methods:
+
+  void init_samples(double[][] samples, boolean copy) throws VisADException;
+  void cram_missing(boolean[] range_select);
+  boolean isMissing();
+  public boolean equals(Object set);
+  public Object clone();
+  public Object cloneButType(MathType type) throws VisADException;
+
+  See Gridded1DDoubleSet.java for an example.
+  */
+
+  double[][] getDoubles(boolean copy) throws VisADException;
+
+  double[][] indexToDouble(int[] index) throws VisADException;
+
+  int[] doubleToIndex(double[][] value) throws VisADException;
+
+  double[][] gridToDouble(double[][] grid) throws VisADException;
+
+  double[][] doubleToGrid(double[][] value) throws VisADException;
+
+  void doubleToInterp(double[][] value, int[][] indices, double[][] weights)
+    throws VisADException;
+
+}
+
diff --git a/visad/GriddedSet.java b/visad/GriddedSet.java
new file mode 100644
index 0000000..04dea14
--- /dev/null
+++ b/visad/GriddedSet.java
@@ -0,0 +1,1043 @@
+//
+// GriddedSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   GriddedSet is implemented by those Set sub-classes whose samples
+   lie on a rectangular grid topology (but note the geometry need
+   not be rectangular).  It is a M-dimensional array of points in
+   R^N where ManifoldDimension = M <= N = DomainDimension.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For more detail, see the example in Linear2DSet.java.<P>
+
+   Grid coordinates are zero-based and in the range -0.5 to length-0.5
+   i.e., there are "length" intervals of length 1.0,
+   the first centered on 0.0 and the last centered on length-1.0
+   points outside this range are indicated by the grid coordinate
+   Double.NaN.<P>
+*/
+public class GriddedSet extends SampledSet implements GriddedSetIface {
+
+  int[] Lengths;
+  // error tolerance for Newton's method solvers
+  // not static because may become data dependent
+  float EPS = (float) 1.0E-15;
+  // Pos records the grid's orientation
+  // (i.e., the sign of the cross-products of the grid edges)
+  boolean Pos;
+
+  /** construct a GriddedSet with samples */
+  public GriddedSet(MathType type, float[][] samples, int[] lengths)
+         throws VisADException {
+    this(type, samples, lengths, null, null, null, true);
+  }
+
+  /** construct a GriddedSet with samples and non-default CoordinateSystem */
+  public GriddedSet(MathType type, float[][] samples, int[] lengths,
+                    CoordinateSystem coord_sys, Unit[] units,
+                    ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, lengths, coord_sys, units, errors, true);
+  }
+
+  public GriddedSet(MathType type, float[][] samples, int[] lengths,
+             CoordinateSystem coord_sys, Unit[] units,
+             ErrorEstimate[] errors, boolean copy)
+             throws VisADException {
+    super(type, lengths.length, coord_sys, units, errors);
+    init_lengths(lengths);
+    if (samples == null ) {
+	setMySamples(null);
+    }
+    else {
+      init_samples(samples, copy);
+    }
+  }
+
+  private void init_lengths(int[] lengths) throws VisADException {
+    Lengths = new int[ManifoldDimension];
+    Length = 1;
+    for (int j=0; j<ManifoldDimension; j++) {
+      if (lengths[j] < 1) {
+        throw new SetException("GriddedSet: each grid length must be" +
+                               " at least 1 (length#" + j + " is " +
+                               lengths[j]);
+      }
+      Lengths[j] = lengths[j];
+      Length = Length * lengths[j];
+    }
+  }
+
+  /**
+   * Abreviated Factory method for creating the proper gridded set
+   * (Gridded1DSet, Gridded2DSet, etc.).
+   *
+   * @param type                 MathType for the returned set
+   * @param samples              Set samples
+   * @param lengths              The dimensionality of the manifold.  <code>
+   *                             lengths[i}</code> contains the number of points
+   *                             in the manifold for dimension <code>i</code>.
+   * @throws VisADException      problem creating the set
+   */
+  public static GriddedSet create(MathType type, float[][] samples,
+                                  int[] lengths) throws VisADException {
+    return create(type, samples, lengths, null, null, null);
+  }
+
+  /**
+   * General Factory method for creating the proper gridded set
+   * (Gridded1DSet, Gridded2DSet, etc.).
+   *
+   * @param type                 MathType for the returned set
+   * @param samples              Set samples
+   * @param lengths              The dimensionality of the manifold.  <code>
+   *                             lengths[i}</code> contains the number of points
+   *                             in the manifold for dimension <code>i</code>.
+   * @param coord_sys            CoordinateSystem for the GriddedSet
+   * @param units                Unit-s of the values in <code>samples</code>
+   * @param errors               ErrorEstimate-s for the values
+   * @throws VisADException      problem creating the set
+   */
+  public static GriddedSet create(MathType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem coord_sys,
+                                  Unit[] units, ErrorEstimate[] errors)
+         throws VisADException {
+    return create(type, samples, lengths, coord_sys, units, errors,
+                  true, true);
+  }
+
+  /**
+   * General Factory method for creating the proper gridded set
+   * (Gridded1DSet, Gridded2DSet, etc.).
+   *
+   * @param type                 MathType for the returned set
+   * @param samples              Set samples
+   * @param lengths              The dimensionality of the manifold.  <code>
+   *                             lengths[i}</code> contains the number of points
+   *                             in the manifold for dimension <code>i</code>.
+   * @param coord_sys            CoordinateSystem for the GriddedSet
+   * @param units                Unit-s of the values in <code>samples</code>
+   * @param errors               ErrorEstimate-s for the values
+   * @param copy                 make a copy of the samples
+   * @throws VisADException      problem creating the set
+   */
+  public static GriddedSet create(MathType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem coord_sys,
+                                  Unit[] units, ErrorEstimate[] errors, 
+                                  boolean copy)
+         throws VisADException {
+    return create(type, samples, lengths, coord_sys, units, errors,
+                  copy, true);
+  }
+
+  /**
+   * General Factory method for creating the proper gridded set
+   * (Gridded1DSet, Gridded2DSet, etc.).
+   *
+   * @param type                 MathType for the returned set
+   * @param samples              Set samples
+   * @param lengths              The dimensionality of the manifold.  <code>
+   *                             lengths[i}</code> contains the number of points
+   *                             in the manifold for dimension <code>i</code>.
+   * @param coord_sys            CoordinateSystem for the GriddedSet
+   * @param units                Unit-s of the values in <code>samples</code>
+   * @param errors               ErrorEstimate-s for the values
+   * @param copy                 make a copy of the samples
+   * @param test                 test to make sure samples are valid.  Used
+   *                             for creating Gridded*DSets where the 
+   *                             manifold dimension is equal to the domain
+   *                             dimension
+   * @throws VisADException      problem creating the set
+   */
+    public static GriddedSet create(MathType type, float[][] samples,
+                                   int[] lengths, CoordinateSystem coord_sys,
+                           Unit[] units, ErrorEstimate[] errors,
+                           boolean copy, boolean test)
+          throws VisADException {
+    int domain_dimension = samples.length;
+    int manifold_dimension = lengths.length;
+    if (manifold_dimension > domain_dimension) {
+      throw new SetException("GriddedSet.create: manifold_dimension " +
+                             manifold_dimension + " is greater than" +
+                             " domain_dimension " + domain_dimension);
+    }
+
+    switch (domain_dimension) {
+      case 1:
+        return new Gridded1DSet(type, samples,
+                                lengths[0],
+                                coord_sys, units, errors, copy);
+      case 2:
+        if (manifold_dimension == 1) {
+          return new Gridded2DSet(type, samples,
+                                  lengths[0],
+                                  coord_sys, units, errors, copy);
+        }
+        else {
+          return new Gridded2DSet(type, samples,
+                                  lengths[0], lengths[1],
+                                  coord_sys, units, errors, copy, test);
+        }
+      case 3:
+        if (manifold_dimension == 1) {
+          return new Gridded3DSet(type, samples,
+                                  lengths[0],
+                                  coord_sys, units, errors, copy);
+        }
+        else if (manifold_dimension == 2) {
+          return new Gridded3DSet(type, samples,
+                                  lengths[0], lengths[1],
+                                  coord_sys, units, errors, copy);
+        }
+        else {
+          return new Gridded3DSet(type, samples,
+                                  lengths[0], lengths[1], lengths[2],
+                                  coord_sys, units, errors, copy, test);
+        }
+      default:
+        return new GriddedSet(type, samples,
+                              lengths,
+                              coord_sys, units, errors, copy);
+    }
+  }
+
+  public Set makeSpatial(SetType type, float[][] samples) throws VisADException {
+    return create(type, samples, Lengths, null, null, null, false, false);
+  }
+
+  public int getLength(int i) {
+    return Lengths[i];
+  }
+
+  public int[] getLengths() {
+    int[] lens = new int[Lengths.length];
+    for (int i=0; i<Lengths.length; i++) {
+      lens[i] = Lengths[i];
+    }
+    return lens;
+  }
+
+  /** returns a zig-zagging ennumeration of index values with
+      good coherence */
+  public int[] getWedge() {
+    int[] wedge = new int[Length];
+    int len = Lengths[0];
+    int i, j, k, base, dim;
+    boolean flip;
+    for (i=0; i<len; i++) wedge[i] = i;
+    for (dim=1; dim<ManifoldDimension; dim++) {
+      flip = true;
+      base = len;
+      k = len;
+      for (j=1; j<Lengths[dim]; j++) {
+        if (flip) {
+          for (i=len-1; i>=0; i--) {
+            wedge[k] = wedge[i] + base;
+            k++;
+          }
+        }
+        else {
+          for (i=0; i<len; i++) {
+            wedge[k] = wedge[i] + base;
+            k++;
+          }
+        }
+        base += len;
+        flip = !flip;
+      }
+      len *= Lengths[dim];
+    }
+    return wedge;
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int i, j, k;
+    int[] indexI = new int[ManifoldDimension];
+    int length = index.length;
+    float[][] grid = new float[ManifoldDimension][length];
+    for (i=0; i<length; i++) {
+      if (0 <= index[i] && index[i] < Length) {
+        k = index[i];
+        for (j=0; j<ManifoldDimension-1; j++) {
+          indexI[j] = k % Lengths[j];
+          k = k / Lengths[j];
+        }
+        indexI[ManifoldDimension-1] = k;
+      }
+      else {
+        for (j=0; j<ManifoldDimension; j++) indexI[j] = -1;
+      }
+      for (j=0; j<ManifoldDimension; j++) grid[j][i] = (float) indexI[j];
+    }
+    return gridToValue(grid);
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    int i, j, k;
+    if (value.length != DomainDimension) {
+      throw new SetException("GriddedSet.valueToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+
+    float[][] grid = valueToGrid(value);
+    for (i=0; i<length; i++) {
+      if (Double.isNaN(grid[ManifoldDimension-1][i])) {
+        index[i] = -1;
+        continue;
+      }
+      k = (int) (grid[ManifoldDimension-1][i] + 0.5);
+      for (j=ManifoldDimension-2; j>=0; j--) {
+        if (Double.isNaN(grid[j][i])) {
+          k = -1;
+          break;
+        }
+        k = ((int) (grid[j][i] + 0.5)) + Lengths[j] * k;
+      }
+      index[i] = k;
+    }
+    return index;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    if (Length > 1) {
+      for (int j=0; j<DomainDimension; j++) {
+        if (Lengths[j] < 2) {
+          throw new SetException("GriddedSet.gridToValue: requires all grid " +
+                                 "dimensions to be > 1");
+        }
+      }
+    }
+    throw new UnimplementedException("GriddedSet.gridToValue");
+  }
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    if (Length > 1) {
+      for (int j=0; j<DomainDimension; j++) {
+        if (Lengths[j] < 2) {
+          throw new SetException("GriddedSet.valueToGrid: requires all grid " +
+                                 "dimensions to be > 1");
+        }
+      }
+    }
+    throw new UnimplementedException("GriddedSet.valueToGrid");
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices, float[][] weights)
+              throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("GriddedSet.valueToInterp: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length) {
+      throw new SetException("GriddedSet.valueToInterp: indices length " +
+                             indices.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    if (weights.length != length) {
+      throw new SetException("GriddedSet.valueToInterp: weights length " +
+                             weights.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    float[][] grid = valueToGrid(value); // convert value array to grid coord array
+
+    int i, j, k; // loop indices
+    int lis; // temporary length of is & cs
+    int length_is; // final length of is & cs, varies by i
+    int isoff; // offset along one grid dimension
+    float a, b; // weights along one grid dimension; a + b = 1.0
+    int[] is; // array of indices, becomes part of indices
+    float[] cs; // array of coefficients, become part of weights
+
+    int base; // base index, as would be returned by valueToIndex
+    int[] l = new int[ManifoldDimension]; // integer 'factors' of base
+    float[] c = new float[ManifoldDimension]; // fractions with l; -0.5 <= c <= 0.5
+
+    // array of index offsets by grid dimension
+    int[] off = new int[ManifoldDimension];
+    off[0] = 1;
+    for (j=1; j<ManifoldDimension; j++) off[j] = off[j-1] * Lengths[j-1];
+
+    for (i=0; i<length; i++) {
+      // compute length_is, base, l & c
+      length_is = 1;
+      if (Double.isNaN(grid[ManifoldDimension-1][i])) {
+        base = -1;
+      }
+      else {
+        l[ManifoldDimension-1] = (int) (grid[ManifoldDimension-1][i] + 0.5);
+        // WLH 23 Dec 99
+        if (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1]) {
+          l[ManifoldDimension-1]--;
+        }
+        c[ManifoldDimension-1] = grid[ManifoldDimension-1][i] -
+                                 ((float) l[ManifoldDimension-1]);
+        if (!((l[ManifoldDimension-1] == 0 && c[ManifoldDimension-1] <= 0.0) ||
+              (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1] - 1 &&
+               c[ManifoldDimension-1] >= 0.0))) {
+          // only interp along ManifoldDimension-1 if between two valid grid coords
+          length_is *= 2;
+        }
+        base = l[ManifoldDimension-1];
+        if (base >= Lengths[ManifoldDimension-1]) base = -1;
+      }
+      for (j=ManifoldDimension-2; j>=0 && base>=0; j--) {
+        if (Double.isNaN(grid[j][i])) {
+          base = -1;
+        }
+        else {
+          l[j] = (int) (grid[j][i] + 0.5);
+          if (l[j] == Lengths[j]) l[j]--; // WLH 23 Dec 99
+          c[j] = grid[j][i] - ((float) l[j]);
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            length_is *= 2;
+          }
+          base = l[j] + Lengths[j] * base;
+          if (l[j] < 0 || l[j] >= Lengths[j]) base = -1;
+        }
+      }
+
+      if (base < 0) {
+        // value is out of grid so return null
+        is = null;
+        cs = null;
+      }
+      else {
+        // create is & cs of proper length, and init first element
+        is = new int[length_is];
+        cs = new float[length_is];
+        is[0] = base;
+        cs[0] = 1.0f;
+        lis = 1;
+
+        for (j=0; j<ManifoldDimension; j++) {
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            if (c[j] >= 0.0) {
+              // grid coord above base
+              isoff = off[j];
+              a = 1.0f - c[j];
+              b = c[j];
+            }
+            else {
+              // grid coord below base
+              isoff = -off[j];
+              a = 1.0f + c[j];
+              b = -c[j];
+           }
+            // float is & cs; adjust new offsets; split weights
+            for (k=0; k<lis; k++) {
+              is[k+lis] = is[k] + isoff;
+              cs[k+lis] = cs[k] * b;
+              cs[k] *= a;
+            }
+            lis *= 2;
+          }
+        }
+      }
+      indices[i] = is;
+      weights[i] = cs;
+    }
+  }
+
+  /**
+   * Returns the indexes of the neighboring points for every point in the set.
+   * <code>neighbors.length</code> should be at least <code>getLength()</code>.
+   * On return, <code>neighbors[i][j]</code> will be the index of the <code>j
+   * </code>-th neighboring point of point <code>i</code>.  This method
+   * will allocate and set the array <code>neighbors[i]</code> for all
+   * <code>i</code>.  For points in the interior of the set, the number of
+   * neighboring points is equal to <code>2*getManifoldDimension()</code>.  The
+   * number of neighboring points decreases, however, if the point in question
+   * is an exterior point on a hyperface, hyperedge, or hypercorner.
+   *
+   * @param neighbors                The array to contain the indexes of the
+   *                                 neighboring points.
+   * @throws NullPointerException    if the array is <code>null</code>.
+   * @throws ArrayIndexOutOfBoundsException
+   *                                 if <code>neighbors.length < getLength()
+   *                                 </code>.
+   * @throws VisADException          if a VisAD failure occurs.
+   */
+  public void getNeighbors( int[][] neighbors )
+              throws VisADException
+  {
+    int ii, ix, iy, iz, ii_R, ii_L, ii_U, ii_D, ii_F, ii_B;
+    int a, b, c, d;
+    int LengthX;
+    int LengthY;
+    int LengthZ;
+    int LengthXY;
+    int LengthXYZ;
+
+    switch( ManifoldDimension )
+    {
+       case 1:
+         neighbors[0] = new int[1];
+         neighbors[Length - 1] = new int[1];
+         neighbors[0][0] = 1;
+         neighbors[Length - 1][0] = Length - 2;
+
+         for ( ii = 1; ii < (Length - 1); ii++ ) {
+           neighbors[ii] = new int[2];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + 1;
+         }
+         break;
+       case 2:
+
+         LengthX = Lengths[0];
+         LengthY = Lengths[1];
+
+         //- corners:
+         ii = 0;
+         neighbors[ii] = new int[2];
+         neighbors[ii][0] = ii + LengthX;
+         neighbors[ii][1] = ii + 1;
+
+         ii = Length - 1;
+         neighbors[ii] = new int[2];
+         neighbors[ii][0] = ii - LengthX;
+         neighbors[ii][1] = ii - 1;
+
+         ii = Length - LengthX;
+         neighbors[ii] = new int[2];
+         neighbors[ii][0] = ii - LengthX;
+         neighbors[ii][1] = ii + 1;
+
+         ii = LengthX - 1;
+         neighbors[ii] = new int[2];
+         neighbors[ii][0] = ii + LengthX;
+         neighbors[ii][1] = ii - 1;
+
+         //- edge points, not corners:
+         for ( iy = 1; iy < (LengthY - 1); iy++ )
+         {
+           ii = iy*LengthX;
+           neighbors[ii] = new int[3];
+           neighbors[ii][0] = ii + LengthX;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii + 1;
+
+           ii = iy*LengthX + LengthX - 1;
+           neighbors[ii] = new int[3];
+           neighbors[ii][0] = ii + LengthX;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii - 1;
+         }
+
+         for ( ix = 1; ix < (LengthX - 1); ix++ )
+         {
+           ii = ix;
+           neighbors[ii] = new int[3];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + 1;
+           neighbors[ii][2] = ii + LengthX;
+
+           ii = (LengthY-1)*LengthX + ix;
+           neighbors[ii] = new int[3];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + 1;
+           neighbors[ii][2] = ii - LengthX;
+         }
+         //- interior points:
+         for ( iy = 1; iy < (LengthY - 1); iy++) {
+           for ( ix = 1; ix < (LengthX - 1); ix++) {
+
+             ii   = iy*LengthX + ix;
+             ii_R = ii + 1;
+             ii_L = ii - 1;
+             ii_U = ii + LengthX;
+             ii_D = ii - LengthX;
+             neighbors[ii] = new int[4];
+             neighbors[ii][0] = ii_R;
+             neighbors[ii][1] = ii_L;
+             neighbors[ii][2] = ii_U;
+             neighbors[ii][3] = ii_D;
+           }
+         }
+         break;
+
+       case 3:
+
+         LengthX = Lengths[0];
+         LengthY = Lengths[1];
+         LengthZ = Lengths[2];
+         LengthXY = LengthX*LengthY;
+         LengthXYZ = LengthX*LengthY*LengthZ;
+
+         //- corners:
+         ii = 0;
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii + LengthX;
+         neighbors[ii][1] = ii + 1;
+         neighbors[ii][2] = ii + LengthXY;
+
+         ii = LengthX*LengthY*( LengthZ - 1);
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii + 1;
+         neighbors[ii][1] = ii + LengthX;
+         neighbors[ii][2] = ii - LengthXY;
+
+         ii = LengthX*LengthY - 1;
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii - LengthX;
+         neighbors[ii][1] = ii - 1;
+         neighbors[ii][2] = ii + LengthXY;
+
+         ii = LengthX*LengthY*LengthZ - 1;
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii - 1;
+         neighbors[ii][1] = ii - LengthX;
+         neighbors[ii][2] = ii - LengthXY;
+
+         ii = LengthX*(LengthY - 1);
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii - LengthX;
+         neighbors[ii][1] = ii + 1;
+         neighbors[ii][2] = ii + LengthXY;
+
+         ii = LengthXY*(LengthZ - 1) + LengthX*(LengthY - 1);
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii + 1;
+         neighbors[ii][1] = ii - LengthX;
+         neighbors[ii][2] = ii - LengthXY;
+
+         ii = LengthX - 1;
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii + LengthX;
+         neighbors[ii][1] = ii - 1;
+         neighbors[ii][2] = ii + LengthXY;
+
+         ii = LengthXY*(LengthZ - 1) + (LengthX - 1);
+         neighbors[ii] = new int[3];
+         neighbors[ii][0] = ii - 1;
+         neighbors[ii][1] = ii + LengthX;
+         neighbors[ii][2] = ii - LengthXY;
+
+         //- edges:
+         for ( iy = 1; iy < (LengthY - 1); iy++ )
+         {
+           a = iy*LengthX;
+           b = a + LengthX-1;
+
+           ii = a;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii + LengthX;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii + 1;
+           neighbors[ii][3] = ii + LengthXY;
+
+           ii = a + LengthX-1;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii + LengthX;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii - 1;
+           neighbors[ii][3] = ii + LengthXY;
+
+           ii = a + (LengthZ-1)*LengthXY;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii + LengthX;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii - 1;
+           neighbors[ii][3] = ii - LengthXY;
+
+           ii = b + (LengthZ-1)*LengthXY;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii + LengthX;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii - 1;
+           neighbors[ii][3] = ii - LengthXY;
+         }
+         for ( ix = 1; ix < (LengthX - 1); ix++ )
+         {
+           ii = ix;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + 1;
+           neighbors[ii][2] = ii + LengthX;
+           neighbors[ii][3] = ii + LengthXY;
+
+           ii = (LengthY-1)*LengthX + ix;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + 1;
+           neighbors[ii][2] = ii - LengthX;
+           neighbors[ii][3] = ii + LengthXY;
+
+           ii = (LengthZ-1)*LengthXY + ix;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + 1;
+           neighbors[ii][2] = ii + LengthX;
+           neighbors[ii][3] = ii - LengthXY;
+
+           ii = (LengthZ-1)*LengthXY + (LengthY-1)*LengthX + ix;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + 1;
+           neighbors[ii][2] = ii - LengthX;
+           neighbors[ii][3] = ii - LengthXY;
+         }
+
+         for ( iz = 1; iz < (LengthZ - 1); iz++ )
+         {
+           a = iz*LengthXY;
+           b = a + (LengthX-1);
+
+           ii = a;
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii + 1;
+           neighbors[ii][1] = ii + LengthX;
+           neighbors[ii][2] = ii + LengthXY;
+           neighbors[ii][3] = ii - LengthXY;
+
+           ii = a + (LengthX-1);
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii + LengthX;
+           neighbors[ii][2] = ii + LengthXY;
+           neighbors[ii][3] = ii + LengthXY;
+
+           ii = a + LengthX*(LengthY-1);
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii + 1;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii + LengthXY;
+           neighbors[ii][3] = ii - LengthXY;
+
+           ii = a + (LengthXY-1);
+           neighbors[ii] = new int[4];
+           neighbors[ii][0] = ii - 1;
+           neighbors[ii][1] = ii - LengthX;
+           neighbors[ii][2] = ii + LengthXY;
+           neighbors[ii][3] = ii - LengthXY;
+         }
+
+         //- sides:
+
+         for ( iy = 1; iy < (LengthY - 1); iy++ )
+         {
+           for ( ix = 1; ix < ( LengthX - 1); ix++ )
+           {
+             ii = iy*LengthX + ix;
+             neighbors[ii] = new int[5];
+             neighbors[ii][0] = ii + 1;
+             neighbors[ii][1] = ii - 1;
+             neighbors[ii][2] = ii + LengthX;
+             neighbors[ii][3] = ii - LengthX;
+             neighbors[ii][4] = ii + LengthXY;
+
+             ii = (LengthZ - 1)*LengthXY + iy*LengthX + ix;
+             neighbors[ii] = new int[5];
+             neighbors[ii][0] = ii + 1;
+             neighbors[ii][1] = ii - 1;
+             neighbors[ii][2] = ii + LengthX;
+             neighbors[ii][3] = ii - LengthX;
+             neighbors[ii][4] = ii - LengthXY;
+           }
+         }
+
+         for ( iz = 1; iz < (LengthZ - 1); iz++ )
+         {
+           for ( ix = 1; ix < ( LengthX - 1); ix++ )
+           {
+             ii = iz*LengthXY + ix;
+             neighbors[ii] = new int[5];
+             neighbors[ii][0] = ii + 1;
+             neighbors[ii][1] = ii - 1;
+             neighbors[ii][2] = ii + LengthX;
+             neighbors[ii][3] = ii + LengthXY;
+             neighbors[ii][4] = ii - LengthXY;
+
+             ii = iz*LengthXY + LengthX*(LengthY - 1) + ix;
+             neighbors[ii] = new int[5];
+             neighbors[ii][0] = ii + 1;
+             neighbors[ii][1] = ii - 1;
+             neighbors[ii][2] = ii - LengthX;
+             neighbors[ii][3] = ii + LengthXY;
+             neighbors[ii][4] = ii - LengthXY;
+           }
+         }
+
+         for ( iz = 1; iz < (LengthZ - 1); iz++ )
+         {
+           for ( iy = 1; iy < ( LengthY - 1); iy++ )
+           {
+             ii = iz*LengthXY + iy*LengthX;
+             neighbors[ii] = new int[5];
+             neighbors[ii][0] = ii + 1;
+             neighbors[ii][1] = ii + LengthX;
+             neighbors[ii][2] = ii - LengthX;
+             neighbors[ii][3] = ii + LengthXY;
+             neighbors[ii][4] = ii - LengthXY;
+
+             ii = iz*LengthXY + iy*LengthX + ( LengthX - 1);
+             neighbors[ii] = new int[5];
+             neighbors[ii][0] = ii - 1;
+             neighbors[ii][1] = ii + LengthX;
+             neighbors[ii][2] = ii - LengthX;
+             neighbors[ii][3] = ii + LengthXY;
+             neighbors[ii][4] = ii - LengthXY;
+           }
+         }
+
+         //- interior points:
+         for ( iz = 1; iz < (LengthZ - 1); iz++) {
+           for ( iy = 1; iy < (LengthY - 1); iy++) {
+             for ( ix = 1; ix < (LengthX - 1); ix++) {
+
+               ii   = iz*LengthXY + iy*LengthX + ix;
+               ii_R = ii + 1;
+               ii_L = ii - 1;
+               ii_F = ii + LengthX;
+               ii_B = ii - LengthX;
+               ii_U = ii + LengthXY;
+               ii_D = ii - LengthXY;
+               neighbors[ii] = new int[6];
+               neighbors[ii][0] = ii_R;
+               neighbors[ii][1] = ii_L;
+               neighbors[ii][2] = ii_F;
+               neighbors[ii][3] = ii_B;
+               neighbors[ii][4] = ii_U;
+               neighbors[ii][5] = ii_D;
+             }
+           }
+         }
+         break;
+
+       default:
+         throw new UnimplementedException("getNeighbors(): ManifoldDimension >"+
+                                 ManifoldDimension+" not currently implemented" );
+
+    }
+  }
+
+  /**
+   * Returns the indexes of the neighboring points along a manifold
+   * dimesion for every point in the set. Elements <code>[i][0]</code>
+   * and <code>[i][1]</code> of the returned array are the indexes of the
+   * neighboring sample points in the direction of decreasing and increasing
+   * manifold index, respectively, for sample point <code>i</code>.  If a sample
+   * point doesn't have a neighboring point (because it is an exterior point,
+   * for example) then the value of the corresponding index will be -1.
+   *
+   * @param manifoldIndex          The index of the manifold dimension along
+   *                               which to return the neighboring points.
+   * @throws ArrayIndexOutOfBoundsException
+   *                               if <code>manifoldIndex < 0 || 
+   *                               manifoldIndex >= getManifoldDimension()
+   *                               </code>.
+   * @see #getManifoldDimension()
+   */
+  public int[][] getNeighbors( int manifoldIndex )
+  {
+    int[][] neighbors = new int[ Length ][2];
+    int[] m_coords = new int[ ManifoldDimension ];
+    int[][] indeces = new int[2][ ManifoldDimension ];
+    int ii_tmp, idx_u, idx_d, mm, tt, ii, jj, kk, k;
+
+    for ( ii = 0; ii < Length; ii++ )
+    {
+      //- get the manifold coordinates for each index  -*
+      ii_tmp = ii;
+      for ( jj = 0; jj < (ManifoldDimension-1); jj++ ) {
+        m_coords[jj] = ii_tmp % Lengths[jj];
+        ii_tmp /= Lengths[jj];
+      }
+      m_coords[ManifoldDimension-1] = ii_tmp;
+
+      //- get coordinates of two neighbors on each side  -*
+      for ( kk = 0; kk < 2; kk++) {
+        for ( jj = 0; jj < ManifoldDimension; jj++) {
+          indeces[kk][jj] = m_coords[jj];
+        }
+      }
+
+      idx_u = m_coords[ manifoldIndex ] + 1;
+      idx_d = m_coords[ manifoldIndex ] - 1;
+      indeces[1][manifoldIndex] = idx_u;
+      indeces[0][manifoldIndex] = idx_d;
+      if ( idx_u >= Lengths[manifoldIndex] ) {
+        indeces[1][manifoldIndex] = -1;
+      }
+      else if ( idx_d < 0 ) {
+        indeces[0][manifoldIndex] = -1;
+      }
+
+      //- compute index for each new coordinates  -*
+      for ( kk = 0; kk < 2; kk++ )
+      {
+        if ( indeces[kk][manifoldIndex] != -1 ) {
+          ii_tmp = 0;
+          for ( mm = (ManifoldDimension-1); mm>=0; mm--) {
+            k = indeces[kk][mm];
+            for ( tt = 0; tt < mm; tt++ ) {
+              k = k*Lengths[tt];
+            }
+            ii_tmp += k;
+          }
+          neighbors[ii][kk] = ii_tmp;
+        }
+        else {
+          neighbors[ii][kk] = -1;
+        }
+      }
+    }
+    return neighbors;
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof GriddedSet) || set == null ||
+        set instanceof LinearSet ||
+        set instanceof Gridded1DDoubleSet) return false;
+    if (this == set) return true;
+    if (testNotEqualsCache((Set) set)) return false;
+    if (testEqualsCache((Set) set)) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    try {
+      int i, j;
+      if (DomainDimension != ((GriddedSet) set).getDimension() ||
+          ManifoldDimension != ((GriddedSet) set).getManifoldDimension() ||
+          Length != ((GriddedSet) set).getLength()) return false;
+      for (j=0; j<ManifoldDimension; j++) {
+        if (Lengths[j] != ((GriddedSet) set).getLength(j)) return false;
+      }
+      // Sets are immutable, so no need for 'synchronized'
+      float[][] samples = ((GriddedSet) set).getSamples(false);
+      float[][]mySamples = getMySamples();
+      if (mySamples != null) {
+        for (j=0; j<DomainDimension; j++) {
+          for (i=0; i<Length; i++) {
+            if (mySamples[j][i] == mySamples[j][i]
+                  ? mySamples[j][i] != samples[j][i]
+                  : samples[j][i] == samples[j][i]) {
+              addNotEqualsCache((Set) set);
+              return false;
+            }
+          }
+        }
+      }
+      else {
+        float[][] this_samples = getSamples(false);
+        for (j=0; j<DomainDimension; j++) {
+          for (i=0; i<Length; i++) {
+            if (this_samples[j][i] == this_samples[j][i]
+                  ? this_samples[j][i] != samples[j][i]
+                  : samples[j][i] == samples[j][i]) {
+              addNotEqualsCache((Set) set);
+              return false;
+            }
+          }
+        }
+      }
+      addEqualsCache((Set) set);
+      return true;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Returns the hash code of this instance. {@link Object#hashCode()} should be
+   * overridden whenever {@link Object#equals(Object)} is.
+   * @return			The hash code of this instance (includes the
+   *				values).
+   */
+  public int hashCode()
+  {
+    if (!hashCodeSet)
+    {
+      hashCode = unitAndCSHashCode();
+      hashCode ^= DomainDimension ^ ManifoldDimension ^ Length;
+      for (int j=0; j<ManifoldDimension; j++)
+	hashCode ^= Lengths[j];
+      float[][]mySamples = getMySamples();
+      if (mySamples != null)
+	for (int j=0; j<DomainDimension; j++)
+	  for (int i=0; i<Length; i++)
+	    hashCode ^= Float.floatToIntBits(mySamples[j][i]);
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new GriddedSet(type,     getMySamples(), Lengths, DomainCoordinateSystem,
+                          SetUnits, SetErrors);
+  }
+
+  public String longString(String pre) throws VisADException {
+    String s;
+    int j;
+    if (DomainDimension == ManifoldDimension) {
+      s = pre + getClass().getName() + ": Dimension = " +
+          DomainDimension + " Length = " + Length + "\n";
+      for (j=0; j<DomainDimension; j++) {
+        s = s + pre + "  Dimension " + j + ":" + " Length = " + Lengths[j] +
+                " Range = " + Low[j] + " to " + Hi[j] + "\n";
+      }
+    }
+    else {
+      s = pre + getClass().getName() + ": DomainDimension = " +
+          DomainDimension + " ManifoldDimension = " + ManifoldDimension +
+          " Length = " + Length + "\n";
+      for (j=0; j<ManifoldDimension; j++) {
+        s = s + pre + "  ManifoldDimension " + j + ":" +
+            " Length = " + Lengths[j] + "\n";
+      }
+      for (j=0; j<DomainDimension; j++) {
+        s = s + pre + "  DomainDimension " + j + ":" + " Range = " +
+            Low[j] + " to " + Hi[j] + "\n";
+      }
+    }
+    return s;
+  }
+
+}
+
diff --git a/visad/GriddedSetIface.java b/visad/GriddedSetIface.java
new file mode 100644
index 0000000..a072009
--- /dev/null
+++ b/visad/GriddedSetIface.java
@@ -0,0 +1,82 @@
+//
+// GriddedSetIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * GriddedSetIface is the interface to a finite set of samples of R^n.<P>
+ */
+public interface GriddedSetIface
+  extends SampledSetIface
+{
+  /**
+   * Returns the number of grid points in a given dimension.
+   *
+   * @param i			The index of the dimension.
+   * @return			The number of grid points in dimension 
+   *				<code>i</code>.
+   */
+  int getLength(int i);
+
+  /**
+   * Returns the number of grid points in all dimensions.
+   *
+   * @return			The number of grid points in all dimensions.
+   *				Element [i] is the number of grid points in
+   *				dimension <code>i</code>.
+   */
+  int[] getLengths();
+
+  /**
+   * Returns the interpolated samples of the set corresponding to an array of
+   * grid points with non-integer coordinates.
+   *
+   * @param grid                The coordinates of the interpolation grid
+   *                            points for which interpolated sample values are
+   *                            desired. <code>grid[i][j]</code> is the i-th
+   *                            grid coordinate of the j-th interpolation point.
+   * @return                    The interpolated samples of the set.  Element
+   *                            [i][j] is the i-th coordinate of the j-th
+   *                            interpolation point.
+   * @throws VisADException	VisAD failure.
+   */
+  float[][] gridToValue(float[][] grid) throws VisADException;
+
+  /**
+   * Returns the non-integer grid coordinates corresponding to an array of 
+   * points.
+   *
+   * @param value               The array of points for which non-integer
+   *                            grid coordinates are desired.
+   *                            <code>value[i][j]</code> is the i-th coordinate
+   *                            of the j-th point.
+   * @return                    The array of grid coordinates corresponding
+   *                            to the points.  Element [i][j] is the i-th
+   *                            non-integer grid coordinate of the j-th point.
+   * @throws VisADException	VisAD failure.
+   */
+  float[][] valueToGrid(float[][] value) throws VisADException;
+}
diff --git a/visad/HSVCoordinateSystem.java b/visad/HSVCoordinateSystem.java
new file mode 100644
index 0000000..961a68b
--- /dev/null
+++ b/visad/HSVCoordinateSystem.java
@@ -0,0 +1,256 @@
+//
+// HSVCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   HSVCoordinateSystem is the VisAD CoordinateSystem class for
+   (Hue, Saturation, Value) with Reference (Red, Green, Blue).
+   Algorithm from Foley and van Dam.<P>
+*/
+public class HSVCoordinateSystem extends CoordinateSystem {
+
+  private static Unit[] coordinate_system_units =
+    {CommonUnit.degree, null, null};
+
+  public HSVCoordinateSystem(RealTupleType reference)
+         throws VisADException {
+    super(reference, coordinate_system_units);
+  }
+
+  /** trusted constructor for initializers */
+  HSVCoordinateSystem(RealTupleType reference, boolean b) {
+    super(reference, coordinate_system_units, b);
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("HSVCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    int j = 0;
+    double f = 0.0, p = 0.0, q = 0.0, t = 0.0;
+    for (int i=0; i<len ;i++) {
+      double h = tuples[0][i] % 360.0; // belt
+      if (h < 0.0) h += 360.0;         // suspenders
+      double s = Math.max(0.0, Math.min(1.0, tuples[1][i]));
+      double v = Math.max(0.0, Math.min(1.0, tuples[2][i]));
+      if (s == 0.0) {
+        value[0][i] = v;
+        value[1][i] = v;
+        value[2][i] = v;
+      }
+      else { // if (s != 0.0)
+        h = h / 60.0f;
+        j = (int) Math.floor(h);
+        f = h - j;
+        p = v * (1.0 - s);
+        q = v * (1.0 - s * f);
+        t = v * (1.0 - s * (1.0 - f));
+        switch (j) {
+          case 0:
+            value[0][i] = v;
+            value[1][i] = t;
+            value[2][i] = p;
+            break;
+          case 1:
+            value[0][i] = q;
+            value[1][i] = v;
+            value[2][i] = p;
+            break;
+          case 2:
+            value[0][i] = p;
+            value[1][i] = v;
+            value[2][i] = t;
+            break;
+          case 3:
+            value[0][i] = p;
+            value[1][i] = q;
+            value[2][i] = v;
+            break;
+          case 4:
+            value[0][i] = t;
+            value[1][i] = p;
+            value[2][i] = v;
+            break;
+          case 5:
+          default:
+            value[0][i] = v;
+            value[1][i] = p;
+            value[2][i] = q;
+            break;
+        } // end switch (i)
+      } // end if (s != 0.0)
+    } // end for (int i=0; i<len ;i++)
+    return value;
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("HSVCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    for (int i=0; i<len ;i++) {
+      double r = Math.max(0.0, Math.min(1.0, tuples[0][i]));
+      double g = Math.max(0.0, Math.min(1.0, tuples[1][i]));
+      double b = Math.max(0.0, Math.min(1.0, tuples[2][i]));
+      double max = Math.max(r, Math.max(g, b));
+      double min = Math.min(r, Math.min(g, b));
+      value[2][i] = max; // V
+      value[1][i] = (max != 0.0) ? (max - min) / max : 0.0; // S
+      if (value[1][i] == 0.0) {
+        value[2][i] = 0.0; // H (use 0.0 rather than undetermined)
+      }
+      else {
+        double rc = (max - r) / (max - min);
+        double gc = (max - g) / (max - min);
+        double bc = (max - b) / (max - min);
+        double h;
+        if (r == max) h = bc - gc;
+        else if (g == max) h = 2.0 + rc - bc;
+        else if (b == max) h = 4.0 + gc - rc;
+        else {
+          throw new CoordinateSystemException("HSVCoordinateSystem: bad h");
+        }
+        h = 60.0 * h;
+        if (h < 0.0) h += 360.0;
+        value[2][i] = h; // H
+      }
+    }
+    return value;
+  }
+
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("HSVCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    int j = 0;
+    float f = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f;
+    for (int i=0; i<len ;i++) {
+      float h = tuples[0][i] % 360.0f; // belt
+      if (h < 0.0f) h += 360.0f;       // suspenders
+      float s = Math.max(0.0f, Math.min(1.0f, tuples[1][i]));
+      float v = Math.max(0.0f, Math.min(1.0f, tuples[2][i]));
+      if (s == 0.0f) {
+        value[0][i] = v;
+        value[1][i] = v;
+        value[2][i] = v;
+      }
+      else { // if (s != 0.0f)
+        h = h / 60.0f;
+        j = (int) Math.floor(h);
+        f = h - j;
+        p = v * (1.0f - s);
+        q = v * (1.0f - s * f);
+        t = v * (1.0f - s * (1.0f - f));
+        switch (j) {
+          case 0:
+            value[0][i] = v;
+            value[1][i] = t;
+            value[2][i] = p;
+            break;
+          case 1:
+            value[0][i] = q;
+            value[1][i] = v;
+            value[2][i] = p;
+            break;
+          case 2:
+            value[0][i] = p;
+            value[1][i] = v;
+            value[2][i] = t;
+            break;
+          case 3:
+            value[0][i] = p;
+            value[1][i] = q;
+            value[2][i] = v;
+            break;
+          case 4:
+            value[0][i] = t;
+            value[1][i] = p;
+            value[2][i] = v;
+            break;
+          case 5:
+          default:
+            value[0][i] = v;
+            value[1][i] = p;
+            value[2][i] = q;
+            break;
+        } // end switch (i)
+      } // end if (s != 0.0f)
+    } // end for (int i=0; i<len ;i++)
+    return value;
+  }
+
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("HSVCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    for (int i=0; i<len ;i++) {
+      float r = Math.max(0.0f, Math.min(1.0f, tuples[0][i]));
+      float g = Math.max(0.0f, Math.min(1.0f, tuples[1][i]));
+      float b = Math.max(0.0f, Math.min(1.0f, tuples[2][i]));
+      float max = Math.max(r, Math.max(g, b));
+      float min = Math.min(r, Math.min(g, b));
+      value[2][i] = max; // V
+      value[1][i] = (max != 0.0f) ? (max - min) / max : 0.0f; // S
+      if (value[1][i] == 0.0f) {
+        value[2][i] = 0.0f; // H (use 0.0f rather than undetermined)
+      }
+      else {
+        float rc = (max - r) / (max - min);
+        float gc = (max - g) / (max - min);
+        float bc = (max - b) / (max - min);
+        float h;
+        if (r == max) h = bc - gc;
+        else if (g == max) h = 2.0f + rc - bc;
+        else if (b == max) h = 4.0f + gc - rc;
+        else {
+          throw new CoordinateSystemException("HSVCoordinateSystem: bad h");
+        }
+        h = 60.0f * h;
+        if (h < 0.0f) h += 360.0f;
+        value[2][i] = h; // H
+      }
+    }
+    return value;
+  }
+
+  public boolean equals(Object cs) {
+    return (cs instanceof HSVCoordinateSystem);
+  }
+
+}
+
diff --git a/visad/IdentityCoordinateSystem.java b/visad/IdentityCoordinateSystem.java
new file mode 100644
index 0000000..f3479e5
--- /dev/null
+++ b/visad/IdentityCoordinateSystem.java
@@ -0,0 +1,129 @@
+//
+// IdentityCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * A CoordinateSystem that will
+ * return the input values when <CODE>toReference()</CODE> and
+ * <CODE>fromReference()</CODE> are called.  Useful in constructing
+ * <CODE>CartesianProductCoordinateSystem</CODE>s.
+ * @see visad.CartesianProductCoordinateSystem
+ */
+public class IdentityCoordinateSystem extends CoordinateSystem
+{
+
+    /**
+     * Construct a new <CODE>IdentityCoordinateSystem</CODE> for
+     * values of the type specified.
+     * @param  type  type of the values
+     */
+    public IdentityCoordinateSystem(RealTupleType type)
+        throws VisADException
+    {
+        this(type, type.getDefaultUnits());
+    }
+
+    /**
+     * Construct a new <CODE>IdentityCoordinateSystem</CODE> for
+     * values of the type specified.
+     * @param  type  type of the values
+     */
+    public IdentityCoordinateSystem(RealTupleType type, Unit[] units)
+        throws VisADException
+    {
+        super(type, units);
+    }
+
+    /**
+     * Simple implementation of abstract method.  Returns the input values.
+     * @param values  input values
+     * @return values
+     * @throws VisADException  values are null or wrong dimension
+     */
+    public double[][] fromReference(double[][] values)
+        throws VisADException
+    {
+        if (values == null || values.length != getDimension())
+            throw new VisADException("values are null or wrong dimension");
+        return values;
+    }
+        
+    /**
+     * Simple implementation of abstract method.  Returns the input values.
+     * @param values  input values
+     * @return values
+     * @throws VisADException  values are null or wrong dimension
+     */
+    public double[][] toReference(double[][] values)
+        throws VisADException
+    {
+        if (values == null || values.length != getDimension())
+            throw new VisADException("values are null or wrong dimension");
+        return values;
+    }
+
+    /**
+     * Simple implementation of abstract method.  Returns the input values.
+     * @param values  input values
+     * @return values
+     * @throws VisADException  values are null or wrong dimension
+     */
+    public float[][] fromReference(float[][] values)
+        throws VisADException
+    {
+        if (values == null || values.length != getDimension())
+            throw new VisADException("values are null or wrong dimension");
+        return values;
+    }
+        
+    /**
+     * Simple implementation of abstract method.  Returns the input values.
+     * @param values  input values
+     * @return values
+     * @throws VisADException  values are null or wrong dimension
+     */
+    public float[][] toReference(float[][] values)
+        throws VisADException
+    {
+        if (values == null || values.length != getDimension())
+            throw new VisADException("values are null or wrong dimension");
+        return values;
+    }
+
+    /**
+     * Check to see if the object in question is equal to this.
+     * @param  o  object in question
+     * @return  true if they are equal, otherwise false.
+     */
+    public boolean equals(Object o)
+    {
+        if (!(o instanceof IdentityCoordinateSystem)) return false;
+        if (!(getReference().equals(
+                ((IdentityCoordinateSystem) o).getReference()))) return false;
+        return true;
+    }
+}
diff --git a/visad/ImageFlatField.java b/visad/ImageFlatField.java
new file mode 100644
index 0000000..5e07747
--- /dev/null
+++ b/visad/ImageFlatField.java
@@ -0,0 +1,541 @@
+//
+// ImageFlatField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Graphics2D;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
+import java.rmi.RemoteException;
+import java.util.Arrays;
+
+/**
+ * ImageFlatField is a VisAD FlatField backed by a java.awt.image.BufferedImage
+ * object, instead of the usual float[][] or double[][] samples array.
+ * Expands the samples into floats or doubles on demand using
+ * {@link #unpackFloats(boolean)}, which can be expensive for repeated
+ * operations of certain types. Such calls can be avoided for certain types of
+ * visualization using an 8-bit image with
+ * {@link visad.bom.ShadowImageFunctionTypeJ3D}.
+ */
+public class ImageFlatField extends FlatField {
+
+  // -- Constants --
+
+  /** Debugging flag. */
+  public static final boolean DEBUG = false;
+
+
+  // -- Fields --
+
+  /** The image backing this FlatField. */
+  protected BufferedImage image;
+
+  /** Dimensions of the image. */
+  protected int num, width, height;
+
+
+  // -- Static methods --
+
+  /**
+   * Converts the given BufferedImage to a format efficient
+   * with ImageFlatField's grabBytes method and usable with
+   * Java3D texturing by reference.
+   */
+  public static BufferedImage make3ByteRGB(BufferedImage image) {
+    // create BufferedImage of "TYPE_3BYTE_RGB" (TYPE_CUSTOM)
+    // This type of image is efficient with grabBytes, and is
+    // compatible with Java3D's texturing by reference support.
+    if (image == null) return null;
+    int dataType = DataBuffer.TYPE_BYTE;
+    ColorModel colorModel = new ComponentColorModel(
+      ColorSpace.getInstance(ColorSpace.CS_sRGB),
+      false, false, ColorModel.TRANSLUCENT, dataType);
+    int w = image.getWidth(), h = image.getHeight();
+    byte[][] data = new byte[3][w * h];
+    SampleModel model = new BandedSampleModel(dataType, w, h, data.length);
+    DataBuffer buffer = new DataBufferByte(data, data[0].length);
+    WritableRaster raster = Raster.createWritableRaster(model, buffer, null);
+    BufferedImage result = new BufferedImage(colorModel, raster, false, null);
+
+    // paint image into buffered image
+    Graphics2D g = result.createGraphics();
+    g.drawRenderedImage(image, null);
+    g.dispose();
+    g = null;
+
+    return result;
+  }
+
+  /** Constructs a FunctionType suitable for use with the given image. */
+  public static FunctionType makeFunctionType(BufferedImage img)
+    throws VisADException
+  {
+    if (img == null) throw new VisADException("image cannot be null");
+    RealType x = RealType.getRealType("ImageElement");
+    RealType y = RealType.getRealType("ImageLine");
+    RealTupleType xy = new RealTupleType(x, y);
+    int num = img.getRaster().getNumBands();
+    MathType range = null;
+    if (num == 4) {
+      RealType r = RealType.getRealType("Red");
+      RealType g = RealType.getRealType("Green");
+      RealType b = RealType.getRealType("Blue");
+      RealType a = RealType.getRealType("Alpha");
+      range = new RealTupleType(r, g, b, a);
+    }
+    else if (num == 3) {
+      RealType r = RealType.getRealType("Red");
+      RealType g = RealType.getRealType("Green");
+      RealType b = RealType.getRealType("Blue");
+      range = new RealTupleType(r, g, b);
+    }
+    else if (num == 1) range = RealType.getRealType("Intensity");
+    else throw new VisADException("Unsupported # of bands (" + num + ")");
+    return new FunctionType(xy, range);
+  }
+
+  /** Constructs a domain Set suitable for use with the given image. */
+  public static Set makeDomainSet(BufferedImage img) throws VisADException {
+    RealType x = RealType.getRealType("ImageElement");
+    RealType y = RealType.getRealType("ImageLine");
+    RealTupleType xy = new RealTupleType(x, y);
+    int w = img.getWidth(), h = img.getHeight();
+    return new Linear2DSet(xy, 0, w - 1, w, h - 1, 0, h);
+  }
+
+
+  // -- Constructors --
+
+  /** Constructs an ImageFlatField around the given BufferedImage. */
+  public ImageFlatField(BufferedImage img)
+    throws VisADException, RemoteException
+  {
+    this(makeFunctionType(img), makeDomainSet(img));
+    setImage(img);
+  }
+
+  public ImageFlatField(FunctionType type) throws VisADException {
+    this(type, type.getDomain().getDefaultSet(), null, null, null, null);
+  }
+
+  public ImageFlatField(FunctionType type, Set domain_set)
+                        throws VisADException {
+    this(type, domain_set, null, null, null, null);
+  }
+
+  public ImageFlatField(FunctionType type, Set domain_set,
+                        CoordinateSystem range_coord_sys, Set[] range_sets,
+                        Unit[] units) throws VisADException {
+    this(type, domain_set, range_coord_sys, null, range_sets, units);
+  }
+
+  public ImageFlatField(FunctionType type, Set domain_set,
+                        CoordinateSystem[] range_coord_syses, Set[] range_sets,
+                        Unit[] units) throws VisADException {
+    this(type, domain_set, null, range_coord_syses, range_sets, units);
+  }
+
+  public ImageFlatField(FunctionType type, Set domain_set,
+                        CoordinateSystem range_coord_sys,
+                        CoordinateSystem[] range_coord_syses,
+                        Set[] range_sets, Unit[] units) throws VisADException {
+    super(type, domain_set, range_coord_sys,
+      range_coord_syses, range_sets, units);
+
+    RealTupleType domain = type.getDomain();
+    if (domain.getNumberOfRealComponents() != 2) {
+      throw new VisADException(
+        "FunctionType domain must be flat with 2 components");
+    }
+    MathType range = type.getRange();
+    if (range instanceof RealType) num = 1;
+    else if (range instanceof RealTupleType) {
+      num = ((RealTupleType) range).getNumberOfRealComponents();
+    }
+    if (num != 1 && num != 3 && num != 4) {
+      throw new VisADException(
+        "FunctionType range must be flat with 1, 3 or 4 components");
+    }
+    if (domain_set instanceof Gridded2DSet) {
+      int[] len = ((Gridded2DSet) domain_set).getLengths();
+      width = len[0];
+      height = len[1];
+    }
+    else {
+      throw new VisADException(
+        "Domain set must be Gridded2DSet");
+    }
+  }
+
+
+  // -- ImageFlatField API methods --
+
+  /** Gets the image backing this FlatField. */
+  public BufferedImage getImage() {
+    pr ("getImage");
+    return image;
+  }
+
+  /** Sets the image backing this FlatField. */
+  public void setImage(BufferedImage image)
+    throws VisADException, RemoteException
+  {
+//    pr ("setImage");
+    if (image == null) throw new VisADException("image cannot be null");
+    if (image.getWidth() != width || image.getHeight() != height) {
+      throw new VisADException("Image dimensions do not match domain set");
+    }
+    if (image.getRaster().getNumBands() != num) {
+      throw new VisADException(
+        "Image component count does not match FunctionType range");
+    }
+    this.image = image;
+    clearMissing();
+    notifyReferences();
+  }
+
+  /** Gets RealType for each domain component (X and Y). */
+  public RealType[] getDomainTypes() {
+    RealTupleType domain = ((FunctionType) getType()).getDomain();
+    return domain.getRealComponents();
+  }
+
+  /** Gets RealType for each range component. */
+  public RealType[] getRangeTypes() {
+    MathType range = ((FunctionType) getType()).getRange();
+    RealType[] v = null;
+    if (range instanceof RealType) v = new RealType[] {(RealType) range};
+    else if (range instanceof TupleType) {
+      v = ((TupleType) range).getRealComponents();
+    }
+    else v = new RealType[0];
+    return v;
+  }
+
+
+  // -- FlatField API methods --
+
+  public void setSamples(Data[] range, boolean copy)
+    throws VisADException, RemoteException
+  {
+    throw new VisADException("Use setImage(Image) for ImageFlatField");
+  }
+
+  /**
+   * This method has been overridden to avoid a call to
+   * unpackValues or unpackFloats during range computation.
+   */
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+    throws VisADException
+  {
+    if (isMissing()) return shadow;
+
+    ShadowRealTupleType domainType = ((ShadowFunctionType) type).getDomain();
+    int n = domainType.getDimension();
+    double[][] ranges = new double[2][n];
+    // DomainSet.computeRanges handles Reference
+    shadow = getDomainSet().computeRanges(domainType, shadow, ranges, true);
+    ShadowRealTupleType shadRef;
+    // skip range if no range components are mapped
+    int[] indices = ((ShadowFunctionType) type).getRangeDisplayIndices();
+    boolean anyMapped = false;
+    for (int i=0; i<TupleDimension; i++) {
+      if (indices[i] >= 0) anyMapped = true;
+    }
+    if (!anyMapped) return shadow;
+
+    // check for any range coordinate systems
+    boolean anyRangeRef = (RangeCoordinateSystem != null);
+    if (RangeCoordinateSystems != null) {
+      for (int i=0; i<RangeCoordinateSystems.length; i++) {
+        anyRangeRef |= (RangeCoordinateSystems[i] != null);
+      }
+    }
+    ranges = anyRangeRef ? new double[2][TupleDimension] : null;
+
+    // get image raster
+    WritableRaster raster = image.getRaster();
+
+    double[] min = new double[TupleDimension];
+    Arrays.fill(min, Double.MAX_VALUE);
+    double[] max = new double[TupleDimension];
+    Arrays.fill(max, -Double.MAX_VALUE);
+    double[] vals = new double[TupleDimension];
+    for (int y=0; y<height; y++) {
+      for (int x=0; x<width; x++) {
+        raster.getPixel(x, y, vals);
+        for (int i=0; i<TupleDimension; i++) {
+          min[i] = Math.min(min[i], vals[i]);
+          max[i] = Math.max(max[i], vals[i]);
+        }
+      }
+    }
+    for (int i=0; i<TupleDimension; i++) {
+      int k = indices[i];
+      if (k >= 0 || anyRangeRef) {
+        Unit dunit = ((RealType) ((FunctionType)
+          Type).getFlatRange().getComponent(i)).getDefaultUnit();
+        if (dunit != null && !dunit.equals(RangeUnits[i])) {
+          min[i] = dunit.toThis(min[i], RangeUnits[i]);
+          max[i] = dunit.toThis(max[i], RangeUnits[i]);
+        }
+        if (anyRangeRef) {
+          ranges[0][i] = Math.min(ranges[0][i], min[i]);
+          ranges[1][i] = Math.max(ranges[1][i], max[i]);
+        }
+        if (k >= 0 && k < shadow.ranges[0].length) {
+          shadow.ranges[0][k] = Math.min(shadow.ranges[0][k], min[i]);
+          shadow.ranges[1][k] = Math.max(shadow.ranges[1][k], max[i]);
+        }
+      }
+    }
+    if (RangeCoordinateSystem != null) {
+      // computeRanges for Reference (relative to range) RealTypes
+      ShadowRealTupleType rangeType =
+        (ShadowRealTupleType) ((ShadowFunctionType) type).getRange();
+      shadRef = rangeType.getReference();
+      shadow = computeReferenceRanges(rangeType, RangeCoordinateSystem,
+        RangeUnits, shadow, shadRef, ranges);
+    }
+    else if (RangeCoordinateSystems != null) {
+      TupleType rangeTupleType = (TupleType) ((FunctionType) Type).getRange();
+      int j = 0;
+      for (int i=0; i<RangeCoordinateSystems.length; i++) {
+        MathType component = rangeTupleType.getComponent(i);
+        if (component instanceof RealType) j++;
+        else { // (component instanceof RealTupleType)
+          int m = ((RealTupleType) component).getDimension();
+          if (RangeCoordinateSystems[i] != null) {
+            // computeRanges for Reference (relative to range
+            // component) RealTypes
+            double[][] subRanges = new double[2][m];
+            Unit[] subUnits = new Unit[m];
+            for (int k=0; k<m; k++) {
+              subRanges[0][k] = ranges[0][j];
+              subRanges[1][k] = ranges[1][j];
+              subUnits[k] = RangeUnits[j];
+              j++;
+            }
+            ShadowRealTupleType rangeType = (ShadowRealTupleType)
+              ((ShadowTupleType) ((ShadowFunctionType)
+              type).getRange()).getComponent(i);
+            shadRef = rangeType.getReference();
+            shadow = computeReferenceRanges(rangeType,
+              RangeCoordinateSystems[i], subUnits,
+              shadow, shadRef, subRanges);
+          }
+          else { // (RangeCoordinateSystems[i] == null)
+            j += m;
+          }
+        } // end if (component instanceof RealTupleType)
+      } // end for (int i=0; i<RangeCoordinateSystems.length; i++)
+    } // end if (RangeCoordinateSystems != null)
+    return shadow;
+  }
+
+  /**
+   * Unpacks an array of doubles from field sample values.
+   *
+   * @param copy            Ignored (always returns a copy).
+   */
+  protected double[][] unpackValues(boolean copy) throws VisADException {
+    pr ("unpackValues(" + copy + ")");
+    // copy flag is ignored
+    Raster r = image.getRaster();
+    double[][] samps = new double[num][width * height];
+    for (int c=0; c<num; c++) r.getSamples(0, 0, width, height, c, samps[c]);
+    return samps;
+  }
+
+  /**
+   * Unpacks an array of floats from field sample values.
+   *
+   * @param copy            Ignored (always returns a copy).
+   */
+  protected float[][] unpackFloats(boolean copy) throws VisADException {
+    pr ("unpackFloats(" + copy + ")");
+    // copy flag is ignored
+    Raster r = image.getRaster();
+    float[][] samps = new float[num][width * height];
+    for (int c=0; c<num; c++) r.getSamples(0, 0, width, height, c, samps[c]);
+    return samps;
+  }
+
+  protected double[] unpackValues(int s_index) throws VisADException {
+    pr ("unpackValues(" + s_index + ")");
+    Raster r = image.getRaster();
+    double[] samps = new double[num];
+    r.getPixel(s_index % width, s_index / width, samps);
+    return samps;
+  }
+
+  protected float[] unpackFloats(int s_index) throws VisADException {
+    pr ("unpackFloats(" + s_index + ")");
+    Raster r = image.getRaster();
+    float[] samps = new float[num];
+    r.getPixel(s_index % width, s_index / width, samps);
+    return samps;
+  }
+
+  protected double[] unpackOneRangeComp(int comp) throws VisADException {
+    pr ("unpackOneRangeComp(" + comp + ")");
+    Raster r = image.getRaster();
+    double[] samps = new double[width * height];
+    r.getSamples(0, 0, width, height, comp, samps);
+    return samps;
+  }
+
+  public Data getSample(int index) throws VisADException, RemoteException {
+    double[] v = unpackValues(index);
+    RealTupleType range = (RealTupleType) ((FunctionType) getType()).getRange();
+    return new RealTuple(range, v);
+  }
+
+  protected void pr(String message) {
+    if (DEBUG) {
+      String s = hashCode () + " " + getClass().getName () + "  " + message;
+      new Exception(s).printStackTrace();
+    }
+  }
+
+
+  // -- FlatFieldIface API methods --
+
+  public void setSamples(double[][] range, boolean copy)
+    throws RemoteException, VisADException
+  {
+    throw new VisADException("Use setImage(Image) for ImageFlatField");
+  }
+
+  public void setSamples(float[][] range, boolean copy)
+    throws RemoteException, VisADException
+  {
+    throw new VisADException("Use setImage(Image) for ImageFlatField");
+  }
+
+  public void setSamples(double[][] range, ErrorEstimate[] errors,
+    boolean copy) throws RemoteException, VisADException
+  {
+    throw new VisADException("Use setImage(Image) for ImageFlatField");
+  }
+
+  public void setSamples(int start, double[][] range)
+    throws RemoteException, VisADException
+  {
+    throw new VisADException("Use setImage(Image) for ImageFlatField");
+  }
+
+  public void setSamples(float[][] range, ErrorEstimate[] errors, boolean copy)
+    throws RemoteException, VisADException
+  {
+    throw new VisADException("Use setImage(Image) for ImageFlatField");
+  }
+
+  public byte[][] grabBytes() {
+    pr ("grabBytes");
+    byte[][] data = grabBytes(image);
+    if (data == null) return null;
+    if (data.length > num) {
+      byte[][] bytes = new byte[num][];
+      System.arraycopy(data, 0, bytes, 0, num);
+      data = bytes;
+    }
+    return data;
+  }
+
+  public static byte[][] grabBytes(BufferedImage image) {
+    WritableRaster raster = image.getRaster();
+    if (raster.getTransferType() != DataBuffer.TYPE_BYTE) return null;
+
+    DataBuffer buffer = raster.getDataBuffer();
+    if (buffer instanceof DataBufferByte) {
+      SampleModel model = raster.getSampleModel();
+      if (model instanceof BandedSampleModel) {
+        // fastest way to extract bytes; no copy
+        if (DEBUG) System.err.println("grabBytes: FAST");
+        byte[][] data = ((DataBufferByte) buffer).getBankData();
+        return data;
+      }
+      else if (model instanceof ComponentSampleModel) {
+        // medium speed way to extract bytes; direct array copy
+        if (DEBUG) System.err.println("grabBytes: MEDIUM");
+        byte[][] data = ((DataBufferByte) buffer).getBankData();
+        ComponentSampleModel csm = (ComponentSampleModel) model;
+        int[] bandOffsets = csm.getBandOffsets();
+        int[] bankIndices = csm.getBankIndices();
+        int pixelStride = csm.getPixelStride();
+        int scanlineStride = csm.getScanlineStride();
+        int numBands = bandOffsets.length;
+        int width = image.getWidth();
+        int height = image.getHeight();
+        int numPixels = width * height;
+        byte[][] bytes = new byte[numBands][numPixels];
+        for (int c=0; c<numBands; c++) {
+          for (int h=0; h<height; h++) {
+            for (int w=0; w<width; w++) {
+              int ndx = width * h + w;
+              int q = bandOffsets[c] + h * scanlineStride + w * pixelStride;
+              bytes[c][ndx] = data[bankIndices[c]][q];
+            }
+          }
+        }
+        return bytes;
+      } // model instanceof ComponentSampleModel
+    } // buffer instanceof DataBufferByte
+
+    if (DEBUG) System.err.println("grabBytes: make3ByteRGB");
+    return grabBytes(make3ByteRGB(image));
+/*
+    // slower, more general way to extract bytes; use PixelGrabber
+    // CTR NOTE - Something is fishy with this method of pixel extraction.
+    // Results do not seem to match those of the FAST or MEDIUM methods.
+    if (DEBUG) System.err.println("grabBytes: SLOW");
+    int numPixels = width * height;
+    int[] words = new int[numPixels];
+    PixelGrabber grabber = new PixelGrabber(
+      image.getSource(), 0, 0, width, height, words, 0, width);
+    try { grabber.grabPixels(); }
+    catch (InterruptedException e) { e.printStackTrace(); }
+
+    ColorModel cm = grabber.getColorModel();
+    byte[][] bytes = new byte[num][numPixels];
+    for (int i=0; i<numPixels; i++) {
+      int pixel = words[i];
+      int a = (pixel >> 24) & 0xff;
+      int r = (pixel >> 16) & 0xff;
+      int g = (pixel >> 8) & 0xff;
+      int b = pixel & 0xff;
+      bytes[0][i] = (byte) r;
+      if (num >= 2) bytes[1][i] = (byte) g;
+      if (num >= 3) bytes[2][i] = (byte) b;
+      if (num >= 4) bytes[3][i] = (byte) a;
+    }
+    return bytes;
+*/
+  }
+
+}
diff --git a/visad/Integer1DSet.java b/visad/Integer1DSet.java
new file mode 100644
index 0000000..a98f439
--- /dev/null
+++ b/visad/Integer1DSet.java
@@ -0,0 +1,63 @@
+//
+// Integer1DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Integer1DSet represents a finite set of samples of R at
+   an integer lattice based at the origin (i.e, 0, 1, 2, ..., length-1).<P>
+*/
+public class Integer1DSet extends Linear1DSet
+       implements IntegerSet {
+
+  /** a 1-D set with null errors and generic type */
+  public Integer1DSet(int length) throws VisADException {
+    this(RealType.Generic, length, null, null, null);
+  }
+
+  public Integer1DSet(MathType type, int length) throws VisADException {
+    this(type, length, null, null, null);
+  }
+
+  /** construct a 1-dimensional set with values {0, 1, ..., length-1};
+      coordinate_system and units must be compatible with defaults for
+      type, or may be null; errors may be null */
+  public Integer1DSet(MathType type, int length, CoordinateSystem coord_sys,
+                      Unit[] units, ErrorEstimate[] errors) throws VisADException {
+    super(type, 0.0, (double) (length - 1), length, coord_sys, units, errors);
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new Integer1DSet(type, Length, DomainCoordinateSystem,
+                            SetUnits, SetErrors);
+  }
+
+  public String longString(String pre) throws VisADException {
+    return pre + "Integer1DSet: Length = " + Length + "\n";
+  }
+
+}
+
diff --git a/visad/Integer2DSet.java b/visad/Integer2DSet.java
new file mode 100644
index 0000000..d8500b9
--- /dev/null
+++ b/visad/Integer2DSet.java
@@ -0,0 +1,112 @@
+//
+// Integer2DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ Integer2DSet represents a finite set of samples of R^2 at
+ an integer lattice based at the origin.<P>
+
+ The order of the samples is the rasterization of the orders of
+ the 1D components, with the first component increasing fastest.
+ For more detail, see the description in Linear2DSet.java.<P>
+*/
+public class Integer2DSet extends Linear2DSet
+       implements IntegerSet {
+
+  public Integer2DSet(MathType type, Integer1DSet[] sets) throws VisADException {
+    this(type, sets, null, null, null);
+  }
+
+  /** a 2-D set with null errors and generic type */
+  public Integer2DSet(int length1, int length2)
+         throws VisADException {
+    this(RealTupleType.Generic2D,
+         get_integer1d_array(RealTupleType.Generic2D, length1, length2, null),
+         null, null, null);
+  }
+
+  public Integer2DSet(MathType type, int length1, int length2)
+         throws VisADException {
+    this(type, get_integer1d_array(type, length1, length2, null),
+         null, null, null);
+  }
+
+  public Integer2DSet(MathType type, Integer1DSet[] sets,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    super(type, sets, coord_sys, units, errors);
+  }
+
+  /** construct a 2-dimensional set with values
+      {0, 1, ..., length1-1} x {0, 1, ..., length2-1};
+      coordinate_system and units must be compatible with defaults for
+      type, or may be null; errors may be null */
+  public Integer2DSet(MathType type, int length1, int length2,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, get_integer1d_array(type, length1, length2, units),
+         coord_sys, units, errors);
+  }
+
+  private static Integer1DSet[] get_integer1d_array(MathType type,
+                            int length1, int length2, Unit[] units)
+          throws VisADException {
+    type = Set.adjustType(type);
+    Integer1DSet[] sets = new Integer1DSet[2];
+    RealType[] types = new RealType[1];
+    SetType set_type;
+    Unit[] us = {null};
+
+    types[0] = (RealType) ((SetType) type).getDomain().getComponent(0);
+    set_type = new SetType(new RealTupleType(types));
+    if (units != null && units.length > 0) us[0] = units[0];
+    sets[0] = new Integer1DSet(set_type, length1, null, us, null);
+
+    types[0] = (RealType) ((SetType) type).getDomain().getComponent(1);
+    set_type = new SetType(new RealTupleType(types));
+    if (units != null && units.length > 1) us[0] = units[1];
+    sets[1] = new Integer1DSet(set_type, length2, null, us, null);
+
+    return sets;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    Integer1DSet[] sets = {(Integer1DSet) X.clone(),
+                           (Integer1DSet) Y.clone()};
+    return new Integer2DSet(type, sets, DomainCoordinateSystem,
+                            SetUnits, SetErrors);
+  }
+
+  public String longString(String pre) throws VisADException {
+    String s = pre + "Integer2DSet: Length = " + Length + "\n";
+    s = s + pre + "  Dimension 1: Length = " + X.getLength() + "\n";
+    s = s + pre + "  Dimension 2: Length = " + Y.getLength() + "\n";
+    return s;
+  }
+
+}
+
diff --git a/visad/Integer3DSet.java b/visad/Integer3DSet.java
new file mode 100644
index 0000000..50d37f5
--- /dev/null
+++ b/visad/Integer3DSet.java
@@ -0,0 +1,120 @@
+//
+// Integer3DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Integer3DSet represents a finite set of samples of R^3 at
+   an integer lattice based at the origin.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For more detail, see the description in Linear2DSet.java.<P>
+*/
+public class Integer3DSet extends Linear3DSet
+       implements IntegerSet {
+
+  public Integer3DSet(MathType type, Integer1DSet[] sets) throws VisADException {
+    this(type, sets, null, null, null);
+  }
+
+  /** a 3-D set with null errors and generic type */
+  public Integer3DSet(int length1, int length2, int length3)
+         throws VisADException {
+    this(RealTupleType.Generic3D,
+         get_integer1d_array(RealTupleType.Generic3D, length1, length2,
+                             length3, null),
+         null, null, null);
+  }
+
+  public Integer3DSet(MathType type, int length1, int length2, int length3)
+         throws VisADException {
+    this(type, get_integer1d_array(type, length1, length2, length3, null),
+         null, null, null);
+  }
+
+  public Integer3DSet(MathType type, Integer1DSet[] sets,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    super(type, sets, coord_sys, units, errors);
+  }
+
+  /** construct a 3-dimensional set with values {0, 1, ..., length1-1}
+      x {0, 1, ..., length2-1} x {0, 1, ..., length3-1};
+      coordinate_system and units must be compatible with defaults for
+      type, or may be null; errors may be null */
+  public Integer3DSet(MathType type, int length1, int length2, int length3,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, get_integer1d_array(type, length1, length2, length3, units),
+         coord_sys, units, errors);
+  }
+
+  private static Integer1DSet[] get_integer1d_array(MathType type,
+             int length1, int length2, int length3, Unit[] units)
+          throws VisADException {
+    type = Set.adjustType(type);
+    Integer1DSet[] sets = new Integer1DSet[3];
+    RealType[] types = new RealType[1];
+    SetType set_type;
+    Unit[] us = {null};
+
+    types[0] = (RealType) ((SetType) type).getDomain().getComponent(0);
+    set_type = new SetType(new RealTupleType(types));
+    if (units != null && units.length > 0) us[0] = units[0];
+    sets[0] = new Integer1DSet(set_type, length1, null, us, null);
+
+    types[0] = (RealType) ((SetType) type).getDomain().getComponent(1);
+    set_type = new SetType(new RealTupleType(types));
+    if (units != null && units.length > 1) us[0] = units[1];
+    sets[1] = new Integer1DSet(set_type, length2, null, us, null);
+
+    types[0] = (RealType) ((SetType) type).getDomain().getComponent(2);
+    set_type = new SetType(new RealTupleType(types));
+    if (units != null && units.length > 2) us[0] = units[2];
+    sets[2] = new Integer1DSet(set_type, length3, null, us, null);
+
+    return sets;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    Integer1DSet[] sets = {(Integer1DSet) X.clone(),
+                           (Integer1DSet) Y.clone(),
+                           (Integer1DSet) Z.clone()};
+    return new Integer3DSet(type, sets, DomainCoordinateSystem,
+                            SetUnits, SetErrors);
+  }
+
+  public String longString(String pre) throws VisADException {
+    String s = pre + "Integer3DSet: Length = " + Length + "\n";
+    s = s + pre + "  Dimension 1: Length = " + X.getLength() + "\n";
+    s = s + pre + "  Dimension 2: Length = " + Y.getLength() + "\n";
+    s = s + pre + "  Dimension 3: Length = " + Z.getLength() + "\n";
+    return s;
+  }
+
+}
+
diff --git a/visad/IntegerNDSet.java b/visad/IntegerNDSet.java
new file mode 100644
index 0000000..a1fc1ac
--- /dev/null
+++ b/visad/IntegerNDSet.java
@@ -0,0 +1,157 @@
+//
+// IntegerNDSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   IntegerNDSet represents a finite set of samples of R^n at
+   an integer lattice based at the origin.<P>
+*/
+public class IntegerNDSet extends LinearNDSet
+       implements IntegerSet {
+
+  /** an N-D set with null errors and generic type */
+  public IntegerNDSet(int[] lengths) throws VisADException {
+    this(get_generic_type(lengths), lengths, null, null, null);
+  }
+
+  public IntegerNDSet(MathType type, int[] lengths) throws VisADException {
+    this(type, lengths, null, null, null);
+  }
+
+  /** construct an N-dimensional set with values in the cross product
+      of {0, 1, ..., lengths[i]-1}
+      for i=0, ..., lengths[lengths.length-1];
+      coordinate_system and units must be compatible with defaults for
+      type, or may be null; errors may be null */
+  public IntegerNDSet(MathType type, int[] lengths,
+                    CoordinateSystem coord_sys, Unit[] units,
+                    ErrorEstimate[] errors) throws VisADException {
+    super(type, LinearNDSet.get_linear1d_array(type, get_firsts(lengths),
+                                               get_lasts(lengths), lengths,
+                                               units),
+          coord_sys, units, errors);
+  }
+
+  public IntegerNDSet(MathType type, Integer1DSet[] sets) throws VisADException {
+    this(type, sets, null, null, null);
+  }
+
+  /** construct an N-dimensional set with values in the cross product
+      of {0, 1, ..., lengths[i]-1}
+      for i=0, ..., lengths[lengths.length-1];
+      coordinate_system and units must be compatible with defaults for
+      type, or may be null; errors may be null */
+  public IntegerNDSet(MathType type, Integer1DSet[] sets,
+                    CoordinateSystem coord_sys, Unit[] units,
+                    ErrorEstimate[] errors) throws VisADException {
+    super(type, sets, coord_sys, units, errors);
+  }
+
+  /**
+   * Abreviated factory method for creating the proper integer set
+   * (Integer1DSet, Integer2DSet, etc.).
+   */
+  public static GriddedSet create(MathType type, int[] lengths)
+         throws VisADException {
+    return create(type, lengths, null, null, null);
+  }
+
+  /**
+   * General factory method for creating the proper integer set
+   * (Integer1DSet, Integer2DSet, etc.).
+   */
+  public static GriddedSet create(MathType type, int[] lengths,
+                                  CoordinateSystem coord_sys, Unit[] units,
+                                  ErrorEstimate[] errors)
+         throws VisADException {
+    switch (lengths.length) {
+      case 1:
+        return new Integer1DSet(type, lengths[0],
+                                coord_sys, units, errors);
+      case 2:
+        return new Integer2DSet(type, lengths[0], lengths[1],
+                                coord_sys, units, errors);
+      case 3:
+        return new Integer3DSet(type, lengths[0], lengths[1], lengths[2],
+                                coord_sys, units, errors);
+      default:
+        return new IntegerNDSet(type, lengths,
+                              coord_sys, units, errors);
+    }
+  }
+
+  private static SetType get_generic_type(int[] lengths)
+          throws VisADException {
+    if (lengths == null || lengths.length == 0) {
+      throw new SetException("IntegerNDSet: bad lengths");
+    }
+    int n = lengths.length;
+    RealType[] reals = new RealType[n];
+    for (int i=0; i<n; i++) reals[i] = RealType.Generic;
+    return new SetType(new RealTupleType(reals));
+  }
+
+  private static double[] get_firsts(int[] lengths) {
+    double[] firsts = new double[lengths.length];
+    for (int j=0; j<lengths.length; j++) firsts[j] = 0.0;
+    return firsts;
+  }
+
+  private static double[] get_lasts(int[] lengths) {
+    double[] lasts = new double[lengths.length];
+    for (int j=0; j<lengths.length; j++) lasts[j] = (double) (lengths[j] - 1);
+    return lasts;
+  }
+
+  private static int[] get_lengths(Real[] lengths) {
+    int[] ss = new int[lengths.length];
+    for (int j=0; j<lengths.length; j++) ss[j] = (int) lengths[j].getValue();
+    return ss;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    int[] lens = new int[DomainDimension];
+    for (int j=0; j<DomainDimension; j++) {
+      lens[j] = L[j].getLength();
+    }
+    return new IntegerNDSet(type, lens, DomainCoordinateSystem,
+                          SetUnits, SetErrors);
+  }
+
+  public String longString(String pre) throws VisADException {
+    String s = pre + "IntegerNDSet: Dimension = " +
+               DomainDimension + " Length = " + Length + "\n";
+    for (int j=0; j<DomainDimension; j++) {
+      // s = s + pre + "  Dimension " + j + ":" + " Length = " +
+      //     L[j].getLength() + "\n";
+      s = s + pre + "  Dimension " + j + ":" + " Linear1DSet = " + L[j] + "\n";
+    }
+    return s;
+  }
+
+}
+
diff --git a/visad/IntegerSet.java b/visad/IntegerSet.java
new file mode 100644
index 0000000..0213737
--- /dev/null
+++ b/visad/IntegerSet.java
@@ -0,0 +1,36 @@
+//
+// IntegerSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   IntegerSet is an interface for finite sets of samples of
+   R^n at an integer lattice based at the origin.<P>
+*/
+public interface IntegerSet {
+
+}
+
diff --git a/visad/InverseCoordinateSystem.java b/visad/InverseCoordinateSystem.java
new file mode 100644
index 0000000..ece84c3
--- /dev/null
+++ b/visad/InverseCoordinateSystem.java
@@ -0,0 +1,101 @@
+//
+// InverseCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   InverseCoordinateSystem is the VisAD CoordinateSystem class
+   for inverting other CoordinateSystems.<P>
+*/
+public class InverseCoordinateSystem extends CoordinateSystem {
+
+  private CoordinateSystem inverse;
+  private int dimension;
+
+  /** construct a CoordinateSystem that whose transforms invert
+      the transforms of inverse (i.e., toReference and
+      fromReference are switched); for example, this could be
+      used to define Cartesian coordinates releative to a
+      refernce in spherical coordinates */
+  public InverseCoordinateSystem(RealTupleType reference, CoordinateSystem inv)
+         throws VisADException {
+    super(reference, inv.getReference().getDefaultUnits());
+    inverse = inv;
+    dimension = reference.getDimension();
+    Unit[] inv_units = inv.getCoordinateSystemUnits();
+    Unit[] ref_units = reference.getDefaultUnits();
+    if (inv_units.length != dimension) {
+      throw new CoordinateSystemException("InverseCoordinateSystem: " +
+                                          "dimensions don't match");
+    }
+    for (int i=0; i<inv_units.length; i++) {
+      if ((inv_units[i] == null && ref_units[i] != null) ||
+          (inv_units[i] != null && !inv_units[i].equals(ref_units[i]))) {
+        throw new CoordinateSystemException("InverseCoordinateSystem: " +
+          "Units don't match " + i + " " + inv_units[i] + " " + ref_units[i]);
+      }
+    }
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("InverseCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    return inverse.fromReference(tuples);
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("InverseCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    return inverse.toReference(tuples);
+  }
+
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("InverseCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    return inverse.fromReference(tuples);
+  }
+
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != dimension) {
+      throw new CoordinateSystemException("InverseCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    return inverse.toReference(tuples);
+  }
+
+  public boolean equals(Object cs) {
+    return (cs instanceof InverseCoordinateSystem &&
+            inverse.equals(((InverseCoordinateSystem) cs).inverse));
+  }
+
+}
+
diff --git a/visad/InverseLinearScaledCS.java b/visad/InverseLinearScaledCS.java
new file mode 100644
index 0000000..8d21755
--- /dev/null
+++ b/visad/InverseLinearScaledCS.java
@@ -0,0 +1,54 @@
+//
+// InverseLinearScaledCS.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   An interface which CoordinateSystems can implement to indicate that 
+   they invert another CS, but the transforms linearly scale the CS being
+   inverted.  For example, this.toReference linearly scales CS.fromReference
+   according to coeffs returned by the getScale/Offset methods. 
+   This could be used by a DataRenderer when the Display 
+   CoordinateSystem: (Lon,Lat->Display.X,Y) inverts a Data CoordinateSystem:
+   (line,elem -> Lon, Lat).
+*/
+
+
+public interface InverseLinearScaledCS {
+
+  /**
+   * The linear scale/offset coefficients
+   */
+  public double[] getScale();
+
+  public double[] getOffset();
+
+  /**
+   *  The CoordinateSystem being inverted
+   *
+   */
+  public CoordinateSystem getInvertedCoordinateSystem();
+}
diff --git a/visad/Irregular1DSet.java b/visad/Irregular1DSet.java
new file mode 100644
index 0000000..64e77a0
--- /dev/null
+++ b/visad/Irregular1DSet.java
@@ -0,0 +1,252 @@
+//
+// Irregular1DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Irregular1DSet represents a finite set of samples of R.
+   Note that Irregular1DSet reorders samples given in the
+   constructor;  to convert between values and indices,
+   always use the methods valueToIndex and indexToValue.<P>
+*/
+public class Irregular1DSet extends IrregularSet {
+
+  float LowX, HiX;
+
+  Gridded1DSet SortedSet;
+
+  /** a 1-D irregular set with null errors, CoordinateSystem
+      and Units are defaults from type */
+  public Irregular1DSet(MathType type, float[][] samples)
+         throws VisADException {
+    this(type, samples, null, null, null, true);
+  }
+
+  /** a 1-D irregular set; samples array is organized
+      float[1][number_of_samples]; samples need not be
+      sorted - the constructor sorts samples to define
+      a 1-D "triangulation";
+      coordinate_system and units must be compatible with
+      defaults for type, or may be null; errors may be null */
+  public Irregular1DSet(MathType type, float[][] samples,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, coord_sys, units, errors, true);
+  }
+
+  public Irregular1DSet(MathType type, float[][] samples,
+                 CoordinateSystem coord_sys, Unit[] units,
+                 ErrorEstimate[] errors, boolean copy)
+         throws VisADException {
+    super(type, samples, samples.length, coord_sys,
+          units, errors, null, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+
+    // sort samples so that creation of a Gridded1DSet is possible
+    float[][] sortedSamples = new float[1][Length];
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<Length; i++) {
+      sortedSamples[0][i] = mySamples[0][i];
+    }
+    newToOld = QuickSort.sort(sortedSamples[0]);
+    oldToNew = new int[Length];
+    for (int i=0; i<Length; i++) oldToNew[newToOld[i]] = i;
+    SortedSet = new Gridded1DSet(type, sortedSamples, Length,
+                                 coord_sys, units, errors, false);
+  }
+
+  public Set makeSpatial(SetType type, float[][] samples) throws VisADException {
+    if (samples.length == 3) {
+      return new Irregular3DSet(type, samples, newToOld, oldToNew,
+                                null, null, null, false);
+    }
+    else if (samples.length == 2) {
+      return new Irregular2DSet(type, samples, newToOld, oldToNew,
+                                null, null, null, false);
+    }
+    else if (samples.length == 1) {
+      // may need new sort (CoordinateSystem may change order)
+      return new Irregular1DSet(type, samples, null, null, null, false);
+    }
+    else {
+      throw new SetException("Irregular1DSet.makeSpatial: bad samples length");
+    }
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int[] newIndex = new int[index.length];
+    for (int i=0; i<index.length; i++) {
+      newIndex[i] =
+          (0 <= index[i] && index[i] < Length) ? oldToNew[index[i]] : -1;
+    }
+    float[][] value = SortedSet.indexToValue(newIndex);
+    return value;
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    int[] index = SortedSet.valueToIndex(value);
+    int[] newIndex = new int[index.length];
+    for (int i=0; i<index.length; i++) {
+      newIndex[i] = (index[i] == -1) ? -1 : newToOld[index[i]];
+    }
+    return newIndex;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if no interpolation is possible */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float[][] weights) throws VisADException {
+    SortedSet.valueToInterp(value, indices, weights);
+    for (int j=0; j<indices.length; j++) {
+      if (indices[j] != null) {
+        int[] newIndex = new int[indices[j].length];
+        for (int i=0; i<indices[j].length; i++) newIndex[i] = newToOld[indices[j][i]];
+        indices[j] = newIndex;
+      }
+    }
+  }
+
+  public float getLowX() {
+    return LowX;
+  }
+
+  public float getHiX() {
+    return HiX;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+      return new Irregular1DSet(type, getMySamples(), DomainCoordinateSystem,
+                             SetUnits, SetErrors);
+  }
+
+  /* run 'java visad.Irregular1DSet' to test the Irregular1DSet class */
+  public static void main(String[] argv) throws VisADException {
+    // set up samples
+    float[][] samp = { {130, 55, 37, 28, 61, 40, 104, 52, 65, 12} };
+    int length = samp[0].length;
+    int[] index = new int[length];
+    for (int i=0; i<length; i++) {
+      index[i] = i;
+    }
+    // instantiate Irregular1DSet
+    RealType test1 = RealType.getRealType("x");
+    RealType[] t_array = {test1};
+    RealTupleType t_tuple = new RealTupleType(t_array);
+    Irregular1DSet iSet1D = new Irregular1DSet(t_tuple, samp);
+
+    // print out samples and test indexToValue
+    System.out.println("Samples (indexToValue test):");
+    float[][] value = iSet1D.indexToValue(index);
+    for (int i=0; i<iSet1D.Length; i++) {
+      System.out.println("#"+i+":\t"+value[0][i]);
+    }
+
+    for (int i=0; i<iSet1D.Length; i++) {
+      System.out.println("#"+i+":\t"+iSet1D.oldToNew[i]+" "+iSet1D.newToOld[i]);
+    }
+
+    // test valueToIndex
+    System.out.println("\nvalueToIndex test:");
+    float[][] value2 = { {10, 20,  30,  40,  50,  60, 70,
+                           80, 90, 100, 110, 120, 130} };
+    int[] index2 = iSet1D.valueToIndex(value2);
+    for (int i=0; i<index2.length; i++) {
+      System.out.println(value2[0][i]+"\t--> "+index2[i]);
+    }
+
+    // test valueToInterp
+    System.out.println("\nvalueToInterp test:");
+    int[][] indices = new int[value2[0].length][];
+    float[][] weights = new float[value2[0].length][];
+    iSet1D.valueToInterp(value2, indices, weights);
+    for (int i=0; i<value2[0].length; i++) {
+      System.out.print(value2[0][i]+"\t--> ["+indices[i][0]);
+      for (int j=1; j<indices[i].length; j++) {
+        System.out.print(", "+indices[i][j]);
+      }
+      System.out.print("]\tweight total: ");
+      float total = 0;
+      for (int j=0; j<weights[i].length; j++) {
+        total += weights[i][j];
+      }
+      System.out.println(total);
+    }
+  }
+
+/* Here's the output:
+
+iris 56% java visad.Irregular1DSet
+Samples (indexToValue test):
+#0:     12.0
+#1:     28.0
+#2:     37.0
+#3:     40.0
+#4:     52.0
+#5:     55.0
+#6:     61.0
+#7:     65.0
+#8:     104.0
+#9:     130.0
+
+valueToIndex test:
+10.0    --> 0
+20.0    --> 1
+30.0    --> 1
+40.0    --> 3
+50.0    --> 4
+60.0    --> 6
+70.0    --> 7
+80.0    --> 7
+90.0    --> 8
+100.0   --> 8
+110.0   --> 8
+120.0   --> 9
+130.0   --> 9
+
+valueToInterp test:
+10.0    --> [0, 0]      weight total: 1.0
+20.0    --> [1, 1, 0]   weight total: 1.0
+30.0    --> [1, 1, 2]   weight total: 1.0
+40.0    --> [3, 3, 4]   weight total: 1.0
+50.0    --> [4, 4, 3]   weight total: 1.0
+60.0    --> [6, 6, 5]   weight total: 1.0
+70.0    --> [7, 7, 8]   weight total: 1.0
+80.0    --> [7, 7, 8]   weight total: 1.0
+90.0    --> [8, 8, 7]   weight total: 1.0
+100.0   --> [8, 8, 7]   weight total: 1.0
+110.0   --> [8, 8, 9]   weight total: 1.0
+120.0   --> [9, 9, 8]   weight total: 1.0
+130.0   --> [9, 9]      weight total: 1.0
+iris 57%
+
+*/
+
+}
+
diff --git a/visad/Irregular2DSet.java b/visad/Irregular2DSet.java
new file mode 100644
index 0000000..b209a26
--- /dev/null
+++ b/visad/Irregular2DSet.java
@@ -0,0 +1,543 @@
+//
+// Irregular2DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * <P>{@link IrregularSet} for a finite number of samples of R.</P>
+ *
+ * <P>NOTE: There is no {@link Irregular2DSet} with a manifold dimension equal
+ * to one.  Use {@link Gridded2DSet} with a manifold dimension equal to one
+ * instead.</P>
+ *
+ * <p>When you call an {@link Irregular2DSet} constructor without a {@link
+ * Delaunay} argument, the constructor uses the {@link Delaunay#factory(float[][], boolean)}
+ * method to implictly compute a Delaunay triangulation. 3000 points is the
+ * current break-point from Watson's algorithm to Clarkson's algorithm. So,
+ * currently, at 3001 points you start using Clarkson's algorithm, which rounds
+ * coordinates to integers.  If your values are small enough that integer
+ * rounding will merge some of them to the same value (and hence create
+ * colinear or colocated points), there will be trouble. One approach is
+ * to scale your coordinates up so integer rounding does not merge values.
+ * Another is to ensure that you use Watson's algorithm by using <code>new
+ * DelaunayWatson(samples)</code> as the {@link Delaunay} argument of the {@link
+ * Irregular2DSet} constructor.</p>
+ */
+public class Irregular2DSet extends IrregularSet {
+
+  private float LowX, HiX, LowY, HiY;
+
+  /** a 2-D irregular set with null errors, CoordinateSystem
+      and Units are defaults from type; topology is computed
+      by the constructor */
+  public Irregular2DSet(MathType type, float[][] samples)
+         throws VisADException {
+    this(type, samples, null, null, null, null, true);
+  }
+
+  /** a 2-D irregular set; samples array is organized
+      float[2][number_of_samples];  no geometric constraint on
+      samples; if delan is non-null it defines the topology of
+      samples (which must have manifold dimension 2), else the
+      constructor computes a topology with manifold dimension 2;
+      note that Gridded2DSet can be used for an irregular set
+      with domain dimension 2 and manifold dimension 1;
+      coordinate_system and units must be compatible with
+      defaults for type, or may be null; errors may be null */
+  public Irregular2DSet(MathType type, float[][] samples,
+                        CoordinateSystem coord_sys, Unit[] units,
+                        ErrorEstimate[] errors, Delaunay delan)
+                                          throws VisADException {
+    this(type, samples, coord_sys, units, errors, delan, true);
+  }
+
+  public Irregular2DSet(MathType type, float[][] samples,
+                        CoordinateSystem coord_sys, Unit[] units,
+                        ErrorEstimate[] errors, Delaunay delan,
+                        boolean copy) throws VisADException {
+    super(type, samples, samples.length, coord_sys,
+          units, errors, delan, copy);
+    if (samples.length != 2) {
+      throw new SetException("Irregular2DSet: ManifoldDimension " +
+                             "must be 2 for this constructor");
+    }
+    LowX = Low[0];
+    HiX = Hi[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    oldToNew = null;
+    newToOld = null;
+  }
+
+  /* shortcut constructor for constructing Irregular2DSet
+     using Delaunay from existing Irregular2DSet */
+/* CTR: 1-12-98
+  public Irregular2DSet(MathType type, float[][] samples,
+                 Irregular2DSet delaunay_set) throws VisADException {
+    this(type, samples, delaunay_set, null, null, null, true);
+  }
+*/
+
+  /* complete constructor for constructing Irregular2DSet
+     using Delaunay from existing Irregular2DSet */
+/* CTR: 1-12-98
+  public Irregular2DSet(MathType type, float[][] samples,
+                        Irregular2DSet delaunay_set,
+                        CoordinateSystem coord_sys, Unit[] units,
+                        ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, delaunay_set, coord_sys, units, errors, true);
+  }
+
+  public Irregular2DSet(MathType type, float[][] samples,
+                        Irregular2DSet delaunay_set,
+                        CoordinateSystem coord_sys, Unit[] units,
+                        ErrorEstimate[] errors, boolean copy)
+                        throws VisADException {
+    super(type, samples, delaunay_set.getManifoldDimension(),
+          coord_sys, units, errors, copy);
+    int dim = delaunay_set.getManifoldDimension();
+    if (dim != 2) {
+      throw new SetException("Irregular2DSet: delaunay_set ManifoldDimension " +
+                             "must be 2");
+    }
+    if (Length != delaunay_set.Length) {
+      throw new SetException("Irregular2DSet: delaunay_set length not match");
+    }
+    Delan = delaunay_set.Delan;
+    LowX = Low[0];
+    HiX = Hi[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    oldToNew = null;
+    newToOld = null;
+  }
+*/
+
+  /** shortcut constructor for constructing Irregular2DSet
+      using sort from existing Irregular1DSet */
+  public Irregular2DSet(MathType type, float[][] samples,
+               int[] new2old, int[] old2new) throws VisADException {
+    this(type, samples, new2old, old2new, null, null, null, true);
+  }
+
+  /** complete constructor for constructing Irregular2DSet
+      using sort from existing Irregular1DSet */
+  public Irregular2DSet(MathType type, float[][] samples,
+                        int[] new2old, int[] old2new,
+                        CoordinateSystem coord_sys, Unit[] units,
+                        ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, new2old, old2new, coord_sys, units, errors, true);
+  }
+
+  public Irregular2DSet(MathType type, float[][] samples,
+                        int[] new2old, int[] old2new,
+                        CoordinateSystem coord_sys, Unit[] units,
+                        ErrorEstimate[] errors, boolean copy)
+                        throws VisADException {
+    super(type, samples, 1, coord_sys, units, errors, null, copy);
+    if (Length != new2old.length || Length != old2new.length) {
+      throw new SetException("Irregular2DSet: sort lengths do not match");
+    }
+    newToOld = new int[Length];
+    oldToNew = new int[Length];
+    System.arraycopy(new2old, 0, newToOld, 0, Length);
+    System.arraycopy(old2new, 0, oldToNew, 0, Length);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    Delan = null;
+  }
+
+  public Set makeSpatial(SetType type, float[][] samples) throws VisADException {
+    if (samples.length == 3) {
+      if (ManifoldDimension == 1) {
+        return new Irregular3DSet(type, samples, newToOld, oldToNew,
+                                  null, null, null, false);
+      }
+      else {
+/* WLH 15 Dec 98
+        if (Delan.Tri == null || Delan.Tri.length == 0) return null;
+*/
+        if (Delan == null || Delan.Tri == null || Delan.Tri.length == 0) return null;
+        return new Irregular3DSet(type, samples, null, null, null,
+                                  Delan, false);
+      }
+    }
+    else if (samples.length == 2) {
+      if (ManifoldDimension == 1) {
+        return new Irregular2DSet(type, samples, newToOld, oldToNew,
+                                  null, null, null, false);
+      }
+      else {
+/* WLH 15 Dec 98
+        if (Delan.Tri == null || Delan.Tri.length == 0) return null;
+*/
+        if (Delan == null || Delan.Tri == null || Delan.Tri.length == 0) return null;
+        return new Irregular2DSet(type, samples, null, null, null,
+                                  Delan, false);
+      }
+    }
+    else {
+      throw new SetException("Irregular2DSet.makeSpatial: bad samples length");
+    }
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    float[][] value = new float[2][index.length];
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<index.length; i++) {
+      if ( (index[i] >= 0) && (index[i] < Length) ) {
+        value[0][i] = mySamples[0][index[i]];
+        value[1][i] = mySamples[1][index[i]];
+      }
+      else {
+        value[0][i] = value[1][i] = Float.NaN;
+      }
+    }
+    return value;
+  }
+
+  /** valueToTri returns an array of containing triangles given
+      an array of points in R^DomainDimension */
+  public int[] valueToTri(float[][] value) throws VisADException {
+    if (ManifoldDimension != 2) {
+      throw new SetException("Irregular2DSet.valueToTri: " +
+                             "ManifoldDimension must be 2, not " +
+                             ManifoldDimension);
+    }
+    int length = value[0].length;
+    if (length != value[1].length) {
+      throw new SetException("Irregular2DSet.valueToTri: lengths " +
+                             "don't match");
+    }
+    if (Delan == null) {
+      throw new SetException("Irregular2DSet.valueToTri: triangulation " +
+                             "undefined");
+    }
+    int[] tri = new int[length];
+    int curtri = 0;
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<length; i++) {
+      // Return -1 if iteration loop fails
+      tri[i] = -1;
+      boolean foundit = false;
+      if (curtri < 0) curtri = 0;
+      for (int itnum=0; (itnum<Delan.Tri.length) && !foundit; itnum++) {
+        // define data
+        int t0 = Delan.Tri[curtri][0];
+        int t1 = Delan.Tri[curtri][1];
+        int t2 = Delan.Tri[curtri][2];
+        float Ax = mySamples[0][t0];
+        float Ay = mySamples[1][t0];
+        float Bx = mySamples[0][t1];
+        float By = mySamples[1][t1];
+        float Cx = mySamples[0][t2];
+        float Cy = mySamples[1][t2];
+        float Px = value[0][i];
+        float Py = value[1][i];
+
+        // tests whether point is contained in current triangle
+        float tval0 = (Bx-Ax)*(Py-Ay) - (By-Ay)*(Px-Ax);
+        float tval1 = (Cx-Bx)*(Py-By) - (Cy-By)*(Px-Bx);
+        float tval2 = (Ax-Cx)*(Py-Cy) - (Ay-Cy)*(Px-Cx);
+        boolean test0 = (tval0 == 0) || ( (tval0 > 0) == (
+                        (Bx-Ax)*(Cy-Ay) - (By-Ay)*(Cx-Ax) > 0) );
+        boolean test1 = (tval1 == 0) || ( (tval1 > 0) == (
+                        (Cx-Bx)*(Ay-By) - (Cy-By)*(Ax-Bx) > 0) );
+        boolean test2 = (tval2 == 0) || ( (tval2 > 0) == (
+                        (Ax-Cx)*(By-Cy) - (Ay-Cy)*(Bx-Cx) > 0) );
+
+        // flip to prevent tight loop of two triangles in
+        // degenerate triangulation
+        int it2 = itnum / 2;
+        boolean flip = ((it2 % 2) == 0);
+        // figure out which triangle to go to next
+        if (!test0 && !test1 && !test2) curtri = -1;
+        else if (!test0 && !test1) {
+          if (flip) {
+            int nextri = Delan.Walk[curtri][1];
+            if (nextri >= 0) curtri = nextri;
+            else curtri = Delan.Walk[curtri][0];
+          }
+          else {
+            int nextri = Delan.Walk[curtri][0];
+            if (nextri >= 0) curtri = nextri;
+            else curtri = Delan.Walk[curtri][1];
+          }
+        }
+        else if (!test1 && !test2) {
+          if (flip) {
+            int nextri = Delan.Walk[curtri][2];
+            if (nextri >= 0) curtri = nextri;
+            else curtri = Delan.Walk[curtri][1];
+          }
+          else {
+            int nextri = Delan.Walk[curtri][1];
+            if (nextri >= 0) curtri = nextri;
+            else curtri = Delan.Walk[curtri][2];
+          }
+        }
+        else if (!test2 && !test0) {
+          if (flip) {
+            int nextri = Delan.Walk[curtri][0];
+            if (nextri >= 0) curtri = nextri;
+            else curtri = Delan.Walk[curtri][2];
+          }
+          else {
+            int nextri = Delan.Walk[curtri][2];
+            if (nextri >= 0) curtri = nextri;
+            else curtri = Delan.Walk[curtri][0];
+          }
+        }
+        else if (!test0) curtri = Delan.Walk[curtri][0];
+        else if (!test1) curtri = Delan.Walk[curtri][1];
+        else if (!test2) curtri = Delan.Walk[curtri][2];
+        else foundit = true;
+
+        // Return -1 if outside of the convex hull
+        if (curtri < 0) foundit = true;
+        if (foundit) tri[i] = curtri;
+      }
+    }
+    return tri;
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Irregular2DDSet.valueToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    float[][]mySamples = getMySamples();
+    int[] tri = valueToTri(value);
+    int[] index = new int[tri.length];
+    for (int i=0; i<tri.length; i++) {
+      if (tri[i] < 0) {
+        index[i] = -1;
+      }
+      else {
+        // current values
+        float x = value[0][i];
+        float y = value[1][i];
+
+        // triangle indices
+        int t = tri[i];
+        int t0 = Delan.Tri[t][0];
+        int t1 = Delan.Tri[t][1];
+        int t2 = Delan.Tri[t][2];
+
+        // partial distances
+        float D00 = mySamples[0][t0] - x;
+        float D01 = mySamples[1][t0] - y;
+        float D10 = mySamples[0][t1] - x;
+        float D11 = mySamples[1][t1] - y;
+        float D20 = mySamples[0][t2] - x;
+        float D21 = mySamples[1][t2] - y;
+
+        // distances squared
+        float Dsq0 = D00*D00 + D01*D01;
+        float Dsq1 = D10*D10 + D11*D11;
+        float Dsq2 = D20*D20 + D21*D21;
+
+        // find the minimum distance
+        float min = Math.min(Dsq0, Dsq1);
+        min = Math.min(min, Dsq2);
+        if (min == Dsq0) index[i] = t0;
+        else if (min == Dsq1) index[i] = t1;
+        else index[i] = t2;
+      }
+    }
+    return index;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if no interpolation is possible */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float[][] weights) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Irregular2DDSet.valueToInterp: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if ( (indices.length < length) || (weights.length < length) ) {
+      throw new SetException(
+                       "Irregular2DSet.valueToInterp: lengths don't match");
+    }
+    float[][]mySamples = getMySamples();
+    int[] tri = valueToTri(value);
+    for (int i=0; i<tri.length; i++) {
+      if (tri[i] < 0) {
+        indices[i] = null;
+        weights[i] = null;
+      }
+      else {
+        // indices and weights sub-arrays
+        int[] ival = new int[3];
+        float[] wval = new float[3];
+        // current values
+        float x = value[0][i];
+        float y = value[1][i];
+
+        // triangle indices
+        int t = tri[i];
+        int t0 = Delan.Tri[t][0];
+        int t1 = Delan.Tri[t][1];
+        int t2 = Delan.Tri[t][2];
+        ival[0] = t0;
+        ival[1] = t1;
+        ival[2] = t2;
+
+        // triangle vertices
+        float x0 = mySamples[0][t0];
+        float y0 = mySamples[1][t0];
+        float x1 = mySamples[0][t1];
+        float y1 = mySamples[1][t1];
+        float x2 = mySamples[0][t2];
+        float y2 = mySamples[1][t2];
+
+        // perpendicular lines
+        float C0x = y2-y1;
+        float C0y = x1-x2;
+        float C1x = y2-y0;
+        float C1y = x0-x2;
+        float C2x = y1-y0;
+        float C2y = x0-x1;
+
+        // weights
+        wval[0] = ( ( (x - x1)*C0x) + ( (y - y1)*C0y) )
+                / ( ((x0 - x1)*C0x) + ((y0 - y1)*C0y) );
+        wval[1] = ( ( (x - x0)*C1x) + ( (y - y0)*C1y) )
+                / ( ((x1 - x0)*C1x) + ((y1 - y0)*C1y) );
+        wval[2] = ( ( (x - x0)*C2x) + ( (y - y0)*C2y) )
+                / ( ((x2 - x0)*C2x) + ((y2 - y0)*C2y) );
+
+        // fill in arrays
+        indices[i] = ival;
+        weights[i] = wval;
+      }
+    }
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    if (ManifoldDimension == 1) {
+	return new Irregular2DSet(type, getMySamples(), newToOld, oldToNew,
+                            DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+    else {
+	return new Irregular2DSet(type, getMySamples(), DomainCoordinateSystem,
+                                SetUnits, SetErrors, Delan);
+    }
+  }
+
+  /* run 'java visad.Irregular2DSet' to test the Irregular2DSet class */
+  public static void main(String[] argv) throws VisADException {
+    float[][] samp = { {139, 357, 416, 276, 495, 395, 578, 199},
+                        {102,  44, 306, 174, 108, 460, 333, 351} };
+    RealType test1 = RealType.getRealType("x");
+    RealType test2 = RealType.getRealType("y");
+    RealType[] t_array = {test1, test2};
+    RealTupleType t_tuple = new RealTupleType(t_array);
+    Irregular2DSet iSet2D = new Irregular2DSet(t_tuple, samp);
+
+   
+    // print out Samples information
+    System.out.println("Samples:");
+    float[][]mySamples = iSet2D.getMySamples();
+    for (int i=0; i<mySamples[0].length; i++) {
+      System.out.println("#"+i+":\t"+mySamples[0][i]
+                               +", "+mySamples[1][i]);
+    }
+    System.out.println();
+
+    // test valueToIndex function
+    System.out.println("valueToIndex test:");
+    float[][] value = { {164, 287, 311, 417, 522, 366, 445},
+                         {131, 323,  90, 264, 294, 421,  91} };
+    int[] index = iSet2D.valueToIndex(value);
+    for (int i=0; i<index.length; i++) {
+      System.out.println(value[0][i]+", "
+                        +value[1][i]+"\t--> #"+index[i]);
+    }
+    System.out.println();
+
+    // test valueToInterp function
+    System.out.println("valueToInterp test:");
+    int[][] indices = new int[value[0].length][];
+    float[][] weights = new float[value[0].length][];
+    iSet2D.valueToInterp(value, indices, weights);
+    for (int i=0; i<value[0].length; i++) {
+      System.out.println(value[0][i]+", "+value[1][i]+"\t--> ["
+                        +indices[i][0]+", "
+                        +indices[i][1]+", "
+                        +indices[i][2]+"]\tweight total: "
+                       +(weights[i][0]+weights[i][1]+weights[i][2]));
+    }
+    System.out.println();
+
+  }
+
+/* Here's the output:
+
+iris 136% java visad.Irregular2DSet
+Samples:
+#0:     139.0, 102.0
+#1:     357.0, 44.0
+#2:     416.0, 306.0
+#3:     276.0, 174.0
+#4:     495.0, 108.0
+#5:     395.0, 460.0
+#6:     578.0, 333.0
+#7:     199.0, 351.0
+
+valueToIndex test:
+164.0, 131.0    --> #0
+287.0, 323.0    --> #7
+311.0, 90.0     --> #1
+417.0, 264.0    --> #2
+522.0, 294.0    --> #6
+366.0, 421.0    --> #5
+445.0, 91.0     --> #4
+
+valueToInterp test:
+164.0, 131.0    --> [0, 3, 7]   weight total: 0.99999994
+287.0, 323.0    --> [2, 3, 7]   weight total: 1.0
+311.0, 90.0     --> [0, 1, 3]   weight total: 1.0
+417.0, 264.0    --> [2, 3, 4]   weight total: 1.0
+522.0, 294.0    --> [2, 4, 6]   weight total: 1.0
+366.0, 421.0    --> [2, 5, 7]   weight total: 1.0
+445.0, 91.0     --> [1, 3, 4]   weight total: 1.0
+
+iris 137%
+
+*/
+
+}
+
diff --git a/visad/Irregular3DSet.java b/visad/Irregular3DSet.java
new file mode 100644
index 0000000..81070ea
--- /dev/null
+++ b/visad/Irregular3DSet.java
@@ -0,0 +1,3021 @@
+//
+// Irregular3DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Irregular3DSet represents a finite set of samples of R^3.<P>
+
+   No Irregular3DSet with ManifoldDimension = 1.  Use
+   Gridded3DSet with ManifoldDimension = 1 instead.<P>
+*/
+public class Irregular3DSet extends IrregularSet {
+
+  private float LowX, HiX, LowY, HiY, LowZ, HiZ;
+
+  /** a 3-D irregular set with null errors, CoordinateSystem
+      and Units are defaults from type; topology is computed
+      by the constructor */
+  public Irregular3DSet(MathType type, float[][] samples)
+                      throws VisADException {
+    this(type, samples, null, null, null, null, true);
+  }
+
+  /** a 3-D irregular set; samples array is organized
+      float[3][number_of_samples];  no geometric constraint on
+      samples; if delan is non-null it defines the topology of
+      samples (which may have manifold dimension 2 or 3), else
+      the constructor computes a topology with manifold dimension
+      3; note that Gridded3DSet can be used for an irregular set
+      with domain dimension 3 and manifold dimension 1;
+      coordinate_system and units must be compatible with
+      defaults for type, or may be null; errors may be null */
+  public Irregular3DSet(MathType type, float[][] samples,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors, Delaunay delan)
+                                         throws VisADException {
+    this(type, samples, coord_sys, units, errors, delan, true);
+  }
+
+  public Irregular3DSet(MathType type, float[][] samples,
+                 CoordinateSystem coord_sys, Unit[] units,
+                 ErrorEstimate[] errors, Delaunay delan,
+		 boolean copy) throws VisADException {
+    /* ManifoldDimension might not be equal to samples.length
+       if a 2D triangulation has been specified */
+    super(type, samples, (delan == null) ? samples.length
+                                         : delan.Tri[0].length-1,
+          coord_sys, units, errors, delan, copy);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+    oldToNew = null;
+    newToOld = null;
+  }
+
+  /** construct Irregular3DSet using sort from existing
+      Irregular1DSet */
+  public Irregular3DSet(MathType type, float[][] samples,
+               int[] new2old, int[] old2new) throws VisADException {
+    this(type, samples, new2old, old2new, null, null, null, true);
+  }
+
+  /** construct Irregular3DSet using sort from existing
+      Irregular1DSet */
+  public Irregular3DSet(MathType type, float[][] samples,
+                        int[] new2old, int[] old2new,
+                        CoordinateSystem coord_sys, Unit[] units,
+                        ErrorEstimate[] errors) throws VisADException {
+    this(type, samples, new2old, old2new, coord_sys, units, errors, true);
+  }
+
+  public Irregular3DSet(MathType type, float[][] samples,
+                 int[] new2old, int[] old2new,
+                 CoordinateSystem coord_sys, Unit[] units,
+                 ErrorEstimate[] errors, boolean copy)
+                 throws VisADException {
+    super(type, samples, 1, coord_sys, units, errors, null, copy);
+    if (Length != new2old.length || Length != old2new.length) {
+      throw new SetException("Irregular3DSet: sort lengths do not match");
+    }
+    newToOld = new int[Length];
+    oldToNew = new int[Length];
+    System.arraycopy(new2old, 0, newToOld, 0, Length);
+    System.arraycopy(old2new, 0, oldToNew, 0, Length);
+    LowX = Low[0];
+    HiX = Hi[0];
+    LowY = Low[1];
+    HiY = Hi[1];
+    LowZ = Low[2];
+    HiZ = Hi[2];
+    Delan = null;
+  }
+
+  public Set makeSpatial(SetType type, float[][] samples) throws VisADException {
+    if (samples.length == 3) {
+      if (ManifoldDimension == 1) {
+        return new Irregular3DSet(type, samples, newToOld, oldToNew,
+                                  null, null, null, false);
+      }
+      else {
+        if (Delan.Tri == null || Delan.Tri.length == 0) return null;
+        return new Irregular3DSet(type, samples, null, null, null,
+	                          Delan, false);
+      }
+    }
+    else {
+      throw new SetException("Irregular3DSet.makeSpatial: bad samples length");
+    }
+  }
+
+  /** convert an array of 1-D indices to an array of values in
+      R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    float[][] value = new float[3][index.length];
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<index.length; i++) {
+      if ( (index[i] >= 0) && (index[i] < Length) ) {
+        value[0][i] = mySamples[0][index[i]];
+        value[1][i] = mySamples[1][index[i]];
+        value[2][i] = mySamples[2][index[i]];
+      }
+      else {
+        value[0][i] = value[1][i] = value[2][i] = Float.NaN;
+      }
+    }
+    return value;
+  }
+
+  /** valueToTri returns an array of containing triangles given
+     an array of points in R^DomainDimension */
+  public int[] valueToTri(float[][] value) throws VisADException {
+    if (ManifoldDimension != 3) {
+      throw new SetException("Irregular3DSet.valueToTri: " +
+                             "ManifoldDimension must be 3, not " +
+                             ManifoldDimension);
+    }
+    int length = value[0].length;
+    if (length != value[1].length || length != value[2].length) {
+      throw new SetException("Irregular3DSet.valueToTri: lengths " +
+                             "don't match");
+    }
+
+    boolean nonConvex = Delan.getNonConvex();
+
+    float[] PA = new float[3];
+    float[] PB = new float[3];
+    float[] PC = new float[3];
+    float[] PD = new float[3];
+    float[] BAxCB = new float[3];
+    float[] CBxDC = new float[3];
+    float[] DCxAD = new float[3];
+    float[] ADxBA = new float[3];
+    float sum_BAxCB;
+    float sum_CBxDC;
+    float sum_DCxAD;
+    float sum_ADxBA;
+
+    boolean[] fail_tri = new boolean[Delan.Tri.length];
+    for ( int kk = 0; kk < fail_tri.length; kk++ ) { 
+      fail_tri[kk] = false;
+    }
+    int [] fail_list = null;
+    int fail_length = 0;
+
+    int[] tri = new int[length];
+    int curtri = 0;
+
+// System.out.println("length = " + length + " Delan.Tri.length = " +
+//                    Delan.Tri.length);
+
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<length; i++) {
+
+      // System.out.println("\nvalue["+i+"] = ("+value[0][i]+", "+value[1][i]+", "+value[2][i]+")");
+
+      // Return -1 if iteration loop fails
+      tri[i] = -1;
+      boolean foundit = false;
+      if (curtri < 0) curtri = 0;
+      int itnum;
+      for (itnum=0; (itnum<Delan.Tri.length) && !foundit; itnum++) {
+        // define data
+        int t0 = Delan.Tri[curtri][0];
+        int t1 = Delan.Tri[curtri][1];
+        int t2 = Delan.Tri[curtri][2];
+        int t3 = Delan.Tri[curtri][3];
+        float Ax = mySamples[0][t0];
+        float Ay = mySamples[1][t0];
+        float Az = mySamples[2][t0];
+        float Bx = mySamples[0][t1];
+        float By = mySamples[1][t1];
+        float Bz = mySamples[2][t1];
+        float Cx = mySamples[0][t2];
+        float Cy = mySamples[1][t2];
+        float Cz = mySamples[2][t2];
+        float Dx = mySamples[0][t3];
+        float Dy = mySamples[1][t3];
+        float Dz = mySamples[2][t3];
+        float Px = value[0][i];
+        float Py = value[1][i];
+        float Pz = value[2][i];
+
+        PA[0]     =  Px-Ax;
+        PA[1]     =  Py-Ay;
+        PA[2]     =  Pz-Az;
+
+        PB[0]     =  Px-Bx;
+        PB[1]     =  Py-By;
+        PB[2]     =  Pz-Bz;
+
+        PC[0]     =  Px-Cx;
+        PC[1]     =  Py-Cy;
+        PC[2]     =  Pz-Cz;
+
+        PD[0]     =  Px-Dx;
+        PD[1]     =  Py-Dy;
+        PD[2]     =  Pz-Dz;
+
+        BAxCB[0]  =  (By-Ay)*(Cz-Bz)-(Bz-Az)*(Cy-By);
+        BAxCB[1]  =  (Bz-Az)*(Cx-Bx)-(Bx-Ax)*(Cz-Bz);
+        BAxCB[2]  =  (Bx-Ax)*(Cy-By)-(By-Ay)*(Cx-Bx);
+        sum_BAxCB =  Math.abs(BAxCB[0]) + Math.abs(BAxCB[1]) +
+                     Math.abs(BAxCB[2]);
+
+        CBxDC[0]  =  (Cy-By)*(Dz-Cz)-(Cz-Bz)*(Dy-Cy);
+        CBxDC[1]  =  (Cz-Bz)*(Dx-Cx)-(Cx-Bx)*(Dz-Cz);
+        CBxDC[2]  =  (Cx-Bx)*(Dy-Cy)-(Cy-By)*(Dx-Cx);
+        sum_CBxDC =  Math.abs(CBxDC[0]) + Math.abs(CBxDC[1]) +
+                     Math.abs(CBxDC[2]);
+
+        DCxAD[0]  =  (Dy-Cy)*(Az-Dz)-(Dz-Cz)*(Ay-Dy);
+        DCxAD[1]  =  (Dz-Cz)*(Ax-Dx)-(Dx-Cx)*(Az-Dz);
+        DCxAD[2]  =  (Dx-Cx)*(Ay-Dy)-(Dy-Cy)*(Ax-Dx);
+        sum_DCxAD =  Math.abs(DCxAD[0]) + Math.abs(DCxAD[1]) +
+                     Math.abs(DCxAD[2]);
+
+        ADxBA[0]  =  (Ay-Dy)*(Bz-Az)-(Az-Dz)*(By-Ay);
+        ADxBA[1]  =  (Az-Dz)*(Bx-Ax)-(Ax-Dx)*(Bz-Az);
+        ADxBA[2]  =  (Ax-Dx)*(By-Ay)-(Ay-Dy)*(Bx-Ax);
+        sum_ADxBA =  Math.abs(ADxBA[0]) + Math.abs(ADxBA[1]) +
+                     Math.abs(ADxBA[2]);
+
+        // test whether point is contained in current triangle
+
+        float tval1 = BAxCB[0]*PA[0] + BAxCB[1]*PA[1] + BAxCB[2]*PA[2];
+        float tval2 = CBxDC[0]*PB[0] + CBxDC[1]*PB[1] + CBxDC[2]*PB[2];
+        float tval3 = DCxAD[0]*PC[0] + DCxAD[1]*PC[1] + DCxAD[2]*PC[2];
+        float tval4 = ADxBA[0]*PD[0] + ADxBA[1]*PD[1] + ADxBA[2]*PD[2];
+    
+
+     // System.out.println("Px-Ax: "+(Px-Ax)+" Py-Ay: "+(Py-Ay)+" Pz-Az: "+(Pz-Az));
+     // System.out.println("Px-Bx: "+(Px-Bx)+" Py-By: "+(Py-By)+" Pz-Bz: "+(Pz-Bz));
+     // System.out.println("Px-Cx: "+(Px-Cx)+" Py-Cy: "+(Py-Cy)+" Pz-Cz: "+(Pz-Cz));
+     // System.out.println("Px-Dx: "+(Px-Dx)+" Py-Dy: "+(Py-Dy)+" Pz-Dz: "+(Pz-Dz));
+     // System.out.println("sum_BAxCB: "+sum_BAxCB+" sum_CBxDC: "+sum_CBxDC+" sum_DCxAD "+sum_DCxAD+" sum_ADxBA "+sum_ADxBA);
+     // System.out.println("curtri: "+curtri+" tval1: "+tval1+" tval2: "+tval2+" tval3: "+tval3+" tval4: "+tval4);
+
+        boolean test1 = ((tval1 == 0.0f) || ( (tval1 > 0) == (
+                        BAxCB[0]*(Dx-Ax)
+                        + BAxCB[1]*(Dy-Ay)
+                        + BAxCB[2]*(Dz-Az) > 0) )) && (sum_BAxCB != 0);
+
+        boolean test2 = ((tval2 == 0.0f) || ( (tval2 > 0) == (
+                        CBxDC[0]*(Ax-Bx)
+                        + CBxDC[1]*(Ay-By)
+                        + CBxDC[2]*(Az-Bz) > 0) )) && (sum_CBxDC != 0);
+
+        boolean test3 = ((tval3 == 0.0f) || ( (tval3 > 0) == (
+                        DCxAD[0]*(Bx-Cx)
+                        + DCxAD[1]*(By-Cy)
+                        + DCxAD[2]*(Bz-Cz) > 0) )) && (sum_DCxAD != 0);
+
+        boolean test4 = ((tval4 == 0.0f) || ( (tval4 > 0) == (
+                        ADxBA[0]*(Cx-Dx)
+                        + ADxBA[1]*(Cy-Dy)
+                        + ADxBA[2]*(Cz-Dz) > 0) )) && (sum_ADxBA != 0);
+
+        // System.out.println("i: "+i+" curtri: "+curtri+" test1: "+test1+" test2: "+test2+" test3: "+test3+" test4: "+test4);
+
+
+        // figure out which triangle to go to next
+
+        if (!test1 || !test2 || !test3 || !test4) {
+          // record failed tri
+          fail_tri[curtri] = true;
+          // add to list of failed tris for efficient reset
+          if (fail_list == null) {
+            fail_list = new int[4];
+            fail_length = 0;
+          }
+          else if (fail_length >= fail_list.length) {
+            int[] new_fail_list = new int[2 * fail_list.length];
+            System.arraycopy(fail_list, 0, new_fail_list, 0, fail_list.length);
+            fail_list = new_fail_list;
+          }
+          fail_list[fail_length] = curtri;
+          fail_length++;
+
+          int t = -1;
+          boolean fail = true;
+          if (!test1 && fail) {
+            t = Delan.Walk[curtri][0];
+            if (t != -1) fail = fail_tri[t];
+          }
+          if (!test2 && fail) {
+            t = Delan.Walk[curtri][1];
+            if (t != -1) fail = fail_tri[t];
+          }
+          if (!test3 && fail) {
+            t = Delan.Walk[curtri][2];
+            if (t != -1) fail = fail_tri[t];
+          }
+          if (!test4 && fail) {
+            t = Delan.Walk[curtri][3];
+            if (t != -1) fail = fail_tri[t];
+          }
+
+          if (!fail || t == -1) {
+            curtri = t;
+          }
+          if (fail) curtri = -1;
+
+          if (nonConvex) {
+            // to deal with non-convex Set, but very slow
+            if (curtri == -1) {
+              for (int jj=0; jj<fail_tri.length; jj++) {
+                if (!fail_tri[jj]) {
+                  curtri = jj;
+                  break;
+                }
+              }
+            }
+          }
+
+        }
+        else {
+          foundit = true;
+        }
+
+        // Return -1 if outside of the convex hull
+        if (curtri < 0) {
+          // System.out.println("outside of the convex hull " + i);
+          foundit = true;
+        }
+        if (foundit) {
+          tri[i] = curtri;
+        }
+      } // end for (itnum=0; (itnum<Delan.Tri.length) && !foundit; itnum++)
+
+      // reset all fail_tri to false
+      if (fail_list != null) {
+        for (int ii=0; ii<fail_length; ii++) {
+          fail_tri[fail_list[ii]] = false;
+        }
+        fail_list = null;
+      }
+
+    } // end for (int i=0; i<length; i++)
+
+    return tri;
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of
+      1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Irregular3DSet.valueToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    float[][]mySamples = getMySamples();
+    int[] tri = valueToTri(value);
+    int[] index = new int[tri.length];
+    for (int i=0; i<tri.length; i++) {
+      if (tri[i] < 0) {
+        index[i] = -1;
+      }
+      else {
+        // current values
+        float x = value[0][i];
+        float y = value[1][i];
+        float z = value[2][i];
+
+        // triangle indices
+        int t = tri[i];
+        int t0 = Delan.Tri[t][0];
+        int t1 = Delan.Tri[t][1];
+        int t2 = Delan.Tri[t][2];
+        int t3 = Delan.Tri[t][3];
+
+        // partial distances
+        float D00 = mySamples[0][t0] - x;
+        float D01 = mySamples[1][t0] - y;
+        float D02 = mySamples[2][t0] - z;
+        float D10 = mySamples[0][t1] - x;
+        float D11 = mySamples[1][t1] - y;
+        float D12 = mySamples[2][t1] - z;
+        float D20 = mySamples[0][t2] - x;
+        float D21 = mySamples[1][t2] - y;
+        float D22 = mySamples[2][t2] - z;
+        float D30 = mySamples[0][t3] - x;
+        float D31 = mySamples[1][t3] - y;
+        float D32 = mySamples[2][t3] - z;
+
+        // distances squared
+        float Dsq0 = D00*D00 + D01*D01 + D02*D02;
+        float Dsq1 = D10*D10 + D11*D11 + D12*D12;
+        float Dsq2 = D20*D20 + D21*D21 + D22*D22;
+        float Dsq3 = D30*D30 + D31*D31 + D32*D32;
+
+        // find the minimum distance
+        float min = Math.min(Dsq0, Dsq1);
+        min = Math.min(min, Dsq2);
+        min = Math.min(min, Dsq3);
+        if (min == Dsq0) index[i] = t0;
+        else if (min == Dsq1) index[i] = t1;
+        else if (min == Dsq2) index[i] = t2;
+        else index[i] = t3;
+      }
+    }
+    return index;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if no interpolation is possible */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float[][] weights) throws VisADException {
+    if (value.length < DomainDimension) {
+      throw new SetException("Irregular3DSet.valueToInterp: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if ( (indices.length < length) || (weights.length < length) ) {
+      throw new SetException("Irregular3DSet.valueToInterp:"
+                            +" lengths don't match");
+    }
+    // System.out.println("value: "+value[0][0]+", "+value[1][0]+", "+value[2][0]);
+    float[][]mySamples = getMySamples();
+    int[] tri = valueToTri(value);
+    for (int i=0; i<tri.length; i++) {
+      if (tri[i] < 0) {
+        indices[i] = null;
+        weights[i] = null;
+      }
+      else {
+        // indices and weights sub-arrays
+        int[] ival = new int[4];
+        float[] wval = new float[4];
+        // current values
+        float x = value[0][i];
+        float y = value[1][i];
+        float z = value[2][i];
+
+        // triangle indices
+        int t = tri[i];
+        int t0 = Delan.Tri[t][0];
+        int t1 = Delan.Tri[t][1];
+        int t2 = Delan.Tri[t][2];
+        int t3 = Delan.Tri[t][3];
+        ival[0] = t0;
+        ival[1] = t1;
+        ival[2] = t2;
+        ival[3] = t3;
+
+        // triangle vertices
+        float x0 = mySamples[0][t0];
+        float y0 = mySamples[1][t0];
+        float z0 = mySamples[2][t0];
+        float x1 = mySamples[0][t1];
+        float y1 = mySamples[1][t1];
+        float z1 = mySamples[2][t1];
+        float x2 = mySamples[0][t2];
+        float y2 = mySamples[1][t2];
+        float z2 = mySamples[2][t2];
+        float x3 = mySamples[0][t3];
+        float y3 = mySamples[1][t3];
+        float z3 = mySamples[2][t3];
+
+        // perpendicular lines
+        float C0x = (y3-y1)*(z2-z1) - (z3-z1)*(y2-y1);
+        float C0y = (z3-z1)*(x2-x1) - (x3-x1)*(z2-z1);
+        float C0z = (x3-x1)*(y2-y1) - (y3-y1)*(x2-x1);
+        float C1x = (y3-y0)*(z2-z0) - (z3-z0)*(y2-y0);
+        float C1y = (z3-z0)*(x2-x0) - (x3-x0)*(z2-z0);
+        float C1z = (x3-x0)*(y2-y0) - (y3-y0)*(x2-x0);
+        float C2x = (y3-y0)*(z1-z0) - (z3-z0)*(y1-y0);
+        float C2y = (z3-z0)*(x1-x0) - (x3-x0)*(z1-z0);
+        float C2z = (x3-x0)*(y1-y0) - (y3-y0)*(x1-x0);
+        float C3x = (y2-y0)*(z1-z0) - (z2-z0)*(y1-y0);
+        float C3y = (z2-z0)*(x1-x0) - (x2-x0)*(z1-z0);
+        float C3z = (x2-x0)*(y1-y0) - (y2-y0)*(x1-x0);
+
+        // weights
+        wval[0] = ( ( (x - x1)*C0x) + ( (y - y1)*C0y) + ( (z - z1)*C0z) )
+                / ( ((x0 - x1)*C0x) + ((y0 - y1)*C0y) + ((z0 - z1)*C0z) );
+        wval[1] = ( ( (x - x0)*C1x) + ( (y - y0)*C1y) + ( (z - z0)*C1z) )
+                / ( ((x1 - x0)*C1x) + ((y1 - y0)*C1y) + ((z1 - z0)*C1z) );
+        wval[2] = ( ( (x - x0)*C2x) + ( (y - y0)*C2y) + ( (z - z0)*C2z) )
+                / ( ((x2 - x0)*C2x) + ((y2 - y0)*C2y) + ((z2 - z0)*C2z) );
+        wval[3] = ( ( (x - x0)*C3x) + ( (y - y0)*C3y) + ( (z - z0)*C3z) )
+                / ( ((x3 - x0)*C3x) + ((y3 - y0)*C3y) + ((z3 - z0)*C3z) );
+
+        // fill in arrays
+        indices[i] = ival;
+        weights[i] = wval;
+      }
+    }
+  }
+
+  /** return basic lines in array[0], fill-ins in array[1]
+      and labels in array[2] */
+  public VisADGeometryArray[][] makeIsoLines(float[] intervals,
+                  float lowlimit, float highlimit, float base,
+                  float[] fieldValues, byte[][] color_values,
+                  boolean[] swap, boolean dash,
+                  boolean fill, ScalarMap[] smap,
+                  double[] scale, double label_size,
+                  boolean sphericalDisplayCS) throws VisADException {
+    if (ManifoldDimension != 2) {
+      throw new DisplayException("Irregular3DSet.makeIsoLines: " +
+                                 "ManifoldDimension must be 2, not " +
+                                 ManifoldDimension);
+    }
+
+    // WLH 21 May 99
+    if (intervals == null) return null;
+
+    int[][] Tri = Delan.Tri;
+    float[][] samples = getSamples(false);
+    int npolygons = Tri.length;
+    int nvertex = Delan.Vertices.length;
+    if (npolygons < 1 || nvertex < 3) return null;
+
+    // estimate number of vertices
+    int maxv = 2 * 2 * Length;
+
+    int color_length = (color_values != null) ? color_values.length : 0;
+    byte[][] color_levels = null;
+    if (color_length > 0) {
+      if (color_length > 3) color_length = 3; // no alpha for lines
+      color_levels = new byte[color_length][maxv];
+    }
+    float[] vx = new float[maxv];
+    float[] vy = new float[maxv];
+    float[] vz = new float[maxv];
+
+    int numv = 0;
+
+    for (int jj=0; jj<npolygons; jj++) {
+      int va = Tri[jj][0];
+      int vb = Tri[jj][1];
+      int vc = Tri[jj][2];
+
+      float ga = fieldValues[va];
+      // test for missing
+      if (ga != ga) continue;
+
+      float gb = fieldValues[vb];
+      // test for missing
+      if (gb != gb) continue;
+
+      float gc = fieldValues[vc];
+      // test for missing
+      if (gc != gc) continue;
+
+      byte[] auxa = null;
+      byte[] auxb = null;
+      byte[] auxc = null;
+      if (color_length > 0) {
+        auxa = new byte[color_length];
+        auxb = new byte[color_length];
+        auxc = new byte[color_length];
+        for (int i=0; i<color_length; i++) {
+          auxa[i] = color_values[i][va];
+          auxb[i] = color_values[i][vb];
+          auxc[i] = color_values[i][vc];
+        }
+      }
+
+      float gn = ga < gb ? ga : gb;
+      gn = gc < gn ? gc : gn;
+
+      float gx = ga > gb ? ga : gb;
+      gx = gc > gx ? gc : gx;
+
+      for (int il=0; il<intervals.length; il++) {
+        float gg = intervals[il];
+
+        if (numv+8 >= maxv) {
+          // allocate more space
+          maxv = 2 * maxv;
+          byte[][] t = color_levels;
+          color_levels = new byte[color_length][maxv];
+          for (int i=0; i<color_length; i++) {
+            System.arraycopy(t[i], 0, color_levels[i], 0, numv);
+          }
+          float[] tx = vx;
+          float[] ty = vy;
+          float[] tz = vz;
+          vx = new float[maxv];
+          vy = new float[maxv];
+          vz = new float[maxv];
+          System.arraycopy(tx, 0, vx, 0, numv);
+          System.arraycopy(ty, 0, vy, 0, numv);
+          System.arraycopy(tz, 0, vz, 0, numv);
+        }
+
+        float gba, gca, gcb;
+        float ratioba, ratioca, ratiocb;
+        int ii;
+        int t;
+
+        // make sure gg is within contouring limits
+        if (gg < gn) continue;
+        if (gg > gx) break;
+        if (gg < lowlimit) continue;
+        if (gg > highlimit) break;
+
+        // compute orientation of lines inside box
+        ii = 0;
+        if (gg > ga) ii = 1;
+        if (gg > gb) ii += 2;
+        if (gg > gc) ii += 4;
+        if (ii > 3) ii = 7 - ii;
+        if (ii <= 0) continue;
+
+
+        switch (ii) {
+          case 1:
+            gba = gb-ga;
+            gca = gc-ga;
+
+            ratioba = (gg-ga)/gba;
+            ratioca = (gg-ga)/gca;
+
+            if (color_length > 0) {
+              for (int i=0; i<color_length; i++) {
+                t = (int) ( (1.0f - ratioba) * ((auxa[i] < 0) ?
+                      ((float) auxa[i]) + 256.0f : ((float) auxa[i]) ) +
+                    ratioba * ((auxb[i] < 0) ?
+                      ((float) auxb[i]) + 256.0f : ((float) auxb[i]) ) );
+                color_levels[i][numv] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+                t = (int) ( (1.0f - ratioca) * ((auxa[i] < 0) ?
+                      ((float) auxa[i]) + 256.0f : ((float) auxa[i]) ) +
+                    ratioca * ((auxc[i] < 0) ?
+                      ((float) auxc[i]) + 256.0f : ((float) auxc[i]) ) );
+                color_levels[i][numv+1] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                color_levels[i][numv] = auxa[i] + (auxb[i]-auxa[i]) * ratioba;
+                color_levels[i][numv+1] = auxa[i] + (auxc[i]-auxa[i]) * ratioca;
+*/
+              }
+            }
+
+            vx[numv] = samples[0][va] + (samples[0][vb]-samples[0][va]) * ratioba;
+            vy[numv] = samples[1][va] + (samples[1][vb]-samples[1][va]) * ratioba;
+            vz[numv] = samples[2][va] + (samples[2][vb]-samples[2][va]) * ratioba;
+            numv++;
+            vx[numv] = samples[0][va] + (samples[0][vc]-samples[0][va]) * ratioca;
+            vy[numv] = samples[1][va] + (samples[1][vc]-samples[1][va]) * ratioca;
+            vz[numv] = samples[2][va] + (samples[2][vc]-samples[2][va]) * ratioca;
+            numv++;
+          break;
+
+          case 2:
+            gba = gb-ga;
+            gcb = gc-gb;
+
+            ratioba = (gg-ga)/gba;
+            ratiocb = (gg-gb)/gcb;
+
+            if (color_length > 0) {
+              for (int i=0; i<color_length; i++) {
+                t = (int) ( (1.0f - ratioba) * ((auxa[i] < 0) ?
+                      ((float) auxa[i]) + 256.0f : ((float) auxa[i]) ) +
+                    ratioba * ((auxb[i] < 0) ?
+                      ((float) auxb[i]) + 256.0f : ((float) auxb[i]) ) );
+                color_levels[i][numv] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+                t = (int) ( (1.0f - ratiocb) * ((auxb[i] < 0) ?
+                      ((float) auxb[i]) + 256.0f : ((float) auxb[i]) ) +
+                    ratiocb * ((auxc[i] < 0) ?
+                      ((float) auxc[i]) + 256.0f : ((float) auxc[i]) ) );
+                color_levels[i][numv+1] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                color_levels[i][numv] = auxa[i] + (auxb[i]-auxa[i]) * ratioba;
+                color_levels[i][numv+1] = auxb[i] + (auxc[i]-auxb[i]) * ratiocb;
+*/
+              }
+            }
+
+            vx[numv] = samples[0][va] + (samples[0][vb]-samples[0][va]) * ratioba;
+            vy[numv] = samples[1][va] + (samples[1][vb]-samples[1][va]) * ratioba;
+            vz[numv] = samples[2][va] + (samples[2][vb]-samples[2][va]) * ratioba;
+            numv++;
+            vx[numv] = samples[0][vb] + (samples[0][vc]-samples[0][vb]) * ratiocb;
+            vy[numv] = samples[1][vb] + (samples[1][vc]-samples[1][vb]) * ratiocb;
+            vz[numv] = samples[2][vb] + (samples[2][vc]-samples[2][vb]) * ratiocb;
+            numv++;
+          break;
+
+          case 3:
+            gca = gc-ga;
+            gcb = gc-gb;
+
+            ratioca = (gg-ga)/gca;
+            ratiocb = (gg-gb)/gcb;
+
+            if (color_length > 0) {
+              for (int i=0; i<color_length; i++) {
+                t = (int) ( (1.0f - ratioca) * ((auxa[i] < 0) ?
+                      ((float) auxa[i]) + 256.0f : ((float) auxa[i]) ) +
+                    ratioca * ((auxc[i] < 0) ?
+                      ((float) auxc[i]) + 256.0f : ((float) auxc[i]) ) );
+                color_levels[i][numv] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+                t = (int) ( (1.0f - ratiocb) * ((auxb[i] < 0) ?
+                      ((float) auxb[i]) + 256.0f : ((float) auxb[i]) ) +
+                    ratiocb * ((auxc[i] < 0) ?
+                      ((float) auxc[i]) + 256.0f : ((float) auxc[i]) ) );
+                color_levels[i][numv+1] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                color_levels[i][numv] = auxa[i] + (auxc[i]-auxa[i]) * ratioca;
+                color_levels[i][numv+1] = auxb[i] + (auxc[i]-auxb[i]) * ratiocb;
+*/
+              }
+            }
+
+            vx[numv] = samples[0][va] + (samples[0][vc]-samples[0][va]) * ratioca;
+            vy[numv] = samples[1][va] + (samples[1][vc]-samples[1][va]) * ratioca;
+            vz[numv] = samples[2][va] + (samples[2][vc]-samples[2][va]) * ratioca;
+            numv++;
+            vx[numv] = samples[0][vb] + (samples[0][vc]-samples[0][vb]) * ratiocb;
+            vy[numv] = samples[1][vb] + (samples[1][vc]-samples[1][vb]) * ratiocb;
+            vz[numv] = samples[2][vb] + (samples[2][vc]-samples[2][vb]) * ratiocb;
+            numv++;
+          break;
+
+        } // end switch (ii)
+      } // end for (int il=0; il<numc && numv+8<mav; il++, gg += interval)
+
+    } // end for (int jj=0; jj<npolygons; jj++)
+
+    VisADLineArray lineArray = new VisADLineArray();
+    float[][] coordinates = new float[3][numv];
+    System.arraycopy(vx, 0, coordinates[0], 0, numv);
+    System.arraycopy(vy, 0, coordinates[1], 0, numv);
+    System.arraycopy(vz, 0, coordinates[2], 0, numv);
+    vx = null;
+    vy = null;
+    vz = null;
+    byte[][] colors = null;
+    if (color_length > 0) {
+      colors = new byte[3][numv];
+      System.arraycopy(color_levels[0], 0, colors[0], 0, numv);
+      System.arraycopy(color_levels[1], 0, colors[1], 0, numv);
+      System.arraycopy(color_levels[2], 0, colors[2], 0, numv);
+/* MEM_WLH
+      colors = new byte[3][numv];
+      for (int i=0; i<3; i++) {
+        for (int j=0; j<numv; j++) {
+          int k = (int) (color_levels[i][j] * 255.0);
+          k = (k < 0) ? 0 : (k > 255) ? 255 : k;
+          colors[i][j] = (byte) ((k < 128) ? k : k - 256);
+        }
+      }
+*/
+      color_levels = null;
+    }
+    setGeometryArray(lineArray, coordinates, 4, colors);
+    return new VisADLineArray[][] {{lineArray}, null, null};
+  }
+
+  public VisADGeometryArray makeIsoSurface(float isolevel,
+         float[] fieldValues, byte[][] color_values, boolean indexed)
+         throws VisADException {
+
+    if (ManifoldDimension != 3) {
+      throw new DisplayException("Irregular3DSet.main_isosurf: " +
+                                 "ManifoldDimension must be 3, not " +
+                                 ManifoldDimension);
+    }
+
+    float[][] fieldVertices = new float[3][];
+    byte[][] color_levels = null;
+    if (color_values != null) {
+      color_levels = new byte[color_values.length][];
+/* MEM_WLH
+      cfloat = new float[color_values.length][];
+      for (int i = 0; i< color_levels.length; i++) {
+        if (color_values[i] != null) {
+          cfloat[i] = new float[color_values[i].length];
+          for (int j=0; j<color_values[i].length; j++) {
+            int k = color_values[i][j];
+            if (k < 0) k += 256;
+            cfloat[i][j] = (k / 255.0f);
+          }
+        }
+      }
+*/
+    }
+    int[][][] polyToVert = new int[1][][];
+    int[][][] vertToPoly = new int[1][][];
+    makeIsosurface(isolevel, fieldValues, color_values, fieldVertices,
+                   color_levels, polyToVert, vertToPoly);
+
+/* MEM_WLH
+    byte[][] c = null;
+    if (color_levels != null) {
+      c = new byte[color_levels.length][];
+      for (int i = 0; i< color_levels.length; i++) {
+        if (color_levels[i] != null) {
+          c[i] = new byte[color_levels[i].length];
+          for (int j=0; j<color_levels[i].length; j++) {
+            int k = (int) (color_levels[i][j] * 255.0);
+            k = (k < 0) ? 0 : (k > 255) ? 255 : k;
+            c[i][j] = (byte) ((k < 128) ? k : k - 256);
+          }
+        }
+      }
+      // FREE
+      color_levels = null;
+    }
+*/
+
+    int nvertex = vertToPoly[0].length;
+    int npolygons = polyToVert[0].length;
+    float[] NX = new float[nvertex];
+    float[] NY = new float[nvertex];
+    float[] NZ = new float[nvertex];
+
+    if (nvertex == 0 || npolygons == 0) return null;
+
+
+    // with make_normals
+    float[] NxA = new float[npolygons];
+    float[] NxB = new float[npolygons];
+    float[] NyA = new float[npolygons];
+    float[] NyB = new float[npolygons];
+    float[] NzA = new float[npolygons];
+    float[] NzB = new float[npolygons];
+    float[] Pnx = new float[npolygons];
+    float[] Pny = new float[npolygons];
+    float[] Pnz = new float[npolygons];
+
+    make_normals(fieldVertices[0], fieldVertices[1], fieldVertices[2],
+                 NX, NY, NZ, nvertex, npolygons, Pnx, Pny, Pnz,
+                 NxA, NxB, NyA, NyB, NzA, NzB, vertToPoly[0], polyToVert[0]);
+
+    // take the garbage out
+    NxA = NxB = NyA = NyB = NzA = NzB = Pnx = Pny = Pnz = null;
+
+
+
+    // with poly_triangle_stripe
+    float[] normals = new float[3 * nvertex];
+    int j = 0;
+    for (int i=0; i<nvertex; i++) {
+      normals[j++] = (float) NX[i];
+      normals[j++] = (float) NY[i];
+      normals[j++] = (float) NZ[i];
+    }
+    // take the garbage out
+    NX = NY = NZ = null;
+
+    int[] stripe = new int[6 * npolygons];
+
+    int size_stripe =
+      poly_triangle_stripe(stripe, nvertex, npolygons,
+                           vertToPoly[0], polyToVert[0]);
+    // take the garbage out
+    vertToPoly = null;
+    polyToVert = null;
+
+    if (indexed) {
+      VisADIndexedTriangleStripArray array =
+        new VisADIndexedTriangleStripArray();
+
+      // set up indices
+      array.indexCount = size_stripe;
+      array.indices = new int[size_stripe];
+      System.arraycopy(stripe, 0, array.indices, 0, size_stripe);
+      array.stripVertexCounts = new int[1];
+      array.stripVertexCounts[0] = size_stripe;
+      // take the garbage out
+      stripe = null;
+
+      // set coordinates and colors
+      setGeometryArray(array, fieldVertices, 4, color_levels);
+      // take the garbage out
+      fieldVertices = null;
+      color_levels = null;
+
+      // array.vertexFormat |= NORMALS;
+      array.normals = normals;
+      return array;
+    }
+    else { // if (!indexed)
+      VisADTriangleStripArray array = new VisADTriangleStripArray();
+      array.stripVertexCounts = new int[] {size_stripe};
+      array.vertexCount = size_stripe;
+
+      array.normals = new float[3 * size_stripe];
+      int k = 0;
+      for (int i=0; i<3*size_stripe; i+=3) {
+        j = 3 * stripe[k];
+        array.normals[i] = normals[j];
+        array.normals[i+1] = normals[j+1];
+        array.normals[i+2] = normals[j+2];
+        k++;
+      }
+      normals = null;
+
+      array.coordinates = new float[3 * size_stripe];
+      k = 0;
+      for (int i=0; i<3*size_stripe; i+=3) {
+        j = stripe[k];
+        array.coordinates[i] = fieldVertices[0][j];
+        array.coordinates[i+1] = fieldVertices[1][j];
+        array.coordinates[i+2] = fieldVertices[2][j];
+        k++;
+      }
+      fieldVertices = null;
+
+      if (color_levels != null) {
+        int color_length = color_levels.length;
+        array.colors = new byte[color_length * size_stripe];
+        k = 0;
+        if (color_length == 4) {
+          for (int i=0; i<color_length*size_stripe; i+=color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i+1] = color_levels[1][j];
+            array.colors[i+2] = color_levels[2][j];
+            array.colors[i+3] = color_levels[3][j];
+            k++;
+          }
+        }
+        else { // if (color_length == 3)
+          for (int i=0; i<color_length*size_stripe; i+=color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i+1] = color_levels[1][j];
+            array.colors[i+2] = color_levels[2][j];
+            k++;
+          }
+        }
+      }
+      color_levels = null;
+      stripe = null;
+      return array;
+    }
+  }
+
+  /** compute an Isosurface through the Irregular3DSet
+      given an array of fieldValues at each sample,
+      an isolevel at which to form the surface, and
+      other values (e.g., colors) at each sample in
+      auxValues;
+      return vertex locations in fieldVertices[3][nvertex];
+      return values of auxValues at vertices in auxLevels;
+      return pointers from vertices to polys in
+             vertToPoly[1][nvertex][nverts[i]];
+      return pointers from polys to vertices in
+             polyToVert[1][npolygons][4]; */
+  private void makeIsosurface(float isolevel, float[] fieldValues,
+                              byte[][] auxValues, float[][] fieldVertices,
+                              byte[][] auxLevels, int[][][] polyToVert,
+                              int[][][] vertToPoly) throws VisADException {
+    boolean DEBUG = false;
+    if (ManifoldDimension != 3) {
+      throw new DisplayException("Irregular3DSet.makeIsosurface: " +
+                                 "ManifoldDimension must be 3, not " +
+                                 ManifoldDimension);
+    }
+    if (fieldValues.length != Length) {
+      throw new DisplayException("Irregular3DSet.makeIsosurface: "
+                                 +"fieldValues length does't match");
+    }
+    if (Double.isNaN(isolevel)) {
+      throw new DisplayException("Irregular3DSet.makeIsosurface: "
+                                 +"isolevel cannot be missing");
+    }
+    if (fieldVertices.length != 3 || polyToVert.length != 1
+                                  || vertToPoly.length != 1) {
+      throw new DisplayException("Irregular3DSet.makeIsosurface: return value"
+                                 + " arrays not correctly initialized " +
+                                 fieldVertices.length + " " + polyToVert.length +
+                                 " " + vertToPoly.length);
+    }
+    int naux = (auxValues != null) ? auxValues.length : 0;
+    if (naux > 0) {
+      if (auxLevels == null || auxLevels.length != naux) {
+        throw new DisplayException("Irregular3DSet.makeIsosurface: "
+                                   +"auxLevels length doesn't match");
+      }
+      for (int i=0; i<naux; i++) {
+        if (auxValues[i].length != Length) {
+          throw new DisplayException("Irregular3DSet.makeIsosurface: "
+                                     +"auxValues lengths don't match");
+        }
+      }
+    }
+    else {
+      if (auxLevels != null) {
+        throw new DisplayException("Irregular3DSet.makeIsosurface: "
+                                   +"auxValues null but auxLevels not null");
+      }
+    }
+
+
+    if (DEBUG) {
+      System.out.println("isolevel = " + isolevel + "\n");
+      System.out.println("fieldValues " + fieldValues.length);
+      for (int i=0; i<fieldValues.length; i++) {
+        System.out.println("  " + i + " -> " + fieldValues[i]);
+      }
+      System.out.println(Delan.sampleString(getMySamples()));
+    }
+
+
+    int trilength = Delan.Tri.length;
+
+    // temporary storage of polyToVert structure
+    int[][] polys = new int[trilength][4];
+
+    // pointers from global edge number to nvertex
+    int[] globalToVertex = new int[Delan.NumEdges];
+    for (int i=0; i<Delan.NumEdges; i++) globalToVertex[i] = -1;
+
+    // global edges temporary storage array
+    float[][] edgeInterp = new float[DomainDimension][Delan.NumEdges];
+    for (int i=0; i<Delan.NumEdges; i++) edgeInterp[0][i] = Float.NaN;
+
+    // global edges temporary storage array for aux levels
+    byte[][] auxInterp = (naux > 0) ? new byte[naux][Delan.NumEdges] : null;
+
+    int t;
+
+    int nvertex = 0;
+    int npolygons = 0;
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<trilength; i++) {
+      int v0 = Delan.Tri[i][0];
+      int v1 = Delan.Tri[i][1];
+      int v2 = Delan.Tri[i][2];
+      int v3 = Delan.Tri[i][3];
+      float f0 = (float) fieldValues[v0];
+      float f1 = (float) fieldValues[v1];
+      float f2 = (float) fieldValues[v2];
+      float f3 = (float) fieldValues[v3];
+      int e0, e1, e2, e3, e4, e5;
+
+      // compute tetrahedron signature
+      // vector from v0 to v3
+      float vx = mySamples[0][v3] - mySamples[0][v0];
+      float vy = mySamples[1][v3] - mySamples[1][v0];
+      float vz = mySamples[2][v3] - mySamples[2][v0];
+      // cross product (v2 - v0) x (v1 - v0)
+      float sx = mySamples[0][v2] - mySamples[0][v0];
+      float sy = mySamples[1][v2] - mySamples[1][v0];
+      float sz = mySamples[2][v2] - mySamples[2][v0];
+      float tx = mySamples[0][v1] - mySamples[0][v0];
+      float ty = mySamples[1][v1] - mySamples[1][v0];
+      float tz = mySamples[2][v1] - mySamples[2][v0];
+      float cx = sy * tz - sz * ty;
+      float cy = sz * tx - sx * tz;
+      float cz = sx * ty - sy * tx;
+      // signature is sign of v (dot) c
+      float sig = vx * cx + vy * cy + vz * cz;
+
+      // 8 possibilities
+      int index = ((f0 > isolevel) ? 1 : 0)
+                + ((f1 > isolevel) ? 2 : 0)
+                + ((f2 > isolevel) ? 4 : 0)
+                + ((f3 > isolevel) ? 8 : 0);
+      // apply signature to index
+      if (sig < 0.0f) index = 15 - index;
+
+      switch (index) {
+        case 0:
+        case 15:             // plane does not intersect this tetrahedron
+          break;
+
+        case 1:
+        case 14:             // plane slices a triangle
+          // define edge values needed
+          e0 = Delan.Edges[i][0];
+          e1 = Delan.Edges[i][1];
+          e2 = Delan.Edges[i][2];
+
+          // fill in edge interpolation values if they haven't been found
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e0])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e0] != edgeInterp[0][e0]) {
+            float a = (isolevel - f1)/(f0 - f1);
+            if (a < 0) a = -a;
+            edgeInterp[0][e0] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v1];
+            edgeInterp[1][e0] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v1];
+            edgeInterp[2][e0] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v1];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) );
+              auxInterp[j][e0] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e0] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v1];
+*/
+            }
+            globalToVertex[e0] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e1])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e1] != edgeInterp[0][e1]) {
+            float a = (isolevel - f2)/(f0 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e1] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e1] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e1] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e1] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e1] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e1] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e2])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e2] != edgeInterp[0][e2]) {
+            float a = (isolevel - f3)/(f0 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e2] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e2] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e2] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e2] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e2] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e2] = nvertex;
+            nvertex++;
+          }
+
+          // fill in the polys and vertToPoly arrays
+          polys[npolygons][0] = e0;
+          if (index == 1) {
+            polys[npolygons][1] = e1;
+            polys[npolygons][2] = e2;
+          }
+          else { // index == 14
+            polys[npolygons][1] = e2;
+            polys[npolygons][2] = e1;
+          }
+          polys[npolygons][3] = -1;
+
+          // on to the next tetrahedron
+          npolygons++;
+          break;
+
+        case 2:
+        case 13:             // plane slices a triangle
+          // define edge values needed
+          e0 = Delan.Edges[i][0];
+          e3 = Delan.Edges[i][3];
+          e4 = Delan.Edges[i][4];
+
+          // fill in edge interpolation values if they haven't been found
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e0])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e0] != edgeInterp[0][e0]) {
+            float a = (isolevel - f1)/(f0 - f1);
+            if (a < 0) a = -a;
+            edgeInterp[0][e0] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v1];
+            edgeInterp[1][e0] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v1];
+            edgeInterp[2][e0] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v1];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) );
+              auxInterp[j][e0] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e0] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v1];
+*/
+            }
+            globalToVertex[e0] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e3])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e3] != edgeInterp[0][e3]) {
+            float a = (isolevel - f2)/(f1 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e3] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e3] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e3] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e3] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e3] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e3] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e4])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e4] != edgeInterp[0][e4]) {
+            float a = (isolevel - f3)/(f1 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e4] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e4] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e4] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e4] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e4] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e4] = nvertex;
+            nvertex++;
+          }
+
+          // fill in the polys array
+          polys[npolygons][0] = e0;
+          if (index == 2) {
+            polys[npolygons][1] = e4;
+            polys[npolygons][2] = e3;
+          }
+          else { // index == 13
+            polys[npolygons][1] = e3;
+            polys[npolygons][2] = e4;
+          }
+          polys[npolygons][3] = -1;
+
+          // on to the next tetrahedron
+          npolygons++;
+          break;
+
+        case 3:
+        case 12:             // plane slices a quadrilateral
+          // define edge values needed
+          e1 = Delan.Edges[i][1];
+          e2 = Delan.Edges[i][2];
+          e3 = Delan.Edges[i][3];
+          e4 = Delan.Edges[i][4];
+
+          // fill in edge interpolation values if they haven't been found
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e1])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e1] != edgeInterp[0][e1]) {
+            float a = (isolevel - f2)/(f0 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e1] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e1] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e1] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e1] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e1] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e1] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e2])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e2] != edgeInterp[0][e2]) {
+            float a = (isolevel - f3)/(f0 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e2] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e2] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e2] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e2] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e2] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e2] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e4])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e4] != edgeInterp[0][e4]) {
+            float a = (isolevel - f3)/(f1 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e4] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e4] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e4] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e4] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e4] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e4] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e3])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e3] != edgeInterp[0][e3]) {
+            float a = (isolevel - f2)/(f1 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e3] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e3] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e3] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e3] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e3] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e3] = nvertex;
+            nvertex++;
+          }
+
+
+          // fill in the polys array
+          polys[npolygons][0] = e1;
+          if (index == 3) {
+            polys[npolygons][1] = e2;
+            polys[npolygons][2] = e4;
+            polys[npolygons][3] = e3;
+          }
+          else { // index == 12
+            polys[npolygons][1] = e3;
+            polys[npolygons][2] = e4;
+            polys[npolygons][3] = e2;
+          }
+
+          // on to the next tetrahedron
+          npolygons++;
+          break;
+
+        case 4:
+        case 11:             // plane slices a triangle
+          // define edge values needed
+          e1 = Delan.Edges[i][1];
+          e3 = Delan.Edges[i][3];
+          e5 = Delan.Edges[i][5];
+
+          // fill in edge interpolation values if they haven't been found
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e1])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e1] != edgeInterp[0][e1]) {
+            float a = (isolevel - f2)/(f0 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e1] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e1] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e1] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e1] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e1] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e1] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e3])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e3] != edgeInterp[0][e3]) {
+            float a = (isolevel - f2)/(f1 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e3] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e3] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e3] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e3] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e3] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e3] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e5])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e5] != edgeInterp[0][e5]) {
+            float a = (isolevel - f3)/(f2 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e5] = (float) a*mySamples[0][v2] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e5] = (float) a*mySamples[1][v2] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e5] = (float) a*mySamples[2][v2] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e5] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e5] = (float) a*auxValues[j][v2] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e5] = nvertex;
+            nvertex++;
+          }
+
+          // fill in the polys array
+          polys[npolygons][0] = e1;
+          if (index == 4) {
+            polys[npolygons][1] = e3;
+            polys[npolygons][2] = e5;
+          }
+          else { // index == 11
+            polys[npolygons][1] = e5;
+            polys[npolygons][2] = e3;
+          }
+          polys[npolygons][3] = -1;
+
+          // on to the next tetrahedron
+          npolygons++;
+          break;
+
+        case 5:
+        case 10:             // plane slices a quadrilateral
+          // define edge values needed
+          e0 = Delan.Edges[i][0];
+          e2 = Delan.Edges[i][2];
+          e3 = Delan.Edges[i][3];
+          e5 = Delan.Edges[i][5];
+
+          // fill in edge interpolation values if they haven't been found
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e0])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e0] != edgeInterp[0][e0]) {
+            float a = (isolevel - f1)/(f0 - f1);
+            if (a < 0) a = -a;
+            edgeInterp[0][e0] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v1];
+            edgeInterp[1][e0] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v1];
+            edgeInterp[2][e0] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v1];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) );
+              auxInterp[j][e0] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e0] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v1];
+*/
+            }
+            globalToVertex[e0] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e2])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e2] != edgeInterp[0][e2]) {
+            float a = (isolevel - f3)/(f0 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e2] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e2] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e2] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e2] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e2] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e2] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e5])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e5] != edgeInterp[0][e5]) {
+            float a = (isolevel - f3)/(f2 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e5] = (float) a*mySamples[0][v2] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e5] = (float) a*mySamples[1][v2] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e5] = (float) a*mySamples[2][v2] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e5] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e5] = (float) a*auxValues[j][v2] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e5] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e3])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e3] != edgeInterp[0][e3]) {
+            float a = (isolevel - f2)/(f1 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e3] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e3] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e3] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e3] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e3] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e3] = nvertex;
+            nvertex++;
+          }
+
+
+          // fill in the polys array
+          polys[npolygons][0] = e0;
+          if (index == 5) {
+            polys[npolygons][1] = e3;
+            polys[npolygons][2] = e5;
+            polys[npolygons][3] = e2;
+          }
+          else { // index == 10
+            polys[npolygons][1] = e2;
+            polys[npolygons][2] = e5;
+            polys[npolygons][3] = e3;
+          }
+
+          // on to the next tetrahedron
+          npolygons++;
+          break;
+
+        case 6:
+        case 9:              // plane slices a quadrilateral
+          // define edge values needed
+          e0 = Delan.Edges[i][0];
+          e1 = Delan.Edges[i][1];
+          e4 = Delan.Edges[i][4];
+          e5 = Delan.Edges[i][5];
+
+          // fill in edge interpolation values if they haven't been found
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e0])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e0] != edgeInterp[0][e0]) {
+            float a = (isolevel - f1)/(f0 - f1);
+            if (a < 0) a = -a;
+            edgeInterp[0][e0] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v1];
+            edgeInterp[1][e0] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v1];
+            edgeInterp[2][e0] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v1];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) );
+              auxInterp[j][e0] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e0] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v1];
+*/
+            }
+            globalToVertex[e0] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e1])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e1] != edgeInterp[0][e1]) {
+            float a = (isolevel - f2)/(f0 - f2);
+            if (a < 0) a = -a;
+            edgeInterp[0][e1] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v2];
+            edgeInterp[1][e1] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v2];
+            edgeInterp[2][e1] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v2];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) );
+              auxInterp[j][e1] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e1] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v2];
+*/
+            }
+            globalToVertex[e1] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e5])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e5] != edgeInterp[0][e5]) {
+            float a = (isolevel - f3)/(f2 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e5] = (float) a*mySamples[0][v2] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e5] = (float) a*mySamples[1][v2] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e5] = (float) a*mySamples[2][v2] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e5] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e5] = (float) a*auxValues[j][v2] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e5] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e4])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e4] != edgeInterp[0][e4]) {
+            float a = (isolevel - f3)/(f1 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e4] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e4] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e4] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e4] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e4] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e4] = nvertex;
+            nvertex++;
+          }
+
+
+          // fill in the polys array
+          polys[npolygons][0] = e0;
+          if (index == 6) {
+            polys[npolygons][1] = e4;
+            polys[npolygons][2] = e5;
+            polys[npolygons][3] = e1;
+          }
+          else { // index == 9
+            polys[npolygons][1] = e1;
+            polys[npolygons][2] = e5;
+            polys[npolygons][3] = e4;
+          }
+
+          // on to the next tetrahedron
+          npolygons++;
+          break;
+
+        case 7:
+        case 8:              // plane slices a triangle
+          // interpolate between 3:0, 3:1, 3:2 for tri edges, same as case 8
+          // define edge values needed
+          e2 = Delan.Edges[i][2];
+          e4 = Delan.Edges[i][4];
+          e5 = Delan.Edges[i][5];
+
+          // fill in edge interpolation values if they haven't been found
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e2])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e2] != edgeInterp[0][e2]) {
+            float a = (isolevel - f3)/(f0 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e2] = (float) a*mySamples[0][v0] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e2] = (float) a*mySamples[1][v0] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e2] = (float) a*mySamples[2][v0] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v0] < 0) ?
+                    ((float) auxValues[j][v0]) + 256.0f :
+                    ((float) auxValues[j][v0]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e2] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e2] = (float) a*auxValues[j][v0] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e2] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e4])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e4] != edgeInterp[0][e4]) {
+            float a = (isolevel - f3)/(f1 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e4] = (float) a*mySamples[0][v1] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e4] = (float) a*mySamples[1][v1] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e4] = (float) a*mySamples[2][v1] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v1] < 0) ?
+                    ((float) auxValues[j][v1]) + 256.0f :
+                    ((float) auxValues[j][v1]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e4] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e4] = (float) a*auxValues[j][v1] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e4] = nvertex;
+            nvertex++;
+          }
+/* WLH 25 Oct 97
+          if (Double.isNaN(edgeInterp[0][e5])) {
+*/
+          // test for missing
+          if (edgeInterp[0][e5] != edgeInterp[0][e5]) {
+            float a = (isolevel - f3)/(f2 - f3);
+            if (a < 0) a = -a;
+            edgeInterp[0][e5] = (float) a*mySamples[0][v2] + (1-a)*mySamples[0][v3];
+            edgeInterp[1][e5] = (float) a*mySamples[1][v2] + (1-a)*mySamples[1][v3];
+            edgeInterp[2][e5] = (float) a*mySamples[2][v2] + (1-a)*mySamples[2][v3];
+            for (int j=0; j<naux; j++) {
+              t = (int) ( a * ((auxValues[j][v2] < 0) ?
+                    ((float) auxValues[j][v2]) + 256.0f :
+                    ((float) auxValues[j][v2]) ) +
+                  (1.0f - a) * ((auxValues[j][v3] < 0) ?
+                    ((float) auxValues[j][v3]) + 256.0f :
+                    ((float) auxValues[j][v3]) ) );
+              auxInterp[j][e5] = (byte)
+                ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+              auxInterp[j][e5] = (float) a*auxValues[j][v2] + (1-a)*auxValues[j][v3];
+*/
+            }
+            globalToVertex[e5] = nvertex;
+            nvertex++;
+          }
+
+          // fill in the polys array
+          polys[npolygons][0] = e2;
+          if (index == 7) {
+            polys[npolygons][1] = e4;
+            polys[npolygons][2] = e5;
+          }
+          else { // index == 8
+            polys[npolygons][1] = e5;
+            polys[npolygons][2] = e4;
+          }
+          polys[npolygons][3] = -1;
+
+          // on to the next tetrahedron
+          npolygons++;
+          break;
+      } // end switch (index)
+    } // end for (int i=0; i<trilength; i++)
+
+
+
+    if (DEBUG) {
+      System.out.println("\npolys (polys -> global edges) " + npolygons + "\n");
+      for (int i=0; i<npolygons; i++) {
+        String s = "  " + i + " -> ";
+        for (int j=0; j<4; j++) {
+          s = s + " " + polys[i][j];
+        }
+        System.out.println(s + "\n");
+      }
+    }
+
+
+
+    // transform polys array into polyToVert array
+    polyToVert[0] = new int[npolygons][];
+    for (int i=0; i<npolygons; i++) {
+      int n = polys[i][3] < 0 ? 3 : 4;
+      polyToVert[0][i] = new int[n];
+      for (int j=0; j<n; j++) polyToVert[0][i][j] = globalToVertex[polys[i][j]];
+    }
+
+
+    if (DEBUG) {
+      System.out.println("\npolyToVert (polys -> vertices) " + npolygons + "\n");
+      for (int i=0; i<npolygons; i++) {
+        String s = "  " + i + " -> ";
+        for (int j=0; j<polyToVert[0][i].length; j++) {
+          s = s + " " + polyToVert[0][i][j];
+        }
+        System.out.println(s + "\n");
+      }
+    }
+
+
+    // build nverts helper array
+    int[] nverts = new int[nvertex];
+    for (int i=0; i<nvertex; i++) nverts[i] = 0;
+    for (int i=0; i<npolygons; i++) {
+      nverts[polyToVert[0][i][0]]++;
+      nverts[polyToVert[0][i][1]]++;
+      nverts[polyToVert[0][i][2]]++;
+      if (polyToVert[0][i].length > 3) nverts[polyToVert[0][i][3]]++;
+    }
+
+    // initialize vertToPoly array
+    vertToPoly[0] = new int[nvertex][];
+    for (int i=0; i<nvertex; i++) {
+      vertToPoly[0][i] = new int[nverts[i]];
+    }
+
+    // fill in vertToPoly array
+    for (int i=0; i<nvertex; i++) nverts[i] = 0;
+    for (int i=0; i<npolygons; i++) {
+      int a = polyToVert[0][i][0];
+      int b = polyToVert[0][i][1];
+      int c = polyToVert[0][i][2];
+      vertToPoly[0][a][nverts[a]++] = i;
+      vertToPoly[0][b][nverts[b]++] = i;
+      vertToPoly[0][c][nverts[c]++] = i;
+      if (polyToVert[0][i].length > 3) {
+        int d = polyToVert[0][i][3];
+        if (d != -1) vertToPoly[0][d][nverts[d]++] = i;
+      }
+    }
+
+
+
+    if (DEBUG) {
+      System.out.println("\nvertToPoly (vertices -> polys) " + nvertex + "\n");
+      for (int i=0; i<nvertex; i++) {
+        String s = "  " + i + " -> ";
+        for (int j=0; j<vertToPoly[0][i].length; j++) {
+          s = s + " " + vertToPoly[0][i][j];
+        }
+        System.out.println(s + "\n");
+      }
+    }
+
+
+
+    // set up fieldVertices and auxLevels
+    fieldVertices[0] = new float[nvertex];
+    fieldVertices[1] = new float[nvertex];
+    fieldVertices[2] = new float[nvertex];
+    for (int j=0; j<naux; j++) {
+      auxLevels[j] = new byte[nvertex];
+    }
+    for (int i=0; i<Delan.NumEdges; i++) {
+      int k = globalToVertex[i];
+      if (k >= 0) {
+        fieldVertices[0][k] = edgeInterp[0][i];
+        fieldVertices[1][k] = edgeInterp[1][i];
+        fieldVertices[2][k] = edgeInterp[2][i];
+        for (int j=0; j<naux; j++) {
+          auxLevels[j][k] = auxInterp[j][i];
+        }
+      }
+    }
+
+
+    if (DEBUG) {
+      System.out.println("\nfieldVertices " + nvertex + "\n");
+      for (int i=0; i<nvertex; i++) {
+        String s = "  " + i + " -> ";
+        for (int j=0; j<3; j++) {
+          s = s + " " + fieldVertices[j][i];
+        }
+        System.out.println(s + "\n");
+      }
+    }
+
+  }
+
+/*
+  make_normals and poly_triangle_stripe altered according to:
+  Pol_f_Vert[9*i + 8]   --> vertToPoly[i].length
+  Pol_f_Vert[9*i + off] --> vertToPoly[i][off]
+  Vert_f_Pol[7*i + 6]   --> polyToVert[i].length
+  Vert_f_Pol[7*i + off] --> polyToVert[i][off]
+*/
+
+  /* from Contour3D.java, used by make_normals */
+  static final float  EPS_0 = (float) 1.0e-5;
+
+/* copied from Contour3D.java */
+  private static void make_normals(float[] VX, float[] VY, float[] VZ,
+                   float[] NX, float[] NY, float[] NZ, int nvertex,
+                   int npolygons, float[] Pnx, float[] Pny, float[] Pnz,
+                   float[] NxA, float[] NxB, float[] NyA, float[] NyB,
+                   float[] NzA, float[] NzB,
+                   int[][] vertToPoly, int[][] polyToVert)
+/* WLH 25 Oct 97
+                   int[] Pol_f_Vert, int[] Vert_f_Pol )
+*/
+          throws VisADException {
+
+   int   i, k,  n;
+   int   i1, i2, ix, iy, iz, ixb, iyb, izb;
+   int   max_vert_per_pol, swap_flag;
+   float x, y, z, a, minimum_area, len;
+
+   int iv[] = new int[3];
+   if (nvertex <= 0) return;
+
+
+   for (i = 0; i < nvertex; i++) {
+      NX[i] = 0;
+      NY[i] = 0;
+      NZ[i] = 0;
+   }
+
+   // WLH 12 Nov 2001
+   // minimum_area = (float) ((1.e-4 > EPS_0) ? 1.e-4 : EPS_0);
+   minimum_area = Float.MIN_VALUE;
+
+   /* Calculate maximum number of vertices per polygon */
+/* WLH 25 Oct 97
+   k = 6;
+   n = 7*npolygons;
+*/
+   k = 0;
+   while ( true ) {
+/* WLH 25 Oct 97
+       for (i=k+7; i<n; i+=7)
+           if (Vert_f_Pol[i] > Vert_f_Pol[k]) break;
+*/
+       for (i=k+1; i<npolygons; i++)
+           if (polyToVert[i].length > polyToVert[k].length) break;
+
+/* WLH 25 Oct 97
+       if (i >= n) break;
+*/
+       if (i >= npolygons) break;
+       k = i;
+   }
+/* WLH 25 Oct 97
+   max_vert_per_pol = Vert_f_Pol[k];
+*/
+   max_vert_per_pol = polyToVert[k].length;
+
+   /* Calculate the Normals vector components for each Polygon */
+   for ( i=0; i<npolygons; i++) {  /* Vectorized */
+/* WLH 25 Oct 97
+      if (Vert_f_Pol[6+i*7]>0) {
+         NxA[i] = VX[Vert_f_Pol[1+i*7]] - VX[Vert_f_Pol[0+i*7]];
+         NyA[i] = VY[Vert_f_Pol[1+i*7]] - VY[Vert_f_Pol[0+i*7]];
+         NzA[i] = VZ[Vert_f_Pol[1+i*7]] - VZ[Vert_f_Pol[0+i*7]];
+      }
+*/
+      if (polyToVert[i].length>0) {
+         int j1 = polyToVert[i][1];
+         int j0 = polyToVert[i][0];
+         NxA[i] = VX[j1] - VX[j0];
+         NyA[i] = VY[j1] - VY[j0];
+         NzA[i] = VZ[j1] - VZ[j0];
+      }
+   }
+
+   swap_flag = 0;
+   for ( k = 2; k < max_vert_per_pol; k++ ) {
+
+      if (swap_flag==0) {
+         /*$dir no_recurrence */        /* Vectorized */
+         for ( i=0; i<npolygons; i++ ) {
+/* WLH 25 Oct 97
+            if ( Vert_f_Pol[k+i*7] >= 0 ) {
+               NxB[i]  = VX[Vert_f_Pol[k+i*7]] - VX[Vert_f_Pol[0+i*7]];
+               NyB[i]  = VY[Vert_f_Pol[k+i*7]] - VY[Vert_f_Pol[0+i*7]];
+               NzB[i]  = VZ[Vert_f_Pol[k+i*7]] - VZ[Vert_f_Pol[0+i*7]];
+*/
+            if ( k < polyToVert[i].length ) {
+               int jk = polyToVert[i][k];
+               int j0 = polyToVert[i][0];
+               NxB[i]  = VX[jk] - VX[j0];
+               NyB[i]  = VY[jk] - VY[j0];
+               NzB[i]  = VZ[jk] - VZ[j0];
+               Pnx[i] = NyA[i]*NzB[i] - NzA[i]*NyB[i];
+               Pny[i] = NzA[i]*NxB[i] - NxA[i]*NzB[i];
+               Pnz[i] = NxA[i]*NyB[i] - NyA[i]*NxB[i];
+               NxA[i] = Pnx[i]*Pnx[i] + Pny[i]*Pny[i] + Pnz[i]*Pnz[i];
+               if (NxA[i] > minimum_area) {
+                  Pnx[i] /= NxA[i];
+                  Pny[i] /= NxA[i];
+                  Pnz[i] /= NxA[i];
+               }
+            }
+         }
+      }
+      else {  /* swap_flag!=0 */
+         /*$dir no_recurrence */        /* Vectorized */
+         for (i=0; i<npolygons; i++) {
+/* WLH 25 Oct 97
+            if ( Vert_f_Pol[k+i*7] >= 0 ) {
+               NxA[i]  = VX[Vert_f_Pol[k+i*7]] - VX[Vert_f_Pol[0+i*7]];
+               NyA[i]  = VY[Vert_f_Pol[k+i*7]] - VY[Vert_f_Pol[0+i*7]];
+               NzA[i]  = VZ[Vert_f_Pol[k+i*7]] - VZ[Vert_f_Pol[0+i*7]];
+*/
+            if ( k < polyToVert[i].length ) {
+               int jk = polyToVert[i][k];
+               int j0 = polyToVert[i][0];
+               NxA[i]  = VX[jk] - VX[j0];
+               NyA[i]  = VY[jk] - VY[j0];
+               NzA[i]  = VZ[jk] - VZ[j0];
+               Pnx[i] = NyB[i]*NzA[i] - NzB[i]*NyA[i];
+               Pny[i] = NzB[i]*NxA[i] - NxB[i]*NzA[i];
+               Pnz[i] = NxB[i]*NyA[i] - NyB[i]*NxA[i];
+               NxB[i] = Pnx[i]*Pnx[i] + Pny[i]*Pny[i] + Pnz[i]*Pnz[i];
+               if (NxB[i] > minimum_area) {
+                  Pnx[i] /= NxB[i];
+                  Pny[i] /= NxB[i];
+                  Pnz[i] /= NxB[i];
+               }
+            }
+         } // end for (i=0; i<npolygons; i++)
+      } // end swap_flag!=0
+
+       /* This Loop <CAN'T> be Vectorized */
+      for ( i=0; i<npolygons; i++ ) {
+/* WLH 25 Oct 97
+         if (Vert_f_Pol[k+i*7] >= 0) {
+            iv[0] = Vert_f_Pol[0+i*7];
+            iv[1] = Vert_f_Pol[(k-1)+i*7];
+            iv[2] = Vert_f_Pol[k+i*7];
+*/
+         if (k < polyToVert[i].length) {
+            iv[0] = polyToVert[i][0];
+            iv[1] = polyToVert[i][k-1];
+            iv[2] = polyToVert[i][k];
+              x = Pnx[i];   y = Pny[i];   z = Pnz[i];
+
+/*
+System.out.println("vertices: " + iv[0] + " " + iv[1] + " " + iv[2]);
+System.out.println("  normal: " + x + " " + y + " " + z + "\n");
+*/
+
+            // Update the origin vertex
+               NX[iv[0]] += x;   NY[iv[0]] += y;   NZ[iv[0]] += z;
+
+            // Update the vertex that defines the first vector
+               NX[iv[1]] += x;   NY[iv[1]] += y;   NZ[iv[1]] += z;
+
+            // Update the vertex that defines the second vector
+               NX[iv[2]] += x;   NY[iv[2]] += y;   NZ[iv[2]] += z;
+         }
+      } // end for ( i=0; i<npolygons; i++ )
+
+       swap_flag = ( (swap_flag != 0) ? 0 : 1 );
+   } // end for ( k = 2; k < max_vert_per_pol; k++ )
+
+    /* Normalize the Normals */
+    for ( i=0; i<nvertex; i++ ) {  /* Vectorized */
+        len = (float) Math.sqrt(NX[i]*NX[i] + NY[i]*NY[i] + NZ[i]*NZ[i]);
+        if (len > EPS_0) {
+            NX[i] /= len;
+            NY[i] /= len;
+            NZ[i] /= len;
+        }
+    }
+
+  }
+
+  /* from Contour3D.java, used by poly_triangle_stripe */
+  static final int NTAB[] =
+  {   0,1,2,       1,2,0,       2,0,1,
+      0,1,3,2,     1,2,0,3,     2,3,1,0,     3,0,2,1,
+      0,1,4,2,3,   1,2,0,3,4,   2,3,1,4,0,   3,4,2,0,1,   4,0,3,1,2,
+      0,1,5,2,4,3, 1,2,0,3,5,4, 2,3,1,4,0,5, 3,4,2,5,1,0, 4,5,3,0,2,1,
+      5,0,4,1,3,2
+  };
+
+  /* from Contour3D.java, used by poly_triangle_stripe */
+  static final int ITAB[] =
+  {   0,2,1,       1,0,2,       2,1,0,
+      0,3,1,2,     1,0,2,3,     2,1,3,0,     3,2,0,1,
+      0,4,1,3,2,   1,0,2,4,3,   2,1,3,0,4,   3,2,4,1,0,   4,3,0,2,1,
+      0,5,1,4,2,3, 1,0,2,5,3,4, 2,1,3,0,4,5, 3,2,4,1,5,0, 4,3,5,2,0,1,
+      5,4,0,3,1,2
+  };
+
+  /* from Contour3D.java, used by poly_triangle_stripe */
+  static final int STAB[] =  { 0, 9, 25, 50 };
+
+/* copied from Contour3D.java */
+  static int poly_triangle_stripe( int[] Tri_Stripe,
+                                   int nvertex, int npolygons,
+                                   int[][] vertToPoly, int[][] polyToVert)
+/* WLH 25 Oct 97
+                            int[] Pol_f_Vert, int[] Vert_f_Pol )
+*/
+          throws VisADException {
+   int  i, j, k, m, ii, npol, cpol, idx, off, Nvt,
+        vA, vB, ivA, ivB, iST, last_pol;
+   boolean f_line_conection = false;
+
+   boolean[] vet_pol = new boolean[npolygons];
+
+    last_pol = 0;
+    npol = 0;
+    iST = 0;
+    ivB = 0;
+
+    for (i=0; i<npolygons; i++) vet_pol[i] = true;  /* Vectorized */
+
+    while (true)
+    {
+        /* find_unselected_pol(cpol); */
+        for (cpol=last_pol; cpol<npolygons; cpol++) {
+           if ( vet_pol[cpol] ) break;
+        }
+        if (cpol == npolygons) {
+            cpol = -1;
+        }
+        else {
+            last_pol = cpol;
+        }
+        /* end  find_unselected_pol(cpol); */
+
+        if (cpol < 0) break;
+/*      ypdate_polygon            */
+// System.out.println("1 vet_pol[" + cpol + "] = false");
+        vet_pol[cpol] = false;
+/* end     update_polygon            */
+
+/*      get_vertices_of_pol(cpol,Vt,Nvt); {    */
+/* WLH 25 Oct 97
+            Nvt = Vert_f_Pol[(j=cpol*7)+6];
+            off = j;
+*/
+            Nvt = polyToVert[cpol].length;
+/*      }                                      */
+/* end      get_vertices_of_pol(cpol,Vt,Nvt); {    */
+
+
+        for (ivA=0; ivA<Nvt; ivA++) {
+            ivB = (((ivA+1)==Nvt) ? 0:(ivA+1));
+/*          get_pol_vert(Vt[ivA],Vt[ivB],npol) { */
+               npol = -1;
+/* WLH 25 Oct 97
+               if (Vert_f_Pol[ivA+off]>=0 && Vert_f_Pol[ivB+off]>=0) {
+                  i=Vert_f_Pol[ivA+off]*9;
+                  k=i+Pol_f_Vert [i+8];
+                  j=Vert_f_Pol[ivB+off]*9;
+                  m=j+Pol_f_Vert [j+8];
+                  while (i>0 && j>0 && i<k && j <m ) {
+                     if (Pol_f_Vert [i] == Pol_f_Vert [j] &&
+                         vet_pol[Pol_f_Vert[i]] ) {
+                        npol=Pol_f_Vert [i];
+                        break;
+                     }
+                     else if (Pol_f_Vert [i] < Pol_f_Vert [j])
+                          i++;
+                     else
+                          j++;
+                  }
+               }
+*/
+               if (ivA < polyToVert[cpol].length && ivB < polyToVert[cpol].length) {
+                  i=polyToVert[cpol][ivA];
+                  int ilim = vertToPoly[i].length;
+                  k = 0;
+                  j=polyToVert[cpol][ivB];
+                  int jlim = vertToPoly[j].length;
+                  m = 0;
+                  // while (0<k && k<ilim && 0<m && m<jlim) {
+                  while (0<i && k<ilim && 0<j && m<jlim) {
+                     if (vertToPoly[i][k] == vertToPoly[j][m] &&
+                         vet_pol[vertToPoly[i][k]] ) {
+                        npol=vertToPoly[i][k];
+                        break;
+                     }
+                     else if (vertToPoly[i][k] < vertToPoly[j][m])
+                          k++;
+                     else
+                          m++;
+                  }
+               }
+
+/*          }                                   */
+/* end          get_pol_vert(Vt[ivA],Vt[ivB],npol) { */
+            if (npol >= 0) break;
+        }
+        /* insert polygon alone */
+        if (npol < 0)
+        { /*ptT = NTAB + STAB[Nvt-3];*/
+            idx = STAB[Nvt-3];
+            if (iST > 0)
+            {   Tri_Stripe[iST]   = Tri_Stripe[iST-1];    iST++;
+/* WLH 25 Oct 97
+                Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx]+off];
+*/
+// System.out.println("1 Tri_Stripe[" + iST + "] from poly " + cpol);
+                Tri_Stripe[iST++] = polyToVert[cpol][NTAB[idx]];
+            }
+            else f_line_conection = true; /* WLH 3-9-95 added */
+            for (ii=0; ii< ((Nvt < 6) ? Nvt:6); ii++) {
+/* WLH 25 Oct 97
+                Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx++]+off];
+*/
+// System.out.println("2 Tri_Stripe[" + iST + "] from poly " + cpol);
+                Tri_Stripe[iST++] = polyToVert[cpol][NTAB[idx++]];
+              }
+            continue;
+        }
+
+        if (( (ivB != 0) && ivA==(ivB-1)) || ( !(ivB != 0) && ivA==Nvt-1)) {
+         /* ptT = ITAB + STAB[Nvt-3] + (ivB+1)*Nvt; */
+            idx = STAB[Nvt-3] + (ivB+1)*Nvt;
+
+            if (f_line_conection)
+            {   Tri_Stripe[iST]   = Tri_Stripe[iST-1];    iST++;
+/* WLH 25 Oct 97
+                Tri_Stripe[iST++] = Vert_f_Pol[ITAB[idx-1]+off];
+*/
+// System.out.println("3 Tri_Stripe[" + iST + "] from poly " + cpol);
+                Tri_Stripe[iST++] = polyToVert[cpol][ITAB[idx-1]];
+                f_line_conection = false;
+            }
+            for (ii=0; ii<((Nvt < 6) ? Nvt:6); ii++) {
+/* WLH 25 Oct 97
+                Tri_Stripe[iST++] = Vert_f_Pol[ITAB[--idx]+off];
+*/
+// System.out.println("4 Tri_Stripe[" + iST + "] from poly " + cpol);
+                Tri_Stripe[iST++] = polyToVert[cpol][ITAB[--idx]];
+            }
+
+        }
+        else {
+         /* ptT = NTAB + STAB[Nvt-3] + (ivB+1)*Nvt; */
+            idx = STAB[Nvt-3] + (ivB+1)*Nvt;
+
+            if (f_line_conection)
+            {   Tri_Stripe[iST]   = Tri_Stripe[iST-1];    iST++;
+/* WLH 25 Oct 97
+                Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx-1]+off];
+*/
+// System.out.println("5 Tri_Stripe[" + iST + "] from poly " + cpol);
+                Tri_Stripe[iST++] = polyToVert[cpol][NTAB[idx-1]];
+                f_line_conection = false;
+            }
+            for (ii=0; ii<((Nvt < 6) ? Nvt:6); ii++) {
+/* WLH 25 Oct 97
+                Tri_Stripe[iST++] = Vert_f_Pol[NTAB[--idx]+off];
+*/
+// System.out.println("6 Tri_Stripe[" + iST + "] from poly " + cpol);
+                Tri_Stripe[iST++] = polyToVert[cpol][NTAB[--idx]];
+            }
+
+        }
+
+        vB = Tri_Stripe[iST-1];
+        vA = Tri_Stripe[iST-2];
+        cpol = npol;
+
+        while (true)
+        {
+/*          get_vertices_of_pol(cpol,Vt,Nvt)  {   */
+/* WLH 25 Oct 97
+                Nvt = Vert_f_Pol [(j=cpol*7)+6];
+                off = j;
+*/
+                Nvt = polyToVert[cpol].length;
+/*          }                                     */
+
+
+/*          update_polygon(cpol)                  */
+// System.out.println("2 vet_pol[" + cpol + "] = false");
+            vet_pol[cpol] = false;
+/* WLH 25 Oct 97
+            for (ivA=0; ivA<Nvt && Vert_f_Pol[ivA+off]!=vA; ivA++);
+            for (ivB=0; ivB<Nvt && Vert_f_Pol[ivB+off]!=vB; ivB++);
+*/
+            for (ivA=0; ivA<Nvt && polyToVert[cpol][ivA]!=vA; ivA++);
+            for (ivB=0; ivB<Nvt && polyToVert[cpol][ivB]!=vB; ivB++);
+                 if (( (ivB != 0) && ivA==(ivB-1)) || (!(ivB != 0) && ivA==Nvt-1)) {
+                /* ptT = NTAB + STAB[Nvt-3] + ivA*Nvt + 2; */
+                    idx = STAB[Nvt-3] + ivA*Nvt + 2;
+
+                    for (ii=2; ii<((Nvt < 6) ? Nvt:6); ii++) {
+/* WLH 25 Oct 97
+                        Tri_Stripe[iST++] = Vert_f_Pol[NTAB[idx++]+off];
+*/
+// System.out.println("7 Tri_Stripe[" + iST + "] from poly " + cpol);
+                        Tri_Stripe[iST++] = polyToVert[cpol][NTAB[idx++]];
+                    }
+                 }
+                 else {
+                /*  ptT = ITAB + STAB[Nvt-3] + ivA*Nvt + 2; */
+                    idx = STAB[Nvt-3] + ivA*Nvt + 2;
+
+                    for (ii=2; ii<((Nvt < 6) ? Nvt:6); ii++) {
+/* WLH 25 Oct 97
+                        Tri_Stripe[iST++] = Vert_f_Pol[ITAB[idx++]+off];
+*/
+// System.out.println("8 Tri_Stripe[" + iST + "] from poly " + cpol);
+                        Tri_Stripe[iST++] = polyToVert[cpol][ITAB[idx++]];
+                    }
+                 }
+
+            vB = Tri_Stripe[iST-1];
+            vA = Tri_Stripe[iST-2];
+
+/*          get_pol_vert(vA,vB,cpol) {     */
+               cpol = -1;
+/* WLH 25 Oct 97
+               if (vA>=0 && vB>=0) {
+                 i=vA*9;
+                 k=i+Pol_f_Vert [i+8];
+                 j=vB*9;
+                 m=j+Pol_f_Vert [j+8];
+                 while (i>0 && j>0 && i<k && j<m) {
+                    if (Pol_f_Vert [i] == Pol_f_Vert [j] &&
+                        vet_pol[Pol_f_Vert[i]] ) {
+                      cpol=Pol_f_Vert[i];
+                      break;
+                    }
+                    else if (Pol_f_Vert [i] < Pol_f_Vert [j])
+                      i++;
+                    else
+                      j++;
+                 }
+               }
+*/
+               if (vA>=0 && vB>=0) {
+                 int ilim = vertToPoly[vA].length;
+                 k = 0;
+                 int jlim = vertToPoly[vB].length;
+                 m = 0;
+                 // while (0<k && k<ilim && 0<m && m<jlim) {
+                 while (0<vA && k<ilim && 0<vB && m<jlim) {
+                    if (vertToPoly[vA][k] == vertToPoly[vB][m] &&
+                        vet_pol[vertToPoly[vA][k]] ) {
+                      cpol=vertToPoly[vA][k];
+                      break;
+                    }
+                    else if (vertToPoly[vA][k] < vertToPoly[vB][m])
+                      k++;
+                    else
+                      m++;
+                 }
+               }
+
+/*         }                               */
+
+            if (cpol < 0) {
+
+                vA = Tri_Stripe[iST-3];
+/*          get_pol_vert(vA,vB,cpol) {   */
+               cpol = -1;
+/* WLH 25 Oct 97
+               if (vA>=0 && vB>=0) {
+                 i=vA*9;
+                 k=i+Pol_f_Vert [i+8];
+                 j=vB*9;
+                 m=j+Pol_f_Vert [j+8];
+                 while (i>0 && j>0 && i<k && j<m) {
+                    if (Pol_f_Vert [i] == Pol_f_Vert [j] &&
+                        vet_pol[Pol_f_Vert[i]] ) {
+                      cpol=Pol_f_Vert[i];
+                      break;
+                    }
+                    else if (Pol_f_Vert [i] < Pol_f_Vert [j])
+                      i++;
+                    else
+                      j++;
+                 }
+               }
+*/
+               if (vA>=0 && vB>=0) {
+                 int ilim = vertToPoly[vA].length;
+                 k = 0;
+                 int jlim = vertToPoly[vB].length;
+                 m = 0;
+                 // while (0<k && k<ilim && 0<m && m<jlim) {
+                 while (0<vA && k<ilim && 0<vB && m<jlim) {
+                    if (vertToPoly[vA][k] == vertToPoly[vB][m] &&
+                        vet_pol[vertToPoly[vA][k]] ) {
+                      cpol=vertToPoly[vA][k];
+                      break;
+                    }
+                    else if (vertToPoly[vA][k] < vertToPoly[vB][m])
+                      k++;
+                    else
+                      m++;
+                 }
+               }
+
+/*          }                            */
+                if (cpol < 0) {
+                    f_line_conection  = true;
+                    break;
+                }
+                else {
+// System.out.println("9 Tri_Stripe[" + iST + "] where cpol = " + cpol);
+                    // WLH 5 May 2004 - fix bug vintage 1990 or 91
+                    if (iST > 0) {
+                      Tri_Stripe[iST] = Tri_Stripe[iST-1];
+                      iST++;
+                    }
+                    Tri_Stripe[iST++] = vA;
+                    i = vA;
+                    vA = vB;
+                    vB = i;
+                }
+            }
+        }
+    }
+
+    return iST;
+  }
+
+  /** create a 2-D GeometryArray from this Set and color_values */
+  public VisADGeometryArray make2DGeometry(byte[][] color_values,
+         boolean indexed) throws VisADException {
+    if (DomainDimension != 3) {
+      throw new SetException("Irregular3DSet.make2DGeometry: " +
+                             "DomainDimension must be 3, not " +
+                             DomainDimension);
+    }
+    if (ManifoldDimension != 2) {
+      throw new SetException("Irregular3DSet.make2DGeometry: " +
+                              "ManifoldDimension must be 2, not " +
+                             ManifoldDimension);
+    }
+
+    int npolygons = Delan.Tri.length;
+    int nvertex = Delan.Vertices.length;
+    if (npolygons < 1 || nvertex < 3) return null;
+
+    // make sure all triangles have the same signature
+    // i.e., winding direction
+    int[][] Tri = Delan.Tri;
+    int[][] Walk = Delan.Walk;
+    int dim = Tri[0].length - 1;
+    int[][] tri = new int[npolygons][];
+    int[] poly_stack = new int[npolygons];
+    int[] walk_stack = new int[npolygons];
+    int sp; // stack pointer
+    for (int ii=0; ii<npolygons; ii++) {
+      // find an un-adjusted triangle
+      if (tri[ii] == null) {
+        // initialize its signature
+        tri[ii] = new int[3];
+        tri[ii][0] = Tri[ii][0];
+        tri[ii][1] = Tri[ii][1];
+        tri[ii][2] = Tri[ii][2];
+        // first stack entry, for recursive search of triangles
+        // via Walk array
+        sp = 0;
+        walk_stack[sp] = 0;
+        poly_stack[sp] = ii;
+        while (true) {
+          // find neighbor triangle via Walk
+          int i = poly_stack[sp];
+          int w = walk_stack[sp];
+          int j = Walk[i][w];
+          if (j >= 0 && tri[j] == null) {
+            // compare signatures of neighbors
+            int v1 = Tri[i][w];
+            int v2 = Tri[i][(w + 1) % 3];
+            int i1 = -1;
+            int i2 = -1;
+            int j1 = -1;
+            int j2 = -1;
+            for (int k=0; k<3; k++) {
+              if (tri[i][k] == v1) i1 = k;
+              if (tri[i][k] == v2) i2 = k;
+              if (Tri[j][k] == v1) j1 = k;
+              if (Tri[j][k] == v2) j2 = k;
+            }
+            tri[j] = new int[3];
+            tri[j][0] = Tri[j][0];
+            if ( ( (((i1 + 1) % 3) == i2) && (((j1 + 1) % 3) == j2) ) ||
+                 ( (((i2 + 1) % 3) == i1) && (((j2 + 1) % 3) == j1) ) ) {
+              tri[j][1] = Tri[j][2];
+              tri[j][2] = Tri[j][1];
+            }
+            else {
+              tri[j][1] = Tri[j][1];
+              tri[j][2] = Tri[j][2];
+            }
+            // add j to stack
+            sp++;
+            walk_stack[sp] = 0;
+            poly_stack[sp] = j;
+          }
+          else { // (j < 0 || tri[j] != null)
+            while (true) {
+              walk_stack[sp]++;
+              if (walk_stack[sp] < 3) {
+                break;
+              }
+              else {
+                sp--;
+                if (sp < 0) break;
+              }
+            } // end while (true)
+          } // end if (j < 0 || tri[j] != null)
+          if (sp < 0) break;
+        } // end while (true)
+      } // end if (tri[ii] == null)
+    } // end for (int ii=0; ii<npolygons; ii++)
+
+    float[][] samples = getSamples(false);
+    float[] NxA = new float[npolygons];
+    float[] NxB = new float[npolygons];
+    float[] NyA = new float[npolygons];
+    float[] NyB = new float[npolygons];
+    float[] NzA = new float[npolygons];
+    float[] NzB = new float[npolygons];
+    float[] Pnx = new float[npolygons];
+    float[] Pny = new float[npolygons];
+    float[] Pnz = new float[npolygons];
+    float[] NX = new float[nvertex];
+    float[] NY = new float[nvertex];
+    float[] NZ = new float[nvertex];
+
+    make_normals(samples[0], samples[1], samples[2],
+                 NX, NY, NZ, nvertex, npolygons, Pnx, Pny, Pnz,
+                 NxA, NxB, NyA, NyB, NzA, NzB, Delan.Vertices, tri);
+                 // NxA, NxB, NyA, NyB, NzA, NzB, Delan.Vertices, Delan.Tri);
+
+    // take the garbage out
+    NxA = NxB = NyA = NyB = NzA = NzB = Pnx = Pny = Pnz = null;
+
+    float[] normals = new float[3 * nvertex];
+    int j = 0;
+    for (int i=0; i<nvertex; i++) {
+      normals[j++] = (float) NX[i];
+      normals[j++] = (float) NY[i];
+      normals[j++] = (float) NZ[i];
+    }
+    // take the garbage out
+    NX = NY = NZ = null;
+
+    // temporary array to hold maximum possible polytriangle strip
+    int[] stripe = new int[6 * npolygons];
+    int size_stripe =
+      poly_triangle_stripe(stripe, nvertex, npolygons,
+                           Delan.Vertices, Delan.Tri);
+
+    if (indexed) {
+      VisADIndexedTriangleStripArray array =
+        new VisADIndexedTriangleStripArray();
+
+      // array.vertexFormat |= NORMALS;
+      array.normals = normals;
+      // take the garbage out
+      normals = null;
+
+      // set up indices
+      array.indexCount = size_stripe;
+      array.indices = new int[size_stripe];
+      System.arraycopy(stripe, 0, array.indices, 0, size_stripe);
+      array.stripVertexCounts = new int[1];
+      array.stripVertexCounts[0] = size_stripe;
+      // take the garbage out
+      stripe = null;
+
+      // set coordinates and colors
+      setGeometryArray(array, samples, 4, color_values);
+      // take the garbage out
+      samples = null;
+      return array;
+    }
+    else { // if (!indexed)
+      VisADTriangleStripArray array = new VisADTriangleStripArray();
+      array.stripVertexCounts = new int[] {size_stripe};
+      array.vertexCount = size_stripe;
+
+      array.normals = new float[3 * size_stripe];
+      int k = 0;
+      for (int i=0; i<3*size_stripe; i+=3) {
+        j = 3 * stripe[k];
+        array.normals[i] = normals[j];
+        array.normals[i+1] = normals[j+1];
+        array.normals[i+2] = normals[j+2];
+        k++;
+      }
+      normals = null;
+
+      array.coordinates = new float[3 * size_stripe];
+      k = 0;
+      for (int i=0; i<3*size_stripe; i+=3) {
+        j = stripe[k];
+        array.coordinates[i] = samples[0][j];
+        array.coordinates[i+1] = samples[1][j];
+        array.coordinates[i+2] = samples[2][j];
+/*
+System.out.println("strip[" + k + "] = (" + array.coordinates[i] + ", " +
+                   array.coordinates[i+1] + ", " +
+                   array.coordinates[i+2] + ")");
+*/
+        k++;
+      }
+      samples = null;
+
+      if (color_values != null) {
+        int color_length = color_values.length;
+        array.colors = new byte[color_length * size_stripe];
+        k = 0;
+        if (color_length == 4) {
+          for (int i=0; i<color_length*size_stripe; i+=color_length) {
+            j = stripe[k];
+            array.colors[i] = color_values[0][j];
+            array.colors[i+1] = color_values[1][j];
+            array.colors[i+2] = color_values[2][j];
+            array.colors[i+3] = color_values[3][j];
+            k++;
+          }
+        }
+        else { // if (color_length == 3)
+          for (int i=0; i<color_length*size_stripe; i+=color_length) {
+            j = stripe[k];
+            array.colors[i] = color_values[0][j];
+            array.colors[i+1] = color_values[1][j];
+            array.colors[i+2] = color_values[2][j];
+            k++;
+          }
+        }
+      }
+      color_values = null;
+      stripe = null;
+      return array;
+    } // end if (!indexed)
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    if (ManifoldDimension == 1) {
+	return new Irregular3DSet(type, getMySamples(), newToOld, oldToNew,
+                            DomainCoordinateSystem, SetUnits, SetErrors);
+    }
+    else {
+	return new Irregular3DSet(type, getMySamples(), DomainCoordinateSystem,
+                                SetUnits, SetErrors, Delan);
+    }
+  }
+
+  /* run 'java visad.Irregular3DSet' to test the Irregular3DSet class */
+  public static void main(String[] argv) throws VisADException {
+    float[][] samp = { {179, 232, 183, 244, 106, 344, 166, 304, 286},
+                        { 86, 231, 152, 123, 183, 153, 308, 325,  89},
+                        {121, 301, 346, 352, 123, 125, 187, 101, 142} };
+    RealType test1 = RealType.getRealType("x");
+    RealType test2 = RealType.getRealType("y");
+    RealType test3 = RealType.getRealType("z");
+    RealType[] t_array = {test1, test2, test3};
+    RealTupleType t_tuple = new RealTupleType(t_array);
+    Irregular3DSet iSet3D = new Irregular3DSet(t_tuple, samp);
+
+    // print out Samples information
+    float[][]mySamples = iSet3D.getMySamples();
+    System.out.println("Samples:");
+    for (int i=0; i<mySamples[0].length; i++) {
+      System.out.println("#"+i+":\t"+mySamples[0][i]+", "
+                                    +mySamples[1][i]+", "
+                                    +mySamples[2][i]);
+    }
+    System.out.println(iSet3D.Delan.Tri.length
+                     +" tetrahedrons in tetrahedralization.");
+
+
+    // test valueToIndex function
+    System.out.println("\nvalueToIndex test:");
+    float[][] value = { {189, 221, 319, 215, 196},
+                         {166, 161, 158, 139, 285},
+                         {207, 300, 127, 287, 194} };
+    int[] index = iSet3D.valueToIndex(value);
+    for (int i=0; i<index.length; i++) {
+      System.out.println(value[0][i]+", "+value[1][i]+", "
+                        +value[2][i]+"\t--> #"+index[i]);
+    }
+
+    // test valueToInterp function
+    System.out.println("\nvalueToInterp test:");
+    int[][] indices = new int[value[0].length][];
+    float[][] weights = new float[value[0].length][];
+    iSet3D.valueToInterp(value, indices, weights);
+    for (int i=0; i<value[0].length; i++) {
+      System.out.println(value[0][i]+", "+value[1][i]+", "
+                        +value[2][i]+"\t--> ["
+                        +indices[i][0]+", "
+                        +indices[i][1]+", "
+                        +indices[i][2]+", "
+                        +indices[i][3]+"]\tweight total: "
+                       +(weights[i][0]+weights[i][1]
+                        +weights[i][2]+weights[i][3]));
+    }
+
+    // test makeIsosurface function
+    System.out.println("\nmakeIsosurface test:");
+    float[] field = {100, 300, 320, 250, 80, 70, 135, 110, 105};
+    float[][] slice = new float[3][];
+    int[][][] polyvert = new int[1][][];
+    int[][][] vertpoly = new int[1][][];
+    iSet3D.makeIsosurface(288, field, null, slice, null, polyvert, vertpoly);
+    for (int i=0; i<slice[0].length; i++) {
+      for (int j=0; j<3; j++) {
+        slice[j][i] = (float) Math.round(1000*slice[j][i]) / 1000;
+      }
+    }
+    System.out.println("polygons:");
+    for (int i=0; i<polyvert[0].length; i++) {
+      System.out.print("#"+i+":");
+/* WLH 25 Oct 97
+      for (int j=0; j<4; j++) {
+        if (polyvert[0][i][j] != -1) {
+          if (j == 1) {
+            if (polyvert[0][i][3] == -1) {
+              System.out.print("(tri)");
+            }
+            else {
+              System.out.print("(quad)");
+            }
+          }
+          System.out.println("\t"+slice[0][polyvert[0][i][j]]
+                            +", "+slice[1][polyvert[0][i][j]]
+                            +", "+slice[2][polyvert[0][i][j]]);
+        }
+      }
+*/
+      for (int j=0; j<polyvert[0][i].length; j++) {
+        if (j == 1) {
+          if (polyvert[0][i].length == 3) {
+            System.out.print("(tri)");
+          }
+          else {
+            System.out.print("(quad)");
+          }
+        }
+        System.out.println("\t"+slice[0][polyvert[0][i][j]]
+                          +", "+slice[1][polyvert[0][i][j]]
+                          +", "+slice[2][polyvert[0][i][j]]);
+      }
+    }
+    System.out.println();
+    for (int i=0; i<polyvert[0].length; i++) {
+      int a = polyvert[0][i][0];
+      int b = polyvert[0][i][1];
+      int c = polyvert[0][i][2];
+      int d = polyvert[0][i].length==4 ? polyvert[0][i][3] : -1;
+      boolean found = false;
+      for (int j=0; j<vertpoly[0][a].length; j++) {
+        if (vertpoly[0][a][j] == i) found = true;
+      }
+      if (!found) {
+        System.out.println("vertToPoly array corrupted at triangle #"
+                           +i+" vertex #0!");
+      }
+      found = false;
+      for (int j=0; j<vertpoly[0][b].length; j++) {
+        if (vertpoly[0][b][j] == i) found = true;
+      }
+      if (!found) {
+        System.out.println("vertToPoly array corrupted at triangle #"
+                           +i+" vertex #1!");
+      }
+      found = false;
+      for (int j=0; j<vertpoly[0][c].length; j++) {
+        if (vertpoly[0][c][j] == i) found = true;
+      }
+      if (!found) {
+        System.out.println("vertToPoly array corrupted at triangle #"
+                           +i+" vertex #2!");
+      }
+      found = false;
+      if (d != -1) {
+        for (int j=0; j<vertpoly[0][d].length; j++) {
+          if (vertpoly[0][d][j] == i) found = true;
+        }
+        if (!found) {
+          System.out.println("vertToPoly array corrupted at triangle #"
+                             +i+" vertex #3!");
+        }
+      }
+    }
+  }
+
+/* Here's the output:
+
+iris 45% java visad.Irregular3DSet
+Samples:
+#0:     179.0, 86.0, 121.0
+#1:     232.0, 231.0, 301.0
+#2:     183.0, 152.0, 346.0
+#3:     244.0, 123.0, 352.0
+#4:     106.0, 183.0, 123.0
+#5:     344.0, 153.0, 125.0
+#6:     166.0, 308.0, 187.0
+#7:     304.0, 325.0, 101.0
+#8:     286.0, 89.0, 142.0
+15 tetrahedrons in tetrahedralization.
+
+valueToIndex test:
+189.0, 166.0, 207.0     --> #0
+221.0, 161.0, 300.0     --> #2
+319.0, 158.0, 127.0     --> #5
+215.0, 139.0, 287.0     --> #2
+196.0, 285.0, 194.0     --> #6
+
+valueToInterp test:
+189.0, 166.0, 207.0     --> [0, 1, 2, 4]        weight total: 1.0
+221.0, 161.0, 300.0     --> [1, 2, 3, 8]        weight total: 1.0
+319.0, 158.0, 127.0     --> [4, 5, 6, 8]        weight total: 0.9999999999999999
+215.0, 139.0, 287.0     --> [1, 2, 3, 8]        weight total: 1.0
+196.0, 285.0, 194.0     --> [1, 5, 6, 7]        weight total: 1.0
+
+makeIsosurface test:
+polygons:
+#0:     237.843, 226.93, 291.817
+(tri)   227.2, 236.6, 292.709
+        236.547, 236.937, 288.368
+#1:     228.82, 222.3, 290.2
+(quad)  182.418, 142.4, 313.273
+        225.127, 228.382, 291.291
+        172.733, 156.133, 316.267
+#2:     225.127, 228.382, 291.291
+(tri)   227.2, 236.6, 292.709
+        235.323, 222.262, 291.215
+#3:     228.82, 222.3, 290.2
+(quad)  182.418, 142.4, 313.273
+        235.323, 222.262, 291.215
+        198.33, 142.623, 315.637
+#4:     234.88, 205.08, 313.24
+(tri)   237.843, 226.93, 291.817
+        235.323, 222.262, 291.215
+#5:     182.418, 142.4, 313.273
+(tri)   210.886, 138.743, 348.743
+        198.33, 142.623, 315.637
+#6:     234.88, 205.08, 313.24
+(quad)  235.323, 222.262, 291.215
+        210.886, 138.743, 348.743
+        198.33, 142.623, 315.637
+#7:     225.127, 228.382, 291.291
+(quad)  227.2, 236.6, 292.709
+        172.733, 156.133, 316.267
+        180.059, 178.984, 318.497
+#8:     234.88, 205.08, 313.24
+(tri)   237.843, 226.93, 291.817
+        236.547, 236.937, 288.368
+#9:     228.82, 222.3, 290.2
+(tri)   225.127, 228.382, 291.291
+        235.323, 222.262, 291.215
+#10:    237.843, 226.93, 291.817
+(tri)   227.2, 236.6, 292.709
+        235.323, 222.262, 291.215
+
+iris 46%
+
+*/
+
+}
+
diff --git a/visad/IrregularSet.java b/visad/IrregularSet.java
new file mode 100644
index 0000000..a5f0926
--- /dev/null
+++ b/visad/IrregularSet.java
@@ -0,0 +1,286 @@
+//
+// IrregularSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   IrregularSet is implemented by those Set sub-classes whose samples
+   do not form any ordered pattern.  It is a M-dimensional array of
+   points in R^N where ManifoldDimension = M <= N = DomainDimension.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For more detail, see the example in Linear2DSet.java.<P>
+*/
+public class IrregularSet extends SampledSet {
+
+  public Delaunay Delan = null;
+
+  /** oldToNew and newToOld used when ManifoldDimension = 1
+      but DomainDimension > 1 */
+  /** maps old samples indices to sorted samples indices */
+  int[] oldToNew;
+  /** maps sorted samples indices to old samples indices */
+  int[] newToOld;
+
+  /** construct an IrregularSet */
+  public IrregularSet(MathType type, float[][] samples)
+         throws VisADException {
+    this(type, samples, samples.length, null, null, null, null, true);
+  }
+
+  /** construct an IrregularSet with non-default CoordinateSystem */
+  public IrregularSet(MathType type, float[][] samples,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors) throws VisADException {
+    this (type, samples, samples.length, coord_sys,
+          units, errors, null, true);
+  }
+
+  /** construct an IrregularSet with non-default Delaunay */
+  public IrregularSet(MathType type, float[][] samples, Delaunay delan)
+         throws VisADException {
+    this(type, samples, samples.length, null, null, null, delan, true);
+  }
+
+  /** construct an IrregularSet with non-default
+      CoordinateSystem and non-default Delaunay */
+  public IrregularSet(MathType type, float[][] samples,
+                      CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors, Delaunay delan)
+         throws VisADException {
+    this(type, samples, samples.length, coord_sys,
+         units, errors, delan, true);
+  }
+
+  /** construct an IrregularSet with ManifoldDimension != DomainDimension,
+      with non-default CoordinateSystem, and with non-default Delaunay */
+  public IrregularSet(MathType type, float[][] samples,
+                      int manifold_dimension, CoordinateSystem coord_sys,
+                      Unit[] units, ErrorEstimate[] errors, Delaunay delan)
+         throws VisADException {
+    this(type, samples, manifold_dimension, coord_sys,
+         units, errors, delan, true);
+  }
+
+  public IrregularSet(MathType type, float[][] samples,
+                      int manifold_dimension, CoordinateSystem coord_sys,
+                      Unit[] units, ErrorEstimate[] errors, Delaunay delan,
+                      boolean copy) throws VisADException {
+    super(type, manifold_dimension, coord_sys, units, errors);
+    if (samples == null ) {
+      throw new SetException("IrregularSet: samples cannot be null");
+    }
+    init_samples(samples, copy);
+
+    // initialize Delaunay triangulation structure
+    if (ManifoldDimension > 1) {
+      if (delan != null) {
+        if (copy) Delan = (Delaunay) delan.clone();
+        else Delan = delan;
+      }
+      else Delan = Delaunay.factory(samples, false);
+    }
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    float[][] value = new float[DomainDimension][index.length];
+    float[][]mySamples = getMySamples();
+    for (int i=0; i<index.length; i++) {
+      if ( (index[i] >= 0) && (index[i] < Length) ) {
+        for (int j=0; j<DomainDimension; j++) {
+          value[j][i] = mySamples[j][index[i]];
+        }
+      }
+      else {
+        for (int j=0; j<DomainDimension; j++) {
+          value[j][i] = Float.NaN;
+        }
+      }
+    }
+    return value;
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    throw new UnimplementedException("IrregularSet.valueToIndex");
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if no interpolation is possible */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float weights[][]) throws VisADException {
+    throw new UnimplementedException("IrregularSet.valueToInterp");
+  }
+
+  /**
+   * Returns the indexes of neighboring samples for all samples.
+   *
+   * @param neighbors		The indexes of the neighboring points.	On
+   *				input, <code>neighbors.length</code>
+   *				must be greater than or equal to
+   *				<code>getLength()</code>.  On output,
+   *				<code>neighbors[i][j]</code> will be the index
+   *				of the <code>j</code>th neighboring sample of
+   *				sample <code>i</code>.
+   * @throws VisADException     if a VisAD failure occurs.
+   */
+  public void getNeighbors( int[][] neighbors )
+         throws VisADException
+  {
+    if ( ManifoldDimension == 1 )
+    {
+      neighbors[0] = new int[2];
+      neighbors[Length - 1] = new int[2];
+      neighbors[0][0] = 1;
+      neighbors[Length - 1][0] = Length - 2;
+
+      for ( int ii = 1; ii < (Length - 1); ii++ ) {
+        neighbors[ii] = new int[2];
+        neighbors[ii][0] = ii - 1;
+        neighbors[ii][0] = ii + 1;
+      }
+    }
+    else if ( ManifoldDimension < 4 )
+    {
+
+      int[][] Vertices = Delan.Vertices;
+      int[][] Tri = Delan.Tri;
+      int n_samples = Vertices.length;
+      int n_triangles;
+      int cnt, ii, jj, kk, tt, index;
+      int[] indeces;
+
+      for ( ii = 0; ii < n_samples; ii++ )
+      {
+        indeces = new int[n_samples];
+        n_triangles = Vertices[ii].length;
+        for ( jj = 0; jj < n_triangles; jj++ )
+        {
+          for ( kk = 0; kk < Tri[ Vertices[ii][jj] ].length; kk++ )
+          {
+            index = Tri[ Vertices[ii][jj] ][kk];
+            if ( index != ii ) indeces[ index ]++;
+          }
+        }
+        cnt = 0;
+        for ( tt = 0; tt < n_samples; tt++ ) {
+          if ( indeces[tt] > 0 ) cnt++;
+        }
+        neighbors[ii] = new int[ cnt ];
+        cnt = 0;
+        for ( tt = 0; tt < n_samples; tt++ ) {
+          if ( indeces[tt] > 0 ) {
+            neighbors[ii][cnt] = tt;
+            cnt++;
+          }
+        }
+        indeces = null;
+      }
+    }
+    else
+    {
+      throw new UnimplementedException("getNeighbors(): ManifoldDimension > 3 ");
+    }
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof IrregularSet) || set == null ||
+        set instanceof LinearSet) return false;
+    if (this == set) return true;
+    if (testNotEqualsCache((Set) set)) return false;
+    if (testEqualsCache((Set) set)) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    try {
+      int i, j;
+      if (DomainDimension != ((IrregularSet) set).getDimension() ||
+          ManifoldDimension != ((IrregularSet) set).getManifoldDimension() ||
+          Length != ((IrregularSet) set).getLength()) return false;
+      // Sets are immutable, so no need for 'synchronized'
+      float[][]mySamples = getMySamples();
+      float[][] samples = ((IrregularSet) set).getSamples(false);
+      for (j=0; j<DomainDimension; j++) {
+        for (i=0; i<Length; i++) {
+          if (mySamples[j][i] != samples[j][i]) {
+            addNotEqualsCache((Set) set);
+            return false;
+          }
+        }
+      }
+      addEqualsCache((Set) set);
+      return true;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                            A clone of this instance.
+   */
+  public Object clone() {
+    IrregularSet clone = (IrregularSet)super.clone();
+
+    if (Delan != null)
+      clone.Delan = (Delaunay)Delan.clone();
+
+    return clone;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+     return new IrregularSet(type, getMySamples(), DomainCoordinateSystem,
+                          SetUnits, SetErrors, Delan);
+  }
+
+  public String longString(String pre) throws VisADException {
+    String s;
+    int j;
+    if (DomainDimension == ManifoldDimension) {
+      s = pre + getClass().getName() + ": Dimension = " +
+          DomainDimension + " Length = " + Length + "\n";
+      for (j=0; j<DomainDimension; j++) {
+        s = s + pre + "  Dimension " + j + ":" +
+                " Range = " + Low[j] + " to " + Hi[j] + "\n";
+      }
+    }
+    else {
+      s = pre + getClass().getName() + ": DomainDimension = " +
+          DomainDimension + " ManifoldDimension = " + ManifoldDimension +
+          " Length = " + Length + "\n";
+      for (j=0; j<DomainDimension; j++) {
+        s = s + pre + "  DomainDimension " + j + ":" + " Range = " +
+            Low[j] + " to " + Hi[j] + "\n";
+      }
+    }
+    return s;
+  }
+
+}
+
diff --git a/visad/KeyboardBehavior.java b/visad/KeyboardBehavior.java
new file mode 100644
index 0000000..0b8ebd7
--- /dev/null
+++ b/visad/KeyboardBehavior.java
@@ -0,0 +1,88 @@
+//
+//  KeyboardBehavior.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+import java.awt.event.KeyEvent;
+
+public interface KeyboardBehavior {
+
+  // standard keyboard functions
+  /** Identifier for function to translate the display upwards */
+  int TRANSLATE_UP = 0;
+
+  /** Identifier for function to translate the display downwards */
+  int TRANSLATE_DOWN = 1;
+
+  /** Identifier for function to translate the display to the left */
+  int TRANSLATE_LEFT = 2;
+
+  /** Identifier for function to translate the display to the right */
+  int TRANSLATE_RIGHT = 3;
+
+  /** Identifier for function to zoom in the display */
+  int ZOOM_IN = 4;
+
+  /** Identifier for function to zoom out the display */
+  int ZOOM_OUT = 5;
+
+  /** 
+   * Identifier for function to reset the display to the original projection
+   * or last saved projection 
+   * @see visad.ProjectionControl#resetProjection()
+   */
+  int RESET = 6;
+
+  /** 
+   * Mask to indicate there are no modifiers for this key.
+   * @see #mapKeyToFunction(int function, int keycode, int modifiers)
+   */
+  int NO_MASK = 0;
+
+  /**
+   * Maps key represented by keycode & modifiers to the given function.
+   * Each function can only have one key/modifier combination assigned 
+   * to it at a time.
+   * @see java.awt.event.KeyEvent
+   * @see java.awt.event.InputEvent
+   * @param  function  keyboard function (TRANSLATE_UP, ZOOM_IN, etc)
+   * @param  keycode   <CODE>KeyEvent</CODE> virtual keycodes 
+   * @param  modifiers <CODE>InputEvent</CODE> key mask
+   */
+  void mapKeyToFunction(int function, int keycode, int modifiers);
+
+  /**
+   *  Process a key event.  Determines whether a meaningful key was pressed.
+   *  place.
+   *  @param  event  KeyEvent stimulus
+   */
+  void processKeyEvent(KeyEvent event);
+
+  /** 
+   * Executes the given function. 
+   * @param  function   function to perform (TRANSLATE_UP, ZOOM_IN, etc)
+   */
+  void execFunction(int function);
+}
diff --git a/visad/LICENSE b/visad/LICENSE
new file mode 100644
index 0000000..c90e7f6
--- /dev/null
+++ b/visad/LICENSE
@@ -0,0 +1,30 @@
+Copyright 1998 University Corporation for Atmospheric Research/Unidata
+
+Portions of this software were developed by the Unidata Program at the 
+University Corporation for Atmospheric Research.
+
+Access and use of this software shall impose the following obligations
+and understandings on the user. The user is granted the right, without
+any fee or cost, to use, copy, modify, alter, enhance and distribute
+this software, and any derivative works thereof, and its supporting
+documentation for any purpose whatsoever, provided that this entire
+notice appears in all copies of the software, derivative works and
+supporting documentation.  Further, UCAR requests that the user credit
+UCAR/Unidata in any publications that result from the use of this
+software or in any product that includes this software. The names UCAR
+and/or Unidata, however, may not be used in any advertising or publicity
+to endorse or promote any products or commercial entity unless specific
+written permission is obtained from UCAR/Unidata. The user also
+understands that UCAR/Unidata is not obligated to provide the user with
+any support, consulting, training or assistance of any kind with regard
+to the use, operation and performance of this software nor to provide
+the user with any updates, revisions, new versions or "bug fixes."
+
+THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "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 UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
+INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/visad/Linear1DSet.java b/visad/Linear1DSet.java
new file mode 100644
index 0000000..a885935
--- /dev/null
+++ b/visad/Linear1DSet.java
@@ -0,0 +1,628 @@
+//
+// Linear1DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Linear1DSet represents a finite set of samples of R in
+   an arithmetic progression.<P>
+
+   The samples are ordered from First to Last.<P>
+*/
+public class Linear1DSet extends Gridded1DSet
+       implements LinearSet, GriddedDoubleSet {
+
+  private double First, Last, Step, Invstep;
+  private boolean cacheSamples;
+
+  /** 
+   * Construct a 1-D arithmetic progression with null 
+   * errors and generic type 
+   * @param first      first value in progression
+   * @param last       last value in progression
+   * @param length     number of samples in progression (R)
+   * @throws  VisADException  problem creating set
+   */
+  public Linear1DSet(double first, double last, int length)
+         throws VisADException {
+    this(RealType.Generic, first, last, length, null, null, null);
+  }
+
+  /** 
+   * Construct a 1-D arithmetic progression with the specified
+   * <code>type</code> and null errors.
+   * @param type       MathType for this Linear1DSet.  
+   * @param first      first value in progression
+   * @param last       last value in progression
+   * @param length     number of samples in progression (R)
+   * @throws  VisADException  problem creating set
+   */
+  public Linear1DSet(MathType type, double first, double last, int length)
+         throws VisADException {
+    this(type, first, last, length, null, null, null);
+  }
+
+  /** 
+   * Construct a 1-D arithmetic progression with 
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear2DSet.  
+   * @param first      first value in progression
+   * @param last       last value in progression
+   * @param length     number of samples in progression (R)
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with default
+   *                   of <code>type</code> if specified.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear1DSet(MathType type, double first, double last, int length,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors) throws VisADException {
+    this(type, first, last, length, coord_sys, units, errors, false);
+  }
+
+  /** 
+   * Construct a 1-D arithmetic progression with 
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear2DSet.  
+   * @param first      first value in progression
+   * @param last       last value in progression
+   * @param length     number of samples in progression (R)
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with default
+   *                   of <code>type</code> if specified.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear1DSet(MathType type, double first, double last, int length,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors, 
+                     boolean cache) throws VisADException {
+    super(type, (float[][]) null, length, coord_sys, units, errors);
+    if (DomainDimension != 1) {
+      throw new SetException("Linear1DSet: DomainDimension must be 1, not " +
+                             DomainDimension);
+    }
+    First = first;
+    Last = last;
+    Length = length;
+    if (Length < 1) throw new SetException("Linear1DSet: number of samples (" +
+                                           Length + " must be greater than 0");
+    Step = (Length < 2) ? 1.0 : (Last - First) / (Length - 1);
+    Invstep = 1.0 / Step;
+    LowX = (float) Math.min(First, First + Step * (Length - 1));
+    HiX = (float) Math.max(First, First + Step * (Length - 1));
+    Low[0] = LowX;
+    Hi[0] = HiX;
+    if (SetErrors[0] != null ) {
+      SetErrors[0] =
+        new ErrorEstimate(SetErrors[0].getErrorValue(), (Low[0] + Hi[0]) / 2.0,
+                          Length, SetErrors[0].getUnit());
+    }
+    cacheSamples = cache;
+  }
+
+  /** 
+   * Convert an array of 1-D indices to an array of values in 
+   * the set corresponding to those indices.
+   * @param index  array of indices of values in R^1 space.
+   * @return  values in R^1 space corresponding to indices.
+   * @throws  VisADException  problem converting indices to values.
+   */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int length = index.length;
+    float[][] values = new float[1][length];
+    for (int i=0; i<length; i++) {
+      if (0 <= index[i] && index[i] < Length) {
+        values[0][i] = (float) (First + ((double) index[i]) * Step);
+      }
+      else {
+        values[0][i] = Float.NaN;
+      }
+    }
+    return values;
+  }
+
+  /** 
+   * Convert an array of 1-D indices to an array of double values in 
+   * the set corresponding to those indices.
+   * @param index  array of indices of values in R space.
+   * @return  values in R space corresponding to indices.
+   * @throws  VisADException  problem converting indices to values.
+   */
+  public double[][] indexToDouble(int[] index) throws VisADException {
+    int length = index.length;
+    double[][] values = new double[1][length];
+    for (int i=0; i<length; i++) {
+      if (0 <= index[i] && index[i] < Length) {
+        values[0][i] = (First + ((double) index[i]) * Step);
+      }
+      else {
+        values[0][i] = Double.NaN;
+      }
+    }
+    return values;
+  }
+
+  public int[] doubleToIndex(double[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("Linear1DSet.doubleToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+    double l = -0.5;
+    double h = Length - 0.5;
+    for (int i=0; i<length; i++) {
+      double di = 0.5 + (value[0][i] - First) * Invstep;
+      index[i] = (0.0 < di && di < (double) Length) ? (int) di : -1;
+    }
+    return index;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    if (grid.length != 1) {
+      throw new SetException("Linear1DSet.gridToValue: grid dimension" +
+                             " should be 1, not " + grid.length);
+    }
+    /* remove DRM: 2004-09-14
+    if (Length < 2) {
+      throw new SetException("Linear1DSet.gridToValue: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    */
+    int length = grid[0].length;
+    float[][] value = new float[1][length];
+    float[] value0 = value[0];
+    float[] grid0 = grid[0];
+    float l = -0.5f;
+    float h = ((float) Length) - 0.5f;
+    float g;
+
+    for (int i=0; i<length; i++) {
+      g = grid0[i];
+      value0[i] = (float) ((l < g && g < h) ? First + g * Step : Float.NaN);
+    }
+    return value;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R */
+  public double[][] gridToDouble(double[][] grid) throws VisADException {
+    if (grid.length != 1) {
+      throw new SetException("Linear1DSet.gridToValue: grid dimension" +
+                             " should be 1, not " + grid.length);
+    }
+    /* remove DRM: 2004-09-14
+    if (Length < 2) {
+      throw new SetException("Linear1DSet.gridToValue: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    */
+    int length = grid[0].length;
+    double[][] value = new double[1][length];
+    double[] value0 = value[0];
+    double[] grid0 = grid[0];
+    double l = -0.5;
+    double h = (Length) - 0.5;
+    double g;
+
+    for (int i=0; i<length; i++) {
+      g = grid0[i];
+      value0[i] = ((l < g && g < h) ? First + g * Step : Float.NaN);
+    }
+    return value;
+  }
+
+  /** transform an array of values in R to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    if (value.length != 1) {
+      throw new SetException("Linear1DSet.valueToGrid: value dimension" +
+                             " should be 1, not " + value.length);
+    }
+    /* remove DRM: 2004-09-14
+    if (Lengths[0] < 2) {
+      throw new SetException("Linear1DSet.valueToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    */
+    int length = value[0].length;
+    float[][] grid = new float[1][length];
+    float[] grid0 = grid[0];
+    float[] value0 = value[0];
+    float l = (float) (First - 0.5 * Step);
+    float h = (float) (First + (((float) Length) - 0.5) * Step);
+    float v;
+
+    if (h < l) {
+      float temp = l;
+      l = h;
+      h = temp;
+    }
+    for (int i=0; i<length; i++) {
+      v = value0[i];
+      grid0[i] = (float) ((l < v && v < h) ? (v - First) * Invstep : Float.NaN);
+    }
+    return grid;
+  }
+
+  /** transform an array of values in R to an array
+      of non-integer grid coordinates */
+  public double[][] doubleToGrid(double[][] value) throws VisADException {
+    if (value.length != 1) {
+      throw new SetException("Linear1DSet.valueToGrid: value dimension" +
+                             " should be 1, not " + value.length);
+    }
+    /* remove DRM: 2004-09-14
+    if (Lengths[0] < 2) {
+      throw new SetException("Linear1DSet.valueToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    */
+    int length = value[0].length;
+    double[][] grid = new double[1][length];
+    double[] grid0 = grid[0];
+    double[] value0 = value[0];
+    double l = (First - 0.5 * Step);
+    double h = (First + (((float) Length) - 0.5) * Step);
+    double v;
+
+    if (h < l) {
+      double temp = l;
+      l = h;
+      h = temp;
+    }
+    for (int i=0; i<length; i++) {
+      v = value0[i];
+      grid0[i] = ((l < v && v < h) ? (v - First) * Invstep : Double.NaN);
+    }
+    return grid;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void doubleToInterp(double[][] value, int[][] indices,
+    double[][] weights) throws VisADException
+  {
+    if (value.length != DomainDimension) {
+      throw new SetException("Linear1DSet.doubleToInterp: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length) {
+      throw new SetException("Linear1DSet.doubleToInterp: indices length " +
+                             indices.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    if (weights.length != length) {
+      throw new SetException("Linear1DSet.doubleToInterp: weights length " +
+                             weights.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    // convert value array to grid coord array
+    double[][] grid = doubleToGrid(value);
+
+    int i, j, k; // loop indices
+    int lis; // temporary length of is & cs
+    int length_is; // final length of is & cs, varies by i
+    int isoff; // offset along one grid dimension
+    double a, b; // weights along one grid dimension; a + b = 1.0
+    int[] is; // array of indices, becomes part of indices
+    double[] cs; // array of coefficients, become part of weights
+
+    int base; // base index, as would be returned by valueToIndex
+    int[] l = new int[ManifoldDimension]; // integer 'factors' of base
+    // fractions with l; -0.5 <= c <= 0.5
+    double[] c = new double[ManifoldDimension];
+
+    // array of index offsets by grid dimension
+    int[] off = new int[ManifoldDimension];
+    off[0] = 1;
+    for (j=1; j<ManifoldDimension; j++) off[j] = off[j-1] * Lengths[j-1];
+
+    for (i=0; i<length; i++) {
+      // compute length_is, base, l & c
+      length_is = 1;
+      if (Double.isNaN(grid[ManifoldDimension-1][i])) {
+        base = -1;
+      }
+      else {
+        l[ManifoldDimension-1] = (int) (grid[ManifoldDimension-1][i] + 0.5);
+        // WLH 23 Dec 99
+        if (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1]) {
+          l[ManifoldDimension-1]--;
+        }
+        c[ManifoldDimension-1] = grid[ManifoldDimension-1][i] -
+                                 ((double) l[ManifoldDimension-1]);
+        if (!((l[ManifoldDimension-1] == 0 && c[ManifoldDimension-1] <= 0.0) ||
+              (l[ManifoldDimension-1] == Lengths[ManifoldDimension-1] - 1 &&
+               c[ManifoldDimension-1] >= 0.0))) {
+          // only interp along ManifoldDimension-1
+          // if between two valid grid coords
+          length_is *= 2;
+        }
+        base = l[ManifoldDimension-1];
+      }
+      for (j=ManifoldDimension-2; j>=0 && base>=0; j--) {
+        if (Double.isNaN(grid[j][i])) {
+          base = -1;
+        }
+        else {
+          l[j] = (int) (grid[j][i] + 0.5);
+          if (l[j] == Lengths[j]) l[j]--; // WLH 23 Dec 99
+          c[j] = grid[j][i] - ((double) l[j]);
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            length_is *= 2;
+          }
+          base = l[j] + Lengths[j] * base;
+        }
+      }
+
+      if (base < 0) {
+        // value is out of grid so return null
+        is = null;
+        cs = null;
+      }
+      else {
+        // create is & cs of proper length, and init first element
+        is = new int[length_is];
+        cs = new double[length_is];
+        is[0] = base;
+        cs[0] = 1.0f;
+        lis = 1;
+
+        for (j=0; j<ManifoldDimension; j++) {
+          if (!((l[j] == 0 && c[j] <= 0.0) ||
+                (l[j] == Lengths[j] - 1 && c[j] >= 0.0))) {
+            // only interp along dimension j if between two valid grid coords
+            if (c[j] >= 0.0) {
+              // grid coord above base
+              isoff = off[j];
+              a = 1.0f - c[j];
+              b = c[j];
+            }
+            else {
+              // grid coord below base
+              isoff = -off[j];
+              a = 1.0f + c[j];
+              b = -c[j];
+            }
+            // double is & cs; adjust new offsets; split weights
+            for (k=0; k<lis; k++) {
+              is[k+lis] = is[k] + isoff;
+              cs[k+lis] = cs[k] * b;
+              cs[k] *= a;
+            }
+            lis *= 2;
+          }
+        }
+      }
+      indices[i] = is;
+      weights[i] = cs;
+    }
+  }
+
+  /**
+   * Get the first value in this arithmetic progression.
+   * @return  first value
+   */
+  public double getFirst() {
+    return First;
+  }
+
+  /**
+   * Get the last value in this arithmetic progression.
+   * @return  last value
+   */
+  public double getLast() {
+    return Last;
+  }
+
+  /**
+   * Get the interval between values in this progression
+   * @return  step interval of progression
+   */
+  public double getStep() {
+    return Step;
+  }
+
+  /**
+   * Get the inverse of the step (1.0/getStep()).
+   * @return  inverse of step interval of progression
+   */
+  public double getInvstep() {
+    return Invstep;
+  }
+
+  /**
+   * Check to see if this is an empty progression
+   * @return always false.
+   */
+  public boolean isMissing() {
+    return false;
+  }
+
+  /**
+   * Return the array of values as doubles in R space corresponding to
+   * this arithmetic progression.
+   * @param  copy  if true, return a copy of the samples.
+   * @return  array of values in R space.
+   * @throws  VisADException  problem creating samples.
+   */
+  public double[][] getDoubles(boolean copy) throws VisADException {
+    /*  DRM 2003-01-16
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToDouble(indices);
+    */
+    double[][] samples = new double[1][Length];
+    for (int i=0; i<Length; i++) {
+      samples[0][i] = (First + ((double) i) * Step);
+    }
+    return samples;
+  }
+
+  /**
+   * Return the array of values in R space corresponding to
+   * this arithmetic progression.
+   * @param  copy  if true, return a copy of the samples.
+   * @return  array of values in R space.
+   * @throws  VisADException  problem creating samples.
+   */
+  public float[][] getSamples(boolean copy) throws VisADException {
+    /*  DRM 2003-01-16
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToValue(indices);
+    */
+    float[][]mySamples = getMySamples();
+    if (mySamples != null) {
+      return copy ? Set.copyFloats(mySamples) : mySamples;
+    }
+    float[][] samples = makeSamples ();
+    if (cacheSamples) {
+      setMySamples(samples);
+      return copy ? Set.copyFloats(samples) : samples;
+    }
+    return samples;
+  }
+
+  /** code to actually enumerate the samples for this progression*/
+  private float[][] makeSamples() throws VisADException {
+    float[][] samples = new float[1][Length];
+    for (int i=0; i<Length; i++) {
+      samples[0][i] = (float) (First + ((double) i) * Step);
+    }
+    return samples;
+  }
+
+  /**
+   * Check to see if this Linear1DSet is equal to the Object
+   * in question.
+   * @param  set  Object in question
+   * @return true if <code>set</code> is a Linear1DSet and 
+   *         the type, first, last and lengths of the progressions
+   *         are equal.
+   */
+  public boolean equals(Object set) {
+    boolean flag;
+    if (!(set instanceof Linear1DSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    try {
+      flag = (First == ((Linear1DSet) set).getFirst() &&
+              Last == ((Linear1DSet) set).getLast() &&
+              Length == ((Linear1DSet) set).getLength());
+    }
+    catch (VisADException e) {
+      return false;
+    }
+    return flag;
+  }
+
+  /**
+   * Returns the hash code for this instance.
+   * @return                    The hash code for this instance.
+   */
+  public int hashCode()
+  {
+    if (!hashCodeSet)
+    {
+      hashCode =
+        unitAndCSHashCode() ^
+        new Double(First).hashCode() ^
+        new Double(Last).hashCode() ^
+        Length;
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  /**
+   * Get the indexed component (X is at 0 and is the only valid index)
+   *
+   * @param i Index of component
+   * @return The requested component
+   * @exception ArrayIndexOutOfBoundsException If an invalid index is
+   *                                           specified.
+   */
+  public Linear1DSet getLinear1DComponent(int i) {
+    if (i == 0) return this;
+    else throw new ArrayIndexOutOfBoundsException("Invalid component index " +
+                                                  i);
+  }
+
+  /**
+   * Return a clone of this object with a new MathType.
+   * @param  type  new MathType.
+   * @return  new Linear1DSet with <code>type</code>.
+   * @throws VisADException  if <code>type</code> is not compatible
+   *                         with MathType of this.
+   */
+  public Object cloneButType(MathType type) throws VisADException {
+    return new Linear1DSet(type, First, Last, Length, DomainCoordinateSystem,
+                           SetUnits, SetErrors, cacheSamples);
+  }
+
+  /**
+   * Extended version of the toString() method.
+   * @param  pre  prefix for string.
+   * @return wordy string describing this Linear1DSet.
+   */
+  public String longString(String pre) throws VisADException {
+    return pre + "Linear1DSet: Length = " + Length +
+           " Range = " + First + " to " + Last + "\n";
+  }
+
+}
+
diff --git a/visad/Linear2DSet.java b/visad/Linear2DSet.java
new file mode 100644
index 0000000..21f0897
--- /dev/null
+++ b/visad/Linear2DSet.java
@@ -0,0 +1,492 @@
+//
+// Linear2DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Linear2DSet represents a finite set of samples of R^2 in
+   a cross product of two arithmetic progressions.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For example, here is the order of 30 product samples when the
+   first component is has 6 samples and the second component has
+   5 samples (note index is 0-based):<P>
+
+<PRE>
+<!-- -->             second (Y) component
+<!-- -->
+<!-- -->   first      0   6  12  18  24
+<!-- -->              1   7  13  19  25
+<!-- -->    (X)       2   8  14  20  26
+<!-- -->              3   9  15  21  27
+<!-- -->  component   4  10  16  22  28
+<!-- -->              5  11  17  23  29
+</PRE>
+*/
+public class Linear2DSet extends Gridded2DSet
+       implements LinearSet {
+
+  Linear1DSet X, Y;
+  private boolean cacheSamples;
+
+  /**
+   * Construct a 2-D cross product of <code>sets</code> with a
+   * generic MathType.
+   * @param sets     Linear1DSets that make up this Linear2DSet.
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear2DSet(Linear1DSet[] sets) throws VisADException {
+    this (RealTupleType.Generic2D, sets, null, null, null);
+  }
+
+  /**
+   * Construct a 2-D cross product of <code>sets</code> with the
+   * specified <code>type</code>.
+   * @param type     MathType for this Linear2DSet.  Must be consistent
+   *                 with MathType-s of sets.
+   * @param sets     Linear1DSets that make up this Linear2DSet.
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear2DSet(MathType type, Linear1DSet[] sets) throws VisADException {
+    this (type, sets, null, null, null);
+  }
+
+  /** 
+   * Construct a 2-D cross product of arithmetic progressions with
+   * null errors and generic type.
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear2DSet(double first1, double last1, int length1,
+                     double first2, double last2, int length2)
+         throws VisADException {
+    this(RealTupleType.Generic2D,
+         LinearNDSet.get_linear1d_array(RealTupleType.Generic2D,
+                                        first1, last1, length1,
+                                        first2, last2, length2, null),
+         null, null, null);
+  }
+
+  /** 
+   * Construct a 2-D cross product of arithmetic progressions with
+   * null errors and the specified <code>type</code>.
+   * @param type       MathType for this Linear2DSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear2DSet(MathType type, double first1, double last1, int length1,
+                                    double first2, double last2, int length2)
+         throws VisADException {
+    this(type, LinearNDSet.get_linear1d_array(type, first1, last1, length1,
+                                              first2, last2, length2, null),
+         null, null, null);
+  }
+
+  /** 
+   * Construct a 2-D cross product of arithmetic progressions with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear2DSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear2DSet(MathType type, double first1, double last1, int length1,
+                     double first2, double last2, int length2,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors) throws VisADException {
+    this(type, first1, last1, length1,
+         first2, last2, length2, coord_sys, units, errors, false);
+  }
+
+  /** 
+   * Construct a 2-D cross product of arithmetic progressions with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear2DSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear2DSet(MathType type, double first1, double last1, int length1,
+                     double first2, double last2, int length2,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors, 
+                     boolean cache) throws VisADException {
+    this(type,
+         LinearNDSet.get_linear1d_array(type, first1, last1, length1,
+                                        first2, last2, length2, units),
+         coord_sys, units, errors, cache);
+  }
+
+  /**
+   * Construct a 2-D cross product of <code>sets</code>, with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear2DSet.  Must be consistent
+   *                   with MathType-s of <code>sets</code>.
+   * @param sets       Linear1DSets that make up this Linear2DSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear2DSet(MathType type, Linear1DSet[] sets,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors) throws VisADException {
+    this(type, sets, coord_sys, units, errors, false);
+  }
+
+  /**
+   * Construct a 2-D cross product of <code>sets</code>, with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear2DSet.  Must be consistent
+   *                   with MathType-s of <code>sets</code>.
+   * @param sets       Linear1DSets that make up this Linear2DSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}.
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear2DSet(MathType type, Linear1DSet[] sets,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors, boolean cache) throws VisADException {
+    super(type, (float[][]) null, sets[0].getLength(), sets[1].getLength(),
+          coord_sys, LinearNDSet.units_array_linear1d(sets, units), errors);
+    if (DomainDimension != 2) {
+      throw new SetException("Linear2DSet: DomainDimension must be 2, not " +
+                             DomainDimension);
+    }
+    if (sets.length != 2) {
+      throw new SetException("Linear2DSet: ManifoldDimension must be 2" +
+                             ", not " + sets.length);
+    }
+    Linear1DSet[] ss = LinearNDSet.linear1d_array_units(sets, units);
+    X = ss[0];
+    Y = ss[1];
+    LengthX = X.getLength();
+    LengthY = Y.getLength();
+    Length = LengthX * LengthY;
+    Low[0] = X.getLowX();
+    Hi[0] = X.getHiX();
+    Low[1] = Y.getLowX();
+    Hi[1] = Y.getHiX();
+    if (SetErrors[0] != null ) {
+      SetErrors[0] =
+        new ErrorEstimate(SetErrors[0].getErrorValue(), (Low[0] + Hi[0]) / 2.0,
+                          Length, SetErrors[0].getUnit());
+    }
+    if (SetErrors[1] != null ) {
+      SetErrors[1] =
+        new ErrorEstimate(SetErrors[1].getErrorValue(), (Low[1] + Hi[1]) / 2.0,
+                          Length, SetErrors[1].getUnit());
+    }
+    cacheSamples = cache;
+  }
+
+  /** 
+   * Convert an array of 1-D indices to an array of values in 
+   * R^2 space.
+   * @param index  array of indices of values in R^2 space.
+   * @return  values in R^2 space corresponding to indices.
+   * @throws  VisADException  problem converting indices to values.
+   */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int length = index.length;
+    int[] indexX = new int[length];
+    int[] indexY = new int[length];
+    float[][] values = new float[2][];
+
+    for (int i=0; i<length; i++) {
+      if (0 <= index[i] && index[i] < Length) {
+        indexX[i] = index[i] % LengthX;
+        indexY[i] = index[i] / LengthX;
+      }
+      else {
+        indexX[i] = -1;
+        indexY[i] = -1;
+      }
+    }
+    float[][] valuesX = X.indexToValue(indexX);
+    float[][] valuesY = Y.indexToValue(indexY);
+    values[0] = valuesX[0];
+    values[1] = valuesY[0];
+    return values;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^2 */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    if (grid.length != ManifoldDimension) {
+      throw new SetException("Linear2DSet.gridToValue: grid dimension " +
+                             grid.length +
+                             " not equal to Manifold dimension " +
+                             ManifoldDimension);
+    }
+    if (ManifoldDimension != 2) {
+      throw new SetException("Linear2DSet.gridToValue: Manifold dimension " +
+                             "must be 2, not " + ManifoldDimension);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Linear2DSet.gridToValue: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = grid[0].length;
+    float[][] gridX = new float[1][];
+    gridX[0] = grid[0];
+    float[][] gridY = new float[1][];
+    gridY[0] = grid[1];
+    float[][] valueX = X.gridToValue(gridX);
+    float[][] valueY = Y.gridToValue(gridY);
+    float[][] value = new float[2][];
+    value[0] = valueX[0];
+    value[1] = valueY[0];
+    return value;
+  }
+
+  /** transform an array of values in R^2 to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    if (value.length != 2) {
+      throw new SetException("Linear2DSet.valueToGrid: value dimension" +
+                             " must be 2, not " + value.length);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("Linear2DSet.valueToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = value[0].length;
+    float[][] valueX = new float[1][];
+    valueX[0] = value[0];
+    float[][] valueY = new float[1][];
+    valueY[0] = value[1];
+    float[][] gridX = X.valueToGrid(valueX);
+    float[][] gridY = Y.valueToGrid(valueY);
+    float[][] grid = new float[2][];
+    grid[0] = gridX[0];
+    grid[1] = gridY[0];
+    return grid;
+  }
+
+  /**
+   * Return the first arithmetic progression for this
+   * cross product (X of XY).
+   * @return first arithmetic progression as a Linear1DSet.
+   */
+  public Linear1DSet getX() {
+    return X;
+  }
+
+  /**
+   * Return the second arithmetic progression for this
+   * cross product (Y of XY).
+   * @return second arithmetic progression as a Linear1DSet.
+   */
+  public Linear1DSet getY() {
+    return Y;
+  }
+
+  /**
+   * Check to see if this is an empty cross-product.
+   * @return always false.
+   */
+  public boolean isMissing() {
+    return false;
+  }
+
+  /**
+   * Return the array of values in R^2 space corresponding to
+   * this cross product of arithmetic progressions.
+   * @param  copy  if true, return a copy of the samples.
+   * @return  array of values in R^2 space.
+   * @throws  VisADException  problem creating samples.
+   */
+  public float[][] getSamples(boolean copy) throws VisADException {
+    /*  DRM 2003-01-16
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToValue(indices);
+    */
+    float[][]mySamples = getMySamples();
+    if (mySamples != null) {
+      return copy ? Set.copyFloats(mySamples) : mySamples;
+    }
+    float[][] samples = makeSamples ();
+    if (cacheSamples) {
+      setMySamples(samples);
+      return copy ? Set.copyFloats(samples) : samples;
+    }
+    return samples;
+  }
+
+  /** code to actually enumerate the samples from the Linear1DSets
+      into an array in R^2 space. */
+  private float[][] makeSamples() throws VisADException {
+    float[][] xVals = X.getSamples(false);
+    float[][] yVals = Y.getSamples(false);
+    float[][] samples = new float[2][getLength()];
+    int idx = 0;
+    for (int j = 0; j < yVals[0].length; j++) {
+      for (int i = 0; i < xVals[0].length; i++) {
+        samples[0][idx] = (float) xVals[0][i];
+        samples[1][idx] = (float) yVals[0][j];
+        idx++;
+      }
+    }
+    return samples;
+  }
+
+  /**
+   * Check to see if this Linear2DSet is equal to the Object
+   * in question.
+   * @param  set  Object in question
+   * @return true if <code>set</code> is a Linear2DSet and each
+   *         of the Linear1DSet-s that make up this cross product
+   *         are equal.
+   */
+  public boolean equals(Object set) {
+    if (!(set instanceof Linear2DSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    return (X.equals(((Linear2DSet) set).getX()) &&
+            Y.equals(((Linear2DSet) set).getY()));
+  }
+
+  /**
+   * Returns the hash code for this instance.
+   * @return                    The hash code for this instance.
+   */
+  public int hashCode()
+  {
+    if (!hashCodeSet)
+    {
+      hashCode = unitAndCSHashCode() ^ X.hashCode() ^ Y.hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  /**
+   * Get the indexed component (X is at 0, Y is at 1)
+   *
+   * @param i Index of component
+   * @return The requested component
+   * @exception ArrayIndexOutOfBoundsException If an invalid index is
+   *                                           specified.
+   */
+  public Linear1DSet getLinear1DComponent(int i) {
+    if (i == 0) return getX();
+    else if (i == 1) return getY();
+    else if (i < 0) {
+      throw new ArrayIndexOutOfBoundsException("Negative component index " + i);
+    } else {
+      throw new ArrayIndexOutOfBoundsException("Component index " + i +
+                                               " must be less than 2");
+    }
+  }
+
+  /**
+   * Return a clone of this object with a new MathType.
+   * @param  type  new MathType.
+   * @return  new Linear2DSet with <code>type</code>.
+   * @throws VisADException  if <code>type</code> is not compatible
+   *                         with MathType of component Linear1DSets.
+   */
+  public Object cloneButType(MathType type) throws VisADException {
+    Linear1DSet[] sets = {(Linear1DSet) X.clone(),
+                          (Linear1DSet) Y.clone()};
+    return new Linear2DSet(type, sets, DomainCoordinateSystem,
+                           SetUnits, SetErrors, cacheSamples);
+  }
+
+  /**
+   * Extended version of the toString() method.
+   * @param  pre  prefix for string.
+   * @return wordy string describing this Linear2DSet.
+   */
+  public String longString(String pre) throws VisADException {
+    String s = pre + "Linear2DSet: Length = " + Length + "\n";
+    s = s + pre + "  Dimension 1: Length = " + X.getLength() +
+            " Range = " + X.getFirst() + " to " + X.getLast() + "\n";
+    s = s + pre + "  Dimension 2: Length = " + Y.getLength() +
+            " Range = " + Y.getFirst() + " to " + Y.getLast() + "\n";
+    return s;
+  }
+
+}
+
diff --git a/visad/Linear3DSet.java b/visad/Linear3DSet.java
new file mode 100644
index 0000000..97bae05
--- /dev/null
+++ b/visad/Linear3DSet.java
@@ -0,0 +1,1899 @@
+//
+// Linear3DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Linear3DSet represents a finite set of samples of R^3 in
+   a cross product of three arithmetic progressions.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For more detail, see the example in Linear2DSet.java.<P>
+*/
+public class Linear3DSet extends Gridded3DSet
+       implements LinearSet {
+
+  Linear1DSet X, Y, Z;
+  private boolean cacheSamples;
+
+  /**
+   * Construct a 3-D cross product of <code>sets</code> with a
+   * generic MathType.
+   * @param sets     Linear1DSets that make up this Linear3DSet.
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear3DSet(Linear1DSet[] sets) throws VisADException {
+    this(RealTupleType.Generic3D, sets, null, null, null);
+  }
+
+  /**
+   * Construct a 3-D cross product of <code>sets</code> with the
+   * specified <code>type</code>.
+   * @param type     MathType for this Linear3DSet.  Must be consistent
+   *                 with MathType-s of sets.
+   * @param sets     Linear1DSets that make up this Linear3DSet.
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear3DSet(MathType type, Linear1DSet[] sets) throws VisADException {
+    this(type, sets, null, null, null);
+  }
+
+  /** 
+   * Construct a 3-D cross product of arithmetic progressions with
+   * null errors and generic type.
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param first3     first value in third arithmetic progression
+   * @param last3      last value in third arithmetic progression
+   * @param length3    number of values in third arithmetic progression
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear3DSet(double first1, double last1, int length1,
+                     double first2, double last2, int length2,
+                     double first3, double last3, int length3)
+         throws VisADException {
+    this(RealTupleType.Generic3D,
+         LinearNDSet.get_linear1d_array(RealTupleType.Generic3D,
+                                        first1, last1, length1,
+                                        first2, last2, length2,
+                                        first3, last3, length3, null),
+         null, null, null);
+  }
+
+  /** 
+   * Construct a 3-D cross product of arithmetic progressions with
+   * null errors and the specified <code>type</code>.
+   * @param type       MathType for this Linear3DSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param first3     first value in third arithmetic progression
+   * @param last3      last value in third arithmetic progression
+   * @param length3    number of values in third arithmetic progression
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear3DSet(MathType type, double first1, double last1, int length1,
+                                    double first2, double last2, int length2,
+                                    double first3, double last3, int length3)
+         throws VisADException {
+    this(type, LinearNDSet.get_linear1d_array(type, first1, last1, length1,
+                                              first2, last2, length2,
+                                              first3, last3, length3, null),
+         null, null, null);
+  }
+
+  /** 
+   * Construct a 3-D cross product of arithmetic progressions with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear3DSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param first3     first value in third arithmetic progression
+   * @param last3      last value in third arithmetic progression
+   * @param length3    number of values in third arithmetic progression
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear3DSet(MathType type, double first1, double last1, int length1,
+                     double first2, double last2, int length2, double first3,
+                     double last3, int length3, CoordinateSystem coord_sys,
+                     Unit[] units, ErrorEstimate[] errors) throws VisADException {
+    this(type, first1, last1, length1, first2, last2, length2, 
+         first3, last3, length3, coord_sys, units, errors, false);
+  }
+
+  /** 
+   * Construct a 3-D cross product of arithmetic progressions with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear3DSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param first3     first value in third arithmetic progression
+   * @param last3      last value in third arithmetic progression
+   * @param length3    number of values in third arithmetic progression
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}.
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public Linear3DSet(MathType type, double first1, double last1, int length1,
+                     double first2, double last2, int length2, double first3,
+                     double last3, int length3, CoordinateSystem coord_sys,
+                     Unit[] units, ErrorEstimate[] errors,
+                     boolean cache) throws VisADException {
+    this(type, LinearNDSet.get_linear1d_array(type, first1, last1, length1,
+                                              first2, last2, length2,
+                                              first3, last3, length3, units),
+         coord_sys, units, errors, cache);
+  }
+
+  /**
+   * Construct a 3-D cross product of <code>sets</code>, with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear3DSet.  Must be consistent
+   *                   with MathType-s of <code>sets</code>.
+   * @param sets       Linear1DSets that make up this Linear3DSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear3DSet(MathType type, Linear1DSet[] sets,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors) throws VisADException {
+    this(type, sets, coord_sys, units, errors, false);
+  }
+
+  /**
+   * Construct a 3-D cross product of <code>sets</code>, with
+   * the specified <code>type</code>, <code>coord_sys</code>, 
+   * <code>units</code> and <code>errors</code>.
+   * @param type       MathType for this Linear3DSet.  Must be consistent
+   *                   with MathType-s of <code>sets</code>.
+   * @param sets       Linear1DSets that make up this Linear3DSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}.
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public Linear3DSet(MathType type, Linear1DSet[] sets,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors, boolean cache) throws VisADException {
+    super(type, (float[][]) null, sets[0].getLength(), sets[1].getLength(),
+          sets[2].getLength(), coord_sys,
+          LinearNDSet.units_array_linear1d(sets, units), errors);
+    if (DomainDimension != 3) {
+      throw new SetException("Linear3DSet: DomainDimension must be 3, not " +
+                             DomainDimension);
+    }
+    if (sets.length != 3) {
+      throw new SetException("Linear3DSet: ManifoldDimension must be 3, not " +
+                             sets.length);
+    }
+    Linear1DSet[] ss = LinearNDSet.linear1d_array_units(sets, units);
+    X = ss[0];
+    Y = ss[1];
+    Z = ss[2];
+    LengthX = X.getLength();
+    LengthY = Y.getLength();
+    LengthZ = Z.getLength();
+    Length = LengthX * LengthY * LengthZ;
+    Low[0] = X.getLowX();
+    Hi[0] = X.getHiX();
+    Low[1] = Y.getLowX();
+    Hi[1] = Y.getHiX();
+    Low[2] = Z.getLowX();
+    Hi[2] = Z.getHiX();
+    if (SetErrors[0] != null ) {
+      SetErrors[0] =
+        new ErrorEstimate(SetErrors[0].getErrorValue(), (Low[0] + Hi[0]) / 2.0,
+                          Length, SetErrors[0].getUnit());
+    }
+    if (SetErrors[1] != null ) {
+      SetErrors[1] =
+        new ErrorEstimate(SetErrors[1].getErrorValue(), (Low[1] + Hi[1]) / 2.0,
+                          Length, SetErrors[1].getUnit());
+    }
+    if (SetErrors[2] != null ) {
+      SetErrors[2] =
+        new ErrorEstimate(SetErrors[2].getErrorValue(), (Low[2] + Hi[2]) / 2.0,
+                          Length, SetErrors[2].getUnit());
+    }
+    cacheSamples = cache;
+  }
+
+  /** 
+   * Convert an array of 1-D indices to an array of values in 
+   * R^3 space.
+   * @param index  array of indices of values in R^3 space.
+   * @return  values in R^3 space corresponding to indices.
+   * @throws  VisADException  problem converting indices to values.
+   */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int length = index.length;
+    int[] indexX = new int[length];
+    int[] indexY = new int[length];
+    int[] indexZ = new int[length];
+    float[][] values = new float[3][];
+
+    for (int i=0; i<length; i++) {
+      if (0 <= index[i] && index[i] < Length) {
+        indexX[i] = index[i] % LengthX;
+        int k = index[i] / LengthX;
+        indexY[i] = k % LengthY;
+        indexZ[i] = k / LengthY;
+      }
+      else {
+        indexX[i] = -1;
+        indexY[i] = -1;
+        indexZ[i] = -1;
+      }
+    }
+    float[][] valuesX = X.indexToValue(indexX);
+    float[][] valuesY = Y.indexToValue(indexY);
+    float[][] valuesZ = Z.indexToValue(indexZ);
+    values[0] = valuesX[0];
+    values[1] = valuesY[0];
+    values[2] = valuesZ[0];
+    return values;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^3 */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    if (grid.length != ManifoldDimension) {
+      throw new SetException("Linear3DSet.gridToValue: grid dimension " +
+                             grid.length +
+                             " not equal to Manifold dimension " +
+                             ManifoldDimension);
+    }
+    if (ManifoldDimension != 3) {
+      throw new SetException("Linear3DSet.gridToValue: ManifoldDimension " +
+                             "must be 3, not " + ManifoldDimension);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2 || Lengths[2] < 2)) {
+      throw new SetException("Linear3DSet.gridToValue: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = grid[0].length;
+    float[][] gridX = new float[1][];
+    gridX[0] = grid[0];
+    float[][] gridY = new float[1][];
+    gridY[0] = grid[1];
+    float[][] gridZ = new float[1][];
+    gridZ[0] = grid[2];
+    float[][] valueX = X.gridToValue(gridX);
+    float[][] valueY = Y.gridToValue(gridY);
+    float[][] valueZ = Z.gridToValue(gridZ);
+    float[][] value = new float[3][];
+    value[0] = valueX[0];
+    value[1] = valueY[0];
+    value[2] = valueZ[0];
+    return value;
+  }
+
+  /** transform an array of values in R^3 to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    if (value.length != 3) {
+      throw new SetException("Linear3DSet.valueToGrid: value dimension" +
+                             " must be 3, not " + value.length);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2 || Lengths[2] < 2)) {
+      throw new SetException("Linear3DSet.valueToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = value[0].length;
+    float[][] valueX = new float[1][];
+    valueX[0] = value[0];
+    float[][] valueY = new float[1][];
+    valueY[0] = value[1];
+    float[][] valueZ = new float[1][];
+    valueZ[0] = value[2];
+    float[][] gridX = X.valueToGrid(valueX);
+    float[][] gridY = Y.valueToGrid(valueY);
+    float[][] gridZ = Z.valueToGrid(valueZ);
+    float[][] grid = new float[3][];
+    grid[0] = gridX[0];
+    grid[1] = gridY[0];
+    grid[2] = gridZ[0];
+    return grid;
+  }
+
+  /**
+   * Return the first arithmetic progression for this
+   * cross product (X of XYZ).
+   * @return first arithmetic progression as a Linear1DSet.
+   */
+  public Linear1DSet getX() {
+    return X;
+  }
+
+  /**
+   * Return the second arithmetic progression for this
+   * cross product (Y of XYZ).
+   * @return second arithmetic progression as a Linear1DSet.
+   */
+  public Linear1DSet getY() {
+    return Y;
+  }
+
+  /**
+   * Return the third arithmetic progression for this
+   * cross product (Z of XYZ).
+   * @return third arithmetic progression as a Linear1DSet.
+   */
+  public Linear1DSet getZ() {
+    return Z;
+  }
+
+  /**
+   * Check to see if this is an empty cross-product.
+   * @return always false.
+   */
+  public boolean isMissing() {
+    return false;
+  }
+
+  /**
+   * Return the array of values in R^3 space corresponding to
+   * this cross product of arithmetic progressions.
+   * @param  copy  if true, return a copy of the samples.
+   * @return  array of values in R^3 space.
+   * @throws  VisADException  problem creating samples.
+   */
+  public float[][] getSamples(boolean copy) throws VisADException {
+    /*  DRM 2003-01-16
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToValue(indices);
+    */
+    float[][]mySamples = getMySamples();
+    if (mySamples != null) {
+      return copy ? Set.copyFloats(mySamples) : mySamples;
+    }
+    float[][] samples = makeSamples ();
+    if (cacheSamples) {
+      setMySamples(samples);
+      return copy ? Set.copyFloats(samples) : samples;
+    }
+    return samples;
+  }
+
+  /** code to actually enumerate the samples from the Linear1DSets
+      into an array in R^3 space. */
+  private float[][] makeSamples () throws VisADException {
+    float[][] xVals = X.getSamples(false);
+    float[][] yVals = Y.getSamples(false);
+    float[][] zVals = Z.getSamples(false);
+    float[][] samples = new float[3][Length];
+    int idx = 0;
+    for (int k = 0; k < zVals[0].length; k++) {
+      for (int j = 0; j < yVals[0].length; j++) {
+        for (int i = 0; i < xVals[0].length; i++) {
+          // set or load coordinate values
+          samples[0][idx] = (float) xVals[0][i];
+          samples[1][idx] = (float) yVals[0][j];
+          samples[2][idx] = (float) zVals[0][k];
+          idx++;
+        }
+      }
+    }
+    return samples;
+  }
+
+  /** note makeSpatial never returns a Linear3DSet,
+      so this is not enough;
+      must handle it like linear texture mapping;
+      also, want to exploit Texture3D -
+      so must figure out how to make texture alpha work */
+  public VisADGeometryArray[] make3DGeometry(byte[][] color_values)
+         throws VisADException {
+    if (ManifoldDimension != 3) {
+      throw new SetException("Linear3DSet.make3DGeometry: " +
+                             "ManifoldDimension must be 3, not " +
+                             ManifoldDimension);
+    }
+    int lengthX = X.getLength();
+    int lengthY = Y.getLength();
+    int lengthZ = Z.getLength();
+    if (lengthX < 2 || lengthY < 2 || lengthZ < 2 ||
+        color_values.length < 4) {
+      VisADGeometryArray array = makePointGeometry(color_values);
+      return new VisADGeometryArray[] {array, array, array};
+    }
+    double firstX = X.getFirst();
+    double firstY = Y.getFirst();
+    double firstZ = Z.getFirst();
+    double lastX = X.getLast();
+    double lastY = Y.getLast();
+    double lastZ = Z.getLast();
+/* Now the 'brick' has dimensions lengthX * lengthY * lengthZ
+   and locations in each of X, Y and Z are in arithmetic
+   progression, ranging from firstX to lastX in X, etc.
+
+   The color component are laid out as follows: */
+    float red, green, blue, alpha;
+    for (int x=0; x<lengthX; x++) {
+      for (int y=0; y<lengthY; y++) {
+        for (int z=0; z<lengthZ; z++) {
+          red = color_values[0][x + lengthX * (y + lengthY * z)];
+          green = color_values[2][x + lengthX * (y + lengthY * z)];
+          blue = color_values[3][x + lengthX * (y + lengthY * z)];
+          alpha = color_values[4][x + lengthX * (y + lengthY * z)];
+        }
+      }
+    }
+    /* now construct 3 triangle strip arrays, for the planes
+       perpendicular to X, Y and Z */
+    VisADTriangleStripArray[] arrays = {new VisADTriangleStripArray(),
+      new VisADTriangleStripArray(), new VisADTriangleStripArray()};
+
+    return arrays;
+  }
+
+  /* almost identical with Gridded3DSet.makeIsoSurface, except:
+     1. spatial_maps and permute arguments
+     2. compute 'float[] sos' array
+     3. call linear_isosurf rather than isosurf, with added sos argument
+     4. permute VX, VY and VZ
+  */
+  public VisADGeometryArray makeLinearIsoSurface(float isolevel,
+         float[] fieldValues, byte[][] color_values, boolean indexed,
+         ScalarMap[] spatial_maps, int[] permute)
+         throws VisADException {
+    boolean debug = false;
+
+    int      i, NVT, cnt;
+    int      size_stripe;
+    int      xdim_x_ydim, xdim_x_ydim_x_zdim;
+    int      num_cubes, nvertex, npolygons;
+    int      ix, iy, ii;
+    int nvertex_estimate;
+
+    if (ManifoldDimension != 3) {
+      throw new DisplayException("Linear3DSet.makeLinearIsoSurface: " +
+                                 "ManifoldDimension must be 3, not " +
+                                 ManifoldDimension);
+    }
+
+    /* adapt isosurf algorithm to Gridded3DSet variables */
+    // NOTE X & Y swap
+    int xdim = LengthY;
+    int ydim = LengthX;
+    int zdim = LengthZ;
+
+    float[] ptGRID = fieldValues;
+
+    xdim_x_ydim = xdim * ydim;
+    xdim_x_ydim_x_zdim = xdim_x_ydim * zdim;
+    num_cubes = (xdim-1) * (ydim-1) * (zdim-1);
+
+    int[]  ptFLAG = new int[ num_cubes ];
+    int[]  ptAUX  = new int[ xdim_x_ydim_x_zdim ];
+    int[]  pcube  = new int[ num_cubes+1 ];
+
+    // System.out.println("pre-flags: isolevel = " + isolevel +
+    //                    " xdim, ydim, zdim = " + xdim + " " + ydim + " " + zdim);
+
+    npolygons = flags( isolevel, ptFLAG, ptAUX, pcube,
+                       ptGRID, xdim, ydim, zdim );
+
+    if (debug) System.out.println("npolygons= "+npolygons);
+
+    if (npolygons == 0) return null;
+
+    // take the garbage out
+    pcube = null;
+
+    nvertex_estimate = 4 * npolygons + 100;
+    ix = 9 * (nvertex_estimate + 50);
+    iy = 7 * npolygons;
+
+    float[][] VX = new float[1][nvertex_estimate];
+    float[][] VY = new float[1][nvertex_estimate];
+    float[][] VZ = new float[1][nvertex_estimate];
+
+    byte[][] color_temps = null;
+    if (color_values != null) {
+      color_temps = new byte[color_values.length][];
+    }
+
+    int[] Pol_f_Vert = new int[ix];
+    int[] Vert_f_Pol = new int[iy];
+    int[][] arg_Pol_f_Vert = new int[][] {Pol_f_Vert};
+
+    // get scales and offsets from linear grid coordinates to graphics coordinates
+    float[] sos = new float[6];
+    Unit[] set_units = getSetUnits();
+    RealTupleType domain_type = ((SetType) getType()).getDomain();
+    Unit[] def_units = domain_type.getDefaultUnits();
+    Linear1DSet[] sets = {getX(), getY(), getZ()};
+    double[] so = new double[2];
+    double[] data = new double[2];
+    double[] display = new double[2];
+    for (i=0; i<3; i++) {
+      double first = sets[i].getFirst();
+      double step = sets[i].getStep();
+      if (set_units[i] != null && !set_units[i].equals(def_units[i])) {
+        double uo = set_units[i].toThat(0.0, def_units[i]);
+        double us = set_units[i].toThat(1.0, def_units[i]) - uo;
+        first = uo + us * first;
+        step = us * step;
+      }
+      spatial_maps[i].getScale(so, data, display);
+      sos[2 * i + 0] = (float) (so[1] + so[0] * first); // overall X offset
+      sos[2 * i + 1] = (float) (so[0] * step);          // overall X scale
+    }
+
+    // NOTE X & Y swap
+    float t = sos[0];
+    sos[0] = sos[2];
+    sos[2] = t;
+    t = sos[1];
+    sos[1] = sos[3];
+    sos[3] = t;
+    nvertex = linear_isosurf( isolevel, ptFLAG, nvertex_estimate, npolygons,
+                              ptGRID, xdim, ydim, zdim, VY, VX, VZ,
+                              color_values, color_temps, arg_Pol_f_Vert,
+                              Vert_f_Pol, sos );
+    Pol_f_Vert = arg_Pol_f_Vert[0];
+
+    if (nvertex == 0) return null;
+
+    // take the garbage out
+    ptFLAG = null;
+    ptAUX = null;
+/*
+for (int j=0; j<nvertex; j++) {
+  System.out.println("iso vertex[" + j + "] " + VX[0][j] + " " + VY[0][j] +
+                     " " + VZ[0][j]);
+}
+*/
+    float[][] fieldVertices = new float[3][nvertex];
+    // NOTE - NO X & Y swap
+    System.arraycopy(VX[0], 0, fieldVertices[permute[0]], 0, nvertex);
+    System.arraycopy(VY[0], 0, fieldVertices[permute[1]], 0, nvertex);
+    System.arraycopy(VZ[0], 0, fieldVertices[permute[2]], 0, nvertex);
+    // take the garbage out
+    VX = null;
+    VY = null;
+    VZ = null;
+
+    byte[][] color_levels = null;
+    if (color_values != null) {
+      color_levels = new byte[color_values.length][nvertex];
+      System.arraycopy(color_temps[0], 0, color_levels[0], 0, nvertex);
+      System.arraycopy(color_temps[1], 0, color_levels[1], 0, nvertex);
+      System.arraycopy(color_temps[2], 0, color_levels[2], 0, nvertex);
+      if (color_temps.length > 3) {
+        System.arraycopy(color_temps[3], 0, color_levels[3], 0, nvertex);
+      }
+      // take the garbage out
+      color_temps = null;
+    }
+
+    if (debug) System.out.println("nvertex= "+nvertex);
+
+    float[] NxA = new float[npolygons];
+    float[] NxB = new float[npolygons];
+    float[] NyA = new float[npolygons];
+    float[] NyB = new float[npolygons];
+    float[] NzA = new float[npolygons];
+    float[] NzB = new float[npolygons];
+
+    float[] Pnx = new float[npolygons];
+    float[] Pny = new float[npolygons];
+    float[] Pnz = new float[npolygons];
+
+    float[] NX = new float[nvertex];
+    float[] NY = new float[nvertex];
+    float[] NZ = new float[nvertex];
+
+    make_normals( fieldVertices[0], fieldVertices[1],  fieldVertices[2],
+                  NX, NY, NZ, nvertex, npolygons, Pnx, Pny, Pnz,
+                  NxA, NxB, NyA, NyB, NzA, NzB, Pol_f_Vert, Vert_f_Pol);
+
+    // take the garbage out
+    NxA = NxB = NyA = NyB = NzA = NzB = Pnx = Pny = Pnz = null;
+
+    float[] normals = new float[3 * nvertex];
+    int j = 0;
+    for (i=0; i<nvertex; i++) {
+      normals[j++] = (float) NX[i];
+      normals[j++] = (float) NY[i];
+      normals[j++] = (float) NZ[i];
+    }
+    // take the garbage out
+    NX = NY = NZ = null;
+
+    /* ----- Find PolyTriangle Stripe */
+    // temporary array to hold maximum possible polytriangle strip
+    int[] stripe = new int[6 * npolygons];
+    int[] vet_pol = new int[npolygons];
+    size_stripe = poly_triangle_stripe( vet_pol, stripe, nvertex,
+                                        npolygons, Pol_f_Vert, Vert_f_Pol );
+
+    // take the garbage out
+    Pol_f_Vert = null;
+    Vert_f_Pol = null;
+
+    if (indexed) {
+      VisADIndexedTriangleStripArray array =
+        new VisADIndexedTriangleStripArray();
+
+      // set up indices
+      array.indexCount = size_stripe;
+      array.indices = new int[size_stripe];
+      System.arraycopy(stripe, 0, array.indices, 0, size_stripe);
+      array.stripVertexCounts = new int[1];
+      array.stripVertexCounts[0] = size_stripe;
+      // take the garbage out
+      stripe = null;
+
+      // set coordinates and colors
+      setGeometryArray(array, fieldVertices, 4, color_levels);
+      // take the garbage out
+      fieldVertices = null;
+      color_levels = null;
+
+      // array.vertexFormat |= NORMALS;
+      array.normals = normals;
+
+      if (debug) {
+        System.out.println("size_stripe= "+size_stripe);
+        for(ii=0;ii<size_stripe;ii++) System.out.println(+array.indices[ii]);
+      }
+      return array;
+    }
+    else { // if (!indexed)
+      VisADTriangleStripArray array = new VisADTriangleStripArray();
+      array.stripVertexCounts = new int[] {size_stripe};
+      array.vertexCount = size_stripe;
+
+      array.normals = new float[3 * size_stripe];
+      int k = 0;
+      for (i=0; i<3*size_stripe; i+=3) {
+        j = 3 * stripe[k];
+        array.normals[i] = normals[j];
+        array.normals[i+1] = normals[j+1];
+        array.normals[i+2] = normals[j+2];
+        k++;
+      }
+      normals = null;
+
+      array.coordinates = new float[3 * size_stripe];
+      k = 0;
+      for (i=0; i<3*size_stripe; i+=3) {
+        j = stripe[k];
+        array.coordinates[i] = fieldVertices[0][j];
+        array.coordinates[i+1] = fieldVertices[1][j];
+        array.coordinates[i+2] = fieldVertices[2][j];
+        k++;
+      }
+      fieldVertices = null;
+
+      if (color_levels != null) {
+        int color_length = color_levels.length;
+        array.colors = new byte[color_length * size_stripe];
+        k = 0;
+        if (color_length == 4) {
+          for (i=0; i<color_length*size_stripe; i+=color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i+1] = color_levels[1][j];
+            array.colors[i+2] = color_levels[2][j];
+            array.colors[i+3] = color_levels[3][j];
+            k++;
+          }
+        }
+        else { // if (color_length == 3)
+          for (i=0; i<color_length*size_stripe; i+=color_length) {
+            j = stripe[k];
+            array.colors[i] = color_levels[0][j];
+            array.colors[i+1] = color_levels[1][j];
+            array.colors[i+2] = color_levels[2][j];
+            k++;
+          }
+        }
+      }
+      color_levels = null;
+      stripe = null;
+      return array;
+    } // end if (!indexed)
+  }
+
+  /* almost identical with Gridded3DSet.isosurf, except:
+     1. sos argument
+     2. compute xo, xs, yo, ys, zo, zs, and using them instead of samples
+     3. not using samples array
+  */
+  private int linear_isosurf( float isovalue, int[] ptFLAG, int nvertex_estimate,
+                              int npolygons, float[] ptGRID, int xdim, int ydim,
+                              int zdim, float[][] VX, float[][] VY, float[][] VZ,
+                              byte[][] auxValues, byte[][] auxLevels,
+                              int[][] Pol_f_Vert, int[] Vert_f_Pol, float[] sos )
+          throws VisADException {
+
+    int  ix, iy, iz, caseA, above, bellow, front, rear, mm, nn;
+    int  ii, jj, kk, ncube, cpl, pvp, pa, ve;
+    int[] calc_edge = new int[13];
+    int  xx, yy, zz;
+    float    cp;
+    float  vnode0 = 0;
+    float  vnode1 = 0;
+    float  vnode2 = 0;
+    float  vnode3 = 0;
+    float  vnode4 = 0;
+    float  vnode5 = 0;
+    float  vnode6 = 0;
+    float  vnode7 = 0;
+    int  pt = 0;
+    int  n_pol;
+    int  aa;
+    int  bb;
+    int  temp;
+    float  nodeDiff;
+    int xdim_x_ydim = xdim*ydim;
+    int nvet;
+
+    int t;
+
+    float[][]mySamples = getMySamples();
+    // use these instead of samples
+    float xo = sos[0];
+    float xs = sos[1];
+    float yo = sos[2];
+    float ys = sos[3];
+    float zo = sos[4];
+    float zs = sos[5];
+    // don't use samples
+    // float[][] samples = getSamples(false);
+
+    int naux = (auxValues != null) ? auxValues.length : 0;
+    if (naux > 0) {
+      if (auxLevels == null || auxLevels.length != naux) {
+        throw new SetException("Linear3DSet.isosurf: "
+                              +"auxLevels length " + auxLevels.length +
+                               " doesn't match expected " + naux);
+      }
+      for (int i=0; i<naux; i++) {
+        if (auxValues[i].length != Length) {
+          throw new SetException("Linear3DSet.isosurf: expected auxValues " +
+                                " length#" + i + " to be " + Length +
+                                 ", not " + auxValues[i].length);
+        }
+      }
+    }
+    else {
+      if (auxLevels != null) {
+        throw new SetException("Linear3DSet.isosurf: "
+                              +"auxValues null but auxLevels not null");
+      }
+    }
+
+    // temporary storage of auxLevels structure
+    byte[][] tempaux = (naux > 0) ? new byte[naux][nvertex_estimate] : null;
+
+    bellow = rear = 0;  above = front = 1;
+
+    /* Initialize the Auxiliar Arrays of Pointers */
+/* WLH 25 Oct 97
+    ix = 9 * (npolygons*2 + 50);
+    iy = 7 * npolygons;
+    ii = ix + iy;
+*/
+    for (jj=0; jj<Pol_f_Vert[0].length; jj++) {
+      Pol_f_Vert[0][jj] = BIG_NEG;
+    }
+    for (jj=8; jj<Pol_f_Vert[0].length; jj+=9) {
+      Pol_f_Vert[0][jj] = 0;
+    }
+    for (jj=0; jj<Vert_f_Pol.length; jj++) {
+      Vert_f_Pol[jj] = BIG_NEG;
+    }
+    for (jj=6; jj<Vert_f_Pol.length; jj+=7) {
+      Vert_f_Pol[jj] = 0;
+    }
+
+    /* Allocate the auxiliar edge vectors
+    size ixPlane = (xdim - 1) * ydim = xdim_x_ydim - ydim
+    size iyPlane = (ydim - 1) * xdim = xdim_x_ydim - xdim
+    size izPlane = xdim
+    */
+
+    xx = xdim_x_ydim - ydim;
+    yy = xdim_x_ydim - xdim;
+    zz = ydim;
+    ii = 2 * (xx + yy + zz);
+
+    int[] P_array = new int[ii];
+
+    /* Calculate the Vertex of the Polygons which edges were
+       calculated above */
+    nvet = ncube = cpl = pvp = 0;
+
+
+        for ( iz = 0; iz < zdim - 1; iz++ ) {
+
+            for ( ix = 0; ix < xdim - 1; ix++ ) {
+
+                for ( iy = 0; iy < ydim - 1; iy++ ) {
+                    if ( (ptFLAG[ncube] != 0 & ptFLAG[ncube] != 0xFF) ) {
+
+                        if (nvet + 12 > nvertex_estimate) {
+                          // allocate more space
+                          nvertex_estimate = 2 * (nvet + 12);
+                          if (naux > 0) {
+                            for (int i=0; i<naux; i++) {
+                              byte[] tt = tempaux[i];
+                              tempaux[i] = new byte[nvertex_estimate];
+                              System.arraycopy(tt, 0, tempaux[i], 0, nvet);
+                            }
+                          }
+                          float[] tt = VX[0];
+                          VX[0] = new float[nvertex_estimate];
+                          System.arraycopy(tt, 0, VX[0], 0, tt.length);
+                          tt = VY[0];
+                          VY[0] = new float[nvertex_estimate];
+                          System.arraycopy(tt, 0, VY[0], 0, tt.length);
+                          tt = VZ[0];
+                          VZ[0] = new float[nvertex_estimate];
+                          System.arraycopy(tt, 0, VZ[0], 0, tt.length);
+                          int big_ix = 9 * (nvertex_estimate + 50);
+                          int[] it = Pol_f_Vert[0];
+                          Pol_f_Vert[0] = new int[big_ix];
+                          for (jj=0; jj<Pol_f_Vert[0].length; jj++) {
+                            Pol_f_Vert[0][jj] = BIG_NEG;
+                          }
+                          for (jj=8; jj<Pol_f_Vert[0].length; jj+=9) {
+                            Pol_f_Vert[0][jj] = 0;
+                          }
+                          System.arraycopy(it, 0, Pol_f_Vert[0], 0, it.length);
+                        }
+
+           /* WLH 2 April 99 */
+           vnode0 = ptGRID[pt];
+           vnode1 = ptGRID[pt + ydim];
+           vnode2 = ptGRID[pt + 1];
+           vnode3 = ptGRID[pt + ydim + 1];
+           vnode4 = ptGRID[pt + xdim_x_ydim];
+           vnode5 = ptGRID[pt + ydim + xdim_x_ydim];
+           vnode6 = ptGRID[pt + 1 + xdim_x_ydim];
+           vnode7 = ptGRID[pt + 1 + ydim + xdim_x_ydim];
+
+                        if ( (ptFLAG[ncube] < MAX_FLAG_NUM) ) {
+                        /*  fill_Vert_f_Pol(ncube); */
+
+                                  kk  = pol_edges[ptFLAG[ncube]][2];
+                                  aa = ptFLAG[ncube];
+                                  bb = 4;
+                                  pa  = pvp;
+                                  n_pol = pol_edges[ptFLAG[ncube]][1];
+                                  for (ii=0; ii < n_pol; ii++) {
+                                      Vert_f_Pol[pa+6] = ve = kk&MASK;
+                                      ve+=pa;
+                                      for (jj=pa; jj<ve && jj<pa+6; jj++) {
+
+                                            Vert_f_Pol[jj] = pol_edges[aa][bb];
+                                            bb++;
+                                            if (bb >= 16) {
+                                                aa++;
+                                                bb -= 16;
+                                            }
+                                      }
+                                           kk >>= 4;    pa += 7;
+                                  }
+                        /* end  fill_Vert_f_Pol(ncube); */
+                        /* */
+
+         /* find_vertex(); */
+/* WLH 2 April 99
+           vnode0 = ptGRID[pt];
+           vnode1 = ptGRID[pt + ydim];
+           vnode2 = ptGRID[pt + 1];
+           vnode3 = ptGRID[pt + ydim + 1];
+           vnode4 = ptGRID[pt + xdim_x_ydim];
+           vnode5 = ptGRID[pt + ydim + xdim_x_ydim];
+           vnode6 = ptGRID[pt + 1 + xdim_x_ydim];
+           vnode7 = ptGRID[pt + 1 + ydim + xdim_x_ydim];
+*/
+
+
+   if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0002) != 0) ) {  /* cube vertex 0-1 */
+         if ( (iz != 0) || (iy != 0) ) {
+           calc_edge[1] = P_array[ bellow*xx + ix*ydim + iy ];
+         }
+         else {
+             cp = ( ( isovalue - vnode0 ) / ( vnode1 - vnode0 ) );
+             VX[0][nvet] = xo + xs * (cp + ix);
+             VY[0][nvet] = yo + ys * iy;
+             VZ[0][nvet] = zo + zs * iz;
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + ydim] + (1.0f-cp) * samples[0][pt];
+             VY[0][nvet] = (float) cp * samples[1][pt + ydim] + (1.0f-cp) * samples[1][pt];
+             VZ[0][nvet] = (float) cp * samples[2][pt + ydim] + (1.0f-cp) * samples[2][pt];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + ydim] < 0) ?
+                     ((float) auxValues[j][pt + ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + ydim]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt] < 0) ?
+                     ((float) auxValues[j][pt]) + 256.0f :
+                     ((float) auxValues[j][pt]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim] +
+                                  (1.0f-cp) * auxValues[j][pt];
+*/
+             }
+
+             calc_edge[1] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0004) != 0) ) {  /* cube vertex 0-2 */
+         if ( (iz != 0) || (ix != 0) ) {
+           calc_edge[2] = P_array[ 2*xx + bellow*yy + iy*xdim + ix ];
+         }
+         else {
+             cp = ( ( isovalue - vnode0 ) / ( vnode2 - vnode0 ) );
+             VX[0][nvet] = xo + xs * ix;
+             VY[0][nvet] = yo + ys * (cp + iy);
+             VZ[0][nvet] = zo + zs * iz;
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + 1] + (1.0f-cp) * samples[0][pt];
+             VY[0][nvet] = (float) cp * samples[1][pt + 1] + (1.0f-cp) * samples[1][pt];
+             VZ[0][nvet] = (float) cp * samples[2][pt + 1] + (1.0f-cp) * samples[2][pt];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + 1] < 0) ?
+                     ((float) auxValues[j][pt + 1]) + 256.0f :
+                     ((float) auxValues[j][pt + 1]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt] < 0) ?
+                     ((float) auxValues[j][pt]) + 256.0f :
+                     ((float) auxValues[j][pt]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1] +
+                                  (1.0f-cp) * auxValues[j][pt];
+*/
+             }
+
+             calc_edge[2] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0008) != 0) ) {  /* cube vertex 0-4 */
+         if ( (ix != 0) || (iy != 0) ) {
+           calc_edge[3] = P_array[ 2*xx + 2*yy + rear*zz + iy ];
+         }
+         else {
+             cp = ( ( isovalue - vnode0 ) / ( vnode4 - vnode0 ) );
+             VX[0][nvet] = xo + xs * ix;
+             VY[0][nvet] = yo + ys * iy;
+             VZ[0][nvet] = zo + zs * (cp + iz);
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + xdim_x_ydim] +
+                        (1.0f-cp) * samples[0][pt];
+             VY[0][nvet] = (float) cp * samples[1][pt + xdim_x_ydim] +
+                        (1.0f-cp) * samples[1][pt];
+             VZ[0][nvet] = (float) cp * samples[2][pt + xdim_x_ydim] +
+                        (1.0f-cp) * samples[2][pt];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + xdim_x_ydim] < 0) ?
+                     ((float) auxValues[j][pt + xdim_x_ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + xdim_x_ydim]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt] < 0) ?
+                     ((float) auxValues[j][pt]) + 256.0f :
+                     ((float) auxValues[j][pt]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + xdim_x_ydim] +
+                                  (1.0f-cp) * auxValues[j][pt];
+*/
+             }
+
+             calc_edge[3] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0010) != 0) ) {  /* cube vertex 1-3 */
+         if ( (iz != 0) ) {
+           calc_edge[4] =  P_array[ 2*xx + bellow*yy + iy*xdim + (ix+1) ];
+         }
+         else {
+             cp = ( ( isovalue - vnode1 ) / ( vnode3 - vnode1 ) );
+             VX[0][nvet] = xo + xs * (ix+1);
+             VY[0][nvet] = yo + ys * (cp + iy);
+             VZ[0][nvet] = zo + zs * iz;
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + ydim + 1] +
+                        (1.0f-cp) * samples[0][pt + ydim];
+             VY[0][nvet] = (float) cp * samples[1][pt + ydim + 1] +
+                        (1.0f-cp) * samples[1][pt + ydim];
+             VZ[0][nvet] = (float) cp * samples[2][pt + ydim + 1] +
+                        (1.0f-cp) * samples[2][pt + ydim];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + ydim + 1] < 0) ?
+                     ((float) auxValues[j][pt + ydim + 1]) + 256.0f :
+                     ((float) auxValues[j][pt + ydim + 1]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt + ydim] < 0) ?
+                     ((float) auxValues[j][pt + ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + ydim]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + 1] +
+                                  (1.0f-cp) * auxValues[j][pt + ydim];
+*/
+             }
+
+             calc_edge[4] = nvet;
+             P_array[ 2*xx + bellow*yy + iy*xdim + (ix+1) ] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0020) != 0) ) {  /* cube vertex 1-5 */
+         if ( (iy != 0) ) {
+           calc_edge[5] = P_array[ 2*xx + 2*yy + front*zz + iy ];
+         }
+         else {
+             cp = ( ( isovalue - vnode1 ) / ( vnode5 - vnode1 ) );
+             VX[0][nvet] = xo + xs * (ix+1);
+             VY[0][nvet] = yo + ys * iy;
+             VZ[0][nvet] = zo + zs * (cp + iz);
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + ydim + xdim_x_ydim] +
+                        (1.0f-cp) * samples[0][pt + ydim];
+             VY[0][nvet] = (float) cp * samples[1][pt + ydim + xdim_x_ydim] +
+                        (1.0f-cp) * samples[1][pt + ydim];
+             VZ[0][nvet] = (float) cp * samples[2][pt + ydim + xdim_x_ydim] +
+                        (1.0f-cp) * samples[2][pt + ydim];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ?
+                     ((float) auxValues[j][pt + ydim + xdim_x_ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + ydim + xdim_x_ydim]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt + ydim] < 0) ?
+                     ((float) auxValues[j][pt + ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + ydim]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + xdim_x_ydim] +
+                                  (1.0f-cp) * auxValues[j][pt + ydim];
+*/
+             }
+
+             calc_edge[5] = nvet;
+             P_array[ 2*xx + 2*yy + front*zz + iy ] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0040) != 0) ) {  /* cube vertex 2-3 */
+         if ( (iz != 0) ) {
+           calc_edge[6] = P_array[ bellow*xx + ix*ydim + (iy+1) ];
+         }
+         else {
+             cp = ( ( isovalue - vnode2 ) / ( vnode3 - vnode2 ) );
+             VX[0][nvet] = xo + xs * (cp + ix);
+             VY[0][nvet] = yo + ys * (iy+1);
+             VZ[0][nvet] = zo + zs * iz;
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + ydim + 1] +
+                        (1.0f-cp) * samples[0][pt + 1];
+             VY[0][nvet] = (float) cp * samples[1][pt + ydim + 1] +
+                        (1.0f-cp) * samples[1][pt + 1];
+             VZ[0][nvet] = (float) cp * samples[2][pt + ydim + 1] +
+                        (1.0f-cp) * samples[2][pt + 1];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + ydim + 1] < 0) ?
+                     ((float) auxValues[j][pt + ydim + 1]) + 256.0f :
+                     ((float) auxValues[j][pt + ydim + 1]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt + 1] < 0) ?
+                     ((float) auxValues[j][pt + 1]) + 256.0f :
+                     ((float) auxValues[j][pt + 1]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + 1] +
+                                  (1.0f-cp) * auxValues[j][pt + 1];
+*/
+             }
+
+             calc_edge[6] = nvet;
+             P_array[ bellow*xx + ix*ydim + (iy+1) ] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0080) != 0) ) {  /* cube vertex 2-6 */
+         if ( (ix != 0) ) {
+           calc_edge[7] = P_array[ 2*xx + 2*yy + rear*zz + (iy+1) ];
+         }
+         else {
+             cp = ( ( isovalue - vnode2 ) / ( vnode6 - vnode2 ) );
+             VX[0][nvet] = xo + xs * ix;
+             VY[0][nvet] = yo + ys * (iy+1);
+             VZ[0][nvet] = zo + zs * (cp + iz);
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim] +
+                        (1.0f-cp) * samples[0][pt + 1];
+             VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim] +
+                        (1.0f-cp) * samples[1][pt + 1];
+             VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim] +
+                        (1.0f-cp) * samples[2][pt + 1];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ?
+                     ((float) auxValues[j][pt + 1 + xdim_x_ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + 1 + xdim_x_ydim]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt + 1] < 0) ?
+                     ((float) auxValues[j][pt + 1]) + 256.0f :
+                     ((float) auxValues[j][pt + 1]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + xdim_x_ydim] +
+                                  (1.0f-cp) * auxValues[j][pt + 1];
+*/
+             }
+
+             calc_edge[7] = nvet;
+             P_array[ 2*xx + 2*yy + rear*zz + (iy+1) ] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0100) != 0) ) {  /* cube vertex 3-7 */
+         cp = ( ( isovalue - vnode3 ) / ( vnode7 - vnode3 ) );
+         VX[0][nvet] = xo + xs * (ix+1);
+         VY[0][nvet] = yo + ys * (iy+1);
+         VZ[0][nvet] = zo + zs * (cp + iz);
+/*
+         VX[0][nvet] = (float) cp * samples[0][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[0][pt + ydim + 1];
+         VY[0][nvet] = (float) cp * samples[1][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[1][pt + ydim + 1];
+         VZ[0][nvet] = (float) cp * samples[2][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[2][pt + ydim + 1];
+*/
+
+         for (int j=0; j<naux; j++) {
+           t = (int) ( cp * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ?
+                 ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) + 256.0f :
+                 ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) ) +
+               (1.0f - cp) * ((auxValues[j][pt + ydim + 1] < 0) ?
+                 ((float) auxValues[j][pt + ydim + 1]) + 256.0f :
+                 ((float) auxValues[j][pt + ydim + 1]) ) );
+           tempaux[j][nvet] = (byte)
+             ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+           tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + ydim + xdim_x_ydim] +
+                              (1.0f-cp) * auxValues[j][pt + ydim + 1];
+*/
+         }
+
+         calc_edge[8] = nvet;
+         P_array[ 2*xx + 2*yy + front*zz + (iy+1) ] = nvet;
+         nvet++;
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0200) != 0) ) {  /* cube vertex 4-5 */
+         if ( (iy != 0) ) {
+           calc_edge[9] = P_array[ above*xx + ix*ydim + iy ];
+         }
+         else {
+             cp = ( ( isovalue - vnode4 ) / ( vnode5 - vnode4 ) );
+             VX[0][nvet] = xo + xs * (cp + ix);
+             VY[0][nvet] = yo + ys * iy;
+             VZ[0][nvet] = zo + zs * (iz+1);
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + ydim + xdim_x_ydim] +
+                        (1.0f-cp) * samples[0][pt + xdim_x_ydim];
+             VY[0][nvet] = (float) cp * samples[1][pt + ydim + xdim_x_ydim] +
+                        (1.0f-cp) * samples[1][pt + xdim_x_ydim];
+             VZ[0][nvet] = (float) cp * samples[2][pt + ydim + xdim_x_ydim] +
+                        (1.0f-cp) * samples[2][pt + xdim_x_ydim];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ?
+                     ((float) auxValues[j][pt + ydim + xdim_x_ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + ydim + xdim_x_ydim]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt + xdim_x_ydim] < 0) ?
+                     ((float) auxValues[j][pt + xdim_x_ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + xdim_x_ydim]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + xdim_x_ydim] +
+                                  (1.0f-cp) * auxValues[j][pt + xdim_x_ydim];
+*/
+             }
+
+             calc_edge[9] = nvet;
+             P_array[ above*xx + ix*ydim + iy ] = nvet;
+             nvet++;
+         }
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0400) != 0) ) {  /* cube vertex 4-6 */
+         if ( (ix != 0) ) {
+           calc_edge[10] = P_array[ 2*xx + above*yy + iy*xdim + ix ];
+         }
+         else {
+             cp = ( ( isovalue - vnode4 ) / ( vnode6 - vnode4 ) );
+             VX[0][nvet] = xo + xs * ix;
+             VY[0][nvet] = yo + ys * (cp + iy);
+             VZ[0][nvet] = zo + zs * (iz+1);
+/*
+             VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim] +
+                        (1.0f-cp) * samples[0][pt + xdim_x_ydim];
+             VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim] +
+                        (1.0f-cp) * samples[1][pt + xdim_x_ydim];
+             VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim] +
+                        (1.0f-cp) * samples[2][pt + xdim_x_ydim];
+*/
+
+             for (int j=0; j<naux; j++) {
+               t = (int) ( cp * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ?
+                     ((float) auxValues[j][pt + 1 + xdim_x_ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + 1 + xdim_x_ydim]) ) +
+                   (1.0f - cp) * ((auxValues[j][pt + xdim_x_ydim] < 0) ?
+                     ((float) auxValues[j][pt + xdim_x_ydim]) + 256.0f :
+                     ((float) auxValues[j][pt + xdim_x_ydim]) ) );
+               tempaux[j][nvet] = (byte)
+                 ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+               tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + xdim_x_ydim] +
+                                  (1.0f-cp) * auxValues[j][pt + xdim_x_ydim];
+*/
+             }
+
+             calc_edge[10] = nvet;
+             P_array[ 2*xx + above*yy + iy*xdim + ix ] = nvet;
+             nvet++;
+         }
+     }
+    if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0800) != 0) ) {  /* cube vertex 5-7 */
+         cp = ( ( isovalue - vnode5 ) / ( vnode7 - vnode5 ) );
+         VX[0][nvet] = xo + xs * (ix+1);
+         VY[0][nvet] = yo + ys * (cp + iy);
+         VZ[0][nvet] = zo + zs * (iz+1);
+/*
+         VX[0][nvet] = (float) cp * samples[0][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[0][pt + ydim + xdim_x_ydim];
+         VY[0][nvet] = (float) cp * samples[1][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[1][pt + ydim + xdim_x_ydim];
+         VZ[0][nvet] = (float) cp * samples[2][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[2][pt + ydim + xdim_x_ydim];
+*/
+
+         for (int j=0; j<naux; j++) {
+           t = (int) ( cp * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ?
+                 ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) + 256.0f :
+                 ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) ) +
+               (1.0f - cp) * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ?
+                 ((float) auxValues[j][pt + ydim + xdim_x_ydim]) + 256.0f :
+                 ((float) auxValues[j][pt + ydim + xdim_x_ydim]) ) );
+           tempaux[j][nvet] = (byte)
+             ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+           tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + ydim + xdim_x_ydim] +
+                              (1.0f-cp) * auxValues[j][pt + ydim + xdim_x_ydim];
+*/
+         }
+
+         calc_edge[11] = nvet;
+         P_array[ 2*xx + above*yy + iy*xdim + (ix+1) ] = nvet;
+         nvet++;
+     }
+     if ( ((pol_edges[ptFLAG[ncube]][3] & 0x1000) != 0) ) {  /* cube vertex 6-7 */
+         cp = ( ( isovalue - vnode6 ) / ( vnode7 - vnode6 ) );
+         VX[0][nvet] = xo + xs * (cp + ix);
+         VY[0][nvet] = yo + ys * (iy+1);
+         VZ[0][nvet] = zo + zs * (iz+1);
+/*
+         VX[0][nvet] = (float) cp * samples[0][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[0][pt + 1 + xdim_x_ydim];
+         VY[0][nvet] = (float) cp * samples[1][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[1][pt + 1 + xdim_x_ydim];
+         VZ[0][nvet] = (float) cp * samples[2][pt + 1 + ydim + xdim_x_ydim] +
+                    (1.0f-cp) * samples[2][pt + 1 + xdim_x_ydim];
+*/
+
+         for (int j=0; j<naux; j++) {
+           t = (int) ( cp * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ?
+                 ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) + 256.0f :
+                 ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) ) +
+               (1.0f - cp) * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ?
+                 ((float) auxValues[j][pt + 1 + xdim_x_ydim]) + 256.0f :
+                 ((float) auxValues[j][pt + 1 + xdim_x_ydim]) ) );
+           tempaux[j][nvet] = (byte)
+             ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+           tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + ydim + xdim_x_ydim] +
+                              (1.0f-cp) * auxValues[j][pt + 1 + xdim_x_ydim];
+*/
+         }
+
+         calc_edge[12] = nvet;
+         P_array[ above*xx + ix*ydim + (iy+1) ] = nvet;
+         nvet++;
+     }
+
+         /* end  find_vertex(); */
+                         /* update_data_structure(ncube); */
+                             kk = pol_edges[ptFLAG[ncube]][2];
+                             nn = pol_edges[ptFLAG[ncube]][1];
+                             for (ii=0; ii<nn; ii++) {
+                                  mm = pvp+(kk&MASK);
+                                  for (jj=pvp; jj<mm; jj++) {
+                                      Vert_f_Pol [jj] = ve = calc_edge[Vert_f_Pol [jj]];
+                            //        Pol_f_Vert[0][ve*9 + (Pol_f_Vert[0][ve*9 + 8])++]  = cpl;
+                                      temp = Pol_f_Vert[0][ve*9 + 8];
+                                      Pol_f_Vert[0][ve*9 + temp] = cpl;
+                                      Pol_f_Vert[0][ve*9 + 8] = temp + 1;
+                                  }
+                                  kk >>= 4;    pvp += 7;    cpl++;
+                             }
+                         /* end  update_data_structure(ncube); */
+                        }
+                        else { // !(ptFLAG[ncube] < MAX_FLAG_NUM)
+       /* find_vertex_invalid_cube(ncube); */
+
+    ptFLAG[ncube] &= 0x1FF;
+    if ( (ptFLAG[ncube] != 0 & ptFLAG[ncube] != 0xFF) )
+    { if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0010) != 0) )     /* cube vertex 1-3 */
+/* WLH 24 Oct 97
+      {   if (!(iz != 0 ) && vnode3 < INV_VAL && vnode1 < INV_VAL)
+      {   if (!(iz != 0 ) && !Float.isNaN(vnode3) && !Float.isNaN(vnode1))
+*/
+      // test for not missing
+      {   if (!(iz != 0 ) && vnode3 == vnode3 && vnode1 == vnode1)
+        {
+              cp = ( ( isovalue - vnode1 ) / ( vnode3 - vnode1 ) );
+              VX[0][nvet] = xo + xs * (ix+1);
+              VY[0][nvet] = yo + ys * (cp + iy);
+              VZ[0][nvet] = zo + zs * iz;
+/*
+              VX[0][nvet] = (float) cp * mySamples[0][pt + ydim + 1] +
+                         (1.0f-cp) * mySamples[0][pt + ydim];
+              VY[0][nvet] = (float) cp * mySamples[1][pt + ydim + 1] +
+                         (1.0f-cp) * mySamples[1][pt + ydim];
+              VZ[0][nvet] = (float) cp * mySamples[2][pt + ydim + 1] +
+                         (1.0f-cp) * mySamples[2][pt + ydim];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + ydim + 1] < 0) ?
+                      ((float) auxValues[j][pt + ydim + 1]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim + 1]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + ydim] < 0) ?
+                      ((float) auxValues[j][pt + ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + 1] +
+                                   (1.0f-cp) * auxValues[j][pt + ydim];
+*/
+              }
+
+              P_array[ 2*xx + bellow*yy + iy*xdim + (ix+1) ] = nvet;
+              nvet++;
+        }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0020) != 0) )    /* cube vertex 1-5 */
+/* WLH 24 Oct 97
+      {   if (!(iy != 0) && vnode5 < INV_VAL && vnode1 < INV_VAL)
+      {   if (!(iy != 0) && !Float.isNaN(vnode5) && !Float.isNaN(vnode1))
+*/
+      // test for not missing
+      {   if (!(iy != 0) && vnode5 == vnode5 && vnode1 == vnode1)
+        {
+              cp = ( ( isovalue - vnode1 ) / ( vnode5 - vnode1 ) );
+              VX[0][nvet] = xo + xs * (ix+1);
+              VY[0][nvet] = yo + ys * iy;
+              VZ[0][nvet] = zo + zs * (cp + iz);
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[0][pt + ydim];
+              VY[0][nvet] = (float) cp * samples[1][pt + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[1][pt + ydim];
+              VZ[0][nvet] = (float) cp * samples[2][pt + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[2][pt + ydim];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + ydim + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim + xdim_x_ydim]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + ydim] < 0) ?
+                      ((float) auxValues[j][pt + ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + xdim_x_ydim] +
+                                   (1.0f-cp) * auxValues[j][pt + ydim];
+*/
+              }
+
+              P_array[ 2*xx + 2*yy + front*zz + iy ] = nvet;
+              nvet++;
+        }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0040) != 0) )     /* cube vertex 2-3 */
+/* WLH 24 Oct 97
+      {   if (!(iz != 0) && vnode3 < INV_VAL && vnode2 < INV_VAL)
+      {   if (!(iz != 0) && !Float.isNaN(vnode3) && !Float.isNaN(vnode2))
+*/
+      // test for not missing
+      {   if (!(iz != 0) && vnode3 == vnode3 && vnode2 == vnode2)
+        {
+              cp = ( ( isovalue - vnode2 ) / ( vnode3 - vnode2 ) );
+              VX[0][nvet] = xo + xs * (cp + ix);
+              VY[0][nvet] = yo + ys * (iy+1);
+              VZ[0][nvet] = zo + zs * iz;
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + ydim + 1] +
+                         (1.0f-cp) * samples[0][pt + 1];
+              VY[0][nvet] = (float) cp * samples[1][pt + ydim + 1] +
+                         (1.0f-cp) * samples[1][pt + 1];
+              VZ[0][nvet] = (float) cp * samples[2][pt + ydim + 1] +
+                         (1.0f-cp) * samples[2][pt + 1];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + ydim + 1] < 0) ?
+                      ((float) auxValues[j][pt + ydim + 1]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim + 1]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + 1] < 0) ?
+                      ((float) auxValues[j][pt + 1]) + 256.0f :
+                      ((float) auxValues[j][pt + 1]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + 1] +
+                                   (1.0f-cp) * auxValues[j][pt + 1];
+*/
+              }
+
+              P_array[ bellow*xx + ix*ydim + (iy+1) ] = nvet;
+              nvet++;
+        }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0080) != 0) )  /* cube vertex 2-6 */
+/* WLH 24 Oct 97
+      {   if (!(ix != 0) && vnode6 < INV_VAL && vnode2 < INV_VAL)
+      {   if (!(ix != 0) && !Float.isNaN(vnode6) && !Float.isNaN(vnode2))
+*/
+      // test for not missing
+      {   if (!(ix != 0) && vnode6 == vnode6 && vnode2 == vnode2)
+        {
+              cp = ( ( isovalue - vnode2 ) / ( vnode6 - vnode2 ) );
+              VX[0][nvet] = xo + xs * ix;
+              VY[0][nvet] = yo + ys * (iy+1);
+              VZ[0][nvet] = zo + zs * (cp + iz);
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim] +
+                         (1.0f-cp) * samples[0][pt + 1];
+              VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim] +
+                         (1.0f-cp) * samples[1][pt + 1];
+              VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim] +
+                         (1.0f-cp) * samples[2][pt + 1];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + 1 + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + 1 + xdim_x_ydim]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + 1] < 0) ?
+                      ((float) auxValues[j][pt + 1]) + 256.0f :
+                      ((float) auxValues[j][pt + 1]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + xdim_x_ydim] +
+                                   (1.0f-cp) * auxValues[j][pt + 1];
+*/
+              }
+
+              P_array[ 2*xx + 2*yy + rear*zz + (iy+1) ] = nvet;
+              nvet++;
+        }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0100) != 0) )     /* cube vertex 3-7 */
+/* WLH 24 Oct 97
+      {   if (vnode7 < INV_VAL && vnode3 < INV_VAL)
+      {   if (!Float.isNaN(vnode7) && !Float.isNaN(vnode3))
+*/
+      // test for not missing
+      {   if (vnode7 == vnode7 && vnode3 == vnode3)
+          {
+              cp = ( ( isovalue - vnode3 ) / ( vnode7 - vnode3 ) );
+              VX[0][nvet] = xo + xs * (ix+1);
+              VY[0][nvet] = yo + ys * (iy+1);
+              VZ[0][nvet] = zo + zs * (cp + iz);
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[0][pt + ydim + 1];
+              VY[0][nvet] = (float) cp * samples[1][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[1][pt + ydim + 1];
+              VZ[0][nvet] = (float) cp * samples[2][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[2][pt + ydim + 1];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + ydim + 1] < 0) ?
+                      ((float) auxValues[j][pt + ydim + 1]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim + 1]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + ydim + xdim_x_ydim] +
+                                   (1.0f-cp) * auxValues[j][pt + ydim + 1];
+*/
+              }
+
+              P_array[ 2*xx + 2*yy + front*zz + (iy+1) ] = nvet;
+              nvet++;
+        }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0200) != 0) )    /* cube vertex 4-5 */
+/* WLH 24 Oct 97
+      {   if (!(iy != 0) && vnode5 < INV_VAL && vnode4 < INV_VAL)
+      {   if (!(iy != 0) && !Float.isNaN(vnode5) && !Float.isNaN(vnode4))
+*/
+      // test for not missing
+      {   if (!(iy != 0) && vnode5 == vnode5 && vnode4 == vnode4)
+        {
+              cp = ( ( isovalue - vnode4 ) / ( vnode5 - vnode4 ) );
+              VX[0][nvet] = xo + xs * (cp + ix);
+              VY[0][nvet] = yo + ys * iy;
+              VZ[0][nvet] = zo + zs * (iz+1);
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[0][pt + xdim_x_ydim];
+              VY[0][nvet] = (float) cp * samples[1][pt + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[1][pt + xdim_x_ydim];
+              VZ[0][nvet] = (float) cp * samples[2][pt + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[2][pt + xdim_x_ydim];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + ydim + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim + xdim_x_ydim]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + xdim_x_ydim]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + ydim + xdim_x_ydim] +
+                                   (1.0f-cp) * auxValues[j][pt + xdim_x_ydim];
+*/
+              }
+
+              P_array[ above*xx + ix*ydim + iy ] = nvet;
+              nvet++;
+          }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0400) != 0) )     /* cube vertex 4-6 */
+/* WLH 24 Oct 97
+      {   if (!(ix != 0) && vnode6 < INV_VAL && vnode4 < INV_VAL)
+      {   if (!(ix != 0) && !Float.isNaN(vnode6) && !Float.isNaN(vnode4))
+*/
+      // test for not missing
+      {   if (!(ix != 0) && vnode6 == vnode6 && vnode4 == vnode4)
+          {
+              cp = ( ( isovalue - vnode4 ) / ( vnode6 - vnode4 ) );
+              VX[0][nvet] = xo + xs * ix;
+              VY[0][nvet] = yo + ys * (cp + iy);
+              VZ[0][nvet] = zo + zs * (iz+1);
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + 1 + xdim_x_ydim] +
+                         (1.0f-cp) * samples[0][pt + xdim_x_ydim];
+              VY[0][nvet] = (float) cp * samples[1][pt + 1 + xdim_x_ydim] +
+                         (1.0f-cp) * samples[1][pt + xdim_x_ydim];
+              VZ[0][nvet] = (float) cp * samples[2][pt + 1 + xdim_x_ydim] +
+                         (1.0f-cp) * samples[2][pt + xdim_x_ydim];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + 1 + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + 1 + xdim_x_ydim]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + xdim_x_ydim]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + xdim_x_ydim] +
+                                   (1.0f-cp) * auxValues[j][pt + xdim_x_ydim];
+*/
+              }
+
+              P_array[ 2*xx + above*yy + iy*xdim + ix ] = nvet;
+              nvet++;
+          }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x0800) != 0) )     /* cube vertex 5-7 */
+/* WLH 24 Oct 97
+      {   if (vnode7 < INV_VAL && vnode5 < INV_VAL)
+      {   if (!Float.isNaN(vnode7) && !Float.isNaN(vnode5))
+*/
+      // test for not missing
+      {   if (vnode7 == vnode7 && vnode5 == vnode5)
+        {
+              cp = ( ( isovalue - vnode5 ) / ( vnode7 - vnode5 ) );
+              VX[0][nvet] = xo + xs * (ix+1);
+              VY[0][nvet] = yo + ys * (cp + iy);
+              VZ[0][nvet] = zo + zs * (iz+1);
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[0][pt + ydim + xdim_x_ydim];
+              VY[0][nvet] = (float) cp * samples[1][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[1][pt + ydim + xdim_x_ydim];
+              VZ[0][nvet] = (float) cp * samples[2][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[2][pt + ydim + xdim_x_ydim];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + ydim + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + ydim + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + ydim + xdim_x_ydim]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + ydim + xdim_x_ydim] +
+                                   (1.0f-cp) * auxValues[j][pt + ydim + xdim_x_ydim];
+*/
+              }
+
+              P_array[ 2*xx + above*yy + iy*xdim + (ix+1) ] = nvet;
+              nvet++;
+        }
+      }
+      if ( ((pol_edges[ptFLAG[ncube]][3] & 0x1000) != 0) )     /* cube vertex 6-7 */
+/* WLH 24 Oct 97
+      {   if (vnode7 < INV_VAL && vnode6 < INV_VAL)
+      {   if (!Float.isNaN(vnode7) && !Float.isNaN(vnode6))
+*/
+      // test for not missing
+      {   if (vnode7 == vnode7 && vnode6 == vnode6)
+        {
+              cp = ( ( isovalue - vnode6 ) / ( vnode7 - vnode6 ) );
+              VX[0][nvet] = xo + xs * (cp + ix);
+              VY[0][nvet] = yo + ys * (iy+1);
+              VZ[0][nvet] = zo + zs * (iz+1);
+/*
+              VX[0][nvet] = (float) cp * samples[0][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[0][pt + 1 + xdim_x_ydim];
+              VY[0][nvet] = (float) cp * samples[1][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[1][pt + 1 + xdim_x_ydim];
+              VZ[0][nvet] = (float) cp * samples[2][pt + 1 + ydim + xdim_x_ydim] +
+                         (1.0f-cp) * samples[2][pt + 1 + xdim_x_ydim];
+*/
+
+              for (int j=0; j<naux; j++) {
+                t = (int) ( cp * ((auxValues[j][pt + 1 + ydim + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + 1 + ydim + xdim_x_ydim]) ) +
+                    (1.0f - cp) * ((auxValues[j][pt + 1 + xdim_x_ydim] < 0) ?
+                      ((float) auxValues[j][pt + 1 + xdim_x_ydim]) + 256.0f :
+                      ((float) auxValues[j][pt + 1 + xdim_x_ydim]) ) );
+                tempaux[j][nvet] = (byte)
+                  ( (t < 0) ? 0 : ((t > 255) ? -1 : ((t < 128) ? t : t - 256) ) );
+/* MEM_WLH
+                tempaux[j][nvet] = (float) cp * auxValues[j][pt + 1 + ydim + xdim_x_ydim] +
+                                   (1.0f-cp) * auxValues[j][pt + 1 + xdim_x_ydim];
+*/
+              }
+
+              P_array[ above*xx + ix*ydim + (iy+1) ] = nvet;
+              nvet++;
+        }
+      }
+     }
+        /* end  find_vertex_invalid_cube(ncube); */
+
+                        }
+                    } /* end  if (exist_polygon_in_cube(ncube)) */
+                    ncube++; pt++;
+                } /* end  for ( iy = 0; iy < ydim - 1; iy++ ) */
+             /* swap_planes(Z,rear,front); */
+                caseA = rear;
+                rear = front;
+                front = caseA;
+                pt++;
+             /* end  swap_planes(Z,rear,front); */
+            } /* end  for ( ix = 0; ix < xdim - 1; ix++ ) */
+           /*  swap_planes(XY,bellow,above); */
+               caseA = bellow;
+               bellow = above;
+               above = caseA;
+            pt += ydim;
+           /* end  swap_planes(XY,bellow,above); */
+        } /* end  for ( iz = 0; iz < zdim - 1; iz++ ) */
+
+    // copy tempaux array into auxLevels array
+    for (int i=0; i<naux; i++) {
+      auxLevels[i] = new byte[nvet];
+      System.arraycopy(tempaux[i], 0, auxLevels[i], 0, nvet);
+    }
+
+    return nvet;
+  }
+
+
+  /**
+   * Check to see if this Linear3DSet is equal to the Object
+   * in question.
+   * @param  set  Object in question
+   * @return true if <code>set</code> is a Linear3DSet and each
+   *         of the Linear1DSet-s that make up this cross product
+   *         are equal.
+   */
+  public boolean equals(Object set) {
+    if (!(set instanceof Linear3DSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    return (X.equals(((Linear3DSet) set).getX()) &&
+            Y.equals(((Linear3DSet) set).getY()) &&
+            Z.equals(((Linear3DSet) set).getZ()));
+  }
+
+  /**
+   * Returns the hash code for this instance.
+   * @return                    The hash code for this instance.
+   */
+  public int hashCode()
+  {
+    if (!hashCodeSet)
+    {
+      hashCode =
+        unitAndCSHashCode() ^ X.hashCode() ^ Y.hashCode() ^ Z.hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  /**
+   * Get the indexed component (X is at 0, Y is at 1, and Z is at 2)
+   *
+   * @param i Index of component
+   * @return The requested component
+   * @exception ArrayIndexOutOfBoundsException If an invalid index is
+   *                                           specified.
+   */
+  public Linear1DSet getLinear1DComponent(int i) {
+    if (i == 0) return getX();
+    else if (i == 1) return getY();
+    else if (i == 2) return getZ();
+    else throw new ArrayIndexOutOfBoundsException("Invalid component index");
+  }
+
+  /**
+   * Return a clone of this object with a new MathType.
+   * @param  type  new MathType.
+   * @return  new Linear3DSet with <code>type</code>.
+   * @throws VisADException  if <code>type</code> is not compatible
+   *                         with MathType of component Linear1DSets.
+   */
+  public Object cloneButType(MathType type) throws VisADException {
+    Linear1DSet[] sets = {(Linear1DSet) X.clone(),
+                          (Linear1DSet) Y.clone(),
+                          (Linear1DSet) Z.clone()};
+    return new Linear3DSet(type, sets, DomainCoordinateSystem,
+                           SetUnits, SetErrors, cacheSamples);
+  }
+
+  /**
+   * Extended version of the toString() method.
+   * @param  pre  prefix for string.
+   * @return wordy string describing this Linear3DSet.
+   */
+  public String longString(String pre) throws VisADException {
+    String s = pre + "Linear3DSet: Length = " + Length + "\n";
+    s = s + pre + "  Dimension 1: Length = " + X.getLength() +
+            " Range = " + X.getFirst() + " to " + X.getLast() + "\n";
+    s = s + pre + "  Dimension 2: Length = " + Y.getLength() +
+            " Range = " + Y.getFirst() + " to " + Y.getLast() + "\n";
+    s = s + pre + "  Dimension 3: Length = " + Z.getLength() +
+            " Range = " + Z.getFirst() + " to " + Z.getLast() + "\n";
+    return s;
+  }
+
+}
+
diff --git a/visad/LinearLatLonSet.java b/visad/LinearLatLonSet.java
new file mode 100644
index 0000000..af98b30
--- /dev/null
+++ b/visad/LinearLatLonSet.java
@@ -0,0 +1,536 @@
+//
+// LinearLatLonSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   LinearLatLonSet represents a finite set of samples of
+   (Latitude, Longitude) in a cross product of two
+   arithmetic progressions.<P>
+
+   This class exists to override valueToInterp (as defined in GriddedSet)
+   in order to handle Longitude wrapping.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For more detail, see the example in Linear2DSet.java.<P>
+*/
+public class LinearLatLonSet extends Linear2DSet {
+
+  private boolean LongitudeWrap;
+  private double WrapStep, WrapFactor;
+  private int latI;             // index of latitude component
+  private int lonI;             // index of longitude component
+  private double halfPiLat;     // 0.5*pi in set lat units
+  private double halfPiLon;     // 0.5*pi in set lon units
+  private double twoPiLon;      // 2*pi in set lon units
+  private Linear1DSet lat;      // references X or Y in super
+  private Linear1DSet lon;      // references Y or X in super
+
+  /**
+   * Construct a 2-D cross product of arithmetic progressions whose east
+   * and west edges may be joined (for interpolation purposes), with
+   * null errors, CoordinateSystem and Units are defaults from type.
+   * @param type     MathType for this LinearLatLonSet.  Must be consistent
+   *                 with MathType-s of sets.
+   * @param sets     Linear1DSets that make up this LinearLatLonSet.
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public LinearLatLonSet(MathType type, Linear1DSet[] sets) throws VisADException {
+    this(type, sets, null, null, null);
+  }
+
+  /** 
+   * Construct a 2-D cross product of arithmetic progressions whose east
+   * and west edges may be joined (for interpolation purposes), with
+   * null errors, CoordinateSystem and Units are defaults from type 
+   * @param type       MathType for this LinearLatLonSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public LinearLatLonSet(MathType type, double first1, double last1, int length1,
+                                        double first2, double last2, int length2)
+         throws VisADException {
+    this(type, first1, last1, length1, first2, last2, length2, null, null, null);
+  }
+
+  /**
+   * Construct a 2-D cross product of arithmetic progressions whose east
+   * and west edges may be joined (for interpolation purposes), with
+   * specified <code>errors</code>, <code>coord_sys</code> and <code>
+   * units</code>.
+   * @param type       MathType for this LinearLatLonSet.  Must be consistent
+   *                   with MathType-s of sets.
+   * @param sets       Linear1DSets that make up this LinearLatLonSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public LinearLatLonSet(MathType type, Linear1DSet[] sets,
+                         CoordinateSystem coord_sys, Unit[] units,
+                         ErrorEstimate[] errors) throws VisADException {
+    this(type, sets, coord_sys, units, errors, false);
+  }
+
+  /**
+   * Construct a 2-D cross product of arithmetic progressions whose east
+   * and west edges may be joined (for interpolation purposes), with
+   * specified <code>errors</code>, <code>coord_sys</code> and <code>
+   * units</code>.
+   * @param type       MathType for this LinearLatLonSet.  Must be consistent
+   *                   with MathType-s of sets.
+   * @param sets       Linear1DSets that make up this LinearLatLonSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public LinearLatLonSet(MathType type, Linear1DSet[] sets,
+                         CoordinateSystem coord_sys, Unit[] units,
+                         ErrorEstimate[] errors, 
+                         boolean cache) throws VisADException {
+    super(type, sets, coord_sys, units, errors, cache);
+    setParameters();
+    checkWrap();
+  }
+
+  /** a 2-D cross product of arithmetic progressions that whose east
+      and west edges may be joined (for interpolation purposes);
+      coordinate_system and units must be compatible with defaults
+      for type, or may be null; errors may be null */
+  /** 
+   * Construct a 2-D cross product of arithmetic progressions whose east
+   * and west edges may be joined (for interpolation purposes), with
+   * specified <code>errors</code>, <code>coord_sys</code> and <code>
+   * units</code>.
+   * @param type       MathType for this LinearLatLonSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public LinearLatLonSet(MathType type, double first1, double last1, int length1,
+                         double first2, double last2, int length2,
+                         CoordinateSystem coord_sys, Unit[] units,
+                         ErrorEstimate[] errors) throws VisADException {
+    this(type, first1, last1, length1, first2, last2, length2, coord_sys,
+          units, errors, false);
+  }
+
+  /** 
+   * Construct a 2-D cross product of arithmetic progressions whose east
+   * and west edges may be joined (for interpolation purposes), with
+   * specified <code>errors</code>, <code>coord_sys</code> and <code>
+   * units</code>.
+   * @param type       MathType for this LinearLatLonSet.  
+   * @param first1     first value in first arithmetic progression
+   * @param last1      last value in first arithmetic progression
+   * @param length1    number of values in first arithmetic progression
+   * @param first2     first value in second arithmetic progression
+   * @param last2      last value in second arithmetic progression
+   * @param length2    number of values in second arithmetic progression
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with values in
+   *                   <code>sets</code>.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}
+   * @throws VisADException illegal sets or other VisAD error.
+   */
+  public LinearLatLonSet(MathType type, double first1, double last1, int length1,
+                         double first2, double last2, int length2,
+                         CoordinateSystem coord_sys, Unit[] units,
+                         ErrorEstimate[] errors,
+                         boolean cache) throws VisADException {
+    super(type, first1, last1, length1, first2, last2, length2, coord_sys,
+          units, errors, cache);
+    setParameters();
+    checkWrap();
+  }
+
+  private void setParameters() throws VisADException, UnitException {
+    MathType    type0 = ((SetType)getType()).getDomain().getComponent(0);
+
+    latI = RealType.Latitude.equals(type0) ? 0 : 1;
+
+    if (latI == 0) {
+      lonI = 1;
+      lat = X;
+      lon = Y;
+    } else {
+      lonI = 0;
+      lat = Y;
+      lon = X;
+    }
+
+    Unit[] units = getSetUnits();
+    halfPiLat = SI.radian.toThat(0.5*Math.PI, units[latI]);
+    halfPiLon = SI.radian.toThat(0.5*Math.PI, units[lonI]);
+    twoPiLon = 4.0 * halfPiLon;
+  }
+
+  private void setLatLonUnits()
+  {
+  }
+
+  void checkWrap() throws VisADException {
+    Unit[] units = getSetUnits();
+
+    if (Low[latI] < -halfPiLat || Hi[latI] > halfPiLat ||
+        Low[lonI] < -twoPiLon || Hi[lonI] > twoPiLon ||
+        (Hi[lonI] - Low[lonI]) > twoPiLon) {
+      throw new SetException("LinearLatLonSet: out of bounds " +
+                             "(note Lat and Lon in Radians)");
+    }
+    LongitudeWrap =
+        (Hi[lonI] - Low[lonI]) + 2.0 * Math.abs(lon.getStep()) >= twoPiLon &&
+        lon.getLength() > 1;
+    if (LongitudeWrap) {
+      WrapStep = twoPiLon - (Hi[lonI] - Low[lonI]);
+      WrapFactor = Math.abs(lon.getStep()) / WrapStep;
+    }
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in (Latitude, Longitude) */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    if (grid.length != 2) {
+      throw new SetException("LinearLatLonSet.gridToValue: grid dimension" +
+                             " should be 2, not " + grid.length);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("LinearLatLonSet.gridToValue: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = grid[0].length;
+    float[][] gridLat = new float[1][];
+    gridLat[0] = grid[latI];
+    float[][] gridLon = new float[1][];
+    gridLon[0] = grid[lonI];
+    if (LongitudeWrap) {
+      float len = (float) lon.getLength();
+      float lenh = len - 0.5f;
+      float lenm = len - 1.0f;
+      for (int i=0; i<length; i++) {
+        if (gridLon[0][i] > lenm) {
+          gridLon[0][i] = (float) (lenm + (gridLon[0][i] - lenm) / WrapFactor);
+          if (gridLon[0][i] > lenh) gridLon[0][i] -= len;
+        }
+        else if (gridLon[0][i] < 0.0) {
+          gridLon[0][i] = (float) (gridLon[0][i] / WrapFactor);
+          if (gridLon[0][i] < -0.5) gridLon[0][i] += len;
+        }
+      }
+    }
+    float[][] valueLat = lat.gridToValue(gridLat);
+    float[][] valueLon = lon.gridToValue(gridLon);
+    float[][] value = new float[2][];
+    value[latI] = valueLat[0];
+    value[lonI] = valueLon[0];
+    return value;
+  }
+
+  /** transform an array of values in (Latitude, Longitude)
+      to an array of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    if (value.length != 2) {
+      throw new SetException("LinearLatLonSet.valueToGrid: value dimension" +
+                             " should be 2, not " + value.length);
+    }
+    if (Length > 1 && (Lengths[0] < 2 || Lengths[1] < 2)) {
+      throw new SetException("LinearLatLonSet.valueToGrid: requires all grid " +
+                             "dimensions to be > 1");
+    }
+    int length = value[0].length;
+    float[][] valueLat = new float[1][];
+    valueLat[0] = value[latI];
+    float[] valueLon = value[lonI];
+    float[][] gridLat = lat.valueToGrid(valueLat); // Latitude-s
+
+    float[] gridLon = new float[length];
+    float l = (float) (lon.getFirst() - 0.5 * lon.getStep());
+    float h = (float) (lon.getFirst() + (((float) lon.getLength()) - 0.5) * lon.getStep());
+    if (h < l) {
+      float temp = l;
+      l = h;
+      h = temp;
+    }
+    if (LongitudeWrap) {
+      l = (float) (l + 0.5 * Math.abs(lon.getStep()) - 0.5 * WrapStep);
+      h = (float) (l + twoPiLon);
+    }
+    for (int i=0; i<length; i++) {
+      float v = (float) (valueLon[i] % (twoPiLon));
+      if (v <= l ) v += twoPiLon;
+      else if (h <= v) v -= twoPiLon;
+      gridLon[i] = (float)
+        ((l < v && v < h) ? (v - lon.getFirst()) * lon.getInvstep() : Double.NaN);
+    }
+    if (LongitudeWrap) {
+      float len = (float) lon.getLength();
+      float lenh = len - 0.5f;
+      float lenm = len - 1.0f;
+      for (int i=0; i<length; i++) {
+        if (gridLon[i] > lenm) {
+          gridLon[i] = (float) (lenm + (gridLon[i] - lenm) * WrapFactor);
+          if (gridLon[i] > lenh) gridLon[i] -= len;
+        }
+        else if (gridLon[i] < 0.0) {
+          gridLon[i] = (float) (gridLon[i] * WrapFactor);
+          if (gridLon[i] < -0.5) gridLon[i] += len;
+        }
+      }
+    }
+    float[][] grid = new float[2][];
+    grid[latI] = gridLat[0];
+    grid[lonI] = gridLon;
+    return grid;
+  }
+
+  /** for each of an array of values in (Latitude, Longitude), compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible).
+      this code is the result of substituting 2 for ManifoldDimension in
+      GriddedSet.valueToInterp, and adding logic to handle LongitudeWrap */
+  public void valueToInterp(float[][] value, int[][] indices, float weights[][])
+              throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("LinearLatLonSet Domain dimension" +
+                             " should be 2, not " + DomainDimension);
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length) {
+      throw new SetException("LinearLatLonSet.valueToInterp: indices length " +
+                             indices.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    if (weights.length != length) {
+      throw new SetException("LinearLatLonSet.valueToInterp: weights length " +
+                             weights.length +
+                             " doesn't match value[0] length " +
+                             value[0].length);
+    }
+    float[][] grid = valueToGrid(value); // convert value array to grid coord array
+    /* if LongitudeWrap, grid[lonI][i] should be between -0.5 and lon.Length-0.5 */
+
+    int i, j, k; // loop indices
+    int lis; // temporary length of is & cs
+    int length_is; // final length of is & cs, varies by i
+    int isoff; // offset along one grid dimension
+    float a, b; // weights along one grid dimension; a + b = 1.0
+    int[] is; // array of indices, becomes part of indices
+    float[] cs; // array of coefficients, become part of weights
+
+    int base; // base index, as would be returned by valueToIndex
+    int[] l = new int[2]; // integer 'factors' of base
+    float[] c = new float[2]; // fractions with l; -0.5 <= c <= 0.5
+
+    // array of index offsets by grid dimension
+    int[] off = new int[2];
+    off[0] = 1;
+    off[1] = off[0] * Lengths[0];
+
+    for (i=0; i<length; i++) {
+      boolean WrapThis = false;
+      // compute length_is, base, l & c
+      length_is = 1;
+      if (Double.isNaN(grid[lonI][i])) {
+        base = -1;
+      }
+      else {
+        l[lonI] = (int) (grid[lonI][i] + 0.5);
+        c[lonI] = grid[lonI][i] - ((float) l[lonI]);
+        if (!((l[lonI] == 0 && c[lonI] <= 0.0) ||
+              (l[lonI] == Lengths[lonI] - 1 && c[lonI] >= 0.0))) {
+          // interp along Longitude (dim lonI) if between two valid grid coords
+          length_is *= 2;
+        }
+        else if (LongitudeWrap) {
+          length_is *= 2;
+          WrapThis = true;
+        }
+        base = l[lonI];
+      }
+      if (base>=0) {
+        if (Double.isNaN(grid[latI][i])) {
+          base = -1;
+        }
+        else {
+          l[latI] = (int) (grid[latI][i] + 0.5);
+          c[latI] = grid[latI][i] - ((float) l[latI]);
+          if (!((l[latI] == 0 && c[latI] <= 0.0) ||
+                (l[latI] == Lengths[latI] - 1 && c[latI] >= 0.0))) {
+            // interp along Latitude (dim latI) if between two valid grid coords
+            length_is *= 2;
+          }
+          base = off[latI] * l[latI] + off[lonI] * base;
+        }
+      }
+
+      if (base < 0) {
+        // value is out of grid so return null
+        is = null;
+        cs = null;
+      }
+      else {
+        // create is & cs of proper length, and init first element
+        is = new int[length_is];
+        cs = new float[length_is];
+        is[0] = base;
+        cs[0] = 1.0f;
+        lis = 1;
+
+        // unroll loop over dimension = 0, 1
+        // WLH 31 Oct 2001
+        if (!((l[latI] == 0 && c[latI] <= 0.0) ||
+              (l[latI] == Lengths[latI] - 1 && c[latI] >= 0.0)) ) {
+        // if (WrapThis || !((l[latI] == 0 && c[latI] <= 0.0) ||
+        //       (l[latI] == Lengths[latI] - 1 && c[latI] >= 0.0)) ) {
+
+          // interp along Latitude (dim latI) if between two valid grid coords
+          if (c[latI] >= 0.0) {
+            // grid coord above base
+            isoff = off[latI];
+            a = 1.0f - c[latI];
+            b = c[latI];
+          }
+          else {
+            // grid coord below base
+            isoff = -off[latI];
+            a = 1.0f + c[latI];
+            b = -c[latI];
+          }
+          // float is & cs; adjust new offsets; split weights
+          for (k=0; k<lis; k++) {
+            is[k+lis] = is[k] + isoff;
+            cs[k+lis] = cs[k] * b;
+            cs[k] *= a;
+          }
+          lis *= 2;
+        }
+        if (WrapThis || !((l[lonI] == 0 && c[lonI] <= 0.0) ||
+              (l[lonI] == Lengths[lonI] - 1 && c[lonI] >= 0.0)) ) {
+          // interp along Longitude (dim lonI) if between two valid grid coords
+          if (WrapThis && l[lonI] == 0) {
+            // c[lonI] <= 0.0; grid coord below base
+            isoff = off[lonI] * (Lengths[lonI] - 1); // so Wrap to top
+            a = 1.0f + c[lonI];
+            b = -c[lonI];
+          }
+          else if (WrapThis && l[lonI] == Lengths[lonI] - 1) {
+            // c[lonI] >= 0.0; grid coord above base
+            isoff = -off[lonI] * (Lengths[lonI] - 1); // so Wrap to bottom
+            a = 1.0f - c[lonI];
+            b = c[lonI];
+          }
+          else if (c[lonI] >= 0.0) {
+            // grid coord above base
+            isoff = off[lonI];
+            a = 1.0f - c[lonI];
+            b = c[lonI];
+          }
+          else {
+            // grid coord below base
+            isoff = -off[lonI];
+            a = 1.0f + c[lonI];
+            b = -c[lonI];
+          }
+          // float is & cs; adjust new offsets; split weights
+          for (k=0; k<lis; k++) {
+            is[k+lis] = is[k] + isoff;
+            cs[k+lis] = cs[k] * b;
+            cs[k] *= a;
+          }
+          lis *= 2;
+        }
+        // end of unroll loop over dimension = 0, 1
+      }
+      indices[i] = is;
+      weights[i] = cs;
+    }
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof LinearLatLonSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    return (X.equals(((LinearLatLonSet) set).getX()) &&
+            Y.equals(((LinearLatLonSet) set).getY()));
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    Linear1DSet[] sets = {(Linear1DSet) X.clone(),
+                          (Linear1DSet) Y.clone()};
+    return new LinearLatLonSet(type, sets, DomainCoordinateSystem,
+                               SetUnits, SetErrors);
+  }
+
+  public String longString(String pre) throws VisADException {
+    String s = pre + "LinearLatLonSet: Length = " + Length + "\n";
+    s = s + pre + "  Dimension 1: Length = " + X.getLength() +
+            " Range = " + X.getFirst() + " to " + X.getLast() + "\n";
+    s = s + pre + "  Dimension 2: Length = " + Y.getLength() +
+            " Range = " + Y.getFirst() + " to " + Y.getLast() + "\n";
+    return s;
+  }
+
+}
+
diff --git a/visad/LinearNDSet.java b/visad/LinearNDSet.java
new file mode 100644
index 0000000..43c2997
--- /dev/null
+++ b/visad/LinearNDSet.java
@@ -0,0 +1,575 @@
+//
+// LinearNDSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   LinearNDSet represents a finite set of samples of R^Dimension
+   in a cross product of arithmetic progressions.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For more detail, see the example in Linear2DSet.java.<P>
+*/
+public class LinearNDSet extends GriddedSet
+       implements LinearSet {
+
+  Linear1DSet[] L;
+  private boolean cacheSamples = false;
+
+  /** 
+   * Construct an N-dimensional set as the product of N Linear1DSets,
+   * with null errors, CoordinateSystem and Units are defaults from
+   * type.
+   * @param type     MathType for this LinearNDSet.  Must be consistent
+   *                 with MathType-s of sets.
+   * @param l	     Linear1DSets that make up this LinearNDSet.
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public LinearNDSet(MathType type, Linear1DSet[] l) throws VisADException {
+    this(type, l, null, null, null);
+  }
+
+  /** 
+   * Construct an N-dimensional set as the product of N arithmetic
+   * progressions (lengths[i] samples between firsts[i] and lasts[i]),
+   * with null errors, CoordinateSystem and Units are defaults from type 
+   * @param type       MathType for this LinearNDSet.
+   * @param firsts     array of first values for each of the 
+   *                   arithmetic progressions
+   * @param lasts      array of last values for each of the 
+   *                   arithmetic progressions
+   * @param lengths    array of number of samples for each of the 
+   *                   arithmetic progressions
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public LinearNDSet(MathType type, double[] firsts, double[] lasts, int[] lengths)
+         throws VisADException {
+    this(type, get_linear1d_array(type, firsts, lasts, lengths, null),
+         null, null, null);
+  }
+
+  /** 
+   * Construct an N-dimensional set as the product of N arithmetic
+   * progressions (lengths[i] samples between firsts[i] and lasts[i]),
+   * coordinate_system and units must be compatible with defaults for
+   * type, or may be null; errors may be null.
+   * @param type       MathType for this LinearNDSet.
+   * @param firsts     array of first values for each of the 
+   *                   arithmetic progressions
+   * @param lasts      array of last values for each of the 
+   *                   arithmetic progressions
+   * @param lengths    array of number of samples for each of the 
+   *                   arithmetic progressions
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with default of
+   *                   <code>type</code> if not null.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public LinearNDSet(MathType type, double[] firsts, 
+                     double[] lasts, int[] lengths,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors) throws VisADException {
+    this(type, firsts, lasts, lengths, coord_sys, units, errors, false);
+  }
+
+  /** 
+   * Construct an N-dimensional set as the product of N arithmetic
+   * progressions (lengths[i] samples between firsts[i] and lasts[i]),
+   * coordinate_system and units must be compatible with defaults for
+   * type, or may be null; errors may be null.
+   * @param type       MathType for this LinearNDSet.
+   * @param firsts     array of first values for each of the 
+   *                   arithmetic progressions
+   * @param lasts      array of last values for each of the 
+   *                   arithmetic progressions
+   * @param lengths    array of number of samples for each of the 
+   *                   arithmetic progressions
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with default of
+   *                   <code>type</code> if not null.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}.
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public LinearNDSet(MathType type, double[] firsts, 
+                     double[] lasts, int[] lengths,
+                     CoordinateSystem coord_sys, Unit[] units,
+                     ErrorEstimate[] errors,
+                     boolean cache) throws VisADException {
+    this(type, get_linear1d_array(type, firsts, lasts, lengths, units),
+         coord_sys, units, errors, cache);
+  }
+
+  /** 
+   * Construct an N-dimensional set as the product of N Linear1DSets;
+   * coordinate_system and units must be compatible with defaults
+   * for type, or may be null; errors may be null 
+   * @param type     MathType for this LinearNDSet.  Must be consistent
+   *                 with MathType-s of sets.
+   * @param l	     Linear1DSets that make up this LinearNDSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with default of
+   *                   <code>type</code> if not null.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public LinearNDSet(MathType type, Linear1DSet[] l, CoordinateSystem coord_sys,
+                   Unit[] units, ErrorEstimate[] errors) throws VisADException {
+    this(type, l, coord_sys, units, errors, false);
+  }
+
+  /** 
+   * Construct an N-dimensional set as the product of N Linear1DSets;
+   * coordinate_system and units must be compatible with defaults
+   * for type, or may be null; errors may be null 
+   * @param type     MathType for this LinearNDSet.  Must be consistent
+   *                 with MathType-s of sets.
+   * @param l	     Linear1DSets that make up this LinearNDSet.
+   * @param coord_sys  CoordinateSystem for this set.  May be null, but
+   *                   if not, must be consistent with <code>type</code>.
+   * @param units      Unit-s for the values in <code>sets</code>.  May
+   *                   be null, but must be convertible with default of
+   *                   <code>type</code> if not null.
+   * @param errors     ErrorEstimate-s for values in <code>sets</code>,
+   *                   may be null
+   * @param cache      if true, enumerate and cache the samples.  This will
+   *                   result in a larger memory footprint, but will
+   *                   reduce the time to return samples from calls to 
+   *                   {@link #getSamples()}.
+   * @throws VisADException problem creating VisAD objects.
+   */
+  public LinearNDSet(MathType type, Linear1DSet[] l,
+                   CoordinateSystem coord_sys,
+                   Unit[] units, ErrorEstimate[] errors,
+                   boolean cache) throws VisADException {
+    super(type, null, get_lengths(l), coord_sys,
+          LinearNDSet.units_array_linear1d(l, units), errors);
+    if (DomainDimension != ManifoldDimension) {
+      throw new SetException("LinearNDSet: DomainDimension != ManifoldDimension");
+    }
+    L = LinearNDSet.linear1d_array_units(l, units);
+    for (int j=0; j<DomainDimension; j++) {
+      Low[j] = L[j].getLowX();
+      Hi[j] = L[j].getHiX();
+      if (SetErrors[j] != null ) {
+        SetErrors[j] =
+          new ErrorEstimate(SetErrors[j].getErrorValue(), (Low[j] + Hi[j]) / 2.0,
+                            Length, SetErrors[j].getUnit());
+      }
+    }
+    cacheSamples = cache;
+  }
+
+  private static int[] get_lengths(Linear1DSet[] l) throws VisADException {
+    // used by LinearNDSet constructor
+    int[] lengths = new int[l.length];
+    for (int j=0; j<l.length; j++) lengths[j] = l[j].getLength();
+    return lengths;
+  }
+
+  /**
+   * General Factory method for creating the proper linear set
+   * (Linear1DSet, Linear2DSet, etc.).
+   */
+  public static LinearSet create(MathType type, double[] firsts,
+                                  double[] lasts, int[] lengths)
+         throws VisADException {
+    return create(type, firsts, lasts, lengths, null, null, null);
+  }
+
+  public static LinearSet create(MathType type, double[] firsts, double[] lasts,
+                                  int[] lengths, CoordinateSystem coord_sys,
+                                  Unit[] units, ErrorEstimate[] errors)
+         throws VisADException {
+    switch (firsts.length) {
+      case 1:
+        return new Linear1DSet(type, firsts[0], lasts[0], lengths[0],
+                                coord_sys, units, errors);
+      case 2:
+        return new Linear2DSet(type, firsts[0], lasts[0], lengths[0],
+                                firsts[1], lasts[1], lengths[1],
+                                coord_sys, units, errors);
+      case 3:
+        return new Linear3DSet(type, firsts[0], lasts[0], lengths[0],
+                                firsts[1], lasts[1], lengths[1],
+                                firsts[2], lasts[2], lengths[2],
+                                coord_sys, units, errors);
+      default:
+        return new LinearNDSet(type, firsts, lasts, lengths,
+                               coord_sys, units, errors);
+    }
+  }
+
+  static Linear1DSet[] linear1d_array_units(Linear1DSet[] sets,
+                    Unit[] units) throws VisADException {
+    if (units == null) return sets;
+    int n = sets.length;
+    if (units.length != n) {
+      throw new SetException("units and sets lengths don't match");
+    }
+    Linear1DSet[] ss = new Linear1DSet[n];
+    for (int i=0; i<n; i++) {
+      Unit[] su = sets[i].getSetUnits();
+      if (units[i] == null || units[i].equals(su[0])) {
+        ss[i] = sets[i];
+      }
+      else {
+        CoordinateSystem cs = sets[i].getCoordinateSystem();
+        double first = sets[i].getFirst();
+        double last = sets[i].getLast();
+        if (su[0] != null) {
+          first = units[i].toThis(first, su[0]);
+          last = units[i].toThis(last, su[0]);
+        }
+        su = new Unit[] {units[i]};
+        ss[i] = new Linear1DSet(sets[i].getType(), first, last,
+                                sets[i].getLength(),
+                                sets[i].getCoordinateSystem(), su,
+                                sets[i].getSetErrors());
+      }
+    }
+    return ss;
+  }
+
+  static Unit[] units_array_linear1d(Linear1DSet[] sets,
+                    Unit[] units) throws VisADException {
+    int n = sets.length;
+    Unit[] newu = new Unit[n];
+    if (units != null && units.length != n) {
+      throw new SetException("units and sets lengths don't match");
+    }
+    for (int i=0; i<n; i++) {
+      if (units != null && units[i] != null) {
+        newu[i] = units[i];
+      }
+      else {
+        Unit[] su = sets[i].getSetUnits();
+        if (su != null) newu[i] = su[0];
+      }
+    }
+    return newu;
+  }
+
+  static Linear1DSet[] get_linear1d_array(MathType type, double[] firsts,
+                    double[] lasts, int[] lengths, Unit[] units)
+         throws VisADException {
+    // used by LinearNDSet and IntegerNDSet constructors
+    type = Set.adjustType(type);
+    int len = lengths.length;
+    if (len != firsts.length || len != lasts.length) {
+      throw new SetException("LinearNDSet: array dimensions don't match");
+    }
+    Linear1DSet[] l = new Linear1DSet[len];
+    for (int j=0; j<len; j++) {
+      RealType[] types =
+        {(RealType) ((SetType) type).getDomain().getComponent(j)};
+      SetType set_type = new SetType(new RealTupleType(types));
+      Unit[] us = {null};
+      if (units != null && units.length > j) us[0] = units[j];
+      l[j] = new Linear1DSet(set_type, firsts[j], lasts[j], lengths[j],
+                             null, us, null);
+    }
+    return l;
+  }
+
+  static Linear1DSet[] get_linear1d_array(MathType type,
+                 double first1, double last1, int length1,
+                 double first2, double last2, int length2, Unit[] units)
+                       throws VisADException {
+    double[] firsts = {first1, first2};
+    double[] lasts = {last1, last2};
+    int[] lengths = {length1, length2};
+    return get_linear1d_array(type, firsts, lasts, lengths, units);
+  }
+
+  static Linear1DSet[] get_linear1d_array(MathType type,
+                 double first1, double last1, int length1,
+                 double first2, double last2, int length2,
+                 double first3, double last3, int length3, Unit[] units)
+                       throws VisADException {
+    double[] firsts = {first1, first2, first3};
+    double[] lasts = {last1, last2, last3};
+    int[] lengths = {length1, length2, length3};
+    return get_linear1d_array(type, firsts, lasts, lengths, units);
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int dim = getDimension();
+    int length = index.length;
+    int[][] indexN = new int[dim][length];
+    float[][] values = new float[dim][];
+    int[] lengthN = new int[dim];
+    for (int j=0; j<dim; j++) lengthN[j] = L[j].getLength();
+    for (int i=0; i<length; i++) {
+      int k = index[i];
+      if (0 <= k && k < Length) {
+        for (int j=0; j<dim-1; j++) {
+          indexN[j][i] = k % lengthN[j];
+          k = k / lengthN[j];
+        }
+        indexN[dim-1][i] = k;
+      }
+      else {
+        for (int j=0; j<dim; j++) indexN[j][i] = -1;
+      }
+    }
+    for (int j=0; j<dim; j++) {
+      float[][] vals = L[j].indexToValue(indexN[j]);
+      values[j] = vals[0];
+    }
+    return values;
+  }
+
+  /** transform an array of non-integer grid coordinates to an array
+      of values in R^DomainDimension */
+  public float[][] gridToValue(float[][] grid) throws VisADException {
+    int j;
+    if (grid.length != DomainDimension) {
+      throw new SetException("LinearNDSet.gridToValue: grid dimension " +
+                             grid.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    if (Length > 1) {
+      for (j=0; j<DomainDimension; j++) {
+        if (Lengths[j] < 2) {
+          throw new SetException("LinearNDSet.gridToValue: requires all grid " +
+                                 "dimensions to be > 1");
+        }
+      }
+    }
+    int length = grid[0].length;
+    float[][][] gridJ = new float[DomainDimension][1][];
+    float[][][] valueJ = new float[DomainDimension][][];
+    for (j=0; j<DomainDimension; j++) {
+      gridJ[j][0] = grid[j];
+      valueJ[j] = L[j].gridToValue(gridJ[j]);
+    }
+    float[][] value = new float[DomainDimension][];
+    for (j=0; j<DomainDimension; j++) value[j] = valueJ[j][0];
+    return value;
+  }
+
+  /** transform an array of values in R^DomainDimension to an array
+      of non-integer grid coordinates */
+  public float[][] valueToGrid(float[][] value) throws VisADException {
+    int j;
+    if (value.length != DomainDimension) {
+      throw new SetException("LinearNDSet.valueToGrid: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    if (Length > 1) {
+      for (j=0; j<DomainDimension; j++) {
+        if (Lengths[j] < 2) {
+          throw new SetException("LinearNDSet.valueToGrid: requires all grid " +
+                                 "dimensions to be > 1");
+        }
+      }
+    }
+    int length = value[0].length;
+    float[][][] valueJ = new float[DomainDimension][1][];
+    float[][][] gridJ = new float[DomainDimension][][];
+    for (j=0; j<DomainDimension; j++) {
+      valueJ[j][0] = value[j];
+      gridJ[j] = L[j].valueToGrid(valueJ[j]);
+    }
+    float[][] grid = new float[DomainDimension][];
+    for (j=0; j<DomainDimension; j++) grid[j] = gridJ[j][0];
+    return grid;
+  }
+
+  /**
+   * Check to see if this is an empty cross-product.
+   * @return always false.
+   */
+  public boolean isMissing() {
+    return false;
+  }
+
+  /**
+   * Get the array of first values of each of the arithmetic progressions
+   * in this cross product.
+   * @return  array of first values.
+   */
+  public double[] getFirsts() throws VisADException {
+    double[] firsts = new double[L.length];
+    for (int j=0; j<firsts.length; j++) firsts[j] = L[j].getFirst();
+    return firsts;
+  }
+
+  /**
+   * Get the array of last values of each of the arithmetic progressions
+   * in this cross product.
+   * @return  array of last values.
+   */
+  public double[] getLasts() throws VisADException {
+    double[] lasts = new double[L.length];
+    for (int j=0; j<lasts.length; j++) lasts[j] = L[j].getLast();
+    return lasts;
+  }
+
+  /**
+   * Return the array of values in R^N space corresponding to
+   * this cross product of arithmetic progressions.
+   * @param  copy  if true, return a copy of the samples.
+   * @return  array of values in R^N space.
+   * @throws  VisADException  problem creating samples.
+   */
+  public float[][] getSamples(boolean copy) throws VisADException {
+    float[][]mySamples = getMySamples();
+    if (mySamples != null) {
+      return copy ? Set.copyFloats(mySamples) : mySamples;
+    }
+    float[][] samples = makeSamples ();
+    if (cacheSamples) {
+      setMySamples(samples);
+      return copy ? Set.copyFloats(samples) : samples;
+    }
+    return samples;
+  }
+
+  private float[][] makeSamples() throws VisADException {
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToValue(indices);
+  }
+
+  /**
+   * Check to see if this LinearNDSet is equal to the Object
+   * in question.
+   * @param  set  Object in question
+   * @return true if <code>set</code> is a LinearNDSet and each
+   *         of the Linear1DSet-s that make up this cross product
+   *         are equal.
+   */
+  public boolean equals(Object set) {
+    if (!(set instanceof LinearNDSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    if (DomainDimension != ((LinearNDSet) set).getDimension()) return false;
+    for (int j=0; j<DomainDimension; j++) {
+      if (!L[j].equals(((LinearNDSet) set).getLinear1DComponent(j))) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the hash code for this instance.
+   * @return                    The hash code for this instance.
+   */
+  public int hashCode()
+  {
+    if (!hashCodeSet)
+    {
+      hashCode = unitAndCSHashCode();
+      for (int i = 0; i < DomainDimension; ++i)
+        hashCode ^= L[i].hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  /**
+   * Get the indexed component.
+   *
+   * @param i Index of component
+   * @return The requested component
+   * @exception ArrayIndexOutOfBoundsException If an invalid index is
+   *                                           specified.
+   */
+  public Linear1DSet getLinear1DComponent(int i) {
+    return L[i];
+  }
+
+  /**
+   * Return a clone of this object with a new MathType.
+   * @param  type  new MathType.
+   * @return  new LinearNDSet with <code>type</code>.
+   * @throws VisADException  if <code>type</code> is not compatible
+   *                         with MathType of component Linear1DSets.
+   */
+  public Object cloneButType(MathType type) throws VisADException {
+    Linear1DSet[] l = new Linear1DSet[DomainDimension];
+    for (int j=0; j<DomainDimension; j++) {
+      l[j] = (Linear1DSet) L[j].clone();
+    }
+    return new LinearNDSet(type, l, DomainCoordinateSystem,
+                         SetUnits, SetErrors, cacheSamples);
+  }
+
+  /**
+   * Extended version of the toString() method.
+   * @param  pre  prefix for string.
+   * @return wordy string describing this LinearNDSet.
+   */
+  public String longString(String pre) throws VisADException {
+    String s = pre + "LinearNDSet: Dimension = " +
+               DomainDimension + " Length = " + Length + "\n";
+    for (int j=0; j<DomainDimension; j++) {
+      s = s + pre + "  Dimension " + j + ":" + " Length = " + L[j].getLength() +
+              " Range = " + L[j].getFirst() + " to " + L[j].getLast() + "\n";
+    }
+    return s;
+  }
+
+  /** run 'java visad.LinearNDSet' to test the LinearNDSet class */
+  public static void main(String args[]) throws VisADException {
+    RealTupleType type =
+      new RealTupleType(RealType.Generic, RealType.Generic);
+    double[] firsts = {0.0, 0.0};
+    double[] lasts = {3.0, 3.0};
+    int[] lengths = {4, 4};
+    LinearNDSet set = new LinearNDSet(type, firsts, lasts, lengths);
+    float[][] values = set.getSamples();
+    int n = values.length;
+    int m = values[0].length;
+    for (int j=0; j<m; j++) {
+      for (int i=0; i<n; i++) {
+        System.out.println("values[" + i + "][" + j + "] = " +
+                           values[i][j]);
+      }
+    }
+  }
+
+}
+
diff --git a/visad/LinearSet.java b/visad/LinearSet.java
new file mode 100644
index 0000000..b113161
--- /dev/null
+++ b/visad/LinearSet.java
@@ -0,0 +1,49 @@
+//
+// LinearSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   LinearSet is an interface for finite sets of samples of
+   R^Dimension in a cross product of arithmetic progressions.<P>
+
+   The order of the samples is the rasterization of the orders of
+   the 1D components, with the first component increasing fastest.
+   For more detail, see the example in Linear2DSet.java.<P>
+*/
+public interface LinearSet {
+
+  /**
+   * Get the indexed component.
+   *
+   * @param i Index of component
+   * @return The requested component
+   * @exception ArrayIndexOutOfBoundsException If an invalid index is
+   *                                           specified.
+   */
+  Linear1DSet getLinear1DComponent(int i);
+
+}
diff --git a/visad/List1DDoubleSet.java b/visad/List1DDoubleSet.java
new file mode 100644
index 0000000..75e0d8b
--- /dev/null
+++ b/visad/List1DDoubleSet.java
@@ -0,0 +1,180 @@
+//
+// List1DDoubleSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * List1DDoubleSet is the class for Set-s containing lists of
+ * 1-D double values with no topology.  This set does not support 
+ * interpolation.<P>
+ */
+public class List1DDoubleSet extends SimpleSet {
+
+  double[] data;
+
+  /**
+   * Constructs with a non-default CoordinateSystem.
+   *
+   * @param d			The data samples.  Must not be 
+   *				<code>null</code>.
+   * @param type		The type of the set.
+   * @param coord_sys		The coordinate system transformation.  May be
+   *				<code>null</code>.
+   * @param units		The units of the values.  May be 
+   *				<code>null</code>.
+   * @throws VisADException	Couldn't create set.
+   */
+  public List1DDoubleSet(double[] d, MathType type, CoordinateSystem coord_sys,
+            Unit[] units) throws VisADException {
+    super(type, coord_sys, units, null);
+    if (DomainDimension != 1) {
+      throw new SetException("List1DDoubleSet: type must be 1-D");
+    }
+    data = d;
+    Length = d.length;
+  }
+
+  public boolean isMissing() {
+    return (data == null);
+  }
+
+  /**
+   * Converts an array of 1-D indices to an array of values in R^1.  Note that a
+   * returned values may be Double.NaN due to double-to-float conversion.
+   * @param indices		The indices of the values to be returned.
+   * @return			The values associated with the given indices.
+   *				A Float.NaN will be returned for an out-of-range
+   *				index or a double value that cannot be converted
+   *				to a float.
+   */
+  public float[][] indexToValue(int[] indices) throws VisADException {
+    int length = indices.length;
+    float[][] value = new float[1][length];
+    for (int i=0; i<length; i++) {
+      if (indices[i] < 0 || indices[i] >= Length) {
+        for (int k=0; k<DomainDimension; k++) {
+          value[k][i] = Float.NaN;
+        }
+      }
+      else {
+        value[0][i] = (float)data[indices[i]];
+      }
+    }
+    return value;
+  }
+
+  /**
+   * Converts an array of 1-D indices to an array of values in R^1.
+   * @param indices		The indices of the values to be returned.
+   * @return			The values associated with the given indices.
+   *				A Double.NaN will be returned for an out-of-
+   *				range index.
+   */
+  public double[][] indexToDouble(int[] indices) throws VisADException {
+    int length = indices.length;
+    double[][] value = new double[1][length];
+    for (int i=0; i<length; i++) {
+      if (indices[i] < 0 || indices[i] >= Length) {
+        for (int k=0; k<DomainDimension; k++) {
+          value[k][i] = Double.NaN;
+        }
+      }
+      else {
+        value[0][i] = data[indices[i]];
+      }
+    }
+    return value;
+  }
+
+  /**
+   * Converts an array of values in R^1 to an array of 1-D indices.  This
+   * method is unimplemented because this class doesn't support interpolation.
+   * @param values		The values to have their indicies returned.
+   * @throws UnimplementedException
+   *				Always.
+   */
+  public int[] valueToIndex(float[][] values) throws VisADException {
+    throw new UnimplementedException("List1DDoubleSet.valueToIndex");
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float weights[][]) throws VisADException {
+    throw new UnimplementedException("List1DDoubleSet.valueToInterpx");
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof List1DDoubleSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    if (Length != ((Set) set).Length) return false;
+    for (int i=0; i<Length; i++) {
+      /*
+       * Use of Double.doubleToLongBits(...) in the following accomodates
+       * Double.NaN values and matches the use of Double.hashCode() in the
+       * hashCode() method.
+       */
+      if (Double.doubleToLongBits(data[i]) != 
+            Double.doubleToLongBits(((List1DDoubleSet) set).data[i]))
+          return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the hash code of this instance.
+   * @return	The hash code of this instance.
+   */
+  
+  public int hashCode() {
+    if (!hashCodeSet)
+    {
+      hashCode = unitAndCSHashCode() ^ Length;
+      for (int i=0; i<Length; i++)
+	hashCode ^= new Double(data[i]).hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new List1DDoubleSet(data, type, DomainCoordinateSystem, SetUnits);
+  }
+
+  /**
+   * Returns a pretty string about this instance.
+   * @return		A pretty string about this instance.
+   */
+  public String longString(String pre)
+  {
+    return pre + getClass().getName() + ": Dimension = 1" +
+	" Length = " + data.length + "\n";
+  }
+}
+
diff --git a/visad/List1DSet.java b/visad/List1DSet.java
new file mode 100644
index 0000000..1fe2014
--- /dev/null
+++ b/visad/List1DSet.java
@@ -0,0 +1,134 @@
+//
+// List1DSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * List1DSet is the class for Set-s containing lists of
+ * 1-D values with no topology.  This set does not support interpolation.<P>
+ */
+public class List1DSet extends SimpleSet {
+
+  float[] data;
+
+  /**
+   * Constructs with a non-default CoordinateSystem.
+   *
+   * @param d			The data samples.  Must not be 
+   *				<code>null</code>.
+   * @param type		The type of the set.
+   * @param coord_sys		The coordinate system transformation.  May be
+   *				<code>null</code>.
+   * @param units		The units of the values.  May be 
+   *				<code>null</code>.
+   * @throws VisADException	Couldn't create set.
+   */
+  public List1DSet(float[] d, MathType type, CoordinateSystem coord_sys,
+            Unit[] units) throws VisADException {
+    super(type, coord_sys, units, null);
+    if (DomainDimension != 1) {
+      throw new SetException("List1DSet: type must be 1-D");
+    }
+    data = d;
+    Length = d.length;
+  }
+
+  public boolean isMissing() {
+    return (data == null);
+  }
+
+  /** convert an array of 1-D indices to an array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int length = index.length;
+    float[][] value = new float[1][length];
+    for (int i=0; i<length; i++) {
+      if (index[i] < 0 || index[i] >= Length) {
+        for (int k=0; k<DomainDimension; k++) {
+          value[k][i] = Float.NaN;
+        }
+      }
+      else {
+        value[0][i] = data[index[i]];
+      }
+    }
+    return value;
+  }
+
+  /** convert an array of values in R^DomainDimension to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    throw new UnimplementedException("List1DSet.valueToIndex");
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float weights[][]) throws VisADException {
+    throw new UnimplementedException("List1DSet.valueToInterpx");
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof List1DSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    if (Length != ((Set) set).Length) return false;
+    for (int i=0; i<Length; i++) {
+      /*
+       * The use of Float.floatToIntBits(...) in the following accomodates 
+       * Float.NaN values and matches the equals(...) method.
+       */
+      if (Float.floatToIntBits(data[i]) !=
+          Float.floatToIntBits(((List1DSet) set).data[i]))
+        return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the hash code of this instance.
+   * @return		The hash code of this instance.
+   */
+  public int hashCode() {
+    if (!hashCodeSet)
+    {
+      hashCode = unitAndCSHashCode() ^ Length;
+      for (int i=0; i<Length; i++)
+	hashCode ^= Float.floatToIntBits(data[i]);
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new List1DSet(data, type, DomainCoordinateSystem, SetUnits);
+  }
+
+  public String longString(String pre) throws VisADException {
+    return pre + "List1DSet";
+  }
+}
+
diff --git a/visad/LocalDisplay.java b/visad/LocalDisplay.java
new file mode 100644
index 0000000..e6b837f
--- /dev/null
+++ b/visad/LocalDisplay.java
@@ -0,0 +1,148 @@
+//
+// LocalDisplay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Component;
+import java.awt.Container;
+
+import java.awt.image.BufferedImage;
+
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+/**
+   LocalDisplay is the VisAD interface for local instances of displays.
+   It declares the methods which are used by applications.
+*/
+public interface LocalDisplay
+  extends Display
+{
+  /** add a display activity handler */
+  void addActivityHandler(ActivityHandler ah)
+    throws VisADException;
+
+  /** remove a display activity handler */
+  void removeActivityHandler(ActivityHandler ah)
+    throws VisADException;
+
+  /** add a DisplayListener */
+  void addDisplayListener(DisplayListener listener);
+
+  /** add a MessageListener */
+  void addMessageListener(MessageListener listener);
+
+  /** link refs to this Display using the non-default renderer;
+      must be local DataRendererImpls;
+      this method may only be invoked after all links to ScalarMaps
+      have been made;
+      the maps[i] array applies only to rendering refs[i];
+  */
+  void replaceReferences(RemoteDisplay rDpy, DataRenderer renderer,
+                         DataReference[] refs, ConstantMap[][] constant_maps)
+         throws VisADException, RemoteException;
+
+  /** link refs to this Display using the non-default renderer;
+      must be local DataRendererImpls;
+      this method may only be invoked after all links to ScalarMaps
+      have been made;
+      the maps[i] array applies only to rendering refs[i];
+  */
+  void addReferences(DataRenderer renderer,
+                             DataReference[] refs,
+                             ConstantMap[][] constant_maps)
+         throws VisADException, RemoteException;
+
+  /** return the java.awt.Component (e.g., JPanel or AppletPanel)
+      this Display uses; returns null for an offscreen Display */
+  Component getComponent();
+
+  /** return the DisplayRenderer associated with this Display */
+  DisplayRenderer getDisplayRenderer();
+
+  /**
+   * Returns the list of DataRenderer-s.
+   * @return			The list of DataRenderer-s.
+   */
+  Vector getRenderers();
+
+  /**
+   * Returns a clone of the list of DataRenderer-s.  A clone is returned
+   * to avoid concurrent access problems by the Display thread.
+   * @return			A clone of the list of DataRenderer-s.
+   */
+  Vector getRendererVector();
+  
+  /** only called for Control objects associated with 'single'
+      DisplayRealType-s */
+  Control getControl(Class c);
+
+  /** find specified occurance for Control object of the specified class */
+  Control getControl(Class c, int inst);
+
+  /** find all Control objects of the specified class */
+  Vector getControls(Class c);
+
+  /** return the GraphicsModeControl associated with this Display */
+  GraphicsModeControl getGraphicsModeControl();
+
+  /** return a captured image of the display */
+  BufferedImage getImage();
+
+  /** return a Vector of the ScalarMap-s associated with this Display */
+  Vector getMapVector()
+         throws VisADException, RemoteException;
+
+  /** return the ProjectionControl associated with this Display */
+  ProjectionControl getProjectionControl();
+
+  /** get a GUI component containing this Display's Control widgets,
+      creating the widgets as necessary */
+  Container getWidgetPanel();
+
+  double[] make_matrix(double rotx, double roty, double rotz,
+                       double scale,
+		       double transx, double transy, double transz);
+
+  double[] multiply_matrix(double[] a, double[] b);
+
+  /**
+   * Removes a DisplayListener.
+   * @param listener		The listener to be removed.  Nothing happens
+   *				if the listener isn't registered with this
+   *				instance.
+   */
+  void removeDisplayListener(DisplayListener listener);
+
+  /**
+   * Removes a MessageListener.
+   *
+   * @param listener The listener to be removed.  Nothing happens if
+   *		     the listener isn't registered with this instance.
+   */
+  void removeMessageListener(MessageListener listener);
+}
diff --git a/visad/LogCoordinateSystem.java b/visad/LogCoordinateSystem.java
new file mode 100644
index 0000000..1649360
--- /dev/null
+++ b/visad/LogCoordinateSystem.java
@@ -0,0 +1,148 @@
+//
+// LogCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * A CoordinateSystem to transform between values and their logarithms.
+ * The logarithm is the reference.
+ */
+public class LogCoordinateSystem extends CoordinateSystem {
+  
+  private double base = 10.0;
+
+  /** 
+   * Construct a coordinate system with logarithmical reference 
+   * of base 10.  
+   * @param  reference  MathType of values
+   * @throws VisADException  some VisAD error
+   */
+  public LogCoordinateSystem(RealTupleType reference) 
+    throws VisADException 
+  {
+    this(reference, 10.0);
+  }
+
+  /** 
+   * Construct a coordinate system with logarithmical reference specified
+   * @param  reference  MathType of values
+   * @param  base  logrithmic base
+   * @throws VisADException  negative or zero base specified
+   */
+  public LogCoordinateSystem(RealTupleType reference, double base) 
+    throws VisADException 
+  {
+    super(reference, reference.getDefaultUnits());
+    if (base <= 0)
+      throw new VisADException(
+        "LogCoordinateSystem: log base (" + base + ") must be positive");
+    this.base = base;
+  }
+  
+
+  /** 
+   * Convert values to logarithmic values. 
+   * @param  values  array of values
+   * @return array of logarithms of values
+   * @throws VisADException values dimension not the same as CS dimension
+   */
+  public double[][] toReference(double[][] values) 
+    throws VisADException 
+  {
+    if (values == null || values[0].length < 1) return values;
+    if (values.length != getDimension())
+    {
+      throw new CoordinateSystemException(
+         "LogCoordinateSystem." + 
+         "toReference: values wrong dimension");
+    }
+    
+    int len = values[0].length;
+    double[][] logValues = new double[getDimension()][len];
+
+    for(int i = 0; i < getDimension(); i++)
+    {
+      for (int j = 0; j < len; j++)
+      {
+        logValues[i][j] = Math.log(values[i][j])/Math.log(base);
+      }
+    }
+    return logValues;
+  }
+
+  
+  /** 
+   * Convert logrithmic values to values. 
+   * @param  logValues  array of logrithmic values
+   * @return array of values
+   * @throws VisADException logValues dimension not the same as CS dimension
+   */
+  public double[][] fromReference(double[][] logValues) 
+    throws VisADException 
+  {
+    if (logValues == null || logValues[0].length < 1) return logValues;
+    if (logValues.length != getDimension())
+    {
+      throw new CoordinateSystemException(
+         "LogCoordinateSystem." + 
+         "fromReference: logValues wrong dimension");
+    }
+  
+    int len = logValues[0].length;
+    double[][] values = new double[getDimension()][len];
+  
+    for(int i = 0; i < getDimension(); i++)
+    {
+      for (int j = 0; j < len; j++)
+      {
+        values[i][j] = Math.pow(base, logValues[i][j]);
+      }
+    }
+    return values;
+   }
+  
+  /**
+   * Get the base used in this LogCoordinateSystem.
+   */
+  public double getBase() {
+    return base;
+  }
+
+  /**
+   * See if the Object in question is equal to this LogCoordinateSystem
+   * @param cs  Object in question
+   * @return  true if cs's reference tuples and base is equal to this's
+   */
+  public boolean equals(Object cs) 
+  {
+    if (!(cs instanceof LogCoordinateSystem)) return false;
+    LogCoordinateSystem that = (LogCoordinateSystem) cs;
+    return this == that ||
+           (that.getReference().equals(this.getReference()) &&
+           (Double.doubleToLongBits(that.base) == 
+           Double.doubleToLongBits(this.base)));
+  }
+}
diff --git a/visad/LogarithmicUnit.java b/visad/LogarithmicUnit.java
new file mode 100644
index 0000000..ee0edc5
--- /dev/null
+++ b/visad/LogarithmicUnit.java
@@ -0,0 +1,651 @@
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.io.Serializable;
+
+/**
+ * A class that represents a scaled unit with an offset.
+ * 
+ * Instances are immutable.
+ * 
+ * @author Steven R. Emmerson
+ * 
+ *         This is part of Steve Emerson's Unit package that has been
+ *         incorporated into VisAD.
+ * 
+ *         Instances are immutable.
+ */
+public final class LogarithmicUnit extends Unit implements Serializable {
+    private static final long       serialVersionUID    = 1L;
+    private static final Unit       ONE                 = new DerivedUnit();
+    /**
+     * The reference level.
+     * 
+     * @serial
+     */
+    private final Unit              reference;
+    /**
+     * The logarithmic base.
+     * 
+     * @serial
+     */
+    private final double            base;
+    /**
+     * The natural logarithm of the base (for computational efficiency).
+     */
+    private final transient double  lnBase;
+
+    /**
+     * Constructs from a reference level and a logarithmic base. The identifier
+     * will be empty.
+     * 
+     * @param base
+     *            The logarithmic base. Must be 2, {@link Math#E}, or 10.
+     * @param reference
+     *            The reference level.
+     * @throws IllegalArgumentException
+     *             if {@code base} isn't one of the allowed values.
+     * @throws NullPointerException
+     *             if {@code reference} is {@code null}.
+     */
+    private LogarithmicUnit(final double base, final Unit reference) {
+        this(reference, base, "");
+    }
+
+    /**
+     * Constructs from a reference level, a logarithmic base, and an identifier.
+     * 
+     * @param reference
+     *            The reference level.
+     * @param base
+     *            The logarithmic base. Must be 2, {@link Math#E}, or 10.
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * @throws IllegalArgumentException
+     *             if {@code base} isn't one of the allowed values.
+     * @throws NullPointerException
+     *             if {@code reference} is {@code null}.
+     */
+    private LogarithmicUnit(final Unit reference, final double base,
+            final String identifier) {
+        super(identifier);
+        if (reference == null) {
+            throw new NullPointerException("Null reference level");
+        }
+        if (base != 2 && base != Math.E && base != 10) {
+            throw new IllegalArgumentException("Invalid logarithmic base: "
+                    + base);
+        }
+        this.reference = reference;
+        this.base = base;
+        lnBase = base == Math.E
+                ? 1
+                : Math.log(base);
+    }
+
+    static Unit getInstance(final double base, final Unit reference) {
+        return new LogarithmicUnit(base, reference);
+    }
+
+    /**
+     * Indicates if this instance is dimensionless. Logarithmic units are
+     * dimensionless by definition.
+     * 
+     * @return {@code true} always.
+     */
+    @Override
+    public boolean isDimensionless() {
+        return true;
+    }
+
+    /**
+     * Indicates if this instance is a unit of time.
+     * 
+     * @return {@code true} if and only if values in this unit are convertible
+     *         with seconds.
+     */
+    protected boolean isTime() {
+        return SI.second.isConvertible(reference);
+    }
+
+    /**
+     * Clones this unit, changing the identifier.
+     * 
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * @return A unit equal to this unit but with the given identifier.
+     */
+    @Override
+    protected Unit protectedClone(final String identifier) {
+        return new LogarithmicUnit(reference, base, identifier);
+    }
+
+    @Override
+    public Unit scale(final double scale) throws UnitException {
+        return ScaledUnit.getInstance(scale, this);
+    }
+
+    @Override
+    public Unit shift(final double offset) throws UnitException {
+        return OffsetUnit.getInstance(offset, this);
+    }
+
+    @Override
+    public Unit log(final double base) throws UnitException {
+        throw new UnitException(
+                "Can't form logarithmic unit from logarithmic unit: " + this);
+    }
+
+    /**
+     * Raises this unit to a power. Only certain powers are meaningful.
+     * 
+     * @param power
+     *            The power to raise this unit by. The only meaningful values
+     *            are {@code 0} and {@code 1}.
+     * @return The result of raising this unit to the given power.
+     * @throws IllegalArgumentException
+     *             if {@code power} isn't {@code 0} or {@code 1}.
+     */
+    @Override
+    public Unit pow(final int power) {
+        Unit result;
+        if (power == 0) {
+            result = ONE;
+        }
+        else if (power == 1) {
+            result = this;
+        }
+        else {
+            throw new IllegalArgumentException("Invalid power: " + power);
+        }
+        return result;
+    }
+
+    /**
+     * Raises this unit to a power. Only certain powers are meaningful.
+     * 
+     * @param power
+     *            The power to raise this unit by. The only meaningful values
+     *            are 0 and 1.
+     * @return The result of raising this unit to the given power.
+     */
+    @Override
+    public Unit pow(final double power) {
+        if (power != 0 || power != 1) {
+            throw new IllegalArgumentException("Invalid power: " + power);
+        }
+        return pow((int) Math.round(power));
+    }
+
+    /**
+     * Returns the N-th root of this unit. Only the 1st root is meaningful.
+     * 
+     * @param root
+     *            The root to take. Must be {@code 1}.
+     * @return This instance.
+     * @throws IllegalArgumentException
+     *             if {@code root} isn't {@code 1}.
+     */
+    @Override
+    public Unit root(final int root) throws IllegalArgumentException {
+        if (root != 1) {
+            throw new IllegalArgumentException("Invalid root: " + root);
+        }
+        return this;
+    }
+
+    /**
+     * Return the definition of this unit as a string.
+     * 
+     * @return The definition of this unit (e.g., {@code "lg(re 0.001 W)"}) for
+     *         a Bel unit with a milliwatt reference level.
+     */
+    @Override
+    public String getDefinition() {
+        final StringBuilder buf = new StringBuilder(40);
+        if (base == 2) {
+            buf.append("lb");
+        }
+        else if (base == Math.E) {
+            buf.append("ln");
+        }
+        else if (base == 10) {
+            buf.append("lg");
+        }
+        else {
+            throw new AssertionError("Invalid base: " + base);
+        }
+        buf.append("(re ");
+        buf.append(reference.toString());
+        buf.append(")");
+        return buf.toString();
+    }
+
+    /**
+     * Multiply this unit by another unit. Only certain other units are
+     * meaningful.
+     * 
+     * @param that
+     *            The unit with which to multiply this unit. Must be
+     *            dimensionless.
+     * @return A unit equal to this instance multiplied by the given unit.
+     * @throws IllegalArgumentException
+     *             if {@code that} isn't dimensionless.
+     * @throws UnitException
+     *             Can't multiply units.
+     */
+    @Override
+    public Unit multiply(final Unit that) throws UnitException {
+        if (!that.isDimensionless()) {
+            throw new IllegalArgumentException("Not dimensionless: " + that);
+        }
+        final Unit result;
+        if (that.equals(ONE)) {
+            result = this;
+        }
+        else if (that instanceof LogarithmicUnit) {
+            throw new UnitException("Can't multiply by: " + that);
+        }
+        else {
+            result = that.multiply(this);
+        }
+        return result;
+    }
+
+    /**
+     * Divide this unit by another unit. Only certain other units are
+     * meaningful.
+     * 
+     * @param that
+     *            The unit to divide into this unit. Must be dimensionless.
+     * @return A unit equal to this instance divided by the given unit.
+     * @exception UnitException
+     *                Can't divide units.
+     */
+    @Override
+    public Unit divide(final Unit that) throws UnitException {
+        if (!that.isDimensionless()) {
+            throw new IllegalArgumentException("Not dimensionless: " + that);
+        }
+        final Unit result;
+        if (that.equals(ONE)) {
+            result = this;
+        }
+        else if (that instanceof LogarithmicUnit) {
+            throw new UnitException("Can't divide by: " + that);
+        }
+        else {
+            result = that.divideInto(this);
+        }
+        return result;
+    }
+
+    /**
+     * Divide this unit into another unit. This operation isn't meaningful.
+     * 
+     * @param that
+     *            The unit to be divide by this unit.
+     * @return Never
+     * @throws UnitException
+     *             if this method is called.
+     */
+    @Override
+    protected Unit divideInto(final Unit that) throws UnitException {
+        throw new UnitException("Can't divide into: " + that);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        double[] newValues;
+        if (equals(that) || that instanceof PromiscuousUnit) {
+            newValues = (copy)
+                    ? (double[]) values.clone()
+                    : values;
+        }
+        else {
+            newValues = that.toThat(values, reference, copy);
+            for (int i = 0; i < newValues.length; ++i) {
+                newValues[i] = Math.log(newValues[i]) / lnBase;
+            }
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false, convert values in place.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        float[] newValues;
+        if (equals(that) || that instanceof PromiscuousUnit) {
+            newValues = (copy)
+                    ? (float[]) values.clone()
+                    : values;
+        }
+        else {
+            newValues = that.toThat(values, reference, copy);
+            for (int i = 0; i < newValues.length; ++i) {
+                newValues[i] = (float) (Math.log(newValues[i]) / lnBase);
+            }
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double values[], final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float values[], final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return a
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double values[], final Unit that,
+            final boolean copy) throws UnitException {
+        double[] newValues = (copy)
+                ? (double[]) values.clone()
+                : values;
+        if (!(equals(that) || that instanceof PromiscuousUnit)) {
+            for (int i = 0; i < newValues.length; ++i) {
+                newValues[i] = Math.exp(newValues[i] * lnBase);
+            }
+            newValues = that.toThis(newValues, reference, copy);
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return a
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float values[], final Unit that,
+            final boolean copy) throws UnitException {
+        float[] newValues = (copy)
+                ? (float[]) values.clone()
+                : values;
+        if (!(equals(that) || that instanceof PromiscuousUnit)) {
+            for (int i = 0; i < newValues.length; ++i) {
+                newValues[i] = (float) Math.exp(newValues[i] * lnBase);
+            }
+            newValues = that.toThis(newValues, reference, copy);
+        }
+        return newValues;
+    }
+
+    /**
+     * Gets the absolute unit of this unit. An interval in the underlying
+     * physical quantity has the same numeric value in an absolute unit of a
+     * unit as in the unit itself -- but an absolute unit is always referenced
+     * to the physical origin of the underlying physical quantity. For example,
+     * the absolute unit corresponding to degrees celsius is degrees kelvin --
+     * and calling this method on a degrees celsius unit obtains a degrees
+     * kelvin unit.
+     * 
+     * @return The absolute unit corresponding to this unit.
+     */
+    @Override
+    public Unit getAbsoluteUnit() {
+        return reference.getAbsoluteUnit();
+    }
+
+    /**
+     * Indicate whether this unit is convertible with another unit. If one unit
+     * is convertible with another, then the <code>toThis(...)</code>/ and
+     * <code>toThat(...)</code> methods will not throw a UnitException. Unit A
+     * is convertible with unit B if and only if unit B is convertible with unit
+     * A; hence, calling-order is irrelevant.
+     * 
+     * @param unit
+     *            The other unit.
+     * @return True if and only if this unit is convertible with the other unit.
+     */
+    @Override
+    public boolean isConvertible(final Unit unit) {
+        return reference.isConvertible(unit);
+    }
+
+    private static void myAssert(final boolean bool) {
+        if (!bool) {
+            throw new AssertionError();
+        }
+    }
+
+    /**
+     * Test this class.
+     * 
+     * @param args
+     *            Arguments (ignored).
+     * @exception UnitException
+     *                A problem occurred.
+     */
+    public static void main(final String[] args) throws UnitException {
+        final BaseUnit meter = SI.meter;
+        final ScaledUnit micron = new ScaledUnit(1e-6, meter);
+        final Unit cubicMicron = micron.pow(3);
+        final LogarithmicUnit Bz = new LogarithmicUnit(10, cubicMicron);
+        myAssert(Bz.isDimensionless());
+        myAssert(Bz.equals(Bz));
+        myAssert(Bz.getAbsoluteUnit().equals(cubicMicron));
+        myAssert(!Bz.equals(cubicMicron));
+        myAssert(!Bz.equals(micron));
+        myAssert(!Bz.equals(meter));
+        try {
+            Bz.multiply(meter);
+            myAssert(false);
+        }
+        catch (final Exception e) {
+        }
+        try {
+            Bz.divide(meter);
+            myAssert(false);
+        }
+        catch (final Exception e) {
+        }
+        try {
+            Bz.pow(2);
+            myAssert(false);
+        }
+        catch (final Exception e) {
+        }
+        double value = Bz.toThat(0, Bz.getAbsoluteUnit());
+        myAssert(0.9 < value && value < 1.1);
+        value = Bz.toThat(1, Bz.getAbsoluteUnit());
+        myAssert(9 < value && value < 11);
+        value = Bz.toThis(1, Bz.getAbsoluteUnit());
+        myAssert(-0.1 < value && value < 0.1);
+        value = Bz.toThis(10, Bz.getAbsoluteUnit());
+        myAssert(0.9 < value && value < 1.1);
+        final String string = Bz.toString();
+        myAssert(string.equals("lg(re 9.999999999999999E-19 m3)"));
+        System.out.println("Done");
+    }
+
+    /**
+     * Indicates if this instance equals a unit.
+     * 
+     * @param unit
+     *            The unit.
+     * @return <code>true</code> if and only if this instance equals the unit.
+     */
+    @Override
+    public boolean equals(final Unit unit) {
+        if (this == unit) {
+            return true;
+        }
+        if (!(unit instanceof LogarithmicUnit)) {
+            return false;
+        }
+        final LogarithmicUnit that = (LogarithmicUnit) unit;
+        return base == that.base && reference.equals(that.reference);
+    }
+
+    /**
+     * Returns the hash code of this instance. {@link Object#hashCode()} should
+     * be overridden whenever {@link Object#equals(Object)} is.
+     * 
+     * @return The hash code of this instance.
+     */
+    @Override
+    public int hashCode() {
+        if (hashCode == 0) {
+            hashCode = reference.hashCode() ^ Double.valueOf(base).hashCode();
+        }
+        return hashCode;
+    }
+
+    @Override
+    public DerivedUnit getDerivedUnit() {
+        return reference.getDerivedUnit();
+    }
+}
diff --git a/visad/Makefile.WinNT b/visad/Makefile.WinNT
new file mode 100644
index 0000000..9a99d61
--- /dev/null
+++ b/visad/Makefile.WinNT
@@ -0,0 +1,1065 @@
+# Makefile.WinNT for VisAD version 2.0
+# For use with Windows 9X/NT/XP (e.g., Microsoft's NMAKE, available at:
+#      ftp://ftp.microsoft.com/Softlib/MSLFILES/nmake15.exe)
+# To compile VisAD using this Makefile, type:
+#      nmake -f Makefile.WinNT compile
+# To compile VisAD in a Unix-based system, use Makefile instead.
+
+# VisAD system for interactive analysis and visualization of numerical
+# data.  Copyright (C) 1996 - 2003 Bill Hibbard, Curtis Rueden, Tom
+# Rink and Dave Glowacki.
+#
+# 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., 59 Temple Place - Suite 330, Boston,
+# MA 02111-1307, USA
+
+default:
+	@echo Type one of the following:
+	@echo nmake -f Makefile.WinNT recompile (recompile modified source files)
+	@echo nmake -f Makefile.WinNT compile (compile source files from scratch)
+	@echo nmake -f Makefile.WinNT debug (compile source files for debugging)
+	@echo nmake -f Makefile.WinNT jar (make visad_src-2.0.jar)
+	@echo nmake -f Makefile.WinNT save (save files to a backup directory)
+	@echo nmake -f Makefile.WinNT classes (make jar files containing classes)
+	@echo nmake -f Makefile.WinNT clear (clear source files)
+	@echo nmake -f Makefile.WinNT clean (clean class files)
+	@echo nmake -f Makefile.WinNT docs (create javadoc documentation)
+
+.SUFFIXES : .java .class
+
+JC = javac
+JFLAGS = -J-mx64m -target 1.4 -source 1.4
+
+# to always use IBM's 'jikes' compiler, uncomment the following two lines
+#JC = jikes
+#JFLAGS =
+
+JAR = jar
+JARVERBOSE = v
+
+RMIC = rmic
+RMIC_DIR = ..
+RMIC_COLLAB_DIR = .
+RMIC_CLUSTER_DIR = .
+# if you are using JDK 1.2, uncomment the following three lines
+#RMIC_DIR = .
+#RMIC_COLLAB_DIR = collab
+#RMIC_CLUSTER_DIR = ..\cluster
+
+.java.class:
+	$(JC) $(JFLAGS) $<
+
+DEL = del
+
+DOC = javadoc
+DOC_DIR = ..\docs
+DOC_MEM = 96m
+DOC_LINKS = -link http://java.sun.com/j2se/1.4/docs/api
+DOCFLAGS = $(DOC_LINKS) -d $(DOC_DIR) -J-Xmx$(DOC_MEM) -package \
+	-doctitle 'VisAD Documentation' $(DOC_EXTRAFLAGS)
+
+JAR_FILES1 = \
+dods\dap\*.java \
+dods\dap\parser\*.java \
+dods\dap\Server\*.java \
+dods\util\*.java \
+edu\wisc\ssec\mcidas\*.java \
+edu\wisc\ssec\mcidas\adde\*.java \
+gnu\regexp\*.java \
+HTTPClient\0* \
+HTTPClient\*.html \
+HTTPClient\*.java \
+ncsa\hdf\hdf5lib\*.java \
+ncsa\hdf\hdf5lib\exceptions\*.java \
+loci\formats\*.java \
+loci\formats\codec\*.java \
+loci\formats\gui\*.java \
+loci\formats\in\*.java \
+loci\formats\out\*.java \
+nom\tam\fits\*.java \
+nom\tam\util\*.java \
+nom\tam\test\*.java
+
+JAR_FILES2 = \
+ucar\COPYRIGHT \
+ucar\VERSION \
+ucar\multiarray\*.java \
+ucar\netcdf\*.java \
+ucar\tests\*.java \
+ucar\tests\test.nc \
+ucar\tests\test.out \
+visad\README \
+visad\DEDICATION \
+visad\NOTICE \
+visad\LICENSE \
+visad\DATE \
+visad\Makefile \
+visad\Makefile.WinNT \
+visad\README.nexusrmi \
+visad\rmic_script \
+visad\VisAD.Manifest \
+visad\Gridded1D.txt \
+visad\Gridded2D.txt \
+visad\Gridded3D.txt \
+visad\*.java
+
+JAR_FILES3 = \
+visad\aeri\*.java \
+visad\aune\README.aune \
+visad\aune\Makefile \
+visad\aune\shsize.fcm \
+visad\aune\*.java \
+visad\aune\*.f \
+visad\aune\*.c \
+visad\benjamin\Makefile \
+visad\benjamin\*.java \
+visad\benjamin\*.f \
+visad\benjamin\*.c \
+visad\benjamin\*.h \
+visad\benjamin\switch.inp \
+visad\benjamin\*.table \
+visad\bom\*.java \
+visad\browser\*.java \
+visad\browser\*.html \
+visad\browser\README.browser \
+visad\cluster\*.java \
+visad\collab\*.java
+
+JAR_FILES4 = \
+visad\data\*.java \
+visad\data\in\*.java \
+visad\data\in\package.html \
+visad\data\units\*.java \
+visad\data\units\*.jj \
+visad\data\amanda\*.java \
+visad\data\avi\*.java \
+visad\data\bio\*.java \
+visad\data\biorad\*.java \
+visad\data\dods\*.java \
+visad\data\dods\package.html \
+visad\data\fits\*.java \
+visad\data\gif\*.java \
+visad\data\gif\sseclogo.gif \
+visad\data\gis\*.java \
+visad\data\hdf5\*.java \
+visad\data\hdf5\COPYING \
+visad\data\hdf5\hdf5objects\*.java \
+visad\data\hdfeos\*.java \
+visad\data\hdfeos\README.hdfeos \
+visad\data\hdfeos\hdfeosc\*.java \
+visad\data\hdfeos\hdfeosc\*.c \
+visad\data\hdfeos\hdfeosc\Makefile
+
+JAR_FILES5 = \
+visad\data\jai\*.java \
+visad\data\mcidas\*.java \
+visad\data\mcidas\README.mcidas \
+visad\data\netcdf\*.java \
+visad\data\netcdf\units\*.java \
+visad\data\netcdf\in\*.java \
+visad\data\netcdf\out\*.java \
+visad\data\qt\*.java \
+visad\data\text\*.java \
+visad\data\text\README.text \
+visad\data\text\example1.txt \
+visad\data\text\example3.txt \
+visad\data\text\example2.csv \
+visad\data\tiff\*.java \
+visad\data\vis5d\*.java \
+visad\data\visad\*.java \
+visad\data\visad\object\*.java \
+visad\examples\*.java \
+visad\examples\display_test.c \
+visad\formula\*.java \
+visad\georef\*.java
+
+JAR_FILES6 = \
+visad\install\*.java \
+visad\install\install_visad \
+visad\install\README \
+visad\install\README.html \
+visad\java2d\*.java \
+visad\java3d\Makefile \
+visad\java3d\*.java \
+visad\java3d\*.c \
+visad\jmet\*.java \
+visad\matrix\*.java \
+visad\math\*.java \
+visad\meteorology\*.java \
+visad\paoloa\README.paoloa \
+visad\paoloa\Makefile \
+visad\paoloa\*.java \
+visad\paoloa\*.f \
+visad\paoloa\*.c \
+visad\paoloa\spline\Makefile \
+visad\paoloa\spline\*.java \
+visad\paoloa\spline\*.f \
+visad\paoloa\spline\*.c
+
+JAR_FILES7 = \
+visad\python\*.java \
+visad\python\*.py \
+visad\python\README.python \
+visad\rabin\*.java \
+visad\ss\*.java \
+visad\ss\*.gif \
+visad\ss\README.ss \
+visad\ss\*.java \
+visad\util\*.java \
+visad\util\*.jhf
+
+jar:
+	date /t > DATE
+	time /t >> DATE
+	cd ..
+	$(JAR) $(JARVERBOSE)cf visad_src-2.0.jar $(JAR_FILES1)
+	$(JAR) $(JARVERBOSE)uf visad_src-2.0.jar $(JAR_FILES2)
+	$(JAR) $(JARVERBOSE)uf visad_src-2.0.jar $(JAR_FILES3)
+	$(JAR) $(JARVERBOSE)uf visad_src-2.0.jar $(JAR_FILES4)
+	$(JAR) $(JARVERBOSE)uf visad_src-2.0.jar $(JAR_FILES5)
+	$(JAR) $(JARVERBOSE)uf visad_src-2.0.jar $(JAR_FILES6)
+	$(JAR) $(JARVERBOSE)uf visad_src-2.0.jar $(JAR_FILES7)
+	move visad_src-2.0.jar visad
+	cd visad
+	copy visad_src-2.0.jar visadsrc.jar
+	dir /-w visad_src-2.0.jar visadsrc.jar
+
+BACKUP = backup
+
+SAVE_FILES1 = \
+visad\README \
+visad\DEDICATION \
+visad\NOTICE \
+visad\LICENSE \
+visad\DATE \
+visad\Makefile \
+visad\Makefile.WinNT \
+visad\README.nexusrmi \
+visad\rmic_script \
+visad\VisAD.Manifest \
+visad\*.java \
+visad\examples\*.java \
+visad\examples\display_test.c \
+visad\java3d\*.java \
+visad\java2d\*.java \
+visad\python\*.java \
+visad\python\*.py \
+visad\python\README.python \
+visad\browser\*.java \
+visad\browser\*.html \
+visad\browser\README.browser \
+visad\util\*.java \
+visad\util\*.jhf \
+visad\matrix\*.java \
+visad\math\*.java \
+visad\formula\*.java \
+visad\ss\*.java \
+visad\ss\*.gif \
+visad\ss\README.ss \
+visad\cluster\*.java \
+visad\collab\*.java \
+visad\data\*.java \
+visad\data\units\*.java \
+visad\data\units\*.jj \
+visad\data\in\*.java \
+visad\data\in\package.html \
+visad\data\dods\*.java \
+visad\data\dods\package.html \
+visad\data\netcdf\*.java \
+visad\data\netcdf\units\*.java \
+visad\data\netcdf\in\*.java \
+visad\data\netcdf\out\*.java \
+visad\data\fits\*.java \
+loci\formats\*.java \
+loci\formats\codec\*.java \
+loci\formats\gui\*.java \
+loci\formats\in\*.java \
+loci\formats\out\*.java \
+nom\tam\fits\*.java \
+nom\tam\util\*.java \
+nom\tam\test\*.java \
+HTTPClient\0* \
+HTTPClient\*.html \
+HTTPClient\*.java \
+ucar\COPYRIGHT \
+ucar\VERSION \
+ucar\multiarray\*.java \
+ucar\netcdf\*.java \
+ucar\tests\*.java \
+ucar\tests\test.nc \
+ucar\tests\test.out \
+dods\dap\*.java \
+dods\dap\parser\*.java \
+dods\dap\Server\*.java \
+dods\util\*.java \
+gnu\regexp\*.java \
+visad\data\hdfeos\*.java \
+visad\data\hdfeos\hdfeosc\*.java \
+visad\data\hdfeos\hdfeosc\*.c \
+visad\data\hdfeos\hdfeosc\Makefile \
+visad\data\vis5d\*.java \
+visad\data\gif\*.java \
+visad\data\gis\*.java \
+visad\data\tiff\*.java \
+edu\wisc\ssec\mcidas\*.java \
+edu\wisc\ssec\mcidas\adde\*.java \
+visad\data\mcidas\*.java \
+visad\data\mcidas\README.mcidas \
+visad\data\avi\*.java \
+visad\data\bio\*.java \
+visad\data\biorad\*.java \
+visad\data\jai\*.java \
+visad\data\qt\*.java \
+visad\data\visad\*.java \
+visad\data\visad\object\*.java \
+visad\data\text\*.java \
+visad\data\text\*.java \
+visad\data\text\README.text \
+visad\data\text\example1.txt \
+visad\data\text\example3.txt \
+visad\data\text\example2.csv \
+visad\data\hdf5\*.java \
+visad\data\hdf5\hdf5objects\*.java \
+ncsa\hdf\hdf5lib\*.java \
+ncsa\hdf\hdf5lib\exceptions\*.java
+
+SAVE_FILES2 = \
+visad\jmet\*.java \
+visad\paoloa\README.paoloa \
+visad\paoloa\Makefile \
+visad\paoloa\*.java \
+visad\paoloa\*.f \
+visad\paoloa\*.c \
+visad\paoloa\spline\Makefile \
+visad\paoloa\spline\*.java \
+visad\paoloa\spline\*.f \
+visad\paoloa\spline\*.c \
+visad\aune\README.aune \
+visad\aune\Makefile \
+visad\aune\shsize.fcm \
+visad\aune\*.java \
+visad\aune\*.f \
+visad\aune\*.c \
+visad\benjamin\Makefile \
+visad\benjamin\*.java \
+visad\benjamin\*.f \
+visad\benjamin\*.c \
+visad\benjamin\*.h \
+visad\benjamin\switch.inp \
+visad\benjamin\*.table \
+visad\rabin\*.java \
+visad\bom\*.java \
+visad\aeri\*.java \
+visad\data\amanda\*.java \
+visad\georef\*.java \
+visad\meteorology\*.java \
+visad\install\*.java \
+visad\install\install_visad \
+visad\install\README \
+visad\install\README.html \
+visad\Gridded1D.txt \
+visad\Gridded2D.txt \
+visad\Gridded3D.txt
+
+save:
+	cd ..
+	if not exist $(BACKUP) mkdir $(BACKUP)
+	if exist $(BACKUP)\visad\*.java \
+		$(DEL) $(BACKUP)\visad\*.java
+	if exist $(BACKUP)\visad\cluster\*.java \
+		$(DEL) $(BACKUP)\visad\cluster\*.java
+	if exist $(BACKUP)\visad\collab\*.java \
+		$(DEL) $(BACKUP)\visad\collab\*.java
+	if exist $(BACKUP)\visad\examples\*.java \
+		$(DEL) $(BACKUP)\visad\examples\*.java
+	if exist $(BACKUP)\visad\java3d\*.java \
+		$(DEL) $(BACKUP)\visad\java3d\*.java
+	if exist $(BACKUP)\visad\java2d\*.java \
+		$(DEL) $(BACKUP)\visad\java2d\*.java
+	if exist $(BACKUP)\visad\matrix\*.java \
+		$(DEL) $(BACKUP)\visad\matrix\*.java
+	if exist $(BACKUP)\visad\math\*.java \
+		$(DEL) $(BACKUP)\visad\math\*.java
+	if exist $(BACKUP)\visad\python\*.java \
+		$(DEL) $(BACKUP)\visad\python\*.java
+	if exist $(BACKUP)\visad\browser\*.java \
+		$(DEL) $(BACKUP)\visad\browser\*.java
+	if exist $(BACKUP)\visad\util\*.java \
+		$(DEL) $(BACKUP)\visad\util\*.java
+	if exist $(BACKUP)\visad\util\*.jhf \
+		$(DEL) $(BACKUP)\visad\util\*.jhf
+	if exist $(BACKUP)\visad\formula\*.java \
+		$(DEL) $(BACKUP)\visad\formula\*.java
+	if exist $(BACKUP)\visad\ss\*.java \
+		$(DEL) $(BACKUP)\visad\ss\*.java
+	if exist $(BACKUP)\visad\ss\*.gif \
+		$(DEL) $(BACKUP)\visad\ss\*.gif
+	if exist $(BACKUP)\visad\data\*.java \
+		$(DEL) $(BACKUP)\visad\data\*.java
+	if exist $(BACKUP)\visad\data\units\*.java \
+		$(DEL) $(BACKUP)\visad\data\units\*.java
+	if exist $(BACKUP)\visad\data\in\*.java \
+		$(DEL) $(BACKUP)\visad\data\in\*.java
+	if exist $(BACKUP)\visad\data\dods\*.java \
+		$(DEL) $(BACKUP)\visad\data\dods\*.java
+	if exist $(BACKUP)\visad\data\netcdf\*.java \
+		$(DEL) $(BACKUP)\visad\data\netcdf\*.java
+	if exist $(BACKUP)\visad\data\netcdf\units\*.java \
+		$(DEL) $(BACKUP)\visad\data\netcdf\units\*.java
+	if exist $(BACKUP)\visad\data\netcdf\in\*.java \
+		$(DEL) $(BACKUP)\visad\data\netcdf\in\*.java
+	if exist $(BACKUP)\visad\data\netcdf\out\*.java \
+		$(DEL) $(BACKUP)\visad\data\netcdf\out\*.java
+	if exist $(BACKUP)\visad\data\fits\*.java \
+		$(DEL) $(BACKUP)\visad\data\fits\*.java
+	if exist $(BACKUP)\loci\formats\*.java \
+		$(DEL) $(BACKUP)\loci\formats\*.java
+	if exist $(BACKUP)\loci\formats\codec\*.java \
+		$(DEL) $(BACKUP)\loci\formats\codec\*.java
+	if exist $(BACKUP)\loci\formats\gui\*.java \
+		$(DEL) $(BACKUP)\loci\formats\gui\*.java
+	if exist $(BACKUP)\loci\formats\in\*.java \
+		$(DEL) $(BACKUP)\loci\formats\in\*.java
+	if exist $(BACKUP)\loci\formats\out\*.java \
+		$(DEL) $(BACKUP)\loci\formats\out\*.java
+	if exist $(BACKUP)\nom\tam\fits\*.java \
+		$(DEL) $(BACKUP)\nom\tam\fits\*.java
+	if exist $(BACKUP)\nom\tam\util\*.java \
+		$(DEL) $(BACKUP)\nom\tam\util\*.java
+	if exist $(BACKUP)\nom\tam\test\*.java \
+		$(DEL) $(BACKUP)\nom\tam\test\*.java
+	if exist $(BACKUP)\HTTPClient\*.java \
+		$(DEL) $(BACKUP)\HTTPClient\*.java
+	if exist $(BACKUP)\ucar\multiarray\*.java \
+		$(DEL) $(BACKUP)\ucar\multiarray\*.java
+	if exist $(BACKUP)\ucar\netcdf\*.java \
+		$(DEL) $(BACKUP)\ucar\netcdf\*.java
+	if exist $(BACKUP)\ucar\tests\*.java \
+		$(DEL) $(BACKUP)\ucar\tests\*.java
+	if exist $(BACKUP)\dods\dap\*.java \
+		$(DEL) $(BACKUP)\dods\dap\*.java
+	if exist $(BACKUP)\dods\dap\parser\*.java \
+		$(DEL) $(BACKUP)\dods\dap\parser\*.java
+	if exist $(BACKUP)\dods\dap\Server\*.java \
+		$(DEL) $(BACKUP)\dods\dap\Server\*.java
+	if exist $(BACKUP)\dods\util\*.java \
+		$(DEL) $(BACKUP)\dods\util\*.java
+	if exist $(BACKUP)\gnu\regexp\*.java \
+		$(DEL) $(BACKUP)\gnu\regexp\*.java
+	if exist $(BACKUP)\visad\data\hdfeos\*.java \
+		$(DEL) $(BACKUP)\visad\data\hdfeos\*.java
+	if exist $(BACKUP)\visad\data\hdfeos\hdfeosc\*.java \
+		$(DEL) $(BACKUP)\visad\data\hdfeos\hdfeosc\*.java
+	if exist $(BACKUP)\visad\data\vis5d\*.java \
+		$(DEL) $(BACKUP)\visad\data\vis5d\*.java
+	if exist $(BACKUP)\edu\wisc\ssec\mcidas\*.java \
+		$(DEL) $(BACKUP)\edu\wisc\ssec\mcidas\*.java
+	if exist $(BACKUP)\edu\wisc\ssec\mcidas\adde\*.java \
+		$(DEL) $(BACKUP)\edu\wisc\ssec\mcidas\adde\*.java
+	if exist $(BACKUP)\visad\data\mcidas\*.java \
+		$(DEL) $(BACKUP)\visad\data\mcidas\*.java
+	if exist $(BACKUP)\visad\data\avi\*.java \
+		$(DEL) $(BACKUP)\visad\data\avi\*.java
+	if exist $(BACKUP)\visad\data\bio\*.java \
+		$(DEL) $(BACKUP)\visad\data\bio\*.java
+	if exist $(BACKUP)\visad\data\biorad\*.java \
+		$(DEL) $(BACKUP)\visad\data\biorad\*.java
+	if exist $(BACKUP)\visad\data\jai\*.java \
+		$(DEL) $(BACKUP)\visad\data\jai\*.java
+	if exist $(BACKUP)\visad\data\qt\*.java \
+		$(DEL) $(BACKUP)\visad\data\qt\*.java
+	if exist $(BACKUP)\visad\data\gif\*.java \
+		$(DEL) $(BACKUP)\visad\data\gif\*.java
+	if exist $(BACKUP)\visad\data\gis\*.java \
+		$(DEL) $(BACKUP)\visad\data\gis\*.java
+	if exist $(BACKUP)\visad\data\tiff\*.java \
+		$(DEL) $(BACKUP)\visad\data\tiff\*.java
+	if exist $(BACKUP)\visad\data\visad\*.java \
+		$(DEL) $(BACKUP)\visad\data\visad\*.java
+	if exist $(BACKUP)\visad\data\visad\object\*.java \
+		$(DEL) $(BACKUP)\visad\data\visad\object\*.java
+	if exist $(BACKUP)\visad\data\text\*.java \
+		$(DEL) $(BACKUP)\visad\data\text\*.java
+	if exist $(BACKUP)\visad\data\hdf5\*.java \
+		$(DEL) $(BACKUP)\visad\data\hdf5\*.java
+	if exist $(BACKUP)\visad\data\hdf5\hdf5objects\*.java \
+		$(DEL) $(BACKUP)\visad\data\hdf5\hdf5objects\*.java
+	if exist $(BACKUP)\ncsa\hdf\hdf5lib\*.java \
+		$(DEL) $(BACKUP)\ncsa\hdf\hdf5lib\*.java
+	if exist $(BACKUP)\ncsa\hdf\hdf5lib\exceptions\*.java \
+		$(DEL) $(BACKUP)\ncsa\hdf\hdf5lib\exceptions\*.java
+	if exist $(BACKUP)\visad\jmet\*.java \
+		$(DEL) $(BACKUP)\visad\jmet\*.java
+	if exist $(BACKUP)\visad\paoloa\*.java \
+		$(DEL) $(BACKUP)\visad\paoloa\*.java
+	if exist $(BACKUP)\visad\paoloa\spline\*.java \
+		$(DEL) $(BACKUP)\visad\paoloa\spline\*.java
+	if exist $(BACKUP)\visad\aune\*.java \
+		$(DEL) $(BACKUP)\visad\aune\*.java
+	if exist $(BACKUP)\visad\benjamin\*.java \
+		$(DEL) $(BACKUP)\visad\benjamin\*.java
+	if exist $(BACKUP)\visad\rabin\*.java \
+		$(DEL) $(BACKUP)\visad\rabin\*.java
+	if exist $(BACKUP)\visad\bom\*.java \
+		$(DEL) $(BACKUP)\visad\bom\*.java
+	if exist $(BACKUP)\visad\aeri\*.java \
+		$(DEL) $(BACKUP)\visad\aeri\*.java
+	if exist $(BACKUP)\visad\data\amanda\*.java \
+		$(DEL) $(BACKUP)\visad\data\amanda\*.java
+	if exist $(BACKUP)\visad\georef\*.java \
+		$(DEL) $(BACKUP)\visad\georef\*.java
+	if exist $(BACKUP)\visad\meteorology\*.java \
+		$(DEL) $(BACKUP)\visad\meteorology\*.java
+	if exist $(BACKUP)\visad\install\*.java \
+		$(DEL) $(BACKUP)\visad\install\*.java
+	if exist $(BACKUP)\visad\install\install_visad \
+		$(DEL) $(BACKUP)\visad\install\install_visad
+	if exist $(BACKUP)\visad\install\README \
+		$(DEL) $(BACKUP)\visad\install\README
+	if exist $(BACKUP)\visad\install\README.html \
+		$(DEL) $(BACKUP)\visad\install\README.html
+	$(JAR) $(JARVERBOSE)cf $(BACKUP)\save.jar $(SAVE_FILES1)
+	$(JAR) $(JARVERBOSE)uf $(BACKUP)\save.jar $(SAVE_FILES2)
+	cd $(BACKUP)
+	$(JAR) $(JARVERBOSE)xf save.jar
+	$(DEL) save.jar
+
+CLASS_FILES = \
+DATE \
+visad\*.class \
+visad\java3d\*.class \
+visad\java2d\*.class \
+visad\python\*.class \
+visad\browser\*.class \
+visad\util\*.class \
+visad\util\*.jhf \
+visad\matrix\*.class \
+visad\math\*.class \
+visad\formula\*.class \
+visad\ss\*.class \
+visad\ss\*.gif \
+visad\cluster\*.class \
+visad\collab\*.class \
+visad\data\*.class \
+visad\data\units\*.class \
+visad\data\in\*.class \
+visad\data\dods\*.class \
+visad\data\netcdf\*.class \
+visad\data\netcdf\units\*.class \
+visad\data\netcdf\in\*.class \
+visad\data\netcdf\out\*.class \
+HTTPClient\*.class \
+ucar\multiarray\*.class \
+ucar\netcdf\*.class \
+ucar\tests\*.class \
+dods\dap\*.class \
+dods\dap\parser\*.class \
+dods\dap\Server\*.class \
+dods\util\*.class \
+gnu\regexp\*.class \
+visad\data\fits\*.class \
+loci\formats\*.class \
+loci\formats\readers.txt \
+loci\formats\writers.txt \
+loci\formats\codec\*.class \
+loci\formats\gui\*.class \
+loci\formats\in\*.class \
+loci\formats\out\*.class \
+nom\tam\fits\*.class \
+nom\tam\util\*.class \
+nom\tam\test\*.class \
+visad\data\hdfeos\*.class \
+visad\data\hdfeos\hdfeosc\*.class \
+visad\data\vis5d\*.class \
+edu\wisc\ssec\mcidas\*.class \
+edu\wisc\ssec\mcidas\adde\*.class \
+visad\data\mcidas\*.class \
+visad\data\avi\*.class \
+visad\data\bio\*.class \
+visad\data\biorad\*.class \
+visad\data\jai\*.class \
+visad\data\qt\*.class \
+visad\data\gif\*.class \
+visad\data\gis\*.class \
+visad\data\tiff\*.class \
+visad\data\visad\*.class \
+visad\data\visad\object\*.class \
+visad\data\text\*.class \
+visad\data\hdf5\*.class \
+visad\data\hdf5\hdf5objects\*.class \
+ncsa\hdf\hdf5lib\*.class \
+ncsa\hdf\hdf5lib\exceptions\*.class \
+visad\jmet\*.class \
+visad\paoloa\*.class \
+visad\paoloa\spline\*.class \
+visad\aune\*.class \
+visad\benjamin\*.class \
+visad\rabin\*.class \
+visad\bom\*.class \
+visad\aeri\*.class \
+visad\data\amanda\*.class \
+visad\georef\*.class \
+visad\meteorology\*.class \
+visad\install\*.class
+
+classes:
+	cd ..
+	date /t > DATE
+	time /t >> DATE
+	$(JAR) $(JARVERBOSE)cfm visad.jar visad\VisAD.Manifest $(CLASS_FILES)
+	dir /-w visad.jar
+
+EXAMPLES_TAR_FILES = \
+visad\examples\*.java \
+visad\examples\display_test.c \
+visad\examples\*.html \
+visad\examples\*.class
+
+examplesjar:
+	cd ..
+	$(JAR) $(JARVERBOSE)cf visad_examples.jar $(EXAMPLES_TAR_FILES)
+	dir /-w visad_examples.jar
+
+clear:
+	if exist *.java $(DEL) *.java
+	if exist cluster\*.java $(DEL) cluster\*.java
+	if exist collab\*.java $(DEL) collab\*.java
+	if exist java3d\*.java $(DEL) java3d\*.java
+	if exist java2d\*.java $(DEL) java2d\*.java
+	if exist python\*.java $(DEL) python\*.java
+	if exist browser\*.java $(DEL) browser\*.java
+	if exist util\*.java $(DEL) util\*.java
+	if exist util\*.jhf $(DEL) util\*.jhf
+	if exist matrix\*.java $(DEL) matrix\*.java
+	if exist math\*.java $(DEL) math\*.java
+	if exist formula\*.java $(DEL) formula\*.java
+	if exist ss\*.java $(DEL) ss\*.java
+	if exist ss\*.gif $(DEL) ss\*.gif
+	if exist data\*.java $(DEL) data\*.java
+	if exist data\units\*.java $(DEL) data\units\*.java
+	if exist data\in\*.java $(DEL) data\in\*.java
+	if exist data\dods\*.java $(DEL) data\dods\*.java
+	if exist data\netcdf\*.java $(DEL) data\netcdf\*.java
+	if exist data\netcdf\units\*.java $(DEL) data\netcdf\units\*.java
+	if exist data\netcdf\in\*.java $(DEL) data\netcdf\in\*.java
+	if exist data\netcdf\out\*.java $(DEL) data\netcdf\out\*.java
+	if exist data\fits\*.java $(DEL) data\fits\*.java
+	if exist ..\loci\formats\*.java $(DEL) ..\loci\formats\*.java
+	if exist ..\loci\formats\codec\*.java $(DEL) ..\loci\formats\codec\*.java
+	if exist ..\loci\formats\gui\*.java $(DEL) ..\loci\formats\gui\*.java
+	if exist ..\loci\formats\in\*.java $(DEL) ..\loci\formats\in\*.java
+	if exist ..\loci\formats\out\*.java $(DEL) ..\loci\formats\out\*.java
+	if exist ..\nom\tam\fits\*.java $(DEL) ..\nom\tam\fits\*.java
+	if exist ..\nom\tam\util\*.java $(DEL) ..\nom\tam\util\*.java
+	if exist ..\nom\tam\test\*.java $(DEL) ..\nom\tam\test\*.java
+	if exist ..\HTTPClient\*.java $(DEL) ..\HTTPClient\*.java
+	if exist ..\ucar\multiarray\*.java $(DEL) ..\ucar\multiarray\*.java
+	if exist ..\ucar\netcdf\*.java $(DEL) ..\ucar\netcdf\*.java
+	if exist ..\ucar\tests\*.java $(DEL) ..\ucar\tests\*.java
+	if exist ..\dods\dap\*.java $(DEL) ..\dods\dap\*.java 
+	if exist ..\dods\dap\parser\*.java $(DEL) ..\dods\dap\parser\*.java 
+	if exist ..\dods\dap\Server\*.java $(DEL) ..\dods\dap\Server\*.java 
+	if exist ..\dods\util\*.java $(DEL) ..\dods\util\*.java 
+	if exist ..\gnu\regexp\*.java $(DEL) ..\gnu\regexp\*.java 
+	if exist data\hdfeos\*.java $(DEL) data\hdfeos\*.java
+	if exist data\hdfeos\hdfeosc\*.java $(DEL) data\hdfeos\hdfeosc\*.java
+	if exist data\vis5d\*.java $(DEL) data\vis5d\*.java
+	if exist data\gif\*.java $(DEL) data\gif\*.java
+	if exist data\gis\*.java $(DEL) data\gis\*.java
+	if exist data\tiff\*.java $(DEL) data\tiff\*.java
+	if exist ..\edu\wisc\ssec\mcidas\*.java $(DEL) ..\edu\wisc\ssec\mcidas\*.java
+	if exist ..\edu\wisc\ssec\mcidas\adde\*.java $(DEL) ..\edu\wisc\ssec\mcidas\adde\*.java
+	if exist data\mcidas\*.java $(DEL) data\mcidas\*.java
+	if exist data\avi\*.java $(DEL) data\avi\*.java
+	if exist data\bio\*.java $(DEL) data\bio\*.java
+	if exist data\biorad\*.java $(DEL) data\biorad\*.java
+	if exist data\jai\*.java $(DEL) data\jai\*.java
+	if exist data\qt\*.java $(DEL) data\qt\*.java
+	if exist data\visad\*.java $(DEL) data\visad\*.java
+	if exist data\visad\object\*.java $(DEL) data\visad\object\*.java
+	if exist data\text\*.java $(DEL) data\text\*.java
+	if exist data\hdf5\*.java $(DEL) data\hdf5\*.java
+	if exist data\hdf5\hdf5objects\*.java $(DEL) data\hdf5\hdf5objects\*.java
+	if exist ..\ncsa\hdf\hdf5lib\*.java $(DEL) ..\ncsa\hdf\hdf5lib\*.java
+	if exist ..\ncsa\hdf\hdf5lib\exceptions\*.java $(DEL) ..\ncsa\hdf\hdf5lib\exceptions\*.java
+	if exist jmet\*.java $(DEL) jmet\*.java
+	if exist paoloa\*.java $(DEL) paoloa\*.java
+	if exist paoloa\spline\*.java $(DEL) paoloa\spline\*.java
+	if exist aune\*.java $(DEL) aune\*.java
+	if exist benjamin\*.java $(DEL) benjamin\*.java
+	if exist rabin\*.java $(DEL) rabin\*.java
+	if exist bom\*.java $(DEL) bom\*.java
+	if exist aeri\*.java $(DEL) aeri\*.java
+	if exist data\amanda\*.java $(DEL) data\amanda\*.java
+	if exist georef\*.java $(DEL) georef\*.java
+	if exist meteorology\*.java $(DEL) meteorology\*.java
+	if exist install\*.java $(DEL) install\*.java
+	if exist examples\*.java $(DEL) examples\*.java
+
+clean:
+	if exist *.class $(DEL) *.class
+	if exist cluster\*.class $(DEL) cluster\*.class
+	if exist collab\*.class $(DEL) collab\*.class
+	if exist java3d\*.class $(DEL) java3d\*.class
+	if exist java2d\*.class $(DEL) java2d\*.class
+	if exist python\*.class $(DEL) python\*.class
+	if exist browser\*.class $(DEL) browser\*.class
+	if exist util\*.class $(DEL) util\*.class
+	if exist matrix\*.class $(DEL) matrix\*.class
+	if exist math\*.class $(DEL) math\*.class
+	if exist formula\*.class $(DEL) formula\*.class
+	if exist ss\*.class $(DEL) ss\*.class
+	if exist data\*.class $(DEL) data\*.class
+	if exist data\units\*.class $(DEL) data\units\*.class
+	if exist data\in\*.class $(DEL) data\in\*.class
+	if exist data\in\*.class $(DEL) data\in\*.class
+	if exist data\dods\*.class $(DEL) data\dods\*.class
+	if exist data\netcdf\units\*.class $(DEL) data\netcdf\units\*.class
+	if exist data\netcdf\in\*.class $(DEL) data\netcdf\in\*.class
+	if exist data\netcdf\out\*.class $(DEL) data\netcdf\out\*.class
+	if exist data\fits\*.class $(DEL) data\fits\*.class
+	if exist ..\loci\formats\*.class $(DEL) ..\loci\formats\*.class
+	if exist ..\loci\formats\codec\*.class $(DEL) ..\loci\formats\codec\*.class
+	if exist ..\loci\formats\gui\*.class $(DEL) ..\loci\formats\gui\*.class
+	if exist ..\loci\formats\in\*.class $(DEL) ..\loci\formats\in\*.class
+	if exist ..\loci\formats\out\*.class $(DEL) ..\loci\formats\out\*.class
+	if exist ..\nom\tam\fits\*.class $(DEL) ..\nom\tam\fits\*.class
+	if exist ..\nom\tam\util\*.class $(DEL) ..\nom\tam\util\*.class
+	if exist ..\nom\tam\test\*.class $(DEL) ..\nom\tam\test\*.class
+	if exist ..\HTTPClient\*.class $(DEL) ..\HTTPClient\*.class
+	if exist ..\ucar\multiarray\*.class $(DEL) ..\ucar\multiarray\*.class
+	if exist ..\ucar\netcdf\*.class $(DEL) ..\ucar\netcdf\*.class
+	if exist ..\ucar\tests\*.class $(DEL) ..\ucar\tests\*.class
+	if exist ..\dods\dap\*.class $(DEL) ..\dods\dap\*.class 
+	if exist ..\dods\dap\parser\*.class $(DEL) ..\dods\dap\parser\*.class 
+	if exist ..\dods\dap\Server\*.class $(DEL) ..\dods\dap\Server\*.class 
+	if exist ..\dods\util\*.class $(DEL) ..\dods\util\*.class 
+	if exist ..\gnu\regexp\*.class $(DEL) ..\gnu\regexp\*.class 
+	if exist data\hdfeos\*.class $(DEL) data\hdfeos\*.class
+	if exist data\hdfeos\hdfeosc\*.class $(DEL) data\hdfeos\hdfeosc\*.class
+	if exist data\vis5d\*.class $(DEL) data\vis5d\*.class
+	if exist ..\edu\wisc\ssec\mcidas\*.class $(DEL) ..\edu\wisc\ssec\mcidas\*.class
+	if exist ..\edu\wisc\ssec\mcidas\adde\*.class $(DEL) ..\edu\wisc\ssec\mcidas\adde\*.class
+	if exist data\mcidas\*.class $(DEL) data\mcidas\*.class
+	if exist data\avi\*.class $(DEL) data\avi\*.class
+	if exist data\bio\*.class $(DEL) data\bio\*.class
+	if exist data\biorad\*.class $(DEL) data\biorad\*.class
+	if exist data\jai\*.class $(DEL) data\jai\*.class
+	if exist data\qt\*.class $(DEL) data\qt\*.class
+	if exist data\gif\*.class $(DEL) data\gif\*.class
+	if exist data\gis\*.class $(DEL) data\gis\*.class
+	if exist data\tiff\*.class $(DEL) data\tiff\*.class
+	if exist data\visad\*.class $(DEL) data\visad\*.class
+	if exist data\visad\object\*.class $(DEL) data\visad\object\*.class
+	if exist data\text\*.class $(DEL) data\text\*.class
+	if exist data\hdf5\*.class $(DEL) data\hdf5\*.class
+	if exist data\hdf5\hdf5objects\*.class $(DEL) data\hdf5\hdf5objects\*.class
+	if exist ..\ncsa\hdf\hdf5lib\*.class $(DEL) ..\ncsa\hdf\hdf5lib\*.class
+	if exist ..\ncsa\hdf\hdf5lib\exceptions\*.class $(DEL) ..\ncsa\hdf\hdf5lib\exceptions\*.class
+	if exist jmet\*.class $(DEL) jmet\*.class
+	if exist paoloa\*.class $(DEL) paoloa\*.class
+	if exist paoloa\spline\*.class $(DEL) paoloa\spline\*.class
+	if exist aune\*.class $(DEL) aune\*.class
+	if exist benjamin\*.class $(DEL) benjamin\*.class
+	if exist rabin\*.class $(DEL) rabin\*.class
+	if exist bom\*.class $(DEL) bom\*.class
+	if exist aeri\*.class $(DEL) aeri\*.class
+	if exist data\amanda\*.class $(DEL) data\amanda\*.class
+	if exist georef\*.class $(DEL) georef\*.class
+	if exist meteorology\*.class $(DEL) meteorology\*.class
+	if exist install\*.class $(DEL) install\*.class
+	if exist examples\*.class $(DEL) examples\*.class
+
+RMIC_FILES = \
+visad.RemoteActionImpl \
+visad.RemoteCellImpl \
+visad.RemoteDataImpl \
+visad.RemoteDataReferenceImpl \
+visad.RemoteDisplayImpl \
+visad.RemoteFieldImpl \
+visad.RemoteFunctionImpl \
+visad.RemoteReferenceLinkImpl \
+visad.RemoteServerImpl \
+visad.RemoteSlaveDisplayImpl \
+visad.RemoteThingImpl \
+visad.RemoteThingReferenceImpl
+
+RMIC_COLLAB_FILES = \
+visad.collab.RemoteDisplayMonitorImpl \
+visad.collab.RemoteDisplaySyncImpl \
+visad.collab.RemoteEventProviderImpl
+
+RMIC_CLUSTER_FILES = \
+visad.cluster.RemoteAgentContactImpl \
+visad.cluster.RemoteClientAgentImpl \
+visad.cluster.RemoteClientDataImpl \
+visad.cluster.RemoteClientFieldImpl \
+visad.cluster.RemoteClientPartitionedFieldImpl \
+visad.cluster.RemoteClientTupleImpl \
+visad.cluster.RemoteClusterDataImpl \
+visad.cluster.RemoteNodeDataImpl \
+visad.cluster.RemoteNodeFieldImpl \
+visad.cluster.RemoteNodePartitionedFieldImpl \
+visad.cluster.RemoteNodeTupleImpl \
+visad.cluster.RemoteProxyAgentImpl
+
+recompile: \
+*.class \
+java3d\*.class \
+java2d\*.class \
+cluster\*.class \
+collab\*.class \
+data\*.class \
+..\HTTPClient\*.class \
+..\ucar\multiarray\*.class \
+..\ucar\netcdf\*.class \
+..\ucar\tests\*.class \
+..\dods\dap\*.class \
+..\dods\dap\parser\*.class \
+..\dods\dap\Server\*.class \
+..\dods\util\*.class \
+..\gnu\regexp\*.class \
+data\units\*.class \
+data\in\*.class \
+data\dods\*.class \
+data\netcdf\units\*.class \
+data\netcdf\in\*.class \
+data\netcdf\out\*.class \
+data\netcdf\*.class \
+..\loci\formats\*.class \
+..\loci\formats\codec\*.class \
+..\loci\formats\gui\*.class \
+..\loci\formats\in\*.class \
+..\loci\formats\out\*.class \
+..\nom\tam\util\*.class \
+..\nom\tam\fits\*.class \
+..\nom\tam\test\*.class \
+data\fits\*.class \
+data\vis5d\*.class \
+..\edu\wisc\ssec\mcidas\*.class \
+..\edu\wisc\ssec\mcidas\adde\*.class \
+data\mcidas\*.class \
+data\avi\*.class \
+data\bio\*.class \
+data\biorad\*.class \
+data\jai\*.class \
+data\qt\*.class \
+data\gif\*.class \
+data\gis\*.class \
+data\tiff\*.class \
+data\visad\*.class \
+data\visad\object\*.class \
+data\text\*.class \
+data\hdfeos\hdfeosc\*.class \
+data\hdfeos\*.class \
+..\ncsa\hdf\hdf5lib\*.class \
+..\ncsa\hdf\hdf5lib\exceptions\*.class \
+data\hdf5\hdf5objects\*.class \
+data\hdf5\*.class \
+python\*.class \
+matrix\*.class \
+math\*.class \
+browser\*.class \
+util\*.class \
+formula\*.class \
+ss\*.class \
+jmet\*.class \
+paoloa\*.class \
+paoloa\spline\*.class \
+benjamin\*.class \
+rabin\*.class \
+aune\*.class \
+bom\*.class \
+aeri\*.class \
+data\amanda\*.class \
+georef\*.class \
+meteorology\*.class \
+install\*.class
+	cd examples
+	$(JC) $(JFLAGS) *.java
+	cd ..
+	cd $(RMIC_DIR)
+	$(RMIC) $(RMIC_FILES)
+	cd $(RMIC_COLLAB_DIR)
+	$(RMIC) $(RMIC_COLLAB_FILES)
+	cd $(RMIC_CLUSTER_DIR)
+	$(RMIC) $(RMIC_CLUSTER_FILES)
+
+compile: clean
+	$(JC) $(JFLAGS) *.java
+	$(JC) $(JFLAGS) java3d\*.java
+	$(JC) $(JFLAGS) java2d\*.java
+	$(JC) $(JFLAGS) cluster\*.java
+	$(JC) $(JFLAGS) collab\*.java
+	$(JC) $(JFLAGS) data\*.java
+	$(JC) $(JFLAGS) data\units\*.java
+	$(JC) $(JFLAGS) ..\HTTPClient\*.java
+	$(JC) $(JFLAGS) ..\ucar\multiarray\*.java
+	$(JC) $(JFLAGS) ..\ucar\netcdf\*.java
+	$(JC) $(JFLAGS) ..\ucar\tests\*.java
+	$(JC) $(JFLAGS) ..\dods\dap\*.java
+	$(JC) $(JFLAGS) ..\dods\dap\parser\*.java
+	$(JC) $(JFLAGS) ..\dods\dap\Server\*.java
+	$(JC) $(JFLAGS) ..\dods\util\*.java
+	$(JC) $(JFLAGS) ..\gnu\regexp\*.java
+	$(JC) $(JFLAGS) data\in\*.java
+	$(JC) $(JFLAGS) data\dods\*.java
+	$(JC) $(JFLAGS) data\netcdf\units\*.java
+	$(JC) $(JFLAGS) data\netcdf\in\*.java
+	$(JC) $(JFLAGS) data\netcdf\out\*.java
+	$(JC) $(JFLAGS) data\netcdf\*.java
+	$(JC) $(JFLAGS) ..\loci\formats\*.java
+	$(JC) $(JFLAGS) ..\loci\formats\codec\*.java
+	$(JC) $(JFLAGS) ..\loci\formats\gui\*.java
+	$(JC) $(JFLAGS) ..\loci\formats\in\*.java
+	$(JC) $(JFLAGS) ..\loci\formats\out\*.java
+	$(JC) $(JFLAGS) ..\nom\tam\util\*.java
+	$(JC) $(JFLAGS) ..\nom\tam\fits\*.java
+	$(JC) $(JFLAGS) ..\nom\tam\test\*.java
+	$(JC) $(JFLAGS) data\fits\*.java
+	$(JC) $(JFLAGS) data\vis5d\*.java
+	$(JC) $(JFLAGS) ..\edu\wisc\ssec\mcidas\*.java
+	$(JC) $(JFLAGS) ..\edu\wisc\ssec\mcidas\adde\*.java
+	$(JC) $(JFLAGS) data\mcidas\*.java
+	$(JC) $(JFLAGS) data\avi\*.java
+	$(JC) $(JFLAGS) data\bio\*.java
+	$(JC) $(JFLAGS) data\biorad\*.java
+	$(JC) $(JFLAGS) data\jai\*.java
+	$(JC) $(JFLAGS) data\qt\*.java
+	$(JC) $(JFLAGS) data\gif\*.java
+	$(JC) $(JFLAGS) data\gis\*.java
+	$(JC) $(JFLAGS) data\tiff\*.java
+	$(JC) $(JFLAGS) data\visad\*.java
+	$(JC) $(JFLAGS) data\visad\object\*.java
+	$(JC) $(JFLAGS) data\text\*.java
+	$(JC) $(JFLAGS) data\hdfeos\hdfeosc\*.java
+	$(JC) $(JFLAGS) data\hdfeos\*.java
+	$(JC) $(JFLAGS) ..\ncsa\hdf\hdf5lib\exceptions\*.java
+	$(JC) $(JFLAGS) ..\ncsa\hdf\hdf5lib\*.java
+	$(JC) $(JFLAGS) data\hdf5\hdf5objects\*.java
+	$(JC) $(JFLAGS) data\hdf5\*.java
+	$(JC) $(JFLAGS) python\*.java
+	$(JC) $(JFLAGS) matrix\*.java
+	$(JC) $(JFLAGS) math\*.java
+	$(JC) $(JFLAGS) browser\*.java
+	$(JC) $(JFLAGS) util\*.java
+	$(JC) $(JFLAGS) formula\*.java
+	$(JC) $(JFLAGS) ss\*.java
+	$(JC) $(JFLAGS) jmet\*.java
+	$(JC) $(JFLAGS) paoloa\*.java
+	$(JC) $(JFLAGS) paoloa\spline\*.java
+	$(JC) $(JFLAGS) benjamin\*.java
+	$(JC) $(JFLAGS) rabin\*.java
+	$(JC) $(JFLAGS) aune\*.java
+	$(JC) $(JFLAGS) bom\*.java
+	$(JC) $(JFLAGS) aeri\*.java
+	$(JC) $(JFLAGS) data\amanda\*.java
+	$(JC) $(JFLAGS) georef\*.java
+	$(JC) $(JFLAGS) meteorology\*.java
+	$(JC) $(JFLAGS) install\*.java
+	cd examples
+	$(JC) $(JFLAGS) *.java
+	cd ..
+	cd $(RMIC_DIR)
+	$(RMIC) $(RMIC_FILES)
+	cd $(RMIC_COLLAB_DIR)
+	$(RMIC) $(RMIC_COLLAB_FILES)
+	cd $(RMIC_CLUSTER_DIR)
+	$(RMIC) $(RMIC_CLUSTER_FILES)
+
+debug:
+	@nmake /nologo -f Makefile.WinNT compile \
+	"JFLAGS = -J-mx64m -g"
+
+VISAD_PACKAGES = \
+visad \
+visad.cluster \
+visad.collab \
+visad.java3d \
+visad.java2d \
+visad.python \
+visad.browser \
+visad.util \
+visad.matrix \
+visad.math \
+visad.formula \
+visad.ss \
+visad.data \
+visad.data.units \
+visad.data.in \
+visad.data.dods \
+visad.data.netcdf \
+visad.data.netcdf.units \
+visad.data.netcdf.in \
+visad.data.netcdf.out \
+visad.data.fits \
+loci.formats \
+loci.formats.codec \
+loci.formats.gui \
+loci.formats.in \
+loci.formats.out \
+nom.tam.fits \
+nom.tam.util \
+nom.tam.test \
+HTTPClient \
+ucar.multiarray \
+ucar.util \
+ucar.netcdf \
+ucar.tests \
+dods.dap \
+dods.dap.parser \
+dods.dap.Server \
+dods.util \
+gnu.regexp \
+visad.data.hdfeos \
+visad.data.hdfeos.hdfeosc \
+visad.data.vis5d \
+visad.data.gif \
+visad.data.gis \
+visad.data.tiff \
+edu.wisc.ssec.mcidas \
+visad.data.mcidas \
+visad.data.avi \
+visad.data.bio \
+visad.data.biorad \
+visad.data.jai \
+visad.data.qt \
+visad.data.visad \
+visad.data.visad.object \
+visad.data.text \
+ncsa.hdf.hdf5lib \
+ncsa.hdf.hdf5lib.exceptions \
+visad.data.hdf5 \
+visad.data.hdf5.hdf5objects \
+visad.jmet \
+visad.paoloa \
+visad.paoloa.spline \
+visad.aune \
+visad.benjamin \
+visad.rabin \
+visad.bom \
+visad.aeri \
+visad.data.amanda \
+visad.georef \
+visad.meteorology \
+visad.install
+
+docs:
+	if not exist $(DOC_DIR) mkdir $(DOC_DIR)
+	$(DOC) $(DOCFLAGS) $(VISAD_PACKAGES)
+
+jikes_compile:
+	@nmake /nologo -f Makefile.WinNT compile \
+	"JC = jikes" \
+	"JFLAGS = "
diff --git a/visad/MathType.java b/visad/MathType.java
new file mode 100644
index 0000000..60b9d6e
--- /dev/null
+++ b/visad/MathType.java
@@ -0,0 +1,1450 @@
+//
+// MathType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.Vector;
+import java.util.Hashtable;
+
+/**
+   MathType is the superclass for VisAD's hierarchy of mathematical types.
+   It encompasses the mathematical concepts of scalars, tuples (i.e.,
+   n-dimensional vectors), functions, and certain forms of sets.<p>
+
+   VisAD <b>Data</b> objects are finite approximations to math objects.
+   Every Data object possesses a MathType, which characterizes the
+   mathematical object that the data approximate.  This MathType is not
+   synonymous with the class of the Data object, even though the class
+   names for a Data object and its corresponding MathType object (Set
+   and SetType, e.g.) may be similar.<p>
+
+   MathType objects are immutable; one implication is that the setDefaultSet
+   method (in RealTupleType) can be invoked only <b>prior</b> to using the
+   related getDefaultSet method.<p>
+*/
+public abstract class MathType extends Object implements java.io.Serializable {
+
+  /** true if this MathType is defined by the system */
+  boolean SystemIntrinsic;
+
+  /** this constructor assumes it is not creating an instrinsic MathType */
+  public MathType() {
+    this(false);
+  }
+
+  /**
+   * Create a MathType
+   *
+   * @param b <tt>true</tt> if this is an intrinsic MathType
+   */
+  MathType(boolean b) {
+    super();
+    SystemIntrinsic = b;
+  }
+
+  /** ScalarTypes are equal if they have the same name;
+      TupleTypes are equal if their components are equal;
+      FunctionTypes are equal if their domains and ranges
+      are equal */
+  public abstract boolean equals(Object type);
+
+  /** this is useful for determining compatibility of
+      Data objects for binary mathematical operations;
+      any RealTypes are equal; any TextTypes are equal;
+      TupleTypes are equal if their components are equal;
+      FunctionTypes are equal if their domains and ranges
+      are equal */
+  public abstract boolean equalsExceptName(MathType type);
+
+  /* TDR - May 1998.  As above, except units must be convertible */
+  public abstract boolean equalsExceptNameButUnits( MathType type )
+           throws VisADException;
+
+  /* TDR - June 1998           */
+  public abstract MathType cloneDerivative( RealType d_partial )
+           throws VisADException;
+
+  /* TDR - July 1998  */
+  public abstract MathType binary( MathType type, int op, Vector names )
+         throws VisADException;
+
+  /* TDR - July 1998 */
+  public abstract MathType unary( int op, Vector names )
+         throws VisADException;
+
+  /** returns a missing Data object for any MathType */
+  public abstract Data missingData() throws VisADException, RemoteException;
+
+  public abstract ShadowType buildShadowType(DataDisplayLink link, ShadowType parent)
+    throws VisADException, RemoteException;
+
+/* WLH 5 Jan 2000
+  public abstract String toString();
+*/
+  public String toString() {
+    return prettyString(0);
+  }
+
+  /** return a String that indents complex MathTypes
+      for human readability */
+  public String prettyString() {
+    return prettyString(0);
+  }
+
+  public abstract String prettyString(int indent);
+
+  /** create a MathType from its string representation;
+      essentially the inverse of the prettyString method */
+  public static MathType stringToType(String s) throws VisADException {
+    int length = s.length();
+    String r = "";
+    for (int i=0; i<length; i++) {
+      String t = s.substring(i, i+1);
+      if (!t.equals(" ") && !t.equals("\t") && !t.equals("\n")) {
+        r = r + t;
+      }
+    }
+    length = r.length();
+    if (length == 0) {
+      throw new TypeException("MathType.stringToType: badly formed string");
+    }
+    int[] len = {length};
+    MathType type = stringToType(r, len);
+    if (length != len[0]) {
+      throw new TypeException("MathType.stringToType: badly formed string");
+    }
+    return type;
+  }
+
+  private static MathType stringToType(String s, int[] len)
+          throws VisADException {
+    MathType ret_type;
+    String s0 = s.substring(0, 1);
+    if (s.startsWith("Set") || s.startsWith("SET") || s.startsWith("set")) {
+      String sr = s.substring(3);
+      int[] lensr = {sr.length()};
+      MathType type0 = stringToType(sr, lensr);
+      if (type0 instanceof RealType) {
+        ret_type = new SetType((RealType) type0);
+      }
+      else if (type0 instanceof RealTupleType) {
+        ret_type = new SetType((RealTupleType) type0);
+      }
+      else {
+        throw new TypeException("MathType.stringToType: badly formed string");
+      }
+      len[0] = 3 + lensr[0];
+      return ret_type;
+    }
+    else if (s0.equals("(")) {
+      String sr = s.substring(1);
+      int[] lensr = {sr.length()};
+      MathType type0 = stringToType(sr, lensr);
+      String t = sr.substring(lensr[0]);
+      if (type0 == null || t == null || t.equals("")) {
+        throw new TypeException("MathType.stringToType: badly formed string");
+      }
+      if (t.startsWith("->")) {
+        if (!(type0 instanceof RealType) &&
+            !(type0 instanceof RealTupleType)) {
+          throw new TypeException("MathType.stringToType: badly formed string");
+        }
+        String tr = t.substring(2);
+        int[] lentr = {tr.length()};
+        MathType type1 = stringToType(tr, lentr);
+        t = tr.substring(lentr[0]);
+        if (!t.startsWith(")") || type1 == null) {
+          throw new TypeException("MathType.stringToType: badly formed string");
+        }
+        len[0] = 1 + lensr[0] + 2 + lentr[0] + 1;
+        ret_type = new FunctionType(type0, type1);
+        return ret_type;
+      }
+      else {
+        Vector v = new Vector();
+        v.addElement(type0);
+        int lentup = 1 + lensr[0];
+        while (t.startsWith(",")) {
+          String tr = t.substring(1);
+          int[] lentr = {tr.length()};
+          MathType type1 = stringToType(tr, lentr);
+          if (type1 == null) {
+            throw new TypeException("MathType.stringToType: badly formed string");
+          }
+          v.addElement(type1);
+          lentup = lentup + 1 + lentr[0];
+          t = tr.substring(lentr[0]);
+        }
+        if (!t.startsWith(")")) {
+          throw new TypeException("MathType.stringToType: badly formed string");
+        }
+        len[0] = lentup + 1;
+        MathType[] types = new MathType[v.size()];
+        boolean all_real = true;
+        for (int i=0; i<v.size(); i++) {
+          types[i] = (MathType) v.elementAt(i);
+          all_real &= (types[i] instanceof RealType);
+        }
+        if (all_real) {
+          RealType[] rtypes = new RealType[v.size()];
+          for (int i=0; i<v.size(); i++) {
+            rtypes[i] = (RealType) types[i];
+          }
+          ret_type = new RealTupleType(rtypes);
+        }
+        else {
+          ret_type = new TupleType(types);
+        }
+        return ret_type;
+      }
+    }
+    else if ((0 <= s0.compareTo("a") && s0.compareTo("z") <= 0) ||
+             (0 <= s0.compareTo("A") && s0.compareTo("Z") <= 0)) {
+      for (int i=1; i<len[0]; i++) {
+        s0 = s.substring(i, i+1);
+/* old check
+        if (!((0 <= s0.compareTo("a") && s0.compareTo("z") <= 0) ||
+              (0 <= s0.compareTo("A") && s0.compareTo("Z") <= 0) ||
+              (0 <= s0.compareTo("0") && s0.compareTo("9") <= 0) ||
+              s0.equals("_") || 
+              // add in some other valid chars
+              s0.equals("%") || s0.equals("+") ||
+              s0.equals("/") || s0.equals(":") ||
+              s0.equals("[") || s0.equals("]") || s0.equals("^"))) {
+*/
+              // non-printable characters
+        if (!(0 <= s0.compareTo("!") && s0.compareTo("~") <= 0) ||
+              // illegal ScalarType chars and MathType parsing delimiters
+              s0.equals(")") ||
+              s0.equals("(") ||
+              s0.equals(",") ||
+              s0.equals("-") ||
+              s0.equals(">") ||
+              // also eliminate regex chars
+              s0.equals(".") ||
+              s0.equals("*") ||
+              s0.equals("?") ||
+              s0.equals("|")) {
+          len[0] = i;
+          break;
+        }
+      }
+      String rs = s.substring(0, len[0]);
+      String t = s.substring(len[0]);
+      if (t.startsWith("(Text)")) {
+        ret_type = TextType.getTextType(rs);
+        len[0] += 6;
+      }
+      else {
+        ret_type = RealType.getRealType(rs);
+      }
+      return ret_type;
+    }
+    else {
+      throw new TypeException("MathType.stringToType: badly formed string");
+    }
+  }
+
+  private static Vector timeAliases = makeTimeAliasVector();
+
+  private static Vector makeTimeAliasVector() {
+    Vector v = new Vector();
+    v.add("time");
+    v.add("Time");
+    v.add("TIME");
+    return v;
+  }
+
+  /** Adds a ScalarType name that guessMaps should map to Animation. */
+  public static void addTimeAlias(String name) {
+    synchronized (timeAliases) {
+      timeAliases.add(name);
+    }
+  }
+
+  /** Guesses at a set of "default" mappings for this MathType.
+      Intuitively, first we look for a FunctionType with domain dimension 3,
+      then a nested group of FunctionTypes with 'cumulative' domain
+      dimension 3. Next we look for a FunctionType or nested group of
+      FunctionTypes with domain dimension 2. Then, we look for a
+      FunctionType with domain dimension 1. Nested groups of FunctionTypes
+      may be nested with TupleTypes, which is indicated by some of the
+      MathType templates. Lastly, if no matching FunctionTypes are found,
+      then we look for 3-D, 2-D, or 1-D SetTypes. */
+  public ScalarMap[] guessMaps(boolean threeD) {
+    MathType m = this;
+
+    // set up aliases for "time" RealType to be mapped to Animation
+    DataStruct[][] ds;
+    synchronized (timeAliases) {
+      int len = timeAliases.size();
+      ds = new DataStruct[1][len];
+      for (int i=0; i<len; i++) {
+        String name = (String) timeAliases.elementAt(i);
+        ds[0][i] = new DataStruct(name);
+      }
+    }
+
+    // find a FunctionType whose 1-D domain is a RealType matching above
+    findTimeFunction(m, ds, null);
+    int timeFunc = -1;
+    for (int i=0; i<ds[0].length; i++) {
+      if (ds[0][i].fvalid && ds[0][i].funcs.size() > 0) {
+        timeFunc = i;
+        break;
+      }
+    }
+
+    // compile a FunctionType and SetType list to search for template matches
+    Vector flist = new Vector(); // functions
+    Vector slist = new Vector(); // sets
+    Vector tlist = new Vector(); // tuples of reals
+    if (timeFunc < 0) buildTypeList(m, flist, slist, tlist);
+    else {
+      // found a "time" RealType; only search ranges of "time" FunctionTypes
+      for (int i=0; i<ds[0][timeFunc].funcs.size(); i++) {
+        FunctionType f = (FunctionType) ds[0][timeFunc].funcs.elementAt(i);
+        buildTypeList(f.getRange(), flist, slist, tlist);
+      }
+    }
+
+    // look for matches between Function templates and FunctionTypes list
+    int numfuncs = flist.size();
+    for (int t=(threeD ? 0 : 4); t<7; t++) {
+      for (int fi=0; fi<numfuncs; fi++) {
+        FunctionType ft = (FunctionType) flist.elementAt(fi);
+        switch (t) {
+          case 0: // 3-D ONLY
+            //   ((x, y, z) -> (..., a, ...))
+            //   ((x, y, z) -> a)
+            // x -> X, y -> Y, z -> Z, a -> IsoContour
+            if (!ft.getFlat()) break;
+            RealTupleType domain = ft.getDomain();
+            if (domain.getDimension() != 3) break;
+            MathType range = ft.getRange();
+            RealType x, y, z;
+            CoordinateSystem cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                x = (RealType) ref.getComponent(0);
+                y = (RealType) ref.getComponent(1);
+                z = (RealType) ref.getComponent(2);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                x = (RealType) domain.getComponent(0);
+                y = (RealType) domain.getComponent(1);
+                z = (RealType) domain.getComponent(2);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            RealType a;
+            if (range instanceof RealType) {
+              a = (RealType) range;
+              try {
+                ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 4 : 5];
+                if (RealType.Latitude.equals(x)) {
+                  smaps[0] = new ScalarMap(x, Display.YAxis);
+                  smaps[1] = new ScalarMap(y, Display.XAxis);
+                }
+                else {
+                  smaps[0] = new ScalarMap(x, Display.XAxis);
+                  smaps[1] = new ScalarMap(y, Display.YAxis);
+                }
+                smaps[2] = new ScalarMap(z, Display.ZAxis);
+                smaps[3] = new ScalarMap(a, Display.IsoContour);
+                if (timeFunc >= 0) {
+                  Object o = ds[0][timeFunc].funcs.elementAt(0);
+                  RealTupleType rtt = ((FunctionType) o).getDomain();
+                  RealType time = (RealType) rtt.getComponent(0);
+                  smaps[4] = new ScalarMap(time, Display.Animation);
+                }
+                return smaps;
+              }
+              catch (VisADException exc) { }
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType mt;
+                try {
+                  mt = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  break;
+                }
+                if (mt instanceof RealType) {
+                  a = (RealType) mt;
+                  try {
+                    ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 4 : 5];
+                    if (RealType.Latitude.equals(x)) {
+                      smaps[0] = new ScalarMap(x, Display.YAxis);
+                      smaps[1] = new ScalarMap(y, Display.XAxis);
+                    }
+                    else {
+                      smaps[0] = new ScalarMap(x, Display.XAxis);
+                      smaps[1] = new ScalarMap(y, Display.YAxis);
+                    }
+                    smaps[2] = new ScalarMap(z, Display.ZAxis);
+                    smaps[3] = new ScalarMap(a, Display.IsoContour);
+                    if (timeFunc >= 0) {
+                      Object o = ds[0][timeFunc].funcs.elementAt(0);
+                      RealTupleType rtt = ((FunctionType) o).getDomain();
+                      RealType time = (RealType) rtt.getComponent(0);
+                      smaps[4] = new ScalarMap(time, Display.Animation);
+                    }
+                    return smaps;
+                  }
+                  catch (VisADException exc) {
+                    break;
+                  }
+                }
+              }
+            }
+            break;
+
+          case 1: // 3-D ONLY
+            //   (z -> ((x, y) -> (..., a, ...)))
+            //   (z -> (..., ((x, y) -> (..., a, ...)), ...))
+            //   (z -> ((x, y) -> a))
+            //   (z -> (..., ((x, y) -> a), ...))
+            // x -> X, y -> Y, z -> Z, a -> IsoContour
+            domain = ft.getDomain();
+            if (domain.getDimension() != 1) break;
+            cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                z = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                z = (RealType) domain.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = ft.getRange();
+            FunctionType rf = null;
+            if (range instanceof FunctionType) {
+              rf = (FunctionType) range;
+              if (!rf.getFlat() || rf.getDomain().getDimension() != 2) break;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  rf = null;
+                  break;
+                }
+                if (ttci instanceof FunctionType) {
+                  FunctionType ftci = (FunctionType) ttci;
+                  if (ftci.getFlat() && ftci.getDomain().getDimension() == 2) {
+                    rf = ftci;
+                  }
+                  break;
+                }
+              }
+            }
+            if (rf == null) break;
+            RealTupleType rfd = rf.getDomain();
+            cs = rfd.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                x = (RealType) ref.getComponent(0);
+                y = (RealType) ref.getComponent(1);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                x = (RealType) rfd.getComponent(0);
+                y = (RealType) rfd.getComponent(1);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = rf.getRange();
+            a = null;
+            if (range instanceof RealType) {
+              a = (RealType) range;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  break;
+                }
+                if (ttci instanceof RealType) {
+                  a = (RealType) ttci;
+                  break;
+                }
+              }
+            }
+            if (a == null) break;
+            try {
+              ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 4 : 5];
+              if (RealType.Latitude.equals(x)) {
+                smaps[0] = new ScalarMap(x, Display.YAxis);
+                smaps[1] = new ScalarMap(y, Display.XAxis);
+              }
+              else {
+                smaps[0] = new ScalarMap(x, Display.XAxis);
+                smaps[1] = new ScalarMap(y, Display.YAxis);
+              }
+              smaps[2] = new ScalarMap(z, Display.ZAxis);
+              smaps[3] = new ScalarMap(a, Display.IsoContour);
+              if (timeFunc >= 0) {
+                Object o = ds[0][timeFunc].funcs.elementAt(0);
+                RealTupleType rtt = ((FunctionType) o).getDomain();
+                RealType time = (RealType) rtt.getComponent(0);
+                smaps[4] = new ScalarMap(time, Display.Animation);
+              }
+              return smaps;
+            }
+            catch (VisADException exc) { }
+
+          case 2: // 3-D ONLY
+            //   ((x, y) -> (z -> (..., a, ...)))
+            //   ((x, y) -> (..., (z -> (..., a, ...)), ...))
+            //   ((x, y) -> (z -> a))
+            //   ((x, y) -> (..., (z -> a), ...))
+            // x -> X, y -> Y, z -> Z, a -> RGB
+            //
+            domain = ft.getDomain();
+            if (domain.getDimension() != 2) break;
+            cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                x = (RealType) ref.getComponent(0);
+                y = (RealType) ref.getComponent(1);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                x = (RealType) domain.getComponent(0);
+                y = (RealType) domain.getComponent(1);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = ft.getRange();
+            rf = null;
+            if (range instanceof FunctionType) {
+              rf = (FunctionType) range;
+              if (!rf.getFlat() || rf.getDomain().getDimension() != 1) break;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  rf = null;
+                  break;
+                }
+                if (ttci instanceof FunctionType) {
+                  FunctionType ftci = (FunctionType) ttci;
+                  if (ftci.getFlat() && ftci.getDomain().getDimension() == 1) {
+                    rf = ftci;
+                  }
+                  break;
+                }
+              }
+            }
+            if (rf == null) break;
+            rfd = rf.getDomain();
+            cs = rfd.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                z = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                z = (RealType) rfd.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = rf.getRange();
+            a = null;
+            if (range instanceof RealType) {
+              a = (RealType) range;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  break;
+                }
+                if (ttci instanceof RealType) {
+                  a = (RealType) ttci;
+                  break;
+                }
+              }
+            }
+            if (a == null) break;
+            try {
+              ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 4 : 5];
+              if (RealType.Latitude.equals(x)) {
+                smaps[0] = new ScalarMap(x, Display.YAxis);
+                smaps[1] = new ScalarMap(y, Display.XAxis);
+              }
+              else {
+                smaps[0] = new ScalarMap(x, Display.XAxis);
+                smaps[1] = new ScalarMap(y, Display.YAxis);
+              }
+              smaps[2] = new ScalarMap(z, Display.ZAxis);
+              smaps[3] = new ScalarMap(a, Display.IsoContour);
+              if (timeFunc >= 0) {
+                Object o = ds[0][timeFunc].funcs.elementAt(0);
+                RealTupleType rtt = ((FunctionType) o).getDomain();
+                RealType time = (RealType) rtt.getComponent(0);
+                smaps[4] = new ScalarMap(time, Display.Animation);
+              }
+              return smaps;
+            }
+            catch (VisADException exc) { }
+
+          case 3: // 3-D ONLY
+            //   (x -> (y -> (z -> (..., a, ...))))
+            //   (x -> (..., (y -> (z -> (..., a, ...))), ...))
+            //   (x -> (y -> (..., z -> (..., a, ...)), ...))
+            //   (x -> (..., (y -> (..., (z -> (..., a, ...)), ...)), ...))
+            //   (x -> (y -> (z -> a)))
+            //   (x -> (..., (y -> (z -> a)), ...))
+            //   (x -> (y -> (..., (z -> a), ...)))
+            //   (x -> (..., (y -> (..., (z -> a), ...)), ...))
+            // x -> X, y -> Y, z -> Z, a -> RGB
+            domain = ft.getDomain();
+            if (domain.getDimension() != 1) break;
+            cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                x = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                x = (RealType) domain.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            // find nested "y"
+            range = ft.getRange();
+            rf = null;
+            if (range instanceof FunctionType) {
+              rf = (FunctionType) range;
+              if (rf.getDomain().getDimension() != 1) break;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  rf = null;
+                  break;
+                }
+                if (ttci instanceof FunctionType) {
+                  FunctionType ftci = (FunctionType) ttci;
+                  if (ftci.getDomain().getDimension() == 1) {
+                    rf = ftci;
+                  }
+                  break;
+                }
+              }
+            }
+            if (rf == null) break;
+            rfd = rf.getDomain();
+            cs = rfd.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                y = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                y = (RealType) rfd.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            // find nested "z"
+            range = rf.getRange();
+            rf = null;
+            if (range instanceof FunctionType) {
+              rf = (FunctionType) range;
+              if (rf.getDomain().getDimension() != 1) break;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  rf = null;
+                  break;
+                }
+                if (ttci instanceof FunctionType) {
+                  FunctionType ftci = (FunctionType) ttci;
+                  if (ftci.getDomain().getDimension() == 1) {
+                    rf = ftci;
+                  }
+                  break;
+                }
+              }
+            }
+            if (rf == null) break;
+            rfd = rf.getDomain();
+            cs = rfd.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                z = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                z = (RealType) rfd.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            // find nested "a"
+            range = rf.getRange();
+            a = null;
+            if (range instanceof RealType) {
+              a = (RealType) range;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  break;
+                }
+                if (ttci instanceof RealType) {
+                  a = (RealType) ttci;
+                  break;
+                }
+              }
+            }
+            if (a == null) break;
+            try {
+              ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 4 : 5];
+              if (RealType.Latitude.equals(x)) {
+                smaps[0] = new ScalarMap(x, Display.YAxis);
+                smaps[1] = new ScalarMap(y, Display.XAxis);
+              }
+              else {
+                smaps[0] = new ScalarMap(x, Display.XAxis);
+                smaps[1] = new ScalarMap(y, Display.YAxis);
+              }
+              smaps[2] = new ScalarMap(z, Display.ZAxis);
+              smaps[3] = new ScalarMap(a, Display.IsoContour);
+              if (timeFunc >= 0) {
+                Object o = ds[0][timeFunc].funcs.elementAt(0);
+                RealTupleType rtt = ((FunctionType) o).getDomain();
+                RealType time = (RealType) rtt.getComponent(0);
+                smaps[4] = new ScalarMap(time, Display.Animation);
+              }
+              return smaps;
+            }
+            catch (VisADException exc) { }
+
+          case 4: // 2-D or 3-D
+            //   ((x, y) -> (..., r, ..., g, ..., b, ...))
+            // x -> X, y -> Y, r -> Red, g -> Green, b -> Blue
+            //   ((x, y) -> (..., a, ...))
+            //   ((x, y) -> a)
+            // x -> X, y -> Y, a -> RGB
+            if (!ft.getFlat()) break;
+            domain = ft.getDomain();
+            if (domain.getDimension() != 2) break;
+            cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                x = (RealType) ref.getComponent(0);
+                y = (RealType) ref.getComponent(1);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                x = (RealType) domain.getComponent(0);
+                y = (RealType) domain.getComponent(1);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = ft.getRange();
+            RealType[] rgb = new RealType[3];
+            int rgbc = 0;
+            if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  break;
+                }
+                if (ttci instanceof RealType && rgbc < 3) {
+                  rgb[rgbc++] = (RealType) ttci;
+                }
+              }
+            }
+            else if (range instanceof RealType) {
+              rgb[rgbc++] = (RealType) range;
+            }
+            if (rgbc == 0) break;
+            if (rgbc < 3) {
+              try {
+                ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 3 : 4];
+                if (RealType.Latitude.equals(x)) {
+                  smaps[0] = new ScalarMap(x, Display.YAxis);
+                  smaps[1] = new ScalarMap(y, Display.XAxis);
+                }
+                else {
+                  smaps[0] = new ScalarMap(x, Display.XAxis);
+                  smaps[1] = new ScalarMap(y, Display.YAxis);
+                }
+                smaps[2] = new ScalarMap(rgb[0], Display.RGB);
+                if (timeFunc >= 0) {
+                  Object o = ds[0][timeFunc].funcs.elementAt(0);
+                  RealTupleType rtt = ((FunctionType) o).getDomain();
+                  RealType time = (RealType) rtt.getComponent(0);
+                  smaps[3] = new ScalarMap(time, Display.Animation);
+                }
+                return smaps;
+              }
+              catch (VisADException exc) { }
+            }
+            else {
+              try {
+                ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 5 : 6];
+                if (RealType.Latitude.equals(x)) {
+                  smaps[0] = new ScalarMap(x, Display.YAxis);
+                  smaps[1] = new ScalarMap(y, Display.XAxis);
+                }
+                else {
+                  smaps[0] = new ScalarMap(x, Display.XAxis);
+                  smaps[1] = new ScalarMap(y, Display.YAxis);
+                }
+                smaps[2] = new ScalarMap(rgb[0], Display.Red);
+                smaps[3] = new ScalarMap(rgb[1], Display.Green);
+                smaps[4] = new ScalarMap(rgb[2], Display.Blue);
+                if (timeFunc >= 0) {
+                  Object o = ds[0][timeFunc].funcs.elementAt(0);
+                  RealTupleType rtt = ((FunctionType) o).getDomain();
+                  RealType time = (RealType) rtt.getComponent(0);
+                  smaps[5] = new ScalarMap(time, Display.Animation);
+                }
+                return smaps;
+              }
+              catch (VisADException exc) { }
+            }
+            break;
+
+          case 5: // 2-D or 3-D
+            //   (x -> (y -> (..., a, ...)))
+            //   (x -> (y -> a))
+            // 3-D: x -> X, y -> Y, a -> Z
+            // 2-D: x -> X, y -> Y, a -> RGB
+            domain = ft.getDomain();
+            if (domain.getDimension() != 1) break;
+            cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                x = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                x = (RealType) domain.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = ft.getRange();
+            if (!(range instanceof FunctionType)) break;
+            ft = (FunctionType) range;
+            if (!ft.getFlat()) break;
+            domain = ft.getDomain();
+            if (domain.getDimension() != 1) break;
+            cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                y = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                y = (RealType) domain.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = ft.getRange();
+            a = null;
+            if (range instanceof RealType) {
+              a = (RealType) range;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  break;
+                }
+                if (ttci instanceof RealType) {
+                  a = (RealType) ttci;
+                  break;
+                }
+              }
+            }
+            if (a == null) break;
+            try {
+              ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 3 : 4];
+              if (RealType.Latitude.equals(x)) {
+                smaps[0] = new ScalarMap(x, Display.YAxis);
+                smaps[1] = new ScalarMap(y, Display.XAxis);
+              }
+              else {
+                smaps[0] = new ScalarMap(x, Display.XAxis);
+                smaps[1] = new ScalarMap(y, Display.YAxis);
+              }
+              smaps[2] = new ScalarMap(a, threeD ? Display.ZAxis
+                                                 : Display.RGB);
+              if (timeFunc >= 0) {
+                Object o = ds[0][timeFunc].funcs.elementAt(0);
+                RealTupleType rtt = ((FunctionType) o).getDomain();
+                RealType time = (RealType) rtt.getComponent(0);
+                smaps[3] = new ScalarMap(time, Display.Animation);
+              }
+              return smaps;
+            }
+            catch (VisADException exc) { }
+
+          case 6: // 2-D or 3-D
+            //   (x -> (..., a, ...))
+            //   (x -> a)
+            // x -> X, a -> Y
+            if (!ft.getFlat()) break;
+            domain = ft.getDomain();
+            if (domain.getDimension() != 1) break;
+            cs = domain.getCoordinateSystem();
+            if (cs != null) {
+              RealTupleType ref = cs.getReference();
+              try {
+                x = (RealType) ref.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            else {
+              try {
+                x = (RealType) domain.getComponent(0);
+              }
+              catch (VisADException exc) {
+                break;
+              }
+            }
+            range = ft.getRange();
+            a = null;
+            if (range instanceof RealType) {
+              a = (RealType) range;
+            }
+            else if (range instanceof TupleType) {
+              TupleType tt = (TupleType) range;
+              for (int i=0; i<tt.getDimension(); i++) {
+                MathType ttci;
+                try {
+                  ttci = tt.getComponent(i);
+                }
+                catch (VisADException exc) {
+                  break;
+                }
+                if (ttci instanceof RealType) {
+                  a = (RealType) ttci;
+                  break;
+                }
+              }
+            }
+            if (a == null) break;
+            try {
+              ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? 2 : 3];
+              if (RealType.Latitude.equals(x)) {
+                smaps[0] = new ScalarMap(x, Display.YAxis);
+                smaps[1] = new ScalarMap(a, Display.XAxis);
+              }
+              else {
+                smaps[0] = new ScalarMap(x, Display.XAxis);
+                smaps[1] = new ScalarMap(a, Display.YAxis);
+              }
+              if (timeFunc >= 0) {
+                Object o = ds[0][timeFunc].funcs.elementAt(0);
+                RealTupleType rtt = ((FunctionType) o).getDomain();
+                RealType time = (RealType) rtt.getComponent(0);
+                smaps[2] = new ScalarMap(time, Display.Animation);
+              }
+              return smaps;
+            }
+            catch (VisADException exc) { }
+            break;
+        }
+      }
+    }
+
+    // look for matches between Set templates and SetTypes list
+    final DisplayRealType[] spatial =
+      {Display.XAxis, Display.YAxis, Display.ZAxis};
+    final boolean[] mark = {false, false, false};
+    int maxdim = threeD ? 3 : 2;
+    int numsets = slist.size();
+    for (int dim=maxdim; dim>=1; --dim) {
+      for (int si=0; si<numsets; si++) {
+        //   Set(x, y, z)
+        // x -> X, y -> Y, z -> Z
+        //   Set(x, y)
+        // x -> X, y -> Y
+        //   Set(x)
+        // x -> X
+        SetType st = (SetType) slist.elementAt(si);
+        RealTupleType domain = st.getDomain();
+        CoordinateSystem cs = domain.getCoordinateSystem();
+        if (cs != null) {
+          // use CoordinateSystem reference instead of original RealTupleType
+          domain = cs.getReference();
+        }
+        if (domain.getDimension() != dim) continue;
+        try {
+          ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? dim : dim + 1];
+          for (int i=0; i<dim; i++) {
+            RealType rt = (RealType) domain.getComponent(i);
+            if (RealType.Latitude.equals(rt)) {
+              smaps[i] = new ScalarMap(rt, spatial[1]);
+              mark[1] = true;
+            }
+            else if (RealType.Longitude.equals(rt)) {
+              smaps[i] = new ScalarMap(rt, spatial[0]);
+              mark[0] = true;
+            }
+          }
+          for (int i=0; i<dim; i++) {
+            RealType rt = (RealType) domain.getComponent(i);
+            if (!RealType.Latitude.equals(rt) && !RealType.Longitude.equals(rt)) {
+              for (int j=0; j<3; j++) {
+                if (!mark[j]) {
+                  smaps[i] = new ScalarMap(rt, spatial[j]);
+                  mark[j] = true;
+                }
+              }
+            }
+          }
+          if (timeFunc >= 0) {
+            Object o = ds[0][timeFunc].funcs.elementAt(0);
+            RealTupleType rtt = ((FunctionType) o).getDomain();
+            RealType time = (RealType) rtt.getComponent(0);
+            smaps[dim] = new ScalarMap(time, Display.Animation);
+          }
+          return smaps;
+        }
+        catch (VisADException exc) { }
+      }
+    }
+
+    // if the only match is a RealTupleType, map to first few tuple elements
+    int numtuples = tlist.size();
+    if (numtuples >= 1) {
+      // use first RealTupleType - (x, y, z, ...)
+      // x -> X, y -> Y, z -> Z
+      RealTupleType domain = (RealTupleType) tlist.elementAt(0);
+      CoordinateSystem cs = domain.getCoordinateSystem();
+      if (cs != null) {
+        // use CoordinateSystem reference instead of original RealTupleType
+        domain = cs.getReference();
+      }
+      int dim = domain.getDimension();
+      if (dim > maxdim) dim = maxdim;
+      try {
+        ScalarMap[] smaps = new ScalarMap[timeFunc < 0 ? dim : dim + 1];
+        for (int i=0; i<dim; i++) {
+          RealType rt = (RealType) domain.getComponent(i);
+          smaps[i] = new ScalarMap(rt, spatial[i]);
+        }
+        if (timeFunc >= 0) {
+          Object o = ds[0][timeFunc].funcs.elementAt(0);
+          RealTupleType rtt = ((FunctionType) o).getDomain();
+          RealType time = (RealType) rtt.getComponent(0);
+          smaps[dim] = new ScalarMap(time, Display.Animation);
+        }
+        return smaps;
+      }
+      catch (VisADException exc) { }
+    }
+
+    return null;
+  }
+
+  /** used by guessMaps to recursively build a list of FunctionTypes to
+      attempt template matching with */
+  private void buildTypeList(MathType mt,
+    Vector flist, Vector slist, Vector tlist)
+  {
+    if (mt instanceof TupleType) {
+      TupleType tt = (TupleType) mt;
+      if (tt instanceof RealTupleType) {
+        // found a tuple of reals; add it to RealTuple list
+        tlist.addElement(mt);
+      }
+      else {
+        // search each tuple component
+        for (int i=0; i<tt.getDimension(); i++) {
+          try {
+            buildTypeList(tt.getComponent(i), flist, slist, tlist);
+          }
+          catch (VisADException exc) { }
+        }
+      }
+    }
+    else if (mt instanceof SetType) {
+      // found a set; add it to set list
+      slist.addElement(mt);
+    }
+    else if (mt instanceof FunctionType) {
+      // found a function; add it to function list and recurse function range
+      flist.addElement(mt);
+      FunctionType ft = (FunctionType) mt;
+      buildTypeList(ft.getRange(), flist, slist, tlist);
+    }
+    return;
+  }
+
+  /** used by guessMaps to recursively find a "time" RealType inside
+      a 1-D function domain */
+  private void findTimeFunction(MathType mt, DataStruct[][] info,
+                                Hashtable invalid) {
+    boolean wasnull = false;
+    if (invalid == null) {
+      invalid = new Hashtable();
+      wasnull = true;
+    }
+    if (mt instanceof TupleType) {
+      TupleType tt = (TupleType) mt;
+      // search each element of the tuple
+      for (int i=0; i<tt.getDimension(); i++) {
+        MathType tc = null;
+        try {
+          tc = tt.getComponent(i);
+        }
+        catch (VisADException exc) { }
+        findTimeFunction(tc, info, invalid);
+      }
+    }
+    else if (mt instanceof SetType) {
+      SetType st = (SetType) mt;
+      // search set's domain
+      findTimeFunction(st.getDomain(), info, invalid);
+    }
+    else if (mt instanceof FunctionType) {
+      FunctionType ft = (FunctionType) mt;
+      RealTupleType domain = ft.getDomain();
+      MathType range = ft.getRange();
+      RealType rtc0 = null;
+      try {
+        rtc0 = (RealType) domain.getComponent(0);
+      }
+      catch (VisADException exc) { }
+      boolean found = false;
+      if (rtc0 != null && domain.getDimension() == 1) {
+        // search function's domain for RealType with matching name
+        String rtname = rtc0.getName();
+        for (int i=0; i<info[0].length; i++) {
+          if (rtname.equals(info[0][i].name)) {
+            info[0][i].funcs.addElement(ft);
+            found = true;
+          }
+        }
+
+        // WLH 19 Oct 2001
+        Unit rtc0_unit = rtc0.getDefaultUnit();
+        if (SI.second.isConvertible(rtc0_unit) ||
+            CommonUnit.secondsSinceTheEpoch.isConvertible(rtc0_unit)) {
+          int len = info[0].length;
+          DataStruct[] temp = new DataStruct[len + 1];
+          for (int i=0; i<len; i++) {
+            temp[i] = info[0][i];
+          }
+          temp[len] = new DataStruct(rtname);
+          temp[len].funcs.addElement(ft);
+          info[0] = temp;
+          found = true;
+        }
+
+      }
+      // search function's domain
+      if (!found) findTimeFunction(domain, info, invalid);
+      // search function's range
+      findTimeFunction(range, info, invalid);
+    }
+    else if (mt instanceof RealType) {
+      RealType rt = (RealType) mt;
+      String rtname = rt.getName();
+      invalid.put(rtname, rt);
+/*
+      for (int i=0; i<info[0].length; i++) {
+        // invalidate RealTypes not in a 1-D function domain
+        if (rtname.equals(info[0][i].name)) info[0][i].fvalid = false;
+      }
+*/
+    }
+
+    if (wasnull) {
+      for (int i=0; i<info[0].length; i++) {
+        // invalidate RealTypes not in a 1-D function domain
+        if (invalid.get(info[0][i].name) != null) info[0][i].fvalid = false;
+      }
+    }
+
+    return;
+  }
+
+  /** return true if st occurs in mt */
+  public static boolean findScalarType(MathType mt, ScalarType st)
+         throws VisADException {
+    if (mt == null || st == null) return false;
+    if (mt instanceof TupleType) {
+      TupleType tt = (TupleType) mt;
+      // search each element of the tuple
+      for (int i=0; i<tt.getDimension(); i++) {
+        MathType tc = tt.getComponent(i);
+        if (findScalarType(tc, st)) return true;
+      }
+
+      // WLH 8 Jan 2003
+      if (mt instanceof RealTupleType) {
+        CoordinateSystem cs = ((RealTupleType) mt).getCoordinateSystem();
+        if (cs != null) {
+          if (findScalarType(cs.getReference(), st)) return true;
+        }
+      }
+
+      return false;
+    }
+    else if (mt instanceof SetType) {
+      SetType et = (SetType) mt;
+      // search set's domain
+      return findScalarType(et.getDomain(), st);
+    }
+    else if (mt instanceof FunctionType) {
+      FunctionType ft = (FunctionType) mt;
+      RealTupleType domain = ft.getDomain();
+      MathType range = ft.getRange();
+      return findScalarType(domain, st) || findScalarType(range, st);
+    }
+    else if (mt instanceof ScalarType) {
+      return (mt.equals(st));
+    }
+    return false;
+  }
+
+
+  /** run 'java visad.MathType' to test MathType.prettyString()
+      and MathType.guessMaps() */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    RealType X = RealType.getRealType("Xxxxxx");
+    RealType Y = RealType.getRealType("Yyyyyy");
+    RealType Z = RealType.getRealType("Zzzzzz");
+
+    RealType A = RealType.getRealType("Aaaaaa");
+    RealType B = RealType.getRealType("Bbbbbb");
+
+    RealType[] domain2d = {X, Y};
+    RealTupleType Domain2d = new RealTupleType(domain2d);
+
+    RealType[] range2d = {A, B};
+    RealTupleType Range2d = new RealTupleType(range2d);
+
+    // construct first MathType
+    FunctionType Field2d1 = new FunctionType(Domain2d, A);
+    FunctionType Field2d2 = new FunctionType(Domain2d, Range2d);
+    FunctionType Field2d3 = new FunctionType(Domain2d, B);
+    FunctionType function = new FunctionType(X, Field2d2);
+    MathType[] littles = {Range2d, Field2d1, function};
+    TupleType little = new TupleType(littles);
+    FunctionType little_function = new FunctionType(X, little);
+    SetType set = new SetType(Domain2d);
+    MathType[] types = {Range2d, little_function, Field2d1, Field2d2,
+                        function, set, Field2d3};
+    TupleType tuple = new TupleType(types);
+    FunctionType big_function = new FunctionType(Range2d, tuple);
+
+    // test prettyString()
+    System.out.println("prettyString for first MathType:");
+    String s1 = big_function.prettyString();
+    System.out.println(s1 + "\n");
+    MathType t1 = stringToType(s1);
+    System.out.println("stringToType for first MathType:");
+    System.out.println(t1.prettyString() + "\n");
+
+    // construct second MathType
+    RealType T = RealType.getRealType("time");
+    RealTupleType Domain1d = new RealTupleType(new RealType[] {T});
+    RealType Rxx = RealType.getRealType("Red");
+    RealType Gxx = RealType.getRealType("Green");
+    RealType Bxx = RealType.getRealType("Blue");
+    RealTupleType Range3d = new RealTupleType(new RealType[] {Rxx, Gxx, Bxx});
+    FunctionType image = new FunctionType(Domain2d, Range3d);
+    function = new FunctionType(Domain1d, image);
+
+    // test prettyString() again
+    System.out.println("prettyString for second MathType:");
+    String s2 = function.prettyString();
+    System.out.println(s2 + "\n");
+    MathType t2 = stringToType(s2);
+    System.out.println("stringToType for second MathType:");
+    System.out.println(t2.prettyString() + "\n");
+
+    // test guessMaps()
+    System.out.println("Guessing at some good mappings for this MathType...");
+    ScalarMap[] smaps = function.guessMaps(true);
+    if (smaps == null) {
+      System.out.println("Could not identify a good set of mappings!");
+    }
+    else {
+      for (int i=0; i<smaps.length; i++) {
+        ScalarType s = smaps[i].getScalar();
+        DisplayRealType ds = smaps[i].getDisplayScalar();
+        System.out.println(s.getName() + " -> " + ds.getName());
+      }
+    }
+
+    String s3 = "((Row, Col, Lev) -> Radiance)";
+    String s3s = stringToType(s3).prettyString();
+    System.out.println("s3 = \n" + s3 + "\ns3s = \n" + s3s);
+  }
+
+  /** used by guessMaps to store miscellaneous information
+      throughout findTimeFunction's recursive calls */
+  private class DataStruct {
+    /** whether this DataStruct's funcs are valid */
+    boolean fvalid = true;
+    /** The name of the RealType in the domain of the FunctionTypes in funcs */
+    String name;
+    /** FunctionType objects with 1-D domains which match name */
+    Vector funcs = new Vector();
+
+    /** constructor */
+    DataStruct(String s) { name = s; }
+  }
+
+}
diff --git a/visad/MessageEvent.java b/visad/MessageEvent.java
new file mode 100644
index 0000000..d162dcb
--- /dev/null
+++ b/visad/MessageEvent.java
@@ -0,0 +1,194 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+public class MessageEvent
+{
+  public static final int ID_GENERIC = 1;
+
+  private int id, originator;
+  private String str;
+  private RemoteData data;
+
+  /**
+   * Create a message event with the given ID
+   *
+   * @param id Message ID
+   */
+  public MessageEvent(int id)
+  {
+    this(id, -1, null, null);
+  }
+
+  /**
+   * Create a message event with the given string
+   *
+   * @param str Message <tt>String</tt>
+   */
+  public MessageEvent(String str)
+  {
+    this(ID_GENERIC, -1, str, null);
+  }
+
+  /**
+   * Create a message event with the given data
+   *
+   * @param data Message <tt>Data</tt>
+   */
+  public MessageEvent(RemoteData data)
+  {
+    this(ID_GENERIC, -1, null, data);
+  }
+
+  /**
+   * Create a message event with the given ID and string
+   *
+   * @param id Message ID
+   * @param str Message <tt>String</tt>
+   */
+  public MessageEvent(int id, String str)
+  {
+    this(id, -1, str, null);
+  }
+
+  /**
+   * Create a message event with the given ID and data
+   *
+   * @param id Message ID
+   * @param data Message <tt>Data</tt>
+   */
+  public MessageEvent(int id, RemoteData data)
+  {
+    this(id, -1, null, data);
+  }
+
+  /**
+   * Create a message event with the given string and data
+   *
+   * @param str Message <tt>String</tt>
+   * @param data Message <tt>Data</tt>
+   */
+  public MessageEvent(String str, RemoteData data)
+  {
+    this(ID_GENERIC, -1, str, data);
+  }
+
+
+  /**
+   * Create a message event with the given ID, string and data
+   *
+   * @param id Message ID
+   * @param str Message <tt>String</tt>
+   * @param data Message <tt>Data</tt>
+   */
+  public MessageEvent(int id, String str, RemoteData data)
+  {
+    this(id, -1, str, data);
+  }
+
+  /**
+   * Create a message event with the given IDs, string and data
+   *
+   * @param id Message ID
+   * @param originator Originator ID.
+   * @param str Message <tt>String</tt>
+   * @param data Message <tt>Data</tt>
+   */
+  public MessageEvent(int id, int originator, String str, RemoteData data)
+  {
+    this.id = id;
+    this.originator = originator;
+    this.str = str;
+    this.data = data;
+  }
+
+  /**
+   * Get this message's ID.
+   *
+   * @return The message ID.
+   */
+  public int getId() { return id; }
+
+  /**
+   * Get the ID of the originator of this message.
+   *
+   * @return The originator's ID.
+   */
+  public int getOriginatorId() { return originator; }
+
+  /**
+   * Get the string associated with this message.
+   *
+   * @return The message string.
+   */
+  public String getString() { return str; }
+
+  /**
+   * Get the data associated with this message.
+   *
+   * @return The message <tt>Data</tt>.
+   */
+  public RemoteData getData() { return data; }
+
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer(getClass().getName());
+
+    boolean needComma = false;
+
+    buf.append('[');
+    if (id != ID_GENERIC) {
+      if (needComma) buf.append(',');
+      buf.append("id=");
+      buf.append(id);
+      needComma = true;
+    }
+    if (originator != -1) {
+      if (needComma) buf.append(',');
+      buf.append("originator=");
+      buf.append(originator);
+      needComma = true;
+    }
+    if (str != null) {
+      if (needComma) buf.append(',');
+      buf.append("str=\"");
+      buf.append(str);
+      buf.append("\"");
+      needComma = true;
+    }
+    if (data != null) {
+      if (needComma) buf.append(',');
+      buf.append("data=");
+      try {
+        buf.append(data.local());
+      } catch(Exception e) {
+        buf.append('?');
+        buf.append(e.getMessage());
+        buf.append('?');
+      }
+      needComma = true;
+    }
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/MessageListener.java b/visad/MessageListener.java
new file mode 100644
index 0000000..5ae8e66
--- /dev/null
+++ b/visad/MessageListener.java
@@ -0,0 +1,40 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+ * Interface for objects which wish to receive VisAD messages.
+ */
+public interface MessageListener
+{
+  /**
+   * Receive a general VisAD message broadcast via
+   * <tt>DisplayImpl.sendMessage()</tt>
+   *
+   * @param msg The message
+   */
+  void receiveMessage(MessageEvent msg)
+    throws RemoteException;
+}
diff --git a/visad/MouseBehavior.java b/visad/MouseBehavior.java
new file mode 100644
index 0000000..22a0e4e
--- /dev/null
+++ b/visad/MouseBehavior.java
@@ -0,0 +1,144 @@
+//
+// MouseBehavior.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   MouseBehavior is the VisAD interface for mouse behaviors
+   for Java3D and Java2D
+*/
+
+public interface MouseBehavior {
+
+  /**
+   * Get the helper class used by this MouseBehavior.  
+   * The <CODE>MouseHelper</CODE> defines the actions taken based
+   * on <CODE>MouseEvent</CODE>s.
+   * @return  <CODE>MouseHelper</CODE> being used.
+   */
+  MouseHelper getMouseHelper();
+
+  /**
+   * Return the VisAD ray corresponding to the VisAD cursor coordinates.
+   * @param  cursor  array (x,y,z) of cursor location
+   * @return  corresponding VisADRay
+   * @see visad.VisADRay
+   * @see visad.DisplayRenderer#getCursor()
+   */
+  VisADRay cursorRay(double[] cursor);
+
+  /**
+   * Return the VisAD ray corresponding to the component coordinates.
+   * @param  screen_x  x coordinate of the component
+   * @param  screen_y  y coordinate of the component
+   * @return  corresponding VisADRay
+   * @see visad.VisADRay
+   * @see visad.LocalDisplay#getComponent()
+   */
+  VisADRay findRay(int screen_x, int screen_y);
+
+  /**
+   * Return the screen coordinates corresponding to the VisAD coordinates.
+   * @param  position  array of VisAD coordinates
+   * @return  corresponding (x, y) screen coordinates
+   */
+  int[] getScreenCoords(double[] position);
+
+  /**
+   * Multiply the two matrices together.
+   * @param  a  first matrix
+   * @param  b  second matrix
+   * @return  new resulting matrix
+   */
+  double[] multiply_matrix(double[] a, double[] b);
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scale  scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  double[] make_matrix(double rotx, double roty,
+      double rotz, double scale, double transx, double transy, double transz);
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scalex  x scaling factor
+   * @param scaley  y scaling factor
+   * @param scalez  z scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  double[] make_matrix(double rotx, double roty,
+      double rotz, double scalex, double scaley, double scalez, double transx, double transy, double transz);
+
+  /**
+   * Get the rotation, scale and translation parameters for the specified
+   * matrix.  Results are not valid for non-uniform aspect (scale).
+   * @param  rot  array to hold x,y,z rotation values
+   * @param  scale  array to hold scale value(s). If length == 1, assumes
+   *                uniform scaling.
+   * @param  trans  array to hold x,y,z translation values
+   */
+  void instance_unmake_matrix(double[] rot, double[] scale,
+                              double[] trans, double[] matrix);
+
+  /**
+   * Create a translation matrix.  no translation in Z direction (useful
+   * for 2D)
+   * @param  transx   x translation amount
+   * @param  transy   y translation amount
+   * @return  new translation matrix.  This can be used to translate
+   *          the current matrix
+   * @see #multiply_matrix(double[] a, double[] b)
+   */
+  double[] make_translate(double transx, double transy);
+
+  /**
+   * Create a translation matrix.
+   * @param  transx   x translation amount
+   * @param  transy   y translation amount
+   * @param  transz   z translation amount
+   * @return  new translation matrix.  This can be used to translate
+   *          the current matrix
+   * @see #multiply_matrix(double[] a, double[] b)
+   */
+  double[] make_translate(double transx, double transy, double transz);
+
+}
+
diff --git a/visad/MouseHelper.java b/visad/MouseHelper.java
new file mode 100644
index 0000000..2fb1cf8
--- /dev/null
+++ b/visad/MouseHelper.java
@@ -0,0 +1,884 @@
+//
+// MouseHelper.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.event.*;
+
+import java.rmi.*;
+import java.awt.*;
+
+import visad.browser.Convert;
+
+/**
+   MouseHelper is the VisAD helper class for MouseBehaviorJ3D
+   and MouseBehaviorJ2D.<p>
+
+   MouseHelper is preferred by cats everywhere.<p>
+*/
+public class MouseHelper
+  implements RendererSourceListener
+{
+
+
+
+  MouseBehavior behavior;
+
+  /** DisplayRenderer for Display */
+  DisplayRenderer display_renderer;
+
+  /** Display */
+  DisplayImpl display;
+
+  /** ProjectionControl for Display */
+  private ProjectionControl proj;
+
+  /** DataRenderer for direct manipulation */
+  protected DataRenderer direct_renderer = null;
+
+  /** matrix from ProjectionControl when mousePressed1 (left) */
+  protected double[] tstart;
+
+  /** screen location when mousePressed1 or mousePressed3 */
+  protected int start_x, start_y;
+  protected double xmul, ymul;
+  protected double xymul;
+  //protected double[] xtrans = new double[3];
+  //protected double[] ytrans = new double[3];
+
+  /** mouse in window (not used) */
+  private boolean mouseEntered;
+
+  /** ((InputEvent) event).getModifiers() when mouse pressed */
+  protected int mouseModifiers;
+
+  /** flag for 2-D mode */
+  private boolean mode2D;
+
+// start of variables for table-driven mapping from mouse buttons and
+//     keys to functons
+
+  // index values for functions
+  public static final int NONE = -1, ROTATE = 0, ZOOM = 1, TRANSLATE = 2,
+    CURSOR_TRANSLATE = 3, CURSOR_ZOOM = 4, CURSOR_ROTATE = 5, DIRECT = 6;
+
+  /* Number of functions */
+  //protected int NFUNCTIONS = 7;
+
+  // index values for mouse buttons
+  public static final int LEFT = 0, CENTER = 1, RIGHT = 2;
+
+  // actual mouse buttons pressed
+  protected boolean[] actual_button = {false, false, false};
+
+  // mouse button pressed accounting for combos
+  protected int virtual_button = -1;
+
+  // array of enables for functions
+  protected boolean[] function = {false, false, false, false, false, false, false};
+
+  // save previous function to compute function change
+  protected boolean[] old_function = {false, false, false, false, false, false, false};
+
+  // enable any two mouse buttons = the third
+  private boolean enable_combos = true;
+
+  // mapping from buttons/keys to function
+  //   function_map[button][CTRL][SHIFT] where
+  //   button = 0, 1, 2
+  //   CTRL = 0, 1
+  //   SHIFT = 0, 1
+  // initialize with defaults
+  int[][][] function_map =
+    {{{ROTATE, ZOOM}, {TRANSLATE, NONE}},
+     {{CURSOR_TRANSLATE, CURSOR_ZOOM}, {CURSOR_ROTATE, NONE}},
+     {{DIRECT, DIRECT}, {DIRECT, DIRECT}}};
+
+// end of variables for table-driven mapping from mouse buttons
+//     and keys to functons
+
+  public MouseHelper(DisplayRenderer r, MouseBehavior b) {
+    behavior = b;
+    display_renderer = r;
+    display = display_renderer.getDisplay();
+    proj = display.getProjectionControl();
+    mode2D = display_renderer.getMode2D();
+
+    // track Display's DataRenderers in case direct_renderer is removed
+    display.addRendererSourceListener(this);
+
+    //function =
+    //  new boolean[] {false, false, false, false, false, false, false};
+    //enable_combos = true;
+  }
+
+  /** 
+   * Get the MouseBehavior for this Helper
+   * @return MouseBehavior
+   */
+  public MouseBehavior getMouseBehavior() { return behavior; }
+
+  /** 
+   * Get the Display for this Helper
+   * @return Display
+   */
+  public DisplayImpl getDisplay() { return display; }
+
+  /** 
+   * Get the DisplayRenderer for this Helper
+   * @return DisplayRenderer
+   */
+  public DisplayRenderer getDisplayRenderer() { return display_renderer; }
+
+  /** 
+   * Get the ProjectionControl for this Helper
+   * @return ProjectionControl
+   */
+  public ProjectionControl getProjectionControl() { return proj; }
+
+  /** 
+   * Get whether we're in 2D mode
+   * @return true if 2D mode
+   */
+  public boolean getMode2D() { return mode2D; }
+
+  /**
+   * Process the given event treating it as a local event.
+   * @param event event to process.
+   */
+  public void processEvent(AWTEvent event) {
+    processEvent(event, VisADEvent.LOCAL_SOURCE);
+  }
+
+  // WLH 17 Aug 2000  (no longer used)
+  private boolean first = true;
+
+  /** 
+   * Process the given event, treating it as coming from a remote source
+   *  if remote flag is set.
+   * @param event event to process.
+   * @param remoteId  id of remote source.
+   */
+  public void processEvent(AWTEvent event, int remoteId) {
+
+    if (getMouseBehavior() == null) return;
+
+    // WLH 13 May 2003
+    // if (first) {
+    if (event instanceof MouseEvent &&
+        ((MouseEvent) event).getID() == MouseEvent.MOUSE_PRESSED) {
+      start_x = 0;
+      start_y = 0;
+      VisADRay start_ray = getMouseBehavior().findRay(start_x, start_y);
+      VisADRay start_ray_x = getMouseBehavior().findRay(start_x + 1, start_y);
+      VisADRay start_ray_y = getMouseBehavior().findRay(start_x, start_y + 1);
+
+      if (start_ray != null && start_ray_x != null && start_ray_y != null) {
+        double[] tstart = getProjectionControl().getMatrix();
+        double[] rot = new double[3];
+        double[] scale = new double[3];
+        double[] trans = new double[3];
+        getMouseBehavior().instance_unmake_matrix(rot, scale, trans, tstart);
+        double[] trot = getMouseBehavior().make_matrix(rot[0], rot[1], rot[2],
+                                             scale[0], scale[1], scale[2],
+                                             0.0, 0.0, 0.0);
+        double[] xmat = getMouseBehavior().make_translate(
+                           start_ray_x.position[0] - start_ray.position[0],
+                           start_ray_x.position[1] - start_ray.position[1],
+                           start_ray_x.position[2] - start_ray.position[2]);
+        double[] ymat = getMouseBehavior().make_translate(
+                           start_ray_y.position[0] - start_ray.position[0],
+                           start_ray_y.position[1] - start_ray.position[1],
+                           start_ray_y.position[2] - start_ray.position[2]);
+        double[] xmatmul = getMouseBehavior().multiply_matrix(trot, xmat);
+        double[] ymatmul = getMouseBehavior().multiply_matrix(trot, ymat);
+        getMouseBehavior().instance_unmake_matrix(rot, scale, trans, xmatmul);
+        //double xmul = trans[0];
+        xmul = trans[0];
+        getMouseBehavior().instance_unmake_matrix(rot, scale, trans, ymatmul);
+        //double ymul = trans[1];
+        ymul = trans[1];
+        xymul = Math.sqrt(xmul * xmul + ymul * ymul);
+        // System.out.println("xymul = " + xymul);
+        first = false;
+      }
+    }
+
+    if (!(event instanceof MouseEvent)) {
+      System.out.println("MouseHelper.processStimulus: non-MouseEvent");
+      return;
+    }
+
+    MouseEvent mouse_event = (MouseEvent) event;
+
+    int event_id = event.getID();
+
+    if (event_id == MouseEvent.MOUSE_ENTERED) {
+      mouseEntered = true;
+      try {
+        DisplayEvent e = new DisplayEvent(getDisplay(),
+          DisplayEvent.MOUSE_ENTERED, mouse_event, remoteId);
+        getDisplay().notifyListeners(e);
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+      return;
+    }
+    else if (event_id == MouseEvent.MOUSE_EXITED) {
+      mouseEntered = false;
+      try {
+        DisplayEvent e = new DisplayEvent(getDisplay(),
+          DisplayEvent.MOUSE_EXITED, mouse_event, remoteId);
+        getDisplay().notifyListeners(e);
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+      return;
+    }
+    else if (event_id == MouseEvent.MOUSE_MOVED) {
+      try {
+        DisplayEvent e = new DisplayEvent(getDisplay(),
+          DisplayEvent.MOUSE_MOVED, mouse_event, remoteId);
+        getDisplay().notifyListeners(e);
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+      return;
+    }
+    else if (event_id == MouseEvent.MOUSE_PRESSED ||
+             event_id == MouseEvent.MOUSE_RELEASED) {
+      int m = ((InputEvent) event).getModifiers();
+      int m1 = m & InputEvent.BUTTON1_MASK;
+      int m2 = m & InputEvent.BUTTON2_MASK;
+      int m3 = m & InputEvent.BUTTON3_MASK;
+      int mctrl = m & InputEvent.CTRL_MASK;
+      int mshift = m & InputEvent.SHIFT_MASK;
+
+      if (event_id == MouseEvent.MOUSE_PRESSED) {
+        getDisplay().updateBusyStatus();
+        if (m1 != 0) {
+          actual_button[LEFT] = true;
+        }
+        if (m2 != 0) {
+          actual_button[CENTER] = true;
+        }
+        if (m3 != 0) {
+          actual_button[RIGHT] = true;
+        }
+        mouseModifiers = m;
+      }
+      else { // event_id == MouseEvent.MOUSE_RELEASED
+        getDisplay().updateBusyStatus();
+        if (m1 != 0) {
+          actual_button[LEFT] = false;
+        }
+        if (m2 != 0) {
+          actual_button[CENTER] = false;
+        }
+        if (m3 != 0) {
+          actual_button[RIGHT] = false;
+        }
+        mouseModifiers = 0;
+      }
+
+      // compute button combos
+      int n = 0, sum = 0;
+      for (int i=0; i<3; i++) {
+        if (actual_button[i]) {
+          n++;
+          sum += i;
+        }
+      }
+      if (n == 1) {
+        virtual_button = sum;
+      }
+      else if (n == 2 && getEnableCombos()) {
+        virtual_button = 3 - sum;
+      }
+      else { // n == 0 || n == 3 || (n == 2 && !getEnableCombos())
+        virtual_button = -1;
+      }
+  
+      // compute old and new functions
+      for (int i=0; i<function.length; i++) {
+        old_function[i] = function[i];
+        function[i] = false;
+      }
+
+      int vctrl = (mctrl == 0) ? 0 : 1;
+      int vshift = (mshift == 0) ? 0 : 1;
+      int f = (virtual_button < 0) ? -1 :
+              function_map[virtual_button][vctrl][vshift];
+
+      if (f >= 0) function[f] = true;
+
+      boolean cursor_off = enableFunctions((MouseEvent) event);
+
+      if (event_id == MouseEvent.MOUSE_PRESSED) {
+        try {
+          DisplayEvent e = new DisplayEvent(getDisplay(),
+            DisplayEvent.MOUSE_PRESSED, mouse_event, remoteId);
+          getDisplay().notifyListeners(e);
+        }
+        catch (VisADException e) {
+        }
+        catch (RemoteException e) {
+        }
+        if (m1 != 0) {
+          try {
+            DisplayEvent e = new DisplayEvent(getDisplay(),
+              DisplayEvent.MOUSE_PRESSED_LEFT, mouse_event, remoteId);
+            getDisplay().notifyListeners(e);
+          }
+          catch (VisADException e) {
+          }
+          catch (RemoteException e) {
+          }
+        }
+        if (m2 != 0) {
+          try {
+            DisplayEvent e = new DisplayEvent(getDisplay(),
+              DisplayEvent.MOUSE_PRESSED_CENTER, mouse_event, remoteId);
+            getDisplay().notifyListeners(e);
+          }
+          catch (VisADException e) {
+          }
+          catch (RemoteException e) {
+          }
+        }
+        if (m3 != 0) {
+          try {
+            DisplayEvent e = new DisplayEvent(getDisplay(),
+              DisplayEvent.MOUSE_PRESSED_RIGHT, mouse_event, remoteId);
+            getDisplay().notifyListeners(e);
+          }
+          catch (VisADException e) {
+          }
+          catch (RemoteException e) {
+          }
+        }
+      }
+      else { // event_id == MouseEvent.MOUSE_RELEASED
+        try {
+          DisplayEvent e = new DisplayEvent(getDisplay(),
+            DisplayEvent.MOUSE_RELEASED, mouse_event, remoteId);
+          getDisplay().notifyListeners(e);
+        }
+        catch (VisADException e) {
+        }
+        catch (RemoteException e) {
+        }
+        if (m1 != 0) {
+          try {
+            DisplayEvent e = new DisplayEvent(getDisplay(),
+              DisplayEvent.MOUSE_RELEASED_LEFT, mouse_event, remoteId);
+            getDisplay().notifyListeners(e);
+          }
+          catch (VisADException e) {
+          }
+          catch (RemoteException e) {
+          }
+        }
+        if (m2 != 0) {
+          try {
+            DisplayEvent e = new DisplayEvent(getDisplay(),
+              DisplayEvent.MOUSE_RELEASED_CENTER, mouse_event, remoteId);
+            getDisplay().notifyListeners(e);
+          }
+          catch (VisADException e) {
+          }
+          catch (RemoteException e) {
+          }
+        }
+        if (m3 != 0) {
+          try {
+            DisplayEvent e = new DisplayEvent(getDisplay(),
+              DisplayEvent.MOUSE_RELEASED_RIGHT, mouse_event, remoteId);
+            getDisplay().notifyListeners(e);
+          }
+          catch (VisADException e) {
+          }
+          catch (RemoteException e) {
+          }
+        }
+      }
+      if (cursor_off) getDisplayRenderer().setCursorOn(false);
+    }
+    else if (event_id == MouseEvent.MOUSE_DRAGGED) {
+      handleMouseDragged(mouse_event, remoteId);
+      try {
+        DisplayEvent e = new DisplayEvent(getDisplay(),
+          DisplayEvent.MOUSE_DRAGGED, mouse_event, remoteId);
+        getDisplay().notifyListeners(e);
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+    }
+
+  }
+
+  protected void handleMouseDragged(MouseEvent event, int remoteId) {
+      MouseBehavior mouseBehavior = getMouseBehavior();
+      boolean cursor = function[CURSOR_TRANSLATE] ||
+                       function[CURSOR_ZOOM] ||
+                       function[CURSOR_ROTATE];
+
+      boolean matrix = function[ROTATE] ||
+                       function[ZOOM] ||
+                       function[TRANSLATE];
+
+      if (cursor || matrix || function[DIRECT]) {
+        getDisplay().updateBusyStatus();
+
+        Dimension d = event.getComponent().getSize();
+        int current_x = event.getX();
+        int current_y = event.getY();
+
+        if (matrix) {
+          double[] t1 = null;
+          double[] t2 = null;
+          if (function[ZOOM]) {
+            // current_y -> scale
+            double scale =
+              Math.exp((start_y-current_y) / (double) d.height);
+            t1 = getMouseBehavior().make_matrix(0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0);
+          }
+          if (function[TRANSLATE]) {
+            // current_x, current_y -> translate
+            // WLH 9 Aug 2000
+            double transx = xmul * (start_x - current_x);
+            double transy = ymul * (start_y - current_y);
+            // System.out.println("xmul = " + xmul + " ymul = " + ymul);
+            // System.out.println("transx = " + transx + " transy = " + transy);
+            t1 = getMouseBehavior().make_translate(-transx, -transy);
+          }
+
+          double [] myMatrix = tstart;
+          if (function[ROTATE]) {
+            if (getMode2D()) {
+              double transx = xmul * (start_x - current_x);
+              double transy = ymul * (start_y - current_y);
+              t1 = getMouseBehavior().make_translate(-transx, -transy);
+            }
+            else {
+              // don't do 3-D rotation in 2-D mode
+              double angley =
+                - (current_x - start_x) * 100.0 / (double) d.width;
+              double anglex =
+                - (current_y - start_y) * 100.0 / (double) d.height;
+              double[] transA         = { 0.0, 0.0, 0.0 };
+              double[] rotA           = { 0.0, 0.0, 0.0 };
+              double[] scaleA         = { 0.0, 0.0, 0.0 };
+              mouseBehavior.instance_unmake_matrix(rotA, scaleA, transA, myMatrix);
+
+              if(display_renderer.getScaleRotation()) {
+                  angley = angley/scaleA[0];
+                  anglex = anglex/scaleA[0];
+              }
+
+              if(display_renderer.getRotateAboutCenter()) {
+                  myMatrix = mouseBehavior.multiply_matrix(mouseBehavior.make_translate(-transA[0], -transA[1], -transA[2]), myMatrix);
+                  t2 = mouseBehavior.make_translate(transA[0], transA[1], transA[2]);
+              }
+
+              t1 = mouseBehavior.make_matrix(anglex, angley,
+                0.0, 1.0, 0.0, 0.0, 0.0);
+            }
+          }
+
+
+          if (t1 != null) {
+            t1 = mouseBehavior.multiply_matrix(t1, myMatrix);
+
+            if(t2!=null) {
+                t1 = mouseBehavior.multiply_matrix(t2,t1);
+            }
+
+
+            try {
+              getProjectionControl().setMatrix(t1);
+            } catch (VisADException e) {}
+            catch (RemoteException e) {}
+          }
+          return;
+        } // end if (matrix)
+
+
+
+        if (function[CURSOR_ZOOM]) {
+          if (!getMode2D()) {
+            // don't do cursor Z in 2-D mode
+            // current_y -> 3-D cursor Z
+            float diff =
+              (start_y - current_y) * 4.0f / (float) d.height;
+            getDisplayRenderer().drag_depth(diff);
+          }
+        }
+        if (function[CURSOR_ROTATE]) {
+          if (!getMode2D()) {
+            // don't do 3-D rotation in 2-D mode
+            double angley =
+              - (current_x - start_x) * 100.0 / (double) d.width;
+            double anglex =
+              - (current_y - start_y) * 100.0 / (double) d.height;
+            double[] t1 = getMouseBehavior().make_matrix(anglex, angley,
+              0.0, 1.0, 0.0, 0.0, 0.0);
+            t1 = getMouseBehavior().multiply_matrix(t1, tstart);
+            try {
+              getProjectionControl().setMatrix(t1);
+            }
+            catch (VisADException e) {
+            }
+            catch (RemoteException e) {
+            }
+          }
+        }
+        if(function[CURSOR_TRANSLATE]) {
+          // current_x, current_y -> 3-D cursor X and Y
+          VisADRay cursor_ray = getMouseBehavior().findRay(current_x, current_y);
+          if (cursor_ray != null) {
+            getDisplayRenderer().drag_cursor(cursor_ray, false);
+          }
+        }
+
+        if (function[DIRECT]) {
+          if (direct_renderer != null) {
+            VisADRay direct_ray = getMouseBehavior().findRay(current_x, current_y);
+            if (direct_ray != null) {
+              direct_renderer.setLastMouseModifiers(mouseModifiers);
+              direct_renderer.drag_direct(direct_ray, false, mouseModifiers);
+            }
+          }
+        }
+      }
+  }
+
+  /**
+   * Enable the functions for this mouse helper
+   * @param event  The MouseEvent
+   */
+  protected boolean enableFunctions(MouseEvent event) {
+
+    boolean cursor_off = false;
+
+    if (event == null) {
+      for (int i=0; i<function.length; i++) {
+        old_function[i] = function[i];
+        function[i] = false;
+      }
+    }
+
+    // compute old and new cursor and matrix enables
+    boolean cursor = function[CURSOR_TRANSLATE] ||
+                     function[CURSOR_ZOOM] ||
+                     function[CURSOR_ROTATE];
+    boolean old_cursor = old_function[CURSOR_TRANSLATE] ||
+                         old_function[CURSOR_ZOOM] ||
+                         old_function[CURSOR_ROTATE];
+
+    boolean matrix = function[ROTATE] ||
+                     function[ZOOM] ||
+                     function[TRANSLATE];
+    boolean old_matrix = old_function[ROTATE] ||
+                         old_function[ZOOM] ||
+                         old_function[TRANSLATE];
+
+    // disable functions
+    if (old_cursor && !cursor) {
+      // getDisplayRenderer().setCursorOn(false);
+      cursor_off = true;
+    }
+
+    if (old_function[DIRECT] && !function[DIRECT]) {
+      getDisplayRenderer().setDirectOn(false);
+      if (direct_renderer != null) {
+        direct_renderer.release_direct();
+        direct_renderer = null;
+      }
+
+    }
+
+    // enable functions
+    if (matrix && !old_matrix) {
+
+      start_x = ((MouseEvent) event).getX();
+      start_y = ((MouseEvent) event).getY();
+
+      // WLH 9 Aug 2000
+      VisADRay start_ray = getMouseBehavior().findRay(start_x, start_y);
+      VisADRay start_ray_x = getMouseBehavior().findRay(start_x + 1, start_y);
+      VisADRay start_ray_y = getMouseBehavior().findRay(start_x, start_y + 1);
+
+      tstart = getProjectionControl().getMatrix();
+      // print_matrix("tstart", tstart);
+      double[] rot = new double[3];
+      double[] scale = new double[3];
+      double[] trans = new double[3];
+      getMouseBehavior().instance_unmake_matrix(rot, scale, trans, tstart);
+      double sts = scale[0];
+      double[] trot = getMouseBehavior().make_matrix(rot[0], rot[1], rot[2],
+                                           scale[0], scale[1], scale[2],
+                                           0.0, 0.0, 0.0);
+      // print_matrix("trot", trot);
+
+      // WLH 17 Aug 2000
+      double[] xmat = getMouseBehavior().make_translate(
+                         start_ray_x.position[0] - start_ray.position[0],
+                         start_ray_x.position[1] - start_ray.position[1],
+                         start_ray_x.position[2] - start_ray.position[2]);
+      double[] ymat = getMouseBehavior().make_translate(
+                         start_ray_y.position[0] - start_ray.position[0],
+                         start_ray_y.position[1] - start_ray.position[1],
+                         start_ray_y.position[2] - start_ray.position[2]);
+      double[] xmatmul = getMouseBehavior().multiply_matrix(trot, xmat);
+      double[] ymatmul = getMouseBehavior().multiply_matrix(trot, ymat);
+/*
+      print_matrix("xmat", xmat);
+      print_matrix("ymat", ymat);
+      print_matrix("xmatmul", xmatmul);
+      print_matrix("ymatmul", ymatmul);
+*/
+      getMouseBehavior().instance_unmake_matrix(rot, scale, trans, xmatmul);
+      xmul = trans[0];
+      getMouseBehavior().instance_unmake_matrix(rot, scale, trans, ymatmul);
+      ymul = trans[1];
+
+      // horrible hack, WLH 17 Aug 2000
+      if (getMouseBehavior() instanceof visad.java2d.MouseBehaviorJ2D) {
+        double factor = xymul / Math.sqrt(xmul * xmul + ymul * ymul);
+        xmul *= factor;
+        ymul *= factor;
+
+        xmul = Math.abs(xmul);
+        ymul = -Math.abs(ymul);
+      }
+/*
+      System.out.println("xmul = " + Convert.shortString(xmul) +
+                         " ymul = " + Convert.shortString(ymul) +
+                         " scale = " + Convert.shortString(sts));
+*/
+
+    } // end if (matrix && !old_matrix)
+
+
+    if (cursor && !old_cursor) {
+
+      // turn cursor on whenever mouse button2 pressed
+      getDisplayRenderer().setCursorOn(true);
+
+      start_x = ((MouseEvent) event).getX();
+      start_y = ((MouseEvent) event).getY();
+
+      tstart = getProjectionControl().getMatrix();
+    }
+
+    if (function[CURSOR_TRANSLATE] && !old_function[CURSOR_TRANSLATE]) {
+      VisADRay cursor_ray = getMouseBehavior().findRay(start_x, start_y);
+      if (cursor_ray != null) {
+        getDisplayRenderer().drag_cursor(cursor_ray, true);
+      }
+    }
+
+    if (function[CURSOR_ZOOM] && !old_function[CURSOR_ZOOM]) {
+      if (!getMode2D()) {
+        // don't do cursor Z in 2-D mode
+        // current_y -> 3-D cursor Z
+        VisADRay cursor_ray =
+          getMouseBehavior().cursorRay(getDisplayRenderer().getCursor());
+        getDisplayRenderer().depth_cursor(cursor_ray);
+      }
+    }
+
+    if (function[DIRECT] && !old_function[DIRECT]) {
+      if (getDisplayRenderer().anyDirects()) {
+        int current_x = ((MouseEvent) event).getX();
+        int current_y = ((MouseEvent) event).getY();
+        VisADRay direct_ray =
+          getMouseBehavior().findRay(current_x, current_y);
+        if (direct_ray != null) {
+          direct_renderer =
+            getDisplayRenderer().findDirect(direct_ray, mouseModifiers);
+          if (direct_renderer != null) {
+            getDisplayRenderer().setDirectOn(true);
+            direct_renderer.setLastMouseModifiers(mouseModifiers);
+            direct_renderer.drag_direct(direct_ray, true,
+              mouseModifiers);
+          }
+        }
+      }
+    }
+    return cursor_off;
+  }
+
+  /** 
+   * Enable/disable the interpretation of any pair of mouse buttons
+   * as the third button.
+   * @param  e  enable/disable. If true (default), interpret any pair 
+   *            of mouse buttons as the third button.
+   */
+  public void setEnableCombos(boolean e) {
+    enable_combos = e;
+    enableFunctions(null);
+  }
+
+  /**
+   * Get whether the mouse button combos are enabled
+   * @return true if any pair of mouse buttons is interpreted as the third
+   */
+  public boolean getEnableCombos() { return enable_combos; }
+
+  /** 
+   * Set mapping from (button, ctrl, shift) to function.
+   *
+   *  <pre>
+   *  map[button][ctrl][shift] =
+   *    MouseHelper.NONE               for no function
+   *    MouseHelper.ROTATE             for box rotate
+   *    MouseHelper.ZOOM               for box zoom
+   *    MouseHelper.TRANSLATE          for box translate
+   *    MouseHelper.CURSOR_TRANSLATE   for cursor translate
+   *    MouseHelper.CURSOR_ZOOM        for cursor on Z axis (3-D only)
+   *    MouseHelper.CURSOR_ROTATE      for box rotate with cursor
+   *    MouseHelper.DIRECT             for direct manipulate
+   *  where button = 0 (left), 1 (center), 2 (right)
+   *  ctrl = 0 (CTRL key not pressed), 1 (CTRL key pressed)
+   *  shift = 0 (SHIFT key not pressed), 1 (SHIFT key pressed)
+   *
+   *  Note some direct manipulation DataRenderers test the status of
+   *  CTRL and SHIFT keys, so it is advisable that the DIRECT function
+   *  be invariant to the state of ctrl and shift in the map array.
+   *
+   *  For example, to set the left mouse button for direct
+   *  manipulation, and the center button for box rotation
+   *  (only without shift or control):
+   *  mouse_helper.setFunctionMap(new int[][][]
+   *    {{{MouseHelper.DIRECT, MouseHelper.DIRECT},
+   *      {MouseHelper.DIRECT, MouseHelper.DIRECT}},
+   *     {{MouseHelper.ROTATE, MouseHelper.NONE},
+   *      {MouseHelper.NONE, MouseHelper.NONE}},
+   *     {{MouseHelper.NONE, MouseHelper.NONE},
+   *      {MouseHelper.NONE, MouseHelper.NONE}}});
+   *  </pre>
+   * @param  map map of functions.  map must be int[3][2][2]
+   * @throws VisADException  bad map
+   */
+  public void setFunctionMap(int[][][] map) throws VisADException {
+    if (map == null || map.length != 3) {
+      throw new DisplayException("bad map array");
+    }
+    for (int i=0; i<3; i++) {
+      if (map[i] == null || map[i].length != 2) { 
+        throw new DisplayException("bad map array");
+      }
+      for (int j=0; j<2; j++) {
+        if (map[i][j] == null || map[i][j].length != 2) {
+          throw new DisplayException("bad map array");
+        }
+        for (int k=0; k<2; k++) {
+          if (map[i][j][k] >= function.length) {
+            throw new DisplayException("bad map array value: " + map[i][j][k]);
+          }
+        }
+      }
+    }
+    for (int i=0; i<3; i++) {
+      for (int j=0; j<2; j++) {
+        for (int k=0; k<2; k++) {
+          function_map[i][j][k] = map[i][j][k];
+        }
+      }
+    }
+    enableFunctions(null);
+  }
+
+
+  /**
+   * Print out a readable form of a matrix.  Useful for
+   * debugging.
+   * @param title  title to prepend to output.
+   * @param m  matrix to print.
+   */
+  public void print_matrix(String title, double[] m) {
+    if (getMouseBehavior() == null) return;
+    double[] rot = new double[3];
+    double[] scale = new double[3];
+    double[] trans = new double[3];
+    getMouseBehavior().instance_unmake_matrix(rot, scale, trans, m);
+    StringBuffer buf = new StringBuffer(title);
+    buf.append(" = (");
+    buf.append(Convert.shortString(rot[0]));
+    buf.append(", ");
+    buf.append(Convert.shortString(rot[1]));
+    buf.append(", ");
+    buf.append(Convert.shortString(rot[2]));
+    buf.append("), ");
+    if (scale[0] == scale[1] && scale[0] == scale[2]) {
+      buf.append(Convert.shortString(scale[0]));
+      buf.append(", (");
+    } else {
+      buf.append("(");
+      buf.append(Convert.shortString(scale[0]));
+      buf.append(", ");
+      buf.append(Convert.shortString(scale[1]));
+      buf.append(", ");
+      buf.append(Convert.shortString(scale[2]));
+      buf.append("), (");
+    }
+    buf.append(Convert.shortString(trans[0]));
+    buf.append(", ");
+    buf.append(Convert.shortString(trans[1]));
+    buf.append(", ");
+    buf.append(Convert.shortString(trans[2]));
+    buf.append(")");
+    System.out.println(buf.toString());
+  }
+
+  /**
+   * Implementation for RendererSourceListener.  Notifies that the
+   * renderer has been deleted.
+   * @param renderer DataRenderer that was deleted.
+   */
+  public void rendererDeleted(DataRenderer renderer)
+  {
+    if (direct_renderer != null) {
+      if (direct_renderer == renderer || direct_renderer.equals(renderer)) {
+        direct_renderer = null;
+      }
+    }
+  }
+
+
+
+
+}
diff --git a/visad/NOTICE b/visad/NOTICE
new file mode 100644
index 0000000..0eaebbf
--- /dev/null
+++ b/visad/NOTICE
@@ -0,0 +1,482 @@
+		  GNU LIBRARY GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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., 59 Temple Place - Suite 330, Boston,
+    MA 02111-1307, 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/visad/OffsetUnit.java b/visad/OffsetUnit.java
new file mode 100644
index 0000000..b323cb5
--- /dev/null
+++ b/visad/OffsetUnit.java
@@ -0,0 +1,717 @@
+//
+// OffsetUnit.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * A class that represents a scaled unit with an offset.
+ * 
+ * Instances are immutable.
+ * 
+ * @author Steven R. Emmerson
+ * 
+ *         This is part of Steve Emerson's Unit package that has been
+ *         incorporated into VisAD.
+ * 
+ *         Instances are immutable.
+ */
+public final class OffsetUnit extends Unit implements Serializable {
+    private static final long       serialVersionUID    = 1L;
+
+    /**
+     * The associated (unoffset) underlying unit.
+     */
+    final Unit                      underUnit;
+
+    /**
+     * The offset for this unit (e.g. 273.15 for the celsius unit when the
+     * kelvin unit is associated scaled unit).
+     */
+    final double                    offset;
+
+    /**
+     * The date formatter.
+     * 
+     * @serial
+     */
+    private static SimpleDateFormat dateFormat;
+
+    /**
+     * The arbitrary time origin.
+     * 
+     * @serial
+     */
+    private static double           offsetUnitOrigin;
+
+    /**
+     * The millisecond unit.
+     */
+    private static Unit             millisecond;
+
+    static {
+        try {
+            dateFormat = (SimpleDateFormat) DateFormat.getDateInstance(
+                    DateFormat.SHORT, Locale.US);
+            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+            dateFormat.applyPattern("yyyy-MM-dd HH:mm:ss.SSS 'UTC'");
+            final Calendar calendar = new GregorianCalendar(TimeZone
+                    .getTimeZone("UTC"));
+            calendar.clear();
+            calendar.set(2001, 0, 1, 0, 0, 0); // data.netcdf.units.UnitParser
+            offsetUnitOrigin = calendar.getTime().getTime();
+            millisecond = SI.second.scale(0.001).clone("ms");
+        }
+        catch (final Exception e) {
+            System.err
+                    .println("OffsetUnit.<clinit>: Couldn't initialize class: "
+                            + e);
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Construct an offset, dimensionless unit. The identifier will be empty.
+     * 
+     * @param offset
+     *            The amount of offset.
+     */
+    public OffsetUnit(final double offset) {
+        this(offset, "");
+    }
+
+    /**
+     * Construct an offset, dimensionless unit with an identifier.
+     * 
+     * @param offset
+     *            The amount of offset.
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * 
+     */
+    public OffsetUnit(final double offset, final String identifier) {
+        super(identifier);
+        this.offset = offset;
+        underUnit = new ScaledUnit(1.0);
+    }
+
+    /**
+     * Construct an offset unit from another unit. The identifier will be that
+     * of the base unit if the offset is zero; otherwise, the identifier will be
+     * <code>null</code>.
+     * 
+     * @param offset
+     *            The amount of offset.
+     * @param unit
+     *            The other unit.
+     */
+    public OffsetUnit(final double offset, final Unit unit) {
+        this(offset, unit, null);
+    }
+
+    /**
+     * Construct an offset unit from another unit and an identifier.
+     * 
+     * @param offset
+     *            The amount of offset.
+     * @param unit
+     *            The other unit.
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     */
+    public OffsetUnit(final double offset, final Unit unit,
+            final String identifier) {
+        super(identifier != null
+                ? identifier
+                : offset == 0
+                        ? unit.getIdentifier()
+                        : null);
+        this.offset = offset;
+        underUnit = unit;
+    }
+
+    /**
+     * Returns an instance based on an offset and an underlying unit.
+     * 
+     * @param offset
+     *            The offset.
+     * @param unit
+     *            The underlying unit.
+     * @return An instance corresponding to the input.
+     */
+    static Unit getInstance(final double offset, final Unit unit) {
+        return offset == 0
+                ? unit
+                : new OffsetUnit(offset, unit);
+    }
+
+    /**
+     * <p>
+     * Indicates if this instance is dimensionless. A unit is dimensionless if
+     * it is a measure of a dimensionless quantity like angle or concentration.
+     * Examples of dimensionless units include radian, degree, steradian, and
+     * "g/kg".
+     * </p>
+     * 
+     * @return True if an only if this unit is dimensionless.
+     */
+    @Override
+    public boolean isDimensionless() {
+        return underUnit.isDimensionless();
+    }
+
+    /**
+     * Indicates if this instance is a unit of time.
+     * 
+     * @return <code>true</code> if and only if this instance is a unit of time.
+     */
+    protected boolean isTime() {
+        return SI.second.isConvertible(underUnit);
+    }
+
+    /**
+     * Clones this unit, changing the identifier.
+     * 
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * @return A unit equal to this unit but with the given identifier.
+     */
+    @Override
+    protected Unit protectedClone(final String identifier) {
+        return new OffsetUnit(0, this, identifier);
+    }
+
+    @Override
+    public Unit scale(final double amount) throws UnitException {
+        return OffsetUnit.getInstance(offset / amount, underUnit.scale(amount));
+    }
+
+    @Override
+    public Unit shift(final double offset) throws UnitException {
+        return OffsetUnit.getInstance(offset + this.offset, underUnit);
+    }
+
+    @Override
+    public Unit log(final double base) throws UnitException {
+        return LogarithmicUnit.getInstance(base, this);
+    }
+
+    /**
+     * Raises an offset unit to a power. Raising an offset unit to a power is
+     * equivalent to first stripping-off the offset amount and then raising the
+     * resulting non-offset unit to the power.
+     * 
+     * @param power
+     *            The power to raise this unit by.
+     * @return A corresponding unit.
+     * @throws UnitException
+     *             if the underlying unit can't be raised to the given power.
+     */
+    @Override
+    public Unit pow(final int power) throws UnitException {
+        return underUnit.pow(power);
+    }
+
+    /**
+     * Raises an offset unit to a power. Raising an offset unit to a power is
+     * equivalent to first stripping-off the offset amount and then raising the
+     * resulting non-offset unit to the power.
+     * 
+     * @param power
+     *            The power to raise this unit by.
+     * @return A corresponding unit.
+     * @throws UnitException
+     *             if the underlying unit can't be raised to the given power.
+     */
+    @Override
+    public Unit pow(final double power) throws UnitException {
+        return underUnit.pow(power);
+    }
+
+    /**
+     * Returns the N-th root of this unit. Taking the root of an offset unit is
+     * equivalent to first stripping-off the offset amount and then taking the
+     * root of the resulting non-offset unit.
+     * 
+     * @param root
+     *            The root to take (e.g. 2 means square root). May not be zero.
+     * @return The unit corresponding to the <code>root</code>-th root of this
+     *         unit.
+     * @throws IllegalArgumentException
+     *             The root value is zero or the resulting unit would have a
+     *             non-integral unit dimension.
+     * @throws UnitException
+     *             if the underlying unit can't have the given root taken.
+     */
+    @Override
+    public Unit root(final int root) throws IllegalArgumentException,
+            UnitException {
+        return underUnit.root(root);
+    }
+
+    /**
+     * Return the definition of this unit.
+     * 
+     * @return The definition of this unit (e.g. "K @ 273.15" for degree
+     *         celsius).
+     */
+    @Override
+    public String getDefinition() {
+        String definition;
+        String scaledString = underUnit.toString();
+
+        if (scaledString.indexOf(' ') != -1) {
+            scaledString = "(" + scaledString + ")";
+        }
+        if (!isTime()) {
+            definition = scaledString + " @ " + offset;
+        }
+        else {
+            try {
+                definition = scaledString
+                        + " since "
+                        + dateFormat
+                                .format(new Date((long) (millisecond.toThis(
+                                        offset, underUnit) + offsetUnitOrigin)));
+            }
+            catch (final UnitException e) {
+                definition = e.toString();
+            } // can't happen
+        }
+        return definition;
+    }
+
+    /**
+     * Multiply an offset unit by another unit.
+     * 
+     * @param that
+     *            The unit with which to multiply this unit.
+     * @return A unit equal to this instance multiplied by the given unit.
+     * @exception UnitException
+     *                Can't multiply units.
+     */
+    @Override
+    public Unit multiply(final Unit that) throws UnitException {
+        return that.multiply(underUnit);
+    }
+
+    /**
+     * Divide an offset unit by another unit.
+     * 
+     * @param that
+     *            The unit to divide into this unit.
+     * @return A unit equal to this instance divided by the given unit.
+     * @exception UnitException
+     *                Can't divide units.
+     */
+    @Override
+    public Unit divide(final Unit that) throws UnitException {
+        return that.divideInto(underUnit);
+    }
+
+    /**
+     * Divide an offset unit into another unit.
+     * 
+     * @param that
+     *            The unit to be divide by this unit.
+     * @return The quotient of the two units.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             Meaningless operation.
+     */
+    @Override
+    protected Unit divideInto(final Unit that) throws UnitException {
+        return that.divide(underUnit);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        double[] newValues;
+        if (equals(that) || that instanceof PromiscuousUnit) {
+            newValues = (copy)
+                    ? (double[]) values.clone()
+                    : values;
+        }
+        else {
+            newValues = that.toThat(values, underUnit, copy);
+            for (int i = 0; i < newValues.length; ++i) {
+                if (newValues[i] == newValues[i]) {
+                    newValues[i] -= offset;
+                }
+            }
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false, convert values in place.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        float[] newValues;
+        if (equals(that) || that instanceof PromiscuousUnit) {
+            newValues = (copy)
+                    ? (float[]) values.clone()
+                    : values;
+        }
+        else {
+            newValues = that.toThat(values, underUnit, copy);
+            for (int i = 0; i < newValues.length; ++i) {
+                if (newValues[i] == newValues[i]) {
+                    newValues[i] -= offset;
+                }
+            }
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double values[], final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float values[], final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return a
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double values[], final Unit that,
+            final boolean copy) throws UnitException {
+        double[] newValues = (copy)
+                ? (double[]) values.clone()
+                : values;
+        if (!(equals(that) || that instanceof PromiscuousUnit)) {
+            for (int i = 0; i < newValues.length; ++i) {
+                if (newValues[i] == newValues[i]) {
+                    newValues[i] += offset;
+                }
+            }
+            newValues = that.toThis(newValues, underUnit, copy);
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return a
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float values[], final Unit that,
+            final boolean copy) throws UnitException {
+        float[] newValues = (copy)
+                ? (float[]) values.clone()
+                : values;
+        if (!(equals(that) || that instanceof PromiscuousUnit)) {
+            for (int i = 0; i < newValues.length; ++i) {
+                if (newValues[i] == newValues[i]) {
+                    newValues[i] += offset;
+                }
+            }
+            newValues = that.toThis(newValues, underUnit, copy);
+        }
+        return newValues;
+    }
+
+    /**
+     * Gets the absolute unit of this unit. An interval in the underlying
+     * physical quantity has the same numeric value in an absolute unit of a
+     * unit as in the unit itself -- but an absolute unit is always referenced
+     * to the physical origin of the underlying physical quantity. For example,
+     * the absolute unit corresponding to degrees celsius is degrees kelvin --
+     * and calling this method on a degrees celsius unit obtains a degrees
+     * kelvin unit.
+     * 
+     * @return The absolute unit corresponding to this unit.
+     */
+    @Override
+    public Unit getAbsoluteUnit() {
+        return underUnit.getAbsoluteUnit();
+    }
+
+    /**
+     * Indicate whether this unit is convertible with another unit. If one unit
+     * is convertible with another, then the <code>toThis(...)</code>/ and
+     * <code>toThat(...)</code> methods will not throw a UnitException. Unit A
+     * is convertible with unit B if and only if unit B is convertible with unit
+     * A; hence, calling-order is irrelevant.
+     * 
+     * @param unit
+     *            The other unit.
+     * @return True if and only if this unit is convertible with the other unit.
+     */
+    @Override
+    public boolean isConvertible(final Unit unit) {
+        return underUnit.isConvertible(unit);
+    }
+
+    private static void myAssert(final boolean assertion) {
+        if (!assertion) {
+            throw new AssertionError();
+        }
+    }
+
+    private static void myAssert(final Unit have, final Unit expect) {
+        if (!have.equals(expect)) {
+            throw new AssertionError(have.toString() + " != " + expect);
+        }
+    }
+
+    private static void myAssert(final double have, final double expect) {
+        if (have != expect) {
+            throw new AssertionError("" + have + " != " + expect);
+        }
+    }
+
+    private static void myAssert(final double[] have, final double[] expect) {
+        if (!Arrays.equals(have, expect)) {
+            throw new AssertionError("" + have + " != " + expect);
+        }
+    }
+
+    /**
+     * Test this class.
+     * 
+     * @param args
+     *            Arguments (ignored).
+     * @exception UnitException
+     *                A problem occurred.
+     */
+    public static void main(final String[] args) throws UnitException {
+        final BaseUnit degK = SI.kelvin;
+        final Unit degC = new OffsetUnit(273.15, degK);
+        final ScaledUnit degR = new ScaledUnit(1 / 1.8, degK);
+        final Unit degF = new OffsetUnit(459.67, degR);
+
+        myAssert(degC, degC);
+        myAssert(!degC.equals(degK));
+        myAssert(!degC.equals(degF));
+        myAssert(degC.isConvertible(degC));
+        myAssert(degC.isConvertible(degK));
+        myAssert(degC.isConvertible(degF));
+        myAssert(degF, degF);
+
+        myAssert(degF.toThis(0, degC), degC.toThat(0, degF));
+        myAssert(degC.toThis(32, degF), degF.toThat(32, degC));
+        myAssert(degC.toThis(degF.toThis(0, degC), degF), 0);
+        myAssert(degC.toThat(degF.toThat(32, degC), degF), 32);
+
+        double[] values = { 0, 100 };
+
+        myAssert(degF.toThis(values, degC), degC.toThat(values, degF));
+        myAssert(degC.toThis(degF.toThis(values, degC), degF), values);
+
+        values = new double[] { 32, 212 };
+
+        myAssert(degC.toThis(values, degF), degF.toThat(values, degC));
+
+        myAssert(degF.pow(2), degR.pow(2));
+        myAssert(degF.multiply(degC), degC.multiply(degF));
+        myAssert(degF.multiply(degC), degR.multiply(degK));
+        myAssert(degF.divide(degC), degR.divide(degK));
+        myAssert(degC.divide(degF), degK.divide(degR));
+
+        System.out.println("Done");
+    }
+
+    /**
+     * Indicates if this instance equals a unit.
+     * 
+     * @param unit
+     *            The unit.
+     * @return <code>true</code> if and only if this instance equals the unit.
+     */
+    @Override
+    public boolean equals(final Unit unit) {
+        if (this == unit) {
+            return true;
+        }
+        if (offset == 0)
+            return underUnit.equals(unit);
+        if (!(unit instanceof OffsetUnit)) {
+            return false;
+        }
+        final OffsetUnit that = (OffsetUnit) unit;
+        return offset == that.offset && underUnit.equals(that.underUnit);
+    }
+
+    /**
+     * Returns the hash code of this instance. {@link Object#hashCode()} should
+     * be overridden whenever {@link Object#equals(Object)} is.
+     * 
+     * @return The hash code of this instance (includes the values).
+     */
+    @Override
+    public int hashCode() {
+        if (hashCode == 0) {
+            hashCode = offset == 0
+                ? underUnit.hashCode()
+                : underUnit.hashCode() ^ Double.valueOf(offset).hashCode();
+        }
+        return hashCode;
+    }
+
+    @Override
+    public DerivedUnit getDerivedUnit() {
+        return underUnit.getDerivedUnit();
+    }
+}
diff --git a/visad/PlotDigits.java b/visad/PlotDigits.java
new file mode 100644
index 0000000..4c32832
--- /dev/null
+++ b/visad/PlotDigits.java
@@ -0,0 +1,380 @@
+//
+// PlotDigits.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   PlotDigits calculates an array of points to be plotted to
+   the screen as vector pairs, given a number and a bounding
+   rectangle, for use as a label on a contour R^2.<P>
+
+*/
+public class PlotDigits {
+
+  // these variables are filled in by the plotdigits method
+  public float[] Vx;   // x coordinates of label's digits
+  public float[] Vy;   // y coordinates of label's digits
+  public float[] VxB;  // x coordinates of label's digits in reverse display
+  public float[] VyB;  // y coordinates of label's digits in reverse display
+  /*
+   * VxB and VyB can be combined with Vx and Vy to make any combination of
+   * normal writing, backwards writing, upside-down writing, and
+   * backwards and upside-down writing, for use in 3D rotation.
+   */
+  public int NumVerts;  // number of vertices put into Vx, Vy
+  public float Number; // number to plot
+
+  /*
+   * Plot the digits for a contour label in a vector font format.
+   *     Note: ROWS = XK TO XM, COLS = YK TO YM
+   * Input:  gg - label value
+   *         xk, yk, xm, ym - bounds for the label.
+   * Output:  Vx, Vy, VxB, VyB - the vertices of the label's digits.
+   * Return:  number of vertices put into vx,vy.
+   */
+  public void plotdigits(float gg, float xk, float yk,
+                         float xm, float ym, int max, boolean[] swap)
+         throws VisADException {
+    int[] lb = { 0,   // 91 elements
+      105,102,80,20,02,05,27,87,105,85,103,3,1,5,87,105,102,80,
+      60,7,0,87,105,102,80,70,52,54,52,30,20,2,5,27,104,57,50,100,0,
+      100,107,67,62,40,20,2,5,27,80,102,105,87,27,5,2,20,30,52,57,
+      107,100,4,105,102,80,70,52,55,37,27,5,2,20,30,52,55,77,87,105,
+      27, 5,2,20,80,102,105,87,77,55,50 };
+    int[] lt = { 0,   // 12 elements
+      1,10,15,22,35,40,49,60,63,80,91 };
+    float xmk, ymk, hgt, h, dig;
+    float row, col, hl, he;
+    float rs, cs;
+    int jg, j1, j2, j3, isign;
+    int ib, ie, llin = 0, llel = 0, m;
+    int i;
+    NumVerts = 0;
+
+    // extract digits from gg:
+    // jg - integer to left of decimal of float gg
+    // j1, j2, j3 - integers to right of decimal of float gg
+    // dig - number of digits to plot
+    // isign - sign of gg
+    jg = (int) gg;
+    if (gg < 0) {
+      jg = -jg;
+      gg = -gg;
+      isign = -1;
+      dig = 0.5f;
+    }
+    else {
+      isign = 1;
+      dig = 0.0f;
+    }
+
+    j1 = ( (int) (gg * 10.0) ) % 10;
+    j2 = ( (int) (gg * 100.0) ) % 10;
+    j3 = ( (int) (gg * 1000.0) ) % 10;
+
+    // examine digits to left of decimal point
+    if (jg>=100) {
+      j1 = j2 = j3 = 0;
+      dig += 3.0;
+    }
+    else if (jg>=10) {
+      j3 = 0;
+      dig += 4.5;
+      if (j2==0) {
+        dig -= 1.0;
+        if (j1==0) dig -= 1.0f;
+      }
+    }
+    else {
+      dig += 4.5;
+      if (j3==0) {
+        dig -= 1.0;
+        if (j2==0) {
+          dig -= 1.0;
+          if (j1==0) dig -= 1.0f;
+        }
+      }
+    }
+    if (dig<2.0) dig = 2.0f;
+    // end extract digits routine
+
+    xmk = xm-xk;
+    if (xmk < 0) xmk = -xmk;
+    ymk = ym-yk;
+    if (ymk < 0) ymk = -ymk;
+
+    if (swap[0]) {
+      hgt = ymk/1.2f;
+      h = xmk/(dig+0.2f);
+      if (h < hgt) hgt=h;
+      row = (xm > xk ? xm : xk)-0.5f*(xmk-dig*hgt);
+      col = (ym > yk ? ym : yk)-0.5f*(ymk-hgt);
+    }
+    else {
+      hgt = xmk/1.2f;
+      h = ymk/(dig+0.2f);
+      if (h < hgt) hgt=h;
+      row = (xm > xk ? xm : xk)-0.5f*(xmk-hgt);
+      col = (ym > yk ? ym : yk)-0.5f*(ymk-dig*hgt);
+    }
+    h = hgt/10.0f;
+
+    rs = cs = 0.0f;
+
+    Vx = new float[max];
+    Vy = new float[max];
+
+    // PLOT 1000THS
+    if (j3 != 0) {
+      ib = lt[j3+1];
+      ie = lt[j3+2]-1;
+      for (i=ib;i<=ie;i++) {
+        if (swap[0]) {
+          llel = lb[i]/10;
+          llin = lb[i]-llel*10;
+        }
+        else {
+          llin = lb[i]/10;
+          llel = lb[i]-llin*10;
+        }
+        hl = h*llin;
+        he = h*llel;
+        if (i != ib) {
+          Vx[NumVerts] = rs;
+          Vy[NumVerts] = cs;
+          NumVerts++;
+          Vx[NumVerts] = row-hl;
+          Vy[NumVerts] = col-he;
+          NumVerts++;
+        }
+        rs = row-hl;
+        cs = col-he;
+      }
+      // SPACE FOR COLUMN OF DIGIT
+      if (swap[0]) {
+        row = row-hgt;
+      }
+      else {
+        col = col-hgt;
+      }
+    }
+
+    // PLOT 100THS
+    if (j2 != 0 || j3 != 0) {
+      ib = lt[j2+1];
+      ie = lt[j2+2]-1;
+      for (i=ib;i<=ie;i++) {
+        if (swap[0]) {
+          llel = lb[i]/10;
+          llin = lb[i]-llel*10;
+        }
+        else {
+          llin = lb[i]/10;
+          llel = lb[i]-llin*10;
+        }
+        hl = h*llin;
+        he = h*llel;
+        if (i != ib) {
+          Vx[NumVerts] = rs;
+          Vy[NumVerts] = cs;
+          NumVerts++;
+          Vx[NumVerts] = row-hl;
+          Vy[NumVerts] = col-he;
+          NumVerts++;
+        }
+        rs = row-hl;
+        cs = col-he;
+      }
+      // space for column of digit
+      if (swap[0]) {
+        row = row-hgt;
+      }
+      else {
+        col = col-hgt;
+      }
+    }
+
+    // PLOT 10THS
+    if (j1 != 0 || j2 != 0 || j3 != 0) {
+      // PLOT DIGIT RIGHT OF DECIMAL
+      ib = lt[j1+1];
+      ie = lt[j1+2]-1;
+      for (i=ib;i<=ie;i++) {
+        if (swap[0]) {
+          llel = lb[i]/10;
+          llin = lb[i]-llel*10;
+        }
+        else {
+          llin = lb[i]/10;
+          llel = lb[i]-llin*10;
+        }
+        hl = h*llin;
+        he = h*llel;
+        if (i != ib) {
+          Vx[NumVerts] = rs;
+          Vy[NumVerts] = cs;
+          NumVerts++;
+          Vx[NumVerts] = row-hl;
+          Vy[NumVerts] = col-he;
+          NumVerts++;
+        }
+        rs = row-hl;
+        cs = col-he;
+      }
+
+      // space for column of digit
+      if (swap[0]) {
+        row = row-hgt;
+      }
+      else {
+        col = col-hgt;
+      }
+
+      // plot decimal cross
+      if (swap[0]) {
+        Vx[NumVerts] = row-0.2f*hgt;
+        Vy[NumVerts] = col-0.1f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row-0.3f*hgt;
+        Vy[NumVerts] = col-0.2f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row-0.2f*hgt;
+        Vy[NumVerts] = col-0.2f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row-0.3f*hgt;
+        Vy[NumVerts] = col-0.1f*hgt;
+        NumVerts++;
+      }
+      else {
+        Vx[NumVerts] = row-0.1f*hgt;
+        Vy[NumVerts] = col-0.2f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row-0.2f*hgt;
+        Vy[NumVerts] = col-0.3f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row-0.2f*hgt;
+        Vy[NumVerts] = col-0.2f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row-0.1f*hgt;
+        Vy[NumVerts] = col-0.3f*hgt;
+        NumVerts++;
+      }
+
+      // half space for column of decimal cross
+      if (swap[0]) {
+        row = row-0.5f*hgt;
+      }
+      else {
+        col = col-0.5f*hgt;
+      }
+    }
+
+    // PLOT DIGITS LEFT OF DECIMAL
+    // 100:
+    do {
+      m = jg-(jg/10)*10;
+      ib = lt[m+1];
+      ie = lt[m+2]-1;
+      for (i=ib;i<=ie;i++) {
+        if (swap[0]) {
+          llel = lb[i]/10;
+          llin = lb[i]-llel*10;
+        }
+        else {
+          llin = lb[i]/10;
+          llel = lb[i]-llin*10;
+        }
+        hl = h*llin;
+        he = h*llel;
+        if (i != ib) {
+          Vx[NumVerts] = rs;
+          Vy[NumVerts] = cs;
+          NumVerts++;
+          Vx[NumVerts] = row-hl;
+          Vy[NumVerts] = col-he;
+          NumVerts++;
+        }
+        rs = row-hl;
+        cs = col-he;
+      }
+      jg = jg/10;
+      // SPACE FOR COLUMN OF DIGIT
+      if (swap[0]) {
+        row = row-hgt;
+      }
+      else {
+        col = col-hgt;
+      }
+    } while (jg != 0);
+
+
+    if (isign < 0) {
+      // PLOT MINUS SIGN
+      if (swap[0]) {
+        Vx[NumVerts] = row-0.4f*hgt;
+        Vy[NumVerts] = col-0.5f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row;
+        Vy[NumVerts] = col-0.5f*hgt;
+        NumVerts++;
+      }
+      else {
+        Vx[NumVerts] = row-0.5f*hgt;
+        Vy[NumVerts] = col-0.4f*hgt;
+        NumVerts++;
+        Vx[NumVerts] = row-0.5f*hgt;
+        Vy[NumVerts] = col;
+        NumVerts++;
+      }
+    }
+    VxB = new float[max];
+    VyB = new float[max];
+    for (int r=0; r<NumVerts; r++) {
+      VxB[r] = (xm+xk)-Vx[r];
+      VyB[r] = (ym+yk)-Vy[r];
+    }
+    if (swap[0]) {
+      float[] temp = VyB;
+      VyB = Vy;
+      Vy = temp;
+      temp = VxB;
+      VxB = Vx;
+      Vx = temp;
+    }
+    if (swap[1]) {
+      float[] temp = VxB;
+      VxB = Vx;
+      Vx = temp;
+    }
+    if (swap[2]) {
+      float[] temp = VyB;
+      VyB = Vy;
+      Vy = temp;
+    }
+  }
+
+}
+
diff --git a/visad/PlotText.java b/visad/PlotText.java
new file mode 100644
index 0000000..08e4011
--- /dev/null
+++ b/visad/PlotText.java
@@ -0,0 +1,1404 @@
+//
+// PlotText.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+  History:
+  04 July 2003: Extended the render_font and render_label
+    methods to allow rotation of individual characters, scaling, and
+    offsets. (Sylvain Letourneau)
+*/
+
+package visad;
+
+import java.awt.*;
+import java.awt.font.*;
+import java.awt.geom.*;
+import java.util.Vector;
+
+import visad.browser.Convert;
+import visad.util.HersheyFont;
+
+/**
+   PlotText calculates an array of points to be plotted to
+   the screen as vector pairs, given a String and location,
+   orientation and size in space.<P>
+
+   The font is a simple one, and includes characters from
+   the ASCII collating sequence from 0x20 thru 0x7E.
+
+   Most of this was taken from the original visad.PlotText.
+*/
+public class PlotText extends Object {
+
+  static final double XMIN = -1.0;
+  static final double YMIN = -1.0;
+  static final double ZMIN = -1.0;
+  static final double WIDTH = .8;
+
+  /* base line and up vectors */
+  static double[] bx = { 0.07, 0.0, 0.0 }, ux = { 0.0, 0.07, 0.07 };
+  static double[] by = { 0.0, 0.07, 0.0 }, uy = { -0.07, 0.0, -0.07 };
+  static double[] bz = { 0.0, 0.0, -0.07 }, uz = { 0.07, 0.07, 0.0 };
+
+  /* vector characters  -- (100 + x) value indicates beginning of segment */
+  /* characters are ordered by ASCII collating sequence, starting at 0x20 */
+  static float[][] charCodes = {
+
+    {100f,0f}, // sp
+    {101f,8f,1f,3f,3f,3f,3f,8f,1f,8f,101f,1f,1f,0f,3f,0f,3f,1f,1f,1f}, // !
+    {101f,8f,0f,5f,104f,8f,3f,5f}, // "
+    {101.5f,8f,1.5f,0f,103.5f,8f,3.5f,0f,100f,5f,5f,5f,100f,3f,5f,3f}, // #
+    {101.5f,8f,1.5f,0f,102.5f,8f,2.5f,0f,104f,5.5f,3f,7f,1f,7f,0f,5.5f,0f,4.5f,4f,3.5f,4f,2.5f,3f,1f,1f,1f,0f,2.5f}, // $
+    {100f,8f,0f,7f,1f,7f,1f,8f,0f,8f,105f,8f,0f,0f,104f,1f,4f,0f,5f,0f,5f,1f,4f,1f}, // %
+    {105f,0f,0f,5f,0f,7f,1f,8f,3f,8f,4f,7f,4f,5f,0f,3f,0f,1f,1f,0f,3f,0f,5f,3f,5f,4f}, // &
+    {101f,8f,0f,5f}, // '
+    {104f,8f,2f,6f,2f,2f,4f,0f}, // (
+    {101f,8f,3f,6f,3f,2f,1f,0f}, // )
+    {100f,7f,5f,1f,102.5f,7f,2.5f,1f,100f,1f,5f,7f,105f,4f,0f,4f}, // *
+    {102.5f,7f,2.5f,1f,100f,4f,5f,4f}, // +
+    {103f,0f,2f,0f,2f,1f,3f,1f,3f,0f,2.1f,-2f}, // ,
+    {100f,4f,5f,4f}, // -
+    {102f,0f,3f,0f,3f,1f,2f,1f,2f,0f}, // .
+    {100f,0f,5f,8f}, // /
+    {102f,8f,0f,6f,0f,2f,2f,0f,3f,0f,5f,2f,5f,6f,3f,8f,2f,8f}, // 0
+    {101f,7f,2.5f,8f,2.5f,0f,1f,0f,4f,0f}, // 1
+    {100f,7f,1f,8f,4f,8f,5f,7f,5f,5f,0f,0f,5f,0f}, // 2
+    {100f,7f,1f,8f,4f,8f,5f,7f,5f,5f,4f,4f,3f,4f,4f,4f,5f,3f,5f,1f,4f,0f,1f,0f,0f,1f}, // 3
+    {103f,8f,0f,4f,5f,4f,5f,8f,5f,0f}, // 4
+    {100f,1f,1f,0f,4f,0f,5f,1f,5f,4f,4f,5f,0f,5f,0f,8f,5f,8f}, // 5
+    {105f,7f,4f,8f,1f,8f,0f,7f,0f,1f,1f,0f,4f,0f,5f,1f,5f,3f,4f,4f,0f,4f}, // 6
+    {100f,8f,5f,8f,3f,0f}, // 7
+    {101f,8f,0f,7f,0f,5f,1f,4f,4f,4f,5f,5f,5f,7f,4f,8f,1f,8f,101f,4f,0f,3f,0f,1f,1f,0f,4f,0f,5f,1f,5f,3f,4f,4f}, // 8
+    {101f,0f,1f,0f,4f,0f,5f,1f,5f,7f,4f,8f,1f,8f,0f,7f,0f,5f,1f,4f,5f,4f}, // 9
+    {102f,7f,2f,5f,3f,5f,3f,7f,2f,7f,102f,3f,2f,1f,3f,1f,3f,3f,2f,3f}, // :
+    {100f,7f,0f,5f,1f,5f,1f,7f,0f,7f,100f,0f,1f,1f,1f,3f,0f,3f,0f,1f,1f,1f}, // ;
+    {105f,7f,0f,4f,5f,1f}, // <
+    {100f,5f,5f,5f,100f,3f,5f,3f}, // =
+    {100f,7f,5f,4f,0f,1f}, // >
+    {100f,7f,1f,8f,4f,8f,5f,7f,5f,5f,4f,4f,2.5f,4f,2.5f,2f,102.5f,1f,2.5f,0f}, // ?
+    {104f,0f,1f,0f,0f,1f,0f,7f,1f,8f,4f,8f,5f,7f,5f,3f,4f,1.5f,3f,2f,1.5f,4f,1.5f,5f,2.5f,6f,4f,5f,3f,2f},   // @
+    {100f,0f,0f,7f,1f,8f,4f,8f,5f,7f,5f,0f,5f,4f,0f,4f}, // A
+    {100f,8f,0f,0f,4f,0f,5f,1f,5f,3f,4f,4f,5f,5f,5f,7f,4f,8f,0f,8f,0f,4f,4f,4f}, // B
+    {105f,7f,4f,8f,1f,8f,0f,7f,0f,1f,1f,0f,4f,0f,5f,1f}, // C
+    {100f,8f,0f,0f,4f,0f,5f,1f,5f,7f,4f,8f,0f,8f}, // D
+    {105f,8f,0f,8f,0f,4f,3f,4f,0f,4f,0f,0f,5f,0f}, // E
+    {105f,8f,0f,8f,0f,4f,3f,4f,0f,4f,0f,0f}, // F
+    {105f,7f,4f,8f,1f,8f,0f,7f,0f,1f,1f,0f,4f,0f,5f,1f,5f,4f,3f,4f}, // G
+    {100f,8f,0f,0f,0f,4f,5f,4f,5f,8f,5f,0f}, // H
+    {100f,8f,5f,8f,2.5f,8f,2.5f,0f,0f,0f,5f,0f}, // I
+    {105f,8f,5f,1f,4f,0f,1f,0f,0f,1f,0f,3f}, // J
+    {100f,8f,0f,0f,0f,4f,5f,8f,0f,4f,5f,0f}, // K
+    {100f,8f,0f,0f,5f,0f}, // L
+    {100f,0f,0f,8f,2.5f,4f,5f,8f,5f,0f}, // M
+    {100f,0f,0f,8f,5f,0f,5f,8f}, // N
+    {101f,8f,0f,7f,0f,1f,1f,0f,4f,0f,5f,1f,5f,7f,4f,8f,1f,8f}, // O
+    {100f,0f,0f,8f,4f,8f,5f,7f,5f,5f,4f,4f,0f,4f}, // P
+    {101f,8f,0f,7f,0f,1f,1f,0f,4f,0f,5f,1f,5f,7f,4f,8f,1f,8f,103f,3f,5f,0f}, // Q
+    {100f,0f,0f,8f,4f,8f,5f,7f,5f,5f,4f,4f,0f,4f,3f,4f,5f,0f}, // R
+    {105f,7f,4f,8f,1f,8f,0f,7f,0f,5f,1f,4f,4f,4f,5f,3f,5f,1f,4f,0f,1f,0f,0f,1f}, // S
+    {100f,8f,5f,8f,2.5f,8f,2.5f,0f}, // T
+    {100f,8f,0f,1f,1f,0f,4f,0f,5f,1f,5f,8f}, // U
+    {100f,8f,2.5f,0f,5f,8f}, // V
+    {100f,8f,0f,0f,2.5f,4f,5f,0f,5f,8f}, // W
+    {100f,8f,5f,0f,100f,0f,5f,8f}, // X
+    {100f,8f,2.5f,4f,5f,8f,2.5f,4f,2.5f,0f}, // Y
+    {100f,8f,5f,8f,0f,0f,5f,0f}, // Z
+    {104f,8f,2f,8f,2f,0f,4f,0f}, // [
+    {100f,8f,5f,0f}, // \
+    {101f,8f,3f,8f,3f,0f,1f,0f}, // ]
+    {102f,6f,3f,8f,4f,6f}, // ^
+    {100f,-2f,5f,-2f}, // _
+    {102f,8f,4f,6f}, // `
+    {104f,5f,4f,1f,3f,0f,1f,0f,0f,1f,0f,4f,1f,5f,3f,5f,4f,4f,4f,1f,5f,0f}, // a
+    {100f,8f,0f,0f,0f,1f,1f,0f,4f,0f,5f,1f,5f,4f,4f,5f,3f,5f,0f,3f}, // b
+    {105f,0f,1f,0f,0f,1f,0f,4f,1f,5f,4f,5f,5f,4f}, // c
+    {105f,3f,3f,5f,1f,5f,0f,4f,0f,1f,1f,0f,4f,0f,5f,1f,5f,0f,5f,8f}, // d
+    {105f,0f,1f,0f,0f,1f,0f,4f,1f,5f,4f,5f,5f,4f,4f,3f,0f,3f}, // e
+    {103f,0f,3f,7f,4f,8f,5f,8f,5f,7f,101f,4f,4f,4f}, // f
+    {105f,5f,5f,-3f,4f,-4f,1f,-4f,105f,1f,4f,0f,1f,0f,0f,1f,0f,4f,1f,5f,3f,5f,5f,3f}, // g
+    {100f,8f,0f,0f,0f,3f,3f,5f,4f,5f,5f,4f,5f,0f}, // h
+    {103f,4f,3f,0f,4f,0f,1f,0f,103f,6.5f,3f,5.5f}, // i
+    {104f,4f,4f,-3f,3f,-4f,1f,-4f,0f,-3f,0f,-1f,1f,0f,104f,6.5f,4f,5.5f}, // j
+    {101f,8f,1f,0f,101f,3f,5f,5f,101f,3f,5f,0f}, // k
+    {102f,8f,3f,8f,3f,0f}, // l
+    {100f,0f,0f,5f,0f,4f,1f,5f,4f,5f,5f,4f,5f,0f,102.5f,5f,2.5f,2.0f}, // m
+    {100f,0f,0f,5f,0f,4f,1f,5f,4f,5f,5f,3f,5f,0f}, // n
+    {101f,0f,0f,1f,0f,4f,1f,5f,4f,5f,5f,4f,5f,1f,4f,0f,1f,0f}, // o
+    {100f,-4f,0f,1f,1f,0f,4f,0f,5f,1f,5f,4f,4f,5f,3f,5f,0f,3f,0f,1f,0f,5f}, // p
+    {105f,-4f,5f,1f,4f,0f,1f,0f,0f,1f,0f,4f,1f,5f,3f,5f,5f,3f,5f,1f,5f,5f}, // q
+    {100f,5f,0f,0f,0f,3f,3f,5f,4f,5f,5f,4f}, // r
+    {105f,4f,3f,5f,2f,5f,0f,4f,0f,3f,5f,2f,5f,1f,3f,0f,2f,0f,0f,1f}, // s
+    // {105f,4f,4f,5f,3f,5f,1f,3.5f,3f,3f,4f,3f,5f,1f,4f,0f,3f,0f,1f,1f}, // s
+    {102.5f,8f,2.5f,0f,100.5f,5f,4.5f,5f}, // t
+    {100f,5f,0f,1f,1f,0f,3f,0f,5f,3f,5f,5f,5f,0f}, // u
+    {100f,5f,0f,3f,2.5f,0f,5f,3f,5f,5f}, // v
+    {100f,5f,0f,0f,2.5f,3f,5f,0f,5f,5f}, // w
+    {100f,5f,5f,0f,105f,5f,0f,0f}, // x
+    {100f,5f,0f,3f,3f,0f,5f,3f,5f,5f,5f,-3f,3f,-4f}, // y
+    {100f,5f,5f,5f,0f,0f,5f,0f}, // z
+    {104f,8f,3f,8f,2f,4.5f,1f,4.5f,2f,4.5f,3f,0f,4f,0f}, // {
+    {103.5f,8f,3.5f,0f}, // |
+    {102f,8f,3f,8f,4f,4.5f,5f,4.5f,4f,4.5f,3f,0f,2f,0f}, // }
+    {100f,4f,1f,5f,3f,4f,4f,5f}, // ~
+    {100f,0f} // RO
+  };
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of vectors for drawing.
+   *
+   * @param  axis  [=0 (x), =1 (y), or =2 (z)
+   * @param  pos  position along axis to put label in [-1,1]
+   * @param  str  the text string to "print"
+   * @param  line  line number for multi-line text (0 = first line)
+   * @param  c  color (not used yet)
+   *
+   * @return VisADLineArray of all the vectors needed to draw the
+   * characters in this string
+  */
+  public static VisADLineArray render_label(int axis, double pos, String str,
+                                            int line, long c) {
+    double XMIN = -1.0;
+    double YMIN = -1.0;
+    double ZMIN = -1.0;
+
+    /* base line and up vectors */
+    double[] bx = { 0.07, 0.0, 0.0 }, ux = { 0.0, 0.07, 0.07 };
+    double[] by = { 0.0, 0.07, 0.0 }, uy = { -0.07, 0.0, -0.07 };
+    double[] bz = { 0.0, 0.0, -0.07 }, uz = { 0.07, 0.07, 0.0 };
+
+    double[] base = null;
+    double[] up = null;
+    double[] start = new double[3];
+
+    if (axis==0) { // x
+      base = bx;
+      up = ux;
+      start[0] = pos;
+      start[1] = YMIN * (1.1 + 0.07*line);
+      start[2] = ZMIN * (1.1 + 0.07*line);
+    }
+    else if (axis==1) { // y
+      base = by;
+      up = uy;
+      start[0] = XMIN * (1.1 + 0.07*line);
+      start[1] = pos;
+      start[2] = ZMIN * (1.1 + 0.07*line);
+    }
+    else if (axis==2) { // z
+      base = bz;
+      up = uz;
+      start[0] = XMIN * (1.1 + 0.07*line);
+      start[1] = YMIN * (1.1 + 0.07*line);
+      start[2] = pos;
+    }
+    // abcd 5 February 2001
+    return render_label(str, start, base, up, TextControl.Justification.CENTER,
+                TextControl.Justification.BOTTOM, 0.0,1.0, null);
+  }
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of vectors for drawing.
+   *
+   * @param str  String to use
+   * @param  start point (x,y,z)
+   * @param  base  (x,y,z) of baseline vector
+   * @param  up  (x,y,z) of "up" direction vector
+   * @param  center is <CODE>true</CODE> if string is to be centered
+   *
+   * @return VisADLineArray of all the vectors needed to draw the
+   * characters in this string
+  */
+  public static VisADLineArray render_label(String str, double[] start,
+         double[] base, double[] up, boolean center) {
+    return render_label(str, start, base, up,
+           (center ? TextControl.Justification.CENTER :
+                     TextControl.Justification.LEFT),
+           TextControl.Justification.BOTTOM, 0.0, 1.0, null);
+  }
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of vectors for drawing.
+   *
+   * @param str  String to use
+   * @param  start point (x,y,z)
+   * @param  base  (x,y,z) of baseline vector
+   * @param  up  (x,y,z) of "up" direction vector
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   * @param  verticalJustification is one of:<ul>
+   * <li> TextControl.Justification.TOP - Top justified text
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.BOTTOM - Bottom justified text (normal)
+   * </ul>
+   *
+   * @return VisADLineArray of all the vectors needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_label(String str, double[] start,
+         double[] base, double[] up, TextControl.Justification justification,
+         TextControl.Justification verticalJustification) {
+    return render_label(str, start, base, up, justification,
+                        verticalJustification, 0.0, 1.0, null);
+  }
+
+  // abcd 5 February 2001
+  // was
+  // * @param  center is <CODE>true</CODE> if string is to be centered
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of vectors for drawing.
+   *
+   * @param str  String to use
+   * @param  start point (x,y,z)
+   * @param  base  (x,y,z) of baseline vector
+   * @param  up  (x,y,z) of "up" direction vector
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   *
+   * @return VisADLineArray of all the vectors needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_label(String str, double[] start,
+         double[] base, double[] up, TextControl.Justification justification) {
+    return render_label(str, start, base, up, justification,
+                        TextControl.Justification.BOTTOM, 0.0, 1.0, null);
+  }
+
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of vectors for drawing.
+   *
+   * @param str  String to use
+   * @param  start point (x,y,z)
+   * @param  base  (x,y,z) of baseline vector
+   * @param  up  (x,y,z) of "up" direction vector
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   * @param characRotation is the angle (in degrees) at which each character
+   * in str is rotated with respect to the base line of the text.  A positive
+   * value rotates the characters clockwise; a negative value
+   * rotates them counterclockwise.
+   *
+   * @return VisADLineArray of all the vectors needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_label(String str, double[] start,
+         double[] base, double[] up, TextControl.Justification justification,
+         double characRotation) {
+    return render_label(str, start, base, up, justification,
+                        TextControl.Justification.BOTTOM,
+                        characRotation, 1.0, null);
+  }
+
+  // abcd 5 February 2001
+  // was
+  // * @param  center is <CODE>true</CODE> if string is to be centered
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of vectors for drawing.
+   *
+   * @param str  String to use
+   * @param  start point (x,y,z)
+   * @param  base  (x,y,z) of baseline vector
+   * @param  up  (x,y,z) of "up" direction vector
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   * @param  verticalJustification is one of:<ul>
+   * <li> TextControl.Justification.TOP - Top justified text
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.BOTTOM - Bottom justified text (normal)
+   * </ul>
+   * @param characRotation is the angle (in degrees) at which each character
+   * in str is rotated with respect to the base line of the text.  A positive
+   * value rotates the characters clockwise; a negative value
+   * rotates them counterclockwise.
+   * @param scale is the scaling factor.
+   * @param offsets is a 1x3 array defining the offsets in X, Y, Z, respectively.
+   *
+   * @return VisADLineArray of all the lines needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_label(String str, double[] start,
+         double[] base, double[] up, TextControl.Justification justification,
+         TextControl.Justification verticalJustification,
+         double characRotation, double scale, double[] offsets) {
+    if (offsets == null) {
+      offsets = new double[]{0.0, 0.0, 0.0};
+    }
+    if (scale <= 0.0) {
+      scale = 1.0;
+    }
+
+    /*  System.out.println("in render_label:" +
+        " characRotation= " + characRotation +
+        " scale=" + scale +
+        " offset=[" + offsets[0] + ", " + offsets[1] +
+        ", " + offsets[2] + "]");
+    */
+
+    double []start_off = new double[3];
+    start_off[0] = start[0] + offsets[0];
+    start_off[1] = start[1] + offsets[1];
+    start_off[2] = start[2] + offsets[2];
+
+    double[] base_scaled = new double[3];
+    base_scaled[0] = base[0] * scale;
+    base_scaled[1] = base[1] * scale;
+    base_scaled[2] = base[2] * scale;
+
+    double[] up_scaled = new double[3];
+    up_scaled[0] = up[0] * scale;
+    up_scaled[1] = up[1] * scale;
+    up_scaled[2] = up[2] * scale;
+
+    double[] temp;
+    double cx, cy, cz;
+    double startx = 0.0;
+    double starty = 0.0;
+    double startz = 0.0;
+    double sw;
+    int i, j, k, v2, len;
+
+    cx = start_off[0];
+    cy = start_off[1];
+    cz = start_off[2];
+    len = str.length();
+    // allow 20 2-point 3-component strokes per character
+    float[] plot = new float[120 * len];
+
+    int plot_index = 0;
+
+    double angle = Math.toRadians(-characRotation);
+    float angle2 = (float) (angle + Math.PI/2.0);
+    double w, h, x, y;
+
+    /* draw left justified text */
+
+    for (i=0; i<len; i++) {
+      char cur_char = str.charAt(i);
+
+      k = str.charAt(i) - 32;
+      if (k < 0 || k > 127) continue; // invalid - just skip
+
+      int verts = charCodes[k].length/2;
+
+      /* make the vertex array for this character */
+      /* points with x>9 are 'start new segment' flag */
+
+      int plot_index_begin = plot_index;
+      float maxX = 0.f;
+      float maxY = 0.f, minY = 10.f;
+      int temp_index = 0;
+      for (j=0; j<verts; j++) {
+
+        if (verts == 1) break; // handle space character
+
+        boolean dup_point = true;
+        if (j == (verts - 1) ) dup_point = false; // don't dupe last point
+        float px, py, pz;
+        w = (double) charCodes[k][temp_index]*.1;
+        if (w > 9.0) {
+          if (j != 0) plot_index -= 3; // reset pointer to remove last point
+          w = w - 10.0;
+          dup_point = false;
+        }
+
+        temp_index++;
+        h = (double) charCodes[k][temp_index]*.1;
+        temp_index++;
+
+        if (w > maxX) maxX = (float) w;
+        if (h > maxY) maxY = (float) h;
+        if (h < minY) minY = (float) h;
+
+        // System.out.println(i + " " + j + " " + cur_char + ". w=" + w + " h=" + h);
+        x = (float) (w * Math.cos(angle) - h * Math.sin(angle));
+        y = (float) (w * Math.sin(angle) + h * Math.cos(angle));
+        // System.out.println(i + ". " + k + ". " + j + ". x=" + x + ", y="+ y);
+
+        px = (float) (cx + x * base_scaled[0] + y * up_scaled[0]);
+        py = (float) (cy + x * base_scaled[1] + y * up_scaled[1]);
+        pz = (float) (cz + x * base_scaled[2] + y * up_scaled[2]);
+
+        plot[plot_index] = px;
+        plot[plot_index + 1] = py;
+        plot[plot_index + 2] = pz;
+
+        if (dup_point) { // plot points are in pairs -- set up for next pair
+          plot[plot_index + 3] = plot[plot_index];
+          plot[plot_index + 4] = plot[plot_index + 1];
+          plot[plot_index + 5] = plot[plot_index + 2];
+          plot_index += 3;
+        }
+        plot_index += 3;
+      }
+      if (minY > maxY) {
+        minY = maxY;  // no vertice
+      }
+      //  System.out.println(i + ". " + cur_char + ". maxX=" + maxX + ", minY="+ minY + ", maxY="+ maxY);
+
+      // Calculate offsets due to rotations of characters
+      x = maxX;
+      y = (float)(maxY-minY + 0.1);
+      float x_plus = (float) (x * Math.abs(Math.cos(angle)) +
+                              y * Math.abs(Math.cos(angle2)));
+      float cur_x_off = 0.0f;
+      if (Math.cos(angle) < 0) {
+        cur_x_off = (float) (x * Math.abs(Math.cos(angle)));
+      }
+      if (Math.cos(angle2) < 0) {
+        cur_x_off += (float) ((maxY+.05) * Math.abs(Math.cos(angle2)));
+      }
+      else if (minY < 0) {
+        cur_x_off += (float) ((-minY+0.05) * Math.abs(Math.cos(angle2)));
+      }
+
+      // System.out.println(i + ". " + cur_char + ". x=" + x + " y=" + y + " x_plus=" + x_plus + " cur_x_off=" + cur_x_off);
+
+      // Apply offsets
+      for (j=plot_index_begin;j<plot_index; j=j+6) {
+        plot[j] += cur_x_off * base_scaled[0];
+        plot[j + 1] += cur_x_off * base_scaled[1];
+        plot[j + 2] += cur_x_off * base_scaled[2];
+        plot[j + 3] += cur_x_off * base_scaled[0];
+        plot[j + 4] += cur_x_off * base_scaled[1];
+        plot[j + 5] += cur_x_off * base_scaled[2];
+      }
+
+      /* calculate position for next char */
+      double width = Math.max(WIDTH, x_plus + 0.3);
+      cx += (float) (width * base_scaled[0]);
+      cy += (float) (width * base_scaled[1]);
+      cz += (float) (width * base_scaled[2]);
+
+    } // end for (i=0; i<len; i++)
+
+    if (plot_index <= 0) return null;
+
+/* grf 22 Jan 2004 - alter to get vertical justification correct
+*/
+    float cxoff = 0.0f;
+    float cyoff = 0.0f;
+    float czoff = 0.0f;
+
+    // LEFT is normal (or TOP or BOTTOM)
+    if (justification == TextControl.Justification.CENTER) {
+      cxoff = (float)((cx - start_off[0])/2.);
+      cyoff = (float)((cy - start_off[1])/2.);
+      czoff = (float)((cz - start_off[2])/2.);
+
+    } else if (justification == TextControl.Justification.RIGHT) {
+      cxoff = (float)(cx - start_off[0]);
+      cyoff = (float)(cy - start_off[1]);
+      czoff = (float)(cz - start_off[2]);
+    }
+
+    // BOTTOM is normal (or LEFT or RIGHT)
+    if (verticalJustification == TextControl.Justification.TOP) {
+      final double height = WIDTH;
+      cxoff += height * up_scaled[0];
+      cyoff += height * up_scaled[1];
+      czoff += height * up_scaled[2];
+    } else if (verticalJustification == TextControl.Justification.CENTER) {
+      final double height = WIDTH;
+      cxoff += height * up_scaled[0] / 2.0;
+      cyoff += height * up_scaled[1] / 2.0;
+      czoff += height * up_scaled[2] / 2.0;
+    }
+
+
+/* grf 22 Jan 2004 - alter to get vertical justification correct
+*/
+    if (cxoff != 0.0f || cyoff != 0.0f || czoff != 0.0f) { 
+      for (i=0; i<plot_index; i=i+3) {
+        plot[i] = plot[i] - cxoff;
+        plot[i+1] = plot[i+1] - cyoff;
+        plot[i+2] = plot[i+2] - czoff;
+      }
+    }
+
+    VisADLineArray array = new VisADLineArray();
+    float[] coordinates = new float[plot_index];
+    System.arraycopy(plot, 0, coordinates, 0, plot_index);
+    array.coordinates = coordinates;
+    array.vertexCount = plot_index / 3;
+
+/* WLH 20 Feb 98
+    array.vertexFormat = COORDINATES;
+*/
+
+    return array;
+  }
+
+  /** make a short string for value for use in slider label */
+  public static String shortString(double val)
+  {
+    return Convert.shortString(val);
+  }
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of lines for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null HersheyFont font
+   * @param  start point (x,y,z)
+   * @param  base  (x,y,z) of baseline vector
+   * @param  up  (x,y,z) of "up" direction vector
+   * @param  center is <CODE>true</CODE> if string is to be centered
+   *
+   * @return VisADLineArray of all the lines needed to draw the
+   * characters in this string
+  */
+  public static VisADLineArray render_font(String str, HersheyFont font,
+           double[] start, double[] base, double[] up, boolean center) {
+    return render_font(str, font, start, base, up,
+                       (center ? TextControl.Justification.CENTER :
+                                  TextControl.Justification.LEFT),
+                       TextControl.Justification.BOTTOM, 0.0, 1, null);
+  }
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of lines for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null HersheyFont name
+   * @param  start
+   * @param  base
+   * @param  up
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   *
+   * @return VisADLineArray of all the lines needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_font(String str, HersheyFont font,
+            double[] start, double[] base, double[] up,
+            TextControl.Justification justification) {
+    return render_font(str, font, start, base, up, justification,
+                       TextControl.Justification.BOTTOM, 0.0, 1.0, null);
+  }
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of lines for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null HersheyFont name
+   * @param  start
+   * @param  base
+   * @param  up
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   * @param characRotation is the angle (in degrees) at which each character
+   * in str is rotated with respect to the base line of the text.  A positive
+   * value rotates the characters clockwise; a negative value
+   * rotates them counterclockwise.
+   *
+   * @return VisADLineArray of all the lines needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_font(String str, HersheyFont font,
+         double[] start, double[] base, double[] up,
+         TextControl.Justification justification, double characRotation) {
+    return render_font(str, font, start, base, up, justification,
+                       TextControl.Justification.BOTTOM,
+                       characRotation, 1.0, null);
+  }
+
+
+ /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of lines for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null HersheyFont name
+   * @param  start
+   * @param  base
+   * @param  up
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   * @param  verticalJustification is one of:<ul>
+   * <li> TextControl.Justification.TOP - Top justified text
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.BOTTOM - Bottom justified text (normal)
+   * </ul>
+   *
+   * @return VisADLineArray of all the lines needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_font(String str, HersheyFont font,
+         double[] start, double[] base, double[] up,
+         TextControl.Justification justification,
+         TextControl.Justification verticalJustification) {
+    return render_font(str, font, start, base, up, justification,
+                       verticalJustification, 0.0, 1.0, null);
+  }
+
+
+
+ // abcd, 3 March 2003
+ /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of lines for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null HersheyFont name
+   * @param  start
+   * @param  base
+   * @param  up
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   * @param  verticalJustification is one of:<ul>
+   * <li> TextControl.Justification.TOP - Top justified text
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.BOTTOM - Bottom justified text (normal)
+   * </ul>
+   * @param characRotation is the angle (in degrees) at which each character
+   * in str is rotated with respect to the base line of the text.  A positive
+   * value rotates the characters clockwise; a negative value
+   * rotates them counterclockwise.
+   * @param scale is the scaling factor.
+   * @param offsets is a 1x3 array defining the offsets in X, Y, Z, respectively.
+   *
+   * @return VisADLineArray of all the lines needed to draw the
+   * characters in this string
+   */
+  public static VisADLineArray render_font(String str, HersheyFont font,
+            double[] start, double[] base, double[] up,
+            TextControl.Justification justification,
+            TextControl.Justification verticalJustification,
+            double characRotation, double scale, double[] offsets) {
+    /*
+      System.out.println("in render_font with HersheyFont font:" +
+      " characRotation= " + characRotation +
+      " scale=" + scale +
+      " offset=[" + offsets[0] + ", " + offsets[1] +
+      ", " + offsets[2] + "]"              );
+    */
+
+    if (offsets == null) {
+      offsets = new double[]{0.0, 0.0, 0.0};
+    }
+    double []start_off = new double[3];
+    start_off[0] = start[0] + offsets[0];
+    start_off[1] = start[1] + offsets[1];
+    start_off[2] = start[2] + offsets[2];
+
+    if (scale <= 0.0) {
+      scale = 1.0;
+    }
+    double[] base_scaled = new double[3];
+    base_scaled[0] = base[0] * scale;
+    base_scaled[1] = base[1] * scale;
+    base_scaled[2] = base[2] * scale;
+
+    double[] up_scaled = new double[3];
+    up_scaled[0] = up[0] * scale;
+    up_scaled[1] = up[1] * scale;
+    up_scaled[2] = up[2] * scale;
+
+    int maxChars = font.getCharactersInSet();
+
+    double width = 0;
+    double startx = 0.0;
+    double starty = 0.0;
+    double startz = 0.0;
+
+    double cx = start_off[0];
+    double cy = start_off[1];
+    double cz = start_off[2];
+    int len = str.length();
+    boolean isFixed = font.getFixedWidth();
+
+    // allow 2-point 3-component strokes per character
+    int maxSeg = font.getMaxPoints();
+    float[] plot = new float[maxSeg * 6 * len];
+
+    int plot_index = 0;
+    int [] charMinX = font.getCharacterMinX();
+    int [] charMaxX = font.getCharacterMaxX();
+    int charMinY = font.getCharacterSetMinY();
+    int charMaxY = font.getCharacterSetMaxY();
+    int charSetMinX = font.getCharacterSetMinX();
+    int charSetMaxX = font.getCharacterSetMaxX();
+    boolean isCursive = font.getIsCursive();
+
+    float oldpx = 0.f;
+    float oldpy = 0.f;
+    float oldpz = 0.f;
+    float x,y, px, py, pz;
+    float w,h;
+
+    // look at each character in the string
+    double angle = Math.toRadians(-characRotation);
+    float angle2 = (float) (angle + Math.PI/2.0);
+    for (int i=0; i<len; i++) {
+
+      char cur_char = str.charAt(i);
+
+      int k = str.charAt(i) - (int) ' ';
+      if (k < 0 || k > maxChars) continue; // invalid - just skip
+
+      char [][] charVector = font.getCharacterVector(k);
+      int verts = font.getNumberOfPoints(k);
+
+      /* calculate position for start of this char - about .08 seems right*/
+      if (i > 0) {
+
+        width = .08;
+        if (isCursive) width = -.08;
+        cx += width * base_scaled[0];
+        cy += width * base_scaled[1];
+        cz += width * base_scaled[2];
+      }
+
+      // System.out.println(i + ". " + cur_char + ". charMinY=" + charMinY + " charMaxY=" + charMaxY);
+      // System.out.println(i + ". " + cur_char + " cx=" + cx + " cy=" + cy + " cz=" + cz);
+
+      int plot_index_begin = plot_index;
+      boolean skip = true;
+      float maxX = 0.f;
+      float maxY = 0.f, minY = (float)charMaxY;
+
+
+      for (int j=1; j<verts; j++) {
+
+        if (charVector[0][j] == (int) ' ') {
+          skip = true;
+
+        } else {
+          // make the coordinates relative to 0
+          if (isFixed) {
+            w = (float)(charVector[0][j] - charMinX[k])
+              / (float)(charSetMaxX - charSetMinX);
+          } else {
+            w = (float)(charVector[0][j] - charMinX[k])
+              / (float)(charMaxX[k] - charMinX[k]);
+          }
+
+          // invert y coordinate
+          h = (float) (charMaxY - charVector[1][j] )
+            / (charMaxY - charMinY);
+
+          if (w > maxX) maxX = w;
+          if (h > maxY) maxY = h;
+          if (h < minY) minY = h;
+          //          System.out.println(i + " " + j + " " + cur_char + ". w=" + w + " h=" + h);
+          x = (float) (w * Math.cos(angle) - h * Math.sin(angle));
+          y = (float) (w * Math.sin(angle) + h * Math.cos(angle));
+          // System.out.println(i + ". " + k + ". " + j + ". x=" + x + ", y="+ y);
+
+          px = (float) (cx + x * base_scaled[0] + y * up_scaled[0]);
+          py = (float) (cy + x * base_scaled[1] + y * up_scaled[1]);
+          pz = (float) (cz + x * base_scaled[2] + y * up_scaled[2]);
+
+          // System.out.println(i + ". " + k + ". " + j + ". px=" + px + ", py="+ py + ", pz=" + pz);
+
+          // need pairs of points
+          if (!skip) {
+            plot[plot_index] = oldpx;
+            plot[plot_index + 1] = oldpy;
+            plot[plot_index + 2] = oldpz;
+            plot[plot_index + 3] = px;
+            plot[plot_index + 4] = py;
+            plot[plot_index + 5] = pz;
+            plot_index += 6;
+          }
+          skip = false;
+          oldpx = px;
+          oldpy = py;
+          oldpz = pz;
+        }
+      }
+
+      if (verts == 1) maxX = .5f;
+
+      if (minY > maxY) {
+        minY = maxY;  // no vertice
+      }
+
+      // System.out.println(i + ". " + cur_char + ". maxX=" + maxX + ", minY="+ minY + ", maxY="+ maxY);
+
+      // Calculate offsets due to rotations of characters
+      x = maxX;
+      y = (float)Math.max((maxY - minY + 0.3), 0.5);
+      float x_plus = (float) (x * Math.abs(Math.cos(angle)) +
+                              y * Math.abs(Math.cos(angle2)));
+      float cur_x_off = 0.0f;
+      if (Math.cos(angle) < 0) {
+        cur_x_off = (float) (x * Math.abs(Math.cos(angle)));
+      }
+      if (Math.cos(angle2) < 0) {
+        cur_x_off += (float) (y * Math.abs(Math.cos(angle2)));
+      }
+      // The space required to center horizontally
+      float y1 = (float) ((minY + .30)/2.0 - minY);
+      cur_x_off += (float) (y1 * Math.cos(angle2));
+
+      // System.out.println(i + ". " + cur_char + ". x=" + x + " y=" + y + " y1=" + y1 + " x_plus=" + x_plus + " cur_x_off=" + cur_x_off);
+
+      // Apply offsets
+      for (int j=plot_index_begin;j<plot_index; j=j+6) {
+        plot[j] += cur_x_off * base_scaled[0];
+        plot[j + 1] += cur_x_off * base_scaled[1];
+        plot[j + 2] += cur_x_off * base_scaled[2];
+        plot[j + 3] += cur_x_off * base_scaled[0];
+        plot[j + 4] += cur_x_off * base_scaled[1];
+        plot[j + 5] += cur_x_off * base_scaled[2];
+      }
+
+      // move pointer to the end position of this character
+      width = width + x_plus;
+      cx += width * base_scaled[0];
+      cy += width * base_scaled[1];
+      cz += width * base_scaled[2];
+
+    } // end for (i=0; i<len; i++)
+
+
+    if (plot_index <= 0) return null;
+
+    // now re-justify text along x-axis if need be.
+/* grf 22 Jan 2004 - alter to get vertical justification correct
+*/
+    float cxoff = 0.0f;
+    float cyoff = 0.0f;
+    float czoff = 0.0f;
+
+    // LEFT is normal (or TOP or BOTTOM)
+    if (justification == TextControl.Justification.CENTER) {
+      cxoff = (float)((cx - start_off[0])/2.);
+      cyoff = (float)((cy - start_off[1])/2.);
+      czoff = (float)((cz - start_off[2])/2.);
+    } else if (justification == TextControl.Justification.RIGHT) {
+      cxoff = (float)(cx - start_off[0]);
+      cyoff = (float)(cy - start_off[1]);
+      czoff = (float)(cz - start_off[2]);
+
+    }
+
+    // BOTTOM is normal (or LEFT or RIGHT)
+    if (verticalJustification == TextControl.Justification.TOP) {
+      final double height = WIDTH;
+      cxoff += height * up_scaled[0];
+      cyoff += height * up_scaled[1];
+      czoff += height * up_scaled[2];
+    } else if (verticalJustification == TextControl.Justification.CENTER) {
+      final double height = WIDTH;
+      cxoff += height * up_scaled[0] / 2.0;
+      cyoff += height * up_scaled[1] / 2.0;
+      czoff += height * up_scaled[2] / 2.0;
+    }
+
+/* grf 22 Jan 2004 - alter to get vertical justification correct
+   only alter if needed
+*/
+    if (cxoff != 0.0f || cyoff != 0.0f || czoff != 0.0f) { 
+      for (int i=0; i<plot_index; i=i+3) {
+        plot[i] = plot[i] - cxoff;
+        plot[i+1] = plot[i+1] - cyoff;
+        plot[i+2] = plot[i+2] - czoff;
+      }
+    }
+
+    // finally, make the VisADLineArray
+    VisADLineArray array = new VisADLineArray();
+    float[] coordinates = new float[plot_index];
+    System.arraycopy(plot, 0, coordinates, 0, plot_index);
+    array.coordinates = coordinates;
+    array.vertexCount = plot_index / 3;
+
+    return array;
+  }
+
+// abcd 5 February 2001
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of triangles for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null font
+   * @param  start point (x,y,z)
+   * @param  base  (x,y,z) of baseline vector
+   * @param  up  (x,y,z) of "up" direction vector
+   * @param  center is <CODE>true</CODE> if string is to be centered
+   *
+   * @return VisADTriangleArray of all the triangles needed to draw the
+   * characters in this string
+  */
+  public static VisADTriangleArray render_font(String str, Font font,
+            double[] start, double[] base, double[] up, boolean center) {
+    return render_font(str, font, start, base, up,
+                       (center ? TextControl.Justification.CENTER :
+                                 TextControl.Justification.LEFT),
+                       TextControl.Justification.BOTTOM,
+                       0.0, 1.0, null);
+  }
+
+// abcd 19 March 2003
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of triangles for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null font
+   * @param  start
+   * @param  base
+   * @param  up
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   *
+   * @return VisADTriangleArray of all the triangles needed to draw the
+   * characters in this string
+   */
+  public static VisADTriangleArray render_font(String str, Font font,
+            double[] start, double[] base, double[] up,
+            TextControl.Justification justification) {
+    return render_font(str, font, start, base, up, justification,
+                       TextControl.Justification.BOTTOM, 0.0, 1.0, null);
+
+  }
+
+  /**
+   * Convert a string of characters (ASCII collating sequence) into a
+   *  series of triangles for drawing.
+   *
+   * @param str  String to use
+   * @param  font  non-null font
+   * @param  start
+   * @param  base
+   * @param  up
+   * @param  justification is one of:<ul>
+   * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+   * <li> TextControl.Justification.CENTER - Centered text
+   * <li> TextControl.Justification.RIGHT - Right justified text
+   * </ul>
+   * @param characRotation is the angle (in degrees) at which each character
+   * in str is rotated with respect to the base line of the text.  A positive
+   * value rotates the characters clockwise; a negative value
+   * rotates them counterclockwise.
+   *
+   * @return VisADTriangleArray of all the triangles needed to draw the
+   * characters in this string
+   */
+  public static VisADTriangleArray render_font(String str, Font font,
+         double[] start, double[] base, double[] up,
+         TextControl.Justification justification, double characRotation) {
+    return render_font(str, font, start, base, up, justification,
+                       TextControl.Justification.BOTTOM,
+                       characRotation, 1.0, null);
+  }
+
+
+  /**
+  * Convert a string of characters (ASCII collating sequence) into a
+  *  series of triangles for drawing.
+  *
+  * @param str  String to use
+  * @param  font  non-null font
+  * @param  start
+  * @param  base
+  * @param  up
+  * @param  justification is one of:<ul>
+  * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+  * <li> TextControl.Justification.CENTER - Centered text
+  * <li> TextControl.Justification.RIGHT - Right justified text
+  * </ul>
+  * @param  verticalJustification is one of:<ul>
+  * <li> TextControl.Justification.TOP - Top justified text
+  * <li> TextControl.Justification.CENTER - Centered text
+  * <li> TextControl.Justification.BOTTOM - Bottom justified text (normal)
+  * </ul>
+  *
+  * @return VisADTriangleArray of all the triangles needed to draw the
+  * characters in this string
+  */
+  public static VisADTriangleArray render_font(String str, Font font,
+         double[] start, double[] base, double[] up,
+         TextControl.Justification justification,
+         TextControl.Justification verticalJustification) {
+    return render_font(str, font, start, base, up, justification,
+                       verticalJustification, 0.0, 1.0, null);
+  }
+
+
+  /**
+  * Convert a string of characters (ASCII collating sequence) into a
+  *  series of triangles for drawing.
+  *
+  * @param str  String to use
+  * @param  font  non-null font
+  * @param  start
+  * @param  base
+  * @param  up
+  * @param  justification is one of:<ul>
+  * <li> TextControl.Justification.LEFT - Left justified text (ie: normal text)
+  * <li> TextControl.Justification.CENTER - Centered text
+  * <li> TextControl.Justification.RIGHT - Right justified text
+  * </ul>
+  * @param  verticalJustification is one of:<ul>
+  * <li> TextControl.Justification.TOP - Top justified text
+  * <li> TextControl.Justification.CENTER - Centered text
+  * <li> TextControl.Justification.BOTTOM - Bottom justified text (normal)
+  * </ul>
+  * @param characRotation is the angle (in degrees) at which each character
+  * in str is rotated with respect to the base line of the text.  A positive
+  * value rotates the characters clockwise; a negative value
+  * rotates them counterclockwise.
+  * @param scale is the scaling factor.
+  * @param offsets is a 1x3 array defining the offsets in X, Y, Z, respectively.
+  *
+  * @return VisADTriangleArray of all the triangles needed to draw the
+  * characters in this string
+  */
+  public static VisADTriangleArray render_font(String str, Font font,
+         double[] start, double[] base, double[] up,
+         TextControl.Justification justification,
+         TextControl.Justification verticalJustification,
+         double characRotation, double scale, double[] offsets) {
+    VisADTriangleArray array = null;
+
+    // System.out.println("x, y, z = " + x + " " + y + " " + z);
+    // System.out.println("center = " + center);
+
+
+    /* System.out.println("in render_font with Java font:" +
+       " characRotation= " + characRotation +
+       " scale=" + scale +
+       " offset=[" + offsets[0] + ", " + offsets[1] +
+       ", " + offsets[2] + "]");
+    */
+    if (offsets == null) {
+      offsets = new double[]{0.0, 0.0, 0.0};
+    }
+    double []start_off = new double[3];
+    start_off[0] = start[0] + offsets[0];
+    start_off[1] = start[1] + offsets[1];
+    start_off[2] = start[2] + offsets[2];
+
+    if (scale < 0.0) {
+      scale = 1.0;
+    }
+
+    float fsize = font.getSize();
+    float fsize_inv = (float)(scale / fsize);
+    //float fsize_inv = (float)(1.0 / fsize);
+
+    // ??
+    // Graphics2D g2 = null;
+    // FontRenderContext frc = g2.getFontRenderContext();
+    int str_len = str.length();
+    AffineTransform at = null;
+    boolean isAntiAliased = false;
+    boolean usesFractionalMetrics = false;
+    FontRenderContext frc =
+      new FontRenderContext(at, isAntiAliased, usesFractionalMetrics);
+    GlyphVector gv = font.createGlyphVector(frc, "M");
+    float maxW = (float) (fsize_inv * gv.getGlyphMetrics(0).
+                          getBounds2D().getWidth());
+
+    double flatness = 0.05; // ??
+
+    Vector big_vector = new Vector();
+    int big_len = 1000;
+    float[][] big_samples = new float[2][big_len];
+    float[] seg = new float[6];
+
+
+
+    float x_offset = 0.0f;
+    for (int str_index=0; str_index<str_len; str_index++) {
+      char[] chars = {str.charAt(str_index)};
+      gv = font.createGlyphVector(frc, chars);
+
+      int ng = gv.getNumGlyphs();
+      if (ng == 0) continue;
+      int path_count = 0;
+      Vector samples_vector = new Vector();
+
+      // abcd - 1 February 2001
+      // Get x increment from the fonts 'advance' property
+      // float x_plus = (float) (fsize_inv * gv.getGlyphMetrics(0).getAdvance());
+      //System.out.println(str_index + " " + chars[0] + " " + x_plus + " " + fsize_inv);
+
+      // Compute advance along baseline
+      float angle = (float) Math.toRadians(-characRotation);
+      float angle2 = (float) (angle + Math.PI/2.0);
+      float x = (float) (fsize_inv * gv.getGlyphMetrics(0).getAdvance());
+      float y = (float) (fsize_inv *
+                         (gv.getGlyphMetrics(0).getBounds2D().getHeight())
+                         + 0.2);
+      float x_plus = (float) (x * Math.abs(Math.cos(angle)) +
+                              y * Math.abs(Math.cos(angle2)));
+
+      // Compute offset along baseline
+      float y1 = (float) (fsize_inv *
+                          (gv.getGlyphMetrics(0).getBounds2D().getY() * -1)
+                          + 0.2);
+      float cur_x_off = 0.0f;
+      if (Math.cos(angle) < 0) {
+        cur_x_off = (float) (x * Math.abs(Math.cos(angle)));
+      }
+      if (Math.cos(angle2) < 0) {
+        cur_x_off += (float) (y1 * Math.abs(Math.cos(angle2)));
+      }
+      else {
+        cur_x_off += (float) ((y-y1) * Math.abs(Math.cos(angle2)));
+      }
+      // Compute offset perpendicular to the baseline
+      float w = (float) (fsize_inv * gv.getGlyphMetrics(0).getBounds2D().
+                         getWidth());
+      float x_start = (float) (fsize_inv * gv.getGlyphMetrics(0).getBounds2D().
+                               getX());
+      float space = (float) ((maxW - w)/2.0);
+      float cur_y_off = (float) ((space - x_start) * Math.cos(angle2));
+      //System.out.println("\n" + str_index + " " + chars[0] + " " + gv.getGlyphMetrics(0).getBounds2D() + " " + gv.getGlyphMetrics(0).getLSB() + " " + gv.getGlyphMetrics(0).getRSB());
+      //System.out.println("\n" + str_index + " " + chars[0] + " w=" + w + " x_start=" + x_start + " space=" + space + " maxW=" + maxW);
+      //System.out.println("\n" + str_index + " " + chars[0] + " " + "x=" + x + " y=" + y + " y1=" + y1 + " x_plus=" + x_plus + " cur_x_off=" + cur_x_off + " cur_y_off=" + cur_y_off);
+
+      for (int ig=0; ig<ng; ig++) {
+        Shape sh = null;
+        if (characRotation != 0.0) {
+          Shape sh0 = gv.getGlyphOutline(ig);
+          angle = (float) Math.toRadians(characRotation);
+          AffineTransform at2 = AffineTransform.getRotateInstance(angle);
+          sh = at2.createTransformedShape(sh0);
+        }
+        else {
+          sh = gv.getGlyphOutline(ig);
+        }
+
+        // pi only has SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point types
+        PathIterator pi = sh.getPathIterator(at, flatness);
+        int k = 0;
+        while (!pi.isDone()) {
+          int segType = pi.currentSegment(seg);
+          switch(segType) {
+            case PathIterator.SEG_MOVETO:
+              if (k > 0) {
+                //System.out.println("SEG_MOVETO  k = " + k + "  ig = " + ig);
+                float[][] samples = new float[2][k];
+                System.arraycopy(big_samples[0], 0, samples[0], 0, k);
+                System.arraycopy(big_samples[1], 0, samples[1], 0, k);
+                samples_vector.addElement(samples);
+                k = 0;
+                path_count++;
+              }
+              // NOTE falls through to SEG_LINETO to add first point
+            case PathIterator.SEG_LINETO:
+              big_samples[0][k] = x_offset + cur_x_off + fsize_inv * seg[0];
+              big_samples[1][k] = - cur_y_off - fsize_inv * seg[1];
+              k++;
+              if (k >= big_len) {
+                float[][] bs = new float[2][2 * big_len];
+                System.arraycopy(big_samples[0], 0, bs[0], 0, big_len);
+                System.arraycopy(big_samples[1], 0, bs[1], 0, big_len);
+                big_samples = bs;
+                big_len = 2 * big_len;
+              }
+              break;
+            case PathIterator.SEG_CLOSE:
+              if (k > 0) {
+//System.out.println("SEG_CLOSE  k = " + k + "  ig = " + ig);
+                float[][] samples = new float[2][k];
+                System.arraycopy(big_samples[0], 0, samples[0], 0, k);
+                System.arraycopy(big_samples[1], 0, samples[1], 0, k);
+                samples_vector.addElement(samples);
+                k = 0;
+                path_count++;
+              }
+              break;
+          }
+          pi.next();
+        } // end while (!pi.isDone())
+        if (k > 0) {
+// System.out.println("  end  k = " + k + "  ig = " + ig);
+          float[][] samples = new float[2][k];
+          System.arraycopy(big_samples[0], 0, samples[0], 0, k);
+          System.arraycopy(big_samples[1], 0, samples[1], 0, k);
+          samples_vector.addElement(samples);
+          k = 0;
+          path_count++;
+        }
+
+      } // end for (int ig=0; ig<ng; ig++)
+
+      if (path_count == 1) {
+// System.out.println("  char  " + chars[0]);
+        big_vector.addElement(samples_vector.elementAt(0));
+      }
+      else if (path_count > 1) {
+        // System.out.println("path_count = " + path_count +
+        //                    " for char = " + chars[0]);
+        float[][][] ss = new float[path_count][][];
+        for (int i=0; i<path_count; i++) {
+          ss[i] = (float[][]) samples_vector.elementAt(i);
+        }
+        try {
+          if (path_count == 2 &&
+              (!DelaunayCustom.inside(ss[0], ss[1][0][0], ss[1][1][0]) &&
+               !DelaunayCustom.inside(ss[1], ss[0][0][0], ss[0][1][0]))) {
+            // don't link for disconnected paths link "i"
+// System.out.println("  no link for  " + chars[0] + " " + path_count);
+            for (int i=0; i<path_count; i++) {
+              big_vector.addElement(ss[i]);
+            }
+          }
+          else {
+// System.out.println("  call link for  " + chars[0] + " " + path_count);
+            big_vector.addElement(DelaunayCustom.link(ss));
+          }
+        }
+        catch (VisADException ex) {
+          System.out.println(ex);
+        }
+      }
+      samples_vector.removeAllElements();
+
+      x_offset += x_plus;
+    } // end for (int str_index=0; str_index<str_len; str_index++)
+
+    /*
+     * abcd 5 February 2001
+     * Figure out how far to the 'left' our text should start
+     */
+    // x_offset = center ? -0.5f * x_offset : 0.0f;
+
+    // Set default to LEFT
+    if (justification == TextControl.Justification.CENTER) {
+      x_offset = -0.5f * x_offset;
+    } else if (justification == TextControl.Justification.RIGHT) {
+      x_offset = -1.0f * x_offset;
+    } else { // Default LEFT (or TOP or BOTTOM)
+      x_offset = 0.0f;
+    }
+    /*
+     * abcd 20 March 2003
+     * Figure out how far to 'up' our text should start
+     */
+/* grf 22 Jan 2004 - alter to get vertical justification correct
+   Set to default BOTTOM
+*/
+    float y_offset = (float)(0.8*scale);
+    if (verticalJustification == TextControl.Justification.CENTER) {
+      y_offset = -0.5f * y_offset;
+    } else if ( verticalJustification == TextControl.Justification.TOP) {
+      y_offset = -1.0f * y_offset;
+    } else { // BOTTOM (or LEFT or RIGHT)
+      y_offset = 0.0f;
+    }
+
+    int n = big_vector.size();
+    VisADTriangleArray[] arrays = new VisADTriangleArray[n];
+    for (int i=0; i<n; i++) {
+      float[][] samples = (float[][]) big_vector.elementAt(i);
+// System.out.println("samples " + i + " " + samples[0][0] + " " + samples[1][0] +
+//                    " " + samples[0][1] + " " + samples[1][1]);
+      int[][] tris = null;
+      try {
+        tris = DelaunayCustom.fillCheck(samples, false);
+      }
+      catch (VisADException ex) {
+      }
+      if (tris == null || tris.length == 0) continue;
+      int m = tris.length;
+      float[] coordinates = new float[9 * m];
+      for (int j=0; j<m; j++) {
+        int j9 = 9 * j;
+        for (int tj=0; tj<3; tj++) {
+          int j3 = j9 + 3 * tj;
+          coordinates[j3 + 0] = (float)
+            (start_off[0] +  base[0] * (samples[0][tris[j][tj]] + x_offset) +
+             up[0] * (samples[1][tris[j][tj]] + y_offset));
+          coordinates[j3 + 1] = (float)
+            (start_off[1] +
+             base[1] * (samples[0][tris[j][tj]] + x_offset) +
+             up[1] * (samples[1][tris[j][tj]] + y_offset));
+          coordinates[j3 + 2] = (float)
+            (start_off[2] +
+             base[2] * (samples[0][tris[j][tj]] + x_offset) +
+             up[2] * (samples[1][tris[j][tj]] + y_offset));
+        }
+      }
+      float[] normals = new float[9 * m];
+      for (int j=0; j<3*m; j++) {
+        int j3 = 3 * j;
+        normals[j3 + 0] = 0.0f;
+        normals[j3 + 1] = 0.0f;
+        normals[j3 + 2] = 1.0f;
+      }
+      arrays[i] = new VisADTriangleArray();
+      arrays[i].vertexCount = 3 * m;
+      arrays[i].coordinates = coordinates;
+      arrays[i].normals = normals;
+      // System.out.println("array[" + i + "] has " + m + " tris");
+    } // end for (int i=0; i<n; i++)
+
+    array = new VisADTriangleArray();
+    try {
+      VisADGeometryArray.merge(arrays, array);
+    }
+    catch (VisADException ex) {
+      array = new VisADTriangleArray();
+    }
+    if (array.coordinates == null) return null;
+    return array;
+  }
+
+}
diff --git a/visad/PolarCoordinateSystem.java b/visad/PolarCoordinateSystem.java
new file mode 100644
index 0000000..d479683
--- /dev/null
+++ b/visad/PolarCoordinateSystem.java
@@ -0,0 +1,134 @@
+//
+// PolarCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   PolarCoordinateSystem is the VisAD CoordinateSystem class
+   for (Longitude, Radius) with a Cartesian Reference,
+   and with Longitude in degrees.<P>
+*/
+public class PolarCoordinateSystem extends CoordinateSystem {
+
+  private static Unit[] coordinate_system_units =
+    {CommonUnit.degree, null};
+
+  /** construct a CoordinateSystem for (longitude, radius)
+      relative to a 2-D Cartesian reference;
+      this constructor supplies units = {CommonUnit.Degree, null}
+      to the super constructor, in order to ensure Unit
+      compatibility with its use of trigonometric functions */
+  public PolarCoordinateSystem(RealTupleType reference)
+         throws VisADException {
+    super(reference, coordinate_system_units);
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("PolarCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[2][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[1][i] < 0.0) {
+        value[0][i] = Double.NaN;
+        value[1][i] = Double.NaN;
+      }
+      else {
+        double coslon = Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        double sinlon = Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        value[0][i] = tuples[1][i] * coslon;
+        value[1][i] = tuples[1][i] * sinlon;
+      }
+    }
+    return value;
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("PolarCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[2][len];
+    for (int i=0; i<len ;i++) {
+      value[1][i] = Math.sqrt(tuples[0][i] * tuples[0][i] +
+                              tuples[1][i] * tuples[1][i]);
+      value[0][i] =
+        Data.RADIANS_TO_DEGREES * Math.atan2(tuples[1][i], tuples[0][i]);
+      // if (value[0][i] < 0.0) value[0][i] += 180.0;
+      if (value[0][i] < 0.0) value[0][i] += 360.0; // WLH 9 Sept 99
+    }
+    return value;
+  }
+
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("PolarCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[2][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[1][i] < 0.0) {
+        value[0][i] = Float.NaN;
+        value[1][i] = Float.NaN;
+      }
+      else {
+        float coslon = (float) Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        float sinlon = (float) Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        value[0][i] = tuples[1][i] * coslon;
+        value[1][i] = tuples[1][i] * sinlon;
+      }
+    }
+    return value;
+  }
+
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("PolarCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[2][len];
+    for (int i=0; i<len ;i++) {
+      value[1][i] = (float) Math.sqrt(tuples[0][i] * tuples[0][i] +
+                                      tuples[1][i] * tuples[1][i]);
+      value[0][i] = (float)
+        (Data.RADIANS_TO_DEGREES * Math.atan2(tuples[1][i], tuples[0][i]));
+      // if (value[0][i] < 0.0) value[0][i] += 180.0;
+      if (value[0][i] < 0.0) value[0][i] += 360.0; // WLH 9 Sept 99
+    }
+    return value;
+  }
+
+  public boolean equals(Object cs) {
+    return (cs instanceof PolarCoordinateSystem);
+  }
+
+}
+
diff --git a/visad/ProductSet.java b/visad/ProductSet.java
new file mode 100644
index 0000000..dd3f935
--- /dev/null
+++ b/visad/ProductSet.java
@@ -0,0 +1,639 @@
+//
+// ProductSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+//
+// TO_DO
+// getWedge
+//
+
+/**
+   ProductSet is the cross-product of an array of SampledSets.<P>
+*/
+public class ProductSet extends SampledSet {
+
+  SampledSet[] Sets;
+
+  /** construct a ProductSet with an array of SampledSets */
+  public ProductSet(SampledSet[] sets) throws VisADException {
+    this(makeType(sets), sets, null, null, null, true);
+  }
+
+  /** create the product of the sets array, with null errors,
+      CoordinateSystem and Units are defaults from type */
+  public ProductSet(MathType type, SampledSet[] sets) throws VisADException {
+    this(type, sets, null, null, null, true);
+  }
+
+  /** create the product of the sets array; coordinate_system
+      and units must be compatible with defaults for type,
+      or may be null; errors may be null */
+  public ProductSet(MathType type, SampledSet[] sets,
+                    CoordinateSystem coord_sys, Unit[] units,
+                    ErrorEstimate[] errors) throws VisADException {
+    this(type, sets, coord_sys, units, errors, true);
+  }
+
+  ProductSet(MathType type, SampledSet[] sets, CoordinateSystem coord_sys,
+             Unit[] units, ErrorEstimate[] errors, boolean copy)
+             throws VisADException {
+    super(type, find_manifold_dim(sets, units), coord_sys, units, errors);
+    int dim = 0;
+    for (int i=0; i<sets.length; i++) {
+      dim += sets[i].DomainDimension;
+    }
+    if (dim != DomainDimension) {
+      throw new SetException("ProductSet: DomainDimension does not match");
+    }
+    if (copy) {
+      Sets = new SampledSet[sets.length];
+      for (int i=0; i<sets.length; i++) {
+       	Sets[i] = (SampledSet) sets[i].clone();
+      }
+   }
+    else Sets = sets;
+    Length = 1;
+    for (int i=0; i<sets.length; i++) {
+      Length *= sets[i].Length;
+    }
+    Low = new float[DomainDimension];
+    Hi = new float[DomainDimension];
+    int base = 0;
+    for (int i=0; i<sets.length; i++) {
+      float[] low = sets[i].getLow();
+      float[] hi = sets[i].getHi();
+      int set_dim = sets[i].getDimension();
+      for (int j=0; j<set_dim; j++) {
+        Low[base + j] = low[j];
+        Hi[base + j] = hi[j];
+      }
+      base += set_dim;
+    }
+  }
+
+  /**
+   * Returns the {@link SampledSet}s that constitute this instance.  The
+   * returned array may be modified without affecting this instance.
+   *
+   * @return                         The {@link SampledSet}s that constitute
+   *                                 this instance.
+   */
+  public SampledSet[] getSets() {
+    return (SampledSet[])Sets.clone();  // return defensive copy
+  }
+
+  private static int find_manifold_dim(SampledSet[] sets, Unit[] units)
+          throws VisADException {
+    if (sets == null || sets[0] == null) {
+      throw new SetException("ProductSet: Sets cannot be missing");
+    }
+    if (sets.length < 2) {
+      throw new SetException("ProductSet: must be at least 2 sets");
+    }
+    int manifold_dim = 0;
+    int dim = 0;
+    for (int i=0; i<sets.length; i++) {
+      if (units != null) {
+        int n = sets[i].getDimension();
+        if ((dim + n) > units.length) {
+          throw new SetException("ProductSet: Sets exceed ManifoldDimension " +
+                                 units.length);
+        }
+        Unit[] su = sets[i].getSetUnits();
+        if (su == null) {
+          throw new SetException("ProductSet: Set#" + i + " is null");
+        }
+        for (int j=0; j<n; j++) {
+          if (units[dim+j] != null || su[j] != null) {
+            if (units[dim+j] == null || su[j] == null ||
+                !units[dim+j].equals(su[j])) {
+              throw new SetException("ProductSet: Expected set " + i +
+                                     ", element " + j + " units to be " +
+                                     units[dim+j] + " not " + su[j]);
+            }
+          }
+        }
+      }
+      dim += sets[i].getDimension();
+      manifold_dim += sets[i].getManifoldDimension();
+    }
+    return manifold_dim;
+  }
+
+  static MathType makeType(SampledSet[] sets) throws VisADException {
+    int n = sets.length;
+    RealTupleType[] types = new RealTupleType[n];
+    int count = 0;
+    for (int i=0; i<n; i++) {
+      types[i] = ((SetType) sets[i].getType()).getDomain();
+      count += types[i].getDimension();
+    }
+    RealType[] reals = new RealType[count];
+    int k=0;
+    for (int i=0; i<n; i++) {
+      for (int j=0; j<types[i].getDimension(); j++) {
+        reals[k++] = (RealType) types[i].getComponent(j);
+      }
+    }
+    return new SetType(new RealTupleType(reals));
+  }
+
+  public SampledSet product() throws VisADException {
+    int n = Sets.length;
+    SampledSet[] sets = new SampledSet[n];
+    for (int i=0; i<n; i++) {
+      if (Sets[i] instanceof GriddedSet ||
+          Sets[i] instanceof IrregularSet) {
+        sets[i] = Sets[i];
+      }
+      else if (Sets[i] instanceof ProductSet) {
+        sets[i] = ((ProductSet) Sets[i]).product();
+      }
+      else if (Sets[i] instanceof UnionSet) {
+        sets[i] = ((UnionSet) Sets[i]).product();
+      }
+      else {
+        throw new UnimplementedException("ProductSet.product: " +
+                                         Sets[i].getClass());
+      }
+    } // end for (int i=0; i<n; i++)
+    SampledSet prod = sets[0];
+    for (int i=1; i<n; i++) {
+      if (prod instanceof ProductSet) {
+        prod = ((ProductSet) prod).product(sets[i]);
+      }
+      else if (prod instanceof UnionSet) {
+        prod = ((UnionSet) prod).product(sets[i]);
+      }
+      /* WLH 10 January 2001 - add missing "else" to following */
+      else if (sets[i] instanceof ProductSet) {
+        prod = ((ProductSet) sets[i]).inverseProduct(prod);
+      }
+      else if (sets[i] instanceof UnionSet) {
+        prod = ((UnionSet) sets[i]).inverseProduct(prod);
+      }
+      else {
+        prod = new ProductSet(new SampledSet[] {prod, sets[i]});
+      }
+    }
+    return prod;
+  }
+
+  public SampledSet product(SampledSet set) throws VisADException {
+    int n = Sets.length;
+    if (set instanceof ProductSet) {
+      int m = ((ProductSet) set).Sets.length;
+      SampledSet[] sets = new SampledSet[n + m];
+      for (int i=0; i<n; i++) {
+        sets[i] = Sets[i];
+      }
+      for (int j=0; j<m; j++) {
+        sets[n + j] = ((ProductSet) set).Sets[j];
+      }
+      return new ProductSet(sets);
+    }
+    else if (set instanceof UnionSet) {
+      int m = ((UnionSet) set).Sets.length;
+      SampledSet[] sets = new SampledSet[m];
+      for (int j=0; j<m; j++) {
+        sets[j] = product(((UnionSet) set).Sets[j]);
+      }
+      return new UnionSet(sets);
+    }
+    else {
+      SampledSet[] sets = new SampledSet[n + 1];
+      for (int i=0; i<n; i++) sets[i] = Sets[i];
+      sets[n] = set;
+      return new ProductSet(sets);
+    }
+  }
+
+  public SampledSet inverseProduct(SampledSet set) throws VisADException {
+    int n = Sets.length;
+    if (set instanceof ProductSet) {
+      int m = ((ProductSet) set).Sets.length;
+      SampledSet[] sets = new SampledSet[n + m];
+      for (int j=0; j<m; j++) {
+        sets[j] = ((ProductSet) set).Sets[j];
+      }
+      for (int i=0; i<n; i++) {
+        sets[m + i] = Sets[i];
+      }
+      return new ProductSet(sets);
+    }
+    else if (set instanceof UnionSet) {
+      int m = ((UnionSet) set).Sets.length;
+      SampledSet[] sets = new SampledSet[m];
+      for (int j=0; j<m; j++) {
+        sets[j] = inverseProduct(((UnionSet) set).Sets[j]);
+      }
+      return new UnionSet(sets);
+    }
+    else {
+      SampledSet[] sets = new SampledSet[n + 1];
+      sets[0] = set;
+      for (int i=0; i<n; i++) {
+        sets[i + 1] = Sets[i];
+      }
+      return new ProductSet(sets);
+    }
+  }
+
+  /** this should return Gridded3DSet or Irregular3DSet;
+      no need for make*DGeometry or makeIso* in this class */
+  public Set makeSpatial(SetType type, float[][] samples) throws VisADException {
+    int n = Sets.length;
+    int dim = samples.length;
+    if (dim != DomainDimension || dim != 3) {
+      throw new SetException("ProductSet.makeSpatial: samples bad dimension");
+    }
+    try {
+      //
+      // TO_DO
+      // make a Gridded3DSet if possible
+      // otherwise be smart in making an Irregular3DSet
+      // note: cannot re-order samples
+      //
+      boolean any_product_or_union = false;
+      for (int i=0; i<n; i++) {
+        if (Sets[i] instanceof ProductSet ||
+            Sets[i] instanceof UnionSet) {
+          any_product_or_union = true;
+        }
+      }
+      if (any_product_or_union) {
+        return product().makeSpatial(type, samples);
+      }
+      else {
+        boolean all_gridded = true;
+        int[] lengths = new int[ManifoldDimension];
+        int k = 0;
+        for (int i=0; i<n; i++) {
+          if (!(Sets[i] instanceof GriddedSet)) {
+            all_gridded = false;
+            break;
+          }
+          int[] ls = ((GriddedSet) Sets[i]).getLengths();
+          for (int j=0; j<ls.length; j++) {
+            lengths[k++] = ls[j];
+          }
+        }
+        if (all_gridded) {
+          return GriddedSet.create(type, samples, lengths);
+        }
+        //
+        // TO_DO
+        // can assume that no factors are ProductSet or UnionSet
+        //
+      }
+      throw new UnimplementedException("ProductSet.makeSpatial");
+    }
+    catch (VisADException e) {
+      return new Irregular3DSet(type, samples, null,
+                                null, null, null, false);
+    }
+  }
+
+  /** copied from Set */
+  public float[][] getSamples(boolean copy) throws VisADException {
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToValue(indices);
+  }
+
+  /** convert an array of 1-D indices to an
+      array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int nsets = Sets.length;
+    int npts = index.length;
+    int[][] indices = new int[nsets][npts];
+    float[][] value = new float[DomainDimension][];
+    for (int j=0; j<npts; j++) {
+      int num = index[j];
+      for (int i=0; i<nsets; i++) {
+        indices[i][j] = num % Sets[i].Length;
+        num /= Sets[i].Length;
+      }
+    }
+    int curdim = 0;
+    for (int i=0; i<nsets; i++) {
+      float[][] temp_vals = Sets[i].indexToValue(indices[i]);
+      for (int k=0; k<temp_vals.length; k++) {
+        value[curdim++] = temp_vals[k];
+      }
+    }
+    return value;
+  }
+
+  /** convert an array of values in R^DomainDimension
+      to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    int nsets = Sets.length;
+    int npts = value[0].length;
+    int[] index = new int[npts];
+    float[][][] vals = new float[nsets][][];
+    int curdim = 0;
+    int[][] temp_inds = new int[nsets][];
+    for (int i=0; i<nsets; i++) {
+      vals[i] = new float[Sets[i].DomainDimension][];
+      for (int k=0; k<Sets[i].DomainDimension; k++) {
+        vals[i][k] = value[curdim++];
+      }
+      temp_inds[i] = Sets[i].valueToIndex(vals[i]);
+    }
+    for (int j=0; j<npts; j++) {
+      int ind_j = 0;
+      int num = 1;
+      for (int i=0; i<nsets; i++) {
+        ind_j += temp_inds[i][j]*num;
+        num *= Sets[i].Length;
+      }
+      index[j] = ind_j;
+    }
+    return index;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float[][] weights) throws VisADException {
+    int nsets = Sets.length;
+    int npts = value[0].length;
+    float[][][] vals = new float[nsets][][];
+    int curdim = 0;
+    int[][][] temp_inds = new int[nsets][npts][];
+    float[][][] temp_wgts = new float[nsets][npts][];
+
+    // get valueToInterp results of individual sets
+    for (int i=0; i<nsets; i++) {
+      vals[i] = new float[Sets[i].DomainDimension][];
+      for (int k=0; k<Sets[i].DomainDimension; k++) {
+        vals[i][k] = value[curdim++];
+      }
+      Sets[i].valueToInterp(vals[i], temp_inds[i], temp_wgts[i]);
+    }
+
+    // merge valueToInterp results into indices and weights arrays
+    for (int j=0; j<npts; j++) {
+      int[] ptr = new int[nsets];
+      int num_inds = 1;
+      for (int i=0; i<nsets; i++) {
+        ptr[i] = 0;
+        num_inds *= temp_inds[i][j].length;
+      }
+      indices[j] = new int[num_inds];
+      weights[j] = new float[num_inds];
+
+      // take the entire cross-product of returned indices
+      int ind_num = 0;
+      while (ptr[0] < temp_inds[0][j].length) {
+
+        // calculate index and weight values of current set values
+        int ind_j = 0;
+        float wgt_j = 1;
+        int num = 1;
+        for (int i=0; i<nsets; i++) {
+          ind_j += temp_inds[i][j][ptr[i]]*num;
+          wgt_j *= temp_wgts[i][j][ptr[i]];
+          num *= Sets[i].Length;
+        }
+        indices[j][ind_num] = ind_j;
+        weights[j][ind_num] = wgt_j;
+        ind_num++;
+
+        // advance cross-product pointers
+        ptr[nsets-1]++;
+        for (int i=nsets-2; i>=0; i--) {
+          if (ptr[i+1] >= temp_inds[i+1][j].length) {
+            ptr[i+1] = 0;
+            ptr[i]++;
+          }
+          else i = 0;
+        }
+      }
+    }
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                        A clone of this instance.
+   */
+  public Object clone() {
+    ProductSet clone = (ProductSet)super.clone();
+    
+    /*
+     * The array of sampled sets is cloned because getSamples(false) allows
+     * clients to modify the values and the clone() general contract forbids
+     * cross-clone effects.
+     */
+    if (Sets != null) {
+        clone.Sets = new SampledSet[Sets.length];
+        for (int i = 0; i < Sets.length; i++)
+            clone.Sets[i] = (SampledSet)Sets[i].clone();
+    }
+    
+    return clone;
+  }
+
+  public Object cloneButType(MathType type) throws VisADException {
+    return new ProductSet(type, Sets, DomainCoordinateSystem,
+                          SetUnits, SetErrors);
+  }
+
+  public boolean equals(Object set) {
+    if (!(set instanceof ProductSet) || set == null) return false;
+    if (this == set) return true;
+
+    ProductSet pset = (ProductSet) set;
+    if (pset.DomainDimension != DomainDimension
+     || pset.ManifoldDimension != ManifoldDimension) return false;
+    for (int i=0; i<Sets.length; i++) {
+      if (!Sets[i].equals(pset.Sets[i])) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the hash code of this instance.
+   * @return		The hash code of this instance.
+   */
+  public int hashCode() {
+    if (!hashCodeSet)
+    {
+      for (int i=0; i<Sets.length; i++)
+	hashCode ^= Sets[i].hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  public boolean isMissing() {
+    for (int i=0; i<Sets.length; i++) {
+      if (Sets[i].isMissing()) return true;
+    }
+    return false;
+  }
+
+  public String longString(String pre) throws VisADException {
+    return pre + "ProductSet: Dimension = " + DomainDimension + "\n";
+  }
+
+  /* run 'java visad.ProductSet' to test the ProductSet class */
+  public static void main(String[] argv) throws VisADException {
+    RealType vis_xcoord = RealType.getRealType("x");
+    RealType vis_ycoord = RealType.getRealType("y");
+
+    // create Gridded2DSet
+    RealType[] vis_arrayG = {vis_xcoord, vis_ycoord};
+    RealTupleType vis_tupleG = new RealTupleType(vis_arrayG);
+    float[][] sampG = { { 12.5f,  26.5f, 29.74f, 36.78f,
+                         52.12f,  67.8f,  87.8f,  97.2f},
+                        { 34.2f,  36.2f,  37.2f,  32.6f,
+                         70.87f, 73.49f, 80.32f, 77.24f} };
+    Gridded2DSet gSet = new Gridded2DSet(vis_tupleG, sampG, 4, 2);
+
+    // create Irregular1DSet
+    RealType[] vis_arrayI = {vis_xcoord};
+    RealTupleType vis_tupleI = new RealTupleType(vis_arrayI);
+    float[][] sampI = { {-874f, 345f, -102f, 902f, -769f, 96f} };
+    Irregular1DSet iSet = new Irregular1DSet(vis_tupleI, sampI);
+
+    // create ProductSet as cross-product of gSet and iSet
+    RealType[] vis_arrayP = {vis_xcoord, vis_ycoord, vis_xcoord};
+    RealTupleType vis_tupleP = new RealTupleType(vis_arrayP);
+    SampledSet[] sets = {gSet, iSet};
+    ProductSet pSet = new ProductSet(vis_tupleP, sets);
+
+    // run some tests
+    System.out.println("ProductSet created.");
+    System.out.println("ManifoldDimension = "+pSet.getManifoldDimension());
+    System.out.println("-----------------");
+    System.out.println("indexToValue test:");
+    int[] index1 = {0, 3, 6, 9, 12, 15, 18, 21};
+    float[][] value1 = pSet.indexToValue(index1);
+    for (int i=0; i<index1.length; i++) {
+      System.out.print("index "+index1[i]+" \t==> ("+value1[0][i]);
+      for (int j=1; j<value1.length; j++) {
+        System.out.print(", "+value1[j][i]);
+      }
+      System.out.println(")");
+    }
+
+    System.out.println("-----------------");
+    System.out.println("valueToIndex test:");
+    float[][] value2 = { {  10f,   40f,   90f,   25f,
+                            50f,  100f,   30f,   70f},
+                         {  35f,   30f,   80f,   35f,
+                            70f,   75f,   36f,   75f},
+                         {-880f, -890f, -870f,  350f,
+                           340f,  360f, -100f, -110f} };
+    int[] index2 = pSet.valueToIndex(value2);
+    for (int i=0; i<index2.length; i++) {
+      System.out.print("("+value2[0][i]);
+      for (int j=1; j<value2.length; j++) {
+        System.out.print(", "+value2[j][i]);
+      }
+      System.out.println(")\t==> index "+index2[i]);
+    }
+
+    System.out.println("------------------");
+    System.out.println("valueToInterp test:");
+    float[][] value3 = { {  15f,   50f,   80f,   25f,
+                            50f,  100f,   30f,   70f},
+                         {  45f,   40f,   70f,   35f,
+                            70f,   75f,   36f,   65f},
+                         {-800f, -750f, -810f,  300f,
+                           245f,  200f, -150f, -120f} };
+    int[][] indexI = new int[value3[0].length][];
+    float[][] weightI = new float[value3[0].length][];
+    pSet.valueToInterp(value3, indexI, weightI);
+    for (int l=0; l<value3[0].length; l++) {
+      System.out.print("("+value3[0][l]);
+      for (int k=1; k<value3.length; k++) {
+        System.out.print(", "+value3[k][l]);
+      }
+      System.out.print(")\t==> indices ["+indexI[l][0]);
+      for (int k=1; k<indexI[l].length; k++) {
+        System.out.print(", "+indexI[l][k]);
+      }
+      System.out.print("], ");
+      System.out.print(" weight total = ");
+      float wtotal = 0;
+      for (int k=0; k<weightI[l].length; k++) wtotal += weightI[l][k];
+      System.out.println(wtotal);
+    }
+
+    System.out.println();
+  }
+
+/* Here's the output:
+
+iris 21% java visad.ProductSet
+ProductSet created.
+ManifoldDimension = 3
+-----------------
+indexToValue test:
+index 0         ==> (12.5, 34.2, -874.0)
+index 3         ==> (36.78, 32.6, -874.0)
+index 6         ==> (87.8, 80.32, -874.0)
+index 9         ==> (26.5, 36.2, 345.0)
+index 12        ==> (52.12, 70.87, 345.0)
+index 15        ==> (97.2, 77.24, 345.0)
+index 18        ==> (29.74, 37.2, -102.0)
+index 21        ==> (67.8, 73.49, -102.0)
+-----------------
+valueToIndex test:
+(10.0, 35.0, -880.0)    ==> index 0
+(40.0, 30.0, -890.0)    ==> index 3
+(90.0, 80.0, -870.0)    ==> index 6
+(25.0, 35.0, 350.0)     ==> index 9
+(50.0, 70.0, 340.0)     ==> index 12
+(100.0, 75.0, 360.0)    ==> index 15
+(30.0, 36.0, -100.0)    ==> index 18
+(70.0, 75.0, -110.0)    ==> index 21
+------------------
+valueToInterp test:
+(15.0, 45.0, -800.0)    ==> indices [32, 0, 36, 4],  weight total = 1.0
+(50.0, 40.0, -750.0)    ==> indices [35, 19, 39, 23],  weight total = 1.0
+(80.0, 70.0, -810.0)    ==> indices [38, 6, 39, 7, 34, 2, 35, 3],  weight total = 1.0
+(25.0, 35.0, 300.0)     ==> indices [9, 41, 8, 40],  weight total = 1.0
+(50.0, 70.0, 245.0)     ==> indices [12, 44, 8, 40],  weight total = 1.0
+(100.0, 75.0, 200.0)    ==> indices [47, 15, 43, 11],  weight total = 1.0
+(30.0, 36.0, -150.0)    ==> indices [18, 34, 19, 35],  weight total = 1.0
+(70.0, 65.0, -120.0)    ==> indices [22, 38, 23, 39, 18, 34, 19, 35],  weight total = 1.0
+
+iris 22%
+
+*/
+
+}
+
diff --git a/visad/ProjectionControl.java b/visad/ProjectionControl.java
new file mode 100644
index 0000000..f4aaee5
--- /dev/null
+++ b/visad/ProjectionControl.java
@@ -0,0 +1,507 @@
+//
+// ProjectionControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.StringTokenizer;
+
+import visad.browser.Convert;
+import visad.util.Util;
+
+/**
+   ProjectionControl is the VisAD interface for controlling the Projection
+   from 3-D to 2-D.<P>
+*/
+public abstract class ProjectionControl extends Control {
+  /** matrix[] shouldn't be used by non-ProjectionControl classes */
+  protected double[] matrix = null;
+  private double[] savedProjectionMatrix = null;
+
+  private double[] asp = {1.0, 1.0, 1.0}; // WLH 24 Nov 2000
+
+  /** Length of a 2D matrix */
+  public static final int MATRIX2D_LENGTH = 6;
+  /** Major dimension of the 2D matrix */
+  public static final int MATRIX2D_MAJOR = 3;
+  /** Minor dimension of the 2D matrix */
+  public static final int MATRIX2D_MINOR = 2;
+
+  /** Length of a 2D matrix */
+  public static final int MATRIX3D_LENGTH = 16;
+  /** Major dimension of the 3D matrix */
+  public static final int MATRIX3D_MAJOR = 4;
+  /** Minor dimension of the 3D matrix */
+  public static final int MATRIX3D_MINOR = 4;
+
+  /**
+   * Construct a ProjectionControl for the display in question.
+   * @param d  display to control
+   * @throws VisADException  d already has a ProjectionControl or some other
+   *                         VisAD Error
+   */
+  public ProjectionControl(DisplayImpl d) throws VisADException {
+    super(d);
+    if (d.getProjectionControl() != null) {
+      throw new DisplayException("display already has a ProjectionControl");
+    }
+  }
+
+  /**
+   * Returns a copy of the graphics projection matrix.  The matrix has
+   * 6 elements in the 2-D case and 16 elements in the 3-D case.
+   *
+   * @return			A copy of the graphics projection matrix.
+   */
+  public double[] getMatrix() {
+    double[] c = new double[matrix.length];
+    System.arraycopy(matrix, 0, c, 0, matrix.length);
+    return c;
+  }
+
+  /** 
+   * Set the matrix that defines the graphics projection 
+   * @param m array of the matrix values (16 elements in Java3D case, 
+   *          6 elements in Java2D case) 
+   * @throws VisADException   invalid matrix length
+   * @throws RemoteException  Java RMI failure.
+   */
+  public void setMatrix(double[] m)
+         throws VisADException, RemoteException {
+    if (m == null) return;
+    if (m.length != matrix.length) {
+      throw new DisplayException("setMatrix: input length must be " +
+                                 matrix.length);
+    }
+    System.arraycopy(m, 0, matrix, 0, matrix.length);
+  }
+
+  /** 
+   * Get a string that can be used to reconstruct this control later 
+   * @return String representation of this object that can be used for
+   *         reconstruction.
+   */
+  public String getSaveString() {
+    final int len = matrix.length;
+
+    final int major, minor;
+    if (len == MATRIX2D_LENGTH) {
+      major = MATRIX2D_MAJOR;
+      minor = MATRIX2D_MINOR;
+    } else if (len == MATRIX3D_LENGTH) {
+      major = MATRIX3D_MAJOR;
+      minor = MATRIX3D_MINOR;
+    } else {
+      major = len;
+      minor = 1;
+    }
+
+    StringBuffer sb = new StringBuffer(25 * len);
+    sb.append(major);
+    if (minor > 1) {
+      sb.append(" x ");
+      sb.append(minor);
+    }
+    sb.append('\n');
+
+    for (int j=0; j<minor; j++) {
+      for (int i=0; i<major; i++) {
+        if (i > 0) {
+          sb.append(' ');
+        }
+        sb.append(matrix[major * j + i]);
+      }
+      sb.append('\n');
+    }
+
+    return sb.toString();
+  }
+
+  /** 
+   * Set the properties of this control using the specified save string 
+   * @param save  String to use for setting the properties.
+   * @throws VisADException   VisAD failure.
+   * @throws RemoteException  Java RMI failure.
+   */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    int eol = save.indexOf('\n');
+    if (eol < 0) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save.substring(0, eol));
+    int numTokens = st.countTokens();
+
+    // determine matrix size
+    int size = -1;
+    if (numTokens == 3) {
+      int len = Convert.getInt(st.nextToken());
+      if (len < 1) {
+        throw new VisADException("First matrix dimension is not positive");
+      }
+      if (!st.nextToken().equalsIgnoreCase("x")) {
+        throw new VisADException("Invalid save string");
+      }
+      int len0 = Convert.getInt(st.nextToken());
+      if (len0 < 1) {
+        throw new VisADException("Second matrix dimension is not positive");
+      }
+      size = len * len0;
+    }
+    else if (numTokens == 1) {
+      size = Convert.getInt(st.nextToken());
+      if (size < 1) {
+        throw new VisADException("Matrix size is not positive");
+      }
+    }
+    else throw new VisADException("Cannot determine matrix size");
+
+    // get matrix entries
+    st = new StringTokenizer(save.substring(eol + 1));
+    numTokens = st.countTokens();
+    if (numTokens < size) {
+      throw new VisADException("Not enough matrix entries");
+    }
+    double[] m = new double[size];
+    for (int i=0; i<size; i++) m[i] = Convert.getDouble(st.nextToken());
+    setMatrix(m);
+  }
+
+  /** 
+   * Set aspect ratio of axes
+   * @param aspect ratios; 3 elements for Java3D, 2 for Java2D 
+   * @throws VisADException   VisAD failure.
+   * @throws RemoteException  Java RMI failure.
+   */
+  public abstract void setAspect(double[] aspect)
+         throws VisADException, RemoteException;
+
+  // WLH 24 Nov 2000
+  /** 
+   * Set aspect ratio of axes, in ScalarMaps rather than matrix
+   * @param aspect ratios; 3 elements for Java3D, 2 for Java2D 
+   * @throws VisADException   VisAD failure.
+   * @throws RemoteException  Java RMI failure.
+   */
+  public void setAspectCartesian(double[] aspect)
+         throws VisADException, RemoteException {
+    if (aspect != null) {
+      for (int i=0; i<aspect.length; i++) {
+        if (aspect[i] <= 0.0) {
+          throw new DisplayException("aspect must be positive");
+        }
+        asp[i] = aspect[i];
+      }
+    }
+    getDisplay().setAspectCartesian(asp);
+  }
+
+  public double[] getAspectCartesian() {
+    return (double[]) asp.clone();
+  }
+
+  /**
+   * Saves the current display projection matrix.  The projection may 
+   * later be restored by the method <code>resetProjection()</code>.
+   * @see #resetProjection()
+   */
+  public void saveProjection()
+  {
+    savedProjectionMatrix = getMatrix();
+  }
+
+  /** 
+   * Get the matrix that defines the saved graphics projection 
+   * @return array of the matrix values (16 elements in Java3D case, 
+   *          6 elements in Java2D case) 
+   */
+  public double[] getSavedProjectionMatrix() {
+    double[] c = new double[savedProjectionMatrix.length];
+    System.arraycopy(
+      savedProjectionMatrix, 0, c, 0, savedProjectionMatrix.length);
+    return c;
+  }
+  /**
+   * Restores to projection matrix at time of last <code>saveProjection()</code>
+   * call -- if one was made -- or to initial projection otherwise.
+   * @see #saveProjection()
+   * @throws VisADException   VisAD failure.
+   * @throws RemoteException  Java RMI failure.
+   */
+  public void resetProjection()
+         throws VisADException, RemoteException
+  {
+    setMatrix(savedProjectionMatrix);
+  }
+
+  /** Default scaling factor for 2D matrix */
+  public static final double SCALE2D = 0.65;
+  /** Inverse of  SCALE2D */
+  public static final double INVSCALE2D = 1.0 / SCALE2D;
+
+  /**
+   * Convert a 2D matrix to a 3D matrix, retaining the scale and aspect
+   * of the 2D matrix.
+   * @param matrix  2D matrix to convert
+   * @throws  VisADException  wrong length for matrix (not MATRIX2D_LENGTH)
+   */
+  public static double[] matrix2DTo3D(double[] matrix)
+         throws VisADException {
+    if (matrix.length != MATRIX2D_LENGTH) {
+      throw new DisplayException("matrix2DTo3D: input length must be " +
+                                 MATRIX2D_LENGTH);
+    }
+    double[] mat = new double[MATRIX3D_LENGTH];
+    for (int i=0; i<MATRIX3D_LENGTH; i++) mat[i] = 0.0;
+    mat[0] = SCALE2D * matrix[0];
+    mat[1] = SCALE2D * matrix[2];
+    mat[3] = matrix[4];
+    mat[4] = SCALE2D * matrix[1];
+    mat[5] = -SCALE2D * matrix[3];
+    mat[7] = -matrix[5];
+    mat[10] = 1.0;
+    mat[15] = 1.0;
+    return mat;
+  }
+
+  /**
+   * Convert a 3D matrix to a 2D matrix, retaining the scale and aspect
+   * of the 3D matrix.
+   * @param matrix  3D matrix to convert
+   * @throws  VisADException  wrong length for matrix (not MATRIX3D_LENGTH)
+   */
+  public static double[] matrix3DTo2D(double[] matrix)
+         throws VisADException {
+    if (matrix.length != MATRIX3D_LENGTH) {
+      throw new DisplayException("matrix3DTo2D: input length must be " +
+                                 MATRIX3D_LENGTH);
+    }
+    double[] mat = new double[MATRIX2D_LENGTH];
+    mat[0] = INVSCALE2D * matrix[0];
+    mat[1] = INVSCALE2D * matrix[4];
+    mat[2] = INVSCALE2D * matrix[1];
+    mat[3] = -INVSCALE2D * matrix[5];
+    mat[4] = matrix[3];
+    mat[5] = -matrix[7];
+    return mat;
+  }
+
+  /**
+   * Convert a 3D matrix to a 2D matrix or vice-versa, retaining the scale 
+   * and aspect of the original matrix.  Helper interface to pass an unknown
+   * matrix to <CODE>matrix3DTo2D</CODE> or <CODE>matrix2DTo3D</CODE>.
+   * @see #matrix3DTo2D
+   * @see #matrix2DTo3D
+   * @param matrix  matrix to convert
+   * @throws  VisADException  wrong length for matrix (not MATRIX3D_LENGTH)
+   */
+  public static double[] matrixDConvert(double[] matrix)
+         throws VisADException {
+    if (matrix.length == MATRIX3D_LENGTH) {
+      return matrix3DTo2D(matrix);
+    }
+    if (matrix.length == MATRIX2D_LENGTH) {
+      return matrix2DTo3D(matrix);
+    }
+    throw new DisplayException("matrixDConvert: input length must be " +
+                               MATRIX3D_LENGTH + " or " + MATRIX2D_LENGTH);
+  }
+
+  /** clear all 'pairs' in switches that involve re */
+  public void clearSwitches(DataRenderer re) {
+  }
+
+  private boolean matrixEquals(double[] newMatrix)
+  {
+    if (matrix == null) {
+      if (newMatrix != null) {
+        return false;
+      }
+    } else if (newMatrix == null) {
+      return false;
+    } else {
+      if (matrix.length != newMatrix.length) {
+        return false;
+      }
+
+      for (int i = 0; i < matrix.length; i++) {
+        if (!Util.isApproximatelyEqual(matrix[i], newMatrix[i])) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  // WLH 24 Nov 2000
+  private boolean aspEquals(double[] newAsp)
+  {
+    if (asp == null) {
+      if (newAsp != null) {
+        return false;
+      }
+    } else if (newAsp == null) {
+      return false;
+    } else {
+      if (asp.length != newAsp.length) {
+        return false;
+      }
+
+      for (int i = 0; i < asp.length; i++) {
+        if (!Util.isApproximatelyEqual(asp[i], newAsp[i])) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /** 
+   * Copy the state of a remote control to this control 
+   * @param  rmt  remote control
+   * @throws VisADException  rmt is null or not a ProjectionControl or
+   *                         some other VisAD error
+   */
+  public void syncControl(Control rmt)
+        throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof ProjectionControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    ProjectionControl pc = (ProjectionControl )rmt;
+
+    if (!matrixEquals(pc.matrix)) {
+      try {
+        setMatrix(pc.matrix);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not set matrix: " + re.getMessage());
+      }
+    }
+    // WLH 24 Nov 2000
+    if (!aspEquals(pc.asp)) {
+      try {
+        setAspectCartesian(pc.asp);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not setAspectCartesian: " + re.getMessage());
+      }
+    }
+
+  }
+
+  /**
+   * Check to see if the object in question is equal to this ProjectionControl.
+   * The two are equal if they are both ProjectionControls and their projection
+   * matrices are equal.
+   * @param o  object in question.
+   */
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    ProjectionControl pc = (ProjectionControl )o;
+
+    if (!matrixEquals(pc.matrix)) {
+      return false;
+    }
+
+    // WLH 24 Nov 2000
+    if (!aspEquals(pc.asp)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Create a clone of this ProjectionControl.
+   * @return  clone
+   */
+  public Object clone()
+  {
+    ProjectionControl pc = (ProjectionControl )super.clone();
+    if (matrix != null) {
+      pc.matrix = (double[] )matrix.clone();
+    }
+    // WLH 24 Nov 2000
+    if (asp != null) {
+      pc.asp = (double[] )asp.clone();
+    }
+
+    return pc;
+  }
+
+  /**
+   * A string representation of this ProjectionControl.
+   * @return human readable string that tells the properties of this control.
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("ProjectionControl[");
+    if (matrix == null) {
+      buf.append("null");
+    } else {
+      int major, minor;
+      if (matrix.length == MATRIX2D_LENGTH) {
+        major = MATRIX2D_MAJOR;
+        minor = MATRIX2D_MINOR;
+      } else if (matrix.length == MATRIX3D_LENGTH) {
+        major = MATRIX3D_MAJOR;
+        minor = MATRIX3D_MINOR;
+      } else {
+        major = 1;
+        minor = matrix.length;
+      }
+
+      int offset = 0;
+      for (int i = 0; i < major; i++) {
+        if (i > 0) {
+          buf.append(',');
+        }
+        for (int j = 0; j < minor; j++) {
+          buf.append(j == 0 ? '(' : ',');
+          buf.append(matrix[offset + j]);
+        }
+        buf.append(')');
+        offset += minor;
+      }
+    }
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
+
diff --git a/visad/PromiscuousUnit.java b/visad/PromiscuousUnit.java
new file mode 100644
index 0000000..cc911ef
--- /dev/null
+++ b/visad/PromiscuousUnit.java
@@ -0,0 +1,203 @@
+//
+// PromiscuousUnit.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+/**
+ * PromiscuousUnit is the VisAD class for units that are convertable with any
+ * other Unit.
+ * 
+ * Instances are immutable.
+ */
+public final class PromiscuousUnit extends Unit {
+    private static final long   serialVersionUID    = 1L;
+    static final Unit           promiscuous         = new PromiscuousUnit();
+
+    private PromiscuousUnit() {
+        super("UniversalUnit");
+    }
+
+    /**
+     * <p>
+     * Indicates if this instance is dimensionless. A unit is dimensionless if
+     * it is a measure of a dimensionless quantity like angle or concentration.
+     * Examples of dimensionless units include radian, degree, steradian, and
+     * "g/kg".
+     * </p>
+     * 
+     * <p>
+     * This implementation always returns <code>false</code> because the typical
+     * use of this method is to determine whether or not a function of
+     * dimensionless values (e.g. sin(), log()) may be applied to values in this
+     * unit and such functions shouldn't be applied to values in this unit.
+     * Instead, the client should ensure that the values are in a true,
+     * dimensionless unit.
+     * </p>
+     * 
+     * @return True if an only if this unit is dimensionless.
+     */
+    @Override
+    public boolean isDimensionless() {
+        return false;
+    }
+
+    /**
+     * Clones this unit, changing the identifier. This method always throws an
+     * exception because promiscuous units may not be cloned.
+     * 
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * @throws UnitException
+     *             Promiscuous units may not be cloned. Always thrown.
+     */
+    @Override
+    protected Unit protectedClone(final String identifier) throws UnitException {
+        throw new UnitException("Promiscuous units may not be cloned");
+    }
+
+    /**
+     * Returns the definition of this unit. For promiscuous units, this is the
+     * same as the identifier.
+     * 
+     * @return The definition of this unit. Won't be <code>null
+   *                    </code>
+     *         but may be empty.
+     */
+    @Override
+    public String getDefinition() {
+        return getIdentifier();
+    }
+
+    @Override
+    public Unit scale(final double amount) throws UnitException {
+        return this;
+    }
+
+    @Override
+    public Unit shift(final double offset) throws UnitException {
+        return this;
+    }
+
+    @Override
+    public Unit log(final double base) {
+        return this;
+    }
+
+    @Override
+    public Unit pow(final int power) {
+        return this;
+    }
+
+    @Override
+    public Unit root(final int root) {
+        return this;
+    }
+
+    @Override
+    public Unit pow(final double power) {
+        return this;
+    }
+
+    @Override
+    public Unit multiply(final Unit that) {
+        return that;
+    }
+
+    @Override
+    public Unit divide(final Unit that) throws UnitException {
+        return CommonUnit.dimensionless.divide(that);
+    }
+
+    public Unit divide(final PromiscuousUnit that) {
+        return that;
+    }
+
+    @Override
+    protected Unit divideInto(final Unit that) throws UnitException {
+        return that;
+    }
+
+    @Override
+    public double[] toThis(final double[] values, final Unit that) {
+        return values;
+    }
+
+    @Override
+    public double[] toThat(final double[] values, final Unit that) {
+        return values;
+    }
+
+    @Override
+    public float[] toThis(final float[] values, final Unit that) {
+        return values;
+    }
+
+    @Override
+    public float[] toThat(final float[] values, final Unit that) {
+        return values;
+    }
+
+    /**
+     * Indicate whether this unit is convertible with another unit. A
+     * PromiscuousUnit is always convertible with another unit.
+     * 
+     * @param unit
+     *            The other unit.
+     * @return True, always.
+     */
+    @Override
+    public boolean isConvertible(final Unit unit) {
+        return true;
+    }
+
+    @Override
+    public boolean equals(final Unit unit) {
+        return (unit instanceof PromiscuousUnit);
+    }
+
+    /**
+     * Returns the hash code of this instance. {@link Object#hashCode()} should
+     * be overridden whenever {@link Object#equals(Object)} is.
+     * 
+     * @return The hash code of this instance (includes the values).
+     */
+    @Override
+    public int hashCode() {
+        if (hashCode == 0) {
+            hashCode = System.identityHashCode(promiscuous);
+        }
+        return hashCode;
+    }
+
+    /**
+     * Returns the dimensionless unit one with the identifier "1".
+     */
+    @Override
+    public DerivedUnit getDerivedUnit() {
+        return new DerivedUnit("1");
+    }
+}
diff --git a/visad/QuantityDimension.java b/visad/QuantityDimension.java
new file mode 100644
index 0000000..6e70400
--- /dev/null
+++ b/visad/QuantityDimension.java
@@ -0,0 +1,280 @@
+//
+// QuantityDimension.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.io.Serializable;
+
+/**
+ * This class represents the dimension of a quantity. For example, consider a
+ * nonrelativistic particle of mass <VAR>m</VAR> in uniform motion which travels
+ * a distance <VAR>l</VAR> in a time <VAR>t</VAR>. Its velocity is
+ * <VAR>v=l/t</VAR> and its kinetic energy is
+ * <VAR>E=mv<SUP>2</SUP>/2=ml<SUP>2</SUP>t<SUP>-2</SUP>/2</VAR>. The dimension
+ * of <VAR>E</VAR> is dim <VAR>E</VAR>=ML<SUP>2</SUP>T<SUP>-2</SUP> and the
+ * dimensional exponents are 1, 2, and -2.
+ * 
+ * A QuantityDimension is immutable.
+ */
+public final class QuantityDimension implements Serializable, Comparable {
+	/**
+	 * The dimensional exponents.
+	 */
+	private/* final */byte[]	exponents;
+
+	/**
+	 * Constructs from nothing (i.e. constructs a dimensionless quantity).
+	 */
+	public QuantityDimension() {
+		initialize(0);
+	}
+
+	/**
+	 * Constructs from another dimension of a quantity.
+	 */
+	public QuantityDimension(final QuantityDimension that) {
+		initialize(that.exponents.length);
+
+		System
+				.arraycopy(that.exponents, 0, exponents, 0,
+						that.exponents.length);
+	}
+
+	/**
+	 * Constructs from an existing Unit.
+	 */
+	public QuantityDimension(final Unit unit) throws UnitException {
+		initialize(unit.getDerivedUnit());
+	}
+
+	/**
+	 * Initialize from a number of base quantities. Private to ensure use of the
+	 * public constructors and the concomitant setting of the dimensional
+	 * exponents.
+	 */
+	private void initialize(final int n) {
+		exponents = new byte[n];
+	}
+
+	/**
+	 * Initialize from a DerivedUnit.
+	 */
+	private void initialize(final DerivedUnit unit) throws UnitException {
+		initialize(BaseQuantity.size());
+
+		for (int i = 0; i < unit.factors.length; ++i) {
+			final String name = unit.factors[i].baseUnit.quantityName();
+			final BaseQuantity baseQuantity = BaseQuantity.get(name);
+
+			if (baseQuantity == null) {
+				throw new UnitException("No base quantity for \"" + name + "\"");
+			}
+
+			exponents[baseQuantity.getIndex()] = (byte) unit.factors[i].power;
+		}
+	}
+
+	/**
+	 * Compare this dimension of a quantity to another.
+	 */
+	public int compareTo(final Object obj) {
+		final QuantityDimension that = (QuantityDimension) obj;
+
+		return isShorterThan(that)
+				? compare(this, that)
+				: -compare(that, this);
+	}
+
+	/**
+	 * Indicate whether or not this dimension of a quantity is shorter or equal
+	 * to another.
+	 */
+	private boolean isShorterThan(final QuantityDimension that) {
+		return exponents.length < that.exponents.length;
+	}
+
+	/**
+	 * Compare a shorter dimensional quantity to a longer one.
+	 * 
+	 * @precondition <code>shorter.exponents.length <=
+	 * 			longer.exponents.length</code>.
+	 */
+	private static int compare(final QuantityDimension shorter,
+			final QuantityDimension longer) {
+		int n = 0;
+
+		for (int i = 0; n == 0 && i < shorter.exponents.length; ++i) {
+			n = shorter.exponents[i] - longer.exponents[i];
+		}
+
+		for (int i = shorter.exponents.length; n == 0
+				&& i < longer.exponents.length; ++i) {
+			n = -longer.exponents[i];
+		}
+
+		return n;
+	}
+
+	/**
+	 * Indicate whether or not this dimension of a quantity is the same as
+	 * another.
+	 */
+	@Override
+	public boolean equals(final Object obj) {
+		return compareTo(obj) == 0;
+	}
+
+	/**
+	 * Raise this dimension of a quantity by a power.
+	 */
+	public QuantityDimension raise(final int power) {
+		final QuantityDimension newDimension = new QuantityDimension(this);
+
+		for (int i = 0; i < newDimension.exponents.length; ++i) {
+			newDimension.exponents[i] *= power;
+		}
+
+		return newDimension;
+	}
+
+	/**
+	 * Multiply this dimension of a quantity by another.
+	 */
+	public QuantityDimension multiply(final QuantityDimension that) {
+		return isShorterThan(that)
+				? multiply(this, that)
+				: multiply(that, this);
+	}
+
+	/**
+	 * Multiply a shorter dimension of a quantity by a longer one.
+	 */
+	private static QuantityDimension multiply(final QuantityDimension shorter,
+			final QuantityDimension longer) {
+		final QuantityDimension newDimension = new QuantityDimension(longer);
+
+		for (int i = 0; i < shorter.exponents.length; ++i) {
+			newDimension.exponents[i] += shorter.exponents[i];
+		}
+
+		return newDimension;
+	}
+
+	/**
+	 * Divide this dimension of a quantity by another.
+	 */
+	public QuantityDimension divide(final QuantityDimension that) {
+		return multiply(that.raise(-1));
+	}
+
+	/**
+	 * Indicate whether or not this dimension of a quantity is dimensionless.
+	 */
+	public boolean isDimensionless() {
+		for (int i = 0; i < exponents.length; ++i) {
+			if (exponents[i] != 0) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Return a string representation of this dimension of a quantity.
+	 */
+	@Override
+	public String toString() {
+		String rep;
+
+		if (isDimensionless()) {
+			rep = "1"; // dimensionless quantity
+		}
+		else {
+			final StringBuffer buf = new StringBuffer(128);
+
+			for (int i = 0; i < exponents.length; ++i) {
+				final int exponent = exponents[i];
+
+				if (exponent != 0) {
+					if (buf.length() > 0) {
+						buf.append(" ");
+					}
+
+					buf.append("(");
+					buf.append(BaseQuantity.get(i).getName());
+					buf.append(")");
+
+					if (exponent != 1) {
+						buf.append("^");
+						buf.append(exponent);
+					}
+				}
+			}
+
+			rep = buf.toString();
+		}
+
+		return rep;
+	}
+
+	/**
+	 * Test this class.
+	 */
+	// /*
+	public static void main(final String[] args) {
+		final QuantityDimension dim1 = new QuantityDimension();
+
+		dim1.exponents[0] = -3;
+		dim1.exponents[1] = -2;
+		dim1.exponents[2] = -1;
+		dim1.exponents[3] = 0;
+		dim1.exponents[4] = 1;
+		dim1.exponents[5] = 2;
+		dim1.exponents[6] = 3;
+		dim1.exponents[7] = 4;
+
+		System.out.println("dim1=(" + dim1 + ")");
+		System.out.println("dim1.equals(dim1)=" + dim1.equals(dim1));
+		System.out.println("dim1.compareTo(dim1)=" + dim1.compareTo(dim1));
+
+		final QuantityDimension dim2 = new QuantityDimension();
+
+		dim2.exponents[0] = -3;
+		dim2.exponents[1] = -2;
+		dim2.exponents[2] = -1;
+		dim2.exponents[3] = 1;
+		dim2.exponents[4] = 1;
+		dim2.exponents[5] = 2;
+		dim2.exponents[6] = 3;
+		dim2.exponents[7] = 4;
+
+		System.out.println("dim2=(" + dim2 + ")");
+		System.out.println("dim1.equals(dim2)=" + dim1.equals(dim2));
+		System.out.println("dim1.compareTo(dim2)=" + dim1.compareTo(dim2));
+	}
+	// */
+}
diff --git a/visad/QuickSort.java b/visad/QuickSort.java
new file mode 100644
index 0000000..02243d0
--- /dev/null
+++ b/visad/QuickSort.java
@@ -0,0 +1,237 @@
+//
+// QuickSort.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   QuickSort sorts a set of samples in R using a quicksort
+   algorithm combined with an insertion sort algorithm to
+   avoid an excess number of recursive calls.<P>
+
+   All of QuickSort's variables and methods are static.<P>
+*/
+public class QuickSort {
+
+  // number of elements to sort with insertion sort
+  private static final int CHEAT_NUM = 15;
+
+  // default number of elements to test in main method
+  private static final int ELEMENTS = 100000;
+
+  // Insertion Sort
+  private static void insertion(float a[], int[] p, int lo, int hi)
+                                      throws VisADException {
+    for (int i=lo+1; i<=hi; i++) {
+      int j = i;
+      float B = a[i];
+      int P = p[i];
+      while ((j > 0) && (a[j-1] > B)) {
+        a[j] = a[j-1];
+        p[j] = p[j-1];
+        j--;
+      }
+      a[j] = B;
+      p[j] = P;
+    }
+  }
+
+  // version for doubles
+  private static void insertion(double a[], int[] p, int lo, int hi)
+                                      throws VisADException {
+    for (int i=lo+1; i<=hi; i++) {
+      int j = i;
+      double B = a[i];
+      int P = p[i];
+      while ((j > 0) && (a[j-1] > B)) {
+        a[j] = a[j-1];
+        p[j] = p[j-1];
+        j--;
+      }
+      a[j] = B;
+      p[j] = P;
+    }
+  }
+
+  // Quick Sort
+  private static void sort(float a[], int[] p, int lo0, int hi0)
+                                      throws VisADException {
+    // call the insertion sort if few enough elements
+    if (hi0-lo0 < CHEAT_NUM) {
+      insertion(a, p, lo0, hi0);
+    }
+    else {
+      int lo = lo0;
+      int hi = hi0;
+
+      // start in the middle
+      float mid = a[(lo0+hi0)/2];
+
+      // loop through the array until indices cross
+      while (lo <= hi) {
+        // find lo-most element >= partition element
+        while ( (lo < hi0) && (a[lo] < mid) ) ++lo;
+
+        // find hi-most element <= partition element
+        while ( (hi > lo0) && (a[hi] > mid) ) --hi;
+
+        // swap indices if they have not crossed
+        if (lo <= hi) {
+          int P = p[lo];
+          p[lo] = p[hi];
+          p[hi] = P;
+          float T = a[lo];
+          a[lo++] = a[hi];
+          a[hi--] = T;
+        }
+      }
+      // sort the left partition if necessary
+      if (lo0 < hi) sort(a, p, lo0, hi);
+
+      // sort the right partition if necessary
+      if (lo < hi0) sort(a, p, lo, hi0);
+    }
+  }
+
+  // version for doubles
+  private static void sort(double a[], int[] p, int lo0, int hi0)
+                                      throws VisADException {
+    // call the insertion sort if few enough elements
+    if (hi0-lo0 < CHEAT_NUM) {
+      insertion(a, p, lo0, hi0);
+    }
+    else {
+      int lo = lo0;
+      int hi = hi0;
+
+      // start in the middle
+      double mid = a[(lo0+hi0)/2];
+
+      // loop through the array until indices cross
+      while (lo <= hi) {
+        // find lo-most element >= partition element
+        while ( (lo < hi0) && (a[lo] < mid) ) ++lo;
+
+        // find hi-most element <= partition element
+        while ( (hi > lo0) && (a[hi] > mid) ) --hi;
+
+        // swap indices if they have not crossed
+        if (lo <= hi) {
+          int P = p[lo];
+          p[lo] = p[hi];
+          p[hi] = P;
+          double T = a[lo];
+          a[lo++] = a[hi];
+          a[hi--] = T;
+        }
+      }
+      // sort the left partition if necessary
+      if (lo0 < hi) sort(a, p, lo0, hi);
+
+      // sort the right partition if necessary
+      if (lo < hi0) sort(a, p, lo, hi0);
+    }
+  }
+
+  /**
+   * Sort the array in place and return an array of the
+   * orginal indices.
+   * @param  a  array of floats to sort
+   * @return  array of the original indices of each  element of a.
+   */
+  public static int[] sort(float a[]) throws VisADException {
+    int[] p = new int[a.length];
+    for (int i=0; i<a.length; i++) p[i] = i;
+    sort(a, p, 0, a.length-1);
+    return p;
+  }
+
+  /**
+   * Sort the array in place and return an array of the
+   * orginal indices.
+   * @param  a  array of doubles to sort
+   * @return  array of the original indices of each  element of a.
+   */
+  public static int[] sort(double a[]) throws VisADException {
+    int[] p = new int[a.length];
+    for (int i=0; i<a.length; i++) p[i] = i;
+    sort(a, p, 0, a.length-1);
+    return p;
+  }
+
+  /* run 'java visad.QuickSort [elements]' to test the QuickSort class.
+     [elements] defaults to 100000, or you can specify your own value. */
+  public static void main(String[] argv) throws VisADException {
+    int elements = ELEMENTS;
+    if (argv.length > 0) {
+      try {
+        elements = Integer.parseInt(argv[0]);
+      }
+      catch (Exception e) {
+        System.out.println("Usage: java visad.QuickSort "
+                          +"[number of elements to sort]");
+        System.exit(1);
+      }
+    }
+    System.out.print("Creating array of "+elements+" random elements...");
+    long start1 = System.currentTimeMillis();
+    float[] test = new float[elements];
+    // make up an array with random elements
+    for (int i=0; i<elements; i++) {
+      test[i] = (float) (1000*Math.random());
+    }
+    long end1 = System.currentTimeMillis();
+    float time1 = (float) (end1-start1) /1000;
+    System.out.println("\nCreation of random elements took "
+                                          +time1+" seconds.");
+    System.out.print("Sorting...");
+    long start2 = System.currentTimeMillis();
+    int[] p = sort(test);
+    long end2 = System.currentTimeMillis();
+    System.out.println("done.");
+    for (int i=1; i<elements; i++) {
+      if (test[i-1] > test[i]) {
+        System.out.println("Error in sort, values not in order!");
+        System.exit(1);
+      }
+    }
+    float time2 = (float) (end2-start2) /1000;
+    System.out.println("Sort of elements took "+time2+" seconds.");
+    System.exit(0);
+  }
+
+/* Here's the output:
+
+iris 99% java visad.QuickSort 500000
+Creating array of 500000 random elements...
+Creation of random elements took 11.691 seconds.
+Sorting...done.
+Sort of elements took 4.953 seconds.
+iris 100%
+
+*/
+
+}
+
diff --git a/visad/README b/visad/README
new file mode 100644
index 0000000..e3d8cb7
--- /dev/null
+++ b/visad/README
@@ -0,0 +1,399 @@
+
+                           VisAD
+
+1. Introduction
+
+VisAD is a pure Java system for interactive and collaborative
+visualization and analysis of numerical data.  It is described
+in detail in the:
+
+  VisAD Java Class Library Developers Guide
+
+available from the VisAD web page at:
+
+  http://www.ssec.wisc.edu/~billh/visad.html
+
+This README file primarily consists of installation
+instructions.
+
+
+2. Downloading the VisAD Source Code
+
+To download the VisAD source code, first make sure the current
+directory is a directory in your CLASSPATH (which we will refer
+to as '/parent_dir' through the rest of this README file).
+Then get:
+
+  ftp://ftp.ssec.wisc.edu/pub/visad-2.0/visad_src-2.0.jar
+ 
+If you have previously downloaded the VisAD source you
+should run 'make clear' in your visad directory to clear
+out the old source files before you unpack the new source.
+
+Unpack the jar file by running:
+ 
+  jar xvf visad_src-2.0.jar
+ 
+Unpacking VisAD will create the following sub-directories:
+
+  visad				// the core VisAD package
+  visad/ss			// VisAD Spread Sheet
+  visad/formula			// formula parser
+  visad/java3d			// Java3D displays for VisAD
+  visad/java2d			// Java2D displays for VisAD
+  visad/util			// VisAD UI utilities
+  visad/collab                  // collaboration support
+  visad/cluster                 // data and displays distributed on clusters
+  visad/python                  // python scripts for VisAD
+  visad/browser                 // connecting applets to VisAD servers
+  visad/math                    // math (fft, histogram) operations
+  visad/matrix                  // JAMA (matlab) matrix operations
+  visad/data			// VisAD data (file) format adapters
+  visad/data/fits		// VisAD - FITS file adapter
+  visad/data/netcdf		// VisAD - netCDF file adapter
+  visad/data/netcdf/in		// netCDF file adapter input
+  visad/data/netcdf/out		// netCDF file adapter output
+  visad/data/netcdf/units	// units parser for netCDF adapter
+  visad/data/hdfeos		// VisAD - HDF-EOS file adapter
+  visad/data/hdfeos/hdfeosc	// native interface to HDF-EOS
+  visad/data/vis5d		// VisAD - Vis5D file adapter
+  visad/data/mcidas		// VisAD - McIDAS file adapter
+  visad/data/gif		// VisAD - GIF file adapter
+  visad/data/tiff		// VisAD - TIFF file adapter
+  visad/data/jai		// VisAD file adapter for various image formats
+  visad/data/biorad             // VisAD - Biorad file adapter
+  visad/data/visad		// VisAD serialized object file adapter
+  visad/data/hdf5 		// VisAD - HDF-5 file adapter
+  visad/data/hdf5/hdf5objects	// VisAD - HDF-5 file adapter
+  visad/data/amanda             // VisAD - F2000 file adapter (neutrino events)
+  visad/paoloa			// GoesCollaboration application
+  visad/paoloa/spline		// spline fitting application
+  visad/aune			// ShallowFluid application
+  visad/benjamin		// Galaxy application
+  visad/rabin                   // Rainfall estimation spread sheet
+  visad/bom                     // wind barb rendering for ABOM
+  visad/jmet                    // JMet - Java Meteorology package
+  visad/aeri                    // Aeri data visualization
+  visad/georef                  // specialized earth coordinates
+  visad/meteorology             // meteorology
+  visad/examples		// small application examples
+  nom/tam/fits			// Java FITS file binding
+  nom/tam/util			// Java FITS file binding
+  nom/tam/test			// Java FITS file binding
+  ucar/multiarray		// Java netCDF file binding
+  ucar/util			// Java netCDF file binding
+  ucar/netcdf			// Java netCDF file binding
+  ucar/tests			// Java netCDF file binding
+  edu/wisc/ssec/mcidas          // Java McIDAS file binding
+  edu/wisc/ssec/mcidas/adde     // Java McIDAS file binding
+  ncsa/hdf/hdf5lib              // Java HDF-5 file binding
+  ncsa/hdf/hdf5lib/exceptions   // Java HDF-5 file binding
+  ij                            // ImageJ
+  ij/gui                        // ImageJ
+  ij/io                         // ImageJ
+  ij/measure                    // ImageJ
+  ij/plugin                     // ImageJ
+  ij/plugin/filter              // ImageJ
+  ij/plugin/frame               // ImageJ
+  ij/process                    // ImageJ
+  ij/text                       // ImageJ
+  ij/util                       // ImageJ
+
+These directories correspond to the packages in distributed with
+VisAD, except that the classes in visad/examples are in the
+default package (i.e., they do not include a package statement).
+ 
+
+3. Building VisAD
+
+Your CLASSPATH sould include:
+
+  1. The parent directory of your visad directory.
+  2. The current directory.
+
+Thus if VisAD is installed at /parent_dir/visad and you use csh,
+your .cshrc file should include:
+
+  setenv CLASSPATH /parent_dir:.
+
+VisAD requires JDK 1.2 and Java3D.  More information about
+these is available at:
+
+  http://java.sun.com/
+
+On systems that support Unix make, you can simply run:
+
+  make debug
+
+to compile the Java source code in all the directories unpacked
+from the source distribution, as well as native code in the
+visad/data/hdfeos/hdfeosc directory and certain application
+directories.  If you want 'make debug' to compile native
+libraries, then you may need to change the line:
+
+  JAVADIR=/opt/java
+
+in visad/Makefile if your java is installed in a directory
+other than /opt/java.
+
+If you have NMAKE on WinNT you may run:
+
+  nmake -f Makefile.WinNT debug
+
+This does not compile native code.
+
+Note that using 'make debug' rather than 'make compile' will
+enable you to run using jdb in place of java in order to make
+error reports that include line numbers in stack dumps.
+
+If you cannot use Unix make or WinNT NMAKE, you must invoke the
+Java compiler on the Java source files in all the directories
+unpacked from the source distribution.  Note that the Java
+source code in the visad/examples directory has no package, so
+you must run 'cd visad/examples' before you compile these Java
+source files.
+
+If you do not use make, then you must also run the rmic compiler
+on the following classes (after they are compiled by the javac
+compiler):
+
+  visad.RemoteActionImpl
+  visad.RemoteCellImpl
+  visad.RemoteDataImpl
+  visad.RemoteDataReferenceImpl
+  visad.RemoteDisplayImpl
+  visad.RemoteFieldImpl
+  visad.RemoteFunctionImpl
+  visad.RemoteReferenceLinkImpl
+  visad.RemoteServerImpl
+  visad.RemoteSlaveDisplayImpl
+  visad.RemoteThingImpl
+  visad.RemoteThingReferenceImpl
+  visad.collab.RemoteDisplayMonitorImpl
+  visad.collab.RemoteDisplaySyncImpl
+  visad.collab.RemoteEventProviderImpl
+  visad.cluster.RemoteAgentContactImpl
+  visad.cluster.RemoteClientAgentImpl
+  visad.cluster.RemoteClientDataImpl
+  visad.cluster.RemoteClientFieldImpl
+  visad.cluster.RemoteClientTupleImpl
+  visad.cluster.RemoteClusterDataImpl
+  visad.cluster.RemoteNodeDataImpl
+  visad.cluster.RemoteNodeFieldImpl
+  visad.cluster.RemoteNodePartitionedFieldImpl
+  visad.cluster.RemoteNodeTupleImpl
+
+
+4. Building Native Code for the HDF-EOS and HDF-5 File Adapters
+
+Although VisAD is a pure Java system, it does require native
+code interfaces in its adapters for HDF-EOS and HDF-5 file
+formats.  We believe that the need for these will disappear
+as the organizations supporting these file formats develop
+Java interfaces.
+
+You can build the necessary libraries from source or on
+Sparc Solaris you can simply download:
+
+  ftp://ftp.ssec.wisc.edu/pub/visad-2.0/libhdfeos.so
+
+into your visad/data/hdfeos/hdfeosc directory, and download
+the appropriate file (for Sparc Solaris, Irix, Linux and
+Windows) from:
+
+  ftp://hdf.ncsa.uiuc.edu/HDF5/current/java-hdf5/JHI5_1_1_bin/lib/
+
+into your ncsa/hdf/hdf5lib directory according to instructions
+available under 'Download' at:
+
+  http://hdf.ncsa.uiuc.edu/java-hdf5-html/
+
+The HDF-EOS and HDF-5 file adapters include native interfaces
+(JNI) to file interfaces written in C.  To make the HDF-EOS
+VisAD native library on systems that support Unix make,
+change to the visad/data/hdfeos/hdfeosc directory and run
+'make all'.
+
+Note that the native code in visad/data/hdfeos/hdfeosc does
+not include NASA/Hughes' HDF-EOS C file interface code; it
+only includes our C native code for creating a Java binding
+to their HDF-EOS C file interface.  You must obtain the
+HDF-EOS C file interface code directly from NASA and NCSA.
+To do this, please follow the instructions in:
+
+   visad/data/hdfeos/README.hdfeos
+
+We have successfully linked these libraries on Irix and
+Solaris.
+
+You can also make the HDF-5 native libraries from source,
+according to instructions available from:
+
+  http://hdf.ncsa.uiuc.edu/java-hdf5-html/
+
+Before you can run applications that use the HDF-EOS and
+HDF-5 file adapters, you must add:
+ 
+  /parent_dir/visad/data/hdfeos/hdfeosc
+
+and:
+
+  /parent_dir/ncsa/hdf/hdf5lib
+ 
+to your LD_LIBRARY_PATH.
+
+
+5. Building Native Code for Applications
+
+Although VisAD is a pure Java system, applications of VisAD
+may include native code.  The reality is that most science
+code is still written in Fortran.
+
+The applications in visad/paoloa, visad/paoloa/spline,
+visad/aune and visad/benjamin also include native code in
+both C and Fortran.
+
+Edit the Makefile in the visad/paoloa, visad/paoloa/spline,
+visad/aune and visad/benjamin to change the path:
+ 
+  JAVADIR=/opt/java
+ 
+to point to the appopriate directory where you installed Java.
+
+On systems that support Unix make, change to each of
+the directories visad/paoloa, visad/paoloa/spline, visad/aune
+and visad/benjamin run 'make'.  This will create the shared
+object files (i.e., file names ending in ".so") containing
+native code.  To run these applications make sure that your
+LD_LIBRARY_PATH includes ".", change to one of these
+directories:
+
+  /parent_dir/visad/paoloa
+  /parent_dir/visad/paoloa/spline
+  /parent_dir/visad/aune
+  /parent_dir/visad/benjamin
+
+and run the appropriate "java ..." command.
+
+Note that the applications in visad/paoloa require data files
+available from:
+
+  ftp://ftp.ssec.wisc.edu/pub/visad-2.0/paoloa-files.tar.Z
+
+
+6. Downloading VisAD Classes in Jar Files
+
+If you want to write applications for VisAD but don't want
+to compile VisAD from source, you can dowload a jar file that
+includes the VisAD classes.  This file is:
+
+  ftp://ftp.ssec.wisc.edu/pub/visad-2.0/visad.jar
+
+Once you've got visad.jar simply add:
+
+   /parent_dir/visad.jar;.
+
+to your CLASSPATH.  Then you can compile and run applications
+that import the VisAD classes.  However, if your application
+uses the HDF-EOS or HDF-5 file format adapters, then you will
+need to compile the native code as described in Section 4 of
+this README file.  The visad.jar file includes the classes from
+these packages:
+
+  visad				// the core VisAD package
+  visad/ss			// VisAD Spread Sheet
+  visad/formula			// formula parser
+  visad/java3d			// Java3D displays for VisAD
+  visad/java2d			// Java2D displays for VisAD
+  visad/util			// VisAD UI utilities
+  visad/collab                  // collaboration support
+  visad/cluster                 // data and displays distributed on clusters
+  visad/python                  // python scripts for VisAD
+  visad/browser                 // connecting applets to VisAD servers
+  visad/math                    // math (fft, histogram) operations
+  visad/matrix                  // JAMA (matlab) matrix operations
+  visad/data			// VisAD data (file) format adapters
+  visad/data/fits		// VisAD - FITS file adapter
+  visad/data/netcdf		// VisAD - netCDF file adapter
+  visad/data/netcdf/in		// netCDF file adapter input
+  visad/data/netcdf/out		// netCDF file adapter output
+  visad/data/netcdf/units	// units parser for netCDF adapter
+  visad/data/hdfeos		// VisAD - HDF-EOS file adapter
+  visad/data/hdfeos/hdfeosc	// native interface to HDF-EOS
+  visad/data/vis5d		// VisAD - Vis5D file adapter
+  visad/data/mcidas		// VisAD - McIDAS file adapter
+  visad/data/gif		// VisAD - GIF file adapter
+  visad/data/tiff		// VisAD - TIFF file adapter
+  visad/data/jai		// VisAD file adapter for various image formats
+  visad/data/biorad             // VisAD - Biorad file adapter
+  visad/data/visad		// VisAD serialized object file adapter
+  visad/data/hdf5 		// VisAD - HDF-5 file adapter
+  visad/data/hdf5/hdf5objects	// VisAD - HDF-5 file adapter
+  visad/data/amanda             // VisAD - F2000 file adapter (neutrino events)
+  visad/paoloa			// GoesCollaboration application
+  visad/paoloa/spline		// spline fitting application
+  visad/aune			// ShallowFluid application
+  visad/benjamin		// Galaxy application
+  visad/rabin                   // Rainfall estimation spread sheet
+  visad/bom                     // wind barb rendering for ABOM
+  visad/jmet                    // JMet - Java Meteorology package
+  visad/aeri                    // Aeri data visualization
+  visad/georef                  // specialized earth coordinates
+  visad/meteorology             // meteorology
+  visad/examples		// small application examples
+  nom/tam/fits			// Java FITS file binding
+  nom/tam/util			// Java FITS file binding
+  nom/tam/test			// Java FITS file binding
+  ucar/multiarray		// Java netCDF file binding
+  ucar/util			// Java netCDF file binding
+  ucar/netcdf			// Java netCDF file binding
+  ucar/tests			// Java netCDF file binding
+  edu/wisc/ssec/mcidas          // Java McIDAS file binding
+  edu/wisc/ssec/mcidas/adde     // Java McIDAS file binding
+  ncsa/hdf/hdf5lib              // Java HDF-5 file binding
+  ncsa/hdf/hdf5lib/exceptions   // Java HDF-5 file binding
+  ij                            // ImageJ
+  ij/gui                        // ImageJ
+  ij/io                         // ImageJ
+  ij/measure                    // ImageJ
+  ij/plugin                     // ImageJ
+  ij/plugin/filter              // ImageJ
+  ij/plugin/frame               // ImageJ
+  ij/process                    // ImageJ
+  ij/text                       // ImageJ
+  ij/util                       // ImageJ
+
+In order to run the examples with visad.jar, download:
+
+  ftp://ftp.ssec.wisc.edu/pub/visad-2.0/visad_examples.jar
+
+Unpack this jar file by running:
+
+  jar xvf visad_examples.jar
+
+This will put *.java and *.class files into your visad/examples
+directory.  Change to that directory and run the appropriate
+example application.  Make sure that '.' is in your CLASS_PATH.
+
+
+7. Problems
+
+If you have problems, send an email message to the VisAD mailing
+list at:
+
+  visad-list at ssec.wisc.edu
+
+Join the list by sending an email message to:
+
+  majordomo at ssec.wisc.edu
+
+with:
+
+  subscribe visad-list
+
+as the first line of the message body (not the subject line).
+
+Please include any compiler or run time error messages in the text
+of email messages to the mailing list.
+
+
diff --git a/visad/README.nexusrmi b/visad/README.nexusrmi
new file mode 100644
index 0000000..c3ed51a
--- /dev/null
+++ b/visad/README.nexusrmi
@@ -0,0 +1,57 @@
+
+               Compiling and Running VisAD Using NexusRMI
+
+1. Introduction
+
+You can compile and run VisAD using NexusRMI, an alternate implementation
+of RMI (Java's distributed object technology) based on the Nexus
+high-performance communication system.
+
+NexusRMI was written by Fabian Breg and Dennis Gannon of Indiana University
+and is available from:
+
+  http://www.extreme.indiana.edu/hpjava/nexusrmi/index.html
+
+
+2. Compiling VisAD Using NexusRMI
+
+You'll need to unpack all of Java's class library Jar files into a
+directory in your CLASSPATH.  These Jar files include:
+
+  jre/lib/ext/iiimp.jar
+  jre/lib/i18n.jar
+  jre/lib/rt.jar
+  lib/tools.jar
+  lib/dt.jar
+
+and if you're using Java3D:
+
+  jre/lib/ext/vecmath.jar
+  jre/lib/ext/j3dcore.jar
+  jre/lib/ext/j3daudio.jar
+  jre/lib/ext/j3dutils.jar
+
+This is necessary for the nexusrmic compiler to be able to find the
+Java classes.
+
+Then run the following commands in your visad directory:
+
+  make tonexus
+
+  make nexusrmi_compile
+
+The first make command replaces all imports of 'java.rmi' with 'nexusrmi'
+in the VisAD source code.  The second make command compiles VisAD,
+substituting the nexusrmic compiler for the rmic compiler.  Note you
+can change the 'nexusrmi' imports back to 'java.rmi' by running:
+
+  make tojava
+
+This lets you compile using the standard Java implementation of RMI.
+
+
+3. Running VisAD Using NexusRMI
+
+Start nexusrmiregistry (the NexusRMI alternative to rmiregistry).
+Then just run your VisAD applications.
+
diff --git a/visad/RangeControl.java b/visad/RangeControl.java
new file mode 100644
index 0000000..617fe77
--- /dev/null
+++ b/visad/RangeControl.java
@@ -0,0 +1,185 @@
+//
+// RangeControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.StringTokenizer;
+
+import visad.browser.Convert;
+import visad.util.Util;
+
+/**
+   RangeControl is the VisAD class for controlling SelectRange display scalars.<P>
+*/
+public class RangeControl extends Control {
+
+  private boolean initialized = false;
+  private double RangeLow = Double.NaN;
+  private double RangeHi = Double.NaN;
+
+  public RangeControl(DisplayImpl d) {
+    super(d);
+  }
+
+  /** initialize the range of selected values as (range[0], range[1]) */
+  public void initializeRange(float[] range)
+    throws VisADException, RemoteException
+  {
+    initializeRange(new double[] {(double) range[0], (double) range[1]});
+  }
+
+  /** initialize the range of selected values as (range[0], range[1]) */
+  public void initializeRange(double[] range)
+    throws VisADException, RemoteException
+  {
+    changeRange(range, initialized);
+  }
+
+  /** set the range of selected values as (range[0], range[1]) */
+  public void setRange(float[] range)
+         throws VisADException, RemoteException {
+    setRange(new double[] {(double) range[0], (double) range[1]});
+  }
+
+  /** set the range of selected values as (range[0], range[1]) */
+  public void setRange(double[] range)
+         throws VisADException, RemoteException {
+    if (RangeLow != RangeLow ||
+        !Util.isApproximatelyEqual(RangeLow, range[0]) ||
+        RangeHi != RangeHi ||
+        !Util.isApproximatelyEqual(RangeHi, range[1]))
+    {
+      changeRange(range, true);
+    }
+  }
+
+  private void changeRange(double[] range, boolean notify)
+    throws VisADException, RemoteException
+  {
+    RangeLow = range[0];
+    RangeHi = range[1];
+    initialized = (RangeLow == RangeLow && RangeHi == RangeHi);
+    if (notify) {
+      changeControl(true);
+    }
+  }
+
+  /** return the range of selected values */
+  public float[] getRange() {
+    float[] range = new float[2];
+    range[0] = (float) RangeLow;
+    range[1] = (float) RangeHi;
+    return range;
+  }
+
+  /** return the range of selected values */
+  public double[] getDoubleRange() {
+    double[] range = new double[2];
+    range[0] = RangeLow;
+    range[1] = RangeHi;
+    return range;
+  }
+
+  /** get a string that can be used to reconstruct this control later */
+  public String getSaveString() {
+    return RangeLow + " " + RangeHi;
+  }
+
+  /** reconstruct this control using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save);
+    if (st.countTokens() < 2) throw new VisADException("Invalid save string");
+    double[] r = new double[2];
+    for (int i=0; i<2; i++) r[i] = Convert.getDouble(st.nextToken());
+    initializeRange(r);
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof RangeControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    RangeControl rc = (RangeControl )rmt;
+
+    boolean changed = false;
+
+    if (!Util.isApproximatelyEqual(RangeLow, rc.RangeLow)) {
+      changed = true;
+      RangeLow = rc.RangeLow;
+    }
+    if (!Util.isApproximatelyEqual(RangeHi, rc.RangeHi)) {
+      changed = true;
+      RangeHi = rc.RangeHi;
+    }
+    initialized = (RangeLow == RangeLow && RangeHi == RangeHi);
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    RangeControl rc = (RangeControl )o;
+
+    if (!Util.isApproximatelyEqual(RangeLow, rc.RangeLow)) {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(RangeHi, rc.RangeHi)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  public String toString()
+  {
+    return "RangeControl[" + RangeLow + "," + RangeHi + "]";
+  }
+}
+
diff --git a/visad/Real.java b/visad/Real.java
new file mode 100644
index 0000000..6a7596a
--- /dev/null
+++ b/visad/Real.java
@@ -0,0 +1,1172 @@
+//
+// Real.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+ * Real is the class of VisAD scalar data for real numbers represented
+ * as double precision floating point values.  Double.NaN is used to
+ * indicate missing values, because it has the appropriate arithmetic
+ * semantics.  Real objects are immutable.<P>
+ */
+public class Real
+  extends       Scalar
+  implements    RealIface
+{
+
+  private final double Value;
+  private final Unit unit;
+  private final ErrorEstimate Error;
+
+  /**
+   * Constructs a Real object.  This is the most general constructor.
+   * @param type                The type of the Real.
+   * @param value               The value of the Real.  May be
+   *                            <code>Double.NaN</code>.
+   * @param u                   The unit of the Real.  May be <code>null</code>.
+   *                            If non-<code>null</code> and
+   *                            <code>type.isInterval()</code> returns true,
+   *                            then the unit will actually be
+   *                            <code>u.getAbsoluteUnit()</code>.
+   * @param error               Error estimate of the Real.  May be
+   *                            <code>null</code>.
+   * @throws UnitException      if the default unit of the type is inconvertible
+   *                            with the unit argument (i.e. if <code>
+   *                            Unit.canConvert(u, type.getDefaultUnit())</code>
+   *                            returns false).
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public Real(RealType type, double value, Unit u, ErrorEstimate error)
+         throws VisADException {
+    super(type);
+    if (!Unit.canConvert(u, type.getDefaultUnit())) {
+      throw new UnitException("Real: Unit \"" + u +
+                              "\" must be convertable" +
+                              " with Type default Unit \"" +
+                              type.getDefaultUnit() + "\"");
+    }
+    unit = u != null && type.isInterval() ? u.getAbsoluteUnit() : u;
+    Value = value;
+    Error = Double.isNaN(value) ? null : error;
+  }
+
+  /**
+   * Constructs a Real object.  The error estimate will be based on a numeric
+   * value.
+   * @param type                The type of the Real.
+   * @param value               The value of the Real.  May be
+   *                            <code>Double.NaN</code>.
+   * @param u                   The unit of the Real.  May be <code>null</code>.
+   *                            If non-<code>null</code> and
+   *                            <code>type.isInterval()</code> returns true,
+   *                            then the unit will actually be
+   *                            <code>u.getAbsoluteUnit()</code>.
+   * @param error               Value for constructing an error estimate for the
+   *                            Real in units of <code>u != null &&
+   *                            type.isInterval() ? u.getAbsoluteUnit() :
+   *                            u</code>.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public Real(RealType type, double value, Unit u, double error)
+         throws VisADException {
+    this(type, value, u,
+      new ErrorEstimate(value, Math.abs(error),
+        u != null && type.isInterval() ? u.getAbsoluteUnit() : u));
+  }
+
+  /**
+   * Constructs a Real object.  The error estimate will be <code>null</code>.
+   * @param type                The type of the Real.
+   * @param value               The value of the Real.  May be
+   *                            <code>Double.NaN</code>.
+   * @param u                   The unit of the Real.  May be <code>null</code>.
+   *                            If non-<code>null</code> and
+   *                            <code>type.isInterval()</code> returns true,
+   *                            then the unit will actually be
+   *                            <code>u.getAbsoluteUnit()</code>.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   */
+  public Real(RealType type, double value, Unit u)
+         throws VisADException {
+    this(type, value, u, null);
+  }
+
+  /**
+   * Constructs a Real object.  The unit of the Real will be the default unit of
+   * the RealType and the error estimate will be <code>null</code>.
+   * @param type                The type of the Real.
+   * @param value               The value of the Real in units of
+   *                            <code>type.getDefaultUnit()</code>.  May be
+   *                            <code>Double.NaN</code>.
+   */
+  public Real(RealType type, double value) {
+    this(type, value, type.getDefaultUnit(), null, false);
+  }
+
+  /**
+   * Constructs a Real object.  The value will be missing, the unit of the
+   * Real will be the default unit of the RealType, and the error estimate
+   * will be <code>null</code>.
+   * @param type                The type of the Real.
+   */
+  public Real(RealType type) {
+    this(type, Double.NaN, type.getDefaultUnit(), null, false);
+  }
+
+  /**
+   * Constructs a generic Real object.  The RealType of the Real will be
+   * <code>RealType.Generic</code>, the unit of the Real will be
+   * <code>RealType.Generic.getDefaultUnit()</code>, and the error estimate
+   * will be based on a numeric value.
+   * @param value               The value of the Real.  May be
+   *                            <code>Double.NaN</code>.
+   * @param error               Value for constructing an error estimate for the
+   *                            Real in units of
+   *                            <code>RealType.Generic.getDefaultUnit()</code>.
+   */
+  public Real(double value, double error) {
+    this(RealType.Generic, value, RealType.Generic.getDefaultUnit(),
+         new ErrorEstimate(value, Math.abs(error), RealType.Generic.getDefaultUnit()),
+         false);
+  }
+
+  /**
+   * Constructs a generic Real object.  The RealType of the Real will be
+   * <code>RealType.Generic</code>, the unit of the Real will be
+   * <code>RealType.Generic.getDefaultUnit()</code>, and the error estimate
+   * will be 0.0.
+   * @param value               The value of the Real.  May be
+   *                            <code>Double.NaN</code>.
+   */
+  public Real(double value) {
+    this(RealType.Generic, value, RealType.Generic.getDefaultUnit(),
+         new ErrorEstimate(value, 0.0, RealType.Generic.getDefaultUnit()), false);
+  }
+
+  /** trusted constructor for other constructors */
+  protected Real(RealType type, double value, Unit u, ErrorEstimate error,
+               boolean checkUnit) {
+    super(type);
+    if(u !=null && checkUnit && !Unit.canConvert(u, type.getDefaultUnit())) {
+	throw new IllegalArgumentException("Real: Unit \"" + u +
+                              "\" must be convertable" +
+                              " with Type default Unit \"" +
+                              type.getDefaultUnit() + "\"");
+    }
+
+    unit = u != null && type.isInterval() ? u.getAbsoluteUnit() : u;
+    Value = value;
+    Error = Double.isNaN(value) ? null : error;
+  }
+
+  /**
+   * Get the value for this Real in the units of the data.
+   * @return value as a double.
+   */
+  public final double getValue() {
+    return Value;
+  }
+
+
+  /**
+  * Methods for Jython comparisons with doubles and other Reals
+  *
+  * @return =0 for false, =1 for true
+  */
+  public int __gt__(double other) {
+    if (Value > other) return 1;
+    return 0;
+  }
+  public int __lt__(double other) {
+    if (Value < other) return 1;
+    return 0;
+  }
+  public int __ge__(double other) {
+    if (Value >= other) return 1;
+    return 0;
+  }
+  public int __le__(double other) {
+    if (Value <= other) return 1;
+    return 0;
+  }
+  public int __ne__(double other) {
+    if (Value != other) return 1;
+    return 0;
+  }
+  public int __eq__(double other) {
+    if (Value == other) return 1;
+    return 0;
+  }
+
+  public int __gt__(Real other) throws VisADException, RemoteException {
+    if (other == null) return 0;
+    double d = ((Real) subtract(other)).getValue();
+    if (d > 0.0) return 1;
+    return 0;
+  }
+  public int __lt__(Real other) throws VisADException, RemoteException {
+    if (other == null) return 0;
+    double d = ((Real) subtract(other)).getValue();
+    if (d < 0.0) return 1;
+    return 0;
+  }
+  public int __ge__(Real other) throws VisADException, RemoteException {
+    if (other == null) return 0;
+    double d = ((Real) subtract(other)).getValue();
+    if (d >= 0.0) return 1;
+    return 0;
+  }
+  public int __le__(Real other) throws VisADException, RemoteException {
+    if (other == null) return 0;
+    double d = ((Real) subtract(other)).getValue();
+    if (d <= 0.0) return 1;
+    return 0;
+  }
+  public int __eq__(Real other) throws VisADException, RemoteException {
+    if (other == null) return 0;
+    double d = ((Real) subtract(other)).getValue();
+    if (d == 0.0) return 1;
+    return 0;
+  }
+  public int __ne__(Real other) throws VisADException, RemoteException {
+    if (other == null) return 1;
+    double d = ((Real) subtract(other)).getValue();
+    if (d != 0.0) return 1;
+    return 0;
+  }
+
+  // The following methods were removed to allow use of Jython
+  // versions beyond 2.2.  The issue was that for overloaded methods
+  // the newer Jython interpreters might select the wrong method
+  // if another one passed in a Class that had a __float__() or
+  // other "casting" signature.
+
+  /**
+  * Methods to convert types for Jython
+  *
+  * @return this Real as a float, long, or int
+  *
+  */
+  /*
+  public double __float__() {
+    return Value;
+  }
+
+  public long __long__() {
+    return (long) Value;
+  }
+
+  public int __int__() {
+    return (int) Value;
+  }
+  */
+
+  /** get double value converted to unit_out */
+  /**
+   * Get the value for this Real converted to unit_out.
+   * @param unit_out  unit for return value
+   * @return value in unit_out units.
+   * @throws VisADException  if either the Real's Unit or the unit_out
+   *                         is null (but not both).
+   */
+  public final double getValue(Unit unit_out) throws VisADException {
+    if (unit_out == null) {
+      if (unit != null) {  // can't convert to null unit
+        throw new UnitException("Real.getValue: illegal Unit conversion");
+      }
+      return Value;
+    }
+    else {
+      if (unit == null) {  // unit_out not null, but unit is
+        throw new UnitException("Real.getValue: illegal Unit conversion");
+      }
+      if (((RealType)getType()).isInterval())
+        unit_out = unit_out.getAbsoluteUnit();
+      return unit_out.toThis(Value, unit);
+    }
+  }
+
+  /**
+   * Check to see if the value of this Real is a NaN.
+   * @return true if a NaN.
+   */
+  public boolean isMissing() {
+    // note inf and -inf have proper semantics and are not missing
+    return (Double.isNaN(Value));
+  }
+
+/*- TDR  May 1998
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+ */
+
+  public Data binary(Data data, int op, MathType new_type,
+                     int sampling_mode, int error_mode )
+              throws VisADException, RemoteException {
+    /*- TDR May 1998 */
+    if ( new_type == null ) {
+      throw new TypeException("binary: new_type may not be null");
+    }
+    /*- end */
+    if (data instanceof Real) {
+      Real that = (Real)data;
+
+  /*- TDR May 28 1998 */
+      if ( !(new_type instanceof RealType) ) {
+        throw new TypeException("binary: new_type doesn't match return type");
+      }
+  /*- end */
+
+      Unit          thisUnit = getUnit();
+      Unit          thatUnit = that.getUnit();
+      double        thisValue = getValue();
+      double        thatValue = that.getValue();
+      ErrorEstimate thisErr = getError();
+      ErrorEstimate thatErr = that.getError();
+      Unit          outUnit = null;  // default; the following switch might set
+      double        outValue = Double.NaN;
+
+      /*
+       * Set the output value.  If possible, he output unit is first determined
+       * and then the output numeric value.
+       */
+      switch (op) {
+        case ADD:
+        case SUBTRACT:
+        case INV_SUBTRACT:
+        case MAX:
+        case MIN:
+          if (thisUnit == null || thatUnit == null) {
+            outUnit = null;
+          }
+          else if (thisUnit == CommonUnit.promiscuous) {
+            outUnit = thatUnit.getAbsoluteUnit();
+          }
+          else if (thatUnit == CommonUnit.promiscuous) {
+            outUnit = thisUnit.getAbsoluteUnit();
+          }
+          else {
+            try {
+              outUnit = thisUnit.getAbsoluteUnit();
+              thisValue = outUnit.toThis(thisValue, thisUnit);
+              thatValue = outUnit.toThis(thatValue, thatUnit);
+
+              if (error_mode != NO_ERRORS
+                && thisErr != null && thatErr != null) {
+
+                if (!outUnit.equals(thisUnit)) {
+                  Unit  errUnit = thisErr.getUnit();
+
+                  if (errUnit == null)
+                    errUnit = thisUnit;
+
+                  double newErr =
+                    outUnit.toThis(thisErr.getErrorValue(), errUnit);
+
+                  thisErr = new ErrorEstimate(thisValue, newErr, outUnit);
+                }
+
+                if (!outUnit.equals(thatUnit)) {
+                  Unit  errUnit = thatErr.getUnit();
+
+                  if (errUnit == null)
+                    errUnit = thatUnit;
+
+                  double newErr =
+                    outUnit.toThis(thatErr.getErrorValue(), errUnit);
+
+                  thatErr = new ErrorEstimate(thatValue, newErr, outUnit);
+                }
+              }
+            }
+            catch (UnitException e) {           // inconvertible units
+              outUnit = null;
+            }
+          }
+          switch (op) {
+            case ADD:
+              outValue = thisValue + thatValue;
+              break;
+            case SUBTRACT:
+              outValue = thisValue - thatValue;
+              break;
+            case INV_SUBTRACT:
+              outValue = thatValue - thisValue;
+              break;
+            case MAX:
+              outValue = Math.max(thisValue, thatValue);
+              break;
+            case MIN:
+              outValue = Math.min(thisValue, thatValue);
+              break;
+          }
+          break;
+
+        case MULTIPLY:
+        case DIVIDE:
+        case INV_DIVIDE:
+          if (thisUnit != null) {
+            Unit absUnit = thisUnit.getAbsoluteUnit();
+            thisValue = absUnit.toThis(thisValue, thisUnit);
+            thisUnit = absUnit;
+          }
+          if (thatUnit != null) {
+            Unit absUnit = thatUnit.getAbsoluteUnit();
+            thatValue = absUnit.toThis(thatValue, thatUnit);
+            thatUnit = absUnit;
+          }
+          if (thisUnit == null || thatUnit == null) {
+            outUnit = null;
+          }
+          else {
+            switch(op) {
+              case MULTIPLY:
+                outUnit = 
+                  thisUnit.equals(CommonUnit.promiscuous)
+                    ? thatUnit
+                    : thatUnit.equals(CommonUnit.promiscuous)
+                      ? thisUnit
+                      : thisUnit.multiply(thatUnit);
+                break;
+              case DIVIDE:
+                outUnit = 
+                  thatUnit.equals(CommonUnit.promiscuous)
+                    ? thisUnit
+                    : thisUnit.divide(thatUnit);
+                break;
+              case INV_DIVIDE:
+                outUnit = 
+                  thisUnit.equals(CommonUnit.promiscuous)
+                    ? thatUnit
+                    : thatUnit.divide(thisUnit);
+                break;
+            }
+          }
+          switch(op) {
+            case MULTIPLY:
+              outValue = thisValue * thatValue;
+              break;
+            case DIVIDE:
+              outValue = thisValue / thatValue;
+              break;
+            case INV_DIVIDE:
+              outValue = thatValue / thisValue;
+              break;
+          }
+          break;
+
+        case POW:
+          if (thisUnit != null) {
+            Unit absUnit = thisUnit.getAbsoluteUnit();
+            thisValue = absUnit.toThis(thisValue, thisUnit);
+            thisUnit = absUnit;
+          }
+          if (thatUnit != null && !CommonUnit.promiscuous.equals(unit)) {
+            Unit absUnit = thatUnit.getAbsoluteUnit();
+            thatValue = absUnit.toThis(thatValue, thatUnit);
+            thatUnit = absUnit;
+          }
+          if (thisUnit != null && (
+              thisUnit.equals(CommonUnit.promiscuous) ||
+              thisUnit.equals(CommonUnit.dimensionless))) {
+            outUnit = thisUnit;
+          }
+          else {
+            outUnit = null;
+          }
+          outValue = Math.pow(thisValue, thatValue);
+          break;
+
+        case INV_POW:
+          if (thatUnit != null) {
+            Unit absUnit = thatUnit.getAbsoluteUnit();
+            thatValue = absUnit.toThis(thatValue, thatUnit);
+            thatUnit = absUnit;
+          }
+          if (thisUnit != null && !CommonUnit.promiscuous.equals(unit)) {
+            Unit absUnit = thisUnit.getAbsoluteUnit();
+            thisValue = absUnit.toThis(thisValue, thisUnit);
+            thisUnit = absUnit;
+          }
+          if (thatUnit != null && (
+              thatUnit.equals(CommonUnit.promiscuous) ||
+              thatUnit.equals(CommonUnit.dimensionless))) {
+            outUnit = thatUnit;
+          }
+          else {
+            outUnit = null;
+          }
+          outValue = Math.pow(thatValue, thisValue);
+          break;
+
+        case ATAN2:
+        case ATAN2_DEGREES:
+        case INV_ATAN2:
+        case INV_ATAN2_DEGREES:
+        case REMAINDER:
+        case INV_REMAINDER:
+          if (thisUnit != null && thatUnit != null) {
+            Unit absUnit = thisUnit.getAbsoluteUnit();
+            thisValue = absUnit.toThis(thisValue, thisUnit);
+            thatValue = absUnit.toThis(thatValue, thatUnit);
+            thisUnit = absUnit;
+            thatUnit = absUnit;
+          }
+          switch(op) {
+            case ATAN2:
+              outValue = Math.atan2(thisValue, thatValue);
+              outUnit = CommonUnit.radian;
+              break;
+            case ATAN2_DEGREES:
+              outValue =
+                Data.RADIANS_TO_DEGREES * Math.atan2(thisValue, thatValue);
+              outUnit = CommonUnit.degree;
+              break;
+            case INV_ATAN2:
+              outValue = Math.atan2(thatValue, thisValue);
+              outUnit = CommonUnit.radian;
+              break;
+            case INV_ATAN2_DEGREES:
+              outValue =
+                Data.RADIANS_TO_DEGREES * Math.atan2(thatValue, thisValue);
+              outUnit = CommonUnit.degree;
+              break;
+            case REMAINDER:
+              outValue = thisValue % thatValue;
+              outUnit = thisUnit;
+              break;
+            case INV_REMAINDER:
+              outValue = thatValue % thisValue;
+              outUnit = thatUnit;
+              break;
+          }
+          break;
+        default:
+          throw new ArithmeticException("Real.binary: illegal operation");
+      }
+
+      if (error_mode == NO_ERRORS || thisErr == null || thatErr == null) {
+        return new Real(((RealType) new_type), outValue, outUnit, null);
+      }
+      else {
+        return new Real(((RealType) new_type), outValue, outUnit,
+                   new ErrorEstimate(outValue, outUnit, op, thisErr, thatErr,
+                   error_mode));
+      }
+    }
+    else if (data instanceof Text) {
+      throw new TypeException("Real.binary: types don't match");
+    }
+    else if (data instanceof TupleIface) {
+      /* BINARY - TDR May 28, 1998
+      return data.binary(this, invertOp(op), sampling_mode, error_mode);
+      */
+      /* BINARY - TDR June 5, 1998 */
+      if ( !(data.getType()).equalsExceptName(new_type) ) {
+        throw new TypeException();
+      }
+      return data.binary(this, invertOp(op), new_type, sampling_mode, error_mode);
+      /* BINARY - end  */
+    }
+    else if (data instanceof Field) {
+      /* BINARY - TDR May 28, 1998
+      return data.binary(this, invertOp(op), sampling_mode, error_mode);
+      */
+      /* BINARY - TDR June 5, 1998 */
+      if ( !(data.getType()).equalsExceptName(new_type) ) {
+        throw new TypeException();
+      }
+      return data.binary(this, invertOp(op), new_type, sampling_mode, error_mode);
+      /* BINARY - end  */
+    }
+    else {
+      throw new TypeException("Real.binary");
+    }
+  }
+
+  /** unary function on a Real; override some trig functions based
+      on Unit; transcental functions destroy dimensionfull Unit */
+  public Data unary(int op, MathType new_type, int sampling_mode, int error_mode)
+              throws VisADException {
+    Unit thisUnit;      // input unit
+    double thisValue;   // input value
+    if (unit == null) {
+      thisUnit = null;
+      thisValue = Value;
+    }
+    else {
+      /*
+       * Condition input numeric value and unit.  If the input unit is
+       * dimensionless, then the input numeric value is converted to be in units
+       * of the dimensionless unit 1; otherwise, the input numeric value is
+       * converted to be in units of the absolute unit of the input unit.
+       */
+      thisUnit = Unit.canConvert(CommonUnit.dimensionless, unit)
+        ? CommonUnit.dimensionless : unit.getAbsoluteUnit();
+      thisValue = thisUnit.toThis(Value, unit);
+    }
+    double value;       // output value
+    Unit u;             // output unit
+    /*- TDR  June 1998  */
+    if ( new_type == null ) {
+      throw new TypeException("unary: new_type may not be null");
+    }
+    switch (op) {
+      case ABS:
+        value = Math.abs(thisValue);
+        u = thisUnit;
+        break;
+      case ACOS:
+        value = Math.acos(thisValue);
+        u = CommonUnit.radian;
+        break;
+      case ACOS_DEGREES:
+        value = Data.RADIANS_TO_DEGREES * Math.acos(thisValue);
+        u = CommonUnit.degree;
+        break;
+      case ASIN:
+        value = Math.asin(thisValue);
+        u = CommonUnit.radian;
+        break;
+      case ASIN_DEGREES:
+        value = Data.RADIANS_TO_DEGREES * Math.asin(thisValue);
+        u = CommonUnit.degree;
+        break;
+      case ATAN:
+        value = Math.atan(thisValue);
+        u = CommonUnit.radian;
+        break;
+      case ATAN_DEGREES:
+        value = Data.RADIANS_TO_DEGREES * Math.atan(thisValue);
+        u = CommonUnit.degree;
+        break;
+      case CEIL:
+        value = Math.ceil(thisValue);
+        u = thisUnit;
+        break;
+      case COS:
+        // do cos in radians, unless unit is degrees
+        value = CommonUnit.degree.equals(thisUnit) ?
+                Math.cos(Data.DEGREES_TO_RADIANS * thisValue) : Math.cos(thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case COS_DEGREES:
+        // do cos in degrees, unless unit is radians
+        value = CommonUnit.radian.equals(thisUnit) ?
+                Math.cos(thisValue) : Math.cos(Data.DEGREES_TO_RADIANS * thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case EXP:
+        value = Math.exp(thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case FLOOR:
+        value = Math.floor(thisValue);
+        u = thisUnit;
+        break;
+      case LOG:
+        value = Math.log(thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case RINT:
+        value = Math.rint(thisValue);
+        u = thisUnit;
+        break;
+      case ROUND:
+        value = Math.round(thisValue);
+        u = thisUnit;
+        break;
+      case SIN:
+        // do sin in radians, unless unit is degrees
+        value = CommonUnit.degree.equals(thisUnit) ?
+                Math.sin(Data.DEGREES_TO_RADIANS * thisValue) : Math.sin(thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case SIN_DEGREES:
+        // do sin in degrees, unless unit is radians
+        value = CommonUnit.radian.equals(thisUnit) ?
+                Math.sin(thisValue) : Math.sin(Data.DEGREES_TO_RADIANS * thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case SQRT:
+        value = Math.sqrt(thisValue);
+        // WLH 26 Nov 2001
+        // u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        if (thisUnit == null) {
+          u = null;
+        }
+        else {
+          try {
+            u = thisUnit.sqrt();
+          }
+          catch (IllegalArgumentException e) {
+            u = null;
+          }
+          catch (UnitException e) {
+            u = null;
+          }
+        }
+        break;
+      case TAN:
+        // do tan in radians, unless unit is degrees
+        value = CommonUnit.degree.equals(thisUnit) ?
+                Math.tan(Data.DEGREES_TO_RADIANS * thisValue) : Math.tan(thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case TAN_DEGREES:
+        // do tan in degrees, unless unit is radians
+        value = CommonUnit.radian.equals(thisUnit) ?
+                Math.tan(thisValue) : Math.tan(Data.DEGREES_TO_RADIANS * thisValue);
+        u = CommonUnit.dimensionless.equals(thisUnit) ? thisUnit : null;
+        break;
+      case NEGATE:
+        value = -thisValue;
+        u = thisUnit;
+        break;
+      case NOP:
+        value = thisValue;
+        u = thisUnit;
+        break;
+      default:
+        throw new ArithmeticException("Real.unary: illegal operation");
+    }
+    if (error_mode == NO_ERRORS || Error == null) {
+      /*- TDR June 1998
+      return new Real(((RealType) Type), value, u, null);
+      */
+      return new Real((RealType) new_type, value, u, null);
+    }
+    else {
+      /*- TDR June 1998
+      return new Real(((RealType) Type), value, u,
+                      new ErrorEstimate(value, u, op, Error, error_mode));
+      */
+      return new Real((RealType) new_type, value, u,
+                      new ErrorEstimate(value, u, op, Error, error_mode));
+    }
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    if (Double.isNaN(Value)) return shadow;
+    int i = ((ShadowRealType) type).getIndex();
+    if (i >= 0) {
+      double value;
+      Unit dunit = ((RealType) Type).getDefaultUnit();
+      if (dunit != null && !dunit.equals(unit)) {
+        value = dunit.toThis(Value, unit);
+      }
+      else {
+        value = Value;
+      }
+      if (value == value) {
+        shadow.ranges[0][i] = Math.min(shadow.ranges[0][i], value);
+        shadow.ranges[1][i] = Math.max(shadow.ranges[1][i], value);
+      }
+    }
+    return shadow;
+  }
+
+  public Unit getUnit() {
+    return unit;
+  }
+
+  public ErrorEstimate getError() {
+    return Error;
+  }
+
+  /** return a Real that clones this, except its ErrorEstimate
+      is adjusted for the sampling error in error */
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    if (isMissing() || Error == null ||
+        error == null || error.isMissing()) return this;
+    double a = ((Real) error).getValue();
+    double b = Error.getErrorValue();
+    double e = (error_mode == INDEPENDENT) ? Math.sqrt(a * a + b * b) :
+                                             Math.abs(a) + Math.abs(b);
+    return new Real((RealType) Type, Value, unit,
+                    new ErrorEstimate(Value, e, unit));
+  }
+
+  /** clone this, but with a new value */
+  public Real cloneButValue(double value) throws VisADException {
+    return new Real((RealType) Type, value, unit, Error, true);
+  }
+
+  /** clone this, but with a new Unit */
+  public Real cloneButUnit(Unit u) throws VisADException {
+    return new Real((RealType) Type, Value, u, Error);
+  }
+
+  public String toString() {
+    try {
+      if (Double.isNaN(Value)) {
+        return "missing";
+      }
+      else {
+        return
+          (Unit.canConvert(getUnit(), CommonUnit.secondsSinceTheEpoch) &&
+          !getUnit().getAbsoluteUnit().equals(getUnit()))
+            ? new DateTime(this).toString()
+            : Double.toString(Value);
+      }
+    }
+    catch (VisADException e) {
+      return e.toString();
+    }
+  }
+
+  /**
+   * Gets a string that represents just the value portion of this Real -- but
+   * with full semantics (e.g. numeric value and unit).
+   * @return                    A string representation of just the value
+   *                            portion of this Real.
+   */
+  public String toValueString() {
+    String      result;
+    try {
+      if (Double.isNaN(Value)) {
+        result = "missing";
+      }
+      else {
+          if (Unit.canConvert(getUnit(), CommonUnit.secondsSinceTheEpoch) &&
+              !getUnit().getAbsoluteUnit().equals(getUnit())) {
+            result = new DateTime(this).toValueString();
+          }
+          else {
+            Unit u =
+              unit != null ? unit : ((RealType)getType()).getDefaultUnit();
+            result = Float.toString((float)Value) + (u == null ? "" : " " + u);
+          }
+      }
+    }
+    catch (VisADException e) {
+      result = e.toString();
+    }
+    return result;
+  }
+
+  public String longString(String pre) throws VisADException {
+    if (Double.isNaN(Value)) {
+      return pre + "missing\n";
+    }
+    else if (Unit.canConvert(getUnit(), CommonUnit.secondsSinceTheEpoch) &&
+        !getUnit().getAbsoluteUnit().equals(getUnit())) {
+      return pre + "Real.Time: Value = " +
+             new DateTime(this).toString() + "\n";
+    }
+    else {
+      return pre + "Real: Value = " + Value +
+             "  (TypeName: " + ((RealType) Type).getName() + ")\n";
+    }
+  }
+
+  /**
+   * Compares this Real to another.
+   * @param object              The other Real to compare against.  It shall be
+   *                            a Real with a compatible (i.e. convertible)
+   *                            unit.
+   * @return                    A negative integer, zero, or a positive integer
+   *                            depending on whether this Real is considered
+   *                            less than, equal to, or greater than the other
+   *                            Real, respectively.  If the values of the Real-s
+   *                            in the default unit are equal, then the <code>
+   *                            ErrorEstimate.compareTo()</code> method is used
+   *                            to break the tie.
+   */
+  public int compareTo(Object object)
+  {
+    Real        that = (Real)object;
+    int         comp;
+    try
+    {
+      Unit      defaultUnit = ((RealType)getType()).getDefaultUnit();
+      comp = new Double(getValue(defaultUnit)).compareTo(
+             new Double(that.getValue(defaultUnit)));
+      if (comp == 0) {
+        if (Error == null) {
+          comp = that.Error == null ? 0 : -1;
+        }
+        else if (that.Error == null) {
+          comp = 1;
+        }
+        else {
+          comp = Error.compareTo(that.Error);
+        }
+      }
+    }
+    catch (VisADException e)
+    {
+      comp = 1; // make problem Real-s greater than anything
+    }
+    return comp;
+  }
+
+  /**
+   * Indicates if this Real is semantically identical to an object.
+   * @param obj                 The object.
+   * @return                    <code>true</code> if and only if this Real
+   *                            is semantically identical to the object.
+   */
+  public boolean equals(Object obj) {
+    return obj != null && obj instanceof Real &&
+      getType().equals(((Real)obj).getType()) && compareTo(obj) == 0;
+  }
+
+  /**
+   * Returns the hash code of this Real.
+   * @return                    The hash code of this Real.  If two Real-s are
+   *                            semantically identical, then their hash codes
+   *                            are equal.
+   */
+  public int hashCode() {
+    RealType    realType = (RealType)getType();
+    int         hashCode = realType.hashCode();
+    try
+    {
+      hashCode ^= new Double(getValue(realType.getDefaultUnit())).hashCode();
+    }
+    catch (VisADException e)
+    {}  // ignore because can't happen
+    if (Error != null)
+      hashCode ^= Error.hashCode();
+    return hashCode;
+  }
+
+  /** run 'java visad.Real' to test the Real class */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    byte b = 10;
+    Real w = new Real(b);
+
+    int ii = 14;
+    short s = 12;
+    Real t = new Real(1.0);
+    Real x = new Real(12);
+    Real y = new Real(12L);
+    Real u = new Real(ii);
+    Real v = new Real(s);
+
+    System.out.println("x = " + x + "\nw = " + w);
+
+    System.out.println("x + w = " + x.add(w));
+    System.out.println("x - w = " + x.subtract(w));
+    System.out.println("x * w = " + x.multiply(w));
+    System.out.println("x / w = " + x.divide(w));
+    System.out.println("sqrt(x) = " + x.sqrt());
+
+    System.out.println("");
+
+    Real fahrenheit =
+      new Real(
+        RealType.getRealType(
+          "FahrenheitTemperature",
+          new OffsetUnit(459.67,
+            new ScaledUnit(1/1.8, SI.kelvin, "degR"),
+              "degF"),
+          null),
+        32.0);
+    Real kelvin = new Real(RealType.getRealType("Temperature", SI.kelvin, null), 300);
+    System.out.println("300 kelvin + 32 fahrenheit = " +
+      ((Real)kelvin.add(fahrenheit)).toValueString());
+    System.out.println("300 kelvin - 32 fahrenheit = " +
+      ((Real)kelvin.subtract(fahrenheit)).toValueString());
+    System.out.println("max(300 kelvin, 32 fahrenheit) = " +
+      ((Real)kelvin.max(fahrenheit)).toValueString());
+    System.out.println("min(300 kelvin, 32 fahrenheit) = " +
+      ((Real)kelvin.min(fahrenheit)).toValueString());
+
+    System.out.println("");
+
+    System.out.println("32 fahrenheit + 300 kelvin = " +
+      ((Real)fahrenheit.add(kelvin)).toValueString());
+    System.out.println("32 fahrenheit - 300 kelvin = " +
+      ((Real)fahrenheit.subtract(kelvin)).toValueString());
+    System.out.println("max(32 fahrenheit, 300 kelvin) = " +
+      ((Real)fahrenheit.max(kelvin)).toValueString());
+    System.out.println("min(32 fahrenheit, 300 kelvin) = " +
+      ((Real)fahrenheit.min(kelvin)).toValueString());
+
+    System.out.println("");
+
+    Real deltaF =
+      new Real(
+        RealType.getRealType(
+          "DeltaFahrenheitTemperature",
+          new OffsetUnit(459.67,
+            new ScaledUnit(1/1.8, SI.kelvin, "degR"),
+              "degF"),
+          null,
+          RealType.INTERVAL),
+        32.0);
+    System.out.println("300 kelvin + 32 deltaF = " +
+      ((Real)kelvin.add(deltaF)).toValueString());
+    System.out.println("300 kelvin - 32 deltaF = " +
+      ((Real)kelvin.subtract(deltaF)).toValueString());
+    System.out.println("max(300 kelvin, 32 deltaF) = " +
+      ((Real)kelvin.max(deltaF)).toValueString());
+    System.out.println("min(300 kelvin, 32 deltaF) = " +
+      ((Real)kelvin.min(deltaF)).toValueString());
+
+    System.out.println("");
+
+    System.out.println("32 deltaF + 300 kelvin = " +
+      ((Real)deltaF.add(kelvin)).toValueString());
+    System.out.println("32 deltaF - 300 kelvin = " +
+      ((Real)deltaF.subtract(kelvin)).toValueString());
+    System.out.println("max(32 deltaF, 300 kelvin) = " +
+      ((Real)deltaF.max(kelvin)).toValueString());
+    System.out.println("min(32 deltaF, 300 kelvin) = " +
+      ((Real)deltaF.min(kelvin)).toValueString());
+
+    System.out.println("");
+
+    Real deltaK =
+      new Real(
+        RealType.getRealType( "DeltaTemperature", SI.kelvin, null, RealType.INTERVAL),
+        100.0);
+    System.out.println("300 kelvin + 100 deltaK = " +
+      ((Real)kelvin.add(deltaK)).toValueString());
+    System.out.println("300 kelvin - 100 deltaK = " +
+      ((Real)kelvin.subtract(deltaK)).toValueString());
+    System.out.println("max(300 kelvin, 100 deltaK) = " +
+      ((Real)kelvin.max(deltaK)).toValueString());
+    System.out.println("min(300 kelvin, 100 deltaK) = " +
+      ((Real)kelvin.min(deltaK)).toValueString());
+
+    System.out.println("");
+
+    System.out.println("100 deltaK + 300 kelvin = " +
+      ((Real)deltaK.add(kelvin)).toValueString());
+    System.out.println("100 deltaK - 300 kelvin = " +
+      ((Real)deltaK.subtract(kelvin)).toValueString());
+    System.out.println("max(100 deltaK, 300 kelvin) = " +
+      ((Real)deltaK.max(kelvin)).toValueString());
+    System.out.println("min(100 deltaK, 300 kelvin) = " +
+      ((Real)deltaK.min(kelvin)).toValueString());
+
+    System.out.println("");
+
+    System.out.println("100 deltaK + 32 deltaF = " +
+      ((Real)deltaK.add(deltaF)).toValueString());
+    System.out.println("100 deltaK - 32 deltaF = " +
+      ((Real)deltaK.subtract(deltaF)).toValueString());
+    System.out.println("max(100 deltaK, 32 deltaF) = " +
+      ((Real)deltaK.max(deltaF)).toValueString());
+    System.out.println("min(100 deltaK, 32 deltaF) = " +
+      ((Real)deltaK.min(deltaF)).toValueString());
+
+    System.out.println("");
+
+    System.out.println("32 deltaF + 100 deltaK = " +
+      ((Real)deltaF.add(deltaK)).toValueString());
+    System.out.println("32 deltaF - 100 deltaK = " +
+      ((Real)deltaF.subtract(deltaK)).toValueString());
+    System.out.println("max(32 deltaF, 100 deltaK) = " +
+      ((Real)deltaF.max(deltaK)).toValueString());
+    System.out.println("min(32 deltaF, 100 deltaK) = " +
+      ((Real)deltaF.min(deltaK)).toValueString());
+
+    System.out.println("");
+
+    System.out.println("300 kelvin + -(32 fahrenheit) = " +
+      ((Real)kelvin.add(fahrenheit.negate())).toValueString());
+    System.out.println("32 fahrenheit + -(300 kelvin) = " +
+      ((Real)fahrenheit.add(kelvin.negate())).toValueString());
+
+    System.out.println("");
+
+    Unit        foot = new ScaledUnit(3.048, SI.meter);
+    Unit        yard = new ScaledUnit(3, (ScaledUnit)foot);
+    System.out.println("log(1 yard / 3 feet) = " +
+      ((Real)new Real(RealType.getRealType("OneYard", SI.meter, null), 1, yard)
+        .divide(new Real(RealType.getRealType("ThreeFeet", SI.meter, null), 3, foot))
+        .log()).toValueString());
+  }
+
+/* Here's the output:
+
+x = 12.0
+w = 10.0
+x + w = 22.0
+x - w = 2.0
+x * w = 120.0
+x / w = 1.2
+sqrt(x) = 3.4641016151377544
+
+300 kelvin + 32 fahrenheit = 573.15 K
+300 kelvin - 32 fahrenheit = 26.85 K
+max(300 kelvin, 32 fahrenheit) = 300.0 K
+min(300 kelvin, 32 fahrenheit) = 273.15 K
+
+32 fahrenheit + 300 kelvin = 1031.67 degR
+32 fahrenheit - 300 kelvin = -48.33 degR
+max(32 fahrenheit, 300 kelvin) = 540.0 degR
+min(32 fahrenheit, 300 kelvin) = 491.67 degR
+
+300 kelvin + 32 deltaF = 317.77777 K
+300 kelvin - 32 deltaF = 282.22223 K
+max(300 kelvin, 32 deltaF) = 300.0 K
+min(300 kelvin, 32 deltaF) = 17.777779 K
+
+32 deltaF + 300 kelvin = 572.0 degR
+32 deltaF - 300 kelvin = -508.0 degR
+max(32 deltaF, 300 kelvin) = 540.0 degR
+min(32 deltaF, 300 kelvin) = 32.0 degR
+
+300 kelvin + 100 deltaK = 400.0 K
+300 kelvin - 100 deltaK = 200.0 K
+max(300 kelvin, 100 deltaK) = 300.0 K
+min(300 kelvin, 100 deltaK) = 100.0 K
+
+100 deltaK + 300 kelvin = 400.0 K
+100 deltaK - 300 kelvin = -200.0 K
+max(100 deltaK, 300 kelvin) = 300.0 K
+min(100 deltaK, 300 kelvin) = 100.0 K
+
+100 deltaK + 32 deltaF = 117.77778 K
+100 deltaK - 32 deltaF = 82.22222 K
+max(100 deltaK, 32 deltaF) = 100.0 K
+min(100 deltaK, 32 deltaF) = 17.777779 K
+
+32 deltaF + 100 deltaK = 212.0 degR
+32 deltaF - 100 deltaK = -148.0 degR
+max(32 deltaF, 100 deltaK) = 180.0 degR
+min(32 deltaF, 100 deltaK) = 32.0 degR
+
+300 kelvin + -(32 fahrenheit) = 26.85 K
+32 fahrenheit + -(300 kelvin) = -48.33 degR
+
+log(1 yard / 3 feet) = 0.0 
+
+*/
+
+}
+
diff --git a/visad/RealIface.java b/visad/RealIface.java
new file mode 100644
index 0000000..99ea17e
--- /dev/null
+++ b/visad/RealIface.java
@@ -0,0 +1,150 @@
+//
+// RealIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+ *  Interface to scalar data for real numbers represented
+ *  as double precision floating point values.  Double.NaN is used to
+ *  indicate missing values, because it has the appropriate arithmetic
+ *  semantics.
+ */
+public interface RealIface
+  extends ScalarIface
+{
+  /**
+   * Returns the numeric value in the unit of {@link #getUnit()}.
+   *
+   * @return		The numeric value in the unit of {@link #getUnit()}.
+   */
+  double getValue();
+
+  /**
+   * Returns the numeric value in a particular unit.
+   *
+   * @param unit_out	The desired unit for the numeric value.  Must be
+   *				convertible with {@link #getUnit()}.
+   * @return		The numeric value in the given unit.
+   */
+  double getValue(Unit unit_out)
+    throws VisADException;
+
+  /**
+   * Returns the unit of this instance.
+   *
+   * @return			The unit of this instance.
+   */
+  Unit getUnit();
+
+  /**
+   * Returns the uncertainty in the numeric value of this instance.
+   *
+   * @return			The uncertainty in the numeric value of this
+   *				instance.
+   */
+  ErrorEstimate getError();
+
+  /**
+   * Returns a clone, except that the ErrorEstimate of the clone
+   * is adjusted for a given error mode and uncertainty.
+   *
+   * @param error		The uncertainty by which to adjust the clone.
+   * @param error_mode		The mode for propagating errors.  See {@link
+   *				Data}.
+   * @return			A clone of this instance with a modified
+   *				uncertainty.
+   * @throws VisADException	VisAD failure.
+   * @throws RemoteException	Java RMI failure.
+   */
+  Data adjustSamplingError(Data error, int error_mode)
+    throws VisADException, RemoteException;
+
+  /**
+   * Returns a clone of this instance with a different numeric value.  The unit
+   * is unchanged.
+   *
+   * @param value		The numeric value for the clone.
+   * @return			A clone of this nstance with the given numeric
+   *				value.
+   * @throws VisADException	VisAD failure.
+   */
+  Real cloneButValue(double value)
+    throws VisADException;
+
+  /**
+   * Returns a clone of this instance but with a new Unit.  The numeric value is
+   * unchanged.
+   *
+   * @param u			The unit for the clone.
+   * @return			A clone of this instance but with the given
+   *				unit.
+   */
+  Real cloneButUnit(Unit u)
+    throws VisADException;
+
+  /*
+   * Returns a string representation of this instance.
+   *
+   * @return			A string representation of this instance.
+   */
+  String toString();
+
+  /**
+   * Returns a string that represents just the value portion of this Real -- but
+   * with full semantics (e.g. numeric value and unit).
+   *
+   * @return			A string representation of just the value
+   *				portion of this Real.
+   */
+  String toValueString();
+
+  /**
+   * Compares this Real to another.
+   *
+   * @param object		The other Real to compare against.  It shall be
+   *				a Real with a compatible (i.e. convertible)
+   *				unit.
+   * @return                    A negative integer, zero, or a positive integer
+   *                            depending on whether this Real is considered
+   *                            less than, equal to, or greater than the other
+   *                            Real, respectively.  If the values of the Real-s
+   *                            in the default unit are equal, then the <code>
+   *                            ErrorEstimate.compareTo()</code> method is used
+   *                            to break the tie.
+   */
+  int compareTo(Object object);
+
+  /**
+   * Returns the hash code of this Real.
+   *
+   * @return			The hash code of this Real.  If two Real-s are
+   *				semantically identical, then their hash codes
+   *				are equal.
+   */
+  int hashCode();
+}
diff --git a/visad/RealTuple.java b/visad/RealTuple.java
new file mode 100644
index 0000000..2fb46a0
--- /dev/null
+++ b/visad/RealTuple.java
@@ -0,0 +1,485 @@
+//
+// RealTuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RealTuple is the VisAD data class for vectors in R^n for n>0.
+   RealTuple objects are immutable.<P>
+*/
+public class RealTuple
+  extends	Tuple
+  implements	RealTupleIface
+{
+
+  private CoordinateSystem TupleCoordinateSystem;
+
+  private boolean checkRealUnits;
+
+  /*
+   * Simply copies of Unit-s from Real tupleComponents.
+   * Will be null if RealTuple(RealTupleType) constructor is used.
+   */
+  private Unit[] TupleUnits = null;
+
+  /** 
+   * construct a RealTuple object with the missing value 
+   * @param type  RealTupleType of this RealTuple
+   */
+  public RealTuple(RealTupleType type) {
+    super(type);
+    TupleCoordinateSystem = type.getCoordinateSystem();
+    if (tupleComponents != null) {
+      int n = tupleComponents.length;
+      TupleUnits = new Unit[n];
+      for (int i=0; i<n; i++) TupleUnits[i] = null;
+    }
+  }
+
+  /** 
+   * construct a RealTuple according to an array of Real objects;
+   * coordinate_system may be null; otherwise coordinate_system.getReference() 
+   * must equal type.getCoordinateSystem.getReference() 
+   *
+   * @param type  RealTupleType of this RealTuple
+   * @param reals array of reals
+   * @param coord_sys  CoordinateSystem for this RealTuple
+   */
+  public RealTuple(RealTupleType type, Real[] reals, CoordinateSystem coord_sys)
+         throws VisADException, RemoteException {
+      this(type, reals, coord_sys, null, true);
+  }
+
+  /** 
+   * Construct a RealTuple according to an array of Real objects;
+   * coordinate_system may be null; otherwise coordinate_system.getReference() 
+   * must equal type.getCoordinateSystem.getReference() 
+   *
+   * @param type  RealTupleType of this RealTuple
+   * @param reals array of reals
+   * @param coord_sys  CoordinateSystem for this RealTuple
+   * @param units array of Units corresponding to the array of Reals.
+   * @param checkUnits  true to make sure the units of the Reals are convertible
+   *                    with the RealType units.  <b>NB: setting this to false
+   *                    can cause problems if the units are not convertible.
+   *                    Only do this if you know what you are doing.</b>
+   */
+  public RealTuple(RealTupleType type, Real[] reals, CoordinateSystem coord_sys,
+                   Unit[] units, boolean checkUnits)
+         throws VisADException, RemoteException {
+    super(type, reals, false);  // copy == false because Reals are immutable
+    TupleUnits = units;
+    checkRealUnits = checkUnits;
+    init_coord_sys(coord_sys);
+  }
+
+  /** 
+   * construct a RealTuple according to an array of Real objects 
+   * @param reals array of reals
+   */
+  public RealTuple(Real[] reals)
+         throws VisADException, RemoteException {
+    this((RealTupleType)buildTupleType(reals), reals, null, 
+          buildTupleUnits(reals), false);
+  }
+
+  /** 
+   * Construct a RealTuple according to a RealTupleType and a double array 
+   * @param type  RealTupleType of this RealTuple
+   * @param values values for each component.  Units are the default units
+   *               of the RealTupleType components.
+   */
+  public RealTuple(RealTupleType type, double[] values)
+         throws VisADException, RemoteException {
+    this(type, buildRealArray(type, values), null, type.getDefaultUnits(), false);
+  }
+
+  /** initialize TupleCoordinateSystem and TupleUnits */
+  private void init_coord_sys(CoordinateSystem coord_sys)
+          throws VisADException {
+    CoordinateSystem cs = ((RealTupleType) Type).getCoordinateSystem();
+    if (coord_sys == null) {
+      TupleCoordinateSystem = cs;
+    }
+    else {
+      if (cs == null || !cs.getReference().equals(coord_sys.getReference())) {
+        throw new CoordinateSystemException("RealTuple: coord_sys " +
+                                            coord_sys.getReference() +
+                                            " must match" +
+                                            " Type.DefaultCoordinateSystem " +
+                                            (cs == null ? null :
+                                             cs.getReference()));
+      }
+      TupleCoordinateSystem = coord_sys;
+    }
+    if (TupleCoordinateSystem != null &&
+        !Unit.canConvertArray(TupleCoordinateSystem.getCoordinateSystemUnits(),
+                              ((RealTupleType) Type).getDefaultUnits())) {
+      throw new UnitException("RealTuple: CoordinateSystem Units must be " +
+                              "convertable with Type default Units");
+    }
+
+    if (TupleUnits == null) {
+      int n = tupleComponents.length;
+      TupleUnits = new Unit[n];
+      for (int i=0; i<n; i++) {
+           TupleUnits[i] = ((Real) tupleComponents[i]).getUnit();
+      }
+    }
+
+    if (checkRealUnits) {
+        if(!Unit.canConvertArray(TupleUnits,
+                                 ((RealTupleType) Type).getDefaultUnits())) {
+          throw new UnitException("Tuple: Units must be convertable with " +
+                                  "Type default Units");
+        }
+    }
+
+    if(TupleCoordinateSystem != null &&
+       !Unit.canConvertArray(TupleCoordinateSystem.getCoordinateSystemUnits(),
+                             TupleUnits)) {
+      throw new UnitException("Tuple: Units must be convertable with " +
+                              "CoordinateSystem Units");
+    }
+  }
+
+  private static Real[] buildRealArray(RealTupleType type, double[] values)
+                        throws VisADException {
+    Real[] reals = new Real[values.length];
+    for (int i=0; i<values.length; i++) {
+      reals[i] = new Real((RealType) type.getComponent(i), values[i]);
+    }
+    return reals;
+  }
+
+  private static Unit[] buildTupleUnits(Real[] reals) throws VisADException {
+    if (reals == null || reals.length == 0) return (Unit[]) null;
+    int n = reals.length;
+    Unit[] units = new Unit[reals.length];
+    for (int i=0; i<n; i++) units[i] = reals[i].getUnit();
+    return units;
+  }
+
+  /**
+   * Adds a listener for changes to this instance.  Because instances of this
+   * class don't change, this method does nothing.
+   *
+   * @param listener                     The listener for changes.
+   */
+  public final void addReference(ThingReference listener) {
+  }
+
+  /**
+   * Removes a listener for changes to this instance.  Because instances of this
+   * class don't change, this method does nothing.
+   *
+   * @param listener                    The change listener to be removed.
+   */
+  public final void removeReference(ThingReference listener) {
+  }
+
+  /**
+   * Get the values of the Real components
+   * @return double array of the values of each Real component
+   */
+  public double[] getValues() {
+    int n = getDimension();
+    double[] values = new double[n];
+    Data[] tupleComps = getComponents(false);
+    for (int i=0; i<n; i++) values[i] = ((Real) tupleComps[i]).getValue();
+    return values;
+  }
+
+  /** get Units of Real components */
+  public Unit[] getTupleUnits() {
+    return Unit.copyUnitsArray(TupleUnits);
+  }
+
+  /** get ErrorEstimates of Real components */
+  public ErrorEstimate[] getErrors()
+         throws VisADException, RemoteException {
+    int n = getDimension();
+    ErrorEstimate[] errors = new ErrorEstimate[n];
+    for (int i=0; i<n; i++) errors[i] = ((Real) getComponent(i)).getError();
+    return errors;
+  }
+
+  /** get CoordinateSystem */
+  public CoordinateSystem getCoordinateSystem() {
+    return TupleCoordinateSystem;
+  }
+
+  /*- TDR  May 1998
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+   */
+  public Data binary(Data data, int op, MathType new_type,
+                     int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    /* BINARY - TDR May 28, 1998  */
+    MathType m_type;
+    if ( new_type == null ) {
+      throw new TypeException("binary: new_type may not be null" );
+    }
+    /* BINARY - end  */
+    if (data instanceof RealTuple) {
+      if (!Type.equalsExceptName(data.getType())) {
+        throw new TypeException("RealTuple.binary: types don't match");
+      }
+      /*- TDR May  1998 */
+      if ( !Type.equalsExceptName(new_type) ) {
+        throw new TypeException("RealTuple.binary: new_type doesn't match return type");
+      }
+      /*- end  */
+      if (isMissing() || data.isMissing()) {
+        return new RealTuple((RealTupleType) new_type);
+      }
+      int dim = getDimension();
+      double[][] vals = new double[dim][1];
+      for (int j=0; j<dim; j++) {
+        vals[j][0] = ((Real) ((RealTuple) data).getComponent(j)).getValue();
+      }
+      ErrorEstimate[] errors_out = new ErrorEstimate[dim];
+      vals = CoordinateSystem.transformCoordinates(
+                      (RealTupleType) Type, getCoordinateSystem(),
+                      getTupleUnits(), errors_out,
+                      (RealTupleType) data.getType(),
+                      ((RealTuple) data).getCoordinateSystem(),
+                      ((RealTuple) data).getTupleUnits(),
+                      ((RealTuple) data).getErrors(), vals);
+      Real[] reals = new Real[dim];
+      Unit[] tupleUnits = getTupleUnits();
+      for (int j=0; j<dim; j++) {
+        Real real = new Real((RealType) ((RealTupleType) Type).getComponent(j),
+                             vals[j][0], tupleUnits[j], errors_out[j]);
+        /*- TDR May 1998 */
+        m_type = ((RealTupleType)new_type).getComponent(j);
+        /*- end */
+        reals[j] =
+        /*- TDR May 1998
+          (Real) getComponent(j).binary(real, op, sampling_mode, error_mode);
+         */
+          (Real) getComponent(j).binary(real, op, m_type, sampling_mode, error_mode);
+      }
+      /* BINARY - TDR May 28, 1998
+      return new RealTuple((RealTupleType)Type, reals, TupleCoordinateSystem);
+      */
+      return new RealTuple((RealTupleType) new_type, reals, null );
+    }
+    else if (data instanceof TupleIface) {
+      throw new TypeException("RealTuple.binary: types don't match");
+    }
+    else if (data instanceof Real) {
+      if (isMissing() || data.isMissing()) {
+        return new RealTuple((RealTupleType) Type);
+      }
+      int dim = getDimension();
+      Real[] reals = new Real[dim];
+      for (int j=0; j<dim; j++) {
+        m_type = ((RealTupleType)new_type).getComponent(j);
+
+        /*- TDR May 1998
+        reals[j] = (Real) getComponent(j).binary(data, op, sampling_mode,
+                                                    error_mode);
+        */
+        reals[j] = (Real) getComponent(j).binary(data, op, m_type,
+                                             sampling_mode, error_mode);
+
+      }
+      /*- TDR May 1998
+      return new RealTuple((RealTupleType) Type, reals, TupleCoordinateSystem);
+      */
+      return new RealTuple((RealTupleType) new_type, reals, null );
+    }
+    else if (data instanceof Field) {
+      /*- TDR June 3, 1998 */
+      if ( !(data.getType()).equalsExceptName(new_type) ) {
+        throw new TypeException();
+      }
+        return data.binary(this, invertOp(op), new_type, sampling_mode, error_mode);
+      /*- end  */
+    }
+    else {
+      throw new TypeException("RealTuple.binary");
+    }
+  }
+
+  /*-  TDR  July 1998
+  public Data unary(int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+  */
+  public Data unary(int op, MathType new_type,
+                    int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if ( new_type == null ) {
+      throw new TypeException("unary: new_type may not be null");
+    }
+
+    if ( !Type.equalsExceptName(new_type)) {
+      throw new TypeException("unary: new_type doesn't match return type");
+    }
+    RealTupleType RT_type= (RealTupleType)new_type;
+
+    if (isMissing()) return new RealTuple((RealTupleType) Type);
+    int dim = getDimension();
+    Real[] reals = new Real[dim];
+    for (int j=0; j<dim; j++) {
+      reals[j] = (Real) getComponent(j).unary(op, RT_type.getComponent(j),
+                                                 sampling_mode, error_mode);
+    }
+    return new RealTuple((RealTupleType) new_type, reals, getCoordinateSystem());
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    shadow = super.computeRanges(type, shadow);
+    ShadowRealTupleType shad_ref = ((ShadowRealTupleType) type).getReference();
+    if (isMissing() || shad_ref == null) return shadow;
+    int n = tupleComponents.length;
+    // computeRanges for Reference RealTypes
+    double[][] ranges = new double[2][n];
+    for (int i=0; i<n; i++) {
+      double value = ((Real) getComponent(i)).getValue();
+
+      // WLH 20 Nov 2001
+      Unit[] tupleUnits = getTupleUnits();
+      Unit unit =
+        ((RealType) ((RealTupleType) Type).getComponent(i)).getDefaultUnit();
+      if (unit != null && !unit.equals(tupleUnits[i])) {
+        value = unit.toThis(value, tupleUnits[i]);
+      }
+
+      if (value != value) return shadow;
+      ranges[0][i] = value;
+      ranges[1][i] = value;
+    }
+    return computeReferenceRanges((ShadowRealTupleType) type,
+                                  // getCoordinateSystem(), getTupleUnits(),
+                                  getCoordinateSystem(),
+                                  ((RealTupleType) Type).getDefaultUnits(),
+                                  shadow, shad_ref, ranges);
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                    A clone of this instance.
+   */
+  public final Object clone() {
+      /*
+       * I (Steve Emmerson) believe that this implementation should return
+       * "this" to reduce the memory-footprint but Bill believes that doing so
+       * would be counter-intuitive and might harm applications.
+       */
+    try {
+      return super.clone();
+    }
+    catch (CloneNotSupportedException ex) {
+      throw new RuntimeException("Assertion failure");
+    }
+  }
+
+  /**
+   * Provide a String representation of this RealTuple.
+   */
+  public String toString() {
+    if (isMissing()) return "missing";
+    Data[] tupleComps = getComponents(false);
+    String s = "(" + tupleComps[0];
+    for (int i=1; i<getDimension(); i++) {
+      s = s + ", " + tupleComps[i];
+    }
+    return s + ")";
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    String s = pre + "RealTuple\n" + pre + "  Type: " + Type.toString() + "\n";
+    if (isMissing()) return s + "  missing\n";
+    for (int i=0; i<getDimension(); i++) {
+      s = s + pre + "  Tuple Component " + i + ": Value = " +
+          ((Real) getComponent(i)).getValue() + "  (TypeName = " +
+          ((RealType) getComponent(i).getType()).getName() + ")\n";
+    }
+    return s;
+  }
+
+  /** run 'java visad.RealTuple' to test the RealTuple class */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    byte b = 10;
+    Real w = new Real(b);
+
+    Real[] reals1 = {new Real(1), new Real(2), new Real(3)};
+    RealTuple rt1 = new RealTuple(reals1);
+    Real[] reals2 = {new Real(6), new Real(5), new Real(4)};
+    RealTuple rt2 = new RealTuple(reals2);
+
+    System.out.println("rt1 = " + rt1 + "\nrt2 = " + rt2);
+
+    System.out.println("rt1 + rt2 = " + rt1.add(rt2));
+    System.out.println("rt1 - rt2 = " + rt1.subtract(rt2));
+    System.out.println("rt1 * rt2 = " + rt1.multiply(rt2));
+    System.out.println("rt1 / rt2 = " + rt1.divide(rt2));
+    System.out.println("sqrt(rt1) = " + rt1.sqrt());
+
+    System.out.println("rt1 + w = " + rt1.add(w));
+    System.out.println("rt1 - w = " + rt1.subtract(w));
+    System.out.println("rt1 * w = " + rt1.multiply(w));
+    System.out.println("rt1 / w = " + rt1.divide(w));
+
+    System.out.println("w + rt2 = " + w.add(rt2));
+    System.out.println("w - rt2 = " + w.subtract(rt2));
+    System.out.println("w * rt2 = " + w.multiply(rt2));
+    System.out.println("w / rt2 = " + w.divide(rt2));
+  }
+
+/* Here's the output:
+
+iris 201% java visad.RealTuple
+rt1 = (1, 2, 3)
+rt2 = (6, 5, 4)
+rt1 + rt2 = (7, 7, 7)
+rt1 - rt2 = (-5, -3, -1)
+rt1 * rt2 = (6, 10, 12)
+rt1 / rt2 = (0.166667, 0.4, 0.75)
+sqrt(rt1) = (1, 1.41421, 1.73205)
+rt1 + w = (11, 12, 13)
+rt1 - w = (-9, -8, -7)
+rt1 * w = (10, 20, 30)
+rt1 / w = (0.1, 0.2, 0.3)
+w + rt2 = (16, 15, 14)
+w - rt2 = (4, 5, 6)
+w * rt2 = (60, 50, 40)
+w / rt2 = (1.66667, 2, 2.5)
+iris 202%
+
+*/
+
+}
diff --git a/visad/RealTupleIface.java b/visad/RealTupleIface.java
new file mode 100644
index 0000000..da76283
--- /dev/null
+++ b/visad/RealTupleIface.java
@@ -0,0 +1,82 @@
+//
+// RealTupleIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+ * Interface to the VisAD data class for vectors in R^n for n>0.
+ */
+public interface RealTupleIface
+  extends TupleIface
+{
+  /**
+   * Returns the values of the components.
+   *
+   * @return			The values of the components.
+   */
+  double[] getValues();
+
+  /**
+   * Returns the units of the components.
+   *
+   * @return			The units of the components.
+   */
+  Unit[] getTupleUnits();
+
+  /**
+   * Returns the uncertainties of the components.
+   *
+   * @return			The uncertainties of the components.
+   * @throws VisADException	VisAD failure.
+   * @throws RemoteException	Java RMI failure.
+   */
+  ErrorEstimate[] getErrors()
+    throws VisADException, RemoteException;
+
+  /**
+   * Returns the coordinate system transformation.
+   *
+   * @return			The coordinate system transformation.  May be
+   *				<code>null</code>.
+   */
+  CoordinateSystem getCoordinateSystem();
+
+  /**
+   * Clones this instance.
+   *
+   * @return			A clone of this instance.
+   */
+  Object clone();
+
+  /**
+   * Returns a string representation of this instance.
+   *
+   * @return			A string representation of this instance.
+   */
+  String toString();
+}
diff --git a/visad/RealTupleType.java b/visad/RealTupleType.java
new file mode 100644
index 0000000..12ca399
--- /dev/null
+++ b/visad/RealTupleType.java
@@ -0,0 +1,505 @@
+//
+// RealTupleType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.Vector;
+
+/**
+   RealTupleType is the VisAD data type for tuples in R^n, for n>0.<P>
+*/
+public class RealTupleType extends TupleType {
+
+  /** if not null, this coordinate system is used as a default for
+      RealTuple-s of this type */
+  private CoordinateSystem DefaultCoordinateSystem;
+
+  /** default Unit-s derived from RealType components */
+  private Unit[] DefaultUnits;
+
+  /** if not null, this sampling is used as a default when this type
+      is used as the domain or range of a field;
+      null unless explicitly set */
+  private Set DefaultSet;
+  private boolean DefaultSetEverAccessed;
+
+  private static RealType[] components2c =
+    {RealType.XAxis, RealType.YAxis};
+  /**
+   * System intrinsic
+   * RealTupleType for (RealType.XAxis, RealType.YAxis)
+   */
+  public static final RealTupleType SpatialCartesian2DTuple =
+    new RealTupleType(components2c, true);
+
+
+  private static RealType[] components3c =
+    {RealType.XAxis, RealType.YAxis, RealType.ZAxis};
+  /**
+   * System intrinsic
+   * for (RealType.XAxis, RealType.YAxis, RealType.ZAxis)
+   */
+  public static final RealTupleType SpatialCartesian3DTuple =
+    new RealTupleType(components3c, true);
+
+
+  private static RealType[] components2e =
+    {RealType.Longitude, RealType.Latitude};
+  /**
+   * System intrinsic
+   * for (RealType.Longitude, RealType.Latitude)
+   */
+  public final static RealTupleType SpatialEarth2DTuple =
+    new RealTupleType(components2e, true);
+
+
+  private static RealType[] componentsll =
+    {RealType.Latitude, RealType.Longitude};
+  /**
+   * System intrinsic
+   * for (RealType.Latitude, RealType.Longitude)
+   */
+  public final static RealTupleType LatitudeLongitudeTuple =
+    new RealTupleType(componentsll, true);
+
+
+  private static RealType[] components3e =
+    {RealType.Longitude, RealType.Latitude, RealType.Altitude};
+  /**
+   * System intrinsic
+   * for (RealType.Longitude, RealType.Latitude, RealType.Altitude)
+   */
+  public final static RealTupleType SpatialEarth3DTuple =
+    new RealTupleType(components3e, true);
+
+
+  private static RealType[] componentslla =
+    {RealType.Latitude, RealType.Longitude, RealType.Altitude};
+  /**
+   * System intrinsic
+   * for (RealType.Latitude, RealType.Longitude, RealType.Altitude)
+   */
+  public final static RealTupleType LatitudeLongitudeAltitude =
+    new RealTupleType(componentslla, true);
+
+
+  private static RealType[] components1t =
+    {RealType.Time};
+  /**
+   * System intrinsic for (RealType.Time)
+   */
+  public static final RealTupleType Time1DTuple =
+    new RealTupleType(components1t, true);
+
+
+  private static RealType[] components2g =
+    {RealType.Generic, RealType.Generic};
+  /**
+   * System intrinsic for (RealType.Generic, RealType.Generic)
+   */
+  public final static RealTupleType Generic2D =
+    new RealTupleType(components2g, true);
+
+  private static RealType[] components3g =
+    {RealType.Generic, RealType.Generic, RealType.Generic};
+  /**
+   * System intrinsic
+   * for (RealType.Generic, RealType.Generic, RealType.Generic)
+   */
+  public final static RealTupleType Generic3D =
+    new RealTupleType(components3g, true);
+
+
+  /** array of component types;
+      default CoordinateSystem and Set are null */
+  public RealTupleType(RealType[] types) throws VisADException {
+    this(types, null, null);
+  }
+
+  /** construct a RealTupleType with one component */
+  public RealTupleType(RealType a) throws VisADException {
+    this(makeArray(a), null, null);
+  }
+
+  /** construct a RealTupleType with two components */
+  public RealTupleType(RealType a, RealType b) throws VisADException {
+    this(makeArray(a, b), null, null);
+  }
+
+  /** construct a RealTupleType with three components */
+  public RealTupleType(RealType a, RealType b, RealType c)
+         throws VisADException {
+    this(makeArray(a, b, c), null, null);
+  }
+
+  /** construct a RealTupleType with four components */
+  public RealTupleType(RealType a, RealType b, RealType c, RealType d)
+         throws VisADException {
+    this(makeArray(a, b, c, d), null, null);
+  }
+
+  /** array of component types;
+      default CoordinateSystem for values of this type (including
+      Function domains) and may be null; default Set used when this
+      type is a FunctionType domain and may be null */
+  public RealTupleType(RealType[] types, CoordinateSystem coord_sys, Set set)
+         throws VisADException {
+    super(types);
+    if (coord_sys != null && types.length != coord_sys.getDimension()) {
+      throw new CoordinateSystemException(
+        "RealTupleType: bad CoordinateSystem dimension");
+    }
+    DefaultCoordinateSystem = coord_sys;
+    DefaultSet = set;
+    DefaultSetEverAccessed = false;
+    setDefaultUnits(types);
+    if (DefaultCoordinateSystem != null &&
+        !Unit.canConvertArray(DefaultCoordinateSystem.getCoordinateSystemUnits(),
+                              DefaultUnits)) {
+      throw new UnitException("RealTupleType: CoordinateSystem Units must be " +
+                              "convertable with default Units");
+    }
+    if (DefaultSet != null &&
+        !Unit.canConvertArray(DefaultSet.getSetUnits(), DefaultUnits)) {
+      throw new UnitException("RealTupleType: default Set Units must be " +
+                              "convertable with default Units");
+    }
+    if (DefaultCoordinateSystem != null && DefaultSet != null) {
+      CoordinateSystem cs = DefaultSet.getCoordinateSystem();
+      if (cs != null &&
+          !cs.getReference().equals(DefaultCoordinateSystem.getReference())) {
+        throw new CoordinateSystemException("RealTupleType: Default coordinate system " +
+                                            (coord_sys == null ? null :
+                                             coord_sys.getReference()) +
+                                            " must match" +
+                                            " default set CoordinateSystem " +
+                                            (cs == null ? null :
+                                             cs.getReference()));
+      }
+      if (!Unit.canConvertArray(DefaultCoordinateSystem.getCoordinateSystemUnits(),
+                                DefaultSet.getSetUnits())) {
+        throw new UnitException("RealTupleType: CoordinateSystem Units must be " +
+                                "convertable with default Set Units");
+      }
+    }
+  }
+
+  /** construct a RealTupleType with one component */
+  public RealTupleType(RealType a, CoordinateSystem coord_sys,
+         Set set) throws VisADException {
+    this(makeArray(a), coord_sys, set);
+  }
+
+  /** construct a RealTupleType with two components */
+  public RealTupleType(RealType a, RealType b,
+         CoordinateSystem coord_sys, Set set) throws VisADException {
+    this(makeArray(a, b), coord_sys, set);
+  }
+
+  /** construct a RealTupleType with three components */
+  public RealTupleType(RealType a, RealType b, RealType c,
+         CoordinateSystem coord_sys, Set set) throws VisADException {
+    this(makeArray(a, b, c), coord_sys, set);
+  }
+
+  /** construct a RealTupleType with four components */
+  public RealTupleType(RealType a, RealType b, RealType c, RealType d,
+         CoordinateSystem coord_sys, Set set) throws VisADException {
+    this(makeArray(a, b, c, d), coord_sys, set);
+  }
+
+  /** trusted constructor for initializers */
+  RealTupleType(RealType[] types, boolean b) {
+    this(types, null, b);
+  }
+
+  /** trusted constructor for initializers */
+  RealTupleType(RealType[] types, CoordinateSystem coord_sys, boolean b) {
+    super(types, b);
+    DefaultCoordinateSystem = coord_sys;
+    DefaultSet = null;
+    DefaultSetEverAccessed = false;
+    setDefaultUnits(types);
+    if (DefaultCoordinateSystem != null &&
+        !Unit.canConvertArray(DefaultCoordinateSystem.getCoordinateSystemUnits(),
+                              DefaultUnits)) {
+      throw new VisADError("RealTupleType (trusted): CoordinateSystem Units " +
+                           "must be convertable with default Units");
+    }
+  }
+
+  /**
+   * Performs an arithmetic operation with another MathType.  CoordinateSystem
+   * information, if it exists, will be lost.
+   *
+   * @param type		The other  MathType.
+   * @param op			The arithmetic operation (see
+   *				<code>Data</code>).
+   * @param names		Database of names.
+   * @return                    The MathType corresponding to the specified
+   *                            arithmetic operation between this and the
+   *                            other MathType.  If the returned type is
+   *                            a RealTupleType, then it will not have a
+   *                            CoordinateSystem.
+   * @throws TypeException	<code>type</code> is </code>null</code> or
+   *				can't be arithmetically combined with a
+   *				RealTupleType.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @see visad.Data
+   */
+  public MathType binary( MathType type, int op, Vector names )
+                  throws TypeException, VisADException
+  {
+    if (type == null) {
+      throw new TypeException("RealTupleType.binary: type may not be null" );
+    }
+    if (type instanceof RealTupleType) {
+      int n_comps = getDimension();
+      RealType[] new_types = new RealType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        RealType type_component =
+          (RealType) ((RealTupleType) type).getComponent(ii);
+        new_types[ii] =
+          (RealType) (this.getComponent(ii)).binary( type_component, op, names );
+      }
+      return new RealTupleType( new_types );
+    }
+    else if (type instanceof RealType) {
+      int n_comps = getDimension();
+      RealType[] new_types = new RealType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        new_types[ii] =
+          (RealType) (this.getComponent(ii)).binary( type, op, names );
+      }
+      return new RealTupleType( new_types );
+    }
+    else if (type instanceof FunctionType &&
+             ((FunctionType) type).getRange().equalsExceptName(this)) {
+      return new FunctionType(((FunctionType) type).getDomain(),
+        ((FunctionType) type).getRange().binary(this, DataImpl.invertOp(op), names));
+    }
+    else {
+      throw new TypeException("RealTupleType.binary: types don't match" );
+    }
+/* WLH 10 Sept 98
+    int n_comps = getDimension();
+    MathType new_type = null;
+    if (type instanceof RealTupleType)
+    {
+      RealType[] R_types = new RealType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        R_types[ii] = (RealType) getComponent(ii).binary(
+                                 ((RealTupleType)type).getComponent(ii), op, names );
+      }
+      new_type = new RealTupleType( R_types, DefaultCoordinateSystem, null );
+    }
+    else if (type instanceof RealType)
+    {
+      RealType[] R_types = new RealType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        R_types[ii] = (RealType) getComponent(ii).binary( type, op, names );
+      }
+      new_type = new RealTupleType( R_types, DefaultCoordinateSystem, null );
+    }
+    else if (type instanceof TupleType)
+    {
+      throw new TypeException();
+    }
+    else if (type instanceof FunctionType )
+    {
+      new_type = type.binary( this, DataImpl.invertOp(op), names );
+    }
+    return new_type;
+*/
+  }
+
+  public MathType unary( int op, Vector names )
+                  throws VisADException
+  {
+    int n_comps = getDimension();
+    RealType[] R_types = new RealType[ n_comps ];
+
+    for ( int ii = 0; ii < n_comps; ii++ ) {
+      R_types[ii] = (RealType) getComponent(ii).unary(op, names);
+    }
+
+    return new RealTupleType( R_types, DefaultCoordinateSystem, null );
+  }
+
+  public static RealType[] makeArray(RealType a) {
+    RealType[] types = {a};
+    return types;
+  }
+
+  public static RealType[] makeArray(RealType a, RealType b) {
+    RealType[] types = {a, b};
+    return types;
+  }
+
+  public static RealType[] makeArray(RealType a, RealType b, RealType c) {
+    RealType[] types = {a, b, c};
+    return types;
+  }
+
+  public static RealType[] makeArray(RealType a, RealType b, RealType c,
+                                      RealType d) {
+    RealType[] types = {a, b, c, d};
+    return types;
+  }
+
+  private void setDefaultUnits(RealType[] types) {
+    int n = types.length;
+    DefaultUnits = new Unit[n];
+    for (int i=0; i<n; i++) {
+      DefaultUnits[i] = types[i].getDefaultUnit();
+    }
+  }
+
+  /** get default Units of RealType components; copy DefaultUnits
+      array to ensure that it cannot be altered */
+  public Unit[] getDefaultUnits() {
+    return Unit.copyUnitsArray(DefaultUnits);
+  }
+
+  /** get default CoordinateSystem */
+  public CoordinateSystem getCoordinateSystem() {
+    return DefaultCoordinateSystem;
+  }
+
+  /** set the default sampling;
+      this is an unavoidable violation of MathType immutability -
+      a RealTupleType must be an argument (directly or through a
+      SetType) to the constructor of its default Set;
+      this method throws an Exception if getDefaultSet has
+      previously been invoked */
+  public synchronized void setDefaultSet(Set sampling) throws VisADException {
+    if (sampling.getDimension() != getDimension()) {
+      throw new SetException(
+           "RealTupleType.setDefaultSet: dimensions don't match");
+    }
+    if (DefaultSetEverAccessed) {
+      throw new TypeException("RealTupleType: DefaultSet already accessed" +
+                               " so cannot change");
+    }
+    DefaultSet = sampling;
+
+    /* WLH 4 Feb 98 copied from constructor */
+    if (DefaultSet != null &&
+        !Unit.canConvertArray(DefaultSet.getSetUnits(), DefaultUnits)) {
+      throw new UnitException("RealTupleType: default Set Units must be " +
+                              "convertable with default Units");
+    }
+    if (DefaultCoordinateSystem != null && DefaultSet != null) {
+      CoordinateSystem cs = DefaultSet.getCoordinateSystem();
+      if (cs != null &&
+          !cs.getReference().equals(DefaultCoordinateSystem.getReference())) {
+        throw new CoordinateSystemException("RealTupleType: Default coordinate system " +
+                                            DefaultCoordinateSystem.getReference() +
+                                            " must match" +
+                                            " default set CoordinateSystem " +
+                                            (cs == null ? null :
+                                             cs.getReference()));
+      }
+      if (!Unit.canConvertArray(DefaultCoordinateSystem.getCoordinateSystemUnits(),
+                                DefaultSet.getSetUnits())) {
+        throw new UnitException("RealTupleType: CoordinateSystem Units must be " +
+                                "convertable with default Set Units");
+      }
+    }
+  }
+
+  /** get default Set*/
+  public synchronized Set getDefaultSet() {
+    DefaultSetEverAccessed = true;
+    return DefaultSet;
+  }
+
+  public boolean equalsExceptName(MathType type) {
+    int n = getDimension();
+    try {
+      if (type instanceof RealType) {
+        return (n == 1 && getComponent(0) instanceof RealType);
+      }
+      if (!(type instanceof RealTupleType)) return false;
+      if (n != ((TupleType) type).getDimension()) return false;
+      boolean flag = true;
+      for (int i=0; i<n; i++) {
+        flag = flag && getComponent(i).equalsExceptName(
+                              ((TupleType) type).getComponent(i) );
+      }
+      return flag;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  public boolean equalsExceptNameButUnits(MathType type) {
+    int n = getDimension();
+    try {
+      if (type instanceof RealType) {
+        return type.equalsExceptNameButUnits(this);
+      }
+      if (!(type instanceof RealTupleType)) return false;
+      if (n != ((RealTupleType) type).getDimension()) return false;
+      boolean flag = true;
+      for (int i=0; i<n; i++) {
+        flag = flag && getComponent(i).equalsExceptNameButUnits(
+                              ((RealTupleType) type).getComponent(i) );
+      }
+      return flag;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  public String prettyString(int indent) {
+    try {
+      if (getDimension() == 1) {
+        return ((RealType) getComponent(0)).toString();
+      }
+    }
+    catch (VisADException e) {
+      // return toString();  WLH 5 Jan 2000
+      return super.prettyString(0);
+    }
+    // return toString();  WLH 5 Jan 2000
+    return super.prettyString(0);
+  }
+
+  public Data missingData() {
+    return new RealTuple(this);
+  }
+
+  public ShadowType buildShadowType(DataDisplayLink link, ShadowType parent)
+             throws VisADException, RemoteException {
+    return link.getRenderer().makeShadowRealTupleType(this, link, parent);
+  }
+
+}
+
diff --git a/visad/RealType.java b/visad/RealType.java
new file mode 100644
index 0000000..c5aa27b
--- /dev/null
+++ b/visad/RealType.java
@@ -0,0 +1,1213 @@
+//
+// RealType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   RealType is the VisAD scalar data type for real number variables.<P>
+*/
+public class RealType extends ScalarType {
+
+  private static final long serialVersionUID = 1L;
+  private Unit DefaultUnit; // default Unit of RealType
+
+  /** if not null, this sampling is used as a default when this type
+      is used as the domain or range of a field;
+      null unless explicitly set */
+  private Set DefaultSet;
+  private boolean DefaultSetEverAccessed;
+
+  /**
+   * The attribute mask of this RealType.
+   * @serial
+   */
+  private final int     attrMask;
+
+  /**
+   * The interval attribute.  This attribute should be used during construction
+   * of a RealType if the RealType refers to an interval (e.g. length
+   * difference, delta temperature).  In general, RealType-s that are temporal
+   * in nature should have this attribute because the "time" variable in most
+   * formulae actually refer to time differences (e.g. "time since the
+   * beginning of the experiment").  One obvious exception to this lies with
+   * cosmology, where "time" is absolute rather than an interval.
+   */
+  public static final int       INTERVAL = 1;
+
+  /**
+   * Monotonically-increasing counter for creating new, unique names.
+   */
+  private static int            count;
+
+  /** Cartesian spatial coordinate - X axis */
+  public final static RealType XAxis = new RealType("XAxis", null, true);
+  /** Cartesian spatial coordinate - Y axis */
+  public final static RealType YAxis = new RealType("YAxis", null, true);
+  /** Cartesian spatial coordinate - Z axis */
+  public final static RealType ZAxis = new RealType("ZAxis", null, true);
+
+  /** Spherical spatial coordinate for Latitude*/
+  public final static RealType Latitude =
+    new RealType("Latitude", CommonUnit.degree, true);
+  /** Spherical spatial coordinate for Longitude*/
+  public final static RealType Longitude =
+    new RealType("Longitude", CommonUnit.degree, true);
+  public final static RealType Altitude =
+    new RealType("Altitude", CommonUnit.meter, true);
+  public final static RealType Radius =
+    new RealType("Radius", null, true);
+
+  /** Temporal-interval coordinate */
+  public final static RealType TimeInterval =
+    new RealType("TimeInterval", CommonUnit.second, INTERVAL, true);
+
+  /** Timestamp coordinate */
+  public final static RealType Time =
+    new RealType("Time", CommonUnit.secondsSinceTheEpoch, true);
+
+  /** astronomical coordinates */
+  public final static RealType Declination =
+    new RealType("Declination", CommonUnit.degree, true);
+  public final static RealType RightAscension =
+    new RealType("RightAscension", CommonUnit.degree, true);
+
+  /** generic RealType */
+  public final static RealType Generic =
+    new RealType("GENERIC_REAL", CommonUnit.promiscuous, true);
+
+
+  /**
+   * Constructs from a name (two RealTypes are equal if their names are equal).
+   * Assumes <code>null</code> for the default Unit and default Set and that
+   * the RealType does <em>not</em> refer to an interval.
+   * @param name                The name for the RealType.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   * @deprecated Use {@link #getRealType(String)}
+   */
+  public RealType(String name) throws VisADException {
+    this(name, 0);
+  }
+
+  /**
+   * Constructs from a name (two RealTypes are equal if their names are equal)
+   * and whether or not the RealType refers to an interval (e.g. length
+   * difference, delta temperature).  Assumes <code>null</code> for the default
+   * Unit and default Set.
+   * @param name                The name for the RealType.
+   * @param attrMask            The attribute mask. 0 or INTERVAL.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   * @deprecated Use {@link #getRealType(String, int)}
+   */
+  public RealType(String name, int attrMask) throws VisADException {
+    this(name, null, null, attrMask);
+  }
+
+  /**
+   * Constructs from a name (two RealTypes are equal if their names are equal)
+   * a default Unit, and a default Set.  Assumes that the RealType does
+   * <em>not</em> refer to an interval.
+   * @param name                The name for the RealType.
+   * @param u                   The default unit for the RealType.  May be
+   *                            <code>null</code>.
+   * @param set                 The default sampling set for the RealType.
+   *                            Used when this type is a FunctionType domain.
+   *                            May be <code>null</code>.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   * @deprecated Use {@link #getRealType(String, Unit, Set)}
+   */
+  public RealType(String name, Unit u, Set set) throws VisADException {
+    this(name, u, set, 0);
+  }
+
+  /**
+   * Constructs from a name (two RealTypes are equal if their names are equal)
+   * and a default Unit.  Assumes the default Set, and that the
+   * RealType does <em>not</em> refer to an interval.
+   * @param name                The name for the RealType.
+   * @param u                   The default unit for the RealType.  May be
+   *                            <code>null</code>.  
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   * @deprecated Use {@link #getRealType(String, Unit)}
+   */
+  public RealType(String name, Unit u) throws VisADException {
+    this(name, u, null, 0);
+  }
+
+  /**
+   * Constructs from a name (two RealTypes are equal if their names are equal)
+   * a default Unit, a default Set, and whether or not the RealType refers to
+   * an interval (e.g. length difference, delta temperature).  This is the most
+   * general, public constructor.
+   * @param name                The name for the RealType.
+   * @param u                   The default unit for the RealType.  May be
+   *                            <code>null</code>.  If non-<code>null</code>
+   *                            and the RealType refers to an interval,
+   *                            then the default unit will actually be
+   *                            <code>u.getAbsoluteUnit()</code>.
+   * @param set                 The default sampling set for the RealType.
+   *                            Used when this type is a FunctionType domain.
+   *                            May be <code>null</code>.
+   * @param attrMask            The attribute mask. 0 or INTERVAL.
+   * @throws VisADException     Couldn't create necessary VisAD object.
+   * @deprecated Use {@link #getRealType(String, Unit, Set, int)}
+   */
+  public RealType(String name, Unit u, Set set, int attrMask)
+    throws VisADException
+  {
+    super(name);
+    if (set != null && set.getDimension() != 1) {
+      throw new SetException("RealType: default set dimension != 1");
+    }
+    DefaultUnit =
+      u != null && isSet(attrMask, INTERVAL) ? u.getAbsoluteUnit() : u;
+    DefaultSet = set;
+    DefaultSetEverAccessed = false;
+    if (DefaultUnit != null && DefaultSet != null) {
+      Unit[] us = {DefaultUnit};
+      if (!Unit.canConvertArray(us, DefaultSet.getSetUnits())) {
+        throw new UnitException("RealType: default Unit must be convertable " +
+                                "with Set default Unit");
+      }
+    }
+    this.attrMask = attrMask;
+  }
+
+  /** trusted constructor for initializers */
+  protected RealType(String name, Unit u, boolean b) {
+    this(name, u, 0, b);
+  }
+
+  /** trusted constructor for initializers */
+  protected RealType(String name, Unit u, int attrMask, boolean b) {
+    super(name, b);
+    DefaultUnit =
+      u != null && isSet(attrMask, INTERVAL) ? u.getAbsoluteUnit() : u;
+    DefaultSet = null;
+    DefaultSetEverAccessed = false;
+    this.attrMask = attrMask;
+  }
+
+  /** trusted constructor for initializers */
+  protected RealType(String name, Unit u, Set s, int attrMask, boolean b)
+    throws SetException
+  {
+    super(name, b);
+    if (s != null && s.getDimension() != 1) {
+      throw new SetException("RealType: default set dimension != 1");
+    }
+    DefaultUnit =
+      u != null && isSet(attrMask, INTERVAL) ? u.getAbsoluteUnit() : u;
+    DefaultSet = s;
+    DefaultSetEverAccessed = false;
+    this.attrMask = attrMask;
+  }
+
+  /**
+   * Gets the attribute mask of this RealType.
+   * @return                    The attribute mask of this RealType.
+   */
+  public final int getAttributeMask() {
+    return attrMask;
+  }
+
+  /**
+   * Indicates whether or not this RealType refers to an interval (e.g.
+   * length difference, delta temperature).
+   * @return                    Whether or not this RealType refers to an
+   *                            interval.
+   */
+  public final boolean isInterval() {
+    return isSet(getAttributeMask(), INTERVAL);
+  }
+
+  /**
+   * Indicates if the given bits are set in an integer.
+   */
+  private static boolean isSet(int value, int mask) {
+    return (value & mask) == mask;
+  }
+
+  /** get default Unit */
+  public Unit getDefaultUnit() {
+    return DefaultUnit;
+  }
+
+  /** get default Set*/
+  public synchronized Set getDefaultSet() {
+    DefaultSetEverAccessed = true;
+    return DefaultSet;
+  }
+
+  /** set the default Set;
+      this is a violation of MathType immutability to allow a
+      a RealType to be an argument (directly or through a
+      SetType) to the constructor of its default Set;
+      this method throws an Exception if getDefaultSet has
+      previously been invoked */
+  public synchronized void setDefaultSet(Set sampling) throws VisADException {
+    if (sampling.getDimension() != 1) {
+      throw new SetException(
+           "RealType.setDefaultSet: default set dimension != 1");
+    }
+    if (DefaultSetEverAccessed) {
+      throw new TypeException("RealType: DefaultSet already accessed" +
+                               " so cannot change");
+    }
+    DefaultSet = sampling;
+
+    if (DefaultUnit != null && DefaultSet != null) {
+      Unit[] us = {DefaultUnit};
+      if (!Unit.canConvertArray(us, DefaultSet.getSetUnits())) {
+        throw new UnitException("RealType: default Unit must be convertable " +
+                                "with Set default Unit");
+      }
+    }
+  }
+
+  /** 
+   * Check the equality of type with this RealType;
+   * two RealType-s are equal if they have the same name, 
+   * convertible DefaultUnit-s, same DefaultSet and attrMask;
+   * a RealType copied from a remote Java virtual machine may have
+   * the same name but different values for other fields
+   * @param  type  object in question
+   * @return true if type is a RealType and the conditions above are met
+   */
+  public boolean equals(Object type) {
+    if (!(type instanceof RealType)) return false;
+
+    // WLH 26 Aug 2001
+    // return Name.equals(((RealType) type).Name);
+    if (!Name.equals(((RealType) type).Name)) return false;
+    if (DefaultUnit == null) {
+      if (((RealType) type).DefaultUnit != null) return false;
+    }
+    else {
+      // DRM 30 Nov 2001  - make less strict
+      //if (!DefaultUnit.equals(((RealType) type).DefaultUnit)) return false;
+      if (!Unit.canConvert(DefaultUnit, ((RealType) type).DefaultUnit)) return false;
+    }
+    if (DefaultSet == null) {
+      if (((RealType) type).DefaultSet != null) return false;
+    }
+    else {
+      if (!DefaultSet.equals(((RealType) type).DefaultSet)) return false;
+    }
+    return attrMask == ((RealType) type).attrMask;
+  }
+
+  /** any two RealType-s are equal except Name */
+  public boolean equalsExceptName(MathType type) {
+    if (type instanceof RealTupleType) {
+      try {
+        return (((RealTupleType) type).getDimension() == 1 &&
+                ((RealTupleType) type).getComponent(0) instanceof RealType);
+      }
+      catch (VisADException e) {
+        return false;
+      }
+    }
+    return (type instanceof RealType);
+  }
+
+  /*- TDR May 1998  */
+  /**
+   * Check to see if type has convertible units with this RealType.
+   * @param  type  MathType to check
+   * @return true if type is a RealType or a RealTupleType of dimension
+   *              1 AND the units are convertible with this RealType's
+   *              default Unit.
+   */
+  public boolean equalsExceptNameButUnits(MathType type) {
+    try {
+      if (type instanceof RealTupleType &&
+          ((RealTupleType) type).getDimension() == 1 &&
+          ((RealTupleType) type).getComponent(0) instanceof RealType) {
+          RealType rt = (RealType) ((RealTupleType) type).getComponent(0);
+        return Unit.canConvert( this.getDefaultUnit(),
+                                ((RealType)rt).getDefaultUnit() );
+      }
+    }
+    catch (VisADException e) {
+      return false;
+    }
+    if (!(type instanceof RealType)) {
+      return false;
+    }
+    else {
+      return Unit.canConvert( this.getDefaultUnit(),
+                              ((RealType)type).getDefaultUnit() );
+    }
+  }
+
+  /*- TDR June 1998  */
+  public MathType cloneDerivative( RealType d_partial )
+         throws VisADException
+  {
+    String newName = "d_"+this.getName()+"_"+
+                      "d_"+d_partial.getName();
+
+    RealType newType = null;
+    Unit R_unit = this.DefaultUnit;
+    Unit D_unit = d_partial.getDefaultUnit();
+    Unit u = null;
+    if ( R_unit != null && D_unit != null )
+    {
+      u = R_unit.divide( D_unit );
+    }
+    newType = getRealType(newName, u);
+
+    return newType;
+  }
+
+  /*- TDR July 1998  */
+  public MathType binary( MathType type, int op, Vector names )
+         throws VisADException
+  {
+/* WLH 10 Sept 98 */
+    if (type == null) {
+      throw new TypeException("TupleType.binary: type may not be null" );
+    }
+
+    Unit newUnit = null;
+    MathType newType = null;
+    String newName;
+    if (type instanceof RealType) {
+      RealType that = (RealType)type;
+      Unit unit = ((RealType)type).getDefaultUnit();
+      Unit thisUnit = DefaultUnit;
+      int newAttrMask = 0;
+
+      /*
+       * Determine the attributes of the RealType that will result from the
+       * operation.
+       */
+      switch (op)
+      {
+        case Data.SUBTRACT:
+        case Data.INV_SUBTRACT:
+          if (isInterval() != that.isInterval())
+              newAttrMask |= INTERVAL;
+          break;
+        case Data.ADD:
+        case Data.MAX:
+        case Data.MIN:
+          if (isInterval() && that.isInterval())
+            newAttrMask |= INTERVAL;
+          break;
+        case Data.MULTIPLY:
+        case Data.DIVIDE:
+        case Data.REMAINDER:
+        case Data.INV_DIVIDE:
+        case Data.INV_REMAINDER:
+          if (isInterval() != that.isInterval())
+            newAttrMask |= INTERVAL;
+          break;
+        case Data.POW:
+          newAttrMask = getAttributeMask();
+          break;
+        case Data.INV_POW:
+          newAttrMask = that.getAttributeMask();
+          break;
+        case Data.ATAN2:
+        case Data.INV_ATAN2:
+        case Data.ATAN2_DEGREES:
+        case Data.INV_ATAN2_DEGREES:
+        default:
+      }
+
+      /*
+       * Determine the RealType that will result from the operation.  Use the
+       * previously-determined attributes.
+       */
+      switch (op)
+      {
+        case Data.ADD:
+        case Data.SUBTRACT:
+        case Data.INV_SUBTRACT:
+        case Data.MAX:
+        case Data.MIN:
+          if (CommonUnit.promiscuous.equals(unit)) {
+            newType = this;
+          }
+          else if (CommonUnit.promiscuous.equals(thisUnit)) {
+            newType = type;
+          }
+          else {
+            if (thisUnit == null || unit == null) {
+              newUnit = null;
+              if (thisUnit == null && unit == null) {
+                newName = Name;
+              }
+              else {
+                newName = getUniqueGenericName(names, newUnit);
+              }
+            }
+            else {
+              if (!thisUnit.isConvertible(unit)) {
+                throw new UnitException();
+              }
+              newUnit = thisUnit;
+              newName = Name;
+            }
+            newType = getRealType(newName, newUnit, newAttrMask);
+            if (newType == null) {
+              /*
+               * The new RealType can't be created -- possibly because the
+               * attribute mask differs from an extant RealType.  Create a new
+               * RealType from a new name.
+               */
+              newType = getRealType(newName(newUnit), newUnit, newAttrMask);
+            }
+          }
+          break;
+
+        case Data.MULTIPLY:
+          if (CommonUnit.promiscuous.equals(unit) ||
+              CommonUnit.dimensionless.isConvertible(unit)) {
+            newType = this;
+          }
+          else if (CommonUnit.promiscuous.equals(thisUnit) ||
+              CommonUnit.dimensionless.isConvertible(thisUnit)) {
+            newType = type;
+          }
+          else {
+            newUnit = (unit != null && thisUnit != null)
+                ? thisUnit.multiply(unit)
+                : null;
+            newName = getUniqueGenericName(names, newUnit);
+            newType = getRealType(newName, newUnit, newAttrMask);
+          }
+          break;
+
+        case Data.DIVIDE:
+          if (CommonUnit.promiscuous.equals(unit) ||
+              CommonUnit.dimensionless.isConvertible(unit)) {
+            newType = this;
+          }
+          else {
+            newUnit = (unit != null && thisUnit != null)
+                ? thisUnit.divide(unit)
+                : null;
+            newName = getUniqueGenericName(names, newUnit);
+            newType = getRealType(newName, newUnit, newAttrMask);
+          }
+          break;
+
+        case Data.INV_DIVIDE:
+          if (CommonUnit.promiscuous.equals(thisUnit) ||
+              CommonUnit.dimensionless.isConvertible(thisUnit)) {
+            newType = type;
+          }
+          else {
+            newUnit = (unit != null && thisUnit != null)
+                ? unit.divide(thisUnit)
+                : null;
+            newName = getUniqueGenericName(names, newUnit);
+            newType = getRealType(newName, newUnit, newAttrMask);
+          }
+          break;
+
+        case Data.POW:
+          if (thisUnit != null && CommonUnit.dimensionless.equals(thisUnit)) {
+              newUnit = CommonUnit.dimensionless;
+          }
+          else {
+                  newUnit = CommonUnit.promiscuous;
+          }
+          newName = getUniqueGenericName(names, newUnit);
+          newType = getRealType(newName, newUnit, newAttrMask);
+          break;
+
+        case Data.INV_POW:
+          if (unit != null && unit.equals(CommonUnit.dimensionless)) {
+              newUnit = CommonUnit.dimensionless;
+          }
+          else {
+                  newUnit = CommonUnit.promiscuous;
+          }
+          newName = getUniqueGenericName(names, newUnit);
+          newType = getRealType(newName, newUnit, newAttrMask);
+          break;
+
+        case Data.ATAN2:
+        case Data.INV_ATAN2:
+          newUnit = CommonUnit.radian;
+          newName = getUniqueGenericName(names, newUnit);
+          newType = getRealType(newName, newUnit, newAttrMask);
+          break;
+
+        case Data.ATAN2_DEGREES:
+        case Data.INV_ATAN2_DEGREES:
+          newUnit = CommonUnit.degree;
+          newName = getUniqueGenericName(names, newUnit);
+          newType = getRealType(newName, newUnit, newAttrMask);
+          break;
+
+        case Data.REMAINDER:
+          newType = this;
+          break;
+
+        case Data.INV_REMAINDER:
+          newType = type;
+          break;
+
+        default:
+          throw new ArithmeticException("RealType.binary: illegal operation");
+      }
+    }
+    else if ( type instanceof TextType ) {
+      throw new TypeException("RealType.binary: types don't match");
+    }
+    else if ( type instanceof TupleType ) {
+      return type.binary( this, DataImpl.invertOp(op), names );
+    }
+    else if ( type instanceof FunctionType ) {
+      return type.binary( this, DataImpl.invertOp(op), names );
+    }
+/* WLH 10 Sept 98 - not necessary
+    else if (type instanceof RealTupleType) {
+      int n_comps = ((TupleType) type).getDimension();
+      RealType[] new_types = new RealType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        new_types[ii] = (RealType)
+          (((TupleType) type).getComponent(ii)).binary(this,
+                                       DataImpl.invertOp(op), names);
+      }
+      return new RealTupleType( new_types );
+    }
+    else if (type instanceof TupleType) {
+      int n_comps = ((TupleType) type).getDimension();
+      MathType[] new_types = new MathType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        new_types[ii] =
+          (((TupleType) type).getComponent(ii)).binary(this,
+                                       DataImpl.invertOp(op), names);
+      }
+      return new TupleType( new_types );
+    }
+    else if (type instanceof FunctionType) {
+      return new FunctionType(((FunctionType) type).getDomain(),
+        ((FunctionType) type).getRange().binary(this,
+        DataImpl.invertOp(op), names));
+    }
+    else {
+      throw new TypeException("RealType.binary: types don't match" );
+    }
+*/
+
+    return newType;
+  }
+
+  private static String newName(Unit unit) {
+    return "RealType_" + Integer.toString(++count) + "_" + 
+      (unit == null ? "nullUnit" : unit.toString());
+  }
+
+  /*- TDR July 1998 */
+  public MathType unary( int op, Vector names )
+         throws VisADException
+  {
+    MathType newType;
+    Unit newUnit;
+    String newName;
+
+    /*
+     * Determine the attributes of the RealType that will result from the
+     * operation.
+     */
+    int newAttrMask;
+    switch (op)
+    {
+      case Data.CEIL:
+      case Data.FLOOR:
+      case Data.RINT:
+      case Data.ROUND:
+      case Data.NOP:
+      case Data.ABS:
+      case Data.NEGATE:
+        newAttrMask = getAttributeMask();
+        break;
+      case Data.ACOS:
+      case Data.ASIN:
+      case Data.ATAN:
+      case Data.ACOS_DEGREES:
+      case Data.ASIN_DEGREES:
+      case Data.ATAN_DEGREES:
+      case Data.COS:
+      case Data.COS_DEGREES:
+      case Data.SIN:
+      case Data.SIN_DEGREES:
+      case Data.TAN:
+      case Data.TAN_DEGREES:
+      case Data.SQRT:
+      case Data.EXP:
+      case Data.LOG:
+      default:
+        newAttrMask = 0;                // clear all attributes
+    }
+
+    /*
+     * Determine the RealType that will result from the operation.  Use the
+     * previously-determined attributes.
+     */
+    switch (op)
+    {
+      case Data.ABS:
+      case Data.CEIL:
+      case Data.FLOOR:
+      case Data.NEGATE:
+      case Data.NOP:
+      case Data.RINT:
+      case Data.ROUND:
+        newType = this;
+        break;
+
+      case Data.ACOS:
+      case Data.ASIN:
+      case Data.ATAN:
+        newUnit = CommonUnit.radian;
+        newName = getUniqueGenericName( names, newUnit );
+        newType = getRealType(newName, newUnit, newAttrMask);
+        break;
+
+      case Data.ACOS_DEGREES:
+      case Data.ASIN_DEGREES:
+      case Data.ATAN_DEGREES:
+        newUnit = CommonUnit.degree;
+        newName = getUniqueGenericName( names, newUnit );
+        newType = getRealType(newName, newUnit, newAttrMask);
+        break;
+
+      case Data.COS:
+      case Data.COS_DEGREES:
+      case Data.SIN:
+      case Data.SIN_DEGREES:
+      case Data.TAN:
+      case Data.TAN_DEGREES:
+      case Data.EXP:
+      case Data.LOG:
+        newUnit = Unit.canConvert(CommonUnit.dimensionless, DefaultUnit)
+          ? CommonUnit.dimensionless : null;
+        newName = getUniqueGenericName( names, newUnit );
+        newType = getRealType(newName, newUnit, newAttrMask);
+        break;
+      case Data.SQRT:
+        newUnit = Unit.canConvert(CommonUnit.dimensionless, DefaultUnit)
+          ? CommonUnit.dimensionless : 
+              (DefaultUnit == null) 
+                  ? null : DefaultUnit.sqrt();
+        newName = getUniqueGenericName( names, newUnit );
+        newType = getRealType(newName, newUnit, newAttrMask);
+        break;
+
+      default:
+        throw new ArithmeticException("RealType.unary: illegal operation");
+    }
+
+    return newType;
+  }
+
+  private static String getUniqueGenericName( Vector names, String ext )
+  {
+    String name = null;
+
+    /*
+     * Ensure that the name is acceptable as a RealType name.
+     */
+    if (ext.indexOf(".") > -1)
+      ext = ext.replace('.', '_');
+    if (ext.indexOf(" ") > -1)
+      ext = ext.replace(' ', '_');
+    if (ext.indexOf("(") > -1)
+      ext = ext.replace('(', '_');
+    if (ext.indexOf(")") > -1)
+      ext = ext.replace(')', '_');
+
+    for ( int ii = 1; ; ii++ )
+    {
+      name = "Generic_"+ii +"_"+ext;
+      if ( !names.contains(name) ) {
+        names.addElement(name);
+        break;
+      }
+    }
+    return name;
+  }
+
+  private static String getUniqueGenericName(Vector names, Unit unit) {
+    return getUniqueGenericName(
+      names, unit == null ? "nullUnit" : unit.toString());
+  }
+
+  public Data missingData() throws VisADException {
+    return new Real(this);
+  }
+
+  /**
+   * Returns a RealType corresponding to a name.  If a RealType with the given
+   * name doesn't exist, then it's created (with default unit, representational
+   * set, and attribute mask) and returned; otherwise, the previously existing
+   * RealType is returned if it is compatible with the input arguments (the
+   * unit, representational set, and attribute mask are ignored in the
+   * comparison); otherwise <code>null</code> is returned.
+   *
+   * @param name                    The name for the RealType.
+   * @return                        A RealType corresponding to the input
+   *                                arguments or <code>null</code>.
+   * @throws NullPointerException   if the name is <code>null</code>.
+   */
+  public static final RealType getRealType(String name)
+  {
+    if (name == null)
+      throw new NullPointerException();
+    /*
+     * The following should catch most of the times that an instance with the
+     * given name was previously-created -- without the performance-hit of 
+     * catching an exception.
+     */
+    RealType rt = getRealTypeByName(name);
+    if (rt == null)
+    {
+      /*
+       * An instance with the given name didn't exist but might have just been
+       * created by another thread -- so we have to invoke the constructor
+       * inside a try-block.
+       */
+      try
+      {
+        rt = new RealType(name);
+      }
+      catch (VisADException e)
+      {
+        rt = getRealTypeByName(name);
+      }
+    }
+    return rt;
+  }
+
+  /**
+   * Returns a RealType corresponding to a name and unit.  If a RealType
+   * with the given name doesn't exist, then it's created (with default
+   * representational set and attribute mask) and returned; otherwise, the
+   * previously existing RealType is returned if it is compatible with the input
+   * arguments (the representational set and attribute mask are ignored in the
+   * comparison); otherwise <code>null</code> is returned.  Note that the unit
+   * of the returned RealType will be convertible with the unit argument but
+   * might not equal it.
+   *
+   * @param name                    The name for the RealType.
+   * @param u	                    The unit for the RealType.
+   * @return                        A RealType corresponding to the input
+   *                                arguments or <code>null</code>.
+   * @throws NullPointerException   if the name is <code>null</code>.
+   */
+  public static final RealType getRealType(String name, Unit u)
+  {
+    if (name == null)
+      throw new NullPointerException();
+    /*
+     * The following should catch most of the times that an instance with the
+     * given name was previously-created -- without the performance-hit of 
+     * catching an exception.
+     */
+    RealType rt = getRealTypeByName(name);
+    if (rt != null)
+    {
+      /*
+       * Ensure that the previously-created instance conforms to the input
+       * arguments.
+       */
+      if (!Unit.canConvert(u, rt.DefaultUnit)) {
+// System.out.println("getRealType " + name + " unit mismatchA " + u + " " + rt.DefaultUnit);
+        rt = null;
+      }
+    }
+    else
+    {
+      /*
+       * An instance with the given name didn't exist but might have just been
+       * created by another thread -- so we have to invoke the constructor
+       * inside a try-block.
+       */
+      try
+      {
+        rt = new RealType(name, u);
+      }
+      catch (VisADException e)
+      {
+        rt = getRealTypeByName(name);
+        if (rt != null) {
+          if (!Unit.canConvert(u, rt.DefaultUnit)) {
+// System.out.println("getRealType " + name + " unit mismatchB " + u + " " + rt.DefaultUnit);
+            rt = null;
+          }
+        }
+      }
+    }
+    return rt;
+  }
+
+  /**
+   * Returns a RealType corresponding to a name and attribute mask.  If a
+   * RealType with the given name doesn't exist, then it's created (with
+   * default unit and representational set) and returned; otherwise, the
+   * previously existing RealType is returned if it is compatible with the
+   * input arguments (the unit and representational set are ignored in the
+   * comparison); otherwise <code>null</code> is returned.  Note that the unit
+   * of the returned RealType will be convertible with the unit argument but
+   * might not equal it.
+   *
+   * @param name                    The name for the RealType.
+   * @param attrMask                The attribute mask for the RealType.
+   * @return                        A RealType corresponding to the input
+   *                                arguments or <code>null</code>.
+   * @throws NullPointerException   if the name is <code>null</code>.
+   */
+  public static RealType getRealType(String name, int attrMask)
+  {
+    if (name == null)
+      throw new NullPointerException();
+    /*
+     * The following should catch most of the times that an instance with the
+     * given name was previously-created -- without the performance-hit of 
+     * catching an exception.
+     */
+    RealType rt = getRealTypeByName(name);
+    if (rt != null)
+    {
+      /*
+       * Ensure that the previously-created instance conforms to the input
+       * arguments.
+       */
+      if (attrMask != rt.attrMask)
+        rt = null;
+    }
+    else
+    {
+      /*
+       * An instance with the given name didn't exist but might have just been
+       * created by another thread -- so we have to invoke the constructor
+       * inside a try-block.
+       */
+      try
+      {
+        rt = new RealType(name, attrMask);
+      }
+      catch (VisADException e)
+      {
+        rt = getRealTypeByName(name);
+        if (rt != null) {
+          if (attrMask != rt.attrMask) {
+            rt = null;
+          }
+        }
+      }
+    }
+    return rt;
+  }
+
+  /**
+   * Returns a RealType corresponding to a name, unit, and representational set.
+   * If a RealType with the given name doesn't exist, then it's created (with a
+   * default attribute mask) and returned; otherwise, the previously existing
+   * RealType is returned if it is compatible with the input arguments (the
+   * attribute mask is ignored in the comparison); otherwise <code>null</code>
+   * is returned.  Note that the unit of the returned RealType will be
+   * convertible with the unit argument but might not equal it.
+   *
+   * @param name                    The name for the RealType.
+   * @param u	                    The unit for the RealType.
+   * @param set                     The representational set for the RealType.
+   * @return                        A RealType corresponding to the input
+   *                                arguments or <code>null</code>.
+   * @throws NullPointerException   if the name is <code>null</code>.
+   */
+  public static RealType getRealType(String name, Unit u, Set set)
+  {
+    if (name == null)
+      throw new NullPointerException();
+    /*
+     * The following should catch most of the times that an instance with the
+     * given name was previously-created -- without the performance-hit of 
+     * catching an exception.
+     */
+    RealType rt = getRealTypeByName(name);
+    if (rt != null)
+    {
+      /*
+       * Ensure that the previously-created instance conforms to the input
+       * arguments.
+       */
+      if (!Unit.canConvert(u, rt.DefaultUnit) ||
+          (set == null ? rt.DefaultSet != null : !set.equals(rt.DefaultSet)))
+      {
+        rt = null;
+      }
+    }
+    else
+    {
+      /*
+       * An instance with the given name didn't exist but might have just been
+       * created by another thread -- so we have to invoke the constructor
+       * inside a try-block.
+       */
+      try
+      {
+        rt = new RealType(name, u, set);
+      }
+      catch (VisADException e)
+      {
+        rt = getRealTypeByName(name);
+        if (rt != null) {
+          if (!Unit.canConvert(u, rt.DefaultUnit) ||
+              (set == null ? rt.DefaultSet != null :
+                             !set.equals(rt.DefaultSet))) {
+            rt = null;
+          }
+        }
+      }
+    }
+    return rt;
+  }
+
+  /**
+   * Returns a RealType corresponding to a name, unit, and attribute mask.  If
+   * a RealType with the given name doesn't exist, then it's created (with a
+   * default representational set) and returned; otherwise, the previously
+   * existing RealType is returned if it is compatible with the input arguments
+   * (the representational set is ignored in the comparison); otherwise
+   * <code>null</code> is returned.  Note that the unit of the returned RealType
+   * will be convertible with the unit argument but might not equal it.
+   *
+   * @param name                    The name for the RealType.
+   * @param u	                    The unit for the RealType.
+   * @param attrMask                The attribute mask for the RealType.
+   * @return                        A RealType corresponding to the input
+   *                                arguments or <code>null</code>.
+   * @throws NullPointerException   if the name is <code>null</code>.
+   */
+  public static final RealType getRealType(String name, Unit u, int attrMask)
+  {
+    if (name == null)
+      throw new NullPointerException();
+    /*
+     * The following should catch most of the times that an instance with the
+     * given name was previously-created -- without the performance-hit of 
+     * catching an exception.
+     */
+    RealType rt = getRealTypeByName(name);
+    if (rt != null)
+    {
+      /*
+       * Ensure that the previously-created instance conforms to the input
+       * arguments.
+       */
+      if (!Unit.canConvert(u, rt.DefaultUnit) || rt.attrMask != attrMask)
+        rt = null;
+    }
+    else
+    {
+      /*
+       * An instance with the given name didn't exist but might have just been
+       * created by another thread -- so we have to invoke the constructor
+       * inside a try-block.
+       */
+      try
+      {
+        rt = new RealType(name, u, null, attrMask);
+      }
+      catch (VisADException e)
+      {
+        rt = getRealTypeByName(name);
+        if (rt != null) {
+          if (!Unit.canConvert(u, rt.DefaultUnit) ||
+              rt.attrMask != attrMask) {
+            rt = null;
+          }
+        }
+      }
+    }
+    return rt;
+  }
+
+  /**
+   * Returns a RealType corresponding to a name, unit, representational set,
+   * and attribute mask.  If a RealType with the given name doesn't exist, then
+   * it's created and returned; otherwise, the previously existing RealType
+   * is returned if it is compatible with the input arguments; otherwise
+   * <code>null</code> is returned.  Note that the unit of the returned RealType
+   * will be convertible with the unit argument but might not equal it.
+   *
+   * @param name                    The name for the RealType.
+   * @param u	                    The unit for the RealType.
+   * @param set                     The representational set for the RealType.
+   * @param attrMask                The attribute mask for the RealType.
+   * @return                        A RealType corresponding to the input
+   *                                arguments or <code>null</code>.
+   * @throws NullPointerException   if the name is <code>null</code>.
+   */
+  public static final RealType getRealType(String name, Unit u, Set set,
+                                           int attrMask)
+  {
+    if (name == null)
+      throw new NullPointerException();
+    /*
+     * The following should catch most of the times that an instance with the
+     * given name was previously-created -- without the performance-hit of 
+     * catching an exception.
+     */
+    RealType rt = getRealTypeByName(name);
+    if (rt != null)
+    {
+      /*
+       * Ensure that the previously-created instance conforms to the input
+       * arguments.
+       */
+      if (!Unit.canConvert(u, rt.DefaultUnit) ||
+          (set == null ? rt.DefaultSet != null : !set.equals(rt.DefaultSet)) ||
+          rt.attrMask != attrMask)
+      {
+        rt = null;
+      }
+    }
+    else
+    {
+      /*
+       * An instance with the given name didn't exist but might have just been
+       * created by another thread -- so we have to invoke the constructor
+       * inside a try-block.
+       */
+      try
+      {
+        rt = new RealType(name, u, set, attrMask);
+      }
+      catch (VisADException e)
+      {
+        rt = getRealTypeByName(name);
+        if (rt != null) {
+          if (!Unit.canConvert(u, rt.DefaultUnit) ||
+              (set == null ? rt.DefaultSet != null :
+                             !set.equals(rt.DefaultSet)) ||
+              rt.attrMask != attrMask) {
+            rt = null;
+          }
+        }
+      }
+    }
+    return rt;
+  }
+
+  /** return any RealType constructed in this JVM with name,
+      or null */
+  public static RealType getRealTypeByName(String name) {
+    ScalarType real = ScalarType.getScalarTypeByName(name);
+    if (!(real instanceof RealType)) {
+      return null;
+    }
+    return (RealType) real;
+  }
+
+  public ShadowType buildShadowType(DataDisplayLink link, ShadowType parent)
+             throws VisADException, RemoteException {
+    return link.getRenderer().makeShadowRealType(this, link, parent);
+  }
+
+/* WLH 5 Jan 2000
+  public String toString() {
+    return getName();
+  }
+*/
+
+  public String prettyString(int indent) {
+    // return toString();  WLH 5 Jan 2000
+    return getName();
+  }
+
+  public static void main( String[] args )
+         throws VisADException
+  {
+
+  //- Tests for unary   --*
+    MathType m_type;
+    RealType real_R = new RealType( "Red_Brightness", null, null );
+    RealType real_G = new RealType( "Green_Brightness", null, null );
+    RealType real_B = new RealType( "Blue_Brightness", null, null );
+    RealType[] reals = { real_R, real_G, real_B };
+
+    RealTupleType RGBtuple = new RealTupleType( reals );
+
+    m_type = RGBtuple.unary( Data.COS, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.unary( Data.COS_DEGREES, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.unary( Data.ABS, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.unary( Data.ACOS, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.unary( Data.ACOS_DEGREES, new Vector() );
+      System.out.println( m_type.toString() );
+
+    RealType real_A = new RealType( "distance", SI.meter, null );
+
+    m_type = real_A.unary( Data.EXP, new Vector() );
+      System.out.println( m_type.toString() );
+
+
+  //- Tests for binary   --*
+
+    real_A = RealType.Generic;
+ // real_A = new RealType( "temperature", SI.kelvin, null );
+    m_type = RGBtuple.binary( real_A, Data.ADD, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.binary( real_A, Data.MULTIPLY, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.binary( real_A, Data.POW, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.binary( real_A, Data.ATAN2, new Vector() );
+      System.out.println( m_type.toString() );
+
+    m_type = RGBtuple.binary( real_A, Data.ATAN2_DEGREES, new Vector() );
+      System.out.println( m_type.toString() );
+
+    // and finally, force an Exception
+    System.out.println("force a TypeException:");
+    RealType r = new RealType("a.b");
+
+  }
+
+}
+
diff --git a/visad/RealVectorType.java b/visad/RealVectorType.java
new file mode 100644
index 0000000..b844a11
--- /dev/null
+++ b/visad/RealVectorType.java
@@ -0,0 +1,165 @@
+//
+// RealVectorType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RealVectorType is the VisAD data type for vector field tuple
+   in R^n, for n>0.<P>
+*/
+public abstract class RealVectorType extends RealTupleType {
+
+  public RealVectorType(RealType[] types) throws VisADException {
+    super(types);
+  }
+
+  public RealVectorType(RealType[] types, CoordinateSystem coord_sys)
+         throws VisADException {
+    super(types, coord_sys, null);
+  }
+
+  public RealVectorType(RealType a) throws VisADException {
+    super(a);
+  }
+
+  public RealVectorType(RealType a, RealType b) throws VisADException {
+    super(a, b);
+  }
+
+  public RealVectorType(RealType a, RealType b, RealType c)
+         throws VisADException {
+    super(a, b, c);
+  }
+
+  public RealVectorType(RealType a, RealType b, RealType c, RealType d)
+         throws VisADException {
+    super(a, b, c, d);
+  }
+
+  /** transform an array of vector values from a field, based on a
+      coordinate transform of the field domain.  This may use the
+      Jacobean of the coordinate transform, but may be more complex.
+      For example, vectors in m/s would not transform for a simple
+      rescaling transform.  Or the transform may be to a moving
+      coordinate system.
+
+      out, coord_out, units_out, in, coord_in, units_in are the
+      arguments to the corresponding call to transformCoordinates;
+      loc_errors_out are the ErrorEstimates for loc from that call;
+      inloc and outloc contain the input and output values from the
+      corresponding call to transformCoordinates;
+      coord_vector and errors_in are the CoordinateSystem and ErrorEstimates
+      associated with values;
+      value are the vector values (already resampled at loc);
+      return new value array;
+      return transformed ErrorEstimates in errors_out array */
+  public abstract double[][] transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        ErrorEstimate[] errors_in,
+                        ErrorEstimate[] errors_out,
+                        double[][] inloc, double[][] outloc,
+                        double[][] value)
+         throws VisADException, RemoteException;
+
+  public float[][] transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        ErrorEstimate[] errors_in,
+                        ErrorEstimate[] errors_out,
+                        float[][] inloc, float[][] outloc,
+                        float[][] value)
+         throws VisADException, RemoteException {
+    double[][] dinloc = Set.floatToDouble(inloc);
+    double[][] doutloc = Set.floatToDouble(outloc);
+    double[][] dvalue = Set.floatToDouble(value);
+    dvalue = transformVectors(out, coord_out, units_out, loc_errors_out,
+                              in, coord_in, units_in, coord_vector,
+                              errors_in, errors_out, dinloc, doutloc,
+                              dvalue);
+    return Set.doubleToFloat(dvalue);
+  }
+
+  /** transform a single vector in a RealTuple, based on a coordinate
+      transform of the field domain.  Similar to the previous
+      definition of transformVectors. */
+  public RealTuple transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        double[][] inloc, double[][] outloc,
+                        RealTuple tuple)
+         throws VisADException, RemoteException {
+    if (!tuple.getType().equals(this)) {
+      throw new TypeException("RealVectorType.transformVectors");
+    }
+    int n = getDimension();
+    double[][] value = new double[n][1];
+    ErrorEstimate[] errors_in = new ErrorEstimate[n];
+    for (int j=0; j<n; j++) {
+      value[j][0] = ((Real) tuple.getComponent(j)).getValue();
+      errors_in[j] = ((Real) tuple.getComponent(j)).getError();
+    }
+    ErrorEstimate[] errors_out = new ErrorEstimate[n];
+    value = transformVectors(out, coord_out, units_out, loc_errors_out,
+                             in, coord_in, units_in, coord_vector,
+                             errors_in, errors_out, inloc, outloc, value);
+    double[] vals = new double[n];
+    Real[] reals = new Real[n];
+    for (int j=0; j<n; j++) {
+      reals[j] = new Real(
+        (RealType) ((Real) tuple.getComponent(j)).getType(),
+        value[j][0],
+        ((Real) tuple.getComponent(j)).getUnit(),
+        errors_out[j]);
+    }
+    return new RealTuple((RealTupleType) tuple.getType(), reals,
+                         tuple.getCoordinateSystem());
+  }
+
+  public RealTuple transformVectors(
+                        RealTupleType out, CoordinateSystem coord_out,
+                        Unit[] units_out, ErrorEstimate[] loc_errors_out,
+                        RealTupleType in, CoordinateSystem coord_in,
+                        Unit[] units_in, CoordinateSystem coord_vector,
+                        float[][] inloc, float[][] outloc, RealTuple tuple)
+         throws VisADException, RemoteException {
+    double[][] dinloc = Set.floatToDouble(inloc);
+    double[][] doutloc = Set.floatToDouble(outloc);
+    return transformVectors(out, coord_out, units_out, loc_errors_out,
+                            in, coord_in, units_in, coord_vector,
+                            dinloc, doutloc, tuple);
+  }
+
+}
+
diff --git a/visad/ReferenceActionLink.java b/visad/ReferenceActionLink.java
new file mode 100644
index 0000000..a836c39
--- /dev/null
+++ b/visad/ReferenceActionLink.java
@@ -0,0 +1,203 @@
+//
+// ReferenceActionLink.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+   ReferenceActionLink objects are used by Action objects to
+   define their connections with ThingReference objects.<P>
+*/
+public class ReferenceActionLink {
+
+  ThingReference ref;
+  ActionImpl local_action;
+  Action action;  // may be remote or local
+
+  /** this id is unique among ReferenceActionLink attached to Action */
+  private long id;
+
+  /** name of the associated thing */
+  private String name;
+
+  /** set by incTick */
+  private long NewTick;
+  /** value of NewTick at last setTicks() call */
+  private long OldTick;
+  /** set by setTicks if OldTick < NewTick; cleared by resetTicks */
+  private boolean tickFlag;
+
+  /** Ball describes state of protocol between this ReferenceActionLink
+      and ThingReference ref;
+      false when this is waiting for a ThingChangedEvent;
+      true when ref is waiting for an acknowledgement */
+  private boolean Ball;
+
+  public ReferenceActionLink(ThingReference r, ActionImpl local_a, Action a,
+                             long jd)
+    throws RemoteException, VisADException {
+    if (r == null || a == null) {
+      throw new ReferenceException("ReferenceActionLink: ThingReference and " +
+                                   "Action cannot be null");
+    }
+    ref = r;
+    local_action = local_a;
+    action = a;
+    Ball = true;
+    id = jd;
+
+    name = ref.getName();
+
+    NewTick = ref.getTick();
+    OldTick = NewTick - 1;
+  }
+
+  long getId() {
+    return id;
+  }
+
+  public ThingReference getThingReference() {
+    return ref;
+  }
+
+  public ActionImpl getLocalAction() {
+    return local_action;
+  }
+
+  public Action getAction() {
+    return action;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  /** set value of NewTick; presumably increases value */
+  synchronized void incTick(long t) {
+    NewTick = t;
+/*
+if (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)) {
+  try {
+    DisplayImpl.printStack("incTick " +
+                           getThingReference().getName());
+    System.out.println("  NewTick = " + NewTick + " OldTick = " + OldTick);
+  } catch (VisADException e) {} catch (RemoteException e) {}
+}
+*/
+  }
+
+  /** sync consumer's tick count with producer's tick count */
+  public synchronized void setTicks() {
+    tickFlag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+    OldTick = NewTick;
+/*
+if (tickFlag) {
+  try {
+    System.out.println("setTicks " + getThingReference().getName());
+  } catch (VisADException e) {} catch (RemoteException e) {}
+}
+*/
+  }
+
+/*
+  public void printTicks(String s) {
+System.out.println(s + ":  tickFlag = " + tickFlag + "  OldTick = " + OldTick +
+                  "  NewTick = " + NewTick);
+  }
+*/
+
+  /** returns true if there is an action pending */
+  public synchronized boolean peekTicks() {
+    return (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+  }
+
+  /** check whether this link requests Action to be applied */
+  public synchronized boolean checkTicks() {
+    return tickFlag;
+  }
+
+  /** clear internal state */
+  synchronized void resetTicks() {
+/*
+try {
+  System.out.println("resetTicks " + getThingReference().getName());
+} catch (VisADException e) {} catch (RemoteException e) {}
+*/
+    tickFlag = false;
+  }
+
+  /** return any waiting event */
+  ThingChangedEvent getThingChangedEvent()
+        throws RemoteException, VisADException
+  {
+    ThingChangedEvent event = null;
+
+    // if the reference has an event waiting...
+    if (Ball) {
+      // remember that we picked up the event
+      Ball = false;
+      // get the event
+      event = ref.acknowledgeThingChanged(action);
+/*
+System.out.println("ReferenceActionLink.getThingChangedEvent Ball = false " +
+                   "event non null = " + (event != null));
+*/
+    }
+    return event;
+  }
+
+  /** return any waiting event */
+  ThingChangedEvent peekThingChangedEvent()
+        throws RemoteException, VisADException
+  {
+    ThingChangedEvent event = null;
+
+    // if the reference has an event waiting...
+    if (Ball) {
+      // get the event
+      event = ref.peekThingChanged(action);
+      if (event != null) NewTick = event.getTick(); 
+    }
+    return event;
+  }
+
+  void acknowledgeThingChangedEvent(long actionTick) {
+    NewTick = actionTick;
+    Ball = true;
+/*
+if (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)) {
+  try {
+    DisplayImpl.printStack("acknowledgeThingChangedEvent " +
+                           getThingReference().getName());
+    System.out.println("  NewTick = " + NewTick + " OldTick = " + OldTick);
+  } catch (VisADException e) {} catch (RemoteException e) {}
+}
+*/
+  }
+
+}
+
diff --git a/visad/ReferenceException.java b/visad/ReferenceException.java
new file mode 100644
index 0000000..4f9037c
--- /dev/null
+++ b/visad/ReferenceException.java
@@ -0,0 +1,38 @@
+//
+// ReferenceException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   ReferenceException is an exception for an error with a VisAD DataReference.<P>
+*/
+public class ReferenceException extends VisADException {
+
+  public ReferenceException() { super(); }
+  public ReferenceException(String s) { super(s); }
+
+}
+
diff --git a/visad/RemoteAction.java b/visad/RemoteAction.java
new file mode 100644
index 0000000..1442c04
--- /dev/null
+++ b/visad/RemoteAction.java
@@ -0,0 +1,37 @@
+//
+// RemoteAction.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteAction is the interface for Remote Action-s.<P>
+*/
+public interface RemoteAction extends Remote, Action {
+
+}
+
diff --git a/visad/RemoteActionImpl.java b/visad/RemoteActionImpl.java
new file mode 100644
index 0000000..21c9b91
--- /dev/null
+++ b/visad/RemoteActionImpl.java
@@ -0,0 +1,109 @@
+//
+// RemoteActionImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.rmi.server.UnicastRemoteObject;
+
+/**
+   RemoteActionImpl is the VisAD remote adapter for ActionImpl.<P>
+*/
+public abstract class RemoteActionImpl extends UnicastRemoteObject
+       implements RemoteAction {
+
+  final transient ActionImpl AdaptedAction;
+
+  RemoteActionImpl(ActionImpl a) throws RemoteException {
+    AdaptedAction = a;
+  }
+
+  // WLH 4 Dec 98
+  // public void thingChanged(ThingChangedEvent e)
+  public boolean thingChanged(ThingChangedEvent e)
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteActionImpl.thingChanged: " +
+                                     "AdaptedAction is null");
+    }
+    // WLH 4 Dec 98
+    // AdaptedAction.thingChanged(e);
+    return AdaptedAction.thingChanged(e);
+  }
+
+  /** create link to ThingReference;
+      must be RemoteThingReference */
+  public void addReference(ThingReference ref)
+         throws VisADException, RemoteException {
+    if (!(ref instanceof RemoteThingReference)) {
+      throw new RemoteVisADException("RemoteActionImpl.addReference: requires " +
+                                     "RemoteThingReference");
+    }
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteActionImpl.addReference: " +
+                                     "AdaptedAction is null");
+    }
+    // WLH - will 'this' be passed to RemoteThingReference ref as a RemoteAction?
+    AdaptedAction.adaptedAddReference((RemoteThingReference) ref,
+                                      (RemoteAction) this);
+  }
+
+  /** delete link to a ThingReference
+      must be RemoteThingReference */
+  public void removeReference(ThingReference ref)
+         throws VisADException, RemoteException {
+    if (!(ref instanceof RemoteThingReference)) {
+      throw new RemoteVisADException("RemoteActionImpl.removeReference: requires " +
+                                     "RemoteThingReference");
+    }
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteActionImpl.removeReference: " +
+                                     "AdaptedAction is null");
+    }
+    AdaptedAction.adaptedRemoveReference((RemoteThingReference) ref);
+  }
+
+  /** delete all links to ThingReferences */
+  public void removeAllReferences()
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteActionImpl.removeAllReferences: " +
+                                     "AdaptedAction is null");
+    }
+    AdaptedAction.removeAllReferences();
+  }
+
+  /** return name of this Action */
+  public String getName() throws VisADException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteActionImpl.getName: " +
+                                     "AdaptedAction is null");
+    }
+    return AdaptedAction.getName();
+  }
+
+}
+
diff --git a/visad/RemoteCell.java b/visad/RemoteCell.java
new file mode 100644
index 0000000..4b443fc
--- /dev/null
+++ b/visad/RemoteCell.java
@@ -0,0 +1,37 @@
+//
+// RemoteCell.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteCell is the interface for Remote Cell-s.<P>
+*/
+public interface RemoteCell extends Remote, Cell {
+
+}
+
diff --git a/visad/RemoteCellImpl.java b/visad/RemoteCellImpl.java
new file mode 100644
index 0000000..4ca8be7
--- /dev/null
+++ b/visad/RemoteCellImpl.java
@@ -0,0 +1,85 @@
+//
+// RemoteCellImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteCellImpl is the VisAD class for remote access to
+   Cell-s.<P>
+*/
+public class RemoteCellImpl extends RemoteActionImpl
+       implements RemoteCell {
+  // and RemoteActionImpl extends UnicastRemoteObject
+
+  public RemoteCellImpl(CellImpl d) throws RemoteException {
+    super(d);
+  }
+
+  /**
+   * set a non-triggering link to a DataReference; this is
+   * used to give the Cell access to Data without triggering
+   * the Cell's doAction whenever the Data changes;
+   * these 'other' DataReferences are identified by their
+   * integer index
+   * @param index - identifier of DataReference
+   * @param ref - DataReference to be linked
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  public void setOtherReference(int index, DataReference ref)
+         throws VisADException, RemoteException {
+    if (!(ref instanceof RemoteDataReference)) {
+      throw new RemoteVisADException("RemoteCellImpl.setOutputReference: " +
+                            "requires RemoteDataReferenceImpl");
+    }
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteCellImpl.setOutputReference: " +
+                                     "AdaptedAction is null");
+    }
+    ((CellImpl) AdaptedAction).
+      adaptedSetOtherReference(index, (RemoteDataReference) ref);
+  }
+
+  /**
+   * @return the non-triggering link to a DataReference
+   * identified by index
+   * @param index - identifier of DataReference to return
+   * @throws VisADException - a VisAD error occurred
+   * @throws RemoteException - an RMI error occurred
+   */
+  public DataReference getOtherReference(int index)
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteCellImpl.getOutputReference: " +
+                                     "AdaptedAction is null");
+    }
+    return ((CellImpl) AdaptedAction).getOtherReference(index);
+  }
+
+}
+
diff --git a/visad/RemoteData.java b/visad/RemoteData.java
new file mode 100644
index 0000000..16b95d1
--- /dev/null
+++ b/visad/RemoteData.java
@@ -0,0 +1,37 @@
+//
+// RemoteData.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteData is the interface for Remote VisAD data objects.<P>
+*/
+public interface RemoteData extends Remote, Data, RemoteThing {
+
+}
+
diff --git a/visad/RemoteDataImpl.java b/visad/RemoteDataImpl.java
new file mode 100644
index 0000000..530902e
--- /dev/null
+++ b/visad/RemoteDataImpl.java
@@ -0,0 +1,1242 @@
+//
+// RemoteDataImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+   RemoteDataImpl is the VisAD remote adapter for DataImpl.<P>
+   Adapts methods from DataImpl, but not:
+     equals, toString, hashCode or clone.<P>
+*/
+public class RemoteDataImpl extends RemoteThingImpl
+       implements RemoteData {
+
+  /** 'this' is the Remote adaptor for AdaptedData (which is local);
+      AdaptedData is transient because UnicastRemoteObject is
+      Serializable, but a copy of 'this' on another JVM will not
+      be local to AdaptedData and cannot adapt it;
+      the methods of RemoteDataImpl test for null AdaptedData */
+  final transient DataImpl AdaptedData;
+
+  /**
+   * construct a RemoteDataImpl adapting local data
+   * @param data adapted DataImpl
+   * @throws RemoteException an RMI error occurred
+   */
+  public RemoteDataImpl(DataImpl data) throws RemoteException {
+    super(data);
+    AdaptedData = data;
+  }
+
+  // methods adapted from Data;
+  //   do not adapt equals, toString, hashCode or clone
+
+  /**
+   * @return a local copy (AdaptedData, which is Serializable)
+   */
+  public DataImpl local() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.local " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData;
+  }
+
+  /**
+   * @return MathType of this Data
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public MathType getType() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.getType " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.getType();
+  }
+
+  /**
+   * @return flag indicating whether this Data has a missing value
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public boolean isMissing() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.isMissing " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.isMissing();
+  }
+
+  /**
+   * Pointwise binary operation between this (AdaptedData) and data.
+   * Applies to Reals, Tuples (recursively to components), and to
+   * Field ranges (Field domains implicitly resampled if necessary).
+   * Does not apply to Field domains or Sets (regarded as domains
+   * of Fields wthout ranges). Data.ADD is only op defined for
+   * Text, interpreted as concatenate. MathTypes of this and data
+   * must match, or one may match the range of the other if it is
+   * a FunctionType.
+   * @param data  other Data operand for binary operation
+   * @param op  may be Data.ADD, Data.SUBTRACT, etc; these include all
+   *             binary operations defined for Java primitive data types
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, which takes the MathType of this unless the default
+   *         Units of that MathType conflict with Units of the result,
+   *         in which case a generic MathType with appropriate Units is
+   *         constructed
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.binary " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.binary(data, op, sampling_mode, error_mode);
+  }
+
+  /**
+   * Pointwise binary operation between this (AdaptedData) and data.
+   * Applies to Reals, Tuples (recursively to components), and to
+   * Field ranges (Field domains implicitly resampled if necessary).
+   * Does not apply to Field domains or Sets (regarded as domains
+   * of Fields wthout ranges). Data.ADD is only op defined for
+   * Text, interpreted as concatenate. MathTypes of this and data
+   * must match, or one may match the range of the other if it is
+   * a FunctionType.
+   * @param data  other Data operand for binary operation
+   * @param op  may be Data.ADD, Data.SUBTRACT, etc; these include all
+   *             binary operations defined for Java primitive data types
+   * @param new_type  MathType of the result
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, with MathType = new_type
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data binary(Data data, int op, MathType new_type,
+                     int sampling_mode, int error_mode )
+              throws VisADException, RemoteException {
+    if (AdaptedData == null ) {
+      throw new RemoteVisADException("RemoteDataImpl.binary " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.binary(data, op, new_type, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to add data to this, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data add(Data data) throws VisADException, RemoteException {
+    return binary(data, ADD, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to subtract data from this, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data subtract(Data data) throws VisADException, RemoteException {
+    return binary(data, SUBTRACT, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to multiply this by data, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data multiply(Data data) throws VisADException, RemoteException {
+    return binary(data, MULTIPLY, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to divide this by data, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data divide(Data data) throws VisADException, RemoteException {
+    return binary(data, DIVIDE, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to raise this to data power, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data pow(Data data) throws VisADException, RemoteException {
+    return binary(data, POW, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the max of this and data, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data max(Data data) throws VisADException, RemoteException {
+    return binary(data, MAX, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the min of this and data, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data min(Data data) throws VisADException, RemoteException {
+    return binary(data, MIN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing radian Units, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2(Data data) throws VisADException, RemoteException {
+    return binary(data, ATAN2, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing degree Units, using default modes
+   * for sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2Degrees(Data data) throws VisADException, RemoteException {
+    return binary(data, ATAN2_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to take the remainder of this divided by
+   * data, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation (Data.NO_ERRORS)
+   * @param data  other Data operand for binary operation
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data remainder(Data data) throws VisADException, RemoteException {
+    return binary(data, REMAINDER, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call binary() to add data to this
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data add(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, ADD, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to subtract data from this
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data subtract(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, SUBTRACT, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to multiply this by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data multiply(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, MULTIPLY, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to divide this by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data divide(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, DIVIDE, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to raise this to data power
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data pow(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, POW, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the max of this and data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data max(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, MAX, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the min of this and data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data min(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, MIN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing radian Units
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, ATAN2, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the atan of this by data
+   * producing degree Units
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan2Degrees(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, ATAN2_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call binary() to take the remainder of this divided by data
+   * @param data  other Data operand for binary operation
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data remainder(Data data, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return binary(data, REMAINDER, sampling_mode, error_mode);
+  }
+
+  /**
+   * Pointwise unary operation applied to this (AdaptedData). Applies
+   * to Reals, Tuples (recursively to components), and to Field
+   * ranges (Field domains implicitly resampled if necessary).
+   * Does not apply to Field domains, Sets (regarded as domains
+   * of Fields wthout ranges) or Text.
+   * @param op  may be Data.ABS, Data.ACOS, etc; these include all
+   *             unary operations defined for Java primitive data types
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation, which takes the MathType of this unless
+   *         the default Units of that MathType conflict with Units of
+   *         the result, in which case a generic MathType with appropriate
+   *         Units is constructed
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data unary(int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.unary " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.unary(op, sampling_mode, error_mode);
+  }
+
+  /**
+   * Pointwise unary operation applied to this (AdaptedData). Applies
+   * to Reals, Tuples (recursively to components), and to Field
+   * ranges (Field domains implicitly resampled if necessary).
+   * Does not apply to Field domains, Sets (regarded as domains
+   * of Fields wthout ranges) or Text.
+   * @param op  may be Data.ABS, Data.ACOS, etc; these include all
+   *             unary operations defined for Java primitive data types
+   * @param new_type  MathType of the result
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result, with MathType = new_type
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data unary(int op, MathType new_type,
+                    int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.unary " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.unary(op, new_type, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to clone this except with a new MathType
+   * @param new_type  MathType of returned Data object
+   * @return clone of this Data object except with new MathType
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data changeMathType(MathType new_type)
+         throws VisADException, RemoteException {
+    return unary(NOP, new_type, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the absolute value of this, using
+   * default modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data abs() throws VisADException, RemoteException {
+    return unary(ABS, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acos() throws VisADException, RemoteException {
+    return unary(ACOS, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acosDegrees() throws VisADException, RemoteException {
+    return unary(ACOS_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asin() throws VisADException, RemoteException {
+    return unary(ASIN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asinDegrees() throws VisADException, RemoteException {
+    return unary(ASIN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * radian Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan() throws VisADException, RemoteException {
+    return unary(ATAN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * degree Units, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atanDegrees() throws VisADException, RemoteException {
+    return unary(ATAN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the ceiling of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data ceil() throws VisADException, RemoteException {
+    return unary(CEIL, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming radian
+   * Units unless this actual Units are degrees,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cos() throws VisADException, RemoteException {
+    return unary(COS, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cosDegrees() throws VisADException, RemoteException {
+    return unary(COS_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the exponent of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data exp() throws VisADException, RemoteException {
+    return unary(EXP, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the floor of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data floor() throws VisADException, RemoteException {
+    return unary(FLOOR, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the log of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and
+   * error estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data log() throws VisADException, RemoteException {
+    return unary(LOG, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the rint (essentially round)
+   * of this, using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data rint() throws VisADException, RemoteException {
+    return unary(RINT, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the round of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error
+   * estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data round() throws VisADException, RemoteException {
+    return unary(ROUND, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming radian
+   * Units unless this actual Units are degrees,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sin() throws VisADException, RemoteException {
+    return unary(SIN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sinDegrees() throws VisADException, RemoteException {
+    return unary(SIN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the square root of this, using default
+   * modes for sampling (Data.NEAREST_NEIGHBOR) and error
+   * estimation (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sqrt() throws VisADException, RemoteException {
+    return unary(SQRT, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming radian
+   * Units unless this actual Units are degrees,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tan() throws VisADException, RemoteException {
+    return unary(TAN, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming degree
+   * Units unless this actual Units are radians,
+   * using default modes for sampling
+   * (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tanDegrees() throws VisADException, RemoteException {
+    return unary(TAN_DEGREES, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to negate this, using default modes for
+   * sampling (Data.NEAREST_NEIGHBOR) and error estimation
+   * (Data.NO_ERRORS)
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data negate() throws VisADException, RemoteException {
+    return unary(NEGATE, NEAREST_NEIGHBOR, NO_ERRORS);
+  }
+
+  /**
+   * call unary() to take the absolute value of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data abs(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ABS, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acos(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ACOS, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arccos of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data acosDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ACOS_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asin(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ASIN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arcsin of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data asinDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ASIN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * radian Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atan(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ATAN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the arctan of this producing
+   * degree Units
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data atanDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ATAN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the ceiling of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data ceil(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(CEIL, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cos(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(COS, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the cos of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data cosDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(COS_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the exponent of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data exp(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(EXP, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the floor of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data floor(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(FLOOR, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the log of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data log(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(LOG, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the rint (essentially round)
+   * of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data rint(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(RINT, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the round of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data round(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(ROUND, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sin(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(SIN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the sin of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sinDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(SIN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the square root of this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data sqrt(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(SQRT, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming radian
+   * Units unless this actual Units are degrees
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tan(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(TAN, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to take the tan of this assuming degree
+   * Units unless this actual Units are radians
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data tanDegrees(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(TAN_DEGREES, sampling_mode, error_mode);
+  }
+
+  /**
+   * call unary() to negate this
+   * @param sampling_mode  may be Data.NEAREST_NEIGHBOR or
+   *                        Data.WEIGHTED_AVERAGE
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return result of operation
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public Data negate(int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return unary(NEGATE, sampling_mode, error_mode);
+  }
+
+  /**
+   * compute ranges of values in this of given RealTypes, using
+   * a dummy DisplayImplJ2D
+   * @param reals array of RealTypes whose value ranges to compute
+   * @return double[reals.length][2] giving the low and high value
+   *         in this for each RealType in reals
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.computeRanges " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.computeRanges(reals);
+  }
+
+  /**
+   * Compute ranges of values for each of 'n' RealTypes in
+   * DisplayImpl.RealTypeVector. Called from DataRenderer
+   * with n = DisplayImpl.getScalarCount().
+   * @param type ShadowType generated for MathType of this
+   * @param n number of RealTypes in DisplayImpl.RealTypeVector
+   * @return DataShadow instance containing double[][] array
+   *         of RealType ranges, and an animation sampling Set
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.computeRanges " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.computeRanges(type, n);
+  }
+
+  /**
+   * Recursive version of computeRanges(), called down through
+   * Data object tree.
+   * @param type ShadowType generated for MathType of this
+   * @param shadow DataShadow instance whose contained double[][]
+   *               array and animation sampling Set are modified
+   *               according to RealType values in this, and used
+   *               as return value
+   * @return DataShadow instance containing double[][] array
+   *         of RealType ranges, and an animation sampling Set
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.computeRanges " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.computeRanges(type, shadow);
+  }
+
+  /**
+   * return a clone of this, except with ErrorEstimates
+   * combined with values in error, according to error_mode
+   * @param error
+   * @param error_mode  may be Data.INDEPENDENT, Data.DEPENDENT or
+   *                     Data.NO_ERRORS;
+   * @return clone of this, except with ErrorEstimates set
+   *         according to values in error
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.adjustSamplingError " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.adjustSamplingError(error, error_mode);
+  }
+
+  /**
+   * @return a longer String than returned by toString()
+   */
+  public String longString() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.longString " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.longString();
+  }
+
+  /**
+   * @param pre String added to start of each line
+   * @return a longer String than returned by toString(),
+   *         indented by pre (a string of blanks)
+   */
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteDataImpl.longString " +
+                                     "AdaptedData is null");
+    }
+    return AdaptedData.longString(pre);
+  }
+
+  /**
+   * A VisAD adaptation of clone that works for local or remote Data.
+   * Catches CloneNotSupportedException and throws message in a
+   * RuntimeException.
+   * @return for DataImpl return clone(), and for RemoteDataImpl
+   *         return clone() inherited from UnicastRemoteObject
+   */
+  public Object dataClone() throws RemoteException {
+    try {
+      return clone();
+    }
+    catch (CloneNotSupportedException e) {
+      throw new VisADError("RemoteDataImpl.dataClone: " +
+                           "CloneNotSupportedException occurred");
+    }
+  }
+
+}
+
diff --git a/visad/RemoteDataReference.java b/visad/RemoteDataReference.java
new file mode 100644
index 0000000..dbda384
--- /dev/null
+++ b/visad/RemoteDataReference.java
@@ -0,0 +1,38 @@
+//
+// RemoteDataReference.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteDataReference is the interface for Remote DataReference-s.<P>
+*/
+public interface RemoteDataReference
+       extends Remote, DataReference, RemoteThingReference {
+
+}
+
diff --git a/visad/RemoteDataReferenceImpl.java b/visad/RemoteDataReferenceImpl.java
new file mode 100644
index 0000000..cbfaca0
--- /dev/null
+++ b/visad/RemoteDataReferenceImpl.java
@@ -0,0 +1,119 @@
+//
+// RemoteDataReferenceImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteDataReferenceImpl is VisAD remote adapter for DataReferenceImpl.<P>
+*/
+public class RemoteDataReferenceImpl extends RemoteThingReferenceImpl
+       implements RemoteDataReference {
+
+  /**
+   * construct a RemoteDataReferenceImpl adapting the given
+   * DataReferenceImpl
+   * @param ref adpted DataReferenceImpl
+   * @throws RemoteException an RMI error occurred
+   */
+  public RemoteDataReferenceImpl(DataReferenceImpl ref)
+         throws RemoteException {
+    super(ref);
+  }
+
+  /**
+   * set this RemoteDataReferenceImpl to refer to given Data
+   * @param d Data to be set (must be RemoteDataImpl)
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public synchronized void setData(Data d)
+         throws VisADException, RemoteException {
+    if (d == null) {
+      throw new ReferenceException("RemoteDataReferenceImpl: data " +
+                                   "cannot be null");
+    }
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteDataReferenceImpl.setData: " +
+                                     "AdaptedThingReference is null");
+    }
+    if (d instanceof DataImpl) {
+      // allow Data object passed by copy from remote JVM
+      ((DataReferenceImpl) AdaptedThingReference).setData(d);
+    }
+    else {
+      ((DataReferenceImpl) AdaptedThingReference).adaptedSetData((RemoteData) d,
+                                          (RemoteDataReference) this);
+    }
+  }
+
+  /**
+   * return referenced Data object, but if Data is a FieldImpl
+   * return a RemoteFieldImpl referencing Data to avoid copying
+   * entire FieldImpl between JVMs
+   * @return referenced Data object, or null if none
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public Data getData() throws VisADException, RemoteException {
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteDataReferenceImpl.getData: " +
+                                     "AdaptedThingReference is null");
+    }
+    Data data = ((DataReferenceImpl) AdaptedThingReference).getData();
+    if (data instanceof FieldImpl) {
+      // decide here whether to return copy of remote reference
+      boolean return_copy = false;
+      if (return_copy) {
+        return data;
+      }
+      else {
+        return new RemoteFieldImpl((FieldImpl) data);
+      }
+    }
+    else {
+      return data;
+    }
+  }
+
+  /**
+   * this is more efficient than getData().getType() for
+   * RemoteDataReferences
+   * @return the MathType of referenced Data object, or null if none;
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  public MathType getType() throws VisADException, RemoteException {
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteDataReferenceImpl.getType: " +
+                                     "AdaptedThingReference is null");
+    }
+    return ((DataReferenceImpl) AdaptedThingReference).getData().getType();
+  }
+
+}
+
diff --git a/visad/RemoteDisplay.java b/visad/RemoteDisplay.java
new file mode 100644
index 0000000..7110f7b
--- /dev/null
+++ b/visad/RemoteDisplay.java
@@ -0,0 +1,64 @@
+//
+// RemoteDisplay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.event.MouseEvent;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+import visad.collab.DisplaySync;
+import visad.collab.RemoteDisplayMonitor;
+import visad.collab.RemoteDisplaySync;
+
+/**
+   RemoteDisplay is the interface for Remote Display-s.<P>
+*/
+public interface RemoteDisplay extends Remote, Display {
+  String getName() throws VisADException, RemoteException;
+  String getDisplayClassName() throws RemoteException;
+  int getDisplayAPI() throws VisADException, RemoteException;
+  String getDisplayRendererClassName() throws RemoteException;
+  Vector getMapVector() throws VisADException, RemoteException;
+  Vector getConstantMapVector()
+	throws VisADException, RemoteException;
+  GraphicsModeControl getGraphicsModeControl()
+	throws VisADException, RemoteException;
+  Vector getReferenceLinks()
+	throws VisADException, RemoteException;
+  RemoteDisplayMonitor getRemoteDisplayMonitor()
+        throws RemoteException;
+  DisplaySync getDisplaySync()
+        throws RemoteException;
+  RemoteDisplaySync getRemoteDisplaySync()
+        throws RemoteException;
+  void sendMouseEvent(MouseEvent e)
+        throws VisADException, RemoteException;
+
+}
diff --git a/visad/RemoteDisplayImpl.java b/visad/RemoteDisplayImpl.java
new file mode 100644
index 0000000..69111bf
--- /dev/null
+++ b/visad/RemoteDisplayImpl.java
@@ -0,0 +1,419 @@
+//
+// RemoteDisplayImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Component;
+import java.awt.event.MouseEvent;
+import java.util.*;
+import java.rmi.*;
+
+import visad.collab.DisplayMonitor;
+import visad.collab.DisplayMonitorImpl;
+import visad.collab.DisplaySync;
+import visad.collab.DisplaySyncImpl;
+import visad.collab.RemoteDisplayMonitor;
+import visad.collab.RemoteDisplayMonitorImpl;
+import visad.collab.RemoteDisplaySync;
+import visad.collab.RemoteDisplaySyncImpl;
+
+/**
+   RemoteDisplayImpl is the VisAD class for remote access to
+   Display-s.<P>
+*/
+public class RemoteDisplayImpl extends RemoteActionImpl
+       implements RemoteDisplay {
+  // and RemoteActionImpl extends UnicastRemoteObject
+
+  public RemoteDisplayImpl(DisplayImpl d) throws RemoteException {
+    super(d);
+  }
+
+
+  // CTR - begin code for slaved displays
+
+  /** links a slave display to this display */
+  public void addSlave(RemoteSlaveDisplay display)
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new VisADException("RemoteDisplayImpl.addSlave(): " +
+                               "AdaptedAction is null");
+    }
+    if (!(AdaptedAction instanceof DisplayImpl)) {
+      throw new VisADException("RemoteDisplayImpl.addSlave(): " +
+                               "AdaptedAction must be DisplayImpl");
+    }
+    DisplayImpl d = (DisplayImpl) AdaptedAction;
+    ((DisplayImpl) AdaptedAction).addSlave(display);
+  }
+
+  /** removes a link between a slave display and this display */
+  public void removeSlave(RemoteSlaveDisplay display)
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new VisADException("RemoteDisplayImpl.removeSlave(): " +
+                               "AdaptedAction is null");
+    }
+    if (!(AdaptedAction instanceof DisplayImpl)) {
+      throw new VisADException("RemoteDisplayImpl.removeSlave(): " +
+                               "AdaptedAction must be DisplayImpl");
+    }
+    DisplayImpl d = (DisplayImpl) AdaptedAction;
+    d.removeSlave(display);
+  }
+
+  /** removes all links between slave displays and this display */
+  public void removeAllSlaves() throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new VisADException("RemoteDisplayImpl.removeAllSlaves(): " +
+                               "AdaptedAction is null");
+    }
+    if (!(AdaptedAction instanceof DisplayImpl)) {
+      throw new VisADException("RemoteDisplayImpl.removeAllSlaves(): " +
+                               "AdaptedAction must be DisplayImpl");
+    }
+    DisplayImpl d = (DisplayImpl) AdaptedAction;
+    d.removeAllSlaves();
+  }
+
+  /** whether there are any slave displays linked to this display */
+  public boolean hasSlaves() throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new VisADException("RemoteDisplayImpl.hasSlaves(): " +
+                               "AdaptedAction is null");
+    }
+    if (!(AdaptedAction instanceof DisplayImpl)) {
+      throw new VisADException("RemoteDisplayImpl.removeAllSlaves(): " +
+                               "AdaptedAction must be DisplayImpl");
+    }
+    DisplayImpl d = (DisplayImpl) AdaptedAction;
+    return d.hasSlaves();
+  }
+
+  /** sends a mouse event to this remote display's associated display */
+  public void sendMouseEvent(MouseEvent e)
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new VisADException("RemoteDisplayImpl.sendMouseEvent(): " +
+                               "AdaptedAction is null");
+    }
+    if (!(AdaptedAction instanceof DisplayImpl)) {
+      throw new VisADException("RemoteDisplayImpl.sendMouseEvent(): " +
+                               "AdaptedAction must be DisplayImpl");
+    }
+    DisplayImpl d = (DisplayImpl) AdaptedAction;
+    MouseBehavior mb = d.getMouseBehavior();
+    if (mb == null) {
+      throw new VisADException("RemoteDisplayImpl.sendMouseEvent(): " +
+                               "MouseBehavior is null");
+    }
+    MouseHelper mh = mb.getMouseHelper();
+    if (mh == null) {
+      throw new VisADException("RemoteDisplayImpl.sendMouseEvent(): " +
+                               "MouseHelper is null");
+    }
+
+    // tweak MouseEvent to have proper associated Component
+    Component c = d.getComponent();
+    int id = e.getID();
+    long when = e.getWhen();
+    int mods = e.getModifiers();
+    int x = e.getX();
+    int y = e.getY();
+    int clicks = e.getClickCount();
+    boolean popup = e.isPopupTrigger();
+    MouseEvent ne = new MouseEvent(c, id, when, mods, x, y, clicks, popup);
+
+    // send mouse event with remote source flag set
+    mh.processEvent(ne, VisADEvent.UNKNOWN_REMOTE_SOURCE);
+  }
+
+  // CTR - end code for slaved displays
+
+
+  /** link ref to this Display; this method may only be invoked
+      after all links to ScalarMaps have been made */
+  public void addReference(ThingReference ref)
+         throws VisADException, RemoteException {
+    if (!(ref instanceof DataReference)) {
+      throw new ReferenceException("RemoteDisplayImpl.addReference: ref " +
+                                   "must be DataReference");
+    }
+    addReference((DataReference) ref, null);
+  }
+
+  /** link ref to this Display; must be RemoteDataReference; this
+      method may only be invoked after all links to ScalarMaps have
+      been made; the ConstantMap array applies only to rendering ref */
+  public void addReference(DataReference ref,
+         ConstantMap[] constant_maps) throws VisADException, RemoteException {
+    if (!(ref instanceof RemoteDataReference)) {
+      throw new RemoteVisADException("RemoteDisplayImpl.addReference: requires " +
+                                     "RemoteDataReference");
+    }
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteDisplayImpl.addReference: " +
+                                     "AdaptedAction is null");
+    }
+    ((DisplayImpl) AdaptedAction).adaptedAddReference(
+                     (RemoteDataReference) ref, (RemoteDisplay) this,
+                     constant_maps);
+  }
+
+  /** link ref to this Display using the non-default renderer;
+      refs may be a mix of RemoteDataReference & DataReferenceImpl;
+      cannot be called through RemoteDisplay interface, since
+      renderer implements neither Remote nor Serializable;
+      must be called locally;
+      this method may only be invoked after all links to ScalarMaps
+      have been made;
+      this is a method of DisplayImpl and RemoteDisplayImpl rather
+      than Display - see Section 6.1 of the Developer's Guide for
+      more information */
+  public void addReferences(DataRenderer renderer, DataReference ref)
+         throws VisADException, RemoteException {
+    addReferences(renderer, new DataReference[] {ref}, null);
+  }
+
+  /** link ref to this Display using the non-default renderer;
+      refs may be a mix of RemoteDataReference & DataReferenceImpl;
+      cannot be called through RemoteDisplay interface, since
+      renderer implements neither Remote nor Serializable;
+      must be called locally;
+      this method may only be invoked after all links to ScalarMaps
+      have been made;
+      the maps array applies only to rendering ref;
+      this is a method of DisplayImpl and RemoteDisplayImpl rather
+      than Display - see Section 6.1 of the Developer's Guide for
+      more information */
+  public void addReferences(DataRenderer renderer, DataReference ref,
+                            ConstantMap[] constant_maps)
+         throws VisADException, RemoteException {
+    addReferences(renderer, new DataReference[] {ref},
+                  new ConstantMap[][] {constant_maps});
+  }
+
+  /** link refs to this Display using the non-default renderer;
+      refs may be a mix of RemoteDataReference & DataReferenceImpl;
+      cannot be called through RemoteDisplay interface, since
+      renderer implements neither Remote nor Serializable;
+      must be called locally;
+      this method may only be invoked after all links to ScalarMaps
+      have been made; this is a method of DisplayImpl and
+      RemoteDisplayImpl rather than Display - see Section 6.1 of the
+      Developer's Guide for more information */
+  public void addReferences(DataRenderer renderer, DataReference[] refs)
+         throws VisADException, RemoteException {
+    addReferences(renderer, refs, null);
+  }
+
+  /** link refs to this Display using the non-default renderer;
+      refs may be a mix of RemoteDataReference & DataReferenceImpl;
+      cannot be called through RemoteDisplay interface, since
+      renderer implements neither Remote nor Serializable;
+      must be called locally;
+      this method may only be invoked after all links to ScalarMaps
+      have been made;
+      the maps[i] array applies only to rendering refs[i];
+      this is a method of DisplayImpl and RemoteDisplayImpl rather
+      than Display - see Section 6.1 of the Developer's Guide for
+      more information */
+  public void addReferences(DataRenderer renderer, DataReference[] refs,
+         ConstantMap[][] constant_maps) throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteDisplayImpl.addReferences: " +
+                                     "AdaptedAction is null");
+    }
+    ((DisplayImpl) AdaptedAction).adaptedAddReferences(renderer, refs,
+                     (RemoteDisplay) this, constant_maps);
+  }
+
+  /** remove link to a DataReference;
+      because DataReference array input to adaptedAddReferences may be a
+      mix of local and remote, we tolerate either here */
+  public void removeReference(ThingReference ref)
+         throws VisADException, RemoteException {
+    if (!(ref instanceof DataReference)) {
+      throw new ReferenceException("RemoteDisplayImpl.addReference: ref " +
+                                   "must be DataReference");
+    }
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteDisplayImpl.removeReference: " +
+                                     "AdaptedAction is null");
+    }
+    ((DisplayImpl) AdaptedAction).adaptedDisplayRemoveReference((DataReference) ref);
+  }
+
+  /** add a ScalarMap to this Display */
+  public void addMap(ScalarMap map)
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteDisplayImpl.addMap: " +
+                                     "AdaptedAction is null");
+    }
+    ((DisplayImpl) AdaptedAction).addMap(map);
+  }
+
+  /** remove a ScalarMap from this Display */
+  public void removeMap(ScalarMap map)
+         throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteDisplayImpl.removeMap: " +
+                                     "AdaptedAction is null");
+    }
+    ((DisplayImpl) AdaptedAction).removeMap(map);
+  }
+
+  /** clear set of ScalarMap-s associated with this display */
+  public void clearMaps() throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteDisplayImpl.clearMaps: " +
+                                     "AdaptedAction is null");
+    }
+    ((DisplayImpl) AdaptedAction).clearMaps();
+  }
+
+  /** destroy this display */
+  public void destroy() throws VisADException, RemoteException {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException("RemoteDisplayImpl.destroy: " +
+                                     "AdaptedAction is null");
+    }
+    ((DisplayImpl) AdaptedAction).destroy();
+  }
+
+  public String getDisplayClassName() throws RemoteException {
+    return AdaptedAction.getClass().getName();
+  }
+
+  public int getDisplayAPI() throws RemoteException, VisADException {
+    return ((DisplayImpl) AdaptedAction).getAPI();
+  }
+
+  public String getDisplayRendererClassName() throws RemoteException {
+    DisplayRenderer dr = ((DisplayImpl )AdaptedAction).getDisplayRenderer();
+    return dr.getClass().getName();
+  }
+
+  public Vector getMapVector()
+	throws VisADException, RemoteException
+  {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException(getClass().getName() + ".getMapVector: " +
+                                     "AdaptedAction is null");
+    }
+
+    return ((DisplayImpl) AdaptedAction).getMapVector();
+  }
+
+
+  public Vector getConstantMapVector()
+	throws VisADException, RemoteException
+  {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException(getClass().getName() + ".getConstantMapVector: " +
+                                     "AdaptedAction is null");
+    }
+
+    return ((DisplayImpl) AdaptedAction).getConstantMapVector();
+  }
+
+  public GraphicsModeControl getGraphicsModeControl()
+	throws VisADException, RemoteException
+  {
+    if (AdaptedAction == null) {
+      throw new RemoteVisADException(getClass().getName() + ".getGraphicsModeControl: " +
+                                     "AdaptedAction is null");
+    }
+
+    return ((DisplayImpl) AdaptedAction).getGraphicsModeControl();
+  }
+
+  public Vector getReferenceLinks()
+	throws VisADException, RemoteException
+  {
+    Vector links = new Vector();
+
+    Vector rv = ((DisplayImpl )AdaptedAction).getRenderers();
+    Enumeration e = rv.elements();
+    while (e.hasMoreElements()) {
+      DataRenderer dr = (DataRenderer )e.nextElement();
+
+      DataDisplayLink[] dl = dr.getLinks();
+      if (dl != null) {
+	for (int i = 0; i < dl.length; i++) {
+          try {
+            links.addElement(new RemoteReferenceLinkImpl(dl[i]));
+          } catch (RemoteException re) {
+            // skip remote links
+          }
+        }
+      }
+    }
+
+    return links;
+  }
+
+  public RemoteDisplayMonitor getRemoteDisplayMonitor()
+        throws RemoteException
+  {
+    DisplayMonitor dpyMon = ((DisplayImpl )AdaptedAction).getDisplayMonitor();
+    return new RemoteDisplayMonitorImpl((DisplayMonitorImpl )dpyMon);
+  }
+
+  /**
+   * Returns a remotely-usable wrapper for the associated Display's
+   * synchronization object.
+   *
+   */
+  public DisplaySync getDisplaySync()
+        throws RemoteException
+  {
+    return ((DisplayImpl )AdaptedAction).getDisplaySync();
+  }
+
+  /**
+   * Returns a remotely-usable wrapper for the associated Display's
+   * synchronization object.
+   */
+  public RemoteDisplaySync getRemoteDisplaySync()
+        throws RemoteException
+  {
+    DisplaySync dpySync = ((DisplayImpl )AdaptedAction).getDisplaySync();
+    return new RemoteDisplaySyncImpl((DisplaySyncImpl )dpySync);
+  }
+
+  /**
+   * Send a message to all </tt>MessageListener</tt>s.
+   *
+   * @param msg Message being sent.
+   */
+  public void sendMessage(MessageEvent msg)
+    throws RemoteException
+  {
+    ((DisplayImpl )AdaptedAction).sendMessage(msg);
+  }
+}
diff --git a/visad/RemoteField.java b/visad/RemoteField.java
new file mode 100644
index 0000000..e8eda23
--- /dev/null
+++ b/visad/RemoteField.java
@@ -0,0 +1,37 @@
+//
+// RemoteField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteField is the interface for Remote VisAD Field-s.<P>
+*/
+public interface RemoteField extends Remote, Field {
+
+}
+
diff --git a/visad/RemoteFieldImpl.java b/visad/RemoteFieldImpl.java
new file mode 100644
index 0000000..41dd56a
--- /dev/null
+++ b/visad/RemoteFieldImpl.java
@@ -0,0 +1,289 @@
+//
+// RemoteFieldImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   RemoteFieldImpl is the VisAD remote adapter for FieldImpl.<P>
+*/
+public class RemoteFieldImpl extends RemoteFunctionImpl
+       implements RemoteField {
+
+  /** construct a RemoteFieldImpl object to provide remote
+      access to field */
+  public RemoteFieldImpl(FieldImpl field) throws RemoteException {
+    super(field);
+  }
+
+  /** methods adapted from Field */
+  public void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FieldImpl) AdaptedData).setSamples(range, copy);
+  }
+
+  public void setSamples(double[][] range)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FieldImpl) AdaptedData).setSamples(range);
+  }
+
+  public void setSamples(float[][] range)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FieldImpl) AdaptedData).setSamples(range);
+  }
+
+  public Set getDomainSet() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getDomainSet: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getDomainSet();
+  }
+
+  public int getLength() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getLength: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getLength();
+  }
+
+  public Unit[] getDomainUnits() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getDomainUnits: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getDomainUnits();
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getDomainCoordinateSystem: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getDomainCoordinateSystem();
+  }
+
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getSample: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getSample(index);
+  }
+
+  public void setSample(RealTuple domain, Data range)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.setSample: " +
+                                     "AdaptedData is null");
+    }
+    ((FieldImpl) AdaptedData).setSample(domain, range);
+  }
+
+  public void setSample(RealTuple domain, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.setSample: " +
+                                     "AdaptedData is null");
+    }
+    ((FieldImpl) AdaptedData).setSample(domain, range, copy);
+  }
+
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.setSample: " +
+                                     "AdaptedData is null");
+    }
+    ((FieldImpl) AdaptedData).setSample(index, range);
+  }
+
+  public void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.setSample: " +
+                                     "AdaptedData is null");
+    }
+    ((FieldImpl) AdaptedData).setSample(index, range, copy);
+  }
+
+  public Field extract(int component)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.extract: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).extract(component);
+  }
+
+  /** combine domains of two outermost nested Fields into a single
+      domain and Field */
+  public Field domainMultiply()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.domainMultiply: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).domainMultiply();
+  }
+
+  /** combine domains to depth, if possible */
+  public Field domainMultiply(int depth)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.domainMultiply: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).domainMultiply(depth);
+  }
+
+  /** factor Field domain into domains of two nested Fields */
+  public Field domainFactor( RealType factor )
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.domainFactor: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).domainFactor(factor);
+  }
+
+  public float[][] getFloats()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getValues: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getFloats();
+  }
+
+  public float[][] getFloats(boolean copy)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getValues: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getFloats(copy);
+  }
+
+  public double[][] getValues()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getValues: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getValues();
+  }
+
+  public double[][] getValues(boolean copy)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getValues: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getValues(copy);
+  }
+
+  public String[][] getStringValues()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getStringValues: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getStringValues();
+  }
+
+  public Unit[] getDefaultRangeUnits()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getDefaultRangeUnits: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getDefaultRangeUnits();
+  }
+
+  public Unit[][] getRangeUnits()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getRangeUnits: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getRangeUnits();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getRangeCoordinateSystem: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getRangeCoordinateSystem();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem(int i)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.getRangeCoordinateSystem: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).getRangeCoordinateSystem(i);
+  }
+
+  public boolean isFlatField() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.isFlatField: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).isFlatField();
+  }
+
+  public Enumeration domainEnumeration()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFieldImpl.domainEnumeration: " +
+                                     "AdaptedData is null");
+    }
+    return ((FieldImpl) AdaptedData).domainEnumeration();
+  }
+
+}
+
diff --git a/visad/RemoteFlatField.java b/visad/RemoteFlatField.java
new file mode 100644
index 0000000..0a10052
--- /dev/null
+++ b/visad/RemoteFlatField.java
@@ -0,0 +1,37 @@
+//
+// RemoteFlatField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteFlatField is the interface for Remote VisAD FlatField-s.<P>
+*/
+public interface RemoteFlatField extends Remote, FlatFieldIface {
+
+}
+
diff --git a/visad/RemoteFlatFieldImpl.java b/visad/RemoteFlatFieldImpl.java
new file mode 100644
index 0000000..d2075bb
--- /dev/null
+++ b/visad/RemoteFlatFieldImpl.java
@@ -0,0 +1,220 @@
+//
+// RemoteFlatFieldImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+
+/**
+ * RemoteFlatFieldImpl is the VisAD remote adapter for FlatField.<br>
+ * <br>
+ * Another way to approach the problem of moving data to a remote
+ * machine would be to have a remote class which serializes a sample and
+ * copies it over to the remote machine where it is cached for
+ * fast access.  This would be a much better approach if the application
+ * does a lot of computations on a set of static samples.
+ */
+public class RemoteFlatFieldImpl
+  extends RemoteFieldImpl
+  implements RemoteFlatField
+{
+
+  /** construct a RemoteFieldImpl object to provide remote
+      access to field */
+  public RemoteFlatFieldImpl(FlatField flatField)
+    throws RemoteException, VisADException
+  {
+    super(flatField);
+  }
+
+  /**
+   * Returns the sampling set of each flat component.
+   * @return		The sampling set of each component in the flat range.
+   */
+  public Set[] getRangeSets()
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.getRangeSets: " +
+                                     "AdaptedData is null");
+    }
+    return ((FlatField )AdaptedData).getRangeSets();
+  }
+
+  /** return array of ErrorEstimates associated with each
+      RealType component of range; each ErrorEstimate is a
+      mean error for all samples of a range RealType
+      component */
+  public ErrorEstimate[] getRangeErrors()
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.getRangeErrors: " +
+                                     "AdaptedData is null");
+    }
+    return ((FlatField )AdaptedData).getRangeErrors();
+  }
+
+  /** set ErrorEstimates associated with each RealType
+      component of range */
+  public void setRangeErrors(ErrorEstimate[] errors)
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.setRangeErrors: " +
+                                     "AdaptedData is null");
+    }
+    ((FlatField )AdaptedData).setRangeErrors(errors);
+  }
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      double[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet; copy array if copy flag is true */
+  public void setSamples(double[][] range, boolean copy)
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FlatField )AdaptedData).setSamples(range, copy);
+  }
+
+  /** set range array as range values of this FlatField;
+      the array is dimensioned
+      float[number_of_range_components][number_of_range_samples];
+      the order of range values must be the same as the order of domain
+      indices in the DomainSet; copy array if copy flag is true */
+  public void setSamples(float[][] range, boolean copy)
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FlatField )AdaptedData).setSamples(range, copy);
+  }
+
+  /** set the range values of the function including ErrorEstimate-s;
+      the order of range values must be the same as the order of
+      domain indices in the DomainSet */
+  public void setSamples(double[][] range, ErrorEstimate[] errors,
+                         boolean copy)
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FlatField )AdaptedData).setSamples(range, errors, copy);
+  }
+
+  public void setSamples(int start, double[][] range)
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FlatField )AdaptedData).setSamples(start, range);
+  }
+
+  /** set the range values of the function including ErrorEstimate-s;
+      the order of range values must be the same as the order of
+      domain indices in the DomainSet */
+  public void setSamples(float[][] range, ErrorEstimate[] errors,
+                         boolean copy)
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.setSamples: " +
+                                     "AdaptedData is null");
+    }
+    ((FlatField )AdaptedData).setSamples(range, errors, copy);
+  }
+
+  public byte[][] grabBytes()
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.grabBytes: " +
+                                     "AdaptedData is null");
+    }
+    return ((FlatField )AdaptedData).grabBytes();
+  }
+
+
+  /** get values for 'Flat' components in default range Unit-s */
+  public double[] getValues(int s_index)
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.getValues: " +
+                                     "AdaptedData is null");
+    }
+    return ((FlatField )AdaptedData).getValues(s_index);
+  }
+
+  /** mark this FlatField as non-missing */
+  public void clearMissing()
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.clearMissing: " +
+                                     "AdaptedData is null");
+    }
+    ((FlatField )AdaptedData).clearMissing();
+  }
+
+  /** convert this FlatField to a (non-Flat) FieldImpl */
+  public Field convertToField()
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.convertToField: " +
+                                     "AdaptedData is null");
+    }
+    return ((FlatField )AdaptedData).convertToField();
+  }
+
+  /**
+   * Gets the number of components in the "flat" range.
+   *
+   * @return The number of components in the "flat" range.
+   */
+  public int getRangeDimension()
+    throws RemoteException, VisADException
+  {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFlatFieldImpl.getRangeDimension: " +
+                                     "AdaptedData is null");
+    }
+    return ((FlatField )AdaptedData).getRangeDimension();
+  }
+}
diff --git a/visad/RemoteFunction.java b/visad/RemoteFunction.java
new file mode 100644
index 0000000..c4bbe4f
--- /dev/null
+++ b/visad/RemoteFunction.java
@@ -0,0 +1,37 @@
+//
+// RemoteFunction.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteFunction is the interface for Remote VisAD Function-s.<P>
+*/
+public interface RemoteFunction extends Remote, Function {
+
+}
+
diff --git a/visad/RemoteFunctionImpl.java b/visad/RemoteFunctionImpl.java
new file mode 100644
index 0000000..4738c44
--- /dev/null
+++ b/visad/RemoteFunctionImpl.java
@@ -0,0 +1,170 @@
+//
+// RemoteFunctionImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteFunctionImpl is the VisAD remote adapter for FieldImpl.<P>
+*/
+public abstract class RemoteFunctionImpl extends RemoteDataImpl
+       implements RemoteFunction {
+
+  public RemoteFunctionImpl(FunctionImpl function) throws RemoteException {
+    super(function);
+  }
+
+  /** methods adapted from Function */
+  public int getDomainDimension() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.getDomainDimension: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).getDomainDimension();
+  }
+
+  public Unit[] getDomainUnits() throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.getDomainUnits: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).getDomainUnits();
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem()
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException(
+                 "RemoteFunctionImpl.getDomainCoordinateSystem: " +
+                 "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).getDomainCoordinateSystem();
+  }
+
+  public Data evaluate(Real domain)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.evaluate: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).evaluate(domain);
+  }
+
+  public Data evaluate(Real domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.evaluate: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).evaluate(domain,
+                                                 sampling_mode, error_mode);
+  }
+
+  public Data evaluate(RealTuple domain)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.evaluate: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).evaluate(domain);
+  }
+
+  public Data evaluate(RealTuple domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.evaluate: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).evaluate(domain,
+                                                 sampling_mode, error_mode);
+  }
+
+  public Field resample(Set set) throws VisADException, RemoteException {
+    return resample(set, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+  }
+
+  /** can decide whether to return the local FieldImpl returned by
+      ((FunctionImpl) AdaptedData).resample, or whether to return a
+      RemoteFunctionImpl adapted for that FieldImpl;
+      the same is true for the methods: extract, binary, unary, evaluate
+      and getSample (as long as their return value is an instanceof Field) */
+  public Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.resample: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).resample(set, sampling_mode, error_mode);
+  }
+
+  public Data derivative( RealTuple location, RealType[] d_partial_s,
+                          MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.derivative: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).derivative( location, d_partial_s, derivType_s, error_mode);
+  }
+
+  public Data derivative( int error_mode )
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.derivative: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).derivative(error_mode);
+  }
+
+  public Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.derivative: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).derivative(derivType_s, error_mode);
+  }
+
+  public Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.derivative: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).derivative(d_partial, error_mode);
+  }
+
+  public Function derivative( RealType d_partial, MathType derivType, int error_mode )
+         throws VisADException, RemoteException {
+    if (AdaptedData == null) {
+      throw new RemoteVisADException("RemoteFunctionImpl.derivative: " +
+                                     "AdaptedData is null");
+    }
+    return ((FunctionImpl) AdaptedData).derivative( d_partial, derivType, error_mode);
+  }
+}
+
diff --git a/visad/RemoteReferenceLink.java b/visad/RemoteReferenceLink.java
new file mode 100644
index 0000000..8a2b318
--- /dev/null
+++ b/visad/RemoteReferenceLink.java
@@ -0,0 +1,48 @@
+//
+// RemoteReferenceLink.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+/**
+   RemoteReferenceLink is the interface for links to Remote objects.<P>
+*/
+public interface RemoteReferenceLink extends Remote
+{
+  /** return the name of the DataRenderer used to render this reference */
+  String getRendererClassName() throws VisADException, RemoteException;
+
+  /** return a reference to the remote Data object */
+  RemoteDataReference getReference()
+	throws VisADException, RemoteException;
+
+  /** return the list of ConstantMap-s which apply to this Data object */
+  Vector getConstantMapVector() throws VisADException, RemoteException;
+}
diff --git a/visad/RemoteReferenceLinkImpl.java b/visad/RemoteReferenceLinkImpl.java
new file mode 100644
index 0000000..e826e76
--- /dev/null
+++ b/visad/RemoteReferenceLinkImpl.java
@@ -0,0 +1,154 @@
+//
+// RemoteReferenceLinkImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import java.rmi.server.UnicastRemoteObject;
+
+/**
+   RemoteReferenceLinkImpl is the VisAD remote adapter for DataDisplayLink-s.
+*/
+public class RemoteReferenceLinkImpl extends UnicastRemoteObject
+        implements RemoteReferenceLink
+{
+  DataDisplayLink link;
+
+  /** create a Remote reference to a DataDisplayLink */
+  public RemoteReferenceLinkImpl(DataDisplayLink ddl)
+	throws RemoteException
+  {
+    if (ddl == null) {
+      throw new RemoteException("Cannot link to null link");
+    }
+
+    DataReference ref;
+    ref = ddl.getDataReference();
+    if (!(ref instanceof DataReferenceImpl)) {
+      throw new RemoteException("Cannot link to non-DataReferenceImpl (" +
+                                ref.getClass().getName() + ")");
+    }
+
+    link = ddl;
+  }
+
+  /** return the name of the DataRenderer used to render this reference */
+  public String getRendererClassName()
+	throws VisADException, RemoteException
+  {
+    if (link == null) {
+      return null;
+    }
+
+    DataRenderer rend = link.getRenderer();
+    if (rend == null) {
+      return null;
+    }
+
+    return rend.getClass().getName();
+  }
+
+  /** return a reference to the remote Data object */
+  public RemoteDataReference getReference()
+	throws VisADException, RemoteException
+  {
+    DataReferenceImpl di = (DataReferenceImpl )link.getDataReference();
+    return new RemoteDataReferenceImpl(di);
+  }
+
+  /** return the list of ConstantMap-s which apply to this Data object */
+  public Vector getConstantMapVector()
+	throws VisADException, RemoteException
+  {
+    return link.getConstantMaps();
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!(o instanceof RemoteReferenceLink)) {
+      return false;
+    }
+
+    boolean result;
+    if (o instanceof RemoteReferenceLinkImpl) {
+      RemoteReferenceLinkImpl rrli = (RemoteReferenceLinkImpl )o;
+      result = link.equals(rrli.link);
+    } else {
+      RemoteReferenceLink rrl = (RemoteReferenceLink )o;
+      try {
+        result = getReference().equals(rrl.getReference());
+      } catch (Exception e){
+        result = false;
+      }
+    }
+
+    return result;
+  }
+
+  /** return a String representation of the referenced data */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer();
+    try {
+      if (link == null) {
+        buf.append("<null>");
+      } else {
+        buf.append(link.getDataReference().getName());
+      }
+      buf.append(" -> ");
+      buf.append(getRendererClassName());
+    } catch (RemoteException e) {
+      return null;
+    } catch (VisADException e) {
+      return null;
+    }
+
+    Vector v;
+    try {
+      v = getConstantMapVector();
+      Enumeration e = v.elements();
+      if (e.hasMoreElements()) {
+	buf.append(':');
+	while (e.hasMoreElements()) {
+	  ConstantMap cm = (ConstantMap )e.nextElement();
+	  buf.append(" [");
+          buf.append(cm.getConstant());
+          buf.append(" -> ");
+          buf.append(cm.getDisplayScalar());
+          buf.append(']');
+	}
+      }
+    } catch (RemoteException e) {
+    } catch (VisADException e) {
+    }
+
+    return buf.toString();
+  }
+}
diff --git a/visad/RemoteServer.java b/visad/RemoteServer.java
new file mode 100644
index 0000000..88841ea
--- /dev/null
+++ b/visad/RemoteServer.java
@@ -0,0 +1,89 @@
+//
+// RemoteServer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteServer is the interface for serving RemoteDataReferences.
+   A RemoteServerImpl should be bound to a URL via Naming.rebind,
+   and accessed remotely via this RemoteServer interface.<P>
+*/
+public interface RemoteServer extends Remote {
+
+  /** return the RemoteDataReference with index on this
+      RemoteServer, or null */
+  RemoteDataReference getDataReference(int index)
+         throws RemoteException;
+
+  /** return the RemoteDataReference with name on this
+      RemoteServer, or null */
+  RemoteDataReference getDataReference(String name)
+         throws VisADException, RemoteException;
+
+  /** return an array of all RemoteDataReferences on this
+      RemoteServer, or null */
+  RemoteDataReference[] getDataReferences()
+         throws RemoteException;
+
+  /** add a new RemoteDataReferenceImpl to server and extend array */
+  void addDataReference(RemoteDataReferenceImpl ref)
+         throws RemoteException;
+
+  /** set array of all RemoteDataReferences on this RemoteServer */
+  void setDataReferences(RemoteDataReferenceImpl[] rs)
+         throws RemoteException;
+
+  /** remove a RemoteDataReferenceImpl from server and shrink size of array */
+  void removeDataReference(RemoteDataReferenceImpl ref)
+         throws RemoteException;
+
+  /** return array of all RemoteDisplays in this RemoteServer */
+  RemoteDisplay[] getDisplays()
+         throws RemoteException;
+
+  /** get a RemoteDisplay by index */
+  RemoteDisplay getDisplay(int index)
+         throws RemoteException;
+
+  /** get a RemoteDisplay by name */
+  RemoteDisplay getDisplay(String name)
+         throws VisADException, RemoteException;
+
+  /** add a new RemoteDisplayImpl to server and extend array */
+  void addDisplay(RemoteDisplayImpl rd)
+         throws RemoteException;
+
+  /** set all RemoteDisplayImpls to serve */
+  void setDisplays(RemoteDisplayImpl[] rd)
+         throws RemoteException;
+
+  /** remove a RemoteDisplayImpl from server and shrink size of array */
+  void removeDisplay(RemoteDisplayImpl rd)
+         throws RemoteException;
+}
+
diff --git a/visad/RemoteServerImpl.java b/visad/RemoteServerImpl.java
new file mode 100644
index 0000000..88a66d5
--- /dev/null
+++ b/visad/RemoteServerImpl.java
@@ -0,0 +1,279 @@
+//
+// RemoteServerImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.rmi.server.UnicastRemoteObject;
+
+public class RemoteServerImpl extends UnicastRemoteObject
+       implements RemoteServer
+{
+  private RemoteDataReferenceImpl[] refs;
+  private RemoteDisplayImpl[] dpys;
+
+  public RemoteServerImpl()
+         throws RemoteException {
+    this(null, null);
+  }
+
+  public RemoteServerImpl(RemoteDataReferenceImpl[] rs)
+         throws RemoteException {
+    this(rs, null);
+  }
+
+  public RemoteServerImpl(RemoteDisplayImpl[] rd)
+         throws RemoteException {
+    this(null, rd);
+  }
+
+  /** construct a RemoteServerImpl and initialize it with
+      an array of RemoteDataReferenceImpls */
+  public RemoteServerImpl(RemoteDataReferenceImpl[] rs, RemoteDisplayImpl[] rd)
+         throws RemoteException {
+    super();
+    refs = rs;
+    dpys = rd;
+  }
+
+  /** get a RemoteDataReference by index */
+  public synchronized RemoteDataReference getDataReference(int index)
+         throws RemoteException {
+    if (refs != null && 0 <= index && index < refs.length) return refs[index];
+    else return null;
+  }
+
+  /** get a RemoteDataReference by name */
+  public synchronized RemoteDataReference getDataReference(String name)
+         throws VisADException, RemoteException {
+    if (name != null && refs != null) {
+      for (int i=0; i<refs.length; i++) {
+        if (name.equals(refs[i].getName())) return refs[i];
+      }
+    }
+    return null;
+  }
+
+  /** return array of all RemoteDataReferences in this RemoteServer */
+  public synchronized RemoteDataReference[] getDataReferences()
+         throws RemoteException {
+    if (refs == null || refs.length == 0) return null;
+    // is this copy necessary?
+    RemoteDataReference[] rs =
+      new RemoteDataReference[refs.length];
+    for (int i=0; i<refs.length; i++) rs[i] = refs[i];
+    return rs;
+  }
+
+  /** set one RemoteDataReference in the array on this
+      RemoteServer (and extend length of array if necessary) */
+  public synchronized void setDataReference(int index, RemoteDataReferenceImpl ref)
+         throws VisADException {
+    if (index < 0) {
+      throw new RemoteVisADException("RemoteServerImpl.setDataReference: " +
+                                     "negative index");
+    }
+    if (refs == null || index >= refs.length) {
+      RemoteDataReferenceImpl[] rs = new RemoteDataReferenceImpl[index + 1];
+      for (int i=0; i<index; i++) {
+        if (refs != null && i < refs.length) rs[i] = refs[i];
+        else rs[i] = null;
+      }
+      refs = rs;
+    }
+    refs[index] = ref;
+  }
+
+  /**
+   * Add a DataReferenceImpl to server (after wrapping it in
+   * a RemoteDataReferenceImpl)
+   */
+  public void addDataReference(DataReferenceImpl ref)
+    throws RemoteException
+  {
+    addDataReference(new RemoteDataReferenceImpl(ref));
+  }
+
+  /** add a new RemoteDataReferenceImpl to server and extend array */
+  public synchronized void addDataReference(RemoteDataReferenceImpl ref) {
+    if (ref == null) return;
+
+    int len;
+    if (refs == null || refs.length == 0) len = 0;
+    else len = refs.length;
+
+    RemoteDataReferenceImpl[] nr = new RemoteDataReferenceImpl[len + 1];
+
+    if (len > 0) System.arraycopy(refs, 0, nr, 0, len);
+
+    nr[len] = ref;
+    refs = nr;
+  }
+
+  /** set array of all RemoteDataReferences on this RemoteServer */
+  public synchronized void setDataReferences(RemoteDataReferenceImpl[] rs) {
+    if (rs == null) {
+      refs = null;
+      return;
+    }
+    refs = new RemoteDataReferenceImpl[rs.length];
+    for (int i=0; i<refs.length; i++) {
+      refs[i] = rs[i];
+    }
+  }
+
+  /** remove a RemoteDataReferenceImpl from server and shrink size of array */
+  public synchronized void removeDataReference(RemoteDataReferenceImpl ref) {
+    int len;
+    if (refs == null || refs.length == 0) len = 0;
+    else len = refs.length;
+
+    int index = -1;
+    for (int i=0; i<len; i++) {
+      if (refs[i] == ref) {
+        index = i;
+        break;
+      }
+    }
+    if (index < 0) return;
+
+    RemoteDataReferenceImpl[] nr = new RemoteDataReferenceImpl[len - 1];
+    if (index > 0) System.arraycopy(refs, 0, nr, 0, index);
+    if (index < len - 1) {
+      System.arraycopy(refs, index + 1, nr, index, len - index - 1);
+    }
+
+    refs = nr;
+  }
+
+  /** return array of all RemoteDisplays in this RemoteServer */
+  public RemoteDisplay[] getDisplays()
+         throws RemoteException
+  {
+    if (dpys == null || dpys.length == 0) return null;
+    // is this copy necessary?
+    RemoteDisplay[] rd =
+      new RemoteDisplay[dpys.length];
+    for (int i=0; i<dpys.length; i++) rd[i] = dpys[i];
+    return rd;
+  }
+
+  /** get a RemoteDisplay by index */
+  public RemoteDisplay getDisplay(int index)
+  {
+    if (dpys != null && index >= 0 && index < dpys.length) {
+      return dpys[index];
+    }
+    return null;
+  }
+
+  /** get a RemoteDisplay by name */
+  public RemoteDisplay getDisplay(String name)
+	throws VisADException, RemoteException
+  {
+    if (dpys == null) {
+      throw new RemoteException("No displays associated with this server");
+    }
+
+    for (int i=0; i<dpys.length; i++) {
+      if (dpys[i] == null) {
+	continue;
+      }
+      if (dpys[i].getName().equals(name)) {
+	return dpys[i];
+      }
+    }
+
+    throw new RemoteException("Display \"" + name + "\" not found");
+  }
+
+  /** add DisplayImpl to server (after wrapping it in a RemoteDisplayImpl) */
+  public void addDisplay(DisplayImpl di)
+    throws RemoteException
+  {
+    addDisplay(new RemoteDisplayImpl(di));
+  }
+
+  /** add a new RemoteDisplayImpl to server and extend array */
+  public synchronized void addDisplay(RemoteDisplayImpl rd) {
+    if (rd == null) {
+      return;
+    }
+
+    int len;
+    if (dpys == null || dpys.length == 0) {
+      len = 0;
+    } else {
+      len = dpys.length;
+    }
+
+    RemoteDisplayImpl[] nd = new RemoteDisplayImpl[len + 1];
+
+    if (len > 0) System.arraycopy(dpys, 0, nd, 0, len);
+
+    nd[len] = rd;
+    dpys = nd;
+  }
+
+  /** set all RemoteDisplayImpls to serve */
+  public synchronized void setDisplays(RemoteDisplayImpl[] rd) {
+    if (rd == null) {
+      dpys = null;
+      return;
+    }
+    dpys = new RemoteDisplayImpl[rd.length];
+    for (int i=0; i<dpys.length; i++) {
+      dpys[i] = rd[i];
+    }
+  }
+
+  /** remove a RemoteDisplayImpl from server and shrink size of array */
+  public synchronized void removeDisplay(RemoteDisplayImpl rd) {
+    //
+    int len;
+    if (dpys == null || dpys.length == 0) len = 0;
+    else len = dpys.length;
+
+    int index = -1;
+    for (int i=0; i<len; i++) {
+      if (dpys[i] == rd) {
+        index = i;
+        break;
+      }
+    }
+    if (index < 0) return;
+
+    RemoteDisplayImpl[] nd = new RemoteDisplayImpl[len - 1];
+    if (index > 0) System.arraycopy(dpys, 0, nd, 0, index);
+    if (index < len - 1) {
+      System.arraycopy(dpys, index + 1, nd, index, len - index - 1);
+    }
+
+    dpys = nd;
+  }
+
+}
+
diff --git a/visad/RemoteSlaveDisplay.java b/visad/RemoteSlaveDisplay.java
new file mode 100644
index 0000000..b6492b1
--- /dev/null
+++ b/visad/RemoteSlaveDisplay.java
@@ -0,0 +1,44 @@
+//
+// RemoteSlaveDisplay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/** RemoteSlaveDisplay is the interface for "slave" displays
+    that receive their images from another source, instead of doing the
+    rendering themselves. */
+public interface RemoteSlaveDisplay extends Remote {
+
+  /** Update this slave display with the given RLE-encoded image pixels */
+  void sendImage(int[] pixels, int width, int height, int type)
+    throws RemoteException;
+
+  /** Send the given message to this slave display */
+  void sendMessage(String message) throws RemoteException;
+
+}
+
diff --git a/visad/RemoteSlaveDisplayImpl.java b/visad/RemoteSlaveDisplayImpl.java
new file mode 100644
index 0000000..da3f029
--- /dev/null
+++ b/visad/RemoteSlaveDisplayImpl.java
@@ -0,0 +1,266 @@
+//
+// RemoteSlaveDisplayImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.*;
+import javax.swing.*;
+import visad.browser.Convert;
+
+/** RemoteSlaveDisplayImpl is an implementation of a slaved display that
+    receives its images from a RemoteDisplay (via RMI). */
+public class RemoteSlaveDisplayImpl extends UnicastRemoteObject
+  implements RemoteSlaveDisplay, MouseListener, MouseMotionListener
+{
+
+  private RemoteDisplay display;
+  private BufferedImage image;
+  private JComponent component;
+  private Vector listen = new Vector();
+
+  /** Construct a new slaved display linked to the given RemoteDisplay */
+  public RemoteSlaveDisplayImpl(RemoteDisplay d)
+    throws VisADException, RemoteException
+  {
+    display = d;
+    if (display != null) {
+      display.addSlave(this);
+    }
+
+    component = new JComponent() {
+      public void paint(Graphics g) {
+        if (image != null) g.drawImage(image, 0, 0, this);
+      }
+
+      public Dimension getPreferredSize() {
+        if (image == null) return new Dimension(256, 256);
+        return new Dimension(image.getWidth(), image.getHeight());
+      }
+    };
+    component.addMouseListener(this);
+    component.addMouseMotionListener(this);
+  }
+
+  /** Remove the link from this slaved display to its remote display */
+  public void unlink() throws VisADException, RemoteException {
+    display.removeSlave(this);
+  }
+
+  /** Get this slave display's component, for adding to a user interface */
+  public JComponent getComponent() {
+    return component;
+  }
+
+  /** Add a display listener to this slave display.
+      The following display events are supported:<ul>
+      <li>DisplayEvent.FRAME_DONE
+      <li>DisplayEvent.MOUSE_PRESSED
+      <li>DisplayEvent.MOUSE_PRESSED_LEFT
+      <li>DisplayEvent.MOUSE_PRESSED_CENTER
+      <li>DisplayEvent.MOUSE_PRESSED_RIGHT
+      <li>DisplayEvent.MOUSE_RELEASED
+      <li>DisplayEvent.MOUSE_RELEASED_LEFT
+      <li>DisplayEvent.MOUSE_RELEASED_CENTER
+      <li>DisplayEvent.MOUSE_RELEASED_RIGHT
+      </ul> */
+  public void addDisplayListener(DisplayListener l) {
+    synchronized (listen) {
+      listen.add(l);
+    }
+  }
+
+  /** Remove a display listener from this slave display */
+  public void removeDisplayListener(DisplayListener l) {
+    synchronized (listen) {
+      listen.remove(l);
+    }
+  }
+
+  /** Get this slave display's current image */
+  public BufferedImage getImage() {
+    return image;
+  }
+
+  /** Update this slave display with the given RLE-encoded image pixels */
+  public void sendImage(int[] pixels, int width, int height, int type)
+    throws RemoteException
+  {
+    // decode pixels
+    int[] decoded = Convert.decodeRLE(pixels);
+
+    // build image from decoded pixels
+    BufferedImage img = new BufferedImage(width, height, type);
+    img.setRGB(0, 0, width, height, decoded, 0, width);
+
+    // wait for image to finish, just in case
+    MediaTracker mt = new MediaTracker(component);
+    mt.addImage(image, 0);
+    try {
+      mt.waitForID(0);
+    }
+    catch (InterruptedException exc) { }
+    image = img;
+
+    // redraw display using new image
+    component.repaint();
+
+    // notify listeners of display change
+    DisplayEvent e = new DisplayEvent(display, DisplayEvent.FRAME_DONE);
+    synchronized (listen) {
+      for (int i=0; i<listen.size(); i++) {
+        DisplayListener l = (DisplayListener) listen.elementAt(i);
+        try {
+          l.displayChanged(e);
+        }
+        catch (VisADException exc) {
+          exc.printStackTrace();
+        }
+      }
+    }
+  }
+
+  /** Send the given message to this slave display */
+  public void sendMessage(String message) throws RemoteException {
+    // The message should be of the form:
+    //   class\nnumber\nstate
+    // where class is the class name of the control that has changed,
+    // number is the index into that class name's control list,
+    // and state is the save string corresponding to the control's new state.
+    StringTokenizer st = new StringTokenizer(message, "\n");
+    Class c = null;
+    try {
+      c = Class.forName(st.nextToken());
+    }
+    catch (ClassNotFoundException exc) { }
+    int index = Convert.getInt(st.nextToken());
+    String save = st.nextToken();
+    // CTR: ignore message to RMI-based slave display clients for now
+  }
+
+  public void mouseClicked(MouseEvent e) {
+    // This event currently generates a "type not recognized" error
+    // sendMouseEvent(e);
+  }
+
+  public void mouseEntered(MouseEvent e) {
+    sendMouseEvent(e);
+  }
+
+  public void mouseExited(MouseEvent e) {
+    sendMouseEvent(e);
+  }
+
+  public void mousePressed(MouseEvent e) {
+    sendMouseEvent(e);
+
+    // notify display listeners
+    DisplayEvent de1 =
+      new DisplayEvent(display, DisplayEvent.MOUSE_PRESSED, e);
+    DisplayEvent de2 = null;
+    if (SwingUtilities.isLeftMouseButton(e)) {
+      de2 = new DisplayEvent(display, DisplayEvent.MOUSE_PRESSED_LEFT, e);
+    }
+    else if (SwingUtilities.isMiddleMouseButton(e)) {
+      de2 = new DisplayEvent(display, DisplayEvent.MOUSE_PRESSED_CENTER, e);
+    }
+    else if (SwingUtilities.isRightMouseButton(e)) {
+      de2 = new DisplayEvent(display, DisplayEvent.MOUSE_PRESSED_RIGHT, e);
+    }
+    synchronized (listen) {
+      for (int i=0; i<listen.size(); i++) {
+        DisplayListener l = (DisplayListener) listen.elementAt(i);
+        try {
+          l.displayChanged(de1);
+          if (de2 != null) l.displayChanged(de2);
+        }
+        catch (VisADException exc) {
+          exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          exc.printStackTrace();
+        }
+      }
+    }
+  }
+
+  public void mouseReleased(MouseEvent e) {
+    sendMouseEvent(e);
+
+    // notify display listeners
+    DisplayEvent de1 =
+      new DisplayEvent(display, DisplayEvent.MOUSE_RELEASED, e);
+    DisplayEvent de2 = null;
+    if (SwingUtilities.isLeftMouseButton(e)) {
+      de2 = new DisplayEvent(display, DisplayEvent.MOUSE_RELEASED_LEFT, e);
+    }
+    else if (SwingUtilities.isMiddleMouseButton(e)) {
+      de2 =
+        new DisplayEvent(display, DisplayEvent.MOUSE_RELEASED_CENTER, e);
+    }
+    else if (SwingUtilities.isRightMouseButton(e)) {
+      de2 = new DisplayEvent(display, DisplayEvent.MOUSE_RELEASED_RIGHT, e);
+    }
+    synchronized (listen) {
+      for (int i=0; i<listen.size(); i++) {
+        DisplayListener l = (DisplayListener) listen.elementAt(i);
+        try {
+          l.displayChanged(de1);
+          if (de2 != null) l.displayChanged(de2);
+        }
+        catch (VisADException exc) {
+          exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          exc.printStackTrace();
+        }
+      }
+    }
+  }
+
+  public void mouseDragged(MouseEvent e) {
+    sendMouseEvent(e);
+  }
+
+  public void mouseMoved(MouseEvent e) {
+    // This event currently generates a "type not recognized" error
+    // sendMouseEvent(e);
+  }
+
+  /** feed MouseEvents to this display's MouseHelper */
+  private void sendMouseEvent(MouseEvent e) {
+    try {
+      display.sendMouseEvent(e);
+    }
+    catch (VisADException exc) { }
+    catch (RemoteException exc) { }
+  }
+
+}
diff --git a/visad/RemoteSourceListener.java b/visad/RemoteSourceListener.java
new file mode 100644
index 0000000..dc0a3f0
--- /dev/null
+++ b/visad/RemoteSourceListener.java
@@ -0,0 +1,42 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * The interface for objects which need to be informed
+ * when a remote data source goes away.
+ */
+public interface RemoteSourceListener
+{
+  /**
+   * A remote <tt>Data</tt> object is no longer available.
+   *
+   * @param name The name of the <tt>Data</tt> object.
+   */
+  void dataSourceLost(String name);
+
+  /**
+   * A remote collaboration peer is no longer available.
+   */
+  void collabSourceLost(int connectionID);
+}
diff --git a/visad/RemoteThing.java b/visad/RemoteThing.java
new file mode 100644
index 0000000..ad4913c
--- /dev/null
+++ b/visad/RemoteThing.java
@@ -0,0 +1,44 @@
+//
+// RemoteThing.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteThing is the interface for Remote VisAD Thing objects.<P>
+*/
+public interface RemoteThing extends Remote, Thing {
+
+  /** Tick is incremented in a RemoteThing object, rather than
+      propogating Thing changes to RemoteThingReference-s */
+  long incTick() throws RemoteException;
+
+  /** RemoteThingReference-s poll getTick() */
+  long getTick() throws RemoteException;
+
+}
+
diff --git a/visad/RemoteThingImpl.java b/visad/RemoteThingImpl.java
new file mode 100644
index 0000000..67e854d
--- /dev/null
+++ b/visad/RemoteThingImpl.java
@@ -0,0 +1,105 @@
+//
+// RemoteThingImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.rmi.server.UnicastRemoteObject;
+
+/**
+   RemoteThingImpl is the VisAD remote adapter for ThingImpl.<P>
+*/
+// public abstract class RemoteThingImpl extends UnicastRemoteObject
+public class RemoteThingImpl extends UnicastRemoteObject
+       implements RemoteThing {
+
+  /** 'this' is the Remote adaptor for AdaptedThing (which is local);
+      AdaptedThing is transient because UnicastRemoteObject is
+      Serializable, but a copy of 'this' on another JVM will not
+      be local to AdaptedThing and cannot adapt it;
+      the methods of RemoteThingImpl text for null AdaptedThing */
+  final transient ThingImpl AdaptedThing;
+
+  /** Tick increments each time data changes;
+      used in place of propogating notifyReferences
+      to Remote parents */
+  private long Tick;
+
+  public RemoteThingImpl(ThingImpl thing) throws RemoteException {
+    AdaptedThing = thing;
+    Tick = Long.MIN_VALUE + 1;
+  }
+
+  /** Tick is incremented in a RemoteThing object, rather than
+      propogating Thing changes to RemoteThingReference-s */
+  public long incTick() {
+    Tick += 1;
+    if (Tick == Long.MAX_VALUE) Tick = Long.MIN_VALUE + 1;
+    return Tick;
+  }
+
+  /** RemoteThingReference-s can (but don't currently) poll getTick() */
+  public long getTick() {
+    return Tick;
+  }
+
+  /** methods adapted from Thing;
+      do not adapt equals, toString, hashCode or clone */
+
+  /** add a ThingReference to this RemoteThingImpl;
+      must be RemoteThingReference;
+      called by ThingReference.setThing */
+  public void addReference(ThingReference r) throws VisADException {
+    if (!(r instanceof RemoteThingReference)) {
+      throw new RemoteVisADException("RemoteThingImpl.addReference: must use " +
+                                     "RemoteThingReference");
+    }
+    if (AdaptedThing == null) {
+      throw new RemoteVisADException("RemoteThingImpl.addReference " +
+                                     "AdaptedThing is null");
+    }
+    AdaptedThing.adaptedAddReference((RemoteThingReference )r,
+                                     (RemoteThing )this);
+  }
+
+  /** remove a ThingReference to this RemoteThingImpl;
+      must be RemoteThingReferenceImpl;
+      called by ThingReference.setThing */
+  public void removeReference(ThingReference r) throws VisADException {
+    if (!(r instanceof RemoteThingReference)) {
+      throw new RemoteVisADException("RemoteThingImpl.addReference: must use " +
+                                     "RemoteThingReference");
+    }
+    if (AdaptedThing == null) {
+      throw new RemoteVisADException("RemoteThingImpl.removeReference " +
+                                     "AdaptedThing is null");
+    }
+    AdaptedThing.adaptedRemoveReference((RemoteThingReference )r,
+                                        (RemoteThing )this);
+  }
+
+}
+
diff --git a/visad/RemoteThingReference.java b/visad/RemoteThingReference.java
new file mode 100644
index 0000000..bc27422
--- /dev/null
+++ b/visad/RemoteThingReference.java
@@ -0,0 +1,37 @@
+//
+// RemoteThingReference.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteThingReference is the interface for Remote ThingReference-s.<P>
+*/
+public interface RemoteThingReference extends Remote, ThingReference {
+
+}
+
diff --git a/visad/RemoteThingReferenceImpl.java b/visad/RemoteThingReferenceImpl.java
new file mode 100644
index 0000000..f060a6e
--- /dev/null
+++ b/visad/RemoteThingReferenceImpl.java
@@ -0,0 +1,161 @@
+//
+// RemoteThingReferenceImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.rmi.server.UnicastRemoteObject;
+
+/**
+   RemoteThingReferenceImpl is VisAD remote adapter for ThingReferenceImpl.<P>
+*/
+public class RemoteThingReferenceImpl extends UnicastRemoteObject
+       implements RemoteThingReference {
+
+  final transient ThingReferenceImpl AdaptedThingReference;
+
+  public RemoteThingReferenceImpl(ThingReferenceImpl ref) throws RemoteException {
+    AdaptedThingReference = ref;
+  }
+
+  /** set this RemoteThingReferenceImpl to refer to t;
+      must be RemoteThingImpl */
+  public synchronized void setThing(Thing t)
+         throws VisADException, RemoteException {
+    if (t == null) {
+      throw new ReferenceException("RemoteThingReferenceImpl: thing " +
+                                   "cannot be null");
+    }
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.setThing: " +
+                                     "AdaptedThingReference is null");
+    }
+    if (t instanceof ThingImpl) {
+      // allow Thing object passed by copy from remote JVM
+      AdaptedThingReference.setThing(t);
+    }
+    else {
+      AdaptedThingReference.adaptedSetThing((RemoteThing) t,
+                                          (RemoteThingReference) this);
+    }
+  }
+
+  public Thing getThing() throws VisADException, RemoteException {
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.getThing: " +
+                                     "AdaptedThingReference is null");
+    }
+    return AdaptedThingReference.getThing();
+  }
+
+  public long getTick() throws VisADException, RemoteException {
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.getTick: " +
+                                     "AdaptedThingReference is null");
+    }
+    return AdaptedThingReference.getTick();
+  }
+
+  public long incTick() throws VisADException, RemoteException {
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.incTick: " +
+                                     "AdaptedThingReference is null");
+    }
+    return AdaptedThingReference.incTick();
+  }
+
+  public String getName() throws VisADException, RemoteException {
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.getName: " +
+                                     "AdaptedThingReference is null");
+    }
+    return AdaptedThingReference.getName();
+  }
+
+  /** addThingChangedListener and removeThingChangedListener
+      provide ThingChangedEvent source semantics;
+      Action must be RemoteAction */
+  public void addThingChangedListener(ThingChangedListener a, long id)
+         throws VisADException, RemoteException {
+    if (!(a instanceof RemoteAction)) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.addThingChanged" +
+                                     "Listener: Action must be Remote");
+    }
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl." +
+                                     "addThingChangedListener: " +
+                                     "AdaptedThingReference is null");
+    }
+    AdaptedThingReference.adaptedAddThingChangedListener(((RemoteAction) a), id);
+  }
+
+  /** ThingChangedListener must be RemoteAction */
+  public void removeThingChangedListener(ThingChangedListener a)
+         throws VisADException, RemoteException {
+    if (!(a instanceof RemoteAction)) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.removeThingChanged" +
+                                     "Listener: Action must be Remote");
+    }
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl." +
+                                     "removeThingChangedListener: " +
+                                     "AdaptedThingReference is null");
+    }
+    AdaptedThingReference.adaptedRemoveThingChangedListener(((RemoteAction) a));
+  }
+
+  /** Action must be RemoteAction */
+  public ThingChangedEvent peekThingChanged(Action a)
+         throws VisADException, RemoteException {
+    if (!(a instanceof RemoteAction)) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.acknowledge" +
+                                     "ThingChanged: Action must be Remote");
+    }
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl." +
+                                     "acknowledgeThingChanged: " +
+                                     "AdaptedThingReference is null");
+    }
+    return AdaptedThingReference.adaptedPeekThingChanged(((RemoteAction) a));
+  }
+
+  /** Action must be RemoteAction */
+  public ThingChangedEvent acknowledgeThingChanged(Action a)
+         throws VisADException, RemoteException {
+    if (!(a instanceof RemoteAction)) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl.acknowledge" +
+                                     "ThingChanged: Action must be Remote");
+    }
+    if (AdaptedThingReference == null) {
+      throw new RemoteVisADException("RemoteThingReferenceImpl." +
+                                     "acknowledgeThingChanged: " +
+                                     "AdaptedThingReference is null");
+    }
+    return AdaptedThingReference.adaptedAcknowledgeThingChanged(((RemoteAction) a));
+  }
+
+}
+
diff --git a/visad/RemoteTupleIface.java b/visad/RemoteTupleIface.java
new file mode 100644
index 0000000..ec2a9e1
--- /dev/null
+++ b/visad/RemoteTupleIface.java
@@ -0,0 +1,37 @@
+//
+// RemoteTupleIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   RemoteTupleIface is the interface for Remote VisAD TupleIface-s.<P>
+*/
+public interface RemoteTupleIface extends Remote, TupleIface {
+
+}
+
diff --git a/visad/RemoteVisADException.java b/visad/RemoteVisADException.java
new file mode 100644
index 0000000..fa33fde
--- /dev/null
+++ b/visad/RemoteVisADException.java
@@ -0,0 +1,39 @@
+//
+// RemoteVisADException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   RemoteVisADException is an exception for an error with a
+   VisAD Remote class.<P>
+*/
+public class RemoteVisADException extends VisADException {
+
+  public RemoteVisADException() { super(); }
+  public RemoteVisADException(String s) { super(s); }
+
+}
+
diff --git a/visad/RendererControl.java b/visad/RendererControl.java
new file mode 100644
index 0000000..c72e6e8
--- /dev/null
+++ b/visad/RendererControl.java
@@ -0,0 +1,454 @@
+//
+// RendererControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Color;
+
+import java.rmi.RemoteException;
+
+import java.util.Enumeration;
+
+import visad.util.Util;
+
+/**
+ * <CODE>RendererControl</CODE> is the VisAD class for controlling
+ * <CODE>DisplayRenderer</CODE> data.<P>
+ */
+public class RendererControl
+  extends Control
+{
+  //private transient DisplayRenderer renderer = null;  not needed DRM 25-May-01
+
+  private float[] backgroundColor = new float[] { 0.0f, 0.0f, 0.0f};
+  private float[] boxColor = new float[] { 1.0f, 1.0f, 1.0f};
+  private float[] cursorColor = new float[] { 1.0f, 1.0f, 1.0f};
+  private float[] foregroundColor = new float[] { 1.0f, 1.0f, 1.0f};
+
+  private boolean boxOn = false;
+
+  /**
+   * Construct a renderer control.
+   * @param dpy Display with which this control is associated.
+   */
+  public RendererControl(DisplayImpl dpy)
+  {
+    super(dpy);
+  }
+
+  /**
+   * Get the background color.
+   * @return A 3 element array of <CODE>float</CODE> values
+   *         in the range <CODE>[0.0f - 1.0f]</CODE>
+   *         in the order <I>(Red, Green, Blue)</I>.
+   */
+  public float[] getBackgroundColor()
+  {
+    return backgroundColor;
+  }
+
+  /**
+   * Set the background color.
+   * @param color background color
+   */
+    public void setBackgroundColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setBackgroundColor(r, g, b);
+  }
+
+  /**
+   * Set the background color.  All specified values should be in the range
+   * <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   */
+  public void setBackgroundColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    backgroundColor[0] = r;
+    backgroundColor[1] = g;
+    backgroundColor[2] = b;
+    changeControl(true);
+  }
+
+  /**
+   * Get the foreground color set using 
+   * {@link #setForegroundColor(float, float, float)}.
+   * <B>NOTE</B>:  The values returned may not be
+   * indicative of the actual color of any of the components of the foreground
+   * (box, cursor, axes) since the color of each of these can be set 
+   * individually.
+   * @return A 3 element array of <CODE>float</CODE> values
+   *         in the range <CODE>[0.0f - 1.0f]</CODE>
+   *         in the order <I>(Red, Green, Blue)</I>.
+   * @see #setForegroundColor(float, float, float)
+   */
+  public float[] getForegroundColor()
+  {
+    return foregroundColor;
+  }
+
+  /**
+   * Convenience method to set the foreground color (box, cursor and axes).   
+   * Overrides any previous calls to setCursorColor, setBoxColor and 
+   * ScalarMap.setScaleColor().
+   * @param color foreground color
+   */
+  public void setForegroundColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setForegroundColor(r, g, b);
+  }
+
+  /**
+   * Convenience method to set the foreground color (box, cursor and axes).   
+   * Overrides any previous calls to setCursorColor, setBoxColor and 
+   * ScalarMap.setScaleColor().
+   * All specified values should be in the range
+   * <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   * @see #getForegroundColor()
+   * @see #setCursorColor(float, float, float)
+   * @see #setBoxColor(float, float, float)
+   * @see ScalarMap#setScaleColor(float[])
+   */
+  public void setForegroundColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    setCursorColor(r,g,b);
+    setBoxColor(r,g,b);
+    foregroundColor[0] = r;
+    foregroundColor[1] = g;
+    foregroundColor[2] = b;
+    if (getDisplayRenderer() != null) {
+      DisplayImpl dpy = getDisplayRenderer().getDisplay();
+      if (dpy != null) {
+        for (Enumeration e = display.getMapVector().elements(); 
+              e.hasMoreElements();)
+        {
+          ScalarMap map = (ScalarMap) e.nextElement();
+          if (map.getAxisScale() != null) map.setScaleColor(foregroundColor);
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the box color.
+   * @return A 3 element array of <CODE>float</CODE> values
+   *         in the range <CODE>[0.0f - 1.0f]</CODE>
+   *         in the order <I>(Red, Green, Blue)</I>.
+   */
+  public float[] getBoxColor()
+  {
+    return boxColor;
+  }
+
+  /**
+   * Get the box visibility.
+   * @return <CODE>true</CODE> if the box is visible.
+   */
+  public boolean getBoxOn()
+  {
+    return boxOn;
+  }
+
+  /**
+   * Set the box color.
+   * @param color box color
+   */
+  public void setBoxColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setBoxColor(r, g, b);
+  }
+
+  /**
+   * Set the box color.  All specified values should be in the range
+   * <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   */
+  public void setBoxColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    boxColor[0] = r;
+    boxColor[1] = g;
+    boxColor[2] = b;
+    changeControl(true);
+  }
+
+  /**
+   * Set the box visibility.
+   * @param on <CODE>true</CODE> if the box should be visible.
+   */
+  public void setBoxOn(boolean on)
+    throws RemoteException, VisADException
+  {
+    boxOn = on;
+    changeControl(true);
+  }
+
+  /**
+   * Get the cursor color.
+   * @return A 3 element array of <CODE>float</CODE> values
+   *         in the range <CODE>[0.0f - 1.0f]</CODE>
+   *         in the order <I>(Red, Green, Blue)</I>.
+   */
+  public float[] getCursorColor()
+  {
+    return cursorColor;
+  }
+
+  /**
+   * Set the cursor color.
+   * @param color cursor color
+   */
+  public void setCursorColor(Color color)
+    throws RemoteException, VisADException
+  {
+    final float r = (float )color.getRed() / 255.0f;
+    final float g = (float )color.getGreen() / 255.0f;
+    final float b = (float )color.getBlue() / 255.0f;
+    setCursorColor(r, g, b);
+  }
+
+  /**
+   * Set the cursor color.  All specified values should be in the range
+   * <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   */
+  public void setCursorColor(float r, float g, float b)
+    throws RemoteException, VisADException
+  {
+    cursorColor[0] = r;
+    cursorColor[1] = g;
+    cursorColor[2] = b;
+    changeControl(true);
+  }
+
+  /**
+   * Utility array used to compare to <CODE>float[]</CODE> arrays.
+   * @param one The first array.
+   * @param two The second array.
+   * @return <CODE>true</CODE> if the arrays are equal.
+   */
+  private static boolean floatArrayEquals(float[] one, float[] two)
+  {
+    if (one == null) {
+      if (two != null) {
+        return false;
+      }
+    } else if (two == null) {
+      return false;
+    } else if (one.length != two.length) {
+      return false;
+    } else {
+      for (int i = 0; i < one.length; i++) {
+        if (!Util.isApproximatelyEqual(one[i], two[i])) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /** get a string that can be used to reconstruct this control later */
+  public String getSaveString() {
+    return null;
+  }
+
+  /** reconstruct this control using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    throw new UnimplementedException(
+      "Cannot setSaveString on this type of control");
+  }
+
+  /**
+   * Copy the state of the specified control.
+   * @param ctl <CODE>Control</CODE> to copy.
+   */
+  public void syncControl(Control ctl)
+    throws VisADException
+  {
+    if (ctl == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(ctl instanceof RendererControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + ctl.getClass().getName());
+    }
+
+    RendererControl rc = (RendererControl )ctl;
+
+    boolean changed = false;
+
+    if (!floatArrayEquals(backgroundColor, rc.backgroundColor)) {
+      changed = true;
+      backgroundColor = rc.backgroundColor;
+    }
+    if (!floatArrayEquals(foregroundColor, rc.foregroundColor)) {
+      changed = true;
+      foregroundColor = rc.foregroundColor;
+    }
+    if (!floatArrayEquals(boxColor, rc.boxColor)) {
+      changed = true;
+      boxColor = rc.boxColor;
+    }
+    if (!floatArrayEquals(cursorColor, rc.cursorColor)) {
+      changed = true;
+      cursorColor = rc.cursorColor;
+    }
+
+    if (boxOn != rc.boxOn) {
+      changed = true;
+      boxOn = rc.boxOn;
+    }
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  /**
+   * Compare this object to another object.
+   * @param o Object to compare.
+   * @return <CODE>true</CODE> if this  object is "equal" to the
+   *         specified object.
+   */
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    RendererControl rc = (RendererControl )o;
+
+    if (!floatArrayEquals(backgroundColor, rc.backgroundColor)) {
+      return false;
+    }
+    if (!floatArrayEquals(foregroundColor, rc.foregroundColor)) {
+      return false;
+    }
+    if (!floatArrayEquals(boxColor, rc.boxColor)) {
+      return false;
+    }
+    if (!floatArrayEquals(cursorColor, rc.cursorColor)) {
+      return false;
+    }
+
+    if (boxOn != rc.boxOn) {
+      return false;
+    }
+
+    return true;
+  }
+
+  public Object clone()
+  {
+    RendererControl rc = (RendererControl )super.clone();
+    if (backgroundColor != null) {
+      rc.backgroundColor = (float[] )backgroundColor.clone();
+    }
+    if (foregroundColor != null) {
+      rc.foregroundColor = (float[] )foregroundColor.clone();
+    }
+    if (boxColor != null) {
+      rc.boxColor = (float[] )boxColor.clone();
+    }
+    if (cursorColor != null) {
+      rc.cursorColor = (float[] )cursorColor.clone();
+    }
+
+    return rc;
+  }
+
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("RendererControl[");
+
+    buf.append("bg=");
+    buf.append(backgroundColor[0]);
+    buf.append('/');
+    buf.append(backgroundColor[1]);
+    buf.append('/');
+    buf.append(backgroundColor[2]);
+
+    buf.append(",fg=");
+    buf.append(foregroundColor[0]);
+    buf.append('/');
+    buf.append(foregroundColor[1]);
+    buf.append('/');
+    buf.append(foregroundColor[2]);
+
+    buf.append(",cursor=");
+    buf.append(cursorColor[0]);
+    buf.append('/');
+    buf.append(cursorColor[1]);
+    buf.append('/');
+    buf.append(cursorColor[2]);
+
+    buf.append(",box=");
+    buf.append(boxColor[0]);
+    buf.append('/');
+    buf.append(boxColor[1]);
+    buf.append('/');
+    buf.append(boxColor[2]);
+
+    buf.append(',');
+    if (!boxOn) buf.append('!');
+    buf.append("boxOn");
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/RendererSourceListener.java b/visad/RendererSourceListener.java
new file mode 100644
index 0000000..778c883
--- /dev/null
+++ b/visad/RendererSourceListener.java
@@ -0,0 +1,29 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+public interface RendererSourceListener
+{
+  void rendererDeleted(DataRenderer renderer);
+  //void rendererAdded(DataRenderer renderer);
+}
diff --git a/visad/SI.java b/visad/SI.java
new file mode 100644
index 0000000..c4852c3
--- /dev/null
+++ b/visad/SI.java
@@ -0,0 +1,229 @@
+//
+// SI.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.io.Serializable;
+
+/**
+ * A class that represents the SI system of units.
+ *
+ * @author Steven R. Emmerson
+ *
+ * This is part of Steve Emerson's Unit package that has been
+ * incorporated into VisAD.
+ */
+public final class SI
+    implements Serializable
+{
+    /*
+     * The base units of the SI system of units:
+     */
+
+    /**
+     * Base unit of electric current.
+     * The ampere is that constant current which, if maintained
+     * in two straight parallel conductors of infinite length,
+     * of negligible circular cross section, and placed 1 meter
+     * apart in vacuum, would produce between these conductors a
+     * force equal to 2 x 10^-7 newton per meter of length.
+     */
+    public static /*final*/ BaseUnit	ampere;
+
+    /**
+     * Base unit of luminous intensity.
+     * The candela is the luminous intensity, in a given
+     * direction, of a source that emits monochromatic
+     * radiation of frequency 540 x 10^12 hertz and that has a
+     * radiant intensity in that direction of (1/683) watt per
+     * steradian.
+     */
+    public static /*final*/ BaseUnit	candela;
+
+    /**
+     * Base unit of thermodynamic temperature.
+     * The kelvin, unit of thermodynamic temperature, is the
+     * fraction 1/273.16 of the thermodynamic temperature of the
+     * triple point of water.
+     */
+    public static /*final*/ BaseUnit	kelvin;
+
+    /**
+     * Base unit of mass.
+     * The kilogram is the unit of mass; it is equal to the mass
+     * of the international prototype of the kilogram.
+     */
+    public static /*final*/ BaseUnit	kilogram;
+
+    /**
+     * Base unit of length.
+     * The meter is the length of the path travelled by light
+     * in vacuum during a time interval of 1/299 792 458 of a
+     * second.
+     */
+    public static /*final*/ BaseUnit	meter;
+
+    /**
+     * Base unit of time.
+     * The second is the duration of 9 192 631 770 periods of
+     * the radiation corresponding to the trasition between
+     * the two hyperfine levels of the ground state of the
+     * cesium-133 atom.
+     */
+    public static /*final*/ BaseUnit	second;
+
+    /**
+     * Base unit of amount of substance.
+     * The mole is the amount of substance of a system which
+     * contains as many elementary entities as there are atoms
+     * in 0.012 kilogram of carbon 12.
+     */
+    public static /*final*/ BaseUnit	mole;
+
+    /**
+     * Base unit of angular measure.
+     * The radian is the plane angle between two radii of a
+     * circle that cut off on the circumference an arc equal in
+     * length to the radius.  This unit is dimensionless.
+     */
+    public static /*final*/ BaseUnit	radian;
+
+    /**
+     * Base unit of solid angle.
+     * The steradian is the solid angle that, having its vertex
+     * in the center of a sphere, cuts off an area of the surface
+     * equal to that of a square with sides of length equal to the
+     * radius of the sphere.  This unit is dimensionless.
+     */
+    public static /*final*/ BaseUnit	steradian;
+
+    static
+    {
+	try
+	{
+	    /**
+	     * Base unit of electric current.
+	     * The ampere is that constant current which, if maintained
+	     * in two straight parallel conductors of infinite length,
+	     * of negligible circular cross section, and placed 1 meter
+	     * apart in vacuum, would produce between these conductors a
+	     * force equal to 2 x 10^-7 newton per meter of length.
+	     */
+	    ampere = BaseUnit.addBaseUnit("ElectricCurrent", "ampere", "A");
+
+	    /**
+	     * Base unit of luminous intensity.
+	     * The candela is the luminous intensity, in a given
+	     * direction, of a source that emits monochromatic
+	     * radiation of frequency 540 x 10^12 hertz and that has a
+	     * radiant intensity in that direction of (1/683) watt per
+	     * steradian.
+	     */
+	    candela =
+	      BaseUnit.addBaseUnit("LuminousIntensity", "candela", "cd");
+
+	    /**
+	     * Base unit of thermodynamic temperature.
+	     * The kelvin, unit of thermodynamic temperature, is the
+	     * fraction 1/273.16 of the thermodynamic temperature of the
+	     * triple point of water.
+	     */
+	    kelvin = BaseUnit.addBaseUnit("Temperature", "kelvin", "K");
+
+	    /**
+	     * Base unit of mass.
+	     * The kilogram is the unit of mass; it is equal to the mass
+	     * of the international prototype of the kilogram.
+	     */
+	    kilogram = BaseUnit.addBaseUnit("Mass", "kilogram", "kg");
+
+	    /**
+	     * Base unit of length.
+	     * The meter is the length of the path travelled by light
+	     * in vacuum during a time interval of 1/299 792 458 of a
+	     * second.
+	     */
+	    meter = BaseUnit.addBaseUnit("Length", "meter", "m");
+
+	    /**
+	     * Base unit of time.
+	     * The second is the duration of 9 192 631 770 periods of
+	     * the radiation corresponding to the trasition between
+	     * the two hyperfine levels of the ground state of the
+	     * cesium-133 atom.
+	     */
+	    second = BaseUnit.addBaseUnit("Time", "second", "s");
+
+	    /**
+	     * Base unit of amount of substance.
+	     * The mole is the amount of substance of a system which
+	     * contains as many elementary entities as there are atoms
+	     * in 0.012 kilogram of carbon 12.
+	     */
+	    mole = BaseUnit.addBaseUnit("AmountOfSubstance", "mole", "mol");
+
+	    /**
+	     * Base unit of angular measure.
+	     * The radian is the plane angle between two radii of a
+	     * circle that cut off on the circumference an arc equal in
+	     * length to the radius.  This unit is dimensionless.
+	     */
+	    radian = BaseUnit.addBaseUnit("Angle", "radian", "rad", true);
+
+	    /**
+	     * Base unit of solid angle.
+	     * The steradian is the solid angle that, having its vertex
+	     * in the center of a sphere, cuts off an area of the surface
+	     * equal to that of a square with sides of length equal to the
+	     * radius of the sphere.  This unit is dimensionless.
+	     */
+	    steradian =
+		BaseUnit.addBaseUnit("SolidAngle", "steradian", "sr", true);
+	}
+	catch (UnitException e) {}
+    }
+
+    private SI() {
+    }
+
+    /**
+     * Test this class.
+     *
+     * @param args		Arguments (ignored).
+     */
+    public static void main (String[] args)
+    {
+	System.out.println("ampere      = \"" + ampere + "\"");
+	System.out.println("candela     = \"" + candela + "\"");
+	System.out.println("kelvin      = \"" + kelvin + "\"");
+	System.out.println("kilogram    = \"" + kilogram + "\"");
+	System.out.println("meter       = \"" + meter + "\"");
+	System.out.println("second      = \"" + second + "\"");
+	System.out.println("mole        = \"" + mole + "\"");
+	System.out.println("radian      = \"" + radian + "\"");
+	System.out.println("steradian   = \"" + steradian + "\"");
+    }
+}
diff --git a/visad/SampledSet.java b/visad/SampledSet.java
new file mode 100644
index 0000000..3264b35
--- /dev/null
+++ b/visad/SampledSet.java
@@ -0,0 +1,709 @@
+//
+// SampledSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+
+import java.util.Arrays;
+
+
+/**
+ * SampledSet is the abstract superclass of GriddedSets, PolyCells and MultiCells.
+ * SampledSet objects are intended to be immutable (but see {@link
+ * #getSamples(boolean)} for an exception).
+ */
+public abstract class SampledSet extends SimpleSet implements SampledSetIface {
+
+  /**           */
+  static int cnt = 0;
+
+  /**           */
+  int mycnt = cnt++;
+
+  /**           */
+  private static int cacheSizeThreshold = -1;
+
+
+
+
+  /**           */
+  private Object cacheId;
+
+  /**           */
+  float[][] Samples;
+
+  /**           */
+  float Low[], Hi[];
+
+  /**
+   * 
+   *
+   * @param type 
+   * @param manifold_dimension 
+   *
+   * @throws VisADException 
+   */
+  public SampledSet(MathType type, int manifold_dimension)
+          throws VisADException {
+    super(type, manifold_dimension);
+  }
+
+  /**
+   * 
+   *
+   * @param type 
+   * @param manifold_dimension 
+   * @param coord_sys 
+   * @param units 
+   * @param errors 
+   *
+   * @throws VisADException 
+   */
+  public SampledSet(MathType type, int manifold_dimension,
+                    CoordinateSystem coord_sys, Unit[] units,
+                    ErrorEstimate[] errors)
+          throws VisADException {
+    super(type, manifold_dimension, coord_sys, units, errors);
+    Low = new float[DomainDimension];
+    Hi = new float[DomainDimension];
+  }
+
+  /**
+   * 
+   *
+   * @param type 
+   *
+   * @throws VisADException 
+   */
+  public SampledSet(MathType type) throws VisADException {
+    this(type, null, null, null);
+  }
+
+  /**
+   * 
+   *
+   * @param type 
+   * @param coord_sys 
+   * @param units 
+   * @param errors 
+   *
+   * @throws VisADException 
+   */
+  public SampledSet(MathType type, CoordinateSystem coord_sys, Unit[] units,
+                    ErrorEstimate[] errors)
+          throws VisADException {
+    super(type, coord_sys, units, errors);
+    Low = new float[DomainDimension];
+    Hi = new float[DomainDimension];
+  }
+
+
+  /**
+   * 
+   *
+   * @throws Throwable 
+   */
+  public void finalize() throws Throwable {
+    if (cacheId != null) {
+      // System.err.println ("sampled set finalize");
+      visad.data.DataCacheManager.getCacheManager().removeFromCache(cacheId);
+    }
+    super.finalize();
+  }
+
+
+  /**
+   * 
+   *
+   * @param threshold 
+   */
+  public static void setCacheSizeThreshold(int threshold) {
+    cacheSizeThreshold = threshold;
+  }
+
+
+  /**
+   * 
+   *
+   * @param samples 
+   */
+  protected void setMySamples(float[][] samples) {
+    if (cacheSizeThreshold >= 0 && samples != null && samples.length > 0 &&
+        samples[0].length > cacheSizeThreshold) {
+      if (cacheId != null) {
+        visad.data.DataCacheManager.getCacheManager().updateData(
+          cacheId, samples);
+      }
+      else {
+        cacheId =
+            visad.data.DataCacheManager.getCacheManager().addToCache(getClass().getSimpleName(), samples);
+      }
+      //      visad.data.DataCacheManager.getCacheManager().printStats();
+      return;
+    }
+    this.Samples = samples;
+  }
+
+
+  /**
+   * 
+   *
+   * @return samples
+   */
+  protected float[][] getMySamples() {
+    if (cacheId != null) {
+      return visad.data.DataCacheManager.getCacheManager().getFloatArray2D(
+               cacheId);
+    }
+    return Samples;
+  }
+
+  /**
+   * 
+   *
+   * @param samples 
+   *
+   * @throws VisADException 
+   */
+  void init_samples(float[][] samples) throws VisADException {
+    init_samples(samples, true);
+  }
+
+  /**
+   * 
+   *
+   * @param samples 
+   * @param copy 
+   *
+   * @throws VisADException 
+   */
+  void init_samples(float[][] samples, boolean copy) throws VisADException {
+    if (samples.length != DomainDimension) {
+      throw new SetException("SampledSet.init_samples: " +
+                             "sample dimension " + samples.length +
+                             " doesn't match expected length " +
+                             DomainDimension);
+    }
+    if (Length == 0) {
+      // Length set in init_lengths, but not called for IrregularSet
+      Length = samples[0].length;
+    }
+    else {
+      if (Length != samples[0].length) {
+        throw new SetException("SampledSet.init_samples: " +
+                               "sample#0 length " + samples[0].length +
+                               " doesn't match expected length " + Length);
+      }
+    }
+    // MEM
+    float[][] mySamples;
+
+    if (copy) {
+      mySamples = new float[DomainDimension][Length];
+    }
+    else {
+      mySamples = samples;
+    }
+    for (int j = 0; j < DomainDimension; j++) {
+      if (samples[j].length != Length) {
+        throw new SetException("SampledSet.init_samples: " + "sample#" + j +
+                               " length " + samples[j].length +
+                               " doesn't match expected length " + Length);
+      }
+      float[] samplesJ = samples[j];
+      float[] SamplesJ = mySamples[j];
+      if (copy) {
+        System.arraycopy(samplesJ, 0, SamplesJ, 0, Length);
+      }
+      Low[j] = Float.POSITIVE_INFINITY;
+      Hi[j] = Float.NEGATIVE_INFINITY;
+      float sum = 0.0f;
+      for (int i = 0; i < Length; i++) {
+/* WLH 4 May 99
+        if (SamplesJ[i] != SamplesJ[i]) {
+          throw new SetException(
+                     "SampledSet.init_samples: sample values cannot be missing");
+        }
+        if (Float.isInfinite(SamplesJ[i])) {
+          throw new SetException(
+                     "SampledSet.init_samples: sample values cannot be infinite");
+        }
+        if (SamplesJ[i] < Low[j]) Low[j] = SamplesJ[i];
+        if (SamplesJ[i] > Hi[j]) Hi[j] = SamplesJ[i];
+*/
+        if (SamplesJ[i] == SamplesJ[i] && !Float.isInfinite(SamplesJ[i])) {
+          if (SamplesJ[i] < Low[j]) Low[j] = SamplesJ[i];
+          if (SamplesJ[i] > Hi[j]) Hi[j] = SamplesJ[i];
+        }
+        else {
+          SamplesJ[i] = Float.NaN;
+        }
+        sum += SamplesJ[i];
+      }
+      if (SetErrors[j] != null) {
+        SetErrors[j] = new ErrorEstimate(SetErrors[j].getErrorValue(),
+                                         sum / Length, Length,
+                                         SetErrors[j].getUnit());
+      }
+    }
+    setMySamples(mySamples);
+  }
+
+  /**
+   * 
+   *
+   * @param range_select 
+   */
+  public void cram_missing(boolean[] range_select) {
+    float[][] mySamples = getMySamples();
+    int n = Math.min(range_select.length, mySamples[0].length);
+    for (int i = 0; i < n; i++) {
+      if (!range_select[i]) mySamples[0][i] = Float.NaN;
+    }
+    setMySamples(mySamples);
+  }
+
+  /**
+   * 
+   *
+   * @param samples 
+   */
+  void cram_samples(float[][] samples) {
+    setMySamples(samples);
+  }
+
+
+  /**
+   * 
+   *
+   * @param neighbors 
+   * @param weights 
+   *
+   * @throws VisADException 
+   */
+  public void getNeighbors(int[][] neighbors, float[][] weights)
+          throws VisADException {
+    getNeighbors(neighbors);
+
+    int n_points;
+    float distance;
+    float distance_squared;
+    float diff;
+    float lambda_squared;
+    float constant = 4f;
+    float pi_squared = (float)(Math.PI * Math.PI);
+
+    float[][] mySamples = getMySamples();
+    float[][] samples = (mySamples != null)
+                        ? mySamples
+                        : getSamples();
+
+    for (int ii = 0; ii < Length; ii++) {
+      n_points = neighbors[ii].length;
+      weights[ii] = new float[n_points];
+
+      for (int kk = 0; kk < n_points; kk++) {
+        distance_squared = 0f;
+        for (int tt = 0; tt < DomainDimension; tt++) {
+          diff = samples[tt][ii] - samples[tt][neighbors[ii][kk]];
+          distance_squared += diff * diff;
+        }
+        lambda_squared = (distance_squared * constant) / pi_squared;
+
+        weights[ii][kk] = (float)Math.exp((double)(-1f *
+                (distance_squared / lambda_squared)));
+      }
+    }
+  }
+
+  /**
+   * 
+   *
+   * @return true or false
+   */
+  public boolean isMissing() {
+    return (getMySamples() == null);
+  }
+
+  /**
+   * <p>Returns a copy of the samples of this instance.  Element <code>[i][j]
+   * </code> of the returned array is the <code>j</code>-th value of the
+   * <code>i</code>-th component.</p>
+   *
+   * <p>This method is equivalent to <code>getSamples(true)</code>.</p>
+   *
+   * @return                     A copy of the sample array.
+   * @see #getSamples(boolean)
+   *
+   * @throws VisADException 
+   */
+  public float[][] getSamples() throws VisADException {
+    return getSamples(true);
+  }
+
+  /**
+   * <p>Returns the samples of this instance or a copy of the samples.</p>
+   *
+   * <p>Note that, if the actual sample array is returned, then it is possible
+   * to modify the values of this instance -- breaking the immutability aspect
+   * of this class.  Don't do this unless you enjoy debugging.</p>
+   *
+   * @param copy                 Whether or not a copy of the sample array
+   *                             should be returned.
+   * @return                     The sample array is <code>copy</code> is <code>
+   *                             false; otherwise, a copy of the sample array.
+   *
+   * @throws VisADException 
+   */
+  public float[][] getSamples(boolean copy) throws VisADException {
+    float[][] mySamples = getMySamples();
+    return copy
+           ? Set.copyFloats(mySamples)
+           : mySamples;
+  }
+
+  /**
+   * 
+   *
+   * @param type 
+   * @param shadow 
+   *
+   * @return DataShadow
+   *
+   * @throws VisADException 
+   */
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+          throws VisADException {
+    int n = getDimension();
+    double[][] ranges = new double[2][n];
+    return computeRanges(type, shadow, ranges, false);
+  }
+
+  /**
+   * 
+   *
+   * @param type 
+   * @param shadow 
+   * @param ranges 
+   * @param domain 
+   *
+   * @return DataShadow
+   *
+   * @throws VisADException 
+   */
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow,
+                                  double[][] ranges, boolean domain)
+          throws VisADException {
+    if (isMissing()) return shadow;
+    setAnimationSampling(type, shadow, domain);
+
+    int[] indices = new int[DomainDimension];
+    for (int i = 0; i < DomainDimension; i++) {
+      ShadowRealType real = null;
+      if (type instanceof ShadowSetType) {
+        real =
+          (ShadowRealType)((ShadowSetType)type).getDomain().getComponent(i);
+      }
+      else if (type instanceof ShadowRealTupleType) {
+        real = (ShadowRealType)((ShadowRealTupleType)type).getComponent(i);
+      }
+      else {
+        throw new TypeException("SampledSet.computeRanges: bad ShadowType " +
+                                type.getClass().getName());
+      }
+      indices[i] = real.getIndex();
+    }
+
+    for (int i = 0; i < DomainDimension; i++) {
+      int k = indices[i];
+      double min = Low[i];
+      double max = Hi[i];
+      Unit dunit = ((RealType)((SetType)Type).getDomain().getComponent(
+                     i)).getDefaultUnit();
+      if (dunit != null && !dunit.equals(SetUnits[i])) {
+        min = dunit.toThis(min, SetUnits[i]);
+        max = dunit.toThis(max, SetUnits[i]);
+      }
+      if (ranges != null) {
+        ranges[0][i] = min;
+        ranges[1][i] = max;
+      }
+      if (k >= 0 && k < shadow.ranges[0].length) {
+        shadow.ranges[0][k] = Math.min(shadow.ranges[0][k], min);
+        shadow.ranges[1][k] = Math.max(shadow.ranges[1][k], max);
+      }
+    }
+
+    /* WLH 1 March 98 - moved from FieldImpl and FlatField computeRanges */
+    ShadowRealTupleType domain_type = null;
+    if (type instanceof ShadowRealTupleType) {
+      domain_type = (ShadowRealTupleType)type;
+    }
+    else if (type instanceof ShadowSetType) {
+      domain_type = ((ShadowSetType)type).getDomain();
+    }
+    if (domain_type != null && ranges != null) {
+      ShadowRealTupleType shad_ref = domain_type.getReference();
+      if (shad_ref != null) {
+        // computeRanges for Reference (relative to domain) RealTypes
+        // WLH 20 Nov 2001
+        shadow = computeReferenceRanges(
+          domain_type, DomainCoordinateSystem,
+          ((SetType)Type).getDomain().getDefaultUnits(), shadow, shad_ref,
+          ranges);
+        // SetUnits, shadow, shad_ref, ranges);
+      }
+    }
+
+    return shadow;
+  }
+
+  /**
+   * create a 1-D GeometryArray from this Set and color_values;
+   *   only used by Irregular3DSet and Gridded3DSet 
+   *
+   * @param color_values 
+   *
+   * @return new VisADGeometryArray
+   *
+   * @throws VisADException 
+   */
+  public VisADGeometryArray make1DGeometry(byte[][] color_values)
+          throws VisADException {
+    if (DomainDimension != 3) {
+      throw new SetException("SampledSet.make1DGeometry: " +
+                             "DomainDimension must be 3, not " +
+                             DomainDimension);
+    }
+    if (ManifoldDimension != 1) {
+      throw new SetException("SampledSet.make1DGeometry: " +
+                             "ManifoldDimension must be 1, not " +
+                             ManifoldDimension);
+    }
+    VisADGeometryArray array = null;
+    if (Length == 0) {
+      return null;
+    }
+    else if (Length == 1) {
+      array = new VisADPointArray();
+    }
+    else {
+      array = new VisADLineStripArray();
+      ((VisADLineStripArray)array).stripVertexCounts = new int[1];
+      ((VisADLineStripArray)array).stripVertexCounts[0] = Length;
+    }
+    // set coordinates and colors
+    setGeometryArray(array, 4, color_values);
+    return array;
+  }
+
+  /**
+   * create a 3-D GeometryArray from this Set and color_values;
+   *   NOTE - this version only makes points;
+   *   NOTE - when textures are supported by Java3D the Gridded3DSet
+   *   implementation of make3DGeometry should use Texture3D, and
+   *   the Irregular3DSet implementation should resample to a
+   *   Gridded3DSet and use Texture3D;
+   *   only used by Irregular3DSet and Gridded3DSet 
+   *
+   * @param color_values 
+   *
+   * @return arrays of VisADGeometryArray
+   *
+   * @throws VisADException 
+   */
+  public VisADGeometryArray[] make3DGeometry(byte[][] color_values)
+          throws VisADException {
+    if (ManifoldDimension != 3) {
+      throw new SetException("SampledSet.make3DGeometry: " +
+                             "ManifoldDimension must be 3, not " +
+                             ManifoldDimension);
+    }
+    VisADGeometryArray array = makePointGeometry(color_values);
+    return new VisADGeometryArray[] {array, array, array};
+  }
+
+  /**
+   * create a PointArray from this Set and color_values;
+   *   can be applied to  ManifoldDimension = 1, 2 or 3 
+   *
+   * @param color_values 
+   *
+   * @return VisADGeometryArray
+   *
+   * @throws VisADException 
+   */
+  public VisADGeometryArray makePointGeometry(byte[][] color_values)
+          throws VisADException {
+    if (DomainDimension != 3) {
+      throw new SetException("SampledSet.makePointGeometry: " +
+                             "DomainDimension must be 3, not " +
+                             DomainDimension);
+    }
+    VisADPointArray array = new VisADPointArray();
+    // set coordinates and colors
+    setGeometryArray(array, 4, color_values);
+    return array;
+  }
+
+  /**
+   * copy and transpose Samples (from this Set( and color_values
+   *   into array; if color_length == 3 don't use color_values[3] 
+   *
+   * @param array 
+   * @param color_length 
+   * @param color_values 
+   *
+   * @throws VisADException 
+   */
+  public void setGeometryArray(VisADGeometryArray array, int color_length,
+                               byte[][] color_values)
+          throws VisADException {
+    setGeometryArray(array, getSamples(false), color_length, color_values);
+  }
+
+  /**
+   * copy and transpose samples and color_values into array;
+   *   if color_length == 3 don't use color_values[3] 
+   *
+   * @param array 
+   * @param samples 
+   * @param color_length 
+   * @param color_values 
+   *
+   * @throws VisADException 
+   */
+  public static void setGeometryArray(VisADGeometryArray array,
+                                      float[][] samples, int color_length,
+                                      byte[][] color_values)
+          throws VisADException {
+    if (samples == null) {
+      throw new SetException("SampledSet.setGeometryArray: " +
+                             "Null samples array");
+    }
+    else if (samples.length != 3) {
+      throw new SetException("SampledSet.setGeometryArray: " +
+                             "Expected 3 dimensions in samples array, not " +
+                             samples.length);
+    }
+    int len = samples[0].length;
+    array.vertexCount = len;
+    // MEM
+    float[] coordinates = new float[3 * len];
+    int j = 0;
+    for (int i = 0; i < len; i++) {
+      coordinates[j++] = samples[0][i];
+      coordinates[j++] = samples[1][i];
+      coordinates[j++] = samples[2][i];
+    }
+
+    array.coordinates = coordinates;
+    if (color_values != null) {
+      color_length = Math.min(color_length, color_values.length);
+      // MEM
+      byte[] colors = new byte[color_length * len];
+      j = 0;
+      if (color_length == 4) {
+        for (int i = 0; i < len; i++) {
+          colors[j++] = color_values[0][i];
+          colors[j++] = color_values[1][i];
+          colors[j++] = color_values[2][i];
+          colors[j++] = color_values[3][i];
+          // colors[j++] = (1.0f - color_values[3][i]);
+        }
+      }
+      else if (color_length == 3) {
+        for (int i = 0; i < len; i++) {
+          colors[j++] = color_values[0][i];
+          colors[j++] = color_values[1][i];
+          colors[j++] = color_values[2][i];
+        }
+      }
+
+      // BMF 2009-03-17
+      // this addresses a issue where a 2D display is used without color
+      // mapping
+      else if (color_length == 0) {
+        colors = null;
+      }
+      else {
+        throw new SetException("SampledSet.setGeometryArray: " +
+                               "color_length must be 0, 3 or 4, not " +
+                               color_length);
+      }
+
+      array.colors = colors;
+    }
+  }
+
+  /**
+   * 
+   *
+   * @return array of low values
+   */
+  public float[] getLow() {
+    float[] low = new float[Low.length];
+    for (int i = 0; i < Low.length; i++)
+      low[i] = Low[i];
+    return low;
+  }
+
+  /**
+   * 
+   *
+   * @return array of hi values
+   */
+  public float[] getHi() {
+    float[] hi = new float[Hi.length];
+    for (int i = 0; i < Hi.length; i++)
+      hi[i] = Hi[i];
+    return hi;
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                    A clone of this instance.
+   */
+  public Object clone() {
+    SampledSet clone = (SampledSet)super.clone();
+    if (clone.cacheId != null) {
+      clone.cacheId = null;
+    }
+
+    /*
+     * The array of sample values is cloned because getSamples(false) allows
+     * clients to modify the values and the clone() general contract forbids
+     * cross-clone effects.
+     */
+    float[][] mySamples = getMySamples();
+    if (mySamples != null) {
+      clone.setMySamples(visad.util.Util.clone(mySamples));
+    }
+    return clone;
+  }
+}
+
diff --git a/visad/SampledSetIface.java b/visad/SampledSetIface.java
new file mode 100644
index 0000000..b72225f
--- /dev/null
+++ b/visad/SampledSetIface.java
@@ -0,0 +1,50 @@
+//
+// SetIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Interface to the abstract superclass of GriddedSet-s and the like.
+ */
+public interface SampledSetIface
+  extends SimpleSetIface
+{
+  /**
+   * Returns the minimum sample values.
+   *
+   * @return			The minimum sample values.  Element [i] is the
+   *				minimum value for dimension <code>i</code>.
+   */
+  float[] getLow();
+
+  /**
+   * Returns the maximum sample values.
+   *
+   * @return			The maximum sample values.  Element [i] is the
+   *				maximum value for dimension <code>i</code>.
+   */
+  float[] getHi();
+}
diff --git a/visad/Scalar.java b/visad/Scalar.java
new file mode 100644
index 0000000..c61e7a5
--- /dev/null
+++ b/visad/Scalar.java
@@ -0,0 +1,86 @@
+//
+// Scalar.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Scalar is the superclass of the VisAD hierarchy of scalar data.<P>
+ */
+public abstract class Scalar
+  extends	DataImpl
+  implements	ScalarIface, Comparable
+{
+
+  public Scalar(ScalarType type) {
+    super(type);
+  }
+
+  /**
+   * Adds a listener for changes to this instance.  Because instances of this
+   * class don't change, this method does nothing.
+   *
+   * @param listener                     The listener for changes.
+   */
+  public final void addReference(ThingReference listener) {
+  }
+
+  /**
+   * Removes a listener for changes to this instance.  Because instances of this
+   * class don't change, this method does nothing.
+   *
+   * @param listener                    The change listener to be removed.
+   */
+  public final void removeReference(ThingReference listener) {
+  }
+
+  /**
+   * Indicates if this scalar is semantically identical to an object.
+   * @param obj			The object.
+   * @return			<code>true</code> if and only if this scalar
+   *				is semantically identical to the object.
+   */
+  public abstract boolean equals(Object obj);
+
+  /**
+   * Clones this instance.
+   *
+   * @return                      A clone of this instance.
+   */
+  public final Object clone() {
+      /*
+       * Steve Emmerson believes that this implementation should return
+       * "this" to reduce resouce-usage but Bill believes that doing so is
+       * counter-intuitive and might harm applications.
+       */
+    try {
+      return super.clone();
+    }
+    catch (CloneNotSupportedException ex) {
+      throw new RuntimeException("Assertion failure");
+    }
+  }
+}
+
diff --git a/visad/ScalarIface.java b/visad/ScalarIface.java
new file mode 100644
index 0000000..1aafbfd
--- /dev/null
+++ b/visad/ScalarIface.java
@@ -0,0 +1,43 @@
+//
+// ScalarIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Interface to the VisAD hierarchy of scalar data.
+ */
+public interface ScalarIface
+  extends Data
+{
+  /**
+   * Indicates if this instance is semantically identical to an object.
+   *
+   * @param obj			The object.
+   * @return			<code>true</code> if and only if this instance
+   *				is semantically identical to the object.
+   */
+  boolean equals(Object obj);
+}
diff --git a/visad/ScalarMap.java b/visad/ScalarMap.java
new file mode 100644
index 0000000..aa8e227
--- /dev/null
+++ b/visad/ScalarMap.java
@@ -0,0 +1,1425 @@
+//
+// ScalarMap.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.*;
+
+/**
+   A ScalarMap object defines a mapping from a RealType
+   to a DisplayRealType.  A set of ScalarMap objects
+   define how data are dislayed.<P>
+
+   The mapping of values is linear.  Any non-linear mapping
+   must be handled by Display CoordinateSystem-s.<P>
+*/
+public class ScalarMap extends Object
+        implements Cloneable, java.io.Serializable, Comparable {
+
+  // WLH 31 Aug 2000
+  // display Unit to use rather than Scalar default Unit
+  private Unit overrideUnit = null;
+  // scale and offset for converting from Scalar default Unit to overrideUnit
+  private double override_scale, override_offset;
+
+  private ScalarType Scalar;
+  private DisplayRealType DisplayScalar;
+
+  // index into Display.RealTypeVector
+  private int ScalarIndex;
+  // index into Display.DisplayRealTypeVector
+  private int DisplayScalarIndex;
+  // index into ValueArray
+  int ValueIndex;
+
+  // control associated with DisplayScalar, or null
+  private transient Control control;
+  // unique Display this ScalarMap is part of
+  private transient DisplayImpl display;
+
+  /** true if dataRange set by application;
+      disables automatic setting */
+  private boolean isManual;
+
+  /** true if Scalar values need to be scaled */
+  boolean isScaled;
+  /** ranges of values of DisplayScalar */
+  double[] displayRange = new double[2];
+
+  /** ranges of values of Scalar */
+  private double[] dataRange = new double[2];
+  
+  /** ranges of values of Scalar in default units*/
+  private double[] defaultUnitRange = new double[2];
+
+  /** scale and offset */
+  private double scale, offset;
+
+  /** incremented by incTick */
+  private long NewTick;
+  /** value of NewTick at last setTicks call */
+  private long OldTick;
+  /** set by setTicks if OldTick < NewTick; cleared by resetTicks */
+  private boolean tickFlag;
+
+  private String scalarName = null;
+
+  /** location of axis scale if DisplayScalar is XAxis, YAxis or ZAxis 
+  private int axis = -1;
+  private int axis_ordinal = -1;
+   < removed for AxisScale 10-Oct-2000 > */
+  private boolean scale_flag = false;
+  private boolean back_scale_flag = false;
+  //private float[] scale_color = {1.0f, 1.0f, 1.0f}; <DRM 10-Oct-2000>
+  private boolean scale_on = true;
+  private boolean underscore_to_blank = false;
+
+  /** Vector of ScalarMapListeners */
+  private transient Vector ListenerVector = new Vector();
+
+  /** AxisScale */
+  private AxisScale axisScale = null;  // added DRM 10-Oct-2000
+
+  /**
+   * Construct a <CODE>ScalarMap</CODE> that maps the scalar to
+   * the display_scalar.
+   * @param  scalar  ScalarType (must be RealType at present)
+   * @param  display_scalar   DisplayScalar to map to.  If the
+   *                          display_scalar is one of the spatial
+   *                          axes (X, Y, Z) an AxisScale will be
+   *                          created.
+   * @throws VisADException   VisAD error
+   */
+  public ScalarMap(ScalarType scalar, DisplayRealType display_scalar)
+         throws VisADException {
+    this(scalar, display_scalar, true);
+  }
+
+  ScalarMap(ScalarType scalar, DisplayRealType display_scalar,
+            boolean needNonNullScalar)
+         throws VisADException {
+    if (scalar == null && needNonNullScalar) {
+      throw new DisplayException("ScalarMap: scalar is null");
+    }
+    if (display_scalar == null) {
+      throw new DisplayException("ScalarMap: display_scalar is null");
+    }
+    if (display_scalar.equals(Display.List)) {
+      throw new DisplayException("ScalarMap: display_scalar may not be List");
+    }
+    boolean text = display_scalar.getText();
+    if (scalar != null) {
+/* WLH 15 June 2000
+      if (text && !(scalar instanceof TextType)) {
+        throw new DisplayException("ScalarMap: RealType scalar cannot be " +
+                                   "used with TextType display_scalar");
+      }
+*/
+      if (!text && !(scalar instanceof RealType)) {
+        throw new DisplayException("ScalarMap: TextType scalar cannot be " +
+                                   "used with RealType display_scalar");
+      }
+    }
+    control = null;
+    Scalar = scalar;
+    DisplayScalar = display_scalar;
+    display = null;
+    ScalarIndex = -1;
+    DisplayScalarIndex = -1;
+    isScaled = DisplayScalar.getRange(displayRange);
+    isManual = false;
+    dataRange[0] = Double.NaN;
+    dataRange[1] = Double.NaN;
+    defaultUnitRange[0] = dataRange[0];
+    defaultUnitRange[1] = dataRange[1];
+    OldTick = Long.MIN_VALUE;
+    NewTick = Long.MIN_VALUE + 1;
+    tickFlag = false;
+    if (Scalar != null) scalarName = Scalar.getName();
+    if (DisplayScalar.equals(Display.XAxis) ||
+        DisplayScalar.equals(Display.YAxis) ||
+        DisplayScalar.equals(Display.ZAxis)) {
+        axisScale = new AxisScale(this);
+    }
+  }
+
+  /** re-enable auto-scaling for this ScalarMap */
+  public void resetAutoScale() {
+    isManual = false;
+  }
+
+  /** disable auto-scaling for this ScalarMap */
+  public void disableAutoScale() {
+    isManual = true;
+  }
+
+  /** determine whether this ScalarMap is auto-scaled */
+  public boolean isAutoScale() {
+    return !isManual;
+  }
+
+  // WLH 22 August 2001
+  public boolean doInitialize() {
+    if (DisplayScalar.equals(Display.IsoContour)) {
+      if (control != null) {
+        float[] lowhibase = new float[3];
+        boolean[] dashes = new boolean[1];
+        float[] levs =
+          ((ContourControl) control).getLevels(lowhibase, dashes);
+        return (levs == null);
+      }
+      else {
+        return false;
+      }
+    }
+    else {
+      return isScaled && !isManual;
+    }
+  }
+
+  // WLH 31 Aug 2000
+  /** 
+   * Set display Unit to override default Unit of Scalar;
+   *  MUST be called before any data are displayed 
+   * @param  unit  unit that data will be displayed with
+   * @throws  VisADException  <CODE>unit</CODE> is not convertable with
+   *                          the default unit or scalar is not a RealType.
+   */
+  public void setOverrideUnit(Unit unit) throws VisADException {
+    if (!(Scalar instanceof RealType)) {
+      throw new UnitException("Scalar is not RealType");
+    }
+    Unit rtunit = ((RealType) Scalar).getDefaultUnit();
+    if (!Unit.canConvert(unit, rtunit)) {
+      throw new UnitException("unit not convertable with RealType default");
+    }
+    if (unit != null) {
+      overrideUnit = unit;
+      override_offset = overrideUnit.toThis(0.0, rtunit);
+      override_scale = overrideUnit.toThis(1.0, rtunit) - override_offset;
+    }
+  }
+
+  // WLH 31 Aug 2000
+  /**
+   * Return the override unit.
+   * @return  Unit being used in the display.
+   */
+  public Unit getOverrideUnit() {
+    return overrideUnit;
+  }
+
+  /**
+   * Get the name being used on the axis scale.
+   * @return  name of the scale - either the default or the one set by
+   *          <CODE>setScalarName</CODE>
+   * @see  #setScalarName(String name)
+   */
+  public String getScalarName() {
+    return scalarName;
+  }
+
+  /**
+   * Set the name being used on the axis scale.
+   * @param name	new name for the scalar.
+   * @see  AxisScale#setTitle(String name)
+   */
+  public void setScalarName(String name) {
+    scalarName = name;
+    if (axisScale != null) axisScale.setTitle(scalarName);
+  }
+
+  /** invoke incTick on every application call to setRange */
+  public long incTick() {
+    // WLH 19 Feb 2001 - move to after increment NewTick
+    // if (display != null) display.controlChanged();
+    NewTick += 1;
+    if (NewTick == Long.MAX_VALUE) NewTick = Long.MIN_VALUE + 1;
+/*
+System.out.println(Scalar + " -> " + DisplayScalar +
+                   "  incTick = " + NewTick);
+*/
+    if (display != null) display.controlChanged();
+    return NewTick;
+  }
+
+  /** set tickFlag according to OldTick and NewTick */
+  public synchronized void setTicks() {
+    tickFlag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+/*
+System.out.println(Scalar + " -> " + DisplayScalar +
+                   "  set  tickFlag = " + tickFlag);
+*/
+    OldTick = NewTick;
+    if (control != null) control.setTicks();
+  }
+
+  public synchronized boolean peekTicks(DataRenderer r, DataDisplayLink link) {
+    if (control == null) {
+/*
+boolean flag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+if (flag) {
+  System.out.println(Scalar + " -> " + DisplayScalar + "  peek  flag = " + flag);
+}
+*/
+      return (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+    }
+    else {
+/*
+boolean flag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick));
+boolean cflag = control.peekTicks(r, link);
+if (flag || cflag) {
+  System.out.println(Scalar + " -> " + DisplayScalar + "  peek   flag = " +
+                     flag + " cflag = " + cflag);
+}
+*/
+      return (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)) ||
+             control.peekTicks(r, link);
+    }
+  }
+
+  /** return true if application called setRange */
+  public synchronized boolean checkTicks(DataRenderer r, DataDisplayLink link) {
+    if (control == null) {
+/*
+System.out.println(Scalar + " -> " + DisplayScalar + "  check  tickFlag = " +
+                   tickFlag);
+*/
+      return tickFlag;
+    }
+    else {
+/*
+boolean cflag = control.checkTicks(r, link);
+System.out.println(Scalar + " -> " + DisplayScalar + "  check  tickFlag = " +
+                   tickFlag + " cflag = " + cflag);
+*/
+      return tickFlag || control.checkTicks(r, link);
+    }
+  }
+
+  /** reset tickFlag */
+  synchronized void resetTicks() {
+// System.out.println(Scalar + " -> " + DisplayScalar + "  reset");
+    tickFlag = false;
+    if (control != null) control.resetTicks();
+  }
+
+  /** 
+   * Get the ScalarType that is the map domain 
+   * @return  ScalarType of map domain
+   */
+  public ScalarType getScalar() {
+    return Scalar;
+  }
+
+  /** 
+   * Get the DisplayRealType that is the map range 
+   * @return  DisplayRealType of map range
+   */
+  public DisplayRealType getDisplayScalar() {
+    return DisplayScalar;
+  }
+
+  /** 
+   * Get the DisplayImpl this ScalarMap is linked to 
+   * @return  display that this ScalarMap is linked to
+   */
+  public DisplayImpl getDisplay() {
+    return display;
+  }
+
+  /**
+   * Clear the link to the VisAD display.  This will subsequently
+   * cause {@link #getDisplay()} and {@link #getControl()} to return
+   * <code>null</code>; consequently, information stored in the Control
+   * might have to be reestablished.  This method invokes the method {@link
+   * ScalarMapListener#controlChanged(ScalarMapControlEvent)} on all registered
+   * {@link ScalarMapListener}s with this instance as the event source, {@link
+   * ScalarMapEvent#CONTROL_REMOVED} as the event ID, and the control as the
+   * event control.
+   *
+   * @throws RemoteException    Java RMI failure
+   * @throws VisADException     VisAD failure
+   */
+  synchronized void nullDisplay()
+    throws RemoteException, VisADException
+  {
+    if (control != null) {
+      control.nullControl();
+      ScalarMapControlEvent evt;
+      evt = new ScalarMapControlEvent(this, ScalarMapEvent.CONTROL_REMOVED,
+                                      control);
+      notifyCtlListeners(evt);
+    }
+    control = null;
+
+    display = null;
+    ScalarIndex = -1;
+    DisplayScalarIndex = -1;
+    scale_flag = back_scale_flag;
+
+    if (axisScale != null) axisScale.setAxisOrdinal(-1);
+  }
+
+  /** 
+   * Set the DisplayImpl this ScalarMap is linked to 
+   * @param  d   display to link to
+   * @throws  VisADException  map is already linked to a DisplayImpl or
+   *                          other VisAD error
+   */
+  synchronized void setDisplay(DisplayImpl d)
+               throws VisADException {
+    if (d.equals(display)) return;
+    if (display != null) {
+      throw new DisplayException("ScalarMap.setDisplay: ScalarMap cannot belong" +
+                                 " to two Displays");
+    }
+    display = d;
+    if (scale_flag) makeScale();
+// System.out.println("setDisplay " + Scalar + " -> " + DisplayScalar);
+    // WLH 27 Nov 2000
+    if (!(this instanceof ConstantMap)) {
+      ProjectionControl pcontrol = display.getProjectionControl();
+      try {
+        setAspectCartesian(pcontrol.getAspectCartesian());
+      }
+      catch (RemoteException e) {
+      }
+    }
+  }
+
+  /**
+   * Gets the Control for the DisplayScalar.  The Control is constructed when
+   * this ScalarMap is linked to a Display via an invocation of the {@link
+   * Display#addMap(ScalarMap)} method.  Not all ScalarMaps have Controls,
+   * generally depending on the ScalarMap's DisplayRealType.  If a ScalarMap is
+   * removed from a Display (via the {@link Display#clearMaps()} method, then,
+   * in general, any information in the ScalarMap's control will be lost and
+   * must be reestablished.
+   *
+   * @return                    The Control for the DisplayScalar or <code>
+   *                            null</code> if one has not yet been set.
+   */
+  public Control getControl() {
+    return control;
+  }
+
+  /**
+   * Creates the Control for the associated DisplayScalar.  This method invokes
+   * the method {@link ScalarMapListener#controlChanged(ScalarMapControlEvent)}
+   * on all registered {@link ScalarMapListener}s with this instance as
+   * the event source and {@link ScalarMapEvent#CONTROL_ADDED} or {@link
+   * ScalarMapEvent#CONTROL_REPLACED} as the event ID -- depending on whether
+   * this is the first control or not.  The event control is the previous
+   * control if the event ID is {@link ScalarMapEvent#CONTROL_REPLACED}.  If the
+   * event ID is {@link ScalarMapEvent#CONTROL_ADDED}, then the event control is
+   * the created control or <code>null</code> -- depending on whether or not the
+   * control was successfully created.
+   *
+   * @throws RemoteException    Java RMI failure
+   * @throws VisADException     VisAD failure
+   */
+  synchronized void setControl() throws VisADException, RemoteException {
+    int evtID;
+    Control evtCtl;
+    if (control != null) {
+      evtID = ScalarMapEvent.CONTROL_REPLACED;
+      evtCtl = control;
+    } else {
+      evtID = ScalarMapEvent.CONTROL_ADDED;
+      evtCtl = null;
+    }
+
+    if (display == null) {
+      throw new DisplayException("ScalarMap.setControl: not part of " +
+                                 "any Display");
+    }
+    control = display.getDisplayRenderer().makeControl(this);
+    if (control != null) {
+      display.addControl(control);
+
+      if (evtCtl == null) {
+        evtCtl = control;
+      }
+    }
+
+    if (control != null || evtCtl != null) {
+      notifyCtlListeners(new ScalarMapControlEvent(this, evtID, evtCtl));
+    }
+  }
+
+  /** return value is true if data (RealType) values are linearly
+   *  scaled to display (DisplayRealType) values;
+   *  if so, then values are scaled by:
+   *  display_value = data_value * so[0] + so[1];
+   *  (data[0], data[1]) defines range of data values (either passed
+   *  in to setRange or computed by autoscaling logic) and
+   *  (display[0], display[1]) defines range of display values;
+   *  so, data, display must each be passed in as double[2] arrays;
+   *  note if overrideUnit != null, so and data are in overrideUnit 
+   *  @param  so       array to contain scale and offset
+   *  @param  data     array to contain the data range
+   *  @param  display  array to contain the display range
+   *  @return  true if data are linearly scaled
+   */
+  public boolean getScale(double[] so, double[] data, double[] display) {
+    // WLH 31 Aug 2000
+    if (overrideUnit != null) {
+      so[0] = scale * override_scale;
+      so[1] = scale * override_offset + offset;
+    }
+    else {
+      so[0] = scale;
+      so[1] = offset;
+    }
+    data[0] = dataRange[0];
+    data[1] = dataRange[1];
+    display[0] = displayRange[0];
+    display[1] = displayRange[1];
+    return isScaled;
+  }
+
+  /**
+   * Returns the current range of the {@link RealType} data.  The range is
+   * implicitly set by autoscaling logic or may be explicitly set by the {@link
+   * #setRange(double,double)} method.  Note that if overrideUnit != null,
+   * then dataRange is in overrideUnit.
+   *
+   * @return                    The current range of the {@link RealType} data.
+   *                            The array is new and may be safely modified.
+   */
+  public double[] getRange() {
+    double[] range = {dataRange[0], dataRange[1]};
+    return range;
+  }
+
+  /** explicitly set the range of data (RealType) values according
+   *  to Unit conversion between this ScalarMap's RealType and
+   *  DisplayRealType (both must have Units and they must be
+   *  convertable; if neither this nor setRange is invoked, then
+   *  the range will be computed from the initial values of Data
+   *  objects linked to the Display by autoscaling logic. 
+   *  @throws  VisADException   VisAD error
+   *  @throws  RemoteException  Java RMI error
+   */
+  public void setRangeByUnits()
+         throws VisADException, RemoteException {
+    isManual = true;
+    setRange(null, 0.0, 0.0, true);
+    if (scale == scale && offset == offset) {
+      incTick(); // did work, so wake up Display
+    }
+    else {
+      isManual = false; // didn't work, so don't lock out auto-scaling
+    }
+  }
+
+  /**
+   * Explicitly sets the range of {@link RealType} data values that is mapped to
+   * the natural range of {@link DisplayRealType} display values.  This method
+   * is used to define a linear map from Scalar to DisplayScalar values.  If
+   * neither this nor {@link #setRangeByUnits()} is invoked, then the range will
+   * be computed by autoscaling logic from the initial values of Data objects
+   * linked to the Display.  If the range of data values is (0.0, 1.0), for
+   * example, this method may be invoked with low = 1.0 and hi = 0.0 to invert
+   * the display scale.
+   *
+   * @param low                 One end of the range of applicable data.
+   * @param hi                  The other end of the range of applicable data.
+   * @throws VisADException     VisAD failure.
+   * @throws RemoteException    Java RMI failure.
+   */
+  public void setRange(double low, double hi)
+         throws VisADException, RemoteException {
+    setRange(low, hi, VisADEvent.LOCAL_SOURCE);
+  }
+
+  /** explicitly set the range of data (RealType) values; used for
+   *  linear map from Scalar to DisplayScalar values;
+   *  if neither this nor setRangeByUnits is invoked, then the
+   *  range will be computed from the initial values of Data
+   *  objects linked to the Display by autoscaling logic;
+   *  if the range of data values is (0.0, 1.0), for example, this
+   *  method may be invoked with low = 1.0 and hi = 0.0 to invert
+   *  the display scale .
+   *  @param  low         lower range value (see notes above)
+   *  @param  hi          upper range value (see notes above)
+   *  @param  remoteId    id of remote scale
+   *  @throws  VisADException   VisAD error
+   *  @throws  RemoteException  Java RMI error
+   */
+  public void setRange(double low, double hi, int remoteId)
+         throws VisADException, RemoteException {
+    if (DisplayScalar.equals(Display.Animation)) {
+      System.err.println("Warning: setRange on " +
+        "ScalarMap to Display.Animation has no effect.");
+      return;
+    }
+    isManual = true;
+    setRange(null, low, hi, false, remoteId);
+    if (scale == scale && offset == offset) {
+      incTick(); // did work, so wake up Display
+    }
+    else {
+      isManual = false; // didn't work, so don't lock out auto-scaling
+    }
+  }
+
+  /** set range used for linear map from Scalar to DisplayScalar values;
+      this is the call for automatic scaling */
+  public void setRange(DataShadow shadow)
+         throws VisADException, RemoteException {
+    if (!isManual) setRange(shadow, 0.0, 0.0, false, VisADEvent.LOCAL_SOURCE);
+  }
+
+  /** set range used for linear map from Scalar to
+      DisplayScalar values */
+  private synchronized void setRange(DataShadow shadow, double low, double hi,
+          boolean unit_flag) throws VisADException, RemoteException {
+    setRange(shadow, low, hi, unit_flag, VisADEvent.LOCAL_SOURCE);
+  }
+
+  /** set range used for linear map from Scalar to
+      DisplayScalar values */
+  private synchronized void setRange(DataShadow shadow, double low, double hi,
+          boolean unit_flag, int remoteId)
+         throws VisADException, RemoteException {
+    int i = ScalarIndex;
+    if (shadow != null) {
+      // WLH - 23 Sept 99
+      if (DisplayScalar.equals(Display.Latitude) ||
+          DisplayScalar.equals(Display.Longitude)) {
+        Unit data_unit =
+          (Scalar instanceof RealType) ? ((RealType) Scalar).getDefaultUnit() :
+                                         null;
+        Unit display_unit = DisplayScalar.getDefaultUnit();
+        if (data_unit != null && display_unit != null &&
+            Unit.canConvert(data_unit, display_unit)) {
+          dataRange[0] = data_unit.toThis(displayRange[0], display_unit);
+          dataRange[1] = data_unit.toThis(displayRange[1], display_unit);
+        }
+        else {
+          if (i < 0 || i >= shadow.ranges[0].length) return;
+          dataRange[0] = shadow.ranges[0][i];
+          dataRange[1] = shadow.ranges[1][i];
+        }
+      }
+      else {
+        if (i < 0 || i >= shadow.ranges[0].length) return;
+        dataRange[0] = shadow.ranges[0][i];
+        dataRange[1] = shadow.ranges[1][i];
+      }
+    }
+    else if (unit_flag) {
+      Unit data_unit =
+        (Scalar instanceof RealType) ? ((RealType) Scalar).getDefaultUnit() :
+                                       null;
+      Unit display_unit = DisplayScalar.getDefaultUnit();
+      if (data_unit == null || display_unit == null) {
+        throw new UnitException("ScalarMap.setRangeByUnits: null Unit");
+      }
+      dataRange[0] = data_unit.toThis(displayRange[0], display_unit);
+      dataRange[1] = data_unit.toThis(displayRange[1], display_unit);
+/*
+System.out.println("data_unit = " + data_unit + " display_unit = " + display_unit);
+System.out.println("dataRange = " + dataRange[0] + " " + dataRange[1] +
+" displayRange = " + displayRange[0] + " " + displayRange[1]);
+*/
+    }
+    else {
+      dataRange[0] = low;
+      dataRange[1] = hi;
+      // WLH 31 Aug 2000
+      // manual range is in overrideUnit. so convert to Scalar default Unit
+      if (overrideUnit != null) {
+        dataRange[0] = (dataRange[0] - override_offset) / override_scale;
+        dataRange[1] = (dataRange[1] - override_offset) / override_scale;
+      }
+    }
+/*
+if (shadow != null || remoteId != VisADEvent.LOCAL_SOURCE) {
+  System.out.println(Scalar + " -> " + DisplayScalar + " range: " + dataRange[0] +
+                     " to " + dataRange[1] + " " + display.getName());
+}
+*/
+    // at this point dataRange is range for Scalar default Unit
+    //   even if (overrideUnit != null)
+    // DRM 17 Feb 2006 - so set the defaultUnitRange to be these values.
+    defaultUnitRange[0] = dataRange[0];
+    defaultUnitRange[1] = dataRange[1];
+    if (defaultUnitRange[0] == defaultUnitRange[1]) {
+      double half = defaultUnitRange[0] / 2000.0;
+      if (half < 0.5) half = 0.5;
+      defaultUnitRange[0] -= half;
+      defaultUnitRange[1] += half;
+    }
+
+    if (isScaled) {
+      computeScaleAndOffset();
+    }
+    else { // if (!isScaled)
+      if (dataRange[0] == Double.MAX_VALUE ||
+          dataRange[1] == -Double.MAX_VALUE) {
+        dataRange[0] = Double.NaN;
+        dataRange[1] = Double.NaN;
+      }
+      
+
+      // WLH 31 Aug 2000
+      if (overrideUnit != null) {
+        // now convert dataRange to overrideUnit
+        dataRange[0] = defaultUnitRange[0] * override_scale + override_offset;
+        dataRange[1] = defaultUnitRange[1] * override_scale + override_offset;
+      }
+
+    }
+/*
+System.out.println(Scalar + " -> " + DisplayScalar + " range: " + dataRange[0] +
+                   " to " + dataRange[1] + " scale: " + scale + " " + offset);
+*/
+    if (DisplayScalar.equals(Display.Animation) && shadow != null) {
+      if (control != null && ((AnimationControl)control).getComputeSet()) {
+        Set set = shadow.animationSampling;
+        /* DRM: 04 Jan 2003
+        if (set == null) {
+          return;
+        }
+        */
+        ((AnimationControl) control).setSet(set, true);
+      }
+    }
+    else if (DisplayScalar.equals(Display.IsoContour)) {
+      if (control != null) {
+
+        // WLH 10 July 2002
+        // don't set if application has called control.setLevels()
+        float[] lowhibase = new float[3];
+        boolean[] dashes = new boolean[1];
+
+        boolean public_set =
+          ((ContourControl) control).getPublicSet();
+        if (!public_set) {
+          boolean[] bvalues = new boolean[2];
+          float[] values = new float[5];
+          ((ContourControl) control).getMainContours(bvalues, values);
+          if (shadow == null) {
+            // don't set surface value for auto-scale
+            values[0] = (float) dataRange[0]; // surfaceValue
+          }
+          // CTR: 29 Jul 1999: interval should never be zero
+          float f = (float) (dataRange[1] - dataRange[0]) / 10.0f;
+          if (f != 0.0f) values[1] = f; // contourInterval
+          values[2] = (float) dataRange[0]; // lowLimit
+          values[3] = (float) dataRange[1]; // hiLimit
+          values[4] = (float) dataRange[0]; // base
+          ((ContourControl) control).setMainContours(bvalues, values,
+                                                     true, true);
+        }
+      }
+    }
+    else if (DisplayScalar.equals(Display.XAxis) ||
+             DisplayScalar.equals(Display.YAxis) ||
+             DisplayScalar.equals(Display.ZAxis)) {
+      if (dataRange[0] != Double.MAX_VALUE &&
+          dataRange[1] != -Double.MAX_VALUE &&
+          dataRange[0] == dataRange[0] &&
+          dataRange[1] == dataRange[1] &&
+          dataRange[0] != dataRange[1] &&
+          scale == scale && offset == offset) {
+        if (display != null) {
+          makeScale();
+        }
+        else {
+          scale_flag = true;
+        }
+        back_scale_flag = true;
+      }
+    }
+
+    if (dataRange[0] == dataRange[0] &&
+        dataRange[1] == dataRange[1] && ListenerVector != null) {
+      ScalarMapEvent evt;
+      evt = new ScalarMapEvent(this, (shadow == null ?
+                                      ScalarMapEvent.MANUAL :
+                                      ScalarMapEvent.AUTO_SCALE), remoteId);
+      Vector listeners_clone = null;
+      synchronized (ListenerVector) {
+        listeners_clone = (Vector) ListenerVector.clone();
+      }
+      Enumeration listeners = listeners_clone.elements();
+      while (listeners.hasMoreElements()) {
+        ScalarMapListener listener =
+          (ScalarMapListener) listeners.nextElement();
+        listener.mapChanged(evt);
+      }
+    }
+  }
+
+  private void computeScaleAndOffset() {
+
+    if (dataRange[0] == Double.MAX_VALUE ||
+        dataRange[1] == -Double.MAX_VALUE) {
+      dataRange[0] = Double.NaN;
+      dataRange[1] = Double.NaN;
+      scale = Double.NaN;
+      offset = Double.NaN;
+    }
+    else {
+      if (dataRange[0] == dataRange[1]) {
+        // WLH 11 April 2000
+        double half = dataRange[0] / 2000.0;
+        if (half < 0.5) half = 0.5;
+        dataRange[0] -= half;
+        dataRange[1] += half;
+      }
+
+      // WLH 31 Aug 2000
+      if (overrideUnit != null) {
+        // now convert dataRange to overrideUnit
+        dataRange[0] = defaultUnitRange[0] * override_scale + override_offset;
+        dataRange[1] = defaultUnitRange[1] * override_scale + override_offset;
+      }
+
+      scale = (displayRange[1] - displayRange[0]) /
+              (dataRange[1] - dataRange[0]);
+      offset = displayRange[0] - scale * dataRange[0];
+    }
+    if (Double.isInfinite(scale) || Double.isInfinite(offset) ||
+        scale != scale || offset != offset) {
+      dataRange[0] = Double.NaN;
+      dataRange[1] = Double.NaN;
+      scale = Double.NaN;
+      offset = Double.NaN;
+    }
+
+  }
+
+  /** add a ScalarMapListener, to be notified whenever setRange is
+   *  invoked 
+   *  @param  listener   <CODE>ScalarMapListener</CODE> to recieve notification
+   *                     of changes.
+   */
+  public void addScalarMapListener(ScalarMapListener listener) {
+    if (ListenerVector == null) {
+      ListenerVector = new Vector();
+    }
+    ListenerVector.addElement(listener);
+    if (dataRange[0] == dataRange[0] &&
+        dataRange[1] == dataRange[1]) {
+      try {
+        listener.mapChanged(new ScalarMapEvent(this, ScalarMapEvent.MANUAL));
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+    }
+  }
+
+  /** remove a ScalarMapListener 
+   *  @param  listener   <CODE>ScalarMapListener</CODE> to remove from the list
+   */
+  public void removeScalarMapListener(ScalarMapListener listener) {
+    if (listener != null && ListenerVector != null) {
+      ListenerVector.removeElement(listener);
+    }
+  }
+
+  /** Send a <CODE>ScalarMapEvent</CODE> to all control listeners */
+  private void notifyCtlListeners(ScalarMapControlEvent evt)
+    throws RemoteException, VisADException
+  {
+    if (ListenerVector != null) {
+      Vector listeners_clone = null;
+      synchronized (ListenerVector) {
+        listeners_clone = (Vector) ListenerVector.clone();
+      }
+      Enumeration listeners = listeners_clone.elements();
+      while (listeners.hasMoreElements()) {
+        ScalarMapListener listener =
+          (ScalarMapListener) listeners.nextElement();
+        listener.controlChanged(evt);
+      }
+    }
+  }
+
+  /**
+   * Change underscore characters (_) in the Scalar name to blanks.
+   * Can be used to change the displayed scalar name on the axis.
+   * @param  u2b   true to change, false to change back
+   * @see #setScalarName  as an alternative
+   */
+  public void setUnderscoreToBlank(boolean u2b) {
+    underscore_to_blank = u2b;
+    if (Scalar != null) {
+      scalarName = Scalar.getName();
+      if (underscore_to_blank) {
+        scalarName = scalarName.replace('_', ' ');
+      }
+      // set the label on the scale as well.  DRM 17-Nov-2000
+      if (axisScale != null) axisScale.setTitle(scalarName);
+    }
+  }
+
+  private static final double SCALE = 0.06;
+  private static final double OFFSET = 1.05;
+
+  /**
+   * Create the scale that is displayed.  This is called automatically
+   * when <CODE>setRange(lo, hi)</CODE> and <CODE>setDisplay</CODE> are
+   * called.  It makes a call to <CODE>AxisScale.makeScale()</CODE> where
+   * the actual hard work is done.
+   * @throws VisADException   VisAD error.
+   */
+  public void makeScale() throws VisADException {
+    if (axisScale != null) 
+    {
+        DisplayRenderer displayRenderer = null;
+        if (display == null) return;
+        displayRenderer = display.getDisplayRenderer();
+        if (displayRenderer == null) return;
+        boolean scaleMade = axisScale.makeScale();
+        if (scaleMade)
+        {
+          //displayRenderer.setScale(axis, axis_ordinal, array, scale_color);
+          if (scale_on) {
+            displayRenderer.setScale(axisScale);
+          } else {
+            displayRenderer.clearScale(axisScale);
+          }
+          scale_flag = false;
+        }
+    }
+  }
+
+  /**
+   * Enable the display of the scale for this map.  This can be used
+   * to selectively turn on or off the scales in a display.  Must be
+   * used in conjunction with <CODE>GraphicsModeControl.setScaleEnable()</CODE>
+   * or <CODE>DisplayRenderer.setScaleOn(boolean on)</CODE>.  
+   * @param  on  true will enable display of axis, false will disable display
+   * @see visad.GraphicsModeControl#setScaleEnable(boolean enable)
+   * @see visad.DisplayRenderer#setScaleOn(boolean on)
+   * @see visad.AxisScale#setVisible(boolean visible)
+   */
+  public void setScaleEnable(boolean on) {
+    scale_on = on;
+    if (axisScale != null) axisScale.setVisible(on);
+  }
+
+  /**
+   * See if the AxisScale is visible or not.
+   * @return true if the AxisScale is visible
+   */
+  public boolean getScaleEnable() { return scale_on; }
+
+  /** 
+   * Set color of axis scales; color must be float[3] with red,
+   * green and blue components; DisplayScalar must be XAxis,
+   * YAxis or ZAxis.  Preferred method is to use <CODE>AxisScale.setColor<CODE>
+   * methods.
+   * @param  color  array of R,G,B values of color.
+   * @throws  VisADException  non-spatial DisplayScalar or wrong length
+   *                          of color array
+   * @see #getAxisScale()
+   * @see visad.AxisScale#setColor(Color color)
+   * @see visad.AxisScale#setColor(float[] color)
+   */
+  public void setScaleColor(float[] color) throws VisADException {
+    if (!DisplayScalar.equals(Display.XAxis) &&
+        !DisplayScalar.equals(Display.YAxis) &&
+        !DisplayScalar.equals(Display.ZAxis)) {
+     throw new DisplayException("ScalarMap.setScaleColor: DisplayScalar " +
+                                "must be XAxis, YAxis or ZAxis");
+    }
+    if (color == null || color.length != 3) {
+     throw new DisplayException("ScalarMap.setScaleColor: color is " +
+                                "null or wrong length");
+    }
+    // DRM 10-Oct 2000
+    axisScale.setColor(color);
+  }
+
+  public boolean badRange() {
+    // WLH 15 Feb 2002
+    boolean bad = (isScaled && (scale != scale || offset != offset));
+    if (DisplayScalar.equals(Display.Animation)) {
+      if (control != null) {
+        Set set = ((AnimationControl) control).getSet();
+        bad |= (set == null);
+      }
+      else {
+        bad = true;
+      }
+    }
+    return bad;
+    // return (isScaled && (scale != scale || offset != offset));
+  }
+
+  /** return an array of display (DisplayRealType) values by
+   *  linear scaling (if applicable) the data_values array
+   *  (RealType values) 
+   * @param   values to scale as doubles
+   * @return  array of display values
+   */
+  public float[] scaleValues(double[] values) {
+/* WLH 23 June 99
+    if (values == null || badRange()) return null;
+*/
+    if (values == null) return null;
+    float[] new_values = new float[values.length];
+    if (badRange()) {
+      for (int i=0; i<values.length; i++) new_values[i] = Float.NaN;
+    }
+    else {
+// double[] old_values = values;
+      // WLH 31 Aug 2000
+      //if (overrideUnit != null) {
+      // DRM 11 Jun 2003
+      if (overrideUnit != null &&
+          !overrideUnit.equals(((RealType) Scalar).getDefaultUnit())) {
+        try {
+          values =
+            overrideUnit.toThis(values, ((RealType) Scalar).getDefaultUnit());
+        }
+        catch (UnitException e) {
+        }
+      }
+      if (isScaled) {
+        for (int i=0; i<values.length; i++) {
+          new_values[i] = (float) (offset + scale * values[i]);
+        }
+      }
+      else {
+        for (int i=0; i<values.length; i++) {
+          new_values[i] = (float) values[i];
+        }
+      }
+/*
+if (overrideUnit != null) {
+  System.out.println("values = " + old_values[0] + " " + values[0] + " " +
+                     new_values[0]);
+}
+*/
+    }
+/* SRE 27 Oct 99
+    System.out.println(
+      "ScalarMap.scaleValues(double[]): values[0] = " + values[0] +
+      "; new_values[0] = " + new_values[0]);
+*/
+    return new_values;
+  }
+
+  /** return an array of display (DisplayRealType) values by
+   *  linear scaling (if applicable) the data_values array
+   *  (RealType values) 
+   * @param   values to scale as floats
+   * @return  array of display values
+   */
+  public float[] scaleValues(float[] values) {
+    return scaleValues(values, true);
+  }
+
+  /** return an array of display (DisplayRealType) values by
+   *  linear scaling (if applicable) the data_values array
+   *  (RealType values) 
+   * @param   values to scale as floats
+   * @param   newArray   false to scale in place
+   * @return  array of display values
+   */
+  public float[] scaleValues(float[] values, boolean newArray) {
+/* WLH 23 June 99
+    if (values == null || badRange()) return null;
+*/
+    if (values == null) return null;
+    float[] new_values = null;
+    if (badRange()) {
+      new_values = (newArray) ? new float[values.length] : values;
+      for (int i=0; i<values.length; i++) new_values[i] = Float.NaN;
+    }
+    else {
+// float[] old_values = values;
+      // WLH 31 Aug 2000
+      //if (overrideUnit != null) {
+      // DRM 11 Jun 2003
+      if (overrideUnit != null &&
+          !overrideUnit.equals(((RealType) Scalar).getDefaultUnit())) {
+        try {
+          values =
+            overrideUnit.toThis(values, ((RealType) Scalar).getDefaultUnit(), newArray);
+        }
+        catch (UnitException e) {
+        }
+      }
+      if (isScaled) {
+        new_values = (newArray) ? new float[values.length] : values;
+        for (int i=0; i<values.length; i++) {
+          if (values[i] == values[i]) {
+            new_values[i] = (float) (offset + scale * values[i]);
+          } else {
+            new_values[i] = Float.NaN;
+          }
+        }
+      }
+      else {
+        new_values = values;
+      }
+/*
+if (overrideUnit != null) {
+  System.out.println("values = " + old_values[0] + " " + values[0] + " " +
+                     new_values[0]);
+}
+*/
+    }
+/* SRE 27 Oct 99
+    System.out.println(
+      "ScalarMap.scaleValues(double[]): values[0] = " + values[0] +
+      "; new_values[0] = " + new_values[0]);
+*/
+    return new_values;
+  }
+
+  /** return an array of display (DisplayRealType) values by
+   *  linear scaling (if applicable) the data_values array
+   *  (RealType values); results are scaled by the given scale factor
+   * @param   values to scale as bytes
+   * @return  array of display values
+   */
+  public byte[] scaleValues(byte[] values, int factor) throws VisADException {
+    if (values == null) return null;
+    byte[] new_values = null;
+    if (badRange()) {
+      new_values = new byte[values.length];
+    }
+    else {
+      if (overrideUnit != null &&
+          !overrideUnit.equals(((RealType) Scalar).getDefaultUnit())) {
+        throw new VisADException(
+          "scaleValues(byte[]): non-default units not supported");
+      }
+      if (isScaled) {
+        new_values = new byte[values.length];
+        for (int i=0; i<values.length; i++) {
+          float v = (float) values[i];
+          if (v < 0) v += 256;
+          v = (float) (factor * (offset + scale * v));
+          if (v < 0) v = 0;
+          else if (v > 255) v = 255;
+          new_values[i] = (byte) v;
+        }
+      }
+      else {
+        new_values = values;
+      }
+    }
+    return new_values;
+  }
+
+  /** return an array of data (RealType) values by inverse
+   *  linear scaling (if applicable) the display_values array
+   *  (DisplayRealType values); this is useful for direct
+   *  manipulation and cursor labels 
+   * @param  values	display values
+   * @return data values
+   */
+  public float[] inverseScaleValues(float[] values) {
+    return inverseScaleValues(values, true);
+  }
+
+  /** return an array of data (RealType) values by inverse
+   *  linear scaling (if applicable) the display_values array
+   *  (DisplayRealType values); this is useful for direct
+   *  manipulation and cursor labels 
+   * @param  values	display values
+   * @param  newArray  false to transform in place
+   * @return data values
+   */
+  public float[] inverseScaleValues(float[] values, boolean newArray) {
+    if (values == null) return null;
+    float[] new_values = (newArray) ? new float[values.length] : values;
+    if (isScaled) {
+      for (int i=0; i<values.length; i++) {
+        if (values[i] == values[i]) {
+           new_values[i] = (float) ((values[i] - offset) / scale);
+        } else {
+           new_values[i] = Float.NaN;
+        }
+      }
+    }
+    else {
+      if (newArray) {
+        for (int i=0; i<values.length; i++) {
+          new_values[i] = values[i];
+        }
+      }
+    }
+    // WLH 31 Aug 2000
+    if (overrideUnit != null) {
+// float[] old_values = new_values;
+      try {
+        new_values =
+          overrideUnit.toThat(new_values, ((RealType) Scalar).getDefaultUnit(), false); // already copied above
+      }
+      catch (UnitException e) {
+      }
+/*
+System.out.println("inverse values = " + values[0] + " " + old_values[0] + " " +
+                   new_values[0]);
+*/
+    }
+    return new_values;
+  }
+
+  /** ensure that non-Manual components of flow_tuple have equal
+      dataRanges symmetric about 0.0 */
+  public static void equalizeFlow(Vector mapVector, DisplayTupleType flow_tuple)
+         throws VisADException, RemoteException {
+    double[] range = new double[2];
+    double low = Double.MAX_VALUE;
+    double hi = -Double.MAX_VALUE;
+    boolean anyAuto = false;
+
+    Enumeration maps = mapVector.elements();
+    while(maps.hasMoreElements()) {
+      ScalarMap map = ((ScalarMap) maps.nextElement());
+      DisplayRealType dtype = map.getDisplayScalar();
+      DisplayTupleType tuple = dtype.getTuple();
+      if (flow_tuple.equals(tuple) && !map.isManual &&
+          !map.badRange()) {
+        anyAuto = true;
+        low = Math.min(low, map.dataRange[0]);
+        hi = Math.max(hi, map.dataRange[1]);
+      }
+    }
+    if (!anyAuto) return;
+    hi = Math.max(hi, -low);
+    low = -hi;
+    maps = mapVector.elements();
+    while(maps.hasMoreElements()) {
+      ScalarMap map = ((ScalarMap) maps.nextElement());
+      DisplayRealType dtype = map.getDisplayScalar();
+      DisplayTupleType tuple = dtype.getTuple();
+      if (flow_tuple.equals(tuple) && !map.isManual &&
+          !map.badRange()) {
+        map.setRange(null, low, hi, false);
+      }
+    }
+  }
+
+  /** Get index of DisplayScalar in display.DisplayRealTypeVector */
+  int getDisplayScalarIndex() {
+    return DisplayScalarIndex;
+  }
+
+  /** get index of Scalar in display.RealTypeVector */
+  int getScalarIndex() {
+    return ScalarIndex;
+  }
+
+  /** set index of Scalar in display.RealTypeVector */
+  void setScalarIndex(int index) {
+    ScalarIndex = index;
+  }
+
+  /** set index of DisplayScalar in display.DisplayRealTypeVector */
+  void setDisplayScalarIndex(int index) {
+    DisplayScalarIndex = index;
+  }
+
+  /** set index of DisplayScalar in value array used by
+      ShadowType.doTransform */
+  public void setValueIndex(int index) {
+    ValueIndex = index;
+  }
+
+  /** get index of DisplayScalar in value array used by
+      ShadowType.doTransform */
+  public int getValueIndex() {
+    return ValueIndex;
+  }
+
+  /**
+   * Compares this ScalarMap with another object.
+   * @param o                     The other object.
+   * @return                      A value that is negative, zero, or positive
+   *                              depending on whether this instance is less
+   *                              than, equal to, or greater than the other
+   *                              object, respectively.
+   * @throws ClassCastException   if the other object isn't a {@link ScalarMap}.
+   * @throws NullPointerException if the other object is <code>null</code>.
+   */
+  public int compareTo(Object o)
+  {
+    return -((ScalarMap)o).compareTo(this);
+  }
+
+  /**
+   * Compares this ScalarMap with another ScalarMap.  The ScalarType-s are
+   * first compared; if they compare equal, then the DisplayRealType-s are
+   * compared.
+   * @param that                The other ScalarMap.
+   * @return            A value that is negative, zero, or positive depending on
+   *                    whether this ScalarMap is considered less than, equal
+   *                    to, or greater than the other ScalarMap, respectively.
+   */
+  protected int compareTo(ScalarMap that)
+  {
+    int         comp = getScalar().compareTo(that.getScalar());
+    if (comp == 0)
+      comp = getDisplayScalar().compareTo(that.getDisplayScalar());
+    return comp;
+  }
+
+  /**
+   * Indicates if this ScalarMap is the same as another object.
+   * @param o           The other object.
+   * @return            <code>true</code> if and only if the other object is a
+   *                    ScalarMap and compares equal to this ScalarMap.
+   */
+  public boolean equals(Object o)
+  {
+    return o instanceof ScalarMap && compareTo(o) == 0;
+  }
+
+  /**
+   * Returns the hash code for this ScalarMap.  If <code>scalarMap1.equals(
+   * scalarMap2)</code> is true, then <code>scalarMap1.hashCode() ==
+   * scalarMap2.hashCode()</code>.
+   * @return            The hash code for this ScalarMap.
+   */
+  public int hashCode()
+  {
+    ScalarType s = getScalar();
+    DisplayRealType ds = getDisplayScalar();
+
+    int hash = 0;
+    if (s != null) {
+      if (ds != null) {
+        hash = s.hashCode() ^ ds.hashCode();
+      } else {
+        hash = s.hashCode();
+      }
+    } else if (ds != null) {
+      hash = ds.hashCode();
+    }
+
+    return hash;
+  }
+
+  /**
+   * Create and return a copy of this ScalarMap.
+   * @return  copy of the ScalarMap or <CODE>null</CODE> if a copy couldn't
+   *          be created.
+   */
+  public Object clone()
+  {
+    try {
+      ScalarMap sm = new ScalarMap(Scalar, DisplayScalar);
+      copy(sm);
+      return sm;
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  protected void copy(ScalarMap map)
+    throws VisADException, RemoteException
+  {
+    map.isScaled = isScaled;
+    map.isManual = isManual;
+    map.dataRange[0] = dataRange[0];
+    map.dataRange[1] = dataRange[1];
+    map.defaultUnitRange[0] = defaultUnitRange[0];
+    map.defaultUnitRange[1] = defaultUnitRange[1];
+    map.displayRange[0] = displayRange[0];
+    map.displayRange[1] = displayRange[1];
+    map.scale = scale;
+    map.offset = offset;
+    map.axisScale = (axisScale != null) ? axisScale.clone(map) : null;
+    map.scale_flag = scale_flag;
+    map.back_scale_flag = back_scale_flag;
+    if (map.display != null) {
+      map.setControl();
+    }
+  }
+
+  /**
+   * Returns a string representation of the ScalarMap.
+   * @return  a string that "textually represents" this ScalarMap.
+   */
+  public String toString() {
+    return toString("");
+  }
+
+  /**
+   * Returns a string representation of the ScalarMap with the specified
+   * prefix prepended.
+   * @param pre  prefix to prepend to the representation
+   * @return  a string that "textually represents" this ScalarMap with
+   *          <CODE>pre</CODE> prepended.
+   */
+  public String toString(String pre) {
+    return pre + "ScalarMap: " + Scalar.toString() +
+           " -> " + DisplayScalar.toString() + "\n";
+  }
+
+  /**
+   * Get the AxisScale associated with this ScalarMap.
+   * @return the AxisScale or null if not a spatial ScalarMap
+   */
+  public AxisScale getAxisScale()
+  {
+      return axisScale;
+  }
+
+  /**
+   * set aspect ratio of XAxis, YAxis & ZAxis in ScalarMaps rather
+   * than matrix (i.e., don't distort text fonts);
+   * won't work for spherical, polar, cylindrical coordinates
+   * @param aspect ratios; 3 elements for Java3D, 2 for Java2D
+   * @throws VisADException a VisAD error occurred
+   * @throws RemoteException an RMI error occurred
+   */
+  void setAspectCartesian(double[] aspect)
+       throws VisADException, RemoteException {
+    double asp = Double.NaN;
+    if (DisplayScalar.equals(Display.XAxis)) asp = aspect[0];
+    if (DisplayScalar.equals(Display.YAxis)) asp = aspect[1];
+    if (DisplayScalar.equals(Display.ZAxis)) asp = aspect[2];
+    if (asp == asp) {
+      isScaled = DisplayScalar.getRange(displayRange);
+      displayRange[0] *= asp;
+      displayRange[1] *= asp;
+      computeScaleAndOffset();
+
+      makeScale();
+// needs work in AxisScale ****
+    }
+    // note XAxis, YAxis and ZAxis have no unit, so cannot be setRangeByUnits()
+  }
+
+}
diff --git a/visad/ScalarMapControlEvent.java b/visad/ScalarMapControlEvent.java
new file mode 100644
index 0000000..ad4297c
--- /dev/null
+++ b/visad/ScalarMapControlEvent.java
@@ -0,0 +1,75 @@
+//
+// ScalarMapControlEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * <CODE>ScalarMapControlEvent</CODE> is the VisAD class for control-related
+ * <CODE>Event</CODE>s from <CODE>ScalarMap</CODE> objects.  They are
+ * sourced by <CODE>ScalarMap</CODE> objects and received by
+ * <CODE>ScalarMapListener</CODE> objects.<P>
+ */
+public class ScalarMapControlEvent
+  extends ScalarMapEvent
+{
+  private Control control;
+
+  /**
+   * Create a control-related <CODE>ScalarMap</CODE> event
+   *
+   * @param map the map to which this event refers
+   * @param id the event type.
+   * @param ctl the control.
+   */
+  public ScalarMapControlEvent(ScalarMap map, int id, Control ctl)
+  {
+    // don't pass map as the source, since source
+    // is transient inside Event
+    super(map, id);
+    this.control = ctl;
+  }
+
+  /**
+   * Get the <CODE>Control</CODE> referred to by this event.
+   *
+   * @return the <CODE>Control</CODE>
+   */
+  public Control getControl()
+  {
+    return control;
+  }
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("ScalarMapControlEvent[");
+    buf.append(getIdString());
+    buf.append(", ");
+    buf.append(getMapString());
+    buf.append(", ");
+    buf.append(control);
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/ScalarMapEvent.java b/visad/ScalarMapEvent.java
new file mode 100644
index 0000000..a2130f2
--- /dev/null
+++ b/visad/ScalarMapEvent.java
@@ -0,0 +1,169 @@
+//
+// ScalarMapEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * <CODE>ScalarMapEvent</CODE> is the VisAD class for <CODE>Event</CODE>s
+ * from <CODE>ScalarMap</CODE> objects.  They are
+ * sourced by <CODE>ScalarMap</CODE> objects and received by
+ * <CODE>ScalarMapListener</CODE> objects.<P>
+ */
+public class ScalarMapEvent extends VisADEvent {
+
+  /** values for id */
+  public final static int UNKNOWN = 0;
+  public final static int AUTO_SCALE = 1;
+  public final static int MANUAL = 2;
+  public final static int CONTROL_ADDED = 3;
+  public final static int CONTROL_REMOVED = 4;
+  public final static int CONTROL_REPLACED = 5;
+
+  private int id = UNKNOWN;
+
+  private ScalarMap map; // source of event
+
+  /**
+   * Create a <CODE>ScalarMap</CODE> event
+   *
+   * @param map map to which this event refers
+   * @param id the event type.
+   */
+  public ScalarMapEvent(ScalarMap map, int id)
+  {
+    this(map, id, LOCAL_SOURCE);
+  }
+
+  /**
+   * Create a <CODE>ScalarMap</CODE> event
+   *
+   * @param map map to which this event refers
+   * @param id the event type.
+   */
+  public ScalarMapEvent(ScalarMap map, int id, int remoteId)
+  {
+    // don't pass map as the source, since source
+    // is transient inside Event
+    super(null, 0, null, remoteId);
+    this.map = map;
+    this.id = id;
+  }
+
+  /**
+   * Create a <CODE>ScalarMap</CODE> event
+   *
+   * @param map map to which this event refers
+   * @param auto <CODE>true</CODE> if this is an AUTO_SCALE event
+   *             <CODE>false</CODE> if it's a MANUAL event.
+   *
+   * @deprecated - Explicitly cite the event ID using the
+   *               <CODE>ScalarMapEvent(ScalarMap map, int id)</CODE>
+   *               constructor.
+   */
+  public ScalarMapEvent(ScalarMap map, boolean auto)
+  {
+    // don't pass map as the source, since source
+    // is transient inside Event
+    super(null, 0, null, LOCAL_SOURCE);
+    this.map = map;
+    this.id = auto ? AUTO_SCALE : MANUAL;
+  }
+
+  /**
+   * Get the ScalarMap that sent this ScalarMapEvent (or
+   * a copy if the ScalarMap was on a different JVM)
+   *
+   * @return the <CODE>ScalarMap</CODE>
+   */
+  public ScalarMap getScalarMap()
+  {
+    return map;
+  }
+
+  /**
+   * Get the ID type of this event.
+   *
+   * @return <CODE>ScalarMapEvent</CODE> type.  Valid types are:
+   *         <UL>
+   *           <LI>ScalarMapEvent.AUTO_SCALE
+   *           <LI>ScalarMapEvent.MANUAL
+   *           <LI>ScalarMapEvent.CONTROL_ADDED
+   *           <LI>ScalarMapEvent.CONTROL_REMOVED
+   *           <LI>ScalarMapEvent.CONTROL_REPLACED
+   *         </UL>
+   */
+  public int getId()
+  {
+    return id;
+  }
+
+  /**
+   * Get the ID type of this event as a String
+   *
+   * @return The event type string.
+   */
+  public String getIdString()
+  {
+    switch (id) {
+    case AUTO_SCALE: return "AUTO_SCALE";
+    case MANUAL: return "MANUAL";
+    case CONTROL_ADDED: return "CONTROL_ADDED";
+    case CONTROL_REMOVED: return "CONTROL_REMOVED";
+    case CONTROL_REPLACED: return "CONTROL_REPLACED";
+    }
+
+    return "UNKNOWN_ID=" + id;
+  }
+
+  /**
+   * Get a one-line description of the <CODE>ScalarMap</CODE> which
+   * originated this event.
+   *
+   * @return the one-line map description.
+   */
+  String getMapString()
+  {
+    StringBuffer buf = new StringBuffer();
+    if (map instanceof ConstantMap) {
+      buf.append(((ConstantMap )map).getConstant());
+    } else {
+      buf.append(map.getScalar());
+    }
+    buf.append("->");
+    buf.append(map.getDisplayScalar());
+    return buf.toString();
+  }
+
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("ScalarMapEvent[");
+    buf.append(getIdString());
+    buf.append(", ");
+    buf.append(getMapString());
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/ScalarMapListener.java b/visad/ScalarMapListener.java
new file mode 100644
index 0000000..d71fbbe
--- /dev/null
+++ b/visad/ScalarMapListener.java
@@ -0,0 +1,61 @@
+//
+// ScalarMapListener.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.EventListener;
+import java.rmi.*;
+
+/**
+ * <CODE>ScalarMapListener</CODE> is the EventListener interface for
+ * <CODE>ScalarMapEvents</CODE>.<P>
+ */
+public interface ScalarMapListener
+  extends EventListener
+{
+
+  /**
+   * Receive a <CODE>ScalarMapEvent</CODE> when the map data changes.
+   *
+   * @param evt the event
+   *
+   * @exception VisADException If there is a problem notifying this listener.
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  void mapChanged(ScalarMapEvent evt)
+    throws VisADException, RemoteException;
+
+  /**
+   * Receive a <CODE>ScalarMapEvent</CODE> when the map control changes.
+   *
+   * @param evt the event
+   *
+   * @exception VisADException If there is a problem notifying this listener.
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  void controlChanged(ScalarMapControlEvent evt)
+    throws VisADException, RemoteException;
+}
diff --git a/visad/ScalarType.java b/visad/ScalarType.java
new file mode 100644
index 0000000..eab42a0
--- /dev/null
+++ b/visad/ScalarType.java
@@ -0,0 +1,338 @@
+//
+// ScalarType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.io.InvalidObjectException;
+
+import java.lang.ref.ReferenceQueue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import visad.util.WeakMapValue;
+
+/**
+ * ScalarType is the superclass of the VisAD hierarchy of scalar data types.
+ */
+public abstract class ScalarType extends MathType implements Comparable {
+
+  // name of scalar type - enforce uniqueness locally
+  // but do not rely on it - names may be duplicated on remote systems
+  String Name;
+
+  /**
+   * Hashtable of scalar names used to make sure scalar names are unique
+   * (within local VM).  Because the values in the hashtable are actually {@link
+   * WeakMapValue}s, the existance of a {@link ScalarType} in the hashtable will
+   * not prevent it from being garbage-collected when it is no longer strongly
+   * referenced.
+   */
+  private static Map ScalarHash = new HashMap();
+
+  // Aliases for scalar names
+  private static Map Translations = new HashMap();
+  private static Map ReverseTranslations = new HashMap();
+
+  // Queue that receives garbage-collected ScalarType instances.
+  private static final ReferenceQueue queue = new ReferenceQueue();
+
+  /**
+   * Create a <CODE>ScalarType</CODE> with the specified name.
+   *
+   * @param name The name of this <CODE>ScalarType</CODE>
+   *
+   * @exception VisADException If the name is not valid.
+   */
+  public ScalarType(String name) throws VisADException {
+    super();
+    Name = name;
+    synchronized(getClass()) {
+	checkQueue();
+	validateName(name, "name");
+	ScalarHash.put(name, new WeakMapValue(Name, this, queue));
+    }
+  }
+
+  /**
+   * Trusted constructor used to create standard VisAD <CODE>RealType</CODE>s
+   * without all the name-checking overhead.
+   *
+   * @param name Trusted name.
+   * @param b Dummy value used to indicate that this is a trusted constructor.
+   */
+  ScalarType(String name, boolean b) {
+    super(b);
+    Name = name;
+    synchronized(getClass()) {
+	checkQueue();
+	ScalarHash.put(name, new WeakMapValue(Name, this, queue));
+    }
+  }
+
+  /**
+   * Compares this object with another of the same type.  The comparison is
+   * on the names.
+   *
+   * @param obj		The other object of the same type.
+   * @return		A value less than zero, zero, or greater than zero
+   *			depending on whether this object is considered less
+   *			than, equal to, or greater than the other object,
+   *			respectively.
+   * @throws ClassCastException if the object is not a {@link ScalarType}.
+   */
+  public int compareTo(Object obj) {
+    return getName().compareTo(((ScalarType)obj).getName());
+  }
+
+  /**
+   * Indicates if this ScalarType is the same as another object.
+   *
+   * @param obj		The other object.
+   * @return		<code>true</code> if and only if the other object is a
+   *			ScalarType and compares equal to this ScalarType.
+   */
+  public boolean equals(Object obj) {
+    return obj instanceof ScalarType && compareTo(obj) == 0;
+  }
+
+  /**
+   * Obtains the hash code for this object.  If
+   * <code>scalarType1.equals(scalarType2)</code>, then
+   * <code>scalarType1.hashCode() == scalarType2.hashCode()</code>.
+   *
+   * @return		The hash code for this object.
+   */
+  public int hashCode() {
+    return getName().hashCode();
+  }
+
+  /**
+   * Change the primary name for this <CODE>ScalarType</CODE>.
+   * The original name can still be used.<P>
+   * If multiple aliases are created, the last one is dominant.<P>
+   * This is handy for translating standard VisAD <CODE>RealType</CODE>
+   * names to a language other than English.
+   *
+   * @param alias The new name.
+   *
+   * @exception TypeException If the new name is not valid.
+   */
+  public void alias(String alias)
+    throws TypeException
+  {
+    synchronized(getClass()) {
+      if (!Name.equals(Translations.get(alias))) {
+	validateName(alias, "alias");
+	Translations.put(alias, Name);
+      }
+      ReverseTranslations.put(Name, alias);
+    }
+  }
+
+  /**
+   * Returns this <CODE>ScalarType</CODE>'s name.  If an alias exists, then it
+   * it is returned; otherwise, the name given to the constructor is used.
+   *
+   * @return The name of this <CODE>ScalarType</CODE>.
+   */
+  public final String getName() {
+    synchronized(getClass()) {
+      String alias = (String )ReverseTranslations.get(Name);
+      if (alias != null) {
+	return alias;
+      }
+    }
+    return Name;
+  }
+
+  /**
+   * Returns the original name of this instance.  This method ignores any alias
+   * and returns the name given to the constructor.
+   *
+   * @return                      The original name.
+   */
+  public final String getOriginalName() {
+    return Name;
+  }
+
+	/**
+	 * Returns the alias of this instance or <code>null</code> if this instance
+	 * has no alias. This method returns the alias set by the most recent
+	 * {@link #alias(String)} invocation.
+	 * 
+	 * @return The alias or <code>null</code>.
+	 */
+  
+  public final String getAlias() {
+    synchronized(getClass()) {
+      return (String)ReverseTranslations.get(Name);
+    }
+  }
+
+  public String getNameWithBlanks() {
+    return getName().replace('_', ' ');
+  }
+
+  /**
+   * Get the <CODE>ScalarType</CODE> which has the specified name.
+   *
+   * @param name Name of <CODE>ScalarType</CODE>.
+   * @return Either the <CODE>ScalarType</CODE> if found,
+   *          or <CODE>null</CODE>.
+   */
+  public static synchronized ScalarType getScalarTypeByName(String name) {
+    if (name == null) {
+      return null;
+    }
+    checkQueue();
+    if (Translations.containsKey(name)) {
+      name = (String )Translations.get(name);
+    }
+    ScalarType st;
+    Object obj = ScalarHash.get(name);
+    st =
+      obj == null
+	? null
+	: (ScalarType)((WeakMapValue)obj).getValue();
+    return st;
+  }
+
+  /**
+   * Throw a <CODE>TypeException</CODE> if the name is invalid.
+   *
+   * @param name Name to check.
+   * @param type Type used in exception message.
+   *
+   * @exception TypeException If there is a problem with the name.
+   */
+  public static synchronized void validateName(String name, String type)
+    throws TypeException
+  {
+    if (name == null) {
+      throw new TypeException("ScalarType: " + type + " cannot be null");
+    }
+    if (name.indexOf(".") > -1 ||
+        name.indexOf(" ") > -1 ||
+        name.indexOf("(") > -1 ||
+        name.indexOf(")") > -1) {
+      throw new TypeException("ScalarType: " + type + " cannot contain " +
+                              "space . ( or ) " + name);
+    }
+    if (getScalarTypeByName(name) != null) {
+      throw new TypeException("ScalarType: " + type + " already used");
+    }
+    if (Translations.containsKey(name)) {
+      throw new TypeException("ScalarType: " + type + " already used" +
+			      " as an alias");
+    }
+  }
+
+/*
+  public static void dumpAliases()
+  {
+    java.util.Enumeration en;
+
+    boolean needHead = true;
+    en = Translations.keys();
+    while (en.hasMoreElements()) {
+      Object key = en.nextElement();
+      if (needHead) {
+        System.err.println("== Translation table");
+        needHead = false;
+      }
+      System.err.println("   \"" + key + "\" => \"" +
+                         Translations.get(key) + "\"");
+    }
+
+    boolean needMid = true;
+    en = ReverseTranslations.keys();
+    while (en.hasMoreElements()) {
+      Object key = en.nextElement();
+      if (needMid) {
+        if (needHead) {
+          System.err.println("== Reverse Translation table");
+          needHead = false;
+        } else {
+          System.err.println("-- Reverse Translation table");
+        }
+        needMid = false;
+      }
+      System.err.println("   \"" + key + "\" => \"" +
+                         ReverseTranslations.get(key) + "\"");
+    }
+    if (!needHead) {
+      System.err.println("==");
+    }
+  }
+*/
+
+  /**
+   * <p>Returns the instance corresponding to this newly deserialized instance.
+   * If a ScalarType with the same name as this instance already exists and
+   * is compatible with this instance, then it is returned.  Otherwise, this
+   * instance is returned. </p>
+   *
+   * <p>This method is protected so that it is always invoked during
+   * deserialization and final to prevent subclasses from evading it.</p>
+   *
+   * @return                        the unique ScalarType object corresponding
+   *                                to this object's name.
+   * @throws InvalidObjectException if an incompatible ScalarType with the same
+   *                                name as this instance already exists.
+   */
+  protected final Object readResolve()
+    throws InvalidObjectException
+  {
+    ScalarType st;
+    synchronized(getClass()) {
+	st = getScalarTypeByName(Name);
+	if (st == null) {
+	  ScalarHash.put(Name, new WeakMapValue(Name, this, queue));
+	  st = this;
+	}
+	else if (!equals(st)) {
+	  throw new InvalidObjectException(toString());
+	}
+    }
+    return st;
+  }
+
+  /**
+   * Checks the queue for garbage-collected instances and removes them from the
+   * hash table and translation tables.
+   */
+  private static synchronized void checkQueue() {
+    for (WeakMapValue ref; (ref = (WeakMapValue)queue.poll()) != null; ) {
+      Object name = ref.getKey();
+      ScalarHash.remove(name);
+      Object alias = ReverseTranslations.remove(name);
+      if (alias != null)
+	Translations.remove(alias);
+    }
+  }
+}
+
diff --git a/visad/ScaledUnit.java b/visad/ScaledUnit.java
new file mode 100644
index 0000000..578d54b
--- /dev/null
+++ b/visad/ScaledUnit.java
@@ -0,0 +1,727 @@
+//
+// ScaledUnit.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * A class that represents a certain amount of a derived unit.
+ * 
+ * Instances are immutable.
+ * 
+ * @author Steven R. Emmerson
+ * 
+ *         This is part of Steve Emerson's Unit package that has been
+ *         incorporated into VisAD.
+ */
+public final class ScaledUnit extends Unit implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * The amount of the associated derived unit.
+     */
+    final double              amount;
+
+    /**
+     * The underlying unit that's scaled.
+     */
+    final Unit                underUnit;
+
+    /**
+     * Construct a dimensionless scaled unit. The identifier will be empty.
+     * 
+     * @param amount
+     *            The given amount of this unit.
+     */
+    public ScaledUnit(final double amount) {
+        this(amount, "");
+    }
+
+    /**
+     * Construct a dimensionless scaled unit with an identifier.
+     * 
+     * @param amount
+     *            The given amount of this unit.
+     * @param identifier
+     *            Name or abbreviation for the unit. May be <code>null</code> or
+     *            empty.
+     */
+    public ScaledUnit(final double amount, final String identifier) {
+        super(identifier);
+        this.amount = amount;
+        underUnit = new DerivedUnit();
+    }
+
+    /**
+     * Constructs a scaled unit from another unit. The identifier will be that
+     * of the other unit if the amount is 1; otherwise, the identifier will be
+     * <code>null</code>.
+     * 
+     * @param amount
+     *            The given amount of the other unit (e.g. 0.9144 to create a
+     *            yard unit if <code>unit</code> represents a meter).
+     * @param unit
+     *            The other unit.
+     */
+    public ScaledUnit(final double amount, final Unit unit) {
+        this(amount, unit, null);
+    }
+
+    /**
+     * Constructs a scaled unit from another unit and an identifier.
+     * 
+     * @param amount
+     *            The given amount of the base unit (e.g. 0.9144 to create a
+     *            yard unit if <code>unit</code> represents a meter).
+     * @param unit
+     *            The other unit.
+     * @param identifier
+     *            Name or abbreviation for the unit. May be <code>null</code> or
+     *            empty. If {@code null} and if {@code amount} is {@code 1},
+     *            then the identifier of {@code unit} is used.
+     */
+    public ScaledUnit(final double amount, final Unit unit,
+            final String identifier) {
+        super(identifier != null
+                ? identifier
+                : amount == 1
+                        ? unit.getIdentifier()
+                        : null);
+        if (unit instanceof ScaledUnit) {
+            final ScaledUnit that = (ScaledUnit) unit;
+            this.amount = amount * that.amount;
+            this.underUnit = that.underUnit;
+        }
+        else {
+            this.amount = amount;
+            this.underUnit = unit;
+        }
+    }
+
+    public ScaledUnit(final double amount, final BaseUnit unit) {
+        this(amount, (Unit) unit);
+    }
+
+    public ScaledUnit(final double amount, final BaseUnit unit, final String id) {
+        this(amount, (Unit) unit, id);
+    }
+
+    public ScaledUnit(final double amount, final DerivedUnit unit) {
+        this(amount, (Unit) unit);
+    }
+
+    public ScaledUnit(final double amount, final DerivedUnit unit,
+            final String id) {
+        this(amount, (Unit) unit, id);
+    }
+
+    /**
+     * Factory method for creating a scaled unit. The identifier will be that of
+     * the input unit if the scaling amount is 1; otherwise, the identifier will
+     * be <code>null</code>.
+     * 
+     * @param amount
+     *            The given amount of the scaled unit (e.g. 3.0 to create a yard
+     *            unit if <code>unit</code> represents a foot.
+     * @param unit
+     *            The given unit.
+     * @return A corresponding scaled unit.
+     * @throws UnitException
+     *             Can't create Scaled Unit from <code>unit</code>.
+     */
+    public static ScaledUnit create(final double amount, final Unit unit)
+            throws UnitException {
+        return (amount == 1 && unit instanceof ScaledUnit)
+                ? (ScaledUnit) unit
+                : new ScaledUnit(amount, unit);
+    }
+
+    /**
+     * Returns an instance based on a scale amount and an underlying unit.
+     * 
+     * @param amount
+     *            The amount of {@code unit}
+     * @param unit
+     *            The underlying unit.
+     * @return An instance corresponding to the input.
+     * @throws UnitException
+     *             If the instance can't be created.
+     */
+    static Unit getInstance(final double amount, final Unit unit)
+            throws UnitException {
+        if (amount == 0) {
+            throw new IllegalArgumentException("Zero amount argument");
+        }
+        return (amount == 1)
+                ? unit
+                : new ScaledUnit(amount, unit);
+    }
+
+    /**
+     * <p>
+     * Indicates if this instance is dimensionless. A unit is dimensionless if
+     * it is a measure of a dimensionless quantity like angle or concentration.
+     * Examples of dimensionless units include radian, degree, steradian, and
+     * "g/kg".
+     * </p>
+     * 
+     * @return True if an only if this unit is dimensionless.
+     */
+    @Override
+    public boolean isDimensionless() {
+        return underUnit.isDimensionless();
+    }
+
+    /**
+     * Clones this unit, changing the identifier.
+     * 
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * @return A unit equal to this instance but with the given identifier.
+     */
+    @Override
+    protected Unit protectedClone(final String identifier) {
+        return new ScaledUnit(amount, underUnit, identifier);
+    }
+
+    @Override
+    public Unit scale(final double amount) throws UnitException {
+        return ScaledUnit.getInstance(amount * this.amount, underUnit);
+    }
+
+    @Override
+    public Unit shift(final double offset) throws UnitException {
+        return OffsetUnit.getInstance(offset, this);
+    }
+
+    @Override
+    public Unit log(final double base) {
+        return LogarithmicUnit.getInstance(base, this);
+    }
+
+    /**
+     * Raises this unit to a power.
+     * 
+     * @param power
+     *            The power to raise this unit by.
+     * @return The unit resulting from raising this unit to <code>power</code>.
+     * @throws UnitException
+     *             if the underlying unit can't be raised to the given power.
+     * promise: This unit has not been modified.
+     */
+    @Override
+    public Unit pow(final int power) throws UnitException {
+        return new ScaledUnit(Math.pow(amount, power), underUnit.pow(power));
+    }
+
+    /**
+     * Raises this unit to a power.
+     * 
+     * @param power
+     *            The power to raise this unit by. If this unit is not
+     *            dimensionless, then the value must be integral.
+     * @return The unit resulting from raising this unit to <code>power</code>.
+     * @throws IllegalArgumentException
+     *             This unit is not dimensionless and <code>power</code> has a
+     *             non-integral value.
+     * @throws UnitException
+     *             if the underlying unit can't be raised to the given power.
+     * promise: The unit has not been modified.
+     */
+    @Override
+    public Unit pow(final double power) throws IllegalArgumentException,
+            UnitException {
+        return new ScaledUnit(Math.pow(amount, power), underUnit.pow(power));
+    }
+
+    /**
+     * Returns the N-th root of this unit.
+     * 
+     * @param root
+     *            The root to take (e.g. 2 means square root). May not be zero.
+     * @return The unit corresponding to the <code>root</code>-th root of this
+     *         unit.
+     * @throws IllegalArgumentException
+     *             The root value is zero or the resulting unit would have a
+     *             non-integral unit dimension.
+     * @throws UnitException
+     *             if the underlying unit given can't have the given root taken.
+     * promise: This unit has not been modified.
+     */
+    @Override
+    public Unit root(final int root) throws IllegalArgumentException,
+            UnitException {
+        return new ScaledUnit(Math.pow(amount, 1. / root), underUnit.root(root));
+    }
+
+    /**
+     * Returns the definition of this unit.
+     * 
+     * @return The definition of this unit (e.g. "0.9144 m" for a yard).
+     */
+    @Override
+    public String getDefinition() {
+        String definition;
+        if (underUnit == null) {
+            /* Probably exception thrown during construction */
+            definition = "<unconstructed ScaledUnit>";
+        }
+        else {
+            final String derivedString = underUnit.toString();
+            definition = amount == 1
+                    ? derivedString
+                    : derivedString.length() == 0
+                            ? Double.toString(amount)
+                            : Double.toString(amount) + " " + derivedString;
+        }
+        return definition;
+    }
+
+    /**
+     * Get the scale amount
+     * 
+     * @return The scale amount
+     */
+    public double getAmount() {
+        return amount;
+    }
+
+    /**
+     * Get the underlying unit
+     * 
+     * @return The underlying unit
+     */
+    public Unit getUnit() {
+        return underUnit;
+    }
+
+    /**
+     * Multiplies this unit by another unit.
+     * 
+     * @param that
+     *            The unit with which to multiply this unit.
+     * @return The product of the two units.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             Meaningless operation.
+     */
+    @Override
+    public Unit multiply(final Unit that) throws UnitException {
+        return create(amount, underUnit.multiply(that));
+    }
+
+    /**
+     * Divides this unit by another unit.
+     * 
+     * @param that
+     *            The unit to divide into this unit.
+     * @return The quotient of the two units.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             Meaningless operation.
+     */
+    @Override
+    public Unit divide(final Unit that) throws UnitException {
+        return create(amount, underUnit.divide(that));
+    }
+
+    /**
+     * Divides this unit into another unit.
+     * 
+     * @param that
+     *            The unit to be divided by this unit.
+     * @return The quotient of the two units.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             Meaningless operation.
+     */
+    @Override
+    protected Unit divideInto(final Unit that) throws UnitException {
+        return create(1. / amount, underUnit.divideInto(that));
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that)
+            throws UnitException {
+        return toThis(values, that, true);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThis(final double[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        double[] newValues;
+        if (equals(that) || that instanceof PromiscuousUnit) {
+            newValues = (copy)
+                    ? (double[]) values.clone()
+                    : values;
+        }
+        else {
+            newValues = that.toThat(values, underUnit, copy);
+            for (int i = 0; i < newValues.length; ++i) {
+                if (newValues[i] == newValues[i]) {
+                    newValues[i] /= amount;
+                }
+            }
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            The values to be converted.
+     * @param that
+     *            The unit of <code>values</code>.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values in units of this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThis(final float[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        float[] newValues;
+        if (equals(that) || that instanceof PromiscuousUnit) {
+            newValues = (copy)
+                    ? (float[]) values.clone()
+                    : values;
+        }
+        else {
+            newValues = that.toThat(values, underUnit, copy);
+            for (int i = 0; i < newValues.length; ++i) {
+                if (newValues[i] == newValues[i]) {
+                    newValues[i] /= amount;
+                }
+            }
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * require: The units are identical.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double[] values, final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @return The converted values.
+     * require: The units are identical.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float[] values, final Unit that)
+            throws UnitException {
+        return toThat(values, that, true);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public double[] toThat(final double values[], final Unit that,
+            final boolean copy) throws UnitException {
+        double[] newValues = (copy)
+                ? (double[]) values.clone()
+                : values;
+        if (!(equals(that) || that instanceof PromiscuousUnit)) {
+            for (int i = 0; i < newValues.length; ++i) {
+                newValues[i] *= amount;
+            }
+            newValues = that.toThis(newValues, underUnit);
+        }
+        return newValues;
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values to be converted in units of this unit.
+     * @param that
+     *            The unit to which to convert the values.
+     * @param copy
+     *            if false and <code>that</code> equals this, return
+     *            <code>values</code>, else return a new array
+     * @return The converted values.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             The units are not convertible.
+     */
+    @Override
+    public float[] toThat(final float values[], final Unit that,
+            final boolean copy) throws UnitException {
+        float[] newValues = (copy)
+                ? (float[]) values.clone()
+                : values;
+        if (!(equals(that) || that instanceof PromiscuousUnit)) {
+            for (int i = 0; i < newValues.length; ++i) {
+                newValues[i] *= amount;
+            }
+            newValues = that.toThis(newValues, underUnit);
+        }
+        return newValues;
+    }
+
+    /**
+     * Indicate whether this unit is convertible with another unit. If one unit
+     * is convertible with another, then the <code>toThis(...)</code>/ and
+     * <code>toThat(...)</code> methods will not throw a UnitException. Unit A
+     * is convertible with unit B if and only if unit B is convertible with unit
+     * A; hence, calling-order is irrelevant.
+     * 
+     * @param unit
+     *            The other unit.
+     * @return True if and only if this unit is convertible with the other unit.
+     */
+    @Override
+    public boolean isConvertible(final Unit unit) {
+        return unit == null
+                ? false
+                : underUnit.isConvertible(unit);
+    }
+
+    private static void myAssert(final boolean assertion) {
+        if (!assertion) {
+            throw new AssertionError();
+        }
+    }
+
+    private static void myAssert(final Unit have, final Unit expect) {
+        if (!have.equals(expect)) {
+            throw new AssertionError(have.toString() + " != " + expect);
+        }
+    }
+
+    private static void myAssert(final double have, final double expect) {
+        if (have != expect) {
+            throw new AssertionError("" + have + " != " + expect);
+        }
+    }
+
+    private static void myAssert(final double[] have, final double[] expect) {
+        if (!Arrays.equals(have, expect)) {
+            throw new AssertionError("" + have + " != " + expect);
+        }
+    }
+
+    /**
+     * Test this class.
+     * 
+     * @param args
+     *            Arguments (ignored).
+     * @exception UnitException
+     *                A problem occurred.
+     */
+    public static void main(final String[] args) throws UnitException {
+        final BaseUnit meter = BaseUnit.addBaseUnit("Length", "meter");
+        final BaseUnit second = BaseUnit.addBaseUnit("Time", "second");
+        final DerivedUnit meterPerSec = new DerivedUnit(new BaseUnit[] { meter,
+                second }, new int[] { 1, -1 });
+        final Unit milePerHour = new ScaledUnit(0.44704, meterPerSec);
+        final Unit milePerHour2 = milePerHour.pow(2);
+
+        myAssert(milePerHour, milePerHour);
+        myAssert(!milePerHour.equals(meterPerSec));
+        myAssert(milePerHour.isConvertible(milePerHour));
+        myAssert(milePerHour.isConvertible(meterPerSec));
+        myAssert(!milePerHour.equals(milePerHour2));
+        myAssert(!milePerHour.isConvertible(milePerHour2));
+        myAssert(!milePerHour.equals(meter));
+        myAssert(!milePerHour.isConvertible(meter));
+        myAssert(milePerHour2, milePerHour2);
+        myAssert(milePerHour2.isConvertible(milePerHour2));
+        myAssert(milePerHour2.sqrt(), milePerHour);
+
+        final BaseUnit kg = BaseUnit.addBaseUnit("Mass", "kilogram");
+        final DerivedUnit kgPerSec = new DerivedUnit(new BaseUnit[] { kg,
+                second }, new int[] { 1, -1 });
+        final Unit poundPerSec = new ScaledUnit(0.453592, kgPerSec);
+
+        myAssert(milePerHour.multiply(poundPerSec), poundPerSec
+                .multiply(milePerHour));
+        myAssert(milePerHour.divide(poundPerSec), poundPerSec.divide(
+                milePerHour).pow(-1));
+
+        myAssert(milePerHour.toThis(1, meterPerSec) != 1);
+        myAssert(milePerHour.toThat(1, meterPerSec) != 1);
+
+        myAssert(milePerHour.toThis(1, meterPerSec), meterPerSec.toThat(1,
+                milePerHour));
+        myAssert(meterPerSec.toThat(milePerHour.toThat(1, meterPerSec),
+                milePerHour), 1);
+
+        final double[] values = { 1, 2 };
+
+        myAssert(milePerHour.toThis(values, meterPerSec), meterPerSec.toThat(
+                values, milePerHour));
+        myAssert(meterPerSec.toThat(milePerHour.toThat(values, meterPerSec),
+                milePerHour), values);
+
+        System.out.println("Checking exceptions:");
+        try {
+            milePerHour.toThis(5, poundPerSec);
+            throw new AssertionError();
+        }
+        catch (final UnitException e) {
+            System.out.println(e.getMessage());
+        }
+        System.out.println("Done");
+    }
+
+    /**
+     * Indicates if this instance is equal to a unit.
+     * 
+     * @param unit
+     *            The unit.
+     * @return <code>true</code> if and only if this instance equals the unit.
+     */
+    @Override
+    public boolean equals(final Unit unit) {
+        if (this == unit) {
+            return true;
+        }
+        if (amount == 1)
+            return underUnit.equals(unit);
+        if (!(unit instanceof ScaledUnit)) {
+            return false;
+        }
+        final ScaledUnit that = (ScaledUnit) unit;
+        return amount == that.amount && underUnit.equals(that.underUnit);
+    }
+
+    /**
+     * Returns the hash code of this instance. {@link Object#hashCode()} should
+     * be overridden whenever {@link Object#equals(Object)} is.
+     * 
+     * @return The hash code of this instance (includes the values).
+     */
+    @Override
+    public int hashCode() {
+        if (hashCode == 0) {
+            hashCode = amount == 1
+                ? underUnit.hashCode()
+                : underUnit.hashCode() ^ Double.valueOf(amount).hashCode();
+        }
+        return hashCode;
+    }
+
+    @Override
+    public DerivedUnit getDerivedUnit() {
+        return underUnit.getDerivedUnit();
+    }
+
+}
diff --git a/visad/Set.java b/visad/Set.java
new file mode 100644
index 0000000..51b3a7f
--- /dev/null
+++ b/visad/Set.java
@@ -0,0 +1,895 @@
+//
+// Set.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+//
+// TO DO:
+//
+// finish GriddednDSet using VisAD Enumerated logic.
+//
+// add MultiGridded for union of grids
+// Field evalualtion as mean of non-missing values in each grid
+//
+// Field and FlatField: options (mode?) for Interpolated and NotInterpolated
+//
+// Field and FlatField: test for operations on IntegerSet and optimize
+//
+// change lots of methods to default protection (visible within package only)
+//
+//
+
+/**
+ * <P>Set is the abstract superclass of the VisAD hierarchy of sets.<P>
+ *
+ * <P>Set-s are subsets of R^n for n>0.  For the most part, Set objects are
+ * immutable (but see {@link SampledSet#getSamples(boolean)}).<P>
+ */
+public abstract class Set extends DataImpl implements SetIface {
+
+  int DomainDimension; // this is a subset of R^DomainDimension
+  int Length;          // number of samples
+  CoordinateSystem DomainCoordinateSystem;
+  transient boolean hashCodeSet = false;
+  transient int hashCode = 0;
+
+  Unit[] SetUnits;
+  ErrorEstimate[] SetErrors;
+
+  /** caches of Set-s that return false and true to equals();
+      useful for GriddedSet and other Set subclasses which have
+      expensive equals tests;
+      WLH 6 Nov 97 - don't use these. they hinder garbage collection
+      and won't accomplish much practical
+  private static final int CACHE_LENGTH = 10;
+  private Set[] NotEqualsCache;
+  private int LastNotEquals;
+  private Set[] EqualsCache;
+  private int LastEquals;
+*/
+
+  /** construct a Set object */
+  public Set(MathType type) throws VisADException {
+    this(type, null, null, null);
+  }
+
+  /**
+   * Constructs a Set object with a non-default CoordinateSystem.<p>
+   *
+   * @param type		The type of the Set.
+   * @param coord_sys		The CoordinateSystem associated with this
+   *				Set.
+   */
+  public Set(MathType type, CoordinateSystem coord_sys) throws VisADException {
+    this(type, coord_sys, null, null);
+  }
+
+  /**
+   * Constructs a Set object with a non-default CoordinateSystem, non-default
+   * Unit-s, and non-default errors.  This is the most general constructor.<p>
+   *
+   * @param type		The MathType of the set.  May be a RealType,
+   *				a RealTupleType, or a SetType.
+   * @param coord_sys		Optional coordinate system for the domain of the
+   *				set.  May be <code>null</code>, in which case
+   *				the default coordinate system of the domain is
+   *				used.
+   * @param units               Optional units for the values.  May
+   *                            be <code> null</code>, in which case
+   *                            the default units of the domain are
+   *                            used.  If the <code>i</code>th element is
+   *                            non-<code>null</code> and the RealType of the
+   *                            corresponding domain component is an interval,
+   *                            then the unit that is actually used is <code>
+   *                            units[i].getAbsoluteUnit()</code>.
+   * @param errors              Error estimates.  May be <code>null</code>.
+   *                            <code>errors[i]</code> is the error estimate
+   *                            for the <code>i</code>-th component and may be
+   *                            <code>null</code>.
+   */
+  public Set(MathType type, CoordinateSystem coord_sys, Unit[] units,
+             ErrorEstimate[] errors) throws VisADException {
+    super(adjustType(type));
+    Length = 0;
+    DomainDimension = getDimension(type);
+    RealTupleType DomainType = ((SetType) Type).getDomain();
+    CoordinateSystem cs = DomainType.getCoordinateSystem();
+    if (coord_sys == null) {
+      DomainCoordinateSystem = cs;
+    }
+    else {
+      if (cs == null || !cs.getReference().equals(coord_sys.getReference())) {
+        throw new CoordinateSystemException(
+          "Set: coord_sys " + coord_sys.getReference() +
+          " must match Type.DefaultCoordinateSystem " +
+          (cs == null ? null : cs.getReference()));
+      }
+      DomainCoordinateSystem = coord_sys;
+    }
+    if (DomainCoordinateSystem != null &&
+        !Unit.canConvertArray(DomainCoordinateSystem.getCoordinateSystemUnits(),
+                              DomainType.getDefaultUnits())) {
+      throw new UnitException("Set: CoordinateSystem Units must be " +
+                              "convertable with DomainType default Units");
+    }
+
+    if (units == null) {
+      SetUnits = (DomainCoordinateSystem == null) ?
+                 DomainType.getDefaultUnits() :
+                 DomainCoordinateSystem.getCoordinateSystemUnits();
+    }
+    else {
+      if (units.length != DomainDimension) {
+        throw new UnitException("Set: units dimension " + units.length +
+                                " does not match Domain dimension " +
+                                DomainDimension);
+      }
+      SetUnits = new Unit[DomainDimension];
+      Unit[] dunits = DomainType.getDefaultUnits();
+      for (int i=0; i<DomainDimension; i++) {
+        if (units[i] == null && dunits[i] != null) {
+          SetUnits[i] = dunits[i];
+        }
+        else {
+          SetUnits[i] =
+	    ((RealType)DomainType.getComponent(i)).isInterval()
+	      ? units[i].getAbsoluteUnit()
+	      : units[i];
+        }
+      }
+    }
+    if(!Unit.canConvertArray(SetUnits, DomainType.getDefaultUnits())) {
+      throw new UnitException(
+          "Set: Actual units not convertable with DomainType default units ");
+    }
+    if (SetUnits == null) SetUnits = new Unit[DomainDimension];
+
+    SetErrors = new ErrorEstimate[DomainDimension];
+    if (errors != null) {
+      if (errors.length != DomainDimension) {
+        throw new SetException("Set: errors dimension " + errors.length +
+                               " does not match Domain dimension " +
+                               DomainDimension);
+      }
+      for (int i=0; i<DomainDimension; i++) SetErrors[i] = errors[i];
+    }
+  }
+
+  /** get DomainDimension (i.e., this is a subset of R^DomainDimension) */
+  static int getDimension(MathType type) throws VisADException {
+    if (type == null) {
+      throw new TypeException("Set.getDimension: type cannot be null");
+    }
+    if (type instanceof SetType) {
+      return ((SetType) type).getDomain().getDimension();
+    }
+    else if (type instanceof RealTupleType) {
+      return ((RealTupleType) type).getDimension();
+    }
+    else if (type instanceof RealType) {
+      return 1;
+    }
+    else {
+      throw new TypeException("Set: Type must be SetType or RealTupleType," +
+                              " not " + type.getClass().getName());
+    }
+  }
+
+  static MathType adjustType(MathType type) throws VisADException {
+    if (type == null) {
+      throw new TypeException("Set.adjustType: type cannot be null");
+    }
+    if (type instanceof SetType) {
+      return type;
+    }
+    else if (type instanceof RealTupleType) {
+      return new SetType(type);
+    }
+    else if (type instanceof RealType) {
+      return new SetType(type);
+    }
+    else {
+      throw new TypeException("Set: Type must be SetType, RealTupleType" +
+                              " or RealType, not " +
+                              type.getClass().getName());
+    }
+  }
+
+  /**
+   * Returns the units of the values in the set.  The units may differ from the
+   * default units of the underlying MathType but will be convertible with them.
+   * @return                    The units of the values in the set.  Will not be
+   *                            <code>null</code>.  RETURN_VALUE<code>[i]</code>
+   *                            is the unit of the <code>i</code>-th component
+   *                            and may be <code>null</code>.
+   */
+  public Unit[] getSetUnits() {
+    return Unit.copyUnitsArray(SetUnits);
+  }
+
+  /**
+   * Returns the error estimates of the values in the set.
+   * @return                    Error estimates for the set.  Will not be
+   *                            <code>null</code>.  RETURN_VALUE<code>[i]</code>
+   *                            is the error estimate for the <code>i</code>-th
+   *                            component and may be <code>null</code>.
+   */
+  public ErrorEstimate[] getSetErrors() {
+    return ErrorEstimate.copyErrorsArray(SetErrors);
+  }
+
+  /**
+   * Gets the coordinate system of this domain set (DomainCoordinateSystem).
+   * @return                    The coordinate system of this domain
+   *                            set.  This will be the coordinate
+   *                            system passed to the constructor if
+   *                            non-<code>null</code>; otherwise, the
+   *                            (default) coordinate system of the
+   *                            underlying RealTupleType (which may be
+   *                            <code>null</code>).
+   */
+  public CoordinateSystem getCoordinateSystem() {
+    return DomainCoordinateSystem;
+  }
+
+  /** get DomainDimension (i.e., this is a subset of R^DomainDimension) */
+  public int getDimension() {
+    return DomainDimension;
+  }
+
+  /** for non-SimpleSet, ManifoldDimension = DomainDimension */
+  public int getManifoldDimension() {
+    return DomainDimension;
+  }
+
+  /** get the number of samples */
+  public int getLength() throws VisADException {
+    return Length;
+  }
+
+  /**
+   * A wrapper around {@link #getLength() getLength} for JPython.
+   *
+   * @return The number of elements in the Set
+   */
+  public int __len__() throws VisADException {
+    return Length;
+  }
+
+  /** return an enumeration of sample indices in a spatially
+      coherent order; this is useful for efficiency */
+  public int[] getWedge() {
+    int[] wedge = new int[Length];
+    for (int i=0; i<Length; i++) wedge[i] = i;
+    return wedge;
+  }
+
+  /** return an enumeration of sample values in index order
+     (i.e., not in getWedge order); the return array is
+     organized as float[domain_dimension][number_of_samples] */
+  public float[][] getSamples() throws VisADException {
+    return getSamples(true);
+  }
+
+  public float[][] getSamples(boolean copy) throws VisADException {
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToValue(indices);
+  }
+
+  public double[][] getDoubles() throws VisADException {
+    return getDoubles(true);
+  }
+
+  public double[][] getDoubles(boolean copy) throws VisADException {
+    return floatToDouble(getSamples(true));
+  }
+
+  public void cram_missing(boolean[] range_select) {
+  }
+
+  /**
+   * return Set values corresponding to Set indices
+   * @param index array of integer indices
+   * @return float[domain_dimension][indices.length] array of
+   *         Set values
+   * @throws VisADException a VisAD error occurred
+   */
+  public abstract float[][] indexToValue(int[] index) throws VisADException;
+
+  /**
+   * return Set indices of Set values closest to value elements
+   *        (return -1 for any value outside Set range)
+   * @param value float[domain_dimension][number_of_values] array of
+   *        Set values
+   * @return array of integer indices
+   * @throws VisADException a VisAD error occurred
+   */
+  public abstract int[] valueToIndex(float[][] value) throws VisADException;
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException {
+    return computeRanges(type, shadow, null, false);
+  }
+
+  /**
+   * this default does not set ranges - it is used by FloatSet and
+   * DoubleSet
+   */
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow,
+                                  double[][] ranges, boolean domain)
+         throws VisADException {
+    setAnimationSampling(type, shadow, domain);
+    return shadow;
+  }
+
+  /** domain == true is this is the domain of a Field */
+  void setAnimationSampling(ShadowType type, DataShadow shadow, boolean domain)
+       throws VisADException {
+    // default does nothing
+  }
+
+  /** merge 1D sets; used for default animation set */
+  public Set merge1DSets(Set set) throws VisADException {
+    if (DomainDimension != 1 || set.getDimension() != 1 ||
+        equals(set)) return this;
+    int length = getLength();
+    // all indices in this
+    int[] indices = getWedge();
+    // all values in this
+    double[][] old_values = indexToDouble(indices); // WLH 21 Nov 2001
+    // transform values from this to set
+    ErrorEstimate[] errors_out = new ErrorEstimate[1];
+
+    double[][] values = CoordinateSystem.transformCoordinates(
+                   ((SetType) set.getType()).getDomain(),
+                   set.getCoordinateSystem(), set.getSetUnits(),
+                   null /* set.getSetErrors() */,
+                   ((SetType) Type).getDomain(),
+                   DomainCoordinateSystem,
+                   SetUnits, null /* SetErrors */, old_values);
+    // find indices of set not covered by this
+    int set_length = set.getLength();
+    boolean[] set_indices = new boolean[set_length];
+    for (int i=0; i<set_length; i++) set_indices[i] = true;
+    if (set_length > 1) {
+      // set indices for values in this
+      int[] test_indices = set.doubleToIndex(values);
+      try {
+        for (int i=0; i<length; i++) {
+          if (test_indices[i] > -1) set_indices[test_indices[i]] = false;
+        }
+      } catch (ArrayIndexOutOfBoundsException aioobe) {
+        throw new VisADException("Cannot merge sets");
+      }
+    }
+    else {
+      double[][] set_values = set.getDoubles();
+      double set_val = set_values[0][0];
+      double min = Double.MAX_VALUE;
+      // double max = Double.MIN_VALUE;
+      double max = -Double.MAX_VALUE;
+      for (int i=0; i<length; i++) {
+        if (values[0][i] > max) max = values[0][i];
+        if (values[0][i] < min) min = values[0][i];
+      }
+      double delt = (max - min) / length;
+      //System.out.println("min = " + min + " max = " + max + " delt = " + delt);
+      if ((min - delt) <= set_val && set_val <= (max + delt)) {
+        set_indices[0] = false;
+      }
+    }
+
+    // now set_indices = true for indices of set not covered by this
+    int num_new = 0;
+    for (int i=0; i<set_length; i++) if (set_indices[i]) num_new++;
+    if (num_new == 0) return this; // all covered, so nothing to do
+    // not all covered, so merge values of this with values of set
+    // not covered; first get uncovered indices
+    int[] new_indices = new int[num_new];
+    num_new = 0;
+    for (int i=0; i<set_length; i++) {
+      if (set_indices[i]) {
+        new_indices[num_new] = i;
+        num_new++;
+      }
+    }
+
+    // get uncovered values
+    double[][] new_values = set.indexToDouble(new_indices);
+
+    // transform values for Units and CoordinateSystem
+    new_values = CoordinateSystem.transformCoordinates(
+                     ((SetType) Type).getDomain(),
+                     DomainCoordinateSystem, SetUnits, null /* errors_out */,
+                     ((SetType) set.getType()).getDomain(),
+                     set.getCoordinateSystem(), set.getSetUnits(),
+                     null /* set.getSetErrors() */, new_values);
+
+    // merge uncovered values with values of this
+    double[][] all_values = new double[1][length + num_new];
+    // WLH 21 Nov 2001
+    for (int i=0; i<length; i++) all_values[0][i] = old_values[0][i];
+    for (int i=0; i<num_new; i++) {
+      all_values[0][length + i] = new_values[0][i];
+    }
+
+    // sort all_values then construct Gridded1DSet
+    // just use ErrorEstimates from this
+    QuickSort.sort(all_values[0]);
+    return new Gridded1DDoubleSet(Type, all_values, all_values[0].length,
+                                  DomainCoordinateSystem, SetUnits,
+                                  SetErrors, false);
+  }
+
+  public Set makeSpatial(SetType type, float[][] samples) throws VisADException {
+    throw new SetException("Set.makeSpatial: not valid for this Set");
+  }
+
+  public VisADGeometryArray make1DGeometry(byte[][] color_values)
+         throws VisADException {
+    throw new SetException("Set.make1DGeometry: not valid for this Set");
+  }
+
+  public VisADGeometryArray make2DGeometry(byte[][] color_values,
+         boolean indexed) throws VisADException {
+    throw new SetException("Set.make2DGeometry: not valid for this Set");
+  }
+
+  public VisADGeometryArray[] make3DGeometry(byte[][] color_values)
+         throws VisADException {
+    throw new SetException("Set.make3DGeometry: not valid for this Set");
+  }
+
+  public VisADGeometryArray makePointGeometry(byte[][] color_values)
+         throws VisADException {
+    throw new SetException("Set.makePointGeometry: not valid for this Set");
+  }
+
+  /** return basic lines in array[0], fill-ins in array[1]
+      and labels in array[2] */
+  public VisADGeometryArray[][] makeIsoLines(float[] intervals,
+                  float lowlimit, float highlimit, float base,
+                  float[] fieldValues, byte[][] color_values,
+                  boolean[] swap, boolean dash,
+                  boolean fill, ScalarMap[] smap,
+                  double[] scale, double label_size, boolean sphericalDisplayCS
+                  ) throws VisADException {
+    throw new SetException("Set.makeIsoLines: not valid for this Set");
+  }
+
+  public VisADGeometryArray makeIsoSurface(float isolevel,
+         float[] fieldValues, byte[][] color_values, boolean indexed)
+         throws VisADException {
+    throw new SetException("Set.makeIsoSurface: not valid for this Set");
+  }
+
+  /**
+   * Returns an array of sample-point values corresponding to an array of 
+   * sample-point indicies.
+   *
+   * @param index              The indicies of the sample points.
+   * @return                   A corresponding array of sample-point values.
+   *                           <em>RETURN_VALUE</em><code>[i][j]</code> is the
+   *                           <code>j</code>th component of sample-point
+   *                           <code>i</code>.
+   * @throws VisADException    if a VisAD failure occurs.
+   */
+  public double[][] indexToDouble(int[] index) throws VisADException {
+    return floatToDouble(indexToValue(index));
+  }
+
+  public int[] doubleToIndex(double[][] value) throws VisADException {
+    return valueToIndex(doubleToFloat(value));
+  }
+
+  public static double[][] floatToDouble(float[][] value) {
+    if (value == null) return null;
+    double[][] val = new double[value.length][];
+    for (int i=0; i<value.length; i++) {
+      if (value[i] == null) {
+        val[i] = null;
+      }
+      else {
+        val[i] = new double[value[i].length];
+        for (int j=0; j<value[i].length; j++) {
+          val[i][j] = value[i][j];
+        }
+      }
+    }
+    return val;
+  }
+
+  public static float[][] doubleToFloat(double[][] value) {
+    if (value == null) return null;
+    float[][] val = new float[value.length][];
+    for (int i=0; i<value.length; i++) {
+      if (value[i] == null) {
+        val[i] = null;
+      }
+      else {
+        val[i] = new float[value[i].length];
+        for (int j=0; j<value[i].length; j++) {
+          val[i][j] = (float) value[i][j];
+        }
+      }
+    }
+    return val;
+  }
+
+  public static float[][] copyFloats(float[][] samples) {
+    if (samples == null) return null;
+    int dim = samples.length;
+    float[][] s_copy = new float[dim][];
+    for (int j=0; j<dim; j++) {
+      int len = samples[j].length;
+      s_copy[j] = new float[len];
+      System.arraycopy(samples[j], 0, s_copy[j], 0, len);
+    }
+    return s_copy;
+  }
+
+  public static double[][] copyDoubles(double[][] samples) {
+    if (samples == null) return null;
+    int dim = samples.length;
+    double[][] s_copy = new double[dim][];
+    for (int j=0; j<dim; j++) {
+      int len = samples[j].length;
+      s_copy[j] = new double[len];
+      System.arraycopy(samples[j], 0, s_copy[j], 0, len);
+    }
+    return s_copy;
+  }
+
+  /**
+   * <p>Returns the indexes of the neighboring points for every point
+   * in the set. <code>neighbors.length</code> should be at least
+   * <code>getLength()</code>.  On return, <code>neighbors[i][j]</code>
+   * will be the index of the <code>j </code>-th neighboring point of
+   * point <code>i</code>.  This method will allocate and set the array
+   * <code>neighbors[i]</code> for all <code>i</code>.</p>
+   *
+   * <p>This implementation always throws an {@link UnimplementedException}.</p>
+   *
+   * @param neighbors                The array to contain the indexes of the
+   *                                 neighboring points.
+   * @throws NullPointerException    if the array is <code>null</code>.
+   * @throws ArrayIndexOutOfBoundsException
+   *                                 if <code>neighbors.length < getLength()
+   *                                 </code>.
+   * @throws VisADException          if a VisAD failure occurs.
+   */
+  public void getNeighbors( int[][] neighbors )
+              throws VisADException
+  {
+    throw new UnimplementedException("Set: getNeighbors()");
+  }
+
+  public void getNeighbors( int[][] neighbors, float[][] weights )
+         throws VisADException
+  {
+    throw new UnimplementedException("Set: getNeighbors()");
+  }
+
+  /**
+   * <p>Returns the indexes of the neighboring points along a manifold
+   * dimesion for every point in the set. Elements <code>[i][0]</code>
+   * and <code>[i][1]</code> of the returned array are the indexes of the
+   * neighboring sample points in the direction of decreasing and increasing
+   * manifold index, respectively, for sample point <code>i</code>.  If a sample
+   * point doesn't have a neighboring point (because it is an exterior point,
+   * for example) then the value of the corresponding index will be -1.</p>
+   *
+   * <p>This implementation always throws an {@link UnimplementedException}.</p>
+   *
+   * @param dimension	           The index of the manifold dimension along
+   *                                which to return the neighboring points.
+   * @throws ArrayIndexOutOfBoundsException
+   *                                if <code>manifoldIndex < 0 || 
+   *                                manifoldIndex >= getManifoldDimension()
+   *                                </code>.
+   * @throws VisADException         if a VisAD failure occurs.
+   * @see #getManifoldDimension()
+   */
+  public int[][] getNeighbors(int dimension)
+                 throws VisADException
+  {
+    throw new UnimplementedException("Set: getNeighbors()");
+  }
+
+  /** test set against a cache of Set-s not equal to this */
+  public boolean testNotEqualsCache(Set set) {
+/* WLH 6 Nov 97
+    if (NotEqualsCache == null || set == null) return false;
+    for (int i=0; i<CACHE_LENGTH; i++) {
+      if (set == NotEqualsCache[i]) return true;
+    }
+*/
+    return false;
+  }
+
+  /** add set to a cache of Set-s not equal to this */
+  public void addNotEqualsCache(Set set) {
+/* WLH 6 Nov 97
+    if (NotEqualsCache == null) {
+      NotEqualsCache = new Set[CACHE_LENGTH];
+      for (int i=0; i<CACHE_LENGTH; i++) NotEqualsCache[i] = null;
+      LastNotEquals = 0;
+    }
+    NotEqualsCache[LastNotEquals] = set;
+    LastNotEquals = (LastNotEquals + 1) % CACHE_LENGTH;
+*/
+  }
+
+  /** test set against a cache of Set-s equal to this */
+  public boolean testEqualsCache(Set set) {
+/* WLH 6 Nov 97
+    if (EqualsCache == null || set == null) return false;
+    for (int i=0; i<CACHE_LENGTH; i++) {
+      if (set == EqualsCache[i]) return true;
+    }
+*/
+    return false;
+  }
+
+  /** add set to a cache of Set-s equal to this */
+  public void addEqualsCache(Set set) {
+/* WLH 6 Nov 97
+    if (EqualsCache == null) {
+      EqualsCache = new Set[CACHE_LENGTH];
+      for (int i=0; i<CACHE_LENGTH; i++) EqualsCache[i] = null;
+      LastEquals = 0;
+    }
+    EqualsCache[LastEquals] = set;
+    LastEquals = (LastEquals + 1) % CACHE_LENGTH;
+*/
+  }
+
+  /** test equality of SetUnits and DomainCoordinateSystem
+      between this and set */
+  public boolean equalUnitAndCS(Set set) {
+    if (DomainCoordinateSystem == null) {
+      if (set.DomainCoordinateSystem != null) return false;
+    }
+    else {
+      if (!DomainCoordinateSystem.equals(set.DomainCoordinateSystem)) {
+        return false;
+      }
+    }
+    if (SetUnits != null || set.SetUnits != null) {
+      if (SetUnits == null || set.SetUnits == null) return false;
+      int n = SetUnits.length;
+      if (n != set.SetUnits.length) return false;
+      for (int i=0; i<n; i++) {
+        if (SetUnits[i] == null && set.SetUnits[i] == null) continue;
+        if (SetUnits[i] == null || set.SetUnits[i] == null) return false;
+        if (!SetUnits[i].equals(set.SetUnits[i])) return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns the hash code of the units and coordinate-system.  This is the
+   * hash code analogue of {@link #equalUnitAndCS(Set)}.
+   * @return			The hash code of the units and coordinate
+   *				system.
+   */
+  public int unitAndCSHashCode()
+  {
+    int	hashCode = 0;
+    if (DomainCoordinateSystem != null)
+      hashCode ^= DomainCoordinateSystem.hashCode();
+    if (SetUnits != null)
+      for (int i=0; i<SetUnits.length; i++)
+        if (SetUnits[i] != null)
+	  hashCode ^= SetUnits[i].hashCode();
+    return hashCode;
+  }
+
+  /** for JPython */
+  public Data __getitem__(int index) throws VisADException, RemoteException {
+    int[] indices = {index};
+    double[][] values = indexToDouble(indices);
+    RealType[] types = ((SetType) getType()).getDomain().getRealComponents();
+    Real[] reals = new Real[DomainDimension];
+    for (int i=0; i<DomainDimension; i++) {
+      reals[i] = new Real(types[i], values[i][0], SetUnits[i]);
+    }
+    if (DomainDimension == 1) {
+      return reals[0];
+    }
+    else {
+      RealTupleType rtt = ((SetType) getType()).getDomain();
+      return new RealTuple(rtt, reals, getCoordinateSystem());
+    }
+  }
+
+  /** test for equality */
+  public abstract boolean equals(Object set);
+
+  /**
+   * Clones this instance.
+   *
+   * @return                            A clone of this instance.
+   */
+  public Object clone() {
+    try {
+      return super.clone();
+    }
+    catch (CloneNotSupportedException ex) {
+      throw new RuntimeException("Assertion failure");  // can't happen
+    }
+  }
+
+  // WLH 3 April 2003
+  public Data unary( int op, MathType new_type, int sampling_mode, int error_mode )
+              throws VisADException, RemoteException {
+    if (op == Data.NOP) {
+      return (Set) cloneButType(new_type);
+    }
+    else {
+      throw new TypeException("Set: unary");
+    }
+  }
+
+  /** copy this Set, but give it a new MathType;
+      this is safe, since constructor checks consistency of
+      DomainCoordinateSystem and SetUnits with Type */
+  public abstract Object cloneButType(MathType type) throws VisADException;
+
+  public String longString() throws VisADException {
+    return longString("");
+  }
+
+  public String longString(String pre) throws VisADException {
+    throw new TypeException("Set.longString");
+  }
+
+  /* run 'java visad.Set' to test the MathType and Set classes */
+  public static void main(String args[]) throws VisADException {
+
+    // visad.type.Crumb crumb_type = new visad.type.Crumb(); // this works
+    // visad.data.Crumb crumb_data = new visad.data.Crumb(); // this works
+    // but this does not work:   Crumb crumb = new Crumb();
+
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType count = RealType.getRealType("count");
+
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+
+    try {
+      TupleType radiancexxx = new TupleType(types2);
+    }
+    catch (TypeException e) {
+      // System.out.println(e);
+      e.printStackTrace(); // same as e.printStackTrace(System.out);
+    }
+    try {
+      FunctionType image_tuplexxx = new FunctionType(earth_location, radiance);
+    }
+    catch (TypeException e) {
+      System.out.println(e);
+    }
+    try {
+      FunctionType image_visxxx = new FunctionType(earth_location, vis_radiance);
+    }
+    catch (TypeException e) {
+      System.out.println(e);
+    }
+
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+    FunctionType image_vis = new FunctionType(earth_location, vis_radiance);
+    FunctionType image_ir = new FunctionType(earth_location, ir_radiance);
+
+    FunctionType ir_histogram = new FunctionType(ir_radiance, count);
+
+    System.out.println(image_tuple);
+    System.out.println(ir_histogram);
+
+    Linear2DSet set2d = new Linear2DSet(earth_location,
+                                        0.0, 127.0, 128, 0.0, 127.0, 128);
+    Linear1DSet set1d = new Linear1DSet(ir_radiance, 0.0, 255.0, 256);
+    Integer2DSet iset2d = new Integer2DSet(earth_location, 128, 128);
+    Integer1DSet iset1d = new Integer1DSet(ir_radiance, 256);
+
+    FlatField imaget1 = new FlatField(image_tuple, set2d);
+    FlatField imagev1 = new FlatField(image_vis, set2d);
+    FlatField imager1 = new FlatField(image_ir, set2d);
+    FlatField histogram1 = new FlatField(ir_histogram, set1d);
+
+    System.out.println(imaget1);
+    System.out.println(histogram1);
+
+    System.out.println(set2d);
+    System.out.println(set1d);
+    System.out.println(iset2d);
+    System.out.println(iset1d);
+
+    if (set1d instanceof IntegerSet) System.out.println(" set1d ");
+    if (set2d instanceof IntegerSet) System.out.println(" set2d ");
+    if (iset1d instanceof IntegerSet) System.out.println(" iset1d ");
+    if (iset2d instanceof IntegerSet) System.out.println(" iset2d ");
+    System.out.println("");
+
+    int i = 14;
+    short s = 12;
+    byte b = 10;
+    Real t = new Real(1.0);
+    Real x = new Real(12);
+    Real y = new Real(12L);
+    Real u = new Real(i);
+    Real v = new Real(s);
+    Real w = new Real(b);
+
+    System.out.println(t);
+    System.out.println("" + x + " " + y + " " + u + " " + v + " " + w);
+  }
+
+/* Here's the output:
+
+iris 235% java visad.Set
+visad.TypeException: TupleType: all components are RealType, must use RealTupleType
+        at visad.TupleType.<init>(TupleType.java:47)
+        at visad.Set.main(Set.java:155)
+FunctionType (Real): (Latitude(degrees), Longitude(degrees)) -> (vis_radiance, ir_radiance)
+FunctionType (Real): (ir_radiance) -> count
+FlatField  missing
+
+FlatField  missing
+
+Linear2DSet: Length = 16384
+  Dimension 1: Length = 128 Range = 0 to 127
+  Dimension 2: Length = 128 Range = 0 to 127
+
+Linear1DSet: Length = 256 Range = 0 to 255
+
+Integer2DSet: Length = 16384
+  Dimension 1: Length = 128
+  Dimension 2: Length = 128
+
+Integer1DSet: Length = 256
+
+ iset1d
+ iset2d
+
+1
+12 12 14 12 10
+iris 236%
+
+*/
+
+}
+
diff --git a/visad/Set1DIface.java b/visad/Set1DIface.java
new file mode 100644
index 0000000..a91068a
--- /dev/null
+++ b/visad/Set1DIface.java
@@ -0,0 +1,47 @@
+//
+// Set1DIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Interface to a finite set of samples of R.<P>
+ */
+public interface Set1DIface
+{
+  /**
+   * Returns the minimum value in the set.
+   *
+   * @return			The lowest value in the set.
+   */
+  float getLowX();
+
+  /**
+   * Returns the maximum value in the set.
+   *
+   * @return			The highest value in the set.
+   */
+  float getHiX();
+}
diff --git a/visad/SetException.java b/visad/SetException.java
new file mode 100644
index 0000000..099bf39
--- /dev/null
+++ b/visad/SetException.java
@@ -0,0 +1,38 @@
+//
+// SetException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   SetException is an exception for an error with a VisAD sampling.<P>
+*/
+public class SetException extends VisADException {
+
+  public SetException() { super(); }
+  public SetException(String s) { super(s); }
+
+}
+
diff --git a/visad/SetIface.java b/visad/SetIface.java
new file mode 100644
index 0000000..96ffc9d
--- /dev/null
+++ b/visad/SetIface.java
@@ -0,0 +1,229 @@
+//
+// SetIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Interface to the abstract superclass of the VisAD hierarchy of sets.<P>
+ * Sets are subsets of R^n for n>0.
+ */
+public interface SetIface
+  extends Data
+{
+  /**
+   * Returns the units of the samples in the set.
+   *
+   * @return			The units of the samples in the set.  Element
+   *				[i] is the unit of the i-th coordinate.
+   */
+  Unit[] getSetUnits();
+
+  /**
+   * Returns the error estimates of the samples in the set.
+   *
+   * @return			The error estimates of the samples in the set.
+   *				Element [i] is the error estimate of the i-th
+   *				coordinate.
+   */
+  ErrorEstimate[] getSetErrors();
+
+  /**
+   * Returns the coordinate system transformation of the set.
+   *
+   * @return			The coordinate system transformation of the set.
+   */
+  CoordinateSystem getCoordinateSystem();
+
+  /**
+   * Returns the rank of the samples in the set.
+   *
+   * @return			The rank of the samples in the set.
+   */
+  int getDimension();
+
+  /**
+   * Returns the rank of the manifold of the set.
+   *
+   * @return			The rank of the manifold of the set.
+   */
+  int getManifoldDimension();
+
+  /**
+   * Returns the number of samples in the set.
+   *
+   * @return			The number of samples in the set.
+   */
+  int getLength() throws VisADException;
+
+  /**
+   * Returns the samples of the set corresponding to an array of 1-D indices.
+   *
+   * @param index		The array of 1-D indices.
+   * @return			The values of the set corresponding to the input
+   *				indices.  Element [i][j] is the i-th coordinate
+   *				of the sample at index <code>index[j]</code>.
+   * @throws VisADException	VisAD failure.
+   */
+  float[][] indexToValue(int[] index) throws VisADException;
+
+  /**
+   * Returns the 1-D indices corresponding to an array of points.
+   * The set must have at least two samples.
+   *
+   * @param value               An array of points. <code>value[i][j]</code> is
+   *                            the i-th coordinate of the j-th point.
+   * @return                    Indices of the nearest samples in the set.
+   *                            If the j-th point lies within the set, then
+   *                            element [i] is the index of the closest sample;
+   *                            otherwise, element [i] is -1.
+   * @throws VisADException	VisAD failure.
+   */
+  int[] valueToIndex(float[][] value) throws VisADException;
+
+  Set makeSpatial(SetType type, float[][] values) throws VisADException;
+
+  /**
+   * Returns a zig-zagging enumeration of sample indices with good coherence.
+   *
+   * @return			Indices of the samples of the set in a zig-
+   *				zagging pattern with good coherence.
+   */
+  int[] getWedge();
+
+  /**
+   * Returns an enumeration of the samples of the set in index order.  This is
+   * the same as <code>getSamples(true)</code>.
+   *
+   * @return			An enumeration of the samples of the set.
+   *				Element [i][j] is the i-th coordinate of the
+   *				j-th sample.
+   * @throws VisADException	VisAD failure.
+   * @see #getSamples(boolean copy)
+   */
+  float[][] getSamples() throws VisADException;
+
+  /**
+   * Returns an enumeration of the samples of the set in index order.
+   *
+   * @param copy		Whether or not to make a copy of the samples
+   *				of the set.
+   * @return			An enumeration of the samples of the set.
+   *				Element [i][j] is the i-th coordinate of the
+   *				j-th sample.
+   * @throws VisADException	VisAD failure.
+   */
+  float[][] getSamples(boolean copy) throws VisADException;
+
+  /**
+   * Returns an enumeration of the samples of the set in index order.  This is
+   * the same as <code>getDoubles(true)</code>.
+   *
+   * @return			An enumeration of the samples of the set.
+   *				Element [i][j] is the i-th coordinate of the
+   *				j-th sample.
+   * @throws VisADException	VisAD failure.
+   * @see #getDoubles(boolean copy)
+   */
+  double[][] getDoubles() throws VisADException;
+
+  /**
+   * Returns an enumeration of the samples of the set in index order.
+   *
+   * @param copy		Whether or not to make a copy of the samples
+   *				of the set.
+   * @return			An enumeration of the samples of the set.
+   *				Element [i][j] is the i-th coordinate of the
+   *				j-th sample.
+   * @throws VisADException	VisAD failure.
+   */
+  double[][] getDoubles(boolean copy) throws VisADException;
+
+  void cram_missing(boolean[] range_select);
+
+  Set merge1DSets(Set set) throws VisADException;
+
+  VisADGeometryArray make1DGeometry(byte[][] color_values)
+    throws VisADException;
+
+  VisADGeometryArray make2DGeometry(byte[][] color_values, boolean indexed)
+    throws VisADException;
+
+  VisADGeometryArray[] make3DGeometry(byte[][] color_values)
+    throws VisADException;
+
+  VisADGeometryArray makePointGeometry(byte[][] color_values)
+    throws VisADException;
+
+  VisADGeometryArray[][] makeIsoLines(
+      float[] intervals,
+      float lowlimit,
+      float highlimit,
+      float base,
+      float[] fieldValues,
+      byte[][] color_values,
+      boolean[] swap,
+      boolean dash,
+      boolean fill,
+      ScalarMap[] smap,
+      double[] scale,
+      double label_size,
+      boolean sphericalDisplayCS)
+    throws VisADException;
+
+  VisADGeometryArray makeIsoSurface(
+      float isolevel,
+      float[] fieldValues,
+      byte[][] color_values,
+      boolean indexed)
+    throws VisADException;
+
+  double[][] indexToDouble(int[] index)
+    throws VisADException;
+
+  int[] doubleToIndex(double[][] value)
+    throws VisADException;
+
+  void getNeighbors(int[][] neighbors)
+    throws VisADException;
+
+  void getNeighbors(int[][] neighbors, float[][] weights)
+    throws VisADException;
+
+  int[][] getNeighbors(int dimension) throws VisADException;
+
+  boolean equalUnitAndCS(Set set);
+
+  boolean equals(java.lang.Object set);
+
+  /**
+   * Clones this set -- changing the MathType.
+   *
+   * @param type		The MathType for the clone.
+   * @return			A clone of this set with the new MathType.
+   * @throws VisADException	VisAD failure.
+   */
+  Object cloneButType(MathType type) throws VisADException;
+}
diff --git a/visad/SetType.java b/visad/SetType.java
new file mode 100644
index 0000000..5264d7c
--- /dev/null
+++ b/visad/SetType.java
@@ -0,0 +1,125 @@
+//
+// SetType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.Vector;
+
+/**
+   SetType is the VisAD data type for subsets of R^n for n>0.<P>
+*/
+public class SetType extends MathType {
+
+  private RealTupleType Domain;
+
+  /** type must be a RealType or a RealTupleType */
+  public SetType(MathType type) throws VisADException {
+    super();
+    if (type instanceof RealType) {
+      RealType[] types = {(RealType) type};
+      Domain = new RealTupleType(types);
+    }
+    else if (type instanceof RealTupleType) {
+      Domain = (RealTupleType) type;
+    }
+    else {
+      throw new TypeException("SetType: Domain must be RealType or RealTupleType");
+    }
+  }
+
+  public boolean equals(Object type) {
+    if (!(type instanceof SetType)) return false;
+    return Domain.equals(((SetType) type).getDomain());
+  }
+
+  public boolean equalsExceptName(MathType type) {
+    if (!(type instanceof SetType)) return false;
+    return Domain.equalsExceptName(((SetType) type).getDomain());
+  }
+
+  /*- TDR May 1998  */
+  public boolean equalsExceptNameButUnits( MathType type ) {
+    if (!(type instanceof SetType)) return false;
+    return Domain.equalsExceptNameButUnits(((SetType) type).getDomain());
+  }
+
+  /*- TDR June 1998  */
+  public MathType cloneDerivative( RealType d_partial )
+         throws VisADException
+  {
+    throw new UnimplementedException("SetType: cloneDerivative");
+  }
+
+  /*- TDR July 1998  */
+  public MathType binary( MathType type, int op, Vector names )
+         throws VisADException
+  {
+    throw new UnimplementedException("SetType.binary");
+  }
+
+  /*- TDR July 1998  */
+  public MathType unary( int op, Vector names )
+         throws VisADException
+  {
+    return this; // WLH 3 April 2003
+  }
+
+/* WLH 5 Jan 2000
+  public String toString() {
+    return "Set" + Domain.toString();
+  }
+*/
+
+  public String prettyString(int indent) {
+    // return toString();  WLH 5 Jan 2000
+    String s = Domain.toString();
+    if (s.lastIndexOf("(") < 0) {
+      return "Set(" + s + ")";
+    }
+    else {
+      return "Set" + s;
+    }
+  }
+
+  public Data missingData() throws VisADException {
+    return new DoubleSet(this);
+  }
+
+  /** if the domain passed to constructor was a RealType,
+      getDomain returns a RealTupleType with that RealType
+      as its single component */
+  public RealTupleType getDomain() {
+    return Domain;
+  }
+
+  public ShadowType buildShadowType(DataDisplayLink link, ShadowType parent)
+             throws VisADException, RemoteException {
+    return link.getRenderer().makeShadowSetType(this, link, parent);
+  }
+
+}
+
diff --git a/visad/ShadowFunctionOrSetType.java b/visad/ShadowFunctionOrSetType.java
new file mode 100644
index 0000000..024b873
--- /dev/null
+++ b/visad/ShadowFunctionOrSetType.java
@@ -0,0 +1,4064 @@
+//
+// ShadowFunctionOrSetType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+MEM - memory use reduction strategy:
+
+changes marked by MEM_WLH
+
+1. if (isTexture) no assembleSpatial  NOT NEEDED
+
+  but if (isTexture) then domain_values = null,
+  so spatial display_values[i] = null so assembleSpatial
+  does nothing (except construct a SingletonSet spatial_set)
+
+2. byte[][] assembleColor  DONE
+
+  this has a bad space / time tradeoff in makeIso*,
+  so create a boolean in GMC to choose between
+  (Irregular3DSet, Gridded3DSet) and their slower extensions
+
+3. after use, set  display_values[i] = null;  DONE
+
+  done in many ShadowType.assemble*  - marked by MEM_WLH
+
+4. don't fill arrays that won't get used
+
+  are there any of these ??
+
+5. replace getValues() by getFloats(false)
+  already done for getSamples
+
+6. assembleColors first  DONE
+
+7. boolean[] range_select
+
+8. in-line byteToFloat and floatToByte  DONE
+  VisADCanvasJ2D, Gridded3DSet, Irregular3DSet
+
+N. iso-surface computation uses:
+
+  Irregular3DSet.makeIsosurface:
+
+float[len] fieldValues
+float[3 or 4][len] auxValues (color_values)
+float[3][nvertex] fieldVertices
+float[3 or 4][nvertex] auxLevels
+int[1][npolygons][3 or 4] polyToVert
+int[1][nvertex][nverts[i]] vertToPoly
+
+int[Delan.Tri.length][4] polys
+int[Delan.NumEdges] globalToVertex
+float[DomainDimension][Delan.NumEdges] edgeInterp
+float[3 or 4][Delan.NumEdges] auxInterp
+
+
+
+
+  Gridded3DSet.makeIsoSurface:
+
+start with nvertex_estimate = 4 * npolygons + 100
+
+int[num_cubes] ptFLAG
+int[xdim_x_ydim_x_zdim] ptAUX
+int[num_cubes+1] pcube
+float[1][4 * npolygons] VX
+float[1][4 * npolygons] VY
+float[1][4 * npolygons] VZ
+float[3 or 4][nvet] color_temps
+float[3 or 4][len] cfloat
+int[7 * npolygons] Vert_f_Pol
+int[1][36 * npolygons] Pol_f_Vert
+
+  number of bytes = 268 (or 284) * npolygons +
+                    24 (or 28) * len
+
+isosurf:
+
+float[3][len] samples
+float[3 or 4][nvertex_estimate] tempaux
+
+  number of bytes = 48 (or 64) * npolygons +
+                    12 * len
+
+
+float[npolygons] NxA, NxB, NyA, NyB, NzA, NzB
+float[npolygons] Pnx, Pny, Pnz
+float[nvertex] NX, NY, NZ
+
+  number of bytes = 84 * npolygons
+
+make_normals:
+
+none
+
+
+  total number of bytes = 36 (or 40) * len +
+                          400 (to 432) * npolygons
+
+so if len = 0.5M and npolygons = 0.1M
+bytes = 20M + 43.2M = 63.2M
+
+*/
+
+
+package visad;
+
+import java.awt.color.*;
+import java.awt.image.*;
+import java.rmi.*;
+import java.text.*;
+import java.util.*;
+
+
+/**
+   The ShadowFunctionOrSetType class is an abstract parent for
+   classes that implement ShadowFunctionType or ShadowSetType.<P>
+*/
+public abstract class ShadowFunctionOrSetType extends ShadowType {
+
+  ShadowRealTupleType Domain;
+  ShadowType Range; // null for ShadowSetType
+
+  /** RangeComponents is an array of ShadowRealType-s that are
+      ShadowRealType components of Range or ShadowRealType
+      components of ShadowRealTupleType components of Range;
+      a non-ShadowRealType and non-ShadowTupleType Range is marked
+      by null;
+      components of a ShadowTupleType Range that are neither
+      ShadowRealType nor ShadowRealTupleType are ignored */
+  ShadowRealType[] RangeComponents; // null for ShadowSetType
+  ShadowRealType[] DomainComponents;
+  ShadowRealType[] DomainReferenceComponents;
+
+  /** true if range is ShadowRealType or Flat ShadowTupleType
+      not the same as FunctionType.Flat;
+      also true for ShadowSetType */
+  boolean Flat;
+
+  /** value_indices from parent */
+  int[] inherited_values;
+
+  /** this constructor is a bit of a kludge to get around
+      single inheritance problems */
+  public ShadowFunctionOrSetType(MathType t, DataDisplayLink link, ShadowType parent,
+                                 ShadowRealTupleType domain, ShadowType range)
+      throws VisADException, RemoteException {
+    super(t, link, parent);
+    Domain = domain;
+    Range = range;
+    if (this instanceof ShadowFunctionType) {
+      Flat = (Range instanceof ShadowRealType) ||
+             (Range instanceof ShadowTextType) ||
+             (Range instanceof ShadowTupleType &&
+              ((ShadowTupleType) Range).isFlat());
+      MultipleSpatialDisplayScalar = Domain.getMultipleSpatialDisplayScalar() ||
+                                     Range.getMultipleSpatialDisplayScalar();
+      MultipleDisplayScalar = Domain.getMultipleDisplayScalar() ||
+                              Range.getMultipleDisplayScalar();
+      MappedDisplayScalar = Domain.getMappedDisplayScalar() ||
+                              Range.getMappedDisplayScalar();
+      RangeComponents = getComponents(Range, true);
+    }
+    else if (this instanceof ShadowSetType) {
+      Flat = true;
+      MultipleDisplayScalar = Domain.getMultipleDisplayScalar();
+      MappedDisplayScalar = Domain.getMappedDisplayScalar();
+      RangeComponents = null;
+    }
+    else {
+      throw new DisplayException("ShadowFunctionOrSetType: must be " +
+                                 "ShadowFunctionType or ShadowSetType");
+    }
+    DomainComponents = getComponents(Domain, false);
+    DomainReferenceComponents = getComponents(Domain.getReference(), false);
+  }
+
+  public boolean getFlat() {
+    return Flat;
+  }
+
+  public ShadowRealType[] getRangeComponents() {
+    return RangeComponents;
+  }
+
+  public ShadowRealType[] getDomainComponents() {
+    return DomainComponents;
+  }
+
+  public ShadowRealType[] getDomainReferenceComponents() {
+    return DomainReferenceComponents;
+  }
+
+  /** used by FlatField.computeRanges */
+  int[] getRangeDisplayIndices() throws VisADException {
+    if (!(this instanceof ShadowFunctionType)) {
+      throw new DisplayException("ShadowFunctionOrSetType.getRangeDisplay" +
+                                 "Indices: must be ShadowFunctionType");
+    }
+    int n = RangeComponents.length;
+    int[] indices = new int[n];
+    for (int i=0; i<n; i++) {
+      indices[i] = RangeComponents[i].getIndex();
+    }
+    return indices;
+  }
+
+  public int[] getInheritedValues() {
+    return inherited_values;
+  }
+
+  /** checkIndices: check for rendering difficulty, etc */
+  public int checkIndices(int[] indices, int[] display_indices,
+             int[] value_indices, boolean[] isTransform, int levelOfDifficulty)
+      throws VisADException, RemoteException {
+    // add indices & display_indices from Domain
+    int[] local_indices = Domain.sumIndices(indices);
+    int[] local_display_indices = Domain.sumDisplayIndices(display_indices);
+    int[] local_value_indices = Domain.sumValueIndices(value_indices);
+
+    if (Domain.testTransform()) Domain.markTransform(isTransform);
+    if (this instanceof ShadowFunctionType) {
+      Range.markTransform(isTransform);
+    }
+
+    // get value_indices arrays used by doTransform
+    inherited_values = copyIndices(value_indices);
+    // check for any mapped
+    if (levelOfDifficulty == NOTHING_MAPPED) {
+      if (checkAny(local_display_indices)) {
+        levelOfDifficulty = NESTED;
+      }
+    }
+
+    // test legality of Animation and SelectValue in Domain
+    int avCount = checkAnimationOrValue(Domain.getDisplayIndices());
+    if (Domain.getDimension() != 1) {
+      if (avCount > 0) {
+        throw new BadMappingException("Animation and SelectValue may only occur " +
+                                      "in 1-D Function domain: " +
+                                      "ShadowFunctionOrSetType.checkIndices");
+      }
+      else {
+        // eventually ShadowType.testTransform is used to mark Animation,
+        // Value or Range as isTransform when multiple occur in Domain;
+        // however, temporary hack in Renderer.isTransformControl requires
+        // multiple occurence of Animation and Value to throw an Exception
+        if (avCount > 1) {
+          throw new BadMappingException("only one Animation and SelectValue may " +
+                                        "occur Set domain: " +
+                                        "ShadowFunctionOrSetType.checkIndices");
+        }
+      }
+    }
+
+    if (Flat || this instanceof ShadowSetType) {
+      if (this instanceof ShadowFunctionType) {
+        if (Range instanceof ShadowTupleType) {
+          local_indices =
+            ((ShadowTupleType) Range).sumIndices(local_indices);
+          local_display_indices =
+            ((ShadowTupleType) Range).sumDisplayIndices(local_display_indices);
+          local_value_indices =
+            ((ShadowTupleType) Range).sumValueIndices(local_value_indices);
+        }
+        else if (Range instanceof ShadowScalarType) {
+          ((ShadowScalarType) Range).incrementIndices(local_indices);
+          local_display_indices = addIndices(local_display_indices,
+            ((ShadowScalarType) Range).getDisplayIndices());
+          local_value_indices = addIndices(local_value_indices,
+            ((ShadowScalarType) Range).getValueIndices());
+        }
+
+        // test legality of Animation and SelectValue in Range
+        if (checkAnimationOrValue(Range.getDisplayIndices()) > 0) {
+          throw new BadMappingException("Animation and SelectValue may not " +
+                                        "occur in Function range: " +
+                                        "ShadowFunctionOrSetType.checkIndices");
+        }
+      } // end if (this instanceof ShadowFunctionType)
+      anyContour = checkContour(local_display_indices);
+      anyFlow = checkFlow(local_display_indices);
+      anyShape = checkShape(local_display_indices);
+      anyText = checkText(local_display_indices);
+
+      LevelOfDifficulty =
+        testIndices(local_indices, local_display_indices, levelOfDifficulty);
+/*
+System.out.println("ShadowFunctionOrSetType.checkIndices 1:" +
+                   " LevelOfDifficulty = " + LevelOfDifficulty +
+                   " isTerminal = " + isTerminal +
+                   " Type = " + Type.prettyString());
+*/
+      // compute Domain type
+      if (!Domain.getMappedDisplayScalar()) {
+        Dtype = D0;
+      }
+      else if (Domain.getAllSpatial() && checkR4(Domain.getDisplayIndices())) {
+        Dtype = D1;
+      }
+      else if (checkR1D3(Domain.getDisplayIndices())) {
+        Dtype = D3;
+      }
+      else if (checkR2D2(Domain.getDisplayIndices())) {
+        Dtype = D2;
+      }
+      else if (checkAnimationOrValue(Domain.getDisplayIndices()) > 0) {
+        Dtype = D4;
+      }
+      else {
+        Dtype = Dbad;
+      }
+
+      if (this instanceof ShadowFunctionType) {
+        // compute Range type
+        if (!Range.getMappedDisplayScalar()) {
+          Rtype = R0;
+        }
+        else if (checkR1D3(Range.getDisplayIndices())) {
+          Rtype = R1;
+        }
+        else if (checkR2D2(Range.getDisplayIndices())) {
+          Rtype = R2;
+        }
+        else if (checkR3(Range.getDisplayIndices())) {
+          Rtype = R3;
+        }
+        else if (checkR4(Range.getDisplayIndices())) {
+          Rtype = R4;
+        }
+        else {
+          Rtype = Rbad;
+        }
+      }
+      else { // this instanceof ShadowSetType
+        Rtype = R0; // implicit - Set has no range
+      }
+
+      if (LevelOfDifficulty == NESTED) {
+        if (Dtype != Dbad && Rtype != Rbad) {
+          if (Dtype == D4) {
+            LevelOfDifficulty = SIMPLE_ANIMATE_FIELD;
+          }
+          else {
+            LevelOfDifficulty = SIMPLE_FIELD;
+          }
+        }
+        else {
+          LevelOfDifficulty = LEGAL;
+        }
+      }
+/*
+System.out.println("ShadowFunctionOrSetType.checkIndices 2:" +
+                   " LevelOfDifficulty = " + LevelOfDifficulty +
+                   " Dtype = " + Dtype + " Rtype = " + Rtype);
+*/
+      adjustProjectionSeam = checkAdjustProjectionSeam();
+
+      if (this instanceof ShadowFunctionType) {
+        float[] default_values = getLink().getDefaultValues();
+        boolean textureEnabled = 
+          default_values[display.getDisplayScalarIndex(Display.TextureEnable)] > 0.5f;
+
+        boolean pointMode =  
+          default_values[
+            display.getDisplayScalarIndex(Display.PointMode)] > 0.5f;
+
+        // test for texture mapping
+        // WLH 30 April 99
+        isTextureMap = !getMultipleDisplayScalar() &&
+                       getLevelOfDifficulty() == ShadowType.SIMPLE_FIELD &&
+                       ((FunctionType) getType()).getReal() &&   // ??
+                       Domain.getDimension() == 2 &&
+                       Domain.getAllSpatial() &&
+                       !Domain.getSpatialReference() &&
+                       Display.DisplaySpatialCartesianTuple.equals(
+                               Domain.getDisplaySpatialTuple() ) &&
+                       checkColorAlphaRange(Range.getDisplayIndices()) &&
+                       checkAny(Range.getDisplayIndices()) &&
+                       textureEnabled &&
+                       //!display.getGraphicsModeControl().getPointMode();
+                       !pointMode;
+
+        curvedTexture = getLevelOfDifficulty() == ShadowType.SIMPLE_FIELD &&
+                        Domain.getAllSpatial() &&
+                        checkSpatialOffsetColorAlphaRange(Domain.getDisplayIndices()) &&
+                        checkSpatialOffsetColorAlphaRange(Range.getDisplayIndices()) &&
+                        checkAny(Range.getDisplayIndices()) &&
+                        //!display.getGraphicsModeControl().getPointMode();
+                        !pointMode;
+
+        // WLH 15 March 2000
+        // isTexture3D = !getMultipleDisplayScalar() &&
+        isTexture3D =  getLevelOfDifficulty() == ShadowType.SIMPLE_FIELD &&
+                       ((FunctionType) getType()).getReal() &&   // ??
+                       Domain.getDimension() == 3 &&
+                       Domain.getAllSpatial() &&
+                       // WLH 1 April 2000
+                       // !Domain.getMultipleDisplayScalar() && // WLH 15 March 2000
+                       checkSpatialRange(Domain.getDisplayIndices()) && // WLH 1 April 2000
+                       !Domain.getSpatialReference() &&
+                       Display.DisplaySpatialCartesianTuple.equals(
+                               Domain.getDisplaySpatialTuple() ) &&
+                       checkColorAlphaRange(Range.getDisplayIndices()) &&
+                       checkAny(Range.getDisplayIndices()) &&
+                       textureEnabled &&
+                       //!display.getGraphicsModeControl().getPointMode();
+                       !pointMode;
+
+        // note GgraphicsModeControl.setTextureEnable(false) disables this
+        isLinearContour3D =
+                       getLevelOfDifficulty() == ShadowType.SIMPLE_FIELD &&
+                       ((FunctionType) getType()).getReal() &&   // ??
+                       Domain.getDimension() == 3 &&
+                       Domain.getAllSpatial() &&
+                       !Domain.getMultipleDisplayScalar() &&
+                       !Domain.getSpatialReference() &&
+                       Display.DisplaySpatialCartesianTuple.equals(
+                               Domain.getDisplaySpatialTuple() ) &&
+                       checkContourColorAlphaRange(Range.getDisplayIndices()) &&
+                       checkContour(Range.getDisplayIndices());
+
+/*
+System.out.println("checkIndices.isTexture3D = " + isTexture3D + " " +
+                   (getLevelOfDifficulty() == ShadowType.SIMPLE_FIELD) + " " +
+                   ((FunctionType) getType()).getReal() + " " +
+                   (Domain.getDimension() == 3) + " " +
+                   Domain.getAllSpatial() + " " +
+                   checkSpatialRange(Domain.getDisplayIndices()) + " " +
+                   !Domain.getSpatialReference() + " " +
+                   Display.DisplaySpatialCartesianTuple.equals(
+                        Domain.getDisplaySpatialTuple() ) + " " +
+                   checkColorAlphaRange(Range.getDisplayIndices()) + " " +
+                   checkAny(Range.getDisplayIndices()) + " " +
+                   textureEnabled &&
+                   //!display.getGraphicsModeControl().getPointMode() );
+                   !pointMode;
+*/
+
+/*
+System.out.println("checkIndices.isTextureMap = " + isTextureMap + " " +
+                   !getMultipleDisplayScalar() + " " +
+                   (getLevelOfDifficulty() == ShadowType.SIMPLE_FIELD) + " " +
+                   ((FunctionType) getType()).getReal() + " " +
+                   (Domain.getDimension() == 2) + " " +
+                   Domain.getAllSpatial() + " " +
+                   !Domain.getSpatialReference() + " " +
+                   Display.DisplaySpatialCartesianTuple.equals(
+                               Domain.getDisplaySpatialTuple() ) + " " +
+                   checkColorAlphaRange(Range.getDisplayIndices()) + " " +
+                   checkAny(Range.getDisplayIndices()) + " " +
+                   textureEnabled &&
+                   //!display.getGraphicsModeControl().getPointMode() );
+                   !pointMode;
+
+System.out.println("checkIndices.curvedTexture = " + curvedTexture + " " +
+                    (getLevelOfDifficulty() == ShadowType.SIMPLE_FIELD) + " " +
+                    (Domain.getDimension() == 2) + " " +
+                    Domain.getAllSpatial() + " " +
+                    checkSpatialOffsetColorAlphaRange(Domain.getDisplayIndices()) + " " +
+                    checkSpatialOffsetColorAlphaRange(Range.getDisplayIndices()) + " " +
+                    checkAny(Range.getDisplayIndices()) + " " +
+                    textureEnabled &&
+                    //!display.getGraphicsModeControl().getPointMode() );
+                    !pointMode;
+*/
+      }
+    }
+    else { // !Flat && this instanceof ShadowFunctionType
+      if (levelOfDifficulty == NESTED) {
+        if (!checkNested(Domain.getDisplayIndices())) {
+          levelOfDifficulty = LEGAL;
+        }
+      }
+      LevelOfDifficulty = Range.checkIndices(local_indices, local_display_indices,
+                                             local_value_indices, isTransform,
+                                             levelOfDifficulty);
+/*
+System.out.println("ShadowFunctionOrSetType.checkIndices 3:" +
+                   " LevelOfDifficulty = " + LevelOfDifficulty);
+*/
+    }
+
+    return LevelOfDifficulty;
+  }
+
+  public ShadowRealTupleType getDomain() {
+    return Domain;
+  }
+
+  public ShadowType getRange() {
+    return Range;
+  }
+
+  /** mark Control-s as needing re-Transform */
+  void markTransform(boolean[] isTransform) {
+    if (Range != null) Range.markTransform(isTransform);
+  }
+
+  /** transform data into a (Java3D or Java2D) scene graph;
+      add generated scene graph components as children of group;
+      group is Group (Java3D) or VisADGroup (Java2D);
+      value_array are inherited valueArray values;
+      default_values are defaults for each display.DisplayRealTypeVector;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer,
+                             ShadowType shadow_api)
+         throws VisADException, RemoteException {
+
+    visad.util.Trace.msg("ShadowFunctionOrSetType:"+data.getType());
+    // return if data is missing or no ScalarMaps
+    if (data.isMissing()) return false;
+    if (LevelOfDifficulty == NOTHING_MAPPED) return false;
+
+    // if transform has taken more than 500 milliseconds and there is
+    // a flag requesting re-transform, throw a DisplayInterruptException
+    DataDisplayLink link = renderer.getLink();
+
+// if (link != null) System.out.println("\nstart doTransform " + (System.currentTimeMillis() - link.start_time));
+
+    if (link != null) {
+      boolean time_flag = false;
+      if (link.time_flag) {
+        time_flag = true;
+      }
+      else {
+        if (500 < System.currentTimeMillis() - link.start_time) {
+          link.time_flag = true;
+          time_flag = true;
+        }
+      }
+      if (time_flag) {
+        if (link.peekTicks()) {
+          throw new DisplayInterruptException("please wait . . .");
+        }
+        Enumeration maps = link.getSelectedMapVector().elements();
+        while(maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) maps.nextElement();
+          if (map.peekTicks(renderer, link)) {
+            throw new DisplayInterruptException("please wait . . .");
+          }
+        }
+      }
+    } // end if (link != null)
+
+    // get 'shape' flags
+    boolean anyContour = getAnyContour();
+    boolean anyFlow = getAnyFlow();
+    boolean anyShape = getAnyShape();
+    boolean anyText = getAnyText();
+
+    boolean indexed = shadow_api.wantIndexed();
+
+    // get some precomputed values useful for transform
+    // length of ValueArray
+    int valueArrayLength = display.getValueArrayLength();
+    // mapping from ValueArray to DisplayScalar
+    int[] valueToScalar = display.getValueToScalar();
+    // mapping from ValueArray to MapVector
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    // array to hold values for various mappings
+    float[][] display_values = new float[valueArrayLength][];
+
+    // get values inherited from parent;
+    // assume these do not include SelectRange, SelectValue
+    // or Animation values - see temporary hack in
+    // DataRenderer.isTransformControl
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0) {
+        display_values[i] = new float[1];
+        display_values[i][0] = value_array[i];
+      }
+    }
+
+    // check for only contours and only disabled contours
+    if (getIsTerminal() && anyContour &&
+        !anyFlow && !anyShape && !anyText) {   // WLH 13 March 99
+      boolean any_enabled = false;
+      for (int i=0; i<valueArrayLength; i++) {
+        int displayScalarIndex = valueToScalar[i];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+        if (real.equals(Display.IsoContour) && inherited_values[i] == 0) {
+          // non-inherited IsoContour, so generate contours
+          ContourControl control = (ContourControl)
+            ((ScalarMap) MapVector.elementAt(valueToMap[i])).getControl();
+          boolean[] bvalues = new boolean[2];
+          float[] fvalues = new float[5];
+          control.getMainContours(bvalues, fvalues);
+          if (bvalues[0]) any_enabled = true;
+        }
+      }
+      if (!any_enabled) return false;
+    }
+
+    Set domain_set = null;
+    Unit[] dataUnits = null;
+    CoordinateSystem dataCoordinateSystem = null;
+    if (this instanceof ShadowFunctionType) {
+      // currently only implemented for Field
+      // must eventually extend to Function
+      if (!(data instanceof Field)) {
+        throw new UnimplementedException("data must be Field: " +
+                                         "ShadowFunctionOrSetType.doTransform: ");
+      }
+      domain_set = ((Field) data).getDomainSet();
+      dataUnits = ((Function) data).getDomainUnits();
+      dataCoordinateSystem = ((Function) data).getDomainCoordinateSystem();
+    }
+    else if (this instanceof ShadowSetType) {
+      domain_set = (Set) data;
+      dataUnits = ((Set) data).getSetUnits();
+      dataCoordinateSystem = ((Set) data).getCoordinateSystem();
+    }
+    else {
+      throw new DisplayException(
+          "must be ShadowFunctionType or ShadowSetType: " +
+          "ShadowFunctionOrSetType.doTransform");
+    }
+
+    float[][] domain_values = null;
+    double[][] domain_doubles = null;
+    Unit[] domain_units = ((RealTupleType) Domain.getType()).getDefaultUnits();
+    int domain_length;
+    int domain_dimension;
+    try {
+      domain_length = domain_set.getLength();
+      domain_dimension = domain_set.getDimension();
+    }
+    catch (SetException e) {
+      return false;
+    }
+
+    // ShadowRealTypes of Domain
+    ShadowRealType[] DomainComponents = getDomainComponents();
+
+    int alpha_index = display.getDisplayScalarIndex(Display.Alpha);
+
+    // array to hold values for Text mapping (can only be one)
+    String[] text_values = null;
+    // get any text String and TextControl inherited from parent
+    TextControl text_control = shadow_api.getParentTextControl();
+    String inherited_text = shadow_api.getParentText();
+    if (inherited_text != null) {
+      text_values = new String[domain_length];
+      for (int i=0; i<domain_length; i++) {
+        text_values[i] = inherited_text;
+      }
+    }
+
+    boolean isTextureMap = getIsTextureMap() &&
+                           // default_values[alpha_index] > 0.99 &&
+                           renderer.isLegalTextureMap() &&
+                           (domain_set instanceof Linear2DSet ||
+                            (domain_set instanceof LinearNDSet &&
+                             domain_set.getDimension() == 2));
+
+    // DRM 2003-08-21
+    //int curved_size = display.getGraphicsModeControl().getCurvedSize();
+    int curved_size = (int)
+      default_values[display.getDisplayScalarIndex(Display.CurvedSize)];
+    /* DRM 2005-08-29 - default value now correct in link.prepareData()
+    int curved_size =  
+         (cMapCurveSize > 0)
+             ? cMapCurveSize
+             : display.getGraphicsModeControl().getCurvedSize();
+    */
+
+    float textureEnable =
+      default_values[display.getDisplayScalarIndex(Display.TextureEnable)];
+    boolean texture = textureEnable > 0.5f;
+    /* DRM 2005-08-29 - default value now correct in link.prepareData()
+    boolean texture = display.getGraphicsModeControl().getTextureEnable();
+    if (textureEnable > -0.5f) {
+      texture = (textureEnable > 0.5f);
+    }
+    */
+
+    boolean curvedTexture = getCurvedTexture() &&
+                            !isTextureMap &&
+                            texture &&
+                            curved_size > 0 &&
+                            getIsTerminal() && // implied by getCurvedTexture()?
+                            shadow_api.allowCurvedTexture() &&
+                            //default_values[alpha_index] > 0.99 &&
+                            renderer.isLegalTextureMap() &&
+                            domain_set.getManifoldDimension() == 2 &&
+                            domain_set instanceof GriddedSet;
+    boolean domainOnlySpatial =
+      Domain.getAllSpatial() && !Domain.getMultipleDisplayScalar();
+
+    boolean isTexture3D = getIsTexture3D() &&
+                           // default_values[alpha_index] > 0.99 &&
+                           renderer.isLegalTextureMap() &&
+                           (domain_set instanceof Linear3DSet ||
+                            (domain_set instanceof LinearNDSet &&
+                             domain_set.getDimension() == 3));
+    int t3dm = (int)
+      default_values[display.getDisplayScalarIndex(Display.Texture3DMode)];
+
+
+    // WLH 1 April 2000
+    boolean range3D = isTexture3D && anyRange(Domain.getDisplayIndices());
+
+    boolean isLinearContour3D = getIsLinearContour3D() &&
+                                domain_set instanceof Linear3DSet &&
+                                shadow_api.allowLinearContour();
+
+/*
+System.out.println("doTransform.isTextureMap = " + isTextureMap + " " +
+                   getIsTextureMap() + " " +
+                   // (default_values[alpha_index] > 0.99) + " " +
+                   renderer.isLegalTextureMap() + " " +
+                   (domain_set instanceof Linear2DSet) + " " +
+                   (domain_set instanceof LinearNDSet) + " " +
+                   (domain_set.getDimension() == 2));
+
+System.out.println("doTransform.curvedTexture = " + curvedTexture + " " +
+                        getCurvedTexture() + " " +
+                        !isTextureMap + " " +
+                        (curved_size > 0) + " " +
+                        getIsTerminal() + " " +
+                        shadow_api.allowCurvedTexture() + " " +
+                        (default_values[alpha_index] > 0.99) + " " +
+                        renderer.isLegalTextureMap() + " " +
+                        (domain_set instanceof Gridded2DSet) + " " +
+                        (domain_set instanceof GriddedSet) + " " +
+                        (domain_set.getDimension() == 2) );
+*/
+
+    float[] coordinates = null;
+    float[] texCoords = null;
+    float[] normals = null;
+    byte[] colors = null;
+    int data_width = 0;
+    int data_height = 0;
+    int data_depth = 0;
+    int texture_width = 1;
+    int texture_height = 1;
+    int texture_depth = 1;
+    float[] coordinatesX = null;
+    float[] texCoordsX = null;
+    float[] normalsX = null;
+    byte[] colorsX = null;
+    float[] coordinatesY = null;
+    float[] texCoordsY = null;
+    float[] normalsY = null;
+    byte[] colorsY = null;
+    float[] coordinatesZ = null;
+    float[] texCoordsZ = null;
+    float[] normalsZ = null;
+    byte[] colorsZ = null;
+
+    int[] volume_tuple_index = null;
+
+// if (link != null) System.out.println("test isTextureMap " + (System.currentTimeMillis() - link.start_time));
+
+    if (isTextureMap) {
+      /***
+      Linear1DSet X = null;
+      Linear1DSet Y = null;
+      if (domain_set instanceof Linear2DSet) {
+        X = ((Linear2DSet) domain_set).getX();
+        Y = ((Linear2DSet) domain_set).getY();
+      }
+      else {
+        X = ((LinearNDSet) domain_set).getLinear1DComponent(0);
+        Y = ((LinearNDSet) domain_set).getLinear1DComponent(1);
+      }
+      float[][] limits = new float[2][2];
+      limits[0][0] = (float) X.getFirst();
+      limits[0][1] = (float) X.getLast();
+      limits[1][0] = (float) Y.getFirst();
+      limits[1][1] = (float) Y.getLast();
+
+      // get domain_set sizes
+      data_width = X.getLength();
+      data_height = Y.getLength();
+      texture_width = shadow_api.textureWidth(data_width);
+      texture_height = shadow_api.textureHeight(data_height);
+
+      // WLH 27 Jan 2003
+      float half_width = 0.5f / ((float) (data_width - 1));
+      float half_height = 0.5f / ((float) (data_height - 1));
+      half_width = (limits[0][1] - limits[0][0]) * half_width;
+      half_height = (limits[1][1] - limits[1][0]) * half_height;
+      limits[0][0] -= half_width;
+      limits[0][1] += half_width;
+      limits[1][0] -= half_height;
+      limits[1][1] += half_height;
+
+      // convert values to default units (used in display)
+      limits = Unit.convertTuple(limits, dataUnits, domain_units);
+
+      int[] tuple_index = new int[3];
+      if (DomainComponents.length != 2) {
+        throw new DisplayException("texture domain dimension != 2:" +
+                                   "ShadowFunctionOrSetType.doTransform");
+      }
+      for (int i=0; i<DomainComponents.length; i++) {
+        Enumeration maps = DomainComponents[i].getSelectedMapVector().elements();
+        while (maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) maps.nextElement();
+          DisplayRealType real = map.getDisplayScalar();
+          DisplayTupleType tuple = real.getTuple();
+          if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+            // scale values
+            limits[i] = map.scaleValues(limits[i]);
+            // get spatial index
+            tuple_index[i] = real.getTupleIndex();
+            break;
+          }
+        }
+//      if (tuple == null ||
+//          !tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+//        throw new DisplayException("texture with bad tuple: " +
+//                                   "ShadowFunctionOrSetType.doTransform");
+//      }
+//      if (maps.hasMoreElements()) {
+//        throw new DisplayException("texture with multiple spatial: " +
+//                                   "ShadowFunctionOrSetType.doTransform");
+//      }
+      } // end for (int i=0; i<DomainComponents.length; i++)
+      // get spatial index not mapped from domain_set
+      tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+      DisplayRealType real = (DisplayRealType)
+        Display.DisplaySpatialCartesianTuple.getComponent(tuple_index[2]);
+      int value2_index = display.getDisplayScalarIndex(real);
+      float value2 = default_values[value2_index];
+      // float value2 = 0.0f;  WLH 30 Aug 99
+      for (int i=0; i<valueArrayLength; i++) {
+        if (inherited_values[i] > 0 &&
+            real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+          value2 = value_array[i];
+          break;
+        }
+      }
+
+      coordinates = new float[12];
+      // corner 0
+      coordinates[tuple_index[0]] = limits[0][0];
+      coordinates[tuple_index[1]] = limits[1][0];
+      coordinates[tuple_index[2]] = value2;
+      // corner 1
+      coordinates[3 + tuple_index[0]] = limits[0][1];
+      coordinates[3 + tuple_index[1]] = limits[1][0];
+      coordinates[3 + tuple_index[2]] = value2;
+      // corner 2
+      coordinates[6 + tuple_index[0]] = limits[0][1];
+      coordinates[6 + tuple_index[1]] = limits[1][1];
+      coordinates[6 + tuple_index[2]] = value2;
+      // corner 3
+      coordinates[9 + tuple_index[0]] = limits[0][0];
+      coordinates[9 + tuple_index[1]] = limits[1][1];
+      coordinates[9 + tuple_index[2]] = value2;
+
+      // move image back in Java3D 2-D mode
+      shadow_api.adjustZ(coordinates);
+
+      texCoords = new float[8];
+      float ratiow = ((float) data_width) / ((float) texture_width);
+      float ratioh = ((float) data_height) / ((float) texture_height);
+      shadow_api.setTexCoords(texCoords, ratiow, ratioh);
+
+      normals = new float[12];
+      float n0 = ((coordinates[3+2]-coordinates[0+2]) *
+                  (coordinates[6+1]-coordinates[0+1])) -
+                 ((coordinates[3+1]-coordinates[0+1]) *
+                  (coordinates[6+2]-coordinates[0+2]));
+      float n1 = ((coordinates[3+0]-coordinates[0+0]) *
+                  (coordinates[6+2]-coordinates[0+2])) -
+                 ((coordinates[3+2]-coordinates[0+2]) *
+                  (coordinates[6+0]-coordinates[0+0]));
+      float n2 = ((coordinates[3+1]-coordinates[0+1]) *
+                  (coordinates[6+0]-coordinates[0+0])) -
+                 ((coordinates[3+0]-coordinates[0+0]) *
+                  (coordinates[6+1]-coordinates[0+1]));
+
+      float nlen = (float) Math.sqrt(n0 *  n0 + n1 * n1 + n2 * n2);
+      n0 = n0 / nlen;
+      n1 = n1 / nlen;
+      n2 = n2 / nlen;
+
+      // corner 0
+      normals[0] = n0;
+      normals[1] = n1;
+      normals[2] = n2;
+      // corner 1
+      normals[3] = n0;
+      normals[4] = n1;
+      normals[5] = n2;
+      // corner 2
+      normals[6] = n0;
+      normals[7] = n1;
+      normals[8] = n2;
+      // corner 3
+      normals[9] = n0;
+      normals[10] = n1;
+      normals[11] = n2;
+
+      colors = new byte[12];
+      for (int i=0; i<12; i++) colors[i] = (byte) 127;
+//for (int i=0; i < 4; i++) {
+//  System.out.println("i = " + i + " texCoords = " + texCoords[2 * i] + " " +
+//                     texCoords[2 * i + 1]);
+//  System.out.println(" coordinates = " + coordinates[3 * i] + " " +
+//                     coordinates[3 * i + 1] + " " + coordinates[3 * i + 2]);
+//  System.out.println(" normals = " + normals[3 * i]  + " " + normals[3 * i + 1] +
+//                     " " + normals[3 * i + 2]);
+//}
+    ***/
+    }
+    else if (isTexture3D) {
+      visad.util.Trace.call1("ShadowFunctionOrSetType:isTexture3D");
+      Linear1DSet X = null;
+      Linear1DSet Y = null;
+      Linear1DSet Z = null;
+      if (domain_set instanceof Linear3DSet) {
+        X = ((Linear3DSet) domain_set).getX();
+        Y = ((Linear3DSet) domain_set).getY();
+        Z = ((Linear3DSet) domain_set).getZ();
+      }
+      else {
+        X = ((LinearNDSet) domain_set).getLinear1DComponent(0);
+        Y = ((LinearNDSet) domain_set).getLinear1DComponent(1);
+        Z = ((LinearNDSet) domain_set).getLinear1DComponent(2);
+      }
+      float[][] limits = new float[3][2];
+      limits[0][0] = (float) X.getFirst();
+      limits[0][1] = (float) X.getLast();
+      limits[1][0] = (float) Y.getFirst();
+      limits[1][1] = (float) Y.getLast();
+      limits[2][0] = (float) Z.getFirst();
+      limits[2][1] = (float) Z.getLast();
+
+      // convert values to default units (used in display)
+      limits = Unit.convertTuple(limits, dataUnits, domain_units, false);
+
+      // get domain_set sizes
+      data_width = X.getLength();
+      data_height = Y.getLength();
+      data_depth = Z.getLength();
+      texture_width = shadow_api.textureWidth(data_width);
+      texture_height = shadow_api.textureHeight(data_height);
+      texture_depth = shadow_api.textureDepth(data_depth);
+
+      int[] tuple_index = new int[3];
+      if (DomainComponents.length != 3) {
+        throw new DisplayException("texture3D domain dimension != 3:" +
+                                   "ShadowFunctionOrSetType.doTransform");
+      }
+      for (int i=0; i<DomainComponents.length; i++) {
+        Enumeration maps = DomainComponents[i].getSelectedMapVector().elements();
+
+        while (maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) maps.nextElement();
+          DisplayRealType real = map.getDisplayScalar();
+          DisplayTupleType tuple = real.getTuple();
+          if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+            // scale values
+            limits[i] = map.scaleValues(limits[i]);
+            // get spatial index
+            tuple_index[i] = real.getTupleIndex();
+            break;
+          }
+        }
+/*
+        if (tuple == null ||
+            !tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+          throw new DisplayException("texture with bad tuple: " +
+                                     "ShadowFunctionOrSetType.doTransform");
+        }
+        if (maps.hasMoreElements()) {
+          throw new DisplayException("texture with multiple spatial: " +
+                                     "ShadowFunctionOrSetType.doTransform");
+        }
+*/
+      } // end for (int i=0; i<DomainComponents.length; i++)
+      volume_tuple_index = tuple_index;
+
+      coordinatesX = new float[12 * data_width];
+      coordinatesY = new float[12 * data_height];
+      coordinatesZ = new float[12 * data_depth];
+      for (int i=0; i<data_depth; i++) {
+        int i12 = i * 12;
+        float depth = limits[2][0] +
+          (limits[2][1] - limits[2][0]) * i / (data_depth - 1.0f);
+        // corner 0
+        coordinatesZ[i12 + tuple_index[0]] = limits[0][0];
+        coordinatesZ[i12 + tuple_index[1]] = limits[1][0];
+        coordinatesZ[i12 + tuple_index[2]] = depth;
+        // corner 1
+        coordinatesZ[i12 + 3 + tuple_index[0]] = limits[0][1];
+        coordinatesZ[i12 + 3 + tuple_index[1]] = limits[1][0];
+        coordinatesZ[i12 + 3 + tuple_index[2]] = depth;
+        // corner 2
+        coordinatesZ[i12 + 6 + tuple_index[0]] = limits[0][1];
+        coordinatesZ[i12 + 6 + tuple_index[1]] = limits[1][1];
+        coordinatesZ[i12 + 6 + tuple_index[2]] = depth;
+        // corner 3
+        coordinatesZ[i12 + 9 + tuple_index[0]] = limits[0][0];
+        coordinatesZ[i12 + 9 + tuple_index[1]] = limits[1][1];
+        coordinatesZ[i12 + 9 + tuple_index[2]] = depth;
+      }
+
+      for (int i=0; i<data_height; i++) {
+        int i12 = i * 12;
+        float height = limits[1][0] +
+          (limits[1][1] - limits[1][0]) * i / (data_height - 1.0f);
+        // corner 0
+        coordinatesY[i12 + tuple_index[0]] = limits[0][0];
+        coordinatesY[i12 + tuple_index[1]] = height;
+        coordinatesY[i12 + tuple_index[2]] = limits[2][0];
+        // corner 1
+        coordinatesY[i12 + 3 + tuple_index[0]] = limits[0][1];
+        coordinatesY[i12 + 3 + tuple_index[1]] = height;
+        coordinatesY[i12 + 3 + tuple_index[2]] = limits[2][0];
+        // corner 2
+        coordinatesY[i12 + 6 + tuple_index[0]] = limits[0][1];
+        coordinatesY[i12 + 6 + tuple_index[1]] = height;
+        coordinatesY[i12 + 6 + tuple_index[2]] = limits[2][1];
+        // corner 3
+        coordinatesY[i12 + 9 + tuple_index[0]] = limits[0][0];
+        coordinatesY[i12 + 9 + tuple_index[1]] = height;
+        coordinatesY[i12 + 9 + tuple_index[2]] = limits[2][1];
+      }
+
+      for (int i=0; i<data_width; i++) {
+        int i12 = i * 12;
+        float width = limits[0][0] +
+          (limits[0][1] - limits[0][0]) * i / (data_width - 1.0f);
+        // corner 0
+        coordinatesX[i12 + tuple_index[0]] = width;
+        coordinatesX[i12 + tuple_index[1]] = limits[1][0];
+        coordinatesX[i12 + tuple_index[2]] = limits[2][0];
+        // corner 1
+        coordinatesX[i12 + 3 + tuple_index[0]] = width;
+        coordinatesX[i12 + 3 + tuple_index[1]] = limits[1][1];
+        coordinatesX[i12 + 3 + tuple_index[2]] = limits[2][0];
+        // corner 2
+        coordinatesX[i12 + 6 + tuple_index[0]] = width;
+        coordinatesX[i12 + 6 + tuple_index[1]] = limits[1][1];
+        coordinatesX[i12 + 6 + tuple_index[2]] = limits[2][1];
+        // corner 3
+        coordinatesX[i12 + 9 + tuple_index[0]] = width;
+        coordinatesX[i12 + 9 + tuple_index[1]] = limits[1][0];
+        coordinatesX[i12 + 9 + tuple_index[2]] = limits[2][1];
+      }
+
+      float ratiow = ((float) data_width) / ((float) texture_width);
+      float ratioh = ((float) data_height) / ((float) texture_height);
+      float ratiod = ((float) data_depth) / ((float) texture_depth);
+
+      if (t3dm == GraphicsModeControl.STACK2D) {
+        texCoordsX =
+          shadow_api.setTexStackCoords(data_width, 0, ratiow, ratioh, ratiod);
+        texCoordsY =
+          shadow_api.setTexStackCoords(data_height, 1, ratiow, ratioh, ratiod);
+        texCoordsZ =
+          shadow_api.setTexStackCoords(data_depth, 2, ratiow, ratioh, ratiod);
+      } else {
+        texCoordsX =
+          shadow_api.setTex3DCoords(data_width, 0, ratiow, ratioh, ratiod);
+        texCoordsY =
+          shadow_api.setTex3DCoords(data_height, 1, ratiow, ratioh, ratiod);
+        texCoordsZ =
+          shadow_api.setTex3DCoords(data_depth, 2, ratiow, ratioh, ratiod);
+      }
+
+      normalsX = new float[12 * data_width];
+      normalsY = new float[12 * data_height];
+      normalsZ = new float[12 * data_depth];
+      float n0, n1, n2, nlen;
+      n0 = ((coordinatesX[3+2]-coordinatesX[0+2]) *
+            (coordinatesX[6+1]-coordinatesX[0+1])) -
+           ((coordinatesX[3+1]-coordinatesX[0+1]) *
+            (coordinatesX[6+2]-coordinatesX[0+2]));
+      n1 = ((coordinatesX[3+0]-coordinatesX[0+0]) *
+            (coordinatesX[6+2]-coordinatesX[0+2])) -
+           ((coordinatesX[3+2]-coordinatesX[0+2]) *
+            (coordinatesX[6+0]-coordinatesX[0+0]));
+      n2 = ((coordinatesX[3+1]-coordinatesX[0+1]) *
+            (coordinatesX[6+0]-coordinatesX[0+0])) -
+           ((coordinatesX[3+0]-coordinatesX[0+0]) *
+            (coordinatesX[6+1]-coordinatesX[0+1]));
+      nlen = (float) Math.sqrt(n0 *  n0 + n1 * n1 + n2 * n2);
+      n0 = n0 / nlen;
+      n1 = n1 / nlen;
+      n2 = n2 / nlen;
+      for (int i=0; i<normalsX.length; i+=3) {
+        normalsX[i] = n0;
+        normalsX[i + 1] = n1;
+        normalsX[i + 2] = n2;
+      }
+
+      n0 = ((coordinatesY[3+2]-coordinatesY[0+2]) *
+            (coordinatesY[6+1]-coordinatesY[0+1])) -
+           ((coordinatesY[3+1]-coordinatesY[0+1]) *
+            (coordinatesY[6+2]-coordinatesY[0+2]));
+      n1 = ((coordinatesY[3+0]-coordinatesY[0+0]) *
+            (coordinatesY[6+2]-coordinatesY[0+2])) -
+           ((coordinatesY[3+2]-coordinatesY[0+2]) *
+            (coordinatesY[6+0]-coordinatesY[0+0]));
+      n2 = ((coordinatesY[3+1]-coordinatesY[0+1]) *
+            (coordinatesY[6+0]-coordinatesY[0+0])) -
+           ((coordinatesY[3+0]-coordinatesY[0+0]) *
+            (coordinatesY[6+1]-coordinatesY[0+1]));
+      nlen = (float) Math.sqrt(n0 *  n0 + n1 * n1 + n2 * n2);
+      n0 = n0 / nlen;
+      n1 = n1 / nlen;
+      n2 = n2 / nlen;
+      for (int i=0; i<normalsY.length; i+=3) {
+        normalsY[i] = n0;
+        normalsY[i + 1] = n1;
+        normalsY[i + 2] = n2;
+      }
+
+      n0 = ((coordinatesZ[3+2]-coordinatesZ[0+2]) *
+            (coordinatesZ[6+1]-coordinatesZ[0+1])) -
+           ((coordinatesZ[3+1]-coordinatesZ[0+1]) *
+            (coordinatesZ[6+2]-coordinatesZ[0+2]));
+      n1 = ((coordinatesZ[3+0]-coordinatesZ[0+0]) *
+            (coordinatesZ[6+2]-coordinatesZ[0+2])) -
+           ((coordinatesZ[3+2]-coordinatesZ[0+2]) *
+            (coordinatesZ[6+0]-coordinatesZ[0+0]));
+      n2 = ((coordinatesZ[3+1]-coordinatesZ[0+1]) *
+            (coordinatesZ[6+0]-coordinatesZ[0+0])) -
+           ((coordinatesZ[3+0]-coordinatesZ[0+0]) *
+            (coordinatesZ[6+1]-coordinatesZ[0+1]));
+      nlen = (float) Math.sqrt(n0 *  n0 + n1 * n1 + n2 * n2);
+      n0 = n0 / nlen;
+      n1 = n1 / nlen;
+      n2 = n2 / nlen;
+      for (int i=0; i<normalsZ.length; i+=3) {
+        normalsZ[i] = n0;
+        normalsZ[i + 1] = n1;
+        normalsZ[i + 2] = n2;
+      }
+
+      colorsX = new byte[12 * data_width];
+      colorsY = new byte[12 * data_height];
+      colorsZ = new byte[12 * data_depth];
+      for (int i=0; i<12*data_width; i++) colorsX[i] = (byte) 127;
+      for (int i=0; i<12*data_height; i++) colorsY[i] = (byte) 127;
+      for (int i=0; i<12*data_depth; i++) colorsZ[i] = (byte) 127;
+
+/*
+for (int i=0; i < 4; i++) {
+  System.out.println("i = " + i + " texCoordsX = " + texCoordsX[3 * i] + " " +
+                     texCoordsX[3 * i + 1]);
+  System.out.println(" coordinatesX = " + coordinatesX[3 * i] + " " +
+                     coordinatesX[3 * i + 1] + " " + coordinatesX[3 * i + 2]);
+  System.out.println(" normalsX = " + normalsX[3 * i]  + " " +
+                     normalsX[3 * i + 1] + " " + normalsX[3 * i + 2]);
+}
+*/
+      visad.util.Trace.call2("ShadowFunctionOrSetType:isTexture3D");
+    }
+    // WLH 1 April 2000
+    // else { // !isTextureMap && !isTexture3D
+    // WLH 16 July 2000 - add '&& !isLinearContour3D'
+    if (!isTextureMap && (!isTexture3D || range3D) && !isLinearContour3D) {
+
+// if (link != null) System.out.println("start domain " + (System.currentTimeMillis() - link.start_time));
+      visad.util.Trace.call1("ShadowFunctionOrSetType:domain");
+
+      // get values from Function Domain
+      // NOTE - may defer this until needed, if needed
+      if (domain_dimension == 1) {
+        domain_doubles = domain_set.getDoubles(false);
+        domain_doubles = Unit.convertTuple(domain_doubles, dataUnits, domain_units);
+        mapValues(display_values, domain_doubles, DomainComponents);
+      }
+      else {
+        // do shallow clone and don't copy in convert DRM 2003-02-24
+        // domain_values = (float[][]) domain_set.getSamples(false).clone();
+        domain_values = (float[][]) domain_set.getSamples(true);
+
+        // convert values to default units (used in display)
+        // MEM & FREE
+        //domain_values = Unit.convertTuple(domain_values, dataUnits, domain_units);
+        domain_values = 
+           Unit.convertTuple(domain_values, dataUnits, domain_units, false);
+        // System.out.println("got domain_values: domain_length = " + domain_length);
+
+        // map domain_values to appropriate DisplayRealType-s
+        // MEM
+        mapValues(display_values, domain_values, DomainComponents, false);
+      }
+
+      // System.out.println("mapped domain_values");
+
+      ShadowRealTupleType domain_reference = Domain.getReference();
+
+/*
+      System.out.println("domain_reference = " + domain_reference);
+      if (domain_reference != null) {
+        System.out.println("getMappedDisplayScalar = " +
+                           domain_reference.getMappedDisplayScalar());
+      }
+*/
+
+      visad.util.Trace.call2("ShadowFunctionOrSetType:domain");
+// if (link != null) System.out.println("end domain " + (System.currentTimeMillis() - link.start_time));
+
+      if (domain_reference != null && domain_reference.getMappedDisplayScalar()) {
+        visad.util.Trace.call1("ShadowFunctionOrSetType:domain_ref_mapped");
+        // apply coordinate transform to domain values
+        RealTupleType ref = (RealTupleType) domain_reference.getType();
+        // MEM
+        float[][] reference_values = null;
+        double[][] reference_doubles = null;
+        if (domain_dimension == 1) {
+          visad.util.Trace.call1("ShadowFunctionOrSetType:domain_ref_mapped:1");
+          reference_doubles =
+            CoordinateSystem.transformCoordinates(
+              ref, null, ref.getDefaultUnits(), null,
+              (RealTupleType) Domain.getType(), dataCoordinateSystem,
+              domain_units, null, domain_doubles);
+          visad.util.Trace.call2("ShadowFunctionOrSetType:domain_ref_mapped:1");
+        }
+        else {
+          // this interferes with correct handling of missing data
+          // if (curvedTexture && domainOnlySpatial) {
+          if (false) {
+
+ //if (link != null) System.out.println("start compute spline " + (System.currentTimeMillis() - link.start_time));
+
+            int[] lengths = ((GriddedSet) domain_set).getLengths();
+            data_width = lengths[0];
+            data_height = lengths[1];
+            texture_width = shadow_api.textureWidth(data_width);
+            texture_height = shadow_api.textureHeight(data_height);
+
+            int size = (data_width + data_height) / 2;
+            curved_size = Math.max(1, Math.min(curved_size, size / 32));
+
+            int nwidth = 2 + (data_width - 1) / curved_size;
+            int nheight = 2 + (data_height - 1) / curved_size;
+/*
+System.out.println("data_width = " + data_width + " data_height = " + data_height +
+     " texture_width = " + texture_width + " texture_height = " + texture_height +
+     " nwidth = " + nwidth + " nheight = " + nheight);
+*/
+            int nn = nwidth * nheight;
+            int[] is = new int[nwidth];
+            int[] js = new int[nheight];
+            for (int i=0; i<nwidth; i++) {
+              is[i] = Math.min(i * curved_size, data_width - 1);
+            }
+            for (int j=0; j<nheight; j++) {
+              js[j] = Math.min(j * curved_size, data_height - 1);
+            }
+            float[][] spline_domain =
+              new float[domain_dimension][nwidth * nheight];
+            int k = 0;
+            for (int j=0; j<nheight; j++) {
+              for (int i=0; i<nwidth; i++) {
+                int ij = is[i] + data_width * js[j];
+                spline_domain[0][k] = domain_values[0][ij];
+                spline_domain[1][k] = domain_values[1][ij];
+                if (domain_dimension == 3) spline_domain[2][k] = domain_values[2][ij];
+                k++;
+              }
+            }
+            float[][] spline_reference =
+              CoordinateSystem.transformCoordinates(
+                ref, null, ref.getDefaultUnits(), null,
+                (RealTupleType) Domain.getType(), dataCoordinateSystem,
+                domain_units, null, spline_domain, false);
+            reference_values = new float[domain_dimension][domain_length];
+            for (int i=0; i<domain_length; i++) {
+              reference_values[0][i] = Float.NaN;
+              reference_values[1][i] = Float.NaN;
+              if (domain_dimension == 3) reference_values[2][i] = Float.NaN;
+            }
+            k = 0;
+            for (int j=0; j<nheight; j++) {
+              for (int i=0; i<nwidth; i++) {
+                int ij = is[i] + data_width * js[j];
+                reference_values[0][ij] = spline_reference[0][k];
+                reference_values[1][ij] = spline_reference[1][k];
+                if (domain_dimension == 3) reference_values[2][ij] = spline_reference[2][k];
+                k++;
+              }
+            }
+// if (link != null) System.out.println("end compute spline " + (System.currentTimeMillis() - link.start_time));
+          }
+          else { // if !(curvedTexture && domainOnlySpatial)
+            visad.util.Trace.call1("ShadowFunctionOrSetType:domain_ref_mapped:2");
+            reference_values =
+              CoordinateSystem.transformCoordinates(
+                ref, null, ref.getDefaultUnits(), null,
+                (RealTupleType) Domain.getType(), dataCoordinateSystem,
+                domain_units, null, domain_values, false);
+            visad.util.Trace.call2("ShadowFunctionOrSetType:domain_ref_mapped:2");
+          }
+        } // end if !(domain_dimension == 1)
+
+        // WLH 13 Macrh 2000
+        // if (anyFlow) {
+          renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                      ref.getDefaultUnits(), (RealTupleType) Domain.getType(),
+                      new CoordinateSystem[] {dataCoordinateSystem},
+                      domain_units);
+        // WLH 13 Macrh 2000
+        // }
+
+        //
+        // TO_DO
+        // adjust any RealVectorTypes in range
+        // see FlatField.resample and FieldImpl.resample
+        //
+
+// if (link != null) System.out.println("start map reference " + (System.currentTimeMillis() - link.start_time));
+        visad.util.Trace.call1("ShadowFunctionOrSetType:map reference");
+
+        // map reference_values to appropriate DisplayRealType-s
+        ShadowRealType[] DomainReferenceComponents = getDomainReferenceComponents();
+        // MEM
+        if (domain_dimension == 1) {
+          mapValues(display_values, reference_doubles, DomainReferenceComponents);
+        }
+        else {
+          mapValues(display_values, reference_values, DomainReferenceComponents);
+        }
+
+// if (link != null) System.out.println("end map reference " + (System.currentTimeMillis() - link.start_time));
+        visad.util.Trace.call2("ShadowFunctionOrSetType:map reference");
+
+/*
+for (int i=0; i<DomainReferenceComponents.length; i++) {
+  System.out.println("DomainReferenceComponents[" + i + "] = " +
+                     DomainReferenceComponents[i]);
+  System.out.println("reference_values[" + i + "].length = " +
+                     reference_values[i].length);
+}
+        System.out.println("mapped domain_reference values");
+*/
+        // FREE
+        reference_values = null;
+        reference_doubles = null;
+        visad.util.Trace.call2("ShadowFunctionOrSetType:domain_ref_mapped");
+      }
+      else { // if !(domain_reference != null &&
+             //      domain_reference.getMappedDisplayScalar())
+        // WLH 13 March 2000
+        // if (anyFlow) {
+/* WLH 23 May 99
+          renderer.setEarthSpatialData(Domain, null, null,
+                      null, (RealTupleType) Domain.getType(),
+                      new CoordinateSystem[] {dataCoordinateSystem},
+                      domain_units);
+*/
+          visad.util.Trace.call1("ShadowFunctionOrSetType:!domain_ref_mapped");
+          RealTupleType ref = (domain_reference == null) ? null :
+                              (RealTupleType) domain_reference.getType();
+          Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+          renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                      ref_units, (RealTupleType) Domain.getType(),
+                      new CoordinateSystem[] {dataCoordinateSystem},
+                      domain_units);
+        // WLH 13 March 2000
+        // }
+          visad.util.Trace.call2("ShadowFunctionOrSetType:!domain_ref_mapped");
+      }
+      // FREE
+      domain_values = null;
+      domain_doubles = null;
+    } // end if (!isTextureMap && (!isTexture3D || range3D) &&
+      //         !isLinearContour3D)
+
+    if (this instanceof ShadowFunctionType) {
+
+// if (link != null) System.out.println("start range " + (System.currentTimeMillis() - link.start_time));
+
+      // get range_values for RealType and RealTupleType
+      // components, in defaultUnits for RealType-s
+      // MEM - may copy (in convertTuple)
+      float[][] range_values = ((Field) data).getFloats(false);
+
+      // System.out.println("got range_values");
+
+      if (range_values != null) {
+        visad.util.Trace.call1("ShadowFunctionOrSetType:range_values != null");
+        // map range_values to appropriate DisplayRealType-s
+        ShadowRealType[] RangeComponents = getRangeComponents();
+        // MEM
+        mapValues(display_values, range_values, RangeComponents, true);
+
+        // System.out.println("mapped range_values");
+
+        //
+        // transform any range CoordinateSystem-s
+        // into display_values, then mapValues
+        //
+        int[] refToComponent = getRefToComponent();
+        ShadowRealTupleType[] componentWithRef = getComponentWithRef();
+        int[] componentIndex = getComponentIndex();
+
+        if (refToComponent != null) {
+
+          for (int i=0; i<refToComponent.length; i++) {
+            int n = componentWithRef[i].getDimension();
+            int start = refToComponent[i];
+            float[][] values = new float[n][];
+            for (int j=0; j<n; j++) values[j] = range_values[j + start];
+            ShadowRealTupleType component_reference =
+              componentWithRef[i].getReference();
+            RealTupleType ref = (RealTupleType) component_reference.getType();
+            Unit[] range_units;
+            CoordinateSystem[] range_coord_sys;
+            if (i == 0 && componentWithRef[i].equals(Range)) {
+              range_units = ((Field) data).getDefaultRangeUnits();
+              range_coord_sys = ((Field) data).getRangeCoordinateSystem();
+            }
+            else {
+              Unit[] dummy_units = ((Field) data).getDefaultRangeUnits();
+              range_units = new Unit[n];
+              for (int j=0; j<n; j++) range_units[j] = dummy_units[j + start];
+              range_coord_sys =
+                ((Field) data).getRangeCoordinateSystem(componentIndex[i]);
+            }
+
+            float[][] reference_values = null;
+            if (range_coord_sys.length == 1) {
+              // MEM
+              reference_values =
+                CoordinateSystem.transformCoordinates(
+                  ref, null, ref.getDefaultUnits(), null,
+                  (RealTupleType) componentWithRef[i].getType(),
+                  range_coord_sys[0], range_units, null, values);
+
+              // WLH 13 March 2000
+              // if (anyFlow) {
+                renderer.setEarthSpatialData(componentWithRef[i],
+                      component_reference, ref, ref.getDefaultUnits(),
+                      (RealTupleType) componentWithRef[i].getType(),
+                      range_coord_sys, range_units);
+              // WLH 13 March 2000
+              // }
+
+            }
+            else {
+              // MEM
+              reference_values = new float[n][domain_length];
+              float[][] temp = new float[n][1];
+              for (int j=0; j<domain_length; j++) {
+                for (int k=0; k<n; k++) temp[k][0] = values[k][j];
+                temp =
+                  CoordinateSystem.transformCoordinates(
+                    ref, null, ref.getDefaultUnits(), null,
+                    (RealTupleType) componentWithRef[i].getType(),
+                    range_coord_sys[j], range_units, null, temp);
+                for (int k=0; k<n; k++) reference_values[k][j] = temp[k][0];
+              }
+
+              // WLH 13 March 2000
+              // if (anyFlow) {
+                renderer.setEarthSpatialData(componentWithRef[i],
+                      component_reference, ref, ref.getDefaultUnits(),
+                      (RealTupleType) componentWithRef[i].getType(),
+                      range_coord_sys, range_units);
+              // WLH 13 March 2000
+              // }
+
+            }
+
+            // map reference_values to appropriate DisplayRealType-s
+            // MEM
+/* WLH 17 April 99
+            mapValues(display_values, reference_values,
+                      getComponents(componentWithRef[i], false));
+*/
+            mapValues(display_values, reference_values,
+                      getComponents(component_reference, false));
+            // FREE
+            reference_values = null;
+            // FREE (redundant reference to range_values)
+            values = null;
+          } // end for (int i=0; i<refToComponent.length; i++)
+        } // end (refToComponent != null)
+
+// if (link != null) System.out.println("end range " + (System.currentTimeMillis() - link.start_time));
+
+        // setEarthSpatialData calls when no CoordinateSystem
+        // WLH 13 March 2000
+        // if (Range instanceof ShadowTupleType && anyFlow) {
+        if (Range instanceof ShadowTupleType) {
+          if (Range instanceof ShadowRealTupleType) {
+            Unit[] range_units = ((Field) data).getDefaultRangeUnits();
+            CoordinateSystem[] range_coord_sys =
+              ((Field) data).getRangeCoordinateSystem();
+/* WLH 23 May 99
+            renderer.setEarthSpatialData((ShadowRealTupleType) Range,
+                      null, null, null, (RealTupleType) Range.getType(),
+                      range_coord_sys, range_units);
+*/
+            ShadowRealTupleType component_reference =
+              ((ShadowRealTupleType) Range).getReference();
+            RealTupleType ref = (component_reference == null) ? null :
+                                (RealTupleType) component_reference.getType();
+            Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+            renderer.setEarthSpatialData((ShadowRealTupleType) Range,
+                      component_reference, ref, ref_units,
+                      (RealTupleType) Range.getType(),
+                      range_coord_sys, range_units);
+          }
+          else { // if (!(Range instanceof ShadowRealTupleType))
+            Unit[] dummy_units = ((Field) data).getDefaultRangeUnits();
+            int start = 0;
+            int n = ((ShadowTupleType) Range).getDimension();
+            for (int i=0; i<n ;i++) {
+              ShadowType range_component =
+                ((ShadowTupleType) Range).getComponent(i);
+              if (range_component instanceof ShadowRealTupleType) {
+                int m = ((ShadowRealTupleType) range_component).getDimension();
+                Unit[] range_units = new Unit[m];
+                for (int j=0; j<m; j++) range_units[j] = dummy_units[j + start];
+                CoordinateSystem[] range_coord_sys =
+                  ((Field) data).getRangeCoordinateSystem(i);
+/* WLH 23 May 99
+                renderer.setEarthSpatialData((ShadowRealTupleType)
+                      range_component, null, null,
+                      null, (RealTupleType) range_component.getType(),
+                      range_coord_sys, range_units);
+*/
+                ShadowRealTupleType component_reference =
+                  ((ShadowRealTupleType) range_component).getReference();
+                RealTupleType ref = (component_reference == null) ? null :
+                                    (RealTupleType) component_reference.getType();
+                Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+                renderer.setEarthSpatialData((ShadowRealTupleType) range_component,
+                      component_reference, ref, ref_units,
+                      (RealTupleType) range_component.getType(),
+                      range_coord_sys, range_units);
+                start += ((ShadowRealTupleType) range_component).getDimension();
+              }
+              else if (range_component instanceof ShadowRealType) {
+                start++;
+              }
+            }
+          } // end if (!(Range instanceof ShadowRealTupleType))
+        } // end if (Range instanceof ShadowTupleType)
+
+        // FREE
+        range_values = null;
+        visad.util.Trace.call2("ShadowFunctionOrSetType:range_values != null");
+      } // end if (range_values != null)
+
+      if (anyText && text_values == null) {
+        for (int i=0; i<valueArrayLength; i++) {
+          if (display_values[i] != null) {
+            int displayScalarIndex = valueToScalar[i];
+            ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+            ScalarType real = map.getScalar();
+            DisplayRealType dreal = display.getDisplayScalar(displayScalarIndex);
+            if (dreal.equals(Display.Text) && real instanceof RealType) {
+              text_control = (TextControl) map.getControl();
+              text_values = new String[domain_length];
+              NumberFormat format = text_control.getNumberFormat();
+              if (display_values[i].length == 1) {
+                String text = null;
+                if (display_values[i][0] != display_values[i][0]) {
+                  text = "";
+                }
+                else if (format == null) {
+                  text = PlotText.shortString(display_values[i][0]);
+                }
+                else {
+                  text = format.format(display_values[i][0]);
+                }
+                for (int j=0; j<domain_length; j++) {
+                  text_values[j] = text;
+                }
+              }
+              else {
+                if (format == null) {
+                  for (int j=0; j<domain_length; j++) {
+                    if (display_values[i][j] != display_values[i][j]) {
+                      text_values[j] = "";
+                    }
+                    else {
+                      text_values[j] = PlotText.shortString(display_values[i][j]);
+                    }
+                  }
+                }
+                else {
+                  for (int j=0; j<domain_length; j++) {
+                    if (display_values[i][j] != display_values[i][j]) {
+                      text_values[j] = ""; 
+                    }
+                    else {
+                      text_values[j] = format.format(display_values[i][j]);
+                    }
+                  }
+                }
+              }
+              break;
+            }
+          }
+        }
+
+        if (text_values == null) {
+          String[][] string_values = ((Field) data).getStringValues();
+          if (string_values != null) {
+            int[] textIndices = ((FunctionType) getType()).getTextIndices();
+            int n = string_values.length;
+            if (Range instanceof ShadowTextType) {
+              Vector maps = shadow_api.getTextMaps(-1, textIndices);
+              if (!maps.isEmpty()) {
+                text_values = string_values[0];
+                ScalarMap map = (ScalarMap) maps.firstElement();
+                text_control = (TextControl) map.getControl();
+  /*
+  System.out.println("Range is ShadowTextType, text_values[0] = " +
+                     text_values[0] + " n = " + n);
+  */
+              }
+            }
+            else if (Range instanceof ShadowTupleType) {
+              for (int i=0; i<n; i++) {
+                Vector maps = shadow_api.getTextMaps(i, textIndices);
+                if (!maps.isEmpty()) {
+                  text_values = string_values[i];
+                  ScalarMap map = (ScalarMap) maps.firstElement();
+                  text_control = (TextControl) map.getControl();
+  /*
+  System.out.println("Range is ShadowTupleType, text_values[0] = " +
+                     text_values[0] + " n = " + n + " i = " + i);
+  */
+                }
+              }
+            } // end if (Range instanceof ShadowTupleType)
+          } // end if (string_values != null)
+        } // end if (text_values == null)
+      } // end if (anyText && text_values == null)
+    } // end if (this instanceof ShadowFunctionType)
+
+// if (link != null) System.out.println("start assembleSelect " + (System.currentTimeMillis() - link.start_time));
+    visad.util.Trace.call1("ShadowFunctionOrSetType:assembleSelect");
+
+    //
+    // NOTE -
+    // currently assuming SelectRange changes require Transform
+    // see DataRenderer.isTransformControl
+    //
+    // get array that composites SelectRange components
+    // range_select is null if all selected
+    // MEM
+    boolean[][] range_select =
+      shadow_api.assembleSelect(display_values, domain_length, valueArrayLength,
+                     valueToScalar, display, shadow_api);
+/*
+if (range_select[0] != null) {
+  int numforced = 0;
+  for (int k=0; k<range_select[0].length; k++) {
+    if (!range_select[0][k]) numforced++;
+  }
+  System.out.println("assembleSelect: numforced = " + numforced);
+}
+*/
+    if (range_select[0] != null && range_select[0].length == 1 &&
+        !range_select[0][0]) {
+      // single missing value in range_select[0], so render nothing
+      visad.util.Trace.call2("ShadowFunctionOrSetType:assembleSelect");
+      return false;
+    }
+
+// if (link != null) System.out.println("end assembleSelect " + (System.currentTimeMillis() - link.start_time));
+    visad.util.Trace.call2("ShadowFunctionOrSetType:assembleSelect");
+
+    // System.out.println("assembleSelect");
+ /*
+System.out.println("doTerminal: isTerminal = " + getIsTerminal() +
+                   " LevelOfDifficulty = " + LevelOfDifficulty);
+*/
+    if (getIsTerminal()) {
+      if (!getFlat()) {
+        throw new DisplayException("terminal but not Flat");
+      }
+
+      GraphicsModeControl mode = (GraphicsModeControl)
+        display.getGraphicsModeControl().clone();
+      float pointSize =
+        default_values[display.getDisplayScalarIndex(Display.PointSize)];
+      mode.setPointSize(pointSize, true);
+      float lineWidth =
+        default_values[display.getDisplayScalarIndex(Display.LineWidth)];
+      mode.setLineWidth(lineWidth, true);
+      int lineStyle = (int)
+        default_values[display.getDisplayScalarIndex(Display.LineStyle)];
+      mode.setLineStyle(lineStyle, true);
+      int polygonMode = (int)
+        default_values[display.getDisplayScalarIndex(Display.PolygonMode)];
+      mode.setPolygonMode(polygonMode, true);
+      float polygonOffset =
+        default_values[display.getDisplayScalarIndex(Display.PolygonOffset)];
+      mode.setPolygonOffset(polygonOffset, true);
+      float polygonOffsetFactor = 
+        default_values[
+          display.getDisplayScalarIndex(Display.PolygonOffsetFactor)];
+      mode.setPolygonOffsetFactor(polygonOffsetFactor, true);
+      float cacheAppearances = 
+        default_values[
+          display.getDisplayScalarIndex(Display.CacheAppearances)];
+      mode.setCacheAppearances(cacheAppearances > 0.5f);
+      float mergeArrays = 
+        default_values[
+          display.getDisplayScalarIndex(Display.MergeGeometries)];
+      mode.setMergeGeometries(mergeArrays > 0.5f);
+
+      //boolean pointMode = mode.getPointMode();
+      boolean pointMode = 
+          default_values[
+            display.getDisplayScalarIndex(Display.PointMode)] > 0.5f;
+
+
+      float missingTransparent =
+          default_values[display.getDisplayScalarIndex(Display.MissingTransparent)];
+      boolean isMissingTransparent = missingTransparent > 0.5f;
+      /* DRM: 2005-08-29 - default value now correct
+      boolean isMissingTransparent = mode.getMissingTransparent();
+      if (missingTransparent > -0.5f) {
+        isMissingTransparent = (missingTransparent > 0.5f);
+      }
+      */
+
+// if (link != null) System.out.println("start assembleColor " + (System.currentTimeMillis() - link.start_time));
+      visad.util.Trace.call1("ShadowFunctionOrSetType:assembleColor ");
+
+      // MEM_WLH - this moved
+      boolean[] single_missing = {false, false, false, false};
+      // assemble an array of RGBA values
+      // MEM
+      byte[][] color_values =
+        shadow_api.assembleColor(display_values, valueArrayLength, valueToScalar,
+                      display, default_values, range_select,
+                      single_missing, shadow_api);
+/*
+if (range_select[0] != null) {
+  int numforced = 0;
+  for (int k=0; k<range_select[0].length; k++) {
+    if (!range_select[0][k]) numforced++;
+  }
+  System.out.println("assembleColor: numforced = " + numforced);
+}
+*/
+/*
+if (color_values != null) {
+  System.out.println("color_values.length = " + color_values.length +
+                     " color_values[0].length = " + color_values[0].length);
+  System.out.println(color_values[0][0] + " " + color_values[1][0] +
+                     " " + color_values[2][0] + 
+                     ((color_values.length > 3)? " " + color_values[3][0]:""));
+}
+*/
+
+      if (range_select[0] != null && range_select[0].length == 1 &&
+          !range_select[0][0]) {
+        // single missing value in range_select[0], so render nothing
+        return false;
+      }
+      visad.util.Trace.call2("ShadowFunctionOrSetType:assembleColor ");
+
+// if (link != null) System.out.println("end assembleColor " + (System.currentTimeMillis() - link.start_time));
+
+      float[][] flow1_values = new float[3][];
+      float[][] flow2_values = new float[3][];
+      float[] flowScale = new float[2];
+      // MEM
+      shadow_api.assembleFlow(flow1_values, flow2_values, flowScale,
+                   display_values, valueArrayLength, valueToScalar,
+                   display, default_values, range_select, renderer,
+                   shadow_api);
+/*
+if (range_select[0] != null) {
+  int numforced = 0;
+  for (int k=0; k<range_select[0].length; k++) {
+    if (!range_select[0][k]) numforced++;
+  }
+  System.out.println("assembleFlow: numforced = " + numforced);
+}
+*/
+      if (range_select[0] != null && range_select[0].length == 1 &&
+          !range_select[0][0]) {
+        // single missing value in range_select[0], so render nothing
+        return false;
+      }
+
+      // System.out.println("assembleFlow");
+
+      // assemble an array of Display.DisplaySpatialCartesianTuple values
+      // and possibly spatial_set
+      float[][] spatial_values = new float[3][];
+
+      // spatialDimensions[0] = spatialDomainDimension and
+      // spatialDimensions[1] = spatialManifoldDimension
+      int[] spatialDimensions = new int[2];
+      // flags for swapping rows and columns in contour labels
+      boolean[] swap = {false, false, false};
+
+// if (link != null) System.out.println("start assembleSpatial " + (System.currentTimeMillis() - link.start_time));
+      visad.util.Trace.call1("ShadowFunctionOrSetType:assembleSpatial");
+
+      // WLH 29 April 99
+      boolean[][] spatial_range_select = new boolean[1][];
+
+      // MEM - but not if isTextureMap
+      Set spatial_set =
+        shadow_api.assembleSpatial(spatial_values, display_values, valueArrayLength,
+                        valueToScalar, display, default_values,
+                        inherited_values, domain_set, Domain.getAllSpatial(),
+                        anyContour && !isLinearContour3D,
+                        spatialDimensions, spatial_range_select,
+                        flow1_values, flow2_values, flowScale, swap, renderer,
+                        shadow_api);
+
+      if (isLinearContour3D) {
+        spatial_set = domain_set;
+        spatialDimensions[0] = 3;
+        spatialDimensions[1] = 3;
+      }
+
+      // WLH 29 April 99
+      boolean spatial_all_select = true;
+      if (spatial_range_select[0] != null) {
+        spatial_all_select = false;
+        if (range_select[0] == null) {
+          range_select[0] = spatial_range_select[0];
+        }
+        else if (spatial_range_select[0].length == 1) {
+          for (int j=0; j<range_select[0].length; j++) {
+            range_select[0][j] =
+              range_select[0][j] && spatial_range_select[0][0];
+          }
+        }
+        else {
+          for (int j=0; j<range_select[0].length; j++) {
+            range_select[0][j] =
+              range_select[0][j] && spatial_range_select[0][j];
+          }
+        }
+      }
+      spatial_range_select = null;
+
+/*
+if (range_select[0] != null) {
+  int numforced = 0;
+  for (int k=0; k<range_select[0].length; k++) {
+    if (!range_select[0][k]) numforced++;
+  }
+  System.out.println("assembleSpatial: numforced = " + numforced);
+}
+*/
+/*
+System.out.println("assembleSpatial  (spatial_set == null) = " +
+  (spatial_set == null));
+if (spatial_set != null) {
+  System.out.println("spatial_set.length = " + spatial_set.getLength());
+}
+System.out.println("  spatial_values lengths = " + spatial_values[0].length +
+  " " + spatial_values[1].length + " " + spatial_values[2].length);
+System.out.println("  isTextureMap = " + isTextureMap);
+*/
+      if (range_select[0] != null && range_select[0].length == 1 &&
+          !range_select[0][0]) {
+        // single missing value in range_select[0], so render nothing
+        return false;
+      }
+
+      visad.util.Trace.call2("ShadowFunctionOrSetType:assembleSpatial");
+// if (link != null) System.out.println("end assembleSpatial " + (System.currentTimeMillis() - link.start_time));
+
+      int spatialDomainDimension = spatialDimensions[0];
+      int spatialManifoldDimension = spatialDimensions[1];
+
+      // System.out.println("assembleSpatial");
+
+      int spatial_length = Math.min(domain_length, spatial_values[0].length);
+
+      int color_length = Math.min(domain_length, color_values[0].length);
+      int alpha_length = color_values[3].length;
+/*
+      System.out.println("assembleColor, color_length = " + color_length +
+                         "  " + color_values.length);
+*/
+
+// if (link != null) System.out.println("start missing color " + (System.currentTimeMillis() - link.start_time));
+
+      visad.util.Trace.call1("ShadowFunctionOrSetType:missing color");
+      float constant_alpha = Float.NaN;
+      float[] constant_color = null;
+
+      // note alpha_length <= color_length
+      if (alpha_length == 1) {
+/* MEM_WLH
+        if (color_values[3][0] != color_values[3][0]) {
+*/
+        if (single_missing[3]) {
+          // a single missing alpha value, so render nothing
+          // System.out.println("single missing alpha");
+          return false;
+        }
+        // System.out.println("single alpha " + color_values[3][0]);
+        // constant alpha, so put it in appearance
+/* MEM_WLH
+        if (color_values[3][0] > 0.999999f) {
+*/
+        if (color_values[3][0] == -1) {  // = 255 unsigned
+          constant_alpha = 0.0f;
+          // constant_alpha = 1.0f; WLH 26 May 99
+          // remove alpha from color_values
+          byte[][] c = new byte[3][];
+          c[0] = color_values[0];
+          c[1] = color_values[1];
+          c[2] = color_values[2];
+          color_values = c;
+        }
+        else { // not opaque
+/* TransparencyAttributes with constant alpha seems to have broken
+   from The Java 3D API Specification: p. 118 transparency = 1 - alpha,
+   p. 116 transparency 0.0 = opaque, 1.0 = clear */
+/*
+          broken alpha - put it back when alpha fixed
+          constant_alpha =
+            new TransparencyAttributes(TransparencyAttributes.NICEST,
+                             1.0f - byteToFloat(color_values[3][0]));
+   so expand constant alpha to variable alpha
+   and note no alpha in Java2D:
+*/
+          byte v = color_values[3][0];
+          color_values[3] = new byte[color_values[0].length];
+          for (int i=0; i<color_values[0].length; i++) {
+            color_values[3][i] = v;
+          }
+/*
+System.out.println("replicate alpha = " + v + " " + constant_alpha +
+                   " " + color_values[0].length + " " +
+                   color_values[3].length);
+*/
+        } // end not opaque
+/*
+        broken alpha - put it back when alpha fixed
+        // remove alpha from color_values
+        byte[][] c = new byte[3][];
+        c[0] = color_values[0];
+        c[1] = color_values[1];
+        c[2] = color_values[2];
+        color_values = c;
+*/
+      } // end if (alpha_length == 1)
+      if (color_length == 1) {
+        // DRM 2007-06-29: allow transparency on lines
+        //if (spatialManifoldDimension == 1 ||
+        //    shadow_api.allowConstantColorSurfaces()) {
+        if (shadow_api.allowConstantColorSurfaces()) {
+/* MEM_WLH
+          if (color_values[0][0] != color_values[0][0] ||
+              color_values[1][0] != color_values[1][0] ||
+              color_values[2][0] != color_values[2][0]) {
+*/
+          if (single_missing[0] || single_missing[1] ||
+              single_missing[2]) {
+            // System.out.println("single missing color");
+            // a single missing color value, so render nothing
+            return false;
+          }
+/* MEM_WLH
+          constant_color = new float[] {color_values[0][0], color_values[1][0],
+                                        color_values[2][0]};
+*/
+          constant_color = new float[] {byteToFloat(color_values[0][0]),
+                                        byteToFloat(color_values[1][0]),
+                                        byteToFloat(color_values[2][0])};
+          color_values = null; // in this case, alpha doesn't matter
+        }
+        else {
+          // constant color doesn't work for surfaces in Java3D
+          // because of lighting
+          byte[][] c = new byte[color_values.length][domain_length];
+          for (int i=0; i<color_values.length; i++) {
+            java.util.Arrays.fill(c[i], color_values[i][0]);
+          }
+          color_values = c;
+        }
+      } // end if (color_length == 1)
+      visad.util.Trace.call2("ShadowFunctionOrSetType:missing color");
+
+// if (link != null) System.out.println("end missing color " + (System.currentTimeMillis() - link.start_time));
+
+      if (range_select[0] != null && range_select[0].length == 1 &&
+          !range_select[0][0]) {
+        // single missing value in range_select[0], so render nothing
+        return false;
+      }
+      if (LevelOfDifficulty == SIMPLE_FIELD) {
+        // only manage Spatial, Contour, Flow, Color, Alpha and
+        // SelectRange here
+        //
+        // TO_DO
+        // Flow rendering trajectories, which will be tricky -
+        // FlowControl must contain trajectory start points
+        //
+
+/* MISSING TEST
+        for (int i=0; i<spatial_values[0].length; i+=3) {
+          spatial_values[0][i] = Float.NaN;
+        }
+END MISSING TEST */
+
+        //
+        // TO_DO
+        // missing color_values and range_select
+        //
+        // in Java3D:
+        // NaN color component values are rendered as 1.0
+        // NaN spatial component values of points are NOT rendered
+        // NaN spatial component values of lines are rendered at infinity
+        // NaN spatial component values of triangles are a mess ??
+        //
+/*
+        System.out.println("spatialDomainDimension = " +
+                           spatialDomainDimension +
+                           " spatialManifoldDimension = " +
+                           spatialManifoldDimension +
+                           " anyContour = " + anyContour +
+                           " pointMode = " + pointMode);
+*/
+        VisADGeometryArray array;
+
+        visad.util.Trace.call1("ShadowFunctionOrSetType:assembleShape");
+        boolean anyShapeCreated = false;
+        VisADGeometryArray[] arrays =
+          shadow_api.assembleShape(display_values, valueArrayLength, valueToMap,
+                         MapVector, valueToScalar, display, default_values,
+                         inherited_values, spatial_values, color_values,
+                         range_select, -1, shadow_api);
+/*
+if (range_select[0] != null) {
+  int numforced = 0;
+  for (int k=0; k<range_select[0].length; k++) {
+    if (!range_select[0][k]) numforced++;
+  }
+  System.out.println("assembleShape: numforced = " + numforced);
+}
+*/
+        if (arrays != null) {
+          for (int i=0; i<arrays.length; i++) {
+            array = arrays[i];
+            shadow_api.addToGroup(group, array, mode,
+                                  constant_alpha, constant_color);
+            array = null;
+/* WLH 13 March 99 - why null in place of constant_alpha?
+            appearance = makeAppearance(mode, null, constant_color, geometry);
+*/
+          }
+          anyShapeCreated = true;
+          arrays = null;
+        }
+        visad.util.Trace.call2("ShadowFunctionOrSetType:assembleShape");
+
+        boolean anyTextCreated = false;
+        if (anyText && text_values != null && text_control != null) {
+          visad.util.Trace.call1("ShadowFunctionOrSetType:makeText");
+          array = shadow_api.makeText(text_values, text_control, spatial_values,
+                                      color_values, range_select);
+          shadow_api.addTextToGroup(group, array, mode,
+                                    constant_alpha, constant_color);
+          array = null;
+          anyTextCreated = true;
+          visad.util.Trace.call2("ShadowFunctionOrSetType:makeText");
+        }
+
+        boolean anyFlowCreated = false;
+        if (anyFlow) {
+          visad.util.Trace.call1("ShadowFunctionOrSetType:flow");
+          // try Flow1
+
+          visad.util.Trace.call1("ShadowFunctionOrSetType:makeStreamline flow1");
+          arrays = shadow_api.makeStreamline(0, flow1_values, flowScale[0],
+                        spatial_values, spatial_set, spatialManifoldDimension,
+                        color_values, range_select,  valueArrayLength,
+                        valueToMap, MapVector);
+          visad.util.Trace.call2("ShadowFunctionOrSetType:makeStreamline flow1");
+          if (arrays != null) {
+            for (int i=0; i<arrays.length; i++) {
+              if (arrays[i] != null) {
+                shadow_api.addToGroup(group, arrays[i], mode,
+                                      constant_alpha, constant_color);
+                arrays[i] = null;
+              }
+            }
+          }
+          else {
+            visad.util.Trace.call1("ShadowFunctionOrSetType:makeFlow flow1");
+            arrays = shadow_api.makeFlow(0, flow1_values, flowScale[0],
+                              spatial_values, color_values, range_select);
+            if (arrays != null) {
+              for (int i=0; i<arrays.length; i++) {
+                if (arrays[i] != null) {
+                  shadow_api.addToGroup(group, arrays[i], mode,
+                                        constant_alpha, constant_color);
+                  arrays[i] = null;
+                }
+              }
+            }
+            visad.util.Trace.call2("ShadowFunctionOrSetType:makeFlow flow1");
+          }
+          anyFlowCreated = true;
+
+          // try Flow2
+
+          visad.util.Trace.call1("ShadowFunctionOrSetType:makeStreamline flow2");
+          arrays = shadow_api.makeStreamline(1, flow2_values, flowScale[1],
+                          spatial_values, spatial_set, spatialManifoldDimension,
+                          color_values, range_select, valueArrayLength,
+                        valueToMap, MapVector);
+          visad.util.Trace.call2("ShadowFunctionOrSetType:makeStreamline flow2");
+          if (arrays != null) {
+            for (int i=0; i<arrays.length; i++) {
+              if (arrays[i] != null) {
+                shadow_api.addToGroup(group, arrays[i], mode,
+                                      constant_alpha, constant_color);
+                arrays[i] = null;
+              }
+            }
+          }
+          else {
+            visad.util.Trace.call1("ShadowFunctionOrSetType:makeFlow flow2");
+            arrays = shadow_api.makeFlow(1, flow2_values, flowScale[1],
+                              spatial_values, color_values, range_select);
+            if (arrays != null) {
+              for (int i=0; i<arrays.length; i++) {
+                if (arrays[i] != null) {
+                  shadow_api.addToGroup(group, arrays[i], mode,
+                                        constant_alpha, constant_color);
+                  arrays[i] = null;
+                }
+              }
+            }
+            visad.util.Trace.call2("ShadowFunctionOrSetType:makeFlow flow2");
+          }
+          anyFlowCreated = true;
+          visad.util.Trace.call2("ShadowFunctionOrSetType:flow");
+        }
+
+        boolean anyContourCreated = false;
+        if (anyContour) {
+
+/* Test01 at 64 x 64 x 64
+domain 701, 491
+range 20, 20
+assembleColor 210, 201
+assembleSpatial 130, 140
+makeIsoSurface 381, 520
+makeGeometry 350, 171
+  all makeGeometry time in calls to Java3D constructors, setCoordinates, etc
+*/
+
+// if (link != null) System.out.println("start makeContour " + (System.currentTimeMillis() - link.start_time));
+        visad.util.Trace.call1("ShadowFunctionOrSetType:makeContour");
+          anyContourCreated =
+            shadow_api.makeContour(valueArrayLength, valueToScalar,
+                       display_values, inherited_values, MapVector, valueToMap,
+                       domain_length, range_select, spatialManifoldDimension,
+                       spatial_set, color_values, indexed, group, mode,
+                       swap, constant_alpha, constant_color, shadow_api,
+                       Domain, DomainReferenceComponents, domain_set, domain_units,
+                       dataCoordinateSystem);
+        visad.util.Trace.call2("ShadowFunctionOrSetType:makeContour");
+// if (link != null) System.out.println("end makeContour " + (System.currentTimeMillis() - link.start_time));
+        } // end if (anyContour)
+
+        spatial_offset_values = null;
+
+        if (!anyContourCreated && !anyFlowCreated &&
+            !anyTextCreated && !anyShapeCreated) {
+          // MEM
+          if (isTextureMap) {
+            visad.util.Trace.call1("ShadowFunctionOrSetType:linear texture");
+            if (color_values == null) {
+              // must be color_values array for texture mapping
+              color_values = new byte[3][domain_length];
+              for (int i=0; i<domain_length; i++) {
+                color_values[0][i] = floatToByte(constant_color[0]);
+                color_values[1][i] = floatToByte(constant_color[1]);
+                color_values[2][i] = floatToByte(constant_color[2]);
+              }
+            }
+            if (range_select[0] != null && range_select[0].length > 1) {
+              int len = range_select[0].length;
+                                                                                                                                    
+/* can be misleading because of the way transparency composites
+              float alpha =
+                default_values[display.getDisplayScalarIndex(Display.Alpha)];
+// System.out.println("alpha = " + alpha);
+              if (constant_alpha == constant_alpha) {
+                alpha = 1.0f - constant_alpha;
+// System.out.println("constant_alpha = " + alpha);
+              }
+              if (color_values.length < 4) {
+                byte[][] c = new byte[4][];
+                c[0] = color_values[0];
+                c[1] = color_values[1];
+                c[2] = color_values[2];
+                c[3] = new byte[len];
+                for (int i=0; i<len; i++) c[3][i] = floatToByte(alpha);
+                constant_alpha = Float.NaN;
+                color_values = c;
+              }
+              for (int i=0; i<len; i++) {
+                if (!range_select[0][i]) {
+                  // make missing pixel invisible (transparent)
+                  color_values[3][i] = 0;
+                }
+              }
+*/
+              // WLH 27 March 2000
+              float alpha =
+                default_values[display.getDisplayScalarIndex(Display.Alpha)];
+// System.out.println("alpha = " + alpha);
+              if (constant_alpha == constant_alpha) {
+                alpha = 1.0f - constant_alpha;
+// System.out.println("constant_alpha = " + alpha);
+              }
+              if (color_values.length < 4) {
+                byte[][] c = new byte[4][];
+                c[0] = color_values[0];
+                c[1] = color_values[1];
+                c[2] = color_values[2];
+                c[3] = new byte[len];
+                for (int i=0; i<len; i++) c[3][i] = floatToByte(alpha);
+                constant_alpha = Float.NaN;
+                color_values = c;
+              }
+              //if (mode.getMissingTransparent()) {
+              if (isMissingTransparent) {
+                for (int i=0; i<len; i++) {
+                  if (!range_select[0][i]) {
+                    // make missing pixel invisible (transparent)
+                    color_values[3][i] = 0;
+                  }
+                }
+              }
+              else {
+                for (int i=0; i<len; i++) {
+                  if (!range_select[0][i]) {
+                    // make missing pixel black
+                    color_values[0][i] = 0;
+                    color_values[1][i] = 0;
+                    color_values[2][i] = 0;
+                  }
+                }
+              }
+            } // end if (range_select[0] != null)
+
+//- begin texture split logic ------------------------------------------------------
+            int[] lens = ((GriddedSet)domain_set).getLengths();
+                                                                                                                             
+            int limit = link.getDisplay().getDisplayRenderer().getTextureWidthMax();
+                                                                                                                             
+            int y_sub_len = lens[1];
+            int n_y_sub   = 1;
+            while( y_sub_len >= limit ) {
+              y_sub_len /= 2;
+              n_y_sub *= 2;
+            }
+            int[][] y_start_stop = new int[n_y_sub][2];
+            for (int k = 0; k < n_y_sub-1; k++) {
+              y_start_stop[k][0] = k*y_sub_len;
+              y_start_stop[k][1] = (k+1)*y_sub_len - 1;
+            }
+            int k = n_y_sub-1;
+            y_start_stop[k][0] = k*y_sub_len;
+            y_start_stop[k][1] = lens[1] - 1;
+                                                                                                                             
+            int x_sub_len = lens[0];
+            int n_x_sub   = 1;
+            while( x_sub_len >= limit ) {
+              x_sub_len /= 2;
+              n_x_sub *= 2;
+            }
+            int[][] x_start_stop = new int[n_x_sub][2];
+            for (k = 0; k < n_x_sub-1; k++) {
+              x_start_stop[k][0] = k*x_sub_len;
+              x_start_stop[k][1] = (k+1)*x_sub_len - 1;
+            }
+            k = n_x_sub-1;
+            x_start_stop[k][0] = k*x_sub_len;
+            x_start_stop[k][1] = lens[0] - 1;
+
+            if (n_y_sub == 1 && n_x_sub == 1) { //- don't split texture
+              buildLinearTexture(group, domain_set, dataUnits, domain_units, default_values, shadow_api,
+                                 valueArrayLength, valueToScalar, value_array, color_values, 
+                                 mode, constant_color, constant_alpha);
+            }
+            else {
+              Object branch = shadow_api.makeBranch();
+                                                                                                                             
+              int start   = 0;
+              int i_total = 0;
+              for (int i=0; i<n_y_sub; i++) {
+                int leny = y_start_stop[i][1] - y_start_stop[i][0] + 1;
+                for (int j=0; j<n_x_sub; j++) {
+                  int lenx = x_start_stop[j][1] - x_start_stop[j][0] + 1;
+                  float[][] g00 = ((GriddedSet)domain_set).gridToValue(new float[][] {{x_start_stop[j][0]}, {y_start_stop[i][0]}});
+                  float[][] g11 = ((GriddedSet)domain_set).gridToValue(new float[][] {{x_start_stop[j][1]}, {y_start_stop[i][1]}});
+                  double x0 = g00[0][0];
+                  double x1 = g11[0][0];
+                  double y0 = g00[1][0];
+                  double y1 = g11[1][0];
+                  Set dset = new Linear2DSet(x0, x1, lenx, y0, y1, leny);
+                  //- tile piece
+                  byte[][] color_valuesW = null;
+                  if (color_values.length == 3) color_valuesW = new byte[3][lenx*leny];
+                  if (color_values.length == 4) color_valuesW = new byte[4][lenx*leny];
+                  int cnt = 0;
+                  for (k=0; k<leny; k++) {
+                    start = x_start_stop[j][0] +  i_total*lens[0] +  k*lens[0];
+                    System.arraycopy(color_values[0], start, color_valuesW[0], cnt, lenx);
+                    System.arraycopy(color_values[1], start, color_valuesW[1], cnt, lenx);
+                    System.arraycopy(color_values[2], start, color_valuesW[2], cnt, lenx);
+                    if (color_values.length == 4) System.arraycopy(color_values[3], start, color_valuesW[3], cnt, lenx);
+                    cnt += lenx;
+                  }
+                  Object branch1 = shadow_api.makeBranch();
+                  buildLinearTexture(branch1, dset, dataUnits, domain_units, default_values, shadow_api,
+                                     valueArrayLength, valueToScalar, value_array, color_valuesW,
+                                     mode, constant_color, constant_alpha);
+                  shadow_api.addToGroup(branch, branch1);
+                }
+                i_total += leny;
+              }
+              shadow_api.addToGroup(group, branch);
+            }
+//-- end texture split logic -------------------------------------------------------------
+            //System.out.println("isTextureMap done");
+            visad.util.Trace.call2("ShadowFunctionOrSetType:linear texture");
+            return false;
+          } // end if (isTextureMap)
+          else if (curvedTexture) {
+// if (link != null) System.out.println("start texture " + (System.currentTimeMillis() - link.start_time));
+            visad.util.Trace.call1("ShadowFunctionOrSetType:curved texture");
+            if (color_values == null) { // never true?
+              // must be color_values array for texture mapping
+              color_values = new byte[3][domain_length];
+              for (int i=0; i<domain_length; i++) {
+                color_values[0][i] = floatToByte(constant_color[0]);
+                color_values[1][i] = floatToByte(constant_color[1]);
+                color_values[2][i] = floatToByte(constant_color[2]);
+              }
+            }
+
+            // DRM 10-Nov-2005 - copy logic from linear texture
+            //if (range_select[0] != null) {
+            if (range_select[0] != null && range_select[0].length > 1) {
+              int len = range_select[0].length;
+              float alpha =
+                default_values[display.getDisplayScalarIndex(Display.Alpha)];
+              if (constant_alpha == constant_alpha) {
+                alpha = 1.0f - constant_alpha;
+              }
+              if (color_values.length < 4) {
+                byte[][] c = new byte[4][];
+                c[0] = color_values[0];
+                c[1] = color_values[1];
+                c[2] = color_values[2];
+                c[3] = new byte[len];
+                for (int i=0; i<len; i++) c[3][i] = floatToByte(alpha);
+                constant_alpha = Float.NaN;
+                color_values = c;
+              }
+
+              // DRM 10-Nov-2005
+              //if (isMissingTransparent && color_values.length > 3) {
+              if (isMissingTransparent) {
+                for (int i=0; i<domain_length; i++) {
+                  if (!range_select[0][i]) {
+                    // make missing pixel invisible (transparent)
+                    color_values[3][i] = 0;
+                  }
+                }
+              }
+              else {
+                for (int i=0; i<domain_length; i++) {
+                  if (!range_select[0][i]) {
+                    color_values[0][i] = 0;
+                    color_values[1][i] = 0;
+                    color_values[2][i] = 0;
+                  }
+                }
+              }
+            }
+//-- begin texture split logic  ----------------------------------------------------------
+            // get domain_set sizes
+            int[] lens = ((GriddedSet)domain_set).getLengths();
+                                                                                                                                   
+            int limit = link.getDisplay().getDisplayRenderer().getTextureWidthMax();
+                                                                                                                                   
+            int y_sub_len = lens[1];
+            int n_y_sub   = 1;
+            while( y_sub_len >= limit ) {
+              y_sub_len /= 2;
+              n_y_sub *= 2;
+            }
+            int[][] y_start_stop = new int[n_y_sub][2];
+            for (int k = 0; k < n_y_sub-1; k++) {
+              y_start_stop[k][0] = k*y_sub_len;
+              y_start_stop[k][1] = (k+1)*y_sub_len - 1;
+            }
+            int k = n_y_sub-1;
+            y_start_stop[k][0] = k*y_sub_len;
+            y_start_stop[k][1] = lens[1] - 1;
+                                                                                                                                   
+                                                                                                                                   
+            int x_sub_len = lens[0];
+            int n_x_sub   = 1;
+            while( x_sub_len >= limit ) {
+              x_sub_len /= 2;
+              n_x_sub *= 2;
+            }
+            int[][] x_start_stop = new int[n_x_sub][2];
+            for (k = 0; k < n_x_sub-1; k++) {
+              x_start_stop[k][0] = k*x_sub_len;
+              x_start_stop[k][1] = (k+1)*x_sub_len - 1;
+            }
+            k = n_x_sub-1;
+            x_start_stop[k][0] = k*x_sub_len;
+            x_start_stop[k][1] = lens[0] - 1;
+
+                                                                                                                                   
+            if (n_y_sub == 1 && n_x_sub == 1) { //- don't split texture map
+              buildCurvedTexture(group, domain_set, dataUnits, domain_units, default_values, shadow_api,
+                                 valueArrayLength, valueToScalar, value_array, color_values, mode,
+                                 constant_color, constant_alpha, curved_size, spatial_values,
+                                 spatial_all_select, renderer, range_select, domainOnlySpatial,
+                                 null, lens[0], lens[1], lens[0], lens[1]);
+            }
+            else {
+              Object branch = shadow_api.makeBranch();
+              float[][] samples = spatial_values;
+
+              int start   = 0;
+              int i_total = 0;
+              for (int i=0; i<n_y_sub; i++) {
+                int leny = y_start_stop[i][1] - y_start_stop[i][0] + 1;
+                for (int j=0; j<n_x_sub; j++) {
+                  int lenx = x_start_stop[j][1] - x_start_stop[j][0] + 1;
+
+                  if (j > 0) {  // vertical stitch
+                    float[][] samplesC = new float[3][4*leny];
+                    byte[][] color_valuesC  = new byte[color_values.length][4*leny];
+                    int cntv = 0;
+                    int startv = x_start_stop[j][0] + i_total*lens[0];
+                    for (int iv=0; iv < leny; iv++) {
+                      samplesC[0][cntv] = samples[0][startv-2];
+                      samplesC[0][cntv+1] = samples[0][startv-1];
+                      samplesC[0][cntv+2] = samples[0][startv];
+                      samplesC[0][cntv+3] = samples[0][startv+1];
+                      samplesC[1][cntv] = samples[1][startv-2];
+                      samplesC[1][cntv+1] = samples[1][startv-1];
+                      samplesC[1][cntv+2] = samples[1][startv];
+                      samplesC[1][cntv+3] = samples[1][startv+1];
+                      samplesC[2][cntv] = samples[2][startv-2];
+                      samplesC[2][cntv+1] = samples[2][startv-1];
+                      samplesC[2][cntv+2] = samples[2][startv];
+                      samplesC[2][cntv+3] = samples[2][startv+1];
+                      color_valuesC[0][cntv] = color_values[0][startv-2];
+                      color_valuesC[0][cntv+1] = color_values[0][startv-1];
+                      color_valuesC[0][cntv+2] = color_values[0][startv];
+                      color_valuesC[0][cntv+3] = color_values[0][startv+1];
+                      color_valuesC[1][cntv] = color_values[1][startv-2];
+                      color_valuesC[1][cntv+1] = color_values[1][startv-1];
+                      color_valuesC[1][cntv+2] = color_values[1][startv];
+                      color_valuesC[1][cntv+3] = color_values[1][startv+1];
+                      color_valuesC[2][cntv] = color_values[2][startv-2];
+                      color_valuesC[2][cntv+1] = color_values[2][startv-1];
+                      color_valuesC[2][cntv+2] = color_values[2][startv];
+                      color_valuesC[2][cntv+3] = color_values[2][startv+1];
+                      if (color_valuesC.length == 4) {
+                        color_valuesC[3][cntv] = color_values[3][startv-2];
+                        color_valuesC[3][cntv+1] = color_values[3][startv-1];
+                        color_valuesC[3][cntv+2] = color_values[3][startv];
+                        color_valuesC[3][cntv+3] = color_values[3][startv+1];
+                      }
+                      cntv += 4;
+                      startv += lens[0];
+                    }
+                    Object branchv = shadow_api.makeBranch();
+                    buildCurvedTexture(branchv, null, dataUnits, domain_units, default_values, shadow_api,
+                                       valueArrayLength, valueToScalar, value_array, color_valuesC, mode,
+                                       constant_color, constant_alpha, curved_size, samplesC,
+                                       spatial_all_select, renderer, range_select, domainOnlySpatial,
+                                       null,
+                                       4, leny, lens[0], lens[1]);
+                    shadow_api.addToGroup(branch, branchv);
+                  }
+                  if (i > 0) {  // horz stitch
+                    float[][] samplesC = new float[3][4*lenx];
+                    byte[][] color_valuesC = new byte[color_values.length][4*lenx];
+                    int starth = x_start_stop[j][0] + i_total*lens[0];
+                    int cnth = 0;
+                    System.arraycopy(samples[0], starth-2*lens[0], samplesC[0], cnth, lenx);
+                    System.arraycopy(samples[1], starth-2*lens[0], samplesC[1], cnth, lenx);
+                    System.arraycopy(samples[2], starth-2*lens[0], samplesC[2], cnth, lenx);
+                    cnth += lenx;
+                    System.arraycopy(samples[0], starth-1*lens[0], samplesC[0], cnth, lenx);
+                    System.arraycopy(samples[1], starth-1*lens[0], samplesC[1], cnth, lenx);
+                    System.arraycopy(samples[2], starth-1*lens[0], samplesC[2], cnth, lenx);
+                    cnth += lenx;
+                    System.arraycopy(samples[0], starth, samplesC[0], cnth, lenx);
+                    System.arraycopy(samples[1], starth, samplesC[1], cnth, lenx);
+                    System.arraycopy(samples[2], starth, samplesC[2], cnth, lenx);
+                    cnth += lenx;
+                    System.arraycopy(samples[0], starth+1*lens[0], samplesC[0], cnth, lenx);
+                    System.arraycopy(samples[1], starth+1*lens[0], samplesC[1], cnth, lenx);
+                    System.arraycopy(samples[2], starth+1*lens[0], samplesC[2], cnth, lenx);
+                                                                                                                                   
+                    cnth = 0;
+                    System.arraycopy(color_values[0], starth-2*lens[0], color_valuesC[0], cnth, lenx);
+                    System.arraycopy(color_values[1], starth-2*lens[0], color_valuesC[1], cnth, lenx);
+                    System.arraycopy(color_values[2], starth-2*lens[0], color_valuesC[2], cnth, lenx);
+                    if (color_values.length == 4) System.arraycopy(color_values[3], starth-2*lens[0], color_valuesC[3], cnth, lenx);
+                    cnth += lenx;
+                    System.arraycopy(color_values[0], starth-1*lens[0], color_valuesC[0], cnth, lenx);
+                    System.arraycopy(color_values[1], starth-1*lens[0], color_valuesC[1], cnth, lenx);
+                    System.arraycopy(color_values[2], starth-1*lens[0], color_valuesC[2], cnth, lenx);
+                    if (color_values.length == 4) System.arraycopy(color_values[3], starth-1*lens[0], color_valuesC[3], cnth, lenx);
+                    cnth += lenx;
+                    System.arraycopy(color_values[0], starth, color_valuesC[0], cnth, lenx);
+                    System.arraycopy(color_values[1], starth, color_valuesC[1], cnth, lenx);
+                    System.arraycopy(color_values[2], starth, color_valuesC[2], cnth, lenx);
+                    if (color_values.length == 4) System.arraycopy(color_values[3], starth, color_valuesC[3], cnth, lenx);
+                    cnth += lenx;
+                    System.arraycopy(color_values[0], starth+1*lens[0], color_valuesC[0], cnth, lenx);
+                    System.arraycopy(color_values[1], starth+1*lens[0], color_valuesC[1], cnth, lenx);
+                    System.arraycopy(color_values[2], starth+1*lens[0], color_valuesC[2], cnth, lenx);
+                    if (color_values.length == 4) System.arraycopy(color_values[3], starth+1*lens[0], color_valuesC[3], cnth, lenx);
+                                                                                                                                   
+                                                                                                                                   
+                    Object branchh = shadow_api.makeBranch();
+                    buildCurvedTexture(branchh, null, dataUnits, domain_units, default_values, shadow_api,
+                                       valueArrayLength, valueToScalar, value_array, color_valuesC, mode,
+                                       constant_color, constant_alpha, curved_size, samplesC,
+                                       spatial_all_select, renderer, range_select, domainOnlySpatial,
+                                       null,
+                                       lenx, 4, lens[0], lens[1]);
+
+                    shadow_api.addToGroup(branch, branchh);
+                  }
+                  //- tile piece
+                  byte[][] color_valuesW = null;
+                  if (color_values.length == 3) color_valuesW = new byte[3][lenx*leny];
+                  if (color_values.length == 4) color_valuesW = new byte[4][lenx*leny];
+
+                  int cnt = 0;
+                  for (k=0; k<leny; k++) {
+                    start = x_start_stop[j][0] + i_total*lens[0] +  k*lens[0];
+                    System.arraycopy(color_values[0], start, color_valuesW[0], cnt, lenx);
+                    System.arraycopy(color_values[1], start, color_valuesW[1], cnt, lenx);
+                    System.arraycopy(color_values[2], start, color_valuesW[2], cnt, lenx);
+                    if (color_values.length == 4) System.arraycopy(color_values[3], start, color_valuesW[3], cnt, lenx); 
+                    cnt += lenx;
+                  }
+                  Object branch1 = shadow_api.makeBranch();
+                  buildCurvedTexture(branch1, null, dataUnits, domain_units, default_values, shadow_api,
+                                     valueArrayLength, valueToScalar, value_array, color_valuesW, mode,
+                                     constant_color, constant_alpha, curved_size, samples,
+                                     spatial_all_select, renderer, range_select, domainOnlySpatial,
+                                     new int[] {x_start_stop[j][0], y_start_stop[i][0]},
+                                     lenx, leny, lens[0], lens[1]);
+
+                  shadow_api.addToGroup(branch, branch1);
+                }
+                i_total += leny;
+              }
+              shadow_api.addToGroup(group, branch);
+            }
+//-- end texture split logic
+            //System.out.println("curvedTexture done");
+// if (link != null) System.out.println("end texture " + (System.currentTimeMillis() - link.start_time));
+            visad.util.Trace.call2("ShadowFunctionOrSetType:curved texture");
+            return false;
+          } // end if (curvedTexture)
+          else if (isTexture3D) {
+            visad.util.Trace.call1("ShadowFunctionOrSetType:3D texture");
+            if (color_values == null) {
+              // must be color_values array for texture mapping
+              color_values = new byte[3][domain_length];
+              for (int i=0; i<domain_length; i++) {
+                color_values[0][i] = floatToByte(constant_color[0]);
+                color_values[1][i] = floatToByte(constant_color[1]);
+                color_values[2][i] = floatToByte(constant_color[2]);
+              }
+            }
+            if (range_select[0] != null && range_select[0].length > 1) {
+              int len = range_select[0].length;
+
+/* can be misleading because of the way transparency composites
+WLH 15 March 2000 */
+              float alpha =
+                default_values[display.getDisplayScalarIndex(Display.Alpha)];
+              if (constant_alpha == constant_alpha) {
+                alpha = 1.0f - constant_alpha;
+              }
+              if (color_values.length < 4) {
+                byte[][] c = new byte[4][];
+                c[0] = color_values[0];
+                c[1] = color_values[1];
+                c[2] = color_values[2];
+                c[3] = new byte[len];
+                for (int i=0; i<len; i++) c[3][i] = floatToByte(alpha);
+                constant_alpha = Float.NaN;
+                color_values = c;
+              }
+              for (int i=0; i<len; i++) {
+                if (!range_select[0][i]) {
+                  // make missing pixel invisible (transparent)
+                  color_values[3][i] = 0;
+
+                  // WLH 15 March 2000
+                  // make missing pixel black
+                  color_values[0][i] = 0;
+                  color_values[1][i] = 0;
+                  color_values[2][i] = 0;
+                }
+              }
+/* WLH 15 March 2000 */
+
+/* WLH 15 March 2000
+              for (int i=0; i<len; i++) {
+                if (!range_select[0][i]) {
+                  // make missing pixel black
+                  color_values[0][i] = 0;
+                  color_values[1][i] = 0;
+                  color_values[2][i] = 0;
+                }
+              }
+*/
+            } // end if (range_select[0] != null)
+
+            // MEM
+            VisADQuadArray[] qarray =
+              {new VisADQuadArray(), new VisADQuadArray(), new VisADQuadArray()};
+            qarray[0].vertexCount = coordinatesX.length / 3;
+            qarray[0].coordinates = coordinatesX;
+            qarray[0].texCoords = texCoordsX;
+            qarray[0].colors = colorsX;
+            qarray[0].normals = normalsX;
+
+            qarray[1].vertexCount = coordinatesY.length / 3;
+            qarray[1].coordinates = coordinatesY;
+            qarray[1].texCoords = texCoordsY;
+            qarray[1].colors = colorsY;
+            qarray[1].normals = normalsY;
+
+            qarray[2].vertexCount = coordinatesZ.length / 3;
+            qarray[2].coordinates = coordinatesZ;
+            qarray[2].texCoords = texCoordsZ;
+            qarray[2].colors = colorsZ;
+            qarray[2].normals = normalsZ;
+
+            /*
+            // WLH 3 June 99 - until Texture3D works on NT (etc)
+            if (t3dm = GraphicsModeControl.STACK2D) {
+              BufferedImage[][] images = new BufferedImage[3][];
+              for (int i=0; i<3; i++) {
+                images[i] = createImages(i, data_width, data_height, data_depth,
+                                 texture_width, texture_height, texture_depth,
+                                 color_values);
+              }
+              BufferedImage[] imagesX = null;
+              BufferedImage[] imagesY = null;
+              BufferedImage[] imagesZ = null;
+            }
+            */
+
+            VisADQuadArray qarrayX = null;
+            VisADQuadArray qarrayY = null;
+            VisADQuadArray qarrayZ = null;
+            for (int i=0; i<3; i++) {
+              if (volume_tuple_index[i] == 0) {
+                qarrayX = qarray[i];
+                //imagesX = images[i];
+              }
+              else if (volume_tuple_index[i] == 1) {
+                qarrayY = qarray[i];
+                //imagesY = images[i];
+              }
+              else if (volume_tuple_index[i] == 2) {
+                qarrayZ = qarray[i];
+                //imagesZ = images[i];
+              }
+            }
+            VisADQuadArray qarrayXrev = reverse(qarrayX);
+            VisADQuadArray qarrayYrev = reverse(qarrayY);
+            VisADQuadArray qarrayZrev = reverse(qarrayZ);
+
+            if (t3dm == GraphicsModeControl.STACK2D) {
+      // WLH 3 June 99 - comment this out until Texture3D works on NT (etc)
+              BufferedImage[][] images = new BufferedImage[3][];
+              for (int i=0; i<3; i++) {
+                images[i] = createImages(i, data_width, data_height, data_depth,
+                                 texture_width, texture_height, texture_depth,
+                                 color_values);
+              }
+              BufferedImage[] imagesX = null;
+              BufferedImage[] imagesY = null;
+              BufferedImage[] imagesZ = null;
+              for (int i=0; i<3; i++) {
+                if (volume_tuple_index[i] == 0) {
+                  imagesX = images[i];
+                }
+                else if (volume_tuple_index[i] == 1) {
+                  imagesY = images[i];
+                }
+                else if (volume_tuple_index[i] == 2) {
+                  imagesZ = images[i];
+                }
+              }
+              shadow_api.textureStackToGroup(group, qarrayX, qarrayY, qarrayZ,
+                                      qarrayXrev, qarrayYrev, qarrayZrev,
+                                      imagesX, imagesY, imagesZ,
+                                      mode, constant_alpha, constant_color,
+                                      texture_width, texture_height, texture_depth,
+                                      renderer);
+            } else {
+
+              BufferedImage[] images =
+                createImages(2, data_width, data_height, data_depth,
+                             texture_width, texture_height, texture_depth,
+                             color_values);
+              shadow_api.texture3DToGroup(group, qarrayX, qarrayY, qarrayZ,
+                                          qarrayXrev, qarrayYrev, qarrayZrev,
+                                          images, mode, constant_alpha,
+                                          constant_color, texture_width,
+                                          texture_height, texture_depth, renderer);
+
+            }
+            // System.out.println("isTexture3D done");
+            visad.util.Trace.call1("ShadowFunctionOrSetType:3D texture");
+            return false;
+          } // end if (isTexture3D)
+          else if (pointMode || spatial_set == null ||
+                   spatialManifoldDimension == 0 ||
+                   spatialManifoldDimension == 3) {
+            visad.util.Trace.call1("ShadowFunctionOrSetType:point mode");
+            if (range_select[0] != null) {
+              int len = range_select[0].length;
+              if (len == 1 || spatial_values[0].length == 1) {
+                return false;
+              }
+              for (int j=0; j<len; j++) {
+                // range_select[0][j] is either 0.0f or Float.NaN -
+                // setting to Float.NaN will move points off the screen
+                if (!range_select[0][j]) {
+                  spatial_values[0][j] = Float.NaN;
+                }
+              }
+              // CTR 13 Oct 1998 - call new makePointGeometry signature
+              array = makePointGeometry(spatial_values, color_values, true);
+              // System.out.println("makePointGeometry for some missing");
+            }
+            else {
+              array = makePointGeometry(spatial_values, color_values);
+              // System.out.println("makePointGeometry for pointMode");
+            }
+            visad.util.Trace.call2("ShadowFunctionOrSetType:point mode");
+          }
+          else if (spatialManifoldDimension == 1) {
+            visad.util.Trace.call1("ShadowFunctionOrSetType:manifold dimension == 1");
+            if (range_select[0] != null) {
+              // WLH 27 March 2000
+              //if (mode.getMissingTransparent()) {
+              if (isMissingTransparent) {
+                spatial_set.cram_missing(range_select[0]);
+                spatial_all_select = false;
+              }
+              else {
+                if (color_values == null) {
+                  color_values = new byte[4][domain_length];
+                  for (int i=0; i<domain_length; i++) {
+                    color_values[0][i] = floatToByte(constant_color[0]);
+                    color_values[1][i] = floatToByte(constant_color[1]);
+                    color_values[2][i] = floatToByte(constant_color[2]);
+                  }
+                }
+                for (int i=0; i<domain_length; i++) {
+                  if (!range_select[0][i]) {
+                    color_values[0][i] = 0;
+                    color_values[1][i] = 0;
+                    color_values[2][i] = 0;
+                  }
+                }
+              }
+
+            }
+            array = spatial_set.make1DGeometry(color_values);
+            if (array != null) {
+              if (!spatial_all_select) array = array.removeMissing();
+              if (getAdjustProjectionSeam()) {
+                array = array.adjustLongitude(renderer);
+                array = array.adjustSeam(renderer);
+              }
+            }
+            visad.util.Trace.call2("ShadowFunctionOrSetType:manifold dimension == 1");
+            // System.out.println("make1DGeometry");
+          }
+          else if (spatialManifoldDimension == 2) {
+            visad.util.Trace.call1("ShadowFunctionOrSetType:manifold dimension == 2");
+            if (range_select[0] != null) {
+              // WLH 27 March 2000
+              //if (mode.getMissingTransparent()) {
+              if (isMissingTransparent) {
+                spatial_set.cram_missing(range_select[0]);
+                spatial_all_select = false;
+              }
+              else {
+                if (color_values == null) {
+                  color_values = new byte[4][domain_length];
+                  for (int i=0; i<domain_length; i++) {
+                    color_values[0][i] = floatToByte(constant_color[0]);
+                    color_values[1][i] = floatToByte(constant_color[1]);
+                    color_values[2][i] = floatToByte(constant_color[2]);
+                  }
+                }
+                for (int i=0; i<domain_length; i++) {
+                  if (!range_select[0][i]) {
+                    color_values[0][i] = 0;
+                    color_values[1][i] = 0;
+                    color_values[2][i] = 0;
+                  }
+                }
+              }
+
+            }
+            array = spatial_set.make2DGeometry(color_values, indexed);
+            if (array != null) {
+              if (!spatial_all_select) array = array.removeMissing();
+              if (getAdjustProjectionSeam()) {
+                array = array.adjustLongitude(renderer);
+                array = array.adjustSeam(renderer);
+              }
+            }
+            // System.out.println("make2DGeometry  vertexCount = " +
+            //                    array.vertexCount);
+            visad.util.Trace.call2("ShadowFunctionOrSetType:manifold dimension == 2");
+          }
+          else {
+            throw new DisplayException("bad spatialManifoldDimension: " +
+                                       "ShadowFunctionOrSetType.doTransform");
+          }
+
+          if (array != null && array.vertexCount > 0) {
+            shadow_api.addToGroup(group, array, mode,
+                                  constant_alpha, constant_color);
+            // System.out.println("array.makeGeometry");
+            //  FREE
+            array = null;
+/* WLH 25 June 2000
+            if (renderer.getIsDirectManipulation()) {
+              renderer.setSpatialValues(spatial_values);
+            }
+*/
+          }
+        } // end if (!anyContourCreated && !anyFlowCreated &&
+          //         !anyTextCreated && !anyShapeCreated)
+
+        // WLH 25 June 2000
+        if (renderer.getIsDirectManipulation()) {
+          renderer.setSpatialValues(spatial_values);
+        }
+
+// if (link != null) System.out.println("end doTransform " + (System.currentTimeMillis() - link.start_time));
+
+        return false;
+      } // end if (LevelOfDifficulty == SIMPLE_FIELD)
+      else if (LevelOfDifficulty == SIMPLE_ANIMATE_FIELD) {
+
+        Control control = null;
+        Object swit = null;
+        int index = -1;
+
+        if (DomainComponents.length == 1) {
+          RealType real = (RealType) DomainComponents[0].getType();
+          for (int i=0; i<valueArrayLength; i++) {
+            ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+            float[] values = display_values[i];
+            if (values != null && real.equals(map.getScalar())) {
+              int displayScalarIndex = valueToScalar[i];
+              DisplayRealType dreal =
+                display.getDisplayScalar(displayScalarIndex);
+              if (dreal.equals(Display.Animation) ||
+                  dreal.equals(Display.SelectValue)) {
+                swit = shadow_api.makeSwitch();
+                index = i;
+                control = map.getControl();
+                break;
+              }
+            } // end if (values != null && && real.equals(map.getScalar()))
+          } // end for (int i=0; i<valueArrayLength; i++)
+        } // end if (DomainComponents.length == 1)
+
+        if (control == null) {
+          throw new DisplayException("bad SIMPLE_ANIMATE_FIELD: " +
+                                     "ShadowFunctionOrSetType.doTransform");
+        }
+
+        for (int i=0; i<domain_length; i++) {
+          Object branch = shadow_api.makeBranch();
+          if (range_select[0] == null || range_select[0].length == 1 ||
+              range_select[0][i]) {
+            VisADGeometryArray array = null;
+
+            float[][] sp = new float[3][1];
+            if (spatial_values[0].length > 1) {
+              sp[0][0] = spatial_values[0][i];
+              sp[1][0] = spatial_values[1][i];
+              sp[2][0] = spatial_values[2][i];
+            }
+            else {
+              sp[0][0] = spatial_values[0][0];
+              sp[1][0] = spatial_values[1][0];
+              sp[2][0] = spatial_values[2][0];
+            }
+            byte[][] co = new byte[3][1];
+            if (color_values == null) {
+              co[0][0] = floatToByte(constant_color[0]);
+              co[1][0] = floatToByte(constant_color[1]);
+              co[2][0] = floatToByte(constant_color[2]);
+            }
+            else if (color_values[0].length > 1) {
+              co[0][0] = color_values[0][i];
+              co[1][0] = color_values[1][i];
+              co[2][0] = color_values[2][i];
+            }
+            else {
+              co[0][0] = color_values[0][0];
+              co[1][0] = color_values[1][0];
+              co[2][0] = color_values[2][0];
+            }
+            boolean[][] ra = {{true}};
+
+            boolean anyShapeCreated = false;
+            VisADGeometryArray[] arrays =
+              shadow_api.assembleShape(display_values, valueArrayLength,
+                            valueToMap, MapVector, valueToScalar, display,
+                            default_values, inherited_values,
+                            sp, co, ra, i, shadow_api);
+            if (arrays != null) {
+              for (int j=0; j<arrays.length; j++) {
+                array = arrays[j];
+                shadow_api.addToGroup(branch, array, mode,
+                                      constant_alpha, constant_color);
+                array = null;
+/* why null constant_alpha?
+                appearance = makeAppearance(mode, null, constant_color, geometry);
+*/
+              }
+              anyShapeCreated = true;
+              arrays = null;
+            }
+
+            boolean anyTextCreated = false;
+            if (anyText && text_values != null && text_control != null) {
+              String[] te = new String[1];
+              if (text_values.length > 1) {
+                te[0] = text_values[i];
+              }
+              else {
+                te[0] = text_values[0];
+              }
+              array = shadow_api.makeText(te, text_control, sp, co, ra);
+              if (array != null) {
+                shadow_api.addTextToGroup(branch, array, mode,
+                                          constant_alpha, constant_color);
+                array = null;
+                anyTextCreated = true;
+              }
+            }
+
+            boolean anyFlowCreated = false;
+            if (anyFlow) {
+	      if (flow1_values != null && flow1_values[0] != null) {
+                // try Flow1
+                float[][] f1 = new float[3][1];
+                if (flow1_values[0].length > 1) {
+                  f1[0][0] = flow1_values[0][i];
+                  f1[1][0] = flow1_values[1][i];
+                  f1[2][0] = flow1_values[2][i];
+                }
+                else {
+                  f1[0][0] = flow1_values[0][0];
+                  f1[1][0] = flow1_values[1][0];
+                  f1[2][0] = flow1_values[2][0];
+                }
+                arrays = shadow_api.makeFlow(0, f1, flowScale[0], sp, co, ra);
+                if (arrays != null) {
+                  for (int j=0; j<arrays.length; j++) {
+                    if (arrays[j] != null) {
+                      shadow_api.addToGroup(branch, arrays[j], mode,
+                                            constant_alpha, constant_color);
+                      arrays[j] = null;
+                    }
+                  }
+                }
+                anyFlowCreated = true;
+              }
+    
+              // try Flow2
+              if (flow2_values != null && flow2_values[0] != null) {
+                float[][] f2 = new float[3][1];
+                if (flow2_values[0].length > 1) {
+                  f2[0][0] = flow2_values[0][i];
+                  f2[1][0] = flow2_values[1][i];
+                  f2[2][0] = flow2_values[2][i];
+                }
+                else {
+                  f2[0][0] = flow2_values[0][0];
+                  f2[1][0] = flow2_values[1][0];
+                  f2[2][0] = flow2_values[2][0];
+                }
+                arrays = shadow_api.makeFlow(1, f2, flowScale[1], sp, co, ra);
+                if (arrays != null) {
+                  for (int j=0; j<arrays.length; j++) {
+                    if (arrays[j] != null) {
+                      shadow_api.addToGroup(branch, arrays[j], mode,
+                                            constant_alpha, constant_color);
+                      arrays[j] = null;
+                    }
+                  }
+                }
+                anyFlowCreated = true;
+              }
+            }
+
+            if (!anyShapeCreated && !anyTextCreated &&
+                !anyFlowCreated) {
+              array = new VisADPointArray();
+              array.vertexCount = 1;
+              coordinates = new float[3];
+              coordinates[0] = sp[0][0];
+              coordinates[1] = sp[1][0];
+              coordinates[2] = sp[2][0];
+              array.coordinates = coordinates;
+              if (color_values != null) {
+                colors = new byte[3];
+                colors[0] = co[0][0];
+                colors[1] = co[1][0];
+                colors[2] = co[2][0];
+                array.colors = colors;
+              }
+              shadow_api.addToGroup(branch, array, mode,
+                                    constant_alpha, constant_color);
+              array = null;
+              // System.out.println("addChild " + i + " of " + domain_length);
+            }
+          }
+          else { // if (range_select[0][i])
+/* WLH 18 Aug 98
+   empty BranchGroup or Shape3D may cause NullPointerException
+   from Shape3DRetained.setLive
+            // add null BranchGroup as child to maintain order
+            branch.addChild(new Shape3D());
+*/
+            // System.out.println("addChild " + i + " of " + domain_length +
+            //                    " MISSING");
+          }
+          shadow_api.addToSwitch(swit, branch);
+        } // end for (int i=0; i<domain_length; i++)
+
+        shadow_api.addSwitch(group, swit, control, domain_set, renderer);
+        return false;
+      }  // end if (LevelOfDifficulty == SIMPLE_ANIMATE_FIELD)
+      else { // must be LevelOfDifficulty == LEGAL
+        // add values to value_array according to SelectedMapVector-s
+        // of RealType-s in Domain (including Reference) and Range
+        //
+        // accumulate Vector of value_array-s at this ShadowType,
+        // to be rendered in a post-process to scanning data
+        //
+        // ** OR JUST EACH FIELD INDEPENDENTLY **
+        //
+/*
+        return true;
+*/
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowFunctionOrSetType.doTransform");
+      }
+    }
+    else { // !isTerminal
+      // domain_values and range_values, as well as their References,
+      // already converted to default Units and added to display_values
+
+      // add values to value_array according to SelectedMapVector-s
+      // of RealType-s in Domain (including Reference), and
+      // recursively call doTransform on Range values
+
+      //
+      // TO_DO
+      // SelectRange (use boolean[][] range_select from assembleSelect),
+      //   SelectValue, Animation
+      // DataRenderer.isTransformControl temporary hack:
+      // SelectRange.isTransform,
+      // !SelectValue.isTransform, !Animation.isTransform
+      //
+      // may need to split ShadowType.checkAnimationOrValue
+      // Display.Animation has no range, is single
+      // Display.Value has no range, is not single
+      //
+      // see Set.merge1DSets
+
+      boolean post = false;
+
+      Control control = null;
+      Object swit = null;
+      int index = -1;
+
+      if (DomainComponents.length == 1) {
+        RealType real = (RealType) DomainComponents[0].getType();
+        for (int i=0; i<valueArrayLength; i++) {
+          ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+          float[] values = display_values[i];
+          if (values != null && real.equals(map.getScalar())) {
+            int displayScalarIndex = valueToScalar[i];
+            DisplayRealType dreal =
+              display.getDisplayScalar(displayScalarIndex);
+            if (dreal.equals(Display.Animation) ||
+                dreal.equals(Display.SelectValue)) {
+              swit = shadow_api.makeSwitch();
+              index = i;
+              control = map.getControl();
+              break;
+            }
+          } // end if (values != null && real.equals(map.getScalar()))
+        } // end for (int i=0; i<valueArrayLength; i++)
+      } // end if (DomainComponents.length == 1)
+
+      if (control != null) {
+        shadow_api.addSwitch(group, swit, control, domain_set, renderer);
+/*
+        group.addChild(swit);
+        control.addPair(swit, domain_set, renderer);
+*/
+      }
+
+      float[] range_value_array = new float[valueArrayLength];
+      for (int j=0; j<display.getValueArrayLength(); j++) {
+        range_value_array[j] = Float.NaN;
+      }
+      for (int i=0; i<domain_length; i++) {
+        if (range_select[0] == null || range_select[0].length == 1 ||
+            range_select[0][i]) {
+          if (text_values != null && text_control != null) {
+            shadow_api.setText(text_values[i], text_control);
+          }
+          else {
+            shadow_api.setText(null, null);
+          }
+          for (int j=0; j<valueArrayLength; j++) {
+            if (display_values[j] != null) {
+              if (display_values[j].length == 1) {
+                range_value_array[j] = display_values[j][0];
+              }
+              else {
+                range_value_array[j] = display_values[j][i];
+              }
+            }
+          }
+
+          // push lat_index and lon_index for flow navigation
+          int[] lat_lon_indices = renderer.getLatLonIndices();
+          if (control != null) {
+            Object branch = shadow_api.makeBranch();
+            post |= shadow_api.recurseRange(branch, ((Field) data).getSample(i),
+                                             range_value_array, default_values,
+                                             renderer);
+            shadow_api.addToSwitch(swit, branch);
+            // System.out.println("addChild " + i + " of " + domain_length);
+          }
+          else {
+            Object branch = shadow_api.makeBranch();
+            post |= shadow_api.recurseRange(branch, ((Field) data).getSample(i),
+                                             range_value_array, default_values,
+                                             renderer);
+            shadow_api.addToGroup(group, branch);
+          }
+          // pop lat_index and lon_index for flow navigation
+          renderer.setLatLonIndices(lat_lon_indices);
+
+        }
+        else { // if (!range_select[0][i])
+          if (control != null) {
+            // add null BranchGroup as child to maintain order
+            Object branch = shadow_api.makeBranch();
+            shadow_api.addToSwitch(swit, branch);
+            // System.out.println("addChild " + i + " of " + domain_length +
+            //                    " MISSING");
+          }
+        }
+      }
+
+/* why later than addPair & addChild(swit) ??
+      if (control != null) {
+        // initialize swit child selection
+        control.init();
+      }
+*/
+
+      return post;
+/*
+      throw new UnimplementedException("ShadowFunctionOrSetType.doTransform: " +
+                                       "not terminal");
+*/
+    } // end if (!isTerminal)
+  }
+
+  public BufferedImage createImage(int data_width, int data_height,
+                       int texture_width, int texture_height,
+                       byte[][] color_values) throws VisADException {
+    return createImage(data_width, data_height,
+      texture_width, texture_height, color_values, false);
+  }
+
+  public BufferedImage createImage(int data_width, int data_height,
+                       int texture_width, int texture_height,
+                       byte[][] color_values, boolean byRef)
+                       throws VisADException {
+    //if (byRef) {
+    if (false) {
+      if (data_width > texture_width || data_height > texture_height) {
+        throw new VisADException(
+          "Data dimensions cannot exceed texture dimensions");
+      }
+      int size = texture_width * texture_height;
+      if (data_width != texture_width || data_height != texture_height) {
+        // expand color_values array to match texture size
+        byte[][] new_color_values =
+          new byte[color_values.length][size];
+        for (int c=0; c<color_values.length; c++) {
+          for (int h=0; h<data_height; h++) {
+            System.arraycopy(color_values[c], data_width * h,
+              new_color_values[c], texture_width * h, data_width);
+          }
+        }
+        color_values = new_color_values;
+      }
+
+      // CTR 17 Jan 2006 - create BufferedImage with TYPE_CUSTOM of the form
+      // "TYPE_3BYTE_RGB" or "TYPE_4BYTE_RGBA", since those types can work with
+      // Java3D texturing by reference
+      ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+      ColorModel colorModel = new ComponentColorModel(colorSpace,
+        color_values.length > 3, false, ColorModel.TRANSLUCENT,
+        DataBuffer.TYPE_BYTE);
+      SampleModel sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE,
+        texture_width, texture_height, color_values.length);
+      DataBuffer buffer = new DataBufferByte(color_values, size);
+      WritableRaster raster =
+        Raster.createWritableRaster(sampleModel, buffer, null);
+      return new BufferedImage(colorModel, raster, false, null);
+    }
+
+    BufferedImage image = null;
+    if (color_values.length > 3) {
+      if (!byReference) {
+      ColorModel colorModel = ColorModel.getRGBdefault();
+      WritableRaster raster =
+        colorModel.createCompatibleWritableRaster(texture_width, texture_height);
+      DataBuffer db = raster.getDataBuffer();
+      if (!(db instanceof DataBufferInt)) {
+        throw new UnimplementedException("getRGBdefault isn't DataBufferInt");
+      }
+      image = new BufferedImage(colorModel, raster, false, null);
+      int[] intData = ((DataBufferInt)db).getData();
+      int k = 0;
+      int m = 0;
+      int r, g, b, a;
+      for (int j=0; j<data_height; j++) {
+        for (int i=0; i<data_width; i++) {
+          r = (color_values[0][k] < 0) ? color_values[0][k] + 256 :
+                                         color_values[0][k];
+          g = (color_values[1][k] < 0) ? color_values[1][k] + 256 :
+                                         color_values[1][k];
+          b = (color_values[2][k] < 0) ? color_values[2][k] + 256 :
+                                         color_values[2][k];
+          a = (color_values[3][k] < 0) ? color_values[3][k] + 256 :
+                                         color_values[3][k];
+          intData[m++] = ((a << 24) | (r << 16) | (g << 8) | b);
+          k++;
+        }
+        for (int i=data_width; i<texture_width; i++) {
+          intData[m++] = 0;
+        }
+      }
+      for (int j=data_height; j<texture_height; j++) {
+        for (int i=0; i<texture_width; i++) {
+          intData[m++] = 0;
+        }
+      }
+      }
+      else {
+      image = new BufferedImage(texture_width, texture_height, BufferedImage.TYPE_4BYTE_ABGR);
+      Raster raster = image.getRaster();
+      DataBuffer db = raster.getDataBuffer();
+      byte[] byteData = ((DataBufferByte)db).getData();
+
+      int k = 0;
+      int m = 0;
+      byte r, g, b, a;
+      for (int j=0; j<data_height; j++) {
+        for (int i=0; i<data_width; i++) {
+          r = color_values[0][k];
+          g = color_values[1][k];
+          b = color_values[2][k];
+          a = color_values[3][k];
+          /**
+          r = (color_values[0][k] < 0) ? color_values[0][k] + 256 :
+                                         color_values[0][k];
+          g = (color_values[1][k] < 0) ? color_values[1][k] + 256 :
+                                         color_values[1][k];
+          b = (color_values[2][k] < 0) ? color_values[2][k] + 256 :
+                                         color_values[2][k];
+          a = (color_values[3][k] < 0) ? color_values[3][k] + 256 :
+                                         color_values[3][k];
+          **/
+          /**
+          if (byReference) {
+            intData[m++] = ((a << 24) | (b << 16) | (g << 8) | r);
+          }
+          else {
+            intData[m++] = ((a << 24) | (r << 16) | (g << 8) | b);
+          }
+          **/
+          byteData[m++] = a;
+          byteData[m++] = b;
+          byteData[m++] = g;
+          byteData[m++] = r;
+          k++;
+        }
+        for (int i=data_width; i<texture_width; i++) {
+          byteData[m++] = 0;
+          byteData[m++] = 0;
+          byteData[m++] = 0;
+          byteData[m++] = 0;
+        }
+      }
+      for (int j=data_height; j<texture_height; j++) {
+        for (int i=0; i<texture_width; i++) {
+          byteData[m++] = 0;
+          byteData[m++] = 0;
+          byteData[m++] = 0;
+          byteData[m++] = 0;
+        }
+      }
+     }
+      /*
+      if (byReference) {
+        image = new BufferedImage(texture_width, texture_height, BufferedImage.TYPE_4BYTE_ABGR);
+        image.setRGB(0,0,texture_width,texture_height,intData,0,texture_width);
+      }
+      */
+    }
+    else { // (color_values.length == 3)
+      ColorModel colorModel = ColorModel.getRGBdefault();
+      WritableRaster raster =
+        colorModel.createCompatibleWritableRaster(texture_width, texture_height);
+
+      // WLH 2 Nov 2000
+      DataBuffer db = raster.getDataBuffer();
+      int[] intData = null;
+      if (db instanceof DataBufferInt) {
+        intData = ((DataBufferInt)db).getData();
+        image = new BufferedImage(colorModel, raster, false, null);
+      }
+      else {
+        image = new BufferedImage(texture_width, texture_height,
+                                  BufferedImage.TYPE_INT_RGB);
+        intData = new int[texture_width * texture_height];
+/*
+        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); 
+        int[] nBits = {8, 8, 8};
+        colorModel =
+          new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, 0); 
+        raster = 
+          colorModel.createCompatibleWritableRaster(texture_width, texture_height);
+*/
+      }
+
+      // image = new BufferedImage(colorModel, raster, false, null);
+      // int[] intData = ((DataBufferInt)raster.getDataBuffer()).getData();
+      int k = 0;
+      int m = 0;
+      int r, g, b, a;
+      for (int j=0; j<data_height; j++) {
+        for (int i=0; i<data_width; i++) {
+          r = (color_values[0][k] < 0) ? color_values[0][k] + 256 :
+                                         color_values[0][k];
+          g = (color_values[1][k] < 0) ? color_values[1][k] + 256 :
+                                         color_values[1][k];
+          b = (color_values[2][k] < 0) ? color_values[2][k] + 256 :
+                                         color_values[2][k];
+          a = 255;
+          intData[m++] = ((a << 24) | (r << 16) | (g << 8) | b);
+          k++;
+        }
+        for (int i=data_width; i<texture_width; i++) {
+          intData[m++] = 0;
+        }
+      }
+      for (int j=data_height; j<texture_height; j++) {
+        for (int i=0; i<texture_width; i++) {
+          intData[m++] = 0;
+        }
+      }
+      //-TDR
+      //image = new BufferedImage(texture_width, texture_height, BufferedImage.TYPE_4BYTE_ABGR);
+      //image.setRGB(0,0,texture_width,texture_height,intData,0,texture_width);
+
+      // WLH 2 Nov 2000
+      if (!(db instanceof DataBufferInt)) {
+ System.out.println("byteData 3 2");
+        image.setRGB(0, 0, texture_width, texture_height, intData, 0, texture_width);
+/*
+        byte[] byteData = ((DataBufferByte)raster.getDataBuffer()).getData();
+        k = 0;
+        for (int i=0; i<intData.length; i++) {
+          byteData[k++] = (byte) (intData[i] & 255);
+          byteData[k++] = (byte) ((intData[i] >> 8) & 255);
+          byteData[k++] = (byte) ((intData[i] >> 16) & 255);
+        }
+*/
+/* WLH 4 Nov 2000, from com.sun.j3d.utils.geometry.Text2D
+        // For now, jdk 1.2 only handles ARGB format, not the RGBA we want
+        BufferedImage bImage = new BufferedImage(width, height,
+                                                 BufferedImage.TYPE_INT_ARGB);
+        Graphics offscreenGraphics = bImage.createGraphics();
+
+        // First, erase the background to the text panel - set alpha to 0
+        Color myFill = new Color(0f, 0f, 0f, 0f);
+        offscreenGraphics.setColor(myFill);
+        offscreenGraphics.fillRect(0, 0, width, height);
+
+        // Next, set desired text properties (font, color) and draw String
+        offscreenGraphics.setFont(font);
+        Color myTextColor = new Color(color.x, color.y, color.z, 1f);
+        offscreenGraphics.setColor(myTextColor);
+        offscreenGraphics.drawString(text, 0, height - descent);
+*/
+      } // end if (!(db instanceof DataBufferInt))
+
+    } // end if (color_values.length == 3)
+    return image;
+  }
+
+  public BufferedImage[] createImages(int axis, int data_width_in,
+           int data_height_in, int data_depth_in, int texture_width_in,
+           int texture_height_in, int texture_depth_in, byte[][] color_values)
+         throws VisADException {
+    int data_width, data_height, data_depth;
+    int texture_width, texture_height, texture_depth;
+    int kwidth, kheight, kdepth;
+    if (axis == 2) {
+      kwidth = 1;
+      kheight = data_width_in;
+      kdepth = data_width_in * data_height_in;
+      data_width = data_width_in;
+      data_height = data_height_in;
+      data_depth = data_depth_in;
+      texture_width = texture_width_in;
+      texture_height = texture_height_in;
+      texture_depth = texture_depth_in;
+
+    }
+    else if (axis == 1) {
+      kwidth = 1;
+      kdepth = data_width_in;
+      kheight = data_width_in * data_height_in;
+      data_width = data_width_in;
+      data_depth = data_height_in;
+      data_height = data_depth_in;
+      texture_width = texture_width_in;
+      texture_depth = texture_height_in;
+      texture_height = texture_depth_in;
+    }
+    else if (axis == 0) {
+      kdepth = 1;
+      kwidth = data_width_in;
+      kheight = data_width_in * data_height_in;
+      data_depth = data_width_in;
+      data_width = data_height_in;
+      data_height = data_depth_in;
+      texture_depth = texture_width_in;
+      texture_width = texture_height_in;
+      texture_height = texture_depth_in;
+    }
+    else {
+      return null;
+    }
+    BufferedImage[] images = new BufferedImage[texture_depth];
+    for (int d=0; d<data_depth; d++) {
+      if (color_values.length > 3) {
+        ColorModel colorModel = ColorModel.getRGBdefault();
+        WritableRaster raster =
+          colorModel.createCompatibleWritableRaster(texture_width, texture_height);
+
+        images[d] = new BufferedImage(colorModel, raster, false, null);
+/* WLH 23 Feb 2000
+        if (axis == 1) {
+          images[(data_depth-1) - d] =
+            new BufferedImage(colorModel, raster, false, null);
+        }
+        else {
+          images[d] = new BufferedImage(colorModel, raster, false, null);
+        }
+*/
+        DataBuffer db = raster.getDataBuffer();
+        if (!(db instanceof DataBufferInt)) {
+          throw new UnimplementedException("getRGBdefault isn't DataBufferInt");
+        }
+        int[] intData = ((DataBufferInt)db).getData();
+        // int k = d * data_width * data_height;
+        int kk = d * kdepth;
+        int m = 0;
+        int r, g, b, a;
+        for (int j=0; j<data_height; j++) {
+          int k = kk + j * kheight;
+          for (int i=0; i<data_width; i++) {
+            r = (color_values[0][k] < 0) ? color_values[0][k] + 256 :
+                                           color_values[0][k];
+            g = (color_values[1][k] < 0) ? color_values[1][k] + 256 :
+                                           color_values[1][k];
+            b = (color_values[2][k] < 0) ? color_values[2][k] + 256 :
+                                           color_values[2][k];
+            a = (color_values[3][k] < 0) ? color_values[3][k] + 256 :
+                                           color_values[3][k];
+            intData[m++] = ((a << 24) | (r << 16) | (g << 8) | b);
+            // k++;
+            k += kwidth;
+          }
+          for (int i=data_width; i<texture_width; i++) {
+            intData[m++] = 0;
+          }
+        }
+        for (int j=data_height; j<texture_height; j++) {
+          for (int i=0; i<texture_width; i++) {
+            intData[m++] = 0;
+          }
+        }
+      }
+      else { // (color_values.length == 3)
+        ColorModel colorModel = ColorModel.getRGBdefault();
+        WritableRaster raster =
+          colorModel.createCompatibleWritableRaster(texture_width, texture_height);
+        images[d] = new BufferedImage(colorModel, raster, false, null);
+/* WLH 23 Feb 2000
+        if (axis == 1) {
+          images[(data_depth-1) - d] =
+            new BufferedImage(colorModel, raster, false, null);
+        }
+        else {
+          images[d] = new BufferedImage(colorModel, raster, false, null);
+        }
+*/
+        DataBuffer db = raster.getDataBuffer();
+        if (!(db instanceof DataBufferInt)) {
+          throw new UnimplementedException("getRGBdefault isn't DataBufferInt");
+        }
+        int[] intData = ((DataBufferInt)db).getData();
+
+        // int k = d * data_width * data_height;
+        int kk = d * kdepth;
+        int m = 0;
+        int r, g, b, a;
+        for (int j=0; j<data_height; j++) {
+          int k = kk + j * kheight;
+          for (int i=0; i<data_width; i++) {
+            r = (color_values[0][k] < 0) ? color_values[0][k] + 256 :
+                                           color_values[0][k];
+            g = (color_values[1][k] < 0) ? color_values[1][k] + 256 :
+                                           color_values[1][k];
+            b = (color_values[2][k] < 0) ? color_values[2][k] + 256 :
+                                           color_values[2][k];
+            a = 255;
+            intData[m++] = ((a << 24) | (r << 16) | (g << 8) | b);
+            // k++;
+            k += kwidth;
+          }
+          for (int i=data_width; i<texture_width; i++) {
+            intData[m++] = 0;
+          }
+        }
+        for (int j=data_height; j<texture_height; j++) {
+          for (int i=0; i<texture_width; i++) {
+            intData[m++] = 0;
+          }
+        }
+      } // end if (color_values.length == 3)
+    } // end for (int d=0; d<data_depth; d++)
+    for (int d=data_depth; d<texture_depth; d++) {
+      ColorModel colorModel = ColorModel.getRGBdefault();
+      WritableRaster raster =
+        colorModel.createCompatibleWritableRaster(texture_width, texture_height);
+      images[d] = new BufferedImage(colorModel, raster, false, null);
+      DataBuffer db = raster.getDataBuffer();
+      if (!(db instanceof DataBufferInt)) {
+        throw new UnimplementedException("getRGBdefault isn't DataBufferInt");
+      }
+      int[] intData = ((DataBufferInt)db).getData();
+
+      for (int i=0; i<texture_width*texture_height; i++) {
+        intData[i] = 0;
+      }
+    }
+    return images;
+  }
+
+  public VisADQuadArray reverse(VisADQuadArray array) {
+    VisADQuadArray qarray = new VisADQuadArray();
+    qarray.coordinates = new float[array.coordinates.length];
+    qarray.texCoords = new float[array.texCoords.length];
+    qarray.colors = new byte[array.colors.length];
+    qarray.normals = new float[array.normals.length];
+
+    int count = array.vertexCount;
+    qarray.vertexCount = count;
+    int color_length = array.colors.length / count;
+    int tex_length = array.texCoords.length / count;
+    int i3 = 0;
+    int k3 = 3 * (count - 1);
+    int ic = 0;
+    int kc = color_length * (count - 1);
+    int it = 0;
+    int kt = tex_length * (count - 1);
+    for (int i=0; i<count; i++) {
+      qarray.coordinates[i3] = array.coordinates[k3];
+      qarray.coordinates[i3 + 1] = array.coordinates[k3 + 1];
+      qarray.coordinates[i3 + 2] = array.coordinates[k3 + 2];
+      qarray.texCoords[it] = array.texCoords[kt];
+      qarray.texCoords[it + 1] = array.texCoords[kt + 1];
+      if (tex_length == 3) qarray.texCoords[it + 2] = array.texCoords[kt + 2];
+      qarray.normals[i3] = array.normals[k3];
+      qarray.normals[i3 + 1] = array.normals[k3 + 1];
+      qarray.normals[i3 + 2] = array.normals[k3 + 2];
+      qarray.colors[ic] = array.colors[kc];
+      qarray.colors[ic + 1] = array.colors[kc + 1];
+      qarray.colors[ic + 2] = array.colors[kc + 2];
+      if (color_length == 4) qarray.colors[ic + 3] = array.colors[kc + 3];
+      i3 += 3;
+      k3 -= 3;
+      ic += color_length;
+      kc -= color_length;
+      it += tex_length;
+      kt -= tex_length;
+    }
+    return qarray;
+  }
+
+  private void buildLinearTexture(Object group, Set domain_set, Unit[] dataUnits, Unit[] domain_units,
+                                  float[] default_values, ShadowType shadow_api, int valueArrayLength,
+                                  int[] valueToScalar, float[] value_array, byte[][] color_values,
+                                  GraphicsModeControl mode, float[] constant_color, float constant_alpha) 
+          throws VisADException {
+    float[] coordinates = null;
+    float[] texCoords = null;
+    float[] normals = null;
+    byte[] colors = null;
+    int data_width = 0;
+    int data_height = 0;
+    int data_depth = 0;
+    int texture_width = 1;
+    int texture_height = 1;
+    int texture_depth = 1;
+                                                                                                                                
+// if (link != null) System.out.println("test isTextureMap " + (System.currentTimeMillis() - link.start_time));
+      Linear1DSet X = null;
+      Linear1DSet Y = null;
+      if (domain_set instanceof Linear2DSet) {
+        X = ((Linear2DSet) domain_set).getX();
+        Y = ((Linear2DSet) domain_set).getY();
+      }
+      else {
+        X = ((LinearNDSet) domain_set).getLinear1DComponent(0);
+        Y = ((LinearNDSet) domain_set).getLinear1DComponent(1);
+      }
+      float[][] limits = new float[2][2];
+      limits[0][0] = (float) X.getFirst();
+      limits[0][1] = (float) X.getLast();
+      limits[1][0] = (float) Y.getFirst();
+      limits[1][1] = (float) Y.getLast();
+                                                                                                                                
+      // get domain_set sizes
+      data_width  = X.getLength();
+      data_height = Y.getLength();
+      texture_width = shadow_api.textureWidth(data_width);
+      texture_height = shadow_api.textureHeight(data_height);
+                                                                                                                                
+      // WLH 27 Jan 2003
+      float half_width = 0.5f / ((float) (data_width - 1));
+      float half_height = 0.5f / ((float) (data_height - 1));
+      half_width  = (limits[0][1] - limits[0][0]) * half_width;
+      half_height = (limits[1][1] - limits[1][0]) * half_height;
+      limits[0][0] -= half_width;
+      limits[0][1] += half_width;
+      limits[1][0] -= half_height;
+      limits[1][1] += half_height;
+                                                                                                                                
+      // convert values to default units (used in display)
+      limits = Unit.convertTuple(limits, dataUnits, domain_units);
+      int[] tuple_index = new int[3];
+      if (DomainComponents.length != 2) {
+        throw new DisplayException("texture domain dimension != 2:" +
+                                   "ShadowFunctionOrSetType.doTransform");
+      }
+      for (int i=0; i<DomainComponents.length; i++) {
+        Enumeration maps = DomainComponents[i].getSelectedMapVector().elements();
+        while (maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) maps.nextElement();
+          DisplayRealType real = map.getDisplayScalar();
+          DisplayTupleType tuple = real.getTuple();
+          if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+            // scale values
+            limits[i] = map.scaleValues(limits[i]);
+            // get spatial index
+            tuple_index[i] = real.getTupleIndex();
+            break;
+          }
+        }
+/*
+        if (tuple == null ||
+            !tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+          throw new DisplayException("texture with bad tuple: " +
+                                     "ShadowFunctionOrSetType.doTransform");
+        }
+        if (maps.hasMoreElements()) {
+          throw new DisplayException("texture with multiple spatial: " +
+                                     "ShadowFunctionOrSetType.doTransform");
+        }
+*/
+      } // end for (int i=0; i<DomainComponents.length; i++)
+      // get spatial index not mapped from domain_set
+      tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+      DisplayRealType real = (DisplayRealType)
+        Display.DisplaySpatialCartesianTuple.getComponent(tuple_index[2]);
+      int value2_index = display.getDisplayScalarIndex(real);
+      float value2 = default_values[value2_index];
+      // float value2 = 0.0f;  WLH 30 Aug 99
+      for (int i=0; i<valueArrayLength; i++) {
+        if (inherited_values[i] > 0 &&
+            real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+          value2 = value_array[i];
+          break;
+        }
+      }
+      coordinates = new float[12];
+      // corner 0
+      coordinates[tuple_index[0]] = limits[0][0];
+      coordinates[tuple_index[1]] = limits[1][0];
+      coordinates[tuple_index[2]] = value2;
+      // corner 1
+      coordinates[3 + tuple_index[0]] = limits[0][1];
+      coordinates[3 + tuple_index[1]] = limits[1][0];
+      coordinates[3 + tuple_index[2]] = value2;
+      // corner 2
+      coordinates[6 + tuple_index[0]] = limits[0][1];
+      coordinates[6 + tuple_index[1]] = limits[1][1];
+      coordinates[6 + tuple_index[2]] = value2;
+      // corner 3
+      coordinates[9 + tuple_index[0]] = limits[0][0];
+      coordinates[9 + tuple_index[1]] = limits[1][1];
+      coordinates[9 + tuple_index[2]] = value2;
+                                                                                                                                
+      // move image back in Java3D 2-D mode
+      shadow_api.adjustZ(coordinates);
+                                                                                                                                
+      texCoords = new float[8];
+      float ratiow = ((float) data_width) / ((float) texture_width);
+      float ratioh = ((float) data_height) / ((float) texture_height);
+      shadow_api.setTexCoords(texCoords, ratiow, ratioh);
+                                                                                                                                
+      normals = new float[12];
+      float n0 = ((coordinates[3+2]-coordinates[0+2]) *
+                  (coordinates[6+1]-coordinates[0+1])) -
+                 ((coordinates[3+1]-coordinates[0+1]) *
+                  (coordinates[6+2]-coordinates[0+2]));
+      float n1 = ((coordinates[3+0]-coordinates[0+0]) *
+                  (coordinates[6+2]-coordinates[0+2])) -
+                 ((coordinates[3+2]-coordinates[0+2]) *
+                  (coordinates[6+0]-coordinates[0+0]));
+      float n2 = ((coordinates[3+1]-coordinates[0+1]) *
+                  (coordinates[6+0]-coordinates[0+0])) -
+                 ((coordinates[3+0]-coordinates[0+0]) *
+                  (coordinates[6+1]-coordinates[0+1]));
+                                                                                                                                
+      float nlen = (float) Math.sqrt(n0 *  n0 + n1 * n1 + n2 * n2);
+      n0 = n0 / nlen;
+      n1 = n1 / nlen;
+      n2 = n2 / nlen;
+      // corner 0
+      normals[0] = n0;
+      normals[1] = n1;
+      normals[2] = n2;
+      // corner 1
+      normals[3] = n0;
+      normals[4] = n1;
+      normals[5] = n2;
+      // corner 2
+      normals[6] = n0;
+      normals[7] = n1;
+      normals[8] = n2;
+      // corner 3
+      normals[9] = n0;
+      normals[10] = n1;
+      normals[11] = n2;
+                                                                                                                                
+      colors = new byte[12];
+      for (int i=0; i<12; i++) colors[i] = (byte) 127;
+
+      VisADQuadArray qarray = new VisADQuadArray();
+      qarray.vertexCount = 4;
+      qarray.coordinates = coordinates;
+      qarray.texCoords = texCoords;
+      qarray.colors = colors;
+      qarray.normals = normals;
+                                                                                                                                    
+      BufferedImage image =
+         createImage(data_width, data_height, texture_width,
+                     texture_height, color_values);
+      shadow_api.textureToGroup(group, qarray, image, mode,
+                                constant_alpha, constant_color,
+                                texture_width, texture_height);
+
+  }
+
+  private void buildCurvedTexture(Object group, Set domain_set, Unit[] dataUnits, Unit[] domain_units,
+                                  float[] default_values, ShadowType shadow_api, int valueArrayLength,
+                                  int[] valueToScalar, float[] value_array, byte[][] color_values,
+                                  GraphicsModeControl mode, float[] constant_color, float constant_alpha,
+                                  int curved_size, float[][] spatial_values, boolean spatial_all_select,
+                                  DataRenderer renderer, boolean[][] range_select, boolean domainOnlySpatial, 
+                                  int[] start, int lenX, int lenY, int bigX, int bigY)
+          throws VisADException 
+  {
+//System.out.println("ShadowFunctionOrSetType, start Curved texture:  " + System.currentTimeMillis());
+      float[] coordinates = null;
+      float[] texCoords = null;
+      float[] normals = null;
+      byte[] colors = null;
+      int data_width = 0;
+      int data_height = 0;
+      int texture_width = 1;
+      int texture_height = 1;
+                                                                                                                                  
+      int[] lengths = null;
+                                                                                                                                  
+      // get domain_set sizes
+      if (domain_set != null) {
+        lengths = ((GriddedSet) domain_set).getLengths();
+      }
+      else {
+        lengths = new int[] {lenX, lenY};
+      }
+                                                                                                                                  
+      data_width  = lengths[0];
+      data_height = lengths[1];
+      // texture sizes must be powers of two
+      texture_width  = shadow_api.textureWidth(data_width);
+      texture_height = shadow_api.textureHeight(data_height);
+                                                                                                                                  
+      // compute size of triangle array to mapped texture onto
+      int size = (data_width + data_height) / 2;
+      curved_size = Math.max(2, Math.min(curved_size, size / 32));
+                                                                                                                                  
+      int nwidth = 2 + (data_width - 1) / curved_size;
+      int nheight = 2 + (data_height - 1) / curved_size;
+
+      // WLH 14 Aug 2001
+      if (range_select[0] != null && !domainOnlySpatial) {
+      // System.out.println("force curved_size = 1");
+        curved_size = 1;
+        nwidth = data_width;
+        nheight = data_height;
+      }
+
+// System.out.println("curved_size = " + curved_size + " nwidth = " + nwidth +
+//                    " nheight = " + nheight);
+                                                                                                                                               
+      int nn = nwidth * nheight;
+      coordinates = new float[3 * nn];
+      int k = 0;
+      int[] is = new int[nwidth];
+      int[] js = new int[nheight];
+      for (int i=0; i<nwidth; i++) {
+         is[i] = Math.min(i * curved_size, data_width - 1);
+      }
+      for (int j=0; j<nheight; j++) {
+         js[j] = Math.min(j * curved_size, data_height - 1);
+      }
+      //if (domain_set != null) {
+      if (start == null) {
+        for (int j=0; j<nheight; j++) {
+          for (int i=0; i<nwidth; i++) {
+              int ij = is[i] + data_width * js[j];
+              coordinates[k++] = spatial_values[0][ij];
+              coordinates[k++] = spatial_values[1][ij];
+              coordinates[k++] = spatial_values[2][ij];
+/*
+double size = Math.sqrt(spatial_values[0][ij] * spatial_values[0][ij] +
+                        spatial_values[1][ij] * spatial_values[1][ij] +
+                        spatial_values[2][ij] * spatial_values[2][ij]);
+if (size < 0.2) {
+  System.out.println("spatial_values " + is[i] + " " + js[j] + " " +
+  spatial_values[0][ij] + " " + spatial_values[1][ij] + " " + spatial_values[2][ij]);
+}
+*/
+          }
+        }
+      }
+      else {
+        for (int j=0; j<nheight; j++) {
+          for (int i=0; i<nwidth; i++) {
+             int ij = is[i] + data_width * js[j];
+             int x = ij % lenX;
+             int y = ij / lenX;
+             ij    = (start[0] + x) + (start[1] + y)*bigX;
+             coordinates[k++] = spatial_values[0][ij];
+             coordinates[k++] = spatial_values[1][ij];
+             coordinates[k++] = spatial_values[2][ij];
+          }
+        }
+      }
+
+      normals = Gridded3DSet.makeNormals(coordinates, nwidth, nheight);
+      colors = new byte[3 * nn];
+      for (int i=0; i<3*nn; i++) colors[i] = (byte) 127;
+                                                                                                                                             
+      float ratiow = ((float) data_width) / ((float) texture_width);
+      float ratioh = ((float) data_height) / ((float) texture_height);
+                                                                                                                                             
+      // WLH 27 Jan 2003
+      float half_width = 0.5f / ((float) texture_width);
+      float half_height = 0.5f / ((float) texture_height);
+      float width = 1.0f / ((float) texture_width);
+      float height = 1.0f / ((float) texture_height);
+                                                                                                                                             
+      int mt = 0;
+      texCoords = new float[2 * nn];
+// System.out.println("nwidth = " + nwidth + " nheight = " + nheight +
+//                    " ratiow = " + ratiow + " ratioh = " + ratioh);
+      for (int j=0; j<nheight; j++) {
+         for (int i=0; i<nwidth; i++) {
+/* WLH 27 Jan 2003
+                texCoords[mt++] = ratiow * is[i] / (data_width - 1.0f);
+                texCoords[mt++] = 1.0f - ratioh * js[j] / (data_height - 1.0f);
+*/
+            // WLH 27 Jan 2003
+            float isfactor = is[i] / (data_width - 1.0f);
+            float jsfactor = js[j] / (data_height - 1.0f);
+            texCoords[mt++] = (ratiow - width) * isfactor + half_width;
+            texCoords[mt++] = 1.0f - (ratioh - height) * jsfactor - half_height;
+// System.out.println("texCoords = " + texCoords[mt-2] + " " + texCoords[mt-1] +
+//                    " isfactor = " + isfactor + " jsfactor = " + jsfactor);
+         }
+      }
+
+      VisADTriangleStripArray tarray = new VisADTriangleStripArray();
+      tarray.stripVertexCounts = new int[nheight - 1];
+      for (int i=0; i<nheight - 1; i++) {
+         tarray.stripVertexCounts[i] = 2 * nwidth;
+      }
+      int len = (nheight - 1) * (2 * nwidth);
+      tarray.vertexCount = len;
+      tarray.normals = new float[3 * len];
+      tarray.coordinates = new float[3 * len];
+      tarray.colors = new byte[3 * len];
+      tarray.texCoords = new float[2 * len];
+
+      // shuffle normals into tarray.normals, etc
+      k = 0;
+      int kt = 0;
+      int nwidth3 = 3 * nwidth;
+      int nwidth2 = 2 * nwidth;
+      for (int i=0; i<nheight-1; i++) {
+         int m = i * nwidth3;
+         mt = i * nwidth2;
+         for (int j=0; j<nwidth; j++) {
+            tarray.coordinates[k] = coordinates[m];
+            tarray.coordinates[k+1] = coordinates[m+1];
+            tarray.coordinates[k+2] = coordinates[m+2];
+            tarray.coordinates[k+3] = coordinates[m+nwidth3];
+            tarray.coordinates[k+4] = coordinates[m+nwidth3+1];
+            tarray.coordinates[k+5] = coordinates[m+nwidth3+2];
+
+            tarray.normals[k] = normals[m];
+            tarray.normals[k+1] = normals[m+1];
+            tarray.normals[k+2] = normals[m+2];
+            tarray.normals[k+3] = normals[m+nwidth3];
+            tarray.normals[k+4] = normals[m+nwidth3+1];
+            tarray.normals[k+5] = normals[m+nwidth3+2];
+
+            tarray.colors[k] = colors[m];
+            tarray.colors[k+1] = colors[m+1];
+            tarray.colors[k+2] = colors[m+2];
+            tarray.colors[k+3] = colors[m+nwidth3];
+            tarray.colors[k+4] = colors[m+nwidth3+1];
+            tarray.colors[k+5] = colors[m+nwidth3+2];
+
+            tarray.texCoords[kt] = texCoords[mt];
+            tarray.texCoords[kt+1] = texCoords[mt+1];
+            tarray.texCoords[kt+2] = texCoords[mt+nwidth2];
+            tarray.texCoords[kt+3] = texCoords[mt+nwidth2+1];
+
+            k += 6;
+            m += 3;
+            kt += 4;
+            mt += 2;
+         }
+      }
+
+      if (!spatial_all_select) {
+        tarray = (VisADTriangleStripArray) tarray.removeMissing();
+      }
+
+      if (getAdjustProjectionSeam()) {
+        tarray = (VisADTriangleStripArray) tarray.adjustLongitude(renderer);
+        tarray = (VisADTriangleStripArray) tarray.adjustSeam(renderer);
+      }
+
+      BufferedImage image =
+         createImage(data_width, data_height, texture_width,
+                     texture_height, color_values);
+
+      shadow_api.textureToGroup(group, tarray, image, mode,
+                                constant_alpha, constant_color,
+                                texture_width, texture_height);
+  }
+
+}
+
diff --git a/visad/ShadowFunctionType.java b/visad/ShadowFunctionType.java
new file mode 100644
index 0000000..5673766
--- /dev/null
+++ b/visad/ShadowFunctionType.java
@@ -0,0 +1,44 @@
+//
+// ShadowFunctionType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   The ShadowFunctionType class shadows the FunctionType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowFunctionType extends ShadowFunctionOrSetType {
+
+  public ShadowFunctionType(MathType t, DataDisplayLink link, ShadowType parent,
+                            ShadowRealTupleType domain, ShadowType range)
+         throws VisADException, RemoteException {
+    super(t, link, parent, domain, range);
+  }
+
+}
+
diff --git a/visad/ShadowRealTupleType.java b/visad/ShadowRealTupleType.java
new file mode 100644
index 0000000..2822299
--- /dev/null
+++ b/visad/ShadowRealTupleType.java
@@ -0,0 +1,192 @@
+//
+// ShadowRealTupleType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   The ShadowRealTupleType class shadows the RealTupleType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowRealTupleType extends ShadowTupleType {
+
+  /** Shadow of Type.getCoordinateSystem().getReference() */
+  private ShadowRealTupleType Reference;
+
+  /** If allSpatial, need complex logic with permutation;
+      need to indicate subspace of DisplaySpatialCartesianTuple
+      or of another spatial DisplayTupleType, and permutation
+      of subspace components;
+      true if all tupleComponents map to DisplaySpatialTuple
+      without overlap, or if allSpatialReference is true */
+  private boolean allSpatial;
+
+  /** true if using Reference.allSpatial */
+  private boolean allSpatialReference;
+
+  /** unique spatial DisplayTupleType whose components
+      are mapped from components of this;
+      uniqueness enforced by BadMappingException-s;
+      implicit anySpatial = (DisplaySpatialTuple != null) */
+  private DisplayTupleType DisplaySpatialTuple;
+
+  /** true if DisplaySpatialTuple is from Reference */
+  private boolean spatialReference;
+
+  /** mapping from components of DisplaySpatialTuple to
+      components of this, or -1;
+      use default values for components of DisplaySpatial
+      whose permutation == -1 from ConstantMap-s or from
+      DisplayRealType.DefaultValue */
+  private int[] permutation;
+
+  public ShadowRealTupleType(MathType t, DataDisplayLink link, ShadowType parent,
+                             ShadowType[] tcs, ShadowType adapter)
+         throws VisADException, RemoteException {
+    super(t, link, parent, tcs);
+
+    // check if tupleComponents are mapped to components of
+    // Display.DisplaySpatialCartesianTuple, or to components
+    // of a DisplayTupleType whose Reference is
+    // Display.DisplaySpatialCartesianTuple
+    allSpatial = true;
+    allSpatialReference = false;
+    DisplaySpatialTuple = null;
+    spatialReference = false;
+
+    // note DisplayIndices already computed by super constructor,
+    // except for contribution by Reference
+    for (int j=0; j<tupleComponents.length; j++) {
+      ShadowRealType real = (ShadowRealType) tupleComponents[j];
+      MappedDisplayScalar |= real.getMappedDisplayScalar();
+
+      DisplayTupleType tuple = real.getDisplaySpatialTuple();
+      int[] index = real.getDisplaySpatialTupleIndex();
+      if (tuple != null) {
+        if (DisplaySpatialTuple != null) {
+          if (tuple.equals(DisplaySpatialTuple)) {
+            for (int k=0; k<3 && index[k]>=0; k++) {
+              if (permutation[index[k]] >= 0) {
+                allSpatial = false;
+              }
+              else {
+                permutation[index[k]] = j;
+              }
+            }
+          }
+          else {
+            throw new BadMappingException("mapped to multiple" +
+                                          " spatial DisplayTupleType-s: " +
+                                          "ShadowRealTupleType");
+          }
+        }
+        else { // DisplaySpatialTuple == null
+          DisplaySpatialTuple = tuple;
+          permutation = new int[tuple.getDimension()];
+          for (int i=0; i<tuple.getDimension(); i++) permutation[i] = -1;
+          for (int k=0; k<3 && index[k]>=0; k++) {
+            permutation[index[k]] = j;
+          }
+        }
+      }
+      else { // tuple == null
+        allSpatial = false;
+      }
+    } // end for (int j=0; j<tupleComponents.length; j++) {
+
+    if (((RealTupleType) Type).getCoordinateSystem() != null) {
+      RealTupleType ref =
+        ((RealTupleType) Type).getCoordinateSystem().getReference();
+      Reference = (ShadowRealTupleType)
+        ref.buildShadowType(Link, adapter).getAdaptedShadowType();
+      DisplayTupleType tuple = Reference.getDisplaySpatialTuple();
+      // note mappings of CoordinateSystem.Reference count
+      DisplayIndices = addIndices(DisplayIndices, Reference.getDisplayIndices());
+      ValueIndices = addIndices(ValueIndices, Reference.getValueIndices());
+      if (tuple != null) {
+        if (DisplaySpatialTuple != null) {
+          if (!DisplaySpatialTuple.equals(tuple)) {
+            throw new BadMappingException("mapped to multiple" +
+                                          " spatial DisplayTupleType-s " +
+                                          "(through CoordinateSystem.Reference): " +
+                                          "ShadowRealTupleType");
+          }
+          else {
+            allSpatial = false;
+          }
+        }
+        else {
+          DisplaySpatialTuple = tuple;
+          spatialReference = true;
+          allSpatial = Reference.getAllSpatial();
+          allSpatialReference = allSpatial;
+          permutation = Reference.getPermutation();
+        }
+      }
+/* WLH 23 June 99
+      MultipleDisplayScalar |=
+        (MappedDisplayScalar && Reference.getMappedDisplayScalar());
+*/
+      MultipleDisplayScalar |=
+        (MappedDisplayScalar && Reference.getMappedDisplayScalar()) ||
+        Reference.getMultipleDisplayScalar();
+
+      MappedDisplayScalar |= Reference.getMappedDisplayScalar();
+    }
+    else { // ((RealTupleType) Type).DefaultCoordinateSystem == null
+      Reference = null;
+    }
+  }
+
+  public int[] getPermutation() {
+    int[] ii = new int[permutation.length];
+    for (int i=0; i<permutation.length; i++) ii[i] = permutation[i];
+    return ii;
+  }
+
+  public boolean getAllSpatial() {
+    return allSpatial;
+  }
+
+  public DisplayTupleType getDisplaySpatialTuple() {
+    return DisplaySpatialTuple;
+  }
+
+  public boolean getMappedDisplayScalar() {
+    return MappedDisplayScalar;
+  }
+
+  public boolean getSpatialReference() {
+    return spatialReference;
+  }
+
+  public ShadowRealTupleType getReference() {
+    return Reference;
+  }
+
+}
+
diff --git a/visad/ShadowRealType.java b/visad/ShadowRealType.java
new file mode 100644
index 0000000..22c3e59
--- /dev/null
+++ b/visad/ShadowRealType.java
@@ -0,0 +1,147 @@
+//
+// ShadowRealType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.text.*;
+import java.rmi.*;
+
+/**
+   The ShadowRealType class shadows the RealType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowRealType extends ShadowScalarType {
+
+  public ShadowRealType(MathType type, DataDisplayLink link, ShadowType parent)
+      throws VisADException, RemoteException {
+    super(type, link, parent);
+  }
+
+  /** transform data into a (Java3D or Java2D) scene graph;
+      add generated scene graph components as children of group;
+      group is Group (Java3D) or VisADGroup (Java2D);
+      value_array are inherited valueArray values;
+      default_values are defaults for each display.DisplayRealTypeVector;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer,
+                             ShadowType shadow_api)
+         throws VisADException, RemoteException {
+
+    if (data.isMissing()) return false;
+    if (LevelOfDifficulty == NOTHING_MAPPED) return false;
+
+    if (!(data instanceof Real)) {
+      throw new DisplayException("data must be Real: " +
+                                 "ShadowRealType.doTransform");
+    }
+
+    // get some precomputed values useful for transform
+    // length of ValueArray
+    int valueArrayLength = display.getValueArrayLength();
+    // mapping from ValueArray to DisplayScalar
+    int[] valueToScalar = display.getValueToScalar();
+    // mapping from ValueArray to MapVector
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    // array to hold values for various mappings
+    float[][] display_values = new float[valueArrayLength][];
+
+    // get values inherited from parent;
+    // assume these do not include SelectRange, SelectValue
+    // or Animation values - see temporary hack in
+    // DataRenderer.isTransformControl
+    int[] inherited_values = getInheritedValues();
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0) {
+        display_values[i] = new float[1];
+        display_values[i][0] = value_array[i];
+      }
+    }
+
+    Real real = (Real) data;
+
+    RealType rtype = (RealType) getType();
+    RealType[] realComponents = {rtype};
+    double[][] value = new double[1][1];
+    value[0][0] = real.getValue(rtype.getDefaultUnit());
+    ShadowRealType[] RealComponents = {this};
+    mapValues(display_values, value, RealComponents);
+
+    // get any text String and TextControl inherited from parent
+    String text_value = getParentText();
+    TextControl text_control = getParentTextControl();
+
+    if (getAnyText() && text_value == null) {
+      // get any text String and TextControl from this
+      Enumeration maps = getSelectedMapVector().elements();
+      while(maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        if (dreal.equals(Display.Text)) {
+          text_control = (TextControl) map.getControl();
+          NumberFormat format = text_control.getNumberFormat();
+          if (value[0][0] != value[0][0]) {
+            text_value = "";
+          }
+          else if (format == null) {
+            text_value = PlotText.shortString(value[0][0]);
+          }
+          else {
+            text_value = format.format(value[0][0]);
+          }
+          break;
+        }
+      }
+    }
+
+    boolean[][] range_select =
+      shadow_api.assembleSelect(display_values, 1, valueArrayLength,
+                     valueToScalar, display, shadow_api);
+
+    if (range_select[0] != null && !range_select[0][0]) {
+      // data not selected
+      return false;
+    }
+
+    // add values to value_array according to SelectedMapVector
+    if (getIsTerminal()) {
+      // cannot be any Reference when RealType is terminal
+      return terminalTupleOrScalar(group, display_values, text_value,
+                                   text_control, valueArrayLength,
+                                   valueToScalar, default_values,
+                                   inherited_values, renderer, shadow_api);
+    }
+    else {
+      // nothing to render at a non-terminal RealType
+    }
+    return false;
+  }
+
+}
+
diff --git a/visad/ShadowScalarType.java b/visad/ShadowScalarType.java
new file mode 100644
index 0000000..b4c761a
--- /dev/null
+++ b/visad/ShadowScalarType.java
@@ -0,0 +1,234 @@
+//
+// ShadowScalarType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowScalarType class shadows the ScalarType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowScalarType extends ShadowType {
+
+  // index into Display.RealTypeVector, or -1
+  int Index;
+
+  // Vector of ScalarMap-s applying to this ScalarType
+  private Vector SelectedMapVector;
+
+  // set DisplaySpatialTuple if this is mapped to a
+  // component of Display.DisplaySpatialCartesianTuple
+  // or to a component of a DisplayTupleType whose
+  // Reference is Display.DisplaySpatialCartesianTuple
+  // implicit anySpatial = (DisplaySpatialTuple != null)
+  //
+  // if this is mapped to multiple components of DisplaySpatialTuple
+  // then DisplaySpatialTuple != null && DisplaySpatialTupleIndexIndex > 1
+  //
+  // if this is mapped to components of different spatial DisplayTupleType-s
+  // then throw BadMappingException if mapped to components of different
+  private DisplayTupleType DisplaySpatialTuple;
+  // index in DisplaySpatialTuple
+  private int[] DisplaySpatialTupleIndex;
+  private int DisplaySpatialTupleIndexIndex;
+
+  /** value_indices from parent */
+  private int[] inherited_values;
+
+  public ShadowScalarType(MathType type, DataDisplayLink link, ShadowType parent)
+      throws VisADException, RemoteException {
+    super(type, link, parent);
+
+    DisplaySpatialTuple = null;
+    DisplaySpatialTupleIndex = new int[3];
+    for (int k=0; k<3; k++) DisplaySpatialTupleIndex[k] = -1;
+    DisplaySpatialTupleIndexIndex = 0;
+
+    int spatial_count = 0;
+    Index = -1;
+    SelectedMapVector = new Vector();
+    Enumeration maps = display.getMapVector().elements();
+    // determine which ScalarMap-s apply to this ShadowScalarType
+    while(maps.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) maps.nextElement();
+/*
+System.out.println("map: " + map.getScalar().getName() + " -> " +
+                   map.getDisplayScalar().getName());
+*/
+      if (map.getScalar().equals(Type)) {
+/*
+System.out.println("Type = " + ((ScalarType) Type).getName() +
+                   " DisplayIndex = " + map.getDisplayScalarIndex());
+*/
+        Index = map.getScalarIndex();
+        SelectedMapVector.addElement(map);
+        Link.addSelectedMapVector(map);
+        DisplayIndices[map.getDisplayScalarIndex()]++;
+        ValueIndices[map.getValueIndex()]++;
+        // could test here for any DisplayIndices[.] > 1
+        // i.e., multiple maps between same ScalarType and DisplayRealType
+        // actually tested in DisplayImpl.addMap
+        //
+        // compute DisplaySpatialTuple and DisplaySpatialTupleIndex
+        DisplayTupleType tuple =
+          (DisplayTupleType) map.getDisplayScalar().getTuple();
+        if (tuple != null &&
+            !tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+          CoordinateSystem coord_sys = tuple.getCoordinateSystem();
+          if (coord_sys == null ||
+              !coord_sys.getReference().equals(
+               Display.DisplaySpatialCartesianTuple)) {
+            tuple = null; // tuple not spatial, no worries
+          }
+        }
+        if (tuple != null) {
+          spatial_count++;
+          if (DisplaySpatialTuple != null) {
+            if (!tuple.equals(DisplaySpatialTuple)) {
+              // this mapped to multiple spatial DisplayTupleType-s
+              ScalarType real = (ScalarType) Type;
+              throw new BadMappingException(real.getName() +
+                         " mapped to multiple spatial DisplayTupleType-s: " +
+                                            "ShadowScalarType");
+            }
+          }
+          else { // DisplaySpatialTuple == null
+            DisplaySpatialTuple = tuple;
+          }
+          DisplaySpatialTupleIndex[DisplaySpatialTupleIndexIndex] =
+            map.getDisplayScalar().getTupleIndex();
+          DisplaySpatialTupleIndexIndex++;
+        } // end if (tuple != null)
+      } // end if (map.Scalar.equals(Type)) {
+    } // end while(maps.hasMoreElements()) {
+    MultipleSpatialDisplayScalar = (spatial_count > 1);
+    MultipleDisplayScalar = (SelectedMapVector.size() > 1);
+    MappedDisplayScalar = (SelectedMapVector.size() > 0);
+  }
+
+  /** increment indices for 'shadowed' ScalarType */
+  void incrementIndices(int[] indices) {
+    // this test allows multiple occurences of a Scalar
+    // as long as it is not mapped
+    if (MappedDisplayScalar && Index < indices.length) indices[Index]++;
+  }
+
+  /** increment indices for ShadowScalarType
+      and then test as possible terminal node */
+  public int checkIndices(int[] indices, int[] display_indices,
+             int[] value_indices, boolean[] isTransform, int levelOfDifficulty)
+      throws VisADException, RemoteException {
+    // add indices & display_indices from ScalarType
+    int[] local_indices = copyIndices(indices);
+    incrementIndices(local_indices);
+    int[] local_display_indices = addIndices(display_indices, DisplayIndices);
+    int[] local_value_indices = addIndices(value_indices, ValueIndices);
+
+    markTransform(isTransform);
+
+    // get value_indices arrays used by doTransform
+    inherited_values = copyIndices(value_indices);
+
+    // check for any mapped
+    if (levelOfDifficulty == NOTHING_MAPPED) {
+      if (checkAny(DisplayIndices)) {
+        levelOfDifficulty = NESTED;
+      }
+    }
+
+    // test legality of Animation and SelectValue
+    if (checkAnimationOrValue(DisplayIndices) > 0) {
+      throw new BadMappingException("Animation and SelectValue may not " +
+                                    "occur in range: " +
+                                    "ShadowScalarType.checkIndices");
+    }
+
+    anyContour = checkContour(local_display_indices);
+    anyFlow = checkFlow(local_display_indices);
+    anyShape = checkShape(local_display_indices);
+    anyText = checkText(local_display_indices);
+    adjustProjectionSeam = getAdjustProjectionSeam();
+
+    LevelOfDifficulty =
+      testIndices(local_indices, local_display_indices, levelOfDifficulty);
+    if (LevelOfDifficulty == NESTED) {
+      if (checkR2D2(DisplayIndices)) {
+        LevelOfDifficulty = SIMPLE_TUPLE;
+      }
+      else {
+        LevelOfDifficulty = LEGAL;
+      }
+    }
+    return LevelOfDifficulty;
+  }
+
+  public int[] getInheritedValues() {
+    return inherited_values;
+  }
+
+  public boolean getMappedDisplayScalar() {
+    return MappedDisplayScalar;
+  }
+
+  public DisplayTupleType getDisplaySpatialTuple() {
+    return DisplaySpatialTuple;
+  }
+
+  public int[] getDisplaySpatialTupleIndex() {
+    return DisplaySpatialTupleIndex;
+  }
+
+  public int getIndex() {
+    return Index;
+  }
+
+  public Vector getSelectedMapVector() {
+    return (Vector) SelectedMapVector.clone();
+  }
+
+  /** mark Control-s as needing re-Transform */
+  void markTransform(boolean[] isTransform) {
+    synchronized (SelectedMapVector) {
+      Enumeration maps = SelectedMapVector.elements();
+      while(maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType real = map.getDisplayScalar();
+        if (real.equals(Display.Animation) ||
+            real.equals(Display.SelectValue) ||
+            real.equals(Display.SelectRange)) {
+          Control control = map.getControl();
+          if (control != null) {
+            isTransform[control.getIndex()] = true;
+          }
+        }
+      }
+    }
+  }
+
+}
+
diff --git a/visad/ShadowSetType.java b/visad/ShadowSetType.java
new file mode 100644
index 0000000..5bcf9a3
--- /dev/null
+++ b/visad/ShadowSetType.java
@@ -0,0 +1,44 @@
+//
+// ShadowSetType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   The ShadowSetType class shadows the SetType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowSetType extends ShadowFunctionOrSetType {
+
+  public ShadowSetType(MathType t, DataDisplayLink link, ShadowType parent,
+                       ShadowRealTupleType domain)
+         throws VisADException, RemoteException {
+    super(t, link, parent, domain, null);
+  }
+
+}
+
diff --git a/visad/ShadowTextType.java b/visad/ShadowTextType.java
new file mode 100644
index 0000000..2a79051
--- /dev/null
+++ b/visad/ShadowTextType.java
@@ -0,0 +1,129 @@
+//
+// ShadowTextType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowTextType class shadows the TextType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowTextType extends ShadowScalarType {
+
+  public ShadowTextType(MathType t, DataDisplayLink link, ShadowType parent)
+                 throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  /** transform data into a (Java3D or Java2D) scene graph;
+      add generated scene graph components as children of group;
+      group is Group (Java3D) or VisADGroup (Java2D);
+      value_array are inherited valueArray values;
+      default_values are defaults for each display.DisplayRealTypeVector;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer,
+                             ShadowType shadow_api)
+         throws VisADException, RemoteException {
+
+    if (data.isMissing()) return false;
+    if (LevelOfDifficulty == NOTHING_MAPPED) return false;
+
+    if (!(data instanceof Text)) {
+      throw new DisplayException("data must be Text: " +
+                                 "ShadowTextType.doTransform");
+    }
+
+    // get some precomputed values useful for transform
+    // length of ValueArray
+    int valueArrayLength = display.getValueArrayLength();
+    // mapping from ValueArray to DisplayScalar
+    int[] valueToScalar = display.getValueToScalar();
+    // mapping from ValueArray to MapVector
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    // array to hold values for various mappings
+    float[][] display_values = new float[valueArrayLength][];
+
+// ????
+    // get values inherited from parent;
+    // assume these do not include SelectRange, SelectValue
+    // or Animation values - see temporary hack in
+    // DataRenderer.isTransformControl
+    int[] inherited_values = getInheritedValues();
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0) {
+        display_values[i] = new float[1];
+        display_values[i][0] = value_array[i];
+      }
+    }
+
+    boolean[][] range_select =
+      shadow_api.assembleSelect(display_values, 1, valueArrayLength,
+                     valueToScalar, display, shadow_api);
+
+    if (range_select[0] != null && !range_select[0][0]) {
+      // data not selected
+      return false;
+    }
+
+    // get any text String and TextControl inherited from parent
+    String text_value = shadow_api.getParentText();
+    TextControl text_control = shadow_api.getParentTextControl();
+    boolean anyText = getAnyText();
+    if (anyText && text_value == null) {
+      // get any text String and TextControl from this
+      Vector maps = getSelectedMapVector();
+      if (!maps.isEmpty()) {
+        text_value = ((Text) data).getValue();
+        ScalarMap map = (ScalarMap) maps.firstElement();
+        text_control = (TextControl) map.getControl();
+      }
+    }
+
+//
+// never renders text ????
+//
+
+    // add values to value_array according to SelectedMapVector
+    if (getIsTerminal()) {
+      // ????
+      return terminalTupleOrScalar(group, display_values, text_value,
+                                   text_control, valueArrayLength,
+                                   valueToScalar, default_values,
+                                   inherited_values, renderer, shadow_api);
+    }
+    else {
+      // nothing to render at a non-terminal TextType
+    }
+    return false;
+  }
+
+}
+
diff --git a/visad/ShadowTupleType.java b/visad/ShadowTupleType.java
new file mode 100644
index 0000000..35ef6e7
--- /dev/null
+++ b/visad/ShadowTupleType.java
@@ -0,0 +1,511 @@
+//
+// ShadowTupleType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.text.*;
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowTupleType class shadows the TupleType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowTupleType extends ShadowType {
+
+  ShadowType[] tupleComponents;
+
+  private ShadowRealType[] RealComponents;
+
+  /** value_indices from parent */
+  private int[] inherited_values;
+
+  /** true if no component with mapped Scalar components is a
+      ShadowSetType, ShadowFunctionType or ShadowTupleType;
+      not the same as TupleType.Flat */
+  private boolean Flat;
+
+  public ShadowTupleType(MathType t, DataDisplayLink link, ShadowType parent,
+                         ShadowType[] tcs) throws VisADException, RemoteException {
+    super(t, link, parent);
+    tupleComponents = tcs;
+    int n = tupleComponents.length;
+    Flat = true;
+    ShadowType[] components = new ShadowType[n];
+    MultipleSpatialDisplayScalar = false;
+    MultipleDisplayScalar = false;
+    // compute Flat, DisplayIndices and ValueIndices
+    for (int i=0; i<n; i++) {
+      ShadowType shadow = tupleComponents[i];
+      MultipleSpatialDisplayScalar |=
+        tupleComponents[i].getMultipleSpatialDisplayScalar();
+      MultipleDisplayScalar |= tupleComponents[i].getMultipleDisplayScalar();
+      boolean mappedComponent = tupleComponents[i].getMappedDisplayScalar();
+      MappedDisplayScalar |= mappedComponent;
+      if (shadow instanceof ShadowFunctionType ||
+          shadow instanceof ShadowSetType ||
+          (shadow instanceof ShadowTupleType &&
+           !(shadow instanceof ShadowRealTupleType)) ) {
+        if (mappedComponent) Flat = false;
+      }
+      else if (shadow instanceof ShadowScalarType ||
+               shadow instanceof ShadowRealTupleType) {
+        // treat ShadowRealTupleType component as
+        // a set of ShadowRealType components
+        DisplayIndices = addIndices(DisplayIndices, shadow.getDisplayIndices());
+        ValueIndices = addIndices(ValueIndices, shadow.getValueIndices());
+      }
+    }
+    RealComponents = getComponents(this, true);
+  }
+
+  public ShadowRealType[] getRealComponents() {
+    return RealComponents;
+  }
+
+  // copy and increment indices for each ShadowScalarType component and
+  // each ShadowRealType component of a ShadowRealTupleType component
+  int[] sumIndices(int[] indices) {
+    int[] local_indices = copyIndices(indices);
+    int n = tupleComponents.length;
+    for (int j=0; j<n; j++) {
+      ShadowType shadow = (ShadowType) tupleComponents[j];
+      if (shadow instanceof ShadowScalarType) {
+        ((ShadowScalarType) shadow).incrementIndices(local_indices);
+      }
+      else if (shadow instanceof ShadowRealTupleType) {
+        // treat ShadowRealTupleType component as
+        // a set of ShadowRealType components
+        local_indices =
+          ((ShadowRealTupleType) shadow).sumIndices(local_indices);
+      }
+    }
+    return local_indices;
+  }
+
+  /** add DisplayIndices (from each ShadowScalarType and
+      ShadowRealTupleType component) */
+  int[] sumDisplayIndices(int[] display_indices) throws VisADException {
+    return addIndices(display_indices, DisplayIndices);
+  }
+
+  /** add ValueIndices (from each ShadowScalarType and
+      ShadowRealTupleType component) */
+  int[] sumValueIndices(int[] value_indices) throws VisADException {
+    return addIndices(value_indices, ValueIndices);
+  }
+
+  /*
+    if (Flat) {
+      terminal, so check condition 5
+    }
+    else {
+      check condition 2 on Flat components
+      pass levelOfDifficulty down to checkIndices on non-Flat components
+    }
+  */
+  public int checkIndices(int[] indices, int[] display_indices,
+             int[] value_indices, boolean[] isTransform, int levelOfDifficulty)
+      throws VisADException, RemoteException {
+    // add indices & display_indices from RealType and
+    // RealTupleType components
+    int[] local_indices = sumIndices(indices);
+    int[] local_display_indices = sumDisplayIndices(display_indices);
+    int[] local_value_indices = sumValueIndices(value_indices);
+
+    anyContour = checkContour(local_display_indices);
+    anyFlow = checkFlow(local_display_indices);
+    anyShape = checkShape(local_display_indices);
+    anyText = checkText(local_display_indices);
+    adjustProjectionSeam = checkAdjustProjectionSeam();
+
+    markTransform(isTransform);
+
+    // get value_indices arrays used by doTransform
+    inherited_values = copyIndices(value_indices);
+
+    // check for any mapped
+    if (levelOfDifficulty == NOTHING_MAPPED) {
+      if (checkAny(DisplayIndices)) {
+        levelOfDifficulty = NESTED;
+      }
+    }
+
+    if (Flat) {
+      // test legality of Animation and SelectValue
+      if (checkAnimationOrValue(DisplayIndices) > 0) {
+        throw new BadMappingException("Animation and SelectValue may not " +
+                                      "occur in range: " +
+                                      "ShadowTupleType.checkIndices");
+      }
+
+      LevelOfDifficulty =
+        testIndices(local_indices, local_display_indices, levelOfDifficulty);
+      if (LevelOfDifficulty == NESTED) {
+        if (checkR2D2(DisplayIndices)) {
+          LevelOfDifficulty = SIMPLE_TUPLE;
+        }
+        else {
+          LevelOfDifficulty = LEGAL;
+        }
+      }
+    }
+    else { // !Flat
+      if (levelOfDifficulty == NESTED) {
+        if (!checkNested(DisplayIndices)) {
+          levelOfDifficulty = LEGAL;
+        }
+      }
+      int minLevelOfDifficulty = ShadowType.NOTHING_MAPPED;
+      for (int j=0; j<tupleComponents.length; j++) {
+        ShadowType shadow = (ShadowType) tupleComponents[j];
+        // treat ShadowRealTupleType component as a set of
+        // ShadowRealType components (i.e., not terminal)
+        if (shadow instanceof ShadowFunctionType ||
+            shadow instanceof ShadowSetType ||
+            (shadow instanceof ShadowTupleType &&
+             !(shadow instanceof ShadowRealTupleType)) ) {
+          int level = shadow.checkIndices(local_indices, local_display_indices,
+                                          local_value_indices, isTransform,
+                                          levelOfDifficulty);
+          if (level < minLevelOfDifficulty) {
+            minLevelOfDifficulty = level;
+          }
+        }
+      }
+      LevelOfDifficulty = minLevelOfDifficulty;
+    }
+    return LevelOfDifficulty;
+  }
+
+  public int[] getInheritedValues() {
+    return inherited_values;
+  }
+
+  /** get number of components */
+  public int getDimension() {
+    return tupleComponents.length;
+  }
+
+  public ShadowType getComponent(int i) {
+    return tupleComponents[i];
+  }
+
+  /** mark Control-s as needing re-Transform */
+  void markTransform(boolean[] isTransform) {
+    for (int i=0; i<tupleComponents.length; i++) {
+      tupleComponents[i].markTransform(isTransform);
+    }
+  }
+
+  /** transform data into a (Java3D or Java2D) scene graph;
+      add generated scene graph components as children of group;
+      group is Group (Java3D) or VisADGroup (Java2D);
+      value_array are inherited valueArray values;
+      default_values are defaults for each display.DisplayRealTypeVector;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer,
+                             ShadowType shadow_api)
+         throws VisADException, RemoteException {
+
+    if (data.isMissing()) return false;
+    if (LevelOfDifficulty == NOTHING_MAPPED) return false;
+
+    if (!(data instanceof TupleIface)) {
+      throw new DisplayException("data must be Tuple: " +
+                                 "ShadowTupleType.doTransform");
+    }
+
+    // get some precomputed values useful for transform
+    // length of ValueArray
+    int valueArrayLength = display.getValueArrayLength();
+    // mapping from ValueArray to DisplayScalar
+    int[] valueToScalar = display.getValueToScalar();
+    // mapping from ValueArray to MapVector
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    // array to hold values for various mappings
+    float[][] display_values = new float[valueArrayLength][];
+
+    // get values inherited from parent;
+    // assume these do not include SelectRange, SelectValue
+    // or Animation values - see temporary hack in
+    // DataRenderer.isTransformControl
+    int[] inherited_values = getInheritedValues();
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0) {
+        display_values[i] = new float[1];
+        display_values[i][0] = value_array[i];
+      }
+    }
+
+    TupleIface tuple = (TupleIface) data;
+    RealType[] realComponents = ((TupleType) data.getType()).getRealComponents();
+    int length = realComponents.length;
+    if (length > 0) {
+      double[][] value = new double[length][1];
+      Unit[] value_units = new Unit[length];
+      int j = 0;
+      for (int i=0; i<tuple.getDimension(); i++) {
+        Data component = tuple.getComponent(i);
+        if (component instanceof Real) {
+          value_units[j] = realComponents[j].getDefaultUnit();
+          value[j][0] =
+            ((Real) component).getValue(value_units[j]);
+          j++;
+        }
+        else if (component instanceof RealTuple) {
+          for (int k=0; k<((RealTuple) component).getDimension(); k++) {
+            value_units[j] = realComponents[j].getDefaultUnit();
+            value[j][0] =
+              ((Real) ((RealTuple) component).getComponent(k)).
+                                              getValue(value_units[j]);
+            j++;
+          }
+        }
+      }
+      ShadowRealType[] RealComponents = getRealComponents();
+      mapValues(display_values, value, RealComponents);
+
+      int[] refToComponent = getRefToComponent();
+      ShadowRealTupleType[] componentWithRef = getComponentWithRef();
+      int[] componentIndex = getComponentIndex();
+
+      if (refToComponent != null) {
+
+        // TO_DO
+
+        for (int i=0; i<refToComponent.length; i++) {
+          int n = componentWithRef[i].getDimension();
+          int start = refToComponent[i];
+          double[][] values = new double[n][];
+          for (j=0; j<n; j++) values[j] = value[j + start];
+          ShadowRealTupleType component_reference =
+            componentWithRef[i].getReference();
+          RealTupleType ref = (RealTupleType) component_reference.getType();
+          Unit[] range_units;
+          CoordinateSystem range_coord_sys;
+          if (i == 0 && componentWithRef[i].equals(this)) {
+            range_units = value_units;
+            range_coord_sys = ((RealTuple) data).getCoordinateSystem();
+          }
+          else {
+            range_units = new Unit[n];
+            for (j=0; j<n; j++) range_units[j] = value_units[j + start];
+            range_coord_sys = ((RealTuple) ((TupleIface) data).
+                    getComponent(componentIndex[i])).getCoordinateSystem();
+          }
+
+          // MEM
+          double[][] reference_values =
+            CoordinateSystem.transformCoordinates(
+              ref, null, ref.getDefaultUnits(), null,
+              (RealTupleType) componentWithRef[i].getType(),
+              range_coord_sys, range_units, null, values);
+
+          // WLH 13 March 2000
+          // if (getAnyFlow()) {
+            renderer.setEarthSpatialData(componentWithRef[i],
+                      component_reference, ref, ref.getDefaultUnits(),
+                      (RealTupleType) componentWithRef[i].getType(),
+                      new CoordinateSystem[] {range_coord_sys}, range_units);
+          // WLH 13 March 2000
+          // }
+
+          // map reference_values to appropriate DisplayRealType-s
+          // MEM
+          mapValues(display_values, reference_values,
+                    getComponents(component_reference, false));
+          // FREE
+          reference_values = null;
+          // FREE (redundant reference to range_values)
+          values = null;
+        } // end for (int i=0; i<refToComponent.length; i++)
+      } // end if (refToComponent != null)
+
+      // setEarthSpatialData calls when no CoordinateSystem
+      // WLH 13 March 2000
+      // if (this instanceof ShadowTupleType && getAnyFlow()) {
+      if (this instanceof ShadowTupleType) {
+        if (this instanceof ShadowRealTupleType) {
+          Unit[] range_units = value_units;
+          CoordinateSystem range_coord_sys =
+            ((RealTuple) data).getCoordinateSystem();
+/* WLH 23 May 99
+          renderer.setEarthSpatialData((ShadowRealTupleType) this,
+                    null, null, null, (RealTupleType) this.getType(),
+                    new CoordinateSystem[] {range_coord_sys}, range_units);
+*/
+          ShadowRealTupleType component_reference =
+            ((ShadowRealTupleType) this).getReference();
+          RealTupleType ref = (component_reference == null) ? null :
+                                (RealTupleType) component_reference.getType();
+          Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+          renderer.setEarthSpatialData((ShadowRealTupleType) this,
+                    component_reference, ref, ref_units,
+                    (RealTupleType) this.getType(),
+                    new CoordinateSystem[] {range_coord_sys}, range_units);
+        }
+        else { // if (!(this instanceof ShadowRealTupleType))
+          int start = 0;
+          int n = ((ShadowTupleType) this).getDimension();
+          for (int i=0; i<n ;i++) {
+            ShadowType component =
+              ((ShadowTupleType) this).getComponent(i);
+            if (component instanceof ShadowRealTupleType) {
+              int m = ((ShadowRealTupleType) component).getDimension();
+              Unit[] range_units = new Unit[m];
+              for (j=0; j<m; j++) range_units[j] = value_units[j + start];
+              CoordinateSystem range_coord_sys =
+                ((RealTuple) ((TupleIface) data).getComponent(i)).getCoordinateSystem();
+/* WLH 23 May 99
+              renderer.setEarthSpatialData((ShadowRealTupleType)
+                      component, null, null,
+                      null, (RealTupleType) component.getType(),
+                      new CoordinateSystem[] {range_coord_sys}, range_units);
+*/
+              ShadowRealTupleType component_reference =
+                ((ShadowRealTupleType) component).getReference();
+              RealTupleType ref = (component_reference == null) ? null :
+                                  (RealTupleType) component_reference.getType();
+              Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+              renderer.setEarthSpatialData((ShadowRealTupleType) component,
+                      component_reference, ref, ref_units,
+                      (RealTupleType) component.getType(),
+                      new CoordinateSystem[] {range_coord_sys}, range_units);
+              start += ((ShadowRealTupleType) component).getDimension();
+            }
+            else if (component instanceof ShadowRealType) {
+              start++;
+            }
+          }
+        } // end if (!(this instanceof ShadowRealTupleType))
+      } // end if (this instanceof ShadowTupleType)
+
+    } // end if (length > 0)
+
+    // get any text String and TextControl inherited from parent
+    String text_value = getParentText();
+    TextControl text_control = getParentTextControl();
+    boolean anyText = getAnyText();
+    if (anyText && text_value == null) {
+      for (int i=0; i<valueArrayLength; i++) {
+        if (display_values[i] != null) {
+          int displayScalarIndex = valueToScalar[i];
+          ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+          ScalarType real = map.getScalar();
+          DisplayRealType dreal = display.getDisplayScalar(displayScalarIndex);
+          if (dreal.equals(Display.Text) && real instanceof RealType) {
+            text_control = (TextControl) map.getControl();
+            NumberFormat format = text_control.getNumberFormat();
+            if (display_values[i][0] != display_values[i][0]) {
+              text_value = "";
+            }
+            else if (format == null) {
+              text_value = PlotText.shortString(display_values[i][0]);
+            }
+            else {
+              text_value = format.format(display_values[i][0]);
+            }
+            break;
+          }
+        }
+      }
+
+      if (text_value == null) {
+        for (int i=0; i<tuple.getDimension(); i++) {
+          Data component = tuple.getComponent(i);
+          if (component instanceof Text) {
+            ShadowTextType type = (ShadowTextType) tupleComponents[i];
+            Vector maps = type.getSelectedMapVector();
+            if (!maps.isEmpty()) {
+              text_value = ((Text) component).getValue();
+              ScalarMap map = (ScalarMap) maps.firstElement();
+              text_control = (TextControl) map.getControl();
+            }
+          }
+        }
+      }
+    } // end if (anyText && text_value == null)
+
+    boolean[][] range_select =
+      shadow_api.assembleSelect(display_values, 1, valueArrayLength,
+                     valueToScalar, display, shadow_api);
+
+    if (range_select[0] != null && !range_select[0][0]) {
+      // data not selected
+      return false;
+    }
+
+    if (getIsTerminal()) {
+      return terminalTupleOrScalar(group, display_values, text_value,
+                                   text_control, valueArrayLength,
+                                   valueToScalar, default_values,
+                                   inherited_values, renderer, shadow_api);
+    }
+    else { // if (!isTerminal)
+      boolean post = false;
+      // add values to value_array according to SelectedMapVector-s
+      // of RealType-s in components (including Reference), and
+      // recursively call doTransform on other components
+      for (int i=0; i<valueArrayLength; i++) {
+        if (display_values[i] != null) {
+          value_array[i] = display_values[i][0];
+        }
+      }
+
+      if (text_value != null && text_control != null) {
+        setText(text_value, text_control);
+      }
+      else {
+        setText(null, null);
+      }
+
+      for (int i=0; i<tuple.getDimension(); i++) {
+        Data component = tuple.getComponent(i);
+        if (!(component instanceof Real) &&
+            !(component instanceof RealTuple)) {
+          // push lat_index and lon_index for flow navigation
+          int[] lat_lon_indices = renderer.getLatLonIndices();
+          post |= shadow_api.recurseComponent(i, group, component, value_array,
+                                              default_values, renderer);
+          // pop lat_index and lon_index for flow navigation
+          renderer.setLatLonIndices(lat_lon_indices);
+        }
+      }
+      return post;
+    }
+  }
+
+
+  public boolean isFlat() {
+    return Flat;
+  }
+
+}
+
diff --git a/visad/ShadowType.java b/visad/ShadowType.java
new file mode 100644
index 0000000..412c22e
--- /dev/null
+++ b/visad/ShadowType.java
@@ -0,0 +1,4195 @@
+//
+// ShadowType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Font;
+import java.awt.image.BufferedImage;
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import visad.util.HersheyFont;
+
+/**
+ * The ShadowType hierarchy shadows the MathType hierarchy, within a
+ * DataDisplayLink.
+ * <P>
+ */
+public abstract class ShadowType extends Object implements java.io.Serializable {
+
+  /** possible values for LevelOfDifficulty */
+  public static final int NOTHING_MAPPED = 6;
+  public static final int SIMPLE_TUPLE = 5;
+  public static final int SIMPLE_ANIMATE_FIELD = 4;
+  public static final int SIMPLE_FIELD = 3;
+  public static final int NESTED = 2;
+  public static final int LEGAL = 1;
+
+  /**  Image ByReference flags **/
+  public static final String PROP_IMAGE_BY_REF = "visad.java3d.imageByRef";
+
+  /** Property to create a texture for a single value color filled contour */
+  public static final String PROP_CONTOURFILL_SINGLE_VALUE_AS_TEXTURE = "visad.contourFillSingleValueAsTexture";
+  public static final boolean byReference;
+  public static final boolean yUp;
+  static {
+    byReference = Boolean.parseBoolean(System.getProperty(PROP_IMAGE_BY_REF, "false"));
+    if (byReference) {
+      yUp = true;
+    } else {
+      yUp = false;
+    }
+    //System.err.println("IMAGE_BY_REF:" + byReference);
+  }
+
+
+  /** basic information about this ShadowType */
+  MathType Type; // MathType being shadowed
+  transient DataDisplayLink Link;
+  transient DisplayImpl display;
+  transient private Data data; // from Link.getData()
+  private ShadowType Parent;
+
+  /** information calculated by constructors */
+  /**
+   * count of occurences of DisplayRealType-s ShadowScalarType: set for mappings
+   * to DisplayRealType-s ShadowTupleType (incl ShadowRealTupleType): set to sum
+   * for ShadowScalarType & ShadowRealTupleType components ShadowRealTupleType:
+   * add contribution from Reference
+   */
+  int[] DisplayIndices;
+  /**
+   * ValueIndices is like DisplayIndices, but distinguishes different
+   * ScalarMap-s of non-Single DisplayRealTypes
+   */
+  int[] ValueIndices;
+  /**
+   * MultipleSpatialDisplayScalar is true if any RealType component is mapped to
+   * multiple spatial DisplayRealType-s
+   */
+  boolean MultipleSpatialDisplayScalar;
+  /**
+   * MultipleDisplayScalar is true if any RealType component is mapped to
+   * multiple DisplayRealType-s, or if any RealTupleType component and its
+   * Reference are both mapped
+   */
+  boolean MultipleDisplayScalar;
+  /**
+   * MappedDisplayScalar is true if any RealType component is mapped to any
+   * DisplayRealType-s, including via a RealTupleType.Reference
+   */
+  boolean MappedDisplayScalar;
+
+  /** information calculated by checkIndices & testIndices */
+  boolean isTerminal;
+  int LevelOfDifficulty;
+  boolean isTextureMap;
+  boolean curvedTexture;
+  boolean isTexture3D;
+  boolean isLinearContour3D;
+  boolean adjustProjectionSeam;
+
+  /**
+   * Dtype and Rtype used only with ShadowSetType and Flat ShadowFunctionType
+   */
+  int Dtype; // Domain Type: D0, D1, D2, D3, D4 or Dbad
+  int Rtype; // Range Type: R0, R1, R2, R3, R4 or Rbad
+  /** possible values for Dtype */
+  static final int D0 = 0; // (Unmapped)*
+  static final int D1 = 1; // allSpatial + (SpatialOffset, IsoContour, Flow,
+  // Text, Shape, ShapeScale, Color, Alpha, Range,
+  // Unmapped)*
+  static final int D2 = 2; // (SpatialOffset, Spatial, Color, Alpha,
+  // Range, Unmapped)*
+  static final int D3 = 3; // (Color, Alpha, Range, Unmapped)*
+  static final int D4 = 4; // (Animation, Value)*
+  static final int Dbad = 5;
+  /** possible values for Rtype */
+  static final int R0 = 0; // (Unmapped)*
+  static final int R1 = 1; // (Color, Alpha, Range, Unmapped)*
+  static final int R2 = 2; // (Spatial, SpatialOffset, Color, Alpha,
+  // Range, Unmapped)*
+  static final int R3 = 3; // (IsoContour, Flow, Text, Shape, ShapeScale Color,
+  // Alpha, Range, Unmapped)*
+  static final int R4 = 4; // (Spatial, SpatialOffset, IsoContour, Flow,
+  // Text, Shape, ShapeScale, Color, Alpha, Range,
+  // Unmapped)*
+  static final int Rbad = 5;
+
+  /** spatial DisplayTupleType at terminal nodes */
+  DisplayTupleType spatialTuple = null;
+  /** number of spatial DisplayRealType components at terminal nodes */
+  int spatialDimension;
+  /** flags for any IsoContour or Flow at terminal nodes */
+  boolean anyContour;
+  boolean anyFlow;
+  boolean anyShape;
+  boolean anyText;
+
+  /** streamline flags */
+  boolean streamline1;
+  boolean streamline2;
+  float streamlineDensity1;
+  float streamlineDensity2;
+  float arrowScale1;
+  float arrowScale2;
+  float stepFactor1;
+  float stepFactor2;
+  float packingFactor1;
+  float packingFactor2;
+  float cntrWeight1;
+  float cntrWeight2;
+  int n_pass1;
+  int n_pass2;
+  float reduction1;
+  float reduction2;
+  // ---------------------
+
+  /** makeContour, manifoldDimension == 2 */
+  int[] cnt = { 0 };
+  ProjectionControl p_cntrl = null;
+  ContourControl c_cntrl = null;
+  // -------------------------------------
+
+  /**
+   * makeContour, manifoldDimension == 3. Needed for case of missing final
+   * spatial coords
+   */
+  float[][] spatial_offset_values = null;
+
+  /**
+   * used by getComponents to record RealTupleTypes with coordinate transforms
+   */
+  int[] refToComponent;
+  ShadowRealTupleType[] componentWithRef;
+  int[] componentIndex;
+
+  public ShadowType(MathType type, DataDisplayLink link, ShadowType parent)
+      throws VisADException, RemoteException {
+    Type = type;
+    Link = link;
+    display = link.getDisplay();
+    Parent = parent;
+    data = link.getData();
+    DisplayIndices = zeroIndices(display.getDisplayScalarCount());
+    ValueIndices = zeroIndices(display.getValueArrayLength());
+    isTerminal = false;
+    isTextureMap = false;
+    curvedTexture = false;
+    isTexture3D = false;
+    isLinearContour3D = false;
+    adjustProjectionSeam = true;
+    LevelOfDifficulty = NOTHING_MAPPED;
+    MultipleSpatialDisplayScalar = false;
+    MultipleDisplayScalar = false;
+    MappedDisplayScalar = false;
+    p_cntrl = display.getProjectionControl();
+  }
+
+  public DataDisplayLink getLink() {
+    return Link;
+  }
+
+  public int getLevelOfDifficulty() {
+    return LevelOfDifficulty;
+  }
+
+  public boolean getIsTerminal() {
+    return isTerminal;
+  }
+
+  public boolean getIsTextureMap() {
+    return isTextureMap;
+  }
+
+  public boolean getCurvedTexture() {
+    return curvedTexture;
+  }
+
+  public boolean getIsTexture3D() {
+    return isTexture3D;
+  }
+
+  public boolean getIsLinearContour3D() {
+    return isLinearContour3D;
+  }
+
+  public int[] getRefToComponent() {
+    return refToComponent;
+  }
+
+  public ShadowRealTupleType[] getComponentWithRef() {
+    return componentWithRef;
+  }
+
+  public int[] getComponentIndex() {
+    return componentIndex;
+  }
+
+  public boolean getAdjustProjectionSeam() {
+    return adjustProjectionSeam;
+  }
+
+  public ShadowRealType[] getComponents(ShadowType type, boolean doRef)
+      throws VisADException {
+    if (type == null)
+      return null;
+    if (doRef) {
+      refToComponent = null;
+      componentWithRef = null;
+      componentIndex = null;
+    }
+    ShadowRealType[] reals;
+    if (type instanceof ShadowRealType) {
+      ShadowRealType[] r = { (ShadowRealType) type };
+      return r;
+    } else if (type instanceof ShadowRealTupleType) {
+      int n = ((ShadowRealTupleType) type).getDimension();
+      reals = new ShadowRealType[n];
+      for (int i = 0; i < n; i++) {
+        reals[i] = (ShadowRealType) ((ShadowRealTupleType) type)
+            .getComponent(i);
+      }
+      if (doRef) {
+        ShadowRealTupleType ref = ((ShadowRealTupleType) type).getReference();
+        if (ref != null && ref.getMappedDisplayScalar()) {
+          refToComponent = new int[1];
+          componentWithRef = new ShadowRealTupleType[1];
+          componentIndex = new int[1];
+          refToComponent[0] = 0;
+          componentWithRef[0] = (ShadowRealTupleType) type;
+          componentIndex[0] = 0;
+        }
+      }
+    } else if (type instanceof ShadowTupleType) {
+      int m = ((ShadowTupleType) type).getDimension();
+      int n = 0;
+      int nref = 0;
+      for (int i = 0; i < m; i++) {
+        ShadowType component = ((ShadowTupleType) type).getComponent(i);
+        if (component instanceof ShadowRealType) {
+          n++;
+        } else if (component instanceof ShadowRealTupleType) {
+          n += getComponents(component, false).length;
+          if (doRef) {
+            ShadowRealTupleType ref = ((ShadowRealTupleType) component)
+                .getReference();
+            if (ref != null && ref.getMappedDisplayScalar())
+              nref++;
+          }
+        }
+      }
+      reals = new ShadowRealType[n];
+      int j = 0;
+      if (nref == 0)
+        doRef = false;
+      if (doRef) {
+        refToComponent = new int[nref];
+        componentWithRef = new ShadowRealTupleType[nref];
+        componentIndex = new int[nref];
+      }
+      int rj = 0;
+      for (int i = 0; i < m; i++) {
+        ShadowType component = ((ShadowTupleType) type).getComponent(i);
+        if (component instanceof ShadowRealType
+            || component instanceof ShadowRealTupleType) {
+          /*
+           * WLH 17 April 99 ShadowRealType[] r = getComponents(component,
+           * false); for (int k=0; k<r.length; k++) { reals[j] = r[k]; j++; }
+           */
+          if (doRef && component instanceof ShadowRealTupleType) {
+            ShadowRealTupleType ref = ((ShadowRealTupleType) component)
+                .getReference();
+            if (ref != null && ref.getMappedDisplayScalar()) {
+              refToComponent[rj] = j;
+              componentWithRef[rj] = (ShadowRealTupleType) component;
+              componentIndex[rj] = i;
+              rj++;
+            }
+          }
+          ShadowRealType[] r = getComponents(component, false);
+          for (int k = 0; k < r.length; k++) {
+            reals[j] = r[k];
+            j++;
+          }
+        }
+      }
+    } else {
+      reals = null;
+    }
+    return reals;
+  }
+
+  public Data getData() {
+    return data;
+  }
+
+  public ShadowType getAdaptedShadowType() {
+    return this;
+  }
+
+  /**
+   * create a zero'd array of indices (for each RealType or each
+   * DisplayRealType)
+   */
+  static int[] zeroIndices(int length) {
+    int[] local_indices = new int[length];
+    for (int i = 0; i < length; i++) {
+      local_indices[i] = 0;
+    }
+    return local_indices;
+  }
+
+  /** copy an array of indices (for each RealType or each DisplayRealType) */
+  static int[] copyIndices(int[] indices) {
+    int[] local_indices = new int[indices.length];
+    for (int i = 0; i < indices.length; i++) {
+      local_indices[i] = indices[i];
+    }
+    return local_indices;
+  }
+
+  /** add arrays of indices (for each RealType or each DisplayRealType) */
+  static int[] addIndices(int[] indices, int[] indices2) throws VisADException {
+    if (indices.length != indices2.length) {
+      throw new DisplayException("ShadowType.addIndices: lengths don't match");
+    }
+    int[] local_indices = new int[indices.length];
+    for (int i = 0; i < indices.length; i++) {
+      local_indices[i] = indices[i] + indices2[i];
+    }
+    return local_indices;
+  }
+
+  public boolean getAnyContour() {
+    return anyContour;
+  }
+
+  public boolean getAnyFlow() {
+    return anyFlow;
+  }
+
+  public boolean getAnyShape() {
+    return anyShape;
+  }
+
+  public boolean getAnyText() {
+    return anyText;
+  }
+
+  /**
+   * test for display_indices in (Spatial, SpatialOffset, Color, Alpha,
+   * Animation, Range, Value, Flow, Text, Unmapped)
+   */
+  boolean checkNested(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplaySpatialCartesianTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplaySpatialCartesianTuple))))
+        continue; // Spatial
+      // SpatialOffset
+      if (Display.DisplaySpatialOffsetTuple.equals(tuple))
+        continue;
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow1Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow1Tuple))))
+        continue; // Flow1
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow2Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow2Tuple))))
+        continue; // Flow2
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.HSV) || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.Alpha) || real.equals(Display.Animation)
+          || real.equals(Display.SelectValue)
+          || real.equals(Display.SelectRange) || real.equals(Display.Shape)
+          || real.equals(Display.ShapeScale) || real.equals(Display.Text))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * test for display_indices in (Spatial, SpatialOffset, IsoContour, Color,
+   * Alpha, Flow, Text, Range, Unmapped)
+   */
+  boolean checkR4(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplaySpatialCartesianTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplaySpatialCartesianTuple))))
+        continue; // Spatial
+      // SpatialOffset
+      if (Display.DisplaySpatialOffsetTuple.equals(tuple))
+        continue;
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow1Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow1Tuple))))
+        continue; // Flow1
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow2Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow2Tuple))))
+        continue; // Flow2
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.HSV) || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.Alpha) || real.equals(Display.SelectRange)
+          || real.equals(Display.Shape) || real.equals(Display.ShapeScale)
+          || real.equals(Display.Text) || real.equals(Display.IsoContour))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * test for display_indices in (IsoContour, Color, Alpha, Flow, Text, Range,
+   * Unmapped)
+   */
+  boolean checkR3(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow1Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow1Tuple))))
+        continue; // Flow1
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow2Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow2Tuple))))
+        continue; // Flow2
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.HSV) || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.Alpha) || real.equals(Display.SelectRange)
+          || real.equals(Display.Shape) || real.equals(Display.ShapeScale)
+          || real.equals(Display.Text) || real.equals(Display.IsoContour))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * test for display_indices in (Color, Alpha, Range, Flow, Shape, ShapeScale,
+   * Text, Unmapped)
+   */
+  boolean checkR1D3(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow1Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow1Tuple))))
+        continue; // Flow1
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow2Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow2Tuple))))
+        continue; // Flow2
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.HSV) || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.Alpha) || real.equals(Display.Shape)
+          || real.equals(Display.ShapeScale) || real.equals(Display.Text)
+          || real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /** test for display_indices in (Color, Range, Unmapped) */
+  boolean checkColorRange(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (real.equals(Display.RGB) || real.equals(Display.HSV)
+          || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /** test for display_indices in (Color, Alpha, Range, Unmapped) */
+  boolean checkColorAlphaRange(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.Alpha) || real.equals(Display.HSV)
+          || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /** test for display_indices in (IsoContour, Color, Alpha, Range, Unmapped) */
+  boolean checkContourColorAlphaRange(int[] display_indices)
+      throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (real.equals(Display.IsoContour))
+        continue;
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.Alpha) || real.equals(Display.HSV)
+          || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * test for display_indices in (Spatial, SpatialOffset, Color, Alpha, Range,
+   * Flow, Shape, ShapeScale, Text, Unmapped)
+   */
+  boolean checkR2D2(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplaySpatialCartesianTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplaySpatialCartesianTuple))))
+        continue; // Spatial
+      // SpatialOffset
+      if (Display.DisplaySpatialOffsetTuple.equals(tuple))
+        continue;
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow1Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow1Tuple))))
+        continue; // Flow1
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow2Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow2Tuple))))
+        continue; // Flow2
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.HSV) || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.Alpha) || real.equals(Display.Shape)
+          || real.equals(Display.ShapeScale) || real.equals(Display.Text)
+          || real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * test for display_indices in (Spatial, SpatialOffset, Color, Range,
+   * Unmapped)
+   */
+  /*
+   * WLH 16 July 2000 boolean checkSpatialColorRange(int[] display_indices)
+   * throws RemoteException { for (int i=0; i<display_indices.length; i++) { if
+   * (display_indices[i] == 0) continue; DisplayRealType real =
+   * (DisplayRealType) display.getDisplayScalar(i); DisplayTupleType tuple =
+   * real.getTuple(); if (tuple != null &&
+   * (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+   * (tuple.getCoordinateSystem() != null &&
+   * tuple.getCoordinateSystem().getReference().equals(
+   * Display.DisplaySpatialCartesianTuple)))) continue; // Spatial //
+   * SpatialOffset if (Display.DisplaySpatialOffsetTuple.equals(tuple))
+   * continue; if (tuple != null && (tuple.equals(Display.DisplayRGBTuple) ||
+   * (tuple.getCoordinateSystem() != null &&
+   * tuple.getCoordinateSystem().getReference().equals(
+   * Display.DisplayRGBTuple)))) continue; // Color if (real.equals(Display.RGB)
+   * || real.equals(Display.HSV) || real.equals(Display.CMY)) continue; // more
+   * Color if (real.equals(Display.SelectRange)) continue; return false; }
+   * return true; }
+   */
+
+  /**
+   * test for display_indices in (Spatial, SpatialOffset, Color, Alpha, Range,
+   * Unmapped)
+   */
+  boolean checkSpatialOffsetColorAlphaRange(int[] display_indices)
+      throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplaySpatialCartesianTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplaySpatialCartesianTuple))))
+        continue; // Spatial
+      // SpatialOffset
+      if (Display.DisplaySpatialOffsetTuple.equals(tuple))
+        continue;
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.Alpha) || real.equals(Display.HSV)
+          || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * test for display_indices in (Spatial, Color, Alpha, Range, Unmapped)
+   */
+  boolean checkSpatialColorAlphaRange(int[] display_indices)
+      throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplaySpatialCartesianTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplaySpatialCartesianTuple))))
+        continue; // Spatial
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.Alpha) || real.equals(Display.HSV)
+          || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /** test for display_indices in (Spatial, Range, Unmapped) */
+  boolean checkSpatialRange(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplaySpatialCartesianTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplaySpatialCartesianTuple))))
+        continue; // Spatial
+      if (real.equals(Display.SelectRange))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /** test for any Animation or Value in display_indices */
+  int checkAnimationOrValue(int[] display_indices) throws RemoteException {
+    int count = 0;
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      if (real.equals(Display.Animation) || real.equals(Display.SelectValue))
+        count++;
+    }
+    return count;
+  }
+
+  /** test for any SelectRange in display_indices */
+  boolean anyRange(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      if (real.equals(Display.SelectRange))
+        return true;
+    }
+    return false;
+  }
+
+  /** test for any IsoContour in display_indices */
+  boolean checkContour(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      if (real.equals(Display.IsoContour))
+        return true;
+    }
+    return false;
+  }
+
+  /** test for any Flow in display_indices */
+  boolean checkFlow(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow1Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow1Tuple))))
+        return true; // Flow1
+      if (tuple != null
+          && (tuple.equals(Display.DisplayFlow2Tuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayFlow2Tuple))))
+        return true; // Flow2
+    }
+    return false;
+  }
+
+  /** test for any Shape in display_indices */
+  boolean checkShape(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      if (real.equals(Display.Shape))
+        return true;
+    }
+    return false;
+  }
+
+  /** test for any Text in display_indices */
+  boolean checkText(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      /*
+       * System.out.println("checkText: display_indices[" + i + "] = " +
+       * display_indices[i] + " real = " + ((DisplayRealType)
+       * display.getDisplayScalar(i)).getName());
+       */
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      /*
+       * System.out.println("checkText: real = " + real.getName());
+       */
+      if (real.equals(Display.Text)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  boolean checkAdjustProjectionSeam() throws RemoteException {
+    float[] default_values = getLink().getDefaultValues();
+    return default_values[display
+        .getDisplayScalarIndex(Display.AdjustProjectionSeam)] > 0.5f;
+  }
+
+  /** test for display_indices in (Color, Unmapped) */
+  boolean checkColor(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.HSV) || real.equals(Display.CMY))
+        continue; // more Color
+      return false;
+    }
+    return true;
+  }
+
+  /** test for display_indices in (Color, Alpha, Unmapped) */
+  boolean checkColorOrAlpha(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null
+          && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+              .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+              .getReference().equals(Display.DisplayRGBTuple))))
+        continue; // Color
+      if (real.equals(Display.RGB) || real.equals(Display.RGBA)
+          || real.equals(Display.HSV) || real.equals(Display.CMY))
+        continue; // more Color
+      if (real.equals(Display.Alpha))
+        continue;
+      return false;
+    }
+    return true;
+  }
+
+  /** test for any non-zero display_indices */
+  boolean checkAny(int[] display_indices) throws RemoteException {
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] > 0)
+        return true;
+    }
+    return false;
+  }
+
+  /** applied at terminal nodes */
+  int testIndices(int[] indices, int[] display_indices, int levelOfDifficulty)
+      throws VisADException, RemoteException {
+    // can apply ScalarMap-s as scan down MathType tree
+    // make sure some DisplayRealType is mapped
+
+    // test whether any RealType-s occur more than once
+    for (int i = 0; i < indices.length; i++) {
+      if (indices[i] > 1) {
+        ScalarType real = display.getScalar(i);
+        throw new BadMappingException("RealType " + real.getName()
+            + " occurs more than once: " + "ShadowType.testIndices");
+      }
+    }
+
+    // test whether any DisplayRealType occurs at least once;
+    // test whether any Single DisplayRealType occurs more than once
+    for (int i = 0; i < display_indices.length; i++) {
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      if (display_indices[i] > 0)
+        isTerminal = true;
+      if (display_indices[i] > 1 && real.isSingle()) {
+        throw new BadMappingException("Single " + "DisplayRealType "
+            + real.getName() + " occurs more than once: "
+            + "ShadowType.testIndices");
+      }
+    }
+
+    // test whether DisplayRealType-s from multiple
+    // spatial DisplayTupleType-s occur
+    spatialTuple = null;
+    spatialDimension = 0;
+    for (int i = 0; i < display_indices.length; i++) {
+      if (display_indices[i] > 0) {
+        DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+        DisplayTupleType rtuple = real.getTuple();
+        if (rtuple != null) {
+          if (rtuple.equals(Display.DisplaySpatialCartesianTuple)
+              || (rtuple.getCoordinateSystem() != null && rtuple
+                  .getCoordinateSystem().getReference().equals(
+                      Display.DisplaySpatialCartesianTuple))) {
+            if (spatialTuple != null && !spatialTuple.equals(rtuple)) {
+              throw new BadMappingException("DisplayRealType-s occur from "
+                  + "multiple spatial DisplayTupleType-s: "
+                  + "ShadowType.testIndices");
+            }
+            spatialTuple = rtuple;
+            spatialDimension++;
+          }
+        }
+      }
+    }
+
+    if (isTerminal) {
+      if (levelOfDifficulty == LEGAL) {
+        LevelOfDifficulty = LEGAL;
+      } else {
+        LevelOfDifficulty = NESTED;
+      }
+    } else {
+      // this is not illegal but also not a terminal node
+      // (no DisplayRealType-s mapped)
+      LevelOfDifficulty = NOTHING_MAPPED;
+    }
+    /*
+     * System.out.println("testIndices: LevelOfDifficulty = " +
+     * LevelOfDifficulty + " isTerminal = " + isTerminal + " Type = " +
+     * Type.prettyString());
+     */
+    return LevelOfDifficulty;
+  }
+
+  /*
+   * DEPRECATE THIS, no longer needed because SetTypes, Flat FieldTypes and Flat
+   * TupleTypes are terminals: this defines the default logic for ShadowTextType
+   * and ShadowMissingType - which may occur as a Field Range and are treated as
+   * unmapped
+   */
+  /**
+   * scans ShadowType tree to determine display feasibility and precompute
+   * useful information for Data transform; indices & display_indices are counts
+   * (at leaves) of numbers of occurrences of RealTypes and DisplayRealTypes;
+   * isTransform flags for (Animation, Range, Value) re-transform;
+   * levelOfDifficulty passed down and up call chain
+   */
+  public int checkIndices(int[] indices, int[] display_indices,
+      int[] value_indices, boolean[] isTransform, int levelOfDifficulty)
+      throws VisADException, RemoteException {
+
+    adjustProjectionSeam = checkAdjustProjectionSeam();
+    LevelOfDifficulty = testIndices(indices, display_indices, levelOfDifficulty);
+    return LevelOfDifficulty;
+  }
+
+  public DisplayImpl getDisplay() {
+    return display;
+  }
+
+  public MathType getType() {
+    return Type;
+  }
+
+  public boolean getMultipleDisplayScalar() {
+    return MultipleDisplayScalar;
+  }
+
+  public boolean getMultipleSpatialDisplayScalar() {
+    return MultipleSpatialDisplayScalar;
+  }
+
+  public boolean getMappedDisplayScalar() {
+    return MappedDisplayScalar;
+  }
+
+  public int[] getDisplayIndices() {
+    int[] ii = new int[DisplayIndices.length];
+    for (int i = 0; i < DisplayIndices.length; i++)
+      ii[i] = DisplayIndices[i];
+    return ii;
+  }
+
+  public int[] getValueIndices() {
+    int[] ii = new int[ValueIndices.length];
+    for (int i = 0; i < ValueIndices.length; i++)
+      ii[i] = ValueIndices[i];
+    return ii;
+  }
+
+  /**
+   * return true if DisplayIndices include multiple Animation, SelectValue and
+   * SelectRange
+   */
+  boolean testTransform() {
+    int count = 0;
+    for (int i = 0; i < DisplayIndices.length; i++) {
+      if (DisplayIndices[i] == 0)
+        continue;
+      DisplayRealType real = (DisplayRealType) display.getDisplayScalar(i);
+      if (real.equals(Display.Animation) || real.equals(Display.SelectValue)
+          || real.equals(Display.SelectRange)) {
+        count++;
+        if (count > 1)
+          return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * mark Control-s as needing re-Transform; default for ShadowTextType and
+   * ShadowMissingType
+   */
+  void markTransform(boolean[] isTransform) {
+  }
+
+  /**
+   * helpers for doTransform; they are in ShadowType because they are
+   * independent of graphics library
+   */
+
+  /** map values to display_values according to ScalarMap-s in reals */
+  public static void mapValues(float[][] display_values, double[][] values,
+      ShadowRealType[] reals) throws VisADException {
+    int n = values.length;
+    if (n != reals.length) {
+      throw new DisplayException("lengths don't match " + n + " != "
+          + reals.length + ": " + "ShadowType.mapValues");
+    }
+    for (int i = 0; i < n; i++) {
+      Enumeration maps = reals[i].getSelectedMapVector().elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        int value_index = map.getValueIndex();
+        /*
+         * double[] range = map.getRange(); System.out.println(map.getScalar() +
+         * " -> " + map.getDisplayScalar() + " : " + range[0] + " " + range[1] +
+         * "  value_index = " + value_index);
+         */
+        // MEM
+        display_values[value_index] = map.scaleValues(values[i]);
+        /*
+         * int m = values[i].length; for (int j=0; j<m; j++)
+         * System.out.println("values["+i+"]["+j+"] = " + values[i][j] +
+         * " display_values["+value_index+"]["+j+"] = " +
+         * display_values[value_index][j]);
+         */
+        /*
+         * int total = 0; int missing = 0; total =
+         * display_values[value_index].length; for (int j=0;
+         * j<display_values[value_index].length; j++) { if
+         * (display_values[value_index][j] != display_values[value_index][j]) {
+         * missing++; } } System.out.println("  total = " + total +
+         * " missing = " + missing);
+         */
+      }
+    }
+  }
+
+  /** map values into display_values according to ScalarMap-s in reals */
+  public static void mapValues(float[][] display_values, float[][] values,
+      ShadowRealType[] reals) throws VisADException {
+    mapValues(display_values, values, reals, true);
+  }
+
+  /**
+   * Map values into display_values according to ScalarMap-s in reals
+   * 
+   * @param display_values
+   *          return display values
+   * @param values
+   *          data values
+   * @param reals
+   *          the ShadowRealTypes corresponding to the Scalar in maps
+   * @param copy
+   *          if false, scale values in place if reals[index] has only one
+   *          mapping. Use true if values represent a getSamples(false) or
+   *          getFloats(false)
+   */
+  public static void mapValues(float[][] display_values, float[][] values,
+      ShadowRealType[] reals, boolean copy) throws VisADException {
+    int n = values.length;
+    if (n != reals.length) {
+      throw new DisplayException("lengths don't match: ShadowType.mapValues");
+    }
+    for (int i = 0; i < n; i++) {
+      boolean doCopy = copy;
+      int size = reals[i].getSelectedMapVector().size();
+      if (!copy && size > 1)
+        doCopy = true;
+      Enumeration maps = reals[i].getSelectedMapVector().elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        int value_index = map.getValueIndex();
+        /*
+         * double[] range = map.getRange(); System.out.println(map.getScalar() +
+         * " -> " + map.getDisplayScalar() + " : " + range[0] + " " + range[1] +
+         * "  value_index = " + value_index);
+         */
+        // MEM
+        display_values[value_index] = map.scaleValues(values[i], doCopy);
+        /*
+         * int m = values[i].length; for (int j=0; j<m; j++)
+         * System.out.println("values["+i+"]["+j+"] = " + values[i][j] +
+         * " display_values["+value_index+"]["+j+"] = " +
+         * display_values[value_index][j]);
+         */
+        /*
+         * int total = 0; int missing = 0; total =
+         * display_values[value_index].length; for (int j=0;
+         * j<display_values[value_index].length; j++) { if
+         * (display_values[value_index][j] != display_values[value_index][j])
+         * missing++; } System.out.println("  total = " + total + " missing = "
+         * + missing);
+         */
+      }
+    }
+  }
+
+  /* CTR: 13 Oct 1998 - BEGIN CHANGES */
+  public static VisADGeometryArray makePointGeometry(float[][] spatial_values,
+      byte[][] color_values) throws VisADException {
+    return makePointGeometry(spatial_values, color_values, false);
+  }
+
+  public static VisADGeometryArray makePointGeometry(float[][] spatial_values,
+      byte[][] color_values, boolean compress) throws VisADException {
+    if (spatial_values == null) {
+      throw new DisplayException("bad spatial_values: "
+          + "ShadowType.makePointGeometry: bad");
+    }
+    VisADPointArray array = new VisADPointArray();
+
+    if (compress) {
+      // redimension arrays to eliminate Float.NaN values
+      int len = spatial_values.length;
+      int clen;
+      if (color_values == null)
+        clen = 0;
+      else
+        clen = color_values.length;
+      float[] f = spatial_values[0];
+      int flen = f.length;
+      int nan = 0;
+      for (int i = 0; i < flen; i++)
+        if (f[i] != f[i])
+          nan++;
+      if (nan > 0) {
+        float[][] new_s_values = new float[len][flen - nan];
+        byte[][] new_c_values = color_values;
+        if (clen > 0)
+          new_c_values = new byte[clen][flen - nan];
+        int c = 0;
+        for (int i = 0; i < flen; i++) {
+          if (f[i] == f[i]) {
+            for (int j = 0; j < len; j++) {
+              new_s_values[j][c] = spatial_values[j][i];
+            }
+            for (int j = 0; j < clen; j++) {
+              new_c_values[j][c] = color_values[j][i];
+            }
+            c++;
+          }
+        }
+        spatial_values = new_s_values;
+        color_values = new_c_values;
+      }
+    }
+
+    // set coordinates and colors
+    // MEM
+    SampledSet.setGeometryArray(array, spatial_values, 4, color_values);
+    return array;
+  }
+
+  /* CTR: 13 Oct 1998 - END CHANGES */
+
+  /**
+   * collect and transform Shape DisplayRealType values from display_values;
+   * offset by spatial_values, colored by color_values and selected by
+   * range_select
+   */
+  public VisADGeometryArray[] assembleShape(float[][] display_values,
+      int valueArrayLength, int[] valueToMap, Vector MapVector,
+      int[] valueToScalar, DisplayImpl display, float[] default_values,
+      int[] inherited_values, float[][] spatial_values, byte[][] color_values,
+      boolean[][] range_select, int index, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+
+    if (spatial_values[0] == null)
+      return null;
+    int total_length = 0;
+    Vector array_vector = new Vector();
+    float x = spatial_values[0][0];
+    float y = spatial_values[1][0];
+    float z = spatial_values[2][0];
+    byte r = 0;
+    byte g = 0;
+    byte b = 0;
+    byte a = 0;
+    int color_length = 0;
+    if (color_values != null) {
+      color_length = color_values.length;
+      r = color_values[0][0];
+      g = color_values[1][0];
+      b = color_values[2][0];
+      if (color_length > 3)
+        a = color_values[3][0];
+    }
+
+    float[] scales = null;
+    for (int i = 0; i < valueArrayLength; i++) {
+      if (display_values[i] != null) {
+        int displayScalarIndex = valueToScalar[i];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+        if (real.equals(Display.ShapeScale)) {
+          if (index < 0) {
+            scales = display_values[i];
+            display_values[i] = null; // MEM_WLH 27 March 99
+          } else {
+            if (display_values[i].length == 1) {
+              scales = new float[] { display_values[i][0] };
+            } else {
+              scales = new float[] { display_values[i][index] };
+            }
+          }
+        }
+      }
+    }
+    if (scales == null) {
+      int default_index = display.getDisplayScalarIndex(Display.ShapeScale);
+      float default_scale = default_values[default_index];
+      scales = new float[] { default_scale };
+    }
+
+    float[] values = null;
+    ShapeControl control = null;
+    for (int j = 0; j < valueArrayLength; j++) {
+      if (display_values[j] != null) {
+        int displayScalarIndex = valueToScalar[j];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+
+        if (real.equals(Display.Shape)) {
+          if (index < 0) {
+            values = display_values[j];
+            display_values[j] = null; // MEM_WLH 27 March 99
+          } else {
+            if (display_values[j].length == 1) {
+              values = new float[] { display_values[j][0] };
+            } else {
+              values = new float[] { display_values[j][index] };
+            }
+          }
+          control = (ShapeControl) ((ScalarMap) MapVector
+              .elementAt(valueToMap[j])).getControl();
+          if (values == null || control == null)
+            continue;
+
+          // make len maximum of lengths of color_values,
+          // spatial_values & scales
+          int len = values.length;
+          if (color_values != null) {
+            if (color_values[0].length > len)
+              len = color_values[0].length;
+          }
+          if (spatial_values[0].length > len)
+            len = spatial_values[0].length;
+          if (scales.length > len)
+            len = scales.length;
+          // expand values if necessary
+          if (values.length < len) {
+            float[] new_values = new float[len];
+            for (int i = 0; i < len; i++)
+              new_values[i] = values[0];
+            values = new_values;
+          }
+
+          // WLH 31 May 2000
+          float cscale = control.getScale();
+
+          VisADGeometryArray[] arrays = control.getShapes(values);
+          for (int i = 0; i < arrays.length; i++) {
+            if (range_select[0] != null) {
+              if (range_select[0].length == 1) {
+                if (!range_select[0][0])
+                  arrays[i] = null;
+              } else {
+                if (!range_select[0][i])
+                  arrays[i] = null;
+              }
+            }
+            VisADGeometryArray array = arrays[i];
+            if (array != null) {
+              if (spatial_values[0].length > 1) {
+                x = spatial_values[0][i];
+                y = spatial_values[1][i];
+                z = spatial_values[2][i];
+              }
+              int npts = array.coordinates.length / 3;
+              // offset shape location by spatial values
+              float scale = (scales.length == 1) ? scales[0] : scales[i];
+
+              // WLH 31 May 2000
+              scale *= cscale;
+
+              for (int k = 0; k < array.coordinates.length; k += 3) {
+                array.coordinates[k] = x + scale * array.coordinates[k];
+                array.coordinates[k + 1] = y + scale * array.coordinates[k + 1];
+                array.coordinates[k + 2] = z + scale * array.coordinates[k + 2];
+              }
+
+              if (array.colors == null && color_values != null) {
+                array.colors = new byte[color_length * npts];
+                if (color_values[0].length > 1) {
+                  r = color_values[0][i];
+                  g = color_values[1][i];
+                  b = color_values[2][i];
+                  if (color_length > 3)
+                    a = color_values[3][i];
+                }
+                for (int k = 0; k < array.colors.length; k += color_length) {
+                  array.colors[k] = r;
+                  array.colors[k + 1] = g;
+                  array.colors[k + 2] = b;
+                  if (color_length > 3)
+                    array.colors[k + 3] = a;
+                }
+              }
+
+            }
+          } // end for (int i=0; i<arrays.length; i++)
+          total_length += arrays.length;
+          array_vector.addElement(arrays);
+        } // end if (real.equals(Display.Shape))
+      } // end if (display_values[i] != null)
+    } // end for (int j=0; j<valueArrayLength; j++)
+
+    if (total_length == 0)
+      return null;
+    VisADGeometryArray[] total_arrays = new VisADGeometryArray[total_length];
+    Enumeration arrayses = array_vector.elements();
+    int k = 0;
+    while (arrayses.hasMoreElements()) {
+      VisADGeometryArray[] arrays = (VisADGeometryArray[]) arrayses
+          .nextElement();
+      System.arraycopy(arrays, 0, total_arrays, k, arrays.length);
+      k += arrays.length;
+    }
+
+    // WLH 30 May 2002
+    DataRenderer renderer = getLink().getRenderer();
+    if (getAdjustProjectionSeam()) {
+      for (int i = 0; i < total_length; i++) {
+        if (total_arrays[i] != null) {
+          total_arrays[i] = total_arrays[i].adjustLongitudeBulk(renderer);
+        }
+      }
+    }
+
+    return total_arrays;
+  }
+
+  /**
+   * collect and transform spatial DisplayRealType values from display_values;
+   * add spatial offset DisplayRealType values; adjust flow1_values and
+   * flow2_values for any coordinate transform; if needed, return a spatial Set
+   * from spatial_values, with the same topology as domain_set (or an
+   * appropriate Irregular topology); domain_set = null and allSpatial = false
+   * if not called from ShadowFunctionType
+   */
+  public Set assembleSpatial(float[][] spatial_values,
+      float[][] display_values, int valueArrayLength, int[] valueToScalar,
+      DisplayImpl display, float[] default_values, int[] inherited_values,
+      Set domain_set, boolean allSpatial, boolean set_for_shape,
+      int[] spatialDimensions, boolean[][] range_select,
+      float[][] flow1_values, float[][] flow2_values, float[] flowScale,
+      boolean[] swap, DataRenderer renderer, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+    DisplayTupleType spatial_tuple = null;
+    // number of spatial samples, default is 1
+    int len = 1;
+    // number of non-inherited spatial dimensions
+    int spatialDimension = 0;
+    // temporary holder for non-inherited tuple_index values
+    int[] tuple_indices = new int[3];
+    spatialDimensions[0] = 0; // spatialDomainDimension
+    spatialDimensions[1] = 0; // spatialManifoldDimension
+    // holder for SpatialOffset values
+    float[][] offset_values = new float[3][];
+    boolean[] offset_copy = { false, false, false };
+
+    // spatial map RealType Units
+    Unit[] spatial_units = new Unit[] { null, null, null };
+    // spatial map getRange() results for flow adjustment
+    double[] ranges = new double[] { Double.NaN, Double.NaN, Double.NaN };
+    // some helpers for computing ranges for flow adjustment
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    // indexed by tuple_index
+    int[] spatial_value_indices = { -1, -1, -1 };
+
+    for (int i = 0; i < valueArrayLength; i++) {
+      if (display_values[i] != null) {
+        int displayScalarIndex = valueToScalar[i];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+        DisplayTupleType tuple = real.getTuple();
+
+        if (tuple != null
+            && (tuple.equals(Display.DisplaySpatialCartesianTuple) || (tuple
+                .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+                .getReference().equals(Display.DisplaySpatialCartesianTuple)))) {
+          if (spatial_tuple != null && !spatial_tuple.equals(tuple)) {
+            throw new DisplayException("multiple spatial display tuples: "
+                + "ShadowType.assembleSpatial");
+          }
+          spatial_tuple = tuple;
+          int tuple_index = real.getTupleIndex();
+          spatial_value_indices[tuple_index] = i;
+          spatial_values[tuple_index] = display_values[i];
+          len = Math.max(len, display_values[i].length);
+          display_values[i] = null; // MEM_WLH 27 March 99
+          spatialDimensions[0]++; // spatialDomainDimension
+          if (inherited_values[i] == 0) {
+            // don't count inherited spatial dimensions
+            tuple_indices[spatialDimension] = tuple_index;
+            spatialDimension++; // # non-inherited spatial dimensions
+          }
+          ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+          double[] map_range = map.getRange();
+          ranges[tuple_index] = map_range[1] - map_range[0];
+          spatial_units[tuple_index] = ((RealType) map.getScalar())
+              .getDefaultUnit();
+        }
+      } // end if (display_values[i] != null)
+    } // end for (int i=0; i<valueArrayLength; i++)
+    if (spatial_tuple == null) {
+      spatial_tuple = Display.DisplaySpatialCartesianTuple;
+    }
+
+    if (spatialDimension == 0) {
+      // len = 1 in this case
+      spatialDimensions[1] = 0; // spatialManifoldDimension
+    } else if (domain_set == null) {
+      spatialDimensions[1] = spatialDimension; // spatialManifoldDimension
+    } else if (!allSpatial) {
+      spatialDimensions[1] = spatialDimension; // spatialManifoldDimension
+      if (set_for_shape) {
+        // cannot inherit Set topology from Field domain, so
+        // construct IrregularSet topology of appropriate dimension
+        RealType[] reals = new RealType[spatialDimension];
+        float[][] samples = new float[spatialDimension][];
+        for (int i = 0; i < spatialDimension; i++) {
+          reals[i] = RealType.Generic;
+          samples[i] = spatial_values[tuple_indices[i]];
+        }
+        RealTupleType tuple_type = new RealTupleType(reals);
+        // MEM
+        try {
+          switch (spatialDimension) {
+          case 1:
+            domain_set = new Irregular1DSet(tuple_type, samples, null, null,
+                null, false);
+            break;
+          case 2:
+            domain_set = new Irregular2DSet(tuple_type, samples, null, null,
+                null, null, false);
+            break;
+          case 3:
+            domain_set = new Irregular3DSet(tuple_type, samples, null, null,
+                null, null, false);
+            break;
+          }
+        } catch (VisADException e) {
+          domain_set = null;
+        }
+        // System.out.println("IrregularSet done");
+      } else { // !set_for_shape
+        domain_set = null;
+      }
+    } else { // spatialDimension > 0 && allSpatial && domain_set != null
+      // spatialManifoldDimension
+      spatialDimensions[1] = domain_set.getManifoldDimension();
+    }
+
+    //
+    // need a spatial Set for shape (e.g., contour)
+    // or spatialManifoldDimension < 3
+    // NOTE - 3-D volume rendering may eventually need a spatial Set
+    //
+    boolean set_needed = domain_set != null
+        && (set_for_shape || spatialDimensions[1] < 3);
+
+    boolean[] missing_checked = { false, false, false };
+    for (int i = 0; i < 3; i++) {
+      if (spatial_values[i] == null) {
+        // fill any null spatial value arrays with default values
+        // MEM
+        spatial_values[i] = new float[len];
+        int default_index = display
+            .getDisplayScalarIndex(((DisplayRealType) spatial_tuple
+                .getComponent(i)));
+        float default_value = default_values[default_index];
+        java.util.Arrays.fill(spatial_values[i], default_value);
+        missing_checked[i] = true;
+      } else if (spatial_values[i].length == 1) {
+        // check solitary spatial value array for missing
+        float v = spatial_values[i][0];
+        missing_checked[i] = true;
+        if (v != v || Float.isInfinite(v)) {
+          // missing with length = 1, so nothing to render
+          range_select[0] = new boolean[1];
+          range_select[0][0] = false;
+          return null;
+        }
+        if (len > 1) {
+          // expand solitary spatial value array
+          // MEM
+          spatial_values[i] = new float[len];
+          for (int j = 0; j < len; j++)
+            spatial_values[i][j] = v;
+        }
+      }
+    } // end for (int i=0; i<3; i++)
+
+    // first equalize lengths of flow*_values and spatial_values
+    boolean anyFlow = false;
+    int[] flen = { 0, 0 };
+    float[][][] ff_values = { flow1_values, flow2_values };
+    for (int k = 0; k < 2; k++) {
+      for (int i = 0; i < 3; i++) {
+        if (ff_values[k][i] != null) {
+          anyFlow = true;
+          flen[k] = Math.max(flen[k], ff_values[k][i].length);
+        }
+      }
+    }
+    len = Math.max(len, Math.max(flen[0], flen[1]));
+    fillOut(spatial_values, len);
+    if (flen[0] > 0)
+      fillOut(flow1_values, len);
+    if (flen[1] > 0)
+      fillOut(flow2_values, len);
+
+    boolean spatial_flow = anyFlow;
+    for (int i = 0; i < 3; i++) {
+      if (ranges[i] == ranges[i]) {
+        if (spatial_units[i] == null) {
+          spatial_flow = false;
+          break;
+        }
+        for (int j = 0; j < 3; j++) {
+          if (ranges[j] == ranges[j]) {
+            if (spatial_units[j] == null) {
+              spatial_flow = false;
+              break;
+            }
+            if (!Unit.canConvert(spatial_units[i], spatial_units[j])) {
+              spatial_flow = false;
+              break;
+            }
+          }
+        }
+      }
+    }
+    // System.out.println("spatial_flow = " + spatial_flow);
+    boolean[] spatial_flows = new boolean[] {spatial_flow,spatial_flow};
+
+    if (spatial_flow) {
+      for (int i = 0; i < valueArrayLength; i++) {
+        if (display_values[i] != null) {
+          int displayScalarIndex = valueToScalar[i];
+          DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+          DisplayTupleType tuple = real.getTuple();
+           if (tuple != null) {
+              if (tuple.equals(Display.DisplayFlow1SphericalTuple) ||
+                  tuple.equals(Display.DisplayFlow2SphericalTuple)) {
+            	  int index =  tuple.equals(Display.DisplayFlow1SphericalTuple) ? 0 : 1;
+              	  spatial_flows[index] = false;
+                  display_values[i] = null; 
+              }
+           }
+        }
+      }
+
+      // adjust flow for spatial setRange scaling
+      double max_range = -1.0;
+      for (int i = 0; i < 3; i++) {
+        if (ranges[i] == ranges[i]) {
+          double ar = Math.abs(ranges[i]);
+          if (ar > max_range)
+            max_range = ar;
+        }
+      }
+      for (int i = 0; i < 3; i++) {
+        if (ranges[i] == ranges[i]) {
+          ranges[i] = ranges[i] / max_range;
+        } else {
+          ranges[i] = 1.0;
+        }
+      }
+      for (int k = 0; k < 2; k++) {
+        if (!(renderer.getRealVectorTypes(k) instanceof EarthVectorType) && spatial_flows[k]) {
+          if (ff_values[k][0] != null || ff_values[k][1] != null
+              || ff_values[k][2] != null) {
+            for (int j = 0; j < len; j++) {
+              float old_speed = 0.0f;
+              float new_speed = 0.0f;
+              for (int i = 0; i < 3; i++) {
+                if (ff_values[k][i] != null) {
+                  old_speed += ff_values[k][i][j] * ff_values[k][i][j];
+                  ff_values[k][i][j] *= ranges[i];
+                  new_speed += ff_values[k][i][j] * ff_values[k][i][j];
+                }
+              }
+              // but don't change vector magnitude ??
+              float ratio = (float) Math.sqrt(old_speed / new_speed);
+              // BMF 2009-03-04: flow values cannot be NaN
+              if (Float.isNaN(ratio)) ratio = 0f;
+              for (int i = 0; i < 3; i++) {
+                if (ff_values[k][i] != null) {
+                  ff_values[k][i][j] *= ratio;
+                }
+              }
+            }
+          } // end if (ff_values[k][0] != null || ...)
+        } // end if (!(renderer.getRealVectorTypes(k) instanceof
+          // EarthVectorType))
+      } // end for (int k=0; k<2; k++)
+
+    } // end if (spatial_flow)
+
+    // adjust Flow values for coordinate transform
+    if (spatial_tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+      // if (anyFlow) { WLH 4 March 2000
+      renderer.setEarthSpatialDisplay(null, spatial_tuple, display,
+          spatial_value_indices, default_values, ranges);
+      // } WLH 4 March 2000
+    } else {
+      // transform tuple_values to DisplaySpatialCartesianTuple
+      CoordinateSystem coord = spatial_tuple.getCoordinateSystem();
+
+      float[][][] vector_ends = new float[2][][];
+
+      // WLH 4 March 2000
+      renderer.setEarthSpatialDisplay(coord, spatial_tuple, display,
+          spatial_value_indices, default_values, ranges);
+      if (spatial_flow) {
+        if (anyFlow) {
+          // WLH 4 March 2000
+          // renderer.setEarthSpatialDisplay(coord, spatial_tuple, display,
+          // spatial_value_indices, default_values, ranges);
+
+          // compute and transform 'end points' of flow vectors
+          for (int k = 0; k < 2; k++) {
+            if (!(renderer.getRealVectorTypes(k) instanceof EarthVectorType) && spatial_flows[k]) {
+              if (flen[k] > 0) {
+                vector_ends[k] = new float[3][len];
+                for (int i = 0; i < 3; i++) {
+                  if (ff_values[k][i] != null) {
+                    for (int j = 0; j < len; j++) {
+                      vector_ends[k][i][j] = spatial_values[i][j]
+                          + flowScale[k] * ff_values[k][i][j];
+                    }
+                  } else { // (ff_values[k][i] == null)
+                    for (int j = 0; j < len; j++) {
+                      vector_ends[k][i][j] = spatial_values[i][j];
+                    }
+                  }
+                } // end for (int i=0; i<3; i++)
+                vector_ends[k] = coord.toReference(vector_ends[k]);
+              } // end if (flen[k] > 0)
+            } // end if (!(renderer.getRealVectorTypes(k) instanceof
+              // EarthVectorType))
+          } // end for (int k=0; k<2; k++)
+        }
+
+      } // end if (spatial_flow)
+
+      // transform spatial_values
+      float[][] new_spatial_values = coord.toReference(spatial_values);
+      /*
+       * System.out.println("in length = " + spatial_values[0].length +
+       * " out length = " + new_spatial_values[0].length); if
+       * (spatial_values[0].length == 5329) { // System.out.println(domain_set);
+       * // 73 73 for (int i=0; i<spatial_values[0].length; i+=71) {
+       * System.out.println("out " + new_spatial_values[0][i] + " " +
+       * new_spatial_values[1][i] + " " + new_spatial_values[2][i] + " in " +
+       * spatial_values[0][i] + " " + spatial_values[1][i] + " " +
+       * spatial_values[2][i] + " (i,j) = " + i/73 + " " + i%73); } }
+       */
+      for (int i = 0; i < 3; i++)
+        spatial_values[i] = new_spatial_values[i];
+
+      if (spatial_flow) {
+
+        if (anyFlow) {
+          // subtract transformed spatial_values from transformed flow vectors
+          for (int k = 0; k < 2; k++) {
+            if (!(renderer.getRealVectorTypes(k) instanceof EarthVectorType) && spatial_flows[k]) {
+              if (flen[k] > 0) {
+                for (int i = 0; i < 3; i++) {
+                  for (int j = 0; j < len; j++) {
+                    vector_ends[k][i][j] = (vector_ends[k][i][j] - spatial_values[i][j])
+                        / flowScale[k];
+                  }
+                  ff_values[k][i] = vector_ends[k][i];
+                }
+              }
+            } // end if (!(renderer.getRealVectorTypes(k) instanceof
+              // EarthVectorType))
+          } // end for (int k=0; k<2; k++)
+        }
+
+      } // end if (spatial_flow)
+
+      missing_checked = new boolean[] { false, false, false };
+
+    } // end if (!spatial_tuple.equals(Display.DisplaySpatialCartesianTuple))
+
+    // calculate if need to swap rows and cols in contour line labels
+    swap[0] = false;
+    if (allSpatial && spatialDimensions[1] == 2 && len > 1) {
+
+      // find the axis most nearly parallel to first grid direction
+      // i.e., vector from first sample (0) to second sample (1)
+      float simax = 0.0f;
+      float max = -1.0f;
+      int imax = -1;
+      for (int i = 0; i < 3; i++) {
+        float sdiff = spatial_values[i][1] - spatial_values[i][0];
+        float diff = Math.abs(sdiff);
+        if (diff > max) {
+          simax = sdiff;
+          max = diff;
+          imax = i;
+        }
+      }
+
+      // set ll = number of samples along a side of fastest factor of
+      // Gridded2DSet
+      // i.e., "stride"
+      // WLH 6 April 2001
+      // int ll = len;
+      int ll = len - 1;
+      if (domain_set != null && domain_set instanceof Gridded2DSet) {
+        ll = ((Gridded2DSet) domain_set).getLength(0);
+        if (ll > (len - 1))
+          ll = len - 1; // WLH 6 April 2001
+      }
+
+      // find the axis most nearly parallel to second grid direction
+      // i.e., vector from first sample (0) to second sample (1)
+      float sjmax = 0.0f;
+      max = -1.0f;
+      int jmax = -1;
+      for (int i = 0; i < 3; i++) {
+        if (i != imax) {
+          // WLH 6 April 2001
+          // float sdiff = spatial_values[i][ll-1] - spatial_values[i][0];
+          float sdiff = spatial_values[i][ll] - spatial_values[i][0];
+          float diff = Math.abs(sdiff);
+          if (diff > max) {
+            sjmax = sdiff;
+            max = diff;
+            jmax = i;
+          }
+        }
+      } // end for (int i=0; i<3; i++)
+      /*-TDR, debug
+      System.out.println("imax: "+imax+" jmax: "+jmax);
+      System.out.println("simax: "+simax+" sjmax: "+sjmax);
+       */
+      if (imax == 0) {
+        swap[0] = true;
+        swap[1] = (simax < 0.0f);
+        swap[2] = (sjmax < 0.0f);
+      } else if (imax == 1) {
+        /*-TDR, (4-18-01):
+           wrong grid dimension swapped
+
+         swap[1] = (sjmax < 0.0f);
+         swap[2] = (simax < 0.0f);
+         */
+        swap[2] = (sjmax < 0.0f);
+        swap[1] = (simax < 0.0f);
+      } else { // imax == 2
+        if (jmax == 1) {
+          swap[0] = true;
+          swap[1] = (simax < 0.0f);
+          swap[2] = (sjmax < 0.0f);
+        } else {
+          /*-TDR, (4-18-01) Untested:
+             should probably be same as change above
+           swap[1] = (sjmax < 0.0f);
+           swap[2] = (simax < 0.0f);
+           */
+          swap[2] = (sjmax < 0.0f);
+          swap[1] = (simax < 0.0f);
+        }
+      }
+      /*-TDR, debug
+      System.out.println("swap[0]: "+swap[0]+" swap[1]: "+swap[1]+
+                         " swap[2]: "+swap[2]);
+       */
+    }
+
+    // assemble SpatialOffsets
+    int offset_len = len;
+    for (int i = 0; i < valueArrayLength; i++) {
+      if (display_values[i] != null) {
+        int displayScalarIndex = valueToScalar[i];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+        DisplayTupleType tuple = real.getTuple();
+        if (Display.DisplaySpatialOffsetTuple.equals(tuple)) {
+          int tuple_index = real.getTupleIndex();
+          if (offset_values[tuple_index] == null) {
+            offset_values[tuple_index] = display_values[i];
+          } else {
+            int leno = offset_values[tuple_index].length;
+            int lend = display_values[i].length;
+            if (leno > lend) {
+              // assume lend == 1
+              float[] off;
+              if (offset_copy[tuple_index]) {
+                off = offset_values[tuple_index];
+              } else {
+                off = new float[leno];
+                offset_copy[tuple_index] = true;
+              }
+              for (int j = 0; j < leno; j++) {
+                off[j] = offset_values[tuple_index][j] + display_values[i][0];
+              }
+              offset_values[tuple_index] = off;
+              off = null;
+            } else if (leno < lend) {
+              // assume leno == 1
+              float[] off = new float[lend];
+              for (int j = 0; j < lend; j++) {
+                off[j] = offset_values[tuple_index][0] + display_values[i][j];
+              }
+              offset_values[tuple_index] = off;
+              off = null;
+              offset_copy[tuple_index] = true;
+            } else {
+              float[] off;
+              if (offset_copy[tuple_index]) {
+                off = offset_values[tuple_index];
+              } else {
+                off = new float[leno];
+                offset_copy[tuple_index] = true;
+              }
+              for (int j = 0; j < leno; j++) {
+                off[j] = offset_values[tuple_index][j] + display_values[i][j];
+              }
+              offset_values[tuple_index] = off;
+              off = null;
+            }
+          }
+          display_values[i] = null; // MEM_WLH 27 March 99
+          offset_len = Math.max(offset_len, offset_values[tuple_index].length);
+        } // end if (Display.DisplaySpatialOffsetTuple.equals(tuple))
+      } // end if (display_values[i] != null)
+    } // end for (int i=0; i<valueArrayLength; i++)
+
+    boolean[] offset_missing_checked = { false, false, false };
+    for (int i = 0; i < 3; i++) {
+      if (offset_values[i] == null) {
+        // WLH 13 June 2003
+        DisplayRealType offset = (DisplayRealType) Display.DisplaySpatialOffsetTuple
+            .getComponent(i);
+        int default_index = display.getDisplayScalarIndex(offset);
+        if (0 <= default_index && default_index < default_values.length) {
+          float default_value = default_values[default_index];
+          if (default_value == default_value) {
+            offset_values[i] = new float[] { default_value };
+          }
+        }
+        // end WLH 13 June 2003
+        offset_missing_checked[i] = true;
+      } else if (offset_values[i].length == 1) {
+        offset_missing_checked[i] = true;
+        if (offset_values[i][0] != offset_values[i][0]
+            || Float.isInfinite(offset_values[i][0])) {
+          // missing with length = 1, so nothing to render
+          range_select[0] = new boolean[1];
+          range_select[0][0] = false;
+          return null;
+        }
+      }
+    }
+
+    // spatial offsets longer than spatial, so increase len
+    if (offset_len > len) {
+      // assume len == 1
+      for (int i = 0; i < 3; i++) {
+        float[] s = new float[offset_len];
+        for (int k = 0; k < offset_len; k++)
+          s[k] = spatial_values[i][0];
+        spatial_values[i] = s;
+        s = null;
+      }
+      len = offset_len;
+    }
+
+    // add any spatial offsets to spatial values
+    for (int i = 0; i < 3; i++) {
+      if (offset_values[i] != null) {
+        int leno = offset_values[i].length;
+        if (leno < len) {
+          // assume leno == 1
+          for (int k = 0; k < offset_len; k++) {
+            spatial_values[i][k] += offset_values[i][0];
+          }
+        } else {
+          // assume leno == len
+          for (int k = 0; k < offset_len; k++) {
+            spatial_values[i][k] += offset_values[i][k];
+          }
+        }
+        offset_values[i] = null;
+        missing_checked[i] = missing_checked[i] && offset_missing_checked[i];
+      }
+
+      if (!missing_checked[i]) {
+        for (int j = 0; j < len; j++) {
+          if (spatial_values[i][j] != spatial_values[i][j]
+              || Float.isInfinite(spatial_values[i][j])) {
+            if (range_select[0] == null) {
+              range_select[0] = new boolean[len];
+              for (int k = 0; k < len; k++)
+                range_select[0][k] = true;
+            } else if (range_select[0].length < len) {
+              // assume range_select[0].length == 1
+              boolean[] r = new boolean[len];
+              for (int k = 0; k < len; k++)
+                r[k] = range_select[0][0];
+              range_select[0] = r;
+            }
+            range_select[0][j] = false;
+            spatial_values[i][j] = Float.NaN;
+          }
+        }
+      }
+    } // end for (int i=0; i<3; i++)
+
+    spatial_offset_values = offset_values;
+
+    if (set_needed) {
+      try {
+        if (spatialDimension == 0) {
+          double[] values = new double[3];
+          for (int i = 0; i < 3; i++)
+            values[i] = spatial_values[i][0];
+          RealTuple tuple = new RealTuple(Display.DisplaySpatialCartesianTuple,
+              values);
+          return new SingletonSet(tuple);
+        } else {
+          SetType type = new SetType(Display.DisplaySpatialCartesianTuple);
+          // MEM
+          // WLH 5 April 2000
+          // return domain_set.makeSpatial(type, spatial_values);
+          return shadow_api.makeSpatialSet(domain_set, type, spatial_values);
+        }
+      } catch (VisADException e) {
+        return null;
+      }
+    } else {
+      return null;
+    }
+  }
+
+  // WLH 5 April 2000
+  public Set makeSpatialSet(Set domain_set, SetType type,
+      float[][] spatial_values) throws VisADException {
+    return domain_set.makeSpatial(type, spatial_values);
+  }
+
+  private static void fillOut(float[][] values, int flen) {
+    for (int i = 0; i < values.length; i++) {
+      if (values[i] != null) {
+        int len = values[i].length;
+        if (len < flen) {
+          // assume len == 1
+          float[] s = new float[flen];
+          float v = values[i][0];
+          for (int k = 0; k < flen; k++)
+            s[k] = v;
+          values[i] = s;
+        }
+      }
+    }
+  }
+
+  /**
+   * assemble Flow components; Flow components are 'single', so no compositing
+   * is required
+   */
+  public void assembleFlow(float[][] flow1_values, float[][] flow2_values,
+      float[] flowScale, float[][] display_values, int valueArrayLength,
+      int[] valueToScalar, DisplayImpl display, float[] default_values,
+      boolean[][] range_select, DataRenderer renderer, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    int[] flen = { 0, 0 };
+    float[][][] ff_values = { flow1_values, flow2_values };
+    DisplayTupleType[] flow_tuple = { Display.DisplayFlow1Tuple,
+        Display.DisplayFlow2Tuple };
+    DisplayTupleType[] actual_tuple = { null, null };
+
+    boolean anyFlow = false;
+    /*
+     * WLH 15 April 2000 ScalarMap[][] maps = new ScalarMap[2][3];
+     */
+
+    for (int i = 0; i < valueArrayLength; i++) {
+      if (display_values[i] != null) {
+        int displayScalarIndex = valueToScalar[i];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+        DisplayTupleType tuple = real.getTuple();
+        for (int k = 0; k < 2; k++) {
+          if (tuple != null
+              && (tuple.equals(flow_tuple[k]) || (tuple.getCoordinateSystem() != null && tuple
+                  .getCoordinateSystem().getReference().equals(flow_tuple[k])))) {
+            if (actual_tuple[k] != null && !actual_tuple[k].equals(tuple)) {
+              throw new DisplayException("multiple flow " + k
+                  + " display tuples: " + "ShadowType.assembleFlow");
+            }
+            actual_tuple[k] = tuple;
+            ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+            FlowControl control = (FlowControl) map.getControl();
+            flowScale[k] = control.getFlowScale();
+            int flow_index = real.getTupleIndex();
+            ff_values[k][flow_index] = display_values[i];
+            flen[k] = Math.max(flen[k], display_values[i].length);
+            // if not DisplayFlow*SphericalTuple, null out values
+            if (actual_tuple[k].equals(flow_tuple[k])) {
+              display_values[i] = null; // MEM_WLH 27 March 99
+            }
+            /*
+             * WLH 15 April 2000 maps[k][flow_index] = map;
+             */
+
+            anyFlow = true;
+
+            if (k == 0) {
+              streamline1 = control.streamlinesEnabled();
+              streamlineDensity1 = control.getStreamlineDensity();
+              arrowScale1 = control.getArrowScale();
+              stepFactor1 = control.getStepFactor();
+              packingFactor1 = control.getStreamlinePacking();
+              float[] pp = control.getStreamlineSmoothing();
+              cntrWeight1 = pp[0];
+              n_pass1 = (int) pp[1];
+              reduction1 = control.getStreamlineReduction();
+            }
+            if (k == 1) {
+              streamline2 = control.streamlinesEnabled();
+              streamlineDensity2 = control.getStreamlineDensity();
+              arrowScale2 = control.getArrowScale();
+              stepFactor2 = control.getStepFactor();
+              packingFactor2 = control.getStreamlinePacking();
+              float[] pp = control.getStreamlineSmoothing();
+              cntrWeight2 = pp[0];
+              n_pass2 = (int) pp[1];
+              reduction2 = control.getStreamlineReduction();
+            }
+          }
+        }
+      }
+    }
+
+    /*
+     * WLH 15 April 2000 if (anyFlow) renderer.setFlowDisplay(maps, flowScale);
+     */
+
+    //
+    // TO_DO
+    // this should all happen in flow rendering method
+    //
+    for (int k = 0; k < 2; k++) {
+      boolean[] missing_checked = { false, false, false };
+      if (flen[k] > 0) {
+        for (int i = 0; i < 3; i++) {
+          if (ff_values[k][i] == null) {
+            // fill any null flow value arrays with default values
+            // MEM
+            ff_values[k][i] = new float[flen[k]];
+            int default_index = display
+                .getDisplayScalarIndex(((DisplayRealType) flow_tuple[k]
+                    .getComponent(i)));
+            float default_value = default_values[default_index];
+            for (int j = 0; j < flen[k]; j++) {
+              ff_values[k][i][j] = default_value;
+            }
+            missing_checked[i] = true;
+          } else if (ff_values[k][i].length == 1) {
+            // check solitary spatial value array for missing
+            float v = ff_values[k][i][0];
+            missing_checked[i] = true;
+            if (v != v) {
+              // missing with length = 1, so nothing to render
+              range_select[0] = new boolean[1];
+              range_select[0][0] = false;
+              return;
+            }
+            if (flen[k] > 1) {
+              // expand solitary flow value array
+              ff_values[k][i] = new float[flen[k]];
+              for (int j = 0; j < flen[k]; j++) {
+                ff_values[k][i][j] = v;
+              }
+            }
+          } // end if (ff_values[k][i].length == 1)
+        } // end for (int i=0; i<3; i++)
+      } // end if (flen[k] > 0)
+
+      if (actual_tuple[k] != null && !actual_tuple[k].equals(flow_tuple[k])) {
+        missing_checked = new boolean[] { false, false, false };
+        // transform tuple_values to flow_tuple[k]
+        CoordinateSystem coord = actual_tuple[k].getCoordinateSystem();
+        float[][] new_ff_values = coord.toReference(ff_values[k]);
+        for (int i = 0; i < 3; i++)
+          ff_values[k][i] = new_ff_values[i];
+      }
+
+      if (flen[k] > 0) {
+        for (int i = 0; i < 3; i++) {
+          if (!missing_checked[i]) {
+            for (int j = 0; j < flen[k]; j++) {
+              if (ff_values[k][i][j] != ff_values[k][i][j]) {
+                if (range_select[0] == null) {
+                  range_select[0] = new boolean[flen[k]];
+                  for (int m = 0; m < flen[k]; m++)
+                    range_select[0][m] = true;
+                } else if (range_select[0].length < flen[k]) {
+                  // assume range_select[0].length == 1
+                  boolean[] r = new boolean[flen[k]];
+                  for (int m = 0; m < flen[k]; m++)
+                    r[m] = range_select[0][0];
+                  range_select[0] = r;
+                }
+                range_select[0][j] = false;
+                ff_values[k][i][j] = 0.0f;
+              }
+            } // end for (int j=0; j<flen[k]; j++)
+          } // end if (!missing_checked[i])
+        } // end for (int i=0; i<3; i++)
+      } // end if (flen[k] > 0)
+    } // end for (int k=0; k<2; k++)
+    // end of 'this should all happen in flow rendering method'
+  }
+
+  public static final float METERS_PER_DEGREE = 111137.0f;
+
+  public static float[][] adjustFlowToEarth(int which, float[][] flow_values,
+      float[][] spatial_values, float flowScale, DataRenderer renderer)
+      throws VisADException {
+    return adjustFlowToEarth(which, flow_values, spatial_values, flowScale, renderer, false);
+  }
+
+  public static float[][] adjustFlowToEarth(int which, float[][] flow_values,
+      float[][] spatial_values, float flowScale, DataRenderer renderer, boolean force)
+      throws VisADException {
+    // System.out.println("adjustFlowToEarth " + renderer.getDisplay().getName()
+    // + " " + renderer.getRealVectorTypes(which)); // IDV
+	// Move this down into the check for shouldAdjust
+    //if (!(renderer.getRealVectorTypes(which) instanceof EarthVectorType) && !force) {
+      // only do this for EarthVectorType
+    //  return flow_values;
+    //}
+
+
+    FlowControl fcontrol = null;
+    DisplayImpl display = null;
+    boolean shouldAdjust = true;
+    DataDisplayLink link = renderer.getLinks()[0];
+    if (link != null && !force) {
+      display = link.getDisplay();
+      if (display != null) {
+
+        if (which == 0) {
+          fcontrol = (FlowControl) display.getControl(Flow1Control.class);
+        } else if (which == 1) {
+          fcontrol = (FlowControl) display.getControl(Flow2Control.class);
+        }
+        if (fcontrol == null) {
+          throw new VisADException(
+              "adjustFlowToEarth: Unable to get FlowControl");
+        }
+        shouldAdjust = fcontrol.getAdjustFlowToEarth();
+        // add one more check
+        Vector maps = link.getSelectedMapVector();
+        boolean haveSpeedDir = false;
+        for (int i = 0; i < maps.size(); i++) {
+        	ScalarMap map = (ScalarMap) maps.get(i);
+        	DisplayRealType displayType = map.getDisplayScalar();
+        	if ((which == 0 && (displayType.equals(Display.Flow1Azimuth) ||
+        	                    displayType.equals(Display.Flow1Radial)  ||
+        	                    displayType.equals(Display.Flow1Elevation)))  ||
+        	    (which == 1 && (displayType.equals(Display.Flow2Azimuth)  ||
+        	                    displayType.equals(Display.Flow2Radial)  ||
+        	                    displayType.equals(Display.Flow2Elevation)))) {
+        		haveSpeedDir = true;
+        		break;
+        	}
+        }
+        if (!(renderer.getRealVectorTypes(which) instanceof EarthVectorType) && !haveSpeedDir ) {
+        	shouldAdjust = false;
+        }
+      }
+    }
+    if (!shouldAdjust)
+      return flow_values;
+
+    int flen = flow_values[0].length;
+
+    // get flow_values maximum
+    float scale = 0.0f;
+    for (int j = 0; j < flen; j++) {
+      if (Math.abs(flow_values[0][j]) > scale) {
+        scale = (float) Math.abs(flow_values[0][j]);
+      }
+      if (Math.abs(flow_values[1][j]) > scale) {
+        scale = (float) Math.abs(flow_values[1][j]);
+      }
+      if (Math.abs(flow_values[2][j]) > scale) {
+        scale = (float) Math.abs(flow_values[2][j]);
+      }
+    }
+    float inv_scale = 1.0f / scale;
+    if (inv_scale != inv_scale)
+      inv_scale = 1.0f;
+    /*
+     * System.out.println("spatial_values = " + spatial_values[0][0] + " " +
+     * spatial_values[1][0] + " " + spatial_values[2][0]);
+     */
+    // convert spatial DisplayRealType values to earth coordinates
+    float[][] base_spatial_locs = new float[3][]; // WLH 9 Dec 99
+    float[][] earth_locs = renderer.spatialToEarth(spatial_values,
+        base_spatial_locs);
+    if (earth_locs == null)
+      return flow_values;
+    int elen = earth_locs.length; // 2 or 3
+    /*
+     * System.out.println("earth_locs = " + earth_locs[0][0] + " " +
+     * earth_locs[1][0]);
+     */
+
+    // convert earth coordinate Units to (radian, radian, meter)
+    boolean other_meters = false;
+    Unit[] earth_units = renderer.getEarthUnits();
+    if (earth_units != null) {
+      if (Unit.canConvert(earth_units[0], CommonUnit.radian)) {
+        earth_locs[0] = CommonUnit.radian.toThis(earth_locs[0], earth_units[0]);
+      }
+      if (Unit.canConvert(earth_units[1], CommonUnit.radian)) {
+        earth_locs[1] = CommonUnit.radian.toThis(earth_locs[1], earth_units[1]);
+      }
+      if (elen == 3 && earth_units.length == 3
+          && Unit.canConvert(earth_units[2], CommonUnit.meter)) {
+        other_meters = true;
+        earth_locs[2] = CommonUnit.meter.toThis(earth_locs[2], earth_units[2]);
+      }
+    }
+    /*
+     * System.out.println("radian earth_locs = " + earth_locs[0][0] + " " +
+     * earth_locs[1][0]);
+     */
+    // add scaled flow vector to earth location
+    if (elen == 3) {
+      // assume meters even if other_meters == false
+      float factor_lat = (float) (inv_scale * 1000.0f * Data.DEGREES_TO_RADIANS / METERS_PER_DEGREE);
+      float factor_vert = inv_scale * 1000.0f;
+      for (int j = 0; j < flen; j++) {
+        earth_locs[2][j] += factor_vert * flow_values[2][j];
+        earth_locs[1][j] += factor_lat * flow_values[0][j]
+            / ((float) Math.cos(earth_locs[0][j]));
+        earth_locs[0][j] += factor_lat * flow_values[1][j];
+      }
+    } else {
+      float factor_lat = 0.00001f * inv_scale
+          * (0.5f * renderer.getLatLonRange());
+      for (int j = 0; j < flen; j++) {
+        earth_locs[1][j] += factor_lat * flow_values[0][j]
+            / ((float) Math.cos(earth_locs[0][j]));
+        earth_locs[0][j] += factor_lat * flow_values[1][j];
+      }
+    }
+    /*
+     * System.out.println("flow earth_locs = " + earth_locs[0][0] + " " +
+     * earth_locs[1][0]);
+     */
+    // convert earth coordinate Units from (radian, radian, meter)
+    if (earth_units != null) {
+      if (Unit.canConvert(earth_units[0], CommonUnit.radian)) {
+        earth_locs[0] = CommonUnit.radian.toThat(earth_locs[0], earth_units[0]);
+      }
+      if (Unit.canConvert(earth_units[1], CommonUnit.radian)) {
+        earth_locs[1] = CommonUnit.radian.toThat(earth_locs[1], earth_units[1]);
+      }
+      if (elen == 3 && earth_units.length == 3
+          && Unit.canConvert(earth_units[2], CommonUnit.meter)) {
+        earth_locs[2] = CommonUnit.meter.toThat(earth_locs[2], earth_units[2]);
+      }
+    }
+    /*
+     * System.out.println("degree earth_locs = " + earth_locs[0][0] + " " +
+     * earth_locs[1][0]);
+     */
+    // convert earth coordinates to spatial DisplayRealType values
+    if (elen == 3) {
+      earth_locs = renderer.earthToSpatial(earth_locs, null, base_spatial_locs);
+    } else {
+      // apply vertical flow in earthToSpatial
+      float factor_vert = 0.00001f * inv_scale;
+      float[] vert = new float[flen];
+      for (int j = 0; j < flen; j++) {
+        vert[j] = factor_vert * flow_values[2][j];
+      }
+      earth_locs = renderer.earthToSpatial(earth_locs, vert, base_spatial_locs);
+      for (int i=0; i<earth_locs.length; i++) {
+        if (earth_locs[i] == null) {
+          earth_locs[i] = new float[flen];
+          for (int j = 0; j < flen; j++)
+            earth_locs[i][j] = spatial_values[i][j];
+        }
+      }
+    }
+    /*
+     * System.out.println("spatial earth_locs = " + earth_locs[0][0] + " " +
+     * earth_locs[1][0] + " " + earth_locs[2][0]);
+     */
+    // flow = change in spatial_values
+    for (int i = 0; i < 3; i++) {
+      for (int j = 0; j < flen; j++) {
+        earth_locs[i][j] -= spatial_values[i][j];
+      }
+    }
+    /*
+     * System.out.println("vector earth_locs = " + earth_locs[0][0] + " " +
+     * earth_locs[1][0] + " " + earth_locs[2][0]);
+     */
+    // combine earth_locs direction with flow_values magnitude
+    for (int j = 0; j < flen; j++) {
+      float mag = (float) Math.sqrt(flow_values[0][j] * flow_values[0][j]
+          + flow_values[1][j] * flow_values[1][j] + flow_values[2][j]
+          * flow_values[2][j]);
+      float new_mag = (float) Math.sqrt(earth_locs[0][j] * earth_locs[0][j]
+          + earth_locs[1][j] * earth_locs[1][j] + earth_locs[2][j]
+          * earth_locs[2][j]);
+      float ratio = mag / new_mag;
+      flow_values[0][j] = ratio * earth_locs[0][j];
+      flow_values[1][j] = ratio * earth_locs[1][j];
+      flow_values[2][j] = ratio * earth_locs[2][j];
+    }
+/*
+System.out.println("adjusted flow values = " + flow_values[0][0] + " " +
+                   flow_values[1][0] + " " + flow_values[2][0]);
+*/
+    testFlow("adjust", flow_values);
+    return flow_values;
+  }
+
+  public VisADGeometryArray[] makeStreamline(int which, float[][] flow_values,
+      float flowScale, float[][] spatial_values, Set spatial_set,
+      int spatialManifoldDimension, byte[][] color_values,
+      boolean[][] range_select, int valueArrayLength, int[] valueToMap,
+      Vector MapVector) throws VisADException {
+
+    if (flow_values[0] == null)
+      return null;
+    if (spatial_set == null)
+      return null;
+
+    if (which == 0 && !streamline1)
+      return null;
+    if (which == 1 && !streamline2)
+      return null;
+
+    if (!(spatial_set instanceof Gridded3DSet))
+      return null;
+
+    // -- TDR, only 2 possibilities: (0,1),(0,2),(1,2) because streamline
+    // algorithm, adapted from Vis5D, only works with 2 flow components.
+    // 2004-01-14
+    FunctionType ftype = (FunctionType) Type;
+    RealTupleType rtt = ftype.getFlatRange();
+    RealType[] range_reals = rtt.getRealComponents();
+
+    int flow_dim0 = -1;
+    int flow_dim1 = -1;
+    int cnt_flow_maps = 0;
+    for (int k = 0; k < range_reals.length; k++) {
+      for (int i = 0; i < valueArrayLength; i++) {
+        ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+        DisplayRealType dreal = map.getDisplayScalar();
+        ScalarType scalar = map.getScalar();
+        if (!scalar.equals(range_reals[k]))
+          continue;
+        if ((dreal.equals(Display.Flow1X)) || (dreal.equals(Display.Flow2X))
+            || (dreal.equals(Display.Flow1Elevation))
+            || (dreal.equals(Display.Flow2Elevation))) {
+          if (flow_dim0 == -1) {
+            flow_dim0 = 0;
+          } else {
+            flow_dim0 = 0;
+            flow_dim1 = 1;
+          }
+          cnt_flow_maps++;
+        }
+        if ((dreal.equals(Display.Flow1Y)) || (dreal.equals(Display.Flow2Y))
+            || (dreal.equals(Display.Flow1Azimuth))
+            || (dreal.equals(Display.Flow2Azimuth))) {
+          if (flow_dim0 == -1) {
+            flow_dim0 = 1;
+          } else {
+            flow_dim1 = 1;
+          }
+          cnt_flow_maps++;
+        }
+        if ((dreal.equals(Display.Flow1Z)) || (dreal.equals(Display.Flow2Z))
+            || (dreal.equals(Display.Flow1Radial))
+            || (dreal.equals(Display.Flow2Radial))) {
+          flow_dim1 = 2;
+          cnt_flow_maps++;
+        }
+      }
+    }
+
+    if (cnt_flow_maps > 2) {
+      throw new BadMappingException(
+          "only one or two ScalarMaps to Flow per data allowed if streamlines enabled");
+    }
+    // - 2004-01-14 ------------------------------------------------------
+
+    if (range_select[0] != null) {
+      if ((range_select[0].length == 1) && (!range_select[0][0]))
+        return null;
+      for (int ii = 0; ii < range_select[0].length; ii++) {
+        if (!range_select[0][ii]) {
+          flow_values[0][ii] = Float.NaN;
+          flow_values[1][ii] = Float.NaN;
+          // -TDR, 2004-01-15
+          flow_values[2][ii] = Float.NaN;
+        }
+      }
+    }
+
+    DataRenderer renderer = getLink().getRenderer();
+    flow_values = adjustFlowToEarth(which, flow_values, spatial_values,
+        flowScale, renderer);
+
+    /*- start make streamline vertices
+    System.out.println("----start streamlines----------"); */
+
+    float density = 1;
+    float arrowScale = 1;
+    float stepFactor = 2;
+    float packingFactor = 1;
+    float cntrWeight = 3;
+    int n_pass = 0;
+    float reduction = 1f;
+
+    int[] numl = new int[1];
+
+    int[][] n_verts = new int[1][];
+    float[][][] vr = new float[1][][];
+    float[][][] vc = new float[1][][];
+    int nr;
+    int nc;
+
+    if (which == 0)
+      density = streamlineDensity1;
+    if (which == 1)
+      density = streamlineDensity2;
+    if (which == 0)
+      arrowScale = arrowScale1;
+    if (which == 1)
+      arrowScale = arrowScale2;
+    if (which == 0)
+      stepFactor = stepFactor1;
+    if (which == 1)
+      stepFactor = stepFactor2;
+    if (which == 0)
+      packingFactor = packingFactor1;
+    if (which == 1)
+      packingFactor = packingFactor2;
+    if (which == 0)
+      cntrWeight = cntrWeight1;
+    if (which == 1)
+      cntrWeight = cntrWeight2;
+    if (which == 0)
+      n_pass = n_pass1;
+    if (which == 1)
+      n_pass = n_pass2;
+    if (which == 0)
+      reduction = reduction1;
+    if (which == 1)
+      reduction = reduction2;
+
+    if (spatialManifoldDimension == 2) {
+      nc = ((Gridded3DSet) spatial_set).LengthX;
+      nr = ((Gridded3DSet) spatial_set).LengthY;
+
+      Gridded2DSet gset = new Gridded2DSet(
+          RealTupleType.Generic2D,
+          new float[][] { spatial_values[flow_dim0], spatial_values[flow_dim1] },
+          nc, nr, null, null, null, false, false);
+
+      Stream2D.stream(flow_values[flow_dim0], flow_values[flow_dim1], nr, nc,
+          density, stepFactor, arrowScale, vr, vc, n_verts, numl, gset,
+          packingFactor, cntrWeight, n_pass, reduction);
+    } else {
+      throw new VisADException(
+          "only manifoldDimension==2 supported for streamlimes");
+    }
+
+    VisADLineArray[] arrays = new VisADLineArray[numl[0]];
+    Integer2DSet grid_set = new Integer2DSet(nc, nr);
+
+    for (int kk = 0; kk < arrays.length; kk++) {
+      arrays[kk] = new VisADLineArray();
+      float[][] grid = new float[2][n_verts[0][kk]];
+      System.arraycopy(vr[0][kk], 0, grid[1], 0, n_verts[0][kk]);
+      System.arraycopy(vc[0][kk], 0, grid[0], 0, n_verts[0][kk]);
+
+      float[][] spatial_set_vals = ((Gridded3DSet) spatial_set)
+          .gridToValue(grid);
+
+      byte[][] intrp_color_values = null;
+      if (color_values != null) {
+        int len = color_values.length;
+        intrp_color_values = new byte[len][n_verts[0][kk]];
+        int[] indices = grid_set.valueToIndex(grid);
+        for (int cc = 0; cc < n_verts[0][kk]; cc++) {
+          if (indices[cc] >= 0) {
+            intrp_color_values[0][cc] = color_values[0][indices[cc]];
+            intrp_color_values[1][cc] = color_values[1][indices[cc]];
+            intrp_color_values[2][cc] = color_values[2][indices[cc]];
+            if (len > 3) {
+              intrp_color_values[3][cc] = color_values[3][indices[cc]];
+            }
+          } else {
+            intrp_color_values[0][cc] = (byte) 255;
+            intrp_color_values[1][cc] = 0;
+            intrp_color_values[2][cc] = 0;
+            if (len > 3) {
+              intrp_color_values[3][cc] = color_values[3][indices[cc]];
+            }
+          }
+        }
+      }
+
+      Gridded3DSet.setGeometryArray(arrays[kk], spatial_set_vals, 4,
+          intrp_color_values);
+      arrays[kk] = (VisADLineArray) arrays[kk].removeMissing();
+    }
+
+    return arrays;
+  }
+
+  private static void testFlow(String id, float[][] flow_values) {
+    int flen = flow_values[0].length;
+    for (int i = 0; i < flen; i++) {
+      if (flow_values[0][i] != flow_values[0][i]
+          || flow_values[1][i] != flow_values[1][i]
+          || flow_values[2][i] != flow_values[2][i]
+          || Float.isInfinite(flow_values[0][i])
+          || Float.isInfinite(flow_values[1][i])
+          || Float.isInfinite(flow_values[2][i])) {
+        // System.out.println(id + " " + i + " " + flow_values[0][i] + " " +
+        // flow_values[1][i] + " " + flow_values[2][i]);
+        flow_values[0][i] = 0.0f;
+        flow_values[1][i] = 0.0f;
+        flow_values[2][i] = 0.0f;
+      }
+    }
+  }
+
+  private static final float BACK_SCALE = -0.15f;
+  private static final float PERP_SCALE = 0.15f;
+
+  /** which = 0 for Flow1 and which = 1 for Flow2 */
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+      float flowScale, float[][] spatial_values, byte[][] color_values,
+      boolean[][] range_select) throws VisADException {
+    if (flow_values[0] == null)
+      return null;
+    if (spatial_values[0] == null)
+      return null;
+
+    VisADLineArray array = new VisADLineArray();
+
+    int len = spatial_values[0].length;
+    int flen = flow_values[0].length;
+    int rlen = 0; // number of non-missing values
+    if (range_select[0] == null) {
+      rlen = len;
+    } else {
+      for (int j = 0; j < range_select[0].length; j++) {
+        if (range_select[0][j])
+          rlen++;
+      }
+    }
+    if (rlen == 0)
+      return null;
+
+    DataRenderer renderer = getLink().getRenderer();
+    flow_values = adjustFlowToEarth(which, flow_values, spatial_values,
+        flowScale, renderer);
+
+    array.vertexCount = 6 * rlen;
+
+    float[] coordinates = new float[18 * rlen];
+    int m = 0;
+    // flow vector
+    float f0 = 0.0f, f1 = 0.0f, f2 = 0.0f;
+    // arrow head vector
+    float a0 = 0.0f, a1 = 0.0f, a2 = 0.0f;
+    float b0 = 0.0f, b1 = 0.0f, b2 = 0.0f;
+    for (int j = 0; j < len; j++) {
+      if (range_select[0] == null || range_select[0][j]) {
+        if (flen == 1) {
+          f0 = flowScale * flow_values[0][0];
+          f1 = flowScale * flow_values[1][0];
+          f2 = flowScale * flow_values[2][0];
+        } else {
+          f0 = flowScale * flow_values[0][j];
+          f1 = flowScale * flow_values[1][j];
+          f2 = flowScale * flow_values[2][j];
+        }
+        int k = m;
+        // base point of flow vector
+        coordinates[m++] = spatial_values[0][j];
+        coordinates[m++] = spatial_values[1][j];
+        coordinates[m++] = spatial_values[2][j];
+        int n = m;
+        // k = orig m
+        // m = orig m + 3
+        // end point of flow vector
+        coordinates[m++] = coordinates[k++] + f0;
+        coordinates[m++] = coordinates[k++] + f1;
+        coordinates[m++] = coordinates[k++] + f2;
+        k = n;
+        // n = orig m + 3
+        // m = orig m + 6
+        // repeat end point of flow vector as
+        // first point of first arrow head
+        coordinates[m++] = coordinates[n++];
+        coordinates[m++] = coordinates[n++];
+        coordinates[m++] = coordinates[n++];
+        boolean mode2d = display.getDisplayRenderer().getMode2D();
+        b0 = a0 = BACK_SCALE * f0;
+        b1 = a1 = BACK_SCALE * f1;
+        b2 = a2 = BACK_SCALE * f2;
+
+        if (mode2d
+            || (Math.abs(f2) <= Math.abs(f0) && Math.abs(f2) <= Math.abs(f1))) {
+          a0 += PERP_SCALE * f1;
+          a1 -= PERP_SCALE * f0;
+          b0 -= PERP_SCALE * f1;
+          b1 += PERP_SCALE * f0;
+        } else if (Math.abs(f1) <= Math.abs(f0)) {
+          a0 += PERP_SCALE * f2;
+          a2 -= PERP_SCALE * f0;
+          b0 -= PERP_SCALE * f2;
+          b2 += PERP_SCALE * f0;
+        } else { // f0 is least
+          a1 += PERP_SCALE * f2;
+          a2 -= PERP_SCALE * f1;
+          b1 -= PERP_SCALE * f2;
+          b2 += PERP_SCALE * f1;
+        }
+
+        k = n;
+        // n = orig m + 6
+        // m = orig m + 9
+        // second point of first arrow head
+        coordinates[m++] = coordinates[n++] + a0;
+        coordinates[m++] = coordinates[n++] + a1;
+        coordinates[m++] = coordinates[n++] + a2;
+
+        n = k;
+        // k = orig m + 6
+        // first point of second arrow head
+        coordinates[m++] = coordinates[k++];
+        coordinates[m++] = coordinates[k++];
+        coordinates[m++] = coordinates[k++];
+
+        // n = orig m + 6
+        // second point of second arrow head
+        coordinates[m++] = coordinates[n++] + b0;
+        coordinates[m++] = coordinates[n++] + b1;
+        coordinates[m++] = coordinates[n++] + b2;
+      }
+    }
+    array.coordinates = coordinates;
+    // array.vertexFormat = COORDINATES;
+
+    if (color_values != null) {
+      int c_len = color_values.length;
+      byte[] colors = new byte[6 * c_len * rlen];
+      m = 0;
+      float c0 = 0.0f, c1 = 0.0f, c2 = 0.0f;
+      for (int j = 0; j < len; j++) {
+        if (range_select[0] == null || range_select[0][j]) {
+          int k1 = m;
+          int k2 = m;
+          int k3 = m;
+          int k4 = m;
+          int k5 = m;
+          // repeat color 6 times
+          colors[m++] = color_values[0][j];
+          colors[m++] = color_values[1][j];
+          colors[m++] = color_values[2][j];
+          if (c_len == 4)
+            colors[m++] = color_values[3][j];
+          colors[m++] = colors[k1++];
+          colors[m++] = colors[k1++];
+          colors[m++] = colors[k1++];
+          if (c_len == 4)
+            colors[m++] = colors[k1++];
+          colors[m++] = colors[k2++];
+          colors[m++] = colors[k2++];
+          colors[m++] = colors[k2++];
+          if (c_len == 4)
+            colors[m++] = colors[k2++];
+          colors[m++] = colors[k3++];
+          colors[m++] = colors[k3++];
+          colors[m++] = colors[k3++];
+          if (c_len == 4)
+            colors[m++] = colors[k3++];
+          colors[m++] = colors[k4++];
+          colors[m++] = colors[k4++];
+          colors[m++] = colors[k4++];
+          if (c_len == 4)
+            colors[m++] = colors[k4++];
+          colors[m++] = colors[k5++];
+          colors[m++] = colors[k5++];
+          colors[m++] = colors[k5++];
+          if (c_len == 4)
+            colors[m++] = colors[k5++];
+        }
+      }
+      array.colors = colors;
+      // array.vertexFormat |= COLOR_3;
+    }
+
+    // WLH 30 May 2002
+    if (getAdjustProjectionSeam()) {
+      array = (VisADLineArray) array.adjustLongitudeBulk(renderer);
+    }
+
+    return new VisADGeometryArray[] { array };
+  }
+
+  private static final double FONT_SCALE = 0.07;
+
+  /**
+   * abcd - 2 February 2001 Rotate the base and up vectors
+   * 
+   * Rotation is in degrees clockwise from positive X axis
+   */
+  static void rotateVectors(double[] base, double[] up, double rotationDegrees) {
+    double rotation = Data.DEGREES_TO_RADIANS * rotationDegrees;
+    double sinRotation = Math.sin(rotation);
+    double cosRotation = Math.cos(rotation);
+    double[] newBase = new double[3];
+    double[] newUp = new double[3];
+
+    // Check if no rotation is needed
+    if (rotationDegrees == 0.0) {
+      return;
+    }
+
+    // For each axis
+    for (int i = 0; i < 3; i++) {
+      // Rotate the point
+      newBase[i] = cosRotation * base[i] - sinRotation * up[i];
+      newUp[i] = sinRotation * base[i] + cosRotation * up[i];
+    }
+
+    // Copy data back to arrays
+    System.arraycopy(newBase, 0, base, 0, 3);
+    System.arraycopy(newUp, 0, up, 0, 3);
+  }
+
+  public VisADGeometryArray makeText(String[] text_values,
+      TextControl text_control, float[][] spatial_values,
+      byte[][] color_values, boolean[][] range_select) throws VisADException {
+    if (text_values == null || text_values.length == 0 || text_control == null)
+      return null;
+
+    if (spatial_values[0] == null)
+      return null;
+
+    byte r = 0;
+    byte g = 0;
+    byte b = 0;
+    byte a = 0;
+    int color_length = 0;
+    if (color_values != null) {
+      color_length = color_values.length;
+      r = color_values[0][0];
+      g = color_values[1][0];
+      b = color_values[2][0];
+      if (color_length > 3)
+        a = color_values[3][0];
+    }
+
+    int n = text_values.length;
+
+    // CTR 22 Jan 2001
+    if (n > spatial_values[0].length)
+      n = spatial_values[0].length;
+
+    VisADGeometryArray[] as = new VisADGeometryArray[n];
+    // abcd 5 February 2001
+    // boolean center = text_control.getCenter();
+    TextControl.Justification justification = text_control.getJustification();
+    // abcd 19 March 2003
+    TextControl.Justification verticalJustification = text_control
+        .getVerticalJustification();
+    double size = text_control.getSize();
+    Font font = text_control.getFont();
+    HersheyFont hfont = text_control.getHersheyFont();
+    // SL 22 June 2003
+    double rotation = text_control.getRotation();
+    double characterRotation = text_control.getCharacterRotation();
+    double scale = text_control.getScale();
+    double[] offset = text_control.getOffset();
+
+    // WLH 31 May 2000
+    boolean sphere = text_control.getSphere();
+    float[][] spatial_sphere = null;
+    if (sphere) {
+      spatial_sphere = Display.DisplaySphericalCoordSys
+          .fromReference(spatial_values);
+    }
+
+    double[] start = new double[3];
+    double[] base = new double[] { size * FONT_SCALE, 0.0, 0.0 };
+    double[] up = new double[] { 0.0, size * FONT_SCALE, 0.0 };
+
+    // abcd 2 February 2001
+    // This cannot be moved outside the for loop
+    rotateVectors(base, up, text_control.getRotation());
+
+    int k = 0;
+    for (int i = 0; i < n; i++) {
+      if (range_select[0] == null || range_select[0].length == 1
+          || range_select[0][i]) {
+        /*
+         * System.out.println("makeText, i = " + i + " text = " + text_values[i]
+         * + " spatial_values = " + spatial_values[0][i] + " " +
+         * spatial_values[1][i] + " " + spatial_values[2][i]);
+         */
+        if (sphere) {
+          double size_in_radians = (size * FONT_SCALE) / spatial_sphere[2][i];
+          double size_in_degrees = size_in_radians * Data.RADIANS_TO_DEGREES;
+          double lon_size_in_degrees = size_in_degrees
+              / Math.cos(Data.DEGREES_TO_RADIANS * spatial_sphere[0][i]);
+          start = new double[] { spatial_sphere[0][i], spatial_sphere[1][i],
+              spatial_sphere[2][i] };
+          base = new double[] { 0.0, lon_size_in_degrees, 0.0 };
+          up = new double[] { size_in_degrees, 0.0, 0.0 };
+
+          // abcd 2 February 2001
+          // This cannot be moved outside the for loop
+          rotateVectors(base, up, text_control.getRotation());
+
+          if (font != null) {
+            as[k] = PlotText.render_font(text_values[i], font, start, base, up,
+                justification, verticalJustification, characterRotation, scale,
+                offset);
+          } else if (hfont != null) {
+            as[k] = PlotText.render_font(text_values[i], hfont, start, base,
+                up, justification, verticalJustification, characterRotation,
+                scale, offset);
+
+          } else {
+            as[k] = PlotText.render_label(text_values[i], start, base, up,
+                justification, verticalJustification, characterRotation, scale,
+                offset);
+          }
+          int len = (as[k] == null) ? 0 : as[k].coordinates.length;
+          if (len > 0) {
+            float[] coordinates = as[k].coordinates;
+            float[][] cs = new float[3][len / 3];
+            int m = 0;
+            for (int j = 0; j < len / 3; j++) {
+              cs[0][j] = coordinates[m++];
+              cs[1][j] = coordinates[m++];
+              cs[2][j] = coordinates[m++];
+            }
+            cs = Display.DisplaySphericalCoordSys.toReference(cs);
+            m = 0;
+            for (int j = 0; j < len / 3; j++) {
+              coordinates[m++] = cs[0][j];
+              coordinates[m++] = cs[1][j];
+              coordinates[m++] = cs[2][j];
+            }
+            as[k].coordinates = coordinates; // not necessary
+            if (font != null) {
+              float[] normals = as[k].normals;
+              for (int j3 = 0; j3 < len; j3 += 3) {
+                float c = (float) Math.sqrt(coordinates[j3 + 0]
+                    * coordinates[j3 + 0] + coordinates[j3 + 1]
+                    * coordinates[j3 + 1] + coordinates[j3 + 2]
+                    * coordinates[j3 + 2]);
+                float cinv = (c == 0.0f) ? 1.0f : 1.0f / c;
+                normals[j3 + 0] = cinv * coordinates[j3 + 0];
+                normals[j3 + 1] = cinv * coordinates[j3 + 1];
+                normals[j3 + 2] = cinv * coordinates[j3 + 2];
+              }
+              as[k].normals = normals; // not necessary
+            }
+          }
+        } else { // !sphere
+          start = new double[] { spatial_values[0][i], spatial_values[1][i],
+              spatial_values[2][i] };
+          if (font != null) {
+            as[k] = PlotText.render_font(text_values[i], font, start, base, up,
+                justification, verticalJustification, characterRotation, scale,
+                offset);
+
+          } else if (hfont != null) {
+            as[k] = PlotText.render_font(text_values[i], hfont, start, base,
+                up, justification, verticalJustification, characterRotation,
+                scale, offset);
+
+          } else {
+            as[k] = PlotText.render_label(text_values[i], start, base, up,
+                justification, verticalJustification, characterRotation, scale,
+                offset);
+          }
+        }
+
+        int len = (as[k] == null) ? 0 : as[k].coordinates.length;
+        int numPts = len / 3;
+        if (len > 0 && color_values != null) {
+          byte[] colors = new byte[numPts * color_length];
+          if (color_values[0].length > 1) {
+            r = color_values[0][k];
+            g = color_values[1][k];
+            b = color_values[2][k];
+            if (color_length > 3)
+              a = color_values[3][k];
+          }
+          for (int j = 0; j < colors.length; j += color_length) {
+            colors[j] = r;
+            colors[j + 1] = g;
+            colors[j + 2] = b;
+            if (color_length > 3)
+              colors[j + 3] = a;
+          }
+          as[k].colors = colors;
+        }
+        k++;
+      }
+    } // end for (int i=0; i<n; i++)
+    if (k == 0)
+      return null;
+    VisADGeometryArray[] arrays = new VisADGeometryArray[k];
+    System.arraycopy(as, 0, arrays, 0, k);
+    VisADGeometryArray array = null;
+
+    // WLH 30 May 2002
+    DataRenderer renderer = getLink().getRenderer();
+    for (int i = 0; i < k; i++) {
+      if (arrays[i] != null) {
+        if (getAdjustProjectionSeam()) {
+          arrays[i] = arrays[i].adjustLongitudeBulk(renderer);
+        }
+        if (array == null)
+          array = (VisADGeometryArray) arrays[i].clone();
+      }
+    }
+    if (array != null)
+      VisADGeometryArray.merge(arrays, array);
+    return array;
+  }
+
+  /**
+   * composite and transform color and Alpha DisplayRealType values from
+   * display_values, and return as (Red, Green, Blue, Alpha)
+   */
+  public byte[][] assembleColor(float[][] display_values, int valueArrayLength,
+      int[] valueToScalar, DisplayImpl display, float[] default_values,
+      boolean[][] range_select, boolean[] single_missing, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+    float[][] rgba_values = new float[4][];
+    float[] rgba_value_counts = { 0.0f, 0.0f, 0.0f, 0.0f };
+    float[] rgba_singles = new float[4];
+    float[] rgba_single_counts = { 0.0f, 0.0f, 0.0f, 0.0f };
+    float[][] tuple_values = new float[3][];
+    float[] tuple_value_counts = { 0.0f, 0.0f, 0.0f };
+    float[] tuple_singles = new float[3];
+    float[] tuple_single_counts = { 0.0f, 0.0f, 0.0f };
+
+    // mark array to keep track of which valueIndices have
+    // contributed to display color_tuples
+    boolean[] mark = new boolean[valueArrayLength];
+    for (int i = 0; i < valueArrayLength; i++)
+      mark[i] = false;
+
+    // loop to assemble values for each different
+    // display color_tuple
+    while (true) {
+      DisplayTupleType color_tuple = null;
+      for (int i = 0; i < valueArrayLength; i++) {
+        float[] values = display_values[i];
+        if (values != null && !mark[i]) {
+          int len = values.length;
+          int displayScalarIndex = valueToScalar[i];
+          DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+          DisplayTupleType tuple = real.getTuple();
+          // check whether this real is part of a display color tuple
+          if (tuple != null
+              && (tuple.equals(Display.DisplayRGBTuple) || (tuple
+                  .getCoordinateSystem() != null && tuple.getCoordinateSystem()
+                  .getReference().equals(Display.DisplayRGBTuple)))) {
+            if (color_tuple == null || color_tuple.equals(tuple)) {
+              if (color_tuple == null) {
+                // start a new color_tuple
+                color_tuple = tuple;
+                for (int j = 0; j < 3; j++) {
+                  tuple_singles[j] = 0.0f;
+                  tuple_single_counts[j] = 0.0f;
+                  tuple_values[j] = null;
+                  tuple_value_counts[j] = 0.0f;
+                }
+              }
+              int index = real.getTupleIndex();
+              if (len == 1) {
+                tuple_singles[index] += values[0];
+                tuple_single_counts[index]++;
+              } else { // (len != 1)
+                singleComposite(index, tuple_values, tuple_value_counts, values);
+              }
+              // FREE
+              display_values[i] = null; // MEM_WLH 27 March 99
+              mark[i] = true;
+            } // end if (color_tuple == null || color_tuple.equals(tuple))
+          } // end if component of a color tuple
+        } // end if (values != null && !mark[i])
+      } // end for (int i=0; i<valueArrayLength; i++)
+      if (color_tuple != null) {
+        colorSum(3, tuple_values, tuple_value_counts, tuple_singles,
+            tuple_single_counts, display, color_tuple, default_values);
+        if (!color_tuple.equals(Display.DisplayRGBTuple)) {
+          // equalize all rgba_values[index] to same length
+          // and fill with default values
+          equalizeAndDefault(tuple_values, display, color_tuple, default_values);
+          // transform tuple_values to DisplayRGBTuple
+          CoordinateSystem coord = color_tuple.getCoordinateSystem();
+          tuple_values = coord.toReference(tuple_values);
+        }
+        colorComposite(rgba_values, rgba_value_counts, tuple_values);
+      } else { // if (color_tuple == null)
+        // no new color_tuple found on this loop iteration
+        break;
+      }
+    } // end while (true)
+
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+    for (int i = 0; i < valueArrayLength; i++) {
+      float[] values = display_values[i];
+      if (values != null && !mark[i]) {
+        int len = values.length;
+        int displayScalarIndex = valueToScalar[i];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+        if (real.equals(Display.RGB) || real.equals(Display.HSV)
+            || real.equals(Display.CMY)) {
+          ColorControl control = (ColorControl) ((ScalarMap) MapVector
+              .elementAt(valueToMap[i])).getControl();
+          /*
+           * ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+           * System.out.println("map = " + map); int nummissing = 0; for (int
+           * k=0; k<values.length; k++) { if (values[k] != values[k])
+           * nummissing++; } System.out.println("values: nummissing = " +
+           * nummissing);
+           */
+
+          float[][] color_values = control.lookupValues(values);
+
+          /*
+           * nummissing = 0; for (int k=0; k<color_values[0].length; k++) { if
+           * (color_values[0][k] != color_values[0][k]) nummissing++; }
+           * System.out.println("color_values: nummissing = " + nummissing);
+           */
+          if (real.equals(Display.HSV)) {
+            // transform color_values to DisplayRGBTuple
+            color_values = Display.DisplayHSVCoordSys.toReference(color_values);
+          } else if (real.equals(Display.CMY)) {
+            // transform color_values to DisplayRGBTuple
+            color_values = Display.DisplayCMYCoordSys.toReference(color_values);
+          } else if (real.equals(Display.RGB)) {
+            // do nothing
+          } else {
+            throw new DisplayException("unrecognized color CoordinateSsystem: "
+                + "ShadowType.assembleColor");
+          }
+          if (len == 1) {
+            for (int index = 0; index < 3; index++) {
+              rgba_singles[index] += color_values[index][0];
+              rgba_single_counts[index]++;
+            }
+          } else { // (len != 1)
+            colorComposite(rgba_values, rgba_value_counts, color_values);
+          }
+          // FREE
+          display_values[i] = null; // MEM_WLH 27 March 99
+        } // end if (real.equals(Display.RGB) || HSV || CMY)
+        if (real.equals(Display.RGBA)) {
+          ColorAlphaControl control = (ColorAlphaControl) ((ScalarMap) MapVector
+              .elementAt(valueToMap[i])).getControl();
+          float[][] color_values = control.lookupValues(values);
+          if (len == 1) {
+            for (int index = 0; index < 4; index++) {
+              rgba_singles[index] += color_values[index][0];
+              rgba_single_counts[index]++;
+            }
+          } else { // (len != 1)
+            colorComposite(rgba_values, rgba_value_counts, color_values);
+
+            for (int index = 0; index < 4; index++) {
+              singleComposite(index, rgba_values, rgba_value_counts,
+                  color_values[index]);
+              // FREE
+              color_values[index] = null;
+            }
+          }
+          // FREE
+          display_values[i] = null; // MEM_WLH 27 March 99
+        } // end if (real.equals(Display.RGBA))
+        if (real.equals(Display.Alpha)) {
+          if (len == 1) {
+            rgba_singles[3] += values[0];
+            rgba_single_counts[3]++;
+          } else {
+            singleComposite(3, rgba_values, rgba_value_counts, values);
+          }
+          // FREE
+          display_values[i] = null; // MEM_WLH 27 March 99
+        } // end if (real.equals(Display.Alpha))
+        // no need for 'mark[i] = true;' in this loop
+      } // end if (values != null && !mark[i])
+    } // end for (int i=0; i<valueArrayLength; i++)
+    if (rgba_values[0] == null && rgba_values[1] == null
+        && rgba_values[2] == null && rgba_values[3] == null) {
+      // no long color vectors, so try singles, then defaults
+      for (int index = 0; index < 4; index++) {
+        rgba_values[index] = new float[1];
+        if (rgba_single_counts[index] > 0) {
+          rgba_values[index][0] = rgba_singles[index]
+              / rgba_single_counts[index];
+        } else {
+          // nothing mapped to this color component, so use default
+          int default_index = getDefaultColorIndex(display, index);
+          /*
+           * WLH 7 Feb 98 int default_index = index == 0 ?
+           * display.getDisplayScalarIndex(Display.Red) : (index == 1 ?
+           * display.getDisplayScalarIndex(Display.Green) : (index == 2 ?
+           * display.getDisplayScalarIndex(Display.Blue) :
+           * display.getDisplayScalarIndex(Display.Alpha) ) );
+           */
+          rgba_values[index][0] = default_values[default_index];
+        }
+      }
+    } else {
+      colorSum(4, rgba_values, rgba_value_counts, rgba_singles,
+          rgba_single_counts, display, Display.DisplayRGBTuple, default_values);
+      // equalize all rgba_values[index] to same length
+      // and fill with default values
+      equalizeAndDefault(rgba_values, display, Display.DisplayRGBTuple,
+          default_values);
+    }
+
+    // test for any missing values
+    int big_len = rgba_values[0].length;
+    for (int i = 0; i < 4; i++) {
+      int len = rgba_values[i].length;
+      for (int j = 0; j < len; j++) {
+        if (rgba_values[i][j] != rgba_values[i][j]) {
+          if (range_select[0] == null) {
+            range_select[0] = new boolean[big_len];
+            for (int k = 0; k < big_len; k++)
+              range_select[0][k] = true;
+          }
+          if (len > 1) {
+            range_select[0][j] = false;
+            rgba_values[i][j] = 0.0f;
+          } else {
+            for (int k = 0; k < big_len; k++)
+              range_select[0][k] = false;
+            // leave any single color value missing -
+            // this will prevent anything from being rendered
+            // MEM_WLH
+            rgba_values[i][j] = 0.0f;
+            single_missing[i] = true;
+          }
+        }
+      } // end for (int j=0; j<len; j++)
+    } // end for (int i=0; i<4; i++)
+
+    //
+    // TO_DO
+    // should colors be clamped to range (0.0f, 1.0f)?
+    //
+
+    /*
+     * MEM_WLH return rgba_values;
+     */
+    // MEM_WLH
+    // page 291 of Java3D book says byte colors are [0, 255] range
+    byte[][] b = new byte[rgba_values.length][];
+    for (int i = 0; i < rgba_values.length; i++) {
+      if (rgba_values[i] != null) {
+        int len = rgba_values[i].length;
+        b[i] = new byte[len];
+        for (int j = 0; j < len; j++) {
+          int k = (int) (rgba_values[i][j] * 255.0);
+          k = (k < 0) ? 0 : (k > 255) ? 255 : k;
+          b[i][j] = (byte) ((k < 128) ? k : k - 256);
+        }
+      }
+    }
+    return b;
+  }
+
+  public static final float byteToFloat(byte b) {
+    return (b < 0) ? (((float) b) + 256.0f) / 255.0f : ((float) b) / 255.0f;
+    //
+    // no 255.0f divide:
+    // return ((b < 0) ? ((float) b) + 256.0f : ((float) b));
+  }
+
+  public static final byte floatToByte(float f) {
+    /*
+     * int k = (int) (f 255.0); k = (k < 0) ? 0 : (k > 255) ? 255 : k; return
+     * (byte) ((k < 128) ? k : k - 256);
+     */
+    int k = (int) (f * 255.0);
+    return (byte) ((k < 0) ? 0 : ((k > 255) ? -1 : ((k < 128) ? k : k - 256)));
+    //
+    // no 255.0f multiply:
+    // return ((byte) ( ((int) f) < 0 ? 0 : ((int) f) > 255 ? -1 :
+    // ((int) f) < 128 ? ((byte) f) : ((byte) (f - 256.0f)) ));
+  }
+
+  static void colorSum(int nindex, float[][] tuple_values,
+      float[] tuple_value_counts, float[] tuple_singles,
+      float[] tuple_single_counts, DisplayImpl display, DisplayTupleType tuple,
+      float[] default_values) throws VisADException {
+
+    for (int index = nindex - 1; index >= 0; index--) {
+      if (tuple_values[index] == null) {
+        if (tuple_single_counts[index] > 0) {
+          tuple_values[index] = new float[1];
+          tuple_values[index][0] = tuple_singles[index];
+          tuple_value_counts[index] = tuple_single_counts[index];
+        }
+      } else { // (tuple_values[index] != null)
+        // DRM: 2003-09-19 allow for setting by ConstantMap
+        // int cm = display.getGraphicsModeControl().getColorMode();
+        int cm = (int) default_values[display
+            .getDisplayScalarIndex(Display.ColorMode)];
+        /*
+         * DRM: 2005-09-25 default_values now has default from GMC int cm =
+         * (colorMode >= 0) ? colorMode :
+         * display.getGraphicsModeControl().getColorMode();
+         */
+        float inv_count = cm == GraphicsModeControl.SUM_COLOR_MODE ? 1.0f
+            : 1.0f / (tuple_value_counts[index] + tuple_single_counts[index]);
+        float[] t_values = tuple_values[index];
+        for (int j = 0; j < t_values.length; j++) {
+          if (t_values[j] == t_values[j]) {
+            t_values[j] = inv_count * (t_values[j] + tuple_singles[index]);
+          }
+        }
+      }
+    } // end for (int index=0; index<nindex; index++)
+  }
+
+  public static int getDefaultColorIndex(DisplayImpl display, int index) {
+    return index == 0 ? display.getDisplayScalarIndex(Display.Red)
+        : (index == 1 ? display.getDisplayScalarIndex(Display.Green)
+            : (index == 2 ? display.getDisplayScalarIndex(Display.Blue)
+                : display.getDisplayScalarIndex(Display.Alpha)));
+  }
+
+  /** equalize lengths and fill with default values */
+  static void equalizeAndDefault(float[][] tuple_values, DisplayImpl display,
+      DisplayTupleType tuple, float[] default_values) throws VisADException {
+    int nindex = tuple_values.length;
+    // fill any empty tuple_values[index] with default values
+    for (int index = 0; index < nindex; index++) {
+      if (tuple_values[index] == null) {
+        tuple_values[index] = new float[1];
+        int default_index = (index < 3) ? display
+            .getDisplayScalarIndex(((DisplayRealType) tuple.getComponent(index)))
+            : display.getDisplayScalarIndex(Display.Alpha);
+        tuple_values[index][0] = default_values[default_index];
+        /*
+         * System.out.println("default color " + index + " is " +
+         * default_values[default_index]);
+         */
+      }
+    }
+    // compute maximum length of tuple_values[index]
+    int len = 1;
+    for (int index = 0; index < nindex; index++) {
+      len = Math.max(len, tuple_values[index].length);
+    }
+    // entend any tuple_values[index], except Alpha, to maximum length
+    for (int index = 0; index < 3; index++) {
+      int t_len = tuple_values[index].length;
+      if (len > t_len) {
+        if (t_len != 1) {
+          throw new DisplayException(
+              "bad length: ShadowType.equalizeAndDefault");
+        }
+        float[] t = new float[len];
+        float v = tuple_values[index][0];
+        for (int i = 0; i < len; i++)
+          t[i] = v;
+        tuple_values[index] = t;
+      }
+    }
+  }
+
+  /**
+   * composite tuple_values into rgba_values and rgba_value_counts, for index =
+   * 0, 1, 2
+   */
+  static void colorComposite(float[][] rgba_values, float[] rgba_value_counts,
+      float[][] tuple_values) throws VisADException {
+    for (int index = 0; index < 3; index++) {
+      singleComposite(index, rgba_values, rgba_value_counts,
+          tuple_values[index]);
+      // FREE
+      tuple_values[index] = null;
+    }
+  }
+
+  /**
+   * composite values into rgba_values[index] and rgba_value_counts[index]
+   */
+  static void singleComposite(int index, float[][] rgba_values,
+      float[] rgba_value_counts, float[] values) throws VisADException {
+    if (values == null)
+      return;
+    if (rgba_values[index] == null) {
+      rgba_values[index] = values;
+      rgba_value_counts[index] = 1.0f;
+    } else {
+      rgba_value_counts[index]++;
+      int rgba_len = rgba_values[index].length;
+      int values_len = values.length;
+      if (rgba_len == values_len) {
+        for (int j = 0; j < rgba_len; j++) {
+          rgba_values[index][j] += values[j];
+        }
+      } else if (values_len == 1) {
+        for (int j = 0; j < rgba_len; j++) {
+          rgba_values[index][j] += values[0];
+        }
+      } else if (rgba_len == 1) {
+        for (int j = 0; j < rgba_len; j++) {
+          values[j] += rgba_values[index][0];
+        }
+        rgba_values[index] = values;
+      } else {
+        throw new DisplayException("bad length: ShadowType.singleComposite");
+      }
+    }
+  }
+
+  /**
+   * return a composite of SelectRange DisplayRealType values from
+   * display_values, as 0.0 for select and Double.Nan for no select (these
+   * values can be added to other DisplayRealType values)
+   */
+  public boolean[][] assembleSelect(float[][] display_values,
+      int domain_length, int valueArrayLength, int[] valueToScalar,
+      DisplayImpl display, ShadowType shadow_api) throws VisADException {
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+    boolean[][] range_select = new boolean[1][];
+    boolean anySelect = false;
+    for (int i = 0; i < valueArrayLength; i++) {
+      float[] values = display_values[i];
+      if (values != null) {
+        int displayScalarIndex = valueToScalar[i];
+        DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+        if (real.equals(Display.SelectRange)) {
+          if (range_select[0] == null) {
+            // MEM
+            range_select[0] = new boolean[domain_length];
+            for (int j = 0; j < domain_length; j++) {
+              range_select[0][j] = true;
+            }
+          }
+          RangeControl control = (RangeControl) ((ScalarMap) MapVector
+              .elementAt(valueToMap[i])).getControl();
+          float[] range = control.getRange();
+          // System.out.println("range = " + range[0] + " " + range[1]);
+
+          if (values.length == 1) {
+            if (values[0] < range[0] || range[1] < values[0]) {
+              for (int j = 0; j < domain_length; j++) {
+                range_select[0][j] = false;
+              }
+              anySelect = true;
+            }
+          } else {
+            for (int j = 0; j < values.length; j++) {
+              if (values[j] < range[0] || range[1] < values[j]) {
+                range_select[0][j] = false;
+                anySelect = true;
+                /*
+                 * System.out.println("range = " + range[0] + " " + range[1] +
+                 * " values[" + j + "] = " + values[j]);
+                 */
+              }
+            }
+          }
+          // FREE
+          display_values[i] = null; // MEM_WLH 27 March 99
+        } // end if (real.equals(Display.SelectRange))
+      } // end if (values != null)
+    } // end for (int i=0; i<valueArrayLength; i++)
+    if (range_select[0] != null && !anySelect)
+      range_select[0] = null;
+    return range_select;
+  }
+
+  /**
+   * transform data into a (Java3D or Java2D) scene graph; add generated scene
+   * graph components as children of group; group is Group (Java3D) or
+   * VisADGroup (Java2D); value_array are inherited valueArray values;
+   * default_values are defaults for each display.DisplayRealTypeVector; return
+   * true if need post-process
+   */
+  public boolean terminalTupleOrScalar(Object group, float[][] display_values,
+      String text_value, TextControl text_control, int valueArrayLength,
+      int[] valueToScalar, float[] default_values, int[] inherited_values,
+      DataRenderer renderer, ShadowType shadow_api) throws VisADException,
+      RemoteException {
+
+    GraphicsModeControl mode = (GraphicsModeControl) display
+        .getGraphicsModeControl().clone();
+    float pointSize = default_values[display
+        .getDisplayScalarIndex(Display.PointSize)];
+    mode.setPointSize(pointSize, true);
+    float lineWidth = default_values[display
+        .getDisplayScalarIndex(Display.LineWidth)];
+    mode.setLineWidth(lineWidth, true);
+    int lineStyle = (int) default_values[display
+        .getDisplayScalarIndex(Display.LineStyle)];
+    mode.setLineStyle(lineStyle, true);
+    float polygonOffset = default_values[display
+        .getDisplayScalarIndex(Display.PolygonOffset)];
+    mode.setPolygonOffset(polygonOffset, true);
+    float polygonOffsetFactor = default_values[display
+        .getDisplayScalarIndex(Display.PolygonOffsetFactor)];
+    mode.setPolygonOffsetFactor(polygonOffsetFactor, true);
+    float cacheAppearances = default_values[display
+        .getDisplayScalarIndex(Display.CacheAppearances)];
+    mode.setCacheAppearances(cacheAppearances > 0.5f);
+    float mergeArrays = default_values[display
+        .getDisplayScalarIndex(Display.MergeGeometries)];
+    mode.setMergeGeometries(mergeArrays > 0.5f);
+
+    float[][] flow1_values = new float[3][];
+    float[][] flow2_values = new float[3][];
+    float[] flowScale = new float[2];
+    boolean[][] range_select = new boolean[1][];
+    shadow_api.assembleFlow(flow1_values, flow2_values, flowScale,
+        display_values, valueArrayLength, valueToScalar, display,
+        default_values, range_select, renderer, shadow_api);
+
+    if (range_select[0] != null && !range_select[0][0]) {
+      // data not selected
+      return false;
+    }
+
+    boolean[] swap = { false, false, false };
+    int[] spatialDimensions = new int[2];
+    float[][] spatial_values = new float[3][];
+    shadow_api.assembleSpatial(spatial_values, display_values,
+        valueArrayLength, valueToScalar, display, default_values,
+        inherited_values, null, false, false, spatialDimensions, range_select,
+        flow1_values, flow2_values, flowScale, swap, renderer, shadow_api);
+
+    if (range_select[0] != null && !range_select[0][0]) {
+      // data not selected
+      return false;
+    }
+
+    boolean[] single_missing = { false, false, false, false };
+    byte[][] color_values = shadow_api.assembleColor(display_values,
+        valueArrayLength, valueToScalar, display, default_values, range_select,
+        single_missing, shadow_api);
+
+    if (range_select[0] != null && !range_select[0][0]) {
+      // data not selected
+      return false;
+    }
+
+    int LevelOfDifficulty = getLevelOfDifficulty();
+    if (LevelOfDifficulty == SIMPLE_TUPLE) {
+      // only manage Spatial, Color and Alpha here
+      // i.e., the 'dots'
+
+      if (single_missing[0] || single_missing[1] || single_missing[2]) {
+        // System.out.println("single missing alpha");
+        // a single missing color value, so render nothing
+        return false;
+      }
+      // put single color in appearance
+      /*
+       * ColoringAttributes constant_color = new ColoringAttributes();
+       * constant_color.setColor(byteToFloat(color_values[0][0]),
+       * byteToFloat(color_values[1][0]), byteToFloat(color_values[2][0]));
+       */
+      float[] constant_color = new float[] { byteToFloat(color_values[0][0]),
+          byteToFloat(color_values[1][0]), byteToFloat(color_values[2][0]) };
+      float constant_alpha = Float.NaN;
+
+      VisADGeometryArray array;
+
+      boolean anyShapeCreated = false;
+      int[] valueToMap = display.getValueToMap();
+      Vector MapVector = display.getMapVector();
+      VisADGeometryArray[] arrays = shadow_api.assembleShape(display_values,
+          valueArrayLength, valueToMap, MapVector, valueToScalar, display,
+          default_values, inherited_values, spatial_values, color_values,
+          range_select, -1, shadow_api);
+      if (arrays != null) {
+        for (int i = 0; i < arrays.length; i++) {
+          array = arrays[i];
+          if (array != null) {
+            shadow_api.addToGroup(group, array, mode, constant_alpha,
+                constant_color);
+            /*
+             * WLH 25 June 2000 if (renderer.getIsDirectManipulation()) {
+             * renderer.setSpatialValues(spatial_values); }
+             */
+          }
+        }
+        anyShapeCreated = true;
+      }
+
+      boolean anyTextCreated = false;
+      if (text_value != null && text_control != null) {
+        String[] text_values = { text_value };
+        array = shadow_api.makeText(text_values, text_control, spatial_values,
+            color_values, range_select);
+        shadow_api.addTextToGroup(group, array, mode, constant_alpha,
+            constant_color);
+        anyTextCreated = true;
+      }
+
+      boolean anyFlowCreated = false;
+      // try Flow1
+      arrays = shadow_api.makeFlow(0, flow1_values, flowScale[0],
+          spatial_values, color_values, range_select);
+      if (arrays != null) {
+        for (int i = 0; i < arrays.length; i++) {
+          if (arrays[i] != null) {
+            shadow_api.addToGroup(group, arrays[i], mode, constant_alpha,
+                constant_color);
+          }
+        }
+        anyFlowCreated = true;
+      }
+      // try Flow2
+      arrays = shadow_api.makeFlow(1, flow2_values, flowScale[1],
+          spatial_values, color_values, range_select);
+      if (arrays != null) {
+        for (int i = 0; i < arrays.length; i++) {
+          if (arrays[i] != null) {
+            shadow_api.addToGroup(group, arrays[i], mode, constant_alpha,
+                constant_color);
+          }
+        }
+        anyFlowCreated = true;
+      }
+
+      if (!anyFlowCreated && !anyTextCreated && !anyShapeCreated) {
+        array = makePointGeometry(spatial_values, null);
+        if (array != null && array.vertexCount > 0) {
+          shadow_api.addToGroup(group, array, mode, constant_alpha,
+              constant_color);
+          /*
+           * WLH 25 June 2000 if (renderer.getIsDirectManipulation()) {
+           * renderer.setSpatialValues(spatial_values); }
+           */
+        }
+      }
+
+      // WLH 25 June 2000
+      if (renderer.getIsDirectManipulation()) {
+        renderer.setSpatialValues(spatial_values);
+      }
+
+      return false;
+    } else { // if (!(LevelOfDifficulty == SIMPLE_TUPLE))
+      // must be LevelOfDifficulty == LEGAL
+      // add values to value_array according to SelectedMapVector-s
+      // of RealType-s in components (including Reference)
+      //
+      // accumulate Vector of value_array-s at this ShadowTypeJ3D,
+
+      // to be rendered in a post-process to scanning data
+      throw new UnimplementedException("terminal LEGAL unimplemented: "
+          + "ShadowType.terminalTupleOrReal");
+    }
+  }
+
+  public boolean makeContour(int valueArrayLength, int[] valueToScalar,
+      float[][] display_values, int[] inherited_values, Vector MapVector,
+      int[] valueToMap, int domain_length, boolean[][] range_select,
+      int spatialManifoldDimension, Set spatial_set, byte[][] color_values,
+      boolean indexed, Object group, GraphicsModeControl mode, boolean[] swap,
+      float constant_alpha, float[] constant_color, ShadowType shadow_api,
+      ShadowRealTupleType Domain, ShadowRealType[] DomainReferenceComponents,
+      Set domain_set, Unit[] domain_units, CoordinateSystem dataCoordinateSystem)
+      throws VisADException {
+    boolean anyContourCreated = false;
+
+    // WLH 4 May 2001
+    DataRenderer renderer = getLink().getRenderer();
+
+    double[] matrix = p_cntrl.getMatrix();
+    double scale = Double.NaN;
+    double[] scale_a = new double[3];
+    MouseBehavior mouse = display.getMouseBehavior();
+    if (mouse != null) {
+      double[] rot_a = new double[3];
+      double[] trans_a = new double[3];
+      mouse.instance_unmake_matrix(rot_a, scale_a, trans_a, matrix);
+      scale = scale_a[0];
+    }
+
+    /*
+     * try { System.out.println("makeContour " +
+     * getLink().getThingReference().getName()); } catch (RemoteException e) { }
+     */
+
+    boolean isLinearContour3D = getIsLinearContour3D()
+        && spatial_set instanceof Linear3DSet;
+    ScalarMap[] spatial_maps = { null, null, null };
+    int[] permute = { -1, -1, -1 };
+    if (isLinearContour3D) {
+      RealType[] reals = ((SetType) spatial_set.getType()).getDomain()
+          .getRealComponents();
+      for (int i = 0; i < valueArrayLength; i++) {
+        ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
+        ScalarType sc = map.getScalar();
+        RealType real = (sc instanceof RealType) ? (RealType) sc : null;
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+
+        if (tuple != null && tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+          int tuple_index = dreal.getTupleIndex();
+          for (int j = 0; j < reals.length; j++) {
+            if (real.equals(reals[j])) {
+              permute[j] = tuple_index;
+              spatial_maps[j] = map;
+              break;
+            }
+          }
+        }
+      }
+    }
+
+    ShadowRealTupleType domain_reference = null;
+    CoordinateSystem coord_sys = null;
+    boolean sphericalDisplayCS = false;
+    if (spatialTuple != null)
+      coord_sys = spatialTuple.getCoordinateSystem();
+    if (coord_sys != null) {
+      sphericalDisplayCS = coord_sys instanceof SphericalCoordinateSystem;
+    }
+    domain_reference = Domain.getReference();
+    boolean singleValueAsTexture = Boolean.parseBoolean(System.getProperty(PROP_CONTOURFILL_SINGLE_VALUE_AS_TEXTURE, "false"));
+
+    for (int i = 0; i < valueArrayLength; i++) {
+      int displayScalarIndex = valueToScalar[i];
+      DisplayRealType real = display.getDisplayScalar(displayScalarIndex);
+      if (real.equals(Display.IsoContour) && display_values[i] != null
+          && display_values[i].length == domain_length
+          && inherited_values[i] == 0) {
+        // non-inherited IsoContour, so generate contours
+        VisADGeometryArray array = null;
+        ContourControl control = (ContourControl) ((ScalarMap) MapVector
+            .elementAt(valueToMap[i])).getControl();
+        c_cntrl = control;
+
+        boolean[] bvalues = new boolean[2];
+        float[] fvalues = new float[5];
+        control.getMainContours(bvalues, fvalues);
+        boolean doContour = bvalues[0];
+        boolean doLabels = bvalues[1];
+        float isoLvl = fvalues[0];
+
+        if (scale != scale)
+          scale = ContourControl.getInitScale();
+        double label_size = control.getLabelSize();
+        if (spatialManifoldDimension == 3 || spatialManifoldDimension == 2) {
+          anyContourCreated = true;
+        }
+        if (doContour) {
+          if (range_select[0] != null) {
+            int len = range_select[0].length;
+            if (len == 1 || display_values[i].length == 1)
+              break;
+
+            // WLH 30 July 99
+            int dlen = display_values[i].length;
+            float[] temp = display_values[i];
+            display_values[i] = new float[dlen];
+            System.arraycopy(temp, 0, display_values[i], 0, dlen);
+
+            for (int j = 0; j < len; j++) {
+              if (!range_select[0][j]) {
+                display_values[i][j] = Float.NaN;
+              }
+            }
+          }
+          if (spatialManifoldDimension == 3) {
+            if (isoLvl == isoLvl) { // not NaN
+              if (spatial_set != null) {
+                if (isLinearContour3D) {
+                  array = ((Linear3DSet) spatial_set).makeLinearIsoSurface(
+                      isoLvl, display_values[i], color_values, indexed,
+                      spatial_maps, permute);
+                } else {
+                  array = spatial_set.makeIsoSurface(isoLvl, display_values[i],
+                      color_values, indexed);
+                }
+
+                // WLH 4 May 2001
+                if (array != null && getAdjustProjectionSeam()) {
+                  try {
+                    array = array.adjustLongitude(renderer);
+                    array = array.adjustSeam(renderer);
+                  } catch (Exception e) {
+                  }
+                }
+
+                // add all data to group
+                shadow_api.addToGroup(group, array, mode, constant_alpha,
+                    constant_color);
+                array = null;
+              } else if (coord_sys != null) { // missing spatials set as result
+                                              // of transform (coord_sys)
+                array = ((Gridded3DSet) domain_set)
+                    .makeIsoSurfaceMissingSpatial(isoLvl, display_values[i],
+                        color_values, indexed, Domain, domain_reference,
+                        domain_units, dataCoordinateSystem, coord_sys,
+                        DomainReferenceComponents, spatialTuple,
+                        spatial_offset_values);
+                if (array != null) {
+                  array = array.removeMissing();
+                }
+                shadow_api.addToGroup(group, array, mode, constant_alpha,
+                    constant_color);
+                array = null;
+              }
+            }
+            // anyContourCreated = true;
+          } else if (spatialManifoldDimension == 2) {
+            if (spatial_set != null) {
+
+              float[] lowhibase = new float[3];
+              boolean[] doStyle = { false };
+              float[] levs = control.getLevels(lowhibase, doStyle);
+
+              boolean fill = control.contourFilled();
+              ScalarMap[] smap = new ScalarMap[2]; // changed to 2 to pass
+                                                   // IsoContour Map to Set
+              ScalarType sc = ((ScalarMap) MapVector.elementAt(valueToMap[i]))
+                  .getScalar();
+              if (fill) {
+                for (int kk = 0; kk < MapVector.size(); kk++) {
+                  ScalarMap sm = (ScalarMap) MapVector.elementAt(kk);
+                  if (sm != null) {
+                    if (sm.getScalar().equals(sc)
+                        && (sm.getDisplayScalar().equals(Display.RGB) || sm
+                            .getDisplayScalar().equals(Display.RGBA))) {
+                      smap[0] = sm;
+                    }
+                  }
+                }
+                if (smap[0] == null) {
+                  throw new DisplayException(
+                      "IsoContour color-fill is enabled, so " + sc
+                          + " must also be mapped to Display.RGB");
+                }
+              }
+
+              // BMF 2006-10-04 get the IsoContour ScalarMap
+              for (int kk = 0; kk < MapVector.size(); kk++) {
+                ScalarMap sm = (ScalarMap) MapVector.elementAt(kk);
+                if (sm != null) {
+                  if (sm.getScalar().equals(sc)
+                      && sm.getDisplayScalar().equals(Display.IsoContour)) {
+                    smap[1] = sm;
+                  }
+                }
+              }
+              float maxValue = Float.NEGATIVE_INFINITY;
+              float minValue = Float.POSITIVE_INFINITY;
+              boolean haveSingleValue = false;
+              // if the singleValueAsTexture is true, loop over the values
+              visad.util.Trace.call1("ShadowType:isSingleValue");
+              if (fill && singleValueAsTexture) {
+                int dlen = display_values[i].length;
+                for (int j = 0; j < dlen; j++) {
+                  if (display_values[i][j] > maxValue)
+                    maxValue = display_values[i][j];
+                  if (display_values[i][j] < minValue)
+                    minValue = display_values[i][j];
+                }
+                haveSingleValue = (maxValue != Float.NEGATIVE_INFINITY &&
+                               minValue !=Float.POSITIVE_INFINITY &&
+                               maxValue == minValue);
+              }
+              visad.util.Trace.call2("ShadowType:isSingleValue");
+              // if we are filling and we have a single value, don't try
+              // to contour and return false;
+              if (haveSingleValue && singleValueAsTexture) return false;
+
+              visad.util.Trace.call1("ShadowType:makeIsoLines");
+              VisADGeometryArray[][] array_s = spatial_set.makeIsoLines(levs,
+                  lowhibase[0], lowhibase[1], lowhibase[2], display_values[i],
+                  color_values, swap, doStyle[0], fill, smap, scale_a,
+                  label_size, sphericalDisplayCS);
+              visad.util.Trace.call2("ShadowType:makeIsoLines");
+
+              // even though no contours were created, we at least tried
+              // so have to return true.
+              if (array_s == null) return anyContourCreated;
+                boolean adjust = getAdjustProjectionSeam();
+                // make necessary adjustments
+                for (int j = 0; j < array_s.length; j++) {
+                   if (array_s[j] != null) {
+                      for (int k=0; k< array_s[j].length; k++) {
+                        VisADGeometryArray arr = array_s[j][k];
+                        if (arr != null) {
+                          if (adjust) {
+                            arr = arr.adjustLongitude(renderer);
+                            arr = arr.adjustSeam(renderer);
+                          }
+                          arr = arr.removeMissing();
+                        }
+                        array_s[j][k] = arr;
+                      }
+                   }
+                }
+
+                VisADGeometryArray[] uBasicLines = array_s[0];
+                VisADGeometryArray[] fillLines = array_s[1];
+                VisADGeometryArray[] labelLines = null;
+                VisADGeometryArray[] sBasicLines = null;
+
+                if (array_s != null) {
+                  // set'em if you got em
+                  switch (array_s.length) {
+                  case 4:
+                    sBasicLines = array_s[3];
+                  case 3:
+                    labelLines = array_s[2];
+                  }
+
+                //if (array_s.length > 0 && uBasicLines.length > 0) {
+                if (array_s.length > 0) {
+
+                  // label mode, forcing labels to have solid J3D line style
+                  GraphicsModeControl labelMode 
+                    = (GraphicsModeControl) mode.clone();
+                  labelMode.setLineStyle(GraphicsModeControl.SOLID_STYLE, false);
+
+                  // mode for dashed lines rendered with J3D line style
+                  GraphicsModeControl styledMode 
+                    = (GraphicsModeControl) mode.clone();
+                  styledMode.setLineStyle(control.getDashedStyle(), false);
+
+                  if (fill) {
+                    // BMF set offset to make labels more clear.
+                    // FIXME: There may be a better value to use here
+                    labelMode.setPolygonOffsetFactor(10f, false);
+                    labelMode.setPolygonOffset(1f, false);
+
+                    // make adjustment for basic lines
+                    if (uBasicLines != null) {
+                      for (VisADGeometryArray arr : uBasicLines) {
+                        if (arr == null)
+                          continue;
+                        shadow_api.adjustZ(arr.coordinates);
+                      }
+                    }
+                    // there may not be unstyled lines
+                    if (sBasicLines != null) {
+                      for (VisADGeometryArray arr : sBasicLines) {
+                        if (arr == null)
+                          continue;
+                        shadow_api.adjustZ(arr.coordinates);
+                      }
+                    }
+                  }
+
+                  // add unstyled lines
+                  if (uBasicLines != null) {
+                    for (VisADGeometryArray arr : uBasicLines) {
+                      if (arr == null)
+                        continue;
+                      shadow_api.addToGroup(group, arr, mode, constant_alpha,
+                          constant_color);
+                    }
+                  }
+                  array_s[0] = null;
+                  uBasicLines = null;
+
+                  // add styled lines
+                  if (sBasicLines != null) {
+                    for (VisADGeometryArray arr : sBasicLines) {
+                      if (arr == null)
+                        continue;
+                      shadow_api.addToGroup(group, arr, styledMode,
+                          constant_alpha, constant_color);
+                    }
+                    sBasicLines = null;
+                    array_s[3] = null;
+                  }
+
+                  if (doLabels && labelLines != null) {
+                    shadow_api.addLabelsToGroup(group, labelLines, labelMode,
+                        control, p_cntrl, cnt, constant_alpha, constant_color);
+                    labelLines = null;
+                    array_s[2] = null;
+
+                  } else if (!doLabels && fillLines != null) {
+                    // fill in contour lines in place of labels
+                    shadow_api.addToGroup(group, fillLines[0], mode,
+                        constant_alpha, constant_color);
+                    if (fillLines.length == 2) {  //- styled lines available
+                      shadow_api.addToGroup(group, fillLines[1], styledMode,
+                         constant_alpha, constant_color);
+                      fillLines[1] = null;
+                    }
+                    fillLines[0] = null;
+                    array_s[1] = null;
+                  }
+                  array_s = null;
+                }
+              }
+            } // end if (spatial_set != null)
+            // anyContourCreated = true;
+          } // end if (spatialManifoldDimension == 2)
+        } // end if (bvalues[CONTOUR])
+      } // end if (real.equals(Display.IsoContour) && not inherited)
+    } // end for (int i=0; i<valueArrayLength; i++)
+
+    return anyContourCreated;
+  }
+
+  public int textureWidth(int data_width) {
+    return data_width;
+  }
+
+  public int textureHeight(int data_height) {
+    return data_height;
+  }
+
+  public int textureDepth(int data_depth) {
+    return data_depth;
+  }
+
+  public void adjustZ(float[] coordinates) {
+  }
+
+  public void setTexCoords(float[] texCoords, float ratiow, float ratioh) {
+  }
+
+  public float[] setTex3DCoords(int length, int axis, float ratiow,
+      float ratioh, float ratiod) {
+    return null;
+  }
+
+  public float[] setTexStackCoords(int length, int axis, float ratiow,
+      float ratioh, float ratiod) {
+    return null;
+  }
+
+  public Vector getTextMaps(int i, int[] textIndices) {
+    return new Vector();
+  }
+
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+      GraphicsModeControl mode, float constant_alpha, float[] constant_color)
+      throws VisADException {
+    return false;
+  }
+
+  //public void addLabelsToGroup(Object group, VisADGeometryArray[][] arrays,
+  public void addLabelsToGroup(Object group, VisADGeometryArray[] arrays,
+      GraphicsModeControl mode, ContourControl control,
+      ProjectionControl p_cntrl, int[] cnt, float constant_alpha,
+      float[] contstant_color) throws VisADException {
+  }
+
+  public boolean addTextToGroup(Object group, VisADGeometryArray array,
+      GraphicsModeControl mode, float constant_alpha, float[] constant_color)
+      throws VisADException {
+    return addToGroup(group, array, mode, constant_alpha, constant_color);
+  }
+
+  public void textureToGroup(Object group, VisADGeometryArray array,
+      BufferedImage image, GraphicsModeControl mode, float constant_alpha,
+      float[] constant_color, int texture_width, int texture_height)
+      throws VisADException {
+  }
+
+  public void texture3DToGroup(Object group, VisADGeometryArray arrayX,
+      VisADGeometryArray arrayY, VisADGeometryArray arrayZ,
+      VisADGeometryArray arrayXrev, VisADGeometryArray arrayYrev,
+      VisADGeometryArray arrayZrev, BufferedImage[] images,
+      GraphicsModeControl mode, float constant_alpha, float[] constant_color,
+      int texture_width, int texture_height, int texture_depth,
+      DataRenderer renderer) throws VisADException {
+  }
+
+  public void textureStackToGroup(Object group, VisADGeometryArray arrayX,
+      VisADGeometryArray arrayY, VisADGeometryArray arrayZ,
+      VisADGeometryArray arrayXrev, VisADGeometryArray arrayYrev,
+      VisADGeometryArray arrayZrev, BufferedImage[] imagesX,
+      BufferedImage[] imagesY, BufferedImage[] imagesZ,
+      GraphicsModeControl mode, float constant_alpha, float[] constant_color,
+      int texture_width, int texture_height, int texture_depth,
+      DataRenderer renderer) throws VisADException {
+  }
+
+  public Object makeSwitch() {
+    return null;
+  }
+
+  public Object makeBranch() {
+    return null;
+  }
+
+  public void addToGroup(Object group, Object branch) throws VisADException {
+  }
+
+  public void addToSwitch(Object swit, Object branch) throws VisADException {
+  }
+
+  public void addSwitch(Object group, Object swit, Control control,
+      Set domain_set, DataRenderer renderer) throws VisADException {
+  }
+
+  public boolean recurseRange(Object group, Data data, float[] value_array,
+      float[] default_values, DataRenderer renderer) throws VisADException,
+      RemoteException {
+    return false;
+  }
+
+  public boolean recurseComponent(int i, Object group, Data data,
+      float[] value_array, float[] default_values, DataRenderer renderer)
+      throws VisADException, RemoteException {
+    return false;
+  }
+
+  public boolean wantIndexed() {
+    return false;
+  }
+
+  public TextControl getParentTextControl() {
+    return null;
+  }
+
+  public String getParentText() {
+    return null;
+  }
+
+  public void setText(String text, TextControl control) {
+  }
+
+  public boolean allowCurvedTexture() {
+    return true;
+  }
+
+  public boolean allowConstantColorSurfaces() {
+    return true;
+  }
+
+  public boolean allowLinearContour() {
+    return true;
+  }
+
+  public String toString() {
+    return getClass() + " for \n  " + Type.toString();
+    // return " LevelOfDifficulty = " + LevelOfDifficulty;
+  }
+
+}
diff --git a/visad/ShapeControl.java b/visad/ShapeControl.java
new file mode 100644
index 0000000..11694a8
--- /dev/null
+++ b/visad/ShapeControl.java
@@ -0,0 +1,373 @@
+//
+// ShapeControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+import visad.util.Util;
+
+/**
+   ShapeControl is the VisAD class for controlling Shape display scalars.<P>
+*/
+public class ShapeControl extends Control {
+
+  private SimpleSet shapeSet = null;
+  private VisADGeometryArray[] shapes = null;
+
+  // WLH 31 May 2000
+  private float scale = 1.0f;
+
+  // WLH 6 Aug 2001
+  private boolean autoScale = false;
+  private ProjectionControlListener pcl = null;
+
+  public ShapeControl(DisplayImpl d) {
+    super(d);
+  }
+
+  /**
+   * Sets the SimpleSet that defines the mapping from RealType values to indices
+   * into an array of shapes.  This method <em>must be called before</em> the
+   * setShapes() method to avoid a subsequent NullPointerException in the
+   * running of the display.
+   *
+   * @param set			1-D set of values that defines the mapping from
+   *				RealType values to indices into the array of
+   *				shapes.  The domain dimension shall be 1.
+   */
+  public synchronized void setShapeSet(SimpleSet set)
+         throws VisADException, RemoteException {
+    if (set == null) {
+      shapeSet = null;
+      shapes = null;
+      return;
+    }
+    if (set.getDimension() != 1) {
+      throw new DisplayException("ShapeControl.setShapeSet: domain " +
+                                 "dimension must be 1");
+    }
+    shapeSet = set;
+    shapes = new VisADGeometryArray[shapeSet.getLength()];
+    changeControl(true);
+  }
+
+  /** set the shape associated with index;
+      the VisADGeometryArray class hierarchy defines various
+      kinds of shapes */
+  public synchronized void setShape(int index, VisADGeometryArray shape)
+         throws VisADException, RemoteException {
+    if (shapes == null) return;
+    if (0 <= index && index < shapes.length) {
+      shapes[index] = shape;
+    }
+    changeControl(true);
+  }
+
+  /**
+   * Sets the array of shapes.  The array is accessed by index determined from
+   * the value-to-index mapping established by the <code>setShapeSet()</code>
+   * method.  The VisADGeometryArray class hierarchy defines various kinds of
+   * shapes.  This method <em>must be called after</em> the setShapeSet()
+   * method to avoid a subsequent NullPointerException in the running of the
+   * display.
+   *
+   * @param shs			The array of shapes.
+   */
+  public synchronized void setShapes(VisADGeometryArray[] shs)
+         throws VisADException, RemoteException {
+    if (shapeSet == null) return;
+    if (shs != null && shs.length > 0) {
+      final int len = Math.min(shs.length, shapes.length);
+      for (int i=0; i<len; i++) {
+        shapes[i] = shs[i];
+      }
+      if (shapes.length > shs.length) {
+        for (int i=shs.length; i < shapes.length; i++) {
+          shapes[i] = null;
+        }
+      }
+    }
+    changeControl(true);
+  }
+
+  public synchronized VisADGeometryArray[] getShapes(float[] values)
+         throws VisADException {
+    if (values == null || values.length < 1) return null;
+    VisADGeometryArray[] sh = new VisADGeometryArray[values.length];
+    if (shapeSet == null) return sh;
+    float[][] set_values = new float[1][];
+    set_values[0] = values;
+    int[] indices = null;
+    if (shapeSet.getLength() < 2) {
+      indices = new int[values.length];
+      for (int i=0; i<indices.length; i++) indices[i] = 0;
+    }
+    else {
+      indices = shapeSet.valueToIndex(set_values);
+    }
+    if (shapes == null) {
+      for (int i=0; i<indices.length; i++) sh[i] = null;
+    }
+    else {
+      for (int i=0; i<indices.length; i++) {
+        if (0 <= indices[i] && indices[i] < shapes.length &&
+            shapes[indices[i]] != null)
+        {
+          sh[i] = (VisADGeometryArray) shapes[indices[i]].clone();
+        }
+        else {
+          sh[i] = null;
+        }
+      }
+    }
+    return sh;
+  }
+
+  // WLH 31 May 2000
+  public void setScale(float s)
+         throws VisADException, RemoteException {
+    if (s == s) {
+      scale = s;
+      changeControl(true);
+    }
+  }
+
+  // WLH 31 May 2000
+  public float getScale() {
+    return scale;
+  }
+
+  private boolean shapeSetEquals(SimpleSet newShapeSet)
+  {
+    if (shapeSet == null) {
+      if (newShapeSet != null) {
+        return false;
+      }
+    } else if (newShapeSet == null) {
+      return false;
+    } else if (!shapeSet.equals(newShapeSet)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  private boolean shapesEquals(VisADGeometryArray[] newShapes)
+  {
+    if (shapes == null) {
+      if (newShapes != null) {
+        return false;
+      }
+    } else if (newShapes == null) {
+      return false;
+    } else {
+      if (shapes.length != newShapes.length) {
+        return false;
+      }
+
+      for (int i = 0; i < shapes.length; i++) {
+        if (shapes[i] == null) {
+          if (newShapes[i] != null) {
+            return false;
+          }
+        } else if (newShapes[i] == null) {
+          return false;
+        } else if (!shapes[i].equals(newShapes[i])) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /** get a string that can be used to reconstruct this control later */
+  public String getSaveString() {
+    return null;
+  }
+
+  /** reconstruct this control using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    throw new UnimplementedException(
+      "Cannot setSaveString on this type of control");
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof ShapeControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    ShapeControl sc = (ShapeControl )rmt;
+
+    boolean changed = false;
+
+    if (!shapeSetEquals(sc.shapeSet)) {
+      changed = true;
+      shapeSet = sc.shapeSet;
+    }
+
+    if (!shapesEquals(sc.shapes)) {
+      changed = true;
+      shapes = sc.shapes;
+    }
+
+    // WLH 31 May 2000
+    if (!Util.isApproximatelyEqual(scale, sc.scale)) {
+      changed = true;
+      scale = sc.scale;
+    }
+
+    // WLH 6 Aug 2001
+    if (autoScale != sc.autoScale) {
+      // changed = true;
+      setAutoScale(sc.autoScale);
+    }
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  public void setAutoScale(boolean auto)
+         throws VisADException {
+    if (auto == autoScale) return;
+    DisplayImpl display = getDisplay();
+    DisplayRenderer dr = display.getDisplayRenderer();
+    MouseBehavior mouse = dr.getMouseBehavior();
+    ProjectionControl pc = display.getProjectionControl();
+    if (auto) {
+      pcl = new ProjectionControlListener(mouse, this, pc);
+      pc.addControlListener(pcl);
+    }
+    else {
+      pc.removeControlListener(pcl);
+    }
+    autoScale = auto;
+    try {
+      changeControl(true);
+    }
+    catch (RemoteException e) {
+    }
+  }
+
+  public void nullControl() {
+    try {
+      setAutoScale(false);
+    }
+    catch (VisADException e) {
+    }
+    super.nullControl();
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    ShapeControl sc = (ShapeControl )o;
+
+    if (!shapeSetEquals(sc.shapeSet) || !shapesEquals(sc.shapes)) {
+      return false;
+    }
+
+    // WLH 31 May 2000
+    if (!Util.isApproximatelyEqual(scale, sc.scale)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  public Object clone()
+  {
+    ShapeControl sc = (ShapeControl )super.clone();
+    if (shapes != null) {
+      sc.shapes = (VisADGeometryArray[] )shapes.clone();
+    }
+    sc.scale = scale;
+    return sc;
+  }
+
+
+  class ProjectionControlListener implements ControlListener {
+    private boolean pfirst = true;
+    private MouseBehavior mouse;
+    private ProjectionControl pcontrol;
+    private ShapeControl shapeControl;
+    private double base_scale = 1.0;
+    private float last_cscale = 1.0f;
+    private double base_size = 1.0;
+
+    ProjectionControlListener(MouseBehavior m, ShapeControl s,
+                              ProjectionControl p) {
+      mouse = m;
+      shapeControl = s;
+      pcontrol = p;
+    }
+
+    public void controlChanged(ControlEvent e)
+           throws VisADException, RemoteException {
+      double[] matrix = pcontrol.getMatrix();
+      double[] rot = new double[3];
+      double[] scale = new double[3];
+      double[] trans = new double[3];
+      mouse.instance_unmake_matrix(rot, scale, trans, matrix);
+
+      if (pfirst) {
+        pfirst = false;
+        base_scale = scale[0];
+        last_cscale = 1.0f;
+        base_size = shapeControl.getScale();
+      }
+      else {
+        float cscale = (float) (base_scale / scale[0]);
+        float ratio = cscale / last_cscale;
+        if (ratio < 0.95f || 1.05f < ratio) {
+          last_cscale = cscale;
+          shapeControl.setScale((float) base_size * cscale);
+        }
+      }
+    }
+  }
+}
diff --git a/visad/SimpleSet.java b/visad/SimpleSet.java
new file mode 100644
index 0000000..40c5201
--- /dev/null
+++ b/visad/SimpleSet.java
@@ -0,0 +1,126 @@
+//
+// SimpleSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+
+/**
+ * SimpleSet is the abstract superclass of Sets with a unique ManifoldDimension.
+ * For the most part, SimpleSet objects are immutable (but see {@link 
+ * SampledSet#getSamples(boolean)}).
+ */
+public abstract class SimpleSet extends Set implements SimpleSetIface {
+
+  /** dimension of subspace that set is embedded in */
+  int ManifoldDimension;
+
+  public SimpleSet(MathType type, int manifold_dimension) throws VisADException {
+    this(type, manifold_dimension, null, null, null);
+/* WLH 17 Oct 97
+    super(type);
+    ManifoldDimension = manifold_dimension;
+    if (ManifoldDimension > DomainDimension ||
+        ManifoldDimension < 0) {
+      throw new SetException("SimpleSet: bad ManifoldDimension" +
+                             ManifoldDimension);
+    }
+*/
+  }
+
+  public SimpleSet(MathType type, int manifold_dimension,
+                   CoordinateSystem coord_sys, Unit[] units,
+                   ErrorEstimate[] errors)
+         throws VisADException {
+    super(type, coord_sys, units, errors);
+    ManifoldDimension = manifold_dimension;
+    if (ManifoldDimension > DomainDimension ||
+        ManifoldDimension < 0) {
+      throw new SetException("SimpleSet: bad ManifoldDimension" +
+                             ManifoldDimension);
+    }
+  }
+
+  public SimpleSet(MathType type) throws VisADException {
+    this(type, null, null, null);
+  }
+
+  public SimpleSet(MathType type, CoordinateSystem coord_sys, Unit[] units,
+                   ErrorEstimate[] errors) throws VisADException {
+    super(type, coord_sys, units, errors);
+    ManifoldDimension = DomainDimension;
+  }
+
+  /** get ManifoldDimension */
+  public int getManifoldDimension() {
+    return ManifoldDimension;
+  }
+
+  /** domain == true is this is the domain of a Field */
+  void setAnimationSampling(ShadowType type, DataShadow shadow, boolean domain)
+       throws VisADException {
+    if (shadow.isAnimationSampling(domain)) return;
+    if (DomainDimension != 1) return;
+    ShadowRealType real;
+    if (type instanceof ShadowRealType) {
+      real = (ShadowRealType) type;
+    }
+    else if (type instanceof ShadowSetType) {
+      ShadowRealTupleType tuple = ((ShadowSetType) type).getDomain();
+      if (tuple.getDimension() != 1) return; // should throw an Exception
+      real = (ShadowRealType) tuple.getComponent(0);
+    }
+    else if (type instanceof ShadowRealTupleType) {
+      // should throw an Exception
+      if (((ShadowRealTupleType) type).getDimension() != 1) return;
+      real = (ShadowRealType) ((ShadowRealTupleType) type).getComponent(0);
+    }
+    else {
+      return;
+    }
+    Enumeration maps = real.getSelectedMapVector().elements();
+    while (maps.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      if (map.getDisplayScalar().equals(Display.Animation)) {
+        shadow.setAnimationSampling(this, domain);
+        return;
+      }
+    }
+  }
+
+  /** convert an array of values to arrays of indices and weights for
+      those indices, appropriate for interpolation; the values array is
+      organized as float[domain_dimension][number_of_values]; indices
+      and weights must be passed in as int[number_of_values][] and
+      float[number_of_values][]; on return, quantity( values[.][i] )
+      can be estimated as the sum over j of
+      weights[i][j] * quantity (sample at indices[i][j]);
+      no estimate possible if indices[i] and weights[i] are null */
+  public abstract void valueToInterp(float[][] value, int[][] indices,
+                          float weights[][]) throws VisADException;
+
+}
+
diff --git a/visad/SimpleSetIface.java b/visad/SimpleSetIface.java
new file mode 100644
index 0000000..b8e6133
--- /dev/null
+++ b/visad/SimpleSetIface.java
@@ -0,0 +1,55 @@
+//
+// SimpleSetIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Interface to the abstract superclass of Sets with a unique ManifoldDimension.
+ */
+public interface SimpleSetIface
+  extends SetIface
+{
+  /**
+   * Returns the interpolation parameters for an array of points.
+   *
+   * @param values              An array of points. <code>value[i][j]</code> is
+   *                            the i-th coordinate of the j-th points.
+   * @param indices             Indices of the neighboring samples in the set.
+   *                            If the j-th points lies within the set, then
+   *                            returned element [i][j] is the index of the
+   *                            i-th neighboring sample in the set; otherwise,
+   *                            returned array [j] is <code>null</code>.
+   * @param weights             Weights for interpolating the neighboring
+   *                            samples in the set.  If the j-th points lies
+   *                            within the set, then returned element [i][j]
+   *                            is the weight of the i-th neighboring sample
+   *                            in the set; otherwise, returned array [j] is
+   *                            <code>null</code>.
+   * @throws VisADException	VisAD failure.
+   */
+  void valueToInterp(float[][] values, int[][] indices, float[][] weights)
+    throws VisADException;
+}
diff --git a/visad/SingletonSet.java b/visad/SingletonSet.java
new file mode 100644
index 0000000..c1ebb5e
--- /dev/null
+++ b/visad/SingletonSet.java
@@ -0,0 +1,368 @@
+//
+// SingletonSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   SingletonSet is the class for Set-s containing one member.<P>
+*/
+public class SingletonSet extends SampledSet {
+
+  private static final long serialVersionUID = 1L;
+  private RealTuple data;
+  
+  private static final int[] nilNeighbors = {};
+
+  /**  
+   * Construct a SingletonSet with the single sample given by a RealTuple 
+   *
+   * @param d  sample as a RealTuple
+   * @throws  VisADException  Couldn't create the necessary VisAD object
+   * @throws  RemoteException  Couldn't create the necessary remote object
+   */
+  public SingletonSet(RealTuple d) throws VisADException, RemoteException {
+    this(d, d.getType(), null, d.getTupleUnits(), null);
+  }
+
+  /**  
+   * Construct a SingletonSet with the single sample given by a RealTuple,
+   * and a non-default CoordinateSystem, Units and ErrorEstimates.
+   *
+   * @param d  sample as a RealTuple
+   * @param coord_sys  CoordinateSystem
+   * @param units   Units
+   * @param errors  ErrorEstimate
+   *
+   * @throws  VisADException  Couldn't create the necessary VisAD object
+   * @throws  RemoteException  Couldn't create the necessary remote object
+   */
+  public SingletonSet(RealTuple d, CoordinateSystem coord_sys, Unit[] units,
+                      ErrorEstimate[] errors)
+         throws VisADException, RemoteException {
+    this(d, d.getType(), coord_sys, units, errors);
+  }
+
+  /** construct a SingletonSet with a different MathType than its
+      RealTuple argument, and a non-default CoordinateSystem */
+  private SingletonSet(RealTuple d, MathType type, CoordinateSystem coord_sys,
+                       Unit[] units, ErrorEstimate[] errors)
+          throws VisADException, RemoteException {
+    super(type, 0, coord_sys, units, errors); // ManifoldDimension = 0
+    int dim = d.getDimension();
+    float[][] samples = new float[dim][1];
+    for (int k=0; k<dim; k++) {
+      samples[k][0] = (float) (((Real) d.getComponent(k)).getValue());
+    }
+    init_samples(samples);
+    data = d;
+    Length = 1;
+    for (int j=0; j<DomainDimension; j++) {
+      if (SetErrors[j] != null ) {
+        SetErrors[j] =
+          new ErrorEstimate(SetErrors[j].getErrorValue(),
+                            ((Real) data.getComponent(j)).getValue(), 1,
+                            SetErrors[j].getUnit());
+      }
+    }
+  }
+
+  /**
+   * Constructs from a type, numeric values, units, coordinate system, and
+   * error estimates.
+   *
+   * @param type                  The type for this instance.
+   * @param values                The numeric values in units of the <code>
+   *                              units</code> argument (if 
+   *                              non-<code>null</code>); otherwise, in units of
+   *                              the coordinate system argument (if non-
+   *                              <code>null</code>); otherwise, in the
+   *                              default units of the type argument.
+   * @param coordSys              The coordinate system transformation for this
+   *                              instance or <code>null</code>.
+   * @param units                 The units of the numeric values or <code>
+   *                              null</code>.
+   * @param errors                Error estimates for the values or <code>
+   *                              null</code>.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   * @throws NullPointerException if the type or values argument is
+   *                              <code>null</code>.
+   * @see CoordinateSystem#getCoordinateSystemUnits()
+   */
+  public SingletonSet(RealTupleType type, double[] values,
+    CoordinateSystem coordSys, Unit[] units, ErrorEstimate[] errors)
+      throws VisADException, RemoteException {
+
+    super(type, 0, coordSys, units, errors); // ManifoldDimension = 0
+
+    int             dim = type.getDimension();
+    float[][]       samples = new float[dim][1];
+    Real[]          reals = new Real[dim];
+    
+    units = getSetUnits();
+    errors = getSetErrors();
+
+    for (int k=0; k<dim; k++) {
+      samples[k][0] = (float)values[k];
+      reals[k] = new Real(
+	(RealType)type.getComponent(k), values[k], units[k], errors[k]);
+    }
+
+    init_samples(samples);
+
+    data = new RealTuple(type, reals, coordSys);
+    Length = 1;
+  }
+
+  /**
+   * Check if the samples are missing.
+   *
+   * @return false by definition, a SingletonSet never has missing samples
+   *               since it is initialized with a RealTuple..
+   */
+  public boolean isMissing() {
+    return false;
+  }
+  
+  /**
+   * <p>Returns an array of indexes for neighboring points for each point in the
+   * set.  If <code>neighbors</code> is the return value, then
+   * <code>neighbors[i][j]</code> is the index of the <code>j</code>th 
+   * neighbor of sample <code>i</code>.</p>
+   *
+   * <p>This implementation places an array of length zero into the single
+   * element of the input array.  The length is zero because instances of this
+   * class have no neighboring sample points.</p>
+   *
+   * @param neighbors                The array to hold the indicial arrays for
+   *                                 each sample point.
+   * @throws ArrayIndexOutOfBoundsException
+   *                                 if the length of the argument array is
+                                     zero.
+   */
+  public void getNeighbors(int[][] neighbors) {
+    neighbors[0] = nilNeighbors;
+  }
+
+  /** 
+   * convert an array of 1-D indices to an array of values in R^DomainDimension 
+   *
+   * @return float array of values
+   * @throws VisADException  couldn't create the necessary VisAD object
+   */
+  public float[][] indexToValue(int[] index)
+         throws VisADException {
+    return Set.doubleToFloat(indexToDouble(index));
+  }
+
+  /** 
+   * convert an array of 1-D indices to an array of doubles in R^DomainDimension
+   *
+   * @return double array of values
+   * @throws VisADException  couldn't create the necessary VisAD object
+   */
+  public double[][] indexToDouble(int[] index)
+         throws VisADException {
+    int length = index.length;
+    double[][] value = new double[DomainDimension][length];
+    double[] v = new double[DomainDimension];
+    for (int k=0; k<DomainDimension; k++) {
+      try {
+        v[k] = ((Real) data.getComponent(k)).getValue();
+      }
+      catch (RemoteException e) {
+        v[k] = Double.NaN;
+      }
+    }
+    for (int i=0; i<length; i++) {
+      if (index[i] < 0 || index[i] >= Length) {
+        for (int k=0; k<DomainDimension; k++) {
+          value[k][i] = Double.NaN;
+        }
+      }
+      else {
+        for (int k=0; k<DomainDimension; k++) {
+          value[k][i] = v[k];
+        }
+      }
+    }
+    return value;
+  }
+
+  /** 
+   * convert an array of values in R^DomainDimension to an array of 1-D indices
+   *
+   * @return indices 
+   * @throws VisADException  couldn't create the necessary VisAD object
+   */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    return doubleToIndex(Set.floatToDouble(value));
+  }
+
+  /** 
+   * convert an array of doubles in R^DomainDimension to an array of 1-D indices
+   *
+   * @return indices 
+   * @throws VisADException  couldn't create the necessary VisAD object
+   */
+  public int[] doubleToIndex(double[][] value) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("SingletonSet.doubleToIndex: value dimension " +
+                             value.length + " not equal to Domain dimension " +
+                             DomainDimension);
+    }
+    int length = value[0].length;
+    int[] index = new int[length];
+    for (int i=0; i<length; i++) {
+      index[i] = 0;
+    }
+    return index;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float weights[][]) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("SingletonSet.valueToInterp: bad dimension");
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length || weights.length != length) {
+      throw new SetException("SingletonSet.valueToInterp: lengths don't match");
+    }
+    for (int i=0; i<length; i++) {
+      indices[i] = new int[1];
+      weights[i] = new float[1];
+      indices[i][0] = 0;
+      weights[i][0] = 1.0f;
+    }
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void doubleToInterp(double[][] value, int[][] indices,
+                            double weights[][]) throws VisADException {
+    if (value.length != DomainDimension) {
+      throw new SetException("SingletonSet.doubleToInterp: bad dimension");
+    }
+    int length = value[0].length; // number of values
+    if (indices.length != length || weights.length != length) {
+      throw new SetException("SingletonSet.doubleToInterp: lengths don't match");
+    }
+    for (int i=0; i<length; i++) {
+      indices[i] = new int[1];
+      weights[i] = new double[1];
+      indices[i][0] = 0;
+      weights[i][0] = 1.0;
+    }
+  }
+
+  /**
+   * Get the values from the RealTuple as an array of doubles
+   * @return samples as doubles.
+   * @throws VisADException  couldn't create the necessary VisAD object
+   */
+  public double[][] getDoubles(boolean copy) throws VisADException {
+    int n = getLength();
+    int[] indices = new int[n];
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToDouble(indices);
+  }
+
+  /**
+   * Get the RealTuple that this SingletonSet was initialized with.
+   *
+   * @return  RealTuple
+   */
+  public RealTuple getData() {
+    return data;
+  }
+
+  /**
+   * See if this SingletonSet is equal to the Object in question.  They
+   * are equal if they are the same object and have the same Units, 
+   * CoordinateSystem, dimension and their getData() returns are the equal.
+   *
+   * @return  true if equal, otherwise false
+   */
+  public boolean equals(Object set) {
+    if (!(set instanceof SingletonSet) || set == null) return false;
+    if (this == set) return true;
+    if (!equalUnitAndCS((Set) set)) return false;
+    if (DomainDimension != ((SingletonSet) set).getDimension()) return false;
+    if ( !data.equals( ((SingletonSet) set).getData()) ) return false;
+    return true;
+  }
+
+  /**
+   * Returns the hash code of this instance.
+   * @return		The hash code of this instance.
+   */
+  public int hashCode() {
+    if (!hashCodeSet)
+    {
+      hashCode = unitAndCSHashCode() ^ DomainDimension ^ data.hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  /**
+   * Clone this SingletonSet, but change the MathType
+   *
+   * @param  type  new MathType
+   * 
+   * @return  clone of this set with new MathType
+   * @throws VisADException  couldn't create the new SingletonSet
+   */
+  public Object cloneButType(MathType type) throws VisADException {
+    try {
+      return new SingletonSet(data, type, DomainCoordinateSystem,
+                              SetUnits, SetErrors);
+    }
+    catch (RemoteException e) {
+      throw new VisADError("SingletonSet.cloneButType: " + e.toString());
+    }
+  }
+
+  /**
+   * Create string representation of this Set with a given prefix
+   *
+   * @param  pre   Prefix for the string.
+   * @return  String of the form pre + "SingletonSet: " + getData().toString()
+   * @throws VisADException  couldn't create the longString
+   */
+  public String longString(String pre) throws VisADException {
+    return pre + "SingletonSet: " + data;
+  }
+}
+
diff --git a/visad/SocketSlaveDisplay.java b/visad/SocketSlaveDisplay.java
new file mode 100644
index 0000000..d502a18
--- /dev/null
+++ b/visad/SocketSlaveDisplay.java
@@ -0,0 +1,478 @@
+//
+// SocketSlaveDisplay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Component;
+import java.awt.event.MouseEvent;
+import java.net.*;
+import java.io.*;
+import java.rmi.RemoteException;
+import java.util.*;
+import visad.browser.*;
+
+/** A SocketSlaveDisplay server wraps around a VisAD display, providing support
+    for stand-alone remote displays (i.e., not dependent on the VisAD packages)
+    that communicate with the server using sockets. For an example, see
+    examples/Test68.java together with the stand-alone VisAD applet
+    visad.browser.VisADApplet, usable from within a web browser. */
+public class SocketSlaveDisplay implements RemoteSlaveDisplay {
+
+  /** debugging flag */
+  private static final boolean DEBUG = false;
+
+  /** the default port for server/client communication */
+  private static final int DEFAULT_PORT = 4567;
+
+  /** list of control classes that support socket-based collaboration */
+  private static final Class[] supportedControls = {
+    GraphicsModeControl.class, ContourControl.class
+  };
+
+  /** the port at which the server communicates with clients */
+  private int port;
+
+  /** the server's associated VisAD display */
+  private DisplayImpl display;
+
+  /** flag that prevents getImage() calls from signaling a FRAME_DONE event */
+  private boolean flag;
+
+  /** array of image data extracted from the VisAD display */
+  private byte[] pix;
+
+  /** height of image */
+  private int h;
+
+  /** width of image */
+  private int w;
+
+  /** the server's associated socket */
+  private ServerSocket serverSocket;
+
+  /** vector of client sockets connected to the server */
+  private Vector clientSockets = new Vector();
+
+  /** vector of client sockets' input streams */
+  private Vector clientInputs = new Vector();
+
+  /** vector of client sockets' output streams */
+  private Vector clientOutputs = new Vector();
+
+  /** vector of client socket ids */
+  private Vector clientIds = new Vector();
+
+  /** thread monitoring incoming clients */
+  private Thread connectThread = null;
+
+  /** thread monitoring communication between server and clients */
+  private Thread commThread = null;
+
+  /** whether the server is still alive */
+  private boolean alive = true;
+
+  /** next available client ID number */
+  private int clientID = 0;
+
+  /** this socket slave display */
+  private final SocketSlaveDisplay socketSlave = this;
+
+  /** contains the code for monitoring incoming clients */
+  private Runnable connect = new Runnable() {
+    public void run() {
+      while (alive) {
+        try {
+          // wait for a new socket to connect
+          Socket socket = serverSocket.accept();
+          if (!alive) break;
+          if (socket != null) {
+            synchronized (clientSockets) {
+              // add client to the list
+              clientSockets.add(socket);
+
+              // add client's input and output streams to the list
+              DataInputStream in =
+                new DataInputStream(socket.getInputStream());
+              clientInputs.add(in);
+              DataOutputStream out =
+                new DataOutputStream(socket.getOutputStream());
+              clientOutputs.add(out);
+
+              // assign client an ID number
+              out.writeInt(++clientID);
+              clientIds.add(new Integer(clientID));
+            }
+          }
+        }
+        catch (IOException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+    }
+  };
+
+  /** contains the code for monitoring server/client communication */
+  private Runnable comm = new Runnable() {
+    public void run() {
+      while (alive) {
+        boolean silence = true;
+        Object[] sockets, inputs, outputs, cids;
+        synchronized (clientSockets) {
+          sockets = clientSockets.toArray();
+          inputs = clientInputs.toArray();
+          outputs = clientOutputs.toArray();
+          cids = clientIds.toArray();
+        }
+        for (int i=0; i<sockets.length; i++) {
+          Socket socket = (Socket) sockets[i];
+          DataInputStream in = (DataInputStream) inputs[i];
+          DataOutputStream out = (DataOutputStream) outputs[i];
+          int cid = ((Integer) cids[i]).intValue();
+
+          // check for client requests in the form of MouseEvent data
+          try {
+            if (in.available() > 0) {
+              silence = false;
+              // receive the client data
+              int sid = in.readInt();
+              if (DEBUG && sid != cid) {
+                System.err.println("Warning: client #" + cid + " believes " +
+                  "its ID number is " + sid);
+              }
+              int eventType = in.readInt();
+
+              if (eventType == VisADApplet.REFRESH) {
+                // send latest display image to the client
+                updateClient(socket, in, out);
+
+                // send latest supported control states to the client
+                for (int j=0; j<supportedControls.length; j++) {
+                  Class c = supportedControls[j];
+                  Vector v = display.getControls(c);
+
+                  // send control state message to each client
+                  for (int k=0; k<v.size(); k++) {
+                    Control control = (Control) v.elementAt(k);
+                    String message = c.getName() + "\n" +
+                      k + "\n" + control.getSaveString();
+                    updateClient(message, socket, in, out);
+                  }
+                }
+
+                // send latest ScalarMap states to the client
+                Vector maps = display.getMapVector();
+                for (int j=0; j<maps.size(); j++) {
+                  ScalarMap map = (ScalarMap) maps.elementAt(j);
+                  ScalarType scalar = map.getScalar();
+                  DisplayRealType displayScalar = map.getDisplayScalar();
+                  double[] range = map.getRange();
+                  String message = "visad.ScalarMap\n" +
+                    scalar.getName() + " " + displayScalar.getName() + " " +
+                    range[0] + " " + range[1];
+                  updateClient(message, socket, in, out);
+                }
+              }
+              else if (eventType == VisADApplet.MOUSE_EVENT) {
+                int mid = in.readInt();
+                long when = in.readLong();
+                int mods = in.readInt();
+                int x = in.readInt();
+                int y = in.readInt();
+                int clicks = in.readInt();
+                boolean popup = in.readBoolean();
+
+                // construct resulting MouseEvent and process it
+                Component c = display.getComponent();
+                MouseEvent me =
+                  new MouseEvent(c, mid, when, mods, x, y, clicks, popup);
+                MouseBehavior mb = display.getMouseBehavior();
+                MouseHelper mh = mb.getMouseHelper();
+                mh.processEvent(me, cid);
+              }
+              else if (eventType == VisADApplet.MESSAGE) {
+                int len = in.readInt();
+                char[] c = new char[len];
+                for (int j=0; j<len; j++) c[j] = in.readChar();
+                String message = new String(c);
+                StringTokenizer st = new StringTokenizer(message, "\n");
+                String controlClass = st.nextToken();
+                int index = Convert.getInt(st.nextToken());
+                String save = st.nextToken();
+                Class cls = null;
+                try {
+                  cls = Class.forName(controlClass);
+                }
+                catch (ClassNotFoundException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                }
+                if (cls != null) {
+                  Control control = display.getControl(cls, index);
+                  if (control != null) {
+                    // verify that control state has actually changed
+                    if (!save.equals(control.getSaveString())) {
+                      try {
+                        synchronized (socketSlave) {
+                          // temporarily disable control change notification
+                          display.removeSlave(socketSlave);
+                          control.setSaveString(save);
+                          display.addSlave(socketSlave);
+                        }
+                        // notify clients of change
+                        for (int k=0; k<sockets.length; k++) {
+                          // skip event source client
+                          int kid = ((Integer) cids[k]).intValue();
+                          if (kid != cid) {
+                            Socket ksocket = (Socket) sockets[k];
+                            DataInputStream kin = (DataInputStream) inputs[k];
+                            DataOutputStream kout =
+                              (DataOutputStream) outputs[k];
+                            updateClient(message, ksocket, kin, kout);
+                          }
+                        }
+                      }
+                      catch (VisADException exc) {
+                        if (DEBUG) exc.printStackTrace();
+                      }
+                      catch (RemoteException exc) {
+                        if (DEBUG) exc.printStackTrace();
+                      }
+                    }
+                  }
+                  else {
+                    if (DEBUG) System.err.println("Warning: ignoring " +
+                      "change to unknown control from client #" + cid);
+                  }
+                }
+              }
+              else { // Unknown event type
+                if (DEBUG) System.err.println("Warning: " +
+                  "ignoring unknown event type from client #" + cid);
+              }
+            }
+          }
+          catch (SocketException exc) {
+            // there is a problem with this socket, so kill it
+            killSocket(socket, in, out);
+            break;
+          }
+          catch (IOException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+        }
+        if (silence) {
+          try {
+            Thread.sleep(200);
+          }
+          catch (InterruptedException exc) { }
+        }
+      }
+    }
+  };
+
+  /** construct a SocketSlaveDisplay for the given VisAD display */
+  public SocketSlaveDisplay(DisplayImpl d) throws IOException {
+    this(d, DEFAULT_PORT);
+  }
+
+  /** construct a SocketSlaveDisplay for the given VisAD display,
+      and communicate with clients using the given port */
+  public SocketSlaveDisplay(DisplayImpl d, int port) throws IOException {
+    display = d;
+    this.port = port;
+
+    // create a server socket at the given port
+    serverSocket = new ServerSocket(port);
+
+    // create a thread that listens for connecting clients
+    connectThread = new Thread(connect,
+      "SocketSlaveDisplay-Connect-" + display.getName());
+    connectThread.start();
+
+    // create a thread for client/server communication
+    commThread = new Thread(comm,
+      "SocketSlaveDisplay-Comm-" + display.getName());
+    commThread.start();
+
+    // register socket server as a slaved display
+    display.addSlave(this);
+  }
+
+  /** get the socket port used by this SocketSlaveDisplay */
+  public int getPort() {
+    return port;
+  }
+
+  /** send the latest display image to the given socket */
+  private void updateClient(Socket socket, DataInputStream in,
+    DataOutputStream out)
+  {
+    if (pix != null) {
+      try {
+        // send image width, height and array length to the output stream
+        out.writeInt(w);
+        out.writeInt(h);
+        out.writeInt(pix.length);
+
+        // send pixel data to the output stream
+        out.write(pix);
+      }
+      catch (SocketException exc) {
+        // there is a problem with this socket, so kill it
+        killSocket(socket, in, out);
+      }
+      catch (IOException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+    }
+    else if (DEBUG) System.err.println("Null pixels!");
+  }
+
+  /** send a message to the given client */
+  private void updateClient(String message, Socket socket,
+    DataInputStream in, DataOutputStream out)
+  {
+    try {
+      // send message to the output stream
+      out.writeInt(-1); // special code of width -1 indicates message
+      out.writeInt(message.length());
+      out.writeChars(message);
+    }
+    catch (SocketException exc) {
+      // there is a problem with this socket, so kill it
+      killSocket(socket, in, out);
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+  }
+
+  /** display automatically calls sendImage when its content changes */
+  public synchronized void sendImage(int[] pixels, int width, int height,
+    int type) throws RemoteException
+  {
+    // convert pixels to byte array
+    pix = Convert.intToBytes(pixels);
+    w = width;
+    h = height;
+
+    // update all clients with the new image
+    int numSockets;
+    Object[] sockets, inputs, outputs;
+    synchronized (clientSockets) {
+      sockets = clientSockets.toArray();
+      inputs = clientInputs.toArray();
+      outputs = clientOutputs.toArray();
+    }
+    for (int i=0; i<sockets.length; i++) {
+      updateClient((Socket) sockets[i], (DataInputStream) inputs[i],
+        (DataOutputStream) outputs[i]);
+    }
+  }
+
+  /** send the given message to this slave display */
+  public synchronized void sendMessage(String message) throws RemoteException {
+    Object[] sockets, inputs, outputs;
+    synchronized (clientSockets) {
+      sockets = clientSockets.toArray();
+      inputs = clientInputs.toArray();
+      outputs = clientOutputs.toArray();
+    }
+    for (int i=0; i<sockets.length; i++) {
+      updateClient(message, (Socket) sockets[i],
+        (DataInputStream) inputs[i], (DataOutputStream) outputs[i]);
+    }
+  }
+
+  /** shut down the given socket */
+  private void killSocket(Socket socket, DataInputStream in,
+    DataOutputStream out)
+  {
+    // shut down socket input stream
+    try {
+      in.close();
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+
+    // shut down socket output stream
+    try {
+      out.close();
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+
+    // shut down socket itself
+    try {
+      socket.close();
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+
+    // remove socket from socket vectors
+    synchronized (clientSockets) {
+      int index = clientSockets.indexOf(socket);
+      clientSockets.removeElementAt(index);
+      clientInputs.removeElementAt(index);
+      clientOutputs.removeElementAt(index);
+      clientIds.removeElementAt(index);
+    }
+  }
+
+  /** destroy this server and kills all associated threads */
+  public void killServer() {
+    // set flag to cause server's threads to stop running
+    alive = false;
+
+    // shut down all client sockets
+    while (true) {
+      Socket socket = null;
+      DataInputStream in = null;
+      DataOutputStream out = null;
+      synchronized (clientSockets) {
+        if (clientSockets.size() > 0) {
+          socket = (Socket) clientSockets.elementAt(0);
+          in = (DataInputStream) clientInputs.elementAt(0);
+          out = (DataOutputStream) clientOutputs.elementAt(0);
+        }
+      }
+      if (socket == null) break;
+      else killSocket(socket, in, out);
+    }
+
+    // shut down server socket
+    try {
+      serverSocket.close();
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+  }
+
+}
+
diff --git a/visad/SphericalCoordinateSystem.java b/visad/SphericalCoordinateSystem.java
new file mode 100644
index 0000000..c6e318b
--- /dev/null
+++ b/visad/SphericalCoordinateSystem.java
@@ -0,0 +1,181 @@
+//
+// SphericalCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   SphericalCoordinateSystem is the VisAD CoordinateSystem class
+   for (Latitude, Longitude, Radius) with a Cartesian Reference,
+   and with Latitude and Longitude in degrees.<P>
+*/
+public class SphericalCoordinateSystem extends CoordinateSystem {
+
+  private static Unit[] coordinate_system_units =
+    {CommonUnit.degree, CommonUnit.degree, null};
+
+  /** construct a CoordinateSystem for (latitude, longitude,
+      radius) relative to a 3-D Cartesian reference;
+      this constructor supplies units =
+      {CommonUnit.Degree, CommonUnit.Degree, null} to the super
+      constructor, in order to ensure Unit compatibility with its
+      use of trigonometric functions */
+  public SphericalCoordinateSystem(RealTupleType reference)
+         throws VisADException {
+    super(reference, coordinate_system_units);
+  }
+
+  /** trusted constructor for initializers */
+  SphericalCoordinateSystem(RealTupleType reference, boolean b) {
+    super(reference, coordinate_system_units, b);
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("SphericalCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[2][i] < 0.0) {
+        value[0][i] = Double.NaN;
+        value[1][i] = Double.NaN;
+        value[2][i] = Double.NaN;
+      }
+      else {
+        double coslat = Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        double sinlat = Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        double coslon = Math.cos(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        double sinlon = Math.sin(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        value[0][i] = tuples[2][i] * coslon * coslat;
+        value[1][i] = tuples[2][i] * sinlon * coslat;
+        value[2][i] = tuples[2][i] * sinlat;
+      }
+    }
+    return value;
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("SphericalCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[3][len];
+    for (int i=0; i<len ;i++) {
+      value[2][i] = Math.sqrt(tuples[0][i] * tuples[0][i] +
+                              tuples[1][i] * tuples[1][i] +
+                              tuples[2][i] * tuples[2][i]);
+      value[0][i] =
+        Data.RADIANS_TO_DEGREES * Math.asin(tuples[2][i] / value[2][i]);
+      value[1][i] =
+        Data.RADIANS_TO_DEGREES * Math.atan2(tuples[1][i], tuples[0][i]);
+      if (value[1][i] < 0.0) value[1][i] += 360.0;
+    }
+    return value;
+  }
+
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("SphericalCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[2][i] < 0.0) {
+        value[0][i] = Float.NaN;
+        value[1][i] = Float.NaN;
+        value[2][i] = Float.NaN;
+      }
+      else {
+        float coslat = (float) Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        float sinlat = (float) Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        float coslon = (float) Math.cos(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        float sinlon = (float) Math.sin(Data.DEGREES_TO_RADIANS * tuples[1][i]);
+        value[0][i] = tuples[2][i] * coslon * coslat;
+        value[1][i] = tuples[2][i] * sinlon * coslat;
+        value[2][i] = tuples[2][i] * sinlat;
+      }
+    }
+    return value;
+  }
+
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("SphericalCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[3][len];
+    for (int i=0; i<len ;i++) {
+      value[2][i] = (float) Math.sqrt(tuples[0][i] * tuples[0][i] +
+                                      tuples[1][i] * tuples[1][i] +
+                                      tuples[2][i] * tuples[2][i]);
+      value[0][i] = (float)
+        (Data.RADIANS_TO_DEGREES * Math.asin(tuples[2][i] / value[2][i]));
+      value[1][i] = (float)
+        (Data.RADIANS_TO_DEGREES * Math.atan2(tuples[1][i], tuples[0][i]));
+      if (value[1][i] < 0.0f) value[1][i] += 360.0f;
+    }
+    return value;
+  }
+
+  public static float[] getNormal(float[] xyz) throws VisADException {
+    CoordinateSystem cs = Display.DisplaySphericalCoordSys; 
+    float[][] llrA = cs.fromReference(new float[][] {{xyz[0]}, {xyz[1]}, {xyz[2]}});
+    float[][] llrB = new float[][] {{llrA[0][0]}, {llrA[1][0]}, {llrA[2][0] + 1f}};
+    float[][] xyzB = cs.toReference(llrB);
+
+    float delX = xyzB[0][0] - xyz[0];
+    float delY = xyzB[1][0] - xyz[1];
+    float delZ = xyzB[2][0] - xyz[2];
+
+    float mag = (float) Math.sqrt(delX*delX+delY*delY+delZ*delZ);
+    return new float[] {delX/mag, delY/mag, delZ/mag};
+  }
+
+  public static float[] getUnitI(float[] xyz) throws VisADException {
+    CoordinateSystem cs = Display.DisplaySphericalCoordSys;
+    float[][] llrA = cs.fromReference(new float[][] {{xyz[0]}, {xyz[1]}, {xyz[2]}});
+    float[][] llrB = new float[][] {{llrA[0][0]}, {llrA[1][0] + 0.05f}, {llrA[2][0]}};
+    float[][] xyzB = cs.toReference(llrB);
+
+    float delX = xyzB[0][0] - xyz[0];
+    float delY = xyzB[1][0] - xyz[1];
+    float delZ = xyzB[2][0] - xyz[2];
+
+    float mag = (float) Math.sqrt(delX*delX+delY*delY+delZ*delZ);
+    return new float[] {delX/mag, delY/mag, delZ/mag};
+  }
+
+
+  public boolean equals(Object cs) {
+    return (cs instanceof SphericalCoordinateSystem);
+  }
+
+}
+
diff --git a/visad/Stream2D.java b/visad/Stream2D.java
new file mode 100644
index 0000000..7fe07e8
--- /dev/null
+++ b/visad/Stream2D.java
@@ -0,0 +1,569 @@
+//
+// Stream2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+public class Stream2D
+{
+
+/*
+ * trace one stream line for 2-D u and v arrays.
+ * Note that the input arrays ugrid & vgrid should be in
+ * column-major (FORTRAN) order.
+ *
+ * Input:  ugrid, vgrid - the 2-D wind arrays.
+ *         nr, nc - size of 2-D array in rows and columns.
+ *         vr, vc - arrays to put streamline vertices
+ *         dir - direction 1.0=forward, -1.0=backward
+ *         maxv - size of vx, vy arrays
+ *         numv - pointer to int to return number of vertices in vx, vy
+ *         markarrow - mark array for arrows
+ *         markstart - mark array for starting streamlines
+ *         markend - mark array for ending streamlines
+ *         nrarrow, ncarrow - size of markarrow
+ *         nrstart, ncstart - size of markstart
+ *         nrend, ncend - size of markend
+ *         row, col - start location for streamline
+ *         step - step size for streamline
+ *         rowlength, collength - scale constants for arrows
+ *         irend, icend - location of most recent end box
+ * Return:  1 = ok
+ *          0 = no more memory for streamlines
+ */
+
+static float LENGTH = 0.015f;
+
+static void stream_trace( float[] ugrid, float[] vgrid, int nr, int nc,
+                          float dir, float[][] vr2, float[][] vc2, int[] numv,
+                          byte[] markarrow, byte[] markstart, byte[] markend,
+                          int nrarrow, int ncarrow, int nrstart, int ncstart,
+                          int nrend, int ncend, float row, float col, float stepFactor,
+                          float rowlength, float collength, int irend, int icend,
+                          Gridded2DSet spatial_set, float[][] spatial_values, int[] offgrid, float cell_fraction)
+       throws VisADException
+{
+
+  int irstart, icstart, ir, ic, ire, ice;
+  int ira, ica, irs, ics;
+  int nend, num;
+  float prevrow, prevcol;
+  float a, c, ac, ad, bc, bd;
+  float u, v;
+
+  num = numv[0];
+  nend = 0;
+
+  float[] vr = vr2[0];
+  float[] vc = vc2[0];
+
+  float[] vec_row = new float[2];
+  float[] vec_col = new float[2];
+
+  while (true) {
+    float ubd, ubc, uad, uac, vbd, vbc, vad, vac;
+
+
+    /*- interpolate velocity at row, col
+    --------------------------------------*/
+
+    ir = (int) row;
+    ic = (int) col;
+    a = row - (float)ir;
+    c = col - (float)ic;
+    ac = a*c;
+    ad = a*(1f-c);
+    bc = (1f-a)*c;
+    bd = (1f-a)*(1f-c);
+
+    ubd = ugrid[ir*nc + ic];
+    ubc = ugrid[ir*nc + ic+1];
+    uad = ugrid[(ir+1)*nc + ic];
+    uac = ugrid[(ir+1)*nc + (ic+1)];
+    vbd = vgrid[ir*nc + ic];
+    vbc = vgrid[ir*nc + (ic+1)];
+    vad = vgrid[(ir+1)*nc + ic];
+    vac = vgrid[(ir+1)*nc + (ic+1)];
+
+
+    /* terminate stream if missing data
+    ------------------------------------*/
+    if (ubd != ubd || ubc != ubc ||
+        uad != uad || uac != uac ||
+        vbd != vbd || vbc != vbc ||
+        vad != vad || vac != vac)
+    {
+       break;
+    }
+
+    u = bd * ubd + bc * ubc +
+        ad * uad + ac * uac;
+    v = bd * vbd + bc * vbc +
+        ad * vad + ac * vac;
+
+
+    /* propogate streamline
+    ------------------------------------*/
+    prevrow = row;
+    prevcol = col;
+
+    float[][] loc = new float[2][1];
+    loc[1][0] = row;
+    loc[0][0] = col;
+
+    float mag = (float) Math.sqrt((double)(u*u + v*v));
+    int[] lens = spatial_set.getLengths();
+    int lenX = lens[0];
+    int ii = ((int)row)*lenX + (int)col;
+
+    float del_x = spatial_values[0][ii+1] - spatial_values[0][ii];
+    float del_y = spatial_values[1][ii+1] - spatial_values[1][ii];
+    if (((del_x != del_x) || (del_y != del_y)) || ((u!=u || v!=v))) {
+      break;
+    }
+    float mag_col = (float) Math.sqrt((double)(del_x*del_x + del_y*del_y));
+    vec_col[0] = del_x/mag_col;
+    vec_col[1] = del_y/mag_col;
+
+    del_x = spatial_values[0][ii+lenX] - spatial_values[0][ii];
+    del_y = spatial_values[1][ii+lenX] - spatial_values[1][ii];
+    float mag_row = (float) Math.sqrt((double)(del_x*del_x + del_y*del_y));
+    vec_row[0] = del_x/mag_row;
+    vec_row[1] = del_y/mag_row;
+
+    float dist = 1f;
+    float step = stepFactor*cell_fraction*(dist/mag);
+
+    loc[0][0] += step*dir*(u*vec_col[0] + v*vec_col[1]);
+    loc[1][0] += step*dir*(u*vec_row[0] + v*vec_row[1]);
+
+
+    row = loc[1][0];
+    col = loc[0][0];
+
+
+    /* terminate stream if out of grid
+    -----------------------------------*/
+    if (row < 0 || col < 0 || row >= nr-1 || col >= nc-1 || row != row || col != col) {
+      offgrid[0]++;
+      break;
+    }
+
+    ire = ( (int) (nrend * (row) / ((float) nr-1.0) ) );
+    ice = ( (int) (ncend * (col) / ((float) nc-1.0) ) );
+
+    /* terminate stream if enters marked end box
+    ---------------------------------------------*/
+    if (ire != irend || ice != icend)
+    {
+      irend = ire;
+      icend = ice;
+      if (markend[icend*nrend + irend] == 1) {
+        break;
+      }
+      markend[icend*nrend + irend] = 1;
+      nend = 0;
+    }
+
+    /* terminate stream if too many steps in one end box
+    -----------------------------------------------------*/
+    nend++;
+    if (nend > 2.5*(nr/(cell_fraction*nrend)) ) {
+      //-System.out.println("Stream2D: too many steps in one end box");
+      break;
+    }
+
+
+    /*- check vertex array length, expand if necessary
+    ---------------------------------------------------*/
+    if (num >= vr.length - 4) {
+      float[] tmp = new float[vr.length + 50];
+      System.arraycopy(vr, 0, tmp, 0, vr.length);
+      vr = tmp;
+      tmp = new float[vc.length + 50];
+      System.arraycopy(vc, 0, tmp, 0, vc.length);
+      vc = tmp;
+    }
+
+    /*- make line segment
+    ----------------------*/
+    vr[num]   = prevrow;
+    vc[num++] = prevcol;
+    vr[num]   = row;
+    vc[num++] = col;
+
+    /* mark start box
+    ---------------------*/
+    irs = ( (int) (nrstart * (row) / ((float) nr-1.0) ) );
+    ics = ( (int) (ncstart * (col) / ((float) nc-1.0) ) );
+
+    if (markstart[ (ics) * nrstart + (irs) ] == 0) {
+      markstart[ (ics) * nrstart + (irs) ] = 1;
+    }
+
+
+    /*- check for need to draw arrow head
+    --------------------------------------*/
+    ira = ( (int) (nrarrow * (row) / ((float) nr-1.0) ) );
+    ica = ( (int) (ncarrow * (col) / ((float) nc-1.0) ) );
+
+    if (markarrow[ica*nrstart + ira] == 0) {
+      double rv, cv, vl;
+      markarrow[ica*nrstart + ira] = 1;
+      rv = dir * (row - prevrow);
+      cv = dir * (col - prevcol);
+      vl =  Math.sqrt(rv*rv + cv*cv);
+      vl *= 3;
+      if (vl > 0.000000001) {
+        rv = rv / vl;
+        cv = cv / vl;
+      }
+
+      /*- check vertex array length, expand if necessary
+      ---------------------------------------------------*/
+      if ( num >= vr.length - 6) {
+        float[] tmp = new float[vr.length + 50];
+        System.arraycopy(vr, 0, tmp, 0, vr.length);
+        vr = tmp;
+        tmp = new float[vc.length + 50];
+        System.arraycopy(vc, 0, tmp, 0, vc.length);
+        vc = tmp;
+      }
+      vr[num]   = row;
+      vc[num++] = col;
+      vr[num]   = row - ((float)(rv + cv)) * rowlength;
+      vc[num++] = col + ((float)(rv - cv)) * collength;
+      vr[num]   = row;
+      vc[num++] = col;
+      vr[num]   = row + ((float)(cv - rv)) * rowlength;
+      vc[num++] = col - ((float)(cv + rv)) * collength;
+    }
+
+  } /* end while */
+
+  numv[0] = num;
+  vr2[0] = vr;
+  vc2[0] = vc;
+}
+
+
+
+/*
+ * Compute stream lines for 2-D u and v arrays.
+ * Note that the input arrays ugrid & vgrid should be in
+ * row-major order.
+ *
+ * Input:  ugrid, vgrid - the 2-D wind arrays.
+ *         nr, nc - size of 2-D array in rows and columns.
+ *         density - relative density of streamlines.
+ *         vr, vc - arrays to put streamline vertices
+ *         maxv - size of vx, vy arrays
+ *         numv - pointer to int to return number of vertices in vx, vy
+ * Return:  1 = ok
+ *          0 = error  (out of memory)
+ */
+
+public static int stream( float[] ugrid, float[] vgrid, int nr, int nc,
+                   float density, float stepFactor, float arrowScale,
+                   float[][][] vr, float[][][] vc,
+                   int[][] numv, int[] numl,
+                   Gridded2DSet spatial_set, float packingFactor, float cntrWeight, int n_pass, float reduction)
+       throws VisADException
+{
+  int irstart, icstart, irend, icend, ir, ic, ire, ice;
+  int ira, ica, irs, ics;
+  int nrarrow, ncarrow, nrstart, ncstart, nrend, ncend;
+  int nend;
+  float row, col, prevrow, prevcol;
+  float a, c, ac, ad, bc, bd;
+  float u, v, step, rowlength, collength;
+  float dir;
+  float cell_fraction = 0.05f;
+
+  byte[] markarrow;
+  byte[] markstart;
+  byte[] markend;
+
+  int n_lines = 0;
+
+  /* initialize vertex counts */
+  int[] num = new int[1];
+
+
+  /* density calculations */
+  if (density < 0.5) density = 0.5f;
+
+  // WLH 30 March 2006
+  // if (density > 2.0) density = 2.0f;
+  if (density > nr/15f) density = nr/15f;
+  if (density > nc/15f) density = nc/15f;
+
+  nrarrow = (int)(15f*density);
+  ncarrow = (int)(15f*density);
+  nrstart = (int)(15f*density);
+  ncstart = (int)(15f*density);
+
+  // WLH 30 March 2006
+  if (nrarrow > nr) nrarrow = nr;
+  if (ncarrow > nc) ncarrow = nc;
+  if (nrstart > nr) nrstart = nr;
+  if (ncstart > nc) ncstart = nc;
+
+  nrend = 4*nrstart;
+  ncend = 4*ncstart;
+
+  // WLH 30 March 2006
+  if (nrend > nr) nrend = nr;
+  if (ncend > nc) ncend = nc;
+
+
+  //-TDR ----------------------------
+  float n_start_per_cell = 0.50f;
+  float n_end_per_cell   = 4.00f;
+  n_start_per_cell = 30f*density/((nc+nr)/2);
+  n_end_per_cell  = packingFactor*8f*n_start_per_cell;
+
+
+  nrstart = (int) (n_start_per_cell * nr);
+  nrarrow = nrstart;
+  ncstart = (int) (n_start_per_cell * nc);
+  ncarrow = ncstart;
+  nrend   = (int) (n_end_per_cell * nr);
+  ncend   = (int) (n_end_per_cell * nc);
+
+
+  rowlength = LENGTH * nr / density;
+  collength = LENGTH * nc / density;
+  rowlength *= arrowScale;
+  collength *= arrowScale;
+
+
+  numv[0] = new int[50];
+  vr[0]   = new float[50][];
+  vc[0]   = new float[50][];
+
+  /*- allocate mark arrays */
+  markarrow = new byte[nrarrow*ncarrow];
+  markstart = new byte[nrstart*ncstart];
+  markend   = new byte[nrend*ncend];
+
+  /*- initialize mark array */
+  for ( int kk = 0; kk < nrstart*ncstart; kk++ ) {
+    markstart[kk] = 0;
+    markarrow[kk] = 1;
+  }
+  for ( int kk = 0; kk < nrend*ncend; kk++ ) {
+    markend[kk] = 0;
+  }
+
+  /*- only draw arrows in every ninth box */
+  for (ir = 1; ir<nrarrow; ir+=3) {
+    for (ic = 1; ic<ncarrow; ic+=3) {
+      markarrow[ic*nrarrow + ir] = 0;
+    }
+  }
+
+
+  step = 1;
+  float[][] spatial_values = spatial_set.getSamples(false);
+
+
+  //-TDR, smoothing
+  if (cntrWeight > 6f) cntrWeight = 6;
+  float ww = (6f - cntrWeight)/4f;
+  for (int pass=0; pass<n_pass; pass++) {
+    float[] new_ugrid = new float[ugrid.length];
+    float[] new_vgrid = new float[vgrid.length];
+    System.arraycopy(ugrid, 0, new_ugrid, 0, new_ugrid.length);
+    System.arraycopy(vgrid, 0, new_vgrid, 0, new_vgrid.length);
+    for (int iir=1; iir < nr-1; iir++) {
+      for (int iic=1; iic < nc-1; iic++) {
+        int kk = iir*nc + iic;
+        float suu = ugrid[kk];
+        float sua = ugrid[kk-1];
+        float sub = ugrid[kk+1];
+        float suc = ugrid[kk+nc];
+        float sud = ugrid[kk-nc];
+        new_ugrid[kk] = (cntrWeight*suu + ww*sua + ww*sub + ww*suc + ww*sud)/6;
+        float svv = vgrid[kk];
+        float sva = vgrid[kk-1];
+        float svb = vgrid[kk+1];
+        float svc = vgrid[kk+nc];
+        float svd = vgrid[kk-nc];
+        new_vgrid[kk] = (cntrWeight*svv + ww*sva + ww*svb + ww*svc + ww*svd)/6;
+      }
+    }
+    ugrid = new_ugrid;
+    vgrid = new_vgrid;
+  }
+
+  int cnt = 0;
+  int[] offgrid = new int[1];
+
+  /* iterate over start boxes */
+  for (icstart=0; icstart<ncstart; icstart++) {
+    for (irstart=0; irstart<nrstart; irstart++) {
+
+      cnt = 0;
+      if (markstart[icstart*nrstart + irstart] == 0) {
+        markstart[ icstart*nrstart + irstart ] = 1;
+
+        /* trace streamline forward */
+        row = ( ((float) nr-1f) * ((float) (irstart)+0.5f) / (float) nrstart );
+        col = ( ((float) nc-1f) * ((float) (icstart)+0.5f) / (float) ncstart );
+        irend = ( (int) (nrend * (row) / ((float) nr-1f) ) );
+        icend = ( (int) (ncend * (col) / ((float) nc-1f) ) );
+
+        markend[icend*nrend + irend] = 1;
+
+        if (n_lines == vr[0].length) {
+          float[][] f_tmp = new float[vr[0].length + 50][];
+          System.arraycopy(vr[0], 0, f_tmp, 0, vr[0].length);
+          vr[0] = f_tmp;
+
+          f_tmp = new float[vc[0].length + 50][];
+          System.arraycopy(vc[0], 0, f_tmp, 0, vc[0].length);
+          vc[0] = f_tmp;
+        }
+
+
+        float[][] vr2 = new float[1][];
+        float[][] vc2 = new float[1][];
+        vr2[0] = new float[200];
+        vc2[0] = new float[200];
+        vr[0][n_lines] = vr2[0];
+        vc[0][n_lines] = vc2[0];
+
+        dir = 1f;
+        num[0] = 0;
+        offgrid[0] = 0;
+
+        stream_trace( ugrid, vgrid, nr, nc, dir,
+                      vr2, vc2,
+                      num,
+                      markarrow, markstart, markend, nrarrow, ncarrow,
+                      nrstart, ncstart, nrend, ncend, row, col, stepFactor,
+                      rowlength, collength, irend, icend, spatial_set,
+                      spatial_values, offgrid, cell_fraction);
+
+        vr[0][n_lines] = vr2[0];
+        vc[0][n_lines] = vc2[0];
+
+
+        cnt += num[0];
+        if (num[0] > 0) {
+
+          if ( n_lines == numv[0].length ) {
+            int[] tmp = new int[numv[0].length + 50];
+            System.arraycopy(numv[0], 0, tmp, 0, numv[0].length);
+            numv[0] = tmp;
+          }
+
+          float[] ftmp = new float[num[0]];
+          System.arraycopy(vr[0][n_lines], 0, ftmp, 0, ftmp.length);
+          vr[0][n_lines] = ftmp;
+          ftmp = new float[num[0]];
+          System.arraycopy(vc[0][n_lines], 0, ftmp, 0, ftmp.length);
+          vc[0][n_lines] = ftmp;
+
+          numv[0][n_lines] = num[0];
+          n_lines++;
+        }
+
+
+        /* trace streamline backward */
+        if (n_lines == vr[0].length) {
+          float[][] f_tmp = new float[vr[0].length + 50][];
+          System.arraycopy(vr[0], 0, f_tmp, 0, vr[0].length);
+          vr[0] = f_tmp;
+
+          f_tmp = new float[vc[0].length + 50][];
+          System.arraycopy(vc[0], 0, f_tmp, 0, vc[0].length);
+          vc[0] = f_tmp;
+        }
+
+        vr2 = new float[1][];
+        vc2 = new float[1][];
+        vr2[0] = new float[200];
+        vc2[0] = new float[200];
+        vr[0][n_lines] = vr2[0];
+        vc[0][n_lines] = vc2[0];
+
+        dir = -1f;
+        num[0] = 0;
+
+        stream_trace( ugrid, vgrid, nr, nc, dir,
+                      vr2, vc2,
+                      num,
+                      markarrow, markstart, markend, nrarrow, ncarrow,
+                      nrstart, ncstart, nrend, ncend, row, col, stepFactor,
+                      rowlength, collength, irend, icend, spatial_set,
+                      spatial_values, offgrid, cell_fraction);
+
+        vr[0][n_lines] = vr2[0];
+        vc[0][n_lines] = vc2[0];
+
+        cnt += num[0];
+        if (num[0] > 0) {
+
+          if ( n_lines == numv[0].length ) {
+            int[] tmp = new int[numv[0].length + 50];
+            System.arraycopy(numv[0], 0, tmp, 0, numv[0].length);
+            numv[0] = tmp;
+          }
+
+          float[] ftmp = new float[num[0]];
+          System.arraycopy(vr[0][n_lines], 0, ftmp, 0, ftmp.length);
+          vr[0][n_lines] = ftmp;
+          ftmp = new float[num[0]];
+          System.arraycopy(vc[0][n_lines], 0, ftmp, 0, ftmp.length);
+          vc[0][n_lines] = ftmp;
+
+          numv[0][n_lines] = num[0];
+          n_lines++;
+        }
+
+        //-TDR
+        if ((cnt < (((nr+nc)/2)/cell_fraction)*reduction ) && reduction != 1f) {
+          if (offgrid[0] == 0) {
+            n_lines -= 2;
+          }
+        }
+
+      } /* end if */
+    } /* end for */
+  } /* end for */
+
+
+  int[] tmp = new int[n_lines];
+  System.arraycopy(numv[0], 0, tmp, 0, n_lines);
+  numv[0] = tmp;
+
+  numl[0] = n_lines;
+
+  return 1;
+}
+
+}
diff --git a/visad/Text.java b/visad/Text.java
new file mode 100644
index 0000000..630dd09
--- /dev/null
+++ b/visad/Text.java
@@ -0,0 +1,131 @@
+//
+// Text.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   Text is the class of VisAD scalar data for text strings.  The null
+   pointer is used to indicate missing.  Text objects are immutable.<P>
+*/
+public class Text extends Scalar {
+
+  private String Value;
+
+  /** construct a Text object with the missing value */
+  public Text(TextType type) throws VisADException {
+    super(type);
+    if (!(type instanceof TextType)) throw new TypeException("Text: bad type");
+    Value = null; // set initial value to missing
+  }
+
+  /** construct a Text object */
+  public Text(TextType type, String value) throws VisADException {
+    super(type);
+    if (!(type instanceof TextType)) throw new TypeException("Text: bad type");
+    Value = value;
+  }
+
+  /** construct a Text object with the generic TEXT type (TextType.Generic) */
+  public Text(String value) {
+    super(TextType.Generic);
+    Value = value;
+  }
+
+  public String getValue() {
+    return Value;
+  }
+
+  public boolean isMissing() {
+    return (Value == null);
+  }
+
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException {
+    if (data instanceof Text && op == ADD) {
+      return new Text((TextType) Type, Value + ((Text) data).getValue());
+    }
+    else {
+      throw new TypeException("Text.binary: types don't match");
+    }
+  }
+
+  public Data unary(int op, int sampling_mode, int error_mode)
+              throws VisADException {
+    throw new TypeException("Text.unary");
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException {
+    return shadow;
+  }
+
+  public String toString() {
+    return Value;
+  }
+
+  public String longString(String pre) {
+    return pre + "Text: Value = " + Value +
+           "  (TypeName = " + ((TextType) Type).getName() + ")\n";
+  }
+
+  /**
+   * Compares this Text to another.
+   * @param object		The other Text to compare against.  It shall be
+   *				a Text.
+   * @return                    A negative integer, zero, or a positive integer
+   *                            depending on whether this Text is considered
+   *                            less than, equal to, or greater than the other
+   *                            Text, respectively.
+   */
+  public int compareTo(Object object) {
+    return getValue().compareTo(((Text)object).getValue());
+  }
+
+  /**
+   * Indicates if this Text is semantically identical to an object.
+   * @param obj			The object.
+   * @return			<code>true</code> if and only if this Text
+   *				is semantically identical to the object.
+   */
+  public boolean equals(Object obj) {
+    if (obj == null || !(obj instanceof Text)) {
+      return false;
+    }
+
+    String objValue = ((Text)obj).getValue();
+
+    if (Value == null) {
+      return (objValue == null);
+    }
+
+    if (objValue == null) {
+      return false;
+    }
+
+    return objValue.equals(Value);
+  }
+}
+
diff --git a/visad/TextControl.java b/visad/TextControl.java
new file mode 100644
index 0000000..99600e9
--- /dev/null
+++ b/visad/TextControl.java
@@ -0,0 +1,656 @@
+//
+// TextControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Font;
+import java.rmi.RemoteException;
+import java.text.NumberFormat;
+
+import visad.util.HersheyFont;
+import visad.util.Util;
+
+/**
+   TextControl is the VisAD class for controlling Text display scalars.<P>
+*/
+public class TextControl extends Control {
+
+  private Object font = null;
+
+  // abcd 5 February 2001
+  //private boolean center = false;
+
+  private double size = 1.0;
+
+  private double factor = 1.0;
+
+  private double autoSizeFactor = 1.0;
+
+  // WLH 31 May 2000
+  // draw on sphere surface
+  private boolean sphere = false;
+
+  private NumberFormat format = null;
+
+  // abcd 1 February 2001
+  // Rotation, in degrees, clockwise along positive x axis
+  private double rotation = 0.0;
+  // SL  15 March 2003
+  private double characterRotation = 0.0;   // characterRotation
+  private double scale = 1.0;  // Scaling factor
+  private double[] offset = new double[]{0.0, 0.0, 0.0};
+
+  // WLH 6 Aug 2001
+  private boolean autoSize = false;
+  private ProjectionControlListener pcl = null;
+
+  /**
+   * A class to represent the different types of justification.
+   * Use a class so the user can't just pass in an arbitrary integer
+   *
+   * @author abcd 5 February 2001
+   */
+  public static class Justification {
+    private String name;
+
+    /** Predefined value for left justification */
+    public static final Justification LEFT = new Justification("Left");
+
+    /** Predefined value for center justification */
+    public static final Justification CENTER = new Justification("Center");
+
+    /** Predefined value for right justification */
+    public static final Justification RIGHT = new Justification("Right");
+
+    /** Predefined value for top justification */
+    public static final Justification TOP = new Justification("Top");
+
+    /** Predefined value for bottom justification */
+    public static final Justification BOTTOM = new Justification("Bottom");
+
+    /**
+     * Constructor - simply store the name
+     */
+    public Justification(String newName)
+    {
+      name = newName;
+    }
+  }
+  private Justification justification = Justification.LEFT;
+  private Justification verticalJustification = Justification.BOTTOM;
+
+  public TextControl(DisplayImpl d) {
+    super(d);
+  }
+
+  public void setAutoSize(boolean auto)
+         throws VisADException {
+    if (auto == autoSize) return;
+    DisplayImpl display = getDisplay();
+    DisplayRenderer dr = display.getDisplayRenderer();
+    MouseBehavior mouse = dr.getMouseBehavior();
+    ProjectionControl pc = display.getProjectionControl();
+    if (auto) {
+      if (pcl == null) {
+        pcl = new ProjectionControlListener(mouse, this, pc);
+        pc.addControlListener(pcl);
+      }
+    }
+    if (pcl != null) {
+      pcl.setActive(auto);
+    }
+    /*
+    else {
+      pc.removeControlListener(pcl);
+    }
+    */
+    autoSize = auto;
+    try {
+      changeControl(true);
+    }
+    catch (RemoteException e) {
+    }
+  }
+
+  public boolean getAutoSize() {
+    return autoSize;
+  }
+
+  public void nullControl() {
+    try {
+      setAutoSize(false);
+    }
+    catch (VisADException e) {
+    }
+    super.nullControl();
+  }
+
+  /** set the font; in the initial release this has no effect
+  *
+  * @param f is the java.awt.Font or the visad.util.HersheyFont
+  */
+
+  public void setFont(Object f)
+         throws VisADException, RemoteException {
+    if (f instanceof java.awt.Font ||
+        f instanceof visad.util.HersheyFont ||
+        f == null) {
+      font = f;
+      changeControl(true);
+    } else {
+      throw new VisADException("Font must be java.awt.Font or HersheyFont");
+    }
+
+  }
+
+  /** return the java.awt.Font
+  *
+  * @return the java.awt.Font if the font is of that type; otherwise, null
+  */
+  public Font getFont() {
+    if (font instanceof java.awt.Font) {
+      return (Font) font;
+    } else {
+      return null;
+    }
+  }
+
+  /** return the HersheyFont
+  *
+  * @return the visad.util.HersheyFont if the font is of
+  * that type; otherwise, null
+  */
+  public HersheyFont getHersheyFont() {
+    if (font instanceof visad.util.HersheyFont) {
+      return (HersheyFont) font;
+    } else {
+      return null;
+    }
+  }
+
+  /** set the centering flag; if true, text will be centered at
+      mapped locations; if false, text will be to the right
+      of mapped locations */
+  public void setCenter(boolean c)
+         throws VisADException, RemoteException {
+    // abcd 5 February 2001
+    justification = Justification.CENTER;
+    //center = c;
+    changeControl(true);
+  }
+
+// TODO: Deprecate this?
+  /** return the centering flag */
+  public boolean getCenter() {
+    // abcd 5 February 2001
+    //return center;
+    if (justification == Justification.CENTER) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Set the justification flag
+   *
+   * Possible values are TextControl.Justification.LEFT,
+   * TextControl.Justification.CENTER and TextControl.Justification.RIGHT
+   */
+  // abcd 5 February 2001
+  public void setJustification(Justification newJustification)
+         throws VisADException, RemoteException
+  {
+    // Store the new value
+    justification = newJustification;
+
+    // Tell the control it's changed
+    changeControl(true);
+  }
+
+  /**
+   * Return the justification value
+   */
+  // abcd 5 February 2001
+  public Justification getJustification()
+  {
+    return justification;
+  }
+
+  /**
+   * Set the vertical justification flag
+   *
+   * Possible values are TextControl.Justification.TOP,
+   * TextControl.Justification.CENTER and TextControl.Justification.BOTTOM
+   * 
+   */
+  public void setVerticalJustification(Justification newJustification)
+         throws VisADException, RemoteException
+  {
+    // Store the new value
+    verticalJustification = newJustification;
+
+    // Tell the control it's changed
+    changeControl(true);
+  }
+
+  /**
+   * Return the vertical justification value
+   *
+   */
+  public Justification getVerticalJustification()
+  {
+    return verticalJustification;
+  }
+
+  /** set the size of characters; the default is 1.0 */
+  public void setSize(double s)
+         throws VisADException, RemoteException {
+    factor = s;
+    size = factor*autoSizeFactor;
+    changeControl(true);
+  }
+
+  private void setSizeForAuto(double autoSizeFactor)
+          throws VisADException, RemoteException {
+    this.autoSizeFactor = autoSizeFactor;
+    size = factor*autoSizeFactor;
+    changeControl(true);
+  }
+  
+  /** return the size */
+  public double getSize() {
+    return size;
+  }
+
+  // WLH 31 May 2000
+  public void setSphere(boolean s)
+         throws VisADException, RemoteException {
+    sphere = s;
+    changeControl(true);
+  }
+
+  // WLH 31 May 2000
+  public boolean getSphere() {
+    return sphere;
+  }
+
+  // WLH 16 June 2000
+  public void setNumberFormat(NumberFormat f)
+         throws VisADException, RemoteException {
+    format = f;
+    changeControl(true);
+  }
+
+  // WLH 16 June 2000
+  public NumberFormat getNumberFormat() {
+    return format;
+  }
+
+  private boolean fontEquals(Object newFont)
+  {
+    if (font == null) {
+      if (newFont != null) {
+        return false;
+      }
+    } else if (newFont == null) {
+      return false;
+    } else if (!font.equals(newFont)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  // WLH 16 June 2000
+  private boolean formatEquals(NumberFormat newFormat)
+  {
+    if (format == null) {
+      if (newFormat != null) {
+        return false;
+      }
+    } else if (newFormat == null) {
+      return false;
+    } else if (!format.equals(newFormat)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Set the rotation
+   *
+   * abcd 1 February 2001
+   */
+  public void setRotation(double newRotation)
+         throws VisADException, RemoteException
+  {
+    // Store the new rotation
+    rotation = newRotation;
+    // Tell the control it's changed
+    changeControl(true);
+  }
+
+  /**
+   * Get the rotation
+   *
+   * abcd 1 February 2001
+   */
+  public double getRotation()
+  {
+    return rotation;
+  }
+
+  /** get a string that can be used to reconstruct this control later */
+  public String getSaveString() {
+    return null;
+  }
+
+  /** reconstruct this control using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    throw new UnimplementedException(
+      "Cannot setSaveString on this type of control");
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof TextControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    TextControl tc = (TextControl )rmt;
+
+    boolean changed = false;
+
+    if (!fontEquals(tc.font)) {
+      changed = true;
+      font = tc.font;
+    }
+
+    // abcd 5 February 2001
+    //if (center != tc.center) {
+    //  changed = true;
+    //  center = tc.center;
+    //}
+    if (justification != tc.justification) {
+      changed = true;
+      justification = tc.justification;
+    }
+    // abcd 19 February 2003
+    if (verticalJustification != tc.verticalJustification) {
+      changed = true;
+      verticalJustification = tc.verticalJustification;
+    }
+
+    if (!Util.isApproximatelyEqual(size, tc.size)) {
+      changed = true;
+      size = tc.size;
+    }
+
+    // WLH 31 May 2000
+    if (sphere != tc.sphere) {
+      changed = true;
+      sphere = tc.sphere;
+    }
+
+    // WLH 16 June 2000
+    if (!formatEquals(tc.format)) {
+      changed = true;
+      format = tc.format;
+    }
+
+    // abcd 1 February 2001
+    if (!Util.isApproximatelyEqual(rotation, tc.rotation)) {
+      changed = true;
+      rotation = tc.rotation;
+    }
+
+    // SL 16 July 2003
+    if (!Util.isApproximatelyEqual(characterRotation, tc.characterRotation)) {
+      changed = true;
+      characterRotation = tc.characterRotation;
+    }
+
+    // WLH 6 Aug 2001
+    if (autoSize != tc.autoSize) {
+      // changed = true;
+      setAutoSize(tc.autoSize);
+    }
+
+    // SL 16 July 2003
+    if (!Util.isApproximatelyEqual(scale, tc.scale)) {
+      changed = true;
+      scale = tc.scale;
+    }
+
+    // SL 16 July 2003
+    for (int i=0; i<3; i++) {
+      if (!Util.isApproximatelyEqual(offset[i], tc.offset[i])) {
+        changed = true;
+        offset[i] = tc.offset[i];
+      }
+    }
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    TextControl tc = (TextControl )o;
+
+    if (!fontEquals(font)) {
+      return false;
+    }
+
+    // abcd 5 February 2001
+    //if (center != tc.center) {
+    if (justification != tc.justification) {
+      return false;
+    }
+    // abcd 19 March 2003
+    if (verticalJustification != tc.verticalJustification) {
+      return false;
+    }
+
+    // WLH 31 May 2000
+    if (sphere != tc.sphere) {
+      return false;
+    }
+
+    // WLH 16 June 2000
+    if (!formatEquals(tc.format)) {
+      return false;
+    }
+
+    // abcd 1 February 2001
+    if (!Util.isApproximatelyEqual(rotation, tc.rotation)) {
+      return false;
+    }
+
+    if (!Util.isApproximatelyEqual(size, tc.size)) {
+      return false;
+    }
+
+    // WLH 6 Aug 2001
+    if (autoSize != tc.autoSize) {
+      return false;
+    }
+
+    // SL 18 July 2003
+    if (!Util.isApproximatelyEqual(characterRotation, tc.characterRotation)) {
+      return false;
+    }
+
+    // SL 18 July 2003
+    if (!Util.isApproximatelyEqual(scale, tc.scale)) {
+      return false;
+    }
+
+    // SL 18 July 2003
+    for (int i=0; i<3; i++) {
+      if (!Util.isApproximatelyEqual(offset[i], tc.offset[i])) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Gets the value of characterRotation
+   *
+   * @return the value of characterRotation
+   */
+  public double getCharacterRotation()  {
+    return this.characterRotation;
+  }
+
+  /**
+   * Sets the value of characterRotation
+   *
+   * @param argCharacterRotation Value to assign to this.characterRotation
+   */
+  public void setCharacterRotation(double argCharacterRotation) throws
+    VisADException, RemoteException
+  {
+    this.characterRotation = argCharacterRotation;
+    // Tell the control it's changed
+    changeControl(true);
+  }
+
+  /**
+   * Gets the value of scale
+   *
+   * @return the value of scale
+   */
+  public double getScale()  {
+    return this.scale;
+  }
+
+  /**
+   * Sets the value of scale
+   *
+   * @param argScale Value to assign to this.scale
+   */
+  public void setScale(double argScale)
+    throws VisADException, RemoteException {
+    this.scale = argScale;
+    // Tell the control it's changed
+    changeControl(true);
+  }
+
+  /**
+   * Gets the value of offset
+   *
+   * @return the value of offset
+   */
+  public double[] getOffset()  {
+    double[] aOffset = new double[]{
+      this.offset[0], this.offset[1], this.offset[2]};
+    return aOffset;
+  }
+
+  /**
+   * Sets the value of offset
+   *
+   * @param argOffset Value to assign to this.offset
+   */
+  public void setOffset(double[] argOffset)
+    throws VisADException, RemoteException {
+    this.offset[0] = argOffset[0];
+    this.offset[1] = argOffset[1];
+    this.offset[2] = argOffset[2];
+    // Tell the control it's changed
+    changeControl(true);
+  }
+
+  class ProjectionControlListener implements ControlListener {
+    private boolean pfirst = true;
+    private MouseBehavior mouse;
+    private ProjectionControl pcontrol;
+    private TextControl text_control;
+    private double base_scale = 1.0;
+    private float last_cscale = 1.0f;
+    private boolean active = false;
+
+    ProjectionControlListener(MouseBehavior m, TextControl t,
+                              ProjectionControl p) {
+      mouse = m;
+      text_control = t;
+      pcontrol = p;
+    }
+    
+    public void setActive(boolean onoroff) {
+      active = onoroff;
+    }
+
+    public void controlChanged(ControlEvent e)
+           throws VisADException, RemoteException {
+      if (!active) return;
+      double[] matrix = pcontrol.getMatrix();
+      double[] rot = new double[3];
+      double[] scale = new double[3];
+      double[] trans = new double[3];
+      mouse.instance_unmake_matrix(rot, scale, trans, matrix);
+
+      if (pfirst) {
+        pfirst = false;
+        base_scale = scale[2];
+        last_cscale = 1.0f;
+      }
+      else {
+        float cscale = (float) (base_scale / scale[2]);
+        float ratio = cscale / last_cscale;
+        if (ratio < 0.95f || 1.05f < ratio) {
+          last_cscale = cscale;
+          text_control.setSizeForAuto(cscale);
+        }
+      }
+    }
+  }
+}
diff --git a/visad/TextType.java b/visad/TextType.java
new file mode 100644
index 0000000..01af1f0
--- /dev/null
+++ b/visad/TextType.java
@@ -0,0 +1,141 @@
+//
+// TextType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.Vector;
+
+/**
+   TextType is the VisAD scalar data type for text string variables.<P>
+*/
+public class TextType extends ScalarType {
+
+  public final static TextType Generic = new TextType("GENERIC_TEXT", true);
+
+  /** name of type (two TextTypes are equal if their names are equal) */
+  public TextType(String name) throws VisADException {
+    super(name);
+  }
+
+  TextType(String name, boolean b) {
+    super(name, b);
+  }
+
+  public boolean equals(Object type) {
+    if (!(type instanceof TextType)) return false;
+    return getName().equals(((TextType )type).getName());
+  }
+
+  public boolean equalsExceptName(MathType type) {
+    return (type instanceof TextType);
+  }
+
+  /*- TDR May 1998  */
+  public boolean equalsExceptNameButUnits( MathType type )
+         throws VisADException
+  {
+    throw new UnimplementedException("TextType: equalsExceptNameButUnits");
+  }
+
+  /*- TDR June 1998  */
+  public MathType cloneDerivative( RealType d_partial )
+         throws VisADException
+  {
+    throw new UnimplementedException("TexType: cloneDerivative");
+  }
+
+  /*- TDR July 1998  */
+  public MathType binary( MathType type, int op, Vector names )
+         throws VisADException
+  {
+    if (type == null) {
+      throw new TypeException("TextType.binary: type may not be null" );
+    }
+    if (type instanceof TextType) {
+      return this;
+    }
+    else {
+      throw new TypeException("TextType.binary: types don't match" );
+    }
+/* WLH 10 Sept 98
+    throw new UnimplementedException("TextType: binary");
+*/
+  }
+
+  /*- TDR July 1998  */
+  public MathType unary( int op, Vector names )
+         throws VisADException
+  {
+    throw new UnimplementedException("TextType: unary");
+  }
+
+  /** create a new TextType, or return it if it already exists */
+  public static synchronized TextType getTextType(String name) {
+    TextType result = getTextTypeByName(name);
+    if (result == null) {
+      try {
+        result = new TextType(name);
+      }
+      catch (VisADException e) {
+        result = null;
+      }
+    }
+    return result;
+  }
+
+  /** return any TextType constructed in this JVM with name,
+      or null */
+  public static synchronized TextType getTextTypeByName(String name) {
+    ScalarType text = ScalarType.getScalarTypeByName(name);
+    if (!(text instanceof TextType)) {
+      return null;
+    }
+    return (TextType) text;
+  }
+
+/* WLH 5 Jan 2000
+  public String toString() {
+    return getName() + "(Text)";
+  }
+*/
+
+  public String prettyString(int indent) {
+    // return toString();  WLH 5 Jan 2000
+    return getName() + "(Text)";
+  }
+
+  public Data missingData() throws VisADException {
+    return new Text(this);
+  }
+
+  public ShadowType buildShadowType(DataDisplayLink link, ShadowType parent)
+             throws VisADException, RemoteException {
+    return link.getRenderer().makeShadowTextType(this, link, parent);
+  }
+
+}
+
diff --git a/visad/Thing.java b/visad/Thing.java
new file mode 100644
index 0000000..55a33a1
--- /dev/null
+++ b/visad/Thing.java
@@ -0,0 +1,45 @@
+//
+// Thing.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   Thing is the top-level interface for objects that send
+   ThingChangedEvents to Actions.<p>
+*/
+public interface Thing {
+
+  /** add a ThingReference to this Thing object */
+  void addReference(ThingReference r)
+         throws VisADException, RemoteException;
+
+  /** remove a ThingReference from this Thing object */
+  void removeReference(ThingReference r)
+         throws VisADException, RemoteException;
+}
+
diff --git a/visad/ThingChangedEvent.java b/visad/ThingChangedEvent.java
new file mode 100644
index 0000000..b0c6fa9
--- /dev/null
+++ b/visad/ThingChangedEvent.java
@@ -0,0 +1,62 @@
+//
+// ThingChangedEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Event;
+
+/**
+   ThingChangedEvent is the VisAD class for changes in objects
+   (usually Data objects) referred to by ThingReference objects.
+   They are sourced by ThingReference objects and received by
+   Action objects.<P>
+*/
+public class ThingChangedEvent extends Event {
+
+  /** this is the id attached to the target ActionReferenceLink
+      of the target Action */
+  private long id;
+
+  /** this is the Tick value from the ThingReference change
+      that generated this ThingChangedEvent */
+  private long Tick;
+
+  public ThingChangedEvent(long jd, long tick) {
+    super(null, 0, null);
+    id = jd;
+    Tick = tick;
+  }
+
+  public long getId() {
+    return id;
+  }
+
+  public long getTick() {
+    return Tick;
+  }
+
+}
+
diff --git a/visad/ThingChangedLink.java b/visad/ThingChangedLink.java
new file mode 100644
index 0000000..c47b181
--- /dev/null
+++ b/visad/ThingChangedLink.java
@@ -0,0 +1,138 @@
+//
+// ThingChangedLink.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+
+/**
+   ThingChangedLink objects are used by ThingReference objects to
+   define their connections with Action objects.  That is, a
+   ThingReference has a Vector of ThingChangedLinks, one for
+   each attached Action.<P>
+
+   Action has a Vector of ReferenceActionLinks, one for
+   each attached ThingReference.<P>
+*/
+class ThingChangedLink extends Object {
+
+  private Action action;  // may be remote or local
+  private boolean Ball;
+                           // true when Action is ready for a ThingChangedEvent
+                           // false when this is waiting for an acknowledgement
+  private ThingChangedEvent event; // non-null only when !Ball
+
+  /** this id is from the corresponding ReferenceActionLink */
+  private long id;
+
+  ThingChangedLink(Action a, long jd) throws VisADException {
+    if (a == null) {
+      throw new ReferenceException("ThingChangedLink: Action cannot be null");
+    }
+    action = a;
+    Ball = true;
+    id = jd;
+  }
+
+  long getId() {
+    return id;
+  }
+
+  Action getAction() {
+    return action;
+  }
+
+  /** possibly return a new event to the Action */
+  public synchronized ThingChangedEvent peekThingChangedEvent()
+  {
+    return event;
+  }
+
+  /** acknowledge the last event from the ThingReference,
+   *  and possibly return a new event to the Action
+   */
+/* WLH 27 July 99 synchronized helps but does not fix */
+  // public ThingChangedEvent acknowledgeThingChangedEvent()
+  public synchronized ThingChangedEvent acknowledgeThingChangedEvent()
+  {
+    // if there is an event queued...
+    if (event != null) {
+
+      // pass the queued event back to the Action
+      ThingChangedEvent e = event;
+      event = null;
+      return e;
+    }
+
+    // remember that Action is ready for another event
+    Ball = true;
+/*
+if (action instanceof ActionImpl) {
+  System.out.println("ThingChangedLink.acknowledgeThingChangedEvent Ball = true");
+}
+else {
+  System.out.println("ThingChangedLink.acknowledgeThingChangedEvent Ball = true" +
+                   " REMOTE");
+}
+*/
+    return null;
+  }
+
+  /** either deliver the event to the corresponding Action object
+   *  or, if the Action isn't ready yet, queue the event for
+   *  later delivery
+   */
+/* WLH 27 July 99 synchronized helps but does not fix */
+  // public void queueThingChangedEvent(ThingChangedEvent e)
+  public synchronized void queueThingChangedEvent(ThingChangedEvent e)
+        throws RemoteException, VisADException
+  {
+    if (Ball) {
+      // if Action is ready for another event, pass it on
+/* WLH 27 July 99
+      Ball = action.thingChanged(e);
+*/
+      Ball = false;
+      boolean temp_ball = action.thingChanged(e);
+      if (temp_ball) Ball = true;
+/*
+if (action instanceof ActionImpl) {
+  System.out.println("ThingChangedLink.queueThingChangedEvent Ball = " + Ball);
+}
+else {
+  System.out.println("ThingChangedLink.queueThingChangedEvent Ball = " + Ball +
+                   " REMOTE");
+}
+*/
+    }
+    else {
+      // Action hasn't acknowledged previous event, queue this one
+      event = e;
+    }
+  }
+
+}
+
diff --git a/visad/ThingChangedListener.java b/visad/ThingChangedListener.java
new file mode 100644
index 0000000..60b5328
--- /dev/null
+++ b/visad/ThingChangedListener.java
@@ -0,0 +1,43 @@
+//
+// ThingChangedListener.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.EventListener;
+import java.rmi.*;
+
+/**
+   ThingChangedListener is the EventListener interface for
+   ThingChangedEvents.  ThingChangedListener is extended by
+   Action.<P>
+*/
+public interface ThingChangedListener extends EventListener {
+
+  boolean thingChanged(ThingChangedEvent e)
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/ThingImpl.java b/visad/ThingImpl.java
new file mode 100644
index 0000000..6f7f060
--- /dev/null
+++ b/visad/ThingImpl.java
@@ -0,0 +1,220 @@
+//
+// ThingImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+/**
+   ThingImpl is the abstract superclass of the VisAD objects that send
+   ThingChangedEvents to Actions.<p>
+*/
+// public abstract class ThingImpl  WLH 8 July 2000
+public class ThingImpl
+       implements Thing, Cloneable, java.io.Serializable {
+
+  class RemotePair {
+    RemoteThingReference ref;
+    RemoteThing data;
+
+    RemotePair(RemoteThingReference r, RemoteThing d)
+        throws ReferenceException {
+      if (r == null) {
+        throw new ReferenceException("Cannot create RemotePair " +
+                                     "from null RemoteThingReference");
+      }
+      if (d == null) {
+        throw new ReferenceException("Cannot create RemotePair " +
+                                     "from null RemoteThing");
+      }
+      ref = r;
+      data = d;
+    }
+
+    public boolean equals(Object pair) {
+
+      // make sure we're comparing against another RemotePair
+      if (!(pair instanceof RemotePair)) return false;
+
+      // check null reference/data conditions
+      RemotePair rp = (RemotePair )pair;
+
+      // ref/data are non-null, use the equals() method
+      return (ref.equals(((RemotePair) pair).ref) &&
+              data.equals(((RemotePair) pair).data));
+    }
+  }
+
+  /** vector of ThingReference that reference this Thing object */
+    //To save on memory let's not create this automatically, rather
+    //we create this on demand.
+    //          private transient Vector references = new Vector();
+    private transient Vector references;
+
+  public ThingImpl() {
+  }
+
+  /**
+   * Adds a listener for changes to this thing.  The listener will be notified
+   * when this thing changes.
+   *
+   * @param r                     The listener for changes.
+   * @throws RemoteVisADException if the listener isn't a {@link
+   *                              ThingReferenceImpl}.
+   * @throws VisADException       if a VisAD failure occurs.
+   */
+  public void addReference(ThingReference r) throws RemoteVisADException {
+    if (!(r instanceof ThingReferenceImpl)) {
+      throw new RemoteVisADException("ThingImpl.addReference: must use " +
+                                     "ThingReferenceImpl");
+    }
+    synchronized (this) {
+      if (references == null) references = new Vector();
+    }
+    references.addElement(r);
+/* DEBUG
+    System.out.println("ThingImpl " + " addReference " +
+                       "(" + System.getProperty("os.name") + ")");
+*/
+  }
+
+  /** method for use by RemoteThingImpl that adapts this ThingImpl */
+  void adaptedAddReference(RemoteThingReference r, RemoteThing t)
+        throws VisADException {
+    RemotePair p;
+    try {
+      p = new RemotePair(r, t);
+    } catch (ReferenceException e) {
+      throw new ReferenceException("ThingImpl.adaptedAddReference: " +
+                                   e.getMessage());
+    }
+    synchronized (this) {
+      if (references == null) references = new Vector();
+    }
+    references.addElement(p);
+/* DEBUG
+    System.out.println("ThingImpl.adaptedAddReference " +
+                       "(" + System.getProperty("os.name") + ")");
+*/
+  }
+
+  /** remove a ThingReference to this ThingImpl;
+      must be local ThingReferenceImpl;
+      called by ThingReference.setThing;
+      would like 'default' visibility here, but must be declared
+      'public' because it is defined in the Thing interface */
+  public void removeReference(ThingReference r)
+         throws VisADException {
+    if (references == null) {
+      throw new ReferenceException("ThingImpl.removeReference: already clear");
+    }
+    if (!(r instanceof ThingReferenceImpl)) {
+      throw new RemoteVisADException("ThingImpl.removeReference: must use " +
+                                     "RemoteThing for RemoteThingReference");
+    }
+    if (!references.removeElement(r)) {
+      throw new ReferenceException("ThingImpl.removeReference: not found");
+    }
+  }
+
+  /** method for use by RemoteThingImpl that adapts this ThingImpl */
+  void adaptedRemoveReference(RemoteThingReference r, RemoteThing t)
+       throws VisADException {
+    if (references == null) {
+      throw new ReferenceException("ThingImpl.removeReference: already clear");
+    }
+
+    RemotePair p;
+    try {
+      p = new RemotePair(r, t);
+    } catch (ReferenceException e) {
+      throw new ReferenceException("ThingImpl.adaptedRemoveReference: " +
+                                   e.getMessage());
+    }
+
+    if (!references.removeElement(p)) {
+      throw new ReferenceException("ThingImpl.adaptedRemoveReference: " +
+                                   " not found");
+    }
+  }
+
+  /** notify local ThingReferenceImpl-s that this ThingImpl has changed;
+      incTick in RemoteThingImpl for RemoteThingReferenceImpl-s;
+      would like 'default' visibility here, but must be declared
+      'public' because it is defined in the Thing interface */
+  public void notifyReferences()
+         throws VisADException, RemoteException {
+    if (references != null) {
+      // lock references for iterating through it
+      synchronized (references) {
+        Enumeration refs = references.elements();
+        while (refs.hasMoreElements()) {
+          Object r = refs.nextElement();
+          if (r instanceof ThingReferenceImpl) {
+            // notify local ThingReferenceImpl
+            ((ThingReferenceImpl) r).incTick();
+          }
+          else { // r instanceof RemotePair
+            // RemoteThingReference, so only incTick in
+            // local RemoteThingImpl
+            RemoteThing d = ((RemotePair) r).data;
+            d.incTick();
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * <p>Clones this instance.  Information on the set of listeners to changes in
+   * this instance is not cloned, so -- following the general contract of the
+   * <code>clone() </code> method -- subclasses should not test for equality of
+   * the set of listeners in any <code>equals(Object)</code> method.</p>
+   *
+   * <p>This implementation never throws {@link CloneNotSupportedException}.</p>
+   *
+   * @return                            A clone of this instance.
+   * @throws CloneNotSupportedException if cloning isn't supported.
+   */
+  public Object clone() throws CloneNotSupportedException {
+    ThingImpl clone;
+    
+    try {
+      clone = (ThingImpl)super.clone();
+    }
+    catch (CloneNotSupportedException ex) {
+      throw new Error("Assertion failure");  // can't happen
+    }
+
+    clone.references = new Vector();
+
+    return clone;
+  }
+
+}
+
diff --git a/visad/ThingReference.java b/visad/ThingReference.java
new file mode 100644
index 0000000..a410eb3
--- /dev/null
+++ b/visad/ThingReference.java
@@ -0,0 +1,71 @@
+//
+// ThingReference.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   ThingReference is the VisAD interface for named holders for
+   objects.  It can provide a symbol table binding between a name and
+   a variable for a user interface that includes a formula interpreter,
+   or a full language interpreter (e.g., a Java interpreter).<P>
+
+   During computations the object referenced by a ThingReference
+   may change.<p>
+
+   ThingReference is a source of ThingChangedEvent-s, and thus defines
+   addThingChangedListener and removeThingChangedListener.<P>
+
+   ThingReference objects may be local (ThingReferenceImpl) or
+   remote (RemoteThingReferenceImpl).<P>
+*/
+public interface ThingReference {
+
+  /** invokes t.addReference((ThingReference r) */
+  void setThing(Thing t) throws VisADException, RemoteException;
+
+  Thing getThing() throws VisADException, RemoteException;
+
+  long getTick() throws VisADException, RemoteException;
+
+  long incTick() throws VisADException, RemoteException;
+
+  String getName() throws VisADException, RemoteException;
+
+  void addThingChangedListener(ThingChangedListener l, long id)
+         throws VisADException, RemoteException;
+
+  void removeThingChangedListener(ThingChangedListener l)
+         throws VisADException, RemoteException;
+
+  ThingChangedEvent acknowledgeThingChanged(Action a)
+         throws VisADException, RemoteException;
+
+  ThingChangedEvent peekThingChanged(Action a)
+         throws VisADException, RemoteException;
+}
+
diff --git a/visad/ThingReferenceImpl.java b/visad/ThingReferenceImpl.java
new file mode 100644
index 0000000..7fc63e0
--- /dev/null
+++ b/visad/ThingReferenceImpl.java
@@ -0,0 +1,314 @@
+//
+// ThingReferenceImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   ThingReferenceImpl is a local implementation of ThingReference.<P>
+
+   ThingReferenceImpl is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public class ThingReferenceImpl extends Object implements ThingReference {
+
+  /** name of scalar type */
+  String Name;
+
+  /** Thing object refered to (mutable);
+      ThingReferenceImpl is not Serializable, but mark as transient anyway */
+  private transient Thing thing;
+  /** ref = this if thing is local
+      ref = a RemoteThingReferenceImpl if thing is remote;
+      ThingReferenceImpl is not Serializable, but mark as transient anyway */
+  private transient ThingReference ref;
+
+  /** Tick increments each time thing changes */
+  private long Tick;
+
+  /** Vector of ThingChangedLinks;
+      ThingReferenceImpl is not Serializable, but mark as transient anyway */
+  transient Vector ListenerVector = new Vector();
+
+  /**
+   * Constructs from a name for the instance.
+   *
+   * @param name                   The name for this instance.
+   * @throws VisADException        if the name is <code>null</code>.
+   */
+  public ThingReferenceImpl(String name) throws VisADException {
+    if (name == null) {
+      throw new VisADException("ThingReference: name cannot be null");
+    }
+    Name = name;
+    Tick = Long.MIN_VALUE + 1;
+  }
+
+  public Thing getThing() {
+    return thing;
+  }
+
+  /**
+   * Sets the underlying thing to reference.
+   *
+   * @param t                      The thing to reference.
+   * @throws RemoteVisADException if the thing is a {@link RemoteThing}.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  public synchronized void setThing(Thing t)
+         throws VisADException, RemoteException {
+/* WLH 9 July 98
+    if (t == null) {
+      throw new ReferenceException("ThingReferenceImpl: thing cannot be null");
+    }
+*/
+    if (t instanceof RemoteThing) {
+      throw new RemoteVisADException("ThingReferenceImpl.setThing: cannot use " +
+                                     "RemoteThing");
+    }
+    if (thing != null) thing.removeReference(ref);
+    ref = this;
+    thing = t;
+    if (t != null) t.addReference(ref);
+    incTick();
+  }
+
+  /** method for use by RemoteThingReferenceImpl that adapts this
+      ThingReferenceImpl */
+  synchronized void adaptedSetThing(RemoteThing t, RemoteThingReference r)
+               throws VisADException, RemoteException {
+    if (thing != null) thing.removeReference(ref);
+    ref = r;
+    thing = t;
+    t.addReference(ref);
+    incTick();
+  }
+
+  public long getTick() {
+    return Tick;
+  }
+
+  /** synchronized because incTick, setThing, and adaptedSetThing
+      share access to thing and ref */
+  public synchronized long incTick()
+         throws VisADException, RemoteException {
+// if (getName() != null) DisplayImpl.printStack("incTick " + getName());
+    Tick += 1;
+    if (Tick == Long.MAX_VALUE) Tick = Long.MIN_VALUE + 1;
+    if (ListenerVector != null) {
+      synchronized (ListenerVector) {
+
+/* CTR 26 May 2000 handle remote disconnects more robustly
+        Enumeration listeners = ListenerVector.elements();
+        while (listeners.hasMoreElements()) {
+          ThingChangedLink listener =
+            (ThingChangedLink) listeners.nextElement();
+          ThingChangedEvent e =
+            new ThingChangedEvent(listener.getId(), Tick);
+          listener.queueThingChangedEvent(e);
+        }
+*/
+        int i = 0;
+        while (i < ListenerVector.size()) {
+          ThingChangedLink listener =
+            (ThingChangedLink) ListenerVector.elementAt(i);
+          ThingChangedEvent e =
+            new ThingChangedEvent(listener.getId(), Tick);
+          try {
+            listener.queueThingChangedEvent(e);
+            i++;
+          }
+          catch (ConnectException exc) {
+            // CTR 26 May 2000 remote listener has died; remove it from list
+            ListenerVector.remove(i);
+          }
+        }
+
+      }
+    }
+    return Tick;
+  }
+
+  public ThingChangedEvent peekThingChanged(Action a)
+         throws VisADException {
+    if (!(a instanceof ActionImpl)) {
+      throw new RemoteVisADException("ThingReferenceImpl.peekThingChanged:" +
+                                     " Action must be local");
+    }
+    if (ListenerVector == null) return null;
+    ThingChangedLink listener = findThingChangedLink(a);
+    if (listener == null) {
+      return null;
+    }
+    return listener.peekThingChangedEvent();
+  }
+
+  public ThingChangedEvent acknowledgeThingChanged(Action a)
+         throws VisADException {
+    if (!(a instanceof ActionImpl)) {
+      throw new RemoteVisADException("ThingReferenceImpl.acknowledgeThingChanged:" +
+                                     " Action must be local");
+    }
+    if (ListenerVector == null) return null;
+    ThingChangedLink listener = findThingChangedLink(a);
+    if (listener == null) {
+      return null;
+    }
+    return listener.acknowledgeThingChangedEvent();
+  }
+
+  public ThingChangedEvent adaptedPeekThingChanged(RemoteAction a)
+         throws VisADException {
+    if (ListenerVector == null) return null;
+    ThingChangedLink listener = findThingChangedLink(a);
+    if (listener == null) {
+      return null;
+    }
+    return listener.peekThingChangedEvent();
+  }
+
+  public ThingChangedEvent adaptedAcknowledgeThingChanged(RemoteAction a)
+         throws VisADException {
+    if (ListenerVector == null) return null;
+    ThingChangedLink listener = findThingChangedLink(a);
+    if (listener == null) {
+      return null;
+    }
+    return listener.acknowledgeThingChangedEvent();
+  }
+
+  /** find ThingChangedLink with action */
+  public ThingChangedLink findThingChangedLink(Action a)
+         throws VisADException {
+    if (a == null) {
+      throw new ReferenceException("ThingReferenceImpl.findThingChangedLink: " +
+                                   "Action cannot be null");
+    }
+    if (ListenerVector == null) return null;
+    synchronized (ListenerVector) {
+      Enumeration listeners = ListenerVector.elements();
+      while (listeners.hasMoreElements()) {
+        ThingChangedLink listener =
+          (ThingChangedLink) listeners.nextElement();
+        if (a.equals(listener.getAction())) return listener;
+      }
+    }
+    return null;
+  }
+
+  public String getName() {
+    return Name;
+  }
+
+  /**
+   * Adds a listener for changes in the underlying {@link Thing}.  If the
+   * thing changes, then the listener is notified.
+   *
+   * @param listener              The listener.
+   * @param id                    The id of the corresponding {@link ReferenceActionLink}.
+   * @throws RemoteVisADException if the listener isn't an {@link ActionImpl}.
+   * @throws ReferenceException   if the listener is already registered.
+   * @throws VisADException       if a VisAD failure occurs in a subsystem.
+   */
+  public void addThingChangedListener(ThingChangedListener listener, long id)
+         throws RemoteVisADException, ReferenceException, VisADException {
+    if (!(listener instanceof ActionImpl)) {
+      throw new RemoteVisADException("ThingReferenceImpl.addThingChanged" +
+                                     "Listener: Action must be local");
+    }
+    synchronized (this) {
+      if (ListenerVector == null) ListenerVector = new Vector();
+    }
+    synchronized(ListenerVector) {
+      if (findThingChangedLink((ActionImpl) listener) != null) {
+        throw new ReferenceException("ThingReferenceImpl.addThingChangedListener:" +
+                                     " link to Action already exists");
+      }
+      ListenerVector.addElement(new ThingChangedLink((ActionImpl) listener, id));
+    }
+  }
+
+  /** method for use by RemoteThingReferenceImpl that adapts this
+      ThingReferenceImpl */
+  void adaptedAddThingChangedListener(RemoteAction a, long id)
+       throws VisADException {
+    synchronized (this) {
+      if (ListenerVector == null) ListenerVector = new Vector();
+    }
+    synchronized(ListenerVector) {
+      if (findThingChangedLink(a) != null) {
+        throw new ReferenceException("ThingReferenceImpl.addThingChangedListener:" +
+                                     " link to Action already exists");
+      }
+      ListenerVector.addElement(new ThingChangedLink(a, id));
+    }
+  }
+
+  /** ThingChangedListener must be local ActionImpl */
+  public void removeThingChangedListener(ThingChangedListener a)
+         throws VisADException {
+    if (!(a instanceof ActionImpl)) {
+      throw new RemoteVisADException("ThingReferenceImpl.removeThingChanged" +
+                                     "Listener: Action must be local");
+    }
+    if (ListenerVector != null) {
+      synchronized(ListenerVector) {
+        ThingChangedLink listener = findThingChangedLink((ActionImpl) a);
+        if (listener != null) {
+          ListenerVector.removeElement(listener);
+        }
+      }
+    }
+  }
+
+  /** method for use by RemoteThingReferenceImpl that adapts this
+      ThingReferenceImpl */
+  void adaptedRemoveThingChangedListener(RemoteAction a)
+       throws VisADException {
+    if (ListenerVector != null) {
+      synchronized(ListenerVector) {
+        ThingChangedLink listener = findThingChangedLink(a);
+        if (listener != null) {
+          ListenerVector.removeElement(listener);
+        }
+      }
+    }
+  }
+
+  public boolean equals(Object obj) {
+    if (!(obj instanceof ThingReference)) return false;
+    return obj == this;
+  }
+
+  public String toString() {
+    return "ThingReference " + Name;
+  }
+
+}
+
diff --git a/visad/ToggleControl.java b/visad/ToggleControl.java
new file mode 100644
index 0000000..de9b424
--- /dev/null
+++ b/visad/ToggleControl.java
@@ -0,0 +1,146 @@
+//
+// ToggleControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import visad.browser.Convert;
+
+/**
+   ToggleControl is the VisAD class for toggling other Control-s
+   on and off.<P>
+*/
+public class ToggleControl extends Control {
+
+  private boolean on;
+  private Control parent;
+
+  public ToggleControl(DisplayImpl d, Control p) {
+    super(d);
+    parent = p;
+    on = true;
+  }
+
+  public Control getParent() {
+    return parent;
+  }
+
+  public boolean getOn() {
+    return on;
+  }
+
+  public void setOn(boolean o)
+         throws VisADException, RemoteException {
+    on = o;
+    changeControl(true);
+  }
+
+  private boolean parentEquals(Control newParent)
+  {
+    if (parent == null) {
+      if (newParent != null) {
+        return false;
+      }
+    } else if (newParent == null) {
+      return false;
+    } else if (!parent.equals(newParent)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /** get a string that can be used to reconstruct this control later */
+  public String getSaveString() {
+    return "" + on;
+  }
+
+  /** reconstruct this control using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    setOn(Convert.getBoolean(save));
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof ToggleControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    ToggleControl tc = (ToggleControl )rmt;
+
+    boolean changed = false;
+
+    if (on != tc.on) {
+      changed = true;
+      on = tc.on;
+    }
+
+    if (!parentEquals(tc.parent)) {
+      changed = true;
+      parent = tc.parent;
+    }
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    ToggleControl tc = (ToggleControl )o;
+
+    if (on != tc.on) {
+      return false;
+    }
+
+    if (!parentEquals(tc.parent)) {
+      return false;
+    }
+
+    return true;
+  }
+}
+
diff --git a/visad/TriangleStripBuilder.java b/visad/TriangleStripBuilder.java
new file mode 100644
index 0000000..d6d6d75
--- /dev/null
+++ b/visad/TriangleStripBuilder.java
@@ -0,0 +1,550 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+package visad;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+import visad.util.FloatTupleArray;
+
+/**
+ * Used by <code>Contour2D</code>'s filled contour algorithm to build a
+ * <code>VisADTriangleStripArray</code>. Triangle vertices, normals, and colors
+ * are stored until the <code>compile</code> is called, at which point the
+ * builder is invalidated. <p> This code is very specific and makes many
+ * assumptions about the data used to create the strips. Input validation is
+ * kept to a minimum, and when input is validated it is done with assertions so
+ * they can be enabled and disabled at runtime.
+ * <p>
+ * Arrays of vertices and normals grow dynamically when needed to avoid an 
+ * initial large array allocation.
+ * @see {@link visad.test.FloatTupleArray}
+ */
+class TriangleStripBuilder {
+
+  static Logger log = Logger.getLogger(TriangleStripBuilder.class.getName());
+  /**
+   * Flag enabling strip merging with previous grid boxes.
+   */
+  public static final int MERGE_PREVIOUS = 2 ^ 0;
+  /**
+   * Flag disabling all merging. All other policy flags are ignored.
+   */
+  public static final int MERGE_NONE = 0;
+  
+  /**
+   * Default policy for strip merging. NOTE: Currently the default is set to
+   * only merge previous. The algorithm for merging in the current grid box does
+   * not work properly.
+   */
+  public static final int DEF_MERGE_POLICY = MERGE_PREVIOUS;
+  /** Vertices for the entire grid. */
+  private final FloatTupleArray vertices;
+  /** Normals corresponding to the vertices for the entire grid. */
+  private final FloatTupleArray normals;
+  /** Number of grid rows. */
+  private int gridRows;
+  /** Number of grid columns. */
+  private int gridCols;
+  /** The current grid box number. */
+  private int curBoxNum;
+  /** Metadata for the previous grid box. */
+  private GridBox prevGridBox;
+  /** Metadata for the current grid box. */
+  private GridBox curGridBox;
+  /**
+   * Strip objects that will be compiled into strip counts and color arrays.
+   */
+  private final List<StripProps> strips;
+  /** Dimension of the color arrays. */
+  private final int colorDim;
+  /** If true merging in the previous grid box will be attempted. */
+  private boolean mergePrevious;
+  private VisADTriangleStripArray compiledStrip;
+  
+  private int numFlipped;
+  private int numMerged;
+
+  /**
+   * New instance with the default coordinate delta and merging policy.
+   * 
+   * @param rows Number of grid rows
+   * @param cols Number of grid cols
+   * @param colorDim number of color elements
+   */
+  TriangleStripBuilder(int rows, int cols, int colorDim) {
+    this(rows, cols, colorDim, DEF_MERGE_POLICY);
+  }
+
+  /**
+   * New instance with the default coordinate delta.
+   * 
+   * @param rows Number of grid rows
+   * @param cols Number of grid cols
+   * @param colorDim number of color elements
+   * @param mergePolicy A logical OR'ing of <code>MERGE</code> constants that
+   *          specifies the options used when merging strips.
+   */
+  TriangleStripBuilder(int rows, int cols, int colorDim, int mergePolicy) {
+    this.gridRows = rows;
+    this.gridCols = cols;
+    this.curBoxNum = 0;
+    this.colorDim = colorDim;
+    this.mergePrevious = (mergePolicy & MERGE_PREVIOUS) == MERGE_PREVIOUS;
+    log.finer("mergePolicy prev:" + mergePrevious);
+
+    this.vertices = FloatTupleArray.Factory.newInstance(2, rows * cols * 4);
+    this.normals = FloatTupleArray.Factory.newInstance(3, rows * cols * 4);
+
+    strips = new ArrayList<StripProps>();
+    
+    compiledStrip = null;
+  }
+
+  /**
+   * Compile the computed strips into a scene graph object. The first time compile
+   * is called a <code>VisADTriangleStripArray</code> is created and subsequent 
+   * calls return the initially compiled object.
+   * 
+   * @param set Used to convert grid coordinates to display coordinates.
+   * @return A valid object ready for adding to the scene graph.
+   * @throws visad.VisADException
+   */
+  public VisADTriangleStripArray compile(Gridded3DSet set) throws VisADException {
+    int totalBoxes = gridRows * gridCols;
+    if (curBoxNum != totalBoxes) {
+      throw new IllegalStateException(String.format("Not finished. On gridbox %s of %s", curBoxNum,
+          totalBoxes));
+    }
+    if (compiledStrip != null) return compiledStrip;
+
+    byte[] colors = new byte[colorDim * vertices.size()];
+
+    // stripVertexCounts are the number of vertices that comprise each
+    // individual strip, which were compiled in the StripProps.vertexCount-s
+    int[] stripVertexCounts = new int[strips.size()];
+    for (int sIdx = 0, clrIdx = 0; sIdx < strips.size(); sIdx++) {
+      StripProps strip = strips.get(sIdx);
+      // each vertex for a strip gets the same color
+      for (int vIdx = 0; vIdx < strip.vertexCount; vIdx++) {
+        for (int i = 0; i < colorDim; i++) {
+          colors[clrIdx++] = strip.color[i];
+        }
+      }
+      stripVertexCounts[sIdx] = strip.vertexCount;
+    }
+
+    // compile normals.
+    float[][] curNormals = normals.toArray();
+    float[] newNormals = new float[curNormals.length * curNormals[0].length];
+    for (int i = 0, j = 0; i < curNormals[0].length; i++) {
+      newNormals[j++] = curNormals[0][i];
+      newNormals[j++] = curNormals[1][i];
+      newNormals[j++] = curNormals[2][i];
+    }
+
+    try {
+      compiledStrip = new VisADTriangleStripArray();
+      // number of vertices for each individual strips
+      compiledStrip.stripVertexCounts = stripVertexCounts;
+      // number of vertices in this geometry
+      compiledStrip.vertexCount = vertices.size();
+      compiledStrip.coordinates = set.gridToValue(vertices.toArray(), true)[0];
+      compiledStrip.normals = newNormals;
+      compiledStrip.colors = colors;
+    } catch (VisADException e) {
+      // ensure null strip is an exception occurs
+      compiledStrip = null;
+      throw e;
+    }
+    
+    log.fine("compiled " + toString());
+    //System.err.println("compiled " + toString());
+    
+    return compiledStrip;
+  }
+
+  /**
+   * Add vertices to the builder. Merging is performed according to either the
+   * default policy or the policy provided during instantiation. Unless an error
+   * occurs the vertices provided are always either merged with a current strip 
+   * or used to create a new strip.
+   * <p>
+   * Assumptions:
+   * <ol>
+   * <li>Caller will not give points in an order that will cause triangles to 
+   * be folded over one another.
+   * <li>Caller will provide strips, not individual triangle vertices. 
+   * <li>Grid is traversed left to right.
+   * </ol>
+   * 
+   * @param kase flag indicating the contour case that generated these vertices.
+   * @param side flag indicating the side of the grid box the vertices end on.
+   * @param orientation flag indicating the orientation of the vertices.
+   */
+  public void addVerticies(int lvlIdx, float[][] verts, float[][] norms, byte[] color,
+      byte sideFirst, byte orientFirst, byte sideLast, byte orientLast) {
+
+
+    StripProps strip = null;
+
+    //
+    // MERGE IN PREVIOUS GRID BOX
+    //
+    
+    if (mergePrevious && curGridBox.strips.size() == 0) {
+      strip = mergeToPrevious(lvlIdx, verts, norms, sideFirst, orientFirst, sideLast, 
+          orientLast);
+    }
+
+    //
+    // CREATE NEW STRIP
+    //
+    if (strip == null) {
+      strip = new StripProps(color, lvlIdx, sideFirst, orientFirst, sideLast, orientLast);
+
+      this.vertices.add(verts);
+      this.normals.add(norms);
+      strip.vertexCount = verts[0].length;
+
+      strips.add(strip);
+      curGridBox.add(strip);
+    }
+
+    // // strip cannot be null here
+    // int start = this.vertices.size() - strip.vertexCount;
+    // log.finest(toString(this.vertices.elements(), start,
+    // strip.vertexCount));
+  }
+  
+  /**
+   * Attempt to merge vertices with a strip in the previous grid box.
+   * @param lvlIdx
+   * @param verts
+   * @param norms
+   * @param kase flag indicating the contour case that generated these vertices.
+   * @param side flag indicating the side of the grid box the vertices end on.
+   * @param orientation flag indicating the orientation of the vertices.
+   * 
+   * @return the merged strip, or null if no merge took place
+   */
+  protected StripProps mergeToPrevious(int lvlIdx, float[][] verts, float[][] norms,
+      byte sideFirst, byte orientFirst, byte sideLast, byte orientLast) {
+
+    boolean modified = false;
+    //currently we can only merge to the last strip added to the prev grid box
+    StripProps prevStrip = prevGridBox == null ? null : prevGridBox.lastStrip();
+
+    boolean firstCol = curBoxNum % gridCols == 1; // can't when in first column
+    boolean correctLvl = false; // has to have the same color level index
+    boolean shareSide = false; // have to share a side
+    boolean oppositeOrient = false; // have to have the opposite orientation
+    if (prevStrip != null) {
+      correctLvl = prevStrip.lvlIdx == lvlIdx;
+      shareSide = prevStrip.sideLast == Contour2D.SIDE_RIGHT && sideFirst == Contour2D.SIDE_LEFT;
+      if (prevStrip.orientLast == Contour2D.CLOCKWISE) {
+        oppositeOrient = (orientFirst == Contour2D.CNTRCLOCKWISE);
+      } else {
+        oppositeOrient = (orientFirst == Contour2D.CLOCKWISE);
+      }
+    }
+    
+    if (!firstCol && correctLvl && shareSide && oppositeOrient) {
+      
+      if (!oppositeOrient) {
+        // FIXME: (1) no flipping for now, (2) if flipped then somehow new orientation has to be recorded.
+        boolean mustFlip = false;
+        if (mustFlip) {
+          flipStrip(verts, norms);
+          oppositeOrient = true;
+          numFlipped++;
+        }
+      }
+
+      // add the new
+      int num = verts[0].length - 2;
+      this.vertices.add(verts, 2, num);
+      this.normals.add(norms, 2, num);
+      prevStrip.vertexCount += num;
+      prevStrip.sideLast = sideLast;
+      prevStrip.orientLast = orientLast;
+      modified = true;
+
+      // since we merged a current box strip with one in the
+      // previous box, we swap it's ownership to the current box
+      prevGridBox.strips.remove(prevStrip);
+      curGridBox.strips.add(prevStrip);
+      
+      numMerged++;
+    }
+    return modified ? prevStrip : null;
+  }
+  
+  public String toString() {
+    float avgLen = strips.size() == 0 ? 0 : vertices.size() / strips.size();
+    int maxLen = 0;
+    for (int i=0; i<strips.size(); i++) {
+      StripProps strip = strips.get(i);
+      if (strip.vertexCount > maxLen) maxLen = strip.vertexCount;
+    }
+    return String.format("<%s numStrips=%s numFlipped=%s avgLen=%s maxLen=%s numMerged=%s>",
+        TriangleStripBuilder.class.getName(), strips.size(), numFlipped, avgLen, maxLen, numMerged);
+  }
+
+  /**
+   * Set the current grid box. The previous grid box will be cleared and will
+   * become the former current grid box.
+   * 
+   * @param row
+   * @param col
+   */
+  public void setGridBox(int row, int col) {
+    if (prevGridBox != null) {
+      prevGridBox.strips.clear();
+    }
+    prevGridBox = curGridBox;
+    curBoxNum++;
+    curGridBox = new GridBox(row, col);
+  }
+
+  /**
+   * Container for grid box metadata.
+   */
+  class GridBox {
+
+    int row;
+    int col;
+    final List<StripProps> strips;
+
+    GridBox(int row, int col) {
+      this.row = row;
+      this.col = col;
+      strips = new ArrayList<StripProps>(3);
+    }
+
+    void add(StripProps strip) {
+      strips.add(strip);
+      assert strip.vertexCount >= 3 : "Stripcount < 3";
+    }
+
+    StripProps lastStrip() {
+      return strips.size() > 0 ? strips.get(strips.size() - 1) : null;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("<GridBox row=%s col=%s>", row, col);
+    }
+  }
+
+  /**
+   * Container for strip metadata.
+   */
+  class StripProps {
+
+    /** Color for all vertices. */
+    final byte[] color;
+    /** Number of vertices. Must be at least 2. */
+    int vertexCount;
+    /** Index into the contour level array for this strip. */
+    final int lvlIdx;
+    
+    final byte sideFirst;
+    final byte orientFirst;
+    byte sideLast;
+    byte orientLast;
+
+    boolean flipped = false;
+    
+    StripProps(byte[] color, int lvlIdx, byte sideFirst, byte orientFirst,
+        byte sideLast, byte orientLast) {
+      this.color = color;
+      this.lvlIdx = lvlIdx;
+      this.sideFirst = sideFirst;
+      this.orientFirst = orientFirst;
+      this.sideLast = sideLast;
+      this.orientLast = orientLast;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("<StripProps count=%s lvlIdx=%s color=%s>", vertexCount, lvlIdx, 
+          Arrays.toString(color));
+    }
+  }
+  
+  /**
+   * Used in coordinate comparisons.
+   */
+  static final double DEF_COORD_DELTA = 2.1e-5;
+
+  static boolean coordEquals(float c1, float c2, double delta) {
+    return Math.abs(c1 - c2) <= delta;
+  }
+
+  /** Alias for checking coordinate equality. */
+  static boolean coordEquals(float c1, float c2) {
+    return coordEquals(c1, c2, DEF_COORD_DELTA);
+  }
+
+  static boolean coordsEqual(FloatTupleArray v1, int idx1, float[][] v2, int idx2, double delta) {
+    boolean match = true;
+    for (int dim = 0; dim < v1.dim(); dim++) {
+      match &= coordEquals(v1.get(dim, idx1), v2[dim][idx2], delta);
+    }
+    return match;
+  }
+
+  /**
+   * Check if coordinates are equal.
+   * 
+   * @param v1 Vertex array 1
+   * @param idx1 Index of the coordinates to check in v1
+   * @param v2 Vertex array 2
+   * @param idx2 Index of coordinates to check in v2
+   * @return True if all components at the given indices are equal.
+   */
+  static boolean coordsEqual(FloatTupleArray v1, int idx1, float[][] v2, int idx2) {
+    return coordsEqual(v1, idx1, v2, idx2, DEF_COORD_DELTA);
+  }
+
+  /**
+   * Check if 2 vertices of a line match 2 vertices of a triangle.
+   * 
+   * @param v1 Vertex array of the line
+   * @param idx1 Index of the first point of the line, where idx1 + 1 is the
+   *          second.
+   * @param v2 Vertex array of the triangle
+   * @param idx2 Index of the first vertex of the triangle, where idx+1 and
+   *          idx1+2 will be the next two.
+   * @param delta delta to use for comparisons
+   * @return An array with the matching triangle vertices in index 1 and 2 and
+   *         the unmatched triangle vertex index at index 3. If no match is
+   *         found return null.
+   */
+  static synchronized int[] stripCoordEquals(FloatTupleArray stripVerticies, int sIdx,
+      float[][] triVerticies, int tIdx) {
+    return stripCoordEquals(stripVerticies, sIdx, triVerticies, tIdx, DEF_COORD_DELTA);
+  }
+
+  static synchronized int[] stripCoordEquals(FloatTupleArray stripVerticies, int sIdx,
+      float[][] triVerticies, int tIdx, double delta) {
+    assert sIdx + 1 < stripVerticies.size();
+    assert tIdx + 2 < triVerticies[0].length;
+
+    int s1 = sIdx, s2 = sIdx + 1;
+    int t1 = tIdx, t2 = tIdx + 1, t3 = tIdx + 2;
+
+    if (coordsEqual(stripVerticies, s1, triVerticies, t1, delta)) {
+      if (coordsEqual(stripVerticies, s2, triVerticies, t2, delta)) {
+        return new int[] { t1, t2, t3 };
+
+      } else if (coordsEqual(stripVerticies, s2, triVerticies, t3, delta)) {
+        return new int[] { t1, t3, t2 };
+
+      }
+    } else if (coordsEqual(stripVerticies, s1, triVerticies, t2, delta)) {
+      if (coordsEqual(stripVerticies, s2, triVerticies, t1, delta)) {
+        return new int[] { t2, t1, t3 };
+
+      } else if (coordsEqual(stripVerticies, s2, triVerticies, t3, delta)) {
+        return new int[] { t2, t3, t1 };
+
+      }
+
+    } else if (coordsEqual(stripVerticies, s1, triVerticies, t3, delta)) {
+      if (coordsEqual(stripVerticies, s2, triVerticies, t1, delta)) {
+        return new int[] { t3, t1, t2 };
+
+      } else if (coordsEqual(stripVerticies, s2, triVerticies, t2, delta)) {
+        return new int[] { t3, t2, t1 };
+
+      }
+    }
+    return null;
+  }
+
+  static float[] extractCoords(float[][] vertices, int idx) {
+    float[] coords = new float[vertices.length];
+    for (int i = 0; i < vertices.length; i++) {
+      coords[i] = vertices[i][idx];
+    }
+    return coords;
+  }
+
+  static String toString(float[][] coords) {
+    int len = coords.length > 0 ? coords[0].length : 0;
+    return toString(coords, 0, len);
+  }
+
+  static String toString(float[][] coords, int start, int num) {
+    StringBuilder buf = new StringBuilder();
+    for (int i = start, cnt = 0; cnt < num; i++, cnt++) {
+      buf.append("(");
+      for (int dim = 0; dim < coords.length - 1; dim++) {
+        buf.append("" + coords[dim][i] + ",");
+      }
+      buf.append("" + coords[coords.length - 1][i] + ") ");
+    }
+    return buf.toString();
+  }
+
+  static void flipStrip(float[][] verts, float[][] norms) {
+    int num = verts[0].length % 2 == 0 ? verts[0].length - 1 : verts[0].length - 2;
+    for (int i = 0; i < num; i += 2) {
+      float tv = verts[0][i];
+      verts[0][i] = verts[0][i + 1];
+      verts[0][i + 1] = tv;
+      tv = verts[1][i];
+      verts[1][i] = verts[1][i + 1];
+      verts[1][i + 1] = tv;
+
+      float tn = norms[0][i];
+      norms[0][i] = norms[0][i + 1];
+      norms[0][i + 1] = tn;
+      tn = norms[1][i];
+      norms[1][i] = norms[1][i + 1];
+      norms[1][i + 1] = tn;
+      tn = norms[2][i];
+      norms[2][i] = norms[2][i + 1];
+      norms[2][i + 1] = tn;
+    }
+  }
+
+  static String toString(FloatTupleArray coords) {
+    return toString(coords, 0, coords.size());
+  }
+
+  static String toString(FloatTupleArray coords, int start, int num) {
+    StringBuilder buf = new StringBuilder();
+    for (int i = start, cnt = 0; cnt < num; i++, cnt++) {
+      buf.append("(");
+      for (int dim = 0; dim < coords.dim() - 1; dim++) {
+        buf.append("" + coords.get(dim, i) + ",");
+      }
+      buf.append("" + coords.get(coords.dim() - 1, i) + ") ");
+    }
+    return buf.toString();
+  }
+}
diff --git a/visad/Tuple.java b/visad/Tuple.java
new file mode 100644
index 0000000..2666864
--- /dev/null
+++ b/visad/Tuple.java
@@ -0,0 +1,561 @@
+//
+// Tuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.RemoteException;
+import java.util.Vector;
+
+/**
+   <P>Tuple is the general VisAD data class for vectors.
+   Tuple objects are immutable in the sense that the sequence of {@link Data}
+   objects cannot be modified; Tuple objects are mutable, however, in the
+   sense that a contained {@link Data} object might be mutable (e.g. it might
+   be a {@link Field} with modifiable range values).</P>
+*/
+public class Tuple extends DataImpl implements TupleIface {
+
+  Data[] tupleComponents;  // might be null (see Tuple(TupleType))
+
+  /** 
+   * Construct a Tuple object with missing value 
+   * @param  type  the TupleType
+   */
+  public Tuple(TupleType type) {
+    super(type);
+    if (type instanceof RealTupleType &&
+        !(this instanceof RealTuple)) {
+      throw new VisADError("must construct as RealTuple");
+    }
+  }
+
+  /** 
+   * Construct a Tuple object from a type and an array of Data objects 
+   * @param  type  the TupleType
+   * @param  datums  the Data components
+   */
+  public Tuple(TupleType type, Data[] datums)
+         throws VisADException, RemoteException {
+    this(type, datums, true);
+  }
+
+  /** 
+   * Construct a Tuple object from a type and an array of Data objects 
+   * @param  type  the TupleType
+   * @param  datums  the Data components
+   * @param  copy  if true, copy the data objects
+   */
+  public Tuple(TupleType type, Data[] datums, boolean copy)
+         throws VisADException, RemoteException {
+    this(type, datums, copy, true);
+  }
+
+  /** 
+   * Construct a Tuple object from a type and an array of Data objects 
+   * @param  type  the TupleType
+   * @param  datums  the Data components
+   * @param  copy  if true, copy the data objects
+   * @param  checkType  if true, make sure the type matches the datum types.
+   */
+  public Tuple(TupleType type, Data[] datums, boolean copy, boolean checkType)
+         throws VisADException, RemoteException {
+    super(type);
+    if (checkType && !checkTupleType(type, datums)) {
+      throw new TypeException("Tuple: type does not match data");
+    }
+    if (type instanceof RealTupleType &&
+        !(this instanceof RealTuple)) {
+      throw new TypeException("must construct as RealTuple");
+    }
+    int n = datums.length;
+    tupleComponents = new Data[n];
+    for (int i=0; i<n; i++) {
+      if (copy) {
+        tupleComponents[i] = (Data) datums[i].dataClone();
+      }
+      else {
+        tupleComponents[i] = datums[i];
+      }
+      if (tupleComponents[i] instanceof DataImpl) {
+        ((DataImpl) tupleComponents[i]).setParent(this);
+      }
+    }
+  }
+
+  /** 
+   * Construct a Tuple object from an array of Data objects;
+   * this constructs its MathType from the MathTypes of the
+   * data array
+   *
+   * @param  datums  the Data components
+   * @param  copy  if true, copy the data objects
+   */
+  public Tuple(Data[] datums, boolean copy)
+         throws VisADException, RemoteException {
+    this(buildTupleType(datums), datums, copy, false);
+  }
+
+  /** 
+   * Construct a Tuple object from an array of Data objects;
+   * this constructs its MathType from the MathTypes of the
+   * data array
+   *
+   * @param  datums  the Data components
+   */
+  public Tuple(Data[] datums)
+         throws VisADException, RemoteException {
+    this(buildTupleType(datums), datums, true, false);
+  }
+
+  /**
+   * Create a Tuple from an array of Data objects.  
+   * @return a Tuple.
+   */
+  public static Tuple makeTuple(Data[] datums)
+         throws VisADException, RemoteException {
+    return new Tuple(datums);
+  }
+
+  /** Check a TupleType for an array of Data */
+  static boolean checkTupleType(TupleType type, Data[] datums)
+         throws VisADException, RemoteException {
+    if (datums == null || type == null) return false;
+    int n = datums.length;
+    if (n != type.getDimension()) return false;
+    for (int i=0; i<n; i++) {
+      if (!type.getComponent(i).equals(datums[i].getType())) return false;
+    }
+    return true;
+  }
+
+  /** 
+   * Make a TupleType for an array of Data 
+   * @param datums  array of Data objects
+   */
+  public static TupleType buildTupleType(Data[] datums)
+         throws VisADException, RemoteException {
+    if (datums == null) {
+      throw new TypeException("Tuple: # components must be > 0");
+    }
+    int n = datums.length;
+    if (n < 1) {
+      throw new TypeException("Tuple: # components must be > 0");
+    }
+    MathType[] types = new MathType[n];
+    boolean allReal = true;
+    for (int i=0; i<n; i++) {
+      types[i] = datums[i].getType();
+      if (!(types[i] instanceof RealType)) allReal = false;
+    }
+    if (allReal) {
+      RealType[] real_types = new RealType[n];
+      for (int i=0; i<n; i++) real_types[i] = (RealType) types[i];
+      return new RealTupleType(real_types);
+    }
+    else {
+      return new TupleType(types);
+    }
+  }
+
+  /**
+   * Get all the Real components from this Tuple.
+   * @return  an array of Real-s
+   */
+  public Real[] getRealComponents()
+         throws VisADException, RemoteException {
+    if (getComponents(false) == null) return null;
+    Vector reals = new Vector();
+    for (int i=0; i<getDimension(); i++) {
+      Data comp = getComponent(i);
+      if (comp instanceof Real) {
+        reals.addElement(comp);
+      }
+      else if (comp instanceof RealTuple) {
+        RealTuple rt = (RealTuple) comp;
+        for (int j=0; j<rt.getDimension(); j++) {
+          reals.addElement(rt.getComponent(j));
+        }
+      }
+    }
+    if (reals.size() == 0) return null;
+    Real[] realComponents = new Real[reals.size()];
+    for (int i=0; i<reals.size(); i++) {
+      realComponents[i] = (Real) reals.elementAt(i);
+    }
+    return realComponents;
+  }
+
+  /** 
+   * Returns the components that constitute this instance.  If this instance
+   * has no components, then <code>null</code> is returned.  A returned array
+   * may be modified without affecting the behavior of this instance.
+   *
+   * @return                    The components of this instance or <code>
+   *                            null</code>.
+   */
+  public final  Data[] getComponents() {
+      return getComponents(true);
+  }
+
+  public static int cloneCnt = 0;
+
+  /** 
+   * Returns the components that constitute this instance.  If this instance
+   * has no components, then <code>null</code> is returned.  IF copy==true a returned array
+   * may be modified without affecting the behavior of this instance. Else, the returned array is the
+   * actual component array
+   * @param copy  if true then return a copy of the tuple array. Else return the actual array
+   *
+   * @return                    The components of this instance or <code>
+   *                            null</code>.
+   */
+    
+  public Data[] getComponents(boolean copy) {
+      if(!copy) return tupleComponents;
+      else if(tupleComponents == null)
+	  return null;
+      else {
+	  cloneCnt++;
+	  return (Data[])tupleComponents.clone();
+
+      }
+  }
+
+
+  /**
+   * Check if there is no Data in this Tuple.
+   * @return true if there is no data.
+   */
+  public  boolean isMissing() {
+      //jeffmc: 11-25-09: This method used to just call getComponents()==null which  resulted in a clone
+      //of the tuple array every time.
+      //      return (getComponents()== null);
+      //Instead pass in copy==false
+      return (getComponents(false)== null);
+  }
+
+
+  /** 
+   * Return number of components in this Tuple.
+   * @return the number of components.
+   */
+  public int getDimension() {
+    if (tupleComponents != null) {
+      return tupleComponents.length;
+    }
+    else {
+      return ((TupleType) getType()).getDimension();
+    }
+  }
+
+  /**
+   * Returns a component of this instance.  If this instance has no components,
+   * then an object of the appropriate {@link MathType} is created that has
+   * no data and is returned.  A returned component is the actual component of
+   * this instance and is not a copy or clone.
+   *
+   * @param i                     The zero-based index of the coponent to
+   *                              return.
+   * @return                      The <code>i</code>-th component of this
+   *                              instance.
+   * @throws VisADException       if this instance has no components and
+   *                              couldn't create one with no data.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   * @throws TypeException        if the index is less than zero or greater than
+   *                              <code>getDimension()-1</code>.
+   */
+  public  Data getComponent(int i) throws VisADException, RemoteException {
+    if (isMissing()) {
+      return ((TupleType) Type).getComponent(i).missingData();
+    }
+    else if (0 <= i && i < getDimension()) {
+      return (Data) tupleComponents[i];
+    }
+    else {
+      throw new TypeException("Tuple: component index out of range: " + i);
+    }
+  }
+
+
+/*-  TDR May 1998
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+ */
+  public Data binary(Data data, int op, MathType new_type,
+                    int sampling_mode, int error_mode )
+             throws VisADException, RemoteException {
+    /* BINARY - TDR May 28, 1998 */
+    if ( new_type == null ) {
+      throw new TypeException("binary: new_type may not be null" );
+    }
+    MathType m_type;
+    /* BINARY - end */
+    if (data instanceof RealTuple) {
+      throw new TypeException("Tuple.binary: types don't match");
+    }
+    else if (data instanceof Tuple) {
+      if (!Type.equalsExceptName(data.getType())) {
+        throw new TypeException("Tuple.binary: types don't match");
+      }
+      /* BINARY - TDR May 28, 1998 */
+      if ( !Type.equalsExceptName(new_type) ) {
+        throw new TypeException();
+      }
+      /* BINARY - end */
+      if (isMissing() || data.isMissing()) {
+        return new Tuple((TupleType) new_type);
+      }
+      int dim = getDimension();
+      Data[] datums = new Data[dim];
+      for (int j=0; j<dim; j++) {
+        /* BINARY - TDR June 2, 1998 */
+        m_type = ((TupleType)new_type).getComponent(j);
+        //System.out.println("m_type = " + m_type);
+        /* end  */
+        datums[j] = getComponent(j).binary(((Tuple) data).getComponent(j), op,
+                                              m_type, sampling_mode, error_mode);
+      }
+      return new Tuple(datums);
+    }
+    else if (data instanceof Real) {
+      /* BINARY - TDR May 28, 1998 */
+      if ( !Type.equalsExceptName(new_type) ) {
+        throw new TypeException();
+      }
+      /* BINARY - end */
+      if (isMissing() || data.isMissing()) {
+        return new Tuple((TupleType) new_type);
+      }
+      int dim = getDimension();
+      Data[] datums = new Data[getDimension()];
+      for (int j=0; j<dim; j++) {
+        /* BINARY - TDR June 2, 1998 */
+        m_type = ((TupleType)new_type).getComponent(j);
+        /* end  */
+        datums[j] = getComponent(j).binary(data, op, m_type, sampling_mode, error_mode);
+      }
+      return new Tuple(datums);
+    }
+    else if (data instanceof Text) {
+      throw new TypeException("Tuple.binary: types don't match");
+    }
+    else if (data instanceof Field) {
+     /* BINARY - TDR May 28, 1998
+      return data.binary(this, invertOp(op), sampling_mode, error_mode);
+      */
+      /* BINARY - TDR June 3, 1998 */
+      if ( !(data.getType()).equalsExceptName(new_type) ) {
+        throw new TypeException();
+      }
+      return data.binary(this, invertOp(op), new_type, sampling_mode, error_mode);
+      /* BINARY - end  */
+    }
+    else {
+      throw new TypeException("Tuple.binary");
+    }
+  }
+
+  /*- TDR  July 1998
+  public Data unary(int op, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+  */
+  public Data unary(int op, MathType new_type,
+                    int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    if ( new_type == null ) {
+      throw new TypeException("unary: new_type may not be null");
+    }
+
+    if ( !Type.equalsExceptName( new_type )) {
+       throw new TypeException("unary: new_type doesn't match return type");
+    }
+    TupleType T_type = (TupleType)new_type;
+
+    if (isMissing()) return new Tuple((TupleType) new_type);
+
+    int dim = getDimension();
+    Data[] datums = new Data[dim];
+    for (int j=0; j<dim; j++) {
+      datums[j] = getComponent(j).unary(op, T_type.getComponent(j),
+                                           sampling_mode, error_mode);
+    }
+    return new Tuple(datums);
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    if (isMissing()) return shadow;
+    for (int i=0; i<getDimension(); i++) {
+      shadow =
+        getComponent(i).computeRanges(((ShadowTupleType) type).getComponent(i),
+                                         shadow);
+    }
+    return shadow;
+  }
+
+  /** return a Tuple that clones this, except its ErrorEstimate-s
+      are adjusted for sampling errors in error */
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    if (isMissing() || error == null || error.isMissing()) return this;
+    int n = getDimension();
+    Data[] newComponents = new Data[n];
+    for (int i=0; i<n; i++) {
+      Data errorComponent = ((Tuple) error).getComponent(i);
+      newComponents[i] =
+        getComponent(i).adjustSamplingError(errorComponent, error_mode);
+    }
+    return new Tuple(newComponents);
+  }
+
+  /**
+   * A wrapper around {@link #getComponent(int) getComponent} for JPython.
+   *
+   * @return The requested Data object.
+   */
+  public Data __getitem__(int index) throws VisADException, RemoteException {
+    return getComponent(index);
+  }
+
+  /**
+   * A wrapper around {@link #getLength() getLength} for JPython.
+   *
+   * @return The number of components of the Tuple
+   */
+  public int __len__() {
+    return getDimension();
+  }
+
+  /**
+   * Return the number of components of the Tuple
+   *
+   * @return Number of components.
+   */
+  public int getLength() {
+    return getDimension();
+  }
+
+  /**
+   * <p>Clones this instance.  The <code>clone()</code> method of each 
+   * component is invoked.</p>
+   *
+   * <p>This implementation throws {@link CloneNotSupportedException} if and
+   * only if one of the components throws it.</p>
+   *
+   * @return                            A clone of this instance.
+   * @throws CloneNotSupportedException if cloning isn't supported.
+   */
+  public Object clone() throws CloneNotSupportedException {
+    Tuple clone = (Tuple)super.clone();
+
+    if (clone.tupleComponents != null) {
+      clone.tupleComponents = new Data[tupleComponents.length];
+
+      for (int i = 0; i < tupleComponents.length; i++) {
+        Data comp = tupleComponents[i];
+
+        if (comp == null) {
+          clone.tupleComponents[i] = null;
+        }
+        else {
+          try {
+            /*
+             * Data.dataClone() is invoked because Data.clone() doesn't and
+             * can't exist.
+             */
+            clone.tupleComponents[i] = (Data)tupleComponents[i].dataClone();
+          }
+          catch (RemoteException ex) {
+            throw new RuntimeException(ex.toString());
+          }
+        }
+      }
+    }
+
+    return clone;
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    String s = pre + "Tuple\n" + pre + "  Type: " + Type.toString() + "\n";
+    if (isMissing()) return s + "  missing\n";
+    for (int i=0; i<getDimension(); i++) {
+      s = s + pre + "  Tuple Component " + i + ":\n" +
+              getComponent(i).longString(pre + "    ");
+    }
+    return s;
+  }
+
+  /**
+   * Indicates if this Tuple is identical to an object.
+   *
+   * @param obj         The object.
+   * @return            <code>true</code> if and only if the object is
+   *                    a Tuple and both Tuple-s have identical component
+   *                    sequences.
+   */
+  public boolean equals(Object obj) {
+    boolean     equals;
+    if (!(obj instanceof Tuple)) {
+      equals = false;
+    }
+    else {
+      Tuple     that = (Tuple)obj;
+      if (this == that) {
+        equals = true;
+      }
+      else if (tupleComponents == null || that.tupleComponents == null) {
+        equals = tupleComponents == that.tupleComponents;
+      }
+      else if (tupleComponents.length != that.tupleComponents.length) {
+        equals = false;
+      }
+      else {
+        equals = true;
+        for (int i = 0; i < tupleComponents.length; ++i)
+          if (!(tupleComponents[i].equals(that.tupleComponents[i]))) {
+            equals = false;
+            break;
+          }
+      }
+    }
+    return equals;
+  }
+
+  /**
+   * Returns the hash code of this object.
+   * @return            The hash code of this object.
+   */
+  public int hashCode() {
+    int hashCode = 0;
+    if (tupleComponents != null)
+      for (int i = 0; i < tupleComponents.length; ++i)
+        hashCode ^= tupleComponents[i].hashCode();
+    return hashCode;
+  }
+
+}
+
diff --git a/visad/TupleIface.java b/visad/TupleIface.java
new file mode 100644
index 0000000..b45cbcd
--- /dev/null
+++ b/visad/TupleIface.java
@@ -0,0 +1,66 @@
+//
+// TupleIface.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   TupleIface is the VisAD data interface for vectors.<P>
+*/
+public interface TupleIface extends Data {
+
+  Real[] getRealComponents() throws VisADException, RemoteException;
+
+  /** return number of components */
+  int getDimension() throws RemoteException;
+
+  /** return component for i between 0 and getDimension() - 1 */
+  Data getComponent(int i) throws VisADException, RemoteException;
+
+  boolean isMissing() throws RemoteException;
+
+  Data binary(Data data, int op, MathType new_type,
+                     int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  Data unary(int op, MathType new_type,
+                    int sampling_mode, int error_mode)
+         throws VisADException, RemoteException;
+
+  DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException;
+
+  /** return a Tuple that clones this, except its ErrorEstimate-s
+      are adjusted for sampling errors in error */
+  Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException;
+
+  String longString(String pre)
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/TupleType.java b/visad/TupleType.java
new file mode 100644
index 0000000..68aa610
--- /dev/null
+++ b/visad/TupleType.java
@@ -0,0 +1,389 @@
+//
+// TupleType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+import java.util.*;
+
+/**
+   TupleType is the general VisAD data type for vectors.<P>
+*/
+public class TupleType extends MathType {
+
+  /** tupleComponents must be accessible to subclasses */
+  private MathType[] tupleComponents;
+
+  private boolean Flat; // true if all components are RealType or RealTupleType
+  /** realComponents contains all RealType components and RealType components
+      of RealTupleType components */
+  final private RealType[] realComponents;
+  /** range of tupleComponents[i] in realComponents; NEVER USED */
+  private int[] lows, his;
+
+  /** array of component types */
+  public TupleType(MathType[] types) throws VisADException {
+    super();
+    boolean allReal = true;
+    Flat = true;
+    int n = types.length;
+    int nFlat = 0;
+    tupleComponents = new MathType[n];
+    if (n < 1) throw new TypeException("TupleType: # components must be > 0");
+    for (int i=0; i<n; i++) {
+      if (types[i] == null) {
+        throw new TypeException("TupleType: components must be non-null");
+      }
+      tupleComponents[i] = types[i];
+      if (!(types[i] instanceof RealType)) allReal = false;
+      if (types[i] instanceof RealType) {
+        nFlat++;
+      }
+      else if (types[i] instanceof RealTupleType) {
+        nFlat += ((RealTupleType) types[i]).getDimension();
+      }
+      else {
+        Flat = false;
+      }
+    }
+    if (allReal && !(this instanceof RealTupleType)) {
+      throw new TypeException("TupleType: all components are RealType," +
+                              " must use RealTupleType");
+    }
+    realComponents = new RealType[nFlat];
+    lows = new int[n];
+    his = new int[n];
+    int j = 0;
+    for (int i=0; i<n; i++) {
+      lows[i] = j;
+      if (types[i] instanceof RealType) {
+        realComponents[j] = (RealType) types[i];
+        j++;
+      }
+      else if (types[i] instanceof RealTupleType) {
+        int m = ((RealTupleType) types[i]).getDimension();
+        for (int k=0; k<m; k++) {
+          realComponents[j] = (RealType)
+            ((RealTupleType) types[i]).getComponent(k);
+          j++;
+        }
+      }
+      his[i] = j;
+    }
+  }
+
+  /** trusted constructor for initializers (RealTupleType and DisplayTupleType) */
+  TupleType(RealType[] types, boolean b) {
+    super(b);
+    boolean allReal = true;
+    Flat = true;
+    int n = types.length;
+    int nFlat = 0;
+    tupleComponents = new MathType[n];
+    for (int i=0; i<n; i++) {
+      tupleComponents[i] = types[i];
+      nFlat++;
+    }
+    realComponents = new RealType[nFlat];
+    lows = new int[n];
+    his = new int[n];
+    int j = 0;
+    for (int i=0; i<n; i++) {
+      lows[i] = j;
+      if (types[i] instanceof RealType) {
+        realComponents[j] = (RealType) types[i];
+        j++;
+      }
+      his[i] = j;
+    }
+  }
+
+  /** get number of components */
+  public int getDimension() {
+    return tupleComponents.length;
+  }
+
+  /** get array of components */
+  public MathType[] getComponents() {
+    return tupleComponents;
+  }
+
+  public RealType[] getRealComponents() {
+    return realComponents;
+  }
+
+  public int getNumberOfRealComponents() {
+    return realComponents.length;
+  }
+
+  /**
+   * A wrapper around {@link #getComponent(int) getComponent} for JPython.
+   *
+   * @return The requested Data object.
+   */
+  public MathType __getitem__(int index) throws VisADException {
+    return getComponent(index);
+  }
+
+  /**
+   * A wrapper around {@link #getComponents() getComponents} for JPython.
+   *
+   * @return The number of components of this TupleType
+   */
+  public int __len__() {
+    return tupleComponents.length;
+  }
+
+  /** return component for i between 0 and getDimension() - 1 */
+  public MathType getComponent(int i) throws VisADException {
+    if (0 <= i && i < tupleComponents.length) {
+      return tupleComponents[i];
+    }
+    else {
+      throw new TypeException("TupleType: component index out of range");
+    }
+  }
+
+  /** return index of first RealType component with name;
+      if no such component, return -1 */
+  public int getIndex(String name) {
+    return getIndex(RealType.getRealTypeByName(name));
+  }
+
+  /** return index of first component with type;
+      if no such component, return -1 */
+  public int getIndex(MathType type) {
+    for (int i=0; i<tupleComponents.length; i++) {
+      if (tupleComponents[i].equals(type)) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  public boolean equals(Object type) {
+    if (!(type instanceof TupleType)) return false;
+    try {
+      int n = tupleComponents.length;
+      if (n != ((TupleType) type).getDimension()) return false;
+      for (int i=0; i<n; i++) {
+        if (!tupleComponents[i].equals(
+                     ((TupleType) type).getComponent(i))) return false;
+      }
+    }
+    catch (VisADException e) {
+      return false;
+    }
+    return true;
+  }
+
+  public int hashCode() {
+    return Arrays.asList(tupleComponents).hashCode();
+  }
+
+  public boolean equalsExceptName(MathType type) {
+    if (!(type instanceof TupleType)) return false;
+    try {
+      int n = tupleComponents.length;
+      if (n != ((TupleType) type).getDimension()) return false;
+      boolean flag = true;
+      for (int i=0; i<n; i++) {
+        flag = flag && tupleComponents[i].equalsExceptName(
+                              ((TupleType) type).getComponent(i) );
+      }
+      return flag;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /*- TDR June 1998    */
+  public boolean equalsExceptNameButUnits(MathType type) {
+    if (!(type instanceof TupleType)) return false;
+    try {
+      int n = tupleComponents.length;
+      if (n != ((TupleType) type).getDimension()) return false;
+      boolean flag = true;
+      for (int i=0; i<n; i++) {
+        flag = flag && tupleComponents[i].equalsExceptNameButUnits(
+                              ((TupleType) type).getComponent(i) );
+      }
+      return flag;
+    }
+    catch (VisADException e) {
+      return false;
+    }
+  }
+
+  /*- TDR June 1998  */
+  public MathType cloneDerivative( RealType d_partial )
+         throws VisADException
+  {
+    int n_comps = tupleComponents.length;
+    MathType[] new_types = new MathType[ n_comps ];
+    boolean allReal = true;
+
+    for ( int ii = 0; ii < n_comps; ii++ ) {
+      new_types[ii] = (this.getComponent(ii)).cloneDerivative( d_partial );
+      if (!(new_types[ii] instanceof RealType) ) {
+        allReal = false;
+      }
+    }
+    if ( allReal ) {
+      RealType[] r_types = new RealType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        r_types[ii] = (RealType) new_types[ii];
+      }
+      return new RealTupleType( r_types );
+    }
+    else {
+      return new TupleType( new_types );
+    }
+  }
+
+  /*- TDR July 1998  */
+  public MathType binary( MathType type, int op, Vector names )
+         throws VisADException
+  {
+    if (type == null) {
+      throw new TypeException("TupleType.binary: type may not be null" );
+    }
+    if (type instanceof RealTupleType) {
+      throw new TypeException("TupleType.binary: types don't match" );
+    }
+    else if (type instanceof TupleType) {
+      int n_comps = tupleComponents.length;
+      MathType[] new_types = new MathType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        MathType type_component = ((TupleType) type).getComponent(ii);
+        new_types[ii] = (this.getComponent(ii)).binary( type_component, op, names );
+      }
+      return new TupleType( new_types );
+    }
+    else if (type instanceof RealType) {
+      int n_comps = tupleComponents.length;
+      MathType[] new_types = new MathType[ n_comps ];
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        new_types[ii] = (this.getComponent(ii)).binary( type, op, names );
+      }
+      return new TupleType( new_types );
+    }
+    else if (type instanceof FunctionType &&
+             ((FunctionType) type).getRange().equalsExceptName(this)) {
+      return new FunctionType(((FunctionType) type).getDomain(),
+        ((FunctionType) type).getRange().binary(this, DataImpl.invertOp(op), names));
+    }
+    else {
+      throw new TypeException("TupleType.binary: types don't match" );
+    }
+/* WLH 10 Sept 98
+    int n_comps = tupleComponents.length;
+    MathType[] new_types = new MathType[ n_comps ];
+    for ( int ii = 0; ii < n_comps; ii++ ) {
+      new_types[ii] = (this.getComponent(ii)).binary( type, op, names );
+    }
+
+    return new TupleType( new_types );
+*/
+  }
+
+  /*- TDR July 1998  */
+  public MathType unary( int op, Vector names )
+         throws VisADException
+  {
+    int n_comps = tupleComponents.length;
+    MathType[] new_types = new MathType[ n_comps ];
+    for ( int ii = 0; ii < n_comps; ii++ ) {
+      new_types[ii] = (this.getComponent(ii)).unary( op, names );
+    }
+
+    return new TupleType( new_types );
+  }
+
+
+  /**
+   * Is this a "flat" tuple?
+   *
+   * @return true if all components are RealType or RealTupleType.
+   */
+  public boolean getFlat() {
+    return Flat;
+  }
+
+/* WLH 5 Jan 2000
+  public String toString() {
+    String t = "(" + tupleComponents[0].toString();
+    for (int i=1; i<tupleComponents.length; i++) {
+      t = t + ", " + tupleComponents[i].toString();
+    }
+    return t + ")";
+  }
+*/
+
+  public String prettyString(int indent) {
+    int n = tupleComponents.length;
+    String[] cs = new String[n];
+    int[] lens = new int[n];
+    int maxlen = 0;
+    int sumlen = 0;
+    for (int i=0; i<n; i++) {
+      cs[i] = tupleComponents[i].prettyString(indent+1);
+      lens[i] = cs[i].length();
+      if (lens[i] > maxlen) maxlen = lens[i];
+      sumlen += lens[i];
+    }
+    if (sumlen + indent <= 72) {
+      String s = "(" + cs[0];
+      for (int i=1; i<n; i++) {
+        s = s + ", " + cs[i];
+      }
+      return s + ")";
+    }
+    else {
+      String blanks = "";
+      for (int j=0; j<indent+1; j++) blanks = blanks + " ";
+      String s = "(" + cs[0];
+      if (1 < n) s = s + ",";
+      for (int i=1; i<n; i++) {
+        s = s + "\n" + blanks + cs[i];
+        if (i+1 < n) s = s + ",";
+      }
+      return s + ")";
+    }
+  }
+
+  public Data missingData() {
+    return new Tuple(this);
+  }
+
+  public ShadowType buildShadowType(DataDisplayLink link, ShadowType parent)
+             throws VisADException, RemoteException {
+    return link.getRenderer().makeShadowTupleType(this, link, parent);
+  }
+
+}
+
diff --git a/visad/TypeException.java b/visad/TypeException.java
new file mode 100644
index 0000000..4a06b34
--- /dev/null
+++ b/visad/TypeException.java
@@ -0,0 +1,38 @@
+//
+// TypeException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   TypeException is an exception for a bad VisAD data type.<P>
+*/
+public class TypeException extends VisADException {
+
+  public TypeException() { super(); }
+  public TypeException(String s) { super(s); }
+
+}
+
diff --git a/visad/UnimplementedException.java b/visad/UnimplementedException.java
new file mode 100644
index 0000000..874fb00
--- /dev/null
+++ b/visad/UnimplementedException.java
@@ -0,0 +1,38 @@
+//
+// UnimplementedException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   UnimplementedException is an exception for a VisAD method not yet implemented.<P>
+*/
+public class UnimplementedException extends VisADException {
+
+  public UnimplementedException() { super(); }
+  public UnimplementedException(String s) { super(s); }
+
+}
+
diff --git a/visad/UnionSet.java b/visad/UnionSet.java
new file mode 100644
index 0000000..6ebdac6
--- /dev/null
+++ b/visad/UnionSet.java
@@ -0,0 +1,958 @@
+//
+// UnionSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+//
+// TO_DO
+// getWedge
+//
+
+/**
+ * UnionSet is the union of an array of SampledSets.  They
+ * must match in domain dimension, manifold dimension,
+ * CoordinateSystem, and Units.  No sets in the array can be null,
+ * and there must be at least one SampledSet in the array
+ * (i.e. array.length != 0).<P>
+ */
+public class UnionSet extends SampledSet {
+
+  private static final long serialVersionUID = 1L;
+  SampledSet[] Sets;
+
+  /**
+   * Construct a UnionSet with an array of SampledSets with null errors.
+   * CoordinateSystem and Units are defaults from type.
+   *
+   * @param  type  MathType for the sets.  Sets the CoordinateSystem and Units.
+   * @param  sets  array of SampledSets.  All sets must match in domain
+   *               dimension and manifold dimension and no sets in the
+   *               array can be null.  There must be at least one SampledSet
+   *               in the array (i.e. sets.length != 0).
+   *
+   * @exception  VisADException  problem creating the UnionSet
+   */
+  public UnionSet(MathType type, SampledSet[] sets) throws VisADException {
+    this(type, sets, null, null, null, true);
+  }
+
+  /** create the union of the sets array; coordinate_system
+      and units must be compatible with defaults for type,
+      or may be null; errors may be null */
+  private UnionSet(MathType type, SampledSet[] sets,
+                  CoordinateSystem coord_sys, Unit[] units,
+                  ErrorEstimate[] errors) throws VisADException {
+    this(type, sets, coord_sys, units, errors, true);
+  }
+
+  public UnionSet(MathType type, SampledSet[] sets, CoordinateSystem coord_sys,
+             Unit[] units, ErrorEstimate[] errors, boolean copy)
+             throws VisADException {
+    super(type, find_manifold_dim(sets), sets[0].getCoordinateSystem(),
+          sets[0].getSetUnits(), null);
+    DomainDimension = sets[0].DomainDimension;
+    if (copy) {
+      Sets = new SampledSet[sets.length];
+      for (int i=0; i<sets.length; i++) {
+        Sets[i] = (SampledSet) sets[i].clone();
+      }
+    }
+    else Sets = sets;
+    Length = 0;
+    for (int i=0; i<sets.length; i++) {
+      Length += Sets[i].Length;
+    }
+    Low = new float[DomainDimension];
+    Hi = new float[DomainDimension];
+    for (int i=0; i<DomainDimension; i++) {
+      Low[i] = Sets[0].Low[i];
+      Hi[i] = Sets[0].Hi[i];
+      for (int j=1; j<sets.length; j++) {
+        if (sets[j].Low[i] < Low[i]) Low[i] = sets[j].Low[i];
+        if (sets[j].Hi[i] > Hi[i]) Hi[i] = sets[j].Hi[i];
+      }
+    }
+  }
+
+  private static int find_manifold_dim(SampledSet[] sets)
+                                        throws VisADException {
+    if (sets == null || sets.length == 0 || sets[0] == null) {
+      throw new SetException("UnionSet: Sets cannot be missing");
+    }
+    if (sets.length < 2) {
+    /* DRM - 03-Jan-2000
+      throw new SetException("UnionSet: must be at least 2 sets");
+    */
+      return sets[0].ManifoldDimension;
+    }
+    int dim = sets[0].DomainDimension;
+    int mdim = sets[0].ManifoldDimension;
+    CoordinateSystem cs0 = sets[0].getCoordinateSystem();
+    Unit[] units0 = sets[0].getSetUnits();
+    for (int i=1; i<sets.length; i++) {
+      if (sets[i] == null) {
+        throw new SetException("UnionSet: Sets cannot be missing");
+      }
+      if (sets[i].DomainDimension != dim) {
+        throw new SetException("UnionSet: set #" + i +
+                               " Domain dimension is " +
+                               sets[i].DomainDimension + ", not " + dim);
+      }
+      if (sets[i].ManifoldDimension != mdim) {
+        throw new SetException("UnionSet: set #" + i +
+                               " Manifold dimension is " +
+                               sets[i].ManifoldDimension + ", not " + mdim);
+      }
+      CoordinateSystem cs = sets[i].getCoordinateSystem();
+      if (cs0 != null || cs != null) {
+        if (cs0 == null || cs == null || !cs0.equals(cs)) {
+          throw new CoordinateSystemException("UnionSet: Coordinate system #" +
+                                              i + " (" + (cs == null ? null :
+                                                          cs.getReference()) +
+                                              " must match #0 " +
+                                              (cs0 == null ? null :
+                                               cs0.getReference()));
+        }
+      }
+      Unit[] units = sets[i].getSetUnits();
+      if (units0 != null || units != null) {
+        if (units0 == null || units == null ||
+            units0.length != units.length) {
+          throw new SetException("UnionSet: Expected " +
+                                 (units0 == null ? "null" :
+                                  Integer.toString(units0.length)) +
+                                 " units for set " + i + ", not " +
+                                 (units == null ? "null" :
+                                  Integer.toString(units.length)));
+        }
+        for (int j=0; j<units0.length; j++) {
+          if (units0[j] != null || units[j] != null) {
+            if (units0[j] == null || units[j] == null ||
+                !units0[j].equals(units[j])) {
+              throw new SetException("UnionSet: Expected set " + i +
+                                     ", element " + j + " units to be " +
+                                     units0[j] + " not " + units[j]);
+            }
+          }
+        }
+      }
+    }
+    return mdim;
+  }
+
+  /**
+   * Returns the {@link SampledSet}s that constitute this instance.  The
+   * returned array may be modified without affecting the behavior of this
+   * instance.
+   *
+   * @return                        The {@link SampledSet}s that constitute this
+   *                                instance.
+   */
+  public SampledSet[] getSets() {
+    return (SampledSet[]) Sets.clone();
+    // return Sets; WLH 28 Nov 2000
+  }
+
+  /**
+   * Construct a UnionSet with an array of SampledSets
+   *
+   * @param  sets  array of SampledSets.  All sets must match in domain
+   *               dimension and manifold dimension, CoordinateSystem,
+   *               and Units. and no sets in the array can be null.
+   *               There must be at least one SampledSet
+   *               in the array (i.e. sets.length != 0).
+   *
+   * @exception  VisADException  problem creating the UnionSet
+   */
+  public UnionSet(SampledSet[] sets) throws VisADException {
+    this(sets[0].getType(), sets, null, null, null, true);
+  }
+
+  /**
+   * Return a SampledSet that is a UnionSet of ProductSets of
+   * GriddedSets and IrregularSets
+   *
+   * @return  resulting UnionSet of ProductSets
+   * @exception  VisADException  problem creating the UnionSet
+   */
+  public SampledSet product() throws VisADException {
+    int n = Sets.length;
+    SampledSet[] sets = new SampledSet[n];
+    int count = 0;
+    for (int i=0; i<n; i++) {
+      if (Sets[i] instanceof GriddedSet ||
+          Sets[i] instanceof IrregularSet) {
+        sets[i] = Sets[i];
+      }
+      else if (Sets[i] instanceof ProductSet) {
+        sets[i] = ((ProductSet) Sets[i]).product();
+      }
+      else if (Sets[i] instanceof UnionSet) {
+        sets[i] = ((UnionSet) Sets[i]).product();
+      }
+      else {
+        throw new UnimplementedException("UnionSet.product: " +
+                                         Sets[i].getClass());
+      }
+      if (sets[i] instanceof UnionSet) {
+        count += ((UnionSet) sets[i]).Sets.length;
+      }
+      else {
+        count++;
+      }
+    } // end for (int i=0; i<n; i++)
+    SampledSet[] summands = new SampledSet[count];
+    int k = 0;
+    for (int i=0; i<n; i++) {
+      if (sets[i] instanceof UnionSet) {
+        for (int j=0; j<((UnionSet) sets[i]).Sets.length; j++) {
+          summands[k++] = ((UnionSet) sets[i]).Sets[j];
+        }
+      }
+      else {
+        summands[k++] = sets[i];
+      }
+    }
+    return new UnionSet(getType(), summands);
+  }
+
+  /**
+   * Create a UnionSet that is the cross product of this UnionSet and
+   * the input SampledSet.
+   *
+   * @param  set   input SampledSet
+   * @return a SampledSet that is a UnionSet of ProductSets of
+   *           this UnionSet and the input SampledSet
+   * @exception   VisADException  error creating necessary VisAD object
+   */
+  public SampledSet product(SampledSet set) throws VisADException {
+    int n = Sets.length;
+    SampledSet[] sets = new SampledSet[n];
+    boolean union_of_union = false;
+    for (int i=0; i<n; i++) {
+      if (Sets[i] instanceof ProductSet) {
+        sets[i] = ((ProductSet) Sets[i]).product(set);
+      }
+      else {
+        if (set instanceof ProductSet) {
+          sets[i] = ((ProductSet) set).inverseProduct(Sets[i]);
+        }
+        else if (set instanceof UnionSet) {
+          sets[i] = ((UnionSet) set).inverseProduct(Sets[i]);
+          union_of_union = true;
+        }
+        else {
+          sets[i] = new ProductSet(new SampledSet[] {Sets[i], set});
+        }
+      }
+    }
+    SampledSet union = new UnionSet(sets);
+    if (union_of_union) {
+      union = ((UnionSet) union).product();
+    }
+    return union;
+  }
+
+  /**
+   * Create a UnionSet that is the inverse cross product of this UnionSet and
+   * the input SampledSet.
+   *
+   * @param  set   input SampledSet
+   *
+   * @return       a SampledSet that is a UnionSet of inverse ProductSets of
+   *               this UnionSet and the input SampledSet
+   * @exception    VisADException  error creating necessary VisAD object
+   */
+  public SampledSet inverseProduct(SampledSet set) throws VisADException {
+    int n = Sets.length;
+    SampledSet[] sets = new SampledSet[n];
+    boolean union_of_union = false;
+    for (int i=0; i<n; i++) {
+      if (Sets[i] instanceof ProductSet) {
+        sets[i] = ((ProductSet) Sets[i]).inverseProduct(set);
+      }
+      else {
+        if (set instanceof ProductSet) {
+          sets[i] = ((ProductSet) set).product(Sets[i]);
+        }
+        else if (set instanceof UnionSet) {
+          sets[i] = ((UnionSet) set).product(Sets[i]);
+          union_of_union = true;
+        }
+        else {
+          sets[i] = new ProductSet(new SampledSet[] {set, Sets[i]});
+        }
+      }
+    }
+    SampledSet union = new UnionSet(sets);
+    if (union_of_union) {
+      union = ((UnionSet) union).product();
+    }
+    return union;
+  }
+
+  public Set makeSpatial(SetType type, float[][] samples) throws VisADException {
+    int n = Sets.length;
+    int dim = samples.length;
+    SampledSet[] sets = new SampledSet[n];
+    int base = 0;
+    for (int i=0; i<n; i++) {
+      int len = Sets[i].Length;
+      float[][] s = new float[dim][len];
+      for (int j=0; j<dim; j++) {
+        for (int k=0; k<len; k++) s[j][k] = samples[j][base + k];
+      }
+      sets[i] = (SampledSet) Sets[i].makeSpatial(type, s);
+      base += len;
+    }
+    UnionSet set = new UnionSet((SetType) sets[0].getType(), sets);
+    return set;
+  }
+
+  public void cram_missing(boolean[] range_select) {
+    int rl = range_select.length;
+    int n = Sets.length;
+    int k = 0;
+    for (int i=0; i<n; i++) {
+      int len = 0;
+      try {
+        len = Sets[i].getLength();
+      }
+      catch (VisADException e) {
+        return;
+      }
+      if ((k + len) > rl) return;
+      boolean[] ri = new boolean[len];
+      System.arraycopy(range_select, k, ri, 0, len);
+      Sets[i].cram_missing(ri);
+      k += len;
+    }
+  }
+
+  /** create a 2-D GeometryArray from this Set and color_values */
+  public VisADGeometryArray make2DGeometry(byte[][] color_values,
+         boolean indexed) throws VisADException {
+    if (DomainDimension != 3) {
+      throw new SetException("UnionSet.make2DGeometry: " +
+                              "DomainDimension must be 3, not " +
+                             DomainDimension);
+    }
+    if (ManifoldDimension != 2) {
+      throw new SetException("UnionSet.make2DGeometry: " +
+                              "ManifoldDimension must be 2, not " +
+                             ManifoldDimension);
+    }
+    int n = Sets.length;
+    int dim = (color_values != null) ? color_values.length : 0;
+    if (indexed) {
+      VisADIndexedTriangleStripArray[] arrays =
+        new VisADIndexedTriangleStripArray[n];
+      int base = 0;
+      for (int i=0; i<n; i++) {
+        int len = Sets[i].Length;
+        byte[][] c = null;
+        if (color_values != null) {
+          c = new byte[dim][len];
+          for (int j=0; j<dim; j++) {
+            for (int k=0; k<len; k++) c[j][k] = color_values[j][base + k];
+          }
+        }
+        VisADGeometryArray array = Sets[i].make2DGeometry(c, indexed);
+        if (array instanceof VisADIndexedTriangleStripArray) {
+          arrays[i] = (VisADIndexedTriangleStripArray) array;
+        }
+        else {
+          arrays[i] = null;
+        }
+        base += len;
+      }
+      return VisADIndexedTriangleStripArray.merge(arrays);
+    }
+    else { // if (!indexed)
+      VisADTriangleStripArray[] arrays =
+        new VisADTriangleStripArray[n];
+      int base = 0;
+      for (int i=0; i<n; i++) {
+        int len = Sets[i].Length;
+        byte[][] c = null;
+        if (color_values != null) {
+          c = new byte[dim][len];
+          for (int j=0; j<dim; j++) {
+            for (int k=0; k<len; k++) c[j][k] = color_values[j][base + k];
+          }
+        }
+        VisADGeometryArray array = Sets[i].make2DGeometry(c, indexed);
+        if (array instanceof VisADTriangleStripArray) {
+          arrays[i] = (VisADTriangleStripArray) array;
+        }
+        else {
+          arrays[i] = null;
+        }
+        base += len;
+      }
+      return VisADTriangleStripArray.merge(arrays);
+    }
+  }
+
+  /** create a 1-D GeometryArray from this Set and color_values */
+  public VisADGeometryArray make1DGeometry(byte[][] color_values)
+         throws VisADException {
+    if (DomainDimension != 3) {
+      throw new SetException("UnionSet.make1DGeometry: " +
+                              "DomainDimension must be 3, not " +
+                             DomainDimension);
+    }
+    if (ManifoldDimension != 1) {
+      throw new SetException("UnionSet.make1DGeometry: " +
+                             "ManifoldDimension must be 1, not " +
+                             ManifoldDimension);
+    }
+    int n = Sets.length;
+    int dim = (color_values != null) ? color_values.length : 0;
+    VisADLineStripArray[] arrays =
+      new VisADLineStripArray[n];
+    int base = 0;
+    for (int i=0; i<n; i++) {
+      int len = Sets[i].Length;
+      byte[][] c = null;
+      if (color_values != null) {
+        c = new byte[dim][len];
+        for (int j=0; j<dim; j++) {
+          for (int k=0; k<len; k++) c[j][k] = color_values[j][base + k];
+        }
+      }
+      VisADGeometryArray array = Sets[i].make1DGeometry(c);
+      if (array instanceof VisADLineStripArray) {
+        arrays[i] = (VisADLineStripArray) array;
+      }
+      else {
+        arrays[i] = null;
+      }
+      base += len;
+    }
+    VisADLineStripArray array = VisADLineStripArray.merge(arrays);
+    return array;
+  }
+
+  /** return basic lines in array[0], fill-ins in array[1]
+      and labels in array[2] */
+  public VisADGeometryArray[][] makeIsoLines(float[] intervals,
+                  float low, float hi, float base,
+                  float[] fieldValues, byte[][] color_values,
+                  boolean[] swap, boolean dash,
+                  boolean fill, ScalarMap[] smap,
+                  double[] scale, double label_size,
+                  boolean sphericalDisplayCS) throws VisADException {
+    if (DomainDimension != 3) {
+      throw new DisplayException("UnionSet.makeIsoLines: " +
+                                 "DomainDimension must be 3, not " +
+                                 DomainDimension);
+    }
+    if (ManifoldDimension != 2) {
+      throw new DisplayException("UnionSet.makeIsoLines: " +
+                                 "ManifoldDimension must be 2, not " +
+                                 ManifoldDimension);
+    }
+    int n = Sets.length;
+    int dim = color_values.length;
+    VisADLineArray[][][] arrays = new VisADLineArray[n][][];
+    int kbase = 0;
+    for (int i=0; i<n; i++) {
+      int len = Sets[i].Length;
+      byte[][] c = new byte[dim][len];
+      float[] f = new float[len];
+      for (int j=0; j<dim; j++) {
+        for (int k=0; k<len; k++) c[j][k] = color_values[j][kbase + k];
+      }
+      for (int k=0; k<len; k++) f[k] = fieldValues[kbase + k];
+      arrays[i] =
+        (VisADLineArray[][]) Sets[i].makeIsoLines(intervals, low, hi, base,
+                                                  f, c, swap, dash, fill, smap,
+                                                  scale, label_size, sphericalDisplayCS);
+      kbase += len;
+    }
+    VisADLineArray[][] arrays2 = new VisADLineArray[4][];
+    for (int j=0; j<2; j++) { //- merge lines/fill
+      arrays2[j] = new VisADLineArray[1];
+      VisADLineArray[] arrays3 = new VisADLineArray[n];
+      for (int i=0; i<n; i++) {
+        arrays3[i] = arrays[i][j][0];
+      }
+      arrays2[j][0] = VisADLineArray.merge(arrays3);
+    }
+    for (int j=2; j<4; j++) { //- don't merge labels
+      int cnt = 0;
+      for (int i=0; i<n; i++) {
+        cnt += arrays[i][j].length;
+      }
+      arrays2[j] = new VisADLineArray[cnt];
+      cnt = 0;
+      for (int i=0; i<n; i++) {
+        for (int j2=0; j2<arrays[i][j].length; j2++) {
+          arrays2[j][cnt++] = arrays[i][j][j2];
+        }
+      }
+    }
+    return arrays2;
+  }
+
+  public VisADGeometryArray makeIsoSurface(float isolevel,
+         float[] fieldValues, byte[][] color_values, boolean indexed)
+         throws VisADException {
+    if (DomainDimension != 3) {
+      throw new DisplayException("UnionSet.makeIsoSurface: " +
+                                 "DomainDimension must be 3, not " +
+                                 DomainDimension);
+    }
+    if (ManifoldDimension != 3) {
+      throw new DisplayException("UnionSet.makeIsoSurface: " +
+                                 "ManifoldDimension must be 3, not " +
+                                 ManifoldDimension);
+    }
+    int n = Sets.length;
+    int dim = color_values.length;
+    if (indexed) {
+      VisADIndexedTriangleStripArray[] arrays =
+        new VisADIndexedTriangleStripArray[n];
+      int base = 0;
+      for (int i=0; i<n; i++) {
+        int len = Sets[i].Length;
+        byte[][] c = new byte[dim][len];
+        float[] f = new float[len];
+        for (int j=0; j<dim; j++) {
+          for (int k=0; k<len; k++) c[j][k] = color_values[j][base + k];
+        }
+        for (int k=0; k<len; k++) f[k] = fieldValues[base + k];
+        arrays[i] = (VisADIndexedTriangleStripArray)
+          Sets[i].makeIsoSurface(isolevel, f, c, indexed);
+        base += len;
+      }
+      return VisADIndexedTriangleStripArray.merge(arrays);
+    }
+    else { // if (!indexed)
+      VisADTriangleStripArray[] arrays =
+        new VisADTriangleStripArray[n];
+      int base = 0;
+      for (int i=0; i<n; i++) {
+        int len = Sets[i].Length;
+        byte[][] c = new byte[dim][len];
+        float[] f = new float[len];
+        for (int j=0; j<dim; j++) {
+          for (int k=0; k<len; k++) c[j][k] = color_values[j][base + k];
+        }
+        for (int k=0; k<len; k++) f[k] = fieldValues[base + k];
+        arrays[i] = (VisADTriangleStripArray)
+          Sets[i].makeIsoSurface(isolevel, f, c, indexed);
+        base += len;
+      }
+      return VisADTriangleStripArray.merge(arrays);
+    } // end if (!indexed)
+  }
+
+  /** copied from Set */
+  public float[][] getSamples(boolean copy) throws VisADException {
+    int n = getLength();
+    int[] indices = new int[n];
+    // do NOT call getWedge
+    for (int i=0; i<n; i++) indices[i] = i;
+    return indexToValue(indices);
+  }
+
+  /** convert an array of 1-D indices to an
+      array of values in R^DomainDimension */
+  public float[][] indexToValue(int[] index) throws VisADException {
+    int nsets = Sets.length;
+    int npts = index.length;
+    float[][] value = new float[DomainDimension][npts];
+    if (npts == getLength()) {
+      boolean ramp = true;
+      for (int i=0; i<npts; i++) {
+        if (index[i] != i) {
+          ramp = false;
+          break;
+        }
+      }
+      if (ramp) {
+        int k = 0;
+        for (int j=0; j<nsets; j++) {
+          int sub_length = Sets[j].getLength();
+          int[] sub_inds = new int[sub_length];
+          for (int i=0; i<sub_length; i++) sub_inds[i] = i;
+          float[][] sub_vals = Sets[j].indexToValue(sub_inds);
+          for (int m=0; m<DomainDimension; m++) {
+            System.arraycopy(sub_vals[m], 0, value[m], k, sub_length);
+/*
+try {
+            System.arraycopy(sub_vals[m], 0, value[m], k, sub_length);
+}
+catch (ArrayIndexOutOfBoundsException e) {
+  System.out.println("m = " + m + " k = " + k + " sub_length = " + sub_length +
+                     " sub_vals[m].length = " + sub_vals[m].length +
+                     " value[m].length = " + value[m].length);
+  System.out.println("npts = " + npts + " getLength = " + getLength() +
+                     " nsets = " + nsets + " j = " + j);
+  for (int i=0; i<nsets; i++) System.out.print(Sets[i].getLength() + " ");
+  System.out.print("\n");
+
+m = 0 k = 1 sub_length = 2 sub_vals[m].length = 2 value[m].length = 2
+npts = 2 getLength = 2 nsets = 2 j = 1
+1 2 
+m = 1 k = 1 sub_length = 2 sub_vals[m].length = 2 value[m].length = 2
+npts = 2 getLength = 2 nsets = 2 j = 1
+1 2 
+java.lang.ArrayIndexOutOfBoundsException
+        at visad.UnionSet.makeSpatial(UnionSet.java:321)
+}
+*/
+/*
+            for (int i=0; i<sub_length; i++) {
+              value[m][k+i] = sub_vals[m][i];
+            }
+*/
+          }
+          k += sub_length;
+        }
+        return value;
+      } // end if (ramp)
+    } // end if (npts == getLength())
+
+    int[][] subindex = new int[nsets][];
+
+    // classify each index point into its proper subset
+    int[] ind_lens = new int[nsets];
+    int[] set_num = new int[npts];
+    int[] new_ind = new int[npts];
+    for (int j=0; j<npts; j++) {
+      int q = 0;
+      int curind = index[j];
+      while (q < nsets && curind >= Sets[q].Length) {
+        curind -= Sets[q++].Length;
+/*
+System.out.println("curind = " + curind +
+                   " Sets[" + (q-1) + "].Length = " + Sets[q-1].Length);
+*/
+      }
+      if (q == nsets) curind += Sets[--q].Length;
+      ind_lens[q]++;
+      set_num[j] = q;
+      new_ind[j] = curind;
+/*
+System.out.println("index[" + j + "] = " + index[j] +
+                   " ind_lens[" + q + "] = " + ind_lens[q] +
+                   " set_num[" + j + "] = " + set_num[j] +
+                   " new_ind[" + j + "] = " + new_ind[j]);
+*/
+    }
+
+    // allocate subset space
+    for (int i=0; i<nsets; i++) {
+      subindex[i] = (ind_lens[i] > 0) ? new int[ind_lens[i]] : null;
+      ind_lens[i] = 0;
+    }
+
+    // copy each index point into its proper subset
+    for (int j=0; j<npts; j++) {
+      subindex[set_num[j]][ind_lens[set_num[j]]++] = new_ind[j];
+    }
+
+    // call Sets indexToValue methods
+    float[][][] subvals = new float[nsets][][];
+    for (int i=0; i<nsets; i++) {
+      if (subindex[i] != null) {
+        subvals[i] = Sets[i].indexToValue(subindex[i]);
+      }
+    }
+
+    // compile value array
+    for (int i=0; i<nsets; i++) ind_lens[i] = 0;
+    for (int j=0; j<npts; j++) {
+      for (int k=0; k<DomainDimension; k++) {
+        value[k][j] = subvals[set_num[j]][k][ind_lens[set_num[j]]];
+/*
+System.out.println("set_num[" + j + "] = " + set_num[j] +
+                   " ind_lens[set_num[" + j + "]] = " + ind_lens[set_num[j]]);
+*/
+      }
+      ind_lens[set_num[j]] += 1;
+    }
+
+    return value;
+  }
+
+  /** convert an array of values in R^DomainDimension
+      to an array of 1-D indices */
+  public int[] valueToIndex(float[][] value) throws VisADException {
+    int nsets = Sets.length;
+    int npts = value[0].length;
+    int[] index = new int[npts];
+
+    // get closest indices in each set
+    int[][] subindex = new int[nsets][];
+    for (int i=0; i<nsets; i++) {
+      subindex[i] = Sets[i].valueToIndex(value);
+    }
+
+    // get actual values of returned indices
+    float[][][] subvalue = new float[nsets][][];
+    for (int i=0; i<nsets; i++) {
+      subvalue[i] = Sets[i].indexToValue(subindex[i]);
+    }
+
+    // compute actual indices
+    for (int j=0; j<npts; j++) {
+      float[] dist_sqr = new float[nsets];
+      int best_set = 0;
+      for (int i=0; i<nsets; i++) {
+        // compute distance between subvalue[i] and value[i]
+        dist_sqr[i] = 0;
+        for (int k=0; k<DomainDimension; k++) {
+          if (subindex[i][j] == -1) {
+            dist_sqr[i] = Float.MAX_VALUE;
+            break;
+          }
+          else {
+            float d = subvalue[i][k][j] - value[k][j];
+            dist_sqr[i] += d*d;
+          }
+        }
+        if (dist_sqr[best_set] > dist_sqr[i]) best_set = i;
+      }
+      int ind = subindex[best_set][j];
+      while (best_set > 0) ind += Sets[--best_set].Length;
+      index[j] = ind;
+    }
+
+    return index;
+  }
+
+  /** for each of an array of values in R^DomainDimension, compute an array
+      of 1-D indices and an array of weights, to be used for interpolation;
+      indices[i] and weights[i] are null if i-th value is outside grid
+      (i.e., if no interpolation is possible) */
+  public void valueToInterp(float[][] value, int[][] indices,
+                            float[][] weights) throws VisADException {
+    int nsets = Sets.length;
+    int length = indices.length;
+    int offset = 0;
+    for (int i=0; i<nsets; i++) {
+      int[][] temp_indices = new int[length][];
+      float[][] temp_weights = new float[length][];
+      Sets[i].valueToInterp(value, temp_indices, temp_weights);
+      for (int j=0; j<length; j++) {
+        if (indices[j] == null && temp_indices[j] != null) {
+          int m = temp_indices[j].length;
+          indices[j] = new int[m];
+          weights[j] = new float[m];
+          for (int k=0; k<m; k++) {
+            indices[j][k] = temp_indices[j][k] + offset;
+            weights[j][k] = temp_weights[j][k];
+          }
+        }
+      }
+      offset += Sets[i].getLength();
+    }
+  }
+
+  /**
+   * Clones this instance.
+   *
+   * @return                        A clone of this instance.
+   */
+  public Object clone() {
+    UnionSet clone = (UnionSet)super.clone();
+    
+    /*
+     * The array of sampled sets is cloned because getSamples(false) allows
+     * clients to modify the values and the clone() general contract forbids
+     * cross-clone effects.
+     */
+    if (Sets != null) {
+        clone.Sets = new SampledSet[Sets.length];
+        for (int i = 0; i < Sets.length; i++)
+            clone.Sets[i] = (SampledSet)Sets[i].clone();
+    }
+    
+    return clone;
+  }
+
+  /**
+   * Clone this UnionSet, but give it a new MathType; this is safe,
+   * since constructor checks consistency of DomainCoordinateSystem
+   * and SetUnits with type.
+   *
+   * @param   type   new MathType for the UnionSet
+   * @return  UnionSet with the new MathType
+   *
+   * @exception  VisADException  couldn't create the new UnionSet
+   */
+  public Object cloneButType(MathType type) throws VisADException {
+    int n = Sets.length;
+    SampledSet[] sets = new SampledSet[n];
+    for (int i=0; i<n; i++) {
+      sets[i] = (SampledSet) Sets[i].cloneButType(type);
+    }
+    return new UnionSet(type, sets, DomainCoordinateSystem,
+                        SetUnits, SetErrors);
+/* WLH 3 April 2003
+    return new UnionSet(type, Sets, DomainCoordinateSystem,
+                        SetUnits, SetErrors);
+*/
+  }
+
+  /**
+   * Check to see if two UnionSets are equal.
+   *
+   * @return  true if each of the sets in set is equal to the sets in this.
+   */
+  public boolean equals(Object set) {
+    if (!(set instanceof UnionSet) || set == null) return false;
+    if (this == set) return true;
+
+    UnionSet uset = (UnionSet) set;
+    if (uset.DomainDimension != DomainDimension
+     || uset.ManifoldDimension != ManifoldDimension) return false;
+
+    //Added this to make sure both arrays are equal length
+    if (Sets.length != uset.Sets.length) return false;    
+
+    for (int i=0; i<Sets.length; i++) {
+      if (!Sets[i].equals(uset.Sets[i])) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the hash code of this instance.
+   * @return		The hash code of this instance.
+   */
+  public int hashCode() {
+    if (!hashCodeSet)
+    {
+      hashCode = DomainDimension ^ ManifoldDimension;
+      for (int i=0; i<Sets.length; i++)
+	hashCode ^= Sets[i].hashCode();
+      hashCodeSet = true;
+    }
+    return hashCode;
+  }
+
+  /**
+   * Check to see if any of the sets in this UnionSet has missing data.
+   *
+   * @return  true if any of the sets has missing data, otherwise false
+   */
+  public boolean isMissing() {
+    for (int i=0; i<Sets.length; i++) {
+      if (Sets[i].isMissing()) return true;
+    }
+    return false;
+  }
+
+  /* run 'java visad.UnionSet' to test the UnionSet class */
+  public static void main(String[] argv) throws VisADException {
+    RealType vis_xcoord = RealType.getRealType("1D");
+    RealType[] vis_array = {vis_xcoord};
+    RealTupleType vis_tuple = new RealTupleType(vis_array);
+
+    // create Gridded1DSet
+    float[][] sampG = { {2f, 4f, 6f, 8f, 10f, 12f, 14f, 16f} };
+    Gridded1DSet gSet = new Gridded1DSet(vis_tuple, sampG, 8);
+
+    // create Irregular1DSet
+    float[][] sampI = { {100f, 90f, 110f, 80f, 120f, 70f, 130f, 60f} };
+    Irregular1DSet iSet = new Irregular1DSet(vis_tuple, sampI);
+
+    // create UnionSet as the union of gSet and iSet
+    SampledSet[] sets = {gSet, iSet};
+    UnionSet uSet = new UnionSet(vis_tuple, sets);
+
+    // run some tests
+    System.out.println("UnionSet created.");
+    System.out.println("ManifoldDimension = "+uSet.getManifoldDimension());
+    System.out.println("-----------------");
+    System.out.println("indexToValue test:");
+    int[] index1 = {0, 3, 9, 12, 6, 15};
+    float[][] value1 = uSet.indexToValue(index1);
+    for (int i=0; i<index1.length; i++) {
+      System.out.print("index "+index1[i]+" \t==> ("+value1[0][i]);
+      for (int j=1; j<value1.length; j++) {
+        System.out.print(", "+value1[j][i]);
+      }
+      System.out.println(")");
+    }
+
+    System.out.println("-----------------");
+    System.out.println("valueToIndex test:");
+    // float[][] value2 = { {10f, 40f, 90f, 25f, 50f, 100f, 30f, 70f} };
+    float[][] value2 = { {15f, 40f, 92f, 25f, 50f, 103f, 37f, 77f} };
+    int[] index2 = uSet.valueToIndex(value2);
+    for (int i=0; i<index2.length; i++) {
+      System.out.print("("+value2[0][i]);
+      for (int j=1; j<value2.length; j++) {
+        System.out.print(", "+value2[j][i]);
+      }
+      System.out.println(")\t==> index "+index2[i]);
+    }
+
+    System.out.println("-----------------");
+    System.out.println("valueToInterp test:");
+    int n = value2[0].length;
+    int[][] indices = new int[n][];
+    float[][] weights = new float[n][];
+    uSet.valueToInterp(value2, indices, weights);
+    for (int i=0; i<n; i++) {
+      System.out.print("("+value2[0][i]);
+      for (int j=1; j<value2.length; j++) {
+        System.out.print(", "+value2[j][i]);
+      }
+      System.out.print(")\t==>");
+      if (indices[i] == null || indices[i].length == 0) {
+        System.out.println(" missing");
+      }
+      else {
+        int m = indices[i].length;
+        for (int j=0; j<m; j++) {
+          System.out.print(" (" + indices[i][j] + "," + weights[i][j] + ")");
+        }
+        System.out.println(" ");
+      }
+    }
+
+    System.out.println();
+  }
+
+  public String longString(String pre) throws VisADException {
+    String s = pre + "UnionSet: Dimension = " + DomainDimension +
+               " Length = " + getLength() + "\n";
+    int n = Sets.length;
+    for (int i=0; i<n; i++) {
+      s = s + Sets[i].toString();
+    }
+    return s;
+  }
+
+}
+
diff --git a/visad/Unit.java b/visad/Unit.java
new file mode 100644
index 0000000..b379b92
--- /dev/null
+++ b/visad/Unit.java
@@ -0,0 +1,1138 @@
+//
+// Unit.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+ */
+
+package visad;
+
+import java.io.Serializable;
+
+/**
+ * A class that represents a unit of a quantity.
+ * 
+ * Instances are immutable.
+ * 
+ * @author Steven R. Emmerson
+ * 
+ *         This is part of Steve Emmerson's Unit package that has been
+ *         incorporated into VisAD.
+ */
+public abstract class Unit implements Serializable {
+    private static final long   serialVersionUID    = 1L;
+
+    /**
+     * The identifier (name or abbreviation) for this unit.
+     * 
+     * @serial
+     */
+    private final String        identifier;
+
+    protected transient int     hashCode            = 0;
+
+    /*
+     * added by Bill Hibbard for VisAD
+     */
+
+    /**
+     * <p>
+     * Converts a tuple of double value arrays; returning a new tuple.
+     * </p>
+     * 
+     * <p>
+     * This implementation uses {@link #toThis(double[], Unit)} to convert the
+     * individual arrays.
+     * </p>
+     * 
+     * @param value
+     *            The tuple of numeric value arrays to convert.
+     *            <code> value[i][j]</code> is the value of the <code> i</code>
+     *            th component of sample-point <code>j </code>.
+     * @param units_in
+     *            The units of the input numeric values.
+     *            <code>units_in[i]</code> is the unit of the <code>i</code>th
+     *            conponent.
+     * @param units_out
+     *            The units of the output numeric values.
+     *            <code>units_out[i]</code> is the unit for the <code>i</code>th
+     *            conponent.
+     * @return Returns the converted values in a new array where RETURN_VALUE
+     *         <code>[i][j]</code> is the converted value of
+     *         <code>value[i][j]</code>.
+     * @throws UnitException
+     *             If an ouput unit is <code>null</code> and the corresponding
+     *             input unit is neither <code>null</code> nor a
+     *             {@link PromiscuousUnit}, or if an input unit is not
+     *             convertible with its corresponding output unit.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static double[][] convertTuple(final double[][] value,
+            final Unit[] units_in, final Unit[] units_out)
+            throws UnitException, VisADException {
+        return convertTuple(value, units_in, units_out, true);
+    }
+
+    /**
+     * <p>
+     * Converts a tuple of double value arrays, optionally returning a new tuple
+     * depending on the value of <code>copy</code>.
+     * </p>
+     * 
+     * <p>
+     * This implementation uses {@link #toThis(double[], Unit)} to convert the
+     * individual arrays.
+     * </p>
+     * 
+     * @param value
+     *            The tuple of numeric value arrays to convert.
+     *            <code> value[i][j]</code> is the value of the <code> i</code>
+     *            th component of sample-point <code>j </code>.
+     * @param units_in
+     *            The units of the input numeric values.
+     *            <code>units_in[i]</code> is the unit of the <code>i</code>th
+     *            conponent.
+     * @param units_out
+     *            The units of the output numeric values.
+     *            <code>units_out[i]</code> is the unit for the <code>i</code>th
+     *            conponent.
+     * @param copy
+     *            If true, a new array is created, otherwise if a unit in
+     *            <code>units_in</code> equals the unit at the corresponding
+     *            index in the <code>units_out</code>, the input value array at
+     *            that index is returned instead of a new array.
+     * @return If <code>units_in</code> equals <code>units_out
+   *                           </code>
+     *         copy is false, then this just returns the value argument.
+     *         Otherwise, returns the the converted values in a new array where
+     *         RETURN_VALUE <code>[i][j]</code> is the converted value of
+     *         <code>value[i][j]</code>.
+     * @throws UnitException
+     *             If an ouput unit is <code>null</code> and the corresponding
+     *             input unit is neither <code>null</code> nor a
+     *             {@link PromiscuousUnit}, or if an input unit is not
+     *             convertible with its corresponding output unit.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static double[][] convertTuple(final double[][] value,
+            final Unit[] units_in, final Unit[] units_out, final boolean copy)
+            throws UnitException, VisADException {
+
+        // If the input array equals the output array then simply return the
+        // value array
+        if (java.util.Arrays.equals(units_in, units_out) && !copy) {
+            return value;
+        }
+        final double[][] new_value = new double[value.length][];
+        for (int i = 0; i < value.length; i++) {
+            if (units_out[i] == null) {
+                if (units_in[i] != null
+                        && !(units_in[i] instanceof PromiscuousUnit)) {
+                    throw new UnitException(
+                            "Unit.convertTuple: illegal Unit conversion");
+                }
+                new_value[i] = (copy)
+                        ? (double[]) value[i].clone()
+                        : value[i];
+            }
+            else {
+                // If they are equal just do an assignment
+                if (units_out[i].equals(units_in[i]) && !copy) {
+                    new_value[i] = value[i];
+                }
+                else {
+                    // else do the conversion (creates a new array)
+                    new_value[i] = units_out[i].toThis(value[i], units_in[i]);
+                }
+            }
+        }
+        return new_value;
+    }
+
+    /**
+     * <p>
+     * Converts a tuple of float value arrays; returning a new tuple.
+     * </p>
+     * 
+     * <p>
+     * This implementation uses {@link #toThis(float[], Unit)} to convert the
+     * individual arrays.
+     * </p>
+     * 
+     * @param value
+     *            The tuple of numeric value arrays to convert.
+     *            <code> value[i][j]</code> is the value of the <code> i</code>
+     *            th component of sample-point <code>j </code>.
+     * @param units_in
+     *            The units of the input numeric values.
+     *            <code>units_in[i]</code> is the unit of the <code>i</code>th
+     *            conponent.
+     * @param units_out
+     *            The units of the output numeric values.
+     *            <code>units_out[i]</code> is the unit for the <code>i</code>th
+     *            conponent.
+     * @return Returns the converted values in a new array where RETURN_VALUE
+     *         <code>[i][j]</code> is the converted value of
+     *         <code>value[i][j]</code>.
+     * @throws UnitException
+     *             If an ouput unit is <code>null</code> and the corresponding
+     *             input unit is neither <code>null</code> nor a
+     *             {@link PromiscuousUnit}, or if an input unit is not
+     *             convertible with its corresponding output unit.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static float[][] convertTuple(final float[][] value,
+            final Unit[] units_in, final Unit[] units_out)
+            throws UnitException, VisADException {
+        return convertTuple(value, units_in, units_out, true);
+    }
+
+    /**
+     * <p>
+     * Converts a tuple of float value arrays, optionally returning a new tuple
+     * depending on the value of <code>copy</code>.
+     * </p>
+     * 
+     * <p>
+     * This implementation uses {@link #toThis(float[], Unit)} to convert the
+     * individual arrays.
+     * </p>
+     * 
+     * @param value
+     *            The tuple of numeric value arrays to convert.
+     *            <code> value[i][j]</code> is the value of the <code> i</code>
+     *            th component of sample-point <code>j </code>.
+     * @param units_in
+     *            The units of the input numeric values.
+     *            <code>units_in[i]</code> is the unit of the <code>i</code>th
+     *            conponent.
+     * @param units_out
+     *            The units of the output numeric values.
+     *            <code>units_out[i]</code> is the unit for the <code>i</code>th
+     *            conponent.
+     * @param copy
+     *            If true, a new array is created, otherwise if a unit in
+     *            <code>units_in</code> equals the unit at the corresponding
+     *            index in the <code>units_out</code>, the input value array at
+     *            that index is returned instead of a new array.
+     * @return If <code>units_in</code> equals <code>units_out
+   *                           </code>
+     *         copy is false, then this just returns the value argument.
+     *         Otherwise, returns the the converted values in a new array where
+     *         RETURN_VALUE <code>[i][j]</code> is the converted value of
+     *         <code>value[i][j]</code>.
+     * @throws UnitException
+     *             If an ouput unit is <code>null</code> and the corresponding
+     *             input unit is neither <code>null</code> nor a
+     *             {@link PromiscuousUnit}, or if an input unit is not
+     *             convertible with its corresponding output unit.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static float[][] convertTuple(final float[][] value,
+            final Unit[] units_in, final Unit[] units_out, final boolean copy)
+            throws UnitException, VisADException {
+
+        // If the input array equals the output array then simply return the
+        // value array
+        if (java.util.Arrays.equals(units_in, units_out) && !copy) {
+            return value;
+        }
+        final float[][] new_value = new float[value.length][];
+        for (int i = 0; i < value.length; i++) {
+            if (units_out[i] == null) {
+                if (units_in[i] != null
+                        && !(units_in[i] instanceof PromiscuousUnit)) {
+                    throw new UnitException(
+                            "Unit.convertTuple: illegal Unit conversion");
+                }
+                new_value[i] = (copy)
+                        ? (float[]) value[i].clone()
+                        : value[i];
+            }
+            else {
+                // If they are equal just do an assignment
+                if (units_out[i].equals(units_in[i]) && !copy) {
+                    new_value[i] = value[i];
+                }
+                else {
+                    // else do the conversion
+                    new_value[i] = units_out[i].toThis(value[i], units_in[i]);
+                }
+            }
+        }
+        return new_value;
+    }
+
+    /**
+     * Indicates if values in two units are convertible. The values of two units
+     * are convertible if each unit is either <code>null</code> or the
+     * promiscuous unit, or if one unit is neither <code>null</code> nor the
+     * promiscuous unit and the other unit is either the promiscuous unit or a
+     * convertible unit.
+     * 
+     * @param unita
+     *            One unit.
+     * @param unitb
+     *            The other unit.
+     * @return <code>true</code> if and only if values in the the two units are
+     *         convertible.
+     * @see #isConvertible(Unit)
+     */
+    public static boolean canConvert(Unit unita, Unit unitb) {
+        if (CommonUnit.promiscuous.equals(unita) ||
+            CommonUnit.promiscuous.equals(unitb)) {
+            return true;
+        }
+        if (unita == null && unitb == null) {
+            return true;
+        }
+        if (unita == null || unitb == null) {
+            return false;
+        }
+        // WLH - real logic goes here
+        return unita.isConvertible(unitb);
+    }
+
+    /**
+     * Indicate whether this unit is convertible with another unit. If one unit
+     * is convertible with another, then the <code>toThis(...)</code>/ and
+     * <code>toThat(...)</code> methods will not throw a UnitException. Unit A
+     * is convertible with unit B if and only if unit B is convertible with unit
+     * A; hence, calling-order is irrelevant.
+     * 
+     * @param unit
+     *            The other unit.
+     * @return True if and only if this unit is convertible with the other unit.
+     */
+    public abstract boolean isConvertible(Unit unit);
+
+    /**
+     * <p>
+     * Indicates whether or not two unit arrays are convertible. Two such arrays
+     * are convertible if and only if the units of their corresponding elements
+     * are convertible.
+     * </p>
+     * 
+     * <p>
+     * This implementation uses {@link #canConvert(Unit, Unit)} to determine
+     * convertibility of the element pairs.
+     * 
+     * @param unita
+     *            One array of units. May be <code>null</code>.
+     * @param unitb
+     *            The other array of units. May be <code>null</code>.
+     * @return <code>true</code> if and only if both unit arrays are
+     *         <code>null</code> or the two unit arrays are element-by-element
+     *         convertible.
+     */
+    public static boolean canConvertArray(Unit[] unita, Unit[] unitb) {
+        if (unita == null && unitb == null) {
+            return true;
+        }
+        if (unita == null) {
+            unita = new Unit[unitb.length];
+        }
+        if (unitb == null) {
+            unitb = new Unit[unita.length];
+        }
+        final int n = unita.length;
+        if (n != unitb.length) {
+            return false;
+        }
+        for (int i = 0; i < n; i++) {
+            if (!canConvert(unita[i], unitb[i])) {
+                // System.out.println("i = " + i + " " + unita[i] + " != " +
+                // unitb[i]);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Copys an array of units. This is a helper method for {@link Set},
+     * {@link RealTupleType}, {@link CoordinateSystem}, etc.
+     * 
+     * @param units
+     *            The array of units or <code>null</code>.
+     * @return A copy of the array of units or <code>null</code>. if the input
+     *         array is <code>null</code>.
+     */
+    public static Unit[] copyUnitsArray(final Unit[] units) {
+        return units == null
+                ? null
+                : (Unit[]) units.clone();
+    }
+
+    /**
+     * Indicates whether or not this instance is equal to an object.
+     * 
+     * @param that
+     *            The object in question.
+     * @return <code>true</code> if and only if this instance equals the unit.
+     */
+    @Override
+    public boolean equals(final Object that) {
+        if (!(that instanceof Unit)) {
+            return false;
+        }
+        return equals((Unit) that);
+    }
+
+    /**
+     * Abstract method for computing hashcode
+     */
+    @Override
+    public abstract int hashCode();
+
+    /**
+     * Indicates whether or not this instance is equal to a unit.
+     * 
+     * @param unit
+     *            The unit.
+     * @return <code>true</code> if and only if this instance equals the unit.
+     */
+    public abstract boolean equals(Unit unit);
+
+    /**
+     * Transform double values and (optionally) error estimates.
+     * 
+     * @param unit_out
+     *            The unit of the output numeric values or <code>null</code>.
+     * @param errors_out
+     *            The output error estimate. <code>errors_out[0]
+   *                          </code>
+     *            will contain the output error estimate, which may be
+     *            <code>null</code>.
+     * @param unit_in
+     *            The unit of the input numeric values.
+     * @param error_in
+     *            The input error estimate or <code>null</code>.
+     * @param value
+     *            The input numeric value.
+     * @return The corresponding, transformed numeric values in the same array
+     *         only if the input and output units were <code>null</code>;
+     *         otherwise, a new array is returned.
+     * @throws NullPointerException
+     *             if <code>errors_out</code> is <code>null
+   *                              </code>
+     *             .
+     * @throws UnitException
+     *             if the input and output unit aren't convertible.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static double[] transformUnits(final Unit unit_out,
+            final ErrorEstimate[] errors_out, final Unit unit_in,
+            final ErrorEstimate error_in, final double[] value)
+            throws UnitException, VisADException {
+
+        return transformUnits(unit_out, errors_out, unit_in, error_in, value,
+                true);
+    }
+
+    /**
+     * Transform float values and (optionally) error estimates.
+     * 
+     * @param unit_out
+     *            The unit of the output numeric values or <code>null</code>.
+     * @param errors_out
+     *            The output error estimate. <code>errors_out[0]
+   *                          </code>
+     *            will contain the output error estimate, which may be
+     *            <code>null</code>.
+     * @param unit_in
+     *            The unit of the input numeric values.
+     * @param error_in
+     *            The input error estimate or <code>null</code>.
+     * @param value
+     *            The input numeric value.
+     * @return The corresponding, transformed numeric values in the same array
+     *         only if the input and output units were <code>null</code>;
+     *         otherwise, a new array is returned.
+     * @throws NullPointerException
+     *             if <code>errors_out</code> is <code>null
+   *                              </code>
+     *             .
+     * @throws UnitException
+     *             if the input and output unit aren't convertible.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static float[] transformUnits(final Unit unit_out,
+            final ErrorEstimate[] errors_out, final Unit unit_in,
+            final ErrorEstimate error_in, final float[] value)
+            throws UnitException, VisADException {
+
+        return transformUnits(unit_out, errors_out, unit_in, error_in, value,
+                true);
+    }
+
+    /**
+     * Transform double values and (optionally) error estimates.
+     * 
+     * @param unit_out
+     *            The unit of the output numeric values or <code>null</code>.
+     * @param errors_out
+     *            The output error estimate. <code>errors_out[0]
+   *                          </code>
+     *            will contain the output error estimate, which may be
+     *            <code>null</code>.
+     * @param unit_in
+     *            The unit of the input numeric values.
+     * @param error_in
+     *            The input error estimate or <code>null</code>.
+     * @param value
+     *            The input numeric value.
+     * @param copy
+     *            if false and <code>unit_out</code> equals <code>unit_in</code>
+     *            , transform values in place.
+     * @return The corresponding, transformed numeric values in the same array
+     *         only if the input and output units were <code>null</code>;
+     *         otherwise, a new array is returned.
+     * @throws NullPointerException
+     *             if <code>errors_out</code> is <code>null
+   *                              </code>
+     *             .
+     * @throws UnitException
+     *             if the input and output unit aren't convertible.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static double[] transformUnits(final Unit unit_out,
+            final ErrorEstimate[] errors_out, final Unit unit_in,
+            final ErrorEstimate error_in, final double[] value,
+            final boolean copy) throws UnitException, VisADException {
+
+        if (unit_out == null || unit_in == null) {
+            errors_out[0] = error_in;
+            return (copy)
+                    ? (double[]) value.clone()
+                    : value;
+        }
+        else {
+            // convert value array
+            final double[] val = unit_out.toThis(value, unit_in, copy);
+
+            // construct new ErrorEstimate, if needed
+            if (error_in == null) {
+                errors_out[0] = null;
+            }
+            else {
+                // scale data.ErrorEstimate for Unit.toThis
+                final double error = 0.5 * error_in.getErrorValue();
+                final double mean = error_in.getMean();
+                final double new_error = Math.abs(unit_out.toThis(mean + error,
+                        unit_in)
+                        - unit_out.toThis(mean - error, unit_in));
+                errors_out[0] = new ErrorEstimate(val, new_error, unit_out);
+            }
+
+            // return value array
+            return val;
+        }
+    }
+
+    /**
+     * Transform float values and (optionally) error estimates.
+     * 
+     * @param unit_out
+     *            The unit of the output numeric values or <code>null</code>.
+     * @param errors_out
+     *            The output error estimate. <code>errors_out[0]
+   *                          </code>
+     *            will contain the output error estimate, which may be
+     *            <code>null</code>.
+     * @param unit_in
+     *            The unit of the input numeric values.
+     * @param error_in
+     *            The input error estimate or <code>null</code>.
+     * @param value
+     *            The input numeric value.
+     * @param copy
+     *            if false and <code>unit_out</code> equals <code>unit_in</code>
+     *            , transform values in place.
+     * @return The corresponding, transformed numeric values in the same array
+     *         only if the input and output units were <code>null</code>;
+     *         otherwise, a new array is returned.
+     * @throws NullPointerException
+     *             if <code>errors_out</code> is <code>null
+   *                              </code>
+     *             .
+     * @throws UnitException
+     *             if the input and output unit aren't convertible.
+     * @throws VisADException
+     *             if a VisAD failure occurs.
+     */
+    public static float[] transformUnits(final Unit unit_out,
+            final ErrorEstimate[] errors_out, final Unit unit_in,
+            final ErrorEstimate error_in, final float[] value,
+            final boolean copy) throws UnitException, VisADException {
+
+        if (unit_out == null || unit_in == null) {
+            errors_out[0] = error_in;
+            return (copy)
+                    ? (float[]) value.clone()
+                    : value;
+        }
+        else {
+            // convert value array
+            final float[] val = unit_out.toThis(value, unit_in, copy);
+
+            // construct new ErrorEstimate, if needed
+            if (error_in == null) {
+                errors_out[0] = null;
+            }
+            else {
+                // scale data.ErrorEstimate for Unit.toThis
+                final double error = 0.5 * error_in.getErrorValue();
+                final double mean = error_in.getMean();
+                final double new_error = Math.abs(unit_out.toThis(mean + error,
+                        unit_in)
+                        - unit_out.toThis(mean - error, unit_in));
+                errors_out[0] = new ErrorEstimate(val, new_error, unit_out);
+            }
+
+            // return value array
+            return val;
+        }
+    }
+
+    Unit scale(final double amount, final boolean b) {
+        return new ScaledUnit(amount, this);
+    }
+
+    /*
+     * end of added by Bill Hibbard for VisAD
+     */
+
+    /**
+     * Constructs from nothing.
+     */
+    protected Unit() {
+        this.identifier = null;
+    }
+
+    /**
+     * Constructs from an identifier.
+     * 
+     * @param identifier
+     *            Name or abbreviation for the unit. May be <code>null</code> or
+     *            empty.
+     */
+    protected Unit(String identifier) {
+        try {
+            identifier = adjustCheckAndCache(identifier);
+        }
+        catch (final UnitExistsException e) {
+            System.err.println("WARNING: " + e);
+        }
+        this.identifier = identifier;
+    }
+
+    /**
+     * Adjusts, checks, and caches a unit identifier and its unit.
+     * 
+     * @param identifier
+     *            Name or abbreviation for the unit. May be <code>null</code> or
+     *            empty.
+     * @return The identifier adjusted as necessary in order to be valid (e.g.
+     *         whitespace replacement).
+     * @throws UnitExistsException
+     *             A different unit with the same, non-null and non-empty
+     *             identifier already exists. The identifier and unit are not
+     *             cached.
+     */
+    protected final String adjustCheckAndCache(final String identifier)
+            throws UnitExistsException {
+        if (identifier != null && identifier.length() > 0) {
+            /*
+             * identifier = identifier.replace(' ', '_'); // ensure no
+             * whitespace synchronized(identifierMap) { Unit previous =
+             * (Unit)identifierMap.get(identifier); if (previous != null) throw
+             * new UnitExistsException(identifier);
+             * identifierMap.put(identifier, this); }
+             */
+        }
+        return identifier;
+    }
+
+    /**
+     * <p>
+     * Indicates if this instance is dimensionless. A unit is dimensionless if
+     * it is a measure of a dimensionless quantity like angle or concentration.
+     * Examples of dimensionless units include radian, degree, steradian, and
+     * "g/kg".
+     * </p>
+     * 
+     * @return True if an only if this unit is dimensionless.
+     */
+    public abstract boolean isDimensionless();
+
+    /**
+     * <p>
+     * Clones this unit, changing the identifier.
+     * </p>
+     * 
+     * <p>
+     * This implementation uses the {@link #protectedClone(String)} method.
+     * </p>
+     * 
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty.
+     * @return A unit equal to this instance but with the given identifier
+     *         (adjusted if necessary).
+     * @throws UnitException
+     *             The unit may not be cloned. This will only occur if
+     *             <code>getIdentifier()!=null</code>.
+     * @see #adjustCheckAndCache(String)
+     */
+    public Unit clone(final String identifier) throws UnitException {
+        return protectedClone(adjustCheckAndCache(identifier));
+    }
+
+    /**
+     * Clones this unit, changing the identifier.
+     * 
+     * @param identifier
+     *            The name or abbreviation for the cloned unit. May be
+     *            <code>null</code> or empty. It shall have already passed the
+     *            {@link #adjustCheckAndCache(String)} method.
+     * @return A unit equal to this instance but with the given identifier.
+     * @throws UnitException
+     *             if the unit may not be cloned. This will only occur if
+     *             <code>getIdentifier()!=null</code>.
+     */
+    protected abstract Unit protectedClone(String identifier)
+            throws UnitException;
+
+    /**
+     * Raise this unit to a power.
+     * 
+     * @param power
+     *            The power to raise this unit by.
+     * @return The resulting unit.
+     * require: The unit is not an offset unit.
+     * promise: The unit has not been modified.
+     * @exception UnitException
+     *                It's meaningless to raise this unit by a power.
+     */
+    public abstract Unit pow(int power) throws UnitException;
+
+    /**
+     * Returns the N-th root of this unit.
+     * 
+     * @param root
+     *            The root to take (e.g. 2 means square root). Must not be zero.
+     * @return The unit corresponding to the <code>root</code>-th root of this
+     *         unit.
+     * require: The unit is not an offset unit.
+     * promise: The unit has not been modified.
+     * @exception UnitException
+     *                It's meaningless to raise this unit by a power.
+     * @throws IllegalArgumentException
+     *             The root value is zero or the resulting unit would have a
+     *             non-integral unit dimension.
+     */
+    public abstract Unit root(int root) throws IllegalArgumentException,
+            UnitException;
+
+    /**
+     * Returns the square-root of this unit. This method is identical to
+     * {@link #root(int root)} with a value of <code>2</code>.
+     * 
+     * @return The unit corresponding to the square-root of this unit.
+     * promise: This unit has not been modified.
+     * @throws IllegalArgumentException
+     *             The resulting unit would have a non-integral unit dimension.
+     * @throws UnitException
+     *             It is meaningless to take a root of this unit.
+     */
+    public Unit sqrt() throws IllegalArgumentException, UnitException {
+        return root(2);
+    }
+
+    /**
+     * Raise a unit to a power.
+     * 
+     * @param power
+     *            The power to raise this unit by. If this unit is not
+     *            dimensionless, then the value must be integral.
+     * @return The unit resulting from raising this unit to <code>power</code>.
+     * @throws UnitException
+     *             It's meaningless to raise this unit by a power.
+     * @throws IllegalArgumentException
+     *             This unit is not dimensionless and <code>power</code> has a
+     *             non-integral value.
+     * promise: The unit has not been modified.
+     */
+    public abstract Unit pow(double power) throws UnitException,
+            IllegalArgumentException;
+
+    /**
+     * Scale this unit by an amount.
+     * 
+     * @param amount
+     *            The amount by which to scale this unit. E.g. Unit yard =
+     *            meter.scale(0.9144);
+     * @return A unit equal to this instance scaled by the given amount.
+     * @exception UnitException
+     *                This unit cannot be scaled.
+     */
+    public abstract Unit scale(final double amount) throws UnitException;
+
+    /**
+     * Shift this unit by an amount.
+     * 
+     * @param offset
+     *            The amount by which to shift this unit. E.g. Unit celsius =
+     *            kelvin.shift(273.15);
+     * @return A unit equal to this instance with the origin shifted by the
+     *         given amount.
+     * @exception UnitException
+     *                The unit subclass is unknown.
+     */
+    public abstract Unit shift(final double offset) throws UnitException;
+
+    /**
+     * Returns the logarithmic unit that has this unit as its reference level.
+     * Not all units can be used as a reference level.
+     * 
+     * @param base
+     *            The logarithmic base: one of {@code 2}, {@link Math#E}, or
+     *            {@code 10}.
+     * @return The logarithmic unit that has this instance as its reference
+     *         level.
+     * @throws IllegalArgumentException
+     *             if {@code base} isn't one of the allowed values.
+     * @throws UnitException
+     *             if this unit can't be used as a reference level for a
+     *             logarithmic unit.
+     */
+    public abstract Unit log(final double base) throws UnitException;
+
+    /**
+     * Multiply this unit by another unit.
+     * 
+     * @param that
+     *            The given unit to multiply this unit by.
+     * @return The resulting unit.
+     * @throws UnitException
+     *             It's meaningless to multiply these units.
+     */
+    public abstract Unit multiply(Unit that) throws UnitException;
+
+    /**
+     * Divide this unit by another unit.
+     * 
+     * @param that
+     *            The unit to divide into this unit.
+     * @return The quotient of the two units.
+     * promise: Neither unit has been modified.
+     * @throws UnitException
+     *             It's meaningless to divide these units.
+     */
+    public abstract Unit divide(Unit that) throws UnitException;
+
+    /**
+     * Divide this unit into another unit.
+     * 
+     * @param that
+     *            The unit to be divided by this unit.
+     * @return The quotient of the two units.
+     * @throws UnitException
+     *             It's meaningless to divide these units.
+     */
+    protected abstract Unit divideInto(Unit that) throws UnitException;
+
+    /**
+     * Convert a value to this unit from another unit.
+     * 
+     * @param value
+     *            The value in units of the other unit.
+     * @param that
+     *            The other unit.
+     * @return The value converted from the other unit to this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public double toThis(final double value, final Unit that)
+            throws UnitException {
+        return toThis(new double[] { value }, that, false)[0];
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            Values in units of the other unit.
+     * @param that
+     *            The other unit.
+     * @return Values in this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public abstract double[] toThis(double[] values, Unit that)
+            throws UnitException;
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            Values in units of the other unit.
+     * @param that
+     *            The other unit.
+     * @return Values in this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public abstract float[] toThis(float[] values, Unit that)
+            throws UnitException;
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            Values in units of the other unit.
+     * @param that
+     *            The other unit.
+     * @param copy
+     *            true to make a copy if units are not equal. Ignored in this
+     *            class.
+     * @return Values in this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public double[] toThis(final double[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return toThis(values, that);
+    }
+
+    /**
+     * Convert values to this unit from another unit.
+     * 
+     * @param values
+     *            Values in units of the other unit.
+     * @param that
+     *            The other unit.
+     * @param copy
+     *            true to make a copy if units are not equal. Ignored in this
+     *            class.
+     * @return Values in this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public float[] toThis(final float[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return toThis(values, that);
+    }
+
+    /**
+     * Convert a value from this unit to another unit.
+     * 
+     * @param value
+     *            The value in this unit.
+     * @param that
+     *            The other unit.
+     * @return The value in units of the other unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public double toThat(final double value, final Unit that)
+            throws UnitException {
+        return toThat(new double[] { value }, that, false)[0];
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values in this unit.
+     * @param that
+     *            The other unit.
+     * @return Values converted to the other unit from this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public abstract double[] toThat(double[] values, Unit that)
+            throws UnitException;
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values in this unit.
+     * @param that
+     *            The other unit.
+     * @return Values converted to the other unit from this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public abstract float[] toThat(float[] values, Unit that)
+            throws UnitException;
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values in this unit.
+     * @param that
+     *            The other unit.
+     * @param copy
+     *            true to make a copy if units are not equal. Ignored in this
+     *            class.
+     * @return Values converted to the other unit from this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public double[] toThat(final double[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return toThat(values, that);
+    }
+
+    /**
+     * Convert values from this unit to another unit.
+     * 
+     * @param values
+     *            The values in this unit.
+     * @param that
+     *            The other unit.
+     * @param copy
+     *            true to make a copy if units are not equal. Ignored in this
+     *            class.
+     * @return Values converted to the other unit from this unit.
+     * require: The units are convertible.
+     * promise: Neither unit has been modified.
+     * @exception UnitException
+     *                The units are not convertible.
+     */
+    public float[] toThat(final float[] values, final Unit that,
+            final boolean copy) throws UnitException {
+        return toThat(values, that);
+    }
+
+    /**
+     * Returns a string representation of this unit.
+     * 
+     * @return The string representation of this unit. Won't be
+     *         <code>null</code> but may be empty.
+     */
+    @Override
+    public final String toString() {
+        String s = getIdentifier();
+        if (s == null || s.length() == 0) {
+            s = getDefinition();
+        }
+        return s;
+    }
+
+    /**
+     * Returns the identifier (name or abbreviation) of this unit.
+     * 
+     * @return The identifier of this unit. May be <code>null</code> but won't
+     *         be empty.
+     */
+    public final String getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Returns the definition of this unit.
+     * 
+     * @return The definition of this unit. Won't be <code>null
+     *                  </code>
+     *         but may be empty.
+     */
+    public abstract String getDefinition();
+
+    /**
+     * Gets the absolute unit of this unit. An interval in the underlying
+     * physical quantity has the same numeric value in an absolute unit of a
+     * unit as in the unit itself -- but an absolute unit is always referenced
+     * to the physical origin of the underlying physical quantity. For example,
+     * the absolute unit corresponding to degrees celsius is degrees kelvin --
+     * and calling this method on a degrees celsius unit obtains a degrees
+     * kelvin unit.
+     * 
+     * @return The absolute unit corresponding to this unit.
+     */
+    public Unit getAbsoluteUnit() {
+        return this;
+    }
+
+    /**
+     * Returns the derived unit that underlies this unit.
+     * 
+     * @return The derived unit that underlies this unit.
+     */
+    public abstract DerivedUnit getDerivedUnit();
+    
+    private static void myAssert(boolean value) {
+        if (!value)
+            throw new AssertionError();
+    }
+
+    /**
+     * Test this class.
+     *
+     * @param args		Arguments (ignored).
+     * @exception UnitException	A problem occurred.
+     */
+    public static void main(String[] args) throws UnitException {
+        BaseUnit m = BaseUnit.addBaseUnit("length", "meter", "m");
+        Unit one = new DerivedUnit();
+        myAssert(m.equals(new ScaledUnit(1.0, m)));
+        myAssert(m.equals(new OffsetUnit(0.0, m)));
+        myAssert(one.equals(new ScaledUnit(1.0, one)));
+        myAssert(one.equals(new OffsetUnit(0.0, one)));
+        myAssert(new ScaledUnit(1.0, m).equals(new OffsetUnit(0.0, m)));
+        myAssert(new OffsetUnit(0.0, m).equals(new ScaledUnit(1.0, m)));
+        myAssert(!m.equals(null));
+        myAssert(!new DerivedUnit(m).equals(null));
+        myAssert(new DerivedUnit(m).equals(m));
+        myAssert(m.equals(new DerivedUnit(m)));
+        System.out.println("Success");
+    }
+}
diff --git a/visad/UnitException.java b/visad/UnitException.java
new file mode 100644
index 0000000..42a78d7
--- /dev/null
+++ b/visad/UnitException.java
@@ -0,0 +1,54 @@
+//
+// UnitException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * A class for exceptions in the units package.
+ * @author Steve Emmerson
+ *
+ * This is part of Steve Emmerson's Unit package that has been
+ * incorporated into VisAD.
+ */
+public class UnitException
+    extends VisADException    // change by Bill Hibbard for VisAD
+{
+    /**
+     * Create an exception with no detail message.
+     */
+    public UnitException()
+    {
+	super();
+    }
+
+    /**
+     * Create an exception with a detail message.
+     */
+    public UnitException(String msg)
+    {
+	super(msg);
+    }
+}
diff --git a/visad/UnitExistsException.java b/visad/UnitExistsException.java
new file mode 100644
index 0000000..bd1c958
--- /dev/null
+++ b/visad/UnitExistsException.java
@@ -0,0 +1,48 @@
+//
+// UnitExistsException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+ * Provides support for attempting to define a unit with a previously-used
+ * identifier.
+ *
+ * @author Steven R. Emmerson
+ *
+ * This is part of Steve Emmerson's Unit package that has been
+ * incorporated into VisAD.
+ */
+public final class UnitExistsException
+    extends UnitException
+{
+    /**
+     * Creates an exception from a unit identifier.
+     */
+    public UnitExistsException(String id)
+    {
+	super("Unit \"" + id + "\" already exists");
+    }
+}
diff --git a/visad/ValueControl.java b/visad/ValueControl.java
new file mode 100644
index 0000000..8098668
--- /dev/null
+++ b/visad/ValueControl.java
@@ -0,0 +1,47 @@
+//
+// ValueControl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.rmi.*;
+
+/**
+   ValueControl is the VisAD interface for controlling SelectValue
+   display scalars.<P>
+*/
+public interface ValueControl extends AVControl {
+
+  /** set the selected value */
+  void setValue(double value)
+         throws VisADException, RemoteException;
+
+  void init() throws VisADException;
+
+  /** return the selected value */
+  double getValue();
+
+}
+
diff --git a/visad/VisAD-Style.xjs b/visad/VisAD-Style.xjs
new file mode 100644
index 0000000..a792773
--- /dev/null
+++ b/visad/VisAD-Style.xjs
@@ -0,0 +1,4869 @@
+<?xml version="1.0" encoding="UTF-8"?> 
+<java version="1.6.0_16" class="java.beans.XMLDecoder"> 
+ <object class="jindent.JindentSettings"> 
+  <object class="java.util.HashMap"> 
+   <void method="put"> 
+    <string>java.combinePaddingParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.combinePaddingParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeBlockComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeBlockComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.formatHeader</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.formatHeader</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>general.reportEveryFileAccess</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>general.reportEveryFileAccess</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.enumBraceStyle</string> 
+    <object class="jindent.settings.BracesSetting"> 
+     <string>java.enumBraceStyle</string> 
+     <void property="settingsMap"> 
+      <void method="get"> 
+       <string>indentCuddledBraces</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentAfterRightBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>cuddleEmptyBraces</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineAfterLeftBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentLeftBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineBeforeRightBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.minimumCommentIndent</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.minimumCommentIndent</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeCommas</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeCommas</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertParenthesesIntoConditions</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertParenthesesIntoConditions</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.noPaddingOfEmptyBrackets</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.noPaddingOfEmptyBrackets</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.labelNewLine</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.labelNewLine</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeCaseColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeCaseColons</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPublicClassJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPublicClassJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPrivateFieldJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPrivateFieldJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeIfParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeIfParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeTernaryHooks</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeTernaryHooks</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.enumConstantsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.enumConstantsSeparator</string> 
+     <void property="value"> 
+      <string>--- constant enums -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocMethodReturn</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocMethodReturn</string> 
+     <void property="value"> 
+      <string> *
+ * @return </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterLastImport</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterLastImport</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedThrows</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedThrows</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.setMethodsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.setMethodsSeparator</string> 
+     <void property="value"> 
+      <string>--- set methods -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertMethodsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertMethodsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertEnumConstantsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertEnumConstantsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertStaticFieldsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertStaticFieldsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSeparatorCommentsOnlyBetweenTypeDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSeparatorCommentsOnlyBetweenTypeDeclarations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPrivateInterfaceJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPrivateInterfaceJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.importNonJDKSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.importNonJDKSeparator</string> 
+     <void property="value"> 
+      <string>--- non-JDK imports -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deleteBlockComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.deleteBlockComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.maxNumOfAnnotationValuesInOneLine</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.maxNumOfAnnotationValuesInOneLine</string> 
+     <void property="value"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>ide.eclipse.formatEditorFileBeforeSave</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>ide.eclipse.formatEditorFileBeforeSave</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBetweenCaseBlocks</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBetweenCaseBlocks</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterHeader</string> 
+    <object class="jindent.settings.NZNumberSetting"> 
+     <string>java.blankLinesAfterHeader</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapParametersOfMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapParametersOfMethodConstructorDeclarations</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapBeforeRightParenthesisOfMethodConstructorCalls</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapBeforeRightParenthesisOfMethodConstructorCalls</string> 
+     <void property="index"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapFirstArgumentOfMethodConstructorCalls</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapFirstArgumentOfMethodConstructorCalls</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeCatchParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeCatchParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deleteSingleLineComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.deleteSingleLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.headerInsertionMode</string> 
+    <object class="jindent.settings.HeaderSetting"> 
+     <string>java.headerInsertionMode</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.separatorCommentFillCharacters</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.separatorCommentFillCharacters</string> 
+     <void property="value"> 
+      <string>-</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingConditionalOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingConditionalOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.formatEndOfLineComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.formatEndOfLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.maxNumOfMarkerAnnotationInOneLine</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.maxNumOfMarkerAnnotationInOneLine</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeForSemicolons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeForSemicolons</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertInterfacesSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertInterfacesSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>environment.conventionName</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>environment.conventionName</string> 
+     <void property="value"> 
+      <string>VisAD Convention</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.allowWrappingOfFirstArgumentOfNestedMethodCalls</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.allowWrappingOfFirstArgumentOfNestedMethodCalls</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentCommentsByTabs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.indentCommentsByTabs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapImplements</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapImplements</string> 
+     <void property="index"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeBangsAfterAndOr</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeBangsAfterAndOr</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeTernaryColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeTernaryColons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeEnumConstantsParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeEnumConstantsParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.footerTemplate</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.footerTemplate</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocInterfaceParamSeparator</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocInterfaceParamSeparator</string> 
+     <void property="value"> 
+      <string> *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.tryCatchBraceStyle</string> 
+    <object class="jindent.settings.BracesSetting"> 
+     <string>java.tryCatchBraceStyle</string> 
+     <void property="settingsMap"> 
+      <void method="get"> 
+       <string>indentAfterRightBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>noBlankLinesAfterLeftBrace</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineAfterLeftBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>rightBraceNewLine</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentLeftBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>doNotInsertBeforeSingleRightBrace</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineBeforeRightBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createProtectedAnnotationTypeJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createProtectedAnnotationTypeJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.separatorCommentLineLength</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.separatorCommentLineLength</string> 
+     <void property="value"> 
+      <int>79</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.footerDetectionExcludeKeys</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.footerDetectionExcludeKeys</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedRightParenthesisOfMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedRightParenthesisOfMethodConstructorDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapLines</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.wrapLines</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>environment.dateFormat</string> 
+    <object class="jindent.settings.DateFormatSetting"> 
+     <string>environment.dateFormat</string> 
+     <void property="value"> 
+      <string>EEE, MMM d, ''yy</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeJavaDocComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeJavaDocComments</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterFooter</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterFooter</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deepIndentForMultipleDeclarations</string> 
+    <object class="jindent.settings.AbsoluteRelativeINumberSetting"> 
+     <string>java.deepIndentForMultipleDeclarations</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+     <void property="value"> 
+      <int>20</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createFriendlyConstructorJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createFriendlyConstructorJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>general.formatterMessages</string> 
+    <object class="jindent.settings.MessageReportSetting"> 
+     <string>general.formatterMessages</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignExceedingAnnotationDefaults</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignExceedingAnnotationDefaults</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>general.parserMessages</string> 
+    <object class="jindent.settings.MessageReportSetting"> 
+     <string>general.parserMessages</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeStatementsContainingBlocks</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeStatementsContainingBlocks</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertImportJDKSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertImportJDKSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.formatTrailingComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.formatTrailingComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.jindentNotePosition</string> 
+    <object class="jindent.settings.PositionSetting"> 
+     <string>java.jindentNotePosition</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingWhileParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingWhileParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.noPaddingOfEmptyBracesInInitializers</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.noPaddingOfEmptyBracesInInitializers</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>ide.netbeans.formatEditorFileBeforeSave</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>ide.netbeans.formatEditorFileBeforeSave</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertClassesSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertClassesSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createProtectedInterfaceJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createProtectedInterfaceJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeFilteredCode</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeFilteredCode</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignAssignments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignAssignments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingEqualityOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingEqualityOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deleteTrailingComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.deleteTrailingComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeReturnParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeReturnParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingMultiplicativeOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingMultiplicativeOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingIfParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingIfParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPublicEnumJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPublicEnumJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesToSeparateStaticImportGroups</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesToSeparateStaticImportGroups</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapFirstParameterOfMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapFirstParameterOfMethodConstructorDeclarations</string> 
+     <void property="index"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createProtectedEnumJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createProtectedEnumJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.maxLineLength</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.maxLineLength</string> 
+     <void property="value"> 
+      <int>78</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocInterfaceBottom</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocInterfaceBottom</string> 
+     <void property="value"> 
+      <string> *
+ * @version        $version$, $date$
+ * @author         $author$    
+ */</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedFirstParameterOfMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedFirstParameterOfMethodConstructorDeclarations</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterMethods</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterMethods</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.classInterfaceBraceStyle</string> 
+    <object class="jindent.settings.BracesSetting"> 
+     <string>java.classInterfaceBraceStyle</string> 
+     <void property="settingsMap"> 
+      <void method="get"> 
+       <string>indentCuddledBraces</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentAfterRightBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>cuddleEmptyBraces</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineAfterLeftBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentLeftBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>doNotInsertBeforeSingleRightBrace</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineBeforeRightBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.headerDetectionIncludeKeys</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.headerDetectionIncludeKeys</string> 
+     <void property="value"> 
+      <string>// </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPrivateConstructorJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPrivateConstructorJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.formatBlockComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.formatBlockComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPublicInterfaceJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPublicInterfaceJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterEnumerations</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterEnumerations</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocConstructorBottom</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocConstructorBottom</string> 
+     <void property="value"> 
+      <string> */</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeBracesOfArrayInitializers</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeBracesOfArrayInitializers</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedExtends</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedExtends</string> 
+     <void property="value"> 
+      <int>4</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocConstructorParamSeparator</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocConstructorParamSeparator</string> 
+     <void property="value"> 
+      <string> *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapComparisonOperators</string> 
+    <object class="jindent.settings.BAWrappingSetting"> 
+     <string>java.wrapComparisonOperators</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.noSpacesBetweenEmptyForSemicolons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.noSpacesBetweenEmptyForSemicolons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesToSeparateJindentNote</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesToSeparateJindentNote</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alternativeIndent</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.alternativeIndent</string> 
+     <void property="value"> 
+      <int>8</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeSingleLineComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeSingleLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.headerTemplate</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.headerTemplate</string> 
+     <void property="value"> 
+      <string>//
+// $fileName$
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2008 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapBeforeLeftParenthesisOfMethodConstructorCalls</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapBeforeLeftParenthesisOfMethodConstructorCalls</string> 
+     <void property="index"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertMissingJavaDocTags</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertMissingJavaDocTags</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.outputEndOfLineFormat</string> 
+    <object class="jindent.settings.EndOfLineFormatSetting"> 
+     <string>java.outputEndOfLineFormat</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapArgumentsOfMethodConstructorCalls</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapArgumentsOfMethodConstructorCalls</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSetMethodsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSetMethodsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeEllipses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeEllipses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedImplements</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedImplements</string> 
+     <void property="value"> 
+      <int>4</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createProtectedClassJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createProtectedClassJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createFriendlyAnnotationTypeJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createFriendlyAnnotationTypeJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertGetMethodsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertGetMethodsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeBracketsInTypes</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeBracketsInTypes</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterAssertColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterAssertColons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.separateChunksByExceedingLines</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.separateChunksByExceedingLines</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeAssertColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeAssertColons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeAnnotationTypeMemberParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeAnnotationTypeMemberParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.staticFieldsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.staticFieldsSeparator</string> 
+     <void property="value"> 
+      <string>--- static fields -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.fieldsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.fieldsSeparator</string> 
+     <void property="value"> 
+      <string>--- fields -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.outputEncoding</string> 
+    <object class="jindent.settings.EncodingSetting"> 
+     <string>java.outputEncoding</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.emptyStatementsDeclarationsOnNewLine</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.emptyStatementsDeclarationsOnNewLine</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingAssignmentOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingAssignmentOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSeparatorCommentsInTypeDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSeparatorCommentsInTypeDeclarations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocInterfaceParam</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocInterfaceParam</string> 
+     <void property="value"> 
+      <string> * @param $paramName$</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocAnnotationType</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocAnnotationType</string> 
+     <void property="value"> 
+      <string>/**
+ * Annotation type description
+ *
+ */</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapBeforeRightParenthesisOfMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapBeforeRightParenthesisOfMethodConstructorDeclarations</string> 
+     <void property="index"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.importJDKSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.importJDKSeparator</string> 
+     <void property="value"> 
+      <string>--- JDK imports -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocInterfaceTop</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocInterfaceTop</string> 
+     <void property="value"> 
+      <string>/**
+ * Interface description
+ *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentCaseFromSwitch</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentCaseFromSwitch</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeMethodConstructorDeclarationParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeMethodConstructorDeclarationParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeFooter</string> 
+    <object class="jindent.settings.NZNumberSetting"> 
+     <string>java.blankLinesBeforeFooter</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createProtectedFieldJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createProtectedFieldJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.noPaddingOfEmptyParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.noPaddingOfEmptyParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>environment.systemVariables</string> 
+    <object class="jindent.settings.SystemVariablesSetting"> 
+     <string>environment.systemVariables</string> 
+     <void property="variablesMap"> 
+      <void method="get"> 
+       <string>user.timezone</string> 
+       <void property="value"> 
+        <string></string> 
+       </void> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPrivateEnumJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPrivateEnumJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.conventionNote</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.conventionNote</string> 
+     <void property="value"> 
+      <string> Formatted in $conventionName$ on $date$</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertBracesIntoDoWhileStatements</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertBracesIntoDoWhileStatements</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocClassParam</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocClassParam</string> 
+     <void property="value"> 
+      <string> * @param $paramName$</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSeparatorCommentsOnlyBetweenClassInterfaceEnumDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSeparatorCommentsOnlyBetweenClassInterfaceEnumDeclarations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocMethodBottom</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocMethodBottom</string> 
+     <void property="value"> 
+      <string> */</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterLabelColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterLabelColons</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterCastParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterCastParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.neverDeleteFirstColumnComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.neverDeleteFirstColumnComments</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSeparatorCommentsInImportDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSeparatorCommentsInImportDeclarations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeMethodConstructorCallParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeMethodConstructorCallParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.classesSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.classesSeparator</string> 
+     <void property="value"> 
+      <string>--- classes -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingBrackets</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingBrackets</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocConstructorExceptionSeparator</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocConstructorExceptionSeparator</string> 
+     <void property="value"> 
+      <string> *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedFirstArgumentOfMethodConstructorCalls</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedFirstArgumentOfMethodConstructorCalls</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.annotationOnNewLine</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.annotationOnNewLine</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.getMethodsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.getMethodsSeparator</string> 
+     <void property="value"> 
+      <string>--- get methods -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocConstructorParam</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocConstructorParam</string> 
+     <void property="value"> 
+      <string> * @param $paramName$ </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertInitializersSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertInitializersSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSeparatorCommentsInNestedClassInterfaceEnumDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSeparatorCommentsInNestedClassInterfaceEnumDeclarations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocClass</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocClass</string> 
+     <void property="value"> 
+      <string>/**
+ * Class $objectName$
+ *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterClasses</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterClasses</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapExtendsClasses</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapExtendsClasses</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.headerDetectionSmartMode</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.headerDetectionSmartMode</string> 
+     <void property="value"> 
+      <int>10</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocClassTop</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocClassTop</string> 
+     <void property="value"> 
+      <string>/**
+ * Class description
+ *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.formatFooter</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.formatFooter</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>ide.eclipse.formatEditorFileAfterOpen</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>ide.eclipse.formatEditorFileAfterOpen</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPublicMethodJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPublicMethodJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertStaticInitializersSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertStaticInitializersSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocInterface</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocInterface</string> 
+     <void property="value"> 
+      <string>/**
+ * $objectName$ 
+ *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocClassBottom</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocClassBottom</string> 
+     <void property="value"> 
+      <string> *
+ * @version        $version$, $date$
+ * @author         $author$    
+ */</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocMethodTop</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocMethodTop</string> 
+     <void property="value"> 
+      <string>/**
+ * </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapNumericalOperators</string> 
+    <object class="jindent.settings.BAWrappingSetting"> 
+     <string>java.wrapNumericalOperators</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeEndOfLineComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeEndOfLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignAnnotationDefaults</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignAnnotationDefaults</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeBranchingStatements</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeBranchingStatements</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPublicAnnotationTypeJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPublicAnnotationTypeJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignExceedingComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignExceedingComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.generalBraceStyle</string> 
+    <object class="jindent.settings.BracesSetting"> 
+     <string>java.generalBraceStyle</string> 
+     <void property="settingsMap"> 
+      <void method="get"> 
+       <string>indentCuddledBraces</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentAfterRightBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>cuddleEmptyBraces</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineAfterLeftBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>rightBraceNewLine</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentLeftBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineBeforeRightBrace</string> 
+       <void property="infinite"> 
+        <boolean>true</boolean> 
+       </void> 
+       <void property="value"> 
+        <int>2</int> 
+       </void> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentLeadingsByTabs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.indentLeadingsByTabs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>general.javaDocMessages</string> 
+    <object class="jindent.settings.MessageReportSetting"> 
+     <string>general.javaDocMessages</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPrivateAnnotationTypeJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPrivateAnnotationTypeJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignExceedingDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignExceedingDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.separateChunksByComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.separateChunksByComments</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPrivateMethodJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPrivateMethodJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterEndOfLineComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterEndOfLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterConstructors</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterConstructors</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingMethodConstructorCallParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingMethodConstructorCallParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapAfterMethodConstructorDeclarationModifiers</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapAfterMethodConstructorDeclarationModifiers</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedLeftParenthesisOfMethodConstructorCalls</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedLeftParenthesisOfMethodConstructorCalls</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentAssignmentsByTabs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.indentAssignmentsByTabs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeSingleMemberAnnotationParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeSingleMemberAnnotationParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterTernaryHooks</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterTernaryHooks</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createFriendlyFieldJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createFriendlyFieldJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingNormalAnnotationParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingNormalAnnotationParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterInstanceInitializers</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterInstanceInitializers</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeBangs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeBangs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeHeader</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeHeader</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertAnnotationsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertAnnotationsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterBlockComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterBlockComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.enumsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.enumsSeparator</string> 
+     <void property="value"> 
+      <string>--- enums -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.nestedInterfacesSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.nestedInterfacesSeparator</string> 
+     <void property="value"> 
+      <string>--- inner interfaces -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeLabelColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeLabelColons</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeSynchronizedParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeSynchronizedParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterStaticInitializers</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterStaticInitializers</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertFieldsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertFieldsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingCatchParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingCatchParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocMethodParam</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocMethodParam</string> 
+     <void property="value"> 
+      <string> * @param $paramName$ </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeLocalVariables</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeLocalVariables</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapBecauseOfComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.wrapBecauseOfComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterForSemicolons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterForSemicolons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.useCaseSensitiveNameSorting</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.useCaseSensitiveNameSorting</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeBrackets</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeBrackets</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterFilteredCode</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterFilteredCode</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterForColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterForColons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertEnumsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertEnumsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.constructorsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.constructorsSeparator</string> 
+     <void property="value"> 
+      <string>--- constructors -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterInterfaces</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterInterfaces</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertConstructorsSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertConstructorsSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.neverIndentAndFormatFirstColumnComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.neverIndentAndFormatFirstColumnComments</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.optimizeImports</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.optimizeImports</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deleteJavaDocComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.deleteJavaDocComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentAnnotationDefaultsByTabs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.indentAnnotationDefaultsByTabs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSeparatorCommentsInClassInterfaceEnumDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSeparatorCommentsInClassInterfaceEnumDeclarations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.sortClassInterfaceEnumDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.sortClassInterfaceEnumDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>version</string> 
+    <object class="jindent.settings.FloatSetting"> 
+     <string>version</string> 
+     <void property="value"> 
+      <float>4.3</float> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentDeclarationsByTabs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.indentDeclarationsByTabs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPrivateClassJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPrivateClassJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocMethodException</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocMethodException</string> 
+     <void property="value"> 
+      <string> * @throws $exceptionName$ </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertImportNonJDKSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertImportNonJDKSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapChainedMethodConstructorCalls</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.wrapChainedMethodConstructorCalls</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.staticInitializersSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.staticInitializersSeparator</string> 
+     <void property="value"> 
+      <string>--- static initializers -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignFirstTernaryExpression</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignFirstTernaryExpression</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeForParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeForParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeSemicolons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeSemicolons</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapConditionalOperators</string> 
+    <object class="jindent.settings.BAWrappingSetting"> 
+     <string>java.wrapConditionalOperators</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertBracesIntoWhileStatements</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertBracesIntoWhileStatements</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentSize</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentSize</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertBracesIntoForStatements</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertBracesIntoForStatements</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocConstructorTop</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocConstructorTop</string> 
+     <void property="value"> 
+      <string>/**
+ * </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.importDeclarationsSorter</string> 
+    <object class="jindent.settings.SorterSetting"> 
+     <string>java.importDeclarationsSorter</string> 
+     <void property="sorterRoot"> 
+      <object class="jindent.settings.sorter.SorterElement"> 
+       <string>Java</string> 
+       <int>2</int> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Imports</string> 
+         <int>19</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Package Membership</string> 
+           <int>31</int> 
+           <boolean>true</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-JDK Packages</string> 
+             <int>33</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>JDK Packages</string> 
+             <int>32</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Modifiers</string> 
+           <int>39</int> 
+           <boolean>true</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Static</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Static</string> 
+             <int>40</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+      </object> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBetweenDifferentStatements</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBetweenDifferentStatements</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.allowWrappingAfterAssignments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.allowWrappingAfterAssignments</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.typeDeclarationsSorter</string> 
+    <object class="jindent.settings.SorterSetting"> 
+     <string>java.typeDeclarationsSorter</string> 
+     <void property="sorterRoot"> 
+      <object class="jindent.settings.sorter.SorterElement"> 
+       <string>Java</string> 
+       <int>2</int> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Annotation Types</string> 
+         <int>13</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Enum Constants</string> 
+         <int>12</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Enums</string> 
+         <int>11</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Classes</string> 
+         <int>3</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Interfaces</string> 
+         <int>4</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+      </object> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPublicFieldJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPublicFieldJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesToSeparateConventionNote</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesToSeparateConventionNote</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingMethodConstructorDeclarationParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingMethodConstructorDeclarationParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeAssertParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeAssertParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapThrows</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapThrows</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertBracesIntoIfElseStatements</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertBracesIntoIfElseStatements</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterTernaryColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterTernaryColons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingExpressionParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingExpressionParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesToSeparateImportGroups</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesToSeparateImportGroups</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeThrowParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeThrowParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeWhileParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeWhileParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createPublicConstructorJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createPublicConstructorJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBetweenEnumElementsAndClassBody</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBetweenEnumElementsAndClassBody</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>environment.userVariables</string> 
+    <object class="jindent.settings.UserVariablesSetting"> 
+     <string>environment.userVariables</string> 
+     <void property="variablesMap"> 
+      <void method="put"> 
+       <string>author</string> 
+       <object class="jindent.settings.uservariables.UserVariable"> 
+        <string>author</string> 
+        <string>Enter your name here...</string> 
+       </object> 
+      </void> 
+      <void method="put"> 
+       <string>version</string> 
+       <object class="jindent.settings.uservariables.UserVariable"> 
+        <string>version</string> 
+        <string>Enter version here...</string> 
+       </object> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapThrowsExceptions</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapThrowsExceptions</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.allowWrappingAfterLeftParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.allowWrappingAfterLeftParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deepIndent</string> 
+    <object class="jindent.settings.AbsoluteRelativeINumberSetting"> 
+     <string>java.deepIndent</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+     <void property="value"> 
+      <int>45</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingSingleMemberAnnotationParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingSingleMemberAnnotationParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingBitwiseOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingBitwiseOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignExceedingAssignments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignExceedingAssignments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.footerDetectionIncludeKeys</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.footerDetectionIncludeKeys</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterPackage</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterPackage</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterBangs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterBangs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterFields</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterFields</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createFriendlyInterfaceJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createFriendlyInterfaceJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocMethodParamSeparator</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocMethodParamSeparator</string> 
+     <void property="value"> 
+      <string> *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.singleIfStatementInOneLine</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.singleIfStatementInOneLine</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentWrappedLeftParenthesisOfMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.indentWrappedLeftParenthesisOfMethodConstructorDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeSwitchParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeSwitchParentheses</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapExtends</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapExtends</string> 
+     <void property="index"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.sortExceptionsInTemplates</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.sortExceptionsInTemplates</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertSeparatorCommentsOnlyBetweenImportDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertSeparatorCommentsOnlyBetweenImportDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesBeforeFirstCaseBlock</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesBeforeFirstCaseBlock</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapAfterMethodDeclarationReturnTypes</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapAfterMethodDeclarationReturnTypes</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingEnumConstantsParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingEnumConstantsParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeNormalAnnotationParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeNormalAnnotationParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deleteEndOfLineComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.deleteEndOfLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocEnum</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocEnum</string> 
+     <void property="value"> 
+      <string>/**
+ * Enum description
+ *
+ */</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.footerInsertionMode</string> 
+    <object class="jindent.settings.FooterSetting"> 
+     <string>java.footerInsertionMode</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createFriendlyMethodJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createFriendlyMethodJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.maxEnumElementsInOneLine</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.maxEnumElementsInOneLine</string> 
+     <void property="value"> 
+      <int>3</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingRelationalOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingRelationalOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.importGroupingDepth</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.importGroupingDepth</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingAdditiveOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingAdditiveOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterBangsAfterAndOr</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterBangsAfterAndOr</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocMethodExceptionSeparator</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocMethodExceptionSeparator</string> 
+     <void property="value"> 
+      <string> *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.tabulatorSize</string> 
+    <object class="jindent.settings.NZNumberSetting"> 
+     <string>java.tabulatorSize</string> 
+     <void property="value"> 
+      <int>8</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.footerDetectionSmartMode</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.footerDetectionSmartMode</string> 
+     <void property="infinite"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.specialElseIfTreatment</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.specialElseIfTreatment</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapDeclarationInitializersToRightSide</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.wrapDeclarationInitializersToRightSide</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.headerDetectionExcludeKeys</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.headerDetectionExcludeKeys</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>general.logFile</string> 
+    <object class="jindent.settings.LogFileSetting"> 
+     <string>general.logFile</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.firstLevelIndent</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.firstLevelIndent</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.conventionNotePosition</string> 
+    <object class="jindent.settings.PositionSetting"> 
+     <string>java.conventionNotePosition</string> 
+     <void property="index"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertNestedClassesSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertNestedClassesSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterAnnotationTypes</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterAnnotationTypes</string> 
+     <void property="value"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.avoidConfusingIndentationsForMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.avoidConfusingIndentationsForMethodConstructorDeclarations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>general.extensionManager</string> 
+    <object class="jindent.settings.ExtensionManagerSetting"> 
+     <string>general.extensionManager</string> 
+     <void property="extensionsMap"> 
+      <void method="put"> 
+       <string>java</string> 
+       <object class="jindent.settings.extensionmanager.ExtensionAssignment"> 
+        <string>java</string> 
+        <string>Java Formatter</string> 
+       </object> 
+      </void> 
+      <void method="put"> 
+       <string>sqlj</string> 
+       <object class="jindent.settings.extensionmanager.ExtensionAssignment"> 
+        <string>sqlj</string> 
+        <string>Java/SQLJ Formatter</string> 
+       </object> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeForColons</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeForColons</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.keepBlankLines</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.keepBlankLines</string> 
+     <void property="infinite"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createFriendlyClassJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createFriendlyClassJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocField</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocField</string> 
+     <void property="value"> 
+      <string>/**           */</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createProtectedConstructorJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createProtectedConstructorJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.formatSingleLineComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.formatSingleLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocConstructorException</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocConstructorException</string> 
+     <void property="value"> 
+      <string> * @throws $exceptionName$ </string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.avoidConfusingIndentations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.avoidConfusingIndentations</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingBracesOfInitializers</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingBracesOfInitializers</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createProtectedMethodJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createProtectedMethodJavaDocs</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.initializersSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.initializersSeparator</string> 
+     <void property="value"> 
+      <string>--- initializers -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingSwitchParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingSwitchParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingSynchronizedParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingSynchronizedParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingShiftOperators</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingShiftOperators</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.singleElseStatementInOneLine</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.singleElseStatementInOneLine</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterJavaDocComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterJavaDocComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.sortImportDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.sortImportDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingCastParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingCastParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterStatementsContainingBlocks</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterStatementsContainingBlocks</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.alignTernaryExpressions</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.alignTernaryExpressions</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterLocalVariables</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterLocalVariables</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentLabels</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.indentLabels</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.annotationsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.annotationsSeparator</string> 
+     <void property="value"> 
+      <string>--- annotations -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.sortTypeDeclarations</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.sortTypeDeclarations</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.methodsSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.methodsSeparator</string> 
+     <void property="value"> 
+      <string>--- methods -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.inputTabulatorSize</string> 
+    <object class="jindent.settings.NZNumberSetting"> 
+     <string>java.inputTabulatorSize</string> 
+     <void property="value"> 
+      <int>8</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.methodConstructorBraceStyle</string> 
+    <object class="jindent.settings.BracesSetting"> 
+     <string>java.methodConstructorBraceStyle</string> 
+     <void property="settingsMap"> 
+      <void method="get"> 
+       <string>indentCuddledBraces</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentAfterRightBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>cuddleEmptyBraces</string> 
+       <void property="value"> 
+        <boolean>true</boolean> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineAfterLeftBrace</string> 
+       <void property="value"> 
+        <int>100</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>indentLeftBrace</string> 
+       <void property="value"> 
+        <int>1</int> 
+       </void> 
+      </void> 
+      <void method="get"> 
+       <string>minLinesToInsertBlankLineBeforeRightBrace</string> 
+       <void property="value"> 
+        <int>100</int> 
+       </void> 
+      </void> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.blankLinesAfterSingleLineComments</string> 
+    <object class="jindent.settings.NumberSetting"> 
+     <string>java.blankLinesAfterSingleLineComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.insertNestedInterfacesSeparator</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.insertNestedInterfacesSeparator</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.deleteObsoleteJavaDocTags</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.deleteObsoleteJavaDocTags</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterTildes</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterTildes</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.createFriendlyEnumJavaDocs</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.createFriendlyEnumJavaDocs</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapBeforeLeftParenthesisOfMethodConstructorDeclarations</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapBeforeLeftParenthesisOfMethodConstructorDeclarations</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.maxArrayElementsInOneLine</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.maxArrayElementsInOneLine</string> 
+     <void property="value"> 
+      <int>5</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceBeforeTildes</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceBeforeTildes</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingBracesOfGenerics</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingBracesOfGenerics</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.maxNumOfAnnotationAssignmentsInOneLine</string> 
+    <object class="jindent.settings.INumberSetting"> 
+     <string>java.maxNumOfAnnotationAssignmentsInOneLine</string> 
+     <void property="value"> 
+      <int>2</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.classInterfaceEnumDeclarationsSorter</string> 
+    <object class="jindent.settings.SorterSetting"> 
+     <string>java.classInterfaceEnumDeclarationsSorter</string> 
+     <void property="sorterRoot"> 
+      <object class="jindent.settings.sorter.SorterElement"> 
+       <string>Java</string> 
+       <int>2</int> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Static Fields</string> 
+         <int>8</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Initialized</string> 
+           <int>9</int> 
+           <boolean>true</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Initialized</string> 
+             <int>20</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Initialized</string> 
+             <int>21</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Transient Modifier</string> 
+           <int>53</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Transient</string> 
+             <int>54</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Transient</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Volatile Modifier</string> 
+           <int>55</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Volatile</string> 
+             <int>56</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Volatile</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Types</string> 
+           <int>30</int> 
+           <boolean>false</boolean> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Static Initializers</string> 
+         <int>10</int> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Fields</string> 
+         <int>7</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Initialized</string> 
+           <int>9</int> 
+           <boolean>true</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Initialized</string> 
+             <int>20</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Initialized</string> 
+             <int>21</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Transient Modifier</string> 
+           <int>53</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Transient</string> 
+             <int>54</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Transient</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Volatile Modifier</string> 
+           <int>55</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Volatile</string> 
+             <int>56</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Volatile</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Types</string> 
+           <int>30</int> 
+           <boolean>false</boolean> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Initializers</string> 
+         <int>9</int> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Enum Constants</string> 
+         <int>12</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Static Modifier</string> 
+           <int>39</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Static</string> 
+             <int>40</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Static</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Constructors</string> 
+         <int>6</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Parameter Count</string> 
+           <int>23</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Annotation Types</string> 
+         <int>52</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Static Modifier</string> 
+           <int>39</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Static</string> 
+             <int>40</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Static</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Enums</string> 
+         <int>11</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Static Modifier</string> 
+           <int>39</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Static</string> 
+             <int>40</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Static</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Methods</string> 
+         <int>5</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Method Type</string> 
+           <int>24</int> 
+           <boolean>true</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Ordinary Methods</string> 
+             <int>29</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Getters</string> 
+             <int>25</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Has Getters</string> 
+             <int>27</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Is Getters</string> 
+             <int>28</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Setters</string> 
+             <int>26</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Parameter Count</string> 
+           <int>23</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Static Modifier</string> 
+           <int>39</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Static</string> 
+             <int>40</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Static</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Synchronized Modifier</string> 
+           <int>49</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Synchronized</string> 
+             <int>50</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Synchronized</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Native Modifier</string> 
+           <int>45</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Native</string> 
+             <int>46</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Native</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Inner Interfaces</string> 
+         <int>4</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Static Modifier</string> 
+           <int>39</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Static</string> 
+             <int>40</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Static</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+       <void method="add"> 
+        <object class="jindent.settings.sorter.SorterElement"> 
+         <string>Inner Classes</string> 
+         <int>3</int> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Name</string> 
+           <int>22</int> 
+           <boolean>true</boolean> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Access Modifiers</string> 
+           <int>34</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Public</string> 
+             <int>35</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Friendly</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Protected</string> 
+             <int>37</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Private</string> 
+             <int>36</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Annotation Modifier</string> 
+           <int>51</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Annotated</string> 
+             <int>52</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Annotated</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Final Modifier</string> 
+           <int>41</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Final</string> 
+             <int>42</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Final</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Abstract Modifier</string> 
+           <int>43</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Abstract</string> 
+             <int>44</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Abstract</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by Static Modifier</string> 
+           <int>39</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Static</string> 
+             <int>40</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-Static</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+         <void method="add"> 
+          <object class="jindent.settings.sorter.OptionalSorterElement"> 
+           <string>Sort by StrictFP Modifier</string> 
+           <int>47</int> 
+           <boolean>false</boolean> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>StrictFP</string> 
+             <int>48</int> 
+            </object> 
+           </void> 
+           <void method="add"> 
+            <object class="jindent.settings.sorter.SorterElement"> 
+             <string>Non-StrictFP</string> 
+             <int>38</int> 
+            </object> 
+           </void> 
+          </object> 
+         </void> 
+        </object> 
+       </void> 
+      </object> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.interfacesSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.interfacesSeparator</string> 
+     <void property="value"> 
+      <string>--- interfaces -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.indentContentOfFirstColumnComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.indentContentOfFirstColumnComments</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.wrapImplementsInterfaces</string> 
+    <object class="jindent.settings.WrappingSetting"> 
+     <string>java.wrapImplementsInterfaces</string> 
+     <void property="index"> 
+      <int>1</int> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.javaDocClassParamSeparator</string> 
+    <object class="jindent.settings.StringArraySetting"> 
+     <string>java.javaDocClassParamSeparator</string> 
+     <void property="value"> 
+      <string> *</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.spaceAfterCommas</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.spaceAfterCommas</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.nestedClassesSeparator</string> 
+    <object class="jindent.settings.StringSetting"> 
+     <string>java.nestedClassesSeparator</string> 
+     <void property="value"> 
+      <string>--- inner classes -</string> 
+     </void> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.inputEncoding</string> 
+    <object class="jindent.settings.EncodingSetting"> 
+     <string>java.inputEncoding</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.paddingForParentheses</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.paddingForParentheses</string> 
+    </object> 
+   </void> 
+   <void method="put"> 
+    <string>java.formatJavaDocComments</string> 
+    <object class="jindent.settings.BooleanSetting"> 
+     <string>java.formatJavaDocComments</string> 
+     <void property="value"> 
+      <boolean>true</boolean> 
+     </void> 
+    </object> 
+   </void> 
+  </object> 
+ </object> 
+</java> 
diff --git a/visad/VisAD.Manifest b/visad/VisAD.Manifest
new file mode 100644
index 0000000..75a16b6
--- /dev/null
+++ b/visad/VisAD.Manifest
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: visad.ss.SpreadSheet
diff --git a/visad/VisADAppearance.java b/visad/VisADAppearance.java
new file mode 100644
index 0000000..248d419
--- /dev/null
+++ b/visad/VisADAppearance.java
@@ -0,0 +1,58 @@
+//
+// VisADAppearance.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Image;
+
+/**
+   VisADAppearance stands in for j3d.Switch
+   and is Serializable.<P>
+*/
+public class VisADAppearance extends VisADSceneGraphObject {
+
+  public VisADGeometryArray array = null;
+  public transient Image image = null;
+  public boolean color_flag = false;
+  public float red, green, blue;
+  public float alpha;
+  public float lineWidth = 1.0f;
+  public float pointSize = 1.0f;
+  public int lineStyle = GraphicsModeControl.SOLID_STYLE;
+
+  // Serializable substitutes for image
+  public int image_type = -1;
+  public int image_width = 0;
+  public int image_height = 0;
+  public int[] image_pixels = null;
+  public int texture_width = 0;
+  public int texture_height = 0;
+
+  public VisADAppearance() {
+  }
+
+}
+
diff --git a/visad/VisADError.java b/visad/VisADError.java
new file mode 100644
index 0000000..dd9ab9a
--- /dev/null
+++ b/visad/VisADError.java
@@ -0,0 +1,41 @@
+//
+// VisADError.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADError is the superclass of all errors defined within the
+   VisAD package.<P>
+*/
+public class VisADError extends Error {
+
+  public VisADError() { super(); }
+  public VisADError(String s) { super(s); }
+  public VisADError(Throwable t) { super(t); }
+  public VisADError(String s, Throwable t) { super(s, t); }
+
+}
+
diff --git a/visad/VisADEvent.java b/visad/VisADEvent.java
new file mode 100644
index 0000000..af1ec02
--- /dev/null
+++ b/visad/VisADEvent.java
@@ -0,0 +1,65 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.awt.Event;
+
+/**
+ * <CODE>VisADEvent</CODE> is the VisAD class for <CODE>Event</CODE>s
+ * passed between <CODE>Display</CODE>s.
+ */
+public class VisADEvent
+  extends Event
+{
+  public static final int LOCAL_SOURCE = 0;
+  public static final int UNKNOWN_REMOTE_SOURCE = -1;
+
+  /** non-zero if this event came from a remote source */
+  private int remoteId; 
+
+  public VisADEvent(Object target, int id, Object arg, int remoteId)
+  {
+    super(target, id, arg);
+    this.remoteId = remoteId;
+  }
+
+  /**
+   * Get the remote event source id.
+   *
+   * @return the remote id (or 0 if this was a local event)
+   */
+  public int getRemoteId()
+  {
+    return remoteId;
+  }
+
+  /**
+   * Get whether the event came from a remote source.
+   *
+   * @return true if remote, false if local
+   */
+  public boolean isRemote()
+  {
+    return remoteId != LOCAL_SOURCE;
+  }
+}
diff --git a/visad/VisADException.java b/visad/VisADException.java
new file mode 100644
index 0000000..ef9d40c
--- /dev/null
+++ b/visad/VisADException.java
@@ -0,0 +1,41 @@
+//
+// VisADException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADException is the superclass of all exceptions defined within the
+   VisAD package.<P>
+*/
+public class VisADException extends Exception {
+
+  public VisADException() { super(); }
+  public VisADException(String s) { super(s); }
+  public VisADException(String s, Throwable cause) { super(s, cause); }
+  public VisADException(Throwable cause) { super(cause); }
+
+}
+
diff --git a/visad/VisADGeometryArray.java b/visad/VisADGeometryArray.java
new file mode 100644
index 0000000..486f2aa
--- /dev/null
+++ b/visad/VisADGeometryArray.java
@@ -0,0 +1,574 @@
+//
+// VisADGeometryArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.*;
+
+/**
+   VisADGeometryArray stands in for j3d.GeometryArray
+   and is Serializable.<P>
+*/
+public abstract class VisADGeometryArray extends VisADSceneGraphObject
+       implements Cloneable {
+
+  public int vertexCount;
+  public int vertexFormat;
+  public float[] coordinates;
+  public float[] normals;
+  public byte[] colors;
+  public float[] texCoords;
+
+  // stuff for longitude
+  boolean any_longitude_rotate = false;
+  int longitude_axis = -1;
+  ScalarMap longitude_map = null;
+  CoordinateSystem longitude_cs = null;
+  float[][] longitude_coords = null;
+
+  public VisADGeometryArray() {
+    vertexCount = 0;
+    vertexFormat = 0;
+    coordinates = null;
+    normals = null;
+    colors = null;
+    texCoords = null;
+  }
+
+  /** eliminate any vectors or triangles crossing seams of
+      map projections, defined by display-side CoordinateSystems;
+      this default implementation does nothing */
+  public VisADGeometryArray adjustSeam(DataRenderer renderer)
+         throws VisADException {
+    CoordinateSystem coord_sys = renderer.getDisplayCoordinateSystem();
+    // WLH 13 March 2000
+    // if (coord_sys == null) return this;
+    if (coord_sys == null || coord_sys instanceof SphericalCoordinateSystem) {
+      return this;
+    }
+    return this;
+  }
+
+  /** like adjustLongitude, but rather than splitting vectors or
+      triangles, keep the VisADGeometryArray intact but possibly
+      move it in longitude (try to keep its centroid on the "main"
+      side of the seam) */
+  public VisADGeometryArray adjustLongitudeBulk(DataRenderer renderer)
+         throws VisADException {
+    float[] lons = getLongitudes(renderer, true); // bulk = true
+    return this;
+  }
+
+  /** split any vectors or triangles crossing crossing longitude
+      seams when Longitude is mapped to a Cartesian display axis;
+      default implementation: rotate if necessary, then return points */
+  public VisADGeometryArray adjustLongitude(DataRenderer renderer)
+         throws VisADException {
+    float[] lons = getLongitudes(renderer);
+    if (any_longitude_rotate) {
+      // some coordinates changed, so return VisADPointArray
+      VisADPointArray array = new VisADPointArray();
+      array.vertexCount = vertexCount;
+      array.coordinates = coordinates;
+      array.colors = colors;
+      return array;
+    }
+    else {
+      return this;
+    }
+  }
+
+  static float rotateOneLongitude(float lon, float base) {
+    if (lon == lon) {
+      float x = (lon - base) % 360.0f;
+      return (x + ((x < 0.0f) ? (360.0f + base) : base));
+    }
+    else {
+      return lon;
+    }
+  }
+
+  void rotateLongitudes(float[] lons, float base, boolean bulk)
+       throws VisADException {
+    boolean any = false;
+    // so rotate longitudes to base
+    if (bulk) {
+      float mean_lon = 0.0f;
+      int n = 0;
+      for (int i=0; i<vertexCount; i++) {
+        if (lons[i] == lons[i]) {
+          mean_lon += lons[i];
+          n++;
+        }
+      }
+      mean_lon = mean_lon / n;
+      float x = (mean_lon - base) % 360.0f;
+      x += (x < 0.0f) ? (360.0f + base) : base;
+      if (x != mean_lon) {
+        x = x - mean_lon;
+        any = true;
+      }
+      if (any) {
+        for (int i=0; i<vertexCount; i++) {
+          if (lons[i] == lons[i]) {
+            lons[i] += x;
+          }
+        }
+      }
+    }
+    else { // !bulk
+      for (int i=0; i<vertexCount; i++) {
+        if (lons[i] == lons[i]) {
+          float x = (lons[i] - base) % 360.0f;
+          x += (x < 0.0f) ? (360.0f + base) : base;
+          if (x != lons[i]) {
+            lons[i] = x;
+            any = true;
+          }
+        }
+      }
+    }
+    if (any) {
+      if (longitude_cs == null) {
+        float[] coords = longitude_map.scaleValues(lons);
+        for (int i=0; i<vertexCount; i++) {
+          coordinates[3 * i + longitude_axis] = coords[i];
+        }
+      }
+      else {
+        longitude_coords[longitude_axis] = longitude_map.scaleValues(lons);
+        float[][] coords = longitude_cs.toReference(longitude_coords);
+        int k = 0;
+        for (int i=0; i<vertexCount; i++) {
+          coordinates[k++] = coords[0][i];
+          coordinates[k++] = coords[1][i];
+          coordinates[k++] = coords[2][i];
+        }
+      }
+      any_longitude_rotate = true;
+    }
+  }
+
+  float[] getLongitudes(DataRenderer renderer)
+          throws VisADException {
+    return getLongitudes(renderer, false);
+  }
+
+  float[] getLongitudes(DataRenderer renderer, boolean bulk)
+          throws VisADException {
+    any_longitude_rotate = false;
+    longitude_map = null;
+    longitude_axis = -1;
+    longitude_cs = null;
+    longitude_coords = null;
+
+    // Vector mapVector = renderer.getDisplay().getMapVector();
+    // this is only approximately correct
+    DataDisplayLink[] links = renderer.getLinks();
+    Vector mapVector =
+      (links == null || links.length == 0) ? new Vector()
+                                           : links[0].getSelectedMapVector();
+
+    Enumeration maps = mapVector.elements();
+    while(maps.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      DisplayRealType dreal = map.getDisplayScalar();
+      DisplayTupleType tuple = dreal.getTuple();
+      if (!RealType.Longitude.equals(map.getScalar())) continue;
+      // getCircular() true for Latitude, Longitude, CylAzimuth, etc
+      if (dreal.getCircular()) return null; // do nothing!
+      if (tuple != null &&
+          (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+           (tuple.getCoordinateSystem() != null &&
+            tuple.getCoordinateSystem().getReference().equals(
+            Display.DisplaySpatialCartesianTuple)))) { // spatial
+
+// System.out.println("getLongitudes: found a map from Longitude to a spatial axis");
+        // have found a map from Longitude to a spatial DisplayRealType
+        // other than Longitude or CylAzimuth
+        double[] map_range = map.getRange();
+        float map_min = (float) map_range[0];
+        float map_max = (float) map_range[1];
+// System.out.println("map = " + map);
+// System.out.println("map_min = " + map_min + " map_max = " + map_max);
+
+        // leave some information for getLongitudeRange
+        longitude_map = map;
+        longitude_axis = dreal.getTupleIndex();
+        longitude_cs = tuple.getCoordinateSystem(); // may be null
+
+        float[] lons = null;
+        if (longitude_cs == null) {
+          lons = new float[vertexCount];
+          for (int i=0; i<vertexCount; i++) {
+            lons[i] = coordinates[3 * i + longitude_axis];
+          }
+        }
+        else {
+          float[][] coords = new float[3][vertexCount];
+          int k = 0;
+          for (int i=0; i<vertexCount; i++) {
+            coords[0][i] = coordinates[k++];
+            coords[1][i] = coordinates[k++];
+            coords[2][i] = coordinates[k++];
+          }
+          longitude_coords = longitude_cs.fromReference(coords);
+          lons = longitude_coords[longitude_axis];
+        }
+        lons = longitude_map.inverseScaleValues(lons);
+        // get range of Longitude values
+        float lon_min = Float.MAX_VALUE;
+        // float lon_max = Float.MIN_VALUE;
+        float lon_max = -Float.MAX_VALUE;
+        for (int i=0; i<vertexCount; i++) {
+          if (lons[i] == lons[i]) {
+// System.out.println("lons[" + i + "] = " + lons[i]);
+            if (lons[i] < lon_min) lon_min = lons[i];
+            if (lons[i] > lon_max) lon_max = lons[i];
+          }
+        }
+// System.out.println("lon_min = " + lon_min + " lon_max = " + lon_max);
+        if (lon_min == Float.MAX_VALUE) {
+          longitude_coords = null;
+          return lons;
+        }
+        boolean any_rotate = false;
+        if (map_min == map_min && map_max == map_max) {
+          float map_delta = 0.1f * (map_max - map_min);
+// System.out.println("map_delta = " + map_delta);
+          if ( ((map_min + map_delta) < lon_min &&
+                (map_max + map_delta) < lon_max) ||
+               (lon_min < (map_min - map_delta) &&
+                lon_max < (map_max - map_delta)) ) {
+
+            float new_lon_min = rotateOneLongitude(lon_min, map_min);
+            float new_lon_max = rotateOneLongitude(lon_max, map_min);
+            float dist_min =
+              (lon_min < map_min) ? (map_min - lon_min) :
+              (map_max < lon_min) ? (lon_min - map_max) : 0.0f;
+            float new_dist_min =
+              (new_lon_min < map_min) ? (map_min - new_lon_min) :
+              (map_max < new_lon_min) ? (new_lon_min - map_max) : 0.0f;
+            float dist_max =
+              (lon_max < map_min) ? (map_min - lon_max) :
+              (map_max < lon_max) ? (lon_max - map_max) : 0.0f;
+            float new_dist_max =
+              (new_lon_max < map_min) ? (map_min - new_lon_max) :
+              (map_max < new_lon_max) ? (new_lon_max - map_max) : 0.0f;
+            if ((new_dist_min + new_dist_max) < (dist_min + dist_max)) {
+
+              // actual longitudes are shifted significantly from map,
+              // so rotate longitudes to base at map_min
+// System.out.println("rotateLongitudes to map_min " + map_min);
+              any_rotate = true;
+              rotateLongitudes(lons, map_min, bulk);
+            }
+          }
+        }
+        if (!any_rotate && (lon_min + 360.0f) < lon_max) {
+// System.out.println("rotateLongitudes to lon_min " + lon_min);
+          rotateLongitudes(lons, lon_min, bulk);
+        }
+/*
+for (int i=0; i<vertexCount; i++) {
+  System.out.println("return lons[" + i + "] = " + lons[i]);
+}
+*/
+        longitude_coords = null;
+        return lons;
+      } // end if (tuple != null && ...
+    } // end while(maps.hasMoreElements())
+
+    int[] indices = renderer.getLatLonIndices();
+    if (indices[0] < 0 || indices[1] < 0) return null;
+    float[][] locs = new float[3][vertexCount];
+    int k = 0;
+    for (int i=0; i<vertexCount; i++) {
+      locs[0][i] = coordinates[k++];
+      locs[1][i] = coordinates[k++];
+      locs[2][i] = coordinates[k++];
+    }
+    float[][] latlons = renderer.earthToSpatial(locs, null);
+    longitude_coords = null;
+    return latlons[1];
+  }
+
+  // always called after getLongitudes()
+  float[] getLongitudeRange(float[] lons, int[] axis,
+                            float[] coords) {
+    float[] lon_range = {Float.NaN, Float.NaN};
+    axis[0] = -1;
+    coords[0] = Float.NaN;
+    coords[1] = Float.NaN;
+    float lon_min = Float.MAX_VALUE;
+    // float lon_max = Float.MIN_VALUE;
+    float lon_max = -Float.MAX_VALUE;
+    for (int i=0; i<vertexCount; i++) {
+      if (lons[i] == lons[i]) {
+        if (lons[i] < lon_min) lon_min = lons[i];
+        if (lons[i] > lon_max) lon_max = lons[i];
+      }
+    }
+    // WLH 30 Dec 99
+    if ((lon_max - lon_min) < 1.0f) {
+      lon_max += 0.5f;
+      lon_min -= 0.5f;
+    }
+    if (lon_min <= lon_max) {
+/* WLH 30 Dec 99
+      float delta = 1.0f; // allow a little slop in Longitudes
+*/
+      float delta = (lon_max - lon_min) / 10.0f; // allow a little slop in Longitudes
+      if (delta > 1.0f) delta = 1.0f;
+
+      float x = (lon_min + delta) % 180.0f;
+      if (x < 0.0f) x += 180.0f;
+      float y = (lon_min + delta) - x;
+      if ((lon_max - delta) < y + 360.0f) {
+        lon_range[0] = y;
+        lon_range[1] = y + 360.0f;
+      }
+      else {
+        lon_range[0] = lon_min;
+        lon_range[1] = lon_min + 360.0f;
+      }
+      if (longitude_map != null && longitude_cs == null) {
+        float[] xcoords = longitude_map.scaleValues(lon_range);
+        coords[0] = xcoords[0];
+        coords[1] = xcoords[1];
+        axis[0] = longitude_axis;
+      }
+      else {
+        coords[0] = Float.NaN;
+        coords[1] = Float.NaN;
+        axis[0] = -1;
+      }
+    }
+    return lon_range;
+  }
+
+  public VisADGeometryArray removeMissing() {
+    VisADPointArray array = new VisADPointArray();
+    float[] coords = new float[coordinates.length];
+    int color_length = 3;
+    byte[] cols = null;
+    if (colors != null) {
+      cols = new byte[colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    int k = 0;
+    int m = 0;
+    int j = 0;
+    boolean any_missing = false;
+    for (int i=0; i<coordinates.length; i+=3) {
+      if (coordinates[i] == coordinates[i] &&
+          coordinates[i+1] == coordinates[i+1] &&
+          coordinates[i+2] == coordinates[i+2]) {
+        coords[k] = coordinates[i];
+        coords[k+1] = coordinates[i+1];
+        coords[k+2] = coordinates[i+2];
+        if (colors != null) {
+          cols[m] = colors[j];
+          cols[m+1] = colors[j+1];
+          cols[m+2] = colors[j+2];
+          m += 3;
+          if (color_length == 4) {
+            cols[m++] = colors[j+3];
+          }
+        }
+        k += 3;
+      }
+      else { // missing coordinates values
+        any_missing = true;
+      }
+      j += color_length;
+    }
+    if (!any_missing) {
+      return this;
+    }
+    else {
+      array.coordinates = new float[k];
+      System.arraycopy(coords, 0, array.coordinates, 0, k);
+      if (colors != null) {
+        array.colors = new byte[m];
+        System.arraycopy(cols, 0, array.colors, 0, m);
+      }
+      return array;
+    }
+  }
+
+  static void merge(VisADGeometryArray[] arrays, VisADGeometryArray array)
+         throws VisADException {
+    if (arrays == null || arrays.length == 0 || array == null) return;
+    int n = arrays.length;
+    int count = 0;
+    boolean color_flag = false;
+    boolean normal_flag = false;
+    boolean texCoord_flag = false;
+    boolean any = false;
+    int vf = 0;
+
+    for (int i=0; i<n; i++) {
+      if (arrays[i] != null) {
+        color_flag = (arrays[i].colors != null);
+        normal_flag = (arrays[i].normals != null);
+        texCoord_flag = (arrays[i].texCoords != null);
+        vf = arrays[i].vertexFormat;
+        any = true;
+      }
+    }
+    if (!any) return;
+
+    int color_length = -1;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] == null) continue;
+      count += arrays[i].vertexCount;
+      if (color_flag != (arrays[i].colors != null) ||
+          normal_flag != (arrays[i].normals != null) ||
+          texCoord_flag != (arrays[i].texCoords != null)) {
+        throw new DisplayException("VisADGeometryArray.merge: formats don't match");
+      }
+      // WLH 4 Feb 2004 - fix for kevin.manross3.txt
+      if (color_length < 0 && arrays[i].colors != null &&
+          arrays[i].coordinates != null) {
+        int c1 = arrays[i].colors.length;
+        int c2 = arrays[i].coordinates.length;
+        color_length = (c1 == c2) ? 3 : 4;
+      }
+    }
+    if (color_length < 0) color_length = 3;
+    float[] coordinates = new float[3 * count];
+    byte[] colors = null;
+    float[] normals = null;
+    float[] texCoords = null;
+    if (color_flag) {
+      colors = new byte[color_length * count];
+    }
+    if (normal_flag) {
+      normals = new float[3 * count];
+    }
+    if (texCoord_flag) {
+      texCoords = new float[3 * count];
+    }
+    int k = 0;
+    int kc = 0;
+    int kn = 0;
+    int kt = 0;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] == null) continue;
+      float[] c = arrays[i].coordinates;
+      for (int j=0; j<3*arrays[i].vertexCount; j++) {
+        coordinates[k++] = c[j];
+      }
+      if (color_flag) {
+        byte[] b = arrays[i].colors;
+        // WLH 4 Feb 2004 - fix for kevin.manross3.txt
+        for (int j=0; j<b.length; j++) {
+          colors[kc++] = b[j];
+        }
+      }
+      if (normal_flag) {
+        c = arrays[i].normals;
+        for (int j=0; j<3*arrays[i].vertexCount; j++) {
+          normals[kn++] = c[j];
+        }
+      }
+      if (texCoord_flag) {
+        c = arrays[i].texCoords;
+        for (int j=0; j<3*arrays[i].vertexCount; j++) {
+          texCoords[kt++] = c[j];
+        }
+      }
+    }
+    array.vertexCount = count;
+    array.coordinates = coordinates;
+    array.colors = colors;
+    array.normals = normals;
+    array.texCoords = texCoords;
+    array.vertexFormat = vf;
+    return;
+  }
+
+  public String toString() {
+    String string = "GeometryArray, vertexCount = " + vertexCount +
+                    " vertexFormat = " + vertexFormat;
+    if (coordinates != null) {
+      string = string + "\n coordinates = " + floatArrayString(coordinates);
+    }
+    if (colors != null) {
+      string = string + "\n colors = " + byteArrayString(colors);
+    }
+    if (normals != null) {
+      string = string + "\n normals = " + floatArrayString(normals);
+    }
+    if (texCoords != null) {
+      string = string + "\n texCoords = " + floatArrayString(texCoords);
+    }
+
+    return string;
+  }
+
+  static String floatArrayString(float[] value) {
+    String string = "";
+    for (int i=0; i<value.length; i++) string = string + " " + value[i];
+    return string;
+  }
+
+  static String byteArrayString(byte[] value) {
+    String string = "";
+    for (int i=0; i<value.length; i++) string = string + " " + value[i];
+    return string;
+  }
+
+  public void copy(VisADGeometryArray array) {
+    array.vertexCount = vertexCount;
+    array.vertexFormat = vertexFormat;
+    if (coordinates != null) {
+      array.coordinates = new float[coordinates.length];
+      System.arraycopy(coordinates, 0, array.coordinates, 0,
+                       coordinates.length);
+    }
+    if (normals != null) {
+      array.normals = new float[normals.length];
+      System.arraycopy(normals, 0, array.normals, 0,
+                       normals.length);
+    }
+    if (colors != null) {
+      array.colors = new byte[colors.length];
+      System.arraycopy(colors, 0, array.colors, 0,
+                       colors.length);
+    }
+    if (texCoords != null) {
+      array.texCoords = new float[texCoords.length];
+      System.arraycopy(texCoords, 0, array.texCoords, 0,
+                       texCoords.length);
+    }
+  }
+
+  public abstract Object clone();
+
+}
+
diff --git a/visad/VisADGroup.java b/visad/VisADGroup.java
new file mode 100644
index 0000000..19d3d37
--- /dev/null
+++ b/visad/VisADGroup.java
@@ -0,0 +1,121 @@
+//
+// VisADGroup.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+import java.util.Vector;
+
+/**
+   VisADGroup stands in for j3d.Group
+   and is Serializable.<P>
+*/
+public class VisADGroup extends VisADSceneGraphObject {
+
+  private Vector children = new Vector();
+
+  public VisADGroup() {
+  }
+
+  public synchronized void addChild(VisADSceneGraphObject child)
+         throws DisplayException {
+    if (child == null) return;
+    synchronized(child) {
+      if (child.parent != null) {
+        throw new DisplayException("VisADGroup.addChild: already has parent");
+      }
+      children.addElement(child);
+      child.parent = this;
+    }
+  }
+
+  public synchronized int numChildren() {
+    return children.size();
+  }
+
+  public synchronized VisADSceneGraphObject getChild(int index) {
+    return (VisADSceneGraphObject) children.elementAt(index);
+  }
+
+  public synchronized void setChild(VisADSceneGraphObject child, int index)
+         throws DisplayException {
+    if (child == null) return;
+    synchronized(child) {
+      if (child.parent != null) {
+        throw new DisplayException("VisADGroup.setChild: already has parent");
+      }
+      VisADSceneGraphObject c = null;
+      if (children.size() > index) {
+        c = (VisADSceneGraphObject) children.elementAt(index);
+        if (c != null) {
+          synchronized(c) {
+            // nested VisADSceneGraphObject synchronized cannot
+            // deadlock: parent-less first, with-parent second
+            c.parent = null;
+            children.setElementAt(child, index);
+          }
+        }
+        children.setElementAt(child, index);
+      }
+      else {
+        children.addElement(child);
+      }
+      child.parent = this;
+    }
+  }
+
+  public synchronized void removeChild(int index) {
+    VisADSceneGraphObject c =
+      (VisADSceneGraphObject) children.elementAt(index);
+    if (c != null) {
+      synchronized(c) {
+        c.parent = null;
+        children.removeElementAt(index);
+      }
+    }
+  }
+
+  void detachChild(VisADSceneGraphObject child) {
+    children.removeElement(child);
+  }
+
+  public synchronized Vector getChildren() {
+    return (Vector) children.clone();
+  }
+
+  public String toString() {
+    String s = getClass().getName() + " ";
+    synchronized (children) {
+      int n = children.size();
+      s = s + n + " ";
+      for (int i=0; i<n; i++) {
+        s = s + children.elementAt(i).toString();
+      }
+    }
+    return s;
+  }
+
+}
+
diff --git a/visad/VisADIndexedTriangleStripArray.java b/visad/VisADIndexedTriangleStripArray.java
new file mode 100644
index 0000000..cfd2523
--- /dev/null
+++ b/visad/VisADIndexedTriangleStripArray.java
@@ -0,0 +1,733 @@
+//
+// VisADIndexedTriangleStripArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADIndexedTriangleStripArray stands in for
+   j3d.IndexedTriangleStripArray and is Serializable.<P>
+*/
+public class VisADIndexedTriangleStripArray extends VisADGeometryArray {
+  public int indexCount; // should = indices.length
+  public int[] indices;
+  public int[] stripVertexCounts;
+
+  public static VisADIndexedTriangleStripArray
+                merge(VisADIndexedTriangleStripArray[] arrays)
+         throws VisADException {
+    if (arrays == null || arrays.length == 0) return null;
+    VisADIndexedTriangleStripArray array =
+      new VisADIndexedTriangleStripArray();
+    merge(arrays, array);
+    int count = 0;
+    int nind = 0;
+    int nstrips = 0;
+    int n = arrays.length;
+
+    // WLH 1 May 99
+    int[] start = new int[n];
+
+    start[0] = 0;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] != null) {
+        count += arrays[i].indexCount;
+        nind += arrays[i].indices.length;
+        nstrips += arrays[i].stripVertexCounts.length;
+
+        // WLH 1 May 99
+        if (i > 0) start[i] = start[i-1] + arrays[i-1].vertexCount;
+
+      }
+    }
+    if (nstrips <= 0) return null;
+    int[] indices = new int[nind];
+    int[] stripVertexCounts = new int[nstrips];
+    nind = 0;
+    nstrips = 0;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] != null) {
+        int incind = arrays[i].indices.length;
+        int incnstrips = arrays[i].stripVertexCounts.length;
+        for (int j=0; j<incind; j++) {
+
+          // WLH 1 May 99
+          // indices[nind + j] = arrays[i].indices[j];
+          indices[nind + j] = arrays[i].indices[j] + start[i];
+
+        }
+        for (int j=0; j<incnstrips; j++) {
+          stripVertexCounts[nstrips + j] = arrays[i].stripVertexCounts[j];
+        }
+        nind += incind;
+        nstrips += incnstrips;
+      }
+    }
+    array.indexCount = count;
+    array.indices = indices;
+    array.stripVertexCounts = stripVertexCounts;
+    return array;
+  }
+
+  private static final int MUL = 6;
+
+  public VisADGeometryArray adjustLongitude(DataRenderer renderer)
+         throws VisADException {
+    float[] lons = getLongitudes(renderer);
+    if (lons == null) return this;
+    int[] axis = new int[1];
+    float[] lon_coords = new float[2];
+    float[] lon_range = getLongitudeRange(lons, axis, lon_coords);
+    if (lon_range[0] != lon_range[0] ||
+        lon_range[1] != lon_range[1]) return this;
+    float bottom = lon_range[0];
+    float top = lon_range[1];
+    float low = bottom + 30.0f;
+    float hi = top - 30.0f;
+    int lon_axis = axis[0];
+    float coord_bottom = lon_coords[0];
+    float coord_top = lon_coords[1];
+
+    // midpoint from this (if this_split)
+    int mid_first = -1;
+    int mid_second = -1;
+
+    // save midpoint from last (if last_split)
+    int last_mid_first = -1;
+    int last_mid_second = -1;
+
+    // midpoint between -3 and +3 (if this_split^last_split)
+    int sillymid_first = -1;
+    int sillymid_second = -1;
+
+    // most recent point
+    int last = -1;
+
+    // point before most recent
+    int early = -1;
+
+    // this point
+    int point = -1;
+
+
+    VisADIndexedTriangleStripArray array = new VisADIndexedTriangleStripArray();
+    // worst case makes 3 times as many triangles
+    float[] coords = new float[MUL * coordinates.length];
+    System.arraycopy(coordinates, 0, coords, 0, coordinates.length);
+    float[] nos = null;
+    if (normals != null) {
+      nos = new float[MUL * normals.length];
+      System.arraycopy(normals, 0, nos, 0, normals.length);
+    }
+    int color_length = 0;
+    byte[] cols = null;
+    if (colors != null) {
+      color_length = 3;
+      cols = new byte[MUL * colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+      System.arraycopy(colors, 0, cols, 0, colors.length);
+    }
+    float[] texs = null;
+    if (texCoords != null) {
+      texs = new float[MUL * texCoords.length];
+      System.arraycopy(texCoords, 0, texs, 0, texCoords.length);
+    }
+    int coord_index = coordinates.length / 3; // index to add next point
+    // worst case makes 3 times as many indices
+    int[] inds = new int[MUL * indices.length];
+    int ind_index = 0; // index to add next indices entry
+    // worst case makes as many strips as there were points
+    int[] svcs = new int[coordinates.length];
+    int svc_index = 0;
+    int last_i = 0; // start i for each vertex strip
+
+    // int m = 0;  replaced by: ind_index
+    boolean any_split = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      boolean this_split = false;
+      int accum = 0; // strip counter
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]-1; i++) {
+        boolean last_split = this_split; // true if last edge was split
+        if (last_split) {
+          last_mid_first = mid_first;
+          last_mid_second = mid_second;
+        }
+        if ((lons[indices[i]] < low && hi < lons[indices[i+1]]) ||
+            (lons[indices[i+1]] < low && hi < lons[indices[i]])) {
+          this_split = true;
+          any_split = true;
+          if (lon_axis < 0) {
+            // not enough info to interpolate, so treat split as a break
+            if (accum >= 3) {
+              svcs[svc_index] = accum;
+              svc_index++;
+            }
+            last = indices[i+1];
+            accum = 1; // reset strip counter;
+            continue;
+          }
+          // lon_axis >= 0
+          // split line by interpolation
+          float a, b;
+          float coord_first, coord_second;
+          if (lons[indices[i]] < low) {
+            a = lons[indices[i]] - bottom;
+            b = top - lons[indices[i+1]];
+            coord_first = coord_bottom;
+            coord_second = coord_top;
+          }
+          else {
+            a = top - lons[indices[i]];
+            b = lons[indices[i+1]] - bottom;
+            coord_first = coord_top;
+            coord_second = coord_bottom;
+          }
+          float alpha = b / (a + b);
+          alpha = (alpha != alpha || alpha < 0.0f) ? 0.0f :
+                    ((1.0f < alpha) ? 1.0f : alpha);
+          float beta = 1.0f - alpha;
+
+          mid_first = coord_index++;
+          mid_second = coord_index++;
+          int f3 = 3 * mid_first;
+          int s3 = 3 * mid_second;
+          int i3 = 3 * indices[i];
+          int ip3 = 3 * indices[i+1];
+          coords[f3] = alpha * coordinates[i3] + beta * coordinates[ip3];
+          coords[f3+1] = alpha * coordinates[i3+1] + beta * coordinates[ip3+1];
+          coords[f3+2] = alpha * coordinates[i3+2] + beta * coordinates[ip3+2];
+          coords[s3] = coords[f3];
+          coords[s3+1] = coords[f3+1];
+          coords[s3+2] = coords[f3+2];
+          coords[f3+lon_axis] = coord_first;
+          coords[s3+lon_axis] = coord_second;
+          if (normals != null) {
+            nos[f3] = alpha * normals[i3] + beta * normals[ip3];
+            nos[f3+1] = alpha * normals[i3+1] + beta * normals[ip3+1];
+            nos[f3+2] = alpha * normals[i3+2] + beta * normals[ip3+2];
+            nos[s3] = nos[f3];
+            nos[s3+1] = nos[f3+1];
+            nos[s3+2] = nos[f3+2];
+          }
+          if (color_length == 3) {
+            cols[f3] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[i3]) +
+              beta * ShadowType.byteToFloat(colors[ip3]));
+            cols[f3+1] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[i3+1]) +
+              beta * ShadowType.byteToFloat(colors[ip3+1]));
+            cols[f3+2] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[i3+2]) +
+              beta * ShadowType.byteToFloat(colors[ip3+2]));
+            cols[s3] = cols[f3];
+            cols[s3+1] = cols[f3+1];
+            cols[s3+2] = cols[f3+2];
+          }
+          else if (color_length == 4) {
+            int f4 = 4 * mid_first;
+            int s4 = 4 * mid_second;
+            int i4 = 4 * indices[i];
+            int ip4 = 4 * indices[i+1];
+            cols[f4] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[i4]) +
+              beta * ShadowType.byteToFloat(colors[ip4]));
+            cols[f4+1] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[i4+1]) +
+              beta * ShadowType.byteToFloat(colors[ip4+1]));
+            cols[f4+2] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[i4+2]) +
+              beta * ShadowType.byteToFloat(colors[ip4+2]));
+            cols[f4+3] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[i4+3]) +
+              beta * ShadowType.byteToFloat(colors[ip4+3]));
+            cols[s4] = cols[f4];
+            cols[s4+1] = cols[f4+1];
+            cols[s4+2] = cols[f4+2];
+            cols[s4+3] = cols[f4+3];
+          }
+          if (texCoords != null) {
+            int f2 = 2 * mid_first;
+            int s2 = 2 * mid_second;
+            int i2 = 2 * indices[i];
+            int ip2 = 2 * indices[i+1];
+            texs[f2] = alpha * texCoords[i2] + beta * texCoords[ip2];
+            texs[f2+1] = alpha * texCoords[i2+1] + beta * texCoords[ip2+1];
+            texs[s2] = cols[f2];
+            texs[s2+1] = cols[f2+1];
+          }
+        }
+        else { // no split
+          this_split = false;
+        }
+
+        if (accum > 0 && this_split != last_split && lon_axis >= 0) {
+          // need to compute mid edge from -3 to +3
+
+          // split line by interpolation
+          float a, b;
+          float coord_first, coord_second;
+          if (lons[indices[i-1]] < low) {
+            a = lons[indices[i-1]] - bottom;
+            b = top - lons[indices[i+1]];
+            coord_first = coord_bottom;
+            coord_second = coord_top;
+          }
+          else {
+            a = top - lons[indices[i-1]];
+            b = lons[indices[i+1]] - bottom;
+            coord_first = coord_top;
+            coord_second = coord_bottom;
+          }
+          float alpha = b / (a + b);
+          alpha = (alpha != alpha || alpha < 0.0f) ? 0.0f :
+                    ((1.0f < alpha) ? 1.0f : alpha);
+          float beta = 1.0f - alpha;
+
+          sillymid_first = coord_index++;
+          sillymid_second = coord_index++;
+          int f3 = 3 * sillymid_first;
+          int s3 = 3 * sillymid_second;
+          int im3 = 3 * indices[i-1];
+          int ip3 = 3 * indices[i+1];
+          coords[f3] = alpha * coordinates[im3] + beta * coordinates[ip3];
+          coords[f3+1] = alpha * coordinates[im3+1] + beta * coordinates[ip3+1];
+          coords[f3+2] = alpha * coordinates[im3+2] + beta * coordinates[ip3+2];
+          coords[s3] = coords[f3];
+          coords[s3+1] = coords[f3+1];
+          coords[s3+2] = coords[f3+2];
+          coords[f3+lon_axis] = coord_first;
+          coords[s3+lon_axis] = coord_second;
+          if (normals != null) {
+            nos[f3] = alpha * normals[im3] + beta * normals[ip3];
+            nos[f3+1] = alpha * normals[im3+1] + beta * normals[ip3+1];
+            nos[f3+2] = alpha * normals[im3+2] + beta * normals[ip3+2];
+            nos[s3] = nos[f3];
+            nos[s3+1] = nos[f3+1];
+            nos[s3+2] = nos[f3+2];
+          }
+          if (color_length == 3) {
+            cols[f3] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[im3]) +
+              beta * ShadowType.byteToFloat(colors[ip3]));
+            cols[f3+1] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[im3+1]) +
+              beta * ShadowType.byteToFloat(colors[ip3+1]));
+            cols[f3+2] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[im3+2]) +
+              beta * ShadowType.byteToFloat(colors[ip3+2]));
+            cols[s3] = cols[f3];
+            cols[s3+1] = cols[f3+1];
+            cols[s3+2] = cols[f3+2];
+          }
+          else if (color_length == 4) {
+            int f4 = 4 * mid_first;
+            int s4 = 4 * mid_second;
+            int im4 = 4 * indices[i-1];
+            int ip4 = 4 * indices[i+1];
+            cols[f4] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[im4]) +
+              beta * ShadowType.byteToFloat(colors[ip4]));
+            cols[f4+1] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[im4+1]) +
+              beta * ShadowType.byteToFloat(colors[ip4+1]));
+            cols[f4+2] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[im4+2]) +
+              beta * ShadowType.byteToFloat(colors[ip4+2]));
+            cols[f4+3] = ShadowType.floatToByte(
+              alpha * ShadowType.byteToFloat(colors[im4+3]) +
+              beta * ShadowType.byteToFloat(colors[ip4+3]));
+            cols[s4] = cols[f4];
+            cols[s4+1] = cols[f4+1];
+            cols[s4+2] = cols[f4+2];
+            cols[s4+3] = cols[f4+3];
+          }
+          if (texCoords != null) {
+            int f2 = 2 * mid_first;
+            int s2 = 2 * mid_second;
+            int im2 = 2 * indices[i-1];
+            int ip2 = 2 * indices[i+1];
+            texs[f2] = alpha * texCoords[im2] + beta * texCoords[ip2];
+            texs[f2+1] = alpha * texCoords[im2+1] + beta * texCoords[ip2+1];
+            texs[s2] = cols[f2];
+            texs[s2+1] = cols[f2+1];
+          }
+        }
+
+        if (this_split) {
+          if (accum == 0) {
+            early = mid_second;
+            last = indices[i+1];
+            accum = 2;
+            continue; // don't make any triangles yet, for accum = 0
+          } // end if (accum == 0)
+          else if (last_split) { // && this_split
+            point = mid_first;
+            accum++;
+            if (accum == 3) {
+              inds[ind_index++] = early;
+              inds[ind_index++] = last;
+              inds[ind_index++] = point;
+            }
+            else {
+              inds[ind_index++] = point;
+            }
+            // bind off
+            svcs[svc_index] = accum;
+            svc_index++;
+            // accum = 0, but more to come
+
+            // create a 2-triangle strip
+            //   (last_mid_first, i-3, mid_second, i+3)
+            early = last_mid_first;
+            last = indices[i-1];
+            point = mid_second;
+            accum = 3;
+            inds[ind_index++] = early;
+            inds[ind_index++] = last;
+            inds[ind_index++] = point;
+
+            point = indices[i+1];
+            accum++;
+            inds[ind_index++] = point;
+          }
+          else { // !last_split && accum > 0 && this_split
+            // add (mid_first, i-3, sillymid_first)
+            point = mid_first;
+            accum++;
+            if (accum == 2) {
+              early = last;
+              last = point;
+            }
+            else if (accum == 3) {
+              inds[ind_index++] = early;
+              inds[ind_index++] = last;
+              inds[ind_index++] = point;
+            }
+            else {
+              inds[ind_index++] = point;
+            }
+
+            point = indices[i-1];
+            accum++;
+            if (accum == 2) {
+              early = last;
+              last = point;
+            }
+            else if (accum == 3) {
+              inds[ind_index++] = early;
+              inds[ind_index++] = last;
+              inds[ind_index++] = point;
+            }
+            else {
+              inds[ind_index++] = point;
+            }
+
+            point = sillymid_first;
+            accum++;
+            if (accum == 2) {
+              early = last;
+              last = point;
+            }
+            else if (accum == 3) {
+              inds[ind_index++] = early;
+              inds[ind_index++] = last;
+              inds[ind_index++] = point;
+            }
+            else {
+              inds[ind_index++] = point;
+            }
+
+            // (accum >= 3)
+            svcs[svc_index] = accum;
+            svc_index++;
+            // accum = 0, but more to come
+
+            // start a triangle strip
+            //   (sillymid_second, mid_second, i+3)
+            early = sillymid_second;
+            last = mid_second;
+            point = indices[i+1];
+            accum = 3;
+            inds[ind_index++] = early;
+            inds[ind_index++] = last;
+            inds[ind_index++] = point;
+          }
+        }
+        else { // !this_split
+          if (accum == 0) {
+            early = indices[i];
+            last = indices[i+1];
+            accum = 2;
+            continue; // don't make any triangles yet, for accum = 0
+          } // end if (accum == 0)
+          else if (last_split && lon_axis >= 0) { // && !this_split
+            // first, bind off
+            if (accum >= 3) {
+              svcs[svc_index] = accum;
+              svc_index++;
+            }
+            // accum = 0, but more to come
+
+            early = indices[i-1];
+            last = last_mid_first;
+            point = sillymid_first;
+            accum = 3;
+            inds[ind_index++] = early;
+            inds[ind_index++] = last;
+            inds[ind_index++] = point;
+            // bind off again
+            svcs[svc_index] = accum;
+            svc_index++;
+            // accum = 0, but more to come
+
+            // create a 2-triangle strip
+            //   (last_mid_second, sillymid_second, i, i+3)
+            early = last_mid_second;
+            last = sillymid_second;
+            point = indices[i];
+            accum = 3;
+            inds[ind_index++] = early;
+            inds[ind_index++] = last;
+            inds[ind_index++] = point;
+
+            point = indices[i+1];
+            accum++;
+            inds[ind_index++] = point;
+          }
+          else { // (!last_split || lon_axis < 0) && accum > 0 && !this_split
+            // just add the next point (i+3)
+            point = indices[i+1];
+            accum++;
+            if (accum == 2) {
+              early = last;
+              last = point;
+            }
+            else if (accum == 3) {
+              inds[ind_index++] = early;
+              inds[ind_index++] = last;
+              inds[ind_index++] = point;
+            }
+            else {
+              inds[ind_index++] = point;
+            }
+          }
+        } // end if no split
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]-1; i++)
+      if (accum >= 3) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc];
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_split) {
+      return this;
+    }
+    else {
+      array.coordinates = new float[3 * coord_index];
+      System.arraycopy(coords, 0, array.coordinates, 0, 3 * coord_index);
+      if (normals != null) {
+        array.normals = new float[3 * coord_index];
+        System.arraycopy(nos, 0, array.normals, 0, 3 * coord_index);
+      }
+      if (colors != null) {
+        array.colors = new byte[color_length * coord_index];
+        System.arraycopy(cols, 0, array.colors, 0, color_length * coord_index);
+      }
+      if (texCoords != null) {
+        array.texCoords = new float[2 * coord_index];
+        System.arraycopy(texs, 0, array.texCoords, 0, 2 * coord_index);
+      }
+      array.vertexCount = coord_index;
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      array.indices = new int[ind_index];
+      System.arraycopy(inds, 0, array.indices, 0, ind_index);
+      return array;
+    }
+  }
+
+  public VisADGeometryArray removeMissing() {
+    VisADIndexedTriangleStripArray array =
+      new VisADIndexedTriangleStripArray();
+    float[] coords = new float[coordinates.length];
+    float[] nos = null;
+    if (normals != null) {
+      nos = new float[normals.length];
+    }
+    int color_length = 3;
+    byte[] cols = null;
+    if (colors != null) {
+      cols = new byte[colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    float[] texs = null;
+    if (texCoords != null) {
+      texs = new float[texCoords.length];
+    }
+    int[] compress = new int[indices.length];
+    int j = 0;
+    int k = 0;
+    for (int i=0; i<coordinates.length; i+=3) {
+      if (coordinates[i] == coordinates[i] &&
+          coordinates[i+1] == coordinates[i+1] &&
+          coordinates[i+2] == coordinates[i+2]) {
+        compress[j] = k;
+        int k3 = 3 * k;
+        coords[k3] = coordinates[i];
+        coords[k3+1] = coordinates[i+1];
+        coords[k3+2] = coordinates[i+2];
+        if (normals != null) {
+          nos[k3] = normals[i];
+          nos[k3+1] = normals[i+1];
+          nos[k3+2] = normals[i+2];
+        }
+        if (colors != null) {
+          int kc = color_length * k;
+          int ic = color_length * j;
+          cols[kc] = colors[ic];
+          cols[kc+1] = colors[ic+1];
+          cols[kc+2] = colors[ic+2];
+          if (color_length == 4) {
+            cols[kc+3] = colors[ic+3];
+          }
+        }
+        if (texCoords != null) {
+          int kt = 2 * k;
+          int it = 2 * j;
+          texs[kt] = texCoords[it];
+          texs[kt+1] = texCoords[it+1];
+        }
+        k++;
+      }
+      else { // missing coordinates
+        compress[j] = -1;
+      }
+      j++;
+    } // end for (int i=0; i<coordinates.length; i+=3)
+    array.coordinates = new float[3 * k];
+    System.arraycopy(coords, 0, array.coordinates, 0, 3 * k);
+    if (normals != null) {
+      array.normals = new float[3 * k];
+      System.arraycopy(nos, 0, array.normals, 0, 3 * k);
+    }
+    if (colors != null) {
+      array.colors = new byte[color_length * k];
+      System.arraycopy(cols, 0, array.colors, 0, color_length * k);
+    }
+    if (texCoords != null) {
+      array.texCoords = new float[2 * k];
+      System.arraycopy(texs, 0, array.texCoords, 0, 2 * k);
+    }
+    array.vertexCount = k;
+
+    int[] new_indices = new int[indices.length];
+    int[] svcs = new int[coordinates.length / 4];
+    int svc_index = 0;
+    int last_i = 0; // start i for each vertex strip
+
+    int m = 0;
+    boolean any_missing = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      int accum = 0;
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]; i++) {
+
+        if (compress[indices[i]] >= 0) {
+          accum++;
+          if (accum >= 3) {
+            int iml = i;
+            if (accum == 3) {
+              iml = i - 2;
+            }
+            for (int im=iml; im<=i; im++) {
+              new_indices[m] = compress[indices[im]];
+              m++;
+            } // end for (im=iml; im<=i; im+=3)
+          } // end if (accum >= 3)
+        }
+        else { // missing coordinates values
+          any_missing = true;
+          if (accum >= 3) {
+            svcs[svc_index] = accum;
+            svc_index++;
+          }
+          accum = 0; // reset strip counter;
+        }
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]; i++)
+      if (accum >= 3) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc];
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_missing) {
+      return this;
+    }
+    else {
+      array.indices = new int[m];
+      System.arraycopy(new_indices, 0, array.indices, 0, m);
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      return array;
+    }
+  }
+
+  public String toString() {
+/*
+    String string = "VisADIndexedTriangleStripArray\n" + super.toString() +
+                    "\n indexCount = " + indexCount;
+*/
+    String string = "VisADIndexedTriangleStripArray, indexCount = " + indexCount;
+    string = string + "\n stripVertexCounts = ";
+    for (int i=0; i<stripVertexCounts.length; i++) {
+      string = string + stripVertexCounts[i] + " ";
+    }
+    string = string + "\n indices = ";
+    for (int i=0; i<indices.length; i++) {
+      string = string + indices[i] + " ";
+    }
+    return string;
+  }
+
+  public Object clone() {
+    VisADIndexedTriangleStripArray array =
+      new VisADIndexedTriangleStripArray();
+    copy(array);
+    array.indexCount = indexCount;
+    if (stripVertexCounts != null) {
+      array.stripVertexCounts = new int[stripVertexCounts.length];
+      System.arraycopy(stripVertexCounts, 0, array.stripVertexCounts, 0,
+                       stripVertexCounts.length);
+    }
+    if (indices != null) {
+      array.indices = new int[indices.length];
+      System.arraycopy(indices, 0, array.indices, 0, indices.length);
+    }
+    return array;
+  }
+
+}
+
diff --git a/visad/VisADLineArray.java b/visad/VisADLineArray.java
new file mode 100644
index 0000000..e8129d1
--- /dev/null
+++ b/visad/VisADLineArray.java
@@ -0,0 +1,396 @@
+//
+// VisADLineArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADLineArray stands in for j3d.LineArray
+   and is Serializable.<P>
+*/
+public class VisADLineArray extends VisADGeometryArray {
+
+  /**
+   * Merge an array of VisADLineArrays into a single VisADLineArray.
+   * @param  arrays  array of VisADLineArrays (may be null)
+   * @return a single VisADLineArray with all the info of arrays.
+   *         returns null if input is null.
+   */
+  public static VisADLineArray merge(VisADLineArray[] arrays)
+         throws VisADException {
+    if (arrays == null || arrays.length == 0) return null;
+    VisADLineArray array = new VisADLineArray();
+    merge(arrays, array);
+/*
+    int n = arrays.length;
+    int count = 0;
+    boolean color_flag = (arrays[0].colors != null);
+    for (int i=0; i<n; i++) {
+      count += arrays[i].vertexCount;
+      if (color_flag != (arrays[i].colors != null)) {
+        throw new DisplayException("VisADLineArray.merge: formats don't match");
+      }
+    }
+    float[] coordinates = new float[3 * count];
+    byte[] colors = null;
+    if (color_flag) {
+      colors = new byte[3 * count];
+    }
+    int k = 0;
+    int m = 0;
+    for (int i=0; i<n; i++) {
+      float[] c = arrays[i].coordinates;
+      for (int j=0; j<3*arrays[i].vertexCount; j++) {
+        coordinates[k++] = c[j];
+      }
+      if (color_flag) {
+        byte[] b = arrays[i].colors;
+        for (int j=0; j<3*arrays[i].vertexCount; j++) {
+          colors[m++] = b[j];
+        }
+      }
+    }
+    VisADLineArray array = new VisADLineArray();
+    array.vertexCount = count;
+    array.coordinates = coordinates;
+    array.colors = colors;
+    array.vertexFormat = arrays[0].vertexFormat;
+*/
+    return array;
+  }
+
+  public VisADGeometryArray adjustLongitude(DataRenderer renderer)
+         throws VisADException {
+    float[] lons = getLongitudes(renderer);
+    if (any_longitude_rotate) {
+      VisADLineArray array = new VisADLineArray();
+      array.vertexCount = vertexCount;
+      array.coordinates = coordinates;
+      array.colors = colors;
+      return array;
+    }
+    else {
+      return this;
+    }
+  }
+
+
+  private final static double LIMIT = 1.0f; // constant for TEST = 0
+  private final static double ALPHA = 0.01f; // constant for TEST = 1
+
+  /** eliminate any vectors or triangles crossing seams of
+      map projections, defined by display-side CoordinateSystems;
+      this default implementation does nothing */
+  public VisADGeometryArray adjustSeam(DataRenderer renderer)
+         throws VisADException {
+    CoordinateSystem coord_sys = renderer.getDisplayCoordinateSystem();
+    if (coord_sys == null || 
+        coord_sys instanceof SphericalCoordinateSystem ||
+        coord_sys instanceof CylindricalCoordinateSystem ||
+        coordinates == null) {
+      return this;
+    }
+
+    int len = coordinates.length / 3;
+
+    // WLH 15 March 2000
+    if (len < 6) return this;
+
+// System.out.println("VisADLineArray.adjustSeam try");
+
+    double[][] cs = new double[3][len];
+    int j = 0;
+    for (int i=0; i<len; i++) {
+      cs[0][i] = coordinates[j++];
+      cs[1][i] = coordinates[j++];
+      cs[2][i] = coordinates[j++];
+    }
+    double[][] rs = coord_sys.fromReference(Set.copyDoubles (cs));
+    boolean[] test = new boolean[len];
+    int last_i;
+
+    boolean any_split = false;
+
+    // TEST 1
+    if (len < 2) return this;
+    double[][] bs = new double[3][len/2];
+    double[][] ss = new double[3][len/2];
+    // ALPHA = 0.01f
+    double ALPHA1 = 1.0f + ALPHA;
+    double ALPHA1m = 1.0f - ALPHA;
+    for (int i=0; i<len/2; i++) {
+      // BS = point ALPHA * opposite direction
+      // bs = pt_i + 0.01 * (pt_i - pt_ip1), not ref
+      bs[0][i] = ALPHA1 * rs[0][2*i] - ALPHA * rs[0][2*i+1];
+      bs[1][i] = ALPHA1 * rs[1][2*i] - ALPHA * rs[1][2*i+1];
+      bs[2][i] = ALPHA1 * rs[2][2*i] - ALPHA * rs[2][2*i+1];
+      // SS = point ALPHA * same direction
+      // ss = pt_ip1 + 0.01 * (pt_ip1 - pt_i), not ref
+      ss[0][i] = ALPHA1 * rs[0][2*i+1] - ALPHA * rs[0][2*i];
+      ss[1][i] = ALPHA1 * rs[1][2*i+1] - ALPHA * rs[1][2*i];
+      ss[2][i] = ALPHA1 * rs[2][2*i+1] - ALPHA * rs[2][2*i];
+    }
+    double[][] ds = coord_sys.toReference(bs);
+    // ds = pt_i + 0.01 * (pt_i - pt_ip1), ref
+    double[][] es = coord_sys.toReference(ss);
+    // es = pt_ip1 + 0.01 * (pt_ip1 - pt_i), ref
+    double IALPHA = 1.0f / ALPHA;
+    for (int i=0; i<len; i+=2) {
+      // a = original line segment, ref
+      // a = pt_ip1 - pt_i, ref
+      double a0 = cs[0][i+1] - cs[0][i];
+      double a1 = cs[1][i+1] - cs[1][i];
+      double a2 = cs[2][i+1] - cs[2][i];
+      // b = estimate of vector using ALPHA * opposite direction
+      // b = 100.0 * (pt_i - toRef(pt_i + 0.01 * (pt_i - pt_ip1)) )
+      // if no break, b = pt_ip1 - pt_i, ref
+      double b0 = IALPHA * (cs[0][i] - ds[0][i/2]);
+      double b1 = IALPHA * (cs[1][i] - ds[1][i/2]);
+      double b2 = IALPHA * (cs[2][i] - ds[2][i/2]);
+      double aa = (a0 * a0 + a1 * a1 + a2 * a2);
+      double aminusb =
+        (b0 - a0) * (b0 - a0) +
+        (b1 - a1) * (b1 - a1) +
+        (b2 - a2) * (b2 - a2);
+      double abratio = aminusb / aa;
+
+      // c = estimate of vector using ALPHA * opposite direction
+      // c = 100.0 * (pt_ip1 - toRef(pt_ip1 + 0.01 * (pt_ip1 - pt_i)) )
+      // if no break, c = pti - pt_ip1, ref
+      double c0 = IALPHA * (cs[0][i+1] - es[0][i/2]);
+      double c1 = IALPHA * (cs[1][i+1] - es[1][i/2]);
+      double c2 = IALPHA * (cs[2][i+1] - es[2][i/2]);
+      double aminusc =
+        (c0 + a0) * (c0 + a0) +
+        (c1 + a1) * (c1 + a1) +
+        (c2 + a2) * (c2 + a2);
+      double acratio = aminusc / aa;
+
+      // true for bad segment
+      test[i] = (0.01f < abratio) || (0.01f < acratio);
+/*
+if ((0.01f < abratio) != (0.01f < acratio)) {
+System.out.println("test[" + i + "] " + abratio + " " + acratio);
+}
+*/
+
+      if (test[i]) any_split = true;
+    } // end for (int i=0; i<len; i+=2)
+
+    bs = null;
+    ss = null;
+
+    if (!any_split) {
+      return this;
+    }
+
+    // TEST 0
+    double[] lengths = new double[len];
+    for (int i=0; i<len;  i++) lengths[i] = 0.0f;
+    double mean_length = 0.0f;
+    double var_length = 0.0f;
+    double max_length = 0.0f;
+    int num_length = 0;
+    
+    for (int i=0; i<len; i+=2) {
+      double cd = (cs[0][i+1] - cs[0][i]) * (cs[0][i+1] - cs[0][i]) +
+                 (cs[1][i+1] - cs[1][i]) * (cs[1][i+1] - cs[1][i]) +
+                 (cs[2][i+1] - cs[2][i]) * (cs[2][i+1] - cs[2][i]);
+      if (!test[i]) {
+        lengths[i] = cd;
+        num_length++;
+        mean_length += lengths[i];
+        var_length += lengths[i] * lengths[i];
+        if (lengths[i] > max_length) max_length = lengths[i];
+      }
+    } // end for (int i=0; i<len; i+=2)
+    if (num_length < 2) return this;
+    mean_length = mean_length / num_length;
+    var_length = (double)
+      Math.sqrt((var_length - mean_length * mean_length) / num_length);
+    double limit_length = mean_length + LIMIT * var_length;
+/**TDR, TEST0 causes some problems...come back to this.
+System.out.println("limit_length = " + limit_length + " " + max_length + " " +
+                   mean_length + " " + var_length + " " + num_length);
+    if (max_length >= limit_length) {
+      for (int i=0; i<len; i+=2) {
+        test[i] = test[i] || (lengths[i] > limit_length);
+      }
+    }
+    */
+
+    cs = null;
+    rs = null;
+
+    float[] lastcoord = null;
+    byte[] lastcol = null;
+    VisADLineArray array = new VisADLineArray();
+    // worst case splits every line
+    float[] coords = new float[3 * coordinates.length];
+    int color_length = 0;
+    byte[] cols = null;
+    if (colors != null) {
+      color_length = 3;
+      cols = new byte[3 * colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+
+    int ki = 0;
+    int kj = 0;
+    j = 0;
+    for (int i=0; i<3*len; i+=6) {
+      if (!test[i/3]) {
+        coords[ki] = coordinates[i];
+        coords[ki+1] = coordinates[i+1];
+        coords[ki+2] = coordinates[i+2];
+        coords[ki+3] = coordinates[i+3];
+        coords[ki+4] = coordinates[i+4];
+        coords[ki+5] = coordinates[i+5];
+        ki += 6;
+        if (color_length == 3) {
+          cols[kj] = colors[j];
+          cols[kj+1] = colors[j+1];
+          cols[kj+2] = colors[j+2];
+          cols[kj+3] = colors[j+3];
+          cols[kj+4] = colors[j+4];
+          cols[kj+5] = colors[j+5];
+          kj += 6;
+        }
+        else if (color_length == 4) {
+          cols[kj] = colors[j];
+          cols[kj+1] = colors[j+1];
+          cols[kj+2] = colors[j+2];
+          cols[kj+3] = colors[j+3];
+          cols[kj+4] = colors[j+4];
+          cols[kj+5] = colors[j+5];
+          cols[kj+6] = colors[j+6];
+          cols[kj+7] = colors[j+7];
+          kj += 8;
+        }
+      }
+      else {
+        any_split = true;
+      }
+      j += 2 * color_length;
+    }
+
+   //System.out.println("VisADLineArray.adjustSeam any_split = " + any_split);
+
+    if (!any_split) {
+      return this;
+    }
+    else {
+      array.vertexCount = ki / 3;
+      array.coordinates = new float[ki];
+      System.arraycopy(coords, 0, array.coordinates, 0, ki);
+      if (colors != null) {
+        array.colors = new byte[kj];
+        System.arraycopy(cols, 0, array.colors, 0, kj);
+      }
+      return array;
+    }
+  }
+
+  public VisADGeometryArray removeMissing() {
+    VisADLineArray array = new VisADLineArray();
+    float[] coords = new float[coordinates.length];
+    int color_length = 3;
+    byte[] cols = null;
+    if (colors != null) {
+      cols = new byte[colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    int k = 0;
+    int m = 0;
+    int j = 0;
+    boolean any_missing = false;
+    for (int i=0; i<coordinates.length; i+=6) {
+      if (coordinates[i] == coordinates[i] &&
+          coordinates[i+1] == coordinates[i+1] &&
+          coordinates[i+2] == coordinates[i+2] &&
+          coordinates[i+3] == coordinates[i+3] &&
+          coordinates[i+4] == coordinates[i+4] &&
+          coordinates[i+5] == coordinates[i+5] &&
+          !Float.isInfinite(coordinates[i]) &&
+          !Float.isInfinite(coordinates[i+1]) &&
+          !Float.isInfinite(coordinates[i+2]) &&
+          !Float.isInfinite(coordinates[i+3]) &&
+          !Float.isInfinite(coordinates[i+4]) &&
+          !Float.isInfinite(coordinates[i+5])) {
+        coords[k] = coordinates[i];
+        coords[k+1] = coordinates[i+1];
+        coords[k+2] = coordinates[i+2];
+        coords[k+3] = coordinates[i+3];
+        coords[k+4] = coordinates[i+4];
+        coords[k+5] = coordinates[i+5];
+        if (colors != null) {
+          cols[m] = colors[j];
+          cols[m+1] = colors[j+1];
+          cols[m+2] = colors[j+2];
+          m += 3;
+          if (color_length == 4) {
+            cols[m++] = colors[j+3];
+          }
+          cols[m] = colors[j+color_length];
+          cols[m+1] = colors[j+color_length+1];
+          cols[m+2] = colors[j+color_length+2];
+          m += 3;
+          if (color_length == 4) {
+            cols[m++] = colors[j+color_length+3];
+          }
+        }
+        k += 6;
+      }
+      else { // missing coordinates values
+        any_missing = true;
+      }
+      j += 2 * color_length;
+    }
+    if (!any_missing) {
+      return this;
+    }
+    else {
+      array.coordinates = new float[k];
+      System.arraycopy(coords, 0, array.coordinates, 0, k);
+      if (colors != null) {
+        array.colors = new byte[m];
+        System.arraycopy(cols, 0, array.colors, 0, m);
+      }
+      return array;
+    }
+  }
+
+  /**
+   * Clone this VisADLineArray
+   * @return clone of this
+   */
+  public Object clone() {
+    VisADLineArray array = new VisADLineArray();
+    copy(array);
+    return array;
+  }
+
+}
+
diff --git a/visad/VisADLineStripArray.java b/visad/VisADLineStripArray.java
new file mode 100644
index 0000000..b92ab2b
--- /dev/null
+++ b/visad/VisADLineStripArray.java
@@ -0,0 +1,566 @@
+//
+// VisADLineStripArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADLineStripArray stands in for j3d.LineStripArray
+   and is Serializable.<P>
+*/
+public class VisADLineStripArray extends VisADGeometryArray {
+  public int[] stripVertexCounts;
+
+  public static VisADLineStripArray merge(VisADLineStripArray[] arrays)
+         throws VisADException {
+    if (arrays == null || arrays.length == 0) return null;
+    VisADLineStripArray array = new VisADLineStripArray();
+    merge(arrays, array);
+    int n = arrays.length;
+    int nstrips = 0;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] != null) {
+        nstrips += arrays[i].stripVertexCounts.length;
+      }
+    }
+    if (nstrips <= 0) return null;
+    int[] stripVertexCounts = new int[nstrips];
+    nstrips = 0;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] != null) {
+        int incnstrips = arrays[i].stripVertexCounts.length;
+        for (int j=0; j<incnstrips; j++) {
+          stripVertexCounts[nstrips + j] = arrays[i].stripVertexCounts[j];
+        }
+        nstrips += incnstrips;
+      }
+    }
+    array.stripVertexCounts = stripVertexCounts;
+    return array;
+  }
+
+  private final static int TEST = 1;
+  private final static float LIMIT = 4.0f; // constant for TEST = 0
+  private final static float ALPHA = 0.1f; // constant for TEST = 1
+
+  public VisADGeometryArray adjustSeam(DataRenderer renderer)
+         throws VisADException {
+    CoordinateSystem coord_sys = renderer.getDisplayCoordinateSystem();
+    // DRM 19 March 2002
+    //if (coord_sys == null || coord_sys instanceof SphericalCoordinateSystem) {
+    //  return this;
+    //}
+    if (coord_sys == null || coord_sys instanceof SphericalCoordinateSystem ||
+        coordinates == null) {
+      return this;
+    }
+
+    int len = coordinates.length / 3;
+
+    // WLH 15 March 2000
+    if (len < 6) return this;
+
+    float[][] cs = new float[3][len];
+    int j = 0;
+    for (int i=0; i<len; i++) {
+      cs[0][i] = coordinates[j++];
+      cs[1][i] = coordinates[j++];
+      cs[2][i] = coordinates[j++];
+    }
+    float[][] rs = coord_sys.fromReference(Set.copyFloats (cs));
+    boolean[] test = new boolean[len];
+    int last_i;
+
+    if (TEST == 0) {
+      float[] ratios = new float[len];
+      for (int i=0; i<len;  i++) ratios[i] = 0.0f;
+      float mean_ratio = 0.0f;
+      float var_ratio = 0.0f;
+      float max_ratio = 0.0f;
+      int num_ratio = 0;
+
+      last_i = 0; // start i for each vertex strip
+      for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+        for (int i=last_i; i<last_i+stripVertexCounts[i_svc]-1; i++) {
+          float cd = (cs[0][i+1] - cs[0][i]) * (cs[0][i+1] - cs[0][i]) +
+                     (cs[1][i+1] - cs[1][i]) * (cs[1][i+1] - cs[1][i]) +
+                     (cs[2][i+1] - cs[2][i]) * (cs[2][i+1] - cs[2][i]);
+          float rd = (rs[0][i+1] - rs[0][i]) * (rs[0][i+1] - rs[0][i]) +
+                     (rs[1][i+1] - rs[1][i]) * (rs[1][i+1] - rs[1][i]) +
+                     (rs[2][i+1] - rs[2][i]) * (rs[2][i+1] - rs[2][i]);
+          if (rd > 0.0f) {
+            ratios[i] = cd / rd;
+            num_ratio++;
+            mean_ratio += ratios[i];
+            var_ratio += ratios[i] * ratios[i];
+            if (ratios[i] > max_ratio) max_ratio = ratios[i];
+          }
+        } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+        last_i += stripVertexCounts[i_svc];
+      } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+      if (num_ratio < 2) return this;
+      mean_ratio = mean_ratio / num_ratio;
+      var_ratio = (float)
+        Math.sqrt((var_ratio - mean_ratio * mean_ratio) / num_ratio);
+      float limit_ratio = mean_ratio + LIMIT * var_ratio;
+      if (max_ratio < limit_ratio) return this;
+      last_i = 0; // start i for each vertex strip
+      for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+        for (int i=last_i; i<last_i+stripVertexCounts[i_svc]-1; i++) {
+          test[i] = (ratios[i] > limit_ratio);
+        } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+        last_i += stripVertexCounts[i_svc];
+      } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+    }
+    else if (TEST == 1) {
+      if (len < 2) return this;
+      float[][] bs = new float[3][len-1];
+      float ALPHA1 = 1.0f + ALPHA;
+      for (int i=0; i<len-1; i++) {
+        bs[0][i] = ALPHA1 * rs[0][i] - ALPHA * rs[0][i+1];
+        bs[1][i] = ALPHA1 * rs[1][i] - ALPHA * rs[1][i+1];
+        bs[2][i] = ALPHA1 * rs[2][i] - ALPHA * rs[2][i+1];
+      }
+      float[][] ds = coord_sys.toReference(bs);
+      float IALPHA = 1.0f / ALPHA;
+      last_i = 0; // start i for each vertex strip
+      for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+        for (int i=last_i; i<last_i+stripVertexCounts[i_svc]-1; i++) {
+          float a0 = cs[0][i+1] - cs[0][i];
+          float a1 = cs[1][i+1] - cs[1][i];
+          float a2 = cs[2][i+1] - cs[2][i];
+          float b0 = IALPHA * (cs[0][i] - ds[0][i]);
+          float b1 = IALPHA * (cs[1][i] - ds[1][i]);
+          float b2 = IALPHA * (cs[2][i] - ds[2][i]);
+          float aa = (a0 * a0 + a1 * a1 + a2 * a2);
+          float bb = (b0 * b0 + b1 * b1 + b2 * b2);
+          float ab = (b0 * a0 + b1 * a1 + b2 * a2);
+          // b = A projected onto B, as a signed fraction of B
+          float b = ab / bb;
+          // c = (norm(A projected onto B) / norm(A)) ^ 2
+          float c = (ab * ab) / (aa * bb);
+          test[i] = !(0.5f < b && b < 2.0f && 0.5f < c);
+        } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+        last_i += stripVertexCounts[i_svc];
+      } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+    } // end TEST == 1
+    cs = null;
+    rs = null;
+
+    float[] lastcoord = null;
+    byte[] lastcol = null;
+    VisADLineStripArray array = new VisADLineStripArray();
+    // worst case splits every line
+    float[] coords = new float[3 * coordinates.length];
+    int color_length = 0;
+    byte[] cols = null;
+    if (colors != null) {
+      color_length = 3;
+      cols = new byte[3 * colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    // worst case makes as many strips as there were points
+    int[] svcs = new int[coordinates.length];
+    int svc_index = 0;
+    last_i = 0; // start i for each vertex strip
+
+    int[] km = {0, 0};
+    j = 0;
+    boolean any_split = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      int accum = 0; // strip counter
+      j = color_length * last_i / 3;
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3) {
+        // first, add point at "i"
+        float coord[] =
+          new float[] {coordinates[i], coordinates[i+1], coordinates[i+2]};
+        byte[] col = null;
+        if (color_length == 3) {
+          col = new byte[] {colors[j], colors[j+1], colors[j+2]};
+        }
+        else if (color_length == 4) {
+          col = new byte[] {colors[j], colors[j+1], colors[j+2], colors[j+3]};
+        }
+        accum++;
+        if (accum == 1) {
+          lastcoord = coord;
+          lastcol = col;
+        }
+        else {
+          nextPoint(accum, color_length, coords, cols, coord, col,
+                    lastcoord, lastcol, km);
+        }
+        if (i == last_i+stripVertexCounts[i_svc]*3-3) continue; // last point
+        if (test[i/3]) {
+          any_split = true;
+          // treat split as a break
+          if (accum >= 2) {
+            svcs[svc_index] = accum;
+            svc_index++;
+          }
+          accum = 0; // reset strip counter;
+        } // end if split
+        j += color_length;
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+      if (accum >= 2) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc]*3;
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_split) {
+      return this;
+    }
+    else {
+      array.vertexCount = km[0] / 3;
+      array.coordinates = new float[km[0]];
+      System.arraycopy(coords, 0, array.coordinates, 0, km[0]);
+      if (colors != null) {
+        array.colors = new byte[km[1]];
+        System.arraycopy(cols, 0, array.colors, 0, km[1]);
+      }
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      return array;
+    }
+  }
+
+  public VisADGeometryArray adjustLongitude(DataRenderer renderer)
+         throws VisADException {
+    float[] lons = getLongitudes(renderer);
+    if (lons == null) return this;
+    int[] axis = new int[1];
+    float[] lon_coords = new float[2];
+    float[] lon_range = getLongitudeRange(lons, axis, lon_coords);
+    if (lon_range[0] != lon_range[0] ||
+        lon_range[1] != lon_range[1]) return this;
+    float bottom = lon_range[0];
+    float top = lon_range[1];
+    float low = bottom + 30.0f;
+    float hi = top - 30.0f;
+    int lon_axis = axis[0];
+    float coord_bottom = lon_coords[0];
+    float coord_top = lon_coords[1];
+    float[] lastcoord = null;
+    byte[] lastcol = null;
+
+    VisADLineStripArray array = new VisADLineStripArray();
+    // worst case splits every line
+    float[] coords = new float[3 * coordinates.length];
+    int color_length = 0;
+    byte[] cols = null;
+    if (colors != null) {
+      color_length = 3;
+      cols = new byte[3 * colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    // worst case makes as many strips as there were points
+    int[] svcs = new int[coordinates.length];
+    int svc_index = 0;
+    int last_i = 0; // start i for each vertex strip
+
+    int[] km = {0, 0};
+    int j = 0;
+    boolean any_split = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      int accum = 0; // strip counter
+      j = color_length * last_i / 3;
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3) {
+        // first, add point at "i"
+        float coord[] =
+          new float[] {coordinates[i], coordinates[i+1], coordinates[i+2]};
+        byte[] col = null;
+        if (color_length == 3) {
+          col = new byte[] {colors[j], colors[j+1], colors[j+2]};
+        }
+        else if (color_length == 4) {
+          col = new byte[] {colors[j], colors[j+1], colors[j+2], colors[j+3]};
+        }
+        accum++;
+        if (accum == 1) {
+          lastcoord = coord;
+          lastcol = col;
+        }
+        else {
+          nextPoint(accum, color_length, coords, cols, coord, col,
+                    lastcoord, lastcol, km);
+        }
+        if (i == last_i+stripVertexCounts[i_svc]*3-3) continue; // last point
+        int i3 = i / 3;
+        if ((lons[i3] < low && hi < lons[i3 + 1]) ||
+            (lons[i3 + 1] < low && hi < lons[i3])) {
+          any_split = true;
+          if (lon_axis < 0) {
+            // not enough info to interpolate, so treat split as a break
+            if (accum >= 2) {
+              svcs[svc_index] = accum;
+              svc_index++;
+            }
+            accum = 0; // reset strip counter;
+          }
+          else { // lon_axis >= 0
+            // split line by interpolation
+            float a, b;
+            float coord_first, coord_second;
+            if (lons[i3] < low) {
+              a = lons[i3] - bottom;
+              b = top - lons[i3 + 1];
+              coord_first = coord_bottom;
+              coord_second = coord_top;
+            }
+            else {
+              a = top - lons[i3];
+              b = lons[i3 + 1] - bottom;
+              coord_first = coord_top;
+              coord_second = coord_bottom;
+            }
+            float alpha = b / (a + b);
+            alpha = (alpha != alpha || alpha < 0.0f) ? 0.0f :
+                      ((1.0f < alpha) ? 1.0f : alpha);
+            float beta = 1.0f - alpha;
+            // create first point of split;
+            coord = new float[]
+              {alpha * coordinates[i] + beta * coordinates[i+3],
+               alpha * coordinates[i+1] + beta * coordinates[i+4],
+               alpha * coordinates[i+2] + beta * coordinates[i+5]};
+            coord[lon_axis] = coord_first;
+            col = null;
+            if (color_length == 3) {
+              col = new byte[]
+                {ShadowType.floatToByte(
+                  alpha * ShadowType.byteToFloat(colors[j]) +
+                  beta * ShadowType.byteToFloat(colors[j+3])),
+                 ShadowType.floatToByte(
+                  alpha * ShadowType.byteToFloat(colors[j+1]) +
+                  beta * ShadowType.byteToFloat(colors[j+4])),
+                 ShadowType.floatToByte(
+                  alpha * ShadowType.byteToFloat(colors[j+2]) +
+                  beta * ShadowType.byteToFloat(colors[j+5]))};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {ShadowType.floatToByte(
+                  alpha * ShadowType.byteToFloat(colors[j]) +
+                  beta * ShadowType.byteToFloat(colors[j+4])),
+                 ShadowType.floatToByte(
+                  alpha * ShadowType.byteToFloat(colors[j+1]) +
+                  beta * ShadowType.byteToFloat(colors[j+5])),
+                 ShadowType.floatToByte(
+                  alpha * ShadowType.byteToFloat(colors[j+2]) +
+                  beta * ShadowType.byteToFloat(colors[j+6])),
+                 ShadowType.floatToByte(
+                  alpha * ShadowType.byteToFloat(colors[j+3]) +
+                  beta * ShadowType.byteToFloat(colors[j+7]))};
+            }
+            accum++;
+            if (accum == 1) {
+              lastcoord = coord;
+              lastcol = col;
+            }
+            else {
+              nextPoint(accum, color_length, coords, cols, coord, col,
+                        lastcoord, lastcol, km);
+            }
+            // break strip between first and second points
+            if (accum >= 2) {
+              svcs[svc_index] = accum;
+              svc_index++;
+            }
+            accum = 0; // reset strip counter;
+
+            // create second point of split, identical to first except:
+            coord[lon_axis] = coord_second;
+            accum++;
+            if (accum == 1) {
+              lastcoord = coord;
+              lastcol = col;
+            }
+            else {
+              nextPoint(accum, color_length, coords, cols, coord, col,
+                        lastcoord, lastcol, km);
+            }
+
+          } // end if (lon_axis >= 0)
+        } // end if split
+        j += color_length;
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+      if (accum >= 2) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc]*3;
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_split) {
+      return this;
+    }
+    else {
+      array.vertexCount = km[0] / 3;
+      array.coordinates = new float[km[0]];
+      System.arraycopy(coords, 0, array.coordinates, 0, km[0]);
+      if (colors != null) {
+        array.colors = new byte[km[1]];
+        System.arraycopy(cols, 0, array.colors, 0, km[1]);
+      }
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      return array;
+    }
+
+  }
+
+  private void nextPoint(int accum, int color_length, float[] coords,
+                         byte[] cols, float[] coord, byte[] col,
+                         float[] lastcoord, byte[] lastcol, int[] km) {
+    if (accum == 2) {
+      coords[km[0]] = lastcoord[0];
+      coords[km[0]+1] = lastcoord[1];
+      coords[km[0]+2] = lastcoord[2];
+      km[0] += 3;
+      if (colors != null) {
+        cols[km[1]] = lastcol[0];
+        cols[km[1]+1] = lastcol[1];
+        cols[km[1]+2] = lastcol[2];
+        km[1] += 3;
+        if (color_length == 4) {
+          cols[km[1]++] = lastcol[3];
+        }
+      }
+    } // end if (accum == 2)
+    coords[km[0]] = coord[0];
+    coords[km[0]+1] = coord[1];
+    coords[km[0]+2] = coord[2];
+    km[0] += 3;
+    if (colors != null) {
+      cols[km[1]] = col[0];
+      cols[km[1]+1] = col[1];
+      cols[km[1]+2] = col[2];
+      km[1] += 3;
+      if (color_length == 4) {
+        cols[km[1]++] = col[3];
+      }
+    }
+  }
+
+  public VisADGeometryArray removeMissing() {
+    VisADLineStripArray array = new VisADLineStripArray();
+    float[] coords = new float[coordinates.length];
+    int color_length = 3;
+    byte[] cols = null;
+    if (colors != null) {
+      cols = new byte[colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    int[] svcs = new int[coordinates.length / 4];
+    int svc_index = 0;
+    int last_i = 0; // start i for each vertex strip
+
+    int k = 0;
+    int m = 0;
+    int j = 0;
+    boolean any_missing = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      int accum = 0; // strip counter
+      j = color_length * last_i / 3;
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3) {
+
+        if (coordinates[i] == coordinates[i] &&
+            coordinates[i+1] == coordinates[i+1] &&
+            coordinates[i+2] == coordinates[i+2]) {
+          accum++;
+          if (accum >= 2) {
+            int iml = i;
+            int jml = j;
+            if (accum == 2) {
+              iml = i - 3;
+              jml = j - color_length;
+            }
+            int jm = jml;
+            for (int im=iml; im<=i; im+=3) {
+              coords[k] = coordinates[im];
+              coords[k+1] = coordinates[im+1];
+              coords[k+2] = coordinates[im+2];
+              if (colors != null) {
+                cols[m] = colors[jm];
+                cols[m+1] = colors[jm+1];
+                cols[m+2] = colors[jm+2];
+                m += 3;
+                if (color_length == 4) {
+                  cols[m++] = colors[jm+3];
+                }
+              }
+              k += 3;
+              jm += color_length;
+            } // end for (im=iml; im<=i; im+=3)
+          } // end if (accum >= 2)
+        }
+        else { // missing coordinates values
+          any_missing = true;
+          if (accum >= 2) {
+            svcs[svc_index] = accum;
+            svc_index++;
+          }
+          accum = 0; // reset strip counter;
+        }
+        j += color_length;
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+      if (accum >= 2) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc]*3;
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_missing) {
+      return this;
+    }
+    else {
+      array.vertexCount = k / 3;
+      array.coordinates = new float[k];
+      System.arraycopy(coords, 0, array.coordinates, 0, k);
+      if (colors != null) {
+        array.colors = new byte[m];
+        System.arraycopy(cols, 0, array.colors, 0, m);
+      }
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      return array;
+    }
+  }
+
+  public Object clone() {
+    VisADLineStripArray array = new VisADLineStripArray();
+    copy(array);
+    if (stripVertexCounts != null) {
+      array.stripVertexCounts = new int[stripVertexCounts.length];
+      System.arraycopy(stripVertexCounts, 0, array.stripVertexCounts, 0,
+                       stripVertexCounts.length);
+    }
+    return array;
+  }
+
+}
+
diff --git a/visad/VisADPointArray.java b/visad/VisADPointArray.java
new file mode 100644
index 0000000..c05dc4b
--- /dev/null
+++ b/visad/VisADPointArray.java
@@ -0,0 +1,55 @@
+//
+// VisADPointArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADPointArray stands in for j3d.PointArray
+   and is Serializable.<P>
+*/
+public class VisADPointArray extends VisADGeometryArray {
+
+  public Object clone() {
+    VisADPointArray array = new VisADPointArray();
+    copy(array);
+    return array;
+  }
+
+  /** like the default implementation in VisADGeometryArray.java,
+      except no need to construct new VisADPointArray since this
+      already is a VisADPointArray;
+      split any vectors or trianlges crossing crossing longitude
+      seams when Longitude is mapped to a Cartesian display axis;
+      default implementation: rotate if necessary, then return points */
+  public VisADGeometryArray adjustLongitude(DataRenderer renderer)
+         throws VisADException {
+    float[] lons = getLongitudes(renderer);
+    // note that rotateLongitudes makes any changes to this.coordinates
+    return this;
+  }
+
+}
+
diff --git a/visad/VisADQuadArray.java b/visad/VisADQuadArray.java
new file mode 100644
index 0000000..a9b5866
--- /dev/null
+++ b/visad/VisADQuadArray.java
@@ -0,0 +1,42 @@
+//
+// VisADQuadArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADQuadArray stands in for j3d.QuadArray
+   and is Serializable.<P>
+*/
+public class VisADQuadArray extends VisADGeometryArray {
+
+  public Object clone() {
+    VisADQuadArray array = new VisADQuadArray();
+    copy(array);
+    return array;
+  }
+
+}
+
diff --git a/visad/VisADRay.java b/visad/VisADRay.java
new file mode 100644
index 0000000..471c83a
--- /dev/null
+++ b/visad/VisADRay.java
@@ -0,0 +1,42 @@
+//
+// VisADRay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADRay stands in for j3d.PickRay
+   and is Serializable.<P>
+*/
+public class VisADRay implements java.io.Serializable {
+
+  public double[] position = new double[3];
+  public double[] vector = new double[3];
+
+  public VisADRay() {
+  }
+
+}
+
diff --git a/visad/VisADSceneGraphObject.java b/visad/VisADSceneGraphObject.java
new file mode 100644
index 0000000..86255bd
--- /dev/null
+++ b/visad/VisADSceneGraphObject.java
@@ -0,0 +1,49 @@
+//
+// VisADSceneGraphObject.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADSceneGraphObject stands in for j3d.SceneGraphObject;
+   it is the parent of VisAD j3d stand-ins, and is Serializable.<P>
+*/
+public class VisADSceneGraphObject extends Object
+       implements java.io.Serializable {
+
+  VisADGroup parent = null;
+
+  public VisADSceneGraphObject() {
+  }
+
+  public synchronized void detach() {
+    if (parent != null) {
+      parent.detachChild(this);
+      parent = null;
+    }
+  }
+
+}
+
diff --git a/visad/VisADSwitch.java b/visad/VisADSwitch.java
new file mode 100644
index 0000000..1422f1b
--- /dev/null
+++ b/visad/VisADSwitch.java
@@ -0,0 +1,73 @@
+//
+// VisADSwitch.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADSwitch stands in for j3d.Switch
+   and is Serializable.<P>
+*/
+public class VisADSwitch extends VisADGroup {
+
+  private int which = -1;
+
+  // used by visad.cluster.ShadowNodeFunctionTypeJ3D to store
+  // Set for Animation VisADSwitch
+  // null to indicate volume rendering VisADSwitch
+  private Set set = null;
+
+  public VisADSwitch() {
+  }
+
+  public int getWhichChild() {
+    return which;
+  }
+
+  public synchronized void setWhichChild(int index) {
+    if (index >= 0 && index < numChildren()) {
+      which = index;
+    }
+  }
+
+  public synchronized VisADSceneGraphObject getSelectedChild() {
+    if (which >= 0 && which < numChildren()) {
+      return getChild(which);
+    }
+    else {
+      return null;
+    }
+  }
+
+  public void setSet(Set s) {
+    set = s;
+  }
+
+  public Set getSet() {
+    return set;
+  }
+
+}
+
diff --git a/visad/VisADTriangleArray.java b/visad/VisADTriangleArray.java
new file mode 100644
index 0000000..56b134b
--- /dev/null
+++ b/visad/VisADTriangleArray.java
@@ -0,0 +1,59 @@
+//
+// VisADTriangleArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADTriangleArray stands in for j3d.TrianlgeArray
+   and is Serializable.<P>
+*/
+public class VisADTriangleArray extends VisADGeometryArray {
+
+  /**
+   * Clone this VisADTriangleArray
+   * @return clone of this
+   */
+  public Object clone() {
+    VisADTriangleArray array = new VisADTriangleArray();
+    copy(array);
+    return array;
+  }
+
+  /**
+   * Merge an array of VisADTriangleArrays into a single VisADTriangleArray.
+   * @param  arrays  array of VisADTriangleArrays (may be null)
+   * @return a single VisADTriangleArray with all the info of arrays.
+   *         returns null if input is null.
+   */
+  public static VisADTriangleArray merge(VisADTriangleArray[] arrays)
+         throws VisADException {
+    if (arrays == null || arrays.length == 0) return null;
+    VisADTriangleArray array = new VisADTriangleArray();
+    merge(arrays, array);
+    return array;
+  }
+}
+
diff --git a/visad/VisADTriangleStripArray.java b/visad/VisADTriangleStripArray.java
new file mode 100644
index 0000000..414d50a
--- /dev/null
+++ b/visad/VisADTriangleStripArray.java
@@ -0,0 +1,1415 @@
+//
+// VisADTriangleStripArray.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad;
+
+/**
+   VisADTriangleStripArray stands in for
+   j3d.TriangleStripArray and is Serializable.<P>
+*/
+public class VisADTriangleStripArray extends VisADGeometryArray {
+  public int[] stripVertexCounts;
+
+  public static VisADTriangleStripArray
+                merge(VisADTriangleStripArray[] arrays)
+         throws VisADException {
+    if (arrays == null || arrays.length == 0) return null;
+    VisADTriangleStripArray array = new VisADTriangleStripArray();
+    merge(arrays, array);
+    int n = arrays.length;
+    int nstrips = 0;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] != null) {
+        nstrips += arrays[i].stripVertexCounts.length;
+      }
+    }
+    if (nstrips <= 0) return null;
+    int[] stripVertexCounts = new int[nstrips];
+    nstrips = 0;
+    for (int i=0; i<n; i++) {
+      if (arrays[i] != null) {
+        int incnstrips = arrays[i].stripVertexCounts.length;
+        for (int j=0; j<incnstrips; j++) {
+          stripVertexCounts[nstrips + j] = arrays[i].stripVertexCounts[j];
+        }
+        nstrips += incnstrips;
+      }
+    }
+    array.stripVertexCounts = stripVertexCounts;
+    return array;
+  }
+
+  private final static float LIMIT = 1.0f; // constant for TEST = 0
+  private final static double ALPHA = 0.01; // constant for TEST = 1
+
+  public VisADGeometryArray adjustSeam(DataRenderer renderer)
+         throws VisADException {
+    CoordinateSystem coord_sys = renderer.getDisplayCoordinateSystem();
+    // DRM 19 March 2002
+    //if (coord_sys == null || coord_sys instanceof SphericalCoordinateSystem) {
+    //  return this;
+    //}
+    if (coord_sys == null || coord_sys instanceof SphericalCoordinateSystem ||
+        coordinates == null) {
+      return this;
+    }
+
+    int len = coordinates.length / 3;
+    double[][] cs = new double[3][len];
+    int j = 0;
+    for (int i=0; i<len; i++) {
+      cs[0][i] = coordinates[j++];
+      cs[1][i] = coordinates[j++];
+      cs[2][i] = coordinates[j++];
+    }
+    double[][] rs = coord_sys.fromReference(Set.copyDoubles(cs));
+    boolean[] test = new boolean[len];
+    int last_i;
+
+    // for TEST 0
+    double[] lengths = new double[len];
+    for (int i=0; i<len;  i++) lengths[i] = 0.0f;
+    double mean_length = 0.0;
+    double var_length = 0.0;
+    double max_length = 0.0;
+    int num_length = 0;
+
+    boolean any_split = false;
+
+    // TEST 1
+    if (len < 2) return this;
+    double[][] bs = new double[3][len-1];
+    double[][] ss = new double[3][len-1];
+    double ALPHA1 = 1.0 + ALPHA;
+    double ALPHA1m = 1.0 - ALPHA;
+    for (int i=0; i<len-1; i++) {
+      // BS = point ALPHA * opposite direction
+      bs[0][i] = ALPHA1 * rs[0][i] - ALPHA * rs[0][i+1];
+      bs[1][i] = ALPHA1 * rs[1][i] - ALPHA * rs[1][i+1];
+      bs[2][i] = ALPHA1 * rs[2][i] - ALPHA * rs[2][i+1];
+      // SS = point ALPHA * same direction
+      ss[0][i] = ALPHA1 * rs[0][i+1] - ALPHA * rs[0][i];
+      ss[1][i] = ALPHA1 * rs[1][i+1] - ALPHA * rs[1][i];
+      ss[2][i] = ALPHA1 * rs[2][i+1] - ALPHA * rs[2][i];
+    }
+    double[][] ds = coord_sys.toReference(bs);
+    double[][] es = coord_sys.toReference(ss);
+    double IALPHA = 1.0 / ALPHA;
+
+    last_i = 0; // start i for each vertex strip
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]-1; i++) {
+        // A = original line segment
+        double a0 = cs[0][i+1] - cs[0][i];
+        double a1 = cs[1][i+1] - cs[1][i];
+        double a2 = cs[2][i+1] - cs[2][i];
+        // B = estimate of vector using ALPHA * opposite direction
+        double b0 = IALPHA * (cs[0][i] - ds[0][i]);
+        double b1 = IALPHA * (cs[1][i] - ds[1][i]);
+        double b2 = IALPHA * (cs[2][i] - ds[2][i]);
+        double aa = (a0 * a0 + a1 * a1 + a2 * a2);
+        double aminusb =
+          (b0 - a0) * (b0 - a0) +
+          (b1 - a1) * (b1 - a1) +
+          (b2 - a2) * (b2 - a2);
+        double abratio = aminusb / aa;
+
+        // C = estimate of vector using ALPHA * opposite direction
+        double c0 = IALPHA * (cs[0][i+1] - es[0][i]);
+        double c1 = IALPHA * (cs[1][i+1] - es[1][i]);
+        double c2 = IALPHA * (cs[2][i+1] - es[2][i]);
+        double aminusc =
+          (c0 + a0) * (c0 + a0) +
+          (c1 + a1) * (c1 + a1) +
+          (c2 + a2) * (c2 + a2);
+        double acratio = aminusc / aa;
+
+        // true for bad segment
+        test[i] = (0.01f < abratio) || (0.01f < acratio);
+/*
+        double bb = (b0 * b0 + b1 * b1 + b2 * b2);
+        double ab = (b0 * a0 + b1 * a1 + b2 * a2);
+        // b = A projected onto B, as a signed fraction of B
+        double b = ab / bb;
+        // c = (norm(A projected onto B) / norm(A)) ^ 2
+        double c = (ab * ab) / (aa * bb);
+        test[i] = !(0.5f < b && b < 2.0f && 0.5f < c);
+*/
+        // TEST 0
+        double cd = (cs[0][i+1] - cs[0][i]) * (cs[0][i+1] - cs[0][i]) +
+                   (cs[1][i+1] - cs[1][i]) * (cs[1][i+1] - cs[1][i]) +
+                   (cs[2][i+1] - cs[2][i]) * (cs[2][i+1] - cs[2][i]);
+        if (!test[i]) {
+          lengths[i] = cd;
+          num_length++;
+          mean_length += lengths[i];
+          var_length += lengths[i] * lengths[i];
+          if (lengths[i] > max_length) max_length = lengths[i];
+        }
+
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+      last_i += stripVertexCounts[i_svc];
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    cs = null;
+    rs = null;
+    bs = null;
+    ss = null;
+    ds = null;
+    es = null;
+
+    // TEST 0
+    if (num_length < 2) return this;
+    mean_length = mean_length / num_length;
+    var_length = //(float)
+      Math.sqrt((var_length - mean_length * mean_length) / num_length);
+    double limit_length = mean_length + LIMIT * var_length;
+
+    if (max_length >= limit_length) {
+      last_i = 0; // start i for each vertex strip
+      for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+        for (int i=last_i; i<last_i+stripVertexCounts[i_svc]-1; i++) {
+
+// WLH 20 June 2001
+          // test[i] = test[i] || (lengths[i] > limit_length);
+
+          if (test[i]) any_split = true;
+        } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+        last_i += stripVertexCounts[i_svc];
+      } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+    }
+
+// System.out.println("any_split = " + any_split);
+
+    if (!any_split) {
+      return this;
+    }
+
+    // most recent point
+    float[] lastcoord = null;
+    float[] lastno = null;
+    byte[] lastcol = null;
+    float[] lasttex = null;
+
+    // point before most recent
+    float[] earlycoord = null;
+    float[] earlyno = null;
+    byte[] earlycol = null;
+    float[] earlytex = null;
+
+    // this point
+    float[] coord = null;
+    float[] no = null;
+    byte[] col = null;
+    float[] tex = null;
+
+    VisADTriangleStripArray array = new VisADTriangleStripArray();
+    // worst case makes 3 times as many triangles
+    int worst = 6; // WLH 30 Dec 99 - try new worst
+    float[] coords = new float[worst * coordinates.length];
+    float[] nos = null;
+    if (normals != null) {
+      nos = new float[worst * normals.length];
+    }
+    int color_length = 0;
+    byte[] cols = null;
+    if (colors != null) {
+      color_length = 3;
+      cols = new byte[worst * colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    float[] texs = null;
+    if (texCoords != null) {
+      texs = new float[worst * texCoords.length];
+    }
+    // worst case makes as many strips as there were points
+    int[] svcs = new int[coordinates.length];
+    int svc_index = 0;
+    last_i = 0; // start i for each vertex strip
+
+    int[] kmr = {0, 0, 0};
+    int t = 0;
+    j = 0;
+    any_split = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      boolean this_split = false;
+      int accum = 0; // strip counter
+      j = (color_length * last_i / 3) - color_length;
+      t = (2 * last_i / 3) - 2;
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3-3; i+=3) {
+        j += color_length;
+        t += 2;
+        boolean last_split = this_split; // true if last edge was split
+        if (test[i/3]) {
+          this_split = true;
+          any_split = true;
+
+          // treat split as a break
+          if (accum >= 3) {
+            svcs[svc_index] = accum;
+            svc_index++;
+          }
+          lastcoord = new float[]
+            {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+          if (normals != null) {
+            lastno = new float[]
+              {normals[i+3], normals[i+4], normals[i+5]};
+          }
+          if (color_length == 3) {
+            lastcol = new byte[]
+              {colors[j+3], colors[j+4], colors[j+5]};
+          }
+          else if (color_length == 4) {
+            lastcol = new byte[]
+              {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+          }
+          if (texCoords != null) {
+            lasttex = new float[]
+              {texCoords[t+2], texCoords[t+3]};
+          }
+          accum = 1; // reset strip counter;
+          continue;
+
+        }
+        else { // no split
+          this_split = false;
+        }
+
+        if (!this_split) {
+          if (accum == 0) {
+            earlycoord = new float[]
+              {coordinates[i], coordinates[i+1], coordinates[i+2]};
+            lastcoord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              earlyno = new float[]
+                {normals[i], normals[i+1], normals[i+2]};
+              lastno = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              earlycol = new byte[]
+                {colors[j], colors[j+1], colors[j+2]};
+              lastcol = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              earlycol = new byte[]
+                {colors[j], colors[j+1], colors[j+2], colors[j+3]};
+              lastcol = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              earlytex = new float[]
+                {texCoords[t], texCoords[t+1]};
+              lasttex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum = 2;
+            continue; // don't make any triangles yet, for accum = 0
+          } // end if (accum == 0)
+          else { // (!last_split || lon_axis < 0) && accum > 0 && !this_split
+            // just add the next point (i+3)
+            coord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              no = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              col = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              tex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum++;
+
+            // WLH
+            if (earlycoord == null) {
+              earlycoord = new float[3];
+              if (normals != null) {
+                earlyno = new float[3];
+              }
+              if (color_length > 0) {
+                earlycol = new byte[color_length];
+              }
+              if (texCoords != null) {
+                earlytex = new float[2];
+              }
+            }
+
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+          }
+        } // end if no split
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3-3; i+=3)
+      if (accum >= 3) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc] * 3;
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_split) {
+      return this;
+    }
+    else {
+      array.coordinates = new float[kmr[0]];
+      System.arraycopy(coords, 0, array.coordinates, 0, kmr[0]);
+      if (normals != null) {
+        array.normals = new float[kmr[0]];
+        System.arraycopy(nos, 0, array.normals, 0, kmr[0]);
+      }
+      if (colors != null) {
+        array.colors = new byte[kmr[1]];
+        System.arraycopy(cols, 0, array.colors, 0, kmr[1]);
+      }
+      if (texCoords != null) {
+        array.texCoords = new float[kmr[2]];
+        System.arraycopy(texs, 0, array.texCoords, 0, kmr[2]);
+      }
+      array.vertexCount = kmr[0] / 3;
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      return array;
+    }
+  }
+/* */
+
+  public VisADGeometryArray adjustLongitude(DataRenderer renderer)
+         throws VisADException {
+    float[] lons = getLongitudes(renderer);
+    if (lons == null) return this;
+    int[] axis = new int[1];
+    float[] lon_coords = new float[2];
+    float[] lon_range = getLongitudeRange(lons, axis, lon_coords);
+    if (lon_range[0] != lon_range[0] ||
+        lon_range[1] != lon_range[1]) return this;
+    float bottom = lon_range[0];
+    float top = lon_range[1];
+    float low = bottom + 30.0f;
+    float hi = top - 30.0f;
+    int lon_axis = axis[0];
+    float coord_bottom = lon_coords[0];
+    float coord_top = lon_coords[1];
+
+    // midpoint from this (if this_split)
+    float[] midcoord_first = null;
+    float[] midcoord_second = null;
+    float[] midno = null;
+    byte[] midcol = null;
+    float[] midtex = null;
+
+    // save midpoint from last (if last_split)
+    float[] last_midcoord_first = null;
+    float[] last_midcoord_second = null;
+    float[] last_midno = null;
+    byte[] last_midcol = null;
+    float[] last_midtex = null;
+
+    // midpoint between -3 and +3 (if this_split^last_split)
+    float[] sillymidcoord_first = null;
+    float[] sillymidcoord_second = null;
+    float[] sillymidno = null;
+    byte[] sillymidcol = null;
+    float[] sillymidtex = null;
+
+    // most recent point
+    float[] lastcoord = null;
+    float[] lastno = null;
+    byte[] lastcol = null;
+    float[] lasttex = null;
+
+    // point before most recent
+    float[] earlycoord = null;
+    float[] earlyno = null;
+    byte[] earlycol = null;
+    float[] earlytex = null;
+
+    // this point
+    float[] coord = null;
+    float[] no = null;
+    byte[] col = null;
+    float[] tex = null;
+
+
+    VisADTriangleStripArray array = new VisADTriangleStripArray();
+    // worst case makes 3 times as many triangles
+    int worst = 6; // WLH 30 Dec 99 - try new worst
+    float[] coords = new float[worst * coordinates.length];
+    float[] nos = null;
+    if (normals != null) {
+      nos = new float[worst * normals.length];
+    }
+    int color_length = 0;
+    byte[] cols = null;
+    if (colors != null) {
+      color_length = 3;
+      cols = new byte[worst * colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    float[] texs = null;
+    if (texCoords != null) {
+      texs = new float[worst * texCoords.length];
+    }
+    // worst case makes as many strips as there were points
+    int[] svcs = new int[coordinates.length];
+    int svc_index = 0;
+    int last_i = 0; // start i for each vertex strip
+
+    int[] kmr = {0, 0, 0};
+    int t = 0;
+    int j = 0;
+    boolean any_split = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      boolean this_split = false;
+      int accum = 0; // strip counter
+      j = (color_length * last_i / 3) - color_length;
+      t = (2 * last_i / 3) - 2;
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3-3; i+=3) {
+        j += color_length;
+        t += 2;
+        boolean last_split = this_split; // true if last edge was split
+        if (last_split) {
+          last_midcoord_first = midcoord_first;
+          last_midcoord_second = midcoord_second;
+          last_midno = midno;
+          last_midcol = midcol;
+          last_midtex = midtex;
+        }
+        int i3 = i / 3;
+        if ((lons[i3] < low && hi < lons[i3 + 1]) ||
+            (lons[i3 + 1] < low && hi < lons[i3])) {
+/*
+System.out.println("any_split " + lons[i3] + " " + lons[i3 + 1] + " " +
+                   low + " " + hi + " " + bottom + " " + top);
+*/
+          this_split = true;
+          any_split = true;
+          if (lon_axis < 0) {
+            // not enough info to interpolate, so treat split as a break
+            if (accum >= 3) {
+              svcs[svc_index] = accum;
+              svc_index++;
+            }
+            lastcoord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              lastno = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              lastcol = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              lastcol = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              lasttex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum = 1; // reset strip counter;
+            continue;
+          }
+          // lon_axis >= 0
+          // split line by interpolation
+          float a, b;
+          float coord_first, coord_second;
+          if (lons[i3] < low) {
+            a = lons[i3] - bottom;
+            b = top - lons[i3 + 1];
+            coord_first = coord_bottom;
+            coord_second = coord_top;
+          }
+          else {
+            a = top - lons[i3];
+            b = lons[i3 + 1] - bottom;
+            coord_first = coord_top;
+            coord_second = coord_bottom;
+          }
+          float alpha = b / (a + b);
+          alpha = (alpha != alpha || alpha < 0.0f) ? 0.0f :
+                    ((1.0f < alpha) ? 1.0f : alpha);
+          float beta = 1.0f - alpha;
+
+          midcoord_first = new float[]
+            {alpha * coordinates[i] + beta * coordinates[i+3],
+             alpha * coordinates[i+1] + beta * coordinates[i+4],
+             alpha * coordinates[i+2] + beta * coordinates[i+5]};
+          midcoord_second = new float[]
+            {midcoord_first[0], midcoord_first[1], midcoord_first[2]};
+          midcoord_first[lon_axis] = coord_first;
+          midcoord_second[lon_axis] = coord_second;
+          if (normals != null) {
+            midno = new float[]
+              {alpha * normals[i] + beta * normals[i+3],
+               alpha * normals[i+1] + beta * normals[i+4],
+               alpha * normals[i+2] + beta * normals[i+5]};
+          }
+          if (color_length == 3) {
+            midcol = new byte[]
+              {ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j]) +
+                beta * ShadowType.byteToFloat(colors[j+3])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j+1]) +
+                beta * ShadowType.byteToFloat(colors[j+4])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j+2]) +
+                beta * ShadowType.byteToFloat(colors[j+5]))};
+          }
+          else if (color_length == 4) {
+            midcol = new byte[]
+              {ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j]) +
+                beta * ShadowType.byteToFloat(colors[j+4])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j+1]) +
+                beta * ShadowType.byteToFloat(colors[j+5])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j+2]) +
+                beta * ShadowType.byteToFloat(colors[j+6])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j+3]) +
+                beta * ShadowType.byteToFloat(colors[j+7]))};
+          }
+          if (texCoords != null) {
+            midtex = new float[]
+              {alpha * texCoords[t] + beta * texCoords[t+2],
+               alpha * texCoords[t+1] + beta * texCoords[t+3]};
+          }
+        }
+        else { // no split
+          this_split = false;
+        }
+
+        if (accum > 0 && this_split != last_split && lon_axis >= 0) {
+          // need to compute mid edge from -3 to +3
+
+          // split line by interpolation
+          float a, b;
+          float coord_first, coord_second;
+          if (lons[i3 - 1] < low) {
+            a = lons[i3 - 1] - bottom;
+            b = top - lons[i3 + 1];
+            coord_first = coord_bottom;
+            coord_second = coord_top;
+          }
+          else {
+            a = top - lons[i3 - 1];
+            b = lons[i3 + 1] - bottom;
+            coord_first = coord_top;
+            coord_second = coord_bottom;
+          }
+          float alpha = b / (a + b);
+          alpha = (alpha != alpha || alpha < 0.0f) ? 0.0f :
+                    ((1.0f < alpha) ? 1.0f : alpha);
+          float beta = 1.0f - alpha;
+
+          sillymidcoord_first = new float[]
+            {alpha * coordinates[i-3] + beta * coordinates[i+3],
+             alpha * coordinates[i-2] + beta * coordinates[i+4],
+             alpha * coordinates[i-1] + beta * coordinates[i+5]};
+          sillymidcoord_second = new float[]
+            {sillymidcoord_first[0], sillymidcoord_first[1], sillymidcoord_first[2]};
+          sillymidcoord_first[lon_axis] = coord_first;
+          sillymidcoord_second[lon_axis] = coord_second;
+          if (normals != null) {
+            sillymidno = new float[]
+              {alpha * normals[i-3] + beta * normals[i+3],
+               alpha * normals[i-2] + beta * normals[i+4],
+               alpha * normals[i-1] + beta * normals[i+5]};
+          }
+          if (color_length == 3) {
+            sillymidcol = new byte[]
+              {ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j-3]) +
+                beta * ShadowType.byteToFloat(colors[j+3])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j-2]) +
+                beta * ShadowType.byteToFloat(colors[j+4])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j-1]) +
+                beta * ShadowType.byteToFloat(colors[j+5]))};
+          }
+          else if (color_length == 4) {
+            sillymidcol = new byte[]
+              {ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j-4]) +
+                beta * ShadowType.byteToFloat(colors[j+4])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j-3]) +
+                beta * ShadowType.byteToFloat(colors[j+5])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j-2]) +
+                beta * ShadowType.byteToFloat(colors[j+6])),
+               ShadowType.floatToByte(
+                alpha * ShadowType.byteToFloat(colors[j-1]) +
+                beta * ShadowType.byteToFloat(colors[j+7]))};
+          }
+          if (texCoords != null) {
+            sillymidtex = new float[]
+              {alpha * texCoords[t-2] + beta * texCoords[t+2],
+               alpha * texCoords[t-1] + beta * texCoords[t+3]};
+          }
+        }
+
+        if (this_split) {
+          if (accum == 0) {
+            earlycoord = new float[]
+              {midcoord_second[0], midcoord_second[1], midcoord_second[2]};
+            lastcoord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              earlyno = new float[]
+                {midno[0], midno[1], midno[2]};
+              lastno = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              earlycol = new byte[]
+                {midcol[0], midcol[1], midcol[2]};
+              lastcol = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              earlycol = new byte[]
+                {midcol[0], midcol[1], midcol[2], midcol[3]};
+              lastcol = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              earlytex = new float[]
+                {midtex[0], midtex[1]};
+              lasttex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum = 2;
+            continue; // don't make any triangles yet, for accum = 0
+          } // end if (accum == 0)
+          else if (last_split) { // && this_split
+            coord = midcoord_first;
+            no = midno;
+            col = midcol;
+            tex = midtex;
+            accum++;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+            // bind off
+            svcs[svc_index] = accum;
+            svc_index++;
+            // accum = 0, but more to come
+
+            // create a 2-triangle strip
+            //   (last_mid_first, i-3, mid_second, i+3)
+            earlycoord = last_midcoord_first;
+            earlyno = last_midno;
+            earlycol = last_midcol;
+            earlytex = last_midtex;
+            lastcoord = new float[]
+              {coordinates[i-3], coordinates[i-2], coordinates[i-1]};
+            if (normals != null) {
+              lastno = new float[]
+                {normals[i-3], normals[i-2], normals[i-1]};
+            }
+            if (color_length == 3) {
+              lastcol = new byte[]
+                {colors[j-3], colors[j-2], colors[j-1]};
+            }
+            else if (color_length == 4) {
+              lastcol = new byte[]
+                {colors[j-4], colors[j-3], colors[j-2], colors[j-1]};
+            }
+            if (texCoords != null) {
+              lasttex = new float[]
+                {texCoords[t-2], texCoords[t-1]};
+            }
+            coord = midcoord_second;
+            no = midno;
+            col = midcol;
+            tex = midtex;
+            accum = 3;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+            coord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              no = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              col = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              tex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum++;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+          }
+          else { // !last_split && accum > 0 && this_split
+            // add (mid_first, i-3, sillymid_first)
+            coord = midcoord_first;
+            no = midno;
+            col = midcol;
+            tex = midtex;
+            accum++;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+            coord = new float[]
+              {coordinates[i-3], coordinates[i-2], coordinates[i-1]};
+            if (normals != null) {
+              no = new float[]
+                {normals[i-3], normals[i-2], normals[i-1]};
+            }
+            if (color_length == 3) {
+              col = new byte[]
+                {colors[j-3], colors[j-2], colors[j-1]};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {colors[j-4], colors[j-3], colors[j-2], colors[j-1]};
+            }
+            if (texCoords != null) {
+              tex = new float[]
+                {texCoords[t-2], texCoords[t-1]};
+            }
+            accum++;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+            coord = sillymidcoord_first;
+            no = sillymidno;
+            col = sillymidcol;
+            tex = sillymidtex;
+            accum++;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+            // (accum >= 3)
+            svcs[svc_index] = accum;
+            svc_index++;
+            // accum = 0, but more to come
+
+            // start a triangle strip
+            //   (sillymid_second, mid_second, i+3)
+            earlycoord = sillymidcoord_second;
+            earlyno = sillymidno;
+            earlycol = sillymidcol;
+            earlytex = sillymidtex;
+            lastcoord = midcoord_second;
+            lastno = midno;
+            lastcol = midcol;
+            lasttex = midtex;
+            coord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              no = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              col = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              tex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum = 3;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+          }
+        }
+        else { // !this_split
+          if (accum == 0) {
+            earlycoord = new float[]
+              {coordinates[i], coordinates[i+1], coordinates[i+2]};
+            lastcoord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              earlyno = new float[]
+                {normals[i], normals[i+1], normals[i+2]};
+              lastno = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              earlycol = new byte[]
+                {colors[j], colors[j+1], colors[j+2]};
+              lastcol = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              earlycol = new byte[]
+                {colors[j], colors[j+1], colors[j+2], colors[j+3]};
+              lastcol = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              earlytex = new float[]
+                {texCoords[t], texCoords[t+1]};
+              lasttex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum = 2;
+            continue; // don't make any triangles yet, for accum = 0
+          } // end if (accum == 0)
+          else if (last_split && lon_axis >= 0) { // && !this_split
+            // first, bind off
+            if (accum >= 3) {
+              svcs[svc_index] = accum;
+              svc_index++;
+            }
+            // accum = 0, but more to come
+
+            // create a lone triangle (i-3, last_mid_first, sillymid_first)
+            earlycoord = new float[]
+              {coordinates[i-3], coordinates[i-2], coordinates[i-1]};
+            if (normals != null) {
+              earlyno = new float[]
+                {normals[i-3], normals[i-2], normals[i-1]};
+            }
+            if (color_length == 3) {
+              earlycol = new byte[]
+                {colors[j-3], colors[j-2], colors[j-1]};
+            }
+            else if (color_length == 4) {
+              earlycol = new byte[]
+                {colors[j-4], colors[j-3], colors[j-2], colors[j-1]};
+            }
+            if (texCoords != null) {
+              earlytex = new float[]
+                {texCoords[t-2], texCoords[t-1]};
+            }
+            lastcoord = last_midcoord_first;
+            lastno = last_midno;
+            lastcol = last_midcol;
+            lasttex = last_midtex;
+            coord = sillymidcoord_first;
+            no = sillymidno;
+            col = sillymidcol;
+            tex = sillymidtex;
+            accum = 3;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+            // bind off again
+            svcs[svc_index] = accum;
+            svc_index++;
+            // accum = 0, but more to come
+
+            // create a 2-triangle strip
+            //   (last_mid_second, sillymid_second, i, i+3)
+            earlycoord = last_midcoord_second;
+            earlyno = last_midno;
+            earlycol = last_midcol;
+            earlytex = last_midtex;
+            lastcoord = sillymidcoord_second;
+            lastno = sillymidno;
+            lastcol = sillymidcol;
+            lasttex = sillymidtex;
+            coord = new float[]
+              {coordinates[i], coordinates[i+1], coordinates[i+2]};
+            if (normals != null) {
+              no = new float[]
+                {normals[i], normals[i+1], normals[i+2]};
+            }
+            if (color_length == 3) {
+              col = new byte[]
+                {colors[j], colors[j+1], colors[j+2]};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {colors[j], colors[j+1], colors[j+2], colors[j+3]};
+            }
+            if (texCoords != null) {
+              tex = new float[]
+                {texCoords[t], texCoords[t+1]};
+            }
+            accum = 3;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+            coord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              no = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              col = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              tex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum++;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+          }
+          else { // (!last_split || lon_axis < 0) && accum > 0 && !this_split
+            // just add the next point (i+3)
+
+            if (earlycoord == null) {
+              earlycoord = new float[3];
+              if (normals != null) {
+                earlyno = new float[3];
+              }
+              if (color_length > 0) {
+                earlycol = new byte[color_length];
+              }
+              if (texCoords != null) {
+                earlytex = new float[2];
+              }
+            }
+
+            coord = new float[]
+              {coordinates[i+3], coordinates[i+4], coordinates[i+5]};
+            if (normals != null) {
+              no = new float[]
+                {normals[i+3], normals[i+4], normals[i+5]};
+            }
+            if (color_length == 3) {
+              col = new byte[]
+                {colors[j+3], colors[j+4], colors[j+5]};
+            }
+            else if (color_length == 4) {
+              col = new byte[]
+                {colors[j+4], colors[j+5], colors[j+6], colors[j+7]};
+            }
+            if (texCoords != null) {
+              tex = new float[]
+                {texCoords[t+2], texCoords[t+3]};
+            }
+            accum++;
+            nextPoint(accum, color_length, coords, nos, cols, texs,
+                 coord, no, col, tex, lastcoord, lastno, lastcol,
+                 lasttex, earlycoord, earlyno, earlycol, earlytex, kmr);
+          }
+        } // end if no split
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3-3; i+=3)
+      if (accum >= 3) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc] * 3;
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_split) {
+      return this;
+    }
+    else {
+      array.coordinates = new float[kmr[0]];
+      System.arraycopy(coords, 0, array.coordinates, 0, kmr[0]);
+      if (normals != null) {
+        array.normals = new float[kmr[0]];
+        System.arraycopy(nos, 0, array.normals, 0, kmr[0]);
+      }
+      if (colors != null) {
+        array.colors = new byte[kmr[1]];
+        System.arraycopy(cols, 0, array.colors, 0, kmr[1]);
+      }
+      if (texCoords != null) {
+        array.texCoords = new float[kmr[2]];
+        System.arraycopy(texs, 0, array.texCoords, 0, kmr[2]);
+      }
+      array.vertexCount = kmr[0] / 3;
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      return array;
+    }
+  }
+
+  private void nextPoint(int accum, int color_length, float[] coords,
+                float[] nos, byte[] cols, float[] texs,
+                float[] coord, float[] no, byte[] col, float[] tex,
+                float[] lastcoord, float[] lastno, byte[] lastcol,
+                float[] lasttex, float[] earlycoord, float[] earlyno,
+                byte[] earlycol, float[] earlytex, int[] kmr) {
+    if (accum == 2) {
+      System.arraycopy(lastcoord, 0, earlycoord, 0, 3);
+      System.arraycopy(coord, 0, lastcoord, 0, 3);
+      if (normals != null) {
+        System.arraycopy(lastno, 0, earlyno, 0, 3);
+        System.arraycopy(no, 0, lastno, 0, 3);
+      }
+      if (colors != null) {
+        System.arraycopy(lastcol, 0, earlycol, 0, color_length);
+        System.arraycopy(col, 0, lastcol, 0, color_length);
+      }
+      if (texCoords != null) {
+/* WLH 9 March 2000
+        System.arraycopy(lasttex, 0, earlytex, 0, 3);
+        System.arraycopy(tex, 0, lasttex, 0, 3);
+*/
+        System.arraycopy(lasttex, 0, earlytex, 0, 2);
+        System.arraycopy(tex, 0, lasttex, 0, 2);
+      }
+      return;
+    }
+    else if (accum == 3) {
+      // put early point on new strip
+      coords[kmr[0]] = earlycoord[0];
+      coords[kmr[0]+1] = earlycoord[1];
+      coords[kmr[0]+2] = earlycoord[2];
+      if (normals != null) {
+        nos[kmr[0]] = earlyno[0];
+        nos[kmr[0]+1] = earlyno[1];
+        nos[kmr[0]+2] = earlyno[2];
+      }
+      kmr[0] += 3;
+      if (colors != null) {
+        cols[kmr[1]] = earlycol[0];
+        cols[kmr[1]+1] = earlycol[1];
+        cols[kmr[1]+2] = earlycol[2];
+        kmr[1] += 3;
+        if (color_length == 4) {
+          cols[kmr[1]++] = earlycol[3];
+        }
+      }
+      if (texCoords != null) {
+        texs[kmr[2]] = earlytex[0];
+        texs[kmr[2]+1] = earlytex[1];
+        kmr[2] += 2;
+      }
+
+      // put last point on new strip
+      coords[kmr[0]] = lastcoord[0];
+      coords[kmr[0]+1] = lastcoord[1];
+      coords[kmr[0]+2] = lastcoord[2];
+      if (normals != null) {
+        nos[kmr[0]] = lastno[0];
+        nos[kmr[0]+1] = lastno[1];
+        nos[kmr[0]+2] = lastno[2];
+      }
+      kmr[0] += 3;
+      if (colors != null) {
+        cols[kmr[1]] = lastcol[0];
+        cols[kmr[1]+1] = lastcol[1];
+        cols[kmr[1]+2] = lastcol[2];
+        kmr[1] += 3;
+        if (color_length == 4) {
+          cols[kmr[1]++] = lastcol[3];
+        }
+      }
+      if (texCoords != null) {
+        texs[kmr[2]] = lasttex[0];
+        texs[kmr[2]+1] = lasttex[1];
+        kmr[2] += 2;
+      }
+    } // end if (accum == 3)
+
+    // put this point on new strip
+    coords[kmr[0]] = coord[0];
+    coords[kmr[0]+1] = coord[1];
+    coords[kmr[0]+2] = coord[2];
+    if (normals != null) {
+      nos[kmr[0]] = no[0];
+      nos[kmr[0]+1] = no[1];
+      nos[kmr[0]+2] = no[2];
+    }
+    kmr[0] += 3;
+    if (colors != null) {
+      cols[kmr[1]] = col[0];
+      cols[kmr[1]+1] = col[1];
+      cols[kmr[1]+2] = col[2];
+      kmr[1] += 3;
+      if (color_length == 4) {
+        cols[kmr[1]++] = col[3];
+      }
+    }
+    if (texCoords != null) {
+      texs[kmr[2]] = tex[0];
+      texs[kmr[2]+1] = tex[1];
+      kmr[2] += 2;
+    }
+  }
+
+  public VisADGeometryArray removeMissing() {
+    VisADTriangleStripArray array = new VisADTriangleStripArray();
+    float[] coords = new float[coordinates.length];
+    float[] nos = null;
+    if (normals != null) {
+      nos = new float[normals.length];
+    }
+    int color_length = 3;
+    byte[] cols = null;
+    if (colors != null) {
+      cols = new byte[colors.length];
+      if (colors.length != coordinates.length) color_length = 4;
+    }
+    float[] texs = null;
+    if (texCoords != null) {
+      texs = new float[texCoords.length];
+    }
+    int[] svcs = new int[coordinates.length / 4];
+    int svc_index = 0;
+    int last_i = 0; // start i for each vertex strip
+
+    int k = 0;
+    int m = 0;
+    int t = 0;
+    int r = 0;
+    int j = 0;
+    boolean any_missing = false;
+    for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++) {
+      int accum = 0; // strip counter
+      j = color_length * last_i / 3;
+      t = 2 * last_i / 3;
+      for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3) {
+
+        if (coordinates[i] == coordinates[i] &&
+            coordinates[i+1] == coordinates[i+1] &&
+            coordinates[i+2] == coordinates[i+2]) {
+          accum++;
+          if (accum >= 3) {
+            int iml = i;
+            int jml = j;
+            int tml = t;
+            if (accum == 3) {
+              iml = i - 6;
+              jml = j - 2 * color_length;
+              tml = t - 4;
+            }
+            int jm = jml;
+            int tm = tml;
+            for (int im=iml; im<=i; im+=3) {
+              coords[k] = coordinates[im];
+              coords[k+1] = coordinates[im+1];
+              coords[k+2] = coordinates[im+2];
+              if (normals != null) {
+                nos[k] = normals[im];
+                nos[k+1] = normals[im+1];
+                nos[k+2] = normals[im+2];
+              }
+              if (colors != null) {
+                cols[m] = colors[jm];
+                cols[m+1] = colors[jm+1];
+                cols[m+2] = colors[jm+2];
+                m += 3;
+                if (color_length == 4) {
+                  cols[m++] = colors[jm+3];
+                }
+              }
+              if (texCoords != null) {
+                texs[r] = texCoords[tm];
+                texs[r+1] = texCoords[tm+1];
+                r += 2;
+              }
+              k += 3;
+              jm += color_length;
+              tm += 2;
+            } // end for (im=iml; im<=i; im+=3)
+          } // end if (accum >= 3)
+        }
+        else { // missing coordinates values
+          any_missing = true;
+          if (accum >= 3) {
+            svcs[svc_index] = accum;
+            svc_index++;
+          }
+          accum = 0; // reset strip counter;
+        }
+        j += color_length;
+        t += 2;
+      } // end for (int i=last_i; i<last_i+stripVertexCounts[i_svc]*3; i+=3)
+      if (accum >= 3) {
+        svcs[svc_index] = accum;
+        svc_index++;
+      }
+      last_i += stripVertexCounts[i_svc] * 3;
+    } // end for (int i_svc=0; i_svc<stripVertexCounts.length; i_svc++)
+
+    if (!any_missing) {
+      return this;
+    }
+    else {
+      array.coordinates = new float[k];
+      System.arraycopy(coords, 0, array.coordinates, 0, k);
+      if (normals != null) {
+        array.normals = new float[k];
+        System.arraycopy(nos, 0, array.normals, 0, k);
+      }
+      if (colors != null) {
+        array.colors = new byte[m];
+        System.arraycopy(cols, 0, array.colors, 0, m);
+      }
+      if (texCoords != null) {
+        array.texCoords = new float[r];
+        System.arraycopy(texs, 0, array.texCoords, 0, r);
+      }
+      array.vertexCount = k / 3;
+      array.stripVertexCounts = new int[svc_index];
+      System.arraycopy(svcs, 0, array.stripVertexCounts, 0, svc_index);
+      return array;
+    }
+  }
+
+  public String toString() {
+/*
+    String string = "VisADTriangleStripArray\n" + super.toString() +
+                    "\n indexCount = " + indexCount;
+*/
+    String string = "VisADTriangleStripArray ";
+    string = string + "\n stripVertexCounts = ";
+    for (int i=0; i<stripVertexCounts.length; i++) {
+      string = string + stripVertexCounts[i] + " ";
+    }
+    return string;
+  }
+
+  public Object clone() {
+    VisADTriangleStripArray array =
+      new VisADTriangleStripArray();
+    copy(array);
+    if (stripVertexCounts != null) {
+      array.stripVertexCounts = new int[stripVertexCounts.length];
+      System.arraycopy(stripVertexCounts, 0, array.stripVertexCounts, 0,
+                       stripVertexCounts.length);
+    }
+    return array;
+  }
+
+}
+
+/*
+documentation for VisADTriangleStripArray.adjustLongitude()
+and VisADIndexedTriangleStripArray.adjustLongitude()
+
+given a sequence of points (i-1, i, i+1) along a triangle strip:
+and the three possible split points (silly = last .xor. this)
+
+                    i
+                    /\
+                   /  \
+                  /    \
+                 /      \
+          2nd   /        \   1st
+    last mid   /          \   mid (this)
+          1st /            \ 2nd
+             /              \
+            /                \
+           /                  \
+          i-1    1st  2nd     i+1
+                silly mid
+
+longitude
+split?
+l   t
+a   h
+s   i
+t   s
+
+                    1
+No  No              /\
+                   /  \
+                  /    \
+                 /      \
+                /        \
+               /          \
+              /            \
+             /              \
+            /                \
+           /                  \
+          0                    2
+
+
+                    1
+No  Yes             /\
+                   /  \
+                  /    \
+                 /      \
+                /        \ 2
+               /          \
+              /            \ 6
+             /              \
+            /                \
+           /                  \
+          0, 3     4, 5*       7
+
+
+                    1, 7
+Yes No              /\
+                   /  \
+                  /    \
+                 /      \
+          0, 5* /        \
+               /          \
+            3 /            \
+             /              \
+            /                \
+           /                  \
+          2*       4, 6        8
+
+
+                    1
+Yes Yes             /\
+                   /  \
+                  /    \
+                 /      \
+             0  /        \ 2
+               /          \
+           3* /            \ 5
+             /              \
+            /                \
+           /                  \
+          4                    6
+
+
+the numbers are the order of visiting points in the new strip
+and n* indicates start a new strip
+
+*/
+
diff --git a/visad/aeri/Aeri.java b/visad/aeri/Aeri.java
new file mode 100644
index 0000000..7bb345e
--- /dev/null
+++ b/visad/aeri/Aeri.java
@@ -0,0 +1,1209 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+RH looks interesting up to 10000 m
+MR better near boundary layer (but tighter color range)
+*/
+
+package visad.aeri;
+
+import visad.*;
+import visad.util.*;
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+import visad.data.netcdf.*;
+import visad.bom.WindPolarCoordinateSystem;
+import visad.data.mcidas.BaseMapAdapter;
+import visad.data.mcidas.AreaAdapter;
+import visad.meteorology.ImageSequenceManager;
+import visad.meteorology.NavigatedImage;
+import visad.meteorology.ImageSequence;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.io.File;
+import visad.data.visad.VisADSerialForm;
+
+// JFC packages
+import javax.swing.*;
+
+// AWT packages
+import java.awt.*;
+import java.awt.event.*;
+
+public class Aeri
+       implements ScalarMapListener
+{
+  RealType latitude;
+  RealType longitude;
+  RealType altitude;
+
+  //- (lon,lat,alt)
+  //
+  RealTupleType spatial_domain;
+  RealType time;
+  RealType stn_idx;
+
+  RealType temp;
+  RealType dwpt;
+  RealType wvmr;
+  RealType RH;
+  RealType theta;
+  RealType thetaE;
+  RealType band1 = null;
+
+  //- (T,TD,WV,AGE)
+  //
+  RealTupleType advect_range;
+
+  FunctionType advect_type;
+  FunctionType advect_field_type;
+
+  FieldImpl advect_field;
+  FieldImpl stations_field;
+  ImageSequence image_seq;
+
+  int n_stations = 5;
+
+  double[] station_lat;
+  double[] station_lon;
+  double[] station_alt;
+  double[] station_id;
+
+  BaseMapAdapter baseMap;
+  DataReference map_ref;
+  DataReference img_ref;
+
+  ScalarMap xmap;
+  ScalarMap ymap;
+  ScalarMap zmap;
+  ScalarMap img_map;
+  boolean xmapEvent = false;
+  boolean ymapEvent = false;
+  boolean imgEvent = false;
+  boolean first_xy_Event = false;
+  boolean first_img_Event = false;
+
+  float latmin, latmax;
+  float lonmin, lonmax;
+  float del_lat, del_lon;
+  double[] x_range, y_range;
+
+  int height_limit = 3000; // meters
+  boolean rh = false;
+  boolean tm = false;
+  boolean pt = false;
+  boolean ept = false;
+
+  int start_date = 0;
+  double start_time = 0.0;
+
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException
+  {
+    Aeri aeri = new Aeri(args);
+  }
+
+  public Aeri(String[] args)
+         throws VisADException, RemoteException, IOException
+  {
+    String vadfile = null;
+    String baseDate = "20000112";
+    for (int i=0; i<args.length; i++) {
+      if (args[i] == null) {
+      }
+      else if (args[i].endsWith(".vad")) {
+        vadfile = args[i];
+      }
+      else if (args[i].equals("-limit") && (i+1) < args.length) {
+        try {
+          height_limit = Integer.parseInt(args[i+1]);
+        }
+        catch (NumberFormatException e) {
+          System.out.println("bad height limit: " + args[i+1]);
+        }
+        i++;
+      }
+      else if (args[i].equals("-date") && (i+1) < args.length) {
+        baseDate = args[i+1];
+        i++;
+      }
+      else if (args[i].equals("-rh")) {
+        rh = true;
+        tm = false;
+      }
+      else if (args[i].equals("-temp")) {
+        tm = true;
+        rh = false;
+      }
+      else if (args[i].equals("-theta")) {
+        pt = true;
+      }
+      else if (args[i].equals("-thetaE")) {
+        ept = true;
+      }
+    }
+    if (vadfile != null) {
+      init_from_vad(vadfile);
+    }
+    else {
+      init_from_cdf(baseDate);
+    }
+    wvmr.alias("MR");
+    temp.alias("T");
+
+    try {
+      String fs = System.getProperty("file.separator");
+      image_seq = init_images("."+fs+"data"+fs+"image"+fs+baseDate);
+      band1 = (RealType)
+         ((RealTupleType)
+          ((FunctionType)
+           ((FunctionType)image_seq.getType()).getRange()).getRange()).getComponent(0);
+    }
+    catch ( Exception e ) {
+   //-System.out.println(e.getMessage());
+      System.out.println("no AREA image data, proceeding...");
+      image_seq = null;
+    }
+
+
+    JFrame frame = new JFrame("VisAD AERI Viewer");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in frame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+    // panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    // panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    DisplayImpl display = makeDisplay(panel);
+
+    int WIDTH = 1000;
+    int HEIGHT = 600;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+  }
+
+  void init_from_cdf(String baseDate)
+       throws VisADException, RemoteException, IOException
+  {
+    station_lat = new double[n_stations];
+    station_lon = new double[n_stations];
+    station_alt = new double[n_stations];
+    station_id = new double[n_stations];
+
+    longitude = RealType.Longitude;
+    latitude = RealType.Latitude;
+    RH = RealType.getRealType("RH", SI.second);
+    stn_idx = RealType.getRealType("stn_idx");
+    theta = RealType.getRealType("theta");
+    thetaE = RealType.getRealType("thetaE");
+
+    String[] wind_files = new String[n_stations];
+    String[] rtvl_files = new String[n_stations];
+
+    String truncatedDate = baseDate;
+    wind_files[0] = "./data/" + baseDate + "_lamont_windprof.cdf";
+    wind_files[1] = "./data/" + baseDate + "_hillsboro_windprof.cdf";
+    wind_files[2] = "./data/" + baseDate + "_morris_windprof.cdf";
+    wind_files[3] = "./data/" + baseDate + "_purcell_windprof.cdf";
+    wind_files[4] = "./data/" + baseDate + "_vici_windprof.cdf";
+
+    File file = new File("./data/lamont_" + truncatedDate + "AP.cdf");
+
+    if (file.exists()) {
+      rtvl_files[0] = "./data/lamont_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[0] = "./data/lamont_" + truncatedDate + "AG.cdf";
+    }
+
+    file = new File("./data/hillsboro_" + truncatedDate + "AP.cdf");
+    if (file.exists()) {
+      rtvl_files[1] = "./data/hillsboro_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[1] = "./data/hillsboro_" + truncatedDate + "AG.cdf";
+    }
+
+    file = new File("./data/morris_" + truncatedDate + "AP.cdf");
+    if (file.exists()) {
+      rtvl_files[2] = "./data/morris_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[2] = "./data/morris_" + truncatedDate + "AG.cdf";
+    }
+
+    file = new File("./data/purcell_" + truncatedDate + "AP.cdf");
+    if (file.exists()) {
+      rtvl_files[3] = "./data/purcell_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[3] = "./data/purcell_" + truncatedDate + "AG.cdf";
+    }
+
+    file = new File("./data/vici_" + truncatedDate + "AP.cdf");
+    if (file.exists()) {
+      rtvl_files[4] = "./data/vici_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[4] = "./data/vici_" + truncatedDate + "AG.cdf";
+    }
+
+    FieldImpl[] winds = makeWinds(wind_files);
+       System.out.println(winds[0].getType().prettyString());
+
+    FieldImpl[] rtvls = makeAeri(rtvl_files);
+       System.out.println(rtvls[0].getType().prettyString());
+
+    spatial_domain = new RealTupleType(longitude, latitude, altitude);
+    RealType[] r_types = {temp, dwpt, wvmr, RH, theta, thetaE};
+    advect_range = new RealTupleType(r_types);
+    advect_type = new FunctionType(spatial_domain, advect_range);
+    advect_field_type = new FunctionType(RealType.Time, advect_type);
+
+    stations_field =
+        new FieldImpl(new FunctionType( stn_idx, advect_field_type),
+                                        new Integer1DSet( stn_idx, n_stations,
+                                                          null, null, null));
+
+    for ( int kk = 0; kk < n_stations; kk++ )
+    {
+      advect_field = makeAdvect(winds[kk], rtvls[kk], kk);
+      stations_field.setSample(kk, advect_field);
+    }
+
+    VisADSerialForm vad_form = new VisADSerialForm();
+    vad_form.save("aeri_winds_" + baseDate + "." +
+                  height_limit + ".vad", stations_field, true);
+
+       System.out.println(stations_field.getType().prettyString());
+  }
+
+  void init_from_vad( String vad_file )
+       throws VisADException, RemoteException, IOException
+  {
+    VisADSerialForm vad_form = new VisADSerialForm();
+    stations_field = (FieldImpl) vad_form.open( vad_file );
+
+    MathType file_type = stations_field.getType();
+
+    stn_idx = (RealType)
+      ((RealTupleType)((FunctionType)file_type).getDomain()).getComponent(0);
+    FunctionType f_type0 = (FunctionType) ((FunctionType)file_type).getRange();
+    time = (RealType) ((RealTupleType)f_type0.getDomain()).getComponent(0);
+    FunctionType f_type1 = (FunctionType) f_type0.getRange();
+    spatial_domain = (RealTupleType) f_type1.getDomain();
+    longitude = (RealType) spatial_domain.getComponent(0);
+    latitude = (RealType) spatial_domain.getComponent(1);
+    altitude = (RealType) spatial_domain.getComponent(2);
+
+    RealTupleType rtt = (RealTupleType) f_type1.getRange();
+    temp = (RealType) rtt.getComponent(0);
+    dwpt = (RealType) rtt.getComponent(1);
+    wvmr = (RealType) rtt.getComponent(2);
+    RH = (RealType) rtt.getComponent(3);
+    theta = (RealType) rtt.getComponent(4);
+    thetaE = (RealType) rtt.getComponent(5);
+  }
+
+  public static ImageSequence init_images(String image_directory)
+         throws VisADException, RemoteException, IOException
+  {
+    String fs = System.getProperty("file.separator");
+
+    if ( image_directory == null ) {
+   //-image_directory = "."+fs+"data"+fs+"image"+fs+"vis";
+      image_directory = "."+fs+"data"+fs+"image"+fs+"ir_display";
+   //-image_directory = "."+fs+"data"+fs+"image"+fs+"ir";
+    }
+
+    File file = new File(image_directory);
+    String[] image_files = file.list();
+    int n_images = image_files.length;
+    NavigatedImage[] nav_images = new NavigatedImage[n_images];
+
+    for ( int ii = 0; ii < n_images; ii++ ) {
+      AreaAdapter area = new AreaAdapter(image_directory+fs+image_files[ii]);
+      FlatField image = area.getData();
+      DateTime img_start = area.getImageStartTime();
+      nav_images[ii] = new NavigatedImage(image, img_start, "AREA");
+    }
+
+    ImageSequenceManager img_manager =
+      new ImageSequenceManager(nav_images);
+
+    return img_manager.getImageSequence();
+  }
+
+
+  DisplayImpl makeDisplay(JPanel panel)
+       throws VisADException, RemoteException, IOException
+  {
+    del_lon = 15f;
+    del_lat = 15f;
+
+    baseMap = new BaseMapAdapter("OUTLUSAM");
+    map_ref = new DataReferenceImpl("map");
+
+    if ( ! baseMap.isEastPositive() )
+    {
+      baseMap.setEastPositive(true);
+    }
+
+    //- make barber poles for each station
+    //
+    DataImpl poles = makePoles();
+    DataReference poles_ref = new DataReferenceImpl("poles");
+    poles_ref.setData(poles);
+
+    DisplayImpl display = new DisplayImplJ3D("aeri");
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    DisplayRenderer dr = display.getDisplayRenderer();
+    dr.setBoxOn(false);
+
+    xmap = new ScalarMap(longitude, Display.XAxis);
+    xmap.setScaleEnable(false);
+    ymap = new ScalarMap(latitude, Display.YAxis);
+    ymap.setScaleEnable(false);
+    zmap = new ScalarMap(altitude, Display.ZAxis);
+
+    display.addMap(xmap);
+    display.addMap(ymap);
+    display.addMap(zmap);
+    // note RH bonces around because temperature does
+    ScalarMap cmap = null;
+    if (rh) {
+      cmap = new ScalarMap(RH, Display.RGB);
+    }
+    else if (tm) {
+      cmap = new ScalarMap(temp, Display.RGB);
+    }
+    else if (pt) {
+      cmap = new ScalarMap(theta, Display.RGB);
+    }
+    else if (ept) {
+      cmap = new ScalarMap(thetaE, Display.RGB);
+    }
+    else {
+      cmap = new ScalarMap(wvmr, Display.RGB);
+    }
+    display.addMap(cmap);
+    ColorMapWidget cmw = new ColorMapWidget(cmap, null, true, false);
+    LabeledColorWidget cwidget = new LabeledColorWidget(cmw);
+    ScalarMap tmap = new ScalarMap(RealType.Time, Display.Animation);
+    display.addMap(tmap);
+    AnimationControl control = (AnimationControl) tmap.getControl();
+    control.setStep(50);
+    AnimationWidget awidget = new AnimationWidget(tmap);
+
+    if ( image_seq != null ) {
+      img_map = new ScalarMap(band1, Display.RGB);
+      img_map.addScalarMapListener(this);
+      display.addMap(img_map);
+      ColorControl cc = (ColorControl) img_map.getControl();
+      cc.initGreyWedge();
+    }
+
+    zmap.setRange(0.0, hgt_max);
+
+    DataReference advect_ref = new DataReferenceImpl("advect_ref");
+    advect_ref.setData(stations_field);
+
+    ConstantMap[] map_constMap =
+      new ConstantMap[]
+    {
+      new ConstantMap(1., Display.Red),
+      new ConstantMap(1., Display.Green),
+      new ConstantMap(1., Display.Blue),
+      new ConstantMap(-.98, Display.ZAxis)
+    };
+
+    ConstantMap[] img_constMap =
+      new ConstantMap[] {new ConstantMap(-.99, Display.ZAxis)};
+
+    img_ref = new DataReferenceImpl("image");
+
+    display.disableAction();
+    display.addReference(poles_ref);
+    display.addReference(advect_ref);
+    display.addReference(map_ref, map_constMap);
+    if ( image_seq != null ) {
+      display.addReference(img_ref, img_constMap);
+    }
+
+    xmap.addScalarMapListener(this);
+    ymap.addScalarMapListener(this);
+    display.enableAction();
+    JPanel dpanel = new JPanel();
+    dpanel.setLayout(new BoxLayout(dpanel, BoxLayout.Y_AXIS));
+    dpanel.add(display.getComponent());
+    JPanel wpanel = new JPanel();
+    wpanel.setLayout(new BoxLayout(wpanel, BoxLayout.Y_AXIS));
+    cwidget.setMaximumSize(new Dimension(400, 200));
+    awidget.setMaximumSize(new Dimension(400, 400));
+    wpanel.add(cwidget);
+    wpanel.add(awidget);
+    Dimension d = new Dimension(400, 600);
+    wpanel.setMaximumSize(d);
+    panel.add(wpanel);
+    panel.add(dpanel);
+    return display;
+  }
+
+  double lon_min = Double.MAX_VALUE;
+  double lon_max = -Double.MAX_VALUE;
+  double lat_min = Double.MAX_VALUE;
+  double lat_max = -Double.MAX_VALUE;
+  double hgt_max = -Double.MAX_VALUE;
+
+  DataImpl makePoles()
+           throws VisADException, RemoteException
+  {
+    SampledSet[] set_s = new SampledSet[n_stations];
+    int ii = 0;
+    float[][] locs = new float[3][2];
+
+    for ( int kk = 0; kk < n_stations; kk++ ) {
+      boolean any = false;
+      float hgt = -Float.MAX_VALUE;
+      FieldImpl station = (FieldImpl) stations_field.getSample(kk);
+      if (station == null) continue;
+      int len = station.getLength();
+      for (int i=0; i<len; i++) {
+        FieldImpl pole = (FieldImpl) station.getSample(i);
+        if (pole == null || pole.getLength() < 2) continue;
+        Set set = pole.getDomainSet();
+        float[][] samples = set.getSamples(false);
+        // float[] lo = ((SampledSet)set).getLow();
+        float[] hi = ((SampledSet)set).getHi();
+        if (hi[2] > hgt) hgt = hi[2];
+        if (!any && samples[0][0] == samples[0][0] &&
+            samples[1][0] == samples[1][0]) {
+          any = true;
+
+          locs[0][0] = samples[0][0];
+          locs[1][0] = samples[1][0];
+          locs[0][1] = samples[0][0];
+          locs[1][1] = samples[1][0];
+
+          if (samples[0][0] > lat_max) lat_max = samples[0][0];
+          if (samples[0][0] < lat_min) lat_min = samples[0][0];
+          if (samples[1][0] > lon_max) lon_max = samples[1][0];
+          if (samples[1][0] < lon_min) lon_min = samples[1][0];
+        }
+      }
+      if (any) {
+        locs[2][0] = 0.0f;
+        locs[2][1] = hgt;
+        set_s[ii++] = new Gridded3DSet(spatial_domain, locs, 2, null, null, null);
+// System.out.println("set_s[" + kk + "] = " + set_s[kk]);
+        if (hgt > hgt_max) hgt_max = hgt;
+      }
+    }
+    SampledSet[] set_ss = new SampledSet[ii];
+    System.arraycopy(set_s, 0, set_ss, 0, ii);
+    return new UnionSet(spatial_domain, set_ss);
+  }
+
+  public void mapChanged(ScalarMapEvent e)
+       throws VisADException, RemoteException
+  {
+    if ( xmap.equals(e.getScalarMap()) ) {
+      xmapEvent = true;
+    }
+    else if ( ymap.equals(e.getScalarMap()) ) {
+      ymapEvent = true;
+    }
+    else if ( img_map.equals(e.getScalarMap()) ) {
+      imgEvent = true;
+    }
+    if (( xmapEvent && ymapEvent ) && !(first_xy_Event) ) {
+      x_range = xmap.getRange();
+      y_range = ymap.getRange();
+      latmin = (float)y_range[0];
+      latmax = (float)y_range[1];
+      lonmin = (float)x_range[0];
+      lonmax = (float)x_range[1];
+      baseMap.setLatLonLimits(latmin-del_lat, latmax+del_lat,
+                              lonmin-del_lon, lonmax+del_lon);
+      DataImpl map = baseMap.getData();
+      map_ref.setData(map);
+      first_xy_Event = true;
+      xmap.setRange(lonmin, lonmax);
+      ymap.setRange(latmin, latmax);
+      img_ref.setData(image_seq);
+    }
+    if ( imgEvent && !first_img_Event ) {
+      double[] i_range = img_map.getRange();
+      System.out.println(i_range[0]+" "+i_range[1]);
+      first_img_Event = true;
+      img_map.setRange(i_range[1], i_range[0]);
+    }
+  }
+
+  public void controlChanged(ScalarMapControlEvent e)
+  {
+  }
+
+  FieldImpl[] makeWinds(String[] files)
+              throws VisADException, RemoteException, IOException
+  {
+    DataImpl[] file_data = new DataImpl[n_stations];
+    FieldImpl[] time_field = new FieldImpl[n_stations];
+    double[][] time_offset = null;
+    double[] base_date = new double[n_stations];
+    double[] base_time = new double[n_stations];
+    Gridded1DSet d_set = null;
+    FlatField new_ff = null;
+
+    RealType alt;
+    RealType spd;
+    RealType dir;
+
+    RealType u_wind = RealType.getRealType("u_wind");
+    RealType v_wind = RealType.getRealType("v_wind");
+
+    //- create a new netcdf reader
+    Plain plain = new Plain();
+
+    //- retrieve file data objects
+    for ( int kk = 0; kk < n_stations; kk++) {
+      file_data[kk] = plain.open(files[kk]);
+    }
+
+    //- make sub mathtype for file objects
+    MathType file_type = file_data[0].getType();
+    System.out.println(file_type.prettyString());
+    System.out.println();
+
+    FunctionType f_type0 =
+      (FunctionType)((TupleType)file_type).getComponent(2);
+
+    TupleType ttype = (TupleType) f_type0.getRange();
+    int p_field_index = ttype.getDimension() - 1;
+    FunctionType f_type1 =
+  //- (FunctionType)((TupleType)f_type0.getRange()).getComponent(10);
+  //- (FunctionType)((TupleType)f_type0.getRange()).getComponent(12);
+      (FunctionType) ttype.getComponent(p_field_index);
+
+    RealTupleType rng_tuple = (RealTupleType) f_type1.getRange();
+    int altitude_index = rng_tuple.getIndex("Altitude");
+ //-altitude = (RealType)((RealTupleType)f_type1.getRange()).getComponent(0);
+ // altitude = (RealType)((RealTupleType)f_type1.getRange()).getComponent(1);
+    altitude = (RealType) rng_tuple.getComponent(altitude_index);
+    int windSpeed_index = rng_tuple.getIndex("windSpeed");
+ //-spd = (RealType)((RealTupleType)f_type1.getRange()).getComponent(4);
+ //-spd = (RealType)((RealTupleType)f_type1.getRange()).getComponent(5);
+    spd = (RealType) rng_tuple.getComponent(windSpeed_index);
+    int windDir_index = rng_tuple.getIndex("windDir");
+ //-dir = (RealType)((RealTupleType)f_type1.getRange()).getComponent(3);
+ //-dir = (RealType)((RealTupleType)f_type1.getRange()).getComponent(4);
+    dir = (RealType) rng_tuple.getComponent(windDir_index);
+    RealType[] r_types = { dir, spd };
+
+    /* WLH 28 Dec 99 */
+    RealType[] uv_types = { u_wind, v_wind };
+    // EarthVectorType uv = new EarthVectorType(uv_types); WLH meeds m/s
+    RealTupleType uv = new RealTupleType(uv_types);
+    CoordinateSystem cs = new WindPolarCoordinateSystem(uv);
+    RealTupleType ds = new RealTupleType(r_types, cs, null);
+
+
+    FunctionType alt_to_ds = new FunctionType(altitude, ds);
+    FunctionType alt_to_uv = new FunctionType(altitude, uv);
+
+    RealType domain_type = (RealType)
+      ((TupleType)f_type0.getRange()).getComponent(0);
+    time = domain_type;
+    FunctionType new_type = new FunctionType(RealType.Time, alt_to_uv);
+
+    FieldImpl[] winds = new FieldImpl[n_stations];
+
+    for ( int ii = 0; ii < n_stations; ii++ )
+    {
+      base_date[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(0)).getValue();
+      base_time[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(1)).getValue();
+      time_field[ii] = (FieldImpl)
+        ((Tuple)file_data[ii]).getComponent(2);
+      station_lat[ii] =
+        ((Real)((Tuple)time_field[ii].getSample(0)).getComponent(6)).getValue();
+      station_lon[ii] =
+       -((Real)((Tuple)time_field[ii].getSample(0)).getComponent(7)).getValue();
+      station_alt[ii] =
+        ((Real)((Tuple)time_field[ii].getSample(0)).getComponent(8)).getValue();
+      station_id[ii] =
+        ((Real)((Tuple)time_field[ii].getSample(0)).getComponent(9)).getValue();
+      if (ii == 0) {
+        start_time = base_time[0];
+        start_date = (int) base_date[0];
+      }
+
+/*
+System.out.println("wind " + ii + " date: " + base_date[ii] +
+                   " time: " + base_time[ii]);
+wind 0 date: 1.9991226E7 time: 9.461664E8
+wind 1 date: 1.9991226E7 time: 9.461664E8
+wind 2 date: 1.9991226E7 time: 9.461664E8
+wind 3 date: 1.9991226E7 time: 9.461664E8
+wind 4 date: 1.9991226E7 time: 9.461664E8
+
+looks like 19991226 and seconds since 1-1-70
+time_offset looks like seconds since 0Z
+*/
+
+      int length = time_field[ii].getLength();
+      time_offset = new double[1][length];
+      FlatField[] range_data = new FlatField[length];
+
+      double[][] samples = null; // WLH
+      for ( int jj = 0; jj < length; jj++ )
+      {
+        Tuple range = (Tuple) time_field[ii].getSample(jj);
+        time_offset[0][jj] = (double)((Real)range.getComponent(0)).getValue();
+
+     //-FlatField p_field = (FlatField) range.getComponent(10);
+     //-FlatField p_field = (FlatField) range.getComponent(12);
+        FlatField p_field = (FlatField) range.getComponent(p_field_index);
+        double[][] values =
+          p_field.getValues(); // WLH - (alt, ?, ?, dir, spd, ???)
+        double[][] new_values = new double[2][values[0].length];
+
+        if ( jj == 0 )  //- only once, vertical range gates don't change
+        {
+          samples = new double[1][values[0].length]; // WLH
+       //-System.arraycopy(values[0], 0, samples[0], 0, samples[0].length);
+       //-System.arraycopy(values[1], 0, samples[0], 0, samples[0].length);
+          System.arraycopy(values[altitude_index], 0, samples[0], 0, samples[0].length);
+          d_set = new Gridded1DSet(altitude, Set.doubleToFloat(samples), samples[0].length);
+        }
+        new_ff = new FlatField(alt_to_uv, d_set);
+
+/* WLH - fill in missing winds - this also extrapolates to missing
+start or end altitudes - but it does nothing if all winds are missing */
+        int n_not_miss = 0;
+        int[] not_miss = new int[values[0].length];
+        for ( int mm = 0; mm < values[0].length; mm++ )
+        {
+       //-if ( values[3][mm] <= -9999 ) {
+       //-if ( values[4][mm] <= -9999 ) {
+          if ( values[windDir_index][mm] <= -9999 ) {
+            new_values[0][mm] = Float.NaN;
+          }
+          else {
+         //-new_values[0][mm] = values[3][mm];
+         //-new_values[0][mm] = values[4][mm];
+            new_values[0][mm] = values[windDir_index][mm];
+          }
+
+       //-if ( values[4][mm] <= -9999 ) {
+       //-if ( values[5][mm] <= -9999 ) {
+          if ( values[windSpeed_index][mm] <= -9999 ) {
+            new_values[1][mm] = Float.NaN;
+          }
+          else {
+         //-new_values[1][mm] = values[4][mm];
+         //-new_values[1][mm] = values[5][mm];
+            new_values[1][mm] = values[windSpeed_index][mm];
+          }
+
+          if (new_values[0][mm] == new_values[0][mm] &&
+              new_values[1][mm] == new_values[1][mm]) {
+            not_miss[n_not_miss] = mm;
+            n_not_miss++;
+          }
+        }
+        if (0 < n_not_miss && n_not_miss < values[0].length) {
+          int nn = n_not_miss;
+          if (not_miss[0] > 0) nn += not_miss[0];
+          int endlen = values[0].length - (not_miss[n_not_miss-1]+1);
+          if (endlen > 0) nn += endlen;
+
+          float[][] newer_values = new float[2][nn];
+          float[][] newer_samples = new float[1][nn];
+          // fill in non-missing values
+          for (int i=0; i<n_not_miss; i++) {
+            newer_values[0][not_miss[0] + i] =
+              (float) new_values[0][not_miss[i]];
+            newer_values[1][not_miss[0] + i] =
+              (float) new_values[1][not_miss[i]];
+            newer_samples[0][not_miss[0] + i] =
+              (float) samples[0][not_miss[i]];
+          }
+          // extrapolate if necessary for starting values
+          for (int i=0; i<not_miss[0]; i++) {
+            newer_values[0][i] = (float) new_values[0][not_miss[0]];
+            newer_values[1][i] = (float) new_values[1][not_miss[0]];
+            newer_samples[0][i] = (float) samples[0][not_miss[0]];
+          }
+          // extrapolate if necessary for ending values
+          for (int i=0; i<endlen; i++) {
+            newer_values[0][not_miss[0] + n_not_miss + i] =
+              (float) new_values[0][not_miss[n_not_miss-1]];
+            newer_values[1][not_miss[0] + n_not_miss + i] =
+              (float) new_values[1][not_miss[n_not_miss-1]];
+            newer_samples[0][not_miss[0] + n_not_miss + i] =
+              (float) samples[0][not_miss[n_not_miss-1]];
+          }
+          Gridded1DSet newer_d_set =
+            new Gridded1DSet(altitude, newer_samples, nn);
+          FlatField newer_ff = new FlatField(alt_to_uv, newer_d_set);
+          newer_ff.setSamples(cs.toReference(newer_values));
+          new_ff = (FlatField) newer_ff.resample(d_set,
+                                                 Data.WEIGHTED_AVERAGE,
+                                                 Data.NO_ERRORS );
+        }
+        else {
+          new_ff.setSamples(cs.toReference(new_values));
+        }
+/* end WLH - fill in missing winds */
+
+        range_data[jj] = new_ff;
+      }
+
+
+      double[][] times = new double[1][length];
+      for (int i=0; i<length; i++) {
+        times[0][i] = base_time[0] + time_offset[0][i];
+      }
+      Gridded1DSet domain_set =
+        new Gridded1DSet(RealType.Time, Set.doubleToFloat(times), length);
+
+      winds[ii] = new FieldImpl(new_type, domain_set);
+      winds[ii].setSamples(range_data, false);
+    }
+    plain = null;
+    return winds;
+  }
+
+  FieldImpl[] makeAeri(String[] files)
+              throws VisADException, RemoteException, IOException
+  {
+    DataImpl[] file_data = new DataImpl[n_stations];
+    FieldImpl[] time_field = new FieldImpl[n_stations];
+    double[] station_lat = new double[n_stations];
+    double[] station_lon = new double[n_stations];
+    double[] station_alt = new double[n_stations];
+    double[] station_id = new double[n_stations];
+    double[][] time_offset = null;
+    double[] base_date = new double[n_stations];
+    double[] base_time = new double[n_stations];
+
+    //- create a new netcdf reader
+    Plain plain = new Plain();
+
+    //- retrieve file data objects
+    for ( int kk = 0; kk < n_stations; kk++ ) {
+      file_data[kk] = plain.open(files[kk]);
+    }
+    System.out.println(file_data[0].getType().prettyString());
+
+    //- make sub mathtype for file objects
+    MathType file_type = file_data[0].getType();
+    FunctionType f_type0 = (FunctionType)
+      ((TupleType)file_type).getComponent(1);
+    FunctionType f_type1 = (FunctionType)
+      ((TupleType)f_type0.getRange()).getComponent(1);
+
+    RealTupleType rtt = (RealTupleType) f_type1.getRange();
+    temp = (RealType) rtt.getComponent(1);
+    dwpt = (RealType) rtt.getComponent(2);
+    wvmr = (RealType) rtt.getComponent(3);
+
+
+    RealType domain_type = (RealType)
+      ((TupleType)f_type0.getRange()).getComponent(0);
+    FunctionType new_type = new FunctionType(RealType.Time, f_type1);
+
+    FieldImpl[] rtvls = new FieldImpl[n_stations];
+
+
+    for ( int ii = 0; ii < n_stations; ii++ )
+    {
+      base_time[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(0)).getValue();
+      time_field[ii] = (FieldImpl) ((Tuple)file_data[ii]).getComponent(1);
+      base_date[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(2)).getValue();
+/*
+System.out.println("aeri " + ii + " date: " + base_date[ii] +
+                   " time: " + base_time[ii]);
+aeri 0 date: 991226.0 time: 9.46166724E8
+aeri 1 date: 991226.0 time: 9.46167982E8
+aeri 2 date: 991226.0 time: 9.4616806E8
+aeri 3 date: 991226.0 time: 9.46168061E8
+aeri 4 date: 991226.0 time: 9.4616807E8
+
+looks like 991226 and seconds since 1-1-70
+time_offset looks like seconds since 0Z
+*/
+
+      int length = time_field[ii].getLength();
+      time_offset = new double[1][length];
+      Data[] range_data = new Data[length];
+
+      for ( int jj = 0; jj < length; jj++ )
+      {
+        Tuple range = (Tuple) time_field[ii].getSample(jj);
+        time_offset[0][jj] = (double)((Real)range.getComponent(0)).getValue();
+
+        FlatField p_field = (FlatField) range.getComponent(1);
+        double[][] values = p_field.getValues();
+        double[][] new_values = new double[4][values[0].length];
+
+        for ( int mm = 0; mm < values[0].length; mm++ )
+        {
+          if ( values[0][mm] == -9999 ) {
+            new_values[0][mm] = Float.NaN;
+          }
+          else {
+            new_values[0][mm] = values[0][mm];
+          }
+
+          if ( values[1][mm] == -9999 ) {
+            new_values[1][mm] = Float.NaN;
+          }
+          else {
+            new_values[1][mm] = values[1][mm];
+          }
+
+          if ( values[2][mm] == -9999 ) {
+            new_values[2][mm] = Float.NaN;
+          }
+          else {
+            new_values[2][mm] = values[2][mm];
+          }
+
+          if ( values[3][mm] == -9999 ) {
+            new_values[3][mm] = Float.NaN;
+          }
+          else {
+            new_values[3][mm] = values[3][mm];
+          }
+        }
+        p_field.setSamples(new_values);
+        if (!f_type1.equals(p_field.getType())) {
+          p_field = (FlatField) p_field.changeMathType(f_type1);
+        }
+        range_data[jj] = p_field;
+      }
+
+      double[][] times = new double[1][length];
+      for (int i=0; i<length; i++) {
+        times[0][i] = base_time[0] + time_offset[0][i];
+      }
+      Gridded1DSet domain_set =
+        new Gridded1DSet(RealType.Time, Set.doubleToFloat(times), length);
+
+      rtvls[ii] = new FieldImpl(new_type, domain_set);
+      rtvls[ii].setSamples(range_data, false);
+    }
+    return rtvls;
+  }
+
+  FieldImpl makeAdvect( FieldImpl winds, FieldImpl rtvls, int stn_idx )
+            throws VisADException, RemoteException, IOException
+  {
+    float wind_time;
+    float[][] value_s = new float[1][1];
+    int[] index_s = new int[1];
+    int rtvl_idx;
+ //-FlatField alt_to_wind;
+    FieldImpl alt_to_wind;
+    FieldImpl wind_to_rtvl_time;
+ //-FlatField alt_to_rtvl;
+    FieldImpl alt_to_rtvl;
+    FlatField advect;
+    FieldImpl advect_field;
+    FlatField[] rtvl_on_wind;
+    FieldImpl[] wind_on_wind;
+    int n_samples;
+    double age;
+    double age_max = 3600;
+    double rtvl_intvl;
+    double rtvl_time;
+    double rtvl_time_0;
+    double rtvl_intvl_min = 476;
+    int n_advect_pts = 10;
+    int n_levels_max = 65;
+    float[][] advect_locs = new float[3][n_advect_pts*n_levels_max];
+    float[][] rtvl_vals = new float[6][n_advect_pts*n_levels_max];
+    CoordinateSystem cs;
+    double[][] dir_spd;
+    double[][] uv_wind;
+    int idx = 0;
+    float alt;
+    Set rtvls_domain;
+    float[] rtvl_times;
+    double factor = .5*(1.0/111000.0);  //-knt to ms, m to degree
+    factor *= 1.10;
+
+    //- time(rtvl) -> (lon,lat,alt) -> (T,TD,WV,AGE)
+    //
+    advect_field = new FieldImpl(advect_field_type, rtvls.getDomainSet());
+
+    //- resample winds domain (time) to rtvls
+    //
+
+    rtvls_domain = rtvls.getDomainSet();
+    wind_to_rtvl_time = (FieldImpl)
+      winds.resample( rtvls_domain,
+                      Data.WEIGHTED_AVERAGE,
+                      Data.NO_ERRORS );
+
+
+    //- resample rtvls domain (altitude) to winds at each rtvl time
+    //
+    int len = rtvls.getLength();
+    rtvl_on_wind = new FlatField[len];
+    wind_on_wind = new FieldImpl[len];
+    for ( int tt = 0; tt < len; tt++ ) {
+      alt_to_rtvl = (FieldImpl) rtvls.getSample(tt);
+      alt_to_wind = (FieldImpl) wind_to_rtvl_time.getSample(tt);
+      Set ds = alt_to_wind.getDomainSet();
+// WLH height limit
+      float[][] samples = ds.getSamples();
+      float[] ns = new float[samples[0].length];
+      int nn = 0;
+      for (int i=0; i<samples[0].length; i++) {
+        if (samples[0][i] < height_limit) {
+          ns[nn] = samples[0][i];
+          nn++;
+        }
+      }
+      float[][] new_samples = new float[1][nn];
+      System.arraycopy(ns, 0, new_samples[0], 0, nn);
+      ds = new Gridded1DSet(ds.getType(), new_samples, nn);
+      wind_on_wind[tt] = (FieldImpl)
+        alt_to_wind.resample( ds,
+                              Data.WEIGHTED_AVERAGE,
+                              Data.NO_ERRORS );
+// end WLH height limit
+      rtvl_on_wind[tt] = (FlatField)
+        alt_to_rtvl.resample( ds,
+                              Data.WEIGHTED_AVERAGE,
+                              Data.NO_ERRORS );
+    }
+
+    //- get rtvls time domain samples
+    //
+    float[][] f_array = rtvls_domain.getSamples();
+    rtvl_times = f_array[0];
+
+    //- loop over rtvl sampling in time   -*
+    //
+    for ( int tt = n_advect_pts; tt < len; tt++ )
+    {
+      rtvl_idx = tt;
+      // alt_to_wind = (FieldImpl)wind_to_rtvl_time.getSample(tt);
+      alt_to_wind = wind_on_wind[tt];
+      int alt_len = alt_to_wind.getLength();
+
+      uv_wind = alt_to_wind.getValues();
+
+      //- get wind data height sampling
+      //
+      float[][] heights = alt_to_wind.getDomainSet().getSamples();
+
+      n_samples = 0;
+      rtvl_time_0 = rtvl_times[tt];
+
+      //- loop over wind profiler vertical range  -*
+      //
+      for ( int jj = 0; jj < alt_len; jj++ )
+      {
+        alt = heights[0][jj];
+
+        //- loop over rtvl time samples   -*
+        //
+        for ( int ii = 0; ii < n_advect_pts; ii++ )
+        {
+          rtvl_time = rtvl_times[rtvl_idx - ii];
+          age = rtvl_time - rtvl_time_0;
+
+// NOTE wind dir is direction wind is from
+// AND U is positive east (call to baseMap.setEastPositive(false)
+// makes this positive west) and V is positive north
+//
+// adjust for shortening of longitude with increasing latitude
+          double lat_radians = (Math.PI/180.0)*station_lat[stn_idx];
+          advect_locs[0][n_samples] = (float)
+            (-uv_wind[0][jj]*age*factor/Math.cos(lat_radians) +
+         //- Math.abs(station_lon[stn_idx]));
+             station_lon[stn_idx]);
+          advect_locs[1][n_samples] = (float)
+            (-uv_wind[1][jj]*age*factor + station_lat[stn_idx]);
+
+          advect_locs[2][n_samples] = alt;
+
+          double[][] vals = rtvl_on_wind[rtvl_idx - ii].getValues();
+
+          rtvl_vals[0][n_samples] = (float) vals[1][jj];
+          rtvl_vals[1][n_samples] = (float) vals[2][jj];
+          rtvl_vals[2][n_samples] = (float) vals[3][jj];
+
+          rtvl_vals[3][n_samples] = (float)
+            relativeHumidity(vals[1][jj], vals[2][jj]);
+
+          rtvl_vals[4][n_samples] = (float)
+            potentialTemperature(vals[1][jj], vals[0][jj]);
+
+          rtvl_vals[5][n_samples] = (float)
+            equivPotentialTemperature(rtvl_vals[4][n_samples],
+                                      vals[1][jj], vals[0][jj]);
+
+          n_samples++;
+        }
+      }
+      int lengthX = n_samples/alt_len;
+      int lengthY = alt_len;
+
+      float[][] samples = new float[3][lengthX*lengthY];
+      System.arraycopy(advect_locs[0], 0, samples[0], 0, n_samples);
+      System.arraycopy(advect_locs[1], 0, samples[1], 0, n_samples);
+      System.arraycopy(advect_locs[2], 0, samples[2], 0, n_samples);
+
+      float[][] range = new float[6][n_samples];
+      System.arraycopy(rtvl_vals[0], 0, range[0], 0, n_samples);
+      System.arraycopy(rtvl_vals[1], 0, range[1], 0, n_samples);
+      System.arraycopy(rtvl_vals[2], 0, range[2], 0, n_samples);
+      System.arraycopy(rtvl_vals[3], 0, range[3], 0, n_samples);
+      System.arraycopy(rtvl_vals[4], 0, range[4], 0, n_samples);
+      System.arraycopy(rtvl_vals[5], 0, range[5], 0, n_samples);
+
+      Gridded3DSet g3d_set =
+         new Gridded3DSet(spatial_domain, samples, lengthX, lengthY);
+
+      advect = new FlatField(advect_type, g3d_set);
+      advect.setSamples(range);
+      advect_field.setSample(tt, advect, false);
+    }
+    return advect_field;
+  }
+
+  /** saturation vapor pressure over water.  t in kelvin.
+  *
+  */
+  public static double satVapPres(double t) {
+    double coef[]={6.1104546,0.4442351,1.4302099e-2, 2.6454708e-4,
+              3.0357098e-6, 2.0972268e-8, 6.0487594e-11,-1.469687e-13};
+
+    // sat vap pressures every 5C from -50 to -200
+    double escold[] = {
+      0.648554685769663908E-01, 0.378319512256073479E-01,
+      0.222444934288790197E-01, 0.131828928424683120E-01,
+      0.787402077141244848E-02, 0.473973049488473318E-02,
+      0.287512035504357928E-02, 0.175743037675810294E-02,
+      0.108241739518850975E-02, 0.671708939185605941E-03,
+      0.419964702632039404E-03, 0.264524363863469876E-03,
+      0.167847963736813220E-03, 0.107285397631620379E-03,
+      0.690742634496135612E-04, 0.447940489768084267E-04,
+      0.292570419563937303E-04, 0.192452912634994161E-04,
+      0.127491372410747951E-04, 0.850507010275505138E-05,
+      0.571340025334971129E-05, 0.386465029673876238E-05,
+      0.263210971965005286E-05, 0.180491072930570428E-05,
+      0.124607850555816049E-05, 0.866070571346870824E-06,
+      0.605982217668895538E-06, 0.426821197943242768E-06,
+      0.302616508514379476E-06, 0.215963854234913987E-06,
+      0.155128954578336869E-06};
+
+    double temp = t - 273.16;
+    double retval;
+
+    if (temp != temp) {
+      retval = Double.NaN;
+    }
+    else if (temp > -50.) {
+      retval = ( coef[0] + temp*(coef[1] + temp*(coef[2] + temp*(coef[3] +
+      temp*(coef[4] + temp*(coef[5] + temp*(coef[6] + temp*coef[7])))))) );
+    }
+    else {
+       double tt = (-temp - 50.)/5.;
+       int inx = (int) tt;
+       if (inx < escold.length) {
+         retval = escold[inx] + (tt % 1.)*(escold[inx+1]-escold[inx]);
+       } else {
+         retval = 1e-7;
+       }
+    }
+    return retval;
+  }
+
+  /** mixing ratio
+  *
+  */
+  public static double mixingRatio(double t, double p) {
+    double e = satVapPres(t);
+    return ( 621.97*e/(p - e) );
+  }
+
+  /** relative humidity
+  *
+  */
+  public static double relativeHumidity(double t, double td) {
+    return (satVapPres(td) / satVapPres(t));
+  }
+
+  public static double potentialTemperature( double t, double p ) 
+  {
+    double R_Cp = 0.28585;
+    double Ps = 1000;  //- reference pressure, (mb)
+
+    return t*Math.pow((Ps/p), R_Cp);
+  }
+
+  public static double equivPotentialTemperature(double theta, 
+                                                 double temp,
+                                                 double press )
+  {
+    double Lc = 2.5*1000000.0;
+    double Cp = 1004.0;
+    double thetaE;
+    double Qs;
+
+    Qs = mixingRatio(temp, press*100);
+    Qs = Qs/1000.0;
+    thetaE = theta*Math.exp((Lc*Qs)/(Cp*temp));
+    return thetaE;
+  }
+
+  public static double equivPotentialTemperatureStar( double theta,
+                                                      double Q,
+                                                      double temp )
+  {
+    double Lc = 2.5*1000000.0;
+    double Cp = 1004.0;
+
+    Q = Q/1000.0;
+    return theta*Math.exp((Lc*Q)/(Cp*temp));
+  }
+}
diff --git a/visad/aeri/LinearVectorPointMethod.java b/visad/aeri/LinearVectorPointMethod.java
new file mode 100644
index 0000000..c4d6c66
--- /dev/null
+++ b/visad/aeri/LinearVectorPointMethod.java
@@ -0,0 +1,265 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.aeri;
+
+import visad.*;
+import visad.matrix.*;
+import visad.data.hdfeos.PolarStereographic;
+
+import java.lang.reflect.*;
+
+public class LinearVectorPointMethod
+{
+  JamaMatrix jm_A;
+  double scale_1;
+  double scale_2;
+  double scale_3;
+  double[][] centroid_ll;
+
+  public LinearVectorPointMethod(double[][] lonlat_s)
+         throws VisADException
+  {
+    if ( lonlat_s[0].length != 3 ) {
+      throw new VisADException("number of points must equal 3");
+    }
+
+    scale_1 = 1;
+    scale_2 = (1+Math.sin(lonlat_s[1][0]))/(1+Math.sin(lonlat_s[1][1]));
+    scale_3 = (1+Math.sin(lonlat_s[1][0]))/(1+Math.sin(lonlat_s[1][2]));
+
+    CoordinateSystem cs = new PolarStereographic(lonlat_s[0][0], lonlat_s[1][0]);
+    double[][] verts_xy = cs.fromReference(lonlat_s);
+
+    double[][] del_xy = new double[2][lonlat_s[0].length];
+    double[] centroid_xy = triangleCentroid(verts_xy);
+
+    centroid_ll = cs.toReference(new double[][] 
+                                                { {centroid_xy[0]}, {centroid_xy[1]} });
+    System.out.println("centroid lon: "+centroid_ll[0][0]*Data.RADIANS_TO_DEGREES);
+    System.out.println("centroid lat: "+centroid_ll[1][0]*Data.RADIANS_TO_DEGREES);
+
+    double scale_c = (1+Math.sin(lonlat_s[1][0]))/(1+Math.sin(centroid_ll[1][0]));
+    double scale_c_squared = scale_c*scale_c;
+
+    del_xy[0][0] = verts_xy[0][0] - centroid_xy[0];
+    del_xy[1][0] = verts_xy[1][0] - centroid_xy[1];
+
+    del_xy[0][1] = verts_xy[0][1] - centroid_xy[0];
+    del_xy[1][1] = verts_xy[1][1] - centroid_xy[1];
+
+    del_xy[0][2] = verts_xy[0][2] - centroid_xy[0];
+    del_xy[1][2] = verts_xy[1][2] - centroid_xy[1];
+
+    double[][] X_values = new double[6][6];
+
+    X_values[0][0] = 1;
+    X_values[0][1] = 0;
+    X_values[0][2] = 1;
+    X_values[0][3] = 0;
+    X_values[0][4] = 1;
+    X_values[0][5] = 0;
+
+    X_values[1][0] = 0;
+    X_values[1][1] = 1;
+    X_values[1][2] = 0;
+    X_values[1][3] = 1;
+    X_values[1][4] = 0;
+    X_values[1][5] = 1;
+
+    X_values[2][0] = del_xy[0][0]/scale_c_squared;
+    X_values[2][1] = -del_xy[1][0]/scale_c_squared;
+    X_values[2][2] = del_xy[0][1]/scale_c_squared;
+    X_values[2][3] = -del_xy[1][1]/scale_c_squared;
+    X_values[2][4] = del_xy[0][2]/scale_c_squared;
+    X_values[2][5] = -del_xy[1][2]/scale_c_squared;
+
+    X_values[3][0] = del_xy[1][0]/scale_c_squared;
+    X_values[3][1] = del_xy[0][0]/scale_c_squared;
+    X_values[3][2] = del_xy[1][1]/scale_c_squared;
+    X_values[3][3] = del_xy[0][1]/scale_c_squared;
+    X_values[3][4] = del_xy[1][2]/scale_c_squared;
+    X_values[3][5] = del_xy[0][2]/scale_c_squared;
+
+    X_values[4][0] = del_xy[0][0]/scale_c_squared;
+    X_values[4][1] = del_xy[1][0]/scale_c_squared;
+    X_values[4][2] = del_xy[0][1]/scale_c_squared;
+    X_values[4][3] = del_xy[1][1]/scale_c_squared;
+    X_values[4][4] = del_xy[0][2]/scale_c_squared;
+    X_values[4][5] = del_xy[1][2]/scale_c_squared;
+
+
+    X_values[5][0] = -del_xy[1][0]/scale_c_squared;
+    X_values[5][1] = del_xy[0][0]/scale_c_squared;
+    X_values[5][2] = -del_xy[1][1]/scale_c_squared;
+    X_values[5][3] = del_xy[0][1]/scale_c_squared;
+    X_values[5][4] = -del_xy[1][2]/scale_c_squared;
+    X_values[5][5] = del_xy[0][2]/scale_c_squared;
+
+    try {
+      jm_A = new JamaMatrix(X_values);
+      jm_A = jm_A.transpose();
+    }
+    catch (IllegalAccessException e) {
+    }
+    catch (InstantiationException e) {
+    }
+    catch (InvocationTargetException e) {
+    }
+  }
+
+  public double[][] getCentroid() 
+  {
+    return centroid_ll;
+  }
+    
+  public double[] getKinematics( double[][] uv_wind )
+         throws VisADException
+  {
+    double[][] values = new double[1][6];
+
+    values[0][0] = uv_wind[0][0]/scale_1;
+    values[0][1] = uv_wind[1][0]/scale_1;
+    values[0][2] = uv_wind[0][1]/scale_2;
+    values[0][3] = uv_wind[1][1]/scale_2;
+    values[0][4] = uv_wind[0][2]/scale_3;
+    values[0][5] = uv_wind[1][2]/scale_3;
+
+    JamaMatrix x = null;
+    try {
+      JamaMatrix jm_b = new JamaMatrix(values);
+      x = jm_A.solve(jm_b.transpose());
+    }
+    catch (IllegalAccessException e) {
+    }
+    catch (InstantiationException e) {
+    }
+    catch (InvocationTargetException e) {
+    }
+
+    return (x.getValues())[0];
+  }
+
+  public static void main(String args[])
+         throws VisADException
+  {
+    double[][] lonlat_s = new double[2][3];
+    lonlat_s[0][0] = -97.485*Data.DEGREES_TO_RADIANS;
+    lonlat_s[1][0] =   36.605*Data.DEGREES_TO_RADIANS;
+
+    lonlat_s[0][1] = -99.204*Data.DEGREES_TO_RADIANS;
+    lonlat_s[1][1] =   36.072*Data.DEGREES_TO_RADIANS;
+
+    lonlat_s[0][2] =  -97.522*Data.DEGREES_TO_RADIANS;
+    lonlat_s[1][2] =   34.984*Data.DEGREES_TO_RADIANS;
+
+    LinearVectorPointMethod lvpm = new LinearVectorPointMethod(lonlat_s);
+
+    double[][] uv_wind = new double[2][3];
+
+    uv_wind[0][0] = 6.5;
+    uv_wind[1][0] = 19.8;
+    uv_wind[0][1] = 11.0;
+    uv_wind[1][1] = 9.2;
+    uv_wind[0][2] = 8.2;
+    uv_wind[1][2] = 11.7;
+
+    double[] div_vort = lvpm.getKinematics(uv_wind);
+
+    for ( int ii = 0; ii < div_vort.length; ii++ ) {
+      System.out.println(div_vort[ii]);
+    }
+    System.out.println(+(5.0*Double.NaN));
+  }
+
+  private static double[] triangleCentroid(double[][] verts)
+  {
+    double[] centroid_xy = new double[2];
+    double[][] midpoint_12 = new double[2][1];
+    double[][] midpoint_13 = new double[2][1];
+
+    double[][] verts_xy = new double[2][3];
+    verts_xy[0][0] = verts[0][0];
+    verts_xy[0][1] = verts[0][1];
+    verts_xy[0][2] = verts[0][2];
+    verts_xy[1][0] = verts[1][0];
+    verts_xy[1][1] = verts[1][1];
+    verts_xy[1][2] = verts[1][2];
+
+    double slope_12_3;
+    double slope_13_2;
+    double slope_23_1;
+    double yintercept_12_3;
+    double yintercept_13_2;
+    double yintercept_23_1;
+
+    boolean rotate = false;
+    double rot_angle;
+
+    midpoint_12[0][0] = (verts_xy[0][1] - verts_xy[0][0])/2 + verts_xy[0][0];
+    midpoint_12[1][0] = (verts_xy[1][1] - verts_xy[1][0])/2 + verts_xy[1][0];
+
+    midpoint_13[0][0] = (verts_xy[0][2] - verts_xy[0][0])/2 + verts_xy[0][0];
+    midpoint_13[1][0] = (verts_xy[1][2] - verts_xy[1][0])/2 + verts_xy[1][0];
+
+    slope_12_3 = (verts_xy[1][2] - midpoint_12[1][0])/(verts_xy[0][2] - midpoint_12[0][0]);
+    slope_13_2 = (verts_xy[1][1] - midpoint_13[1][0])/(verts_xy[0][1] - midpoint_13[0][0]);
+
+    if (Double.isInfinite(slope_12_3) || Double.isInfinite(slope_13_2)) 
+    {
+      System.out.println("infinite slope");
+      rotate_clockwise(verts_xy, 90*Data.DEGREES_TO_RADIANS);
+      rotate_clockwise(midpoint_12, 90*Data.DEGREES_TO_RADIANS);
+      rotate_clockwise(midpoint_13, 90*Data.DEGREES_TO_RADIANS);
+      rotate = true;
+    } 
+
+    yintercept_12_3 = verts_xy[1][2] - slope_12_3*verts_xy[0][2];
+    yintercept_13_2 = verts_xy[1][1] - slope_13_2*verts_xy[0][1];
+
+    centroid_xy[0] = (yintercept_12_3 - yintercept_13_2)/(slope_13_2 - slope_12_3);
+    centroid_xy[1] = slope_12_3*centroid_xy[0] + yintercept_12_3;
+
+    if ( rotate == true ) {
+      //-rotate centroid back
+      double[][] xy = new double[2][1];
+      xy[0][0] = centroid_xy[0];
+      xy[1][0] = centroid_xy[1];
+      rotate_clockwise(xy, -90*Data.DEGREES_TO_RADIANS);
+      centroid_xy[0] = xy[0][0];
+      centroid_xy[1] = xy[1][0];
+    }
+    return centroid_xy;
+  }
+
+  private static void rotate_clockwise( double[][] points, double rot_angle )
+  {
+    for ( int ii = 0; ii < points[0].length; ii++ ) {
+      double x = points[0][ii];
+      double y = points[1][ii];
+      double angle = Math.atan2(y,x);
+      double r = Math.sqrt(x*x + y*y);
+      points[0][ii] = r*Math.cos(angle - rot_angle);
+      points[1][ii] = r*Math.sin(angle - rot_angle);
+    }
+  }
+}
diff --git a/visad/aeri/Qdiv.java b/visad/aeri/Qdiv.java
new file mode 100644
index 0000000..2830ef8
--- /dev/null
+++ b/visad/aeri/Qdiv.java
@@ -0,0 +1,1361 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+RH looks interesting up to 10000 m
+MR better near boundary layer (but tighter color range)
+*/
+
+package visad.aeri;
+
+import visad.*;
+import visad.util.*;
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.TwoDDisplayRendererJ3D;
+import visad.data.netcdf.*;
+import visad.bom.WindPolarCoordinateSystem;
+import visad.data.mcidas.BaseMapAdapter;
+import visad.data.visad.VisADSerialForm;
+import visad.data.mcidas.AreaAdapter;
+import visad.meteorology.ImageSequenceManager;
+import visad.meteorology.NavigatedImage;
+import visad.meteorology.ImageSequence;
+import java.rmi.RemoteException;
+import java.io.IOException;
+import java.io.File;
+
+// JFC packages
+import javax.swing.*;
+
+// AWT packages
+import java.awt.*;
+import java.awt.event.*;
+
+public class Qdiv
+       implements ScalarMapListener
+{
+  RealType latitude;
+  RealType longitude;
+  RealType altitude;
+
+  //- (lon,lat,alt)
+  //
+  RealTupleType spatial_domain;
+  RealType time;
+  RealType stn_idx;
+
+  RealType temp;
+  RealType dwpt;
+  RealType wvmr;
+  RealType RH;
+  RealType theta;
+  RealType thetaE;
+  RealType u_wind;
+  RealType v_wind;
+  RealType wvmr_u;
+  RealType wvmr_v;
+  RealType band1;
+  RealType div_qV;
+  RealType q_divV;
+  RealType qAdvct;
+  RealType shape;
+
+  RealTupleType wind_aeri;
+  RealTupleType div_qV_comps;
+  FunctionType alt_to_wind_aeri;
+  FunctionType time_to_alt_to_wind_aeri;
+  FunctionType alt_to_divqV;
+  FunctionType time_to_alt_to_divqV;
+
+  FieldImpl advect_field;
+  FieldImpl stations_field;
+  FieldImpl divqV_field;
+  ImageSequence image_seq;
+  Set timeDomain;
+
+  int n_stations = 3;
+
+  double[] station_lat;
+  double[] station_lon;
+  double[] station_alt;
+  double[] station_id;
+  double[] stat_xoffset = new double[n_stations];
+  double[] stat_yoffset = new double[n_stations];
+  double[][] centroid_ll;
+
+  BaseMapAdapter baseMap;
+  DataReference map_ref;
+
+  ScalarMap xmap;
+  ScalarMap ymap;
+  ScalarMap zmap;
+  ScalarMap img_map;
+  boolean xmapEvent = false;
+  boolean ymapEvent = false;
+  boolean imgEvent = false;
+  boolean firstEvent = false;
+  boolean first_img_Event = false;
+
+  float latmin, latmax;
+  float lonmin, lonmax;
+  float del_lat, del_lon;
+  double[] x_range, y_range;
+
+  int height_limit = 4000;     //- meters
+  int time_intrvl  =  900;     //- seconds
+  boolean rh = false;
+  boolean tm = false;
+  boolean pt = false;
+  boolean ept = false;
+
+  int start_date = 0;
+  double start_time = 0.0;
+
+  double[] scale_offset_x = new double[2];
+  double[] scale_offset_y = new double[2];
+
+  AnimationControl ani_cntrl;
+
+  int   alt_factor = 8;
+  int   n_hres_alt_samples;
+  float alt_min;
+  float alt_max;
+
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException
+  {
+    Qdiv qdiv = new Qdiv(args);
+  }
+
+  public Qdiv(String[] args)
+         throws VisADException, RemoteException, IOException
+  {
+    String vadfile = null;
+    String baseDate = "19991226";
+    for (int i=0; i<args.length; i++) {
+      if (args[i] == null) {
+      }
+      else if (args[i].endsWith(".vad")) {
+        vadfile = args[i];
+      }
+      else if (args[i].equals("-limit") && (i+1) < args.length) {
+        try {
+          height_limit = Integer.parseInt(args[i+1]);
+        }
+        catch (NumberFormatException e) {
+          System.out.println("bad height limit: " + args[i+1]);
+        }
+        i++;
+      }
+      else if (args[i].equals("-date") && (i+1) < args.length) {
+        baseDate = args[i+1];
+        i++;
+      }
+      else if (args[i].equals("-rh")) {
+        rh = true;
+        tm = false;
+      }
+      else if (args[i].equals("-temp")) {
+        tm = true;
+        rh = false;
+      }
+      else if (args[i].equals("-theta")) {
+        pt = true;
+      }
+      else if (args[i].equals("-thetaE")) {
+        ept = true;
+      }
+    }
+    if (vadfile != null) {
+      init_from_vad(vadfile);
+    }
+    else {
+      init_from_cdf(baseDate);
+    }
+    wvmr.alias("MR");
+    temp.alias("T");
+
+    try {
+      String fs = System.getProperty("file.separator");
+      image_seq = Qdiv.init_images("."+fs+"data"+fs+"image"+fs+baseDate);
+      band1 = (RealType)
+         ((RealTupleType)
+          ((FunctionType)
+           ((FunctionType)image_seq.getType()).getRange()).getRange()).getComponent(0);
+    }
+    catch ( Exception e ) {
+      System.out.println("no AREA image data");
+      image_seq = null;
+    }
+
+    JFrame frame = new JFrame("VisAD AERI/QDIV Viewer");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in frame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+    // panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    // panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    JPanel panel2 = new JPanel();
+    panel2.setLayout(new BoxLayout(panel2, BoxLayout.X_AXIS));
+
+    DisplayImpl display = makeDisplay(panel, panel2);
+
+    int WIDTH = 1200;
+    int HEIGHT = 800;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+
+    //-makeDisplay2(panel2);
+
+    JFrame frame2 = new JFrame("image color");
+    frame2.setSize(400, 200);
+    frame2.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+    frame2.getContentPane().add(panel2);
+ //-frame2.setVisible(true);
+  }
+
+  void init_from_cdf(String baseDate)
+       throws VisADException, RemoteException, IOException
+  {
+    station_lat = new double[n_stations];
+    station_lon = new double[n_stations];
+    station_alt = new double[n_stations];
+    station_id = new double[n_stations];
+
+    longitude = RealType.Longitude;
+    latitude = RealType.Latitude;
+    RH = RealType.getRealType("RH", SI.second);
+    stn_idx = RealType.getRealType("stn_idx");
+    theta = RealType.getRealType("theta");
+    thetaE = RealType.getRealType("thetaE");
+    u_wind = RealType.getRealType("u_wind");
+    v_wind = RealType.getRealType("v_wind");
+    wvmr_u = RealType.getRealType("wvmr_u");
+    wvmr_v = RealType.getRealType("wvmr_v");
+    div_qV = RealType.getRealType("div_qV");
+    q_divV = RealType.getRealType("q_divV");
+    qAdvct = RealType.getRealType("qAdvct");
+    shape = RealType.getRealType("shape");
+
+    String[] wind_files = new String[n_stations];
+    String[] rtvl_files = new String[n_stations];
+
+    String truncatedDate = baseDate;
+
+    wind_files[0] = "./data/" + baseDate + "_lamont_windprof.cdf";
+    wind_files[1] = "./data/" + baseDate + "_vici_windprof.cdf";
+    wind_files[2] = "./data/" + baseDate + "_purcell_windprof.cdf";
+
+    rtvl_files[0] = "./data/lamont_" + truncatedDate + "AG.cdf";
+    rtvl_files[1] = "./data/vici_" + truncatedDate + "AG.cdf";
+    rtvl_files[2] = "./data/purcell_" + truncatedDate + "AG.cdf";
+
+    File file = new File("./data/lamont_" + truncatedDate + "AP.cdf");
+
+    if (file.exists()) {
+      rtvl_files[0] = "./data/lamont_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[0] = "./data/lamont_" + truncatedDate + "AG.cdf";
+    }
+
+    file = new File("./data/vici_" + truncatedDate + "AP.cdf");
+    if (file.exists()) {
+      rtvl_files[1] = "./data/vici_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[1] = "./data/vici_" + truncatedDate + "AG.cdf";
+    }
+
+        file = new File("./data/purcell_" + truncatedDate + "AP.cdf");
+    if (file.exists()) {
+      rtvl_files[2] = "./data/purcell_" + truncatedDate + "AP.cdf";
+    }
+    else {
+      rtvl_files[2] = "./data/purcell_" + truncatedDate + "AG.cdf";
+    }
+
+    FieldImpl[] winds = makeWinds(wind_files);
+       System.out.println(winds[0].getType().prettyString());
+
+    FieldImpl[] rtvls = makeAeri(rtvl_files);
+       System.out.println(rtvls[0].getType().prettyString());
+
+    RealType[] r_types = {u_wind, v_wind, temp, dwpt, wvmr, wvmr_u, wvmr_v, thetaE};
+    wind_aeri = new RealTupleType(r_types);
+    alt_to_wind_aeri = new FunctionType(altitude, wind_aeri);
+    time_to_alt_to_wind_aeri = new FunctionType(RealType.Time, alt_to_wind_aeri);
+    div_qV_comps = new RealTupleType(new RealType[] {div_qV, thetaE, shape});
+    alt_to_divqV = new FunctionType(altitude, div_qV_comps);
+    time_to_alt_to_divqV = new FunctionType(RealType.Time, alt_to_divqV);
+    
+    spatial_domain = new RealTupleType(longitude, latitude, altitude);
+
+    stations_field = make_wind_aeri(winds, rtvls);
+    System.out.println(stations_field.getType().prettyString());
+
+    divqV_field = makeDivqV(stations_field);
+    System.out.println("makeDivqV:  Done");
+  }
+
+  void init_from_vad( String vad_file )
+       throws VisADException, RemoteException, IOException
+  {
+    VisADSerialForm vad_form = new VisADSerialForm();
+    stations_field = (FieldImpl) vad_form.open( vad_file );
+
+    MathType file_type = stations_field.getType();
+
+    stn_idx = (RealType)
+      ((RealTupleType)((FunctionType)file_type).getDomain()).getComponent(0);
+    FunctionType f_type0 = (FunctionType) ((FunctionType)file_type).getRange();
+    time = (RealType) ((RealTupleType)f_type0.getDomain()).getComponent(0);
+    FunctionType f_type1 = (FunctionType) f_type0.getRange();
+    spatial_domain = (RealTupleType) f_type1.getDomain();
+    longitude = (RealType) spatial_domain.getComponent(0);
+    latitude = (RealType) spatial_domain.getComponent(1);
+    altitude = (RealType) spatial_domain.getComponent(2);
+
+    RealTupleType rtt = (RealTupleType) f_type1.getRange();
+    temp = (RealType) rtt.getComponent(0);
+    dwpt = (RealType) rtt.getComponent(1);
+    wvmr = (RealType) rtt.getComponent(2);
+    RH = (RealType) rtt.getComponent(3);
+    theta = (RealType) rtt.getComponent(4);
+    thetaE = (RealType) rtt.getComponent(5);
+  }
+
+  public static ImageSequence init_images(String image_directory)
+         throws VisADException, RemoteException, IOException
+  {
+    String fs = System.getProperty("file.separator");
+
+    if ( image_directory == null ) {
+   //-image_directory = "."+fs+"data"+fs+"image"+fs+"vis";
+      image_directory = "."+fs+"data"+fs+"image"+fs+"ir_display";
+   //-image_directory = "."+fs+"data"+fs+"image"+fs+"ir";
+    }
+
+    File file = new File(image_directory);
+    String[] image_files = file.list();
+    int n_images = image_files.length;
+    NavigatedImage[] nav_images = new NavigatedImage[n_images];
+
+    for ( int ii = 0; ii < n_images; ii++ ) {
+      AreaAdapter area = new AreaAdapter(image_directory+fs+image_files[ii]);
+      FlatField image = area.getData();
+      DateTime img_start = area.getImageStartTime();
+      nav_images[ii] = new NavigatedImage(image, img_start, "AREA");
+    }
+
+    ImageSequenceManager img_manager = 
+      new ImageSequenceManager(nav_images);
+
+    return img_manager.getImageSequence();
+  }
+
+  DisplayImpl makeDisplay(JPanel panel, JPanel panel2)
+       throws VisADException, RemoteException, IOException
+  {
+    del_lon = 8.0f;
+    del_lat = 8.0f;
+
+    baseMap = new BaseMapAdapter("OUTLUSAM");
+    map_ref = new DataReferenceImpl("map");
+
+    if ( ! baseMap.isEastPositive() )
+    {
+      baseMap.setEastPositive(true);
+    }
+
+    //- make barber poles for each station
+    //
+    DataImpl poles = makePoles();
+    DataReference poles_ref = new DataReferenceImpl("poles");
+    poles_ref.setData(poles);
+
+    DisplayImpl display = new DisplayImplJ3D("aeri");
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    mode.setLineWidth(1.5f);
+    DisplayRenderer dr = display.getDisplayRenderer();
+    dr.setBoxOn(false);
+
+    xmap = new ScalarMap(longitude, Display.XAxis);
+    xmap.setScaleEnable(false);
+    xmap.addScalarMapListener(this);
+
+    ymap = new ScalarMap(latitude, Display.YAxis);
+    ymap.setScaleEnable(false);
+    ymap.addScalarMapListener(this);
+
+    double[] lon_mm = getArrayMinMax(station_lon);
+    double[] lat_mm = getArrayMinMax(station_lat);
+    baseMap.setLatLonLimits((float)(lat_mm[0]-del_lat), (float)(lat_mm[1]+del_lat),
+                            (float)(lon_mm[0]-del_lon), (float)(lon_mm[1]+del_lon));
+    DataImpl map = baseMap.getData();
+    map_ref.setData(map);
+
+    zmap = new ScalarMap(altitude, Display.ZAxis);
+
+    display.addMap(xmap);
+    display.addMap(ymap);
+    display.addMap(zmap);
+
+    float rad = 0.0600f;
+  //float len = 0.01165f;
+  //float len = 0.01045f;
+    float len = 0.0078375f;
+    ScalarMap shape_map = new ScalarMap(shape, Display.Shape);
+    display.addMap(shape_map);
+ //-VisADTriangleStripArray cyl = visad.aeri.Cylinder.makeCylinder(16, rad, len);
+    VisADTriangleStripArray cyl = makeCylinder(14, rad, len);
+    System.out.println("makeCylinder done");
+    VisADGeometryArray[] shapes;
+    shapes = new VisADGeometryArray[] {cyl};
+    ShapeControl shape_control = (ShapeControl) shape_map.getControl();
+    shape_control.setShapeSet(new Integer1DSet(1));
+    shape_control.setShapes(shapes);
+
+ //-ScalarMap flowx = new ScalarMap(u_wind, Display.Flow1X);
+ //-ScalarMap flowy = new ScalarMap(v_wind, Display.Flow1Y);
+    ScalarMap flowx = new ScalarMap(wvmr_u, Display.Flow1X);
+    ScalarMap flowy = new ScalarMap(wvmr_v, Display.Flow1Y);
+    display.addMap(flowx);
+    display.addMap(flowy);
+    FlowControl flow_cntrl = (FlowControl) flowx.getControl();
+    flow_cntrl.setFlowScale(0.5f);
+    flow_cntrl = (FlowControl) flowy.getControl();
+    flow_cntrl.setFlowScale(0.5f);
+
+
+    // note RH bonces around because temperature does
+    ScalarMap cmap = null;
+    if (rh) {
+      cmap = new ScalarMap(RH, Display.RGB);
+    }
+    else if (tm) {
+      cmap = new ScalarMap(temp, Display.RGB);
+    }
+    else if (pt) {
+      cmap = new ScalarMap(theta, Display.RGB);
+    }
+    else if (ept) {
+      cmap = new ScalarMap(thetaE, Display.RGB);
+    }
+    else {
+      cmap = new ScalarMap(wvmr, Display.RGB);
+    }
+    display.addMap(cmap);
+
+    ScalarMap cmap2 = new ScalarMap(thetaE, Display.RGB);
+ //-ScalarMap cmap2 = new ScalarMap(div_qV, Display.RGB);
+    display.addMap(cmap2);
+
+    ColorMapWidget cmw = new ColorMapWidget(cmap, null, true, false);
+    LabeledColorWidget cwidget = new LabeledColorWidget(cmw);
+    ScalarMap tmap = new ScalarMap(RealType.Time, Display.Animation);
+    display.addMap(tmap);
+    ani_cntrl = (AnimationControl) tmap.getControl();
+    ani_cntrl.setStep(200);
+    AnimationWidget awidget = new AnimationWidget(tmap);
+
+    zmap.setRange(0.0, hgt_max);
+
+    img_map = new ScalarMap(band1, Display.RGB);
+    img_map.addScalarMapListener(this);
+    display.addMap(img_map);
+ //-ColorMapWidget cw = new ColorMapWidget(img_map, null, true, false);
+ //-LabeledColorWidget img_widget = new LabeledColorWidget(cw);
+    ColorControl cc = (ColorControl) img_map.getControl();
+    cc.initGreyWedge();
+ //-panel2.add(img_widget);
+
+    ConstantMap[] map_constMap =
+      new ConstantMap[]
+    {
+      new ConstantMap(1., Display.Red),
+      new ConstantMap(1., Display.Green),
+      new ConstantMap(1., Display.Blue),
+      new ConstantMap(-.98, Display.ZAxis)
+    };
+
+    display.addReference(poles_ref);
+    display.addReference(map_ref, map_constMap);
+ 
+    ConstantMap[] c_maps = new ConstantMap[2];
+    for ( int kk = 0; kk < n_stations; kk++ ) {
+      double display_x = station_lon[kk]*scale_offset_x[0] + scale_offset_x[1];
+      c_maps[0] = new ConstantMap( display_x, Display.XAxis);
+      double display_y = station_lat[kk]*scale_offset_y[0] + scale_offset_y[1];
+      c_maps[1] = new ConstantMap( display_y, Display.YAxis);
+      DataReference station_ref = new DataReferenceImpl("station: "+kk);
+      station_ref.setData(stations_field.getSample(kk));
+      display.addReference(station_ref, c_maps);
+    }
+
+    double display_x = (centroid_ll[0][0]*Data.RADIANS_TO_DEGREES)*
+                        scale_offset_x[0] + scale_offset_x[1];
+    double display_y = (centroid_ll[1][0]*Data.RADIANS_TO_DEGREES)*
+                        scale_offset_y[0] + scale_offset_y[1];
+    ConstantMap[] c_maps2 = new ConstantMap[] 
+    {
+      new ConstantMap( display_x, Display.XAxis),
+      new ConstantMap( display_y, Display.YAxis)
+  //- new ConstantMap( 20.0, Display.LineWidth)
+    };
+    DataReference centroid_ref = new DataReferenceImpl("centroid");
+    centroid_ref.setData(divqV_field);
+    display.addReference(centroid_ref, c_maps2);
+
+    ConstantMap[] img_constMap =
+      new ConstantMap[] {new ConstantMap(-.99, Display.ZAxis)};
+
+    if ( image_seq != null ) {
+      DataReference img_ref = new DataReferenceImpl("image");
+      img_ref.setData(image_seq);
+      display.addReference(img_ref, img_constMap);
+    }
+
+    JPanel dpanel = new JPanel();
+    dpanel.setLayout(new BoxLayout(dpanel, BoxLayout.Y_AXIS));
+    dpanel.add(display.getComponent());
+
+    JPanel wpanel = new JPanel();
+    wpanel.setLayout(new BoxLayout(wpanel, BoxLayout.Y_AXIS));
+ //-cwidget.setMaximumSize(new Dimension(400, 200));
+ //-awidget.setMaximumSize(new Dimension(400, 400));
+    cwidget.setMaximumSize(new Dimension(400, 100));
+    awidget.setMaximumSize(new Dimension(400, 200));
+    wpanel.add(cwidget);
+    wpanel.add(awidget);
+    JPanel hpanel = new JPanel();
+    hpanel.setLayout(new BoxLayout(hpanel, BoxLayout.X_AXIS));
+    makeDisplay2(hpanel);
+    wpanel.add(hpanel);
+    Dimension d = new Dimension(400, 800);
+    wpanel.setMaximumSize(d);
+    panel.add(dpanel);
+    panel.add(wpanel);
+
+    return display;
+  }
+
+  void makeDisplay2( JPanel panel )
+       throws VisADException, RemoteException, IOException
+  {
+    DisplayImpl display = new DisplayImplJ3D("components", 
+                                              new TwoDDisplayRendererJ3D());
+
+    DisplayRenderer dr = display.getDisplayRenderer();
+    dr.setBoxOn(false);
+
+    final ScalarMap xmap = new ScalarMap(RealType.Time, Display.XAxis);
+    final ScalarMap ymap = new ScalarMap(altitude, Display.YAxis);
+    final ScalarMap cmap = new ScalarMap(div_qV, Display.RGB);
+    display.addMap(xmap);
+    display.addMap(ymap);
+    display.addMap(cmap);
+
+    class Listener implements ControlListener 
+    {
+      double[][] value;
+      public void controlChanged(ControlEvent ce) {
+        int step = ani_cntrl.getCurrent();
+        try {
+          value = timeDomain.indexToDouble(new int[] {step});
+          xmap.setRange((value[0][0] - 14400), value[0][0]);
+        }
+        catch (VisADException e) {
+        }
+        catch (RemoteException e) {
+        }
+      }
+    }
+    ani_cntrl.addControlListener(new Listener());
+
+    DataReference time_height_ref =
+      new DataReferenceImpl("time_height_ref");
+
+    time_height_ref.setData(divqV_field.domainMultiply());
+    display.addReference(time_height_ref);
+    GraphicsModeControl mode = display.getGraphicsModeControl(); 
+    mode.setScaleEnable(true);
+
+    panel.add(display.getComponent());
+  }
+
+  public static double[] getArrayMinMax(double[] array)
+  {
+    double min = Double.MAX_VALUE;
+    double max = -Double.MAX_VALUE;
+    double[] min_max = new double[2];
+
+    for (int ii = 0; ii < array.length; ii++) {
+     if (array[ii] > max) max = array[ii];
+     if (array[ii] < min) min = array[ii];
+    }
+    min_max[0] = min;
+    min_max[1] = max;
+    
+    return min_max;
+  }
+
+  double hgt_max = -Double.MAX_VALUE;
+
+  DataImpl makePoles()
+           throws VisADException, RemoteException
+  {
+    SampledSet[] set_s = new SampledSet[n_stations];
+    int ii = 0;
+    float[][] locs = new float[3][2];
+
+    for ( int kk = 0; kk < n_stations; kk++ ) {
+      boolean any = false;
+      float hgt = -Float.MAX_VALUE;
+      FieldImpl station = (FieldImpl) stations_field.getSample(kk);
+      if (station == null) continue;
+      int len = station.getLength();
+      for (int i=0; i<len; i++) {
+        FieldImpl pole = (FieldImpl) station.getSample(i);
+        if (pole == null || pole.getLength() < 2) continue;
+        Set set = pole.getDomainSet();
+        float[][] samples = set.getSamples(false);
+        // float[] lo = ((SampledSet)set).getLow();
+        float[] hi = ((SampledSet)set).getHi();
+        if (hi[0] > hgt) hgt = hi[0];
+        if (!any && samples[0][0] == samples[0][0])
+        {
+          any = true;
+          locs[0][0] = (float) station_lon[kk];
+          locs[1][0] = (float) station_lat[kk];
+          locs[0][1] = locs[0][0];
+          locs[1][1] = locs[1][0];
+        }
+      }
+      if (any) {
+        locs[2][0] = 0.0f;
+        locs[2][1] = hgt;
+        set_s[ii++] = new Gridded3DSet(spatial_domain, locs, 2, null, null, null);
+// System.out.println("set_s[" + kk + "] = " + set_s[kk]);
+        if (hgt > hgt_max) hgt_max = hgt;
+      }
+    }
+    SampledSet[] set_ss = new SampledSet[ii];
+    System.arraycopy(set_s, 0, set_ss, 0, ii);
+    return new UnionSet(spatial_domain, set_ss);
+  }
+
+  public void mapChanged(ScalarMapEvent e)
+       throws VisADException, RemoteException
+  {
+    if ( xmap.equals(e.getScalarMap()) ) {
+      xmapEvent = true;
+    }
+    else if ( ymap.equals(e.getScalarMap()) ) {
+      ymapEvent = true;
+    }
+    else if ( img_map.equals(e.getScalarMap()) ) {
+      imgEvent = true;
+    }
+    if (( xmapEvent && ymapEvent ) && !(firstEvent) ) {
+      double[] minmax = getArrayMinMax(station_lat);
+      latmin = (float)minmax[0];
+      latmax = (float)minmax[1];
+
+      minmax = getArrayMinMax(station_lon);
+      lonmin = (float) minmax[0];
+      lonmax = (float) minmax[1];
+
+      firstEvent = true;
+      xmap.setRange(lonmin, lonmax);
+      ymap.setRange(latmin, latmax);
+      double[] so = new double[2];
+      double[] data = new double[2];
+      double[] display = new double[2];
+      xmap.getScale(so, data, display);
+      scale_offset_x[0] = so[0];
+      scale_offset_x[1] = so[1];
+      ymap.getScale(so, data, display);
+      scale_offset_y[0] = so[0];
+      scale_offset_y[1] = so[1];
+    }
+    if ( imgEvent && !first_img_Event ) {
+      double[] i_range = img_map.getRange();
+      System.out.println(i_range[0]+" "+i_range[1]);
+      first_img_Event = true;
+      img_map.setRange(i_range[1], i_range[0]);
+    }
+  }
+
+  public void controlChanged(ScalarMapControlEvent e)
+  {
+  }
+
+  FieldImpl[] makeWinds(String[] files)
+              throws VisADException, RemoteException, IOException
+  {
+    DataImpl[] file_data = new DataImpl[n_stations];
+    FieldImpl[] time_field = new FieldImpl[n_stations];
+    double[][] time_offset = null;
+    double[] base_date = new double[n_stations];
+    double[] base_time = new double[n_stations];
+    Gridded1DSet d_set = null;
+    FlatField new_ff = null;
+
+    RealType alt;
+    RealType spd;
+    RealType dir;
+
+    //- create a new netcdf reader
+    Plain plain = new Plain();
+
+    //- retrieve file data objects
+    for ( int kk = 0; kk < n_stations; kk++) {
+      file_data[kk] = plain.open(files[kk]);
+    }
+
+    //- make sub mathtype for file objects
+    MathType file_type = file_data[0].getType();
+    System.out.println(file_type.prettyString());
+    System.out.println();
+
+    FunctionType f_type0 =
+      (FunctionType)((TupleType)file_type).getComponent(2);
+
+    int n_comps = ((TupleType)f_type0.getRange()).getDimension();
+    FunctionType f_type1 =
+      (FunctionType)((TupleType)f_type0.getRange()).getComponent(n_comps-1);
+
+    RealTupleType rt_type = (RealTupleType) f_type1.getRange();
+    int alt_idx = rt_type.getIndex("Altitude");
+    altitude = (RealType) rt_type.getComponent(alt_idx);
+    int ws_idx = rt_type.getIndex("windSpeed");
+    spd = (RealType) rt_type.getComponent(ws_idx);
+    int wd_idx = rt_type.getIndex("windDir");
+    dir = (RealType) rt_type.getComponent(wd_idx);
+
+    RealType[] r_types = { dir, spd };
+
+    /* WLH 28 Dec 99 */
+    RealType[] uv_types = { u_wind, v_wind };
+    // EarthVectorType uv = new EarthVectorType(uv_types); WLH meeds m/s
+    RealTupleType uv = new RealTupleType(uv_types);
+    CoordinateSystem cs = new WindPolarCoordinateSystem(uv);
+    RealTupleType ds = new RealTupleType(r_types, cs, null);
+
+
+    FunctionType alt_to_ds = new FunctionType(altitude, ds);
+    FunctionType alt_to_uv = new FunctionType(altitude, uv);
+
+    RealType domain_type = (RealType)
+      ((TupleType)f_type0.getRange()).getComponent(0);
+ //-time = domain_type;
+    time = RealType.Time;
+    FunctionType new_type = new FunctionType(RealType.Time, alt_to_uv);
+ //-FunctionType new_type = new FunctionType(time, alt_to_uv);
+
+    FieldImpl[] winds = new FieldImpl[n_stations];
+
+    for ( int ii = 0; ii < n_stations; ii++ )
+    {
+      base_date[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(0)).getValue();
+      base_time[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(1)).getValue();
+      time_field[ii] = (FieldImpl)
+        ((Tuple)file_data[ii]).getComponent(2);
+      station_lat[ii] =
+        ((Real)((Tuple)time_field[ii].getSample(0)).getComponent(6)).getValue();
+      station_lon[ii] =
+        -((Real)((Tuple)time_field[ii].getSample(0)).getComponent(7)).getValue();
+      station_alt[ii] =
+        ((Real)((Tuple)time_field[ii].getSample(0)).getComponent(8)).getValue();
+      station_id[ii] =
+        ((Real)((Tuple)time_field[ii].getSample(0)).getComponent(9)).getValue();
+      if (ii == 0) {
+        start_time = base_time[0];
+        start_date = (int) base_date[0];
+      }
+
+/*
+System.out.println("wind " + ii + " date: " + base_date[ii] +
+                   " time: " + base_time[ii]);
+wind 0 date: 1.9991226E7 time: 9.461664E8
+wind 1 date: 1.9991226E7 time: 9.461664E8
+wind 2 date: 1.9991226E7 time: 9.461664E8
+wind 3 date: 1.9991226E7 time: 9.461664E8
+wind 4 date: 1.9991226E7 time: 9.461664E8
+
+looks like 19991226 and seconds since 1-1-70
+time_offset looks like seconds since 0Z
+*/
+
+      int length = time_field[ii].getLength();
+      double[][] times = new double[1][length];
+      time_offset = new double[1][length];
+      FlatField[] range_data = new FlatField[length];
+
+      double[][] samples = null; // WLH
+      int n_not_all_miss = 0;
+      for ( int jj = 0; jj < length; jj++ )
+      {
+        Tuple range = (Tuple) time_field[ii].getSample(jj);
+        time_offset[0][jj] = (double)((Real)range.getComponent(0)).getValue();
+
+        FlatField p_field = (FlatField) range.getComponent(n_comps-1);
+        double[][] values =
+          p_field.getValues(); // WLH - (alt, ?, ?, dir, spd, ???)
+        double[][] new_values = new double[2][values[0].length];
+
+        if ( jj == 0 )  //- only once, vertical range gates don't change
+        {
+          samples = new double[1][values[0].length]; // WLH
+          System.arraycopy(values[alt_idx], 0, samples[0], 0, samples[0].length);
+          d_set = new Gridded1DSet(altitude, Set.doubleToFloat(samples),
+                                   samples[0].length);
+        }
+        new_ff = new FlatField(alt_to_uv, d_set);
+
+/* WLH - fill in missing winds - this also extrapolates to missing
+start or end altitudes - but it does nothing if all winds are missing */
+        int n_not_miss = 0;
+        int n_levels = values[0].length;
+        int[] not_miss = new int[n_levels];
+        for ( int mm = 0; mm < n_levels; mm++ )
+        {
+          if ( values[wd_idx][mm] <= -9999 ) {
+            new_values[0][mm] = Float.NaN;
+          }
+          else {
+            new_values[0][mm] = values[wd_idx][mm];
+          }
+
+          if ( values[ws_idx][mm] <= -9999 ) {
+            new_values[1][mm] = Float.NaN;
+          }
+          else {
+            new_values[1][mm] = values[ws_idx][mm];
+          }
+       //-System.out.println("("+mm+", "+jj+") "+new_values[0][mm]+",  "+new_values[1][mm]);
+
+          if (new_values[0][mm] == new_values[0][mm] &&
+              new_values[1][mm] == new_values[1][mm]) {
+            not_miss[n_not_miss] = mm;
+            n_not_miss++;
+          }
+        }
+     //-if ( (n_levels - 3) < n_not_miss && n_not_miss < n_levels) {
+        if ( (n_levels - 7) < n_not_miss && n_not_miss <= n_levels) {
+          int nn = n_not_miss;
+          if (not_miss[0] > 0) nn += not_miss[0];
+          int endlen = values[0].length - (not_miss[n_not_miss-1]+1);
+          if (endlen > 0) nn += endlen;
+
+          float[][] newer_values = new float[2][nn];
+          float[][] newer_samples = new float[1][nn];
+          // fill in non-missing values
+          for (int i=0; i<n_not_miss; i++) {
+            newer_values[0][not_miss[0] + i] =
+              (float) new_values[0][not_miss[i]];
+            newer_values[1][not_miss[0] + i] =
+              (float) new_values[1][not_miss[i]];
+            newer_samples[0][not_miss[0] + i] =
+              (float) samples[0][not_miss[i]];
+          }
+          // extrapolate if necessary for starting values
+          for (int i=0; i<not_miss[0]; i++) {
+            newer_values[0][i] = (float) new_values[0][not_miss[0]];
+            newer_values[1][i] = (float) new_values[1][not_miss[0]];
+            newer_samples[0][i] = (float) samples[0][not_miss[0]];
+          }
+          // extrapolate if necessary for ending values
+          for (int i=0; i<endlen; i++) {
+            newer_values[0][not_miss[0] + n_not_miss + i] =
+              (float) new_values[0][not_miss[n_not_miss-1]];
+            newer_values[1][not_miss[0] + n_not_miss + i] =
+              (float) new_values[1][not_miss[n_not_miss-1]];
+            newer_samples[0][not_miss[0] + n_not_miss + i] =
+              (float) samples[0][not_miss[n_not_miss-1]];
+          }
+          Gridded1DSet newer_d_set =
+            new Gridded1DSet(altitude, newer_samples, nn);
+          FlatField newer_ff = new FlatField(alt_to_uv, newer_d_set);
+          newer_ff.setSamples(cs.toReference(newer_values));
+          new_ff = (FlatField) newer_ff.resample(d_set,
+                                                 Data.WEIGHTED_AVERAGE,
+                                                 Data.NO_ERRORS );
+
+          range_data[n_not_all_miss] = new_ff;
+          times[0][n_not_all_miss] = base_time[0] + time_offset[0][jj];
+          n_not_all_miss++;
+        }
+        else {
+       //-new_ff.setSamples(cs.toReference(new_values));
+        }
+/* end WLH - fill in missing winds */
+
+     //-range_data[jj] = new_ff;
+      }
+
+/* resample() doesn't work for doubles ? */
+      double[][] new_times = new double[1][n_not_all_miss];
+      Data[] new_range_data = new Data[n_not_all_miss];
+      System.arraycopy(times[0], 0, new_times[0], 0, n_not_all_miss);
+      System.arraycopy(range_data, 0, new_range_data, 0, n_not_all_miss);
+      System.out.println("n_not_all_miss: "+n_not_all_miss);
+      Gridded1DSet domain_set =
+        new Gridded1DSet(RealType.Time, Set.doubleToFloat(new_times), n_not_all_miss);
+
+      winds[ii] = new FieldImpl(new_type, domain_set);
+      winds[ii].setSamples(new_range_data, false);
+    }
+    return winds;
+  }
+
+  FieldImpl[] makeAeri(String[] files)
+              throws VisADException, RemoteException, IOException
+  {
+    DataImpl[] file_data = new DataImpl[n_stations];
+    FieldImpl[] time_field = new FieldImpl[n_stations];
+    double[][] time_offset = null;
+    double[] base_date = new double[n_stations];
+    double[] base_time = new double[n_stations];
+
+    //- create a new netcdf reader
+    Plain plain = new Plain();
+
+    //- retrieve file data objects
+    for ( int kk = 0; kk < n_stations; kk++ ) {
+      file_data[kk] = plain.open(files[kk]);
+    }
+    System.out.println(file_data[0].getType().prettyString());
+
+    //- make sub mathtype for file objects
+    MathType file_type = file_data[0].getType();
+    FunctionType f_type0 = (FunctionType)
+      ((TupleType)file_type).getComponent(1);
+    FunctionType f_type1 = (FunctionType)
+      ((TupleType)f_type0.getRange()).getComponent(1);
+
+    RealTupleType rtt = (RealTupleType) f_type1.getRange();
+    RealType pres = RealType.getRealType("press");
+    temp = RealType.getRealType("temp");
+    dwpt = RealType.getRealType("dwpt");
+    wvmr = RealType.getRealType("wvmr");
+    RealType[] r_types = {pres, temp, dwpt, wvmr};
+    f_type1 = new FunctionType(f_type1.getDomain(), new RealTupleType(r_types));
+
+
+    RealType domain_type = (RealType)
+      ((TupleType)f_type0.getRange()).getComponent(0);
+
+    FunctionType new_type = new FunctionType(RealType.Time, f_type1);
+
+    FieldImpl[] rtvls = new FieldImpl[n_stations];
+
+
+    for ( int ii = 0; ii < n_stations; ii++ )
+    {
+      base_time[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(0)).getValue();
+      time_field[ii] = (FieldImpl) ((Tuple)file_data[ii]).getComponent(1);
+      base_date[ii] = (double)
+        ((Real)((Tuple)file_data[ii]).getComponent(2)).getValue();
+
+      int length = time_field[ii].getLength();
+      time_offset = new double[1][length];
+      Data[] range_data = new Data[length];
+      double[][] times = new double[1][length];
+      int not_all_missing = 0;
+
+      for ( int jj = 0; jj < length; jj++ )
+      {
+        Tuple range = (Tuple) time_field[ii].getSample(jj);
+        time_offset[0][jj] = (double)((Real)range.getComponent(0)).getValue();
+
+        FlatField p_field = (FlatField) range.getComponent(1);
+        double[][] values = p_field.getValues();
+        double[][] new_values = new double[4][values[0].length];
+        int num_missing = 0;
+        int n_levels = values[0].length;
+
+        for ( int mm = 0; mm < n_levels; mm++ )
+        {
+          if ( values[0][mm] == -9999 ) {
+            new_values[0][mm] = Float.NaN;
+          }
+          else {
+            new_values[0][mm] = values[0][mm];
+          }
+
+          if ( values[1][mm] == -9999 ) {
+            new_values[1][mm] = Float.NaN;
+          }
+          else {
+            new_values[1][mm] = values[1][mm];
+          }
+
+          if ( values[2][mm] == -9999 ) {
+            new_values[2][mm] = Float.NaN;
+          }
+          else {
+            new_values[2][mm] = values[2][mm];
+          }
+
+          if ( values[3][mm] == -9999 ) {
+            new_values[3][mm] = Float.NaN;
+            num_missing++;  //- if one range component missing, probably all missing
+          }
+          else {
+            new_values[3][mm] = values[3][mm];
+          }
+        }
+
+     //-if ( n_levels != num_missing )
+        if ( num_missing < 3 )
+        {
+          FlatField new_ff = new FlatField(f_type1, p_field.getDomainSet());
+          new_ff.setSamples(new_values);
+          range_data[not_all_missing] = new_ff;
+          times[0][not_all_missing] = base_time[0] + time_offset[0][jj];
+          not_all_missing++;
+        }
+      }
+
+      System.out.println("not_all_missing: "+not_all_missing);
+
+      double[][] new_times = new double[1][not_all_missing];
+      Data[] new_range_data = new Data[not_all_missing];
+      System.arraycopy(times[0], 0, new_times[0], 0, not_all_missing);
+      System.arraycopy(range_data, 0, new_range_data, 0, not_all_missing);
+
+      Gridded1DSet domain_set =
+        new Gridded1DSet(RealType.Time, Set.doubleToFloat(new_times), not_all_missing);
+
+      rtvls[ii] = new FieldImpl(new_type, domain_set);
+      rtvls[ii].setSamples(new_range_data, false);
+    }
+    return rtvls;
+  }
+
+  FieldImpl make_wind_aeri( FieldImpl[] winds, FieldImpl[] rtvls )
+            throws VisADException, RemoteException, IOException
+  {
+    float wind_time;
+    float[][] value_s = new float[1][1];
+    int[] index_s = new int[1];
+ //-FlatField alt_to_wind;
+    FieldImpl alt_to_wind;
+    FieldImpl wind_to_timeDomain;
+    FieldImpl rtvl_to_timeDomain;
+ //-FlatField alt_to_rtvl;
+    FieldImpl alt_to_rtvl;
+    FlatField advect;
+    FieldImpl advect_field;
+    FieldImpl rtvl_on_wind;
+    FieldImpl wind_on_wind;
+    int n_samples;
+    double[][] dir_spd;
+    double[][] uv_wind;
+    int idx = 0;
+    float alt;
+    Set rtvls_domain;
+    double[][] new_values = null;
+
+    FieldImpl stations_field =
+        new FieldImpl(new FunctionType( stn_idx, time_to_alt_to_wind_aeri),
+                                        new Integer1DSet( stn_idx, n_stations,
+                                                          null, null, null));
+
+    double[] lows = new double[n_stations*2];
+    double[] his = new double[n_stations*2];
+
+    for ( int kk = 0; kk < n_stations; kk++ ) 
+    {
+      Set d_set = winds[kk].getDomainSet();
+      lows[kk] = (double) (((SampledSet)d_set).getLow())[0];
+      his[kk] = (double) (((SampledSet)d_set).getHi())[0];
+      d_set = rtvls[kk].getDomainSet();
+      lows[n_stations+kk] = (double) (((SampledSet)d_set).getLow())[0];
+      his[n_stations+kk] = (double) (((SampledSet)d_set).getHi())[0];
+    }
+
+    double[] minmax = getArrayMinMax(lows);
+    double hi_low = minmax[1];
+    minmax = getArrayMinMax(his);
+    double low_hi = minmax[0];
+    System.out.println("hi_low: "+hi_low);
+    System.out.println("low_hi: "+low_hi);
+  
+    timeDomain = new Linear1DSet(time,
+                                 hi_low, low_hi,
+                                 (int) (low_hi - hi_low)/time_intrvl );
+
+    for ( int kk = 0; kk < n_stations; kk++ )
+    {
+      //- time(rtvl) -> (alt) -> (U,V,T,TD,WV)
+      //
+      FieldImpl time_wind_aeri = new FieldImpl(time_to_alt_to_wind_aeri, timeDomain);
+
+      //- resample winds to timeDomain
+      //
+
+      wind_to_timeDomain = (FieldImpl)
+         winds[kk].resample(timeDomain,
+                            Data.WEIGHTED_AVERAGE,
+                            Data.NO_ERRORS );
+
+      //- resample rtvls to timeDomain
+
+      rtvl_to_timeDomain = (FieldImpl)
+         rtvls[kk].resample(timeDomain,
+                            Data.WEIGHTED_AVERAGE,
+                            Data.NO_ERRORS );
+
+   /**
+      rtvl_to_timeDomain = linearInterp(rtvls[kk], timeDomain);
+    **/
+
+      //- resample rtvls domain (altitude) to winds at each rtvl time
+      //
+      int dim =
+        ((RealTupleType)
+          ((FunctionType)time_to_alt_to_wind_aeri.getRange()).getRange()).getDimension(); 
+      int n_times = timeDomain.getLength();
+      Set ds = null;
+      int nn = 0;
+      for ( int tt = 0; tt < n_times; tt++ )
+      {
+        alt_to_rtvl = (FieldImpl) rtvl_to_timeDomain.getSample(tt);
+        alt_to_wind = (FieldImpl) wind_to_timeDomain.getSample(tt);
+
+
+        if ( tt == 0 ) {  //- these won't change over time
+
+        ds = alt_to_wind.getDomainSet();
+
+/**--- WLH height limit  ---*/
+        float[][] samples = ds.getSamples();
+        float[] ns = new float[samples[0].length];
+        for (int i=0; i<samples[0].length; i++) {
+          if ((samples[0][i] - station_alt[kk]) < height_limit) {
+            ns[nn] = samples[0][i] - (float)station_alt[kk];
+            nn++;
+          }
+        }
+        float[][] new_samples = new float[1][nn];
+        System.arraycopy(ns, 0, new_samples[0], 0, nn);
+        ds = new Gridded1DSet(ds.getType(), new_samples, nn);
+        new_values = new double[dim][nn];
+
+        }
+
+
+        rtvl_on_wind = (FieldImpl)
+          alt_to_rtvl.resample( ds,
+                                Data.WEIGHTED_AVERAGE,
+                                Data.NO_ERRORS );
+
+        double[][] uv_values = alt_to_wind.getValues();
+        double[][] rtvl_values = rtvl_on_wind.getValues();
+
+        for (int ii = 0; ii < nn; ii++) {
+          new_values[0][ii] = uv_values[0][ii];
+          new_values[1][ii] = uv_values[1][ii];
+          new_values[2][ii] = rtvl_values[1][ii];
+          new_values[3][ii] = rtvl_values[2][ii];
+          new_values[4][ii] = rtvl_values[3][ii];
+          new_values[5][ii] = uv_values[0][ii]*rtvl_values[3][ii];
+          new_values[6][ii] = uv_values[1][ii]*rtvl_values[3][ii];
+          new_values[7][ii] = Aeri.equivPotentialTemperatureStar(
+                Aeri.potentialTemperature(rtvl_values[1][ii], rtvl_values[0][ii]),
+                rtvl_values[3][ii],
+                rtvl_values[1][ii] );
+        }
+
+        FlatField ff = new FlatField(alt_to_wind_aeri, ds);
+        ff.setSamples(new_values);
+
+        time_wind_aeri.setSample(tt, ff);
+      }
+      stations_field.setSample(kk, time_wind_aeri, false);
+    }
+
+    return stations_field;
+  }
+
+  FieldImpl makeDivqV( FieldImpl stations )
+            throws VisADException, RemoteException
+  {
+    double[][] uv_comps = new double[2][3];
+
+    double[][] lonlat = new double[2][3];
+    lonlat[0][0] = station_lon[0]*Data.DEGREES_TO_RADIANS;
+    lonlat[0][1] = station_lon[1]*Data.DEGREES_TO_RADIANS;
+    lonlat[0][2] = station_lon[2]*Data.DEGREES_TO_RADIANS;
+    lonlat[1][0] = station_lat[0]*Data.DEGREES_TO_RADIANS;
+    lonlat[1][1] = station_lat[1]*Data.DEGREES_TO_RADIANS;
+    lonlat[1][2] = station_lat[2]*Data.DEGREES_TO_RADIANS;
+
+    LinearVectorPointMethod lvpm =
+      new LinearVectorPointMethod(lonlat);
+
+    centroid_ll = lvpm.getCentroid();
+
+    FieldImpl station0 = (FieldImpl) stations.getSample(0);
+    FieldImpl station1 = (FieldImpl) stations.getSample(1);
+    FieldImpl station2 = (FieldImpl) stations.getSample(2);
+
+    FlatField ff = (FlatField) station0.getSample(0);
+    Set alt_set = ff.getDomainSet();
+    int alt_len = alt_set.getLength();
+    alt_min = (((SampledSet)alt_set).getLow())[0];
+    alt_max = (((SampledSet)alt_set).getHi())[0];
+    n_hres_alt_samples = alt_len*alt_factor;
+    Set hres_alt_set = 
+      new Linear1DSet( alt_set.getType(),
+                       (double)alt_min, (double)alt_max, n_hres_alt_samples);
+
+    FieldImpl new_field = new FieldImpl(time_to_alt_to_divqV, station0.getDomainSet());
+
+    for (int tt = 0; tt < station0.getLength(); tt++)
+    {
+      FlatField field0 = (FlatField) station0.getSample(tt);
+      FlatField field1 = (FlatField) station1.getSample(tt);
+      FlatField field2 = (FlatField) station2.getSample(tt);
+
+      double[][] values0 = field0.getValues(false);
+      double[][] values1 = field1.getValues(false);
+      double[][] values2 = field2.getValues(false);
+
+      FlatField new_ff = new FlatField(alt_to_divqV, alt_set);
+      double[][] new_values = new double[3][alt_len];
+
+      for (int kk = 0; kk < alt_len; kk++ )
+      {
+        boolean any_missing = false;
+
+        uv_comps[0][0] = values0[0][kk];
+        uv_comps[1][0] = values0[1][kk];
+      //uv_comps[0][0] = values0[5][kk];
+      //uv_comps[1][0] = values0[6][kk];
+
+        uv_comps[0][1] = values1[0][kk];
+        uv_comps[1][1] = values1[1][kk];
+      //uv_comps[0][1] = values1[5][kk];
+      //uv_comps[1][1] = values1[6][kk];
+
+        uv_comps[0][2] = values2[0][kk];
+        uv_comps[1][2] = values2[1][kk];
+      //uv_comps[0][2] = values2[5][kk];
+      //uv_comps[1][2] = values2[6][kk];
+
+        if (Double.isNaN(uv_comps[0][0]) || Double.isNaN(uv_comps[1][0])) {
+          any_missing = true;
+        }
+        else if (Double.isNaN(uv_comps[0][1]) || Double.isNaN(uv_comps[1][1])) {
+          any_missing = true;
+        }
+        else if (Double.isNaN(uv_comps[0][2]) || Double.isNaN(uv_comps[1][2])) {
+          any_missing = true;
+        }
+
+        if ( ! any_missing ) {
+          double[] kinematics = lvpm.getKinematics(uv_comps);
+          new_values[0][kk] = kinematics[4];
+          new_values[1][kk] = (values0[7][kk] + values1[7][kk] + values2[7][kk])/3;
+        }
+        else {
+          new_values[0][kk] = Double.NaN;
+          new_values[1][kk] = Double.NaN;
+        }
+      }
+      new_ff.setSamples(new_values);
+      new_field.setSample(tt, 
+                          new_ff.resample(hres_alt_set, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS), 
+                          false);
+    }
+    return new_field;
+  }
+
+  public static VisADTriangleStripArray makeCylinder( int n_faces, float rad, float len)
+  {
+    float[][] xy_points = new float[2][n_faces];
+
+    double del_theta = (2*Math.PI)/n_faces;
+    double theta = del_theta/2;
+    int[] index = new int[n_faces+1];
+
+    for ( int kk = 0; kk < n_faces; kk++ ) {
+      xy_points[0][kk] = (float) Math.cos(theta);
+      xy_points[1][kk] = (float) Math.sin(theta);
+      theta += del_theta;
+      index[kk] = kk;
+    }
+    index[n_faces] = 0;
+
+    VisADTriangleStripArray cyl = new VisADTriangleStripArray();
+    cyl.vertexCount = 2*(n_faces+1);
+    cyl.coordinates = new float[cyl.vertexCount*3];
+    cyl.normals = new float[cyl.vertexCount*3];
+    cyl.stripVertexCounts = new int[1];
+    cyl.stripVertexCounts[0] = cyl.vertexCount;
+
+    for ( int kk = 0; kk < n_faces+1; kk++ ) {
+      int ii = kk*6;
+      cyl.coordinates[ii] = rad*xy_points[0][index[kk]];
+      cyl.coordinates[ii+1] = rad*xy_points[1][index[kk]];
+      cyl.coordinates[ii+2] = len;
+      ii += 3;
+
+      cyl.coordinates[ii] = rad*xy_points[0][index[kk]];
+      cyl.coordinates[ii+1] = rad*xy_points[1][index[kk]];
+      cyl.coordinates[ii+2] = -len;
+    }
+
+    for ( int kk = 0; kk < n_faces+1; kk++ ) {
+      int ii = kk*6;
+      cyl.normals[ii] = xy_points[0][index[kk]];
+      cyl.normals[ii+1] = xy_points[1][index[kk]];
+      cyl.normals[ii+2] = 0;
+      ii += 3;
+
+      cyl.normals[ii] = xy_points[0][index[kk]];
+      cyl.normals[ii+1] = xy_points[1][index[kk]];
+      cyl.normals[ii+2] = 0;
+    }
+    return cyl;
+  }
+}
diff --git a/visad/bom/BarbManipulationRendererJ2D.java b/visad/bom/BarbManipulationRendererJ2D.java
new file mode 100644
index 0000000..2b1181b
--- /dev/null
+++ b/visad/bom/BarbManipulationRendererJ2D.java
@@ -0,0 +1,811 @@
+//
+// BarbManipulationRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+import java.rmi.*;
+
+
+/**
+   BarbManipulationRendererJ2D is the VisAD class for direct
+   manipulation rendering of wind barbs under Java2D
+*/
+public class BarbManipulationRendererJ2D extends DirectManipulationRendererJ2D
+       implements BarbRenderer {
+
+  /** this DataRenderer supports direct manipulation for Tuple
+      representations of wind barbs; two of the Tuple's Real components
+      must be mapped to Flow1X and Flow1Y, or Flow2X and Flow2Y */
+  public BarbManipulationRendererJ2D () {
+    super();
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbFunctionTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTupleTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbSetTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbTupleTypeJ2D(type, link, parent);
+  }
+
+  /** dummy for BarbRenderer */
+  public float[] makeVector(boolean south, float x, float y, float z,
+                     float scale, float pt_size, float f0, float f1,
+                     float[] vx, float[] vy, float[] vz, int[] numv,
+                     float[] tx, float[] ty, float[] tz, int[] numt) {
+    return null;
+  }
+
+  private boolean knotsConvert = true;
+
+  public void setKnotsConvert(boolean enable) {
+    knotsConvert = enable;
+  }
+
+  public boolean getKnotsConvert() {
+    return knotsConvert;
+  }
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  private final static String notFlatTupleType =
+    "not Flat Tuple";
+  private final static String multipleFlowTuples =
+    "mappings to both Flow1 and Flow2";
+  private final static String multipleFlowMapping =
+    "RealType with multiple flow mappings";
+  private final static String noFlow =
+    "must be RealTypes mapped to flow X and flow Y";
+  private final static String nonCartesian =
+    "non-Cartesian spatial mapping";
+
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+  private transient MathType type = null;
+  private transient ShadowTupleType shadow = null;
+
+  private CoordinateSystem coord = null;
+
+  /** point on direct manifold line or plane */
+  private float point_x, point_y, point_z;
+  /** normalized direction of line or perpendicular to plane */
+  private float line_x, line_y, line_z;
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+
+  /** mapping from flow components to Tuple Real components */
+  private int[] flowToComponent = {-1, -1, -1};
+  /** mapping from flow components to ScalarMaps */
+  private ScalarMap[] directMap = {null, null, null};
+
+  /** (barbValues[0], barbValues[1]) = (x, y) barb head location
+      (barbValues[2], barbValues[3]) = (x, y) barb tail location */
+  private float[] barbValues = null;
+  /** which_barb = 0 (Flow1) or 1 (Flow2);
+      redundant with tuple */
+  private int which_barb = -1;
+  /** flow from data when first */
+  private float[] data_flow = {0.0f, 0.0f, 0.0f};
+  /** data and display magnitudes when first */
+  private float data_speed = 0.0f;
+  private float display_speed = 0.0f;
+
+  /** if user adjusts speed, make sure start speed is greater than EPS */
+  private static final float EPS = 0.2f;
+
+  private boolean refirst = false;
+
+  private boolean stop = false;
+
+  /** pick error offset, communicated from checkClose() to drag_direct() */
+  private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
+  /** count down to decay offset to 0.0 */
+  private int offset_count = 0;
+  /** initial offset_count */
+  private static final int OFFSET_COUNT_INIT = 30;
+
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  public void checkDirect() throws VisADException, RemoteException {
+    // realCheckDirect();
+    //
+    // must customize
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    type = link.getType();
+    if (!(type instanceof TupleType) || !((TupleType) type).getFlat()) {
+      whyNotDirect = notFlatTupleType;
+      return;
+    }
+    flowToComponent = new int[] {-1, -1, -1};
+    directMap = new ScalarMap[] {null, null, null};
+    shadow = (ShadowTupleType) link.getShadow().getAdaptedShadowType();
+    DisplayTupleType[] tuples = {null};
+    whyNotDirect = findFlow(shadow, display, tuples, flowToComponent);
+    if (whyNotDirect != null) return;
+    if (coord == null) {
+      if (tuples[0] == null ||
+          flowToComponent[0] < 0 || flowToComponent[1] < 0) {
+        whyNotDirect = noFlow;
+        return;
+      }
+    }
+    else {
+      if (tuples[0] == null ||
+          flowToComponent[1] < 0 || flowToComponent[2] < 0) {
+        whyNotDirect = noFlow;
+        return;
+      }
+    }
+
+    ShadowRealType[] components = shadow.getRealComponents();
+    for (int i=0; i<components.length; i++) {
+      DisplayTupleType spatial_tuple = components[i].getDisplaySpatialTuple();
+      if (spatial_tuple != null &&
+          !Display.DisplaySpatialCartesianTuple.equals(spatial_tuple)) {
+        whyNotDirect = nonCartesian;
+        return;
+      }
+    }
+
+    // needs more, will find out when we write drag_direct
+    setIsDirectManipulation(true);
+  }
+
+  /** check for flow mappings;
+      does not allow flow mapping through CoordinateSystem */
+  private String findFlow(ShadowTupleType shadow,
+                          DisplayImpl display, DisplayTupleType[] tuples,
+                          int[] flowToComponent) {
+    ShadowRealType[] components = shadow.getRealComponents();
+    for (int i=0; i<components.length; i++) {
+      int num_flow_per_real = 0;
+      Enumeration maps = components[i].getSelectedMapVector().elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+        if (Display.DisplayFlow1Tuple.equals(tuple) ||
+            Display.DisplayFlow2Tuple.equals(tuple)) {
+          if (tuples[0] != null) {
+            if (!tuples[0].equals(tuple)) {
+              return multipleFlowTuples;
+            }
+          }
+          else {
+            tuples[0] = tuple;
+          }
+          num_flow_per_real++;
+          if (num_flow_per_real > 1) {
+            return multipleFlowMapping;
+          }
+          int index = dreal.getTupleIndex();
+          flowToComponent[index] = i;
+          directMap[index] = map;
+        }
+        else if (Display.DisplayFlow1SphericalTuple.equals(tuple) ||
+                 Display.DisplayFlow2SphericalTuple.equals(tuple)) {
+          if (tuples[0] != null) {
+            if (!tuples[0].equals(tuple)) {
+              return multipleFlowTuples;
+            }
+          }
+          else {
+            tuples[0] = tuple;
+            coord = tuple.getCoordinateSystem();
+          }
+          num_flow_per_real++;
+          if (num_flow_per_real > 1) {
+            return multipleFlowMapping;
+          }
+          int index = dreal.getTupleIndex();
+          flowToComponent[index] = i;
+          directMap[index] = map;
+        }
+      } // while (maps.hasMoreElements())
+    }
+    return null;
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+  public synchronized void setVectorSpatialValues(float[] mbarb, int which) {
+    // (barbValues[0], barbValues[1]) = (x, y) barb head location
+    // (barbValues[2], barbValues[3]) = (x, y) barb tail location
+    barbValues = mbarb;
+    which_barb = which;
+  }
+
+// methods customized from DataRenderer:
+
+  /** set spatialValues from ShadowType.doTransform */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // do nothing - manipulate barb values rather than spatial values
+    // spatialValues = spatial_values;
+  }
+
+  /** find minimum distance from ray to barb tail */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    if (barbValues == null) return Float.MAX_VALUE;
+    float o_x = (float) origin[0];
+    float o_y = (float) origin[1];
+    float o_z = (float) origin[2];
+    float d_x = (float) direction[0];
+    float d_y = (float) direction[1];
+    float d_z = (float) direction[2];
+/*
+System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
+System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
+*/
+    float x = barbValues[2] - o_x;
+    float y = barbValues[3] - o_y;
+    float z = 0.0f - o_z;
+    float dot = x * d_x + y * d_y + z * d_z;
+    x = x - dot * d_x;
+    y = y - dot * d_y;
+    z = z - dot * d_z;
+
+    offsetx = x;
+    offsety = y;
+    offsetz = z;
+
+    return (float) Math.sqrt(x * x + y * y + z * z); // distance
+  }
+
+  /** mouse button released, ending direct manipulation */
+  public synchronized void release_direct() {
+    // may need to do this for performance
+  }
+
+  public void stop_direct() {
+    stop = true;
+  }
+
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    // System.out.println("drag_direct " + first + " " + type);
+    if (barbValues == null || ref == null || shadow == null) return;
+
+    if (first) {
+      stop = false;
+    }
+    else {
+      if (stop) return;
+    }
+
+    // modify direction if mshift != 0
+    // modify speed if mctrl != 0
+    // modify speed and direction if neither
+    int mshift = mouseModifiers & InputEvent.SHIFT_MASK;
+    int mctrl = mouseModifiers & InputEvent.CTRL_MASK;
+
+    float o_x = (float) ray.position[0];
+    float o_y = (float) ray.position[1];
+    float o_z = (float) ray.position[2];
+    float d_x = (float) ray.vector[0];
+    float d_y = (float) ray.vector[1];
+    float d_z = (float) ray.vector[2];
+
+    if (pickCrawlToCursor) {
+      if (first) {
+        offset_count = OFFSET_COUNT_INIT;
+      }
+      else {
+        if (offset_count > 0) offset_count--;
+      }
+      if (offset_count > 0) {
+        float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
+        o_x += mult * offsetx;
+        o_y += mult * offsety;
+        o_z += mult * offsetz;
+      }
+    }
+
+    if (first || refirst) {
+      point_x = barbValues[2];
+      point_y = barbValues[3];
+      point_z = 0.0f;
+      line_x = 0.0f;
+      line_y = 0.0f;
+      line_z = 1.0f; // lineAxis == 2 in DataRenderer.drag_direct
+    } // end if (first || refirst)
+
+    float[] x = new float[3]; // x marks the spot
+    // DirectManifoldDimension = 2
+    // intersect ray with plane
+    float dot = (point_x - o_x) * line_x +
+                (point_y - o_y) * line_y +
+                (point_z - o_z) * line_z;
+    float dot2 = d_x * line_x + d_y * line_y + d_z * line_z;
+    if (dot2 == 0.0) return;
+    dot = dot / dot2;
+    // x is intersection
+    x[0] = o_x + dot * d_x;
+    x[1] = o_y + dot * d_y;
+    x[2] = o_z + dot * d_z;
+/*
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+    try {
+
+      Tuple data;
+      try {
+        data = (Tuple) link.getData();
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return;
+        }
+        throw re;
+      }
+
+      int n = ((TupleType) data.getType()).getNumberOfRealComponents();
+      Real[] reals = new Real[n];
+
+      int k = 0;
+      int m = data.getDimension();
+      for (int i=0; i<m; i++) {
+        Data component = data.getComponent(i);
+        if (component instanceof Real) {
+          reals[k++] = (Real) component;
+        }
+        else if (component instanceof RealTuple) {
+          for (int j=0; j<((RealTuple) component).getDimension(); j++) {
+            reals[k++] = (Real) ((RealTuple) component).getComponent(j);
+          }
+        }
+      }
+
+      if (first || refirst) {
+        // get first Data flow vector
+        for (int i=0; i<3; i++) {
+          int j = flowToComponent[i];
+          data_flow[i] = (j >= 0) ? (float) reals[j].getValue() : 0.0f;
+        }
+
+        if (coord != null) {
+          float[][] ds = {{data_flow[0]}, {data_flow[1]}, {data_flow[2]}};
+          ds = coord.toReference(ds);
+          data_flow[0] = ds[0][0];
+          data_flow[1] = ds[1][0];
+          data_flow[2] = ds[2][0];
+        }
+
+        data_speed = (float) Math.sqrt(data_flow[0] * data_flow[0] +
+                                       data_flow[1] * data_flow[1] +
+                                       data_flow[2] * data_flow[2]);
+        float barb0 = barbValues[2] - barbValues[0];
+        float barb1 = barbValues[3] - barbValues[1];
+/*
+System.out.println("data_flow = " + data_flow[0] + " " + data_flow[1] +
+                   " " + data_flow[2]);
+System.out.println("barbValues = " + barbValues[0] + " " + barbValues[1] +
+                   "   " + barbValues[2] + " " + barbValues[3]);
+System.out.println("data_speed = " + data_speed);
+*/
+      } // end if (first || refirst)
+
+      // convert x to a flow vector, and from spatial to earth
+      if (getRealVectorTypes(which_barb) instanceof EarthVectorType) {
+        // don't worry about vector magnitude -
+        // data_speed & display_speed take care of that
+        float eps = 0.0001f; // estimate derivative with a little vector
+        float[][] spatial_locs =
+          {{barbValues[0], barbValues[0] + eps * (x[0] - barbValues[0])},
+           {barbValues[1], barbValues[1] + eps * (x[1] - barbValues[1])},
+           {0.0f, 0.0f}};
+/*
+System.out.println("spatial_locs = " + spatial_locs[0][0] + " " +
+                   spatial_locs[0][1] + " " + spatial_locs[1][0] + " " +
+                   spatial_locs[1][1]);
+*/
+        float[][] earth_locs = spatialToEarth(spatial_locs);
+        // WLH - 18 Aug 99
+        if (earth_locs == null) return;
+/*
+System.out.println("earth_locs = " + earth_locs[0][0] + " " +
+                   earth_locs[0][1] + " " + earth_locs[1][0] + " " +
+                   earth_locs[1][1]);
+*/
+        x[2] = 0.0f;
+        x[0] = (earth_locs[1][1] - earth_locs[1][0]) *
+               ((float) Math.cos(Data.DEGREES_TO_RADIANS * earth_locs[0][0]));
+        x[1] = earth_locs[0][1] - earth_locs[0][0];
+/*
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else { // if (!(getRealVectorTypes(which_barb) instanceof EarthVectorType))
+        // convert x to vector
+        x[0] -= barbValues[0];
+        x[1] -= barbValues[1];
+
+        // adjust for spatial map scalings but don't worry about vector
+        // magnitude - data_speed & display_speed take care of that
+        // also, spatial is Cartesian
+        double[] ranges = getRanges();
+        for (int i=0; i<3; i++) {
+          x[i] /= ranges[i];
+        }
+/*
+System.out.println("ranges = " + ranges[0] + " " + ranges[1] +
+                   " " + ranges[2]);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+
+      // WLH 6 August 99
+      x[0] = -x[0];
+      x[1] = -x[1];
+      x[2] = -x[2];
+
+/* may need to do this for performance
+      float[] xx = {x[0], x[1], x[2]};
+      addPoint(xx);
+*/
+
+      float x_speed =
+        (float) Math.sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
+      if (x_speed < 0.000001f) x_speed = 0.000001f;
+      if (first || refirst) {
+        display_speed = x_speed;
+      }
+      refirst = false;
+
+      if (mshift != 0) {
+        // only modify data_flow direction
+        float ratio = data_speed / x_speed;
+        x[0] *= ratio;
+        x[1] *= ratio;
+        x[2] *= ratio;
+/*
+System.out.println("direction, ratio = " + ratio + " " +
+                   data_speed + " " + x_speed);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else if (mctrl != 0) {
+        // only modify data_flow speed
+        float ratio = x_speed / display_speed;
+        if (data_speed < EPS) {
+          data_flow[0] = 2.0f * EPS;
+          refirst = true;
+        }
+        x[0] = ratio * data_flow[0];
+        x[1] = ratio * data_flow[1];
+        x[2] = ratio * data_flow[2];
+/*
+System.out.println("speed, ratio = " + ratio + " " +
+                   x_speed + " " + display_speed);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else {
+        // modify data_flow speed and direction
+        float ratio = data_speed / display_speed;
+        if (data_speed < EPS) {
+          data_flow[0] = 2.0f * EPS;
+          x[0] = data_flow[0];
+          x[1] = data_flow[1];
+          x[2] = data_flow[2];
+          refirst = true;
+        }
+        else {
+          x[0] *= ratio;
+          x[1] *= ratio;
+          x[2] *= ratio;
+        }
+      }
+
+      if (coord != null) {
+        float[][] xs = {{x[0]}, {x[1]}, {x[2]}};
+        xs = coord.fromReference(xs);
+        x[0] = xs[0][0];
+        x[1] = xs[1][0];
+        x[2] = xs[2][0];
+      }
+
+      // now replace flow values
+      Vector vect = new Vector();
+      for (int i=0; i<3; i++) {
+        int j = flowToComponent[i];
+        if (j >= 0) {
+          RealType rtype = (RealType) reals[j].getType();
+          reals[j] = new Real(rtype, (double) x[i], rtype.getDefaultUnit(), null);
+
+          // WLH 31 Aug 2000
+          Real r = reals[j];
+          Unit overrideUnit = directMap[i].getOverrideUnit();
+          Unit rtunit = rtype.getDefaultUnit();
+          // units not part of Time string
+          if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+              !RealType.Time.equals(rtype)) {
+            double d = (float) overrideUnit.toThis((double) x[0], rtunit);
+            r = new Real(rtype, d, overrideUnit);
+            String valueString = r.toValueString();
+            vect.addElement(rtype.getName() + " = " + valueString);
+          }
+          else {
+            // create location string
+            vect.addElement(rtype.getName() + " = " + x[i]);
+          }
+
+        }
+      }
+      getDisplayRenderer().setCursorStringVector(vect);
+
+      Data newData = null;
+      // now build new RealTuple or Flat Tuple
+      if (data instanceof RealTuple) {
+        newData = new RealTuple(((RealTupleType) data.getType()), reals,
+                                ((RealTuple) data).getCoordinateSystem());
+      }
+      else {
+        Data[] new_components = new Data[m];
+        k = 0;
+        for (int i=0; i<m; i++) {
+          Data component = data.getComponent(i);
+          if (component instanceof Real) {
+            new_components[i] = reals[k++];
+          }
+          else if (component instanceof RealTuple) {
+            Real[] sub_reals = new Real[((RealTuple) component).getDimension()];
+            for (int j=0; j<((RealTuple) component).getDimension(); j++) {
+              sub_reals[j] = reals[k++];
+            }
+            new_components[i] =
+              new RealTuple(((RealTupleType) component.getType()), sub_reals,
+                            ((RealTuple) component).getCoordinateSystem());
+          }
+        }
+        newData = new Tuple(new_components, false);
+      }
+      ref.setData(newData);
+    }
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  public Object clone() {
+    return new BarbManipulationRendererJ2D();
+  }
+
+  static final int N = 5;
+
+  /** test BarbManipulationRendererJ2D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    // construct RealTypes for wind record components
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType windx = RealType.getRealType("windx",
+                          CommonUnit.meterPerSecond);     
+    RealType windy = RealType.getRealType("windy",
+                          CommonUnit.meterPerSecond);
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+
+    // EarthVectorType extends RealTupleType and says that its
+    // components are vectors in m/s with components parallel
+    // to Longitude (positive east) and Latitude (positive north)
+    EarthVectorType windxy = new EarthVectorType(windx, windy);
+
+    RealType wind_dir = RealType.getRealType("wind_dir",
+                          CommonUnit.degree);
+    RealType wind_speed = RealType.getRealType("wind_speed",
+                          CommonUnit.meterPerSecond);
+    RealTupleType windds = null;
+    if (args.length > 0) {
+      System.out.println("polar winds");
+      windds =
+        new RealTupleType(new RealType[] {wind_dir, wind_speed},
+        new WindPolarCoordinateSystem(windxy), null);
+    }
+
+    // construct Java2D display and mappings that govern
+    // how wind records are displayed
+    DisplayImpl display = new DisplayImplJ2D("display1");
+    ScalarMap lonmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(lonmap);
+    ScalarMap latmap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(latmap);
+
+    FlowControl flow_control;
+    if (args.length > 0) {
+      ScalarMap winds_map = new ScalarMap(wind_speed, Display.Flow1Radial);
+      display.addMap(winds_map);
+      winds_map.setRange(0.0, 1.0); // do this for barb rendering
+      ScalarMap windd_map = new ScalarMap(wind_dir, Display.Flow1Azimuth);
+      display.addMap(windd_map);
+      windd_map.setRange(0.0, 360.0); // do this for barb rendering
+      flow_control = (FlowControl) windd_map.getControl();
+      flow_control.setFlowScale(0.15f); // this controls size of barbs
+    }
+    else {
+      ScalarMap windx_map = new ScalarMap(windx, Display.Flow1X);
+      display.addMap(windx_map);
+      windx_map.setRange(-1.0, 1.0); // do this for barb rendering
+      ScalarMap windy_map = new ScalarMap(windy, Display.Flow1Y);
+      display.addMap(windy_map);
+      windy_map.setRange(-1.0, 1.0); // do this for barb rendering
+      flow_control = (FlowControl) windy_map.getControl();
+      flow_control.setFlowScale(0.15f); // this controls size of barbs
+    }
+
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    DataReferenceImpl[] refs = new DataReferenceImpl[N * N];
+    int k = 0;
+    // create an array of N by N winds
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        double u = 2.0 * i / (N - 1.0) - 1.0;
+        double v = 2.0 * j / (N - 1.0) - 1.0;
+
+        // each wind record is a Tuple (lon, lat, (windx, windy), red, green)
+        // set colors by wind components, just for grins
+        Tuple tuple;
+        double fx = 30.0 * u;
+        double fy = 30.0 * v;
+        if (args.length > 0) {
+          double fd = Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy);
+          double fs = Math.sqrt(fx * fx + fy * fy);
+          tuple = new Tuple(new Data[]
+            {new Real(lon, 10.0 * u), new Real(lat, 10.0 * v - 40.0),
+             new RealTuple(windds, new double[] {fd, fs}),
+             new Real(red, u), new Real(green, v)});
+        }
+        else {
+          tuple = new Tuple(new Data[]
+            {new Real(lon, 10.0 * u), new Real(lat, 10.0 * v - 40.0),
+             new RealTuple(windxy, new double[] {fx, fy}),
+             new Real(red, u), new Real(green, v)});
+        }
+
+        // construct reference for wind record
+        refs[k] = new DataReferenceImpl("ref_" + k);
+        refs[k].setData(tuple);
+
+        // link wind record to display via BarbManipulationRendererJ2D
+        // so user can change barb by dragging it
+        // drag with right mouse button and shift to change direction
+        // drag with right mouse button and no shift to change speed
+        BarbManipulationRendererJ2D renderer = new BarbManipulationRendererJ2D();
+        renderer.setKnotsConvert(true);
+        display.addReferences(renderer, refs[k]);
+
+        // link wind record to a CellImpl that will listen for changes
+        // and print them
+        WindGetterJ2D cell = new WindGetterJ2D(refs[k]);
+        cell.addReference(refs[k]);
+
+        k++;
+      }
+    }
+
+    // instead of linking the wind record "DataReferenceImpl refs" to
+    // the WindGetterJ2Ds, you can have some user interface event (e.g.,
+    // the user clicks on "DONE") trigger code that does a getData() on
+    // all the refs and stores the records in a file.
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test BarbManipulationRendererJ2D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
+class WindGetterJ2D extends CellImpl {
+  DataReferenceImpl ref;
+
+  public WindGetterJ2D(DataReferenceImpl r) {
+    ref = r;
+  }
+
+  public void doAction() throws VisADException, RemoteException {
+    Tuple tuple = (Tuple) ref.getData();
+    float lon = (float) ((Real) tuple.getComponent(0)).getValue();
+    float lat = (float) ((Real) tuple.getComponent(1)).getValue();
+    RealTuple wind = (RealTuple) tuple.getComponent(2);
+    float windx = (float) ((Real) wind.getComponent(0)).getValue();
+    float windy = (float) ((Real) wind.getComponent(1)).getValue();
+    System.out.println("wind = (" + windx + ", " + windy + ") at (" +
+                       + lat + ", " + lon +")");
+  }
+
+}
+
diff --git a/visad/bom/BarbManipulationRendererJ3D.java b/visad/bom/BarbManipulationRendererJ3D.java
new file mode 100644
index 0000000..3c384b8
--- /dev/null
+++ b/visad/bom/BarbManipulationRendererJ3D.java
@@ -0,0 +1,1197 @@
+//
+// BarbManipulationRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import java.text.NumberFormat;
+import javax.swing.*;
+import java.util.*;
+import java.rmi.*;
+
+
+/**
+   BarbManipulationRendererJ3D is the VisAD class for direct
+   manipulation rendering of wind barbs under Java3D
+*/
+public class BarbManipulationRendererJ3D extends DirectManipulationRendererJ3D
+       implements BarbRenderer {
+
+  /** this DataRenderer supports direct manipulation for Tuple
+      representations of wind barbs; two of the Tuple's Real components
+      must be mapped to Flow1X and Flow1Y, or Flow2X and Flow2Y */
+  public BarbManipulationRendererJ3D () {
+    super();
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbFunctionTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTupleTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbSetTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbTupleTypeJ3D(type, link, parent);
+  }
+
+  private boolean knotsConvert = true;
+
+  public void setKnotsConvert(boolean enable) {
+    knotsConvert = enable;
+  }
+
+  public boolean getKnotsConvert() {
+    return knotsConvert;
+  }
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  private final static String notFlatTupleType =
+    "not Flat Tuple";
+  private final static String multipleFlowTuples =
+    "mappings to both Flow1 and Flow2";
+  private final static String multipleFlowMapping =
+    "RealType with multiple flow mappings";
+  private final static String noFlow =
+    "must be RealTypes mapped to flow X and flow Y";
+  private final static String nonCartesian =
+    "non-Cartesian spatial mapping";
+
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+  private transient MathType type = null;
+  private transient ShadowTupleType shadow = null;
+
+  private CoordinateSystem coord = null;
+
+  /** point on direct manifold line or plane */
+  private float point_x, point_y, point_z;
+  /** normalized direction of line or perpendicular to plane */
+  private float line_x, line_y, line_z;
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+
+  /** mapping from flow components to Tuple Real components */
+  private int[] flowToComponent = {-1, -1, -1};
+  /** mapping from flow components to ScalarMaps */
+  private ScalarMap[] directMap = {null, null, null};
+
+  /** (barbValues[0], barbValues[1]) = (x, y) barb head location
+      (barbValues[2], barbValues[3]) = (x, y) barb tail location */
+  private float[] barbValues = null;
+  /** which_barb = 0 (Flow1) or 1 (Flow2);
+      redundant with tuple */
+  private int which_barb = -1;
+  /** flow from data when first */
+  private float[] data_flow = {0.0f, 0.0f, 0.0f};
+  /** data and display magnitudes when first */
+  private float data_speed = 0.0f;
+  private float display_speed = 0.0f;
+
+  /** if user adjusts speed, make sure start speed is greater than EPS */
+  private static final float EPS = 0.2f;
+
+  private boolean refirst = false;
+
+  private boolean stop = false;
+
+  // grf 17 Nov 2003 
+  private boolean noNumbers = true; // for no numbers on wind barbs
+  public boolean getNoNumbers() { return noNumbers; }
+  public void setNoNumbers(boolean flag) { noNumbers = flag; }
+  private int numDecPlaces = 1; // default of 1 decimal place on wind barbs
+  public void setNumDecPlaces(int num) { numDecPlaces = num; }
+  public int getNumDecPlaces() { return numDecPlaces; }
+
+  /** pick error offset, communicated from checkClose() to drag_direct() */
+  private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
+  /** count down to decay offset to 0.0 */
+  private int offset_count = 0;
+  /** initial offset_count */
+  private static final int OFFSET_COUNT_INIT = 30;
+
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  public void checkDirect() throws VisADException, RemoteException {
+    // realCheckDirect();
+    //
+    // must customize
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+    link = getLinks()[0];
+    ref = link.getDataReference();
+    type = link.getType();
+    if (!(type instanceof TupleType) || !((TupleType) type).getFlat()) {
+      whyNotDirect = notFlatTupleType;
+      return;
+    }
+    flowToComponent = new int[] {-1, -1, -1};
+    directMap = new ScalarMap[] {null, null, null};
+    shadow = (ShadowTupleType) link.getShadow().getAdaptedShadowType();
+    DisplayTupleType[] tuples = {null};
+    whyNotDirect = findFlow(shadow, display, tuples, flowToComponent);
+    if (whyNotDirect != null) return;
+    if (coord == null) {
+      if (tuples[0] == null ||
+          flowToComponent[0] < 0 || flowToComponent[1] < 0) {
+        whyNotDirect = noFlow;
+        return;
+      }
+    }
+    else {
+      if (tuples[0] == null ||
+          flowToComponent[1] < 0 || flowToComponent[2] < 0) {
+        whyNotDirect = noFlow;
+        return;
+      }
+    }
+
+    ShadowRealType[] components = shadow.getRealComponents();
+    for (int i=0; i<components.length; i++) {
+      DisplayTupleType spatial_tuple = components[i].getDisplaySpatialTuple();
+      if (spatial_tuple != null &&
+          !Display.DisplaySpatialCartesianTuple.equals(spatial_tuple)) {
+        whyNotDirect = nonCartesian;
+        return;
+      }
+    }
+
+    // needs more, will find out when we write drag_direct
+    setIsDirectManipulation(true);
+  }
+
+  /** check for flow mappings;
+      does not allow flow mapping through CoordinateSystem */
+  private String findFlow(ShadowTupleType shadow,
+                          DisplayImpl display, DisplayTupleType[] tuples,
+                          int[] flowToComponent) {
+    ShadowRealType[] components = shadow.getRealComponents();
+    for (int i=0; i<components.length; i++) {
+      int num_flow_per_real = 0;
+      Enumeration maps = components[i].getSelectedMapVector().elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+        if (Display.DisplayFlow1Tuple.equals(tuple) ||
+            Display.DisplayFlow2Tuple.equals(tuple)) {
+          if (tuples[0] != null) {
+            if (!tuples[0].equals(tuple)) {
+              return multipleFlowTuples;
+            }
+          }
+          else {
+            tuples[0] = tuple;
+          }
+          num_flow_per_real++;
+          if (num_flow_per_real > 1) {
+            return multipleFlowMapping;
+          }
+          int index = dreal.getTupleIndex();
+          flowToComponent[index] = i;
+          directMap[index] = map;
+        }
+        else if (Display.DisplayFlow1SphericalTuple.equals(tuple) ||
+                 Display.DisplayFlow2SphericalTuple.equals(tuple)) {
+          if (tuples[0] != null) {
+            if (!tuples[0].equals(tuple)) {
+              return multipleFlowTuples;
+            }
+          }
+          else {
+            tuples[0] = tuple;
+            coord = tuple.getCoordinateSystem();
+          }
+          num_flow_per_real++;
+          if (num_flow_per_real > 1) {
+            return multipleFlowMapping;
+          }
+          int index = dreal.getTupleIndex();
+          flowToComponent[index] = i;
+          directMap[index] = map;
+        }
+      } // while (maps.hasMoreElements())
+    }
+    return null;
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+  public synchronized void setVectorSpatialValues(float[] mbarb, int which) {
+    // (barbValues[0], barbValues[1]) = (x, y) barb head location
+    // (barbValues[2], barbValues[3]) = (x, y) barb tail location
+    barbValues = mbarb;
+    which_barb = which;
+  }
+
+// methods customized from DataRenderer:
+
+  /** set spatialValues from ShadowType.doTransform */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // do nothing - manipulate barb values rather than spatial values
+    // spatialValues = spatial_values;
+  }
+
+  /** find minimum distance from ray to barb tail */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    if (barbValues == null) return Float.MAX_VALUE;
+    float o_x = (float) origin[0];
+    float o_y = (float) origin[1];
+    float o_z = (float) origin[2];
+    float d_x = (float) direction[0];
+    float d_y = (float) direction[1];
+    float d_z = (float) direction[2];
+/*
+System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
+System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
+*/
+    float x = barbValues[2] - o_x;
+    float y = barbValues[3] - o_y;
+    float z = 0.0f - o_z;
+    float dot = x * d_x + y * d_y + z * d_z;
+    x = x - dot * d_x;
+    y = y - dot * d_y;
+    z = z - dot * d_z;
+
+    offsetx = x;
+    offsety = y;
+    offsetz = z;
+
+    return (float) Math.sqrt(x * x + y * y + z * z); // distance
+  }
+
+  /** mouse button released, ending direct manipulation */
+  public synchronized void release_direct() {
+    // may need to do this for performance
+  }
+
+  public void stop_direct() {
+    stop = true;
+  }
+
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    if (barbValues == null || ref == null || shadow == null) return;
+
+    if (first) {
+      stop = false;
+    }
+    else {
+      if (stop) return;
+    }
+
+    // modify direction if mshift != 0
+    // modify speed if mctrl != 0
+    // modify speed and direction if neither
+    int mshift = mouseModifiers & InputEvent.SHIFT_MASK;
+    int mctrl = mouseModifiers & InputEvent.CTRL_MASK;
+
+    float o_x = (float) ray.position[0];
+    float o_y = (float) ray.position[1];
+    float o_z = (float) ray.position[2];
+    float d_x = (float) ray.vector[0];
+    float d_y = (float) ray.vector[1];
+    float d_z = (float) ray.vector[2];
+
+    if (pickCrawlToCursor) {
+      if (first) {
+        offset_count = OFFSET_COUNT_INIT;
+      }
+      else {
+        if (offset_count > 0) offset_count--;
+      }
+      if (offset_count > 0) {
+        float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
+        o_x += mult * offsetx;
+        o_y += mult * offsety;
+        o_z += mult * offsetz;
+      }
+    }
+
+    if (first || refirst) {
+      point_x = barbValues[2];
+      point_y = barbValues[3];
+      point_z = 0.0f;
+      line_x = 0.0f;
+      line_y = 0.0f;
+      line_z = 1.0f; // lineAxis == 2 in DataRenderer.drag_direct
+    } // end if (first || refirst)
+
+    float[] x = new float[3]; // x marks the spot
+    // DirectManifoldDimension = 2
+    // intersect ray with plane
+    float dot = (point_x - o_x) * line_x +
+                (point_y - o_y) * line_y +
+                (point_z - o_z) * line_z;
+    float dot2 = d_x * line_x + d_y * line_y + d_z * line_z;
+    if (dot2 == 0.0) return;
+    dot = dot / dot2;
+    // x is intersection
+    x[0] = o_x + dot * d_x;
+    x[1] = o_y + dot * d_y;
+    x[2] = o_z + dot * d_z;
+/*
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+    try {
+
+      Tuple data = (Tuple) link.getData();
+      int n = ((TupleType) data.getType()).getNumberOfRealComponents();
+      Real[] reals = new Real[n];
+
+      int k = 0;
+      int m = data.getDimension();
+      for (int i=0; i<m; i++) {
+        Data component = data.getComponent(i);
+        if (component instanceof Real) {
+          reals[k++] = (Real) component;
+        }
+        else if (component instanceof RealTuple) {
+          for (int j=0; j<((RealTuple) component).getDimension(); j++) {
+            reals[k++] = (Real) ((RealTuple) component).getComponent(j);
+          }
+        }
+      }
+
+      if (first || refirst) {
+        // get first Data flow vector
+        for (int i=0; i<3; i++) {
+          int j = flowToComponent[i];
+          data_flow[i] = (j >= 0) ? (float) reals[j].getValue() : 0.0f;
+        }
+
+        if (coord != null) {
+          float[][] ds = {{data_flow[0]}, {data_flow[1]}, {data_flow[2]}};
+          ds = coord.toReference(ds);
+          data_flow[0] = ds[0][0];
+          data_flow[1] = ds[1][0];
+          data_flow[2] = ds[2][0];
+        }
+
+        data_speed = (float) Math.sqrt(data_flow[0] * data_flow[0] +
+                                       data_flow[1] * data_flow[1] +
+                                       data_flow[2] * data_flow[2]);
+        float barb0 = barbValues[2] - barbValues[0];
+        float barb1 = barbValues[3] - barbValues[1];
+/*
+System.out.println("data_flow = " + data_flow[0] + " " + data_flow[1] +
+                   " " + data_flow[2]);
+System.out.println("barbValues = " + barbValues[0] + " " + barbValues[1] +
+                   "   " + barbValues[2] + " " + barbValues[3]);
+System.out.println("data_speed = " + data_speed);
+*/
+      } // end if (first || refirst)
+
+      // convert x to a flow vector, and from spatial to earth
+      if (getRealVectorTypes(which_barb) instanceof EarthVectorType) {
+        // don't worry about vector magnitude -
+        // data_speed & display_speed take care of that
+        float eps = 0.0001f; // estimate derivative with a little vector
+        float[][] spatial_locs =
+          {{barbValues[0], barbValues[0] + eps * (x[0] - barbValues[0])},
+           {barbValues[1], barbValues[1] + eps * (x[1] - barbValues[1])},
+           {0.0f, 0.0f}};
+/*
+System.out.println("spatial_locs = " + spatial_locs[0][0] + " " +
+                   spatial_locs[0][1] + " " + spatial_locs[1][0] + " " +
+                   spatial_locs[1][1]);
+*/
+        float[][] earth_locs = spatialToEarth(spatial_locs);
+        // WLH - 18 Aug 99
+        if (earth_locs == null) return;
+/*
+System.out.println("earth_locs = " + earth_locs[0][0] + " " +
+                   earth_locs[0][1] + " " + earth_locs[1][0] + " " +
+                   earth_locs[1][1]);
+*/
+        x[2] = 0.0f;
+        x[0] = (earth_locs[1][1] - earth_locs[1][0]) *
+               ((float) Math.cos(Data.DEGREES_TO_RADIANS * earth_locs[0][0]));
+        x[1] = earth_locs[0][1] - earth_locs[0][0];
+/*
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else { // if (!(getRealVectorTypes(which_barb) instanceof EarthVectorType))
+        // convert x to vector
+        x[0] -= barbValues[0];
+        x[1] -= barbValues[1];
+
+        // adjust for spatial map scalings but don't worry about vector
+        // magnitude - data_speed & display_speed take care of that
+        // also, spatial is Cartesian
+        double[] ranges = getRanges();
+        for (int i=0; i<3; i++) {
+          x[i] /= ranges[i];
+        }
+/*
+System.out.println("ranges = " + ranges[0] + " " + ranges[1] +
+                   " " + ranges[2]);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+
+      // WLH 6 August 99
+      x[0] = -x[0];
+      x[1] = -x[1];
+      x[2] = -x[2];
+
+/* may need to do this for performance
+      float[] xx = {x[0], x[1], x[2]};
+      addPoint(xx);
+*/
+
+      float x_speed =
+        (float) Math.sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
+/* WLH 16 April 2002 - from Ken
+      if (x_speed < 0.000001f) x_speed = 0.000001f;
+*/
+      if (x_speed < 0.01f) x_speed = 0.01f;
+      if (first || refirst) {
+        display_speed = x_speed;
+      }
+      refirst = false;
+
+      if (mshift != 0) {
+        // only modify data_flow direction
+        float ratio = data_speed / x_speed;
+        x[0] *= ratio;
+        x[1] *= ratio;
+        x[2] *= ratio;
+/*
+System.out.println("direction, ratio = " + ratio + " " +
+                   data_speed + " " + x_speed);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else if (mctrl != 0) {
+        // only modify data_flow speed
+        float ratio = x_speed / display_speed;
+        if (data_speed < EPS) {
+          data_flow[0] = 2.0f * EPS;
+          refirst = true;
+        }
+        x[0] = ratio * data_flow[0];
+        x[1] = ratio * data_flow[1];
+        x[2] = ratio * data_flow[2];
+/*
+System.out.println("speed, ratio = " + ratio + " " +
+                   x_speed + " " + display_speed);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else {
+        // modify data_flow speed and direction
+        float ratio = data_speed / display_speed;
+/*
+System.out.println("data_speed = " + data_speed +
+                   " display_speed = " + display_speed +
+                   " ratio = " + ratio + " EPS = " + EPS);
+System.out.println("x = " + x[0] + " " + x[1] +" " + x[2] +
+                   " x_speed = " + x_speed);
+  data_speed = 21.213203 display_speed = 0.01 ratio = 2121.3203 EPS = 0.2
+  x = 1.6170928E-4 1.6021729E-4 -0.0 x_speed = 0.01
+  wind = (0.3430372, 0.33987218) at (-35.0, 5.0)
+*/
+        if (data_speed < EPS) {
+          data_flow[0] = 2.0f * EPS;
+          x[0] = data_flow[0];
+          x[1] = data_flow[1];
+          x[2] = data_flow[2];
+          refirst = true;
+        }
+        else {
+          x[0] *= ratio;
+          x[1] *= ratio;
+          x[2] *= ratio;
+        }
+      }
+
+      if (coord != null) {
+        float[][] xs = {{x[0]}, {x[1]}, {x[2]}};
+        xs = coord.fromReference(xs);
+        x[0] = xs[0][0];
+        x[1] = xs[1][0];
+        x[2] = xs[2][0];
+      }
+
+      // now replace flow values
+      Vector vect = new Vector();
+      for (int i=0; i<3; i++) {
+        int j = flowToComponent[i];
+        if (j >= 0) {
+          RealType rtype = (RealType) reals[j].getType();
+          reals[j] = new Real(rtype, (double) x[i], rtype.getDefaultUnit(), null);
+
+          // WLH 31 Aug 2000
+          Real r = reals[j];
+          Unit overrideUnit = null;
+          if (directMap[i] != null) {
+            overrideUnit = directMap[i].getOverrideUnit();
+          }
+          Unit rtunit = rtype.getDefaultUnit();
+          // units not part of Time string
+          if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+              !RealType.Time.equals(rtype)) {
+            double d = (float) overrideUnit.toThis((double) x[0], rtunit);
+            r = new Real(rtype, d, overrideUnit);
+            String valueString = r.toValueString();
+            vect.addElement(rtype.getName() + " = " + valueString);
+          }
+          else {
+            // create location string
+            vect.addElement(rtype.getName() + " = " + x[i]);
+          }
+
+        }
+      }
+      getDisplayRenderer().setCursorStringVector(vect);
+
+      Data newData = null;
+      // now build new RealTuple or Flat Tuple
+      if (data instanceof RealTuple) {
+        newData = new RealTuple(((RealTupleType) data.getType()), reals,
+                                ((RealTuple) data).getCoordinateSystem());
+      }
+      else {
+        Data[] new_components = new Data[m];
+        k = 0;
+        for (int i=0; i<m; i++) {
+          Data component = data.getComponent(i);
+          if (component instanceof Real) {
+            new_components[i] = reals[k++];
+          }
+          else if (component instanceof RealTuple) {
+            Real[] sub_reals = new Real[((RealTuple) component).getDimension()];
+            for (int j=0; j<((RealTuple) component).getDimension(); j++) {
+              sub_reals[j] = reals[k++];
+            }
+            new_components[i] =
+              new RealTuple(((RealTupleType) component.getType()), sub_reals,
+                            ((RealTuple) component).getCoordinateSystem());
+          }
+        }
+        newData = new Tuple(new_components, false);
+      }
+      ref.setData(newData);
+    }
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  public float[] makeVector(boolean south, float x, float y, float z,
+                          float scale, float pt_size, float f0, float f1,
+                          float[] vx, float[] vy, float[] vz, int[] numv,
+                          float[] tx, float[] ty, float[] tz, int[] numt) {
+
+
+    float wsp25,slant,barb,d,c195,s195;
+    float x0,y0;
+    float x1,y1,x2,y2,x3,y3;
+    int nbarb50,nbarb10,nbarb5;
+
+    float[] mbarb = new float[4];
+    mbarb[0] = x;
+    mbarb[1] = y;
+
+    if (getKnotsConvert()) {
+      // convert meters per second to knots
+      f0 *= (3600.0 / 1853.248);
+      f1 *= (3600.0 / 1853.248);
+    }
+
+    float wnd_spd = (float) Math.sqrt(f0 * f0 + f1 * f1);
+    int lenv = vx.length;
+    int lent = tx.length;
+    int nv = numv[0];
+    int nt = numt[0];
+
+    //determine the initial (minimum) length of the flag pole
+    if (wnd_spd >= 2.5) {
+
+      wsp25 = (float) Math.max(wnd_spd + 2.5, 5.0);
+      slant = 0.15f * scale;
+      barb = 0.4f * scale;
+      // WLH 6 Aug 99 - barbs point the other way (duh)
+      x0 = -f0 / wnd_spd;
+      y0 = -f1 / wnd_spd;
+
+      //plot the flag pole
+      // lengthen to 'd = 3.0f * barb'
+      // was 'd = barb' in original BOM code
+      d = 3.0f * barb;
+      x1 = (x +x0*d);
+      y1 = (y +y0*d);
+
+/*
+      // commented out in original BOM code
+      vx[nv] = x;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x1;
+      vy[nv] = y1;
+      vz[nv] = z;
+      nv++;
+      // g.drawLine(x,y,x1,y1);
+*/
+
+      //determine number of wind barbs needed for 10 and 50 kt winds
+      nbarb50 = (int)(wsp25/50.f);
+      nbarb10 = (int)((wsp25 - (nbarb50 * 50.f))/10.f);
+      nbarb5 =  (int)((wsp25 - (nbarb50 * 50.f) - (nbarb10 * 10.f))/5.f);
+
+      //2.5 to 7.5 kt winds are plotted with the barb part way done the pole
+      if (nbarb5 == 1) {
+        barb = barb * 0.4f;
+        slant = slant * 0.4f;
+        x1 = (x + x0 * d);
+        y1 = (y + y0 * d);
+
+        if (south) {
+          x2 = (x + x0 * (d + slant) - y0 * barb);
+          y2 = (y + y0 * (d + slant) + x0 * barb);
+        }
+        else {
+          x2 = (x + x0 * (d + slant) + y0 * barb);
+          y2 = (y + y0 * (d + slant) - x0 * barb);
+        }
+
+        vx[nv] = x1;
+        vy[nv] = y1;
+        vz[nv] = z;
+        nv++;
+        vx[nv] = x2;
+        vy[nv] = y2;
+        vz[nv] = z;
+        nv++;
+// System.out.println("barb5 " + x1 + " " + y1 + "" + x2 + " " + y2);
+        // g.drawLine(x1, y1, x2, y2);
+      }
+
+      //add a little more pole
+      if (wsp25 >= 5.0f && wsp25 < 10.0f) {
+        d = d + 0.125f * scale;
+        x1=(x + x0 * d);
+        y1=(y + y0 * d);
+/* WLH 24 April 99
+        vx[nv] = x;
+        vy[nv] = y;
+        vz[nv] = z;
+        nv++;
+        vx[nv] = x1;
+        vy[nv] = y1;
+        vz[nv] = z;
+        nv++;
+*/
+// System.out.println("wsp25 " + x + " " + y + "" + x1 + " " + y1);
+        // g.drawLine(x, y, x1, y1);
+      }
+
+      //now plot any 10 kt wind barbs
+      barb = 0.4f * scale;
+      slant = 0.15f * scale;
+      for (int j=0; j<nbarb10; j++) {
+        d = d + 0.125f * scale;
+        x1=(x + x0 * d);
+        y1=(y + y0 * d);
+        if (south) {
+          x2 = (x + x0 * (d + slant) - y0 * barb);
+          y2 = (y + y0 * (d + slant) + x0 * barb);
+        }
+        else {
+          x2 = (x + x0 * (d + slant) + y0 * barb);
+          y2 = (y + y0 * (d + slant) - x0 * barb);
+        }
+
+        vx[nv] = x1;
+        vy[nv] = y1;
+        vz[nv] = z;
+        nv++;
+        vx[nv] = x2;
+        vy[nv] = y2;
+        vz[nv] = z;
+        nv++;
+// System.out.println("barb10 " + j + " " + x1 + " " + y1 + "" + x2 + " " + y2);
+        // g.drawLine(x1,y1,x2,y2);
+      }
+/* WLH 24 April 99
+      vx[nv] = x;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x1;
+      vy[nv] = y1;
+      vz[nv] = z;
+      nv++;
+*/
+// System.out.println("line " + x + " " + y + "" + x1 + " " + y1);
+      // g.drawLine(x,y,x1,y1);
+
+      //lengthen the pole to accomodate the 50 knot barbs
+      if (nbarb50 > 0) {
+        d = d +0.125f * scale;
+        x1 = (x + x0 * d);
+        y1 = (y + y0 * d);
+/* WLH 24 April 99
+        vx[nv] = x;
+        vy[nv] = y;
+        vz[nv] = z;
+        nv++;
+        vx[nv] = x1;
+        vy[nv] = y1;
+        vz[nv] = z;
+        nv++;
+*/
+// System.out.println("line50 " + x + " " + y + "" + x1 + " " + y1);
+        // g.drawLine(x,y,x1,y1);
+      }
+
+      //plot the 50 kt wind barbs
+/* WLH 5 Nov 99
+      s195 = (float) Math.sin(195 * Data.DEGREES_TO_RADIANS);
+      c195 = (float) Math.cos(195 * Data.DEGREES_TO_RADIANS);
+*/
+      for (int j=0; j<nbarb50; j++) {
+        x1 = (x + x0 * d);
+        y1 = (y + y0 * d);
+        d = d + 0.3f * scale;
+        x3 = (x + x0 * d);
+        y3 = (y + y0 * d);
+/* WLH 5 Nov 99
+        if (south) {
+          x2 = (x3+barb*(x0*s195+y0*c195));
+          y2 = (y3-barb*(x0*c195-y0*s195));
+        }
+        else {
+          x2 = (x3-barb*(x0*s195+y0*c195));
+          y2 = (y3+barb*(x0*c195-y0*s195));
+        }
+*/
+        if (south) {
+          x2 = (x + x0 * (d + slant) - y0 * barb);
+          y2 = (y + y0 * (d + slant) + x0 * barb);
+        }
+        else {
+          x2 = (x + x0 * (d + slant) + y0 * barb);
+          y2 = (y + y0 * (d + slant) - x0 * barb);
+        }
+
+        float[] xp = {x1,x2,x3};
+        float[] yp = {y1,y2,y3};
+
+        tx[nt] = x1;
+        ty[nt] = y1;
+        tz[nt] = z;
+        nt++;
+        tx[nt] = x2;
+        ty[nt] = y2;
+        tz[nt] = z;
+        nt++;
+        tx[nt] = x3;
+        ty[nt] = y3;
+        tz[nt] = z;
+        nt++;
+/*
+System.out.println("barb50 " + x1 + " " + y1 + "" + x2 + " " + y2 +
+                 "  " + x3 + " " + y3);
+*/
+        // g.fillPolygon(xp,yp,3);
+        //start location for the next barb
+        x1=x3;
+        y1=y3;
+      }
+
+      // grf 17 Nov 2003 change this to shorten the pole and print the speed
+      if (noNumbers) {
+        // WLH 24 April 99 - now plot the pole
+        vx[nv] = x;
+        vy[nv] = y;
+        vz[nv] = z;
+        nv++;
+        vx[nv] = x1;
+        vy[nv] = y1;
+        vz[nv] = z;
+        nv++;
+
+        mbarb[2] = x1;
+        mbarb[3] = y1;
+      } else { // add numerical value to wind barbs
+        // guess some factors to shorten the start of the pole
+        float start_pole = 0.4f*scale;
+        x1 = (x + x0*start_pole);
+        y1 = (y + y0*start_pole);
+        x2 = (x + x0*d);
+        y2 = (y + y0*d);
+
+        // draw the shaft
+        vx[nv] = x1;
+        vy[nv] = y1;
+        vz[nv] = z;
+        nv++;
+        vx[nv] = x2;
+        vy[nv] = y2;
+        vz[nv] = z;
+        nv++;
+
+        mbarb[2] = x2;
+        mbarb[3] = y2;
+  
+        // draw the speed to 1 dec place by default
+        // Experimental factors in front of scale - get same as Swell
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setMaximumFractionDigits(numDecPlaces);
+        String speedString = nf.format((double)wnd_spd);
+        // grf 2 Jun 2004 set z value the same as the barb
+        double[] start = {x, y - 0.20*scale, z};
+        double[] base = {0.375*scale, 0.0, 0.0};
+        double up[] = {0.0, 0.375*scale, 0.0};
+        VisADLineArray array = 
+          PlotText.render_label(speedString, start, base, up, true);
+        int nl = array.vertexCount;
+        int k = 0;
+        for (int i=0; i<nl; i++) {
+          vx[nv] = array.coordinates[k++];
+          vy[nv] = array.coordinates[k++];
+          vz[nv] = array.coordinates[k++];
+
+          nv++;
+        }
+      }
+    }
+    else { // if (wnd_spd < 2.5)
+
+      // wind < 2.5 kts.  Plot a circle
+      float rad = (0.7f * pt_size);
+
+      // draw 8 segment circle, center = (x, y), radius = rad
+      // 1st segment
+      vx[nv] = x - rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 2nd segment
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x;
+      vy[nv] = y + rad;
+      vz[nv] = z;
+      nv++;
+      // 3rd segment
+      vx[nv] = x;
+      vy[nv] = y + rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 4th segment
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x + rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      // 5th segment
+      vx[nv] = x + rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 6th segment
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x;
+      vy[nv] = y - rad;
+      vz[nv] = z;
+      nv++;
+      // 7th segment
+      vx[nv] = x;
+      vy[nv] = y - rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 8th segment
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x - rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+// System.out.println("circle " + x + " " + y + "" + rad);
+      // g.drawOval(x-rad,y-rad,2*rad,2*rad);
+
+      mbarb[2] = x;
+      mbarb[3] = y;
+    }
+
+    numv[0] = nv;
+    numt[0] = nt;
+    return mbarb;
+  }
+
+  public Object clone() {
+    return new BarbManipulationRendererJ3D();
+  }
+
+  static final int N = 5;
+
+  /** test BarbManipulationRendererJ3D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+System.out.println("BMR.main()");
+
+    // construct RealTypes for wind record components
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType windx = RealType.getRealType("windx",
+                          CommonUnit.meterPerSecond);     
+    RealType windy = RealType.getRealType("windy",
+                          CommonUnit.meterPerSecond);     
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+
+    // EarthVectorType extends RealTupleType and says that its
+    // components are vectors in m/s with components parallel
+    // to Longitude (positive east) and Latitude (positive north)
+    EarthVectorType windxy = new EarthVectorType(windx, windy);
+
+    RealType wind_dir = RealType.getRealType("wind_dir",
+                          CommonUnit.degree);
+    RealType wind_speed = RealType.getRealType("wind_speed",
+                          CommonUnit.meterPerSecond);
+    RealTupleType windds = null;
+    if (args.length > 0) {
+      System.out.println("polar winds");
+      windds =
+        new RealTupleType(new RealType[] {wind_dir, wind_speed},
+        new WindPolarCoordinateSystem(windxy), null);
+    }
+
+    // construct Java3D display and mappings that govern
+    // how wind records are displayed
+    DisplayImpl display =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap lonmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(lonmap);
+    ScalarMap latmap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(latmap);
+
+    FlowControl flow_control;
+    if (args.length > 0) {
+      ScalarMap winds_map = new ScalarMap(wind_speed, Display.Flow1Radial);
+      display.addMap(winds_map);
+      winds_map.setRange(0.0, 1.0); // do this for barb rendering
+      ScalarMap windd_map = new ScalarMap(wind_dir, Display.Flow1Azimuth);
+      display.addMap(windd_map);
+      windd_map.setRange(0.0, 360.0); // do this for barb rendering
+      flow_control = (FlowControl) windd_map.getControl();
+      flow_control.setFlowScale(0.15f); // this controls size of barbs
+    }
+    else {
+      ScalarMap windx_map = new ScalarMap(windx, Display.Flow1X);
+      display.addMap(windx_map);
+      windx_map.setRange(-1.0, 1.0); // do this for barb rendering
+      ScalarMap windy_map = new ScalarMap(windy, Display.Flow1Y);
+      display.addMap(windy_map);
+      windy_map.setRange(-1.0, 1.0); // do this for barb rendering
+      flow_control = (FlowControl) windy_map.getControl();
+      flow_control.setFlowScale(0.15f); // this controls size of barbs
+    }
+
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    DataReferenceImpl[] refs = new DataReferenceImpl[N * N];
+    int k = 0;
+    // create an array of N by N winds
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        double u = 2.0 * i / (N - 1.0) - 1.0;
+        double v = 2.0 * j / (N - 1.0) - 1.0;
+
+        // each wind record is a Tuple (lon, lat, (windx, windy), red, green)
+        // set colors by wind components, just for grins
+        Tuple tuple;
+        double fx = 30.0 * u;
+        double fy = 30.0 * v;
+        if (args.length > 0) {
+          double fd = Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy);
+          double fs = Math.sqrt(fx * fx + fy * fy);
+          tuple = new Tuple(new Data[]
+            {new Real(lon, 10.0 * u), new Real(lat, 10.0 * v - 40.0),
+             new RealTuple(windds, new double[] {fd, fs}),
+             new Real(red, u), new Real(green, v)});
+        }
+        else {
+          tuple = new Tuple(new Data[]
+            {new Real(lon, 10.0 * u), new Real(lat, 10.0 * v - 40.0),
+             new RealTuple(windxy, new double[] {fx, fy}),
+             new Real(red, u), new Real(green, v)});
+        }
+
+        // construct reference for wind record
+        refs[k] = new DataReferenceImpl("ref_" + k);
+        refs[k].setData(tuple);
+
+        // link wind record to display via BarbManipulationRendererJ3D
+        // so user can change barb by dragging it
+        // drag with right mouse button and shift to change direction
+        // drag with right mouse button and no shift to change speed
+        BarbManipulationRendererJ3D renderer = new BarbManipulationRendererJ3D();
+        renderer.setKnotsConvert(true);
+        display.addReferences(renderer, refs[k]);
+
+        // link wind record to a CellImpl that will listen for changes
+        // and print them
+        WindGetterJ3D cell = new WindGetterJ3D(flow_control, refs[k]);
+        cell.addReference(refs[k]);
+
+        k++;
+      }
+    }
+
+    // instead of linking the wind record "DataReferenceImpl refs" to
+    // the WindGetterJ3Ds, you can have some user interface event (e.g.,
+    // the user clicks on "DONE") trigger code that does a getData() on
+    // all the refs and stores the records in a file.
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test BarbManipulationRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
+class WindGetterJ3D extends CellImpl {
+  DataReferenceImpl ref;
+
+  float scale = 0.15f;
+  int count = 20;
+  FlowControl flow_control;
+
+  public WindGetterJ3D(FlowControl f, DataReferenceImpl r) {
+    ref = r;
+    flow_control = f;
+  }
+
+  public void doAction() throws VisADException, RemoteException {
+    Tuple tuple = (Tuple) ref.getData();
+    float lon = (float) ((Real) tuple.getComponent(0)).getValue();
+    float lat = (float) ((Real) tuple.getComponent(1)).getValue();
+    RealTuple wind = (RealTuple) tuple.getComponent(2);
+    float windx = (float) ((Real) wind.getComponent(0)).getValue();
+    float windy = (float) ((Real) wind.getComponent(1)).getValue();
+    System.out.println("wind = (" + windx + ", " + windy + ") at (" +
+                       + lat + ", " + lon +")");
+/* a testing hack
+    count--;
+    if (count < 0) {
+      count = 20;
+      scale = 0.15f * 0.3f / scale;
+      flow_control.setFlowScale(scale);
+    }
+*/
+  }
+
+}
+
diff --git a/visad/bom/BarbRenderer.java b/visad/bom/BarbRenderer.java
new file mode 100644
index 0000000..c74dd94
--- /dev/null
+++ b/visad/bom/BarbRenderer.java
@@ -0,0 +1,45 @@
+//
+// BarbRenderer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+/**
+   BarbRenderer is the VisAD interface for rendering of
+   wind barbs.
+*/
+public interface BarbRenderer {
+
+  float[] makeVector(boolean south, float x, float y, float z,
+                     float scale, float pt_size, float f0, float f1,
+                     float[] vx, float[] vy, float[] vz, int[] numv,
+                     float[] tx, float[] ty, float[] tz, int[] numt);
+
+  void setKnotsConvert(boolean enable);
+
+  boolean getKnotsConvert();
+
+}
+
diff --git a/visad/bom/BarbRendererJ2D.java b/visad/bom/BarbRendererJ2D.java
new file mode 100644
index 0000000..2eaa490
--- /dev/null
+++ b/visad/bom/BarbRendererJ2D.java
@@ -0,0 +1,232 @@
+//
+// BarbRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.*;
+
+
+/**
+   BarbRendererJ2D is the VisAD class for rendering of
+   wind barbs under Java2D - otherwise it behaves just
+   like DefaultRendererJ2D
+*/
+public class BarbRendererJ2D extends DefaultRendererJ2D
+       implements BarbRenderer {
+
+  /** this DataRenderer supports direct manipulation for RealTuple
+      representations of wind barbs; four of the RealTuple's Real
+      components must be mapped to XAxis, YAxis, Flow1X and Flow1Y */
+  public BarbRendererJ2D () {
+    super();
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbFunctionTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTupleTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbSetTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbTupleTypeJ2D(type, link, parent);
+  }
+
+  /** dummy for BarbRenderer */
+  public float[] makeVector(boolean south, float x, float y, float z,
+                     float scale, float pt_size, float f0, float f1,
+                     float[] vx, float[] vy, float[] vz, int[] numv,
+                     float[] tx, float[] ty, float[] tz, int[] numt) {
+    return null;
+  }
+
+  private boolean knotsConvert = true;
+
+  public void setKnotsConvert(boolean enable) {
+    knotsConvert = enable;
+  }
+
+  public boolean getKnotsConvert() {
+    return knotsConvert;
+  }
+
+  public Object clone() {
+    return new BarbRendererJ2D();
+  }
+
+  static final int N = 5;
+
+  /** run 'java visad.bom.BarbRendererJ2D middle_latitude'
+          to test with Cartesian winds
+      run 'java visad.bom.BarbRendererJ2D middle_latitude x'
+          to test with polar winds
+      adjust middle_latitude for south or north barbs */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    double mid_lat = -10.0;
+    if (args.length > 0) {
+      try {
+        mid_lat = Double.valueOf(args[0]).doubleValue();
+      }
+      catch(NumberFormatException e) { }
+    }
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType flowx = RealType.getRealType("flowx",
+                          CommonUnit.meterPerSecond);
+    RealType flowy = RealType.getRealType("flowy",
+                          CommonUnit.meterPerSecond);
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+    RealType index = RealType.getRealType("index");
+    EarthVectorType flowxy = new EarthVectorType(flowx, flowy);
+    TupleType range = null;
+    RealType flow_degree = RealType.getRealType("flow_degree",
+                          CommonUnit.degree);
+    RealType flow_speed = RealType.getRealType("flow_speed",
+                          CommonUnit.meterPerSecond);
+    if (args.length > 1) {
+      System.out.println("polar winds");
+      RealTupleType flowds =
+        new RealTupleType(new RealType[] {flow_degree, flow_speed},
+        new WindPolarCoordinateSystem(flowxy), null);
+      range = new TupleType(new MathType[] {lon, lat, flowds, red, green});
+    }
+    else {
+      System.out.println("Cartesian winds");
+      range = new TupleType(new MathType[] {lon, lat, flowxy, red, green});
+    }
+    FunctionType flow_field = new FunctionType(index, range);
+
+    DisplayImpl display = new DisplayImplJ2D("display1");
+    ScalarMap xmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(xmap);
+    ScalarMap ymap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(ymap);
+    if (args.length > 1) {
+      ScalarMap flowd_map = new ScalarMap(flow_degree, Display.Flow1Azimuth);
+      display.addMap(flowd_map);
+      flowd_map.setRange(0.0, 360.0);
+      ScalarMap flows_map = new ScalarMap(flow_speed, Display.Flow1Radial);
+      display.addMap(flows_map);
+      flows_map.setRange(0.0, 1.0);
+      FlowControl flow_control = (FlowControl) flows_map.getControl();
+      flow_control.setFlowScale(0.1f);
+    }
+    else {
+      ScalarMap flowx_map = new ScalarMap(flowx, Display.Flow1X);
+      display.addMap(flowx_map);
+      flowx_map.setRange(-1.0, 1.0);
+      ScalarMap flowy_map = new ScalarMap(flowy, Display.Flow1Y);
+      display.addMap(flowy_map);
+      flowy_map.setRange(-1.0, 1.0);
+      FlowControl flow_control = (FlowControl) flowy_map.getControl();
+      flow_control.setFlowScale(0.1f);
+    }
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    Integer1DSet set = new Integer1DSet(N * N);
+    double[][] values = new double[6][N * N];
+    int m = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        double u = 2.0 * i / (N - 1.0) - 1.0;
+        double v = 2.0 * j / (N - 1.0) - 1.0;
+        values[0][m] = 10.0 * u;
+        values[1][m] = 10.0 * v + mid_lat;
+        double fx = 30.0 * u;
+        double fy = 30.0 * v;
+        if (args.length > 1) {
+          values[2][m] =
+            Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy);
+          values[3][m] = Math.sqrt(fx * fx + fy * fy);
+        }
+        else {
+          values[2][m] = fx;
+          values[3][m] = fy;
+        }
+        values[4][m] = u;
+        values[5][m] = v;
+        m++;
+      }
+    }
+    FlatField field = new FlatField(flow_field, set);
+    field.setSamples(values);
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(field);
+    BarbRendererJ2D renderer = new BarbRendererJ2D();
+    renderer.setKnotsConvert(true);
+    display.addReferences(renderer, ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test BarbRendererJ2D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/bom/BarbRendererJ3D.java b/visad/bom/BarbRendererJ3D.java
new file mode 100644
index 0000000..0ac8b1d
--- /dev/null
+++ b/visad/bom/BarbRendererJ3D.java
@@ -0,0 +1,233 @@
+//
+// BarbRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.*;
+
+
+/**
+   BarbRendererJ3D is the VisAD class for rendering of
+   wind barbs under Java3D - otherwise it behaves just
+   like DefaultRendererJ3D
+*/
+public class BarbRendererJ3D extends DefaultRendererJ3D
+       implements BarbRenderer {
+
+  private BarbManipulationRendererJ3D bmr;
+
+  /** this DataRenderer supports direct manipulation for RealTuple
+      representations of wind barbs; four of the RealTuple's Real
+      components must be mapped to XAxis, YAxis, Flow1X and Flow1Y */
+  public BarbRendererJ3D () {
+    super();
+    bmr = new BarbManipulationRendererJ3D();
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbFunctionTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTupleTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbSetTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbTupleTypeJ3D(type, link, parent);
+  }
+
+  public void setKnotsConvert(boolean enable) {
+    bmr.setKnotsConvert(enable);
+  } 
+  
+  public boolean getKnotsConvert() {
+    return bmr.getKnotsConvert();
+  } 
+
+  public float[] makeVector(boolean south, float x, float y, float z,
+                          float scale, float pt_size, float f0, float f1,
+                          float[] vx, float[] vy, float[] vz, int[] numv,
+                          float[] tx, float[] ty, float[] tz, int[] numt) { 
+    return bmr.makeVector(south, x, y, z, scale, pt_size, f0, f1, vx, vy, vz,
+                          numv, tx, ty, tz, numt);
+  }
+
+  public Object clone() {
+    return new BarbRendererJ3D();
+  }
+
+  static final int N = 5;
+
+  /** run 'java visad.bom.BarbRendererJ3D middle_latitude'
+          to test with Cartesian winds
+      run 'java visad.bom.BarbRendererJ3D middle_latitude x'
+          to test with polar winds
+      adjust middle_latitude for south or north barbs */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    double mid_lat = -10.0;
+    if (args.length > 0) {
+      try {
+        mid_lat = Double.valueOf(args[0]).doubleValue();
+      }
+      catch(NumberFormatException e) { }
+    }
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType flowx = RealType.getRealType("flowx",
+                          CommonUnit.meterPerSecond);
+    RealType flowy = RealType.getRealType("flowy",
+                          CommonUnit.meterPerSecond);
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+    RealType index = RealType.getRealType("index");
+    EarthVectorType flowxy = new EarthVectorType(flowx, flowy);
+    TupleType range = null;
+    RealType flow_degree = RealType.getRealType("flow_degree",
+                          CommonUnit.degree);
+    RealType flow_speed = RealType.getRealType("flow_speed",
+                          CommonUnit.meterPerSecond);
+    if (args.length > 1) {
+      System.out.println("polar winds");
+      RealTupleType flowds =
+        new RealTupleType(new RealType[] {flow_degree, flow_speed},
+        new WindPolarCoordinateSystem(flowxy), null);
+      range = new TupleType(new MathType[] {lon, lat, flowds, red, green});
+    }
+    else {
+      System.out.println("Cartesian winds");
+      range = new TupleType(new MathType[] {lon, lat, flowxy, red, green});
+    }
+    FunctionType flow_field = new FunctionType(index, range);
+
+    DisplayImpl display = new DisplayImplJ3D("display1");
+    ScalarMap xmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(xmap);
+    ScalarMap ymap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(ymap);
+    if (args.length > 1) {
+      ScalarMap flowd_map = new ScalarMap(flow_degree, Display.Flow1Azimuth);
+      display.addMap(flowd_map);
+      flowd_map.setRange(0.0, 360.0);
+      ScalarMap flows_map = new ScalarMap(flow_speed, Display.Flow1Radial);
+      display.addMap(flows_map);
+      flows_map.setRange(0.0, 1.0);
+      FlowControl flow_control = (FlowControl) flows_map.getControl();
+      flow_control.setFlowScale(0.1f);
+    }
+    else {
+      ScalarMap flowx_map = new ScalarMap(flowx, Display.Flow1X);
+      display.addMap(flowx_map);
+      flowx_map.setRange(-1.0, 1.0);
+      ScalarMap flowy_map = new ScalarMap(flowy, Display.Flow1Y);
+      display.addMap(flowy_map);
+      flowy_map.setRange(-1.0, 1.0);
+      FlowControl flow_control = (FlowControl) flowy_map.getControl();
+      flow_control.setFlowScale(0.1f);
+    }
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    Integer1DSet set = new Integer1DSet(N * N);
+    double[][] values = new double[6][N * N];
+    int m = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        double u = 2.0 * i / (N - 1.0) - 1.0;
+        double v = 2.0 * j / (N - 1.0) - 1.0;
+        values[0][m] = 10.0 * u;
+        values[1][m] = 10.0 * v + mid_lat;
+        double fx = 30.0 * u;
+        double fy = 30.0 * v;
+        if (args.length > 1) {
+          values[2][m] =
+            Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy);
+          values[3][m] = Math.sqrt(fx * fx + fy * fy);
+        }
+        else {
+          values[2][m] = fx;
+          values[3][m] = fy;
+        }
+        values[4][m] = u;
+        values[5][m] = v;
+        m++;
+      }
+    }
+    FlatField field = new FlatField(flow_field, set);
+    field.setSamples(values);
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(field);
+    BarbRendererJ3D renderer = new BarbRendererJ3D();
+    renderer.setKnotsConvert(true);
+    display.addReferences(renderer, ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test BarbRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/bom/CBMKeyboardBehaviorJ3D.java b/visad/bom/CBMKeyboardBehaviorJ3D.java
new file mode 100644
index 0000000..a0b1a00
--- /dev/null
+++ b/visad/bom/CBMKeyboardBehaviorJ3D.java
@@ -0,0 +1,108 @@
+//
+//  CBMKeyboardBehaviorJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import java.awt.event.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.java3d.*;
+
+
+public class CBMKeyboardBehaviorJ3D extends KeyboardBehaviorJ3D {
+
+/*
+  // Identifier for function to rotate positively around the Z viewing axis
+  public static final int ROTATE_Z_POS = 7;
+*/
+
+  private static final int MAX_FUNCTIONS = 6;
+
+  public static final int PLUS_ANGLE = 0; // ->
+  public static final int MINUS_ANGLE = 1; // <-
+  public static final int PLUS_SPEED = 2; // up
+  public static final int MINUS_SPEED = 3; // down
+  public static final int NEXT_WIND = 4; // page down
+  public static final int PREVIOUS_WIND = 5; // page up
+
+  private CollectiveBarbManipulation cbm = null;
+
+  // WLH 19 Feb 2001
+  public CBMKeyboardBehaviorJ3D(DisplayRendererJ3D r) {
+    super(r, MAX_FUNCTIONS);
+
+    mapKeyToFunction(PLUS_ANGLE, KeyEvent.VK_RIGHT, NO_MASK);
+    mapKeyToFunction(MINUS_ANGLE, KeyEvent.VK_LEFT, NO_MASK);
+    mapKeyToFunction(PLUS_SPEED, KeyEvent.VK_UP, NO_MASK);
+    mapKeyToFunction(MINUS_SPEED, KeyEvent.VK_DOWN, NO_MASK);
+    mapKeyToFunction(NEXT_WIND, KeyEvent.VK_PAGE_DOWN, NO_MASK);
+    mapKeyToFunction(PREVIOUS_WIND, KeyEvent.VK_PAGE_UP, NO_MASK);
+  }
+
+  public void setWhichCBM(CollectiveBarbManipulation c) throws VisADException {
+    cbm = c;
+  }
+
+  /** 
+   * Executes the given function. 
+   * @param  function   function to perform (TRANSLATE_UP, ZOOM_IN, etc)
+   */
+  public void execFunction(int function) {
+
+    if (cbm == null) return;
+
+// System.out.println("execFunction " + function);
+
+    try {
+      switch (function) {
+        case PLUS_ANGLE:
+          cbm.plusAngle();
+          break;
+        case MINUS_ANGLE:
+          cbm.minusAngle();
+          break;
+        case PLUS_SPEED:
+          cbm.plusSpeed();
+          break;
+        case MINUS_SPEED:
+          cbm.minusSpeed();
+          break;
+        case NEXT_WIND:
+          cbm.nextWind();
+          break;
+        case PREVIOUS_WIND:
+          cbm.previousWind();
+          break;
+        default:
+          break;
+      }
+    }
+    catch (VisADException e) {
+    }
+    catch (RemoteException e) {
+    }
+  }
+}
diff --git a/visad/bom/CollectiveBarbException.java b/visad/bom/CollectiveBarbException.java
new file mode 100644
index 0000000..75a1940
--- /dev/null
+++ b/visad/bom/CollectiveBarbException.java
@@ -0,0 +1,40 @@
+//
+// CollectiveBarbException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+
+/**
+   CollectiveBarbException is an exception for an error with a VisAD display.<P>
+*/
+public class CollectiveBarbException extends VisADException {
+
+  public CollectiveBarbException() { super(); }
+  public CollectiveBarbException(String s) { super(s); }
+
+}
+
diff --git a/visad/bom/CollectiveBarbManipulation.java b/visad/bom/CollectiveBarbManipulation.java
new file mode 100644
index 0000000..fa9bf03
--- /dev/null
+++ b/visad/bom/CollectiveBarbManipulation.java
@@ -0,0 +1,1843 @@
+//
+// CollectiveBarbManipulation.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.util.*;
+import visad.java3d.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+
+/**
+   CollectiveBarbManipulation is the VisAD class for
+   manipulation of collections of wind barbs
+*/
+public class CollectiveBarbManipulation extends Object {
+
+  private FunctionType wind_field_type = null;
+  private TupleType wind_type = null;
+  private RealType station_index = null;
+  private FunctionType wind_station_type = null;
+  private RealType time = null;
+
+  private int nindex = 0;
+  private int[] ntimes;
+  private Tuple[][] tuples;
+  private Tuple[][] tuples2;
+  private FlatField[] wind_stations;
+  private Set[] time_sets;
+  private double[][] times;
+  private int which_time = -1;
+  private int[] which_times;
+
+  private Set time_set = null;
+  private int global_ntimes = 0;
+  private double[] global_times;
+  private int[][] global_to_station; // [nindex][global_ntimes]
+  private int[][] station_to_global; // [nindex][ntimes[i]]
+
+  private float[] lats;
+  private float[] lons;
+
+  private DataReferenceImpl stations_ref;
+  private DataReferenceImpl[] station_refs;
+  private DataRenderer barb_renderer;
+  private DataRenderer[] barb_manipulation_renderers;
+  private BarbMonitor[] barb_monitors;
+
+  private WindMonitor wind_monitor = null;
+
+  private AnimationControl acontrol = null;
+  private ProjectionControl pcontrol = null;
+
+  private DisplayImplJ3D display1;
+  private DisplayImplJ3D display2;
+  private FieldImpl wind_field;
+  private boolean absolute;
+  private float inner_distance;
+  private float outer_distance;
+  private double inner_time;
+  private double outer_time;
+  private DataReference curve_ref = null;
+
+  private boolean barbs;
+  private boolean force_station;
+  private boolean knots;
+
+  private ConstantMap[] cmaps;
+
+  private int azimuth_index;
+  private int radial_index;
+  private int azimuth_index2;
+  private int radial_index2;
+  private int lat_index;
+  private int lon_index;
+
+  private int last_sta = -1;
+  private int last_time = -1;
+  private int last_display = -1;
+  private boolean last_curve = true;
+
+  private float[][] azimuths;
+  private float[][] radials;
+  private float[][] old_azimuths;
+  private float[][] old_radials;
+
+  // data for display2
+  private int station = -1;
+  private DataReferenceImpl[] time_refs = null;
+  private DataRenderer[] barb_manipulation_renderers2 = null;
+  private BarbMonitor2[] barb_monitors2 = null;
+
+  private Object data_lock = new Object();
+
+  private boolean ended = false; // manipulation ended
+
+  private int time_dir = 0; // < 0 for backward only, > 0 for forward only,
+                            // == 0 for both directions
+
+  private static final int NCIRCLE = 24;
+  private DataRenderer[] circle_renderer = null;
+  private DataReferenceImpl[] circle_ref = null;
+  private boolean circle_enable = false;
+
+  private Releaser releaser = null;
+  private DataReferenceImpl releaser_ref = null;
+
+  private Stepper stepper = null;
+  private DataReferenceImpl stepper_ref = null;
+
+  private boolean dzoom = false;
+  private DiscoverableZoom pcl = null;
+
+  /**
+     wf should have MathType:
+       (station_index -> (Time -> tuple))
+     where tuple is flat
+       [e.g., (Latitude, Longitude, (flow_dir, flow_speed))]
+     and must include RealTypes Latitude and Longitude plus
+     RealTypes mapped to Flow1Azimuth and Flow1Radial, or to
+     Flow2Azimuth and Flow2Radial, in the DisplayImplJ3Ds d1
+     and d2 (unless they are not null);
+     d1 must have Time mapped to Animation, and d2 must not;
+
+     abs indicates absolute or relative value adjustment
+     id and od are inner and outer distances in meters
+     it and ot are inner and outer times in seconds
+     influence is 1.0 inside inner, 0.0 outside outer and
+     linear between distance and time influences multiply;
+
+     cms are ConstantMap's used for rendering barbs
+
+     each time the user clicks the right mouse button to
+     manipulate a wind barb, the "reference" values for all
+     wind barbs are set - thus repeatedly adjusting the same
+     barb will magnify its influence over its neighbors;
+
+     sta is index of station for display2;
+
+     need_monitor is true if wf might be changed externally
+     during manipulation
+
+     brbs is true to indicate Barb*RendererJ3D, false to
+     indicate Swell*RendererJ3D
+
+     fs is true to indicate that d2 should switch to whatever
+     station is being manipulated
+
+     kts is false to indicate no m/s to knots conversion in
+     wind barb renderers
+
+     dz is true to indicate to use DiscoverableZoom
+
+     inner_circle_color is array of RGB colors for an inner circle
+     of influence, or null for no inner circles
+     inner_circle_width is the line width for the inner circle
+
+     outer_circle_color is array of RGB colors for an outer circle
+     of influence, or null for no outer circles
+     outer_circle_width is the line width for the outer circle
+  */
+  public CollectiveBarbManipulation(FieldImpl wf,
+                 DisplayImplJ3D d1, DisplayImplJ3D d2, ConstantMap[] cms,
+                 boolean abs, float id, float od, float it, float ot, int sta,
+                 boolean need_monitor, boolean brbs, boolean fs, boolean kts,
+                 boolean dz,
+                 double[] inner_circle_color, int inner_circle_width,
+                 double[] outer_circle_color, int outer_circle_width)
+         throws VisADException, RemoteException {
+    wind_field = wf;
+    display1 = d1;
+    display2 = d2;
+    cmaps = cms;
+    absolute = abs;
+    inner_distance = id;
+    outer_distance = od;
+    inner_time = it;
+    outer_time = ot;
+    curve_ref = null;
+    barbs = brbs;
+    force_station = fs;
+    knots = kts;
+    dzoom = dz;
+
+    // station = sta; // NEW
+
+    if (display1 == null && display2 == null) {
+      throw new CollectiveBarbException("display1 and display2 cannot " +
+                  "both be null");
+    }
+
+    wind_field_type = (FunctionType) wind_field.getType();
+    wind_type = null;
+    station_index = null;
+  
+    try {
+      station_index =
+        (RealType) wind_field_type.getDomain().getComponent(0);
+      if (RealType.Time.equals(station_index)) {
+        throw new CollectiveBarbException("wind_field bad MathType: " +
+                     station_index + " cannot be RealType.Time");
+      }
+      wind_station_type = (FunctionType) wind_field_type.getRange();
+      time = (RealType) wind_station_type.getDomain().getComponent(0);
+      if (!RealType.Time.equals(time)) {
+        throw new CollectiveBarbException("wind_field bad MathType: " +
+                     time + " must be RealType.Time");
+      }
+      wind_type = (TupleType) wind_station_type.getRange();
+      if (!wind_type.getFlat()) {
+        throw new CollectiveBarbException("wind_field bad MathType: " +
+                      wind_type + " must be flat");
+      }
+    }
+    catch (ClassCastException e) {
+      throw new CollectiveBarbException("wind_field bad MathType: " +
+                     wind_field_type);
+    }
+ 
+    lat_index = -1;
+    lon_index = -1;
+    // int tuple_dim = wind_type.getDimension();
+    RealType[] real_types = wind_type.getRealComponents();
+    int tuple_dim = real_types.length;
+    for (int i=0; i<tuple_dim; i++) {
+      RealType real = real_types[i];
+      if (RealType.Latitude.equals(real)) {
+        lat_index = i;
+      }
+      else if (RealType.Longitude.equals(real)) {
+        lon_index = i;
+      }
+    }
+
+    if (lat_index < 0 || lon_index < 0) {
+      throw new CollectiveBarbException("wind data must include Latitude " +
+               "and Longitude " + lat_index + " " + lon_index);
+    }
+
+    azimuth_index = -1;
+    radial_index = -1;
+    int azimuth12 = -1;
+    int radial12 = -2;
+    if (display1 != null) {
+      Vector scalar_map_vector = display1.getMapVector();
+      for (int i=0; i<tuple_dim; i++) {
+        RealType real = real_types[i];
+        Enumeration en = scalar_map_vector.elements();
+        while (en.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) en.nextElement();
+          if (real.equals(map.getScalar())) {
+            DisplayRealType dreal = map.getDisplayScalar();
+            if (Display.Flow1Azimuth.equals(dreal)) {
+              azimuth_index = i;
+              azimuth12 = 1;
+            }
+            else if (Display.Flow2Azimuth.equals(dreal)) {
+              azimuth_index = i;
+              azimuth12 = 2;
+            }
+            else if (Display.Flow1Radial.equals(dreal)) {
+              radial_index = i;
+              radial12 = 1;
+            }
+            else if (Display.Flow2Radial.equals(dreal)) {
+              radial_index = i;
+              radial12 = 2;
+            }
+          }
+        }
+      } // for (int i=0; i<n; i++) {
+      if (azimuth_index < 0 || radial_index < 0 || azimuth12 != radial12) {
+        throw new CollectiveBarbException("wind data must include two " +
+                 "RealTypes mapped to Flow1Azimuth and Flow1Radial, or to " +
+                 "Flow2Azimuth and Flow2Radial, in " +
+                 "display1 " + azimuth_index + " " + radial_index);
+      }
+    }
+
+    azimuth_index2 = -1;
+    radial_index2 = -1;
+    azimuth12 = -1;
+    radial12 = -2;
+    if (display2 != null) {
+      Vector scalar_map_vector = display2.getMapVector();
+      for (int i=0; i<tuple_dim; i++) {
+        RealType real = real_types[i];
+        Enumeration en = scalar_map_vector.elements();
+        while (en.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) en.nextElement();
+          if (real.equals(map.getScalar())) {
+            DisplayRealType dreal = map.getDisplayScalar();
+            if (Display.Flow1Azimuth.equals(dreal)) {
+              azimuth_index2 = i;
+              azimuth12 = 1;
+            }
+            else if (Display.Flow2Azimuth.equals(dreal)) {
+              azimuth_index2 = i;
+              azimuth12 = 2;
+            }
+            else if (Display.Flow1Radial.equals(dreal)) {
+              radial_index2 = i;
+              radial12 = 1;
+            }
+            else if (Display.Flow2Radial.equals(dreal)) {
+              radial_index2 = i;
+              radial12 = 2;
+            }
+          }
+        }
+      } // for (int i=0; i<n; i++) {
+      if (azimuth_index2 < 0 || radial_index2 < 0 || azimuth12 != radial12) {
+        throw new CollectiveBarbException("wind data must include two " +
+                 "RealTypes mapped to Flow1Azimuth and Flow1Radial, or to " +
+                 "Flow2Azimuth and Flow2Radial, in " +
+                 "display2 " + azimuth_index2 + " " + radial_index2);
+      }
+      if (display1 != null) {
+        if (azimuth_index2 != azimuth_index) {
+          throw new CollectiveBarbException("same RealTypes must be mapped " +
+                   "to Flow1Azimuth in display1 and display2 " +
+                   real_types[azimuth_index] + " " + real_types[azimuth_index2]);
+        }
+        if (radial_index2 != radial_index) {
+          throw new CollectiveBarbException("same RealTypes must be mapped " +
+                   "to Flow1Radial in display1 and display2 " +
+                   real_types[radial_index] + " " + real_types[radial_index2]);
+        }
+      }
+      else {
+        azimuth_index = azimuth_index2;
+        radial_index = radial_index2;
+      }
+    }
+
+    if (inner_time < 0.0 ||
+        outer_time < inner_time) {
+      throw new CollectiveBarbException("outer_time must be " +
+                                   "greater than inner_time");
+    }
+    if (inner_distance < 0.0 ||
+        outer_distance < inner_distance) {
+      throw new CollectiveBarbException("outer_distance must be " +
+                                   "greater than distance_time");
+    }
+
+    setupData(); // new data
+    if (display1 != null) {
+      pcontrol = display1.getProjectionControl();
+      if (dzoom) {
+        pcl = new DiscoverableZoom();
+        pcontrol.addControlListener(pcl);
+      }
+      setupStations(); // new data  WLH 6 May 2001
+    }
+
+    // better have CellImpl in place before addControlListener // NEW
+    stepper_ref = new DataReferenceImpl("stepper");
+    stepper = new Stepper();
+    stepper.addReference(stepper_ref);
+
+    if (display1 != null) {
+      acontrol = (AnimationControl) display1.getControl(AnimationControl.class);
+      if (acontrol == null) {
+        throw new CollectiveBarbException("display must include " +
+                       "ScalarMap to Animation");
+      }
+
+      Vector tmap = display1.getMapVector();
+      for (int i=0; i<tmap.size(); i++) {
+        ScalarMap map = (ScalarMap) tmap.elementAt(i);
+        Control c = map.getControl();
+        if (acontrol.equals(c)) {
+          if (!RealType.Time.equals(map.getScalar())) {
+            throw new CollectiveBarbException("must be Time mapped to " +
+                                              "Animation " + map.getScalar());
+          }
+        }
+      }
+
+      // use a ControlListener on Display.Animation to fake
+      // animation of manipulable barbs
+      AnimationControlListener acl = new AnimationControlListener();
+      acontrol.addControlListener(acl);
+
+      stations_ref = new DataReferenceImpl("stations_ref");
+      stations_ref.setData(wind_field);
+      if (barbs) {
+        barb_renderer = new BarbRendererJ3D();
+      }
+      else {
+        barb_renderer = new SwellRendererJ3D();
+      }
+      ((BarbRenderer) barb_renderer).setKnotsConvert(knots);
+      display1.addReferences(barb_renderer, stations_ref, constantMaps());
+
+      // setupStations(); // new data  WLH 6 May 2001
+
+      if (need_monitor) {
+        wind_monitor = new WindMonitor();
+        wind_monitor.addReference(stations_ref);
+      }
+  
+      acontrol.setCurrent(0);
+    } // end if (display1 != null)
+
+    if (display2 != null) {
+      setStation(sta);
+    }
+
+    time_dir = 0;
+
+    circle_ref = new DataReferenceImpl[] {null, null};
+    circle_renderer = new DefaultRendererJ3D[] {null, null};
+    if (display1 != null && inner_circle_color != null) {
+      double cw = (inner_circle_width > 0) ? inner_circle_width : 1;
+      ConstantMap[] color_maps =
+        {new ConstantMap(inner_circle_color[0], Display.Red),
+         new ConstantMap(inner_circle_color[1], Display.Green),
+         new ConstantMap(inner_circle_color[2], Display.Blue),
+         new ConstantMap(cw, Display.LineWidth)};
+      circle_ref[0] = new DataReferenceImpl("inner_circle");
+      circle_ref[0].setData(null);
+      circle_renderer[0] = new DefaultRendererJ3D();
+      display1.addReferences(circle_renderer[0], circle_ref[0], color_maps);
+      circle_renderer[0].toggle(false);
+      circle_renderer[0].suppressExceptions(true);
+    }
+    if (display1 != null && outer_circle_color != null) {
+      double cw = (outer_circle_width > 0) ? outer_circle_width : 1;
+      ConstantMap[] color_maps =
+        {new ConstantMap(outer_circle_color[0], Display.Red),
+         new ConstantMap(outer_circle_color[1], Display.Green),
+         new ConstantMap(outer_circle_color[2], Display.Blue),
+         new ConstantMap(cw, Display.LineWidth)};
+      circle_ref[1] = new DataReferenceImpl("outer_circle");
+      circle_ref[1].setData(null);
+      circle_renderer[1] = new DefaultRendererJ3D();
+      display1.addReferences(circle_renderer[1], circle_ref[1], color_maps);
+      circle_renderer[1].toggle(false);
+      circle_renderer[1].suppressExceptions(true);
+    }
+
+    releaser_ref = new DataReferenceImpl("releaser"); // NEW
+    releaser = new Releaser();
+    releaser.addReference(releaser_ref);
+  }
+
+
+  // assumes inside synchronized (data_lock) { ... }
+  private void setupData()
+          throws VisADException, RemoteException {
+    try {
+      nindex = wind_field.getLength();
+      ntimes = new int[nindex];
+      tuples = new Tuple[nindex][];
+      tuples2 = new Tuple[nindex][];
+      which_times = new int[nindex];
+      wind_stations = new FlatField[nindex];
+      time_sets = new Set[nindex];
+      times = new double[nindex][];
+      azimuths = new float[nindex][];
+      radials = new float[nindex][];
+      old_azimuths = new float[nindex][];
+      old_radials = new float[nindex][];
+      for (int i=0; i<nindex; i++) {
+        wind_stations[i] = (FlatField) wind_field.getSample(i);
+        ntimes[i] = wind_stations[i].getLength();
+        time_sets[i] = wind_stations[i].getDomainSet();
+        double[][] dummy = Set.floatToDouble(time_sets[i].getSamples());
+        times[i] = dummy[0];
+        time_set = (i == 0) ? time_sets[i] : time_set.merge1DSets(time_sets[i]);
+        tuples[i] = new Tuple[ntimes[i]];
+        tuples2[i] = new Tuple[ntimes[i]];
+        azimuths[i] = new float[ntimes[i]];
+        radials[i] = new float[ntimes[i]];
+        old_azimuths[i] = new float[ntimes[i]];
+        old_radials[i] = new float[ntimes[i]];
+        Enumeration e = wind_stations[i].domainEnumeration();
+        for (int j=0; j<ntimes[i]; j++) {
+          tuples[i][j] = (Tuple) wind_stations[i].getSample(j);
+          int n = tuples[i][j].getDimension();
+          Data[] components = new Data[n + 1];
+          for (int k=0; k<n; k++) {
+            components[k] = tuples[i][j].getComponent(k);
+          }
+          components[n] = ((RealTuple) e.nextElement()).getComponent(0);
+          tuples2[i][j] = new Tuple(components);
+        }
+      }
+    }
+    catch (ClassCastException e) {
+      e.printStackTrace();
+      throw new CollectiveBarbException("wind_field bad MathType: " +
+                     wind_field_type);
+    }
+ 
+    global_ntimes = time_set.getLength();
+    global_times = new double[global_ntimes];
+    global_to_station = new int[nindex][global_ntimes];
+    station_to_global = new int[nindex][];
+    for (int i=0; i<nindex; i++) {
+      station_to_global[i] = new int[ntimes[i]];
+      for (int j=0; j<ntimes[i]; j++) station_to_global[i][j] = -1;
+    }
+    RealTupleType in = ((SetType) time_set.getType()).getDomain();
+    for (int j=0; j<global_ntimes; j++) {
+      int[] indices = {j};
+      double[][] fvalues = time_set.indexToDouble(indices);
+      global_times[j] = fvalues[0][0];
+      for (int i=0; i<nindex; i++) {
+        RealTupleType out = ((SetType) time_sets[i].getType()).getDomain();
+        double[][] values = CoordinateSystem.transformCoordinates(
+                                 out, time_sets[i].getCoordinateSystem(),
+                                 time_sets[i].getSetUnits(),
+                                 null /* errors */,
+                                 in, time_set.getCoordinateSystem(),
+                                 time_set.getSetUnits(),
+                                 null /* errors */, fvalues);
+        if (time_sets[i].getLength() == 1) {
+          indices = new int[] {0};
+        }
+        else {
+          indices = time_sets[i].doubleToIndex(values);
+        }
+        global_to_station[i][j] = indices[0];
+        station_to_global[i][indices[0]] = j;
+      } // end for (int i=0; i<nindex; i++)
+    } // end for (int j=0; j<global_ntimes; j++)
+
+    lats = new float[nindex];
+    lons = new float[nindex];
+    for (int i=0; i<nindex; i++) {
+      float[][] values = wind_stations[i].getFloats(false);
+      lats[i] = values[lat_index][0];
+      lons[i] = values[lon_index][0];
+    }
+
+
+    for (int i=0; i<nindex; i++) {
+      for (int j=0; j<ntimes[i]; j++) {
+        Real[] reals = tuples[i][j].getRealComponents();
+        azimuths[i][j] = (float) reals[azimuth_index].getValue();
+        radials[i][j] = (float) reals[radial_index].getValue();
+        old_azimuths[i][j] = azimuths[i][j];
+        old_radials[i][j] = radials[i][j];
+      }
+    }
+
+  }
+
+  // assumes inside synchronized (data_lock) { ... }
+  private void setupStations()
+          throws VisADException, RemoteException {
+    if (station_refs != null) {
+      for (int i=0; i<station_refs.length; i++) {
+        display1.removeReference(station_refs[i]);
+// System.out.println("setupStations: removeReference");
+        barb_monitors[i].removeReference(station_refs[i]);
+        barb_monitors[i].stop();
+      }
+    }
+
+    which_time = -1;
+    station_refs = new DataReferenceImpl[nindex];
+    barb_manipulation_renderers = new DataRenderer[nindex];
+    barb_monitors = new BarbMonitor[nindex];
+// System.out.println("setupStations: time 0");
+    for (int i=0; i<nindex; i++) {
+      station_refs[i] = new DataReferenceImpl("station_ref" + i);
+      station_refs[i].setData(tuples[i][0]);
+      which_times[i] = -1;
+      if (barbs) {
+        barb_manipulation_renderers[i] = new CBarbManipulationRendererJ3D(this, i);
+      }
+      else {
+        barb_manipulation_renderers[i] = new CSwellManipulationRendererJ3D(this, i);
+      }
+      ((BarbRenderer) barb_manipulation_renderers[i]).setKnotsConvert(knots);
+      display1.addReferences(barb_manipulation_renderers[i], station_refs[i],
+                             constantMaps());
+// System.out.println("setupStations: addReferences");
+      barb_monitors[i] = new BarbMonitor(station_refs[i], i);
+      barb_monitors[i].addReference(station_refs[i]);
+    }
+
+    if (pcl != null) pcl.setRenderers(barb_manipulation_renderers, 0.2f);
+
+    // stepper_ref.setData(null); // NEW
+  }
+
+
+  /** construct new wind station at (lat, lon) with initial winds = 0 */
+  public void addStation(float lat, float lon)
+         throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      // construct FlatField station for global times at (lat, lon)
+      FlatField station = new FlatField(wind_station_type, time_set);
+      int n = time_set.getLength();
+      int dim = wind_type.getNumberOfRealComponents();
+      float[][] values = new float[dim][n];
+      for (int i=0; i<n; i++) {
+        for (int j=0; j<dim; j++) values[j][i] = 0.0f;
+        values[lat_index][i] = lat;
+        values[lon_index][i] = lon;
+      }
+      station.setSamples(values, false);
+
+      addStation(station);
+    }
+  }
+
+  public void addStation(FlatField station)
+         throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      // check MathType of station
+      FunctionType ftype = (FunctionType) station.getType();
+      if (!wind_station_type.equals(ftype)) {
+        throw new CollectiveBarbException("station type doesn't match");
+      }
+
+      // add station to wind_field
+      int len = wind_field.getLength();
+      int new_len = len + 1;
+      Integer1DSet new_set = new Integer1DSet(new_len);
+      FieldImpl new_wind_field = new FieldImpl(wind_field_type, new_set);
+      for (int i=0; i<len; i++) {
+        new_wind_field.setSample(i, wind_field.getSample(i), false);
+      }
+      new_wind_field.setSample(len, station, false);
+
+      wind_field = new_wind_field;
+      stations_ref.setData(wind_field);
+
+      try {
+        // recompute internal data
+        setupData();
+        setupStations();
+      }
+      catch (VisADException e) {
+        throw e;
+      }
+      catch (RemoteException e) {
+        throw e;
+      }
+    } // end synchronized (data_lock)
+    stepper.doAction(); // NEW NEW
+  }
+
+  /** set values that govern collective barb adjustment
+     and disable any DataReference to a spatial curve;
+     abs indicates absolute or relative value adjustment
+     id and od are inner and outer distances in meters
+     it and ot are inner and outer times in seconds
+     influence is 1.0 inside inner, 0.0 outside outer and
+     linear between distance and time influences multiply */
+  public void setCollectiveParameters(boolean abs, float id, float od,
+                                      float it, float ot)
+         throws VisADException, RemoteException {
+    absolute = abs;
+    if (it < 0.0 || ot < it) {
+      throw new CollectiveBarbException("outer_time must be " +
+                                   "greater than inner_time");
+    }
+    if (id < 0.0 || od < id) {
+      throw new CollectiveBarbException("outer_distance must be " +
+                                   "greater than distance_time");
+    }
+    inner_distance = id;
+    outer_distance = od;
+    curve_ref = null;
+    inner_time = it;
+    outer_time = ot;
+  }
+
+  /** set values that govern collective barb adjustment
+      but don't change spatial parameters or curve_ref;
+     abs indicates absolute or relative value adjustment
+     it and ot are inner and outer times in seconds
+     influence is 1.0 inside inner, 0.0 outside outer and
+     linear between distance and time influences multiply */
+  public void setCollectiveTimeParameters(boolean abs, float it, float ot)
+         throws VisADException, RemoteException {
+    absolute = abs;
+    if (it < 0.0 || ot < it) {
+      throw new CollectiveBarbException("outer_time must be " +
+                                   "greater than inner_time");
+    }
+    inner_time = it;
+    outer_time = ot;
+  }
+
+  /** set value that governs whether collective changes are
+      propagated only forward in time (dir > 0), only backward
+      (dir < 0), or both (dir == 0) */
+  public void setTimeDir(int dir) {
+    time_dir = dir;
+  }
+
+  /** set a DataReference to a curve (typically from a
+      CurveManipulationRendererJ3D) to replace distance
+      parameters; also set time parameters */
+  public void setCollectiveCurve(boolean abs, DataReference r,
+                                 float it, float ot)
+         throws VisADException, RemoteException {
+    absolute = abs;
+    if (it < 0.0 || ot < it) {
+      throw new CollectiveBarbException("outer_time must be " +
+                                   "greater than inner_time");
+    }
+    curve_ref = r;
+    inner_time = it;
+    outer_time = ot;
+  }
+
+  /** called by the application to select which station is selected
+      in display2 */
+  public void setStation(int sta)
+         throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      if (display2 == null) {
+        throw new CollectiveBarbException("display2 cannot be null");
+      }
+      if (sta < 0 || sta >= nindex) {
+        throw new CollectiveBarbException("bad station index " + sta);
+      }
+
+      if (sta == station) return; // NEW
+      station = sta;
+  
+      if (time_refs != null && time_refs.length != ntimes[station]) {
+        int n = time_refs.length;
+        for (int i=0; i<n; i++) {
+          display2.removeReference(time_refs[i]);
+          barb_monitors2[i].removeReference(time_refs[i]);
+          barb_monitors2[i].stop();
+        }
+        time_refs = null;
+      }
+  
+      if (time_refs == null) {
+        int n = ntimes[station];
+        time_refs = new DataReferenceImpl[n];
+        barb_manipulation_renderers2 = new DataRenderer[n];
+        barb_monitors2 = new BarbMonitor2[n];
+        for (int i=0; i<n; i++) {
+          time_refs[i] = new DataReferenceImpl("time_ref" + i);
+          time_refs[i].setData(tuples2[station][i]);
+          if (ended) {
+            if (barbs) {
+              barb_manipulation_renderers2[i] = new BarbRendererJ3D();
+            }
+            else {
+              barb_manipulation_renderers2[i] = new SwellRendererJ3D();
+            }
+          }
+          else {
+            if (barbs) {
+              barb_manipulation_renderers2[i] = new BarbManipulationRendererJ3D();
+            }
+            else {
+              barb_manipulation_renderers2[i] = new SwellManipulationRendererJ3D();
+            }
+          }
+          ((BarbRenderer) barb_manipulation_renderers2[i]).setKnotsConvert(knots);
+          display2.addReferences(barb_manipulation_renderers2[i], time_refs[i],
+                                 constantMaps());
+          barb_monitors2[i] = new BarbMonitor2(time_refs[i], i);
+          barb_monitors2[i].addReference(time_refs[i]);
+        }
+      }
+      else {
+        int n = ntimes[station];
+        for (int i=0; i<n; i++) {
+          time_refs[i].setData(tuples2[station][i]);
+        }
+      }
+    } // end synchronized (data_lock)
+  }
+
+  public DataRenderer[] getManipulationRenderers() {
+    return barb_manipulation_renderers;
+  }
+
+  public DataRenderer[] getManipulationRenderers2() {
+    return barb_manipulation_renderers2;
+  }
+
+  private ConstantMap[] constantMaps() {
+    if (cmaps == null || cmaps.length == 0) return null;
+    int n = cmaps.length;
+    ConstantMap[] cms = new ConstantMap[n];
+    for (int i=0; i<n; i++) {
+      cms[i] = (ConstantMap) cmaps[i].clone();
+    }
+    return cms;
+  }
+
+  /** called by the application to end manipulation;
+      returns the final wind field */
+  public FieldImpl endManipulation()
+         throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      ended = true;
+      if (display1 != null) {
+        for (int i=0; i<nindex; i++) {
+          display1.removeReference(station_refs[i]);
+          barb_monitors[i].removeReference(station_refs[i]);
+          barb_monitors[i].stop();
+        }
+        if (barbs) {
+          barb_renderer = new BarbRendererJ3D();
+        }
+        else {
+          barb_renderer = new SwellRendererJ3D();
+        }
+        ((BarbRenderer) barb_renderer).setKnotsConvert(knots);
+        display1.addReferences(barb_renderer, stations_ref, constantMaps());
+      }
+      if (display2 != null && time_refs != null) {
+        int n = time_refs.length;
+        for (int i=0; i<n; i++) {
+          display2.removeReference(time_refs[i]);
+          barb_monitors2[i].removeReference(time_refs[i]);
+          barb_monitors2[i].stop();
+        }
+        time_refs = null;
+        setStation(station);
+      }
+      return wind_field;
+    }
+  }
+
+  private boolean afirst = true;
+  private boolean afirst_real = true;
+
+  // for AnimationControl steps
+  class AnimationControlListener implements ControlListener {
+    public void controlChanged(ControlEvent e)
+           throws VisADException, RemoteException {
+      if (afirst) {
+        afirst = false;
+      }
+      if (stepper_ref != null) stepper_ref.setData(null);
+    }
+  }
+
+  class Stepper extends CellImpl { // NEW
+int old_current = -1;
+    public void doAction() throws VisADException, RemoteException {
+      if (afirst || acontrol == null) return;
+// System.out.println("Stepper");
+      synchronized (data_lock) {
+        which_time = -1;
+
+        int current = acontrol.getCurrent();
+        if (current < 0) return;
+        which_time = current;
+
+if (current != old_current) {
+        if (barb_manipulation_renderers == null) return;
+        for (int i=0; i<nindex; i++) {
+          which_times[i] = -1;
+if (barb_manipulation_renderers[i] != null) {
+  barb_manipulation_renderers[i].stop_direct();
+}
+          // if (barb_manipulation_renderers[i] == null) return;
+          // barb_manipulation_renderers[i].stop_direct();
+        }
+}
+/*
+        int current = acontrol.getCurrent();
+        if (current < 0) return;
+        which_time = current;
+*/
+if (current != old_current) {
+        for (int i=0; i<nindex; i++) {
+          int index = global_to_station[i][current];
+          if (index < tuples[i].length) {
+if (station_refs[i] != null) {
+            station_refs[i].setData(tuples[i][index]);
+            which_times[i] = index;
+}
+          }
+          // System.out.println("station " + i + " ref " + index);
+        } // end for (int i=0; i<nindex; i++)
+}
+old_current = current;
+ 
+        if (afirst_real) {
+          afirst_real = false;
+          display1.removeReference(stations_ref);
+        }
+      }
+    }
+  }
+
+  private final static float DEGREE_EPS = 0.1f;
+  private final static float MPS_EPS = 0.01f;
+
+  class WindMonitor extends CellImpl {
+    public void doAction() throws VisADException, RemoteException {
+      synchronized (data_lock) {
+        if (nindex != wind_field.getLength()) {
+          throw new CollectiveBarbException("number of stations changed");
+        }
+        for (int i=0; i<nindex; i++) {
+          FlatField ff = (FlatField) wind_field.getSample(i);
+          if (ntimes[i] != ff.getLength()) {
+            throw new CollectiveBarbException("number of times changed");
+          }
+          Enumeration e = ff.domainEnumeration();
+          for (int j=0; j<ntimes[i]; j++) {
+            Tuple tuple = (Tuple) ff.getSample(j);
+            Real[] reals = tuple.getRealComponents();
+            float new_azimuth = (float) reals[azimuth_index].getValue();
+            float new_radial = (float) reals[radial_index].getValue();
+            if (!visad.util.Util.isApproximatelyEqual(new_azimuth,
+                       azimuths[i][j], DEGREE_EPS) ||
+                !visad.util.Util.isApproximatelyEqual(new_radial,
+                       radials[i][j], MPS_EPS)) {
+              azimuths[i][j] = new_azimuth;
+              radials[i][j] = new_radial;
+              tuples[i][j] = tuple;
+              if (station_refs != null && j == which_times[i]) {
+                station_refs[i].setData(tuples[i][j]);
+// if (i == 0) System.out.println("WindMonitor: station " + i + " time " + j);
+              }
+  
+              int n = tuple.getDimension();
+              Data[] components = new Data[n + 1];
+              for (int k=0; k<n; k++) {
+                components[k] = tuple.getComponent(k);
+              }
+              components[n] = ((RealTuple) e.nextElement()).getComponent(0);
+              tuples2[i][j] = new Tuple(components);
+              if (time_refs != null && i == station) {
+                time_refs[j].setData(tuples2[i][j]);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private Tuple modifyWind(Tuple old_wind, double azimuth, double radial)
+          throws VisADException, RemoteException {
+    Tuple wind = null;
+    Real[] reals = null;
+    if (old_wind instanceof RealTuple) {
+      reals = old_wind.getRealComponents();
+      reals[azimuth_index] = reals[azimuth_index].cloneButValue(azimuth);
+      reals[radial_index] = reals[radial_index].cloneButValue(radial);
+      wind = new RealTuple((RealTupleType) old_wind.getType(), reals,
+                       ((RealTuple) old_wind).getCoordinateSystem());
+    }
+    else { // old_wind instanceof Tuple
+      int n = old_wind.getDimension();
+      int k = 0;
+      Data[] components = new Data[n];
+      for (int c=0; c<n; c++) {
+        components[c] = old_wind.getComponent(c);
+        if (components[c] instanceof Real) {
+          if (k == azimuth_index) {
+            components[c] =
+              ((Real) components[c]).cloneButValue(azimuth);
+          }
+          if (k == radial_index) {
+            components[c] =
+              ((Real) components[c]).cloneButValue(radial);
+          }
+          k++;
+        }
+        else { // (components[c] instanceof RealTuple)
+          int m = ((RealTuple) components[c]).getDimension();
+          if ((k <= azimuth_index && azimuth_index < k+m) ||
+              (k <= radial_index && radial_index < k+m)) {
+            reals = ((RealTuple) components[c]).getRealComponents();
+            if (k <= azimuth_index && azimuth_index < k+m) {
+              reals[azimuth_index - k] =
+                reals[azimuth_index - k].cloneButValue(azimuth);
+            }
+            if (k <= radial_index && radial_index < k+m) {
+              reals[radial_index - k] =
+                reals[radial_index - k].cloneButValue(radial);
+            }
+            components[c] =
+              new RealTuple((RealTupleType) components[c].getType(),
+                            reals,
+                   ((RealTuple) components[c]).getCoordinateSystem());
+          }
+          k += m;
+        } // end if (components[c] instanceof RealTuple)
+      } // end for (int c=0; c<n; c++)
+      wind = new Tuple((TupleType) old_wind.getType(), components,
+                       false);
+    } // end if (old_wind instanceof Tuple)
+    return wind;
+  }
+
+  private void collectiveAdjust(int sta_index, int time_index,
+                               DataReferenceImpl ref, int display_index)
+          throws VisADException, RemoteException {
+
+    Tuple new_wind = (Tuple) ref.getData();
+    Real[] reals = new_wind.getRealComponents();
+    float new_azimuth = (float) reals[azimuth_index].getValue();
+    float new_radial = (float) reals[radial_index].getValue();
+
+// System.out.println("collectiveAdjust " + display_index + " " + sta_index +
+//                    " " + time_index);
+
+    // filter out barb changes due to other doAction calls
+    if (visad.util.Util.isApproximatelyEqual(new_azimuth,
+               azimuths[sta_index][time_index], DEGREE_EPS) &&
+        visad.util.Util.isApproximatelyEqual(new_radial,
+               radials[sta_index][time_index], MPS_EPS)) return;
+    if (display_index == 1 && curve_ref == null) makeCircles(sta_index);
+
+// System.out.println("collectiveAdjust significant change");
+
+    boolean curve = (curve_ref != null);
+    if (last_sta != sta_index || last_time != time_index ||
+        last_display != display_index || last_curve != curve) {
+      if (force_station && sta_index != station) setStation(sta_index);
+
+      last_sta = sta_index;
+      last_time = time_index;
+      last_display = display_index;
+      last_curve = curve;
+      for (int i=0; i<nindex; i++) {
+        for (int j=0; j<ntimes[i]; j++) {
+          old_azimuths[i][j] = azimuths[i][j];
+          old_radials[i][j] = radials[i][j];
+        }
+      }
+    }
+
+    if (wind_monitor != null) wind_monitor.disableAction();
+    boolean display1_was = true;
+    boolean display2_was = true;
+
+    float diff_azimuth = new_azimuth - old_azimuths[sta_index][time_index];
+    float diff_radial = new_radial - old_radials[sta_index][time_index];
+    float lat = lats[sta_index];
+    float lon = lons[sta_index];
+    double time = times[sta_index][time_index];
+    for (int i=0; i<nindex; i++) {
+      double dist_mult = 0.0;
+      if (curve_ref != null) {
+        try {
+          Data data = curve_ref.getData();
+          Gridded2DSet set = null;
+          if (data instanceof Gridded2DSet) {
+            set = (Gridded2DSet) data;
+          }
+          else if (data instanceof UnionSet) {
+            UnionSet us = (UnionSet) curve_ref.getData();
+            SampledSet[] sets = us.getSets();
+            if (sets.length > 0 &&
+                sets[sets.length - 1] instanceof Gridded2DSet) {
+              set = (Gridded2DSet) sets[sets.length - 1];
+            }
+          }
+          if (set != null) {
+            float[][] samples = set.getSamples();
+            if (DelaunayCustom.inside(samples, lat, lon) &&
+                DelaunayCustom.inside(samples, lats[i], lons[i])) {
+              dist_mult = 1.0;
+            }
+          }
+        }
+        catch (VisADException ex) {
+          dist_mult = 0.0;
+        }
+      }
+      else {
+        double lat_diff = Math.abs(lat - lats[i]);
+        double mid_lat = 0.5 * (lat + lats[i]);
+        double coslat = Math.cos(Data.DEGREES_TO_RADIANS * mid_lat);
+        double lon_diff = Math.abs(lon - lons[i]) * coslat;
+        double dist = ShadowType.METERS_PER_DEGREE *
+          Math.sqrt(lon_diff * lon_diff + lat_diff * lat_diff);
+        if (dist > outer_distance) continue;
+        dist_mult = (dist <= inner_distance) ? 1.0 :
+          (outer_distance - dist) / (outer_distance - inner_distance);
+      }
+      for (int j=0; j<ntimes[i]; j++) {
+        int ix = station_to_global[i][j];
+        double time_diff = Math.abs(time - times[i][j]);
+        if (time_diff > outer_time) continue;
+        double time_mult = (time_diff <= inner_time) ? 1.0 :
+          (outer_time - time_diff) / (outer_time - inner_time);
+
+        // WLH 13 July 2000
+        if ((time_dir > 0 && times[i][j] < time) ||
+            (time_dir < 0 && time < times[i][j])) {
+          continue;
+        }
+
+        double mult = dist_mult * time_mult;
+        if (i == sta_index && j == time_index) mult = 1.0;
+        double azimuth_diff = 0.0;
+        double radial_diff = 0.0;
+
+        if (absolute) {
+          azimuth_diff = new_azimuth - old_azimuths[i][j];
+          radial_diff = new_radial - old_radials[i][j];
+        }
+        else {
+          azimuth_diff = new_azimuth - old_azimuths[sta_index][time_index];
+          radial_diff = new_radial - old_radials[sta_index][time_index];
+        }
+        if (azimuth_diff < -180.0) azimuth_diff += 360.0;
+        if (azimuth_diff > 180.0) azimuth_diff -= 360.0;
+        double azimuth = old_azimuths[i][j] + mult * (azimuth_diff);
+        double radial = old_radials[i][j] + mult * (radial_diff);
+        if (radial < 0.0) radial = 0.0;
+
+        Tuple wind = modifyWind(tuples[i][j], azimuth, radial);
+        Tuple wind2 = modifyWind(tuples2[i][j], azimuth, radial);
+
+        azimuths[i][j] = (float) azimuth;
+        radials[i][j] = (float) radial;
+        wind_stations[i].setSample(j, wind);
+        tuples[i][j] = wind;
+        if (station_refs != null && j == which_times[i]) {
+          station_refs[i].setData(tuples[i][j]);
+// if (i == 0) System.out.println("collectiveAdjust: station " + i + " time " + j);
+        }
+        tuples2[i][j] = wind2;
+        if (time_refs != null && i == station) {
+          time_refs[j].setData(tuples2[i][j]);
+        }
+      } // end for (int j=0; j<ntimes[i]; j++)
+    } // end for (int i=0; i<nindex; i++)
+    if (wind_monitor != null) wind_monitor.enableAction();
+  }
+
+  private RealTupleType circle_type = null;
+
+  private void makeCircles(int sta_index) {
+    if ((circle_ref[0] == null && circle_ref[1] == null) ||
+        !circle_enable) return;
+    try {
+      if (circle_type == null) {
+        circle_type = new RealTupleType(RealType.Latitude, RealType.Longitude);
+      }
+      float lat = lats[sta_index];
+      float lon = lons[sta_index];
+      float[] dists =
+        {inner_distance / ShadowType.METERS_PER_DEGREE,
+         outer_distance / ShadowType.METERS_PER_DEGREE};
+      float[][] circle = new float[2][NCIRCLE+1];
+      float coslat = (float) Math.cos(Data.DEGREES_TO_RADIANS * lat);
+      for (int io=0; io<2; io++) {
+        if (circle_ref[io] != null) {
+          for (int i=0; i<=NCIRCLE; i++) {
+            double angle = Data.DEGREES_TO_RADIANS * 360.0 * i / NCIRCLE;
+            float cos = (float) Math.cos(angle);
+            float sin = (float) Math.sin(angle);
+            circle[0][i] = lat + dists[io] * cos;
+            circle[1][i] = lon + dists[io] * sin / coslat;
+          }
+          Gridded2DSet set = new Gridded2DSet(circle_type, circle, NCIRCLE + 1);
+          circle_renderer[io].toggle(true);
+          circle_ref[io].setData(set);
+        }
+      }
+    }
+    catch (VisADException e) {
+      System.out.println("makeCircles " + e);
+    }
+    catch (RemoteException e) {
+      System.out.println("makeCircles " + e);
+    }
+  }
+
+  void circleEnable() {
+    circle_enable = true;
+  }
+
+  public void release() {
+    circle_enable = false;
+    try {
+      releaser_ref.setData(null);
+    }
+    catch (VisADException e) {
+    }
+    catch (RemoteException e) {
+    }
+  }
+
+  class Releaser extends CellImpl {
+    public void doAction() throws VisADException, RemoteException {
+      for (int io=0; io<2; io++) {
+        if (circle_ref[io] != null) {
+          circle_renderer[io].toggle(false);
+          circle_ref[io].setData(null); // ??
+        }
+      }
+    }
+  }
+
+  class BarbMonitor extends CellImpl {
+    DataReferenceImpl ref;
+    int sta_index;
+ 
+    public BarbMonitor(DataReferenceImpl r, int index) {
+      ref = r;
+      sta_index = index;
+    }
+  
+    public void doAction() throws VisADException, RemoteException {
+// System.out.println("BarbMonitor.doAction " + ref.getName());
+      synchronized (data_lock) {
+        int time_index = which_times[sta_index];
+        if (time_index < 0) return;
+        collectiveAdjust(sta_index, time_index, ref, 1);
+      }
+    }
+  }
+
+  class BarbMonitor2 extends CellImpl {
+    DataReferenceImpl ref;
+    int time_index;
+ 
+    public BarbMonitor2(DataReferenceImpl r, int index) {
+      ref = r;
+      time_index = index;
+    }
+  
+    public void doAction() throws VisADException, RemoteException {
+      synchronized (data_lock) {
+        int sta_index = station;
+        if (sta_index < 0) return;
+        collectiveAdjust(sta_index, time_index, ref, 2);
+      }
+    }
+  }
+
+  private int current_index = 0;
+  private static final double five_knots = 5.0;
+  // private static final double five_knots = 5.0 * (1853.248 / 3600.0);
+
+  void plusAngle() throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      int time_index = which_times[current_index];
+      Tuple wind = tuples[current_index][time_index];
+      double azimuth = azimuths[current_index][time_index];
+      double radial = radials[current_index][time_index];
+      azimuth += 10;
+      if (azimuth > 360.0) azimuth -= 360.0;
+      Tuple new_wind = modifyWind(wind, azimuth, radial);
+      wind_stations[current_index].setSample(time_index, new_wind);
+      tuples[current_index][time_index] = new_wind;
+      if (station_refs != null) {
+        station_refs[current_index].setData(tuples[current_index][time_index]);
+// System.out.println("plusAngle: station " + current_index +
+//                    " time " + time_index);
+      }
+
+    }
+  }
+
+  void minusAngle() throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      int time_index = which_times[current_index];
+      Tuple wind = tuples[current_index][time_index];
+      double azimuth = azimuths[current_index][time_index];
+      double radial = radials[current_index][time_index];
+      azimuth -= 10;
+      if (azimuth < 0.0) azimuth += 360.0;
+      Tuple new_wind = modifyWind(wind, azimuth, radial);
+      wind_stations[current_index].setSample(time_index, new_wind);
+      tuples[current_index][time_index] = new_wind;
+      if (station_refs != null) {
+        station_refs[current_index].setData(tuples[current_index][time_index]);
+// System.out.println("minusAngle: station " + current_index +
+//                    " time " + time_index);
+      }
+    }
+  }
+
+  void plusSpeed() throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      int time_index = which_times[current_index];
+      Tuple wind = tuples[current_index][time_index];
+      double azimuth = azimuths[current_index][time_index];
+      double radial = radials[current_index][time_index];
+      radial += five_knots;
+      Tuple new_wind = modifyWind(wind, azimuth, radial);
+      wind_stations[current_index].setSample(time_index, new_wind);
+      tuples[current_index][time_index] = new_wind;
+      if (station_refs != null) {
+        station_refs[current_index].setData(tuples[current_index][time_index]);
+// System.out.println("plusSpeed: station " + current_index +
+//                    " time " + time_index);
+      }
+    }
+  }
+
+  void minusSpeed() throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      int time_index = which_times[current_index];
+      Tuple wind = tuples[current_index][time_index];
+      double azimuth = azimuths[current_index][time_index];
+      double radial = radials[current_index][time_index];
+      radial -= five_knots;
+      if (radial < 0.0) radial = 0.0;
+      Tuple new_wind = modifyWind(wind, azimuth, radial);
+      wind_stations[current_index].setSample(time_index, new_wind);
+      tuples[current_index][time_index] = new_wind;
+      if (station_refs != null) {
+        station_refs[current_index].setData(tuples[current_index][time_index]);
+// System.out.println("minusSpeed: station " + current_index +
+//                    " time " + time_index);
+      }
+    }
+  }
+
+  void nextWind() {
+    int n = nindex;
+    current_index = (current_index < 0) ? 0 : (current_index + 1) % n;
+  }
+
+  void previousWind() {
+    int n = nindex;
+    current_index = (current_index < 0) ? n-1 : (current_index + (n-1)) % n;
+  }
+
+  class CBarbManipulationRendererJ3D extends BarbManipulationRendererJ3D {
+  
+    CollectiveBarbManipulation cbm;
+    int index;
+  
+    CBarbManipulationRendererJ3D(CollectiveBarbManipulation c, int i) {
+      super();
+      cbm = c;
+      index = i;
+    }
+  
+    /** mouse button released, ending direct manipulation */
+    public void release_direct() {
+      cbm.release();
+    }
+  
+    public void drag_direct(VisADRay ray, boolean first,
+                                         int mouseModifiers) {
+      if (first) {
+        cbm.circleEnable();
+        current_index = index;
+      }
+// System.out.println("CBarbManipulationRendererJ3D " + index);
+      super.drag_direct(ray, first, mouseModifiers);
+    }
+  }
+  
+  class CSwellManipulationRendererJ3D extends SwellManipulationRendererJ3D {
+  
+    CollectiveBarbManipulation cbm;
+    int index;
+  
+    CSwellManipulationRendererJ3D(CollectiveBarbManipulation c, int i) {
+      super();
+      cbm = c;
+      index = i;
+    }
+  
+    /** mouse button released, ending direct manipulation */
+    public void release_direct() {
+      cbm.release();
+    }
+  
+    public void drag_direct(VisADRay ray, boolean first,
+                                         int mouseModifiers) {
+      if (first) {
+        cbm.circleEnable();
+        current_index = index;
+      }
+      super.drag_direct(ray, first, mouseModifiers);
+    }
+  }
+
+
+  private static final int NSTAS = 5; // actually NSTAS * NSTAS
+  private static final int NTIMES = 10;
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    // construct RealTypes for wind record components
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealTupleType earth = new RealTupleType(lat, lon);
+    RealType windx = RealType.getRealType("windx",
+                          CommonUnit.meterPerSecond);     
+    RealType windy = RealType.getRealType("windy",
+                          CommonUnit.meterPerSecond);     
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+
+    // EarthVectorType extends RealTupleType and says that its
+    // components are vectors in m/s with components parallel
+    // to Longitude (positive east) and Latitude (positive north)
+    EarthVectorType windxy = new EarthVectorType(windx, windy);
+
+    RealType wind_dir = RealType.getRealType("wind_dir",
+                          CommonUnit.degree);
+    RealType wind_speed = RealType.getRealType("wind_speed",
+                          CommonUnit.meterPerSecond);
+    RealTupleType windds = null;
+    windds =
+      new RealTupleType(new RealType[] {wind_dir, wind_speed},
+      new WindPolarCoordinateSystem(windxy), null);
+
+    RealType stn = RealType.getRealType("station");
+    Set stn_set = new Integer1DSet(stn, NSTAS * NSTAS);
+    RealType time = RealType.Time;
+    double start = new DateTime(1999, 122, 57060).getValue();
+    Set time_set = new Linear1DSet(time, start, start + 2700.0, NTIMES);
+    Set time_set2 = new Linear1DSet(time, start + 150.0, start + 2850.0, NTIMES);
+
+    TupleType tuple_type = null;
+    tuple_type = new TupleType(new MathType[]
+          {lon, lat, windds, red, green});
+
+    FunctionType station_type = new FunctionType(time, tuple_type);
+    FunctionType stations_type = new FunctionType(stn, station_type);
+
+    int image_size = 128;
+    RealType value = RealType.getRealType("value");
+    FunctionType image_type = new FunctionType(earth, value);
+    Linear2DSet image_set =
+      new Linear2DSet(earth, -50.0, -30.0, image_size, -10.0, 10.0, image_size);
+    FlatField image = new FlatField(image_type, image_set);
+    float[][] image_values = new float[1][image_size * image_size];
+    for (int i=0; i<image_size*image_size; i++) {
+      image_values[0][i] = (947 * i) % 677;
+    }
+    image_values[0][0] = 5101;
+    image.setSamples(image_values);
+
+    // construct first Java3D display and mappings that govern
+    // how wind records are displayed
+    DisplayImplJ3D display1 =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap lonmap = new ScalarMap(lon, Display.XAxis);
+    display1.addMap(lonmap);
+    lonmap.setRange(-10.0, 10.0);
+    ScalarMap latmap = new ScalarMap(lat, Display.YAxis);
+    display1.addMap(latmap);
+    latmap.setRange(-50.0, -30.0);
+
+    ScalarMap winds_map = new ScalarMap(wind_speed, Display.Flow1Radial);
+    display1.addMap(winds_map);
+    winds_map.setRange(0.0, 1.0); // do this for barb rendering
+    ScalarMap windd_map = new ScalarMap(wind_dir, Display.Flow1Azimuth);
+    display1.addMap(windd_map);
+    windd_map.setRange(0.0, 360.0); // do this for barb rendering
+    FlowControl flow_control = (FlowControl) windd_map.getControl();
+    flow_control.setFlowScale(0.15f); // this controls size of barbs
+
+    ScalarMap amap = new ScalarMap(time, Display.Animation);
+    display1.addMap(amap);
+    AnimationControl acontrol = (AnimationControl) amap.getControl();
+    acontrol.setStep(1000);
+
+    display1.addMap(new ScalarMap(value, Display.RGB));
+    DataReferenceImpl image_ref = new DataReferenceImpl("image");
+    image_ref.setData(image);
+    // display1.addReference(image_ref);
+
+    // construct second Java3D display and mappings that govern
+    // how wind records are displayed
+    DisplayImplJ3D display2 =
+      new DisplayImplJ3D("display2", new TwoDDisplayRendererJ3D());
+
+    ScalarMap winds_map2 = new ScalarMap(wind_speed, Display.Flow1Radial);
+    display2.addMap(winds_map2);
+    winds_map2.setRange(0.0, 1.0); // do this for barb rendering
+    ScalarMap windd_map2 = new ScalarMap(wind_dir, Display.Flow1Azimuth);
+    display2.addMap(windd_map2);
+    windd_map2.setRange(0.0, 360.0); // do this for barb rendering
+    FlowControl flow_control2 = (FlowControl) windd_map2.getControl();
+    flow_control2.setFlowScale(0.15f); // this controls size of barbs
+
+    ScalarMap tmap = new ScalarMap(time, Display.XAxis);
+    display2.addMap(tmap);
+    tmap.setRange(start, start + 3000.0);
+
+    // create an array of NSTAS by NSTAS winds
+    FieldImpl field = new FieldImpl(stations_type, stn_set);
+    FieldImpl field2 = new FieldImpl(stations_type, stn_set);
+    FieldImpl field3 = new FieldImpl(stations_type, stn_set);
+    int m = 0;
+    for (int i=0; i<NSTAS; i++) {
+      for (int j=0; j<NSTAS; j++) {
+        FlatField ff = new FlatField(station_type, time_set);
+        FlatField ff2 = new FlatField(station_type, time_set2);
+        FlatField ff3 = new FlatField(station_type, time_set2);
+        double[][] values = new double[6][NTIMES];
+        double[][] values2 = new double[6][NTIMES];
+        double[][] values3 = new double[6][NTIMES];
+        for (int k=0; k<NTIMES; k++) {
+          double u = 2.0 * i / (NSTAS - 1.0) - 1.0;
+          double v = 2.0 * j / (NSTAS - 1.0) - 1.0;
+  
+          // each wind record is a Tuple (lon, lat, (windx, windy), red, green)
+          // set colors by wind components, just for grins
+          values[0][k] = 10.0 * u;
+          values[1][k] = 10.0 * v - 40.0;
+          double fx = 30.0 * u;
+          double fy = 30.0 * v;
+          double fd =
+            Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy) + k * 15.0;
+          double fs = Math.sqrt(fx * fx + fy * fy);
+          values[2][k] = fd;
+          values[3][k] = fs;
+          values[4][k] = u;
+          values[5][k] = v;
+
+          values2[0][k] = 10.0 * u + 2.5; 
+          values2[1][k] = 10.0 * v - 40.0 + 2.5;
+          values2[2][k] = fd;
+          values2[3][k] = fs;
+          values2[4][k] = u;
+          values2[5][k] = v;
+
+          values3[0][k] = 10.0 * u + 2.5; 
+          values3[1][k] = 10.0 * v - 40.0 - 2.5;
+          values3[2][k] = fd;
+          values3[3][k] = fs;
+          values3[4][k] = u;
+          values3[5][k] = v;
+        }
+        ff.setSamples(values);
+        field.setSample(m, ff);
+        ff2.setSamples(values2);
+        field2.setSample(m, ff2);
+        ff3.setSamples(values3);
+        field3.setSample(m, ff3);
+        m++;
+      }
+    }
+
+    DataReferenceImpl barb_ref = new DataReferenceImpl("barb");
+    barb_ref.setData(field3);
+    BarbRendererJ3D barb_renderer = new BarbRendererJ3D();
+    display1.addReferences(barb_renderer, barb_ref);
+
+    ConstantMap[] cmaps = {
+      new ConstantMap(1.0, Display.Red),
+      new ConstantMap(0.0, Display.Green),
+      new ConstantMap(0.0, Display.Blue)};
+
+    final CollectiveBarbManipulation cbm =
+      new CollectiveBarbManipulation(field, display1, display2, cmaps, false,
+                                     500000.0f, 1000000.0f, 0.0f, 1000.0f,
+                                     0, false, true, true, false, true,
+                                     new double[] {0.0, 0.5, 0.5}, 1,
+                                     new double[] {0.5, 0.5, 0.0}, 2);
+
+    ConstantMap[] cmaps2 = {
+      new ConstantMap(1.0, Display.Red),
+      new ConstantMap(0.0, Display.Green),
+      new ConstantMap(0.0, Display.Blue)};
+
+    final CollectiveBarbManipulation cbm2 = (args.length > 0) ?
+      new CollectiveBarbManipulation(field2, display1, display2, cmaps2, false,
+                                     500000.0f, 1000000.0f, 0.0f, 1000.0f,
+                                     0, false, false, true, false, true,
+                                     new double[] {0.0, 0.5, 0.5}, 1,
+                                     new double[] {0.5, 0.5, 0.0}, 2) :
+      null;
+
+    CollectiveBarbManipulation[] cbms = (cbm2 == null) ?
+      new CollectiveBarbManipulation[] {cbm} :
+      new CollectiveBarbManipulation[] {cbm, cbm2};
+    DisplayRendererJ3D dr = (DisplayRendererJ3D) display1.getDisplayRenderer();
+    CBMKeyboardBehaviorJ3D kbd = new CBMKeyboardBehaviorJ3D(dr);
+    dr.addKeyboardBehavior(kbd);
+    kbd.setWhichCBM(cbm);
+
+    // construct invisible starter set
+    Gridded2DSet set1 =
+      new Gridded2DSet(earth, new float[][] {{0.0f}, {0.0f}}, 1);
+    Gridded2DSet[] sets = {set1};
+    UnionSet set = new UnionSet(earth, sets);
+    final DataReferenceImpl set_ref = new DataReferenceImpl("set_ref");
+    set_ref.setData(set);
+    int mask = InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK;
+    final CurveManipulationRendererJ3D cmr =
+      new CurveManipulationRendererJ3D(mask, mask, true);
+    display1.addReferences(cmr, set_ref);
+
+    RealTuple rt = new RealTuple(earth, new double[] {Double.NaN, Double.NaN});
+    final DataReferenceImpl point_ref = new DataReferenceImpl("point_ref");
+    point_ref.setData(rt);
+    final PointManipulationRendererJ3D pmr =
+      new PointManipulationRendererJ3D(lat, lon, mask, mask);
+    display1.addReferences(pmr, point_ref);
+    pmr.toggle(false);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test CollectiveBarbManipulation");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+    // panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    // panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    JPanel display_panel = new JPanel();
+    display_panel.setLayout(new BoxLayout(display_panel, BoxLayout.Y_AXIS));
+
+    // add displays to JPanel
+    JPanel panel1 = (JPanel) display1.getComponent();
+    JPanel panel2 = (JPanel) display2.getComponent();
+    Border etchedBorder5 =
+      new CompoundBorder(new EtchedBorder(),
+                         new EmptyBorder(5, 5, 5, 5));
+    panel1.setBorder(etchedBorder5);
+    panel2.setBorder(etchedBorder5);
+    display_panel.add(panel1);
+    display_panel.add(panel2);
+    display_panel.setMaximumSize(new Dimension(400, 800));
+
+    JPanel widget_panel = new JPanel();
+    widget_panel.setLayout(new BoxLayout(widget_panel, BoxLayout.Y_AXIS));
+
+    widget_panel.add(new AnimationWidget(amap));
+
+    final DataReferenceImpl station_select_ref =
+      new DataReferenceImpl("station_select_ref");
+    VisADSlider station_select_slider =
+      new VisADSlider("station", 0, NSTAS * NSTAS - 1, 0, 1.0,
+                      station_select_ref, RealType.Generic);
+    widget_panel.add(station_select_slider);
+    CellImpl cell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        int sta = (int) ((Real) station_select_ref.getData()).getValue();
+        if (0 <= sta && sta < NSTAS * NSTAS) cbm.setStation(sta);
+        if (0 <= sta && sta < NSTAS * NSTAS && cbm2 != null) cbm2.setStation(sta);
+      }
+    };
+    cell.addReference(station_select_ref);
+
+    CellImpl cell2 = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        UnionSet cset = (UnionSet) set_ref.getData();
+        SampledSet[] csets = cset.getSets();
+        if (csets.length > 0) {
+          float[][] samples = csets[csets.length - 1].getSamples();
+          if (samples != null && samples[0].length > 2) {
+            cbm.setCollectiveCurve(false, set_ref, 0.0f, 1000.0f);
+            if (cbm2 != null) cbm2.setCollectiveCurve(false, set_ref, 0.0f, 1000.0f);
+          }
+        }
+      }
+    };
+    cell2.addReference(set_ref);
+
+    final JButton add = new JButton("add");
+    CellImpl cell3 = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        RealTuple rt3 = (RealTuple) point_ref.getData();
+        float lat3 = (float) ((Real) rt3.getComponent(0)).getValue();
+        float lon3 = (float) ((Real) rt3.getComponent(1)).getValue();
+        if (lat3 == lat3 && lon3 == lon3) {
+          cbm.addStation(lat3, lon3);
+          cmr.toggle(true);
+          pmr.toggle(false);
+          add.setText("add");
+        }
+      }
+    };
+    cell3.addReference(point_ref);
+
+    JPanel button_panel = new JPanel();
+    button_panel.setLayout(new BoxLayout(button_panel, BoxLayout.X_AXIS));
+    button_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    button_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    EndManipCBM emc =
+      new EndManipCBM(cbm, cbm2, set_ref, add, cmr, pmr);
+    JButton end = new JButton("end manip");
+    end.addActionListener(emc);
+    end.setActionCommand("end");
+    button_panel.add(end);
+    JButton del = new JButton("delete curve");
+    del.addActionListener(emc);
+    del.setActionCommand("del");
+    button_panel.add(del);
+    add.addActionListener(emc);
+    add.setActionCommand("add");
+    button_panel.add(add);
+    JButton dir = new JButton("time dir");
+    dir.addActionListener(emc);
+    dir.setActionCommand("dir");
+    button_panel.add(dir);
+
+    widget_panel.add(button_panel);
+    widget_panel.setMaximumSize(new Dimension(400, 800));
+
+    panel.add(display_panel);
+    panel.add(widget_panel);
+    frame.getContentPane().add(panel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(800, 800);
+    frame.setVisible(true);
+  }
+}
+
+/** a help class for the GUI in CollectiveBarbManipulation.main() */
+class EndManipCBM implements ActionListener {
+  CollectiveBarbManipulation cbm;
+  CollectiveBarbManipulation cbm2;
+  DataReferenceImpl set_ref;
+  DisplayImpl display;
+  int dir = 0;
+  // boolean add_mode;
+  JButton add;
+  CurveManipulationRendererJ3D cmr;
+  PointManipulationRendererJ3D pmr;
+
+  EndManipCBM(CollectiveBarbManipulation cb, CollectiveBarbManipulation cb2,
+              DataReferenceImpl r, JButton a,
+              CurveManipulationRendererJ3D cm, PointManipulationRendererJ3D p) {
+    cbm = cb;
+    cbm2 = cb2;
+    set_ref = r;
+    add = a;
+    cmr = cm;
+    pmr = p;
+    // add_mode = false;
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("end")) {
+      try {
+        FieldImpl final_field = cbm.endManipulation();
+        FieldImpl final_field2 = (cbm2 != null) ? cbm2.endManipulation() : null;
+      }
+      catch (VisADException ex) {
+      }
+      catch (RemoteException ex) {
+      }
+    }
+    else if (cmd.equals("del")) {
+      try {
+        del_curve();
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+    else if (cmd.equals("add")) {
+      try {
+        // add_mode = !add_mode;
+        if (add.getText().equals("add")) {
+          cmr.toggle(false);
+          pmr.toggle(true);
+          add.setText("ready");
+          del_curve();
+        }
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+    else if (cmd.equals("dir")) {
+      dir = (dir == 0) ? +1 : ((dir > 0) ? -1 : 0);
+      cbm.setTimeDir(dir);
+      if (cbm2 != null) cbm2.setTimeDir(dir);
+    }
+  }
+
+  private void del_curve()
+          throws VisADException, RemoteException {
+    UnionSet set = (UnionSet) set_ref.getData();
+    SampledSet[] sets = set.getSets();
+    int n = sets.length - 1;
+    SampledSet[] new_sets = null; 
+    if (n > 0) {      
+      new_sets = new SampledSet[n];
+      System.arraycopy(sets, 0, new_sets, 0, n);
+    }
+    else {
+      new_sets = new SampledSet[] {new Gridded2DSet(set.getType(),
+                            new float[][] {{0.0f}, {0.0f}}, 1)};
+    }
+    set_ref.setData(new UnionSet(set.getType(), new_sets));
+    cbm.setCollectiveParameters(false, 500000.0f, 1000000.0f, 0.0f, 1000.0f);
+    if (cbm2 != null) {
+      cbm2.setCollectiveParameters(false, 500000.0f, 1000000.0f, 0.0f, 1000.0f);
+    }
+  }
+
+}
+
diff --git a/visad/bom/CurveDelete.java b/visad/bom/CurveDelete.java
new file mode 100644
index 0000000..30ee82c
--- /dev/null
+++ b/visad/bom/CurveDelete.java
@@ -0,0 +1,118 @@
+//
+// CurveDelete.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import java.rmi.*;
+
+
+public class CurveDelete implements ActionListener {
+
+  DataReferenceImpl ref;
+  DisplayImpl display;
+  boolean lines = false;
+  DataReferenceImpl new_ref;
+
+  CurveDelete(DataReferenceImpl r, DisplayImpl d) {
+    ref = r;
+    display = d;
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("del")) {
+      try {
+        UnionSet set = (UnionSet) ref.getData();
+        SampledSet[] sets = set.getSets();
+        SampledSet[] new_sets = new SampledSet[sets.length - 1];
+        System.arraycopy(sets, 0, new_sets, 0, sets.length - 1);
+        ref.setData(new UnionSet(set.getType(), new_sets));
+      }
+      catch (VisADException ex) {
+      }
+      catch (RemoteException ex) {
+      }
+    }
+    else if (cmd.equals("fill")) {
+      UnionSet set = null;
+      try {
+        set = (UnionSet) ref.getData();
+        System.out.println("area = " + DelaunayCustom.computeArea(set));
+      }
+      catch (VisADException ex) {
+        System.out.println(ex.getMessage());
+      }
+      try {
+        // Irregular2DSet new_set = DelaunayCustom.fill(set);
+        Irregular2DSet new_set = DelaunayCustom.fillCheck(set, false);
+        if (new_ref == null) {
+          new_ref = new DataReferenceImpl("fill");
+          ConstantMap[] cmaps = new ConstantMap[]
+            {new ConstantMap(1.0, Display.Blue),
+             new ConstantMap(1.0, Display.Red),
+             new ConstantMap(0.0, Display.Green)};
+          DataRenderer renderer = 
+              (display instanceof DisplayImplJ3D)
+                  ? (DataRenderer) new DefaultRendererJ3D()
+                  : (DataRenderer) new DefaultRendererJ2D();
+          renderer.suppressExceptions(true);
+          display.addReferences(renderer, new_ref, cmaps);
+        }
+        new_ref.setData(new_set);
+      }
+      catch (VisADException ex) {
+        System.out.println(ex.getMessage());
+      }
+      catch (RemoteException ex) {
+        System.out.println(ex.getMessage());
+      }
+    }
+    else if (cmd.equals("lines")) {
+      try {
+        lines = !lines;
+        GraphicsModeControl mode = display.getGraphicsModeControl();
+        if (lines) {
+          mode.setPolygonMode(DisplayImplJ3D.POLYGON_LINE);
+        }
+        else {
+          mode.setPolygonMode(DisplayImplJ3D.POLYGON_FILL);
+        }
+      }
+      catch (VisADException ex) {
+        System.out.println(ex.getMessage());
+      }
+      catch (RemoteException ex) {
+        System.out.println(ex.getMessage());
+      }
+    }
+  }
+}
+
diff --git a/visad/bom/CurveManipulationRendererJ2D.java b/visad/bom/CurveManipulationRendererJ2D.java
new file mode 100644
index 0000000..15d1d01
--- /dev/null
+++ b/visad/bom/CurveManipulationRendererJ2D.java
@@ -0,0 +1,896 @@
+//
+// CurveManipulationRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+import java.rmi.*;
+
+
+/**
+ * CurveManipulationRendererJ2D is the VisAD class for direct
+ * manipulation rendering of curves under Java2D, where curves
+ * are represented by UnionSets of Gridded2DSets with manifold
+ * dimension = 2
+ */
+public class CurveManipulationRendererJ2D extends DirectManipulationRendererJ2D {
+
+  private int mouseModifiersMask = 0;
+  private int mouseModifiersValue = 0;
+  private boolean only_one = false;
+
+  /** 
+   * Construct a DataRenderer that supports direct manipulation of
+   * representations of curves by UnionSets of Gridded2DSets
+   * with manifold dimension = 2; the Set's domain RealTypes
+   * must be mapped to (XAxis, YAxis);
+   */
+  public CurveManipulationRendererJ2D () {
+    this(0, 0);
+  }
+
+  /** 
+   * Construct a DataRenderer that supports direct manipulation of
+   * representations of curves by UnionSets of Gridded2DSets
+   * with manifold dimension = 2; the Set's domain RealTypes
+   * must be mapped to (XAxis, YAxis).
+   * mmm and mmv determine whehter SHIFT or CTRL keys are required -
+   * this is needed since this is a greedy DirectManipulationRenderer
+   * that will grab any right mouse click (that intersects its 2-D
+   * sub-manifold) 
+   * @param  mmm  mouse modifiers
+   * @param  mmv  mouse mask to check.
+   */
+  public CurveManipulationRendererJ2D (int mmm, int mmv) {
+    this(mmm, mmv, false);
+  }
+
+  /** 
+   * Construct a DataRenderer that supports direct manipulation of
+   * representations of curves by UnionSets of Gridded2DSets
+   * with manifold dimension = 2; the Set's domain RealTypes
+   * must be mapped to (XAxis, YAxis).
+   * mmm and mmv determine whehter SHIFT or CTRL keys are required -
+   * this is needed since this is a greedy DirectManipulationRenderer
+   * that will grab any right mouse click (that intersects its 2-D
+   * sub-manifold) 
+   * @param  mmm  mouse modifiers
+   * @param  mmv  mouse mask to check.
+   * @param  oo  if true, only one curve should exist at any one time.
+   */
+  public CurveManipulationRendererJ2D (int mmm, int mmv, boolean oo) {
+    super();
+    mouseModifiersMask = mmm;
+    mouseModifiersValue = mmv;
+    only_one = oo;
+  }
+
+  /**
+   * Create a ShadowType based on the SetType
+   * @param    type   SetType of UnionSet.
+   * @param    link   DataDisplayLink for DataReference
+   * @param    parent Parent ShadowType
+   * @throws   VisADException  problem creating a VisAD object
+   * @throws   RemoteException  problem creating a remote object
+   */
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowCurveSetTypeJ2D(type, link, parent);
+  }
+
+  private float[][] spatialValues = null;
+
+  /** index into spatialValues found by checkClose */
+  private int closeIndex = -1;
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+  private transient MathType type = null;
+  private transient ShadowSetType shadow = null;
+
+  private transient RealType[] domain_reals;
+
+  float[] default_values;
+
+  /** point on direct manifold line or plane */
+  private float point_x, point_y, point_z;
+  /** normalized direction of line or perpendicular to plane */
+  private float line_x, line_y, line_z;
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+  private float[] value = new float[2];
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  /** mapping from spatial axes to tuple component */
+  private int[] axisToComponent = {-1, -1, -1};
+  /** mapping from spatial axes to ScalarMaps */
+  private ScalarMap[] directMap = {null, null, null};
+  /** dimension of direct manipulation
+      (always 2 for CurveManipulationRendererJ2D) */
+  private int directManifoldDimension = 2;
+  /** spatial DisplayTupleType other than
+      DisplaySpatialCartesianTuple */
+  DisplayTupleType tuple;
+  private CoordinateSystem tuplecs;
+
+  private int otherindex = -1;
+  private float othervalue;
+
+  /** possible values for whyNotDirect */
+  private final static String notSetType =
+    "not Set";
+  private final static String notTwoD =
+    "Set must have domain dimension = 2";
+  private final static String domainNotSpatial =
+    "domain must be mapped to spatial";
+  private final static String badCoordSysManifoldDim =
+    "directManifoldDimension must be 2 with spatial CoordinateSystem";
+
+  private boolean stop = false;
+
+  /** pick error offset, communicated from checkClose() to drag_direct() */
+  private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
+  /** count down to decay offset to 0.0 */
+  private int offset_count = 0;
+  /** initial offset_count */
+  private static final int OFFSET_COUNT_INIT = 30;
+
+  /**
+   * Check whether direct manipulation is possible for this Renderer.
+   * Set appropriate error conditions if not possible.
+   * @throws  VisADException  problem accessing information
+   * @throws  RemoteException  problem accessing information for remote objects
+   */
+  public void checkDirect() throws VisADException, RemoteException {
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    default_values = link.getDefaultValues();
+    type = link.getType();
+    if (!(type instanceof SetType)) {
+      whyNotDirect = notSetType;
+      return;
+    }
+    RealTupleType real_tuple = ((SetType) type).getDomain();
+    domain_reals = real_tuple.getRealComponents();
+
+    shadow = (ShadowSetType) link.getShadow().getAdaptedShadowType();
+    ShadowRealTupleType domain = ((ShadowSetType) shadow).getDomain();
+
+    directMap = new ScalarMap[] {null, null, null};
+    ShadowRealType[] components = shadow.getDomain().getRealComponents();
+    if (components.length != 2) {
+      whyNotDirect = notTwoD;
+      return;
+    }
+
+    tuple = domain.getDisplaySpatialTuple();
+    if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
+         (tuple != null &&
+          tuple.getCoordinateSystem().getReference().equals(
+          Display.DisplaySpatialCartesianTuple)) )) {
+      whyNotDirect = domainNotSpatial;
+      return;
+    }
+
+    int[] indices = {-1, -1};
+    for (int i=0; i<components.length; i++) {
+      Enumeration maps = components[i].getSelectedMapVector().elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+        if (tuple != null &&
+            (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+             (tuple.getCoordinateSystem() != null &&
+              tuple.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          int index = dreal.getTupleIndex();
+          axisToComponent[index] = i;
+          directMap[index] = map;
+          indices[i] = index;
+        }
+      } // end while (maps.hasMoreElements())
+    }
+
+    if (indices[0] < 0 || indices[1] < 0) {
+      whyNotDirect = domainNotSpatial;
+      return;
+    }
+
+    // get default value for other component of tuple
+    otherindex = 3 - (indices[0] + indices[1]);
+    DisplayRealType dreal = (DisplayRealType) tuple.getComponent(otherindex);
+    int index = getDisplay().getDisplayScalarIndex(dreal);
+    othervalue = (index > 0) ? default_values[index] :
+                               (float) dreal.getDefaultValue();
+
+    if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+      tuple = null;
+      tuplecs = null;
+    }
+    else {
+      tuplecs = tuple.getCoordinateSystem();
+    }
+
+    directManifoldDimension = 2;
+    setIsDirectManipulation(true);
+  }
+
+  private int getDirectManifoldDimension() {
+    return directManifoldDimension;
+  }
+
+  /**
+   * Get the error messages on why direct manipulation is not possible.
+   * @return error messages.
+   */
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  private int getAxisToComponent(int i) {
+    return axisToComponent[i];
+  }
+
+  private ScalarMap getDirectMap(int i) {
+    return directMap[i];
+  }
+
+  /**
+   * Add a point to the data. Not used, but may be needed for
+   * performance.
+   * @param x  point to add.
+   * @throws VisADException   error occured.
+   */
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+// methods customized from DataRenderer:
+
+  /** 
+   * Set spatialValues from ShadowType.doTransform 
+   * @param spatial_values  spatial values.
+   */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    spatialValues = spatial_values;
+  }
+
+  /** 
+   * Find minimum distance from ray to spatialValues 
+   * @param   origin     origin of ray
+   * @param   direction  direction of the ray
+   * @return  miniumum distance from ray to spatial values
+   */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    int mouseModifiers = getLastMouseModifiers();
+    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
+      return Float.MAX_VALUE;
+    }
+
+    float distance = Float.MAX_VALUE;
+    closeIndex = -1;
+    if (spatialValues != null) {
+      float o_x = (float) origin[0];
+      float o_y = (float) origin[1];
+      float o_z = (float) origin[2];
+      float d_x = (float) direction[0];
+      float d_y = (float) direction[1];
+      float d_z = (float) direction[2];
+  
+      for (int i=0; i<spatialValues[0].length; i++) {
+        float x = spatialValues[0][i] - o_x;
+        float y = spatialValues[1][i] - o_y;
+        float z = spatialValues[2][i] - o_z;
+        float dot = x * d_x + y * d_y + z * d_z;
+        x = x - dot * d_x;
+        y = y - dot * d_y;
+        z = z - dot * d_z;
+        float d = (float) Math.sqrt(x * x + y * y + z * z);
+        if (d < distance) {
+          distance = d;
+          closeIndex = i;
+
+          offsetx = x;
+          offsety = y;
+          offsetz = z;
+
+        }
+  
+      }
+      if (distance <= getDisplayRenderer().getPickThreshhold()) {
+        return distance;
+      }
+    } // end if (spatialValues != null)
+
+    offsetx = 0.0f;
+    offsety = 0.0f;
+    offsetz = 0.0f;
+
+    closeIndex = -1;
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r == r) {
+        return 0.0f;
+      }
+      else {
+        return Float.MAX_VALUE;
+      }
+    }
+    catch (VisADException ex) {
+      return Float.MAX_VALUE;
+    }
+  }
+
+  /** 
+   * Mouse button released, ending direct manipulation.  Does nothing
+   * now, may need to do this for performance.
+   */
+  public synchronized void release_direct() {
+    // may need to do this for performance
+  }
+
+  /**
+   * Stop direct manipulation.
+   */
+  public void stop_direct() {
+    stop = true;
+  }
+
+  int which_set = -1;
+  int which_point = -1;
+
+  /**
+   * This method is called when direct manipulation is occuring.
+   * This adds points to the Gridded2DSet that comprises the current
+   * line being drawn.
+   * @param  ray              ray to dragging point of mouse
+   * @param  first            true if this is the first point.
+   * @param  mouseModifiers   modifiers used with mouse click.
+   */
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    // System.out.println("drag_direct " + first + " " + ref + " " + shadow);
+    // if (spatialValues == null || ref == null || shadow == null) return;
+    if (ref == null || shadow == null) return;
+
+    if (first) {
+      stop = false;
+    }
+    else {
+      if (stop) return;
+    }
+
+    // double[] origin = ray.position;
+    double[] origin = {ray.position[0], ray.position[1], ray.position[2]};
+    double[] direction = ray.vector;
+
+    if (pickCrawlToCursor) {
+      if (first) {
+        offset_count = OFFSET_COUNT_INIT;
+      }
+      else {
+        if (offset_count > 0) offset_count--;
+      }
+      if (offset_count > 0) {
+        float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
+        origin[0] += mult * offsetx;
+        origin[1] += mult * offsety;
+        origin[2] += mult * offsetz;
+      }
+    }
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r != r) {
+        return;
+      }
+      float[][] xcs = {{(float) (origin[0] + r * direction[0])},
+                      {(float) (origin[1] + r * direction[1])},
+                      {(float) (origin[2] + r * direction[2])}};
+      float[] xx = {xcs[0][0], xcs[1][0], xcs[2][0]};
+      if (tuple != null) {
+        if (tuplecs == null) return;
+        xcs = tuplecs.fromReference(xcs);
+      }
+      float[] x = {xcs[0][0], xcs[1][0], xcs[2][0]};
+
+      addPoint(xx);
+
+      UnionSet data;
+      try {
+        data = (UnionSet) link.getData();
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return;
+        }
+        throw re;
+      }
+
+      if (data == null) return;
+      SampledSet[] sets = data.getSets();
+      int n = sets.length;
+
+      UnionSet newData = null;
+
+      // create location string
+      Vector vect = new Vector();
+      for (int i=0; i<3; i++) {
+        int j = getAxisToComponent(i);
+        if (j >= 0) {
+          f[0] = x[i];
+
+          // WLH 13 Feb 2001
+          ScalarMap dm = getDirectMap(i);
+          if (dm == null) return;
+          d = dm.inverseScaleValues(f);
+          // d = getDirectMap(i).inverseScaleValues(f);
+
+          value[j] = d[0];
+          RealType rtype = domain_reals[j];
+
+          // WLH 31 Aug 2000
+          Real rr = new Real(rtype, d[0]);
+          Unit overrideUnit = dm.getOverrideUnit(); // WLH 13 Feb 2001
+          // Unit overrideUnit = getDirectMap(i).getOverrideUnit();
+          Unit rtunit = rtype.getDefaultUnit();
+          // units not part of Time string
+          if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+              !RealType.Time.equals(rtype)) {
+            double dval = overrideUnit.toThis((double) d[0], rtunit);
+            rr = new Real(rtype, dval, overrideUnit);
+          }
+          String valueString = rr.toValueString();
+          vect.addElement(rtype.getName() + " = " + valueString);
+
+        }
+      }
+      getDisplayRenderer().setCursorStringVector(vect);
+
+      if (closeIndex < 0) {
+        if (first) {
+          if (only_one) {
+            SampledSet[] new_sets = new SampledSet[1];
+            float[][] new_samples = {{value[0]}, {value[1]}};
+            new_sets[0] = new Gridded2DSet(type, new_samples, 1,
+                                           data.getCoordinateSystem(),
+                                           data.getSetUnits(), null);
+            newData = new UnionSet(type, new_sets);
+// System.out.println("drag_direct new");
+          }
+          else {
+            SampledSet[] new_sets = new SampledSet[n+1];
+            System.arraycopy(sets, 0, new_sets, 0, n);
+            float[][] new_samples = {{value[0]}, {value[1]}};
+            new_sets[n] = new Gridded2DSet(type, new_samples, 1,
+                                           data.getCoordinateSystem(),
+                                           data.getSetUnits(), null);
+            newData = new UnionSet(type, new_sets);
+          }
+        }
+        else { // !first
+          float[][] samples = sets[n-1].getSamples(false);
+          int m = samples[0].length;
+          float[][] new_samples = new float[2][m+1];
+          System.arraycopy(samples[0], 0, new_samples[0], 0, m);
+          System.arraycopy(samples[1], 0, new_samples[1], 0, m);
+          new_samples[0][m] = value[0];
+          new_samples[1][m] = value[1];
+          sets[n-1] = new Gridded2DSet(type, new_samples, m+1,
+                                       data.getCoordinateSystem(),
+                                       data.getSetUnits(), null);
+          newData = new UnionSet(type, sets);
+        }
+      }
+      else { // closeIndex >= 0
+        if (first) {
+          which_set = -1;
+          which_point = -1;
+          int w = closeIndex;
+          int len = 0;
+          for (int i=0; i<n; i++) {
+            len = sets[i].getLength();
+            if (w < len) {
+              which_set = i;
+              which_point = w;
+              break;
+            }
+            w -= len;
+          }
+          if (which_set < 0) return; // shouldn't happen
+          float[][] samples = sets[which_set].getSamples(false);
+          if (which_point == 0) {
+            samples = invert(samples);
+            which_point = len - 1;
+          }
+          samples[0][which_point] = value[0];
+          samples[1][which_point] = value[1];
+
+          // int m = samples[0].length;
+          if (which_point == (len - 1)) {
+            sets = rotate(sets, which_set);
+            which_set = n - 1;
+            closeIndex = -1;
+          }
+          // sets[which_set]= new Gridded2DSet(type, samples, m,
+          sets[which_set]= new Gridded2DSet(type, samples, len,
+                                     data.getCoordinateSystem(),
+                                     data.getSetUnits(), null);
+          newData = new UnionSet(type, sets);
+// System.out.println("drag_direct len = " + len + " which_set = " + which_set);
+        }
+        else { // !first
+          if (which_set < 0) return; // shouldn't happen
+          float[][] samples = sets[which_set].getSamples(false);
+          int len = samples[0].length;
+
+          // figure out which direction to look for new nearest point
+          int dir = 0;
+          if (which_point == 0) {
+            dir = 1; // cannot happen
+          }
+          else if (which_point == len - 1) {
+            dir = -1; // cannot happen
+          }
+          else {
+            float v0 = value[0] - samples[0][which_point];
+            float v1 = value[1] - samples[1][which_point];
+            float v = (float) Math.sqrt(v0 * v0 + v1 * v1);
+            int NPTS = 5;
+            int wp = which_point + NPTS;
+            if (wp > len - 2) wp = len - 2;
+            float pplus = 0;
+            float psum = 0.0f;
+            for (int i=which_point; i<wp; i++) {
+              float vp0 = samples[0][i + 1] - samples[0][i];
+              float vp1 = samples[1][i + 1] - samples[1][i];
+              float vp = (float) Math.sqrt(vp0 * vp0 + vp1 * vp1);
+              float pp = (v0 * vp0 + v1 * vp1) / v * vp;
+              if (pp > 0.0) pplus += 1;
+              psum += pp;
+            }
+            float pdiv = (wp - which_point) > 0 ? (wp - which_point) : 1.0f;
+            pplus = pplus / pdiv;
+            psum = psum / pdiv;
+
+            int wm = which_point - NPTS;
+            if (wm < 1) wm = 1;
+            float mplus = 0;
+            float msum = 0.0f;
+            for (int i=which_point; i>wm; i--) {
+              float vm0 = samples[0][i - 1] - samples[0][i];
+              float vm1 = samples[1][i - 1] - samples[1][i];
+              float vm = (float) Math.sqrt(vm0 * vm0 + vm1 * vm1);
+              float mm = (v0 * vm0 + v1 * vm1) / v * vm;
+              if (mm > 0.0) mplus += 1;
+              msum += mm;
+            }
+            float mdiv = (which_point - wm) > 0 ? (which_point - wm) : 1.0f;
+            mplus = mplus / mdiv;
+            msum = msum / mdiv;
+            dir = (pplus > mplus) ? 1 : -1;
+            if (pplus == mplus) dir = (psum > msum) ? 1 : -1;
+          }
+
+          float distance = Float.MAX_VALUE; // actually distance squared
+          int new_point = -1;
+          if (dir > 0) {
+            for (int i=which_point + 1; i<len; i++) {
+              float d = (samples[0][i] - value[0]) * (samples[0][i] - value[0]) +
+                        (samples[1][i] - value[1]) * (samples[1][i] - value[1]);
+              if (d < distance) {
+                distance = d;
+                new_point = i;
+              }
+            }
+            if (new_point < 0) return; // shouldn't happen
+            if (which_point + 1 == new_point) {
+              if (which_point + 2 == len) {
+                samples[0][which_point + 1] = value[0];
+                samples[1][which_point + 1] = value[1];
+                which_point = which_point + 1;
+              }
+              else {
+                float[][] new_samples = new float[2][len + 1];
+                System.arraycopy(samples[0], 0, new_samples[0], 0, which_point + 1);
+                System.arraycopy(samples[1], 0, new_samples[1], 0, which_point + 1);
+                new_samples[0][which_point + 1] = value[0];
+                new_samples[1][which_point + 1] = value[1];
+                System.arraycopy(samples[0], which_point + 1, new_samples[0],
+                                 which_point + 2, len - (which_point + 1));
+                System.arraycopy(samples[1], which_point + 1, new_samples[1],
+                                 which_point + 2, len - (which_point + 1));
+                samples = new_samples;
+                which_point = which_point + 1;
+                len++;
+              }
+            }
+            else if (which_point + 2 == new_point) {
+              samples[0][which_point + 1] = value[0];
+              samples[1][which_point + 1] = value[1];
+              which_point = which_point + 1;
+            }
+            else { // which_point + 2 < new_point
+              int new_len = len - (new_point - (which_point + 2));
+              float[][] new_samples = new float[2][new_len];
+              System.arraycopy(samples[0], 0, new_samples[0], 0, which_point + 1);
+              System.arraycopy(samples[1], 0, new_samples[1], 0, which_point + 1);
+              new_samples[0][which_point + 1] = value[0];
+              new_samples[1][which_point + 1] = value[1];
+              System.arraycopy(samples[0], new_point, new_samples[0],
+                               which_point + 2, len - new_point);
+              System.arraycopy(samples[1], new_point, new_samples[1],
+                               which_point + 2, len - new_point);
+              samples = new_samples;
+              which_point = which_point + 1;
+              len = new_len;
+            }
+          }
+          else { // if (dir < 0)
+            for (int i=0; i<which_point; i++) {
+              float d = (samples[0][i] - value[0]) * (samples[0][i] - value[0]) +
+                        (samples[1][i] - value[1]) * (samples[1][i] - value[1]);
+              if (d < distance) {
+                distance = d;
+                new_point = i;
+              }
+            }
+            if (new_point < 0) return; // shouldn't happen
+            if (new_point == which_point - 1) {
+              if (which_point - 1 == 0) {
+                samples[0][which_point - 1] = value[0];
+                samples[1][which_point - 1] = value[1];
+                which_point = which_point - 1;
+              }
+              else {
+                float[][] new_samples = new float[2][len + 1];
+                System.arraycopy(samples[0], 0, new_samples[0], 0, which_point);
+                System.arraycopy(samples[1], 0, new_samples[1], 0, which_point);
+                new_samples[0][which_point] = value[0];
+                new_samples[1][which_point] = value[1];
+                System.arraycopy(samples[0], which_point, new_samples[0],
+                                 which_point + 1, len - which_point);
+                System.arraycopy(samples[1], which_point, new_samples[1],
+                                 which_point + 1, len - which_point);
+                samples = new_samples;
+                len++;
+              }
+            }
+            else if (new_point == which_point - 2) {
+              samples[0][which_point - 1] = value[0];
+              samples[1][which_point - 1] = value[1];
+              which_point = which_point - 1;
+            }
+            else { // new_point < which_point - 2
+              int new_len = len - ((which_point - 2) - new_point);
+              float[][] new_samples = new float[2][new_len];
+              System.arraycopy(samples[0], 0, new_samples[0], 0, new_point + 1);
+              System.arraycopy(samples[1], 0, new_samples[1], 0, new_point + 1);
+              new_samples[0][new_point + 1] = value[0];
+              new_samples[1][new_point + 1] = value[1];
+              System.arraycopy(samples[0], which_point, new_samples[0],
+                               new_point + 2, len - which_point);
+              System.arraycopy(samples[1], which_point, new_samples[1],
+                               new_point + 2, len - which_point);
+              samples = new_samples;
+              which_point = new_point + 1;
+              len = new_len;
+            }
+          } // if end (dir < 0)
+
+
+          if (which_point == 0) {
+            samples = invert(samples);
+            which_point = len - 1;
+          }
+
+          if (which_point == (len - 1)) {
+            sets = rotate(sets, which_set);
+            which_set = n - 1;
+            closeIndex = -1;
+          }
+          sets[which_set]= new Gridded2DSet(type, samples, len,
+                                     data.getCoordinateSystem(),
+                                     data.getSetUnits(), null);
+          newData = new UnionSet(type, sets);
+
+        }
+      }
+
+      ref.setData(newData);
+      link.clearData();
+
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  private float[][] invert(float[][] samples) {
+    int m = samples[0].length;
+    float[][] new_samples = new float[2][m];
+    int m1 = m - 1;
+    for (int i=0; i<m; i++) {
+      new_samples[0][i] = samples[0][m1 - i];
+      new_samples[1][i] = samples[1][m1 - i];
+    }
+    return new_samples;
+  }
+
+  private SampledSet[] rotate(SampledSet[] sets, int which_set) {
+    int n = sets.length;
+    int k = (n - 1) - which_set;
+    if (k == 0) return sets;
+    SampledSet[] new_sets = new SampledSet[n];
+    if (which_set > 0) {
+      System.arraycopy(sets, 0, new_sets, 0, which_set);
+    }
+    if (k > 0) {
+      System.arraycopy(sets, which_set + 1, new_sets, which_set, k);
+    }
+    new_sets[n - 1] = sets[which_set];
+    return new_sets;
+  }
+
+  /**
+   * Clone this renderer.
+   * @return  new Renderer with same parameters.
+   */
+  public Object clone() {
+    return new CurveManipulationRendererJ2D(mouseModifiersMask,
+                                            mouseModifiersValue, only_one);
+  }
+
+  private static final int N = 64;
+
+  /** test CurveManipulationRendererJ2D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealTupleType xy = new RealTupleType(x, y);
+
+    RealType c = RealType.getRealType("c");
+    FunctionType ft = new FunctionType(xy, c);
+
+    // construct Java2D display and mappings
+    DisplayImpl display  = new DisplayImplJ2D("display1");
+    display.addMap(new ScalarMap(x, Display.XAxis));
+    display.addMap(new ScalarMap(y, Display.YAxis));
+    display.addMap(new ScalarMap(c, Display.RGB));
+
+    DisplayRenderer displayRenderer = display.getDisplayRenderer();
+    displayRenderer.setBoxOn(false);
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setLineWidth(2.0f);
+
+    Integer2DSet fset = new Integer2DSet(xy, N, N);
+    FlatField field = new FlatField(ft, fset);
+    float[][] values = new float[1][N * N];
+    int k = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        values[0][k++] = (i - N / 2) * (j - N / 2);
+      }
+    }
+    field.setSamples(values);
+    DataReferenceImpl field_ref = new DataReferenceImpl("field");
+    field_ref.setData(field);
+    // display.addReference(field_ref);
+    DefaultRendererJ2D renderer = new DefaultRendererJ2D();
+    display.addReferences(renderer, field_ref);
+    renderer.toggle(false);
+
+    // construct invisible starter set
+    Gridded2DSet set1 = 
+      new Gridded2DSet(xy, new float[][] {{0.0f}, {0.0f}}, 1) ;
+    Gridded2DSet[] sets = {set1};
+    UnionSet set = new UnionSet(xy, sets);
+
+    DataReferenceImpl ref = new DataReferenceImpl("set");
+    ref.setData(set);
+    boolean only_one = (args.length > 1);
+    CurveManipulationRendererJ2D cmr =
+      new CurveManipulationRendererJ2D(0, 0, only_one);
+    display.addReferences(cmr, ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test CurveManipulationRendererJ2D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    JPanel button_panel = new JPanel();
+    button_panel.setLayout(new BoxLayout(button_panel, BoxLayout.X_AXIS));
+    button_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    button_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    CurveDelete cd = new CurveDelete(ref, display);
+    if (!only_one) {
+      JButton del = new JButton("delete last");
+      del.addActionListener(cd);
+      del.setActionCommand("del");
+      button_panel.add(del);
+    }
+    JButton fill = new JButton("fill");
+    fill.addActionListener(cd);
+    fill.setActionCommand("fill");
+    button_panel.add(fill);
+    panel.add(button_panel);
+    /* Not available in Java 2D
+    JButton lines = new JButton("lines");
+    lines.addActionListener(cd);
+    lines.setActionCommand("lines");
+    button_panel.add(lines);
+    */
+    panel.add(button_panel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
diff --git a/visad/bom/CurveManipulationRendererJ3D.java b/visad/bom/CurveManipulationRendererJ3D.java
new file mode 100644
index 0000000..7eb560d
--- /dev/null
+++ b/visad/bom/CurveManipulationRendererJ3D.java
@@ -0,0 +1,944 @@
+//
+// CurveManipulationRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+import java.rmi.*;
+
+
+/**
+ * CurveManipulationRendererJ3D is the VisAD class for direct
+ * manipulation rendering of curves under Java2D, where curves
+ * are represented by UnionSets of Gridded2DSets with manifold
+ * dimension = 2
+ */
+public class CurveManipulationRendererJ3D extends DirectManipulationRendererJ3D {
+
+  private int mouseModifiersMask = 0;
+  private int mouseModifiersValue = 0;
+  private boolean only_one = false;
+  private boolean greedy = true; // default
+
+  /** 
+   * Construct a DataRenderer that supports direct manipulation of
+   * representations of curves by UnionSets of Gridded2DSets
+   * with manifold dimension = 2; the Set's domain RealTypes
+   * must be mapped to two of (XAxis, YAxis, ZAxis).
+   */
+  public CurveManipulationRendererJ3D () {
+    this(0, 0);
+  }
+
+  /** 
+   * Construct a DataRenderer that supports direct manipulation of
+   * representations of curves by UnionSets of Gridded2DSets
+   * with manifold dimension = 2; the Set's domain RealTypes
+   * must be mapped to two of (XAxis, YAxis, ZAxis).
+   * mmm and mmv determine whehter SHIFT or CTRL keys are required -
+   * this is needed since this is a greedy DirectManipulationRenderer
+   * that will grab any right mouse click (that intersects its 2-D
+   * sub-manifold) 
+   * @param  mmm  mouse modifiers
+   * @param  mmv  mouse mask to check.
+   */
+  public CurveManipulationRendererJ3D (int mmm, int mmv) {
+    this(mmm, mmv, false);
+  }
+
+  /** 
+   * Construct a DataRenderer that supports direct manipulation of
+   * representations of curves by UnionSets of Gridded2DSets
+   * with manifold dimension = 2; the Set's domain RealTypes
+   * must be mapped to two of (XAxis, YAxis, ZAxis).
+   * mmm and mmv determine whehter SHIFT or CTRL keys are required -
+   * this is needed since this is a greedy DirectManipulationRenderer
+   * that will grab any right mouse click (that intersects its 2-D
+   * sub-manifold) 
+   * @param  mmm  mouse modifiers
+   * @param  mmv  mouse mask to check.
+   * @param  oo  if true, only one curve should exist at any one time.
+   */
+  public CurveManipulationRendererJ3D (int mmm, int mmv, boolean oo) {
+    super();
+    mouseModifiersMask = mmm;
+    mouseModifiersValue = mmv;
+    only_one = oo;
+  }
+
+  /**
+   * Create a ShadowType based on the SetType
+   * @param    type   SetType of UnionSet.
+   * @param    link   DataDisplayLink for DataReference
+   * @param    parent Parent ShadowType
+   * @throws   VisADException  problem creating a VisAD object
+   * @throws   RemoteException  problem creating a remote object
+   */
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowCurveSetTypeJ3D(type, link, parent);
+  }
+
+  private float[][] spatialValues = null;
+
+  /** index into spatialValues found by checkClose */
+  private int closeIndex = -1;
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+  private transient MathType type = null;
+  private transient ShadowSetType shadow = null;
+
+  private transient RealType[] domain_reals;
+
+  float[] default_values;
+
+  /** point on direct manifold line or plane */
+  private float point_x, point_y, point_z;
+  /** normalized direction of line or perpendicular to plane */
+  private float line_x, line_y, line_z;
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+  private float[] value = new float[2];
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  /** mapping from spatial axes to tuple component */
+  private int[] axisToComponent = {-1, -1, -1};
+  /** mapping from spatial axes to ScalarMaps */
+  private ScalarMap[] directMap = {null, null, null};
+  /** dimension of direct manipulation
+      (always 2 for CurveManipulationRendererJ3D) */
+  private int directManifoldDimension = 2;
+  /** spatial DisplayTupleType other than
+      DisplaySpatialCartesianTuple */
+  DisplayTupleType tuple;
+  private CoordinateSystem tuplecs;
+
+  private int otherindex = -1;
+  private float othervalue;
+
+  /** possible values for whyNotDirect */
+  private final static String notSetType =
+    "not Set";
+  private final static String notTwoD =
+    "Set must have domain dimension = 2";
+  private final static String domainNotSpatial =
+    "domain must be mapped to spatial";
+  private final static String badCoordSysManifoldDim =
+    "directManifoldDimension must be 2 with spatial CoordinateSystem";
+
+  private boolean stop = false;
+
+  /** pick error offset, communicated from checkClose() to drag_direct() */
+  private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
+  /** count down to decay offset to 0.0 */
+  private int offset_count = 0;
+  /** initial offset_count */
+  private static final int OFFSET_COUNT_INIT = 30;
+
+  /**
+   * Check whether direct manipulation is possible for this Renderer.
+   * Set appropriate error conditions if not possible.
+   * @throws  VisADException  problem accessing information
+   * @throws  RemoteException  problem accessing information for remote objects
+   */
+  public void checkDirect() throws VisADException, RemoteException {
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    default_values = link.getDefaultValues();
+    type = link.getType();
+    if (!(type instanceof SetType)) {
+      whyNotDirect = notSetType;
+      return;
+    }
+    RealTupleType real_tuple = ((SetType) type).getDomain();
+    domain_reals = real_tuple.getRealComponents();
+
+    shadow = (ShadowSetType) link.getShadow().getAdaptedShadowType();
+    ShadowRealTupleType domain = ((ShadowSetType) shadow).getDomain();
+
+    directMap = new ScalarMap[] {null, null, null};
+    ShadowRealType[] components = shadow.getDomain().getRealComponents();
+    if (components.length != 2) {
+      whyNotDirect = notTwoD;
+      return;
+    }
+
+    tuple = domain.getDisplaySpatialTuple();
+    if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
+         (tuple != null &&
+          tuple.getCoordinateSystem().getReference().equals(
+          Display.DisplaySpatialCartesianTuple)) )) {
+      whyNotDirect = domainNotSpatial;
+      return;
+    }
+
+    int[] indices = {-1, -1};
+    for (int i=0; i<components.length; i++) {
+      Enumeration maps = components[i].getSelectedMapVector().elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+        if (tuple != null &&
+            (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+             (tuple.getCoordinateSystem() != null &&
+              tuple.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          int index = dreal.getTupleIndex();
+          axisToComponent[index] = i;
+          directMap[index] = map;
+          indices[i] = index;
+        }
+      } // end while (maps.hasMoreElements())
+    }
+
+    if (indices[0] < 0 || indices[1] < 0) {
+      whyNotDirect = domainNotSpatial;
+      return;
+    }
+
+    // get default value for other component of tuple
+    otherindex = 3 - (indices[0] + indices[1]);
+    DisplayRealType dreal = (DisplayRealType) tuple.getComponent(otherindex);
+    int index = getDisplay().getDisplayScalarIndex(dreal);
+    othervalue = (index > 0) ? default_values[index] :
+                               (float) dreal.getDefaultValue();
+
+    if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+      tuple = null;
+      tuplecs = null;
+    }
+    else {
+      tuplecs = tuple.getCoordinateSystem();
+    }
+
+    directManifoldDimension = 2;
+    setIsDirectManipulation(true);
+  }
+
+  private int getDirectManifoldDimension() {
+    return directManifoldDimension;
+  }
+
+  /**
+   * Get the error messages on why direct manipulation is not possible.
+   * @return error messages.
+   */
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  private int getAxisToComponent(int i) {
+    return axisToComponent[i];
+  }
+
+  private ScalarMap getDirectMap(int i) {
+    return directMap[i];
+  }
+
+  /**
+   * Add a point to the data. Not used, but may be needed for
+   * performance.
+   * @param x  point to add.
+   * @throws VisADException   error occured.
+   */
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+// methods customized from DataRenderer:
+
+  /** 
+   * Set spatialValues from ShadowType.doTransform.
+   * @param spatial_values  spatial values.
+   */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    spatialValues = spatial_values;
+  }
+
+  /** 
+   * Find minimum distance from ray to spatialValues.
+   * @param   origin     origin of ray
+   * @param   direction  direction of the ray
+   * @return  miniumum distance from ray to spatial values
+   */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    int mouseModifiers = getLastMouseModifiers();
+    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
+      return Float.MAX_VALUE;
+    }
+
+    float distance = Float.MAX_VALUE;
+    closeIndex = -1;
+    if (spatialValues != null) {
+      float o_x = (float) origin[0];
+      float o_y = (float) origin[1];
+      float o_z = (float) origin[2];
+      float d_x = (float) direction[0];
+      float d_y = (float) direction[1];
+      float d_z = (float) direction[2];
+  
+      for (int i=0; i<spatialValues[0].length; i++) {
+        float x = spatialValues[0][i] - o_x;
+        float y = spatialValues[1][i] - o_y;
+        float z = spatialValues[2][i] - o_z;
+        float dot = x * d_x + y * d_y + z * d_z;
+        x = x - dot * d_x;
+        y = y - dot * d_y;
+        z = z - dot * d_z;
+        float d = (float) Math.sqrt(x * x + y * y + z * z);
+        if (d < distance) {
+          distance = d;
+          closeIndex = i;
+
+          offsetx = x;
+          offsety = y;
+          offsetz = z;
+
+        }
+  
+      }
+      if (distance <= getDisplayRenderer().getPickThreshhold()) {
+        return distance;
+      }
+    } // end if (spatialValues != null)
+
+    offsetx = 0.0f;
+    offsety = 0.0f;
+    offsetz = 0.0f;
+
+    closeIndex = -1;
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r == r) {
+        if (greedy) {
+          return 0.0f;
+        }
+        else {
+          return getDisplayRenderer().getPickThreshhold() - 0.001f;
+        }
+      }
+      else {
+        return Float.MAX_VALUE;
+      }
+    }
+    catch (VisADException ex) {
+      return Float.MAX_VALUE;
+    }
+  }
+
+  public void setGreedy(boolean greedy) {
+    this.greedy = greedy;
+  }
+
+  /** 
+   * Mouse button released, ending direct manipulation.  Does nothing
+   * now, may need to do this for performance.
+   */
+  public synchronized void release_direct() {
+    // may need to do this for performance
+  }
+
+  /**
+   * Stop direct manipulation.
+   */
+  public void stop_direct() {
+    stop = true;
+  }
+
+  int which_set = -1;
+  int which_point = -1;
+
+  /**
+   * This method is called when direct manipulation is occuring.
+   * This adds points to the Gridded2DSet that comprises the current
+   * line being drawn. 
+   * @param  ray              ray to dragging point of mouse
+   * @param  first            true if this is the first point.
+   * @param  mouseModifiers   modifiers used with mouse click.
+   */
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    // System.out.println("drag_direct " + first + " " + ref + " " + shadow);
+    // if (spatialValues == null || ref == null || shadow == null) return;
+    if (ref == null || shadow == null) return;
+
+    if (first) {
+      stop = false;
+    }
+    else {
+      if (stop) return;
+    }
+
+    // double[] origin = ray.position;
+    double[] origin = {ray.position[0], ray.position[1], ray.position[2]};
+    double[] direction = ray.vector;
+
+    if (pickCrawlToCursor) {
+      if (first) {
+        offset_count = OFFSET_COUNT_INIT;
+      }
+      else {
+        if (offset_count > 0) offset_count--;
+      }
+      if (offset_count > 0) {
+        float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
+        origin[0] += mult * offsetx;
+        origin[1] += mult * offsety;
+        origin[2] += mult * offsetz;
+      }
+    }
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r != r) {
+        return;
+      }
+      float[][] xcs = {{(float) (origin[0] + r * direction[0])},
+                      {(float) (origin[1] + r * direction[1])},
+                      {(float) (origin[2] + r * direction[2])}};
+      float[] xx = {xcs[0][0], xcs[1][0], xcs[2][0]};
+      if (tuple != null) {
+        if (tuplecs == null) return;
+        xcs = tuplecs.fromReference(xcs);
+      }
+      float[] x = {xcs[0][0], xcs[1][0], xcs[2][0]};
+
+      addPoint(xx);
+
+      UnionSet data;
+      try {
+        data = (UnionSet) link.getData();
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return;
+        }
+        throw re;
+      }
+
+      if (data == null) return;
+      SampledSet[] sets = data.getSets();
+      int n = sets.length;
+
+      UnionSet newData = null;
+
+      // create location string
+      Vector vect = new Vector();
+      for (int i=0; i<3; i++) {
+        int j = getAxisToComponent(i);
+        if (j >= 0) {
+          f[0] = x[i];
+
+          // WLH 13 Feb 2001
+          ScalarMap dm = getDirectMap(i);
+          if (dm == null) return;
+          d = dm.inverseScaleValues(f);
+          // d = getDirectMap(i).inverseScaleValues(f);
+
+          value[j] = d[0];
+          RealType rtype = domain_reals[j];
+
+          // WLH 31 Aug 2000
+          Real rr = new Real(rtype, d[0]);
+          Unit overrideUnit = dm.getOverrideUnit(); // WLH 13 Feb 2001
+          // Unit overrideUnit = getDirectMap(i).getOverrideUnit();
+          Unit rtunit = rtype.getDefaultUnit();
+          // units not part of Time string
+          if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+              !RealType.Time.equals(rtype)) {
+            double dval = overrideUnit.toThis((double) d[0], rtunit);
+            rr = new Real(rtype, dval, overrideUnit);
+          }
+          String valueString = rr.toValueString();
+          vect.addElement(rtype.getName() + " = " + valueString);
+
+        }
+      }
+      getDisplayRenderer().setCursorStringVector(vect);
+
+      if (closeIndex < 0) {
+        if (first) {
+          if (only_one) {
+            SampledSet[] new_sets = new SampledSet[1];
+            float[][] new_samples = {{value[0]}, {value[1]}};
+            new_sets[0] = new Gridded2DSet(type, new_samples, 1,
+                                           data.getCoordinateSystem(),
+                                           data.getSetUnits(), null);
+            newData = new UnionSet(type, new_sets);
+// System.out.println("drag_direct new");
+          }
+          else {
+            SampledSet[] new_sets = new SampledSet[n+1];
+            System.arraycopy(sets, 0, new_sets, 0, n);
+            float[][] new_samples = {{value[0]}, {value[1]}};
+            new_sets[n] = new Gridded2DSet(type, new_samples, 1,
+                                           data.getCoordinateSystem(),
+                                           data.getSetUnits(), null);
+            newData = new UnionSet(type, new_sets);
+          }
+        }
+        else { // !first
+          float[][] samples = sets[n-1].getSamples(false);
+          int m = samples[0].length;
+          float[][] new_samples = new float[2][m+1];
+          System.arraycopy(samples[0], 0, new_samples[0], 0, m);
+          System.arraycopy(samples[1], 0, new_samples[1], 0, m);
+          new_samples[0][m] = value[0];
+          new_samples[1][m] = value[1];
+          sets[n-1] = new Gridded2DSet(type, new_samples, m+1,
+                                       data.getCoordinateSystem(),
+                                       data.getSetUnits(), null);
+          newData = new UnionSet(type, sets);
+        }
+      }
+      else { // closeIndex >= 0
+        if (first) {
+          which_set = -1;
+          which_point = -1;
+          int w = closeIndex;
+          int len = 0;
+          for (int i=0; i<n; i++) {
+            len = sets[i].getLength();
+            if (w < len) {
+              which_set = i;
+              which_point = w;
+              break;
+            }
+            w -= len;
+          }
+          if (which_set < 0) return; // shouldn't happen
+          float[][] samples = sets[which_set].getSamples(false);
+          if (which_point == 0) {
+            samples = invert(samples);
+            which_point = len - 1;
+          }
+          samples[0][which_point] = value[0];
+          samples[1][which_point] = value[1];
+
+          // int m = samples[0].length;
+          if (which_point == (len - 1)) {
+            sets = rotate(sets, which_set);
+            which_set = n - 1;
+            closeIndex = -1;
+          }
+          // sets[which_set]= new Gridded2DSet(type, samples, m,
+          sets[which_set]= new Gridded2DSet(type, samples, len,
+                                     data.getCoordinateSystem(),
+                                     data.getSetUnits(), null);
+          newData = new UnionSet(type, sets);
+// System.out.println("drag_direct len = " + len + " which_set = " + which_set);
+        }
+        else { // !first
+          if (which_set < 0) return; // shouldn't happen
+          float[][] samples = sets[which_set].getSamples(false);
+          int len = samples[0].length;
+
+          // figure out which direction to look for new nearest point
+          int dir = 0;
+          if (which_point == 0) {
+            dir = 1; // cannot happen
+          }
+          else if (which_point == len - 1) {
+            dir = -1; // cannot happen
+          }
+          else {
+            float v0 = value[0] - samples[0][which_point];
+            float v1 = value[1] - samples[1][which_point];
+            float v = (float) Math.sqrt(v0 * v0 + v1 * v1);
+            int NPTS = 5;
+            int wp = which_point + NPTS;
+            if (wp > len - 2) wp = len - 2;
+            float pplus = 0;
+            float psum = 0.0f;
+            for (int i=which_point; i<wp; i++) {
+              float vp0 = samples[0][i + 1] - samples[0][i];
+              float vp1 = samples[1][i + 1] - samples[1][i];
+              float vp = (float) Math.sqrt(vp0 * vp0 + vp1 * vp1);
+              float pp = (v0 * vp0 + v1 * vp1) / v * vp;
+              if (pp > 0.0) pplus += 1;
+              psum += pp;
+            }
+            float pdiv = (wp - which_point) > 0 ? (wp - which_point) : 1.0f;
+            pplus = pplus / pdiv;
+            psum = psum / pdiv;
+
+            int wm = which_point - NPTS;
+            if (wm < 1) wm = 1;
+            float mplus = 0;
+            float msum = 0.0f;
+            for (int i=which_point; i>wm; i--) {
+              float vm0 = samples[0][i - 1] - samples[0][i];
+              float vm1 = samples[1][i - 1] - samples[1][i];
+              float vm = (float) Math.sqrt(vm0 * vm0 + vm1 * vm1);
+              float mm = (v0 * vm0 + v1 * vm1) / v * vm;
+              if (mm > 0.0) mplus += 1;
+              msum += mm;
+            }
+            float mdiv = (which_point - wm) > 0 ? (which_point - wm) : 1.0f;
+            mplus = mplus / mdiv;
+            msum = msum / mdiv;
+            dir = (pplus > mplus) ? 1 : -1;
+            if (pplus == mplus) dir = (psum > msum) ? 1 : -1;
+          }
+
+          float distance = Float.MAX_VALUE; // actually distance squared
+          int new_point = -1;
+          if (dir > 0) {
+            for (int i=which_point + 1; i<len; i++) {
+              float d = (samples[0][i] - value[0]) * (samples[0][i] - value[0]) +
+                        (samples[1][i] - value[1]) * (samples[1][i] - value[1]);
+              if (d < distance) {
+                distance = d;
+                new_point = i;
+              }
+            }
+            if (new_point < 0) return; // shouldn't happen
+            if (which_point + 1 == new_point) {
+              if (which_point + 2 == len) {
+                samples[0][which_point + 1] = value[0];
+                samples[1][which_point + 1] = value[1];
+                which_point = which_point + 1;
+              }
+              else {
+                float[][] new_samples = new float[2][len + 1];
+                System.arraycopy(samples[0], 0, new_samples[0], 0, which_point + 1);
+                System.arraycopy(samples[1], 0, new_samples[1], 0, which_point + 1);
+                new_samples[0][which_point + 1] = value[0];
+                new_samples[1][which_point + 1] = value[1];
+                System.arraycopy(samples[0], which_point + 1, new_samples[0],
+                                 which_point + 2, len - (which_point + 1));
+                System.arraycopy(samples[1], which_point + 1, new_samples[1],
+                                 which_point + 2, len - (which_point + 1));
+                samples = new_samples;
+                which_point = which_point + 1;
+                len++;
+              }
+            }
+            else if (which_point + 2 == new_point) {
+              samples[0][which_point + 1] = value[0];
+              samples[1][which_point + 1] = value[1];
+              which_point = which_point + 1;
+            }
+            else { // which_point + 2 < new_point
+              int new_len = len - (new_point - (which_point + 2));
+              float[][] new_samples = new float[2][new_len];
+              System.arraycopy(samples[0], 0, new_samples[0], 0, which_point + 1);
+              System.arraycopy(samples[1], 0, new_samples[1], 0, which_point + 1);
+              new_samples[0][which_point + 1] = value[0];
+              new_samples[1][which_point + 1] = value[1];
+              System.arraycopy(samples[0], new_point, new_samples[0],
+                               which_point + 2, len - new_point);
+              System.arraycopy(samples[1], new_point, new_samples[1],
+                               which_point + 2, len - new_point);
+              samples = new_samples;
+              which_point = which_point + 1;
+              len = new_len;
+            }
+          }
+          else { // if (dir < 0)
+            for (int i=0; i<which_point; i++) {
+              float d = (samples[0][i] - value[0]) * (samples[0][i] - value[0]) +
+                        (samples[1][i] - value[1]) * (samples[1][i] - value[1]);
+              if (d < distance) {
+                distance = d;
+                new_point = i;
+              }
+            }
+            if (new_point < 0) return; // shouldn't happen
+            if (new_point == which_point - 1) {
+              if (which_point - 1 == 0) {
+                samples[0][which_point - 1] = value[0];
+                samples[1][which_point - 1] = value[1];
+                which_point = which_point - 1;
+              }
+              else {
+                float[][] new_samples = new float[2][len + 1];
+                System.arraycopy(samples[0], 0, new_samples[0], 0, which_point);
+                System.arraycopy(samples[1], 0, new_samples[1], 0, which_point);
+                new_samples[0][which_point] = value[0];
+                new_samples[1][which_point] = value[1];
+                System.arraycopy(samples[0], which_point, new_samples[0],
+                                 which_point + 1, len - which_point);
+                System.arraycopy(samples[1], which_point, new_samples[1],
+                                 which_point + 1, len - which_point);
+                samples = new_samples;
+                len++;
+              }
+            }
+            else if (new_point == which_point - 2) {
+              samples[0][which_point - 1] = value[0];
+              samples[1][which_point - 1] = value[1];
+              which_point = which_point - 1;
+            }
+            else { // new_point < which_point - 2
+              int new_len = len - ((which_point - 2) - new_point);
+              float[][] new_samples = new float[2][new_len];
+              System.arraycopy(samples[0], 0, new_samples[0], 0, new_point + 1);
+              System.arraycopy(samples[1], 0, new_samples[1], 0, new_point + 1);
+              new_samples[0][new_point + 1] = value[0];
+              new_samples[1][new_point + 1] = value[1];
+              System.arraycopy(samples[0], which_point, new_samples[0],
+                               new_point + 2, len - which_point);
+              System.arraycopy(samples[1], which_point, new_samples[1],
+                               new_point + 2, len - which_point);
+              samples = new_samples;
+              which_point = new_point + 1;
+              len = new_len;
+            }
+          } // if end (dir < 0)
+
+
+          if (which_point == 0) {
+            samples = invert(samples);
+            which_point = len - 1;
+          }
+
+          if (which_point == (len - 1)) {
+            sets = rotate(sets, which_set);
+            which_set = n - 1;
+            closeIndex = -1;
+          }
+          sets[which_set]= new Gridded2DSet(type, samples, len,
+                                     data.getCoordinateSystem(),
+                                     data.getSetUnits(), null);
+          newData = new UnionSet(type, sets);
+
+        }
+      }
+
+      ref.setData(newData);
+      link.clearData();
+
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  private float[][] invert(float[][] samples) {
+    int m = samples[0].length;
+    float[][] new_samples = new float[2][m];
+    int m1 = m - 1;
+    for (int i=0; i<m; i++) {
+      new_samples[0][i] = samples[0][m1 - i];
+      new_samples[1][i] = samples[1][m1 - i];
+    }
+    return new_samples;
+  }
+
+  private SampledSet[] rotate(SampledSet[] sets, int which_set) {
+    int n = sets.length;
+    int k = (n - 1) - which_set;
+    if (k == 0) return sets;
+    SampledSet[] new_sets = new SampledSet[n];
+    if (which_set > 0) {
+      System.arraycopy(sets, 0, new_sets, 0, which_set);
+    }
+    if (k > 0) {
+      System.arraycopy(sets, which_set + 1, new_sets, which_set, k);
+    }
+    new_sets[n - 1] = sets[which_set];
+    return new_sets;
+  }
+
+  /**
+   * Clone this renderer.
+   * @return  new Renderer with same parameters.
+   */
+  public Object clone() {
+    return new CurveManipulationRendererJ3D(mouseModifiersMask,
+                                            mouseModifiersValue, only_one);
+  }
+
+  private static final int N = 64;
+
+  /** test CurveManipulationRendererJ3D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealTupleType xy = new RealTupleType(x, y);
+
+    RealType c = RealType.getRealType("c");
+    FunctionType ft = new FunctionType(xy, c);
+
+    // construct Java3D display and mappings
+    DisplayImpl display = null;
+    if (args.length == 0) {
+      display = new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    }
+    else {
+      display = new DisplayImplJ3D("display1");
+    }
+    if (args.length == 0 || args[0].equals("z")) {
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.YAxis));
+    }
+    else if (args[0].equals("x")) {
+      display.addMap(new ScalarMap(x, Display.YAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("y")) {
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("radius")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    else if (args[0].equals("lat")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else if (args[0].equals("lon")) {
+      display.addMap(new ScalarMap(x, Display.Latitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    display.addMap(new ScalarMap(c, Display.RGB));
+
+    DisplayRenderer displayRenderer = display.getDisplayRenderer();
+    displayRenderer.setBoxOn(false);
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setLineWidth(2.0f);
+
+    Integer2DSet fset = new Integer2DSet(xy, N, N);
+    FlatField field = new FlatField(ft, fset);
+    float[][] values = new float[1][N * N];
+    int k = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        values[0][k++] = (i - N / 2) * (j - N / 2);
+      }
+    }
+    field.setSamples(values);
+    DataReferenceImpl field_ref = new DataReferenceImpl("field");
+    field_ref.setData(field);
+    // display.addReference(field_ref);
+    DefaultRendererJ3D renderer = new DefaultRendererJ3D();
+    if (args.length > 0 && args[0].equals("radius")) {
+      ConstantMap[] cmaps = {new ConstantMap(0.99, Display.Radius)};
+      display.addReferences(renderer, field_ref, cmaps);
+    }
+    else {
+      display.addReferences(renderer, field_ref);
+      renderer.toggle(false);
+    }
+
+    // construct invisible starter set
+    Gridded2DSet set1 = (args.length > 0 && args[0].equals("radius")) ?
+      new Gridded2DSet(xy, new float[][] {{0.0f}, {0.0f}}, 1) :
+      new Gridded2DSet(xy, new float[][] {{-1000.0f}, {-1000.0f}}, 1);
+    Gridded2DSet[] sets = {set1};
+    UnionSet set = new UnionSet(xy, sets);
+
+    DataReferenceImpl ref = new DataReferenceImpl("set");
+    ref.setData(set);
+    boolean only_one = (args.length > 1);
+    CurveManipulationRendererJ3D cmr =
+      new CurveManipulationRendererJ3D(0, 0, only_one);
+    display.addReferences(cmr, ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test CurveManipulationRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    JPanel button_panel = new JPanel();
+    button_panel.setLayout(new BoxLayout(button_panel, BoxLayout.X_AXIS));
+    button_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    button_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    CurveDelete cd = new CurveDelete(ref, display);
+    if (!only_one) {
+      JButton del = new JButton("delete last");
+      del.addActionListener(cd);
+      del.setActionCommand("del");
+      button_panel.add(del);
+    }
+    JButton fill = new JButton("fill");
+    fill.addActionListener(cd);
+    fill.setActionCommand("fill");
+    button_panel.add(fill);
+    panel.add(button_panel);
+    JButton lines = new JButton("lines");
+    lines.addActionListener(cd);
+    lines.setActionCommand("lines");
+    button_panel.add(lines);
+    panel.add(button_panel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/visad/bom/CutAndPasteFields.java b/visad/bom/CutAndPasteFields.java
new file mode 100644
index 0000000..6833307
--- /dev/null
+++ b/visad/bom/CutAndPasteFields.java
@@ -0,0 +1,886 @@
+//
+// CutAndPasteFields.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.util.*;
+import visad.java3d.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+/**
+</pre>
+   CutAndPasteFields is the VisAD class for cutting and pasting
+   regions of fields.<p>
+   Construct a CutAndPasteFields object linked to a 2-D grid [a FlatField
+   with MathType ((x, y) -> range)]) or a sequence of 2-D grids {a FieldImpl
+   with MathType (t -> ((x, y) -> range))], a DisplayImpl, and an optional
+   integer blend region width. The grid or grids must all have the same
+   Linear domain Set (Linear2DSet or LinearNDSet with domain dimension = 2),
+   and the domain must be mapped to two of XAxis, YAxis and ZAxis. If a
+   sequence of grids, the sequence domain must be mapped to Animation. The
+   grid may have any number of range RealTypes.
+
+   The CutAndPasteFields object operates in a sequence:
+   1. Invokes its start() method to start.
+   2. User drags a grid sector rectangle with the right mouse button.
+      This rectangle must lie inside the grid.
+   3. When the user releases, the rectangle appears fixed over the grid.
+   4. The user can change to a different time step, and drag the rectangle
+      by one of its corners. On release, it must lie inside the grid.
+   5. The source grid sector is pasted into the destination, within blending
+      over a certain width near the destination border.
+   6. The user can change time step or drag the rectangle any number of
+      times: each time the previous paste is undone and the source grid
+      sector is pasted into the new time and location.
+   7. At any point after start(), the application can invoke stop() to
+      stop the process.
+   8. After a source rectangle has been pasted, the application can invoke
+      undo() to undo the paste and stop the process.
+   9. The process can be restarted by invoking start(), any number of times.
+   10. At any point, the application can invoke setBlend() to change the
+       width of the blend region.
+
+   The main() method illustrates a simple GUI and test case with a sequnece
+   of grids. Run 'java visad.bom.CutAndPasteFields' to test with contour
+   values, and run 'java visad.bom.CutAndPasteFields 1' to test with color
+   values.
+</pre>
+*/
+public class CutAndPasteFields extends Object implements ActionListener {
+
+  private boolean debug = true;
+
+  private Field grids = null;
+  private DisplayImpl display = null;
+  private int blend = 0;
+
+  private Object lock = new Object();
+
+  private RealType t = null; // non-null if animation
+  private RealType x = null;
+  private RealType y = null;
+  private RealTupleType xy = null;
+  private MathType range = null; // RealType or RealTupleType
+  private int rangedim = 0;
+  private int nts = 0; // number of steps in sequence
+
+  Set tset = null; // t domain Set
+  Set xyset = null; // (x, y) domain Set
+  int nx, ny; // dimensions of xyset
+  int rx, ry; // dimensions of cut rectangle
+
+  float[][] cut = null;
+  float[][] replaced = null;
+  FlatField replacedff = null;
+  int replacedixlow, replacedixhi;
+  int replacediylow, replacediyhi;
+
+  AnimationControl acontrol = null;
+
+  ScalarMap tmap = null;
+  ScalarMap xmap = null;
+  ScalarMap ymap = null;
+
+  private double xlow, xhi, ylow, yhi; // rect boundaries
+  private int ixlow, ixhi, iylow, iyhi; // x and y indices
+
+  private CellImpl cell_rbb = null;
+  private CellImpl cell_xlyl = null;
+  private CellImpl cell_xlyh = null;
+  private CellImpl cell_xhyl = null;
+  private CellImpl cell_xhyh = null;
+
+  private DataReferenceImpl ref_rbb = null;
+  private DataReferenceImpl ref_xlyl = null;
+  private DataReferenceImpl ref_xlyh = null;
+  private DataReferenceImpl ref_xhyl = null;
+  private DataReferenceImpl ref_xhyh = null;
+  private DataReferenceImpl ref_rect = null;
+
+  private RubberBandBoxRendererJ3D rbbr = null;
+  private BoxDragRendererJ3D xlylr = null;
+  private BoxDragRendererJ3D xlyhr = null;
+  private BoxDragRendererJ3D xhylr = null;
+  private BoxDragRendererJ3D xhyhr = null;
+  private DefaultRendererJ3D rectr = null;
+
+  private CutAndPasteFields thiscp = null;
+
+  public CutAndPasteFields(Field gs, DisplayImplJ3D d)
+         throws VisADException, RemoteException {
+    this(gs, d, 0);
+  }
+
+  /**
+<pre>
+     gs has MathType (t -> ((x, y) -> v)) or ((x, y) -> v)
+     conditions on gs and display:
+     1. x and y mapped to XAxis, YAxis, ZAxis
+     2. (x, y) domain LinearSet
+     3. if (t -> ...), then t is mapped to Animation
+     b is width of blend region
+</pre>
+  */
+  public CutAndPasteFields(Field gs, DisplayImplJ3D d, int b)
+         throws VisADException, RemoteException {
+    grids = gs;
+    display = d;
+    if (b >= 0) blend = b;
+
+    thiscp = this;
+
+    FunctionType gstype = (FunctionType) gs.getType();
+    RealTupleType domain = gstype.getDomain();
+    int domdim = domain.getDimension();
+    if (domdim == 1) {
+      t = (RealType) domain.getComponent(0);
+      tset = gs.getDomainSet();
+      FunctionType gridtype = (FunctionType) gstype.getRange();
+      xy = gridtype.getDomain();
+      int dim = xy.getDimension();
+      if (dim != 2) {
+        throw new VisADException("bad grid Field domain dimension: " + dim);
+      }
+      range = gridtype.getRange();
+      nts = tset.getLength();
+      for (int i=0; i<nts; i++) {
+        FlatField ff = (FlatField) gs.getSample(i);
+        Set s = ff.getDomainSet();
+        if (xyset == null) {
+          xyset = s;
+        }
+        else {
+          if (!xyset.equals(s)) {
+            throw new VisADException("grid sets must match in animation");
+          }
+        }
+      }
+    }
+    else if (domdim == 2) {
+      t = null;
+      tset = null;
+      xy = domain;
+      range = gstype.getRange();
+      xyset = gs.getDomainSet();
+    }
+    else {
+      throw new VisADException("bad grid Field domain dimension: " + domdim);
+    }
+    x = (RealType) xy.getComponent(0);
+    y = (RealType) xy.getComponent(1);
+    if (!(xyset instanceof LinearSet)) {
+      throw new VisADException("grid set must be LinearSet");
+    }
+    nx = ((LinearSet) xyset).getLinear1DComponent(0).getLength();
+    ny = ((LinearSet) xyset).getLinear1DComponent(1).getLength();
+
+    if (range instanceof RealType) {
+      rangedim = 1;
+    }
+    else if (range instanceof RealTupleType) {
+      rangedim = ((RealTupleType) range).getDimension();
+    }
+    else {
+      throw new VisADException("bad grid Field range type: " + range);
+    }
+
+    Vector scalar_map_vector = display.getMapVector();
+    Enumeration en = scalar_map_vector.elements();
+    while (en.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) en.nextElement();
+      ScalarType scalar = map.getScalar();
+      DisplayRealType dreal = map.getDisplayScalar();
+      if (scalar.equals(t)) {
+        if (Display.Animation.equals(dreal)) {
+          tmap = map;
+          acontrol = (AnimationControl) tmap.getControl();
+        }
+      }
+      else if (scalar.equals(x)) {
+        if (Display.XAxis.equals(dreal) ||
+            Display.YAxis.equals(dreal) ||
+            Display.ZAxis.equals(dreal)) {
+          xmap = map;
+        }
+      }
+      else if (scalar.equals(y)) {
+        if (Display.XAxis.equals(dreal) ||
+            Display.YAxis.equals(dreal) ||
+            Display.ZAxis.equals(dreal)) {
+          ymap = map;
+        }
+      }
+    }
+    if (xmap == null || ymap == null) {
+      throw new VisADException("grid domain RealType must be mapped to " +
+                               "XAxis, YAxis or ZAxis");
+    }
+    if (t != null && tmap == null) {
+      throw new VisADException("grid sequence must be mapped to Animation");
+    }
+
+    ref_rbb = new DataReferenceImpl("rbb");
+    ref_xlyl = new DataReferenceImpl("xlyl");
+    ref_xlyh = new DataReferenceImpl("xlyh");
+    ref_xhyl = new DataReferenceImpl("xhyl");
+    ref_xhyh = new DataReferenceImpl("xhyh");
+    ref_rect = new DataReferenceImpl("rect");
+
+    rbbr = new RubberBandBoxRendererJ3D(x, y);
+    display.addReferences(rbbr, ref_rbb);
+    rbbr.suppressExceptions(true);
+    rbbr.toggle(false);
+
+    xlylr = new BoxDragRendererJ3D(thiscp);
+    display.addReferences(xlylr, ref_xlyl);
+    xlylr.suppressExceptions(true);
+    xlylr.toggle(false);
+
+    xlyhr = new BoxDragRendererJ3D(thiscp);
+    display.addReferences(xlyhr, ref_xlyh);
+    xlyhr.suppressExceptions(true);
+    xlyhr.toggle(false);
+
+    xhylr = new BoxDragRendererJ3D(thiscp);
+    display.addReferences(xhylr, ref_xhyl);
+    xhylr.suppressExceptions(true);
+    xhylr.toggle(false);
+
+    xhyhr = new BoxDragRendererJ3D(thiscp);
+    display.addReferences(xhyhr, ref_xhyh);
+    xhyhr.suppressExceptions(true);
+    xhyhr.toggle(false);
+
+    rectr = new DefaultRendererJ3D();
+    display.addReferences(rectr, ref_rect);
+    rectr.suppressExceptions(true);
+    rectr.toggle(false);
+
+
+    cell_xlyl = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        synchronized (lock) {
+          RealTuple rt = (RealTuple) ref_xlyl.getData();
+          if (rt == null) return;
+          double xl = ((Real) rt.getComponent(0)).getValue();
+          double yl = ((Real) rt.getComponent(1)).getValue();
+          if (!Util.isApproximatelyEqual(xl, xlow) ||
+              !Util.isApproximatelyEqual(yl, ylow)) {
+            xhi += (xl - xlow);
+            yhi += (yl - ylow);
+            xlow = xl;
+            ylow = yl;
+            drag();
+          }
+        }
+      }
+    };
+
+    cell_xlyh = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        synchronized (lock) {
+          RealTuple rt = (RealTuple) ref_xlyh.getData();
+          if (rt == null) return;
+          double xl = ((Real) rt.getComponent(0)).getValue();
+          double yh = ((Real) rt.getComponent(1)).getValue();
+          if (!Util.isApproximatelyEqual(xl, xlow) ||
+              !Util.isApproximatelyEqual(yh, yhi)) {
+            xhi += (xl - xlow);
+            ylow += (yh - yhi);
+            xlow = xl;
+            yhi = yh;
+            drag();
+          }
+        }
+      }
+    };
+
+    cell_xhyl = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        synchronized (lock) {
+          RealTuple rt = (RealTuple) ref_xhyl.getData();
+          if (rt == null) return;
+          double xh = ((Real) rt.getComponent(0)).getValue();
+          double yl = ((Real) rt.getComponent(1)).getValue();
+          if (!Util.isApproximatelyEqual(xh, xhi) ||
+              !Util.isApproximatelyEqual(yl, ylow)) {
+            xlow += (xh - xhi);
+            yhi += (yl - ylow);
+            xhi = xh;
+            ylow = yl;
+            drag();
+          }
+        }
+      }
+    };
+
+    cell_xhyh = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        synchronized (lock) {
+          RealTuple rt = (RealTuple) ref_xhyh.getData();
+          if (rt == null) return;
+          double xh = ((Real) rt.getComponent(0)).getValue();
+          double yh = ((Real) rt.getComponent(1)).getValue();
+          if (!Util.isApproximatelyEqual(xh, xhi) ||
+              !Util.isApproximatelyEqual(yh, yhi)) {
+            xlow += (xh - xhi);
+            ylow += (yh - yhi);
+            xhi = xh;
+            yhi = yh;
+            drag();
+          }
+        }
+      }
+    };
+
+    // rubber band box release
+    cell_rbb = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        synchronized (lock) {
+          Set set = (Set) ref_rbb.getData();
+          if (set == null) return;
+          float[][] samples = set.getSamples();
+          if (samples == null) return;
+  
+          xlow = samples[0][0];
+          ylow = samples[1][0];
+          xhi = samples[0][1];
+          yhi = samples[1][1];
+  
+          if (xlow > xhi) {
+            double t = xlow;
+            xlow = xhi;
+            xhi = t;
+          }
+          if (ylow > yhi) {
+            double t = ylow;
+            ylow = yhi;
+            yhi = t;
+          }
+  
+          if (!getIndices(true)) {
+            System.out.println("bad box");
+            return;
+          }
+          getRect();
+          replacedff = null;
+   
+          cell_rbb.removeReference(ref_rbb);
+          drag();
+          cell_xlyl.addReference(ref_xlyl);
+          cell_xlyh.addReference(ref_xlyh);
+          cell_xhyl.addReference(ref_xhyl);
+          cell_xhyh.addReference(ref_xhyh);
+  
+          display.disableAction();
+          xlylr.toggle(true);
+          xlyhr.toggle(true);
+          xhylr.toggle(true);
+          xhyhr.toggle(true);
+          rectr.toggle(true);
+          rbbr.toggle(false);
+          display.enableAction();
+        }
+      }
+    };
+  }
+
+  public void start() throws VisADException, RemoteException {
+    synchronized (lock) {
+      cell_rbb.addReference(ref_rbb);
+      Gridded2DSet dummy_set = new Gridded2DSet(xy, null, 1);
+      ref_rbb.setData(dummy_set);
+      rbbr.toggle(true);
+    }
+  }
+
+  public void stop() throws VisADException, RemoteException {
+    synchronized (lock) {
+      display.disableAction();
+      rbbr.toggle(false);
+      xlylr.toggle(false);
+      xlyhr.toggle(false);
+      xhylr.toggle(false);
+      xhyhr.toggle(false);
+      rectr.toggle(false);
+      ref_xlyl.setData(null);
+      ref_xlyh.setData(null);
+      ref_xhyl.setData(null);
+      ref_xhyh.setData(null);
+      ref_rect.setData(null);
+      display.enableAction();
+  
+      try { cell_rbb.removeReference(ref_rbb); }
+      catch (ReferenceException e) { }
+      try { cell_xlyl.removeReference(ref_xlyl); }
+      catch (ReferenceException e) { }
+      try { cell_xlyh.removeReference(ref_xlyh); }
+      catch (ReferenceException e) { }
+      try { cell_xhyl.removeReference(ref_xhyl); }
+      catch (ReferenceException e) { }
+      try { cell_xhyh.removeReference(ref_xhyh); }
+      catch (ReferenceException e) { }
+    }
+  }
+
+  public void undo() throws VisADException, RemoteException {
+    synchronized (lock) {
+      if (replacedff != null) {
+        float[][] samples = replacedff.getFloats(false);
+        for (int ix=replacedixlow; ix<=replacedixhi; ix++) {
+          for (int iy=replacediylow; iy<=replacediyhi; iy++) {
+            int i = ix + nx * iy;
+            int j = (ix - replacedixlow) + rx * (iy - replacediylow);
+            for (int k=0; k<rangedim; k++) samples[k][i] = replaced[k][j];
+          }
+        }
+        replacedff.setSamples(samples, false);
+      }
+      replacedff = null;
+    }
+    stop();
+  }
+
+  public void setBlend(int b) {
+    if (b >= 0) blend = b;
+  }
+
+  /**
+   set ixlow, iylow, ixhi, iyhi from xlow, ylow, xhi, yhi
+   if (rubber) set rx, ry
+   else make sure ixlow, iylow, ixhi, iyhi are consistent with rx, ry
+   */
+  private boolean getIndices(boolean rubber) throws VisADException {
+    float[][] samples = {{(float) xlow, (float) xhi},
+                         {(float) ylow, (float) yhi}};
+    int[] indices = xyset.valueToIndex(samples);
+    int indexlow = indices[0];
+    int indexhi = indices[1];
+    if (indexlow < 0 || indexhi < 0) return false;
+
+    if (rubber) {
+      samples = xyset.indexToValue(indices);
+      xlow = samples[0][0];
+      ylow = samples[1][0];
+      xhi = samples[0][1];
+      yhi = samples[1][1];
+    }
+
+    if (indexlow > indexhi) {
+      int t= indexlow;
+      indexlow = indexhi;
+      indexhi = t;
+    }
+
+    // i = ix + nx * iy
+    iylow = indexlow / nx;
+    ixlow = indexlow % nx;
+    iyhi = indexhi / nx;
+    ixhi = indexhi % nx;
+    if (ixlow > ixhi) {
+      int t= ixlow;
+      ixlow = ixhi;
+      ixhi = t;
+    }
+    if (iylow > iyhi) {
+      int t= iylow;
+      iylow = iyhi;
+      iyhi = t;
+    }
+
+    if (rubber) {
+      rx = (ixhi - ixlow) + 1;
+      ry = (iyhi - iylow) + 1;
+    }
+    else {
+      int tx = (ixhi - ixlow) + 1;
+      int ty = (iyhi - iylow) + 1;
+      if (rx != tx) {
+        if ((ixlow + rx - 1) < nx) ixhi = ixlow + rx - 1;
+        else ixlow = ixhi - (rx - 1);
+      }
+      if (ry != ty) {
+        if ((iylow + ry - 1) < ny) iyhi = iylow + ry - 1;
+        else iylow = iyhi - (ry - 1);
+      }
+    }
+
+    return true;
+  }
+
+  private void getRect() throws VisADException, RemoteException {
+    FlatField ff = null;
+    if (t != null) {
+      int index = getAnimationIndex();
+      if (index < 0 || index >= nts) return;
+      ff = (FlatField) grids.getSample(index);
+    }
+    else {
+      ff = (FlatField) grids;
+    }
+    float[][] samples = ff.getFloats(false);
+    cut = new float[rangedim][rx * ry];
+    for (int ix=ixlow; ix<=ixhi; ix++) {
+      for (int iy=iylow; iy<=iyhi; iy++) {
+        int i = ix + nx * iy;
+        int j = (ix - ixlow) + rx * (iy - iylow);
+        for (int k=0; k<rangedim; k++) cut[k][j] = samples[k][i];
+      }
+    }
+  }
+
+  private void replaceRect() throws VisADException, RemoteException {
+    int index = 0;
+    if (t != null) {
+      index = getAnimationIndex();
+      if (index < 0 || index >= nts) return;
+    }
+
+    if (!getIndices(false)) {
+      System.out.println("bad box");
+      return;
+    }
+
+    if (replacedff != null) {
+      float[][] samples = replacedff.getFloats(false);
+      for (int ix=replacedixlow; ix<=replacedixhi; ix++) {
+        for (int iy=replacediylow; iy<=replacediyhi; iy++) {
+          int i = ix + nx * iy;
+          int j = (ix - replacedixlow) + rx * (iy - replacediylow);
+          for (int k=0; k<rangedim; k++) samples[k][i] = replaced[k][j];
+        }
+      }
+      replacedff.setSamples(samples, false);
+    }
+
+    FlatField ff = null;
+    if (t != null) {
+      ff = (FlatField) grids.getSample(index);
+    }
+    else {
+      ff = (FlatField) grids;
+    }
+    float[][] samples = ff.getFloats(false);
+    replaced = new float[rangedim][rx * ry];
+    for (int ix=ixlow; ix<=ixhi; ix++) {
+      for (int iy=iylow; iy<=iyhi; iy++) {
+        int i = ix + nx * iy;
+        int j = (ix - ixlow) + rx * (iy - iylow);
+        for (int k=0; k<rangedim; k++) replaced[k][j] = samples[k][i];
+        if (blend == 0) {
+          for (int k=0; k<rangedim; k++) samples[k][i] = cut[k][j];
+        }
+        else {
+          int d = ix - ixlow;
+          if ((ixhi - ix) < d) d = ixhi - ix;
+          if ((iy - iylow) < d) d = iy - iylow;
+          if ((iyhi - iy) < d) d = iyhi - iy;
+          if (d > blend) {
+            for (int k=0; k<rangedim; k++) samples[k][i] = cut[k][j];
+          }
+          else {
+            float a = ((float) d) / ((float) blend);
+            float b = 1.0f - a;
+            for (int k=0; k<rangedim; k++) {
+              samples[k][i] = b * samples[k][i] + a * cut[k][j];
+            }
+          }
+        }
+      }
+    }
+    ff.setSamples(samples, false);
+    replacedff = ff;
+    replacedixlow = ixlow;
+    replacedixhi = ixhi;
+    replacediylow = iylow;
+    replacediyhi = iyhi;
+  }
+
+  private int getAnimationIndex() throws VisADException {
+    int[] indices = {acontrol.getCurrent()};
+    Set aset = acontrol.getSet();
+    double[][] values = aset.indexToDouble(indices);
+    int[] tindices = tset.doubleToIndex(values);
+    return tindices[0];
+  }
+
+  private void drag() throws VisADException, RemoteException {
+    display.disableAction();
+    ref_xlyl.setData(new RealTuple(xy, new double[] {xlow, ylow}));
+    ref_xlyh.setData(new RealTuple(xy, new double[] {xlow, yhi}));
+    ref_xhyl.setData(new RealTuple(xy, new double[] {xhi, ylow}));
+    ref_xhyh.setData(new RealTuple(xy, new double[] {xhi, yhi}));
+    float[][] samples =
+      {{(float) xlow, (float) xlow, (float) xhi, (float) xhi, (float) xlow},
+       {(float) ylow, (float) yhi, (float) yhi, (float) ylow, (float) ylow}};
+    ref_rect.setData(new Gridded2DSet(xy, samples, 5));
+    display.enableAction();
+  }
+
+  // BoxDragRendererJ3D button release
+  void drag_release() {
+    synchronized (lock) {
+      try {
+        replaceRect();
+      }
+      catch (VisADException e) {
+        if (debug) System.out.println("release fail: " + e.toString());
+      }
+      catch (RemoteException e) {
+        if (debug) System.out.println("release fail: " + e.toString());
+      }
+    }
+  }
+
+  private static final int NSTAS = 64; // actually NSTAS * NSTAS
+  private static final int NTIMES = 10;
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    // construct RealTypes for wind record components
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealTupleType xy = new RealTupleType(x, y);
+    RealType windx = RealType.getRealType("windx",
+                          CommonUnit.meterPerSecond);     
+    RealType windy = RealType.getRealType("windy",
+                          CommonUnit.meterPerSecond);     
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+
+    // EarthVectorType extends RealTupleType and says that its
+    // components are vectors in m/s with components parallel
+    // to Longitude (positive east) and Latitude (positive north)
+    EarthVectorType windxy = new EarthVectorType(windx, windy);
+
+    RealType time = RealType.Time;
+    double startt = new DateTime(1999, 122, 57060).getValue();
+    Linear1DSet time_set = new Linear1DSet(time, startt, startt + 2700.0, NTIMES);
+
+    Linear2DSet grid_set = new Integer2DSet(xy, NSTAS, NSTAS);
+
+    RealTupleType tuple_type = new RealTupleType(new RealType[]
+             {lon, lat, windx, windy, red, green});
+
+    FunctionType field_type = new FunctionType(xy, tuple_type);
+    FunctionType seq_type = new FunctionType(time, field_type);
+
+    // construct first Java3D display and mappings that govern
+    // how wind records are displayed
+    DisplayImplJ3D display1 =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap ymap = new ScalarMap(y, Display.YAxis);
+    display1.addMap(ymap);
+    ScalarMap xmap = new ScalarMap(x, Display.XAxis);
+    display1.addMap(xmap);
+
+    ScalarMap cmap = null;
+    if (args.length > 0) {
+      cmap = new ScalarMap(windy, Display.RGB);
+    }
+    else {
+      cmap = new ScalarMap(windy, Display.IsoContour);
+    }
+    display1.addMap(cmap);
+
+    ScalarMap amap = new ScalarMap(time, Display.Animation);
+    display1.addMap(amap);
+    AnimationControl acontrol = (AnimationControl) amap.getControl();
+    acontrol.setStep(500);
+
+    // create an array of NSTAS by NSTAS winds
+    FieldImpl field = new FieldImpl(seq_type, time_set);
+    double[][] values = new double[6][NSTAS * NSTAS];
+    for (int k=0; k<NTIMES; k++) {
+      FlatField ff = new FlatField(field_type, grid_set);
+      int m = 0;
+      for (int i=0; i<NSTAS; i++) {
+        for (int j=0; j<NSTAS; j++) {
+
+          double u = 2.0 * i / (NSTAS - 1.0) - 1.0;
+          double v = 2.0 * j / (NSTAS - 1.0) - 1.0;
+  
+          // each wind record is a Tuple (lon, lat, (windx, windy), red, green)
+          // set colors by wind components, just for grins
+          values[0][m] = 10.0 * u;
+          values[1][m] = 10.0 * v - 40.0;
+          double fx = k + 30.0 * u;
+          double fy = 30.0 * v;
+          double fd =
+            Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy) + k * 15.0;
+          double fs = Math.sqrt(fx * fx + fy * fy);
+          values[2][m] = fd;
+          values[3][m] = fs;
+          values[4][m] = u;
+          values[5][m] = v;
+          m++;
+        }
+      }
+      ff.setSamples(values);
+      field.setSample(k, ff);
+    }
+
+    DataReferenceImpl seq_ref = new DataReferenceImpl("seq");
+    seq_ref.setData(field);
+    display1.addReference(seq_ref);
+
+    final CutAndPasteFields cp = new CutAndPasteFields(field, display1);
+
+    final DataReferenceImpl blend_ref = new DataReferenceImpl("blend_ref");
+    VisADSlider blend_slider =
+      new VisADSlider("blend", 0, 10, 0, 1.0, blend_ref, RealType.Generic);
+    CellImpl blend_cell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        int blend = (int) ((Real) blend_ref.getData()).getValue();
+        cp.setBlend(blend);
+      }
+    };
+    blend_cell.addReference(blend_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test CollectiveBarbManipulation");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+    JPanel panel1 = new JPanel();
+    panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
+    JPanel panel2 = new JPanel();
+    panel2.setLayout(new BoxLayout(panel2, BoxLayout.Y_AXIS));
+
+
+    panel1.add(display1.getComponent());
+    panel1.setMaximumSize(new Dimension(400, 600));
+
+    JPanel panel3 = new JPanel();
+    panel3.setLayout(new BoxLayout(panel3, BoxLayout.X_AXIS));
+    final JButton start = new JButton("start");
+    start.addActionListener(cp);
+    start.setActionCommand("start");
+    final JButton stop = new JButton("stop");
+    stop.addActionListener(cp);
+    stop.setActionCommand("stop");
+    final JButton undo = new JButton("undo");
+    undo.addActionListener(cp);
+    undo.setActionCommand("undo");
+    panel3.add(start);
+    panel3.add(stop);
+    panel3.add(undo);
+
+    panel2.add(new AnimationWidget(amap));
+    if (args.length > 0) {
+      LabeledColorWidget lcw = new LabeledColorWidget(cmap);
+      lcw.setMaximumSize(new Dimension(400, 200));
+      panel2.add(lcw);
+    }
+    else {
+      ContourWidget cw = new ContourWidget(cmap);
+      cw.setMaximumSize(new Dimension(400, 200));
+      panel2.add(cw);
+    }
+    panel2.add(new JLabel(" "));
+    panel2.add(blend_slider);
+    panel2.add(new JLabel(" "));
+    panel2.add(panel3);
+    panel2.setMaximumSize(new Dimension(400, 600));
+
+    panel.add(panel1);
+    panel.add(panel2);
+    frame.getContentPane().add(panel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(800, 600);
+    frame.setVisible(true);
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("start")) {
+      try {
+        start();
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+    else if (cmd.equals("stop")) {
+      try {
+        stop();
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+    else if (cmd.equals("undo")) {
+      try {
+        undo();
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+
+}
+
+class BoxDragRendererJ3D extends DirectManipulationRendererJ3D {
+
+  CutAndPasteFields cp;
+
+  BoxDragRendererJ3D(CutAndPasteFields c) {
+    super();
+    cp = c;
+  }
+
+  /** mouse button released, ending direct manipulation */
+  public void release_direct() {
+    cp.drag_release();
+  }
+}
+
diff --git a/visad/bom/DiscoverableZoom.java b/visad/bom/DiscoverableZoom.java
new file mode 100644
index 0000000..a40837e
--- /dev/null
+++ b/visad/bom/DiscoverableZoom.java
@@ -0,0 +1,198 @@
+//
+// DiscoverableZoom.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+
+/**
+   DiscoverableZoom is the VisAD class for
+   discoverable zoom of displayed data.
+*/
+public class DiscoverableZoom extends Object
+       implements ControlListener {
+
+  private boolean pfirst = true;
+
+  private double base_scale = 1.0;
+  private float last_cscale = 1.0f;
+
+  private float latmul, lonmul;
+  private DataRenderer[] renderers = null;
+  private boolean[] enabled = null;
+  private int nrenderers = -1;
+  private float[] lons = null;
+  private float[] lats = null;
+
+  /** the DataRenderers in the rs array are assumed to each link to
+      a Tuple data object that includes Real fields for Latitude and
+      Longitude, and that Latitude and Longitude are mapped to spatial
+      DisplayRealTypes; the DataRenderers in rs are enabled or disabled
+      to maintain a roughly constant spacing among their visible
+      depictions;
+      distance is the scale for distance;
+      in order to work, this DiscoverableZoom must be added as a
+      Control Listener to the ProjectionControl of the DisplayImpl
+      linked to the DataRenderer array rs;
+      see CollectiveBarbManipulation.java for an example of use */
+  public void setRenderers(DataRenderer[] rs, float distance)
+         throws VisADException, RemoteException {
+    renderers = rs;
+    if (renderers != null) {
+      nrenderers = renderers.length;
+      if (nrenderers == 0) {
+        nrenderers = -1;
+        return;
+      }
+      lons = new float[nrenderers];
+      lats = new float[nrenderers];
+      enabled = new boolean[nrenderers];
+      for (int i=0; i<nrenderers; i++) {
+        lons[i] = Float.NaN;
+        lats[i] = Float.NaN;
+        enabled[i] = true;
+        DataDisplayLink[] links = renderers[i].getLinks();
+        if (links == null || links.length == 0) continue;
+        Data data = links[0].getData();
+        if (data == null || !(data instanceof Tuple)) continue;
+        Real[] reals = ((Tuple) data).getRealComponents();
+        if (reals == null || reals.length == 0) continue;
+        for (int j=0; j<reals.length; j++) {
+          if (RealType.Latitude.equals(reals[j].getType())) {
+            lats[i] = (float) reals[j].getValue();
+          }
+          else if (RealType.Longitude.equals(reals[j].getType())) {
+            lons[i] = (float) reals[j].getValue();
+          }
+        }
+        if (lats[i] != lats[i] || lons[i] != lons[i]) {
+          lons[i] = Float.NaN; 
+          lats[i] = Float.NaN;
+        }
+      }
+      DisplayImpl display = renderers[0].getDisplay();
+      if (display == null) {
+        nrenderers = -1;
+        return;
+      }
+
+      ScalarMap latmap = null;
+      ScalarMap lonmap = null;
+      Vector mapVector = display.getMapVector();
+      Enumeration maps = mapVector.elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        ScalarType real = map.getScalar();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType rtuple = dreal.getTuple();
+        if (rtuple != null) {
+          if (rtuple.equals(Display.DisplaySpatialCartesianTuple) ||
+              (rtuple.getCoordinateSystem() != null &&
+               rtuple.getCoordinateSystem().getReference().equals(
+               Display.DisplaySpatialCartesianTuple))) {
+            // dreal is spatial
+            if (RealType.Latitude.equals(real)) {
+              latmap = map;
+            }
+            else if (RealType.Longitude.equals(real)) {
+              lonmap = map;
+            }
+          }
+        }
+      } // end while (maps.hasMoreElements())
+      if (latmap == null || lonmap == null) {
+        nrenderers = -1;
+        return;
+      }
+      double[] latrange = latmap.getRange();
+      double[] lonrange = lonmap.getRange();
+      latmul = (float) (1.0 / (Math.abs(latrange[1] - latrange[0]) * distance));
+      lonmul = (float) (1.0 / (Math.abs(lonrange[1] - lonrange[0]) * distance));
+      if (latmul != latmul || lonmul != lonmul) {
+        nrenderers = -1;
+        return;
+      }
+    }
+    else { // renderers == null
+      nrenderers = -1;
+      return;
+    }
+  }
+
+  public void controlChanged(ControlEvent e)
+         throws VisADException, RemoteException {
+    ProjectionControl pcontrol = (ProjectionControl) e.getControl();
+    double[] matrix = pcontrol.getMatrix();
+    double[] rot = new double[3];
+    double[] scale = new double[1];
+    double[] trans = new double[3];
+    MouseBehaviorJ3D.unmake_matrix(rot, scale, trans, matrix);
+
+    if (pfirst) {
+      pfirst = false;
+      base_scale = scale[0];
+      last_cscale = 1.0f;
+    }
+    else {
+      if (nrenderers < 0) return;
+      float cscale = (float) (base_scale / scale[0]);
+      float ratio = cscale / last_cscale;
+      if (ratio < 0.95f || 1.05f < ratio) {
+// System.out.println(latmul + " " + lonmul + " " + cscale);
+        last_cscale = cscale;
+        for (int i=0; i<nrenderers; i++) {
+          boolean enable = true;
+          if (lats[i] == lats[i] && lons[i] == lons[i]) {
+            for (int j=0; j<i; j++) {
+              if (enabled[j] && lats[j] == lats[j] && lons[j] == lons[j]) {
+                float latd = latmul * (lats[j] - lats[i]) / cscale;
+                float lond = lonmul * (lons[j] - lons[i]) / cscale;
+                float distsq = (latd * latd + lond * lond);
+// System.out.println(i + " " + lats[i] + " " + lons[i] + " " +
+//                    j + " " + lats[j] + " " + lons[j] + " " +
+//                    latd + " " + lond + " " + distsq);
+                if (distsq < 1.0f) {
+                  enable = false;
+                  break;
+                }
+              }
+            }
+          }
+// System.out.println("enabled[" + i + "] = " + enable);
+          enabled[i] = enable;
+          renderers[i].toggle(enable);
+        }
+      }
+    }
+  }
+}
+
diff --git a/visad/bom/FlexibleTrackManipulation.java b/visad/bom/FlexibleTrackManipulation.java
new file mode 100644
index 0000000..0622709
--- /dev/null
+++ b/visad/bom/FlexibleTrackManipulation.java
@@ -0,0 +1,1047 @@
+//
+// FlexibleTrackManipulation.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.AnimationControl;
+import visad.CellImpl;
+import visad.ConstantMap;
+import visad.ControlEvent;
+import visad.ControlListener;
+import visad.Data;
+import visad.DataReferenceImpl;
+import visad.DataRenderer;
+import visad.DateTime;
+import visad.Display;
+import visad.DisplayException;
+import visad.DisplayRealType;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.Linear1DSet;
+import visad.ProjectionControl;
+import visad.Real;
+import visad.RealTuple;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.Set;
+import visad.ShapeControl;
+import visad.Tuple;
+import visad.TupleType;
+import visad.VisADException;
+import visad.VisADGeometryArray;
+import visad.VisADLineArray;
+import visad.VisADRay;
+import visad.VisADTriangleArray;
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.MouseBehaviorJ3D;
+import visad.java3d.TwoDDisplayRendererJ3D;
+import visad.util.AnimationWidget;
+
+/**
+   FlexibleTrackManipulation is the VisAD class for
+   manipulation of flexible storm tracks (not straight lines)
+*/
+public class FlexibleTrackManipulation extends Object {
+
+  private Object data_lock = new Object();
+
+  private int ntimes = 0;
+  private Tuple[] tuples;
+  private boolean[] tuple_change;
+  private int which_time = -1;
+
+  private Set time_set = null;
+
+  private float[] lats;
+  private float[] lons;
+  private float[] shapes;
+  private float[] old_lats;
+  private float[] old_lons;
+
+  private DataReferenceImpl track_ref;
+  private DataReferenceImpl[] track_refs;
+  private DirectManipulationRendererJ3D[] direct_manipulation_renderers;
+// abcd 23 Aug 2001
+  private ConstantMap[][] constant_maps;
+  private TrackMonitor[] track_monitors;
+
+  private AnimationControl acontrol = null;
+  private ShapeControl shape_control1 = null;
+  private ShapeControl shape_control2 = null;
+  private ProjectionControl pcontrol = null;
+
+  private DisplayImplJ3D display;
+  private FieldImpl storm_track;
+  private FunctionType storm_track_type = null;
+
+  private DataMonitor data_monitor = null;
+
+  private int shape_index;
+  private int lat_index;
+  private int lon_index;
+  private float[] shapeColour;	// In RGB order
+
+  private int last_time = -1;
+
+  /**
+   * constructor
+   * Uses default size of 0.1 and default cyclone symbol geometry
+   */
+  public FlexibleTrackManipulation(DataReferenceImpl tr, DisplayImplJ3D d,
+                                   ScalarMap shape_map1, ScalarMap shape_map2,
+                                   boolean need_monitor)
+         throws VisADException, RemoteException
+  {
+    final float size = 0.1f;
+
+    // construct symbols
+    int nv = 16;
+    VisADGeometryArray[][] ga = makeStormShapes(nv, size);
+    shapeColour = new float[] {1.0f, 1.0f, 1.0f};
+
+    init(tr, d, shape_map1, shape_map2, need_monitor, size, ga);
+  }
+
+
+  /**
+   * Constructor - Use default cyclone shape geometry
+   */
+  public FlexibleTrackManipulation(DataReferenceImpl tr, DisplayImplJ3D d,
+                                   ScalarMap shape_map1, ScalarMap shape_map2,
+                                   boolean need_monitor, float size)
+         throws VisADException, RemoteException
+  {
+    // construct symbols
+    int nv = 16;
+    VisADGeometryArray[][] ga = makeStormShapes(nv, size);
+    shapeColour = new float[] {1.0f, 1.0f, 1.0f};
+
+    init(tr, d, shape_map1, shape_map2, need_monitor, size, ga);
+  }
+
+
+  /**
+   * Construct the FTM stuff
+   *
+   * @param tr A DataReferenceImpl, The visad data for the cyclone track
+   *	tr.getData() should have MathType: *    (Time -> tuple))
+   *	where tuple is flat [e.g., (Latitude, Longitude, shape_index)]
+   *	and must include RealTypes Latitude and Longitude plus
+   *	a RealType mapped to Shape in the DisplayImpl d;
+   *	Time may or may not be mapped to Animation
+   *
+   * @param d A DisplayImplJ3D, - The Display to add the FTM to
+   *
+   * @param shape_map1 First ScalarMap of RealTypes
+   *	in tr.getData()
+   *
+   * @param shape_map2 Second ScalarMap of RealTypes
+   *	in tr.getData()
+   *
+   * @param need_monitor - Need to monitor tr to maintain external
+   *	changes to it.
+   *
+   * @param size - storm symbol size
+   *
+   * @param ga A VisADGeometryArray[][] - two arrays of geometry objects
+   *	which represent the cyclone symbols in the order:
+   *	none, low, depresion1, depresion2, s-cyclone1, s-cyclone2,
+   *	n-cyclone1, n-cyclone2.
+   *	There are two arrays so you can combine shapes (ie: circle & lines)
+   *    The geometry can be built with makeStormShapes() or an application
+   *	defined method.
+   *
+   * @param shapeColour - colour of symbols
+   *
+   * TODO: Have a setCycloneGeometry(VisADGeometryArray[][] ga) method
+   */
+  public FlexibleTrackManipulation(DataReferenceImpl tr, DisplayImplJ3D d,
+                                   ScalarMap shape_map1, ScalarMap shape_map2,
+                                   boolean need_monitor, float size,
+                                   VisADGeometryArray[][] ga,
+                                   float shapeColour[])
+         throws VisADException, RemoteException
+  {
+	this.shapeColour = shapeColour;
+	init(tr, d, shape_map1, shape_map2, need_monitor, size, ga);
+  }
+
+
+  /**
+   * Do the work to construct a FTM
+   */
+  private void init(DataReferenceImpl tr, DisplayImplJ3D d,
+                                   ScalarMap shape_map1, ScalarMap shape_map2,
+                                   boolean need_monitor, float size,
+                                   VisADGeometryArray[][] ga)
+         throws VisADException, RemoteException
+  {
+    track_ref = tr;
+    storm_track = (FlatField) track_ref.getData();
+    display = d;
+
+    pcontrol = display.getProjectionControl();
+    ProjectionControlListener pcl = new ProjectionControlListener();
+    pcontrol.addControlListener(pcl);
+
+    acontrol = (AnimationControl) display.getControl(AnimationControl.class);
+    // use a ControlListener on Display.Animation to fake
+    // animation of the track
+    if (acontrol != null) {
+      AnimationControlListener acl = new AnimationControlListener();
+      acontrol.addControlListener(acl);
+    }
+
+    storm_track_type = (FunctionType) storm_track.getType();
+    TupleType storm_type = null;
+
+    try {
+      RealType time =
+        (RealType) storm_track_type.getDomain().getComponent(0);
+      if (!RealType.Time.equals(time)) {
+        throw new DisplayException("storm_track bad MathType: " +
+                     time + " must be RealType.Time");
+      }
+      storm_type = (TupleType) storm_track_type.getRange();
+      if (!storm_type.getFlat()) {
+        throw new DisplayException("storm_track bad MathType: " +
+                      storm_type + " must be flat");
+      }
+    }
+    catch (ClassCastException e) {
+      throw new DisplayException("storm_track bad MathType: " +
+                     storm_track_type);
+    }
+
+    shape_index = -1;
+    lat_index = -1;
+    lon_index = -1;
+    Vector scalar_map_vector = display.getMapVector();
+    int tuple_dim = storm_type.getDimension();
+    RealType[] real_types = storm_type.getRealComponents();
+    for (int i=0; i<tuple_dim; i++) {
+      RealType real = real_types[i];
+      if (RealType.Latitude.equals(real)) {
+        lat_index = i;
+      }
+      else if (RealType.Longitude.equals(real)) {
+        lon_index = i;
+      }
+      else {
+        Enumeration en = scalar_map_vector.elements();
+        while (en.hasMoreElements()) {
+          ScalarMap map = (ScalarMap) en.nextElement();
+          if (real.equals(map.getScalar())) {
+            DisplayRealType dreal = map.getDisplayScalar();
+            if (Display.Shape.equals(dreal)) {
+              shape_index = i;
+            }
+          }
+        }
+      }
+    } // for (int i=0; i<n; i++) {
+
+    if (lat_index < 0 || lon_index < 0 || shape_index < 0) {
+      throw new DisplayException("storm track data must include Latitude " +
+               "and Longitude and a RealType mapped to Shape " +
+               lat_index + " " + lon_index + " " + shape_index);
+    }
+
+    setupData(storm_track, true, true);
+
+    // Store geometry array in shapes
+    shape_control1 = (ShapeControl) shape_map1.getControl();
+    shape_control1.setShapeSet(new Integer1DSet(8));
+    shape_control1.setShapes(ga[0]);
+
+    shape_control2 = (ShapeControl) shape_map2.getControl();
+    shape_control2.setShapeSet(new Integer1DSet(8));
+    shape_control2.setShapes(ga[1]);
+
+    which_time = -1;
+
+    if (need_monitor) {
+      data_monitor = new DataMonitor();
+      data_monitor.addReference(track_ref);
+    }
+
+    if (acontrol != null) {
+      acontrol.setCurrent(0);
+      display.addReference(track_ref);
+    }
+  }
+
+
+  /**
+   * Class to keep cyclone data in sync with external changes to it
+   */
+  class DataMonitor extends CellImpl {
+    public void doAction() throws VisADException, RemoteException {
+      synchronized (data_lock) {
+        FieldImpl st = (FieldImpl) track_ref.getData();
+        boolean length_change = false;
+        boolean value_change = false;
+        if (!storm_track_type.equals(st.getType())) {
+          throw new VisADException("storm_track MathType may not change");
+        }
+        if (ntimes != st.getLength()) length_change = true;
+        if (st.getLength() >= ntimes) {
+          for (int j=0; j<ntimes; j++) {
+            Real[] reals = ((Tuple) st.getSample(j)).getRealComponents();
+            if (!visad.util.Util.isApproximatelyEqual(lats[j],
+                           (float) reals[lat_index].getValue()) ||
+                !visad.util.Util.isApproximatelyEqual(lons[j],
+                           (float) reals[lon_index].getValue()) ||
+                !visad.util.Util.isApproximatelyEqual(shapes[j],
+                           (float) reals[shape_index].getValue())) {
+              value_change = true;
+              break;
+            }
+          }
+        }
+        else {
+          value_change = true;
+        }
+        storm_track = st;
+        if (length_change || value_change) {
+          setupData(storm_track, length_change, value_change);
+        }
+        else {
+          setupTuples(storm_track);
+        }
+      } // end synchronized (data_lock)
+    }
+  }
+
+
+  private void setupData(FieldImpl storm_track, boolean length_change,
+                         boolean value_change)
+          throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      if (storm_track_type == null) {
+        storm_track_type = (FunctionType) storm_track.getType();
+      }
+      else {
+        if (!storm_track_type.equals(storm_track.getType())) {
+          throw new DisplayException("storm track MathType changed");
+        }
+      }
+
+      boolean special = length_change && !value_change;
+  
+      if (track_refs != null && !special) {
+        for (int i=0; i<track_refs.length; i++) {
+          display.removeReference(track_refs[i]);
+          track_monitors[i].removeReference(track_refs[i]);
+          track_monitors[i].stop();
+        }
+      }
+  
+      int old_ntimes = ntimes;
+
+      try {
+        ntimes = storm_track.getLength();
+        tuples = new Tuple[ntimes];
+        tuple_change = new boolean[ntimes];
+        lats = new float[ntimes];
+        lons = new float[ntimes];
+        old_lats = new float[ntimes];
+        old_lons = new float[ntimes];
+        shapes = new float[ntimes];
+        time_set = storm_track.getDomainSet();
+        for (int j=0; j<ntimes; j++) {
+          tuples[j] = (Tuple) storm_track.getSample(j);
+          tuple_change[j] = false;
+          Real[] reals = tuples[j].getRealComponents();
+          lats[j] = (float) reals[lat_index].getValue();
+          lons[j] = (float) reals[lon_index].getValue();
+          old_lats[j] = lats[j];
+          old_lons[j] = lons[j];
+          shapes[j] = (float) reals[shape_index].getValue();
+        }
+      }
+      catch (ClassCastException e) {
+        throw new DisplayException("storm track bad MathType: " +
+                       storm_track_type);
+      }
+  
+      if (acontrol == null) {
+
+        DataReferenceImpl[] old_track_refs = track_refs;
+        DirectManipulationRendererJ3D[] old_direct_manipulation_renderers =
+          direct_manipulation_renderers;
+        TrackMonitor[] old_track_monitors = track_monitors;
+        ConstantMap[][] old_constant_maps = constant_maps;
+
+        track_refs = new DataReferenceImpl[ntimes];
+        direct_manipulation_renderers = new DirectManipulationRendererJ3D[ntimes];
+        track_monitors = new TrackMonitor[ntimes];
+        constant_maps = new ConstantMap[ntimes][];
+        for (int i=0; i<ntimes; i++) {
+          if (!special || i >= old_ntimes) {
+            track_refs[i] = new DataReferenceImpl("station_ref" + i);
+            track_refs[i].setData(tuples[i]);
+            direct_manipulation_renderers[i] =
+              new FTMDirectManipulationRendererJ3D(this);
+            // abcd 23 August 2001
+            ConstantMap[] foo = {
+              new ConstantMap(shapeColour[0], Display.Red),
+              new ConstantMap(shapeColour[1], Display.Green),
+              new ConstantMap(shapeColour[2], Display.Blue)
+            };
+            constant_maps[i] = foo;
+  
+            display.addReferences(direct_manipulation_renderers[i],
+  		track_refs[i], constant_maps[i]);
+            track_monitors[i] = new TrackMonitor(track_refs[i], i);
+            track_monitors[i].addReference(track_refs[i]);
+          }
+          else { // special && i < old_ntimes
+            track_refs[i] = old_track_refs[i];
+            direct_manipulation_renderers[i] = old_direct_manipulation_renderers[i];
+            constant_maps[i] = old_constant_maps[i];
+            track_monitors[i] = old_track_monitors[i];
+          }
+        }
+      }
+      else {
+        if (!special) {
+          track_refs = new DataReferenceImpl[1];
+          direct_manipulation_renderers = new DirectManipulationRendererJ3D[1];
+          track_monitors = new TrackMonitor[1];
+          constant_maps = new ConstantMap[1][];
+          track_refs[0] = new DataReferenceImpl("station_ref");
+          track_refs[0].setData(tuples[0]);
+          direct_manipulation_renderers[0] =
+            new FTMDirectManipulationRendererJ3D(this);
+          // abcd 23 August 2001
+          constant_maps[0] = new ConstantMap[] {
+            new ConstantMap(shapeColour[0], Display.Red),
+            new ConstantMap(shapeColour[1], Display.Green),
+            new ConstantMap(shapeColour[2], Display.Blue)
+          };
+          display.addReferences(direct_manipulation_renderers[0],
+  		track_refs[0], constant_maps[0]);
+          track_monitors[0] = new TrackMonitor(track_refs[0], 0);
+          track_monitors[0].addReference(track_refs[0]);
+        }
+      }
+    } // end synchronized (data_lock)
+  }
+
+  private void setupTuples(FieldImpl storm_track)
+          throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      for (int j=0; j<ntimes; j++) {
+        tuples[j] = (Tuple) storm_track.getSample(j);
+      }
+    }
+  }
+
+
+  /**
+   * Create the geometry array which contains the shapes for the
+   * cyclone symbols.
+   *
+   * @param nv - The number of vertices?
+   *
+   * @param size - The size of all the symbols, apparently relative
+   *	to the size of the window
+   */
+  
+  public static VisADGeometryArray[][] makeStormShapes(int nv, float size)
+         throws VisADException {
+    VisADLineArray circle = new VisADLineArray();
+    circle.vertexCount = 2 * nv;
+    float[] coordinates = new float[3 * circle.vertexCount];
+    int m = 0;
+    for (int i=0; i<nv; i++) {
+      double b = 2.0 * Math.PI * i / nv;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(b));
+      coordinates[m++] = 0.5f * size * ((float) Math.sin(b));
+      coordinates[m++] = 0.0f;
+      double c = 2.0 * Math.PI * (i + 1) / nv;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(c));
+      coordinates[m++] = 0.5f * size * ((float) Math.sin(c));
+      coordinates[m++] = 0.0f;
+    }
+    circle.coordinates = coordinates;
+
+    VisADTriangleArray filled_circle = new VisADTriangleArray();
+    filled_circle.vertexCount = 3 * nv;
+    coordinates = new float[3 * filled_circle.vertexCount];
+    m = 0;
+    for (int i=0; i<nv; i++) {
+      coordinates[m++] = 0.0f;
+      coordinates[m++] = 0.0f;
+      coordinates[m++] = 0.0f;
+      double b = 2.0 * Math.PI * i / nv;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(b));
+      coordinates[m++] = 0.5f * size * ((float) Math.sin(b));
+      coordinates[m++] = 0.0f;
+      double c = 2.0 * Math.PI * (i + 1) / nv;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(c));
+      coordinates[m++] = 0.5f * size * ((float) Math.sin(c));
+      coordinates[m++] = 0.0f;
+    }
+    filled_circle.coordinates = coordinates;
+    float[] normals = new float[3 * filled_circle.vertexCount];
+    m = 0;
+    for (int i=0; i<3*nv; i++) {
+      normals[m++] = 0.0f;
+      normals[m++] = 0.0f;
+      normals[m++] = 1.0f;
+    }
+    filled_circle.normals = normals;
+
+    // L symbol for Tropical Low
+    // 30/07/2001 abcd - halved the size
+    VisADLineArray ell = new VisADLineArray();
+    ell.vertexCount = 2 * 4;
+    ell.coordinates = new float[] {
+      -0.25f * size, 0.5f * size, 0.0f,   0.0f, 0.5f * size, 0.0f,
+      -0.12f * size, 0.5f * size, 0.0f,   -0.12f * size, -0.5f * size, 0.0f,
+      -0.12f * size, -0.5f * size, 0.0f,  0.5f * size, -0.5f * size, 0.0f,
+      0.5f * size, -0.5f * size, 0.0f,    0.5f * size, -0.37f * size, 0.0f
+    };
+
+    VisADLineArray south = new VisADLineArray();
+    south.vertexCount = 2 * nv;
+    coordinates = new float[3 * south.vertexCount];
+    m = 0;
+    for (int i=0; i<nv/2; i++) {
+      double b = Math.PI * i / nv;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(b));
+      coordinates[m++] = size * ((float) Math.sin(b));
+      coordinates[m++] = 0.0f;
+      double c = Math.PI * (i + 1) / nv;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(c));
+      coordinates[m++] = size * ((float) Math.sin(c));
+      coordinates[m++] = 0.0f;
+      coordinates[m++] = -0.5f * size * ((float) Math.cos(b));
+      coordinates[m++] = -size * ((float) Math.sin(b));
+      coordinates[m++] = 0.0f;
+      coordinates[m++] = -0.5f * size * ((float) Math.cos(c));
+      coordinates[m++] = -size * ((float) Math.sin(c));
+      coordinates[m++] = 0.0f;
+    }
+    south.coordinates = coordinates;
+
+    VisADLineArray north = new VisADLineArray();
+    north.vertexCount = 2 * nv;
+    coordinates = new float[3 * north.vertexCount];
+    m = 0;
+    for (int i=0; i<nv/2; i++) {
+      double b = Math.PI * i / nv;
+      coordinates[m++] = -0.5f * size * ((float) Math.cos(b));
+      coordinates[m++] = size * ((float) Math.sin(b));
+      coordinates[m++] = 0.0f;
+      double c = Math.PI * (i + 1) / nv;
+      coordinates[m++] = -0.5f * size * ((float) Math.cos(c));
+      coordinates[m++] = size * ((float) Math.sin(c));
+      coordinates[m++] = 0.0f;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(b));
+      coordinates[m++] = -size * ((float) Math.sin(b));
+      coordinates[m++] = 0.0f;
+      coordinates[m++] = 0.5f * size * ((float) Math.cos(c));
+      coordinates[m++] = -size * ((float) Math.sin(c));
+      coordinates[m++] = 0.0f;
+    }
+    north.coordinates = coordinates;
+
+    VisADLineArray south_circle =
+      VisADLineArray.merge(new VisADLineArray[] {circle, south});
+    VisADLineArray north_circle =
+      VisADLineArray.merge(new VisADLineArray[] {circle, north});
+
+    VisADGeometryArray[][] ga =
+      {{null, ell, circle, null, south_circle, south, north_circle, north},
+       {null, null, null, filled_circle, null, filled_circle, null, filled_circle}};
+    return ga;
+  }
+
+  public void endManipulation()
+         throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      for (int i=0; i<track_refs.length; i++) {
+        display.removeReference(track_refs[i]);
+      }
+      display.addReference(track_ref);
+    } // end synchronized (data_lock)
+  }
+
+  private boolean pfirst = true;
+
+  class ProjectionControlListener implements ControlListener {
+    private double base_scale = 0.65;
+    private float last_cscale = 1.0f;
+
+    public void controlChanged(ControlEvent e)
+           throws VisADException, RemoteException {
+      double[] matrix = pcontrol.getMatrix();
+      double[] rot = new double[3];
+      double[] scale = new double[1];
+      double[] trans = new double[3];
+      MouseBehaviorJ3D.unmake_matrix(rot, scale, trans, matrix);
+
+/* WLH 10 Sept 2001
+      if (pfirst) {
+        pfirst = false;
+        base_scale = scale[0];
+// System.out.println("base_scale = " + base_scale);
+        last_cscale = 1.0f;
+      }
+      else {
+*/
+        float cscale = (float) (base_scale / scale[0]);
+        float ratio = cscale / last_cscale;
+        if (ratio < 0.95f || 1.05f < ratio) {
+          last_cscale = cscale;
+          if (shape_control1 != null) shape_control1.setScale(cscale);
+          if (shape_control2 != null) shape_control2.setScale(cscale);
+        }
+/* WLH 10 Sept 2001
+      }
+*/
+    }
+  }
+
+  private boolean afirst = true;
+
+  class AnimationControlListener implements ControlListener {
+    public void controlChanged(ControlEvent e)
+           throws VisADException, RemoteException {
+      synchronized (data_lock) {
+        which_time = -1;
+        if (direct_manipulation_renderers == null) return;
+        if (direct_manipulation_renderers[0] == null) return;
+        direct_manipulation_renderers[0].stop_direct();
+    
+        Set ts = acontrol.getSet();
+        if (ts == null) return;
+        if (!time_set.equals(ts)) {
+          throw new CollectiveBarbException("time Set changed");
+        }
+    
+        int current = acontrol.getCurrent();
+        if (current < 0) return;
+        which_time = current;
+
+        track_refs[0].setData(tuples[current]);
+    
+        if (afirst) {
+          afirst = false;
+          if (acontrol != null) display.removeReference(track_ref);
+        }
+      } // end synchronized (data_lock)
+    }
+  }
+
+  class TrackMonitor extends CellImpl {
+    DataReferenceImpl ref;
+    int this_time;
+  
+    public TrackMonitor(DataReferenceImpl r, int t) {
+      ref = r;
+      this_time = t;
+    }
+  
+    private final static float EPS = 0.01f;
+
+    public void doAction() throws VisADException, RemoteException {
+      synchronized (data_lock) {
+        int time_index = this_time;
+        if (acontrol != null) time_index = which_time;
+        if (time_index < 0) return;
+  
+        Tuple storm = (Tuple) ref.getData();
+        Real[] reals = storm.getRealComponents();
+        float new_lat = (float) reals[lat_index].getValue();
+        float new_lon = (float) reals[lon_index].getValue();
+        // filter out barb changes due to other doAction calls
+        if (visad.util.Util.isApproximatelyEqual(new_lat,
+                   lats[time_index], EPS) &&
+            visad.util.Util.isApproximatelyEqual(new_lon,
+                   lons[time_index], EPS)) return;
+  
+        if (afirst) {
+          afirst = false;
+          display.removeReference(track_ref);
+        }
+  
+        if (last_time != time_index) {
+          last_time = time_index;
+          for (int j=0; j<ntimes; j++) {
+            old_lats[j] = lats[j];
+            old_lons[j] = lons[j];
+          }
+        }
+  
+        float diff_lat = new_lat - old_lats[time_index];
+        float diff_lon = new_lon - old_lons[time_index];
+  
+        int mouseModifiers =
+          direct_manipulation_renderers[this_time].getLastMouseModifiers();
+        int mctrl = mouseModifiers & InputEvent.CTRL_MASK;
+        int high_time = (mctrl != 0) ? ntimes : time_index + 1;
+  
+        for (int j=time_index; j<high_time; j++) {
+  
+          double lat = old_lats[j] + diff_lat;
+          double lon = old_lons[j] + diff_lon;
+          int old_shape = (int) (shapes[j] + 0.01);
+          double shape = old_shape;
+          if (4 <= old_shape && old_shape < 6) {
+            if (lat >= 0.0) shape = old_shape + 2;
+          }
+          else if (6 <= old_shape && old_shape < 8) {
+            if (lat < 0.0) shape = old_shape - 2;
+          }
+  
+          Tuple old_storm = tuples[j];
+          if (old_storm instanceof RealTuple) {
+            reals = old_storm.getRealComponents();
+            reals[lat_index] = reals[lat_index].cloneButValue(lat);
+            reals[lon_index] = reals[lon_index].cloneButValue(lon);
+            reals[shape_index] = reals[shape_index].cloneButValue(shape);
+            storm = new RealTuple((RealTupleType) old_storm.getType(), reals,
+                             ((RealTuple) old_storm).getCoordinateSystem());
+          }
+          else { // old_storm instanceof Tuple
+            int n = old_storm.getDimension();
+            int k = 0;
+            Data[] components = new Data[n];
+            for (int c=0; c<n; c++) {
+              components[c] = old_storm.getComponent(c);
+              if (components[c] instanceof Real) {
+                if (k == lat_index) {
+                  components[c] =
+                    ((Real) components[c]).cloneButValue(lat);
+                }
+                if (k == lon_index) {
+                  components[c] =
+                    ((Real) components[c]).cloneButValue(lon);
+                }
+                if (k == shape_index) {
+                  components[c] =
+                    ((Real) components[c]).cloneButValue(shape);
+                }
+                k++;
+              }
+              else { // (components[c] instanceof RealTuple)
+                int m = ((RealTuple) components[c]).getDimension();
+                if ((k <= lat_index && lat_index < k+m) ||
+                    (k <= lon_index && lon_index < k+m) ||
+                    (k <= shape_index && shape_index < k+m)) {
+                  reals = ((RealTuple) components[c]).getRealComponents();
+                  if (k <= lat_index && lat_index < k+m) {
+                    reals[lat_index - k] =
+                      reals[lat_index - k].cloneButValue(lat);
+                  }
+                  if (k <= lon_index && lon_index < k+m) {
+                    reals[lon_index - k] =
+                      reals[lon_index - k].cloneButValue(lon);
+                  }
+                  if (k <= shape_index && shape_index < k+m) {
+                    reals[shape_index - k] =
+                      reals[shape_index - k].cloneButValue(shape);
+                  }
+                  components[c] =
+                    new RealTuple((RealTupleType) components[c].getType(),
+                                  reals,
+                         ((RealTuple) components[c]).getCoordinateSystem());
+                }
+                k += m;
+              } // end if (components[c] instanceof RealTuple)
+            } // end for (int c=0; c<n; c++)
+            storm = new Tuple((TupleType) old_storm.getType(), components,
+                             false);
+          } // end if (old_storm instanceof Tuple)
+  
+          lats[j] = (float) lat;
+          lons[j] = (float) lon;
+          shapes[j] = (float) shape;
+          tuples[j] = storm;
+
+// release
+          // storm_track.setSample(j, tuples[j]);
+          if (direct_on) {
+            tuple_change[j] = true;
+          }
+          else {
+            storm_track.setSample(j, tuples[j]);
+          }
+
+          if (acontrol == null) {
+            track_refs[j].setData(tuples[j]);
+          }
+        } // end for (int j=time_index+1; j<ntimes; j++)
+      } // end synchronized (data_lock)
+    }
+  }
+
+  /**
+   * Get access to the renderers.  Intended for the app to be
+   * able to toggle() this data display.
+   *
+   * abcd 27 April 2001
+   */
+  public DataRenderer[] getManipulationRenderers() {
+    return direct_manipulation_renderers;
+  }
+
+  private boolean direct_on = false;
+
+  public void release() {
+    synchronized (data_lock) {
+      try {
+        for (int i=0; i<ntimes; i++) {
+          if (tuple_change[i]) {
+            tuple_change[i] = false;
+            storm_track.setSample(i, tuples[i]);
+          }
+        }
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+      direct_on = false;
+    }
+  }
+
+  public void start() {
+    synchronized (data_lock) {
+      direct_on = true;
+    }
+  }
+
+  class FTMDirectManipulationRendererJ3D extends DirectManipulationRendererJ3D {
+    FlexibleTrackManipulation ftm;
+
+    FTMDirectManipulationRendererJ3D(FlexibleTrackManipulation f) {
+      super();
+      ftm = f;
+    }
+
+    /** mouse button released, ending direct manipulation */
+    public void release_direct() {
+      ftm.release();
+    }
+
+    public void drag_direct(VisADRay ray, boolean first,
+                                         int mouseModifiers) {
+      if (first) {
+        ftm.start();
+      }
+      super.drag_direct(ray, first, mouseModifiers);
+    }
+
+  }
+
+  private static final int NTIMES = 8;
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    // construct RealTypes for wind record components
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType shape = RealType.getRealType("shape");
+
+    RealType time = RealType.Time;
+    double start = new DateTime(1999, 122, 57060).getValue();
+    Set time_set = new Linear1DSet(time, start, start + 3000.0, NTIMES);
+
+    RealTupleType tuple_type = null;
+    tuple_type = new RealTupleType(lon, lat, shape);
+
+    FunctionType track_type = new FunctionType(time, tuple_type);
+
+    // construct Java3D display and mappings that govern
+    // how wind records are displayed
+    DisplayImplJ3D display =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap lonmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(lonmap);
+    lonmap.setRange(-10.0, 10.0);
+    ScalarMap latmap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(latmap);
+    latmap.setRange(-10.0, 10.0);
+
+    ScalarMap shape_map1 = new ScalarMap(shape, Display.Shape);
+    display.addMap(shape_map1);
+
+    ScalarMap shape_map2 = new ScalarMap(shape, Display.Shape);
+    display.addMap(shape_map2);
+
+    ScalarMap amap = null;
+    if (args.length > 0) {
+      amap = new ScalarMap(time, Display.Animation);
+      display.addMap(amap);
+      AnimationControl acontrol = (AnimationControl) amap.getControl();
+      acontrol.setStep(500);
+    }
+
+    FlatField ff = new FlatField(track_type, time_set);
+    double[][] values = new double[3][NTIMES];
+    for (int k=0; k<NTIMES; k++) {
+      // each track record is a Tuple (lon, lat, shape)
+      values[0][k] = 2.0 * k - 8.0;
+      values[1][k] = 2.0 * k - 8.0;
+      int s =  k % 8;
+      if (4 <= s && s < 6) {
+        if (values[1][k] >= 0.0) s += 2;
+      }
+      else if (6 <= s && s < 8) {
+        if (values[1][k] < 0.0) s -= 2;
+      }
+      values[2][k] = s;
+    }
+    ff.setSamples(values);
+    DataReferenceImpl track_ref = new DataReferenceImpl("track_ref");
+    track_ref.setData(ff);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test FlexibleTrackManipulation");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+    if (amap != null) panel.add(new AnimationWidget(amap));
+
+    FlexibleTrackManipulation ftm =
+      new FlexibleTrackManipulation(track_ref, display, shape_map1, shape_map2,
+                                    true, 0.05f);
+
+    JPanel button_panel = new JPanel();
+    button_panel.setLayout(new BoxLayout(button_panel, BoxLayout.X_AXIS));
+    button_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    button_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    EndManipFTM emf = new EndManipFTM(ftm, track_ref);
+    JButton end = new JButton("end manip");
+    end.addActionListener(emf);
+    end.setActionCommand("end");
+    button_panel.add(end);
+    JButton add = new JButton("add to track");
+    add.addActionListener(emf);
+    add.setActionCommand("add");
+    button_panel.add(add);
+    panel.add(button_panel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 700);
+    frame.setVisible(true);
+  }
+}
+
+class EndManipFTM implements ActionListener {
+  FlexibleTrackManipulation ftm;
+  DataReferenceImpl track_ref;
+
+  EndManipFTM(FlexibleTrackManipulation f, DataReferenceImpl tr) {
+    ftm = f;
+    track_ref = tr;
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("end")) {
+      try {
+        ftm.endManipulation();
+      }
+      catch (VisADException ex) {
+      }
+      catch (RemoteException ex) {
+      }
+    }
+    else if (cmd.equals("add")) {
+      try {
+        FlatField ff = (FlatField) track_ref.getData();
+        int ntimes = ff.getLength();
+        Linear1DSet time_set = (Linear1DSet) ff.getDomainSet();
+        Linear1DSet new_time_set =
+          new Linear1DSet(RealType.Time, time_set.getFirst(),
+                          time_set.getLast() + time_set.getStep(),
+                          ntimes + 1);
+        double[][] values = ff.getValues();
+        double[][] new_values = new double[3][ntimes + 1];
+        System.arraycopy(values[0], 0, new_values[0], 0, ntimes);
+        System.arraycopy(values[1], 0, new_values[1], 0, ntimes);
+        System.arraycopy(values[2], 0, new_values[2], 0, ntimes);
+        int k = ntimes;
+        new_values[0][k] = 2.0 * k - 8.0;
+        new_values[1][k] = 2.0 * k - 8.0;
+        int s =  k % 8;
+        if (4 <= s && s < 6) {
+          if (new_values[1][k] >= 0.0) s += 2;
+        }
+        else if (6 <= s && s < 8) {
+          if (new_values[1][k] < 0.0) s -= 2;
+        }
+        new_values[2][k] = s;
+        FlatField new_ff = new FlatField((FunctionType) ff.getType(), new_time_set);
+        new_ff.setSamples(new_values);
+        track_ref.setData(new_ff);
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+}
+
diff --git a/visad/bom/FrontDrawer.java b/visad/bom/FrontDrawer.java
new file mode 100644
index 0000000..84d20f9
--- /dev/null
+++ b/visad/bom/FrontDrawer.java
@@ -0,0 +1,1608 @@
+//
+// FrontDrawer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.util.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+/*
+find front points inside boundary
+highlight needs sharp boundary, which means different graphics
+arrays, but with matching shapes
+
+do shape variations as periodic functions for front and back
+profiles of shape; bend these along low-pass filter of user
+drawn curve and draw custom Shape; local changes as distinct
+Shapes
+*/
+
+/**
+   FrontDrawer is the VisAD class for manipulation of fronts
+*/
+public class FrontDrawer extends Object implements ControlListener {
+
+  private static boolean debug = true;
+
+  private Object data_lock = new Object();
+
+  private DataReferenceImpl front_ref;
+  private DefaultRendererJ3D front_renderer;
+  private DataReferenceImpl curve_ref = null;
+  private FrontManipulationRendererJ3D front_manipulation_renderer;
+
+  private ReleaseCell release_cell;
+  private DataReferenceImpl release_ref;
+
+  private ZoomCell zoom_cell;
+  private DataReferenceImpl zoom_ref;
+
+  private ProjectionControl pcontrol = null;
+  private ProjectionControlListener pcl = null;
+  private float zoom = 1.0f;
+
+  private AnimationControl acontrol = null;
+
+  private DisplayImplJ3D display;
+  private ScalarMap lat_map = null;
+  private ScalarMap lon_map = null;
+
+  private static Object type_lock = new Object();
+
+  private int ntimes = 0;
+  private int current_time_step = -1;
+
+  private UnionSet init_curve = null; // manifold dimension = 1
+  private static SetType curve_type = null; // Set(Latitude, Longitude)
+  private int lat_index = 0;
+  private int lon_index = 1;
+  private float[][][] curves = null;
+  private boolean[] flips = null;
+
+  private FieldImpl fronts = null;
+  // (RealType.Time -> (front_index ->
+  //       ((Latitude, Longitude) -> (front_red, front_green, front_blue))))
+  private static FunctionType fronts_type = null;
+  private FieldImpl front = null; //
+  // (front_index -> ((Latitude, Longitude) -> (front_red, front_green, front_blue)))
+  private static FunctionType front_type = null;
+  private static FunctionType front_inner = null;
+  private static RealType front_index = null;
+  private static RealType front_red = null;
+  private static RealType front_green = null;
+  private static RealType front_blue = null;
+
+  // shapes for first segment of front
+  private int nfshapes = -1;
+  private float[][][] first_shapes = null;
+  private int[][][] first_tris = null;
+  private float[] first_red = null;
+  private float[] first_green = null;
+  private float[] first_blue = null;
+
+  // shapes for repeating segments of front, after first
+  private int nrshapes = -1;
+  private float[][][] repeat_shapes = null;
+  private int[][][] repeat_tris = null;
+  private float[] repeat_red = null;
+  private float[] repeat_green = null;
+  private float[] repeat_blue = null;
+
+  // length of first segment in graphics coordinates
+  private float fsegment_length;
+  // length of each repeating segment in graphics coordinates
+  private float rsegment_length;
+
+  // number of intervals in curve for first segment
+  private int fprofile_length = -1;
+  // number of intervals in curve for each repeating segment
+  private int rprofile_length = -1;
+
+  // size of filter window for smoothing curve
+  private int filter_window = 1;
+
+  // copy of cs argument
+  float[][][] ccs = null;
+  // copy of fs argument
+  FieldImpl ffs = null;
+
+  public static final int COLD_FRONT = 0;
+  public static final int WARM_FRONT = 1;
+  public static final int OCCLUDED_FRONT = 2;
+  public static final int STATIONARY_FRONT = 3;
+  public static final int CONVERGENCE = 4;
+  public static final int FRONTOGENESIS = 5;
+  public static final int FRONTOLYSIS = 6;
+  public static final int UPPER_COLD_FRONT = 7;
+  public static final int UPPER_WARM_FRONT = 8;
+  public static final int TROUGH = 9;
+  public static final int RIDGE = 10;
+  public static final int MOISTURE = 11;
+  public static final int LOW_LEVEL_JET = 12;
+  public static final int UPPER_LEVEL_JET = 13;
+  public static final int DRY_LINE = 14;
+  public static final int TOTAL_TOTALS = 15;
+  public static final int LIFTED_INDEX = 16;
+  public static final int ISOTHERMS = 17;
+  public static final int THICKNESS_RIDGE = 18;
+  public static final int LOWER_THERMAL_TROUGH = 19;
+  public static final int UPPER_THERMAL_TROUGH = 20;
+  public static final int UNEVEN_LOW_LEVEL_JET = 21;
+
+  private static final float[] rsegmentarray = {
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.05f, // TROUGH = 9
+    0.1f, // RIDGE = 10
+    0.05f, // MOISTURE = 11
+    0.2f, // LOW_LEVEL_JET = 12
+    0.2f, // UPPER_LEVEL_JET = 13
+    0.1f, // DRY_LINE = 14
+    0.05f, // TOTAL_TOTALS = 15
+    0.1f, // LIFTED_INDEX = 16
+    0.15f, // ISOTHERMS = 17
+    0.1f, // THICKNESS_RIDGE = 18
+    0.05f, // LOWER_THERMAL_TROUGH = 19
+    0.1f, // UPPER_THERMAL_TROUGH = 20
+    0.1f // UNEVEN_LOW_LEVEL_JET = 21
+  };
+
+  private static final float[] fsegmentarray = {
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.2f,
+    0.05f, // TROUGH = 9
+    0.1f, // RIDGE = 10
+    0.05f, // MOISTURE = 11
+    0.2f, // LOW_LEVEL_JET = 12
+    0.2f, // UPPER_LEVEL_JET = 13
+    0.1f, // DRY_LINE = 14
+    0.05f, // TOTAL_TOTALS = 15
+    0.1f, // LIFTED_INDEX = 16
+    0.15f, // ISOTHERMS = 17
+    0.1f, // THICKNESS_RIDGE = 18
+    0.05f, // LOWER_THERMAL_TROUGH = 19
+    0.1f, // UPPER_THERMAL_TROUGH = 20
+    0.2f // UNEVEN_LOW_LEVEL_JET = 21
+  };
+
+  private static final float[][][][] rshapesarray = {
+
+    // COLD_FRONT =0
+    {{{0.0f, 0.025f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.025f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.04f, 0.01f}}},
+
+    // WARM_FRONT = 1
+    {{{0.0f, 0.035f, 0.07f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.07f, 0.0525f, 0.035f, 0.0175f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.03f, 0.037f, 0.03f, 0.01f}}},
+
+    // OCCLUDED_FRONT = 2
+    {{{0.0f, 0.025f, 0.05f, 0.07f, 0.105f, 0.14f, 0.17f, 0.2f,
+       0.2f, 0.17f, 0.14f, 0.1225f, 0.105f, 0.0875f, 0.07f, 0.05f, 0.025f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.03f, 0.037f, 0.03f, 0.01f, 0.01f, 0.04f, 0.01f}}},
+
+    // STATIONARY_FRONT = 3
+    {{{0.09f, 0.11f, 0.1275f, 0.145f, 0.1625f, 0.18f, 0.2f,
+       0.2f, 0.1775f, 0.155f, 0.1175f, 0.09f},
+      {0.0f, 0.0f, -0.02f, -0.027f, -0.02f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.01f}},
+     {{0.0f, 0.02f, 0.045f, 0.07f, 0.09f,
+       0.09f, 0.07f, 0.045f, 0.02f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.04f, 0.01f, 0.0f}}},
+
+    // CONVERGENCE = 4
+    {{{0.0f, 0.03f, 0.035f, 0.01f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.11f, 0.135f, 0.13f, 0.1f, 0.05f, 0.0f},
+      {0.01f, 0.04f, 0.035f, 0.01f, 0.01f, 0.01f, 0.01f, 0.01f,
+       0.0f, 0.0f, 0.0f, -0.025f, -0.03f, 0.0f, 0.0f, 0.0f}}},
+
+    // FRONTOGENESIS = 5
+    {{{0.0f, 0.035f, 0.07f, 0.1f, 0.15f,
+       0.15f, 0.1f, 0.0875f, 0.075f, 0.0625f, 0.05f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.025f, 0.035f, 0.025f, 0.01f, 0.01f}},
+     {{0.16f, 0.19f,
+       0.19f, 0.16f},
+      {-0.005f, -0.005f,
+       0.015f, 0.015f}}},
+
+    // FRONTOLYSIS = 6
+    {{{0.0f, 0.035f, 0.07f, 0.1f, 0.15f,
+       0.15f, 0.1f, 0.0875f, 0.075f, 0.0625f, 0.05f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.025f, 0.035f, 0.025f, 0.01f, 0.01f}},
+     {{0.16f, 0.17f, 0.17f, 0.18f, 0.18f, 0.19f,
+       0.19f, 0.18f, 0.18f, 0.17f, 0.17f, 0.16f},
+      {0.0f, 0.0f, -0.01f, -0.01f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.02f, 0.02f, 0.01f, 0.01f}}},
+
+    // UPPER_COLD_FRONT = 7
+    {{{0.0f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.01f}},
+     {{0.0f, 0.03f, 0.06f,
+       0.05f, 0.03f, 0.01f},
+      {0.01f, 0.04f, 0.01f,
+       0.01f, 0.03f, 0.01f}}},
+
+    // UPPER_WARM_FRONT = 8
+    {{{0.0f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.01f}},
+     {{0.0f, 0.015f, 0.03f, 0.045f, 0.06f,
+       0.05f, 0.04f, 0.03f, 0.02f, 0.01f},
+      {0.01f, 0.03f, 0.037f, 0.03f, 0.01f,
+       0.01f, 0.023f, 0.027f, 0.023f, 0.01f}}},
+
+    // TROUGH = 9
+    {{{0.0f, 0.035f,
+       0.035f, 0.0f},
+      {0.0f, 0.0f,
+       0.01f, 0.01f}}},
+
+    // RIDGE = 10
+    {{{0.0f, 0.05f, 0.1f,
+       0.1f, 0.05f, 0.0f},
+      {0.04f, -0.06f, 0.04f,
+       0.06f, -0.04f, 0.06f}}},
+
+    // MOISTURE = 11
+    {{{0.0f, 0.0f, 0.01f, 0.01f, 0.05f,
+       0.05f, 0.0f},
+      {0.01f, 0.05f, 0.05f, 0.01f, 0.01f,
+       0.0f, 0.0f}}},
+
+    // LOW_LEVEL_JET = 12
+    {{{0.0f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.0f},
+      {0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.01f}}},
+
+    // UPPER_LEVEL_JET = 13
+    {{{0.0f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.0f},
+      {-0.01f, -0.01f, -0.01f, -0.01f, -0.01f,
+       0.02f, 0.02f, 0.02f, 0.02f, 0.02f}}},
+
+    // DRY_LINE = 14
+    {{{0.0f, 0.05f,
+       0.05f, 0.0f},
+      {0.0f, 0.0f,
+       0.01f, 0.01f}},
+     {{0.06f, 0.09f,
+       0.09f, 0.06f},
+      {-0.005f, -0.005f,
+       0.015f, 0.015f}}},
+
+    // TOTAL_TOTALS = 15
+    {{{0.0f, 0.035f,
+       0.035f, 0.0f},
+      {0.0f, 0.0f,
+       0.01f, 0.01f}}},
+
+    // LIFTED_INDEX = 16
+    {{{0.0f, 0.05f,
+       0.05f, 0.0f},
+      {0.0f, 0.0f,
+       0.01f, 0.01f}},
+     {{0.06f, 0.09f,
+       0.09f, 0.06f},
+      {-0.005f, -0.005f,
+       0.015f, 0.015f}}},
+
+    // ISOTHERMS = 17
+    {{{0.0f, 0.0f, 0.04f, 0.08f,
+       0.08f, 0.04f, 0.0f, 0.0f,
+       0.02f, 0.02f, 0.06f,
+       0.06f, 0.02f, 0.02f},
+      {0.0f, -0.02f, -0.02f, -0.02f,
+       0.02f, 0.02f, 0.02f, 0.0f,
+       0.0f, 0.01f, 0.01f,
+       -0.01f, -0.01f, 0.0f}}},
+
+    // THICKNESS_RIDGE = 18
+    {{{0.0f, 0.05f, 0.1f,
+       0.1f, 0.05f, 0.0f},
+      {0.01f, -0.06f, 0.01f,
+       0.06f, -0.01f, 0.06f}}},
+
+    // LOWER_THERMAL_TROUGH = 19
+    {{{0.0f, 0.045f,
+       0.045f, 0.0f},
+      {-0.01f, -0.01f,
+       0.02f, 0.02f}}},
+
+    // UPPER_THERMAL_TROUGH = 20
+    {{{0.0f, 0.04f, 0.02f},
+      {0.0f, 0.0f, 0.04f}}},
+
+    // UNEVEN_LOW_LEVEL_JET = 21
+    {{{0.0f, 0.05f, 0.1f,
+       0.1f, 0.05f, 0.0f},
+      {0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f}}},
+
+  };
+
+  private static final float[][] rredarray = {
+    {0.0f},
+    {1.0f},
+    {1.0f},
+    {1.0f, 0.0f},
+    {1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {0.5f},
+    {0.5f},
+    {1.0f},
+    {0.5f},
+    {0.5f},
+    {0.5f, 0.5f}, // DRY_LINE = 14
+    {1.0f},
+    {1.0f, 1.0f}, // LIFTED_INDEX = 16
+    {1.0f},
+    {1.0f},
+    {1.0f},
+    {1.0f},
+    {0.5f}
+  };
+
+  private static final float[][] rgreenarray = {
+    {0.0f},
+    {0.0f},
+    {0.0f},
+    {0.0f, 0.0f},
+    {1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {0.3f},
+    {0.3f},
+    {1.0f},
+    {0.5f},
+    {0.5f},
+    {0.3f, 0.3f}, // DRY_LINE = 14
+    {1.0f},
+    {1.0f, 1.0f}, // LIFTED_INDEX = 16
+    {1.0f},
+    {1.0f},
+    {1.0f},
+    {1.0f},
+    {0.5f}
+  };
+
+  private static final float[][] rbluearray = {
+    {1.0f},
+    {0.0f},
+    {1.0f},
+    {0.0f, 1.0f},
+    {1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {1.0f, 1.0f},
+    {0.0f},
+    {0.0f},
+    {1.0f},
+    {1.0f},
+    {1.0f},
+    {0.0f, 0.0f}, // DRY_LINE = 14
+    {1.0f},
+    {1.0f, 1.0f}, // LIFTED_INDEX = 16
+    {1.0f},
+    {1.0f},
+    {1.0f},
+    {1.0f},
+    {1.0f}
+  };
+
+  private static final float[][][][] fshapesarray = {
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+
+    // LOW_LEVEL_JET = 12
+    {{{0.0f, 0.07f, 0.075f, 0.01f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.01f, 0.075f, 0.07f, 0.0f},
+      {0.0f, -0.07f, -0.065f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.075f, 0.08f, 0.01f}}},
+
+    // UPPER_LEVEL_JET = 13
+    {{{0.0f, 0.06f, 0.077f, 0.04f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.04f, 0.077f, 0.06f, 0.0f},
+      {-0.001f, -0.06f, -0.04f, -0.01f, -0.01f, -0.01f, -0.01f, -0.01f,
+       0.02f, 0.02f, 0.02f, 0.02f, 0.02f, 0.05f, 0.07f, 0.02f}}},
+
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+
+    // UNEVEN_LOW_LEVEL_JET = 21
+    {{{0.0f, 0.07f, 0.075f, 0.01f, 0.05f, 0.1f, 0.15f, 0.2f,
+       0.2f, 0.15f, 0.1f, 0.05f, 0.01f, 0.075f, 0.07f, 0.0f},
+      {0.0f, -0.07f, -0.065f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+       0.01f, 0.01f, 0.01f, 0.01f, 0.01f, 0.075f, 0.08f, 0.01f}}}
+
+  };
+
+  private static final float[][] fredarray = {
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    {0.5f},
+    {0.5f},
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    {0.5f}
+  };
+
+  private static final float[][] fgreenarray = {
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    {0.5f},
+    {0.5f},
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    {0.5f}
+  };
+       
+  private static final float[][] fbluearray = {
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    {1.0f},
+    {1.0f},
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    null,
+    {1.0f}
+  };
+
+  /** manipulable front with predefined pattern front_kind and
+      user specified color arrays */
+  public FrontDrawer(FieldImpl fs, float[][][] cs,
+                     DisplayImplJ3D d, int fw, int front_kind,
+                     float[] fred, float[] fgreen, float[] fblue,
+                     float[] rred, float[] rgreen, float[] rblue)
+         throws VisADException, RemoteException {
+    this(fs, cs, d, fw, fsegmentarray[front_kind], rsegmentarray[front_kind],
+         fshapesarray[front_kind], fred, fgreen, fblue,
+         rshapesarray[front_kind], rred, rgreen, rblue);
+  }
+
+  /** manipulable front with predefined pattern front_kind and
+      default color arrays */
+  public FrontDrawer(FieldImpl fs, float[][][] cs,
+                     DisplayImplJ3D d, int fw, int front_kind)
+         throws VisADException, RemoteException {
+    this(fs, cs, d, fw, fsegmentarray[front_kind], rsegmentarray[front_kind],
+         fshapesarray[front_kind], fredarray[front_kind],
+         fgreenarray[front_kind], fbluearray[front_kind],
+         rshapesarray[front_kind], rredarray[front_kind],
+         rgreenarray[front_kind], rbluearray[front_kind]);
+  }
+
+
+  /**
+     fs is null or has MathType
+       (RealType.Time -> (front_index ->
+           ((Latitude, Longitude) -> (front_red, front_green, front_blue))))
+     cs is null or contains a time array of curves for fs
+     fw is the filter window size for smoothing the curve
+     segment is length in graphics coordinates of first and repeating profiles
+     fshapes is dimensioned [nfshapes][2][points_per_shape]
+     fred, fgreen and fblue are dimensioned [nfshapes]
+     rshapes is dimensioned [nrshapes][2][points_per_shape]
+     rred, rgreen and rblue are dimensioned [nrshapes]
+     fshapes[*][0][*] and rshapes[*][0][*] generally in range 0.0f to segment
+  */
+  public FrontDrawer(FieldImpl fs, float[][][] cs,
+                     DisplayImplJ3D d,
+                     int fw, float segment,
+                     float[][][] fshapes,
+                     float[] fred, float[] fgreen, float[] fblue,
+                     float[][][] rshapes,
+                     float[] rred, float[] rgreen, float[] rblue)
+         throws VisADException, RemoteException {
+    this(fs, cs, d, fw, segment, segment, fshapes, fred, fgreen, fblue,
+         rshapes, rred, rgreen, rblue);
+  }
+
+
+  /**
+     fs is null or has MathType
+       (RealType.Time -> (front_index ->
+           ((Latitude, Longitude) -> (front_red, front_green, front_blue))))
+     cs is null or contains a time array of curves for fs
+     fw is the filter window size for smoothing the curve
+     fsegment is length in graphics coordinates of first profile
+     rsegment is length in graphics coordinates of repeating profile
+     fshapes is dimensioned [nfshapes][2][points_per_shape]
+     fred, fgreen and fblue are dimensioned [nfshapes]
+     rshapes is dimensioned [nrshapes][2][points_per_shape]
+     rred, rgreen and rblue are dimensioned [nrshapes]
+     fshapes[*][0][*] and rshapes[*][0][*] generally in range 0.0f to segment
+  */
+  public FrontDrawer(FieldImpl fs, float[][][] cs,
+                     DisplayImplJ3D d,
+                     int fw, float fsegment, float rsegment,
+                     float[][][] fshapes,
+                     float[] fred, float[] fgreen, float[] fblue,
+                     float[][][] rshapes,
+                     float[] rred, float[] rgreen, float[] rblue)
+         throws VisADException, RemoteException {
+    try {
+      initColormaps(d);
+    }
+    catch (VisADException e) {
+      // if (debug) System.out.println("caught " + e.toString());
+    }
+    ccs = cs;
+    ffs = fs;
+
+    curve_ref = new DataReferenceImpl("curve_ref");
+    Gridded2DSet set = null;
+    if (cs == null || cs[0] == null) {
+      set = new Gridded2DSet(curve_type, new float[][] {{0.0f}, {0.0f}}, 1);
+    }
+    else {
+      set = new Gridded2DSet(curve_type, cs[0], cs[0][0].length);
+    }
+    init_curve = new UnionSet(curve_type, new Gridded2DSet[] {set});
+
+    Data data = curve_ref.getData();
+    Gridded2DSet curve_set = null;
+    if (data != null && data instanceof UnionSet) {
+      SampledSet[] sets = ((UnionSet) data).getSets();
+      if (sets[0] instanceof Gridded2DSet) {
+        curve_set = (Gridded2DSet) sets[0];
+      }
+    }
+    if (curve_set == null) {
+      curve_ref.setData(init_curve);
+    }
+    else {
+      SetType st = (SetType) curve_set.getType();
+      if (!st.equals(curve_type)) {
+        SetType rft =
+          new SetType(new RealTupleType(RealType.Longitude, RealType.Latitude));
+        if (!st.equals(rft)) {
+          throw new SetException("cr data bad MathType");
+        }
+        lat_index = 1;
+        lon_index = 0;
+      }
+    }
+
+    display = d;
+    filter_window = fw;
+    fsegment_length = fsegment;
+    rsegment_length = rsegment;
+
+    if (rshapes == null) {
+      throw new VisADException("bad rshapes");
+    }
+    nrshapes = rshapes.length;
+    for (int i=0; i<nrshapes; i++) {
+      if (rshapes[i] == null || rshapes[i].length != 2 ||
+          rshapes[i][0] == null || rshapes[i][1] == null ||
+          rshapes[i][0].length != rshapes[i][1].length) {
+        throw new VisADException("bad rshapes[" + i + "]");
+      }
+    }
+    if (rred == null || rred.length != nrshapes ||
+        rgreen == null || rgreen.length != nrshapes || 
+        rblue == null || rblue.length != nrshapes) {
+      throw new VisADException("bad rcolors");
+    }
+    repeat_tris = new int[nrshapes][][];
+    for (int i=0; i<nrshapes; i++) {
+      repeat_tris[i] = DelaunayCustom.fill(rshapes[i]);
+    }
+    repeat_shapes = new float[nrshapes][2][];
+    int rlen = 0;
+    for (int i=0; i<nrshapes; i++) {
+      int n = rshapes[i][0].length;
+      rlen += n;
+      repeat_shapes[i][0] = new float[n];
+      repeat_shapes[i][1] = new float[n];
+      System.arraycopy(rshapes[i][0], 0, repeat_shapes[i][0], 0, n);
+      System.arraycopy(rshapes[i][1], 0, repeat_shapes[i][1], 0, n);
+    }
+    rprofile_length = rlen;
+    repeat_red = new float[nrshapes];
+    repeat_green = new float[nrshapes];
+    repeat_blue = new float[nrshapes];
+    System.arraycopy(rred, 0, repeat_red, 0, nrshapes);
+    System.arraycopy(rgreen, 0, repeat_green, 0, nrshapes);
+    System.arraycopy(rblue, 0, repeat_blue, 0, nrshapes);
+
+    if (fshapes == null) {
+      // if no different first shapes, just use repeat shapes
+      nfshapes = nrshapes;
+      first_tris = repeat_tris;
+      first_shapes = repeat_shapes;
+      first_red = repeat_red;
+      first_green = repeat_green;
+      first_blue = repeat_blue;
+    }
+    else {
+      nfshapes = fshapes.length;
+      for (int i=0; i<nfshapes; i++) {
+        if (fshapes[i] == null || fshapes[i].length != 2 ||
+            fshapes[i][0] == null || fshapes[i][1] == null ||
+            fshapes[i][0].length != fshapes[i][1].length) {
+          throw new VisADException("bad fshapes[" + i + "]");
+        }
+      }
+      if (fred == null || fred.length != nfshapes ||
+          fgreen == null || fgreen.length != nfshapes || 
+          fblue == null || fblue.length != nfshapes) {
+        throw new VisADException("bad fcolors");
+      }
+      first_tris = new int[nfshapes][][];
+      for (int i=0; i<nfshapes; i++) {
+        first_tris[i] = DelaunayCustom.fill(fshapes[i]);
+      }
+      first_shapes = new float[nfshapes][2][];
+      int flen = 0;
+      for (int i=0; i<nfshapes; i++) {
+        int n = fshapes[i][0].length;
+        flen += n;
+        first_shapes[i][0] = new float[n];
+        first_shapes[i][1] = new float[n];
+        System.arraycopy(fshapes[i][0], 0, first_shapes[i][0], 0, n);
+        System.arraycopy(fshapes[i][1], 0, first_shapes[i][1], 0, n);
+      }
+      fprofile_length = flen;
+      first_red = new float[nfshapes];
+      first_green = new float[nfshapes];
+      first_blue = new float[nfshapes];
+      System.arraycopy(fred, 0, first_red, 0, nfshapes);
+      System.arraycopy(fgreen, 0, first_green, 0, nfshapes);
+      System.arraycopy(fblue, 0, first_blue, 0, nfshapes);
+    }
+    if (rprofile_length < 5) rprofile_length = 5;
+    if (fprofile_length < 5) fprofile_length = 5;
+
+    pcontrol = display.getProjectionControl();
+    pcl = new ProjectionControlListener();
+    pcontrol.addControlListener(pcl);
+
+    acontrol = (AnimationControl) display.getControl(AnimationControl.class);
+    if (acontrol == null) {
+      throw new DisplayException("display must include " +
+                     "ScalarMap to Animation");
+    }
+    Vector tmap = display.getMapVector();
+    for (int i=0; i<tmap.size(); i++) {
+      ScalarMap map = (ScalarMap) tmap.elementAt(i);
+      Control c = map.getControl();
+      if (acontrol.equals(c)) {
+        if (!RealType.Time.equals(map.getScalar())) {
+          throw new DisplayException("must be Time mapped to " +
+                                     "Animation " + map.getScalar());
+        }
+      }
+    }
+
+    Set aset = acontrol.getSet();
+    if (aset != null) {
+      setupAnimationSet(aset);
+    }
+    else {
+      acontrol.addControlListener(this);
+    }
+
+    // find spatial maps for Latitude and Longitude
+    lat_map = null;
+    lon_map = null;
+    Vector scalar_map_vector = display.getMapVector();
+    Enumeration en = scalar_map_vector.elements();
+    while (en.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) en.nextElement();
+      DisplayRealType real = map.getDisplayScalar();
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple != null &&
+          (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+           (tuple.getCoordinateSystem() != null &&
+            tuple.getCoordinateSystem().getReference().equals(
+            Display.DisplaySpatialCartesianTuple)))) { // Spatial
+        if (RealType.Latitude.equals(map.getScalar())) {
+          lat_map = map;
+        }
+        else if (RealType.Longitude.equals(map.getScalar())) {
+          lon_map = map;
+        }
+      }
+    }
+    if (lat_map == null || lon_map == null) {
+      throw new DisplayException("Latitude and Longitude must be mapped");
+    }
+
+    int mmm = 0;
+    int mmv = 0;
+    front_manipulation_renderer =
+      new FrontManipulationRendererJ3D(this, mmm, mmv);
+    display.addReferences(front_manipulation_renderer, curve_ref);
+
+    front_ref = new DataReferenceImpl("front");
+    front_ref.setData(fronts);
+    front_renderer = new DefaultRendererJ3D();
+    front_renderer.suppressExceptions(true);
+    display.addReferences(front_renderer, front_ref);
+
+    release_ref = new DataReferenceImpl("release");
+    release_cell = new ReleaseCell();
+    release_cell.addReference(release_ref);
+
+    zoom_ref = new DataReferenceImpl("zoom");
+    zoom_cell = new ZoomCell();
+    zoom_cell.addReference(zoom_ref);
+
+    setScale();
+  }
+
+  private void setupAnimationSet(Set aset)
+    throws VisADException {
+    ntimes = aset.getLength();
+    current_time_step = acontrol.getCurrent();
+    if (ccs == null) {
+      curves = new float[ntimes][][];
+    }
+    else {
+      curves = ccs;
+      if (ccs.length != ntimes) {
+        throw new VisADException("cs bad number of times " +
+                                 ccs.length + " != " + ntimes);
+      }
+    }
+    flips = new boolean[ntimes];
+    if (ffs == null) {
+      fronts = new FieldImpl(fronts_type, aset);
+    }
+    else {
+      fronts = ffs;
+      if (!aset.equals(ffs.getDomainSet())) {
+        throw new VisADException("fs bad time Set " +
+                                 ffs.getDomainSet() + " != " + aset);
+      }
+    }
+  }
+
+  public void controlChanged(ControlEvent e) {
+    if (fronts == null) {
+      try {
+        Set aset = acontrol.getSet();
+        if (aset != null) {
+          setupAnimationSet(aset);
+        }
+      }
+      catch (VisADException ee) {
+      }
+    }
+  }
+
+  public DefaultRendererJ3D getFrontRenderer() {
+    return front_renderer;
+  }
+
+  public static void initColormaps(DisplayImplJ3D display)
+         throws VisADException, RemoteException {
+    setupTypes();
+    ScalarMap rmap = new ScalarMap(front_red, Display.Red);
+    rmap.setRange(0.0, 1.0);
+    display.addMap(rmap);
+    ScalarMap gmap = new ScalarMap(front_green, Display.Green);
+    gmap.setRange(0.0, 1.0);
+    display.addMap(gmap);
+    ScalarMap bmap = new ScalarMap(front_blue, Display.Blue);
+    bmap.setRange(0.0, 1.0);
+    display.addMap(bmap);
+  }
+
+  private static void setupTypes() throws VisADException {
+    synchronized (type_lock) {
+      if (curve_type == null) {
+        RealTupleType latlon =
+          new RealTupleType(RealType.Latitude, RealType.Longitude);
+        curve_type = new SetType(latlon);
+        // (front_index -> 
+        //    ((Latitude, Longitude) -> (front_red, front_green, front_blue)))
+        front_index = RealType.getRealType("front_index");
+        front_red = RealType.getRealType("front_red");
+        front_green = RealType.getRealType("front_green");
+        front_blue = RealType.getRealType("front_blue");
+        RealTupleType rgb =
+          new RealTupleType(front_red, front_green, front_blue);
+        front_inner = new FunctionType(latlon, rgb);
+        front_type = new FunctionType(front_index, front_inner);
+        fronts_type = new FunctionType(RealType.Time, front_type);
+      }
+    }
+  }
+
+  private Gridded2DSet last_curve_set = null;
+
+  class ReleaseCell extends CellImpl {
+
+    private boolean first = true;
+
+    public ReleaseCell() {
+    }
+
+    public void doAction() throws VisADException, RemoteException {
+      if (first) {
+        first = false;
+        return;
+      }
+      if (acontrol.getOn()) return;
+      current_time_step = acontrol.getCurrent();
+      // System.out.println("ReleaseCell " + current_time_step + " " + ntimes);
+      if (current_time_step < 0 || current_time_step >= ntimes) return;
+
+      synchronized (data_lock) {
+        Data data = null;
+        if (curve_ref != null) data = curve_ref.getData();
+        Gridded2DSet curve_set = null;
+        if (data == null || !(data instanceof UnionSet)) {
+          if (debug) System.out.println("data null or not UnionSet");
+          if (curve_ref != null) curve_ref.setData(init_curve);
+          curve_set = last_curve_set;
+        }
+        else {
+          SampledSet[] sets = ((UnionSet) data).getSets();
+          if (sets == null || sets.length == 0 ||
+              !(sets[0] instanceof Gridded2DSet)) {
+            if (debug) System.out.println("data not Gridded2DSet");
+            if (curve_ref != null) curve_ref.setData(init_curve);
+            curve_set = last_curve_set;
+          }
+          else if (sets[0].getManifoldDimension() != 1) {
+            if (debug) System.out.println("ManifoldDimension != 1");
+            if (curve_ref != null) curve_ref.setData(init_curve);
+            curve_set = last_curve_set;
+          }
+          else {
+            curve_set = (Gridded2DSet) sets[0];
+          }
+        }
+        if (curve_set == null) {
+          if (debug) System.out.println("curve_set is null");
+          if (curve_ref != null) curve_ref.setData(init_curve);
+          return;
+        }
+
+        float[][] curve_samples = null;
+        try {
+          curve_samples = curve_set.getSamples(false);
+          if (curve_samples == null || curve_samples[0].length < 2) {
+            if (curve_ref != null) curve_ref.setData(init_curve);
+            throw new VisADException("bad curve_samples");
+          }
+        }
+        catch (VisADException e) {
+          // if (debug) System.out.println("release " + e);
+          if (curve_ref != null) curve_ref.setData(init_curve);
+          curve_set = last_curve_set;
+          try {
+            if (curve_set != null) curve_samples = curve_set.getSamples(false);
+            if (curve_samples == null || curve_samples[0].length < 2) {
+              if (curve_ref != null) curve_ref.setData(init_curve);
+              // throw new VisADException("bad last curve_samples");
+            }
+          }
+          catch (VisADException ee) {
+            if (debug) System.out.println("release " + ee);
+            return;
+          }
+        }
+        last_curve_set = curve_set;
+
+        boolean flip = false;
+        double[] lat_range = lat_map.getRange();
+        double[] lon_range = lon_map.getRange();
+        if (lat_range[1] < lat_range[0]) flip = !flip;
+        if (lon_range[1] < lon_range[0]) flip = !flip;
+        if (curve_samples[lat_index][0] < 0.0) flip = !flip;
+        if (lon_index < lat_index) flip = !flip;
+        // if (debug) System.out.println("flip = " + flip);
+    
+        // transform curve to graphics coordinates
+        // in order to "draw" front in graphics coordinates, then
+        // transform back to (lat, lon)
+        float[][] curve = new float[2][];
+        curve[0] = lat_map.scaleValues(curve_samples[lat_index]);
+        curve[1] = lon_map.scaleValues(curve_samples[lon_index]);
+        // inverseScaleValues
+        // if (debug) System.out.println("curve length = " + curve[0].length);
+
+        front = robustCurveToFront(curve, flip);
+        curves[current_time_step] = curve;
+        flips[current_time_step] = flip;
+        if (front != null) fronts.setSample(current_time_step, front);
+
+        // System.out.println("front_ref.setData in ReleaseCell");
+        front_ref.setData(fronts);
+
+        if (curve_ref != null) curve_ref.setData(init_curve);
+      } // end synchronized (data_lock)
+    }
+  }
+
+  class ZoomCell extends CellImpl {
+
+    public ZoomCell() {
+    }
+
+    public void doAction() throws VisADException, RemoteException {
+      synchronized (data_lock) {
+        for (int i=0; i<ntimes; i++) {
+          if (curves[i] != null) {
+            front = robustCurveToFront(curves[i], flips[i]);
+            if (front != null) fronts.setSample(i, front);
+          }
+        }
+      } // end synchronized (data_lock)
+    }
+  }
+
+  private FieldImpl robustCurveToFront(float[][] curve, boolean flip)
+          throws RemoteException {
+    // resample curve uniformly along length
+    float increment = rsegment_length / (rprofile_length * zoom);
+    float[][] old_curve = resample_curve(curve, increment);
+
+    int fw = filter_window;
+
+    for (int tries=0; tries<12; tries++) {
+      // lowpass filter curve
+      curve = smooth_curve(old_curve, fw);
+
+      // resample smoothed curve
+      curve = resample_curve(curve, increment);
+
+      try {
+        front = curveToFront(curve, flip);
+        break;
+      }
+      catch (VisADException e) {
+        old_curve = curve;
+        if (tries > 4) {
+          int n = old_curve[0].length;
+          if (n > 2) {
+            float[][] no = new float[2][n - 2];
+            System.arraycopy(old_curve[0], 1, no[0], 0, n - 2);
+            System.arraycopy(old_curve[1], 1, no[1], 0, n - 2);
+            old_curve = no;
+          }
+        }
+        if (tries > 8) fw = 2 * fw;
+        // if (debug) System.out.println("retry filter window = " + fw + " " + e);
+        if (tries == 9) {
+          System.out.println("cannot smooth curve");
+          front = null;
+        }
+      }
+    }
+    return front;
+  }
+
+
+  public Gridded2DSet getCurve() {
+    return last_curve_set;
+  }
+
+  // FrontManipulationRendererJ3D button release
+  public void release() {
+    try {
+      release_ref.setData(null);
+    }
+    catch (VisADException e) {
+      if (debug) System.out.println("release fail: " + e.toString());
+    }
+    catch (RemoteException e) {
+      if (debug) System.out.println("release fail: " + e.toString());
+    }
+  }
+
+  /** called by the application to end manipulation;
+      returns the final front */
+  public void endManipulation()
+         throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      if (curve_ref != null) display.removeReference(curve_ref);
+      curve_ref = null;
+    }
+  }
+
+  /** called by the application to end manipulation;
+      returns the final front */
+  public Vector endItAll()
+         throws VisADException, RemoteException {
+    synchronized (data_lock) {
+      if (curve_ref != null) display.removeReference(curve_ref);
+      curve_ref = null;
+      if (front_ref != null) {
+        display.removeReference(front_ref);
+        pcontrol.removeControlListener(pcl);
+        release_cell.removeReference(release_ref);
+        zoom_cell.removeReference(zoom_ref);
+      }
+      front_ref = null;
+    }
+    Vector vector = new Vector();
+    vector.addElement(fronts);
+    vector.addElement(curves);
+    return vector;
+  }
+
+  private static final float CLIP_DELTA = 0.001f;
+
+  private FieldImpl curveToFront(float[][] curve, boolean flip)
+         throws VisADException, RemoteException {
+
+    // compute various scaling factors
+    int len = curve[0].length;
+    if (len < 2) {
+      return null;
+    }
+    float[] seg_length = new float[len-1];
+    float curve_length = curveLength(curve, seg_length);
+    float delta = curve_length / (len - 1);
+    // curve[findex] where
+    // float findex = ibase + mul * repeat_shapes[shape][0][j]
+    float mul = rprofile_length * zoom / rsegment_length;
+    // curve_perp[][findex] * ratio * repeat_shapes[shape][1][j]
+    float ratio = delta * mul;
+
+
+    // compute unit perpendiculars to curve
+    float[][] curve_perp = new float[2][len];
+    for (int i=0; i<len; i++) {
+      int im = i - 1;
+      int ip = i + 1;
+      if (im < 0) im = 0;
+      if (ip > len - 1) ip = len - 1;
+      float yp = curve[0][ip] - curve[0][im];
+      float xp = curve[1][ip] - curve[1][im];
+      xp = -xp;
+      float d = (float) Math.sqrt(xp * xp + yp * yp);
+      if (flip) d = -d;
+      xp = xp / d;
+      yp = yp / d;
+      curve_perp[0][i] = xp;
+      curve_perp[1][i] = yp;
+    }
+
+    // build Vector of FlatFields for each shape of each segment
+    Vector inner_field_vector = new Vector();
+    for (int segment=0; true; segment++) {
+
+      // curve[findex] where
+      // float findex = ibase + mul * repeat_shapes[shape][0][j]
+      float segment_length = (segment == 0) ? fsegment_length : rsegment_length;
+      int profile_length = (segment == 0) ? fprofile_length : rprofile_length;
+      mul = profile_length * zoom / segment_length;
+      // curve_perp[][findex] * ratio * repeat_shapes[shape][1][j]
+      // float ratio = delta * mul;
+
+
+      // figure out if clipping is needed for this segment
+      // only happens for last segment
+      boolean clip = false;
+      float xclip = 0.0f;
+      // int ibase = segment * profile_length;
+      int ibase = (segment == 0) ? 0 : fprofile_length + (segment - 1) * rprofile_length;
+      int iend = ibase + profile_length;
+      if (ibase > len - 1) break;
+      if (iend > len - 1) {
+        clip = true;
+        iend = len - 1;
+        xclip = (iend - ibase) / mul;       
+      }
+
+      // set up shapes for first or repeating segment
+      int nshapes = nrshapes;
+      float[][][] shapes = repeat_shapes;
+      int[][][] tris = repeat_tris;
+      float[] red = repeat_red;
+      float[] green = repeat_green;
+      float[] blue = repeat_blue;
+      if (segment == 0) {
+        nshapes = nfshapes;
+        shapes = first_shapes;
+        tris = first_tris;
+        red = first_red;
+        green = first_green;
+        blue = first_blue;
+      }
+
+      // iterate over shapes for segment
+      for (int shape=0; shape<nshapes; shape++) {
+        float[][] samples = shapes[shape];
+        int [][] ts = tris[shape];
+/*
+        // if needed, clip shape
+        if (clip) {
+          float[][][] outs = new float[1][][];
+          int[][][] outt = new int[1][][];
+          DelaunayCustom.clip(samples, ts, 1.0f, 0.0f, xclip, outs, outt);
+          samples = outs[0];
+          ts = outt[0];
+        }
+*/
+        if (samples == null || samples[0].length < 1) break;
+
+        float[][] ss =
+          mapShape(samples, len, ibase, mul, ratio, curve, curve_perp);
+
+// **** get rid of previous calls to fill() ****
+        ts = DelaunayCustom.fill(ss);
+
+        // if needed, clip shape
+        if (clip) {
+          float[][] clip_samples = {{xclip, xclip, xclip - CLIP_DELTA},
+                                    {CLIP_DELTA, -CLIP_DELTA, 0.0f}};
+          float[][] clip_ss =
+            mapShape(clip_samples, len, ibase, mul, ratio, curve, curve_perp);
+          // now solve for:
+          //   xc * clip_samples[0][0] + yc * clip_samples[1][0] = 1
+          //   xc * clip_samples[0][1] + yc * clip_samples[1][1] = 1
+          //   xc * clip_samples[0][2] + yc * clip_samples[1][2] < 1
+          float det = (clip_samples[0][1] * clip_samples[1][0] -
+                       clip_samples[0][0] * clip_samples[1][1]);
+          float xc = (clip_samples[1][0] - clip_samples[1][1]) / det;
+          float yc = (clip_samples[0][1] - clip_samples[0][0]) / det;
+          float v = 1.0f;
+          if (xc * clip_samples[0][2] + yc * clip_samples[1][2] > v) {
+            xc = - xc;
+            yc = - yc;
+            v = -v;
+          }
+
+          float[][][] outs = new float[1][][];
+          int[][][] outt = new int[1][][];
+          DelaunayCustom.clip(ss, ts, xc, yc, v, outs, outt);
+          ss = outs[0];
+          ts = outt[0];
+        }
+
+        if (ss == null) break;
+        int n = ss[0].length;
+
+        // create color values for field
+        float[][] values = new float[3][n];
+        float r = red[shape];
+        float g = green[shape];
+        float b = blue[shape];
+        for (int i=0; i<n; i++) {
+          values[0][i] = r;
+          values[1][i] = g;
+          values[2][i] = b;
+        }
+
+        // construct set and field
+        DelaunayCustom delaunay = new DelaunayCustom(ss, ts);
+        Irregular2DSet set =
+          new Irregular2DSet(curve_type, ss, null, null, null, delaunay);
+        FlatField field = new FlatField(front_inner, set);
+        field.setSamples(values, false);
+        inner_field_vector.addElement(field);
+// some crazy bug - see Gridded3DSet.makeNormals()
+      } // end for (int shape=0; shape<nshapes; shape++)
+    } // end for (int segment=0; true; segment++)
+
+    int nfields = inner_field_vector.size();
+    Integer1DSet iset = new Integer1DSet(front_index, nfields);
+    FieldImpl front = new FieldImpl(front_type, iset);
+    FlatField[] fields = new FlatField[nfields];
+    for (int i=0; i<nfields; i++) {
+      fields[i] = (FlatField) inner_field_vector.elementAt(i);
+    }
+    front.setSamples(fields, false);
+    return front;
+  }
+
+  private float[][] mapShape(float[][] samples, int len, int ibase, float mul,
+                             float ratio, float[][] curve, float[][] curve_perp) {
+    // map shape into "coordinate system" defined by curve segment
+    int n = samples[0].length;
+    float[][] ss = new float[2][n];
+    for (int i=0; i<n; i++) {
+      float findex = ibase + mul * samples[0][i] / zoom;
+      int il = (int) findex;
+      int ih = il + 1;
+
+      if (il < 0) {
+        il = 0;
+        ih = il + 1;
+      }
+      if (ih > len - 1) {
+        ih = len - 1;
+        il = ih - 1;
+      }
+      // if (il < 0) il = 0;
+      // if (il > len - 1) il = len - 1;
+      // if (ih < 0) ih = 0;
+      // if (ih > len - 1) ih = len - 1;
+
+      float a = findex - il;
+
+      if (a < -1.0f) a = -1.0f;
+      if (a > 2.0f) a = 2.0f;
+      // if (a < 0.0f) a = 0.0f;
+      // if (a > 1.0f) a = 1.0f;
+
+      float b = 1.0f - a;
+      float xl =
+        curve[0][il] + ratio * samples[1][i] * curve_perp[0][il] / zoom;
+      float yl =
+        curve[1][il] + ratio * samples[1][i] * curve_perp[1][il] / zoom;
+      float xh =
+        curve[0][ih] + ratio * samples[1][i] * curve_perp[0][ih] / zoom;
+      float yh =
+        curve[1][ih] + ratio * samples[1][i] * curve_perp[1][ih] / zoom;
+      ss[0][i] = b * xl + a * xh;
+      ss[1][i] = b * yl + a * yh;
+    }
+    // map shape back into (lat, lon) coordinates
+    ss[lat_index] = lat_map.inverseScaleValues(ss[0]);
+    ss[lon_index] = lon_map.inverseScaleValues(ss[1]);
+    return ss;
+  }
+
+  public static float[][] smooth_curve(float[][] curve, int window) {
+    int len = curve[0].length;
+    float[][] newcurve = new float[2][len];
+    for (int i=0; i<len; i++) {
+      int win = window;
+      if (i < win) win = i;
+      int ii = (len - 1) - i;
+      if (ii < win) win = ii;
+      float runx = 0.0f;
+      float runy = 0.0f;
+      for (int j=i-win; j<=i+win; j++) {
+        runx += curve[0][j];
+        runy += curve[1][j];
+      }
+      newcurve[0][i] = runx / (2 * win + 1);
+      newcurve[1][i] = runy / (2 * win + 1);
+    }
+    return newcurve;
+  }
+
+  /** resmaple curve into segments approximately increment in length */
+  public static float[][] resample_curve(float[][] curve, float increment) {
+    int len = curve[0].length;
+    float[] seg_length = new float[len-1];
+    float curve_length = curveLength(curve, seg_length);
+    int npoints = 1 + (int) (curve_length / increment);
+    float delta = curve_length / (npoints - 1);
+    float[][] newcurve = new float[2][npoints];
+    newcurve[0][0] = curve[0][0];
+    newcurve[1][0] = curve[1][0];
+    if (npoints < 2) return newcurve;
+    int k = 0;
+    float old_seg = seg_length[k];
+    for (int i=1; i<npoints-1; i++) {
+      float new_seg = delta;
+      while (true) {
+        if (old_seg < new_seg) {
+          new_seg -= old_seg;
+          k++;
+          if (k > len-2) {
+            throw new VisADError("k = " + k + " i = " + i);
+          }
+          old_seg = seg_length[k];
+        }
+        else {
+          old_seg -= new_seg;
+          float a = old_seg / seg_length[k];
+          newcurve[0][i] = a * curve[0][k] + (1.0f - a) * curve[0][k+1];
+          newcurve[1][i] = a * curve[1][k] + (1.0f - a) * curve[1][k+1];
+          break;
+        }
+      }
+    }
+    newcurve[0][npoints-1] = curve[0][len-1];
+    newcurve[1][npoints-1] = curve[1][len-1];
+    return newcurve;
+  }
+
+  /** assumes curve is float[2][len] and seg_length is float[len-1] */
+  public static float curveLength(float[][] curve, float[] seg_length) {
+    int len = curve[0].length;
+    float curve_length = 0.0f;
+    for (int i=0; i<len-1; i++) {
+      seg_length[i] = (float) Math.sqrt(
+        ((curve[0][i+1] - curve[0][i]) * (curve[0][i+1] - curve[0][i])) +
+        ((curve[1][i+1] - curve[1][i]) * (curve[1][i+1] - curve[1][i])));
+      curve_length += seg_length[i];
+    }
+    return curve_length;
+  }
+
+  private boolean pfirst = true;
+
+  class ProjectionControlListener implements ControlListener {
+
+    public void controlChanged(ControlEvent e)
+           throws VisADException, RemoteException {
+      setScale();
+    }
+  }
+
+  private float last_zoom = 1.0f;
+
+  private void setScale()
+          throws VisADException, RemoteException {
+    double[] matrix = pcontrol.getMatrix();
+    double[] rot = new double[3];
+    double[] scale = new double[1];
+    double[] trans = new double[3];
+    MouseBehaviorJ3D.unmake_matrix(rot, scale, trans, matrix);
+
+    zoom = (float) scale[0];
+    float ratio = zoom / last_zoom;
+// System.out.println("setScale " + zoom + " " + last_zoom + " " + ratio);
+
+    if (ratio < 0.95f || 1.05f < ratio) {
+      last_zoom = zoom;
+      if (zoom_ref != null) zoom_ref.setData(null);
+      // if (release_ref != null) release_ref.setData(null);
+// System.out.println("setScale call setData " + zoom + " " + last_zoom +
+//                    " " + ratio);
+    }
+  }
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    // construct RealTypes for wind record components
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+
+    SetType curve_type = new SetType(new RealTupleType(lat, lon));
+
+    // construct Java3D display and mappings
+    DisplayImplJ3D display =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap lonmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(lonmap);
+    lonmap.setRange(0.0, 20.0);
+    ScalarMap latmap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(latmap);
+    latmap.setRange(-40.0, -20.0);
+
+    ScalarMap timemap = new ScalarMap(RealType.Time, Display.Animation);
+    display.addMap(timemap);
+    AnimationControl acontrol = (AnimationControl) timemap.getControl();
+/* WLH 3 Sept 2001 WHY?
+    // acontrol.setSet(new Integer1DSet(RealType.Time, 4));
+*/
+    acontrol.setSet(new Integer1DSet(RealType.Time, 4));
+
+    initColormaps(display);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test FrontDrawer");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+    panel.add(new AnimationWidget(timemap));
+
+    int front_kind = FrontDrawer.COLD_FRONT;
+    try {
+      if (args.length > 0) front_kind = Integer.parseInt(args[0]);
+    }
+    catch(NumberFormatException e) {
+    }
+    FrontDrawer fd = new FrontDrawer(null, null, display, 8, front_kind);
+
+    JPanel button_panel = new JPanel();
+    button_panel.setLayout(new BoxLayout(button_panel, BoxLayout.X_AXIS));
+    button_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    button_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    JButton end = new JButton("detach");
+    FrontActionListener fal = new FrontActionListener(fd, end, display, front_kind);
+    end.addActionListener(fal);
+    end.setActionCommand("detach");
+    button_panel.add(end);
+    panel.add(button_panel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 700);
+    frame.setVisible(true);
+
+/* WLH 3 Sept 2001 WHY?
+    new Delay(5000);
+    acontrol.setSet(new Integer1DSet(RealType.Time, 4));
+*/
+  }
+}
+
+class FrontActionListener implements ActionListener {
+  private FrontDrawer fd;
+  private static boolean debug = true;
+  private JButton end;
+  private DisplayImplJ3D display;
+  private int front_kind;
+
+  private FieldImpl fronts = null;
+  private float[][][] curves = null;
+
+  FrontActionListener(FrontDrawer f, JButton e, DisplayImplJ3D d, int fk) {
+    fd = f;
+    end = e;
+    display = d;
+    front_kind = fk;
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("detach")) {
+      if (end.getText().equals("detach")) {
+        end.setText("attach");
+        Vector vector = null;
+        try {
+          vector = fd.endItAll();
+        }
+        catch (VisADException ex) {
+        }
+        catch (RemoteException ex) {
+        }
+        fronts = (FieldImpl) vector.elementAt(0);
+        curves = (float[][][]) vector.elementAt(1);
+      }
+      else {
+        end.setText("detach");
+        try {
+          // System.out.println("fronts " + fronts);
+          // System.out.println("curves " + curves);
+          fd = new FrontDrawer(fronts, curves, display, 8, front_kind);
+        }
+        catch (VisADException ex) {
+        }
+        catch (RemoteException ex) {
+        }
+      }
+    }
+  }
+}
+
+class FrontManipulationRendererJ3D extends CurveManipulationRendererJ3D {
+
+  FrontDrawer fd;
+
+  FrontManipulationRendererJ3D(FrontDrawer f, int mmm, int mmv) {
+    super(mmm, mmv, true); // true for only one
+    fd = f;
+  }
+
+  /** mouse button released, ending direct manipulation */
+  public void release_direct() {
+    fd.release();
+  }
+}
+
diff --git a/visad/bom/GridEdit.java b/visad/bom/GridEdit.java
new file mode 100644
index 0000000..f2db081
--- /dev/null
+++ b/visad/bom/GridEdit.java
@@ -0,0 +1,1022 @@
+//
+// GridEdit.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.util.*;
+import visad.java3d.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+/**
+</pre>
+   GridEdit is the VisAD class for warping and modifying fields.<p>
+   Construct a GridEdit object linked to a 2-D grid [a FlatField
+   with MathType ((x, y) -> range)]) or a sequence of 2-D grids [a
+   FieldImpl with MathType (t -> ((x, y) -> range))], and a DisplayImpl.
+   The grid or grids must all have the same domain Set, which must be a
+   Gridded2DSet or a GriddedSet with domain dimension = 2.
+   The domain must be mapped to two spatial DisplayRealTypes. If a
+   sequence of grids, the sequence domain must be mapped to Animation.
+   The grid may have any number of range RealTypes.
+
+   The GridEdit object operates in a sequence:
+   1. Invokes its start() method to start.
+   2. User drags grid warp motion lines with the right mouse button.
+      These lines must lie inside the grid.
+   3. If user presses SHIFT while dragging a grid warp motion line, the
+      program prompts for increments for values at the dragged location.
+   4. User can delete lines by clicking the right button on their end
+      points, with CTRL pressed.
+   5. At any point after start(), the application can invoke stop() to
+      stop the dragging process, and warp the grid.
+   6. After the grid has been warped, the application can invoke undo()
+      to undo the paste and stop the process.
+   7. The process can be restarted by invoking start(), any number of times.
+
+   The main() method illustrates a simple GUI and test case with a sequnece
+   of grids. Run 'java visad.bom.GridEdit' to test with contour
+   values, and run 'java visad.bom.GridEdit 1' to test with color
+   values.
+</pre>
+*/
+public class GridEdit extends Object implements ActionListener {
+
+  private boolean debug = true;
+
+  private Field grids = null;
+  private DisplayImpl display = null;
+
+  private Object lock = new Object();
+
+  private RealType t = null; // non-null if animation
+  private RealType x = null;
+  private RealType y = null;
+  private RealTupleType xy = null;
+  private MathType range = null; // RealType or RealTupleType
+  private int rangedim = 0;
+  private int nts = 0; // number of steps in sequence
+
+  Set tset = null; // t domain Set
+  GriddedSet xyset = null; // (x, y) domain Set
+  FunctionType grid_type;
+
+  FlatField replacedff = null;
+  FlatField savedff = null;
+
+  AnimationControl acontrol = null;
+
+  ScalarMap tmap = null;
+  ScalarMap xmap = null;
+  ScalarMap ymap = null;
+  DisplayTupleType xtuple = null;
+  DisplayTupleType ytuple = null;
+
+  private CellImpl cell_rbl = null;
+  private DataReferenceImpl ref_rbl = null;
+  private RubberBandLineRendererJ3D rblr = null;
+
+  private final static int NPICKS = 50;
+  private int npicks = 0;
+  private PickCell[] cell_picks = null;
+  private DataReferenceImpl[] ref_picks = null;
+  private PickManipulationRendererJ3D[] rend_picks = null;
+  private float[][] delta_picks = null;
+
+  private GridEdit thiscp = null;
+
+  private int renderer_mask;
+  private int dialog_mask;
+
+  /**
+<pre>
+     gs has MathType (t -> ((x, y) -> v)) or ((x, y) -> v)
+     conditions on gs and display:
+     1. x and y mapped to XAxis, YAxis, ZAxis
+     2. (x, y) domain GriddedSet
+     3. if (t -> ...), then t is mapped to Animation
+</pre>
+  */
+  public GridEdit(Field gs, DisplayImplJ3D d)
+         throws VisADException, RemoteException {
+    grids = gs;
+    display = d;
+    renderer_mask = InputEvent.CTRL_MASK;
+    dialog_mask = InputEvent.SHIFT_MASK;
+
+    thiscp = this;
+
+    FunctionType gstype = (FunctionType) gs.getType();
+    RealTupleType domain = gstype.getDomain();
+    int domdim = domain.getDimension();
+    if (domdim == 1) {
+      t = (RealType) domain.getComponent(0);
+      tset = gs.getDomainSet();
+      grid_type = (FunctionType) gstype.getRange();
+      xy = grid_type.getDomain();
+      int dim = xy.getDimension();
+      if (dim != 2) {
+        throw new VisADException("bad grid Field domain dimension: " + dim);
+      }
+      range = grid_type.getRange();
+      nts = tset.getLength();
+      for (int i=0; i<nts; i++) {
+        FlatField ff = (FlatField) gs.getSample(i);
+        Set s = ff.getDomainSet();
+        if (!(s instanceof GriddedSet)) {
+          throw new VisADException("grid set must be GriddedSet");
+        }
+        if (xyset == null) {
+          xyset = (GriddedSet) s;
+        }
+        else {
+          if (!xyset.equals(s)) {
+            throw new VisADException("grid sets must match in animation");
+          }
+        }
+      }
+    }
+    else if (domdim == 2) {
+      t = null;
+      tset = null;
+      xy = domain;
+      grid_type = gstype;
+      range = gstype.getRange();
+      Set s = gs.getDomainSet();
+      if (!(s instanceof GriddedSet)) {
+        throw new VisADException("grid set must be GriddedSet");
+      }
+      xyset = (GriddedSet) s;
+    }
+    else {
+      throw new VisADException("bad grid Field domain dimension: " + domdim);
+    }
+    x = (RealType) xy.getComponent(0);
+    y = (RealType) xy.getComponent(1);
+
+    if (range instanceof RealType) {
+      rangedim = 1;
+    }
+    else if (range instanceof RealTupleType) {
+      rangedim = ((RealTupleType) range).getDimension();
+    }
+    else {
+      throw new VisADException("bad grid Field range type: " + range);
+    }
+
+    Vector scalar_map_vector = display.getMapVector();
+    Enumeration en = scalar_map_vector.elements();
+    while (en.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) en.nextElement();
+      ScalarType scalar = map.getScalar();
+      DisplayRealType dreal = map.getDisplayScalar();
+      DisplayTupleType tuple = dreal.getTuple();
+      if (scalar.equals(t)) {
+        if (Display.Animation.equals(dreal)) {
+          tmap = map;
+          acontrol = (AnimationControl) tmap.getControl();
+        }
+      }
+      else if (tuple != null &&
+               (tuple.equals(Display.DisplaySpatialCartesianTuple) ||
+                (tuple.getCoordinateSystem() != null &&
+                 tuple.getCoordinateSystem().getReference().equals(
+                 Display.DisplaySpatialCartesianTuple)))) { // spatial
+        if (scalar.equals(x)) {
+          xmap = map;
+          xtuple = tuple;
+        }
+        else if (scalar.equals(y)) {
+          ymap = map;
+          ytuple = tuple;
+        }
+      }
+    }
+    if (xmap == null || ymap == null || xtuple != ytuple) {
+      throw new VisADException("grid domain RealTypes must be mapped to " +
+                               "spatial DisplayRealTypes from the same DisplayTupleType");
+    }
+    if (t != null && tmap == null) {
+      throw new VisADException("grid sequence must be mapped to Animation");
+    }
+
+    makePicks(NPICKS);
+
+    ref_rbl = new DataReferenceImpl("rbl");
+
+    rblr = new RubberBandLineRendererJ3D(x, y, renderer_mask, 0);
+    display.addReferences(rblr, ref_rbl);
+    rblr.suppressExceptions(true);
+    rblr.toggle(false);
+
+    // rubber band line release
+    cell_rbl = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        synchronized (lock) {
+          Set set = (Set) ref_rbl.getData();
+          if (set == null) return;
+          float[][] samples = set.getSamples();
+          if (samples == null) return;
+          // make sure both ends of set are within grid domain
+          int[] indices = xyset.valueToIndex(samples);
+          if (indices[0] < 0 || indices[1] < 0) return;
+
+          // find an available PickCell
+          int index = -1;
+          for (int i=0; i<npicks; i++) {
+            if (ref_picks[i].getData() == null) {
+              // put stroke in available PickCell
+              index = i;
+              cell_picks[index].setSkip();
+              ref_picks[index].setData(set);
+              rend_picks[index].toggle(true);
+              break;
+            }
+          }
+          if (index < 0) {
+            // no PickCell available, so create NPICKS more
+            index = npicks;
+            makePicks(npicks + NPICKS);
+            cell_picks[index].setSkip();
+            ref_picks[index].setData(set);
+            rend_picks[index].toggle(true);
+          }
+
+          int modifiers = rblr.getLastMouseModifiers();
+          if ((modifiers & dialog_mask) != 0) { // yes
+            FlatField ff = null;
+            int tindex;
+            if (t != null) {
+              tindex = getAnimationIndex();
+              if (tindex < 0 || tindex >= nts) return;
+              ff = (FlatField) grids.getSample(tindex);
+            }
+            else {
+              ff = (FlatField) grids;
+            }
+            Data range = ff.getSample(indices[0]);
+            if (range instanceof Real) {
+              float value = (float) ((Real) range).getValue();
+              String name = ((RealType) range.getType()).getName();
+              String question = "increment " + name + " (current: " + value + ")";
+              String newvs = JOptionPane.showInputDialog(null, question);
+              float newvalue = 0.0f;
+              try {
+                newvalue = Float.parseFloat(newvs);
+              }
+              catch (NumberFormatException e) {
+              }
+              delta_picks[0][index] = newvalue;
+              // System.out.println(name + " increment = " + newvalue);
+            }
+            else if (range instanceof RealTuple) {
+              for (int j=0; j<rangedim; j++) {
+                Real real = (Real) ((RealTuple) range).getComponent(j);
+                float value = (float) real.getValue();
+                String name = ((RealType) real.getType()).getName();
+                String question = "increment " + name + " (default: " + value + ")";
+                String newvs = JOptionPane.showInputDialog(null, question);
+                float newvalue = 0.0f;
+                try {
+                  newvalue = Float.parseFloat(newvs);
+                }
+                catch (NumberFormatException e) {
+                }
+                delta_picks[j][index] = newvalue;
+                // System.out.println(name + " increment = " + newvalue);
+              }
+            }
+          }
+        } // end synchronized (lock)
+      }
+    };
+
+  }
+
+  // make PickCell's etc up from npicks through n-1
+  private void makePicks(int n) throws VisADException, RemoteException  {
+    if (n <= npicks) return;
+    PickCell[] tcell = null;
+    DataReferenceImpl[] tref = null;
+    PickManipulationRendererJ3D[] trend = null;
+    float[][] tdelta = null;
+    if (npicks > 0) {
+      tcell = cell_picks;
+      tref = ref_picks;
+      trend = rend_picks;
+      tdelta = delta_picks;
+    }
+    cell_picks = new PickCell[n];
+    ref_picks = new DataReferenceImpl[n];
+    rend_picks = new PickManipulationRendererJ3D[n];
+    delta_picks = new float[rangedim][n];
+    if (npicks > 0) {
+      System.arraycopy(tcell, 0, cell_picks, 0, npicks);
+      System.arraycopy(tref, 0, ref_picks, 0, npicks);
+      System.arraycopy(trend, 0, rend_picks, 0, npicks);
+      for (int j=0; j<rangedim; j++) {
+        System.arraycopy(tdelta[j], 0, delta_picks[j], 0, npicks);
+      }
+    }
+    display.disableAction();
+    for (int i=npicks; i<n; i++) makePick(i);
+    display.enableAction();
+    npicks = n;
+  }
+
+  // make PickCell etc for index i
+  private void makePick(int i) throws VisADException, RemoteException  {
+    ref_picks[i] = new DataReferenceImpl("pick" + i);
+    rend_picks[i] = new PickManipulationRendererJ3D(renderer_mask, renderer_mask);
+    rend_picks[i].suppressExceptions(true);
+    rend_picks[i].toggle(false);
+    cell_picks[i] = new PickCell(ref_picks[i],  rend_picks[i]);
+    cell_picks[i].setSkip();
+    cell_picks[i].addReference(ref_picks[i]);
+    display.addReferences(rend_picks[i], ref_picks[i]);
+    for (int j=0; j<rangedim; j++) delta_picks[j][i] = 0.0f;
+  }
+
+  /**
+   * enable user to draw move vectors with optional deltas
+   */
+  public void start() throws VisADException, RemoteException {
+    synchronized (lock) {
+      cell_rbl.addReference(ref_rbl);
+      Gridded2DSet dummy_set = new Gridded2DSet(xy, null, 1);
+      ref_rbl.setData(dummy_set);
+      rblr.toggle(true);
+    }
+  }
+
+  /**
+   * warp grid according to move vectors drawn by user
+   * also interpolate user defined delta values at move points
+   */
+  public void stop() throws VisADException, RemoteException {
+    synchronized (lock) {
+      int np = 0; // number of pick points
+      for (int i=0; i<npicks; i++) {
+        if (ref_picks[i].getData() != null) np++;
+      }
+
+      if (np != 0) {
+
+        FlatField ff = null;
+        int index;
+        if (t != null) {
+          index = getAnimationIndex();
+          if (index < 0 || index >= nts) return;
+          ff = (FlatField) grids.getSample(index);
+        }
+        else {
+          ff = (FlatField) grids;
+        }
+        savedff = new FlatField(grid_type, xyset);
+        savedff.setSamples(ff.getFloats(false), false);
+
+        float[][][] set_samples = new float[np][][];
+        float[][] deltas = new float[rangedim][np];
+        int k = 0;
+        for (int i=0; i<npicks; i++) {
+          if (ref_picks[i].getData() != null) {
+            for (int j=0; j<rangedim; j++) deltas[j][k] = delta_picks[j][i];
+            set_samples[k++] = ((Set) ref_picks[i].getData()).getSamples(false);
+          }
+        }
+
+        // TO DO: set deltas = null if all 0.0f
+        FlatField newff = warpGrid(ff, set_samples, deltas);
+
+        ff.setSamples(newff.getFloats(false), false);
+
+        replacedff = ff;
+      } // end if (np != 0)
+
+      display.disableAction();
+      rblr.toggle(false);
+      for (int i=0; i<npicks; i++) {
+        cell_picks[i].setSkip();
+        ref_picks[i].setData(null);
+        rend_picks[i].toggle(true);
+      }
+      for (int j=0; j<rangedim; j++) {
+        for (int i=0; i<npicks; i++) delta_picks[j][i] = 0.0f;
+      }
+      display.enableAction();
+ 
+      try { cell_rbl.removeReference(ref_rbl); }
+      catch (ReferenceException e) { }
+
+    }
+  }
+
+  /**
+   * warpGrid is the workhorse of GridEdit and can be used independently
+   * of any instances of the class
+   * @param ff          A FlatField containing the 2-D grid to be warped.
+   * @param set_samples The move vectors, dimensioned [number_of_moves][2][2]
+   *                 where the second index enumerates x and y and the third
+   *                 index enumerates the "from" and "to" ends of the move.
+   *                 These values are x and y data values
+   * @param deltas   Increments for moved values, to be interpolated over the
+   *       grid, dimensioned [number_of_grid_range_values][number_of_moves].
+   *                 May be null.
+   * @return         Warped grid, with motion vectors and deltas applied.
+   * @throws VisADException bad parameters
+   */
+  public static FlatField warpGrid(FlatField ff, float[][][] set_samples,
+                                   float[][] deltas)
+         throws VisADException, RemoteException {
+
+    if (ff == null || set_samples == null || set_samples.length == 0) {
+      throw new VisADException("null parameter");
+    }
+    FunctionType fft = (FunctionType) ff.getType();
+    RealTupleType xy = fft.getDomain();
+    GriddedSet xyset = (GriddedSet) ff.getDomainSet();
+    MathType range = fft.getRange();
+    int range_dim = -1;
+    int np = set_samples.length; // number of move points
+    if (range instanceof RealType) {
+      range_dim = 1;
+    }
+    else if (range instanceof RealTupleType) {
+      range_dim = ((RealTupleType) range).getDimension();
+    }
+    if (deltas != null) {
+      if (deltas.length != range_dim) {
+        throw new VisADException("deltas bad length");
+      }
+      for (int j=0; j<range_dim; j++) {
+        if (deltas[j] == null || deltas[j].length != np) {
+          throw new VisADException("deltas bad length");
+        }
+      }
+    }
+
+    int nx = xyset.getLength(0);
+    int ny = xyset.getLength(1);
+    // get locations of grid border points, in order
+/*
+    // every point on border
+    int nb = 2 * (nx + ny) - 4; // number of grid border points
+    int[] indicesb = new int[nb];
+    int k = 0;
+    for (int i=0; i<nx; i++) {
+      indicesb[k++] = i;
+    }
+    for (int i=1; i<(ny-1); i++) {
+      indicesb[k++] = (i + 1) * nx - 1; 
+    }
+    for (int i=nx-1; i>=0; i--) {
+      indicesb[k++] = (ny - 1) * nx + i; 
+    }
+    for (int i=ny-2; i>=1; i--) {
+      indicesb[k++] = i * nx; 
+    }
+*/
+
+    // just 4 corner points on border
+    // int nb = 4;
+    // int[] indicesb = {0, (ny - 1) * nx, ny * nx - 1, nx - 1};
+
+    // limit number of points on border
+    int NB = 32;
+    // int NB = 4;
+    int nxb = (nx > NB) ? NB : nx;
+    int nyb = (ny > NB) ? NB : ny;
+    float xb = ((float) nx) / ((float) nxb);
+    float yb = ((float) ny) / ((float) nyb);
+    int nb = 2 * (nxb + nyb) - 4; // number of grid border points
+    int[] indicesb = new int[nb];
+    int k = 0;
+    for (int i=0; i<nxb; i++) {
+      int ii = Math.round(i * xb);
+      if (ii < 0) ii = 0;
+      if (ii > (nx - 1)) ii = nx - 1;
+      indicesb[k++] = ii;
+    }
+    for (int i=1; i<(nyb-1); i++) {
+      int ii = Math.round(i * yb);
+      if (ii < 1) ii = 1;
+      if (ii > (ny - 2)) ii = ny - 2;
+      indicesb[k++] = (ii + 1) * nx - 1; 
+    }
+    for (int i=nxb-1; i>=0; i--) {
+      int ii = Math.round(i * xb);
+      if (ii < 0) ii = 0;
+      if (ii > (nx - 1)) ii = nx - 1;
+      indicesb[k++] = (ny - 1) * nx + ii; 
+    }
+    for (int i=nyb-2; i>=1; i--) {
+      int ii = Math.round(i * yb);
+      if (ii < 1) ii = 1;
+      if (ii > (ny - 2)) ii = ny - 2;
+      indicesb[k++] = ii * nx; 
+    }
+
+    float[][] samplesb = xyset.indexToValue(indicesb); // locations of grid border points
+
+    // check if all move "to" points are inside 0.8 radius of circle
+    float[][] set_locs = new float[2][np];
+    for (int i=0; i<np; i++) {
+      set_locs[0][i] = set_samples[i][0][1];
+      set_locs[1][i] = set_samples[i][1][1];
+    }
+    float[][] set_grid = xyset.valueToGrid(set_locs);
+    float nx2 = (nx - 1.0f) / 2.0f;
+    float ny2 = (ny - 1.0f) / 2.0f;
+    float nm2 = Math.min(nx2, ny2);
+    float nm22 = nm2 * nm2;
+    float nx22 = nx2 * nx2;
+    float ny22 = ny2 * ny2;
+    boolean all_in = true;
+    for (int i=0; i<np; i++) {
+      float d = (set_grid[0][i] - nx2) * (set_grid[0][i] - nx2) / nm22 +
+                (set_grid[1][i] - ny2) * (set_grid[1][i] - ny2) / nm22;
+      if (d > 0.64) {
+        all_in = false;
+        // System.out.println("not all_in d = " + d);
+        break;
+      }
+    }
+
+    if (all_in) {
+      // all move "to" points inside 0.8 radius, so add circle boundary points
+      int ne = 32; // weird bug for ne proportional to nx and ny
+      int new_nb = nb + ne;
+      float[][] new_samplesb = new float[2][new_nb];
+      System.arraycopy(samplesb[0], 0, new_samplesb[0], 0, nb);
+      System.arraycopy(samplesb[1], 0, new_samplesb[1], 0, nb);
+      float[][] circle_grid = new float[2][ne];
+      for (int i=0; i<ne; i++) {
+        double ang = 2.0 * Math.PI * i / (ne);
+        circle_grid[0][i] = nx2 + nm2 * 0.90f * ((float) Math.sin(ang) );
+        circle_grid[1][i] = ny2 + nm2 * 0.90f * ((float) Math.cos(ang) );
+      }
+      float[][] circle_locs = xyset.gridToValue(circle_grid);
+      System.arraycopy(circle_locs[0], 0, new_samplesb[0], nb, ne);
+      System.arraycopy(circle_locs[1], 0, new_samplesb[1], nb, ne);
+      samplesb = new_samplesb;
+      nb = new_nb;
+    }
+
+
+    RealType xmove = RealType.getRealType("xmove");
+    RealType ymove = RealType.getRealType("ymove");
+    RealTupleType xymove = new RealTupleType(xmove, ymove); 
+    FunctionType move_type = new FunctionType(xy, xymove);
+
+    // combine stationary border points with move points
+    int ns = nb + np;
+    float[][] mover = new float[2][ns];
+    float[][] moved = new float[2][ns];
+    for (int i=0; i<nb; i++) {
+      // located at border point
+      moved[0][i] = samplesb[0][i];
+      moved[1][i] = samplesb[1][i];
+      // zero move vector
+      mover[0][i] = 0.0f;
+      mover[1][i] = 0.0f;
+    }
+
+    // float[][] ireg = new float[2][np]; // for non-Delaunay IrregularSet
+    for (int i=0; i<np; i++) {
+      int ip = nb + i;
+      float[][] samples = set_samples[i];
+      // located at "to" end of vector user drew
+      moved[0][ip] = samples[0][1];
+      moved[1][ip] = samples[1][1];
+      // move vector is negative of vector user drew
+      mover[0][ip] = samples[0][0] - samples[0][1];
+      mover[1][ip] = samples[1][0] - samples[1][1];
+      // for non-Delaunay IrregularSet
+      // ireg[0][i] = samples[0][1];
+      // ireg[1][i] = samples[1][1];
+    }
+
+/*
+a nice idea, but this accentuates corner effects
+    // compute Irregular2DSet without any triangles whose vertices
+    // are all 3 on the border
+    Irregular2DSet nset = new Irregular2DSet(xy, ireg);
+    Delaunay del = nset.Delan;
+    int[][] dtris = null;
+    if (del != null) dtris = del.Tri;
+
+    // find closest internal point to each border point
+    int[] closest = new int[nb];
+    for (int i=0; i<nb; i++) {
+      float dist = Float.MAX_VALUE;
+      for (int j=0; j<np; j++) {
+        float d = (moved[0][i] - ireg[0][j]) * (moved[0][i] - ireg[0][j]) +
+                  (moved[1][i] - ireg[1][j]) * (moved[1][i] - ireg[1][j]);
+        if (d < dist) {
+          dist = d;
+          closest[i] = j + nb;
+        }
+      }
+    }
+
+    // compute triangles for borders (no triangles with all 3 vertices on border)
+    int[][] btris = new int[2 * nb][3];
+    int ntris = 0;
+    for (int i=0; i<nb; i++) {
+      int ip = i + 1;
+      if (ip == nb) ip = 0;
+      btris[ntris][0] = i;
+      btris[ntris][1] = ip;
+      btris[ntris][2] = closest[i];
+// System.out.println("a " + btris[ntris][0] + " " + btris[ntris][1] + " " + btris[ntris][2]);
+      ntris++;
+      if (closest[i] != closest[ip]) {
+        btris[ntris][0] = ip;
+        btris[ntris][1] = closest[i];
+        btris[ntris][2] = closest[ip];
+// System.out.println("b " + btris[ntris][0] + " " + btris[ntris][1] + " " + btris[ntris][2]);
+        ntris++;
+      }
+    }
+    // add internal triangles
+    if (dtris != null) {
+      int nd = dtris.length;
+      for (int i=0; i<nd; i++) {
+        btris[ntris][0] = dtris[i][0] + nb;
+        btris[ntris][1] = dtris[i][1] + nb;
+        btris[ntris][2] = dtris[i][2] + nb;
+// System.out.println("c " + btris[ntris][0] + " " + btris[ntris][1] + " " + btris[ntris][2]);
+        ntris++;
+      }
+    }
+    int[][] tris = new int[ntris][3];
+    for (int i=0; i<ntris; i++) {
+      int a = btris[i][0];
+      int b = btris[i][1];
+      int c = btris[i][2];
+      float cross = (moved[0][b]-moved[0][a]) * (moved[1][c]-moved[1][a]) -
+                    (moved[0][c]-moved[0][a]) * (moved[1][b]-moved[1][a]);
+      // ensure uniform rotation direction of triangles
+      if (cross > 0.0f) {
+        tris[i][0] = a;
+        tris[i][1] = b;
+        tris[i][2] = c;
+      }
+      else {
+        tris[i][0] = a; 
+        tris[i][1] = c; 
+        tris[i][2] = b; 
+      }
+// System.out.println("d " + tris[i][0] + " " + tris[i][1] + " " + tris[i][2]);
+    }
+    DelaunayCustom dc = new DelaunayCustom(moved, tris);
+    Irregular2DSet iset = new Irregular2DSet(xy, moved, null, null, null, dc);
+    // end of compute Irregular2DSet without any triangles whose vertices
+    // are all 3 on the border
+*/
+
+    // compute Irregular2DSet via simple Delaunay
+    IrregularSet iset = new Irregular2DSet(xy, moved);
+
+    // moveff is vector Field of motions, at Irregular2DSet combining
+    // stationary border points with inverses of user drawn vectors
+    FlatField moveff = new FlatField(move_type, iset);
+    moveff.setSamples(mover);
+
+    // interpolate inverse motions to grid locations
+    FlatField move_interp = (FlatField) moveff.resample(xyset);
+
+    // form a new "warped" Gridded2DSet of the locations
+    // that grid move to under interpolate inverse motions
+    float[][] bases = xyset.getSamples(true); // copy
+    float[][] offsets = move_interp.getFloats(false);
+    for (int i=0; i<nx*ny; i++) {
+      bases[0][i] += offsets[0][i];
+      bases[1][i] += offsets[1][i];
+    }
+    Gridded2DSet warpset =
+      new Gridded2DSet(xy, bases, nx, ny, null, null, null, false, false);
+
+    // interpolated grid values at locations of warped grid
+    FlatField warpff = (FlatField) ff.resample(warpset);
+
+    // put interpolated values at warped grid into a new grid
+    // at the original grid locations, and return it
+    FlatField newff = new FlatField(fft, xyset);
+    newff.setSamples(warpff.getFloats(false), false);
+
+    if (deltas != null) {
+      // form irregular Field of delta values
+      FlatField deltaff = new FlatField(fft, iset);
+      float[][] ds = new float[range_dim][ns];
+      for (int j=0; j<range_dim; j++) {
+        for (int i=0; i<nb; i++) ds[j][i] = 0.0f;
+        System.arraycopy(deltas[j], 0, ds[j], nb, np);
+      }
+      deltaff.setSamples(ds, false);
+      newff = (FlatField) newff.add(deltaff, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+    }
+
+    // replace any missing newff values with ff values
+    float[][] ff_values = ff.getFloats(false);
+    float[][] newff_values = newff.getFloats(false);
+    boolean any_missing = false;
+    int nff = ff_values[0].length;
+    for (int j=0; j<range_dim; j++) {
+      for (int i=0; i<nff; i++) {
+        if (newff_values[j][i] != newff_values[j][i]) {
+          newff_values[j][i] = ff_values[j][i];
+          any_missing = true;
+        }
+      }
+    }
+    if (any_missing) newff.setSamples(newff_values);
+
+    return newff;
+  }
+
+  /**
+   * undo action of last call to stop()
+   */
+  public void undo() throws VisADException, RemoteException {
+    synchronized (lock) {
+      if (replacedff != null) {
+        float[][] samples = savedff.getFloats(false);
+        replacedff.setSamples(samples, false);
+      }
+      replacedff = null;
+    }
+    stop();
+  }
+
+  // return the index of the current animation step
+  private int getAnimationIndex() throws VisADException {
+    int[] indices = {acontrol.getCurrent()};
+    Set aset = acontrol.getSet();
+    double[][] values = aset.indexToDouble(indices);
+    int[] tindices = tset.doubleToIndex(values);
+    return tindices[0];
+  }
+
+  private static final int NX = 64;
+  private static final int NY = 64;
+  private static final int NTIMES = 4;
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    // construct RealTypes for wind record components
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealTupleType xy = new RealTupleType(x, y);
+    RealType windx = RealType.getRealType("windx",
+                          CommonUnit.meterPerSecond);     
+    RealType windy = RealType.getRealType("windy",
+                          CommonUnit.meterPerSecond);     
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+
+    // EarthVectorType extends RealTupleType and says that its
+    // components are vectors in m/s with components parallel
+    // to Longitude (positive east) and Latitude (positive north)
+    EarthVectorType windxy = new EarthVectorType(windx, windy);
+
+    RealType time = RealType.Time;
+    double startt = new DateTime(1999, 122, 57060).getValue();
+    Linear1DSet time_set = new Linear1DSet(time, startt, startt + 2700.0, NTIMES);
+
+    Linear2DSet grid_set = new Integer2DSet(xy, NX, NY);
+
+    RealTupleType tuple_type = new RealTupleType(new RealType[]
+             {lon, lat, windx, windy, red, green});
+
+    FunctionType field_type = new FunctionType(xy, tuple_type);
+    FunctionType seq_type = new FunctionType(time, field_type);
+
+    // construct first Java3D display and mappings that govern
+    // how wind records are displayed
+    DisplayImplJ3D display1 =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    double NM = Math.max(NX, NY);
+    ScalarMap ymap = new ScalarMap(y, Display.YAxis);
+    display1.addMap(ymap);
+    ScalarMap xmap = new ScalarMap(x, Display.XAxis);
+    display1.addMap(xmap);
+    ymap.setRange(0.0, NM);
+    xmap.setRange(0.0, NM);
+
+    ScalarMap cmap = null;
+    if (args.length > 0) {
+      cmap = new ScalarMap(windy, Display.RGB);
+    }
+    else {
+      cmap = new ScalarMap(windy, Display.IsoContour);
+    }
+    display1.addMap(cmap);
+
+    ScalarMap amap = new ScalarMap(time, Display.Animation);
+    display1.addMap(amap);
+    AnimationControl acontrol = (AnimationControl) amap.getControl();
+    acontrol.setStep(500);
+
+    // create an array of NX by NY winds
+    FieldImpl field = new FieldImpl(seq_type, time_set);
+    double[][] values = new double[6][NX * NY];
+    for (int k=0; k<NTIMES; k++) {
+      FlatField ff = new FlatField(field_type, grid_set);
+      int m = 0;
+      double NX2 = NX / 2.0;
+      double NY2 = NY / 2.0;
+      for (int j=0; j<NY; j++) {
+        for (int i=0; i<NX; i++) {
+
+          double u = 2.0 * (i - NX2) / (NM - 1.0);
+          double v = 2.0 * (j - NY2) / (NM - 1.0);
+  
+          // each wind record is a Tuple (lon, lat, (windx, windy), red, green)
+          // set colors by wind components, just for grins
+          values[0][m] = 10.0 * u;
+          values[1][m] = 10.0 * v - 40.0;
+          double fx = k + 30.0 * u;
+          double fy = 30.0 * v;
+          double fd =
+            Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy) + k * 15.0;
+          double fs = Math.sqrt(fx * fx + fy * fy);
+          values[2][m] = fd;
+          values[3][m] = fs;
+          values[4][m] = u;
+          values[5][m] = v;
+          m++;
+        }
+      }
+      ff.setSamples(values);
+      field.setSample(k, ff);
+    }
+
+    DataReferenceImpl seq_ref = new DataReferenceImpl("seq");
+    seq_ref.setData(field);
+    display1.addReference(seq_ref);
+
+    final GridEdit cp = new GridEdit(field, display1);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test CollectiveBarbManipulation");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+    JPanel panel1 = new JPanel();
+    panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
+    JPanel panel2 = new JPanel();
+    panel2.setLayout(new BoxLayout(panel2, BoxLayout.Y_AXIS));
+
+
+    panel1.add(display1.getComponent());
+    panel1.setMaximumSize(new Dimension(400, 600));
+
+    JPanel panel3 = new JPanel();
+    panel3.setLayout(new BoxLayout(panel3, BoxLayout.X_AXIS));
+    final JButton start = new JButton("start");
+    start.addActionListener(cp);
+    start.setActionCommand("start");
+    final JButton stop = new JButton("stop");
+    stop.addActionListener(cp);
+    stop.setActionCommand("stop");
+    final JButton undo = new JButton("undo");
+    undo.addActionListener(cp);
+    undo.setActionCommand("undo");
+    panel3.add(start);
+    panel3.add(stop);
+    panel3.add(undo);
+
+    panel2.add(new AnimationWidget(amap));
+    panel2.add(new JLabel(" "));
+    if (args.length > 0) {
+      LabeledColorWidget lcw = new LabeledColorWidget(cmap);
+      lcw.setMaximumSize(new Dimension(400, 200));
+      panel2.add(lcw);
+    }
+    else {
+      ContourWidget cw = new ContourWidget(cmap);
+      cw.setMaximumSize(new Dimension(400, 200));
+      panel2.add(cw);
+    }
+    panel2.add(new JLabel(" "));
+    panel2.add(panel3);
+    panel2.setMaximumSize(new Dimension(400, 600));
+
+    panel.add(panel1);
+    panel.add(panel2);
+    frame.getContentPane().add(panel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(800, 600);
+    frame.setVisible(true);
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("start")) {
+      try {
+        start();
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+    else if (cmd.equals("stop")) {
+      try {
+        stop();
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+    else if (cmd.equals("undo")) {
+      try {
+        undo();
+      }
+      catch (VisADException ex) {
+        ex.printStackTrace();
+      }
+      catch (RemoteException ex) {
+        ex.printStackTrace();
+      }
+    }
+  }
+
+}
+
+/**
+ * CellImpl for user clicks to delete move vectors
+ * via setData(null)
+*/
+class PickCell extends CellImpl {
+
+  private boolean skip;
+  private DataRenderer rend;
+  private DataReferenceImpl ref;
+
+  public PickCell(DataReferenceImpl rf, DataRenderer rd) {
+    rend = rd;
+    ref = rf;
+    skip = true;
+  }
+
+  public void doAction() throws VisADException, RemoteException {
+    if (skip) {
+       skip = false;
+    }
+    else {
+      rend.toggle(false);
+      skip = true;
+      ref.setData(null);
+    }
+  }
+
+  public void setSkip() {
+    skip = true;
+  }
+}
+
diff --git a/visad/bom/ImageRendererJ3D.java b/visad/bom/ImageRendererJ3D.java
new file mode 100644
index 0000000..97cef4d
--- /dev/null
+++ b/visad/bom/ImageRendererJ3D.java
@@ -0,0 +1,855 @@
+//
+// ImageRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.rmi.RemoteException;
+
+import javax.media.j3d.BranchGroup;
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.AnimationControl;
+import visad.BadMappingException;
+import visad.CoordinateSystem;
+import visad.CachingCoordinateSystem;
+import visad.InverseLinearScaledCS;
+import visad.Data;
+import visad.DataDisplayLink;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayException;
+import visad.DisplayImpl;
+import visad.DisplayRealType;
+import visad.DisplayTupleType;
+import visad.Field;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Gridded1DDoubleSet;
+import visad.MathType;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.ScalarType;
+import visad.Set;
+import visad.ShadowType;
+import visad.ShadowRealType;
+import visad.ShadowRealTupleType;
+import visad.ShadowFunctionOrSetType;
+import visad.VisADError;
+import visad.VisADException;
+import visad.data.netcdf.Plain;
+import visad.java3d.DefaultRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.ShadowTypeJ3D;
+import visad.java3d.VisADBranchGroup;
+import visad.java3d.VisADImageNode;
+import visad.util.Delay;
+
+/**
+   ImageRendererJ3D is the VisAD class for fast loading of images
+   and image sequences under Java3D.
+
+   WARNING - when reUseFrames is true during doTransform()
+   ImageRendererJ3D makes these assumptions:
+   1. That the images in a new time sequence are identical to
+   any images at the same time in a previous sequence.
+   2. That the image sequence defines the entire animation
+   sampling.<P>
+*/
+public class ImageRendererJ3D extends DefaultRendererJ3D {
+
+  // FOR DEVELOPMENT PURPOSES //////////////////////////////////
+  private static final int DEF_IMG_TYPE;
+
+  //GEOMETRY/COLORBYTE REUSE LOGIC VARIABLES (STARTS HERE)
+  private int last_curve_size = -1;
+  private float last_zaxis_value = Float.NaN;
+  private float last_alpha_value = Float.NaN;
+  private long last_data_hash_code = -1;
+  private boolean adjust_projection_seam = false; //27FEB2012: Projection Seam Change Bug Fix
+  //GEOMETRY/COLORBYTE REUSE LOGIC VARIABLES (ENDS HERE)
+
+
+
+  static {
+    String val = System.getProperty("visad.java3d.8bit", "false");
+    if (Boolean.parseBoolean(val)) {
+      DEF_IMG_TYPE = BufferedImage.TYPE_BYTE_GRAY;
+      System.err.println("WARN: 8bit enabled via system property");
+    } else {
+      DEF_IMG_TYPE = BufferedImage.TYPE_4BYTE_ABGR;
+    }
+  }
+  //////////////////////////////////////////////////////////////
+  
+  // MathTypes that data must equalsExceptNames()
+  private static MathType image_sequence_type, image_type;
+  private static MathType image_sequence_type2, image_type2;
+  private static MathType image_sequence_type3, image_type3;
+
+  // initialize above MathTypes
+  static {
+    try {
+      image_type = MathType.stringToType(
+        "((ImageElement, ImageLine) -> ImageValue)");
+      image_sequence_type = new FunctionType(RealType.Time, image_type);
+      image_type2 = MathType.stringToType(
+        "((ImageElement, ImageLine) -> (ImageValue))");
+      image_sequence_type2 = new FunctionType(RealType.Time, image_type2);
+      image_type3 = MathType.stringToType(
+        "((ImageElement, ImageLine) -> (Red, Green, Blue))");
+      image_sequence_type3 = new FunctionType(RealType.Time, image_type3);
+    }
+    catch (VisADException e) {
+      throw new VisADError(e.getMessage());
+    }
+  }
+
+  /** determine whether the given MathType is usable with ImageRendererJ3D */
+  public static boolean isImageType(MathType type) {
+    return (image_sequence_type.equalsExceptName(type) ||
+            image_sequence_type2.equalsExceptName(type) ||
+            image_sequence_type3.equalsExceptName(type) ||
+            image_type.equalsExceptName(type) ||
+            image_type2.equalsExceptName(type) ||
+            image_type3.equalsExceptName(type));
+  }
+
+  /** @deprecated Use isRendererUsable(MathType, ScalarMap[]) instead. */
+  public static void verifyImageRendererUsable(MathType type, ScalarMap[] maps)
+    throws VisADException
+  {
+    isRendererUsable(type, maps);
+  }
+
+  /** determine whether the given MathType and collection of ScalarMaps
+      meets the criteria to use ImageRendererJ3D. Throw a VisADException
+      if ImageRenderer cannot be used, otherwise return true. */
+  public static boolean isRendererUsable(MathType type, ScalarMap[] maps)
+    throws VisADException
+  {
+    RealType time = null;
+    RealTupleType domain = null;
+    RealTupleType range = null;
+    RealType x = null, y = null;
+    RealType rx = null, ry = null; // WLH 19 July 2000
+    RealType r = null, g = null, b = null;
+    RealType rgb = null;
+
+    // must be a function
+    if (!(type instanceof FunctionType)) {
+      throw new VisADException("Not a FunctionType");
+    }
+    FunctionType function = (FunctionType) type;
+    RealTupleType functionD = function.getDomain();
+    MathType functionR = function.getRange();
+
+    // time function
+    if (function.equalsExceptName(image_sequence_type) ||
+      function.equalsExceptName(image_sequence_type2) ||
+      function.equalsExceptName(image_sequence_type3))
+    {
+      // strip off time RealType
+      time = (RealType) functionD.getComponent(0);
+      function = (FunctionType) functionR;
+      functionD = function.getDomain();
+      functionR = function.getRange();
+    }
+
+    // ((ImageLine, ImageElement) -> ImageValue)
+    // ((ImageLine, ImageElement) -> (ImageValue))
+    // ((ImageLine, ImageElement) -> (Red, Green, Blue))
+    if (function.equalsExceptName(image_type) ||
+        function.equalsExceptName(image_type2) ||
+        function.equalsExceptName(image_type3)) {
+      domain = function.getDomain();
+      MathType rt = function.getRange();
+      if (rt instanceof RealType) {
+        range = new RealTupleType((RealType) rt);
+      }
+      else if (rt instanceof RealTupleType) {
+        range = (RealTupleType) rt;
+      } 
+      else {
+        // illegal MathType
+        throw new VisADException("Illegal RangeType");
+      }
+    }
+    else {
+      // illegal MathType
+      throw new VisADException("Illegal MathType");
+    }
+
+    // extract x and y from domain
+    x = (RealType) domain.getComponent(0);
+    y = (RealType) domain.getComponent(1);
+
+    // WLH 19 July 2000
+    CoordinateSystem cs = domain.getCoordinateSystem();
+    if (cs != null) {
+      RealTupleType rxy = cs.getReference();
+      rx = (RealType) rxy.getComponent(0);
+      ry = (RealType) rxy.getComponent(1);
+    }
+
+    // extract colors from range
+    int dim = range.getDimension();
+    if (dim == 1) rgb = (RealType) range.getComponent(0);
+    else { // dim == 3
+      r = (RealType) range.getComponent(0);
+      g = (RealType) range.getComponent(1);
+      b = (RealType) range.getComponent(2);
+    }
+
+    // verify that collection of ScalarMaps is legal
+    boolean btime = (time == null);
+    boolean bx = false, by = false;
+    boolean brx = false, bry = false; // WLH 19 July 2000
+    boolean br = false, bg = false, bb = false;
+    boolean dbr = false, dbg = false, dbb = false;
+    Boolean latlon = null;
+    DisplayRealType spatial = null;
+
+    for (int i=0; i<maps.length; i++) {
+      ScalarMap m = maps[i];
+      ScalarType md = m.getScalar();
+      DisplayRealType mr = m.getDisplayScalar();
+      boolean ddt = md.equals(time);
+      boolean ddx = md.equals(x);
+      boolean ddy = md.equals(y);
+      boolean ddrx = md.equals(rx);
+      boolean ddry = md.equals(ry);
+      boolean ddr = md.equals(r);
+      boolean ddg = md.equals(g);
+      boolean ddb = md.equals(b);
+      boolean ddrgb = md.equals(rgb);
+
+      // animation mapping
+      if (ddt) {
+        if (btime) throw new VisADException("Multiple Time mappings");
+        if (!mr.equals(Display.Animation)) {
+          throw new VisADException(
+            "Time mapped to something other than Animation");
+        }
+        btime = true;
+      }
+
+      // spatial mapping
+      else if (ddx || ddy || ddrx || ddry) {
+        if (ddx && bx || ddy && by || ddrx && brx || ddry && bry) {
+          throw new VisADException("Duplicate spatial mappings");
+        }
+        if (((ddx || ddy) && (brx || bry)) ||
+            ((ddrx || ddry) && (bx || by))) {
+          throw new VisADException("reference and non-reference spatial mappings");
+        }
+        RealType q = (ddx ? x : null);
+        if (ddy) q = y;
+        if (ddrx) q = rx;
+        if (ddry) q = ry;
+
+        boolean ll;
+        if (mr.equals(Display.XAxis) || mr.equals(Display.YAxis) ||
+          mr.equals(Display.ZAxis))
+        {
+          ll = false;
+        }
+        else if (mr.equals(Display.Latitude) || mr.equals(Display.Longitude) ||
+          mr.equals(Display.Radius))
+        {
+          ll = true;
+        }
+        else throw new VisADException("Illegal domain mapping");
+
+        if (latlon == null) {
+          latlon = new Boolean(ll);
+          spatial = mr;
+        }
+        else if (latlon.booleanValue() != ll) {
+          throw new VisADException("Multiple spatial coordinate systems");
+        }
+        // two mappings to the same spatial DisplayRealType are not allowed
+        else if (spatial == mr) {
+          throw new VisADException(
+            "Multiple mappings to the same spatial DisplayRealType");
+        }
+
+        if (ddx) bx = true;
+        else if (ddy) by = true;
+        else if (ddrx) brx = true;
+        else if (ddry) bry = true;
+      }
+
+      // rgb mapping
+      else if (ddrgb) {
+        if (br || bg || bb) {
+          throw new VisADException("Duplicate color mappings");
+        }
+        if (rgb == null ||
+            !(mr.equals(Display.RGB) || mr.equals(Display.RGBA))) {
+          throw new VisADException("Illegal RGB/RGBA mapping");
+        }
+        dbr = dbg = dbb = true;
+        br = bg = bb = true;
+      }
+
+      // color mapping
+      else if (ddr || ddg || ddb) {
+        if (rgb != null) throw new VisADException("Illegal RGB mapping");
+        RealType q = (ddr ? r : (ddg ? g : b));
+        if (mr.equals(Display.Red)) dbr = true;
+        else if (mr.equals(Display.Green)) dbg = true;
+        else if (mr.equals(Display.Blue)) dbb = true;
+        else throw new VisADException("Illegal color mapping");
+
+        if (ddr) br = true;
+        else if (ddg) bg = true;
+        else bb = true;
+      }
+
+      // illegal ScalarMap involving this MathType
+      else if (ddt || ddx || ddy || ddrx || ddry ||
+        ddr || ddg || ddb || ddrgb)
+      {
+        throw new VisADException("Illegal mapping: " + m);
+      }
+    }
+
+    // return true if all conditions for ImageRendererJ3D are met
+    if (!(btime && ((bx && by) || (brx && bry)) &&
+          br && bg && bb && dbr && dbg && dbb)) {
+      throw new VisADException("Insufficient mappings");
+    }
+    return true;
+  }
+
+  // flag to indicate:
+  // 1. That the images in a new time sequence are identical to
+  //    any images at the same time in a previous sequence.
+  // 2. That the image sequence defines the entire animation
+  //    sampling.<P>
+  private boolean reUseFrames = false;
+
+  private int suggestedBufImgType = DEF_IMG_TYPE;
+  
+  private boolean setSetOnReUseFrames = true;
+
+  private VisADImageNode imagesNode = null;
+
+  private boolean lastByRef = false;
+
+
+  public static boolean isByRefUsable(DataDisplayLink link, ShadowType shadow) throws VisADException, RemoteException {
+        ShadowFunctionOrSetType shadowType = (ShadowFunctionOrSetType) shadow.getAdaptedShadowType();
+        CoordinateSystem dataCoordinateSystem = null;
+        FlatField fltField = null;
+        int num_images = 1;
+        FieldImpl field = (FieldImpl) link.getData();
+        if (!(field instanceof FlatField)) {
+                num_images = field.getDomainSet().getLength();
+                if (1 == num_images) { //If there is a single image in the animation dont do anything, simply return true
+                        return true;
+                }
+                shadowType = (ShadowFunctionOrSetType) shadowType.getRange();
+                fltField = (FlatField) field.getSample(0);
+                dataCoordinateSystem = fltField.getDomainCoordinateSystem();
+        } else {
+                dataCoordinateSystem = ((FlatField)field).getDomainCoordinateSystem();
+                return true;
+        }
+
+        // cs might be cached
+        if (dataCoordinateSystem instanceof CachingCoordinateSystem) {
+                dataCoordinateSystem = ((CachingCoordinateSystem) dataCoordinateSystem).getCachedCoordinateSystem();
+        }
+
+        ShadowRealType[] DomainComponents = shadowType.getDomainComponents();
+        ShadowRealTupleType Domain = shadowType.getDomain();
+        ShadowRealTupleType domain_reference = Domain.getReference();
+        ShadowRealType[] DC = DomainComponents;
+
+        if (domain_reference != null &&
+                domain_reference.getMappedDisplayScalar()) {
+                DC = shadowType.getDomainReferenceComponents();
+        }
+
+        DisplayTupleType spatial_tuple = null;
+        for (int i=0; i<DC.length; i++) {
+                java.util.Enumeration maps = DC[i].getSelectedMapVector().elements();
+                ScalarMap map = (ScalarMap) maps.nextElement();
+                DisplayRealType real = map.getDisplayScalar();
+                spatial_tuple = real.getTuple();
+        }
+
+        CoordinateSystem coord = spatial_tuple.getCoordinateSystem();
+
+        if (coord instanceof CachingCoordinateSystem) {
+                coord = ((CachingCoordinateSystem)coord).getCachedCoordinateSystem();
+        }
+
+        boolean useLinearTexture = false;
+        if (coord instanceof InverseLinearScaledCS) {
+                InverseLinearScaledCS invCS = (InverseLinearScaledCS)coord;
+                useLinearTexture = (invCS.getInvertedCoordinateSystem()).equals(dataCoordinateSystem);
+        }
+
+        /** if useLinearTexture is true at this point, it's true for the first image of a sequence with numimages > 1.  We'll
+            have to assume that byRef will apply until below is resolved.
+         */
+
+        /** more consideration/work needed here.  CoordinateSystems might be equal even if they aren't the same transform (i,j) -> (lon,lat)
+        if (!useLinearTexture) { //If DisplayCoordinateSystem != DataCoordinateSystem
+                if (num_images > 1) { //Its an animation
+                        int lengths[] = ((visad.GriddedSet) fltField.getDomainSet()).getLengths();
+                        int data_width = lengths[0];
+                        int data_height = lengths[1];
+                        for (int i = 1; i < num_images; i++) {//Playing safe: go down the full sequence to find if the full set is Geostaionary or NOT
+                                FlatField ff = (FlatField) field.getSample(i); //Quicker Approach would be to just compare only the first two images in the sequence. But that may not be safe.
+                                CoordinateSystem dcs = ff.getDomainCoordinateSystem();
+                                // dcs might be cached
+                                if (dcs instanceof CachingCoordinateSystem) {
+                                        dcs = ((CachingCoordinateSystem) dcs).getCachedCoordinateSystem();
+                                }
+                                int[] lens = ((visad.GriddedSet) ff.getDomainSet()).getLengths();
+                                if (lens[0] != data_width || lens[1] != data_height || ( (dcs != null) && (dataCoordinateSystem != null) && !dcs.equals(dataCoordinateSystem))) {
+                                        useLinearTexture = false;
+                                        break;
+                                }
+                        }
+                }
+        }
+        */
+        return useLinearTexture;
+  }
+
+  // factory for ShadowFunctionType that defines unique behavior
+  // for ImageRendererJ3D
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowImageFunctionTypeJ3D(type, link, parent);
+  }
+
+  /**
+   * Toggle the re-using of frames when a new image or set of images
+   * is set in the datareference.<P>
+   * <B>WARNING</B> - when reUseFrames is true during doTransform()
+   * ImageRendererJ3D makes these assumptions:
+   * <OL>
+   * <LI>That the images in a new time sequence are identical to
+   *     any images at the same time in a previous sequence.
+   * <LI>That the image sequence defines the entire animation
+   *     sampling.<P>
+   * </OL>
+   */
+  public void setReUseFrames(boolean reuse) {
+    reUseFrames = reuse;
+  }
+
+  /**
+   * Suggest to the underlying shadow type the buffered image type
+   * to use.
+   * 
+   * <b>Experimental</b>: This may changed or removed in future releases.
+   */
+  public void suggestBufImageType(int imageType) {
+    switch (imageType) {
+    case BufferedImage.TYPE_3BYTE_BGR:
+    case BufferedImage.TYPE_4BYTE_ABGR:
+    case BufferedImage.TYPE_BYTE_GRAY:
+//    case BufferedImage.TYPE_USHORT_GRAY:
+      break;
+    default:
+      throw new IllegalArgumentException("unsupported image type");
+    }
+    this.suggestedBufImgType = imageType;
+  }
+  
+  /**
+   * Get the image type. 
+   * @return The buffered image type used to render the image.
+   */
+  int getSuggestedBufImageType() {
+    return suggestedBufImgType;
+  }
+
+  public void setImageNode(VisADImageNode node) {
+    this.imagesNode = node;
+  }
+
+  public VisADImageNode getImageNode() {
+    return this.imagesNode;
+  }
+  
+  /**
+   * Turn on the reusing of frames
+   * @deprecated - use setReUseFrames(boolean reuse)
+   */
+  public void setReUseFrames() {
+    setReUseFrames(true);
+  }
+
+  public boolean getReUseFrames() {
+    return reUseFrames;
+  }
+
+  public void setSetSetOnReUseFrames(boolean ss) {
+    setSetOnReUseFrames = ss;
+  }
+
+  public boolean getSetSetOnReUseFrames() {
+    return setSetOnReUseFrames;
+  }
+
+  // logic to allow ShadowImageFunctionTypeJ3D to 'mark' missing frames
+  private VisADBranchGroup vbranch = null;
+
+  public void clearScene() {
+    vbranch = null;
+    super.clearScene();
+  }
+
+  void setVisADBranch(VisADBranchGroup branch) {
+    vbranch = branch;
+  }
+
+  void markMissingVisADBranch() {
+    if (vbranch != null) vbranch.scratchTime();
+  }
+  // end of logic to allow ShadowImageFunctionTypeJ3D to 'mark' missing frames
+
+  public BranchGroup doTransform() throws VisADException, RemoteException {
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return null;
+    }
+
+    DataDisplayLink link = Links[0];
+    ShadowTypeJ3D type = (ShadowTypeJ3D) link.getShadow();
+    boolean doByRef = false;
+    if (isByRefUsable(link, type) && ShadowType.byReference) {
+      doByRef = true;
+      type = new ShadowImageByRefFunctionTypeJ3D(link.getData().getType(), link, null, 
+                     ((ShadowFunctionOrSetType)type.getAdaptedShadowType()).getInheritedValues(),
+                          (ShadowFunctionOrSetType)type.getAdaptedShadowType(), type.getLevelOfDifficulty());
+    }
+
+    BranchGroup branch = null;
+    if ((lastByRef && doByRef) || (!lastByRef && !doByRef)) { 
+      branch = getBranch();
+    }
+    lastByRef = doByRef;
+
+    if (branch == null) {
+      branch = new BranchGroup();
+      branch.setCapability(BranchGroup.ALLOW_DETACH);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+      last_curve_size = -1;
+      last_zaxis_value = Float.NaN;
+      last_alpha_value = Float.NaN;
+      last_data_hash_code = -1; 
+      adjust_projection_seam = false; //27FEB2012: Projection Seam Change Bug Fix
+    }
+
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return null;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      addException(
+        new DisplayException("Data is null: DefaultRendererJ3D.doTransform"));
+    }
+    else {
+      // check MathType of non-null data, to make sure it is a single-band
+      // image or a sequence of single-band images
+      MathType mtype = link.getType();
+      if (!isImageType(mtype)) {
+        throw new BadMappingException("must be image or image sequence");
+      }
+      link.start_time = System.currentTimeMillis();
+      link.time_flag = false;
+      vbranch = null;
+      // transform data into a depiction under branch
+	long t1 = System.currentTimeMillis();
+      try {
+	if (type instanceof ShadowImageByRefFunctionTypeJ3D) { //GEOMETRY/COLORBYTE REUSE LOGIC Only for ByRef for Time being
+		if (checkAction()) { //This generally decides whether at all retransformation is required or not.
+	        	type.doTransform(branch, data, valueArray,
+                         	link.getDefaultValues(), this);
+		}
+	} else {	//Not byRef (ShadowImageFunctionTypeJ3D)
+		type.doTransform(branch, data, valueArray,
+                         link.getDefaultValues(), this);
+	}
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+	long t2 = System.currentTimeMillis();
+	//System.err.println("Time taken:" + (t2-t1));
+    }
+    link.clearData();
+
+    return branch;
+  }
+
+  public Object clone() {
+    return new ImageRendererJ3D();
+  }
+
+  /** run 'java visad.bom.ImageRendererJ3D len step'
+      to test animation behavior of ImageRendererJ3D
+      renders a loop of len at step ms per frame
+      then updates loop by deleting first time and adding a new last time */
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    int step = 1000;
+    int len = 3;
+    if (args.length > 0) {
+      try {
+        len = Integer.parseInt(args[0]);
+      }
+      catch(NumberFormatException e) {
+        len = 3;
+      }
+    }
+    if (len < 1) len = 1;
+    if (args.length > 1) {
+      try {
+        step = Integer.parseInt(args[1]);
+      }
+      catch(NumberFormatException e) {
+        step = 1000;
+      }
+    }
+    if (step < 1) step = 1;
+
+    // create a netCDF reader
+    Plain plain = new Plain();
+
+    // open a netCDF file containing an image sequence and adapt
+    // it to a Field Data object
+    Field raw_image_sequence = null;
+    try {
+      // raw_image_sequence = (Field) plain.open("images256x256.nc");
+      raw_image_sequence = (Field) plain.open("images.nc");
+    }
+    catch (IOException exc) {
+      String s = "To run this example, the images.nc file must be "
+        +"present in\nthe current directory."
+        +"You can obtain this file from:\n"
+        +"  ftp://www.ssec.wisc.edu/pub/visad-2.0/images.nc.Z";
+      System.out.println(s);
+      System.exit(0);
+    }
+
+    // just take first half of raw_image_sequence
+    FunctionType image_sequence_type =
+      (FunctionType) raw_image_sequence.getType();
+    Set raw_set = raw_image_sequence.getDomainSet();
+    float[][] raw_times = raw_set.getSamples();
+    int raw_len = raw_times[0].length;
+    if (raw_len != 4) {
+      throw new VisADException("wrong number of images in sequence");
+    }
+    float raw_span = (4.0f / 3.0f) * (raw_times[0][3] - raw_times[0][0]);
+
+    double[][] times = new double[1][len];
+    for (int i=0; i<len; i++) {
+      times[0][i] = raw_times[0][i % raw_len] + raw_span * (i / raw_len);
+    }
+    Gridded1DDoubleSet set =
+      new Gridded1DDoubleSet(raw_set.getType(), times, len);
+    Field image_sequence = new FieldImpl(image_sequence_type, set);
+    for (int i=0; i<len; i++) {
+      image_sequence.setSample(i, raw_image_sequence.getSample(i % raw_len));
+    }
+
+    // create a DataReference for image sequence
+    final DataReference image_ref = new DataReferenceImpl("image");
+    image_ref.setData(image_sequence);
+
+    // create a Display using Java3D
+    DisplayImpl display = new DisplayImplJ3D("image display");
+
+    // extract the type of image and use
+    // it to determine how images are displayed
+    FunctionType image_type =
+      (FunctionType) image_sequence_type.getRange();
+    RealTupleType domain_type = image_type.getDomain();
+    // map image coordinates to display coordinates
+    display.addMap(new ScalarMap((RealType) domain_type.getComponent(0),
+                                 Display.XAxis));
+    display.addMap(new ScalarMap((RealType) domain_type.getComponent(1),
+                                 Display.YAxis));
+    // map image brightness values to RGB (default is grey scale)
+    display.addMap(new ScalarMap((RealType) image_type.getRange(),
+                                 Display.RGB));
+    RealType hour_type =
+      (RealType) image_sequence_type.getDomain().getComponent(0);
+    ScalarMap animation_map = new ScalarMap(hour_type, Display.Animation);
+    display.addMap(animation_map);
+    AnimationControl animation_control =
+      (AnimationControl) animation_map.getControl();
+    animation_control.setStep(step);
+    animation_control.setOn(true);
+
+/*
+    // link the Display to image_ref
+    ImageRendererJ3D renderer = new ImageRendererJ3D();
+    display.addReferences(renderer, image_ref);
+    // display.addReference(image_ref);
+*/
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("ImageRendererJ3D test");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+
+    System.out.println("first animation sequence");
+    // link the Display to image_ref
+    ImageRendererJ3D renderer = new ImageRendererJ3D();
+    display.addReferences(renderer, image_ref);
+    // display.addReference(image_ref);
+
+    // wait 4 * len seconds
+    new Delay(len * 4000);
+
+    // substitute a new image sequence for the old one
+    for (int i=0; i<len; i++) {
+      times[0][i] =
+        raw_times[0][(i + 1) % raw_len] + raw_span * ((i + 1) / raw_len);
+    }
+    set = new Gridded1DDoubleSet(raw_set.getType(), times, len);
+    FieldImpl new_image_sequence = new FieldImpl(image_sequence_type, set);
+    for (int i=0; i<len; i++) {
+      new_image_sequence.setSample(i,
+        raw_image_sequence.getSample((i + 1) % raw_len));
+    }
+
+    System.out.println("second animation sequence");
+
+    // tell renderer to resue frames in its scene graph
+    renderer.setReUseFrames(true);
+    image_ref.setData(new_image_sequence);
+  }
+
+
+//GEOMETRY/COLORBYTE REUSE UTILITY METHODS (STARTS HERE)
+  public int getLastCurveSize() {
+	return last_curve_size;
+  }
+
+  public void setLastCurveSize(int csize) {
+	last_curve_size = csize;
+  }
+
+  public float getLastZAxisValue() {
+	return last_zaxis_value;
+  }
+  public void setLastZAxisValue(float zaxis_value) {
+	  last_zaxis_value = zaxis_value;
+  }
+
+  public float getLastAlphaValue() {
+	return last_alpha_value;
+  }
+
+  public void setLastAlphaValue(float alpha) {
+        last_alpha_value = alpha;
+  }
+
+  public long getLastDataHashCode() {
+	return last_data_hash_code;
+  } 
+
+  public void setLastDataHashCode(long lastdata_hashcode) {
+	last_data_hash_code = lastdata_hashcode;
+  }
+
+  //27FEB2012: Projection Seam Change Bug Fix (starts here)
+  public boolean getLastAdjustProjectionSeam() {
+        return adjust_projection_seam;
+  }
+
+  public void setLastAdjustProjectionSeam(boolean adjust) {
+         adjust_projection_seam = adjust;
+  }
+  //27FEB2012: Projection Seam Change Bug Fix (ends here)
+
+
+//GEOMETRY/COLORBYTE REUSE UTILITY METHODS (ENDS HERE)
+
+}
+
diff --git a/visad/bom/PickManipulationRendererJ2D.java b/visad/bom/PickManipulationRendererJ2D.java
new file mode 100644
index 0000000..cc70a54
--- /dev/null
+++ b/visad/bom/PickManipulationRendererJ2D.java
@@ -0,0 +1,423 @@
+//
+// PickManipulationRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.RemoteException;
+
+
+/**
+ * PickManipulationRendererJ2D is the VisAD class for picking
+ * data in 2D.
+ */
+// from  "Heftrich, Torsten, AVENTIS/DE" <Heftrich at crt.hoechst.com>
+public class PickManipulationRendererJ2D extends
+DirectManipulationRendererJ2D {
+
+  private int mouseModifiersMask = 0;
+  private int mouseModifiersValue = 0;
+
+  /**
+   * Default constructor
+   */
+  public PickManipulationRendererJ2D () {
+    this (0, 0);
+  }
+
+  /** 
+   * Construct a new PickManipulationRenderer using the mouseModifiers
+   * supplied.  mmm and mmv determine whehter SHIFT or CTRL keys are 
+   * required - This is needed since this is a greedy 
+   * DirectManipulationRenderer that will grab any right mouse click 
+   * (that intersects its 2-D sub-manifold).
+   * @param mmm  mouse modifiers mask.
+   * @param mmv  mouse modifiers value.
+   */
+  public PickManipulationRendererJ2D (int mmm, int mmv) {
+    super();
+    mouseModifiersMask = mmm;
+    mouseModifiersValue = mmv;
+  }
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+
+  private float[][] spatialValues = null;
+  /** index into spatialValues found by checkClose */
+  private int closeIndex = -1;
+
+  private int directManifoldDimension = -1;
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+
+  /** possible values for whyNotDirect */
+  private final static String notSimpleField =
+    "not simple field";
+  private final static String notSimpleTuple =
+    "not simple tuple";
+
+  private boolean stop = false;
+
+  /**
+   * Check if direct manipulation is possible.  
+   */
+  public void checkDirect() throws VisADException, RemoteException {
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+
+    ShadowType shadow = link.getShadow().getAdaptedShadowType();
+    MathType type = link.getType();
+
+    if (type instanceof FunctionType) {
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_FIELD) {
+        whyNotDirect = notSimpleField;
+        return;
+      }
+    }
+    else if (type instanceof SetType) {
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_FIELD) {
+        whyNotDirect = notSimpleField;
+        return;
+      }
+    }
+    else {
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_TUPLE) {
+        whyNotDirect = notSimpleTuple;
+        return;
+      }
+    }
+
+    setIsDirectManipulation(true);
+  }
+
+  private int getDirectManifoldDimension() {
+    return directManifoldDimension;
+  }
+
+  /**
+   * If direct manipulation is not possible, get the error message
+   * explaining why.
+   * @return error message. Will be null if no errors.
+   */
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  /**
+   * Add a point.  a no-op at this point.  
+   * @param x  point value.
+   */
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+// methods customized from DataRenderer:
+
+  /**
+   * Get the CoordinateSystem for the display side.
+   * @return  null for this DataRenderer
+   */
+  public CoordinateSystem getDisplayCoordinateSystem() {
+    return null;
+  }
+
+  /** 
+   * Set spatialValues from ShadowType.doTransform 
+   * @param spatial_values  X, Y, Z values
+   */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // these are X, Y, Z values
+    spatialValues = spatial_values;
+  }
+
+  /** 
+   * Check if ray intersects sub-manifold.  
+   * @param origin  x,y,z values of the ray
+   * @param direction x,y,z values of the ray?
+   * @return distance from the spatial values.
+   */
+  public synchronized float checkClose(double[] origin, double[]
+direction)
+{
+    int mouseModifiers = getLastMouseModifiers();
+    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
+      return Float.MAX_VALUE;
+    }
+
+    float distance = Float.MAX_VALUE;
+    if (spatialValues == null) return distance;
+    float o_x = (float) origin[0];
+    float o_y = (float) origin[1];
+    float o_z = (float) origin[2];
+    float d_x = (float) direction[0];
+    float d_y = (float) direction[1];
+    float d_z = (float) direction[2];
+ /*
+System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
+System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
+  */
+    for (int i=0; i<spatialValues[0].length; i++) {
+      float x = spatialValues[0][i] - o_x;
+      float y = spatialValues[1][i] - o_y;
+      float z = spatialValues[2][i] - o_z;
+      float dot = x * d_x + y * d_y + z * d_z;
+      x = x - dot * d_x;
+      y = y - dot * d_y;
+      z = z - dot * d_z;
+      float d = (float) Math.sqrt(x * x + y * y + z * z);
+      if (d < distance) {
+        distance = d;
+        closeIndex = i;
+      }
+/*
+System.out.println("spatialValues["+i+"] = " + spatialValues[0][i] + " "
++
+spatialValues[1][i] + " " + spatialValues[2][i] + " d = " + d);
+*/
+    }
+/*
+System.out.println("checkClose: distance = " + distance);
+*/
+    return distance;
+  }
+
+  /**
+   * Return the index of the closes point.
+   * @return index of closest point
+   */
+  public int getCloseIndex() {
+    return closeIndex;
+  }
+
+  /**
+   * Actual workhorse method of manipulation renderer. It's what
+   * gets called when the click is done.
+   * @param ray ray of point where click is.
+   * @param first  if this is the first time.
+   * @param mouseModifiers  modifiers used with the mouse.
+   */
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    if (ref == null) return;
+
+    if (first) {
+      try {
+        ref.setData(ref.getData());
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+    }
+  }
+
+  /** test PickManipulationRendererJ2D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    FunctionType f1d = new FunctionType(x, y);
+    RealTupleType xy = new RealTupleType(x, y);
+    TextType t = new TextType("text");
+    RealType s = RealType.getRealType("shape");
+
+    Data[] td = {new Real(x, 0.5),
+                 new Real(y, 0.5),
+                 new Text(t, "text")};
+    Tuple text = new Tuple(td);
+
+    Real[] sd = {new Real(x, -0.5),
+                 new Real(y, -0.5),
+                 new Real(s, 0.0)};
+    RealTuple shape = new RealTuple(sd);
+
+    Real real = new Real(x, -0.5);
+
+    Real[] rtd = {new Real(x, 0.5),
+                 new Real(y, -0.5)};
+    RealTuple real_tuple = new RealTuple(rtd);
+
+    FlatField field1d = new FlatField(f1d, new Linear1DSet(x, -1.0, -0.5, 64));
+    double[][] values = new double[1][64];
+    for (int i=0; i<64; i++) values[0][i] = 0.5 + Math.abs(i - 31.5) / 63.0;
+    field1d.setSamples(values);
+
+    Set set2d = new Linear2DSet(xy, 0.5, 1.0, 32, -0.25, 0.25, 32);
+
+    // construct Java2D display and mappings
+    DisplayImpl display = new DisplayImplJ2D("display");
+    DisplayRenderer dr = display.getDisplayRenderer();
+    dr.setPickThreshhold(0.2f); // allow sloppy picking
+
+    ScalarMap xmap = new ScalarMap(x, Display.XAxis);
+    display.addMap(xmap);
+    xmap.setRange(-1.0, 1.0);
+
+    ScalarMap ymap = new ScalarMap(y, Display.YAxis);
+    display.addMap(ymap);
+    ymap.setRange(-1.0, 1.0);
+
+    ScalarMap tmap = new ScalarMap(t, Display.Text);
+    display.addMap(tmap);
+    TextControl tcontrol = (TextControl) tmap.getControl();
+    tcontrol.setCenter(true);
+
+    ScalarMap smap = new ScalarMap(s, Display.Shape);
+    display.addMap(smap);
+    ShapeControl scontrol = (ShapeControl) smap.getControl();
+    scontrol.setShapeSet(new Integer1DSet(s, 1));
+    VisADLineArray cross = new VisADLineArray();
+    cross.coordinates = new float[]
+      {0.1f,  0.1f, 0.0f,    -0.1f, -0.1f, 0.0f,
+       0.1f, -0.1f, 0.0f,    -0.1f,  0.1f, 0.0f};
+    cross.vertexCount = cross.coordinates.length / 3;
+    scontrol.setShapes(new VisADGeometryArray[] {cross});
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl tref = new DataReferenceImpl("text");
+    tref.setData(text);
+    display.addReferences(new PickManipulationRendererJ2D(), tref);
+    CellImpl cellt = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("text picked");
+      }
+    };
+    cellt.addReference(tref);
+
+    DataReferenceImpl sref = new DataReferenceImpl("shape");
+    sref.setData(shape);
+    display.addReferences(new PickManipulationRendererJ2D(), sref);
+    CellImpl cells = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("shape picked");
+      }
+    };
+    cells.addReference(sref);
+
+    DataReferenceImpl rref = new DataReferenceImpl("Real");
+    rref.setData(real);
+    ConstantMap[] rmaps = {new ConstantMap(5.0, Display.PointSize)};
+    display.addReferences(new PickManipulationRendererJ2D(), rref, rmaps);
+    CellImpl cellr = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("Real picked");
+      }
+    };
+    cellr.addReference(rref);
+
+    DataReferenceImpl rtref = new DataReferenceImpl("RealTuple");
+    rtref.setData(real_tuple);
+    ConstantMap[] rtmaps = {new ConstantMap(5.0, Display.PointSize)};
+    display.addReferences(new PickManipulationRendererJ2D(), rtref, rtmaps);
+    CellImpl cellrt = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("RealTuple picked");
+      }
+    };
+    cellrt.addReference(rtref);
+
+    DataReferenceImpl field1dref = new DataReferenceImpl("field1d");
+    field1dref.setData(field1d);
+    final PickManipulationRendererJ2D pmr1d = new PickManipulationRendererJ2D();
+    display.addReferences(pmr1d, field1dref);
+    CellImpl cellfield1d = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else {
+          int i = pmr1d.getCloseIndex();
+          System.out.println("1-D Field picked, index = " + i);
+        }
+      }
+    };
+    cellfield1d.addReference(field1dref);
+
+    DataReferenceImpl setref = new DataReferenceImpl("set");
+    setref.setData(set2d);
+    final PickManipulationRendererJ2D pmrset = new PickManipulationRendererJ2D();
+    display.addReferences(pmrset, setref);
+    CellImpl cellset = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else {
+          int i = pmrset.getCloseIndex();
+          System.out.println("set picked, index = " + i);
+        }
+      }
+    };
+    cellset.addReference(setref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test PickManipulationRendererJ2D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
diff --git a/visad/bom/PickManipulationRendererJ3D.java b/visad/bom/PickManipulationRendererJ3D.java
new file mode 100644
index 0000000..c417738
--- /dev/null
+++ b/visad/bom/PickManipulationRendererJ3D.java
@@ -0,0 +1,459 @@
+//
+// PickManipulationRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.RemoteException;
+
+/**
+ * PickManipulationRendererJ3D is the VisAD class for picking
+ * data in 3D.
+ */
+public class PickManipulationRendererJ3D extends DirectManipulationRendererJ3D {
+
+  private int mouseModifiersMask = 0;
+  private int mouseModifiersValue = 0;
+
+  /**
+   * Default constructor
+   */
+  public PickManipulationRendererJ3D () {
+    this (0, 0);
+  }
+
+  /** 
+   * Construct a new PickManipulationRenderer using the mouseModifiers
+   * supplied.  mmm and mmv determine whehter SHIFT or CTRL keys are 
+   * required - This is needed since this is a greedy 
+   * DirectManipulationRenderer that will grab any right mouse click 
+   * (that intersects its 2-D sub-manifold).
+   * @param mmm  mouse modifiers mask.
+   * @param mmv  mouse modifiers value.
+   */
+  public PickManipulationRendererJ3D (int mmm, int mmv) {
+    super();
+    mouseModifiersMask = mmm;
+    mouseModifiersValue = mmv;
+  }
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+
+  private float[][] spatialValues = null;
+  /** index into spatialValues found by checkClose */
+  private int closeIndex = -1;
+
+  private int directManifoldDimension = -1;
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+
+  /** possible values for whyNotDirect */
+  private final static String notSimpleField =
+    "not simple field";
+  private final static String notSimpleTuple =
+    "not simple tuple";
+
+  private boolean stop = false;
+
+  /**
+   * Check if direct manipulation is possible.  
+   */
+  public void checkDirect() throws VisADException, RemoteException {
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+
+    ShadowType shadow = link.getShadow().getAdaptedShadowType();
+    MathType type = link.getType();
+
+    if (type instanceof FunctionType) {
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_FIELD) {
+        whyNotDirect = notSimpleField;
+        return;
+      }
+    }
+    else if (type instanceof SetType) {
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_FIELD) {
+        whyNotDirect = notSimpleField;
+        return;
+      }
+    }
+    else {
+      if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_TUPLE) {
+        whyNotDirect = notSimpleTuple;
+        return;
+      }
+    }
+
+    setIsDirectManipulation(true);
+  }
+
+  private int getDirectManifoldDimension() {
+    return directManifoldDimension;
+  }
+
+  /**
+   * If direct manipulation is not possible, get the error message
+   * explaining why.
+   * @return error message. Will be null if no errors.
+   */
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  /**
+   * Add a point.  a no-op at this point.  
+   * @param x  point value.
+   */
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+// methods customized from DataRenderer:
+
+  /**
+   * Get the CoordinateSystem for the display side.
+   * @return  null for this DataRenderer
+   */
+  public CoordinateSystem getDisplayCoordinateSystem() {
+    return null;
+  }
+
+  /** 
+   * Set spatialValues from ShadowType.doTransform 
+   * @param spatial_values  X, Y, Z values
+   */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // these are X, Y, Z values
+    spatialValues = spatial_values;
+  }
+
+  /** 
+   * Check if ray intersects sub-manifold.  
+   * @param origin  x,y,z values of the ray
+   * @param direction x,y,z values of the ray?
+   * @return distance from the spatial values.
+   */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    int mouseModifiers = getLastMouseModifiers();
+    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
+      return Float.MAX_VALUE;
+    }
+
+    float distance = Float.MAX_VALUE;
+    if (spatialValues == null) return distance;
+    float o_x = (float) origin[0];
+    float o_y = (float) origin[1];
+    float o_z = (float) origin[2];
+    float d_x = (float) direction[0];
+    float d_y = (float) direction[1];
+    float d_z = (float) direction[2];
+/*
+System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
+System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
+*/
+    for (int i=0; i<spatialValues[0].length; i++) {
+      float x = spatialValues[0][i] - o_x;
+      float y = spatialValues[1][i] - o_y;
+      float z = spatialValues[2][i] - o_z;
+      float dot = x * d_x + y * d_y + z * d_z;
+      x = x - dot * d_x;
+      y = y - dot * d_y;
+      z = z - dot * d_z;
+      float d = (float) Math.sqrt(x * x + y * y + z * z);
+      if (d < distance) {
+        distance = d;
+        closeIndex = i;
+      }
+/*
+System.out.println("spatialValues["+i+"] = " + spatialValues[0][i] + " " +
+spatialValues[1][i] + " " + spatialValues[2][i] + " d = " + d);
+*/
+    }
+/*
+System.out.println("checkClose: distance = " + distance);
+*/
+    return distance;
+  }
+
+  /**
+   * Return the index of the closes point.
+   * @return index of closest point
+   */
+  public int getCloseIndex() {
+    return closeIndex;
+  }
+
+  /**
+   * Actual workhorse method of manipulation renderer. It's what
+   * gets called when the click is done.
+   * @param ray ray of point where click is.
+   * @param first  if this is the first time.
+   * @param mouseModifiers  modifiers used with the mouse.
+   */
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    if (ref == null) return;
+
+    if (first) {
+      try {
+        ref.setData(ref.getData());
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+    }
+  }
+
+  public Object clone() {
+    return new PickManipulationRendererJ3D(mouseModifiersMask,
+                                           mouseModifiersValue);
+  }
+
+  /** test PickManipulationRendererJ3D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealType z = RealType.getRealType("z");
+    FunctionType f1d = new FunctionType(x, y);
+    RealTupleType xy = new RealTupleType(x, y);
+    FunctionType f2d = new FunctionType(xy, z);
+    TextType t = new TextType("text");
+    RealType s = RealType.getRealType("shape");
+
+    Data[] td = {new Real(x, 0.5),
+                 new Real(y, 0.5),
+                 new Real(z, 0.5),
+                 new Text(t, "text")};
+    Tuple text = new Tuple(td);
+
+    Real[] sd = {new Real(x, -0.5),
+                 new Real(y, -0.5),
+                 new Real(z, -0.5),
+                 new Real(s, 0.0)};
+    RealTuple shape = new RealTuple(sd);
+
+    Real real = new Real(x, -0.5);
+
+    Real[] rtd = {new Real(x, 0.5),
+                 new Real(y, -0.5),
+                 new Real(z, 0.0)};
+    RealTuple real_tuple = new RealTuple(rtd);
+
+    FlatField field1d = new FlatField(f1d, new Linear1DSet(x, -1.0, -0.5, 64));
+    double[][] values = new double[1][64];
+    for (int i=0; i<64; i++) values[0][i] = 0.5 + Math.abs(i - 31.5) / 63.0;
+    field1d.setSamples(values);
+
+    Set set2d = new Linear2DSet(xy, 0.5, 1.0, 32, -0.25, 0.25, 32);
+    FlatField field2d = new FlatField(f2d, set2d);
+    values = new double[1][1024];
+    int k = 0;
+    for (int i=0; i<32; i++) {
+      for (int j=0; j<32; j++) {
+        values[0][k++] =
+          Math.sqrt((i-15.5) * (i-15.5) + (j-15.5) * (j-15.5)) / 32.0;
+      }
+    }
+    field2d.setSamples(values);
+
+    // construct Java3D display and mappings
+    DisplayImpl display = new DisplayImplJ3D("display");
+    DisplayRenderer dr = display.getDisplayRenderer();
+    dr.setPickThreshhold(0.2f); // allow sloppy picking
+
+    ScalarMap xmap = new ScalarMap(x, Display.XAxis);
+    display.addMap(xmap);
+    xmap.setRange(-1.0, 1.0);
+
+    ScalarMap ymap = new ScalarMap(y, Display.YAxis);
+    display.addMap(ymap);
+    ymap.setRange(-1.0, 1.0);
+
+    ScalarMap zmap = new ScalarMap(z, Display.ZAxis);
+    display.addMap(zmap);
+    zmap.setRange(-1.0, 1.0);
+
+    ScalarMap tmap = new ScalarMap(t, Display.Text);
+    display.addMap(tmap);
+    TextControl tcontrol = (TextControl) tmap.getControl();
+    tcontrol.setCenter(true);
+
+    ScalarMap smap = new ScalarMap(s, Display.Shape);
+    display.addMap(smap);
+    ShapeControl scontrol = (ShapeControl) smap.getControl();
+    scontrol.setShapeSet(new Integer1DSet(s, 1));
+    VisADLineArray cross = new VisADLineArray();
+    cross.coordinates = new float[]
+      {0.1f,  0.1f, 0.0f,    -0.1f, -0.1f, 0.0f,
+       0.1f, -0.1f, 0.0f,    -0.1f,  0.1f, 0.0f};
+    cross.vertexCount = cross.coordinates.length / 3;
+    scontrol.setShapes(new VisADGeometryArray[] {cross});
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    DataReferenceImpl tref = new DataReferenceImpl("text");
+    tref.setData(text);
+    display.addReferences(new PickManipulationRendererJ3D(), tref);
+    CellImpl cellt = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("text picked");
+      }
+    };
+    cellt.addReference(tref);
+
+    DataReferenceImpl sref = new DataReferenceImpl("shape");
+    sref.setData(shape);
+    display.addReferences(new PickManipulationRendererJ3D(), sref);
+    CellImpl cells = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("shape picked");
+      }
+    };
+    cells.addReference(sref);
+
+    DataReferenceImpl rref = new DataReferenceImpl("Real");
+    rref.setData(real);
+    ConstantMap[] rmaps = {new ConstantMap(5.0, Display.PointSize)};
+    display.addReferences(new PickManipulationRendererJ3D(), rref, rmaps);
+    CellImpl cellr = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("Real picked");
+      }
+    };
+    cellr.addReference(rref);
+
+    DataReferenceImpl rtref = new DataReferenceImpl("RealTuple");
+    rtref.setData(real_tuple);
+    ConstantMap[] rtmaps = {new ConstantMap(5.0, Display.PointSize)};
+    display.addReferences(new PickManipulationRendererJ3D(), rtref, rtmaps);
+    CellImpl cellrt = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else System.out.println("RealTuple picked");
+      }
+    };
+    cellrt.addReference(rtref);
+
+    DataReferenceImpl field1dref = new DataReferenceImpl("field1d");
+    field1dref.setData(field1d);
+    final PickManipulationRendererJ3D pmr1d = new PickManipulationRendererJ3D();
+    display.addReferences(pmr1d, field1dref);
+    CellImpl cellfield1d = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else {
+          int i = pmr1d.getCloseIndex();
+          System.out.println("1-D Field picked, index = " + i);
+        }
+      }
+    };
+    cellfield1d.addReference(field1dref);
+
+    DataReferenceImpl field2dref = new DataReferenceImpl("field2d");
+    field2dref.setData(field2d);
+    final PickManipulationRendererJ3D pmr2d = new PickManipulationRendererJ3D();
+    display.addReferences(pmr2d, field2dref);
+    CellImpl cellfield2d = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else {
+          int i = pmr2d.getCloseIndex();
+          System.out.println("2-D Field picked, index = " + i);
+        }
+      }
+    };
+    cellfield2d.addReference(field2dref);
+
+    DataReferenceImpl setref = new DataReferenceImpl("set");
+    setref.setData(set2d);
+    ConstantMap[] smaps = {new ConstantMap(-1.0, Display.ZAxis)};
+    final PickManipulationRendererJ3D pmrset = new PickManipulationRendererJ3D();
+    display.addReferences(pmrset, setref, smaps);
+    CellImpl cellset = new CellImpl() {
+      private boolean first = true;
+      public void doAction() throws VisADException, RemoteException {
+        if (first) first = false;
+        else {
+          int i = pmrset.getCloseIndex();
+          System.out.println("set picked, index = " + i);
+        }
+      }
+    };
+    cellset.addReference(setref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test PickManipulationRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/visad/bom/PointManipulationRendererJ3D.java b/visad/bom/PointManipulationRendererJ3D.java
new file mode 100644
index 0000000..4784b59
--- /dev/null
+++ b/visad/bom/PointManipulationRendererJ3D.java
@@ -0,0 +1,490 @@
+//
+// PointManipulationRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import java.awt.event.InputEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Group;
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.BadDirectManipulationException;
+import visad.CellImpl;
+import visad.CoordinateSystem;
+import visad.DataDisplayLink;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.DisplayRealType;
+import visad.DisplayTupleType;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer2DSet;
+import visad.Real;
+import visad.RealTuple;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.ScalarType;
+import visad.Unit;
+import visad.VisADException;
+import visad.VisADRay;
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+   PointManipulationRendererJ3D is the VisAD class for direct
+   manipulation of single points
+*/
+public class PointManipulationRendererJ3D extends DirectManipulationRendererJ3D {
+
+  private RealType x = null;
+  private RealType y = null;
+  private RealTupleType xy = null;
+
+  private int mouseModifiersMask = 0;
+  private int mouseModifiersValue = 0;
+
+  private BranchGroup branch = null;
+  private BranchGroup group = null;
+
+  /** this DirectManipulationRenderer is quite different - it does not
+      render its data, but only place values into its DataReference
+      on right mouse button press;
+      it uses xarg and yarg to determine spatial ScalarMaps */
+  public PointManipulationRendererJ3D (RealType xarg, RealType yarg) {
+    // Don't match any modifier combinations (mmm = 0)
+    // Don't test for any - ie: accept (and steal) all combinations (mmv = 0)
+    this(xarg, yarg, 0, 0);
+  }
+
+	/**
+	 * xarg and yarg determine spatial ScalarMaps; mmm and mmv determine whether
+	 * SHIFT or CTRL keys are required - this is needed since this is a greedy
+	 * DirectManipulationRenderer that will grab any right mouse click (that
+	 * intersects its 2-D sub-manifold)
+	 * 
+	 * @param mmm
+	 *            - "Mouse Modifier Mask", matches the modifiers we want plus
+	 *            all that we don't want
+	 * @param mmv
+	 *            - "Mouse Modifier Value", equals the subset of mask that we
+	 *            want to match
+	 */
+  
+  public PointManipulationRendererJ3D (RealType xarg, RealType yarg, int mmm, int mmv) {
+    super();
+    x = xarg;
+    y = yarg;
+    mouseModifiersMask = mmm;
+    mouseModifiersValue = mmv;
+  }
+
+  /** don't render - just return BranchGroup for scene graph to
+      render rectangle into */
+  public synchronized BranchGroup doTransform()
+         throws VisADException, RemoteException {
+    branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(Group.ALLOW_CHILDREN_READ);
+    branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+    // check type and maps for valid direct manipulation
+    if (!getIsDirectManipulation()) {
+      throw new BadDirectManipulationException(getWhyNotDirect() +
+        ": DirectManipulationRendererJ3D.doTransform");
+    }
+    setBranch(branch);
+    return branch;
+  }
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+
+  private transient ScalarMap xmap = null;
+  private transient ScalarMap ymap = null;
+
+  float[] default_values;
+
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  /** dimension of direct manipulation
+      (always 2 for PointManipulationRendererJ3D) */
+  private int directManifoldDimension = 2;
+  /** spatial DisplayTupleType other than
+      DisplaySpatialCartesianTuple */
+  private DisplayTupleType tuple;
+  private CoordinateSystem tuplecs;
+
+  private int xindex = -1;
+  private int yindex = -1;
+  private int otherindex = -1;
+  private float othervalue;
+  private float[][] first_x;
+
+  /** possible values for whyNotDirect */
+  private final static String xandyNotMatch =
+    "x and y spatial domains don't match";
+  private final static String xandyNotSpatial =
+    "x and y must be mapped to spatial";
+
+  public void checkDirect() throws VisADException, RemoteException {
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    default_values = link.getDefaultValues();
+
+    xmap = null;
+    ymap = null;
+    Vector scalar_map_vector = display.getMapVector();
+    Enumeration en = scalar_map_vector.elements();
+    while (en.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) en.nextElement();
+      ScalarType real = map.getScalar();
+      if (real.equals(x)) {
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType t = dreal.getTuple();
+        if (t != null &&
+            (t.equals(Display.DisplaySpatialCartesianTuple) ||
+             (t.getCoordinateSystem() != null &&
+              t.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          xmap = map;
+          xindex = dreal.getTupleIndex();
+          if (tuple == null) {
+            tuple = t;
+          }
+          else if (!t.equals(tuple)) {
+            whyNotDirect = xandyNotMatch;
+            return;
+          }
+        }
+      }
+      if (real.equals(y)) {
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType t = dreal.getTuple();
+        if (t != null &&
+            (t.equals(Display.DisplaySpatialCartesianTuple) ||
+             (t.getCoordinateSystem() != null &&
+              t.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          ymap = map;
+          yindex = dreal.getTupleIndex();
+          if (tuple == null) {
+            tuple = t;
+          }
+          else if (!t.equals(tuple)) {
+            whyNotDirect = xandyNotMatch;
+            return;
+          }
+        }
+      }
+    }
+
+    if (xmap == null || ymap == null) {
+      whyNotDirect = xandyNotSpatial;
+      return;
+    }
+
+    xy = new RealTupleType(x, y);
+
+    // get default value for other component of tuple
+    otherindex = 3 - (xindex + yindex);
+    DisplayRealType dreal = (DisplayRealType) tuple.getComponent(otherindex);
+    int index = getDisplay().getDisplayScalarIndex(dreal);
+    othervalue = (index > 0) ? default_values[index] :
+                               (float) dreal.getDefaultValue();
+
+    if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+      tuple = null;
+      tuplecs = null;
+    }
+    else {
+      tuplecs = tuple.getCoordinateSystem();
+    }
+
+    directManifoldDimension = 2;
+    setIsDirectManipulation(true);
+  }
+
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+// methods customized from DataRenderer:
+
+  public CoordinateSystem getDisplayCoordinateSystem() {
+    return tuplecs;
+  }
+
+  /** set spatialValues from ShadowType.doTransform */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // do nothing
+  }
+
+  /** check if ray intersects sub-manifold
+      @return float, 0 - hit, Float.MAX_VALUE - no hit
+  */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    int mouseModifiers = getLastMouseModifiers();
+    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
+      return Float.MAX_VALUE;
+    }
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r == r) {
+        return 0.0f;
+      }
+      else {
+        return Float.MAX_VALUE;
+      }
+    }
+    catch (VisADException ex) {
+      return Float.MAX_VALUE;
+    }
+  }
+
+  public void stop_direct() {
+  }
+
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    if (ref == null) return;
+
+    if (!first) return;
+
+    double[] origin = ray.position;
+    double[] direction = ray.vector;
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r != r) {
+        if (group != null) group.detach();
+        return;
+      }
+      float[][] xx = {{(float) (origin[0] + r * direction[0])},
+                      {(float) (origin[1] + r * direction[1])},
+                      {(float) (origin[2] + r * direction[2])}};
+      if (tuple != null) xx = tuplecs.fromReference(xx);
+
+      first_x = xx;
+
+      Vector vect = new Vector();
+      f[0] = xx[xindex][0];
+      d = xmap.inverseScaleValues(f);
+
+      // WLH 31 Aug 2000
+      Real rr = new Real(x, d[0]);
+      Unit overrideUnit = xmap.getOverrideUnit();
+      Unit rtunit = x.getDefaultUnit();
+      // units not part of Time string
+      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+          !RealType.Time.equals(x)) {
+        double dval =  overrideUnit.toThis((double) d[0], rtunit);
+        rr = new Real(x, dval, overrideUnit);
+      }   
+      String valueString = rr.toValueString();
+
+      vect.addElement(x.getName() + " = " + valueString);
+      f[0] = xx[yindex][0];
+      d = ymap.inverseScaleValues(f);
+
+      // WLH 31 Aug 2000
+      rr = new Real(y, d[0]);
+      overrideUnit = ymap.getOverrideUnit();
+      rtunit = y.getDefaultUnit();
+      // units not part of Time string
+      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+          !RealType.Time.equals(y)) {
+        double dval =  overrideUnit.toThis((double) d[0], rtunit);
+        rr = new Real(y, dval, overrideUnit);
+      }
+      valueString = rr.toValueString();
+
+      valueString = new Real(y, d[0]).toValueString();
+      vect.addElement(y.getName() + " = " + valueString);
+      // getDisplayRenderer().setCursorStringVector(vect);
+
+
+      double[] dd = new double[2];
+      f[0] = first_x[xindex][0];
+      d = xmap.inverseScaleValues(f);
+      dd[0] = d[0];
+      f[0] = first_x[yindex][0];
+      d = ymap.inverseScaleValues(f);
+      dd[1] = d[0];
+
+      RealTuple rt = new RealTuple(xy, dd);
+      ref.setData(rt);
+
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  public Object clone() {
+    return new PointManipulationRendererJ3D(x, y, mouseModifiersMask,
+                                            mouseModifiersValue);
+  }
+
+  private static final int N = 64;
+
+  /** test PointManipulationRendererJ3D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealTupleType xy = new RealTupleType(x, y);
+
+    RealType c = RealType.getRealType("c");
+    FunctionType ft = new FunctionType(xy, c);
+
+    // construct Java3D display and mappings
+    DisplayImpl display = new DisplayImplJ3D("display1");
+    if (args.length == 0 || args[0].equals("z")) {
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.YAxis));
+    }
+    else if (args[0].equals("x")) {
+      display.addMap(new ScalarMap(x, Display.YAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("y")) {
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("radius")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    else if (args[0].equals("lat")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else if (args[0].equals("lon")) {
+      display.addMap(new ScalarMap(x, Display.Latitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    display.addMap(new ScalarMap(c, Display.RGB));
+
+    Integer2DSet fset = new Integer2DSet(xy, N, N);
+    FlatField field = new FlatField(ft, fset);
+    float[][] values = new float[1][N * N];
+    int k = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        values[0][k++] = (i - N / 2) * (j - N / 2);
+      }
+    }
+    field.setSamples(values);
+    DataReferenceImpl field_ref = new DataReferenceImpl("field");
+    field_ref.setData(field);
+    display.addReference(field_ref);
+
+    RealTuple dummy_rt = new RealTuple(xy, new double[] {Double.NaN, Double.NaN});
+    final DataReferenceImpl ref = new DataReferenceImpl("rt");
+    ref.setData(dummy_rt);
+    int m = (args.length > 1) ? InputEvent.CTRL_MASK : 0;
+    display.addReferences(new PointManipulationRendererJ3D(x, y, m, m), ref);
+
+    CellImpl cell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        RealTuple rt = (RealTuple) ref.getData();
+        double dx = ((Real) rt.getComponent(0)).getValue();
+        double dy = ((Real) rt.getComponent(1)).getValue();
+        if (dx == dx && dy == dy) {
+          System.out.println("point (" + dx + ", " + dy + ")");
+        }
+      }
+    };
+    cell.addReference(ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test PointManipulationRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/visad/bom/Radar2DCoordinateSystem.java b/visad/bom/Radar2DCoordinateSystem.java
new file mode 100644
index 0000000..c478c4e
--- /dev/null
+++ b/visad/bom/Radar2DCoordinateSystem.java
@@ -0,0 +1,345 @@
+//
+// Radar2DCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.georef.*;
+
+/**
+   Radar2DCoordinateSystem is the VisAD CoordinateSystem class
+   for radar (range, azimuth) with an Earth (Latitude, Longitude) Reference,
+   and with azimuth in degrees and range in meters.<P>
+*/
+public class Radar2DCoordinateSystem extends NavigatedCoordinateSystem {
+
+  private static Unit[] coordinate_system_units =
+    {CommonUnit.meter, CommonUnit.degree};
+
+  private float centlat, centlon;
+  private float radlow, radres, azlow, azres;
+  private double coscentlat, lonscale, latscale;
+
+  /**
+   * construct a CoordinateSystem for (range, azimuth)
+   * relative to an Earth (Latitude, Longitude) Reference;
+   * this constructor supplies units = {CommonUnit.meter, CommonUnit.degree}
+   * to the super constructor, in order to ensure Unit
+   * compatibility with its use of trigonometric functions.  Values
+   * of range and azimuth are in terms of absolute values of range and azimuth
+   * away from the center point where range is in meters and azimuth = 0 at
+   * north.
+   *
+   * @param  clat        latitude of center point
+   * @param  clon        longitude of center point
+   *
+   * @throws  VisADException   necessary VisAD object couldn't be created.
+   */
+  public Radar2DCoordinateSystem(float clat, float clon)
+    throws VisADException {
+       this(RealTupleType.LatitudeLongitudeTuple, clat, clon,
+            0.0f, 1.0f, 0.0f, 1.0f);
+  }
+
+  /**
+   * construct a CoordinateSystem for (range, azimuth)
+   * relative to an Earth (Latitude, Longitude) Reference;
+   * this constructor supplies units = {CommonUnit.meter, CommonUnit.degree}
+   * to the super constructor, in order to ensure Unit
+   * compatibility with its use of trigonometric functions.  Values
+   * of range and azimuth are in terms of multiples of range and azimuth
+   * resolutions away from the low value (radl, azl). The absolute
+   * range is (radl + range_value * radr) and the absolute azimuth
+   * is (azl + azimuth_value * azr) with azimuth = 0 at north.  This
+   * allows the use of Integer2DSets for the values assuming linear
+   * spacing of range and azimuth.
+   *
+   * @param  clat        latitude of center point
+   * @param  clon        longitude of center point
+   * @param  radl        distance from center point for first possible echo
+   *                     (meters)
+   * @param  radr        distance between subsequent radials (meters)
+   * @param  azl         starting azimuth (degrees)
+   * @param  azr         resolution of degrees between azimuths.
+   *
+   * @throws  VisADException   necessary VisAD object couldn't be created.
+   */
+  public Radar2DCoordinateSystem(float clat, float clon,
+                               float radl, float radr, float azl, float azr)
+    throws VisADException {
+       this(RealTupleType.LatitudeLongitudeTuple, clat, clon,
+            radl, radr, azl, azr);
+  }
+
+
+  /**
+   * construct a CoordinateSystem for (range, azimuth)
+   * relative to an Earth (Latitude, Longitude) Reference;
+   * this constructor supplies units = {CommonUnit.meter, CommonUnit.degree}
+   * to the super constructor, in order to ensure Unit
+   * compatibility with its use of trigonometric functions.  Values
+   * of range and azimuth are in terms of multiples of range and azimuth
+   * resolutions away from the low value (radl, azl). The absolute
+   * range is (radl + range_value * radr) and the absolute azimuth
+   * is (azl + azimuth_value * azr) with azimuth = 0 at north.  This
+   * allows the use of Integer2DSets for the values assuming linear
+   * spacing of range and azimuth.
+   *
+   * @param  reference   reference RealTupleType
+   *                     (should be RealTupleType.LatitudeLongitudeTuple)
+   * @param  clat        latitude of center point
+   * @param  clon        longitude of center point
+   * @param  radl        distance from center point for first possible echo
+   *                     (meters)
+   * @param  radr        distance between subsequent radials (meters)
+   * @param  azl         starting azimuth (degrees)
+   * @param  azr         resolution of degrees between azimuths.
+   *
+   * @throws  VisADException   necessary VisAD object couldn't be created.
+   */
+  public Radar2DCoordinateSystem(RealTupleType reference, float clat, float clon,
+                               float radl, float radr, float azl, float azr)
+         throws VisADException {
+    super(reference, coordinate_system_units);
+    centlat = clat;
+    centlon = clon;
+    radlow = radl;
+    radres = radr;
+    azlow = azl;
+    azres = azr;
+    coscentlat = Math.cos(Data.DEGREES_TO_RADIANS * centlat);
+    lonscale = ShadowType.METERS_PER_DEGREE * coscentlat;
+    latscale = ShadowType.METERS_PER_DEGREE;
+// System.out.println("lonscale = " + lonscale + " latscale = " + latscale);
+  }
+
+  /**
+   * Convert from range/azimuth to latitude/longitude.
+   * Values input are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution).
+   *
+   * @param  tuples  range/azimuth values
+   * @return  lat/lon values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("Radar2DCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("toReference double len = " + len);
+    //double[][] value = new double[2][len];
+    double[][] value = tuples;
+    for (int i=0; i<len ;i++) {
+      double rad = radlow + radres * tuples[0][i];
+      if (rad < 0.0) {
+        value[0][i] = Double.NaN;
+        value[1][i] = Double.NaN;
+// System.out.println(i + " missing  rad = " + rad);
+      }
+      else {
+        double az = azlow + azres * tuples[1][i];
+        double cosaz = Math.cos(Data.DEGREES_TO_RADIANS * az);
+        double sinaz = Math.sin(Data.DEGREES_TO_RADIANS * az);
+        // assume azimuth = 0 at north, then clockwise
+        value[0][i] = centlat + cosaz * rad / latscale;
+        value[1][i] = centlon + sinaz * rad / lonscale;
+/*
+System.out.println(tuples[0][i] + " " + tuples[1][i] + " -> " +
+                   value[0][i] + " " + value[1][i] +
+                   " az, rad = " + az + " " + rad);
+*/
+      }
+    }
+    return value;
+  }
+
+  /**
+   * Convert from latitude/longitude to range/azimuth.
+   * Returned values are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution).
+   *
+   * @param  tuples  lat/lon values
+   * @return  range/azimuth values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("Radar2DCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("fromReference double len = " + len);
+    // double[][] value = new double[2][len];
+    double[][] value = tuples;
+    for (int i=0; i<len ;i++) {
+      double slat = (tuples[0][i] - centlat) * latscale;
+      double slon = (tuples[1][i] - centlon) * lonscale;
+      value[0][i] = (Math.sqrt(slat * slat + slon * slon) - radlow) / radres;
+      value[1][i] =
+        (Data.RADIANS_TO_DEGREES * Math.atan2(slon, slat) - azlow) / azres;
+      if (value[1][i] < 0.0) value[1][i] += 360.0;
+    }
+    return value;
+  }
+
+  /**
+   * Convert from range/azimuth to latitude/longitude.
+   * Values input are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution).
+   *
+   * @param  tuples  range/azimuth values
+   * @return  lat/lon values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("Radar2DCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("toReference float len = " + len);
+    // float[][] value = new float[2][len];
+    float[][] value = tuples;
+    for (int i=0; i<len ;i++) {
+      double rad = radlow + radres * tuples[0][i];
+      if (rad < 0.0) {
+        value[0][i] = Float.NaN;
+        value[1][i] = Float.NaN;
+      }
+      else {
+        double az = azlow + azres * tuples[1][i];
+        double cosaz = Math.cos(Data.DEGREES_TO_RADIANS * az);
+        double sinaz = Math.sin(Data.DEGREES_TO_RADIANS * az);
+        // assume azimuth = 0 at north, then clockwise
+        value[0][i] = (float) (centlat + cosaz * rad / latscale);
+        value[1][i] = (float) (centlon + sinaz * rad / lonscale);
+      }
+    }
+    return value;
+  }
+
+  /**
+   * Convert from latitude/longitude to range/azimuth.
+   * Returned values are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution).
+   *
+   * @param  tuples  lat/lon values
+   * @return  range/azimuth values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("Radar2DCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("fromReference float len = " + len);
+    //float[][] value = new float[2][len];
+    float[][] value = tuples;
+    for (int i=0; i<len ;i++) {
+      double slat = (tuples[0][i] - centlat) * latscale;
+      double slon = (tuples[1][i] - centlon) * lonscale;
+      value[0][i] = (float)
+        ((Math.sqrt(slat * slat + slon * slon) - radlow) / radres);
+      value[1][i] = (float)
+        ((Data.RADIANS_TO_DEGREES * Math.atan2(slon, slat) - azlow) / azres);
+      if (value[1][i] < 0.0) value[1][i] += 360.0f;
+    }
+    return value;
+  }
+
+  /**
+   * Check to see if this is a Radar2DCoordinateSystem
+   *
+   * @param cs  object to compare
+   * @return true if cs is an instance of Radar2DCoordinateSystem
+   */
+  public boolean equals(Object cs) {
+    return (cs instanceof Radar2DCoordinateSystem);
+  }
+
+  /**
+   * Return the azimuth parameters
+   *
+   * @return  array[] (len == 2) where array[0] = azl, array[1] = azr
+   */
+  public float[] getAzimuthParameters()
+  {
+      return new float[] {azlow, azres};
+  }
+
+  /**
+   * Return the range parameters
+   *
+   * @return  array[] (len == 2) where array[0] = radl, array[1] = radr
+   */
+  public float[] getRangeParameters()
+  {
+      return new float[] {radlow, radres};
+  }
+
+  /**
+   * Get center point in lat/lon
+   *
+   * @return latlon array  where array[0] = lat, array[1] = lon
+   */
+  public float[] getCenterPoint()
+  {
+      return new float[] {centlat, centlon};
+  }
+
+  /**
+   * Return String representation of this Radar2DCoordinateSystem
+   *
+   * @return string listing params
+   */
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("Radar 2D CoordinateSystem: \n");
+    buf.append("  Center point = Lat: ");
+    buf.append(PlotText.shortString(centlat));
+    buf.append(" Lon: ");
+    buf.append(PlotText.shortString(centlon));
+    buf.append("\n");
+    buf.append("  Range params = ");
+    buf.append(PlotText.shortString(radlow));
+    buf.append(",");
+    buf.append(PlotText.shortString(radres));
+    buf.append("\n");
+    buf.append("  Azimuth params = ");
+    buf.append(PlotText.shortString(azlow));
+    buf.append(",");
+    buf.append(PlotText.shortString(azres));
+    return buf.toString();
+  }
+
+}
diff --git a/visad/bom/Radar3DCoordinateSystem.java b/visad/bom/Radar3DCoordinateSystem.java
new file mode 100644
index 0000000..d63793c
--- /dev/null
+++ b/visad/bom/Radar3DCoordinateSystem.java
@@ -0,0 +1,569 @@
+//
+// Radar3DCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.georef.*;
+
+/**
+   Radar3DCoordinateSystem is the VisAD CoordinateSystem class
+   for radar (range, azimuth, elevation_angle) with an Earth
+   (Latitude, Longitude, Altitude) Reference, and with
+   azimuth and elevation angle in degrees, and range in meters.<P>
+*/
+public class Radar3DCoordinateSystem extends NavigatedCoordinateSystem {
+
+  public static final double EARTH_RADIUS =
+    ShadowType.METERS_PER_DEGREE * Data.RADIANS_TO_DEGREES;
+
+  private static Unit[] coordinate_system_units =
+    {CommonUnit.meter, CommonUnit.degree, CommonUnit.degree};
+
+  private float centlat, centlon, centalt;
+  private float radlow, radres, azlow, azres, elevlow, elevres;
+  private double coscentlat, lonscale, latscale;
+
+  /**
+   * construct a CoordinateSystem for (range, azimuth, elevation_angle)
+   * relative to an Earth (Latitude, Longitude, Altitude) Reference;
+   * this constructor supplies units =
+   * {CommonUnit.meter, CommonUnit.degree, CommonUnit.degree}
+   * to the super constructor, in order to ensure Unit
+   * compatibility with its use of trigonometric functions.  Values
+   * of range, azimuth, and elevation angle are in terms of absolute values of
+   * range, azimuth and elevation angle (tilt) from the center point where
+   * range is in meters, azimuth = 0 at north and elevation angle is in
+   * degrees.
+   *
+   * @param  clat        latitude of center point
+   * @param  clon        longitude of center point
+   * @param  calt        altitude (meters) of center point
+   *
+   * @throws  VisADException   necessary VisAD object couldn't be created.
+   */
+  public Radar3DCoordinateSystem(float clat, float clon, float calt)
+         throws VisADException {
+    this(new RealTupleType(
+             RealType.Latitude, RealType.Longitude, RealType.Altitude),
+         clat, clon, calt, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
+  }
+
+  /**
+   * construct a CoordinateSystem for (range, azimuth, elevation_angle)
+   * relative to an Earth (Latitude, Longitude, Altitude) Reference;
+   * this constructor supplies units =
+   * {CommonUnit.meter, CommonUnit.degree, CommonUnit.degree}
+   * to the super constructor, in order to ensure Unit
+   * compatibility with its use of trigonometric functions.  Values
+   * of range, azimuth and elevation angle are in terms of multiples of range,
+   * azimuth and elevation angle resolutions away from the low values
+   * (radl, azl, elevl). The absolute range is (radl + range_value * radr)
+   * the absolute azimuth is (azl + azimuth_value * azr) with azimuth = 0 at
+   * north and the absolute elevation angle is
+   * (elevl + elevation_angle_value * elevr).  This allows the use of
+   * Integer3DSets for the values assuming linear spacing of range, azimuth
+   * and elevation angle.
+   *
+   * @param  clat        latitude of center point
+   * @param  clon        longitude of center point
+   * @param  radl        distance from center point for first possible echo
+   *                     (meters)
+   * @param  radr        distance between subsequent range increments (meters)
+   * @param  azl         starting azimuth (degrees)
+   * @param  azr         resolution of degrees between azimuths.
+   * @param  elevl       starting elevation angle (tilt) (degrees)
+   * @param  elevr       resolution of degrees between elevation angles.
+   *
+   * @throws  VisADException   necessary VisAD object couldn't be created.
+   */
+  public Radar3DCoordinateSystem(float clat, float clon, float calt,
+                                 float radl, float radr, float azl, float azr,
+                                 float elevl, float elevr)
+         throws VisADException {
+    this(new RealTupleType(
+             RealType.Latitude, RealType.Longitude, RealType.Altitude),
+         clat, clon, calt, radl, radr, azl, azr, elevl, elevr);
+  }
+
+  /**
+   * construct a CoordinateSystem for (range, azimuth, elevation_angle)
+   * relative to an Earth (Latitude, Longitude, Altitude) Reference;
+   * this constructor supplies units =
+   * {CommonUnit.meter, CommonUnit.degree, CommonUnit.degree}
+   * to the super constructor, in order to ensure Unit
+   * compatibility with its use of trigonometric functions.  Values
+   * of range, azimuth and elevation angle are in terms of multiples of range,
+   * azimuth and elevation angle resolutions away from the low values
+   * (radl, azl, elevl). The absolute range is (radl + range_value * radr)
+   * the absolute azimuth is (azl + azimuth_value * azr) with azimuth = 0 at
+   * north and the absolute elevation angle is
+   * (elevl + elevation_angle_value*elevr). This allows the use of
+   * Integer3DSets for the values assuming linear spacing of range, azimuth
+   * and elevation angle.
+   *
+   * @deprecated use constructors with station altitude to get a true
+   *             altitude above sea level.
+   * @param  reference   reference RealTupleType
+   *                     (should be Latitude, Longitude, Altitude)
+   * @param  clat        latitude of center point
+   * @param  clon        longitude of center point
+   * @param  radl        distance from center point for first possible echo
+   *                     (meters)
+   * @param  radr        distance between subsequent range values (meters)
+   * @param  azl         starting azimuth (degrees)
+   * @param  azr         resolution of degrees between azimuths.
+   * @param  elevl       starting elevation angle (tilt) (degrees)
+   * @param  elevr       resolution of degrees between elevation angles.
+   *
+   * @throws  VisADException   necessary VisAD object couldn't be created.
+   */
+  public Radar3DCoordinateSystem(RealTupleType reference, float clat, float clon,
+                                 float radl, float radr, float azl, float azr,
+                                 float elevl, float elevr)
+         throws VisADException {
+    this(reference, clat, clon, 0.0f, radl, radr, azl, azr, elevl, elevr);
+  }
+
+  /**
+   * construct a CoordinateSystem for (range, azimuth, elevation_angle)
+   * relative to an Earth (Latitude, Longitude, Altitude) Reference;
+   * this constructor supplies units =
+   * {CommonUnit.meter, CommonUnit.degree, CommonUnit.degree}
+   * to the super constructor, in order to ensure Unit
+   * compatibility with its use of trigonometric functions.  Values
+   * of range, azimuth and elevation angle are in terms of multiples of range,
+   * azimuth and elevation angle resolutions away from the low values
+   * (radl, azl, elevl). The absolute range is (radl + range_value * radr)
+   * the absolute azimuth is (azl + azimuth_value * azr) with azimuth = 0 at
+   * north and the absolute elevation angle is
+   * (elevl + elevation_angle_value*elevr). This allows the use of
+   * Integer3DSets for the values assuming linear spacing of range, azimuth
+   * and elevation angle.
+   *
+   * @param  reference   reference RealTupleType
+   *                     (should be Latitude, Longitude, Altitude)
+   * @param  clat        latitude of center point
+   * @param  clon        longitude of center point
+   * @param  calt        altitude (meters) of center point
+   * @param  radl        distance from center point for first possible echo
+   *                     (meters)
+   * @param  radr        distance between subsequent range values (meters)
+   * @param  azl         starting azimuth (degrees)
+   * @param  azr         resolution of degrees between azimuths.
+   * @param  elevl       starting elevation angle (tilt) (degrees)
+   * @param  elevr       resolution of degrees between elevation angles.
+   *
+   * @throws  VisADException   necessary VisAD object couldn't be created.
+   */
+  public Radar3DCoordinateSystem(RealTupleType reference,
+                                 float clat, float clon, float calt,
+                                 float radl, float radr, float azl, float azr,
+                                 float elevl, float elevr)
+         throws VisADException {
+    super(reference, coordinate_system_units);
+    centlat = clat;
+    centlon = clon;
+    centalt = calt;
+    radlow = radl;
+    radres = radr;
+    azlow = azl;
+    azres = azr;
+    elevlow = elevl;
+    elevres = elevr;
+    coscentlat = Math.cos(Data.DEGREES_TO_RADIANS * centlat);
+    lonscale = ShadowType.METERS_PER_DEGREE * coscentlat;
+    latscale = ShadowType.METERS_PER_DEGREE;
+// System.out.println("lonscale = " + lonscale + " latscale = " + latscale);
+  }
+
+  /**
+   * Convert from range/azimuth/elevation to latitude/longitude/altitude.
+   * Values input are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution).  Returned Altitude
+   * is in meters above the station elevation if this was constructed
+   * without the calt parameter.
+   *
+   * @param  tuples  range/azimuth/elevation values
+   * @return  lat/lon/altitude values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("Radar3DCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("toReference double len = " + len);
+    // double[][] value = new double[3][len];
+    double[][] value = tuples;
+/* WLH 7 April 2000
+    for (int i=0; i<len ;i++) {
+      double rad = radlow + radres * tuples[0][i];
+      if (rad < 0.0) {
+        value[0][i] = Double.NaN;
+        value[1][i] = Double.NaN;
+        value[2][i] = Double.NaN;
+      }
+      else {
+        double az = azlow + azres * tuples[1][i];
+        double cosaz = Math.cos(Data.DEGREES_TO_RADIANS * az);
+        double sinaz = Math.sin(Data.DEGREES_TO_RADIANS * az);
+        // assume azimuth = 0 at north, then clockwise
+        double elev = elevlow + elevres * tuples[2][i];
+        double coselev = Math.cos(Data.DEGREES_TO_RADIANS * elev);
+        double sinelev = Math.sin(Data.DEGREES_TO_RADIANS * elev);
+        double rp = Math.sqrt(EARTH_RADIUS * EARTH_RADIUS + rad * rad +
+                              2.0 * sinelev * EARTH_RADIUS * rad);
+
+        value[2][i] = rp - EARTH_RADIUS; // altitude
+
+        double angle = Math.asin(coselev * rad / rp); // really sin(elev+90)
+        double radp = EARTH_RADIUS * angle;
+        value[0][i] = centlat + cosaz * radp / latscale;
+        value[1][i] = centlon + sinaz * radp / lonscale;
+      }
+    }
+*/
+    double er = EARTH_RADIUS + centalt;
+    for (int i=0; i<len ;i++) {
+      double rad = radlow + radres * tuples[0][i];
+      if (rad < 0.0) {
+        value[0][i] = Double.NaN;
+        value[1][i] = Double.NaN;
+        value[2][i] = Double.NaN;
+      }
+      else {
+        double az = azlow + azres * tuples[1][i];
+        double cosaz = Math.cos(Data.DEGREES_TO_RADIANS * az);
+        double sinaz = Math.sin(Data.DEGREES_TO_RADIANS * az);
+        // assume azimuth = 0 at north, then clockwise
+        double elev = elevlow + elevres * tuples[2][i];
+        double coselev = Math.cos(Data.DEGREES_TO_RADIANS * elev);
+        double sinelev = Math.sin(Data.DEGREES_TO_RADIANS * elev);
+        double rp = Math.sqrt(er * er + rad * rad +
+                              2.0 * sinelev * er * rad);
+
+        value[2][i] = rp - er + centalt; // altitude
+
+        double angle = Math.asin(coselev * rad / rp); // really sin(elev+90)
+        double radp = er * angle;
+        value[0][i] = centlat + cosaz * radp / latscale;
+        value[1][i] = centlon + sinaz * radp / lonscale;
+      }
+    }
+    return value;
+  }
+
+  /**
+   * Convert from latitude/longitude/altitude to range/azimuth/elevation.
+   * Values returned are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution)
+   *
+   * @param  tuples  lat/lon/altitude values
+   * @return  range/azimuth/elevation values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("Radar3DCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("fromReference double len = " + len);
+    // double[][] value = new double[3][len];
+    double[][] value = tuples;
+/* WLH 7 April 2000
+    for (int i=0; i<len ;i++) {
+      double slat = (tuples[0][i] - centlat) * latscale;
+      double slon = (tuples[1][i] - centlon) * lonscale;
+      double radp = Math.sqrt(slat * slat + slon * slon);
+      double angle = radp / EARTH_RADIUS;
+
+      double rp = EARTH_RADIUS + tuples[2][i];
+
+      double rad = Math.sqrt(EARTH_RADIUS * EARTH_RADIUS + rp * rp -
+                             2.0 * rp * EARTH_RADIUS * Math.cos(angle));
+      double elev =
+        Data.RADIANS_TO_DEGREES * Math.acos(Math.sin(angle) * rp / rad);
+      value[0][i] = (rad - radlow) / radres;
+      value[1][i] =
+        (Data.RADIANS_TO_DEGREES * Math.atan2(slon, slat) - azlow) / azres;
+      value[2][i] = (elev - elevlow) / elevres;
+      if (value[1][i] < 0.0) value[1][i] += 360.0;
+    }
+*/
+    double er = EARTH_RADIUS + centalt;
+    for (int i=0; i<len ;i++) {
+      double slat = (tuples[0][i] - centlat) * latscale;
+      double slon = (tuples[1][i] - centlon) * lonscale;
+      double radp = Math.sqrt(slat * slat + slon * slon);
+      double angle = radp / er;
+
+      double alt_over = tuples[2][i] - centalt;
+      double rp = er + alt_over;
+
+      double rad = Math.sqrt(er * er + rp * rp -
+                             2.0 * rp * er * Math.cos(angle));
+      double elev =
+        Data.RADIANS_TO_DEGREES * Math.acos(Math.sin(angle) * rp / rad);
+      if (alt_over < 0.0f) elev = -elev;
+      value[0][i] = (rad - radlow) / radres;
+      value[1][i] =
+        (Data.RADIANS_TO_DEGREES * Math.atan2(slon, slat) - azlow) / azres;
+      value[2][i] = (elev - elevlow) / elevres;
+      if (value[1][i] < 0.0) value[1][i] += 360.0;
+    }
+
+    return value;
+  }
+
+  /**
+   * Convert from range/azimuth/elevation to latitude/longitude/altitude.
+   * Values input are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution).  Returned Altitude
+   * is in meters above the station elevation if this was constructed
+   * without the calt parameter.
+   *
+   * @param  tuples  range/azimuth/elevation values
+   * @return  lat/lon/altitude values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("Radar3DCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("toReference float len = " + len);
+    // float[][] value = new float[3][len];
+    float[][] value = tuples;
+/* WLH 7 April 2000
+    for (int i=0; i<len ;i++) {
+      double rad = radlow + radres * tuples[0][i];
+      if (rad < 0.0) {
+        value[0][i] = Float.NaN;
+        value[1][i] = Float.NaN;
+        value[2][i] = Float.NaN;
+      }
+      else {
+        double az = azlow + azres * tuples[1][i];
+        double cosaz = Math.cos(Data.DEGREES_TO_RADIANS * az);
+        double sinaz = Math.sin(Data.DEGREES_TO_RADIANS * az);
+        // assume azimuth = 0 at north, then clockwise
+        double elev = elevlow + elevres * tuples[2][i];
+        double coselev = Math.cos(Data.DEGREES_TO_RADIANS * elev);
+        double sinelev = Math.sin(Data.DEGREES_TO_RADIANS * elev);
+        double rp = Math.sqrt(EARTH_RADIUS * EARTH_RADIUS + rad * rad +
+                              2.0 * sinelev * EARTH_RADIUS * rad);
+
+        value[2][i] = (float) (rp - EARTH_RADIUS); // altitude
+
+        double angle = Math.asin(coselev * rad / rp); // really sin(elev+90)
+        double radp = EARTH_RADIUS * angle;
+        value[0][i] = (float) (centlat + cosaz * radp / latscale);
+        value[1][i] = (float) (centlon + sinaz * radp / lonscale);
+      }
+    }
+*/
+    double er = EARTH_RADIUS + centalt;
+    for (int i=0; i<len ;i++) {
+      double rad = radlow + radres * tuples[0][i];
+      if (rad < 0.0) {
+        value[0][i] = Float.NaN;
+        value[1][i] = Float.NaN;
+        value[2][i] = Float.NaN;
+      }
+      else {
+        double az = azlow + azres * tuples[1][i];
+        double cosaz = Math.cos(Data.DEGREES_TO_RADIANS * az);
+        double sinaz = Math.sin(Data.DEGREES_TO_RADIANS * az);
+        // assume azimuth = 0 at north, then clockwise
+        double elev = elevlow + elevres * tuples[2][i];
+        double coselev = Math.cos(Data.DEGREES_TO_RADIANS * elev);
+        double sinelev = Math.sin(Data.DEGREES_TO_RADIANS * elev);
+        double rp = Math.sqrt(er * er + rad * rad +
+                              2.0 * sinelev * er * rad);
+
+        value[2][i] = (float) (rp - er) + centalt; // altitude
+
+        double angle = Math.asin(coselev * rad / rp); // really sin(elev+90)
+        double radp = er * angle;
+        value[0][i] = (float) (centlat + cosaz * radp / latscale);
+        value[1][i] = (float) (centlon + sinaz * radp / lonscale);
+      }
+    }
+    return value;
+  }
+
+  /**
+   * Convert from latitude/longitude/altitude to range/azimuth/elevation.
+   * Values returned are in terms of multiples of the value resolution
+   * from the low value (ex: low + value * resolution)
+   *
+   * @param  tuples  lat/lon/altitude values
+   * @return  range/azimuth/elevation values
+   *
+   * @throws VisADException  tuples is null or wrong dimension
+   */
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 3) {
+      throw new CoordinateSystemException("Radar3DCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+// System.out.println("fromReference float len = " + len);
+    // float[][] value = new float[3][len];
+    float[][] value = tuples;
+/* WLH 7 April 2000
+    for (int i=0; i<len ;i++) {
+      double slat = (tuples[0][i] - centlat) * latscale;
+      double slon = (tuples[1][i] - centlon) * lonscale;
+      double radp = Math.sqrt(slat * slat + slon * slon);
+      double angle = radp / EARTH_RADIUS;
+
+      double rp = EARTH_RADIUS + tuples[2][i];
+
+      double rad = Math.sqrt(EARTH_RADIUS * EARTH_RADIUS + rp * rp -
+                             2.0 * rp * EARTH_RADIUS * Math.cos(angle));
+      double elev =
+        Data.RADIANS_TO_DEGREES * Math.acos(Math.sin(angle) * rp / rad);
+      value[0][i] = (float) ((rad - radlow) / radres);
+      value[1][i] = (float)
+        ((Data.RADIANS_TO_DEGREES * Math.atan2(slon, slat) - azlow) / azres);
+      value[2][i] = (float) ((elev - elevlow) / elevres);
+      if (value[1][i] < 0.0f) value[1][i] += 360.0f;
+    }
+*/
+    double er = EARTH_RADIUS + centalt;
+    for (int i=0; i<len ;i++) {
+      double slat = (tuples[0][i] - centlat) * latscale;
+      double slon = (tuples[1][i] - centlon) * lonscale;
+      double radp = Math.sqrt(slat * slat + slon * slon);
+      double angle = radp / er;
+
+      double alt_over = tuples[2][i] - centalt;
+      double rp = er + alt_over;
+
+      double rad = Math.sqrt(er * er + rp * rp -
+                             2.0 * rp * er * Math.cos(angle));
+      double elev =
+        Data.RADIANS_TO_DEGREES * Math.acos(Math.sin(angle) * rp / rad);
+      if (alt_over < 0.0f) elev = -elev;
+      value[0][i] = (float) ((rad - radlow) / radres);
+      value[1][i] = (float)
+        ((Data.RADIANS_TO_DEGREES * Math.atan2(slon, slat) - azlow) / azres);
+      value[2][i] = (float) ((elev - elevlow) / elevres);
+      if (value[1][i] < 0.0f) value[1][i] += 360.0f;
+    }
+    return value;
+  }
+
+  /**
+   * Check to see if this is a Radar3DCoordinateSystem
+   *
+   * @param cs  object to compare
+   * @return true if cs is an instance of Radar3DCoordinateSystem
+   */
+  public boolean equals(Object cs) {
+    return (cs instanceof Radar3DCoordinateSystem);
+  }
+
+  /**
+   * Return the elevation angle parameters
+   *
+   * @return  array[] (len == 2) where array[0] = elevl, array[1] = elevr
+   */
+  public float[] getElevationParameters()
+  {
+      return new float[] {elevlow, elevres};
+  }
+
+  /**
+   * Return the azimuth parameters
+   *
+   * @return  array[] (len == 2) where array[0] = azl, array[1] = azr
+   */
+  public float[] getAzimuthParameters()
+  {
+      return new float[] {azlow, azres};
+  }
+
+  /**
+   * Return the range parameters
+   *
+   * @return  array[] (len == 2) where array[0] = radl, array[1] = radr
+   */
+  public float[] getRangeParameters()
+  {
+      return new float[] {radlow, radres};
+  }
+
+  /**
+   * Get center point in lat/lon/alt
+   *
+   * @return latlon array  where array[0] = lat, array[1] = lon, array[2] = alt
+   */
+  public float[] getCenterPoint()
+  {
+      return new float[] {centlat, centlon, centalt};
+  }
+
+  /**
+   * Return String representation of this Radar3DCoordinateSystem
+   *
+   * @return string listing params
+   */
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("Radar 3D CoordinateSystem: \n");
+    buf.append("  Center point = Lat: ");
+    buf.append(PlotText.shortString(centlat));
+    buf.append(" Lon: ");
+    buf.append(PlotText.shortString(centlon));
+    buf.append(" Alt: ");
+    buf.append(PlotText.shortString(centalt));
+    buf.append("\n");
+    buf.append("  Range params = ");
+    buf.append(PlotText.shortString(radlow));
+    buf.append(",");
+    buf.append(PlotText.shortString(radres));
+    buf.append("\n");
+    buf.append("  Azimuth params = ");
+    buf.append(PlotText.shortString(azlow));
+    buf.append(",");
+    buf.append(PlotText.shortString(azres));
+    buf.append("\n");
+    buf.append("  Elevation params = ");
+    buf.append(PlotText.shortString(elevlow));
+    buf.append(",");
+    buf.append(PlotText.shortString(elevres));
+    return buf.toString();
+  }
+}
diff --git a/visad/bom/RadarAdapter.java b/visad/bom/RadarAdapter.java
new file mode 100644
index 0000000..2383b4d
--- /dev/null
+++ b/visad/bom/RadarAdapter.java
@@ -0,0 +1,329 @@
+
+//
+// RadarAdapter.java
+//
+
+/*
+This sofware is part of the Australian Integrated Forecast System (AIFS)
+Copyright (C) 2011 Bureau of Meteorology
+*/
+
+package visad.bom;
+
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+import java.rmi.RemoteException;
+
+import javax.swing.JFrame;
+
+import visad.CommonUnit;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.GraphicsModeControl;
+import visad.Gridded3DSet;
+import visad.Integer2DSet;
+import visad.Integer3DSet;
+import visad.QuickSort;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.VisADException;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+ * RadarAdapter
+ * 
+ * @author - James Kelly : J.Kelly at bom.gov.au - Bill Hibbard (mainly, while
+ *         working at BOM August 1999)
+ * 
+ * 
+ */
+
+public class RadarAdapter {
+	
+  public RadarFile rf;
+  public class PolarData {
+    public double azimuth;
+    public double range;
+  }
+
+  public PolarData[] polar;
+  public int numVectors;
+
+  FlatField radar;
+
+  /**
+   * @deprecated
+   */
+  public RadarAdapter(float centlat, float centlon, String radarSource,
+                      boolean d3d)
+         throws IOException, VisADException {
+    this(centlat, centlon, 0.0f, radarSource, d3d);
+  }
+
+  public RadarAdapter(float centlat, float centlon, float centalt,
+                      String radarSource, boolean d3d)
+         throws IOException, VisADException {
+    try {
+      rf = new RadarFile(radarSource);
+      // buildFlatField(rf);
+    } catch (IOException e) {throw new
+          VisADException("Problem with Radar file: " + e);
+    }
+    System.out.println("Radar Adapter : dtTime = " + rf.dtTime);
+    int naz = rf.pbdataArray.length;
+    float[] azs = new float[naz];
+    int nrad = 0;
+    //
+    // create an array "azs" containing all the azimuth values
+    // example:
+  	//    azs[0] = 61 degrees    = radlow +   0
+		//    azs[1] = 183 degrees   = radlow + 122
+    //    azs[2] = 261 degrees   = radlow + 200
+    //    azs[3] = 262 degrees   = radlow + 201
+    //    azs[4] = 262 degrees   = radlow + 202
+    //
+    for (int i=0; i<naz; i++) {
+      int n = rf.pbdataArray[i].bdata.length;
+      if (n > nrad) nrad = n;
+      azs[i] = (float) rf.pbdataArray[i].azimuth;
+      // System.out.println("i, azs = " + i + " " + azs[i]);
+    }
+    int[] sortToOld = QuickSort.sort(azs);
+    //    radlow : distance from radar of 1st "echo" (radial), in metres eg 4000m
+    float radlow = rf.startrng;
+    //    radres : distance between subsequent radials eg 500m
+    float radres = rf.rngres;
+    float azlow = azs[0];
+    float azres = rf.azimuthres;
+    //
+	//
+	// azs above is the "old" array
+	// prepare to create a "new" array containing a continuous set of
+    // azimuth values between azs[0] and azs[naz-1] degrees
+	// newnaz is the number of continuous azimuth values
+    //
+
+    int newnaz = 1 + (int) ((azs[naz-1] - azs[0]) / azres);
+    int[] newToOld = new int[newnaz];
+    // for (int i=0; i<newnaz; i++) newToOld[i] = -1;
+    for (int i=0; i<newnaz; i++) newToOld[i] = 0;
+	//
+	// The old array above (azs) has only got "azimuth" values for
+	// radials with non-null data
+	// We want to fill in the array with these nulls
+    // So using the example above, newToOld will contain:
+	//    newToOld[0] = 0
+	//    newToOld[1] = -1 ......
+	//    newToOld[122] = 1
+	//    newToOld[123] = -1 ......
+    //    newToOld[200] = 2
+    //    newToOld[201] = 3
+    //    newToOld[202] = 4
+    //    newToOld[203] = -1.......
+	//
+    for (int i=0; i<naz; i++) {
+      int k = (int) ((azs[i] - azs[0]) / azres);
+      if (k < 0) k = 0;
+      if (k > (newnaz-1)) k = newnaz-1;
+      newToOld[k] = sortToOld[i];
+      // System.out.print("k, newToOld = " + k + " " + newToOld[k] + " ");
+    }
+    // System.out.println(" ");
+
+    Radar2DCoordinateSystem rcs2d = null;
+    Radar3DCoordinateSystem rcs3d = null;
+    float elevlow = 0.5f; // degrees
+    float elevres = 0.1f; // degrees
+    int nelev = 1;
+    if (d3d) {
+      //ref = new RealTupleType
+      //        (RealType.Latitude, RealType.Longitude, RealType.Altitude);
+      rcs3d = new Radar3DCoordinateSystem(centlat, centlon, centalt,
+                   radlow, radres, azlow, azres, elevlow, elevres);
+    }
+    else {
+      //ref = new RealTupleType (RealType.Latitude, RealType.Longitude);
+      rcs2d = new Radar2DCoordinateSystem(centlat, centlon,
+                             radlow, radres, azlow, azres);
+    }
+
+    RealType azimuth =
+      RealType.getRealType("azimuth", CommonUnit.degree, null);
+    RealType range = RealType.getRealType("range", CommonUnit.meter, null);
+    // WLH 14 Oct 99
+    // RealType elevation =
+    //   RealType.getRealType("elevation", CommonUnit.meter, null);
+    RealType elevation =
+      RealType.getRealType("elevation", CommonUnit.degree, null);
+
+    RealTupleType radaz = null;
+    if (d3d) {
+      RealType[] domain_components = { range, azimuth, elevation};
+      radaz = new RealTupleType(domain_components, rcs3d, null);
+    }
+    else {
+      RealType[] domain_components = {range, azimuth};
+      radaz = new RealTupleType(domain_components, rcs2d, null);
+    }
+
+    RealType reflection = RealType.getRealType("reflection");
+
+    FunctionType radar_image = new FunctionType(radaz, reflection);
+	  //
+    //    newnaz = 203 using example above
+	  //
+    // System.out.println("newnaz = " + newnaz + "  nrad = " + nrad);
+
+    // float[][] samples = new float[2][nrad * naz];
+	//
+	// For convenience, the "values" array has all the
+	// > 0 data values at the start of the array (indexed by k)
+	// while null data values are stored together at the end of the array
+	//
+
+    // WLH - 21 Sept 99
+    int bignaz = newnaz;
+    if (newnaz == 360) bignaz = 361;
+
+    float[][] values = new float[1][nrad * bignaz]; // WLH - 21 Sept 99
+    int m = 0;
+    for (int i=0; i<newnaz; i++) {
+      int k = newToOld[i];
+      if (k >= 0) {
+	    // there is data for this azimuth
+        byte[] bd = rf.pbdataArray[newToOld[i]].bdata;
+        for (int j=0; j<nrad; j++) {
+          values[0][m] = bd[j];
+   		  // if (bd[j] > 0) System.out.println("i, j = " + i + " " + j + " values = " + values[0][m] );
+          m++;
+        }
+      }
+      else { // k < 0
+	    //
+	    // fill the rest of the array with NaNs
+	    //
+        for (int j=0; j<nrad; j++) {
+          values[0][m] = Float.NaN;
+          m++;
+        }
+      }
+    }
+
+    // WLH - 21 Sept 99
+    if (newnaz == 360) {
+      int offset = nrad * newnaz;
+      for (int j=0; j<nrad; j++) values[0][offset + j] = values[0][j];
+    }
+
+    if (d3d) {
+      if (nelev == 1) {
+        float[][] samples = new float[3][nrad * bignaz];
+        int k = 0;
+        for (int j=0; j<bignaz; j++) {
+          for (int i=0; i<nrad; i++) {
+            samples[0][k] = i;
+            samples[1][k] = j;
+            samples[2][k] = 0;
+            k++;
+          }
+        }
+        Gridded3DSet set = new Gridded3DSet(radaz, samples, nrad, bignaz);
+        radar = new FlatField(radar_image, set);
+      }
+      else {
+        Integer3DSet set = new Integer3DSet(radaz, nrad, bignaz, nelev);
+        radar = new FlatField(radar_image, set);
+      }
+    }
+    else {
+      // Gridded2DSet set = new Gridded2DSet(radaz, samples, nrad, naz);
+      Integer2DSet set = new Integer2DSet(radaz, nrad, bignaz); // WLH - 21 Sept 99
+      radar = new FlatField(radar_image, set);
+    }
+    radar.setSamples(values);
+
+  }
+
+  public FlatField getData() {
+    return radar;
+  }
+
+
+  public static void main(String[] args) throws VisADException, RemoteException {
+    String radarSource = "radar.dat";
+    RadarAdapter ra = null;
+    try {
+        ra = new RadarAdapter(-34.9f, 138.5f, 4.0f, radarSource, false);
+
+    } catch (Exception e) {
+      System.err.println("Caught Exception for \"" + radarSource + "\": " +
+                           e);
+      System.exit(1);
+    }
+
+    FlatField radar = ra.getData();
+    FunctionType radar_image = (FunctionType) radar.getType();
+    RealTupleType radaz = radar_image.getDomain();
+    RealType reflection = (RealType) radar_image.getRange();
+
+    DisplayImplJ3D display = new DisplayImplJ3D("radar");
+    ScalarMap lonmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    //lonmap.setRange(130.0, 150.0);
+    display.addMap(lonmap);
+    ScalarMap latmap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    display.addMap(latmap);
+    display.addMap(new ScalarMap(RealType.Altitude, Display.ZAxis));
+    //latmap.setRange(-45.0, -25.0);
+    //ScalarMap reflectionmap = new ScalarMap(reflection, Display.ZAxis);
+    //display.addMap(reflectionmap);
+    //reflectionmap.setRange(0, 6);
+    // display.addMap(new ScalarMap(reflection, Display.RGB));
+    ScalarMap rgbMap = new ScalarMap(reflection, Display.RGB);
+    //ScalarMap rgbMap = new ScalarMap(reflection, Display.RGBA);
+	  // rgbMap.setRange(0.,6.);
+	display.addMap(rgbMap);
+
+
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    // mode.setTextureEnable(false); WLH - 22 Sept 99
+
+    DataReference ref = new DataReferenceImpl("radar_ref");
+    ref.setData(radar);
+    display.addReference(ref);
+
+    JFrame frame = new JFrame("VisAD BOM radar image");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    frame.getContentPane().add(display.getComponent());
+    int WIDTH = 500;
+    int HEIGHT = 600;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+    /*
+    LabeledColorWidget lw = new LabeledColorWidget(rgbMap);
+    JFrame widgetFrame = new JFrame("VisAD Color Widget");
+    widgetFrame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+    widgetFrame.getContentPane().add(lw);
+    widgetFrame.setSize(lw.getPreferredSize());
+    widgetFrame.setVisible(true);
+    */
+  }
+}
+
diff --git a/visad/bom/RadarDisplay.java b/visad/bom/RadarDisplay.java
new file mode 100644
index 0000000..c9c7196
--- /dev/null
+++ b/visad/bom/RadarDisplay.java
@@ -0,0 +1,275 @@
+
+//
+// RadarDisplay.java
+//
+
+/*
+This sofware is part of the Australian Integrated Forecast System (AIFS)
+Copyright (C) 2011 Bureau of Meteorology
+*/
+
+package visad.bom;
+
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Toolkit;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.rmi.RemoteException;
+
+import javax.swing.JFrame;
+
+import visad.ConstantMap;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.GraphicsModeControl;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.VisADException;
+import visad.data.mcidas.BaseMapAdapter;
+import visad.java3d.DisplayImplJ3D;
+import visad.util.LabeledColorWidget;
+
+/**
+ * RadarDisplay
+ * 
+ * @author - James Kelly : J.Kelly at bom.gov.au based on RadarAdapter.java,
+ *         largely written by Bill Hibbard
+ * 
+ * 
+ */
+
+public class RadarDisplay {
+
+	/*
+     * Set the color map.
+     */
+    private static void
+    setColorMap(ScalarMap colorMap, float min, float max)
+			throws VisADException, RemoteException
+    {
+
+            float[][] table = new float[3][256];
+            for (int i=0; i<256; i++) {
+                    if (i <= 15)
+                    {
+                        table[0][i] = 0.0f;
+                        table[1][i] = 0.0f;
+                        table[2][i] = 0.0f;
+										}
+                    else if (i > 15 && i <= 31)
+                    {
+                        table[0][i] = 0.0f;
+                        table[1][i] = 240.0f/255.0f;
+                        table[2][i] = 240.0f/255.0f;
+                    }
+                    else if (i > 31 && i <= 47)
+										{
+                       table[0][i] = 0.0f;
+                        table[1][i] = 144.0f/255.0f;
+                        table[2][i] = 144.0f/255.0f;
+                    }
+                    else if (i > 47 && i <= 63)
+                    {
+                        table[0][i] = 128.0f/255.0f;
+												table[1][i] = 224.0f/255.0f;
+                        table[2][i] = 80.0f/255.0f;
+                    }
+                    else if (i > 63 && i <= 79)
+                    {
+                        table[0][i] = 100.0f/255.0f;
+                        table[1][i] = 184.0f/255.0f;
+                        table[2][i] = 64.0f/255.0f;
+                    }
+                    else if (i > 79 && i <= 95)
+                    {
+                        table[0][i] = 72.0f/255.0f;
+                        table[1][i] = 144.0f/255.0f;
+                        table[2][i] = 48.0f/255.0f;
+                    }
+                    else if (i > 95 && i <= 111)
+                    {
+                        table[0][i] = 44.0f/255.0f;
+                        table[1][i] = 104.0f/255.0f;
+                        table[2][i] = 32.0f/255.0f;
+                    }
+                    else if (i > 111 && i <= 127)
+                    {
+                        table[0][i] = 16.0f/255.0f;
+                        table[1][i] = 64.0f/255.0f;
+                        table[2][i] = 16.0f/255.0f;
+                    }
+                    else if (i > 127 && i <= 143)
+                    {
+                        table[0][i] = 240.0f/255.0f;
+                        table[1][i] = 192.0f/255.0f;
+                        table[2][i] = 16.0f/255.0f;
+                    }
+                    else if (i > 143 && i <= 159)
+                    {
+                        table[0][i] = 240.0f/255.0f;
+                        table[1][i] = 128.0f/255.0f;
+                        table[2][i] = 32.0f/255.0f;
+                    }
+                    else if (i > 159 && i <= 175)
+                    {
+                        table[0][i] = 240.0f/255.0f;
+                        table[1][i] = 16.0f/255.0f;
+                        table[2][i] = 32.0f/255.0f;
+                    }
+                    else if (i > 175 && i <= 191)
+                    {
+                        table[0][i] = 144.0f/255.0f;
+                        table[1][i] = 0.0f/255.0f;
+                        table[2][i] = 0.0f/255.0f;
+                    }
+                    else if (i > 191 && i <= 207)
+                    {
+                        table[0][i] = 176.0f/255.0f;
+                        table[1][i] = 32.0f/255.0f;
+                        table[2][i] = 128.0f/255.0f;
+                    }
+                    else if (i > 207 && i <= 223)
+                    {
+                        table[0][i] = 202.0f/255.0f;
+                        table[1][i] = 64.0f/255.0f;
+                        table[2][i] = 160.0f/255.0f;
+                    }
+                    else if (i > 223 && i <= 239)
+                    {
+                        table[0][i] = 255.0f/255.0f;
+                        table[1][i] = 255.0f/255.0f;
+                        table[2][i] = 255.0f/255.0f;
+                    }
+                    else if (i > 239 && i <= 255)
+                    {
+                        table[0][i] = 255.0f/255.0f;
+                        table[1][i] = 128.0f/255.0f;
+                        table[2][i] = 224.0f/255.0f;
+                    }
+
+            }
+      LabeledColorWidget lw =
+	  	new LabeledColorWidget(colorMap, table);
+
+      Frame frame = new Frame("VisAD Color Widget");
+      frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+      frame.add(lw);
+      frame.setSize(lw.getPreferredSize());
+      frame.setVisible(true);
+    }
+
+		private static void mapDisplay(DisplayImplJ3D d, String mapFile)
+		{
+		BaseMapAdapter baseMap;
+
+		try {
+			baseMap = new BaseMapAdapter(mapFile);
+		  // lat_map = new ScalarMap(RealType.Latitude, Display.YAxis);
+      // lon_map = new ScalarMap(RealType.Longitude, Display.XAxis);
+
+	    DataReference maplines_ref = new DataReferenceImpl("MapLines");
+      maplines_ref.setData(baseMap.getData());
+
+      ConstantMap[] colMap;
+      colMap = new ConstantMap[4];
+      colMap[0] = new ConstantMap(1., Display.Green);
+      colMap[1] = new ConstantMap(1., Display.Red);
+      colMap[2] = new ConstantMap(0., Display.Blue);
+      colMap[3] = new ConstantMap(-0.99, Display.ZAxis);
+	    d.addReference(maplines_ref, colMap);
+
+		} catch (Exception ne) {ne.printStackTrace(); System.exit(1); }
+   	}
+
+  public static void main(String[] args) throws VisADException, RemoteException {
+		// Adelaide Airport: location of example radar data file radar.dat
+	  float centlat = -34.9581f;
+	  float centlon = 138.5342f;
+		float radius = 6.0f; // degrees
+    String radarSource = "radar.dat";
+    RadarAdapter ra = null;
+    boolean d3d = (args.length > 0);
+    try {
+        ra = new RadarAdapter(centlat, centlon, 0.0f, radarSource, d3d);
+    } catch (Exception e) {
+      System.err.println("Caught Exception for \"" + radarSource + "\": " +
+                           e);
+      System.exit(1);
+    }
+
+    FlatField radar = ra.getData();
+
+/* WLH
+DumpType.dumpDataType(radar, System.out);
+VisAD Data analysis
+    FlatField of length = 54000
+    ((range, azimuth) -> reflection)
+      Domain has 2 components:
+        Integer2DSet: Length = 54000
+          0. Integer1DSet (range) Range = 0 to 149
+          1. Integer1DSet (azimuth) Range = 0 to 359
+      Range has 1 components:
+        0. FloatSet (reflection) Dimension = 1
+        0. number missing = 0
+*/
+
+    FunctionType radar_image = (FunctionType) radar.getType();
+    RealType reflection = (RealType) radar_image.getRange();
+
+    DisplayImplJ3D display = new DisplayImplJ3D("radar");
+    ScalarMap lonmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+		// centre lon 140
+    lonmap.setRange(centlon - radius, centlon + radius);
+    display.addMap(lonmap);
+    ScalarMap latmap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    display.addMap(latmap);
+		// centre lat -32
+    latmap.setRange(centlat - radius, centlat + radius);
+    if (d3d) {
+      ScalarMap altitudemap = new ScalarMap(RealType.Altitude, Display.ZAxis);
+      altitudemap.setRange(0, 30000);
+      display.addMap(altitudemap);
+    }
+    else {
+      ScalarMap reflectionmap = new ScalarMap(reflection, Display.ZAxis);
+      reflectionmap.setRange(0.f, 6.f);
+      display.addMap(reflectionmap);
+    }
+    ScalarMap rgbMap = new ScalarMap(reflection, Display.RGB);
+
+	  display.addMap(rgbMap);
+		setColorMap(rgbMap, 0.f, 6.f);
+		mapDisplay(display, "OUTLAUST");
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    // mode.setTextureEnable(false); WLH - 21 Sept 99
+
+    DataReference ref = new DataReferenceImpl("radar_ref");
+    ref.setData(radar);
+    display.addReference(ref);
+
+    JFrame frame = new JFrame("VisAD BOM radar image");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    frame.getContentPane().add(display.getComponent());
+    int WIDTH = 500;
+    int HEIGHT = 600;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+
+  }
+}
+
diff --git a/visad/bom/RadarFile.java b/visad/bom/RadarFile.java
new file mode 100644
index 0000000..5523c00
--- /dev/null
+++ b/visad/bom/RadarFile.java
@@ -0,0 +1,403 @@
+
+//
+// RadarFile.java
+//
+
+/*
+This sofware is part of the Australian Integrated Forecast System (AIFS)
+Copyright (C) 2011 Bureau of Meteorology
+*/
+
+package visad.bom;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+import java.util.Vector;
+
+import visad.DateTime;
+import visad.VisADException;
+
+/**
+ * RadarFile
+ * 
+ * @author - James Kelly : J.Kelly at bom.gov.au converted from Phil Purdam's
+ *         radl_cnvt.c
+ * 
+ * 
+ */
+
+public class RadarFile {
+
+  public DateTime dtTime;
+  public double  dRadarTime;
+  public float rngres = 250.0f;    // Resolution of range rings, in metres
+  public float startrng = 4000.0f; // Start Range in metres
+  public float azimuthres = 1.0f;  // Resolution of azmuth, in degrees
+  public float elev = 0.0f;        // Elevation of radar beam
+  // public float center_latitiude = -30.0f;
+  // public float center_longitiude = 160.0f;
+  private BufferedReader rf;
+  private int az;
+  // public int azimuth[];
+  public byte radial[][];
+  final static char decimal = '.';
+  final static char percent ='%';
+  final static char[] A2NXlat     = {'\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006',
+                                '\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016',
+                                '\u0020','\u0021','\u0022','\u0023','\u0024','\u0025','\u0026',
+                                '\u0030','\u0031','\u0032','\u0033','\u0034','\u0035','\u0036',
+                                '\u0040','\u0041','\u0042','\u0043','\u0044','\u0045','\u0046',
+                                '\u0050','\u0051','\u0052','\u0053','\u0054','\u0055','\u0056',
+                                '\u0060','\u0061','\u0062','\u0063','\u0064','\u0065','\u0066' };
+
+  final static int maxSize = 250; // maximum number of radial values, usually 512
+  public byte[] bdata;
+
+  public class PolarByteData {
+    public double azimuth;
+    public byte[] bdata;
+    public PolarByteData() { azimuth = 0.0; bdata = new byte[maxSize];}
+    public PolarByteData(double az, byte[] bdata) {
+      azimuth = az;
+      this.bdata = new byte[bdata.length];
+      System.arraycopy(bdata, 0, this.bdata, 0, bdata.length);
+    }
+  }
+  public Vector pbvector = new Vector();
+  public PolarByteData pbdata;
+  public PolarByteData[] pbdataArray;
+
+
+  public RadarFile(String radarSource) throws IOException {
+    // try as a disk file first
+    // try {
+    rf = new BufferedReader( new FileReader (radarSource));
+    az=0;
+    while (rf != null) {
+      readRadial();
+      pbdata = new PolarByteData((double) az, bdata);
+      System.arraycopy(bdata, 0, pbdata.bdata, 0, bdata.length);
+      if (rf != null) pbvector.addElement(pbdata);
+    }
+    pbdataArray = new PolarByteData[pbvector.size()];
+    pbvector.copyInto(pbdataArray);
+
+  }
+
+ /**
+   * Retrieves the time of the radar image as a double.
+   *
+   * @return image time
+   */
+  public double getTime()
+	{
+		return dRadarTime;
+	}
+
+
+  public void setTime(String radarTime)
+  {
+		try {
+		 	dRadarTime = Double.valueOf(radarTime).doubleValue();
+		} catch (NumberFormatException e) {
+			System.out.println("Exception converting Radar Time in module visad.bom.RadarFile.getTime() " + e);
+			dRadarTime = 0.0;
+		}
+	}
+
+
+ /**
+   * Retrieves the time of the radar image as a VisAD DateTime.
+   *
+   * @return image time
+   */
+  public DateTime getRadarTime()
+  {
+    return (dtTime);
+  }
+
+  public void setRadarTime(String timeStamp)
+      throws VisADException
+  {
+    // TIMESTAMP: 19990915024004
+    int year;
+    int month;
+    int day;
+    int hours;
+    int mins;
+    int secs;
+		String[] ids = TimeZone.getAvailableIDs(0);
+		TimeZone timeZone = new SimpleTimeZone(0, ids[0]);
+		Calendar cal = new GregorianCalendar(timeZone);
+
+		year  = Integer.valueOf(timeStamp.substring(0,4)).intValue();
+		month = Integer.valueOf(timeStamp.substring(4,6)).intValue();
+		day   = Integer.valueOf(timeStamp.substring(6,8)).intValue();
+		hours  = Integer.valueOf(timeStamp.substring(8,10)).intValue();
+		mins  = Integer.valueOf(timeStamp.substring(10,12)).intValue();
+		secs  = Integer.valueOf(timeStamp.substring(12,14)).intValue();
+    System.out.println("timeStamp: " + timeStamp);
+    System.out.println("year,month,day,hour,mins,secs: " + year+ " " + month + " " +day+ " " + hours+ " " + mins+ " " + secs);
+		// Subtract 1 from month since Jan = 0, Feb = 1 etc
+		cal.clear();
+		cal.set(year,month-1,day,hours,mins,secs);
+    System.out.println("Initialized with date: " + (cal.getTime()).toString());
+
+    dtTime = new DateTime(cal.getTime());
+    System.out.println("Initialized with date: " + dtTime);
+  }
+
+
+  public void readHeader(char[] cbuff ) {
+  	String radarTime;
+		String thisLine = new String(cbuff);
+		// System.out.println("line = " + thisLine);
+		if (thisLine.startsWith("COUNTRY:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("NAME:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("STNID:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("DATE:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("TIME:")) {
+			radarTime = new String (thisLine.substring(6));
+			System.out.println("radarTime = " + radarTime);
+		} else
+		if (thisLine.startsWith("TIMESTAMP:")) {
+			try {
+      	setRadarTime(thisLine.substring(11)) ;
+			} catch (VisADException e) {
+      	System.out.println("error setting radar time " + e );
+			}
+		} else
+		if (thisLine.startsWith("VERS:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("RNGRES:")) {
+			rngres = Float.valueOf(thisLine.substring(8)).floatValue();
+			System.out.println("rngres = " + rngres);
+		} else
+		if (thisLine.startsWith("ANGRES:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("VIDRES:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("STARTRNG:")) {
+			startrng = Float.valueOf(thisLine.substring(10)).floatValue();
+			System.out.println("startrng = " + startrng);
+		} else
+		if (thisLine.startsWith("ENDRNG:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("PRODUCT:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("IMGFMT:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("ELEV:")) {
+			elev = Float.valueOf(thisLine.substring(6)).floatValue();
+			System.out.println("elev = " + elev);
+		} else
+		if (thisLine.startsWith("DBZLVL:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("CLEARAIR:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("DBZCALDLVL:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("DIGCALDLVL:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("BEAMWIDTH:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("PULSELENGTH:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("STCRANGE:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("TXFREQUENCY:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("TXPEAKPWR:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("ANTGAIN:")) {
+			System.out.println("line = " + thisLine);
+		} else
+		if (thisLine.startsWith("NOISETHRESH:")) {
+			System.out.println("line = " + thisLine);
+		}
+  }
+/* Header format:
+COUNTRY: 036
+NAME: Adel
+STNID: 11
+DATE: 25899
+TIME: 02.40
+TIMESTAMP: 19990915024004
+VERS: 8.13
+RNGRES: 2000
+ANGRES: 1.0
+VIDRES: 6
+STARTRNG: 4000
+ENDRNG: 512000
+PRODUCT: NORMAL
+IMGFMT: CompPPI
+ELEV: 000.5
+DBZLVL: 11.8 27.8 39.0 43.8 48.6 55.0
+CLEARAIR: OFF
+DBZCALDLVL: 11.8 23.0 28.0 31.0 34.0 37.0 40.0 43.0 46.0 49.0 52.0 55.0 58.0 61.0 64.0
+DIGCALDLVL: 17 34 43 50 56 61 68 73 77 84 90 96 101 107 113
+BEAMWIDTH: 3.00
+PULSELENGTH: 1.7
+STCRANGE: 111
+TXFREQUENCY: 2880
+TXPEAKPWR: 500
+ANTGAIN: 33.0
+NOISETHRESH: 14
+*/
+
+
+  public void readRadial() throws IOException {
+
+    int pos = 0;
+    boolean done = false;
+    int  rptCount;
+    int sizeBuff;
+
+    char[] cbuff;
+    char thisChar;
+    StringBuffer sbuff;
+    String ipString;
+
+    sbuff = new StringBuffer();
+    bdata = new byte[maxSize];
+
+    //
+    // %ddd or %ddd.d
+    //
+    // % = first character
+    //
+    ipString = rf.readLine();
+
+// WLH
+if (ipString == null) {
+  rf = null;
+  return;
+}
+
+    cbuff = ipString.toCharArray();
+    sizeBuff = cbuff.length;
+
+// WLH
+if (sizeBuff == 0) {
+//   rf = null;
+  return;
+}
+
+    thisChar = cbuff[pos];
+    if ( percent == thisChar) {
+	// System.out.println("percent found");
+      pos++;
+    } else {
+			readHeader(cbuff);
+		}
+
+    //
+    // ddd = azimuthal direction (degrees)
+    //
+    thisChar = cbuff[pos++];
+    while (Character.isDigit(thisChar) || (thisChar == decimal)) {
+       sbuff.append(thisChar);
+       // System.out.println("thisChar, pos, sbuff =" + thisChar + " " + pos + " " + sbuff);
+       thisChar = cbuff[pos++];
+    }
+    try {
+       // System.out.println("azimuth: " + az);
+       // System.out.println("sbuff, length = " + sbuff + " " + sbuff.length());
+	   if (sbuff.length() != 0)
+         az = Math.round((Float.valueOf(sbuff.toString())).floatValue());
+
+    }
+    catch (NumberFormatException e) {
+       System.out.println("error converting radial " + e );
+    }
+
+    pos--;
+    // assert: next char is alpha
+    while (!done) {
+      // System.out.println("assert: next char is alpha");
+
+      thisChar = cbuff[pos];
+      // System.out.println("thisChar = " + thisChar);
+      pos++;
+      if (thisChar >= 'A' && thisChar <= 'Y') {
+         thisChar -= 'A';
+	  }
+      else {
+         if (thisChar >= 'a' && thisChar <= 'x')
+            thisChar -= 'H';
+         else
+            thisChar = '\u00FF';
+      } // endif
+      if (thisChar != '\u00FF') {
+         thisChar = A2NXlat[(int)thisChar];
+         rptCount = 0;
+		 if (pos < cbuff.length) {
+         while (Character.isDigit( cbuff[pos]) ) {
+             rptCount = (rptCount * 10) + (cbuff[pos] - '0');
+			 if (++pos >= cbuff.length) {
+			 	done = true;
+				break;
+			 }
+         } // endwhile
+		 }
+		 else
+		   done = true;
+         rptCount++;
+         if ((sizeBuff + (rptCount * 2)) > maxSize) {
+           rptCount = (maxSize - sizeBuff)/2;
+           done = true;
+         } // endif
+         while ((rptCount--) > 0) {
+           bdata[sizeBuff] = ((byte)((thisChar)  & '\u000f'));
+           bdata[sizeBuff+1] = ((byte)((thisChar) >> 4));
+           sizeBuff +=2;
+         } // endwhile
+       }
+       else {
+         done = true;
+         pos++;
+       } //  endif
+       // return 0;
+	   // pos++ ;
+     } // endwhile
+
+	// System.out.print("bdata = ");
+	// for (int i=0; i < maxSize; i++) {
+	//      System.out.print(bdata[i] + " ");
+    // }
+	// System.out.println(" ");
+
+    // System.out.println("end readRadial");
+  } // readRadial
+
+
+} // RadarFile
+
diff --git a/visad/bom/RadarFileException.java b/visad/bom/RadarFileException.java
new file mode 100644
index 0000000..3411756
--- /dev/null
+++ b/visad/bom/RadarFileException.java
@@ -0,0 +1,37 @@
+
+//
+// RadarFileException.java
+//
+
+/*
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/**
+ * RadarFileException class is to handle exceptions when dealing
+ * with Australian Bureau of Meteorology Radar files
+ *
+ * @author James Kelly
+ */
+
+package visad.bom;
+
+public class RadarFileException extends Exception {
+
+  public RadarFileException() {super(); }
+  public RadarFileException(String s) {super(s); }
+
+}
diff --git a/visad/bom/RubberBandBoxRendererJ3D.java b/visad/bom/RubberBandBoxRendererJ3D.java
new file mode 100644
index 0000000..c827115
--- /dev/null
+++ b/visad/bom/RubberBandBoxRendererJ3D.java
@@ -0,0 +1,620 @@
+//
+// RubberBandBoxRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+import javax.media.j3d.*;
+
+/**
+   RubberBandBoxRendererJ3D is the VisAD class for direct
+   manipulation of rubber band boxes
+*/
+public class RubberBandBoxRendererJ3D extends DirectManipulationRendererJ3D {
+
+  private RealType x = null;
+  private RealType y = null;
+  private RealTupleType xy = null;
+
+  private int mouseModifiersMask = 0;
+  private int mouseModifiersValue = 0;
+
+  private BranchGroup branch = null;
+  private BranchGroup group = null;
+
+  /** this DirectManipulationRenderer is quite different - it does not
+      render its data, but only place values into its DataReference
+      on right mouse button release;
+      it uses xarg and yarg to determine spatial ScalarMaps */
+  public RubberBandBoxRendererJ3D (RealType xarg, RealType yarg) {
+    this(xarg, yarg, 0, 0);
+  }
+
+  /** xarg and yarg determine spatial ScalarMaps;
+      mmm and mmv determine whehter SHIFT or CTRL keys are required -
+      this is needed since this is a greedy DirectManipulationRenderer
+      that will grab any right mouse click (that intersects its 2-D
+      sub-manifold) */
+  public RubberBandBoxRendererJ3D (RealType xarg, RealType yarg, int mmm, int mmv) {
+    super();
+    x = xarg;
+    y = yarg;
+    mouseModifiersMask = mmm;
+    mouseModifiersValue = mmv;
+  }
+
+  /** don't render - just return BranchGroup for scene graph to
+      render rectangle into */
+  public synchronized BranchGroup doTransform()
+         throws VisADException, RemoteException {
+    branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(Group.ALLOW_CHILDREN_READ);
+    branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+    // check type and maps for valid direct manipulation
+    if (!getIsDirectManipulation()) {
+      throw new BadDirectManipulationException(getWhyNotDirect() +
+        ": DirectManipulationRendererJ3D.doTransform");
+    }
+    setBranch(branch);
+    return branch;
+  }
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+
+  private transient ScalarMap xmap = null;
+  private transient ScalarMap ymap = null;
+
+  float[] default_values;
+
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+  private float[] value = new float[2];
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  /** dimension of direct manipulation
+      (always 2 for RubberBandBoxRendererJ3D) */
+  private int directManifoldDimension = 2;
+  /** spatial DisplayTupleType other than
+      DisplaySpatialCartesianTuple */
+  private DisplayTupleType tuple;
+  private CoordinateSystem tuplecs;
+
+  private int xindex = -1;
+  private int yindex = -1;
+  private int otherindex = -1;
+  private float othervalue;
+
+  private byte red, green, blue; // default colors
+
+  private float[][] first_x;
+  private float[][] last_x;
+  private float[][] clast_x;
+  private float cum_lon;
+
+  /** possible values for whyNotDirect */
+  private final static String xandyNotMatch =
+    "x and y spatial domains don't match";
+  private final static String xandyNotSpatial =
+    "x and y must be mapped to spatial";
+
+
+  private boolean stop = false;
+
+  public void checkDirect() throws VisADException, RemoteException {
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    default_values = link.getDefaultValues();
+
+    xmap = null;
+    ymap = null;
+    Vector scalar_map_vector = display.getMapVector();
+    Enumeration en = scalar_map_vector.elements();
+    while (en.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) en.nextElement();
+      ScalarType real = map.getScalar();
+      if (real.equals(x)) {
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType t = dreal.getTuple();
+        if (t != null &&
+            (t.equals(Display.DisplaySpatialCartesianTuple) ||
+             (t.getCoordinateSystem() != null &&
+              t.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          xmap = map;
+          xindex = dreal.getTupleIndex();
+          if (tuple == null) {
+            tuple = t;
+          }
+          else if (!t.equals(tuple)) {
+            whyNotDirect = xandyNotMatch;
+            return;
+          }
+        }
+      }
+      if (real.equals(y)) {
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType t = dreal.getTuple();
+        if (t != null &&
+            (t.equals(Display.DisplaySpatialCartesianTuple) ||
+             (t.getCoordinateSystem() != null &&
+              t.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          ymap = map;
+          yindex = dreal.getTupleIndex();
+          if (tuple == null) {
+            tuple = t;
+          }
+          else if (!t.equals(tuple)) {
+            whyNotDirect = xandyNotMatch;
+            return;
+          }
+        }
+      }
+    }
+
+    if (xmap == null || ymap == null) {
+      whyNotDirect = xandyNotSpatial;
+      return;
+    }
+
+    xy = new RealTupleType(x, y);
+
+    // get default value for other component of tuple
+    otherindex = 3 - (xindex + yindex);
+    DisplayRealType dreal = (DisplayRealType) tuple.getComponent(otherindex);
+    int index = getDisplay().getDisplayScalarIndex(dreal);
+    othervalue = (index > 0) ? default_values[index] :
+                               (float) dreal.getDefaultValue();
+
+    // get default colors
+    index = getDisplay().getDisplayScalarIndex(Display.Red);
+    float v = (index > 0) ? default_values[index] :
+                           (float) Display.Red.getDefaultValue();
+    red = ShadowType.floatToByte(v);
+    index = getDisplay().getDisplayScalarIndex(Display.Green);
+    v = (index > 0) ? default_values[index] :
+                      (float) Display.Green.getDefaultValue();
+    green = ShadowType.floatToByte(v);
+    index = getDisplay().getDisplayScalarIndex(Display.Blue);
+    v = (index > 0) ? default_values[index] :
+                      (float) Display.Blue.getDefaultValue();
+    blue = ShadowType.floatToByte(v);
+
+    if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+      tuple = null;
+      tuplecs = null;
+    }
+    else {
+      tuplecs = tuple.getCoordinateSystem();
+    }
+
+    directManifoldDimension = 2;
+    setIsDirectManipulation(true);
+  }
+
+  private int getDirectManifoldDimension() {
+    return directManifoldDimension;
+  }
+
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+// methods customized from DataRenderer:
+
+  public CoordinateSystem getDisplayCoordinateSystem() {
+    return tuplecs;
+  }
+
+  /** set spatialValues from ShadowType.doTransform */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // do nothing
+  }
+
+  /** check if ray intersects sub-manifold */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    int mouseModifiers = getLastMouseModifiers();
+    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
+      return Float.MAX_VALUE;
+    }
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r == r) {
+        return 0.0f;
+      }
+      else {
+        return Float.MAX_VALUE;
+      }
+    }
+    catch (VisADException ex) {
+      return Float.MAX_VALUE;
+    }
+  }
+
+  /** mouse button released, ending direct manipulation */
+  public synchronized void release_direct() {
+    // set data in ref
+    if (group != null) group.detach();
+    group = null;
+    try {
+      float[][] samples = new float[2][2];
+      f[0] = first_x[xindex][0];
+      d = xmap.inverseScaleValues(f);
+      samples[0][0] = (float) d[0];
+      f[0] = first_x[yindex][0];
+      d = ymap.inverseScaleValues(f);
+      samples[1][0] = (float) d[0];
+      f[0] = last_x[xindex][0];
+      d = xmap.inverseScaleValues(f);
+      samples[0][1] = (float) d[0];
+      f[0] = last_x[yindex][0];
+      d = ymap.inverseScaleValues(f);
+      samples[1][1] = (float) d[0];
+      Gridded2DSet set = new Gridded2DSet(xy, samples, 2);
+      ref.setData(set);
+      link.clearData();
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("release_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("release_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  public void stop_direct() {
+    stop = true;
+  }
+
+  private static final int EDGE = 20;
+
+  private static final float EPS = 0.005f;
+
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    if (ref == null) return;
+
+    if (first) {
+      stop = false;
+    }
+    else {
+      if (stop) return;
+    }
+
+    double[] origin = ray.position;
+    double[] direction = ray.vector;
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r != r) {
+        if (group != null) group.detach();
+        return;
+      }
+      float[][] xx = {{(float) (origin[0] + r * direction[0])},
+                      {(float) (origin[1] + r * direction[1])},
+                      {(float) (origin[2] + r * direction[2])}};
+      if (tuple != null) xx = tuplecs.fromReference(xx);
+
+      if (first) {
+        first_x = xx;
+        cum_lon = 0.0f;
+      }
+      else if (Display.DisplaySpatialSphericalTuple.equals(tuple)) {
+        float diff = xx[1][0] - clast_x[1][0];
+        if (diff > 180.0f) diff -= 360.0f;
+        else if (diff < -180.0f) diff += 360.0f;
+        cum_lon += diff;
+        if (cum_lon > 360.0f) cum_lon -= 360.0f;
+        else if (cum_lon < -360.0f) cum_lon += 360.0f;
+      }
+      clast_x = xx;
+
+      Vector vect = new Vector();
+      f[0] = xx[xindex][0];
+      d = xmap.inverseScaleValues(f);
+
+      // WLH 31 Aug 2000
+      Real rr = new Real(x, d[0]);
+      Unit overrideUnit = xmap.getOverrideUnit();
+      Unit rtunit = x.getDefaultUnit();
+      // units not part of Time string
+      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+          !RealType.Time.equals(x)) {
+        double dval =  overrideUnit.toThis((double) d[0], rtunit);
+        rr = new Real(x, dval, overrideUnit);
+      }   
+      String valueString = rr.toValueString();
+
+      vect.addElement(x.getName() + " = " + valueString);
+      f[0] = xx[yindex][0];
+      d = ymap.inverseScaleValues(f);
+
+      // WLH 31 Aug 2000
+      rr = new Real(y, d[0]);
+      overrideUnit = ymap.getOverrideUnit();
+      rtunit = y.getDefaultUnit();
+      // units not part of Time string
+      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+          !RealType.Time.equals(y)) {
+        double dval =  overrideUnit.toThis((double) d[0], rtunit);
+        rr = new Real(y, dval, overrideUnit);
+      }
+      valueString = rr.toValueString();
+
+      valueString = new Real(y, d[0]).toValueString();
+      vect.addElement(y.getName() + " = " + valueString);
+      getDisplayRenderer().setCursorStringVector(vect);
+
+      float[][] xxp = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
+      xxp[otherindex][0] += EPS;
+      if (tuplecs != null) xxp = tuplecs.toReference(xxp);
+      float[][] xxm = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
+      xxm[otherindex][0] -= EPS;
+      if (tuplecs != null) xxm = tuplecs.toReference(xxm);
+      double dot = (xxp[0][0] - xxm[0][0]) * direction[0] +
+                   (xxp[1][0] - xxm[1][0]) * direction[1] +
+                   (xxp[2][0] - xxm[2][0]) * direction[2];
+      float abs = (float)
+        Math.sqrt((xxp[0][0] - xxm[0][0]) * (xxp[0][0] - xxm[0][0]) +
+                 (xxp[1][0] - xxm[1][0]) * (xxp[1][0] - xxm[1][0]) +
+                 (xxp[2][0] - xxm[2][0]) * (xxp[2][0] - xxm[2][0]));
+      float other_offset = EPS * (2.0f * EPS / abs);
+      if (dot >= 0.0) other_offset = -other_offset;
+
+      last_x =
+        new float[][] {{clast_x[0][0]}, {clast_x[1][0]}, {clast_x[2][0]}};
+      if (Display.DisplaySpatialSphericalTuple.equals(tuple) &&
+          otherindex != 1) {
+        if (last_x[1][0] < first_x[1][0] && cum_lon > 0.0f) {
+          last_x[1][0] += 360.0;
+        }
+        else if (last_x[1][0] > first_x[1][0] && cum_lon < 0.0f) {
+          last_x[1][0] -= 360.0;
+        }
+      }
+
+      int npoints = 4 * EDGE + 1;
+      float[][] c = new float[3][npoints];
+      for (int i=0; i<EDGE; i++) {
+        float a = ((float) i) / EDGE;
+        float b = 1.0f - a;
+        c[xindex][i] = b * first_x[xindex][0] + a * last_x[xindex][0];
+        c[yindex][i] = first_x[yindex][0];
+        c[otherindex][i] = first_x[otherindex][0] + other_offset;
+        c[xindex][EDGE + i] = last_x[xindex][0];
+        c[yindex][EDGE + i] = b * first_x[yindex][0] + a * last_x[yindex][0];
+        c[otherindex][EDGE + i] = first_x[otherindex][0] + other_offset;
+        c[xindex][2 * EDGE + i] = b * last_x[xindex][0] + a * first_x[xindex][0];
+        c[yindex][2 * EDGE + i] = last_x[yindex][0];
+        c[otherindex][2 * EDGE + i] = first_x[otherindex][0] + other_offset;
+        c[xindex][3 * EDGE + i] = first_x[xindex][0];
+        c[yindex][3 * EDGE + i] = b * last_x[yindex][0] + a * first_x[yindex][0];
+        c[otherindex][3 * EDGE + i] = first_x[otherindex][0] + other_offset;
+      }
+      c[0][npoints - 1] = c[0][0];
+      c[1][npoints - 1] = c[1][0];
+      c[2][npoints - 1] = c[2][0];
+      if (tuple != null) c = tuplecs.toReference(c);
+      float[] coordinates = new float[3 * npoints];
+      for (int i=0; i<npoints; i++) {
+        int i3 = 3 * i;
+        coordinates[i3] = c[0][i];
+        coordinates[i3 + 1] = c[1][i];
+        coordinates[i3 + 2] = c[2][i];
+      }
+      VisADLineStripArray array = new VisADLineStripArray();
+      array.vertexCount = npoints;
+      array.stripVertexCounts = new int[1];
+      array.stripVertexCounts[0] = npoints;
+      array.coordinates = coordinates;
+      byte[] colors = new byte[3 * npoints];
+      for (int i=0; i<npoints; i++) {
+        int i3 = 3 * i;
+        colors[i3] = red;
+        colors[i3 + 1] = green;
+        colors[i3 + 2] = blue;
+      }
+      array.colors = colors;
+      array = (VisADLineStripArray) array.adjustSeam(this);
+
+      DisplayImplJ3D display = (DisplayImplJ3D) getDisplay();
+      GeometryArray geometry = display.makeGeometry(array);
+  
+      DataDisplayLink[] Links = getLinks();
+      if (Links == null || Links.length == 0) {
+        return;
+      }
+      DataDisplayLink link = Links[0];
+
+      float[] default_values = link.getDefaultValues();
+      GraphicsModeControl mode = (GraphicsModeControl)
+        display.getGraphicsModeControl().clone();
+      float pointSize =
+        default_values[display.getDisplayScalarIndex(Display.PointSize)];
+      float lineWidth =
+        default_values[display.getDisplayScalarIndex(Display.LineWidth)];
+      mode.setPointSize(pointSize, true);
+      mode.setLineWidth(lineWidth, true);
+      Appearance appearance =
+        ShadowTypeJ3D.staticMakeAppearance(mode, null, null, geometry, false);
+
+      if (group != null) group.detach();
+      group = null;
+
+      Shape3D shape = new Shape3D(geometry, appearance);
+      group = new BranchGroup();
+      group.setCapability(Group.ALLOW_CHILDREN_READ);
+      group.setCapability(BranchGroup.ALLOW_DETACH);
+      group.addChild(shape);
+      if (branch != null) branch.addChild(group);
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  public Object clone() {
+    return new RubberBandBoxRendererJ3D(x, y, mouseModifiersMask,
+                                        mouseModifiersValue);
+  }
+
+  private static final int N = 64;
+
+  /** test RubberBandBoxRendererJ3D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealTupleType xy = new RealTupleType(x, y);
+
+    RealType c = RealType.getRealType("c");
+    FunctionType ft = new FunctionType(xy, c);
+
+    // construct Java3D display and mappings
+    DisplayImpl display = new DisplayImplJ3D("display1");
+    if (args.length == 0 || args[0].equals("z")) {
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.YAxis));
+    }
+    else if (args[0].equals("x")) {
+      display.addMap(new ScalarMap(x, Display.YAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("y")) {
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("radius")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    else if (args[0].equals("lat")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else if (args[0].equals("lon")) {
+      display.addMap(new ScalarMap(x, Display.Latitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    display.addMap(new ScalarMap(c, Display.RGB));
+
+    Integer2DSet fset = new Integer2DSet(xy, N, N);
+    FlatField field = new FlatField(ft, fset);
+    float[][] values = new float[1][N * N];
+    int k = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        values[0][k++] = (i - N / 2) * (j - N / 2);
+      }
+    }
+    field.setSamples(values);
+    DataReferenceImpl field_ref = new DataReferenceImpl("field");
+    field_ref.setData(field);
+    display.addReference(field_ref);
+
+    Gridded2DSet dummy_set = new Gridded2DSet(xy, null, 1);
+    final DataReferenceImpl ref = new DataReferenceImpl("set");
+    ref.setData(dummy_set);
+    int m = (args.length > 1) ? InputEvent.CTRL_MASK : 0;
+    display.addReferences(new RubberBandBoxRendererJ3D(x, y, m, m), ref);
+
+    CellImpl cell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        Set set = (Set) ref.getData();
+        float[][] samples = set.getSamples();
+        if (samples != null) {
+          System.out.println("box (" + samples[0][0] + ", " + samples[1][0] +
+                             ") to (" + samples[0][1] + ", " + samples[1][1] + ")");
+        }
+      }
+    };
+    cell.addReference(ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test RubberBandBoxRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/visad/bom/RubberBandLineRendererJ3D.java b/visad/bom/RubberBandLineRendererJ3D.java
new file mode 100644
index 0000000..c51c618
--- /dev/null
+++ b/visad/bom/RubberBandLineRendererJ3D.java
@@ -0,0 +1,636 @@
+//
+// RubberBandLineRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+import javax.media.j3d.*;
+
+/**
+   RubberBandLineRendererJ3D is the VisAD class for direct
+   manipulation of rubber band lines
+*/
+public class RubberBandLineRendererJ3D extends DirectManipulationRendererJ3D {
+
+  private RealType x = null;
+  private RealType y = null;
+  private RealTupleType xy = null;
+
+  private int mouseModifiersMask = 0;
+  private int mouseModifiersValue = 0;
+
+  private BranchGroup branch = null;
+  private BranchGroup group = null;
+
+  /** this DirectManipulationRenderer is quite different - it does not
+      render its data, but only place values into its DataReference
+      on right mouse button release;
+      it uses xarg and yarg to determine spatial ScalarMaps */
+  public RubberBandLineRendererJ3D (RealType xarg, RealType yarg) {
+    this(xarg, yarg, 0, 0);
+  }
+
+  /** xarg and yarg determine spatial ScalarMaps;
+      mmm and mmv determine whehter SHIFT or CTRL keys are required -
+      this is needed since this is a greedy DirectManipulationRenderer
+      that will grab any right mouse click (that intersects its 2-D
+      sub-manifold) */
+  public RubberBandLineRendererJ3D (RealType xarg, RealType yarg, int mmm, int mmv) {
+    super();
+    x = xarg;
+    y = yarg;
+    mouseModifiersMask = mmm;
+    mouseModifiersValue = mmv;
+  }
+
+  /** don't render - just return BranchGroup for scene graph to
+      render rectangle into */
+  public synchronized BranchGroup doTransform()
+         throws VisADException, RemoteException {
+    branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(Group.ALLOW_CHILDREN_READ);
+    branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+    // check type and maps for valid direct manipulation
+    if (!getIsDirectManipulation()) {
+      throw new BadDirectManipulationException(getWhyNotDirect() +
+        ": DirectManipulationRendererJ3D.doTransform");
+    }
+    setBranch(branch);
+    return branch;
+  }
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+
+  private transient ScalarMap xmap = null;
+  private transient ScalarMap ymap = null;
+
+  float[] default_values;
+
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+  private float[] value = new float[2];
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  /** dimension of direct manipulation
+      (always 2 for RubberBandLineRendererJ3D) */
+  private int directManifoldDimension = 2;
+  /** spatial DisplayTupleType other than
+      DisplaySpatialCartesianTuple */
+  private DisplayTupleType tuple;
+  private CoordinateSystem tuplecs;
+
+  private int xindex = -1;
+  private int yindex = -1;
+  private int otherindex = -1;
+  private float othervalue;
+
+  private byte red, green, blue; // default colors
+
+  private float[][] first_x;
+  private float[][] last_x;
+  private float[][] clast_x;
+  private float cum_lon;
+
+  /** possible values for whyNotDirect */
+  private final static String xandyNotMatch =
+    "x and y spatial domains don't match";
+  private final static String xandyNotSpatial =
+    "x and y must be mapped to spatial";
+
+
+  private boolean stop = false;
+
+  public void checkDirect() throws VisADException, RemoteException {
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    default_values = link.getDefaultValues();
+
+    xmap = null;
+    ymap = null;
+    Vector scalar_map_vector = display.getMapVector();
+    Enumeration en = scalar_map_vector.elements();
+    while (en.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) en.nextElement();
+      ScalarType real = map.getScalar();
+      if (real.equals(x)) {
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType t = dreal.getTuple();
+        if (t != null &&
+            (t.equals(Display.DisplaySpatialCartesianTuple) ||
+             (t.getCoordinateSystem() != null &&
+              t.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          xmap = map;
+          xindex = dreal.getTupleIndex();
+          if (tuple == null) {
+            tuple = t;
+          }
+          else if (!t.equals(tuple)) {
+            whyNotDirect = xandyNotMatch;
+            return;
+          }
+        }
+      }
+      if (real.equals(y)) {
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType t = dreal.getTuple();
+        if (t != null &&
+            (t.equals(Display.DisplaySpatialCartesianTuple) ||
+             (t.getCoordinateSystem() != null &&
+              t.getCoordinateSystem().getReference().equals(
+              Display.DisplaySpatialCartesianTuple)))) {
+          ymap = map;
+          yindex = dreal.getTupleIndex();
+          if (tuple == null) {
+            tuple = t;
+          }
+          else if (!t.equals(tuple)) {
+            whyNotDirect = xandyNotMatch;
+            return;
+          }
+        }
+      }
+    }
+
+    if (xmap == null || ymap == null) {
+      whyNotDirect = xandyNotSpatial;
+      return;
+    }
+
+    xy = new RealTupleType(x, y);
+
+    // get default value for other component of tuple
+    otherindex = 3 - (xindex + yindex);
+    DisplayRealType dreal = (DisplayRealType) tuple.getComponent(otherindex);
+    int index = getDisplay().getDisplayScalarIndex(dreal);
+    othervalue = (index > 0) ? default_values[index] :
+                               (float) dreal.getDefaultValue();
+
+    // get default colors
+    index = getDisplay().getDisplayScalarIndex(Display.Red);
+    float v = (index > 0) ? default_values[index] :
+                           (float) Display.Red.getDefaultValue();
+    red = ShadowType.floatToByte(v);
+    index = getDisplay().getDisplayScalarIndex(Display.Green);
+    v = (index > 0) ? default_values[index] :
+                      (float) Display.Green.getDefaultValue();
+    green = ShadowType.floatToByte(v);
+    index = getDisplay().getDisplayScalarIndex(Display.Blue);
+    v = (index > 0) ? default_values[index] :
+                      (float) Display.Blue.getDefaultValue();
+    blue = ShadowType.floatToByte(v);
+
+    if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
+      tuple = null;
+      tuplecs = null;
+    }
+    else {
+      tuplecs = tuple.getCoordinateSystem();
+    }
+
+    directManifoldDimension = 2;
+    setIsDirectManipulation(true);
+  }
+
+  private int getDirectManifoldDimension() {
+    return directManifoldDimension;
+  }
+
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+// methods customized from DataRenderer:
+
+  public CoordinateSystem getDisplayCoordinateSystem() {
+    return tuplecs;
+  }
+
+  /** set spatialValues from ShadowType.doTransform */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // do nothing
+  }
+
+  /** check if ray intersects sub-manifold */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    int mouseModifiers = getLastMouseModifiers();
+    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
+      return Float.MAX_VALUE;
+    }
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r == r) {
+        return 0.0f;
+      }
+      else {
+        return Float.MAX_VALUE;
+      }
+    }
+    catch (VisADException ex) {
+      return Float.MAX_VALUE;
+    }
+  }
+
+  /** mouse button released, ending direct manipulation */
+  public synchronized void release_direct() {
+    // set data in ref
+    if (group != null) group.detach();
+    group = null;
+    try {
+      float[][] samples = new float[2][2];
+      f[0] = first_x[xindex][0];
+      d = xmap.inverseScaleValues(f);
+      samples[0][0] = (float) d[0];
+      f[0] = first_x[yindex][0];
+      d = ymap.inverseScaleValues(f);
+      samples[1][0] = (float) d[0];
+      f[0] = last_x[xindex][0];
+      d = xmap.inverseScaleValues(f);
+      samples[0][1] = (float) d[0];
+      f[0] = last_x[yindex][0];
+      d = ymap.inverseScaleValues(f);
+      samples[1][1] = (float) d[0];
+      Gridded2DSet set = new Gridded2DSet(xy, samples, 2);
+      ref.setData(set);
+      link.clearData();
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("release_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("release_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  public void stop_direct() {
+    stop = true;
+  }
+
+  private static final int EDGE = 20;
+
+  private static final float EPS = 0.005f;
+
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    if (ref == null) return;
+
+    if (first) {
+      stop = false;
+    }
+    else {
+      if (stop) return;
+    }
+
+    double[] origin = ray.position;
+    double[] direction = ray.vector;
+
+    try {
+      float r = findRayManifoldIntersection(true, origin, direction, tuple,
+                                            otherindex, othervalue);
+      if (r != r) {
+        if (group != null) group.detach();
+        return;
+      }
+      float[][] xx = {{(float) (origin[0] + r * direction[0])},
+                      {(float) (origin[1] + r * direction[1])},
+                      {(float) (origin[2] + r * direction[2])}};
+      if (tuple != null) xx = tuplecs.fromReference(xx);
+
+      if (first) {
+        first_x = xx;
+        cum_lon = 0.0f;
+      }
+      else if (Display.DisplaySpatialSphericalTuple.equals(tuple)) {
+        float diff = xx[1][0] - clast_x[1][0];
+        if (diff > 180.0f) diff -= 360.0f;
+        else if (diff < -180.0f) diff += 360.0f;
+        cum_lon += diff;
+        if (cum_lon > 360.0f) cum_lon -= 360.0f;
+        else if (cum_lon < -360.0f) cum_lon += 360.0f;
+      }
+      clast_x = xx;
+
+      Vector vect = new Vector();
+      f[0] = xx[xindex][0];
+      d = xmap.inverseScaleValues(f);
+
+      // WLH 31 Aug 2000
+      Real rr = new Real(x, d[0]);
+      Unit overrideUnit = xmap.getOverrideUnit();
+      Unit rtunit = x.getDefaultUnit();
+      // units not part of Time string
+      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+          !RealType.Time.equals(x)) {
+        double dval =  overrideUnit.toThis((double) d[0], rtunit);
+        rr = new Real(x, dval, overrideUnit);
+      }
+      String valueString = rr.toValueString();
+
+      vect.addElement(x.getName() + " = " + valueString);
+      f[0] = xx[yindex][0];
+      d = ymap.inverseScaleValues(f);
+
+      // WLH 31 Aug 2000
+      rr = new Real(y, d[0]);
+      overrideUnit = ymap.getOverrideUnit();
+      rtunit = y.getDefaultUnit();
+      // units not part of Time string
+      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+          !RealType.Time.equals(y)) {
+        double dval =  overrideUnit.toThis((double) d[0], rtunit);
+        rr = new Real(y, dval, overrideUnit);
+      }
+      valueString = rr.toValueString();
+
+      valueString = new Real(y, d[0]).toValueString();
+      vect.addElement(y.getName() + " = " + valueString);
+      getDisplayRenderer().setCursorStringVector(vect);
+
+      float[][] xxp = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
+      xxp[otherindex][0] += EPS;
+      if (tuplecs != null) xxp = tuplecs.toReference(xxp);
+      float[][] xxm = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
+      xxm[otherindex][0] -= EPS;
+      if (tuplecs != null) xxm = tuplecs.toReference(xxm);
+      double dot = (xxp[0][0] - xxm[0][0]) * direction[0] +
+                   (xxp[1][0] - xxm[1][0]) * direction[1] +
+                   (xxp[2][0] - xxm[2][0]) * direction[2];
+      float abs = (float)
+        Math.sqrt((xxp[0][0] - xxm[0][0]) * (xxp[0][0] - xxm[0][0]) +
+                 (xxp[1][0] - xxm[1][0]) * (xxp[1][0] - xxm[1][0]) +
+                 (xxp[2][0] - xxm[2][0]) * (xxp[2][0] - xxm[2][0]));
+      float other_offset = EPS * (2.0f * EPS / abs);
+      if (dot >= 0.0) other_offset = -other_offset;
+
+      last_x =
+        new float[][] {{clast_x[0][0]}, {clast_x[1][0]}, {clast_x[2][0]}};
+      if (Display.DisplaySpatialSphericalTuple.equals(tuple) &&
+          otherindex != 1) {
+        if (last_x[1][0] < first_x[1][0] && cum_lon > 0.0f) {
+          last_x[1][0] += 360.0;
+        }
+        else if (last_x[1][0] > first_x[1][0] && cum_lon < 0.0f) {
+          last_x[1][0] -= 360.0;
+        }
+      }
+
+      // int npoints = 4 * EDGE + 1;
+      // jk Feb 27 2001
+      int npoints = 1 * EDGE;
+      float[][] c = new float[3][npoints];
+      for (int i=0; i<EDGE; i++) {
+        float a = ((float) i) / (EDGE - 1);  // goes from 0...1
+        float b = 1.0f - a;            // goes from 1...0
+        c[xindex][i] = b * first_x[xindex][0] + a * last_x[xindex][0];
+        c[yindex][i] = b * first_x[yindex][0] + a * last_x[yindex][0];
+        // jk Feb 27 2001 below 2 lines replaced with above 2
+        // c[xindex][i] = b * first_x[xindex][0] + a * last_x[xindex][0];
+        // c[yindex][i] = first_x[yindex][0];
+        c[otherindex][i] = first_x[otherindex][0] + other_offset;
+
+        // jk Feb 27 2001 below 3 lines new
+        // c[xindex][EDGE + i] = a * first_x[xindex][0] + b * last_x[xindex][0];
+        // c[yindex][EDGE + i] = a * first_x[yindex][0] + b * last_x[yindex][0];
+        // c[otherindex][EDGE + i] = first_x[otherindex][0] + other_offset;
+
+        /* jk Feb 27 2001 comment out section below
+        c[xindex][EDGE + i] = last_x[xindex][0];
+        c[yindex][EDGE + i] = b * first_x[yindex][0] + a * last_x[yindex][0];
+        c[otherindex][EDGE + i] = first_x[otherindex][0] + other_offset;
+        c[xindex][2 * EDGE + i] = b * last_x[xindex][0] + a * first_x[xindex][0];
+        c[yindex][2 * EDGE + i] = last_x[yindex][0];
+        c[otherindex][2 * EDGE + i] = first_x[otherindex][0] + other_offset;
+        c[xindex][3 * EDGE + i] = first_x[xindex][0];
+        c[yindex][3 * EDGE + i] = b * last_x[yindex][0] + a * first_x[yindex][0];
+        c[otherindex][3 * EDGE + i] = first_x[otherindex][0] + other_offset;
+        */
+      }
+      /* jk
+      c[0][npoints - 1] = c[0][0];
+      c[1][npoints - 1] = c[1][0];
+      c[2][npoints - 1] = c[2][0];
+      */
+
+      if (tuple != null) c = tuplecs.toReference(c);
+      float[] coordinates = new float[3 * npoints];
+      for (int i=0; i<npoints; i++) {
+        int i3 = 3 * i;
+        coordinates[i3] = c[0][i];
+        coordinates[i3 + 1] = c[1][i];
+        coordinates[i3 + 2] = c[2][i];
+      }
+      VisADLineStripArray array = new VisADLineStripArray();
+      array.vertexCount = npoints;
+      array.stripVertexCounts = new int[1];
+      array.stripVertexCounts[0] = npoints;
+      array.coordinates = coordinates;
+      byte[] colors = new byte[3 * npoints];
+      for (int i=0; i<npoints; i++) {
+        int i3 = 3 * i;
+        colors[i3] = red;
+        colors[i3 + 1] = green;
+        colors[i3 + 2] = blue;
+      }
+      array.colors = colors;
+      array = (VisADLineStripArray) array.adjustSeam(this);
+
+      DisplayImplJ3D display = (DisplayImplJ3D) getDisplay();
+      GeometryArray geometry = display.makeGeometry(array);
+
+      DataDisplayLink[] Links = getLinks();
+      if (Links == null || Links.length == 0) {
+        return;
+      }
+      DataDisplayLink link = Links[0];
+
+      float[] default_values = link.getDefaultValues();
+      GraphicsModeControl mode = (GraphicsModeControl)
+        display.getGraphicsModeControl().clone();
+      float pointSize =
+        default_values[display.getDisplayScalarIndex(Display.PointSize)];
+      float lineWidth =
+        default_values[display.getDisplayScalarIndex(Display.LineWidth)];
+      mode.setPointSize(pointSize, true);
+      mode.setLineWidth(lineWidth, true);
+      Appearance appearance =
+        ShadowTypeJ3D.staticMakeAppearance(mode, null, null, geometry, false);
+
+      if (group != null) group.detach();
+      group = null;
+
+      Shape3D shape = new Shape3D(geometry, appearance);
+      group = new BranchGroup();
+      group.setCapability(Group.ALLOW_CHILDREN_READ);
+      group.setCapability(BranchGroup.ALLOW_DETACH);
+      group.addChild(shape);
+      if (branch != null) branch.addChild(group);
+    } // end try
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  public Object clone() {
+    return new RubberBandLineRendererJ3D(x, y, mouseModifiersMask,
+                                         mouseModifiersValue);
+  }
+
+  private static final int N = 64;
+
+  /** test RubberBandLineRendererJ3D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealTupleType xy = new RealTupleType(x, y);
+
+    RealType c = RealType.getRealType("c");
+    FunctionType ft = new FunctionType(xy, c);
+
+    // construct Java3D display and mappings
+    DisplayImpl display = new DisplayImplJ3D("display1");
+    if (args.length == 0 || args[0].equals("z")) {
+      display.addMap(new ScalarMap(y, Display.YAxis));
+      display.addMap(new ScalarMap(x, Display.XAxis));
+    }
+    else if (args[0].equals("x")) {
+      display.addMap(new ScalarMap(x, Display.YAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("y")) {
+      display.addMap(new ScalarMap(x, Display.XAxis));
+      display.addMap(new ScalarMap(y, Display.ZAxis));
+    }
+    else if (args[0].equals("radius")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    else if (args[0].equals("lat")) {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else if (args[0].equals("lon")) {
+      display.addMap(new ScalarMap(x, Display.Latitude));
+      display.addMap(new ScalarMap(y, Display.Radius));
+    }
+    else {
+      display.addMap(new ScalarMap(x, Display.Longitude));
+      display.addMap(new ScalarMap(y, Display.Latitude));
+    }
+    display.addMap(new ScalarMap(c, Display.RGB));
+
+    Integer2DSet fset = new Integer2DSet(xy, N, N);
+    FlatField field = new FlatField(ft, fset);
+    float[][] values = new float[1][N * N];
+    int k = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        values[0][k++] = (i - N / 2) * (j - N / 2);
+      }
+    }
+    field.setSamples(values);
+    DataReferenceImpl field_ref = new DataReferenceImpl("field");
+    field_ref.setData(field);
+    display.addReference(field_ref);
+
+    Gridded2DSet dummy_set = new Gridded2DSet(xy, null, 1);
+    final DataReferenceImpl ref = new DataReferenceImpl("set");
+    ref.setData(dummy_set);
+    int m = (args.length > 1) ? InputEvent.CTRL_MASK : 0;
+    display.addReferences(new RubberBandLineRendererJ3D(x, y, m, m), ref);
+
+    CellImpl cell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        Set set = (Set) ref.getData();
+        float[][] samples = set.getSamples();
+        if (samples != null) {
+          System.out.println("box (" + samples[0][0] + ", " + samples[1][0] +
+                             ") to (" + samples[0][1] + ", " + samples[1][1] + ")");
+        }
+      }
+    };
+    cell.addReference(ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test RubberBandLineRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
diff --git a/visad/bom/SceneGraphRenderer.java b/visad/bom/SceneGraphRenderer.java
new file mode 100644
index 0000000..46a76b0
--- /dev/null
+++ b/visad/bom/SceneGraphRenderer.java
@@ -0,0 +1,3257 @@
+/*
+  This work was originally done by Rob Hackett (R.Hackett at bom.gov.au) at
+  the Australian Bureau of Meteorology in the au.gov.bom.aifs.osa.charts
+  package.  Jeff McWhirter (jeffmc at unidata.ucar.edu) refactored it to
+  remove dependencies on the charts package and have it be a stand-alone
+  scene graph renderer for visad Displays
+
+Copyright (C) 2011 Australian Bureau of Meteorology.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the
+Free Software Foundation; either version 2.1 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 Lesser General Public
+License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with this library; if not, write to the Free Software Foundation, Inc.,
+59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+
+package visad.bom;
+
+
+import visad.*;
+
+import visad.java2d.DisplayImplJ2D;
+import visad.java2d.DisplayRendererJ2D;
+
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.DisplayRendererJ3D;
+
+
+import java.awt.*;
+import java.awt.geom.*;
+import java.awt.image.BufferedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+
+import java.rmi.RemoteException;
+
+import java.text.SimpleDateFormat;
+
+import java.util.ArrayList;
+
+import javax.media.j3d.*;
+
+import javax.swing.*;
+
+import javax.vecmath.Color3f;
+
+
+
+/**
+ * Render the non-texture components of a scene graph to a Graphics2D.
+ * This does not handle any 3D aspect, rotation, etc.
+ */
+public class SceneGraphRenderer {
+
+  /** default width */
+  private final int DEFAULT_WIDTH = 640;
+
+  /** default height */
+  private final int DEFAULT_HEIGHT = 512;
+
+  /** actual width */
+  protected int width = DEFAULT_WIDTH;
+
+  /** actual height */
+  protected int height = DEFAULT_HEIGHT;
+
+  /** 2D mode */
+  public static final int MODE_2D = 2;
+
+  /** 3D mode */
+  public static final int MODE_3D = 3;
+
+  // Default to 3d mode
+
+  /** actual mode */
+  private int mode = MODE_3D;
+
+  /** pixel width */
+  protected float pixelWidth = 1.0f;
+
+  /** page color */
+  private Color pageColour;
+
+  /** frame color */
+  private Color frameColour = Color.black;
+
+
+  /** Hatching */
+  Hatching hatching = new Hatching();
+
+
+
+  /** flag for monochrome */
+  protected boolean monochrome;
+
+  /** flag for gradient fill */
+  protected boolean gradientFill = false;
+
+  /** flag for transparency */
+  protected boolean useTransparency = true;
+
+  /** list of colours */
+  protected ArrayList colours = new ArrayList();
+
+  /** the line path */
+  private GeneralPath linePath;
+
+  /** plot map flag */
+  protected boolean plotMap = true;
+
+  /** the viewport */
+  protected AffineTransform viewPort;
+
+  /** display side coordinate system */
+  CoordinateSystem coordSys;
+
+  /** Mouse behavior */
+  MouseBehavior behavior;
+
+  /** line thickness */
+  private double lineThickness = 1;
+
+  /** transform to screen */
+  private boolean transformToScreenCoords = false;
+
+
+  /**
+   * Default constructor
+   */
+  public SceneGraphRenderer() {}
+
+
+  /**
+   * Get the display side CoordinateSystem
+   *
+   * @return  the display side CoordinateSystem or null
+   */
+  private CoordinateSystem getCoordinateSystem() {
+    return coordSys;
+  }
+
+
+  /**
+   * Get the viewport
+   *
+   * @param displayImpl   the display
+   * @return the viewport
+   */
+  private AffineTransform getViewport(DisplayImpl displayImpl) {
+    ProjectionControl pc = displayImpl.getProjectionControl();
+    behavior = displayImpl.getDisplayRenderer().getMouseBehavior();
+
+    double[] tstart = pc.getMatrix();
+    double[] rotArray = new double[3];
+    double[] scaleArray = new double[3];
+    double[] transArray = new double[3];
+    behavior.instance_unmake_matrix(rotArray, scaleArray, transArray, tstart);
+
+    float lScaleX = (float)scaleArray[0];
+    float lScaleY = (float)scaleArray[1];
+    float lTransX = (float)transArray[0];
+    float lTransY = (float)transArray[1];
+
+
+    // Get the panning/scanning of the location
+    // Convert the 3D viewport from the Location, into a 2D
+    // Affine transform. 
+    // get translation, then append scale NOT other way round
+    viewPort = AffineTransform.getTranslateInstance(lTransX, lTransY);
+    AffineTransform scale = AffineTransform.getScaleInstance(lScaleX,
+                              lScaleY);
+    viewPort.concatenate(scale);
+
+
+    AffineTransform rot =
+      AffineTransform.getRotateInstance(Math.toRadians(-rotArray[2]));
+    viewPort.concatenate(rot);
+
+    // Create another transform which matches the viewport
+    // coordinates to the device coordinates
+    float deviceScaleX = (float)width / 2.0f;
+    float deviceTransX = width / 2.0f;
+    float deviceTransY = height / 2.0f;
+    AffineTransform deviceTrans =
+      AffineTransform.getTranslateInstance(deviceTransX, deviceTransY);
+    AffineTransform deviceScale =
+      AffineTransform.getScaleInstance(deviceScaleX, -deviceScaleX);
+    deviceTrans.concatenate(deviceScale);
+
+    // Join the two together so that coordinates in viewport space
+    // line up with the output coordinates
+    viewPort.preConcatenate(deviceTrans);
+
+
+
+    return viewPort;
+  }
+
+
+
+  /**
+   * Return an array of Longitudes/Latitudes corresponding to the
+   * pixels of the chart. Based on the Charts CoordinateSystem and
+   * ViewPort.
+   *      In the case of an A0 chart, the number of pixels may be
+   * huge, so supply an X and Y size allowing a smaller subset to be used
+   * if necessary
+   * The purpose of this method is to allow an image to be
+   * interpolated onto the chart as an overlay (eg. sat image)
+   * @param xSize The size of the target X dimension
+   * @param ySize The size of the target Y dimension
+   * @return An array of Longitudes/Latitudes corresponding to the
+   * pixels of the chart, sampled to match the target x/y size.
+   * Based on the Charts CoordinateSystem and ViewPort
+   */
+  public float[][] getLonLatSamples(int xSize, int ySize) {
+    float xScale = (float)width / (float)xSize;
+    float yScale = (float)height / (float)ySize;
+    CoordinateSystem coordSys = getCoordinateSystem();
+    float[][] lonLatSamples = null;
+    try {
+      AffineTransform inverse = viewPort.createInverse();
+      // Get the locations of each pixel of the chart
+      float[] pixelSamples = getPixelSamples(xScale, yScale);
+      // Normalise these using the inverse of the ViewPort
+      float[] normSamples = new float[xSize * ySize * 2];
+      inverse.transform(pixelSamples, 0, normSamples, 0, xSize * ySize);
+
+      // Convert normal coordinates to lons/lats, using the
+      // chart coordinate system
+      float[][] xys = new float[3][normSamples.length / 2];
+      for (int i = 0; i < normSamples.length / 2; i++) {
+        xys[0][i] = normSamples[i * 2];
+        xys[1][i] = normSamples[i * 2 + 1];
+        xys[2][i] = 0.0f;
+      }
+
+      float[][] lonLats3d = coordSys.fromReference(xys);
+      // Finally, filter out the extraneous 3rd dimension
+      float[][] lonLats2D = {
+        lonLats3d[1], lonLats3d[0]
+      };
+      lonLatSamples = lonLats2D;
+    }
+    catch (NoninvertibleTransformException e) {
+      System.err.println("Chart.getLonLatSamples: " + e);
+    }
+    catch (VisADException e) {
+      System.err.println("Chart.getLonLatSamples: " + e);
+    }
+    return lonLatSamples;
+  }
+
+  /**
+   * Get the x/y coordinate of every pixel in the chart
+   *
+   * @param xScale the x scale
+   * @param yScale the y scale
+   * @return
+   */
+  private float[] getPixelSamples(float xScale, float yScale) {
+    int xSize = (int)((float)width / xScale);
+    int ySize = (int)((float)height / yScale);
+    float[] samples = new float[xSize * ySize * 2];
+    int cnt = 0;
+    for (int i = 0; i < xSize; i++) {
+      for (int j = 0; j < ySize; j++) {
+        samples[cnt] = i * xScale;
+        samples[cnt + 1] = j * yScale;
+        cnt += 2;
+      }
+    }
+
+    return samples;
+  }
+
+
+  /**
+   * Get a list of the colours used in the chart
+   * This may be necessary if the chart is being plotted to a medium
+   * with limited colours, eg. 8 bit PNG. The antialiasing and gradient
+   * fill used in some charts can easily grab all the available colours
+   * By taking the ones used specifically by the chart, the important
+   * colours can be reserved in the colour table ensuring that any
+   * colour loss has only minimal cosmetic effect
+   * @return The colours used by this chart
+   */
+  public Color[] getColours() {
+    Color[] colourArr = new Color[colours.size()];
+    colours.toArray(colourArr);
+
+    return colourArr;
+  }
+
+  /**
+   * Get the line width
+   *
+   * @return the line width
+   */
+  public float getLineWidth() {
+    return (float)lineThickness;
+  }
+
+  /**
+   * Create a gradient fill. If this is set with graphics.setPaint()
+   * then any shapes which are filled will have a slight gradient
+   * from darker to lighter along the line specified by x1, y1 to x2, y2
+   * @param colour The colour of the middle point of the gradient
+   * @param x1 The X coordinate of the point where the darkest part of
+   *  the gradient will be
+   * @param y1 The Y coordinate of the point where the darkest part of
+   *  the gradient will be
+   * @param x2 The X coordinate of the point where the lightest part of
+   *  the gradient will be
+   * @param y2 The Y coordinate of the point where the lightest part of
+   *  the gradient will be
+   * @return
+   */
+  private GradientPaint makeGradient(Color colour, float x1, float y1,
+                                     float x2, float y2) {
+    float[] rgb = new float[4];
+    colour.getRGBColorComponents(rgb);
+
+    // Define the difference between the lightest and darkest
+    // colours
+    float diff = 0.05f;
+    float[] darkRGB = new float[4];
+    darkRGB[0] = rgb[0] * (1.0f - diff);
+    darkRGB[1] = rgb[1] * (1.0f - diff);
+    darkRGB[2] = rgb[2] * (1.0f - diff);
+    darkRGB[3] = rgb[3];
+    float[] lightRGB = new float[4];
+    lightRGB[0] = rgb[0] * (1.0f + diff);
+    lightRGB[1] = rgb[1] * (1.0f + diff);
+    lightRGB[2] = rgb[2] * (1.0f + diff);
+    lightRGB[3] = rgb[3];
+    for (int i = 0; i < 4; i++) {
+      if (lightRGB[i] > 1.0) {
+        lightRGB[i] = 1.0f;
+      }
+    }
+    Color dark = new Color(darkRGB[0], darkRGB[1], darkRGB[2]);
+    Color light = new Color(lightRGB[0], lightRGB[1], lightRGB[2]);
+    GradientPaint gradient = new GradientPaint(x1, y1, dark, x2, y2, light);
+
+    return gradient;
+  }
+
+
+
+  /**
+   * Render the display to the graphics
+   *
+   * @param graphics  the graphics object
+   * @param display   the display to render
+   */
+  private void render(Graphics2D graphics, DisplayImpl display) {
+    copyVisadDisplay(display, graphics);
+  }
+
+
+  /**
+   * Implements the Plottable interface. This will plot the chart
+   * to a vector graphics file
+   * Don't use this method directly. It is called by the Plotter class
+   *
+   * @param graphics The java2d object used by the third party graphics
+   * library to render the graphics file
+   * @param display the display to render
+   * @param cs the display side coordinate system
+   * @param width   the width of the output
+   * @param height   the height of the output
+   */
+  public void plot(Graphics2D graphics, DisplayImpl display,
+                   CoordinateSystem cs, int width, int height) {
+
+    this.width = width;
+    this.height = height;
+
+    coordSys = cs;
+    viewPort = getViewport(display);
+
+    /*jeffmc: We don't do this for now
+    // If a pre rendered background has been supplied
+    if ( !plotMap) {
+        // Add this to the start of the SVG
+        graphics.setColor(Color.BLACK);
+        //            graphics.setColor(Color.WHITE);
+        graphics.fillRect(0, 0, width, height);
+    } else {
+        // Otherwise
+        if (gradientFill) {
+            GradientPaint oceanGradient = makeGradient(pageColour, width,
+                                              height, 0, 0);
+            graphics.setPaint(oceanGradient);
+        } else {
+            graphics.setColor(pageColour);
+        }
+        graphics.fillRect(0, 0, width, height);
+        }*/
+
+    int lineThick = (int)lineThickness;
+    graphics.setClip(0, 0, width, height);
+
+    // Render the chart
+    render(graphics, display);
+
+    // Draw a frame around the page
+    /*
+    drawEdge((Graphics2D) graphics, width, height,
+             (float) lineThickness * 2);
+    ((Graphics2D) graphics).setColor(frameColour);
+    ((Graphics2D) graphics).setStroke(getStroke(lineThick));
+    */
+    colours.add(frameColour);
+  }
+
+  /**
+   * Print the display
+   *
+   * @param graphics  Graphics to print to
+   * @param pageFormat the page format
+   * @param pageIndex  the page index
+   * @param display the display to render
+   * @param cs the display side coordinate system
+   *
+   * @return  flag for success
+   */
+  public int print(Graphics graphics, PageFormat pageFormat, int pageIndex,
+                   DisplayImpl display, CoordinateSystem cs) {
+
+    coordSys = cs;
+    viewPort = getViewport(display);
+    // Charts are ALL only 1 page
+    // You MUST do this, otherwise it prints infinite copies
+    // which can be a bad thing
+    if (pageIndex > 0) {
+      return Printable.NO_SUCH_PAGE;
+    }
+
+    // DO NOT use trasparency if chart is to be printed
+    // It causes the image to be rasterized which takes
+    // up lots of memory
+    useTransparency = false;
+
+    // Assuming a 2d graphics ...
+    if (graphics instanceof Graphics2D) {
+      Graphics2D graphics2D = (Graphics2D)graphics;
+      // Scale the chart to the page format
+      float scale = scaleToPage(pageFormat, graphics2D, width, height);
+
+      // Reduce the line thinkness to take advantage of the extra
+      // resolution of the printer
+      lineThickness = 1.0 / scale;
+
+      graphics2D.setColor(pageColour);
+      if (plotMap) {
+        graphics2D.fillRect(0, 0, width, height);
+      }
+      graphics2D.setClip(0, 0, width, height);
+
+      // Render the chart
+      render(graphics2D, display);
+
+      boolean transparent = !monochrome && useTransparency;
+      float legendScale = (float)lineThickness * 4;
+
+      drawEdge(graphics2D, width, height, (float)lineThickness * 4);
+
+      colours.add(frameColour);
+    }
+    else {
+      System.err.println("Wrong graphics type!" + " How did THAT happen?");
+    }
+
+    return Printable.PAGE_EXISTS;
+  }
+
+  /**
+   * Draw a frame around the page
+   * @param graphics
+   * @param width
+   * @param height
+   * @param thickness
+   */
+  private void drawEdge(Graphics2D graphics, int width, int height,
+                        float thickness) {
+    graphics.setColor(frameColour);
+    graphics.setStroke(getStroke(thickness));
+
+    float top = 0;
+    float bottom = (float)height;
+    float left = 0;
+    float right = (float)width;
+
+    // Cant use draw rect because we need float parameters
+    GeneralPath linePath = new GeneralPath();
+    linePath.moveTo(left, top);
+    linePath.lineTo(right, top);
+    linePath.lineTo(right, bottom);
+    linePath.lineTo(left, bottom);
+    linePath.lineTo(left, top);
+
+    graphics.draw(linePath);
+  }
+
+  /**
+   * Scale the chart so that it will fit onto the page
+   * @param pageFormat The specifications of the page
+   * @param graphics The graphics context
+   * @param xSize The width of the chart
+   * @param ySize The height of the chart
+   * @return The amount that the chart has to be rescaled to match the
+   * resolution of the printer
+   */
+  private float scaleToPage(PageFormat pageFormat, Graphics2D graphics,
+                            int xSize, int ySize) {
+    // Assume printers have a DPI of 300
+    // TODO Can this be inferred from the PrintService?
+    final int DPI = 300;
+
+    // Find out the size of the imageable part of the paper
+    // (in units of 1/72 inches)
+    double pageWidth72 = pageFormat.getImageableWidth();
+    double pageHeight72 = pageFormat.getImageableHeight();
+
+    // Scale the chart to fit this
+    double scaleX = pageWidth72 / xSize;
+    double scaleY = pageHeight72 / ySize;
+    // Choose the smallest of the 2 dimension scales 
+    // Ensures that both dimensions will fit
+    double scale = scaleX;
+    if (scaleY < scaleX) {
+      scale = scaleY;
+    }
+
+    // Translate past the non-imageable bit
+    double transX = pageFormat.getImageableX() / scale;
+    double transY = pageFormat.getImageableY() / scale;
+
+    // Stretch the chart to match the dimensions of the paper
+    graphics.scale(scale, scale);
+    graphics.translate(transX, transY);
+
+    // Calculate how small a pixel is relative to the basic
+    // unit of size
+    // There are 300 pixels per inch
+    // One unit of size is 1/72 inch
+    // Dots per unit tells us how much to shrink the line width down
+    // to make it use a single dot
+    float dotsPerUnit = (DPI / 72);
+
+    return dotsPerUnit * (float)scale;
+  }
+
+  /**
+   * Draw a reprojected shape
+   *
+   * @param vertices  vertices
+   * @param colour    default color
+   * @param width     line width
+   * @param graphics  the graphics
+   */
+  public void drawShapeReprojected(float[][] vertices, Color colour,
+                                   float width, Graphics2D graphics) {
+    drawShapeReprojected(vertices, colour, width, 0, graphics);
+  }
+
+  /**
+   * Draw a reprojected shape
+   *
+   * @param vertices  vertices
+   * @param colour    default color
+   * @param width     line width
+   * @param dashStyle line dash style
+   * @param graphics  the graphics
+   */
+  public void drawShapeReprojected(float[][] vertices, Color colour,
+                                   float width, int dashStyle,
+                                   Graphics2D graphics) {
+    drawShapeReprojected(vertices, colour, width, dashStyle, graphics, false);
+  }
+
+
+  /**
+   * Draw a reprojected shape
+   *
+   * @param vertices  vertices
+   * @param colour    default color
+   * @param width     line width
+   * @param dashStyle line dash style
+   * @param graphics  the graphics
+   * @param isScreen  vertices are in screen coordinates
+   */
+  public void drawShapeReprojected(float[][] vertices, Color colour,
+                                   float width, int dashStyle,
+                                   Graphics2D graphics, boolean isScreen) {
+
+    graphics.setColor(colour);
+    /*
+    if (dashed) {
+        float[] dash = { 2.0f * pixelWidth, 6.0f * pixelWidth };
+        graphics.setStroke(getStroke(width, dash));
+    } else {
+        graphics.setStroke(getStroke(width));
+    }
+    */
+    graphics.setStroke(
+      getStroke(width, getStrokeDash(dashStyle, pixelWidth)));
+
+
+    GeneralPath line = new GeneralPath();
+    line.moveTo(vertices[0][0], vertices[1][0]);
+    for (int j = 1; j < vertices[0].length; j++) {
+      line.lineTo(vertices[0][j], vertices[1][j]);
+    }
+    if (!isScreen) line.transform(viewPort);
+    line = clip(line);
+    graphics.setColor(colour);
+    //TODO: the colour have a 0 alpha
+    /*
+    graphics.setColor(
+      new Color(colour.getRed(), colour.getGreen(), colour.getBlue()));
+      */
+    graphics.draw(line);
+  }
+
+  /**
+   * Draw the outline of a shape onto the chart
+   * @param vertices The vertices of the shape. A 2-Dimensional array.
+   * The first dimension contains the longitude values of the shape
+   * The second dimension contains the latitude values of the shape
+   * @param colour The colour of the shape
+   * @param width The thickness of the outline
+   * @param dashStyle Set to 0 if the outline is to be drawn as dashes
+   * @param graphics The java2d graphics object supplied by the
+   * plotting/printing medium
+   * @throws VisADException
+   */
+  public void drawShape(float[][] vertices, Color colour, float width,
+                        int dashStyle, Graphics2D graphics)
+          throws VisADException {
+    // Get the Coordinate System of the current location
+    CoordinateSystem coordSys = getCoordinateSystem();
+
+    // Filter out shapes which straddle the discontinuity of
+    // a mercator projection. 
+    boolean crosses = crossesDiscontinuity(coordSys, vertices);
+    if (!crosses) {
+      // Reproject the coordinates to the native coordinate system
+      // eg. Longitude, Latitude -> X, Y
+      float[][] reprojected = coordSys.toReference(vertices);
+
+      drawShapeReprojected(reprojected, colour, width, dashStyle, graphics);
+    }
+  }
+
+  /**
+   * Draw the outline of a shape onto the chart
+   * @param vertices The vertices of the shape. A 2-Dimensional array.
+   * The first dimension contains the longitude values of the shape
+   * The second dimension contains the latitude values of the shape
+   * @param colour The colour of the shape
+   * @param width The thickness of the outline
+   * @param graphics The java2d graphics object supplied by the
+   * plotting/printing medium
+   * @throws VisADException
+   */
+  public void drawShape(float[][] vertices, Color colour, float width,
+                        Graphics2D graphics)
+          throws VisADException {
+    drawShape(vertices, colour, width, 0, graphics);
+  }
+
+  /**
+   * Fill a reprojected shape
+   *
+   * @param vertices  the vertices of the shape
+   * @param colour    the color
+   * @param graphics  the graphics
+   */
+  public void fillShapeReprojected(float[][] vertices, Color colour,
+                                   Graphics2D graphics) {
+    fillShapeReprojected(vertices, colour, graphics, false);
+  }
+
+  /**
+   * Fill a reprojected shape
+   *
+   * @param vertices  the vertices of the shape
+   * @param colour    the color
+   * @param graphics  the graphics
+   * @param isScreen  true if vertices are in screen (AWT) coordinates
+   */
+  public void fillShapeReprojected(float[][] vertices, Color colour,
+                                   Graphics2D graphics, boolean isScreen) {
+    if (gradientFill) {
+      GradientPaint gradient = makeGradient(colour, 0, 0, width, height);
+      graphics.setPaint(gradient);
+    }
+    else {
+      graphics.setColor(colour);
+    }
+    GeneralPath shape = new GeneralPath();
+    if (vertices[0].length > 0) {
+      shape.moveTo(vertices[0][0], vertices[1][0]);
+      for (int j = 1; j < vertices[0].length; j++) {
+        shape.lineTo(vertices[0][j], vertices[1][j]);
+      }
+      if (!isScreen) shape.transform(viewPort);
+      shape = clip(shape);
+      graphics.fill(shape);
+    }
+  }
+
+  /**
+   * Fill a shape onto the chart
+   * @param vertices  The vertices of the shape. A 2-Dimensional array.
+   * The first dimension contains the longitude values of the shape
+   * The second dimension contains the latitude values of the shape
+   * @param colour The colour of the shape
+   * @param graphics The java2d graphics object supplied by the
+   * plotting/printing medium
+   * @throws VisADException
+   */
+  public void fillShape(float[][] vertices, Color colour, Graphics2D graphics)
+          throws VisADException {
+    // Reproject the lats/lons into device coordinates
+    CoordinateSystem coordSys = getCoordinateSystem();
+
+    // Filter out shapes which straddle the discontinuity of
+    // a mercator projection.               
+    boolean crosses = crossesDiscontinuity(coordSys, vertices);
+    if (!crosses) {
+      float[][] reprojected = coordSys.toReference(vertices);
+
+      // Fill the shape in device coordinates
+      fillShapeReprojected(reprojected, colour, graphics);
+    }
+  }
+
+  /**
+   *  Test a polygon to see if it crosses the discontinuity of a
+   *  Mercator projection. Shapes that do this, streak across the entire
+   *  width of the chart and need to be filtered out
+   * @param coordSys The CoordinateSystem of the chart
+   * @param vertices The vertices of the polygon being tested
+   * @return true if the shape crosses the discontinuity of a
+   *  Mercator projection
+   */
+  private boolean crossesDiscontinuity(CoordinateSystem coordSys,
+                                       float[][] vertices) {
+    boolean crosses = false;
+    /*TODO:
+    if (coordSys instanceof visad.earthmap.MercatorCoordinateSystem) {
+        visad.earthmap.MercatorCoordinateSystem merc =
+            (visad.earthmap.MercatorCoordinateSystem) coordSys;
+        double  disco    = merc.getCentreLongitude() + 180.0;
+
+        float[] lonRange = range(vertices[0]);
+        if ((lonRange[0] < disco) && (lonRange[1] > disco)) {
+            return true;
+        }
+        }*/
+
+    return crosses;
+  }
+
+  /**
+   * Get the range of an array of floats
+   * @param array
+   * @return
+   */
+  private float[] range(float[] array) {
+    int numPoints = array.length;
+
+    if (numPoints <= 1) {
+      float[] range = {array[0], array[0]};
+      return range;
+    }
+    float[] clone = (float[])array.clone();
+    java.util.Arrays.sort(clone);
+
+    float[] range = new float[2];
+    range[0] = clone[0];
+    range[1] = clone[clone.length - 1];
+
+    return range;
+  }
+
+  /**
+   * Fill a reprojected shape from a texture
+   *
+   * @param vertices  the shape vertices
+   * @param texture The hatching texture to fill the shape with
+   * can be Hatching.DIAGONAL1, Hatching.DIAGONAL2,
+   * Hatching.DIAGONAL_BOTH, Hatching.HORIZONTAL = 3, Hatching.VERTICAL
+   * or Hatching.SQUARE
+   * @param graphics The java2d graphics object supplied by the
+   * plotting/printing medium
+   */
+  public void fillShapeReprojected(float[][] vertices, int texture,
+                                   Graphics2D graphics) {
+    fillShapeReprojected(vertices, texture, graphics, false);
+  }
+
+  /**
+   * Fill a reprojected shape from a texture
+   *
+   * @param vertices  the shape vertices
+   * @param texture The hatching texture to fill the shape with
+   * can be Hatching.DIAGONAL1, Hatching.DIAGONAL2,
+   * Hatching.DIAGONAL_BOTH, Hatching.HORIZONTAL = 3, Hatching.VERTICAL
+   * or Hatching.SQUARE
+   * @param graphics The java2d graphics object supplied by the
+   * plotting/printing medium
+   * @param isScreen  true if vertices are in screen (AWT) coordinates
+   */
+  public void fillShapeReprojected(float[][] vertices, int texture,
+                                   Graphics2D graphics, boolean isScreen) {
+    BufferedImage fillTexture = hatching.getPattern(texture);
+    Rectangle anchor = new Rectangle(30, 30);
+    TexturePaint hatching = new TexturePaint(fillTexture, anchor);
+    graphics.setPaint(hatching);
+
+    GeneralPath shape = new GeneralPath();
+    shape.moveTo(vertices[0][0], vertices[1][0]);
+    for (int j = 1; j < vertices[0].length; j++) {
+      shape.lineTo(vertices[0][j], vertices[1][j]);
+    }
+    graphics.setPaint(hatching);
+    if (!isScreen) shape.transform(viewPort);
+    shape = clip(shape);
+    graphics.fill(shape);
+  }
+
+  /**
+   * Fill a shape onto the chart
+   * @param data  The vertices of the shape. A 2-Dimensional array.
+   * The first dimension contains the longitude values of the shape
+   * The second dimension contains the latitude values of the shape
+   * @param texture The hatching texture to fill the shape with
+   * can be Hatching.DIAGONAL1, Hatching.DIAGONAL2,
+   * Hatching.DIAGONAL_BOTH, Hatching.HORIZONTAL = 3, Hatching.VERTICAL
+   * or Hatching.SQUARE
+   * @param graphics The java2d graphics object supplied by the
+   * plotting/printing medium
+   * @throws VisADException  problem transforming from lat/lon
+   *                         to display space
+   */
+  public void fillShape(float[][] data, int texture, Graphics2D graphics)
+          throws VisADException {
+    CoordinateSystem coordSys = getCoordinateSystem();
+    float[][] reprojected = coordSys.toReference(data);
+
+    fillShapeReprojected(reprojected, texture, graphics);
+  }
+
+  /**
+   * Draw text onto the chart
+   * @param text The text to draw onto the chart
+   * @param font The font of the text
+   * @param colour The colour of the text
+   * @param x The x position of the text (longitude)
+   * @param y The y position of the text (latitude)
+   * @param graphics The java2d graphics object supplied by the
+   * plotting/printing medium
+   * @throws VisADException
+   */
+  public void drawString(String text, Font font, Color colour, float x,
+                         float y, Graphics2D graphics)
+          throws VisADException {
+    if (text.length() < 1) {
+      return;
+    }
+
+    graphics.setColor(colour);
+
+    // Scale the font to match the pixel size of the display
+    int size = font.getSize();
+    Font scaledFont = font.deriveFont(size * pixelWidth);
+
+    double[][] coords = new double[3][1];
+    coords[0][0] = x;
+    coords[1][0] = y;
+    coords[2][0] = 0.0f;
+
+    graphics.setFont(scaledFont);
+
+    // Reproject the user coordinates to Normal X, Y coordinates
+    CoordinateSystem coordSys = getCoordinateSystem();
+    double[][] reprojected = coordSys.toReference(coords);
+
+    double[] normCoords = {reprojected[0][0], reprojected[1][0]};
+    float[] devCoords = new float[2];
+    viewPort.transform(normCoords, 0, devCoords, 0, 1);
+
+    if ((devCoords[0] > 0) && (devCoords[0] < width) && (devCoords[1] > 0) &&
+        (devCoords[1] < height)) {
+      graphics.drawString(text, devCoords[0], devCoords[1]);
+    }
+
+  }
+
+  /**
+   * Extract the Geometries from the Visad display and render them to the
+   * Graphics2D
+   *
+   * @param display  the display to copy
+   * @param graphics the graphics to render to
+   */
+  private void copyVisadDisplay(DisplayImpl display, Graphics2D graphics) {
+    int mode = -1;
+
+    if (display instanceof DisplayImplJ3D) {
+      mode = MODE_3D;
+    }
+    else if (display instanceof DisplayImplJ2D) {
+      mode = MODE_2D;
+    }
+    DisplayRenderer displayRenderer = null;
+    Object root = null;
+    // If the display is 2D
+    if (mode == MODE_2D) {
+      // Get the root VisADGroup from the display renderer
+      displayRenderer = (DisplayRendererJ2D)display.getDisplayRenderer();
+      root = (VisADGroup)((DisplayRendererJ2D)displayRenderer).getRoot();
+    }
+    else {
+      // Otherwise get the Java3d Group
+      displayRenderer = (DisplayRendererJ3D)display.getDisplayRenderer();
+      root = (Group)((DisplayRendererJ3D)displayRenderer).getRoot();
+    }
+    /*
+    try {
+        displayRenderer.setBoxOn(false);
+        displayRenderer.setScaleOn(false);
+    } catch (VisADException e) {
+        System.err.println("VectorPlotter.generate: " + e);
+    } catch (RemoteException e) {
+        System.err.println("VectorPlotter.generate: " + e);
+    }
+    */
+
+    // Recurse the plotDisplay, converting all plotted
+    // objects to vectors rendered through a Graphics2D object
+    if (mode == MODE_2D) {
+      copyGroup((VisADGroup)root, graphics);
+    }
+    else if (mode == MODE_3D) {
+      copyGroup((Group)root, graphics);
+    }
+  }
+
+  /**
+   * Recursively process each VisAD group in the display, If the group is
+   * a geometry array, stop recursing and plot it
+   *
+   * @param root - The root VisADGroup from which to recurse
+   * @param graphics  the graphics to render to
+   */
+  private void copyGroup(VisADGroup root, Graphics2D graphics) {
+    // Loop over eah of the VisAD groups children
+    for (int i = 0; i < root.numChildren(); i++) {
+      // Get the next Child
+      VisADSceneGraphObject child = root.getChild(i);
+
+      // If this child is a VisADAppearance
+      if (child instanceof VisADAppearance) {
+        VisADAppearance appearance = (VisADAppearance)child;
+        // Plot it's vertices
+        VisADGeometryArray geometry = appearance.array;
+        Color[] colours = getColours(appearance, monochrome);
+        float fsize = (geometry instanceof VisADPointArray)
+                      ? appearance.pointSize
+                      : appearance.lineWidth;
+        float thickness = fsize / 2.0f;
+
+        plot(geometry, colours, thickness, graphics);
+      }
+
+      // If this child is a VisADGroup
+      if (child instanceof VisADGroup) {
+        // Recurse this group
+        copyGroup((VisADGroup)child, graphics);
+      }
+    }
+  }
+
+  /**
+   * Recursively process each Java3d group in the display, If the group is
+   * a geometry array, stop recursing and plot it
+   *
+   * @param root - The root Java 3D Group from which to recurse
+   * @param graphics  the graphics to render to
+   */
+  private void copyGroup(Group root, Graphics2D graphics) {
+    int numChildren = 0;
+    if (root.getCapability(Group.ALLOW_CHILDREN_READ)) {
+      numChildren = root.numChildren();
+    }
+
+    // Check to see which children are rendered
+    int rendered = -1;
+    if (root instanceof Switch) {
+      rendered = ((Switch)root).getWhichChild();
+      if (rendered == -1) return; // means it's not rendered? 
+    }
+    // Loop over each of the VisAD groups children
+    for (int i = 0; i < numChildren; i++) {
+      Node child = root.getChild(i);
+
+      // Only render this node if it is Switched on
+      if ((rendered >= 0) && (rendered != i)) {
+        continue;
+      }
+      if (child instanceof Group) {
+        // Todo
+        // Check for Switches here to support layers?
+        copyGroup((Group)child, graphics);
+      }
+      else if (child instanceof Shape3D) {
+        Shape3D shape = (Shape3D)child;
+        int numGeoms = 0;
+        if (shape.getCapability(Shape3D.ALLOW_GEOMETRY_READ)) {
+          numGeoms = shape.numGeometries();
+        }
+
+        Appearance appearance = shape.getAppearance();
+
+        Color[] colours = getColours(appearance);
+        float thickness = getLineThickness(appearance);
+        int lineStyle = getLineStyle(appearance);
+        Texture texture = appearance.getTexture();
+        for (int j = 0; j < numGeoms; j++) {
+          GeometryArray geom = (GeometryArray)shape.getGeometry(j);
+          plot(geom, colours, thickness, texture, lineStyle, graphics);
+        }
+      }
+      else {
+        // System.err.println ("Unknown scene graph node:" + child.getClass().getName());
+      }
+    }
+  }
+
+  /**
+   * Get the colours from the Appearance
+   *
+   * @param appearance the appearance
+   * @param monochrome true  for monochrome
+   *
+   * @return  the array of colours for the Appearance
+   */
+  private Color[] getColours(VisADAppearance appearance, boolean monochrome) {
+    Color[] colours = null;
+    VisADGeometryArray geometry = appearance.array;
+
+    // If the geometry stores it's colours ...
+    if (geometry.colors != null) {
+      // Get each individual colour
+      int numColours = geometry.colors.length;
+      int numCoords = geometry.coordinates.length;
+      // Get the ratio of colors to points to distinguish RGB
+      // from RGBA. This is a hack until I understand why some
+      // LineArrays from a 2D display contain Alpha values
+      int cr = 3;
+      if (numColours != numCoords) {
+        cr = numColours / (numColours - numCoords);
+      }
+      colours = new Color[numColours / cr];
+      for (int j = 0; j < numColours; j += cr) {
+        float red = 0.0f;
+        float green = 0.0f;
+        float blue = 0.0f;
+        if (!monochrome) {
+          red = byteToFloat(geometry.colors[j]);
+          green = byteToFloat(geometry.colors[j + 1]);
+          blue = byteToFloat(geometry.colors[j + 2]);
+        }
+        colours[j / cr] = new Color(red, green, blue);
+      }
+    }
+    else {
+      // Otherwise fill the array with the global colour
+      float red = 0.0f;
+      float green = 0.0f;
+      float blue = 0.0f;
+      if (!monochrome) {
+        red = appearance.red;
+        green = appearance.green;
+        blue = appearance.blue;
+      }
+      colours = new Color[1];
+      colours[0] = new Color(red, green, blue);
+    }
+
+    return colours;
+  }
+
+  /**
+   * Get the colors from the Appearance
+   *
+   * @param appearance   the Appearance
+   *
+   * @return the colours
+   */
+  private Color[] getColours(Appearance appearance) {
+    Color[] colours = null;
+    ColoringAttributes colourAttr = appearance.getColoringAttributes();
+    int colourFlag = ColoringAttributes.ALLOW_COLOR_READ;
+    TransparencyAttributes transAttr = appearance.getTransparencyAttributes();
+    int transMFlag = TransparencyAttributes.ALLOW_MODE_READ;
+    int transVFlag = TransparencyAttributes.ALLOW_VALUE_READ;
+
+    if ((colourAttr != null) && (colourAttr.getCapability(colourFlag))) {
+      Color3f color3f = new Color3f();
+      colourAttr.getColor(color3f);
+      colours = new Color[1];
+      Color color3 = color3f.get();
+      float[] colourComps = new float[4];
+      colourComps = color3.getColorComponents(colourComps);
+      colourComps[3] = 1.0f;
+      /*
+      if (transAttr != null &&
+          transAttr.getCapability(transMFlag) &&
+          transAttr.getCapability(transVFlag) &&
+          transAttr.getTransparencyMode() != TransparencyAttributes.NONE) {
+          colourComps[3] = transAttr.getTransparency();
+      }
+      */
+      colours[0] = new Color(colourComps[0], colourComps[1], colourComps[2],
+                             colourComps[3]);
+    }
+
+    return colours;
+  }
+
+  /**
+   * Get the line thickness from the Appearance
+   *
+   * @param appearance  the Appearance
+   *
+   * @return  the line thickness
+   */
+  private float getLineThickness(Appearance appearance) {
+    float thickness = 0.0f;
+    LineAttributes lineAttr = appearance.getLineAttributes();
+    if (lineAttr != null && lineAttr.getCapability(LineAttributes.ALLOW_WIDTH_READ)) {
+      thickness = lineAttr.getLineWidth();
+    }
+
+    return thickness;
+  }
+
+  /**
+   * Get the line style from the Appearance
+   *
+   * @param appearance  the Appearance
+   *
+   * @return  the line style
+   */
+  private int getLineStyle(Appearance appearance) {
+    LineAttributes lineAttr = appearance.getLineAttributes();
+    int lineStyle = LineAttributes.PATTERN_SOLID;
+    if (lineAttr != null && lineAttr.getCapability(LineAttributes.ALLOW_PATTERN_READ)) {
+      lineStyle = lineAttr.getLinePattern();
+    }
+
+    return lineStyle;
+  }
+
+  /**
+   * Convert an unsigned byte into a float
+   *
+   * @param byteVal - A number between 0 and 255
+   * @return the number converted to a signed floating point number
+   */
+  private float byteToFloat(byte byteVal) {
+    float floatVal = 0.0f;
+
+    if (byteVal >= 0) {
+      floatVal = ((float)byteVal) / 256.0f;
+    }
+    else {
+      floatVal = ((float)(byteVal + 256)) / 256.0f;
+    }
+
+    return floatVal;
+  }
+
+  /**
+   * Convert a geometry array into plottable vectors
+   *
+   * @param geometryArray   The definition of the shape of the object to be
+   *                        plotted
+   * @param colours         A list of the colours of each vertex of the object
+   * @param thickness       The line thickness with which to draw the object
+   * @param graphics        The graphics to plot to
+   */
+  private void plot(VisADGeometryArray geometryArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {
+    // Draw a VisADPointArray
+    if (geometryArray instanceof VisADPointArray) {
+      VisADPointArray pointArray = (VisADPointArray)geometryArray;
+      plot(pointArray, colours, thickness, graphics);
+      // Draw a VisADLineStripArray
+    }
+    else if (geometryArray instanceof VisADLineStripArray) {
+      VisADLineStripArray lineArray = (VisADLineStripArray)geometryArray;
+      plot(lineArray, colours, thickness, graphics);
+      // Draw a VisADLineArray
+    }
+    else if (geometryArray instanceof VisADLineArray) {
+      VisADLineArray lineArray = (VisADLineArray)geometryArray;
+      plot(lineArray, colours, thickness, graphics);
+      // Draw a VisADTriangleStripArray
+    }
+    else if (geometryArray instanceof VisADTriangleStripArray) {
+      VisADTriangleStripArray triangleArray =
+        (VisADTriangleStripArray)geometryArray;
+      plot(triangleArray, colours, thickness, graphics);
+      // Draw a VisADIndexedTriangleStripArray
+    }
+    else if (geometryArray instanceof VisADIndexedTriangleStripArray) {
+      VisADIndexedTriangleStripArray triangleArray =
+        (VisADIndexedTriangleStripArray)geometryArray;
+      plot(triangleArray, colours, thickness, graphics);
+      // Draw a VisADTriangleArray
+    }
+    else if (geometryArray instanceof VisADTriangleArray) {
+      VisADTriangleArray triangleArray = (VisADTriangleArray)geometryArray;
+      plot(triangleArray, colours, thickness, graphics);
+    }
+    else {
+      // Other geometries go here
+    }
+  }
+
+  /**
+   * Convert a geometry array into plottable vectors
+   *
+   * @param geometryArray   The definition of the shape of the object to be
+   *                        plotted
+   * @param colours         A list of the colours of each vertex of the object
+   * @param thickness       The line thickness with which to draw the object
+   * @param texture         The hatching texture
+   * @param lineStyle       The line style
+   * @param graphics        The graphics to plot to
+   */
+  private void plot(GeometryArray geometryArray, Color[] colours,
+                    float thickness, Texture texture, int lineStyle,
+                    Graphics2D graphics) {
+    //System.err.println("plot:" + geometryArray.getClass().getName());
+    if (geometryArray instanceof LineArray) {
+      LineArray lineArray = (LineArray)geometryArray;
+      plot(lineArray, colours, thickness, lineStyle, graphics);
+    }
+    else if (geometryArray instanceof TriangleArray) {
+      TriangleArray triangleArray = (TriangleArray)geometryArray;
+      plot(triangleArray, colours, thickness, graphics);
+    }
+    else if (geometryArray instanceof QuadArray) {
+      QuadArray quadArray = (QuadArray)geometryArray;
+      if (texture == null) {
+        plot(quadArray, colours, thickness, graphics);
+      }
+      else {
+        //Don't do textures
+      }
+    }
+    else if (geometryArray instanceof LineStripArray) {
+      LineStripArray lineStripArray = (LineStripArray)geometryArray;
+      plot(lineStripArray, colours, thickness, lineStyle, graphics);
+    }
+    else if (geometryArray instanceof TriangleStripArray) {
+      TriangleStripArray triangleArray = (TriangleStripArray)geometryArray;
+      // Treat geometries with texture separately
+      if (texture == null) {
+        plot(triangleArray, colours, thickness, graphics);
+      }
+    }
+    else if (geometryArray instanceof IndexedTriangleStripArray) {
+      IndexedTriangleStripArray triangleArray =
+        (IndexedTriangleStripArray)geometryArray;
+      plot(triangleArray, colours, thickness, graphics);
+    } // Draw a VisADPointArray
+    else if (geometryArray instanceof PointArray) {
+      PointArray pointArray = (PointArray)geometryArray;
+      plot(pointArray, colours, thickness, graphics);
+    }
+    else {
+      // Other geometries go here
+      // System.err.println ("Unknown geometry:" + geometryArray.getClass().getName());
+    }
+  }
+
+  /**
+   * Plot a VisADPointArray into postscript format
+   *
+   * @param pointArray  The pointArray to be plotted
+   * @param colours     The colour to plot the points
+   * @param size        Size of the points
+   * @param graphics    The graphics to plot to
+   */
+  private void plot(VisADPointArray pointArray, Color[] colours, float size,
+                    Graphics2D graphics) {
+    graphics.setColor(colours[0]);
+    graphics.setStroke(getStroke(pixelWidth));
+    float[] coordinates = pointArray.coordinates;
+
+    float hsize = 0.5f * size;
+    // Loop over each point
+    for (int i = 0; i < coordinates.length / 3; i++) {
+      float normalX = coordinates[i * 3];
+      float normalY = coordinates[i * 3 + 1];
+      //graphics.fillRect((int)normalX, (int)normalY, (int)size, (int)size);
+      graphics.fill(
+        new Rectangle2D.Float(normalX - hsize, normalY - hsize, size, size));
+    }
+  }
+
+  /**
+   * Convert a VisADLineStripArray into plottable vectors
+   *
+   * @param lineArray   The VisADLineStripArray
+   * @param colours     The list of colors
+   * @param thickness   The line thickness
+   * @param graphics    The graphics
+   */
+  private void plot(VisADLineStripArray lineArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {
+    graphics.setColor(colours[0]);
+    graphics.setStroke(getStroke(thickness));
+    float[] coordinates = lineArray.coordinates;
+
+    // Get the sizes of all the "chunks"
+    int[] vertexCounts = lineArray.stripVertexCounts;
+
+    int base = 0;
+
+    // Loop over each chunk
+    for (int i = 0; i < vertexCounts.length; i++) {
+      int numCoords = vertexCounts[i];
+
+      if (i < colours.length) {
+        graphics.setColor(colours[i]);
+      }
+
+      GeneralPath path = new GeneralPath();
+      // Store the starting position of this chunk
+      path.moveTo(coordinates[base], coordinates[base + 1]);
+
+      boolean visible = false;
+      float nxLast = coordinates[base];
+      float nyLast = coordinates[base + 1];
+      // Loop over all the points in this chunk
+      for (int j = 0; j < numCoords; j++) {
+        // Get the (normalised) display coordinates
+        float nX = coordinates[base + j * 3];
+        float nY = coordinates[base + j * 3 + 1];
+        // If the line is visible, draw it
+        if (visible(nxLast, nyLast, nX, nY, graphics)) {
+          visible = true;
+        }
+        path.lineTo((float)nX, (float)nY);
+        nxLast = nX;
+        nyLast = nY;
+      }
+      if (visible) {
+        // Convert display coordinates to device coords
+        path.transform(viewPort);
+        graphics.draw(path);
+      }
+      base += 3 * numCoords;
+
+    }
+  }
+
+  /**
+   * Convert a VisADLineArray into plottable vectors
+   *
+   * @param lineArray   The VisADLineArray
+   * @param colours     The list of colors
+   * @param thickness   The line thickness
+   * @param graphics    The graphics
+   */
+  private void plot(VisADLineArray lineArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {
+    graphics.setColor(colours[0]);
+    graphics.setStroke(getStroke(thickness));
+    float[] coordinates = lineArray.coordinates;
+    int numCoords = lineArray.vertexCount;
+
+    for (int j = 0; j < numCoords; j += 2) {
+      if (j < colours.length) {
+        graphics.setColor(colours[j]);
+      }
+
+      // Get (normalised) display coordinates
+      float nX1 = coordinates[j * 3];
+      float nY1 = coordinates[j * 3 + 1];
+      float nX2 = coordinates[j * 3 + 3];
+      float nY2 = coordinates[j * 3 + 4];
+
+      // If the line is visible, draw it
+      if (visible(nX1, nY1, nX2, nY2, graphics)) {
+        GeneralPath path = new GeneralPath();
+        path.moveTo(nX1, nY1);
+        path.lineTo(nX2, nY2);
+
+        // Convert them to device coords, and plot
+        path.transform(viewPort);
+        graphics.draw(path);
+      }
+
+    }
+
+  }
+
+  /**
+   * Convert a VisADTriangleStripArray into plottable vectors
+   *
+   *
+   * @param triangleArray   The VisADTriangleStripArray
+   * @param colours         The list of colors at each vertex
+   * @param thickness       The line thickness
+   * @param graphics        The graphics
+   */
+  private void plot(VisADTriangleStripArray triangleArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {
+    graphics.setColor(colours[0]);
+    graphics.setStroke(getStroke(thickness));
+    float[] coordinates = triangleArray.coordinates;
+
+    // Get the sizes of all the "chunks"
+    int[] vertexCounts = triangleArray.stripVertexCounts;
+
+    int base = 0;
+
+    // Loop over each chunk
+    for (int i = 0; i < vertexCounts.length; i++) {
+      int numCoords = vertexCounts[i];
+
+      // Store the starting position of this chunk
+      float normalLastX = coordinates[base];
+      float normalLastY = coordinates[base + 1];
+      GeneralPath path = new GeneralPath();
+      path.moveTo(normalLastX, normalLastY);
+
+      // Loop over all the points in this chunk
+      for (int j = 0; j < numCoords; j++) {
+        float normalX = coordinates[base + j * 3];
+        float normalY = coordinates[base + j * 3 + 1];
+        normalLastX = normalX;
+        normalLastY = normalY;
+        path.lineTo(normalX, normalY);
+      }
+      path.closePath();
+      path.transform(viewPort);
+      graphics.fill(path);
+
+      base += 3 * numCoords;
+
+    }
+
+  }
+
+  /**
+   * Convert a VisADTriangleArray into plottable vectors
+   *
+   * @param triangleArray   The VisADTriangleArray
+   * @param colours         The list of colors at each vertex
+   * @param thickness       The line thickness
+   * @param graphics        The graphics
+   */
+  private void plot(VisADTriangleArray triangleArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {
+    float[] colour = new float[4];
+    if (colours != null) {
+      colour[0] = ((float)colours[0].getRed()) / 255.0f;
+      colour[1] = ((float)colours[0].getGreen()) / 255.0f;
+      colour[2] = ((float)colours[0].getBlue()) / 255.0f;
+      colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
+      if (!useTransparency) {
+        colour[3] = 1.0f;
+      }
+    }
+
+    graphics.setStroke(getStroke(thickness));
+
+    int vertexCount = triangleArray.vertexCount;
+    float[] coordinates = triangleArray.coordinates;
+
+    for (int i = 0; i < 3 * vertexCount; i += 9) {
+      // If monochrome
+      if (monochrome) {
+        // Set everything except white to black
+        monochromatise(colour);
+      }
+      Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
+      graphics.setColor(color);
+      float[][] vertices = new float[3][3];
+      vertices[0][0] = coordinates[i];
+      vertices[1][0] = coordinates[i + 1];
+      vertices[0][1] = coordinates[i + 3];
+      vertices[1][1] = coordinates[i + 4];
+      vertices[0][2] = coordinates[i + 6];
+      vertices[1][2] = coordinates[i + 7];
+      int clockwise = clockwise(vertices[0], vertices[1]);
+      if (clockwise >= 0) {
+        vertices[0] = reverseDirection(vertices[0]);
+        vertices[1] = reverseDirection(vertices[1]);
+      }
+      fillShapeReprojected(vertices, color, graphics);
+    }
+
+  }
+
+  /**
+   * Convert a VisADIndexedTriangleStripArray into plottable vectors
+   *
+   * @param triangleArray   The VisADIndexedTriangleStripArray
+   * @param colours         The list of colors at each vertex
+   * @param thickness       The line thickness
+   * @param graphics        The graphics
+   */
+  private void plot(VisADIndexedTriangleStripArray triangleArray,
+                    Color[] colours, float thickness, Graphics2D graphics) {
+    graphics.setColor(colours[0]);
+    graphics.setStroke(getStroke(thickness));
+
+    float[] coordinates = triangleArray.coordinates;
+
+    int[] indices = triangleArray.indices;
+    int[] stripVertexCounts = triangleArray.stripVertexCounts;
+
+    int base = 0;
+
+    for (int strip = 0; strip < stripVertexCounts.length; strip++) {
+      if (strip < colours.length) {
+        graphics.setColor(colours[strip]);
+      }
+
+      int count = stripVertexCounts[strip];
+      int index0 = indices[base];
+      int index1 = indices[base + 1];
+
+      GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+      boolean visible = false;
+      for (int i = base + 2; i < base + count; i++) {
+        int index2 = indices[i];
+
+        float normalX0 = coordinates[3 * index0];
+        float normalY0 = coordinates[3 * index0 + 1];
+        float normalX1 = coordinates[3 * index1];
+        float normalY1 = coordinates[3 * index1 + 1];
+        float normalX2 = coordinates[3 * index2];
+        float normalY2 = coordinates[3 * index2 + 1];
+
+        // If any of the triangle is within the
+        // area of interest, plot it
+        // if (visible(xCoords, yCoords, graphics)) {
+        path.moveTo(normalX0, normalY0);
+        path.lineTo(normalX1, normalY1);
+        path.lineTo(normalX2, normalY2);
+        visible = true;
+        //                              }
+
+        index0 = index1;
+        index1 = index2;
+      }
+      if (visible) {
+        path.transform(viewPort);
+        path.closePath();
+        ////////                                graphics.fill(path);
+      }
+      base += count;
+    }
+  }
+
+  /**
+   * Convert a LineStripArray into plottable vectors
+   *
+   * @param lineArray   The LineStripArray
+   * @param colours     The list of colors at each vertex
+   * @param thickness   The line thickness
+   * @param lineStyle   The line style
+   * @param graphics    The graphics
+   */
+  private void plot(LineStripArray lineArray, Color[] colours,
+                    float thickness, int lineStyle, Graphics2D graphics) {
+
+    // Temporary variables for retrieving coordinates
+    int vertexCount = lineArray.getVertexCount();
+    boolean hasAlpha = hasAlpha(lineArray);
+    boolean byRef = isByReference(lineArray);
+    int nCoords = 3 * vertexCount;
+    float[] coordinates = null;
+
+    // Get the coordinates
+    if (byRef) {
+      coordinates = lineArray.getCoordRefFloat();
+    }
+    else {
+      coordinates = new float[nCoords];
+      lineArray.getCoordinates(0, coordinates);
+    }
+
+    coordinates = transformToScreen(coordinates);
+
+    int numStrips = lineArray.getNumStrips();
+    int[] vertexCounts = new int[numStrips];
+    lineArray.getStripVertexCounts(vertexCounts);
+
+    int base = 0;
+    int baseColor = 0;
+
+    float[] colour = new float[4];
+    byte[] refColours = null;
+    int numRefColours = (hasAlpha)
+                        ? 4
+                        : 3;
+    // If a colour was supplied, use it
+    if (colours != null) {
+      colour[0] = ((float)colours[0].getRed()) / 255.0f;
+      colour[1] = ((float)colours[0].getGreen()) / 255.0f;
+      colour[2] = ((float)colours[0].getBlue()) / 255.0f;
+      colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
+    }
+    else {
+      // Get the color array if by ref
+      if (byRef) {
+        // DisplayImplJ3D stores ref as bytes
+        refColours = lineArray.getColorRefByte();
+        for (int i = 0; i < numRefColours; i++) {
+          colour[i] = byteToFloat(refColours[i]);
+        }
+      }
+      else {
+        lineArray.getColor(0, colour);
+      }
+    }
+    if (!hasAlpha || !useTransparency) {
+      colour[3] = 1.0f;
+    }
+
+    // If monochrome
+    if (monochrome) {
+      // Set everything except white to black
+      monochromatise(colour);
+    }
+    graphics.setStroke(
+      getStroke(thickness, getStrokeDash(lineStyle, pixelWidth)));
+
+    Color lastColor = new Color(colour[0], colour[1], colour[2], colour[3]);
+
+    // Loop over each chunk
+    for (int i = 0; i < vertexCounts.length; i++) {
+      int numCoords = vertexCounts[i];
+      linePath = new GeneralPath();
+      graphics.setColor(lastColor);
+      //VisAD stores strips as one complete section so we have to draw each
+      //segment
+      for (int seg = 0; seg < numCoords-1; seg++) {
+        // Attempt to get the color from the geometry
+        if (colours == null) {
+          // Get the color array
+          if (refColours != null) {
+            for (int j = 0; j < numRefColours; j++) {
+              colour[j] = byteToFloat(refColours[baseColor + j]);
+            }
+          }
+          else {
+            lineArray.getColor(base, colour);
+          }
+        }
+        if (!useTransparency || !hasAlpha) {
+          colour[3] = 1.0f;
+        }
+        // If monochrome
+        if (monochrome) {
+          // Set everything except white to black
+          monochromatise(colour);
+        }
+        Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
+        // Draw lines of one colour in a sigle GeneralPath
+        // This makes the resulting image MUCH more slick
+        // when plotting many small lines (eg. observations)
+        if (!color.equals(lastColor)) {
+          graphics.setColor(lastColor);
+          lastColor = color;
+          if (!transformToScreenCoords) linePath.transform(viewPort);
+          //linePath = clip(linePath);
+          graphics.draw(linePath);
+          linePath = new GeneralPath();
+        }
+
+        // Get the (normalised) display coordinates
+        float nX1 = coordinates[base];
+        float nY1 = coordinates[base + 1];
+        float nX2 = coordinates[base + 3];
+        float nY2 = coordinates[base + 4];
+        if (visible(nX1, nY1, nX2, nY2, graphics, transformToScreenCoords)) {
+          linePath.moveTo(nX1, nY1);
+          linePath.lineTo(nX2, nY2);
+        }
+        base += 3;
+        baseColor += numRefColours;
+      }
+      // Translate them to device coordinates and plot
+      if (!transformToScreenCoords) linePath.transform(viewPort);
+      //linePath = clip(linePath);
+      graphics.draw(linePath);
+      base += 3;
+      baseColor += numRefColours;
+    }
+  }
+
+  /**
+   * Convert a LineArray into plottable vectors
+   *
+   * @param lineArray   The LineStripArray
+   * @param colours     The list of colors at each vertex
+   * @param thickness   The line thickness
+   * @param lineStyle   The line style
+   * @param graphics    The graphics
+   */
+  private void plot(LineArray lineArray, Color[] colours, float thickness,
+                    int lineStyle, Graphics2D graphics) {
+
+    boolean hasAlpha = hasAlpha(lineArray);
+    boolean byRef = isByReference(lineArray);
+    float[] colour = new float[4];
+    byte[] refColours = null;
+    int numRefColours = (hasAlpha)
+                        ? 4
+                        : 3;
+    int baseColor = 0;
+    // If a colour was supplied, use it
+    if (colours != null) {
+      colour[0] = ((float)colours[0].getRed()) / 255.0f;
+      colour[1] = ((float)colours[0].getGreen()) / 255.0f;
+      colour[2] = ((float)colours[0].getBlue()) / 255.0f;
+      colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
+    }
+    else {
+      //lineArray.getColor(0, colour);
+      if (byRef) {
+        // DisplayImplJ3D stores ref as bytes
+        refColours = lineArray.getColorRefByte();
+        for (int i = 0; i < numRefColours; i++) {
+          colour[i] = byteToFloat(refColours[i]);
+        }
+      }
+      else {
+        lineArray.getColor(0, colour);
+      }
+    }
+    if (!useTransparency || !hasAlpha) {
+      colour[3] = 1.0f;
+    }
+
+    // If monochrome
+    if (monochrome) {
+      // Set everything except white to black
+      monochromatise(colour);
+    }
+    /*
+    if (lineStyle == LineAttributes.PATTERN_DASH) {
+        // float[] dash = { 24.0f * pixelWidth, 8.0f * pixelWidth };
+        float size  = (float) Math.sqrt(width * width + height * height);
+        float scale = size / 3000.0f;
+        // System.err.println(size + "/" + scale + "Width: " + width + "/"
+        //                    + height);
+        float[] dash = { 18.0f * scale, 6.0f * scale };
+        graphics.setStroke(getStroke(thickness * .5f, dash));
+    } else {
+        graphics.setStroke(getStroke(thickness * .5f));
+    }
+    */
+    //graphics.setStroke(
+    //  getStroke(thickness * .5f, getStrokeDash(lineStyle, pixelWidth)));
+    graphics.setStroke(
+      getStroke(thickness, getStrokeDash(lineStyle, pixelWidth)));
+
+    // Temporary variables for retrieving coordinates
+    int vertexCount = lineArray.getVertexCount();
+    int nCoords = 3 * vertexCount;
+    float[] coordinates = null;
+
+    // Get the coordinates
+    if (byRef) {
+      coordinates = lineArray.getCoordRefFloat();
+    }
+    else {
+      coordinates = new float[nCoords];
+      lineArray.getCoordinates(0, coordinates);
+    }
+
+    /*
+    float[] coordinates = new float[vertexCount * 3];
+    lineArray.getCoordinates(0, coordinates);
+    */
+    coordinates = transformToScreen(coordinates);
+
+    Color lastColor = new Color(colour[0], colour[1], colour[2], colour[3]);
+    linePath = new GeneralPath();
+    graphics.setColor(lastColor);
+    for (int j = 0; j < vertexCount; j += 2) {
+      if (colours == null) {
+        //lineArray.getColor(i, colour);
+        if (refColours != null) { // means we are BY_REFERENCE
+          for (int i = 0; i < numRefColours; i++) {
+            colour[i] = byteToFloat(refColours[baseColor + i]);
+          }
+        }
+        else {
+          lineArray.getColor(j, colour);
+        }
+      }
+      if (!useTransparency || !hasAlpha) {
+        colour[3] = 1.0f;
+      }
+      // If monochrome
+      if (monochrome) {
+        // Set everything except white to black
+        monochromatise(colour);
+      }
+
+      Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
+      // Draw lines of one colour in a sigle GeneralPath
+      // This makes the resulting image MUCH more slick
+      // when plotting many small lines (eg. observations)
+      if (!color.equals(lastColor)) {
+        graphics.setColor(lastColor);
+        lastColor = color;
+        if (!transformToScreenCoords) linePath.transform(viewPort);
+        //linePath = clip(linePath);
+        graphics.draw(linePath);
+        linePath = new GeneralPath();
+      }
+
+      // Get the (normalised) display coordinates
+      float nX1 = coordinates[j * 3];
+      float nY1 = coordinates[j * 3 + 1];
+      float nX2 = coordinates[j * 3 + 3];
+      float nY2 = coordinates[j * 3 + 4];
+      if (!visible(nX1, nY1, nX2, nY2, graphics, transformToScreenCoords)) {
+        continue;
+      }
+      linePath.moveTo(nX1, nY1);
+      linePath.lineTo(nX2, nY2);
+      baseColor += numRefColours * 2;
+    }
+    // Translate them to device coordinates and plot
+    if (!transformToScreenCoords) linePath.transform(viewPort);
+    //linePath = clip(linePath);
+    graphics.draw(linePath);
+
+  }
+
+  /**
+   * Convert a TriangleStripArray into plottable vectors
+   *
+   * @param triangleArray   The TriangleStripArray
+   * @param colours     The list of colors at each vertex
+   * @param thickness   The line thickness
+   * @param graphics    The graphics
+   */
+  private void plot(TriangleStripArray triangleArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {
+
+    int vertexFormat = triangleArray.getVertexFormat();
+    if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2)
+        == GeometryArray.TEXTURE_COORDINATE_2) {
+      return;
+    }
+    boolean hasAlpha = hasAlpha(triangleArray);
+    boolean byRef = isByReference(triangleArray);
+    byte[] refColours = null;
+    int numRefColours = (hasAlpha)
+                        ? 4
+                        : 3;
+    int baseColor = 0;
+    float[] colour = new float[4];
+    if (colours != null) {
+      colour[0] = ((float)colours[0].getRed()) / 255.0f;
+      colour[1] = ((float)colours[0].getGreen()) / 255.0f;
+      colour[2] = ((float)colours[0].getBlue()) / 255.0f;
+      colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
+    }
+    else {
+      if (byRef) {
+        // DisplayImplJ3D stores ref as bytes
+        refColours = triangleArray.getColorRefByte();
+      }
+    }
+    if (!useTransparency || !hasAlpha) {
+      colour[3] = 1.0f;
+    }
+    // If monochrome
+    if (monochrome) {
+      // Set everything except white to black
+      monochromatise(colour);
+    }
+
+    graphics.setStroke(getStroke(thickness));
+    // Temporary variables for retrieving coordinates
+    int vertexCount = triangleArray.getVertexCount();
+    int nCoords = 3 * vertexCount;
+    float[] coordinates = null;
+
+    // Get the coordinates
+    if (byRef) {
+      coordinates = triangleArray.getCoordRefFloat();
+    }
+    else {
+      coordinates = new float[nCoords];
+      triangleArray.getCoordinates(0, coordinates);
+    }
+    coordinates = transformToScreen(coordinates);
+
+    int cCount = 0;
+
+    // Find out how many strips
+    int numStrips = triangleArray.getNumStrips();
+    // Get the sizes of each strip
+    int[] vertexCounts = new int[numStrips];
+    triangleArray.getStripVertexCounts(vertexCounts);
+
+    int base = 0;
+    // Loop over each strip
+    for (int i = 0; i < numStrips; i++) {
+      int numCoords = vertexCounts[i];
+      if (colours == null) {
+        //triangleArray.getColor(cCount++, colour);
+        if (refColours != null) { // means we are BY_REFERENCE
+          for (int j = 0; j < numRefColours; j++) {
+            colour[j] = byteToFloat(refColours[baseColor + j]);
+          }
+        }
+        else {
+          triangleArray.getColor(cCount++, colour);
+        }
+      }
+      if (!useTransparency || !hasAlpha) {
+        colour[3] = 1.0f;
+      }
+      // If monochrome
+      if (monochrome) {
+        // Set everything except white to black
+        monochromatise(colour);
+      }
+      Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
+      graphics.setColor(color);
+
+      float lastNormX2 = coordinates[base];
+      float lastNormY2 = coordinates[base + 1];
+      float lastNormX1 = coordinates[base + 3];
+      float lastNormY1 = coordinates[base + 3 + 1];
+      for (int j = 2; j < numCoords; j++) {
+
+        float normalX = coordinates[base + j * 3];
+        float normalY = coordinates[base + j * 3 + 1];
+        float[][] triangle = new float[3][3];
+        triangle[0][0] = lastNormX1;
+        triangle[1][0] = lastNormY1;
+        triangle[0][1] = lastNormX2;
+        triangle[1][1] = lastNormY2;
+        triangle[0][2] = normalX;
+        triangle[1][2] = normalY;
+        lastNormX2 = lastNormX1;
+        lastNormX1 = normalX;
+        lastNormY2 = lastNormY1;
+        lastNormY1 = normalY;
+        fillShapeReprojected(
+          triangle, color, graphics, transformToScreenCoords);
+      }
+      base += 3 * numCoords;
+      baseColor += numRefColours * numCoords;
+    }
+
+  }
+
+  /**
+   * Convert a TriangleArray into plottable vectors
+   *
+   * @param triangleArray   The TriangleArray
+   * @param colours     The list of colors at each vertex
+   * @param thickness   The line thickness
+   * @param graphics    The graphics
+   */
+  private void plot(TriangleArray triangleArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {
+
+    boolean hasAlpha = hasAlpha(triangleArray);
+    boolean byRef = isByReference(triangleArray);
+    byte[] refColours = null;
+    int numRefColours = (hasAlpha)
+                        ? 4
+                        : 3;
+    int baseColor = 0;
+    float[] colour = new float[4];
+    if (colours != null) {
+      colour[0] = ((float)colours[0].getRed()) / 255.0f;
+      colour[1] = ((float)colours[0].getGreen()) / 255.0f;
+      colour[2] = ((float)colours[0].getBlue()) / 255.0f;
+      colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
+    }
+    else {
+      //triangleArray.getColor(0, colour);
+      if (byRef) {
+        // DisplayImplJ3D stores ref as bytes
+        refColours = triangleArray.getColorRefByte();
+      }
+    }
+    if (!hasAlpha || !useTransparency) {
+      colour[3] = 1.0f;
+    }
+
+    // If monochrome
+    if (monochrome) {
+      // Set everything except white to black
+      monochromatise(colour);
+    }
+
+    graphics.setStroke(getStroke(thickness));
+
+    // Temporary variables for retrieving coordinates
+    int vertexCount = triangleArray.getVertexCount();
+    int nCoords = 3 * vertexCount;
+    float[] coordinates = null;
+
+    // Get the coordinates
+    if (byRef) {
+      coordinates = triangleArray.getCoordRefFloat();
+    }
+    else {
+      coordinates = new float[nCoords];
+      triangleArray.getCoordinates(0, coordinates);
+    }
+
+    coordinates = transformToScreen(coordinates);
+
+    for (int i = 0; i < 3 * vertexCount; i += 9) {
+      if (colours == null) {
+        //triangleArray.getColor(cCount++, colour);
+        if (refColours != null) { // means we are BY_REFERENCE
+          for (int j = 0; j < numRefColours; j++) {
+            colour[j] = byteToFloat(refColours[baseColor + j]);
+          }
+        }
+        else {
+          //if (i < vertexCount * 3) {  is this necessary?
+          triangleArray.getColor(i / 3, colour);
+        }
+      }
+      // If monochrome
+      if (!useTransparency || !hasAlpha) {
+        colour[3] = 1.0f;
+      }
+      if (monochrome) {
+        // Set everything except white to black 
+        monochromatise(colour);
+      }
+      Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
+      graphics.setColor(color);
+
+      // Get the (normalised) display coordinates
+      float[][] vertices = new float[2][3];
+      vertices[0][0] = coordinates[i];
+      vertices[1][0] = coordinates[i + 1];
+      vertices[0][1] = coordinates[i + 3];
+      vertices[1][1] = coordinates[i + 4];
+      vertices[0][2] = coordinates[i + 6];
+      vertices[1][2] = coordinates[i + 7];
+      // Must be clockwise (batik screws up otherwise)
+      int clockwise = clockwise(vertices[0], vertices[1]);
+      if (clockwise >= 0) {
+        vertices[0] = reverseDirection(vertices[0]);
+        vertices[1] = reverseDirection(vertices[1]);
+      }
+      fillShapeReprojected(
+        vertices, color, graphics, transformToScreenCoords);
+      baseColor += 3 * numRefColours;
+    }
+  }
+
+  /**
+   * Convert a QuadArray into plottable vectors
+   *
+   * @param quadArray   The QuadArray
+   * @param colours     The list of colors at each vertex
+   * @param thickness   The line thickness
+   * @param graphics    The graphics
+   */
+  private void plot(QuadArray quadArray, Color[] colours, float thickness,
+                    Graphics2D graphics) {
+    boolean hasAlpha = hasAlpha(quadArray);
+    boolean byRef = isByReference(quadArray);
+    byte[] refColours = null;
+    int numRefColours = (hasAlpha)
+                        ? 4
+                        : 3;
+    int baseColor = 0;
+    float[] colour = new float[4];
+    if (colours != null) {
+      colour[0] = ((float)colours[0].getRed()) / 255.0f;
+      colour[1] = ((float)colours[0].getGreen()) / 255.0f;
+      colour[2] = ((float)colours[0].getBlue()) / 255.0f;
+      colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
+    }
+    else {
+      if (byRef) {
+        // DisplayImplJ3D stores ref as bytes
+        refColours = quadArray.getColorRefByte();
+      }
+    }
+    if (!useTransparency || !hasAlpha) {
+      colour[3] = 1.0f;
+    }
+    // If monochrome
+    if (monochrome) {
+      // Set everything except white to black
+      monochromatise(colour);
+    }
+
+
+    graphics.setStroke(getStroke(thickness));
+
+    // Temporary variables for retrieving coordinates
+    int vertexCount = quadArray.getVertexCount();
+    int nCoords = 3 * vertexCount;
+    float[] coordinates = null;
+
+    // Get the coordinates
+    if (byRef) {
+      coordinates = quadArray.getCoordRefFloat();
+    }
+    else {
+      coordinates = new float[nCoords];
+      quadArray.getCoordinates(0, coordinates);
+    }
+    coordinates = transformToScreen(coordinates);
+
+    //        System.err.println("quad:" + vertexCount);
+    for (int i = 0; i < 3 * vertexCount; i += 12) {
+      if (colours == null) {
+        //triangleArray.getColor(cCount++, colour);
+        if (refColours != null) { // means we are BY_REFERENCE
+          for (int j = 0; j < numRefColours; j++) {
+            colour[j] = byteToFloat(refColours[baseColor + j]);
+          }
+        }
+        else {
+          //if (i < vertexCount * 4) {  is this necessary?
+          quadArray.getColor(i / 4, colour);
+        }
+      }
+      if (!useTransparency || !hasAlpha) {
+        colour[3] = 1.0f;
+      }
+      // If monochrome
+      if (monochrome) {
+        // Set everything except white to black
+        monochromatise(colour);
+      }
+      Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
+      graphics.setColor(color);
+
+      // Get the (normalised) display coordinates
+      float[][] vertices = new float[2][4];
+      vertices[0][0] = coordinates[i];
+      vertices[1][0] = coordinates[i + 1];
+      vertices[0][1] = coordinates[i + 3];
+      vertices[1][1] = coordinates[i + 4];
+      vertices[0][2] = coordinates[i + 6];
+      vertices[1][2] = coordinates[i + 7];
+      vertices[0][3] = coordinates[i + 9];
+      vertices[1][3] = coordinates[i + 10];
+      // Must be clockwise (batik screws up otherwise)
+      int clockwise = clockwise(vertices[0], vertices[1]);
+      if (clockwise >= 0) {
+        vertices[0] = reverseDirection(vertices[0]);
+        vertices[1] = reverseDirection(vertices[1]);
+      }
+      fillShapeReprojected(
+        vertices, color, graphics, transformToScreenCoords);
+      baseColor += 4 * numRefColours;
+    }
+  }
+
+  /**
+   * Plot a PointArray into postscript format
+   *
+   * @param pointArray  The pointArray to be plotted
+   * @param colours     The colour to plot the points
+   * @param size        Size of the points
+   * @param graphics    The graphics to plot to
+   */
+  private void plot(PointArray pointArray, Color[] colours, float size,
+                    Graphics2D graphics) {
+    boolean hasAlpha = hasAlpha(pointArray);
+    boolean byRef = isByReference(pointArray);
+    byte[] refColours = null;
+    int numRefColours = (hasAlpha)
+                        ? 4
+                        : 3;
+    int baseColor = 0;
+    float[] colour = new float[4];
+    if (colours != null) {
+      colour[0] = ((float)colours[0].getRed()) / 255.0f;
+      colour[1] = ((float)colours[0].getGreen()) / 255.0f;
+      colour[2] = ((float)colours[0].getBlue()) / 255.0f;
+      colour[3] = ((float)colours[0].getAlpha()) / 255.0f;
+    } else {
+      if (byRef) {
+        // DisplayImplJ3D stores ref as bytes
+        refColours = pointArray.getColorRefByte();
+      }
+    }
+    if (!hasAlpha || !useTransparency) {
+      colour[3] = 1.0f;
+    }
+
+    // If monochrome
+    if (monochrome) {
+      // Set everything except white to black
+      monochromatise(colour);
+    }
+
+    // Temporary variables for retrieving coordinates
+    int vertexCount = pointArray.getVertexCount();
+    int nCoords = 3 * vertexCount;
+    float[] coordinates = null;
+
+    // Get the coordinates
+    if (byRef) {
+      coordinates = pointArray.getCoordRefFloat();
+    }
+    else {
+      coordinates = new float[nCoords];
+      pointArray.getCoordinates(0, coordinates);
+    }
+    coordinates = transformToScreen(coordinates);
+    graphics.setStroke(getStroke(size));
+    float hsize = 0.5f * size;
+
+    // Loop over each point
+    for (int i = 0; i < vertexCount; i++) {
+      float x = coordinates[i * 3];
+      float y = coordinates[i * 3 + 1];
+      float[] vertices = {x, y};
+      if (!transformToScreenCoords) {
+        float[] device = new float[2];
+        viewPort.transform(new float[] {x, y}, 0, vertices, 0, 1);
+      }
+      if (colours == null) {
+        //pointArray.getColor(i, colour);
+        //triangleArray.getColor(cCount++, colour);
+        if (refColours != null) { // means we are BY_REFERENCE
+          for (int j = 0; j < numRefColours; j++) {
+            colour[j] = byteToFloat(refColours[i * numRefColours + j]);
+          }
+        }
+        else {
+          pointArray.getColor(i, colour);
+        }
+      }
+      if (!useTransparency || !hasAlpha) {
+        colour[3] = 1.0f;
+      }
+      // If monochrome
+      if (monochrome) {
+        // Set everything except white to black
+        monochromatise(colour);
+      }
+      Color color = new Color(colour[0], colour[1], colour[2], colour[3]);
+      graphics.setColor(color);
+      graphics.fill(new Rectangle2D.Float(x - hsize, y - hsize, size, size));
+      /*
+      graphics.setStroke(getStroke(1));
+      graphics.fillRect(
+        (int)vertices[0], (int)vertices[1], (int)size, (int)size);
+        */
+    }
+  }
+
+  /**
+   * Get the number of colour components for the geometry array
+   * @param array the array to check
+   *
+   * @return true if vertexFormat has COLOR_4
+   */
+  private boolean hasAlpha(GeometryArray array) {
+    int vertexFormat = array.getVertexFormat();
+    if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Get whether this array uses BY_REFERENCE for it's coordinates and colors
+   * @param array the array to check
+   *
+   * @return true if vertexFormat has BY_REFERENCE
+   */
+  private boolean isByReference(GeometryArray array) {
+    int vertexFormat = array.getVertexFormat();
+    if ((vertexFormat & GeometryArray.BY_REFERENCE) != 0) {
+      return true;
+    }
+    return false;
+  }
+
+
+  /**
+   * Convert an IndexedTriangleStripArray into plottable vectors
+   *
+   * @param triangleArray   The IndexedTriangleStripArray
+   * @param colours     The list of colors at each vertex
+   * @param thickness   The line thickness
+   * @param graphics    The graphics
+   */
+  private void plot(IndexedTriangleStripArray triangleArray, Color[] colours,
+                    float thickness, Graphics2D graphics) {}
+
+  /**
+   * Get the BasicStroke with the given thickness
+   *
+   * @param thickness the thickness
+   *
+   * @return  the BasicStroke
+   */
+  private BasicStroke getStroke(float thickness) {
+    BasicStroke stroke = new BasicStroke(thickness * (float)lineThickness,
+                                         BasicStroke.CAP_BUTT,
+                                         BasicStroke.JOIN_MITER);
+
+    return stroke;
+  }
+
+  /**
+   * Get the BasicStroke with the given thickness and dash style
+   *
+   * @param thickness  the line thickness
+   * @param dash       the dash array
+   *
+   * @return  the stroke
+   */
+  private BasicStroke getStroke(float thickness, float[] dash) {
+    BasicStroke stroke = new BasicStroke(thickness * (float)lineThickness,
+                                         BasicStroke.CAP_ROUND,
+                                         BasicStroke.JOIN_ROUND, 1.0f, dash,
+                                         5.0f);
+
+    return stroke;
+  }
+
+  /**
+   * Make colours monochromatic by setting everything except white to
+   * black
+   *
+   * @param colour color to monochromatise
+   */
+  private void monochromatise(float[] colour) {
+    if ((colour[0] < 0.8f) && (colour[1] < 0.8f) && (colour[2] < 0.8f)) {
+      colour[0] = 0.0f;
+      colour[1] = 0.0f;
+      colour[2] = 0.0f;
+    }
+
+    // Remove transparency
+    if (colour.length == 4) {
+      colour[3] = 1.0f;
+    }
+  }
+
+  /**
+   * Test the vertices to see if they form a clockwise or anticlockwise
+   * shape.
+   *
+   * @param xCoords  the x coords
+   * @param yCoords  the y coords
+   *
+   * @return 1 means clockwise, -1 means anticlockwise, 0 means
+   *  indeterminate
+   */
+  private int clockwise(float[] xCoords, float[] yCoords) {
+    int ccw = 0;
+    int numCoords = xCoords.length;
+    for (int i = 0; i < numCoords; i++) {
+      int j = (i + 1) % numCoords;
+      int k = (i + 2) % numCoords;
+      float crossProduct = (xCoords[j] - xCoords[i]) *
+                           (yCoords[k] - yCoords[j]);
+      crossProduct -= (yCoords[j] - yCoords[i]) * (xCoords[k] - xCoords[j]);
+
+      if (crossProduct > 0) {
+        ccw++;
+      }
+      else if (crossProduct < 0) {
+        ccw--;
+      }
+    }
+
+    int clockwise = 0;
+    if (ccw > 0) {
+      clockwise = -1;
+    }
+    else if (ccw < 0) {
+      clockwise = 1;
+    }
+
+    return clockwise;
+  }
+
+  /**
+   * Test the vertices to see if they form a clockwise or anticlockwise
+   * shape.
+   * @param xCoords  the x coords
+   * @param yCoords  the y coords
+   *
+   * @return 1 means clockwise, -1 means anticlockwise, 0 means
+   *  indeterminate
+   */
+  private int clockwise(int[] xCoords, int[] yCoords) {
+    int ccw = 0;
+    int numCoords = xCoords.length;
+    for (int i = 0; i < numCoords; i++) {
+      int j = (i + 1) % numCoords;
+      int k = (i + 2) % numCoords;
+      float crossProduct = (xCoords[j] - xCoords[i]) *
+                           (yCoords[k] - yCoords[j]);
+      crossProduct -= (yCoords[j] - yCoords[i]) * (xCoords[k] - xCoords[j]);
+
+      if (crossProduct > 0) {
+        ccw++;
+      }
+      else if (crossProduct < 0) {
+        ccw--;
+      }
+    }
+
+    int clockwise = 0;
+    if (ccw > 0) {
+      clockwise = -1;
+    }
+    else if (ccw < 0) {
+      clockwise = 1;
+    }
+
+    return clockwise;
+  }
+
+  /**
+   * Reverse the order of the vertices in a triangle from clockwise to
+   * anticlockwise and vice versa
+   *
+   * @param coords    The x (or y) coordinates of the triangle
+   *
+   * @return  the reversed coordinates
+   */
+  private int[] reverseDirection(int[] coords) {
+    int temp = coords[2];
+    coords[2] = coords[1];
+    coords[1] = temp;
+
+    return coords;
+  }
+
+  /**
+   * Reverse the order of the vertices in a polygon from clockwise to
+   * anticlockwise and vice versa
+   *
+   * @param coords    The x (or y) coordinates of the triangle
+   *
+   * @return  the reversed coordinates
+   */
+  private float[] reverseDirection(float[] coords) {
+    int numCoords = coords.length;
+    float[] temp = new float[numCoords];
+    for (int i = 0; i < numCoords; i++) {
+      temp[i] = coords[numCoords - i - 1];
+    }
+
+    return temp;
+  }
+
+  /**
+   * Long winded way of checking if any part of an area is visible in an
+   * area bounded by the graphics2d clipping region Significantly
+   * increases the plotting time, but significantly decreases the size of
+   * the resulting file Probably better to investigate ways of clipping
+   * the lines using DelaunayCustom.clip()
+   *
+   * @param x1   The X coordinate of the start of the line
+   * @param y1   The Y coordinate of the start of the line
+   * @param x2   The X coordinate of the end of the line
+   * @param y2   The Y coordinate of the end of the line
+   * @param graphics  the graphics
+   * @return a boolean flag, true means that the triangle is at least
+   *          partially visible
+   *
+   * @return true if visible
+   */
+  private boolean visible(double x1, double y1, double x2, double y2,
+                          Graphics2D graphics) {
+    return visible(x1, y1, x2, y2, graphics, false);
+  }
+
+  /**
+   * Long winded way of checking if any part of an area is visible in an
+   * area bounded by the graphics2d clipping region Significantly
+   * increases the plotting time, but significantly decreases the size of
+   * the resulting file Probably better to investigate ways of clipping
+   * the lines using DelaunayCustom.clip()
+   *
+   * @param x1   The X coordinate of the start of the line
+   * @param y1   The Y coordinate of the start of the line
+   * @param x2   The X coordinate of the end of the line
+   * @param y2   The Y coordinate of the end of the line
+   * @param graphics  the graphics
+   * @param isScreen  true to transform xy to graphics space
+   * @return a boolean flag, true means that the triangle is at least
+   *          partially visible
+   *
+   * @return true if visible
+   */
+  private boolean visible(double x1, double y1, double x2, double y2,
+                          Graphics2D graphics, boolean isScreen) {
+    double[] coords = {x1, y1, x2, y2};
+    double[] device = new double[4];
+    if (!isScreen) {
+      viewPort.transform(coords, 0, device, 0, 2);
+    }
+    else {
+      device = coords;
+    }
+
+    // Find the area occupied by the line
+    double xMin = Math.min(device[0], device[2]);
+    double xMax = Math.max(device[0], device[2]);
+    double yMin = Math.min(device[1], device[3]);
+    double yMax = Math.max(device[1], device[3]);
+
+    int x = (int)xMin;
+    int y = (int)yMin;
+    // Must add 1 to these, because otherwise a vertical or
+    // horizontal line will have 0 area, and be treated as
+    // not intersecting, even when it does
+    int width = (int)(xMax - xMin) + 1;
+    int height = (int)(yMax - yMin) + 1;
+
+    // If any of this area intersects the clip, it is visible
+    boolean visible = graphics.hitClip(x, y, width, height);
+
+    return visible;
+  }
+
+  /**
+   * Is the Shape visible
+   *
+   * @param shape  shape to check
+   *
+   * @return  true if visible
+   */
+  private boolean visible(Shape shape) {
+    boolean visible = shape.intersects(0, 0, width, height);
+
+    return visible;
+  }
+
+  /**
+   * Long winded way of checking if any part of an area is visible in an
+   * area bounded by the graphics2s clipping area Significantly increases
+   * the plotting time, but significantly decreases the size of the
+   * resulting file Probably better to investigate ways of clipping the
+   * lines using DelaunayCustom.clip()
+   *
+   * @param xCoords -
+   *                The X coordinates of the area
+   * @param yCoords -
+   *                The Y coordinates of the area
+   * @param graphics  the graphics
+   * @return a boolean flag, true means that the triangle is at least
+   *          partially visible
+   */
+  private boolean visible(int[] xCoords, int[] yCoords, Graphics2D graphics) {
+    int numVertices = xCoords.length;
+
+    int xMin = Integer.MAX_VALUE;
+    int yMin = Integer.MAX_VALUE;
+    int xMax = Integer.MIN_VALUE;
+    int yMax = Integer.MIN_VALUE;
+
+    for (int i = 0; i < numVertices; i++) {
+      float[] display = {xCoords[i], yCoords[i]};
+      float[] device = new float[2];
+      // Convert the display coordinates to device coordinates
+      viewPort.transform(display, 0, device, 0, 2);
+
+      if (device[0] > xMax) {
+        xMax = (int)device[0];
+      }
+      if (device[0] < xMin) {
+        xMin = (int)device[0];
+      }
+      if (device[1] > yMax) {
+        yMax = (int)device[1];
+      }
+      if (device[1] < yMin) {
+        yMin = (int)device[1];
+      }
+    }
+
+    int x = (int)xMin;
+    int y = (int)yMin;
+    // Must add 1 to these, because otherwise a vertical or
+    // horizontal line will have 0 area, and be treated as
+    // not intersecting, even when it does
+    int width = (int)(xMax - xMin) + 1;
+    int height = (int)(yMax - yMin) + 1;
+
+    // If any of this area intersects the clip, it is visible
+    boolean visible = graphics.hitClip(x, y, width, height);
+
+    return visible;
+  }
+
+
+  /**
+   * Perform Sutherland-Hodgman Clipping on an array of vertices
+   * Not the most efficient method, but it works
+   * @param inVertexArray Array of vertices array[0] - x values,
+   * array[1] y values
+   * @return Clipped vertices
+   */
+  private int[][] clip(int[][] inVertexArray) {
+    int[][] edge = new int[2][2];
+    // Clip Left Edge
+    edge[0][0] = 0;
+    edge[1][0] = height;
+    edge[0][1] = 0;
+    edge[1][1] = 0;
+    inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
+    // Clip Bottom Edge
+    edge[0][0] = 0;
+    edge[1][0] = 0;
+    edge[0][1] = width;
+    edge[1][1] = 0;
+    inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
+    // Clip Right Edge
+    edge[0][0] = width;
+    edge[1][0] = 0;
+    edge[0][1] = width;
+    edge[1][1] = height;
+    inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
+    // Clip Top Edge
+    edge[0][0] = width;
+    edge[1][0] = height;
+    edge[0][1] = width;
+    edge[1][1] = height;
+    inVertexArray = SutherlandHodgmanPolygonClip(inVertexArray, edge);
+
+    return inVertexArray;
+  }
+
+  /**
+   * Perform Sutherland-Hodgman Clipping on an array of vertices
+   * Not the most efficient method, but it works
+   * @param path   GeneralPath to clip
+   *
+   * @return Clipped vertices
+   */
+  private GeneralPath clip(GeneralPath path) {
+    float[][] edge = new float[2][2];
+    // Clip Left Edge
+    edge[0][0] = 0;
+    edge[1][0] = (height * 10);
+    edge[0][1] = 0;
+    edge[1][1] = -(height * 10);
+    path = SutherlandHodgmanPolygonClip(path, edge);
+    // Clip Bottom Edge
+    edge[0][0] = -(width * 10);
+    edge[1][0] = 0;
+    edge[0][1] = (width * 10);
+    edge[1][1] = 0;
+    path = SutherlandHodgmanPolygonClip(path, edge);
+    // Clip Right Edge
+    edge[0][0] = (width * 10);
+    edge[1][0] = -(height * 10);
+    edge[0][1] = (width * 10);
+    edge[1][1] = (height * 10);
+    path = SutherlandHodgmanPolygonClip(path, edge);
+    // Clip Top Edge
+    edge[0][0] = (width * 10);
+    edge[1][0] = (height * 10);
+    edge[0][1] = -(width * 10);
+    edge[1][1] = (height * 10);
+    path = SutherlandHodgmanPolygonClip(path, edge);
+
+    return path;
+  }
+
+  /**
+   * Perform Sutherland-Hodgman Clipping on an array of vertices
+   * Not the most efficient method, but it works
+   *
+   * @param path   GeneralPath to clip
+   * @return Clipped vertices
+   */
+  private GeneralPath clip2(GeneralPath path) {
+    float[][] edge = new float[2][2];
+    // Clip Left Edge
+    edge[0][0] = 0;
+    edge[1][0] = height;
+    edge[0][1] = 0;
+    edge[1][1] = 0;
+    path = SutherlandHodgmanPolygonClip(path, edge);
+    // Clip Bottom Edge
+    edge[0][0] = 0;
+    edge[1][0] = 0;
+    edge[0][1] = width;
+    edge[1][1] = 0;
+    path = SutherlandHodgmanPolygonClip(path, edge);
+    // Clip Right Edge
+    edge[0][0] = width;
+    edge[1][0] = 0;
+    edge[0][1] = width;
+    edge[1][1] = height;
+    path = SutherlandHodgmanPolygonClip(path, edge);
+    // Clip Top Edge
+    edge[0][0] = width;
+    edge[1][0] = height;
+    edge[0][1] = 0;
+    edge[1][1] = height;
+    path = SutherlandHodgmanPolygonClip(path, edge);
+
+    return path;
+  }
+
+  /**
+   * Perform Sutherland-Hodgman clipping against a single edge
+   * @param inVertexArray Array of vertices
+   * @param edge Edge definition - corners must be defined in
+   * anticlockwise order
+   * @return
+   */
+  private int[][] SutherlandHodgmanPolygonClip(int[][] inVertexArray,
+          int[][] edge) {
+    int numVertices = inVertexArray[0].length;
+    int[][] outVertexArray = null;
+    if (numVertices <= 1) {
+      outVertexArray = new int[1][0];
+      return outVertexArray;
+    }
+    ArrayList xList = new ArrayList();
+    ArrayList yList = new ArrayList();
+
+    int xs = inVertexArray[0][inVertexArray[0].length - 1];
+    int ys = inVertexArray[1][inVertexArray[0].length - 1];
+    for (int j = 0; j < inVertexArray[0].length; j++) {
+      // Get the next point from the array
+      int xp = inVertexArray[0][j];
+      int yp = inVertexArray[1][j];
+      // If the next point is within the clip
+      if (insideEdge(xp, yp, edge)) {
+        // If previous point was also within the clip
+        if (insideEdge(xs, ys, edge)) {
+          xList.add(new Integer(xp));
+          yList.add(new Integer(yp));
+        }
+        else {
+          // Otherwise, intersect at the clip edge
+          int[] inter = intersect(new int[] {xs, ys}, new int[] {xp, yp},
+                                  edge);
+          xList.add(new Integer(inter[0]));
+          yList.add(new Integer(inter[1]));
+          xList.add(new Integer(xp));
+          yList.add(new Integer(yp));
+        }
+      }
+      else {
+        // Join the last point to the clip
+        if (insideEdge(xs, ys, edge)) {
+          int[] inter = intersect(new int[] {xs, ys}, new int[] {xp, yp},
+                                  edge);
+          xList.add(new Integer(inter[0]));
+          yList.add(new Integer(inter[1]));
+        }
+      }
+      xs = xp;
+      ys = yp;
+    }
+
+    outVertexArray = new int[2][xList.size()];
+    for (int i = 0; i < xList.size(); i++) {
+      Integer xInt = (Integer)xList.get(i);
+      Integer yInt = (Integer)yList.get(i);
+      outVertexArray[0][i] = xInt.intValue();
+      outVertexArray[1][i] = yInt.intValue();
+    }
+
+    return outVertexArray;
+  }
+
+  /**
+   * Perform Sutherland-Hodgman clipping against a single edge
+   *
+   * @param path   The path
+   * @param edge Edge definition - corners must be defined in
+   * anticlockwise order
+   * @return
+   */
+  private GeneralPath SutherlandHodgmanPolygonClip(GeneralPath path,
+          float[][] edge) {
+    ArrayList xList = new ArrayList();
+    ArrayList yList = new ArrayList();
+
+    AffineTransform blank = AffineTransform.getScaleInstance(1.0, 1.0);
+    java.awt.geom.PathIterator iterator = path.getPathIterator(blank);
+    float[] coords = new float[6];
+    iterator.currentSegment(coords);
+    float xs = coords[0];
+    float ys = coords[1];
+    iterator.next();
+    if (insideEdge(xs, ys, edge)) {
+      xList.add(new Float(xs));
+      yList.add(new Float(ys));
+    }
+    while(!iterator.isDone()) {
+      // Get the next point from the array
+      iterator.currentSegment(coords);
+      float xp = coords[0];
+      float yp = coords[1];
+
+      // If the next point is within the clip
+      if (insideEdge(xp, yp, edge)) {
+        // If previous point was also within the clip
+        if (insideEdge(xs, ys, edge)) {
+          xList.add(new Float(xp));
+          yList.add(new Float(yp));
+        }
+        else {
+          // Otherwise, intersect at the clip edge
+          float[] inter = intersect(new float[] {xs, ys}, new float[] {xp,
+                  yp}, edge);
+          xList.add(new Float(inter[0]));
+          yList.add(new Float(inter[1]));
+          xList.add(new Float(xp));
+          yList.add(new Float(yp));
+        }
+      }
+      else {
+        // Join the last point to the clip
+        if (insideEdge(xs, ys, edge)) {
+          float[] inter = intersect(new float[] {xs, ys}, new float[] {xp,
+                  yp}, edge);
+          xList.add(new Float(inter[0]));
+          yList.add(new Float(inter[1]));
+        }
+      }
+      xs = xp;
+      ys = yp;
+      iterator.next();
+    }
+
+    GeneralPath clippedPath = new GeneralPath();
+    if (xList.size() < 1) {
+      return clippedPath;
+    }
+    Float xInt = (Float)xList.get(0);
+    Float yInt = (Float)yList.get(0);
+    clippedPath.moveTo(xInt.floatValue(), yInt.floatValue());
+    for (int i = 1; i < xList.size(); i++) {
+      xInt = (Float)xList.get(i);
+      yInt = (Float)yList.get(i);
+      clippedPath.lineTo(xInt.floatValue(), yInt.floatValue());
+    }
+
+    return clippedPath;
+  }
+
+  /**
+   * Test if a point is "within" and edge of a rectangle
+   * @param x X coordinate of point
+   * @param y Y coordinate of point
+   * @param edge The edge to compare - must be defined in anticlockwise
+   * order
+   * @return True if the point is within the rectangle
+   */
+  private boolean insideEdge(int x, int y, int[][] edge) {
+    final int X = 0;
+    final int Y = 1;
+    boolean inside = false;
+    // If bottom edge
+    if (edge[X][1] > edge[X][0]) {
+      if (y >= edge[Y][0]) {
+        inside = true;
+      }
+    }
+    // If Top Edge
+    if (edge[X][1] < edge[X][0]) {
+      if (y <= edge[Y][0]) {
+        inside = true;
+      }
+    }
+    // If Right Edge
+    if (edge[Y][1] > edge[Y][0]) {
+      if (x <= edge[X][1]) {
+        inside = true;
+      }
+    }
+    // If Left Edge
+    if (edge[Y][1] < edge[Y][0]) {
+      if (x >= edge[X][1]) {
+        inside = true;
+      }
+    }
+
+    return inside;
+  }
+
+  /**
+   * Test if a point is "within" and edge of a rectangle
+   * @param x X coordinate of point
+   * @param y Y coordinate of point
+   * @param edge The edge to compare - must be defined in anticlockwise
+   * order
+   * @return True if the point is within the rectangle
+   */
+  private boolean insideEdge(float x, float y, float[][] edge) {
+    final int X = 0;
+    final int Y = 1;
+    boolean inside = false;
+    // If bottom edge
+    if (edge[X][1] > edge[X][0]) {
+      if (y >= edge[Y][0]) {
+        inside = true;
+      }
+    }
+    // If Top Edge
+    if (edge[X][1] < edge[X][0]) {
+      if (y <= edge[Y][0]) {
+        inside = true;
+      }
+    }
+    // If Right Edge
+    if (edge[Y][1] > edge[Y][0]) {
+      if (x <= edge[X][1]) {
+        inside = true;
+      }
+    }
+    // If Left Edge
+    if (edge[Y][1] < edge[Y][0]) {
+      if (x >= edge[X][1]) {
+        inside = true;
+      }
+    }
+
+    return inside;
+  }
+
+  /**
+   * Intersect a line between two vertices which crosses the edge of the
+   * clipping region
+   * @param first First point - int[0] is x value, int[1] is y value
+   * @param second  Second point - int[0] is x value, int[1] is y value
+   * @param edge The edge definition - Must be defined in anticlockwise
+   *  order
+   * @return
+   */
+  private int[] intersect(int[] first, int[] second, int[][] edge) {
+    final int X = 0;
+    final int Y = 1;
+
+    int[] intersect = new int[2];
+
+    // If the edge is horizontal
+    if (edge[Y][0] == edge[Y][1]) {
+      intersect[Y] = edge[Y][0];
+      intersect[X] = first[X] +
+                     (edge[Y][0] - first[Y]) * (second[X] - first[X]) /
+                     (second[Y] - first[Y]);
+    }
+    else {
+      intersect[X] = edge[X][0];
+      intersect[Y] = first[Y] +
+                     (edge[X][0] - first[X]) * (second[Y] - first[Y]) /
+                     (second[X] - first[X]);
+    }
+
+    return intersect;
+  }
+
+  /**
+   * Intersect a line between two vertices which crosses the edge of the
+   * clipping region
+   * @param first First point - int[0] is x value, int[1] is y value
+   * @param second  Second point - int[0] is x value, int[1] is y value
+   * @param edge The edge definition - Must be defined in anticlockwise
+   *  order
+   * @return
+   */
+  private float[] intersect(float[] first, float[] second, float[][] edge) {
+    final int X = 0;
+    final int Y = 1;
+
+    float[] intersect = new float[2];
+
+    // If the edge is horizontal
+    if (edge[Y][0] == edge[Y][1]) {
+      intersect[Y] = edge[Y][0];
+      intersect[X] = first[X] +
+                     (edge[Y][0] - first[Y]) * (second[X] - first[X]) /
+                     (second[Y] - first[Y]);
+    }
+    else {
+      intersect[X] = edge[X][0];
+      intersect[Y] = first[Y] +
+                     (edge[X][0] - first[X]) * (second[Y] - first[Y]) /
+                     (second[X] - first[X]);
+    }
+
+    return intersect;
+  }
+
+  /**
+   * Chart exception
+   */
+  public class ChartException extends Exception {
+
+    /**
+     * Create a chart exception
+     *
+     * @param reason  reason for the exception
+     */
+    public ChartException(String reason) {
+      super(reason);
+    }
+  }
+
+  /**
+   * Provide a set of simple hatching patterns
+   */
+  public class Hatching {
+
+    /** number of patterns */
+    public static final int NUM_PATTERNS = 6;
+
+    /** diagonal patter 1 */
+    public static final int DIAGONAL1 = 0;
+
+    /** diagonal pattern 2 */
+    public static final int DIAGONAL2 = 1;
+
+    /** diagonal both */
+    public static final int DIAGONAL_BOTH = 2;
+
+    /** horizontal pattern */
+    public static final int HORIZONTAL = 3;
+
+    /** vertical pattern */
+    public static final int VERTICAL = 4;
+
+    /** square pattern */
+    public static final int SQUARE = 5;
+
+    /** width */
+    private int width = 300;
+
+    /** height */
+    private int height = 300;
+
+    /**
+     * Get the pattern as an image
+     *
+     * @param pattern  the pattern
+     *
+     * @return the image
+     */
+    public BufferedImage getPattern(int pattern) {
+      // Create a 1 bit (monochrome) image
+      BufferedImage fillTexture = new BufferedImage(width, height,
+                                    BufferedImage.TYPE_INT_RGB);
+
+      for (int i = 0; i < width; i++) {
+        for (int j = 0; j < height; j++) {
+          setPoint(i, j, Color.WHITE, fillTexture);
+        }
+      }
+
+      for (int i = 0; i < width; i++) {
+        for (int j = 0; j < height; j++) {
+          if (isSet(pattern, i, j)) {
+            setPoint(i, j, Color.BLACK, fillTexture);
+          }
+        }
+      }
+
+      return fillTexture;
+    }
+
+    /**
+     * Test whether an x,y point for a given pattern is filled
+     *
+     * @param pattern The pattern
+     * @param x       The x location of the point
+     * @param y       The y location of the point
+     *
+     * @return true if filled
+     */
+    private boolean isSet(int pattern, int x, int y) {
+      boolean isSet = false;
+      int repeat = 3;
+
+      if (pattern == DIAGONAL1) {
+        int xx = width / repeat;
+        int yy = height / repeat;
+        if ((x % xx) == (y % yy)) {
+          isSet = true;
+        }
+      }
+      else if (pattern == DIAGONAL2) {
+        int xx = width / repeat;
+        int yy = height / repeat;
+        if ((x % xx) == (yy - (y % yy))) {
+          isSet = true;
+        }
+      }
+      else if (pattern == DIAGONAL_BOTH) {
+        int xx = width / repeat;
+        int yy = height / repeat;
+        if (((x % xx) == (y % yy)) || ((x % xx) == (yy - (y % yy)))) {
+          isSet = true;
+        }
+      }
+      else if (pattern == HORIZONTAL) {
+        int yy = height / repeat;
+        if ((y % yy) == 0) {
+          isSet = true;
+        }
+      }
+      else if (pattern == VERTICAL) {
+        int xx = width / repeat;
+        if ((x % xx) == 0) {
+          isSet = true;
+        }
+      }
+      else if (pattern == SQUARE) {
+        int xx = width / repeat;
+        int yy = height / repeat;
+        if (((x % xx) == 0) || ((y % yy) == 0)) {
+          isSet = true;
+        }
+      }
+
+      return isSet;
+    }
+
+    /**
+     * Set a point in a buffered image. Actually, set a 3x3 square,
+     * it looks better.
+     *
+     * @param x
+     *                The x location of the centre of the square
+     * @param y
+     *                The y location of the centre of the square
+     * @param color color The colour of the square
+     * @param image The buffered image to be plotted
+     */
+    private void setPoint(int x, int y, Color color, BufferedImage image) {
+      int rgb = color.getRGB();
+      for (int i = x - 1; i <= x + 1; i++) {
+        for (int j = y - 1; j <= y + 1; j++) {
+          if ((i >= 0) && (i < width) && (j >= 0) && (j < height)) {
+            image.setRGB(i, j, rgb);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get the BasicStroke float arrays for dashing
+   * @param dashStyle  dashing style
+   * @param pixelWidth
+   * @return array of alternating stroke lengths (on/off)
+   */
+  private float[] getStrokeDash(int dashStyle, float pixelWidth) {
+    float[] dash = null;
+    switch (dashStyle) {
+      case LineAttributes.PATTERN_DOT:
+        // 1 on, 7 off
+        dash = new float[] {1.0f * pixelWidth, 7.0f * pixelWidth};
+        break;
+      case LineAttributes.PATTERN_DASH_DOT:
+        // 7 on, 4 off, 1 on, 4 off
+        dash = new float[] {7.0f * pixelWidth, 4.0f * pixelWidth,
+                            1.0f * pixelWidth, 4 * pixelWidth};
+        break;
+      case LineAttributes.PATTERN_DASH: //
+        // 8 on, 8 off
+        dash = new float[] {8.0f * pixelWidth, 8.0f * pixelWidth};
+        break;
+      case LineAttributes.PATTERN_SOLID: //
+      default: //
+        break;
+    }
+    return dash;
+  }
+
+  /**
+   * Set whether the vertices should be transformed from display coords
+   * to screen coords before drawing.  This ends up bypassing the use of
+   * the AffineTransform when drawing.  Use this if you are in a rotated
+   * 3D display.
+   *
+   * @param value  true to transform
+   */
+  public void setTransformToScreenCoords(boolean value) {
+    transformToScreenCoords = value;
+  }
+
+  /**
+   * Transform coordinates from 3D display space to 2D space (AWT screen) space
+   * @param coords display space coords
+   *
+   * @return screen coords
+   */
+  private float[] transformToScreen(float[] coords) {
+    if (!transformToScreenCoords) return coords;
+    int numvertex = coords.length / 3;
+    float[] retCoords = new float[coords.length];
+    for (int i = 0; i < numvertex; i++) {
+      double[] xyz = {coords[i * 3], coords[i * 3 + 1], coords[i * 3 + 2]};
+      int[] screenLocs = behavior.getScreenCoords(xyz);
+      retCoords[i * 3] = screenLocs[0];
+      retCoords[i * 3 + 1] = screenLocs[1];
+      retCoords[i * 3 + 2] = 0;
+    }
+    return retCoords;
+
+  }
+}
+
diff --git a/visad/bom/ScreenLockedDemo.java b/visad/bom/ScreenLockedDemo.java
new file mode 100644
index 0000000..566c09f
--- /dev/null
+++ b/visad/bom/ScreenLockedDemo.java
@@ -0,0 +1,333 @@
+// ScreenLockedDemo.java
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+// written by Jim Koutsovasilis
+
+package visad.bom;
+
+// Java
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.BorderLayout;
+import java.rmi.RemoteException;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+// VisAD
+import visad.ConstantMap;
+import visad.DataReferenceImpl;
+import visad.DelaunayCustom;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.GraphicsModeControl;
+import visad.Gridded2DDoubleSet;
+import visad.Irregular2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.UnionSet;
+import visad.VisADException;
+import visad.bom.ScreenLockedRendererJ3D;
+import visad.java3d.DefaultRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.RendererJ3D;
+
+
+/**
+ * Demos the ScreenLockedRendererJ3D and its related problems.
+ */
+public final class ScreenLockedDemo
+{
+
+	private DisplayImplJ3D display;
+	private ScalarMap xMap;
+	private ScalarMap yMap;
+
+
+	/**
+	 * Constructor.
+	 */
+	public ScreenLockedDemo() throws VisADException, RemoteException
+	{
+
+		display = new DisplayImplJ3D("display");
+
+		final GraphicsModeControl gmc =
+			display.getGraphicsModeControl();
+		gmc.setScaleEnable(false);
+		gmc.setProjectionPolicy(DisplayImplJ3D.PARALLEL_PROJECTION);
+
+		display.getDisplayRenderer().setBoxOn(false);
+
+		final RealType x = RealType.getRealType("x");
+		final RealType y = RealType.getRealType("y");
+
+		xMap = new ScalarMap(x, Display.XAxis);
+		yMap = new ScalarMap(y, Display.YAxis);
+
+		display.addMap(xMap);
+		display.addMap(yMap);
+
+		addScreenLockedSquare(display);
+		addCross(display);
+
+		final JFrame frame = new JFrame("Screen Locked Demo");
+		final JPanel panel = new JPanel(new BorderLayout());
+
+		panel.add(display.getComponent(), BorderLayout.CENTER);
+
+		final JPanel buttonsPanel = new JPanel();
+		final ButtonListener buttonListener = new ButtonListener();
+
+		JButton button = new JButton("Add Triangle");
+		button.addActionListener(buttonListener);
+		buttonsPanel.add(button);
+
+		button = new JButton("Add Field");
+		button.addActionListener(buttonListener);
+		buttonsPanel.add(button);
+
+		button = new JButton("Set Range");
+		button.addActionListener(buttonListener);
+		buttonsPanel.add(button);
+
+		panel.add(buttonsPanel, BorderLayout.SOUTH);
+
+		frame.getContentPane().add(panel);
+		frame.setSize(640, 480);
+		// frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		frame.setVisible(true);
+
+	} // ScreenLockedDemo.ScreenLockedDemo()
+
+
+	/**
+	 * Adds a coloured square to the display, using a screen
+	 * locked renderer.
+	 */
+	private static void addScreenLockedSquare(final DisplayImpl display)
+		throws VisADException, RemoteException
+	{
+
+		final int numSamples = 4;
+		final double[][]samples = new double[2][numSamples];
+		samples[0][0] = -10;
+		samples[1][0] = 10;
+		samples[0][1] = 10;
+		samples[1][1] = 10;
+		samples[0][2] = 10;
+		samples[1][2] = -10;
+		samples[0][3] = -10;
+		samples[1][3] = -10;
+
+		final RealTupleType domainType = new RealTupleType(
+			RealType.getRealType("a"), RealType.getRealType("b"));
+
+		final Gridded2DDoubleSet set = new Gridded2DDoubleSet(
+			domainType, samples, numSamples);
+		final Irregular2DSet filledSet = DelaunayCustom.fill(set);
+
+		final DataReferenceImpl dataRef =
+			new DataReferenceImpl("square_data_ref");
+		dataRef.setData(filledSet);
+
+		final ScalarMap yMap = new ScalarMap(RealType.getRealType("a"),
+			Display.YAxis);
+		final ScalarMap xMap = new ScalarMap(RealType.getRealType("b"),
+			Display.XAxis);
+		display.addMap(yMap);
+		display.addMap(xMap);
+
+		yMap.setRange(-10, 10);
+		xMap.setRange(-10, 10);
+
+		final RendererJ3D renderer = new ScreenLockedRendererJ3D();
+		final ConstantMap[] maps = new ConstantMap[] {
+			new ConstantMap(1, Display.Red),
+			new ConstantMap(0, Display.Green),
+			new ConstantMap(0, Display.Blue),
+			new ConstantMap(-0.03, Display.ZAxis)};
+		display.addReferences(renderer, dataRef, maps);
+
+	} // ScreenLockedDemo.addScreenLockedSquare()
+
+
+	/**
+	 * Adds a triangle to the display, using a default renderer.
+	 */
+	private static void addTriangle(final DisplayImpl display)
+		throws VisADException, RemoteException
+	{
+
+		final int numSamples = 3;
+		final double[][]samples = new double[2][numSamples];
+		samples[0][0] = 12;
+		samples[1][0] = -15;
+		samples[0][1] = 17;
+		samples[1][1] = -10;
+		samples[0][2] = 22;
+		samples[1][2] = -15;
+
+		final RealTupleType type = new RealTupleType(
+			RealType.getRealType("x"), RealType.getRealType("y"));
+
+		final Gridded2DDoubleSet set = new Gridded2DDoubleSet(
+			type, samples, numSamples);
+		final Irregular2DSet filledSet = DelaunayCustom.fill(set);
+
+		final DataReferenceImpl dataRef =
+			new DataReferenceImpl("triangle_data_ref");
+		dataRef.setData(filledSet);
+
+		final RendererJ3D renderer = new DefaultRendererJ3D();
+		final ConstantMap[] maps = new ConstantMap[] {
+			new ConstantMap(0, Display.Red),
+			new ConstantMap(0, Display.Green),
+			new ConstantMap(1, Display.Blue),
+			new ConstantMap(-0.02, Display.ZAxis)};
+		display.addReferences(renderer, dataRef, maps);
+
+	} // ScreenLockedDemo.addTriangle()
+
+
+	/**
+	 * Adds a cross to the dispaly, using a  default renderer.
+	 */
+	private static void addCross(final DisplayImpl display)
+		throws VisADException, RemoteException
+	{
+
+		final int numSamples = 2;
+		final double[][] samples = new double[2][numSamples];
+
+		samples[0][0] = -12;
+		samples[1][0] = 0;
+		samples[0][1] = 12;
+		samples[1][1] = 0;
+		final RealTupleType type = new RealTupleType(
+			RealType.getRealType("x"), RealType.getRealType("y"));
+		Gridded2DDoubleSet horizontalSet = new Gridded2DDoubleSet(
+			type, samples, numSamples);
+
+		samples[0][0] = 0;
+		samples[1][0] = 12;
+		samples[0][1] = 0;
+		samples[1][1] = -12;
+		Gridded2DDoubleSet verticalSet = new Gridded2DDoubleSet(
+			type, samples, numSamples);
+
+		final UnionSet set = new UnionSet(
+			new Gridded2DDoubleSet[]{
+				horizontalSet, verticalSet});
+		final DataReferenceImpl dataRef =
+			new DataReferenceImpl("lines_data_ref");
+		dataRef.setData(set);
+		final ConstantMap[] maps = new ConstantMap[] {
+			new ConstantMap(2, Display.LineWidth),
+			new ConstantMap(-0.01, Display.ZAxis)};
+		display.addReference(dataRef, maps);
+
+	} // ScreenLockedDemo.addCross()
+
+
+	/**
+	 * Adds a field to the display.
+	 */
+	private static void addField(final DisplayImpl display)
+		throws VisADException, RemoteException
+	{
+
+		final RealTupleType domainType = new RealTupleType(
+			RealType.getRealType("x"), RealType.getRealType("y"));
+		final RealType rangeType = RealType.getRealType("height");
+		final FunctionType functionType =
+			new FunctionType(domainType, rangeType);
+		final FlatField field = FlatField.makeField1(
+			functionType, 11, 18, 10, 11, 18, 10);
+		final DataReferenceImpl dataRef =
+			new DataReferenceImpl("field_data_ref");
+		dataRef.setData(field);
+		display.addMap(new ScalarMap(rangeType, Display.RGB));
+		display.addReference(dataRef);
+
+	} // ScreenLockedDemo.addField()
+
+
+	/**
+	 * Listens for button clicks.
+	 */
+	private class ButtonListener implements ActionListener
+	{
+
+		/**
+		 * The user has clicked on a button.
+		 */
+		public void actionPerformed(ActionEvent event)
+		{
+			
+
+			try {
+				final String command = event.getActionCommand();
+
+				if (command.equals("Add Triangle")) {
+					addTriangle(display);
+				} else if (command.equals("Add Field")) {
+					addField(display);
+				} else {
+					xMap.setRange(-10, 10);
+					yMap.setRange(-10, 10);
+				}
+			} catch (VisADException ex) {
+				System.err.println(ex.getMessage());
+				ex.printStackTrace();
+			} catch (RemoteException ex) {
+				System.err.println(ex.getMessage());
+				ex.printStackTrace();
+			}
+
+		} // ButtonListener.actionPerformed()
+
+	} // class ScreenLockedDemo.ButtonListener
+
+
+	/**
+	 * Used to run the program.
+	 *
+	 * Please read the class javadoc at the top of this file.
+	 *
+	 * @param args arguments are ignored.
+	 */
+	public static final void main(String[] args)
+		throws VisADException, RemoteException
+	{
+
+		new ScreenLockedDemo();
+
+	} // ScreenLockedDemo.main()
+
+} // class ScreenLockedDemo
+
diff --git a/visad/bom/ScreenLockedRendererJ3D.java b/visad/bom/ScreenLockedRendererJ3D.java
new file mode 100644
index 0000000..5dc05c1
--- /dev/null
+++ b/visad/bom/ScreenLockedRendererJ3D.java
@@ -0,0 +1,225 @@
+//
+// ScreenLockedRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+// Java
+import java.rmi.RemoteException;
+import javax.swing.JFrame;
+
+// Java3D
+import javax.media.j3d.*;
+
+// VisAD
+import visad.ConstantMap;
+import visad.DataReferenceImpl;
+import visad.DelaunayCustom;
+import visad.Display;
+import visad.FunctionType;
+import visad.GraphicsModeControl;
+import visad.Gridded2DSet;
+import visad.Irregular2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.Text;
+import visad.TextControl;
+import visad.TextType;
+import visad.RealTupleType;
+import visad.ScalarMap;
+import visad.VisADException;
+import visad.java3d.DefaultRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.DisplayRendererJ3D;
+
+/**
+ * This renderer locks text to its initial position on the
+ * screen.
+ *
+ * The render only works if you have a domain tuple of the form:
+ * (latitude, longitude, text)
+ * or a function type of the form:
+ * ((latitude, longitude)->(text))
+ */
+public class ScreenLockedRendererJ3D extends DefaultRendererJ3D 
+{
+  boolean initWithProj = false;
+
+  /**
+   * Default constructor.
+   */
+  public ScreenLockedRendererJ3D()
+  {
+    super();
+  }
+
+  public ScreenLockedRendererJ3D(boolean initWithProj) {
+    this();
+    this.initWithProj = initWithProj;
+  }
+
+  public void addSwitch(DisplayRendererJ3D displayRenderer,
+                         BranchGroup branch) {
+    if (initWithProj) {
+      displayRenderer.addLockedSceneGraphComponent(branch, initWithProj);
+    }
+    else {
+      displayRenderer.addLockedSceneGraphComponent(branch);
+    }
+  }
+
+
+  /**
+   * This is used for function types of the form:
+   * ((latitude, longitude)->(text))
+   */
+/*
+  public ShadowType makeShadowFunctionType(FunctionType type, 
+    DataDisplayLink link, ShadowType parent) 
+    throws RemoteException, VisADException
+  {
+    return new ShadowScreenLockedFunctionTypeJ3D(type, link, parent);
+  }
+*/
+
+  /**
+   * This is used for tuples of the form:
+   * (latitude, longitude, text)
+   */
+/*
+  public ShadowType makeShadowTupleType(TupleType type, DataDisplayLink link,
+    ShadowType parent) 
+    throws RemoteException, VisADException 
+  { 
+    return new ShadowScreenLockedTupleTypeJ3D(type, link, parent);
+  }
+*/
+  
+  
+/*
+  public ShadowType makeShadowSetType(SetType type, DataDisplayLink link,
+    ShadowType parent)
+    throws RemoteException, VisADException 
+  {
+    return new ShadowScreenLockedSetTypeJ3D(type, link, parent);
+  }
+*/
+
+  /**
+   * Used for testing.
+   * Creates a display with a red square and labels at each
+   * corner of the square.  The square rotates, and moves as
+   * you would expect, but the text is locked to its original
+   * position on the screen.
+   */
+  public static final void main(String [] args)
+    throws VisADException, RemoteException
+  {
+    final DisplayImplJ3D display = new DisplayImplJ3D("display");
+    final DisplayRendererJ3D renderer = 
+      (DisplayRendererJ3D) display.getDisplayRenderer();
+    renderer.setBoxOn(false);
+    renderer.setBackgroundColor(0.0f, 0.2f, 1.0f);
+
+    final GraphicsModeControl gmc = 
+      (GraphicsModeControl) display.getGraphicsModeControl();
+    gmc.setScaleEnable(false);
+    gmc.setProjectionPolicy(DisplayImplJ3D.PARALLEL_PROJECTION);
+
+    final RealTupleType domainType = new RealTupleType(RealType.Latitude,
+      RealType.Longitude);
+    final TextType textType = TextType.getTextType("text");
+    final FunctionType functionType = new FunctionType(domainType, textType);
+
+    // The domain samples make up a square. (clockwise order).
+    float [][] domainSamples = new float[2][4];
+    domainSamples[0][0] = 0f; 
+    domainSamples[1][0] = 0f;
+    domainSamples[0][1] = 10f;
+    domainSamples[1][1] = 0f;
+    domainSamples[0][2] = 10f;
+    domainSamples[1][2] = 10f;
+    domainSamples[0][3] = 0f;
+    domainSamples[1][3] = 10f;
+
+    // Created the filled sqaure.
+    final Gridded2DSet domainSet2 = new Gridded2DSet(domainType, 
+      domainSamples, 4);
+    final Irregular2DSet filledSet = DelaunayCustom.fill(domainSet2);
+    final DataReferenceImpl unlockedDataRef = new DataReferenceImpl(
+      "unlocked_data_ref");
+    unlockedDataRef.setData(filledSet);
+
+    final ScalarMap latMap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    final ScalarMap lonMap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    final ScalarMap textMap = new ScalarMap(textType, Display.Text);
+
+    display.addMap(latMap);
+    display.addMap(lonMap);
+    display.addMap(textMap);
+
+    // Center the square in the display.
+    latMap.setRange(0, 10);
+    lonMap.setRange(0, 10);
+    textMap.setRange(0, 10);
+
+    // Center the labels on the corners of the square.
+    final TextControl textControl = (TextControl) textMap.getControl();
+    textControl.setCenter(true);
+    Text text = new Text(textType, "Screen Locked 1");
+    final DataReferenceImpl lockedDataRef1 = new DataReferenceImpl(
+      "locked_data_ref");
+    lockedDataRef1.setData(text);
+    display.addReferences(new ScreenLockedRendererJ3D(), lockedDataRef1,
+    	new ConstantMap[]{
+		new ConstantMap(-1.0, Display.XAxis),
+		new ConstantMap(1.0, Display.YAxis),
+		new ConstantMap(-0.2, Display.ZAxis)});
+
+    text = new Text(textType, "Screen Locked 2");
+    DataReferenceImpl lockedDataRef2 = new DataReferenceImpl(
+      "locked_data_ref");
+    lockedDataRef2.setData(text);
+    display.addReferences(new ScreenLockedRendererJ3D(), lockedDataRef2,
+    	new ConstantMap[]{
+		new ConstantMap(1.0, Display.XAxis),
+		new ConstantMap(-1.0, Display.YAxis),
+		new ConstantMap(-0.2, Display.ZAxis)});
+
+    // Color the square red.
+    display.addReference(unlockedDataRef, new ConstantMap [] { 
+      new ConstantMap(0.0, Display.Green),
+      new ConstantMap(0.0, Display.Blue),
+      new ConstantMap(-1.0, Display.ZAxis)});
+
+    // Display the frame.
+    final JFrame frame = new JFrame("ScreenLockedRendererJ3D");
+    frame.getContentPane().add(display.getComponent());
+    frame.setSize(400, 400);
+    frame.setVisible(true);
+  }
+
+} // class ScreenLockedRendererJ3D
+
diff --git a/visad/bom/ShadowBarbFunctionTypeJ2D.java b/visad/bom/ShadowBarbFunctionTypeJ2D.java
new file mode 100644
index 0000000..a5e404a
--- /dev/null
+++ b/visad/bom/ShadowBarbFunctionTypeJ2D.java
@@ -0,0 +1,57 @@
+//
+// ShadowBarbFunctionTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbFunctionTypeJ2D class shadows the FunctionType class for
+   BarbRendererJ2D, within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowBarbFunctionTypeJ2D extends ShadowFunctionTypeJ2D {
+
+  public ShadowBarbFunctionTypeJ2D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    DataRenderer renderer = getLink().getRenderer();
+    return ShadowBarbRealTupleTypeJ2D.staticMakeFlow(getDisplay(), which,
+               flow_values, flowScale, spatial_values, color_values,
+               range_select, renderer, false);
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbFunctionTypeJ3D.java b/visad/bom/ShadowBarbFunctionTypeJ3D.java
new file mode 100644
index 0000000..508d5f1
--- /dev/null
+++ b/visad/bom/ShadowBarbFunctionTypeJ3D.java
@@ -0,0 +1,57 @@
+//
+// ShadowBarbFunctionTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbFunctionTypeJ3D class shadows the FunctionType class for
+   BarbRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowBarbFunctionTypeJ3D extends ShadowFunctionTypeJ3D {
+
+  public ShadowBarbFunctionTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    DataRenderer renderer = getLink().getRenderer();
+    return ShadowBarbRealTupleTypeJ3D.staticMakeFlow(getDisplay(), which,
+               flow_values, flowScale, spatial_values, color_values,
+               range_select, renderer, false);
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbRealTupleTypeJ2D.java b/visad/bom/ShadowBarbRealTupleTypeJ2D.java
new file mode 100644
index 0000000..aab61b8
--- /dev/null
+++ b/visad/bom/ShadowBarbRealTupleTypeJ2D.java
@@ -0,0 +1,631 @@
+//
+// ShadowBarbRealTupleTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbRealTupleTypeJ2D class shadows the RealTupleType class
+   for BarbManipulationRendererJ2D and BarbRendererJ2D, within a
+   DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowBarbRealTupleTypeJ2D extends ShadowRealTupleTypeJ2D {
+
+  public ShadowBarbRealTupleTypeJ2D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+
+    DataRenderer renderer = getLink().getRenderer();
+    boolean direct = renderer.getIsDirectManipulation();
+    if (direct && renderer instanceof BarbManipulationRendererJ2D) {
+      return ShadowBarbRealTupleTypeJ2D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, true);
+    }
+    else {
+      return ShadowBarbRealTupleTypeJ2D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, false);
+    }
+  }
+
+
+  private static final int NUM = 512;
+
+  public static VisADGeometryArray[] staticMakeFlow(DisplayImpl display,
+               int which, float[][] flow_values, float flowScale,
+               float[][] spatial_values, byte[][] color_values,
+               boolean[][] range_select, DataRenderer renderer, boolean direct)
+         throws VisADException {
+    if (flow_values[0] == null) return null;
+    if (spatial_values[0] == null) return null;
+
+    int len = spatial_values[0].length;
+    int flen = flow_values[0].length;
+    int rlen = 0; // number of non-missing values
+    if (range_select[0] == null) {
+      rlen = len;
+    }
+    else {
+      for (int j=0; j<range_select[0].length; j++) {
+        if (range_select[0][j]) rlen++;
+      }
+    }
+    if (rlen == 0) return null;
+
+    // WLH 3 June 99
+    boolean[] south = new boolean[len];
+    float[][] earth_locs = renderer.spatialToEarth(spatial_values);
+    if (earth_locs != null) {
+      for (int i=0; i<len; i++) south[i] = (earth_locs[0][i] < 0.0f);
+    }
+    else {
+      // if no latitude information is available, get value set in FlowControl
+      // default = south  where BOM is
+      FlowControl fcontrol = null;
+      if (which == 0) {
+        fcontrol = (FlowControl) display.getControl(Flow1Control.class);
+      }
+      else if (which == 1) {
+        fcontrol = (FlowControl) display.getControl(Flow2Control.class);
+      }
+      if (fcontrol == null) {
+        throw new VisADException(
+          "ShadowBarbRealTupleTypeJ2D: Unable to get FlowControl");
+      }
+      boolean isSouth =
+        (fcontrol.getBarbOrientation() == FlowControl.SH_ORIENTATION);
+      for (int i=0; i<len; i++) south[i] = isSouth;
+    }
+
+    // use default flowScale = 0.02f here, since flowScale for barbs is
+    // just for barb size
+    flow_values = adjustFlowToEarth(which, flow_values, spatial_values,
+                                    0.02f, renderer);
+
+    float[] vx = new float[NUM];
+    float[] vy = new float[NUM];
+    float[] tx = new float[NUM];
+    float[] ty = new float[NUM];
+    byte[] vred = null;
+    byte[] vgreen = null;
+    byte[] vblue = null;
+    byte[] valpha = null;
+    byte[] tred = null;
+    byte[] tgreen = null;
+    byte[] tblue = null;
+    byte[] talpha = null;
+    int numColors = color_values != null ? color_values.length : 3;
+    if (color_values != null) {
+      vred = new byte[NUM];
+      vgreen = new byte[NUM];
+      vblue = new byte[NUM];
+      if (numColors == 4) valpha = new byte[NUM];
+      tred = new byte[NUM];
+      tgreen = new byte[NUM];
+      tblue = new byte[NUM];
+      if (numColors == 4) talpha = new byte[NUM];
+    }
+    int[] numv = {0};
+    int[] numt = {0};
+
+    float scale = flowScale; // ????
+    float pt_size = 0.25f * flowScale; // ????
+
+    // flow vector
+    float f0 = 0.0f, f1 = 0.0f, f2 = 0.0f;
+    for (int j=0; j<len; j++) {
+      if (range_select[0] == null || range_select[0][j]) {
+// NOTE - must scale to knots
+        if (flen == 1) {
+          f0 = flow_values[0][0];
+          f1 = flow_values[1][0];
+          f2 = flow_values[2][0];
+        }
+        else {
+          f0 = flow_values[0][j];
+          f1 = flow_values[1][j];
+          f2 = flow_values[2][j];
+        }
+
+        if (numv[0] + NUM/2 > vx.length) {
+          float[] cx = vx;
+          float[] cy = vy;
+          int l = 2 * vx.length;
+          vx = new float[l];
+          vy = new float[l];
+          System.arraycopy(cx, 0, vx, 0, cx.length);
+          System.arraycopy(cy, 0, vy, 0, cy.length);
+          if (color_values != null) {
+            byte[] cred = vred;
+            byte[] cgreen = vgreen;
+            byte[] cblue = vblue;
+            byte[] calpha = valpha;
+            vred = new byte[l];
+            vgreen = new byte[l];
+            vblue = new byte[l];
+            if (calpha != null) valpha = new byte[l];
+            System.arraycopy(cred, 0, vred, 0, cred.length);
+            System.arraycopy(cgreen, 0, vgreen, 0, cgreen.length);
+            System.arraycopy(cblue, 0, vblue, 0, cblue.length);
+            if (calpha != null) System.arraycopy(calpha, 0, valpha, 0, calpha.length);
+          }
+        }
+        if (numt[0] + NUM/2 > tx.length) {
+          float[] cx = tx;
+          float[] cy = ty;
+          int l = 2 * tx.length;
+          tx = new float[l];
+          ty = new float[l];
+          System.arraycopy(cx, 0, tx, 0, cx.length);
+          System.arraycopy(cy, 0, ty, 0, cy.length);
+          if (color_values != null) {
+            byte[] cred = tred;
+            byte[] cgreen = tgreen;
+            byte[] cblue = tblue;
+            byte[] calpha = talpha;
+            tred = new byte[l];
+            tgreen = new byte[l];
+            tblue = new byte[l];
+            if (calpha != null) talpha = new byte[l];
+            System.arraycopy(cred, 0, tred, 0, cred.length);
+            System.arraycopy(cgreen, 0, tgreen, 0, cgreen.length);
+            System.arraycopy(cblue, 0, tblue, 0, cblue.length);
+            if (calpha != null) System.arraycopy(calpha, 0, talpha, 0, talpha.length);
+          }
+        }
+        int oldnv = numv[0];
+        int oldnt = numt[0];
+        float mbarb[] =
+          makeBarb(south[j], spatial_values[0][j], spatial_values[1][j],
+                   scale, pt_size, f0, f1, vx, vy, numv, tx, ty, numt,
+                   renderer);
+        if (direct) {
+          ((BarbManipulationRendererJ2D) renderer).
+            setVectorSpatialValues(mbarb, which);
+        }
+        int nv = numv[0];
+        int nt = numt[0];
+        if (color_values != null) {
+          if (color_values[0].length > 1) {
+            for (int i=oldnv; i<nv; i++) {
+              vred[i] = color_values[0][j];
+              vgreen[i] = color_values[1][j];
+              vblue[i] = color_values[2][j];
+              if (numColors == 4) valpha[i] = color_values[3][j];
+            }
+            for (int i=oldnt; i<nt; i++) {
+              tred[i] = color_values[0][j];
+              tgreen[i] = color_values[1][j];
+              tblue[i] = color_values[2][j];
+              if (numColors == 4) talpha[i] = color_values[3][j];
+            }
+          }
+          else {  // if (color_values[0].length == 1)
+            for (int i=oldnv; i<nv; i++) {
+              vred[i] = color_values[0][0];
+              vgreen[i] = color_values[1][0];
+              vblue[i] = color_values[2][0];
+              if (numColors == 4) valpha[i] = color_values[3][0];
+            }
+            for (int i=oldnt; i<nt; i++) {
+              tred[i] = color_values[0][0];
+              tgreen[i] = color_values[1][0];
+              tblue[i] = color_values[2][0];
+              if (numColors == 4) talpha[i] = color_values[3][0];
+            }
+          }
+        }
+      } // end if (range_select[0] == null || range_select[0][j])
+    } // end for (int j=0; j<len; j++)
+
+    int nv = numv[0];
+    int nt = numt[0];
+    if (nv == 0) return null;
+
+    VisADGeometryArray[] arrays = null;
+    VisADLineArray array = new VisADLineArray();
+    array.vertexCount = nv;
+
+    float[] coordinates = new float[3 * nv];
+
+    int m = 0;
+    for (int i=0; i<nv; i++) {
+      coordinates[m++] = vx[i];
+      coordinates[m++] = vy[i];
+      coordinates[m++] = 0.0f;
+    }
+    array.coordinates = coordinates;
+
+    byte[] colors = null;
+    if (color_values != null) {
+      colors = new byte[numColors * nv];
+      m = 0;
+      for (int i=0; i<nv; i++) {
+        colors[m++] = vred[i];
+        colors[m++] = vgreen[i];
+        colors[m++] = vblue[i];
+        if (numColors == 4) colors[m++] = valpha[i];
+      }
+      array.colors = colors;
+    }
+
+    VisADTriangleArray tarray = null;
+    if (nt > 0) {
+      tarray = new VisADTriangleArray();
+      tarray.vertexCount = nt;
+
+      coordinates = new float[3 * nt];
+      // float[][] normals = new float[3 * nt]; // not used in Java2D
+
+      m = 0;
+      for (int i=0; i<nt; i++) {
+        coordinates[m++] = tx[i];
+        coordinates[m++] = ty[i];
+        coordinates[m++] = 0.0f;
+      }
+      tarray.coordinates = coordinates;
+
+      if (color_values != null) {
+        colors = new byte[numColors * nt];
+        m = 0;
+        for (int i=0; i<nt; i++) {
+          colors[m++] = tred[i];
+          colors[m++] = tgreen[i];
+          colors[m++] = tblue[i];
+          if (numColors == 4) colors[m++] = talpha[i];
+        }
+        tarray.colors = colors;
+      }
+
+      // WLH 30 May 2002
+      array = (VisADLineArray) array.adjustLongitudeBulk(renderer);
+      tarray = (VisADTriangleArray) tarray.adjustLongitudeBulk(renderer);
+
+      arrays = new VisADGeometryArray[] {array, tarray};
+    }
+    else {
+
+      // WLH 30 May 2002
+      array = (VisADLineArray) array.adjustLongitudeBulk(renderer);
+
+      arrays = new VisADGeometryArray[] {array};
+    }
+
+    return arrays;
+  }
+
+
+  /** adapted from Justin Baker's WindBarb, which is adapted from
+      Mark Govett's barbs.pro IDL routine
+  */
+  static float[] makeBarb(boolean south, float x, float y, float scale,
+                          float pt_size, float f0, float f1,
+                          float[] vx, float[] vy, int[] numv,
+                          float[] tx, float[] ty, int[] numt,
+                          DataRenderer renderer) {
+
+    float wsp25,slant,barb,d,c195,s195;
+    float x0,y0;
+    float x1,y1,x2,y2,x3,y3;
+    int nbarb50,nbarb10,nbarb5;
+
+    float[] mbarb = new float[4];
+    mbarb[0] = x;
+    mbarb[1] = y;
+
+    if (!(renderer instanceof BarbRenderer) ||
+        ((BarbRenderer) renderer).getKnotsConvert()) {
+      // convert meters per second to knots
+      f0 *= (3600.0 / 1853.248);
+      f1 *= (3600.0 / 1853.248);
+    }
+
+    float wnd_spd = (float) Math.sqrt(f0 * f0 + f1 * f1);
+    int lenv = vx.length;
+    int lent = tx.length;
+    int nv = numv[0];
+    int nt = numt[0];
+
+    //determine the initial (minimum) length of the flag pole
+    if (wnd_spd >= 2.5) {
+
+      wsp25 = (float) Math.max(wnd_spd + 2.5, 5.0);
+      slant = 0.15f * scale;
+      barb = 0.4f * scale;
+      // WLH 6 Aug 99 - barbs point the other way (duh)
+      x0 = -f0 / wnd_spd;
+      y0 = -f1 / wnd_spd;
+
+      //plot the flag pole
+      // lengthen to 'd = 3.0f * barb'
+      // was 'd = barb' in original BOM code
+      d = 3.0f * barb;
+      x1 = (x +x0*d);
+      y1 = (y +y0*d);
+
+/*
+      // commented out in original BOM code
+      vx[nv] = x;
+      vy[nv] = y;
+      nv++;
+      vx[nv] = x1;
+      vy[nv] = y1;
+      nv++;
+      // g.drawLine(x,y,x1,y1);
+*/
+
+      //determine number of wind barbs needed for 10 and 50 kt winds
+      nbarb50 = (int)(wsp25/50.f);
+      nbarb10 = (int)((wsp25 - (nbarb50 * 50.f))/10.f);
+      nbarb5 =  (int)((wsp25 - (nbarb50 * 50.f) - (nbarb10 * 10.f))/5.f);
+
+      //2.5 to 7.5 kt winds are plotted with the barb part way done the pole
+      if (nbarb5 == 1) {
+        barb = barb * 0.4f;
+        slant = slant * 0.4f;
+        x1 = (x + x0 * d);
+        y1 = (y + y0 * d);
+
+        if (south) {
+          x2 = (x + x0 * (d + slant) - y0 * barb);
+          y2 = (y + y0 * (d + slant) + x0 * barb);
+        }
+        else {
+          x2 = (x + x0 * (d + slant) + y0 * barb);
+          y2 = (y + y0 * (d + slant) - x0 * barb);
+        }
+
+        vx[nv] = x1;
+        vy[nv] = y1;
+        nv++;
+        vx[nv] = x2;
+        vy[nv] = y2;
+        nv++;
+// System.out.println("barb5 " + x1 + " " + y1 + "" + x2 + " " + y2);
+        // g.drawLine(x1, y1, x2, y2);
+      }
+
+      //add a little more pole
+      if (wsp25 >= 5.0f && wsp25 < 10.0f) {
+        d = d + 0.125f * scale;
+        x1=(x + x0 * d);
+        y1=(y + y0 * d);
+/* WLH 24 April 99
+        vx[nv] = x;
+        vy[nv] = y;
+        nv++;
+        vx[nv] = x1;
+        vy[nv] = y1;
+        nv++;
+*/
+// System.out.println("wsp25 " + x + " " + y + "" + x1 + " " + y1);
+        // g.drawLine(x, y, x1, y1);
+      }
+
+      //now plot any 10 kt wind barbs
+      barb = 0.4f * scale;
+      slant = 0.15f * scale;
+      for (int j=0; j<nbarb10; j++) {
+        d = d + 0.125f * scale;
+        x1=(x + x0 * d);
+        y1=(y + y0 * d);
+        if (south) {
+          x2 = (x + x0 * (d + slant) - y0 * barb);
+          y2 = (y + y0 * (d + slant) + x0 * barb);
+        }
+        else {
+          x2 = (x + x0 * (d + slant) + y0 * barb);
+          y2 = (y + y0 * (d + slant) - x0 * barb);
+        }
+
+        vx[nv] = x1;
+        vy[nv] = y1;
+        nv++;
+        vx[nv] = x2;
+        vy[nv] = y2;
+        nv++;
+// System.out.println("barb10 " + j + " " + x1 + " " + y1 + "" + x2 + " " + y2);
+        // g.drawLine(x1,y1,x2,y2);
+      }
+/* WLH 24 April 99
+      vx[nv] = x;
+      vy[nv] = y;
+      nv++;
+      vx[nv] = x1;
+      vy[nv] = y1;
+      nv++;
+*/
+// System.out.println("line " + x + " " + y + "" + x1 + " " + y1);
+      // g.drawLine(x,y,x1,y1);
+
+      //lengthen the pole to accomodate the 50 knot barbs
+      if (nbarb50 > 0) {
+        d = d +0.125f * scale;
+        x1 = (x + x0 * d);
+        y1 = (y + y0 * d);
+/* WLH 24 April 99
+        vx[nv] = x;
+        vy[nv] = y;
+        nv++;
+        vx[nv] = x1;
+        vy[nv] = y1;
+        nv++;
+*/
+// System.out.println("line50 " + x + " " + y + "" + x1 + " " + y1);
+        // g.drawLine(x,y,x1,y1);
+      }
+
+      //plot the 50 kt wind barbs
+/* WLH 5 Nov 99
+      s195 = (float) Math.sin(195 * Data.DEGREES_TO_RADIANS);
+      c195 = (float) Math.cos(195 * Data.DEGREES_TO_RADIANS);
+*/
+      for (int j=0; j<nbarb50; j++) {
+        x1 = (x + x0 * d);
+        y1 = (y + y0 * d);
+        d = d + 0.3f * scale;
+        x3 = (x + x0 * d);
+        y3 = (y + y0 * d);
+/* WLH 5 Nov 99
+        if (south) {
+          x2 = (x3+barb*(x0*s195+y0*c195));
+          y2 = (y3-barb*(x0*c195-y0*s195));
+        }
+        else {
+          x2 = (x3-barb*(x0*s195+y0*c195));
+          y2 = (y3+barb*(x0*c195-y0*s195));
+        }
+*/
+        if (south) {
+          x2 = (x + x0 * (d + slant) - y0 * barb);
+          y2 = (y + y0 * (d + slant) + x0 * barb);
+        }
+        else {
+          x2 = (x + x0 * (d + slant) + y0 * barb);
+          y2 = (y + y0 * (d + slant) - x0 * barb);
+        }
+
+        float[] xp = {x1,x2,x3};
+        float[] yp = {y1,y2,y3};
+
+        tx[nt] = x1;
+        ty[nt] = y1;
+        nt++;
+        tx[nt] = x2;
+        ty[nt] = y2;
+        nt++;
+        tx[nt] = x3;
+        ty[nt] = y3;
+        nt++;
+/*
+System.out.println("barb50 " + x1 + " " + y1 + "" + x2 + " " + y2 +
+                 "  " + x3 + " " + y3);
+*/
+        // g.fillPolygon(xp,yp,3);
+        //start location for the next barb
+        x1=x3;
+        y1=y3;
+      }
+
+      // WLH 24 April 99 - now plot the pole
+      vx[nv] = x;
+      vy[nv] = y;
+      nv++;
+      vx[nv] = x1;
+      vy[nv] = y1;
+      nv++;
+
+      mbarb[2] = x1;
+      mbarb[3] = y1;
+    }
+    else { // if (wnd_spd < 2.5)
+
+      // wind < 2.5 kts.  Plot a circle
+      float rad = (0.7f * pt_size);
+
+      // draw 8 segment circle, center = (x, y), radius = rad
+      // 1st segment
+      vx[nv] = x - rad;
+      vy[nv] = y;
+      nv++;
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      nv++;
+      // 2nd segment
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      nv++;
+      vx[nv] = x;
+      vy[nv] = y + rad;
+      nv++;
+      // 3rd segment
+      vx[nv] = x;
+      vy[nv] = y + rad;
+      nv++;
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      nv++;
+      // 4th segment
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      nv++;
+      vx[nv] = x + rad;
+      vy[nv] = y;
+      nv++;
+      // 5th segment
+      vx[nv] = x + rad;
+      vy[nv] = y;
+      nv++;
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      nv++;
+      // 6th segment
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      nv++;
+      vx[nv] = x;
+      vy[nv] = y - rad;
+      nv++;
+      // 7th segment
+      vx[nv] = x;
+      vy[nv] = y - rad;
+      nv++;
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      nv++;
+      // 8th segment
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      nv++;
+      vx[nv] = x - rad;
+      vy[nv] = y;
+      nv++;
+// System.out.println("circle " + x + " " + y + "" + rad);
+      // g.drawOval(x-rad,y-rad,2*rad,2*rad);
+
+      mbarb[2] = x;
+      mbarb[3] = y;
+    }
+
+    numv[0] = nv;
+    numt[0] = nt;
+    return mbarb;
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbRealTupleTypeJ3D.java b/visad/bom/ShadowBarbRealTupleTypeJ3D.java
new file mode 100644
index 0000000..f92c621
--- /dev/null
+++ b/visad/bom/ShadowBarbRealTupleTypeJ3D.java
@@ -0,0 +1,350 @@
+//
+// ShadowBarbRealTupleTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbRealTupleTypeJ3D class shadows the RealTupleType class
+   for BarbManipulationRendererJ3D and BarbRendererJ3D, within a
+   DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowBarbRealTupleTypeJ3D extends ShadowRealTupleTypeJ3D {
+
+  public ShadowBarbRealTupleTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+
+    DataRenderer renderer = getLink().getRenderer();
+    boolean direct = renderer.getIsDirectManipulation();
+    if (direct && renderer instanceof BarbManipulationRendererJ3D) {
+      return ShadowBarbRealTupleTypeJ3D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, true);
+    }
+    else {
+      return ShadowBarbRealTupleTypeJ3D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, false);
+    }
+  }
+
+
+  private static final int NUM = 1024;
+
+  public static VisADGeometryArray[] staticMakeFlow(DisplayImpl display,
+               int which, float[][] flow_values, float flowScale,
+               float[][] spatial_values, byte[][] color_values,
+               boolean[][] range_select, DataRenderer renderer, boolean direct)
+         throws VisADException {
+    if (flow_values[0] == null) return null;
+    if (spatial_values[0] == null) return null;
+
+    int len = spatial_values[0].length;
+    int flen = flow_values[0].length;
+    int rlen = 0; // number of non-missing values
+    if (range_select[0] == null) {
+      rlen = len;
+    }
+    else {
+      for (int j=0; j<range_select[0].length; j++) {
+        if (range_select[0][j]) rlen++;
+      }
+    }
+    if (rlen == 0) return null;
+
+    // WLH 3 June 99
+    boolean[] south = new boolean[len];
+    float[][] earth_locs = renderer.spatialToEarth(spatial_values);
+    if (earth_locs != null) {
+      for (int i=0; i<len; i++) south[i] = (earth_locs[0][i] < 0.0f);
+    }
+    else {
+      // if no latitude information is available, get value set in FlowControl
+      // default = south  where BOM is
+      FlowControl fcontrol = null;
+      if (which == 0) {
+        fcontrol = (FlowControl) display.getControl(Flow1Control.class);
+      }
+      else if (which == 1) {
+        fcontrol = (FlowControl) display.getControl(Flow2Control.class);
+      }
+      if (fcontrol == null) {
+        throw new VisADException(
+          "ShadowBarbRealTupleTypeJ3D: Unable to get FlowControl");
+      }
+      boolean isSouth =
+        (fcontrol.getBarbOrientation() == FlowControl.SH_ORIENTATION);
+      for (int i=0; i<len; i++) south[i] = isSouth;
+    }
+
+    // use default flowScale = 0.02f here, since flowScale for barbs is
+    // just for barb size
+    flow_values = adjustFlowToEarth(which, flow_values, spatial_values,
+                                    0.02f, renderer);
+
+    float[] vx = new float[NUM];
+    float[] vy = new float[NUM];
+    float[] vz = new float[NUM];
+    float[] tx = new float[NUM];
+    float[] ty = new float[NUM];
+    float[] tz = new float[NUM];
+    byte[] vred = null;
+    byte[] vgreen = null;
+    byte[] vblue = null;
+    byte[] valpha = null;
+    byte[] tred = null;
+    byte[] tgreen = null;
+    byte[] tblue = null;
+    byte[] talpha = null;
+    int numColors = color_values != null ? color_values.length : 3;
+    if (color_values != null) {
+      vred = new byte[NUM];
+      vgreen = new byte[NUM];
+      vblue = new byte[NUM];
+      if (numColors == 4) valpha = new byte[NUM];
+      tred = new byte[NUM];
+      tgreen = new byte[NUM];
+      tblue = new byte[NUM];
+      if (numColors == 4) talpha = new byte[NUM];
+    }
+    int[] numv = {0};
+    int[] numt = {0};
+
+    float scale = flowScale; // ????
+    float pt_size = 0.25f * flowScale; // ????
+
+    // flow vector
+    float f0 = 0.0f, f1 = 0.0f, f2 = 0.0f;
+    for (int j=0; j<len; j++) {
+      if (range_select[0] == null || range_select[0][j]) {
+// NOTE - must scale to knots
+        if (flen == 1) {
+          f0 = flow_values[0][0];
+          f1 = flow_values[1][0];
+          f2 = flow_values[2][0];
+        }
+        else {
+          f0 = flow_values[0][j];
+          f1 = flow_values[1][j];
+          f2 = flow_values[2][j];
+        }
+
+        if (numv[0] + NUM/4 > vx.length) {
+          float[] cx = vx;
+          float[] cy = vy;
+          float[] cz = vz;
+          int l = 2 * vx.length;
+          vx = new float[l];
+          vy = new float[l];
+          vz = new float[l];
+          System.arraycopy(cx, 0, vx, 0, cx.length);
+          System.arraycopy(cy, 0, vy, 0, cy.length);
+          System.arraycopy(cz, 0, vz, 0, cz.length);
+          if (color_values != null) {
+            byte[] cred = vred;
+            byte[] cgreen = vgreen;
+            byte[] cblue = vblue;
+            byte[] calpha = (numColors == 4) ? valpha : null;
+            vred = new byte[l];
+            vgreen = new byte[l];
+            vblue = new byte[l];
+            if (calpha != null) valpha = new byte[l];
+            System.arraycopy(cred, 0, vred, 0, cred.length);
+            System.arraycopy(cgreen, 0, vgreen, 0, cgreen.length);
+            System.arraycopy(cblue, 0, vblue, 0, cblue.length);
+            if (calpha != null) System.arraycopy(calpha, 0, valpha, 0, calpha.length);
+          }
+        }
+        if (numt[0] + NUM/4 > tx.length) {
+          float[] cx = tx;
+          float[] cy = ty;
+          float[] cz = tz;
+          int l = 2 * tx.length;
+          tx = new float[l];
+          ty = new float[l];
+          tz = new float[l];
+          System.arraycopy(cx, 0, tx, 0, cx.length);
+          System.arraycopy(cy, 0, ty, 0, cy.length);
+          System.arraycopy(cz, 0, tz, 0, cz.length);
+          if (color_values != null) {
+            byte[] cred = tred;
+            byte[] cgreen = tgreen;
+            byte[] cblue = tblue;
+            byte[] calpha = (numColors == 4) ? talpha : null;
+            tred = new byte[l];
+            tgreen = new byte[l];
+            tblue = new byte[l];
+            if (calpha != null) talpha = new byte[l];
+            System.arraycopy(cred, 0, tred, 0, cred.length);
+            System.arraycopy(cgreen, 0, tgreen, 0, cgreen.length);
+            System.arraycopy(cblue, 0, tblue, 0, cblue.length);
+            if (calpha != null) System.arraycopy(calpha, 0, talpha, 0, calpha.length);
+          }
+        }
+        int oldnv = numv[0];
+        int oldnt = numt[0];
+        float mbarb[] =
+          ((BarbRenderer) renderer).makeVector(south[j],
+                   spatial_values[0][j], spatial_values[1][j],
+                   spatial_values[2][j], scale, pt_size, f0, f1, vx, vy, vz,
+                   numv, tx, ty, tz, numt);
+        if (direct) {
+          ((BarbManipulationRendererJ3D) renderer).
+            setVectorSpatialValues(mbarb, which);
+        }
+        int nv = numv[0];
+        int nt = numt[0];
+        if (color_values != null) {
+          if (color_values[0].length > 1) {
+            for (int i=oldnv; i<nv; i++) {
+              vred[i] = color_values[0][j];
+              vgreen[i] = color_values[1][j];
+              vblue[i] = color_values[2][j];
+              if (numColors == 4) valpha[i] = color_values[3][j];
+            }
+            for (int i=oldnt; i<nt; i++) {
+              tred[i] = color_values[0][j];
+              tgreen[i] = color_values[1][j];
+              tblue[i] = color_values[2][j];
+              if (numColors == 4) talpha[i] = color_values[3][j];
+            }
+          }
+          else {  // if (color_values[0].length == 1)
+            for (int i=oldnv; i<nv; i++) {
+              vred[i] = color_values[0][0];
+              vgreen[i] = color_values[1][0];
+              vblue[i] = color_values[2][0];
+              if (numColors == 4) valpha[i] = color_values[3][0];
+            }
+            for (int i=oldnt; i<nt; i++) {
+              tred[i] = color_values[0][0];
+              tgreen[i] = color_values[1][0];
+              tblue[i] = color_values[2][0];
+              if (numColors == 4) talpha[i] = color_values[3][0];
+            }
+          }
+        }
+      } // end if (range_select[0] == null || range_select[0][j])
+    } // end for (int j=0; j<len; j++)
+
+    int nv = numv[0];
+    int nt = numt[0];
+    if (nv == 0) return null;
+
+    VisADGeometryArray[] arrays = null;
+    VisADLineArray array = new VisADLineArray();
+    array.vertexCount = nv;
+
+    float[] coordinates = new float[3 * nv];
+
+    int m = 0;
+    for (int i=0; i<nv; i++) {
+      coordinates[m++] = vx[i];
+      coordinates[m++] = vy[i];
+      coordinates[m++] = vz[i];
+    }
+    array.coordinates = coordinates;
+
+    byte[] colors = null;
+    if (color_values != null) {
+      colors = new byte[numColors * nv];
+      m = 0;
+      for (int i=0; i<nv; i++) {
+        colors[m++] = vred[i];
+        colors[m++] = vgreen[i];
+        colors[m++] = vblue[i];
+        if (numColors == 4) colors[m++] = valpha[i];
+      }
+      array.colors = colors;
+    }
+
+    VisADTriangleArray tarray = null;
+    if (nt > 0) {
+      tarray = new VisADTriangleArray();
+      tarray.vertexCount = nt;
+
+      coordinates = new float[3 * nt];
+      float[] normals = new float[3 * nt];
+
+      m = 0;
+      for (int i=0; i<nt; i++) {
+        coordinates[m++] = tx[i];
+        coordinates[m++] = ty[i];
+        coordinates[m++] = tz[i];
+      }
+      tarray.coordinates = coordinates;
+
+      m = 0;
+      for (int i=0; i<nt; i++) {
+        normals[m++] = 0.0f;
+        normals[m++] = 0.0f;
+        normals[m++] = 1.0f;
+      }
+      tarray.normals = normals;
+
+      if (color_values != null) {
+        colors = new byte[numColors * nt];
+        m = 0;
+        for (int i=0; i<nt; i++) {
+          colors[m++] = tred[i];
+          colors[m++] = tgreen[i];
+          colors[m++] = tblue[i];
+          if (numColors == 4) colors[m++] = talpha[i];
+        }
+        tarray.colors = colors;
+      }
+
+      // WLH 30 May 2002
+      array = (VisADLineArray) array.adjustLongitudeBulk(renderer);
+      tarray = (VisADTriangleArray) tarray.adjustLongitudeBulk(renderer);
+
+      arrays = new VisADGeometryArray[] {array, tarray};
+    }
+    else {
+
+      // WLH 30 May 2002
+      array = (VisADLineArray) array.adjustLongitudeBulk(renderer);
+
+      arrays = new VisADGeometryArray[] {array};
+    }
+
+    return arrays;
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbRealTypeJ2D.java b/visad/bom/ShadowBarbRealTypeJ2D.java
new file mode 100644
index 0000000..1982847
--- /dev/null
+++ b/visad/bom/ShadowBarbRealTypeJ2D.java
@@ -0,0 +1,57 @@
+//
+// ShadowBarbRealTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbRealTypeJ2D class shadows the RealType class for
+   BarbRendererJ2D, within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowBarbRealTypeJ2D extends ShadowRealTypeJ2D {
+
+  public ShadowBarbRealTypeJ2D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    DataRenderer renderer = getLink().getRenderer();
+    return ShadowBarbRealTupleTypeJ2D.staticMakeFlow(getDisplay(), which,
+               flow_values, flowScale, spatial_values, color_values,
+               range_select, renderer, false);
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbRealTypeJ3D.java b/visad/bom/ShadowBarbRealTypeJ3D.java
new file mode 100644
index 0000000..b715c3b
--- /dev/null
+++ b/visad/bom/ShadowBarbRealTypeJ3D.java
@@ -0,0 +1,57 @@
+//
+// ShadowBarbRealTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbRealTypeJ3D class shadows the RealType class for
+   BarbRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowBarbRealTypeJ3D extends ShadowRealTypeJ3D {
+
+  public ShadowBarbRealTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    DataRenderer renderer = getLink().getRenderer();
+    return ShadowBarbRealTupleTypeJ3D.staticMakeFlow(getDisplay(), which,
+               flow_values, flowScale, spatial_values, color_values,
+               range_select, renderer, false);
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbSetTypeJ2D.java b/visad/bom/ShadowBarbSetTypeJ2D.java
new file mode 100644
index 0000000..62f8650
--- /dev/null
+++ b/visad/bom/ShadowBarbSetTypeJ2D.java
@@ -0,0 +1,57 @@
+//
+// ShadowBarbSetTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbSetTypeJ2D class shadows the SetType class for
+   BarbRendererJ2D, within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowBarbSetTypeJ2D extends ShadowSetTypeJ2D {
+
+  public ShadowBarbSetTypeJ2D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    DataRenderer renderer = getLink().getRenderer();
+    return ShadowBarbRealTupleTypeJ2D.staticMakeFlow(getDisplay(), which,
+               flow_values, flowScale, spatial_values, color_values,
+               range_select, renderer, false);
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbSetTypeJ3D.java b/visad/bom/ShadowBarbSetTypeJ3D.java
new file mode 100644
index 0000000..73f50d5
--- /dev/null
+++ b/visad/bom/ShadowBarbSetTypeJ3D.java
@@ -0,0 +1,57 @@
+//
+// ShadowBarbSetTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbSetTypeJ3D class shadows the SetType class for
+   BarbRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowBarbSetTypeJ3D extends ShadowSetTypeJ3D {
+
+  public ShadowBarbSetTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    DataRenderer renderer = getLink().getRenderer();
+    return ShadowBarbRealTupleTypeJ3D.staticMakeFlow(getDisplay(), which,
+               flow_values, flowScale, spatial_values, color_values,
+               range_select, renderer, false);
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbTupleTypeJ2D.java b/visad/bom/ShadowBarbTupleTypeJ2D.java
new file mode 100644
index 0000000..5e6deab
--- /dev/null
+++ b/visad/bom/ShadowBarbTupleTypeJ2D.java
@@ -0,0 +1,66 @@
+//
+// ShadowBarbTupleTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbTupleTypeJ2D class shadows the TupleType class for
+   BarbRendererJ2D, within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowBarbTupleTypeJ2D extends ShadowTupleTypeJ2D {
+
+  public ShadowBarbTupleTypeJ2D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+
+    DataRenderer renderer = getLink().getRenderer();
+    boolean direct = renderer.getIsDirectManipulation();
+    if (direct && renderer instanceof BarbManipulationRendererJ2D) {
+      return ShadowBarbRealTupleTypeJ2D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, true);
+    }
+    else {
+      return ShadowBarbRealTupleTypeJ2D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, false);
+    }
+  }
+
+}
+
diff --git a/visad/bom/ShadowBarbTupleTypeJ3D.java b/visad/bom/ShadowBarbTupleTypeJ3D.java
new file mode 100644
index 0000000..fe36b66
--- /dev/null
+++ b/visad/bom/ShadowBarbTupleTypeJ3D.java
@@ -0,0 +1,66 @@
+//
+// ShadowBarbTupleTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowBarbTupleTypeJ3D class shadows the TupleType class for
+   BarbRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowBarbTupleTypeJ3D extends ShadowTupleTypeJ3D {
+
+  public ShadowBarbTupleTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+
+    DataRenderer renderer = getLink().getRenderer();
+    boolean direct = renderer.getIsDirectManipulation();
+    if (direct && renderer instanceof BarbManipulationRendererJ3D) {
+      return ShadowBarbRealTupleTypeJ3D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, true);
+    }
+    else {
+      return ShadowBarbRealTupleTypeJ3D.staticMakeFlow(getDisplay(), which,
+                 flow_values, flowScale, spatial_values, color_values,
+                 range_select, renderer, false);
+    }
+  }
+
+}
+
diff --git a/visad/bom/ShadowCurveSetTypeJ2D.java b/visad/bom/ShadowCurveSetTypeJ2D.java
new file mode 100644
index 0000000..1acae2b
--- /dev/null
+++ b/visad/bom/ShadowCurveSetTypeJ2D.java
@@ -0,0 +1,94 @@
+//
+// ShadowCurveSetTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java2d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowCurveSetTypeJ2D class shadows the SetType class for
+   CurveManipulationRendererJ2D, within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowCurveSetTypeJ2D extends ShadowSetTypeJ2D {
+
+  /**
+   * Construct a new ShadowCurveSetTypeJ2D.
+   * @param    t      MathType of data (must be a SetType)
+   * @param    link   DataDisplayLink to DataReference
+   * @param    parent parent ShadowType.
+   * @throws VisADException  problem creating ShadowType
+   * @throws RemoteException  problem with remote object creation.
+   */
+  public ShadowCurveSetTypeJ2D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  /** 
+   * Transform data into a Java2D scene graph.
+   * @param  group           group to add generated scene graph components 
+   *                         (children) to
+   * @param  value_array     inherited valueArray values;
+   * @param  default_values  defaults for each display.DisplayRealTypeVector;
+   * @return true if need to post-process 
+   * @throws VisADException  illegal data or some other VisAD error
+   * @throws RemoteException illegal data or some other remote error
+   */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+
+    boolean data_ok = true;
+    if (data == null) data_ok = false;
+    if (!(data instanceof UnionSet)) data_ok = false;
+    if (((UnionSet) data).getManifoldDimension() != 1) data_ok = false; 
+    SampledSet[] sets = ((UnionSet) data).getSets();
+    for (int i=0; i<sets.length; i++) {
+      if (!(sets[i] instanceof Gridded2DSet)) {
+        data_ok = false;
+        break;
+      }
+    }
+    if (!data_ok) {
+      throw new DisplayException("data must be UnionSet of Gridded2DSets " +
+                                 "with manifold dimension = 1");
+    }
+
+    ((CurveManipulationRendererJ2D) renderer).default_values = default_values;
+
+    boolean post = ((ShadowFunctionOrSetType) getAdaptedShadowType()).
+                        doTransform(group, data, value_array,
+                                    default_values, renderer, this);
+    //ensureNotEmpty(group);
+    return post;
+  }
+
+}
+
diff --git a/visad/bom/ShadowCurveSetTypeJ3D.java b/visad/bom/ShadowCurveSetTypeJ3D.java
new file mode 100644
index 0000000..7a2c568
--- /dev/null
+++ b/visad/bom/ShadowCurveSetTypeJ3D.java
@@ -0,0 +1,94 @@
+//
+// ShadowCurveSetTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowCurveSetTypeJ3D class shadows the SetType class for
+   CurveManipulationRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowCurveSetTypeJ3D extends ShadowSetTypeJ3D {
+
+  /**
+   * Construct a new ShadowCurveSetTypeJ3D.
+   * @param    t      MathType of data (must be a SetType)
+   * @param    link   DataDisplayLink to DataReference
+   * @param    parent parent ShadowType.
+   * @throws VisADException  problem creating ShadowType
+   * @throws RemoteException  problem with remote object creation.
+   */
+  public ShadowCurveSetTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  /** 
+   * Transform data into a Java3D scene graph.
+   * @param  group           group to add generated scene graph components 
+   *                         (children) to
+   * @param  value_array     inherited valueArray values;
+   * @param  default_values  defaults for each display.DisplayRealTypeVector;
+   * @return true if need to post-process 
+   * @throws VisADException  illegal data or some other VisAD error
+   * @throws RemoteException illegal data or some other remote error
+   */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+
+    boolean data_ok = true;
+    if (data == null) data_ok = false;
+    if (!(data instanceof UnionSet)) data_ok = false;
+    if (((UnionSet) data).getManifoldDimension() != 1) data_ok = false; 
+    SampledSet[] sets = ((UnionSet) data).getSets();
+    for (int i=0; i<sets.length; i++) {
+      if (!(sets[i] instanceof Gridded2DSet)) {
+        data_ok = false;
+        break;
+      }
+    }
+    if (!data_ok) {
+      throw new DisplayException("data must be UnionSet of Gridded2DSets " +
+                                 "with manifold dimension = 1");
+    }
+
+    ((CurveManipulationRendererJ3D) renderer).default_values = default_values;
+
+    boolean post = ((ShadowFunctionOrSetType) getAdaptedShadowType()).
+                        doTransform(group, data, value_array,
+                                    default_values, renderer, this);
+    ensureNotEmpty(group);
+    return post;
+  }
+
+}
+
diff --git a/visad/bom/ShadowImageByRefFunctionTypeJ3D.java b/visad/bom/ShadowImageByRefFunctionTypeJ3D.java
new file mode 100644
index 0000000..9be59c8
--- /dev/null
+++ b/visad/bom/ShadowImageByRefFunctionTypeJ3D.java
@@ -0,0 +1,2373 @@
+//
+// ShadowImageByRefFunctionTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.media.j3d.Appearance;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Group;
+import javax.media.j3d.ImageComponent;
+import javax.media.j3d.ImageComponent2D;
+import javax.media.j3d.Node;
+import javax.media.j3d.Shape3D;
+import javax.media.j3d.Switch;
+import javax.media.j3d.Texture;
+import javax.media.j3d.Texture2D;
+import javax.media.j3d.TextureAttributes;
+import javax.media.j3d.TransparencyAttributes;
+
+import visad.BadMappingException;
+import visad.BaseColorControl;
+import visad.CachingCoordinateSystem;
+import visad.CoordinateSystem;
+import visad.Data;
+import visad.DataDisplayLink;
+import visad.DataRenderer;
+import visad.Display;
+import visad.DisplayException;
+import visad.DisplayImpl;
+import visad.DisplayRealType;
+import visad.DisplayTupleType;
+import visad.Field;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.Function;
+import visad.GraphicsModeControl;
+import visad.Gridded2DSet;
+import visad.GriddedSet;
+import visad.ImageFlatField;
+import visad.Integer1DSet;
+import visad.InverseLinearScaledCS;
+import visad.Linear1DSet;
+import visad.Linear2DSet;
+import visad.LinearNDSet;
+import visad.MathType;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.Set;
+import visad.ShadowFunctionOrSetType;
+import visad.ShadowRealTupleType;
+import visad.ShadowRealType;
+import visad.ShadowType;
+import visad.Unit;
+import visad.VisADException;
+import visad.VisADQuadArray;
+import visad.VisADTriangleStripArray;
+import visad.java3d.AVControlJ3D;
+import visad.java3d.AnimationControlJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.ShadowFunctionTypeJ3D;
+import visad.java3d.VisADImageNode;
+import visad.java3d.VisADImageTile;
+
+/**
+   The ShadowImageFunctionTypeJ3D class shadows the FunctionType class for
+   ImageRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowImageByRefFunctionTypeJ3D extends ShadowFunctionTypeJ3D {
+
+  private static final long serialVersionUID = 1L;
+  private static final int MISSING1 = Byte.MIN_VALUE;      // least byte
+
+  private VisADImageNode imgNode = null;
+  private VisADImageNode prevImgNode = null;
+
+  //- Ghansham (New variables introduced to preserve scaled values and colorTables)
+  private byte scaled_Bytes[][];  //scaled byte values 
+  private float scaled_Floats[][];  //scaled Float Values
+  private int rset_scalarmap_lookup[][]; //GHANSHAM:12NOV2012 create a lookup for rset FlatField range values on integer values
+
+  private byte[][] itable; //For single band
+  private byte[][] fast_table; //For fast_lookup
+  private byte[][][] threeD_itable; //for multiband
+
+  private float[][] color_values; //special case
+  private boolean first_time; //This variable indicates the first tile of the image.
+  //------------------------------------------------------------------------------
+
+  AnimationControlJ3D animControl = null;
+
+  private boolean reuseImages = false;
+
+  int[] inherited_values = null;
+  ShadowFunctionOrSetType adaptedShadowType = null;
+  int levelOfDifficulty = -1;
+
+  //REUSE GEOMETRY/COLORBYTE VARIABLES (STARTS HERE)
+  boolean regen_colbytes = false;
+  boolean regen_geom = false;
+  boolean apply_alpha = false;
+  //REUSE GEOMETRY/COLORBYTE VARIABLES (ENDS HERE)
+
+  public ShadowImageByRefFunctionTypeJ3D(MathType t, DataDisplayLink link, ShadowType parent) 
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public ShadowImageByRefFunctionTypeJ3D(MathType t, DataDisplayLink link, ShadowType parent,
+                  int[] inherited_values, ShadowFunctionOrSetType adaptedShadowType, int levelOfDifficulty)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+    this.inherited_values  = inherited_values;
+    this.adaptedShadowType = adaptedShadowType;
+    this.levelOfDifficulty = levelOfDifficulty;
+  }
+
+  //REUSE GEOMETRY/COLORBYTE UTILITY METHODS (STARTS HERE)
+   /*This method returns two things:
+	1. whether any spatial maps has return true in checkTicks() function 
+	2. Current ZAxis value 
+  */
+  private Object[] findSpatialMapTicksAndCurrZValue(ShadowFunctionOrSetType MyAdaptedShadowType, DisplayImpl display, 
+				      float  default_values[], float value_array[], int valueToScalar[], DataRenderer renderer, 
+				      DataDisplayLink link, int valueArrayLength) throws VisADException, DisplayException {
+    ShadowRealTupleType Domain = MyAdaptedShadowType.getDomain();
+    ShadowRealType[] DomainComponents = MyAdaptedShadowType.getDomainComponents();
+    ShadowRealTupleType domain_reference = Domain.getReference();
+    ShadowRealType[] DC = DomainComponents;
+    if (domain_reference != null && domain_reference.getMappedDisplayScalar()) {
+	DC = MyAdaptedShadowType.getDomainReferenceComponents();
+    }
+
+    int[] tuple_index = new int[3];
+    DisplayTupleType spatial_tuple = null;
+    boolean spatial_maps_check_ticks = false;
+    for (int i=0; i<DC.length; i++) {
+      Enumeration maps = DC[i].getSelectedMapVector().elements();
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      if (map.checkTicks(renderer, link)) {
+              spatial_maps_check_ticks = true;
+      }
+      DisplayRealType real = map.getDisplayScalar();
+      spatial_tuple = real.getTuple();
+      if (spatial_tuple == null) {
+        /*throw new DisplayException("texture with bad tuple: " +
+                                   "ShadowImageFunctionTypeJ3D.doTransform");*/
+	return null;
+      }
+      tuple_index[i] = real.getTupleIndex();
+      if (maps.hasMoreElements()) {
+        /*throw new DisplayException("texture with multiple spatial: " +
+                                   "ShadowImageFunctionTypeJ3D.doTransform");*/
+	return null;
+      }
+    } 
+
+
+    // get spatial index not mapped from domain_set
+    tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+    DisplayRealType real = (DisplayRealType) spatial_tuple.getComponent(tuple_index[2]);
+    int value2_index = display.getDisplayScalarIndex(real);
+    float value2 = default_values[value2_index];
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0 && real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+        value2 = value_array[i];
+        break;
+      }
+    }
+    tuple_index = null;
+    Object ret_values[] = new Object[2];
+    ret_values[0] = spatial_maps_check_ticks;
+    ret_values[1] = value2;
+    return ret_values;
+  }
+
+  /*This method retuns whether any of the rangemap has return true in checkTicks()*/
+  private boolean findRadianceMapColorControlCheckTicks(ScalarMap cmap, ScalarMap cmaps[], DataRenderer renderer, DataDisplayLink link) {
+	BaseColorControl cc;
+	boolean color_map_changed = false;
+	if (cmap!= null) {
+        	cc = (BaseColorControl) cmap.getControl();
+        	color_map_changed = (cmap.checkTicks(renderer, link) || cc.checkTicks(renderer,link));
+	} else if (cmaps !=null) {
+        	for (int i = 0; i < cmaps.length; i++) {
+            		cc = (BaseColorControl) cmaps[i].getControl();
+			if (null != cc) {
+	            		if (cc.checkTicks(renderer,link) || cmaps[i].checkTicks(renderer, link)) {
+        	        		color_map_changed = true;
+					break;
+            			}
+			} else {
+				if (cmaps[i].checkTicks(renderer, link)) {
+                                        color_map_changed = true;
+					break;
+                                }
+			}
+        	}
+	}
+	return color_map_changed;
+  }
+  /*This method just applies the texture on the already generated geometry.
+    This is used when only colorbytes are generated and geometry is reused. 
+    This does away with buildTexture(Linear/Curve) when geometrt is reused */
+  //GHANSHAM: 01MAR2012 GreyScale Texture (starts here)
+  //Change in applyTexture method. Remove most of the commented stuf. As it is not required.
+  //If the imageType in the new Image is not the same as that in the texture, we will have to recreate the ImageComponent2D and Texture2D
+  //THIS HAPPENS when a single band GreyScale Image (GreyScale LUT applied) gets converted Color image(Colored LUT applied) and vice versa
+  //While applying alpha, if constant_alpha=1.0 we have to nullify transparency_attributes of the current texture 
+  //(Idea lent from textureToGroup function in visad.java3d.ShadowFunctionOrSetTypeJ3D)
+  //If imagetype is GreyScale, the alpha value set is (1-constant_alpha) and not constant_alpha 
+  //(Idea taken from makeColorBytes function for RGBA images where alpha is calculated as c=  (int) (255.0 * (1.0f - constant_alpha)); (See below in makeColorBytes)
+  private void applyTexture(Shape3D shape, VisADImageTile tile, boolean apply_alpha, float constant_alpha) {
+        Appearance app = shape.getAppearance();
+	BufferedImage new_image = (BufferedImage) tile.getImage(0);
+        int new_image_type = new_image.getType();
+	if (regen_colbytes) {
+		Texture2D current_texture = (Texture2D) app.getTexture();
+                ImageComponent2D img2D = (ImageComponent2D) current_texture.getImage(0);
+                BufferedImage current_image = img2D.getImage();
+                if (new_image_type != current_image.getType()) { //In case the LUT Table has changed, the Texture and ImageComponent2D will change
+                        Texture2D texture = new Texture2D(Texture.BASE_LEVEL, getTextureType(new_image_type), current_image.getWidth(), current_image.getHeight());
+                        ImageComponent2D image2d = new ImageComponent2D(getImageComponentType(new_image_type), new_image, true, true);
+                        image2d.setCapability(ImageComponent.ALLOW_IMAGE_WRITE);
+                        image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+                        texture.setImage(0, image2d);
+                        texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+                        texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+                        tile.setImageComponent(image2d);
+                        TextureAttributes texture_attributes = app.getTextureAttributes();
+                        texture.setEnable(true);
+                        app.setTexture(texture);
+                } else {
+			if (animControl == null) {
+                                imgNode.setCurrent(0);
+                        }
+		}
+	}
+
+	
+        if (apply_alpha) {
+            TransparencyAttributes transp_attribs = app.getTransparencyAttributes();
+		if (constant_alpha == 1.0) { //If constant_alpha=1.0, nullify the transparency_attributes
+                        app.setTransparencyAttributes(null); //Idea taken fromtextureToGroup function in visad.java3d.ShadowFunctionOrSetTypeJ3D
+                } else {
+                        if ( Float.isNaN(constant_alpha) ) constant_alpha = 0f;
+
+            		if (null == transp_attribs) {
+                		transp_attribs = new TransparencyAttributes();
+                		transp_attribs.setTransparencyMode(TransparencyAttributes.BLENDED);
+                		transp_attribs.setTransparency(constant_alpha);
+                		transp_attribs.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
+                		app.setTransparencyAttributes(transp_attribs);
+            		} 
+			//Set transparency value = (1.0f - constant_alpha) for GreyScale textures and constant_alpha for Colored textures
+			//12NOV12: GHANSHAM Use inverse alpha logic for 3 byte images too
+                        boolean inversed_alpha = new_image_type == BufferedImage.TYPE_3BYTE_BGR || new_image_type == BufferedImage.TYPE_BYTE_GRAY;
+			transp_attribs.setTransparency(inversed_alpha? (1.0f - constant_alpha): constant_alpha);
+		}
+        }
+    }
+
+    /* This is the real nasty logic that decides following things:
+	1. Regenerate gometry
+	2. Regenerate ColorBytes
+	3. Change in alpha
+	Before doing this it inializes range ScalarMaps, constant_alpha value.
+	It also takes out the terminal ShadowType required in case of animations
+    */
+	//GHANSHAM:30AUG2011 Changed the signaure of initRegenFlags.. passing the ShadowFunctionOrSetType, constant_lapha, cmap and cmaps
+	/*private void initRegenFlags(ImageRendererJ3D imgRenderer, ShadowFunctionOrSetType MyAdaptedShadowType,
+                        float constant_alpha, ScalarMap cmap, ScalarMap cmaps[], Data data, DisplayImpl display,
+                        float default_values[], float[] value_array, int []valueToScalar, int valueArrayLength,
+                        DataDisplayLink link, int curved_size) throws BadMappingException, VisADException {*/
+	 //GHANSHAM: 01MAR2012 GreyScale Texture 
+	 //Modified signature of initRegenFlags. Added hasAlpha boolean variable. This variable signifies if alpha channel is present or not.
+ 	 private void initRegenFlags(ImageRendererJ3D imgRenderer, ShadowFunctionOrSetType MyAdaptedShadowType, 
+			float constant_alpha, ScalarMap cmap, ScalarMap cmaps[], Data data, DisplayImpl display, 
+			float default_values[], float[] value_array, int []valueToScalar, int valueArrayLength, 
+			DataDisplayLink link, int curved_size, boolean hasAlpha) throws BadMappingException, VisADException {
+	
+	/*The nasty logic starts from here
+		Retrieves the curve size, zaxis value, alpha, ff hashcode  value from Renderer class.
+		Compares them with current values and does other checks.
+		Finally store the current values for above variables in the renderer class.*/
+       	int last_curve_size = imgRenderer.getLastCurveSize();
+        float last_zaxis_value = imgRenderer.getLastZAxisValue();
+        float last_alpha_value = imgRenderer.getLastAlphaValue();
+        long last_data_hash_code = imgRenderer.getLastDataHashCode();
+        long current_data_hash_code = data.hashCode();
+	
+	boolean last_adjust_projection_seam = imgRenderer.getLastAdjustProjectionSeam(); //27FEB2012: Projection Seam Change Bug Fix
+	boolean current_adjust_projection_seam = adaptedShadowType.getAdjustProjectionSeam(); //27FEB2012: Projection Seam Change Bug Fix
+	
+	Object map_ticks_z_value[] = findSpatialMapTicksAndCurrZValue(MyAdaptedShadowType, display, default_values, value_array, valueToScalar, imgRenderer, link, valueArrayLength);
+	if (null == map_ticks_z_value) {
+		return;
+	}
+	float current_zaxis_value = Float.parseFloat(map_ticks_z_value[1].toString());
+        if ((-1 != last_curve_size) && Float.isNaN(last_zaxis_value) && (-1 == last_data_hash_code)) { //First Time
+                regen_colbytes = true;
+                regen_geom = true;
+                apply_alpha = true;
+        } else {
+                boolean data_hash_code_changed = (current_data_hash_code != last_data_hash_code);
+                if (data_hash_code_changed) { //dataref.setData()
+                        regen_colbytes = true;
+                        regen_geom = true;
+                        apply_alpha =true;
+                } else {
+                        boolean spatial_maps_check_ticks = Boolean.parseBoolean(map_ticks_z_value[0].toString());
+                        boolean zaxis_value_changed = (Float.compare(last_zaxis_value, current_zaxis_value) != 0);
+                        boolean curve_texture_value_change = (last_curve_size != curved_size);
+                        boolean alpha_changed = (Float.compare(constant_alpha, last_alpha_value) != 0);
+                        boolean radiancemap_colcontrol_check_ticks = findRadianceMapColorControlCheckTicks(cmap, cmaps, imgRenderer, link);
+			boolean projection_seam_changed = (current_adjust_projection_seam != last_adjust_projection_seam); //27FEB2012: Projection Seam Change Bug Fix
+			//GHANSHAM: 01MAR2012 Some change is the reuse decision logic. Use of hasAlpha variable. (starts here)
+			if  (spatial_maps_check_ticks ||  zaxis_value_changed || curve_texture_value_change || projection_seam_changed) { //change in geometry 27FEB2012: Projection Seam Change Bug Fix
+                                regen_geom = true;
+                        } else {
+				if (hasAlpha) {  //For single band image mapped to Display.RGBA, we have to set both apply_alpha and regen_colbytes to true. No way to find that only fourth component of LUT has changed.
+                                        apply_alpha = true;
+                                        regen_colbytes = true;
+                                } else {
+					if (alpha_changed) { //change in alpha value
+                                                apply_alpha = true;
+                                        } else if (radiancemap_colcontrol_check_ticks) { //change in Radiance ScalarMaps or ColorTable
+                                                regen_colbytes = true;
+                                        } else { //Assuming that ff.setSamples() has been called.
+                                                regen_colbytes = true;
+                                        }
+				}
+			}
+			//GHANSHAM: 01MAR2012 Some change is the reuse decision logic. Use of hasAlpha variable. (ends here)
+                }
+        }
+        imgRenderer.setLastCurveSize(curved_size);
+        imgRenderer.setLastZAxisValue(current_zaxis_value);
+        imgRenderer.setLastAlphaValue(constant_alpha);
+	imgRenderer.setLastAdjustProjectionSeam(current_adjust_projection_seam); //27FEB2012: Projection Seam Change Bug Fix
+        imgRenderer.setLastDataHashCode(current_data_hash_code);
+    }
+    //REUSE GEOMETRY/COLORBYTE UTILITY METHODS (ENDS HERE)
+
+  // transform data into a depiction under group
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+	
+    	DataDisplayLink link = renderer.getLink();
+    	// return if data is missing or no ScalarMaps
+    	if (data.isMissing()) {
+      		((ImageRendererJ3D) renderer).markMissingVisADBranch();
+      		return false;
+    	}
+
+    	if (levelOfDifficulty == -1) {
+      		levelOfDifficulty = getLevelOfDifficulty();
+    	}
+
+    	if (levelOfDifficulty == NOTHING_MAPPED) return false;
+
+
+    	if (group instanceof BranchGroup && ((BranchGroup) group).numChildren() > 0) {
+       		Node g = ((BranchGroup) group).getChild(0);
+        	// WLH 06 Feb 06 - support switch in a branch group.
+        	if (g instanceof BranchGroup && ((BranchGroup) g).numChildren() > 0) {
+            		reuseImages = true;
+        	}
+    	}
+
+    	DisplayImpl display = getDisplay();
+
+        // TDR (25JAN2012) First check display GMC. ConstantMap->MissingTransparent takes precedence
+        boolean anyMissing = true; // must be assumed true for now.
+        boolean missingTransparent = display.getGraphicsModeControl().getMissingTransparent();
+        float flag = default_values[display.getDisplayScalarIndex(Display.MissingTransparent)];
+        if (flag >= 0f) {
+           missingTransparent = (flag == 1f);
+        }
+        missingTransparent = (missingTransparent && anyMissing);
+      
+
+    	int cMapCurveSize = (int) default_values[display.getDisplayScalarIndex(Display.CurvedSize)];
+
+    	int curved_size = (cMapCurveSize > 0) ? cMapCurveSize : display.getGraphicsModeControl().getCurvedSize();
+ 
+    	// length of ValueArray
+    	int valueArrayLength = display.getValueArrayLength();
+    	// mapping from ValueArray to DisplayScalar
+    	int[] valueToScalar = display.getValueToScalar();
+	//GHANSHAM:30AUG2011 Restrutured the code  to extract the constant_alpha, cmap, cmaps and ShadowFunctionType so that they can be passed to initRegenFlags method
+	if (adaptedShadowType == null) {
+      		adaptedShadowType = (ShadowFunctionOrSetType) getAdaptedShadowType();
+    	}
+
+	boolean anyContour = adaptedShadowType.getAnyContour();
+    	boolean anyFlow = adaptedShadowType.getAnyFlow();
+    	boolean anyShape = adaptedShadowType.getAnyShape();
+    	boolean anyText = adaptedShadowType.getAnyText();
+
+    	if (anyContour || anyFlow || anyShape || anyText) {
+		throw new BadMappingException("no contour, flow, shape or text allowed");
+    	}
+
+	FlatField imgFlatField = null;
+	Set domain_set = ((Field) data).getDomainSet();
+
+    	ShadowRealType[] DomainComponents = adaptedShadowType.getDomainComponents();
+	int numImages = 1;
+	if (!adaptedShadowType.getIsTerminal()) {
+
+      		Vector domain_maps = DomainComponents[0].getSelectedMapVector();
+      		ScalarMap amap = null;
+      		if (domain_set.getDimension() == 1 && domain_maps.size() == 1) {
+        		ScalarMap map = (ScalarMap) domain_maps.elementAt(0);
+        		if (Display.Animation.equals(map.getDisplayScalar())) {
+          			amap = map;
+        		}
+      		}
+      		if (amap == null) {
+        		throw new BadMappingException("time must be mapped to Animation");
+      		}
+      		animControl = (AnimationControlJ3D) amap.getControl();
+
+        	numImages = domain_set.getLength();
+
+      		adaptedShadowType = (ShadowFunctionOrSetType) adaptedShadowType.getRange();
+      		DomainComponents = adaptedShadowType.getDomainComponents();
+      		imgFlatField = (FlatField) ((FieldImpl)data).getSample(0);
+    	} else {
+      		imgFlatField = (FlatField)data;
+    	}
+
+	 // check that range is single RealType mapped to RGB only
+    	ShadowRealType[] RangeComponents = adaptedShadowType.getRangeComponents();
+    	int rangesize = RangeComponents.length;
+    	if (rangesize != 1 && rangesize != 3) {
+      		throw new BadMappingException("image values must single or triple");
+    	}
+    	ScalarMap cmap  = null;
+    	ScalarMap[] cmaps = null;
+    	int[] permute = {-1, -1, -1};
+    	boolean hasAlpha = false;
+    	if (rangesize == 1) {
+      		Vector mvector = RangeComponents[0].getSelectedMapVector();
+                if (mvector.size() != 1) {
+                        throw new BadMappingException("image values must be mapped to RGB only");
+                }
+                cmap = (ScalarMap) mvector.elementAt(0);
+                if (Display.RGB.equals(cmap.getDisplayScalar())) {
+
+                } else if (Display.RGBA.equals(cmap.getDisplayScalar())) {
+                        hasAlpha = true;
+                } else {
+                        throw new BadMappingException("image values must be mapped to RGB or RGBA");
+                }
+    	} else {
+      		cmaps = new ScalarMap[3];
+      		for (int i=0; i<3; i++) {
+        		Vector mvector = RangeComponents[i].getSelectedMapVector();
+        		if (mvector.size() != 1) {
+          			throw new BadMappingException("image values must be mapped to color only");
+        		}
+       			cmaps[i] = (ScalarMap) mvector.elementAt(0);
+        		if (Display.Red.equals(cmaps[i].getDisplayScalar())) {
+          			permute[0] = i;
+	        	} else if (Display.Green.equals(cmaps[i].getDisplayScalar())) {
+        	  		permute[1] = i;
+        		} else if (Display.Blue.equals(cmaps[i].getDisplayScalar())) {
+	          		permute[2] = i;
+        		} else if (Display.RGB.equals(cmaps[i].getDisplayScalar())) { //Inserted by Ghansham for Mapping all the three scalarMaps to Display.RGB (starts here) 
+                		permute[i] = i;
+	        	} else {               ////Inserted by Ghansham for Mapping all the three scalarMaps to Display.RGB(ends here)
+        	  		throw new BadMappingException("image values must be mapped to Red, Green or Blue only");
+        		}
+      		}
+      		if (permute[0] < 0 || permute[1] < 0 || permute[2] < 0) {
+       			throw new BadMappingException("image values must be mapped to Red, Green or Blue only");
+      		}
+      		//Inserted by Ghansham for Checking that all should be mapped to Display.RGB or not even a single one should be mapped to Display.RGB(starts here)
+        	//This is to check if there is a single Display.RGB ScalarMap
+      		int indx = -1;
+		for (int i = 0; i < 3; i++) {
+      			if (cmaps[i].getDisplayScalar().equals(Display.RGB)) {
+        			indx = i;
+	                	break;
+        	        }
+        	}
+
+        	if (indx != -1){    //if there is a even a single Display.RGB ScalarMap, others must also Display.RGB only
+                	for (int i = 0; i < 3; i++) {
+                        	if (i !=indx && !(cmaps[i].getDisplayScalar().equals(Display.RGB))) {
+                                	throw new BadMappingException("image values must be mapped to (Red, Green, Blue) or (RGB,RGB,RGB) only");
+                        	}
+                	}
+        	}
+        	//Inserted by Ghansham for Checking that all should be mapped to Display.RGB or not even a single one should be mapped to Display.RGB(Ends here)        
+    	}
+
+    	float constant_alpha = default_values[display.getDisplayScalarIndex(Display.Alpha)];
+    	int color_length;
+    	ImageRendererJ3D imgRenderer = (ImageRendererJ3D) renderer;
+    	int imageType = imgRenderer.getSuggestedBufImageType();
+    	if (imageType == BufferedImage.TYPE_4BYTE_ABGR) {
+      		color_length = 4;
+      		if (!hasAlpha) {
+        		color_length = 3;
+        		imageType = BufferedImage.TYPE_3BYTE_BGR;
+      		}
+    	} else if (imageType == BufferedImage.TYPE_3BYTE_BGR) {
+      		color_length = 3;
+    	} else if (imageType == BufferedImage.TYPE_USHORT_GRAY) {
+      		color_length = 2;
+    	} else if (imageType == BufferedImage.TYPE_BYTE_GRAY) {
+      		color_length = 1;
+    	} else {
+      		// we shouldn't ever get here because the renderer validates the 
+      		// imageType before we get it, but just in case...
+      		throw new VisADException("renderer returned unsupported image type");
+    	}
+    	if (color_length == 4) constant_alpha = Float.NaN; // WLH 6 May 2003
+
+	//GHANSHAM: 01MAR2012 GreyScale Texture Support (starts here)
+	if (null != cmap) {
+                BaseColorControl bcc = (BaseColorControl) cmap.getControl();
+                float[][] color_table = bcc.getTable();
+                if (null != color_table) {
+			//12NOV2012:  New Logic->truncate 4-byte buffered image to 3-byte buffered image if alpha is constant
+			//Save some more space. (1-byte per pixel for constant alpha imagery
+			if (isAlphaConstant(color_table) && !missingTransparent) { //TDR (25JAN2012) If missingTransparent=true, lock this out.
+                                if (isColorTableGrey(color_table)) {
+                                        imageType = BufferedImage.TYPE_BYTE_GRAY;
+                                        color_length = 1;
+                                        if (hasAlpha) { //In case alpha channel is present
+                                                constant_alpha = color_table[3][0];
+                                        }
+                                } else {
+                                        imageType = BufferedImage.TYPE_3BYTE_BGR;
+                                        color_length = 3;
+                                        if (hasAlpha) { //In case alpha channel is present
+                                                constant_alpha = color_table[3][0];
+                                        }
+                                }
+                        }
+                }
+        }
+	//GHANSHAM: 01MAR2012 GreyScale Texture Support (ends here)
+      
+        //TDR (25JAN2012) For Color composite, must use ABGR if missingTransparent=true
+        if (cmaps != null && missingTransparent) {
+           color_length = 4;
+           imageType = BufferedImage.TYPE_4BYTE_ABGR;
+           constant_alpha = Float.NaN;
+        }
+
+
+
+	//REUSE GEOMETRY/COLORBYTE LOGIC (STARTS HERE)
+	regen_colbytes = false;
+  	regen_geom = false;
+  	apply_alpha = false; 
+	initRegenFlags((ImageRendererJ3D)renderer, adaptedShadowType, constant_alpha, cmap, cmaps, data, display, default_values, value_array, valueToScalar, valueArrayLength, link, curved_size, hasAlpha);
+	if(!reuseImages) {
+		regen_geom = true;
+		regen_colbytes = true;
+		apply_alpha = true;
+	}
+
+        /**
+        System.err.println("Regenerate Color Bytes:" + regen_colbytes);
+        System.err.println("Regenerate Geometry:" + regen_geom);
+        System.err.println("Apply Alpha:" + apply_alpha);
+	System.err.println("ReuseImages:" + reuseImages);
+        */
+        
+	//REUSE GEOMETRY/COLORBYTE LOGIC (ENDS HERE)
+     	prevImgNode = ((ImageRendererJ3D)renderer).getImageNode();
+
+     	BranchGroup bgImages = null;
+
+	/*REUSE GEOM/COLBYTE: Replaced reuse with reuseImages. Earlier else part of this decision was never being used.
+	  The reason was reuse was always set to false. Compare with your version.
+	  Added one extra line in the else part where I extract the bgImages from the switch.
+	  Now else part occurs when either reuse_colbytes or regen_geom is true.
+	  But when regen_colbytes and regen_geom both are true, then I assume that a new flatfield is set so
+	  go with the if part.
+	*/
+	if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE GEOM/COLBYTE:Earlier reuse variable was used. Replaced it with reuseImages.
+								//Added regen_colbytes and regen_geom. 
+								//This is used when either its first time or full new data has been with different dims.
+		BranchGroup branch = new BranchGroup();
+       		branch.setCapability(BranchGroup.ALLOW_DETACH);
+       		branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+       		branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+       		branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+       		Switch swit = (Switch) makeSwitch();
+
+       		imgNode = new VisADImageNode();
+
+       		bgImages = new BranchGroup();
+       		bgImages.setCapability(BranchGroup.ALLOW_DETACH);
+       		bgImages.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+       		bgImages.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+       		bgImages.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+
+       		swit.addChild(bgImages);
+       		swit.setWhichChild(0);
+       		branch.addChild(swit);
+
+       		imgNode.setBranch(branch);
+       		imgNode.setSwitch(swit);
+       		((ImageRendererJ3D)renderer).setImageNode(imgNode);
+
+       		if ( ((BranchGroup) group).numChildren() > 0 ) {
+         		((BranchGroup)group).setChild(branch, 0);
+       		} else {
+         		((BranchGroup)group).addChild(branch);
+         /*
+         // make sure group is live.  group not empty (above addChild)
+         if (group instanceof BranchGroup) {
+           ((ImageRendererJ3D) renderer).setBranchEarly((BranchGroup) group);
+         }
+         */
+       		}
+	} else { //REUSE GEOM/COLBYTE: If its not the first time. And the dims have not changed but either color bytes or geometry has changed.
+       		imgNode = ((ImageRendererJ3D)renderer).getImageNode();
+       		bgImages = (BranchGroup) imgNode.getSwitch().getChild(0);	//REUSE GEOM/COLBYTE:Extract the bgImages from the avaialable switch
+     	} 
+
+
+    GraphicsModeControl mode = (GraphicsModeControl)
+          display.getGraphicsModeControl().clone();
+
+    // get some precomputed values useful for transform
+    // mapping from ValueArray to MapVector
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    Unit[] dataUnits = null;
+    CoordinateSystem dataCoordinateSystem = null;
+
+
+    if (null != animControl) {
+	Switch swit = new SwitchNotify(imgNode, numImages);  
+      	((AVControlJ3D) animControl).addPair((Switch) swit, domain_set, renderer);
+      	((AVControlJ3D) animControl).init();
+    }
+
+
+    domain_set = imgFlatField.getDomainSet();
+    dataUnits = ((Function) imgFlatField).getDomainUnits();
+    dataCoordinateSystem =
+      ((Function) imgFlatField).getDomainCoordinateSystem();
+
+    int domain_length = domain_set.getLength();
+    int[] lengths = ((GriddedSet) domain_set).getLengths();
+    int data_width = lengths[0];
+    int data_height = lengths[1];
+
+    imgNode.numImages = numImages;
+    imgNode.data_width = data_width;
+    imgNode.data_height = data_height;
+
+    int texture_width_max = link.getDisplay().getDisplayRenderer().getTextureWidthMax();
+    int texture_height_max = link.getDisplay().getDisplayRenderer().getTextureWidthMax();
+
+    int texture_width = textureWidth(data_width);
+    int texture_height = textureHeight(data_height);
+
+    if (reuseImages) {
+      if (prevImgNode.numImages != numImages || 
+          prevImgNode.data_width != data_width || prevImgNode.data_height != data_height) {
+        reuseImages = false;
+      }
+    }
+    if (reuseImages) {
+      imgNode.numChildren = prevImgNode.numChildren;
+      imgNode.imageTiles = prevImgNode.imageTiles;
+    }
+    else {
+	Mosaic mosaic = new Mosaic(data_height, texture_height_max, data_width, texture_width_max);
+      	for (Iterator iter = mosaic.iterator(); iter.hasNext();) {
+        	Tile tile = (Tile) iter.next();
+        	imgNode.addTile(new VisADImageTile(numImages, tile.height, tile.y_start, tile.width, tile.x_start));
+      	}
+    }
+
+    prevImgNode = imgNode;
+
+    ShadowRealTupleType Domain = adaptedShadowType.getDomain();
+    Unit[] domain_units = ((RealTupleType) Domain.getType()).getDefaultUnits();
+    float[] constant_color = null;
+
+    // check that domain is only spatial
+    if (!Domain.getAllSpatial() || Domain.getMultipleDisplayScalar()) {
+      throw new BadMappingException("domain must be only spatial");
+    }
+
+    // check domain and determine whether it is square or curved texture
+    boolean isTextureMap = adaptedShadowType.getIsTextureMap() &&
+                             (domain_set instanceof Linear2DSet ||
+                              (domain_set instanceof LinearNDSet &&
+                               domain_set.getDimension() == 2)) && 
+                             (domain_set.getManifoldDimension() == 2);
+
+
+    boolean curvedTexture = adaptedShadowType.getCurvedTexture() &&
+                            !isTextureMap &&
+                            curved_size > 0 &&
+                            (domain_set instanceof Gridded2DSet ||
+                             (domain_set instanceof GriddedSet &&
+                              domain_set.getDimension() == 2)) &&
+                             (domain_set.getManifoldDimension() == 2);
+
+	if (group instanceof BranchGroup) {
+        	((ImageRendererJ3D) renderer).setBranchEarly((BranchGroup) group);
+        }
+
+    first_time =true; //Ghansham: this variable just indicates to makeColorBytes whether it's the first tile of the image
+    if (isTextureMap) { // linear texture
+
+        if (imgNode.getNumTiles() == 1) {
+          VisADImageTile tile = imgNode.getTile(0);
+	  if (regen_colbytes) { //REUSE COLBYTES: regenerate only if required
+	          makeColorBytesDriver(imgFlatField, cmap, cmaps, constant_alpha, RangeComponents, color_length, domain_length, permute,
+        	              data_width, data_height, imageType, tile, 0);
+	  }
+		if (regen_geom) { //REUSE : REGEN GEOM  regenerate the geometry
+          		buildLinearTexture(bgImages, domain_set, dataUnits, domain_units, default_values, DomainComponents,
+                             valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha, 
+                             value_array, constant_color, display, tile);
+		} else { //REUSE Reuse the branch fully along with geometry. Just apply the colorbytes(Buffered Image)
+                    BranchGroup Branch_L1 = (BranchGroup) bgImages.getChild(0);
+                    Shape3D shape = (Shape3D) Branch_L1.getChild(0);
+                    applyTexture(shape, tile, apply_alpha, constant_alpha);
+                }
+
+        }
+        else {
+          BranchGroup branch = null;
+	  //if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE: Make a fresh branch
+	  if (!reuseImages) {
+	  	branch = new BranchGroup();
+          	branch.setCapability(BranchGroup.ALLOW_DETACH);
+          	branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+          	branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          	branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+	   } else { //REUSE the branch
+		branch = (BranchGroup) bgImages.getChild(0);
+	   }
+	  int branch_tile_indx = 0; //REUSE: to get the branch for a tile in case of multi-tile rendering
+          for (Iterator iter = imgNode.getTileIterator(); iter.hasNext();) {
+             VisADImageTile tile = (VisADImageTile) iter.next();
+
+		if (regen_colbytes) { //REUSE COLBYTES: regenerate only if required
+	                makeColorBytesDriver(imgFlatField, cmap, cmaps, constant_alpha, RangeComponents, color_length, domain_length, permute,
+                      		data_width, data_height, imageType, tile, 0);
+                	first_time = false; //Ghansham: setting 'first_time' variable false after the first tile has been generated
+		}
+		if (regen_geom) { //REUSE: Regenerate the geometry
+
+              		float[][] g00 = ((GriddedSet)domain_set).gridToValue(
+                   			new float[][] {{tile.xStart}, {tile.yStart}});
+              		float[][] g11 = ((GriddedSet)domain_set).gridToValue(
+                   			new float[][] {{tile.xStart+tile.width-1}, {tile.yStart+tile.height-1}});
+
+              		double x0 = g00[0][0];
+              		double x1 = g11[0][0];
+              		double y0 = g00[1][0];
+              		double y1 = g11[1][0];
+              		Set dset = new Linear2DSet(x0, x1, tile.width, y0, y1, tile.height);
+
+              		BranchGroup branch1 = null;
+			if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE: Make a fresh branch for each tile
+				branch1 = new BranchGroup();
+        	      		branch1.setCapability(BranchGroup.ALLOW_DETACH);
+              			branch1.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+              			branch1.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+              			branch1.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+			} else { //REUSE: Reuse the already built branch for each tile
+				branch1 = (BranchGroup) branch.getChild(branch_tile_indx);
+			}
+
+              		buildLinearTexture(branch1, dset, dataUnits, domain_units, default_values, DomainComponents,
+                                 valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha, 
+                                 value_array, constant_color, display, tile);
+			if (!reuseImages|| (regen_colbytes && regen_geom)) {
+		        	branch.addChild(branch1);
+			}
+			g00 = null;
+			g11 = null;
+			dset = null;
+		} else { //REUSE Reuse the branch fully along with geometry. Just apply the colorbytes(Buffered Image)
+                        BranchGroup branch1 = (BranchGroup) branch.getChild(branch_tile_indx);
+                        BranchGroup branch2 = (BranchGroup) branch1.getChild(0); //Beause we create a branch in textureToGroup
+                        Shape3D shape = (Shape3D) branch2.getChild(0);
+                        applyTexture(shape, tile, apply_alpha, constant_alpha);
+                }
+		if (0 == branch_tile_indx) { //Add the branch to get rendered as early as possible
+                        if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE : Add a new branch if created
+                                if (((Group) bgImages).numChildren() > 0) {
+                                        ((Group) bgImages).setChild(branch, 0);
+                                } else {
+                                        ((Group) bgImages).addChild(branch);
+                                }
+                        }
+                }
+
+               	branch_tile_indx++;
+
+          }
+
+        }
+      } // end if (isTextureMap)
+      else if (curvedTexture) {
+
+        int[] lens = ((GriddedSet)domain_set).getLengths();
+        int[] domain_lens = lens;
+
+        if (imgNode.getNumTiles() == 1) {
+          VisADImageTile tile = imgNode.getTile(0);
+	  	if (regen_colbytes) {  //REUSE COLBYTES: regenerate only if required
+                	makeColorBytesDriver(imgFlatField, cmap, cmaps, constant_alpha, RangeComponents, color_length, domain_length, permute,
+                      		data_width, data_height, imageType, tile,  0);
+		}
+	        if (regen_geom) { //REUSE: REGEN GEOM regenerate 
+          		buildCurvedTexture(bgImages, domain_set, dataUnits, domain_units, default_values, DomainComponents,
+                             valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha,
+                             value_array, constant_color, display, curved_size, Domain,
+                             dataCoordinateSystem, renderer, adaptedShadowType, new int[] {0,0},
+                             domain_lens[0], domain_lens[1], domain_lens[0], domain_lens[1], tile);
+		} else { //REUSE Reuse the branch fully along with geometry. Just apply the colorbytes(Buffered Image)
+			BranchGroup Branch_L1 = (BranchGroup) bgImages.getChild(0);
+                    	Shape3D shape = (Shape3D) Branch_L1.getChild(0);
+                    	applyTexture(shape, tile, apply_alpha, constant_alpha);
+		}
+        }
+        else
+        {
+
+	  BranchGroup branch = null;
+	if (!reuseImages || (regen_colbytes && regen_geom)) {  //REUSE: Make a fresh branch
+		branch = new BranchGroup();
+          	branch.setCapability(BranchGroup.ALLOW_DETACH);
+          	branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+          	branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          	branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+	  } else { //REUSE: Reuse already built branch 
+		branch = (BranchGroup) bgImages.getChild(0);
+          } 
+	  int branch_tile_indx = 0; //REUSE: to get the branch for a tile in case of multi-tile rendering
+          for (Iterator iter = imgNode.getTileIterator(); iter.hasNext();) {
+             VisADImageTile tile = (VisADImageTile) iter.next();
+		if (regen_colbytes) { //REUSE COLBYTES: regenerate only if required
+                	makeColorBytesDriver(imgFlatField, cmap, cmaps, constant_alpha, RangeComponents, color_length, domain_length, permute,
+                      		data_width, data_height, imageType, tile, 0);
+                	first_time = false; //Ghansham: setting 'first_time' variable false after the first tile has been generated
+		}
+
+		if (regen_geom) { //REUSE REGEN GEOM regenerate geometry 
+			BranchGroup branch1 = null;
+			if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE: Make a fresh branch group for each tile
+				branch1 = new BranchGroup();
+                        	branch1.setCapability(BranchGroup.ALLOW_DETACH);
+                        	branch1.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+                        	branch1.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+                        	branch1.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+			} else { //REUSE: Reuse the already existing branch for each tile
+				branch1 = (BranchGroup) branch.getChild(branch_tile_indx);
+			}
+			
+             		buildCurvedTexture(branch1, domain_set, dataUnits, domain_units, default_values, DomainComponents,
+                                valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha,
+                                value_array, constant_color, display, curved_size, Domain,
+                                dataCoordinateSystem, renderer, adaptedShadowType, 
+                                new int[] {tile.xStart,tile.yStart}, tile.width, tile.height,
+                                domain_lens[0], domain_lens[1], tile);
+
+			if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE: Add newly created branch 
+                              branch.addChild(branch1);
+			}
+		} else { //REUSE Reuse the branch fully along with geometry. Just apply the colorbytes(Buffered Image)
+			BranchGroup branch1 = (BranchGroup) branch.getChild(branch_tile_indx);
+                        BranchGroup branch2 = (BranchGroup) branch1.getChild(0);
+                        Shape3D shape = (Shape3D) branch2.getChild(0);
+                        applyTexture(shape, tile, apply_alpha, constant_alpha);
+		}
+		if (0 == branch_tile_indx) { //Add the branch to get rendered as early as possible
+			if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE : Add a new branch if created
+                		if (((Group) bgImages).numChildren() > 0) {
+                        		((Group) bgImages).setChild(branch, 0);
+                		} else {
+                        		((Group) bgImages).addChild(branch);
+                		}
+           		}
+		}
+		branch_tile_indx++;
+           }
+
+        }
+      } // end if (curvedTexture)
+      else { // !isTextureMap && !curvedTexture
+        throw new BadMappingException("must be texture map or curved texture map");
+      } 
+
+
+      // make sure group is live.  group not empty (above addChild)
+      /*if (group instanceof BranchGroup) {
+        ((ImageRendererJ3D) renderer).setBranchEarly((BranchGroup) group);
+      }*/
+
+
+      for (int k=1; k<numImages; k++) {
+        FlatField ff = (FlatField) ((Field)data).getSample(k);
+        CoordinateSystem dcs = ff.getDomainCoordinateSystem();
+        GriddedSet domSet = (GriddedSet) ff.getDomainSet();
+        int[] lens = domSet.getLengths();
+
+        // if image dimensions, or dataCoordinateSystem not equal to first image, resample to first
+	if (regen_colbytes) { //REUSE COLBYTES: resample the flatfield only if colorbytes need to be regenerated
+	        if ( (lens[0] != data_width || lens[1] != data_height) || !(dcs.equals(dataCoordinateSystem))) {
+         		ff = (FlatField) ff.resample(imgFlatField.getDomainSet(), Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+        	}
+	}
+
+        first_time = true;
+        scaled_Bytes = null; //scaled byte values 
+        scaled_Floats = null; //scaled Float Values
+	fast_table = null;
+	rset_scalarmap_lookup = null; //GHANSHAM:30AUG2011 create a lookup for rset FlatField range values
+        itable = null; //For single band
+        threeD_itable = null; //for multiband
+        color_values = null; //special case
+
+        for (Iterator iter = imgNode.getTileIterator(); iter.hasNext();) {
+          VisADImageTile tile = (VisADImageTile) iter.next();
+	  if(regen_colbytes) {	//REUSE COLBYTES: regenerate colobytes only if required
+          	makeColorBytesDriver(ff, cmap, cmaps, constant_alpha, RangeComponents, color_length, domain_length, permute,
+                	data_width, data_height, imageType, tile, k);
+           	first_time = false;
+	  }
+        }
+      }
+
+      cmaps = null;
+      first_time = true;
+      scaled_Bytes = null; //scaled byte values 
+      scaled_Floats = null; //scaled Float Values
+      fast_table = null;
+      rset_scalarmap_lookup = null; //GHANSHAM:30AUG2011 create a lookup for rset FlatField range values
+      itable = null; //For single band
+      threeD_itable = null; //for multiband
+      color_values = null; //special case
+
+
+      ensureNotEmpty(bgImages);
+      return false;
+  }
+
+//GHANSHAM: 01MAR2012 GreyScale Texture New Function
+//This function decides whether colortable is grey scale or not
+//It also ensures that alpha value is constant for an RGBA ColorTable float[4][]
+//12NOV2012: Removed Alpha constant Logic from here
+private boolean isColorTableGrey(float[][] color_table) {
+        boolean rgb_same = true;
+        for (int i = 0; i < color_table[0].length; i++) {
+                if (color_table[0][i] != color_table[1][i] || color_table[1][i] != color_table[2][i]) {
+                        rgb_same = false;
+                        break;
+                }
+        }
+	return rgb_same;
+}
+
+//12NOV2012: Separate Function to test if alpha is constant in lookup table
+private boolean isAlphaConstant(float[][] color_table) {
+        boolean alpha_same = true;
+        if (4 == color_table.length) {  //check alpha value is constant through out the look up table
+		int table_len = color_table[3].length;
+                float first_alpha = color_table[3][0];
+                for (int i =1; i < table_len; i++) {
+                        if (first_alpha != color_table[3][i]) {
+                                alpha_same = false;
+                                break;
+                        }
+                }
+        }
+        return alpha_same;
+}
+
+
+// This function calls makeColorBytes function (Ghansham)
+public void makeColorBytesDriver(Data imgFlatField, ScalarMap cmap, ScalarMap[] cmaps, float constant_alpha,
+              ShadowRealType[] RangeComponents, int color_length, int domain_length, int[] permute,
+              int data_width, int data_height,
+              int imageType, VisADImageTile tile, int image_index) throws VisADException, RemoteException {
+        BufferedImage image = null;
+        byte byteData[] = null;
+        int tile_width = tile.width;
+        int tile_height = tile.height;
+        int xStart = tile.xStart;
+        int yStart = tile.yStart;
+        int texture_width = textureWidth(tile_width);
+        int texture_height = textureHeight(tile_height);
+
+       if (!reuseImages) {
+         image = createImageByRef(texture_width, texture_height, imageType);
+         tile.setImage(image_index, image);
+       } else {
+         //image = (CachedBufferedByteImage) tile.getImage(0);
+         image = (BufferedImage) tile.getImage(image_index);
+	 //GHANSHAM: 01MAR2012 GreyScale Texture (starts here) 
+	 //If the incoming ImageType is not the same as the existing imageType, we will have to recreate the image.
+         //THIS HAPPENS when a single band GreyScale Image (GreyScale LUT applied) gets converted Color image(Colored LUT applied) and vice versa
+	 if (image.getType() != imageType) {
+                        image = createImageByRef(texture_width, texture_height, imageType);
+                        tile.setImage(image_index, image);
+         }
+	//GHANSHAM: 01MAR2012 GreyScale Texture (ends here)
+       }
+
+       java.awt.image.Raster raster = image.getRaster();
+       DataBuffer db = raster.getDataBuffer();
+       byteData = ((DataBufferByte)db).getData();
+       java.util.Arrays.fill(byteData, (byte)0);
+       makeColorBytes(imgFlatField, cmap, cmaps, constant_alpha, RangeComponents, color_length, domain_length, permute,
+                    byteData,
+                    data_width, data_height, tile_width, tile_height, xStart, yStart, texture_width, texture_height);
+}
+
+/*  New version contributed by Ghansham (ISRO)
+ This function scales the flatfield values and the colortable for the first tile only using the first_time variable. Rest of the time it only
+ uses scaled values and color table to generate colorbytes for respective tile. Just see the first_time variable use. That is the only difference between
+ this function and earlier function makeColorBytes(). Some new class variables have been introduced to preserve scaled values and colortable.
+ They are made null after all the tiles for a single image has been generated. At the end of doTransform(), they are made null.
+ */
+public void makeColorBytes(Data data, ScalarMap cmap, ScalarMap[] cmaps, float constant_alpha,
+		ShadowRealType[] RangeComponents, int color_length, int domain_length, int[] permute,
+		byte[] byteData, int data_width, int data_height, int tile_width, int tile_height, int xStart, int yStart,
+		int texture_width, int texture_height)
+throws VisADException, RemoteException {
+	if (cmap != null) {
+		BaseColorControl control = (BaseColorControl) cmap.getControl();
+		float[][] table = control.getTable();
+		Set rset = null;
+		boolean is_default_unit = false;
+
+		if (data instanceof FlatField) {
+			// for fast byte color lookup, need:
+				// 1. range data values are packed in bytes
+			if (first_time) {
+				scaled_Bytes = ((FlatField) data).grabBytes();
+			}
+			// 2. range set is Linear1DSet
+			Set[] rsets = ((FlatField) data). getRangeSets();
+			if (rsets != null) rset = rsets[0];
+			// 3. data Unit equals default Unit
+			RealType rtype = (RealType) RangeComponents[0].getType();
+			Unit def_unit = rtype.getDefaultUnit();
+			if (def_unit == null) {
+				is_default_unit = true;
+			} else {
+				Unit[][] data_units = ((FlatField) data).getRangeUnits();
+				Unit data_unit = (data_units == null) ? null : data_units[0][0];
+				is_default_unit = def_unit.equals(data_unit);
+			}
+		}
+		if (table != null) {
+			// combine color table RGB components into ints
+			if (first_time) {
+				itable = new byte[table[0].length][4];
+				// int r, g, b, a = 255;
+				int r, g, b;
+				int c = (int) (255.0 * (1.0f - constant_alpha));
+				int a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+				for (int j=0; j<table[0].length; j++) {
+					c = (int) (255.0 * table[0][j]);
+					r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+					c = (int) (255.0 * table[1][j]);
+					g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+					c = (int) (255.0 * table[2][j]);
+					b = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+					if (color_length == 4) {
+						c = (int) (255.0 * table[3][j]);
+						a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+					}
+					itable[j][0] = (byte) r;
+					itable[j][1] = (byte) g;
+					itable[j][2] = (byte) b;
+					itable[j][3] = (byte) a;
+				}
+			}
+			int tblEnd = table[0].length - 1;
+			// get scale for color table
+			int table_scale = table[0].length;
+			if (data instanceof ImageFlatField && scaled_Bytes != null && is_default_unit) {
+				if (ImageFlatField.DEBUG) {
+					System.err.println("ShadowImageFunctionTypeJ3D.doTransform: " + "cmap != null: looking up color values");
+				}
+				// avoid unpacking floats for ImageFlatFields
+				if (first_time) {
+					scaled_Bytes[0]= cmap.scaleValues(scaled_Bytes[0], table_scale); 
+				}
+				// fast lookup from byte values to color bytes
+				byte[] bytes0 = scaled_Bytes[0];
+
+				int k =0;
+				int color_length_times_texture_width = texture_width*color_length;
+				for (int y=0; y<tile_height; y++) {
+					int image_col_factor = (y+yStart)*data_width + xStart;
+					k= y*color_length_times_texture_width;
+					for (int x=0; x<tile_width; x++) {
+						int i = x + image_col_factor;
+						int j = bytes0[i] & 0xff; // unsigned
+						// clip to table
+						int ndx = j < 0 ? 0 : (j > tblEnd ? tblEnd : j);
+						//12NOV2012: Changed the order to 1, 3, 4 from 4, 3 1 and also put else-if. No need to check other if's if it is executed
+						//Reason: According to the probabilities. Grey Scale, 3-band RGB, 4-band only used in rare cases like: volume rendering
+						if (color_length == 1) {
+							byteData[k] = itable[ndx][0];
+						} else if (color_length == 3) {
+							byteData[k] = itable[ndx][2];
+							byteData[k+1] = itable[ndx][1];
+							byteData[k+2] = itable[ndx][0];
+						} else if (color_length == 4) {
+							byteData[k] = itable[ndx][3];
+							byteData[k+1] = itable[ndx][2];
+							byteData[k+2] = itable[ndx][1];
+							byteData[k+3] = itable[ndx][0];
+						}
+						k += color_length;
+					}
+				}
+			} else if (scaled_Bytes != null && scaled_Bytes[0] != null && is_default_unit && rset != null && rset instanceof Linear1DSet) {
+				// fast since FlatField with bytes, data Unit equals default
+				// Unit and range set is Linear1DSet
+				// get "scale and offset" for Linear1DSet
+				if (first_time) {
+					double first = ((Linear1DSet) rset).getFirst();
+					double step = ((Linear1DSet) rset).getStep();
+					// get scale and offset for ScalarMap
+					double[] so = new double[2];
+					double[] da = new double[2];
+					double[] di = new double[2];
+					cmap.getScale(so, da, di);
+					double scale = so[0];
+					double offset = so[1];
+					// combine scales and offsets for Set, ScalarMap and color table
+					float mult = (float) (table_scale * scale * step);
+					float add = (float) (table_scale * (offset + scale * first));
+
+					// build table for fast color lookup
+					fast_table = new byte[256][];
+					for (int j=0; j<256; j++) {
+						int index = j - 1;
+						if (index >= 0) { // not missing
+							int k = (int) (add + mult * index);
+							// clip to table
+							int ndx = k < 0 ? 0 : (k > tblEnd ? tblEnd : k);
+							fast_table[j] = itable[ndx];
+						}
+					}
+				}
+				// now do fast lookup from byte values to color bytes
+				byte[] bytes0 = scaled_Bytes[0];
+
+				int k = 0;
+				int image_col_offset = yStart*data_width + xStart;
+				int image_col_factor = image_col_offset;
+                                int pot_texture_offset = color_length*(texture_width-tile_width);
+				//Strength Reduction: Replacing multiplications with Addition 12NOV2012
+				for (int y=0; y<tile_height; y++) {
+					for (int x=0; x<tile_width; x++) {
+						int i = x + image_col_factor;
+						int ndx = ((int) bytes0[i]) - MISSING1;
+						//12NOV2012: Changed the order to 1, 3, 4 from 4, 3 1 and also put else-if. No need to check other if's if it is executed
+						//Reason: According to the probabilities. Grey Scale, 3-band RGB, 4-band only used in rare cases like: volume rendering
+                                                if (color_length == 1) {
+                                                        byteData[k]   = fast_table[ndx][0];
+                                                } else if (color_length == 3) {
+                                                        byteData[k]   = fast_table[ndx][2];
+                                                        byteData[k+1] = fast_table[ndx][1];
+                                                        byteData[k+2] = fast_table[ndx][0];
+                                                } else if (color_length == 4) {
+                                                        byteData[k]   = fast_table[ndx][3];
+                                                        byteData[k+1] = fast_table[ndx][2];
+                                                        byteData[k+2] = fast_table[ndx][1];
+                                                        byteData[k+3] = fast_table[ndx][0];
+                                                }
+						k+=color_length;
+					}
+					k += pot_texture_offset;
+                                        image_col_factor += data_width;
+				}
+			} else {
+				// medium speed way to build texture colors
+				if (first_time) {
+					scaled_Bytes = null;
+					scaled_Floats = ((Field) data).getFloats(false);
+					//GHANSHAM:30AUG2011 If rset can be used to create a lookup for range values, create them
+					if (rset instanceof Integer1DSet) {
+						//12NOV2012: NEW LOGIC for Range Set map Lookup (starts here)
+						//This logic stores indices of lookup table from where color values will be picked up
+						//Earlier it was scaled 0...1 values. 
+						int rset_len = rset.getLength();
+						float temp_lookup[] = new float[rset_len];
+						for (int i = 0; i < rset_len; i++) {	
+							temp_lookup[i] = i;
+						}
+						temp_lookup = cmap.scaleValues(temp_lookup, false);
+						rset_scalarmap_lookup = new int[1][rset_len];
+						for (int i = 0; i < rset_len; i++) {
+							rset_scalarmap_lookup[0][i] = (int)(table_scale*temp_lookup[i]);
+						}
+						temp_lookup = null;
+						//12NOV2012: NEW LOGIC for  Range Set map Lookup (ends here)
+
+					} else {
+						scaled_Floats[0] = cmap.scaleValues(scaled_Floats[0]);
+					}
+				}
+				// now do fast lookup from byte values to color bytes
+				float[] values0 = scaled_Floats[0];
+				int k = 0;
+				//int color_length_times_texture_width = texture_width*color_length;
+				int image_col_offset = yStart*data_width + xStart;
+				int image_col_factor = 0;
+				int pot_texture_offset;
+				boolean use_lookup = null != rset_scalarmap_lookup && null != rset_scalarmap_lookup[0];
+				image_col_factor = image_col_offset; 
+				pot_texture_offset = color_length*(texture_width-tile_width);
+				//Strength Reduction: Replacing multiplications with Addition 12NOV2012
+				for (int y=0; y<tile_height; y++) {
+					for (int x=0; x<tile_width; x++) {
+						int i = x + image_col_factor;
+						if (!Float.isNaN(values0[i])) { // not missing
+							int j = 0;
+							//GHANSHAM:30AUG2011 Use the rset lookup to find scaled Range Values
+							if (use_lookup) {
+								//12NOV2012: NEW LOGIC for Range Set map Lookup
+								//It simply finds index from lookup created above. 
+								//It does away with the multiplication of scaled value with lookup table length.
+								//The indices have been calculated once and for all. Usable for count data only.
+								//j = (int) (table_scale*rset_scalarmap_lookup[0][(int)values0[i]]);
+								j = (int) rset_scalarmap_lookup[0][(int)values0[i]];
+							} else {
+								j = (int) (table_scale*values0[i]);
+							}
+							// clip to table
+							int ndx = j < 0 ? 0 : (j > tblEnd ? tblEnd : j);
+							//12NOV2012: Changed the order to 1, 3, 4 from 4, 3 1 and also put else-if. No need to check other if's if it is executed
+							//Reason: According to the probabilities. Grey Scale, 3-band RGB, 4-band only used in rare cases like: volume rendering
+							if (color_length == 1) {
+								byteData[k] = itable[ndx][0];
+							} else if (color_length == 3) {
+								byteData[k] = itable[ndx][2];
+								byteData[k+1] = itable[ndx][1];
+								byteData[k+2] = itable[ndx][0];
+							} else if (color_length == 4) {
+								byteData[k] = itable[ndx][3];
+								byteData[k+1] = itable[ndx][2];
+								byteData[k+2] = itable[ndx][1];
+								byteData[k+3] = itable[ndx][0];
+							}
+						}
+						k+=color_length;
+					}
+					k += pot_texture_offset;
+					image_col_factor += data_width;
+				}
+			}
+		} else { // if (table == null)
+			// slower, more general way to build texture colors
+			if (first_time) {
+				// call lookupValues which will use function since table == null
+				scaled_Bytes = null;
+				itable = null;
+				scaled_Floats = ((Field) data).getFloats(false);
+				scaled_Floats[0] = cmap.scaleValues(scaled_Floats[0]);
+				color_values = control.lookupValues(scaled_Floats[0]);
+			}
+
+			// combine color RGB components into bytes
+			// int r, g, b, a = 255;
+			int r, g, b;
+			int c = (int) (255.0 * (1.0f - constant_alpha));
+			int a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+			int k = 0;
+			int image_col_offset = yStart*data_width + xStart;
+			int image_col_factor = image_col_offset;
+                        int pot_texture_offset = color_length*(texture_width-tile_width);
+			//Strength Reduction: Replacing multiplications with Addition 12NOV2012
+			for (int y=0; y<tile_height; y++) {
+				for (int x=0; x<tile_width; x++) {
+					int i = x + image_col_factor;
+					if (!Float.isNaN(scaled_Floats[0][i])) { // not missing
+						c = (int) (255.0 * color_values[0][i]);
+						r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						c = (int) (255.0 * color_values[1][i]);
+						g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						c = (int) (255.0 * color_values[2][i]);
+						b = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						if (color_length == 4) {
+							c = (int) (255.0 * color_values[3][i]);
+							a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						}
+						//12NOV2012: Changed the order to 1, 3, 4 from 4, 3 1 and also put else-if. No need to check other if's if it is executed
+						//Reason: According to the probabilities. Grey Scale, 3-band RGB, 4-band only used in rare cases like: volume rendering
+						if (color_length == 1) {
+							byteData[k] = (byte) b;
+						} else if (color_length == 3) {
+							byteData[k] = (byte) b;
+							byteData[k+1] = (byte) g;
+							byteData[k+2] = (byte) r;
+						} else if (color_length == 4) {
+							byteData[k] = (byte) a;
+							byteData[k+1] = (byte) b;
+							byteData[k+2] = (byte) g;
+							byteData[k+3] = (byte) r;
+						}
+					}
+					k+=color_length;
+				}
+				k += pot_texture_offset;
+				image_col_factor += data_width;
+			}
+		}
+	} else if (cmaps != null) {
+		Set rsets[] = null;
+		if (data instanceof ImageFlatField) {
+			if (first_time) {
+				scaled_Bytes = ((FlatField) data).grabBytes();
+			}
+		}
+		//GHANSHAM:30AUG2011 Extract rsets from RGB FlatField
+		if (data instanceof FlatField) {
+			rsets = ((FlatField) data). getRangeSets();
+		}
+
+
+		boolean isRGBRGBRGB = ((cmaps[0].getDisplayScalar() == Display.RGB) && (cmaps[1].getDisplayScalar() == Display.RGB) && (cmaps[2].getDisplayScalar() == Display.RGB));
+
+		int r, g, b, c;
+		int tableEnd = 0;
+		if (first_time) {
+			if  (isRGBRGBRGB) { //Inserted by Ghansham (starts here)
+				int map_indx;
+				threeD_itable = new byte[cmaps.length][][];
+				for (map_indx = 0; map_indx < cmaps.length; map_indx++) {
+					BaseColorControl basecolorcontrol = (BaseColorControl)cmaps[map_indx].getControl();
+					float color_table[][] = basecolorcontrol.getTable();
+					threeD_itable[map_indx] = new byte[color_table[0].length][3];
+					int table_indx;
+					for(table_indx = 0; table_indx < threeD_itable[map_indx].length; table_indx++) {
+						c = (int) (255.0 * color_table[0][table_indx]);
+						r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						c = (int) (255.0 * color_table[1][table_indx]);
+						g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						c = (int) (255.0 * color_table[2][table_indx]);
+						b = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						threeD_itable[map_indx][table_indx][0] = (byte) r;
+						threeD_itable[map_indx][table_indx][1] = (byte) g;
+						threeD_itable[map_indx][table_indx][2] = (byte) b;
+					}
+				}
+			}
+		}
+
+		if (scaled_Bytes != null) {
+			// grab bytes directly from ImageFlatField
+			if (ImageFlatField.DEBUG) {
+				System.err.println("ShadowImageFunctionTypeJ3D.doTransform: " + "cmaps != null: grab bytes directly");
+			}
+			//Inserted by Ghansham starts here
+			//IFF:Assume that FlatField is of type (element,line)->(R,G,B) with (Display.RGB,Display.RGB,Display.RGB) as mapping
+			if  (cmaps[0].getDisplayScalar() == Display.RGB && cmaps[1].getDisplayScalar() == Display.RGB && cmaps[2].getDisplayScalar() == Display.RGB) {
+				int map_indx = 0;
+				for (map_indx = 0; map_indx < cmaps.length; map_indx++) {
+					int table_length = threeD_itable[0].length;
+					int color_indx = permute[map_indx];
+					if (first_time) {
+						scaled_Bytes[color_indx] = cmaps[color_indx].scaleValues(scaled_Bytes[color_indx], table_length);
+					}
+					int domainLength =  scaled_Bytes[color_indx].length;
+					int tblEnd = table_length - 1;
+					int data_indx = 0;
+					int texture_index = 0;
+
+					int image_col_offset = yStart*data_width + xStart;
+					int image_col_factor = 0;
+					for (int y=0; y<tile_height; y++) { 
+						image_col_factor = y*data_width + image_col_offset;
+						for (int x=0; x<tile_width; x++) {
+							data_indx = x + image_col_factor;
+							texture_index = x + y*texture_width;
+							texture_index *= color_length;
+							int j = scaled_Bytes[color_indx][data_indx] & 0xff; // unsigned
+							// clip to table
+							int ndx = j < 0 ? 0 : (j > tblEnd ? tblEnd : j);
+							byteData[texture_index+(color_length-color_indx-1)]=threeD_itable[map_indx][ndx][map_indx]; //Check if this logic works well
+						}
+					}
+
+				}
+			} else { //Inserted by Ghansham (Ends here)
+				int data_indx = 0;
+				int texture_index = 0;
+				c = 0;
+				if (color_length == 4) {
+					c = (int) (255.0 * (1.0f - constant_alpha));
+				}
+				//IFF:with (Red,Green,Blue) or (Red,Green,Blue,Alpha) as mapping
+				int image_col_offset = yStart*data_width + xStart;
+				int image_col_factor = image_col_offset;
+				int pot_texture_offset = color_length*(texture_width-data_width);
+				//Strength Reduction: Replacing multiplications with Addition 12NOV2012
+				for (int y=0; y<tile_height; y++) {
+					for (int x=0; x<tile_width; x++) {
+						data_indx = x + image_col_factor;
+						//12NOV2012: We expect 3-Byte RGB to be more common case than 4-byte RGB
+						if (color_length == 3) {
+							byteData[texture_index] = scaled_Bytes[2][data_indx]; //b
+							byteData[texture_index+1] = scaled_Bytes[1][data_indx]; //g
+							byteData[texture_index+2] = scaled_Bytes[0][data_indx]; //r
+						} else {
+							byteData[texture_index] =   (byte)c; //a
+							byteData[texture_index+1] = scaled_Bytes[2][data_indx]; //b
+							byteData[texture_index+2] = scaled_Bytes[1][data_indx]; //g
+							byteData[texture_index+3] = scaled_Bytes[0][data_indx]; //r
+						}
+						texture_index += color_length;
+					}
+					texture_index += pot_texture_offset;
+					image_col_factor += data_width;
+				}
+			}
+		} else {
+			int RGB_tableEnd[] = null;;
+			//GHANSHAM:30AUG2011 Create tableLengths for each of the tables separately rather than single table_length. More safe
+                        if  (isRGBRGBRGB) {
+                                RGB_tableEnd = new int[threeD_itable.length];
+                                for (int indx = 0; indx < threeD_itable.length; indx++) {
+                                        RGB_tableEnd[indx]= threeD_itable[permute[indx]].length - 1;
+                                }
+                        }
+
+			if (first_time) {
+				float[][] values = ((Field) data).getFloats(false);
+				scaled_Floats = new float[3][];
+				for (int i = 0; i < scaled_Floats.length; i++) {
+					//GHANSHAM:30AUG2011 Use the rset lookup to find scaled Range Values	
+					if (rsets != null) {
+						if (rsets[permute[i]] instanceof Integer1DSet) {
+							//12NOV2012: NEW LOGIC for Range Set map Lookup (starts here)
+							//This logic stores indices of lookup table from where color values will be picked up
+							//Earlier it was scaled 0...1 values. 
+							if (null == rset_scalarmap_lookup) {
+								rset_scalarmap_lookup = new int[3][];
+							}
+							int rset_len = rsets[permute[i]].getLength();
+							float temp_lookup[] = new float[rset_len];
+							for (int j = 0; j < rset_len; j++) {
+                                                                temp_lookup[j] = j;
+                                                        }
+							temp_lookup = cmaps[permute[i]].scaleValues(temp_lookup, false);
+							int table_scale = 0;
+							if (isRGBRGBRGB) {
+								table_scale = RGB_tableEnd[i];
+							} else {
+								table_scale = 255;
+							}
+							for (int j = 0; j < rset_len; j++) {
+								rset_scalarmap_lookup[i][j] = (int)(table_scale*temp_lookup[j]);
+							}
+							temp_lookup = null;
+							scaled_Floats[i] = values[permute[i]];
+						} else {
+							scaled_Floats[i] = cmaps[permute[i]].scaleValues(values[permute[i]]);
+						}
+					} else {
+						scaled_Floats[i] = cmaps[permute[i]].scaleValues(values[permute[i]]);
+					}
+				}
+			}
+			c = (int) (255.0 * (1.0f - constant_alpha));
+			int a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+			int m = 0;
+			int k = 0;
+			int image_col_offset = yStart*data_width + xStart;
+			int image_col_factor = image_col_offset;
+			int pot_texture_offset = color_length*(texture_width-tile_width);
+			//12NOV2012: Evaluate boolean variables once and use them within the loop.
+			//No need to evaluate them in the loop. Compiler Optimization: Loop Invariant Code Motion.
+			boolean use_lookup_red = (rset_scalarmap_lookup != null && rset_scalarmap_lookup[0] != null);
+			boolean use_lookup_grn = (rset_scalarmap_lookup != null && rset_scalarmap_lookup[1] != null);
+			boolean use_lookup_blu = (rset_scalarmap_lookup != null && rset_scalarmap_lookup[2] != null);
+			//12NOV2012: NEW LOGIC for Range Set map Lookup
+			//It simply finds index from lookup created above. 
+			//It does away with the multiplication of scaled value with lookup table length.
+			//The indices have been calculated once and for all. Usable for count data only.
+			//See how indx variable is calculated when rset_map_lookup is used.
+			for (int y=0; y<tile_height; y++) {
+				//Strength Reduction: Replacing multiplications with Addition 12NOV2012
+				for (int x=0; x<tile_width; x++) {
+					int i = x + image_col_factor;
+					if (!Float.isNaN(scaled_Floats[0][i]) && !Float.isNaN(scaled_Floats[1][i]) && !Float.isNaN(scaled_Floats[2][i])) { // not missing
+						r=0;g=0;b=0;
+                                                a = 255; //TDR (25JAN2012) init to opaque, can't get alpha from the three RGB tables
+						if (isRGBRGBRGB) { //Inserted by Ghansham (start here)
+							int indx = -1;
+							//GHANSHAM:30AUG2011 Use the rset_scalarmap lookup to find scaled Range Values
+							if (use_lookup_red) {
+								indx = rset_scalarmap_lookup[0][(int)scaled_Floats[0][i]];
+							} else{
+								indx = (int)(RGB_tableEnd[0] * scaled_Floats[0][i]);
+							}
+							indx = (indx < 0) ? 0 : ((indx > RGB_tableEnd[0]) ? RGB_tableEnd[0] : indx);
+							r = threeD_itable[0][indx][0];
+							//GHANSHAM:30AUG2011 Use the rset_scalarmap lookup to find scaled Range Values
+							if (use_lookup_grn) {
+								indx = rset_scalarmap_lookup[1][(int)scaled_Floats[1][i]];
+							} else{
+								indx = (int)(RGB_tableEnd[1] * scaled_Floats[1][i]);
+							}
+							indx = (indx < 0) ? 0 : ((indx > RGB_tableEnd[1]) ? RGB_tableEnd[1] : indx);
+							g = threeD_itable[1][indx][1];
+							//GHANSHAM:30AUG2011 Use the rset_scalarmap lookup to find scaled Range Values
+							if (use_lookup_blu) {
+								indx = rset_scalarmap_lookup[2][(int)scaled_Floats[2][i]];
+							} else {
+								indx = (int)(RGB_tableEnd[2] * scaled_Floats[2][i]);
+							}
+							indx = (indx < 0) ? 0 : ((indx > RGB_tableEnd[2]) ? RGB_tableEnd[2] : indx);
+							b = threeD_itable[2][indx][2];
+						} else { //Inserted by Ghansham (ends here)
+							//GHANSHAM:30AUG2011 Use the rset_scalarmap lookup to find scaled Range Values
+							if (use_lookup_red) {
+								c = rset_scalarmap_lookup[0][(int)scaled_Floats[0][i]];
+							} else {
+								c = (int) (255.0 * scaled_Floats[0][i]);
+							}
+							r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+							//GHANSHAM:30AUG2011 Use the rset_scalarmap lookup to find scaled Range Values
+							if (use_lookup_grn) {
+								c= rset_scalarmap_lookup[1][(int)scaled_Floats[1][i]];
+							} else {
+								c = (int) (255.0 * scaled_Floats[1][i]);
+							}
+							g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+							//GHANSHAM:30AUG2011 Use the rset_scalarmap lookup to find scaled Range Values
+							if (use_lookup_blu) {
+								c= rset_scalarmap_lookup[1][(int)scaled_Floats[2][i]];
+							} else {
+								c = (int) (255.0 * scaled_Floats[2][i]);
+							}
+							b = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+						}
+						//12NOV2012: Changed the order to 1, 3, 4 from 4, 3 1 and also put else-if. No need to check other if's if it is executed
+						//Reason: According to the probabilities. Grey Scale, 3-band RGB, 4-band only used in rare cases like: volume rendering
+						if (color_length == 1) {
+							byteData[k] = (byte) b;
+						} else if (color_length == 3) {
+							byteData[k] = (byte) b;
+							byteData[k+1] = (byte) g;
+							byteData[k+2] = (byte) r;
+						} else if (color_length == 4) {
+							byteData[k] = (byte) a;
+							byteData[k+1] = (byte) b;
+							byteData[k+2] = (byte) g;
+							byteData[k+3] = (byte) r;
+						}
+					}
+					k+=color_length;
+				}
+				k += pot_texture_offset;
+				image_col_factor += data_width;
+			}
+			RGB_tableEnd = null;
+		}
+	} else {
+		throw new BadMappingException("cmap == null and cmaps == null ??");
+	}
+}
+
+
+  public void buildCurvedTexture(Object group, Set domain_set, Unit[] dataUnits, Unit[] domain_units,
+                                 float[] default_values, ShadowRealType[] DomainComponents,
+                                 int valueArrayLength, int[] inherited_values, int[] valueToScalar,
+                                 GraphicsModeControl mode, float constant_alpha, float[] value_array, 
+                                 float[] constant_color, DisplayImpl display,
+                                 int curved_size, ShadowRealTupleType Domain, CoordinateSystem dataCoordinateSystem,
+                                 DataRenderer renderer, ShadowFunctionOrSetType adaptedShadowType,
+                                 int[] start, int lenX, int lenY, int bigX, int bigY,
+                                 VisADImageTile tile)
+         throws VisADException, DisplayException {
+    float[] coordinates = null;
+    float[] texCoords = null;
+    int data_width = 0;
+    int data_height = 0;
+    int texture_width = 1;
+    int texture_height = 1;
+
+    if (dataCoordinateSystem instanceof CachingCoordinateSystem) {
+        dataCoordinateSystem = ((CachingCoordinateSystem)dataCoordinateSystem).getCachedCoordinateSystem();
+    }
+	data_width = lenX;
+        data_height = lenY;
+
+    // texture sizes must be powers of two on older graphics cards.
+    texture_width = textureWidth(data_width);
+    texture_height = textureHeight(data_height);
+                                                                                                                   
+    // transform for any CoordinateSystem in data (Field) Domain
+    ShadowRealTupleType domain_reference = Domain.getReference();
+    ShadowRealType[] DC = DomainComponents;
+
+    if (domain_reference != null &&
+        domain_reference.getMappedDisplayScalar()) {
+      RealTupleType ref = (RealTupleType) domain_reference.getType();
+      renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                  ref.getDefaultUnits(), (RealTupleType) Domain.getType(),
+                  new CoordinateSystem[] {dataCoordinateSystem},
+                  domain_units);
+
+      // ShadowRealTypes of DomainReference
+      DC = adaptedShadowType.getDomainReferenceComponents();
+    }
+    else {
+      RealTupleType ref = (domain_reference == null) ? null :
+                          (RealTupleType) domain_reference.getType();
+      Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+      renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                  ref_units, (RealTupleType) Domain.getType(),
+                  new CoordinateSystem[] {dataCoordinateSystem},
+                  domain_units);
+    }
+                                                                                                                   
+    int[] tuple_index = new int[3];
+    int[] spatial_value_indices = {-1, -1, -1};
+    ScalarMap[] spatial_maps = new ScalarMap[3];
+                                                                                                                   
+    DisplayTupleType spatial_tuple = null;
+    for (int i=0; i<DC.length; i++) {
+      Enumeration maps =
+        DC[i].getSelectedMapVector().elements();
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      DisplayRealType real = map.getDisplayScalar();
+      spatial_tuple = real.getTuple();
+      if (spatial_tuple == null) {
+        throw new DisplayException("texture with bad tuple: " +
+                                   "ShadowImageFunctionTypeJ3D.doTransform");
+      }
+      // get spatial index
+      tuple_index[i] = real.getTupleIndex();
+      spatial_value_indices[tuple_index[i]] = map.getValueIndex();
+      spatial_maps[tuple_index[i]] = map;
+      if (maps.hasMoreElements()) {
+        throw new DisplayException("texture with multiple spatial: " +
+                                   "ShadowImageFunctionTypeJ3D.doTransform");
+      }
+    } // end for (int i=0; i<DC.length; i++)
+
+
+    // get spatial index not mapped from domain_set
+    tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+    DisplayRealType real =
+      (DisplayRealType) spatial_tuple.getComponent(tuple_index[2]);
+    int value2_index = display.getDisplayScalarIndex(real);
+    float value2 = default_values[value2_index];
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0 &&
+          real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+        value2 = value_array[i];
+        break;
+      }
+    }
+                                                                                                                   
+    boolean useLinearTexture = false;
+    double[] scale = null;
+    double[] offset = null;
+    CoordinateSystem coord = null;
+
+    if (spatial_tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+// inside 'if (anyFlow) {}' in ShadowType.assembleSpatial()
+	renderer.setEarthSpatialDisplay(null, spatial_tuple, display,
+               spatial_value_indices, default_values, null);
+    } else {
+      	coord = spatial_tuple.getCoordinateSystem();
+
+      	if (coord instanceof CachingCoordinateSystem) {
+        	coord = ((CachingCoordinateSystem)coord).getCachedCoordinateSystem();
+      	}
+
+      	if (coord instanceof InverseLinearScaledCS) {
+        	InverseLinearScaledCS invCS = (InverseLinearScaledCS)coord;
+        	useLinearTexture = (invCS.getInvertedCoordinateSystem()).equals(dataCoordinateSystem);
+        	scale = invCS.getScale();
+        	offset = invCS.getOffset();
+      	}
+
+// inside 'if (anyFlow) {}' in ShadowType.assembleSpatial()
+      	renderer.setEarthSpatialDisplay(coord, spatial_tuple, display,
+               spatial_value_indices, default_values, null);
+    }
+
+    if (useLinearTexture) {
+    	float scaleX = (float) scale[0];
+      	float scaleY = (float) scale[1];
+      	float offsetX = (float) offset[0];
+      	float offsetY = (float) offset[1];
+
+      	float[][] xyCoords = null;
+	//12NOV2012: GHANSHAM Just adding a general a way to create tile corner coordinates (starts here)
+        int indices[] = new int[4];
+        indices[0] = (start[0] ) + (start[1])*bigX;
+        indices[1] = (start[0]) + (start[1] + lenY-1)*bigX;
+        indices[2] = (start[0] + lenX -1) + (start[1]+ lenY - 1)*bigX;
+        indices[3] = (start[0] + lenX -1) + (start[1])*bigX;
+        xyCoords = domain_set.indexToValue(indices);
+        indices = null;
+        for (int i = 0; i < 4; i++) {
+                xyCoords[0][i] = (xyCoords[0][i] - offsetX)/scaleX;
+                xyCoords[1][i] = (xyCoords[1][i] - offsetY)/scaleY;
+        }
+
+
+      // create VisADQuadArray that texture is mapped onto
+      coordinates = new float[12];
+      // corner 0 (-1,1)
+      coordinates[0] = xyCoords[0][0];
+      coordinates[1] = xyCoords[1][0];
+      coordinates[2] = value2;
+      // corner 1 (-1,-1)
+      coordinates[3] = xyCoords[0][1];
+      coordinates[4] = xyCoords[1][1];
+      coordinates[5] = value2;
+      // corner 2 (1, -1)
+      coordinates[6] = xyCoords[0][2];
+      coordinates[7] = xyCoords[1][2];
+      coordinates[8] = value2;
+      // corner 3 (1,1)
+      coordinates[9] = xyCoords[0][3];
+      coordinates[10] = xyCoords[1][3];
+      coordinates[11] = value2;
+
+      // move image back in Java3D 2-D mode
+      adjustZ(coordinates);
+
+      texCoords = new float[8];
+      float ratiow = ((float) data_width) / ((float) texture_width);
+      float ratioh = ((float) data_height) / ((float) texture_height);
+
+      /*   Assumes texels are squeezed into texture coordinate space (0.0 to 1.0)
+       *   so that the leftmost edge of the first texel is 0.0 and the rightmost
+       *   edge of the last texel is 1.0. The same is true for the height dimension
+       *   if yUp=true which is the case here (byReference).
+       */
+
+      float width = 1.0f / ((float)texture_width);         // Texel width
+      float height = 1.0f / ((float)texture_height);       // Texel height
+      float half_width = 0.5f / ((float) texture_width);   // half texel width
+      float half_height = 0.5f / ((float) texture_height); // half texel height
+
+      /*   Map the data point spatial coordinates to the center of the texels by
+       *   by starting at center of the first texel (texel width) and accumulating
+       *   a full texel width for each data point (the data location is the center
+       *   of the display pixel).  The equations are for yUp=true only (byReference).
+       *   Note: the form of these equations implicitly deal with the case NPOT=false
+       *   wherein the texture_width will be greater than or equal to the data_width.
+       */
+
+      // corner 0
+      texCoords[0] = half_width;
+      texCoords[1] = half_height;
+      // corner 1
+      texCoords[2] = half_width;
+      texCoords[3] = half_height + (data_height-1)*height;
+      // corner 2
+      texCoords[4] = half_width + (data_width-1)*width;
+      texCoords[5] = half_height + (data_height-1)*height;
+      // corner 3
+      texCoords[6] = half_width + (data_width-1)*width;
+      texCoords[7] = half_height;
+
+
+      VisADQuadArray qarray = new VisADQuadArray();
+      qarray.vertexCount = 4;
+      qarray.coordinates = coordinates;
+      qarray.texCoords = texCoords;
+
+      /*REUSE GEOM/COLORBYTES:I have replaced reuse with reuseImages.
+      And here in the else logic I have added a few more lines. 
+        The else part of this never got executed because reuse was always false.
+        Now else part gets executed when reuse is true and either regen_geom or regen_colbytes is true.
+        It just applies geometry to the already available texture.
+        When both are true then if part gets executed. 
+      */
+      	//if (!reuse) 
+      	if (!reuseImages || (regen_colbytes && regen_geom)) {	//REUSE GEOM/COLORBYTES: Earlier reuse variable was used. Replaced it with reuseImages and regeom_colbytes and regen_geom
+        	BufferedImage image = tile.getImage(0);
+         	textureToGroup(group, qarray, image, mode, constant_alpha,
+                        constant_color, texture_width, texture_height, true, true, tile);
+      	} else {	//REUSE GEOM/COLORBYTES: reuse the colorbytes just apply the geometry
+		int num_children = ((BranchGroup) group).numChildren();
+        	if (num_children > 0) {
+                	BranchGroup branch1 = (BranchGroup) ((BranchGroup) group).getChild(0); //This the branch group created by textureToGroup Function
+                	Shape3D shape = (Shape3D) branch1.getChild(0);
+                	shape.setGeometry(((DisplayImplJ3D) display).makeGeometry(qarray));
+        	}
+      	}
+
+    }
+    else {
+      // compute size of triangle array to map texture onto
+      int size = (data_width + data_height) / 2;
+      curved_size = Math.max(2, Math.min(curved_size, size / 32));
+      int nwidth = 2 + (data_width - 1) / curved_size;
+      int nheight = 2 + (data_height - 1) / curved_size;
+
+      // compute locations of triangle vertices in texture
+      int nn = nwidth * nheight;
+      int[] is = new int[nwidth];
+      int[] js = new int[nheight];
+	//12NOV12: Applying Strength Reduction, replacing multiplication/Min functions with additions
+	int ival = 0, jval = 0;
+	for (int i = 0; i < nwidth-1; i++) {
+		is[i] = ival;
+		ival += curved_size;
+	}
+	is[nwidth-1] = data_width -1;
+	for (int j = 0; j < nheight-1; j++) {
+		js[j] = jval;
+		jval += curved_size;
+	}
+	js[nheight-1] = data_height -1;
+	
+
+      // get spatial coordinates at triangle vertices
+      int k = 0;
+      float[][] spline_domain = null;
+	//12NOV2012: GHANSHAM More general way to get triangle corner coordinates
+        //Enhanced version of single tile case. Extended for multi-tile case
+        int[] indices = new int[nn];
+        int col_factor = 0;
+        for (int j=0; j<nheight; j++) {
+                col_factor = (start[1] + js[j]) * bigX;
+                for (int i=0; i<nwidth; i++) {
+                        indices[k] = (start[0] + is[i]) + col_factor;
+                        k++;
+                }
+        }
+        spline_domain = domain_set.indexToValue(indices);
+        indices = null;
+
+
+      spline_domain = Unit.convertTuple(spline_domain, dataUnits, domain_units, false);
+
+
+       if (domain_reference != null
+             && domain_reference.getMappedDisplayScalar()) {
+           RealTupleType ref = (RealTupleType) domain_reference.getType();
+
+            spline_domain =
+                   CoordinateSystem.transformCoordinates(
+                   ref, null, ref.getDefaultUnits(), null,
+                   (RealTupleType) Domain.getType(), dataCoordinateSystem,
+                   domain_units, null, spline_domain);
+       }
+
+       boolean isSpherical = spatial_tuple.equals(Display.DisplaySpatialSphericalTuple);
+       float[][] spatial_values = new float[3][];
+       spatial_values[tuple_index[0]] = spline_domain[0];
+       spatial_values[tuple_index[1]] = spline_domain[1];
+
+       if (isSpherical) { //02JUN2012: allocate array for 3rd dimension if it is spherical coordsys
+               spatial_values[tuple_index[2]] = new float[nn];
+               java.util.Arrays.fill(spatial_values[tuple_index[2]], value2);
+       }
+       for (int i = 0; i < 3; i++) {
+          if (spatial_maps[i] != null) {
+             spatial_values[i] = spatial_maps[i].scaleValues(spatial_values[i], false);
+          }
+       }
+
+
+       if (!spatial_tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+          spatial_values = coord.toReference(spatial_values);
+       }
+
+       boolean spatial_all_select = true;
+
+       if (isSpherical) {
+            for (int i=0; i<nn; i++) {
+               if (Float.isNaN(spatial_values[0][i]) || Float.isNaN(spatial_values[1][i]) || Float.isNaN(spatial_values[2][i])) {
+                      spatial_all_select = false;
+                      break;
+               }
+            }
+       } else {
+            if (Float.isNaN(value2)) {
+                 spatial_all_select = false;
+            } else {
+                 for (int i=0; i<nn; i++) {
+                      if (Float.isNaN(spatial_values[tuple_index[0]][i]) || Float.isNaN(spatial_values[tuple_index[1]][i])) { //02JUN2012:Use tuple_index than 0,1
+                          spatial_all_select = false;
+                          break;
+                      }
+                 }
+           }
+       }
+
+    /*   Assumes texels are squeezed into texture coordinate space (0.0 to 1.0)
+     *   so that the leftmost edge of the first texel is 0.0 and the rightmost
+     *   edge of the last texel is 1.0. The same is true for the height dimension
+     *   if yUp=true which is the case here (byReference).
+     */
+    float width = 1.0f / ((float)texture_width);         // Texel width
+    float height = 1.0f / ((float)texture_height);       // Texel height
+    float half_width = 0.5f / ((float) texture_width);   // half texel width
+    float half_height = 0.5f / ((float) texture_height); // half texel height
+                                                                                                                   
+    VisADTriangleStripArray tarray = new VisADTriangleStripArray();
+    tarray.stripVertexCounts = new int[nheight - 1];
+    java.util.Arrays.fill(tarray.stripVertexCounts, 2 * nwidth);
+
+    int len = (nheight - 1) * (2 * nwidth);
+    tarray.vertexCount = len;
+    tarray.coordinates = new float[3 * len];
+    tarray.texCoords = new float[2 * len];
+                                                                                                                   
+    int m = 0;
+    k = 0;
+    int kt = 0;
+
+    /*   Map the data point spatial coordinates to the center of the texels by
+     *   by starting at center of the first texel (texel width) and accumulating
+     *   a full texel width for each data point (the data location is the center
+     *   of the display pixel).  The equations are for yUp=true only (byReference).
+     *   Note: the form of these equations implicitly deal with the case NPOT=false
+     *   wherein the texture_width will be greater than or equal to the data_width.
+     */
+    float y_coord = 0f;
+    float y_coord2 = 0f;
+    float x_coord = 0f;
+
+    for (int j=0; j<nheight-1; j++) {
+	if (0==j){
+		y_coord = half_height + ((float)js[j])*height;
+	} else {
+		y_coord = y_coord2;
+	}
+	y_coord2 = half_height + ((float)js[j+1])*height;
+        for (int i=0; i<nwidth; i++) {
+	
+		tarray.coordinates[k++] = spatial_values[0][m];
+		tarray.coordinates[k++] = spatial_values[1][m];
+		tarray.coordinates[k++] = isSpherical? spatial_values[2][m] :value2; //02JUN2012: Set coords from spatial values if spherical coordsys 
+		tarray.coordinates[k++] = spatial_values[0][m+nwidth];
+		tarray.coordinates[k++] = spatial_values[1][m+nwidth];
+		tarray.coordinates[k++] = isSpherical? spatial_values[2][m+nwidth] : value2; //02JUN2012: Set coords from spatial values if spherical coordsys
+
+		x_coord = half_width + ((float)is[i])*width;
+        	tarray.texCoords[kt++] = x_coord;
+        	tarray.texCoords[kt++] = y_coord;
+        	tarray.texCoords[kt++] = x_coord;
+        	tarray.texCoords[kt++] = y_coord2;
+
+                m += 1;
+       }
+    }
+
+    is = null;
+    js = null;
+    spatial_values[0] = null;
+    spatial_values[1] = null;
+    spatial_values[2] = null;
+    spatial_values = null;
+    spline_domain[0] = null;
+    spline_domain[1] = null;
+    spline_domain = null;
+    // do surgery to remove any missing spatial coordinates in texture
+    if (!spatial_all_select) {
+      tarray = (VisADTriangleStripArray) tarray.removeMissing();
+    }
+
+    // do surgery along any longitude split (e.g., date line) in texture
+    if (adaptedShadowType.getAdjustProjectionSeam()) {
+      tarray = (VisADTriangleStripArray) tarray.adjustLongitude(renderer);
+      tarray = (VisADTriangleStripArray) tarray.adjustSeam(renderer);
+    }
+    
+    /*REUSE GEOM/COLORBYTES:I have replaced reuse with reuseImages.
+      	And here in the else logic I have added a few more lines. 
+        The else part of this never got executed because reuse was always false.
+        Now else part gets executed when reuseImages is true or either regen_geom or regen_colbytes is true.
+        It just applies geometry to the already available texture.
+        When both regen_geom or regen_colbytes are true then if part gets executed. 
+    */                                                                                                               
+    // add texture as sub-node of group in scene graph
+    	if (!reuseImages || (regen_colbytes && regen_geom)) { //REUSE GEOM/COLORBYTES: Earlier reuse variable was used. Replaced it with reuseImages and regeom_colbytes and regen_geom
+       		BufferedImage image = tile.getImage(0);
+       		textureToGroup(group, tarray, image, mode, constant_alpha, constant_color, texture_width, texture_height, true, true, tile);
+    	} else { //REUSE GEOM/COLORBYTES: Reuse the colorbytes and just apply the geometry
+		int num_children = ((BranchGroup) group).numChildren();
+		if (num_children > 0) {
+        		BranchGroup branch1 = (BranchGroup) ((BranchGroup) group).getChild(0); //This is the branch group created by textureToGroup Function
+                	Shape3D shape = (Shape3D) branch1.getChild(0);
+                	shape.setGeometry(((DisplayImplJ3D) display).makeGeometry(tarray));
+			
+        	} 
+    	}
+
+   }
+	tuple_index = null;
+  }
+
+  public void buildLinearTexture(Object group, Set domain_set, Unit[] dataUnits, Unit[] domain_units,
+                                 float[] default_values, ShadowRealType[] DomainComponents,
+                                 int valueArrayLength, int[] inherited_values, int[] valueToScalar,
+                                 GraphicsModeControl mode, float constant_alpha,
+                                 float[] value_array, float[] constant_color, DisplayImpl display,
+                                 VisADImageTile tile)
+         throws VisADException, DisplayException {
+
+    float[] coordinates = null;
+    float[] texCoords = null;
+    float[] normals = null;
+    int data_width = 0;
+    int data_height = 0;
+    int texture_width = 1;
+    int texture_height = 1;
+
+    Linear1DSet X = null;
+    Linear1DSet Y = null;
+    if (domain_set instanceof Linear2DSet) {
+      X = ((Linear2DSet) domain_set).getX();
+      Y = ((Linear2DSet) domain_set).getY();
+    }
+    else {
+      X = ((LinearNDSet) domain_set).getLinear1DComponent(0);
+      Y = ((LinearNDSet) domain_set).getLinear1DComponent(1);
+    }
+    float[][] limits = new float[2][2];
+    limits[0][0] = (float) X.getFirst();
+    limits[0][1] = (float) X.getLast();
+    limits[1][0] = (float) Y.getFirst();
+    limits[1][1] = (float) Y.getLast();
+                                                                                                                       
+    // get domain_set sizes
+    data_width = X.getLength();
+    data_height = Y.getLength();
+    // texture sizes may need to be powers-of-two on older graphics cards
+    texture_width = textureWidth(data_width);
+    texture_height = textureHeight(data_height);
+
+    // convert values to default units (used in display)
+    limits = Unit.convertTuple(limits, dataUnits, domain_units);
+
+    int[] tuple_index = new int[3];
+    if (DomainComponents.length != 2) {
+      throw new DisplayException("texture domain dimension != 2:" +
+                                 "ShadowFunctionOrSetType.doTransform");
+    }
+
+    // find the spatial ScalarMaps
+    for (int i=0; i<DomainComponents.length; i++) {
+      Enumeration maps = DomainComponents[i].getSelectedMapVector().elements();
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      // scale values
+      limits[i] = map.scaleValues(limits[i]);
+      DisplayRealType real = map.getDisplayScalar();
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple == null ||
+          !tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+        throw new DisplayException("texture with bad tuple: " +
+                                   "ShadowFunctionOrSetType.doTransform");
+      }
+      // get spatial index
+      tuple_index[i] = real.getTupleIndex();
+      if (maps.hasMoreElements()) {
+        throw new DisplayException("texture with multiple spatial: " +
+                                   "ShadowFunctionOrSetType.doTransform");
+      }
+    } // end for (int i=0; i<DomainComponents.length; i++)
+
+
+    // get spatial index not mapped from domain_set
+    tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+    DisplayRealType real = (DisplayRealType)
+      Display.DisplaySpatialCartesianTuple.getComponent(tuple_index[2]);
+    int value2_index = display.getDisplayScalarIndex(real);
+    float value2 = default_values[value2_index];
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0 &&
+          real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+        // value for unmapped spatial dimension
+        value2 = value_array[i];
+        break;
+      }
+    }
+
+    // create VisADQuadArray that texture is mapped onto
+    coordinates = new float[12];
+    // corner 0
+    coordinates[tuple_index[0]] = limits[0][0];
+    coordinates[tuple_index[1]] = limits[1][0];
+    coordinates[tuple_index[2]] = value2;
+    // corner 1
+    coordinates[3 + tuple_index[0]] = limits[0][0];
+    coordinates[3 + tuple_index[1]] = limits[1][1];
+    coordinates[3 + tuple_index[2]] = value2;
+    // corner 2
+    coordinates[6 + tuple_index[0]] = limits[0][1];
+    coordinates[6 + tuple_index[1]] = limits[1][1];
+    coordinates[6 + tuple_index[2]] = value2;
+    // corner 3
+    coordinates[9 + tuple_index[0]] = limits[0][1];
+    coordinates[9 + tuple_index[1]] = limits[1][0];
+    coordinates[9 + tuple_index[2]] = value2;
+                                                                                                                       
+    // move image back in Java3D 2-D mode
+    adjustZ(coordinates);
+                                                                                                                       
+
+    /*   Assumes texels are squeezed into texture coordinate space (0.0 to 1.0)
+     *   so that the leftmost edge of the first texel is 0.0 and the rightmost
+     *   edge of the last texel is 1.0. The same is true for the height dimension
+     *   if yUp=true which is the case here (byReference).
+     */
+
+    float width = 1.0f / ((float)texture_width);         // Texel width
+    float height = 1.0f / ((float)texture_height);       // Texel height
+    float half_width = 0.5f / ((float) texture_width);   // half texel width
+    float half_height = 0.5f / ((float) texture_height); // half texel height
+
+    texCoords = new float[8];
+
+    /*   Map the data point spatial coordinates to the center of the texels by
+     *   by starting at center of the first texel (texel width) and accumulating
+     *   a full texel width for each data point (the data location is the center
+     *   of the display pixel).  The equations are for yUp=true only (byReference).
+     *   Note: the form of these equations implicitly deal with the case NPOT=false
+     *   wherein the texture_width will be greater than or equal to the data_width.
+     */
+
+    // corner 0
+    texCoords[0] = half_width;
+    texCoords[1] = half_height;
+    // corner 1
+    texCoords[2] = half_width;
+    texCoords[3] = half_height + (data_height-1)*height;
+    // corner 2
+    texCoords[4] = half_width + (data_width-1)*width;
+    texCoords[5] = half_height + (data_height-1)*height;
+    // corner 3
+    texCoords[6] = half_width + (data_width-1)*width;
+    texCoords[7] = half_height;
+
+    VisADQuadArray qarray = new VisADQuadArray();
+    qarray.vertexCount = 4;
+    qarray.coordinates = coordinates;
+    qarray.texCoords = texCoords;
+ 
+    /*REUSE GEOM/COLORBYTES:I have replaced reuse with reuseImages.
+      And here in the else logic I have added a few more lines. 
+	The else part of this never got executed because reuse was always false.
+	Now else part gets executed when reuse is true and either regen_geom or regen_colbytes is true.
+        It just applies geometry to the already available texture.
+	When both are true then if part gets executed. 
+    */                                                                                                                      
+    // add texture as sub-node of group in scene graph
+    if (!reuseImages|| (regen_colbytes && regen_geom)) { //REUSE GEOM/COLORBYTES: Earlier reuse variable was used. Replaced it with reuseImages and regeom_colbytes and regen_geom
+      BufferedImage image = tile.getImage(0);
+      textureToGroup(group, qarray, image, mode, constant_alpha,
+                     constant_color, texture_width, texture_height, true, true, tile);
+    }
+    else {
+	int num_children = ((BranchGroup) group).numChildren();
+        if (num_children > 0) { //REUSE GEOM/COLORBYTES: Reuse the colorbytes and apply geometry
+                BranchGroup branch1 = (BranchGroup) ((BranchGroup) group).getChild(0); //This the branch group created by textureToGroup Function
+                Shape3D shape = (Shape3D) branch1.getChild(0);
+                shape.setGeometry(((DisplayImplJ3D) display).makeGeometry(qarray));
+        }
+    }
+
+  }
+
+  //public CachedBufferedByteImage createImageByRef(final int texture_width, final int texture_height, final int imageType) {
+  public BufferedImage createImageByRef(final int texture_width, final int texture_height, final int imageType) {
+      return new BufferedImage(texture_width, texture_height, imageType);
+  }
+
+  public static float[][] getBounds(Set domain_set, float data_width, float data_height,
+           float scaleX, float offsetX, float scaleY, float offsetY)
+      throws VisADException
+  {
+    float[][] xyCoords = new float[2][4];
+
+    float[][] coords0 = ((GriddedSet)domain_set).gridToValue(new float[][] {{0f},{0f}});
+    float[][] coords1 = ((GriddedSet)domain_set).gridToValue(new float[][] {{0f},{(float)(data_height-1)}});
+    float[][] coords2 = ((GriddedSet)domain_set).gridToValue(new float[][] {{(data_width-1f)},{(data_height-1f)}});
+    float[][] coords3 = ((GriddedSet)domain_set).gridToValue(new float[][] {{(data_width-1f)},{0f}});
+
+    float x0 = coords0[0][0];
+    float y0 = coords0[1][0];
+    float x1 = coords1[0][0];
+    float y1 = coords1[1][0];
+    float x2 = coords2[0][0];
+    float y2 = coords2[1][0];
+    float x3 = coords3[0][0];
+    float y3 = coords3[1][0];
+
+    xyCoords[0][0] = (x0 - offsetX)/scaleX;
+    xyCoords[1][0] = (y0 - offsetY)/scaleY;
+
+    xyCoords[0][1] = (x1 - offsetX)/scaleX;
+    xyCoords[1][1] = (y1 - offsetY)/scaleY;
+
+    xyCoords[0][2] = (x2 - offsetX)/scaleX;
+    xyCoords[1][2] = (y2 - offsetY)/scaleY;
+
+    xyCoords[0][3] = (x3 - offsetX)/scaleX;
+    xyCoords[1][3] = (y3 - offsetY)/scaleY;
+
+    return xyCoords;
+  }
+
+}
+
+
+class SwitchNotify extends Switch {
+  VisADImageNode imgNode;
+  int numChildren;
+  Switch swit;
+
+  SwitchNotify(VisADImageNode imgNode, int numChildren) {
+    super();
+    this.imgNode = imgNode;
+    this.numChildren = numChildren;
+    this.swit = imgNode.getSwitch();
+  }
+
+  public int numChildren() {
+    return numChildren;
+  }
+
+  public void setWhichChild(int index) {
+    if (index == Switch.CHILD_NONE) {
+      swit.setWhichChild(Switch.CHILD_NONE);
+    }
+    else if (index >= 0) {
+      if ( swit.getWhichChild() == Switch.CHILD_NONE) {
+        swit.setWhichChild(0);
+      }
+      imgNode.setCurrent(index);
+    }
+  }
+}
+
+
+class Mosaic {
+
+  Tile[][] tiles;
+  ArrayList<Tile>  tileList = new ArrayList<Tile>();
+
+  int n_x_sub = 1;
+  int n_y_sub = 1;
+
+  Mosaic(int lenY, int limitY, int lenX, int limitX) {
+
+    int y_sub_len = limitY;
+    n_y_sub = lenY/y_sub_len;
+    if (n_y_sub == 0) n_y_sub++;
+    if ((lenY - n_y_sub*y_sub_len) > 4) n_y_sub += 1;
+
+    int[][] y_start_stop = new int[n_y_sub][2];
+    for (int k = 0; k < n_y_sub-1; k++) {
+       y_start_stop[k][0] = k*y_sub_len - k;
+       y_start_stop[k][1] = ((k+1)*y_sub_len - 1) - k;
+       // check that we don't exceed limit
+       if ( ((y_start_stop[k][1]-y_start_stop[k][0])+1) > limitY) {
+         y_start_stop[k][1] -= 1; //too big, take away gap fill
+       }
+    }
+    int k = n_y_sub-1;
+    y_start_stop[k][0] = k*y_sub_len - k;
+    y_start_stop[k][1] = lenY - 1 - k;
+
+    int x_sub_len = limitX;
+    n_x_sub = lenX/x_sub_len;
+    if (n_x_sub == 0) n_x_sub++;
+    if ((lenX - n_x_sub*x_sub_len) > 4) n_x_sub += 1;
+
+    int[][] x_start_stop = new int[n_x_sub][2];
+    for (k = 0; k < n_x_sub-1; k++) {
+      x_start_stop[k][0] = k*x_sub_len - k;
+      x_start_stop[k][1] = ((k+1)*x_sub_len - 1) - k;
+      // check that we don't exceed limit
+      if ( ((x_start_stop[k][1]-x_start_stop[k][0])+1) > limitX) {
+        x_start_stop[k][1] -= 1; //too big, take away gap fill
+      }
+    }
+    k = n_x_sub-1; 
+    x_start_stop[k][0] = k*x_sub_len - k;
+    x_start_stop[k][1] = lenX - 1 - k;
+
+    tiles = new Tile[n_y_sub][n_x_sub];
+
+    for (int j=0; j<n_y_sub; j++) {
+      for (int i=0; i<n_x_sub; i++) {
+         tiles[j][i] =
+           new Tile(y_start_stop[j][0], y_start_stop[j][1], x_start_stop[i][0], x_start_stop[i][1]);
+         tileList.add(tiles[j][i]);
+      }
+    }
+  }
+
+  Iterator iterator() {
+    return tileList.iterator();
+  }
+}
+
+class Tile {
+   int y_start;
+   int x_start;
+   int y_stop;
+   int x_stop;
+ 
+   int height;
+   int width;
+
+   Tile(int y_start, int y_stop, int x_start, int x_stop) {
+     this.y_start = y_start;
+     this.y_stop = y_stop;
+     this.x_start = x_start;
+     this.x_stop = x_stop;
+     
+     height = y_stop - y_start + 1;
+     width = x_stop - x_start + 1;
+   }
+}
diff --git a/visad/bom/ShadowImageFunctionTypeJ3D.java b/visad/bom/ShadowImageFunctionTypeJ3D.java
new file mode 100644
index 0000000..f2ab2bf
--- /dev/null
+++ b/visad/bom/ShadowImageFunctionTypeJ3D.java
@@ -0,0 +1,1703 @@
+//
+// ShadowImageFunctionTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+import visad.data.mcidas.BaseMapAdapter;
+import visad.data.mcidas.AreaAdapter;
+import visad.data.gif.GIFForm;
+
+import javax.media.j3d.*;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Vector;
+import java.rmi.*;
+
+import java.net.URL;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.awt.image.BufferedImage;
+
+/**
+   The ShadowImageFunctionTypeJ3D class shadows the FunctionType class for
+   ImageRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowImageFunctionTypeJ3D extends ShadowFunctionTypeJ3D {
+
+  private static final int MISSING1 = Byte.MIN_VALUE;      // least byte
+
+  public ShadowImageFunctionTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  // transform data into a depiction under group
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+
+    DataDisplayLink link = renderer.getLink();
+
+    //System.out.println("start doTransform " + (System.currentTimeMillis() - link.start_time));
+
+    // return if data is missing or no ScalarMaps
+    if (data.isMissing()) {
+      ((ImageRendererJ3D) renderer).markMissingVisADBranch();
+      return false;
+    }
+    if (getLevelOfDifficulty() == NOTHING_MAPPED) return false;
+
+    ShadowFunctionOrSetType adaptedShadowType =
+      (ShadowFunctionOrSetType) getAdaptedShadowType();
+    DisplayImpl display = getDisplay();
+    GraphicsModeControl mode = (GraphicsModeControl)
+      display.getGraphicsModeControl().clone();
+
+    // get 'shape' flags
+    boolean anyContour = adaptedShadowType.getAnyContour();
+    boolean anyFlow = adaptedShadowType.getAnyFlow();
+    boolean anyShape = adaptedShadowType.getAnyShape();
+    boolean anyText = adaptedShadowType.getAnyText();
+
+    if (anyContour || anyFlow || anyShape || anyText) {
+      throw new BadMappingException("no contour, flow, shape or text allowed");
+    }
+
+    // get some precomputed values useful for transform
+    // length of ValueArray
+    int valueArrayLength = display.getValueArrayLength();
+    // mapping from ValueArray to DisplayScalar
+    int[] valueToScalar = display.getValueToScalar();
+    // mapping from ValueArray to MapVector
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    // array to hold values for various mappings
+    float[][] display_values = new float[valueArrayLength][];
+
+    int[] inherited_values = adaptedShadowType.getInheritedValues();
+
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0) {
+        display_values[i] = new float[1];
+        display_values[i][0] = value_array[i];
+      }
+    }
+
+    // analyze data's domain (its a Field)
+    Set domain_set = ((Field) data).getDomainSet();
+    Unit[] dataUnits = ((Function) data).getDomainUnits();
+    CoordinateSystem dataCoordinateSystem =
+      ((Function) data).getDomainCoordinateSystem();
+
+    float[][] domain_values = null;
+    double[][] domain_doubles = null;
+    ShadowRealTupleType Domain = adaptedShadowType.getDomain();
+    Unit[] domain_units = ((RealTupleType) Domain.getType()).getDefaultUnits();
+    int domain_length;
+    int domain_dimension;
+    try {
+      domain_length = domain_set.getLength();
+      domain_dimension = domain_set.getDimension();
+    }
+    catch (SetException e) {
+      return false;
+    }
+
+    //    System.err.println ("domain_length:" + domain_length);
+
+    // ShadowRealTypes of Domain
+    ShadowRealType[] DomainComponents = adaptedShadowType.getDomainComponents();
+
+    // check whether this ShadowImageFunctionTypeJ3D is for an
+    // image (terminal - no recursive calls to doTransform) or an
+    // image sequence (non-terminal - recursive calls to doTransform
+    // for each image in the sequence
+    if (adaptedShadowType.getIsTerminal()) {
+      // terminal, so this is an image
+
+// System.out.println("start colors " + (System.currentTimeMillis() - link.start_time));
+
+      float constant_alpha = Float.NaN;
+      float[] constant_color = null;
+
+      // check that range is single RealType mapped to RGB only
+      ShadowRealType[] RangeComponents = adaptedShadowType.getRangeComponents();
+      int rangesize = RangeComponents.length;
+      if (rangesize != 1 && rangesize != 3) {
+        throw new BadMappingException("image values must single or triple");
+      }
+      ScalarMap cmap  = null;
+      ScalarMap[] cmaps = null;
+      int[] permute = {-1, -1, -1};
+      int color_length = 3;
+      if (rangesize == 1) {
+        Vector mvector = RangeComponents[0].getSelectedMapVector();
+        if (mvector.size() != 1) {
+          throw new BadMappingException("image values must be mapped to RGB only");
+        }
+        cmap = (ScalarMap) mvector.elementAt(0);
+        if (Display.RGB.equals(cmap.getDisplayScalar())) {
+          color_length = 3;
+        }
+        else if (Display.RGBA.equals(cmap.getDisplayScalar())) {
+          color_length = 4;
+        }
+        else {
+          throw new BadMappingException("image values must be mapped to RGB or RGBA");
+        }
+      }
+      else {
+        cmaps = new ScalarMap[3];
+        for (int i=0; i<3; i++) {
+          Vector mvector = RangeComponents[i].getSelectedMapVector();
+          if (mvector.size() != 1) {
+            throw new BadMappingException("image values must be mapped to color only");
+          }
+          cmaps[i] = (ScalarMap) mvector.elementAt(0);
+          if (Display.Red.equals(cmaps[i].getDisplayScalar())) {
+            permute[0] = i;
+          }
+          else if (Display.Green.equals(cmaps[i].getDisplayScalar())) {
+            permute[1] = i;
+          }
+          else if (Display.Blue.equals(cmaps[i].getDisplayScalar())) {
+            permute[2] = i;
+          } else if (Display.RGB.equals(cmaps[i].getDisplayScalar())) {	//Inserted by Ghansham for Mapping all the three scalarMaps to Display.RGB (starts here) 
+		permute[i] = i;
+          }else {		////Inserted by Ghansham for Mapping all the three scalarMaps to Display.RGB(ends here)
+            throw new BadMappingException("image values must be mapped to Red, " +
+                                          "Green or Blue only");
+          }
+        }
+        if (permute[0] < 0 || permute[1] < 0 || permute[2] < 0) {
+          throw new BadMappingException("image values must be mapped to Red, Green and Blue");
+        } 
+
+	//Inserted by Ghansham for Checking that all should be mapped to Display.RGB or not even a single one should be mapped to Display.RGB(starts here)
+	//This is to check if there is a single Display.RGB ScalarMap
+	int indx = -1;
+	for (int i = 0; i < 3; i++) {
+		if (cmaps[i].getDisplayScalar().equals(Display.RGB)) {
+			indx = i;
+			break;
+		} 
+	}
+
+	if (indx != -1){	//if there is a even a single Display.RGB ScalarMap, others must also Display.RGB only
+		for (int i = 0; i < 3; i++) { 
+			if (i !=indx && !(cmaps[i].getDisplayScalar().equals(Display.RGB))) {
+          			throw new BadMappingException("image values must be mapped to (Red, Green, Blue) or (RGB,RGB,RGB) only");
+			}
+		}
+	}
+	//Inserted by Ghansham for Checking that all should be mapped to Display.RGB or not even a single one should be mapped to Display.RGB(Ends here)
+      }
+
+
+      constant_alpha = default_values[display.getDisplayScalarIndex(Display.Alpha)];
+
+      byte[][] color_bytes;
+      if (cmap != null) {
+        // build texture colors in color_bytes array
+        BaseColorControl control = (BaseColorControl) cmap.getControl();
+        float[][] table = control.getTable();
+        byte[][] bytes = null;
+        Set rset = null;
+        boolean is_default_unit = false;
+        if (data instanceof FlatField) {
+          // for fast byte color lookup, need:
+          // 1. range data values are packed in bytes
+          bytes = ((FlatField) data).grabBytes();
+          // 2. range set is Linear1DSet
+          Set[] rsets = ((FlatField) data). getRangeSets();
+          if (rsets != null) rset = rsets[0];
+          // 3. data Unit equals default Unit
+          RealType rtype = (RealType) RangeComponents[0].getType();
+          Unit def_unit = rtype.getDefaultUnit();
+          if (def_unit == null) {
+            is_default_unit = true;
+          }
+          else {
+            Unit[][] data_units = ((FlatField) data).getRangeUnits();
+            Unit data_unit = (data_units == null) ? null : data_units[0][0];
+            is_default_unit = def_unit.equals(data_unit);
+          }
+        }
+        if (table != null) {
+          // combine color table RGB components into ints
+          byte[][] itable = new byte[table[0].length][4];
+          // int r, g, b, a = 255;
+          int r, g, b;
+          int c = (int) (255.0 * (1.0f - constant_alpha));
+          int a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+          for (int j=0; j<table[0].length; j++) {
+            c = (int) (255.0 * table[0][j]);
+            r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            c = (int) (255.0 * table[1][j]);
+            g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            c = (int) (255.0 * table[2][j]);
+            b = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            if (color_length == 4) {
+              c = (int) (255.0 * table[3][j]);
+              a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            }
+            itable[j][0] = (byte) r;
+            itable[j][1] = (byte) g;
+            itable[j][2] = (byte) b;
+            itable[j][3] = (byte) a;
+          }
+          int tblEnd = table[0].length - 1;
+          // get scale for color table
+          int table_scale = table[0].length;
+  
+          if (data instanceof ImageFlatField &&
+              bytes != null && is_default_unit) {
+            if (ImageFlatField.DEBUG) {
+              System.err.println("ShadowImageFunctionTypeJ3D.doTransform: " +
+                "cmap != null: looking up color values");
+            }
+            // avoid unpacking floats for ImageFlatFields
+            color_bytes = new byte[4][domain_length];
+            bytes[0] = cmap.scaleValues(bytes[0], table_scale);
+            // fast lookup from byte values to color bytes
+            byte[] bytes0 = bytes[0];
+            for (int i=0; i<domain_length; i++) {
+              int j = bytes0[i] & 0xff; // unsigned
+              // clip to table
+              int ndx = j < 0 ? 0 : (j > tblEnd ? tblEnd : j);
+              color_bytes[0][i] = itable[ndx][0];
+              color_bytes[1][i] = itable[ndx][1];
+              color_bytes[2][i] = itable[ndx][2];
+              color_bytes[3][i] = itable[ndx][3];
+            }
+          }
+          else if (bytes != null && bytes[0] != null && is_default_unit &&
+              rset != null && rset instanceof Linear1DSet) {
+            // fast since FlatField with bytes, data Unit equals default
+            // Unit and range set is Linear1DSet
+            // get "scale and offset" for Linear1DSet
+            double first = ((Linear1DSet) rset).getFirst();
+            double step = ((Linear1DSet) rset).getStep();
+            // get scale and offset for ScalarMap
+            double[] so = new double[2];
+            double[] da = new double[2];
+            double[] di = new double[2];
+            cmap.getScale(so, da, di);
+            double scale = so[0];
+            double offset = so[1];
+            // combine scales and offsets for Set, ScalarMap and color table
+            float mult = (float) (table_scale * scale * step);
+            float add = (float) (table_scale * (offset + scale * first));
+  
+            // build table for fast color lookup
+            byte[][] fast_table = new byte[256][];
+            for (int j=0; j<256; j++) {
+              int index = j - 1;
+              if (index >= 0) { // not missing
+                int k = (int) (add + mult * index);
+                // clip to table
+                int ndx = k < 0 ? 0 : (k > tblEnd ? tblEnd : k);
+                fast_table[j] = itable[ndx];
+              }
+            }
+  
+            // now do fast lookup from byte values to color bytes
+            color_bytes = new byte[4][domain_length];
+            byte[] bytes0 = bytes[0];
+            for (int i=0; i<domain_length; i++) {
+              int ndx = ((int) bytes0[i]) - MISSING1;
+              color_bytes[0][i] = fast_table[ndx][0];
+              color_bytes[1][i] = fast_table[ndx][1];
+              color_bytes[2][i] = fast_table[ndx][2];
+              color_bytes[3][i] = fast_table[ndx][3];
+            }
+            bytes = null; // take out the garbage
+          }
+          else {
+            // medium speed way to build texture colors
+            bytes = null; // take out the garbage
+            float[][] values = ((Field) data).getFloats(false);
+            values[0] = cmap.scaleValues(values[0]);
+  
+            // now do fast lookup from byte values to color bytes
+            color_bytes = new byte[4][domain_length];
+            float[] values0 = values[0];
+            for (int i=0; i<domain_length; i++) {
+              if (values0[i] == values0[i]) { // not missing
+                int j = (int) (table_scale * values0[i]);
+                // clip to table
+                int ndx = j < 0 ? 0 : (j > tblEnd ? tblEnd : j);
+                color_bytes[0][i] = itable[ndx][0];
+                color_bytes[1][i] = itable[ndx][1];
+                color_bytes[2][i] = itable[ndx][2];
+                color_bytes[3][i] = itable[ndx][3];
+              }
+            }
+            values = null; // take out the garbage
+          }
+        }
+        else { // if (table == null)
+          // slower, more general way to build texture colors
+          bytes = null; // take out the garbage
+          float[][] values = ((Field) data).getFloats(false);
+          values[0] = cmap.scaleValues(values[0]);
+          // call lookupValues which will use function since table == null
+          float[][] color_values = control.lookupValues(values[0]);
+          // combine color RGB components into bytes
+          // int r, g, b, a = 255;
+          int r, g, b;
+          int c = (int) (255.0 * (1.0f - constant_alpha));
+          int a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+          color_bytes = new byte[4][domain_length];
+          for (int i=0; i<domain_length; i++) {
+            if (values[0][i] == values[0][i]) { // not missing
+              c = (int) (255.0 * color_values[0][i]);
+              r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+              c = (int) (255.0 * color_values[1][i]);
+              g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+              c = (int) (255.0 * color_values[2][i]);
+              b = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+              if (color_length == 4) {
+                c = (int) (255.0 * color_values[3][i]);
+                a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+              }
+              color_bytes[0][i] = (byte) r;
+              color_bytes[1][i] = (byte) g;
+              color_bytes[2][i] = (byte) b;
+              color_bytes[3][i] = (byte) a;
+            }
+          }
+          // take out the garbage
+          values = null;
+          color_values = null;
+        }
+      }
+      else if (cmaps != null) {
+        byte[][] bytes = null;
+        if (data instanceof ImageFlatField) {
+          bytes = ((ImageFlatField) data).grabBytes();
+        }
+        if (bytes != null) {
+          // grab bytes directly from ImageFlatField
+          if (ImageFlatField.DEBUG) {
+            System.err.println("ShadowImageFunctionTypeJ3D.doTransform: " +
+              "cmaps != null: grab bytes directly");
+          }
+          color_bytes = new byte[4][];
+	  if  (cmaps[0].getDisplayScalar() == Display.RGB && cmaps[1].getDisplayScalar() == Display.RGB && cmaps[2].getDisplayScalar() == Display.RGB) { //Inserted by Ghansham (starts here)
+		int map_indx = 0;			
+		int r, g, b, c;
+		for (map_indx = 0; map_indx < cmaps.length; map_indx++) {
+			BaseColorControl basecolorcontrol = (BaseColorControl)cmaps[map_indx].getControl();
+			float color_table[][] = basecolorcontrol.getTable();
+			int itable[][] = new int[color_table[0].length][3];
+			int table_indx;
+			for (table_indx = 0; table_indx < itable.length; table_indx++) {
+				c = (int) (255.0 * color_table[0][table_indx]);
+            			r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            			c = (int) (255.0 * color_table[1][table_indx]);
+            			g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            			c = (int) (255.0 * color_table[2][table_indx]);
+            			b = (c < 0) ? 0 : ((c > 255) ? 255 : c);	
+				itable[table_indx][0] = (byte) r;
+            			itable[table_indx][1] = (byte) g;
+            			itable[table_indx][2] = (byte) b;
+			}
+			int table_length = color_table[0].length;
+			int color_indx = permute[map_indx];
+			byte bytes1[] = cmaps[color_indx].scaleValues(bytes[color_indx], table_length); //Memory Leak. overwritting bytes. it can be avoided if required. ScaleValues function does not have a signature of scaleValues(byte [], factor, boolean). its required, if need to save memory.
+			int domainLength =  bytes1.length;
+			int tblEnd = table_length - 1;
+			color_bytes[map_indx] = new byte[domain_length];
+			int data_indx;
+			
+			for (data_indx = 0; data_indx < domainLength; data_indx++) {
+				int j = bytes1[data_indx] & 0xff; // unsigned
+              			// clip to table
+              			int ndx = j < 0 ? 0 : (j > tblEnd ? tblEnd : j);
+                                color_bytes[map_indx][data_indx] = (byte)itable[ndx][map_indx];
+			}
+			itable = null; //Taking out the garbage
+			bytes1 = null; //Taking out the garbage
+		}
+          }else { //Inserted by Ghansham (Ends here)
+		color_bytes[0] = cmaps[permute[0]].scaleValues(bytes[permute[0]], 255);
+          	color_bytes[1] = cmaps[permute[1]].scaleValues(bytes[permute[1]], 255);
+          	color_bytes[2] = cmaps[permute[2]].scaleValues(bytes[permute[2]], 255);
+	  }
+          int c = (int) (255.0 * (1.0f - constant_alpha));
+          color_bytes[3] = new byte[domain_length];
+          Arrays.fill(color_bytes[3], (byte) c);
+        }
+        else {
+          float[][] values = ((Field) data).getFloats(false);
+          float[][] new_values = new float[3][];
+          new_values[0] = cmaps[permute[0]].scaleValues(values[permute[0]]);
+          new_values[1] = cmaps[permute[1]].scaleValues(values[permute[1]]);
+          new_values[2] = cmaps[permute[2]].scaleValues(values[permute[2]]);
+          values = new_values;
+          int r, g, b;
+          int c = (int) (255.0 * (1.0f - constant_alpha));
+          int a = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+          color_bytes = new byte[4][domain_length];
+	 if  (cmaps[0].getDisplayScalar() == Display.RGB && cmaps[1].getDisplayScalar() == Display.RGB && cmaps[2].getDisplayScalar() == Display.RGB) { //Inserted by Ghansham (starts here)
+		int map_indx;
+		for (map_indx = 0; map_indx < cmaps.length; map_indx++) {
+			BaseColorControl basecolorcontrol = (BaseColorControl)cmaps[map_indx].getControl();
+                        float color_table[][] = basecolorcontrol.getTable();
+                        int itable[][] = new int[color_table[0].length][3];
+                        int table_indx;
+			for(table_indx = 0; table_indx < itable.length; table_indx++) {
+				c = (int) (255.0 * color_table[0][table_indx]);
+            			r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            			c = (int) (255.0 * color_table[1][table_indx]);
+            			g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+            			c = (int) (255.0 * color_table[2][table_indx]);
+            			b = (c < 0) ? 0 : ((c > 255) ? 255 : c);	
+				itable[table_indx][0] = (byte) r;
+            			itable[table_indx][1] = (byte) g;
+            			itable[table_indx][2] = (byte) b;
+                        }
+			int tableEnd = color_table[0].length - 1;
+                        for(int data_indx = 0; data_indx < domain_length; data_indx++) {
+                        	int indx = (int)((float)tableEnd * values[map_indx][data_indx]);
+                                // clip to table
+                                indx = indx < 0 ? 0 : (indx > tableEnd ? tableEnd : indx);
+                                color_bytes[map_indx][data_indx] = (byte)itable[indx][map_indx];
+                        }
+			itable = null;	//Take out the garbage
+		}
+		Arrays.fill(color_bytes[3], (byte)a);	
+	 } else {  //Inserted by Ghansham (ends here)
+          	for (int i=0; i<domain_length; i++) {
+	            	if (values[0][i] == values[0][i] && values[1][i] == values[1][i] && values[2][i] == values[2][i]) { // not missing
+		              	c = (int) (255.0 * values[0][i]);
+              			r = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+              			c = (int) (255.0 * values[1][i]);
+              			g = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+              			c = (int) (255.0 * values[2][i]);
+              			b = (c < 0) ? 0 : ((c > 255) ? 255 : c);
+              			color_bytes[0][i] = (byte) r;
+              			color_bytes[1][i] = (byte) g;
+              			color_bytes[2][i] = (byte) b;
+              			color_bytes[3][i] = (byte) a;
+            		}
+          	}
+	}
+          // take out the garbage
+          values = null;
+        }
+      }
+      else {
+        throw new BadMappingException("cmap == null and cmaps == null ??");
+      }
+
+// System.out.println("end colors " + (System.currentTimeMillis() - link.start_time));
+
+      // check domain and determine whether it is square or curved texture
+      if (!Domain.getAllSpatial() || Domain.getMultipleDisplayScalar()) {
+        throw new BadMappingException("domain must be only spatial");
+      }
+
+      boolean isTextureMap = adaptedShadowType.getIsTextureMap() &&
+                             (domain_set instanceof Linear2DSet ||
+                              (domain_set instanceof LinearNDSet &&
+                               domain_set.getDimension() == 2)) &&
+                             (domain_set.getManifoldDimension() == 2);
+
+      // DRM 2003-08-21
+      //int curved_size = display.getGraphicsModeControl().getCurvedSize();
+      int cMapCurveSize = (int)
+        default_values[display.getDisplayScalarIndex(Display.CurvedSize)];
+      int curved_size =  
+        (cMapCurveSize > 0)
+           ? cMapCurveSize
+           : display.getGraphicsModeControl().getCurvedSize();
+      boolean curvedTexture = adaptedShadowType.getCurvedTexture() &&
+                              !isTextureMap &&
+                              curved_size > 0 &&
+                              (domain_set instanceof Gridded2DSet ||
+                               (domain_set instanceof GriddedSet &&
+                                domain_set.getDimension() == 2)) &&
+                              (domain_set.getManifoldDimension() == 2);
+
+      float[] coordinates = null;
+      float[] texCoords = null;
+      float[] normals = null;
+      byte[] colors = null;
+      int data_width = 0;
+      int data_height = 0;
+      int texture_width = 1;
+      int texture_height = 1;
+      float[] coordinatesX = null;
+      float[] texCoordsX = null;
+      float[] normalsX = null;
+      byte[] colorsX = null;
+      float[] coordinatesY = null;
+      float[] texCoordsY = null;
+      float[] normalsY = null;
+      byte[] colorsY = null;
+
+      if (color_length == 4) constant_alpha = Float.NaN; // WLH 6 May 2003
+
+      if (isTextureMap) {
+//System.out.println("ShadowImageFunctionType, start texture map: " + (System.currentTimeMillis() - link.start_time));
+
+        int[] lens = ((GriddedSet)domain_set).getLengths();
+        int limit = link.getDisplay().getDisplayRenderer().getTextureWidthMax();
+
+        int y_sub_len = lens[1];
+        int n_y_sub   = 1;
+        while( y_sub_len >= limit ) {
+           y_sub_len /= 2;
+           n_y_sub *= 2;
+        }
+        int[][] y_start_stop = new int[n_y_sub][2];
+        for (int k = 0; k < n_y_sub-1; k++) {
+           y_start_stop[k][0] = k*y_sub_len;
+           y_start_stop[k][1] = (k+1)*y_sub_len - 1;
+        }
+        int k = n_y_sub-1;
+        y_start_stop[k][0] = k*y_sub_len;
+        y_start_stop[k][1] = lens[1] - 1;
+
+        int x_sub_len = lens[0];
+        int n_x_sub   = 1;
+        while( x_sub_len >= limit ) {
+          x_sub_len /= 2;
+          n_x_sub *= 2;
+        }
+        int[][] x_start_stop = new int[n_x_sub][2];
+        for (k = 0; k < n_x_sub-1; k++) {
+           x_start_stop[k][0] = k*x_sub_len;
+           x_start_stop[k][1] = (k+1)*x_sub_len - 1;
+        }
+        k = n_x_sub-1;
+        x_start_stop[k][0] = k*x_sub_len;
+        x_start_stop[k][1] = lens[0] - 1;
+
+
+        if (n_y_sub == 1 && n_x_sub == 1) {
+          buildLinearTexture(group, domain_set, dataUnits, domain_units, default_values, DomainComponents,
+                             valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha, 
+                             value_array, constant_color, color_bytes, display);
+        }
+        else {
+          BranchGroup branch = new BranchGroup();
+          branch.setCapability(BranchGroup.ALLOW_DETACH);
+          branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+          branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+
+          int start   = 0;
+          int i_total = 0;
+          for (int i=0; i<n_y_sub; i++) {
+            int leny = y_start_stop[i][1] - y_start_stop[i][0] + 1;
+            for (int j=0; j<n_x_sub; j++) {
+              int lenx = x_start_stop[j][1] - x_start_stop[j][0] + 1;
+              float[][] g00 = ((GriddedSet)domain_set).gridToValue(new float[][] {{x_start_stop[j][0]}, {y_start_stop[i][0]}});
+              float[][] g11 = ((GriddedSet)domain_set).gridToValue(new float[][] {{x_start_stop[j][1]}, {y_start_stop[i][1]}});
+              double x0 = g00[0][0];
+              double x1 = g11[0][0];
+              double y0 = g00[1][0];
+              double y1 = g11[1][0];
+              Set dset = new Linear2DSet(x0, x1, lenx, y0, y1, leny);
+              byte[][] color_bytesW = new byte[4][lenx*leny];
+              int cnt = 0;
+              for (k=0; k<leny; k++) {
+                start = x_start_stop[j][0] + i_total*lens[0] + k*lens[0];
+                for (int c=0; c<4; c++) {
+                  System.arraycopy(color_bytes[c], start,
+                    color_bytesW[c], cnt, lenx);
+                }
+                cnt += lenx;
+              }
+              BranchGroup branch1 = new BranchGroup();
+              branch1.setCapability(BranchGroup.ALLOW_DETACH);
+              branch1.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+              branch1.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+              branch1.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+              buildLinearTexture(branch1, dset, dataUnits, domain_units, default_values, DomainComponents,
+                                 valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha, 
+                                 value_array, constant_color, color_bytesW, display);
+              branch.addChild(branch1);
+            }
+            i_total += leny;
+          }
+          // group: top level
+          if (((Group) group).numChildren() > 0) {
+            ((Group) group).setChild(branch, 0);
+          }
+          else {
+            ((Group) group).addChild(branch);
+          }
+        }
+      } // end if (isTextureMap)
+      else if (curvedTexture) {
+//System.out.println("start curved texture " + (System.currentTimeMillis() - link.start_time));
+
+        int[] lens = ((GriddedSet)domain_set).getLengths();
+
+        int limit = link.getDisplay().getDisplayRenderer().getTextureWidthMax();
+	//System.err.println("Texture Limit:" + limit);
+
+        int y_sub_len = lens[1];
+        int n_y_sub   = 1;
+        while( y_sub_len >= limit ) {
+          y_sub_len /= 2;
+          n_y_sub *= 2;
+        }
+        int[][] y_start_stop = new int[n_y_sub][2];
+        for (int k = 0; k < n_y_sub-1; k++) {
+          y_start_stop[k][0] = k*y_sub_len;
+          y_start_stop[k][1] = (k+1)*y_sub_len - 1;
+        }
+        int k = n_y_sub-1;
+        y_start_stop[k][0] = k*y_sub_len;
+        y_start_stop[k][1] = lens[1] - 1;
+       
+
+        int x_sub_len = lens[0];
+        int n_x_sub   = 1;
+        while( x_sub_len >= limit ) {
+          x_sub_len /= 2;
+          n_x_sub *= 2;
+        }
+        int[][] x_start_stop = new int[n_x_sub][2];
+        for (k = 0; k < n_x_sub-1; k++) {
+          x_start_stop[k][0] = k*x_sub_len;
+          x_start_stop[k][1] = (k+1)*x_sub_len - 1;
+        }
+        k = n_x_sub-1;
+        x_start_stop[k][0] = k*x_sub_len;
+        x_start_stop[k][1] = lens[0] - 1;
+
+        //System.out.println("  y    : "+lens[1]+",   x    : "+lens[0]);
+        //System.out.println("n_y_sub: "+n_y_sub+", n_x_sub: "+n_x_sub);
+        if (n_y_sub == 1 && n_x_sub == 1) {
+          buildCurvedTexture(group, domain_set, dataUnits, domain_units, default_values, DomainComponents,
+                             valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha,
+                             value_array, constant_color, color_bytes, display, curved_size, Domain,
+                             dataCoordinateSystem, renderer, adaptedShadowType, new int[] {0,
+                              0}, lens[0], lens[1], null, lens[0], lens[1]);
+        }
+        else 
+        {
+        float[][] samples = ((GriddedSet)domain_set).getSamples(false);
+
+        BranchGroup branch = new BranchGroup();
+        branch.setCapability(BranchGroup.ALLOW_DETACH);
+        branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+        branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+        branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+
+
+        int start   = 0;
+        int i_total = 0;
+        for (int i=0; i<n_y_sub; i++) {
+          int leny = y_start_stop[i][1] - y_start_stop[i][0] + 1;
+          for (int j=0; j<n_x_sub; j++) {
+            int lenx = x_start_stop[j][1] - x_start_stop[j][0] + 1;
+       
+            if (j > 0) {  // vertical stitch
+              float[][] samplesC = new float[2][4*leny];
+              byte[][] color_bytesC  = new byte[4][4*leny];
+              int cntv = 0;
+              int startv = x_start_stop[j][0] + i_total*lens[0];
+              for (int iv=0; iv < leny; iv++) {
+                samplesC[0][cntv] = samples[0][startv-2];
+		samplesC[0][cntv+1] = samples[0][startv-1];
+                samplesC[0][cntv+2] = samples[0][startv];
+                samplesC[0][cntv+3] = samples[0][startv+1];
+                samplesC[1][cntv] = samples[1][startv-2];
+                samplesC[1][cntv+1] = samples[1][startv-1];
+                samplesC[1][cntv+2] = samples[1][startv];
+                samplesC[1][cntv+3] = samples[1][startv+1];
+                for (int c=0; c<4; c++) {
+                  color_bytesC[c][cntv] = color_bytes[c][startv-2];
+                  color_bytesC[c][cntv+1] = color_bytes[c][startv-1];
+                  color_bytesC[c][cntv+2] = color_bytes[c][startv];
+                  color_bytesC[c][cntv+3] = color_bytes[c][startv+1];
+                }
+                cntv += 4;
+                startv += lens[0];
+              }
+              Gridded2DSet gsetv  = new Gridded2DSet(domain_set.getType(), samplesC, 4, leny);
+              BranchGroup branchv = new BranchGroup();
+              branchv.setCapability(BranchGroup.ALLOW_DETACH);
+              branchv.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+              branchv.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+              branchv.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+              buildCurvedTexture(branchv, gsetv, dataUnits, domain_units, default_values, DomainComponents,
+                                 valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha,
+                                 value_array, constant_color, color_bytesC, display, curved_size, Domain,
+                                 dataCoordinateSystem, renderer, adaptedShadowType, new int[] {x_start_stop[j][0],
+                               y_start_stop[i][0]}, lenx, leny, samples, lens[0], lens[1]);
+              branch.addChild(branchv);
+            }
+            if (i > 0) {  // horz stitch
+              float[][] samplesC = new float[2][4*lenx];
+              byte[][] color_bytesC = new byte[4][4*lenx];
+              int starth = x_start_stop[j][0] + i_total*lens[0];
+              int cnth = 0;
+              System.arraycopy(samples[0], starth-2*lens[0], samplesC[0], cnth, lenx);
+              System.arraycopy(samples[1], starth-2*lens[0], samplesC[1], cnth, lenx);
+              cnth += lenx;
+              System.arraycopy(samples[0], starth-1*lens[0], samplesC[0], cnth, lenx);
+              System.arraycopy(samples[1], starth-1*lens[0], samplesC[1], cnth, lenx);
+              cnth += lenx;
+              System.arraycopy(samples[0], starth, samplesC[0], cnth, lenx);
+              System.arraycopy(samples[1], starth, samplesC[1], cnth, lenx);
+              cnth += lenx;
+              System.arraycopy(samples[0], starth+1*lens[0], samplesC[0], cnth, lenx);
+              System.arraycopy(samples[1], starth+1*lens[0], samplesC[1], cnth, lenx);
+
+              cnth = 0;
+              for (int c=0; c<4; c++) {
+                System.arraycopy(color_bytes[c], starth-2*lens[0],
+                  color_bytesC[c], cnth, lenx);
+              }
+              cnth += lenx;
+              for (int c=0; c<4; c++) {
+                System.arraycopy(color_bytes[c], starth-1*lens[0],
+                  color_bytesC[c], cnth, lenx);
+              }
+              cnth += lenx;
+              for (int c=0; c<4; c++) {
+                System.arraycopy(color_bytes[c], starth,
+                  color_bytesC[c], cnth, lenx);
+              }
+              cnth += lenx;
+              for (int c=0; c<4; c++) {
+                System.arraycopy(color_bytes[c], starth+1*lens[0],
+                  color_bytesC[c], cnth, lenx);
+              }
+
+              Gridded2DSet gseth  = new Gridded2DSet(domain_set.getType(), samplesC, lenx, 4);
+              BranchGroup branchh = new BranchGroup();
+              branchh.setCapability(BranchGroup.ALLOW_DETACH);
+              branchh.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+              branchh.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+              branchh.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+              buildCurvedTexture(branchh, gseth, dataUnits, domain_units, default_values, DomainComponents,
+                                 valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha,
+                                 value_array, constant_color, color_bytesC, display, curved_size, Domain,
+                                 dataCoordinateSystem, renderer, adaptedShadowType, new int[] {x_start_stop[j][0],
+                               y_start_stop[i][0]}, lenx, leny, samples, lens[0], lens[1]);
+              branch.addChild(branchh);
+            }
+            //- tile piece
+            byte[][] color_bytesW  = new byte[4][lenx*leny];
+            int cnt = 0;
+            for (k=0; k<leny; k++) {
+              start = x_start_stop[j][0] + i_total*lens[0] + k*lens[0];
+              for (int c=0; c<4; c++) {
+                System.arraycopy(color_bytes[c], start,
+                  color_bytesW[c], cnt, lenx);
+              }
+              cnt += lenx;
+            }
+            Gridded2DSet gset1 = null;
+            BranchGroup branch1 = new BranchGroup();
+            branch1.setCapability(BranchGroup.ALLOW_DETACH);
+            branch1.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+            branch1.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+            branch1.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+            buildCurvedTexture(branch1, gset1, dataUnits, domain_units, default_values, DomainComponents,
+                               valueArrayLength, inherited_values, valueToScalar, mode, constant_alpha,
+                               value_array, constant_color, color_bytesW, display, curved_size, Domain,
+                               dataCoordinateSystem, renderer, adaptedShadowType, new int[] {x_start_stop[j][0], 
+                               y_start_stop[i][0]}, lenx, leny, samples, lens[0], lens[1]);
+            branch.addChild(branch1);
+          }
+          i_total += leny;
+        }
+        color_bytes = null;
+
+        // group: top level
+        if (((Group) group).numChildren() > 0) {
+          ((Group) group).setChild(branch, 0);
+        }
+        else {
+          ((Group) group).addChild(branch);
+        }
+
+        }
+
+      } // end if (curvedTexture)
+      else { // !isTextureMap && !curvedTexture
+        throw new BadMappingException("must be texture map or curved texture map");
+      }
+    }
+    else { // !adaptedShadowType.getIsTerminal()
+      Vector domain_maps = DomainComponents[0].getSelectedMapVector();
+      ScalarMap amap = null;
+      if (domain_set.getDimension() == 1 && domain_maps.size() == 1) {
+        ScalarMap map = (ScalarMap) domain_maps.elementAt(0);
+        if (Display.Animation.equals(map.getDisplayScalar())) {
+          amap = map;
+        }
+      }
+      if (amap == null) {
+        throw new BadMappingException("time must be mapped to Animation");
+      }
+      AnimationControlJ3D control = (AnimationControlJ3D) amap.getControl();
+
+      // get any usable frames from the old scene graph
+      Switch old_swit = null;
+      BranchGroup[] old_nodes = null;
+      double[] old_times = null;
+      boolean[] old_mark = null;
+      int old_len = 0;
+      boolean reuse = ((ImageRendererJ3D) renderer).getReUseFrames();
+      if (group instanceof BranchGroup &&
+          ((BranchGroup) group).numChildren() > 0) {
+        Node g = ((BranchGroup) group).getChild(0);
+
+        // WLH 06 Feb 06 - support switch in a branch group.
+        if (g instanceof BranchGroup &&
+            ((BranchGroup) g).numChildren() > 0) {
+            g = ((BranchGroup) g).getChild(0);
+        }
+
+        if (g instanceof Switch) {
+          old_swit = (Switch) g;
+
+          old_len = old_swit.numChildren();
+          if (old_len > 0) {
+            old_nodes = new BranchGroup[old_len];
+            for (int i=0; i<old_len; i++) {
+              old_nodes[i] = (BranchGroup) old_swit.getChild(i);
+            }
+            // remove old_nodes from old_swit
+            for (int i=0; i<old_len; i++) {
+              old_nodes[i].detach();
+            }
+            old_times = new double[old_len];
+            old_mark = new boolean[old_len];
+            for (int i=0; i<old_len; i++) {
+              old_mark[i] = false;
+              if (old_nodes[i] instanceof VisADBranchGroup && reuse) {
+                old_times[i] = ((VisADBranchGroup) old_nodes[i]).getTime();
+              }
+              else {
+                old_times[i] = Double.NaN;
+              }
+            }
+          }
+        }
+      } // end if (((BranchGroup) group).numChildren() > 0)
+
+      // create frames for new scene graph
+      // Set aset = control.getSet();
+      // double[][] values = aset.getDoubles();
+      double[][] values = domain_set.getDoubles();
+      double[] times = values[0];
+      int len = times.length;
+      double delta = Math.abs((times[len-1] - times[0]) / (1000.0 * len));
+
+      // create new Switch and make live
+      // control.clearSwitches(this); // already done in DataRenderer.doAction
+      Switch swit = null;
+      if (old_swit != null) {
+        swit = old_swit;
+        ((AVControlJ3D) control).addPair((Switch) swit, domain_set, renderer);
+        ((AVControlJ3D) control).init();
+      }
+      else {
+        swit = (Switch) makeSwitch();
+        swit.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+        swit.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+        swit.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+
+        addSwitch(group, swit, control, domain_set, renderer);
+      }
+
+      // insert old frames into new scene graph, and make
+      // new (blank) VisADBranchGroups for rendering new frames
+      VisADBranchGroup[] nodes = new VisADBranchGroup[len];
+      boolean[] mark = new boolean[len];
+      for (int i=0; i<len; i++) {
+        for (int j=0; j<old_len; j++) {
+          if (!old_mark[j] && Math.abs(times[i] - old_times[j]) < delta) {
+            old_mark[j] = true;
+            nodes[i] = (VisADBranchGroup) old_nodes[j];
+            break;
+          }
+        }
+        if (nodes[i] != null) {
+          mark[i] = true;
+        }
+        else {
+          mark[i] = false;
+          nodes[i] = new VisADBranchGroup(times[i]);
+          nodes[i].setCapability(BranchGroup.ALLOW_DETACH);
+          nodes[i].setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+          nodes[i].setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          nodes[i].setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+          ensureNotEmpty(nodes[i]);
+        }
+        addToSwitch(swit, nodes[i]);
+      }
+      for (int j=0; j<old_len; j++) {
+        if (!old_mark[j]) {
+          ((RendererJ3D) renderer).flush(old_nodes[j]);
+          old_nodes[j] = null;
+        }
+      }
+      // make sure group is live
+      if (group instanceof BranchGroup) {
+        ((ImageRendererJ3D) renderer).setBranchEarly((BranchGroup) group);
+      }
+      // change animation sampling, but don't trigger re-transform
+      if (((ImageRendererJ3D) renderer).getReUseFrames() &&
+          ((ImageRendererJ3D) renderer).getSetSetOnReUseFrames()) {
+        control.setSet(domain_set, true);
+      }
+      old_nodes = null;
+      old_times = null;
+      old_mark = null;
+
+      // render new frames
+      for (int i=0; i<len; i++) {
+/*
+// this is to test setBranchEarly()
+if (i == (len / 2)) {
+  System.out.println("doTransform delay");
+  new Delay(5000);
+}
+*/
+        if (!mark[i]) {
+          // not necessary, but perhaps if this is modified
+          // int[] lat_lon_indices = renderer.getLatLonIndices();
+          BranchGroup branch = (BranchGroup) makeBranch();
+          ((ImageRendererJ3D) renderer).setVisADBranch(nodes[i]);
+          recurseRange(branch, ((Field) data).getSample(i),
+                       value_array, default_values, renderer);
+          ((ImageRendererJ3D) renderer).setVisADBranch(null);
+          nodes[i].addChild(branch);
+          // not necessary, but perhaps if this is modified
+          // renderer.setLatLonIndices(lat_lon_indices);
+        }
+      }
+    }
+
+
+    ensureNotEmpty(group);
+    return false;
+  }
+
+  public void buildCurvedTexture(Object group, Set domain_set, Unit[] dataUnits, Unit[] domain_units,
+                                 float[] default_values, ShadowRealType[] DomainComponents,
+                                 int valueArrayLength, int[] inherited_values, int[] valueToScalar,
+                                 GraphicsModeControl mode, float constant_alpha, float[] value_array, 
+                                 float[] constant_color, byte[][] color_bytes, DisplayImpl display,
+                                 int curved_size, ShadowRealTupleType Domain, CoordinateSystem dataCoordinateSystem,
+                                 DataRenderer renderer, ShadowFunctionOrSetType adaptedShadowType,
+                                 int[] start, int lenX, int lenY, float[][] samples, int bigX, int bigY)
+         throws VisADException, DisplayException {
+ //System.out.println("start curved texture " + System.currentTimeMillis());
+    float[] coordinates = null;
+    float[] texCoords = null;
+    float[] normals = null;
+    byte[] colors = null;
+    int data_width = 0;
+    int data_height = 0;
+    int texture_width = 1;
+    int texture_height = 1;
+
+    int[] lengths = null;
+                                                                                                                     
+    // get domain_set sizes
+    if (domain_set != null) {
+      lengths = ((GriddedSet) domain_set).getLengths();
+    }
+    else {
+      lengths = new int[] {lenX, lenY};
+    }
+
+    data_width = lengths[0];
+    data_height = lengths[1];
+    // texture sizes must be powers of two
+    texture_width = textureWidth(data_width);
+    texture_height = textureHeight(data_height);
+                                                                                                                   
+    // compute size of triangle array to mapped texture onto
+    int size = (data_width + data_height) / 2;
+    curved_size = Math.max(2, Math.min(curved_size, size / 32));
+                                                                                                                   
+    int nwidth = 2 + (data_width - 1) / curved_size;
+    int nheight = 2 + (data_height - 1) / curved_size;
+                                                                                                                   
+    // compute locations of triangle vertices in texture
+    int nn = nwidth * nheight;
+    int[] is = new int[nwidth];
+    int[] js = new int[nheight];
+    for (int i=0; i<nwidth; i++) {
+      is[i] = Math.min(i * curved_size, data_width - 1);
+    }
+    for (int j=0; j<nheight; j++) {
+      js[j] = Math.min(j * curved_size, data_height - 1);
+    }
+                                                                                                                   
+    // get spatial coordinates at triangle vertices
+    int[] indices = new int[nn];
+    int k=0;
+    for (int j=0; j<nheight; j++) {
+      for (int i=0; i<nwidth; i++) {
+        indices[k] = is[i] + data_width * js[j];
+        k++;
+      }
+    }
+    float[][] spline_domain = null;
+    if (domain_set == null) {
+      for (int kk = 0; kk < indices.length; kk++) {
+        int x = indices[kk] % lenX;
+        int y = indices[kk] / lenX;
+        indices[kk] = (start[0] + x) + (start[1] + y)*bigX;
+      }
+      spline_domain = new float[2][indices.length];
+      for (int kk=0; kk<indices.length; kk++) {
+        spline_domain[0][kk] = samples[0][indices[kk]];
+        spline_domain[1][kk] = samples[1][indices[kk]];
+      }
+    }
+    else {
+      spline_domain = domain_set.indexToValue(indices);
+    }
+
+    spline_domain =
+        Unit.convertTuple(spline_domain, dataUnits, domain_units, false);
+                                                                                                                   
+    // transform for any CoordinateSystem in data (Field) Domain
+    ShadowRealTupleType domain_reference = Domain.getReference();
+                                                                                                                   
+    ShadowRealType[] DC = DomainComponents;
+    if (domain_reference != null &&
+        domain_reference.getMappedDisplayScalar()) {
+      RealTupleType ref = (RealTupleType) domain_reference.getType();
+      renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                  ref.getDefaultUnits(), (RealTupleType) Domain.getType(),
+                  new CoordinateSystem[] {dataCoordinateSystem},
+                  domain_units);
+      spline_domain =
+        CoordinateSystem.transformCoordinates(
+          ref, null, ref.getDefaultUnits(), null,
+          (RealTupleType) Domain.getType(), dataCoordinateSystem,
+          domain_units, null, spline_domain);
+      // ShadowRealTypes of DomainReference
+      DC = adaptedShadowType.getDomainReferenceComponents();
+    }
+    else {
+      RealTupleType ref = (domain_reference == null) ? null :
+                          (RealTupleType) domain_reference.getType();
+      Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+      renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                  ref_units, (RealTupleType) Domain.getType(),
+                  new CoordinateSystem[] {dataCoordinateSystem},
+                  domain_units);
+    }
+                                                                                                                   
+    int[] tuple_index = new int[3];
+    int[] spatial_value_indices = {-1, -1, -1};
+    ScalarMap[] spatial_maps = new ScalarMap[3];
+                                                                                                                   
+    DisplayTupleType spatial_tuple = null;
+    for (int i=0; i<DC.length; i++) {
+      Enumeration maps =
+        DC[i].getSelectedMapVector().elements();
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      DisplayRealType real = map.getDisplayScalar();
+      spatial_tuple = real.getTuple();
+      if (spatial_tuple == null) {
+        throw new DisplayException("texture with bad tuple: " +
+                                   "ShadowImageFunctionTypeJ3D.doTransform");
+      }
+      // get spatial index
+      tuple_index[i] = real.getTupleIndex();
+      spatial_value_indices[tuple_index[i]] = map.getValueIndex();
+      spatial_maps[tuple_index[i]] = map;
+      if (maps.hasMoreElements()) {
+        throw new DisplayException("texture with multiple spatial: " +
+                                   "ShadowImageFunctionTypeJ3D.doTransform");
+      }
+    } // end for (int i=0; i<DC.length; i++)
+    // get spatial index not mapped from domain_set
+    tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+    DisplayRealType real =
+      (DisplayRealType) spatial_tuple.getComponent(tuple_index[2]);
+    int value2_index = display.getDisplayScalarIndex(real);
+    float value2 = default_values[value2_index];
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0 &&
+          real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+        value2 = value_array[i];
+        break;
+      }
+    }
+                                                                                                                   
+    float[][] spatial_values = new float[3][];
+    spatial_values[tuple_index[0]] = spline_domain[0];
+    spatial_values[tuple_index[1]] = spline_domain[1];
+    spatial_values[tuple_index[2]] = new float[nn];
+    Arrays.fill(spatial_values[tuple_index[2]], value2);
+                                                                                                                   
+    for (int i=0; i<3; i++) {
+      if (spatial_maps[i] != null) {
+        //-TDR spatial_values[i] = spatial_maps[i].scaleValues(spatial_values[i]);
+        spatial_values[i] = spatial_maps[i].scaleValues(spatial_values[i], false);
+      }
+    }
+                             	//System.err.println("Spatial Values:" + spatial_values[0].length);                                                                                     
+    if (spatial_tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+// inside 'if (anyFlow) {}' in ShadowType.assembleSpatial()
+      renderer.setEarthSpatialDisplay(null, spatial_tuple, display,
+               spatial_value_indices, default_values, null);
+    }
+    else {
+      CoordinateSystem coord = spatial_tuple.getCoordinateSystem();
+      spatial_values = coord.toReference(spatial_values);
+      // float[][] new_spatial_values = coord.toReference(spatial_values);
+      // for (int i=0; i<3; i++) spatial_values[i] = new_spatial_values[i];
+                                                                                                                   
+// inside 'if (anyFlow) {}' in ShadowType.assembleSpatial()
+      renderer.setEarthSpatialDisplay(coord, spatial_tuple, display,
+               spatial_value_indices, default_values, null);
+    }
+                                                                                                                   
+    // break from ShadowFunctionOrSetType
+    coordinates = new float[3 * nn];
+    k = 0;
+    for (int i=0; i<nn; i++) {
+      coordinates[k++] = spatial_values[0][i];
+      coordinates[k++] = spatial_values[1][i];
+      coordinates[k++] = spatial_values[2][i];
+    }
+                                                                                                                   
+    boolean spatial_all_select = true;
+    for (int i=0; i<3*nn; i++) {
+      if (coordinates[i] != coordinates[i]) { 
+	spatial_all_select = false;
+	break;
+      }
+    }
+                                                                                                                   
+    normals = Gridded3DSet.makeNormals(coordinates, nwidth, nheight);
+    colors = new byte[3 * nn];
+    Arrays.fill(colors, (byte) 127);
+                                                                                                                   
+    float ratiow = ((float) data_width) / ((float) texture_width);
+    float ratioh = ((float) data_height) / ((float) texture_height);
+                                                                                                                   
+    // WLH 27 Jan 2003
+    float half_width = 0.5f / ((float) texture_width);
+    float half_height = 0.5f / ((float) texture_height);
+    float width = 1.0f / ((float) texture_width);
+    float height = 1.0f / ((float) texture_height);
+                                                                                                                   
+    int mt = 0;
+    texCoords = new float[2 * nn];
+    for (int j=0; j<nheight; j++) {
+      float jsfactor = js[j] / (data_height - 1.0f);
+      for (int i=0; i<nwidth; i++) {
+        //texCoords[mt++] = ratiow * is[i] / (data_width - 1.0f);
+        //texCoords[mt++] = 1.0f - ratioh * js[j] / (data_height - 1.0f);
+        // WLH 27 Jan 2003
+        float isfactor = is[i] / (data_width - 1.0f);
+        texCoords[mt++] = (ratiow - width) * isfactor + half_width;
+        /* yUp = true
+        texCoords[mt++] = (ratioh - height) * jsfactor + half_height;
+        */
+        // yUp = false;
+        texCoords[mt++] = 1.0f - (ratioh - height) * jsfactor - half_height;
+      }
+    }
+    VisADTriangleStripArray tarray = new VisADTriangleStripArray();
+    tarray.stripVertexCounts = new int[nheight - 1];
+    Arrays.fill(tarray.stripVertexCounts, 2*nwidth);
+    int len = (nheight - 1) * (2 * nwidth);
+    tarray.vertexCount = len;
+    tarray.normals = new float[3 * len];
+    tarray.coordinates = new float[3 * len];
+    //tarray.colors = new byte[3 * len];
+    tarray.texCoords = new float[2 * len];
+                                                                                                                   
+    // shuffle normals into tarray.normals, etc
+    k = 0;
+    int kt = 0;
+    int nwidth3 = 3 * nwidth;
+    int nwidth2 = 2 * nwidth;
+    for (int i=0; i<nheight-1; i++) {
+      int m = i * nwidth3;
+      mt = i * nwidth2;
+      for (int j=0; j<nwidth; j++) {
+        tarray.coordinates[k] = coordinates[m];
+        tarray.coordinates[k+1] = coordinates[m+1];
+        tarray.coordinates[k+2] = coordinates[m+2];
+        tarray.coordinates[k+3] = coordinates[m+nwidth3];
+        tarray.coordinates[k+4] = coordinates[m+nwidth3+1];
+        tarray.coordinates[k+5] = coordinates[m+nwidth3+2];
+                                                                                                                   
+        tarray.normals[k] = normals[m];
+        tarray.normals[k+1] = normals[m+1];
+        tarray.normals[k+2] = normals[m+2];
+        tarray.normals[k+3] = normals[m+nwidth3];
+        tarray.normals[k+4] = normals[m+nwidth3+1];
+        tarray.normals[k+5] = normals[m+nwidth3+2];
+                                                                                                                   
+        /*
+        tarray.colors[k] = colors[m];
+        tarray.colors[k+1] = colors[m+1];
+        tarray.colors[k+2] = colors[m+2];
+        tarray.colors[k+3] = colors[m+nwidth3];
+        tarray.colors[k+4] = colors[m+nwidth3+1];
+        tarray.colors[k+5] = colors[m+nwidth3+2];
+        */
+                                                                                                                   
+        tarray.texCoords[kt] = texCoords[mt];
+        tarray.texCoords[kt+1] = texCoords[mt+1];
+        tarray.texCoords[kt+2] = texCoords[mt+nwidth2];
+        tarray.texCoords[kt+3] = texCoords[mt+nwidth2+1];
+                                                                                                                   
+        k += 6;
+        m += 3;
+        kt += 4;
+        mt += 2;
+      }
+    }
+    // do surgery to remove any missing spatial coordinates in texture
+    if (!spatial_all_select) {
+      tarray = (VisADTriangleStripArray) tarray.removeMissing();
+    }
+                                                                                                                   
+    // do surgery along any longitude split (e.g., date line) in texture
+    if (adaptedShadowType.getAdjustProjectionSeam()) {
+      tarray = (VisADTriangleStripArray) tarray.adjustLongitude(renderer);
+      tarray = (VisADTriangleStripArray) tarray.adjustSeam(renderer);
+    }
+// System.out.println("start createImage " + (System.currentTimeMillis() - link.start_time));
+    // create BufferedImage for texture from color_bytes
+    BufferedImage image = createImage(data_width, data_height, texture_width,
+                                      texture_height, color_bytes);
+                                                                                                                   
+// System.out.println("start textureToGroup " + (System.currentTimeMillis() - link.start_time));
+                                                                                                                   
+    // add texture as sub-node of group in scene graph
+    textureToGroup(group, tarray, image, mode, constant_alpha,
+                   constant_color, texture_width, texture_height);
+// System.out.println("end curved texture " + (System.currentTimeMillis() - link.start_time));
+  }
+
+  public void buildLinearTexture(Object group, Set domain_set, Unit[] dataUnits, Unit[] domain_units,
+                                 float[] default_values, ShadowRealType[] DomainComponents,
+                                 int valueArrayLength, int[] inherited_values, int[] valueToScalar,
+                                 GraphicsModeControl mode, float constant_alpha,
+                                 float[] value_array, float[] constant_color, byte[][] color_bytes, DisplayImpl display)
+         throws VisADException, DisplayException {
+
+    float[] coordinates = null;
+    float[] texCoords = null;
+    float[] normals = null;
+    byte[] colors = null;
+    int data_width = 0;
+    int data_height = 0;
+    int texture_width = 1;
+    int texture_height = 1;
+
+    Linear1DSet X = null;
+    Linear1DSet Y = null;
+    if (domain_set instanceof Linear2DSet) {
+      X = ((Linear2DSet) domain_set).getX();
+      Y = ((Linear2DSet) domain_set).getY();
+    }
+    else {
+      X = ((LinearNDSet) domain_set).getLinear1DComponent(0);
+      Y = ((LinearNDSet) domain_set).getLinear1DComponent(1);
+    }
+    float[][] limits = new float[2][2];
+    limits[0][0] = (float) X.getFirst();
+    limits[0][1] = (float) X.getLast();
+    limits[1][0] = (float) Y.getFirst();
+    limits[1][1] = (float) Y.getLast();
+                                                                                                                       
+    // get domain_set sizes
+    data_width = X.getLength();
+    data_height = Y.getLength();
+    // texture sizes must be powers of two
+    texture_width = textureWidth(data_width);
+    texture_height = textureHeight(data_height);
+                                                                                                                       
+                                                                                                                       
+    // WLH 27 Jan 2003
+    float half_width = 0.5f / ((float) (data_width - 1));
+    float half_height = 0.5f / ((float) (data_height - 1));
+    half_width = (limits[0][1] - limits[0][0]) * half_width;
+    half_height = (limits[1][1] - limits[1][0]) * half_height;
+    limits[0][0] -= half_width;
+    limits[0][1] += half_width;
+    limits[1][0] -= half_height;
+    limits[1][1] += half_height;
+                                                                                                                       
+                                                                                                                       
+    // convert values to default units (used in display)
+    limits = Unit.convertTuple(limits, dataUnits, domain_units);
+
+    int[] tuple_index = new int[3];
+    if (DomainComponents.length != 2) {
+      throw new DisplayException("texture domain dimension != 2:" +
+                                 "ShadowFunctionOrSetType.doTransform");
+    }
+                                                                                                                       
+    // find the spatial ScalarMaps
+    for (int i=0; i<DomainComponents.length; i++) {
+      Enumeration maps = DomainComponents[i].getSelectedMapVector().elements();
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      // scale values
+      limits[i] = map.scaleValues(limits[i]);
+      DisplayRealType real = map.getDisplayScalar();
+      DisplayTupleType tuple = real.getTuple();
+      if (tuple == null ||
+          !tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+        throw new DisplayException("texture with bad tuple: " +
+                                   "ShadowFunctionOrSetType.doTransform");
+      }
+      // get spatial index
+      tuple_index[i] = real.getTupleIndex();
+      if (maps.hasMoreElements()) {
+        throw new DisplayException("texture with multiple spatial: " +
+                                   "ShadowFunctionOrSetType.doTransform");
+      }
+    } // end for (int i=0; i<DomainComponents.length; i++)
+    // get spatial index not mapped from domain_set
+    tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+    DisplayRealType real = (DisplayRealType)
+      Display.DisplaySpatialCartesianTuple.getComponent(tuple_index[2]);
+    int value2_index = display.getDisplayScalarIndex(real);
+    float value2 = default_values[value2_index];
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0 &&
+          real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+        // value for unmapped spatial dimension
+        value2 = value_array[i];
+        break;
+      }
+    }
+
+    // create VisADQuadArray that texture is mapped onto
+    coordinates = new float[12];
+    // corner 0
+    coordinates[tuple_index[0]] = limits[0][0];
+    coordinates[tuple_index[1]] = limits[1][0];
+    coordinates[tuple_index[2]] = value2;
+    // corner 1
+    coordinates[3 + tuple_index[0]] = limits[0][1];
+    coordinates[3 + tuple_index[1]] = limits[1][0];
+    coordinates[3 + tuple_index[2]] = value2;
+    // corner 2
+    coordinates[6 + tuple_index[0]] = limits[0][1];
+    coordinates[6 + tuple_index[1]] = limits[1][1];
+    coordinates[6 + tuple_index[2]] = value2;
+    // corner 3
+    coordinates[9 + tuple_index[0]] = limits[0][0];
+    coordinates[9 + tuple_index[1]] = limits[1][1];
+    coordinates[9 + tuple_index[2]] = value2;
+                                                                                                                       
+    // move image back in Java3D 2-D mode
+    adjustZ(coordinates);
+                                                                                                                       
+    texCoords = new float[8];
+    float ratiow = ((float) data_width) / ((float) texture_width);
+    float ratioh = ((float) data_height) / ((float) texture_height);
+    setTexCoords(texCoords, ratiow, ratioh);
+                                                                                                                       
+    normals = new float[12];
+    float n0 = ((coordinates[3+2]-coordinates[0+2]) *
+                (coordinates[6+1]-coordinates[0+1])) -
+               ((coordinates[3+1]-coordinates[0+1]) *
+                (coordinates[6+2]-coordinates[0+2]));
+    float n1 = ((coordinates[3+0]-coordinates[0+0]) *
+                (coordinates[6+2]-coordinates[0+2])) -
+               ((coordinates[3+2]-coordinates[0+2]) *
+                (coordinates[6+0]-coordinates[0+0]));
+    float n2 = ((coordinates[3+1]-coordinates[0+1]) *
+                (coordinates[6+0]-coordinates[0+0])) -
+               ((coordinates[3+0]-coordinates[0+0]) *
+                (coordinates[6+1]-coordinates[0+1]));
+                                                                                                                       
+    float nlen = (float) Math.sqrt(n0 *  n0 + n1 * n1 + n2 * n2);
+    n0 = n0 / nlen;
+    n1 = n1 / nlen;
+    n2 = n2 / nlen;
+
+    // corner 0
+    normals[0] = n0;
+    normals[1] = n1;
+    normals[2] = n2;
+    // corner 1
+    normals[3] = n0;
+    normals[4] = n1;
+    normals[5] = n2;
+    // corner 2
+    normals[6] = n0;
+    normals[7] = n1;
+    normals[8] = n2;
+    // corner 3
+    normals[9] = n0;
+    normals[10] = n1;
+    normals[11] = n2;
+                                                                                                                       
+    colors = new byte[12];
+    for (int i=0; i<12; i++) colors[i] = (byte) 127;
+                                                                                                                       
+    VisADQuadArray qarray = new VisADQuadArray();
+    qarray.vertexCount = 4;
+    qarray.coordinates = coordinates;
+    qarray.texCoords = texCoords;
+    qarray.colors = colors;
+    qarray.normals = normals;
+                                                                                                                       
+// System.out.println("start createImage " + (System.currentTimeMillis() - link.start_time));
+                                                                                                                       
+    // create BufferedImage for texture from color_bytes
+    BufferedImage image = createImage(data_width, data_height, texture_width,
+                                      texture_height, color_bytes);
+                                                                                                                       
+// System.out.println("start textureToGroup " + (System.currentTimeMillis() - link.start_time));
+                                                                                                                       
+    // add texture as sub-node of group in scene graph
+    textureToGroup(group, qarray, image, mode, constant_alpha,
+                   constant_color, texture_width, texture_height);
+                                                                                                                           
+// System.out.println("end texture map " + (System.currentTimeMillis() - link.start_time));
+  }
+
+  public BufferedImage createImage(int data_width, int data_height,
+                                   int texture_width, int texture_height,
+                                   byte[][] color_bytes) throws VisADException {
+    ShadowFunctionOrSetType adaptedShadowType =
+      (ShadowFunctionOrSetType) getAdaptedShadowType();
+    return adaptedShadowType.createImage(data_width, data_height,
+      texture_width, texture_height, color_bytes);
+//    BufferedImage image = null;
+//    ColorModel colorModel = ColorModel.getRGBdefault();
+//    WritableRaster raster = colorModel.createCompatibleWritableRaster(
+//      texture_width, texture_height);
+//    image = new BufferedImage(colorModel, raster, false, null);
+//    int[] intData = ((DataBufferInt)raster.getDataBuffer()).getData();
+//    int k = 0;
+//    int m = 0;
+//    int r, g, b, a;
+//    for (int j=0; j<data_height; j++) {
+//      for (int i=0; i<data_width; i++) {
+//        intData[m++] = color_ints[k++];
+//      }
+//      for (int i=data_width; i<texture_width; i++) {
+//        intData[m++] = 0;
+//      }
+//    }
+//    for (int j=data_height; j<texture_height; j++) {
+//      for (int i=0; i<texture_width; i++) {
+//        intData[m++] = 0;
+//      }
+//    }
+//    return image;
+  }
+
+
+  // test program
+  private static DisplayImpl display;
+  private static BaseMapAdapter baseMap;
+  private static ScalarMap lat_map;
+  private static ScalarMap lon_map;
+  private static ScalarMap xaxis;
+  private static ScalarMap yaxis;
+
+  // run 'java visad.bom.ShadowImageFunctionTypeJ3D' for globe display
+  // run 'java visad.bom.ShadowImageFunctionTypeJ3D X remap'
+  //                     for remapped globe display
+  // run 'java visad.bom.ShadowImageFunctionTypeJ3D X 2D' for flat display
+  public static void main (String[] args) {
+
+    String mapFile = "OUTLSUPW";
+    String areaFile = "AREA2001";
+    boolean threeD = true;
+    boolean remap = false;
+
+    JFrame frame = new JFrame("Map Display");
+    frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+            System.exit(0);
+        }
+    });
+
+    if (args.length > 0 && !args[0].equals("X")) {
+       areaFile = args[0];
+       // mapFile = args[0];
+    }
+    if (args.length == 2) {
+       threeD = (args[1].indexOf("2") >= 0) ? false : true;
+       remap = (args[1].indexOf("2") >= 0) ? false : true;
+    }
+
+    boolean gif = areaFile.endsWith("gif") || areaFile.endsWith("GIF") ||
+                  areaFile.endsWith("jpg") || areaFile.endsWith("JPG");
+
+    try {
+
+      if (mapFile.indexOf("://") > 0) {
+        baseMap = new BaseMapAdapter(new URL(mapFile) );
+      } else {
+        baseMap = new BaseMapAdapter(mapFile);
+      }
+
+      //--- map data to display ---//
+      if (gif) {
+        display = new DisplayImplJ3D("display",
+                                     new TwoDDisplayRendererJ3D());
+        lat_map = new ScalarMap(RealType.getRealType("ImageLine"), Display.YAxis);
+        lon_map = new ScalarMap(RealType.getRealType("ImageElement"), Display.XAxis);
+      }
+      else if (threeD)
+      {
+        display = new DisplayImplJ3D("display");
+        lat_map = new ScalarMap(RealType.Latitude, Display.Latitude);
+        lon_map = new ScalarMap(RealType.Longitude, Display.Longitude);
+      }
+      else
+      {
+        display = new DisplayImplJ3D("display",
+                                     new TwoDDisplayRendererJ3D());
+        lat_map = new ScalarMap(RealType.Latitude, Display.YAxis);
+        lon_map = new ScalarMap(RealType.Longitude, Display.XAxis);
+      }
+
+      display.addMap(lat_map);
+      display.addMap(lon_map);
+
+      if (!gif) {
+        lat_map.setRange(-90.0, 90.0);
+        lon_map.setRange(-180.0, 180.0);
+      }
+
+      DataReference maplines_ref = new DataReferenceImpl("MapLines");
+      maplines_ref.setData(baseMap.getData());
+
+      ConstantMap[] colMap;
+      colMap = new ConstantMap[4];
+      colMap[0] = new ConstantMap(0., Display.Blue);
+      colMap[1] = new ConstantMap(1., Display.Red);
+      colMap[2] = new ConstantMap(0., Display.Green);
+      colMap[3] = new ConstantMap(1.001, Display.Radius); // map lines above image
+
+      FlatField imaget = null;
+      if (gif) {
+        GIFForm gif_form = new GIFForm();
+        imaget = (FlatField) gif_form.open(areaFile);
+      }
+      else {
+        AreaAdapter aa = new AreaAdapter(areaFile);
+        imaget = aa.getData();
+      }
+
+      FunctionType ftype = (FunctionType) imaget.getType();
+      RealTupleType dtype = ftype.getDomain();
+      RealTupleType rtype = (RealTupleType)ftype.getRange();
+
+      if (remap) {
+        int SIZE = 256;
+        RealTupleType lat_lon =
+          ((CoordinateSystem) dtype.getCoordinateSystem()).getReference();
+        Linear2DSet dset = new Linear2DSet(lat_lon, -4.0, 70.0, SIZE,
+                                           -150.0, 5.0, SIZE);
+        imaget = (FlatField)
+          imaget.resample(dset, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+        ftype = (FunctionType) imaget.getType();
+        dtype = ftype.getDomain();
+      }
+
+      if (gif) {
+        ScalarMap rmap = new ScalarMap( (RealType) rtype.getComponent(0),
+                                        Display.Red);
+        display.addMap(rmap);
+        ScalarMap gmap = new ScalarMap( (RealType) rtype.getComponent(1),
+                                        Display.Green);
+        display.addMap(gmap);
+        ScalarMap bmap = new ScalarMap( (RealType) rtype.getComponent(2),
+                                        Display.Blue);
+        display.addMap(bmap);
+      }
+      else {
+        // select which band to show...
+        ScalarMap rgbmap = new ScalarMap( (RealType) rtype.getComponent(0),
+                                          Display.RGBA);
+        display.addMap(rgbmap);
+        BaseColorControl control = (BaseColorControl) rgbmap.getControl();
+        control.initGreyWedge();
+/* test for RGBA */
+        float[][] table = control.getTable();
+        for (int i=0; i<table[3].length; i++) {
+          table[3][i] = table[0][i];
+        }
+        control.setTable(table);
+/* end test for RGBA */
+      }
+
+      DataReferenceImpl ref_image = new DataReferenceImpl("ref_image");
+
+/* start modify imaget to be packed bytes */
+      Set[] range_sets = gif ? new Set[] {new Linear1DSet(0.0, 255.0, 255),
+                                          new Linear1DSet(0.0, 255.0, 255),
+                                          new Linear1DSet(0.0, 255.0, 255)} :
+                               new Set[] {new Integer1DSet(255)};
+      FlatField new_field =
+        new FlatField(ftype, imaget.getDomainSet(), null, null, range_sets, null);
+      float[][] values = imaget.getFloats(false);
+      new_field.setSamples(values);
+      imaget = new_field;
+/* end modify imaget */
+
+      ref_image.setData(imaget);
+
+      display.disableAction();
+      display.addReferences(new ImageRendererJ3D(), ref_image);
+      // display.addReference(ref_image);
+      display.addReference(maplines_ref, colMap);
+      display.enableAction();
+    } catch (Exception ne) {ne.printStackTrace(); System.exit(1); }
+
+    frame.getContentPane().add(display.getComponent());
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/bom/ShadowTextureFillSetTypeJ3D.java b/visad/bom/ShadowTextureFillSetTypeJ3D.java
new file mode 100644
index 0000000..396b350
--- /dev/null
+++ b/visad/bom/ShadowTextureFillSetTypeJ3D.java
@@ -0,0 +1,419 @@
+//
+// ShadowTextureFillSetTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import javax.media.j3d.*;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.WritableRaster;
+import java.awt.image.DataBufferInt;
+
+/**
+   The ShadowTextureFillSetTypeJ3D class shadows the FunctionType class for
+   TextureFillRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowTextureFillSetTypeJ3D extends ShadowSetTypeJ3D {
+
+  private static final int MISSING1 = Byte.MIN_VALUE;      // least byte
+
+  private DisplayImplJ3D display = null;
+
+  public ShadowTextureFillSetTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+    display = (DisplayImplJ3D) link.getDisplay();
+  }
+
+  // transform data into a depiction under group
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+
+    DataDisplayLink link = renderer.getLink();
+
+// System.out.println("start doTransform " + (System.currentTimeMillis() - link.start_time));
+
+    // return if data is missing or no ScalarMaps
+    if (data.isMissing()) {
+      ((ImageRendererJ3D) renderer).markMissingVisADBranch();
+      return false;
+    }
+    if (getLevelOfDifficulty() == NOTHING_MAPPED) return false;
+
+    ShadowFunctionOrSetType adaptedShadowType =
+      (ShadowFunctionOrSetType) getAdaptedShadowType();
+    DisplayImpl display = getDisplay();
+    GraphicsModeControl mode = (GraphicsModeControl)
+      display.getGraphicsModeControl().clone();
+
+    // get 'shape' flags
+    boolean anyContour = adaptedShadowType.getAnyContour();
+    boolean anyFlow = adaptedShadowType.getAnyFlow();
+    boolean anyShape = adaptedShadowType.getAnyShape();
+    boolean anyText = adaptedShadowType.getAnyText();
+
+    if (anyContour || anyFlow || anyShape || anyText) {
+      throw new BadMappingException("no contour, flow, shape or text allowed");
+    }
+
+    // get some precomputed values useful for transform
+    // length of ValueArray
+    int valueArrayLength = display.getValueArrayLength();
+    // mapping from ValueArray to DisplayScalar
+    int[] valueToScalar = display.getValueToScalar();
+    // mapping from ValueArray to MapVector
+    int[] valueToMap = display.getValueToMap();
+    Vector MapVector = display.getMapVector();
+
+    // array to hold values for various mappings
+    float[][] display_values = new float[valueArrayLength][];
+
+    int[] inherited_values = adaptedShadowType.getInheritedValues();
+
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0) {
+        display_values[i] = new float[1];
+        display_values[i][0] = value_array[i];
+      }
+    }
+
+    // analyze data's domain (its a Field)
+    Set domain_set = (Set) data;
+    Unit[] dataUnits = domain_set.getSetUnits();
+    CoordinateSystem dataCoordinateSystem = domain_set.getCoordinateSystem();
+
+    float[][] domain_values = null;
+    double[][] domain_doubles = null;
+    ShadowRealTupleType Domain = adaptedShadowType.getDomain();
+    Unit[] domain_units = ((RealTupleType) Domain.getType()).getDefaultUnits();
+    int domain_length;
+    int domain_dimension;
+    try {
+      domain_length = domain_set.getLength();
+      domain_dimension = domain_set.getDimension();
+    }
+    catch (SetException e) {
+      return false;
+    }
+
+    // ShadowRealTypes of Domain
+    ShadowRealType[] DomainComponents = adaptedShadowType.getDomainComponents();
+
+    if (!adaptedShadowType.getIsTerminal()) {
+      throw new DisplayException("not Terminal");
+    }
+
+    // check domain and determine whether it is square or curved texture
+    if (!Domain.getAllSpatial() || Domain.getMultipleDisplayScalar()) {
+      throw new BadMappingException("domain must be only spatial");
+    }
+
+    // get and process domain values
+    float[][] spline_domain = domain_set.getSamples();
+
+    spline_domain = 
+        Unit.convertTuple(spline_domain, dataUnits, domain_units, false);
+
+    // transform for any CoordinateSystem in data (Field) Domain
+    ShadowRealTupleType domain_reference = Domain.getReference();
+
+    ShadowRealType[] DC = DomainComponents;
+    if (domain_reference != null &&
+        domain_reference.getMappedDisplayScalar()) {
+      RealTupleType ref = (RealTupleType) domain_reference.getType();
+      renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                  ref.getDefaultUnits(), (RealTupleType) Domain.getType(),
+                  new CoordinateSystem[] {dataCoordinateSystem},
+                  domain_units);
+
+      spline_domain =
+        CoordinateSystem.transformCoordinates(
+          ref, null, ref.getDefaultUnits(), null,
+          (RealTupleType) Domain.getType(), dataCoordinateSystem,
+          domain_units, null, spline_domain);
+      // ShadowRealTypes of DomainReference
+      DC = adaptedShadowType.getDomainReferenceComponents();
+    }
+    else {
+      RealTupleType ref = (domain_reference == null) ? null :
+                          (RealTupleType) domain_reference.getType();
+      Unit[] ref_units = (ref == null) ? null : ref.getDefaultUnits();
+      renderer.setEarthSpatialData(Domain, domain_reference, ref,
+                  ref_units, (RealTupleType) Domain.getType(),
+                  new CoordinateSystem[] {dataCoordinateSystem},
+                  domain_units);
+    }
+
+    int[] tuple_index = new int[3];
+    int[] spatial_value_indices = {-1, -1, -1};
+    ScalarMap[] spatial_maps = new ScalarMap[3];
+
+    DisplayTupleType spatial_tuple = null;
+    for (int i=0; i<DC.length; i++) {
+      Enumeration maps =
+        DC[i].getSelectedMapVector().elements();
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      DisplayRealType real = map.getDisplayScalar();
+      spatial_tuple = real.getTuple();
+      if (spatial_tuple == null) {
+        throw new DisplayException("texture with bad tuple: " +
+                                   "ShadowTextureFillSetTypeJ3D.doTransform");
+      }
+      // get spatial index
+      tuple_index[i] = real.getTupleIndex();
+      spatial_value_indices[tuple_index[i]] = map.getValueIndex();
+      spatial_maps[tuple_index[i]] = map;
+      if (maps.hasMoreElements()) {
+        throw new DisplayException("texture with multiple spatial: " +
+                                   "ShadowTextureFillSetTypeJ3D.doTransform");
+      }
+    } // end for (int i=0; i<DC.length; i++)
+    // get spatial index not mapped from domain_set
+    tuple_index[2] = 3 - (tuple_index[0] + tuple_index[1]);
+    DisplayRealType real =
+      (DisplayRealType) spatial_tuple.getComponent(tuple_index[2]);
+    int value2_index = display.getDisplayScalarIndex(real);
+    float value2 = default_values[value2_index];
+    for (int i=0; i<valueArrayLength; i++) {
+      if (inherited_values[i] > 0 &&
+          real.equals(display.getDisplayScalar(valueToScalar[i])) ) {
+        value2 = value_array[i];
+        break;
+      }
+    }
+
+    float[][] spatial_values = new float[3][];
+    spatial_values[tuple_index[0]] = spline_domain[0];
+    spatial_values[tuple_index[1]] = spline_domain[1];
+    spatial_values[tuple_index[2]] = new float[domain_length];
+    for (int i=0; i<domain_length; i++) spatial_values[tuple_index[2]][i] = value2;
+
+    for (int i=0; i<3; i++) {
+      if (spatial_maps[i] != null) {
+        spatial_values[i] = spatial_maps[i].scaleValues(spatial_values[i]);
+      }
+    }
+
+    float scale = ((TextureFillRendererJ3D) renderer).getScale();
+    // compute texture coordinates from Cartesian spatial coordinates
+    float[][] tex_values = new float[3][domain_length];
+    for (int i = 0; i<domain_length; i++) {
+      tex_values[0][i] = scale * spatial_values[tuple_index[0]][i];
+      tex_values[1][i] = scale * spatial_values[tuple_index[1]][i];
+      tex_values[2][i] = scale * spatial_values[tuple_index[2]][i];
+    }
+
+    if (spatial_tuple.equals(Display.DisplaySpatialCartesianTuple)) {
+// inside 'if (anyFlow) {}' in ShadowType.assembleSpatial()
+      renderer.setEarthSpatialDisplay(null, spatial_tuple, display,
+               spatial_value_indices, default_values, null);
+    }
+    else {
+      CoordinateSystem coord = spatial_tuple.getCoordinateSystem();
+      spatial_values = coord.toReference(spatial_values);
+      // float[][] new_spatial_values = coord.toReference(spatial_values);
+      // for (int i=0; i<3; i++) spatial_values[i] = new_spatial_values[i];
+
+// inside 'if (anyFlow) {}' in ShadowType.assembleSpatial()
+      renderer.setEarthSpatialDisplay(coord, spatial_tuple, display,
+               spatial_value_indices, default_values, null);
+    }
+
+    // make spatial set for making VisADGeometryArray using make2DGeometry()
+    SetType type = new SetType(Display.DisplaySpatialCartesianTuple);
+    Set spatial_set = makeSpatialSet(domain_set, type, spatial_values);
+
+    // make spatial set with texture coordinates for making a VisADGeometryArray
+    // so texture coordinates can line up with coordinates
+    Set tex_set = makeSpatialSet(domain_set, type, tex_values);
+
+    boolean indexed = wantIndexed();
+    byte[][] color_values = null;
+    VisADGeometryArray array = spatial_set.make2DGeometry(color_values, indexed);
+    VisADGeometryArray tex_array = tex_set.make2DGeometry(color_values, indexed);
+
+    float[] coordinates = array.coordinates;
+    float[] tex = tex_array.coordinates;
+    int nn = coordinates.length / 3;
+    float[] texCoords = new float[2 * nn];
+    boolean spatial_all_select = true;
+    for (int i=0; i<3*nn; i++) {
+      if (coordinates[i] != coordinates[i]) spatial_all_select = false;
+    }
+    int j = 0;
+    for (int i=0; i<3*nn; i+=3) {
+      texCoords[j] = tex[i];
+      texCoords[j+1] = tex[i+1];
+      j += 2;
+    }
+    array.texCoords = texCoords;
+
+    // do surgery to remove any missing spatial coordinates in texture
+    if (!spatial_all_select) {
+      array = (VisADTriangleStripArray) array.removeMissing();
+    }
+
+    // do surgery along any longitude split (e.g., date line) in texture
+    if (adaptedShadowType.getAdjustProjectionSeam()) {
+      array = (VisADTriangleStripArray) array.adjustLongitude(renderer);
+      array = (VisADTriangleStripArray) array.adjustSeam(renderer);
+    }
+
+// System.out.println("start createImage " + (System.currentTimeMillis() - link.start_time));
+
+    int texture_width = ((TextureFillRendererJ3D) renderer).getTextureWidth();
+    int texture_height = ((TextureFillRendererJ3D) renderer).getTextureHeight();
+    int[] color_ints = ((TextureFillRendererJ3D) renderer).getTexture();
+    // create BufferedImage for texture from color_ints
+    BufferedImage image = createImage(texture_width, texture_height, texture_width,
+                                      texture_height, color_ints);
+
+// System.out.println("start textureToGroup " + (System.currentTimeMillis() - link.start_time));
+
+    // add texture as sub-node of group in scene graph
+    textureToGroup(group, array, image, mode, texture_width, texture_height, renderer);
+
+// System.out.println("end curved texture " + (System.currentTimeMillis() - link.start_time));
+
+
+    ensureNotEmpty(group);
+    return false;
+  }
+
+  public void textureToGroup(Object group, VisADGeometryArray array,
+                            BufferedImage image, GraphicsModeControl mode,
+                            int texture_width, int texture_height,
+                            DataRenderer renderer)
+         throws VisADException {
+    GeometryArray geometry = display.makeGeometry(array);
+    // System.out.println("texture geometry");
+    // create basic Appearance
+    Appearance appearance =
+      makeAppearance(mode, null, null, geometry, false);
+    // create TextureAttributes
+    TextureAttributes texture_attributes = new TextureAttributes();
+
+    // WLH 20 June 2001
+    texture_attributes.setTextureMode(TextureAttributes.REPLACE);
+    // texture_attributes.setTextureMode(TextureAttributes.MODULATE);
+
+    texture_attributes.setPerspectiveCorrectionMode(
+                          TextureAttributes.NICEST);
+    appearance.setTextureAttributes(texture_attributes);
+    // create Texture2D
+// TextureLoader uses 1st argument = 1
+/*
+System.out.println("Texture.BASE_LEVEL = " + Texture.BASE_LEVEL); // 1
+System.out.println("Texture.RGBA = " + Texture.RGBA); // 6
+*/
+    Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                      texture_width, texture_height);
+    texture.setCapability(Texture.ALLOW_IMAGE_READ);
+    ImageComponent2D image2d =
+      new ImageComponent2D(ImageComponent.FORMAT_RGBA, image);
+    image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+    texture.setImage(0, image2d);
+    //
+    // from TextureLoader
+    // TextureLoader uses 3 for both setMinFilter and setMagFilter
+/*
+System.out.println("Texture.FASTEST = " + Texture.FASTEST); // 0
+System.out.println("Texture.NICEST = " + Texture.NICEST); // 1
+System.out.println("Texture.BASE_LEVEL_POINT = " + Texture.BASE_LEVEL_POINT); // 2
+System.out.println("Texture.BASE_LEVEL_LINEAR = " + Texture.BASE_LEVEL_LINEAR); // 3
+*/
+    if (((TextureFillRendererJ3D) renderer).getSmooth()) {
+      // for interpolation:
+      texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+      texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+    }
+    else {
+      // for non-interpolation:
+      texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+      texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+    }
+
+    texture.setBoundaryModeS(Texture.WRAP);
+    texture.setBoundaryModeT(Texture.WRAP);
+    texture.setEnable(true);
+    // end of from TextureLoader
+    //
+    Shape3D shape = new Shape3D(geometry, appearance);
+    shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    appearance.setTexture(texture);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    branch.addChild(shape);
+    if (((Group) group).numChildren() > 0) {
+      ((Group) group).setChild(branch, 0);
+    }
+    else {
+      ((Group) group).addChild(branch);
+    }
+  }
+
+  public BufferedImage createImage(int data_width, int data_height,
+                       int texture_width, int texture_height, int[] color_ints) {
+    BufferedImage image = null;
+    ColorModel colorModel = ColorModel.getRGBdefault();
+    WritableRaster raster =
+      colorModel.createCompatibleWritableRaster(texture_width, texture_height);
+    image = new BufferedImage(colorModel, raster, false, null);
+    int[] intData = ((DataBufferInt)raster.getDataBuffer()).getData();
+    int k = 0;
+    int m = 0;
+    int r, g, b, a;
+    for (int j=0; j<data_height; j++) {
+      for (int i=0; i<data_width; i++) {
+        intData[m++] = color_ints[k++];
+      }
+      for (int i=data_width; i<texture_width; i++) {
+        intData[m++] = 0;
+      }
+    }
+    for (int j=data_height; j<texture_height; j++) {
+      for (int i=0; i<texture_width; i++) {
+        intData[m++] = 0;
+      }
+    }
+    return image;
+  }
+
+}
+
diff --git a/visad/bom/SwellManipulationRendererJ3D.java b/visad/bom/SwellManipulationRendererJ3D.java
new file mode 100644
index 0000000..8921439
--- /dev/null
+++ b/visad/bom/SwellManipulationRendererJ3D.java
@@ -0,0 +1,887 @@
+//
+// SwellManipulationRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+import java.rmi.*;
+
+
+/**
+   SwellManipulationRendererJ3D is the VisAD class for direct
+   manipulation rendering of swells under Java3D
+*/
+public class SwellManipulationRendererJ3D extends BarbManipulationRendererJ3D {
+
+  /** this DataRenderer supports direct manipulation for Tuple
+      representations of wind barbs; two of the Tuple's Real components
+      must be mapped to Flow1X and Flow1Y, or Flow2X and Flow2Y */
+  public SwellManipulationRendererJ3D () {
+    super();
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTupleTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbTupleTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbFunctionTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbSetTypeJ3D(type, link, parent);
+  }
+
+  /** information calculated by checkDirect */
+  /** explanation for invalid use of DirectManipulationRenderer */
+  private String whyNotDirect = null;
+  private final static String notFlatTupleType =
+    "not Flat Tuple";
+  private final static String multipleFlowTuples =
+    "mappings to both Flow1 and Flow2";
+  private final static String multipleFlowMapping =
+    "RealType with multiple flow mappings";
+  private final static String noFlow =
+    "must be RealTypes mapped to flow Azimuth and flow Radial";
+  private final static String nonCartesian =
+    "non-Cartesian spatial mapping";
+
+
+  /** for use in drag_direct */
+  private transient DataDisplayLink link = null;
+  private transient DataReference ref = null;
+  private transient MathType type = null;
+  private transient ShadowTupleType shadow = null;
+
+  private CoordinateSystem coord = null;
+
+  /** point on direct manifold line or plane */
+  private float point_x, point_y, point_z;
+  /** normalized direction of line or perpendicular to plane */
+  private float line_x, line_y, line_z;
+  /** arrays of length one for inverseScaleValues */
+  private float[] f = new float[1];
+  private float[] d = new float[1];
+
+  /** mapping from flow components to Tuple Real components */
+  private int[] flowToComponent = {-1, -1, -1};
+  /** mapping from flow components to ScalarMaps */
+  private ScalarMap[] directMap = {null, null, null};
+
+  /** (barbValues[0], barbValues[1]) = (x, y) barb head location
+      (barbValues[2], barbValues[3]) = (x, y) barb tail location */
+  private float[] barbValues = null;
+  /** which_barb = 0 (Flow1) or 1 (Flow2);
+      redundant with tuple */
+  private int which_barb = -1;
+  /** flow from data when first */
+  private float[] data_flow = {0.0f, 0.0f, 0.0f};
+  /** data and display magnitudes when first */
+  private float data_speed = 0.0f;
+  private float display_speed = 0.0f;
+
+  /** if user adjusts speed, make sure start speed is greater than EPS */
+  private static final float EPS = 0.2f;
+
+  private boolean refirst = false;
+
+  /** pick error offset, communicated from checkClose() to drag_direct() */
+  private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
+  /** count down to decay offset to 0.0 */
+  private int offset_count = 0;
+  /** initial offset_count */
+  private static final int OFFSET_COUNT_INIT = 30;
+
+  public String getWhyNotDirect() {
+    return whyNotDirect;
+  }
+
+  public void checkDirect() throws VisADException, RemoteException {
+    // realCheckDirect();
+    //
+    // must customize
+    setIsDirectManipulation(false);
+
+    DisplayImpl display = getDisplay();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return;
+    }
+    link = Links[0];
+
+    ref = link.getDataReference();
+    type = link.getType();
+    if (!(type instanceof TupleType) || !((TupleType) type).getFlat()) {
+      whyNotDirect = notFlatTupleType;
+      return;
+    }
+    flowToComponent = new int[] {-1, -1, -1};
+    directMap = new ScalarMap[] {null, null, null};
+    shadow = (ShadowTupleType) link.getShadow().getAdaptedShadowType();
+    DisplayTupleType[] tuples = {null};
+    whyNotDirect = findFlow(shadow, display, tuples, flowToComponent);
+    if (whyNotDirect != null) return;
+    if (tuples[0] == null || flowToComponent[1] < 0 || flowToComponent[2] < 0) {
+      whyNotDirect = noFlow;
+      return;
+    }
+
+    ShadowRealType[] components = shadow.getRealComponents();
+    for (int i=0; i<components.length; i++) {
+      DisplayTupleType spatial_tuple = components[i].getDisplaySpatialTuple();
+      if (spatial_tuple != null &&
+          !Display.DisplaySpatialCartesianTuple.equals(spatial_tuple)) {
+        whyNotDirect = nonCartesian;
+        return;
+      }
+    }
+
+    // needs more, will find out when we write drag_direct
+    setIsDirectManipulation(true);
+  }
+
+  /** check for flow mappings;
+      does not allow flow mapping through CoordinateSystem */
+  private String findFlow(ShadowTupleType shadow,
+                          DisplayImpl display, DisplayTupleType[] tuples,
+                          int[] flowToComponent) {
+    ShadowRealType[] components = shadow.getRealComponents();
+    for (int i=0; i<components.length; i++) {
+      int num_flow_per_real = 0;
+      Enumeration maps = components[i].getSelectedMapVector().elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        DisplayRealType dreal = map.getDisplayScalar();
+        DisplayTupleType tuple = dreal.getTuple();
+        if (Display.DisplayFlow1SphericalTuple.equals(tuple) ||
+            Display.DisplayFlow2SphericalTuple.equals(tuple)) {
+          if (tuples[0] != null) {
+            if (!tuples[0].equals(tuple)) {
+              return multipleFlowTuples;
+            }
+          }
+          else {
+            tuples[0] = tuple;
+            coord = tuple.getCoordinateSystem();
+          }
+          num_flow_per_real++;
+          if (num_flow_per_real > 1) {
+            return multipleFlowMapping;
+          }
+          int index = dreal.getTupleIndex();
+          flowToComponent[index] = i;
+          directMap[index] = map;
+        }
+      }
+    }
+    return null;
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    // may need to do this for performance
+  }
+
+  public synchronized void setVectorSpatialValues(float[] mbarb, int which) {
+    // (barbValues[0], barbValues[1]) = (x, y) barb head location
+    // (barbValues[2], barbValues[3]) = (x, y) barb tail location
+    barbValues = mbarb;
+    which_barb = which;
+  }
+
+// methods customized from DataRenderer:
+
+  /** set spatialValues from ShadowType.doTransform */
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    // do nothing - manipulate barb values rather than spatial values
+    // spatialValues = spatial_values;
+  }
+
+  /** find minimum distance from ray to barb tail */
+  public synchronized float checkClose(double[] origin, double[] direction) {
+    if (barbValues == null) return Float.MAX_VALUE;
+    float o_x = (float) origin[0];
+    float o_y = (float) origin[1];
+    float o_z = (float) origin[2];
+    float d_x = (float) direction[0];
+    float d_y = (float) direction[1];
+    float d_z = (float) direction[2];
+/*
+System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
+System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
+*/
+    float x = barbValues[2] - o_x;
+    float y = barbValues[3] - o_y;
+    float z = 0.0f - o_z;
+    float dot = x * d_x + y * d_y + z * d_z;
+    x = x - dot * d_x;
+    y = y - dot * d_y;
+    z = z - dot * d_z;
+
+    offsetx = x;
+    offsety = y;
+    offsetz = z;
+
+    return (float) Math.sqrt(x * x + y * y + z * z); // distance
+  }
+
+  /** mouse button released, ending direct manipulation */
+  public synchronized void release_direct() {
+    // may need to do this for performance
+  }
+
+  public synchronized void drag_direct(VisADRay ray, boolean first,
+                                       int mouseModifiers) {
+    // System.out.println("drag_direct " + first + " " + type);
+    if (barbValues == null || ref == null || shadow == null) return;
+    // modify direction if mshift != 0
+    // modify speed if mctrl != 0
+    // modify speed and direction if neither
+    int mshift = mouseModifiers & InputEvent.SHIFT_MASK;
+    int mctrl = mouseModifiers & InputEvent.CTRL_MASK;
+
+    float o_x = (float) ray.position[0];
+    float o_y = (float) ray.position[1];
+    float o_z = (float) ray.position[2];
+    float d_x = (float) ray.vector[0];
+    float d_y = (float) ray.vector[1];
+    float d_z = (float) ray.vector[2];
+
+    if (pickCrawlToCursor) {
+      if (first) {
+        offset_count = OFFSET_COUNT_INIT;
+      }
+      else {
+        if (offset_count > 0) offset_count--;
+      }
+      if (offset_count > 0) {
+        float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
+        o_x += mult * offsetx;
+        o_y += mult * offsety;
+        o_z += mult * offsetz;
+      }
+    }
+
+    if (first || refirst) {
+      point_x = barbValues[2];
+      point_y = barbValues[3];
+      point_z = 0.0f;
+      line_x = 0.0f;
+      line_y = 0.0f;
+      line_z = 1.0f; // lineAxis == 2 in DataRenderer.drag_direct
+    } // end if (first || refirst)
+
+    float[] x = new float[3]; // x marks the spot
+    // DirectManifoldDimension = 2
+    // intersect ray with plane
+    float dot = (point_x - o_x) * line_x +
+                (point_y - o_y) * line_y +
+                (point_z - o_z) * line_z;
+    float dot2 = d_x * line_x + d_y * line_y + d_z * line_z;
+    if (dot2 == 0.0) return;
+    dot = dot / dot2;
+    // x is intersection
+    x[0] = o_x + dot * d_x;
+    x[1] = o_y + dot * d_y;
+    x[2] = o_z + dot * d_z;
+/*
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+    try {
+
+      Tuple data = (Tuple) link.getData();
+      int n = ((TupleType) data.getType()).getNumberOfRealComponents();
+      Real[] reals = new Real[n];
+
+      int k = 0;
+      int m = data.getDimension();
+      for (int i=0; i<m; i++) {
+        Data component = data.getComponent(i);
+        if (component instanceof Real) {
+          reals[k++] = (Real) component;
+        }
+        else if (component instanceof RealTuple) {
+          for (int j=0; j<((RealTuple) component).getDimension(); j++) {
+            reals[k++] = (Real) ((RealTuple) component).getComponent(j);
+          }
+        }
+      }
+
+      if (first || refirst) {
+        // get first Data flow vector
+        for (int i=0; i<3; i++) {
+          int j = flowToComponent[i];
+          data_flow[i] = (j >= 0) ? (float) reals[j].getValue() : 0.0f;
+        }
+
+        float[][] ds = {{data_flow[0]}, {data_flow[1]}, {data_flow[2]}};
+        ds = coord.toReference(ds);
+        data_flow[0] = ds[0][0];
+        data_flow[1] = ds[1][0];
+        data_flow[2] = ds[2][0];
+
+        data_speed = (float) Math.sqrt(data_flow[0] * data_flow[0] +
+                                       data_flow[1] * data_flow[1] +
+                                       data_flow[2] * data_flow[2]);
+        float barb0 = barbValues[2] - barbValues[0];
+        float barb1 = barbValues[3] - barbValues[1];
+/*
+System.out.println("data_flow = " + data_flow[0] + " " + data_flow[1] +
+                   " " + data_flow[2]);
+System.out.println("barbValues = " + barbValues[0] + " " + barbValues[1] +
+                   "   " + barbValues[2] + " " + barbValues[3]);
+System.out.println("data_speed = " + data_speed);
+*/
+      } // end if (first || refirst)
+
+      // convert x to a flow vector, and from spatial to earth
+      if (getRealVectorTypes(which_barb) instanceof EarthVectorType) {
+        // don't worry about vector magnitude -
+        // data_speed & display_speed take care of that
+        float eps = 0.0001f; // estimate derivative with a little vector
+        float[][] spatial_locs =
+          {{barbValues[0], barbValues[0] + eps * (x[0] - barbValues[0])},
+           {barbValues[1], barbValues[1] + eps * (x[1] - barbValues[1])},
+           {0.0f, 0.0f}};
+/*
+System.out.println("spatial_locs = " + spatial_locs[0][0] + " " +
+                   spatial_locs[0][1] + " " + spatial_locs[1][0] + " " +
+                   spatial_locs[1][1]);
+*/
+        float[][] earth_locs = spatialToEarth(spatial_locs);
+        // WLH - 18 Aug 99
+        if (earth_locs == null) return;
+/*
+System.out.println("earth_locs = " + earth_locs[0][0] + " " +
+                   earth_locs[0][1] + " " + earth_locs[1][0] + " " +
+                   earth_locs[1][1]);
+*/
+        x[2] = 0.0f;
+        x[0] = (earth_locs[1][1] - earth_locs[1][0]) *
+               ((float) Math.cos(Data.DEGREES_TO_RADIANS * earth_locs[0][0]));
+        x[1] = earth_locs[0][1] - earth_locs[0][0];
+/*
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else { // if (!(getRealVectorTypes(which_barb) instanceof EarthVectorType))
+        // convert x to vector
+        x[0] -= barbValues[0];
+        x[1] -= barbValues[1];
+
+        // adjust for spatial map scalings but don't worry about vector
+        // magnitude - data_speed & display_speed take care of that
+        // also, spatial is Cartesian
+        double[] ranges = getRanges();
+        for (int i=0; i<3; i++) {
+          x[i] /= ranges[i];
+        }
+/*
+System.out.println("ranges = " + ranges[0] + " " + ranges[1] +
+                   " " + ranges[2]);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+
+      // WLH 6 August 99
+      x[0] = -x[0];
+      x[1] = -x[1];
+      x[2] = -x[2];
+
+/* may need to do this for performance
+      float[] xx = {x[0], x[1], x[2]};
+      addPoint(xx);
+*/
+
+      float x_speed =
+        (float) Math.sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
+      if (x_speed < 0.000001f) x_speed = 0.000001f;
+      if (first || refirst) {
+        display_speed = x_speed;
+      }
+      refirst = false;
+
+      if (mshift != 0) {
+        // only modify data_flow direction
+        float ratio = data_speed / x_speed;
+        x[0] *= ratio;
+        x[1] *= ratio;
+        x[2] *= ratio;
+/*
+System.out.println("direction, ratio = " + ratio + " " +
+                   data_speed + " " + x_speed);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else if (mctrl != 0) {
+        // only modify data_flow speed
+        float ratio = x_speed / display_speed;
+        if (data_speed < EPS) {
+          data_flow[0] = 2.0f * EPS;
+          refirst = true;
+        }
+        x[0] = ratio * data_flow[0];
+        x[1] = ratio * data_flow[1];
+        x[2] = ratio * data_flow[2];
+/*
+System.out.println("speed, ratio = " + ratio + " " +
+                   x_speed + " " + display_speed);
+System.out.println("x = " + x[0] + " " + x[1] + " " + x[2]);
+*/
+      }
+      else {
+        // modify data_flow speed and direction
+        float ratio = data_speed / display_speed;
+        if (data_speed < EPS) {
+          data_flow[0] = 2.0f * EPS;
+          x[0] = data_flow[0];
+          x[1] = data_flow[1];
+          x[2] = data_flow[2];
+          refirst = true;
+        }
+        else {
+          x[0] *= ratio;
+          x[1] *= ratio;
+          x[2] *= ratio;
+        }
+      }
+
+      float[][] xs = {{x[0]}, {x[1]}, {x[2]}};
+      xs = coord.fromReference(xs);
+      x[0] = xs[0][0];
+      x[1] = xs[1][0];
+      x[2] = xs[2][0];
+
+      // now replace flow values
+      Vector vect = new Vector();
+      for (int i=0; i<3; i++) {
+        int j = flowToComponent[i];
+        if (j >= 0) {
+          RealType rtype = (RealType) reals[j].getType();
+          reals[j] = new Real(rtype, (double) x[i], rtype.getDefaultUnit(), null);
+
+          // WLH 31 Aug 2000
+          Real r = reals[j];
+          Unit overrideUnit = null; 
+          if (directMap[i] != null) {
+            overrideUnit = directMap[i].getOverrideUnit();
+          }
+          Unit rtunit = rtype.getDefaultUnit();
+          // units not part of Time string
+          if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
+              !RealType.Time.equals(rtype)) {
+            double d = (float) overrideUnit.toThis((double) x[0], rtunit);
+            r = new Real(rtype, d, overrideUnit);
+            String valueString = r.toValueString();
+            vect.addElement(rtype.getName() + " = " + valueString);
+          }
+          else {
+            // create location string
+            vect.addElement(rtype.getName() + " = " + x[i]);
+          }
+
+        }
+      }
+      getDisplayRenderer().setCursorStringVector(vect);
+
+      Data newData = null;
+      // now build new RealTuple or Flat Tuple
+      if (data instanceof RealTuple) {
+        newData = new RealTuple(((RealTupleType) data.getType()), reals,
+                                ((RealTuple) data).getCoordinateSystem());
+      }
+      else {
+        Data[] new_components = new Data[m];
+        k = 0;
+        for (int i=0; i<m; i++) {
+          Data component = data.getComponent(i);
+          if (component instanceof Real) {
+            new_components[i] = reals[k++];
+          }
+          else if (component instanceof RealTuple) {
+            Real[] sub_reals = new Real[((RealTuple) component).getDimension()];
+            for (int j=0; j<((RealTuple) component).getDimension(); j++) {
+              sub_reals[j] = reals[k++];
+            }
+            new_components[i] =
+              new RealTuple(((RealTupleType) component.getType()), sub_reals,
+                            ((RealTuple) component).getCoordinateSystem());
+          }
+        }
+        newData = new Tuple(new_components, false);
+      }
+      ref.setData(newData);
+    }
+    catch (VisADException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      // do nothing
+      System.out.println("drag_direct " + e);
+      e.printStackTrace();
+    }
+  }
+
+  /** draw swell, f0 and f1 in meters */
+  public float[] makeVector(boolean south, float x, float y, float z,
+                          float scale, float pt_size, float f0, float f1,
+                          float[] vx, float[] vy, float[] vz, int[] numv,
+                          float[] tx, float[] ty, float[] tz, int[] numt) {
+
+    float d, xd, yd;
+    float x0, y0, x1, y1, x2, y2, x3, y3, x4, y4;
+    float sscale = 0.75f * scale;
+
+    float[] mbarb = new float[4];
+    mbarb[0] = x;
+    mbarb[1] = y;
+
+    float swell_height = (float) Math.sqrt(f0 * f0 + f1 * f1);
+
+    int lenv = vx.length;
+    int nv = numv[0];
+
+    //determine the initial (minimum) length of the flag pole
+    if (swell_height >= 0.1f) {
+      // normalize direction
+      x0 = -f0 / swell_height;
+      y0 = -f1 / swell_height;
+
+      float start_arrow = 0.9f * sscale;
+      float end_arrow = 1.9f * sscale;
+      float arrow_head = 0.3f * sscale;
+      x1 = (x + x0 * start_arrow);
+      y1 = (y + y0 * start_arrow);
+      x2 = (x + x0 * end_arrow);
+      y2 = (y + y0 * end_arrow);
+
+      // draw arrow shaft
+      vx[nv] = x1;
+      vy[nv] = y1;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x2;
+      vy[nv] = y2;
+      vz[nv] = z;
+      nv++;
+
+      mbarb[2] = x2;
+      mbarb[3] = y2;
+
+      xd = x2 - x1;
+      yd = y2 - y1;
+
+      x3 = x2 - 0.3f * (xd - yd);
+      y3 = y2 - 0.3f * (yd + xd);
+      x4 = x2 - 0.3f * (xd + yd);
+      y4 = y2 - 0.3f * (yd - xd);
+
+      // draw arrow head
+      vx[nv] = x2;
+      vy[nv] = y2;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x3;
+      vy[nv] = y3;
+      vz[nv] = z;
+      nv++;
+
+      vx[nv] = x2;
+      vy[nv] = y2;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x4;
+      vy[nv] = y4;
+      vz[nv] = z;
+      nv++;
+
+      // DRM 2001-07-04
+      //int shi = (int) (10.0f * (swell_height + 0.5f));
+      int shi = Math.round(10.0f * (swell_height));
+      float shf = 0.1f * shi;
+      String sh_string = Float.toString(shf);
+      int point = sh_string.indexOf('.');
+      sh_string = sh_string.substring(0, point + 2);
+      // grf 2 Jun 2004 set z value the same as the barb
+      double[] start = {x, y - 0.25 * sscale, z};
+      double[] base = {0.5 * sscale, 0.0, 0.0};
+      double[] up = {0.0, 0.5 * sscale, 0.0};
+      VisADLineArray array =
+        PlotText.render_label(sh_string, start, base, up, true);
+      int nl = array.vertexCount;
+      int k = 0;
+      for (int i=0; i<nl; i++) {
+        vx[nv] = array.coordinates[k++];
+        vy[nv] = array.coordinates[k++];
+        vz[nv] = array.coordinates[k++];
+        nv++;
+      }
+    }
+    else { // if (swell_height < 0.1)
+
+      // wind < 2.5 kts.  Plot a circle
+      float rad = (0.7f * pt_size);
+
+      // draw 8 segment circle, center = (x, y), radius = rad
+      // 1st segment
+      vx[nv] = x - rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 2nd segment
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x;
+      vy[nv] = y + rad;
+      vz[nv] = z;
+      nv++;
+      // 3rd segment
+      vx[nv] = x;
+      vy[nv] = y + rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 4th segment
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y + 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x + rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      // 5th segment
+      vx[nv] = x + rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 6th segment
+      vx[nv] = x + 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x;
+      vy[nv] = y - rad;
+      vz[nv] = z;
+      nv++;
+      // 7th segment
+      vx[nv] = x;
+      vy[nv] = y - rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      // 8th segment
+      vx[nv] = x - 0.7f * rad;
+      vy[nv] = y - 0.7f * rad;
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x - rad;
+      vy[nv] = y;
+      vz[nv] = z;
+      nv++;
+// System.out.println("circle " + x + " " + y + "" + rad);
+      mbarb[2] = x;
+      mbarb[3] = y;
+    }
+
+    numv[0] = nv;
+    return mbarb;
+  }
+
+  public Object clone() {
+    return new SwellManipulationRendererJ3D();
+  }
+
+  static final int N = 5;
+
+  /** test SwellManipulationRendererJ3D */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    // construct RealTypes for swell record components
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+    RealType swell_degree = RealType.getRealType("swell_degree",
+                          CommonUnit.degree);
+    RealType swell_height = RealType.getRealType("swell_height",
+                          CommonUnit.meter);
+
+    // construct Java3D display and mappings that govern
+    // how swell records are displayed
+    DisplayImpl display =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap lonmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(lonmap);
+    ScalarMap latmap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(latmap);
+    ScalarMap swella_map = new ScalarMap(swell_degree, Display.Flow1Azimuth);
+    display.addMap(swella_map);
+    swella_map.setRange(0.0, 360.0); // do this for swell rendering
+    ScalarMap swellh_map = new ScalarMap(swell_height, Display.Flow1Radial);
+    display.addMap(swellh_map);
+    swellh_map.setRange(0.0, 1.0); // do this for swell rendering
+    FlowControl flow_control = (FlowControl) swellh_map.getControl();
+    flow_control.setFlowScale(0.15f); // this controls size of barbs
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    DataReferenceImpl[] refs = new DataReferenceImpl[N * N];
+    int k = 0;
+    // create an array of N by N swells
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        double u = 2.0 * i / (N - 1.0) - 1.0;
+        double v = 2.0 * j / (N - 1.0) - 1.0;
+
+        double fx = 30.0 * u;
+        double fy = 30.0 * v;
+        double fa = Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy);
+        double fh = Math.sqrt(fx * fx + fy * fy);
+
+        // each swell record is a RealTuple (lon, lat,
+        //   swell_degree, swell_height, red, green)
+        // set colors by swell components, just for grins
+        RealTuple tuple = new RealTuple(new Real[]
+          {new Real(lon, 10.0 * u), new Real(lat, 10.0 * v - 40.0),
+           new Real(swell_degree, fa), new Real(swell_height, fh),
+           new Real(red, u), new Real(green, v)});
+
+        // construct reference for swell record
+        refs[k] = new DataReferenceImpl("ref_" + k);
+        refs[k].setData(tuple);
+
+        // link swell record to display via SwellManipulationRendererJ3D
+        // so user can change barb by dragging it
+        // drag with right mouse button and shift to change direction
+        // drag with right mouse button and no shift to change speed
+        SwellManipulationRendererJ3D renderer =
+          new SwellManipulationRendererJ3D();
+        display.addReferences(renderer, refs[k]);
+
+        // link swell record to a CellImpl that will listen for changes
+        // and print them
+        SwellGetterJ3D cell = new SwellGetterJ3D(flow_control, refs[k]);
+        cell.addReference(refs[k]);
+
+        k++;
+      }
+    }
+
+    // instead of linking the wind record "DataReferenceImpl refs" to
+    // the SwellGetterJ3Ds, you can have some user interface event (e.g.,
+    // the user clicks on "DONE") trigger code that does a getData() on
+    // all the refs and stores the records in a file.
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test SwellManipulationRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+}
+
+class SwellGetterJ3D extends CellImpl {
+  DataReferenceImpl ref;
+
+  float scale = 0.15f;
+  int count = 20;
+  FlowControl flow_control;
+
+  public SwellGetterJ3D(FlowControl f, DataReferenceImpl r) {
+    ref = r;
+    flow_control = f;
+  }
+
+  public void doAction() throws VisADException, RemoteException {
+    RealTuple tuple = (RealTuple) ref.getData();
+    float lon = (float) ((Real) tuple.getComponent(0)).getValue();
+    float lat = (float) ((Real) tuple.getComponent(1)).getValue();
+    float dir = (float) ((Real) tuple.getComponent(2)).getValue();
+    float height = (float) ((Real) tuple.getComponent(3)).getValue();
+    System.out.println("swell = (" + dir + ", " + height + ") at (" +
+                       + lat + ", " + lon +")");
+  }
+
+}
+
diff --git a/visad/bom/SwellRendererJ3D.java b/visad/bom/SwellRendererJ3D.java
new file mode 100644
index 0000000..bb7bb36
--- /dev/null
+++ b/visad/bom/SwellRendererJ3D.java
@@ -0,0 +1,232 @@
+//
+// SwellRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.*;
+
+
+/**
+   SwellRendererJ3D is the VisAD class for rendering of
+   wind barbs under Java3D - otherwise it behaves just
+   like DefaultRendererJ3D
+*/
+public class SwellRendererJ3D extends DefaultRendererJ3D
+       implements BarbRenderer {
+
+  private SwellManipulationRendererJ3D smr;
+
+  /** this DataRenderer supports direct manipulation for RealTuple
+      representations of wind barbs; four of the RealTuple's Real
+      components must be mapped to XAxis, YAxis, Flow1X and Flow1Y */
+  public SwellRendererJ3D () {
+    super();
+    smr = new SwellManipulationRendererJ3D();
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbFunctionTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTupleTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbRealTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbSetTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowBarbTupleTypeJ3D(type, link, parent);
+  }
+
+  public void setKnotsConvert(boolean enable) {
+    smr.setKnotsConvert(enable);
+  }
+
+  public boolean getKnotsConvert() {
+    return smr.getKnotsConvert();
+  }
+
+  public float[] makeVector(boolean south, float x, float y, float z,
+                          float scale, float pt_size, float f0, float f1,
+                          float[] vx, float[] vy, float[] vz, int[] numv,
+                          float[] tx, float[] ty, float[] tz, int[] numt) { 
+    return smr.makeVector(south, x, y, z, scale, pt_size, f0, f1, vx, vy, vz,
+                          numv, tx, ty, tz, numt);
+  }
+
+  public Object clone() {
+    return new SwellRendererJ3D();
+  }
+
+  static final int N = 5;
+
+  /** run 'java visad.bom.SwellRendererJ3D middle_latitude'
+          to test with Cartesian winds
+      run 'java visad.bom.SwellRendererJ3D middle_latitude x'
+          to test with polar winds
+      adjust middle_latitude for south or north barbs */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    double mid_lat = -10.0;
+    if (args.length > 0) {
+      try {
+        mid_lat = Double.valueOf(args[0]).doubleValue();
+      }
+      catch(NumberFormatException e) { }
+    }
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType flowx = RealType.getRealType("flowx",
+                          CommonUnit.meterPerSecond);
+    RealType flowy = RealType.getRealType("flowy",
+                          CommonUnit.meterPerSecond);
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+    RealType index = RealType.getRealType("index");
+    EarthVectorType flowxy = new EarthVectorType(flowx, flowy);
+    TupleType range = null;
+    RealType flow_degree = RealType.getRealType("flow_degree",
+                          CommonUnit.degree);
+    RealType flow_speed = RealType.getRealType("flow_speed",
+                          CommonUnit.meterPerSecond);
+    if (args.length > 1) {
+      System.out.println("polar winds");
+      RealTupleType flowds =
+        new RealTupleType(new RealType[] {flow_degree, flow_speed},
+        new WindPolarCoordinateSystem(flowxy), null);
+      range = new TupleType(new MathType[] {lon, lat, flowds, red, green});
+    }
+    else {
+      System.out.println("Cartesian winds");
+      range = new TupleType(new MathType[] {lon, lat, flowxy, red, green});
+    }
+    FunctionType flow_field = new FunctionType(index, range);
+
+    DisplayImpl display = new DisplayImplJ3D("display1");
+    ScalarMap xmap = new ScalarMap(lon, Display.XAxis);
+    display.addMap(xmap);
+    ScalarMap ymap = new ScalarMap(lat, Display.YAxis);
+    display.addMap(ymap);
+    if (args.length > 1) {
+      ScalarMap flowd_map = new ScalarMap(flow_degree, Display.Flow1Azimuth);
+      display.addMap(flowd_map);
+      flowd_map.setRange(0.0, 360.0);
+      ScalarMap flows_map = new ScalarMap(flow_speed, Display.Flow1Radial);
+      display.addMap(flows_map);
+      flows_map.setRange(0.0, 1.0);
+      FlowControl flow_control = (FlowControl) flows_map.getControl();
+      flow_control.setFlowScale(0.1f);
+    }
+    else {
+      ScalarMap flowx_map = new ScalarMap(flowx, Display.Flow1X);
+      display.addMap(flowx_map);
+      flowx_map.setRange(-1.0, 1.0);
+      ScalarMap flowy_map = new ScalarMap(flowy, Display.Flow1Y);
+      display.addMap(flowy_map);
+      flowy_map.setRange(-1.0, 1.0);
+      FlowControl flow_control = (FlowControl) flowy_map.getControl();
+      flow_control.setFlowScale(0.1f);
+    }
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    Integer1DSet set = new Integer1DSet(N * N);
+    double[][] values = new double[6][N * N];
+    int m = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        double u = 2.0 * i / (N - 1.0) - 1.0;
+        double v = 2.0 * j / (N - 1.0) - 1.0;
+        values[0][m] = 10.0 * u;
+        values[1][m] = 10.0 * v + mid_lat;
+        double fx = 30.0 * u;
+        double fy = 30.0 * v;
+        if (args.length > 1) {
+          values[2][m] =
+            Data.RADIANS_TO_DEGREES * Math.atan2(-fx, -fy);
+          values[3][m] = Math.sqrt(fx * fx + fy * fy);
+        }
+        else {
+          values[2][m] = fx;
+          values[3][m] = fy;
+        }
+        values[4][m] = u;
+        values[5][m] = v;
+        m++;
+      }
+    }
+    FlatField field = new FlatField(flow_field, set);
+    field.setSamples(values);
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(field);
+    SwellRendererJ3D renderer = new SwellRendererJ3D();
+    display.addReferences(renderer, ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test SwellRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/bom/Swells.java b/visad/bom/Swells.java
new file mode 100644
index 0000000..89a5ac1
--- /dev/null
+++ b/visad/bom/Swells.java
@@ -0,0 +1,191 @@
+
+//
+// Swells.java
+//
+
+/*
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/**
+ * Swells class for setting up display of swells
+ */
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.RemoteException;
+
+public class Swells extends Exception {
+
+  private static final int NDIRS = 721;
+  private static final int NHEIGHTS = 51;
+
+  // size scale for shapes
+  private static final float SIZE = 0.05f;
+
+  private static WindPolarCoordinateSystem wcs = null;
+  // four points of a zero-degree arrow in polar coordinates
+  private static float[][] arrow_zero =
+    {{0.0f, 0.0f, 7.0f, -7.0f}, {0.9f*SIZE, 1.9f*SIZE, 1.6f*SIZE, 1.6f*SIZE}};
+
+  /** set up ScalarMaps from swellDir and swellHeight to Display.Shape
+      in display; swellDir default Unit must be degree and swellHeight
+      default Unit must be meter */
+  public static void setupSwellDisplay(RealType swellDir, RealType swellHeight,
+                DisplayImpl display) throws VisADException, RemoteException {
+
+    if (wcs == null) wcs = new WindPolarCoordinateSystem();
+
+    if (!CommonUnit.degree.equals(swellDir.getDefaultUnit())) {
+      throw new UnitException("swellDir Unit must be degree");
+    }
+    if (!CommonUnit.meter.equals(swellHeight.getDefaultUnit())) {
+      throw new UnitException("swellHeight Unit must be meter");
+    }
+    // construct dir_set and dir_shapes
+    Linear1DSet dir_set = new Linear1DSet(swellDir, -360.0, 360.0, NDIRS);
+    float[][] dirs = dir_set.getSamples();
+    VisADGeometryArray[] dir_shapes = new VisADGeometryArray[NDIRS];
+    for (int i=0; i<NDIRS; i++) {
+      dir_shapes[i] = new VisADLineArray();
+      dir_shapes[i].vertexCount = 6;
+      // copy arrow at zero degrees
+      float[][] arrow = new float[2][4];
+      System.arraycopy(arrow_zero[0], 0, arrow[0], 0, 4);
+      System.arraycopy(arrow_zero[1], 0, arrow[1], 0, 4);
+      // rotate arrow by dirs[0][i] degrees
+      for (int j=0; j<arrow[0].length; j++) {
+        arrow[0][j] += (dirs[0][i] + 180.0f); // WLH 12 April 2000 - 180 out
+      }
+      // convert arrow from polar to cartesian
+      arrow = wcs.toReference(arrow);
+      // draw arrow as three line segments
+      dir_shapes[i].coordinates = new float[]
+        {arrow[0][1], arrow[1][1], 0.0f,
+         arrow[0][0], arrow[1][0], 0.0f,
+         arrow[0][1], arrow[1][1], 0.0f,
+         arrow[0][2], arrow[1][2], 0.0f,
+         arrow[0][1], arrow[1][1], 0.0f,
+         arrow[0][3], arrow[1][3], 0.0f};
+    }
+
+    // construct height_set and height_shapes
+    Integer1DSet height_set = new Integer1DSet(swellHeight, NHEIGHTS);
+    double[] start = {0.0, -0.5*SIZE, 0.0};
+    double[] base = {SIZE, 0.0, 0.0};
+    double[] up = {0.0, SIZE, 0.0};
+    VisADGeometryArray[] height_shapes = new VisADGeometryArray[NHEIGHTS];
+    for (int i=0; i<NHEIGHTS; i++) {
+      height_shapes[i] =
+        PlotText.render_label(Integer.toString(i), start, base, up, true);
+    }
+
+    ScalarMap dir_map = new ScalarMap(swellDir, Display.Shape);
+    display.addMap(dir_map);
+    ShapeControl dir_control = (ShapeControl) dir_map.getControl();
+    dir_control.setShapeSet(dir_set);
+    dir_control.setShapes(dir_shapes);
+
+    ScalarMap height_map = new ScalarMap(swellHeight, Display.Shape);
+    display.addMap(height_map);
+    ShapeControl height_control = (ShapeControl) height_map.getControl();
+    height_control.setShapeSet(height_set);
+    height_control.setShapes(height_shapes);
+
+    return;
+  }
+
+  static final int N = 5;
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    double mid_lat = -30.0;
+    RealType lat = RealType.Latitude;
+    RealType lon = RealType.Longitude;
+    RealType red = RealType.getRealType("red");
+    RealType green = RealType.getRealType("green");
+    RealType index = RealType.getRealType("index");
+    RealType swell_dir = RealType.getRealType("swell_dir",
+                            CommonUnit.degree);
+    RealType swell_height = RealType.getRealType("swell_speed",
+                            CommonUnit.meter);
+    RealTupleType range =
+      new RealTupleType(new RealType[] {lon, lat, swell_dir,
+                                        swell_height, red, green});
+    FunctionType swell_field = new FunctionType(index, range);
+
+    DisplayImpl display = new DisplayImplJ3D("display1");
+    display.addMap(new ScalarMap(lon, Display.XAxis));
+    display.addMap(new ScalarMap(lat, Display.YAxis));
+    display.addMap(new ScalarMap(red, Display.Red));
+    display.addMap(new ScalarMap(green, Display.Green));
+    display.addMap(new ConstantMap(1.0, Display.Blue));
+
+    setupSwellDisplay(swell_dir, swell_height, display);
+
+    Integer1DSet set = new Integer1DSet(N * N);
+    double[][] values = new double[6][N * N];
+    int m = 0;
+    for (int i=0; i<N; i++) {
+      for (int j=0; j<N; j++) {
+        double u = 2.0 * i / (N - 1.0) - 1.0;
+        double v = 2.0 * j / (N - 1.0) - 1.0;
+        values[0][m] = 10.0 * u;
+        values[1][m] = 10.0 * v + mid_lat;
+        // double sx = 30.0 * u;
+        // double sy = 30.0 * v;
+        double sx = 10.0 * u;
+        double sy = 10.0 * v;
+        values[2][m] = Data.RADIANS_TO_DEGREES * Math.atan2(sx, sy);
+        values[3][m] = 1.0 + Math.sqrt(sx * sx + sy * sy);
+        values[4][m] = u;
+        values[5][m] = v;
+        m++;
+      }
+    }
+    FlatField field = new FlatField(swell_field, set);
+    field.setSamples(values);
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(field);
+    display.addReference(ref);
+
+    // create JFrame (i.e., a window) for display
+    JFrame frame = new JFrame("test BarbRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
diff --git a/visad/bom/TCData.java b/visad/bom/TCData.java
new file mode 100644
index 0000000..8c01c28
--- /dev/null
+++ b/visad/bom/TCData.java
@@ -0,0 +1,718 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+//
+// TCData.java
+//
+// Version 3 April 2001
+
+package visad.bom;
+
+import visad.*;
+import visad.util.Util;
+import java.rmi.RemoteException;
+
+public class TCData {
+
+  // this is the actual TC data object
+  FieldImpl data = null;
+
+  // Time
+  static RealType rtTime = RealType.Time;
+
+  // Location
+  static RealType rtConfidence;
+  static RealType rtLat;
+  static RealType rtLon;
+  static RealType rtError;
+  static RealType rtLocationStyle;
+  static RealTupleType locationTuple;
+  static FunctionType locationFunction;
+
+  // Intensity
+  static RealType rtWindMean;
+  static RealType rtWindGust;
+  static RealType rtCentralPressure;
+  static RealType rtCategory;
+  static RealType rtIntensityStyle;
+
+  // Size
+  static RealType rtGaleRadius;
+  static RealType rtStormRadius;
+  static RealType rtHurricaneRadius;
+  static RealType rtRadiusOfMaximumWinds;
+  static RealType rtSizeStyle;
+
+  // Structure
+  static RealType rtDepth;
+  static RealType rtEyeDiameter;
+  static RealType rtPressureOfLastClosedIsobar;
+  static RealType rtStructureStyle;
+
+  // Track
+  static RealType rtTrackID;
+  static TextType ttTrackType;
+  static TextType ttTrackName;
+  static RealType rtBaseDateTime;
+  static RealType rtCreateDateTime;
+  static TextType ttTrackStyle;
+  static TupleType ttTrack;
+  static FunctionType ftId2Track;
+
+  // Disturbance
+  static RealType rtDisturbanceID;
+  static TextType ttCountry;
+  static TextType ttState;
+  static RealType rtYear;
+  static RealType rtNumber;
+  static TextType ttHistoricalName;
+  static RealType rtOpenDate;
+  static RealType rtCloseDate;
+  static RealType rtArchiveMode;
+  static RealType rtRealtimeMode;
+  static TupleType ttDisturbance;
+  static FunctionType ftId2Disturbance;
+
+  static FunctionType mtTC;
+
+
+  public TCData() throws VisADException {
+    if (mtTC == null) {
+      rtTime = RealType.Time;
+
+      // Location
+      rtConfidence = RealType.getRealType("CONFIDENCE", null, null);
+      rtLat = RealType.Latitude;
+      rtLon = RealType.Longitude;
+      rtError = RealType.getRealType("ERROR", null, null);
+      rtLocationStyle = RealType.getRealType("LOCATIONSTYLE", null, null);
+
+      // Intensity
+      rtWindMean = RealType.getRealType("WINDMEAN", null, null);
+      rtWindGust = RealType.getRealType("WINDGUST", null, null);
+      rtCentralPressure = RealType.getRealType("CENTRALPRESSURE", null, null);
+      rtCategory = RealType.getRealType("CATEGORY", null, null);
+      rtIntensityStyle = RealType.getRealType("INTENSITYSTYLE", null, null);
+
+      // Size
+      rtGaleRadius = RealType.getRealType("GALERADIUS", null, null);
+      rtStormRadius = RealType.getRealType("STORMRADIUS", null, null);
+      rtHurricaneRadius = RealType.getRealType("HURRICANERADIUS", null, null);
+      rtRadiusOfMaximumWinds = RealType.getRealType("RADIUSOFMAXIMUMWINDS", null, null);
+      rtSizeStyle = RealType.getRealType("SIZESTYLE", null, null);
+
+      // Structure
+      rtDepth = RealType.getRealType("DEPTH", null, null);
+      rtEyeDiameter = RealType.getRealType("EYEDIAMETER", null, null);
+      rtPressureOfLastClosedIsobar = RealType.getRealType("PRESSUREOFLASTCLOSEDISOBAR", null, null);
+      rtStructureStyle = RealType.getRealType("STRUCTURESTYLE", null, null);
+
+
+      RealTupleType locationTuple = new RealTupleType(new RealType[]
+       {rtLat, rtLon, rtError, rtConfidence, rtLocationStyle,
+        rtWindMean, rtWindGust, rtCentralPressure, rtCategory, rtIntensityStyle,
+        rtGaleRadius, rtStormRadius, rtHurricaneRadius, rtRadiusOfMaximumWinds, rtSizeStyle,
+        rtDepth, rtEyeDiameter, rtPressureOfLastClosedIsobar, rtStructureStyle });
+      locationFunction = new FunctionType(rtTime, locationTuple);
+
+
+      // Track
+      rtTrackID = RealType.getRealType("TRACKID", null, null);
+      ttTrackType = TextType.getTextType("TRACKTYPE");
+      ttTrackName = TextType.getTextType("TRACKNAME");
+      rtBaseDateTime = RealType.getRealType("BASEDATETIME", null, null);
+      rtCreateDateTime = RealType.getRealType("CREATEDATETIME", null, null);
+      ttTrackStyle = TextType.getTextType("TRACKSTYLE");
+      ttTrack = new TupleType(new MathType[]
+        {ttTrackType, ttTrackName, rtBaseDateTime, rtCreateDateTime,
+         ttTrackStyle, locationFunction});
+      ftId2Track = new FunctionType(rtTrackID, ttTrack);
+
+      // Disturbance
+      rtDisturbanceID = RealType.getRealType("DISTURBANCEID", null, null);
+      ttCountry = TextType.getTextType("COUNTRY");
+      ttState = TextType.getTextType("STATE");
+      ttHistoricalName = TextType.getTextType("HISTORICALNAME");
+      rtYear = RealType.getRealType("YEAR", null, null);
+      rtNumber = RealType.getRealType("NUM", null, null);
+      rtOpenDate = RealType.getRealType("OPENDATE", null, null);
+      rtCloseDate = RealType.getRealType("CLOSEDATE", null, null);
+      rtArchiveMode = RealType.getRealType("ARCHIVEMODE", null, null);
+      rtRealtimeMode = RealType.getRealType("REALTIMEMODE", null, null);
+      TupleType ttDisturbance = new TupleType(new MathType[]
+        {ttCountry, ttState, rtYear, rtNumber, ttHistoricalName,
+         rtOpenDate, rtCloseDate, rtArchiveMode, rtRealtimeMode, ftId2Track});
+      FunctionType ftId2Disturbance =
+        new FunctionType(rtDisturbanceID, ttDisturbance);
+
+      mtTC = ftId2Disturbance;
+    }
+  }
+
+  public FieldImpl getData() {
+    return data;
+  }
+
+  public MathType getType() {
+    return mtTC;
+  }
+
+  public synchronized void addLocation(int disturbanceID, int trackID, double time,
+                                  RealTuple location)
+         throws VisADException, RemoteException {
+    addToTrack(disturbanceID, trackID, time, 5, locationFunction, location);
+  }
+/*
+  public synchronized void addIntensity(int disturbanceID, int trackID, double time,
+                                        RealTuple intensity)
+         throws VisADException, RemoteException {
+    addToTrack(disturbanceID, trackID, time, 6, intensityFunction, intensity);
+  }
+
+  public synchronized void addSize(int disturbanceID, int trackID, double time,
+                                   RealTuple size)
+         throws VisADException, RemoteException {
+    addToTrack(disturbanceID, trackID, time, 7, sizeFunction, size);
+  }
+
+  public synchronized void addSteering(int disturbanceID, int trackID, double time,
+                                       RealTuple steering)
+         throws VisADException, RemoteException {
+    addToTrack(disturbanceID, trackID, time, 8, steeringFunction, steering);
+  }
+*/
+  private void addToTrack(int disturbanceID, int trackID, double time,
+                          int tuple_index, FunctionType function_type,
+                          RealTuple rt)
+         throws VisADException, RemoteException {
+
+    Tuple disturbance = getDisturbance(disturbanceID);
+    if (disturbance == null) {
+      throw new VisADException("invalid disturbanceID");
+    }
+    Tuple track = getTrack(trackID, disturbance);
+    if (track == null) {
+      throw new VisADException("invalid trackID");
+    }
+    FlatField field = (FlatField) track.getComponent(tuple_index);
+    Gridded1DDoubleSet set = (Gridded1DDoubleSet) field.getDomainSet();
+    double[][] times = set.getDoubles(false);
+    int length = set.getLength();
+    double[][] new_times = new double[1][length + 1];
+    float[][] values = field.getFloats(false);
+    int dim = values.length;
+    float[][] new_values = new float[dim][length + 1];
+    int k = 0;
+    int m = -1;
+    for (int i=0; i<length+1; i++) {
+      if (Util.isApproximatelyEqual(time, times[0][k])) {
+        throw new VisADException("time " + time + " already used");
+      }
+      else if (m < 0 && time < times[0][k]) {
+        new_times[0][i] = time;
+        // mark as missing until new_field.setSample(m, rt) call
+        for (int j=0; j<dim; j++) new_values[j][i] = Float.NaN;
+        m = i;
+      }
+      else {
+        new_times[0][i] = times[0][k];
+        for (int j=0; j<dim; j++) new_values[j][i] = values[j][k];
+        k++;
+      }
+    }
+    Gridded1DDoubleSet new_set = 
+      new Gridded1DDoubleSet(rtTime, new_times, length + 1);
+    FlatField new_field = new FlatField(function_type, new_set);
+    new_field.setSamples(new_values, false);
+    new_field.setSample(m, rt);
+
+    Data[] comps = new Data[]
+      {track.getComponent(0),
+       track.getComponent(1),
+       track.getComponent(2),
+       track.getComponent(3),
+       track.getComponent(4),
+       track.getComponent(5)};
+//       track.getComponent(6),
+//       track.getComponent(7),
+//       track.getComponent(8)};
+    comps[tuple_index] = new_field;
+    Tuple new_track = new Tuple(new Data[]
+    {comps[0], comps[1], comps[2], comps[3], comps[4], comps[5]});
+       // comps[6], comps[7], comps[8]});
+    setTrack(trackID, new_track, disturbance);
+    setDisturbance(disturbanceID, disturbance);
+  }
+
+  public static FieldImpl makeTrackField(int trackID, Tuple track)
+         throws VisADException, RemoteException {
+
+    float fid = (float) trackID;
+
+    Gridded1DSet set =
+      new Gridded1DSet(rtTrackID, new float[][] {{fid}}, 1);
+    FieldImpl field = new FieldImpl(ftId2Track, set);
+    //au.gov.bom.fdb.debug.Debug.println(field);
+    //au.gov.bom.fdb.debug.Debug.println(track);
+    field.setSample(0, track);
+    return field;
+  }
+
+
+  public synchronized void addTrack(int disturbanceID, int trackID, Tuple track)
+         throws VisADException, RemoteException {
+    Tuple disturbance = getDisturbance(disturbanceID);
+    if (disturbance == null) {
+      throw new VisADException("invalid disturbanceID");
+    }
+
+    FieldImpl field = (FieldImpl) disturbance.getComponent(9);
+
+    // desired field has MathType ftId2Track;
+    // now we want to add a particular track to this field
+    // in an analagous manner to adding a disturbance (ie the addDisturbance method)
+
+    /* wlh comments:
+    // not necessary since field is mutable
+    // so merge find* methods into get* methods and eliminate set* methods ****
+    // setDisturbance(disturbanceID, disturbance);
+    */
+
+    float fid = (float) trackID;
+    FieldImpl new_field = null;
+    if (field == null) {
+      Gridded1DSet set =
+        new Gridded1DSet(rtTrackID, new float[][] {{fid}}, 1);
+      new_field = new FieldImpl(ftId2Track, set);
+      new_field.setSample(0, track);
+    }
+    else {
+      Gridded1DSet set = (Gridded1DSet) field.getDomainSet();
+      float[][] ids = set.getSamples(false);
+      int length = set.getLength();
+      float[][] new_ids = new float[1][length + 1];
+      int k = 0;
+      int m = -1;
+      for (int i=0; i<length+1; i++) {
+        if (fid == ids[0][k]) {
+          throw new VisADException("trackID " + trackID +
+                                   " already used");
+        }
+        else if (m < 0 && fid < ids[0][k]) {
+          new_ids[0][i] = fid;
+          m = i;
+        }
+        else {
+          new_ids[0][i] = ids[0][k];
+          k++;
+        }
+      }
+      Gridded1DSet new_set =
+        new Gridded1DSet(rtTrackID, new_ids, length + 1);
+      new_field = new FieldImpl(ftId2Track, new_set);
+      k = 0;
+      for (int i=0; i<length+1; i++) {
+        if (i == m) {
+          new_field.setSample(i, track, false);
+        }
+        else {
+          new_field.setSample(i, field.getSample(k), false);
+          k++;
+        }
+      }
+    }
+    Tuple new_disturbance = new Tuple(new Data[]
+      {disturbance.getComponent(0),
+       disturbance.getComponent(1),
+       disturbance.getComponent(2),
+       disturbance.getComponent(3),
+       disturbance.getComponent(4),
+       disturbance.getComponent(5),
+       disturbance.getComponent(6),
+       disturbance.getComponent(7),
+       disturbance.getComponent(8),
+       new_field});
+    setDisturbance(disturbanceID, new_disturbance);
+  }
+
+  public synchronized void addDisturbance(int disturbanceID, Tuple disturbance)
+         throws VisADException, RemoteException {
+    float fid = (float) disturbanceID;
+    if (data == null) {
+      Gridded1DSet set =
+        new Gridded1DSet(rtDisturbanceID, new float[][] {{fid}}, 1);
+      data = new FieldImpl(mtTC, set);
+      data.setSample(0, disturbance);
+    }
+    else {
+      Gridded1DSet set = (Gridded1DSet) data.getDomainSet();
+      float[][] ids = set.getSamples(false);
+      int length = set.getLength();
+      float[][] new_ids = new float[1][length + 1];
+      int k = 0;
+      int m = -1;
+      for (int i=0; i<length+1; i++) {
+        if (fid == ids[0][k]) {
+          throw new VisADException("disturbanceID " + disturbanceID +
+                                   " already used");
+        }
+        else if (m < 0 && fid < ids[0][k]) {
+          new_ids[0][i] = fid;
+          m = i;
+        }
+        else {
+          new_ids[0][i] = ids[0][k];
+          k++;
+        }
+      }
+      Gridded1DSet new_set =
+        new Gridded1DSet(rtDisturbanceID, new_ids, length + 1);
+      FieldImpl new_data = new FieldImpl(mtTC, new_set);
+      k = 0;
+      for (int i=0; i<length+1; i++) {
+        if (i == m) {
+          new_data.setSample(i, disturbance, false);
+        }
+        else {
+          new_data.setSample(i, data.getSample(k), false);
+          k++;
+        }
+      }
+      data = new_data;
+    }
+  }
+
+  private Tuple getDisturbance(int disturbanceID)
+          throws VisADException, RemoteException {
+    int index = findDisturbance(disturbanceID);
+    if (index < 0) return null;
+    else return (Tuple) data.getSample(index);
+  }
+
+  private void setDisturbance(int disturbanceID, Tuple disturbance)
+          throws VisADException, RemoteException {
+    int index = findDisturbance(disturbanceID);
+    if (index >= 0) data.setSample(index, disturbance);
+  }
+
+  private int findDisturbance(int disturbanceID) 
+          throws VisADException, RemoteException {
+    if (data == null) {
+      return -1;
+    }
+    Gridded1DSet set = (Gridded1DSet) data.getDomainSet();
+    float[][] ids = set.getSamples(false);
+    int length = set.getLength();
+    float fid = disturbanceID;
+    for (int i=0; i<length; i++) {
+      if (ids[0][i] == fid) return i;
+    }
+    return -1;
+  }
+
+  private Tuple getTrack(int trackID, Tuple disturbance)
+          throws VisADException, RemoteException {
+    int index = findTrack(trackID, disturbance);
+    if (index < 0) return null;
+    else {
+      FieldImpl field = (FieldImpl) disturbance.getComponent(9);
+      return (Tuple) field.getSample(index);
+    }
+  }
+
+  private void setTrack(int trackID, Tuple track, Tuple disturbance)
+          throws VisADException, RemoteException {
+    int index = findTrack(trackID, disturbance);
+    if (index >= 0) {
+      FieldImpl field = (FieldImpl) disturbance.getComponent(9);
+      field.setSample(index, track);
+    }
+  }
+
+  private int findTrack(int trackID, Tuple disturbance)
+          throws VisADException, RemoteException {
+    if (disturbance == null) {
+      return -1;
+    }
+    FieldImpl field = (FieldImpl) disturbance.getComponent(9);
+    if (field == null) {
+      return -1;
+    }
+    Gridded1DSet set = (Gridded1DSet) field.getDomainSet();
+    float[][] ids = set.getSamples(false);
+    int length = set.getLength();
+    float fid = trackID;
+    for (int i=0; i<length; i++) {
+      if (ids[0][i] == fid) return i;
+    }
+    return -1;
+  }
+
+  public static Tuple makeDisturbance(String country, String state, int year,
+             int number, String historical_name, double open_date, double close_date,
+             int archive_mode, int realtime_mode, FieldImpl tracks)
+         throws VisADException, RemoteException {
+    return new Tuple(new DataImpl[]
+      {new Text(ttCountry, country), new Text(ttState, state),
+       new Real(rtYear, year), new Real(rtNumber, number),
+       new Text(ttHistoricalName, historical_name),
+       new Real(rtOpenDate, open_date), new Real(rtCloseDate, close_date),
+       new Real(rtArchiveMode, archive_mode),
+       new Real(rtRealtimeMode, realtime_mode), tracks});
+  }
+
+  public static Tuple makeTrack(String track_type, String track_name,
+             // jk Feb 2001
+             // int base_date_time, int create_date_time, String display_type,
+             double base_date_time, double create_date_time, String display_type,
+             FlatField locations)
+         throws VisADException, RemoteException {
+
+    //jk : allow for null sizes
+    // if (sizes == null) sizes = TCData.makeMissingSizes();
+    // if (steerings == null) steerings = TCData.makeMissingSteerings();
+    // should also have same test for locations & intensities & steerings
+    // + methods makeMissingLocations and makeMissingIntensities ?
+
+    return new Tuple(new DataImpl[]
+      {new Text(ttTrackType, track_type), new Text(ttTrackName, track_name),
+       new Real(rtBaseDateTime, base_date_time),
+       new Real(rtCreateDateTime, create_date_time),
+       new Text(ttTrackStyle, display_type),
+       locations});
+  }
+
+  
+  /**
+   * jk:
+   * create a flatfield of Disturbance (Tropical Cyclone) Sizes
+   * with values set to "missing"
+   * This allows for the case when the database has no entries yet,
+   * but means we can still create some TCData
+   *
+   */
+  /*
+  public static FlatField makeMissingSizes() 
+         throws VisADException, RemoteException {
+
+    //
+    // make a SIZE and store in the FlatField ffSizes
+    //
+    double[] daTimes = {Double.NaN};
+    int[] iaSizeIds = {-1};
+    float[] faGale_radii = {Float.NaN};
+    float[] faStorm_radii = {Float.NaN};
+    float[] faHurricane_radii = {Float.NaN};
+    float[] faRadii_of_maximum_winds = {Float.NaN};
+    int[] iaSizeStyles = {-1};
+
+    FlatField ffSizes = TCData.makeSizes( daTimes, iaSizeIds, faGale_radii,
+                                     faStorm_radii, faHurricane_radii, faRadii_of_maximum_winds,
+                                     iaSizeStyles);
+    return ffSizes;
+  }
+   */
+
+  public static FlatField makeLocations(double[] times, float[] lats,
+          float[] lons, float[] errors, int[] confidence, int[] location_styles,
+          float[] wind_means, float[] wind_gusts, float[] central_pressures,
+          int[] categories, int[] intensityStyle,
+          float[] gale_radii, float[] storm_radii, float[] hurricane_radii,
+          float[] radii_of_maximum_winds, int[] size_styles,
+          float[] depth, float[] eyeDiameter, float[] pressureOfLastClosedIsobar,
+          int[] structureStyle)
+         throws VisADException, RemoteException {
+
+    if (times == null || lats == null || lons == null ||
+        errors == null || confidence == null || location_styles == null ||
+        wind_means == null ||  wind_gusts == null || central_pressures == null || 
+        categories == null || intensityStyle == null || gale_radii == null || 
+        storm_radii == null || hurricane_radii == null || radii_of_maximum_winds == null || 
+        size_styles == null || depth == null || eyeDiameter == null || 
+        pressureOfLastClosedIsobar == null || structureStyle == null) {
+      throw new VisADException("arguments may not be null");
+    }
+    int length = times.length;
+    if (lats.length != length || lons.length != length ||
+        errors.length != length || confidence.length != length || 
+        location_styles.length != length) {
+      throw new VisADException("argument lengths must match");
+    }
+    int[] permute = QuickSort.sort(times);
+    Gridded1DDoubleSet set =
+      new Gridded1DDoubleSet(rtTime, new double[][] {times}, length);
+    FlatField field = new FlatField(locationFunction, set);
+    
+    float[] plats = new float[length];
+    float[] plons = new float[length];
+    float[] perrors = new float[length];
+    float[] pconfidence = new float[length];
+    float[] pLocation_styles = new float[length];
+    float[] pwind_means = new float[length];
+    float[] pwind_gusts = new float[length];
+    float[] pcentral_pressures = new float[length];
+    float[] pcategories = new float[length];
+    float[] pIntensityStyle = new float[length];
+    float[] pgale_radii = new float[length];
+    float[] pstorm_radii = new float[length];
+    float[] phurricane_radii = new float[length];
+    float[] pradii_of_maximum_winds = new float[length];
+    float[] psize_styles = new float[length];
+    float[] pdepth = new float[length];
+    float[] pEyeDiameter = new float[length];
+    float[] pPressureOfLastClosedIsobar = new float[length];
+    float[] pStructureStyle = new float[length];
+    
+        
+    for (int i=0; i<length; i++) {
+      plats[i] = lats[permute[i]];
+      plons[i] = lons[permute[i]];
+      perrors[i] = errors[permute[i]];
+      pconfidence[i] = confidence[permute[i]];
+      pLocation_styles[i] = location_styles[permute[i]];
+      pwind_means[i] = wind_means[permute[i]];
+      pwind_gusts[i] = wind_gusts[permute[i]];
+      pcentral_pressures[i] = central_pressures[permute[i]];
+      pcategories[i] = categories[permute[i]];
+      pIntensityStyle[i] = intensityStyle[permute[i]];
+      pgale_radii[i] = gale_radii[permute[i]];
+      pstorm_radii[i] = storm_radii[permute[i]];
+      phurricane_radii[i] = hurricane_radii[permute[i]];
+      pradii_of_maximum_winds[i] = radii_of_maximum_winds[permute[i]];
+      psize_styles[i] =
+        (size_styles[permute[i]] < 0) ? Float.NaN : size_styles[permute[i]];
+      pdepth[i] = depth[permute[i]];
+      pEyeDiameter[i] = eyeDiameter[permute[i]];
+      pPressureOfLastClosedIsobar[i] = pressureOfLastClosedIsobar[permute[i]];      
+      pStructureStyle[i] = structureStyle[permute[i]];          
+    }
+    
+    float[][] values = {plats, plons, perrors, pconfidence, pLocation_styles,
+                        pwind_means, pwind_gusts, pcentral_pressures, pcategories,
+                        pIntensityStyle,
+                        pgale_radii, pstorm_radii, phurricane_radii, pradii_of_maximum_winds,
+                        psize_styles,
+                        pdepth, pEyeDiameter, pPressureOfLastClosedIsobar, pStructureStyle };
+
+    field.setSamples(values, false);
+    return field;
+  }
+
+  /**
+   * create a bunch of "intensities" which are measurements of
+   * the intensity of a Tropical Cyclone at particular times
+   *
+   * input: arrays of times, ids, wind_means...
+   * output: a field of mathType intensityFunction, which is represented by:
+   *         (time -> intensityTuple)
+   */
+
+  /* 
+  public static FlatField makeMissingSteerings() 
+         throws VisADException, RemoteException {
+
+    double[] daTimes = {Double.NaN};
+    int[] iaSteeringIds = {-1};
+    float[] faSteering_directions = {Float.NaN};
+    int[] iaSteeringStyles = {-1};
+
+    FlatField ffSteerings = TCData.makeSteerings( daTimes, iaSteeringIds, faSteering_directions,
+                                     iaSteeringStyles);
+
+    return ffSteerings;
+  }
+
+  */
+ 
+  public static void main(String[] args)
+         throws VisADException, RemoteException {
+    MathType mtTC;
+    TCData data = new TCData();
+
+    mtTC = data.getType();
+
+    System.out.println("MathType:\n" + mtTC);
+
+/*
+C:\jamesk\java\tc\visad\bom>java visad.bom.TCDataTest
+MathType:
+ (DISTURBANCEID -> (COUNTRY(Text),
+                   STATE(Text),
+                   YEAR,
+                   NUM,
+                   HISTORICALNAME(Text),
+                   OPENDATE,
+                   CLOSEDATE,
+                   ARCHIVEMODE,
+                   REALTIMEMODE,
+                   (TRACKID -> (TRACKTYPE(Text),
+                                TRACKNAME(Text),
+                                BASEDATETIME,
+                                CREATEDATETIME,
+                                TRACKSTYLE(Text),
+                                (Time -> (Latitude,
+                                          Longitude,
+                                          ERROR,
+                                          CONFIDENCE,
+                                          LOCATIONSTYLE,
+                                          WINDMEAN,
+                                          WINDGUST,
+                                          CENTRALPRESSURE,
+                                          CATEGORY,
+                                          INTENSITYSTYLE,
+                                          GALERADIUS,
+                                          STORMRADIUS,
+                                          HURRICANERADIUS,
+                                          RADIUSOFMAXIMUMWINDS,
+                                          SIZESTYLE,
+                                          DEPTH,
+                                          EYEDIAMETER,
+                                          PRESSUREOFLASTCLOSEDISOBAR,
+                                          STRUCTURESTYLE))))))
+
+was:
+ 
+doll% java visad.bom.TCData
+MathType:
+(DisturbanceID -> (Country(Text),
+                   State(Text),
+                   Year,
+                   Number,
+                   HistoricalName(Text),
+                   OpenDate,
+                   CloseDate,
+                   ArchiveMode,
+                   RealtimeMode,
+                   (TrackID -> (TrackType(Text),
+                                TrackName(Text),
+                                BaseDateTime,
+                                CreateDateTime,
+                                TrackStyle(Text),
+                                (Time -> (LocationID, Latitude, Longitude, Error, LocationStyle)),
+                                (Time -> (IntensityID, WindMean, WindGust, CentralPressure, Category)),
+                                (Time -> (SizeID, GaleRadius, StormRadius, HurricaneRadius, RadiusOfMaximumWinds, SizeStyle)),
+                                (Time -> (SteeringID, SteeringDirection, SteeringStyle))))))
+
+*/
+  }
+}
+
diff --git a/visad/bom/TCDataTest.java b/visad/bom/TCDataTest.java
new file mode 100644
index 0000000..3e75828
--- /dev/null
+++ b/visad/bom/TCDataTest.java
@@ -0,0 +1,270 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+//
+// TCDataTest.java
+//
+// Version 3 April 2001
+
+package visad.bom;
+
+import visad.*;
+import java.rmi.RemoteException;
+// import visad.java3d.DisplayImplJ3D;
+import visad.java2d.DisplayImplJ2D;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+/**
+ * Test program to demonstrate TCData, a class for representing
+ * Tropical Cyclones in VisAD
+ * 
+ * Creates some static TC Data, and displays a simple plot
+ *
+ */
+
+public class TCDataTest {
+
+
+  public static void main(String[] args)
+         throws VisADException, RemoteException {
+    MathType mtTC;
+    TCData data = new TCData();
+
+    mtTC = data.getType();
+
+    System.out.println("MathType:\n" + mtTC);
+
+    // Step 1
+    // Aim: set up the Data Model
+    //
+    // make a FIX and store in the FlatField ffFixes
+    //
+    double[] daTimes = {0.0d, 1000.0d, 2000.0d};
+    // int[] iaFixIds = {0, 1, 2};
+    float[] faLats = {10.0f, 11.0f, 12.0f};
+    float[] faLons = {160.0f, 165.0f, 170.0f};
+    float[] faErrors = {0.0f, 0.0f, 0.0f};
+    int[] iaConfidence = {2, 3, 4};
+    int[] iaLocationStyles = {2, 3, 4};
+    float[] faWind_means = {50.0f, 60.0f, 70.0f};
+    float[] faWind_gusts = {60.0f, 70.0f, 80.0f};
+    float[] faCentral_pressures = {990.0f, 985.0f, 980.0f};
+    int[] iaCategories = {2, 3, 4};
+    int[] iaIntensityStyles = {2, 3, 4};
+    float[] faGaleRadii = {200.0f, 210.0f, 220.0f};
+    float[] faStormRadii = {100.0f, 110.0f, 120.0f};
+    float[] faHurricaneRadii = {60.0f, 70.0f, 80.0f};
+    float[] faRadiiOfMaximumWinds = {50.0f, 60.0f, 70.0f};
+    int[] iaSizeStyles = {2, 3, 4};
+    float[] faDepth = {200.0f, 200.0f, 200.0f};
+    float[] faEyeDiameter = {50.0f, 60.0f, 70.0f};
+    float[] faPressureOfLastClosedIsobar = {200.0f, 210.0f, 220.0f};
+    int[] iaStructureStyles = {2, 3, 4};
+
+    FlatField ffFixes =
+      TCData.makeLocations(daTimes, faLats, faLons, 
+                           faErrors, iaConfidence, iaLocationStyles, faWind_means,
+                           faWind_gusts, faCentral_pressures, iaCategories,
+                           iaIntensityStyles, faGaleRadii, faStormRadii, faHurricaneRadii,
+                           faRadiiOfMaximumWinds, iaSizeStyles, faDepth, faEyeDiameter,
+                           faPressureOfLastClosedIsobar, iaStructureStyles);
+
+ // System.out.println("ffIntensities:\n" + ffFixes);
+
+
+    //
+    // make an INTENSITY and store in the FlatField ffIntensities
+    //
+
+    // FlatField ffIntensities = TCData.makeIntensities( daTimes, iaIntensityIds, faWind_means,
+    //                                 faWind_gusts, faCentral_pressures, iaCategories);
+    // System.out.println("ffIntensities:\n" + ffIntensities);
+
+    //
+    // make a SIZE and store in the FlatField ffSizes
+    //
+
+    // FlatField ffSizes = TCData.makeSizes( daTimes, iaSizeIds, faGale_radii,
+    //                                 faStorm_radii, faHurricane_radii, faRadii_of_maximum_winds,
+    //                                 iaSizeStyles);
+    // System.out.println("ffSizes:\n" + ffSizes);
+
+    //
+    // make a STEERING and store in the FlatField ffSteering
+    //
+
+    // FlatField ffSteerings = TCData.makeSteerings( daTimes, iaSteeringIds, faSteering_directions,
+    //                                 iaSteeringStyles);
+    // System.out.println("ffSteerings:\n" + ffSteerings);
+
+    //
+    // now make a TRACK
+    //
+    String sTrackType = new String("Observed");
+    String sTrackName = new String("RealTime");
+    int iBaseDateTime = 4000;
+    int iCreateDateTime = 5000;
+    String sDisplayType = new String("IDunno");
+
+    Tuple tTrack = TCData.makeTrack(sTrackType, sTrackName, iBaseDateTime, iCreateDateTime, sDisplayType, ffFixes);
+    //                                ffFixes, ffIntensities, ffSizes, ffSteerings);
+
+    // System.out.println("tTrack:\n" + tTrack);
+
+    //
+    // now make a field of TRACKs
+    //
+    int iTrackID = 0;
+    FieldImpl fiTrack = TCData.makeTrackField(iTrackID, tTrack);
+
+    // System.out.println("fiTrack:\n" + fiTrack);
+
+    //
+    // now make a disturbance
+    //
+    String sCountry = new String("Australia");
+    String sState = new String("WA");
+    int iYear = 2000;
+    int iNumber = 0;
+    String sHistoricalName = new String("Olga");
+    int iOpenDate = 5000;
+    int iCloseDate = 15000;
+    int iArchiveMode = 0;
+    int iRealTimeMode = 0;
+
+    Tuple tDisturbance = TCData.makeDisturbance(sCountry, sState, iYear, iNumber,
+                                 sHistoricalName, iOpenDate, iCloseDate, iArchiveMode,
+                                 iRealTimeMode, fiTrack);
+
+    // System.out.println("tDisturbance:\n" + tDisturbance);
+
+    //
+    // now make a field of disturbances
+    //
+    TCData tcd = new TCData();
+    tcd.addDisturbance(0, tDisturbance);
+    FieldImpl fiTCD = tcd.getData();
+    // System.out.println("TCData:\n" + fiTCD);
+
+    // Step 2
+    // Aim: create the View Model
+    // Result: display which is an implementation of the visad.Display interface
+    DisplayImpl display = new DisplayImplJ2D("display");
+    display.addMap(new ScalarMap(TCData.rtTime, Display.XAxis));
+    display.addMap(new ScalarMap(TCData.rtCentralPressure, Display.YAxis));
+
+    // Step 3
+    // Aim: create the Communication Model
+    // Result: dri which is an implementation of the visad.DataReference interface
+    DataReferenceImpl driTC = new DataReferenceImpl("TC");
+    // driTC.setData(ffFixes);
+    driTC.setData(tTrack);
+
+    // link the View Model (display) to the Data Model (via the DataReference)
+      display.addReference(driTC, null);
+
+    // Step 4
+    // Do the swing magic
+    JFrame frame = new JFrame("TCData Display Test");
+      frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+
+
+     JPanel panel = new JPanel();
+     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+     panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+     panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+     frame.getContentPane().add(panel);
+     panel.add(display.getComponent());
+     frame.setSize(300, 300);
+     frame.setVisible(true);
+
+/*
+C:\jamesk\java\tc\visad\bom>java visad.bom.TCDataTest
+MathType:
+(DISTURBANCEID -> (COUNTRY(Text),
+                   STATE(Text),
+                   YEAR,
+                   NUM,
+                   HISTORICALNAME(Text),
+                   OPENDATE,
+                   CLOSEDATE,
+                   ARCHIVEMODE,
+                   REALTIMEMODE,
+                   (TRACKID -> (TRACKTYPE(Text),
+                                TRACKNAME(Text),
+                                BASEDATETIME,
+                                CREATEDATETIME,
+                                TRACKSTYLE(Text),
+                                (Time -> (Latitude,
+                                          Longitude,
+                                          ERROR,
+                                          CONFIDENCE,
+                                          LOCATIONSTYLE,
+                                          WINDMEAN,
+                                          WINDGUST,
+                                          CENTRALPRESSURE,
+                                          CATEGORY,
+                                          INTENSITYSTYLE,
+                                          GALERADIUS,
+                                          STORMRADIUS,
+                                          HURRICANERADIUS,
+                                          RADIUSOFMAXIMUMWINDS,
+                                          SIZESTYLE,
+                                          DEPTH,
+                                          EYEDIAMETER,
+                                          PRESSUREOFLASTCLOSEDISOBAR,
+                                          STRUCTURESTYLE))))))
+
+ 
+ was:
+ 
+doll% java visad.bom.TCData
+MathType:
+(DisturbanceID -> (Country(Text),
+                   State(Text),
+                   Year,
+                   Number,
+                   HistoricalName(Text),
+                   OpenDate,
+                   CloseDate,
+                   ArchiveMode,
+                   RealtimeMode,
+                   (TrackID -> (TrackType(Text),
+                                TrackName(Text),
+                                BaseDateTime,
+                                CreateDateTime,
+                                DisplayType(Text),
+                                (Time -> (FixID, Latitude, Longitude, Error, FixStyle)),
+                                (Time -> (IntensityID, WindMean, WindGust, CentralPressure, Category)),
+                                (Time -> (SizeID, GaleRadius, StormRadius, HurricaneRadius, RadiusOfMaximumWinds, SizeStyle)),
+                                (Time -> (SteeringID, SteeringDirection, SteeringStyle))))))
+
+*/
+  }
+}
+
diff --git a/visad/bom/TextureFillRendererJ3D.java b/visad/bom/TextureFillRendererJ3D.java
new file mode 100644
index 0000000..dbba734
--- /dev/null
+++ b/visad/bom/TextureFillRendererJ3D.java
@@ -0,0 +1,390 @@
+//
+// TextureFillRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import javax.media.j3d.*;
+
+import java.rmi.*;
+import java.io.IOException;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+   TextureFillRendererJ3D is the VisAD class for rendering Sets (usually
+   Irregular2DSets) filled with a cross hatch pattern via texture mapping
+*/
+public class TextureFillRendererJ3D extends DefaultRendererJ3D {
+
+  // MathTypes that data must equalsExceptNames()
+  private static MathType set_type;
+
+  // initialize above MathType
+  static {
+    try {
+      set_type = MathType.stringToType("Set(X, Y)");
+    }
+    catch (VisADException e) {
+      throw new VisADError(e.getMessage());
+    }
+  }
+
+  // texture pattern will repeat 2 * scale times across box
+  private float scale = 10.0f;
+
+  // texture data defining repeating pattern
+  private int texture_width = 0;
+  private int texture_height = 0;
+  private int[] texture = null;
+
+  // true for smooth texture
+  private boolean smooth = false;
+
+  /** texture pattern will repreat 2 * s times across box */
+  public void setScale(float s) {
+    scale = s;
+  }
+
+  public float getScale() {
+    return scale;
+  }
+
+  /** define texture pattern as a w * h rectangle of ints (RGB values);
+      note w and h must be powers of 2, and t.length must be w * h */
+  public void setTexture(int w, int h, int[] t) throws VisADException {
+    int ww = 1;
+    while (ww < w) ww *= 2;
+    int hh = 1;
+    while (hh < h) hh *= 2;
+    if (ww != w || hh != h || t == null || t.length != w * h) {
+      throw new VisADException("bad params");
+    }
+    texture_width = w;
+    texture_height = h;
+    texture = t;
+  }
+
+  public int getTextureWidth() {
+    return texture_width;
+  }
+
+  public int getTextureHeight() {
+    return texture_height;
+  }
+
+  public int[] getTexture() {
+    return texture;
+  }
+
+  /** set s = true to smooth texture */
+  public void setSmooth(boolean s) {
+    smooth = s;
+  }
+
+  public boolean getSmooth() {
+    return smooth;
+  }
+
+  /** determine whether the given MathType is usable with TextureFillRendererJ3D */
+  public static boolean isSetType(MathType type) {
+    return (set_type.equalsExceptName(type));
+  }
+
+  /** determine whether the given MathType and collection of ScalarMaps
+      meets the criteria to use TextureFillRendererJ3D. Throw a VisADException
+      if ImageRenderer cannot be used, otherwise return true. */
+  public static boolean isRendererUsable(MathType type, ScalarMap[] maps)
+    throws VisADException
+  {
+    RealTupleType domain = null;
+    RealType x = null, y = null;
+    RealType rx = null, ry = null;
+
+    // must be a function
+    if (!(type instanceof SetType)) {
+      throw new VisADException("Not a SetType");
+    }
+    SetType set = (SetType) type;
+    domain = set.getDomain();
+
+    // extract x and y from domain
+    x = (RealType) domain.getComponent(0);
+    y = (RealType) domain.getComponent(1);
+
+    // WLH 19 July 2000
+    CoordinateSystem cs = domain.getCoordinateSystem();
+    if (cs != null) {
+      RealTupleType rxy = cs.getReference();
+      rx = (RealType) rxy.getComponent(0);
+      ry = (RealType) rxy.getComponent(1);
+    }
+
+    // verify that collection of ScalarMaps is legal
+    boolean bx = false, by = false;
+    boolean brx = false, bry = false; // WLH 19 July 2000
+    Boolean latlon = null;
+    DisplayRealType spatial = null;
+
+    for (int i=0; i<maps.length; i++) {
+      ScalarMap m = maps[i];
+      ScalarType md = m.getScalar();
+      DisplayRealType mr = m.getDisplayScalar();
+      boolean ddx = md.equals(x);
+      boolean ddy = md.equals(y);
+      boolean ddrx = md.equals(rx);
+      boolean ddry = md.equals(ry);
+
+      // spatial mapping
+      if (ddx || ddy || ddrx || ddry) {
+        if (ddx && bx || ddy && by || ddrx && brx || ddry && bry) {
+          throw new VisADException("Duplicate spatial mappings");
+        }
+        if (((ddx || ddy) && (brx || bry)) ||
+            ((ddrx || ddry) && (bx || by))) {
+          throw new VisADException("reference and non-reference spatial mappings");
+        }
+        RealType q = (ddx ? x : null);
+        if (ddy) q = y;
+        if (ddrx) q = rx;
+        if (ddry) q = ry;
+
+        boolean ll;
+        if (mr.equals(Display.XAxis) || mr.equals(Display.YAxis) ||
+          mr.equals(Display.ZAxis))
+        {
+          ll = false;
+        }
+        else if (mr.equals(Display.Latitude) || mr.equals(Display.Longitude) ||
+          mr.equals(Display.Radius))
+        {
+          ll = true;
+        }
+        else throw new VisADException("Illegal domain mapping");
+
+        if (latlon == null) {
+          latlon = new Boolean(ll);
+          spatial = mr;
+        }
+        else if (latlon.booleanValue() != ll) {
+          throw new VisADException("Multiple spatial coordinate systems");
+        }
+        // two mappings to the same spatial DisplayRealType are not allowed
+        else if (spatial == mr) {
+          throw new VisADException(
+            "Multiple mappings to the same spatial DisplayRealType");
+        }
+
+        if (ddx) bx = true;
+        else if (ddy) by = true;
+        else if (ddrx) brx = true;
+        else if (ddry) bry = true;
+      }
+
+      // illegal ScalarMap involving this MathType
+      else if (ddx || ddy || ddrx || ddry)
+      {
+        throw new VisADException("Illegal mapping: " + m);
+      }
+    }
+
+    // return true if all conditions for TextureFillRendererJ3D are met
+    if (!((bx && by) || (brx && bry))) {
+      throw new VisADException("Insufficient mappings");
+    }
+    return true;
+  }
+
+  // factory for ShadowFunctionType that defines unique behavior
+  // for TextureFillRendererJ3D
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowTextureFillSetTypeJ3D(type, link, parent);
+  }
+
+  public BranchGroup doTransform() throws VisADException, RemoteException {
+    BranchGroup branch = getBranch();
+    if (branch == null) {
+      branch = new BranchGroup();
+      branch.setCapability(BranchGroup.ALLOW_DETACH);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+    }
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return null;
+    }
+
+    DataDisplayLink link = Links[0];
+    ShadowTypeJ3D type = (ShadowTypeJ3D) link.getShadow();
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return null;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      addException(
+        new DisplayException("Data is null: DefaultRendererJ3D.doTransform"));
+    }
+    else {
+      // check MathType of non-null data, to make sure it is a single-band
+      // image or a sequence of single-band images
+      MathType mtype = link.getType();
+      if (!isSetType(mtype)) {
+        throw new BadMappingException("must be set");
+      }
+      link.start_time = System.currentTimeMillis();
+      link.time_flag = false;
+      // transform data into a depiction under branch
+      try {
+        type.doTransform(branch, data, valueArray,
+                         link.getDefaultValues(), this);
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+    }
+    link.clearData();
+    return branch;
+  }
+
+  public Object clone() {
+    return new TextureFillRendererJ3D();
+  }
+
+  /** run 'java visad.bom.TextureFillRendererJ3D smooth' */
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    // create a DataReference for set
+    final DataReference set_ref = new DataReferenceImpl("set");
+
+    // create a Display using Java3D
+    DisplayImpl display = new DisplayImplJ3D("set display");
+
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealTupleType xy = new RealTupleType(x, y);
+
+
+    int points = 23;
+    float[][] samples = new float[2][points];
+
+    for (int i=0; i<points; i++) {
+      samples[0][i] = (float) Math.random();
+      samples[1][i] = (float) Math.random();
+    }
+
+/*
+    int points = 3;
+    float[][] samples = {{1.0f, 1.0f, -1.0f}, {1.0f, -1.0f, 1.0f}};
+*/
+
+    Set set = new Irregular2DSet(xy, samples);
+    set_ref.setData(set);
+
+    display.addMap(new ScalarMap(x, Display.XAxis));
+    display.addMap(new ScalarMap(y, Display.YAxis));
+
+    // link the Display to set_ref
+    TextureFillRendererJ3D renderer = new TextureFillRendererJ3D();
+    int width = 8;
+    int height = width;
+    int half = width / 2;
+    int halfm = half - 1;
+    int halfp = half + 1;
+    int[] texture = new int[width * height];
+    int m = 0;
+    int t = ((255 << 24) | (255 << 16) | (255 << 8) | 255);
+    // int t = ((255 << 16) | (255 << 8) | 255);
+    // int t = ((127 << 24) | (127 << 16) | (127 << 8) | 127);
+    for (int i=0; i<width; i++) {
+      for (int j=0; j<height; j++) {
+        if ((i == half && halfm <= j && j <= halfp) ||
+            (j == half && halfm <= i && i <= halfp)) {
+          texture[m] = t;
+        }
+        else {
+          texture[m] = 0; 
+        }
+        m++;
+      }
+    }
+    renderer.setTexture(width, height, texture);
+    renderer.setScale(10.0f);
+    renderer.setSmooth( (args.length > 0) );
+
+    display.addReferences(renderer, set_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("TextureFillRendererJ3D test");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+    
+    // add display to JPanel
+    panel.add(display.getComponent());
+    
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+
+  }
+
+}
+
diff --git a/visad/bom/TrackManipulation.java b/visad/bom/TrackManipulation.java
new file mode 100644
index 0000000..db07608
--- /dev/null
+++ b/visad/bom/TrackManipulation.java
@@ -0,0 +1,450 @@
+//
+// TrackManipulation.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+import java.rmi.*;
+
+
+/**
+   TrackManipulation is the VisAD class for direct
+   manipulation rendering of storm tracks under Java3D
+*/
+public class TrackManipulation extends Object {
+
+  private DisplayImplJ3D display;
+  private DataReference track1_ref;
+  private DataReference track2_ref;
+  private DataReference track3_ref;
+
+  private RealTupleType latlonshape = null;
+
+  private ScalarMap latmap = null;
+  private ScalarMap lonmap = null;
+  private CoordinateSystem coord = null;
+  private int latindex = -1;
+  private int lonindex = -1;
+  private int otherindex = -1;
+  private float othervalue = 0.0f;
+
+  private ScalarMap shapemap = null;
+  private ShapeControl shapecontrol = null;
+
+  private float x_size;
+  private float y_size;
+  private float angle;
+
+  private static int NE = 32;
+  private float[] x_ellipse = new float[NE + 1];
+  private float[] y_ellipse = new float[NE + 1];
+
+  /** (lat1, lon1) start of track
+      (lat2, lon2) end of track
+      d is a DisplayImplJ3D that has ScalarMaps of Latitude and
+      Longitude but is not linked yet to any DataReferences;
+      this constructor will add another ScalarMap to Shape and
+      link the DisplayImplJ3D d to three data objects, two by
+      direct manipulation */
+  public TrackManipulation(float lat1, float lon1, float lat2, float lon2,
+                           DisplayImplJ3D d, float xs, float ys, float ang)
+         throws VisADException, RemoteException {
+
+    // construct RealTuple objects to be manipulated at ends of track
+    RealTupleType latlon =
+      new RealTupleType(RealType.Latitude, RealType.Longitude);
+    RealTuple track1 = new RealTuple(latlon, new double[] {lat1, lon1});
+    RealTuple track2 = new RealTuple(latlon, new double[] {lat2, lon2});
+
+    // construct RealTuple with RealType mapped to Shape that
+    // creates storm track depiction
+    RealType shape = RealType.getRealType("shape");
+    latlonshape =
+      new RealTupleType(RealType.Latitude, RealType.Longitude, shape);
+    RealTuple track3 = new RealTuple(latlonshape, new double[] {lat1, lon1, 0.0});
+
+    // construct DataReferences for these three RealTuples
+    track1_ref = new DataReferenceImpl("track1_ref");
+    track2_ref = new DataReferenceImpl("track2_ref");
+    track3_ref = new DataReferenceImpl("track3_ref");
+    track1_ref.setData(track1);
+    track2_ref.setData(track2);
+    track3_ref.setData(track3);
+
+    // copy reference to DisplayImplJ3D
+    display = d;
+
+    // compute basic ellipse shape according to constructor
+    // arguments
+    x_size = (float) Math.abs(xs);
+    y_size = (float) Math.abs(ys);
+    angle = ang;
+    float sa = (float) Math.sin(ang * Data.DEGREES_TO_RADIANS);
+    float ca = (float) Math.cos(ang * Data.DEGREES_TO_RADIANS);
+    for (int i=0; i<NE+1; i++) {
+      double b = 2.0 * Math.PI * i / NE;
+      float xe = (float) (x_size * Math.cos(b));
+      float ye = (float) (y_size * Math.sin(b));
+      x_ellipse[i] = ca * xe + sa * ye;
+      y_ellipse[i] = ca * ye - sa * xe;
+    }
+
+    // find ScalarMaps of Latitude and Longitude
+    Vector scalar_map_vector = display.getMapVector();
+    Enumeration en = scalar_map_vector.elements();
+    while (en.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) en.nextElement();
+      if (RealType.Latitude.equals(map.getScalar())) {
+        latmap = map;
+      }
+      else if (RealType.Longitude.equals(map.getScalar())) {
+        lonmap = map;
+      }
+    }
+    if (latmap == null || lonmap == null) {
+      throw new DisplayException("Latitude and Longitude must be mapped " +
+                                 "in display");
+    }
+
+    // get information from latmap and lonmap needed to
+    // compute display locations from manipulated RealTuple
+    // values
+    DisplayRealType latreal = latmap.getDisplayScalar();
+    DisplayRealType lonreal = lonmap.getDisplayScalar();
+    DisplayTupleType lattuple = latreal.getTuple();
+    DisplayTupleType lontuple = lonreal.getTuple();
+    if (lattuple == null || !lattuple.equals(lontuple)) {
+      throw new DisplayException("Latitude and Longitude must be mapped " +
+                                 "to spatial in display(1)");
+    }
+    latindex = latreal.getTupleIndex();
+    lonindex = lonreal.getTupleIndex();
+    otherindex = 3 - (latindex + lonindex);
+    DisplayRealType othertype =
+      (DisplayRealType) lattuple.getComponent(otherindex);
+    othervalue = (float) othertype.getDefaultValue();
+    coord = lattuple.getCoordinateSystem();
+    if (!lattuple.equals(Display.DisplaySpatialCartesianTuple) &&
+        !(coord != null &&
+          coord.getReference().equals(Display.DisplaySpatialCartesianTuple))) {
+      throw new DisplayException("Latitude and Longitude must be mapped " +
+                                 "to spatial in display(2)");
+    }
+
+    // construct ScalarMap to Shape, with a single 'shape'
+    shapemap = new ScalarMap(shape, Display.Shape);
+    display.addMap(shapemap);
+    shapecontrol = (ShapeControl) shapemap.getControl();
+    shapecontrol.setShapeSet(new Integer1DSet(1));
+
+    // link RealTuples to display
+    display.addReferences(new DirectManipulationRendererJ3D(), track1_ref);
+    display.addReferences(new DirectManipulationRendererJ3D(), track2_ref);
+    display.addReference(track3_ref);
+
+    // construct CellImpl that computes storm track shape in
+    // response to user manipulation of track end points, and
+    // link it to those manipulable RealTuples
+    TrackGetter tracker = new TrackGetter();
+    tracker.addReference(track1_ref);
+    tracker.addReference(track2_ref);
+  }
+
+  // compute approximate radius of ellipse in direction (x, y)
+  private float getStep(float x, float y) {
+    double dist = 2.0 * (x_size + y_size);
+    float step = -1.0f;
+    for (int i=0; i<NE+1; i++) {
+      double d = Math.abs(x * y_ellipse[i] - y * x_ellipse[i]);
+      if (d < dist) {
+        dist = d;
+        step = (float) Math.sqrt(x_ellipse[i] * x_ellipse[i] +
+                                 y_ellipse[i] * y_ellipse[i]);
+      }
+    }
+    return step;
+  }
+
+  // maximum size of shape geometry
+  private static final int NUM = 4096;
+
+  /** construct a VisADLineArray in the shape of a storm track
+      with end points given in the values array */
+  private VisADLineArray makeTrack(float[][] values) {
+    float d, xd, yd;
+    float x, y, z, x0, y0, x3, y3, x4, y4, x5, y5;
+    float sscale = 0.75f * 0.15f;
+
+    // start of arrow is located at first manipulable RealTuple
+    x = 0.0f;
+    y = 0.0f;
+    z = values[2][0];
+    // head of arrow is located at second manipulable RealTuple
+    x5 = values[0][1] - values[0][0];
+    y5 = values[1][1] - values[1][0];
+
+    // get arrow vector and length
+    float xdir = x5 - x;
+    float ydir = y5 - y; 
+    float dist = (float) Math.sqrt(xdir * xdir + ydir * ydir);
+
+    // normalize direction
+    x0 = xdir / dist;
+    y0 = ydir / dist;
+
+    // running count of geometry size
+    int nv = 0;
+
+    // allocate arrays for geometry
+    float[] vx = new float[NUM];
+    float[] vy = new float[NUM];
+    float[] vz = new float[NUM];
+    int lenv = vx.length;
+
+    // draw arrow shaft
+    vx[nv] = x;
+    vy[nv] = y;
+    vz[nv] = z;
+    nv++;
+    vx[nv] = x5;
+    vy[nv] = y5;
+    vz[nv] = z;
+    nv++;
+
+    // computation for arrow head
+    xd = sscale * x0;
+    yd = sscale * y0;
+    x3 = x5 - 0.3f * (xd - yd);
+    y3 = y5 - 0.3f * (yd + xd);
+    x4 = x5 - 0.3f * (xd + yd);
+    y4 = y5 - 0.3f * (yd - xd);
+
+    // draw arrow head
+    vx[nv] = x5;
+    vy[nv] = y5;
+    vz[nv] = z;
+    nv++;
+    vx[nv] = x3;
+    vy[nv] = y3;
+    vz[nv] = z;
+    nv++;
+
+    vx[nv] = x5;
+    vy[nv] = y5;
+    vz[nv] = z;
+    nv++;
+    vx[nv] = x4;
+    vy[nv] = y4;
+    vz[nv] = z;
+    nv++;
+
+    // compute number of ellipses and spacing between them
+    float step = getStep(xdir, ydir); 
+    int nsteps = (int) (dist / step);
+    if (nsteps < 1) nsteps = 1;
+    int lim = (vx.length - nv) / (2 * NE);
+    if (nsteps < 1) nsteps = 1;
+    if (nsteps > lim) nsteps = lim;
+    float xstep = xdir / nsteps;
+    float ystep = ydir / nsteps;
+
+    // compute which ellipse points are 'outside' previous
+    // ellipse (and hence visible)
+    boolean[] outside = new boolean[NE + 1];
+    for (int i=0; i<NE+1; i++) {
+      float xs = x_ellipse[i] + xstep;
+      float ys = y_ellipse[i] + ystep;
+      float radius = getStep(xs, ys);
+      float len = (float) Math.sqrt(xs * xs + ys * ys);
+      outside[i] = (len > radius);
+    }
+
+    // construct geometry for visible points of one ellipse
+    float[] xe = new float[2 * NE];
+    float[] ye = new float[2 * NE];
+    int ne = 0;
+    for (int i=0; i<NE; i++) {
+      if (outside[i] && outside[i + 1]) {
+        xe[ne] = x_ellipse[i];
+        ye[ne] = y_ellipse[i];
+        ne++;
+        xe[ne] = x_ellipse[i+1];
+        ye[ne] = y_ellipse[i+1];
+        ne++;
+      }
+    }
+
+    // draw first ellipse
+    float xcenter = x;
+    float ycenter = y;
+    for (int i=0; i<NE; i++) {
+      vx[nv] = x_ellipse[i];
+      vy[nv] = y_ellipse[i];
+      vz[nv] = z;
+      nv++;
+      vx[nv] = x_ellipse[i+1];
+      vy[nv] = y_ellipse[i+1];
+      vz[nv] = z;
+      nv++;
+    }
+
+    // add sequence of ellipses to storm track geometry
+    for (int i=0; i<nsteps; i++) {
+      xcenter += xstep;
+      ycenter += ystep;
+      for (int j=0; j<ne; j++) {
+        vx[nv] = xcenter + xe[j];
+        vy[nv] = ycenter + ye[j];
+        vz[nv] = z;
+        nv++;
+      }
+    }
+
+    // construct and return VisADLineArray from geometry
+    VisADLineArray array = new VisADLineArray();
+    array.vertexCount = nv;
+    float[] coordinates = new float[3 * nv];
+    int m = 0;
+    for (int i=0; i<nv; i++) {
+      coordinates[m++] = vx[i];
+      coordinates[m++] = vy[i];
+      coordinates[m++] = vz[i];
+    }
+    array.coordinates = coordinates;
+    return array;
+  }
+
+  /** this CellImpl computes storm track shapes from RealTuples
+      at start and end of track */
+  class TrackGetter extends CellImpl {
+
+    public TrackGetter() {
+    }
+
+    public void doAction() throws VisADException, RemoteException {
+      // get start and end locations, first in lat and lon
+      float[] latvalues = new float[2];
+      float[] lonvalues = new float[2];
+      RealTuple tuple1 = (RealTuple) track1_ref.getData();
+      latvalues[0] = (float)
+        ((Real) tuple1.getComponent(0)).getValue(RealType.Latitude.getDefaultUnit());
+      lonvalues[0] = (float)
+        ((Real) tuple1.getComponent(1)).getValue(RealType.Longitude.getDefaultUnit());
+      float lat0 = latvalues[0];
+      float lon0 = lonvalues[0];
+      RealTuple tuple2 = (RealTuple) track2_ref.getData();
+      latvalues[1] = (float)
+        ((Real) tuple2.getComponent(0)).getValue(RealType.Latitude.getDefaultUnit());
+      lonvalues[1] = (float)
+        ((Real) tuple2.getComponent(1)).getValue(RealType.Longitude.getDefaultUnit());
+      // scale end locations to graphics coordiantes
+      latvalues = latmap.scaleValues(latvalues);
+      lonvalues = lonmap.scaleValues(lonvalues);
+
+      float[][] values = new float[3][2];
+      for (int k=0; k<2; k++) {
+        values[latindex][k] = latvalues[k];
+        values[lonindex][k] = lonvalues[k];
+        values[otherindex][k] = othervalue;
+      }
+      // if necessary, convert to Cartesian graphics coordinates
+      if (coord != null) {
+        values = coord.toReference(values);
+      }
+
+      // disable display so it doesn't update twice
+      display.disableAction();
+      // first, base storm track shape at location of first
+      // manipulable RealTuple
+      RealTuple track3 = new RealTuple(latlonshape, new double[] {lat0, lon0, 0.0});
+      track3_ref.setData(track3);
+      // second, update shape based on end points
+      VisADGeometryArray shape = makeTrack(values);
+      shapecontrol.setShape(0, shape);
+      // now let display update, just once
+      display.enableAction();
+    }
+
+  }
+
+  /** test TrackManipulation
+      optional command line arguments:
+      java visad.bom.TrackManipulation xsize ysize angle(degrees) */
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    // get command line arguments for ellipse shape
+    float[] fargs = {0.2f, 0.1f, 0.0f};
+    for (int i=0; i<args.length; i++) {
+      try {
+        fargs[i] = Float.parseFloat(args[i]);
+      }
+      catch (NumberFormatException exc) {
+      }
+    }
+
+    // construct Java3D display with lat and lon mappings
+    DisplayImplJ3D display =
+      new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
+    ScalarMap lonmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    display.addMap(lonmap);
+    lonmap.setRange(-10.0, 10.0);
+    ScalarMap latmap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    display.addMap(latmap);
+    latmap.setRange(-10.0, 10.0);
+
+    // construct a TrackManipulation object that sets up manipulation
+    // of storm track shape
+    TrackManipulation track = new TrackManipulation(0.0f, 0.0f, 3.0f, 3.0f,
+                                       display, fargs[0], fargs[1], fargs[2]);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test TrackManipulation");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/bom/WindPolarCoordinateSystem.java b/visad/bom/WindPolarCoordinateSystem.java
new file mode 100644
index 0000000..27b5d93
--- /dev/null
+++ b/visad/bom/WindPolarCoordinateSystem.java
@@ -0,0 +1,146 @@
+//
+// WindPolarCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom;
+
+import visad.*;
+
+/**
+   WindPolarCoordinateSystem is the VisAD CoordinateSystem class
+   for (Longitude, Radius) with a Cartesian Reference,
+   and with Longitude in degrees.<P>
+*/
+public class WindPolarCoordinateSystem extends CoordinateSystem {
+
+  private static Unit[] coordinate_system_units =
+    {CommonUnit.degree, CommonUnit.meterPerSecond};
+
+  /** construct a CoordinateSystem for (longitude, radius)
+      relative to a 2-D Cartesian reference;
+      this constructor supplies units =
+      {CommonUnit.Degree, CommonUnit.meterPerSecond}
+      to the super constructor, in order to ensure Unit
+      compatibility with its use of trigonometric functions */
+  public WindPolarCoordinateSystem(RealTupleType reference)
+         throws VisADException {
+    super(reference, coordinate_system_units);
+  }
+
+  /** constructor to set units */
+  public WindPolarCoordinateSystem(RealTupleType reference, Unit[] units)
+         throws VisADException {
+    super(reference, units);
+  }
+
+  /** simple constructor for "static" conversions */
+  public WindPolarCoordinateSystem() throws VisADException {
+    this(new RealTupleType(RealType.Longitude, RealType.Radius));
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("WindPolarCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[2][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[1][i] < 0.0) {
+        value[0][i] = Double.NaN;
+        value[1][i] = Double.NaN;
+      }
+      else {
+        double coslon = Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        double sinlon = Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        value[0][i] = -tuples[1][i] * sinlon;
+        value[1][i] = -tuples[1][i] * coslon;
+      }
+    }
+    return value;
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("WindPolarCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    double[][] value = new double[2][len];
+    for (int i=0; i<len ;i++) {
+      value[1][i] = Math.sqrt(tuples[0][i] * tuples[0][i] +
+                              tuples[1][i] * tuples[1][i]);
+      value[0][i] =
+        Data.RADIANS_TO_DEGREES * Math.atan2(-tuples[0][i], -tuples[1][i]);
+      if (value[0][i] < 0.0) value[0][i] += 360.0;
+    }
+    return value;
+  }
+
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("WindPolarCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[2][len];
+    for (int i=0; i<len ;i++) {
+      if (tuples[1][i] < 0.0) {
+        value[0][i] = Float.NaN;
+        value[1][i] = Float.NaN;
+      }
+      else {
+        float coslon = (float) Math.cos(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        float sinlon = (float) Math.sin(Data.DEGREES_TO_RADIANS * tuples[0][i]);
+        value[0][i] = -tuples[1][i] * sinlon;
+        value[1][i] = -tuples[1][i] * coslon;
+      }
+    }
+    return value;
+  }
+
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("WindPolarCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    int len = tuples[0].length;
+    float[][] value = new float[2][len];
+    for (int i=0; i<len ;i++) {
+      value[1][i] = (float) Math.sqrt(tuples[0][i] * tuples[0][i] +
+                                      tuples[1][i] * tuples[1][i]);
+      value[0][i] = (float)
+        (Data.RADIANS_TO_DEGREES * Math.atan2(-tuples[0][i], -tuples[1][i]));
+      if (value[0][i] < 0.0) value[0][i] += 360.0;
+    }
+    return value;
+  }
+
+  public boolean equals(Object cs) {
+    return (cs instanceof WindPolarCoordinateSystem);
+  }
+
+}
+
diff --git a/visad/bom/annotations/ImageJ3D.java b/visad/bom/annotations/ImageJ3D.java
new file mode 100644
index 0000000..bee9236
--- /dev/null
+++ b/visad/bom/annotations/ImageJ3D.java
@@ -0,0 +1,207 @@
+//
+// ImageJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+
+import visad.DisplayImpl;
+import visad.VisADException;
+
+import visad.java3d.DisplayImplJ3D;
+
+import visad.util.ImageHelper;
+
+/**
+ *  Meant to encapsulate information representing an Image which is
+ *  going to be rendered on a VisAD display without being
+ *  subject to the usual VisAD transformations. Thus the Image should
+ *  stick in Screen Coordinates.
+ */
+public class ImageJ3D implements ScreenAnnotation
+{
+  /** Image top left positioned on x, y */
+  public static final int TOP_LEFT = 20;
+
+  /** Image top right positioned on x, y */
+  public static final int TOP_RIGHT = 21;
+
+  /** Image bottom right positioned on x, y */
+  public static final int BOTTOM_RIGHT = 22;
+
+  /** Image bottom left positioned on x, y */
+  public static final int BOTTOM_LEFT = 23;
+
+  /** Image centre positioned on x, y */
+  public static final int CENTER = 24;
+  
+  private Image image;
+  private int position;
+  private int x, y;    // location in screen coordinates
+  private int width = -1, height = -1; // of the image, in pixels
+  private double zValue;
+  private double scaleFactor;      // for point size
+
+  /** 
+   *  Constructs a ImageJ3D from specified values in screen coordinates.
+   *
+   *  @param filename  of a valid GIF, JPEG or PNG file.
+   *  @param position  how to place the image relative to (x, y);
+   *         one of Image.TOP_LEFT (default), Image.TOP_RIGHT,
+   *         Image.BOTTOM_RIGHT, Image.BOTTOM_LEFT, Image.CENTER
+   *  @param x  x screen coordinate of image location.
+   *  @param y  y screen coordinate of image location.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param scaleFactor  scale factor for image magnification; greater
+   *         than 0.0.
+   *
+   *  @throws VisADException if the Image is bad.
+   */
+  public ImageJ3D(String filename, int position, int x, int y,
+    double zValue, double scaleFactor)
+        throws VisADException
+  {
+    this(Toolkit.getDefaultToolkit().getImage(filename),
+      position, x, y, zValue, scaleFactor);
+  }
+
+  /** 
+   *  Constructs a ImageJ3D from specified values in screen coordinates.
+   *
+   *  @param image  base java.awt.Image object to represent.
+   *  @param position  how to place the image relative to (x, y);
+   *         one of Image.TOP_LEFT (default), Image.TOP_RIGHT,
+   *         Image.BOTTOM_RIGHT, Image.BOTTOM_LEFT, Image.CENTER
+   *  @param x  x screen coordinate of image location.
+   *  @param y  y screen coordinate of image location.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param scaleFactor  scale factor for image magnification; greater
+   *         than 0.0.
+   *
+   *  @throws VisADException if the Image is bad.
+   */
+  public ImageJ3D(Image image, int position, int x, int y,
+    double zValue, double scaleFactor)
+        throws VisADException
+  {
+    this.position = position;
+    this.x = x;
+    this.y = y;
+    this.zValue = zValue;
+    this.scaleFactor = scaleFactor;
+    setImage(image);
+  }
+
+  /**
+   *  Set the Image for this object.
+   *
+   *  @param image  base java.awt.Image object to represent.
+   *
+   *  @throws VisADException if the Image is bad.
+   */
+  public void setImage(Image image)
+        throws VisADException
+  {
+    this.image = image;
+    // get the image width and height
+    ImageHelper ih = new ImageHelper();
+    do {
+      if (width < 0) { width = image.getWidth(ih); }
+      if (height < 0) { height = image.getHeight(ih); }
+      if (ih.badImage || (width >=0 && height >= 0)) {
+        break;
+      }
+      try {
+        Thread.currentThread().sleep(100);
+      } catch (InterruptedException e) {
+        throw new VisADException("ImageJ3D: Interrupted!!");
+      }
+    } while (true);
+    if (ih.badImage) {
+      throw new VisADException("ImageJ3D: not an image");
+    }
+  }
+
+  /**
+   *  Set the relative position for this object.
+   *
+   *  @param position  how to place the image relative to (x, y);
+   *         one of Image.TOP_LEFT (default), Image.TOP_RIGHT,
+   *         Image.BOTTOM_RIGHT, Image.BOTTOM_LEFT, Image.CENTER
+   */
+  public void setPosition(int position)
+  {
+    this.position = position;
+  }
+
+  /**
+   *  Set the amount to magnify the image.
+   *
+   *  @param scaleFactor  scale factor for image magnification; greater
+   *         than 0.0.
+   */
+  public void setScaleFactor(double scaleFactor)
+  {
+    this.scaleFactor = scaleFactor;
+  }
+
+  /**
+   *  Set coordinates for the ImageJ3D.
+   *
+   *  @param x  x screen coordinate of image location.
+   *  @param y  y screen coordinate of image location.
+   */
+  public void setImageJ3Ds(int x, int y)
+  {
+    this.x = x;
+    this.y = y;
+  } 
+
+  /**
+   *  @param zValue  Virtual world value; larger z is in front.
+   */
+  public void setZValue(double zValue)
+  {
+    this.zValue = zValue;
+  }
+
+  /**
+   *  @param display  the VisAD display for this Image.
+   *
+   *  @return the Image description as a Shape3D object.
+   *
+   *  @throws VisADException if there is a VisAD problem.
+   */
+  public Object toDrawable(DisplayImpl display)
+        throws VisADException
+  {
+    return ScreenAnnotatorUtils.makeImageShape3D(
+      (DisplayImplJ3D)display, image, position,
+      x, y, width, height, zValue, scaleFactor);
+  }
+} // class ImageJ3D
+
diff --git a/visad/bom/annotations/JLabelJ3D.java b/visad/bom/annotations/JLabelJ3D.java
new file mode 100644
index 0000000..486ed94
--- /dev/null
+++ b/visad/bom/annotations/JLabelJ3D.java
@@ -0,0 +1,201 @@
+//
+// JLabelJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.VisADException;
+
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+
+import java.awt.Font;
+
+import javax.media.j3d.Text3D;
+
+/**
+ *  Meant to encapsulate information representing a label which is
+ *  going to be rendered on a VisAD display without being
+ *  subject to the usual VisAD transformations. Thus the label should
+ *  stick in Screen Coordinates.
+ *
+ *  It should render using the Java3D Text3D class.
+ *  Java3D dependent only in needing Text3D.
+ */
+public class JLabelJ3D implements ScreenAnnotation
+{
+  private String text;
+  private int xLocation;      // screen coordinates
+  private int yLocation;      // screen coordinates
+  private float[] colour;
+  private Font font;
+  private int align;
+  private int path;
+  private double zValue;
+  private double fontSizeInPixels;
+
+  /**
+   *  It constructs a JLabelJ3D with text at the
+   *  origin, coloured white.
+   *
+   *  @param text  text of the JLabelJ3D.
+   */
+  public JLabelJ3D(String text)
+        {
+    // need a default Font
+    this(text, 0, 0, new float[] {1, 1, 1},
+      new Font("TestFont", Font.PLAIN, 1),
+      0.0, 12.0, Text3D.ALIGN_FIRST, Text3D.PATH_RIGHT);
+  }
+
+  /**
+   *  Constructor for a JLabel3D - It is filled and should have non
+         *  null Font.
+   *
+   *  @param text  text of the JLabelJ3D.
+   *  @param xLocation  x position in screen coordinates.
+   *  @param yLocation  y position in screen coordinates.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param font  {@link java.awt.Font} to use.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param fontSizeInPixels  font size.
+   *  @param align  one of: <ul>
+   *         <li>Text3D.ALIGN_FIRST
+   *         <li>Text3D.ALIGN_CENTER
+   *         <li>Text3D.ALIGN_LAST  </ul>
+   *  @param path  one of: <ul>
+   *         <li>Text3D.PATH_RIGHT
+   *         <li>Text3D.PATH_LEFT
+   *         <li>Text3D.PATH_DOWN
+   *         <li>Text3D.PATH_UP  </ul>
+   */
+  public JLabelJ3D(String text, int xLocation,
+    int yLocation, float[] colour, Font font, 
+    double zValue, double fontSizeInPixels, int align, int path)
+  {
+    this.text = text;
+    this.xLocation = xLocation;
+    this.yLocation = yLocation;
+    this.colour = colour;
+    this.font = font;
+    this.zValue = zValue;
+    this.fontSizeInPixels = fontSizeInPixels;
+    this.align = align;
+    this.path = path;
+  }
+
+  /**
+   *  @param text  text of the JLabelJ3D.
+   */
+  public void setText(String text)
+  {
+    this.text = text;
+  }
+
+  /**
+   *  @param xLocation  x position in screen coordinates.
+   *  @param yLocation  y position in screen coordinates.
+   */
+  public void setLocation(int xLocation, int yLocation)
+  {
+    this.xLocation = xLocation;
+    this.yLocation = yLocation;
+  }
+
+  /**
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   */
+  public void setColour(float[] colour)
+  {
+                this.colour = colour;
+  }
+
+  /**
+   *  @param font  {@link java.awt.Font} to use.
+   */
+  public void setFont(Font font)
+  {
+                this.font = font;
+  }
+
+  /**
+   *  @param zValue  Virtual world value; larger z is in front.
+   */
+  public void setZValue(double zValue)
+  {
+                this.zValue = zValue;
+  }
+
+  /**
+   *  @param fontSizeInPixels  font size; by default characters
+   *         are 12 pixels in size.
+   */
+  public void setFontSize(double fontSizeInPixels)
+  {
+    this.fontSizeInPixels = fontSizeInPixels;
+  }
+
+  /**
+   *  @param align  one of: <ul>
+   *         <li>Text3D.ALIGN_FIRST
+   *         <li>Text3D.ALIGN_CENTER
+   *         <li>Text3D.ALIGN_LAST  </ul>
+   */
+  public void setAlign(int align)
+  {
+                this.align = align;
+  }
+
+  /**
+   *  @param path  one of: <ul>
+   *         <li>Text3D.PATH_RIGHT
+   *         <li>Text3D.PATH_LEFT
+   *         <li>Text3D.PATH_DOWN
+   *         <li>Text3D.PATH_UP  </ul>
+   */
+  public void setPath(int path)
+  {
+                this.path = path;
+  }
+
+  /**
+   *  Make the JLabelJ3D into a {@link javax.media.j3d.Shape3D}.
+   *
+   *  @param display  the VisAD display for this Label.
+   *
+   *  @return the JLabelJ3D description as a {@link javax.media.j3d.Shape3D}.
+   *  
+   *  @throws VisADException - VisAD couldn't make the geometry array.
+   */
+  public Object toDrawable(DisplayImpl display)
+    throws VisADException
+  {
+    return ScreenAnnotatorUtils.makeJLabelShape3D(
+      (DisplayImplJ3D)display, text,
+      xLocation, yLocation, colour, font, 
+      zValue, fontSizeInPixels, align, path);
+  }
+
+} // class JLabelJ3D
diff --git a/visad/bom/annotations/LabelJ3D.java b/visad/bom/annotations/LabelJ3D.java
new file mode 100644
index 0000000..c3bdb9a
--- /dev/null
+++ b/visad/bom/annotations/LabelJ3D.java
@@ -0,0 +1,402 @@
+//
+// LabelJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.TextControl;
+import visad.VisADException;
+
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+
+import visad.util.HersheyFont;
+
+import java.awt.Font;
+
+/**
+ *  Meant to encapsulate information representing a label which is
+ *  going to be rendered on a VisAD display without being
+ *  subject to the usual VisAD transformations. Thus the label should
+ *  stick in Screen Coordinates. Uses VisAD fonts.
+ *
+ *  Java3D dependent only in needing Shape3D.
+ */
+public class LabelJ3D implements ScreenAnnotation
+{
+  private String text;
+  private int xLocation;      // screen coordinates
+  private int yLocation;      // screen coordinates
+  private float[] colour;
+  // Only one of these may be non null
+  private Font font;           // null for the default
+  private HersheyFont hfont;   // null for the default
+  private TextControl.Justification horizontalJustification;
+  private TextControl.Justification verticalJustification;
+  private double fontSizeInPixels;
+  private double zValue;
+  private boolean filled;
+  private double thickness;
+  private double orientation;  // anticlockwise from x-axis in degrees.
+  private double charRotation;
+
+  /**
+   *  It constructs a LabelJ3D with the given text at the
+   *  origin, coloured white, left justified, horizontally aligned
+   *  with no character rotation.
+   *
+   *  @param text  text of the LabelJ3D.
+   */
+  public LabelJ3D(String text)
+        {
+    this(text, 0, 0, new float[] {1, 1, 1},
+      null, null, 0.0, 12.0, true, 1.0, 0.0,
+      TextControl.Justification.LEFT,
+      TextControl.Justification.BOTTOM, 0.0);
+  }
+
+  /**
+   *  It constructs a LabelJ3D with the given text at the
+   *  specified location, coloured white, left justified,
+   *  horizontally aligned with no character rotation.
+   *
+   *  @param text  text of the LabelJ3D.
+   *  @param xLocation  x position in screen coordinates.
+   *  @param yLocation  y position in screen coordinates.
+   */
+  public LabelJ3D(String text, int xLocation, int yLocation)
+        {
+    this(text, xLocation, yLocation, new float[] {1, 1, 1},
+      null, null, 0.0, 12.0, true, 1.0, 0.0,
+      TextControl.Justification.LEFT,
+      TextControl.Justification.BOTTOM,
+      0.0);
+  }
+
+  /**
+   *  Constructor for a filled font. It uses the java.awt.Font if it
+   *  is not null, otherwise it will use the Hershey font. If both are
+   *  null it will use the VisAD line font.
+   *
+   *  @param text  text of the LabelJ3D.
+   *  @param xLocation  x position in screen coordinates.
+   *  @param yLocation  y position in screen coordinates.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param font  {@link java.awt.Font} to use.
+   *  @param hfont  Hershey font to use; if both fonts are null
+   *         then use the default VisAD line font.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param fontSizeInPixels  font size; by default characters
+   *         are 12 pixels in size.
+   *  @param horizontalJustification  one of <ul>
+   *         <li> TextControl.Justification.LEFT
+   *         <li> TextControl.Justification.CENTER
+   *         <li> TextControl.Justification.RIGHT  </ul>
+   *  @param verticalJustification  one of <ul>
+   *         <li> TextControl.Justification.BOTTOM
+   *         <li> TextControl.Justification.CENTER
+   *         <li> TextControl.Justification.TOP  </ul>
+   */
+  public LabelJ3D(String text, int xLocation,
+    int yLocation, float[] colour, Font font, HersheyFont hfont,
+    double zValue, double fontSizeInPixels,
+    TextControl.Justification horizontalJustification,
+    TextControl.Justification verticalJustification)
+  {
+    this(text, xLocation, yLocation, colour, font, hfont,
+      zValue, fontSizeInPixels, true, 1.0, 0.0,
+      horizontalJustification, verticalJustification,
+      0.0);
+  }
+
+  /**
+   *  Constructor for a filled or unfilled font. It uses the java.awt.Font
+   *  if it is not null otherwise it will use the Hershey font. If both
+   *  are null it will use the VisAD line font.
+   *
+   *  @param text  text of the LabelJ3D.
+   *  @param xLocation  x position in screen coordinates.
+   *  @param yLocation  y position in screen coordinates.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param font  java.awt.Font to use.
+   *  @param hfont  Hershey font to use; if both fonts are null
+   *         then use the default VisAD line font.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param fontSizeInPixels  font size; by default characters
+   *         are 12 pixels in size.
+   *  @param filled  if <code>true</code> the font is rendered as filled,
+   *         if <code>false</code> just the triangles are drawn.
+   *  @param thickness  line width to use if just drawing triangles;
+   *          usually 1.0 is the most useful.
+   *  @param horizontalJustification  one of <ul>
+   *         <li> TextControl.Justification.LEFT
+   *         <li> TextControl.Justification.CENTER
+   *         <li> TextControl.Justification.RIGHT  </ul>
+   *  @param verticalJustification  one of <ul>
+   *         <li> TextControl.Justification.BOTTOM
+   *         <li> TextControl.Justification.CENTER
+   *         <li> TextControl.Justification.TOP  </ul>
+   */
+  public LabelJ3D(String text, int xLocation,
+    int yLocation, float[] colour, Font font, HersheyFont hfont,
+    double zValue, double fontSizeInPixels, boolean filled,
+    double thickness,
+    TextControl.Justification horizontalJustification,
+    TextControl.Justification verticalJustification)
+  {
+    this(text, xLocation, yLocation, colour, font, hfont,
+      zValue, fontSizeInPixels, filled, thickness, 0.0,
+      horizontalJustification, verticalJustification,
+      0.0);
+  }
+
+  /**
+   *  Constructor for LabelJ3D. It uses the {@link java.awt.Font} if it
+   *  is not null, otherwise it will use the Hershey font. If both are
+   *  null it will use the VisAD line font.
+   *
+   *  @param text  text of the LabelJ3D.
+   *  @param xLocation  x position in screen coordinates.
+   *  @param yLocation  y position in screen coordinates.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param font  {@link java.awt.Font} to use.
+   *  @param hfont  Hershey font to use; if both fonts are null
+   *         then use the default VisAD line font.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param fontSizeInPixels  font size; by default characters
+   *         are 12 pixels in size.
+   *  @param filled  if <code>true</code> the font is rendered as filled,
+   *         if <code>false</code> just the triangles are drawn.
+   *  @param thickness  line width to use if just drawing triangles;
+   *          usually 1.0 is the most useful.
+   *  @param orientation  anticlockwise from x-axis in degrees.
+   *  @param horizontalJustification  one of <ul>
+   *         <li> TextControl.Justification.LEFT
+   *         <li> TextControl.Justification.CENTER
+   *         <li> TextControl.Justification.RIGHT 
+   *  @param verticalJustification  one of <ul>
+   *         <li> TextControl.Justification.BOTTOM
+   *         <li> TextControl.Justification.CENTER
+   *         <li> TextControl.Justification.TOP </ul>
+   *  @param charRotation  rotate each character
+   *         <code>charRotation</code> degrees clockwise
+   *         from base line.
+   */
+  public LabelJ3D(String text, int xLocation,
+    int yLocation, float[] colour, Font font, HersheyFont hfont,
+    double zValue, double fontSizeInPixels, boolean filled,
+    double thickness, double orientation,
+    TextControl.Justification horizontalJustification,
+    TextControl.Justification verticalJustification,
+    double charRotation)
+  {
+    this.text = text;
+    this.xLocation = xLocation;
+    this.yLocation = yLocation;
+    this.colour = colour;
+    this.font = font;
+    if (font != null) {
+      hfont = null;
+    } else {
+      this.hfont = hfont;
+    }
+    this.zValue = zValue;
+    this.fontSizeInPixels = fontSizeInPixels;
+    this.filled = filled;
+    this.thickness = thickness;
+    this.orientation = orientation;
+    this.horizontalJustification = horizontalJustification;
+    this.verticalJustification = verticalJustification;
+    this.charRotation = charRotation;
+  }
+
+  /**
+   *  Set the text to render.
+   *
+   *  @param text  text of the LabelJ3D.
+   */
+  public void setText(String text)
+  {
+    this.text = text;
+  }
+
+  /**
+   *  Set the position on the screen at which the text is rendered.
+   *
+   *  @param xLocation  x position in screen coordinates.
+   *  @param yLocation  y position in screen coordinates.
+   */
+  public void setLocation(int xLocation, int yLocation)
+  {
+    this.xLocation = xLocation;
+    this.yLocation = yLocation;
+  }
+
+  /**
+   *  Set the colour used for the text.
+   *
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   */
+  public void setColour(float[] colour)
+  {
+                this.colour = colour;
+  }
+
+  /**
+   *  Set the font to use for the text.
+   *
+   *  @param font  {@link java.awt.Font} to use.
+   */
+  public void setFont(Font font)
+  {
+                this.font = font;
+    this.hfont = null;
+  }
+
+  /**
+   *  Set the Hershey font to use for the text.
+   *
+   *  @param hfont  Hershey font to use; any java.awt.Font is ignored.
+   */
+  public void setHersheyFont(HersheyFont hfont)
+  {
+                this.hfont = hfont;
+                this.font = null;
+  }
+
+  /**
+   *  Set the virtual world z value.
+   *
+   *  @param zValue  Virtual world value; larger z is in front.
+   */
+  public void setZValue(double zValue)
+  {
+                this.zValue = zValue;
+  }
+
+  /**
+   *  Set the font size in pixels.
+   *
+   *  @param fontSizeInPixels  font size; by default characters
+   *         are 12 pixels in size.
+   */
+  public void setFontSize(double fontSizeInPixels)
+  {
+                this.fontSizeInPixels = fontSizeInPixels;
+  }
+
+  /**
+   *  Set the line thickness for rendering the font. Useful in 
+   *  unfilled text.
+   *
+   *  @param thickness  line width to use if just drawing triangles;
+   *          usually 1.0 is the most useful.
+   */
+  public void setThickness(double thickness)
+  {
+                this.thickness = thickness;
+  }
+
+  /**
+   *  Set the flag to control whether the text is filled or not.
+   *
+   *  @param filled  if <code>true</code> the font is rendered as filled,
+   *         if <code>false</code> just the triangles are drawn.
+   */
+  public void setFilled(boolean filled)
+  {
+                this.filled = filled;
+  }
+
+  /**
+   *  Set the orientation of the text (in degrees).
+   *
+   *  @param orientation  anticlockwise from x-axis in degrees.
+   */
+  public void setOrientation(double orientation)
+  {
+                this.orientation = orientation;
+  }
+
+  /**
+   *  Set the horizontal justification of the text.
+   *
+   *  @param justification  one of TextControl.Justification.LEFT
+   *         TextControl.Justification.CENTER
+   *         TextControl.Justification.RIGHT 
+   */
+  public void setHorizontalJustification(TextControl.Justification 
+    justification)
+  {
+                this.horizontalJustification = justification;
+  }
+
+  /**
+   *  Set the vertical justification of the text.
+   *
+   *  @param justification  one of TextControl.Justification.BOTTOM
+   *         TextControl.Justification.CENTER
+   *         TextControl.Justification.TOP 
+   */
+  public void setVerticalJustification(TextControl.Justification 
+    justification)
+  {
+                this.verticalJustification = justification;
+  }
+
+  /**
+   *  Set the amount each character is rotated.
+   *
+   *  @param charRotation  rotate each character
+   *         <code>charRotation</code> degrees clockwise
+   *         from base line.
+   */
+  public void setCharRotation(double charRotation)
+  {
+    this.charRotation = charRotation;
+  }
+
+  /**
+   *  Make the LabelJ3D into a {@link javax.media.j3d.Shape3D}.
+   *
+   *  @param display  the VisAD display for this Label.
+   *
+   *  @return the LabelJ3D description as a Shape3D.
+   *  
+   *  @throws VisADException - VisAD couldn't make the geometry array.
+   */
+  public Object toDrawable(DisplayImpl display)
+    throws VisADException
+  {
+    return ScreenAnnotatorUtils.makeLabelShape3D(
+      (DisplayImplJ3D)display, text,
+      xLocation, yLocation, colour, font, hfont,
+      zValue, fontSizeInPixels, filled, thickness,
+      orientation,
+      horizontalJustification, verticalJustification,
+      charRotation);
+  }
+
+} // class LabelJ3D
diff --git a/visad/bom/annotations/LineJ3D.java b/visad/bom/annotations/LineJ3D.java
new file mode 100644
index 0000000..db0c3c6
--- /dev/null
+++ b/visad/bom/annotations/LineJ3D.java
@@ -0,0 +1,217 @@
+//
+// LineJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+ *  Meant to encapsulate information representing a Line which is
+ *  going to be rendered on a VisAD display without being
+ *  subject to the usual VisAD transformations. Thus the label should
+ *  stick in Screen Coordinates.
+ */
+public class LineJ3D implements ScreenAnnotation
+{
+  /** line style SOLID */
+  public static final int SOLID = 4;
+
+  /** line style DASH */
+  public static final int DASH = 5;
+
+  /** line style DOT */
+  public static final int DOT = 6;
+
+  /** line style DASH_DOT */
+  public static final int DASH_DOT = 7;
+
+  private int style;
+  private int x1, y1;   // points of line in screen coordinates
+  private int x2, y2;
+  private float[] colour;
+  private double zValue;
+  private double thickness;    // for line and point style
+
+  /**
+   *  Simple constructor which makes a zero length line at (0, 0)
+   *  coloured white. More meaningful values can be set
+   *  after construction.
+   */
+  public LineJ3D()
+  {
+    this(SOLID, 0, 0, 0, 0,
+      new float[] {1,1,1}, 0.0, 1.0);
+  }
+
+  /**
+   *  Constructs a solid LineJ3D from 2 points specified
+   *  in screen coordinates.
+   *  
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param thickness  of the line.
+   */
+  public LineJ3D(int x1, int y1, int x2, int y2,
+    float[] colour, double zValue, double thickness)
+  {
+    this(SOLID, x1,y1, x2,y2, 
+      colour, zValue, thickness);  
+  }
+
+  /**
+   *  Constructs a LineJ3D from 2 points specified in screen coordinates.
+   *
+   *  @param style  one of: <ul>
+   *                 <li> LineJ3D.SOLID
+   *                 <li> LineJ3D.DASH
+   *                 <li> LineJ3D.DOT 
+   *                 <li> LineJ3D.DASH_DOT </ul>
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 2 points (columns).
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param zValue  Virtual world value; larger z is in front
+   *  @param thickness  of the line.
+   */
+  public LineJ3D(int style, int[][] points,
+    float[] colour, double zValue, double thickness)
+  {
+    this(style, points[0][0], points[1][0],
+      points[0][1], points[1][1],
+      colour, zValue, thickness);  
+  }
+
+  /**
+   *  Constructs a LineJ3D from 2 points specified
+   *  in screen coordinates.
+   *  
+   *  @param style  one of: <ul>
+   *                 <li> LineJ3D.SOLID
+   *                 <li> LineJ3D.DASH
+   *                 <li> LineJ3D.DOT 
+   *                 <li> LineJ3D.DASH_DOT </ul>
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param thickness  of the line.
+   */
+  public LineJ3D(int style,
+    int x1, int y1, int x2, int y2,
+    float[] colour, double zValue, double thickness)
+  {
+    this.x1 = x1;
+    this.y1 = y1;
+    this.x2 = x2;
+    this.y2 = y2;
+    this.style = style;
+    this.colour = colour;
+    this.zValue = zValue;
+    this.thickness = thickness;
+  }
+
+  /**
+   *  Set the coordinates for the start and end points of the LineJ3D.
+   *
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   */
+  public void setPoints(int x1, int y1, int x2, int y2)
+  {
+    this.x1 = x1;
+    this.y1 = y1;
+    this.x2 = x2;
+    this.y2 = y2;
+  } 
+
+  /**
+   *  Set the coordinates for the start and end points of the LineJ3D.
+   *
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 2 points (columns).
+   */
+  public void setPoints(int[][] points)
+  {
+    setPoints(points[0][0], points[1][0],
+        points[0][1], points[1][1]);
+  }
+
+  /**
+   *  Set the line style for the LineJ3D.
+   *
+   *  @param style  one of: <ul>
+   *                 <li> LineJ3D.SOLID
+   *                 <li> LineJ3D.DASH
+   *                 <li> LineJ3D.DOT
+   *                 <li> LineJ3D.DASH_DOT </ul>
+   */
+  public void setStyle(int style)
+  {
+    this.style = style;
+  }
+
+  /**
+   *  Set the colour for the LineJ3D.
+   *
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   */
+  public void setColour(float[] colour)
+  {
+    this.colour = colour;
+  }
+
+  /**
+   *  Set the Z value for the LineJ3D.
+   *
+   *  @param zValue  Virtual world value; larger z is in front.
+   */
+  public void setZValue(double zValue)
+  {
+    this.zValue = zValue;
+  }
+
+  /**
+   *  Make the LineJ3D into a {@link javax.media.j3d.Shape3D}.
+   *
+   *  @param display  the VisAD display for this Line.
+   *
+   *  @return the LineJ3D description as a Shape3D object.
+   */
+  public Object toDrawable(DisplayImpl display)
+  {
+    return ScreenAnnotatorUtils.makeLineShape3D(
+      (DisplayImplJ3D)display, style,
+      x1, y1, x2, y2, colour, zValue, thickness);
+  }
+} // class LineJ3D
diff --git a/visad/bom/annotations/PointJ3D.java b/visad/bom/annotations/PointJ3D.java
new file mode 100644
index 0000000..e9efc32
--- /dev/null
+++ b/visad/bom/annotations/PointJ3D.java
@@ -0,0 +1,147 @@
+//
+// PointJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+ *  Meant to encapsulate information representing a Point which is
+ *  going to be rendered on a VisAD display without being
+ *  subject to the usual VisAD transformations. Thus the label should
+ *  stick in Screen Coordinates.
+ */
+public class PointJ3D implements ScreenAnnotation
+{
+
+  private int x1, y1;    // point in screen coordinates
+  private float[] colour;
+  private double zValue;
+  private double thickness;      // for point size
+
+  /**
+   *  Simple constructor which makes a point at (0, 0)
+   *  coloured white. More meaningful values can be set 
+   *  after construction.
+   */
+  public PointJ3D()
+  {
+    this(0, 0, new float[] {1,1,1}, 0, 1.0);
+  }
+
+  /** 
+   *  Constructs a PointJ3D from specified values in screen coordinates.
+   *
+   *  @param x1  x screen coordinate of the point.
+   *  @param y1  y screen coordinate of the point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param thickness  used for the size of the point.
+   */
+  public PointJ3D(int x1, int y1,
+    float[] colour, double zValue, double thickness)
+  {
+    this.x1 = x1;
+    this.y1 = y1;
+    this.colour = colour;
+    this.zValue = zValue;
+    this.thickness = thickness;
+  }
+
+  /**
+   *  Constructs a PointJ3D from specified values in screen coordinates.
+   *
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 1 point (column).
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param thickness  used for the size of the point.
+   */
+  public PointJ3D(int[][] points,
+    float[] colour, double zValue, double thickness)
+  {
+    this(points[0][0], points[1][0],
+      colour, zValue, thickness);  
+  }
+
+  /**
+   *  Set coordinates for the PointJ3D.
+   *
+   *  @param x1  x screen coordinate of the point.
+   *  @param y1  y screen coordinate of the point.
+   */
+  public void setPointJ3Ds(int x1, int y1)
+  {
+    this.x1 = x1;
+    this.y1 = y1;
+  } 
+
+  /**
+   *  Set coordinates for the PointJ3D.
+   *
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 1 point (column).
+   */
+  public void setPointJ3Ds(int[][] points)
+  {
+    setPointJ3Ds(points[0][0], points[1][0]);
+  }
+
+  /**
+   *  Set colour for the PointJ3D.
+   *
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   */
+  public void setColour(float[] colour)
+  {
+    this.colour = colour;
+  }
+
+  /**
+   *  Set Z position for the PointJ3D.
+   *
+   *  @param zValue  Virtual world value; larger z is in front.
+   */
+  public void setZValue(double zValue)
+  {
+    this.zValue = zValue;
+  }
+
+  /**
+   *  Make the PointJ3D into a {@link javax.media.j3d.Shape3D}.
+   *
+   *  @param display  the VisAD display for this Point.
+   *
+   *  @return the Triangle description as a Shape3D object.
+   */
+  public Object toDrawable(DisplayImpl display)
+  {
+    return ScreenAnnotatorUtils.makePointShape3D(
+      (DisplayImplJ3D)display,
+      x1, y1, colour, zValue, thickness);
+  }
+} // class PointJ3D
diff --git a/visad/bom/annotations/QuadrilateralJ3D.java b/visad/bom/annotations/QuadrilateralJ3D.java
new file mode 100644
index 0000000..f7c5052
--- /dev/null
+++ b/visad/bom/annotations/QuadrilateralJ3D.java
@@ -0,0 +1,309 @@
+//
+// QuadrilateralJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+ *  Meant to encapsulate information representing a Quadrilateral which is
+ *  going to be rendered on a VisAD display without being
+ *  subject to the usual VisAD transformations. Thus the label should
+ *  stick in Screen Coordinates.
+ */
+public class QuadrilateralJ3D implements ScreenAnnotation
+{
+  /** fill style POINT */
+  public static final int POINT = 1;
+  /** fill style LINE */
+  public static final int LINE = 2;
+  /** fill style FILL */
+  public static final int FILL = 3;
+
+  private int style;
+  // For a "box" (x1, y1) is top left, (x2-x1) is width, (y3-y2) is height
+  private int x1, y1;
+  private int x2, y2;
+  private int x3, y3;
+  private int x4, y4;
+  private float[] colour;
+  private double zValue;
+  private double thickness;          // for line and point style
+
+  /**
+   *  Simple constructor which makes a zero size "box" at (0, 0)
+   *  coloured white. More meaningful values can be set
+   *  after construction.
+   */
+  public QuadrilateralJ3D()
+  {
+    this(FILL, 0, 0, 0, 0, new float[] {1,1,1}, 0, 1.0);
+  }
+
+  /**
+   *  Constructor to make an upright "box" with the given specifications;
+   *  the box should not be self intesecting.
+   *
+   *  @param style  one of <ul>
+   *                <li> QuadrilateralJ3D.FILL, </li>
+   *                <li> QuadrilateralJ3D.LINE, </li>
+   *                <li> QuadrilateralJ3D.POINT.</li> </ul>
+   *  @param x  top left x value in screen coordinates.
+   *  @param y  top left y value in screen coordinates.
+   *  @param width  width, in pixels, of the "box".
+   *  @param height  height, in pixels, of the "box".
+   *  @param colour  red, green blue triple each in the range [0.0, 1.0].
+   *  @param zValue  Virtual world value - larger z is closer to eye.
+   *  @param thickness  used for outline thickness and point size.
+   */
+  public QuadrilateralJ3D(int style, int x, int y,
+    int width, int height, float[] colour, double zValue,
+    double thickness)
+  {
+    this(style, x, y, x + width, y, x + width,
+      y + height, x, y + height, colour, zValue, thickness);
+  }
+
+  /**
+   *  Constructor to make an arbitrary rectangle with the given
+   *  specifications; it should not be self intersecting.
+   *
+   *  @param style  one of <ul>
+   *                <li> QuadrilateralJ3D.FILL, </li>
+   *                <li> QuadrilateralJ3D.LINE, </li>
+   *                <li> QuadrilateralJ3D.POINT.</li> </ul>
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 4 points (columns).
+   *  @param colour  red, green blue triple each in the range [0.0, 1.0]
+   *  @param zValue  Virtual world value - larger z is closer to eye.
+   *  @param thickness  used for outline thickness and point size.
+   */
+  public QuadrilateralJ3D(int style,
+    int[][] points,
+    float[] colour, double zValue, double thickness)
+  {
+    this(style, points[0][0], points[1][0],
+      points[0][1], points[1][1], points[0][2], points[1][2],
+      points[0][3], points[1][3], colour, zValue, thickness);
+  }
+
+  /**
+   *  Constructor to make an arbitrary rectangle with the given
+   *  specifications; it should not be self intersecting.
+   *
+   *  @param style  one of <ul>
+   *                <li> QuadrilateralJ3D.FILL, </li>
+   *                <li> QuadrilateralJ3D.LINE, </li>
+   *                <li> QuadrilateralJ3D.POINT.</li> </ul>
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param x3  x screen coordinate of the third point.
+   *  @param y3  y screen coordinate of the third point.
+   *  @param x4  x screen coordinate of the fourth point.
+   *  @param y4  y screen coordinate of the fourth point.
+   *  @param colour  red, green blue triple each in the range [0.0, 1.0].
+   *  @param zValue  Virtual world value - larger z is closer to eye.
+   *  @param thickness  used for outline thickness and point size.
+   */
+  public QuadrilateralJ3D(int style,
+    int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4,
+    float[] colour, double zValue, double thickness)
+  {
+    this.style = style;
+    this.x1 = x1;
+    this.y1 = y1;
+    this.x2 = x2;
+    this.y2 = y2;
+    this.x3 = x3;
+    this.y3 = y3;
+    this.x4 = x4;
+    this.y4 = y4;
+    this.colour = colour;
+    this.zValue = zValue;
+    this.thickness = thickness;
+  }
+
+  /**
+   *  Set the drawing style of the quadrilateral.
+   *
+   *  @param style  one of <ul>
+   *                <li> QuadrilateralJ3D.FILL, </li>
+   *                <li> QuadrilateralJ3D.LINE, </li>
+   *                <li> QuadrilateralJ3D.POINT.</li> </ul>
+   */
+  public void setStyle(int style)
+  {
+    this.style = style;
+  }
+
+  /**
+   *  Applies a shift to the quadrilateral to place the first point
+   *  on the given coordinates.
+   *  <p>
+   *  Expects a "box" i.e. a vertically aligned rectangle
+   *  with points enumerated clockwise. This sets top left to (x, y)
+   *  with other points adjusted accordingly. If it is not a "box"
+   *  then all points are shifted as though moving the first
+   *  point to (x, y).
+   *
+   *  @param x  new top left x pixel value.
+   *  @param y  new top left y pixel value.
+   */
+  public void setLocation(int x, int y)
+  {
+    int w = x1 - x;
+    int h = y1 - y;
+    x1 = x;
+    y1 = y;
+    x2 = x2 - w;
+    y2 = y2 - h;
+    x3 = x3 - w;
+    y3 = y3 - h;
+    x4 = x4 - w;
+    y4 = y4 - h;
+  }
+
+  /**
+   * Constructs a rectangular box using the existing first point
+   * as the top left point and the input width and height.
+   *
+   * @param width  width, in pixels, of the "box".
+   * @param height  height, in pixels, of the "box".
+   */
+  public void setSize(int width, int height)
+  {
+    x2 = x1 + width;
+    y2 = y1;
+    x3 = x2;
+    y3 = y1 + height;
+    x4 = x1;
+    y4 = y2;
+  }
+
+  /**
+   *  Sets the points for an arbitrary quadrilateral; should not
+   *  be self intersecting.
+   *
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 4 points (columns).
+   */
+  public void setPoints(int[][] points)
+  {
+    setPoints(points[0][0], points[1][0],
+        points[0][1], points[1][1],
+        points[0][2], points[1][2],
+        points[0][3], points[1][3]);
+  }
+
+  /**
+   *  Makes an upright "box".
+   *
+   * @param x  top left x value in screen coordinates.
+   * @param y  top left y value in screen coordinates.
+   * @param width  width, in pixels, of the "box".
+   * @param height  height, in pixels, of the "box".
+   */
+  public void setPoints(int x, int y, int width, int height)
+  {
+    x1 = x;
+    y1 = y;
+    setSize(width, height);
+  }
+
+  /**
+   *  Sets the 4 points for an arbitrary quadrilateral; should not
+   *  be self intersecting.
+   *
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param x3  x screen coordinate of the third point.
+   *  @param y3  y screen coordinate of the third point.
+   *  @param x4  x screen coordinate of the fourth point.
+   *  @param y4  y screen coordinate of the fourth point.
+   */
+  public void setPoints(
+    int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4)
+  {
+    this.x1 = x1;
+    this.y1 = y1;
+    this.x2 = x2;
+    this.y2 = y2;
+    this.x3 = x3;
+    this.y3 = y3;
+    this.x4 = x4;
+    this.y4 = y4;
+  }
+
+  /**
+   *  Set the colour for the quadrilateral.
+   *
+   *  @param colour  red, green blue triple each in the range [0.0, 1.0]
+   */
+  public void setColour(float[] colour)
+  {
+    this.colour = colour;
+  }
+
+  /**
+   *  Set the Z value for the quadrilateral.
+   *
+   *  @param zValue  Virtual world value - larger z is closer to eye.
+   */
+  public void setZValue(double zValue)
+  {
+    this.zValue = zValue;
+  }
+
+  /**
+   *  Set the thickness for the quadrilateral.
+   *
+   *  @param thickness  used for outline thickness and point size.
+   */
+  public void setThickness(double thickness)
+  {
+    this.thickness = thickness;
+  }
+
+  /**
+   *  Make the QuadrilateralJ3D into a {@link javax.media.j3d.Shape3D}.
+   *
+   *  @param display  the VisAD display for this Quadrilateral.
+   *
+   *  @return the QuadrilateralJ3D description as a Shape3D object
+   */
+  public Object toDrawable(DisplayImpl display)
+  {
+    return ScreenAnnotatorUtils.makeQuadrilateralShape3D(
+      (DisplayImplJ3D)display, style,
+      x1, y1, x2, y2, x3, y3, x4, y4, colour,
+      zValue, thickness);
+  }
+} // class QuadrilateralJ3D
diff --git a/visad/bom/annotations/ScreenAnnotation.java b/visad/bom/annotations/ScreenAnnotation.java
new file mode 100644
index 0000000..dbec8fc
--- /dev/null
+++ b/visad/bom/annotations/ScreenAnnotation.java
@@ -0,0 +1,50 @@
+//
+// ScreenAnnotation.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.DisplayImpl;
+import visad.VisADException;
+
+/**
+ *  This ensures that all objects being used for annotations onto
+ *  a VisAD display have the method toDrawable() as required for
+ *  {@link ScreenAnnotator}.
+ */
+public interface ScreenAnnotation
+{
+  /**
+   *  Make the Object into a {@link javax.media.j3d.Shape3D}.
+   *
+   *  @param display  the VisAD display for this Annotation.
+   *
+   *  @return the <code>ScreenAnnotation</code> as an object
+   *          which can be drawn.
+   *
+   *  @throws VisADException  there's a problem with VisAD.
+   */
+  Object toDrawable(DisplayImpl display) throws VisADException;
+}
diff --git a/visad/bom/annotations/ScreenAnnotator.java b/visad/bom/annotations/ScreenAnnotator.java
new file mode 100644
index 0000000..c641f32
--- /dev/null
+++ b/visad/bom/annotations/ScreenAnnotator.java
@@ -0,0 +1,107 @@
+//
+// ScreenAnnotator.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.VisADException;
+
+import java.util.ArrayList;
+
+/**
+ * Take descriptions of geometry primatives in Screen Coordinates
+ * and draw them onto a VisAD display directly. The objects should then
+ * stay on the same screen position.
+ *
+ * Enables thing like scale bars, tables, logos, etc to be independent
+ * of data manipulation tranformations.
+ */
+public abstract class ScreenAnnotator
+{
+  // Objects to be diaplayed.
+  protected ArrayList things = null;
+
+  /**
+   * Default constructor.
+   */
+  public ScreenAnnotator()
+  {
+    things = new ArrayList();
+  }
+
+  /**
+   *  Set the visibility flag.
+   *
+   *  @param on  <code>true</code> to display, <code>false</code> to undisplay.
+   */
+  public abstract void  makeVisible(boolean on);
+
+  /**
+   *  Traverses all the data objects in the list and transforms
+   *  them into viewable objects and then arranges to make them
+   *  visible.
+   *
+   *  @throws VisADException if if any problem creating the picture.
+   */
+  public abstract void draw()
+    throws VisADException;
+
+  /**
+   *  Add the object to the list of items to be drawn.
+   *
+   *  @param object  add this item to the list of things
+   *                  to draw.
+   */
+  public void add(Object object)
+  {
+    synchronized (things) {
+      things.add(object);
+    }
+  }
+
+  /**
+   *  Remove the object from the list of items to be drawn.
+   *
+   *  @param object  remove this item from the list of things
+   *                  to draw.
+   */
+  public void remove(Object object)
+  {
+    synchronized (things) {
+      things.remove(object);
+    }
+  }
+
+  /**
+   *  Remove all items from the list of things to draw.
+   *                  to draw.
+   */
+  public void clear()
+  {
+    synchronized (things) {
+      things.clear();
+    }
+  }
+} // class ScreenAnnotator
diff --git a/visad/bom/annotations/ScreenAnnotatorJ3D.java b/visad/bom/annotations/ScreenAnnotatorJ3D.java
new file mode 100644
index 0000000..0405590
--- /dev/null
+++ b/visad/bom/annotations/ScreenAnnotatorJ3D.java
@@ -0,0 +1,121 @@
+//
+// ScreenAnnotatorJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.DisplayRendererJ3D;
+import visad.VisADException;
+
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Node;
+
+import java.util.Iterator;
+
+/**
+ * Take descriptions of geometry primatives in Screen Coordinates
+ * and draw them onto a VisAD display directly. The objects should then
+ * stay on the same screen position.
+ *
+ * Enables thing like scale bars, tables, logos, etc to be independent
+ * of data manipulation tranformations.
+ */
+public class ScreenAnnotatorJ3D extends ScreenAnnotator
+{
+  private DisplayImplJ3D display;
+  private DisplayRendererJ3D renderer;
+  private BranchGroup group;
+
+  /**
+   *  Construct a ScreenAnnotatorJ3D for the given {@link DisplayImpl}.
+   *
+   *  @param display  the VisAD Display for the ScreenAnnotatorJ3D.
+   */
+  public ScreenAnnotatorJ3D(DisplayImpl display)
+  {
+    this.display = (DisplayImplJ3D)display;
+    renderer = (DisplayRendererJ3D)display.getDisplayRenderer();
+    group = new BranchGroup();
+    // What set of capabilities is really required?
+    group.setCapability(BranchGroup.ALLOW_DETACH);
+    group.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+  }
+
+  /**
+   *  Set the visibility flag.
+   *
+   *  @param on  <code>true</code> to display, <code>false</code> to undisplay.
+   */
+  public void makeVisible(boolean on)
+  {
+    if (on) { // check if already attached - or is Live?
+      if (!group.isLive()) {
+        renderer.getRoot().addChild(group);
+      }
+    } else {
+      if (group.isLive()) {
+        group.detach();
+      }
+    }
+  } // makeVisible
+
+	/**
+	 * Traverses all the data objects and transforms them into
+	 * {@link javax.media.j3d.Shape3D} objects and adds them to a BranchGoup
+	 * which it then attaches to the scene graph.
+	 * 
+	 * @throws VisADException
+	 *             if {@link LabelJ3D} throws the exception.
+	 */
+  
+  public void draw()
+    throws VisADException
+  {
+    synchronized (group) {
+      Node node = null;
+      Iterator it = null;
+      makeVisible(false);
+      // removeAllChildren() not in my version of java3d ?
+      for (int i=group.numChildren()-1; i>=0; i--) {
+        group.removeChild(i);
+      }
+  
+      ScreenAnnotation sa;
+      if (things != null) {
+        it = things.iterator();
+        while (it.hasNext()) {
+          sa = (ScreenAnnotation)it.next();
+          node = (Node)sa.toDrawable(display);
+          group.addChild(node);
+        }
+      }
+  
+      renderer.getRoot().addChild(group);
+      makeVisible(true);
+    }
+  } // draw()
+} // class ScreenAnnotatorJ3D
diff --git a/visad/bom/annotations/ScreenAnnotatorUtils.java b/visad/bom/annotations/ScreenAnnotatorUtils.java
new file mode 100644
index 0000000..55b1365
--- /dev/null
+++ b/visad/bom/annotations/ScreenAnnotatorUtils.java
@@ -0,0 +1,875 @@
+//
+// ScreenAnnotatorUtils.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.PlotText;
+import visad.TextControl;
+import visad.VisADException;
+import visad.VisADGeometryArray;
+import visad.java3d.VisADCanvasJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.DisplayRendererJ3D;
+
+import visad.util.HersheyFont;
+
+import java.awt.Font;
+import java.awt.Image;
+import java.awt.image.ColorModel;
+import java.awt.image.PixelGrabber;
+
+import javax.media.j3d.Appearance;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Canvas3D;
+import javax.media.j3d.ColoringAttributes;
+import javax.media.j3d.Font3D;
+import javax.media.j3d.GeometryArray;
+import javax.media.j3d.LineArray;
+import javax.media.j3d.LineAttributes;
+import javax.media.j3d.PointArray;
+import javax.media.j3d.PointAttributes;
+import javax.media.j3d.PolygonAttributes;
+import javax.media.j3d.QuadArray;
+import javax.media.j3d.Shape3D;
+import javax.media.j3d.Text3D;
+import javax.media.j3d.Transform3D;
+import javax.media.j3d.TransformGroup;
+import javax.media.j3d.TransparencyAttributes;
+import javax.media.j3d.TriangleArray;
+import javax.media.j3d.View;
+
+import javax.vecmath.Point2d;
+import javax.vecmath.Point3d;
+import javax.vecmath.Point3f;
+import javax.vecmath.Vector3d;
+
+/**
+ *  This is a collection of static methods to help with the construction
+ *  of java3D objects used for annotating a VisAD display. The objects are
+ *  intended to be drawn directly to the screen using screen pixel
+ *  locations. See {@link ScreenAnnotator}.
+ *
+ *  This is meant to contain as much of the Java3D dependencies as posible.
+ */
+public class ScreenAnnotatorUtils
+{
+
+  /**
+   *  Construct a {@link BranchGroup} object from a routine
+   *  description of a Label using {@link Text3D}. A label is a Text
+   *  string with attributes used for drawing it.
+   *
+   *  @param display  the VisAD display for this Label.
+   *  @param text  the string.
+   *  @param x  x screen coordinate of the text.
+   *  @param y  y screen coordinate of the text.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param font  {@link java.awt.Font} to use.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param fontSizeInPixels  height of text.
+   *  @param align  one of: <ul>
+   *         <li>Text3D.ALIGN_FIRST
+   *         <li>Text3D.ALIGN_CENTER
+   *         <li>Text3D.ALIGN_LAST  </ul>
+   *  @param path  one of: <ul>
+   *         <li>Text3D.PATH_RIGHT
+   *         <li>Text3D.PATH_LEFT
+   *         <li>Text3D.PATH_DOWN
+   *         <li>Text3D.PATH_UP  </ul>
+   *  @return the {@link BranchGroup}, suitably scaled, representing the 
+   *          label.
+   */
+  public static BranchGroup makeJLabelShape3D(DisplayImplJ3D display,
+    String text, int x, int y, float[] colour, Font font,
+    double zValue, double fontSizeInPixels, int align, int path)
+  {
+    DisplayRendererJ3D renderer =
+      (DisplayRendererJ3D)display.getDisplayRenderer();
+    VisADCanvasJ3D canvas = renderer.getCanvas();
+
+    // get the start point  and one pixel up in vworld coordinates
+    int[][] screenXY = { {x, x},
+                         {y, y-1}  // just one pixel up
+    };
+    Point3d[] points = screenToVworld(screenXY, canvas);
+
+    //adjust z
+    ditherPoints(canvas, 0.4, points);
+    adjustZ(display, canvas, zValue, points);
+
+    // scale by distance in vworld representing one pixel
+    double scale = fontSizeInPixels*
+      (points[1].y - points[0].y)/font.getSize2D();
+ 
+    // make the Text3D geometry object
+    Font3D font3D = new Font3D(font, null); // no extrusion
+    // create at Origin - scale and move later
+    Text3D text3D = new Text3D(font3D, text,
+      new Point3f((float)0, (float)0, (float)0), align, path);
+
+    // make some colours
+    ColoringAttributes textColor = new ColoringAttributes();
+    textColor.setColor(colour[0], colour[1], colour[2]);
+
+    Appearance textAppearance = new Appearance();
+    textAppearance.setColoringAttributes(textColor);
+
+    Shape3D textShape = new Shape3D(text3D, textAppearance);
+
+    // Build a Transformation 
+    // create at Origin, Scale and move to point
+    Transform3D tMove = new Transform3D();
+    tMove.set(new Vector3d(points[0].x, points[0].y, points[0].z));
+    Transform3D tScale = new Transform3D();
+    tScale.set(scale);
+    tMove.mul(tScale);
+
+    TransformGroup tg = new TransformGroup();
+    Transform3D t3D = new Transform3D();
+    tg.setTransform(tMove);
+
+    tg.addChild(textShape);
+
+    BranchGroup bg = new BranchGroup();
+    bg.addChild(tg);
+
+    return bg;
+  } // makeJLabelShape3D
+
+  /**
+   *  Construct a {@link Shape3D} object from a routine description
+   *  of a Label using 2D fonts. A label is a Text string with
+   *  attributes used drawing it.
+   *
+   *  @param display  the VisAD display for this Label.
+   *  @param text  the string.
+   *  @param x  x screen coordinate of the text.
+   *  @param y  y screen coordinate of the text.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param font  {@link java.awt.Font} to use.
+   *  @param hfont  Hershey font to use; if both fonts are null
+   *         then use the default VisAD line font.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param scaleFactor  scale the font by this factor; by default
+   *                       characters are 1 pixel in size.
+   *  @param filled  if <code>true</code> the font is rendered as filled,
+   *         if <code>false</code> just the triangles are drawn.
+   *  @param thickness  line width to use if just drawing triangles;
+   *          usually 1.0 is the most useful.
+   *  @param orientation  angle of rotation of the text anticlockwise
+   *         from the horizontal.
+   *  @param horizontal  one of:<ul>
+   *         <li> TextControl.Justification.LEFT - Left justified
+   *              text (ie: normal text)
+   *         <li> TextControl.Justification.CENTER - Centered text
+   *         <li> TextControl.Justification.RIGHT - Right justified text
+   *         </ul>
+   *  @param vertical  one of:<ul>
+   *         <li> TextControl.Justification.BOTTOM - Bottom justified
+   *              text (normal).
+   *         <li> TextControl.Justification.TOP - Top justified text
+   *         <li> TextControl.Justification.CENTER - Centered text
+   *         </ul>
+   *  @param charRotation  rotate each character
+   *         <code>charRotation</code> degrees clockwise from base line.
+   *
+   *  @return the Label description as a {@link Shape3D}.
+   *
+   *  @throws VisADException  VisAD couldn't make the geometry array.
+   */
+  public static Shape3D makeLabelShape3D(DisplayImplJ3D display,
+    String text, int x, int y, float[] colour,
+    Font font, HersheyFont hfont, double zValue,
+    double scaleFactor, boolean filled, double thickness,
+    double orientation,
+    TextControl.Justification horizontal,
+    TextControl.Justification vertical,
+    double charRotation)
+    throws VisADException
+  {
+    Shape3D shape = null;
+    DisplayRendererJ3D renderer =
+      (DisplayRendererJ3D)display.getDisplayRenderer();
+    VisADCanvasJ3D canvas = renderer.getCanvas();
+
+    // set up some stuff in the Virtual World
+    Point3d position1 = new Point3d();
+    Point3d position2 = new Point3d();
+    Point3d position3 = new Point3d();
+    canvas.getPixelLocationInImagePlate(x, y, position1);
+    // possible different scale in x and y direction?
+    // use default of 1 pixel for font height
+    canvas.getPixelLocationInImagePlate(x+1, y, position2);
+    double pixelInImagePlate = position2.x - position1.x;
+    position2.x = position1.x + pixelInImagePlate*
+      Math.cos(Math.toRadians(orientation));
+    position2.y = position1.y + pixelInImagePlate*
+      Math.sin(Math.toRadians(orientation));
+    position2.z = position1.z;
+    position3.x = position1.x - pixelInImagePlate*
+      Math.sin(Math.toRadians(orientation));
+    position3.y = position1.y + pixelInImagePlate*
+      Math.cos(Math.toRadians(orientation));
+    position3.z = position1.z;
+
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    t.transform(position1);
+    t.transform(position2);
+    t.transform(position3);
+
+    // adjust z
+    Point3d[] textStart = {position1, position2, position3};
+    ditherPoints(canvas, 0.4, textStart);
+    adjustZ(display, canvas, zValue, textStart);
+
+    position1 = textStart[0];
+    position2 = textStart[1];
+    position3 = textStart[2];
+    double[] start = {(double) position1.x,
+                      (double) position1.y,
+                      (double) position1.z};
+    double[] base =  {(double) (position2.x - position1.x),
+                      (double) (position2.y - position1.y),
+                      (double) (position2.z - position1.z)};
+    double[] up =    {(double) (position3.x - position1.x),
+                      (double) (position3.y - position1.y),
+                      (double) (position3.z - position1.z)};
+
+    // scale is now in visad PlotText.java but do here
+    // and put scale = 1 in call to PlotText
+    for (int i=0; i<3; i++) {
+      up[i] = scaleFactor*up[i];
+      base[i] = scaleFactor*base[i];
+    }
+
+    // always use offset of zero. Just change x y if offset wanted
+    double[] offset = {0.0, 0.0, 0.0};
+    VisADGeometryArray array = null;
+    if (font != null) {
+      // to be compatable with versions of VisAD with old
+      // PlotText call the old interface if charRotation
+      // is zero
+      if (charRotation == 0.0) {
+        array = PlotText.render_font(text, font, start,
+          base, up, horizontal, vertical);
+      } else {
+        array = PlotText.render_font(text, font, start,
+          base, up, horizontal, vertical, charRotation, 1.0, offset);
+      }
+    } else if (hfont != null) {
+      if (charRotation == 0.0) {
+        array = PlotText.render_font(text, hfont, start,
+          base, up, horizontal, vertical);
+      } else {
+        array = PlotText.render_font(text, hfont, start,
+          base, up, horizontal, vertical, charRotation, 1.0, offset);
+      }
+    } else { // font && hfont are null
+      if (charRotation == 0.0) {
+        array = PlotText.render_label(text, start, base,
+          up, horizontal, vertical);
+      } else {
+        array = PlotText.render_label(text, start, base,
+          up, horizontal, vertical, charRotation, 1.0, offset);
+      }
+    }
+
+    GeometryArray geom = display.makeGeometry(array);
+
+    Appearance appearance = new Appearance();
+
+    ColoringAttributes coloringAttributes =
+      new ColoringAttributes();
+    coloringAttributes.setColor(colour[0], colour[1], colour[2]);
+    appearance.setColoringAttributes(coloringAttributes);
+
+    LineAttributes lineAttributes = new LineAttributes();
+    lineAttributes.setLineWidth((float)thickness);
+    appearance.setLineAttributes(lineAttributes);
+
+    PolygonAttributes polygonAttributes = new PolygonAttributes();
+    polygonAttributes.setCullFace(PolygonAttributes.CULL_NONE);
+    // should be this by default but didn't render the font
+    if (filled) {
+      polygonAttributes.setPolygonMode(PolygonAttributes.POLYGON_FILL);
+    } else {
+      polygonAttributes.setPolygonMode(PolygonAttributes.POLYGON_LINE);
+    }
+    appearance.setPolygonAttributes(polygonAttributes);
+
+    // change this to return a branch group
+
+    return new Shape3D(geom, appearance);
+  }
+  
+  /**
+   * This adjusts a set of points so that the drawn object has the
+   * same appearance even though the Z value is adjusted to a user
+   * given setting. Important for Perspective view.
+   *
+   * @param display  the VisAD display.
+   * @param canvas  Java3D canvas being drawn on
+   * @param z  Virtual world value; larger z is in front.
+   * @param points  array of values to have their z value adjusted.
+   */
+  public static void adjustZ(DisplayImplJ3D display, Canvas3D canvas,
+    double z, Point3d[] points)
+  {
+    if (display.getGraphicsModeControl().getProjectionPolicy()
+      == View.PERSPECTIVE_PROJECTION)
+    {
+      Point3d leftEye = new Point3d();
+      Point3d rightEye = new Point3d();
+      canvas.getLeftEyeInImagePlate(leftEye);
+      canvas.getRightEyeInImagePlate(rightEye);
+      double xLoc = (leftEye.x + rightEye.x)/2.0;
+      double yLoc = (leftEye.y + rightEye.y)/2.0;
+      double zLoc = (leftEye.z + rightEye.z)/2.0;
+      Point3d eye = new Point3d(xLoc, yLoc, zLoc);
+      Transform3D t = new Transform3D();
+      canvas.getImagePlateToVworld(t);
+      t.transform(eye);
+
+      double a;
+      for (int i=0; i<points.length; i++) {
+        a = (z - points[i].z)/(eye.z - points[i].z);
+        points[i].x = a*(eye.x - points[i].x) + points[i].x;
+        points[i].y = a*(eye.y - points[i].y) + points[i].y;
+        points[i].z = z;
+      }
+    } else { // PARALLEL_PROJECTION
+      for (int i=0; i<points.length; i++) {
+        points[i].z = z;
+      }
+    }
+  } // adjustZ
+
+  /**
+   *  Construct a {@link Shape3D} object from a routine description
+   *  of a Quadrilateral.
+   *
+   *  @param display  the VisAD display for this Quadrilateral.
+   *  @param style  one of: <ul>
+   *                 <li> QuadrilateralJ3D.FILL
+   *                 <li> QuadrilateralJ3D.LINE
+   *                 <li> QuadrilateralJ3D.POINT </ul>
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param x3  x screen coordinate of the third point.
+   *  @param y3  y screen coordinate of the third point.
+   *  @param x4  x screen coordinate of the fourth point.
+   *  @param y4  y screen coordinate of the fourth point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param z  Virtual world value; larger z is in front.
+   *  @param thickness  used for LINE and POINT node.
+   *
+   *  @return the QuadrilateralJ3D description as a {@link Shape3D}.
+   */
+  public static Shape3D makeQuadrilateralShape3D(DisplayImplJ3D display,
+    int style, int x1, int y1, int x2, int y2, int x3, int y3,
+    int x4, int y4, float[] colour, double z, double thickness)
+  {
+    DisplayRendererJ3D renderer =
+      (DisplayRendererJ3D)display.getDisplayRenderer();
+    VisADCanvasJ3D canvas = renderer.getCanvas();
+
+    int[][] screenXY = { {x1, x2, x3, x4},
+                         {y1, y2, y3, y4}
+    };
+    Point3d[] points = screenToVworld(screenXY, canvas);
+
+    //adjust z
+    ditherPoints(canvas, 0.4, points);
+    adjustZ(display, canvas, z, points);
+
+    QuadArray quadArray = new QuadArray(points.length,
+      GeometryArray.COORDINATES);
+    quadArray.setCoordinates(0, points);
+
+    // Make an appearance for outline
+    PointAttributes pointAttributes = new PointAttributes();
+    pointAttributes.setPointSize((float)thickness);
+
+    LineAttributes lineAttributes = new LineAttributes();
+    lineAttributes.setLineWidth((float)thickness);
+
+    PolygonAttributes pointPolygon = new PolygonAttributes();
+    pointPolygon.setCullFace(PolygonAttributes.CULL_NONE);
+    if (style == QuadrilateralJ3D.POINT) {
+      pointPolygon.setPolygonMode(PolygonAttributes.POLYGON_POINT);
+    } else if (style == QuadrilateralJ3D.LINE) {
+      pointPolygon.setPolygonMode(PolygonAttributes.POLYGON_LINE);
+    } else {
+      pointPolygon.setPolygonMode(PolygonAttributes.POLYGON_FILL);
+    }
+
+    // make some colours
+    ColoringAttributes pointColor = new ColoringAttributes();
+    pointColor.setColor(colour[0], colour[1], colour[2]);
+
+    Appearance pointAppearance = new Appearance();
+    pointAppearance.setPointAttributes(pointAttributes);
+    pointAppearance.setLineAttributes(lineAttributes);
+    pointAppearance.setPolygonAttributes(pointPolygon);
+    pointAppearance.setColoringAttributes(pointColor);
+
+    return new Shape3D(quadArray, pointAppearance);
+  } // makeQuadrilateralShape3D
+
+  /**
+   *  Construct a {@link Shape3D} object from a routine description
+   *  of a Triangle.
+   *
+   *  @param display  the VisAD display for this Point.
+   *  @param style  one of <ul>
+   *         <li> TriangleJ3D.FILL, </li>
+   *         <li> TriangleJ3D.LINE, </li>
+   *         <li> TriangleJ3D.POINT </li> </ul>
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param x3  x screen coordinate of the third point.
+   *  @param y3  y screen coordinate of the third point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param z  Virtual world value; larger z is in front.
+   *  @param thickness  used for LINE and POINT node.
+   *
+   *  @return the Triangle description as a {@link Shape3D}.
+   */
+  public static Shape3D makeTriangleShape3D(DisplayImplJ3D display,
+    int style, int x1, int y1, int x2, int y2, int x3, int y3,
+    float[] colour, double z, double thickness)
+  {
+    DisplayRendererJ3D renderer =
+      (DisplayRendererJ3D)display.getDisplayRenderer();
+    VisADCanvasJ3D canvas = renderer.getCanvas();
+
+    // this becomes a call to screenToVworld()
+    
+    int[][] screenXY = { {x1, x2, x3},
+                         {y1, y2, y3}
+    };
+    Point3d[] points = screenToVworld(screenXY, canvas);
+
+    //adjust z
+    ditherPoints(canvas, 0.4, points);
+    adjustZ(display, canvas, z, points);
+
+    TriangleArray triangleArray = new TriangleArray(
+      points.length, GeometryArray.COORDINATES);
+    triangleArray.setCoordinates(0, points);
+
+    // Make an appearance for outline
+    PointAttributes pointAttributes = new PointAttributes();
+    pointAttributes.setPointSize((float)thickness);
+
+    LineAttributes lineAttributes = new LineAttributes();
+    lineAttributes.setLineWidth((float)thickness);
+
+    PolygonAttributes pointPolygon = new PolygonAttributes();
+    pointPolygon.setCullFace(PolygonAttributes.CULL_NONE);
+    if (style == TriangleJ3D.POINT) {
+      pointPolygon.setPolygonMode(PolygonAttributes.POLYGON_POINT);
+    } else if (style == TriangleJ3D.LINE) {
+      pointPolygon.setPolygonMode(PolygonAttributes.POLYGON_LINE);
+    } else {
+      pointPolygon.setPolygonMode(PolygonAttributes.POLYGON_FILL);
+    }
+
+    // make some colours
+    ColoringAttributes pointColor = new ColoringAttributes();
+    pointColor.setColor(colour[0], colour[1], colour[2]);
+
+    Appearance pointAppearance = new Appearance();
+    pointAppearance.setPointAttributes(pointAttributes);
+    pointAppearance.setLineAttributes(lineAttributes);
+    pointAppearance.setPolygonAttributes(pointPolygon);
+    pointAppearance.setColoringAttributes(pointColor);
+
+    return  new Shape3D(triangleArray, pointAppearance);
+  } // makeTriangleShape3D
+
+  /**
+   *  Construct a {@link Shape3D} object from a routine description
+   *  of a Line.
+   *
+   *  @param display  the VisAD display for this Point.
+   *  @param style  one of: <ul>
+   *         <li> LineJ3D.SOLID
+   *         <li> LineJ3D.DASH
+   *         <li> LineJ3D.DOT
+   *         <li> LineJ3D.DASH_DOT </ul>
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param z  Virtual world value; larger z is in front.
+   *  @param thickness  for the line.
+   *
+   *  @return the LineJ3D description as a {@link Shape3D}.
+   */
+  public static Shape3D makeLineShape3D(DisplayImplJ3D display, int style,
+    int x1, int y1, int x2, int y2,
+    float[] colour, double z, double thickness)
+  {
+    DisplayRendererJ3D renderer =
+      (DisplayRendererJ3D)display.getDisplayRenderer();
+    VisADCanvasJ3D canvas = renderer.getCanvas();
+
+    int[][] screenXY = { {x1, x2},
+                         {y1, y2}
+    };
+    Point3d[] points = screenToVworld(screenXY, canvas);
+
+    //adjust z
+    ditherPoints(canvas, 0.4, points);
+    adjustZ(display, canvas, z, points);
+
+    LineArray lineArray = new LineArray(
+      points.length, GeometryArray.COORDINATES);
+    lineArray.setCoordinates(0, points);
+
+    // Make an appearance for outline
+    PointAttributes pointAttributes = new PointAttributes();
+    pointAttributes.setPointSize((float)thickness);
+
+    LineAttributes lineAttributes = new LineAttributes();
+    lineAttributes.setLineWidth((float)thickness);
+    if (style == LineJ3D.SOLID) {
+      lineAttributes.setLinePattern(LineAttributes.PATTERN_SOLID);
+    } else if (style == LineJ3D.DASH) {
+      lineAttributes.setLinePattern(LineAttributes.PATTERN_DASH);
+    } else if (style == LineJ3D.DOT) {
+      lineAttributes.setLinePattern(LineAttributes.PATTERN_DOT);
+    } else if (style == LineJ3D.DASH_DOT) {
+      lineAttributes.setLinePattern(LineAttributes.PATTERN_DASH_DOT);
+    }
+
+    // make some colours
+    ColoringAttributes pointColor = new ColoringAttributes();
+    pointColor.setColor(colour[0], colour[1], colour[2]);
+
+    Appearance pointAppearance = new Appearance();
+    pointAppearance.setPointAttributes(pointAttributes);
+    pointAppearance.setLineAttributes(lineAttributes);
+    pointAppearance.setColoringAttributes(pointColor);
+
+    return  new Shape3D(lineArray, pointAppearance);
+  } // makeLineShape3D
+
+  /**
+   *  Construct a {@link Shape3D} object from a routine description
+   *  of a Point.
+   *
+   *  @param display  the VisAD display for this Point.
+   *  @param x1  x screen coordinate of the point.
+   *  @param y1  y screen coordinate of the point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param z  Virtual world value; larger z is in front.
+   *  @param thickness  gives the size of the Point.
+   *
+   *  @return the Point description as a {@link Shape3D}.
+   */
+  public static Shape3D makePointShape3D(DisplayImplJ3D display,
+    int x1, int y1, float[] colour, double z, double thickness)
+  {
+    DisplayRendererJ3D renderer =
+      (DisplayRendererJ3D)display.getDisplayRenderer();
+    VisADCanvasJ3D canvas = renderer.getCanvas();
+
+    int[][] screenXY = { {x1},
+                         {y1}
+    };
+    Point3d[] points = screenToVworld(screenXY, canvas);
+
+    //adjust z
+    ditherPoints(canvas, 0.4, points);
+    adjustZ(display, canvas, z, points);
+
+    PointArray pointArray = new PointArray(
+      points.length, GeometryArray.COORDINATES);
+    pointArray.setCoordinates(0, points);
+
+    // Make an appearance for outline
+    PointAttributes pointAttributes = new PointAttributes();
+    pointAttributes.setPointSize((float)thickness);
+
+    // make some colours
+    ColoringAttributes pointColor = new ColoringAttributes();
+    pointColor.setColor(colour[0], colour[1], colour[2]);
+
+    Appearance pointAppearance = new Appearance();
+    pointAppearance.setPointAttributes(pointAttributes);
+    pointAppearance.setColoringAttributes(pointColor);
+
+    return  new Shape3D(pointArray, pointAppearance);
+  } // makePointShape3D
+
+  /**
+   *  Convert an array of Pixel points to Point3d array in the
+   *  Virtual World.
+   *
+   *  @param canvas  for the virtual world of interest.
+   *  @param screenXY  the screen points in x, y pairs.
+   *
+   *  @return - array of {@link Point3d} values representing the 
+   *          Virtual World values for the Screen points.
+   */
+  public static Point3d[] screenToVworld(int[][] screenXY,
+    Canvas3D canvas)
+  {
+    int numPoints = screenXY[0].length;
+    Point3d[] points = new Point3d[numPoints];
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    for (int i=0; i< numPoints; i++) {
+      points[i] = new Point3d();
+      canvas.getPixelLocationInImagePlate(screenXY[0][i],
+        screenXY[1][i], points[i]);
+      t.transform(points[i]);
+    }
+
+    return points;
+  } // screenToVWorld
+
+  /**
+   *  Convert an array of Vworld points to int[][] array in 
+   *  Screen coordinates.
+   *
+   *  @param canvas  for the virtual world of interest.
+   *  @param points  array of {@link Point3d} of Vworld coordinates.
+   *
+   *  @return - int[2][] array of values representing the Screen points.
+   */
+  public static int[][] vworldToScreen(Point3d[] points,
+    Canvas3D canvas)
+  {
+    int numPoints = points.length;
+    Point2d point2d = new Point2d();
+    Point3d point3d = new Point3d();
+    int[][] screenXY = new int[2][numPoints];
+    Transform3D t = new Transform3D();
+    canvas.getVworldToImagePlate(t);
+
+    for (int j=0; j<numPoints; j++) {
+      // Copy so that the input is unchanged
+      point3d.x = points[j].x;
+      point3d.y = points[j].y;
+      point3d.z = points[j].z;
+      t.transform(point3d);
+      canvas.getPixelLocationFromImagePlate(point3d, point2d);
+      screenXY[0][j] = (int)point2d.x;
+      screenXY[1][j] = (int)point2d.y;
+    }
+
+    return screenXY;
+  } // vworldToScreen
+
+  /**
+   *  Java3D seems to calculate a floating point pixel value and
+   *  then take the integer part for the screen coordinate.
+   *
+   *  This routine allows the user to move the x, y value of a
+   *  Vworld set of points. If these points represent Vworld values
+   *  for some pixels then it allows the coordinate to be moved to
+   *  the interior of the pixel rather than the top left. Consequently
+   *  converting back from Vworld values to floating point screen
+   *  values should ensure the integer part is mapping back to the
+   *  original pixel.
+   *
+   *  The intended use is in mapping screen coordinates to Vworld,
+   *  allow some adjustments, and then be able to map back to the same
+   *  screen coordinates. 
+   *
+   *  An example is adjusting the z value in Vworld so the point is
+   *  closer to (or further from) the eye but have the image occupy
+   *  the same pixel. Get the original Vworld coordinate, use this
+   *  routine to adjust to a Vworld point interior (centre?) of the
+   *  pixel, and then adjust the z value.
+   *
+   *  Java 3D uses positive y down the screen for pixel values, and
+   *  negative y up the screen in Vworld coordinates. So this routine
+   *  is written accordinagly for doing fractional adjustments.
+   *
+   *  @param canvas  where the points are displayed.
+   *  @param frac  amount of interpoint spacing to use for adjustment;
+   *                it is clamped between [0.1, 0.9].
+   *  @param points  array of values to have their x, y values adjusted.
+   */
+  public static void ditherPoints(Canvas3D canvas, double frac,
+    Point3d[] points)
+  {
+    double deltaX;
+    double deltaY;
+
+    // clamp frac to [(0.1, 0.9]
+    if (frac < 0.1) { frac = 0.1; }
+    if (frac > 0.9) { frac = 0.9; }
+
+    // get interpixel spacing in Vworld corrdinates
+    Point3d p1 = new Point3d();
+    Point3d p2 = new Point3d();
+    Point3d p3 = new Point3d();
+    canvas.getPixelLocationInImagePlate(1, 1, p1);
+    canvas.getPixelLocationInImagePlate(2, 1, p2);
+    canvas.getPixelLocationInImagePlate(1, 2, p3);
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    t.transform(p1);
+    t.transform(p2);
+    t.transform(p3);
+    deltaX = p2.x - p1.x;
+    deltaY = p1.y - p3.y;
+    // use this to get the dithered points
+    for (int i=0; i<points.length; i++) {
+      points[i].x = points[i].x + frac*deltaX;
+      points[i].y = points[i].y - frac*deltaY;
+    }
+  } // ditherPoints
+
+  /**
+   *  Transforms an Image object into a {@link Shape3D}. The image
+   *  object may be scaled.
+   *
+   *  @param display  the VisAD display for this Point.
+   *  @param image  to be converted to {@link Shape3D}.
+   *  @param position  how to place the image relative to (x, y);
+   *         one of: <ul>
+   *                <li> Image.TOP_LEFT (default)
+   *                <li> Image.TOP_RIGHT
+   *                <li> Image.BOTTOM_RIGHT
+   *                <li> Image.BOTTOM_LEFT
+   *                <li> Image.CENTER </ul>
+   *  @param x  x screen coordinate to place the image.
+   *  @param y  y screen coordinate to place the image.
+   *  @param width  of image in pixels.
+   *  @param height  of image in pixels.
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param scale  scale factor for image magnification; greater
+   *         than 0.0.
+   *
+   *  @return the Image description as a {@link Shape3D}.
+   *
+   *  @throws VisADException  if the image can't be accessed
+   *          for some reason.
+   */
+  public static Shape3D makeImageShape3D(DisplayImplJ3D display,
+    Image image, int position, int x, int y, int width, int height,
+    double zValue, double scale)
+    throws VisADException
+  {
+    int index = 0;
+    int ind = 0;
+    int scaledWidth;
+    int scaledHeight;
+
+    DisplayRendererJ3D renderer =
+      (DisplayRendererJ3D)display.getDisplayRenderer();
+    VisADCanvasJ3D canvas = renderer.getCanvas();
+
+    // Adjust for scaling factor
+    scaledWidth = Math.round((float)(width*scale));
+    scaledHeight = Math.round((float)(height*scale));
+    double scaleW = (double)scaledWidth/(double)width;
+    double scaleH = (double)scaledHeight/(double)height;
+
+    // Adjust x, y according to  position
+    // do nothing for TOP_LEFT
+    if (position == ImageJ3D.TOP_RIGHT) { // adjust x
+      x = x - (scaledWidth-1);
+    } else if (position == ImageJ3D.BOTTOM_RIGHT) { // adjust x & y
+      x = x - (scaledWidth-1);
+      y = y - (scaledHeight-1);
+    } else if (position == ImageJ3D.BOTTOM_LEFT) { // adjust y
+      y = y - (scaledHeight-1);
+    } else if (position == ImageJ3D.CENTER) { // adjust x & y
+      x = x - (scaledWidth-1)/2;
+      y = y - (scaledHeight-1)/2;
+    }
+
+    // now calculate the VWorld coordinates for each pixel
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    index = 0;
+    Point3d[] points = new Point3d[scaledWidth*scaledHeight];
+    for (int i=0; i<scaledHeight; i++) {
+      for (int j=0; j<scaledWidth; j++) { // row first
+        points[index] = new Point3d();
+        canvas.getPixelLocationInImagePlate(x + j, y + i, points[index]);
+        t.transform(points[index]);
+        index++;
+      }
+    }
+
+    ditherPoints(canvas, 0.4, points);
+    adjustZ(display, canvas, zValue, points);
+
+    // now get the colours from the image 
+    int[] pixels = new int[width*height];
+    PixelGrabber pixelGrabber = new PixelGrabber(image.getSource(),
+      0, 0,  width, height, pixels, 0, width);
+
+    // get the pixels
+    try {
+      pixelGrabber.grabPixels();
+    } catch (InterruptedException ie) {
+      throw new VisADException(
+        "ScreenAnnotatorUtils.makeImageShape3D():" + " failed to grabPixels()");
+    }
+
+    float[] colours = new float[4*scaledWidth*scaledHeight];
+    ind = 0;
+    index = 0;
+    ColorModel cm = pixelGrabber.getColorModel();
+    for (int i=0; i<scaledHeight; i++) { // down column last
+      for (int j=0; j<scaledWidth; j++) { // row first
+        ind = (int)((double)j/scaleW) + width*(int)((double)i/scaleH);
+        colours[4*index] = (float)cm.getRed(pixels[ind])/255;
+        colours[4*index+1] = (float)cm.getGreen(pixels[ind])/255;
+        colours[4*index+2] = (float)cm.getBlue(pixels[ind])/255;
+        colours[4*index+3] = (float)cm.getAlpha(pixels[ind])/255;
+        index++;
+      }
+    }
+
+    PointArray picture = new PointArray(scaledWidth*scaledHeight,
+      GeometryArray.COORDINATES | GeometryArray.COLOR_4);
+    picture.setCoordinates(0, points);
+    picture.setColors(0, colours);
+
+    Appearance appearance = new Appearance();
+    appearance.setTransparencyAttributes(new TransparencyAttributes(TransparencyAttributes.FASTEST, 0));
+
+    // Use default appearance
+
+     return new Shape3D(picture, appearance);
+  } // makeImageShape3D
+} // class ScreenAnnotatorUtils
diff --git a/visad/bom/annotations/TriangleJ3D.java b/visad/bom/annotations/TriangleJ3D.java
new file mode 100644
index 0000000..88d4027
--- /dev/null
+++ b/visad/bom/annotations/TriangleJ3D.java
@@ -0,0 +1,200 @@
+//
+// TriangleJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.bom.annotations;
+
+import visad.DisplayImpl;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+ *  Meant to encapsulate information representing a Triangle which is
+ *  going to be rendered on a VisAD display without being
+ *  subject to the usual VisAD transformations. Thus the label should
+ *  stick in Screen Coordinates.
+ */
+public class TriangleJ3D implements ScreenAnnotation
+{
+  /** fill style POINT */
+  public static final int POINT = 1;
+  /** fill style LINE */
+  public static final int LINE = 2;
+  /** fill style FILL */
+  public static final int FILL = 3;
+
+  private int style;
+  private int x1, y1;   // points of triangle in screen coordinates
+  private int x2, y2;
+  private int x3, y3;
+  private float[] colour;
+  private double zValue;
+  private double thickness;          // for line and point style
+
+  /**
+   *  Simple constructor which makes a zero size triangle at (0, 0)
+   *  coloured white. Set more meaningful values after construction.
+   */
+  public TriangleJ3D()
+  {
+    this(FILL, 0, 0, 0, 0, 0, 0, new float[] {1,1,1}, 0, 1.0);
+  }
+
+  /**
+   *  Constructs a TriangleJ3D from 3 points specified in
+   *  screen coordinates.
+   *
+   *  @param style  one of <ul>
+   *         <li> TriangleJ3D.FILL, </li>
+   *         <li> TriangleJ3D.LINE, </li>
+   *         <li> TriangleJ3D.POINT.</li> <ul>
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param x3  x screen coordinate of the third point.
+   *  @param y3  y screen coordinate of the third point.
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param thickness  used for outline thickness and point size.
+   */
+  public TriangleJ3D(int style,
+    int x1, int y1, int x2, int y2, int x3, int y3,
+    float[] colour, double zValue, double thickness)
+  {
+    this.x1 = x1;
+    this.y1 = y1;
+    this.x2 = x2;
+    this.y2 = y2;
+    this.x3 = x3;
+    this.y3 = y3;
+    this.style = style;
+    this.colour = colour;
+    this.zValue = zValue;
+    this.thickness = thickness;
+  }
+
+  /**
+   *  Constructs a TriangleJ3D from 3 points specified in
+   *  screen coordinates.
+   *
+   *  @param style  one of <ul>
+   *         <li> TriangleJ3D.FILL, </li>
+   *         <li> TriangleJ3D.LINE, </li>
+   *         <li> TriangleJ3D.POINT.</li> <ul>
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 3 points (columns).
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   *  @param zValue  Virtual world value; larger z is in front.
+   *  @param thickness  used for outline thickness and point size.
+   */
+  public TriangleJ3D(int style, int[][] points,
+    float[] colour, double zValue, double thickness)
+  {
+    this(FILL, points[0][0], points[1][0], points[0][1],
+      points[1][1], points[0][2], points[1][2],
+      colour, zValue, thickness);  
+  }
+
+  /**
+   *  Sets TriangleJ3D from 3 points specified in screen coordinates.
+   *
+   *  @param x1  x screen coordinate of the first point.
+   *  @param y1  y screen coordinate of the first point.
+   *  @param x2  x screen coordinate of the second point.
+   *  @param y2  y screen coordinate of the second point.
+   *  @param x3  x screen coordinate of the third point.
+   *  @param y3  y screen coordinate of the third point.
+   */
+  public void setPoints(int x1, int y1, int x2, int y2, int x3, int y3)
+  {
+    this.x1 = x1;
+    this.y1 = y1;
+    this.x2 = x2;
+    this.y2 = y2;
+    this.x3 = x3;
+    this.y3 = y3;
+  } 
+
+  /**
+   *  Sets TriangleJ3D from the given array of 3 points specified
+   *  in screen coordinates.
+   *
+   *  @param points  2 rows with each column containing a point
+   *         in screen coordinates; requires 3 points (columns).
+   */
+  public void setPoints(int[][] points)
+  {
+    setPoints(points[0][0], points[1][0], points[0][1],
+      points[1][1], points[0][2], points[1][2]);
+  }
+
+  /**
+   *  Set the drawing style of the triangle.
+   *
+   *  @param style  one of <ul>
+   *         <li> TriangleJ3D.FILL, </li>
+   *         <li> TriangleJ3D.LINE, </li>
+   *         <li> TriangleJ3D.POINT.</li> <ul>
+   */
+  public void setStyle(int style)
+  {
+    this.style = style;
+  }
+
+  /**
+   *  Set the colour of the triangle.
+   *
+   *  @param colour  red green blue triple; each value in [0.0, 1.0].
+   */
+  public void setColour(float[] colour)
+  {
+    this.colour = colour;
+  }
+
+  /**
+   *  Set the Z value of the triangle.
+   *
+   *  @param zValue  Virtual world value; larger zValue is in front.
+   */
+  public void setZValue(double zValue)
+  {
+    this.zValue = zValue;
+  }
+
+  /**
+   *  Make the TriangleJ3D into a {@link javax.media.j3d.Shape3D}.
+   *
+   *  @param display  the VisAD display for this Triangle.
+   *
+   *  @return the Triangle description as a Shape3D object.
+   */
+  public Object toDrawable(DisplayImpl display)
+  {
+    return ScreenAnnotatorUtils.makeTriangleShape3D(
+      (DisplayImplJ3D)display, style,
+      x1, y1, x2, y2, x3, y3, colour, zValue, thickness);
+  }
+} // class TriangleJ3D
+
diff --git a/visad/bom/radar.dat b/visad/bom/radar.dat
new file mode 100644
index 0000000..d1bfcaa
--- /dev/null
+++ b/visad/bom/radar.dat
@@ -0,0 +1,197 @@
+
+COUNTRY: 036
+NAME: Adel    
+STNID: 11
+DATE: 25899
+TIME: 02.40
+TIMESTAMP: 19990915024004
+VERS: 8.13
+RNGRES: 2000
+ANGRES: 1.0
+VIDRES: 6
+STARTRNG: 4000
+ENDRNG: 512000
+PRODUCT: NORMAL
+IMGFMT: CompPPI
+ELEV: 000.5
+DBZLVL: 11.8 27.8 39.0 43.8 48.6 55.0 
+CLEARAIR: OFF
+DBZCALDLVL: 11.8 23.0 28.0 31.0 34.0 37.0 40.0 43.0 46.0 49.0 52.0 55.0 58.0 61.0 64.0 
+DIGCALDLVL: 17 34 43 50 56 61 68 73 77 84 90 96 101 107 113 
+BEAMWIDTH: 3.00
+PULSELENGTH: 1.7
+STCRANGE: 111
+TXFREQUENCY: 2880
+TXPEAKPWR: 500
+ANTGAIN: 33.0
+NOISETHRESH: 14
+%000A15I
+%001A15H
+%003H
+%019A1H
+%020A1H
+%022A1HA46H
+%025A1H
+%026A1H
+%027A1H
+%042AH
+%045A1H
+%046A1H
+%047A1H
+%050B
+%051A1H
+%054A17H
+%055A17H
+%056B
+%065A15HA1B
+%067A15H
+%072A1V
+%073A15B
+%075A15B
+%077A64BA39BA17B
+%078A15H
+%079BA15B
+%082A15H
+%083A15H
+%084HA14B
+%085HA16B
+%086A20HA41HA8HA14BA8H
+%087A72H
+%088H
+%089B
+%095B
+%097A15B
+%098H
+%102H
+%111H
+%112HA15H
+%113A15H
+%114A15BI
+%115A17B
+%117A16H
+%149A75B
+%151A29HA2HA21HA10HA10H1A12BAHAHA7H1B
+%187A15B
+%188A15B
+%190A15B
+%191A15B
+%200A14H
+%203A17B
+%204A17H
+%205A17H
+%207A41B
+%210A15H
+%212A23H
+%214A23H
+%217A15H
+%221A36H
+%229A24HA2B
+%232A27B
+%234A29IB
+%235A29IB
+%236A29HB
+%237A30B
+%239A37HB
+%240A40B
+%241A40B
+%243A63H
+%246A15HAH
+%247A17HB
+%248A17HB
+%249A16BAB
+%250A15HI1BA22IH
+%251A15HAIB1A15BA4I1B
+%252A15BHBA16IA5IAB
+%253A15BA2BA14HAHA3H
+%254A15H1A17H
+%255A19H
+%256A15BABHI
+%257A15HA1HA16H
+%258A20B1A7HA4HAB
+%259A15BA2HA9HA4HABA6I
+%260A19BIA14H1BA4IAIB
+%261A19I1A1HBA10HIBA4BAIB
+%262A15BA1HBABA1BA2BA2HA3IBA6I
+%263A18HI1A6BA3BHA2BA6H
+%264A18HBA12B
+%265A18HBA2HA12BA11B
+%266A19BA16IB
+%267A18BA18BA10I
+%268A17BIA18B
+%269A16HABA19HA7BHAI
+%270A17HBA19I
+%271A14OAH1IA5BA12H
+%272A17BA20H
+%273A17B1
+%274A50I
+%275A16BI
+%276A17B
+%277A16H
+%278A14BAH
+%279A14B
+%280A14BABH
+%281A18B
+%282A16I
+%284A16IHB
+%286A17H
+%290A15H
+%291A16IB
+%293A17H
+%295A15HAB
+%296A17I
+%298A17I
+%299A17H
+%300A17H1
+%301A16HB
+%302A17H
+%303A18B
+%304A14HA1HIHB
+%305A11HA4BA1B
+%306A14H
+%307A18HB
+%308A15HA2BA1BA6HA15HA51H
+%309A15BA5H
+%310HA14HA2HAH1
+%311HA14BHAHA1H
+%312A17BABAH1
+%313A16B1
+%314A16HA1IAH1
+%315HA15HA3HA1B
+%316HA19BABHB
+%317A17HBHBH1ABA6HA8BA11HA16HA1BA6BA3HA8BA2HA12BA5B
+%318A16BHA1BH1AB
+%319A21BHAB
+%320A15HA2B1AIAB
+%321A19H1AH
+%322A19BH1IB
+%323A19IABHI
+%324A19HAH
+%325A15HA2BI1
+%326A15HA2IAH
+%327A16BA3I
+%328A16BA2BIA15H
+%329A16BHA2I1B1A20BA73B
+%330A21HAIA6HA6BA10BA4BA6HA12B1AHA11HA14BA8H
+%331A14BH1A4IB1A26H1A27BABA14BA5BA11H
+%332A15IA5BI1A8BA85H
+%333A16B1A3HAHBA3HA53BA10B
+%334A22HIB
+%335A15BHA5IBIB
+%336A12CA9I2
+%337A19IA2HIB1
+%338A24I1B
+%339A25HB
+%340A15BA9B
+%345A26BA12HA14BA29HA12BA21B
+%348B
+%349BA14H
+%350B
+%351B
+%352B
+%353B
+%354A86B
+%355A16BHA60BA11H
+%356BA14BA7BA4BA5HA8BA16BA7HA12BA9B1A10HA1HA2H
+%357BA15BA80B
+%358A15B
+%359BA14H
diff --git a/visad/browser/ContourWidget.java b/visad/browser/ContourWidget.java
new file mode 100644
index 0000000..66c2d41
--- /dev/null
+++ b/visad/browser/ContourWidget.java
@@ -0,0 +1,377 @@
+//
+// ContourWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.StringTokenizer;
+
+/**
+ * A widget that allows users to control graphics mode parameters.
+ */
+public class ContourWidget extends Widget implements ActionListener, ItemListener {
+
+  Checkbox contours;
+  Checkbox labels;
+  Checkbox dashed;
+  TextField interval;
+  TextField base;
+  Label surfaceLabel;
+  Slider surface;
+  ContourRangeSlider contourRange;
+
+  private String cwName;
+  private double cwMinValue;
+  private double cwMaxValue;
+  private boolean cwMainContours;
+  private boolean cwLabels;
+  private float cwSurfaceValue;
+  private float cwContourInterval;
+  private float cwLowLimit;
+  private float cwHiLimit;
+  private float cwBase;
+
+  /**
+   * Constructs a new ContourWidget.
+   */
+  public ContourWidget() {
+    // lay out components with GridBagLayout
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    // construct GUI components
+    contours = new Checkbox("contours", cwMainContours);
+    labels = new Checkbox("labels", cwLabels);
+    dashed = new Checkbox("dashed lines below base", cwContourInterval < 0);
+    interval = new TextField(Convert.shortString(Math.abs(cwContourInterval)));
+    base = new TextField(Convert.shortString(cwBase));
+    surfaceLabel = new Label(RangeSlider.DEFAULT_NAME + " = 0");
+    surface = new Slider(0, 0, 1);
+    contourRange = new ContourRangeSlider(0, 1, this);
+
+    // add listeners
+    contours.addItemListener(this);
+    labels.addItemListener(this);
+    dashed.addItemListener(this);
+    interval.addActionListener(this);
+    base.addActionListener(this);
+    surface.addActionListener(this);
+
+    // lay out Components
+    addComponent(contours, gridbag, 1, 0, 1, 1, 0.0, 0.0);
+    addComponent(labels, gridbag, 2, 0, 2, 1, 0.0, 0.0);
+    addComponent(dashed, gridbag, 1, 1, 3, 1, 0.0, 0.0);
+    addComponent(new Label("interval:"), gridbag, 0, 2, 1, 1, 0.0, 0.0);
+    addComponent(interval, gridbag, 1, 2, 1, 1, 1.0, 0.0);
+    addComponent(new Label("base:"), gridbag, 2, 2, 1, 1, 0.0, 0.0);
+    addComponent(base, gridbag, 3, 2, 1, 1, 1.0, 0.0);
+    addComponent(surfaceLabel, gridbag, 0, 3, 4, 1, 1.0, 0.0);
+    addComponent(surface, gridbag, 0, 4, 4, 1, 1.0, 0.0);
+    addComponent(contourRange, gridbag, 0, 5, 4, 1, 1.0, 1.0);
+  }
+
+  /**
+   * Gets the name of the variable.
+   */
+  public String getName() {
+    return cwName;
+  }
+
+  /**
+   * Sets the name of the variable.
+   */
+  public void setName(String name) {
+    cwName = name;
+    contourRange.setName(name);
+    refreshSurfaceLabel();
+  }
+
+  /**
+   * Gets the minimum contouring value.
+   */
+  public double getMinValue() {
+    return cwMinValue;
+  }
+
+  /**
+   * Gets the maximum contouring value.
+   */
+  public double getMaxValue() {
+    return cwMaxValue;
+  }
+
+  /**
+   * Sets the minimum and maximum contouring values.
+   */
+  public void setRange(float min, float max) {
+    cwMinValue = min;
+    cwMaxValue = max;
+    surface.setBounds(min, max);
+    contourRange.setBounds(min, max);
+  }
+
+  /**
+   * Gets the value of the contours checkbox.
+   */
+  public boolean getMainContours() {
+    return cwMainContours;
+  }
+
+  /**
+   * Sets the value of the contours checkbox.
+   */
+  public void setMainContours(boolean mc) {
+    cwMainContours = mc;
+    contours.setState(mc);
+  }
+
+  /**
+   * Gets the value of the labels checkbox.
+   */
+  public boolean getLabels() {
+    return cwLabels;
+  }
+
+  /**
+   * Sets the value of the labels checkbox.
+   */
+  public void setLabels(boolean lb) {
+    cwLabels = lb;
+    labels.setState(lb);
+  }
+
+  /**
+   * Gets the value of the surface value slider.
+   */
+  public float getSurfaceValue() {
+    return cwSurfaceValue;
+  }
+
+  /**
+   * Sets the value of the surface value slider.
+   */
+  public void setSurfaceValue(float sv) {
+    cwSurfaceValue = sv;
+    surface.setValue(sv);
+    refreshSurfaceLabel();
+  }
+
+  /**
+   * Gets the value of the interval text field.
+   */
+  public float getContourInterval() {
+    return cwContourInterval;
+  }
+
+  /**
+   * Sets the value of the interval text field.
+   */
+  public void setContourInterval(float ci) {
+    cwContourInterval = ci;
+    interval.setText(Convert.shortString(Math.abs(ci)));
+    dashed.setState(ci < 0);
+  }
+
+  /**
+   * Gets the low value of the contour range slider.
+   */
+  public float getLowLimit() {
+    return cwLowLimit;
+  }
+
+  /**
+   * Gets the hi value of the contour range slider.
+   */
+  public float getHiLimit() {
+    return cwHiLimit;
+  }
+
+  /**
+   * Sets the range of the contour range slider.
+   */
+  public void setLimits(float lo, float hi) {
+    cwLowLimit = lo;
+    cwHiLimit = hi;
+    contourRange.setValues(lo, hi);
+  }
+
+  /**
+   * Gets the value of the base text field.
+   */
+  public float getBase() {
+    return cwBase;
+  }
+
+  /**
+   * Sets the value of the base text field.
+   */
+  public void setBase(float bs) {
+    cwBase = bs;
+    base.setText(Convert.shortString(bs));
+  }
+
+  /**
+   * Gets a string representing this widget's current state.
+   */
+  public String getSaveString() {
+    return cwMainContours + " " + cwLabels + " " + cwSurfaceValue + " " +
+      cwContourInterval + " " + cwLowLimit + " " + cwHiLimit + " " + cwBase;
+  }
+
+  /**
+   * Reconstructs this widget's state using the specified save string.
+   */
+  public void setSaveString(String save) {
+    if (save == null) {
+      if (DEBUG) System.err.println("Invalid save string");
+      return;
+    }
+    StringTokenizer st = new StringTokenizer(save);
+    if (st.countTokens() < 7) {
+      if (DEBUG) System.err.println("Invalid save string");
+      return;
+    }
+
+    // determine contour settings
+    boolean mc = Convert.getBoolean(st.nextToken());
+    boolean lb = Convert.getBoolean(st.nextToken());
+    float sv = Convert.getFloat(st.nextToken());
+    float ci = Convert.getFloat(st.nextToken());
+    float lo = Convert.getFloat(st.nextToken());
+    float hi = Convert.getFloat(st.nextToken());
+    float bs = Convert.getFloat(st.nextToken());
+
+    // reset contour settings
+    setMainContours(mc);
+    setLabels(lb);
+    setSurfaceValue(sv);
+    setContourInterval(ci);
+    setLimits(lo, hi);
+    setBase(bs);
+  }
+
+  /**
+   * Refreshes the surface label text.
+   */
+  private void refreshSurfaceLabel() {
+    surfaceLabel.setText((cwName == null ? RangeSlider.DEFAULT_NAME : cwName) +
+      " = " + (Float.isNaN(cwSurfaceValue) ?
+      "---" : Convert.shortString(cwSurfaceValue)));
+  }
+
+  /**
+   * Handles TextField changes.
+   */
+  public void actionPerformed(ActionEvent e) {
+    Object source = e.getSource();
+    if (source == interval) {
+      // interval changed
+      float iv = Float.NaN;
+      try {
+        iv = Float.valueOf(interval.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        interval.setText(Convert.shortString(Math.abs(cwContourInterval)));
+      }
+      if (iv > 0) {
+        if (dashed.getState()) iv = -iv;
+        setContourInterval(iv);
+        contours.requestFocus();
+        notifyListeners(new WidgetEvent(this));
+      }
+    }
+    else if (source == base) {
+      // base value changed
+      float bs = Float.NaN;
+      try {
+        bs = Float.valueOf(base.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        base.setText(Convert.shortString(cwBase));
+      }
+      if (bs == bs) {
+        setBase(bs);
+        contours.requestFocus();
+        notifyListeners(new WidgetEvent(this));
+      }
+    }
+    else if (source == surface) {
+      // surface slider value changed
+      float sv = surface.getValue();
+      setSurfaceValue(sv);
+      notifyListeners(new WidgetEvent(this));
+    }
+  }
+
+  /**
+   * Handles Checkbox changes.
+   */
+  public void itemStateChanged(ItemEvent e) {
+    Object source = e.getItemSelectable();
+    boolean on = (e.getStateChange() == ItemEvent.SELECTED);
+    if (source == contours) setMainContours(on);
+    else if (source == labels) setLabels(on);
+    else if (source == dashed) setContourInterval(-cwContourInterval);
+    notifyListeners(new WidgetEvent(this));
+  }
+
+  /**
+   * Tests ContourWidget.
+   */
+  public static void main(String[] args) {
+    new ContourWidget().testWidget();
+  }
+
+  /**
+   * Subclass of RangeSlider for selecting min and max values.
+   */
+  class ContourRangeSlider extends RangeSlider {
+
+    /**
+     * Parent of this range slider.
+     */
+    private ContourWidget widget;
+
+    /**
+     * Constructs a new range slider for the contour widget.
+     */
+    ContourRangeSlider(float min, float max, ContourWidget parent) {
+      super(DEFAULT_NAME, min, max);
+      widget = parent;
+    }
+
+    /**
+     * Tells parent when the values have changed.
+     */
+    public void valuesUpdated() {
+      widget.setLimits(minValue, maxValue);
+      widget.notifyListeners(new WidgetEvent(widget));
+    }
+
+  }
+
+}
diff --git a/visad/browser/Convert.java b/visad/browser/Convert.java
new file mode 100644
index 0000000..cde8d5e
--- /dev/null
+++ b/visad/browser/Convert.java
@@ -0,0 +1,324 @@
+//
+// Convert.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+/**
+ * Utility methods for various conversions between primitive data types.
+ */
+public class Convert {
+
+  /**
+   * Converts an array of ints to an array of bytes. Each integer is cut into
+   * four byte-size pieces, making the resulting byte array four times the
+   * length of the input int array.
+   *
+   * @param ints The array of ints to be converted to a byte array
+   *
+   * @return An array of bytes corresponding to the original int array.
+   */
+  public static byte[] intToBytes(int[] ints) {
+    int len = ints.length;
+    byte[] bytes = new byte[4 * len];
+    for (int i = 0; i < len; i++) {
+      int q = ints[i];
+      bytes[4 * i] = (byte) (q & 0x000000ff);
+      bytes[4 * i + 1] = (byte) ((q & 0x0000ff00) >> 8);
+      bytes[4 * i + 2] = (byte) ((q & 0x00ff0000) >> 16);
+      bytes[4 * i + 3] = (byte) ((q & 0xff000000) >> 24);
+    }
+    return bytes;
+  }
+
+  /**
+   * Converts an array of bytes to an array of ints. Each group of four bytes
+   * form a single int, making the resulting int array one fourth the length
+   * of the input byte array. Note that trailing elements of the bytes array
+   * will be ignored.
+   *
+   * @param bytes The array of bytes to be converted to an int array
+   *
+   * @return An array of ints corresponding to the original byte array.
+   */
+  public static int[] bytesToInt(byte[] bytes) {
+    int len = bytes.length / 4;
+    int[] ints = new int[len];
+    for (int i = 0; i < len; i++) {
+      // This byte decoding method is not very good; is there a better way?
+      int q3 = bytes[4 * i + 3] << 24;
+      int q2 = bytes[4 * i + 2] << 16;
+      int q1 = bytes[4 * i + 1] << 8;
+      int q0 = bytes[4 * i];
+      if (q2 < 0) q2 += 16777216;
+      if (q1 < 0) q1 += 65536;
+      if (q0 < 0) q0 += 256;
+      ints[i] = q3 | q2 | q1 | q0;
+    }
+    return ints;
+  }
+
+  /**
+   * Escape value for an RLE encoded sequence.
+   */
+  private static final int RLE_ESCAPE = Integer.MIN_VALUE;
+
+  /**
+   * Encodes the given array of ints using a run-length scheme.
+   *
+   * @param array The array of ints to RLE-encode
+   *
+   * @return An RLE-encoded array of ints.
+   */
+  public static int[] encodeRLE(int[] array) {
+    int len = array.length;
+    int[] temp = new int[len];
+    int p = 0;
+
+    for (int i = 0; i < len;) {
+      int q = array[i];
+      int count = 0;
+      while (i < len && q == array[i]) {
+        count++;
+        i++;
+      }
+
+      if (count < 4) {
+        // no gain from RLE; save values directly
+        for (int z = 0; z < count; z++) temp[p++] = q;
+      }
+      else {
+        // compress data using RLE
+        temp[p++] = RLE_ESCAPE;
+        temp[p++] = q;
+        temp[p++] = count;
+      }
+    }
+
+    // trim encoded array
+    int[] encoded = new int[p];
+    System.arraycopy(temp, 0, encoded, 0, p);
+    return encoded;
+  }
+
+  /**
+   * Decodes the given array of ints from a run-length encoding.
+   *
+   * @param array The RLE-encoded array of ints to decode
+   *
+   * @return A decoded array of ints.
+   */
+  public static int[] decodeRLE(int[] array) {
+    // compute size of decoded array
+    int count = 0;
+    int i = 0;
+    while (i < array.length) {
+      if (array[i] == RLE_ESCAPE) {
+        count += array[i + 2];
+        i += 3;
+      }
+      else {
+        count++;
+        i++;
+      }
+    }
+
+    // allocate decoded array
+    int[] decoded = new int[count];
+    int p = 0;
+
+    // decode RLE sequence
+    for (i = 0; i < array.length; i++) {
+      int q = array[i];
+      if (q == RLE_ESCAPE) {
+        int val = array[++i];
+        int cnt = array[++i];
+        for (int z = 0; z < cnt; z++) decoded[p++] = val;
+      }
+      else decoded[p++] = q;
+    }
+    return decoded;
+  }
+
+  /**
+   * Extracts a double from a string.
+   */
+  public static double getDouble(String s) {
+    double d = Double.NaN;
+    try {
+      d = Double.valueOf(s).doubleValue();
+    }
+    catch (NumberFormatException exc) { }
+    return d;
+  }
+
+  /**
+   * Extracts a float from a string.
+   */
+  public static float getFloat(String s) {
+    float f = Float.NaN;
+    if (s != null) {
+      try {
+        f = Float.valueOf(s).floatValue();
+      }
+      catch (NumberFormatException exc) { }
+    }
+    return f;
+  }
+  
+  /**
+   * Extracts a boolean from a string.
+   */
+  public static boolean getBoolean(String s) {
+    if (s == null) return false;
+    char c = s.trim().charAt(0);
+    return c == 'T' || c == 't';
+  }
+
+  /**
+   * Extracts an integer from a string.
+   */
+  public static int getInt(String s) {
+    int i = 0;
+    if (s != null) {
+      try {
+        i = Integer.parseInt(s);
+      }
+      catch (NumberFormatException exc) { }
+    }
+    return i;
+  }
+
+  /**
+   * Number of significant digits after the decimal point.
+   */
+  public static final int PLACES = 3;
+
+  /** Mode where shortString rounds to the nearest value. */
+  public static final int ROUND_NEAREST = 1;
+
+  /** Mode where shortString rounds to the lower value. */
+  public static final int ROUND_DOWN = 2;
+
+  /** Mode where shortString rounds to the higher value. */
+  public static final int ROUND_UP = 3;
+
+  /**
+   * Gets a reasonably short string representation of a double
+   * for use in a graphical user interface. The number will be rounded
+   * to the nearest integer.
+   */
+  public static String shortString(double val) {
+    return shortString(val, ROUND_NEAREST);
+  }
+
+  /**
+   * Gets a reasonably short string representation of a double
+   * for use in a graphical user interface. Mode indicates the method
+   * used to deal insignificant digits: ROUND_NEAREST, ROUND_UP or ROUND_DOWN.
+   */
+  public static String shortString(double val, int mode) {
+    // remember whether or not the number is negative
+    boolean negative = (val < 0.0);
+
+    double origVal = val;
+
+    // now we only need to deal with a positive number
+    val = Math.abs(val);
+
+    if (val < 0.001) {
+      for (int i = 1; i < 30; i++) {
+        val *= 10.0;
+        origVal *= 10.0;
+        if (val >= 1.0) {
+          return shortString(origVal) + "E-" + i;
+        }
+      }
+    }
+
+    // build multiplier for saving significant digits
+    // also build value used to round up insignificant digits
+    int mult = 1;
+    float round;
+    if (mode == ROUND_DOWN) round = negative ? 1 : 0;
+    else if (mode == ROUND_UP) round = negative ? 0 : 1;
+    else round = 0.5f; // mode == ROUND_NEAREST (default)
+    for (int p = PLACES; p > 0; p--) {
+      mult *= 10;
+      round /= 10;
+    }
+
+    // break into digits before (preDot) and after (postDot) the decimal point
+    long l = (long) ((val + round) * mult);
+    long preDot = l / mult;
+    int postDot = (int )(l % mult);
+
+    // format the pre-decimal point number
+    // Integer.toString() is faster than Long.toString(); use it if possible
+    String num;
+    if (preDot <= Integer.MAX_VALUE) {
+      num = Integer.toString((int )preDot);
+    } else {
+      num = Long.toString(preDot);
+    }
+
+    // if there's nothing after the decimal point, use the whole number
+    if (postDot == 0) {
+
+      // make sure we don't return "-0"
+      if (negative && preDot != 0) {
+        return "-" + num;
+      }
+
+      return num;
+    }
+
+    // start building the string
+    StringBuffer buf = new StringBuffer(num.length() + 5);
+
+    // add sign (if necessary), pre-decimal point digits and decimal point
+    if (negative) {
+      buf.append('-');
+    }
+    buf.append(num);
+    buf.append('.');
+
+    // format the post-decimal point digits
+    num = Integer.toString(postDot);
+
+    // add leading zeros if necessary
+    int nlen = num.length();
+    for (int p = PLACES; p > nlen; p--) {
+      buf.append('0');
+    }
+
+    // add actual digits
+    buf.append(num);
+
+    // return the final string
+    return buf.toString();
+  }
+
+}
diff --git a/visad/browser/Divider.java b/visad/browser/Divider.java
new file mode 100644
index 0000000..58886be
--- /dev/null
+++ b/visad/browser/Divider.java
@@ -0,0 +1,134 @@
+//
+// Divider.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.awt.*;
+
+/**
+ * A small divider for visually separating components.
+ */
+public class Divider extends Component {
+
+  /**
+   * Constant for horizontal divider alignment.
+   */
+  public static final int HORIZONTAL = 1;
+
+  /**
+   * Constant for vertical divider alignment.
+   */
+  public static final int VERTICAL = 2;
+
+  /**
+   * Orientation for this divider.
+   */
+  private int orientation;
+
+  /**
+   * Constructor for horizontal divider.
+   */
+  public Divider() {
+    this(HORIZONTAL);
+  }
+
+  /**
+   * Constructor for divider in the given orientation.
+   *
+   * @param orientation Divider.HORIZONTAL or Divider.VERTICAL
+   */
+  public Divider(int orientation) {
+    this.orientation = orientation;
+  }
+
+  /**
+   * Paints the divider.
+   */
+  public void paint(Graphics g) {
+    if (orientation == HORIZONTAL) {
+      int w = getSize().width;
+      g.setColor(Color.white);
+      g.drawRect(0, 0, w-2, 6);
+      g.drawRect(2, 2, w-4, 2);
+      g.setColor(Color.black);
+      g.drawRect(1, 1, w-3, 3);
+    }
+    else if (orientation == VERTICAL) {
+      int h = getSize().height;
+      g.setColor(Color.white);
+      g.drawRect(0, 0, 6, h-2);
+      g.drawRect(2, 2, 2, h-4);
+      g.setColor(Color.black);
+      g.drawRect(1, 1, 3, h-3);
+    }
+  }
+
+  /**
+   * Gets the divider's minimum size.
+   */
+  public Dimension getMinimumSize() {
+    if (orientation == HORIZONTAL) {
+      return new Dimension(0, 6);
+    }
+    else if (orientation == VERTICAL) {
+      return new Dimension(6, 0);
+    }
+    else {
+      return new Dimension(0, 0);
+    }
+  }
+
+  /**
+   * Gets the divider's preferred size.
+   */
+  public Dimension getPreferredSize() {
+    if (orientation == HORIZONTAL) {
+      return new Dimension(0, 6);
+    }
+    else if (orientation == VERTICAL) {
+      return new Dimension(6, 0);
+    }
+    else {
+      return new Dimension(0, 0);
+    }
+  }
+
+  /**
+   * Gets the divider's maximum size.
+   */
+  public Dimension getMaximumSize() {
+    if (orientation == HORIZONTAL) {
+      return new Dimension(Integer.MAX_VALUE, 6);
+    }
+    else if (orientation == VERTICAL) {
+      return new Dimension(6, Integer.MAX_VALUE);
+    }
+    else {
+      return new Dimension(0, 0);
+    }
+  }
+
+}
diff --git a/visad/browser/GMCWidget.java b/visad/browser/GMCWidget.java
new file mode 100644
index 0000000..6295cf9
--- /dev/null
+++ b/visad/browser/GMCWidget.java
@@ -0,0 +1,337 @@
+//
+// GMCWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.StringTokenizer;
+
+/**
+ * A widget that allows users to control graphics mode parameters.
+ */
+public class GMCWidget extends Widget implements ActionListener, ItemListener {
+
+  Checkbox scale;
+  Checkbox point;
+  Checkbox texture;
+  TextField lineWidth;
+  TextField pointSize;
+
+  boolean gmcScaleEnable;
+  boolean gmcPointMode;
+  boolean gmcTextureEnable;
+  float gmcLineWidth;
+  float gmcPointSize;
+  int gmcTransparencyMode;
+  int gmcProjectionPolicy;
+  int gmcPolygonMode;
+  boolean gmcMissingTransparent;
+  int gmcCurvedSize;
+
+  /**
+   * Constructs a new GMCWidget.
+   */
+  public GMCWidget() {
+    // lay out components with GridBagLayout
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+
+    // construct GUI components
+    scale = new Checkbox("Enable scale", gmcScaleEnable);
+    point = new Checkbox("Point mode", gmcPointMode);
+    texture = new Checkbox("Texture mapping", gmcTextureEnable);
+    lineWidth = new TextField(Convert.shortString(gmcLineWidth));
+    pointSize = new TextField(Convert.shortString(gmcPointSize));
+
+    // add listeners
+    scale.addItemListener(this);
+    point.addItemListener(this);
+    texture.addItemListener(this);
+    lineWidth.addActionListener(this);
+    pointSize.addActionListener(this);
+
+    // lay out Components
+    addComponent(scale, gridbag, 0, 0, 1, 1, 0.0, 0.0);
+    addComponent(point, gridbag, 1, 0, 1, 1, 0.0, 0.0);
+    addComponent(texture, gridbag, 2, 0, 2, 1, 0.0, 0.0);
+    addComponent(new Label("Line width:"), gridbag, 0, 1, 1, 1, 0.0, 0.0);
+    addComponent(lineWidth, gridbag, 1, 1, 1, 1, 1.0, 0.0);
+    addComponent(new Label("Point size:"), gridbag, 2, 1, 1, 1, 0.0, 0.0);
+    addComponent(pointSize, gridbag, 3, 1, 1, 1, 1.0, 0.0);
+  }
+
+  /**
+   * Gets the value of the line width text field.
+   */
+  public float getLineWidth() {
+    return gmcLineWidth;
+  }
+
+  /**
+   * Programmatically sets the line width text field.
+   */
+  public void setLineWidth(float lw) {
+    gmcLineWidth = lw;
+    lineWidth.setText(Convert.shortString(lw));
+  }
+
+  /**
+   * Gets the value of the point size text field.
+   */
+  public float getPointSize() {
+    return gmcPointSize;
+  }
+  
+  /**
+   * Programmatically sets the point size text field.
+   */
+  public void setPointSize(float ps) {
+    gmcPointSize = ps;
+    pointSize.setText(Convert.shortString(ps));
+  }
+
+  /**
+   * Gets the value of the point mode checkbox;
+   */
+  public boolean getPointMode() {
+    return gmcPointMode;
+  }
+  
+  /**
+   * Programmatically sets the point mode checkbox.
+   */
+  public void setPointMode(boolean pm) {
+    gmcPointMode = pm;
+    point.setState(pm);
+  }
+
+  /**
+   * Gets the value of the texture enable checkbox.
+   */
+  public boolean getTextureEnable() {
+    return gmcTextureEnable;
+  }
+  
+  /**
+   * Programmatically sets the texture mapping checkbox.
+   */
+  public void setTextureEnable(boolean tm) {
+    gmcTextureEnable = tm;
+    texture.setState(tm);
+  }
+
+  /**
+   * Gets the value of the scale enable checkbox.
+   */
+  public boolean getScaleEnable() {
+    return gmcScaleEnable;
+  }
+  
+  /**
+   * Programmatically sets the scale enabled checkbox.
+   */
+  public void setScaleEnable(boolean se) {
+    gmcScaleEnable = se;
+    scale.setState(se);
+  }
+
+  /**
+   * Gets the transparency mode.
+   */
+  public int getTransparencyMode() {
+    return gmcTransparencyMode;
+  }
+
+  /**
+   * Sets the transparency mode.
+   */
+  public void setTransparencyMode(int tm) {
+    gmcTransparencyMode = tm;
+  }
+
+  /**
+   * Gets the projection policy.
+   */
+  public int getProjectionPolicy() {
+    return gmcProjectionPolicy;
+  }
+
+  /**
+   * Sets the projection policy.
+   */
+  public void setProjectionPolicy(int pp) {
+    gmcProjectionPolicy = pp;
+  }
+
+  /**
+   * Gets the polygon mode.
+   */
+  public int getPolygonMode() {
+    return gmcPolygonMode;
+  }
+
+  /**
+   * Sets the polygon mode.
+   */
+  public void setPolygonMode(int pm) {
+    gmcPolygonMode = pm;
+  }
+
+  /**
+   * Gets whether missing values are transparent.
+   */
+  public boolean getMissingTransparent() {
+    return gmcMissingTransparent;
+  }
+
+  /**
+   * Sets whether missing values are transparent.
+   */
+  public void setMissingTransparent(boolean mt) {
+    gmcMissingTransparent = mt;
+  }
+
+  /**
+   * Gets the curved size.
+   */
+  public int getCurvedSize() {
+    return gmcCurvedSize;
+  }
+
+  /**
+   * Sets the curved size.
+   */
+  public void setCurvedSize(int cs) {
+    gmcCurvedSize = cs;
+  }
+
+  /**
+   * Gets a string representing this widget's current state.
+   */
+  public String getSaveString() {
+    return "" + gmcLineWidth + " " + gmcPointSize + " " + gmcPointMode + " " +
+      gmcTextureEnable + " " + gmcScaleEnable + " " +
+      gmcTransparencyMode + " " + gmcProjectionPolicy + " " +
+      gmcPolygonMode + " " + gmcMissingTransparent + " " + gmcCurvedSize;
+  }
+
+  /**
+   * Reconstructs this widget's state using the specified save string.
+   */
+  public void setSaveString(String save) {
+    if (save == null) {
+      if (DEBUG) System.err.println("Invalid save string");
+      return;
+    }
+    StringTokenizer st = new StringTokenizer(save);
+    int numTokens = st.countTokens();
+    if (numTokens < 10) {
+      System.out.println("Invalid save string");
+      return;
+    }
+
+    // determine graphics mode settings
+    float lw = Convert.getFloat(st.nextToken());
+    float ps = Convert.getFloat(st.nextToken());
+    boolean pm = Convert.getBoolean(st.nextToken());
+    boolean te = Convert.getBoolean(st.nextToken());
+    boolean se = Convert.getBoolean(st.nextToken());
+    int tm = Convert.getInt(st.nextToken());
+    int pp = Convert.getInt(st.nextToken());
+    int pm2 = Convert.getInt(st.nextToken());
+    boolean mt = Convert.getBoolean(st.nextToken());
+    int cs = Convert.getInt(st.nextToken());
+
+    // reset graphics mode settings
+    setLineWidth(lw);
+    setPointSize(ps);
+    setPointMode(pm);
+    setTextureEnable(te);
+    setScaleEnable(se);
+    setTransparencyMode(tm);
+    setProjectionPolicy(pp);
+    setPolygonMode(pm2);
+    setMissingTransparent(mt);
+    setCurvedSize(cs);
+  }
+
+  /**
+   * Handles TextField changes.
+   */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    Object source = e.getSource();
+    if (source == lineWidth) {
+      float lw = Float.NaN;
+      try {
+        lw = Float.valueOf(lineWidth.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        lineWidth.setText(Convert.shortString(gmcLineWidth));
+      }
+      if (lw == lw) {
+        setLineWidth(lw);
+        scale.requestFocus();
+        notifyListeners(new WidgetEvent(this));
+      }
+    }
+    else if (source == pointSize) {
+      float ps = Float.NaN;
+      try {
+        ps = Float.valueOf(pointSize.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        pointSize.setText(Convert.shortString(gmcPointSize));
+      }
+      if (ps == ps) {
+        setPointSize(ps);
+        scale.requestFocus();
+        notifyListeners(new WidgetEvent(this));
+      }
+    }
+  }
+
+  /**
+   * Handles Checkbox changes.
+   */
+  public void itemStateChanged(ItemEvent e) {
+    Object source = e.getItemSelectable();
+    boolean on = (e.getStateChange() == ItemEvent.SELECTED);
+    if (source == scale) setScaleEnable(on);
+    else if (source == point) setPointMode(on);
+    else if (source == texture) setTextureEnable(on);
+    notifyListeners(new WidgetEvent(this));
+  }
+
+  /**
+   * Tests GMCWidget.
+   */
+  public static void main(String[] args) {
+    new GMCWidget().testWidget();
+  }
+
+}
diff --git a/visad/browser/README.browser b/visad/browser/README.browser
new file mode 100644
index 0000000..3467a34
--- /dev/null
+++ b/visad/browser/README.browser
@@ -0,0 +1,40 @@
+The classes in the visad.browser package compile under
+JDK 1.1 for use in web browsers.  They enable client
+applets to connect to VisAD DisplayImpls running on
+the same computer that served the applet.
+
+To test and demonstrate this, we provide a sample
+server in visad/examples/Test68.java, a sample client
+applet in visad/browser/VisADApplet.java, and a sample
+web page in socket_applet.html.
+
+To start the server, change to the visad/examples
+directory and run:
+
+  java Test68
+
+On the same machine, install socket_applet.html in the
+htdocs directory for the machine's web server, and copy
+the *.class files from visad/browser directory to the
+htdocs/visad/browser directory.
+
+On the client machine where you will run your web
+browser, you must make sure that the classes in
+visad/browser are not available via your CLASSPATH.
+You can do this by either using a machine where VisAD
+is not installed or setting CLASSPATH to blank.
+
+If you are using Internet Explorer for web browing,
+simply aim it at socket_applet.html on your server
+machine.  When the applet comes up, substitute the IP
+name of the server for localhost, and click on 'Connect'.
+
+If you using Netscape for web browsing, you must first
+add the following line:
+
+  user_pref("signed.applets.codebase_principal_support", true);
+
+to your ~/.netscape/preferences.js file.  Then aim
+netscape at socket_applet.html on your server machine
+and proceed as described above.
+
diff --git a/visad/browser/RangeSlider.java b/visad/browser/RangeSlider.java
new file mode 100644
index 0000000..2419559
--- /dev/null
+++ b/visad/browser/RangeSlider.java
@@ -0,0 +1,643 @@
+//
+// RangeSlider.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+ * A slider widget that allows users to select a lower and upper bound.
+ */
+public class RangeSlider extends Component
+  implements MouseListener, MouseMotionListener
+{
+
+  /**
+   * Default variable name.
+   */
+  public static final String DEFAULT_NAME = "value";
+
+  /**
+   * Preferred slider height.
+   */
+  public static final int SLIDER_PREF_HEIGHT = 42;
+
+  /**
+   * Preferred slider width.
+   */
+  public static final int SLIDER_PREF_WIDTH = 300;
+
+  /**
+   * Width of grip.
+   */
+  public static final int GRIP_WIDTH = 9;
+
+  /**
+   * Height of grip.
+   */
+  public static final int GRIP_HEIGHT = 17;
+
+  /**
+   * Y-coordinate of top of grip.
+   */
+  public static final int GRIP_TOP_Y = 4;
+
+  /**
+   * Y-coordinate of bottom of grip.
+   */
+  public static final int GRIP_BOTTOM_Y = GRIP_TOP_Y + GRIP_HEIGHT;
+
+  /**
+   * Y-coordinate of middle of grip.
+   */
+  public static final int GRIP_MIDDLE_Y = GRIP_TOP_Y + (GRIP_HEIGHT / 2);
+
+  /**
+   * Height of slider line.
+   */
+  public static final int SLIDER_LINE_HEIGHT = GRIP_HEIGHT + 2;
+
+  /**
+   * Width of slider line.
+   */
+  public static final int SLIDER_LINE_WIDTH = 2;
+
+  /**
+   * Height of font.
+   */
+  public static final int FONT_HEIGHT = 15;
+
+  /**
+   * Y-coordinate of top of font.
+   */
+  public static final int FONT_TOP_Y = 27;
+
+  /**
+   * Y-coordinate of bottom of font.
+   */
+  public static final int FONT_BOTTOM_Y = FONT_TOP_Y + FONT_HEIGHT - 2;
+
+  /**
+   * Percent through scale of min gripper.
+   */
+  protected float minValue = 0;
+
+  /**
+   * Percent through scale of max gripper.
+   */
+  protected float maxValue = 100;
+
+  /**
+   * Minimum slider value.
+   */
+  protected float minLimit = 0.0f;
+
+  /**
+   * Maximum slider value.
+   */
+  protected float maxLimit = 1.0f;
+
+  /**
+   * Location of min gripper.
+   */
+  protected int minGrip = GRIP_WIDTH;
+
+  /**
+   * Location of max gripper.
+   */
+  protected int maxGrip = SLIDER_PREF_WIDTH - GRIP_WIDTH;
+
+  /**
+   * Flag whether mouse is currently affecting min gripper.
+   */
+  private boolean minSlide = false;
+
+  /**
+   * Flag whether mouse is currently affecting max gripper.
+   */
+  private boolean maxSlide = false;
+
+  /**
+   * Flag whether left gripper has moved.
+   */
+  protected boolean lSlideMoved = false;
+
+  /**
+   * Flag whether right gripper has moved.
+   */
+  protected boolean rSlideMoved = false;
+
+  /**
+   * Flag whether current text string value needs updating.
+   */
+  protected boolean textChanged = false;
+
+  /**
+   * Variable name for values.
+   */
+  private String name;
+
+  /**
+   * Label state variable.
+   */
+  private float lastMinLimit = 0.0f;
+
+  /**
+   * Label state variable.
+   */
+  private float lastMaxLimit = 0.0f;
+
+  /**
+   * Label state variable.
+   */
+  private String lastCurStr = "";
+
+  /**
+   * Minimum widget size.
+   */
+  protected Dimension minSize = null;
+
+  /**
+   * Preferred widget size.
+   */
+  protected Dimension prefSize = null;
+
+  /**
+   * Maximum widget size.
+   */
+  protected Dimension maxSize = null;
+
+  /**
+   * Constructs a RangeSlider with the specified range of values.
+   */
+  public RangeSlider(String n, float min, float max) {
+    name = n;
+    resetValues(min, max);
+    addMouseListener(this);
+    addMouseMotionListener(this);
+  }
+
+  /**
+   * Gets minimum and maximum slider values.
+   */
+  public float[] getMinMaxValues() {
+    return new float[] {minValue, maxValue};
+  }
+
+  /**
+   * Resets the minimum and maximum values.
+   */
+  protected void resetValues(float min, float max) {
+    minLimit = min;
+    maxLimit = max;
+    minGrip = GRIP_WIDTH;
+    maxGrip = getSize().width - GRIP_WIDTH;
+    minSlide = false;
+    maxSlide = false;
+    lSlideMoved = true;
+    rSlideMoved = true;
+    textChanged = true;
+
+    int w = getSize().width;
+    minValue = gripToValue(minGrip, w);
+    maxValue = gripToValue(maxGrip, w);
+  }
+
+  /**
+   * Sets the slider's name.
+   */
+  public void setName(String name) {
+    this.name = name;
+    textChanged = true;
+    repaint();
+  }
+
+  /**
+   * Sets the slider's lo and hi bounds.
+   */
+  public void setBounds(float min, float max) {
+    resetValues(min, max);
+    valuesUpdated();
+    repaint();
+  }
+
+  /**
+   * Sets the slider's lo and hi values.
+   */
+  public void setValues(float lo, float hi) {
+    int w = getSize().width;
+    int g;
+
+    minValue = lo;
+    g = minGrip;
+    minGrip = valueToGrip(minValue, w);
+    if (g != minGrip) lSlideMoved = true;
+
+    maxValue = hi;
+    g = maxGrip;
+    maxGrip = valueToGrip(maxValue, w);
+    if (g != maxGrip) rSlideMoved = true;
+
+    textChanged = true;
+    repaint();
+  }
+
+  /**
+   * Redraws the slider if the widget width changes.
+   */
+  public void setBounds(int x, int y, int w, int h) {
+    int lastW = getSize().width;
+    super.setBounds(x, y, w, h);
+    if (lastW != w) {
+      minGrip = valueToGrip(minValue, w);
+      maxGrip = valueToGrip(maxValue, w);
+      Graphics g = getGraphics();
+      drawLabels(g, lastW);
+      if (g != null) g.dispose();
+    }
+  }
+
+  /**
+   * MouseListener method for moving slider.
+   */
+  public void mousePressed(MouseEvent e) {
+    int w = getSize().width;
+    int x = e.getX();
+    int y = e.getY();
+    oldX = x;
+
+    if (Widget.containedIn(x, y, minGrip - (GRIP_WIDTH - 1),
+      GRIP_TOP_Y, GRIP_WIDTH, GRIP_HEIGHT))
+    {
+      // mouse pressed in left grip
+      minSlide = true;
+    }
+    else if (Widget.containedIn(x, y, maxGrip, GRIP_TOP_Y, GRIP_WIDTH, GRIP_HEIGHT)) {
+      // mouse pressed in right grip
+      maxSlide = true;
+    }
+    else if (Widget.containedIn(x, y, minGrip, GRIP_TOP_Y - 3, maxGrip-minGrip,
+      GRIP_TOP_Y + SLIDER_LINE_HEIGHT - 1))
+    {
+      // mouse pressed in pink rectangle
+      minSlide = true;
+      maxSlide = true;
+    }
+    else if (Widget.containedIn(x, y, 0, GRIP_TOP_Y-3, minGrip-GRIP_WIDTH,
+      GRIP_TOP_Y+SLIDER_LINE_HEIGHT-1))
+    {
+      // mouse pressed to left of grips
+      if (x < GRIP_WIDTH) minGrip = GRIP_WIDTH;
+      else minGrip = x;
+      minValue = gripToValue(minGrip, w);
+      minSlide = true;
+      lSlideMoved = true;
+      valuesUpdated();
+      repaint();
+    }
+    else if (Widget.containedIn(x, y, maxGrip + 1 - GRIP_WIDTH, GRIP_TOP_Y - 3,
+      w - maxGrip + GRIP_WIDTH, GRIP_TOP_Y + SLIDER_LINE_HEIGHT - 1))
+    {
+      // mouse pressed to right of grips
+      if (x > w - GRIP_WIDTH) maxGrip = w - GRIP_WIDTH;
+      else maxGrip = x;
+      maxValue = gripToValue(maxGrip, w);
+      maxSlide = true;
+      rSlideMoved = true;
+      valuesUpdated();
+      repaint();
+    }
+  }
+
+  /**
+   * MouseListener method for moving slider.
+   */
+  public void mouseReleased(MouseEvent e) {
+    minSlide = false;
+    maxSlide = false;
+    textChanged = true;
+    repaint();
+  }
+
+  /**
+   * Not used.
+   */
+  public void mouseClicked(MouseEvent e) { }
+
+  /**
+   * Not used.
+   */
+  public void mouseEntered(MouseEvent e) { }
+
+  /**
+   * Not used.
+   */
+  public void mouseExited(MouseEvent e) { }
+
+  /**
+   * Previous mouse X position.
+   */
+  private int oldX;
+
+  /**
+   * MouseMotionListener method for moving slider.
+   */
+  public void mouseDragged(MouseEvent e) {
+    int w = getSize().width;
+    int x = e.getX();
+    int y = e.getY();
+
+    // move entire range
+    if (minSlide && maxSlide) {
+      int change = x - oldX;
+      if (minGrip+change < GRIP_WIDTH) change = GRIP_WIDTH - minGrip;
+      else if (maxGrip + change > w - GRIP_WIDTH) {
+        change = w - GRIP_WIDTH - maxGrip;
+      }
+      if (change != 0) {
+        minGrip += change;
+        minValue = gripToValue(minGrip, w);
+        maxGrip += change;
+        maxValue = gripToValue(maxGrip, w);
+        lSlideMoved = true;
+        rSlideMoved = true;
+        valuesUpdated();
+        repaint();
+      }
+    }
+
+    // move min gripper if it is held
+    else if (minSlide) {
+      if (x < GRIP_WIDTH) minGrip = GRIP_WIDTH;
+      else if (x >= maxGrip) minGrip = maxGrip - 1;
+      else minGrip = x;
+      minValue = gripToValue(minGrip, w);
+      lSlideMoved = true;
+      valuesUpdated();
+      repaint();
+    }
+
+    // move max gripper if it is held
+    else if (maxSlide) {
+      if (x > w - GRIP_WIDTH) maxGrip = w - GRIP_WIDTH;
+      else if (x <= minGrip) maxGrip = minGrip + 1;
+      else maxGrip = x;
+      maxValue = gripToValue(maxGrip, w);
+      rSlideMoved = true;
+      valuesUpdated();
+      repaint();
+    }
+
+    oldX = x;
+  }
+
+  /**
+   * Not used.
+   */
+  public void mouseMoved(MouseEvent e) { }
+
+  /**
+   * Returns minimum size of range slider.
+   */
+  public Dimension getMinimumSize() {
+    if (minSize == null) {
+      minSize = new Dimension(0, SLIDER_PREF_HEIGHT);
+    }
+    return minSize;
+  }
+
+  /**
+   * Sets minimum size of range slider.
+   */
+  public void setMinimumSize(Dimension dim) { minSize = dim; }
+
+  /**
+   * Returns preferred size of range slider.
+   */
+  public Dimension getPreferredSize() {
+    if (prefSize == null) {
+      prefSize = new Dimension(SLIDER_PREF_WIDTH, SLIDER_PREF_HEIGHT);
+    }
+    return prefSize;
+  }
+
+  /**
+   * Sets preferred size of range slider.
+   */
+  public void setPreferredSize(Dimension dim) { prefSize = dim; }
+
+  /**
+   * Returns maximum size of range slider.
+   */
+  public Dimension getMaximumSize() {
+    if (maxSize == null) {
+      maxSize = new Dimension(Integer.MAX_VALUE, SLIDER_PREF_HEIGHT);
+    }
+    return maxSize;
+  }
+
+  /**
+   * Sets preferred size of range slider.
+   */
+  public void setMaximumSize(Dimension dim) { maxSize = dim; }
+
+  protected float gripToValue(int pos, int width) {
+    float q = (float) (pos - GRIP_WIDTH) / (width - 2 * GRIP_WIDTH);
+    return (maxLimit - minLimit) * q + minLimit;
+  }
+
+  protected int valueToGrip(float value, int width) {
+    float rfloat = (((value - (float) minLimit) *
+      (float) (width - (GRIP_WIDTH * 2))) / (maxLimit - minLimit));
+
+    // round away from zero
+    if (rfloat < 0.0f) rfloat -= 0.5f;
+    else rfloat += 0.5f;
+
+    return (int) rfloat + GRIP_WIDTH;
+  }
+
+  /**
+   * Called whenever the min or max value is updated.
+   * This method is meant to be overridden by extension classes.
+   */
+  public void valuesUpdated() { }
+
+  /**
+   * Draws the slider from scratch.
+   */
+  public void paint(Graphics g) {
+    int w = getSize().width;
+
+    // clear old graphics
+    g.setColor(Color.black);
+    g.fillRect(0, 0, w, SLIDER_PREF_HEIGHT);
+
+    // draw slider lines
+    int right = w - 1;
+    g.setColor(Color.white);
+    g.drawLine(0, GRIP_MIDDLE_Y, right, GRIP_MIDDLE_Y);
+    g.drawLine(0, GRIP_TOP_Y - 4, 0, GRIP_TOP_Y + SLIDER_LINE_HEIGHT);
+    g.drawLine(0, GRIP_TOP_Y - 4, SLIDER_LINE_WIDTH, GRIP_TOP_Y - 4);
+    g.drawLine(0, GRIP_TOP_Y + SLIDER_LINE_HEIGHT, SLIDER_LINE_WIDTH,
+      GRIP_TOP_Y + SLIDER_LINE_HEIGHT);
+    g.drawLine(right, GRIP_TOP_Y - 4, right, GRIP_TOP_Y + SLIDER_LINE_HEIGHT);
+    g.drawLine(right,
+      GRIP_TOP_Y - 4, right - SLIDER_LINE_WIDTH, GRIP_TOP_Y - 4);
+    g.drawLine(right, GRIP_TOP_Y + SLIDER_LINE_HEIGHT,
+      right - SLIDER_LINE_WIDTH, GRIP_TOP_Y + SLIDER_LINE_HEIGHT);
+
+    // refresh everything
+    lSlideMoved = true;
+    rSlideMoved = true;
+    textChanged = true;
+    paintMinimum(g);
+  }
+
+  /**
+   * Repaints anything that needs it.
+   */
+  public void repaint() {
+    Graphics g = getGraphics();
+    if (g != null) {
+      paintMinimum(g);
+      g.dispose();
+    }
+  }
+
+  /**
+   * Paints only components that have changed.
+   */
+  private void paintMinimum(Graphics g) {
+    int w = getSize().width;
+    if (lSlideMoved) {
+      g.setColor(Color.black);
+      g.fillRect(SLIDER_LINE_WIDTH, GRIP_TOP_Y, maxGrip - 3, GRIP_HEIGHT);
+      g.setColor(Color.white);
+      g.drawLine(SLIDER_LINE_WIDTH, GRIP_MIDDLE_Y, maxGrip - 3, GRIP_MIDDLE_Y);
+      g.setColor(Color.yellow);
+      int[] xpts = {minGrip - GRIP_WIDTH, minGrip + 1, minGrip + 1};
+      int[] ypts = {GRIP_MIDDLE_Y, GRIP_TOP_Y, GRIP_BOTTOM_Y};
+      g.fillPolygon(xpts, ypts, 3);
+    }
+    if (rSlideMoved) {
+      g.setColor(Color.black);
+      g.fillRect(minGrip + 1, GRIP_TOP_Y, w - minGrip - 3, GRIP_HEIGHT);
+      g.setColor(Color.white);
+      g.drawLine(minGrip + 1, GRIP_MIDDLE_Y, w - 3, GRIP_MIDDLE_Y);
+      g.setColor(Color.yellow);
+      int[] xpts = new int[] {maxGrip + GRIP_WIDTH - 1, maxGrip, maxGrip};
+      int[] ypts = {GRIP_MIDDLE_Y, GRIP_TOP_Y, GRIP_BOTTOM_Y};
+      g.fillPolygon(xpts, ypts, 3);
+    }
+    if (lSlideMoved || rSlideMoved) {
+      g.setColor(Color.pink);
+      g.fillRect(minGrip + 1, GRIP_MIDDLE_Y, maxGrip - minGrip - 1, 3);
+    }
+    if (textChanged) drawLabels(g, w);
+    lSlideMoved = false;
+    rSlideMoved = false;
+    textChanged = false;
+  }
+
+  /**
+   * Updates the labels at the bottom of the widget.
+   */
+  private void drawLabels(Graphics g, int lastW) {
+    int w = getSize().width;
+    FontMetrics fm = g.getFontMetrics();
+    if (lastMinLimit != minLimit || lastW != w) {
+      // minimum bound text string
+      g.setColor(Color.black);
+      int sw = fm.stringWidth(""+lastMinLimit);
+      g.fillRect(1, FONT_TOP_Y, sw, FONT_HEIGHT);
+      lastMinLimit = minLimit;
+    }
+    if (lastMaxLimit != maxLimit || lastW != w) {
+      // maximum bound text string
+      g.setColor(Color.black);
+      int sw = fm.stringWidth(""+lastMaxLimit);
+      g.fillRect(lastW - 4 - sw, FONT_TOP_Y, sw, FONT_HEIGHT);
+      lastMaxLimit = maxLimit;
+    }
+
+    // err on the side of wider bounds
+    String minS, maxS;
+    if (minValue < maxValue) { 
+      minS = Convert.shortString(minValue, Convert.ROUND_DOWN);
+      maxS = Convert.shortString(maxValue, Convert.ROUND_UP);
+    }
+    else {
+      minS = Convert.shortString(minValue, Convert.ROUND_UP);
+      maxS = Convert.shortString(maxValue, Convert.ROUND_DOWN);
+    }
+
+    String curStr = name + " = (" + minS + ", " + maxS + ")";
+    if (!curStr.equals(lastCurStr) || lastW != w) {
+      g.setColor(Color.black);
+      int sw = fm.stringWidth(lastCurStr);
+      g.fillRect((lastW - sw) / 2, FONT_TOP_Y, sw, FONT_HEIGHT);
+      lastCurStr = curStr;
+    }
+    g.setColor(Color.white);
+
+    // err on the side of wider bounds
+    String minStr, maxStr;
+    if (minLimit < maxLimit) {
+      minStr = Convert.shortString(minLimit, Convert.ROUND_DOWN);
+      maxStr = Convert.shortString(maxLimit, Convert.ROUND_UP);
+    }
+    else {
+      minStr = Convert.shortString(minLimit, Convert.ROUND_DOWN);
+      maxStr = Convert.shortString(maxLimit, Convert.ROUND_UP);
+    }
+
+    g.drawString(minStr, 1, FONT_BOTTOM_Y);
+    g.drawString(maxStr, w - 4 - fm.stringWidth(maxStr), FONT_BOTTOM_Y);
+    g.drawString(curStr, (w - fm.stringWidth(curStr)) / 2, FONT_BOTTOM_Y);
+  }
+
+  /**
+   * Main method for testing purposes.
+   */
+  public static void main(String[] argv) {
+    RangeSlider rs = new RangeSlider("", 0.0f, 100.0f);
+    Frame f = new Frame("RangeSlider test");
+    f.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+          System.exit(0);
+        }
+      });
+    f.add(rs);
+    f.pack();
+    f.setVisible(true);
+
+    // dynamically set the values
+    rs.setValues(22.2222f, 76.5432f);
+  }
+
+}
diff --git a/visad/browser/Slider.java b/visad/browser/Slider.java
new file mode 100644
index 0000000..feb2d14
--- /dev/null
+++ b/visad/browser/Slider.java
@@ -0,0 +1,444 @@
+//
+// Slider.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+/**
+ * A simple slider widget.
+ */
+public class Slider extends Component
+  implements MouseListener, MouseMotionListener
+{
+
+  /**
+   * Preferred slider height.
+   */
+  public static final int SLIDER_PREF_HEIGHT = 14;
+
+  /**
+   * Preferred slider width.
+   */
+  public static final int SLIDER_PREF_WIDTH = 300;
+
+  /**
+   * Width of grip.
+   */
+  public static final int GRIP_WIDTH = 29;
+
+  /**
+   * Height of grip.
+   */
+  public static final int GRIP_HEIGHT = 14;
+
+  /**
+   * Y-coordinate of top of grip.
+   */
+  public static final int GRIP_TOP_Y = 0;
+
+  /**
+   * Y-coordinate of bottom of grip.
+   */
+  public static final int GRIP_BOTTOM_Y = GRIP_TOP_Y + GRIP_HEIGHT;
+
+  /**
+   * Y-coordinate of slider line.
+   */
+  public static final int LINE_LEVEL = GRIP_BOTTOM_Y - GRIP_HEIGHT / 2;
+
+  /**
+   * Current width of slider.
+   */
+  protected int width = SLIDER_PREF_WIDTH;
+
+  /**
+   * Current value of slider.
+   */
+  protected float value = 0;
+
+  /**
+   * Minimum value of slider.
+   */
+  protected float minimum = 0;
+
+  /**
+   * Maximum value of slider.
+   */
+  protected float maximum = 100;
+
+  /**
+   * Pixel location of grip.
+   */
+  private int grip = 0;
+
+  /**
+   * Flag whether mouse is currently affecting grip.
+   */
+  private boolean slide = false;
+
+  /**
+   * Flag whether grip has moved.
+   */
+  private boolean moved = false;
+
+  /**
+   * Minimum widget size.
+   */
+  protected Dimension minSize = null;
+
+  /**
+   * Preferred widget size.
+   */
+  protected Dimension prefSize = null;
+
+  /**
+   * Maximum widget size.
+   */
+  protected Dimension maxSize = null;
+
+  /**
+   * Constructs a slider with default value, minimum and maximum.
+   */
+  public Slider() { }
+  
+  /**
+   * Constructs a slider with the specified value, minimum and maximum.
+   */
+  public Slider(float value, float min, float max) {
+    setBounds(min, max);
+    setValue(value);
+    addMouseListener(this);
+    addMouseMotionListener(this);
+  }
+
+  /**
+   * Gets current slider value.
+   */
+  public float getValue() {
+    return value;
+  }
+  
+  /**
+   * Gets minimum slider value.
+   */
+  public float getMinimum() {
+    return minimum;
+  }
+
+  /**
+   * Gets maximum slider value.
+   */
+  public float getMaximum() {
+    return maximum;
+  }
+
+  /**
+   * Sets current slider value.
+   */
+  public void setValue(float value) {
+    this.value = value;
+    grip = valueToGrip(value);
+    repaint();
+  }
+
+  /**
+   * Sets minimum and maximum slider values.
+   */
+  public void setBounds(float min, float max) {
+    minimum = min;
+    maximum = max;
+    repaint();
+  }
+
+  /**
+   * Detects changes in slider width.
+   */
+  public void setBounds(int x, int y, int w, int h) {
+    super.setBounds(x, y, w, h);
+    width = w;
+  }
+
+  /**
+   * Horizontal position in grip where mouse was initially pressed.
+   */
+  private int gripX;
+
+  /**
+   * Updates grip location of slider.
+   */
+  private void updateGrip(int x) {
+    grip = x - gripX;
+    if (grip < 0) grip = 0;
+    if (grip > width - GRIP_WIDTH) grip = width - GRIP_WIDTH;
+    value = gripToValue(grip);
+    notifyListeners();
+    repaint();
+  }
+
+  /**
+   * Vector of listeners for slider changes.
+   */
+  private Vector listeners = new Vector();
+
+  /**
+   * Command string for slider change notification.
+   */
+  private String command = null;
+
+  /**
+   * Adds a listener to be notified of slider changes.
+   */
+  public void addActionListener(ActionListener l) {
+    synchronized (listeners) {
+      listeners.addElement(l);
+    }
+  }
+
+  /**
+   * Removes a listener to be notified of slider changes.
+   */
+  public void removeActionListener(ActionListener l) {
+    synchronized (listeners) {
+      listeners.removeElement(l);
+    }
+  }
+
+  /**
+   * Sets command string for slider change notification.
+   */
+  public void setActionCommand(String cmd) {
+    command = cmd;
+  }
+
+  /**
+   * Notifies listeners of slider change.
+   */
+  public void notifyListeners() {
+    ActionEvent e =
+      new ActionEvent(this, ActionEvent.ACTION_PERFORMED, command);
+    synchronized (listeners) {
+      for (int i=0; i<listeners.size(); i++) {
+        ActionListener l = (ActionListener) listeners.elementAt(i);
+        l.actionPerformed(e);
+      }
+    }
+  }
+
+  /**
+   * MouseListener method for moving grip.
+   */
+  public void mousePressed(MouseEvent e) {
+    int x = e.getX();
+    int y = e.getY();
+
+    if (Widget.containedIn(x, y, grip, 0, GRIP_WIDTH, GRIP_HEIGHT))
+    {
+      // mouse pressed in grip
+      gripX = x - grip;
+      slide = true;
+    }
+    else if (Widget.containedIn(x, y, 0, 0, grip - 1, GRIP_HEIGHT) ||
+      Widget.containedIn(x, y,
+      grip + GRIP_WIDTH, 0, width - grip - GRIP_WIDTH, GRIP_HEIGHT))
+    {
+      // mouse pressed in slider but outside of grip
+      gripX = GRIP_WIDTH / 2;
+      slide = true;
+      updateGrip(x);
+    }
+  }
+
+  /**
+   * MouseListener method for moving grip.
+   */
+  public void mouseReleased(MouseEvent e) {
+    slide = false;
+  }
+
+  /**
+   * Not used.
+   */
+  public void mouseClicked(MouseEvent e) { }
+
+  /**
+   * Not used.
+   */
+  public void mouseEntered(MouseEvent e) { }
+
+  /**
+   * Not used.
+   */
+  public void mouseExited(MouseEvent e) { }
+
+  /**
+   * MouseMotionListener method for moving grip.
+   */
+  public void mouseDragged(MouseEvent e) {
+    int x = e.getX();
+    int y = e.getY();
+
+    // move grip
+    if (slide) updateGrip(x);
+  }
+
+  /**
+   * Not used.
+   */
+  public void mouseMoved(MouseEvent e) { }
+
+  /**
+   * Returns minimum size of slider.
+   */
+  public Dimension getMinimumSize() {
+    if (minSize == null) minSize = new Dimension(0, SLIDER_PREF_HEIGHT);
+    return minSize;
+  }
+
+  /**
+   * Sets minimum size of slider.
+   */
+  public void setMinimumSize(Dimension dim) {
+    minSize = dim;
+  }
+
+  /**
+   * Returns preferred size of slider.
+   */
+  public Dimension getPreferredSize() {
+    if (prefSize == null) {
+      prefSize = new Dimension(SLIDER_PREF_WIDTH, SLIDER_PREF_HEIGHT);
+    }
+    return prefSize;
+  }
+
+  /**
+   * Sets preferred size of slider.
+   */
+  public void setPreferredSize(Dimension dim) {
+    prefSize = dim;
+  }
+
+  /**
+   * Returns maximum size oa slider.
+   */
+  public Dimension getMaximumSize() {
+    if (maxSize == null) {
+      maxSize = new Dimension(Integer.MAX_VALUE, SLIDER_PREF_HEIGHT);
+    }
+    return maxSize;
+  }
+
+  /**
+   * Sets preferred size of slider.
+   */
+  public void setMaximumSize(Dimension dim) {
+    maxSize = dim;
+  }
+
+  /**
+   * Converts grip pixel value to slider value.
+   */
+  private float gripToValue(int grip) {
+    return (float) grip / (width - GRIP_WIDTH) * (maximum - minimum) + minimum;
+  }
+
+  /**
+   * Converts slider value to grip pixel value.
+   */
+  private int valueToGrip(float value) {
+    return (int)
+      ((value - minimum) / (maximum - minimum) * (width - GRIP_WIDTH));
+  }
+
+  /**
+   * Draws the slider.
+   */
+  public void paint(Graphics g) {
+    // draw slider background
+    g.setColor(Widget.PALE_GRAY);
+    g.fillRect(0, 0, grip, LINE_LEVEL);
+    g.fillRect(grip + GRIP_WIDTH, 0, width - grip - GRIP_WIDTH, LINE_LEVEL);
+    g.fillRect(0, LINE_LEVEL + 1, grip, GRIP_HEIGHT - LINE_LEVEL - 1);
+    g.fillRect(grip + GRIP_WIDTH, LINE_LEVEL + 1,
+      width - grip - GRIP_WIDTH, GRIP_HEIGHT - LINE_LEVEL - 1);
+
+    // draw corner grip dots
+    g.drawRect(grip, 0, 0, 0);
+    g.drawRect(grip + GRIP_WIDTH - 1, 0, 0, 0);
+    g.drawRect(grip, GRIP_HEIGHT - 1, 0, 0);
+    g.drawRect(grip + GRIP_WIDTH - 1, GRIP_HEIGHT - 1, 0, 0);
+
+    // draw central grip block
+    g.fillRect(grip + 2, 2, GRIP_WIDTH - 3, GRIP_HEIGHT - 3);
+
+    // draw outer grip outline
+    g.setColor(Color.black);
+    g.drawLine(grip + 1, 0, grip + GRIP_WIDTH - 2, 0);
+    g.drawLine(grip + 1, GRIP_HEIGHT - 1,
+      grip + GRIP_WIDTH - 2, GRIP_HEIGHT - 1);
+    g.drawLine(grip, 1, grip, GRIP_HEIGHT - 2);
+    g.drawLine(grip + GRIP_WIDTH - 1, 1,
+      grip + GRIP_WIDTH - 1, GRIP_HEIGHT - 2);
+
+    // draw inner grip outline
+    g.setColor(Color.white);
+    g.drawLine(grip + 1, 1, grip + GRIP_WIDTH - 2, 1);
+    g.drawLine(grip + 1, 2, grip + 1, GRIP_HEIGHT - 2);
+
+    // draw slider line
+    if (grip > 0) g.drawLine(0, LINE_LEVEL, grip - 1, LINE_LEVEL);
+    g.drawLine(grip + GRIP_WIDTH, LINE_LEVEL, width, LINE_LEVEL);
+  }
+
+  // CTR: ?
+  public void repaint() {
+    Graphics g = getGraphics();
+    if (g != null) {
+      paint(g);
+      g.dispose();
+    }
+  }
+
+  /**
+   * Main method for testing purposes.
+   */
+  public static void main(String[] argv) {
+    Slider s = new Slider(26.3f, 0.0f, 100.0f);
+    Frame f = new Frame("Slider test");
+    f.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+          System.exit(0);
+        }
+      });
+    f.add(s);
+    f.pack();
+    f.setVisible(true);
+  }
+
+}
diff --git a/visad/browser/VisADApplet.java b/visad/browser/VisADApplet.java
new file mode 100644
index 0000000..7821f47
--- /dev/null
+++ b/visad/browser/VisADApplet.java
@@ -0,0 +1,674 @@
+//
+// VisADApplet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.applet.Applet;
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.MemoryImageSource;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * An applet for connecting to a VisAD display available through a
+ * SocketSlaveDisplay server. The applet functions completely independently
+ * of VisAD, using only JDK 1.1 code, so that it can be imbedded
+ * within a web page for use in a web browser.
+ */
+public class VisADApplet extends Applet
+  implements ActionListener, MouseListener, MouseMotionListener, WidgetListener
+{
+
+  /**
+   * Debugging flag.
+   */
+  private static final boolean DEBUG = false;
+
+  /**
+   * The default host name for the SocketSlaveDisplay server.
+   */
+  private static final String DEFAULT_HOST = "localhost";
+
+  /**
+   * The default port at which to connect.
+   */
+  private static final int DEFAULT_PORT = 4567;
+
+  /**
+   * Code for refresh action.
+   */
+  public static final int REFRESH = 0;
+
+  /**
+   * Code for mouse event action.
+   */
+  public static final int MOUSE_EVENT = 1;
+
+  /**
+   * Code for message action.
+   */
+  public static final int MESSAGE = 2;
+
+  /**
+   * Whether the applet client is connected to a server.
+   */
+  private boolean connected = false;
+
+  /**
+   * IP address of the server.
+   */
+  private String address = "";
+
+  /**
+   * Port of the server.
+   */
+  private int port = DEFAULT_PORT;
+
+  /**
+   * Currently connected socket.
+   */
+  private Socket socket = null;
+
+  /**
+   * Output stream of currently connected socket.
+   */
+  private DataOutputStream out = null;
+
+  /**
+   * ID number.
+   */
+  private int id;
+
+  /**
+   * Latest image from the server's display.
+   */
+  private Image image = null;
+
+  /**
+   * Text field for typing in IP address of server.
+   */
+  private TextField addressField;
+
+  /**
+   * Text field for typing in port of server.
+   */
+  private TextField portField;
+
+  /**
+   * Button for connecting to the specified IP address and port.
+   */
+  private Button connectButton;
+
+  /**
+   * Canvas for painting remote display image from the server.
+   */
+  private Component canvas;
+
+  /**
+   * Frame for display widgets.
+   */
+  private Frame frame;
+
+  /**
+   * Layout manager for widget frame.
+   */
+  private GridBagLayout widgetLayout;
+
+  /**
+   * GridBagConstraints object for use in widget layout.
+   */
+  private GridBagConstraints constraints;
+
+  /**
+   * Thread for communicating with server.
+   */
+  private Thread commThread = null;
+
+  /**
+   * Hashtable for storing widgets.
+   */
+  private Hashtable hashtable = new Hashtable();
+
+  private Applet myself;
+  /**
+   * Adds a component to the applet with the specified constraints.
+   */
+  private void addComponent(Component c, GridBagLayout layout,
+    int x, int y, int w, int h, double wx, double wy)
+  {
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridx = x;
+    gbc.gridy = y;
+    gbc.gridwidth = w;
+    gbc.gridheight = h;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weightx = wx;
+    gbc.weighty = wy;
+    layout.setConstraints(c, gbc);
+    add(c);
+  }
+
+  /**
+   * Adds a widget to the control panel.
+   */
+  private void addWidget(Widget widget, String hash) {
+    // add widget to hashtable
+    hashtable.put(hash, widget);
+
+    // add widget to control panel
+    if (constraints.gridy > 0) {
+      Divider divider = new Divider();
+      widgetLayout.setConstraints(divider, constraints);
+      constraints.gridy++;
+      frame.add(divider);
+    }
+    widgetLayout.setConstraints(widget, constraints);
+    constraints.gridy++;
+    frame.add(widget);
+    frame.pack();
+    frame.setVisible(true);
+  }
+
+  /**
+   * Removes all widgets from the control panel.
+   */
+  private synchronized void removeAllWidgets() {
+    // clear hashtable
+    hashtable = new Hashtable();
+
+    // clear control panel
+    constraints.gridy = 0;
+    frame.setVisible(false);
+    frame.removeAll();
+  }
+
+  /**
+   * Initializes the applet and lays out its GUI.
+   */
+  public void init() {
+    // set background to white
+    setBackground(Color.white);
+
+    // lay out components with GridBagLayout
+    GridBagLayout gridbag = new GridBagLayout();
+    setLayout(gridbag);
+    myself = (Applet)this;
+
+    // construct GUI components
+    addressField = new TextField(DEFAULT_HOST);
+    portField = new TextField("" + DEFAULT_PORT, 4);
+    connectButton = new Button("Connect");
+    canvas = new Component() {
+      public void update(Graphics g) { paint(g); }
+      public void paint(Graphics g) {
+        if (connected) {
+          // connected; paint the remote display's image
+          if (image != null) {
+            g.drawImage(image, 0, 0, this);
+          }
+        }
+        else {
+          // not connected; paint directions on how to connect
+          g.setColor(Color.black);
+          g.drawString("VisADApplet", 80, 20);
+          g.drawString("To connect to a VisAD display available", 10, 50);
+          g.drawString("through a SocketSlaveDisplay server, type", 10, 70);
+          g.drawString("the IP address of the server into the IP", 10, 90);
+          g.drawString("address field and type the port of the", 10, 110);
+          g.drawString("server into the port field, then press", 10, 130);
+          g.drawString("the Connect button.", 10, 150);
+        }
+      }
+    };
+
+    // respond to GUI component events
+    addressField.addActionListener(this);
+    portField.addActionListener(this);
+    connectButton.addActionListener(this);
+    canvas.addMouseListener(this);
+    canvas.addMouseMotionListener(this);
+
+    // lay out GUI components
+    addComponent(new Label("IP address"), gridbag, 0, 0, 1, 1, 0.0, 0.0);
+    addComponent(addressField, gridbag, 1, 0, 1, 1, 1.0, 0.0);
+    addComponent(new Label("Port"), gridbag, 2, 0, 1, 1, 0.0, 0.0);
+    addComponent(portField, gridbag, 3, 0, 1, 1, 0.0, 0.0);
+    addComponent(connectButton, gridbag, 4, 0, 1, 1, 0.0, 0.0);
+    addComponent(canvas, gridbag, 0, 1, 5, 1, 1.0, 1.0);
+
+    // construct widget frame
+    frame = new Frame("Controls");
+    widgetLayout = new GridBagLayout();
+    frame.setLayout(widgetLayout);
+    constraints = new GridBagConstraints();
+    constraints.gridx = 0;
+    constraints.gridy = 0;
+    constraints.fill = GridBagConstraints.BOTH;
+    frame.setBackground(Widget.PALE_GRAY);
+  }
+
+  /**
+   * Close the current server connection
+   */
+  public void disconnect() {
+    if (connected) {
+      connected = false;
+      // remove all widget listeners
+      for (int i=0; i<frame.getComponentCount(); i++) {
+        Component c = frame.getComponent(i);
+        if (c instanceof Widget) {
+          Widget w = (Widget) frame.getComponent(i);
+          w.removeWidgetListener(this);
+        }
+      }
+      // remove all widgets from control panel
+      removeAllWidgets();
+      repaint();
+    }
+  }
+
+  /**
+   * Requests a refresh from the server.
+   */
+  private void requestRefresh() {
+    if (out != null) {
+      try {
+        out.writeInt(id);
+        out.writeInt(REFRESH);
+      }
+      catch (SocketException exc) {
+        // problem communicating with the server; it has probably disconnected
+        disconnect();
+      }
+      catch (IOException exc) {
+        // problem communicating with server; it has probably disconnected
+        disconnect();
+      }
+    }
+  }
+
+  /**
+   * Sends the specified mouse event through the socket to the server.
+   */
+  private void sendEvent(MouseEvent e) {
+    int mid = e.getID();
+    long when = e.getWhen();
+    int mods = e.getModifiers();
+    int x = e.getX();
+    int y = e.getY();
+    int clicks = e.getClickCount();
+    boolean popup = e.isPopupTrigger();
+    if (out != null) {
+      try {
+        out.writeInt(id);
+        out.writeInt(MOUSE_EVENT);
+        out.writeInt(mid);
+        out.writeLong(when);
+        out.writeInt(mods);
+        out.writeInt(x);
+        out.writeInt(y);
+        out.writeInt(clicks);
+        out.writeBoolean(popup);
+      }
+      catch (SocketException exc) {
+        // problem communicating with server; it has probably disconnected
+        disconnect();
+      }
+      catch (IOException exc) {
+        // problem communicating with server; it has probably disconnected
+        disconnect();
+      }
+    }
+  }
+
+  /**
+   * Sends the specified message through the socket to the server.
+   */
+  private void sendMessage(String message) {
+    if (out != null) {
+      try {
+        out.writeInt(id);
+        out.writeInt(MESSAGE);
+        out.writeInt(message.length());
+        out.writeChars(message);
+      }
+      catch (SocketException exc) {
+        // problem communicating with server; it has probably disconnected
+        disconnect();
+      }
+      catch (IOException exc) {
+        // problem communicating with server; it has probably disconnected
+        disconnect();
+      }
+    }
+  }
+
+  /**
+   * Fired when a button is pressed or enter is pressed in a text box.
+   */
+  public synchronized void actionPerformed(ActionEvent e) {
+    // highlight the connect button to indicate that connection is happening
+    connectButton.requestFocus();
+
+    // obtain the new IP address and port
+    String address = addressField.getText();
+    int port = this.port;
+    try {
+      port = Integer.parseInt(portField.getText());
+    }
+    catch (NumberFormatException exc) { }
+    portField.setText("" + port);
+
+    // connect to the new IP address and port
+    Socket sock = null;
+    try {
+      sock = new Socket(address, port);
+    }
+    catch (UnknownHostException exc) {
+      addressField.setText("" + this.address);
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    if (sock == null) return;
+
+    if (connected) {
+      // kill the old socket
+      try {
+        socket.close();
+      }
+      catch (IOException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+
+      // wait for old communication thread to die
+      disconnect();
+      while (commThread.isAlive()) {
+        try {
+          Thread.sleep(100);
+        }
+        catch (InterruptedException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+    }
+
+    // finish setting up new socket
+    socket = sock;
+    DataInputStream sin = null;
+    try {
+      sin = new DataInputStream(socket.getInputStream());
+      out = new DataOutputStream(socket.getOutputStream());
+
+      // get client ID number from server
+      id = 0;
+      while (true) {
+        id = sin.readInt();
+        if (id == 0) {
+          try {
+            Thread.sleep(100);
+          }
+          catch (InterruptedException exc) { }
+        }
+        else break;
+      }
+      connected = true;
+    }
+    catch (IOException exc) {
+      // problem communicating with server; it has probably disconnected
+      disconnect();
+    }
+
+    // set a new thread to manage communication with the server
+    final DataInputStream in = sin;
+    final VisADApplet applet = this;
+    commThread = new Thread(new Runnable() {
+      public void run() {
+        try {
+          // request a refresh so that the server sends the image
+          requestRefresh();
+
+          // loop until the socket gets closed
+          while (connected) {
+            // read the latest display image
+            int w = in.readInt();
+            if (w == 0) continue;
+            if (w == -1) {
+              // server is sending a message
+              int len = in.readInt();
+              char[] c = new char[len];
+              for (int i=0; i<len; i++) c[i] = in.readChar();
+              String message = new String(c);
+
+              // detect message type
+              if (message.startsWith("visad.ScalarMap\n")) {
+                // message is a scalar map update
+
+                // Parse message
+                StringTokenizer st = new StringTokenizer(message);
+                st.nextToken(); // skip scalar map id token
+                String scalar = st.nextToken();
+                String displayScalar = st.nextToken();
+                float min = Convert.getFloat(st.nextToken());
+                float max = Convert.getFloat(st.nextToken());
+
+                // update hashtable
+                String mapHash = scalar + " " + displayScalar;
+                String dsHash = "." + displayScalar;
+                Integer index = (Integer) hashtable.get(mapHash);
+                if (index == null) {
+                  // new scalar map
+                  Integer count = (Integer) hashtable.get(dsHash);
+                  if (count == null) {
+                    // new display scalar
+                    count = new Integer(0);
+                  }
+                  index = count;
+                  hashtable.put(mapHash, index);
+                  hashtable.put(dsHash, new Integer(count.intValue() + 1));
+                }
+
+                // Handle supported display scalar types
+                if (displayScalar.equals("DisplayIsoContour")) {
+                  // iso-contour map; update associated ContourWidget
+                  String widgetHash = "Contour" + index.intValue();
+                  ContourWidget widget =
+                    (ContourWidget) hashtable.get(widgetHash);
+                  widget.setName(scalar);
+                  widget.setRange(min, max);
+                }
+              }
+              else {
+                // message is a control update
+
+                // Parse message, which should be of the form:
+                //   class\nnumber\nstate
+                // where class is the class name of the control that has
+                // changed, number is the index into that class name's
+                // control list, and state is the save string
+                // corresponding to the control's new state.
+                StringTokenizer st = new StringTokenizer(message, "\n");
+                String controlClass = st.nextToken();
+                int index = Convert.getInt(st.nextToken());
+                String save = st.nextToken();
+
+                // parse class name
+                int dotIndex = controlClass.lastIndexOf(".");
+                int ctrlIndex = controlClass.lastIndexOf("Control");
+                String widgetName =
+                  controlClass.substring(dotIndex + 1, ctrlIndex);
+
+                // handle special cases
+                if (widgetName.equals("GraphicsMode")) widgetName = "GMC";
+
+                // construct widget class name and hash table key
+                String widgetClass = "visad.browser." + widgetName + "Widget";
+                String widgetHash = widgetName + index;
+
+                // get widget from hashtable
+                Widget widget = (Widget) hashtable.get(widgetHash);
+                if (widget == null) {
+                  // widget not found; instantiate widget of the proper type
+                  try {
+                    widget = (Widget) Class.forName(widgetClass).newInstance();
+                    widget.addWidgetListener(applet);
+                  }
+                  catch (ClassNotFoundException exc) {
+                    if (DEBUG) {
+                      // widget class does not exist
+                      System.err.println("Warning: ignoring status of " +
+                        "unknown " + widgetName + " widget.");
+                    }
+                  }
+                  catch (InstantiationException exc) {
+                    if (DEBUG) {
+                      // widget class cannot be instantiated
+                      System.err.println("Warning: ignoring status of " +
+                        "invalid " + widgetName + "widget.");
+                    }
+                  }
+                  catch (IllegalAccessException exc) {
+                    if (DEBUG) {
+                      // widget class constructor cannot be accessed
+                      System.err.println("Warning: ignoring status of " +
+                        "restricted " + widgetName + "widget.");
+                    }
+                  }
+                  if (widget != null) addWidget(widget, widgetHash);
+                }
+
+                if (widget != null) {
+                  // set widget's state to match save string from message
+                  widget.setSaveString(save);
+                }
+              }
+            }
+            else {
+              // server is sending an image
+              int h = in.readInt();
+              int len = in.readInt();
+              byte[] pixels = new byte[len];
+              int p = 0;
+              while (p < len) p += in.read(pixels, p, len - p);
+              int[] pix = Convert.bytesToInt(pixels);
+
+              // decode pixels from RLE
+              int[] decoded = Convert.decodeRLE(pix);
+
+              // reconstruct the image locally
+              if (image != null) image.flush();
+              image = createImage(new MemoryImageSource(w, h, decoded, 0, w));
+              MediaTracker tracker = new MediaTracker(myself);
+              tracker.addImage(image,0);
+              try { tracker.waitForAll(); }
+              catch (Exception tex) {;}
+
+              // redraw the applet's display canvas
+              canvas.paint(canvas.getGraphics());
+            }
+          }
+        }
+        catch (SocketException exc) {
+          // problem communicating with server; it has probably disconnected
+          applet.disconnect();
+        }
+        catch (IOException exc) {
+          // problem communicating with server; it has probably disconnected
+          applet.disconnect();
+        }
+      }
+    });
+    commThread.start();
+  }
+
+  public void mouseClicked(MouseEvent e) {
+    // This event currently generates a "type not recognized" error
+    // sendEvent(e);
+  }
+
+  public void mouseEntered(MouseEvent e) {
+    sendEvent(e);
+  }
+
+  public void mouseExited(MouseEvent e) {
+    sendEvent(e);
+  }
+
+  public void mousePressed(MouseEvent e) {
+    sendEvent(e);
+  }
+
+  public void mouseReleased(MouseEvent e) {
+    sendEvent(e);
+  }
+
+  public void mouseDragged(MouseEvent e) {
+    sendEvent(e);
+  }
+
+  public void mouseMoved(MouseEvent e) {
+    // This event currently generates a "type not recognized" error
+    // sendEvent(e);
+  }
+
+  public void widgetChanged(WidgetEvent e) {
+    Widget widget = e.getWidget();
+    String widgetClass = widget.getClass().getName();
+    // parse class name
+    int dotIndex = widgetClass.lastIndexOf(".");
+    int wdgtIndex = widgetClass.lastIndexOf("Widget");
+    String widgetName = widgetClass.substring(dotIndex + 1, wdgtIndex);
+
+    // handle special cases
+    String controlName = widgetName;
+    if (controlName.equals("GMC")) controlName = "GraphicsMode";
+
+    // construct control class name
+    String controlClass = "visad." + controlName + "Control";
+    String save = widget.getSaveString();
+
+    // determine widget number
+    int i = 0;
+    int index = -1;
+    Widget w;
+    do {
+      w = (Widget) hashtable.get(widgetName + i);
+      if (w == widget) {
+        index = i;
+        break;
+      }
+      i++;
+    }
+    while (w != null);
+
+    // send message to server
+    String message = controlClass + "\n" + index + "\n" + save;
+    sendMessage(message);
+  }
+
+}
diff --git a/visad/browser/Widget.java b/visad/browser/Widget.java
new file mode 100644
index 0000000..fc97e70
--- /dev/null
+++ b/visad/browser/Widget.java
@@ -0,0 +1,151 @@
+//
+// Widget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+/**
+ * Abstract superclass for all browser widgets.
+ */
+public abstract class Widget extends Panel {
+
+  /**
+   * Debugging flag.
+   */
+  protected static final boolean DEBUG = false;
+
+  /**
+   * Coded string value for true.
+   */
+  protected static final String TRUE = "T";
+
+  /**
+   * Coded string value for false.
+   */
+  protected static final String FALSE = "F";
+
+  protected static final Color PALE_GRAY = new Color(0.8f, 0.8f, 0.8f);
+
+  /**
+   * Vector of widget listeners.
+   */
+  private Vector listeners = new Vector();
+
+  /**
+   * Returns true if (px, py) is inside (x, y, w, h)
+   */
+  public static boolean containedIn(int px, int py,
+    int x, int y, int w, int h)
+  {
+    return new Rectangle(x, y, w, h).contains(px, py);
+  }
+
+  /**
+   * Performs GUI setup common to all widgets.
+   */
+  public Widget() {
+    setBackground(PALE_GRAY);
+  }
+
+  /**
+   * Adds a component to the applet with the specified constraints.
+   */
+  protected void addComponent(Component c, GridBagLayout layout,
+    int x, int y, int w, int h, double wx, double wy)
+  {
+    GridBagConstraints gbc = new GridBagConstraints();
+    gbc.gridx = x;
+    gbc.gridy = y;
+    gbc.gridwidth = w;
+    gbc.gridheight = h;
+    gbc.fill = GridBagConstraints.BOTH;
+    gbc.weightx = wx;
+    gbc.weighty = wy;
+    layout.setConstraints(c, gbc);
+    add(c);
+  }
+
+  /**
+   * Pops up a frame to test this widget.
+   */
+  protected void testWidget() {
+    String title = getClass().getName();
+    title = title.substring(title.lastIndexOf('.') + 1);
+    Frame f = new Frame(title);
+    f.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    f.add(this);
+    f.pack();
+    f.show();
+  }
+
+  /**
+   * Adds a widget listener.
+   */
+  public void addWidgetListener(WidgetListener l) {
+    synchronized (listeners) {
+      listeners.addElement(l);
+    }
+  }
+
+  /**
+   * Removes a widget listener.
+   */
+  public void removeWidgetListener(WidgetListener l) {
+    synchronized (listeners) {
+      listeners.removeElement(l);
+    }
+  }
+
+  /**
+   * Notifies all widget listeners of the given widget event.
+   */
+  public void notifyListeners(WidgetEvent e) {
+    synchronized (listeners) {
+      for (int i=0; i<listeners.size(); i++) {
+        WidgetListener l = (WidgetListener) listeners.elementAt(i);
+        l.widgetChanged(e);
+      }
+    }
+  }
+
+  /**
+   * Gets a string representing this widget's current state.
+   */
+  public abstract String getSaveString();
+
+  /**
+   * Reconstructs this widget's state using the specified save string.
+   */
+  public abstract void setSaveString(String save);
+
+}
diff --git a/visad/browser/WidgetEvent.java b/visad/browser/WidgetEvent.java
new file mode 100644
index 0000000..4cec772
--- /dev/null
+++ b/visad/browser/WidgetEvent.java
@@ -0,0 +1,63 @@
+//
+// WidgetEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.awt.Event;
+
+/**
+ * WidgetEvent represents an Event regarding any of the various browser
+ * Widgets. They are sourced by Widget objects and received by
+ * WidgetListener objects.
+ */
+public class WidgetEvent extends Event {
+
+  /**
+   * Source of event.
+   */
+  private Widget widget;
+
+  /**
+   * Constructs a new WidgetEvent object.
+   *
+   * @param w  widget that sends the event
+   */
+  public WidgetEvent(Widget w) {
+    // don't pass widget as the source, since source is transient inside Event
+    super(null, 0, null);
+    widget = w;
+  }
+
+  /**
+   * Gets the Widget that sent this WidgetEvent.
+   *
+   * @return Widget object source.
+   */
+  public Widget getWidget() {
+    return widget;
+  }
+
+}
diff --git a/visad/browser/WidgetListener.java b/visad/browser/WidgetListener.java
new file mode 100644
index 0000000..834c482
--- /dev/null
+++ b/visad/browser/WidgetListener.java
@@ -0,0 +1,41 @@
+//
+// WidgetListener.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.browser;
+
+import java.util.EventListener;
+
+/**
+ * WidgetListener is the EventListener interface for WidgetEvents.
+ */
+public interface WidgetListener extends EventListener {
+
+  /**
+   * Send a WidgetEvent to this WidgetListener.
+   */
+  void widgetChanged(WidgetEvent e);
+
+}
diff --git a/visad/browser/package.html b/visad/browser/package.html
new file mode 100644
index 0000000..816deed
--- /dev/null
+++ b/visad/browser/package.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides a JDK 1.1-compatible set of classes for using VisAD
+in browsers via sockets connected to a remote server.
+The package is stand-alone; that is, the rest of VisAD is not
+required to run the visad.browser package.
+
+</body>
+</html>
+
diff --git a/visad/browser/socket_applet.html b/visad/browser/socket_applet.html
new file mode 100644
index 0000000..19056a8
--- /dev/null
+++ b/visad/browser/socket_applet.html
@@ -0,0 +1,13 @@
+<html>
+
+<!-- HTML document for use with VisADApplet.java -->
+
+<head><title>VisAD SocketServer Applet</title></head>
+<body>
+<applet code="visad.browser.VisADApplet.class" width=300 height=400
+        alt="Please enable Java to see this applet.">
+Sorry, you must have a browser that supports Java to see the applet.
+</applet>
+</body>
+</html>
+
diff --git a/visad/browser/viewer_applet.html b/visad/browser/viewer_applet.html
new file mode 100644
index 0000000..a404ddd
--- /dev/null
+++ b/visad/browser/viewer_applet.html
@@ -0,0 +1,13 @@
+<html>
+
+<!-- HTML document for use with VisADApplet.java -->
+
+<head><title>VisAD SocketServer Applet</title></head>
+<body>
+<applet codebase="../../../build" code="visad.browser.VisADApplet" width=300 height=400
+        alt="Please enable Java to see this applet.">
+Sorry, you must have a browser that supports Java to see the applet.
+</applet>
+</body>
+</html>
+
diff --git a/visad/cluster/ClientDisplayRendererJ3D.java b/visad/cluster/ClientDisplayRendererJ3D.java
new file mode 100644
index 0000000..64e4c23
--- /dev/null
+++ b/visad/cluster/ClientDisplayRendererJ3D.java
@@ -0,0 +1,61 @@
+//
+// ClientDisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+/**
+ * <CODE>ClientDisplayRendererJ3D</CODE> is the DisplayRenderer
+ * for cluster clients.<P>
+ */
+public class ClientDisplayRendererJ3D extends DefaultDisplayRendererJ3D {
+
+  private long time_out = 10000;
+
+  /**
+   * This is the <CODE>DisplayRenderer</CODE> used for cluster clients.
+   */
+  public ClientDisplayRendererJ3D () {
+    this(10000);
+  }
+
+  public ClientDisplayRendererJ3D (long to) {
+    super();
+    time_out = to;
+  }
+
+  public DataRenderer makeDefaultRenderer() {
+    return new ClientRendererJ3D(time_out);
+  }
+
+  public boolean legalDataRenderer(DataRenderer renderer) {
+    return (renderer instanceof ClientRendererJ3D);
+  }
+
+}
+
diff --git a/visad/cluster/ClientRendererJ3D.java b/visad/cluster/ClientRendererJ3D.java
new file mode 100644
index 0000000..d4f4730
--- /dev/null
+++ b/visad/cluster/ClientRendererJ3D.java
@@ -0,0 +1,577 @@
+//
+// ClientRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+import javax.media.j3d.*;
+
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+import java.io.Serializable;
+
+/**
+   ClientRendererJ3D is the VisAD DataRenderer for cluster clients
+*/
+public class ClientRendererJ3D extends DefaultRendererJ3D {
+
+  private DisplayImpl display = null;
+  private ConstantMap[] cmaps = null;
+
+  private DataDisplayLink link = null;
+  private Data data = null;
+  private boolean cluster = true;
+
+  private RemoteClientAgentImpl[] agents = null;
+  private RemoteClientAgentImpl focus_agent = null;
+  private RemoteAgentContact[] contacts = null;
+
+  private long time_out = 10000;
+
+  private int[] resolutions = null;
+
+  public ClientRendererJ3D () {
+    this(10000);
+  }
+
+  public ClientRendererJ3D (long to) {
+    time_out = to;
+  }
+
+  public void setResolutions(int[] rs) {
+    if (rs == null) return;
+    int n = rs.length;
+    resolutions = new int[n];
+    for (int i=0; i<n; i++) resolutions[i] = rs[i];
+  }
+
+  public DataShadow prepareAction(boolean go, boolean initialize,
+                                  DataShadow shadow)
+         throws VisADException, RemoteException {
+
+    Data old_data = data;
+    DataDisplayLink[] Links = getLinks();
+    if (Links != null && Links.length > 0) {
+      link = Links[0];
+
+      // initialize cmaps if not already
+      if (cmaps == null) {
+        display = getDisplay();
+        Vector cvector = link.getConstantMaps();
+        if (cvector != null && cvector.size() > 0) {
+          int clength = cvector.size();
+          cmaps = new ConstantMap[clength];
+          for (int i=0; i<clength; i++) {
+            cmaps[i] = (ConstantMap) cvector.elementAt(i);
+          }
+        }
+      }
+
+      // get the data
+      try {
+        data = link.getData(); // PROXY
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+      if (data == null) {
+        addException(
+          new DisplayException("Data is null: ClientRendererJ3D.doTransform"));
+      }
+  
+      // is this cluster data?
+      cluster = (data instanceof RemoteClientDataImpl);
+
+/*
+      if (cluster) {
+        Set partitionSet = ((RemoteClientDataImpl) data).getPartitionSet();
+      }
+*/
+
+      if (cluster && data != old_data) { // PROXY
+        // send agents to nodes if data changed
+        RemoteClientDataImpl rcdi = (RemoteClientDataImpl) data;
+        focus_agent = new RemoteClientAgentImpl(null, -1, time_out);
+        RemoteClusterData[] jvmTable = rcdi.getTable();
+        int nagents = jvmTable.length - 1;
+        agents = new RemoteClientAgentImpl[nagents];
+        contacts = new RemoteAgentContact[nagents];
+        for (int i=0; i<nagents; i++) {
+          agents[i] = new RemoteClientAgentImpl(focus_agent, i);
+          DefaultNodeRendererAgent node_agent =
+            new DefaultNodeRendererAgent(agents[i], display.getName(), cmaps);
+          contacts[i] = ((RemoteNodeData) jvmTable[i]).sendAgent(node_agent);
+        }
+      }
+    }
+
+
+
+// WLH new 16 April 2001
+    Vector message = new Vector();
+    Vector map_vector = display.getMapVector();
+    Enumeration maps = map_vector.elements();
+    while (maps.hasMoreElements()) {
+      ScalarMap map = (ScalarMap) maps.nextElement();
+      message.addElement(map);
+      message.addElement(map.getControl());
+    }
+    Serializable[] responses =
+      focus_agent.broadcastWithResponses(message, contacts); // PROXY
+// System.out.println("ClientRendererJ3D.prepareAction messages received");
+
+
+    // now do usual prepareAction()
+    return super.prepareAction(go, initialize, shadow);
+  }
+
+  /** create a scene graph for Data in links[0] */
+  public BranchGroup doTransform() throws VisADException, RemoteException {
+    if (link == null || data == null) {
+      addException(
+        new DisplayException("Data is null: ClientRendererJ3D.doTransform"));
+    }
+
+    if (!cluster) {
+      // not cluster data, so just do the usual
+      return super.doTransform(); // PROXY (do this on user but not on client)
+    }
+
+    int n = contacts.length;
+    Vector[] messages = new Vector[n];
+
+    if (resolutions == null || resolutions.length != n) {
+      resolutions = new int[n];
+      for (int i=0; i<n; i++) resolutions[i] = 1;
+    }
+
+    for (int i=0; i<n; i++) {
+      // String message = "transform";
+      messages[i] = new Vector();
+      messages[i].addElement("transform");
+
+      messages[i].addElement(new Integer(resolutions[i]));
+
+      Vector map_vector = display.getMapVector();
+      Enumeration maps = map_vector.elements();
+      while (maps.hasMoreElements()) {
+        ScalarMap map = (ScalarMap) maps.nextElement();
+        messages[i].addElement(map);
+      }
+    }
+
+    // responses are VisADGroups
+    Serializable[] responses =
+      focus_agent.broadcastWithResponses(messages, contacts);
+// System.out.println("ClientRendererJ3D.doTransform messages received");
+
+    // responses are VisADGroups
+    // need to:
+    // 1. rebuild images and volumes
+    // 2. convert from VisADGroups to BranchGroups
+    //    GeometryArray = display.makeGeometry(VisADGeometryArray)
+    // 3. add them as children of branch
+
+    // link.clearData(); ????
+
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(Group.ALLOW_CHILDREN_READ);
+    branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+    n = responses.length;
+    for (int i=0; i<n; i++) {
+      if (responses[i] != null) {
+        VisADSceneGraphObject vsgo = (VisADSceneGraphObject) responses[i];
+        branch.addChild(convertSceneGraph(vsgo));
+      }
+    }
+    if (n == 0) ShadowTypeJ3D.ensureNotEmpty(branch, display);
+    return branch;
+  }
+
+  private boolean enable_spatial = true;
+
+  public synchronized void setSpatialValues(float[][] spatial_values) {
+    if (enable_spatial) super.setSpatialValues(spatial_values);
+  }
+
+  /* convert from VisAD scene graph to Java3D scene graph
+     and rebuild images and volumes */
+  public Node convertSceneGraph(VisADSceneGraphObject scene)
+         throws VisADException {
+    if (scene instanceof VisADSwitch) {
+      VisADSwitch Vswit = (VisADSwitch) scene;
+      BranchGroup branch = new BranchGroup();
+      branch.setCapability(BranchGroup.ALLOW_DETACH);
+      branch.setCapability(Group.ALLOW_CHILDREN_READ);
+      branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+      branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    
+      Switch swit = new Switch();
+      swit.setCapability(Switch.ALLOW_SWITCH_READ);
+      swit.setCapability(Switch.ALLOW_SWITCH_WRITE);
+      swit.setCapability(BranchGroup.ALLOW_DETACH);
+      swit.setCapability(Group.ALLOW_CHILDREN_READ);
+      swit.setCapability(Group.ALLOW_CHILDREN_WRITE);
+
+      int n = Vswit.numChildren();
+      Set set = Vswit.getSet();
+      // set != null for Animation
+      // set == null for volume rendering
+
+      if (set != null) {
+        // Switch for Animation or SelectValue
+        for (int i=0; i<n; i++) {
+          VisADSceneGraphObject vsgo = Vswit.getChild(i);
+          swit.addChild((Node) convertSceneGraph(vsgo));
+        }
+
+        RealType real = (RealType)
+          ((SetType) set.getType()).getDomain().getComponent(0);
+        AVControl control = null;
+        Vector mapVector = display.getMapVector();
+        Enumeration maps = mapVector.elements();
+        while (maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap )maps.nextElement();
+          if (real.equals(map.getScalar())) {
+            DisplayRealType dreal = map.getDisplayScalar();
+            if (dreal.equals(Display.Animation) ||
+                dreal.equals(Display.SelectValue)) {
+              control = (AVControl) map.getControl();
+              break;
+            }
+          } // end if (values != null && && real.equals(map.getScalar()))
+        }
+        if (control == null) {
+          throw new ClusterException("AVControl is null");
+        }
+
+        // from ShadowFunctionOrSetTypeJ3D.addSwitch()
+        ((AVControlJ3D) control).addPair(swit, set, this);
+        ((AVControlJ3D) control).init();
+      }
+      else { // if (set == null)
+        // Switch for volume rendering
+        // see visad.java3d.ShadowFunctionOrSetTypeJ3D.textureStackToGroup()
+        // and visad.cluster.ShadowNodeFunctionTypeJ3D.textureStackToGroup()
+        if (Vswit.numChildren() != 3) {
+          throw new ClusterException("VisADSwitch for volume render " +
+                                     "must have 3 children");
+        }
+
+        VisADGroup VbranchX =
+          (VisADGroup) ((VisADSwitch) scene).getChild(0);
+        VisADGroup VbranchY =
+          (VisADGroup) ((VisADSwitch) scene).getChild(1);
+        VisADGroup VbranchZ =
+          (VisADGroup) ((VisADSwitch) scene).getChild(2);
+
+        int nX = VbranchX.numChildren();
+        OrderedGroup branchX = new OrderedGroup();
+        branchX.setCapability(Group.ALLOW_CHILDREN_READ);
+        VisADAppearance[] appearanceX = new VisADAppearance[nX];
+        for (int i=0; i<nX; i++) {
+          VisADAppearance appearance = (VisADAppearance) VbranchX.getChild(i);
+          branchX.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        OrderedGroup branchXrev = new OrderedGroup();
+        branchXrev.setCapability(Group.ALLOW_CHILDREN_READ);
+        for (int i=nX-1; i>=0; i--) {
+          VisADAppearance appearance = (VisADAppearance) VbranchX.getChild(i);
+          branchXrev.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+
+        int nY = VbranchY.numChildren();
+        OrderedGroup branchY = new OrderedGroup();
+        branchY.setCapability(Group.ALLOW_CHILDREN_READ);
+        VisADAppearance[] appearanceY = new VisADAppearance[nY];
+        for (int i=0; i<nY; i++) {
+          VisADAppearance appearance = (VisADAppearance) VbranchY.getChild(i);
+          branchY.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        OrderedGroup branchYrev = new OrderedGroup();
+        branchYrev.setCapability(Group.ALLOW_CHILDREN_READ);
+        for (int i=nY-1; i>=0; i--) {
+          VisADAppearance appearance = (VisADAppearance) VbranchY.getChild(i);
+          branchYrev.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+
+        int nZ = VbranchZ.numChildren();
+        OrderedGroup branchZ = new OrderedGroup();
+        branchZ.setCapability(Group.ALLOW_CHILDREN_READ);
+        VisADAppearance[] appearanceZ = new VisADAppearance[nZ];
+        for (int i=0; i<nZ; i++) {
+          VisADAppearance appearance = (VisADAppearance) VbranchZ.getChild(i);
+          branchZ.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        OrderedGroup branchZrev = new OrderedGroup();
+        branchZrev.setCapability(Group.ALLOW_CHILDREN_READ);
+        for (int i=nZ-1; i>=0; i--) {
+          VisADAppearance appearance = (VisADAppearance) VbranchZ.getChild(i);
+          branchZrev.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        swit.addChild(branchX);
+        swit.addChild(branchY);
+        swit.addChild(branchZ);
+        swit.addChild(branchXrev);
+        swit.addChild(branchYrev);
+        swit.addChild(branchZrev);
+
+        ProjectionControlJ3D control =
+          (ProjectionControlJ3D) display.getProjectionControl();
+        control.addPair(swit, this);
+      }
+      branch.addChild(swit);
+      return branch;
+    }
+    else if (scene instanceof VisADGroup) {
+      VisADGroup group = (VisADGroup) scene;
+      BranchGroup branch = new BranchGroup();
+      branch.setCapability(BranchGroup.ALLOW_DETACH);
+      branch.setCapability(Group.ALLOW_CHILDREN_READ);
+      branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+      branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+      int n = group.numChildren();
+      for (int i=0; i<n; i++) {
+        VisADSceneGraphObject vsgo = group.getChild(i);
+        branch.addChild((Node) convertSceneGraph(vsgo));
+      }
+      ShadowTypeJ3D.ensureNotEmpty(branch, display);
+      return branch;
+    }
+    else if (scene instanceof VisADAppearance) {
+      VisADAppearance appearance = (VisADAppearance) scene;
+      GraphicsModeControl mode = display.getGraphicsModeControl();
+      VisADGeometryArray vga = appearance.array;
+      GeometryArray array = ((DisplayImplJ3D) display).makeGeometry(vga);
+      if (array == null) return null;
+      BufferedImage image = null;
+      if (appearance.image_pixels != null) {
+        image = new BufferedImage(appearance.image_width, appearance.image_height,
+                                  appearance.image_type);
+        image.setRGB(0, 0, appearance.image_width, appearance.image_height,
+                     appearance.image_pixels, 0, appearance.image_width);
+/* OR:
+        ColorModel colorModel = ColorModel.getRGBdefault();
+        WritableRaster raster =
+          colorModel.createCompatibleWritableRaster(appearance.image_width, 
+                                                    appearance.image_height);
+        DataBuffer db = raster.getDataBuffer();
+        if (!(db instanceof DataBufferInt)) {
+          throw new UnimplementedException("getRGBdefault isn't DataBufferInt");
+        }
+        image = new BufferedImage(colorModel, raster, false, null);
+        int[] intData = ((DataBufferInt)db).getData();
+        System.arraycopy(appearance.image_pixels, 0, intData, 0, intData.length);
+*/
+      }
+      if (image != null) {
+        // need to do Texture stuff
+        Appearance appearance_j3d =
+          makeTextureAppearance(appearance, mode, array);
+
+        // create TextureAttributes
+        TextureAttributes texture_attributes = new TextureAttributes();
+        texture_attributes.setTextureMode(TextureAttributes.MODULATE);
+        texture_attributes.setPerspectiveCorrectionMode(
+                              TextureAttributes.NICEST);
+        appearance_j3d.setTextureAttributes(texture_attributes);
+
+        int transparencyMode = mode.getTransparencyMode();
+
+        Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                          appearance.texture_width,
+                                          appearance.texture_height);
+        texture.setCapability(Texture.ALLOW_IMAGE_READ);
+        ImageComponent2D image2d =
+          new ImageComponent2D(ImageComponent.FORMAT_RGBA, image);
+        image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+        texture.setImage(0, image2d);
+        // this from textureStackToGroup,
+        // but not in textureToGroup
+        if (transparencyMode == TransparencyAttributes.FASTEST) {
+          texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+          texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+        }
+        else {
+          texture.setBoundaryModeS(Texture.CLAMP);
+          texture.setBoundaryModeT(Texture.CLAMP);
+          texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+          texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+        }
+        texture.setEnable(true);
+        appearance_j3d.setTexture(texture);
+        appearance_j3d.setCapability(Appearance.ALLOW_TEXTURE_READ);
+
+        Shape3D shape = new Shape3D(array, appearance_j3d);
+        shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+        return shape;
+      }
+      else {
+        Appearance appearance_j3d =
+          makeAppearance(appearance, mode, array);
+        Shape3D shape = new Shape3D(array, appearance_j3d);
+        return shape;
+      }
+    }
+    else {
+      throw new VisADException("unknown scene " + scene);
+    }
+  }
+
+  private Appearance makeTextureAppearance(VisADAppearance appearance,
+                           GraphicsModeControl mode, GeometryArray array) {
+    TransparencyAttributes c_alpha = null;
+    if (appearance.alpha == 1.0f) {
+      // constant opaque alpha = NONE
+      c_alpha = null;
+    }
+    else if (appearance.alpha == appearance.alpha) {
+      c_alpha = new TransparencyAttributes(TransparencyAttributes.BLENDED,
+                                           appearance.alpha);
+    }
+    else {
+      c_alpha = new TransparencyAttributes();
+      c_alpha.setTransparencyMode(TransparencyAttributes.BLENDED);
+    }
+    ColoringAttributes c_color = null;
+    if (appearance.red == appearance.red &&
+        appearance.green == appearance.green &&
+        appearance.blue == appearance.blue) {
+      c_color = new ColoringAttributes();
+      c_color.setColor(appearance.red, appearance.green, appearance.blue);
+    }
+    return ShadowTypeJ3D.staticMakeAppearance(mode, c_alpha, null,
+                                              array, false);
+  }
+
+  private Appearance makeAppearance(VisADAppearance appearance,
+                           GraphicsModeControl mode, GeometryArray array) {
+    TransparencyAttributes c_alpha = null;
+    if (appearance.alpha == 1.0f) {
+      // constant opaque alpha = NONE
+      c_alpha = new TransparencyAttributes(TransparencyAttributes.NONE, 0.0f);
+    }
+    else if (appearance.alpha == appearance.alpha) {
+      c_alpha = new TransparencyAttributes(mode.getTransparencyMode(),
+                                           appearance.alpha);
+    }
+    else {
+      c_alpha = new TransparencyAttributes(mode.getTransparencyMode(), 0.0f);
+    }
+    ColoringAttributes c_color = null;
+    if (appearance.red == appearance.red &&
+        appearance.green == appearance.green &&
+        appearance.blue == appearance.blue) {
+      c_color = new ColoringAttributes();
+      c_color.setColor(appearance.red, appearance.green, appearance.blue);
+    }
+    return ShadowTypeJ3D.staticMakeAppearance(mode, c_alpha, c_color,
+                                              array, false);
+  }
+
+  public DataShadow computeRanges(Data data, ShadowType type, DataShadow shadow) 
+         throws VisADException, RemoteException {
+    if (!cluster) {
+      return super.computeRanges(data, type, shadow);
+    }
+
+    DataShadow[] shadows = null;
+    Vector message = new Vector();
+    message.addElement(type);
+    if (shadow == null) {
+      message.addElement(new Integer(getDisplay().getScalarCount()));
+      // shadow =
+      //   data.computeRanges(type, getDisplay().getScalarCount());
+    }
+    else {
+      message.addElement(shadow);
+      // shadow = data.computeRanges(type, shadow);
+    }
+    Serializable[] responses =
+      focus_agent.broadcastWithResponses(message, contacts);
+// System.out.println("ClientRendererJ3D.computeRanges messages received");
+    DataShadow new_shadow = null;
+    int n = responses.length;
+    for (int i=0; i<n; i++) {
+      if (responses[i] != null) {
+        if (new_shadow == null) {
+          new_shadow = (DataShadow) responses[i];
+        }
+        else {
+          new_shadow.merge((DataShadow) responses[i]);
+        }
+      }
+    }
+    return new_shadow;
+  }
+
+  public Object clone() {
+    return new ClientRendererJ3D(time_out);
+  }
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    DisplayImpl display =
+      new DisplayImplJ3D("display", new ClientDisplayRendererJ3D());
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test ClientRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/cluster/ClusterException.java b/visad/cluster/ClusterException.java
new file mode 100644
index 0000000..24fee49
--- /dev/null
+++ b/visad/cluster/ClusterException.java
@@ -0,0 +1,40 @@
+//
+// ClusterException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+/**
+   ClusterException is an exception for VisAD cluster errors.<P>
+*/
+public class ClusterException extends VisADException {
+
+  public ClusterException() { super(); }
+  public ClusterException(String s) { super(s); }
+
+}
+
diff --git a/visad/cluster/DefaultNodeRendererAgent.java b/visad/cluster/DefaultNodeRendererAgent.java
new file mode 100644
index 0000000..e8ace91
--- /dev/null
+++ b/visad/cluster/DefaultNodeRendererAgent.java
@@ -0,0 +1,241 @@
+//
+// DefaultNodeRendererAgent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import java.rmi.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.io.Serializable;
+
+/**
+   DefaultNodeRendererAgent is the class for agents sent from
+   client to nodes to return VisADSceneGraphObjects from
+   NodeRendererJ3Ds.<P>
+*/
+public class DefaultNodeRendererAgent extends NodeAgent {
+
+  /** fields from constructor on client */
+  private String rmtDpyName = null; // display name on client
+  private ConstantMap[] cmaps = null; // ConstantMaps for data on client
+
+  /** fields on node */
+  private RemoteNodeDataImpl data = null; // data on node
+
+  /** fields constructed on node */
+  private DisplayImplJ3D display = null;
+  private NodeDisplayRendererJ3D ndr = null;
+  private DataReferenceImpl ref = null;
+  private NodeRendererJ3D nr = null;
+  private RemoteDisplayImpl remote_display = null;
+
+  public DefaultNodeRendererAgent(RemoteClientAgent source, String name,
+                                  ConstantMap[] cms) {
+    super(source);
+    rmtDpyName = name;
+    if (rmtDpyName == null) rmtDpyName = "null";
+    cmaps = cms;
+  }
+
+  public void run() {
+    Object o = (RemoteNodeDataImpl) getObject();
+    if (o == null || !(o instanceof RemoteNodeDataImpl)) {
+      System.out.println("DefaultNodeRendererAgent cannot run: " +
+                         "object must be RemoteNodeDataImpl " + o);
+      return;
+    }
+    data = (RemoteNodeDataImpl) o;
+
+    try {
+      // construct collaborative display but without links to
+      // data on client; client does not listen to node events;
+      // nodes do not listen to client REFERENCE_ADD events;
+      // nodes do listen to AUTO_SCALE events
+      ndr = new NodeDisplayRendererJ3D();
+// System.out.println("DefaultNodeRendererAgent.run after new NodeDisplayRendererJ3D");
+
+      String name = rmtDpyName + ".remote";
+      display = new DisplayImplJ3D(name, ndr, DisplayImplJ3D.TRANSFORM_ONLY);
+// System.out.println("DefaultNodeRendererAgent.run after new DisplayImplJ3D");
+
+      ref = new DataReferenceImpl("dummy");
+      RemoteDataReferenceImpl remote_ref = new RemoteDataReferenceImpl(ref);
+      remote_ref.setData(data);
+// System.out.println("DefaultNodeRendererAgent.run after setData");
+      nr = new NodeRendererJ3D(this);
+      remote_display = new RemoteDisplayImpl(display);
+// System.out.println("DefaultNodeRendererAgent.run after new RemoteDisplayImpl");
+
+
+      remote_display.addReferences(nr, ref, cmaps);
+// System.out.println("DefaultNodeRendererAgent.run after addReferences");
+
+
+    }
+    catch (VisADException e) {
+      DisplayImpl.printStack("ex " + e);
+      return;
+    }
+    catch (RemoteException e) {
+      DisplayImpl.printStack("ex " + e);
+      return;
+    }
+
+    Thread me = Thread.currentThread();
+// System.out.println("DefaultNodeRendererAgent.run " + me + " " + getAgentThread());
+    while (getAgentThread() == me) {
+// System.out.println("DefaultNodeRendererAgent.run before getMessage call");
+      Serializable message = getMessage();
+
+      Serializable response = null;
+      if (message instanceof String) {
+        String smessage = (String) message;
+        if (smessage.equals("stop")) {
+          return;
+        }
+        else if (smessage.equals("transform")) {
+// System.out.println("DefaultNodeRendererAgent.run trigger " + display.getName());
+          nr.enableTransform();
+          display.reDisplayAll();
+          // NodeRendererJ3D.doTransform() calls
+          // sendToClient(branch) for this, so no response
+          response = "none";
+        }
+      }
+      else if (message instanceof Vector) {
+        Vector vmessage = (Vector) message;
+        Object first = vmessage.elementAt(0);
+        if (first instanceof ShadowType) {
+          // if first element is ShadowType must be computeRanges message
+          ShadowType type = (ShadowType) first;
+          Object second = vmessage.elementAt(1);
+          try {
+            if (second instanceof DataShadow) {
+              DataShadow shadow = (DataShadow) second;
+              response = data.computeRanges(type, shadow);
+            }
+            else if (second instanceof Integer) {
+              int scalar_count = ((Integer) second).intValue();
+              response = data.computeRanges(type, scalar_count);
+            }
+          }
+          catch (VisADException e) {
+            DisplayImpl.printStack("ex " + e);
+            return;
+          }
+          catch (RemoteException e) {
+            DisplayImpl.printStack("ex " + e);
+            return;
+          }
+        }
+        else if (first instanceof ScalarMap) {
+// System.out.println("DefaultNodeRendererAgent.run first is ScalarMap");
+          try {
+            display.removeReference(ref);
+            display.clearMaps();
+            int m = vmessage.size();
+            for (int i=0; i<m; i+=2) {
+              ScalarMap map = (ScalarMap) vmessage.elementAt(i);
+              Control control = (Control) vmessage.elementAt(i + 1);
+              ScalarMap new_map =
+                new ScalarMap(map.getScalar(), map.getDisplayScalar());
+              display.addMap(new_map);
+              double[] range = map.getRange();
+              if (!Display.Animation.equals(new_map.getDisplayScalar())) {
+                new_map.setRange(range[0], range[1]);
+              }
+              Control new_control = new_map.getControl();
+              if (new_control != null) new_control.syncControl(control);
+            }
+            nr = new NodeRendererJ3D(this);
+            remote_display.addReferences(nr, ref, cmaps);
+          }
+          catch (VisADException e) {
+            DisplayImpl.printStack("ex " + e);
+            return;
+          }
+          catch (RemoteException e) {
+            DisplayImpl.printStack("ex " + e);
+            return;
+          }
+          response = "normal";
+// System.out.println("DefaultNodeRendererAgent.run ScalarMap response");
+        }
+        else if (first instanceof String) {
+          String sfirst = (String) first;
+          if (sfirst.equals("transform")) {
+            display.disableAction();
+            int resolution = ((Integer) vmessage.elementAt(1)).intValue();
+            nr.setResolution(resolution);
+            int m = vmessage.size();
+            Vector map_vector = display.getMapVector();
+            if (map_vector.size() != (m - 2)) {
+              System.out.println("ERROR1 " + map_vector.size() +
+                                 " != " + (m - 2));
+              return;
+            }
+            Enumeration maps = map_vector.elements();
+            for (int i=2; i<m; i++) {
+              ScalarMap map1 = (ScalarMap) vmessage.elementAt(i);
+              ScalarMap map2 = (ScalarMap) maps.nextElement();
+              if (!map1.getScalar().equals(map2.getScalar()) ||
+                  !map1.getDisplayScalar().equals(map2.getDisplayScalar()) ) {
+                System.out.println("ERROR2 " + map1 + " != " + map2);
+              }
+              double[] range = map1.getRange();
+              if (!Display.Animation.equals(map2.getDisplayScalar()) &&
+                  !Display.IsoContour.equals(map2.getDisplayScalar())) {
+                try {
+                  map2.setRange(range[0], range[1]);
+                }
+                catch (VisADException e) {
+                  DisplayImpl.printStack("ex " + e);
+                  return;
+                }
+                catch (RemoteException e) {
+                  DisplayImpl.printStack("ex " + e);
+                  return;
+                }
+              }
+            }
+            nr.enableTransform();
+            display.reDisplayAll();
+            display.enableAction();
+            // NodeRendererJ3D.doTransform() calls
+            // sendToClient(branch) for this, so no reponse
+            response = "none";
+          }
+        }
+      }
+      if (response == null) response = "error";
+      if (!response.equals("none")) sendToClient(response);
+    } // end while (getAgentThread() == me)
+  }
+
+}
+
diff --git a/visad/cluster/NodeAgent.java b/visad/cluster/NodeAgent.java
new file mode 100644
index 0000000..5d2611d
--- /dev/null
+++ b/visad/cluster/NodeAgent.java
@@ -0,0 +1,132 @@
+//
+// NodeAgent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import java.rmi.*;
+import java.io.Serializable;
+
+/**
+   NodeAgent is the abstract super-class for agents sent from
+   client to nodes.<P>
+*/
+public abstract class NodeAgent extends Object
+       implements Serializable, Runnable {
+
+  /** object of agent */
+  private Object object = null;
+
+  /** source of agent */
+  private RemoteClientAgent source = null;
+
+  /** RemoteAgentContact for communicating back to client */
+  RemoteAgentContactImpl contact = null;
+
+  /** NodeAgent is Serializable, mark as transient */
+  private transient Thread agentThread;
+
+  /** message from client, if non-null */
+  Serializable message = null;
+
+  public NodeAgent(RemoteClientAgent s) {
+    source = s;
+  }
+
+  // should only one NodeAgent of this class exist on a
+  // RemoteNodeDataImpl's agents Vector?
+  public boolean onlyOne() {
+    return true;
+  }
+
+  public Object getObject() {
+    return object;
+  }
+
+  public Thread getAgentThread() {
+    return agentThread;
+  }
+
+  // message from client
+  public synchronized void sendToNode(Serializable me) {
+// System.out.println("NodeAgent.sendToNode " + me);
+    message = me;
+    notify();
+  }
+
+  // called from run() methods of sub-classes
+  public synchronized Serializable getMessage() {
+// System.out.println("NodeAgent.getMessage enter");
+    while (message == null) {
+      try {
+        wait();
+      }
+      catch (InterruptedException e) {
+      }
+    }
+    Serializable me = message;
+    message = null;
+// System.out.println("NodeAgent.getMessage " + me);
+    return me;
+  }
+
+  public void sendToClient(Serializable message) {
+    try {
+      source.sendToClient(message);
+    }
+    catch (RemoteException e) {
+      System.out.println("unable to send: " + message);
+    }
+  }
+
+  /** create and start Thread, and return contact */
+  public RemoteAgentContactImpl getRemoteAgentContact(Object obj) {
+// System.out.println("NodeAgent.getRemoteAgentContact start Thread");
+    object = obj;
+    agentThread = new Thread(this);
+    agentThread.start();
+    try {
+      contact = new RemoteAgentContactImpl(this);
+      return contact;
+    }
+    catch (RemoteException e) {
+      return null;
+    }
+  }
+
+  public void stop() {
+    sendToNode("stop");
+    agentThread = null;
+  }
+
+  public abstract void run();
+/*
+    Thread me = Thread.currentThread();
+    while (agentThread == me) {
+    }
+*/
+
+}
+
diff --git a/visad/cluster/NodeDisplayRendererJ3D.java b/visad/cluster/NodeDisplayRendererJ3D.java
new file mode 100644
index 0000000..05a340f
--- /dev/null
+++ b/visad/cluster/NodeDisplayRendererJ3D.java
@@ -0,0 +1,57 @@
+//
+// NodeDisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+/**
+ * <CODE>NodeDisplayRendererJ3D</CODE> is the DisplayRenderer
+ * for cluster nodes.<P>
+ */
+public class NodeDisplayRendererJ3D extends TransformOnlyDisplayRendererJ3D {
+
+  /**
+   * This is the <CODE>DisplayRenderer</CODE> used for cluster nodes.
+   * <CODE>TRANSFORM_ONLY</CODE> api.
+   * It transforms data into VisADSceneGraphObject
+   * but does not render (and hence no interaction).
+   */
+  public NodeDisplayRendererJ3D () {
+    super();
+  }
+
+  public DataRenderer makeDefaultRenderer() {
+    return new NodeRendererJ3D();
+  }
+
+  public boolean legalDataRenderer(DataRenderer renderer) {
+    return (renderer instanceof NodeRendererJ3D);
+  }
+
+}
+
diff --git a/visad/cluster/NodeRendererJ3D.java b/visad/cluster/NodeRendererJ3D.java
new file mode 100644
index 0000000..b774b80
--- /dev/null
+++ b/visad/cluster/NodeRendererJ3D.java
@@ -0,0 +1,310 @@
+//
+// NodeRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.util.Delay;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.*;
+
+/**
+   NodeRendererJ3D is the VisAD class for transforming
+   data into VisADSceneGraphObjects, but not rendering,
+   on cluster nodes
+*/
+public class NodeRendererJ3D extends DefaultRendererJ3D {
+
+  private NodeAgent agent = null;
+
+  private boolean enable_transform = false;
+
+  private int resolution = 1;
+
+  /** this constructor is need for NodeDisplayRendererJ3D.makeDefaultRenderer()
+      but it should never be called */
+  public NodeRendererJ3D () {
+    this(null);
+  }
+
+  /** this DataRenderer transforms data into VisADSceneGraphObjects,
+      but does not render, on cluster nodes;
+      send scene graphs back via NodeAgent */
+  public NodeRendererJ3D (NodeAgent a) {
+    super();
+    agent = a;
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowNodeFunctionTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowNodeRealTupleTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowNodeRealTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowNodeSetTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowNodeTupleTypeJ3D(type, link, parent);
+  }
+
+  public void setResolution(int r) {
+    resolution = r;
+  }
+
+  public int getResolution() {
+    return resolution;
+  }
+
+  public void enableTransform() {
+    enable_transform = true;
+  }
+
+  public DataShadow prepareAction(boolean go, boolean initialize,
+                                  DataShadow shadow)
+         throws VisADException, RemoteException {
+    // don't autoscale: initialize = false
+    return super.prepareAction(go, false, shadow);
+  }
+
+  /** re-transform if needed;
+      return false if not done */
+  public boolean doAction() throws VisADException, RemoteException {
+    boolean all_feasible = get_all_feasible();
+    boolean any_changed = get_any_changed();
+    boolean any_transform_control = get_any_transform_control();
+    if (all_feasible && (any_changed || any_transform_control)) {
+/*
+System.out.println("RendererJ3D.doAction: any_changed = " + any_changed +
+                   " any_transform_control = " + any_transform_control);
+System.out.println(getLinks()[0].getThingReference().getName());
+*/
+
+      boolean branch = false;
+
+      // exceptionVector.removeAllElements();
+      clearAVControls();
+      try {
+        // doTransform creates a BranchGroup from a Data object
+        branch = fakeTransform();
+      }
+      catch (OutOfMemoryError e) {
+        // System.out.println("OutOfMemoryError, try again ...");
+        branch = false;
+        new Delay(250);
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        try {
+          branch = fakeTransform();
+        }
+        catch (BadMappingException ee) {
+          addException(ee);
+        }
+        catch (UnimplementedException ee) {
+          addException(ee);
+          branch = false;
+        }
+        catch (RemoteException ee) {
+          addException(ee);
+          branch = false;
+        }
+        catch (DisplayInterruptException ee) {
+          branch = false;
+        }
+      }
+      catch (BadMappingException e) {
+        addException(e);
+        branch = false;
+      }
+      catch (UnimplementedException e) {
+        addException(e);
+        branch = false;
+      }
+      catch (RemoteException e) {
+        addException(e);
+        branch = false;
+      }
+      catch (DisplayInterruptException e) {
+        branch = false;
+      }
+
+      if (!branch) {
+        all_feasible = false;
+        set_all_feasible(all_feasible);
+      }
+    }
+    else { // !(all_feasible && (any_changed || any_transform_control))
+      DataDisplayLink[] links = getLinks();
+      for (int i=0; i<links.length; i++) {
+        links[i].clearData();
+      }
+    }
+    return (all_feasible && (any_changed || any_transform_control));
+  }
+
+  /** create a VisADGroup scene graph for Data in links[0];
+      a substitute for doTransform() without and Java3D classes
+      in its signature */
+  public boolean fakeTransform() throws VisADException, RemoteException {
+
+// System.out.println("NodeRendererJ3D.doTransform enabled = " + enable_transform);
+
+    // don't do work unless requested by the client
+    if (!enable_transform) return true;
+    enable_transform = false;
+
+/*
+Vector map_vector = getDisplay().getMapVector();
+Enumeration maps = map_vector.elements();
+while (maps.hasMoreElements()) {
+  ScalarMap map = (ScalarMap) maps.nextElement();
+  double[] range = map.getRange();
+  Control control = map.getControl();
+  System.out.println(map + " " + ((float) range[0]) + " " + ((float) range[1]));
+  System.out.println("  " + control);
+}
+*/
+
+    VisADGroup branch = new VisADGroup();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return false;
+    }
+    DataDisplayLink link = Links[0];
+
+    ShadowTypeJ3D type = (ShadowTypeJ3D) link.getShadow();
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return false;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      addException(
+        new DisplayException("Data is null: NodeRendererJ3D.doTransform"));
+    }
+    else {
+      link.start_time = System.currentTimeMillis();
+      link.time_flag = false;
+      type.preProcess();
+
+      boolean post_process;
+      try {
+        // transform data into a depiction under branch
+        post_process = type.doTransform(branch, data, valueArray,
+                                        link.getDefaultValues(), this);
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return false;
+        }
+        throw re;
+      }
+
+      if (post_process) type.postProcess(branch);
+    }
+    link.clearData();
+
+    // send VisADGroup scene graph in branch back to client
+    if (agent != null) {
+      agent.sendToClient(branch);
+System.out.println("scene graph sent to client");
+    }
+
+    return true;
+  }
+
+  public Object clone() throws CloneNotSupportedException {
+    throw new CloneNotSupportedException("NodeRendererJ3D");
+  }
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    DisplayImpl display =
+      new DisplayImplJ3D("display", new NodeDisplayRendererJ3D(),
+                         DisplayImplJ3D.TRANSFORM_ONLY);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test NodeRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    // panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/cluster/RemoteAgentContact.java b/visad/cluster/RemoteAgentContact.java
new file mode 100644
index 0000000..e0abc99
--- /dev/null
+++ b/visad/cluster/RemoteAgentContact.java
@@ -0,0 +1,41 @@
+//
+// RemoteAgentContact.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import java.rmi.*;
+import java.io.Serializable;
+
+/**
+   RemoteAgentContact is the interface for RemoteClientAgent
+   to communicate to NodeAgent.<P>
+*/
+public interface RemoteAgentContact extends Remote {
+
+  void sendToNode(Serializable message) throws RemoteException;
+
+}
+
diff --git a/visad/cluster/RemoteAgentContactImpl.java b/visad/cluster/RemoteAgentContactImpl.java
new file mode 100644
index 0000000..588ca1c
--- /dev/null
+++ b/visad/cluster/RemoteAgentContactImpl.java
@@ -0,0 +1,51 @@
+//
+// RemoteAgentContactImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import java.rmi.*;
+import java.rmi.server.UnicastRemoteObject;
+import java.io.Serializable;
+
+/**
+   RemoteAgentContactImpl is the class on nodes for
+   RemoteClientAgent to communicate to NodeAgent.<P> 
+*/
+public class RemoteAgentContactImpl extends UnicastRemoteObject
+       implements RemoteAgentContact {
+
+  NodeAgent agent;
+
+  public RemoteAgentContactImpl(NodeAgent ag) throws RemoteException {
+    agent = ag;
+  }
+
+  public void sendToNode(Serializable message) throws RemoteException {
+    agent.sendToNode(message);
+  }
+
+}
+
diff --git a/visad/cluster/RemoteClientAgent.java b/visad/cluster/RemoteClientAgent.java
new file mode 100644
index 0000000..4f93cb8
--- /dev/null
+++ b/visad/cluster/RemoteClientAgent.java
@@ -0,0 +1,41 @@
+//
+// RemoteClientAgent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import java.rmi.*;
+import java.io.Serializable;
+
+/**
+   RemoteClientAgent is the interface for agents on the client,
+   which typically send NodeAgents to each node.<P>
+*/
+public interface RemoteClientAgent extends Remote {
+
+  void sendToClient(Serializable message) throws RemoteException;
+
+}
+
diff --git a/visad/cluster/RemoteClientAgentImpl.java b/visad/cluster/RemoteClientAgentImpl.java
new file mode 100644
index 0000000..5b1e547
--- /dev/null
+++ b/visad/cluster/RemoteClientAgentImpl.java
@@ -0,0 +1,136 @@
+//
+// RemoteClientAgentImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import java.rmi.*;
+import java.rmi.server.UnicastRemoteObject;
+import java.io.Serializable;
+
+/**
+   RemoteClientAgentImpl is the class for agents on the
+   client, which typically send NodeAgents to each node.<P> 
+*/
+public class RemoteClientAgentImpl extends UnicastRemoteObject
+       implements RemoteClientAgent {
+
+  // null indicates this is the focus_agent
+  private RemoteClientAgentImpl focus_agent;
+
+  private int index = -1;
+
+  private boolean not_all;
+  Serializable[] responses = null;
+
+  private long time_out = 10000;
+
+  public RemoteClientAgentImpl(RemoteClientAgentImpl fa, int ind)
+         throws RemoteException {
+    this(fa, ind, 10000);
+  }
+
+  public RemoteClientAgentImpl(RemoteClientAgentImpl fa, int ind, long to)
+         throws RemoteException {
+    focus_agent = fa;
+    index = ind;
+    time_out = to;
+  }
+
+  public void sendToClient(Serializable message) throws RemoteException {
+    if (focus_agent != null) {
+      focus_agent.sendToClient(index, message);
+    }
+  }
+
+  // should be called only for focus_agent
+  public void sendToClient(int ind, Serializable message)
+         throws RemoteException {
+// System.out.println("RemoteClientAgentImpl.sendToClient " + ind + " " + message);
+    if (0 <= ind && ind < responses.length) {
+      responses[ind] = message;
+      boolean all = true;
+      for (int i=0; i<responses.length; i++) {
+        if (responses[i] == null) all = false;
+      }
+      if (all) {
+        synchronized (this) {
+          not_all = false;
+          notify();
+        }
+      }
+    }
+  }
+
+  public Serializable[] broadcastWithResponses(Serializable message,
+                                               RemoteAgentContact[] contacts)
+         throws VisADException, RemoteException {
+    return broadcastWithResponses(new Serializable[] {message}, contacts);
+  }
+
+  public Serializable[] broadcastWithResponses(Serializable[] messages,
+                                               RemoteAgentContact[] contacts)
+         throws VisADException, RemoteException {
+    int nagents = contacts.length;
+    responses = new Serializable[nagents];
+    not_all = true;
+    for (int i=0; i<nagents; i++) {
+      int im = (messages.length == 1) ? 0 : i;
+        Serializable message = messages[im];
+// System.out.println("RemoteClientAgentImpl.broadcastWithResponses " +
+//                    i + " " + message);
+      responses[i] = null;
+      contacts[i].sendToNode(message);
+    }
+
+    long start_time = System.currentTimeMillis();
+    while (not_all) {
+      synchronized (this) {
+        try {
+          wait(time_out); // wait for at most time_out ms
+        }
+        catch (InterruptedException e) {
+        }
+        long time = System.currentTimeMillis();
+        if (time > start_time + time_out) {
+          not_all = false;
+System.out.println("RemoteClientAgentImpl.broadcastWithResponses time out");
+        }
+      }
+    }
+    for (int i=0; i<responses.length; i++) {
+      if (responses[i] instanceof String &&
+          ((String) responses[i]).equals("error")) {
+        throw new ClusterException("error from node " + i);
+      }
+    }
+// System.out.println("RemoteClientAgentImpl.broadcastWithResponses " +
+//                    "return responses");
+    return responses;
+  }
+
+}
+
diff --git a/visad/cluster/RemoteClientData.java b/visad/cluster/RemoteClientData.java
new file mode 100644
index 0000000..4b40383
--- /dev/null
+++ b/visad/cluster/RemoteClientData.java
@@ -0,0 +1,36 @@
+//
+// RemoteClientData.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+/**
+   RemoteClientData is the interface for cluster client
+   VisAD data objects.<P>
+*/
+public interface RemoteClientData extends RemoteClusterData {
+
+}
+
diff --git a/visad/cluster/RemoteClientDataImpl.java b/visad/cluster/RemoteClientDataImpl.java
new file mode 100644
index 0000000..c0c8368
--- /dev/null
+++ b/visad/cluster/RemoteClientDataImpl.java
@@ -0,0 +1,66 @@
+//
+// RemoteClientDataImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   RemoteClientData is the class for cluster client
+   VisAD data objects.<P>
+*/
+public abstract class RemoteClientDataImpl extends RemoteClusterDataImpl
+       implements RemoteClientData {
+
+  public RemoteClientDataImpl() throws RemoteException {
+  }
+
+  public Data binary(Data data, int op, MathType new_type,
+                    int sampling_mode, int error_mode )
+             throws VisADException, RemoteException {
+    throw new ClusterException("no binary method for cluster client data");
+  }
+
+  public Data binary(Data data, int op, int sampling_mode, int error_mode )
+             throws VisADException, RemoteException {
+    throw new ClusterException("no binary method for cluster client data");
+  }
+
+  public Data unary(int op, MathType new_type,
+                    int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no unary method for cluster client data");
+  }
+
+  public Data unary(int op, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no unary method for cluster client data");
+  }
+
+}
+
diff --git a/visad/cluster/RemoteClientField.java b/visad/cluster/RemoteClientField.java
new file mode 100644
index 0000000..18aafc9
--- /dev/null
+++ b/visad/cluster/RemoteClientField.java
@@ -0,0 +1,38 @@
+//
+// RemoteClientField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+/**
+   RemoteClientField is the interface for cluster client
+   VisAD Field data objects that are not partitioned over nodes.<P>
+*/
+public interface RemoteClientField extends RemoteClientData, RemoteField {
+
+}
+
diff --git a/visad/cluster/RemoteClientFieldImpl.java b/visad/cluster/RemoteClientFieldImpl.java
new file mode 100644
index 0000000..48ec2a9
--- /dev/null
+++ b/visad/cluster/RemoteClientFieldImpl.java
@@ -0,0 +1,318 @@
+//
+// RemoteClientFieldImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.util.Enumeration;
+import java.rmi.*;
+
+/**
+   RemoteClientFieldImpl is the class for cluster client
+   VisAD Field data objects.<P>
+*/
+public class RemoteClientFieldImpl extends RemoteClientDataImpl
+       implements RemoteClientField {
+
+  private Field adaptedField = null;
+  private int length;
+
+  /**
+     must call setupClusterData after constructor to finish the
+     "construction"
+  */
+  public RemoteClientFieldImpl(FunctionType type, Set set)
+         throws VisADException, RemoteException {
+    super();
+    if (type == null) {
+      throw new ClusterException("type cannot be null");
+    }
+    if (set == null) {
+      throw new ClusterException("set cannot be null");
+    }
+    adaptedField = new FieldImpl(type, set);
+    length = set.getLength();
+  }
+
+  public void setSamples(RemoteClientDataImpl[] range)
+         throws VisADException, RemoteException {
+    setSamples(range, false);
+  }
+
+  public void setSamples(RemoteClientDataImpl[] range, boolean copy)
+         throws VisADException, RemoteException {
+    if (range == null) {
+      throw new ClusterException("range cannot be null");
+    }
+    if (range.length != length) {
+      throw new ClusterException("range length must match set length");
+    }
+
+    adaptedField.setSamples(range, false); // don't copy
+    // set this as parent
+    for (int i=0; i<length; i++) {
+      range[i].setParent(this);
+    }
+  }
+
+  public void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(Data[], boolean) method");
+  }
+
+  public void setSamples(double[][] range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(double[][]) method");
+  }
+
+  public void setSamples(float[][] range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(float[][]) method");
+  }
+
+
+
+
+  public MathType getType() throws VisADException, RemoteException {
+    return adaptedField.getType();
+  }
+
+  public boolean isMissing() throws VisADException, RemoteException {
+    return adaptedField.isMissing();
+  }
+
+  public int getDomainDimension() throws VisADException, RemoteException {
+    return adaptedField.getDomainDimension();
+  }
+
+  public Set getDomainSet() throws VisADException, RemoteException {
+    return adaptedField.getDomainSet();
+  }
+
+  public int getLength() throws RemoteException {
+    return length;
+  }
+
+  public Unit[] getDomainUnits() throws VisADException, RemoteException {
+    return adaptedField.getDomainUnits();
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem()
+         throws VisADException, RemoteException {
+    return adaptedField.getDomainCoordinateSystem();
+  }
+
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    return adaptedField.getSample(index);
+  }
+
+  public void setSample(RealTuple domain, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(RealTuple domain, Data range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public Field extract(int component)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no extract() method");
+  }
+
+  public Field domainMultiply()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainMultiply() method");
+  }
+
+  public Field domainMultiply(int depth)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainMultiply() method");
+  }
+
+  public Field domainFactor( RealType factor )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainFactor() method");
+  }
+
+  public double[][] getValues()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getValues() method");
+  }
+
+  public double[][] getValues(boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getValues() method");
+  }
+
+  public float[][] getFloats()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getFloats() method");
+  }
+
+  public float[][] getFloats(boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getFloats() method");
+  }
+
+  public String[][] getStringValues()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getStringValues() method");
+  }
+
+  public Unit[] getDefaultRangeUnits()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public Unit[][] getRangeUnits()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem(int i)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public boolean isFlatField() throws VisADException, RemoteException {
+    return false;
+  }
+
+  public Enumeration domainEnumeration()
+         throws VisADException, RemoteException {
+    return adaptedField.domainEnumeration();
+  }
+
+
+
+  public Data evaluate(Real domain)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(Real domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(RealTuple domain)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(RealTuple domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Field resample(Set set) throws VisADException, RemoteException {
+    throw new ClusterException("no resample() method");
+  }
+
+  public Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no resample() method");
+  }
+
+  public Data derivative( RealTuple location, RealType[] d_partial_s,
+                          MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Data derivative( int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Function derivative( RealType d_partial, MathType derivType, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+
+
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    return adaptedField.computeRanges(type, shadow);
+  }
+
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    return adaptedField.computeRanges(type, n);
+  }
+
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no computeRanges(RealType[]) method");
+  }
+
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    return adaptedField.adjustSamplingError(error, error_mode);
+  }
+
+  public String longString() throws VisADException, RemoteException {
+    return longString("");
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    return pre + "RemoteClientFieldImpl";
+  }
+
+}
+
diff --git a/visad/cluster/RemoteClientPartitionedField.java b/visad/cluster/RemoteClientPartitionedField.java
new file mode 100644
index 0000000..6aa44bf
--- /dev/null
+++ b/visad/cluster/RemoteClientPartitionedField.java
@@ -0,0 +1,39 @@
+//
+// RemoteClientPartitionedField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+/**
+   RemoteClientPartitionedField is the interface for cluster client
+   VisAD Field data objects that are partitioned over nodes.<P>
+*/
+public interface RemoteClientPartitionedField extends
+       RemoteClientData, RemoteField {
+
+}
+
diff --git a/visad/cluster/RemoteClientPartitionedFieldImpl.java b/visad/cluster/RemoteClientPartitionedFieldImpl.java
new file mode 100644
index 0000000..15f27d4
--- /dev/null
+++ b/visad/cluster/RemoteClientPartitionedFieldImpl.java
@@ -0,0 +1,311 @@
+//
+// RemoteClientPartitionedFieldImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.util.Enumeration;
+import java.rmi.*;
+
+/**
+   RemoteClientPartitionedFieldImpl is the class for cluster client
+   VisAD Field data objects.<P>
+*/
+public class RemoteClientPartitionedFieldImpl extends RemoteClientDataImpl
+       implements RemoteClientField {
+
+  private FunctionType type = null;
+  private Set set = null;
+  private int length;
+
+  /**
+     must call setupClusterData after constructor to finish the
+     "construction"
+  */
+  public RemoteClientPartitionedFieldImpl(FunctionType t, Set s)
+         throws VisADException, RemoteException {
+    super();
+    if (t == null) {
+      throw new ClusterException("type cannot be null");
+    }
+    if (s == null) {
+      throw new ClusterException("set cannot be null");
+    }
+    type = t;
+    set = s;
+    length = set.getLength();
+  }
+
+  public void setSamples(RemoteClientDataImpl[] range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(RemoteClientDataImpl[]) method");
+  }
+
+  public void setSamples(RemoteClientDataImpl[] range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(RemoteClientDataImpl[], boolean) " +
+                               "method");
+  }
+
+  public void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(Data[], boolean) method");
+  }
+
+  public void setSamples(double[][] range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(double[][]) method");
+  }
+
+  public void setSamples(float[][] range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(float[][]) method");
+  }
+
+
+
+
+  public MathType getType() throws VisADException, RemoteException {
+    return type;
+  }
+
+  public boolean isMissing() throws VisADException, RemoteException {
+    return false; // ????
+  }
+
+  public int getDomainDimension() throws VisADException, RemoteException {
+    return type.getDomain().getDimension();
+  }
+
+  public Set getDomainSet() throws VisADException, RemoteException {
+    return set;
+  }
+
+  public int getLength() throws RemoteException {
+    return length;
+  }
+
+  public Unit[] getDomainUnits() throws VisADException, RemoteException {
+    return type.getDomain().getDefaultUnits();
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem()
+         throws VisADException, RemoteException {
+    return type.getDomain().getCoordinateSystem();
+  }
+
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getSample() method");
+  }
+
+  public void setSample(RealTuple domain, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(RealTuple domain, Data range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public Field extract(int component)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no extract() method");
+  }
+
+  public Field domainMultiply()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainMultiply() method");
+  }
+
+  public Field domainMultiply(int depth)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainMultiply() method");
+  }
+
+  public Field domainFactor( RealType factor )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainFactor() method");
+  }
+
+  public double[][] getValues()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getValues() method");
+  }
+
+  public double[][] getValues(boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getValues() method");
+  }
+
+  public float[][] getFloats()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getFloats() method");
+  }
+
+  public float[][] getFloats(boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getFloats() method");
+  }
+
+  public String[][] getStringValues()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getStringValues() method");
+  }
+
+  public Unit[] getDefaultRangeUnits()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public Unit[][] getRangeUnits()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem(int i)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no getRangeCoordinateSystem() method");
+  }
+
+  public boolean isFlatField() throws VisADException, RemoteException {
+    return false;
+  }
+
+  public Enumeration domainEnumeration()
+         throws VisADException, RemoteException {
+    // return new FieldEnumerator(this);
+    throw new ClusterException("no domainEnumeration method");
+  }
+
+
+
+  public Data evaluate(Real domain)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(Real domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(RealTuple domain)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(RealTuple domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Field resample(Set set) throws VisADException, RemoteException {
+    throw new ClusterException("no resample() method");
+  }
+
+  public Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no resample() method");
+  }
+
+  public Data derivative( RealTuple location, RealType[] d_partial_s,
+                          MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Data derivative( int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Function derivative( RealType d_partial, MathType derivType, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+
+
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no computeRanges() method");
+  }
+
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no computeRanges() method");
+  }
+
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no computeRanges(RealType[]) method");
+  }
+
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no adjustSamplingError() method");
+  }
+
+  public String longString() throws VisADException, RemoteException {
+    return longString("");
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    return pre + "RemoteClientPartitionedFieldImpl";
+  }
+
+}
+
diff --git a/visad/cluster/RemoteClientTuple.java b/visad/cluster/RemoteClientTuple.java
new file mode 100644
index 0000000..b56bc5d
--- /dev/null
+++ b/visad/cluster/RemoteClientTuple.java
@@ -0,0 +1,38 @@
+//
+// RemoteClientTuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+/**
+   RemoteClientTuple is the interface for cluster client
+   VisAD Tuple data objects.<P>
+*/
+public interface RemoteClientTuple extends RemoteClientData, RemoteTupleIface {
+
+}
+
diff --git a/visad/cluster/RemoteClientTupleImpl.java b/visad/cluster/RemoteClientTupleImpl.java
new file mode 100644
index 0000000..bb92efc
--- /dev/null
+++ b/visad/cluster/RemoteClientTupleImpl.java
@@ -0,0 +1,149 @@
+//
+// RemoteClientTupleImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   RemoteClientTupleImpl is the class for cluster client
+   VisAD Tuple data objects.<P>
+*/
+public class RemoteClientTupleImpl extends RemoteClientDataImpl
+       implements RemoteClientTuple {
+
+  private Tuple adaptedTuple = null;
+  private DataReferenceImpl adaptedTupleRef = null;
+  private boolean allReal = false;
+
+  /**
+     must call setupClusterData after constructor to finish the
+     "construction"
+  */
+  public RemoteClientTupleImpl(Data[] datums)
+         throws VisADException, RemoteException {
+    super();
+    if (datums == null) {
+      throw new ClusterException("datums cannot be null");
+    }
+    int n = datums.length;
+    if (n == 0) {
+      throw new ClusterException("datums.length must be > 0");
+    }
+    allReal = true;
+    for (int i=0; i<n; i++) {
+      if (!(datums[i] instanceof DataImpl ||
+            datums[i] instanceof RemoteClientDataImpl)) {
+        throw new ClusterException("datums must be DataImpl " +
+                                   "or RemoteClientDataImpl");
+      }
+      if (!(datums[i] instanceof Real)) allReal = false;
+    }
+    if (allReal) {
+      Real[] reals = new Real[n];
+      for (int i=0; i<n; i++) reals[i] = (Real) datums[i];
+      adaptedTuple = new RealTuple(reals);
+    }
+    else {
+      adaptedTuple = new Tuple(datums, false); // no copy
+    }
+    // set this as parent for RemoteClientDataImpls
+    boolean any_local = false;
+    for (int i=0; i<n; i++) {
+      if (datums[i] instanceof RemoteClientDataImpl) {
+        ((RemoteClientDataImpl) datums[i]).setParent(this);
+      }
+      else {
+        any_local = true;
+      }
+    }
+    if (any_local) {
+      // hack parent notify logic for non-RemoteClientDataImpl components
+      adaptedTupleRef = new DataReferenceImpl("adaptedTupleRef");
+      adaptedTupleRef.setData(adaptedTuple);
+      CellImpl adaptedTupleCell = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+          notifyReferences();
+        }
+      };
+      adaptedTupleCell.addReference(adaptedTupleRef);
+    }
+  }
+
+  public MathType getType() throws VisADException, RemoteException {
+    return adaptedTuple.getType();
+  }
+
+  public Real[] getRealComponents()
+         throws VisADException, RemoteException {
+    return adaptedTuple.getRealComponents();
+  }
+
+  public int getDimension() throws RemoteException {
+    return adaptedTuple.getDimension();
+  }
+
+  public Data getComponent(int i) throws VisADException, RemoteException {
+    return adaptedTuple.getComponent(i);
+  }
+
+  public boolean isMissing() throws RemoteException {
+    return adaptedTuple.isMissing();
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    return adaptedTuple.computeRanges(type, shadow);
+  }
+
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    return adaptedTuple.computeRanges(type, n);
+  }
+
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    return adaptedTuple.computeRanges(reals);
+  }
+
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    return adaptedTuple.adjustSamplingError(error, error_mode);
+  }
+
+  public String longString() throws VisADException, RemoteException {
+    return longString("");
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    return pre + "RemoteClientTupleImpl";
+  }
+
+}
+
diff --git a/visad/cluster/RemoteClusterData.java b/visad/cluster/RemoteClusterData.java
new file mode 100644
index 0000000..4c52557
--- /dev/null
+++ b/visad/cluster/RemoteClusterData.java
@@ -0,0 +1,48 @@
+//
+// RemoteClusterData.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import java.rmi.*;
+
+/**
+   RemoteClusterData is the interface for cluster client
+   and node Data.<P>
+*/
+public interface RemoteClusterData extends RemoteData {
+
+  RemoteClusterData getClusterData(RealTuple domain)
+         throws RemoteException, VisADException;
+
+  void setupClusterData(Set ps, RemoteClusterData[] table)
+         throws RemoteException, VisADException;
+
+  boolean clusterDataEquals(RemoteClusterData cd)
+         throws RemoteException, VisADException;
+
+}
+
diff --git a/visad/cluster/RemoteClusterDataImpl.java b/visad/cluster/RemoteClusterDataImpl.java
new file mode 100644
index 0000000..3c24fa1
--- /dev/null
+++ b/visad/cluster/RemoteClusterDataImpl.java
@@ -0,0 +1,398 @@
+//
+// RemoteClusterDataImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/* Cluster Design Ideas
+
+Everything is via RMI - no 'local' Impl
+
+a Data object is partitioned if any Field in it has a
+partitioned domain
+
+interfaces:
+  Thing
+    Data
+      RemoteData (extends Remote, Data, RemoteThing)
+        RemoteClusterData
+          RemoteClientData
+            RemoteClientTuple (extends RemoteTupleIface)
+            RemoteClientField (extends RemoteField)
+            RemoteClientPartitionedField (extends RemoteField)
+          RemoteNodeData
+            RemoteNodeTuple (extends RemoteTupleIface)
+            RemoteNodeField (extends RemoteField)
+            RemoteNodePartitionedField (extends RemoteField)
+
+classes:
+  UnicastRemoteObject
+    RemoteThingImpl
+      RemoteDataImpl
+        RemoteClusterDataImpl
+          RemoteClientDataImpl
+            RemoteClientTupleImpl
+            RemoteClientFieldImpl
+            RemoteClientPartitionedFieldImpl
+          RemoteNodeDataImpl
+            RemoteNodeTupleImpl
+            RemoteNodeFieldImpl
+            RemoteNodePartitionedFieldImpl
+
+
+RemoteClientPartitionedFieldImpl.getDomainSet() return UnionSet
+of getDomainSet() returns from each node
+
+add TupleIface extends Data (Tuple implements TupleIface)
+and RemoteTupleIface extends TupleIface
+
+a non-partitioned Data object is local on the client
+  that is, a DataImpl
+
+a partitioned Data object is a RemoteClientDataImpl on the
+cient connected to RemodeNodeDataImpl's on the nodes
+
+NodeAgent, Serializable class sent from client to each node
+gets a Thread on arrival at node, return value from send of
+NodeAgent is RemoteAgentContact (and Impl)
+values from NodeAgent back declared Serializable
+
+  abstract class NodeAgent implements Serializable, Runnable
+    void sendToClient(Serializable message)
+      invokes RemoteClientAgent.sendToClient(message)
+    RemoteAgentContactImpl getRemoteAgentContact()
+  interface RemoteAgentContact extends Remote
+  class RemoteAgentContactImpl implements RemoteAgentContact
+  interface RemoteClientAgent extends Remote
+    void sendToClient(Serializable message)
+  abstract class RemoteClientAgentImpl implements RemoteClientAgent
+  class DefaultNodeRendererAgent extends NodeAgent
+    void run()
+
+  interface RemoteNodeData
+    RemoteAgentContact sendAgent(NodeAgent agent)
+
+  NodeRendererJ3D(NodeAgent agent)
+  NodeRendererJ3D.doTransform()
+    invokes agent.sendToClient(VisADGroup branch)
+
+
+see page 60 of Java Enterprise in a Nutshell
+no easy way to load RMI classes - security issues
+
+
+partitioned data on client has similar data trees on
+client and nodes
+  leaf node on client is:
+    DataImpl
+    Field with partitioned domain (RemoteClientPartitionedFieldImpl)
+  non-leaf node on client is:
+    Tuple (RemoteClientTupleImpl)
+    Field with non-partitioned domain (RemoteClientFieldImpl)
+  leaf tree-nodes on cluster-node is:
+    Field with partitioned domain
+      (RemoteNodePartitionedFieldImpl adapting FlatField)
+  non-leaf tree-nodes on cluster-node is:
+    Tuple (RemoteNodeTupleImpl)
+    Field with non-partitioned domain (RemoteNodeFieldImpl)
+    Field with partitioned domain
+      (RemoteNodePartitionedFieldImpl adapting FieldImpl)
+
+every object in data tree on client connects to objects
+in data trees on nodes
+
+may use DisplayImplJ3D on nodes for graphics, with api = TRANSFORM_ONLY
+  and DisplayRenderer = NodeDisplayRendererJ3D (extends
+  TransformOnlyDisplayRendererJ3D) doesn't render to screen
+uses special DisplayImplJ3D constructor signature (conflict?)
+  for cluster - modified version of collaborative Display
+
+NodeRendererJ3D extends DefaultRendererJ3D, with
+ShadowNode*TypeJ3D - addToGroup() etc to leave as Serializable
+  note must replace 'Image image' in VisADAppearance
+ClientRendererJ3D extends DefaultRendererJ3D, not even using
+ShadowTypes, but assembling VisADSceneGraphs from nodes
+
+
+
+
+may also need way for client to signal implicit resolution
+reduction to nodes - custom DataRenderers with custon ShadowTypes
+whose doTransforms resample down, then call super.doTransform()
+with downsampled data
+
+
+Control field in ScalarMap is marked transient and dglo9.txt
+says it should be.  But can use the getSaveString() and
+setSaveString() methods of Control to transmit Control states.
+
+
+cluster design should include a native VisAD Data Model on
+binary files, via serialization, for an implementation of
+FileFlatField on nodes
+
+also need to support FileFlatFields
+
+*/
+
+/*
+  DisplayImpl.syncRemoteData()
+    . . .
+    if (!cluster) waitForTasks(); // WLH 11 April 2001
+
+only needed for testing client and nodes in same JVM
+BUT, dglo should make this waitForTasks() more precise
+*/
+/*
+possible deadlock in ThreadPool, if all running ActionImpls
+are waiting for other ActionImpls to run
+*/
+
+/* VisAD Data Model on various file formats
+
+Data instance method for write
+Data static method for read
+a parameter to these methods is a file-format-specific
+implementation of a FileIO interface, that is used for
+low level I/O (should deal with missing data in
+file-format-specific way)
+
+other interfaces for constructing appropriate file-format-
+specific structures for Tuple, Field, FlatField, Set, Real,
+Text, RealTuple, CoordinateSystem, Unit, ErrorEstimate
+
+get review from Steve on this
+
+*/
+
+
+package visad.cluster;
+
+import visad.*;
+import java.rmi.*;
+
+/**
+   RemoteClusterDataImpl is the super class for cluster
+   client and node Data.<P>
+*/
+public abstract class RemoteClusterDataImpl extends RemoteDataImpl
+       implements RemoteClusterData {
+
+  /** Set that defines partition of Data across cluster;
+      values in partitionSet's domain RealTupleType are
+      partitioned according to:
+         jvmTable[partitionSet.valueToIndex()] */
+  private Set partitionSet = null;
+
+  /** domain dimension of partitionSet */
+  private int dimension = -1;
+
+  /** lookup table for RemoteClusterData objects on nodes, last
+      entry is on client (for non-distributed data) */
+  private RemoteClusterData[] jvmTable = null;
+
+  /** used for testing equality */
+  private RemoteClusterData me = null;
+
+  public RemoteClusterDataImpl() throws RemoteException {
+    super(null); // RemoteDataImpl.AdaptedData =
+                 // RemoteThingImpl.AdaptedThing = null
+    // but adapt a ThingImpl and a RemoteThingImpl
+    // used for over-riding RemoteThingImpl methods
+    adaptedThingImpl = new ThingImpl();
+    adaptedRemoteThingImpl = new RemoteThingImpl(adaptedThingImpl);
+    me = this;
+  }
+
+  RemoteClusterData[] getTable() {
+    return jvmTable;
+  }
+
+  /** return RemoteClusterData for JVM where data resides;
+      may be RemoteClusterData for client for non-partitioned data;
+      may be null for partitioned data outside partitoning */
+  public RemoteClusterData getClusterData(RealTuple domain)
+         throws RemoteException, VisADException {
+    if (domain == null || partitionSet == null || jvmTable == null) {
+      throw new ClusterException("null domain or setup not done");
+    }
+    if (dimension != domain.getDimension()) {
+      // return client (last entry) for non-partitoned data
+      return jvmTable[jvmTable.length - 1];
+    }
+
+    // "eval" partitionSet at domain
+    // first extract values from domain
+    double[][] vals = new double[dimension][1];
+    for (int i=0; i<dimension; i++) {
+      vals[i][0] = ((Real) domain.getComponent(i)).getValue();
+    }
+    // test whether domain and partitionSet CoordinateSystems match
+    RealTupleType out = ((SetType) partitionSet.getType()).getDomain();
+    CoordinateSystem coord_out = partitionSet.getCoordinateSystem();
+    RealTupleType in = (RealTupleType) domain.getType();
+    CoordinateSystem coord_in = domain.getCoordinateSystem();
+    if (!CoordinateSystem.canConvert(out, coord_out, in, coord_in)) {
+      // return client (last entry) for non-partitoned data
+      return jvmTable[jvmTable.length - 1];
+    }
+
+    // if only one, then just return it
+    if (partitionSet.getLength() == 1) {
+      return jvmTable[0];
+    }
+
+    // transform coordinates and convert units
+    vals = CoordinateSystem.transformCoordinates(
+                     ((SetType) partitionSet.getType()).getDomain(),
+                     partitionSet.getCoordinateSystem(),
+                     partitionSet.getSetUnits(), null,
+                     (RealTupleType) domain.getType(),
+                     domain.getCoordinateSystem(),
+                     domain.getTupleUnits(), null, vals);
+    try {
+      // convert transformed values to a partitionSet index
+      int[] indices = partitionSet.doubleToIndex(vals);
+      // return jvmTable entry
+      return (indices[0] < 0) ? null : jvmTable[indices[0]];
+    }
+    catch (SetException e) {
+      return null;
+    }
+  }
+
+  public void setupClusterData(Set ps, RemoteClusterData[] table)
+         throws RemoteException, VisADException {
+/* WLH 4 Sept 2001
+    if (ps == null || table == null) {
+      throw new ClusterException("ps and table must be non-null");
+    }
+*/
+    if (table == null) {
+      throw new ClusterException("table must be non-null");
+    }
+    if (ps != null) {
+      if ((ps.getLength() + 1) > table.length) {
+        throw new ClusterException("table.length (" + table.length +") must " +
+                                   " >= ps.length + 1 (" + (ps.getLength() + 1) +
+                                   ")");
+      }
+      partitionSet = ps;
+      dimension = ps.getDimension();
+    }
+    else {
+      partitionSet = null;
+      dimension = -1;
+    }
+
+    jvmTable = table;
+  }
+
+  public Set getPartitionSet() {
+    return partitionSet;
+  }
+
+  public boolean clusterDataEquals(RemoteClusterData cd)
+         throws RemoteException {
+    return (cd == me); // seems to work - but does it really?
+  }
+
+  /** parent logic, looosely copied from DataImpl */
+  private RemoteClusterDataImpl parent = null;
+  public void setParent(RemoteClusterDataImpl p) {
+    parent = p;
+  }
+  public void notifyReferences()
+         throws VisADException, RemoteException {
+    adaptedThingImpl.notifyReferences();
+    // recursively propogate data change to parent
+    if (parent != null) parent.notifyReferences();
+  }
+
+  /** over-ride methods of RemoteThingImpl, but note these
+      are for notifyReferences(), which currently does nothing
+      for RemoteThingImpl */
+  private ThingImpl adaptedThingImpl = null;
+  // adaptedRemoteThingImpl constructed from adaptedThingImpl
+  private RemoteThingImpl adaptedRemoteThingImpl = null;
+  public void addReference(ThingReference r) throws VisADException {
+    adaptedRemoteThingImpl.addReference(r);
+  }
+  public void removeReference(ThingReference r) throws VisADException {
+    adaptedRemoteThingImpl.removeReference(r);
+  }
+
+  public DataImpl local() throws VisADException, RemoteException {
+    throw new ClusterException("no local() method for cluster data");
+  }
+
+
+/* MUST OVER-RIDE
+methods that acccess AdaptedThing and AdaptedData
+
+from RemoteThingImpl:
+  public void addReference(ThingReference r) throws VisADException;
+  public void removeReference(ThingReference r) throws VisADException;
+
+from RemoteDataImpl:
+  public DataImpl local() throws VisADException, RemoteException;
+  public MathType getType() throws VisADException, RemoteException;
+  public boolean isMissing() throws VisADException, RemoteException;
+  public Data binary(Data data, int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException;
+  public Data binary(Data data, int op, MathType new_type,
+                     int sampling_mode, int error_mode )
+              throws VisADException, RemoteException;
+  public Data unary(int op, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException;
+  public Data unary(int op, MathType new_type,
+                    int sampling_mode, int error_mode)
+              throws VisADException, RemoteException;
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException;
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException;
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException;
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException;
+  public String longString() throws VisADException, RemoteException;
+  public String longString(String pre)
+         throws VisADException, RemoteException;
+
+END MUST OVER-RIDE */
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException {
+    Real r = new Real(0);
+    RemoteClientTupleImpl cd = new RemoteClientTupleImpl(new Data[] {r});
+    RemoteClientTupleImpl cd2 = new RemoteClientTupleImpl(new Data[] {r});
+    System.out.println(cd.equals(cd)); // true
+    System.out.println(cd.equals(cd2)); // false
+    System.out.println(cd.clusterDataEquals(cd)); // true
+    System.out.println(cd.clusterDataEquals(cd2)); // false
+    System.exit(0);
+  }
+
+}
+
diff --git a/visad/cluster/RemoteNodeData.java b/visad/cluster/RemoteNodeData.java
new file mode 100644
index 0000000..9991b02
--- /dev/null
+++ b/visad/cluster/RemoteNodeData.java
@@ -0,0 +1,41 @@
+//
+// RemoteNodeData.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import java.rmi.*;
+
+/**
+   RemoteNodeData is the interface for cluster node
+   VisAD data objects.<P>
+*/
+public interface RemoteNodeData extends RemoteClusterData {
+
+  RemoteAgentContact sendAgent(NodeAgent agent)
+         throws RemoteException;
+
+}
+
diff --git a/visad/cluster/RemoteNodeDataImpl.java b/visad/cluster/RemoteNodeDataImpl.java
new file mode 100644
index 0000000..2864825
--- /dev/null
+++ b/visad/cluster/RemoteNodeDataImpl.java
@@ -0,0 +1,64 @@
+//
+// RemoteNodeDataImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   RemoteNodeData is the class for cluster node
+   VisAD data objects.<P>
+*/
+public class RemoteNodeDataImpl extends RemoteClusterDataImpl
+       implements RemoteNodeData {
+
+  Vector agents = new Vector();
+
+  public RemoteNodeDataImpl() throws RemoteException {
+  }
+
+  public RemoteAgentContact sendAgent(NodeAgent agent)
+         throws RemoteException {
+    synchronized (agents) {
+      if (agent.onlyOne()) {
+        Class agent_class = agent.getClass();
+        int nagents = agents.size();
+        for (int i=nagents-1; i>=0; i--) {
+          NodeAgent ag = (NodeAgent) agents.elementAt(i);
+          if (agent_class.equals(ag.getClass())) {
+            agents.removeElementAt(i);
+            ag.stop();
+          }
+        }
+      }
+      agents.addElement(agent);
+    }
+    return agent.getRemoteAgentContact(this);
+  }
+
+}
+
diff --git a/visad/cluster/RemoteNodeField.java b/visad/cluster/RemoteNodeField.java
new file mode 100644
index 0000000..fda2d98
--- /dev/null
+++ b/visad/cluster/RemoteNodeField.java
@@ -0,0 +1,38 @@
+//
+// RemoteNodeField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+/**
+   RemoteNodeField is the interface for cluster node
+   VisAD Field data objects that are not paritioned.<P>
+*/
+public interface RemoteNodeField extends RemoteNodeData, RemoteField {
+
+}
+
diff --git a/visad/cluster/RemoteNodeFieldImpl.java b/visad/cluster/RemoteNodeFieldImpl.java
new file mode 100644
index 0000000..3aa578d
--- /dev/null
+++ b/visad/cluster/RemoteNodeFieldImpl.java
@@ -0,0 +1,332 @@
+//
+// RemoteNodeFieldImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.util.Enumeration;
+import java.rmi.*;
+
+/**
+   RemoteNodeFieldImpl is the class for cluster node
+   VisAD Field data objects.<P>
+*/
+public class RemoteNodeFieldImpl extends RemoteNodeDataImpl
+       implements RemoteNodeField {
+
+  private Field adaptedField = null;
+  private int length;
+
+  /**
+     must call setupClusterData after constructor to finish the
+     "construction"
+  */
+  public RemoteNodeFieldImpl(FunctionType type, Set set)
+         throws VisADException, RemoteException {
+    super();
+    if (type == null) {
+      throw new ClusterException("type cannot be null");
+    }
+    if (set == null) {
+      throw new ClusterException("set cannot be null");
+    }
+    adaptedField = new FieldImpl(type, set);
+    length = set.getLength();
+  }
+
+  // WLH 4 Sept 2001
+  /**
+     constructor for rendering without using partitionSet
+  */
+  public RemoteNodeFieldImpl(FieldImpl field)
+         throws VisADException, RemoteException {
+    super();
+    if (field == null) {
+      throw new ClusterException("field cannot be null");
+    }
+    adaptedField = field;
+  }
+
+  public void setSamples(RemoteNodeDataImpl[] range)
+         throws VisADException, RemoteException {
+    setSamples(range, false);
+  }
+
+  // public void setSamples(RemoteNodeDataImpl[] range, boolean copy)
+  public void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException {
+    if (range == null) {
+      throw new ClusterException("range cannot be null");
+    }
+    if (range.length != length) {
+      throw new ClusterException("range length must match set length");
+    }
+    for (int i=0; i<range.length; i++) {
+      if (!(range[i] instanceof RemoteNodeDataImpl)) {
+        throw new ClusterException("range values must be RemoteNodeDataImpl");
+      }
+    }
+
+    adaptedField.setSamples(range, false); // don't copy
+    // set this as parent
+    for (int i=0; i<length; i++) {
+      ((RemoteNodeDataImpl) range[i]).setParent(this);
+    }
+  }
+
+  public void setSamples(double[][] range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(double[][]) method");
+  }
+
+  public void setSamples(float[][] range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSamples(float[][]) method");
+  }
+
+
+
+
+  public MathType getType() throws VisADException, RemoteException {
+    return adaptedField.getType();
+  }
+
+  public boolean isMissing() throws VisADException, RemoteException {
+    return adaptedField.isMissing();
+  }
+
+  public int getDomainDimension() throws VisADException, RemoteException {
+    return adaptedField.getDomainDimension();
+  }
+
+  public Set getDomainSet() throws VisADException, RemoteException {
+    return adaptedField.getDomainSet();
+  }
+
+  public int getLength() throws RemoteException {
+    return length;
+  }
+
+  public Unit[] getDomainUnits() throws VisADException, RemoteException {
+    return adaptedField.getDomainUnits();
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem()
+         throws VisADException, RemoteException {
+    return adaptedField.getDomainCoordinateSystem();
+  }
+
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    return adaptedField.getSample(index);
+  }
+
+  public void setSample(RealTuple domain, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(RealTuple domain, Data range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no setSample() method");
+  }
+
+  public Field extract(int component)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no extract() method");
+  }
+
+  public Field domainMultiply()
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainMultiply() method");
+  }
+
+  public Field domainMultiply(int depth)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainMultiply() method");
+  }
+
+  public Field domainFactor( RealType factor )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no domainFactor() method");
+  }
+
+  public double[][] getValues()
+         throws VisADException, RemoteException {
+    return adaptedField.getValues();
+  }
+
+  public double[][] getValues(boolean copy)
+         throws VisADException, RemoteException {
+    return adaptedField.getValues(copy);
+  }
+
+  public float[][] getFloats()
+         throws VisADException, RemoteException {
+    return adaptedField.getFloats();
+  }
+
+  public float[][] getFloats(boolean copy)
+         throws VisADException, RemoteException {
+    return adaptedField.getFloats(copy);
+  }
+
+  public String[][] getStringValues()
+         throws VisADException, RemoteException {
+    return adaptedField.getStringValues();
+  }
+
+  public Unit[] getDefaultRangeUnits()
+         throws VisADException, RemoteException {
+    return adaptedField.getDefaultRangeUnits();
+  }
+
+  public Unit[][] getRangeUnits()
+         throws VisADException, RemoteException {
+    return adaptedField.getRangeUnits();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws VisADException, RemoteException {
+    return adaptedField.getRangeCoordinateSystem();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem(int i)
+         throws VisADException, RemoteException {
+    return adaptedField.getRangeCoordinateSystem(i);
+  }
+
+  public boolean isFlatField() throws VisADException, RemoteException {
+    return false;
+  }
+
+  public Enumeration domainEnumeration()
+         throws VisADException, RemoteException {
+    return adaptedField.domainEnumeration();
+  }
+
+
+
+  public Data evaluate(Real domain)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(Real domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(RealTuple domain)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Data evaluate(RealTuple domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    throw new ClusterException("no evaluate() method");
+  }
+
+  public Field resample(Set set) throws VisADException, RemoteException {
+    throw new ClusterException("no resample() method");
+  }
+
+  public Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no resample() method");
+  }
+
+  public Data derivative( RealTuple location, RealType[] d_partial_s,
+                          MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Data derivative( int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+  public Function derivative( RealType d_partial, MathType derivType, int error_mode )
+         throws VisADException, RemoteException {
+    throw new ClusterException("no derivative() method");
+  }
+
+
+
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    return adaptedField.computeRanges(type, shadow);
+  }
+
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    return adaptedField.computeRanges(type, n);
+  }
+
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no computeRanges(RealType[]) method");
+  }
+
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    return adaptedField.adjustSamplingError(error, error_mode);
+  }
+
+  public String longString() throws VisADException, RemoteException {
+    return longString("");
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    return pre + "RemoteNodeFieldImpl";
+  }
+
+}
+
diff --git a/visad/cluster/RemoteNodePartitionedField.java b/visad/cluster/RemoteNodePartitionedField.java
new file mode 100644
index 0000000..e53138e
--- /dev/null
+++ b/visad/cluster/RemoteNodePartitionedField.java
@@ -0,0 +1,38 @@
+//
+// RemoteNodePartitionedField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+/**
+   RemoteNodePartitionedField is the interface for cluster node
+   VisAD Field data objects that are paritioned.<P>
+*/
+public interface RemoteNodePartitionedField extends RemoteNodeData, RemoteField {
+
+}
+
diff --git a/visad/cluster/RemoteNodePartitionedFieldImpl.java b/visad/cluster/RemoteNodePartitionedFieldImpl.java
new file mode 100644
index 0000000..dc52b8c
--- /dev/null
+++ b/visad/cluster/RemoteNodePartitionedFieldImpl.java
@@ -0,0 +1,339 @@
+//
+// RemoteNodePartitionedFieldImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.util.Enumeration;
+import java.rmi.*;
+
+/**
+   RemoteNodePartitionedFieldImpl is the class for cluster node
+   VisAD Field data objects that are paritioned.<P>
+*/
+public class RemoteNodePartitionedFieldImpl extends RemoteNodeDataImpl
+       implements RemoteNodePartitionedField {
+
+  private boolean flat; // true if adaptedField is a FlatField
+  private FieldImpl adaptedField = null; // can be FileFlatField?
+  private DataReferenceImpl adaptedFieldRef = null;
+  private int length;
+
+  /**
+     must call setupClusterData after constructor to finish the
+     "construction"
+  */
+  public RemoteNodePartitionedFieldImpl(FunctionType type, Set set)
+         throws VisADException, RemoteException {
+    this(makeField(type, set));
+  }
+
+  /**
+     must call setupClusterData after constructor to finish the
+     "construction"
+  */
+  public RemoteNodePartitionedFieldImpl(FieldImpl adapted)
+         throws VisADException, RemoteException {
+    super();
+    adaptedField = adapted;
+    flat = ((FunctionType) adaptedField.getType()).getFlat();
+
+    // hack parent notify logic for non-RemoteNodeDataImpl range values
+    adaptedFieldRef = new DataReferenceImpl("adaptedFieldRef");
+    adaptedFieldRef.setData(adaptedField);
+    CellImpl adaptedFieldCell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        notifyReferences();
+      }
+    };
+    adaptedFieldCell.addReference(adaptedFieldRef);
+
+    length = adaptedField.getLength();
+  }
+
+  private static FieldImpl makeField(FunctionType type, Set set)
+          throws VisADException, RemoteException {
+    if (type == null) {
+      throw new ClusterException("type cannot be null");
+    }
+    if (set == null) {
+      throw new ClusterException("set cannot be null");
+    }
+    if (type.getFlat()) {
+      return new FlatField(type, set);
+    }
+    else {
+      return new FieldImpl(type, set);
+    }
+  }
+
+  public FieldImpl getAdaptedField() {
+    return adaptedField;
+  }
+
+/* only DataImpl under RemoteNodePartitionedFieldImpl
+   so no setSamples(RemoteNodeDataImpl[] range) methods
+*/
+
+  public void setSamples(Data[] range, boolean copy)
+         throws VisADException, RemoteException {
+    adaptedField.setSamples(range, copy);
+  }
+
+  public void setSamples(double[][] range)
+         throws VisADException, RemoteException {
+    adaptedField.setSamples(range);
+  }
+
+  public void setSamples(float[][] range)
+         throws VisADException, RemoteException {
+    adaptedField.setSamples(range);
+  }
+
+
+
+
+  public MathType getType() throws VisADException, RemoteException {
+    return adaptedField.getType();
+  }
+
+  public boolean isMissing() throws VisADException, RemoteException {
+    return adaptedField.isMissing();
+  }
+
+  public int getDomainDimension() throws VisADException, RemoteException {
+    return adaptedField.getDomainDimension();
+  }
+
+  public Set getDomainSet() throws VisADException, RemoteException {
+    return adaptedField.getDomainSet();
+  }
+
+  public int getLength() throws RemoteException {
+    return length;
+  }
+
+  public Unit[] getDomainUnits() throws VisADException, RemoteException {
+    return adaptedField.getDomainUnits();
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem()
+         throws VisADException, RemoteException {
+    return adaptedField.getDomainCoordinateSystem();
+  }
+
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    return adaptedField.getSample(index);
+  }
+
+  public void setSample(RealTuple domain, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    adaptedField.setSample(domain, range, copy);
+  }
+
+  public void setSample(RealTuple domain, Data range)
+         throws VisADException, RemoteException {
+    adaptedField.setSample(domain, range);
+  }
+
+  public void setSample(int index, Data range, boolean copy)
+         throws VisADException, RemoteException {
+    adaptedField.setSample(index, range, copy);
+  }
+
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    adaptedField.setSample(index, range);
+  }
+
+  public Field extract(int component)
+         throws VisADException, RemoteException {
+    return adaptedField.extract(component);
+  }
+
+  public Field domainMultiply()
+         throws VisADException, RemoteException {
+    return adaptedField.domainMultiply();
+  }
+
+  public Field domainMultiply(int depth)
+         throws VisADException, RemoteException {
+    return adaptedField.domainMultiply(depth);
+  }
+
+  public Field domainFactor( RealType factor )
+         throws VisADException, RemoteException {
+    return adaptedField.domainFactor(factor);
+  }
+
+  public double[][] getValues()
+         throws VisADException, RemoteException {
+    return adaptedField.getValues();
+  }
+
+  public double[][] getValues(boolean copy)
+         throws VisADException, RemoteException {
+    return adaptedField.getValues(copy);
+  }
+
+  public float[][] getFloats()
+         throws VisADException, RemoteException {
+    return adaptedField.getFloats();
+  }
+
+  public float[][] getFloats(boolean copy)
+         throws VisADException, RemoteException {
+    return adaptedField.getFloats(copy);
+  }
+
+  public String[][] getStringValues()
+         throws VisADException, RemoteException {
+    return adaptedField.getStringValues();
+  }
+
+  public Unit[] getDefaultRangeUnits()
+         throws VisADException, RemoteException {
+    return adaptedField.getDefaultRangeUnits();
+  }
+
+  public Unit[][] getRangeUnits()
+         throws VisADException, RemoteException {
+    return adaptedField.getRangeUnits();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws VisADException, RemoteException {
+    return adaptedField.getRangeCoordinateSystem();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem(int i)
+         throws VisADException, RemoteException {
+    return adaptedField.getRangeCoordinateSystem(i);
+  }
+
+  /** even if flat == true, a cast of this to FlatField will fail */
+  public boolean isFlatField() throws VisADException, RemoteException {
+    return false;
+  }
+
+  public Enumeration domainEnumeration()
+         throws VisADException, RemoteException {
+    return adaptedField.domainEnumeration();
+  }
+
+
+
+  public Data evaluate(Real domain)
+         throws VisADException, RemoteException {
+    return adaptedField.evaluate(domain);
+  }
+
+  public Data evaluate(Real domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return adaptedField.evaluate(domain, sampling_mode, error_mode);
+  }
+
+  public Data evaluate(RealTuple domain)
+         throws VisADException, RemoteException {
+    return adaptedField.evaluate(domain);
+  }
+
+  public Data evaluate(RealTuple domain, int sampling_mode, int error_mode)
+              throws VisADException, RemoteException {
+    return adaptedField.evaluate(domain, sampling_mode, error_mode);
+  }
+
+  public Field resample(Set set) throws VisADException, RemoteException {
+    return adaptedField.resample(set);
+  }
+
+  public Field resample(Set set, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    return adaptedField.resample(set, sampling_mode, error_mode);
+  }
+
+  public Data derivative( RealTuple location, RealType[] d_partial_s,
+                          MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    return adaptedField.derivative(location, d_partial_s, derivType_s, error_mode);
+  }
+
+  public Data derivative( int error_mode )
+         throws VisADException, RemoteException {
+    return adaptedField.derivative(error_mode);
+  }
+
+  public Data derivative( MathType[] derivType_s, int error_mode )
+         throws VisADException, RemoteException {
+    return adaptedField.derivative(derivType_s, error_mode);
+  }
+
+  public Function derivative( RealType d_partial, int error_mode )
+         throws VisADException, RemoteException {
+    return adaptedField.derivative(d_partial, error_mode);
+  }
+
+  public Function derivative( RealType d_partial, MathType derivType, int error_mode )
+         throws VisADException, RemoteException {
+    return adaptedField.derivative(d_partial, derivType, error_mode);
+  }
+
+
+
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    return adaptedField.computeRanges(type, shadow);
+  }
+
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    return adaptedField.computeRanges(type, n);
+  }
+
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    return adaptedField.computeRanges(reals);
+  }
+
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    return adaptedField.adjustSamplingError(error, error_mode);
+  }
+
+  public String longString() throws VisADException, RemoteException {
+    return longString("");
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    return pre + "RemoteNodePartitionedFieldImpl";
+  }
+
+}
+
diff --git a/visad/cluster/RemoteNodeTuple.java b/visad/cluster/RemoteNodeTuple.java
new file mode 100644
index 0000000..b7a47de
--- /dev/null
+++ b/visad/cluster/RemoteNodeTuple.java
@@ -0,0 +1,38 @@
+//
+// RemoteNodeTuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+/**
+   RemoteNodeTuple is the interface for cluster node
+   VisAD Tuple data objects.<P>
+*/
+public interface RemoteNodeTuple extends RemoteNodeData, RemoteTupleIface {
+
+}
+
diff --git a/visad/cluster/RemoteNodeTupleImpl.java b/visad/cluster/RemoteNodeTupleImpl.java
new file mode 100644
index 0000000..b20f1ca
--- /dev/null
+++ b/visad/cluster/RemoteNodeTupleImpl.java
@@ -0,0 +1,139 @@
+//
+// RemoteNodeTupleImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   RemoteNodeTupleImpl is the class for cluster client
+   VisAD Tuple data objects.<P>
+*/
+public class RemoteNodeTupleImpl extends RemoteNodeDataImpl
+       implements RemoteNodeTuple {
+
+  private Tuple adaptedTuple = null;
+  private DataReferenceImpl adaptedTupleRef = null;
+
+  /**
+     must call setupClusterData after constructor to finish the
+     "construction"
+  */
+  public RemoteNodeTupleImpl(Data[] datums)
+         throws VisADException, RemoteException {
+    super();
+    if (datums == null) {
+      throw new ClusterException("datums cannot be null");
+    }
+    int n = datums.length;
+    if (n == 0) {
+      throw new ClusterException("datums.length must be > 0");
+    }
+    for (int i=0; i<n; i++) {
+      if (!(datums[i] instanceof DataImpl ||
+            datums[i] instanceof RemoteNodeDataImpl)) {
+        throw new ClusterException("datums must be DataImpl " +
+                                   "or RemoteNodeDataImpl");
+      }
+    }
+    adaptedTuple = new Tuple(datums, false); // no copy
+    // set this as parent for RemoteNodeDataImpls
+    boolean any_local = false;
+    for (int i=0; i<n; i++) {
+      if (datums[i] instanceof RemoteNodeDataImpl) {
+        ((RemoteNodeDataImpl) datums[i]).setParent(this);
+      }
+      else {
+        any_local = true;
+      }
+    }
+    if (any_local) {
+      // hack parent notify logic for non-RemoteNodeDataImpl components
+      adaptedTupleRef = new DataReferenceImpl("adaptedTupleRef");
+      adaptedTupleRef.setData(adaptedTuple);
+      CellImpl adaptedTupleCell = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+          notifyReferences();
+        }
+      };
+      adaptedTupleCell.addReference(adaptedTupleRef);
+    }
+  }
+
+  public MathType getType() throws VisADException, RemoteException {
+    return adaptedTuple.getType();
+  }
+
+  public Real[] getRealComponents()
+         throws VisADException, RemoteException {
+    return adaptedTuple.getRealComponents();
+  }
+
+  public int getDimension() throws RemoteException {
+    return adaptedTuple.getDimension();
+  }
+
+  public Data getComponent(int i) throws VisADException, RemoteException {
+    return adaptedTuple.getComponent(i);
+  }
+
+  public boolean isMissing() throws RemoteException {
+    return adaptedTuple.isMissing();
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    return adaptedTuple.computeRanges(type, shadow);
+  }
+
+  public DataShadow computeRanges(ShadowType type, int n)
+         throws VisADException, RemoteException {
+    return adaptedTuple.computeRanges(type, n);
+  }
+
+  public double[][] computeRanges(RealType[] reals)
+         throws VisADException, RemoteException {
+    return adaptedTuple.computeRanges(reals);
+  }
+
+  public Data adjustSamplingError(Data error, int error_mode)
+         throws VisADException, RemoteException {
+    return adaptedTuple.adjustSamplingError(error, error_mode);
+  }
+
+  public String longString() throws VisADException, RemoteException {
+    return longString("");
+  }
+
+  public String longString(String pre)
+         throws VisADException, RemoteException {
+    return pre + "RemoteNodeTupleImpl";
+  }
+
+}
+
diff --git a/visad/cluster/RemoteProxyAgent.java b/visad/cluster/RemoteProxyAgent.java
new file mode 100644
index 0000000..270f3cf
--- /dev/null
+++ b/visad/cluster/RemoteProxyAgent.java
@@ -0,0 +1,55 @@
+//
+// RemoteProxyAgent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import java.util.Vector;
+import java.rmi.*;
+import java.io.Serializable;
+
+/**
+   RemoteProxyAgent is the interface for agents on the proxy client
+*/
+public interface RemoteProxyAgent extends Remote {
+
+  public RemoteClientData getRemoteClientData() throws RemoteException;
+
+  public void setResolutions(int[] rs) throws RemoteException;
+
+  public Serializable[] prepareAction(boolean go, boolean initialize,
+                                  DataShadow shadow, ConstantMap[] cmaps,
+                                  ScalarMap[] maps, Control[] controls,
+                                  String name, long time_out)
+         throws VisADException, RemoteException;
+
+  public Serializable[] doTransform() throws VisADException, RemoteException;
+
+  public Serializable[] computeRanges(Vector message)
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/cluster/RemoteProxyAgentImpl.java b/visad/cluster/RemoteProxyAgentImpl.java
new file mode 100644
index 0000000..ec4057f
--- /dev/null
+++ b/visad/cluster/RemoteProxyAgentImpl.java
@@ -0,0 +1,153 @@
+//
+// RemoteProxyAgentImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import java.util.Vector;
+import java.rmi.*;
+import java.rmi.server.UnicastRemoteObject;
+import java.io.Serializable;
+
+/**
+   RemoteProxyAgentImpl is the class for agents on proxy clients
+*/
+public class RemoteProxyAgentImpl extends UnicastRemoteObject
+       implements RemoteProxyAgent {
+
+  private RemoteClientDataImpl rcdi = null;
+
+  private boolean sent_node_agents = false;
+
+  private RemoteClientAgentImpl[] agents = null;
+  private RemoteClientAgentImpl focus_agent = null;
+  private RemoteAgentContact[] contacts = null;
+
+  private int[] resolutions = null;
+
+  private ConstantMap[] cmaps = null;
+  private ScalarMap[] maps = null;
+  private Control[] controls = null;
+
+  public RemoteProxyAgentImpl(RemoteClientDataImpl r) throws RemoteException {
+    rcdi = r;
+  }
+
+  public RemoteClientData getRemoteClientData() throws RemoteException {
+    return rcdi;
+  }
+
+  public void setResolutions(int[] rs) {
+    if (rs == null) return;
+    int n = rs.length;
+    resolutions = new int[n];
+    for (int i=0; i<n; i++) resolutions[i] = rs[i];
+  }
+
+  public Serializable[] prepareAction(boolean go, boolean initialize,
+                                  DataShadow shadow, ConstantMap[] cms,
+                                  ScalarMap[] ms, Control[] cos,
+                                  String name, long time_out)
+         throws VisADException, RemoteException {
+
+    cmaps = cms;
+    maps = ms;
+    controls = cos;
+
+    if (!sent_node_agents) {
+      // send agents to nodes if data changed
+      focus_agent = new RemoteClientAgentImpl(null, -1, time_out);
+      RemoteClusterData[] jvmTable = rcdi.getTable();
+      int nagents = jvmTable.length - 1;
+      agents = new RemoteClientAgentImpl[nagents];
+      contacts = new RemoteAgentContact[nagents];
+      for (int i=0; i<nagents; i++) {
+        agents[i] = new RemoteClientAgentImpl(focus_agent, i);
+        DefaultNodeRendererAgent node_agent =
+          new DefaultNodeRendererAgent(agents[i], name, cmaps);
+        contacts[i] = ((RemoteNodeData) jvmTable[i]).sendAgent(node_agent);
+      }
+      sent_node_agents = true;
+    }
+
+    Vector message = new Vector();
+    for (int i=0; i<maps.length; i++) {
+      message.addElement(maps[i]);
+      message.addElement(controls[i]);
+    }
+    Serializable[] responses =
+      focus_agent.broadcastWithResponses(message, contacts); // PROXY
+// System.out.println("RemoteProxyAgentImpl.prepareAction messages received");
+    return responses;
+  }
+
+
+  public Serializable[] doTransform() throws VisADException, RemoteException {
+
+    if (rcdi == null) {
+      throw new DisplayException("Data is null");
+    }
+
+    int n = contacts.length;
+    Vector[] messages = new Vector[n];
+
+    if (resolutions == null || resolutions.length != n) {
+      resolutions = new int[n];
+      for (int i=0; i<n; i++) resolutions[i] = 1;
+    }
+
+    for (int i=0; i<n; i++) {
+      // String message = "transform";
+      messages[i] = new Vector();
+      messages[i].addElement("transform");
+
+      messages[i].addElement(new Integer(resolutions[i]));
+
+      int m = maps.length;
+      for (int j=0; j<m; j++) {
+        messages[i].addElement(maps[j]);
+      }
+    }
+
+    // responses are VisADGroups
+    Serializable[] responses =
+      focus_agent.broadcastWithResponses(messages, contacts);
+// System.out.println("ProxyRendererJ3D.doTransform messages received");
+
+    return responses;
+  }
+
+  public Serializable[] computeRanges(Vector message)
+         throws VisADException, RemoteException {
+
+    Serializable[] responses =
+      focus_agent.broadcastWithResponses(message, contacts);
+// System.out.println("RemoteProxyAgentImpl.computeRanges messages received");
+    return responses;
+  }
+
+}
+
diff --git a/visad/cluster/ShadowNodeFunctionTypeJ3D.java b/visad/cluster/ShadowNodeFunctionTypeJ3D.java
new file mode 100644
index 0000000..ab4b4e4
--- /dev/null
+++ b/visad/cluster/ShadowNodeFunctionTypeJ3D.java
@@ -0,0 +1,330 @@
+//
+// ShadowNodeFunctionTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+import java.awt.image.*;
+
+/**
+   The ShadowNodeFunctionTypeJ3D class shadows the FunctionType class for
+   NodeRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowNodeFunctionTypeJ3D extends ShadowFunctionTypeJ3D {
+
+  public ShadowNodeFunctionTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    Data new_data = data;
+    if (renderer instanceof NodeRendererJ3D &&
+        data instanceof RemoteNodePartitionedFieldImpl) {
+      int resolution = ((NodeRendererJ3D) renderer).getResolution();
+      if (resolution > 1) {
+        FieldImpl adaptedField =
+          ((RemoteNodePartitionedFieldImpl) data).getAdaptedField();
+        Set set = adaptedField.getDomainSet();
+        if (set instanceof Gridded3DSet &&
+            set.getManifoldDimension() == 3) {
+          Gridded3DSet domain_set = (Gridded3DSet) set;
+          float[][] samples = domain_set.getSamples(false);
+          int x_len = domain_set.getLength(0);
+          int y_len = domain_set.getLength(1);
+          int z_len = domain_set.getLength(2);
+          int len = domain_set.getLength();
+          int new_x_len = 1 + (x_len - 1) / resolution;
+          int new_y_len = 1 + (y_len - 1) / resolution;
+          int new_z_len = 1 + (z_len - 1) / resolution;
+          int new_len = new_x_len * new_y_len * new_z_len;
+          float[][] new_samples = new float[3][new_len];
+          for (int x=0; x<new_x_len; x++) {
+            int i = x * resolution;
+            for (int y=0; y<new_y_len; y++) {
+              int j = y * resolution;
+              for (int z=0; z<new_z_len; z++) {
+                int k = z * resolution;
+                int ijk = i + x_len * (j + y_len * k);
+                int xyz = x + new_x_len * (y + new_y_len * z);
+                new_samples[0][xyz] = samples[0][ijk];
+                new_samples[1][xyz] = samples[1][ijk];
+                new_samples[2][xyz] = samples[2][ijk];
+              }
+            }
+          }
+          SetType domain_type = (SetType) domain_set.getType();
+          Gridded3DSet new_domain_set =
+            new Gridded3DSet(domain_type, new_samples,
+                             new_x_len, new_y_len, new_z_len,
+                             domain_set.getCoordinateSystem(),
+                             domain_set.getSetUnits(), null);
+          FieldImpl newAdaptedField = (FieldImpl)
+            adaptedField.resample(new_domain_set);
+          new_data = new RemoteNodePartitionedFieldImpl(newAdaptedField);
+// System.out.println("resolution = " + resolution + " " +
+//                    new_len + " out of " + len);
+        }
+      }
+    }
+    return super.doTransform(group, new_data, value_array,
+                             default_values, renderer);
+  }
+
+  public void textureToGroup(Object group, VisADGeometryArray array,
+                            BufferedImage image, GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color,
+                            int texture_width, int texture_height)
+         throws VisADException {
+    // create basic Appearance
+    VisADAppearance appearance =
+      makeAppearance(mode, constant_alpha, constant_color, array);
+
+    // must encode image as Serializable rather than as Image
+    // appearance.image = image;
+    appearance.image = null;
+    appearance.image_type = image.getType();
+    appearance.image_width = image.getWidth();
+    appearance.image_height = image.getHeight();
+    appearance.image_pixels =
+      image.getRGB(0, 0, appearance.image_width, appearance.image_height, null,
+                   0, appearance.image_width);
+    ((VisADGroup) group).addChild(appearance);
+    appearance.texture_width = texture_width;
+    appearance.texture_height = texture_height;
+  }
+
+  public void texture3DToGroup(Object group, VisADGeometryArray arrayX,
+                    VisADGeometryArray arrayY, VisADGeometryArray arrayZ,
+                    VisADGeometryArray arrayXrev,
+                    VisADGeometryArray arrayYrev,
+                    VisADGeometryArray arrayZrev,
+                    BufferedImage[] images, GraphicsModeControl mode,
+                    float constant_alpha, float[] constant_color,
+                    int texture_width, int texture_height,
+                    int texture_depth, DataRenderer renderer)
+         throws VisADException {
+    // not used now, so do nothing
+  }
+
+  /** client must process the VisADSwitch this makes in order to insert
+      in a Java3D scene graph */
+  public void textureStackToGroup(Object group, VisADGeometryArray arrayX,
+                    VisADGeometryArray arrayY, VisADGeometryArray arrayZ,
+                    VisADGeometryArray arrayXrev,
+                    VisADGeometryArray arrayYrev,
+                    VisADGeometryArray arrayZrev,
+                    BufferedImage[] imagesX,
+                    BufferedImage[] imagesY,
+                    BufferedImage[] imagesZ,
+                    GraphicsModeControl mode,
+                    float constant_alpha, float[] constant_color,
+                    int texture_width, int texture_height,
+                    int texture_depth, DataRenderer renderer)
+         throws VisADException {
+    VisADGeometryArray[] geometryX = makeVisADGeometrys(arrayX);
+    VisADGeometryArray[] geometryY = makeVisADGeometrys(arrayY);
+    VisADGeometryArray[] geometryZ = makeVisADGeometrys(arrayZ);
+// cut and paste ShadowFunctionOrSetTypeJ3D.textureStackToGroup
+
+    // client must treat branchX as ordered
+    VisADGroup branchX = new VisADGroup();
+    int data_depth = geometryX.length;
+    for (int i=0; i<data_depth; i++) {
+      // client must compute c_alpha from constant_alpha
+      VisADAppearance appearance =
+        makeAppearance(mode, constant_alpha, constant_color, geometryX[i]);
+      // must encode image as Serializable rather than as Image
+      // appearance.image = image;
+      appearance.image = null;
+      appearance.image_type = imagesX[i].getType();
+      appearance.image_width = imagesX[i].getWidth();
+      appearance.image_height = imagesX[i].getHeight();
+      appearance.image_pixels =
+        imagesX[i].getRGB(0, 0, appearance.image_width, appearance.image_height,
+                          null, 0, appearance.image_width);
+      // according to logic in ShadowFunctionOrSetTypeJ3D.textureStackToGroup()
+      appearance.texture_width = imagesX[i].getWidth();
+      appearance.texture_height = imagesX[i].getHeight();
+      branchX.addChild(appearance);
+    }
+    // client must construct branchXrev from VisADAppearances in branchX
+
+    // VisADGroup branchYrev = new VisADGroup();
+    // client must treat branchY as ordered
+    VisADGroup branchY = new VisADGroup();
+    int data_height = geometryY.length;
+    for (int i=0; i<data_height; i++) {
+      // client must compute c_alpha from constant_alpha
+      VisADAppearance appearance =
+        makeAppearance(mode, constant_alpha, constant_color, geometryY[i]);
+      // must encode image as Serializable rather than as Image
+      // appearance.image = image;
+      appearance.image = null;
+      appearance.image_type = imagesY[i].getType();
+      appearance.image_width = imagesY[i].getWidth();
+      appearance.image_height = imagesY[i].getHeight();
+      appearance.image_pixels =
+        imagesY[i].getRGB(0, 0, appearance.image_width, appearance.image_height, 
+                          null, 0, appearance.image_width);
+      // according to logic in ShadowFunctionOrSetTypeJ3D.textureStackToGroup()
+      appearance.texture_width = imagesY[i].getWidth();
+      appearance.texture_height = imagesY[i].getHeight();
+      branchY.addChild(appearance);
+    }
+    // client must construct branchYrev from VisADAppearances in branchY
+    // VisADGroup branchYrev = new VisADGroup();
+
+    // VisADGroup branchZrev = new VisADGroup();
+    // client must treat branchZ as ordered
+    VisADGroup branchZ = new VisADGroup();
+    int data_width = geometryZ.length;
+    for (int i=0; i<data_width; i++) {
+      // client must compute c_alpha from constant_alpha
+      VisADAppearance appearance =
+        makeAppearance(mode, constant_alpha, constant_color, geometryZ[i]);
+      // must encode image as Serializable rather than as Image
+      // appearance.image = image;
+      appearance.image = null;
+      appearance.image_type = imagesZ[i].getType();
+      appearance.image_width = imagesZ[i].getWidth();
+      appearance.image_height = imagesZ[i].getHeight();
+      appearance.image_pixels =
+        imagesZ[i].getRGB(0, 0, appearance.image_width, appearance.image_height, 
+                          null, 0, appearance.image_width);
+      // according to logic in ShadowFunctionOrSetTypeJ3D.textureStackToGroup()
+      appearance.texture_width = imagesZ[i].getWidth();
+      appearance.texture_height = imagesZ[i].getHeight();
+      branchZ.addChild(appearance);
+    }
+    // client must construct branchZrev from VisADAppearances in branchZ
+    // VisADGroup branchZrev = new VisADGroup();
+
+    VisADSwitch swit = (VisADSwitch) makeSwitch();
+    swit.addChild(branchX);
+    swit.addChild(branchY);
+    swit.addChild(branchZ);
+    // swit.addChild(branchXrev);
+    // swit.addChild(branchYrev);
+    // swit.addChild(branchZrev);
+    swit.setSet(null); // to distinguish swit from a VisADSwitch for Animation
+
+    VisADGroup branch = new VisADGroup();
+    branch.addChild(swit);
+    if (((VisADGroup) group).numChildren() > 0) {
+      ((VisADGroup) group).setChild(branch, 0);
+    }
+    else {
+      ((VisADGroup) group).addChild(branch);
+    }
+  }
+
+
+  public Object makeSwitch() {
+    return new VisADSwitch();
+  }
+
+  public Object makeBranch() {
+    VisADGroup branch = new VisADGroup();
+    return branch;
+  }
+
+  public void addToGroup(Object group, Object branch)
+         throws VisADException {
+    ((VisADGroup) group).addChild((VisADGroup) branch);
+  }
+
+  public void addToSwitch(Object swit, Object branch)
+         throws VisADException {
+    ((VisADSwitch) swit).addChild((VisADGroup) branch);
+  }
+
+  public void addSwitch(Object group, Object swit, Control control,
+                        Set domain_set, DataRenderer renderer)
+         throws VisADException {
+    ((VisADSwitch) swit).setSet(domain_set); // Serialize domain_set with swit
+    ((VisADGroup) group).addChild((VisADSwitch) swit);
+  }
+
+// NOT true in ShadowFunctionOrSetTypeJ3D
+// could be true for better Serializable compression
+// but false means client does not need to un-index it
+  public boolean wantIndexed() {
+    return false;
+  }
+
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+                            GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color)
+         throws VisADException {
+    return ShadowNodeFunctionTypeJ3D.staticAddToGroup(group, array, mode,
+                                                      constant_alpha, constant_color);
+  }
+
+  public static boolean staticAddToGroup(Object group, VisADGeometryArray array,
+                                         GraphicsModeControl mode,
+                                         float constant_alpha, float[] constant_color)
+         throws VisADException {
+    if (array != null) {
+      VisADAppearance appearance =
+        makeAppearance(mode, constant_alpha, constant_color, array);
+      ((VisADGroup) group).addChild(appearance);
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+  /** construct an VisADAppearance object */
+  static VisADAppearance makeAppearance(GraphicsModeControl mode,
+                      float constant_alpha,
+                      float[] constant_color,
+                      VisADGeometryArray array) {
+    VisADAppearance appearance = new VisADAppearance();
+    appearance.pointSize = mode.getPointSize();
+    appearance.lineWidth = mode.getLineWidth();
+
+    appearance.alpha = constant_alpha; // may be Float.NaN
+    if (constant_color != null && constant_color.length == 3) {
+      appearance.color_flag = true;
+      appearance.red = constant_color[0];
+      appearance.green = constant_color[1];
+      appearance.blue = constant_color[2];
+    }
+    appearance.array = array; // may be null
+    return appearance;
+  }
+
+}
+
diff --git a/visad/cluster/ShadowNodeRealTupleTypeJ3D.java b/visad/cluster/ShadowNodeRealTupleTypeJ3D.java
new file mode 100644
index 0000000..b064896
--- /dev/null
+++ b/visad/cluster/ShadowNodeRealTupleTypeJ3D.java
@@ -0,0 +1,55 @@
+//
+// ShadowNodeRealTupleTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowNodeRealTupleTypeJ3D class shadows the RealTupleType class
+   for NodeRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowNodeRealTupleTypeJ3D extends ShadowRealTupleTypeJ3D {
+
+  public ShadowNodeRealTupleTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+                            GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color)
+         throws VisADException {
+    return ShadowNodeFunctionTypeJ3D.staticAddToGroup(group, array, mode, 
+                                                      constant_alpha, constant_color);
+  }
+
+}
+
diff --git a/visad/cluster/ShadowNodeRealTypeJ3D.java b/visad/cluster/ShadowNodeRealTypeJ3D.java
new file mode 100644
index 0000000..88c4343
--- /dev/null
+++ b/visad/cluster/ShadowNodeRealTypeJ3D.java
@@ -0,0 +1,55 @@
+//
+// ShadowNodeRealTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowNodeRealTypeJ3D class shadows the RealType class for
+   NodeRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowNodeRealTypeJ3D extends ShadowRealTypeJ3D {
+
+  public ShadowNodeRealTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+                            GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color)
+         throws VisADException {
+    return ShadowNodeFunctionTypeJ3D.staticAddToGroup(group, array, mode, 
+                                                      constant_alpha, constant_color);
+  }
+
+}
+
diff --git a/visad/cluster/ShadowNodeSetTypeJ3D.java b/visad/cluster/ShadowNodeSetTypeJ3D.java
new file mode 100644
index 0000000..96627b6
--- /dev/null
+++ b/visad/cluster/ShadowNodeSetTypeJ3D.java
@@ -0,0 +1,55 @@
+//
+// ShadowNodeSetTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowNodeSetTypeJ3D class shadows the SetType class for
+   NodeRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowNodeSetTypeJ3D extends ShadowSetTypeJ3D {
+
+  public ShadowNodeSetTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+                            GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color)
+         throws VisADException {
+    return ShadowNodeFunctionTypeJ3D.staticAddToGroup(group, array, mode, 
+                                                      constant_alpha, constant_color);
+  }
+
+}
+
diff --git a/visad/cluster/ShadowNodeTupleTypeJ3D.java b/visad/cluster/ShadowNodeTupleTypeJ3D.java
new file mode 100644
index 0000000..20a45b3
--- /dev/null
+++ b/visad/cluster/ShadowNodeTupleTypeJ3D.java
@@ -0,0 +1,55 @@
+//
+// ShadowNodeTupleTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowNodeTupleTypeJ3D class shadows the TupleType class for
+   NodeRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowNodeTupleTypeJ3D extends ShadowTupleTypeJ3D {
+
+  public ShadowNodeTupleTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+                            GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color)
+         throws VisADException {
+    return ShadowNodeFunctionTypeJ3D.staticAddToGroup(group, array, mode, 
+                                                      constant_alpha, constant_color);
+  }
+
+}
+
diff --git a/visad/cluster/TestCluster.java b/visad/cluster/TestCluster.java
new file mode 100644
index 0000000..c0ee1d1
--- /dev/null
+++ b/visad/cluster/TestCluster.java
@@ -0,0 +1,264 @@
+//
+// TestCluster.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.java2d.*;
+import visad.util.ContourWidget;
+import visad.data.gif.GIFForm;
+
+import java.rmi.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+   TestCluster is the class for testing the visad.cluster package.<P>
+*/
+public class TestCluster extends Object {
+
+  public TestCluster() {
+  }
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException {
+
+    int node_divide = 2;
+    int number_of_nodes = node_divide * node_divide;
+
+    if (args == null || args.length < 2) {
+      System.out.println("usage: 'java visad.cluster.TestCluster n file.gif'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+    int id = -1;
+    try {
+      id = Integer.parseInt(args[0]);
+    }
+    catch (NumberFormatException e) {
+      System.out.println("usage: 'java visad.cluster.TestCluster n file.gif'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+    if (id < 0 || id > number_of_nodes) {
+      System.out.println("usage: 'java visad.cluster.TestCluster n file.gif'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+
+    boolean client = (id == 0);
+
+    GIFForm gif_form = new GIFForm();
+    FlatField image = (FlatField) gif_form.open(args[1]);
+    if (image == null) {
+      System.out.println("cannot open " + args[1]);
+      return;
+    }
+/*
+new Integer2DSet(imageDomain, nelements, nlines));
+MathType.stringToType("((ImageElement, ImageLine) -> ImageRadiance)");
+*/
+
+    FunctionType image_type = (FunctionType) image.getType();
+    RealTupleType domain_type = image_type.getDomain();
+    Linear2DSet domain_set = (Linear2DSet) image.getDomainSet();
+    Linear1DSet x_set = domain_set.getX();
+    Linear1DSet y_set = domain_set.getY();
+    // getFirst, getLast, getStep, getLength
+    int x_len = x_set.getLength();
+    int y_len = y_set.getLength();
+    int len = domain_set.getLength();
+    Linear2DSet ps =
+      new Linear2DSet(domain_type,
+                      x_set.getFirst(), x_set.getLast(), node_divide,
+                      y_set.getFirst(), y_set.getLast(), node_divide,
+                      domain_set.getCoordinateSystem(),
+                      domain_set.getSetUnits(), null);
+
+    RemoteNodePartitionedField[] node_images =
+      new RemoteNodePartitionedField[number_of_nodes];
+  
+    if (!client) {
+      Linear2DSet[] subsets = new Linear2DSet[number_of_nodes];
+  
+      if (number_of_nodes == 1) {
+        subsets[0] = domain_set;
+        node_images[0] = new RemoteNodePartitionedFieldImpl(image);
+      }
+      else {
+        int[] indices = new int[len];
+        for (int i=0; i<len; i++) indices[i] = i;
+        float[][] values = domain_set.indexToValue(indices);
+        int[] ps_indices = ps.valueToIndex(values);
+        float[][] firsts = new float[2][number_of_nodes];
+        float[][] lasts = new float[2][number_of_nodes];
+        int[][] lows = new int[2][number_of_nodes];
+        int[][] his = new int[2][number_of_nodes];
+        for (int j=0; j<2; j++) {
+          for (int i=0; i<number_of_nodes; i++) {
+            firsts[j][i] = Float.MAX_VALUE;
+            lasts[j][i] = -Float.MAX_VALUE;
+            lows[j][i] = len + 1;
+            his[j][i] = -1;
+          }
+        }
+        for (int i=0; i<len; i++) {
+          int k = ps_indices[i];
+          if (k < 0) continue;
+          int[] index = {indices[i] % x_len, indices[i] / x_len};
+          for (int j=0; j<2; j++) {
+            if (values[j][i] < firsts[j][k]) firsts[j][k] = values[j][i];
+            if (values[j][i] > lasts[j][k]) lasts[j][k] = values[j][i];
+            if (index[j] < lows[j][k]) lows[j][k] = index[j];
+            if (index[j] > his[j][k]) his[j][k] = index[j];
+          }
+        }
+        int k = id - 1;
+        if (his[0][k] < 0 || his[1][k] < 0) {
+          throw new ClusterException("Set partition error");
+        }
+        subsets[k] =
+          new Linear2DSet(domain_type,
+                      firsts[0][k], lasts[0][k], (his[0][k] - lows[0][k] + 1),
+                      firsts[1][k], lasts[1][k], (his[1][k] - lows[1][k] + 1),
+                      domain_set.getCoordinateSystem(),
+                      domain_set.getSetUnits(), null);
+        FieldImpl subimage = (FieldImpl) image.resample(subsets[k]);
+        node_images[k] = new RemoteNodePartitionedFieldImpl(subimage);
+      }
+      int kk = id - 1;
+      String url = "///TestCluster" + kk;
+      try {
+        Naming.rebind(url, node_images[kk]);
+      }
+      catch (Exception e) {
+        System.out.println("rebind " + kk + " " + e);
+        return;
+      }
+      // just so app doesn't exit
+      DisplayImpl display = new DisplayImplJ2D("dummy");
+      return;
+    } // end if (!client)
+
+    for (int k=0; k<number_of_nodes; k++) {
+      String url = "///TestCluster" + k;
+      try {
+        node_images[k] = (RemoteNodePartitionedField) Naming.lookup(url);
+      }
+      catch (Exception e) {
+        System.out.println("lookup " + k + " " + e);
+        return;
+      }
+    }
+    
+    RemoteClientPartitionedFieldImpl client_image =
+      new RemoteClientPartitionedFieldImpl(image_type, domain_set);
+
+    RemoteClusterData[] table =
+      new RemoteClusterData[number_of_nodes + 1];
+    for (int i=0; i<number_of_nodes; i++) {
+      table[i] = node_images[i];
+    }
+    table[number_of_nodes] = client_image;
+
+    for (int i=0; i<table.length; i++) {
+      table[i].setupClusterData(ps, table);
+    }
+
+    DisplayImpl display =
+      // new DisplayImplJ3D("main_display");
+      new DisplayImplJ3D("main_display", new ClientDisplayRendererJ3D(100000));
+
+/*
+    // get a list of decent mappings for this data
+    MathType type = image.getType();
+    ScalarMap[] maps = type.guessMaps(true);
+    // add the maps to the display
+    for (int i=0; i<maps.length; i++) {
+      display.addMap(maps[i]);
+    }
+*/
+
+    // FunctionType image_type = (FunctionType) image.getType();
+    // RealTupleType domain_type = image_type.getDomain();
+    RealType element = (RealType) domain_type.getComponent(0);
+    RealType line = (RealType) domain_type.getComponent(1);
+    RealTupleType range_type = (RealTupleType) image_type.getRange();
+    RealType red = (RealType) range_type.getComponent(0);
+    display.addMap(new ScalarMap(line, Display.YAxis));
+    display.addMap(new ScalarMap(element, Display.XAxis));
+    ScalarMap contour_map = new ScalarMap(red, Display.IsoContour);
+    display.addMap(contour_map);
+
+    // link data to the display
+    DataReferenceImpl ref = new DataReferenceImpl("image");
+    // ref.setData(image);
+    // display.addReference(ref);
+    RemoteDataReferenceImpl remote_ref = new RemoteDataReferenceImpl(ref);
+    remote_ref.setData(client_image);
+    RemoteDisplayImpl remote_display = new RemoteDisplayImpl(display);
+    remote_display.addReference(remote_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test ClientRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+    panel.add(new ContourWidget(contour_map));
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 700);
+    frame.setVisible(true);
+  }
+
+
+/*
+    Real r = new Real(0);
+    RemoteClientTupleImpl cd = new RemoteClientTupleImpl(new Data[] {r});
+    RemoteClientTupleImpl cd2 = new RemoteClientTupleImpl(new Data[] {r});
+    System.out.println(cd.equals(cd)); // true
+    System.out.println(cd.equals(cd2)); // false
+    System.out.println(cd.clusterDataEquals(cd)); // true
+    System.out.println(cd.clusterDataEquals(cd2)); // false
+    System.exit(0);
+*/
+
+}
+
diff --git a/visad/cluster/TestClusterOneJVM.java b/visad/cluster/TestClusterOneJVM.java
new file mode 100644
index 0000000..f75406b
--- /dev/null
+++ b/visad/cluster/TestClusterOneJVM.java
@@ -0,0 +1,233 @@
+//
+// TestClusterOneJVM.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.data.gif.GIFForm;
+
+import java.rmi.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+   TestClusterOneJVM is the class for testing the visad.cluster package.<P>
+*/
+public class TestClusterOneJVM extends Object {
+
+  public TestClusterOneJVM() {
+  }
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException {
+
+    int node_divide = 2;
+    int number_of_nodes = node_divide * node_divide;
+
+    if (args == null || args.length < 1) {
+      System.out.println("usage: 'java visad.cluster.TestClusterOneJVM file.gif'");
+      return;
+    }
+
+    GIFForm gif_form = new GIFForm();
+    FlatField image = (FlatField) gif_form.open(args[0]);
+    if (image == null) {
+      System.out.println("cannot open " + args[0]);
+      return;
+    }
+/*
+new Integer2DSet(imageDomain, nelements, nlines));
+MathType.stringToType("((ImageElement, ImageLine) -> ImageRadiance)");
+*/
+
+    FunctionType image_type = (FunctionType) image.getType();
+    RealTupleType domain_type = image_type.getDomain();
+    Linear2DSet domain_set = (Linear2DSet) image.getDomainSet();
+    Linear1DSet x_set = domain_set.getX();
+    Linear1DSet y_set = domain_set.getY();
+    // getFirst, getLast, getStep, getLength
+    int x_len = x_set.getLength();
+    int y_len = y_set.getLength();
+    int len = domain_set.getLength();
+    Linear2DSet ps =
+      new Linear2DSet(domain_type,
+                      x_set.getFirst(), x_set.getLast(), node_divide,
+                      y_set.getFirst(), y_set.getLast(), node_divide,
+                      domain_set.getCoordinateSystem(),
+                      domain_set.getSetUnits(), null);
+
+    RemoteClientPartitionedFieldImpl client_image =
+      new RemoteClientPartitionedFieldImpl(image_type, domain_set);
+
+    Linear2DSet[] subsets = new Linear2DSet[number_of_nodes];
+
+    RemoteNodePartitionedFieldImpl[] node_images =
+      new RemoteNodePartitionedFieldImpl[number_of_nodes];
+
+    if (number_of_nodes == 1) {
+      subsets[0] = domain_set;
+      node_images[0] = new RemoteNodePartitionedFieldImpl(image);
+    }
+    else {
+      int[] indices = new int[len];
+      for (int i=0; i<len; i++) indices[i] = i;
+      float[][] values = domain_set.indexToValue(indices);
+      int[] ps_indices = ps.valueToIndex(values);
+      float[][] firsts = new float[2][number_of_nodes];
+      float[][] lasts = new float[2][number_of_nodes];
+      int[][] lows = new int[2][number_of_nodes];
+      int[][] his = new int[2][number_of_nodes];
+      for (int j=0; j<2; j++) {
+        for (int i=0; i<number_of_nodes; i++) {
+          firsts[j][i] = Float.MAX_VALUE;
+          lasts[j][i] = -Float.MAX_VALUE;
+          lows[j][i] = len + 1;
+          his[j][i] = -1;
+        }
+      }
+      for (int i=0; i<len; i++) {
+        int k = ps_indices[i];
+        if (k < 0) continue;
+        int[] index = {indices[i] % x_len, indices[i] / x_len};
+        for (int j=0; j<2; j++) {
+          if (values[j][i] < firsts[j][k]) firsts[j][k] = values[j][i];
+          if (values[j][i] > lasts[j][k]) lasts[j][k] = values[j][i];
+          if (index[j] < lows[j][k]) lows[j][k] = index[j];
+          if (index[j] > his[j][k]) his[j][k] = index[j];
+        }
+      }
+      for (int k=0; k<number_of_nodes; k++) {
+        if (his[0][k] < 0 || his[1][k] < 0) {
+          throw new ClusterException("Set partition error");
+        }
+        subsets[k] =
+          new Linear2DSet(domain_type,
+                      firsts[0][k], lasts[0][k], (his[0][k] - lows[0][k] + 1),
+                      firsts[1][k], lasts[1][k], (his[1][k] - lows[1][k] + 1),
+                      domain_set.getCoordinateSystem(),
+                      domain_set.getSetUnits(), null);
+        FieldImpl subimage = (FieldImpl) image.resample(subsets[k]);
+        node_images[k] = new RemoteNodePartitionedFieldImpl(subimage);
+      }
+    }
+
+    RemoteClusterData[] table =
+      new RemoteClusterData[number_of_nodes + 1];
+    for (int i=0; i<number_of_nodes; i++) {
+      table[i] = node_images[i];
+    }
+    table[number_of_nodes] = client_image;
+
+    for (int i=0; i<table.length; i++) {
+      table[i].setupClusterData(ps, table);
+    }
+
+    DisplayImpl display =
+      // new DisplayImplJ3D("main_display");
+      new DisplayImplJ3D("main_display", new ClientDisplayRendererJ3D(100000));
+
+/*
+    // get a list of decent mappings for this data
+    MathType type = image.getType();
+    ScalarMap[] maps = type.guessMaps(true);
+    // add the maps to the display
+    for (int i=0; i<maps.length; i++) {
+      display.addMap(maps[i]);
+    }
+*/
+
+    // FunctionType image_type = (FunctionType) image.getType();
+    // RealTupleType domain_type = image_type.getDomain();
+    RealType line = (RealType) domain_type.getComponent(0);
+    RealType element = (RealType) domain_type.getComponent(1);
+    RealTupleType range_type = (RealTupleType) image_type.getRange();
+    RealType red = (RealType) range_type.getComponent(0);
+    display.addMap(new ScalarMap(line, Display.YAxis));
+    display.addMap(new ScalarMap(element, Display.XAxis));
+    display.addMap(new ScalarMap(red, Display.IsoContour));
+
+    // link data to the display
+    DataReferenceImpl ref = new DataReferenceImpl("image");
+    // ref.setData(image);
+    // display.addReference(ref);
+    RemoteDataReferenceImpl remote_ref = new RemoteDataReferenceImpl(ref);
+    remote_ref.setData(client_image);
+    RemoteDisplayImpl remote_display = new RemoteDisplayImpl(display);
+    remote_display.addReference(remote_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test ClientRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+
+/*
+    Real r = new Real(0);
+    RemoteClientTupleImpl cd = new RemoteClientTupleImpl(new Data[] {r});
+    RemoteClientTupleImpl cd2 = new RemoteClientTupleImpl(new Data[] {r});
+    System.out.println(cd.equals(cd)); // true
+    System.out.println(cd.equals(cd2)); // false
+    System.out.println(cd.clusterDataEquals(cd)); // true
+    System.out.println(cd.clusterDataEquals(cd2)); // false
+    System.exit(0);
+*/
+
+}
+
+
+/*
+to test:
+wait for DisplayMonitor
+wait for DisplayMonitor
+wait for DisplayMonitor
+jdb stop in isEmpty() and find out what's in there
+
+only three of four image sections
+  so sync is not good enough
+hack is for NodeRendererJ3D.doTransform() to wait
+  for all ScalarMaps to have good ranges
+
+perhaps NodeRendererJ3D.doTransform() can wait for quiet incoming events
+  sort of a node version of isEmpty()
+*/
+
diff --git a/visad/cluster/TestProxyCluster.java b/visad/cluster/TestProxyCluster.java
new file mode 100644
index 0000000..088c95a
--- /dev/null
+++ b/visad/cluster/TestProxyCluster.java
@@ -0,0 +1,728 @@
+//
+// TestProxyCluster.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.java2d.*;
+import visad.ss.*;
+import visad.bom.*;
+import visad.data.netcdf.Plain;
+
+import java.util.Vector;
+import java.rmi.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.IOException;
+
+/**
+   TestProxyCluster is the class for testing the visad.cluster package.<P>
+<PRE> Run:
+ On cluster node1:
+   rmiregistry &
+   java -cp visad.jar visad.cluster.TestProxyCluster 1 wrfout_01_000000_0000
+ On cluster node2:
+   rmiregistry &
+   java -cp visad.jar visad.cluster.TestProxyCluster 2 wrfout_01_000000_0001
+ On cluster node3:
+   rmiregistry &
+   java -cp visad.jar visad.cluster.TestProxyCluster 3 wrfout_01_000000_0002
+ On cluster node4:
+   rmiregistry &
+   java -cp visad.jar visad.cluster.TestProxyCluster 4 wrfout_01_000000_0003
+
+ Wait for "data ready as ..." on all four cluster nodes.
+
+ On cluster host:
+   rmiregistry &
+   java -cp visad.jar visad.cluster.TestProxyCluster 0 node1 node2 node3 node4
+
+ Wait for "data ready as ..." on cluster host.
+
+ On user workstation:
+   rmiregistry &
+   java -cp visad.jar visad.cluster.TestProxyCluster 5 host
+
+ After "please wait ..." message in 3-D window goes off, click on "Widgets".
+ When widgets window pops up, slide "RRP" slider over to about 0.003.
+ When iso-surfaces appear, click "Go" to animate.
+ 
+</PRE>
+*/
+public class TestProxyCluster extends FancySSCell implements ActionListener {
+
+  private RemoteDataReferenceImpl remote_ref = null;
+
+  private RemoteProxyAgent agent = null;
+
+  public TestProxyCluster(String name, Frame parent, RemoteProxyAgent a)
+         throws VisADException, RemoteException {
+    super(name, parent);
+    agent = a;
+  }
+
+
+  /**
+   * override method from BasicSSCell
+   */
+  protected String addData(int id, Data data, ConstantMap[] cmaps,
+    String source, int type, boolean notify)
+    throws VisADException, RemoteException
+  {
+    // add Data object to cell
+    DataReferenceImpl ref = new DataReferenceImpl(Name);
+
+    // new
+    if (data instanceof RemoteData) {
+      remote_ref = new RemoteDataReferenceImpl(ref);
+      remote_ref.setData(data);
+    }
+    else {
+        ref.setData(data);
+    }
+
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = addReferenceImpl(id, ref, cmaps, source, type, notify, true);
+    }
+    return cellData.getVariableName();
+  }
+
+  /**
+   * override method from BasicSSCell
+   */
+  protected SSCellData addReferenceImpl(int id, DataReferenceImpl ref,
+    ConstantMap[] cmaps, String source, int type, boolean notify,
+    boolean checkErrors) throws VisADException, RemoteException
+  {
+    // ensure that id is valid
+    if (id == 0) id = getFirstFreeId();
+
+    // ensure that ref is valid
+    if (ref == null) ref = new DataReferenceImpl(Name);
+
+    // notify linked cells of data addition (ADD_DATA message must come first)
+    // if (notify) sendMessage(ADD_DATA, source, ref.getData());
+
+    // add data reference to cell
+    SSCellData cellData =
+      new SSCellData(id, this, ref, cmaps, source, type, checkErrors);
+    CellData.add(cellData);
+
+    if (!IsRemote) {
+      // SERVER: add data reference to display
+      if (HasMappings) VDisplay.addReference(ref, cmaps);
+
+      // add remote data reference to servers
+      synchronized (Servers) {
+        RemoteDataReferenceImpl remoteRef =
+          (RemoteDataReferenceImpl) cellData.getRemoteReference();
+        int len = Servers.size();
+        for (int i=0; i<len; i++) {
+          RemoteServerImpl rs = (RemoteServerImpl) Servers.elementAt(i);
+          rs.addDataReference(remoteRef);
+        }
+      }
+    }
+
+    return cellData;
+  }
+
+  /**
+   * override method from BasicSSCell
+   */
+  public synchronized void setMaps(ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    if (maps == null) return;
+
+    VisADException vexc = null;
+    RemoteException rexc = null;
+
+    if (IsRemote) {
+      // CLIENT: send new mappings to server
+      // sendMessage(SET_MAPS, DataUtility.convertMapsToString(maps), null);
+    }
+    else {
+      // SERVER: set up mappings
+
+      DataReference[] dr;
+      ConstantMap[][] cmaps;
+      synchronized (CellData) {
+        int len = CellData.size();
+        dr = new DataReference[len];
+        cmaps = new ConstantMap[len][];
+        for (int i=0; i<len; i++) {
+          SSCellData cellData = (SSCellData) CellData.elementAt(i);
+          dr[i] = cellData.getReference();
+          cmaps[i] = cellData.getConstantMaps();
+        }
+      }
+      String save = getPartialSaveString();
+      VDisplay.disableAction();
+      clearMaps();
+      for (int i=0; i<maps.length; i++) {
+        if (maps[i] != null) {
+          try {
+            VDisplay.addMap(maps[i]);
+          }
+          catch (VisADException exc) {
+            vexc = exc;
+          }
+          catch (RemoteException exc) {
+            rexc = exc;
+          }
+        }
+      }
+      for (int i=0; i<dr.length; i++) {
+        // determine if ImageRendererJ3D can be used
+        boolean ok = false;
+        Data data = dr[i].getData();
+        if (data == null) {
+        }
+        else if (Possible3D) {
+          MathType type = data.getType();
+          try {
+            ok = ImageRendererJ3D.isRendererUsable(type, maps);
+          }
+          catch (VisADException exc) {
+            if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+          }
+        }
+        // add reference
+        if (ok && Dim != JAVA2D_2D) {
+          VDisplay.addReferences(new ImageRendererJ3D(), dr[i], cmaps[i]);
+        }
+        else {
+          if (remote_ref == null) {
+            VDisplay.addReference(dr[i], cmaps[i]);
+          }
+          else {
+            RemoteVDisplay.addReference(remote_ref, cmaps[i]);
+          }
+        }
+      }
+
+      VDisplay.enableAction();
+      setPartialSaveString(save, true);
+    }
+    HasMappings = true;
+    if (vexc != null) throw vexc;
+    if (rexc != null) throw rexc;
+  }
+
+  /**
+   * override method from BasicSSCell
+   */
+  public synchronized boolean constructDisplay() {
+    boolean success = true;
+    DisplayImpl newDisplay = VDisplay;
+    RemoteDisplay rmtDisplay = RemoteVDisplay;
+    if (IsSlave) {
+      // SLAVE: construct dummy 2-D display
+      try {
+        newDisplay = new DisplayImplJ2D("DUMMY");
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    else if (!CanDo3D && Dim != JAVA2D_2D) {
+      // dimension requires Java3D, but Java3D is disabled for this JVM
+      success = false;
+    }
+    else {
+      // construct display of the proper dimension
+      try {
+        if (IsRemote) {
+          // CLIENT: construct new display from server's remote copy
+          if (Dim == JAVA3D_3D) newDisplay = new DisplayImplJ3D(rmtDisplay);
+          else if (Dim == JAVA2D_2D) {
+            newDisplay = new DisplayImplJ2D(rmtDisplay);
+          }
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(rmtDisplay, tdr);
+          }
+        }
+        else {
+          // SERVER: construct new display and make a remote copy
+          if (Dim == JAVA3D_3D) {
+            UserDisplayRendererJ3D udr =
+              new UserDisplayRendererJ3D(agent, 100000);
+            newDisplay = new DisplayImplJ3D(Name, udr);
+          }
+          else if (Dim == JAVA2D_2D) newDisplay = new DisplayImplJ2D(Name);
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(Name, tdr);
+          }
+          rmtDisplay = new RemoteDisplayImpl(newDisplay);
+        }
+      }
+      catch (NoClassDefFoundError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (UnsatisfiedLinkError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (Exception exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    if (success) {
+      if (VDisplay != null) {
+        try {
+          VDisplay.destroy();
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+      VDisplay = newDisplay;
+      RemoteVDisplay = rmtDisplay;
+    }
+    return success;
+  }
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException, IOException {
+
+    int node_divide = 2;
+    int number_of_nodes = node_divide * node_divide;
+
+    RemoteNodeField[] node_wrfs = new RemoteNodeField[number_of_nodes];
+  
+    if (args == null || args.length < 1) {
+      System.out.println("usage: 'java visad.cluster.TestProxyCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestProxyCluster 0 " +
+                         "node1 node2 node3 node4' for host");
+      System.out.println("       'java visad.cluster.TestProxyCluster 5 " +
+                         "host' for user");
+      System.exit(0);
+    }
+    int pid = -1;
+    try {
+      pid = Integer.parseInt(args[0]);
+    }
+    catch (NumberFormatException e) {
+      System.out.println("usage: 'java visad.cluster.TestProxyCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestProxyCluster 0 " +
+                         "node1 node2 node3 node4' for host");
+      System.out.println("       'java visad.cluster.TestProxyCluster 5 " +
+                         "host' for user");
+      System.exit(0);
+    }
+    if (pid < 0 || pid > number_of_nodes + 1) {
+      System.out.println("usage: 'java visad.cluster.TestProxyCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestProxyCluster 0 " +
+                         "node1 node2 node3 node4' for host");
+      System.out.println("       'java visad.cluster.TestProxyCluster 5 " +
+                         "host' for user");
+      System.exit(0);
+    }
+    if (pid > 0 && pid <= number_of_nodes && args.length < 2) {
+      System.out.println("usage: 'java visad.cluster.TestProxyCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestProxyCluster 0 " +
+                         "node1 node2 node3 node4' for host");
+      System.out.println("       'java visad.cluster.TestProxyCluster 5 " +
+                         "host' for user");
+      System.exit(0);
+    }
+
+    boolean host = (pid == 0);
+    boolean user = (pid == (number_of_nodes+1));
+
+    if (!host && !user) {
+
+      Plain plain = new Plain();
+      FieldImpl wrf = (FieldImpl) plain.open(args[1]);
+      if (wrf == null) {
+        System.out.println("cannot open " + args[1]);
+        return;
+      }
+    
+      FunctionType wrf_type = (FunctionType) wrf.getType();
+System.out.println("wrf_type = " + wrf_type);
+
+      Set time_set = wrf.getDomainSet();
+      int time_length = time_set.getLength();
+      RealTupleType wrf_domain_type = wrf_type.getDomain();
+      RealType time_type = (RealType) wrf_domain_type.getComponent(0);
+      TupleType wrf_range_type = (TupleType) wrf_type.getRange();
+      FunctionType wrf_non_stag_type =
+        (FunctionType) wrf_range_type.getComponent(3);
+      RealTupleType wrf_non_stag_domain_type = wrf_non_stag_type.getDomain();
+      RealTupleType wrf_non_stag_Range_type =
+        (RealTupleType) wrf_non_stag_type.getRange();
+      FunctionType wrf_surface_type =
+        (FunctionType) wrf_range_type.getComponent(5);
+
+      // construct navigated WRF MathType and FieldImpl
+      // note SpatialEarth3DTuple = (Longitude, Latitude, Altitude)
+      RealTupleType nav_wrf_grid_domain_type = RealTupleType.SpatialEarth3DTuple;
+      FunctionType nav_wrf_grid_type =
+        new FunctionType(nav_wrf_grid_domain_type, wrf_non_stag_Range_type);
+      FunctionType nav_wrf_type = new FunctionType(time_type, nav_wrf_grid_type);
+      FieldImpl nav_wrf = new FieldImpl(nav_wrf_type, time_set);
+      Gridded3DSet nav_grid_set = null;
+      int nrows = 0, ncols = 0, nlevs = 0, grid_size = 0;
+      int nvert = 0;
+      for (int i=0; i<time_length; i++) {
+        Tuple wrf_step = (Tuple) wrf.getSample(i);
+        FlatField wrf_non_stag = (FlatField) wrf_step.getComponent(3);
+        FlatField wrf_surface = (FlatField) wrf_step.getComponent(5);
+        // if (nav_grid_set == null) {
+          FlatField wrf_vert = (FlatField) wrf_step.getComponent(6);
+          float[][] wrf_vert_samples = wrf_vert.getFloats(false);
+/*
+          nvert = wrf_vert_samples[0].length;
+          float[] height = new float[nvert];
+          for (int j=0; j<nvert; j++) {
+            height[j] = 0.5f * (wrf_vert_samples[0][j] + wrf_vert_samples[1][j]);
+// System.out.println("height["+ j + "] = " +  height[j]);
+          }
+*/
+          Gridded3DSet wrf_grid_set = (Gridded3DSet) wrf_non_stag.getDomainSet();
+          ncols = wrf_grid_set.getLength(0);
+          nrows = wrf_grid_set.getLength(1);
+          nlevs = wrf_grid_set.getLength(2);
+          float[][] wrf_surface_samples = wrf_surface.getFloats(false);
+          float[] lats = wrf_surface_samples[9];
+          float[] lons = wrf_surface_samples[10];
+          if (lats.length != nrows * ncols) {
+            throw new ClusterException("lats.length = " + lats.length +
+               " != " + nrows + " * " + ncols);
+          }
+/*
+          if (nvert != nlevs) {
+            throw new ClusterException("nvert = " + nvert +
+               " != " + nlevs + " = nlevs");
+          }
+*/
+/*
+int k = 0;
+for (int r=0; r<nrows; r++) {
+  for (int c=0; c<ncols; c++) {
+    System.out.println("row = " + r + " col = " + c + " lat = " +
+                       lats[k] + " lon = " +lons[k]);
+    k++;
+  }
+}
+*/
+          float[][] range_values = wrf_non_stag.getFloats(false);
+          grid_size = nrows * ncols * nlevs;
+          float[][] nav_wrf_samples = new float[3][grid_size];
+          nav_wrf_samples[0] = new float[grid_size];
+          nav_wrf_samples[1] = new float[grid_size];
+          for (int lev=0; lev<nlevs; lev++) {
+            int base = lev * nrows * ncols;
+            for (int rc=0; rc<nrows*ncols; rc++) {
+              nav_wrf_samples[0][base+rc] = lons[rc];
+              nav_wrf_samples[1][base+rc] = lats[rc];
+              // nav_wrf_samples[2][base+rc] = lev;
+            }
+          }
+          nav_wrf_samples[2] = range_values[11]; // Z
+          nav_grid_set =
+            new Gridded3DSet(nav_wrf_grid_domain_type, nav_wrf_samples,
+                             ncols, nrows, nlevs, null, null, null,
+                             false,  // no copy
+                             false); // no consistency test
+        // } // end if (nav_grid_set == null)
+        FlatField nav_wrf_grid = new FlatField(nav_wrf_grid_type, nav_grid_set);
+        nav_wrf_grid.setSamples(range_values, false);
+        nav_wrf.setSample(i, nav_wrf_grid);
+System.out.println("done with time step " + i);
+      } // end for (int i=0; i<time_length; i++)
+
+System.out.println("pid = " + pid);
+
+      RemoteNodeFieldImpl node_data = new RemoteNodeFieldImpl(nav_wrf);
+
+      int kk = pid - 1;
+System.out.println("kk = " + kk);
+      String url = "///TestProxyCluster" + kk;
+      try {
+        Naming.rebind(url, node_data);
+      }
+      catch (Exception e) {
+        System.out.println("rebind " + kk + " " + e);
+        return;
+      }
+      // just so app doesn't exit
+      CellImpl cell = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+        }
+      };
+      System.out.println("data ready as " + nav_wrf_type);
+      return;
+    } // end if (!host && !user)
+
+    if (host) {
+      if (args.length != 5) {
+        System.out.println("usage: 'java visad.cluster.TestProxyCluster n file'");
+        System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+        System.out.println("       'java visad.cluster.TestProxyCluster 0 " +
+                           "node1 node2 node3 node4' for host");
+        System.out.println("       'java visad.cluster.TestProxyCluster 5 " +
+                           "host' for user");
+        System.exit(0);
+      }
+      // this is all host code
+      for (int k=0; k<number_of_nodes; k++) {
+        String url = "//" + args[1+k] + "/TestProxyCluster" + k;
+        try {
+          node_wrfs[k] = (RemoteNodeField) Naming.lookup(url);
+        }
+        catch (Exception e) {
+          System.out.println("lookup " + k + " " + e);
+          return;
+        }
+      }
+  
+      FunctionType nav_wrf_type = (FunctionType) node_wrfs[0].getType();
+      Set time_set = node_wrfs[0].getDomainSet();
+  
+      RemoteClientFieldImpl host_wrf =
+        new RemoteClientFieldImpl(nav_wrf_type, time_set);
+  
+      RemoteClusterData[] table =
+        new RemoteClusterData[number_of_nodes + 1];
+      for (int i=0; i<number_of_nodes; i++) {
+        table[i] = node_wrfs[i];
+      }
+      table[number_of_nodes] = host_wrf;
+  
+      for (int i=0; i<table.length; i++) {
+        table[i].setupClusterData(null, table);
+      }
+
+      RemoteProxyAgentImpl rpai = new RemoteProxyAgentImpl(host_wrf);
+
+      String url = "///TestProxyCluster_Proxy";
+      try {
+        Naming.rebind(url, rpai);
+      }
+      catch (Exception e) {
+        System.out.println("rebind proxy " + e);
+        return;
+      }
+      // just so app doesn't exit
+      CellImpl cell = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+        }
+      };
+      System.out.println("data ready as " + nav_wrf_type);
+    } // end if (host)
+
+    if (user) {
+      if (args.length != 2) {
+        System.out.println("usage: 'java visad.cluster.TestProxyCluster n file'");
+        System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+        System.out.println("       'java visad.cluster.TestProxyCluster 0 " +
+                           "node1 node2 node3 node4' for host");
+        System.out.println("       'java visad.cluster.TestProxyCluster 5 " +
+                           "host' for user");
+        System.exit(0);
+      }
+      String url = "//" + args[1] + "/TestProxyCluster_Proxy";
+
+      RemoteProxyAgent rpa = null;
+      try {
+        rpa = (RemoteProxyAgent) Naming.lookup(url);
+      }
+      catch (Exception e) {
+        System.out.println("lookup proxy " + e);
+        return;
+      }
+
+      UserDummyDataImpl user_wrf =
+        new UserDummyDataImpl(rpa.getRemoteClientData());
+
+      // create JFrame (i.e., a window) for display and slider
+      JFrame frame = new JFrame("test UserRendererJ3D");
+      frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+  
+      TestProxyCluster ss =
+        new TestProxyCluster("TestProxyCluster", frame, rpa);
+  
+      ss.addData(user_wrf);
+  
+      // create JPanel in JFrame
+      JPanel panel = new JPanel();
+      panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+      frame.getContentPane().add(panel);
+  
+      JPanel bpanel = new JPanel();
+      bpanel.setLayout(new BoxLayout(bpanel, BoxLayout.X_AXIS));
+  
+      JButton maps = new JButton("Maps");
+      maps.addActionListener(ss);
+      maps.setActionCommand("map");
+      bpanel.add(maps);
+  
+      JButton show = new JButton("Widgets");
+      show.addActionListener(ss);
+      show.setActionCommand("widgets");
+      bpanel.add(show);
+  
+      JButton res1 = new JButton("Res 1");
+      res1.addActionListener(ss);
+      res1.setActionCommand("res1");
+      bpanel.add(res1);
+  
+      JButton res2 = new JButton("Res 2");
+      res2.addActionListener(ss);
+      res2.setActionCommand("res2");
+      bpanel.add(res2);
+  
+      JButton res3 = new JButton("Res 3");
+      res3.addActionListener(ss);
+      res3.setActionCommand("res3");
+      bpanel.add(res3);
+  
+      JButton res4 = new JButton("Res 4");
+      res4.addActionListener(ss);
+      res4.setActionCommand("res4");
+      bpanel.add(res4);
+  
+      panel.add(ss);
+      panel.add(bpanel);
+  
+      // set size of JFrame and make it visible
+      frame.setSize(600, 600);
+      frame.setVisible(true);
+    } // end if (user)
+
+  }
+
+  int[] res = {1, 1, 1, 1};
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("map")) {
+      hideWidgetFrame();
+      addMapDialog();
+    }
+    else if (cmd.equals("widgets")) {
+      showWidgetFrame();
+    }
+    else if (cmd.equals("res1")) {
+      flipRes(0);
+    }
+    else if (cmd.equals("res2")) {
+      flipRes(1);
+    }
+    else if (cmd.equals("res3")) {
+      flipRes(2);
+    }
+    else if (cmd.equals("res4")) {
+      flipRes(3);
+    }
+  }
+
+  private void flipRes(int k) {
+    res[k] = 5 - res[k];
+    DisplayImpl display = getDisplay();
+    Vector renderers = display.getRendererVector();
+    for (int i=0; i<renderers.size(); i++) {
+      DataRenderer renderer = (DataRenderer) renderers.elementAt(i);
+      if (renderer instanceof ClientRendererJ3D) {
+        ((ClientRendererJ3D) renderer).setResolutions(res);
+      }
+    }
+    display.reDisplayAll();
+  }
+
+}
+
+/*
+java visad.jmet.DumpType wrfout_01_000000_0000
+
+VisAD Data analysis
+  FieldImpl of length = 5
+(Time -> (((west_east_stag, south_north, bottom_top) -> RHO_U),  ****0****
+          ((west_east, south_north_stag, bottom_top) -> RHO_V),  ****1****
+          ((west_east, south_north, bottom_top_stag) -> RW),     ****2****
+          ((west_east, south_north, bottom_top) -> (RRP, RR, TKE,
+                                                    RTP, TP, QVAPOR,
+                                                    QCLOUD, QRAIN, PP,
+                                                    RTB, RRB, Z, PB)),  ****3****
+          ((west_east, south_north, soil_layers) -> TSLB),       ****4****
+          ((west_east, south_north) -> (DZETADZ,  **0** 
+ MAPFAC_M,  **1**
+ HGT,       **2** 
+ TSK,       **3**
+ RAINC,     **4** 
+ RAINNC,    **5**
+ RAINCV,    **6**   
+ GSW,       **7**   
+ GLW,       **8**   
+ XLAT,      **9**                  ****
+ XLONG,     **10**                 ****
+ LU_INDEX,  **11**
+ TMN,       **12**
+ XLAND,     **13**
+ HFX,       **14**
+ QFX,       **15**
+ SNOWC)),   **16**                                ****5****
+          (bottom_top -> (FZM, FZP)),             ****6****
+          (ext_scalar -> (ZETATOP, ITIMESTEP))))  ****7****
+
+
+wrfout_01_000000_0000
+west_east: 0 - 20
+south_north: 0 - 17
+bottom_top: 0 - 22
+
+
+
+wrfout_01_000000_0001
+west_east: 0 - 20
+south_north: 0 - 15
+bottom_top: 0 - 22
+*/
+
diff --git a/visad/cluster/TestROMS.java b/visad/cluster/TestROMS.java
new file mode 100644
index 0000000..d50d6f8
--- /dev/null
+++ b/visad/cluster/TestROMS.java
@@ -0,0 +1,288 @@
+//
+// TestROMS.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.util.*;
+import visad.data.netcdf.Plain;
+import visad.data.mcidas.*;
+
+import java.rmi.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.IOException;
+
+/**
+cd /home1/billh/mead
+java -mx128m visad.cluster.TestROMS NE_Pacific_2.nc roms_his_NEP2.nc
+*/
+public class TestROMS {
+
+  private RemoteDataReferenceImpl remote_ref = null;
+
+  public TestROMS(String[] args)
+         throws VisADException, RemoteException, IOException {
+
+    if (args == null || args.length < 2) {
+      System.out.println(
+        "usage: 'java visad.cluster.TestROMS grid_file history_file'");
+    }
+
+    // read grid and history files
+    Plain plain = new Plain();
+    Tuple grid_file = (Tuple) plain.open(args[0]);
+    if (grid_file == null) {
+      System.out.println("cannot open " + args[0]);
+      return;
+    }
+    Tuple history_file = (Tuple) plain.open(args[1]);
+    if (history_file == null) {
+      System.out.println("cannot open " + args[1]);
+      return;
+    }
+
+    // extract lat-lon arrays from grid file
+    FlatField rho_ff = (FlatField) grid_file.getComponent(16);
+    float[][] rho_floats = rho_ff.getFloats(false);
+    float[][] rho_latlon = {rho_floats[8], rho_floats[9]};
+
+    int n = rho_latlon[1].length;
+/* not needed: adjustLongitude() does this implicitly
+    for (int i=0 ;i<n; i++) {
+      if (rho_latlon[1][i] < 0.0f) rho_latlon[1][i] += 360.0f;
+    }
+*/
+
+    int[] rho_lengths = ((GriddedSet) rho_ff.getDomainSet()).getLengths();
+    rho_ff = null;
+    rho_floats = null;
+    FlatField psi_ff = (FlatField) grid_file.getComponent(17);
+    float[][] psi_floats = psi_ff.getFloats(false);
+    float[][] psi_latlon = {psi_floats[2], psi_floats[3]};
+    int[] psi_lengths = ((GriddedSet) psi_ff.getDomainSet()).getLengths();
+    psi_ff = null;
+    psi_floats = null;
+    FlatField u_ff = (FlatField) grid_file.getComponent(18);
+    float[][] u_floats = u_ff.getFloats(false);
+    float[][] u_latlon = {u_floats[2], u_floats[3]};
+    int[] u_lengths = ((GriddedSet) u_ff.getDomainSet()).getLengths();
+    u_ff = null;
+    u_floats = null;
+    FlatField v_ff = (FlatField) grid_file.getComponent(19);
+    float[][] v_floats = v_ff.getFloats(false);
+    float[][] v_latlon = {v_floats[2], v_floats[3]};
+    int[] v_lengths = ((GriddedSet) v_ff.getDomainSet()).getLengths();
+    v_ff = null;
+    v_floats = null;
+    grid_file = null;
+
+    // extract history data
+    FieldImpl t1 = (FieldImpl) history_file.getComponent(31);
+    Set time_set = t1.getDomainSet();
+    int ntimes = time_set.getLength();
+System.out.println("ntimes = " + ntimes);
+    FunctionType t1type = (FunctionType) t1.getType();
+    RealType time = (RealType) (t1type.getDomain()).getComponent(0);
+    TupleType t1range = (TupleType) t1type.getRange();
+
+    // FunctionType grid_type = (FunctionType) t1range.getComponent(6);
+    // grid_type = ((xi_rho, eta_rho, s_rho) -> (w, temp, salt, rho))
+
+    FunctionType grid_type = (FunctionType) t1range.getComponent(1);
+    // grid_type = ((xi_rho, eta_rho) -> zeta)
+    RealType zeta = (RealType) grid_type.getRange();
+    RealTupleType grid_domain = grid_type.getDomain();
+    RealType xi_rho = (RealType) grid_domain.getComponent(0);
+    RealType eta_rho = (RealType) grid_domain.getComponent(1);
+
+    RealTupleType latlon_type = RealTupleType.LatitudeLongitudeTuple;
+    FunctionType grid_latlon_type =
+      new FunctionType(latlon_type, zeta);
+
+    FunctionType history_type = new FunctionType(time, grid_latlon_type);
+    // history_type = (Time -> ((Latitude, Longitude) -> zeta))
+    // FunctionType history_type = new FunctionType(time, grid_type);
+    // history_type = (Time -> ((xi_rho, eta_rho) -> zeta))
+    Real[] ocean_times = new Real[ntimes];
+    double[][] times = new double[1][ntimes];
+    for (int itime=0; itime<ntimes; itime++) {
+      Tuple t1tuple = (Tuple) t1.getSample(itime);
+      ocean_times[itime] = (Real) t1tuple.getComponent(0);
+      times[0][itime] = ocean_times[itime].getValue();
+    }
+    Unit[] tunits = {ocean_times[0].getUnit()};
+    Gridded1DDoubleSet dset =
+      new Gridded1DDoubleSet(time, times, ntimes, null,
+                             tunits, null, false);
+    FieldImpl history = new FieldImpl(history_type, dset);
+
+    Gridded2DSet grid_set =
+      new Gridded2DSet(latlon_type, rho_latlon,
+                       rho_lengths[0], rho_lengths[1], null, null,
+                       null, false, false);
+
+    for (int itime=0; itime<ntimes; itime++) {
+      Tuple t1tuple = (Tuple) t1.getSample(itime);
+      Real ocean_time = (Real) t1tuple.getComponent(0);
+      FlatField grid = (FlatField) t1tuple.getComponent(1);
+      FlatField grid_latlon = new FlatField(grid_latlon_type, grid_set);
+      grid_latlon.setSamples(grid.getFloats(false), false);
+      history.setSample(itime, grid_latlon);
+    }
+
+    // create the display
+    DisplayImpl display = new DisplayImplJ3D("TestROMS");
+    ScalarMap tmap = new ScalarMap(time, Display.Animation);
+    display.addMap(tmap);
+    ScalarMap xmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    display.addMap(xmap);
+    xmap.setRange(160.0, 245.0);
+    ScalarMap ymap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    display.addMap(ymap);
+    ymap.setRange(20.0, 80.0);
+    ScalarMap zmap = new ScalarMap(zeta, Display.ZAxis);
+    display.addMap(zmap);
+    ScalarMap rgbmap = new ScalarMap(zeta, Display.RGB);
+    display.addMap(rgbmap);
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    mode.setTextureEnable(false);
+    mode.setProjectionPolicy(DisplayImplJ3D.PARALLEL_PROJECTION);
+
+    BaseMapAdapter baseMapAdapter = new BaseMapAdapter("OUTLSUPW");
+    // baseMapAdapter.setLatLonLimits(0.0f, 90.0f, -180.0f, 180.0f);
+    Data map = baseMapAdapter.getData();
+    DataReference maplinesRef = new DataReferenceImpl("MapLines");
+    maplinesRef.setData(map);
+    ConstantMap[] maplinesConstantMap = new ConstantMap[]
+      {new ConstantMap(0.4, Display.ZAxis)};
+    display.addReference(maplinesRef, maplinesConstantMap);
+
+    DataReference dr = new DataReferenceImpl("history");
+    dr.setData(history);
+    display.addReference(dr);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("TestROMS");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    panel.add(new AnimationWidget(tmap, 1000));
+
+    // set size of JFrame and make it visible
+    frame.setSize(600, 900);
+    frame.setVisible(true);
+  }
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException, IOException {
+    TestROMS roms = new TestROMS(args);
+  }
+
+}
+
+/*
+java visad.jmet.DumpType NE_Pacific_2.nc
+NE_Pacific_2.nc:
+0  (xl,
+1   el,
+2   (two -> PLAT),
+3   PLONG,
+4   ROTA,
+5   P1,
+6   P2,
+7   P3,
+8   P4,
+9   XOFF,
+10  YOFF,
+11  depthmin,
+12  depthmax,
+13  f0,
+14  dfdy,
+15  ((xi_rho, eta_rho, bath) -> hraw),
+16  ((xi_rho, eta_rho) -> (h, f, pm, pn, dndx, dmde, x_rho, y_rho, lat_rho, lon_rho, mask_rho, Angle)),
+17  ((xi_psi, eta_psi) -> (x_psi, y_psi, lat_psi, lon_psi, mask_psi)),
+18  ((xi_u, eta_u) -> (x_u, y_u, lat_u, lon_u, mask_u)),
+19  ((xi_v, eta_v) -> (x_v, y_v, lat_v, lon_v, mask_v)))
+. . .
+
+
+java visad.jmet.DumpType roms_his_NEP2.nc
+roms_his_NEP2.nc: 
+0  (ntimes,
+1   ndtfast,
+2   dt,
+3   dtfast, 
+4   dstart,
+5   nhis,
+6   nrst,
+7   ntsavg,
+8   navg,
+9   (tracer -> (tnu2, Akt_bak, Tnudg)),
+10  visc2,
+11  Akv_bak,
+12  rdrg,
+13  rdrg2,
+14  Zob,
+15  Zos,
+16  Znudg,
+17  M2nudg,
+18  M3nudg,
+19  (boundary -> (FSobc_in, FSobc_out, M2obc_in, M2obc_out, M3obc_in, M3obc_out)),
+20  ((boundary, tracer) -> (Tobc_in, Tobc_out)),
+21  rho0,
+22  gamma2,
+23  xl,
+24  el,
+25  theta_s,
+26  theta_b,
+27  Tcline,
+28  hc,
+29  (s_rho -> (sc_r, Cs_r, Lev)), 
+30  (s_w -> (sc_w, Cs_w)),
+31  (Time -> (ocean_time,
+  1           ((xi_rho, eta_rho) -> zeta), 
+  2           ((xi_u, eta_u) -> (ubar, sustr)),
+  3           ((xi_v, eta_v) -> (vbar, svstr)),
+  4           ((xi_u, eta_u, s_rho) -> u),
+  5           ((xi_v, eta_v, s_rho) -> v),
+  6           ((xi_rho, eta_rho, s_rho) -> (w, temp, salt, rho)),
+  7           ((xi_rho, eta_rho, s_w) -> omega))))
+
+*/
+
diff --git a/visad/cluster/TestSSCluster.java b/visad/cluster/TestSSCluster.java
new file mode 100644
index 0000000..802dce4
--- /dev/null
+++ b/visad/cluster/TestSSCluster.java
@@ -0,0 +1,667 @@
+//
+// TestSSCluster.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.java2d.*;
+import visad.ss.*;
+import visad.bom.*;
+import visad.data.vis5d.Vis5DForm;
+
+import java.util.Vector;
+import java.rmi.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.IOException;
+
+/**
+   TestSSCluster is the class for testing the visad.cluster package.<P>
+<PRE>
+If you want to run the demo yourself, download the latest VisAD,
+and also download the v5d file:
+
+  ftp://www.ssec.wisc.edu/pub/visad-2.0/QLQ.v5d
+
+Then run the four commands:
+
+  java visad.cluster.TestSSCluster 1 QLQ.v5d
+  java visad.cluster.TestSSCluster 2 QLQ.v5d
+  java visad.cluster.TestSSCluster 3 QLQ.v5d
+  java visad.cluster.TestSSCluster 4 QLQ.v5d
+
+These create the four cluster nodes. Wait for all four to print
+both messages:
+
+  v5d_type = (Time -> (((row, col, Height) -> QL), ((row, col, Height) -> Q)))
+  data ready as (Time -> ((row, col, Height) -> (QL, Q)))
+
+Then run a fifth command for the client:
+
+  java visad.cluster.TestSSCluster 0 QLQ.v5d
+
+As soon as the window pops up, you can click the "Widgets" button
+and when the widgets have initialized, slide the QL (cloud water)
+slider over to any value between 0.5 and 1.0. When the "please
+wait ..." message disappears in the 3-D window, click on the "Go"
+button to animate. Change animation rate by entering a new number
+of milliseonds per frame in the text box.
+
+Click on any combination of the "Res 1", "Res 2", "Res 3" and
+"Res 4" buttons to toggle between high and low resolution in each
+quadrant. Its interesting to click "Res 1", "Res 2" and "Res 4"
+but not "Res 3", which gives a sense of how this will be used
+in practice: looking at data from all but one node at low
+resolution. Of course, with a data set large enough for this to
+be necessary, the low resolution will not look so blocky,
+
+You can also click on the "Maps" button to get the SpreadSheet
+user interface for setting display mappings. After you click
+"Done", you'll need to click "Widgets" again to see the widgets
+that correspond to your new mappings.
+
+The display clearly shows the partition between quadrants. We
+could get rid of these breaks, but at least for the demo its
+nice to see where the spatial paritions are.
+
+Note these five JVMs running on one machine will eat a lot of
+memory and cycles.
+</PRE>
+*/
+public class TestSSCluster extends FancySSCell implements ActionListener {
+
+  private RemoteDataReferenceImpl remote_ref = null;
+
+  public TestSSCluster(String name, Frame parent)
+         throws VisADException, RemoteException {
+    super(name, parent);
+  }
+
+
+  /**
+   * override method from BasicSSCell
+   */
+  protected String addData(int id, Data data, ConstantMap[] cmaps,
+    String source, int type, boolean notify)
+    throws VisADException, RemoteException
+  {
+    // add Data object to cell
+    DataReferenceImpl ref = new DataReferenceImpl(Name);
+
+    // new
+    if (data instanceof RemoteData) {
+      remote_ref = new RemoteDataReferenceImpl(ref);
+      remote_ref.setData(data);
+    }
+    else {
+        ref.setData(data);
+    }
+
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = addReferenceImpl(id, ref, cmaps, source, type, notify, true);
+    }
+    return cellData.getVariableName();
+  }
+
+  /**
+   * override method from BasicSSCell
+   */
+  protected SSCellData addReferenceImpl(int id, DataReferenceImpl ref,
+    ConstantMap[] cmaps, String source, int type, boolean notify,
+    boolean checkErrors) throws VisADException, RemoteException
+  {
+    // ensure that id is valid
+    if (id == 0) id = getFirstFreeId();
+
+    // ensure that ref is valid
+    if (ref == null) ref = new DataReferenceImpl(Name);
+
+    // notify linked cells of data addition (ADD_DATA message must come first)
+    // if (notify) sendMessage(ADD_DATA, source, ref.getData());
+
+    // add data reference to cell
+    SSCellData cellData =
+      new SSCellData(id, this, ref, cmaps, source, type, checkErrors);
+    CellData.add(cellData);
+
+    if (!IsRemote) {
+      // SERVER: add data reference to display
+      if (HasMappings) VDisplay.addReference(ref, cmaps);
+
+      // add remote data reference to servers
+      synchronized (Servers) {
+        RemoteDataReferenceImpl remoteRef =
+          (RemoteDataReferenceImpl) cellData.getRemoteReference();
+        int len = Servers.size();
+        for (int i=0; i<len; i++) {
+          RemoteServerImpl rs = (RemoteServerImpl) Servers.elementAt(i);
+          rs.addDataReference(remoteRef);
+        }
+      }
+    }
+
+    return cellData;
+  }
+
+  /**
+   * override method from BasicSSCell
+   */
+  public synchronized void setMaps(ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    if (maps == null) return;
+
+    VisADException vexc = null;
+    RemoteException rexc = null;
+
+    if (IsRemote) {
+      // CLIENT: send new mappings to server
+      // sendMessage(SET_MAPS, DataUtility.convertMapsToString(maps), null);
+    }
+    else {
+      // SERVER: set up mappings
+
+      DataReference[] dr;
+      ConstantMap[][] cmaps;
+      synchronized (CellData) {
+        int len = CellData.size();
+        dr = new DataReference[len];
+        cmaps = new ConstantMap[len][];
+        for (int i=0; i<len; i++) {
+          SSCellData cellData = (SSCellData) CellData.elementAt(i);
+          dr[i] = cellData.getReference();
+          cmaps[i] = cellData.getConstantMaps();
+        }
+      }
+      String save = getPartialSaveString();
+      VDisplay.disableAction();
+      clearMaps();
+      for (int i=0; i<maps.length; i++) {
+        if (maps[i] != null) {
+          try {
+            VDisplay.addMap(maps[i]);
+          }
+          catch (VisADException exc) {
+            vexc = exc;
+          }
+          catch (RemoteException exc) {
+            rexc = exc;
+          }
+        }
+      }
+      for (int i=0; i<dr.length; i++) {
+        // determine if ImageRendererJ3D can be used
+        boolean ok = false;
+        Data data = dr[i].getData();
+        if (data == null) {
+        }
+        else if (Possible3D) {
+          MathType type = data.getType();
+          try {
+            ok = ImageRendererJ3D.isRendererUsable(type, maps);
+          }
+          catch (VisADException exc) {
+            if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+          }
+        }
+        // add reference
+        if (ok && Dim != JAVA2D_2D) {
+          VDisplay.addReferences(new ImageRendererJ3D(), dr[i], cmaps[i]);
+        }
+        else {
+          if (remote_ref == null) {
+            VDisplay.addReference(dr[i], cmaps[i]);
+          }
+          else {
+            RemoteVDisplay.addReference(remote_ref, cmaps[i]);
+          }
+        }
+      }
+
+      VDisplay.enableAction();
+      setPartialSaveString(save, true);
+    }
+    HasMappings = true;
+    if (vexc != null) throw vexc;
+    if (rexc != null) throw rexc;
+  }
+
+
+
+  /**
+   * override method from BasicSSCell
+   */
+  public synchronized boolean constructDisplay() {
+    boolean success = true;
+    DisplayImpl newDisplay = VDisplay;
+    RemoteDisplay rmtDisplay = RemoteVDisplay;
+    if (IsSlave) {
+      // SLAVE: construct dummy 2-D display
+      try {
+        newDisplay = new DisplayImplJ2D("DUMMY");
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    else if (!CanDo3D && Dim != JAVA2D_2D) {
+      // dimension requires Java3D, but Java3D is disabled for this JVM
+      success = false;
+    }
+    else {
+      // construct display of the proper dimension
+      try {
+        if (IsRemote) {
+          // CLIENT: construct new display from server's remote copy
+          if (Dim == JAVA3D_3D) newDisplay = new DisplayImplJ3D(rmtDisplay);
+          else if (Dim == JAVA2D_2D) {
+            newDisplay = new DisplayImplJ2D(rmtDisplay);
+          }
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(rmtDisplay, tdr);
+          }
+        }
+        else {
+          // SERVER: construct new display and make a remote copy
+          if (Dim == JAVA3D_3D) {
+            ClientDisplayRendererJ3D cdr = new ClientDisplayRendererJ3D(100000);
+            newDisplay = new DisplayImplJ3D(Name, cdr);
+          }
+          else if (Dim == JAVA2D_2D) newDisplay = new DisplayImplJ2D(Name);
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(Name, tdr);
+          }
+          rmtDisplay = new RemoteDisplayImpl(newDisplay);
+        }
+      }
+      catch (NoClassDefFoundError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (UnsatisfiedLinkError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (Exception exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    if (success) {
+      if (VDisplay != null) {
+        try {
+          VDisplay.destroy();
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+      VDisplay = newDisplay;
+      RemoteVDisplay = rmtDisplay;
+    }
+    return success;
+  }
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException, IOException {
+
+    int node_divide = 2;
+    int number_of_nodes = node_divide * node_divide;
+
+    RemoteNodeField[] node_v5ds = new RemoteNodeField[number_of_nodes];
+  
+    if (args == null || args.length < 2) {
+      System.out.println("usage: 'java visad.cluster.TestSSCluster " +
+                         "n file.v5d'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+    int id = -1;
+    try {
+      id = Integer.parseInt(args[0]);
+    }
+    catch (NumberFormatException e) {
+      System.out.println("usage: 'java visad.cluster.TestSSCluster " +
+                         "n file.v5d'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+    if (id < 0 || id > number_of_nodes) {
+      System.out.println("usage: 'java visad.cluster.TestSSCluster " +
+                         "n file.v5d'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+
+    boolean client = (id == 0);
+
+    Vis5DForm v5d_form = new Vis5DForm();
+    FieldImpl v5d = (FieldImpl) v5d_form.open(args[1]);
+    if (v5d == null) {
+      System.out.println("cannot open " + args[1]);
+      return;
+    }
+  
+    FunctionType v5d_type = (FunctionType) v5d.getType();
+    Set time_set = v5d.getDomainSet();
+    int time_length = time_set.getLength();
+    MathType v5d_range_type = v5d_type.getRange();
+    DataImpl v5d_range0 = (DataImpl) v5d.getSample(0);
+    FunctionType grid_type = null;
+    FunctionType grid_type2 = null;
+    FlatField grid0 = null;
+    if (v5d_range_type instanceof FunctionType) {
+      grid_type = (FunctionType) v5d_range_type;
+      grid_type2 = null;
+      grid0 = (FlatField) v5d_range0;
+    }
+    else {
+      grid_type =
+        (FunctionType) ((TupleType) v5d_range_type).getComponent(0);
+      grid_type2 =
+        (FunctionType) ((TupleType) v5d_range_type).getComponent(1);
+      grid0 = (FlatField) ((Tuple) v5d_range0).getComponent(0);
+    }
+
+    Gridded3DSet domain_set = (Gridded3DSet) grid0.getDomainSet();
+    Gridded3DSet ps = makePS(domain_set, node_divide);
+
+    if (!client) {
+System.out.println("v5d_type = " + v5d_type);
+      RealTupleType domain_type = grid_type.getDomain();
+  
+      RealTupleType time_tuple = v5d_type.getDomain();
+      RealType time = (RealType) time_tuple.getComponent(0);
+      RealType x = (RealType) domain_type.getComponent(0);
+      RealType y = (RealType) domain_type.getComponent(1);
+      RealType z = (RealType) domain_type.getComponent(2);
+      RealType val = (RealType) grid_type.getRange();
+      RealType val2 =
+        (grid_type2 == null) ? null : (RealType) grid_type2.getRange();
+  
+  
+      float[][] samples = domain_set.getSamples(false);
+      int x_len = domain_set.getLength(0);
+      int y_len = domain_set.getLength(1);
+      int z_len = domain_set.getLength(2);
+      int len = domain_set.getLength();
+
+      Gridded3DSet[] subsets = new Gridded3DSet[number_of_nodes];
+  
+      int k = id - 1;
+
+      int ik = k % node_divide;
+      int ig = ik * x_len / node_divide;
+      int igp = (ik + 1) * x_len / node_divide;
+      if (ik == (node_divide - 1)) igp = x_len;
+      int jk = k / node_divide;
+      int jg = jk * y_len / node_divide;
+      int jgp = (jk + 1) * y_len / node_divide;
+      if (jk == (node_divide - 1)) jgp = y_len;
+      int sub_x_len = igp - ig;
+      int sub_y_len = jgp - jg;
+      int sub_len = sub_x_len * sub_y_len * z_len;
+// System.out.println("sub_len = " + sub_len + " out of len = " + len);
+      float[][] sub_samples = new float[3][sub_len];
+      for (int i=0; i<sub_x_len; i++) {
+        for (int j=0; j<sub_y_len; j++) {
+          for (int m=0; m<z_len; m++) {
+            int a = i + sub_x_len * (j + sub_y_len * m);
+            int b = (i + ig) + x_len * ((j + jg) + y_len * m);
+            sub_samples[0][a] = samples[0][b];
+            sub_samples[1][a] = samples[1][b];
+            sub_samples[2][a] = samples[2][b];
+          }
+        }
+      }
+
+      subsets[k] =
+        new Gridded3DSet(domain_type, sub_samples,
+                         sub_x_len, sub_y_len, z_len,
+                        domain_set.getCoordinateSystem(),
+                        domain_set.getSetUnits(), null);
+      RemoteNodeDataImpl[] subgrids = new RemoteNodeDataImpl[time_length];
+      for (int i=0; i<time_length; i++) {
+        DataImpl v5d_sample = (DataImpl) v5d.getSample(i);
+        if (v5d_sample instanceof FlatField) {
+          FlatField grid = (FlatField) v5d_sample;
+          FlatField subgrid = (FlatField) grid.resample(subsets[k]);
+          subgrids[i] = new RemoteNodePartitionedFieldImpl(subgrid);
+        }
+        else {
+          Tuple v5d_tuple = (Tuple) v5d_sample;
+          int ngrids = v5d_tuple.getDimension();
+          RemoteNodeDataImpl[] subsubgrids = new RemoteNodeDataImpl[ngrids];
+          FlatField[] combinegrids = new FlatField[ngrids];
+          for (int j=0; j<ngrids; j++) {
+            FlatField grid = (FlatField) v5d_tuple.getComponent(j);
+            combinegrids[j] = (FlatField) grid.resample(subsets[k]);
+            // subsubgrids[j] =
+            //   new RemoteNodePartitionedFieldImpl(combinegrids[j]);
+          }
+          // subgrids[i] = new RemoteNodeTupleImpl(subsubgrids);
+          FlatField subgrid = (FlatField) FieldImpl.combine(combinegrids);
+          subgrids[i] = new RemoteNodePartitionedFieldImpl(subgrid);
+        }
+      }
+      FunctionType new_v5d_type =
+        new FunctionType(time_tuple, subgrids[0].getType());
+      node_v5ds[k] = new RemoteNodeFieldImpl(new_v5d_type, time_set);
+      node_v5ds[k].setSamples(subgrids, false);
+
+
+      int kk = id - 1;
+      String url = "///TestVis5DCluster" + kk;
+      try {
+        Naming.rebind(url, node_v5ds[kk]);
+      }
+      catch (Exception e) {
+        System.out.println("rebind " + kk + " " + e);
+        return;
+      }
+      // just so app doesn't exit
+      DisplayImpl display = new DisplayImplJ2D("dummy");
+      System.out.println("data ready as " + new_v5d_type);
+      return;
+    } // end if (!client)
+
+    // this is all client code
+    for (int k=0; k<number_of_nodes; k++) {
+      // to test on a real cluster, change to something like:
+      // String ipname = "node" + k + ".ncar.ucar.edu";
+      // String url = "//" + ipname + "/TestVis5DCluster" + k;
+      // Then start up the four server commands on machines named
+      // node1.ncar.ucar.edu, node2.ncar.ucar.edu, node3.ncar.ucar.edu
+      // and node1.ncar.ucar.edu (or whatever).
+      String url = "///TestVis5DCluster" + k;
+      try {
+        node_v5ds[k] = (RemoteNodeField) Naming.lookup(url);
+      }
+      catch (Exception e) {
+        System.out.println("lookup " + k + " " + e);
+        return;
+      }
+    }
+
+    v5d_type = (FunctionType) node_v5ds[0].getType();
+System.out.println("data type = " + v5d_type);
+    time_set = node_v5ds[0].getDomainSet();
+
+    RemoteClientFieldImpl client_v5d =
+      new RemoteClientFieldImpl(v5d_type, time_set);
+
+    RemoteClusterData[] table =
+      new RemoteClusterData[number_of_nodes + 1];
+    for (int i=0; i<number_of_nodes; i++) {
+      table[i] = node_v5ds[i];
+    }
+    table[number_of_nodes] = client_v5d;
+
+    for (int i=0; i<table.length; i++) {
+      table[i].setupClusterData(ps, table);
+    }
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test ClientRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    TestSSCluster ss = new TestSSCluster("TestSSCluster", frame);
+
+    ss.addData(client_v5d);
+    // ss.addData(0, client_v5d, null, "", DIRECT_SOURCE, true);
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    frame.getContentPane().add(panel);
+
+    JPanel bpanel = new JPanel();
+    bpanel.setLayout(new BoxLayout(bpanel, BoxLayout.X_AXIS));
+
+    JButton maps = new JButton("Maps");
+    maps.addActionListener(ss);
+    maps.setActionCommand("map");
+    bpanel.add(maps);
+
+    JButton show = new JButton("Widgets");
+    show.addActionListener(ss);
+    show.setActionCommand("widgets");
+    bpanel.add(show);
+
+    JButton res1 = new JButton("Res 1");
+    res1.addActionListener(ss);
+    res1.setActionCommand("res1");
+    bpanel.add(res1);
+
+    JButton res2 = new JButton("Res 2");
+    res2.addActionListener(ss);
+    res2.setActionCommand("res2");
+    bpanel.add(res2);
+
+    JButton res3 = new JButton("Res 3");
+    res3.addActionListener(ss);
+    res3.setActionCommand("res3");
+    bpanel.add(res3);
+
+    JButton res4 = new JButton("Res 4");
+    res4.addActionListener(ss);
+    res4.setActionCommand("res4");
+    bpanel.add(res4);
+
+    panel.add(ss);
+    panel.add(bpanel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(600, 600);
+    frame.setVisible(true);
+  }
+
+  int[] res = {1, 1, 1, 1};
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("map")) {
+      hideWidgetFrame();
+      addMapDialog();
+    }
+    else if (cmd.equals("widgets")) {
+      showWidgetFrame();
+    }
+    else if (cmd.equals("res1")) {
+      flipRes(0);
+    }
+    else if (cmd.equals("res2")) {
+      flipRes(1);
+    }
+    else if (cmd.equals("res3")) {
+      flipRes(2);
+    }
+    else if (cmd.equals("res4")) {
+      flipRes(3);
+    }
+  }
+
+  private void flipRes(int k) {
+    res[k] = 5 - res[k];
+    DisplayImpl display = getDisplay();
+    Vector renderers = display.getRendererVector();
+    for (int i=0; i<renderers.size(); i++) {
+      DataRenderer renderer = (DataRenderer) renderers.elementAt(i);
+      if (renderer instanceof ClientRendererJ3D) {
+        ((ClientRendererJ3D) renderer).setResolutions(res);
+      }
+    }
+    display.reDisplayAll();
+  }
+
+  private static Gridded3DSet makePS(Gridded3DSet domain_set, int node_divide)
+          throws VisADException {
+    int number_of_nodes = node_divide * node_divide;
+    int x_len = domain_set.getLength(0);
+    int y_len = domain_set.getLength(1);
+    int z_len = domain_set.getLength(2);
+    int len = domain_set.getLength();
+    float[][] samples = domain_set.getSamples(false);
+    float[][] ps_samples = new float[3][number_of_nodes];
+    for (int i=0; i<node_divide; i++) {
+      int ie = i * (x_len - 1) / (node_divide - 1);
+      for (int j=0; j<node_divide; j++) {
+        int je = j * (y_len - 1) / (node_divide - 1);
+        int k = i + node_divide * j;
+        int ke = ie + x_len * (je + y_len * (z_len / 2));
+        ps_samples[0][k] = samples[0][ke];
+        ps_samples[1][k] = samples[1][ke];
+        ps_samples[2][k] = samples[2][ke];
+      }
+    }
+    Gridded3DSet ps =
+      new Gridded3DSet(domain_set.getType(), ps_samples,
+                       node_divide, node_divide, 1,
+                       domain_set.getCoordinateSystem(),
+                       domain_set.getSetUnits(), null);
+    return ps;
+  }
+
+}
+
diff --git a/visad/cluster/TestVis5DCluster.java b/visad/cluster/TestVis5DCluster.java
new file mode 100644
index 0000000..caf32e8
--- /dev/null
+++ b/visad/cluster/TestVis5DCluster.java
@@ -0,0 +1,368 @@
+//
+// TestVis5DCluster.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.java2d.*;
+import visad.util.ContourWidget;
+import visad.util.AnimationWidget;
+import visad.data.vis5d.Vis5DForm;
+
+import java.rmi.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.IOException;
+
+/**
+   TestVis5DCluster is the class for testing the visad.cluster package.<P>
+*/
+public class TestVis5DCluster extends Object {
+
+  public TestVis5DCluster() {
+  }
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException, IOException {
+
+    int node_divide = 2;
+    int number_of_nodes = node_divide * node_divide;
+
+    RemoteNodeField[] node_v5ds = new RemoteNodeField[number_of_nodes];
+  
+    if (args == null || args.length < 2) {
+      System.out.println("usage: 'java visad.cluster.TestVis5DCluster " +
+                         "n file.v5d'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+    int id = -1;
+    try {
+      id = Integer.parseInt(args[0]);
+    }
+    catch (NumberFormatException e) {
+      System.out.println("usage: 'java visad.cluster.TestVis5DCluster " +
+                         "n file.v5d'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+    if (id < 0 || id > number_of_nodes) {
+      System.out.println("usage: 'java visad.cluster.TestVis5DCluster " +
+                         "n file.v5d'");
+      System.out.println("  where n = 0 for client, 1 - " + number_of_nodes +
+                         " for nodes");
+      return;
+    }
+
+    boolean client = (id == 0);
+
+    Vis5DForm v5d_form = new Vis5DForm();
+    FieldImpl v5d = (FieldImpl) v5d_form.open(args[1]);
+    if (v5d == null) {
+      System.out.println("cannot open " + args[1]);
+      return;
+    }
+  
+    FunctionType v5d_type = (FunctionType) v5d.getType();
+    Set time_set = v5d.getDomainSet();
+    int time_length = time_set.getLength();
+    MathType v5d_range_type = v5d_type.getRange();
+    DataImpl v5d_range0 = (DataImpl) v5d.getSample(0);
+    FunctionType grid_type = null;
+    FunctionType grid_type2 = null;
+    FlatField grid0 = null;
+    if (v5d_range_type instanceof FunctionType) {
+      grid_type = (FunctionType) v5d_range_type;
+      grid_type2 = null;
+      grid0 = (FlatField) v5d_range0;
+    }
+    else {
+      grid_type =
+        (FunctionType) ((TupleType) v5d_range_type).getComponent(0);
+      grid_type2 =
+        (FunctionType) ((TupleType) v5d_range_type).getComponent(1);
+      grid0 = (FlatField) ((Tuple) v5d_range0).getComponent(0);
+    }
+
+    Gridded3DSet domain_set = (Gridded3DSet) grid0.getDomainSet();
+    Gridded3DSet ps = makePS(domain_set, node_divide);
+
+    if (!client) {
+System.out.println("v5d_type = " + v5d_type);
+      RealTupleType domain_type = grid_type.getDomain();
+  
+      RealTupleType time_tuple = v5d_type.getDomain();
+      RealType time = (RealType) time_tuple.getComponent(0);
+      RealType x = (RealType) domain_type.getComponent(0);
+      RealType y = (RealType) domain_type.getComponent(1);
+      RealType z = (RealType) domain_type.getComponent(2);
+      RealType val = (RealType) grid_type.getRange();
+      RealType val2 =
+        (grid_type2 == null) ? null : (RealType) grid_type2.getRange();
+  
+  
+      float[][] samples = domain_set.getSamples(false);
+      int x_len = domain_set.getLength(0);
+      int y_len = domain_set.getLength(1);
+      int z_len = domain_set.getLength(2);
+      int len = domain_set.getLength();
+
+      Gridded3DSet[] subsets = new Gridded3DSet[number_of_nodes];
+  
+      int k = id - 1;
+
+      int ik = k % node_divide;
+      int ig = ik * x_len / node_divide;
+      int igp = (ik + 1) * x_len / node_divide;
+      if (ik == (node_divide - 1)) igp = x_len;
+      int jk = k / node_divide;
+      int jg = jk * y_len / node_divide;
+      int jgp = (jk + 1) * y_len / node_divide;
+      if (jk == (node_divide - 1)) jgp = y_len;
+      int sub_x_len = igp - ig;
+      int sub_y_len = jgp - jg;
+      int sub_len = sub_x_len * sub_y_len * z_len;
+// System.out.println("sub_len = " + sub_len + " out of len = " + len);
+      float[][] sub_samples = new float[3][sub_len];
+      for (int i=0; i<sub_x_len; i++) {
+        for (int j=0; j<sub_y_len; j++) {
+          for (int m=0; m<z_len; m++) {
+            int a = i + sub_x_len * (j + sub_y_len * m);
+            int b = (i + ig) + x_len * ((j + jg) + y_len * m);
+            sub_samples[0][a] = samples[0][b];
+            sub_samples[1][a] = samples[1][b];
+            sub_samples[2][a] = samples[2][b];
+          }
+        }
+      }
+
+      subsets[k] =
+        new Gridded3DSet(domain_type, sub_samples,
+                         sub_x_len, sub_y_len, z_len,
+                        domain_set.getCoordinateSystem(),
+                        domain_set.getSetUnits(), null);
+      RemoteNodeDataImpl[] subgrids = new RemoteNodeDataImpl[time_length];
+      for (int i=0; i<time_length; i++) {
+        DataImpl v5d_sample = (DataImpl) v5d.getSample(i);
+        if (v5d_sample instanceof FlatField) {
+          FlatField grid = (FlatField) v5d_sample;
+          FlatField subgrid = (FlatField) grid.resample(subsets[k]);
+          subgrids[i] = new RemoteNodePartitionedFieldImpl(subgrid);
+        }
+        else {
+          Tuple v5d_tuple = (Tuple) v5d_sample;
+          int ngrids = v5d_tuple.getDimension();
+          RemoteNodeDataImpl[] subsubgrids = new RemoteNodeDataImpl[ngrids];
+          FlatField[] combinegrids = new FlatField[ngrids];
+          for (int j=0; j<ngrids; j++) {
+            FlatField grid = (FlatField) v5d_tuple.getComponent(j);
+            combinegrids[j] = (FlatField) grid.resample(subsets[k]);
+            // subsubgrids[j] =
+            //   new RemoteNodePartitionedFieldImpl(combinegrids[j]);
+          }
+          // subgrids[i] = new RemoteNodeTupleImpl(subsubgrids);
+          FlatField subgrid = (FlatField) FieldImpl.combine(combinegrids);
+          subgrids[i] = new RemoteNodePartitionedFieldImpl(subgrid);
+        }
+      }
+      FunctionType new_v5d_type =
+        new FunctionType(time_tuple, subgrids[0].getType());
+      node_v5ds[k] = new RemoteNodeFieldImpl(new_v5d_type, time_set);
+      node_v5ds[k].setSamples(subgrids, false);
+
+
+      int kk = id - 1;
+      String url = "///TestVis5DCluster" + kk;
+      try {
+        Naming.rebind(url, node_v5ds[kk]);
+      }
+      catch (Exception e) {
+        System.out.println("rebind " + kk + " " + e);
+        return;
+      }
+      // just so app doesn't exit
+      DisplayImpl display = new DisplayImplJ2D("dummy");
+      System.out.println("data ready as " + new_v5d_type);
+      return;
+    } // end if (!client)
+
+    // this is all client code
+    for (int k=0; k<number_of_nodes; k++) {
+      String url = "///TestVis5DCluster" + k;
+      try {
+        node_v5ds[k] = (RemoteNodeField) Naming.lookup(url);
+      }
+      catch (Exception e) {
+        System.out.println("lookup " + k + " " + e);
+        return;
+      }
+    }
+
+    v5d_type = (FunctionType) node_v5ds[0].getType();
+System.out.println("data type = " + v5d_type);
+    time_set = node_v5ds[0].getDomainSet();
+
+    RemoteClientFieldImpl client_v5d =
+      new RemoteClientFieldImpl(v5d_type, time_set);
+
+    RemoteClusterData[] table =
+      new RemoteClusterData[number_of_nodes + 1];
+    for (int i=0; i<number_of_nodes; i++) {
+      table[i] = node_v5ds[i];
+    }
+    table[number_of_nodes] = client_v5d;
+
+    for (int i=0; i<table.length; i++) {
+      table[i].setupClusterData(ps, table);
+    }
+
+    DisplayImpl display =
+      // new DisplayImplJ3D("main_display");
+      new DisplayImplJ3D("main_display", new ClientDisplayRendererJ3D(100000));
+
+/*
+    // get a list of decent mappings for this data
+    MathType type = image.getType();
+    ScalarMap[] maps = type.guessMaps(true);
+    // add the maps to the display
+    for (int i=0; i<maps.length; i++) {
+      display.addMap(maps[i]);
+    }
+*/
+
+    grid_type = (FunctionType) v5d_type.getRange();
+    RealTupleType domain_type = grid_type.getDomain();
+  
+  
+    RealTupleType time_tuple = v5d_type.getDomain();
+    RealType time = (RealType) time_tuple.getComponent(0);
+    RealType x = (RealType) domain_type.getComponent(0);
+    RealType y = (RealType) domain_type.getComponent(1);
+    RealType z = (RealType) domain_type.getComponent(2);
+    RealTupleType val_tuple = (RealTupleType) grid_type.getRange();
+    int nvals = val_tuple.getDimension();
+    RealType[] vals = new RealType[nvals];
+    for (int i=0; i<nvals; i++) {
+      vals[i] = (RealType) val_tuple.getComponent(i);
+    }
+
+    ScalarMap animation_map = new ScalarMap(time, Display.Animation);
+    display.addMap(animation_map);
+    display.addMap(new ScalarMap(x, Display.XAxis));
+    display.addMap(new ScalarMap(y, Display.YAxis));
+    display.addMap(new ScalarMap(z, Display.ZAxis));
+    ScalarMap[] contour_maps = new ScalarMap[nvals];
+    for (int i=0; i<nvals; i++) {
+      contour_maps[i] = new ScalarMap(vals[i], Display.IsoContour);
+      display.addMap(contour_maps[i]);
+    }
+
+    // link data to the display
+    DataReferenceImpl ref = new DataReferenceImpl("image");
+    // ref.setData(image);
+    // display.addReference(ref);
+    RemoteDataReferenceImpl remote_ref = new RemoteDataReferenceImpl(ref);
+    remote_ref.setData(client_v5d);
+    RemoteDisplayImpl remote_display = new RemoteDisplayImpl(display);
+    remote_display.addReference(remote_ref);
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test ClientRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+    // panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    // panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    JPanel lpanel = new JPanel();
+    lpanel.setLayout(new BoxLayout(lpanel, BoxLayout.Y_AXIS));
+    // lpanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    // lpanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    JPanel rpanel = new JPanel();
+    rpanel.setLayout(new BoxLayout(rpanel, BoxLayout.Y_AXIS));
+    // rpanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    // rpanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    // add display to JPanel
+    rpanel.add(display.getComponent());
+    AnimationWidget awidget = new AnimationWidget(animation_map);
+    awidget.setMaximumSize(new Dimension(400, 400));
+    lpanel.add(new AnimationWidget(animation_map));
+    for (int i=0; i<nvals; i++) {
+      ContourWidget cwidget = new ContourWidget(contour_maps[i]);
+      cwidget.setMaximumSize(new Dimension(400, 200));
+      lpanel.add(new ContourWidget(contour_maps[i]));
+    }
+
+    lpanel.setMaximumSize(new Dimension(400, 600));
+    panel.add(lpanel);
+    panel.add(rpanel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(800, 600);
+    frame.setVisible(true);
+  }
+
+  private static Gridded3DSet makePS(Gridded3DSet domain_set, int node_divide)
+          throws VisADException {
+    int number_of_nodes = node_divide * node_divide;
+    int x_len = domain_set.getLength(0);
+    int y_len = domain_set.getLength(1);
+    int z_len = domain_set.getLength(2);
+    int len = domain_set.getLength();
+    float[][] samples = domain_set.getSamples(false);
+    float[][] ps_samples = new float[3][number_of_nodes];
+    for (int i=0; i<node_divide; i++) {
+      int ie = i * (x_len - 1) / (node_divide - 1);
+      for (int j=0; j<node_divide; j++) {
+        int je = j * (y_len - 1) / (node_divide - 1);
+        int k = i + node_divide * j;
+        int ke = ie + x_len * (je + y_len * (z_len / 2));
+        ps_samples[0][k] = samples[0][ke];
+        ps_samples[1][k] = samples[1][ke];
+        ps_samples[2][k] = samples[2][ke];
+      }
+    }
+    Gridded3DSet ps =
+      new Gridded3DSet(domain_set.getType(), ps_samples,
+                       node_divide, node_divide, 1,
+                       domain_set.getCoordinateSystem(),
+                       domain_set.getSetUnits(), null);
+    return ps;
+  }
+
+}
+
diff --git a/visad/cluster/TestWRFCluster.java b/visad/cluster/TestWRFCluster.java
new file mode 100644
index 0000000..39ed366
--- /dev/null
+++ b/visad/cluster/TestWRFCluster.java
@@ -0,0 +1,650 @@
+//
+// TestWRFCluster.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+import visad.java2d.*;
+import visad.ss.*;
+import visad.bom.*;
+import visad.data.netcdf.Plain;
+
+import java.util.Vector;
+import java.rmi.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.io.IOException;
+
+/**
+   TestWRFCluster is the class for testing the visad.cluster package.<P>
+<PRE> Run:
+   java visad.cluster.TestWRFCluster 1 /home3/billh/wrf/wrfout_01_000000_0000
+   java visad.cluster.TestWRFCluster 2 /home3/billh/wrf/wrfout_01_000000_0001
+   java visad.cluster.TestWRFCluster 3 /home3/billh/wrf/wrfout_01_000000_0002
+   java visad.cluster.TestWRFCluster 4 /home3/billh/wrf/wrfout_01_000000_0003
+   java visad.cluster.TestWRFCluster 0 doll doll doll doll
+   java visad.cluster.TestWRFCluster 0 demedici demedici demedici demedici
+</PRE>
+*/
+public class TestWRFCluster extends FancySSCell implements ActionListener {
+
+  private RemoteDataReferenceImpl remote_ref = null;
+
+  public TestWRFCluster(String name, Frame parent)
+         throws VisADException, RemoteException {
+    super(name, parent);
+  }
+
+
+  /**
+   * override method from BasicSSCell
+   */
+  protected String addData(int id, Data data, ConstantMap[] cmaps,
+    String source, int type, boolean notify)
+    throws VisADException, RemoteException
+  {
+    // add Data object to cell
+    DataReferenceImpl ref = new DataReferenceImpl(Name);
+
+    // new
+    if (data instanceof RemoteData) {
+      remote_ref = new RemoteDataReferenceImpl(ref);
+      remote_ref.setData(data);
+    }
+    else {
+        ref.setData(data);
+    }
+
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = addReferenceImpl(id, ref, cmaps, source, type, notify, true);
+    }
+    return cellData.getVariableName();
+  }
+
+  /**
+   * override method from BasicSSCell
+   */
+  protected SSCellData addReferenceImpl(int id, DataReferenceImpl ref,
+    ConstantMap[] cmaps, String source, int type, boolean notify,
+    boolean checkErrors) throws VisADException, RemoteException
+  {
+    // ensure that id is valid
+    if (id == 0) id = getFirstFreeId();
+
+    // ensure that ref is valid
+    if (ref == null) ref = new DataReferenceImpl(Name);
+
+    // notify linked cells of data addition (ADD_DATA message must come first)
+    // if (notify) sendMessage(ADD_DATA, source, ref.getData());
+
+    // add data reference to cell
+    SSCellData cellData =
+      new SSCellData(id, this, ref, cmaps, source, type, checkErrors);
+    CellData.add(cellData);
+
+    if (!IsRemote) {
+      // SERVER: add data reference to display
+      if (HasMappings) VDisplay.addReference(ref, cmaps);
+
+      // add remote data reference to servers
+      synchronized (Servers) {
+        RemoteDataReferenceImpl remoteRef =
+          (RemoteDataReferenceImpl) cellData.getRemoteReference();
+        int len = Servers.size();
+        for (int i=0; i<len; i++) {
+          RemoteServerImpl rs = (RemoteServerImpl) Servers.elementAt(i);
+          rs.addDataReference(remoteRef);
+        }
+      }
+    }
+
+    return cellData;
+  }
+
+  /**
+   * override method from BasicSSCell
+   */
+  public synchronized void setMaps(ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    if (maps == null) return;
+
+    VisADException vexc = null;
+    RemoteException rexc = null;
+
+    if (IsRemote) {
+      // CLIENT: send new mappings to server
+      // sendMessage(SET_MAPS, DataUtility.convertMapsToString(maps), null);
+    }
+    else {
+      // SERVER: set up mappings
+
+      DataReference[] dr;
+      ConstantMap[][] cmaps;
+      synchronized (CellData) {
+        int len = CellData.size();
+        dr = new DataReference[len];
+        cmaps = new ConstantMap[len][];
+        for (int i=0; i<len; i++) {
+          SSCellData cellData = (SSCellData) CellData.elementAt(i);
+          dr[i] = cellData.getReference();
+          cmaps[i] = cellData.getConstantMaps();
+        }
+      }
+      String save = getPartialSaveString();
+      VDisplay.disableAction();
+      clearMaps();
+      for (int i=0; i<maps.length; i++) {
+        if (maps[i] != null) {
+          try {
+            VDisplay.addMap(maps[i]);
+          }
+          catch (VisADException exc) {
+            vexc = exc;
+          }
+          catch (RemoteException exc) {
+            rexc = exc;
+          }
+        }
+      }
+      for (int i=0; i<dr.length; i++) {
+        // determine if ImageRendererJ3D can be used
+        boolean ok = false;
+        Data data = dr[i].getData();
+        if (data == null) {
+        }
+        else if (Possible3D) {
+          MathType type = data.getType();
+          try {
+            ok = ImageRendererJ3D.isRendererUsable(type, maps);
+          }
+          catch (VisADException exc) {
+            if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+          }
+        }
+        // add reference
+        if (ok && Dim != JAVA2D_2D) {
+          VDisplay.addReferences(new ImageRendererJ3D(), dr[i], cmaps[i]);
+        }
+        else {
+          if (remote_ref == null) {
+            VDisplay.addReference(dr[i], cmaps[i]);
+          }
+          else {
+            RemoteVDisplay.addReference(remote_ref, cmaps[i]);
+          }
+        }
+      }
+
+      VDisplay.enableAction();
+      setPartialSaveString(save, true);
+    }
+    HasMappings = true;
+    if (vexc != null) throw vexc;
+    if (rexc != null) throw rexc;
+  }
+
+
+
+  /**
+   * override method from BasicSSCell
+   */
+  public synchronized boolean constructDisplay() {
+    boolean success = true;
+    DisplayImpl newDisplay = VDisplay;
+    RemoteDisplay rmtDisplay = RemoteVDisplay;
+    if (IsSlave) {
+      // SLAVE: construct dummy 2-D display
+      try {
+        newDisplay = new DisplayImplJ2D("DUMMY");
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    else if (!CanDo3D && Dim != JAVA2D_2D) {
+      // dimension requires Java3D, but Java3D is disabled for this JVM
+      success = false;
+    }
+    else {
+      // construct display of the proper dimension
+      try {
+        if (IsRemote) {
+          // CLIENT: construct new display from server's remote copy
+          if (Dim == JAVA3D_3D) newDisplay = new DisplayImplJ3D(rmtDisplay);
+          else if (Dim == JAVA2D_2D) {
+            newDisplay = new DisplayImplJ2D(rmtDisplay);
+          }
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(rmtDisplay, tdr);
+          }
+        }
+        else {
+          // SERVER: construct new display and make a remote copy
+          if (Dim == JAVA3D_3D) {
+            ClientDisplayRendererJ3D cdr = new ClientDisplayRendererJ3D(100000);
+            newDisplay = new DisplayImplJ3D(Name, cdr);
+          }
+          else if (Dim == JAVA2D_2D) newDisplay = new DisplayImplJ2D(Name);
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(Name, tdr);
+          }
+          rmtDisplay = new RemoteDisplayImpl(newDisplay);
+        }
+      }
+      catch (NoClassDefFoundError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (UnsatisfiedLinkError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (Exception exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    if (success) {
+      if (VDisplay != null) {
+        try {
+          VDisplay.destroy();
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+      VDisplay = newDisplay;
+      RemoteVDisplay = rmtDisplay;
+    }
+    return success;
+  }
+
+  public static void main(String[] args)
+         throws RemoteException, VisADException, IOException {
+
+    int node_divide = 2;
+    int number_of_nodes = node_divide * node_divide;
+
+    RemoteNodeField[] node_wrfs = new RemoteNodeField[number_of_nodes];
+  
+    if (args == null || args.length < 1) {
+      System.out.println("usage: 'java visad.cluster.TestWRFCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestWRFCluster 0 " +
+                         "node1 node2 node3 node4' for client");
+      System.exit(0);
+    }
+    int pid = -1;
+    try {
+      pid = Integer.parseInt(args[0]);
+    }
+    catch (NumberFormatException e) {
+      System.out.println("usage: 'java visad.cluster.TestWRFCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestWRFCluster 0 " +
+                         "node1 node2 node3 node4' for client");
+      System.exit(0);
+    }
+    if (pid < 0 || pid > number_of_nodes) {
+      System.out.println("usage: 'java visad.cluster.TestWRFCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestWRFCluster 0 " +
+                         "node1 node2 node3 node4' for client");
+      System.exit(0);
+    }
+    if (pid > 0 && args.length < 2) {
+      System.out.println("usage: 'java visad.cluster.TestWRFCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestWRFCluster 0 " +
+                         "node1 node2 node3 node4' for client");
+      System.exit(0);
+    }
+
+
+    boolean client = (pid == 0);
+
+    if (!client) {
+
+      Plain plain = new Plain();
+      FieldImpl wrf = (FieldImpl) plain.open(args[1]);
+      if (wrf == null) {
+        System.out.println("cannot open " + args[1]);
+        return;
+      }
+    
+      FunctionType wrf_type = (FunctionType) wrf.getType();
+System.out.println("wrf_type = " + wrf_type);
+
+      Set time_set = wrf.getDomainSet();
+      int time_length = time_set.getLength();
+      RealTupleType wrf_domain_type = wrf_type.getDomain();
+      RealType time_type = (RealType) wrf_domain_type.getComponent(0);
+      TupleType wrf_range_type = (TupleType) wrf_type.getRange();
+      FunctionType wrf_non_stag_type =
+        (FunctionType) wrf_range_type.getComponent(3);
+      RealTupleType wrf_non_stag_domain_type = wrf_non_stag_type.getDomain();
+      RealTupleType wrf_non_stag_Range_type =
+        (RealTupleType) wrf_non_stag_type.getRange();
+      FunctionType wrf_surface_type =
+        (FunctionType) wrf_range_type.getComponent(5);
+
+      // construct navigated WRF MathType and FieldImpl
+      // note SpatialEarth3DTuple = (Longitude, Latitude, Altitude)
+      RealTupleType nav_wrf_grid_domain_type = RealTupleType.SpatialEarth3DTuple;
+      FunctionType nav_wrf_grid_type =
+        new FunctionType(nav_wrf_grid_domain_type, wrf_non_stag_Range_type);
+      FunctionType nav_wrf_type = new FunctionType(time_type, nav_wrf_grid_type);
+      FieldImpl nav_wrf = new FieldImpl(nav_wrf_type, time_set);
+      Gridded3DSet nav_grid_set = null;
+      int nrows = 0, ncols = 0, nlevs = 0, grid_size = 0;
+      int nvert = 0;
+      for (int i=0; i<time_length; i++) {
+        Tuple wrf_step = (Tuple) wrf.getSample(i);
+        FlatField wrf_non_stag = (FlatField) wrf_step.getComponent(3);
+        FlatField wrf_surface = (FlatField) wrf_step.getComponent(5);
+        // if (nav_grid_set == null) {
+          FlatField wrf_vert = (FlatField) wrf_step.getComponent(6);
+          float[][] wrf_vert_samples = wrf_vert.getFloats(false);
+/*
+          nvert = wrf_vert_samples[0].length;
+          float[] height = new float[nvert];
+          for (int j=0; j<nvert; j++) {
+            height[j] = 0.5f * (wrf_vert_samples[0][j] + wrf_vert_samples[1][j]);
+// System.out.println("height["+ j + "] = " +  height[j]);
+          }
+*/
+          Gridded3DSet wrf_grid_set = (Gridded3DSet) wrf_non_stag.getDomainSet();
+          ncols = wrf_grid_set.getLength(0);
+          nrows = wrf_grid_set.getLength(1);
+          nlevs = wrf_grid_set.getLength(2);
+          float[][] wrf_surface_samples = wrf_surface.getFloats(false);
+          float[] lats = wrf_surface_samples[9];
+          float[] lons = wrf_surface_samples[10];
+          if (lats.length != nrows * ncols) {
+            throw new ClusterException("lats.length = " + lats.length +
+               " != " + nrows + " * " + ncols);
+          }
+/*
+          if (nvert != nlevs) {
+            throw new ClusterException("nvert = " + nvert +
+               " != " + nlevs + " = nlevs");
+          }
+*/
+/*
+int k = 0;
+for (int r=0; r<nrows; r++) {
+  for (int c=0; c<ncols; c++) {
+    System.out.println("row = " + r + " col = " + c + " lat = " +
+                       lats[k] + " lon = " +lons[k]);
+    k++;
+  }
+}
+*/
+          float[][] range_values = wrf_non_stag.getFloats(false);
+          grid_size = nrows * ncols * nlevs;
+          float[][] nav_wrf_samples = new float[3][grid_size];
+          nav_wrf_samples[0] = new float[grid_size];
+          nav_wrf_samples[1] = new float[grid_size];
+          for (int lev=0; lev<nlevs; lev++) {
+            int base = lev * nrows * ncols;
+            for (int rc=0; rc<nrows*ncols; rc++) {
+              nav_wrf_samples[0][base+rc] = lons[rc];
+              nav_wrf_samples[1][base+rc] = lats[rc];
+              // nav_wrf_samples[2][base+rc] = lev;
+            }
+          }
+          nav_wrf_samples[2] = range_values[11]; // Z
+          nav_grid_set =
+            new Gridded3DSet(nav_wrf_grid_domain_type, nav_wrf_samples,
+                             ncols, nrows, nlevs, null, null, null,
+                             false,  // no copy
+                             false); // no consistency test
+        // } // end if (nav_grid_set == null)
+        FlatField nav_wrf_grid = new FlatField(nav_wrf_grid_type, nav_grid_set);
+        nav_wrf_grid.setSamples(range_values, false);
+        nav_wrf.setSample(i, nav_wrf_grid);
+System.out.println("done with time step " + i);
+      } // end for (int i=0; i<time_length; i++)
+
+System.out.println("pid = " + pid);
+
+      RemoteNodeFieldImpl node_data = new RemoteNodeFieldImpl(nav_wrf);
+
+      int kk = pid - 1;
+System.out.println("kk = " + kk);
+      String url = "///TestWRFCluster" + kk;
+      try {
+        Naming.rebind(url, node_data);
+      }
+      catch (Exception e) {
+        System.out.println("rebind " + kk + " " + e);
+        return;
+      }
+      // just so app doesn't exit
+      CellImpl cell = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+        }
+      };
+      System.out.println("data ready as " + nav_wrf_type);
+      return;
+    } // end if (!client)
+
+    // this is all client code
+    if (args.length != 5) {
+      System.out.println("usage: 'java visad.cluster.TestWRFCluster n file'");
+      System.out.println("            for nodes where n = 1 - " + number_of_nodes);
+      System.out.println("       'java visad.cluster.TestWRFCluster 0 " +
+                         "node1 node2 node3 node4' for client");
+      System.exit(0);
+    }
+
+    for (int k=0; k<number_of_nodes; k++) {
+      String url = "//" + args[1+k] + "/TestWRFCluster" + k;
+      try {
+        node_wrfs[k] = (RemoteNodeField) Naming.lookup(url);
+      }
+      catch (Exception e) {
+        System.out.println("lookup " + k + " " + e);
+        return;
+      }
+    }
+
+    FunctionType nav_wrf_type = (FunctionType) node_wrfs[0].getType();
+System.out.println("data type = " + nav_wrf_type);
+    Set time_set = node_wrfs[0].getDomainSet();
+
+    RemoteClientFieldImpl client_wrf =
+      new RemoteClientFieldImpl(nav_wrf_type, time_set);
+
+    RemoteClusterData[] table =
+      new RemoteClusterData[number_of_nodes + 1];
+    for (int i=0; i<number_of_nodes; i++) {
+      table[i] = node_wrfs[i];
+    }
+    table[number_of_nodes] = client_wrf;
+
+    for (int i=0; i<table.length; i++) {
+      table[i].setupClusterData(null, table);
+    }
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test ClientRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    TestWRFCluster ss = new TestWRFCluster("TestWRFCluster", frame);
+
+    ss.addData(client_wrf);
+    // ss.addData(0, client_wrf, null, "", DIRECT_SOURCE, true);
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    frame.getContentPane().add(panel);
+
+    JPanel bpanel = new JPanel();
+    bpanel.setLayout(new BoxLayout(bpanel, BoxLayout.X_AXIS));
+
+    JButton maps = new JButton("Maps");
+    maps.addActionListener(ss);
+    maps.setActionCommand("map");
+    bpanel.add(maps);
+
+    JButton show = new JButton("Widgets");
+    show.addActionListener(ss);
+    show.setActionCommand("widgets");
+    bpanel.add(show);
+
+    JButton res1 = new JButton("Res 1");
+    res1.addActionListener(ss);
+    res1.setActionCommand("res1");
+    bpanel.add(res1);
+
+    JButton res2 = new JButton("Res 2");
+    res2.addActionListener(ss);
+    res2.setActionCommand("res2");
+    bpanel.add(res2);
+
+    JButton res3 = new JButton("Res 3");
+    res3.addActionListener(ss);
+    res3.setActionCommand("res3");
+    bpanel.add(res3);
+
+    JButton res4 = new JButton("Res 4");
+    res4.addActionListener(ss);
+    res4.setActionCommand("res4");
+    bpanel.add(res4);
+
+    panel.add(ss);
+    panel.add(bpanel);
+
+    // set size of JFrame and make it visible
+    frame.setSize(600, 600);
+    frame.setVisible(true);
+  }
+
+  int[] res = {1, 1, 1, 1};
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("map")) {
+      hideWidgetFrame();
+      addMapDialog();
+    }
+    else if (cmd.equals("widgets")) {
+      showWidgetFrame();
+    }
+    else if (cmd.equals("res1")) {
+      flipRes(0);
+    }
+    else if (cmd.equals("res2")) {
+      flipRes(1);
+    }
+    else if (cmd.equals("res3")) {
+      flipRes(2);
+    }
+    else if (cmd.equals("res4")) {
+      flipRes(3);
+    }
+  }
+
+  private void flipRes(int k) {
+    res[k] = 5 - res[k];
+    DisplayImpl display = getDisplay();
+    Vector renderers = display.getRendererVector();
+    for (int i=0; i<renderers.size(); i++) {
+      DataRenderer renderer = (DataRenderer) renderers.elementAt(i);
+      if (renderer instanceof ClientRendererJ3D) {
+        ((ClientRendererJ3D) renderer).setResolutions(res);
+      }
+    }
+    display.reDisplayAll();
+  }
+
+}
+
+/*
+java visad.jmet.DumpType wrfout_01_000000_0000
+
+VisAD Data analysis
+  FieldImpl of length = 5
+(Time -> (((west_east_stag, south_north, bottom_top) -> RHO_U),  ****0****
+          ((west_east, south_north_stag, bottom_top) -> RHO_V),  ****1****
+          ((west_east, south_north, bottom_top_stag) -> RW),     ****2****
+          ((west_east, south_north, bottom_top) -> (RRP, RR, TKE,
+                                                    RTP, TP, QVAPOR,
+                                                    QCLOUD, QRAIN, PP,
+                                                    RTB, RRB, Z, PB)),  ****3****
+          ((west_east, south_north, soil_layers) -> TSLB),       ****4****
+          ((west_east, south_north) -> (DZETADZ,  **0** 
+ MAPFAC_M,  **1**
+ HGT,       **2** 
+ TSK,       **3**
+ RAINC,     **4** 
+ RAINNC,    **5**
+ RAINCV,    **6**   
+ GSW,       **7**   
+ GLW,       **8**   
+ XLAT,      **9**                  ****
+ XLONG,     **10**                 ****
+ LU_INDEX,  **11**
+ TMN,       **12**
+ XLAND,     **13**
+ HFX,       **14**
+ QFX,       **15**
+ SNOWC)),   **16**                                ****5****
+          (bottom_top -> (FZM, FZP)),             ****6****
+          (ext_scalar -> (ZETATOP, ITIMESTEP))))  ****7****
+
+
+wrfout_01_000000_0000
+west_east: 0 - 20
+south_north: 0 - 17
+bottom_top: 0 - 22
+
+
+
+wrfout_01_000000_0001
+west_east: 0 - 20
+south_north: 0 - 15
+bottom_top: 0 - 22
+*/
+
diff --git a/visad/cluster/UserDisplayRendererJ3D.java b/visad/cluster/UserDisplayRendererJ3D.java
new file mode 100644
index 0000000..6d6a06d
--- /dev/null
+++ b/visad/cluster/UserDisplayRendererJ3D.java
@@ -0,0 +1,58 @@
+//
+// UserDisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+/**
+ * <CODE>UserDisplayRendererJ3D</CODE> is the DisplayRenderer
+ * for remote users connecting to a cluster via a proxy on the
+ * client.<P>
+ */
+public class UserDisplayRendererJ3D extends DefaultDisplayRendererJ3D {
+
+  RemoteProxyAgent agent = null;
+
+  long time_out = 10000;
+
+  public UserDisplayRendererJ3D (RemoteProxyAgent a, long to) {
+    super();
+    agent = a;
+    time_out = to;
+  }
+
+  public DataRenderer makeDefaultRenderer() {
+    return new UserRendererJ3D(agent, time_out);
+  }
+
+  public boolean legalDataRenderer(DataRenderer renderer) {
+    return (renderer instanceof UserRendererJ3D);
+  }
+
+}
+
diff --git a/visad/cluster/UserDummyDataImpl.java b/visad/cluster/UserDummyDataImpl.java
new file mode 100644
index 0000000..ff65f95
--- /dev/null
+++ b/visad/cluster/UserDummyDataImpl.java
@@ -0,0 +1,125 @@
+//
+// UserDummyDataImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.cluster;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   RemoteClientData is the class for cluster client
+   VisAD data objects.<P>
+*/
+public class UserDummyDataImpl extends DataImpl {
+
+  private RemoteClientData adaptedRemoteClientData = null;
+
+  private RemoteDataReferenceImpl rref = null;
+  private CellImpl cell = null;
+  private RemoteCellImpl rcell = null;
+  private UserDummyDataImpl uddi = null;
+
+  public UserDummyDataImpl(RemoteClientData rcd)
+         throws VisADException, RemoteException {
+    super(rcd.getType());
+    adaptedRemoteClientData = rcd;
+    DataReferenceImpl ref = new DataReferenceImpl("UserDummy");
+    rref = new RemoteDataReferenceImpl(ref);
+    rref.setData(adaptedRemoteClientData);
+    uddi = this;
+
+    cell = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        uddi.notifyReferences();
+      }
+    };
+    rcell = new RemoteCellImpl(cell);
+    rcell.addReference(rref);
+  }
+
+
+  public MathType getType() {
+    MathType type = null;
+    try {
+      type = adaptedRemoteClientData.getType();
+    }
+    catch (VisADException e) {
+      throw new VisADError(e.toString());
+    }
+    catch (RemoteException e) {
+      throw new VisADError(e.toString());
+    }
+    return type;
+  }
+
+  public boolean isMissing() throws VisADException, RemoteException {
+    return adaptedRemoteClientData.isMissing();
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no computeRanges method for user dummy data");
+  }
+
+  public Data binary(Data data, int op, MathType new_type,
+                    int sampling_mode, int error_mode )
+             throws VisADException, RemoteException {
+    throw new ClusterException("no binary method for user dummy data");
+  }
+
+  public Data binary(Data data, int op, int sampling_mode, int error_mode )
+             throws VisADException, RemoteException {
+    throw new ClusterException("no binary method for user dummy data");
+  }
+
+  public Data unary(int op, MathType new_type,
+                    int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no unary method for user dummy data");
+  }
+
+  public Data unary(int op, int sampling_mode, int error_mode)
+         throws VisADException, RemoteException {
+    throw new ClusterException("no unary method for user dummy data");
+  }
+
+  public Object clone() {
+    UserDummyDataImpl new_uddi = null;
+    try {
+      new_uddi = new UserDummyDataImpl(adaptedRemoteClientData);
+    }
+    catch (VisADException e) {
+      throw new VisADError(e.toString());
+    }
+    catch (RemoteException e) {
+      throw new VisADError(e.toString());
+    }
+    return new_uddi;
+  }
+
+}
+
diff --git a/visad/cluster/UserRendererJ3D.java b/visad/cluster/UserRendererJ3D.java
new file mode 100644
index 0000000..d00663c
--- /dev/null
+++ b/visad/cluster/UserRendererJ3D.java
@@ -0,0 +1,537 @@
+//
+// UserRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*      PROXY
+
+UserRendererJ3D like ClientRendererJ3D, but ?
+
+  ClientRendererJ3D <--> nodes
+
+or
+
+  UserRendererJ3D   <--> RemoteProxyAgentImpl <--> nodes
+                         RemoteProxyAgent
+
+no Java3D on nodes or proxy
+RemoteClientDataImpl on Client or Proxy, not on User
+UserDummyDataImpl extends DataImpl
+  getType() from adaptedRemoteClientData
+  RemoteCellImpl triggered by adaptedRemoteClientData
+      calls notifyReferences()
+
+UserDisplayRendererJ3D extends DefaultDisplayRendererJ3D
+  like ClientDisplayRendererJ3D
+
+*/
+
+package visad.cluster;
+
+import visad.*;
+import visad.java3d.*;
+
+import javax.media.j3d.*;
+
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.rmi.*;
+import java.io.Serializable;
+
+/**
+   UserRendererJ3D is the VisAD DataRenderer for remote users
+   connecting to a cluster via a proxy on the client.
+*/
+public class UserRendererJ3D extends DefaultRendererJ3D {
+
+  // no UserDummyDataImpl variable
+  // data access via RemoteProxyAgent
+  private RemoteProxyAgent agent = null;
+
+  private DisplayImpl display = null;
+  private ConstantMap[] cmaps = null;
+
+  private DataDisplayLink link = null;
+
+  private long time_out = 10000;
+
+  private int[] resolutions = null;
+
+  public UserRendererJ3D() {
+    this(null, 10000);
+  }
+
+  public UserRendererJ3D(RemoteProxyAgent a) {
+    this(a, 10000);
+  }
+
+  public UserRendererJ3D(RemoteProxyAgent a, long to) {
+    agent = a;
+    time_out = to;
+  }
+
+  public void setResolutions(int[] rs) throws RemoteException {
+    try {
+      agent.setResolutions(rs);
+    }
+    catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return;
+      }
+      throw re;
+    }
+  }
+
+  public DataShadow prepareAction(boolean go, boolean initialize,
+                                  DataShadow shadow)
+         throws VisADException, RemoteException {
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links != null && Links.length > 0) {
+      link = Links[0];
+
+      // initialize cmaps if not already
+      if (cmaps == null) {
+        display = getDisplay();
+        Vector cvector = link.getConstantMaps();
+        if (cvector != null && cvector.size() > 0) {
+          int clength = cvector.size();
+          cmaps = new ConstantMap[clength];
+          for (int i=0; i<clength; i++) {
+            cmaps[i] = (ConstantMap) cvector.elementAt(i);
+          }
+        }
+      }
+    }
+
+    Vector map_vector = display.getMapVector();
+    int n = map_vector.size();
+    ScalarMap[] maps = new ScalarMap[n];
+    Control[] controls = new Control[n];
+    for (int i=0; i<n; i++) {
+      maps[i] = (ScalarMap) map_vector.elementAt(i);
+      controls[i] = maps[i].getControl();
+    }
+
+    Serializable[] responses =
+      agent.prepareAction(go, initialize, shadow, cmaps, maps, controls,
+                          display.getName(), time_out);
+
+    // now do usual prepareAction()
+    return super.prepareAction(go, initialize, shadow);
+  }
+
+  /** create a scene graph for Data in links[0] */
+  public BranchGroup doTransform() throws VisADException, RemoteException {
+
+    Serializable[] responses = null;
+    try {
+      // responses are VisADGroups
+      responses = agent.doTransform();
+    }
+    catch (DisplayException e) {
+      addException(e);
+    }
+// System.out.println("UserRendererJ3D.doTransform messages received");
+
+    if (link == null) {
+      addException(
+        new DisplayException("Data is null: UserRendererJ3D.doTransform"));
+      responses = null;
+    }
+
+    // responses are VisADGroups
+    // need to:
+    // 1. rebuild images and volumes
+    // 2. convert from VisADGroups to BranchGroups
+    //    GeometryArray = display.makeGeometry(VisADGeometryArray)
+    // 3. add them as children of branch
+
+    // link.clearData(); ????
+
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(Group.ALLOW_CHILDREN_READ);
+    branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+    int n = (responses == null) ? 0 : responses.length;
+    for (int i=0; i<n; i++) {
+      if (responses[i] != null) {
+        VisADSceneGraphObject vsgo = (VisADSceneGraphObject) responses[i];
+        branch.addChild(convertSceneGraph(vsgo));
+      }
+    }
+    if (n == 0) ShadowTypeJ3D.ensureNotEmpty(branch, display);
+    return branch;
+  }
+
+  public void setSpatialValues(float[][] spatial_values) {
+    super.setSpatialValues(spatial_values);
+  }
+
+  /* convert from VisAD scene graph to Java3D scene graph
+     and rebuild images and volumes */
+  public Node convertSceneGraph(VisADSceneGraphObject scene)
+         throws VisADException {
+    if (scene instanceof VisADSwitch) {
+      VisADSwitch Vswit = (VisADSwitch) scene;
+      BranchGroup branch = new BranchGroup();
+      branch.setCapability(BranchGroup.ALLOW_DETACH);
+      branch.setCapability(Group.ALLOW_CHILDREN_READ);
+      branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+      branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    
+      Switch swit = new Switch();
+      swit.setCapability(Switch.ALLOW_SWITCH_READ);
+      swit.setCapability(Switch.ALLOW_SWITCH_WRITE);
+      swit.setCapability(BranchGroup.ALLOW_DETACH);
+      swit.setCapability(Group.ALLOW_CHILDREN_READ);
+      swit.setCapability(Group.ALLOW_CHILDREN_WRITE);
+
+      int n = Vswit.numChildren();
+      Set set = Vswit.getSet();
+      // set != null for Animation
+      // set == null for volume rendering
+
+      if (set != null) {
+        // Switch for Animation or SelectValue
+        for (int i=0; i<n; i++) {
+          VisADSceneGraphObject vsgo = Vswit.getChild(i);
+          swit.addChild((Node) convertSceneGraph(vsgo));
+        }
+
+        RealType real = (RealType)
+          ((SetType) set.getType()).getDomain().getComponent(0);
+        AVControl control = null;
+        Vector mapVector = display.getMapVector();
+        Enumeration maps = mapVector.elements();
+        while (maps.hasMoreElements()) {
+          ScalarMap map = (ScalarMap )maps.nextElement();
+          if (real.equals(map.getScalar())) {
+            DisplayRealType dreal = map.getDisplayScalar();
+            if (dreal.equals(Display.Animation) ||
+                dreal.equals(Display.SelectValue)) {
+              control = (AVControl) map.getControl();
+              break;
+            }
+          } // end if (values != null && && real.equals(map.getScalar()))
+        }
+        if (control == null) {
+          throw new ClusterException("AVControl is null");
+        }
+
+        // from ShadowFunctionOrSetTypeJ3D.addSwitch()
+        ((AVControlJ3D) control).addPair(swit, set, this);
+        ((AVControlJ3D) control).init();
+      }
+      else { // if (set == null)
+        // Switch for volume rendering
+        // see visad.java3d.ShadowFunctionOrSetTypeJ3D.textureStackToGroup()
+        // and visad.cluster.ShadowNodeFunctionTypeJ3D.textureStackToGroup()
+        if (Vswit.numChildren() != 3) {
+          throw new ClusterException("VisADSwitch for volume render " +
+                                     "must have 3 children");
+        }
+
+        VisADGroup VbranchX =
+          (VisADGroup) ((VisADSwitch) scene).getChild(0);
+        VisADGroup VbranchY =
+          (VisADGroup) ((VisADSwitch) scene).getChild(1);
+        VisADGroup VbranchZ =
+          (VisADGroup) ((VisADSwitch) scene).getChild(2);
+
+        int nX = VbranchX.numChildren();
+        OrderedGroup branchX = new OrderedGroup();
+        branchX.setCapability(Group.ALLOW_CHILDREN_READ);
+        VisADAppearance[] appearanceX = new VisADAppearance[nX];
+        for (int i=0; i<nX; i++) {
+          VisADAppearance appearance = (VisADAppearance) VbranchX.getChild(i);
+          branchX.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        OrderedGroup branchXrev = new OrderedGroup();
+        branchXrev.setCapability(Group.ALLOW_CHILDREN_READ);
+        for (int i=nX-1; i>=0; i--) {
+          VisADAppearance appearance = (VisADAppearance) VbranchX.getChild(i);
+          branchXrev.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+
+        int nY = VbranchY.numChildren();
+        OrderedGroup branchY = new OrderedGroup();
+        branchY.setCapability(Group.ALLOW_CHILDREN_READ);
+        VisADAppearance[] appearanceY = new VisADAppearance[nY];
+        for (int i=0; i<nY; i++) {
+          VisADAppearance appearance = (VisADAppearance) VbranchY.getChild(i);
+          branchY.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        OrderedGroup branchYrev = new OrderedGroup();
+        branchYrev.setCapability(Group.ALLOW_CHILDREN_READ);
+        for (int i=nY-1; i>=0; i--) {
+          VisADAppearance appearance = (VisADAppearance) VbranchY.getChild(i);
+          branchYrev.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+
+        int nZ = VbranchZ.numChildren();
+        OrderedGroup branchZ = new OrderedGroup();
+        branchZ.setCapability(Group.ALLOW_CHILDREN_READ);
+        VisADAppearance[] appearanceZ = new VisADAppearance[nZ];
+        for (int i=0; i<nZ; i++) {
+          VisADAppearance appearance = (VisADAppearance) VbranchZ.getChild(i);
+          branchZ.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        OrderedGroup branchZrev = new OrderedGroup();
+        branchZrev.setCapability(Group.ALLOW_CHILDREN_READ);
+        for (int i=nZ-1; i>=0; i--) {
+          VisADAppearance appearance = (VisADAppearance) VbranchZ.getChild(i);
+          branchZrev.addChild((Shape3D) convertSceneGraph(appearance));
+        }
+        swit.addChild(branchX);
+        swit.addChild(branchY);
+        swit.addChild(branchZ);
+        swit.addChild(branchXrev);
+        swit.addChild(branchYrev);
+        swit.addChild(branchZrev);
+
+        ProjectionControlJ3D control =
+          (ProjectionControlJ3D) display.getProjectionControl();
+        control.addPair(swit, this);
+      }
+      branch.addChild(swit);
+      return branch;
+    }
+    else if (scene instanceof VisADGroup) {
+      VisADGroup group = (VisADGroup) scene;
+      BranchGroup branch = new BranchGroup();
+      branch.setCapability(BranchGroup.ALLOW_DETACH);
+      branch.setCapability(Group.ALLOW_CHILDREN_READ);
+      branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+      branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+      int n = group.numChildren();
+      for (int i=0; i<n; i++) {
+        VisADSceneGraphObject vsgo = group.getChild(i);
+        branch.addChild((Node) convertSceneGraph(vsgo));
+      }
+      ShadowTypeJ3D.ensureNotEmpty(branch, display);
+      return branch;
+    }
+    else if (scene instanceof VisADAppearance) {
+      VisADAppearance appearance = (VisADAppearance) scene;
+      GraphicsModeControl mode = display.getGraphicsModeControl();
+      VisADGeometryArray vga = appearance.array;
+      GeometryArray array = ((DisplayImplJ3D) display).makeGeometry(vga);
+      if (array == null) return null;
+      BufferedImage image = null;
+      if (appearance.image_pixels != null) {
+        image = new BufferedImage(appearance.image_width, appearance.image_height,
+                                  appearance.image_type);
+        image.setRGB(0, 0, appearance.image_width, appearance.image_height,
+                     appearance.image_pixels, 0, appearance.image_width);
+/* OR:
+        ColorModel colorModel = ColorModel.getRGBdefault();
+        WritableRaster raster =
+          colorModel.createCompatibleWritableRaster(appearance.image_width, 
+                                                    appearance.image_height);
+        DataBuffer db = raster.getDataBuffer();
+        if (!(db instanceof DataBufferInt)) {
+          throw new UnimplementedException("getRGBdefault isn't DataBufferInt");
+        }
+        image = new BufferedImage(colorModel, raster, false, null);
+        int[] intData = ((DataBufferInt)db).getData();
+        System.arraycopy(appearance.image_pixels, 0, intData, 0, intData.length);
+*/
+      }
+      if (image != null) {
+        // need to do Texture stuff
+        Appearance appearance_j3d =
+          makeTextureAppearance(appearance, mode, array);
+
+        // create TextureAttributes
+        TextureAttributes texture_attributes = new TextureAttributes();
+        texture_attributes.setTextureMode(TextureAttributes.MODULATE);
+        texture_attributes.setPerspectiveCorrectionMode(
+                              TextureAttributes.NICEST);
+        appearance_j3d.setTextureAttributes(texture_attributes);
+
+        int transparencyMode = mode.getTransparencyMode();
+
+        Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                          appearance.texture_width,
+                                          appearance.texture_height);
+        texture.setCapability(Texture.ALLOW_IMAGE_READ);
+        ImageComponent2D image2d =
+          new ImageComponent2D(ImageComponent.FORMAT_RGBA, image);
+        image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+        texture.setImage(0, image2d);
+        // this from textureStackToGroup,
+        // but not in textureToGroup
+        if (transparencyMode == TransparencyAttributes.FASTEST) {
+          texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+          texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+        }
+        else {
+          texture.setBoundaryModeS(Texture.CLAMP);
+          texture.setBoundaryModeT(Texture.CLAMP);
+          texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+          texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+        }
+        texture.setEnable(true);
+        appearance_j3d.setTexture(texture);
+        appearance_j3d.setCapability(Appearance.ALLOW_TEXTURE_READ);
+
+        Shape3D shape = new Shape3D(array, appearance_j3d);
+        shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+        return shape;
+      }
+      else {
+        Appearance appearance_j3d =
+          makeAppearance(appearance, mode, array);
+        Shape3D shape = new Shape3D(array, appearance_j3d);
+        return shape;
+      }
+    }
+    else {
+      throw new VisADException("unknown scene " + scene);
+    }
+  }
+
+  private Appearance makeTextureAppearance(VisADAppearance appearance,
+                           GraphicsModeControl mode, GeometryArray array) {
+    TransparencyAttributes c_alpha = null;
+    if (appearance.alpha == 1.0f) {
+      // constant opaque alpha = NONE
+      c_alpha = null;
+    }
+    else if (appearance.alpha == appearance.alpha) {
+      c_alpha = new TransparencyAttributes(TransparencyAttributes.BLENDED,
+                                           appearance.alpha);
+    }
+    else {
+      c_alpha = new TransparencyAttributes();
+      c_alpha.setTransparencyMode(TransparencyAttributes.BLENDED);
+    }
+    ColoringAttributes c_color = null;
+    if (appearance.red == appearance.red &&
+        appearance.green == appearance.green &&
+        appearance.blue == appearance.blue) {
+      c_color = new ColoringAttributes();
+      c_color.setColor(appearance.red, appearance.green, appearance.blue);
+    }
+    return ShadowTypeJ3D.staticMakeAppearance(mode, c_alpha, null,
+                                              array, false);
+  }
+
+  private Appearance makeAppearance(VisADAppearance appearance,
+                           GraphicsModeControl mode, GeometryArray array) {
+    TransparencyAttributes c_alpha = null;
+    if (appearance.alpha == 1.0f) {
+      // constant opaque alpha = NONE
+      c_alpha = new TransparencyAttributes(TransparencyAttributes.NONE, 0.0f);
+    }
+    else if (appearance.alpha == appearance.alpha) {
+      c_alpha = new TransparencyAttributes(mode.getTransparencyMode(),
+                                           appearance.alpha);
+    }
+    else {
+      c_alpha = new TransparencyAttributes(mode.getTransparencyMode(), 0.0f);
+    }
+    ColoringAttributes c_color = null;
+    if (appearance.red == appearance.red &&
+        appearance.green == appearance.green &&
+        appearance.blue == appearance.blue) {
+      c_color = new ColoringAttributes();
+      c_color.setColor(appearance.red, appearance.green, appearance.blue);
+    }
+    return ShadowTypeJ3D.staticMakeAppearance(mode, c_alpha, c_color,
+                                              array, false);
+  }
+
+  public DataShadow computeRanges(Data data, ShadowType type, DataShadow shadow) 
+         throws VisADException, RemoteException {
+
+    Vector message = new Vector();
+    message.addElement(type);
+    if (shadow == null) {
+      message.addElement(new Integer(getDisplay().getScalarCount()));
+    }
+    else {
+      message.addElement(shadow);
+    }
+// PROXY: Vector of ShadowType, (Integer or DataShadow)
+    Serializable[] responses =
+      agent.computeRanges(message);
+// System.out.println("UserRendererJ3D.computeRanges messages received");
+    DataShadow new_shadow = null;
+    int n = responses.length;
+    for (int i=0; i<n; i++) {
+      if (responses[i] != null) {
+        if (new_shadow == null) {
+          new_shadow = (DataShadow) responses[i];
+        }
+        else {
+          new_shadow.merge((DataShadow) responses[i]);
+        }
+      }
+    }
+    return new_shadow;
+  }
+
+  public Object clone() {
+    return new UserRendererJ3D(agent, time_out);
+  }
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    DisplayImpl display =
+      new DisplayImplJ3D("display", new ClientDisplayRendererJ3D());
+
+    // create JFrame (i.e., a window) for display and slider
+    JFrame frame = new JFrame("test UserRendererJ3D");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    // create JPanel in JFrame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(panel);
+
+    // add display to JPanel
+    panel.add(display.getComponent());
+
+    // set size of JFrame and make it visible
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/collab/CollabUtil.java b/visad/collab/CollabUtil.java
new file mode 100644
index 0000000..1ec9126
--- /dev/null
+++ b/visad/collab/CollabUtil.java
@@ -0,0 +1,35 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+public class CollabUtil
+{
+  public static final boolean isDisconnectException(RemoteException re)
+  {
+    return (re.detail instanceof java.io.EOFException ||
+            re.detail instanceof java.net.SocketException ||
+            re.detail instanceof java.net.ConnectException);
+  }
+}
diff --git a/visad/collab/ControlMonitorEvent.java b/visad/collab/ControlMonitorEvent.java
new file mode 100644
index 0000000..240a8be
--- /dev/null
+++ b/visad/collab/ControlMonitorEvent.java
@@ -0,0 +1,168 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import visad.Control;
+import visad.VisADException;
+
+/**
+ * <CODE>ControlMonitorEvent</CODE> is the VisAD class for
+ * <CODE>Control</CODE>-related <CODE>Event</CODE>s
+ * from display monitors.  They are sourced by <CODE>DisplayMonitor</CODE>
+ * objects and received by <CODE>MonitorCallback</CODE> objects.
+ */
+public class ControlMonitorEvent
+  extends MonitorEvent
+{
+  private Control ctl;
+
+  /**
+   * Creates a <CODE>ControlMonitorEvent</CODE> for the specified
+   * <CODE>Control</CODE>.
+   *
+   * @param type The event type (either
+   * 			<CODE>MonitorEvent.CONTROL_INIT_REQUESTED</CODE>
+   * 			or <CODE>MonitorEvent.CONTROL_CHANGED</CODE>.)
+   * @param ctl The <CODE>Control</CODE>.
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public ControlMonitorEvent(int type, Control ctl)
+    throws VisADException
+  {
+    this(type, -1, ctl);
+  }
+
+  /**
+   * Creates a <CODE>ControlMonitorEvent</CODE> for the specified
+   * <CODE>Control</CODE>.
+   *
+   * @param type The event type (either
+   * 			<CODE>MonitorEvent.CONTROL_INIT_REQUESTED</CODE>
+   * 			or <CODE>MonitorEvent.CONTROL_CHANGED</CODE>.)
+   * @param originator The ID of the connection from which this event came,
+   *                    relative to the receiver of the event.
+   * @param ctl The <CODE>Control</CODE>.
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public ControlMonitorEvent(int type, int originator, Control ctl)
+    throws VisADException
+  {
+    super(type, originator);
+    if (type != CONTROL_CHANGED && type != CONTROL_INIT_REQUESTED) {
+      throw new VisADException("Bad type for ControlMonitorEvent");
+    }
+    this.ctl = ctl;
+  }
+
+  /**
+   * Gets the <CODE>Control</CODE> to which this event refers.
+   */
+  public Control getControl()
+  {
+    return ctl;
+  }
+
+  /**
+   * Get the key used to uniquely identify this control.
+   *
+   * @return The unique key.
+   */
+  public static String getControlKey(Control ctl)
+  {
+    return ctl.getClass().getName() + "#" + ctl.getInstanceNumber();
+  }
+
+  /**
+   * Get the key used to uniquely identify this event.
+   *
+   * @return The unique key.
+   */
+  public String getKey()
+  {
+    return getControlKey(ctl);
+  }
+
+  /**
+   * Returns <CODE>true</CODE> if the specified object matches this object.
+   *
+   * @param o The object to compare.
+   */
+  public boolean equals(Object o)
+  {
+    if (!(o instanceof ControlMonitorEvent)) {
+      return false;
+    }
+
+    ControlMonitorEvent evt = (ControlMonitorEvent )o;
+    if (getType() != evt.getType()) {
+      return false;
+    }
+
+    return ctl.equals(evt.ctl);
+  }
+
+  /**
+   * Returns an exact clone of this object.
+   */
+  public Object clone()
+  {
+    ControlMonitorEvent evt;
+    try {
+      evt = new ControlMonitorEvent(getType(), getOriginator(),
+                                    (Control )ctl.clone());
+      evt.seqNum = seqNum;
+    } catch (VisADException e) {
+      evt = null;
+    }
+    return evt;
+  }
+
+  /**
+   * Returns a <CODE>String</CODE> representation of this object.
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("CMonEvt[");
+buf.append('#');buf.append(getSequenceNumber());buf.append(' ');
+
+    if (getType() != CONTROL_CHANGED) {
+      buf.append(getTypeName());
+    }
+
+    int orig = getOriginator();
+    if (orig == -1) {
+      buf.append(" Lcl");
+    } else {
+      buf.append("Rmt ");
+      buf.append(orig);
+    }
+
+    buf.append(' ');
+    buf.append(ctl);
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/collab/DisplayMonitor.java b/visad/collab/DisplayMonitor.java
new file mode 100644
index 0000000..248c2bf
--- /dev/null
+++ b/visad/collab/DisplayMonitor.java
@@ -0,0 +1,161 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.io.Serializable;
+
+import java.rmi.RemoteException;
+
+import visad.Control;
+import visad.ControlListener;
+import visad.DisplayListener;
+import visad.MessageListener;
+import visad.RemoteDisplay;
+import visad.RemoteVisADException;
+import visad.ScalarMapListener;
+import visad.VisADException;
+
+/**
+ * <CODE>DisplayMonitor</CODE> is the interface for objects which monitor
+ * the state of <CODE>Control</CODE>s, <CODE>Display</CODE>s and
+ * <CODE>ScalarMap</CODE>s.
+ */
+public interface DisplayMonitor
+  extends ControlListener, DisplayListener, MessageListener,
+          ScalarMapListener, Serializable
+{
+  /** Connection ID used to indicate errors */
+  int UNKNOWN_LISTENER_ID = 0;
+
+  /**
+   * Adds the specified listener to receive <CODE>MonitorEvent</CODE>s
+   * when the monitored <CODE>Display</CODE>'s state changes.
+   *
+   * @param callback The object to which events are delivered.
+   * @param id The unique identifier.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception VisADException If the listener <CODE>Vector</CODE>
+   * 				is uninitialized.
+   */
+  void addListener(MonitorCallback callback, int id)
+    throws RemoteException, VisADException;
+
+  /**
+   * Adds the specified remote display to receive <CODE>MonitorEvent</CODE>s
+   * when the monitored <CODE>Display</CODE>'s state changes.
+   *
+   * @param rd The remote display to add.
+   * @param id The unique listener identifier.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception VisADException If the listener <CODE>Vector</CODE>
+   * 				is uninitialized.
+   */
+  void addListener(RemoteDisplay rd, int id)
+    throws RemoteException, VisADException;
+
+  /**
+   * Initializes links so that <CODE>MonitorEvent</CODE>s will be
+   * exchanged with the specified remote <CODE>Display</CODE>.
+   *
+   * @param rd The remote <CODE>Display</CODE> to synchronize.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If the inter-<CODE>Display</CODE>
+   * 					links could not be made.
+   */
+  void addRemoteListener(RemoteDisplay rd)
+    throws RemoteException, RemoteVisADException;
+
+  /**
+   * Returns a suggestion for a unique listener identifier which is
+   * equal to or greater than the supplied ID.
+   *
+   * @param id The identifier to check.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  int checkID(int id)
+    throws RemoteException;
+
+  /** destroy this monitor */
+  void destroy()
+    throws RemoteException, RemoteVisADException;
+
+  /**
+   * Return the ID associated with the specified <tt>RemoteDisplay</tt>.
+   *
+   * @return <tt>UNKNOWN_LISTENER_ID</tt> if not found;
+   *         otherwise, returns the ID.
+   */
+  int getConnectionID(RemoteDisplay rmtDpy)
+    throws RemoteException;
+
+  /**
+   * Returns <CODE>true</CODE> if there is a <CODE>MonitorEvent</CODE>
+   * for the specified <CODE>Control</CODE> waiting to be delivered to
+   * any listener.
+   *
+   * @param ctl The <CODE>Control</CODE> being found.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  boolean hasEventQueued(Control ctl)
+    throws RemoteException;
+
+  /**
+   * Returns <CODE>true</CODE> if there is a <CODE>MonitorEvent</CODE>
+   * for the specified <CODE>Control</CODE> waiting to be delivered to the
+   * listener with the specified id.
+   *
+   * @param listenerID The identifier for the listener.
+   * @param ctl The <CODE>Control</CODE> being found.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  boolean hasEventQueued(int listenerID, Control ctl)
+    throws RemoteException;
+
+  // WLH 12 April 2001
+  boolean isEmpty()
+    throws RemoteException;
+
+  /**
+   * Forwards the <CODE>MonitorEvent</CODE> to all the listeners
+   * associated with this <CODE>DisplayMonitor</CODE>.
+   *
+   * @param evt The event to forward.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If there is an internal error.
+   */
+  void notifyListeners(MonitorEvent evt)
+    throws RemoteException, RemoteVisADException;
+
+  /**
+   * Set the display synchronization object for this display
+   */
+  void setDisplaySync(DisplaySync sync)
+    throws RemoteException;
+}
diff --git a/visad/collab/DisplayMonitorImpl.java b/visad/collab/DisplayMonitorImpl.java
new file mode 100644
index 0000000..dc17c3e
--- /dev/null
+++ b/visad/collab/DisplayMonitorImpl.java
@@ -0,0 +1,790 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/* Dave's design comments, inserted as a comment by Bill
+
+VisAD collaboration
+
+Collaboration is implemented within two classes, DisplayMonitor
+and DisplaySync.  DisplayMonitor listens to the local Display
+and notifies remote listeners of significant changes.
+DisplaySync receives those notifications and makes them
+happen on the local Display.
+
+The notification takes the form of the various MonitorEvents:
+ * a ControlMonitorEvent is sent when a Control is initialized
+   or changed.
+ * a MapMonitorEvents is sent when a ControlMap or ScalarMap
+   is added or changed, or when all the ControlMaps and
+   ScalarMaps are cleared from a Display.
+ * a ReferenceMonitorEvent is sent when a Data reference is
+   added to or removed from a Display.
+
+All Displays can be collaborative servers, since a
+DisplayMonitor and DisplaySync object are created for every
+Display.
+
+When a collaborative Display is constructed, the following
+steps take place:
+ * The client Display constructor uses its RemoteDisplay
+   parameter to fetch the remote Display's ScalarMaps,
+   ConstantMaps and remote Data references.
+ * The remote Display is connected to the client Display
+   via the client Display's DisplayMonitor and DisplaySync
+   objects.
+ * The client's Controls are synchronized with the
+   remote Display's Controls
+ * From this point, MonitorEvents are used to keep the
+   two Displays synchronized.
+
+MonitorEvent transmission happens in several steps.
+ * DisplayMonitor is notified that the Display has changed.
+ * It builds a MonitorEvent which is forwarded to each of
+   its MonitorSyncer listeners, which are each connected to
+   a remote DisplaySync object.
+ * If the MonitorSyncer is already trying to deliver one
+   or more events, the forwarded event will be added to
+   a queue which will be delivered as soon as the current
+   events have been delivered.
+ * To deliver one or more events, the MonitorSyncer sends a
+   "key" for each event to the remote DisplaySync.
+ * The remote DisplaySync gathers all the event keys,
+   then uses them to request the actual events from the
+   MonitorSyncer.
+ * The MonitorSyncer removes each requested event from its
+   list, then sends the event back to the remote DisplaySync.
+ * The remote DisplaySync receives the event and uses it to
+   synchronize its Display.
+
+This is somewhat complicated but necessary, mainly due to
+networking and execution delays and ordering problems.
+
+The original implementation simply sent each event.  This
+caused problems due to events being delivered out of order.
+For instance, a Control might move from state A to state B
+to state C, but the event for state C would occasionally
+be delivered before state B.
+
+This was fixed by only allowing one set of events to be
+delivered at a time, and forcing events to be delivered
+in the order in which they were received.  This ran into
+problems because if, as above, a Control moved from state A
+through state B to state C, the event for state B might be
+delivered after the local Control moved to state C, but then
+the remote Display might forward the event for state B back
+to the local Display, causing a loop which might eventually
+settle on state B rather than state C.
+
+The current solution causes events to accumulate in a
+single cache, which uses keys which are unique to a given
+Control, ScalarMap, etc. and only the keys are forwarded.
+Events can be superceded up to the point where the remote
+DisplaySync actually requests the event using the key.
+The event for state C would overwrite the event for state B
+in the MonitorSyncer's cache as long as the MonitorSyncer
+received that event before it received the DisplaySync
+request for the event.
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.Vector;
+
+import visad.AnimationControl;
+import visad.Control;
+import visad.ControlEvent;
+import visad.DataDisplayLink;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.DisplayEvent;
+import visad.DisplayImpl;
+import visad.DisplayMapEvent;
+import visad.DisplayReferenceEvent;
+import visad.MessageEvent;
+import visad.RemoteDisplay;
+import visad.RemoteDisplayImpl;
+import visad.RemoteReferenceLinkImpl;
+import visad.RemoteVisADException;
+import visad.ScalarMap;
+import visad.ScalarMapControlEvent;
+import visad.ScalarMapEvent;
+import visad.VisADException;
+
+/**
+ * <tt>DisplayMonitorImpl</tt> is the {@link visad.Display Display} monitor
+ * implementation.<P>
+ * <tt>DisplayMonitorImpl</tt> is not {@link java.io.Serializable Serializable} and
+ * should not be copied between JVMs.<P>
+ */
+public class DisplayMonitorImpl
+  implements DisplayMonitor
+{
+  private int nextListenerID = UNKNOWN_LISTENER_ID + 1;
+
+  /**
+   * The name of this display monitor.
+   */
+  private String Name;
+
+  /**
+   * The {@link visad.Display Display} being monitored.
+   */
+  private DisplayImpl myDisplay;
+
+  /**
+   * The list of objects interested in changes to the monitored
+   * {@link visad.Display Display}.
+   */
+  private ArrayList listeners;
+
+  /**
+   * The synchronization object for the monitored display
+   */
+  private DisplaySync sync;
+
+  /**
+   * Creates a monitor for the specified {@link visad.Display Display}.
+   *
+   * @param dpy The {@link visad.Display Display} to monitor.
+   */
+  public DisplayMonitorImpl(DisplayImpl dpy)
+  {
+    Name = dpy.getName() + ":Mon";
+
+    dpy.addDisplayListener(this);
+    dpy.addMessageListener(this);
+
+    myDisplay = dpy;
+
+    listeners = new ArrayList();
+  }
+
+  /**
+   * Adds the specified listener to receive {@link MonitorEvent MonitorEvents}
+   * when the monitored {@link visad.Display Display's} state changes.
+   *
+   * @param listener The listener to add.
+   * @param id The unique listener identifier.
+   *
+   * @exception VisADException If the listener {@link java.util.Vector Vector}
+   * 				is uninitialized.
+   */
+  public void addListener(MonitorCallback listener, int id)
+    throws RemoteException, VisADException
+  {
+    MonitorSyncer ms = new MonitorSyncer(myDisplay.getName(), listener, id);
+    synchronized (listeners) {
+      listeners.add(ms);
+    }
+  }
+
+  /**
+   * Adds the specified remote display to receive {@link MonitorEvent MonitorEvents}
+   * when the monitored {@link visad.Display Display's} state changes.
+   *
+   * @param rmtDpy The remote display to add.
+   * @param id The unique listener identifier.
+   *
+   * @exception VisADException If the listener {@link java.util.Vector Vector}
+   * 				is uninitialized.
+   */
+  public void addListener(RemoteDisplay rmtDpy, int id)
+    throws RemoteException, VisADException
+  {
+    MonitorSyncer ms = new MonitorSyncer(myDisplay.getName(), rmtDpy, id);
+    synchronized (listeners) {
+      listeners.add(ms);
+    }
+  }
+
+  /**
+   * Initializes links so that {@link MonitorEvent MonitorEvents} will be
+   * exchanged with the specified remote {@link visad.Display Display}.
+   *
+   * @param rd The remote {@link visad.Display Display} to synchronize.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If the inter-{@link visad.Display Display}
+   * 					links could not be made.
+   */
+  public void addRemoteListener(RemoteDisplay rd)
+    throws RemoteException, RemoteVisADException
+  {
+    RemoteDisplayMonitor rdm = rd.getRemoteDisplayMonitor();
+    final int id = negotiateUniqueID(rdm);
+
+    DisplaySyncImpl dsi = (DisplaySyncImpl )myDisplay.getDisplaySync();
+    RemoteDisplaySyncImpl wrap = new RemoteDisplaySyncImpl(dsi);
+    try {
+      rdm.addListener(new RemoteDisplayImpl(myDisplay), id);
+    } catch (Exception e) {
+      throw new RemoteVisADException("Couldn't make this object" +
+                                     " a listener for the remote display");
+    }
+
+    boolean unwind = false;
+    try {
+      addListener(rd, id);
+    } catch (Exception e) {
+      unwind = true;
+    }
+
+    if (unwind) {
+      removeListener(wrap);
+      throw new RemoteVisADException("Couldn't add listener for the" +
+                                     " remote display to this object");
+    }
+  }
+
+  /**
+   * Returns a suggestion for a unique listener identifier which is
+   * equal to or greater than the supplied ID.
+   *
+   * @param id The identifier to check.
+   */
+  public int checkID(int id)
+  {
+    synchronized (listeners) {
+      boolean failed = true;
+      while (failed) {
+        failed = false;
+        ListIterator iter = listeners.listIterator();
+        while (iter.hasNext()) {
+          MonitorSyncer li = (MonitorSyncer )iter.next();
+          if (id == UNKNOWN_LISTENER_ID || li.getID() == id) {
+            id = getNextListenerID();
+            failed = true;
+            break;
+          }
+        }
+      }
+    }
+
+    return id;
+  }
+
+  /**
+   * Handles {@link visad.Control Control} changes.<BR><BR>
+   * If the {@link visad.ControlEvent ControlEvent} is not ignored, a
+   * {@link ControlMonitorEvent ControlMonitorEvent} will be sent to all listeners.
+   *
+   * @param evt The details of the {@link visad.Control Control} change.
+   */
+  public void controlChanged(ControlEvent evt)
+  {
+    // CTR - notify display slaves of control changes
+    if (myDisplay.hasSlaves()) {
+      // construct properly formatted control change string
+      Control control = evt.getControl();
+      String msg = control.getSaveString();
+      Class c = control.getClass();
+      Vector v = myDisplay.getControls(c);
+      int index = -1;
+      for (int i=0; i<v.size(); i++) {
+        Control ctrl = (Control) v.elementAt(i);
+        if (control == ctrl) {
+          index = i;
+          break;
+        }
+      }
+      String message = c.getName() + "\n" + index + "\n" + msg;
+      myDisplay.updateSlaves(message);
+    }
+
+    // don't bother if nobody's listening
+    if (!hasListeners()) {
+      return;
+    }
+
+    if (evt.getControl() instanceof AnimationControl) {
+      // ignore all animation-related events
+      return;
+    }
+
+    Control ctlClone = (Control )(evt.getControl().clone());
+    ControlMonitorEvent ctlEvt;
+    try {
+      ctlEvt = new ControlMonitorEvent(MonitorEvent.CONTROL_CHANGED,
+                                       evt.getRemoteId(), ctlClone);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      ctlEvt = null;
+    }
+
+    if (ctlEvt != null) {
+      notifyListeners(ctlEvt);
+    }
+  }
+
+  /**
+   * Handles ScalarMap control changes.<BR>
+   * <FONT SIZE="-1">This is just a stub which ignores the event.</FONT>
+   *
+   * @param evt The details of the {@link visad.ScalarMap ScalarMap} change.
+   */
+  public void controlChanged(ScalarMapControlEvent evt)
+  {
+    // don't bother if nobody's listening
+    if (!hasListeners()) {
+      return;
+    }
+
+    int id = evt.getId();
+    if (id == ScalarMapEvent.CONTROL_REMOVED ||
+        id == ScalarMapEvent.CONTROL_REPLACED)
+    {
+      evt.getControl().removeControlListener(this);
+    }
+
+    if (id == ScalarMapEvent.CONTROL_REPLACED ||
+        id == ScalarMapEvent.CONTROL_ADDED)
+    {
+      Control ctl = evt.getScalarMap().getControl();
+      controlChanged(new ControlEvent(ctl, evt.getRemoteId()));
+      ctl.addControlListener(this);
+    }
+  }
+
+  /** destroy this monitor */
+  public void destroy()
+  {
+    sync = null;
+    myDisplay.removeDisplayListener(this);
+  }
+
+  /**
+   * Handles notification of objects being added to or removed from
+   * the {@link visad.Display Display}.<BR><BR>
+   * If the {@link visad.DisplayEvent DisplayEvent} is not ignored, a
+   * {@link MapMonitorEvent MapMonitorEvent} or {@link ReferenceMonitorEvent ReferenceMonitorEvent}
+   * will be sent to all listeners.
+   *
+   * @param evt The details of the {@link visad.Display Display} change.
+   */
+  public void displayChanged(DisplayEvent evt)
+  {
+    // don't bother if nobody's listening
+    if (!hasListeners()) {
+      return;
+    }
+
+    MapMonitorEvent mapEvt;
+    ReferenceMonitorEvent refEvt;
+
+    switch (evt.getId()) {
+    case DisplayEvent.MOUSE_PRESSED:
+    case DisplayEvent.TRANSFORM_DONE:
+    case DisplayEvent.FRAME_DONE:
+    case DisplayEvent.MOUSE_PRESSED_CENTER:
+    case DisplayEvent.MOUSE_PRESSED_LEFT:
+    case DisplayEvent.MOUSE_PRESSED_RIGHT:
+    case DisplayEvent.MOUSE_RELEASED:
+    case DisplayEvent.MOUSE_RELEASED_CENTER:
+    case DisplayEvent.MOUSE_RELEASED_LEFT:
+    case DisplayEvent.MOUSE_RELEASED_RIGHT:
+      break;
+    case DisplayEvent.MAP_ADDED:
+      ScalarMap map = (ScalarMap )((DisplayMapEvent )evt).getMap().clone();
+      try {
+        mapEvt = new MapMonitorEvent(MonitorEvent.MAP_ADDED,
+                                     evt.getRemoteId(), map);
+      } catch (VisADException ve) {
+        ve.printStackTrace();
+        mapEvt = null;
+      }
+      if (mapEvt != null) {
+        notifyListeners(mapEvt);
+      }
+      break;
+    case DisplayEvent.MAP_REMOVED:
+      map = (ScalarMap )((DisplayMapEvent )evt).getMap().clone();
+      try {
+        mapEvt = new MapMonitorEvent(MonitorEvent.MAP_REMOVED,
+                                     evt.getRemoteId(), map);
+      } catch (VisADException ve) {
+        ve.printStackTrace();
+        mapEvt = null;
+      }
+      if (mapEvt != null) {
+        notifyListeners(mapEvt);
+      }
+      break;
+    case DisplayEvent.MAPS_CLEARED:
+      boolean sendClear;
+      try {
+        sendClear = sync.isLocalClear();
+      } catch (RemoteException re) {
+        sendClear = false;
+      }
+
+      if (sendClear) {
+        try {
+          mapEvt = new MapMonitorEvent(MonitorEvent.MAPS_CLEARED,
+                                       evt.getRemoteId(), null);
+        } catch (VisADException ve) {
+          ve.printStackTrace();
+          mapEvt = null;
+        }
+
+        if (mapEvt != null) {
+          notifyListeners(mapEvt);
+        }
+      }
+      break;
+    case DisplayEvent.REFERENCE_ADDED:
+      DisplayReferenceEvent dre = (DisplayReferenceEvent )evt;
+
+      DataDisplayLink link = dre.getDataDisplayLink();
+
+      DataReference linkRef;
+      try {
+        linkRef = link.getDataReference();
+      } catch (Exception e) {
+        linkRef = null;
+      }
+
+      if (linkRef != null && linkRef instanceof DataReferenceImpl) {
+        RemoteReferenceLinkImpl rrl;
+        try {
+          rrl = new RemoteReferenceLinkImpl(link);
+        } catch (RemoteException re) {
+          re.printStackTrace();
+          // ignore attempt to link in a remote reference
+          rrl = null;
+        }
+
+        if (rrl != null) {
+          try {
+            refEvt = new ReferenceMonitorEvent(MonitorEvent.REFERENCE_ADDED,
+                                               evt.getRemoteId(), rrl);
+          } catch (VisADException ve) {
+            ve.printStackTrace();
+            refEvt = null;
+          }
+          if (refEvt != null) {
+            notifyListeners(refEvt);
+          }
+        }
+      }
+      break;
+
+    case DisplayEvent.DESTROYED:
+      // Hmmm ... not sure what we want to do here
+      break;
+
+    default:
+      System.err.println("DisplayMonitorImpl.displayChanged: " + Name +
+                         " got " + evt.getClass().getName() + " " + evt +
+                         "=>" + evt.getDisplay());
+      System.exit(1);
+      break;
+    }
+  }
+
+  /**
+   * Return the ID associated with the specified <tt>RemoteDisplay</tt>.
+   *
+   * @return <tt>UNKNOWN_LISTENER_ID</tt> if not found;
+   *         otherwise, returns the ID.
+   */
+  public int getConnectionID(RemoteDisplay rmtDpy)
+  {
+    synchronized (listeners) {
+      ListIterator iter = listeners.listIterator();
+      while (iter.hasNext()) {
+        MonitorSyncer li = (MonitorSyncer )iter.next();
+
+        if (li.isMonitored(rmtDpy)) {
+          return li.getID();
+        }
+      }
+    }
+
+    return UNKNOWN_LISTENER_ID;
+  }
+
+  private int getNextListenerID()
+  {
+    synchronized (listeners) {
+      if (nextListenerID == UNKNOWN_LISTENER_ID) {
+        // don't let anyone have the magic ID
+        nextListenerID++;
+      }
+      return nextListenerID++;
+    }
+  }
+
+  /**
+   * Returns <tt>true</tt> if there is a {@link MonitorEvent MonitorEvent}
+   * for the specified {@link visad.Control Control} waiting to be delivered to
+   * any listener.
+   *
+   * @param ctl The {@link visad.Control Control} being found.
+   */
+  public boolean hasEventQueued(Control ctl)
+  {
+    return hasEventQueued(0, ctl, true);
+  }
+
+  /**
+   * Returns <tt>true</tt> if there is a {@link MonitorEvent MonitorEvent}
+   * for the specified {@link visad.Control Control} waiting to be delivered to the
+   * listener with the specified id.
+   *
+   * @param listenerID The identifier for the listener.
+   * @param ctl The {@link visad.Control Control} being found.
+   */
+  public boolean hasEventQueued(int listenerID, Control ctl)
+  {
+    return hasEventQueued(listenerID, ctl, false);
+  }
+
+
+  /**
+   * Returns <tt>true</tt> if there is a {@link MonitorEvent MonitorEvent}
+   * for the specified {@link visad.Control Control} waiting to be delivered to the
+   * listener with the specified id.
+   *
+   * @param listenerID The identifier for the listener.
+   * @param ctl The {@link visad.Control Control} being found.
+   * @param anyListener return <tt>true</tt> if there is a
+   *                    {@link MonitorEvent MonitorEvent} queued for any
+   *                    listener.
+   */
+  private boolean hasEventQueued(int listenerID, Control ctl,
+                                 boolean anyListener)
+  {
+    boolean result = false;
+
+    synchronized (listeners) {
+      ListIterator iter = listeners.listIterator();
+      while (iter.hasNext()) {
+        MonitorSyncer li = (MonitorSyncer )iter.next();
+
+        if (anyListener || listenerID == li.getID()) {
+          result = li.hasControlEventQueued(ctl);
+          if (!anyListener || result) {
+            break;
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+  // WLH 12 April 2001
+  public boolean isEmpty() {
+    boolean result = true;
+    synchronized (listeners) {
+      ListIterator iter = listeners.listIterator();
+      while (iter.hasNext()) {
+        MonitorSyncer li = (MonitorSyncer )iter.next();
+        if (!li.isEmpty()) result = false;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns <tt>true</tt> if there are listeners for this display.
+   */
+  private boolean hasListeners()
+  {
+    return (listeners.size() > 0);
+  }
+
+  /**
+   * Handles ScalarMap data changes.<BR><BR>
+   * If the {@link visad.ScalarMapEvent ScalarMapEvent} is not ignored, a
+   * {@link MapMonitorEvent MapMonitorEvent} will be sent to all listeners.
+   *
+   * @param evt The details of the {@link visad.ScalarMap ScalarMap} change.
+   */
+  public void mapChanged(ScalarMapEvent evt)
+  {
+    // don't bother if nobody's listening
+    if (!hasListeners()) {
+      return;
+    }
+
+    if (evt.getId() == ScalarMapEvent.AUTO_SCALE) {
+      // ignore internal autoscale events
+      return;
+    }
+
+    ScalarMap mapClone = (ScalarMap )(evt.getScalarMap().clone());
+
+    MapMonitorEvent mapEvt;
+    try {
+      mapEvt = new MapMonitorEvent(MonitorEvent.MAP_CHANGED,
+                                   evt.getRemoteId(),  mapClone);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      mapEvt = null;
+    }
+
+    if (mapEvt != null) {
+      notifyListeners(mapEvt);
+    }
+  }
+
+  /**
+   * Negotiates a listener identifier which is unique for both this
+   * {@link DisplayMonitor DisplayMonitor} and the remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param rdm The remote {@link DisplayMonitor DisplayMonitor} to negotiate with.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If negotiations failed to converge
+   * 					upon a common ID.
+   */
+  private int negotiateUniqueID(RemoteDisplayMonitor rdm)
+    throws RemoteException, RemoteVisADException
+  {
+    // maximum number of rounds of ID negotiation
+    final int MAX_ROUNDS = 20;
+
+    int rmtID = getNextListenerID();
+
+    int id;
+    int round = 0;
+    do {
+      id = rmtID;
+      rmtID = rdm.checkID(id);
+      if (rmtID != id) {
+        id = checkID(rmtID);
+      }
+      round++;
+    } while (id != rmtID && round < MAX_ROUNDS);
+
+    if (round >= MAX_ROUNDS) {
+      throw new RemoteVisADException("ID negotiation failed");
+    }
+
+    return id;
+  }
+
+  /**
+   * Forwards the {@link MonitorEvent MonitorEvent} to all the listeners
+   * associated with this {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param evt The event to forward.
+   */
+  public void notifyListeners(MonitorEvent evt)
+  {
+    final int evtID = evt.getOriginator();
+
+    synchronized (listeners) {
+      ListIterator iter = listeners.listIterator();
+      while (iter.hasNext()) {
+        MonitorSyncer li = (MonitorSyncer )iter.next();
+
+        if (li.isDead()) {
+          // notify Display that this connection is gone
+          myDisplay.lostCollabConnection(li.getID());
+
+          // delete dead listeners
+          iter.remove();
+          continue;
+        }
+
+        if (evtID == li.getID()) {
+          // don't return event to its source
+          continue;
+        }
+
+        li.addEvent(evt);
+      }
+    }
+  }
+
+  /**
+   * Handles <tt>MessageEvent</tt> forwarding.
+   *
+   * @param msg The message to forward.
+   */
+  public void receiveMessage(MessageEvent msg)
+  {
+    // don't bother if nobody's listening
+    if (!hasListeners()) {
+      return;
+    }
+
+    MessageMonitorEvent msgEvt;
+    try {
+      msgEvt = new MessageMonitorEvent(MonitorEvent.MESSAGE_SENT,
+                                   msg.getOriginatorId(),  msg);
+    } catch (RemoteException re) {
+      re.printStackTrace();
+      msgEvt = null;
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      msgEvt = null;
+    }
+
+    if (msgEvt != null) {
+      notifyListeners(msgEvt);
+    }
+  }
+
+  /**
+   * Stops forwarding {@link MonitorEvent MonitorEvent}s to the specified listener.
+   *
+   * @param l Listener to remove.
+   */
+  public void removeListener(MonitorCallback l)
+  {
+    if (l != null) {
+      synchronized (listeners) {
+        ListIterator iter = listeners.listIterator();
+        while (iter.hasNext()) {
+          MonitorSyncer li = (MonitorSyncer )iter.next();
+          if (li.getListener().equals(l)) {
+            iter.remove();
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Set the display synchronization object for this display
+   */
+  public void setDisplaySync(DisplaySync sync)
+  {
+    this.sync = sync;
+  }
+
+  /**
+   * Returns the name of this {@link DisplayMonitor DisplayMonitor}.
+   */
+  public String toString()
+  {
+    return Name;
+  }
+}
diff --git a/visad/collab/DisplaySync.java b/visad/collab/DisplaySync.java
new file mode 100644
index 0000000..9b9e416
--- /dev/null
+++ b/visad/collab/DisplaySync.java
@@ -0,0 +1,38 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+/**
+ * <CODE>DisplaySync</CODE> is the interface for objects which synchronize
+ * a <CODE>Display</CODE> with one or more <CODE>RemoteDisplay</CODE>s.
+ */
+public interface DisplaySync
+  extends MonitorCallback
+{
+  void destroy()
+    throws RemoteException;
+  boolean isLocalClear()
+    throws RemoteException;
+}
diff --git a/visad/collab/DisplaySyncImpl.java b/visad/collab/DisplaySyncImpl.java
new file mode 100644
index 0000000..48aac38
--- /dev/null
+++ b/visad/collab/DisplaySyncImpl.java
@@ -0,0 +1,545 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.Vector;
+
+import visad.ConstantMap;
+import visad.Control;
+import visad.DataRenderer;
+import visad.DisplayImpl;
+import visad.RemoteDataReference;
+import visad.RemoteDisplayImpl;
+import visad.RemoteReferenceLink;
+import visad.RemoteVisADException;
+import visad.ScalarMap;
+import visad.VisADException;
+
+public class DisplaySyncImpl
+  implements Comparator, DisplaySync, Runnable
+{
+  private String Name;
+  private DisplayImpl myDisplay;
+  private DisplayMonitor monitor;
+
+  private Object mapClearSync = new Object();
+  private int mapClearCount = 0;
+
+  private boolean dead = false;
+
+  private Object tableLock = new Object();
+  private Thread thisThread = null;
+
+  private HashMap current = new HashMap();
+  private HashMap diverted = null;
+
+  public DisplaySyncImpl(DisplayImpl dpy)
+    throws RemoteException
+  {
+    Name = dpy.getName() + ":Sync";
+    myDisplay = dpy;
+    monitor = dpy.getDisplayMonitor();
+    monitor.setDisplaySync(this);
+  }
+
+  /**
+   * Adds the specified data reference to this <TT>Display</TT>.
+   *
+   * @param link The link to the remote data reference.
+   *
+   * @exception VisADException If a link could not be made for the
+   * 				remote data reference.
+   */
+  private void addLink(RemoteReferenceLink link)
+    throws VisADException
+  {
+    // build array of ConstantMap values
+    ConstantMap[] cm = null;
+    try {
+      Vector v = link.getConstantMapVector();
+      int len = v.size();
+      if (len > 0) {
+        cm = new ConstantMap[len];
+        for (int i = 0; i < len; i++) {
+          ConstantMap tmp = (ConstantMap )v.elementAt(i);
+          cm[i] = (ConstantMap )tmp.clone();
+        }
+      }
+    } catch (Exception e) {
+      throw new VisADException("Couldn't copy ConstantMaps" +
+                               " for remote DataReference");
+    }
+
+    // get reference to Data object
+    RemoteDataReference ref;
+    try {
+      ref = link.getReference();
+    } catch (Exception e) {
+      throw new VisADException("Couldn't copy remote DataReference");
+    }
+
+    if (ref != null) {
+
+      DataRenderer dr = myDisplay.getDisplayRenderer().makeDefaultRenderer();
+      String defaultClass = dr.getClass().getName();
+
+      // get proper DataRenderer
+      DataRenderer renderer;
+      try {
+        String newClass = link.getRendererClassName();
+        if (newClass == defaultClass) {
+          renderer = null;
+        } else {
+          Object obj = Class.forName(newClass).newInstance();
+          renderer = (DataRenderer )obj;
+        }
+      } catch (Exception e) {
+        throw new VisADException("Couldn't copy remote DataRenderer " +
+                                 "name; using " + defaultClass);
+      }
+
+      // build RemoteDisplayImpl to which reference is attached
+      try {
+        RemoteDisplayImpl rd = new RemoteDisplayImpl(myDisplay);
+
+        // if this reference uses the default renderer...
+        if (renderer == null) {
+          rd.addReference(ref, cm);
+        } else {
+          rd.addReferences(renderer, ref, cm);
+        }
+      } catch (Exception e) {
+        e.printStackTrace();
+        throw new VisADException("Couldn't add remote DataReference " +
+                                 ref + ": " + e.getClass().getName() +
+                                 ": " + e.getMessage());
+      }
+    }
+  }
+
+  public int compare(Object o1, Object o2)
+  {
+    return (((MonitorEvent )o1).getSequenceNumber() -
+            ((MonitorEvent )o2).getSequenceNumber());
+  }
+
+  public void destroy()
+  {
+    monitor = null;
+    myDisplay = null;
+  }
+ 
+  /**
+   * Finds the first map associated with this <TT>Display</TT>
+   * which matches the specified <TT>ScalarMap</TT>.
+   *
+   * @param map The <TT>ScalarMap</TT> to find.
+   */
+  private ScalarMap findMap(ScalarMap map)
+  {
+    ScalarMap found = null;
+
+    boolean isConstMap;
+    Vector v;
+    if (map instanceof ConstantMap) {
+      v = myDisplay.getConstantMapVector();
+      isConstMap = true;
+    } else {
+      v = myDisplay.getMapVector();
+      isConstMap = false;
+    }
+
+    ListIterator iter = v.listIterator();
+    while (iter.hasNext()) {
+      ScalarMap sm = (ScalarMap )iter.next();
+      if (sm.equals(map)) {
+        found = sm;
+        break;
+      }
+    }
+
+    return found;
+  }
+
+  /**
+   * Start event callback.
+   */
+  public void eventReady(RemoteEventProvider provider, Object key)
+  {
+    synchronized (tableLock) {
+      if (thisThread != null) {
+        if (diverted == null) {
+          diverted = new HashMap();
+        }
+        diverted.put(key, provider);
+      } else {
+        current.put(key, provider);
+        thisThread = new Thread(this);
+        thisThread.start();
+      }
+    }
+  }
+
+  public String getName() { return Name; }
+
+  public boolean isLocalClear()
+  {
+    boolean result = true;
+    synchronized (mapClearSync) {
+      if (mapClearCount > 0) {
+        mapClearCount--;
+        result = false;
+      }
+    }
+
+    return result;
+  }
+
+  public boolean isThreadRunning()
+  {
+    return (thisThread != null);
+  }
+
+  private void processMap(HashMap map)
+  {
+    MonitorEvent[] list = new MonitorEvent[map.size()];
+
+    // build the array of events
+    Iterator iter = map.keySet().iterator();
+    for (int i = list.length - 1; i >= 0; i--) {
+      if (iter.hasNext()) {
+        String key = (String )iter.next();
+        list[i] = (MonitorEvent )map.get(key);
+      } else {
+        list[i] = null;
+      }
+    }
+
+    // sort events by order of creation
+    Arrays.sort(list, this);
+
+    int i, attempts;
+    i = attempts = 0;
+    while (i < list.length) {
+      try {
+        processOneEvent(list[i]);
+        i++;
+      } catch (RemoteException re) {
+        if (attempts++ < 5) {
+          // wait a bit, then try again to request the events
+          try { Thread.sleep(500); } catch (InterruptedException ie) { }
+        } else {
+          // if we failed to connect for 10 times, give up
+          dead = true;
+          break;
+        }
+      } catch (RemoteVisADException rve) {
+        System.err.println("While processing " + list[i] + ":");
+        i++;
+        rve.printStackTrace();
+      }
+    }
+  }
+
+  private void processOneEvent(MonitorEvent evt)
+    throws RemoteException, RemoteVisADException
+  {
+    Control lclCtl, rmtCtl;
+    ScalarMap lclMap, rmtMap;
+
+    switch (evt.getType()) {
+    case MonitorEvent.MAP_ADDED:
+
+      rmtMap = ((MapMonitorEvent )evt).getMap();
+
+      // if we haven't already added this map...
+      if (findMap(rmtMap) == null) {
+/* WLH 26 Dec 2002
+        if (!myDisplay.getRendererVector().isEmpty()) {
+          System.err.println("Late addMap: " + rmtMap);
+        } else {
+*/
+          try {
+            myDisplay.addMap(rmtMap, evt.getOriginator());
+          } catch (VisADException ve) {
+            ve.printStackTrace();
+            throw new RemoteVisADException("Map " + rmtMap + " not added: " +
+                                           ve);
+          }
+/*
+        }
+*/
+      }
+      break;
+    case MonitorEvent.MAP_REMOVED:
+
+      rmtMap = ((MapMonitorEvent )evt).getMap();
+
+      // if we have already added this map...
+      if (findMap(rmtMap) != null) {
+          try {
+            myDisplay.removeMap(rmtMap, evt.getOriginator());
+          } catch (VisADException ve) {
+            ve.printStackTrace();
+            throw new RemoteVisADException("Map " + rmtMap + " not removed: " +
+                                           ve);
+          }
+      }
+      break;
+    case MonitorEvent.MAP_CHANGED:
+      rmtMap = ((MapMonitorEvent )evt).getMap();
+      lclMap = findMap(rmtMap);
+      if (lclMap == null) {
+        throw new RemoteVisADException("ScalarMap " + rmtMap + " not found");
+      }
+      // CTR 2 June 2000 - do not set map range if already set (avoid loops)
+      double[] rng = rmtMap.getRange();
+      double[] lclRng = lclMap.getRange();
+      if (rng[0] != lclRng[0] || rng[1] != lclRng[1]) {
+        try {
+          lclMap.setRange(rng[0], rng[1], evt.getOriginator());
+        } catch (VisADException ve) {
+          throw new RemoteVisADException("Map not changed: " + ve);
+        }
+      }
+      break;
+    case MonitorEvent.MAPS_CLEARED:
+      try {
+        myDisplay.removeAllReferences();
+        myDisplay.clearMaps();
+      } catch (VisADException ve) {
+        throw new RemoteVisADException("Maps not cleared: " + ve);
+      } catch (NullPointerException npe) {
+        npe.printStackTrace();
+        throw new RemoteVisADException("Maps not cleared");
+      }
+      break;
+    case MonitorEvent.REFERENCE_ADDED:
+      RemoteReferenceLink ref = ((ReferenceMonitorEvent )evt).getLink();
+      try {
+        addLink(ref);
+      } catch (VisADException ve) {
+        throw new RemoteVisADException("DataReference " + ref +
+                                       " not found by " + Name + ": " +
+                                       ve.getMessage());
+      }
+
+      break;
+    case MonitorEvent.CONTROL_INIT_REQUESTED:
+      // !!! DON'T FORWARD INIT EVENTS TO LISTENERS !!!
+
+      rmtCtl = ((ControlMonitorEvent )evt).getControl();
+      lclCtl = myDisplay.getControl(rmtCtl.getClass(),
+                                    rmtCtl.getInstanceNumber());
+      if (lclCtl == null) {
+        // didn't find control ... maybe it doesn't exist yet?
+        break;
+      }
+
+      try {
+        ControlMonitorEvent cme;
+        cme = new ControlMonitorEvent(MonitorEvent.CONTROL_CHANGED,
+                                      (Control )lclCtl.clone());
+        monitor.notifyListeners(cme);
+      } catch (VisADException ve) {
+        throw new RemoteVisADException("Control " + rmtCtl +
+                                       " not changed by " + Name + ": " + ve);
+      }
+      break;
+    case MonitorEvent.CONTROL_CHANGED:
+      rmtCtl = ((ControlMonitorEvent )evt).getControl();
+      lclCtl = myDisplay.getControl(rmtCtl.getClass(),
+                                    rmtCtl.getInstanceNumber());
+
+      // skip this if we have change events to deliver for this control
+      if (lclCtl != null &&
+          !monitor.hasEventQueued(evt.getOriginator(), lclCtl))
+      {
+
+        try {
+          lclCtl.syncControl(rmtCtl);
+        } catch (VisADException ve) {
+          throw new RemoteVisADException("Control " + lclCtl +
+                                         " not changed by " + Name + ": " +
+                                         ve.getMessage());
+        }
+      }
+
+      break;
+    case MonitorEvent.MESSAGE_SENT:
+      myDisplay.sendMessage(((MessageMonitorEvent )evt).getMessage());
+      break;
+    default:
+      throw new RemoteVisADException("Event " + evt + " not handled");
+    }
+  }
+
+  private HashMap requestEventTable(HashMap table)
+    throws RemoteException
+  {
+    HashMap map = null;
+
+    Iterator iter = table.keySet().iterator();
+    while (iter.hasNext()) {
+      String key = (String )iter.next();
+      RemoteEventProvider provider = (RemoteEventProvider )table.get(key);
+      iter.remove();
+
+      MonitorEvent evt = requestOneEvent(key, provider);
+      if (evt != null) {
+        if (map == null) {
+          map = new HashMap();
+        }
+        map.put(key, evt);
+      }
+    }
+
+    return map;
+  }
+
+  private MonitorEvent requestOneEvent(String key,
+                                       RemoteEventProvider provider)
+    throws RemoteException
+  {
+    // get the event
+    MonitorEvent evt;
+    try {
+      evt = provider.getEvent(key);
+    } catch (RemoteVisADException rve) {
+      rve.printStackTrace();
+      throw new RemoteException(rve.getMessage());
+    }
+
+    if (evt == null) {
+      // if it's already been picked up, we're done
+      return null;
+    }
+
+    switch (evt.getType()) {
+    case MonitorEvent.MAPS_CLEARED:
+      synchronized (mapClearSync) {
+        mapClearCount++;
+      }
+      break;
+    case MonitorEvent.CONTROL_CHANGED:
+      boolean result;
+      try {
+        result = monitor.hasEventQueued(evt.getOriginator(),
+                                        ((ControlMonitorEvent )evt).getControl());
+      } catch (RemoteException re) {
+        re.printStackTrace();
+        result = false;
+      }
+
+      if (result) {
+        // drop this event since we're about to override it
+        return null;
+      }
+      break;
+    }
+
+    return evt;
+  }
+
+  /**
+   * Requests events from the remote provider(s).
+   */
+  public void run()
+  {
+    HashMap map = null;
+
+    boolean done = false;
+    try {
+
+      int attempts = 0;
+      while (!done) {
+        HashMap newMap;
+        try {
+          newMap = requestEventTable(current);
+          done = true;
+        } catch (RemoteException re) {
+          if (attempts++ < 5) {
+            // wait a bit, then try again to request the events
+            try { Thread.sleep(500); } catch (InterruptedException ie) { }
+            newMap = null;
+          } else {
+            // if we failed to connect for 10 times, give up
+            dead = true;
+            break;
+          }
+        }
+
+        if (map == null) {
+          map = newMap;
+        } else if (newMap != null) {
+          map.putAll(newMap);
+        }
+
+        if (done) {
+          synchronized (tableLock) {
+            if (!undivertEvents()) {
+              break;
+            }
+
+            done = false;
+          }
+        }
+      }
+    } finally {
+
+      if (map != null) {
+        processMap(map);
+      }
+
+      // indicate that the thread has exited
+      synchronized (tableLock) {
+        thisThread = null;
+      }
+    }
+  }
+
+  /**
+   * Returns <TT>true</TT> if there were diverted requests.
+   */
+  private boolean undivertEvents()
+  {
+    final boolean undivert;
+    synchronized (tableLock) {
+      // if there are events queued, restore them to the main table
+      undivert = (diverted != null);
+      if (undivert) {
+        current = diverted;
+        diverted = null;
+      }
+    }
+
+    return undivert;
+  }
+}
diff --git a/visad/collab/MapMonitorEvent.java b/visad/collab/MapMonitorEvent.java
new file mode 100644
index 0000000..1d94694
--- /dev/null
+++ b/visad/collab/MapMonitorEvent.java
@@ -0,0 +1,208 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import visad.ConstantMap;
+import visad.ScalarMap;
+import visad.VisADException;
+
+/**
+ * <CODE>MapMonitorEvent</CODE> is the VisAD class for
+ * <CODE>ScalarMap</CODE>-related events from display monitors.
+ * They are sourced by <CODE>DisplayMonitor</CODE> objects and received by
+ * <CODE>MonitorCallback</CODE> objects.
+ */
+public class MapMonitorEvent
+  extends MonitorEvent
+{
+  private ScalarMap map;
+
+  /**
+   * Creates a <CODE>MapMonitorEvent</CODE> for the specified
+   * <CODE>ScalarMap</CODE>.
+   *
+   * @param type The event type (either <CODE>MonitorEvent.MAP_ADDED</CODE>,
+   * 			<CODE>MonitorEvent.MAP_REMOVED</CODE>, or
+   * 			<CODE>MonitorEvent.MAP_CHANGED</CODE>, or
+   * 			<CODE>MonitorEvent.MAPS_CLEARED</CODE>.)
+   * @param map the <CODE>ScalarMap</CODE> (or <CODE>ConstantMap</CODE>).
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public MapMonitorEvent(int type, ScalarMap map)
+    throws VisADException
+  {
+    this(type, -1, map);
+  }
+
+  /**
+   * Creates a <CODE>MapMonitorEvent</CODE> for the specified
+   * <CODE>ScalarMap</CODE>.
+   *
+   * @param type The event type (either <CODE>MonitorEvent.MAP_ADDED</CODE>,
+   * 			<CODE>MonitorEvent.MAP_REMOVED</CODE>, or
+   * 			<CODE>MonitorEvent.MAP_CHANGED</CODE>, or
+   * 			<CODE>MonitorEvent.MAPS_CLEARED</CODE>.)
+   * @param originator The ID of the connection from which this event came,
+   * 			relative to the receiver of the event.
+   * @param map the <CODE>ScalarMap</CODE> (or <CODE>ConstantMap</CODE>).
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public MapMonitorEvent(int type, int originator, ScalarMap map)
+    throws VisADException
+  {
+    super(type, originator);
+    if (type != MAP_ADDED && type != MAP_CHANGED && type != MAPS_CLEARED &&
+        type != MAP_REMOVED) {
+      throw new VisADException("Bad type " + type);
+    }
+    if (map == null && type != MAPS_CLEARED) {
+      throw new VisADException("Null map");
+    }
+    this.map = map;
+  }
+
+  /**
+   * Get the key used to uniquely identify this event.
+   *
+   * @return The unique key.
+   */
+  public String getKey()
+  {
+    String key;
+
+    if (type == MonitorEvent.MAPS_CLEARED) {
+      key = "MAPS_CLEARED";
+    } else {
+      key = map.toString();
+      switch (type) {
+      case MonitorEvent.MAP_ADDED:
+        key = "ADD " + key;
+        break;
+      case MonitorEvent.MAP_REMOVED:
+        key = "RMV " + key;
+        break;
+      case MonitorEvent.MAP_CHANGED:
+        key = "CHG " + key;
+        break;
+      default:
+        System.err.println("MapMonitorEvent type " + type +
+                           " not handled by getKey()");
+        break;
+      }
+    }
+
+    return key;
+  }
+
+  /**
+   * Gets the <CODE>ScalarMap</CODE> to which this event refers.
+   */
+  public ScalarMap getMap()
+  {
+    return map;
+  }
+
+  /**
+   * Returns <CODE>true</CODE> if the specified object matches this object.
+   *
+   * @param o The object to compare.
+   */
+  public boolean equals(Object o)
+  {
+    if (!(o instanceof MapMonitorEvent)) {
+      return false;
+    }
+
+    MapMonitorEvent evt = (MapMonitorEvent )o;
+    if (getType() != evt.getType()) {
+      return false;
+    }
+
+    if (map == null) {
+      if (evt.map != null) {
+        return false;
+      }
+    } else if (evt.map == null) {
+      return false;
+    } else if (!map.equals(evt.map)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Returns an exact clone of this object.
+   */
+  public Object clone()
+  {
+    MapMonitorEvent evt;
+    try {
+      evt = new MapMonitorEvent(getType(), getOriginator(),
+                                (map == null ? null :
+                                 (ScalarMap )map.clone()));
+      evt.seqNum = seqNum;
+    } catch (VisADException e) {
+      evt = null;
+    }
+    return evt;
+  }
+
+  /**
+   * Returns a <CODE>String</CODE> representation of this object.
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("MapMonitorEvent[");
+buf.append('#');buf.append(getSequenceNumber());buf.append(' ');
+
+    buf.append(getTypeName());
+
+    int orig = getOriginator();
+    if (orig == -1) {
+      buf.append(" Lcl");
+    } else {
+      buf.append(" Rmt ");
+      buf.append(orig);
+    }
+
+    if (map == null) {
+      buf.append(" <null>");
+    } else if (map instanceof ConstantMap) {
+      buf.append(' ');
+      buf.append(((ConstantMap )map).getConstant());
+      buf.append(" -> ");
+      buf.append(map.getDisplayScalar());
+    } else {
+      buf.append(' ');
+      buf.append(map.getScalar());
+      buf.append(" -> ");
+      buf.append(map.getDisplayScalar());
+    }
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/collab/MessageMonitorEvent.java b/visad/collab/MessageMonitorEvent.java
new file mode 100644
index 0000000..ac67185
--- /dev/null
+++ b/visad/collab/MessageMonitorEvent.java
@@ -0,0 +1,234 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.FieldImpl;
+import visad.MessageEvent;
+import visad.RemoteData;
+import visad.RemoteFieldImpl;
+import visad.VisADException;
+
+/**
+ * <CODE>MessageMonitorEvent</CODE> is the VisAD class for
+ * <CODE>MessageEvent</CODE>-related events from display monitors.
+ * They are sourced by <CODE>DisplayMonitor</CODE> objects and received by
+ * <CODE>MonitorCallback</CODE> objects.
+ */
+public class MessageMonitorEvent
+  extends MonitorEvent
+{
+  private int id;
+  private String str;
+  private RemoteData data;
+
+  /**
+   * Creates a <CODE>MessageMonitorEvent</CODE> for the specified
+   * <CODE>MessageEvent</CODE>.
+   *
+   * @param msg the <CODE>MessageEvent</CODE>.
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public MessageMonitorEvent(MessageEvent msg)
+    throws RemoteException, VisADException
+  {
+    this(MESSAGE_SENT, -1, msg);
+  }
+
+  /**
+   * Creates a <CODE>MessageMonitorEvent</CODE> for the specified
+   * <CODE>MessageEvent</CODE>.
+   *
+   * @param type The event type (currently only
+   *                             <CODE>MonitorEvent.MESSAGE_SENT</CODE>.)
+   * @param msg the <CODE>MessageEvent</CODE>.
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public MessageMonitorEvent(int type, MessageEvent msg)
+    throws RemoteException, VisADException
+  {
+    this(type, -1, msg);
+  }
+
+  /**
+   * Creates a <CODE>MessageMonitorEvent</CODE> for the specified
+   * <CODE>MessageEvent</CODE>.
+   *
+   * @param type The event type (currently only
+   *                             <CODE>MonitorEvent.MESSAGE_SENT</CODE>.)
+   * @param originator The ID of the connection from which this event came,
+   * 			relative to the receiver of the event.
+   * @param msg the <CODE>MessageEvent</CODE>.
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public MessageMonitorEvent(int type, int originator, MessageEvent msg)
+    throws RemoteException, VisADException
+  {
+    super(type, originator);
+    if (type != MESSAGE_SENT) {
+      throw new VisADException("Bad type for MessageMonitorEvent");
+    }
+
+    this.id = msg.getId();
+    this.str = msg.getString();
+
+    Data data = msg.getData();
+    if (data == null) {
+      this.data = null;
+    } else if (data instanceof RemoteData) {
+      this.data = (RemoteData )data;
+    } else if (data instanceof FieldImpl) {
+      this.data = new RemoteFieldImpl((FieldImpl )data);
+/*
+    } else if (data instanceof FunctionImpl) {
+      this.data = new RemoteFunctionImpl((FunctionImpl )data);
+    } else if (data instanceof DataImpl) {
+      this.data = new RemoteDataImpl((DataImpl )data);
+*/
+    } else {
+      throw new VisADException("Don't know how to make " +
+                               data.getClass().getName() + " remote!");
+    }
+  }
+
+  /**
+   * Get the key used to uniquely identify this event.
+   *
+   * @return The unique key.
+   */
+  public String getKey()
+  {
+    return Integer.toString(id) + str +
+      (data == null ? "null" : data.toString());
+  }
+
+  /**
+   * Gets the <CODE>ScalarMap</CODE> to which this event refers.
+   */
+  public MessageEvent getMessage()
+  {
+    return new MessageEvent(id, getOriginator(), str, data);
+  }
+
+  /**
+   * Returns <CODE>true</CODE> if the specified object matches this object.
+   *
+   * @param o The object to compare.
+   */
+  public boolean equals(Object o)
+  {
+    if (!(o instanceof MessageMonitorEvent)) {
+      return false;
+    }
+
+    MessageMonitorEvent evt = (MessageMonitorEvent )o;
+    if (getType() != evt.getType()) {
+      return false;
+    }
+
+    if (id != evt.id) {
+      return false;
+    }
+
+    if (str == null) {
+      if (evt.str != null) {
+        return false;
+      }
+    } else if (evt.str == null) {
+      return false;
+    } else if (!str.equals(evt.str)) {
+      return false;
+    }
+
+    if (data == null) {
+      if (evt.data != null) {
+        return false;
+      }
+    } else if (evt.data == null) {
+      return false;
+    } else if (!data.equals(evt.data)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Returns an exact clone of this object.
+   */
+  public Object clone()
+  {
+    MessageMonitorEvent evt;
+/*
+    try {
+      evt = new MessageMonitorEvent(getType(), getOriginator(),
+                                    id, str, data);
+      evt.seqNum = seqNum;
+    } catch (VisADException e) {
+      evt = null;
+    }
+*/
+    evt = null;
+    return evt;
+  }
+
+  /**
+   * Returns a <CODE>String</CODE> representation of this object.
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("MessageMonitorEvent[");
+buf.append('#');buf.append(getSequenceNumber());buf.append(' ');
+
+    buf.append(getTypeName());
+
+    int orig = getOriginator();
+    if (orig == -1) {
+      buf.append(" Lcl");
+    } else {
+      buf.append(" Rmt ");
+      buf.append(orig);
+    }
+
+    buf.append(' ');
+    buf.append(id);
+
+    buf.append(' ');
+    buf.append(str);
+
+    if (data == null) {
+      buf.append(" <null>");
+    } else {
+      buf.append(' ');
+      buf.append(data.toString());
+    }
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/collab/MonitorCallback.java b/visad/collab/MonitorCallback.java
new file mode 100644
index 0000000..0384a38
--- /dev/null
+++ b/visad/collab/MonitorCallback.java
@@ -0,0 +1,43 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import visad.RemoteVisADException;
+
+/**
+ * <CODE>MonitorCallback</CODE> is the interface for receivers of
+ * <CODE>MonitorEvent</CODE>s.
+ */
+public interface MonitorCallback
+{
+  /**
+   * Alert the callback object that an event is ready.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If there was an internal problem.
+   */
+  void eventReady(RemoteEventProvider provider, Object key)
+    throws RemoteException, RemoteVisADException;
+}
diff --git a/visad/collab/MonitorEvent.java b/visad/collab/MonitorEvent.java
new file mode 100644
index 0000000..d851174
--- /dev/null
+++ b/visad/collab/MonitorEvent.java
@@ -0,0 +1,206 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+/**
+ * <CODE>MonitorEvent</CODE> is the VisAD superclass for events from
+ * display monitors.<P>
+ * They are sourced by <CODE>DisplayMonitor</CODE> objects and received by
+ * <CODE>MonitorCallback</CODE> objects.
+ */
+public abstract class MonitorEvent
+  implements java.io.Serializable
+{
+  /**
+   * This event occurs whenever a <CODE>ConstantMap</CODE> or
+   * <CODE>ScalarMap</CODE> is added to a <CODE>Display</CODE>.
+   */
+  public static final int MAP_ADDED = 1;
+  /**
+   * This event occurs whenever a <CODE>ConstantMap</CODE> or
+   * <CODE>ScalarMap</CODE> is added to a <CODE>Display</CODE>.
+   */
+  public static final int MAP_CHANGED = 2;
+  /**
+   * This event occurs whenever the <CODE>ConstantMap</CODE>s and
+   * <CODE>ScalarMap</CODE>s are cleared from a <CODE>Display</CODE>.
+   */
+  public static final int MAPS_CLEARED = 3;
+  /**
+   * This event occurs whenever a <CODE>ConstantMap</CODE> or
+   * <CODE>ScalarMap</CODE> is removed from a <CODE>Display</CODE>.
+   */
+  public static final int MAP_REMOVED = 4;
+
+  /**
+   * This event occurs whenever a <CODE>DataReference</CODE> is added to
+   * a <CODE>Display</CODE>.
+   */
+  public static final int REFERENCE_ADDED = 10;
+  /**
+   * This event occurs whenever a <CODE>DataReference</CODE> is removed
+   * from a <CODE>Display</CODE>.
+   */
+  public static final int REFERENCE_REMOVED = 11;
+
+  /**
+   * This event occurs whenever a <CODE>Control</CODE> attached to a
+   * <CODE>Display</CODE> requests that it be initialized to the state of
+   * a remote <CODE>Control</CODE>.
+   */
+  public static final int CONTROL_INIT_REQUESTED = 20;
+  /**
+   * This event occurs whenever the state of a <CODE>Control</CODE> attached
+   * to a <CODE>Display</CODE> is changed.
+   */
+  public static final int CONTROL_CHANGED = 21;
+
+  /**
+   * This event occurs whenever a message is sent.
+   */
+  public static final int MESSAGE_SENT = 22;
+
+  // these two variables provide a unique number for each event
+  private static int nextSeqNum = 0;
+  protected int seqNum = nextSeqNum++;
+
+  // the MonitorEvent type
+  protected int type;
+
+  // the originator of this MonitorEvent
+  // (relative to the receiving DisplayMonitor)
+  private int originator;
+
+  /**
+   * Creates a <CODE>MonitorEvent</CODE>
+   *
+   * @param type The event type.
+   * @param originator The ID of the connection from which this event came,
+   *                   relative to the receiver of the event.
+   */
+  public MonitorEvent(int type, int originator) {
+    this.type = type;
+    this.originator = originator;
+  }
+
+  /**
+   * Gets the type of this <CODE>MonitorEvent</CODE>.
+   */
+  public int getType()
+  {
+    return type;
+  }
+
+  /**
+   * Get the key used to uniquely identify this event.
+   *
+   * @return The unique key.
+   */
+  public abstract String getKey();
+
+  /**
+   * Gets the originator of this <CODE>MonitorEvent</CODE>.
+   */
+  public int getOriginator()
+  {
+    return originator;
+  }
+
+  /**
+   * Gets the sequence number of this <CODE>MonitorEvent</CODE>.
+   */
+  public int getSequenceNumber() {
+    return seqNum;
+  }
+
+  /**
+   * Returns a <CODE>String</CODE> description of the
+   * specified <CODE>MonitorEvent</CODE> type.
+   *
+   * @param type the <CODE>MonitorEvent</CODE> type.
+   *
+   * @return name of the specified type.
+   */
+  public static String getTypeName(int type)
+  {
+    switch (type) {
+    case MAP_ADDED: return "MAP_ADDED";
+    case MAP_REMOVED: return "MAP_REMOVED";
+    case MAP_CHANGED: return "MAP_CHANGED";
+    case MAPS_CLEARED: return "MAPS_CLEARED";
+    case REFERENCE_ADDED: return "REFERENCE_ADDED";
+    case REFERENCE_REMOVED: return "REFERENCE_REMOVED";
+    case CONTROL_INIT_REQUESTED: return "CONTROL_INIT_REQUESTED";
+    case CONTROL_CHANGED: return "CONTROL_CHANGED";
+    }
+    return "Unknown MonitorEvent Type #" + type;
+  }
+
+  /**
+   * Returns a <CODE>String</CODE> description of this
+   * <CODE>MonitorEvent</CODE>'s type.
+   *
+   * @return name of this event's type.
+   */
+  public String getTypeName()
+  {
+    return getTypeName(type);
+  }
+
+  /**
+   * Sets the originator of this <CODE>MonitorEvent</CODE>.
+   *
+   *  @param id The ID of the connection from which this event came,
+   *            relative to the receiver of the event.
+   */
+  public void setOriginator(int id)
+  {
+    originator = id;
+  }
+
+  /**
+   * Returns an exact copy of this <CODE>MonitorEvent</CODE>.
+   */
+  public abstract Object clone();
+
+  /**
+   * Returns a <CODE>String</CODE> representation of this object.
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("MonitorEvent[");
+buf.append('#');buf.append(getSequenceNumber());buf.append(' ');
+
+    buf.append(getTypeName());
+
+    if (originator == -1) {
+      buf.append(" Lcl");
+    } else {
+      buf.append(" Rmt ");
+      buf.append(originator);
+    }
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/collab/MonitorSyncer.java b/visad/collab/MonitorSyncer.java
new file mode 100644
index 0000000..7aa2c07
--- /dev/null
+++ b/visad/collab/MonitorSyncer.java
@@ -0,0 +1,301 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import visad.Control;
+import visad.RemoteDisplay;
+import visad.RemoteVisADException;
+
+import visad.util.ThreadPool;
+
+class MonitorSyncer
+  implements Runnable
+{
+  private String Name;
+
+  private boolean dead = false;
+
+  private Object cacheLock = new Object();
+  private Thread thisThread = null;
+
+  private ArrayList current = new ArrayList();
+  private ArrayList diverted = null;
+
+  private HashMap eventCache = null;
+ 
+  private RemoteDisplay rmtDpy;
+  private MonitorCallback callback;
+  private int id;
+
+  private RemoteEventProvider provider;
+
+  /**
+   * The event callback thread pool and its lock.
+   */
+  private transient static ThreadPool callbackPool = null;
+  private static Object callbackPoolLock = new Object();
+
+  public MonitorSyncer(String name, MonitorCallback callback, int id)
+    throws RemoteException
+  {
+    this.Name = name + ":MonL";
+
+    this.eventCache = new HashMap();
+
+    this.rmtDpy = null;
+    this.callback = callback;
+    this.id = id;
+
+    this.provider = new RemoteEventProviderImpl(this);
+  }
+
+  public MonitorSyncer(String name, RemoteDisplay rmtDpy, int id)
+    throws RemoteException
+  {
+    this.Name = name + ":MonL";
+
+    this.eventCache = new HashMap();
+
+    this.rmtDpy = rmtDpy;
+    this.callback = rmtDpy.getRemoteDisplaySync();
+    this.id = id;
+
+    this.provider = new RemoteEventProviderImpl(this);
+  }
+
+  public void addEvent(MonitorEvent evt)
+  {
+    String key = evt.getKey();
+    synchronized (cacheLock) {
+      MonitorEvent oldEvt = (MonitorEvent )eventCache.put(key, evt);
+
+      if (thisThread != null) {
+        if (diverted == null) {
+          diverted = new ArrayList();
+        }
+        diverted.add(key);
+      } else {
+        current.add(key);
+        thisThread = new Thread(this);
+        thisThread.start();
+      }
+    }
+  }
+
+  public MonitorEvent getEvent(Object key)
+  {
+    MonitorEvent evt;
+    synchronized (cacheLock) {
+      evt = (MonitorEvent )eventCache.remove(key);
+    }
+
+    // mark message as coming from this connection, so we don't see it again
+    if (evt != null) {
+      evt.setOriginator(id);
+    }
+
+    return evt;
+  }
+
+  /**
+   * Get the unique identifier.
+   *
+   * @return the unique identifier.
+   */
+  public int getID() { return id; }
+
+  public MonitorCallback getListener() { return callback; }
+
+  public String getName() { return Name; }
+
+  public boolean hasControlEventQueued(Control ctl)
+  {
+    if (ctl == null) {
+      return false;
+    }
+
+    return eventCache.containsKey(ControlMonitorEvent.getControlKey(ctl));
+  }
+
+  // WLH 12 April 2001
+  public boolean isEmpty() {
+    return eventCache.isEmpty();
+  }
+
+  /**
+   * Check to see if the connection is dead.
+   *
+   * @return <TT>true</TT> if the connection is dead.
+   */
+  public boolean isDead() { return dead; }
+
+  /**
+   * Check to see if this object is monitoring the specified
+   * <tt>RemoteDisplay</tt>.
+   *
+   * @param rmtDpy <tt>RemoteDisplay</tt> being searched for.
+   *
+   * @return <tt>true</tt> if this object is monitoring the display.
+   */
+  public boolean isMonitored(RemoteDisplay rmtDpy)
+  {
+    return this.rmtDpy.equals(rmtDpy);
+  }
+
+  public void run()
+  {
+    boolean done = false;
+    try {
+
+      int attempts = 0;
+      while (!done) {
+        try {
+          sendEventKeys(current);
+          done = true;
+        } catch (RemoteException re) {
+          if (attempts++ < 5) {
+            // wait a bit, then try again to notify the remote Display
+            try { Thread.sleep(500); } catch (InterruptedException ie) { }
+          } else {
+            // if we failed to connect for 10 times, give up
+            dead = true;
+            break;
+          }
+        } catch (RemoteVisADException rve) {
+          rve.printStackTrace();
+          done = true;
+        }
+
+        if (done) {
+          synchronized (cacheLock) {
+            if (!undivertEvents()) {
+              break;
+            }
+
+            done = false;
+          }
+        }
+      }
+    } finally {
+      // indicate that the thread has exited
+      synchronized (cacheLock) {
+        thisThread = null;
+      }
+    }
+  }
+
+  private void sendEventKeys(ArrayList list)
+    throws RemoteException, RemoteVisADException
+  {
+    while (list.size() > 0) {
+      String key = (String )list.remove(0);
+      callback.eventReady(provider, key);
+    }
+  }
+
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("MonitorSyncer[");
+    buf.append(Name);
+    buf.append("=#");
+    buf.append(id);
+    buf.append(']');
+    return buf.toString();
+  }
+
+  /**
+   * Returns <TT>true</TT> if there were diverted requests.
+   */
+  private boolean undivertEvents()
+  {
+    final boolean undivert;
+    synchronized (cacheLock) {
+      // if there are events queued, restore them to the main table
+      undivert = (diverted != null);
+      if (undivert) {
+        current = diverted;
+        diverted = null;
+      }
+    }
+
+    return undivert;
+  }
+
+  /**
+   * Used as key for ControlEvents in listener queue
+   */
+  class ControlEventKey
+  {
+    private Control control;
+    private Class cclass;
+    private int instance;
+
+    ControlEventKey(Control ctl)
+    {
+      control = ctl;
+      cclass = ctl.getClass();
+      instance = ctl.getInstanceNumber();
+    }
+
+    public boolean equals(ControlEventKey key)
+    {
+      return instance == key.instance && cclass.equals(key.cclass);
+    }
+
+    public boolean equals(Object obj)
+    {
+      if (!(obj instanceof ControlEventKey)) {
+        return false;
+      }
+      return equals((ControlEventKey )obj);
+    }
+
+    public boolean equals(Control ctl)
+    {
+      return cclass.equals(ctl.getClass());
+    }
+
+    public int hashCode()
+    {
+      return cclass.hashCode() + instance;
+    }
+
+    public String toString()
+    {
+      StringBuffer buf = new StringBuffer(control.toString());
+      if (buf.length() > 48) {
+        buf.setLength(0);
+      }
+      buf.insert(0, ':');
+      buf.insert(0, instance);
+      buf.insert(0, '#');
+      buf.insert(0, cclass.getName());
+      return buf.toString();
+    }
+  }
+}
diff --git a/visad/collab/ReferenceMonitorEvent.java b/visad/collab/ReferenceMonitorEvent.java
new file mode 100644
index 0000000..cf9183c
--- /dev/null
+++ b/visad/collab/ReferenceMonitorEvent.java
@@ -0,0 +1,159 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import visad.RemoteReferenceLink;
+import visad.VisADException;
+
+/**
+ * <CODE>ReferenceMonitorEvent</CODE> is the VisAD class for
+ * <CODE>RemoteReferenceLink</CODE>-related events from display monitors.
+ * They are sourced by <CODE>DisplayMonitor</CODE> objects and received by
+ * <CODE>MonitorCallback</CODE> objects.
+ */
+public class ReferenceMonitorEvent
+  extends MonitorEvent
+{
+  private RemoteReferenceLink link;
+
+  /**
+   * Creates a <CODE>ReferenceMonitorEvent</CODE> for the specified
+   * <CODE>RemoteReferenceLink</CODE>.
+   *
+   * @param type The event type (either
+   * 			<CODE>MonitorEvent.REFERENCE_ADDED</CODE> or
+   * 			<CODE>MonitorEvent.REFERENCE_REMOVED</CODE>.)
+   * @param link The <CODE>RemoteReferenceLink</CODE>.
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public ReferenceMonitorEvent(int type, RemoteReferenceLink link)
+    throws VisADException
+  {
+    this(type, -1, link);
+  }
+
+  /**
+   * Creates a <CODE>ReferenceMonitorEvent</CODE> for the specified
+   * <CODE>RemoteReferenceLink</CODE>.
+   *
+   * @param type The event type (either
+   * 			<CODE>MonitorEvent.REFERENCE_ADDED</CODE> or
+   * 			<CODE>MonitorEvent.REFERENCE_REMOVED</CODE>.)
+   * @param originator The ID of the connection from which this event came,
+   * 			relative to the receiver of the event.
+   * @param link The <CODE>RemoteReferenceLink</CODE>.
+   *
+   * @exception VisADException When a bad <CODE>type</CODE> is specified.
+   */
+  public ReferenceMonitorEvent(int type, int originator,
+                               RemoteReferenceLink link)
+    throws VisADException
+  {
+    super(type, originator);
+    if (type != REFERENCE_ADDED && type != REFERENCE_REMOVED) {
+      throw new VisADException("Bad type for ReferenceMonitorEvent");
+    }
+    if (link == null) {
+      throw new VisADException("Null link for ReferenceMonitorEvent");
+    }
+    this.link = link;
+  }
+
+  /**
+   * Get the key used to uniquely identify this event.
+   *
+   * @return The unique key.
+   */
+  public String getKey()
+  {
+    return link.toString();
+  }
+
+  /**
+   * Gets the <CODE>RemoteReferenceLink</CODE> to which this event refers.
+   */
+  public RemoteReferenceLink getLink()
+  {
+    return link;
+  }
+
+  /**
+   * Returns <CODE>true</CODE> if the specified object matches this object.
+   *
+   * @param o The object to compare.
+   */
+  public boolean equals(Object o)
+  {
+    if (!(o instanceof ReferenceMonitorEvent)) {
+      return false;
+    }
+
+    ReferenceMonitorEvent evt = (ReferenceMonitorEvent )o;
+    if (getType() != evt.getType()) {
+      return false;
+    }
+
+    return link.equals(evt.link);
+  }
+
+  /**
+   * Returns an exact clone of this object.
+   */
+  public Object clone()
+  {
+    ReferenceMonitorEvent evt;
+    try {
+      evt = new ReferenceMonitorEvent(getType(), getOriginator(), link);
+      evt.seqNum = seqNum;
+    } catch (VisADException e) {
+      evt = null;
+    }
+    return evt;
+  }
+
+  /**
+   * Returns a <CODE>String</CODE> representation of this object.
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("ReferenceMonitorEvent[");
+buf.append('#');buf.append(getSequenceNumber());buf.append(' ');
+
+    buf.append(getTypeName());
+
+    int orig = getOriginator();
+    if (orig == -1) {
+      buf.append(" Lcl ");
+    } else {
+      buf.append(" Rmt ");
+      buf.append(orig);
+    }
+
+    buf.append(' ');
+    buf.append(link);
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/collab/RemoteDisplayMonitor.java b/visad/collab/RemoteDisplayMonitor.java
new file mode 100644
index 0000000..6e724c6
--- /dev/null
+++ b/visad/collab/RemoteDisplayMonitor.java
@@ -0,0 +1,34 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.Remote;
+
+/**
+ * <CODE>RemoteDisplayMonitor</CODE> is the interface for monitoring
+ * <CODE>RemoteDisplay</CODE>s.
+ */
+public interface RemoteDisplayMonitor
+  extends DisplayMonitor, Remote
+{
+}
diff --git a/visad/collab/RemoteDisplayMonitorImpl.java b/visad/collab/RemoteDisplayMonitorImpl.java
new file mode 100644
index 0000000..cc36fae
--- /dev/null
+++ b/visad/collab/RemoteDisplayMonitorImpl.java
@@ -0,0 +1,295 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import java.rmi.server.UnicastRemoteObject;
+
+import visad.Control;
+import visad.ControlEvent;
+import visad.DisplayEvent;
+import visad.MessageEvent;
+import visad.RemoteDisplay;
+import visad.RemoteVisADException;
+import visad.ScalarMapControlEvent;
+import visad.ScalarMapEvent;
+import visad.VisADException;
+
+/**
+ * <CODE>RemoteDisplayMonitorImpl</CODE> is the implementation of the VisAD
+ * {@link RemoteDisplayMonitor RemoteDisplayMonitor} class.
+ */
+public class RemoteDisplayMonitorImpl
+  extends UnicastRemoteObject
+  implements RemoteDisplayMonitor
+{
+  private final transient DisplayMonitorImpl AdaptedMonitor;
+
+  /**
+   * Creates a remotely-accessible wrapper for the specified
+   * {@link DisplayMonitor DisplayMonitor}
+   *
+   * @param dpyMonitor The local DisplayMonitor object to adapt.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  public RemoteDisplayMonitorImpl(DisplayMonitorImpl dpyMonitor)
+    throws RemoteException
+  {
+    AdaptedMonitor = dpyMonitor;
+  }
+
+  /**
+   * Forwards the event to the adapted remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param e The {@link visad.DisplayEvent DisplayEvent} to forward.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If there was an internal problem.
+   */
+  public void displayChanged(DisplayEvent e)
+    throws RemoteException, RemoteVisADException
+  {
+    if (AdaptedMonitor == null) {
+      throw new RemoteVisADException("AdaptedMonitor is null");
+    }
+    AdaptedMonitor.displayChanged(e);
+  }
+
+  /**
+   * Forwards the event to the adapted remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param e The {@link visad.ScalarMapEvent ScalarMapEvent} to forward.
+   */
+  public void mapChanged(ScalarMapEvent e)
+  {
+    if (AdaptedMonitor != null) {
+      AdaptedMonitor.mapChanged(e);
+    }
+  }
+
+  /**
+   * Forwards the event to the adapted remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param e The {@link visad.ScalarMapEvent ScalarMapEvent} to forward.
+   */
+  public void controlChanged(ScalarMapControlEvent e)
+  {
+    if (AdaptedMonitor != null) {
+      AdaptedMonitor.controlChanged(e);
+    }
+  }
+
+  /**
+   * Forwards the event to the adapted remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param e ControlEvent to forward
+   *
+   * @exception RemoteVisADException If there was an internal problem.
+   */
+  public void controlChanged(ControlEvent e)
+    throws RemoteVisADException
+  {
+    if (AdaptedMonitor == null) {
+      throw new RemoteVisADException("AdaptedMonitor is null");
+    }
+    AdaptedMonitor.controlChanged(e);
+  }
+
+  /**
+   * Asks remote {@link DisplayMonitor DisplayMonitor} to check this ID for uniqueness.
+   *
+   * @param id The identifier to check for uniqueness.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  public int checkID(int id)
+    throws RemoteException
+  {
+    if (AdaptedMonitor == null) {
+      throw new RemoteException("AdaptedMonitor is null");
+    }
+    return AdaptedMonitor.checkID(id);
+  }
+
+  /** destroy this monitor */
+  public void destroy()
+    throws RemoteVisADException
+  {
+    throw new RemoteVisADException("Cannot destroy RemoteDisplayMonitor");
+  }
+
+  /**
+   * Adds this listener to the remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param l The listener to add
+   * @param id The unique identifer (determined with <tt>checkID</tt>.)
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If there was an internal problem.
+   */
+  public void addListener(MonitorCallback l, int id)
+    throws RemoteException, RemoteVisADException
+  {
+    if (AdaptedMonitor == null) {
+      throw new RemoteVisADException("AdaptedMonitor is null");
+    }
+    try {
+      AdaptedMonitor.addListener(l, id);
+    } catch (VisADException ve) {
+      throw new RemoteVisADException(ve.getMessage());
+    }
+  }
+
+  /**
+   * Adds this remote display to the remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param rd The remote display to add
+   * @param id The unique identifer (determined with <tt>checkID</tt>.)
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If there was an internal problem.
+   */
+  public void addListener(RemoteDisplay rd, int id)
+    throws RemoteException, RemoteVisADException
+  {
+    if (AdaptedMonitor == null) {
+      throw new RemoteVisADException("AdaptedMonitor is null");
+    }
+    try {
+      AdaptedMonitor.addListener(rd, id);
+    } catch (VisADException ve) {
+      throw new RemoteVisADException(ve.getMessage());
+    }
+  }
+
+  /**
+   * Unusable stub.  Cannot connect two RemoteDisplayMonitors.
+   *
+   * @param rd Ignored.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException <B>ALWAYS</B> thrown.
+   */
+  public void addRemoteListener(RemoteDisplay rd)
+    throws RemoteException, RemoteVisADException
+  {
+    throw new RemoteVisADException("Cannot connect two RemoteDisplayMonitors");
+  }
+
+  /**
+   * Return the ID associated with the specified <tt>RemoteDisplay</tt>.
+   *
+   * @return <tt>UNKNOWN_LISTENER_ID</tt> if not found;
+   *         otherwise, returns the ID.
+   */
+  public int getConnectionID(RemoteDisplay rmtDpy)
+  throws RemoteException
+  {
+    if (AdaptedMonitor == null) {
+      return UNKNOWN_LISTENER_ID;
+    }
+    return AdaptedMonitor.getConnectionID(rmtDpy);
+  }
+
+  /**
+   * Forwards the event to the remote {@link DisplayMonitor DisplayMonitor}.
+   *
+   * @param evt The event to forward.
+   *
+   * @exception RemoteVisADException If there was an internal problem.
+   */
+  public void notifyListeners(MonitorEvent evt)
+    throws RemoteVisADException
+  {
+    if (AdaptedMonitor == null) {
+      throw new RemoteVisADException("AdaptedMonitor is null");
+    }
+    AdaptedMonitor.notifyListeners(evt);
+  }
+
+  /**
+   * Unusable stub.  Unimplemented.
+   *
+   * @param originator Ignored.
+   * @param ctl Ignored.
+   *
+   * @exception RemoteException <B>ALWAYS</B> thrown.
+   */
+  public boolean hasEventQueued(int originator, Control ctl)
+    throws RemoteException
+  {
+    throw new RemoteException("Unimplemented");
+  }
+
+  // WLH 12 April 2001
+  public boolean isEmpty()
+        throws RemoteException {
+    throw new RemoteException("Unimplemented");
+  }
+
+  /**
+   * Unusable stub.  Unimplemented.
+   *
+   * @param ctl Ignored.
+   *
+   * @exception RemoteException <B>ALWAYS</B> thrown.
+   */
+  public boolean hasEventQueued(Control ctl)
+    throws RemoteException
+  {
+    throw new RemoteException("Unimplemented");
+  }
+
+  /**
+   * Handles <tt>MessageEvent</tt> forwarding.
+   *
+   * @param msg The message to forward.
+   */
+  public void receiveMessage(MessageEvent msg)
+    throws RemoteException
+  {
+    if (AdaptedMonitor == null) {
+      throw new RemoteException("AdaptedMonitor is null");
+    }
+    AdaptedMonitor.receiveMessage(msg);
+  }
+
+  /**
+   * Set the display synchronization object for this display
+   */
+  public void setDisplaySync(DisplaySync sync)
+    throws RemoteException
+  {
+    throw new RemoteException("Unimplemented");
+  }
+
+  /**
+   * Returns a string representation of this object.
+   */
+  public String toString()
+  {
+    return "Remote " + AdaptedMonitor;
+  }
+}
diff --git a/visad/collab/RemoteDisplaySync.java b/visad/collab/RemoteDisplaySync.java
new file mode 100644
index 0000000..6f224cc
--- /dev/null
+++ b/visad/collab/RemoteDisplaySync.java
@@ -0,0 +1,35 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.Remote;
+
+/**
+ * <CODE>RemoteDisplaySync</CODE> is the interface for stubs which are
+ * exported to <CODE>RemoteDisplay</CODE>s and used to send back events
+ * used for synchronization.
+ */
+public interface RemoteDisplaySync
+  extends DisplaySync, Remote
+{
+}
diff --git a/visad/collab/RemoteDisplaySyncImpl.java b/visad/collab/RemoteDisplaySyncImpl.java
new file mode 100644
index 0000000..f888a75
--- /dev/null
+++ b/visad/collab/RemoteDisplaySyncImpl.java
@@ -0,0 +1,85 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import java.rmi.server.UnicastRemoteObject;
+
+import visad.RemoteVisADException;
+
+/**
+ * <CODE>RemoteDisplaySyncImpl</CODE> is the implementation of the VisAD
+ * <CODE>RemoteDisplaySync</CODE> class.
+ */
+public class RemoteDisplaySyncImpl
+  extends UnicastRemoteObject
+  implements RemoteDisplaySync
+{
+  private final transient DisplaySyncImpl AdaptedSync;
+
+  /**
+   * Creates a remotely-accessible wrapper for the specified
+   * <CODE>DisplaySync</CODE>
+   *
+   * @param dpySync The local <CODE>DisplaySync</CODE> object to adapt.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   */
+  public RemoteDisplaySyncImpl(DisplaySyncImpl dpySync)
+    throws RemoteException
+  {
+    AdaptedSync = dpySync;
+  }
+
+  public void destroy()
+    throws RemoteException
+  {
+    throw new RemoteException("Illegal");
+  }
+
+  public boolean isLocalClear()
+    throws RemoteException
+  {
+    throw new RemoteException("Illegal");
+  }
+
+  /**
+   * Notifies remote event consumer that an event is ready.
+   *
+   * @param provider Object from which event should be fetched.
+   * @param key Key used to access event.
+   *
+   * @exception RemoteException If there was an RMI-related problem.
+   * @exception RemoteVisADException If there was an internal problem.
+   */
+  public void eventReady(RemoteEventProvider provider, Object key)
+    throws RemoteException, RemoteVisADException
+  {
+    if (AdaptedSync == null) {
+      throw new RemoteVisADException("AdaptedSync is null");
+    }
+
+    AdaptedSync.eventReady(provider, key);
+  }
+}
diff --git a/visad/collab/RemoteEventProvider.java b/visad/collab/RemoteEventProvider.java
new file mode 100644
index 0000000..324bdab
--- /dev/null
+++ b/visad/collab/RemoteEventProvider.java
@@ -0,0 +1,35 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import visad.RemoteVisADException;
+
+public interface RemoteEventProvider
+  extends Remote
+{
+  MonitorEvent getEvent(Object key)
+    throws RemoteException, RemoteVisADException;
+}
diff --git a/visad/collab/RemoteEventProviderImpl.java b/visad/collab/RemoteEventProviderImpl.java
new file mode 100644
index 0000000..ca6f958
--- /dev/null
+++ b/visad/collab/RemoteEventProviderImpl.java
@@ -0,0 +1,52 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.collab;
+
+import java.rmi.RemoteException;
+
+import java.rmi.server.UnicastRemoteObject;
+
+import visad.RemoteVisADException;
+
+public class RemoteEventProviderImpl
+  extends UnicastRemoteObject
+  implements RemoteEventProvider
+{
+  private MonitorSyncer syncer;
+
+  public RemoteEventProviderImpl(MonitorSyncer syncer)
+    throws RemoteException
+  {
+    this.syncer = syncer;
+  }
+
+  public MonitorEvent getEvent(Object key)
+    throws RemoteException, RemoteVisADException
+  {
+    if (syncer == null) {
+      throw new RemoteVisADException("syncer is null");
+    }
+
+    return syncer.getEvent(key);
+  }
+}
diff --git a/visad/data/AreaImageAccessor.java b/visad/data/AreaImageAccessor.java
new file mode 100644
index 0000000..1f6af52
--- /dev/null
+++ b/visad/data/AreaImageAccessor.java
@@ -0,0 +1,182 @@
+package visad.data;
+
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import visad.VisADException;
+import edu.wisc.ssec.mcidas.AreaFile;
+import edu.wisc.ssec.mcidas.AreaFileException;
+import edu.wisc.ssec.mcidas.AreaFileFactory;
+import edu.wisc.ssec.mcidas.adde.AddeImageURL;
+import edu.wisc.ssec.mcidas.adde.AddeURLException;
+
+/**
+ * Knows how to read local satellite data to be used by a cache adaptor.
+ * 
+ * TODO: Add capability to work in conjunciton with ADDE data sources 
+ * including the IDV <code>ucar.visad.data.CachedFlatField</code>.
+ */
+public class AreaImageAccessor implements FlatFieldCacheAccessor, 
+    Comparable<AreaImageAccessor> {
+
+  private static Logger log = Logger.getLogger(AreaImageAccessor.class.getName());
+  
+  private final int band;
+  private final String source;
+  private int startLine;
+  private int numLines;
+  private int lineMag;
+  private int startElem;
+  private int numElems;
+  private int elemMag;
+  private Date nominalTime;
+  private int[][][] readCache;
+  
+  private boolean isAddeSource;
+  
+  /**
+   * Create an instance. No data is read at this time.
+   * 
+   * @param source As described in {@link AreaFileFactory#getAreaFileInstance(String)}.
+   * @param band The band number of the image.
+   * @param readCache Array to use as a cache for reading AREA data into. If null data will
+   *  not be read by-reference. See {@link AreaFile#getData(int[][][])}.
+   * @throws VisADException
+   */
+  public AreaImageAccessor(String source, int band, int[][][] readCache) throws VisADException {
+    super();
+    this.band = band;
+    this.source = source;
+    this.readCache = readCache;
+    if (this.readCache == null) {
+      log.fine("readCache is null, by-reference data reading is disabled");
+    }
+    try {
+      new AddeImageURL(source, AddeImageURL.REQ_IMAGEDATA, "", "");
+      isAddeSource = true;
+      throw new IllegalArgumentException("adde sources are not currently supported");
+    } catch (Exception e) {
+      isAddeSource = false;
+    }
+  }
+
+  /**
+   * Set AREA file subsetting parameters.
+   * 
+   * @param startLine
+   * @param numLines
+   * @param lineMag
+   * @param startElem
+   * @param numElems
+   * @param elemMag
+   */
+  public void setAreaParams(int startLine, int numLines, int lineMag, int startElem, int numElems, 
+        int elemMag) {
+    this.startLine = startLine;
+    this.numLines = numLines;
+    this.lineMag = lineMag;
+    this.startElem = startElem;
+    this.numElems = numElems;
+    this.elemMag = elemMag;
+  }
+  
+  public boolean isAddeSource() {
+    return isAddeSource;
+  }
+
+  protected int[][][] getAreaData() throws AreaFileException, AddeURLException {
+    
+    AreaFile af = null;
+    if (!isAddeSource) {
+      af = AreaFileFactory.getAreaFileInstance(source.toString(), startLine, numLines, lineMag,
+          startElem, numElems, elemMag, band);
+    } else {
+      // expect sub-setting to be done in URL
+      af = AreaFileFactory.getAreaFileInstance(source.toString());
+    }
+    
+    if (nominalTime == null) {
+      nominalTime = af.getAreaDirectory().getNominalTime();
+    } else if (nominalTime.equals(af.getAreaDirectory())) {
+      throw new FlatFieldCacheError("nominal time mismatch on subsequent reads", null);
+    }
+    
+    // use by-reference if possible
+    int[][][] raw = null;
+    if (readCache != null) {
+      raw = af.getData(readCache);
+    } else {
+      raw = af.getData();
+    }
+    return raw; 
+  }
+  
+  public String getSource() {
+    return source;
+  }
+  
+  public double[][] readFlatField() {
+    double[][] range = null;
+    try {
+      
+      int[][][] raw = getAreaData();
+      
+      range = new double[1][numLines * numElems];
+      int idx = 0;
+      for (int line = 0; line < raw[0].length; line++) {
+        for (int elem = 0; elem < raw[0][0].length; elem++) {
+          range[0][idx++] = (double) raw[0][line][elem];
+        }
+      }
+    } catch (Exception e) {
+      log.log(Level.SEVERE, "Could not read image data: " + source.toString(), e);
+    }
+    return range;
+  }
+  
+  public float[][] readFlatFieldFloats() {
+    float[][] range = null;
+    try {
+      
+      int[][][] raw = getAreaData();
+      
+      range = new float[1][numLines * numElems];
+      int idx = 0;
+      for (int line = 0; line < raw[0].length; line++) {
+        for (int elem = 0; elem < raw[0][0].length; elem++) {
+          range[0][idx++] = (float) raw[0][line][elem];
+        }
+      }
+    } catch (Exception e) {
+      log.log(Level.SEVERE, "Could not read AREA file: " + source.toString(), e);
+    }
+    return range;
+  }
+
+  public Date getNominalTime() {
+    if (nominalTime == null) {
+      AreaFile af;
+      try {
+        af = AreaFileFactory.getAreaFileInstance(source.toString());
+      } catch (Exception e) {
+        throw new FlatFieldCacheError("Error getting nominal time from AREA file", e);
+      }
+      nominalTime = af.getAreaDirectory().getNominalTime();
+    }
+    return nominalTime;
+  }
+  
+  public int compareTo(AreaImageAccessor that) {
+    long myTime = getNominalTime().getTime();
+    long yourTime = that.getNominalTime().getTime();
+    if (myTime > yourTime) {
+      return 1;
+    } else if (myTime < yourTime) {
+      return -1;
+    }
+    return 0;
+  }
+  
+  
+}
diff --git a/visad/data/AreaImageCacheAdapter.java b/visad/data/AreaImageCacheAdapter.java
new file mode 100644
index 0000000..2c1994d
--- /dev/null
+++ b/visad/data/AreaImageCacheAdapter.java
@@ -0,0 +1,371 @@
+// FileFlatField.java
+//
+
+/*
+ * VisAD system for interactive analysis and visualization of numerical data.
+ * Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom Rink, Dave
+ * Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin.
+ * 
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+package visad.data;
+
+import java.rmi.RemoteException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import visad.CoordinateSystem;
+import visad.Data;
+import visad.DataShadow;
+import visad.ErrorEstimate;
+import visad.Field;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.MathType;
+import visad.RealTuple;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.Set;
+import visad.ShadowType;
+import visad.SingletonSet;
+import visad.TypeException;
+import visad.Unit;
+import visad.VisADError;
+import visad.VisADException;
+import visad.meteorology.SatelliteImage;
+
+/**
+ * Adapts a <code>FlatField</code> backed by a <code>AreaImageAccessor</code> to work
+ * with a <code>FlatFieldCache</code>.
+ */
+public class AreaImageCacheAdapter extends SatelliteImage implements FlatFieldCache.CacheOwner {
+  // note FileFlatField extends FlatField but may not inherit
+  // any of its methods - it must re-implement all of them
+  // through the adapted FlatField
+
+  private static Logger log = Logger.getLogger(AreaImageCacheAdapter.class.getName());
+  
+  // this is the FileAccessor for reading and writing values from
+  // and to the adapted file
+
+  private final FlatFieldCacheAccessor fileAccessor;
+  private final FlatFieldCache cache;
+  private final SatelliteImage adapted;
+
+  public AreaImageCacheAdapter(SatelliteImage template, AreaImageAccessor accessor, 
+       FlatFieldCache cache) throws VisADException, RemoteException {
+    super((FunctionType)template.getType(), 
+        getNullDomainSet(((FunctionType)template.getType()).getDomain()), template.getStartTime(), 
+        template.getDescription(), template.getSensorName());
+    this.adapted = (SatelliteImage) template.clone();
+    fileAccessor = accessor;
+    this.cache = cache;
+  }
+  
+  public String getId() {
+    return ((AreaImageAccessor) fileAccessor).getSource();
+  }
+
+  public static Set getNullDomainSet(RealTupleType type) throws VisADException {
+    int n = type.getDimension();
+    double[] values = new double[n];
+    for (int i = 0; i < n; i++)
+      values[i] = 0.0;
+    RealTuple tuple;
+    try {
+      tuple = new RealTuple(type, values);
+      return new SingletonSet(tuple);
+    } catch (RemoteException e) {
+      throw new VisADError("FileFlatField.getNullDomainSet: " + e.toString());
+    }
+  }
+
+  protected SatelliteImage getAdaptedFlatField() {
+    try {
+      adapted.setSamples(cache.getData(this, fileAccessor), false);
+    } catch (Exception e) {
+      log.log(Level.SEVERE, "error setting samples", e);
+      throw new FlatFieldCacheError("Error retrieving cached FlatField", e);
+    }
+    return adapted;
+  }
+
+  public Data getSample(int index) throws VisADException, RemoteException {
+    log.finest("getSample");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getSample(index);
+  }
+
+  public int getLength() {
+    log.finest("getLength");
+    int length = 0;
+    try {
+      length = adapted.getLength();
+    } catch (Exception e) {
+      throw new FlatFieldCacheError("Error accessing accessor template", e);
+    }
+    return length;
+  }
+
+  public Unit[] getDomainUnits() {
+    log.finest("getDomainUnits");
+    Unit[] units = null;
+    try {
+      units = adapted.getDomainUnits();
+    } catch (Exception e) {
+      throw new FlatFieldCacheError("Error accessing accessor template", e);
+    }
+    return units;
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem() {
+    log.finest("getDomainCoordinateSystem");
+    CoordinateSystem coordSystem = null;
+    try {
+      adapted.getDomainCoordinateSystem();
+    } catch (Exception e) {
+      throw new FlatFieldCacheError("Error accessing accessor template", e);
+    }
+    return coordSystem;
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem() throws TypeException {
+    log.finest("getRangeCoordinateSystem");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getRangeCoordinateSystem();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem(int component) throws TypeException {
+    log.finest("getRangeCoordinateSystem");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getRangeCoordinateSystem(component);
+  }
+
+  public Unit[][] getRangeUnits() {
+    log.finest("getRangeUnits");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getRangeUnits();
+  }
+
+  public Unit[] getDefaultRangeUnits() {
+    log.finest("getDefaultRangeUnits");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getDefaultRangeUnits();
+  }
+
+  public double[][] getValues() throws VisADException {
+    log.finest("getValues");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getValues();
+  }
+
+  public double[][] getValues(boolean copy) throws VisADException {
+    log.finest("getValues");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getValues(copy);
+  }
+
+  public double[] getValues(int index) throws VisADException {
+    log.finest("getValues");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getValues(index);
+  }
+
+  public float[][] getFloats(boolean copy) throws VisADException {
+    log.finest("getFloats");
+    FlatField fld = getAdaptedFlatField();
+    return fld.getFloats(copy);
+  }
+
+  public Set getDomainSet() {
+    log.finest("getDomainSet");
+    Set domainSet = null;
+    try {
+      domainSet = adapted.getDomainSet();
+    } catch (Exception e) {
+      throw new FlatFieldCacheError("Error accessing accessor template", e);
+    }
+    return domainSet;
+  }
+
+  // setSample is typical of methods that involve changing the
+  // contents of this Field
+  public void setSample(int index, Data range) throws VisADException, RemoteException {
+    log.finest("setSample");
+    synchronized (cache) {
+      FlatField fld = getAdaptedFlatField();
+      cache.setDirty(this, true);
+      fld.setSample(index, range);
+    }
+  }
+
+  public void setSample(RealTuple domain, Data range) throws VisADException, RemoteException {
+    log.finest("setSample");
+    synchronized (cache) {
+      FlatField fld = getAdaptedFlatField();
+      cache.setDirty(this, true);
+      fld.setSample(domain, range);
+    }
+  }
+
+  public void setSample(int index, Data range, boolean copy) throws VisADException, RemoteException {
+    log.finest("getSample");
+    synchronized (cache) {
+      FlatField fld = getAdaptedFlatField();
+      cache.setDirty(this, true);
+      fld.setSample(index, range, copy);
+    }
+  }
+  
+  public void setSamples(double[][] data) throws RemoteException, VisADException {
+    log.finest("getSamples");
+    synchronized (cache) {
+      FlatField fld = getAdaptedFlatField();
+      cache.setDirty(this, true);
+      fld.setSamples(data, false);
+    }
+  }
+  
+  public void setSamples(double[][] range, ErrorEstimate[] errors,
+      boolean copy) throws VisADException, RemoteException {
+    setSamples(range);
+  }
+
+  public boolean isMissing() {
+    log.finest("isMissing");
+    FlatField fld = getAdaptedFlatField();
+    return fld.isMissing();
+  }
+
+  public Data binary(Data data, int op, int sampling_mode, int error_mode) throws VisADException,
+      RemoteException {
+    log.finest("binary");
+    FlatField fld = getAdaptedFlatField();
+    return fld.binary(data, op, sampling_mode, error_mode);
+  }
+
+  public Data binary(Data data, int op, MathType new_type, int sampling_mode, int error_mode)
+      throws VisADException, RemoteException {
+    log.finest("binary");
+    FlatField fld = getAdaptedFlatField();
+    return fld.binary(data, op, new_type, sampling_mode, error_mode);
+  }
+
+  public Data unary(int op, int sampling_mode, int error_mode) throws VisADException,
+      RemoteException {
+    log.finest("unary");
+    FlatField fld = getAdaptedFlatField();
+    return fld.unary(op, sampling_mode, error_mode);
+  }
+
+  public Data unary(int op, MathType new_type, int sampling_mode, int error_mode)
+      throws VisADException {
+    log.finest("unary");
+    FlatField fld = getAdaptedFlatField();
+    return fld.unary(op, new_type, sampling_mode, error_mode);
+  }
+
+  /**
+   * unpack an array of doubles from field sample values according to the
+   * RangeSet-s; returns a copy
+   */
+  public double[][] unpackValues() throws VisADException {
+    log.finest("unpackValues");
+    FlatField fld = getAdaptedFlatField();
+    return fld.unpackValues();
+  }
+
+  /**
+   * unpack an array of floats from field sample values according to the
+   * RangeSet-s; returns a copy
+   */
+  public float[][] unpackFloats() throws VisADException {
+    log.finest("unpackFloats");
+    FlatField fld = getAdaptedFlatField();
+    return fld.unpackFloats();
+  }
+
+  public Field extract(int component) throws VisADException, RemoteException {
+    log.finest("extract");
+    FlatField fld = getAdaptedFlatField();
+    return fld.extract(component);
+  }
+
+  public Field domainFactor(RealType factor) throws VisADException, RemoteException {
+    log.finest("domainFactor");
+    FlatField fld = getAdaptedFlatField();
+    return fld.domainFactor(factor);
+  }
+
+  public Field resample(Set set, int sampling_mode, int error_mode) throws VisADException,
+      RemoteException {
+    log.finest("resample");
+    FlatField fld = getAdaptedFlatField();
+    return fld.resample(set, sampling_mode, error_mode);
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow) throws VisADException {
+    log.finest("computeRanges");
+    FlatField fld = getAdaptedFlatField();
+    return fld.computeRanges(type, shadow);
+  }
+
+  public Data adjustSamplingError(Data error, int error_mode) throws VisADException,
+      RemoteException {
+    log.finest("adjustSamplingError");
+    FlatField fld = getAdaptedFlatField();
+    return fld.adjustSamplingError(error, error_mode);
+  }
+
+  public boolean isFlatField() {
+    return true;
+  }
+
+  /**
+   * Clones this instance. This implementation violates the general <code>
+   * clone()</code> contract in that the returned object will compare unequal to
+   * this instance. As such, this method should probably not be invoked.
+   * 
+   * @return A clone of this instance.
+   */
+  public Object clone() {
+    /*
+     * This implementation should probably just throw a
+     * CloneNotSupportedException but can't because FlatField.clone() doesn't.
+     */
+
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.clone();
+  }
+
+  public String longString(String pre) {
+    log.finest("longString");
+    FlatField fld = getAdaptedFlatField();
+    try {
+      return fld.longString(pre);
+    } catch (VisADException e) {
+      return pre + e.getMessage();
+    }
+  }
+  
+  public String toString() {
+    return this.getClass().toString();
+  }
+}
diff --git a/visad/data/ArrayCache.java b/visad/data/ArrayCache.java
new file mode 100644
index 0000000..99439fb
--- /dev/null
+++ b/visad/data/ArrayCache.java
@@ -0,0 +1,430 @@
+//
+// CachingCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import visad.util.Util;
+
+/**
+ * This class is used by the CachingCoordinateSystem to do the actual caching mapping one array to another one
+ * @version $Revision: 1.5 $ $Date: 2010-01-05 21:02:43 $
+ */
+public class ArrayCache {
+
+  /** Do we cache */
+  private boolean enabled =
+    Boolean.parseBoolean(System.getProperty("visad.data.arraycache.enabled",
+                                            "true"));
+
+  /** lower size threshold */
+  private int lowerThreshold =
+    Integer.parseInt(System.getProperty("visad.data.arraycache.lowerthreshold",
+                                        "1000"));
+
+
+  /** upper size threshold */
+  private int upperThreshold =
+    Integer.parseInt(System.getProperty("visad.data.arraycache.upperthreshold",
+                                        "1000000"));
+
+
+  private boolean useDataCacheManager = 
+      Boolean.parseBoolean(System.getProperty("visad.data.arraycache.usedatacachemanager",
+                                              "false"));
+
+
+
+  private Hashtable<String,Integer>  misses = new Hashtable<String,Integer>();
+
+
+
+
+
+  /**
+   *  ctor
+   */
+  public ArrayCache() {}
+
+
+  /**
+   * ctor
+   *
+   * @param enabled  If false then never cache
+   */
+  public ArrayCache(boolean enabled) {
+    this.enabled = enabled;
+  }
+
+   private String getKey(String key, int size) {
+       return key +"_" + size;
+   }
+
+  /**
+   * Get the converted value for the specified key and input pairs
+   *
+   * @param key The key (e.g., "toReference", "fromReference")
+   * @param input  The input
+   *
+   * @return value for supplied key
+   */
+   
+  public FloatResult get(String key, float[][] input) {
+    if (!shouldHandle(input)) {
+        return new FloatResult(false);
+    }
+    return getInner(key, input);
+  }
+
+
+   private synchronized FloatResult getInner(String key, float[][] input) {
+    key = getKey(key, input[0].length);
+    float[][][] pair = getFloatValue(key);
+    if (pair == null) {
+      return handleCacheMiss(key, input);
+    }
+
+    float[][] lastInput = pair[0];
+    float[][] lastOutput = pair[1];
+    if (lastInput.length != input.length) return null;
+    for (int i = 0; i < input.length; i++) {
+      if (!Arrays.equals(input[i], lastInput[i])) {
+          return handleCacheMiss(key, input);
+      }
+    }
+    misses.remove(key);
+    //?? should we clone the output
+    return new FloatResult(Util.clone(lastOutput));
+  }
+
+
+
+
+
+  /**
+   * Get the converted value for the specified key and input pairs
+   *
+   * @param key The key (e.g., "toReference", "fromReference")
+   * @param input  The input
+   *
+   * @return value for the supplied key
+   */
+   
+  public DoubleResult get(String key, double[][] input) {
+      if (!shouldHandle(input)) {
+         return new DoubleResult(false);
+    }
+      return getInner(key, input);
+  }
+
+
+
+ private synchronized  DoubleResult getInner(String key, double[][] input) {
+
+    key = getKey(key, input[0].length);
+    double[][][] pair = getDoubleValue(key);
+    if (pair == null) {
+        return handleCacheMiss(key, input);
+    }        
+
+    double[][] lastInput = pair[0];
+    double[][] lastOutput = pair[1];
+    if (lastInput.length != input.length) return null;
+    for (int i = 0; i < input.length; i++) {
+       if (!Arrays.equals(input[i], lastInput[i])) {
+         return handleCacheMiss(key, input);
+      }
+    }
+    misses.remove(key);
+    return new DoubleResult(Util.clone(lastOutput));
+  }
+
+
+
+  private DoubleResult handleCacheMiss(String key, double[][]input) {
+        Integer numMisses = misses.get(key);
+        if(numMisses==null) {
+            misses.put(key, numMisses = new Integer(1));
+        } else {
+            misses.put(key, new Integer(numMisses.intValue()+1));
+        }
+        if(numMisses.intValue()>3) {
+            removeValue(key);
+        }
+        return new DoubleResult(numMisses.intValue()<=1);
+  }
+
+
+  private FloatResult handleCacheMiss(String key, float[][]input) {
+        Integer numMisses = misses.get(key);
+        if(numMisses==null) {
+            misses.put(key, numMisses = new Integer(1));
+        } else {
+            misses.put(key, new Integer(numMisses.intValue()+1));
+        }
+        if(numMisses.intValue()>3) {
+            removeValue(key);
+        }
+        return new FloatResult(numMisses.intValue()<=1);
+  }
+
+
+
+
+
+    private boolean shouldHandle(double[][]input) {
+        if(input == null) return false;
+        if(input.length==0) return false;
+        if(input[0]==null) return false;
+        if(!enabled) return false;
+        if (input[0].length <= lowerThreshold) return false;
+        if (input[0].length > upperThreshold) return false;
+        return true;
+    }
+
+
+
+    private boolean shouldHandle(float[][]input) {
+        if(input == null) return false;
+        if(input.length==0) return false;
+        if(input[0]==null) return false;
+        if(!enabled) return false;
+        if (input[0].length <= lowerThreshold) return false;
+        if (input[0].length > upperThreshold) return false;
+        return true;
+    }
+
+
+
+  /**
+   * Put the converted value for the specified key and input pairs
+   *
+   * @param key The key
+   * @param input  The input array
+   * @param results  The array to store
+   */
+  public void put(String key, double[][] input, DoubleResult results) {
+    if(!shouldHandle(input)) return;
+    putInner(key, input, results);
+  }
+
+
+  private synchronized void putInner(String key, double[][] input, DoubleResult results) {
+    if(!results.shouldCache || results.values==null) return;
+    key = getKey(key, input[0].length);
+    storeValue(key,
+             new double[][][] {
+                 (double[][])Util.clone(input), (double[][])Util.clone(results.values)
+    });
+
+  }
+
+
+
+
+  /**
+   * Put the converted value for the specified key and input pairs
+   *
+   * @param key The key
+   * @param input  The input array
+   * @param results  The array to store
+   */
+  public  void put(String key, float[][] input, FloatResult results) {
+    if(!shouldHandle(input)) return;
+    putInner(key, input, results);
+  }
+
+  private synchronized void putInner(String key, float[][] input, FloatResult results) {
+    if(!results.shouldCache || results.values==null) return;
+    key = getKey(key, input[0].length);
+    storeValue(key, new float[][][] {
+      (float[][])Util.clone(input), (float[][])Util.clone(results.values)
+    });
+
+  }
+
+
+
+
+
+    /** holds float arrays or DataCacheManager ids */
+    private Hashtable<Object, Object> map = new Hashtable<Object, Object>();
+
+    private void storeValue(String key, double[][][]value) {
+        checkCache();
+        Object object = value;
+        if(useDataCacheManager) {
+            removeValue(key);
+            object = DataCacheManager.getCacheManager().addToCache("ArrayCache", value, true);
+        }
+        map.put(key, object);
+    }
+
+    private void storeValue(String key, float[][][]value) {
+        checkCache();
+        Object object = value;
+        if(useDataCacheManager) {
+            removeValue(key);
+            object = DataCacheManager.getCacheManager().addToCache("ArrayCache", value, true);
+        }
+        map.put(key, object);
+    }
+
+    public void finalize() throws Throwable {
+        super.finalize();
+        if(useDataCacheManager) {
+            //            System.err.println ("arraycache finalize");
+            clearCache();
+        }
+    }
+
+
+    private void checkCache() {
+        if(map.size()>4) {
+            clearCache();
+        }
+    }
+
+    private void removeValue(Object key) {
+        Object object  = map.get(key);
+        if(object!=null) {
+            if(useDataCacheManager)
+                DataCacheManager.getCacheManager().removeFromCache(object);
+            map.remove(key);
+        }
+    }
+
+    private void clearCache() {
+        for(Enumeration keys= map.keys();keys.hasMoreElements(); ) {
+            Object key= keys.nextElement();
+            Object object  = map.get(key);
+            if(useDataCacheManager) {
+                DataCacheManager.getCacheManager().removeFromCache(object);
+            }
+        }
+        map = new Hashtable<Object, Object>();
+    }
+
+
+    private double[][][] getDoubleValue(String key) {
+        Object object = map.get(key);
+        if(object==null) {
+            return null;
+        }
+        if(useDataCacheManager)
+            return DataCacheManager.getCacheManager().getDoubleArray3D(object);
+        return (double[][][])object;
+    }
+
+    private float[][][] getFloatValue(String key) {
+        Object object = map.get(key);
+        if(object==null) {
+            return null;
+        }
+        if(useDataCacheManager)
+            return DataCacheManager.getCacheManager().getFloatArray3D(object);
+        return (float[][][])object;
+    }
+
+
+
+
+
+
+
+    public static class DoubleResult {
+        public boolean shouldCache = true;
+        public double[][]values;
+
+        public DoubleResult() {
+            shouldCache = false;
+            values = null;
+        }
+
+        public DoubleResult(boolean shouldCache) {
+            this.shouldCache = shouldCache;
+        }
+
+        public DoubleResult(double[][]values) {
+            this.values = values;
+            this.shouldCache = true;
+        }
+
+
+        public double[][]cloneForCache(double[][]a) {
+            if(!shouldCache) return null;
+            return Util.clone(a);
+        }
+
+        public boolean getShouldCache() {
+            return shouldCache;
+        }
+
+    }
+
+
+
+
+    public static class FloatResult {
+        public boolean shouldCache = true;
+        public float[][]values;
+
+        public FloatResult() {
+            shouldCache = false;
+            values = null;
+        }
+
+        public FloatResult(boolean shouldCache) {
+            this.shouldCache = shouldCache;
+        }
+
+        public FloatResult(float[][]values) {
+            this.values = values;
+            this.shouldCache = true;
+        }
+
+        public float[][]cloneForCache(float[][]a) {
+            if(!shouldCache) return null;
+            return Util.clone(a);
+        }
+
+        public boolean getShouldCache() {
+            return shouldCache;
+        }
+
+    }
+
+
+
+
+
+
+
+}
+
diff --git a/visad/data/ArrayWrapper.java b/visad/data/ArrayWrapper.java
new file mode 100644
index 0000000..fe942b2
--- /dev/null
+++ b/visad/data/ArrayWrapper.java
@@ -0,0 +1,260 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+*/
+
+package visad.data;
+
+/**
+ */
+public  class ArrayWrapper {
+   private Object cacheId;
+
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+   
+  public ArrayWrapper(byte[] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(byte[][] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(float[] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(float[][] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(double[] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(double[][] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(int[] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(int[][] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(short[] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   */
+  
+  public ArrayWrapper(short[][] values) {
+      cacheId = DataCacheManager.getCacheManager().addToCache(values);
+  }
+
+    public boolean inMemory() {
+	return DataCacheManager.getCacheManager().inMemory(cacheId);
+    }
+
+    public void updateData(Object newData) {
+	DataCacheManager.getCacheManager().updateData(cacheId, newData);
+    }
+
+
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public byte[] getByteArray1D() {
+      return DataCacheManager.getCacheManager().getByteArray1D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public byte[][] getByteArray2D() {
+      return DataCacheManager.getCacheManager().getByteArray2D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public short[] getShortArray1D() {
+      return DataCacheManager.getCacheManager().getShortArray1D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public short[][] getShortArray2D() {
+      return DataCacheManager.getCacheManager().getShortArray2D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public int[] getIntArray1D() {
+      return DataCacheManager.getCacheManager().getIntArray1D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public int[][] getIntArray2D() {
+      return DataCacheManager.getCacheManager().getIntArray2D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public float[] getFloatArray1D() {
+      return DataCacheManager.getCacheManager().getFloatArray1D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public float[][] getFloatArray2D() {
+      return DataCacheManager.getCacheManager().getFloatArray2D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public double[] getDoubleArray1D() {
+      return DataCacheManager.getCacheManager().getDoubleArray1D(cacheId);
+  }
+
+
+  /**
+   * get the value 
+   *
+   * @return  the value
+   */
+  public double[][] getDoubleArray2D() {
+      return DataCacheManager.getCacheManager().getDoubleArray2D(cacheId);
+  }
+
+
+    public void finalize() throws Throwable {
+        super.finalize();
+        DataCacheManager.getCacheManager().removeFromCache(cacheId);
+    }
+
+}
+
diff --git a/visad/data/BadFormException.java b/visad/data/BadFormException.java
new file mode 100644
index 0000000..8713671
--- /dev/null
+++ b/visad/data/BadFormException.java
@@ -0,0 +1,38 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: BadFormException.java,v 1.11 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+
+import visad.VisADException;
+
+
+/** Exception thrown when the form that the data is in is incorrect. */
+public class BadFormException extends VisADException {
+
+  public BadFormException(String msg) { super(msg); }
+  public BadFormException(String msg, Throwable cause) { super(msg, cause); }
+  public BadFormException(Throwable cause) { super(cause); }
+
+}
diff --git a/visad/data/BadRepositoryException.java b/visad/data/BadRepositoryException.java
new file mode 100644
index 0000000..b460b42
--- /dev/null
+++ b/visad/data/BadRepositoryException.java
@@ -0,0 +1,43 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: BadRepositoryException.java,v 1.10 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+
+import visad.VisADException;
+
+
+/**
+ * Exception thrown when there's something wrong with the repository.
+ */
+public class BadRepositoryException extends VisADException
+{
+    /**
+     * Construct an exception with a message.
+     */
+    public BadRepositoryException(String msg)
+    {
+	super(msg);
+    }
+}
diff --git a/visad/data/BaseDataProcessor.java b/visad/data/BaseDataProcessor.java
new file mode 100644
index 0000000..3b60de6
--- /dev/null
+++ b/visad/data/BaseDataProcessor.java
@@ -0,0 +1,672 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import visad.*;
+
+public abstract class BaseDataProcessor
+  implements DataProcessor
+{
+  /**
+   * Write the <tt>Data</tt> object using the most appropriate
+   * {@link visad.data.DataProcessor DataProcessor} method.
+   */
+  public void process(DataImpl di, Object token)
+    throws VisADException
+  {
+    MathType mt = di.getType();
+
+    boolean done = false;
+    if (mt instanceof ScalarType) {
+      try {
+        if (mt instanceof TextType) {
+          Text t = (Text )di;
+
+          processText((TextType )t.getType(), t.getValue(), t.isMissing(), t,
+                      token);
+          done = true;
+        } else if (mt instanceof RealType) {
+          Real r = (Real )di;
+
+          processReal((RealType )r.getType(), r.getValue(), r.getUnit(),
+                      r.getError(), r, token);
+          done = true;
+        }
+      } catch (UnimplementedException ue) {
+      }
+    } else if (mt instanceof TupleType) {
+      TupleType tt = (TupleType )mt;
+      Tuple ti = (Tuple )di;
+
+      Data[] comps = ti.getComponents();
+
+      if (tt instanceof RealTupleType) {
+        RealTuple rti = (RealTuple )ti;
+        RealTupleType rtt = (RealTupleType )tt;
+
+        if (comps != null) {
+          Real[] reals = new Real[comps.length];
+          for (int i = 0; i < comps.length; i++) {
+            reals[i] = (Real )comps[i];
+          }
+
+          try {
+            processRealTuple(rtt, reals, rti.getCoordinateSystem(), rti,
+                             token);
+            done = true;
+          } catch (UnimplementedException ue) {
+          }
+        }
+      }
+
+      if (!done) {
+        try {
+          processTuple(tt, comps, ti, token);
+          done = true;
+        } catch (UnimplementedException ue) {
+        }
+      }
+    } else if (mt instanceof SetType) {
+      SetType st = (SetType )mt;
+
+      try {
+        if (di instanceof SampledSet) {
+          processSampledSet(st, (SampledSet )di, token);
+          done = true;
+        } else if (di instanceof SimpleSet) {
+          processSimpleSet(st, (SimpleSet )di, token);
+          done = true;
+        }
+      } catch (UnimplementedException ue) {
+      }
+
+    } else if (mt instanceof FunctionType) {
+      FunctionType ft = (FunctionType )mt;
+
+      try {
+        if (di instanceof FlatField) {
+          FlatField ff = (FlatField )di;
+
+          CoordinateSystem cs = null;
+          CoordinateSystem[] rangeCS = null;
+
+          if (ft.getReal()) {
+            cs = ff.getRangeCoordinateSystem()[0];
+          } else {
+            MathType rt = ft.getRange();
+            final int dim = ((TupleType )ft.getRange()).getDimension();
+
+            rangeCS = new CoordinateSystem[dim];
+
+            for (int i = 0; i < dim; i++) {
+              rangeCS[i] = ff.getRangeCoordinateSystem(i)[0];
+            }
+          }
+
+          processFlatField(ft, ff.getDomainSet(), cs, rangeCS,
+                           ff.getRangeSets(), ff.getDefaultRangeUnits(), ff,
+                           token);
+          done = true;
+        } else if (di instanceof FieldImpl) {
+          FieldImpl fi = (FieldImpl )di;
+
+          processFieldImpl(ft, fi.getDomainSet(), fi, token);
+          done = true;
+        }
+      } catch (UnimplementedException ue) {
+      }
+    }
+
+    if (!done) {
+      try {
+        processUnknownData(di, token);
+      } catch (UnimplementedException ue) {
+        throw new UnimplementedException("Couldn't process " +
+                                         di.getClass().getName() +
+                                         " in " + getClass().getName());
+      }
+    }
+  }
+
+  public abstract void processDoubleSet(SetType type, CoordinateSystem cs,
+                                        Unit[] units, DoubleSet set,
+                                        Object token)
+    throws VisADException;
+
+  public abstract void processFieldImpl(FunctionType type, Set set,
+                                        FieldImpl fld, Object token)
+    throws VisADException;
+
+  public abstract void processFlatField(FunctionType type, Set domainSet,
+                                        CoordinateSystem cs,
+                                        CoordinateSystem[] rangeCS,
+                                        Set[] rangeSets, Unit[] units,
+                                        FlatField fld, Object token)
+    throws VisADException;
+
+  public abstract void processFloatSet(SetType type, CoordinateSystem cs,
+                                       Unit[] units, FloatSet set,
+                                       Object token)
+    throws VisADException;
+
+  public abstract void processGridded1DDoubleSet(SetType type,
+                                                 double[][] samples,
+                                                 int[] lengths,
+                                                 CoordinateSystem cs,
+                                                 Unit[] units,
+                                                 ErrorEstimate[] errors,
+                                                 Gridded1DDoubleSet set,
+                                                 Object token)
+    throws VisADException;
+
+  public abstract void processGridded2DDoubleSet(SetType type,
+                                                 double[][] samples,
+                                                 int[] lengths,
+                                                 CoordinateSystem cs,
+                                                 Unit[] units,
+                                                 ErrorEstimate[] errors,
+                                                 Gridded2DDoubleSet set,
+                                                 Object token)
+    throws VisADException;
+
+  public abstract void processGridded3DDoubleSet(SetType type,
+                                                 double[][] samples,
+                                                 int[] lengths,
+                                                 CoordinateSystem cs,
+                                                 Unit[] units,
+                                                 ErrorEstimate[] errors,
+                                                 Gridded3DDoubleSet set,
+                                                 Object token)
+    throws VisADException;
+
+  public abstract void processGridded1DSet(SetType type, float[][] samples,
+                                           int[] lengths, CoordinateSystem cs,
+                                           Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           Gridded1DSet set, Object token)
+    throws VisADException;
+
+  private void processGridded1DSet(SetType st, Gridded1DSet set, Object token)
+    throws VisADException
+  {
+    int[] lengths = set.getLengths();
+    CoordinateSystem cs = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors = set.getSetErrors();
+
+    boolean done = false;
+    try {
+      if (set instanceof Gridded1DDoubleSet) {
+        Gridded1DDoubleSet dset = (Gridded1DDoubleSet )set;
+
+        processGridded1DDoubleSet(st, dset.getDoubles(), lengths, cs, units,
+                                  errors, dset, token);
+        done = true;
+      } else if (set instanceof Integer1DSet) {
+        processInteger1DSet(st, lengths, cs, units, errors,
+                            (Integer1DSet )set, token);
+        done = true;
+      } else if (set instanceof Linear1DSet) {
+        Linear1DSet lset = (Linear1DSet )set;
+
+        double[] firsts = new double[] { lset.getFirst() };
+        double[] lasts = new double[] { lset.getLast() };
+
+        processLinear1DSet(st, firsts, lasts, lengths, cs, units, errors,
+                           lset, token);
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processGridded1DSet(st, set.getSamples(), set.getLengths(),
+                          set.getCoordinateSystem(), set.getSetUnits(),
+                          set.getSetErrors(), set, token);
+    }
+  }
+
+  public abstract void processGridded2DSet(SetType type, float[][] samples,
+                                           int[] lengths, CoordinateSystem cs,
+                                           Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           Gridded2DSet set, Object token)
+    throws VisADException;
+
+  private void processGridded2DSet(SetType st, Gridded2DSet set, Object token)
+    throws VisADException
+  {
+    int[] lengths = set.getLengths();
+    CoordinateSystem cs = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors = set.getSetErrors();
+
+    boolean done = false;
+    try {
+      if (set instanceof Gridded2DDoubleSet) {
+        Gridded2DDoubleSet dset = (Gridded2DDoubleSet )set;
+
+        processGridded2DDoubleSet(st, dset.getDoubles(), lengths, cs, units,
+                                  errors, dset, token);
+        done = true;
+      } else if (set instanceof Integer2DSet) {
+        processInteger2DSet(st, lengths, cs, units, errors,
+                            (Integer2DSet )set, token);
+        done = true;
+      } else if (set instanceof Linear2DSet) {
+        Linear2DSet lset = (Linear2DSet )set;
+
+        double[] firsts = new double[2];
+        double[] lasts = new double[2];
+        for (int i = 0; i < 2; i++) {
+          Linear1DSet tmpSet = lset.getLinear1DComponent(i);
+          firsts[i] = tmpSet.getFirst();
+          lasts[i] = tmpSet.getLast();
+        }
+
+        if (lset instanceof LinearLatLonSet) {
+          processLinearLatLonSet(st, firsts, lasts, lengths, cs, units,
+                                 errors, (LinearLatLonSet )lset, token);
+        } else {
+          processLinear2DSet(st, firsts, lasts, lengths, cs, units, errors,
+                             lset, token);
+        }
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processGridded2DSet(st, set.getSamples(), set.getLengths(),
+                          set.getCoordinateSystem(), set.getSetUnits(),
+                          set.getSetErrors(), set, token);
+    }
+  }
+
+  public abstract void processGridded3DSet(SetType type, float[][] samples,
+                                           int[] lengths, CoordinateSystem cs,
+                                           Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           Gridded3DSet set, Object token)
+    throws VisADException;
+
+  private void processGridded3DSet(SetType st, Gridded3DSet set, Object token)
+    throws VisADException
+  {
+    int[] lengths = set.getLengths();
+    CoordinateSystem cs = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors = set.getSetErrors();
+
+    boolean done = false;
+    try {
+      if (set instanceof Gridded3DDoubleSet) {
+        Gridded3DDoubleSet dset = (Gridded3DDoubleSet )set;
+
+        processGridded3DDoubleSet(st, dset.getDoubles(), lengths, cs, units,
+                                  errors, dset, token);
+        done = true;
+      } else if (set instanceof Integer3DSet) {
+        processInteger3DSet(st, lengths, cs, units, errors,
+                            (Integer3DSet )set, token);
+        done = true;
+      } else if (set instanceof Linear3DSet) {
+        Linear3DSet lset = (Linear3DSet )set;
+
+        double[] firsts = new double[3];
+        double[] lasts = new double[3];
+        for (int i = 0; i < 3; i++) {
+          Linear1DSet tmpSet = lset.getLinear1DComponent(i);
+          firsts[i] = tmpSet.getFirst();
+          lasts[i] = tmpSet.getLast();
+        }
+
+        processLinear3DSet(st, firsts, lasts, lengths, cs, units, errors,
+                           lset, token);
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processGridded3DSet(st, set.getSamples(), set.getLengths(),
+                          set.getCoordinateSystem(), set.getSetUnits(),
+                          set.getSetErrors(), set, token);
+    }
+  }
+
+  public abstract void processGriddedSet(SetType type, float[][] samples,
+                                         int[] lengths, CoordinateSystem cs,
+                                         Unit[] units, ErrorEstimate[] errors,
+                                         GriddedSet set, Object token)
+    throws VisADException;
+
+  private void processGriddedSet(SetType st, GriddedSet set, Object token)
+    throws VisADException
+  {
+    boolean done = false;
+    try {
+      if (set instanceof Gridded1DSet) {
+        processGridded1DSet(st, (Gridded1DSet )set, token);
+        done = true;
+      } else if (set instanceof Gridded2DSet) {
+        processGridded2DSet(st, (Gridded2DSet )set, token);
+        done = true;
+      } else if (set instanceof Gridded3DSet) {
+        processGridded3DSet(st, (Gridded3DSet )set, token);
+        done = true;
+      } else if (set instanceof LinearNDSet) {
+        processLinearNDSet(st, (LinearNDSet )set, token);
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processGriddedSet(st, set.getSamples(), set.getLengths(),
+                        set.getCoordinateSystem(), set.getSetUnits(),
+                        set.getSetErrors(), set, token);
+    }
+  }
+
+  public abstract void processInteger1DSet(SetType type, int[] lengths,
+                                           CoordinateSystem cs, Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           Integer1DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processInteger2DSet(SetType type, int[] lengths,
+                                           CoordinateSystem cs, Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           Integer2DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processInteger3DSet(SetType type, int[] lengths,
+                                           CoordinateSystem cs, Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           Integer3DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processIntegerNDSet(SetType type, int[] lengths,
+                                           CoordinateSystem cs, Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           IntegerNDSet set, Object token)
+    throws VisADException;
+
+  public abstract void processIrregular1DSet(SetType type, float[][] samples,
+                                             CoordinateSystem cs,
+                                             Unit[] units,
+                                             ErrorEstimate[] errors,
+                                             Irregular1DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processIrregular2DSet(SetType type, float[][] samples,
+                                             CoordinateSystem cs,
+                                             Unit[] units,
+                                             ErrorEstimate[] errors,
+                                             Delaunay delaunay,
+                                             Irregular2DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processIrregular3DSet(SetType type, float[][] samples,
+                                             CoordinateSystem cs,
+                                             Unit[] units,
+                                             ErrorEstimate[] errors,
+                                             Delaunay delaunay,
+                                             Irregular3DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processIrregularSet(SetType type, float[][] samples,
+                                           CoordinateSystem cs, Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           Delaunay delaunay,
+                                           IrregularSet set, Object token)
+    throws VisADException;
+
+  private void processIrregularSet(SetType st, IrregularSet set, Object token)
+    throws VisADException
+  {
+    float[][] samples = set.getSamples();
+    CoordinateSystem cs = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    ErrorEstimate[] errors = set.getSetErrors();
+
+    boolean done = false;
+    try {
+      if (set instanceof Irregular1DSet) {
+        processIrregular1DSet(st, samples, cs, units, errors,
+                              (Irregular1DSet )set, token);
+        done = true;
+      } else if (set instanceof Irregular2DSet) {
+        processIrregular2DSet(st, samples, cs, units, errors, set.Delan,
+                              (Irregular2DSet )set, token);
+        done = true;
+      } else if (set instanceof Irregular3DSet) {
+        processIrregular3DSet(st, samples, cs, units, errors, set.Delan,
+                              (Irregular3DSet )set, token);
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processIrregularSet(st, samples, cs, units, errors, set.Delan, set,
+                          token);
+    }
+  }
+
+  public abstract void processLinear1DSet(SetType type, double[] firsts,
+                                          double[] lasts, int[] lengths,
+                                          CoordinateSystem cs, Unit[] units,
+                                          ErrorEstimate[] errors,
+                                          Linear1DSet set, Object token)
+    throws VisADException;
+
+  public void processLinear1DSet(SetType st, Linear1DSet set, Object token)
+    throws VisADException
+  {
+    processLinear1DSet(st, new double[] { set.getFirst() },
+                       new double[] { set.getLast() }, set.getLengths(),
+                       set.getCoordinateSystem(), set.getSetUnits(),
+                       set.getSetErrors(), set, token);
+  }
+
+  public abstract void processLinear2DSet(SetType type, double[] firsts,
+                                          double[] lasts, int[] lengths,
+                                          CoordinateSystem cs, Unit[] units,
+                                          ErrorEstimate[] errors,
+                                          Linear2DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processLinear3DSet(SetType type, double[] firsts,
+                                          double[] lasts, int[] lengths,
+                                          CoordinateSystem cs, Unit[] units,
+                                          ErrorEstimate[] errors,
+                                          Linear3DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processLinearLatLonSet(SetType type, double[] firsts,
+                                              double[] lasts, int[] lengths,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              LinearLatLonSet set,
+                                              Object token)
+    throws VisADException; 
+
+  public abstract void processLinearNDSet(SetType type, double[] firsts,
+                                          double[] lasts, int[] lengths,
+                                          CoordinateSystem cs, Unit[] units,
+                                          ErrorEstimate[] errors,
+                                          LinearNDSet set, Object token)
+    throws VisADException;
+
+  private void processLinearNDSet(SetType st, LinearNDSet set, Object token)
+    throws VisADException
+  {
+    boolean done = false;
+    try {
+      if (set instanceof IntegerNDSet) {
+        processIntegerNDSet(st, set.getLengths(), set.getCoordinateSystem(),
+                            set.getSetUnits(), set.getSetErrors(),
+                            (IntegerNDSet )set, token);
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processLinearNDSet(st, set.getFirsts(), set.getLasts(),
+                         set.getLengths(), set.getCoordinateSystem(),
+                         set.getSetUnits(), set.getSetErrors(), set, token);
+    }
+  }
+
+  public abstract void processList1DSet(SetType type, float[] list,
+                                        CoordinateSystem cs, Unit[] units,
+                                        List1DSet set, Object token)
+    throws VisADException;
+
+  public abstract void processProductSet(SetType type, SampledSet[] sets,
+                                         CoordinateSystem cs, Unit[] units,
+                                         ErrorEstimate[] errors,
+                                         ProductSet set, Object token)
+    throws VisADException;
+
+  public abstract void processReal(RealType type, double value, Unit unit,
+                                   ErrorEstimate error, Real real,
+                                   Object token)
+    throws VisADException;
+
+  public abstract void processRealTuple(RealTupleType type, Real[] components,
+                                        CoordinateSystem cs, RealTuple rt,
+                                        Object token)
+    throws VisADException;
+
+  public abstract void processSampledSet(SetType st, int manifold_dimension,
+                                         CoordinateSystem cs, Unit[] units,
+                                         ErrorEstimate[] errors,
+                                         SampledSet set, Object token)
+    throws VisADException;
+
+  private void processSampledSet(SetType st, SampledSet set, Object token)
+    throws VisADException
+  {
+    boolean done = false;
+    try {
+      if (set instanceof GriddedSet) {
+        processGriddedSet(st, (GriddedSet )set, token);
+        done = true;
+      } else if (set instanceof IrregularSet) {
+        processIrregularSet(st, (IrregularSet )set, token);
+        done = true;
+      } else if (set instanceof ProductSet) {
+        ProductSet ps = (ProductSet )set;
+
+        processProductSet(st, ps.getSets(), ps.getCoordinateSystem(),
+                          ps.getSetUnits(), ps.getSetErrors(), ps, token);
+        done = true;
+      } else if (set instanceof SingletonSet) {
+        SingletonSet ss = (SingletonSet )set;
+
+        processSingletonSet(ss.getData(), ss.getCoordinateSystem(),
+                            ss.getSetUnits(), ss.getSetErrors(), ss, token);
+        done = true;
+      } else if (set instanceof UnionSet) {
+        UnionSet us = (UnionSet )set;
+
+        processUnionSet(st, us.getSets(), us, token);
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processSampledSet(st, set.getManifoldDimension(),
+                        set.getCoordinateSystem(), set.getSetUnits(),
+                        set.getSetErrors(), set, token);
+    }
+  }
+
+  public abstract void processSimpleSet(SetType st, int manifold_dimension,
+                                        CoordinateSystem cs, Unit[] units,
+                                        ErrorEstimate[] errors, SimpleSet set,
+                                        Object token)
+    throws VisADException;
+
+  private void processSimpleSet(SetType st, SimpleSet set, Object token)
+    throws VisADException
+  {
+    boolean done = false;
+    try {
+      if (set instanceof DoubleSet) {
+        DoubleSet ds = (DoubleSet )set;
+
+        processDoubleSet(st, ds.getCoordinateSystem(), ds.getSetUnits(), ds,
+                         token);
+        done = true;
+      } else if (set instanceof FloatSet) {
+        FloatSet fs = (FloatSet )set;
+
+        processFloatSet(st, fs.getCoordinateSystem(), fs.getSetUnits(), fs,
+                        token);
+        done = true;
+      } else if (set instanceof List1DSet) {
+        List1DSet ls = (List1DSet )set;
+
+        float[][] samples = ls.getSamples();
+        processList1DSet(st, samples[0], ls.getCoordinateSystem(),
+                         ls.getSetUnits(), ls, token);
+        done = true;
+      }
+    } catch (UnimplementedException ue) {
+    }
+
+    if (!done) {
+      processSimpleSet(st, set.getManifoldDimension(),
+                       set.getCoordinateSystem(), set.getSetUnits(),
+                       set.getSetErrors(), set, token);
+    }
+  }
+
+  public abstract void processSingletonSet(RealTuple sample,
+                                           CoordinateSystem cs, Unit[] units,
+                                           ErrorEstimate[] errors,
+                                           SingletonSet set, Object token)
+    throws VisADException;
+
+  public abstract void processText(TextType type, String value,
+                                   boolean missing, Text text, Object token)
+    throws VisADException;
+
+  public abstract void processTuple(TupleType type, Data[] components, Tuple t,
+                                    Object token)
+    throws VisADException;
+
+  public abstract void processUnionSet(SetType type, SampledSet[] sets,
+                                       UnionSet set, Object token)
+    throws VisADException;
+
+  public abstract void processUnknownData(DataImpl data, Object token)
+    throws VisADException;
+}
diff --git a/visad/data/CacheStrategy.java b/visad/data/CacheStrategy.java
new file mode 100644
index 0000000..595a5d7
--- /dev/null
+++ b/visad/data/CacheStrategy.java
@@ -0,0 +1,66 @@
+//
+// CacheStrategy.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import visad.*;
+
+public class CacheStrategy
+{
+
+   public CacheStrategy()
+   {
+
+   }
+
+   public int allocate( FlatField[] adaptedFlatFields,
+                        boolean[] adaptedFlatFieldDirty,
+                        long[] adaptedFlatFieldSizes,
+                        long[] adaptedFlatFieldTimes )
+   {
+
+      int adaptedFlatFieldIndex = 0;
+      long oldest = adaptedFlatFieldTimes[0];
+
+      for ( int ii = 0; ii < adaptedFlatFields.length; ii++ )
+      {
+
+         if ( adaptedFlatFields[ii] == null )
+         {
+           adaptedFlatFieldIndex = ii;
+           return adaptedFlatFieldIndex;
+         }
+         else if ( adaptedFlatFieldTimes[ii] < oldest )
+         {
+           oldest = adaptedFlatFieldTimes[ii];
+           adaptedFlatFieldIndex = ii;
+         }
+      }
+
+      return adaptedFlatFieldIndex;
+   }
+
+}
diff --git a/visad/data/CachedBufferedByteImage.java b/visad/data/CachedBufferedByteImage.java
new file mode 100644
index 0000000..0e12903
--- /dev/null
+++ b/visad/data/CachedBufferedByteImage.java
@@ -0,0 +1,206 @@
+//
+// ShadowImageByRefFunctionTypeJ3D.java
+//
+
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+
+
+package visad.data;
+
+
+import visad.*;
+
+import visad.data.ArrayWrapper;
+import visad.data.gif.GIFForm;
+import visad.data.mcidas.AreaAdapter;
+import visad.data.mcidas.BaseMapAdapter;
+
+import visad.java3d.*;
+
+import visad.util.Util;
+
+import java.awt.color.*;
+
+import java.awt.event.*;
+import java.awt.image.*;
+
+import java.io.*;
+
+import java.net.URL;
+
+import java.rmi.*;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.media.j3d.*;
+
+import javax.swing.*;
+
+
+/**
+ * Class CachedBufferedByteImage _more_
+ *
+ *
+ * @author IDV Development Team
+ */
+public class CachedBufferedByteImage extends BufferedImage {
+
+    /** _more_ */
+    public static int cnt = 0;
+
+    /** _more_          */
+    private ArrayWrapper bytes;
+
+    /** _more_ */
+    private String cacheFile;
+
+    /** _more_ */
+    private int myWidth;
+
+    /** _more_ */
+    private int myHeight;
+
+    /** _more_ */
+    private int dbSize;
+
+    /** _more_ */
+    private int dbOffset;
+
+    /** _more_ */
+    private Object MUTEX = new Object();
+
+
+    /**
+     * _more_
+     *
+     * @param width _more_
+     * @param height _more_
+     * @param type _more_
+     */
+    public CachedBufferedByteImage(int width, int height, int type) {
+        super(1, 1, type);
+        BufferedImage  fullImage = new BufferedImage(width, height, type);
+
+        WritableRaster raster    = fullImage.getRaster();
+        DataBuffer     db        = raster.getDataBuffer();
+        byte[]         byteData  = ((DataBufferByte) db).getData();
+        dbSize   = db.getSize();
+        dbOffset = db.getOffset();
+        //      System.err.println("bytes:" + byteData.length);
+        this.myWidth  = width;
+        this.myHeight = height;
+	bytes = new ArrayWrapper(byteData);
+    }
+
+
+
+    /**
+     * _more_
+     *
+     * @param newByteData _more_
+     */
+    public void bytesChanged(byte[] newByteData) {
+        bytes.updateData(newByteData);
+    }
+
+    public void finalize() throws Throwable {
+        super.finalize();
+	bytes = null;
+	//        System.err.println("image finalized");
+	//        DataCacheManager.getCacheManager().removeFromCache(cacheId);
+    }
+
+
+
+    public boolean inMemory() {
+	return  bytes.inMemory();
+    }
+
+
+    /**
+     * _more_
+     *
+     * @return _more_
+     */
+    public byte[] getBytesFromCache() {
+        return bytes.getByteArray1D();
+    }
+
+    /**
+     * _more_
+     *
+     * @return _more_
+     */
+    public int getHeight() {
+        return myHeight;
+    }
+
+    /**
+     * _more_
+     *
+     * @return _more_
+     */
+    public int getWidth() {
+        return myWidth;
+    }
+
+    /**
+     * _more_
+     *
+     * @param obs _more_
+     *
+     * @return _more_
+     */
+    public int getHeight(ImageObserver obs) {
+        return myWidth;
+    }
+
+    /**
+     * _more_
+     *
+     * @param obs _more_
+     *
+     * @return _more_
+     */
+    public int getWidth(ImageObserver obs) {
+        return myHeight;
+    }
+
+    /**
+     * _more_
+     *
+     * @return _more_
+     */
+    public WritableRaster getRaster() {
+        DataBuffer db = new DataBufferByte(getBytesFromCache(), dbSize, dbOffset);
+        WritableRaster newRaster =
+            java.awt.image.Raster.createWritableRaster(getSampleModel(), db,
+                null);
+        return newRaster;
+        //            return super.getRaster();
+    }
+}
+
diff --git a/visad/data/CachedFlatField.java b/visad/data/CachedFlatField.java
new file mode 100644
index 0000000..2b12a25
--- /dev/null
+++ b/visad/data/CachedFlatField.java
@@ -0,0 +1,695 @@
+//
+// CachedFlatField
+//
+
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin, Jeff McWhirter.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+
+package visad.data;
+
+import visad.*;
+import visad.util.DataUtility;
+import java.rmi.RemoteException;
+
+
+/**
+ * This is a FloatField that caches to disk its float array.
+ */
+public class CachedFlatField extends FlatField {
+
+
+    /** the id for this instance */
+    private Object cacheId;
+
+    private boolean inCache = false;
+
+    /** Mutex */
+    transient protected Object MUTEX = new Object();
+
+
+    /** The min/max ranges */
+    DataRange[] ranges;
+
+    /** The min/max ranges */
+    DataRange[] sampleRanges;
+
+    private  CachedFlatField parent;
+
+    /**
+     * Create a new CachedFlatField
+     *
+     * @param type Function type
+     * @param domainSet set for this
+     *
+     * @throws VisADException On badness
+     */
+    public CachedFlatField(FunctionType type, Set domainSet)
+            throws VisADException {
+        this(type, domainSet, (CoordinateSystem) null, (Set[]) null,
+             (Unit[]) null, null);
+    }
+
+
+    /**
+     * Create a new CachedFlatField
+     *
+     * @param floats The values
+     * @param type Function type
+     *
+     * @throws VisADException On badness
+     */
+    public CachedFlatField(FunctionType type, float[][] floats)
+            throws VisADException {
+        this(type, type.getDomain().getDefaultSet(), (CoordinateSystem) null,
+             (Set[]) null, (Unit[]) null, floats);
+    }
+
+    /**
+     * Create a new CachedFlatField
+     *
+     * @param floats The values
+     * @param type Function type
+     * @param domainSet Domain
+     *
+     * @throws VisADException On badness
+     */
+    public CachedFlatField(FunctionType type, Set domainSet, float[][] floats)
+            throws VisADException {
+        this(type, domainSet, (CoordinateSystem) null, (Set[]) null,
+             (Unit[]) null, floats);
+    }
+
+    /**
+     * Create a new CachedFlatField
+     *
+     * @param type Function type
+     * @param domainSet Domain
+     * @param rangeCoordSys  range CoordSystem
+     * @param rangeSets range sets
+     * @param units units
+     * @param floats The values
+     *
+     * @throws VisADException On badness
+     */
+    public CachedFlatField(FunctionType type, Set domainSet,
+                           CoordinateSystem rangeCoordSys, Set[] rangeSets,
+                           Unit[] units, float[][] floats)
+            throws VisADException {
+        this(type, domainSet, rangeCoordSys, null, rangeSets, units, floats);
+    }
+
+    /**
+     * Create a new CachedFlatField
+     *
+     * @param type Function type
+     * @param domainSet Domain
+     * @param rangeCoordSys  range CoordSystem
+     * @param rangeCoordSyses  range CoordSystem's
+     * @param rangeSets range sets
+     * @param units units
+     * @param floats The values
+     *
+     * @throws VisADException On badness
+     */
+    public CachedFlatField(FunctionType type, Set domainSet,
+                           CoordinateSystem rangeCoordSys,
+                           CoordinateSystem[] rangeCoordSyses,
+                           Set[] rangeSets, Unit[] units, float[][] floats)
+            throws VisADException {
+        super(type, domainSet, rangeCoordSys, null, rangeSets, units);
+        initCache(floats);
+    }
+
+    /**
+     * Copy constructor
+     *
+     * @param that What we clone from
+     * @param copy copy the values
+     * @param type Function type
+     * @param domainSet Domain
+     * @param rangeCoordSys  range CoordSystem
+     * @param rangeCoordSysArray  rangeCoordSysArray
+     * @param rangeSets range sets
+     * @param units units
+     *
+     * @throws VisADException On badness
+     */
+    public CachedFlatField(CachedFlatField that, boolean copy,
+                           FunctionType type, Set domainSet,
+                           CoordinateSystem rangeCoordSys,
+                           CoordinateSystem[] rangeCoordSysArray,
+                           Set[] rangeSets, Unit[] units)
+            throws VisADException {
+        super(type, domainSet, rangeCoordSys, rangeCoordSysArray, rangeSets,
+              units);
+
+        this.ranges       = that.ranges;
+        this.sampleRanges = that.sampleRanges;
+        this.cacheId =  null;
+        this.inCache = false;
+
+        //Get the values from the cloned field if they had read their values
+        if(that.haveData()) {
+            //            msg("CCF - cloned object is in cache");
+            // We used to ignore the copy flag - if this causes problems, 
+            // change back to true 
+            //float[][] values = that.unpackFloats(true);
+            float[][] values = that.unpackFloats(copy);
+            if(values == null) {
+                parent = that;
+            }
+            initCache(values);
+        } else {
+            this.parent = that;
+            clearMissing();
+            //            msg("CCF - cloned object not in cache");
+        }
+
+    }
+
+
+
+
+    /**
+     * Clone this object
+     *
+     * @param copy copy the values
+     * @param type Function type
+     * @param domainSet Domain set
+     * @param rangeCoordSys  range CoordSystem
+     * @param rangeCoordSysArray  rangeCoordSysArray
+     * @param rangeSets range sets
+     * @param units units
+     *
+     * @return New field
+     *
+     * @throws VisADException On badness
+     */
+    public CachedFlatField cloneMe(boolean copy, FunctionType type,
+                                   Set domainSet,
+                                   CoordinateSystem rangeCoordSys,
+                                   CoordinateSystem[] rangeCoordSysArray,
+                                   Set[] rangeSets, Unit[] units)
+            throws VisADException {
+
+        CachedFlatField ccf = new CachedFlatField(this, copy, type,
+                                  domainSet, rangeCoordSys,
+                                  rangeCoordSysArray, rangeSets, units);
+        return ccf;
+    }
+
+
+
+
+    public void finalize() throws Throwable {
+        super.finalize();
+        //        System.err.println("CachedFlatField.finalize");
+        if(cacheId!=null) {
+            DataCacheManager.getCacheManager().removeFromCache(cacheId);
+        }
+    }
+
+
+    /**
+     * Set the sample
+     *
+     * @param values the samples
+     * @param errors errors
+     * @param copy   tru to copy
+     *
+     * @throws RemoteException Java RMI Exception
+     * @throws VisADException  Problem in VisAD land
+     */
+    public void setSamples(float[][] values, ErrorEstimate[] errors,
+                           boolean copy)
+            throws VisADException, RemoteException {
+
+        float[][]myFloatValues = new float[values.length][];
+        for (int i = 0; i < myFloatValues.length; i++) {
+            if (copy) {
+                myFloatValues[i] = (float[]) values[i].clone();
+            } else {
+                myFloatValues[i] = values[i];
+            }
+        }
+        setRangeErrors(errors);
+        this.getRanges(values);
+        if(inCache) {
+            DataCacheManager.getCacheManager().updateData(cacheId, myFloatValues);
+        } else {
+            initCache(myFloatValues);
+        }
+
+    }
+
+
+
+
+    static int cnt = 0;
+    public final int mycnt = cnt++;
+
+    /**
+     * Override method so we clear the caches on the cloned object
+     *
+     * @return the clone
+     */
+    public Object clone() {
+        try {
+            //      msg("CCF.clone");
+            CachedFlatField ccf = (CachedFlatField) super.clone();
+            ccf.cacheId = null;
+            float[][]newValues = ccf.unpackFloats(false);
+            ccf.nullRanges();
+            ccf.initCache(newValues);
+            return ccf;
+        } catch(Exception exc) {
+            exc.printStackTrace();
+            throw new RuntimeException(exc);
+        }
+    }
+
+
+
+    /**
+     * init
+     *
+     * @param data data
+     *
+     * @throws VisADException initializing field
+     */
+    protected void initCache(float[][] data) throws VisADException {
+        if(data!=null) {
+            if(cacheId!=null) {
+                DataCacheManager.getCacheManager().updateData(cacheId, data);
+            } else {
+                cacheId = DataCacheManager.getCacheManager().addToCache(getClass().getSimpleName(), data);
+            }
+            inCache = true;
+        }
+        //Read the ranges when we first have data
+        if (ranges == null) {
+            getRanges(data);
+        }
+        clearMissing();
+    }
+
+
+    /**
+     * Set the sample ranges
+     *
+     * @param sampleRanges the sample ranges
+     */
+    public void setSampleRanges(DataRange[] sampleRanges) {
+        this.sampleRanges = sampleRanges;
+    }
+
+
+    /**
+     * Clear the cached ranges
+     */
+    public void clearCachedRange() {
+        sampleRanges = null;
+        ranges       = null;
+    }
+
+
+
+
+    /**
+     * Get the ranges
+     *
+     * @return ranges
+     * 
+    * @throws VisADException  problem getting ranges
+     */
+    public DataRange[] getRanges() throws VisADException {
+        return getRanges(false);
+    }
+
+    /**
+     * Get the ranges
+     *
+     *
+     * @param force   force a recalc
+     * @return ranges  the ranges
+     *
+     * @throws VisADException  problem getting ranges
+     */
+    public DataRange[] getRanges(boolean force) throws VisADException {
+        if (force) {
+            sampleRanges = null;
+        }
+        if (ranges != null) {
+            return ranges;
+        }
+        if (sampleRanges != null) {
+            return sampleRanges;
+        }
+        //        msg("making ranges");
+        return getRanges(unpackFloats(false));
+    }
+
+
+    /**
+     * Get the ranges for the values
+     *
+     * @param values the values
+     *
+     * @return the ranges
+     *
+     * @throws VisADException  Problem in VisAD land
+     */
+    public DataRange[] getRanges(float[][] values) throws VisADException {
+        sampleRanges = null;
+        if (values == null) {
+            return null;
+        }
+        ranges = new DataRange[values.length];
+        for (int rangeIdx = 0; rangeIdx < values.length; rangeIdx++) {
+            float   pMin         = Float.POSITIVE_INFINITY;
+            float   pMax         = Float.NEGATIVE_INFINITY;
+            float[] values_range = values[rangeIdx];
+            int     length       = values_range.length;
+            for (int i = 0; i < length; i++) {
+                float value = values_range[i];
+                if (pMax < value) {
+                    pMax = value;
+                }
+                if (pMin > value) {
+                    pMin = value;
+                }
+            }
+            ranges[rangeIdx] = new DataRange(pMin, pMax);
+        }
+        //        msg("done making ranges");
+        return ranges;
+    }
+
+
+
+
+
+
+
+    /**
+     * Used to provide a hook to derived classes to dynamically read in the data
+     *
+     * @return data
+     */
+    public float[][] readData() {
+        //        msg(" CachedFlatField.readData");
+        return null;
+    }
+
+    /**
+     * Debug statment
+     *
+     * @param s message to print
+     */
+    public void msg(String s) {
+        //        System.err.println("ccf:"+ mycnt + ": " + s);
+    }
+
+
+    /**
+     * Read data from cache
+     *
+     * @return the values from the cache
+     *
+     * @throws VisADException   problem reading data
+     */
+    private float[][] getMyValues() throws VisADException {
+        //        msg("CCF - getMyValues " + inCache);
+        if(inCache) {
+            if(cacheId == null) {
+                //                msg("CCF - WHoa, inCache=true but no cacheId");
+                return null;
+            }
+            return DataCacheManager.getCacheManager().getFloatArray2D(cacheId);
+        }
+
+        float[][] values = null;
+
+        //If we don't have the values and we have a ccf that we were cloned from 
+        //then read the data from it and clear it out
+        if(parent!=null) {
+            values = parent.unpackFloats(true);
+            readValuesFromParent(parent);
+            parent = null;
+        }
+
+
+        if (values == null) {
+            values = readData();
+        }
+
+
+        if (values == null) {
+            //            msg("Floats still null after readData");
+            return null;
+        }
+        initCache(values);
+        return values;
+    }
+
+
+    public boolean haveData() {
+        return inCache;
+    }
+
+
+
+    /**
+     * This gets called to notify derived classes that we jus got the data from the parent ccf
+     *
+     * @param parent The parent CCF we read data from
+     */
+    protected void readValuesFromParent(CachedFlatField parent) throws VisADException {
+    }
+
+
+
+    /**
+     * Get the range value at the index-th sample.
+     *
+     * @param index  index of the sample
+     * @return Data object (Real, RealTuple, or Tuple) corresponding to
+     *         the range at the index-th sample.
+     * @throws VisADException  problem getting data
+     * @throws RemoteException problem getting data from remote object
+     */
+    public Data getSample(int index) throws VisADException, RemoteException {
+        //        msg("getSample");
+        float[][] values = getMyValues();
+
+        if (values == null) {
+            //            msg("Floats still null");
+            return null;
+        }
+        MathType        Type        = getType();
+        ErrorEstimate[] RangeErrors = getRangeErrors();
+
+        if (isMissing() || (index < 0) || (index >= getLength())) {
+            //            msg("is missing");
+            return ((FunctionType) Type).getRange().missingData();
+        }
+        double[][] range = new double[TupleDimension][1];
+        for (int i = 0; i < TupleDimension; i++) {
+            range[i][0] = (double) values[i][index];
+        }
+
+        MathType RangeType = ((FunctionType) Type).getRange();
+        if (RangeType instanceof RealType) {
+            return new Real((RealType) RangeType, range[0][0], RangeUnits[0],
+                            RangeErrors[0]);
+        } else if (RangeType instanceof RealTupleType) {
+            Real[] reals = new Real[TupleDimension];
+            for (int j = 0; j < TupleDimension; j++) {
+                MathType type = ((RealTupleType) RangeType).getComponent(j);
+                reals[j] = new Real((RealType) type, range[j][0],
+                                    RangeUnits[j], RangeErrors[j]);
+            }
+            return new RealTuple((RealTupleType) RangeType, reals,
+                                 RangeCoordinateSystem);
+        } else {  // RangeType is a Flat TupleType
+            int    n      = ((TupleType) RangeType).getDimension();
+            int    j      = 0;
+            Data[] datums = new Data[n];
+            for (int i = 0; i < n; i++) {
+                MathType type = ((TupleType) RangeType).getComponent(i);
+                if (type instanceof RealType) {
+                    datums[i] = new Real((RealType) type, range[j][0],
+                                         RangeUnits[j], RangeErrors[j]);
+                    j++;
+                } else {  // type instanceof RealTupleType
+                    int    m     = ((RealTupleType) type).getDimension();
+                    Real[] reals = new Real[m];
+                    for (int k = 0; k < m; k++) {
+                        RealType ctype =
+                            (RealType) ((RealTupleType) type).getComponent(k);
+                        reals[k] = new Real(ctype, range[j][0],
+                                            RangeUnits[j], RangeErrors[j]);
+                        j++;
+                    }
+                    datums[i] = new RealTuple((RealTupleType) type, reals,
+                            RangeCoordinateSystems[i]);
+                }
+            }
+            return new Tuple(datums, false);
+        }
+    }
+
+
+    /**
+     * get the float values as doubles
+     *
+     * @param copy copy the values
+     *
+     * @return The values
+     *
+     * @throws VisADException On badness
+     */
+    protected double[][] unpackValues(boolean copy) throws VisADException {
+        float[][] values = unpackFloats(false);
+        if (values == null) {
+            msg ("unpackValues: ccf: values are null ");
+            return null;
+        }
+        double[][] doubles = new double[values.length][];
+        for (int i = 0; i < values.length; i++) {
+            float[]  values_i  = values[i];
+            double[] doubles_i = new double[values_i.length];
+            doubles[i] = doubles_i;
+            for (int j = 0; j < values_i.length; j++) {
+                doubles_i[j] = values_i[j];
+            }
+        }
+        return doubles;
+    }
+
+
+    /**
+     * get the float values
+     *
+     * @param copy copy the values
+     *
+     * @return The values
+     *
+     * @throws VisADException On badness
+     */
+    public float[][] unpackFloats(boolean copy) throws VisADException {
+        //        msg("unpackFloats copy=" + copy);
+        float[][] values = getMyValues();
+        if (values == null) {
+            //            msg("unpackFloats gives null");
+            //      System.err.println(mycnt+" CCF.unpackFloats - values are still null");
+            return super.unpackFloats(copy);
+        }
+        float[][] result = null;
+        result = new float[values.length][];
+        for (int i = 0; i < result.length; i++) {
+            if (copy) {
+                result[i] = (float[]) values[i].clone();
+            } else {
+                result[i] = values[i];
+            }
+        }
+        return result;
+    }
+
+
+    /**
+     * Unpack floats
+     *
+     * @param s_index the sample index
+     *
+     * @return  the floats for that index
+     *
+     * @throws VisADException  Problem in VisAD land
+     */
+    protected float[] unpackFloats(int s_index) throws VisADException {
+        float[][] values = getMyValues();
+        if (values == null) {
+            return null;
+        }
+        float[] range = new float[values.length];
+        for (int i = 0; i < TupleDimension; i++) {
+            range[i] = values[i][s_index];
+        }
+        return range;
+    }
+
+
+
+
+
+    /**
+     * Make a clone of this using the new type, units and errors.  Called
+     * from unary and binar
+     *
+     * @param f_type  the new FunctionType
+     * @param units   the new Units
+     * @param errors  the new Errors
+     * @param newValues  the new values
+     *
+     * @return a new FlatField
+     *
+     * @throws VisADException  Problem in VisAD land
+    protected FlatField cloneFloat(MathType f_type, Unit[] units,
+                                   ErrorEstimate[] errors,
+                                   float[][] newValues)
+            throws VisADException {
+        MathType N_type = ((f_type == null)
+                           ? getType()
+                           : f_type);
+
+        // create (initially missing) FlatField for return
+        // use FloatSet rather than RangeSet for intermediate computation results
+        Set[] sets = new Set[TupleDimension];
+        for (int i = 0; i < TupleDimension; i++) {
+            SetType set_type =
+                new SetType(
+                    ((FunctionType) N_type).getFlatRange().getComponent(i));
+            sets[i] = new FloatSet(set_type);
+        }
+        RealTupleType d_type  = ((FunctionType) N_type).getDomain();
+        Set           new_set = null;
+        if ( !d_type.equals(((FunctionType) getType()).getDomain())) {
+            new_set = (Set) getDomainSet().cloneButType(d_type);
+        } else {
+            new_set = getDomainSet();
+        }
+        if (newValues == null) {
+            //If we don't have the values array then copy this one.
+            newValues = unpackFloats(true);
+        }
+        CachedFlatField field = new CachedFlatField((FunctionType) N_type,
+                                    new_set, RangeCoordinateSystem,
+                                    RangeCoordinateSystems, sets, units,
+                                    newValues);
+        return field;
+    }
+     */
+
+}
+
diff --git a/visad/data/DataCacheManager.java b/visad/data/DataCacheManager.java
new file mode 100644
index 0000000..76ae722
--- /dev/null
+++ b/visad/data/DataCacheManager.java
@@ -0,0 +1,1587 @@
+//
+// DataCacheManager
+//
+
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin, Jeff McWhirter.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+
+package visad.data;
+
+
+import java.io.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+import java.awt.*;
+import javax.swing.*;
+
+
+/**
+ * This provides a global cache for primitive array data
+ * You access it via the singelton:<pre>
+     DataCacheManager.getCacheManager()
+</pre>
+* Client objects can store their data with:<pre>
+  Object cacheId = DataCacheManager.getCacheManager().addToCache(theData);
+</pre>
+ * Where data can be 1D or  2D byte/float/int/double arrays
+ * If the data changes then update the cache with:<pre>
+        DataCacheManager.getCacheManager().updateData(cacheId, newData);
+</pre>
+ * When the client object is finalized or is finished with the data call:<pre>
+        DataCacheManager.getCacheManager().removeFromCache(cacheId);
+</pre>
+ * When you want to access the data use one of:<pre>
+      DataCacheManager.getCacheManager().getByteArray1D(cacheId); 
+      DataCacheManager.getCacheManager().getByteArray2D(cacheId); 
+      DataCacheManager.getCacheManager().getFloatArray1D(cacheId); 
+      DataCacheManager.getCacheManager().getFloatArray2D(cacheId); 
+      DataCacheManager.getCacheManager().getShortArray1D(cacheId); 
+      DataCacheManager.getCacheManager().getShortArray2D(cacheId); 
+      DataCacheManager.getCacheManager().getIntArray1D(cacheId); 
+      DataCacheManager.getCacheManager().getIntArray2D(cacheId); 
+      DataCacheManager.getCacheManager().getDoubleArray1D(cacheId); 
+      DataCacheManager.getCacheManager().getDoubleArray2D(cacheId); 
+</pre>
+
+* The cachemanager will keep the data arrays in memory until the total size is greater than getMaxSize(). Then it will serialize the data arrays in a least recently used manner until the totalSize less than the max size.
+ */
+
+public class DataCacheManager  implements Runnable {
+
+
+ private double memoryPercentage = 0.25;    
+
+  /** the singleton */
+  private static DataCacheManager cacheManager;
+
+
+  /** Where to store the cached data */
+  private File cacheDir;
+
+  /** for unique ids */
+  private int idCnt = 0;
+
+  /** for unique ids */
+  private long baseTime;
+
+
+  /** The cache */
+  private Hashtable<Object, CacheInfo> cache = new Hashtable<Object,
+                                                 CacheInfo>();
+
+  /** a mutex */
+  private Object MUTEX = new Object();
+
+  /** Total number of bytes in memory */
+  private int totalSize = 0;
+
+  private boolean running = false;
+
+
+
+  /**
+   * ctor
+   */
+  private DataCacheManager() {
+    baseTime = System.currentTimeMillis();
+    try {
+        //Start  the cache monitor in a thread
+        Thread t = new Thread(this);
+        t.start();
+    } catch(Exception exc) {
+        throw new RuntimeException(exc);
+    }
+  }
+
+
+  /**
+   * The singleton access
+   *
+   * @return the cache manager
+   */
+  public static DataCacheManager getCacheManager() {
+    if (cacheManager == null) {
+      cacheManager = new DataCacheManager();
+    }
+    return cacheManager;
+  }
+
+
+
+
+    public void run() {
+        if(running) return;
+        running= true;
+        try {
+            while(true) {
+                Thread.currentThread().sleep(5000);
+                checkCache();
+            }
+        } catch(Exception exc) {
+            System.err.println ("Error in DataCacheManager:");
+            exc.printStackTrace();
+        }
+        running =false;
+    }
+
+
+  /**
+   * set the directory to write files to
+   *
+   * @param f dir
+   */
+  public void setCacheDir(File f) {
+    this.cacheDir = f;
+  }
+
+
+  /**
+   * get the cache dir. If it was not set then default to current directory
+   *
+   * @return The cache dir
+   */
+  public File getCacheDir() {
+    if (cacheDir == null) {
+      cacheDir = new File(".");
+    }
+    return cacheDir;
+  }
+
+  /**
+   * Get a unique id
+   *
+   * @return unique id
+   */
+  public Object getId() {
+    return "data_" + baseTime + "_" + (idCnt++);
+  }
+
+
+  /**
+   * Add the data to the cache
+   *
+   * @param data the data (i.e., the array)
+   * @param type The type of the data
+   *
+   * @return the unique id
+   */
+    private Object addToCache(String what, Object data, int type, boolean removeIfNeeded) {
+    synchronized (MUTEX) {
+        CacheInfo info = new CacheInfo(this, getId(), data, type, removeIfNeeded);
+      if(what!=null) info.what = what;
+      cache.put(info.getId(), info);
+      totalSize += info.getSize();
+      checkCache();
+      return info.getId();
+    }
+  }
+
+
+
+
+
+  /**
+   * the data has changed for the given cache id
+   *
+   * @param cacheId  cache id
+   * @param data  the new data
+   */
+  public void updateData(Object cacheId, Object data) {
+    synchronized (MUTEX) {
+        //      if(cacheId == null)
+        //          return addToCache(data, findType(data));
+        CacheInfo info = cache.get(cacheId);
+//      if(info==null) {
+//        return addToCache(data);
+//      }
+
+      int oldSize = info.data != null
+                    ? info.getSize()
+                    : 0;
+      info.setData(data);
+      int newSize = info.getSize();
+      totalSize -= oldSize;
+      totalSize += newSize;
+      checkCache();
+    }
+  }
+
+    public boolean inMemory(Object cacheId) {
+        synchronized (MUTEX) {
+        CacheInfo info =  cache.get(cacheId);
+        if(info == null)return false;
+        info.dataAccessed();
+        return (info.data!=null);
+        }
+    }
+
+
+
+  /**
+   * 
+   *
+   * @param cacheId  the cache id
+   *
+   * @return 
+   */
+  private Object getData(Object cacheId) {
+    CacheInfo info = null;
+    Object data = null;
+    synchronized (MUTEX) {
+      info = cache.get(cacheId);
+      if (info == null) return null;
+      data = info.data;
+      info.dataAccessed();
+      if (data != null) return data;
+      try {
+          long t1 = System.currentTimeMillis();
+        FileInputStream fis = new FileInputStream(info.cacheFile);
+        BufferedInputStream bis = new BufferedInputStream(fis,100000);
+        ObjectInputStream ois = new ObjectInputStream(bis);
+        info.setDataFromCache(data = ois.readObject());
+        long t2 = System.currentTimeMillis();
+        System.err.println("Read " + info.getSize() +" bytes from file in " + (t2-t1) +" ms");
+        totalSize += info.getSize();
+        ois.close();
+        bis.close();
+        fis.close();
+        checkCache();
+        info.cacheMissed();
+        return data;
+      }
+      catch (Exception exc) {
+        throw new RuntimeException(exc);
+      }
+    }
+  }
+
+    public  File getCacheFile() {
+        return new File(getCacheDir() + "/" + getId() + ".dat");
+    }
+
+
+
+
+
+
+  /**
+   * Remove the  item from the cache 
+   *
+   * @param cacheId  the cache id 
+   */
+  public void removeFromCache(Object cacheId) {
+    synchronized (MUTEX) {
+        removeFromCache(cache.get(cacheId));
+    }
+  }
+
+
+    private  void removeFromCache(CacheInfo info) {
+        if (info == null) {
+            return;
+        }
+        synchronized (MUTEX) {
+            if (info.data != null) {
+                info.data = null;
+                totalSize -= info.getSize();
+            }
+            cache.remove(info.id);
+            info.remove();
+        }
+    }
+
+
+    public void flushAllCachedData() {
+      synchronized (MUTEX) {
+          for (CacheInfo info : getCacheInfos()) {
+              flushCachedData(info);
+          }
+          Runtime.getRuntime().gc();
+      }
+    }
+
+
+  /**
+   * If this cacheinfo has never been written to disk then write it
+   * null out the data reference
+   *
+   * @param info  the cacheinfo
+   */
+  private void flushCachedData(CacheInfo info) {
+    try {
+      if (info.removeIfNeeded) {
+          removeFromCache(info);
+          return;
+      }
+
+
+      if (info.data == null) {
+        return;
+      }
+
+
+
+      if (!info.cacheFileGood) {
+        FileOutputStream fos = new FileOutputStream(info.cacheFile);
+        BufferedOutputStream bos = new BufferedOutputStream(fos,100000);
+        ObjectOutputStream oos = new ObjectOutputStream(fos);
+        oos.writeObject(info.data);
+        oos.close();
+        bos.close();
+        fos.close();
+      }
+      info.data = null;
+      totalSize -= info.getSize();
+    }
+    catch (Exception exc) {
+      throw new RuntimeException(exc);
+    }
+
+  }
+
+
+  /**
+   * Get the list of sorted CacheInfo objects
+   *
+   * @return  Sorted list of cacheinfos
+   */
+  private List<CacheInfo> getCacheInfos() {
+    synchronized (MUTEX) {
+      List<CacheInfo> infos = new ArrayList<CacheInfo>();
+      for (Enumeration keys = cache.keys(); keys.hasMoreElements(); ) {
+        CacheInfo info = cache.get(keys.nextElement());
+        infos.add(info);
+      }
+      Collections.sort(infos);
+      return infos;
+    }
+  }
+
+
+
+  public  void setMemoryPercent(double percentage) {
+      memoryPercentage = percentage;
+      checkCache();
+  }
+
+  public int getMaxSize() {
+      return (int)(memoryPercentage*Runtime.getRuntime().maxMemory());
+  }
+
+  /**
+   *  Check if we are above the max size. If so then flush data from memory  until we are below the threshold
+   */
+    public  void checkCache() {
+        if (totalSize < getMaxSize()) {
+            return;
+        }
+        synchronized (MUTEX) {
+            //First do the volatile ones
+            for (CacheInfo info : getCacheInfos()) {
+                if(info.removeIfNeeded) {
+                    flushCachedData(info);
+                    if (totalSize <= getMaxSize()) {
+                        break;
+                    }
+                }
+            }
+
+            if (totalSize <= getMaxSize()) {
+                for (CacheInfo info : getCacheInfos()) {
+                    flushCachedData(info);
+                    if (totalSize <= getMaxSize()) {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+
+
+
+
+  /**
+   * Print out the cache statistics 
+   */
+  public void printStats() {
+      System.err.println(getStats());
+  }
+
+
+  public String getStats() {
+    synchronized (MUTEX) {
+        StringBuffer sb = new StringBuffer();
+        int mb =(int)( getMaxSize()/(double)1000000.0);
+        int total =(int)( totalSize/(double)1000000.0);
+        sb.append("Cache total size:" + total +" MB   max size:" + mb +" MB  (" + (100*memoryPercentage)+"% of max memory)");
+        sb.append("\n");
+        List<CacheInfo> infos= getCacheInfos();
+        if(infos.size()==0) {
+            sb.append("nothing in cache");
+            sb.append("\n");
+        } else {
+            sb.append("entry size/in cache/data access/cache miss/last touched");
+            sb.append("\n");
+            int cnt = 0;
+            for (CacheInfo info : infos) {
+                sb.append("   #" + (++cnt) +" ");
+                sb.append(info.toString());
+                /*
+                          "   #" + (++cnt) +" cache entry:" + info.getSize() + "   " + (info.data != null) +
+                                   "   " + info.dataAccessedCnt + "   " + info.cacheMissedCnt + "   " +
+                                   new Date(info.lastTime));
+                */
+                sb.append("\n");
+
+                /*
+                sb.append("what:" + info.what);
+                sb.append("\n");
+                sb.append(info.where.substring(300));
+                sb.append("\n");
+                */
+            }
+
+      }
+        return sb.toString();
+    }
+  }
+
+
+  /**
+   * make sure that the totalSize is the same as the cacheinfos - for debugging
+ 
+   *
+   * @param where 
+   */
+  private void checkStats(String where) {
+    synchronized (MUTEX) {
+      int tmp = 0;
+      for (Enumeration keys = cache.keys(); keys.hasMoreElements(); ) {
+        CacheInfo info = cache.get(keys.nextElement());
+        if (info.data != null) tmp += info.getSize();
+      }
+
+      if (tmp != totalSize) {
+        System.err.println(
+          "WHOAA: " + where + "  " + tmp + " != total size:" + totalSize);
+        for (Enumeration keys = cache.keys(); keys.hasMoreElements(); ) {
+          CacheInfo info = cache.get(keys.nextElement());
+          System.err.println(
+            "   cache entry:" + info.getSize() + " " + (info.data != null));
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Class CacheInfo 
+   *
+   *
+   * @author IDV Development Team
+   */
+  private static class CacheInfo implements Comparable<CacheInfo> {
+
+    /**  */
+    private DataCacheManager cacheManager;
+
+    /**  */
+    private int type;
+
+    /**  */
+    private int size;
+
+    /**  */
+    private Object id;
+
+    /**  */
+    private long lastTime;
+
+    /**  */
+    private Object data;
+
+    /**  */
+    private File cacheFile;
+
+    /**           */
+    private boolean cacheFileGood = false;
+
+    /**           */
+    private int dataAccessedCnt = 0;
+
+    /**           */
+    private int cacheMissedCnt = 0;
+
+    private String where;
+
+    private String what;
+
+    private boolean removeIfNeeded = false;
+
+
+    /**
+     * 
+     *
+     * @param cacheManager 
+     * @param cacheId  the cache id
+     * @param data 
+     * @param type 
+     */
+    public CacheInfo(DataCacheManager cacheManager, Object cacheId, Object data,
+                     int type, boolean removeIfNeeded) {
+      this.id = cacheId;
+      this.cacheManager = cacheManager;
+      this.type = type;
+      this.removeIfNeeded = removeIfNeeded;
+      cacheFile = new File(cacheManager.getCacheDir() + "/" + cacheId + ".dat");
+      this.what = data.toString();
+      where = "";
+      //      where = ucar.unidata.util.LogUtil.getStackTrace();
+      setData(data);
+    }
+
+
+    /**
+     * 
+     */
+    private void dataAccessed() {
+      lastTime = System.currentTimeMillis();
+      dataAccessedCnt++;
+    }
+
+    /**
+     * 
+     */
+    private void cacheMissed() {
+      cacheMissedCnt++;
+    }
+
+    /**
+     * 
+     *
+     * @param data 
+     */
+    private void setData(Object data) {
+      lastTime = System.currentTimeMillis();
+      this.data = data;
+      cacheFileGood = false;
+      size = getArraySize(type, data);
+    }
+
+    /**
+     * Get a string representation of the type
+     *
+     * @param type  the type
+     * @return the string name of the type
+     */
+    public String getTypeName(int type) {
+        return getNameForType(type);
+    }
+
+    public String toString() {
+        return what+"   " + getTypeName(type) + ":" + getSize() + "   " + (data != null) + "   " + dataAccessedCnt + "   " + cacheMissedCnt + "   " + new Date(lastTime);
+    }
+
+    /**
+     * Compare this to another object
+     *
+     * @param o 
+     *
+     * @return  comparison
+     */
+    public int compareTo(CacheInfo o) {
+      CacheInfo that = (CacheInfo)o;
+      if (this.lastTime < that.lastTime) return -1;
+      if (this.lastTime == that.lastTime) return 0;
+      return 1;
+
+    }
+
+    /**
+     * 
+     *
+     * @param data 
+     */
+    private void setDataFromCache(Object data) {
+      lastTime = System.currentTimeMillis();
+      this.data = data;
+    }
+
+
+    /**
+     * 
+     */
+    private void remove() {
+        if(cacheFile!=null)
+            cacheFile.delete();
+    }
+
+    /**
+     * 
+     *
+     * @return 
+     */
+    public int getSize() {
+      return size;
+    }
+
+
+    /**
+     * 
+     *
+     * @return 
+     */
+    public Object getId() {
+      return id;
+    }
+  }
+
+
+/********
+  Begin generated access methods
+*****/
+private static final int TYPE_DOUBLE1D = 0;
+private static final int TYPE_FLOAT1D = 1;
+private static final int TYPE_INT1D = 2;
+private static final int TYPE_SHORT1D = 3;
+private static final int TYPE_BYTE1D = 4;
+private static final int TYPE_DOUBLE2D = 5;
+private static final int TYPE_FLOAT2D = 6;
+private static final int TYPE_INT2D = 7;
+private static final int TYPE_SHORT2D = 8;
+private static final int TYPE_BYTE2D = 9;
+private static final int TYPE_DOUBLE3D = 10;
+private static final int TYPE_FLOAT3D = 11;
+private static final int TYPE_INT3D = 12;
+private static final int TYPE_SHORT3D = 13;
+private static final int TYPE_BYTE3D = 14;
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public double[] getDoubleArray1D(Object cacheId) {
+        return (double[])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(double[] values) {
+    return addToCache(null, values, TYPE_DOUBLE1D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, double[] values) {
+      return addToCache(what, values, TYPE_DOUBLE1D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, double[] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_DOUBLE1D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public float[] getFloatArray1D(Object cacheId) {
+        return (float[])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(float[] values) {
+    return addToCache(null, values, TYPE_FLOAT1D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, float[] values) {
+      return addToCache(what, values, TYPE_FLOAT1D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, float[] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_FLOAT1D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public int[] getIntArray1D(Object cacheId) {
+        return (int[])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(int[] values) {
+    return addToCache(null, values, TYPE_INT1D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, int[] values) {
+      return addToCache(what, values, TYPE_INT1D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, int[] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_INT1D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public short[] getShortArray1D(Object cacheId) {
+        return (short[])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(short[] values) {
+    return addToCache(null, values, TYPE_SHORT1D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, short[] values) {
+      return addToCache(what, values, TYPE_SHORT1D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, short[] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_SHORT1D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public byte[] getByteArray1D(Object cacheId) {
+        return (byte[])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(byte[] values) {
+    return addToCache(null, values, TYPE_BYTE1D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, byte[] values) {
+      return addToCache(what, values, TYPE_BYTE1D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, byte[] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_BYTE1D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public double[][] getDoubleArray2D(Object cacheId) {
+        return (double[][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(double[][] values) {
+    return addToCache(null, values, TYPE_DOUBLE2D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, double[][] values) {
+      return addToCache(what, values, TYPE_DOUBLE2D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, double[][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_DOUBLE2D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public float[][] getFloatArray2D(Object cacheId) {
+        return (float[][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(float[][] values) {
+    return addToCache(null, values, TYPE_FLOAT2D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, float[][] values) {
+      return addToCache(what, values, TYPE_FLOAT2D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, float[][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_FLOAT2D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public int[][] getIntArray2D(Object cacheId) {
+        return (int[][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(int[][] values) {
+    return addToCache(null, values, TYPE_INT2D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, int[][] values) {
+      return addToCache(what, values, TYPE_INT2D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, int[][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_INT2D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public short[][] getShortArray2D(Object cacheId) {
+        return (short[][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(short[][] values) {
+    return addToCache(null, values, TYPE_SHORT2D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, short[][] values) {
+      return addToCache(what, values, TYPE_SHORT2D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, short[][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_SHORT2D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public byte[][] getByteArray2D(Object cacheId) {
+        return (byte[][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(byte[][] values) {
+    return addToCache(null, values, TYPE_BYTE2D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, byte[][] values) {
+      return addToCache(what, values, TYPE_BYTE2D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, byte[][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_BYTE2D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public double[][][] getDoubleArray3D(Object cacheId) {
+        return (double[][][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(double[][][] values) {
+    return addToCache(null, values, TYPE_DOUBLE3D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, double[][][] values) {
+      return addToCache(what, values, TYPE_DOUBLE3D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, double[][][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_DOUBLE3D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public float[][][] getFloatArray3D(Object cacheId) {
+        return (float[][][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(float[][][] values) {
+    return addToCache(null, values, TYPE_FLOAT3D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, float[][][] values) {
+      return addToCache(what, values, TYPE_FLOAT3D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, float[][][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_FLOAT3D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public int[][][] getIntArray3D(Object cacheId) {
+        return (int[][][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(int[][][] values) {
+    return addToCache(null, values, TYPE_INT3D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, int[][][] values) {
+      return addToCache(what, values, TYPE_INT3D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, int[][][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_INT3D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public short[][][] getShortArray3D(Object cacheId) {
+        return (short[][][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(short[][][] values) {
+    return addToCache(null, values, TYPE_SHORT3D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, short[][][] values) {
+      return addToCache(what, values, TYPE_SHORT3D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, short[][][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_SHORT3D, removeIfNeeded);
+  }
+
+
+
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public byte[][][] getByteArray3D(Object cacheId) {
+        return (byte[][][])getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(byte[][][] values) {
+    return addToCache(null, values, TYPE_BYTE3D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, byte[][][] values) {
+      return addToCache(what, values, TYPE_BYTE3D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, byte[][][] values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_BYTE3D, removeIfNeeded);
+  }
+
+
+
+/** Get the size of the array **/
+private static int getArraySize(int type, Object values) {
+
+   if (type == TYPE_DOUBLE1D) {
+        double[] data= (double[]) values;
+        
+        return 8*data.length;
+
+   }
+
+   if (type == TYPE_FLOAT1D) {
+        float[] data= (float[]) values;
+        
+        return 4*data.length;
+
+   }
+
+   if (type == TYPE_INT1D) {
+        int[] data= (int[]) values;
+        
+        return 4*data.length;
+
+   }
+
+   if (type == TYPE_SHORT1D) {
+        short[] data= (short[]) values;
+        
+        return 2*data.length;
+
+   }
+
+   if (type == TYPE_BYTE1D) {
+        byte[] data= (byte[]) values;
+        
+        return 1*data.length;
+
+   }
+
+   if (type == TYPE_DOUBLE2D) {
+        double[][] data= (double[][]) values;
+        if (data[0]==null) return 0;
+        return 8*data.length * data[0].length;
+
+   }
+
+   if (type == TYPE_FLOAT2D) {
+        float[][] data= (float[][]) values;
+        if (data[0]==null) return 0;
+        return 4*data.length * data[0].length;
+
+   }
+
+   if (type == TYPE_INT2D) {
+        int[][] data= (int[][]) values;
+        if (data[0]==null) return 0;
+        return 4*data.length * data[0].length;
+
+   }
+
+   if (type == TYPE_SHORT2D) {
+        short[][] data= (short[][]) values;
+        if (data[0]==null) return 0;
+        return 2*data.length * data[0].length;
+
+   }
+
+   if (type == TYPE_BYTE2D) {
+        byte[][] data= (byte[][]) values;
+        if (data[0]==null) return 0;
+        return 1*data.length * data[0].length;
+
+   }
+
+   if (type == TYPE_DOUBLE3D) {
+        double[][][] data= (double[][][]) values;
+        if (data[0]==null) return 0; if(data[0][0]==null) return 0;
+        return 8*data.length * data[0].length*data[0][0].length;
+
+   }
+
+   if (type == TYPE_FLOAT3D) {
+        float[][][] data= (float[][][]) values;
+        if (data[0]==null) return 0; if(data[0][0]==null) return 0;
+        return 4*data.length * data[0].length*data[0][0].length;
+
+   }
+
+   if (type == TYPE_INT3D) {
+        int[][][] data= (int[][][]) values;
+        if (data[0]==null) return 0; if(data[0][0]==null) return 0;
+        return 4*data.length * data[0].length*data[0][0].length;
+
+   }
+
+   if (type == TYPE_SHORT3D) {
+        short[][][] data= (short[][][]) values;
+        if (data[0]==null) return 0; if(data[0][0]==null) return 0;
+        return 2*data.length * data[0].length*data[0][0].length;
+
+   }
+
+   if (type == TYPE_BYTE3D) {
+        byte[][][] data= (byte[][][]) values;
+        if (data[0]==null) return 0; if(data[0][0]==null) return 0;
+        return 1*data.length * data[0].length*data[0][0].length;
+
+   }
+
+   throw new IllegalArgumentException("Unknown type:" + type);
+}
+
+
+/** Get the name of the type **/
+private static String getNameForType(int type) {
+    if (type == TYPE_DOUBLE1D) {return "double1d";}
+    if (type == TYPE_FLOAT1D) {return "float1d";}
+    if (type == TYPE_INT1D) {return "int1d";}
+    if (type == TYPE_SHORT1D) {return "short1d";}
+    if (type == TYPE_BYTE1D) {return "byte1d";}
+    if (type == TYPE_DOUBLE2D) {return "double2d";}
+    if (type == TYPE_FLOAT2D) {return "float2d";}
+    if (type == TYPE_INT2D) {return "int2d";}
+    if (type == TYPE_SHORT2D) {return "short2d";}
+    if (type == TYPE_BYTE2D) {return "byte2d";}
+    if (type == TYPE_DOUBLE3D) {return "double3d";}
+    if (type == TYPE_FLOAT3D) {return "float3d";}
+    if (type == TYPE_INT3D) {return "int3d";}
+    if (type == TYPE_SHORT3D) {return "short3d";}
+    if (type == TYPE_BYTE3D) {return "byte3d";}
+ return "unknown type";
+}
+
+
+}
+
diff --git a/visad/data/DataNode.java b/visad/data/DataNode.java
new file mode 100644
index 0000000..b711a21
--- /dev/null
+++ b/visad/data/DataNode.java
@@ -0,0 +1,226 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: DataNode.java,v 1.14 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+
+import java.rmi.RemoteException;
+import visad.Data;
+import visad.FlatField;
+import visad.TupleIface;
+import visad.UnimplementedException;
+import visad.VisADException;
+
+
+/**
+ * Abstract class for adapting a VisAD data object to the "Visitor"
+ * design pattern.  This class knows how to traverse an arbitrary VisAD
+ * data object.
+ */
+public abstract class
+DataNode
+{
+    /**
+     * Construct.  Protected to ensure use of the create() factory method.
+     */
+    protected
+    DataNode()
+    {
+    }
+
+
+    /**
+     * Factory method for creating an instance of the appropriate type.
+     *
+     * @param data	The VisAD data object to be traversed.
+     * @return	A DataNode that knows how to traverse the VisAD data object.
+     * @precondition	<code>data</code> is non-null.
+     * @exception UnimplementedException	A (soon to be implemented)
+     *		method isn't implemented yet.
+     */
+    public static DataNode
+    create(Data data)
+	throws UnimplementedException
+    {
+	DataNode	node;
+
+	/*
+	 * Watch the ordering in the following: the first match will be
+	 * taken.
+	 */
+	if (data instanceof TupleIface)
+	    node = new TupleNode((TupleIface)data);
+	else
+	if (data instanceof FlatField)
+	    node = new FlatFieldNode((FlatField)data);
+	else
+	    throw new UnimplementedException(
+		"VisAD data type not yet supported: " +
+		data.getClass().getName());
+
+	return node;
+    }
+
+
+    /**
+     * Accept a visitor and traverse the data object.
+     *
+     * @param visitor	The object that will have it's <code>visit()</code>
+     *			method called for each subcomponent of the VisAD data
+     *			object.
+     * @return		<code>visitor</code> for convenience.
+     * @precondition	<code>visitor</code> is non-null.
+     * @postcondition	<code>visitor</code> has visited the VisAD data object
+     *			<code>data</code>.
+     * @exception UnimplementedException	A (soon to be implemented)
+     *		method isn't implemented yet.
+     * @exception BadFormException	The VisAD data object doesn't "fit"
+     *		the data model used by <code>visitor</code>.
+     * @exception VisADException	Problem in core VisAD (probably
+     *		couldn't create some VisAD object).
+     * @exception RemoteException	Problem accessing the VisAD data
+     *		object.
+     * @see visad.data.DataVisitor
+     */
+    public abstract DataVisitor
+    accept(DataVisitor visitor)
+	throws UnimplementedException, BadFormException, VisADException,
+	    RemoteException;
+}
+
+
+/**
+ * Concrete class for traversing a VisAD Tuple.
+ */
+class
+TupleNode
+    extends	DataNode
+{
+    /**
+     * The VisAD Tuple
+     */
+    protected final TupleIface	tuple;
+
+
+    /**
+     * Construct from a VisAD Tuple.
+     *
+     * @param tuple	The VisAD Tuple to be traversed.
+     * @precondition	<code>tuple</code> is non-null.
+     */
+    protected
+    TupleNode(TupleIface tuple)
+    {
+	this.tuple = tuple;
+    }
+
+
+    /**
+     * Accept a visitor and traverse the Tuple.
+     *
+     * @param visitor	The object that will have it's <code>visit()</code>
+     *			method called for each component of the VisAD
+     *			Tuple.
+     * @precondition	<code>visitor</code> is non-null.
+     * @postcondition	<code>visitor</code> has visited <code>tuple</code>.
+     * @exception UnimplementedException	A (soon to be implemented)
+     *		method isn't implemented yet.
+     * @exception BadFormException	The VisAD data object doesn't "fit"
+     *		the data model used by <code>visitor</code>.
+     * @exception VisADException	Problem in core VisAD (probably
+     *		couldn't create some VisAD object).
+     * @see visad.data.DataVisitor
+     */
+    public DataVisitor
+    accept(DataVisitor visitor)
+	throws UnimplementedException, BadFormException, VisADException,
+	RemoteException
+    {
+	if (visitor.visit(tuple))
+	{
+	    int	ncomp = tuple.getDimension();
+
+	    for (int icomp = 0; icomp < ncomp; ++icomp)
+		DataNode.create(tuple.getComponent(icomp)).accept(visitor);
+	}
+
+	return visitor;
+    }
+}
+
+
+/**
+ * Concrete class for traversing a VisAD FlatField.
+ */
+class
+FlatFieldNode
+    extends	DataNode
+{
+    /**
+     * The VisAD FlatField
+     */
+    protected final FlatField	field;
+
+
+    /**
+     * Construct from a VisAD FlatField.
+     *
+     * @param field	The VisAD FlatField to be traversed.
+     * @precondition    <code>field</code> is non-null.
+     */
+    protected
+    FlatFieldNode(FlatField field)
+    {
+	this.field = field;
+    }
+
+
+    /**
+     * Accept a visitor and traverse the FlatField.
+     *
+     * @param visitor   The object that will have it's <code>visit()</code>
+     *			method called for each component of the VisAD
+     *			FlatField.
+     * @precondition    <code>visitor</code> is non-null.
+     * @postcondition	<code>visitor</code> has visited <code>field</code>.
+     * @exception UnimplementedException        A (soon to be implemented)
+     *		method isn't implemented yet.
+     * @exception BadFormException      The VisAD data object doesn't "fit"
+     *		the data model used by <code>visitor</code>.
+     * @exception VisADException        Problem in core VisAD (probably
+     *		couldn't create some VisAD object).
+     * @see visad.data.DataVisitor
+     */
+    public DataVisitor
+    accept(DataVisitor visitor)
+	throws UnimplementedException, BadFormException, VisADException,
+	    RemoteException
+    {
+	visitor.visit(field);
+
+	return visitor;
+    }
+}
+
+// TODO: Add more types of DataNodes.
diff --git a/visad/data/DataProcessor.java b/visad/data/DataProcessor.java
new file mode 100644
index 0000000..1ac5c2e
--- /dev/null
+++ b/visad/data/DataProcessor.java
@@ -0,0 +1,183 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import visad.*;
+
+/**
+ * A hierarchy of methods used by
+ * {@link visad.data.BaseDataProcessor BaseDataProcessor}
+ * to write an arbitrary {@link visad.Data Data} object.
+ */
+public interface DataProcessor
+{
+  void process(DataImpl data, Object token)
+    throws VisADException;
+  void processDoubleSet(SetType type, CoordinateSystem cs, Unit[] units,
+                        DoubleSet set, Object token)
+    throws VisADException;
+  void processFieldImpl(FunctionType type, Set set, FieldImpl fld,
+                        Object token)
+    throws VisADException;
+  void processFlatField(FunctionType type, Set domainSet, CoordinateSystem cs,
+                        CoordinateSystem[] rangeCS, Set[] rangeSets,
+                        Unit[] units, FlatField fld, Object token)
+    throws VisADException;
+  void processFloatSet(SetType type, CoordinateSystem cs, Unit[] units,
+                       FloatSet set, Object token)
+    throws VisADException;
+  void processGridded1DDoubleSet(SetType type, double[][] samples,
+                                 int[] lengths, CoordinateSystem cs,
+                                 Unit[] units, ErrorEstimate[] errors,
+                                 Gridded1DDoubleSet set, Object token)
+    throws VisADException;
+  void processGridded2DDoubleSet(SetType type, double[][] samples,
+                                 int[] lengths, CoordinateSystem cs,
+                                 Unit[] units, ErrorEstimate[] errors,
+                                 Gridded2DDoubleSet set, Object token)
+    throws VisADException;
+  void processGridded3DDoubleSet(SetType type, double[][] samples,
+                                 int[] lengths, CoordinateSystem cs,
+                                 Unit[] units, ErrorEstimate[] errors,
+                                 Gridded3DDoubleSet set, Object token)
+    throws VisADException;
+  void processGridded1DSet(SetType type, float[][] samples, int[] lengths,
+                           CoordinateSystem cs, Unit[] units,
+                           ErrorEstimate[] errors, Gridded1DSet set,
+                           Object token)
+    throws VisADException;
+  void processGridded2DSet(SetType type, float[][] samples, int[] lengths,
+                           CoordinateSystem cs, Unit[] units,
+                           ErrorEstimate[] errors, Gridded2DSet set,
+                           Object token)
+    throws VisADException;
+  void processGridded3DSet(SetType type, float[][] samples, int[] lengths,
+                           CoordinateSystem cs, Unit[] units,
+                           ErrorEstimate[] errors, Gridded3DSet set,
+                           Object token)
+    throws VisADException;
+
+  void processGriddedSet(SetType type, float[][] samples, int[] lengths,
+                         CoordinateSystem cs, Unit[] units,
+                         ErrorEstimate[] errors, GriddedSet set,
+                         Object token)
+    throws VisADException;
+  void processInteger1DSet(SetType type, int[] lengths, CoordinateSystem cs,
+                           Unit[] units, ErrorEstimate[] errors,
+                           Integer1DSet set, Object token)
+    throws VisADException;
+  void processInteger2DSet(SetType type, int[] lengths, CoordinateSystem cs,
+                           Unit[] units, ErrorEstimate[] errors,
+                           Integer2DSet set, Object token)
+    throws VisADException;
+  void processInteger3DSet(SetType type, int[] lengths, CoordinateSystem cs,
+                           Unit[] units, ErrorEstimate[] errors,
+                           Integer3DSet set, Object token)
+    throws VisADException;
+  void processIntegerNDSet(SetType type, int[] lengths, CoordinateSystem cs,
+                           Unit[] units, ErrorEstimate[] errors,
+                           IntegerNDSet set, Object token)
+    throws VisADException;
+  void processIrregular1DSet(SetType type, float[][] samples,
+                             CoordinateSystem cs, Unit[] units,
+                             ErrorEstimate[] errors, Irregular1DSet set,
+                             Object token)
+    throws VisADException;
+  void processIrregular2DSet(SetType type, float[][] samples,
+                             CoordinateSystem cs, Unit[] units,
+                             ErrorEstimate[] errors, Delaunay delaunay,
+                             Irregular2DSet set, Object token)
+    throws VisADException;
+  void processIrregular3DSet(SetType type, float[][] samples,
+                             CoordinateSystem cs, Unit[] units,
+                             ErrorEstimate[] errors, Delaunay delaunay,
+                             Irregular3DSet set, Object token)
+    throws VisADException;
+  void processIrregularSet(SetType type, float[][] samples,
+                           CoordinateSystem cs, Unit[] units,
+                           ErrorEstimate[] errors, Delaunay delaunay,
+                           IrregularSet set, Object token)
+    throws VisADException;
+
+  void processLinear1DSet(SetType type, double[] firsts, double[] lasts,
+                          int[] lengths, CoordinateSystem cs, Unit[] units,
+                          ErrorEstimate[] errors, Linear1DSet set,
+                          Object token)
+    throws VisADException;
+  void processLinear2DSet(SetType type, double[] firsts, double[] lasts,
+                          int[] lengths, CoordinateSystem cs, Unit[] units,
+                          ErrorEstimate[] errors, Linear2DSet set,
+                          Object token)
+    throws VisADException;
+  void processLinear3DSet(SetType type, double[] firsts, double[] lasts,
+                          int[] lengths, CoordinateSystem cs, Unit[] units,
+                          ErrorEstimate[] errors, Linear3DSet set,
+                          Object token)
+    throws VisADException;
+  void processLinearLatLonSet(SetType type, double[] firsts, double[] lasts,
+                              int[] lengths, CoordinateSystem cs,
+                              Unit[] units, ErrorEstimate[] errors,
+                              LinearLatLonSet set, Object token)
+    throws VisADException; 
+  void processLinearNDSet(SetType type, double[] firsts, double[] lasts,
+                          int[] lengths, CoordinateSystem cs, Unit[] units,
+                          ErrorEstimate[] errors, LinearNDSet set,
+                          Object token)
+    throws VisADException;
+  void processList1DSet(SetType type, float[] list, CoordinateSystem cs,
+                        Unit[] units, List1DSet set, Object token)
+    throws VisADException;
+  void processProductSet(SetType type, SampledSet[] sets, CoordinateSystem cs,
+                         Unit[] units, ErrorEstimate[] errors, ProductSet set,
+                         Object token)
+    throws VisADException;
+  void processReal(RealType type, double value, Unit unit,
+                   ErrorEstimate error, Real real, Object token)
+    throws VisADException;
+  void processRealTuple(RealTupleType type, Real[] components,
+                        CoordinateSystem cs, RealTuple rt, Object token)
+    throws VisADException;
+  void processSampledSet(SetType st, int manifold_dimension,
+                         CoordinateSystem cs, Unit[] units,
+                         ErrorEstimate[] errors, SampledSet set, Object token)
+    throws VisADException;
+  void processSimpleSet(SetType st, int manifold_dimension,
+                        CoordinateSystem cs, Unit[] units,
+                        ErrorEstimate[] errors, SimpleSet set, Object token)
+    throws VisADException;
+  void processSingletonSet(RealTuple sample, CoordinateSystem cs,
+                           Unit[] units, ErrorEstimate[] errors,
+                           SingletonSet set, Object token)
+    throws VisADException;
+  void processText(TextType type, String value, boolean missing, Text text,
+                   Object token)
+    throws VisADException;
+  void processTuple(TupleType type, Data[] components, Tuple t,
+                    Object token)
+    throws VisADException;
+  void processUnionSet(SetType type, SampledSet[] sets, UnionSet set,
+                       Object token)
+    throws VisADException;
+  void processUnknownData(DataImpl data, Object token)
+    throws VisADException;
+}
diff --git a/visad/data/DataRange.java b/visad/data/DataRange.java
new file mode 100644
index 0000000..9311883
--- /dev/null
+++ b/visad/data/DataRange.java
@@ -0,0 +1,236 @@
+/*
+ * $Id: DataRange.java,v 1.1 2009-10-23 10:28:41 jeffmc Exp $
+ *
+ * Copyright  1997-2004 Unidata Program Center/University Corporation for
+ * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
+ * support at unidata.ucar.edu.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+package visad.data;
+
+
+/**
+ * Holds a simple min/max range
+ *
+ *
+ */
+public class DataRange implements java.io.Serializable {
+
+
+    /** The range */
+    public double min, max;
+
+
+    /**
+     * Default ctor
+     *
+     */
+    public DataRange() {
+        min = 0.0;
+        max = 1.0;
+    }
+
+    /**
+     * Create a range with min, max
+     *
+     * @param min min
+     * @param max max
+     *
+     */
+    public DataRange(double min, double max) {
+        this.min = min;
+        this.max = max;
+    }
+
+    /**
+     * ctor
+     *
+     * @param a 2-ary array holding min/max
+     *
+     */
+    public DataRange(double[] a) {
+        this(a[0], a[1]);
+    }
+
+    /**
+     * copy ctor
+     *
+     * @param r object
+     *
+     */
+    public DataRange(DataRange r) {
+        if (r != null) {
+            this.min = r.min;
+            this.max = r.max;
+        }
+    }
+
+    /**
+     * Equals
+     *
+     * @param o Object
+     * @return equals
+     */
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        }
+        if ( !(o instanceof DataRange)) {
+            return false;
+        }
+        DataRange other = (DataRange) o;
+        return ((min == other.min) && (max == other.max));
+    }
+
+    /**
+     * set the values
+     *
+     * @param min min
+     * @param max max
+     */
+    public void set(double min, double max) {
+        this.min = min;
+        this.max = max;
+    }
+
+
+    /**
+     * Get the min
+     * @return  The min value
+     */
+    public double getMin() {
+        return min;
+    }
+
+    /**
+     * Get the max
+     * @return  The max value
+     */
+    public double getMax() {
+        return max;
+    }
+
+    /**
+     * Set the min
+     *
+     * @param v  value
+     */
+    public void setMin(double v) {
+        min = v;
+    }
+
+    /**
+     * Set the max
+     *
+     * @param v  value
+     */
+    public void setMax(double v) {
+        max = v;
+    }
+
+    /**
+     * Set the min
+     *
+     * @param v value
+     */
+    public void setMin(int v) {
+        min = (double) v;
+    }
+
+    /**
+     * Set the max
+     *
+     * @param v value
+     */
+    public void setMax(int v) {
+        max = (double) v;
+    }
+
+    /**
+     * Get a 2-aray array holding min/max
+     * @return array of min and max
+     */
+    public double[] asArray() {
+        return new double[] { min, max };
+    }
+
+    /**
+     * Get a 2-aray array holding min/max
+     * @return array of min and max
+     */
+    public float[] asFloatArray() {
+        return new float[] { (float) min, (float) max };
+    }
+
+    /**
+     * max-min
+     * @return max-min
+     */
+    public double span() {
+        return (max - min);
+    }
+
+    /**
+     * max-min
+     * @return max-min
+     */
+    public double getSpan() {
+        return span();
+    }
+
+    /**
+     * get abs(max-min)
+     * @return abs(max-min)
+     */
+    public double getAbsSpan() {
+        return Math.abs(span());
+    }
+
+    /**
+     * Get the mid point
+     * @return mid point
+     */
+    public double getMid() {
+        return min + span() / 2.0;
+    }
+
+
+    /**
+     * Get percent along the way between min and max
+     *
+     * @param percent percent
+     * @return value
+     */
+    public double getValueOfPercent(double percent) {
+        return getMin() + getSpan() * percent;
+    }
+
+    /**
+     * Ge tthe percent the given value is between min and max
+     *
+     * @param v value
+     * @return percent
+     */
+    public double getPercent(double v) {
+        return (v - min) / span();
+    }
+
+
+
+}
+
diff --git a/visad/data/DataVisitor.java b/visad/data/DataVisitor.java
new file mode 100644
index 0000000..a38bf00
--- /dev/null
+++ b/visad/data/DataVisitor.java
@@ -0,0 +1,81 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: DataVisitor.java,v 1.13 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+
+import java.rmi.RemoteException;
+import visad.FlatField;
+import visad.TupleIface;
+import visad.VisADException;
+
+
+/**
+ * Abstract class for visiting a VisAD data object.  The derived,
+ * concrete subclasses are data-form dependent.  The default action
+ * upon visiting a VisAD data object is to do nothing and tell the caller
+ * to continue.
+ */
+public abstract class
+DataVisitor
+{
+    /**
+     * Visit a VisAD Tuple.
+     *
+     * @param tuple	The VisAD Tuple being visited.
+     * @precondition	<code>tuple</code> is non-null.
+     * @postcondition	<code>tuple</code> has been visited.
+     * @exception BadFormException	The Tuple doesn't fit the data model
+     *					used by the visitor.
+     * @exception VisADException	Core VisAD problem (probably couldn't
+     *					create a VisAD object).
+     * @see visad.data.DataNode
+     */
+    public boolean
+    visit(TupleIface tuple)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return true;
+    }
+
+
+    /**
+     * Visit a VisAD FlatField.
+     *
+     * @param field	The VisAD FlatField being visited.
+     * @precondition	<code>field</code> is non-null.
+     * @postcondition	<code>field</code> has been visited.
+     * @exception BadFormException	The Tuple doesn't fit the data model
+     *					used by the visitor.
+     * @exception VisADException	Core VisAD problem (probably couldn't
+     *					create a VisAD object).
+     * @see visad.data.DataNode
+     */
+    public boolean
+    visit(FlatField field)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return true;
+    }
+}
diff --git a/visad/data/DataWriter.java b/visad/data/DataWriter.java
new file mode 100644
index 0000000..9bd063e
--- /dev/null
+++ b/visad/data/DataWriter.java
@@ -0,0 +1,71 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.IOException;
+import java.io.File;
+
+/**
+ * Standard routines used to write a {@link visad.Data Data} object.
+ */
+public interface DataWriter
+  extends DataProcessor
+{
+  /**
+   * Close the file
+   *
+   * @exception IOException If there is a problem.
+   */
+  void close()
+    throws IOException;
+
+  /**
+   * Flush all data to disk.
+   *
+   * @exception IOException If there is a problem.
+   */
+  void flush()
+    throws IOException;
+
+  /**
+   * Open the named file.  If a file is already being written to,
+   * all data will be flushed and the file will be closed.
+   *
+   * @param name The path used to open the file.
+   *
+   * @exception IOException If there is a problem.
+   */
+  void setFile(String name)
+    throws IOException;
+
+  /**
+   * Open the specified file.  If a file is already being written to,
+   * all data will be flushed and the file will be closed.
+   *
+   * @param file The file.
+   *
+   * @exception IOException If there is a problem.
+   */
+  void setFile(File file)
+    throws IOException;
+}
diff --git a/visad/data/DefaultFamily.java b/visad/data/DefaultFamily.java
new file mode 100644
index 0000000..d35ecbc
--- /dev/null
+++ b/visad/data/DefaultFamily.java
@@ -0,0 +1,296 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.IOException;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.VisADException;
+
+import visad.data.bio.LociForm;
+import visad.data.dods.DODSForm;
+import visad.data.fits.FitsForm;
+import visad.data.gif.GIFForm;
+import visad.data.gis.DemFamily;
+import visad.data.hdfeos.HdfeosAdaptedForm;
+import visad.data.hrit.HRITForm;
+import visad.data.jai.JAIForm;
+import visad.data.mcidas.AreaForm;
+import visad.data.mcidas.MapForm;
+import visad.data.mcidas.PointForm;
+import visad.data.netcdf.Plain;
+import visad.data.text.TextForm;
+import visad.data.vis5d.Vis5DFamily;
+import visad.data.visad.VisADForm;
+
+/**
+  * A container for all the officially supported VisAD datatypes.<br>
+  * <br>
+  * To read a <tt>Data</tt> object from a file or URL:<br>
+  * <pre>
+  *    Data data = new DefaultFamily("dflt").open(string);
+  * </pre>
+  * <br>
+  * To save a Data object to a file:<br>
+  * <pre>
+  *    new DefaultFamily("dflt").save("file.nc", data, true);
+  * </pre>
+  * <br>
+  * To add a Data object to an existing file:<br>
+  * <pre>
+  *    new DefaultFamily("dflt").add("file.nc", data, true);
+  * </pre>
+  */
+public class DefaultFamily
+	extends FunctionFormFamily
+{
+  /**
+    * List of all supported VisAD datatype Forms.
+    */
+  /*
+   *  note that I hardcoded the number of FormNodes (100)
+   *  increase this if you add a new FormNode
+   */
+  private static FormNode[] list = new FormNode[100];
+  private static boolean listInitialized = false;
+
+  /**
+   * Build a list of all known file adapter Forms
+   */
+  private static void buildList()
+  {
+    int i = 0;
+ 
+    try {
+      list[i] = DODSForm.dodsForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new FitsForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new GIFForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new LociForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new HdfeosAdaptedForm();
+      i++;
+    } catch (Throwable t) {
+    }  
+    try {
+      list[i] = new Plain();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new Vis5DFamily("vis5d");
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new VisADForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new VisADForm(true);
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new AreaForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new PointForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new MapForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new DemFamily("Dem Data");
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new TextForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new JAIForm();
+      i++;
+    } catch (Throwable t) {
+    }
+    try {
+      list[i] = new HRITForm();
+      i++;
+    } catch (Throwable t) {
+    }
+
+    // added to support HDF5 adapter (visad.data.hdf5.HDF5Form)
+    try {
+      Object hdf5form = null;
+      ClassLoader cl = ClassLoader.getSystemClassLoader();
+      Class hdf5formClass = cl.loadClass("visad.data.hdf5.HDF5Form");
+      hdf5form = hdf5formClass.newInstance();
+      if (hdf5form != null) list[i++] = (Form)hdf5form;
+    } catch (Throwable t) {
+    }
+
+    // throw an Exception if too many Forms for list
+    FormNode junk = list[i];
+
+    while (i < list.length) {
+      list[i++] = null;
+    }
+    listInitialized = true; // WLH 24 Jan 2000
+  }
+
+  /**
+    * Add a Form to the list of supported VisAD datatype Forms.
+    *
+    * Forms are added to the front of the list, which means
+    * that they will take precedence over existing Forms.
+    * If, for example, an added Form claims to open files
+    * ending in <tt>.gif</tt>, it will supersede the standard
+    * {@link visad.data.gif.GIFForm GIFForm}.
+    *
+    * If the added form discovers that it cannot handle a
+    * file, it can simply throw an exception and DefaultFamily
+    * will pass the file onto the next Form in the list, so any
+    * superseded Form will still be able to open the file.
+    *
+    * @exception ArrayIndexOutOfBoundsException
+    *			If there is no more room in the list.
+    */
+  public static void addFormToList(FormNode form)
+    throws ArrayIndexOutOfBoundsException
+  {
+    synchronized (list) {
+      if (!listInitialized) {
+	buildList();
+      }
+
+      int i = 0;
+      while (i < list.length) {
+        if (list[i] == null) {
+          System.arraycopy(list, 0, list, 1, i);
+          list[0] = form;
+          return;
+        }
+        i++;
+      }
+    }
+
+    throw new ArrayIndexOutOfBoundsException("Only " + list.length +
+                                             " entries allowed");
+  }
+
+  /**
+    * Construct a family of the supported VisAD datatype Forms
+    */
+  public DefaultFamily(String name)
+  {
+    this(name, false);
+  }
+
+  /**
+    * Construct a family of the supported VisAD datatype Forms, with a
+    * netCDF form that converts char to Text if netcdfText flag is set.
+    */
+  public DefaultFamily(String name, boolean netcdfText) {
+    super(name);
+
+    synchronized (list) {
+      if (!listInitialized) {
+	buildList();
+      }
+    }
+
+    if (netcdfText) {
+      forms.addElement(new Plain(true));
+    }
+
+    for (int i = 0; i < list.length && list[i] != null; i++) {
+      forms.addElement(list[i]);
+    }
+  }
+
+  /**
+    * Test the DefaultFamily class
+    */
+  public static void main(String[] args)
+	throws BadFormException, IOException, RemoteException, VisADException
+  {
+    if (args.length < 1) {
+      System.err.println("Usage: DefaultFamily [-v] infile [infile ...]");
+      System.exit(1);
+      return;
+    }
+
+    int		iarg;
+    boolean	verbose = false;	// default
+    for (iarg = 0; iarg < args.length; iarg++) {
+      String	arg = args[iarg];
+      if (!arg.startsWith("-"))
+	break;
+      if (arg.equals("--"))
+      {
+	iarg++;
+	break;
+      }
+      if (arg.equals("-v"))
+	verbose = true;
+    }
+
+    DefaultFamily fr = new DefaultFamily("sample");
+
+    for (; iarg < args.length; iarg++) {
+      String	arg = args[iarg];
+      Data data;
+      System.out.println("Trying dataset " + args[iarg]);
+      data = fr.open(args[iarg]);
+      if (verbose)
+	  System.out.println(args[iarg] + ":\n" + data);
+      else
+	  System.out.println(args[iarg] + ": " + data.getType().prettyString());
+    }
+  }
+}
diff --git a/visad/data/DefaultFamilyTest.java b/visad/data/DefaultFamilyTest.java
new file mode 100644
index 0000000..726305d
--- /dev/null
+++ b/visad/data/DefaultFamilyTest.java
@@ -0,0 +1,67 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import visad.Data;
+
+public class DefaultFamilyTest
+{
+  /**
+    * Test the DefaultFamily class by importing and then exporting a dataset.
+    */
+  public static void main(String[] args)
+  {
+    if (args.length != 2) {
+      System.err.println("Usage: DefaultFamilyTest infile outfile");
+      System.exit(1);
+    }
+    if (args[0].equals(args[1])) {
+      System.err.println("Dataset specifications must be distinct");
+      System.exit(1);
+    }
+
+    String in = args[0];
+    String out = args[1];
+
+    DefaultFamily df = new DefaultFamily("DefaultFamilyTest");
+
+    System.out.println("Opening dataset " + in);
+    try {
+        Data data = df.open(in);
+        System.out.println("Saving dataset " + out);
+        try {
+            df.save(out, data, true);
+        }
+        catch (Exception e) {
+            System.err.println("Couldn't save dataset: " + e);
+            System.exit(1);
+        }
+    }
+    catch (Exception e) {
+        System.err.println("Couldn't open dataset: " + e);
+        System.exit(1);
+    }
+
+    System.exit(0);
+  }
+}
diff --git a/visad/data/DirectoryRepository.java b/visad/data/DirectoryRepository.java
new file mode 100644
index 0000000..7cd5113
--- /dev/null
+++ b/visad/data/DirectoryRepository.java
@@ -0,0 +1,154 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: DirectoryRepository.java,v 1.14 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+
+/**
+ * A data object repository implemented as files in a local directory.
+ */
+public class DirectoryRepository extends Repository
+{
+    /**
+     * The directory.
+     * Effectively "final" if the constructor succeeds.
+     */
+    protected File	dir;
+
+    /**
+     * Unambiguous representation of directory for error messages.
+     * Effectively "final" if the constructor succeeds.
+     */
+    protected String	dirString;
+
+
+    /**
+     * Construct a directory repository with support for the default
+     * forms of data.
+     */
+    public
+    DirectoryRepository(String name, String location)
+	throws BadRepositoryException, IOException
+    {
+	super(name, location);
+
+	// Check the directory.
+	try
+	{
+	    dir = new File(getLocation());
+	}
+	catch (NullPointerException e)
+	{
+	    throw new BadRepositoryException("Null repository name");
+	}
+	dirString = "\"" + getName() + "\" (path \"" + getLocation() + "\")";
+
+	if (!dir.isDirectory())
+	    throw new BadRepositoryException("Repository " + dirString +
+		" is not a directory");
+	if (!dir.canRead())
+	    throw new BadRepositoryException("Repository " + dirString +
+		" is not readable");
+    }
+
+
+    // TODO: method(s) for constructing the data form hierarchy
+
+
+    /**
+     * Return an enumeration of the data objects in this repository.
+     */
+    public Enumeration
+    getEnumeration()
+	throws	BadRepositoryException, SecurityException
+    {
+	return new Enumerator();
+    }
+
+
+    /**
+     * Inner class for enumerating the files in the directory.
+     */
+    public class
+    Enumerator
+	implements	Enumeration
+    {
+	protected int			i;
+	protected final String[]	list;
+
+	protected
+	Enumerator()
+	    throws SecurityException
+	{
+	    list = dir.list();
+	    i = 0;
+	}
+
+	public boolean
+	hasMoreElements()
+	{
+	    return i < list.length;
+	}
+
+	public Object
+	nextElement()
+	    throws NoSuchElementException
+	{
+	    if (i == list.length)
+		throw new NoSuchElementException();
+
+	    return list[i++];
+	}
+    }
+
+
+    /**
+     * Return the fully-qualified name of a persistent data object.
+     */
+    protected String fullName(String id)
+    {
+	return getLocation() + File.separator + id;
+    }
+
+
+    /**
+     * Test this class.
+     */
+    public static void main(String[] args)
+	throws BadRepositoryException, IOException
+    {
+	DirectoryRepository	dir = new DirectoryRepository("Test", ".");
+
+	for (Enumeration en = dir.getEnumeration(); en.hasMoreElements();)
+	    System.out.println((String)en.nextElement());
+
+	System.out.println("dir.fullName(\"foo.bar\") = " +
+	    dir.fullName("foo.bar"));
+    }
+}
diff --git a/visad/data/EmptyDataProcessor.java b/visad/data/EmptyDataProcessor.java
new file mode 100644
index 0000000..75f0c63
--- /dev/null
+++ b/visad/data/EmptyDataProcessor.java
@@ -0,0 +1,337 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import visad.*;
+
+/**
+ * A do-nothing DataProcessor implementation.
+ *
+ * All methods throw
+ * {@link visad.UnimplementedException UnimplementedException}
+ */
+public class EmptyDataProcessor
+  extends BaseDataProcessor
+{
+  public EmptyDataProcessor() { }
+
+  public void processDoubleSet(SetType type, CoordinateSystem cs,
+                               Unit[] units, DoubleSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processFieldImpl(FunctionType type, Set set, FieldImpl fld,
+                               Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processFlatField(FunctionType type, Set domainSet,
+                               CoordinateSystem cs,
+                               CoordinateSystem[] rangeCS, Set[] rangeSets,
+                               Unit[] units, FlatField fld, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processFloatSet(SetType type, CoordinateSystem cs,
+                              Unit[] units, FloatSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processGridded1DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded1DDoubleSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processGridded2DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded2DDoubleSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processGridded3DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded3DDoubleSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processGridded1DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded1DSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processGridded2DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded2DSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processGridded3DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded3DSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processGriddedSet(SetType type, float[][] samples,
+                                int[] lengths, CoordinateSystem cs,
+                                Unit[] units, ErrorEstimate[] errors,
+                                GriddedSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processInteger1DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer1DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processInteger2DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer2DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processInteger3DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer3DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processIntegerNDSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, IntegerNDSet set,
+                                  Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processIrregular1DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors,
+                                    Irregular1DSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processIrregular2DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors, Delaunay delaunay,
+                                    Irregular2DSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processIrregular3DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors, Delaunay delaunay,
+                                    Irregular3DSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processIrregularSet(SetType type, float[][] samples,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Delaunay delaunay,
+                                  IrregularSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processLinear1DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear1DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processLinear2DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear2DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processLinear3DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear3DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processLinearLatLonSet(SetType type, double[] firsts,
+                                     double[] lasts, int[] lengths,
+                                     CoordinateSystem cs, Unit[] units,
+                                     ErrorEstimate[] errors,
+                                     LinearLatLonSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processLinearNDSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, LinearNDSet set,
+                                 Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processList1DSet(SetType type, float[] list,
+                               CoordinateSystem cs, Unit[] units,
+                               List1DSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processProductSet(SetType type, SampledSet[] sets,
+                                CoordinateSystem cs, Unit[] units,
+                                ErrorEstimate[] errors, ProductSet set,
+                                Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processReal(RealType type, double value, Unit unit,
+                          ErrorEstimate error, Real real, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processRealTuple(RealTupleType type, Real[] components,
+                               CoordinateSystem cs, RealTuple rt,
+                               Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processSampledSet(SetType st, int manifold_dimension,
+                                CoordinateSystem cs, Unit[] units,
+                                ErrorEstimate[] errors, SampledSet set,
+                                Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processSimpleSet(SetType st, int manifold_dimension,
+                               CoordinateSystem cs, Unit[] units,
+                               ErrorEstimate[] errors, SimpleSet set,
+                               Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processSingletonSet(RealTuple sample, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  SingletonSet set, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processText(TextType type, String value, boolean missing,
+                          Text text, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processTuple(TupleType type, Data[] components, Tuple t,
+                           Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processUnionSet(SetType type, SampledSet[] sets, UnionSet set,
+                              Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processUnknownData(DataImpl data, Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+}
diff --git a/visad/data/EmptyDataWriter.java b/visad/data/EmptyDataWriter.java
new file mode 100644
index 0000000..07f1b06
--- /dev/null
+++ b/visad/data/EmptyDataWriter.java
@@ -0,0 +1,40 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A do-nothing DataWriter implementation
+ */
+public class EmptyDataWriter
+  extends EmptyDataProcessor
+  implements DataWriter
+{
+  public EmptyDataWriter() { }
+  public void close() throws IOException { }
+  public void flush() throws IOException { }
+  public void setFile(String name) { }
+  public void setFile(File file) { }
+}
diff --git a/visad/data/FileAccessor.java b/visad/data/FileAccessor.java
new file mode 100644
index 0000000..ee26ded
--- /dev/null
+++ b/visad/data/FileAccessor.java
@@ -0,0 +1,58 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: FileAccessor.java,v 1.11 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+import visad.Data;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.VisADException;
+import java.rmi.RemoteException;
+
+
+/**
+ * Exchange data with a "file".
+ */
+public abstract class FileAccessor
+{
+    public abstract void	writeFile(
+				    int[]	fileLocations,
+				    Data	range);
+
+
+    public abstract double[][]	readFlatField(
+				    FlatField	template,
+				    int[]	fileLocation);
+
+
+    public abstract void	writeFlatField(
+				    double[][]	values,
+				    FlatField	template,
+				    int[]	fileLocation);
+
+
+    public abstract FlatField getFlatField() throws VisADException, RemoteException;
+
+    public abstract FunctionType getFunctionType() throws VisADException;
+}
diff --git a/visad/data/FileField.java b/visad/data/FileField.java
new file mode 100644
index 0000000..2e2e434
--- /dev/null
+++ b/visad/data/FileField.java
@@ -0,0 +1,101 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: FileField.java,v 1.12 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FieldException;
+import visad.VisADException;
+import visad.FunctionType;
+
+
+public class FileField extends FieldImpl {
+  // note FileField extends FieldImpl but may not inherit
+  // any of its methods - it must re-implement all of them
+  // through the adapted FieldImpl
+
+  FieldImpl adaptedField;  // won't be null
+
+  // this is the FileAccessor for reading and writing range
+  // samples to the adapted file
+  FileAccessor fileAccessor;
+  // these are the locations in the file for the range samples
+  // of this FileField;
+  // note fileLocations[index] has type int[] that defines
+  // the location for the index-th range sample of this
+  // FileField
+  int[][] fileLocations;
+
+  public FileField(FieldImpl field, FileAccessor accessor,
+                   int[][] locations)
+    throws FieldException, VisADException
+  {
+    super((FunctionType)null, field.getDomainSet());
+
+    if (field instanceof FlatField) {
+      throw new FieldException("FileField: cannot adapt FlatField");
+    }
+    adaptedField = field;
+    fileAccessor = accessor;
+    fileLocations = locations;
+  }
+
+  // must implement all the methods of Data, Function and Field
+  //
+  // most are simple adapters, like this:
+  public Data getSample(int index)
+         throws VisADException, RemoteException {
+    return adaptedField.getSample(index);
+  }
+
+  // setSample changes the contents of this Field,
+  // in both the adaptedField and in the file
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    // set the index-th range sample in adaptedField
+    adaptedField.setSample(index, range);
+    // write range sample through to fileAccessor
+    fileAccessor.writeFile(fileLocations[index], range);
+  }
+
+  // setSamples also changes the file contents;
+  // it could be implemented as a series of calls to setSample
+
+  /**
+   * Clones this instance.  This implementation always throws a {@link
+   * CloneNotSupportedException}.
+   *
+   * @return                            A clone of this instance.
+   * @throws CloneNotSupportedException if cloning isn't supported.
+   */
+  public Object clone() throws CloneNotSupportedException {
+    throw new CloneNotSupportedException();
+  }
+
+}
diff --git a/visad/data/FileFlatField.java b/visad/data/FileFlatField.java
new file mode 100644
index 0000000..711e149
--- /dev/null
+++ b/visad/data/FileFlatField.java
@@ -0,0 +1,622 @@
+// FileFlatField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.IOException;
+
+import java.rmi.RemoteException;
+
+import visad.FlatField;
+import visad.*;
+
+public class FileFlatField extends FlatField {
+  // note FileFlatField extends FlatField but may not inherit
+  // any of its methods - it must re-implement all of them
+  // through the adapted FlatField
+
+  // number of FlatFields in cache
+
+       private static final int MAX_FILE_FLAT_FIELDS = 10;
+
+  // array of cached FlatFields
+
+       private static transient FlatField[] adaptedFlatFields =
+       new FlatField[MAX_FILE_FLAT_FIELDS];
+
+  // true if cache entry differs from file contents
+
+       private static transient boolean adaptedFlatFieldDirty[] =
+       new boolean[MAX_FILE_FLAT_FIELDS];
+
+  // the FileFlatField that owns this cache entry
+
+       private static transient FileFlatField[] adaptedFlatFieldOwner =
+       new FileFlatField[MAX_FILE_FLAT_FIELDS];
+
+  // adaptedFlatFieldSizes and adaptedFlatFieldTimes
+  // may be useful for cache allocation algorithms
+  // approximate sizes of FlatFields in cache
+
+       private static transient long[] adaptedFlatFieldSizes =
+       new long[MAX_FILE_FLAT_FIELDS];
+
+  // times of most recent accesses to FlatFields in cache
+
+       private static transient long[] adaptedFlatFieldTimes =
+       new long[MAX_FILE_FLAT_FIELDS];
+
+  // index of cache entry owned by this FileFlatField;
+  // but only if
+  // this == adaptedFlatFieldOwner[adaptedFlatFieldIndex]
+
+       private transient int adaptedFlatFieldIndex;
+
+
+  // this is the FileAccessor for reading and writing values from
+  // and to the adapted file
+
+       transient FileAccessor fileAccessor;
+
+  // this implements a strategy for cache replacement;
+  // this separates the cahce strategy algorithm from the logic
+  // of FileFlatField
+
+       private transient CacheStrategy cacheStrategy;
+
+  static
+  {
+    // initialize cache of FlatFields
+    if (adaptedFlatFieldOwner != null && adaptedFlatFields != null &&
+        adaptedFlatFieldSizes != null && adaptedFlatFieldTimes != null &&
+        adaptedFlatFieldDirty != null) {
+      for (int i=0; i<MAX_FILE_FLAT_FIELDS; i++) {
+        // mark Owners for all cache entries to indicate not
+        // belonging to any FileFlatField
+        adaptedFlatFieldOwner[i] = null;
+        adaptedFlatFields[i] = null;
+        adaptedFlatFieldSizes[i] = 0;
+        adaptedFlatFieldTimes[i] = System.currentTimeMillis();
+        adaptedFlatFieldDirty[i] = false;
+      }
+    }
+  }
+
+  // all methods lock on adaptedFlatFields cache
+  // to ensure thread safe access
+
+  public FileFlatField( FileAccessor accessor, CacheStrategy strategy )
+    throws VisADException
+  {
+    super( accessor.getFunctionType(),
+           getNullDomainSet(accessor.getFunctionType().getDomain()) );
+
+    fileAccessor = accessor;
+    cacheStrategy = strategy;
+
+    // '0' is in legal range of adaptedFlatFieldIndex,
+    // but not owned by this
+    adaptedFlatFieldIndex = 0;
+  }
+
+  private static Set getNullDomainSet(RealTupleType type)
+          throws VisADException {
+    int n = type.getDimension();
+    double[] values = new double[n];
+    for (int i=0; i<n; i++) values[i] = 0.0;
+    RealTuple tuple;
+    try {
+      tuple = new RealTuple(type, values);
+      return new SingletonSet(tuple);
+    }
+    catch (RemoteException e) {
+      throw new VisADError("FileFlatField.getNullDomainSet: " + e.toString());
+    }
+  }
+
+  private FlatField getAdaptedFlatField()
+  {
+    // if owner array is null,
+    //  assume this object got serialized & unserialized
+    if (adaptedFlatFieldOwner == null) {
+      return null;
+    }
+
+    synchronized (adaptedFlatFields) {
+      for ( int ii = 0; ii < MAX_FILE_FLAT_FIELDS; ii++ )
+      {
+        if (this == adaptedFlatFieldOwner[ii]) {
+
+          // mark time of most recent access
+
+          adaptedFlatFieldTimes[ii] = System.currentTimeMillis();
+
+          return adaptedFlatFields[ii];
+        }
+      }
+
+      // this FileFlatField does not own a cache entry, so invoke
+      // CahceStrategy.allocate to allocate one, possibly by taking
+      // one, possibly by taking one from another FileFlatField;
+      // this will be an area for lots of thought and experimentation;
+
+      adaptedFlatFieldIndex =
+        cacheStrategy.allocate(adaptedFlatFields, adaptedFlatFieldDirty,
+                               adaptedFlatFieldSizes, adaptedFlatFieldTimes);
+
+      // flush cache entry, if dirty
+
+      if (adaptedFlatFieldDirty[adaptedFlatFieldIndex])
+      {
+        try
+        {
+          adaptedFlatFieldOwner[adaptedFlatFieldIndex].flushCache();
+        }
+        catch ( VisADException e )
+        {
+          System.out.println( e.getMessage() );
+        }
+      }
+
+      // create a new entry in adaptedFlatFields at adaptedFlatFieldIndex
+      // and read data values from fileAccessor at fileLocation
+      try
+      {
+        adaptedFlatFields[adaptedFlatFieldIndex] = fileAccessor.getFlatField();
+      }
+      catch ( VisADException e1 )
+      {
+        System.out.println( e1.getMessage() );
+      }
+      catch ( RemoteException e2 )
+      {
+        System.out.println( e2.getMessage() );
+      }
+
+      // mark cache entry as belonging to this FileFlatField
+
+      adaptedFlatFieldOwner[adaptedFlatFieldIndex] = this;
+
+      // get size of adapted FlatField
+      // (by calling a method that currently does not exist)
+
+      /*adaptedFlatFields[adaptedFlatFieldIndex].getSize(); */
+
+      adaptedFlatFieldTimes[adaptedFlatFieldIndex] =
+        System.currentTimeMillis();
+
+      return adaptedFlatFields[adaptedFlatFieldIndex];
+    }
+  }
+
+  private void flushCache()
+      throws VisADException
+  {
+    if (adaptedFlatFields == null) {
+      throw new VisADException("Cannot access serialized FileFlatField");
+    }
+
+    // make sure this owns cache entry
+    if (this == adaptedFlatFieldOwner[adaptedFlatFieldIndex]) {
+      // unpackValues is currently private, would need default protection
+      // for access from FileFlatField
+   /* fileAccessor.writeFlatField(
+        adaptedFlatFields[adaptedFlatFieldIndex].unpackValues(),
+        templateFlatField, fileLocation); */
+    }
+  }
+
+  // must implement all the methods of Data, Function and Field
+  //
+  // most are simple adapters, like this:
+  //
+
+  public Data getSample(int index)
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.getSample(index);
+  }
+
+  public int getLength()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return 0;
+    }
+
+    return fld.getLength();
+  }
+
+  public Unit[] getDomainUnits()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.getDomainUnits();
+  }
+
+  public CoordinateSystem getDomainCoordinateSystem()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.getDomainCoordinateSystem();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem()
+         throws TypeException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.getRangeCoordinateSystem();
+  }
+
+  public CoordinateSystem[] getRangeCoordinateSystem( int component )
+         throws TypeException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.getRangeCoordinateSystem( component );
+  }
+
+  public Unit[][] getRangeUnits()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.getRangeUnits();
+  }
+
+  public Unit[] getDefaultRangeUnits()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.getDefaultRangeUnits();
+  }
+
+  public double[][] getValues()
+         throws VisADException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.getValues();
+  }
+
+  public double[][] getValues(boolean copy)
+         throws VisADException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.getValues(copy);
+  }
+
+  public double[] getValues(int index)
+         throws VisADException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.getValues(index);
+  }
+
+  public float[][] getFloats(boolean copy)
+         throws VisADException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.getFloats(copy);
+  }
+
+  public Set getDomainSet()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.getDomainSet();
+  }
+
+  // setSample is typical of methods that involve changing the
+  // contents of this Field
+  public void setSample(int index, Data range)
+         throws VisADException, RemoteException {
+    if (adaptedFlatFields == null) {
+      throw new VisADException("Cannot access serialized FileFlatField");
+    }
+
+    synchronized (adaptedFlatFields) {
+      FlatField fld = getAdaptedFlatField();
+      if (fld == null) {
+        throw new VisADException("Cannot get cached FlatField");
+      }
+
+      adaptedFlatFieldDirty[adaptedFlatFieldIndex] = true;
+      fld.setSample(index, range);
+    }
+  }
+
+  public void setSample( RealTuple domain, Data range )
+         throws VisADException, RemoteException
+  {
+    if (adaptedFlatFields == null) {
+      throw new VisADException("Cannot access serialized FileFlatField");
+    }
+
+    synchronized (adaptedFlatFields)
+    {
+      FlatField fld = getAdaptedFlatField();
+      if (fld == null) {
+        throw new VisADException("Cannot get cached FlatField");
+      }
+
+      adaptedFlatFieldDirty[adaptedFlatFieldIndex] = true;
+      fld.setSample( domain, range );
+    }
+  }
+
+  public void setSample( int index, Data range, boolean copy )
+         throws VisADException, RemoteException
+  {
+    if (adaptedFlatFields == null) {
+      throw new VisADException("Cannot access serialized FileFlatField");
+    }
+
+    synchronized (adaptedFlatFields)
+    {
+      FlatField fld = getAdaptedFlatField();
+      if (fld == null) {
+        throw new VisADException("Cannot get cached FlatField");
+      }
+
+      adaptedFlatFieldDirty[adaptedFlatFieldIndex] = true;
+      fld.setSample( index, range, copy );
+    }
+  }
+
+  public boolean isMissing()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return true;
+    }
+
+    return fld.isMissing();
+  }
+
+  public Data binary( Data data, int op, int sampling_mode, int error_mode )
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.binary( data, op, sampling_mode, error_mode);
+  }
+
+  public Data binary( Data data, int op, MathType new_type, int sampling_mode, int error_mode )
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.binary( data, op, new_type, sampling_mode, error_mode);
+  }
+
+  public Data unary( int op, int sampling_mode, int error_mode )
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.unary( op, sampling_mode, error_mode );
+  }
+
+  public Data unary(int op, MathType new_type, int sampling_mode, int error_mode)
+         throws VisADException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+    return fld.unary(op, new_type, sampling_mode, error_mode);
+  }
+
+  /** unpack an array of doubles from field sample values according to the
+      RangeSet-s; returns a copy */
+  public double[][] unpackValues() throws VisADException {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.unpackValues();
+  }
+
+  /** unpack an array of floats from field sample values according to the
+      RangeSet-s; returns a copy */
+  public float[][] unpackFloats() throws VisADException {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.unpackFloats();
+  }
+
+  public Field extract( int component )
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.extract( component );
+  }
+
+  public Field domainFactor( RealType factor )
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.domainFactor( factor );
+  }
+
+  public Field resample( Set set, int sampling_mode, int error_mode )
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.resample( set, sampling_mode, error_mode );
+  }
+
+  public DataShadow computeRanges(ShadowType type, DataShadow shadow)
+         throws VisADException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.computeRanges( type, shadow );
+  }
+
+  public Data adjustSamplingError( Data error, int error_mode )
+         throws VisADException, RemoteException
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      throw new VisADException("Cannot get cached FlatField");
+    }
+
+    return fld.adjustSamplingError( error, error_mode );
+  }
+
+  public boolean isFlatField()
+  {
+     return true;
+  }
+
+  /**
+   * Clones this instance.  This implementation violates the general <code>
+   * clone()</code> contract in that the returned object will compare unequal to
+   * this instance.  As such, this method should probably not be invoked.
+   *
+   * @return                            A clone of this instance.
+   */
+  public Object clone()
+  {
+    /*
+     * This implementation should probably just throw a 
+     * CloneNotSupportedException but can't because FlatField.clone() doesn't.
+     */
+      
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return null;
+    }
+
+    return fld.clone();
+  }
+
+  public String toString()
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return "Cannot get cached FlatField";
+    }
+
+    return fld.toString();
+  }
+
+  public String longString(String pre)
+  {
+    FlatField fld = getAdaptedFlatField();
+    if (fld == null) {
+      return pre + "Cannot get cached FlatField";
+    }
+
+    try {
+      return fld.longString(pre);
+    } catch (VisADException e) {
+      return pre + e.getMessage();
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream oos)
+    throws ClassNotFoundException, IOException
+  {
+    throw new java.io.NotSerializableException("FileFlatField is not serializable");
+  }
+
+  private void writeObject(java.io.ObjectOutputStream oos)
+    throws IOException
+  {
+    throw new java.io.NotSerializableException("FileFlatField is not serializable");
+  }
+}
diff --git a/visad/data/FlatFieldCache.java b/visad/data/FlatFieldCache.java
new file mode 100644
index 0000000..74e73a3
--- /dev/null
+++ b/visad/data/FlatFieldCache.java
@@ -0,0 +1,220 @@
+package visad.data;
+
+import java.util.logging.Logger;
+
+import visad.VisADException;
+
+/**
+ * Memory cache for <code>FlatField</code>s.  Maintains a fixed number of cache
+ * arrays in memory.  When a cache member is requested which is not currently 
+ * available in the cache it is loaded directly into one of the cache arrays.
+ * This prevents the allocation or garbage collection of data arrays when they
+ * are created. This allows caching to happen close to the data, rather than the
+ * rendering and is intended to allow for large animation loops with as little 
+ * an affect as possible on rendering. This can however lead to large latency at
+ * startup due to reading data sources and copying them to the cache arrays.
+ * <p>
+ * Most of this class was modeled after the <code>FileFlatField</code>. This cache,
+ * however, is not a static cache, it is a instance cache. The affect of this is 
+ * that if you create several large caches you can very quickly run out of memory.
+ */
+public class FlatFieldCache {
+
+  private static Logger log = Logger.getLogger(FlatFieldCache.class.getName());
+  
+  static interface CacheOwner {
+    public String getId();
+  };
+  
+  /**
+   * Simple cache strategy based on the time a <code>FlatField</code> was last accessed.
+   */
+  static class AccessTimeCacheStrategy implements FlatFieldCacheStrategy {
+
+    public int allocate(Entry[] entries) {
+
+      if (entries == null || entries.length == 0) return -1;
+      
+      int availableIdx = 0;
+      long oldest = entries[0] != null ? entries[0].lastAccessed : 0;
+
+      for (int ii = 0; ii < entries.length; ii++) {
+
+        if (entries[ii] == null) {
+          availableIdx = ii;
+          return availableIdx;
+        } else if (entries[ii].lastAccessed < oldest) {
+          oldest = entries[ii].lastAccessed;
+          availableIdx = ii;
+        }
+      }
+
+      return availableIdx;
+    }
+  }
+
+  /**
+   * Cache entry metadata.
+   */
+  static class Entry {
+
+    CacheOwner owner;
+    float[][] data;
+    boolean dirty = false;
+    long lastAccessed;
+    long size;
+
+    Entry(CacheOwner owner, float[][] data) {
+      this.data = data;
+      this.owner = owner;
+      lastAccessed = System.currentTimeMillis();
+    }
+    
+    public String toString() {
+      return "<Entry lastAccessed="+lastAccessed+" dirty="+dirty+" owner="+owner.getId()+">";
+    }
+  }
+  
+  private final Entry[] cache;
+
+  private final int cacheSize;
+  private FlatFieldCacheStrategy strategy;
+
+  /**
+   * Create a cache with a fixed size and the default strategy.
+   * @param cacheSize
+   */
+  public FlatFieldCache(int cacheSize) {
+    this(cacheSize, new AccessTimeCacheStrategy());
+  }
+  
+  /**
+   * Initialize cache.
+   * @param cacheSize Number of <code>FlatFields</code> to maintain in cache, >= 1.
+   * @param strategy How cache allocation is performed.
+   */
+  public FlatFieldCache(int cacheSize, FlatFieldCacheStrategy strategy) {
+    if (cacheSize < 1) throw new IllegalArgumentException("cache size must be >= 1");
+    this.cacheSize = cacheSize;
+    this.strategy = strategy;
+    cache = new Entry[cacheSize];
+  }
+  
+  protected void updateEntry(Entry entry, float[][] data, CacheOwner owner) {
+    for (int ii = 0; ii < data.length; ii++) {
+      System.arraycopy(data[ii], 0, entry.data[ii], 0, data[ii].length);
+    }
+    entry.owner = owner;
+    entry.dirty = false;
+    entry.lastAccessed = System.currentTimeMillis();
+  }
+  
+  /**
+   * Does the work of getting data from the cache.
+   * 
+   * @param owner
+   * @param fileAccessor
+   * @return cache data array
+   */
+  
+  protected synchronized float[][] getData(AreaImageCacheAdapter owner, FlatFieldCacheAccessor fileAccessor) {
+    
+    // if owner array is null,
+    // assume this object got serialized & unserialized
+    if (cache == null) {
+      log.fine("Cache was null, returning");
+      return null;
+    }
+    
+    for (int ii = 0; ii < cache.length; ii++) {
+      // if the owner is the same as the entries owner
+      // we have the right entry
+      if (cache[ii] != null && owner == cache[ii].owner) {
+        cache[ii].lastAccessed = System.currentTimeMillis();
+        return cache[ii].data;
+      }
+    }
+
+    // this FileFlatField does not own a cache entry, so invoke
+    // CahceStrategy.allocate to allocate one, possibly by taking
+    // one, possibly by taking one from another FileFlatField;
+    // this will be an area for lots of thought and experimentation;
+    
+    float[][] range = null;
+    
+    int idx = strategy.allocate(cache);
+    
+    // cannot allocate
+    if (idx == -1) {
+      range = fileAccessor.readFlatFieldFloats();
+      
+    } else {
+      
+      // entry should only be null once, whence we should create a new one
+      if (cache[idx] == null) {
+        cache[idx] = new Entry(owner, fileAccessor.readFlatFieldFloats());
+      
+      } else {
+        if (cache[idx].dirty) {
+          try {
+            flushCache(cache[idx], fileAccessor);
+          } catch (VisADException e) {
+            throw new FlatFieldCacheError("Could not flush to cache", e);
+          }
+        }
+        
+        // update the cached entry with the new values
+        try {
+          float[][] data = fileAccessor.readFlatFieldFloats();
+          if (cache[idx].data == null) {
+            cache[idx].data = data;
+          } else {
+            updateEntry(cache[idx], data, owner);
+          }
+        } catch (Exception e) {
+          throw new FlatFieldCacheError("Could not update cache entry", e);
+        }
+      }
+      range = cache[idx] != null ? cache[idx].data : null;
+    }
+
+    return range;
+  }
+
+  /**
+   * Not currently implemented.
+   * @param entry
+   * @param fileAccessor
+   * @throws UnsupportedOperationException All the time.
+   */
+  public void flushCache(Entry entry, FlatFieldCacheAccessor fileAccessor) throws VisADException {
+    throw new UnsupportedOperationException("FlatFieldCache.flushCache not implemented");
+  }
+
+  /**
+   * Don't do this.
+   * 
+   * @param owner The owner of the cache entry to mark as dirty.
+   * @param dirty 
+   */
+  public void setDirty(AreaImageCacheAdapter owner, boolean dirty) {
+    for (int ii = 0; ii < cache.length; ii++) {
+      if (cache[ii].owner == owner) {
+        cache[ii].dirty = dirty;
+      }
+    }
+  }
+  
+  public String toString() {
+    StringBuffer buf = new StringBuffer("<FlatFieldCache size=" + this.cacheSize + "\n");
+    for (Entry entry : cache) {
+      buf.append("\t" + (entry == null ? null : entry.toString()) + "\n");
+    }
+    buf.append(">");
+    return buf.toString();
+  }
+
+  public int getSize() {
+    return cacheSize;
+  }
+}
diff --git a/visad/data/FlatFieldCacheAccessor.java b/visad/data/FlatFieldCacheAccessor.java
new file mode 100644
index 0000000..61ccbd1
--- /dev/null
+++ b/visad/data/FlatFieldCacheAccessor.java
@@ -0,0 +1,7 @@
+
+package visad.data;
+
+public interface FlatFieldCacheAccessor {
+    public abstract double[][] readFlatField();
+    public abstract float[][] readFlatFieldFloats();
+}
diff --git a/visad/data/FlatFieldCacheError.java b/visad/data/FlatFieldCacheError.java
new file mode 100644
index 0000000..15003dc
--- /dev/null
+++ b/visad/data/FlatFieldCacheError.java
@@ -0,0 +1,10 @@
+package visad.data;
+
+import visad.VisADError;
+
+public class FlatFieldCacheError extends VisADError {
+  public FlatFieldCacheError(String message, Throwable cause) {
+    super(message);
+    this.initCause(cause);
+  }
+}
diff --git a/visad/data/FlatFieldCacheStrategy.java b/visad/data/FlatFieldCacheStrategy.java
new file mode 100644
index 0000000..21cb54e
--- /dev/null
+++ b/visad/data/FlatFieldCacheStrategy.java
@@ -0,0 +1,17 @@
+package visad.data;
+
+/**
+ * Strategy used to allocate space in the <code>FlatFieldCache</code> for
+ * a new <code>FlatFieldCache.Entry</code>.
+ */
+interface FlatFieldCacheStrategy {
+  
+  /**
+   * Allocate space in the cache containing <code>entries</code>.  It is up to the caller
+   * to ensure the entry at the provided index is saved to a persistent state if necessary. 
+   * @param entries The existing entries in the cache.
+   * @return An index into the cache (<code>FlatFieldCache.Entry</code> array) 
+   *  available for use. 
+   */
+  public int allocate(FlatFieldCache.Entry[] entries);
+}
diff --git a/visad/data/Form.java b/visad/data/Form.java
new file mode 100644
index 0000000..c5b51d9
--- /dev/null
+++ b/visad/data/Form.java
@@ -0,0 +1,63 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: Form.java,v 1.11 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+
+import visad.MathType;
+
+
+/**
+ * A leaf-node in the data form hierarchy for the storage of
+ * persistent data objects.
+ */
+public abstract class
+Form
+    extends FormNode
+{
+    /**
+     * The MathType of an existing data object.  Set by the
+     * getForms(Data data) method.
+     */
+    protected MathType	mathType;
+
+
+    /**
+     * Construct a data form of the given name.
+     */
+    public Form(String name)
+    {
+	super(name);
+    }
+
+
+    /**
+     * Get the MathType.
+     */
+    public MathType
+    getMathType()
+    {
+	return mathType;
+    }
+}
diff --git a/visad/data/FormBlockReader.java b/visad/data/FormBlockReader.java
new file mode 100644
index 0000000..b27280a
--- /dev/null
+++ b/visad/data/FormBlockReader.java
@@ -0,0 +1,58 @@
+//
+// FormBlockReader.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.IOException;
+import visad.*;
+import visad.data.BadFormException;
+
+/**
+ * FormBlockReader is the VisAD interface for reading in
+ * subsets of data, or "blocks," from a data file.
+ */
+public interface FormBlockReader {
+
+  /**
+   * Obtains the specified block from the given file.
+   * @param id The file from which to load data blocks.
+   * @param block_number The block number of the block to load.
+   * @throws VisADException If the block number is invalid.
+   */
+  DataImpl open(String id, int block_number)
+    throws BadFormException, IOException, VisADException;
+
+  /**
+   * Determines the number of blocks in the given file.
+   * @param id The file for which to get a block count.
+   */
+  int getBlockCount(String id)
+    throws BadFormException, IOException, VisADException;
+
+  /** Closes any open files. */
+  void close() throws BadFormException, IOException, VisADException;
+
+}
diff --git a/visad/data/FormFamily.java b/visad/data/FormFamily.java
new file mode 100644
index 0000000..94da4b0
--- /dev/null
+++ b/visad/data/FormFamily.java
@@ -0,0 +1,181 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: FormFamily.java,v 1.16 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Vector;
+import visad.Data;
+import visad.DataImpl;
+import visad.VisADException;
+
+
+/**
+ * A interior node in the data form hierarchy for the storage of
+ * persistent data objects.
+ */
+public class
+FormFamily
+    extends FormNode
+{
+    /**
+     * Construct an interior data-form node with the given name.
+     */
+    public FormFamily(String name)
+    {
+	super(name);
+    }
+
+
+    /**
+     * Save a VisAD data object.
+     */
+    public void save(String id, Data data, boolean replace)
+	throws BadFormException, RemoteException, IOException, VisADException
+    {
+	for (Enumeration e = forms.elements(); e.hasMoreElements(); )
+	{
+	    try
+	    {
+		((FormNode)e.nextElement()).save(id, data, replace);
+	    }
+	    catch (BadFormException xcpt)
+	    {
+		continue;
+	    }
+	    return;
+	}
+	throw new BadFormException("Data object not compatible with \"" +
+					getName() + "\" data family");
+    }
+
+
+    /**
+     * Add data to an existing data object.
+     */
+    public void add(String id, Data data, boolean replace)
+	throws BadFormException
+    {
+	for (Enumeration e = forms.elements(); e.hasMoreElements(); )
+	{
+	    try
+	    {
+		((FormNode)e.nextElement()).add(id, data, replace);
+	    }
+	    catch (BadFormException xcpt)
+	    {
+		continue;
+	    }
+	    return;
+	}
+
+	throw new BadFormException("Data object not compatible with \"" +
+					getName() + "\" data family");
+    }
+
+
+    /**
+     * Open an existing data object.
+     */
+    public DataImpl open(String id)
+	throws BadFormException, IOException, VisADException
+    {
+	for (Enumeration e = forms.elements(); e.hasMoreElements(); )
+	{
+	    try
+	    {
+		return ((FormNode)e.nextElement()).open(id);
+	    }
+	    catch (BadFormException xcpt)
+	    {
+	    }
+	}
+
+	throw new BadFormException("Data object \"" + id +
+		"\" not compatible with \"" + getName() + "\" data family");
+    }
+
+
+    /**
+     * Open an existing data object specified as a URL.
+     */
+    public DataImpl open(URL url)
+	throws BadFormException, IOException, VisADException
+    {
+	for (Enumeration e = forms.elements(); e.hasMoreElements(); )
+	{
+	    try
+	    {
+		return ((FormNode)e.nextElement()).open(url);
+	    }
+	    catch (BadFormException xcpt)
+	    {
+	    }
+	}
+
+	throw new BadFormException("Data object \"" + url +
+		"\" not compatible with \"" + getName() + "\" data family");
+    }
+
+
+    /**
+     * Return the data forms that are compatible with a data object.
+     */
+    public FormNode getForms(Data data)
+	throws RemoteException, VisADException, IOException
+    {
+	FormFamily	family = new FormFamily(getName());
+
+	for (Enumeration e = forms.elements(); e.hasMoreElements(); )
+	{
+	    FormNode	node = ((FormNode)e.nextElement()).getForms(data);
+
+	    if (node != null)
+		family.addFormNode(node);
+	}
+
+	return family.forms.size() == 0
+		    ? null
+		    : family;
+    }
+
+
+    /**
+     * Add a child node to this family of data forms.
+     */
+    public FormFamily addFormNode(FormNode node)
+    {
+	forms.addElement(node);
+	return this;
+    }
+
+
+    /**
+     * The children of this interior node.
+     */
+    protected Vector	forms = new Vector();
+}
diff --git a/visad/data/FormFileInformer.java b/visad/data/FormFileInformer.java
new file mode 100644
index 0000000..38e051f
--- /dev/null
+++ b/visad/data/FormFileInformer.java
@@ -0,0 +1,50 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+/**
+   FormFileInformer is the VisAD interface for checking system- and
+   file-specific data from a file Form.<P>
+*/
+public interface FormFileInformer
+{
+  /**
+   * Check to see if the file name might be right for this form.
+   * @param name   name of the file
+   * @return  true if the name is right for this type of form
+   */
+  boolean isThisType(String name);
+
+  /**
+   * Check to see if the block contains the magic number for this form.
+   * @param block   block of bytes from file
+   * @return  true if the magic number is right
+   */
+  boolean isThisType(byte[] block);
+
+  /**
+   * Get default suffixes for files/URLs handeled by this form.
+   * @return  array of suffixes 
+   */
+  String[] getDefaultSuffixes();
+}
diff --git a/visad/data/FormNode.java b/visad/data/FormNode.java
new file mode 100644
index 0000000..ad8cb07
--- /dev/null
+++ b/visad/data/FormNode.java
@@ -0,0 +1,102 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: FormNode.java,v 1.14 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+import java.io.IOException;
+import java.net.URL;
+import java.rmi.RemoteException;
+import visad.Data;
+import visad.DataImpl;
+import visad.VisADException;
+
+
+/**
+ * A node in the data form hierarchy for the storage of persistent data.
+ *
+ * This class implements the "composite" design pattern; the node will
+ * actually be either a "Form" or a "FormFamily".
+ */
+public abstract class
+FormNode
+{
+    /**
+     * Construct a data-form node with the given name.
+     */
+    public FormNode(String name)
+    {
+	this.name = name;
+    }
+
+
+    /**
+     * Return the name of this node.
+     */
+    public String getName()
+    {
+	return name;
+    }
+
+
+    /**
+     * Save a VisAD data object in this form.
+     */
+    public abstract void
+    save(String id, Data data, boolean replace)
+	throws BadFormException, IOException, RemoteException, VisADException;
+
+
+    /**
+     * Add data to an existing data object.
+     */
+    public abstract void add(String id, Data data, boolean replace)
+	throws BadFormException;
+
+
+    /**
+     * Open an existing data object.
+     */
+    public abstract DataImpl open(String id)
+	throws BadFormException, IOException, VisADException;
+
+
+    /**
+     * Open a data object specified as a URL.
+     */
+    public abstract DataImpl open(URL url)
+	throws BadFormException, VisADException, IOException;
+
+
+    /**
+     * Return the data forms that are compatible with a data object.
+     */
+    public abstract FormNode getForms(Data data)
+	throws VisADException, RemoteException, IOException;
+
+
+    /**
+     * The name of this node.
+     */
+    private final String	name;
+}
diff --git a/visad/data/FormProgressInformer.java b/visad/data/FormProgressInformer.java
new file mode 100644
index 0000000..2f323fe
--- /dev/null
+++ b/visad/data/FormProgressInformer.java
@@ -0,0 +1,42 @@
+//
+// FormProgressInformer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+/**
+ * FormProgressInformer is the VisAD interface for checking
+ * the current operation's progress from a file form.
+ */
+public interface FormProgressInformer {
+
+  /**
+   * Get the percentage complete of the form's current operation.
+   * @return  The percentage complete (0.0 - 100.0), or Double.NaN
+   *          if no operation is currently taking place.
+   */
+  double getPercentComplete();
+
+}
diff --git a/visad/data/FunctionFormFamily.java b/visad/data/FunctionFormFamily.java
new file mode 100644
index 0000000..e020ed9
--- /dev/null
+++ b/visad/data/FunctionFormFamily.java
@@ -0,0 +1,467 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import java.rmi.RemoteException;
+
+import java.util.Enumeration;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.VisADException;
+
+public class FunctionFormFamily
+	extends FormFamily
+{
+  public FunctionFormFamily(String name)
+  {
+    super(name);
+  }
+
+  /**
+    * Base class which tries to perform an operation on an object
+    * using the first valid Form.
+    */
+  abstract class FormFunction
+  {
+    /**
+      * Return 'true' if this object's name applies to the given node.
+      */
+    abstract boolean check(FormFileInformer node);
+
+    /**
+      * Return an InputStream for the object.
+      *
+      * Used to read in the first block of the object.
+      */
+    abstract InputStream getStream() throws IOException;
+
+    /**
+      * The operation to be performed on the object.
+      */
+    abstract boolean function(FormNode node);
+
+    /**
+      * Perform an operation on an object
+      * using the first valid Form.
+      *
+      * If a Form successfully performs the operation, return 'true'.
+      */
+    public boolean run()
+	throws IOException
+    {
+      // see if we can guess the file type based on the name
+      for (Enumeration en = forms.elements(); en.hasMoreElements(); ) {
+	FormNode node = (FormNode)en.nextElement();
+
+	if (node instanceof FormFileInformer) {
+          // WLH 19 Feb 2000 - switch order of try and check
+          // needed for HDF5
+	  try {
+	    if (check((FormFileInformer) node)) {
+	      if (function(node)) {
+		return true;
+	      }
+	    }
+	  } catch (Exception e) {
+	  } catch (Error e) {
+            // WLH 19 Feb 2000 - needed for HDF5
+	  }
+	}
+      }
+
+      // get the first block of data from the file
+      byte[] block = new byte[2048];
+      InputStream is = getStream();
+      if (is != null) {
+        is.read(block);
+        is.close();
+
+        // see if we can guess the file type based on first block of data
+        for (Enumeration en = forms.elements(); en.hasMoreElements(); ) {
+          FormNode node = (FormNode)en.nextElement();
+
+          if (node instanceof FormFileInformer) {
+            // WLH 19 Feb 2000 - switch order of try and check
+            // needed for HDF5
+            try {
+              if (((FormFileInformer )node).isThisType(block)) {
+                if (function(node)) {
+                  return true;
+                }
+              }
+            } catch (Exception e) {
+            } catch (Error e) {
+              // WLH 19 Feb 2000 - needed for HDF5
+            }
+          }
+        }
+      }
+
+      // use the brute-force method of checking all the forms
+      for (Enumeration en = forms.elements(); en.hasMoreElements(); ) {
+	FormNode node = (FormNode)en.nextElement();
+
+	try {
+	  if (function(node)) {
+	    return true;
+	  }
+	} catch (Exception e) {
+	} catch (UnsatisfiedLinkError ule) {
+	}
+      }
+
+      return false;
+    }
+  }
+
+  /**
+    * Perform an operation on a local file object
+    * using the first valid Form.
+    */
+  abstract class FileFunction
+	extends FormFunction
+  {
+    String name;
+
+    public FileFunction()
+    {
+      name = null;
+    }
+
+    boolean check(FormFileInformer node)
+    {
+      return node.isThisType(name);
+    }
+
+    InputStream getStream()
+	throws IOException
+    {
+      return new FileInputStream(name);
+    }
+  }
+
+  /**
+    * Save a Data object to a local file
+    * using the first valid Form.
+    */
+  class SaveForm
+	extends FileFunction
+  {
+    private Data data;
+    private boolean replace;
+
+    public SaveForm(String name, Data data, boolean replace)
+    {
+      this.name = name;
+      this.data = data;
+      this.replace = replace;
+    }
+
+    boolean function(FormNode node)
+    {
+      try {
+	node.save(name, data, replace);
+      } catch (Exception e) {
+	return false;
+      }
+
+      return true;
+    }
+
+    InputStream getStream()
+	throws IOException
+    {
+      FileInputStream stream;
+      try {
+        stream = new FileInputStream(name);
+      } catch (FileNotFoundException fnfe) {
+        stream = null;
+      }
+
+      return stream;
+    }
+  }
+
+  /**
+    * Add a Data object to an existing local file
+    * using the first valid Form.
+    */
+  class AddForm
+	extends FileFunction
+  {
+    private Data data;
+    private boolean replace;
+
+    public AddForm(String name, Data data, boolean replace)
+    {
+      this.name = name;
+      this.data = data;
+      this.replace = replace;
+    }
+
+    boolean function(FormNode node)
+    {
+      try {
+	node.add(name, data, replace);
+      } catch (Exception e) {
+	return false;
+      }
+
+      return true;
+    }
+  }
+
+  /**
+    * Read a Data object from a local file
+    * using the first valid Form.
+    */
+  class OpenStringForm
+	extends FileFunction
+  {
+    private DataImpl data;
+
+    public OpenStringForm(String name)
+    {
+      this.name = name;
+      data = null;
+    }
+
+    boolean function(FormNode node)
+    {
+      try {
+	data = node.open(name);
+      } catch (OutOfMemoryError t) { // WLH 5 Feb 99
+	throw t;
+      } catch (Throwable t) {
+	return false;
+      }
+
+      return true;
+    }
+
+    public DataImpl getData()
+    {
+      return data;
+    }
+  }
+
+  /**
+    * Perform an operation on a remote file object
+    * using the first valid Form.
+    */
+  abstract class URLFunction
+	extends FormFunction
+  {
+    URL url;
+
+    public URLFunction()
+    {
+      url = null;
+    }
+
+    boolean check(FormFileInformer node)
+    {
+      // try both file part of URL and full URL
+      return (node.isThisType(url.getFile()) ||
+              node.isThisType(url.toString()));
+    }
+
+    InputStream getStream()
+	throws IOException
+    {
+      return url.openStream();
+    }
+  }
+
+  /**
+    * Read a Data object from a remote file
+    * using the first valid Form.
+    */
+  class OpenURLForm
+	extends URLFunction
+  {
+    /* CTR: 13 Oct 1998
+    private URL url;
+    */
+    private DataImpl data;
+
+    public OpenURLForm(URL url)
+    {
+      this.url = url;
+      data = null;
+    }
+
+    boolean function(FormNode node)
+    {
+      try {
+	data = node.open(url);
+      } catch (Throwable t) {
+	return false;
+      }
+
+      return true;
+    }
+
+    public DataImpl getData()
+    {
+      return data;
+    }
+  }
+
+  /**
+    * Save a Data object using the first appropriate Form.
+    */
+  public synchronized void save(String id, Data data, boolean replace)
+	throws BadFormException, RemoteException, IOException, VisADException
+  {
+    SaveForm s = new SaveForm(id, data, replace);
+    if (!s.run()) {
+      throw new BadFormException("Data object not compatible with \"" +
+				 getName() + "\" data family");
+    }
+  }
+
+  /**
+    * Add data to an existing data object using the first appropriate Form.
+    */
+  public synchronized void add(String id, Data data, boolean replace)
+	throws BadFormException
+  {
+    AddForm a = new AddForm(id, data, replace);
+    try {
+      if (a.run()) {
+	return;
+      }
+    } catch (IOException e) {
+    }
+
+    throw new BadFormException("Data object not compatible with \"" +
+			       getName() + "\" data family");
+  }
+
+  /**
+    * Open a local data object using the first appropriate Form.
+    */
+  public synchronized DataImpl open(String id)
+	throws BadFormException, VisADException
+  {
+    // Garbage in, garbage out
+    if (id == null) {
+      return null;
+    }
+
+    // try to build a URL from the string
+    URL url;
+    try {
+      url = new URL(id);
+    } catch (MalformedURLException mue) {
+      url = null;
+    }
+
+    DataImpl data = null;
+
+    // if we got a URL, try to extract a Data object from it
+    if (url != null) {
+      OpenURLForm u = new OpenURLForm(url);
+
+      try {
+        if (!u.run()) {
+          data = null;
+        } else {
+          data = u.getData();
+        }
+      } catch (Exception e) {
+        data = null;
+      }
+    }
+
+    // if we didn't get a Data object, look for a filename
+    String file = null;
+    if (data == null) {
+      if (url == null) {
+        file = id;
+      } else if (url.getProtocol() == "file") {
+        file = url.getFile();
+
+        // if file looks like it starts with a Windows drive spec...
+        if (file.length() > 2 && file.charAt(2) == ':' &&
+            file.charAt(0) == '/')
+        {
+          file = file.substring(1);
+        }
+      }
+    }
+
+    // if we found a filename, try to open it
+    if (file != null) {
+      OpenStringForm o = new OpenStringForm(file);
+
+      try {
+        if (!o.run()) {
+          data = null;
+        } else {
+          data = o.getData();
+        }
+      } catch (IOException ioe) {
+        data = null;
+      }
+    }
+
+    // puke if we didn't find a data object
+    if (data == null) {
+      if (file != null && !new java.io.File(file).exists()) {
+        throw new BadFormException("No such data object \"" + id + "\"");
+      }
+      throw new BadFormException("Data object \"" + id +
+                                 "\" not compatible with \"" + getName() +
+                                 "\" data family");
+    }
+
+    return data;
+  }
+
+  /**
+    * Open a remote data object using the first appropriate Form.
+    */
+  public synchronized DataImpl open(URL url)
+	throws BadFormException, IOException, VisADException
+  {
+    OpenURLForm o = new OpenURLForm(url);
+    if (!o.run()) {
+      throw new BadFormException("Data object \"" + url +
+				 "\" not compatible with \"" + getName() +
+				 "\" data family");
+    }
+
+    return o.getData();
+  }
+}
diff --git a/visad/data/LinkedDataSource.java b/visad/data/LinkedDataSource.java
new file mode 100644
index 0000000..62770f0
--- /dev/null
+++ b/visad/data/LinkedDataSource.java
@@ -0,0 +1,113 @@
+//
+// LinkedDataSource.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * A class for linking a data source (e.g., a URL) with a DataReference.
+ * Whenever the data changes at the source, the new data is automatically
+ * loaded and the DataReference is set to point to it. VisAD applications
+ * can then use a CellImpl with the DataReference object in order to detect
+ * changes to the data at its source.
+ */
+public abstract class LinkedDataSource {
+
+  /**
+   * Debugging flag.
+   */
+  protected static final boolean DEBUG = false;
+
+  /**
+   * The name of this LinkedDataSource.
+   */
+  protected String name;
+
+  /**
+   * The DataReference for this LinkedDataSource.
+   */
+  private DataReferenceImpl ref;
+
+  /**
+   * Whether the connection to the data source is still alive.
+   */
+  private boolean alive;
+
+  /**
+   * Construct a LinkedDataSource with the given name.
+   */
+  public LinkedDataSource(String name) {
+    this.name = name;
+    try {
+      ref = new DataReferenceImpl(name);
+    }
+    catch (VisADException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    alive = false;
+  }
+
+  /**
+   * Load initial data from the given data source and remain linked
+   * to the data source, monitoring it for changes to the data.
+   */
+  public abstract void open(String id)
+    throws IOException, VisADException, RemoteException;
+
+  /**
+   * Update the data to which this LinkedDataSource is linked.
+   */
+  public void dataChanged(Data data) throws VisADException, RemoteException {
+    if (data == null) alive = false;
+    else ref.setData(data);
+  }
+
+  /**
+   * Return the name of this LinkedDataSource.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Return the DataReference for this LinkedDataSource.
+   */
+  public DataReferenceImpl getReference() {
+    return ref;
+  }
+
+  /**
+   * Return whether the connection to the data source is still alive.
+   */
+  public boolean isAlive() {
+    return alive;
+  }
+
+}
+
diff --git a/visad/data/MetadataReader.java b/visad/data/MetadataReader.java
new file mode 100644
index 0000000..bed2c96
--- /dev/null
+++ b/visad/data/MetadataReader.java
@@ -0,0 +1,56 @@
+//
+// MetadataReader.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import visad.VisADException;
+
+/**
+ * MetadataReader is the VisAD interface for reading in
+ * a file's associated metadata (other than pixel data).
+ */
+public interface MetadataReader {
+
+  /**
+   * Obtains the specified metadata field's value for the given file.
+   * @param field the name associated with the metadata field
+   * @return the value, or null should the field not exist
+   */
+  Object getMetadataValue(String id, String field)
+    throws BadFormException, IOException, VisADException;
+
+  /**
+   * Obtains a hashtable containing all metadata field/value pairs from
+   * the given file.
+   * @param id the filename
+   * @return the hashtable containing all metadata associated with the file
+   */
+  Hashtable getMetadata(String id)
+    throws BadFormException, IOException, VisADException;
+
+}
diff --git a/visad/data/Repository.java b/visad/data/Repository.java
new file mode 100644
index 0000000..f11829a
--- /dev/null
+++ b/visad/data/Repository.java
@@ -0,0 +1,168 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: Repository.java,v 1.14 2009-03-02 23:35:46 curtis Exp $
+*/
+
+package visad.data;
+
+
+import java.io.IOException;
+import java.net.URL;
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+import visad.Data;
+import visad.DataImpl;
+import visad.VisADException;
+
+
+/**
+ * A repository of persistent data objects.
+ * This class implements the "abstract factory" design pattern.
+ * The concrete implementation of this class could either be
+ * "DirectoryRepository" (for accessing files residing on local disk)
+ * or "RemoteRepository" (for accessing remote files via a server) or
+ * something else.  The concrete class of the "Form" objects will be
+ * determined by this class's concrete class (and, hence, so will the
+ * concrete class of any constructed "FileAccessor").
+ */
+public abstract class Repository
+{
+    /**
+     * The name of this repository.
+     */
+    private final String	name;
+
+    /**
+     * The location of this repository.
+     */
+    private final String	location;
+
+    /**
+     * The data forms supported by this repository.
+     */
+    protected FormNode		forms;
+
+
+    /**
+     * Construct a data repository.
+     */
+    public Repository(String name, String location)
+    {
+	this.name = name;
+	this.location = location;
+    }
+
+    /**
+     * Return the name of this repository.
+     */
+    public String getName()
+    {
+	return name;
+    }
+
+    /**
+     * Return the location of this repository.
+     */
+    public String getLocation()
+    {
+	return location;
+    }
+
+    /**
+     * Return the forms of data that are supported by this repository.
+     */
+    public FormNode getForms()
+    {
+	return forms;
+    }
+
+    /**
+     * Return the forms of data that are both supported by this repository
+     * and compatible with a data object.
+     */
+    public FormNode getForms(Data data)
+	throws VisADException, IOException, RemoteException
+    {
+	return forms.getForms(data);
+    }
+
+    /**
+     * Return an enumeration of the data objects in this repository.
+     */
+    public abstract Enumeration getEnumeration()
+	throws	BadRepositoryException, SecurityException;
+
+    /**
+     * Save a data object in the first compatible data form.
+     */
+    public void save(String id, Data data, boolean replace)
+	throws VisADException, IOException, RemoteException
+    {
+	forms.save(fullName(id), data, replace);
+    }
+
+    /**
+     * Save a data object in a particular form.
+     */
+    public void save(String id, Data data, FormNode form, boolean replace)
+	throws VisADException, RemoteException, IOException
+    {
+	form.save(fullName(id), data, replace);
+    }
+
+    /**
+     * Add a data object to an existing data object in the repository.
+     */
+    public void add(String id, Data data, boolean replace)
+	throws VisADException
+    {
+	forms.add(fullName(id), data, replace);
+    }
+
+    /**
+     * Open an existing data object in the repository.
+     */
+    public DataImpl open(String id)
+	throws VisADException, IOException
+    {
+	return forms.open(fullName(id));
+    }
+
+    /**
+     * Open a data object specified as a URL.  Strictly speaking, this
+     * shouldn't be here because a URL can lie outside the domain of the
+     * repository.  A repository, however, is characterized by the
+     * data forms that it handles as well as its "location".
+     * Consequently, we have this method.
+     */
+    public DataImpl open(URL url)
+	throws VisADException, IOException
+    {
+	return forms.open(url);
+    }
+
+
+    /**
+     * Return the fully-qualified name of a persistent data object.
+     */
+    protected abstract String fullName(String id);
+}
diff --git a/visad/data/SocketDataServer.java b/visad/data/SocketDataServer.java
new file mode 100644
index 0000000..21f6f86
--- /dev/null
+++ b/visad/data/SocketDataServer.java
@@ -0,0 +1,192 @@
+//
+// SocketDataServer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.*;
+import java.net.*;
+import java.rmi.RemoteException;
+import java.util.Vector;
+import visad.*;
+
+/**
+ * A class for exporting data to a socket stream. Whenever the given
+ * DataReference's data changes, it is sent across the socket to any clients
+ * that are listening.
+ */
+public class SocketDataServer {
+
+  /**
+   * The main socket for this SocketDataServer.
+   */
+  protected ServerSocket socket;
+
+  /**
+   * List of client sockets listening to this SocketDataServer.
+   */
+  protected Vector sockets = new Vector();
+
+  /**
+   * List of output streams for client sockets.
+   */
+  protected Vector outs = new Vector();
+
+  /**
+   * The socket's port.
+   */
+  protected int port;
+
+  /**
+   * DataReference whose data is linked to the socket stream.
+   */
+  protected DataReferenceImpl ref;
+
+  /**
+   * Whether the server is still active.
+   */
+  protected boolean alive = true;
+
+  /**
+   * Code for monitoring incoming clients.
+   */
+  private Runnable connect = new Runnable() {
+    public void run() {
+      while (alive) {
+        try {
+          // wait for a new socket to connect
+          Socket s = socket.accept();
+          if (!alive) break;
+          synchronized (sockets) {
+            if (s != null) {
+              // add client to the list
+              sockets.add(s);
+
+              // add client's input and output streams to the list
+              ObjectOutputStream out =
+                new ObjectOutputStream(s.getOutputStream());
+              outs.add(out);
+
+              // send the current data to the client
+              Data data = SocketDataServer.this.ref.getData();
+              out.writeObject(data);
+            }
+          }
+        }
+        catch (IOException exc) { }
+      }
+    }
+  };
+
+  /**
+   * Cell for monitoring data changes.
+   */
+  private CellImpl commCell = new CellImpl() {
+    public synchronized void doAction()
+      throws VisADException, RemoteException
+    {
+      // send new data to each client using its socket
+      synchronized (sockets) {
+        Data data = SocketDataServer.this.ref.getData();
+        int i = 0;
+        while (i < sockets.size()) {
+          Socket s = (Socket) sockets.elementAt(i);
+          ObjectOutputStream out = (ObjectOutputStream) outs.elementAt(i);
+          try {
+            out.writeObject(data);
+            i++;
+          }
+          catch (IOException exc) {
+            // something wrong with this socket; kill it
+            killSocket(i);
+          }
+        }
+      }
+    }
+  };
+
+  /**
+   * Construct a SocketDataServer with the given port and data reference.
+   */
+  public SocketDataServer(int port, DataReferenceImpl ref)
+    throws VisADException, IOException
+  {
+    this.port = port;
+    this.ref = ref;
+
+    // create a server socket at the given port
+    socket = new ServerSocket(port);
+
+    // monitor incoming client socket connections
+    Thread connectThread = new Thread(connect);
+    connectThread.start();
+
+    // monitor data changes
+    commCell.addReference(ref);
+  }
+
+  /**
+   * Shut down the given socket, and removes it from the socket vector.
+   */
+  private void killSocket(int i) {
+    ObjectOutputStream out = (ObjectOutputStream) outs.elementAt(i);
+    Socket s = (Socket) sockets.elementAt(i);
+
+    // shut down socket output stream
+    try {
+      out.close();
+    }
+    catch (IOException exc) { }
+
+    // shut down socket itself
+    try {
+      s.close();
+    }
+    catch (IOException exc) { }
+
+    // remove socket from socket vectors
+    sockets.remove(i);
+    outs.remove(i);
+  }
+
+  /** destroys this server and kills all associated threads */
+  public void killServer() {
+    // set flag to cause server's threads to stop running
+    alive = false;
+
+    // shut down all client sockets
+    synchronized (sockets) {
+      while (sockets.size() > 0) killSocket(0);
+    }
+
+    // shut down server socket
+    try {
+      socket.close();
+    }
+    catch (IOException exc) { }
+  }
+
+}
+
diff --git a/visad/data/SocketDataSource.java b/visad/data/SocketDataSource.java
new file mode 100644
index 0000000..bdd4c00
--- /dev/null
+++ b/visad/data/SocketDataSource.java
@@ -0,0 +1,181 @@
+//
+// SocketDataSource.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data;
+
+import java.io.*;
+import java.net.Socket;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * A class for linking a socket stream to a DataReference. Whenever the source
+ * data changes, the source presumably sends the change through the linked
+ * socket, and the DataReference is set to point at the new data.
+ */
+public class SocketDataSource extends LinkedDataSource {
+
+  /**
+   * The socket connection for this SocketDataSource.
+   */
+  protected Socket socket;
+
+  /**
+   * The output stream for the socket connection.
+   */
+  protected ObjectOutputStream out;
+  
+  /**
+   * The input stream for the socket connection.
+   */
+  protected ObjectInputStream in;
+
+  /**
+   * Code for monitoring socket for incoming source data changes.
+   */
+  protected Runnable comm = new Runnable() {
+    public void run() {
+      Object o;
+
+      // read objects until stream closes
+      while (true) {
+        o = null;
+        try {
+          o = in.readObject();
+        }
+        catch (ClassNotFoundException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (IOException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        if (o == null) break;
+
+        // process object
+        if (o instanceof DataImpl) {
+          // object is updated data
+          try {
+            dataChanged((Data) o);
+          }
+          catch (VisADException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+          catch (RemoteException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+        }
+      }
+
+      // socket has died; shut everything down
+      try {
+        dataChanged(null);
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      try {
+        in.close();
+      }
+      catch (IOException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      try {
+        out.close();
+      }
+      catch (IOException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      try {
+        socket.close();
+      }
+      catch (IOException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+    }
+  };
+
+  /**
+   * Construct a SocketDataSource with the given name.
+   */
+  public SocketDataSource(String name) {
+    super(name);
+  }
+
+  /**
+   * Link to the given socket, updating the local data whenever an
+   * update event is sent through that socket.
+   */
+  public synchronized void open(String id)
+    throws IOException, VisADException, RemoteException
+  {
+    // parse socket URL
+    int index = id.indexOf(":");
+    if (index < 0) throw new VisADException("malformed socket URL: " + id);
+    String host = id.substring(0, index);
+    String p = id.substring(index + 1);
+    int port = -1;
+    try {
+      port = Integer.parseInt(p);
+    }
+    catch (NumberFormatException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    if (port < 0) throw new VisADException("invalid socket port: " + p);
+
+    // open the socket
+    socket = new Socket(host, port);
+    out = new ObjectOutputStream(socket.getOutputStream());
+    in = new ObjectInputStream(socket.getInputStream());
+
+    // set up socket input thread
+    Thread t = new Thread(comm);
+    t.start();
+  }
+
+  /**
+   * Return the socket connection for this SocketDataSource.
+   */
+  public Socket getSocket() {
+    return socket;
+  }
+
+  /**
+   * Writes the specified object out to the socket.
+   */
+  public void writeObject(Object o) {
+    try {
+      out.writeObject(o);
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+  }
+
+}
+
diff --git a/visad/data/amanda/AmandaFile.java b/visad/data/amanda/AmandaFile.java
new file mode 100644
index 0000000..5e8a134
--- /dev/null
+++ b/visad/data/amanda/AmandaFile.java
@@ -0,0 +1,707 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import visad.Data;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.RealType;
+import visad.RealTupleType;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+
+class DoubleCache
+{
+  private double value;
+
+  DoubleCache() { this(Double.NaN); }
+  DoubleCache(double v) { value = v; }
+  double getValue() { return value; }
+  void setValue(double v) { value = v; }
+}
+
+class FloatCache
+{
+  private float value;
+
+  FloatCache() { this(Float.NaN); }
+  FloatCache(float v) { value = v; }
+  float getValue() { return value; }
+  void setValue(float v) { value = v; }
+}
+
+class IntCache
+{
+  private int value;
+
+  IntCache() { this(-1); }
+  IntCache(int v) { value = v; }
+  int getValue() { return value; }
+  void setValue(int v) { value = v; }
+}
+
+public class AmandaFile
+{
+  public static final RealType moduleIndexType =
+    RealType.getRealType("Module_Index");
+
+  public static RealTupleType xyzType;
+
+  static {
+    try {
+      xyzType =
+        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.ZAxis);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      xyzType = null;
+    }
+  }
+
+  private double xmin = Double.MAX_VALUE;
+  private double xmax = Double.MIN_VALUE;
+  private double ymin = Double.MAX_VALUE;
+  private double ymax = Double.MIN_VALUE;
+  private double zmin = Double.MAX_VALUE;
+  private double zmax = Double.MIN_VALUE;
+
+  private ModuleList modules = new ModuleList();
+  private ArrayList events = new ArrayList();
+
+  private HashMap lastCache = new HashMap();
+
+  public AmandaFile(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    FileReader rdr = new FileReader(id);
+    try {
+      loadFile(new BufferedReader(rdr));
+    } finally {
+      try { rdr.close(); } catch (IOException ioe) { }
+    }
+  }
+
+  public AmandaFile(URL url)
+    throws BadFormException, IOException, VisADException
+  {
+    InputStream is = url.openStream();
+    try {
+      loadFile(new BufferedReader(new InputStreamReader(is)));
+    } finally {
+      try { is.close(); } catch (IOException ioe) { }
+    }
+  }
+
+  private void loadFile(BufferedReader br)
+    throws BadFormException, VisADException
+  {
+    // read V record
+    String firstLine;
+    try {
+      firstLine = nextLine(br);
+    } catch (IOException ioe) {
+      throw new BadFormException("Unreadable file");
+    }
+
+    if (firstLine == null || firstLine.length() <= 1 ||
+        firstLine.charAt(0) != 'v' ||
+        !Character.isSpaceChar(firstLine.charAt(1)))
+    {
+      throw new BadFormException("Bad first line \"" + firstLine + "\"");
+    }
+
+    Event currentEvent = null;
+    boolean inSlowEvent = false;
+
+    while (true) {
+      String line;
+      try {
+        line = nextLine(br);
+      } catch (IOException ioe) {
+        throw new BadFormException("Unreadable file");
+      }
+
+      // end loop if we've reached the end of the file
+      if (line == null) {
+        break;
+      }
+
+      // ignore blank lines
+      if (line.length() == 0) {
+        continue;
+      }
+
+      StringTokenizer tok = new StringTokenizer(line);
+
+      String keyword = tok.nextToken();
+
+      if (keyword.equals("array")) {
+        if (modules.isInitialized()) {
+          System.err.println("Warning: Multiple ARRAY lines found");
+        }
+
+        int nmodules = readArrayLine(line, tok);
+
+        continue;
+      }
+
+      if (keyword.equals("om")) {
+        Module module = readOMLine(line, tok);
+        if (module != null) {
+          modules.add(module);
+
+          final float x = module.getX();
+          if (x == x) {
+            if (x < xmin) xmin = x;
+            if (x > xmax) xmax = x;
+          }
+          final float y = module.getY();
+          if (y == y) {
+            if (y < ymin) ymin = y;
+            if (y > ymax) ymax = y;
+          }
+          final float z = module.getZ();
+          if (z == z) {
+            if (z < zmin) zmin = z;
+            if (z > zmax) zmax = x;
+          }
+        }
+
+        continue;
+      }
+
+      if (keyword.equals("es")) {
+        // ignore ES events for now
+        if (inSlowEvent) {
+          System.err.println("Warning: Missing EE for slow event");
+        }
+
+        inSlowEvent = true;
+
+        continue;
+      }
+
+      if (keyword.equals("em")) {
+        if (currentEvent != null) {
+          System.err.println("Warning: Missing EE for " + currentEvent);
+        }
+
+        currentEvent = startEvent(line, tok);
+
+        continue;
+      }
+
+      // read TR and HT records
+      if (keyword.equals("tr")) {
+        MCTrack track = readTrack(line, tok);
+        if (track != null) {
+          if (currentEvent == null) {
+            System.err.println("Found TRACK " + track +
+                               " outside event");
+          } else {
+            currentEvent.add(track);
+          }
+        }
+
+        continue;
+      }
+
+      if (keyword.equals("fit")) {
+        FitTrack fit = readFit(line, tok);
+        if (fit != null) {
+          if (currentEvent == null) {
+            System.err.println("Found FIT " + fit +
+                               " outside event");
+          } else {
+            currentEvent.add(fit);
+          }
+        }
+
+        continue;
+      }
+
+      if (keyword.equals("ht")) {
+        Hit hit = readHit(line, tok);
+        if (hit != null) {
+          if (currentEvent == null) {
+            System.err.println("Found HIT " + hit +
+                               " outside event");
+          } else {
+            currentEvent.add(hit);
+          }
+        }
+
+        continue;
+      }
+
+      if (keyword.equals("ee")) {
+        if (currentEvent == null) {
+          if (inSlowEvent) {
+            inSlowEvent = false;
+          } else {
+            System.err.println("Found EE outside event");
+          }
+        } else {
+          events.add(currentEvent);
+          currentEvent = null;
+        }
+
+        continue;
+      }
+    }
+
+    // remove cached values since they're no longer needed
+    lastCache.clear();
+  }
+
+  private final void dump(java.io.PrintStream out)
+  {
+    final int nEvents = events.size();
+    for (int i = 0; i < nEvents; i++) {
+      ((Event )events.get(i)).dump(out);
+    }
+
+    modules.dump(out);
+  }
+
+  public final Event getEvent(int index)
+  {
+    return (Event )events.get(index);
+  }
+
+  public final int getNumberOfEvents() { return events.size(); }
+
+  public final double getXMax() { return xmax; }
+  public final double getXMin() { return xmin; }
+
+  public final double getYMax() { return ymax; }
+  public final double getYMin() { return ymin; }
+
+  public final double getZMax() { return zmax; }
+  public final double getZMin() { return zmin; }
+
+  public final FieldImpl makeEventData()
+  {
+    final int num = events.size();
+    Integer1DSet set;
+    try {
+      set = new Integer1DSet(Event.indexType, (num == 0 ? 1 : num));
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      return null;
+    }
+
+    FunctionType funcType;
+    FieldImpl fld;
+    try {
+      funcType = new FunctionType(Event.indexType, Hits.timeSequenceType);
+      fld = new FieldImpl(funcType, set);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      return null;
+    }
+
+    if (num > 0) {
+      Data[] samples = new Data[num];
+      for (int e = 0; e < num; e++) {
+        samples[e] = ((Event )events.get(e)).makeHitSequence();
+      }
+      try {
+        fld.setSamples(samples, false);
+      } catch (RemoteException re) {
+        re.printStackTrace();
+      } catch (VisADException ve) {
+        ve.printStackTrace();
+      }
+    }
+
+    return fld;
+  }
+
+  public final FlatField makeModuleData()
+    throws RemoteException, VisADException
+  {
+    // Field of modules
+    final int num = modules.size();
+    Integer1DSet set;
+    try {
+      set = new Integer1DSet(moduleIndexType, num);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      return null;
+    }
+
+    FunctionType funcType;
+    FlatField fld;
+    try {
+      funcType = new FunctionType(moduleIndexType, xyzType);
+      fld = new FlatField(funcType, set);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      return null;
+    }
+
+    if (num > 0) {
+      float[][] samples = new float[3][num];
+      for (int i = 0; i < num; i++) {
+        Module mod = modules.get(i);
+        samples[0][i] = mod.getX();
+        samples[1][i] = mod.getY();
+        samples[2][i] = mod.getZ();
+      }
+      try {
+        fld.setSamples(samples);
+      } catch (RemoteException re) {
+        re.printStackTrace();
+      }
+    }
+
+    return fld;
+  }
+
+  private String nextLine(BufferedReader rdr)
+    throws IOException
+  {
+    String line = rdr.readLine();
+    if (line != null) {
+      line = line.trim().toLowerCase();
+    }
+
+    return line;
+  }
+
+  private int parseChannel(String tokenName, String token)
+    throws NumberFormatException
+  {
+    final int dotIdx = token.indexOf('.');
+    if (dotIdx >= 0) {
+      token = "-" + token.substring(dotIdx + 1);
+    }
+
+    return parseInt(tokenName, token);
+  }
+
+  private double parseDouble(String tokenName, String token)
+    throws NumberFormatException
+  {
+    double value;
+    if (token == null) {
+      value = Double.NaN;
+    } else if (token.equals("inf")) {
+      value = Double.POSITIVE_INFINITY;
+    } else if (token.equals("-inf")) {
+      value = Double.NEGATIVE_INFINITY;
+    } else if (token.equals("?")) {
+      value = Double.NaN;
+    } else if (token.equals("nan")) {
+      value = Double.NaN;
+    } else if (token.equals("*")) {
+      DoubleCache cval = (DoubleCache )lastCache.get(tokenName);
+      if (cval == null) {
+        value = Double.NaN;
+      } else {
+        value = cval.getValue();
+      }
+    } else {
+      value = Double.parseDouble(token);
+    }
+
+    // save value in case next reference uses '*' to access it
+    DoubleCache cache = (DoubleCache )lastCache.get(tokenName);
+    if (cache == null) {
+      lastCache.put(tokenName, new DoubleCache(value));
+    } else {
+      cache.setValue(value);
+    }
+
+    return value;
+  }
+
+  private float parseFloat(String tokenName, String token)
+    throws NumberFormatException
+  {
+    float value;
+    if (token == null) {
+      value = Float.NaN;
+    } else if (token.equals("inf")) {
+      value = Float.POSITIVE_INFINITY;
+    } else if (token.equals("-inf")) {
+      value = Float.NEGATIVE_INFINITY;
+    } else if (token.equals("?")) {
+      value = Float.NaN;
+    } else if (token.equals("nan")) {
+      value = Float.NaN;
+    } else if (token.equals("*")) {
+      FloatCache cval = (FloatCache )lastCache.get(tokenName);
+      if (cval == null) {
+        value = Float.NaN;
+      } else {
+        value = cval.getValue();
+      }
+    } else {
+      value = Float.parseFloat(token);
+    }
+
+    // save value in case next reference uses '*' to access it
+    FloatCache cache = (FloatCache )lastCache.get(tokenName);
+    if (cache == null) {
+      lastCache.put(tokenName, new FloatCache(value));
+    } else {
+      cache.setValue(value);
+    }
+
+    return value;
+  }
+
+  private int parseInt(String tokenName, String token)
+    throws NumberFormatException
+  {
+    int value;
+    if (token == null) {
+      value = -1;
+    } else if (token.equals("inf")) {
+      value = Integer.MAX_VALUE;
+    } else if (token.equals("-inf")) {
+      value = Integer.MIN_VALUE;
+    } else if (token.equals("?")) {
+      value = -1;
+    } else if (token.equals("nan")) {
+      value = -1;
+    } else if (token.equals("*")) {
+      IntCache cval = (IntCache )lastCache.get(tokenName);
+      if (cval == null) {
+        value = -1;
+      } else {
+        value = cval.getValue();
+      }
+    } else {
+      value = Integer.parseInt(token);
+    }
+
+    // save value in case next reference uses '*' to access it
+    IntCache cache = (IntCache )lastCache.get(tokenName);
+    if (cache == null) {
+      lastCache.put(tokenName, new IntCache(value));
+    } else {
+      cache.setValue(value);
+    }
+
+    return value;
+  }
+
+  private int readArrayLine(String line, StringTokenizer tok)
+    throws BadFormException
+  {
+    String detector = tok.nextToken();
+    int nstrings, nmodules;
+    try {
+      float longitude = parseFloat("raLon", tok.nextToken());
+      float latitude = parseFloat("raLat", tok.nextToken());
+      float depth = parseFloat("raDepth", tok.nextToken());
+      nstrings = parseInt("raNStr", tok.nextToken());
+      nmodules = parseInt("raNMod", tok.nextToken());
+    } catch(NumberFormatException e) {
+      throw new BadFormException("Bad ARRAY line \"" + line + "\": " +
+                                 e.getMessage());
+    }
+
+    if (nstrings < 1 || nmodules < 1) {
+      throw new BadFormException("Bad ARRAY line \"" + line + "\": " +
+                                 (nstrings < 1 ? "nstrings < 1" :
+                                  "nmodule < 1"));
+    }
+
+    return nmodules;
+  }
+
+  private final FitTrack readFit(String line, StringTokenizer tok)
+    throws VisADException
+  {
+    // FIT id type xstart ystart zstart zenith azimuth time length energy
+    float xstart, ystart, zstart, zenith, azimuth, length, energy, time;
+
+    // skip ID field
+    tok.nextToken();
+    tok.nextToken();
+
+    try {
+      xstart = parseFloat("fitXStart", tok.nextToken());
+      ystart = parseFloat("fitYStart", tok.nextToken());
+      zstart = parseFloat("fitZStart", tok.nextToken());
+      zenith = parseFloat("fitZenith", tok.nextToken()); // 0.0f toward -z
+      azimuth = parseFloat("fitAzimuth", tok.nextToken()); // 0.0f toward +x
+      time = parseFloat("fitTime", tok.nextToken());
+      length = parseFloat("fitLength", tok.nextToken());
+      energy = parseFloat("fitEnergy", tok.nextToken());
+    } catch(NumberFormatException e) {
+      throw new BadFormException("Bad FIT line \"" + line + "\": " +
+                                 e.getMessage());
+    }
+
+    return new FitTrack(xstart, ystart, zstart, zenith, azimuth, length,
+                        energy, time);
+  }
+
+  private final Hit readHit(String line, StringTokenizer tok)
+    throws VisADException
+  {
+    String chanStr = tok.nextToken();
+    int number = parseChannel("htNum", chanStr);
+    if (number < 0) {
+      System.err.println("Warning: Ignoring HIT for secondary channel \"" +
+                         chanStr + "\"");
+      return null;
+    }
+
+    // find this module
+    Module mod = modules.find(number);
+    if (mod == null) {
+      System.err.println("Warning: Module not found for HIT line \"" +
+                         line + "\"; hit ignored");
+      return null;
+    }
+
+    float amplitude, leadEdgeTime, timeOverThreshold;
+    try {
+      amplitude = parseFloat("htAmp", tok.nextToken());
+
+      // skip pulse id & parent track
+      tok.nextToken();
+      tok.nextToken();
+
+      leadEdgeTime = parseFloat("htLet", tok.nextToken());
+      timeOverThreshold = parseFloat("htTot", tok.nextToken());
+
+      // ignore number of TDC edges
+    } catch(NumberFormatException e) {
+      throw new BadFormException("Bad HIT line \"" + line + "\": " +
+                                 e.getMessage());
+    }
+
+    return new Hit(mod, amplitude, leadEdgeTime, timeOverThreshold);
+  }
+
+  private final Module readOMLine(String line, StringTokenizer tok)
+    throws BadFormException
+  {
+    String numStr = tok.nextToken();
+
+    int number;
+    try {
+      number = parseInt("omNum", numStr);
+    } catch(NumberFormatException e) {
+      throw new BadFormException("unparseable module number \"" + numStr +
+                                 "\" in \"" + line + "\"");
+    }
+
+    if (number < 0) {
+      throw new BadFormException("bad module number \"" + numStr +
+                                 "\" in \"" + line + "\"");
+    }
+
+    int stringOrder, string;
+    float x, y, z;
+    try {
+      stringOrder = parseInt("modOrd", tok.nextToken());
+      string = parseInt("modStr", tok.nextToken());
+      x = parseFloat("modX", tok.nextToken());
+      y = parseFloat("modY", tok.nextToken());
+      z = parseFloat("modZ", tok.nextToken());
+    } catch(NumberFormatException e) {
+      throw new BadFormException("Bad OM line \"" + line + "\": " +
+                                 e.getMessage());
+    }
+
+    return new Module(number, x, y, z, string, stringOrder);
+  }
+
+  private final MCTrack readTrack(String line, StringTokenizer tok)
+    throws VisADException
+  {
+    // TR nr parent type xstart ystart zstart zenith azimuth length energy time
+    // skip first three fields
+    for (int i = 0; i < 3; i++) {
+      tok.nextToken();
+    }
+
+    float xstart, ystart, zstart, zenith, azimuth, length, energy, time;
+    try {
+      xstart = parseFloat("trXStart", tok.nextToken());
+      ystart = parseFloat("trYStart", tok.nextToken());
+      zstart = parseFloat("trZStart", tok.nextToken());
+      zenith = parseFloat("trZenith", tok.nextToken()); // 0.0f toward -z
+      azimuth = parseFloat("trAzimuth", tok.nextToken()); // 0.0f toward +x
+      length = parseFloat("trLength", tok.nextToken());
+      energy = parseFloat("trEnergy", tok.nextToken());
+      time = parseFloat("trTime", tok.nextToken());
+    } catch(NumberFormatException e) {
+      throw new BadFormException("bad TRACK line \"" + line + "\": " +
+                                 e.getMessage());
+    }
+
+    return new MCTrack(xstart, ystart, zstart, zenith, azimuth, length,
+                       energy, time);
+  }
+
+  private final Event startEvent(String line, StringTokenizer tok)
+    throws BadFormException
+  {
+    // assemble EM event
+    int evtNum, runNum, year, day;
+    double time, timeShift;
+    try {
+      evtNum = parseInt("emNum", tok.nextToken());
+      runNum = parseInt("emRun", tok.nextToken());
+      year = parseInt("emYear", tok.nextToken());
+      day = parseInt("emDay", tok.nextToken());
+      time = parseDouble("emTime", tok.nextToken());
+      if (!tok.hasMoreTokens()) {
+        timeShift = Double.NaN;
+      } else {
+        // time shift in nsec of all times in event
+        timeShift = parseDouble("emTimeShift", tok.nextToken()) * 0.000000001;
+      }
+    } catch(NumberFormatException e) {
+      throw new BadFormException("Bad EM line \"" + line + "\": " +
+                                 e.getMessage());
+    }
+
+    return new Event(evtNum, runNum, year, day, time, timeShift);
+  }
+}
diff --git a/visad/data/amanda/BaseTrack.java b/visad/data/amanda/BaseTrack.java
new file mode 100644
index 0000000..3b26de0
--- /dev/null
+++ b/visad/data/amanda/BaseTrack.java
@@ -0,0 +1,424 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Gridded1DSet;
+import visad.Gridded3DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.SetType;
+import visad.VisADException;
+
+import visad.util.Util;
+
+public abstract class BaseTrack
+  implements Comparable
+{
+  public static final RealType indexType =
+    RealType.getRealType("Track_Index");
+
+  private static final RealType energyType =
+    RealType.getRealType("Track_Energy");
+
+  public static FunctionType functionType;
+  public static FunctionType timeSequenceType;
+  public static FieldImpl missing;
+
+  private static FunctionType indexTupleType;
+
+  static {
+    try {
+      functionType =
+        new FunctionType(AmandaFile.xyzType,
+                         new RealTupleType(RealType.Time, energyType));
+
+      timeSequenceType = new FunctionType(RealType.Time,
+                                          new SetType(AmandaFile.xyzType));
+
+      Gridded1DSet set = new Gridded1DSet(RealType.Time, new float[1][1], 1);
+      missing = new FieldImpl(timeSequenceType, set);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      functionType = null;
+      indexTupleType = null;
+      timeSequenceType = null;
+      missing = null;
+    }
+  }
+
+  private static final float LENGTH_SCALE = 1000.0f;
+
+  private static final int X_SAMPLE = 0;
+  private static final int Y_SAMPLE = 1;
+  private static final int Z_SAMPLE = 2;
+
+  private float xstart;
+  private float ystart;
+  private float zstart;
+  private float zenith;
+  private float azimuth;
+  private float length;
+  private float energy;
+  private float time;
+  private float maxLength;
+
+  private float[] timeSteps;
+  private float[][] samples;
+
+  BaseTrack(float xstart, float ystart, float zstart, float zenith,
+            float azimuth, float length, float energy, float time)
+  {
+    this.xstart = xstart;
+    this.ystart = ystart;
+    this.zstart = zstart;
+    this.zenith = zenith;
+    this.azimuth = azimuth;
+    this.length = length;
+    this.energy = energy;
+    this.time = time;
+
+    timeSteps = null;
+    samples = null;
+  }
+
+  private static final int compareFloat(float f0, float f1)
+  {
+    if (Util.isApproximatelyEqual(f0, f1)) {
+      return 0;
+    }
+
+    return (f0 < f1 ? -1 : 1);
+  }
+
+  public int compareTo(Object obj)
+  {
+    if (obj instanceof BaseTrack) {
+      return compareTo((BaseTrack )obj);
+    }
+
+    return getClass().getName().compareTo(obj.getClass().getName());
+  }
+
+  public int compareTo(BaseTrack t)
+  {
+    int cmp = compareFloat(time, t.time);
+    if (cmp == 0) {
+      cmp = compareFloat(xstart, t.xstart);
+      if (cmp == 0) {
+        cmp = compareFloat(ystart, t.ystart);
+        if (cmp == 0) {
+          cmp = compareFloat(zstart, t.zstart);
+          if (cmp == 0) {
+            cmp = compareFloat(zenith, t.zenith);
+            if (cmp == 0) {
+              cmp = compareFloat(azimuth, t.azimuth);
+              if (cmp == 0) {
+                // negate this since we want to prefer higher-energy tracks
+                cmp = -compareFloat(energy, t.energy);
+                if (cmp == 0) {
+                  // negate this since we want to prefer longer tracks
+                  cmp = -compareFloat(length, t.length);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return cmp;
+  }
+
+  final void computeSamples(float[] timeSteps)
+  {
+    final double degrees2radians = Data.DEGREES_TO_RADIANS;
+
+    final double sinZenith = Math.sin(zenith * degrees2radians);
+    final double cosZenith = Math.cos(zenith * degrees2radians);
+
+    final double sinAzimuth = Math.sin(azimuth * degrees2radians);
+    final double cosAzimuth = Math.cos(azimuth * degrees2radians);
+
+    // speed of light (.3 m/nanosecond)
+    final double SPEED_OF_LIGHT = 0.3 /* * 1000.0 */;
+
+    final float timeOrigin, xOrigin, yOrigin, zOrigin;
+    if (timeSteps.length == 0) {
+      timeOrigin = time;
+      xOrigin = xstart;
+      yOrigin = ystart;
+      zOrigin = zstart;
+      samples = null;
+    } else {
+      timeOrigin = timeSteps[0];
+
+      final double length = (timeOrigin - time) * SPEED_OF_LIGHT;
+
+      xOrigin = xstart + (float )(length * sinZenith * cosAzimuth);
+      yOrigin = ystart + (float )(length * sinZenith * sinAzimuth);
+      zOrigin = zstart + (float )(length * cosZenith);
+
+      samples = new float[timeSteps.length + 1][3];
+
+      samples[0][X_SAMPLE] = xOrigin;
+      samples[0][Y_SAMPLE] = yOrigin;
+      samples[0][Z_SAMPLE] = zOrigin;
+    }
+
+    for (int i = 0; i < timeSteps.length; i++) {
+
+      final double length = (timeSteps[i] - timeOrigin) * SPEED_OF_LIGHT;
+
+      float xDelta = (float )(length * sinZenith * cosAzimuth);
+      float yDelta = (float )(length * sinZenith * sinAzimuth);
+      float zDelta = (float )(length * cosZenith);
+
+      samples[i][X_SAMPLE] = xOrigin + xDelta;
+      samples[i][Y_SAMPLE] = yOrigin + yDelta;
+      samples[i][Z_SAMPLE] = zOrigin + zDelta;
+    }
+
+    this.timeSteps = timeSteps;
+  }
+
+  public boolean equals(Object obj) { return compareTo(obj) == 0; }
+
+  public final float getEnergy() { return energy; }
+  public final float getLength() { return length; }
+
+  private final float getMaxSample(int sample, float dfltValue)
+  {
+    if (samples == null) {
+      System.err.println("BaseTrack.getMaxSample() called before " +
+                         "BaseTrack.computeSamples()");
+      Thread.dumpStack();
+      return dfltValue;
+    }
+
+    float max = samples[0][sample];
+    for (int i = 1; i < samples.length; i++) {
+      if (samples[i][sample] > max) max = samples[i][sample];
+    }
+
+    return max;
+  }
+
+  private final float getMinSample(int sample, float dfltValue)
+  {
+    if (samples == null) {
+      System.err.println("BaseTrack.getMinSample() called before " +
+                         "BaseTrack.computeSamples()");
+      Thread.dumpStack();
+      return dfltValue;
+    }
+
+    float min = samples[0][sample];
+    for (int i = 1; i < samples.length; i++) {
+      if (samples[i][sample] < min) min = samples[i][sample];
+    }
+
+    return min;
+  }
+
+  public final float getXMax() { return getMaxSample(X_SAMPLE, xstart); }
+  public final float getXMin() { return getMinSample(X_SAMPLE, xstart); }
+  public final float getYMax() { return getMaxSample(Y_SAMPLE, xstart); }
+  public final float getYMin() { return getMinSample(Y_SAMPLE, ystart); }
+  public final float getZMax() { return getMaxSample(Z_SAMPLE, xstart); }
+  public final float getZMin() { return getMinSample(Z_SAMPLE, zstart); }
+
+  abstract FlatField makeData()
+    throws VisADException;
+
+  final FlatField makeData(float maxLength)
+    throws VisADException
+  {
+    float fldLength = length;
+    if (fldLength > maxLength) {
+      fldLength = maxLength;
+    } else if (fldLength != fldLength) {
+      fldLength = -1.0f;
+    }
+
+    float fldEnergy = energy;
+    if (fldEnergy != fldEnergy) {
+      fldEnergy = 1.0f;
+    }
+
+    float zs = (float) Math.sin(zenith * Data.DEGREES_TO_RADIANS);
+    float zc = (float) Math.cos(zenith * Data.DEGREES_TO_RADIANS);
+    float as = (float) Math.sin(azimuth * Data.DEGREES_TO_RADIANS);
+    float ac = (float) Math.cos(azimuth * Data.DEGREES_TO_RADIANS);
+    float zinc = fldLength * zc;
+    float xinc = fldLength * zs * ac;
+    float yinc = fldLength * zs * as;
+
+    float[][] locs = {{xstart - LENGTH_SCALE * xinc,
+                       xstart + LENGTH_SCALE * xinc},
+                      {ystart - LENGTH_SCALE * yinc,
+                       ystart + LENGTH_SCALE * yinc},
+                      {zstart - LENGTH_SCALE * zinc,
+                       zstart + LENGTH_SCALE * zinc}};
+
+    // construct Field for fit
+    Gridded3DSet set = new Gridded3DSet(AmandaFile.xyzType, locs, 2);
+    FlatField field = new FlatField(functionType, set);
+    float[][] values = {{time, time}, {fldEnergy, fldEnergy}};
+
+    try {
+      field.setSamples(values, false);
+    } catch (RemoteException re) {
+      re.printStackTrace();
+      return null;
+    }
+
+    return field;
+  }
+
+  final FieldImpl makeTimeSequence(float[] timeSteps)
+  {
+    if (timeSteps == null || timeSteps.length == 0) {
+      return null;
+    }
+
+    final double degrees2radians = Data.DEGREES_TO_RADIANS;
+
+    // zenith value is the direction from which
+    // the muon came, not the direction in which it's going,
+    // so it's 180 degrees off
+    final double z2 = ((double )zenith + 180.0) % 360.0;
+
+    final double a2 = (double )azimuth;
+
+    final double sinZenith = Math.sin(z2 * degrees2radians);
+    final double cosZenith = Math.cos(z2 * degrees2radians);
+
+    final double sinAzimuth = Math.sin(a2 * degrees2radians);
+    final double cosAzimuth = Math.cos(a2 * degrees2radians);
+
+    // speed of light (.3 m/nanosecond)
+    final double SPEED_OF_LIGHT = 0.3;
+
+    Gridded3DSet[] sets = new Gridded3DSet[timeSteps.length];
+    Gridded3DSet missingSet = null;
+
+    final float timeOrigin = timeSteps[0];
+    final float timeFinal = timeSteps[timeSteps.length - 1];
+
+    final double baseTime = (timeOrigin - time);
+    final double baseLength = baseTime * SPEED_OF_LIGHT;
+
+    final float xOrigin, yOrigin, zOrigin;
+    xOrigin = xstart + (float )(baseLength * sinZenith * cosAzimuth);
+    yOrigin = ystart + (float )(baseLength * sinZenith * sinAzimuth);
+    zOrigin = zstart + (float )(baseLength * cosZenith);
+
+    final double preTime = baseTime - ((timeFinal - timeOrigin) / 2.0);
+    final double preLength = preTime * SPEED_OF_LIGHT;
+
+    final float xPreOrigin, yPreOrigin, zPreOrigin;
+    xPreOrigin = xstart + (float )(preLength * sinZenith * cosAzimuth);
+    yPreOrigin = ystart + (float )(preLength * sinZenith * sinAzimuth);
+    zPreOrigin = zstart + (float )(preLength * cosZenith);
+
+    for (int i = 0; i < timeSteps.length; i++) {
+
+      final double length = (timeSteps[i] - timeOrigin) * SPEED_OF_LIGHT;
+
+      final float xEndpoint, yEndpoint, zEndpoint;
+      xEndpoint = xOrigin + (float )(length * sinZenith * cosAzimuth);
+      yEndpoint = yOrigin + (float )(length * sinZenith * sinAzimuth);
+      zEndpoint = zOrigin + (float )(length * cosZenith);
+
+      float[][] locs = {
+        { xPreOrigin, xEndpoint },
+        { yPreOrigin, yEndpoint },
+        { zPreOrigin, zEndpoint },
+      };
+
+      Gridded3DSet subSet;
+      try {
+        subSet = new Gridded3DSet(AmandaFile.xyzType, locs, 2);
+      } catch (VisADException ve) {
+        ve.printStackTrace();
+
+        if (missingSet == null) {
+          try {
+            missingSet =
+              new Gridded3DSet(AmandaFile.xyzType, new float[3][1], 1);
+          } catch (VisADException ve2) {
+            ve2.printStackTrace();
+          }
+        }
+        subSet = missingSet;
+      }
+
+      sets[i] = subSet;
+    }
+
+    Gridded1DSet set;
+    try {
+      set = new Gridded1DSet(RealType.Time,
+                             new float[][] { timeSteps },
+                             timeSteps.length);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      set = null;
+    }
+
+    FieldImpl fld;
+    try {
+      fld = new FieldImpl(timeSequenceType, set);
+      fld.setSamples(sets, false);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      fld = missing;
+    } catch (RemoteException re) {
+      re.printStackTrace();
+      fld = missing;
+    }
+
+    return fld;
+  }
+
+  public String toString()
+  {
+    String fullName = getClass().getName();
+    int pt = fullName.lastIndexOf('.');
+    final int ds = fullName.lastIndexOf('$');
+    if (ds > pt) {
+      pt = ds;
+    }
+    String className = fullName.substring(pt == -1 ? 0 : pt + 1);
+
+    return className + "[" + xstart + "," + ystart + "," + zstart +
+      " LA#" + zenith + " LO#" + azimuth + " LE#" + length + " NRG#" +
+      energy + " TIM#" + time + "]";
+  }
+}
diff --git a/visad/data/amanda/Event.java b/visad/data/amanda/Event.java
new file mode 100644
index 0000000..26cf712
--- /dev/null
+++ b/visad/data/amanda/Event.java
@@ -0,0 +1,198 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.FieldImpl;
+import visad.MathType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.TupleType;
+import visad.VisADException;
+
+public class Event
+{
+  public static final RealType indexType =
+    RealType.getRealType("Event_Index");
+
+  public static TupleType tupleType;
+  public static Data missing = Hits.missing;
+
+  static {
+    try {
+      tupleType = new TupleType(new MathType[] {
+        Tracks.functionType, Hits.functionType
+      });
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      tupleType = null;
+    }
+  }
+
+  private int number, run, year, day;
+  private double time, timeShift;
+  private Hits hits;
+  private Tracks tracks;
+
+  Event(int number, int run, int year, int day, double time, double timeShift)
+  {
+    this.number = number;
+    this.run = run;
+    this.year = year;
+    this.day = day;
+    this.time = time;
+    this.timeShift = timeShift;
+
+    this.hits = new Hits();
+    this.tracks = new Tracks();
+  }
+
+  final void add(Hit hit) { hits.add(hit); }
+  final void add(FitTrack track) { tracks.add(track); }
+  final void add(MCTrack track) { tracks.add(track); }
+
+  final void dump(java.io.PrintStream out)
+  {
+    out.println(this);
+    hits.dump(out);
+    tracks.dump(out);
+  }
+
+  public final int getDay() { return day; }
+
+  public final Hit getHit(int idx) { return hits.get(idx); }
+
+  public final int getNumber() { return number; }
+
+  public final int getNumberOfHits() { return hits.size(); }
+  public final int getNumberOfTracks() { return tracks.size(); }
+
+  public final int getRun() { return run; }
+  public final double getTime() { return time; }
+  public final double getTimeShift() { return timeShift; }
+
+  public final BaseTrack getTrack(int idx) { return tracks.get(idx); }
+
+  public final int getYear() { return year; }
+
+  public final float[][] makeHistogram(ScalarMap xMap, ScalarMap yMap,
+                                       ScalarMap cMap, ScalarMap dpyColorMap)
+  {
+    float[] timeSteps = hits.getTimeSteps();
+
+    // create bins
+    int[] bin = new int[timeSteps.length - 1];
+    for (int i = 0; i < bin.length; i++) {
+      bin[i] = 0;
+    }
+
+    // fill bins with count of hits in that bin
+    final int hitsLen = hits.size();
+    for (int i = 0; i < hitsLen; i++) {
+      final float time = hits.get(i).getLeadingEdgeTime();
+
+      // look for the proper bin, and increment its count
+      boolean found = false;
+      for (int j = 0; !found && j < bin.length; j++) {
+        if (time < timeSteps[j]) {
+          bin[j]++;
+          found = true;
+        }
+      }
+
+      // if it wasn't found, toss it in the last bin
+      if (!found) {
+        bin[bin.length - 1]++;
+      }
+    }
+
+    // calculate maximum bin value
+    float binMax = bin[0];
+    for (int i = 1; i < bin.length; i++) {
+      final int val = bin[i];
+      if (val > binMax) {
+        binMax = val;
+      }
+    }
+
+    // build list of point data
+    float[] x = new float[bin.length * 4];
+    float[] y = new float[bin.length * 4];
+    int idx = 0;
+    for (int i = 0; i < bin.length; i++) {
+      x[idx] = timeSteps[i];
+      y[idx] = 0;
+      idx++;
+
+      x[idx] = timeSteps[i];
+      y[idx] = bin[i];
+      idx++;
+
+      x[idx] = timeSteps[i + 1];
+      y[idx] = bin[i];
+      idx++;
+
+      x[idx] = timeSteps[i + 1];
+      y[idx] = 0;
+      idx++;
+    }
+
+    // set the scalarmap ranges
+    try {
+      xMap.setRange(0.0, (double )binMax);
+      yMap.setRange((double )timeSteps[0],
+                    (double )timeSteps[timeSteps.length - 1]);
+      cMap.setRange((double )timeSteps[0],
+                    (double )timeSteps[timeSteps.length - 1]);
+      dpyColorMap.setRange((double )timeSteps[0],
+                           (double )timeSteps[timeSteps.length - 1]);
+    } catch (RemoteException re) {
+      System.err.println("Couldn't set histogram ScalarMap ranges");
+      re.printStackTrace();
+    } catch (VisADException ve) {
+      System.err.println("Couldn't set histogram ScalarMap ranges");
+      ve.printStackTrace();
+    }
+
+    // return the new data
+    return new float[][] { x, y };
+  }
+
+  public final FieldImpl makeHitSequence()
+  {
+    return hits.makeTimeSequence();
+  }
+
+  public final FieldImpl makeTrackSequence(int idx)
+  {
+    return tracks.get(idx).makeTimeSequence(hits.getTimeSteps());
+  }
+
+  public String toString()
+  {
+    return "Event#" + number + "[Y" + year + "D" + day +
+      " H" + hits + " T" + tracks + "]";
+  }
+}
diff --git a/visad/data/amanda/EventList.java b/visad/data/amanda/EventList.java
new file mode 100644
index 0000000..dce1812
--- /dev/null
+++ b/visad/data/amanda/EventList.java
@@ -0,0 +1,68 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.util.ArrayList;
+
+public abstract class EventList
+{
+  private ArrayList list = new ArrayList();
+
+  public EventList() { }
+
+  final void add(Object o) { list.add(o); }
+
+  final void addUnique(Object o)
+  {
+    final int len = list.size();
+    for (int i = 0; i < len; i++) {
+      if (o.equals(list.get(i))) {
+        // don't add duplicates
+        return;
+      }
+    }
+
+    list.add(o);
+  }
+
+  public final void dump(java.io.PrintStream out)
+  {
+    final int num = list.size();
+    for (int i = 0; i < num; i++) {
+      out.println("  " + list.get(i));
+    }
+  }
+
+  final Object internalGet(int i)
+  {
+    if (i < 0 || i >= list.size()) {
+      return null;
+    }
+
+    return list.get(i);
+  }
+
+  final int size() { return list.size(); }
+
+  public final String toString() { return Integer.toString(list.size()); }
+}
diff --git a/visad/data/amanda/EventWidget.java b/visad/data/amanda/EventWidget.java
new file mode 100644
index 0000000..050abc0
--- /dev/null
+++ b/visad/data/amanda/EventWidget.java
@@ -0,0 +1,208 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.awt.BorderLayout;
+
+import java.rmi.RemoteException;
+
+import java.text.DateFormat;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import visad.AnimationControl;
+import visad.CellImpl;
+import visad.DataReferenceImpl;
+import visad.FieldImpl;
+import visad.Real;
+import visad.ScalarMap;
+import visad.VisADException;
+
+import visad.util.VisADSlider;
+
+public class EventWidget
+  extends JPanel
+{
+  private AmandaFile fileData;
+  private DataReferenceImpl eventRef;
+  private AnimationControl animCtl;
+
+  private GregorianCalendar cal;
+  private DateFormat fmt;
+
+  private VisADSlider slider;
+  private int sliderLength;
+
+  private JLabel dateLabel;
+
+  private TrackWidget trackWidget;
+  private HistogramWidget histoWidget;
+
+  private Event thisEvent;
+
+  public EventWidget(AmandaFile fileData, DataReferenceImpl eventRef,
+                     DataReferenceImpl trackRef, AnimationControl animCtl,
+                     HistogramWidget histoWidget)
+    throws RemoteException, VisADException
+  {
+    this(fileData, eventRef, trackRef, animCtl, null, histoWidget);
+  }
+
+  public EventWidget(AmandaFile fileData, DataReferenceImpl eventRef,
+                     DataReferenceImpl trackRef, AnimationControl animCtl,
+                     ScalarMap trackMap, HistogramWidget histoWidget)
+    throws RemoteException, VisADException
+  {
+    super();
+
+    this.fileData = fileData;
+    this.eventRef = eventRef;
+    this.animCtl = animCtl;
+    this.histoWidget = histoWidget;
+
+    cal = new GregorianCalendar();
+
+    fmt =  DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL);
+    fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
+
+    thisEvent = null;
+
+    // initialize before buildSlider() in case it triggers a reference to them
+    if (trackMap == null) {
+      trackWidget = null;
+    } else {
+      trackWidget = new TrackWidget(trackMap, trackRef);
+    }
+    dateLabel = new JLabel();
+
+    slider = buildSlider(fileData.getNumberOfEvents());
+
+    JPanel sliderPanel = new JPanel();
+    sliderPanel.setLayout(new BorderLayout());
+    sliderPanel.add(slider, BorderLayout.NORTH);
+    sliderPanel.add(dateLabel, BorderLayout.SOUTH);
+
+    setLayout(new BorderLayout());
+
+    add(histoWidget, BorderLayout.NORTH);
+    add(sliderPanel, BorderLayout.CENTER);
+    if (trackWidget != null) add(trackWidget, BorderLayout.SOUTH);
+  }
+
+  private VisADSlider buildSlider(int initialLength)
+    throws RemoteException, VisADException
+  {
+    final DataReferenceImpl eSliderRef = new DataReferenceImpl("eSlider");
+
+    sliderLength = initialLength;
+
+    VisADSlider slider = new VisADSlider("event", 0, initialLength - 1, 0, 1.0,
+                                         eSliderRef, Event.indexType, true);
+    slider.hardcodeSizePercent(110); // leave room for label changes
+
+    // call setIndex() whenever slider changes
+    CellImpl cell = new CellImpl() {
+      public void doAction()
+        throws RemoteException, VisADException
+      {
+        Real r = (Real )eSliderRef.getData();
+        if (r != null) {
+          int index = (int )r.getValue();
+          if (index < 0) {
+            index = 0;
+          } else if (index > sliderLength) {
+            index = sliderLength;
+          }
+          indexChanged(index);
+        }
+      }
+    };
+    cell.addReference(eSliderRef);
+
+    return slider;
+  }
+
+  private final Date getDate(int year, int day, double time)
+  {
+    final int hr = (int )((time + 3599.0) / 3600.0);
+    time -= (double )hr * 3600.0;
+
+    final int min = (int )((time + 59.0) / 60.0);
+    time -= (double )min * 60.0;
+
+    final int sec = (int )time;
+    time -= (double )sec;
+
+    final int milli = (int )(time * 1000.0);
+
+    cal.clear();
+
+    cal.set(GregorianCalendar.YEAR, year);
+    cal.set(GregorianCalendar.DAY_OF_YEAR, day);
+    cal.set(GregorianCalendar.HOUR_OF_DAY, hr);
+    cal.set(GregorianCalendar.MINUTE, min);
+    cal.set(GregorianCalendar.SECOND, sec);
+    cal.set(GregorianCalendar.MILLISECOND, milli);
+    cal.set(GregorianCalendar.DST_OFFSET, 0);
+
+    return cal.getTime();
+  }
+
+  public final Event getEvent() { return thisEvent; }
+  public final JLabel getLabel() { return dateLabel; }
+  public final VisADSlider getSlider() { return slider; }
+  public final TrackWidget getTrackWidget() { return trackWidget; }
+
+  /**
+   * This method is called whenever the event index is changed
+   */
+  private void indexChanged(int index)
+    throws RemoteException, VisADException
+  {
+    thisEvent = fileData.getEvent(index);
+    if (thisEvent == null) {
+      eventRef.setData(Event.missing);
+      dateLabel.setText("*** NO DATE ***");
+    } else {
+      final FieldImpl hitSeq = thisEvent.makeHitSequence();
+      eventRef.setData(hitSeq);
+
+      animCtl.setSet(hitSeq.getDomainSet());
+
+      Date date = getDate(thisEvent.getYear(), thisEvent.getDay(),
+                          thisEvent.getTime());
+      dateLabel.setText(fmt.format(date));
+
+    }
+
+    histoWidget.setEvent(thisEvent);
+
+    if (trackWidget != null) trackWidget.setEvent(thisEvent);
+    this.invalidate();
+  }
+}
diff --git a/visad/data/amanda/F2000Form.java b/visad/data/amanda/F2000Form.java
new file mode 100644
index 0000000..ad65655
--- /dev/null
+++ b/visad/data/amanda/F2000Form.java
@@ -0,0 +1,131 @@
+//
+// F2000Form.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.Tuple;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormNode;
+import visad.data.FormFileInformer;
+
+/**
+   F2000Form is the VisAD data format adapter for
+   F2000 files for Amanda events.<P>
+*/
+public class F2000Form
+  extends Form
+  implements FormFileInformer
+{
+  private static int num = 0;
+
+  private AmandaFile file = null;
+
+  public F2000Form()
+  {
+    super("F2000Form#" + num++);
+  }
+
+  public synchronized void add(String id, Data data, boolean replace)
+    throws BadFormException
+  {
+    throw new BadFormException("F2000Form.add");
+  }
+
+  public String[] getDefaultSuffixes()
+  {
+    String[] suff = { "r" };
+    return suff;
+  }
+
+  public synchronized FormNode getForms(Data data)
+  {
+    return null;
+  }
+
+  public final double getXMax() { return file.getXMax(); }
+  public final double getXMin() { return file.getXMin(); }
+
+  public final double getYMax() { return file.getYMax(); }
+  public final double getYMin() { return file.getYMin(); }
+
+  public final double getZMax() { return file.getZMax(); }
+  public final double getZMin() { return file.getZMin(); }
+
+  public boolean isThisType(String name)
+  {
+    return name.endsWith(".r");
+  }
+
+  public boolean isThisType(byte[] block)
+  {
+    return false;
+  }
+
+  private Tuple makeTuple(AmandaFile file)
+    throws VisADException
+  {
+    Tuple t;
+    try {
+      t = new Tuple(new Data[] {file.makeEventData(), file.makeModuleData()});
+    } catch (RemoteException re) {
+      re.printStackTrace();
+      t = null;
+    }
+
+    return t;
+  }
+
+  public synchronized DataImpl open(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    file = new AmandaFile(id);
+    return makeTuple(file);
+  }
+
+  public synchronized DataImpl open(URL url)
+    throws BadFormException, VisADException, IOException
+  {
+    file = new AmandaFile(url);
+    return makeTuple(file);
+  }
+
+  public synchronized void save(String id, Data data, boolean replace)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    throw new BadFormException("F2000Form.save");
+  }
+}
diff --git a/visad/data/amanda/F2000Util.java b/visad/data/amanda/F2000Util.java
new file mode 100644
index 0000000..e80c9b0
--- /dev/null
+++ b/visad/data/amanda/F2000Util.java
@@ -0,0 +1,127 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.rmi.RemoteException;
+
+import visad.BaseColorControl;
+import visad.ScalarMap;
+import visad.VisADException;
+import visad.VisADQuadArray;
+
+public abstract class F2000Util
+{
+  private static final float CUBE = 0.05f;
+
+  public static final VisADQuadArray[] getCubeArray()
+  {
+    VisADQuadArray cube = new VisADQuadArray();
+    cube.coordinates = new float[]
+      {CUBE,  CUBE, -CUBE,     CUBE, -CUBE, -CUBE,
+       CUBE, -CUBE, -CUBE,    -CUBE, -CUBE, -CUBE,
+       -CUBE, -CUBE, -CUBE,    -CUBE,  CUBE, -CUBE,
+       -CUBE,  CUBE, -CUBE,     CUBE,  CUBE, -CUBE,
+
+       CUBE,  CUBE,  CUBE,     CUBE, -CUBE,  CUBE,
+       CUBE, -CUBE,  CUBE,    -CUBE, -CUBE,  CUBE,
+       -CUBE, -CUBE,  CUBE,    -CUBE,  CUBE,  CUBE,
+       -CUBE,  CUBE,  CUBE,     CUBE,  CUBE,  CUBE,
+
+       CUBE,  CUBE,  CUBE,     CUBE,  CUBE, -CUBE,
+       CUBE,  CUBE, -CUBE,     CUBE, -CUBE, -CUBE,
+       CUBE, -CUBE, -CUBE,     CUBE, -CUBE,  CUBE,
+       CUBE, -CUBE,  CUBE,     CUBE,  CUBE,  CUBE,
+
+       -CUBE,  CUBE,  CUBE,    -CUBE,  CUBE, -CUBE,
+       -CUBE,  CUBE, -CUBE,    -CUBE, -CUBE, -CUBE,
+       -CUBE, -CUBE, -CUBE,    -CUBE, -CUBE,  CUBE,
+       -CUBE, -CUBE,  CUBE,    -CUBE,  CUBE,  CUBE,
+
+       CUBE,  CUBE,  CUBE,     CUBE,  CUBE, -CUBE,
+       CUBE,  CUBE, -CUBE,    -CUBE,  CUBE, -CUBE,
+       -CUBE,  CUBE, -CUBE,    -CUBE,  CUBE,  CUBE,
+       -CUBE,  CUBE,  CUBE,     CUBE,  CUBE,  CUBE,
+
+       CUBE, -CUBE,  CUBE,     CUBE, -CUBE, -CUBE,
+       CUBE, -CUBE, -CUBE,    -CUBE, -CUBE, -CUBE,
+       -CUBE, -CUBE, -CUBE,    -CUBE, -CUBE,  CUBE,
+       -CUBE, -CUBE,  CUBE,     CUBE, -CUBE,  CUBE};
+
+    cube.vertexCount = cube.coordinates.length / 3;
+    cube.normals = new float[144];
+    cube.normals = new float[144];
+    for (int i=0; i<24; i+=3) {
+      cube.normals[i]     =  0.0f;
+      cube.normals[i+1]   =  0.0f;
+      cube.normals[i+2]   = -1.0f;
+
+      cube.normals[i+24]  =  0.0f;
+      cube.normals[i+25]  =  0.0f;
+      cube.normals[i+26]  =  1.0f;
+
+      cube.normals[i+48]  =  1.0f;
+      cube.normals[i+49]  =  0.0f;
+      cube.normals[i+50]  =  0.0f;
+
+      cube.normals[i+72]  = -1.0f;
+      cube.normals[i+73]  =  0.0f;
+      cube.normals[i+74]  =  0.0f;
+
+      cube.normals[i+96]  =  0.0f;
+      cube.normals[i+97]  =  1.0f;
+      cube.normals[i+98]  =  0.0f;
+
+      cube.normals[i+120] =  0.0f;
+      cube.normals[i+121] = -1.0f;
+      cube.normals[i+122] =  0.0f;
+    }
+
+    return new VisADQuadArray[] {cube};
+  }
+
+  public static final void invertColorTable(ScalarMap colorMap)
+  {
+    BaseColorControl colorCtl = (BaseColorControl )colorMap.getControl();
+    final int numColors = colorCtl.getNumberOfColors();
+    final int numComps = colorCtl.getNumberOfComponents();
+    float[][] table = colorCtl.getTable();
+    for (int i = 0; i < numColors / 2; i++) {
+      final int swaploc = numColors - (i + 1);
+      for (int j = 0; j < numComps; j++) {
+        float tmp = table[j][i];
+        table[j][i] = table[j][swaploc];
+        table[j][swaploc] = tmp;
+      }
+    }
+
+    try {
+      colorCtl.setTable(table);
+    } catch (RemoteException re) {
+      System.err.println("Couldn't invert color table");
+      re.printStackTrace();
+    } catch (VisADException ve) {
+      System.err.println("Couldn't invert color table");
+      ve.printStackTrace();
+    }
+  }
+}
diff --git a/visad/data/amanda/FitTrack.java b/visad/data/amanda/FitTrack.java
new file mode 100644
index 0000000..0df5f10
--- /dev/null
+++ b/visad/data/amanda/FitTrack.java
@@ -0,0 +1,42 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import visad.FlatField;
+import visad.VisADException;
+
+public class FitTrack
+  extends BaseTrack
+{
+  FitTrack(float xstart, float ystart, float zstart, float zenith,
+           float azimuth, float length, float energy, float time)
+  {
+    super(xstart, ystart, zstart, zenith, azimuth, length, energy, time);
+  }
+
+  final FlatField makeData()
+    throws VisADException
+  {
+    return makeData(10000.0f);
+  }
+}
diff --git a/visad/data/amanda/HistogramWidget.java b/visad/data/amanda/HistogramWidget.java
new file mode 100644
index 0000000..26a307b
--- /dev/null
+++ b/visad/data/amanda/HistogramWidget.java
@@ -0,0 +1,104 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.awt.Dimension;
+
+import java.rmi.RemoteException;
+
+import javax.swing.JPanel;
+
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.GraphicsModeControl;
+import visad.Gridded2DSet;
+import visad.RealType;
+import visad.RealTupleType;
+import visad.ScalarMap;
+import visad.VisADException;
+
+import visad.java2d.DisplayImplJ2D;
+
+public class HistogramWidget
+  extends JPanel
+{
+  private static final RealType countType = RealType.getRealType("count");
+
+  private static RealTupleType histoType;
+
+  static {
+    try {
+      histoType = new RealTupleType(Hit.leadingEdgeTimeType, countType);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't create histogram MathType");
+      ve.printStackTrace();
+      histoType = null;
+    }
+  }
+
+  private ScalarMap dpyColorMap;
+  private DataReferenceImpl ref;
+  private ScalarMap xMap, yMap, cMap;
+
+  public HistogramWidget(ScalarMap dpyColorMap)
+    throws RemoteException, VisADException
+  {
+    this.dpyColorMap = dpyColorMap;
+
+    DisplayImplJ2D dpy = new DisplayImplJ2D("histogram");
+
+    xMap = new ScalarMap(countType, Display.XAxis);
+    yMap = new ScalarMap(Hit.leadingEdgeTimeType, Display.YAxis);
+
+    dpy.addMap(xMap);
+    dpy.addMap(yMap);
+
+    cMap = new ScalarMap(Hit.leadingEdgeTimeType, Display.RGB);
+    dpy.addMap(cMap);
+    F2000Util.invertColorTable(cMap);
+
+    GraphicsModeControl gmc2 = dpy.getGraphicsModeControl();
+    gmc2.setScaleEnable(true);
+
+    ref = new DataReferenceImpl("histogram");
+    // data is set when a new event is selected
+    dpy.addReference(ref);
+
+    JPanel dpyPanel = (JPanel )dpy.getComponent();
+    Dimension dim = new Dimension(250, 250);
+    dpyPanel.setPreferredSize(dim);
+    dpyPanel.setMinimumSize(dim);
+
+    add(dpyPanel);
+  }
+
+  public void setEvent(Event evt)
+    throws RemoteException, VisADException
+  {
+    float[][] histoData = evt.makeHistogram(xMap, yMap, cMap, dpyColorMap);
+
+    Gridded2DSet set = new Gridded2DSet(histoType, histoData,
+                                        histoData[0].length);
+    ref.setData(set);
+  }
+}
diff --git a/visad/data/amanda/Hit.java b/visad/data/amanda/Hit.java
new file mode 100644
index 0000000..f5d8d0a
--- /dev/null
+++ b/visad/data/amanda/Hit.java
@@ -0,0 +1,151 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.rmi.RemoteException;
+
+import visad.RealTuple;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.VisADException;
+
+import visad.util.Util;
+
+public class Hit
+  implements Comparable
+{
+  public static final RealType amplitudeType =
+    RealType.getRealType("Hit_Amplitude");
+  public static final RealType indexType =
+    RealType.getRealType("Hit_Index");
+  public static final RealType leadingEdgeTimeType =
+    RealType.getRealType("Hit_Leading_Edge_Time");
+  public static final RealType moduleType =
+    RealType.getRealType("Hit_Module");
+  private static final RealType timeOverThresholdType =
+    RealType.getRealType("Hit_Time_Over_Threshold");
+
+  public static RealTupleType tupleType;
+
+  public static RealTuple missing;
+
+  static {
+    try {
+      tupleType = new RealTupleType(new RealType[] {
+        moduleType, RealType.XAxis, RealType.YAxis, RealType.ZAxis,
+        amplitudeType, leadingEdgeTimeType, timeOverThresholdType
+      });
+
+      missing = new RealTuple(tupleType);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      tupleType = null;
+      missing = null;
+    }
+  }
+
+  private Module mod;
+  private float amplitude, leadEdgeTime, timeOverThreshold;
+  private RealTuple data;
+
+  Hit(Module mod, float amplitude, float leadEdgeTime,
+      float timeOverThreshold)
+  {
+    this.mod = mod;
+    this.amplitude = amplitude;
+    this.leadEdgeTime = leadEdgeTime;
+    this.timeOverThreshold = timeOverThreshold;
+
+    this.data = null;
+  }
+
+  private static final int compareFloat(float f0, float f1)
+  {
+    if (Util.isApproximatelyEqual(f0, f1)) {
+      return 0;
+    }
+
+    return (f0 < f1 ? -1 : 1);
+  }
+
+  public int compareTo(Object obj)
+  {
+    if (!(obj instanceof Hit)) {
+      return getClass().toString().compareTo(obj.getClass().toString());
+    }
+
+    return compareTo((Hit )obj);
+  }
+
+  public int compareTo(Hit h)
+  {
+    int cmp = compareFloat(leadEdgeTime, h.leadEdgeTime);
+    if (cmp == 0) {
+      cmp = compareFloat(timeOverThreshold, h.timeOverThreshold);
+      if (cmp == 0) {
+        cmp = compareFloat(amplitude, h.amplitude);
+        if (cmp == 0) {
+          cmp = mod.compareTo(h.mod);
+        }
+      }
+    }
+
+    return cmp;
+  }
+
+  public boolean equals(Object obj) { return (compareTo(obj) == 0); }
+
+  public final float getAmplitude() { return amplitude; }
+  public final float getLeadingEdgeTime() { return leadEdgeTime; }
+  public final Module getModule() { return mod; }
+  public final float getTimeOverThreshold() { return timeOverThreshold; }
+
+  public final RealTuple makeData()
+  {
+    if (data == null) {
+      // construct Tuple for hit
+      try {
+        data = new RealTuple(tupleType,
+                             new double[] {
+                               mod.getNumber(),
+                               mod.getX(), mod.getY(), mod.getZ(),
+                               amplitude, leadEdgeTime, timeOverThreshold
+                             });
+      } catch (RemoteException re) {
+        re.printStackTrace();
+        data = missing;
+      } catch (VisADException ve) {
+        ve.printStackTrace();
+        data = missing;
+      }
+    }
+
+    return data;
+  }
+
+  public String toString()
+  {
+    return "Hit[Mod#" + mod.getNumber() + " amp " + amplitude +
+      " let " + leadEdgeTime + " tot " + timeOverThreshold + "]";
+  }
+}
diff --git a/visad/data/amanda/Hits.java b/visad/data/amanda/Hits.java
new file mode 100644
index 0000000..28ec4de
--- /dev/null
+++ b/visad/data/amanda/Hits.java
@@ -0,0 +1,253 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.rmi.RemoteException;
+
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Gridded1DSet;
+import visad.Integer1DSet;
+import visad.RealTuple;
+import visad.RealType;
+import visad.VisADException;
+
+public class Hits
+  extends EventList
+{
+  public static FunctionType functionType;
+  public static FunctionType timeSequenceType;
+  public static FieldImpl missing;
+
+  public static final RealType indexType =
+    RealType.getRealType("Hits_Index");
+
+  private static final int MIN_TIMESTEPS = 20;
+  private static final int MAX_TIMESTEPS = 50;
+
+  private static FunctionType indexTupleType;
+
+  static {
+    try {
+      functionType = new FunctionType(Hit.indexType, Hit.tupleType);
+
+      indexTupleType = new FunctionType(indexType, Hit.tupleType);
+
+      timeSequenceType = new FunctionType(RealType.Time, indexTupleType);
+
+      Gridded1DSet set = new Gridded1DSet(RealType.Time, new float[1][1], 1);
+      missing = new FieldImpl(timeSequenceType, set);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      functionType = null;
+      timeSequenceType = null;
+      indexTupleType = null;
+      missing = null;
+    }
+  }
+
+  private float[] timeSteps = null;
+  private Integer1DSet timeSubSet = null;
+  private FlatField timeMissingFld = null;
+
+  public Hits() { }
+
+  public final void add(Hit hit)
+  {
+    super.add(hit);
+
+    // need to recompute all data objects which depend on list of Hit objects
+    timeSteps = null;
+    timeSubSet = null;
+    timeMissingFld = null;
+  }
+
+  private final boolean computeDataObjects()
+  {
+    final int numHits = size();
+
+    // build some VisAD Data objects which will be used later
+    boolean rtnval = true;
+    try {
+      timeSubSet = new Integer1DSet(indexType, numHits);
+      timeMissingFld = new FlatField(indexTupleType, timeSubSet);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      timeSubSet = null;
+      timeMissingFld = null;
+      rtnval = false;
+    }
+
+    return rtnval;
+  }
+
+  /**
+   * Build the array of timesteps
+   */
+  private final void computeTimeSteps()
+  {
+    final int numHits = size();
+
+    float startTime = Float.MAX_VALUE;
+    float endTime = Float.MIN_VALUE;
+
+    float minLen = Float.MAX_VALUE;
+
+    // gather info needed to compute number of timesteps
+    for (int i = 0; i < numHits; i++) {
+      Hit hit = (Hit )internalGet(i);
+
+      final float st = hit.getLeadingEdgeTime();
+      if (startTime > st) {
+        startTime = st;
+      }
+
+      final float len = hit.getTimeOverThreshold();
+      if (len < minLen) {
+        minLen = len;
+      }
+
+      final float et = st + len;
+      if (endTime < et) {
+        endTime = et;
+      }
+    }
+
+    final float totalTime = endTime - startTime;
+
+    // figure out how many time steps we can fit into the total interval
+    int steps = (int )(totalTime / minLen);
+    if (steps < MIN_TIMESTEPS) {
+      steps = MIN_TIMESTEPS;
+    } else if (steps > MAX_TIMESTEPS) {
+      steps = MAX_TIMESTEPS;
+    }
+
+    // compute amount of time for each step
+    final float stepLen = totalTime / (float )steps;
+
+    timeSteps = new float[steps+1];
+
+    // build array of time steps
+    timeSteps[0] = startTime;
+    for (int i = 0; i < steps; i++) {
+      timeSteps[i+1] = timeSteps[i] + stepLen;
+    }
+  }
+
+  public final Hit get(int i) { return (Hit )super.internalGet(i); }
+
+  final FlatField getHitsBeforeTime(float time)
+  {
+    if (timeSubSet == null || timeMissingFld == null) {
+      if (!computeDataObjects()) {
+        return null;
+      }
+    }
+
+    final int numHits = size();
+
+    RealTuple[] rt = new RealTuple[numHits];
+
+    for (int i = 0; i < numHits; i++) {
+      Hit hit = (Hit )internalGet(i);
+
+      final float leadTime = hit.getLeadingEdgeTime();
+      if (time < leadTime) {
+        rt[i] = Hit.missing;
+      } else {
+        rt[i] = hit.makeData();
+      }
+    }
+
+    FlatField fld;
+    try {
+      fld = new FlatField(indexTupleType, timeSubSet);
+      fld.setSamples(rt, false);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      fld = timeMissingFld;
+    } catch (RemoteException re) {
+      re.printStackTrace();
+      fld = timeMissingFld;
+    }
+
+    return fld;
+  }
+
+  final Gridded1DSet getTimeStepSet(RealType setType)
+  {
+    if (timeSteps == null) {
+      computeTimeSteps();
+    }
+
+    Gridded1DSet set;
+    try {
+      set = new Gridded1DSet(setType,
+                             new float[][] { timeSteps },
+                             timeSteps.length);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      set = null;
+    }
+
+    return set;
+  }
+
+  final float[] getTimeSteps()
+  {
+    if (timeSteps == null) {
+      computeTimeSteps();
+    }
+
+    return timeSteps;
+  }
+
+  final FieldImpl makeTimeSequence()
+  {
+    if (timeSteps == null) {
+      computeTimeSteps();
+    }
+
+    FlatField[] data = new FlatField[timeSteps.length];
+
+    for (int a = 0; a < timeSteps.length; a++) {
+      data[a] = getHitsBeforeTime(timeSteps[a]);
+    }
+
+    FieldImpl fld;
+    try {
+      fld = new FieldImpl(timeSequenceType, getTimeStepSet(RealType.Time));
+      fld.setSamples(data, false);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      fld = null;
+    } catch (RemoteException re) {
+      re.printStackTrace();
+      fld = null;
+    }
+
+    return fld;
+  }
+}
diff --git a/visad/data/amanda/MCTrack.java b/visad/data/amanda/MCTrack.java
new file mode 100644
index 0000000..778333c
--- /dev/null
+++ b/visad/data/amanda/MCTrack.java
@@ -0,0 +1,42 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import visad.FlatField;
+import visad.VisADException;
+
+public class MCTrack
+  extends BaseTrack
+{
+  MCTrack(float xstart, float ystart, float zstart, float zenith,
+          float azimuth, float length, float energy, float time)
+  {
+    super(xstart, ystart, zstart, zenith, azimuth, length, energy, time);
+  }
+
+  final FlatField makeData()
+    throws VisADException
+  {
+    return makeData(1000.0f);
+  }
+}
diff --git a/visad/data/amanda/Module.java b/visad/data/amanda/Module.java
new file mode 100644
index 0000000..3653b30
--- /dev/null
+++ b/visad/data/amanda/Module.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+public class Module
+  extends Point
+  implements Comparable
+{
+  private int number;
+  private int string, stringOrder;
+
+  Module(int number)
+  {
+    this(number, Float.NaN, Float.NaN, Float.NaN, -1, -1);
+  }
+
+  Module(int number, float x, float y, float z, int string, int stringOrder)
+  {
+    super(x, y, z);
+
+    this.number = number;
+    this.string = string;
+    this.stringOrder = stringOrder;
+  }
+
+  public int compareTo(Object obj)
+  {
+    if (!(obj instanceof Module)) {
+      return getClass().toString().compareTo(obj.getClass().toString());
+    }
+
+    return compareTo((Module )obj);
+  }
+
+  public int compareTo(Module mod)
+  {
+    return (number - mod.number);
+  }
+
+  public boolean equals(Object o) { return (compareTo(o) == 0); }
+
+  int getNumber() { return number; }
+
+  public String toString()
+  {
+    return "Module#" + number + super.toString() +
+      "Str#" + string + "/" + stringOrder;
+  }
+}
diff --git a/visad/data/amanda/ModuleList.java b/visad/data/amanda/ModuleList.java
new file mode 100644
index 0000000..f11a41b
--- /dev/null
+++ b/visad/data/amanda/ModuleList.java
@@ -0,0 +1,165 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+
+public class ModuleList
+{
+  private ArrayList list;
+  private Module[] sortedArray;
+
+  public ModuleList()
+  {
+    list = null;
+    sortedArray = null;
+  }
+
+  public void add(Module mod)
+  {
+    if (list == null) {
+      list = new ArrayList();
+    }
+
+    list.add(mod);
+  }
+
+  public void dump(java.io.PrintStream out)
+  {
+    // if there are modules to be sorted, do it now
+    if (list != null && list.size() > 0) {
+      sort();
+    }
+
+    // if there are no modules, we're done dumping
+    if (sortedArray == null) {
+      return;
+    }
+
+    final int nMods = sortedArray.length;
+    for (int i = 0; i < nMods; i++) {
+      out.println(sortedArray[i]);
+    }
+  }
+
+  public Module find(int number)
+  {
+    // if there are modules to be sorted, do it now
+    if (list != null && list.size() > 0) {
+      sort();
+    }
+
+    // if one or more sorted modules exist...
+    if (sortedArray != null) {
+
+      // look for the specified module number...
+      int idx = Arrays.binarySearch(sortedArray, new Module(number));
+      if (idx >= 0) {
+
+        // return the desired module
+        return sortedArray[idx];
+      }
+    }
+
+    // couldn't find a module with that number
+    return null;
+  }
+
+  public Module get(int i)
+  {
+    // if there are modules to be sorted, do it now
+    if (list != null && list.size() > 0) {
+      sort();
+    }
+
+    if (i < 0 || sortedArray == null || i >= sortedArray.length) {
+      return null;
+    }
+
+    return sortedArray[i];
+  }
+
+  public final boolean isInitialized()
+  {
+    return (sortedArray != null || (list != null && list.size() > 0));
+  }
+
+  public int size()
+  {
+    int len = 0;
+
+    if (list != null) {
+      len += list.size();
+    }
+
+    if (sortedArray != null) {
+      len += sortedArray.length;
+    }
+
+    return len;
+  }
+
+  private void sort()
+  {
+    // if some modules have been sorted...
+    if (sortedArray != null) {
+
+      // merge in previously sorted list of modules
+      for (int i = 0; i < sortedArray.length; i++) {
+        list.add(sortedArray[i]);
+      }
+    }
+
+    // sort modules
+    sortedArray = (Module[] )list.toArray(new Module[list.size()]);
+    Arrays.sort(sortedArray);
+
+    // out with the old
+    list.clear();
+  }
+
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("ModuleList[");
+
+    boolean isEmpty = true;
+
+    if (list != null && list.size() > 0) {
+      buf.append("unsorted=");
+      buf.append(list.size());
+      isEmpty = false;
+    }
+
+    if (sortedArray != null) {
+      buf.append("sorted=");
+      buf.append(sortedArray.length);
+      isEmpty = false;
+    }
+
+    if (isEmpty) buf.append("empty");
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/data/amanda/NuView.java b/visad/data/amanda/NuView.java
new file mode 100644
index 0000000..063e7a2
--- /dev/null
+++ b/visad/data/amanda/NuView.java
@@ -0,0 +1,392 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+
+import visad.AnimationControl;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.DisplayRenderer;
+import visad.Integer1DSet;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.ShapeControl;
+import visad.VisADException;
+
+import visad.java3d.DisplayImplJ3D;
+
+import visad.util.AnimationWidget;
+import visad.util.CmdlineConsumer;
+import visad.util.CmdlineParser;
+import visad.util.Util;
+
+/** run 'java NuView in_file' to display data.<br>
+ *  try 'java NuView 100events.r'
+ */
+public class NuView
+  extends WindowAdapter
+  implements CmdlineConsumer
+{
+  private static HashMap colorHash;
+
+  static {
+    colorHash = new HashMap();
+    colorHash.put("black", Color.black);
+    colorHash.put("blue", Color.blue);
+    colorHash.put("cyan", Color.cyan);
+    colorHash.put("darkGray", Color.darkGray);
+    colorHash.put("gray", Color.gray);
+    colorHash.put("green", Color.green);
+    colorHash.put("lightGray", Color.lightGray);
+    colorHash.put("magenta", Color.magenta);
+    colorHash.put("orange", Color.orange);
+    colorHash.put("pink", Color.pink);
+    colorHash.put("red", Color.red);
+    colorHash.put("white", Color.white);
+    colorHash.put("yellow", Color.yellow);
+  };
+
+  private String fileName;
+  private int displayDim;
+  private Color trackColor;
+
+  private DisplayImpl display, display2;
+
+  public NuView(String[] args)
+    throws RemoteException, VisADException
+  {
+    CmdlineParser cmdline = new CmdlineParser(this);
+    if (!cmdline.processArgs(args)) {
+      System.exit(1);
+      return;
+    }
+
+    AmandaFile file = openFile(fileName);
+
+    display = new DisplayImplJ3D("amanda");
+
+    JPanel widgetPanel = buildMainDisplay(display, file, trackColor);
+
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+
+    // make sure display window will fit on the screen
+    if (displayDim > screenSize.height) {
+      displayDim = screenSize.height;
+    }
+
+    JPanel displayPanel = (JPanel )display.getComponent();
+    Dimension dim = new Dimension(displayDim, displayDim);
+    displayPanel.setPreferredSize(dim);
+    displayPanel.setMinimumSize(dim);
+
+    // create JPanel in frame
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+    panel.add(widgetPanel);
+    panel.add(displayPanel);
+
+    JFrame frame = new JFrame("VisAD AMANDA Viewer");
+
+    frame.addWindowListener(this);
+    frame.getContentPane().add(panel);
+    frame.pack();
+    panel.invalidate();
+
+    Dimension fSize = frame.getSize();
+
+    frame.setLocation((screenSize.width - fSize.width) / 2,
+                      (screenSize.height - fSize.height) / 2);
+
+    frame.setVisible(true);
+  }
+
+  private static final Component buildHelp()
+  {
+    String text =
+      "To rotate, hold down the left mouse button and move the mouse in the right-hand display window\n\nTo zoom, move the mouse while holding down the left mouse button and Shift key\n\nTo pan, move the mouse while holding down the left mouse button and Control key";
+    JTextArea textArea = new JTextArea(text);
+    textArea.setLineWrap(true);
+    textArea.setWrapStyleWord(true);
+    textArea.setEditable(false);
+    return textArea;
+
+  }
+
+  private static final JPanel buildMainDisplay(DisplayImpl dpy,
+                                               AmandaFile file,
+                                               Color trackColor)
+    throws RemoteException, VisADException
+  {
+    final double halfRange = getMaxRange(file) / 2.0;
+
+    ScalarMap xMap = new ScalarMap(RealType.XAxis, Display.XAxis);
+    setRange(xMap, file.getXMin(), file.getXMax(), halfRange);
+    dpy.addMap(xMap);
+
+    ScalarMap yMap = new ScalarMap(RealType.YAxis, Display.YAxis);
+    setRange(yMap, file.getYMin(), file.getYMax(), halfRange);
+    dpy.addMap(yMap);
+
+    ScalarMap zMap = new ScalarMap(RealType.ZAxis, Display.ZAxis);
+    setRange(zMap, file.getZMin(), file.getZMax(), halfRange);
+    dpy.addMap(zMap);
+
+    ScalarMap shapeMap = new ScalarMap(Hit.amplitudeType, Display.Shape);
+    dpy.addMap(shapeMap);
+
+    ScalarMap trackMap =
+      new ScalarMap(BaseTrack.indexType, Display.SelectValue);
+    dpy.addMap(trackMap);
+
+    ShapeControl sctl = (ShapeControl )shapeMap.getControl();
+    sctl.setShapeSet(new Integer1DSet(Hit.amplitudeType, 1));
+    sctl.setShapes(F2000Util.getCubeArray());
+
+    ScalarMap shapeScaleMap =
+      new ScalarMap(Hit.amplitudeType, Display.ShapeScale);
+    dpy.addMap(shapeScaleMap);
+    shapeScaleMap.setRange(-20.0, 50.0);
+
+    ScalarMap colorMap = new ScalarMap(Hit.leadingEdgeTimeType, Display.RGB);
+    dpy.addMap(colorMap);
+
+    // invert color table so colors match what is expected
+    F2000Util.invertColorTable(colorMap);
+
+    ScalarMap animMap = new ScalarMap(RealType.Time, Display.Animation);
+    dpy.addMap(animMap);
+
+    DisplayRenderer dpyRenderer = dpy.getDisplayRenderer();
+    dpyRenderer.setBoxOn(false);
+    dpyRenderer.setBackgroundColor(Color.white);
+    dpyRenderer.setForegroundColor(Color.black);
+
+    final DataReferenceImpl eventRef = new DataReferenceImpl("event");
+    // data set by eventWidget below
+    dpy.addReference(eventRef);
+
+    final DataReferenceImpl trackRef = new DataReferenceImpl("track");
+    // data set by eventWidget below
+    dpy.addReference(trackRef, Util.getColorMaps(trackColor));
+
+    final DataReferenceImpl modulesRef = new DataReferenceImpl("modules");
+    modulesRef.setData(file.makeModuleData());
+    dpy.addReference(modulesRef, Util.getColorMaps(Color.black));
+
+/*
+    LabeledColorWidget colorWidget = new LabeledColorWidget(colorMap);
+    // align along left side, to match VisADSlider alignment
+    //   (if we don't left-align, BoxLayout hoses everything)
+    colorWidget.setAlignmentX(Component.LEFT_ALIGNMENT);
+*/
+
+    AnimationControl animCtl = (AnimationControl )animMap.getControl();
+
+    HistogramWidget histoWidget = new HistogramWidget(colorMap);
+
+    EventWidget eventWidget = new EventWidget(file, eventRef, trackRef,
+                                              animCtl, trackMap, histoWidget);
+
+    Component helpText = buildHelp();
+
+    AnimationWidget animWidget;
+    try {
+      animWidget = new AnimationWidget(animMap);
+    } catch (Exception ex) {
+      ex.printStackTrace();
+      animWidget = null;
+    }
+
+    JPanel panel = new JPanel();
+    panel.setLayout(new BorderLayout());
+    panel.setMaximumSize(new Dimension(400, 600));
+
+//    panel.add(colorWidget);
+
+    panel.add(eventWidget, BorderLayout.NORTH);
+    if (animWidget != null) {
+      panel.add(animWidget, BorderLayout.CENTER);
+    }
+    panel.add(helpText, BorderLayout.SOUTH);
+
+//    panel.add(Box.createHorizontalGlue());
+
+    return panel;
+  }
+
+  private static final Component buildText(String text)
+  {
+    JTextArea textArea = new JTextArea(text);
+    textArea.setLineWrap(true);
+    textArea.setWrapStyleWord(true);
+    textArea.setEditable(false);
+    return textArea;
+  }
+
+  public int checkKeyword(String mainName, int thisArg, String[] args)
+  {
+    if (fileName == null) {
+      fileName = args[thisArg];
+      return 1;
+    }
+
+    return 0;
+  }
+
+  public int checkOption(String mainName, char ch, String arg)
+  {
+    if (ch == 'd') {
+      int tmpDim;
+      try {
+        tmpDim = Integer.parseInt(arg);
+      } catch (NumberFormatException nfe) {
+        System.err.println(mainName + ": Bad display dimension \"" + arg +
+                           "\"");
+        return -1;
+      }
+
+      displayDim = tmpDim;
+      return 2;
+    }
+
+    if (ch == 't') {
+      final String lName = arg.toLowerCase();
+      if (!colorHash.containsKey(lName)) {
+        System.err.println(mainName + ": Unknown color \"" + arg +
+                           "\". Valid colors are:");
+
+        Iterator iter = colorHash.keySet().iterator();
+        while (iter.hasNext()) {
+          System.err.println("  " + iter.next());
+        }
+
+        return -1;
+      }
+
+      trackColor = (Color )colorHash.get(lName);
+      return 2;
+    }
+
+    return 0;
+  }
+
+  public boolean finalizeArgs(String mainName)
+  {
+    if (fileName == null) {
+      System.err.println(mainName + ": No file specified!");
+      return false;
+    }
+
+    return true;
+  }
+
+  private static final double getMaxRange(AmandaFile file)
+  {
+    final double xRange = file.getXMax() - file.getXMin();
+    final double yRange = file.getYMax() - file.getYMin();
+    final double zRange = file.getZMax() - file.getZMin();
+
+    return -0.5 * Math.max(xRange, Math.max(yRange, zRange));
+  }
+
+  public void initializeArgs()
+  {
+    displayDim = 800;
+    fileName = null;
+    trackColor = Color.darkGray;
+  }
+
+  public String keywordUsage()
+  {
+    return " fileName";
+  }
+
+  private static final AmandaFile openFile(String fileName)
+    throws VisADException
+  {
+    AmandaFile file;
+    try {
+      if (fileName.startsWith("http://")) {
+        // "ftp://" throws "sun.net.ftp.FtpProtocolException: RETR ..."
+        file = new AmandaFile(new URL(fileName));
+      } else {
+        file = new AmandaFile(fileName);
+      }
+    } catch (IOException ioe) {
+      ioe.printStackTrace();
+      throw new VisADException(ioe.getMessage());
+    }
+
+    return file;
+  }
+
+  public String optionUsage()
+  {
+    return " [-d displayDim] [-t trackColor]";
+  }
+
+  private static final void setRange(ScalarMap map, double min, double max,
+                                     double halfRange)
+    throws RemoteException, VisADException
+  {
+    final double mid = (min + max) / 2.0;
+    map.setRange(mid - halfRange, mid + halfRange);
+  }
+
+  public void windowClosing(WindowEvent evt)
+  {
+    try { display.destroy(); } catch (Exception e) { }
+    try { display2.destroy(); } catch (Exception e) { }
+    System.exit(0);
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    new NuView(args);
+  }
+}
diff --git a/visad/data/amanda/NuView.py b/visad/data/amanda/NuView.py
new file mode 100644
index 0000000..e8cf5eb
--- /dev/null
+++ b/visad/data/amanda/NuView.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env jython
+
+import sys
+
+from java.awt import Component, Dimension
+from java.lang import Boolean
+from javax.swing import Box, BoxLayout, JPanel
+
+from visad import *
+from visad.data.amanda import AmandaFile, BaseTrack, EventWidget, F2000Util, Hit, TrackWidget
+from visad.java3d import DisplayImplJ3D
+from visad.util import LabeledColorWidget
+
+class DisplayFrame:
+  def desty(self, event):
+    self.display.destroy()
+    sys.exit(0)
+
+  def __init__(self, title, display, panel):
+    from java.awt import Toolkit
+    from javax.swing import JFrame
+    self.display = display
+
+    frame = JFrame(title, windowClosing=self.desty)
+    frame.getContentPane().add(panel)
+    frame.pack()
+    frame.invalidate()
+
+    fSize = frame.getSize()
+    screensize = Toolkit.getDefaultToolkit().getScreenSize()
+    frame.setLocation((screensize.width - fSize.width)/2,
+                      (screensize.height - fSize.height)/2)
+    frame.setVisible(1)
+
+class DisplayMaps:
+  def __init__(self, file, display):
+    # compute x, y and z ranges with unity aspect ratios
+    xrange = file.getXMax() - file.getXMin()
+    yrange = file.getYMax() - file.getYMin()
+    zrange = file.getZMax() - file.getZMin()
+    halfrange = -0.5 * max(xrange, max(yrange, zrange))
+    xmid = 0.5 * (file.getXMax() + file.getXMin())
+    ymid = 0.5 * (file.getYMax() + file.getYMin())
+    zmid = 0.5 * (file.getZMax() + file.getZMin())
+    xmin = xmid - halfrange
+    xmax = xmid + halfrange
+    ymin = ymid - halfrange
+    ymax = ymid + halfrange
+    zmin = zmid - halfrange
+    zmax = zmid + halfrange
+
+    xmap = ScalarMap(RealType.XAxis, Display.XAxis)
+    display.addMap(xmap)
+    xmap.setRange(xmin, xmax)
+
+    ymap = ScalarMap(RealType.YAxis, Display.YAxis)
+    display.addMap(ymap)
+    ymap.setRange(ymin, ymax)
+
+    zmap = ScalarMap(RealType.ZAxis, Display.ZAxis)
+    display.addMap(zmap)
+    zmap.setRange(zmin, zmax)
+
+    self.trackmap = ScalarMap(BaseTrack.indexType, Display.SelectValue)
+    display.addMap(self.trackmap)
+
+    self.shapemap = ScalarMap(Hit.amplitudeType, Display.Shape)
+    display.addMap(self.shapemap)
+
+    shapeScalemap = ScalarMap(Hit.amplitudeType, Display.ShapeScale)
+    display.addMap(shapeScalemap)
+    shapeScalemap.setRange(-20.0, 50.0)
+
+    self.letmap = ScalarMap(Hit.leadingEdgeTimeType, Display.RGB)
+    display.addMap(self.letmap)
+
+############################################################################
+
+if len(sys.argv) != 2:
+  sys.stderr.write("Please specify the F2000 file to be read\n")
+  sys.exit(1)
+
+file = AmandaFile(sys.argv[1])
+
+amanda = file.makeEventData()
+modules = file.makeModuleData()
+
+display = DisplayImplJ3D("amanda")
+
+maps = DisplayMaps(file, display)
+
+displayRenderer = display.getDisplayRenderer()
+displayRenderer.setBoxOn(0)
+
+scontrol = maps.shapemap.getControl()
+scontrol.setShapeSet(Integer1DSet(Hit.amplitudeType, 1))
+scontrol.setShapes(F2000Util.getCubeArray())
+
+nevents = amanda.getLength()
+
+amandaRef = DataReferenceImpl("amanda")
+# data set by eventWidget below
+display.addReference(amandaRef)
+
+modulesRef = DataReferenceImpl("modules")
+modulesRef.setData(modules)
+display.addReference(modulesRef)
+
+letWidget = LabeledColorWidget(maps.letmap)
+# align along left side, to match VisADSlider alignment
+#   (if we don't left-align, BoxLayout hoses everything
+letWidget.setAlignmentX(Component.LEFT_ALIGNMENT)
+
+eventWidget = EventWidget(file, amanda, amandaRef, maps.trackmap)
+
+widgetPanel = JPanel()
+widgetPanel.setLayout(BoxLayout(widgetPanel, BoxLayout.Y_AXIS))
+widgetPanel.setMaximumSize(Dimension(400, 600))
+
+widgetPanel.add(letWidget)
+widgetPanel.add(eventWidget)
+widgetPanel.add(Box.createHorizontalGlue())
+
+displayPanel = display.getComponent()
+dim = Dimension(800, 800)
+displayPanel.setPreferredSize(dim)
+displayPanel.setMinimumSize(dim)
+
+# if widgetPanel alignment doesn't match
+#  displayPanel alignment, BoxLayout will freak out
+widgetPanel.setAlignmentX(displayPanel.getAlignmentX())
+widgetPanel.setAlignmentY(displayPanel.getAlignmentY())
+
+# create JPanel in frame
+panel = JPanel()
+panel.setLayout(BoxLayout(panel, BoxLayout.X_AXIS))
+
+panel.add(widgetPanel)
+panel.add(displayPanel)
+
+DisplayFrame("VisAD AMANDA Viewer", display, panel)
diff --git a/visad/data/amanda/Point.java b/visad/data/amanda/Point.java
new file mode 100644
index 0000000..e91a9f0
--- /dev/null
+++ b/visad/data/amanda/Point.java
@@ -0,0 +1,41 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+public class Point
+{
+  private final float x, y, z;
+
+  public Point(float x, float y, float z)
+  {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+  }
+
+  float getX() { return x; }
+  float getY() { return y; }
+  float getZ() { return z; }
+
+  public String toString() { return "[" + x + "," + y + "," + z + "]"; }
+}
diff --git a/visad/data/amanda/TrackWidget.java b/visad/data/amanda/TrackWidget.java
new file mode 100644
index 0000000..3053d0f
--- /dev/null
+++ b/visad/data/amanda/TrackWidget.java
@@ -0,0 +1,180 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FlowLayout;
+
+import java.rmi.RemoteException;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import visad.ControlEvent;
+import visad.ControlListener;
+import visad.DataReferenceImpl;
+import visad.FieldImpl;
+import visad.PlotText;
+import visad.ScalarMap;
+import visad.ScalarMapControlEvent;
+import visad.ScalarMapEvent;
+import visad.ScalarMapListener;
+import visad.ValueControl;
+import visad.VisADException;
+
+import visad.util.VisADSlider;
+
+public class TrackWidget
+  extends JPanel
+  implements ControlListener, ScalarMapListener
+{
+  private ScalarMap map;
+  private DataReferenceImpl ref;
+
+  private Event event;
+
+  private int trackIndex;
+
+  private JLabel lengthLabel, energyLabel;
+
+  public TrackWidget(ScalarMap map, DataReferenceImpl ref)
+    throws RemoteException, VisADException
+  {
+    super();
+
+    ValueControl ctl = (ValueControl )map.getControl();
+    ctl.addControlListener(this);
+
+    this.map = map;
+    this.ref = ref;
+    this.event = null;
+    this.trackIndex = (int )ctl.getValue();
+
+    setLayout(new BorderLayout());
+
+    Component labels = buildLabels();
+
+    VisADSlider trackSlider = new VisADSlider(map, true, true);
+    trackSlider.hardcodeSizePercent(110); // leave room for label changes
+
+    add(trackSlider, BorderLayout.NORTH);
+    add(labels, BorderLayout.SOUTH);
+  }
+
+  public Component buildLabels()
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new FlowLayout());
+
+    lengthLabel = new JLabel("WWWWWW.WWWW");
+    lengthLabel.setMinimumSize(lengthLabel.getSize());
+    energyLabel = new JLabel("WWWWWW.WWWW");
+    energyLabel.setMinimumSize(energyLabel.getSize());
+
+    panel.add(new JLabel("  Length: "));
+    panel.add(lengthLabel);
+    panel.add(new JLabel("  Energy: "));
+    panel.add(energyLabel);
+
+    return panel;
+  }
+
+  private void changeControl(ValueControl ctl)
+  {
+    if (event == null) {
+      trackChanged(null);
+    } else {
+      trackIndex = (int )ctl.getValue();
+
+      trackChanged(event.getTrack(trackIndex));
+    }
+  }
+
+  public void controlChanged(ControlEvent evt)
+  {
+    changeControl((ValueControl )evt.getControl());
+  }
+
+  public void controlChanged(ScalarMapControlEvent evt)
+  {
+    changeControl((ValueControl )evt.getControl());
+  }
+
+  private static final String floatString(float val)
+  {
+    if (val == Float.POSITIVE_INFINITY) {
+      return "inf";
+    } else if (val == Float.NEGATIVE_INFINITY) {
+      return "-inf";
+    } else if (val == Float.NaN) {
+      return "?";
+    }
+
+    return PlotText.shortString(val);
+  }
+
+  public void mapChanged(ScalarMapEvent evt)
+  {
+    System.err.println(evt);
+  }
+
+  public void setEvent(Event evt)
+    throws RemoteException, VisADException
+  {
+    this.event = evt;
+    if (event == null) {
+      trackChanged(null);
+    } else {
+      map.setRange(0.0, (double )event.getNumberOfTracks());
+      trackChanged(event.getTrack(trackIndex));
+    }
+  }
+
+  private void trackChanged(BaseTrack track)
+  {
+    final FieldImpl trackSeq;
+
+    if (track == null) {
+      lengthLabel.setText("");
+      energyLabel.setText("");
+
+      trackSeq = BaseTrack.missing;
+    } else {
+      lengthLabel.setText(floatString(track.getLength()));
+      energyLabel.setText(floatString(track.getEnergy()));
+
+      trackSeq = event.makeTrackSequence(trackIndex);
+    }
+
+    try {
+      ref.setData(trackSeq);
+    } catch (RemoteException re) {
+      re.printStackTrace();
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+
+    this.invalidate();
+  }
+}
diff --git a/visad/data/amanda/Tracks.java b/visad/data/amanda/Tracks.java
new file mode 100644
index 0000000..d3ee303
--- /dev/null
+++ b/visad/data/amanda/Tracks.java
@@ -0,0 +1,88 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.amanda;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.VisADException;
+
+public class Tracks
+  extends EventList
+{
+  public static FunctionType functionType;
+
+  static {
+    try {
+      functionType = new FunctionType(BaseTrack.indexType,
+                                      BaseTrack.functionType);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+      functionType = null;
+    }
+  }
+
+  public Tracks() { }
+
+  public final void add(BaseTrack track) { super.addUnique(track); }
+
+  public final void computeSamples(float[] timeSteps)
+  {
+    final int num = size();
+    for (int i = 0; i < num; i++) {
+      get(i).computeSamples(timeSteps);
+    }
+  }
+
+  public final BaseTrack get(int i)
+  {
+    return (BaseTrack )super.internalGet(i);
+  }
+
+  public final Data makeData()
+    throws RemoteException, VisADException
+  {
+    final int num = size();
+
+    Integer1DSet set = new Integer1DSet(BaseTrack.indexType,
+                                        (num == 0 ? 1 : num));
+    FieldImpl fld = new FieldImpl(functionType, set);
+    if (num > 0) {
+      FlatField[] flds = new FlatField[num];
+      for (int i = 0; i < num; i++) {
+        flds[i] = get(i).makeData();
+      }
+      try {
+        fld.setSamples(flds, true);
+      } catch (RemoteException re) {
+        re.printStackTrace();
+      }
+    }
+
+    return fld;
+  }
+}
diff --git a/visad/data/avi/AVIForm.java b/visad/data/avi/AVIForm.java
new file mode 100644
index 0000000..dabdace
--- /dev/null
+++ b/visad/data/avi/AVIForm.java
@@ -0,0 +1,45 @@
+//
+// AVIForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data. Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.avi;
+
+/**
+ * AVIForm is the VisAD data adapter for uncompressed AVI movies.
+ *
+ * @deprecated Use visad.data.bio.LociForm with
+ *   loci.formats.in.AVIReader and loci.formats.out.AVIWriter
+ */
+public class AVIForm extends visad.data.bio.LociForm {
+
+  public AVIForm() {
+    super(new loci.formats.in.AVIReader(), new loci.formats.out.AVIWriter());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new AVIForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/BioRadForm.java b/visad/data/bio/BioRadForm.java
new file mode 100644
index 0000000..ce30bab
--- /dev/null
+++ b/visad/data/bio/BioRadForm.java
@@ -0,0 +1,44 @@
+//
+// BioRadForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * BioRadForm is the VisAD data adapter for Bio-Rad PIC files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.BioRadReader
+ */
+public class BioRadForm extends LociForm {
+
+  public BioRadForm() {
+    super(new loci.formats.in.BioRadReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new BioRadForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/DeltavisionForm.java b/visad/data/bio/DeltavisionForm.java
new file mode 100644
index 0000000..e010e45
--- /dev/null
+++ b/visad/data/bio/DeltavisionForm.java
@@ -0,0 +1,44 @@
+//
+// DeltavisionForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * DeltavisionForm is the VisAD data adapter for Deltavision DV files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.DeltavisionReader
+ */
+public class DeltavisionForm extends LociForm {
+
+  public DeltavisionForm() {
+    super(new loci.formats.in.DeltavisionReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new DeltavisionForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/FluoviewTiffForm.java b/visad/data/bio/FluoviewTiffForm.java
new file mode 100644
index 0000000..a3b68dc
--- /dev/null
+++ b/visad/data/bio/FluoviewTiffForm.java
@@ -0,0 +1,45 @@
+//
+// FluoviewTiffForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * FluoviewTiffForm is the VisAD data adapter
+ * for Olympus Fluoview TIFF files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.FluoviewReader
+ */
+public class FluoviewTiffForm extends LociForm {
+
+  public FluoviewTiffForm() {
+    super(new loci.formats.in.FluoviewReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new FluoviewTiffForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/GatanForm.java b/visad/data/bio/GatanForm.java
new file mode 100644
index 0000000..5c2efb8
--- /dev/null
+++ b/visad/data/bio/GatanForm.java
@@ -0,0 +1,44 @@
+//
+// GatanForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * GatanForm is the VisAD data adapter for Gatan Digital Micrograph files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.GatanReader
+ */
+public class GatanForm extends LociForm {
+
+  public GatanForm() {
+    super(new loci.formats.in.GatanReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new GatanForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/ICSForm.java b/visad/data/bio/ICSForm.java
new file mode 100644
index 0000000..aa85049
--- /dev/null
+++ b/visad/data/bio/ICSForm.java
@@ -0,0 +1,44 @@
+//
+// ICSForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * ICSForm is the VisAD data adapter for Image Cytometry Standard files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.ICSReader
+ */
+public class ICSForm extends LociForm {
+
+  public ICSForm() {
+    super(new loci.formats.in.ICSReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new ICSForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/IPLabForm.java b/visad/data/bio/IPLabForm.java
new file mode 100644
index 0000000..243cce4
--- /dev/null
+++ b/visad/data/bio/IPLabForm.java
@@ -0,0 +1,44 @@
+//
+// IPLabForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * IPLabForm is the VisAD data adapter for IPLab files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.IPLabReader
+ */
+public class IPLabForm extends LociForm {
+
+  public IPLabForm() {
+    super(new loci.formats.in.IPLabReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new IPLabForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/IPWForm.java b/visad/data/bio/IPWForm.java
new file mode 100644
index 0000000..8a9b98f
--- /dev/null
+++ b/visad/data/bio/IPWForm.java
@@ -0,0 +1,44 @@
+//
+// IPWForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * IPWForm is the VisAD data adapter for Image-Pro workspace (IPW) files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.IPWReader
+ */
+public class IPWForm extends LociForm {
+
+  public IPWForm() {
+    super(new loci.formats.in.IPWReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new IPWForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/ImageProSeqForm.java b/visad/data/bio/ImageProSeqForm.java
new file mode 100644
index 0000000..b30fc64
--- /dev/null
+++ b/visad/data/bio/ImageProSeqForm.java
@@ -0,0 +1,45 @@
+//
+// ImageProSeqForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * ImageProSeqForm is the VisAD data adapter
+ * for Image-Pro sequence (SEQ) files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.SEQReader
+ */
+public class ImageProSeqForm extends LociForm {
+
+  public ImageProSeqForm() {
+    super(new loci.formats.in.SEQReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new ImageProSeqForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/LegacyZVIForm.java b/visad/data/bio/LegacyZVIForm.java
new file mode 100644
index 0000000..820b7b0
--- /dev/null
+++ b/visad/data/bio/LegacyZVIForm.java
@@ -0,0 +1,44 @@
+//
+// LegacyZVIForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * LegacyZVIForm is a VisAD data adapter for Zeiss ZVI files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.LegacyZVIReader
+ */
+public class LegacyZVIForm extends LociForm {
+
+  public LegacyZVIForm() {
+    super(new loci.formats.in.LegacyZVIReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new LegacyZVIForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/LeicaForm.java b/visad/data/bio/LeicaForm.java
new file mode 100644
index 0000000..1e4504e
--- /dev/null
+++ b/visad/data/bio/LeicaForm.java
@@ -0,0 +1,44 @@
+//
+// LeicaForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * LeicaForm is the VisAD data adapter for Leica files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.LeicaReader
+ */
+public class LeicaForm extends LociForm {
+
+  public LeicaForm() {
+    super(new loci.formats.in.LeicaReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new LeicaForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/LociForm.java b/visad/data/bio/LociForm.java
new file mode 100644
index 0000000..414efd0
--- /dev/null
+++ b/visad/data/bio/LociForm.java
@@ -0,0 +1,446 @@
+//
+// LociForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+import java.awt.Image;
+import java.awt.BorderLayout;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.net.URL;
+import java.util.Hashtable;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.filechooser.FileFilter;
+import loci.formats.*;
+import loci.formats.gui.GUITools;
+import visad.*;
+import visad.data.*;
+import visad.java2d.DisplayImplJ2D;
+import visad.util.*;
+
+/**
+ * LociForm is the VisAD data adapter for images handled by the Bio-Formats
+ * package (loci.formats). It works by wrapping a loci.formats.IFormatReader
+ * and/or loci.formats.IFormatWriter object.
+ */
+public class LociForm extends Form implements FormBlockReader,
+  FormFileInformer, FormProgressInformer, MetadataReader
+{
+
+  // -- Static fields --
+
+  private static int formCount = 0;
+
+
+  // -- Fields --
+
+  /** Reader to use for open-related functions. */
+  protected IFormatReader reader;
+
+  /** Writer to use for save-related functions. */
+  protected IFormatWriter writer;
+
+  /** Percent complete for current operation. */
+  protected double percent;
+
+  /** File filters for reader formats. */
+  protected FileFilter[] rFilters;
+
+  /** File filters for writer formats. */
+  protected FileFilter[] wFilters;
+
+
+  // -- Constructor --
+
+  /** Constructs a new LociForm that handles anything from loci.formats. */
+  public LociForm() {
+    this(new ImageReader(), new ImageWriter());
+  }
+
+  /** Constructs a new LociForm that handles the given reader. */
+  public LociForm(IFormatReader reader) {
+    this(reader, null);
+  }
+
+  /** Constructs a new LociForm that handles the given writer. */
+  public LociForm(IFormatWriter writer) {
+    this(null, writer);
+  }
+
+  /** Constructs a new LociForm that handles the given reader/writer pair. */
+  public LociForm(IFormatReader reader, IFormatWriter writer) {
+    super("LociForm" + formCount++);
+    this.reader = reader;
+    this.writer = writer;
+  }
+
+
+  // -- LociForm API methods --
+
+  /** Gets the IFormatReader backing this form's reading capabilities. */
+  public IFormatReader getReader() { return reader; }
+
+  /** Gets the IFormatWriter backing this form's writing capabilities. */
+  public IFormatWriter getWriter() { return writer; }
+
+  /** Sets the frames per second to use when writing files. */
+  public void setFrameRate(int fps) {
+    if (writer == null) return;
+    writer.setFramesPerSecond(fps);
+  }
+
+  /**
+   * A utility method for test reading a file from the command line,
+   * and displaying the results in a simple display.
+   */
+  public void testRead(String[] args) throws VisADException, IOException {
+    if (reader == null) return;
+    String className = getClass().getName();
+    String format = reader.getFormat();
+    if (args == null || args.length < 1) {
+      System.out.println("To test read a file in " + format + " format, run:");
+      System.out.println("  java " + className + " in_file");
+      return;
+    }
+    String id = args[0];
+
+    // check type
+    System.out.print("Checking " + format + " format ");
+    System.out.println(isThisType(id) ? "[yes]" : "[no]");
+
+    // read pixels
+    System.out.print("Reading " + id + " pixel data ");
+    Data data = open(args[0]);
+    System.out.println("[done]");
+    System.out.println("MathType =\n" + data.getType());
+
+    // extract types
+    FunctionType ftype = (FunctionType) data.getType();
+    RealTupleType domain = ftype.getDomain();
+    RealType[] xy = domain.getRealComponents();
+    RealType time = null;
+    if (xy.length == 1) {
+      // assume multiple images over time
+      time = xy[0];
+      ftype = (FunctionType) ftype.getRange();
+      domain = ftype.getDomain();
+      xy = domain.getRealComponents();
+    }
+    MathType range = ftype.getRange();
+    RealType[] values = range instanceof RealType ?
+      new RealType[] {(RealType) range} :
+      ((RealTupleType) range).getRealComponents();
+
+    // configure display
+    DisplayImpl display = new DisplayImplJ2D("display");
+    ScalarMap timeMap = null;
+    if (time != null) {
+      timeMap = new ScalarMap(time, Display.Animation);
+      display.addMap(timeMap);
+    }
+    display.addMap(new ScalarMap(xy[0], Display.XAxis));
+    display.addMap(new ScalarMap(xy[1], Display.YAxis));
+    ScalarMap colorMap = null;
+    if (values.length == 2 || values.length == 3) {
+      // do separate mappings to Red, Green and Blue
+      display.addMap(new ScalarMap(values[0], Display.Red));
+      display.addMap(new ScalarMap(values[1], Display.Green));
+      if (values.length == 3) {
+        display.addMap(new ScalarMap(values[2], Display.Blue));
+      }
+    }
+    else {
+      // use the first component only
+      colorMap = new ScalarMap(values[0], Display.RGB);
+      display.addMap(colorMap);
+    }
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(data);
+    display.addReference(ref);
+    display.getGraphicsModeControl().setScaleEnable(true);
+
+    // pop up frame
+    JFrame frame = new JFrame(format + " Results");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    JPanel p = new JPanel();
+    frame.setContentPane(p);
+    p.setLayout(new BorderLayout());
+    p.add(display.getComponent());
+    if (timeMap != null) {
+      AnimationWidget aw = new AnimationWidget(timeMap);
+      p.add(BorderLayout.SOUTH, aw);
+    }
+    if (colorMap != null) {
+      RangeWidget rw = new RangeWidget(colorMap);
+      LabeledColorWidget lcw = new LabeledColorWidget(colorMap);
+      p.add(BorderLayout.NORTH, rw);
+      p.add(BorderLayout.EAST, lcw);
+    }
+    frame.pack();
+    frame.setLocation(300, 300);
+    frame.setVisible(true);
+  }
+
+  /** Gets file filters for use with formats supported for reading. */
+  public FileFilter[] getReaderFilters() {
+    if (reader == null) return null;
+    if (rFilters == null) rFilters = GUITools.buildFileFilters(reader);
+    return rFilters;
+  }
+
+  /** Gets file filters for use with formats supported for writing. */
+  public FileFilter[] getWriterFilters() {
+    if (writer == null) return null;
+    if (wFilters == null) wFilters = GUITools.buildFileFilters(writer);
+    return wFilters;
+  }
+
+
+  // -- FormNode API methods --
+
+  /**
+   * Opens an existing image file from the given filename.
+   *
+   * @return VisAD Data object containing image data
+   */
+  public DataImpl open(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    percent = 0;
+    int nImages = getBlockCount(id);
+    FieldImpl[] fields = new FieldImpl[nImages];
+    for (int i=0; i<nImages; i++) {
+      fields[i] = (FieldImpl) open(id, i);
+      percent = (double) (i+1) / nImages;
+    }
+
+    DataImpl data;
+    if (nImages == 1) data = fields[0];
+    else {
+      // combine data stack into index function
+      RealType index = RealType.getRealType("index");
+      FunctionType indexFunction =
+        new FunctionType(index, fields[0].getType());
+      Integer1DSet indexSet = new Integer1DSet(nImages);
+      FieldImpl indexField = new FieldImpl(indexFunction, indexSet);
+      indexField.setSamples(fields, false);
+      data = indexField;
+    }
+    close();
+    percent = Double.NaN;
+    return data;
+  }
+
+  /** Saves a VisAD Data object at the given location. */
+  public void save(String id, Data data, boolean replace)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    if (writer == null) throw new BadFormException("No writer");
+    percent = 0;
+    FlatField[] fields = DataUtility.getImageFields(data);
+    try {
+      initHandler(writer, id);
+      for (int i=0; i<fields.length; i++) {
+        Image image;
+        if (fields[i] instanceof ImageFlatField) {
+          image = ((ImageFlatField) fields[i]).getImage();
+        }
+        else image = DataUtility.extractImage(fields[i], false);
+        writer.saveImage(image, i == fields.length - 1);
+        percent = (double) (i+1) / fields.length;
+      }
+    }
+    catch (FormatException exc) { throw new BadFormException(exc); }
+    percent = Double.NaN;
+  }
+
+  /**
+   * Adds data to an existing image file.
+   *
+   * @exception BadFormException Always thrown (this method not
+   * implemented).
+   */
+  public void add(String id, Data data, boolean replace)
+    throws BadFormException
+  {
+    throw new BadFormException("LociForm.add");
+  }
+
+  /**
+   * Opens an existing image file from the given URL.
+   *
+   * @return VisAD data object containing image data
+   * @exception UnimplementedException Always thrown (this method not
+   * implemented).
+   */
+  public DataImpl open(URL url)
+    throws BadFormException, IOException, VisADException
+  {
+    throw new UnimplementedException("LociForm.open(URL)");
+  }
+
+  /** Returns the data forms that are compatible with a data object. */
+  public FormNode getForms(Data data) {
+    return null;
+  }
+
+
+  // -- FormBlockReader API methods --
+
+  /** Obtains the specified image from the given image file. */
+  public DataImpl open(String id, int block_number)
+    throws BadFormException, IOException, VisADException
+  {
+    if (reader == null) throw new BadFormException("No reader");
+    BufferedImage image;
+    try {
+      initHandler(reader, id);
+      image = reader.openImage(block_number);
+    }
+    catch (FormatException exc) { throw new BadFormException(exc); }
+    int width = image.getWidth(), height = image.getHeight();
+    int num = image.getRaster().getNumBands();
+
+    RealType x = RealType.getRealType("ImageElement");
+    RealType y = RealType.getRealType("ImageLine");
+    RealType[] v = new RealType[num];
+    for (int i=0; i<num; i++) v[i] = RealType.getRealType("value" + i);
+    RealTupleType domain = new RealTupleType(x, y);
+    RealTupleType range = new RealTupleType(v);
+    FunctionType fieldType = new FunctionType(domain, range);
+    Linear2DSet fieldSet = new Linear2DSet(domain, 0,
+      width - 1, width, height - 1, 0, height);
+    ImageFlatField field = new ImageFlatField(fieldType, fieldSet);
+    field.setImage(image);
+
+    return field;
+  }
+
+  /** Determines the number of images in the given image file. */
+  public int getBlockCount(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    if (reader == null) throw new BadFormException("No reader");
+    initHandler(reader, id);
+    return reader.getImageCount();
+  }
+
+  /** Closes any open files. */
+  public void close() throws BadFormException, IOException, VisADException {
+    if (reader == null) throw new BadFormException("No reader");
+    reader.close();
+  }
+
+
+  // -- FormFileInformer API methods --
+
+  /** Checks if the given string is a valid filename for an image file. */
+  public boolean isThisType(String name) {
+    if (reader == null) return false;
+    return reader.isThisType(name);
+  }
+
+  /** Checks if the given block is a valid header for an image file. */
+  public boolean isThisType(byte[] block) {
+    if (reader == null) return false;
+    return reader.isThisType(block);
+  }
+
+  /** Returns the default file suffixes for this file format. */
+  public String[] getDefaultSuffixes() {
+    if (reader != null) return reader.getSuffixes();
+    if (writer != null) return writer.getSuffixes();
+    return null;
+  }
+
+
+  // -- FormProgressInformer API methods --
+
+  /** Gets the percentage complete of the form's current operation. */
+  public double getPercentComplete() {
+    return percent;
+  }
+
+
+  // -- MetadataReader API methods --
+
+  /**
+   * Obtains the specified metadata field's value for the given file.
+   *
+   * @param field the name associated with the metadata field
+   * @return the value, or null if the field doesn't exist
+   */
+  public Object getMetadataValue(String id, String field)
+    throws BadFormException, IOException, VisADException
+  {
+    if (reader == null) throw new BadFormException("No reader");
+    initHandler(reader, id);
+    return reader.getMetadataValue(field);
+  }
+
+  /**
+   * Obtains the hashtable containing the metadata field/value pairs from
+   * the given image file.
+   *
+   * @param id the filename
+   * @return the hashtable containing all metadata from the file
+   */
+  public Hashtable getMetadata(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    if (reader == null) throw new BadFormException("No reader");
+    initHandler(reader, id);
+    return reader.getMetadata();
+  }
+
+
+  // -- Main method --
+
+  public static void main(String[] args) throws Exception {
+    new LociForm().testRead(args);
+  }
+
+
+  // -- Helper methods --
+
+  public void initHandler(IFormatHandler h, String id)
+    throws BadFormException, IOException
+  {
+    try {
+      h.setId(id);
+    }
+    catch (FormatException exc) { throw new BadFormException(exc); }
+  }
+
+}
diff --git a/visad/data/bio/MetamorphForm.java b/visad/data/bio/MetamorphForm.java
new file mode 100644
index 0000000..6bb4d6a
--- /dev/null
+++ b/visad/data/bio/MetamorphForm.java
@@ -0,0 +1,44 @@
+//
+// MetamorphForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * MetamorphForm is the VisAD data adapter for Metamorph STK files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.MetamorphReader
+ */
+public class MetamorphForm extends LociForm {
+
+  public MetamorphForm() {
+    super(new loci.formats.in.MetamorphReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new MetamorphForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/OMEReader.java b/visad/data/bio/OMEReader.java
new file mode 100644
index 0000000..4496a16
--- /dev/null
+++ b/visad/data/bio/OMEReader.java
@@ -0,0 +1,48 @@
+//
+// OMEReader.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+import java.io.IOException;
+import visad.VisADException;
+import visad.data.BadFormException;
+
+/**
+ * OMEReader is the interface for reading in a file's associated
+ * metadata in OME-XML format as a loci.ome.xml.OMENode object.
+ */
+public interface OMEReader {
+
+  /**
+   * Obtains a loci.ome.xml.OMENode object representing the
+   * file's metadata as an OME-XML DOM structure.
+   *
+   * @throws BadFormException if the loci.ome.xml package is not present
+   */
+  Object getOMENode(String id)
+    throws BadFormException, IOException, VisADException;
+
+}
diff --git a/visad/data/bio/OpenlabForm.java b/visad/data/bio/OpenlabForm.java
new file mode 100644
index 0000000..347fff1
--- /dev/null
+++ b/visad/data/bio/OpenlabForm.java
@@ -0,0 +1,44 @@
+//
+// OpenlabForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * OpenlabForm is the VisAD data adapter for Openlab LIFF files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.OpenlabReader
+ */
+public class OpenlabForm extends LociForm {
+
+  public OpenlabForm() {
+    super(new loci.formats.in.OpenlabReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new OpenlabForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/PerkinElmerForm.java b/visad/data/bio/PerkinElmerForm.java
new file mode 100644
index 0000000..d49046a
--- /dev/null
+++ b/visad/data/bio/PerkinElmerForm.java
@@ -0,0 +1,44 @@
+//
+// PerkinElmerForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * PerkinElmerForm is the VisAD data adapter for PerkinElmer files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.PerkinElmerReader
+ */
+public class PerkinElmerForm extends LociForm {
+
+  public PerkinElmerForm() {
+    super(new loci.formats.in.PerkinElmerReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new PerkinElmerForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/ZVIForm.java b/visad/data/bio/ZVIForm.java
new file mode 100644
index 0000000..9706fae
--- /dev/null
+++ b/visad/data/bio/ZVIForm.java
@@ -0,0 +1,44 @@
+//
+// ZVIForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * ZVIForm is a VisAD data adapter for Zeiss ZVI files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.ZeissZVIReader
+ */
+public class ZVIForm extends LociForm {
+
+  public ZVIForm() {
+    super(new loci.formats.in.ZeissZVIReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new ZVIForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/ZeissForm.java b/visad/data/bio/ZeissForm.java
new file mode 100644
index 0000000..540a357
--- /dev/null
+++ b/visad/data/bio/ZeissForm.java
@@ -0,0 +1,44 @@
+//
+// ZeissForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.bio;
+
+/**
+ * ZeissForm is the VisAD data adapter for Zeiss LSM files.
+ *
+ * @deprecated Use LociForm with loci.formats.in.ZeissLSMReader
+ */
+public class ZeissForm extends LociForm {
+
+  public ZeissForm() {
+    super(new loci.formats.in.ZeissLSMReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new ZeissForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/bio/package.html b/visad/data/bio/package.html
new file mode 100644
index 0000000..08b349b
--- /dev/null
+++ b/visad/data/bio/package.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html><body>
+Provides data forms for handling common microscopy formats.
+</body></html>
diff --git a/visad/data/biorad/BioRadForm.java b/visad/data/biorad/BioRadForm.java
new file mode 100644
index 0000000..e7a9e80
--- /dev/null
+++ b/visad/data/biorad/BioRadForm.java
@@ -0,0 +1,34 @@
+//
+// BioRadForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.biorad;
+
+/**
+ * BioRadForm is the VisAD data format adapter for Bio-Rad .PIC files.
+ *
+ * @deprecated BioRadForm has moved to the visad.data.bio package.
+ */
+public class BioRadForm extends visad.data.bio.BioRadForm { }
diff --git a/visad/data/dods/Adapter.java b/visad/data/dods/Adapter.java
new file mode 100644
index 0000000..feae309
--- /dev/null
+++ b/visad/data/dods/Adapter.java
@@ -0,0 +1,271 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+import visad.data.*;
+import visad.data.units.Parser;
+
+/**
+ * Provides support for adapting DODS objects to the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class Adapter
+{
+    private static final CacheStrategy	cacheStrategy = new CacheStrategy();
+
+    /**
+     * Returns the VisAD scalar-name equivalent to a DODS name.
+     *
+     * @param name		The DODS name.
+     * @return			The VisAD scalar-name equivalent to the DODS
+     *				name.
+     * @see ScalarType
+     */
+    protected static String scalarName(String name)
+    {
+	return name
+	    .replace('.', '-')
+	    .replace(' ', '_')
+	    .replace('(', '<')
+	    .replace(')', '>');
+    }
+
+    /**
+     * Indicates if a given VisAD {@link MathType} is "flat" (i.e. comprises a
+     * {@link Real}, a {@link RealTuple}, or a {@link Tuple} of {@link Real}s
+     * and {@link RealTuple}s.
+     *
+     * @param mathType		The VisAD mathtype to be investigated.
+     * @return			<code>true</code> if and only if the given
+     *				mathtype is "flat".
+     */
+    protected static boolean isFlat(MathType mathType)
+    {
+	return
+	    mathType instanceof RealType ||
+	    mathType instanceof RealTupleType ||
+	    (mathType instanceof TupleType && ((TupleType)mathType).getFlat());
+    }
+
+    /**
+     * Returns the VisAD {@link RealType} corresponding to a DODS variable.
+     *
+     * @param variable		The DODS variable.  Must be one for which a 
+     *				RealType is possible.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The VisAD RealType corresponding to the
+     *				variable and attribute table.
+     */
+    protected static RealType realType(BaseType variable, DAS das)
+    {
+	return realType(variable.getName(), attributeTable(das, variable));
+    }
+
+    /**
+     * Returns the VisAD {@link RealType} corresponding to a DODS variable.
+     *
+     * @param variable		The DODS variable.  Must be one for which a 
+     *				RealType is possible.
+     * @param table		The DODS attribute table for the variable.
+     *				May be <code>null</code>.
+     * @return			The VisAD RealType corresponding to the
+     *				variable and attribute table.
+     */
+    protected static RealType realType(BaseType variable, AttributeTable table)
+    {
+	return realType(variable.getName(), table);
+    }
+
+    /**
+     * Returns the VisAD {@link RealType} corresponding to a name.
+     *
+     * @param name		The name.
+     * @param das		The DODS DAS in which the information on the
+     *				name is embedded.
+     * @return			The VisAD RealType corresponding to the
+     *				name and metadata.
+     */
+    protected static RealType realType(String name, DAS das)
+    {
+	return realType(name, attributeTable(das, name));
+    }
+
+    /**
+     * Returns the VisAD {@link RealType} corresponding to a name.
+     *
+     * @param name		The name.
+     * @param table		The DODS attribute table for the name.
+     *				May be <code>null</code>.
+     * @return			The VisAD RealType corresponding to the
+     *				name and attribute table.
+     */
+    protected static RealType realType(String name, AttributeTable table)
+    {
+	Unit	unit;
+	if (table == null)
+	{
+	    unit = null;
+	}
+	else
+	{
+	    Attribute	attr = table.getAttribute("units");
+	    if (attr == null)
+	    {
+		attr = table.getAttribute("unit");
+		if (attr == null)
+		{
+		    attr = table.getAttribute("UNITS");
+		    if (attr == null)
+			attr = table.getAttribute("UNIT");
+		}
+	    }
+	    if (attr == null)
+	    {
+		unit = null;
+	    }
+	    else
+	    {
+		if (attr.getType() != Attribute.STRING)
+		{
+		    unit = null;
+		}
+		else
+		{
+		    String	unitSpec = attr.getValueAt(0);
+		    /*
+		     * Remove extraneous quotes.
+		     */
+		    if (unitSpec.startsWith("\"") && unitSpec.endsWith("\""))
+			unitSpec = unitSpec.substring(1, unitSpec.length()-1);
+		    try
+		    {
+			unit = Parser.instance().parse(unitSpec);
+		    }
+		    catch (Exception e)
+		    {
+			System.err.println(
+			    "visad.data.dods.Adapter.realType(String,...): " +
+			    "Ignoring non-decodable unit-specification \"" +
+			    unitSpec + "\" of variable \"" + name + "\"");
+			unit = null;
+		    }
+		}
+	    }
+	}
+	return RealType.getRealType(scalarName(name), unit);
+    }
+
+    /**
+     * Returns the attribute table corresponding to a DODS variable.
+     *
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @param baseType		The type of the sub-component.  May not be
+     *				<code>null</code>.
+     * @return			The attribute table corresponding to the
+     *				variable.  Will be <code>null</code> if no such
+     *				table exists.
+     */
+    protected static AttributeTable attributeTable(DAS das, BaseType baseType)
+    {
+	return das.getAttributeTable(baseType.getName());
+    }
+
+    /**
+     * Returns the attribute table corresponding to a name.
+     *
+     * @param das		The DODS DAS in which information on the name
+     *				is embedded.
+     * @param name		The name to lookup in the DAS.
+     * @return			The attribute table corresponding to the
+     *				name.  Will be <code>null</code> if no such
+     *				table exists.
+     */
+    protected static AttributeTable attributeTable(DAS das, String name)
+    {
+	  return das.getAttributeTable(name);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} corresponding to an array of 
+     * MathTypes.
+     *
+     * @param mathTypes		The array of mathTypes.
+     * @return			The MathType of the input aggregate.  Will be
+     *				<code>null</code> if the array has zero length.
+     *				Will be the first element of a one-element
+     *				array.  Will be either a {@link RealTuple} or a
+     *				{@link Tuple} -- as appropriate -- for a
+     *				multi-element array.
+     * @throws VisADException	VisAD failure.
+     */
+    protected static MathType mathType(MathType[] mathTypes)
+	throws VisADException
+    {
+	MathType	mathType;
+	if (mathTypes.length == 0)
+	{
+	    mathType = null;
+	}
+	else if (mathTypes.length == 1)
+	{
+	    mathType = mathTypes[0];
+	}
+	else
+	{
+	    boolean	allReals = true;
+	    for (int i = 0; i < mathTypes.length && allReals; ++i)
+		allReals &= mathTypes[i] instanceof RealType;
+	    if (allReals)
+	    {
+		RealType[]	realTypes = new RealType[mathTypes.length];
+		for (int i = 0; i < realTypes.length; ++i)
+		    realTypes[i] = (RealType)mathTypes[i];
+		mathType = new RealTupleType(realTypes);
+	    }
+	    else
+	    {
+		mathType = new TupleType(mathTypes);
+	    }
+	}
+	return mathType;
+    }
+
+    /**
+     * Returns the {@link visad.data.FileFlatField} cacheing strategy for DODS
+     * adapters.  This may be used by DODS adapters during the creation of
+     * FileFlatField-s.
+     *
+     * @return			The FileFlatField cacheing strategy.
+     */
+    protected CacheStrategy getCacheStrategy()
+    {
+	return cacheStrategy;
+    }
+}
diff --git a/visad/data/dods/ArrayVariableAdapter.java b/visad/data/dods/ArrayVariableAdapter.java
new file mode 100644
index 0000000..5c8817f
--- /dev/null
+++ b/visad/data/dods/ArrayVariableAdapter.java
@@ -0,0 +1,238 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import dods.dap.Server.InvalidParameterException;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.*;
+
+/**
+ * Provides support for adapting DODS {@link DArray} variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class ArrayVariableAdapter
+    extends	VariableAdapter
+{
+    private final FunctionType		funcType;
+    private final VectorAdapter		vectorAdapter;
+
+    private ArrayVariableAdapter(
+	    DArray array,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	vectorAdapter = factory.vectorAdapter(array.getPrimitiveVector(), das);
+	int		rank = array.numDimensions();
+	RealType[]	realTypes = new RealType[rank];
+	for (int i = 0; i < rank; ++i)
+	{
+	    try
+	    {
+		/*
+		 * The following reverses the dimension order to conform to
+		 * the VisAD convention of innermost first.
+		 */
+		realTypes[rank-1-i] =
+		    RealType.getRealType(
+			array.getDimension(i).getName() + "_ndx");
+	    }
+	    catch (InvalidParameterException e)
+	    {
+		throw new BadFormException(
+		    getClass().getName() + ".<init>: " +
+		    "Couldn't get DArray dimension: " + e);
+	    }
+	}
+	funcType =
+	    new FunctionType(mathType(realTypes), vectorAdapter.getMathType());
+    }
+
+    /**
+     * Returns an instance corresponding to a DODS {@link DArray}.
+     *
+     * @param array		The DODS DArray.  Only the DODS metadata is 
+     *				used: the array needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @param factory		A factory for creating variable adapters.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static ArrayVariableAdapter arrayVariableAdapter(
+	    DArray array, DAS das, VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new ArrayVariableAdapter(array, das, factory);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return funcType;
+    }
+
+    /**
+     * Returns the VisAD {@link FunctionType} of this instance.
+     *
+     * @return			The FunctionType of this instance.
+     */
+    public FunctionType getFunctionType()
+    {
+	return funcType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent the data
+     * values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is copied.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return vectorAdapter.getRepresentationalSets(copy);
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DArray}.
+     *
+     * @param array		The DODS {@link DArray} to have the 
+     *				corresponding VisAD data object returned.
+     *				The array must be compatible with the array
+     *				used to construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				object will be a {@link FieldImpl} and may be
+     *				a {@link FlatField} or {@link 
+     *				visad.data.FileFlatField}.
+     * @throws VisADException	VisAD failure.  Possible the array wasn't
+     *				compatible with the array used to construct this
+     *				instance.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(DArray array, boolean copy)
+	throws VisADException, RemoteException
+    {
+	RealTupleType	domainType = funcType.getDomain();
+	int		rank = domainType.getDimension();
+	int[]		firsts = new int[rank];
+	int[]		lasts = new int[rank];
+	int[]		lengths = new int[rank];
+	boolean		allIntegerSets = true;
+	for (int i = 0; i < rank; ++i)
+	{
+	    int			j = rank - 1 - i;	// reverse dimensions
+	    DArrayDimension	dim;
+	    try
+	    {
+		dim = array.getDimension(i);
+	    }
+	    catch (InvalidParameterException e)
+	    {
+		throw new BadFormException(
+		    getClass().getName() + ".data(DArray,...): " +
+		    "Couldn't get DArray dimension: " + e);
+	    }
+	    int			first = dim.getStart();
+	    int			last = dim.getStop();
+	    int			stride = dim.getStride();
+	    firsts[j] = first;
+	    lasts[j] = last;
+	    lengths[j] = 1 + (last - first) / stride;
+	    allIntegerSets &=
+		((stride == 1 && first == 0) || (stride == -1 && last == 0));
+	}
+	SampledSet	domain =
+	    allIntegerSets
+		? (SampledSet)IntegerNDSet.create(domainType, lengths)
+		: (SampledSet)LinearNDSet.create(
+		    domainType, doubleArray(firsts), doubleArray(lasts),
+		    lengths);
+	PrimitiveVector	vector = array.getPrimitiveVector();
+	FieldImpl	field;
+	if (vectorAdapter.isFlat())
+	{
+	    /*
+	     * TODO: Either modify FileFlatField or subclass it to support
+	     * a domainFactor(...) method that uses FileFlatField-s.
+	     */
+	    field =
+		new FileFlatField(
+		    new VectorAccessor(funcType, vectorAdapter, domain, vector),
+		    getCacheStrategy());
+	}
+	else
+	{
+	    field = new FieldImpl(funcType, domain);
+	    vectorAdapter.setField(vector, field, copy);
+	}
+	return field;
+    }
+
+    /**
+     * Sets a compatible VisAD {@link Field}.  This method is used by {@link
+     * GridVariableAdapter} for the DArray portion of a DODS DGrid.
+     *
+     * @param array		The DODS {@link DArray} to be used to set a
+     *				compatible VisAD Field.  The array must be
+     *				compatible with the array used to construct this
+     *				instance.
+     * @param copy		If true, then the data values are copied.
+     * @param field		The VisAD Field to be set.  The field must be
+     *				compatible with the array.
+     */
+    public void setField(DArray array, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException
+    {
+	vectorAdapter.setField(array.getPrimitiveVector(), field, copy);
+    }
+
+    /**
+     * Returns an array of doubles corresponding to an array of ints.
+     *
+     * @param ints		The array of doubles.
+     * @return			The corresponding array of doubles.
+     */
+    private double[] doubleArray(int[] ints)
+    {
+	double[]	doubles = new double[ints.length];
+	for (int i = 0; i < ints.length; ++i)
+	    doubles[i] = ints[i];
+	return doubles;
+    }
+}
diff --git a/visad/data/dods/AttributeAdapter.java b/visad/data/dods/AttributeAdapter.java
new file mode 100644
index 0000000..2441eb6
--- /dev/null
+++ b/visad/data/dods/AttributeAdapter.java
@@ -0,0 +1,47 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS attribute to the VisAD data-import
+ * context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class AttributeAdapter
+    extends	Adapter
+{
+    /**
+     * Returns the VisAD data object corresponding to this instance.
+     *
+     * @param copy		If true, then a copy of the data object is
+     *				returned.
+     * @return			The VisAD data object corresponding to this
+     *				instance.
+     */
+    public abstract DataImpl data(boolean copy);
+}
diff --git a/visad/data/dods/AttributeAdapterFactory.java b/visad/data/dods/AttributeAdapterFactory.java
new file mode 100644
index 0000000..1c00faf
--- /dev/null
+++ b/visad/data/dods/AttributeAdapterFactory.java
@@ -0,0 +1,252 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for creating adapters that bridge between DODS attributes
+ * and the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class AttributeAdapterFactory
+{
+    private static final AttributeAdapterFactory	instance =
+	new AttributeAdapterFactory();
+
+    /**
+     * Constructs from nothing.
+     */
+    protected AttributeAdapterFactory()
+    {}
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static AttributeAdapterFactory attributeAdapterFactory()
+    {
+	return instance;
+    }
+
+    /**
+     * Returns an adapter of a DODS attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.  The class of
+     *				the object depends on the attribute.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public AttributeAdapter attributeAdapter(String name, Attribute attr)
+	throws BadFormException, VisADException, RemoteException
+    {
+	AttributeAdapter	adapter;
+	int			type = attr.getType();
+	if (type == Attribute.STRING)
+	    adapter = stringAdapter(name, attr);
+	else if (type == Attribute.BYTE)
+	    adapter = byteAdapter(name, attr);
+	else if (type == Attribute.INT16)
+	    adapter = int16Adapter(name, attr);
+	else if (type == Attribute.UINT16)
+	    adapter = uInt16Adapter(name, attr);
+	else if (type == Attribute.INT32)
+	    adapter = int32Adapter(name, attr);
+	else if (type == Attribute.UINT32)
+	    adapter = uInt32Adapter(name, attr);
+	else if (type == Attribute.FLOAT32)
+	    adapter = float32Adapter(name, attr);
+	else if (type == Attribute.FLOAT64)
+	    adapter = float64Adapter(name, attr);
+	else if (type == Attribute.CONTAINER)
+	    adapter = containerAdapter(name, attr);
+	else if (type == Attribute.UNKNOWN)
+	    adapter = unknownAdapter(name, attr);
+	else
+	    throw new BadFormException(
+		getClass().getName() + ".attributeAdapter(): " +
+		"Unknown DODS attribute type: " + attr.getTypeString());
+	return adapter;
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#STRING} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public StringAttributeAdapter stringAdapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new StringAttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#BYTE} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ByteAttributeAdapter byteAdapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new ByteAttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#INT16} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int16AttributeAdapter int16Adapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new Int16AttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#UINT16} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt16AttributeAdapter uInt16Adapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new UInt16AttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#INT32} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int32AttributeAdapter int32Adapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new Int32AttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#UINT32} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt32AttributeAdapter uInt32Adapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new UInt32AttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#FLOAT32} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float32AttributeAdapter float32Adapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new Float32AttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#FLOAT64} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float64AttributeAdapter float64Adapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new Float64AttributeAdapter(name, attr);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#CONTAINER} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ContainerAttributeAdapter containerAdapter(
+	String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return new ContainerAttributeAdapter(name, attr, this);
+    }
+
+    /**
+     * Returns an adapter of a DODS {@link Attribute#UNKNOWN} attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attr		The DODS attribute.
+     * @return			An adapter of the DODS attribute.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UnknownAttributeAdapter unknownAdapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	return UnknownAttributeAdapter.unknownAttributeAdapter(name, attr);
+    }
+}
diff --git a/visad/data/dods/BaseTypeVectorAdapter.java b/visad/data/dods/BaseTypeVectorAdapter.java
new file mode 100644
index 0000000..4592470
--- /dev/null
+++ b/visad/data/dods/BaseTypeVectorAdapter.java
@@ -0,0 +1,107 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link BaseTypePrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class BaseTypeVectorAdapter
+    extends VectorAdapter
+{
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected BaseTypeVectorAdapter(
+	    BaseTypePrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	super(vector, das, factory);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS vector and a
+     * factory for creating DODS variable adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static BaseTypeVectorAdapter baseTypeVectorAdapter(
+	    BaseTypePrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new BaseTypeVectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Sets the range of a compatible VisAD {@link Field}.  The range values are
+     * taken from a DODS primitive vector whose metadata must be compatible with
+     * the metadata of the primitive vector used during construction of this
+     * instance.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then range values are copied from the
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public void setField(
+	    BaseTypePrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException
+    {
+	int		length = vector.getLength();
+	for (int i = 0; i < length; ++i)
+	    field.setSample(
+		i,
+		getVariableAdapter().data(vector.getValue(i), copy),
+		false);
+    }
+}
diff --git a/visad/data/dods/BooleanVariableAdapter.java b/visad/data/dods/BooleanVariableAdapter.java
new file mode 100644
index 0000000..29e1a51
--- /dev/null
+++ b/visad/data/dods/BooleanVariableAdapter.java
@@ -0,0 +1,106 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS DBoolean variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class BooleanVariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final SimpleSet[]	repSets;
+
+    private BooleanVariableAdapter(DBoolean var, DAS das)
+	throws VisADException
+    {
+	realType = realType(var, das);
+	repSets = new SimpleSet[] {new Integer1DSet(realType, 2)};
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS variable.
+     *
+     * @param var		The DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     */
+    public static BooleanVariableAdapter booleanVariableAdapter(
+	    DBoolean var, DAS das)
+	throws VisADException
+    {
+	return new BooleanVariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the data values are copied.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns a VisAD data object corresponding to a DODS {@link DBoolean}.
+     * The DBoolean must be compatible with the the DBoolean used to construct
+     * this instance.  In particular, the name of the DBoolean used to construct
+     * this instance will be used in naming the returned VisAD {@link Real}.
+     *
+     * @param var		The DODS variable.  The variable must be
+     *				compatible with the the variable used to 
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A corresponding VisAD data object.  The class of
+     *				the object will be {@link Real}.
+     */
+    public DataImpl data(DBoolean var, boolean copy)
+    {
+	return new Real(realType, var.getValue() ? 1 : 0);
+    }
+}
diff --git a/visad/data/dods/BooleanVectorAdapter.java b/visad/data/dods/BooleanVectorAdapter.java
new file mode 100644
index 0000000..9c64aef
--- /dev/null
+++ b/visad/data/dods/BooleanVectorAdapter.java
@@ -0,0 +1,78 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link BooleanPrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class BooleanVectorAdapter
+    extends FloatVectorAdapter
+{
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public BooleanVectorAdapter(
+	    BooleanPrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	super(vector, das, factory);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public float[] getFloats(PrimitiveVector vec, boolean copy)
+    {
+	BooleanPrimitiveVector	vector = (BooleanPrimitiveVector)vec;
+	float[]			values = new float[vector.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i) ? 1 : 0;
+	return values;
+    }
+}
diff --git a/visad/data/dods/ByteAttributeAdapter.java b/visad/data/dods/ByteAttributeAdapter.java
new file mode 100644
index 0000000..17fe93f
--- /dev/null
+++ b/visad/data/dods/ByteAttributeAdapter.java
@@ -0,0 +1,52 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#BYTE} attribute to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class ByteAttributeAdapter
+    extends	Int32AttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    public ByteAttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+}
diff --git a/visad/data/dods/ByteValuator.java b/visad/data/dods/ByteValuator.java
new file mode 100644
index 0000000..01146d2
--- /dev/null
+++ b/visad/data/dods/ByteValuator.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for processing byte values in a DODS dataset.  Processing
+ * includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class ByteValuator
+    extends	IntValuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private ByteValuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table, -128, 127);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new ByteValuator(table);
+    }
+}
diff --git a/visad/data/dods/ByteVariableAdapter.java b/visad/data/dods/ByteVariableAdapter.java
new file mode 100644
index 0000000..3f5320d
--- /dev/null
+++ b/visad/data/dods/ByteVariableAdapter.java
@@ -0,0 +1,90 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS DByte variables to the
+ * {@link visad.data.in} context.
+ */
+public class ByteVariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final Valuator	valuator;
+    private final SimpleSet[]	repSets;
+
+    private ByteVariableAdapter(DByte var, DAS das)
+	throws VisADException, RemoteException
+    {
+	AttributeTable	table = attributeTable(das, var);
+	realType = realType(var, table);
+	valuator = Valuator.valuator(table, Attribute.BYTE);
+	repSets = new SimpleSet[] {valuator.getRepresentationalSet(realType)};
+    }
+
+    public static ByteVariableAdapter byteVariableAdapter(DByte var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return new ByteVariableAdapter(var, das);
+    }
+
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the data values are copied.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns a VisAD data object corresponding to a DODS {@link DByte}.
+     * The DByte must be compatible with the the DByte used to construct
+     * this instance.  In particular, the name of the DByte used to construct
+     * this instance will be used in naming the returned VisAD {@link Real}.
+     *
+     * @param var		The DODS variable.  The variable must be
+     *				compatible with the the variable used to 
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A corresponding VisAD data object.  The class of
+     *				the object will be {@link Real}.
+     */
+    public DataImpl data(DByte var, boolean copy)
+    {
+	return new Real(realType, valuator.process(var.getValue()));
+    }
+}
diff --git a/visad/data/dods/ByteVectorAdapter.java b/visad/data/dods/ByteVectorAdapter.java
new file mode 100644
index 0000000..63f4f85
--- /dev/null
+++ b/visad/data/dods/ByteVectorAdapter.java
@@ -0,0 +1,83 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link BytePrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class ByteVectorAdapter
+    extends FloatVectorAdapter
+{
+    private final Valuator	valuator;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ByteVectorAdapter(
+	    BytePrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	super(vector, das, factory);
+	valuator =
+	    Valuator.valuator(
+		attributeTable(das, vector.getTemplate()), Attribute.BYTE);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public float[] getFloats(PrimitiveVector vec, boolean copy)
+    {
+	BytePrimitiveVector	vector = (BytePrimitiveVector)vec;
+	float[]			values = new float[vec.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i);
+	return valuator.process(values);
+    }
+}
diff --git a/visad/data/dods/ContainerAttributeAdapter.java b/visad/data/dods/ContainerAttributeAdapter.java
new file mode 100644
index 0000000..7ac2d8f
--- /dev/null
+++ b/visad/data/dods/ContainerAttributeAdapter.java
@@ -0,0 +1,97 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import java.util.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#CONTAINER} attribute
+ * to the VisAD data-import context.  A container attribute is, basically, an
+ * inner-level attribute table.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class ContainerAttributeAdapter
+    extends	AttributeAdapter
+{
+    private final DataImpl	data;
+
+    /**
+     * Constructs from a name, an appropriate attribute, and a factory for
+     * creating adapters for DODS variables.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @param factory		A factor for creating adapters for DODS 
+     *				variables.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ContainerAttributeAdapter(
+	    String name, Attribute attr, AttributeAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	ArrayList	list = new ArrayList();
+	AttributeTable	table = attr.getContainer();
+	boolean		allReals = true;
+	for (Enumeration en = table.getNames(); en.hasMoreElements(); )
+	{
+	    name = (String)en.nextElement();
+	    DataImpl	data =
+		factory.attributeAdapter(name, table.getAttribute(name))
+		.data(false);
+	    list.add(data);
+	    allReals &= data instanceof Real;
+	}
+	if (list.size() == 1)
+	{
+	    data = (DataImpl)list.get(0);
+	}
+	else
+	{
+	    data =
+		allReals
+		    ? (DataImpl)new RealTuple((Real[])list.toArray(new Real[0]))
+		    : new Tuple((Data[])list.toArray(new Data[0]), false);
+	}
+    }
+
+    /**
+     * Returns the VisAD data object corresponding to this instance.
+     *
+     * @param copy		If true, then a copy of the data object is
+     *				returned.
+     * @return			The VisAD data object corresponding to this
+     *				instance.
+     */
+    public DataImpl data(boolean copy)
+    {
+	return copy ? (DataImpl)data.dataClone() : data;
+    }
+}
diff --git a/visad/data/dods/DODSForm.java b/visad/data/dods/DODSForm.java
new file mode 100644
index 0000000..de0e31d
--- /dev/null
+++ b/visad/data/dods/DODSForm.java
@@ -0,0 +1,248 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import java.lang.reflect.*;
+import java.io.IOException;
+import java.net.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.*;
+import visad.data.in.*;
+
+/**
+ * Provides support for accessing the DODS form of data from VisAD.
+ *
+ * <P>Instances are mutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class DODSForm
+    extends	Form
+    implements	FormFileInformer
+{
+    /**
+     * The suffix in the path-component of a URL specification that identifies
+     * a dataset specification as being a DODS dataset specification.  It 
+     * doesn't have a leading period.
+     */
+    public final static String		SUFFIX = "dods";
+
+    private final static String		periodSuffix = "." + SUFFIX;
+    private final static DODSForm	instance = new DODSForm();
+    private final static String		sourceMessage =
+	"DODS data-import capability is not available -- " +
+	"probably because the DODS package wasn't available when " +
+	"this package was compiled.  If you want DODS data-import " +
+	"capability, then you'll have to first obtain the DODS " +
+	"package (see " +
+	"<http://www.unidata.ucar.edu/packages/dods/index.html>) and " +
+	"then recompile this package.";
+    private final static String		contactMessage =
+	".  This exception should not have occurred.  Contact VisAD support.";
+
+    /**
+     * Constructs from nothing.
+     */
+    protected DODSForm()
+    {
+	super("DODS");
+    }
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static DODSForm dodsForm()
+    {
+	return instance;
+    }
+
+    /**
+     * Throws an exception.
+     *
+     * @param id		An identifier.
+     * @param data		A VisAD data object.
+     * @param replace		Whether or not to replace an existing object.
+     * @throws UnimplementedException	Always.
+     */
+    public void save(String id, Data data, boolean replace) 
+        throws BadFormException, IOException, RemoteException, VisADException
+    {
+	throw new UnimplementedException(
+	    getClass().getName() + ".save(String,Data,boolean): " +
+	    "Can't save data to a DODS server");
+    }
+
+    /**
+     * Throws an exception.
+     *
+     * @param id		An identifier.
+     * @param data		A VisAD data object.
+     * @param replace		Whether or not to replace an existing object.
+     * @throws BadFormException	Always.
+     */
+    public void add(String id, Data data, boolean replace)
+	throws BadFormException
+    {
+	throw new BadFormException(
+	    getClass().getName() + ".add(String,Data,boolean): " +
+	    "Can't add data to a DODS server");
+    }
+
+    /**
+     * Opens an existing DODS dataset.
+     *
+     * @param id		The URL for a DODS dataset.  The path component
+     *				should have a {@link #SUFFIX} suffix.
+     * @return			The VisAD data object corresponding to the 
+     *				specified DODS dataset.  Might be 
+     *				<code>null</code>.
+     * @throws BadFormException	The DODS dataset is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl open(String id) 
+        throws BadFormException, IOException, VisADException
+    {
+	String		header = getClass().getName() + ".open(String): ";
+	DataImpl	data;
+	try
+	{
+	    Class		sourceClass = 
+		Class.forName(
+		    getClass().getPackage().getName() + ".DODSSource");
+	    DataInputStream	source = (DataInputStream)
+		sourceClass.getConstructor(new Class[0])
+		    .newInstance(new Object[0]);
+	    sourceClass.getMethod("open", new Class[] {String.class})
+		.invoke(source, new Object[] {id});
+	    data = new Consolidator(new TimeFactorer(source)).readData();
+	}
+	catch (ClassNotFoundException e)
+	{
+	    throw new VisADException(header + e + ".  " + sourceMessage);
+	}
+	catch (NoSuchMethodException e)
+	{
+	    throw new VisADException(header + e + contactMessage);
+	}
+	catch (SecurityException e)
+	{
+	    throw new VisADException(header + e + contactMessage);
+	}
+	catch (InstantiationException e)
+	{
+	    throw new VisADException(header + e + contactMessage);
+	}
+	catch (IllegalAccessException e)
+	{
+	    throw new VisADException(header + e + contactMessage);
+	}
+	catch (IllegalArgumentException e)
+	{
+	    throw new VisADException(header + e + contactMessage);
+	}
+	catch (InvocationTargetException e)
+	{
+	    throw new VisADException(e.getTargetException().getMessage());
+	}
+	return data;
+    }
+
+    /**
+     * Opens an existing data object.
+     *
+     * @param url		The URL for a DODS dataset.  The path component
+     *				should have a {@link #SUFFIX} suffix.
+     * @return			The VisAD data object corresponding to the 
+     *				DODS dataset.
+     * @throws BadFormException	The DODS dataset is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl open(URL url)
+        throws BadFormException, VisADException, IOException
+    {
+	return open(url.toString());
+    }
+
+    /**
+     * Returns <code>null</code>.
+     *
+     * @param data		A VisAD data object.
+     * @return			<code>null</code>.
+     */
+    public FormNode getForms(Data data)
+    {
+	return null;	// can't save data to a DODS server
+    }
+
+/*
+ * FormFileInformer method implementations:
+ */
+
+    /**
+     * Indicates if a dataset specification is consistent with a DODS dataset
+     * specification.
+     *
+     * @param spec		A dataset specification.  NB: Not a URL.
+     * @return			<code>true</code> if and only if the dataset
+     *				specification is consistent with a DODS dataset
+     *				specification.
+     */
+    public boolean isThisType(String spec)
+    {
+	int	i = spec.lastIndexOf('?');
+	if (i != -1)
+	    spec = spec.substring(0, i);
+	return spec.toLowerCase().endsWith(periodSuffix);
+    }
+
+    /**
+     * Does nothing.  Because the initial block of data in a DODS dataset can't
+     * be obtained from a DODS server, this routine does nothing and always
+     * returns false.
+     *
+     * @param block		A block of data.
+     * @return			<code>false</code> always.
+     */
+    public boolean isThisType(byte[] block)
+    {
+	return false;
+    }
+
+    /**
+     * Returns the path-component suffixes that identifies a dataset
+     * specification as being a DODS dataset specification.  The suffixes don't
+     * have a leading period.  The returned array can be safely modified.
+     *
+     * @return			A freshly-allocated array with the relevant 
+     *				suffixes.
+     */
+    public String[] getDefaultSuffixes()
+    {
+	return new String[] {SUFFIX};
+    }
+}
diff --git a/visad/data/dods/DODSFormTest.java b/visad/data/dods/DODSFormTest.java
new file mode 100644
index 0000000..bc55bfc
--- /dev/null
+++ b/visad/data/dods/DODSFormTest.java
@@ -0,0 +1,83 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import visad.*;
+
+/**
+ * Tests {@link DODSForm}.
+ *
+ * @author Steven R. Emmerson
+ */
+public class DODSFormTest
+{
+    public static void main(String[] args)
+	throws Exception
+    {
+	if (args.length < 1 || args.length > 2)
+	{
+	    System.err.println("Usage: [-v] DODS_dataset_spec");
+	    System.err.println(
+		"e.g. http://www.unidata.ucar.edu/cgi-bin/dods/nph-nc/" +
+		"packages/dods/data/nc_test/COADS-climatology.nc.dods");
+	}
+	else
+	{
+	    boolean	verbose;
+	    String	spec;
+	    if (args.length == 1)
+	    {
+		verbose = false;
+		spec = args[0];
+	    }
+	    else
+	    {
+		verbose = args[0].equals("-v");
+		spec = args[1];
+	    }
+	    Runtime	runtime = Runtime.getRuntime();
+	    long	free1 = runtime.freeMemory();
+	    long	total1 = runtime.totalMemory();
+	    long	used1 = total1 - free1;
+	    System.out.println("Before DODS total/free/used memory: " + 
+		total1 + '/' + free1 + '/' + used1);
+	    DODSForm	form = DODSForm.dodsForm();
+            try {
+	        DataImpl	data = form.open(spec);
+	        long	free2 = runtime.freeMemory();
+	        long	total2 = runtime.totalMemory();
+	        long	used2 = total2 - free2;
+	        if (verbose)
+		    System.out.println(data.toString());
+	        else
+		    visad.jmet.DumpType.dumpDataType(data, System.out);
+	        System.out.println("After DODS total/free/used memory:  " + 
+		    total2 + '/' + free2 + '/' + used2);
+	        System.out.println("Used difference = " + (used2 - used1));
+            } catch (Exception e) {
+                System.err.println("Unable to open \"" + spec + "\": " +
+                    e.getMessage());
+            }
+	}
+    }
+}
diff --git a/visad/data/dods/DODSSource.java b/visad/data/dods/DODSSource.java
new file mode 100644
index 0000000..8c4e76d
--- /dev/null
+++ b/visad/data/dods/DODSSource.java
@@ -0,0 +1,284 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+
+import visad.DataImpl;
+import visad.VisADException;
+import visad.data.BadFormException;
+import visad.data.in.DataInputSource;
+
+import dods.dap.AttributeTable;
+import dods.dap.BaseType;
+import dods.dap.DAS;
+import dods.dap.DConnect;
+import dods.dap.DODSException;
+import dods.dap.parser.ParseException;
+
+/**
+ * Provides support for generating a stream of VisAD data objects from a DODS
+ * dataset.
+ *
+ * <P>Instances are mutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class DODSSource
+    implements  DataInputSource
+{
+    private DataFactory         dataFactory;
+    private DConnect            dConnect;
+    private DAS                 das;
+    private AttributeTable      globalTable;
+    private Enumeration         attrEnum;
+    private Enumeration         varEnum;
+
+    /**
+     * Constructs from nothing.  The default factory for creating VisAD data
+     * objects will be used.
+     */
+    public DODSSource()
+    {
+        this(DataFactory.dataFactory());
+    }
+
+    /**
+     * Constructs from a factory for creating VisAD data objects.
+     *
+     * @param factory           A factory for creating VisAD data objects.
+     */
+    public DODSSource(DataFactory factory)
+    {
+        dataFactory = factory;
+    }
+
+    /**
+     * Opens an existing DODS dataset.
+     *
+     * @param spec              The URL string specification of the DODS dataset
+     *                          The path component should have a ".dods" suffix.
+     * @throws BadFormException The DODS dataset is corrupt.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     */
+    
+    public synchronized void open(String spec)
+        throws BadFormException, RemoteException, VisADException
+    {
+        try
+        {
+            URL         url = new URL(spec);
+            String      path = url.getFile();
+            String      query = null;
+            int         i = path.lastIndexOf('?');
+            if (i != -1)
+            {
+                query = path.substring(i);
+                path = path.substring(0, i);
+            }
+            /*
+             * Because the DConnect class won't construct an instance
+             * from a DODS dataset specification whose path component has a
+             * ".dods" suffix, such a suffix is removed.
+             */
+            String      suffix = ".dods";
+            if (path.toLowerCase().endsWith(suffix))
+            {
+                path    = path.substring(0, path.length()-suffix.length());
+                spec =
+                    new URL(
+                        url.getProtocol(),
+                        url.getHost(),
+                        url.getPort(),
+                        // Change 2004-01-22 query already contains ?
+                        // query == null ? path : path + "?" + query)
+                        query == null ? path : path + query)
+                    .toString();
+            }
+            dConnect = new DConnect(spec);
+            das = dConnect.getDAS();
+            globalTable = das.getAttributeTable("NC_GLOBAL");
+            if (globalTable == null)
+                globalTable = das.getAttributeTable("nc_global");
+            if (globalTable != null)
+            {
+                attrEnum = globalTable.getNames();
+            }
+            else
+            {
+                attrEnum = null;
+                varEnum = dConnect.getData(null).getVariables();
+            }
+        }
+        catch (MalformedURLException e)
+        {
+            throw new BadFormException(e.getMessage());
+        }
+        catch (FileNotFoundException e)
+        {
+            throw new BadFormException(e.getMessage());
+        }
+        catch (ParseException e)
+        {
+            throw new BadFormException(e.getMessage());
+        }
+        catch (DODSException e)
+        {
+            throw new BadFormException(e.getMessage());
+        }
+        catch (IOException e)
+        {
+            throw new BadFormException(e.getMessage());
+        }
+    }
+
+    /**
+     * Returns the next VisAD data object from the DODS dataset.  Returns
+     * <code>null</code> if there is no more objects.
+     *
+     * @return                  A VisAD data object or <code>null</code> if 
+     *                          there are no more such objects.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     */
+    public synchronized DataImpl readData() throws VisADException, RemoteException
+    {
+        DataImpl        data;
+        if (attrEnum != null)
+        {
+            if (attrEnum.hasMoreElements())
+            {
+                String  name = (String)attrEnum.nextElement();
+                data =
+                    dataFactory.data(
+                        name, globalTable.getAttribute(name), true);
+            }
+            else
+            {
+                attrEnum = null;
+                try
+                {
+                    varEnum = dConnect.getData(null).getVariables();
+                }
+                catch (DODSException e)
+                {
+                    throw new RemoteException(
+                        getClass().getName() + ".readData(): " +
+                        "Couldn't get DDS of DODS dataset: " + e);
+                }
+                catch (ParseException e)
+                {
+                    throw new RemoteException(
+                        getClass().getName() + ".readData(): " +
+                        "Couldn't get DDS of DODS dataset: " + e);
+                }
+                catch (IOException e)
+                {
+                    throw new RemoteException(
+                        getClass().getName() + ".readData(): " +
+                        "Couldn't get DDS of DODS dataset: " + e);
+                }
+                data = readData();
+            }
+        }
+        else if (varEnum != null)
+        {
+            if (varEnum.hasMoreElements())
+            {
+                data =
+                    dataFactory.data(
+                        (BaseType)varEnum.nextElement(), das, true);
+            }
+            else
+            {
+                data = null;
+                varEnum = null;
+                dConnect = null;
+                das = null;
+                globalTable = null;
+            }
+        }
+        else
+        {
+            data = null;
+        }
+        return data;
+    }
+
+    /**
+     * Returns a VisAD data object corresponding to the next DODS global
+     * attribute in the currently open dataset.  Returns <code>null</code> if
+     * there isn't another attribute.
+     *
+     * @param name              The name of the attribute.
+     * @return                  A VisAD data object corresponding to the next
+     *                          DODS global attribute or <code>null</code> if
+     *                          no more attributes.
+     * @throws BadFormException The DODS datset is corrupt.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     */
+    protected synchronized DataImpl readAttribute(String name)
+        throws BadFormException, VisADException, RemoteException
+    {
+        return dataFactory.data(name, globalTable.getAttribute(name), true);
+    }
+
+    /**
+     * Returns a VisAD data object corresponding to the next DODS variable in
+     * the currently open dataset.  Returns <code>null</code> if there isn't
+     * another variable.
+     *
+     * @return                  A VisAD data object corresponding to the next
+     *                          DODS variable or <code>null</code> if no more
+     *                          variables.
+     * @throws BadFormException The DODS datset is corrupt.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     */
+    protected synchronized DataImpl readVariable()
+        throws BadFormException, VisADException, RemoteException
+    {
+        DataImpl        data;
+        if (varEnum == null)
+        {
+            data = null;
+        }
+        else if (!varEnum.hasMoreElements())
+        {
+            varEnum = null;
+            data = null;
+        }
+        else
+        {
+            data = dataFactory.data((BaseType)varEnum.nextElement(), das, true);
+        }
+        return data;
+    }
+}
diff --git a/visad/data/dods/DataFactory.java b/visad/data/dods/DataFactory.java
new file mode 100644
index 0000000..1394360
--- /dev/null
+++ b/visad/data/dods/DataFactory.java
@@ -0,0 +1,132 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.*;
+
+/**
+ * Provides support for creating adapters that bridge between DODS data objects
+ * and the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class DataFactory
+{
+    private static final DataFactory		instance = new DataFactory();
+    private final AttributeAdapterFactory	attributeFactory;
+    private final VariableAdapterFactory	variableFactory;
+
+    /**
+     * Constructs from nothing.
+     */
+    protected DataFactory()
+    {
+	this(
+	    AttributeAdapterFactory.attributeAdapterFactory(),
+	    VariableAdapterFactory.variableAdapterFactory());
+    }
+
+    /**
+     * Constructs from adapter factories for DODS attributes and DODS variables.
+     *
+     * @param attributeFactory	An adapter factory for DODS attributes.
+     * @param variableFactory	An adapter factory for DODS variables.
+     */
+    protected DataFactory(
+	AttributeAdapterFactory attributeFactory,
+	VariableAdapterFactory variableFactory)
+    {
+	this.attributeFactory = attributeFactory;
+	this.variableFactory = variableFactory;
+    }
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static DataFactory dataFactory()
+    {
+	return instance;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to adapter factories for
+     * DODS attributes and DODS variables.
+     *
+     * @param attributeFactory	An adapter factory for DODS attributes.
+     * @param variableFactory	An adapter factory for DODS variables.
+     * @return			An instance of this class corresponding to the
+     *				input.
+     */
+    public static DataFactory dataFactory(
+	AttributeAdapterFactory attributeFactory,
+	VariableAdapterFactory variableFactory)
+    {
+	return new DataFactory(attributeFactory, variableFactory);
+    }
+
+    /**
+     * Returns the VisAD data object corresponding to a DODS attribute.
+     *
+     * @param name		The name of the DODS attribute.
+     * @param attribute		A DODS attribute.
+     * @param copy		If true, then a copy of the data object is
+     *				returned.
+     * @return			The VisAD data object corresponding to the DODS
+     *				attribute.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(String name, Attribute attribute, boolean copy)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return attributeFactory.attributeAdapter(name, attribute).data(copy);
+    }
+
+    /**
+     * Returns the VisAD data object corresponding to a DODS variable.
+     *
+     * @param var		A DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @param copy		If true, then a copy of the data object is
+     *				returned.
+     * @return			The VisAD data object corresponding to the DODS
+     *				variable.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(BaseType var, DAS das, boolean copy)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return variableFactory.variableAdapter(var, das).data(var, copy);
+    }
+}
diff --git a/visad/data/dods/Float32AttributeAdapter.java b/visad/data/dods/Float32AttributeAdapter.java
new file mode 100644
index 0000000..0628e37
--- /dev/null
+++ b/visad/data/dods/Float32AttributeAdapter.java
@@ -0,0 +1,65 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#FLOAT32}
+ * attribute to the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Float32AttributeAdapter
+    extends	FloatAttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    public Float32AttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+
+    /**
+     * Returns the numeric value corresponding to a floating-point 
+     * string specification.
+     *
+     * @param spec		A floating-point string specification.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected float floatValue(String spec)
+    {
+	return Float.parseFloat(spec);
+    }
+}
diff --git a/visad/data/dods/Float32Valuator.java b/visad/data/dods/Float32Valuator.java
new file mode 100644
index 0000000..a13474b
--- /dev/null
+++ b/visad/data/dods/Float32Valuator.java
@@ -0,0 +1,84 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.*;
+
+/**
+ * Provides support for processing 32-bit floating-point values in a DODS
+ * dataset.  Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Float32Valuator
+    extends	Valuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private Float32Valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new Float32Valuator(table);
+    }
+
+    /**
+     * Returns the set used to represent unpacked, numeric values associated
+     * with this instance in the range of a VisAD {@link FlatField}.
+     *
+     * @return realType		The VisAD real-type for the set.
+     * @return			The set used to represent numeric values
+     *				associated with this instance.
+     * @throws VisADException	VisAD failure.
+     */
+    public SimpleSet getRepresentationalSet(RealType realType)
+	throws VisADException
+    {
+	return new FloatSet(realType);
+    }
+}
diff --git a/visad/data/dods/Float32VariableAdapter.java b/visad/data/dods/Float32VariableAdapter.java
new file mode 100644
index 0000000..eaaa530
--- /dev/null
+++ b/visad/data/dods/Float32VariableAdapter.java
@@ -0,0 +1,111 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS DFloat32 variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Float32VariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final Valuator	valuator;
+    private final SimpleSet[]	repSets;
+
+    private Float32VariableAdapter(DFloat32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	AttributeTable	table = attributeTable(das, var);
+	realType = realType(var, table);
+	valuator = Valuator.valuator(table, Attribute.FLOAT32);
+	repSets = new SimpleSet[] {new FloatSet(realType)};
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link 
+     * DFloat32}.
+     *
+     * @param var		The DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			A instance of this class corresponding to the
+     *				input.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Float32VariableAdapter float32VariableAdapter(
+	    DFloat32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return new Float32VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DFloat32}.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link Real}.
+     */
+    public DataImpl data(DFloat32 var, boolean copy)
+    {
+	return new Real(realType, valuator.process(var.getValue()));
+    }
+}
diff --git a/visad/data/dods/Float32VectorAdapter.java b/visad/data/dods/Float32VectorAdapter.java
new file mode 100644
index 0000000..ef389ca
--- /dev/null
+++ b/visad/data/dods/Float32VectorAdapter.java
@@ -0,0 +1,83 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Float32PrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Float32VectorAdapter
+    extends FloatVectorAdapter
+{
+    private final Valuator	valuator;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float32VectorAdapter(
+	    Float32PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	super(vector, das, factory);
+	valuator =
+	    Valuator.valuator(
+		attributeTable(das, vector.getTemplate()), Attribute.FLOAT32);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public float[] getFloats(PrimitiveVector vec, boolean copy)
+    {
+	Float32PrimitiveVector	vector = (Float32PrimitiveVector)vec;
+	float[]			values = new float[vector.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i);
+	return valuator.process(values);
+    }
+}
diff --git a/visad/data/dods/Float64AttributeAdapter.java b/visad/data/dods/Float64AttributeAdapter.java
new file mode 100644
index 0000000..8d93d07
--- /dev/null
+++ b/visad/data/dods/Float64AttributeAdapter.java
@@ -0,0 +1,120 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.util.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#FLOAT64} attribute to
+ * the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Float64AttributeAdapter
+    extends	NumericAttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    protected Float64AttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+
+    /**
+     * Returns the numeric value corresponding to a floating-point 
+     * string specification.
+     *
+     * @param spec		A floating-point string specification.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected Number number(String spec)
+    {
+	return new Double(doubleValue(spec));
+    }
+
+    /**
+     * Returns the numeric value corresponding to a floating-point 
+     * string specification.
+     *
+     * @param spec		A floating-point string specification.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected double doubleValue(String spec)
+    {
+	return Double.parseDouble(spec);
+    }
+
+    /**
+     * Returns the VisAD {@link Set} corresponding to the metadata of the
+     * attribute used in constructing this instance and a list of numeric
+     * values.
+     * 
+     * @param list		A list of numeric values.  Each element must 
+     *				be of class {@link java.lang.Double}.
+     * @return			A VisAD set corresponding to the input.  The
+     *				class of the set is either {@link
+     *				visad.Gridded1DDoubleSet} or {@link
+     *				visad.List1DDoubleSet}
+     *				-- depending on whether or not the list is
+     *				sorted.
+     * @throws VisADException	VisAD failure.
+     */
+    protected visad.Set visadSet(List list)
+	throws VisADException
+    {
+	double[]	values = new double[list.size()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = ((Double)list.get(i)).doubleValue();
+	boolean		isSorted;
+	{
+	    int		i = 1;
+	    if (values[0] < values[1])
+		while (i < values.length-1)
+		    if (values[i] > values[++i])
+			break;
+	    else
+		while (i < values.length-1)
+		    if (values[i] < values[++i])
+			break;
+	    isSorted = i == values.length - 1;
+	}
+	return
+	    isSorted
+		? (visad.Set)new Gridded1DDoubleSet(
+		    getRealType(), new double[][] {values}, values.length)
+		: new List1DDoubleSet(values, getRealType(), null, null);
+    }
+}
diff --git a/visad/data/dods/Float64Valuator.java b/visad/data/dods/Float64Valuator.java
new file mode 100644
index 0000000..115a95c
--- /dev/null
+++ b/visad/data/dods/Float64Valuator.java
@@ -0,0 +1,84 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.*;
+
+/**
+ * Provides support for processing 64-bit floating-point values in a DODS
+ * dataset.  Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Float64Valuator
+    extends	Valuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private Float64Valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new Float64Valuator(table);
+    }
+
+    /**
+     * Returns the set used to represent unpacked, numeric values associated
+     * with this instance in the range of a VisAD {@link FlatField}.
+     *
+     * @return realType		The VisAD real-type for the set.
+     * @return			The set used to represent numeric values
+     *				associated with this instance.
+     * @throws VisADException	VisAD failure.
+     */
+    public SimpleSet getRepresentationalSet(RealType realType)
+	throws VisADException
+    {
+	return new DoubleSet(realType);
+    }
+}
diff --git a/visad/data/dods/Float64VariableAdapter.java b/visad/data/dods/Float64VariableAdapter.java
new file mode 100644
index 0000000..de8fd3d
--- /dev/null
+++ b/visad/data/dods/Float64VariableAdapter.java
@@ -0,0 +1,112 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS DFloat64 variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Float64VariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final Valuator	valuator;
+    private final SimpleSet[]	repSets;
+
+    private Float64VariableAdapter(DFloat64 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	AttributeTable	table = attributeTable(das, var);
+	realType = realType(var, table);
+	valuator = Valuator.valuator(table, Attribute.FLOAT64);
+	repSets = new SimpleSet[] {new DoubleSet(realType)};
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link 
+     * DFloat64}.
+     *
+     * @param var		The DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			A instance of this class corresponding to the
+     *				input.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Float64VariableAdapter float64VariableAdapter(
+	    DFloat64 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return new Float64VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				only under duress.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DFloat64}.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link Real}.
+     */
+    public DataImpl data(DFloat64 var, boolean copy)
+    {
+	return new Real(realType, valuator.process(var.getValue()));
+    }
+}
diff --git a/visad/data/dods/Float64VectorAdapter.java b/visad/data/dods/Float64VectorAdapter.java
new file mode 100644
index 0000000..282534b
--- /dev/null
+++ b/visad/data/dods/Float64VectorAdapter.java
@@ -0,0 +1,144 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.in.ArithProg;
+
+/**
+ * Provides support for adapting a DODS {@link Float64PrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Float64VectorAdapter
+    extends NumericVectorAdapter
+{
+    private final Valuator	valuator;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float64VectorAdapter(
+	    Float64PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	super(vector, das, factory);
+	valuator =
+	    Valuator.valuator(
+		attributeTable(das, vector.getTemplate()), Attribute.FLOAT64);
+    }
+
+    /**
+     * Sets the range of a compatible VisAD {@link Field}.  The range values are
+     * taken from a DODS primitive vector whose metadata must be compatible with
+     * the metadata of the primitive vector used during construction of this
+     * instance.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public final void setField(
+	    PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException
+    {
+	if (field.isFlatField())
+	    ((FlatField)field).setSamples(
+		new double[][] {getDoubles(vector, copy)}, /*copy=*/false);
+	else
+	    field.setSamples(new double[][] {getDoubles(vector, copy)});
+    }
+
+    /**
+     * Returns the VisAD {@link GriddedSet} corresponding to the metadata of
+     * the DODS primitive vector used during construction of this instance and
+     * the data values of a compatible DODS primitive vector.
+     *
+     * @param vector		A DODS primitive vector whose metadata is
+     *				compatible with the metadata of the primitive
+     *				vector used in construting this instance.
+     * @return			A VisAD GriddedSet corresponding to the input.
+     *				The (super)class of the object is {@link
+     *				visad.Gridded1DSet}.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public GriddedSet griddedSet(PrimitiveVector vector)
+	throws VisADException, RemoteException
+    {
+	double[]	vals = getDoubles(vector, true);
+	ArithProg	ap = new ArithProg();
+	ap.accumulate(vals);
+	return
+	    ap.isConsistent()
+		/*
+		 * NOTE: A Linear1DSet is quite capable of representing double
+		 * values.
+		 */
+		? (Gridded1DSet)new Linear1DSet(
+		    getMathType(),
+		    ap.getFirst(),
+		    ap.getLast(),
+		    (int)ap.getNumber())
+		: Gridded1DDoubleSet.create(
+		    getMathType(), vals, null, null, null);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public double[] getDoubles(PrimitiveVector vec, boolean copy)
+    {
+	Float64PrimitiveVector	vector = (Float64PrimitiveVector)vec;
+	double[]		values = new double[vector.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i);
+	return valuator.process(values);
+    }
+}
diff --git a/visad/data/dods/FloatAttributeAdapter.java b/visad/data/dods/FloatAttributeAdapter.java
new file mode 100644
index 0000000..c4f4b85
--- /dev/null
+++ b/visad/data/dods/FloatAttributeAdapter.java
@@ -0,0 +1,116 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.util.*;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS floating-point attributes to the VisAD
+ * data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class FloatAttributeAdapter
+    extends	NumericAttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    protected FloatAttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+
+    /**
+     * Returns the numeric value corresponding to a floating-point 
+     * string specification.
+     *
+     * @param spec		A floating-point string specification.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected Number number(String spec)
+    {
+	return new Float(floatValue(spec));
+    }
+
+    /**
+     * Returns the numeric value corresponding to a floating-point 
+     * string specification.
+     *
+     * @param spec		A floating-point string specification.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected abstract float floatValue(String spec);
+
+    /**
+     * Returns the VisAD {@link Set} corresponding to the metadata of the
+     * attribute used in constructing this instance and a list of numeric
+     * values.
+     * 
+     * @param list		A list of numeric values.  Each element must 
+     *				be of class {@link java.lang.Float}.
+     * @return			A VisAD set corresponding to the input.
+     *				The class of the set is either {@link
+     *				visad.Gridded1DSet} or {@link visad.List1DSet}
+     *				-- depending on whether or not the list is
+     *				sorted.
+     * @throws VisADException	VisAD failure.
+     */
+    protected visad.Set visadSet(List list)
+	throws VisADException
+    {
+	float[]	values = new float[list.size()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = ((Float)list.get(i)).floatValue();
+	boolean		isSorted = true;
+	{
+	    int		i = 1;
+	    if (values[0] < values[1])
+		while (i < values.length-1)
+		    if (values[i] > values[++i])
+			break;
+	    else
+		while (i < values.length-1)
+		    if (values[i] < values[++i])
+			break;
+	    isSorted = i == values.length - 1;
+	}
+	return
+	    isSorted
+		? (visad.Set)new Gridded1DSet(
+		    getRealType(), new float[][] {values}, values.length)
+		: new List1DSet(values, getRealType(), null, null);
+    }
+}
diff --git a/visad/data/dods/FloatVectorAdapter.java b/visad/data/dods/FloatVectorAdapter.java
new file mode 100644
index 0000000..9555987
--- /dev/null
+++ b/visad/data/dods/FloatVectorAdapter.java
@@ -0,0 +1,129 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.BadFormException;
+import visad.data.in.ArithProg;
+
+/**
+ * Provides support for adapting DODS floating-point vectors to the VisAD
+ * data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class FloatVectorAdapter
+    extends NumericVectorAdapter
+{
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected FloatVectorAdapter(
+	    PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(vector, das, factory);
+    }
+
+    /**
+     * Sets the range of a compatible VisAD {@link Field}.  The range values are
+     * taken from a DODS primitive vector whose metadata must be compatible with
+     * the metadata of the primitive vector used during construction of this
+     * instance.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public final void setField(
+	    PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException
+    {
+	if (field.isFlatField())
+	    ((FlatField)field).setSamples(
+		new float[][] {getFloats(vector, copy)}, /*copy=*/false);
+	else
+	    field.setSamples(new float[][] {getFloats(vector, copy)});
+    }
+
+    /**
+     * Returns the VisAD {@link GriddedSet} corresponding to the metadata of
+     * the DODS primitive vector used during construction of this instance and
+     * the data values of a compatible DODS primitive vector.
+     *
+     * @param vector		A DODS primitive vector whose metadata is
+     *				compatible with the metadata of the primitive
+     *				vector used in construting this instance.
+     * @return			A VisAD GriddedSet corresponding to the
+     *				input.	The (super)class of the object is {@link
+     *				visad.Gridded1DSet}.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public GriddedSet griddedSet(PrimitiveVector vector)
+	throws VisADException, RemoteException
+    {
+	float[]		vals = getFloats(vector, true);
+	ArithProg	ap = new ArithProg();
+	ap.accumulate(vals);
+	return
+	    ap.isConsistent()
+		? (Gridded1DSet)new Linear1DSet(
+		    getMathType(),
+		    ap.getFirst(),
+		    ap.getLast(),
+		    (int)ap.getNumber())
+		: Gridded1DSet.create(getMathType(), vals, null, null, null);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vector	A DODS primitive vector that is compatible with
+     *					the primitive vector used to construct this
+     *					instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    protected abstract float[] getFloats(PrimitiveVector vector, boolean copy);
+}
diff --git a/visad/data/dods/FullSizeTest.java b/visad/data/dods/FullSizeTest.java
new file mode 100644
index 0000000..0798c89
--- /dev/null
+++ b/visad/data/dods/FullSizeTest.java
@@ -0,0 +1,52 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import visad.DataImpl;
+
+public class FullSizeTest
+{
+    public static void main(String[] args)
+	throws Exception
+    {
+	String	urlSpec =
+	    args.length > 0
+		? args[0]
+		: "http://www.unidata.ucar.edu/cgi-bin/dods/test2/nph-nc/" +
+		  "packages/dods/data/nc_test/COADS-climatology.nc";
+	Runtime	runtime = Runtime.getRuntime();
+	long	free1 = runtime.freeMemory();
+	long	total1 = runtime.totalMemory();
+	long	used1 = total1 - free1;
+	System.out.println("Before DODS total/free/used memory: " + 
+	    total1 + '/' + free1 + '/' + used1);
+	DODSForm	form = DODSForm.dodsForm();
+	DataImpl	data = form.open(urlSpec);
+	long	free2 = runtime.freeMemory();
+	long	total2 = runtime.totalMemory();
+	long	used2 = total2 - free2;
+	System.out.println("After DODS total/free/used memory:  " + 
+	    total2 + '/' + free2 + '/' + used2);
+	System.out.println("Used difference = " + (used2 - used1));
+    }
+}
diff --git a/visad/data/dods/GridVariableAdapter.java b/visad/data/dods/GridVariableAdapter.java
new file mode 100644
index 0000000..20f2c9c
--- /dev/null
+++ b/visad/data/dods/GridVariableAdapter.java
@@ -0,0 +1,360 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import visad.*;
+import visad.data.*;
+
+/**
+ * Provides support for adapting DODS {@link DGrid} variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class GridVariableAdapter
+    extends	VariableAdapter
+{
+    private ArrayVariableAdapter	arrayAdapter;
+    private FunctionType		funcType;
+    private boolean			isFlat;
+    private GridVariableMapAdapter[]	domainAdapters;	// VisAD order
+
+    private GridVariableAdapter(
+	    GridVariableMapAdapter[] domainAdapters,
+	    ArrayVariableAdapter arrayAdapter)
+	throws BadFormException, VisADException, RemoteException
+    {
+	MathType	rangeType = arrayAdapter.getFunctionType().getRange();
+	funcType = new FunctionType(mathType(domainAdapters), rangeType);
+	this.arrayAdapter = arrayAdapter;
+	isFlat = isFlat(rangeType);
+	this.domainAdapters = domainAdapters;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link DGrid}.
+     *
+     * @param grid		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @param factory		A factory for creating variable adapters.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static GridVariableAdapter gridVariableAdapter(
+	    DGrid grid, DAS das, VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	ArrayVariableAdapter		arrayAdapter;
+	GridVariableMapAdapter[]	domainAdapters;
+	try
+	{
+	    DArray	array = (DArray)grid.getVar(0);
+	    int		rank = array.numDimensions();
+	    arrayAdapter = factory.arrayVariableAdapter(array, das);
+	    domainAdapters = new GridVariableMapAdapter[rank];
+	    for (int i = 1; i <= rank; ++i)
+	    {
+		array = (DArray)grid.getVar(i);
+		domainAdapters[rank-i] =	// reverse dimensions
+		    factory.gridVariableMapAdapter(array, das);
+	    }
+	}
+	catch (NoSuchVariableException e)
+	{
+	    throw new BadFormException(
+		"visad.data.dods.GridVariableAdapter.gridVariableAdapter(...): "
+		+ "No such variable: " + e);
+	}
+	return new GridVariableAdapter(domainAdapters, arrayAdapter);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return funcType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent
+     * this instances data values in the range of a VisAD {@link
+     * visad.data.FileFlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				only under duress.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return arrayAdapter.getRepresentationalSets(copy);
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DGrid}.
+     *
+     * @param grid	The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link 
+     *				visad.data.FileFlatField}, {@link FlatField}, 
+     *				or {@link FieldImpl}.
+     * @throws VisADException	VisAD failure.  Possibly the variable wasn't
+     *				compatible with the variable used to construct
+     *				this instance.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(DGrid grid, boolean copy)
+	throws VisADException, RemoteException
+    {
+	FieldImpl	field;
+	try
+	{
+	    int		rank = domainAdapters.length;
+	    SampledSet	domain;
+	    if (rank == 1)
+	    {
+		domain =
+		    (SampledSet)domainAdapters[0].data(
+			(DArray)grid.getVar(1), copy);
+	    }
+	    else
+	    {
+		Gridded1DSet[]	domainSets = new Gridded1DSet[rank];
+		/*
+		 * NOTE: "domainAdapters" is in VisAD order (innermost first).
+		 */
+		for (int i = 0; i < rank; ++i)
+		    domainSets[i] = (Gridded1DSet)
+			domainAdapters[i].data(
+			    (DArray)grid.getVar(rank-i), copy);
+		/*
+		 * Consolidate contiguous sequences of LinearSet-s into 
+		 * Gridded*DSet-s.
+		 */
+		ArrayList	griddedSets = new ArrayList(rank);
+		for (int start = 0; start < rank; )
+		{
+		    if (!(domainSets[start] instanceof LinearSet))
+		    {
+			griddedSets.add(domainSets[start++]);
+		    }
+		    else
+		    {
+			int	stop;
+			for (stop = start + 1;
+			    stop < rank &&
+				domainSets[stop] instanceof LinearSet;
+			    ++stop);
+			int	n = stop - start;
+			if (n == 1)
+			{
+			    griddedSets.add(domainSets[start]);
+			}
+			else
+			{
+			    RealType[]	rTypes = new RealType[n];
+			    double[]	firsts = new double[n];
+			    double[]	lasts = new double[n];
+			    int[]	lengths = new int[n];
+			    for (int i = 0; i < n; ++i)
+			    {
+				Linear1DSet	lin1D = (Linear1DSet)
+				    domainSets[start+i];
+				rTypes[i] = (RealType)
+				    ((SetType)lin1D.getType())
+				    .getDomain().getComponent(0);
+				firsts[i] = lin1D.getFirst();
+				lasts[i] = lin1D.getLast();
+				lengths[i] = lin1D.getLength();
+			    }
+			    griddedSets.add(
+				LinearNDSet.create(
+				    new RealTupleType(rTypes),
+				    firsts, lasts, lengths));
+			}
+			start = stop;
+		    }
+		}
+		if (griddedSets.size() == 1)
+		{
+		    domain = (GriddedSet)griddedSets.get(0);
+		}
+		else
+		{
+		    domain =
+			new ProductSet(
+			    funcType.getDomain(),
+			    (GriddedSet[])griddedSets.toArray(
+				new GriddedSet[0]));
+		}
+	    }
+	    DArray	array = (DArray)grid.getVar(0);
+	    if (isFlat)
+	    {
+		/*
+		 * TODO: Either modify FileFlatField or subclass it to support
+		 * a domainFactor(...) method that uses FileFlatField-s.
+		 */
+		field =
+		    new FileFlatField(
+			new GridAccessor(domain, array), getCacheStrategy());
+	    }
+	    else
+	    {
+		field = new FieldImpl(funcType, domain);
+		arrayAdapter.setField(array, field, copy);
+	    }
+	}
+	catch (NoSuchVariableException e)
+	{
+	    throw new BadFormException(
+		getClass().getName() + ".data(...): " +
+		"No such variable: " + e);
+	}
+	return field;
+    }
+
+    /**
+     * Provides support for accessing a DODS DGrid as a VisAD {@link 
+     * visad.data.FileFlatField}.
+     *
+     * <P>Instances are immutable.</P>
+     *
+     * @author Steven R. Emmerson
+     */
+    protected class GridAccessor
+	extends	FileAccessor
+    {
+	private final SampledSet	domain;
+	private final DArray		array;
+
+	/**
+	 * Constructs from a domain and a DODS {@link DArray}.
+	 *
+	 * @param domain		The domain for the FileFlatField.
+	 * @param array			The DODS variable.
+	 */
+	public GridAccessor(SampledSet domain, DArray array)
+	{
+	    this.domain = domain;
+	    this.array = array;
+	}
+
+	/*
+	 * Returns the VisAD {@link FunctionType} of this instance.
+	 *
+	 * @return			The FunctionType of this instance.
+	 */
+	public FunctionType getFunctionType()
+	{
+	    return funcType;
+	}
+
+	/**
+	 * Returns a VisAD {@link FlatField} corresponding to this instance.
+	 *
+	 * @return			A FlatField corresponding to the
+	 *				construction arguments.
+	 * @throws VisADException	VisAD failure.
+	 * @throws RemoteException	Java RMI failure.
+	 */
+	public FlatField getFlatField()
+	    throws VisADException, RemoteException
+	{
+	    FlatField	field =
+		new FlatField(
+		    funcType,
+		    domain,
+		    (CoordinateSystem[])null,
+		    arrayAdapter.getRepresentationalSets(false),
+		    (Unit[])null);
+	    arrayAdapter.setField(array, field, false);
+	    return field;
+	}
+
+	/**
+	 * Throws a VisADError.
+	 *
+	 * @param values		Some values.
+	 * @param template		A template FlatField.
+	 * @param fileLocation		An array of positional parameters.
+	 * @throws VisADError		This method does nothing and should not
+	 *				have been invoked.  Always thrown.
+	 */
+	public void writeFlatField(
+	    double[][] values, FlatField template, int[] fileLocation)
+	{
+	    throw new VisADError(
+		getClass().getName() + ".writeFlatField(...): " +
+		"Unimplemented method");
+	}
+
+	/**
+	 * Throws a VisADError.
+	 *
+	 * @param template		A template FlatField.
+	 * @param fileLocation		An array of positional parameters.
+	 * @return			<code>null</code>.
+	 * @throws VisADError		This method does nothing and should not
+	 *				have been invoked.  Always thrown.
+	 */
+	public double[][] readFlatField(FlatField template, int[] fileLocation)
+	{
+	    throw new VisADError(
+		getClass().getName() + ".readFlatField(...): " +
+		"Unimplemented method");
+	}
+
+	/**
+	 * Throws a VisADError.
+	 *
+	 * @param fileLocation		An array of positional parameters.
+	 * @param range			The range of a FlatField.
+	 * @throws VisADError		This method does nothing and should not
+	 *				have been invoked.  Always thrown.
+	 */
+	public void writeFile(int[] fileLocation, Data range)
+	{
+	    throw new VisADError(
+		getClass().getName() + ".writeFile(...): " +
+		"Unimplemented method");
+	}
+    }
+}
diff --git a/visad/data/dods/GridVariableMapAdapter.java b/visad/data/dods/GridVariableMapAdapter.java
new file mode 100644
index 0000000..168d067
--- /dev/null
+++ b/visad/data/dods/GridVariableMapAdapter.java
@@ -0,0 +1,100 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.lang.ref.WeakReference;
+import java.util.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting the map vectors of a DODS {@link DGrid}
+ * variable to the {@link visad.data.in} context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class GridVariableMapAdapter
+    extends	VariableAdapter
+{
+    private final VectorAdapter		vectorAdapter;
+    private static final Map		setMap = new WeakHashMap();
+
+    private GridVariableMapAdapter(
+	    DArray array,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	vectorAdapter = factory.vectorAdapter(array.getPrimitiveVector(), das);
+    }
+
+    public static GridVariableMapAdapter gridVariableMapAdapter(
+	    DArray array, DAS das, VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	if (array.numDimensions() != 1)
+	    throw new VisADException(
+	"visad.data.dods.GridVariableMapAdapter.gridVariableMapAdapter(...): " +
+		"Array not one-dimensional");
+	return new GridVariableMapAdapter(array, das, factory);
+    }
+
+    public MathType getMathType()
+    {
+	return vectorAdapter.getMathType();
+    }
+
+    /**
+     * Returns a VisAD data object corresponding to a map vector of a DODS grid.
+     *
+     * @param array	An array that contains data and is compatible with
+     *			the array used during construction.
+     * @param copy	If true, then data values are copied.
+     * @return		The VisAD data object corresponding to the adapted
+     *			map vector.  The (super)class of the returned object
+     *			is {@link GriddedSet}.
+     */
+    public DataImpl data(DArray array, boolean copy)
+	throws VisADException, RemoteException
+    {
+	GriddedSet	newSet =
+	    vectorAdapter.griddedSet(array.getPrimitiveVector());
+	WeakReference	ref = (WeakReference)setMap.get(newSet);
+	if (ref == null)
+	{
+	    setMap.put(newSet, new WeakReference(newSet));
+	}
+	else
+	{
+	    GriddedSet	oldSet = (GriddedSet)ref.get();
+	    if (oldSet == null)
+		setMap.put(newSet, new WeakReference(newSet));
+	    else
+		newSet = oldSet;
+	}
+	return newSet;
+    }
+}
diff --git a/visad/data/dods/Int16AttributeAdapter.java b/visad/data/dods/Int16AttributeAdapter.java
new file mode 100644
index 0000000..ac2faf1
--- /dev/null
+++ b/visad/data/dods/Int16AttributeAdapter.java
@@ -0,0 +1,52 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#INT16} attribute to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Int16AttributeAdapter
+    extends	Int32AttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    public Int16AttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+}
diff --git a/visad/data/dods/Int16Valuator.java b/visad/data/dods/Int16Valuator.java
new file mode 100644
index 0000000..ee343c9
--- /dev/null
+++ b/visad/data/dods/Int16Valuator.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for processing 16-bit integer values in a DODS dataset.  
+ * Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Int16Valuator
+    extends	IntValuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private Int16Valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table, Short.MIN_VALUE, Short.MAX_VALUE);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new Int16Valuator(table);
+    }
+}
diff --git a/visad/data/dods/Int16VariableAdapter.java b/visad/data/dods/Int16VariableAdapter.java
new file mode 100644
index 0000000..fbc85f1
--- /dev/null
+++ b/visad/data/dods/Int16VariableAdapter.java
@@ -0,0 +1,111 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS DInt16 variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Int16VariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final Valuator	valuator;
+    private final SimpleSet[]	repSets;
+
+    private Int16VariableAdapter(DInt16 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	AttributeTable	table = attributeTable(das, var);
+	realType = realType(var, table);
+	valuator = Valuator.valuator(table, Attribute.INT16);
+	repSets = new SimpleSet[] {valuator.getRepresentationalSet(realType)};
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link DInt16}.
+     *
+     * @param var		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Int16VariableAdapter int16VariableAdapter(DInt16 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return new Int16VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				only under duress.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DInt16}.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link Real}.
+     */
+    public DataImpl data(DInt16 var, boolean copy)
+    {
+	return new Real(realType, valuator.process(var.getValue()));
+    }
+}
diff --git a/visad/data/dods/Int16VectorAdapter.java b/visad/data/dods/Int16VectorAdapter.java
new file mode 100644
index 0000000..f5256ac
--- /dev/null
+++ b/visad/data/dods/Int16VectorAdapter.java
@@ -0,0 +1,84 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Int16PrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Int16VectorAdapter
+    extends FloatVectorAdapter
+{
+    private final Valuator	valuator;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int16VectorAdapter(
+	    Int16PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	super(vector, das, factory);
+	valuator =
+	    Valuator.valuator(
+		attributeTable(das, vector.getTemplate()), Attribute.INT16);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public float[] getFloats(PrimitiveVector vec, boolean copy)
+    {
+	Int16PrimitiveVector	vector = (Int16PrimitiveVector)vec;
+	float[]			values = new float[vector.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i);
+	return valuator.process(values);
+    }
+}
diff --git a/visad/data/dods/Int32AttributeAdapter.java b/visad/data/dods/Int32AttributeAdapter.java
new file mode 100644
index 0000000..20b4ffe
--- /dev/null
+++ b/visad/data/dods/Int32AttributeAdapter.java
@@ -0,0 +1,65 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#INT32} attributs to 
+ * the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Int32AttributeAdapter
+    extends	FloatAttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    public Int32AttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+
+    /**
+     * Returns the numeric value corresponding to a floating-point 
+     * string specification.
+     *
+     * @param spec		A floating-point string specification.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected float floatValue(String spec)
+    {
+	return Integer.decode(spec).floatValue();
+    }
+}
diff --git a/visad/data/dods/Int32Valuator.java b/visad/data/dods/Int32Valuator.java
new file mode 100644
index 0000000..880ad9e
--- /dev/null
+++ b/visad/data/dods/Int32Valuator.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for processing 32-bit integer values in a DODS dataset.  
+ * Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Int32Valuator
+    extends	IntValuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private Int32Valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table, Integer.MIN_VALUE, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new Int32Valuator(table);
+    }
+}
diff --git a/visad/data/dods/Int32VariableAdapter.java b/visad/data/dods/Int32VariableAdapter.java
new file mode 100644
index 0000000..0ba6b88
--- /dev/null
+++ b/visad/data/dods/Int32VariableAdapter.java
@@ -0,0 +1,111 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS DInt32 variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Int32VariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final Valuator	valuator;
+    private final SimpleSet[]	repSets;
+
+    private Int32VariableAdapter(DInt32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	AttributeTable	table = attributeTable(das, var);
+	realType = realType(var, table);
+	valuator = Valuator.valuator(table, Attribute.INT32);
+	repSets = new SimpleSet[] {valuator.getRepresentationalSet(realType)};
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link DInt32}.
+     *
+     * @param var		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Int32VariableAdapter int32VariableAdapter(DInt32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return new Int32VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				only under duress.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DInt32}.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link Real}.
+     */
+    public DataImpl data(DInt32 var, boolean copy)
+    {
+	return new Real(realType, valuator.process(var.getValue()));
+    }
+}
diff --git a/visad/data/dods/Int32VectorAdapter.java b/visad/data/dods/Int32VectorAdapter.java
new file mode 100644
index 0000000..beb31ad
--- /dev/null
+++ b/visad/data/dods/Int32VectorAdapter.java
@@ -0,0 +1,85 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.BadFormException;
+
+/**
+ * Provides support for adapting a DODS {@link Int32PrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Int32VectorAdapter
+    extends FloatVectorAdapter
+{
+    private final Valuator	valuator;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int32VectorAdapter(
+	    Int32PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(vector, das, factory);
+	valuator =
+	    Valuator.valuator(
+		attributeTable(das, vector.getTemplate()), Attribute.INT32);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public float[] getFloats(PrimitiveVector vec, boolean copy)
+    {
+	Int32PrimitiveVector	vector = (Int32PrimitiveVector)vec;
+	float[]			values = new float[vector.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i);
+	return valuator.process(values);
+    }
+}
diff --git a/visad/data/dods/IntValuator.java b/visad/data/dods/IntValuator.java
new file mode 100644
index 0000000..517c160
--- /dev/null
+++ b/visad/data/dods/IntValuator.java
@@ -0,0 +1,119 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.*;
+
+/**
+ * Provides support for processing integer numeric values in a DODS dataset.
+ * Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class IntValuator
+    extends	Valuator
+{
+    private final double	lower;	// lower unpacked value limit
+    private final double	upper;	// upper unpacked value limit
+
+    /**
+     * Constructs from the attributes of a DODS integer variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @param lower		Natural lower limit on packed values.
+     * @param upper		Natural upper limit on packed values.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected IntValuator(AttributeTable table, long lower, long upper)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table);
+	double	limitA = process(Math.max(lower, ranger.getMin()));
+	double	limitB = process(Math.min(upper, ranger.getMax()));
+	if (limitA < limitB)
+	{
+	    this.lower = limitA;
+	    this.upper = limitB;
+	}
+	else
+	{
+	    this.lower = limitB;
+	    this.upper = limitA;
+	}
+    }
+
+    /**
+     * Returns the set used to represent unpacked, numeric values associated
+     * with this instance in the range of a VisAD {@link FlatField}.
+     *
+     * @return realType		The VisAD real-type for the set.
+     * @return			The set used to represent numeric values
+     *				associated with this instance.
+     * @throws VisADException	VisAD failure.
+     */
+    public SimpleSet getRepresentationalSet(RealType realType)
+	throws VisADException
+    {
+	SimpleSet	repSet;
+	double		inc = unpacker.getIncrement();
+	if (inc == inc && inc != 1)
+	{
+	    repSet =
+		1+((upper-lower)/inc) < Integer.MAX_VALUE
+		    ? (SimpleSet)new Linear1DSet(
+			realType, lower, upper,
+			1+(int)Math.round((upper-lower)/inc))
+		    : new FloatSet(realType);
+	}
+	else
+	{
+	    /*
+	     * The minimum, potential increment between values is one.
+	     */
+	    if (lower == 0)
+	    {
+		repSet =
+		    1+upper <= Integer.MAX_VALUE
+			? (SimpleSet)new Integer1DSet(
+			    realType, 1+(int)Math.round(upper))
+			: new FloatSet(realType);
+	    }
+	    else
+	    {
+		repSet =
+		    1+upper-lower < Integer.MAX_VALUE
+			? (SimpleSet)new Linear1DSet(realType, lower, upper,
+			    1+(int)Math.round((upper-lower)))
+			: new FloatSet(realType);
+	    }
+	}
+	return repSet;
+    }
+}
diff --git a/visad/data/dods/ListVariableAdapter.java b/visad/data/dods/ListVariableAdapter.java
new file mode 100644
index 0000000..74263d9
--- /dev/null
+++ b/visad/data/dods/ListVariableAdapter.java
@@ -0,0 +1,140 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.*;
+
+/**
+ * Provides support for adapting a DODS {@link DList} variable to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class ListVariableAdapter
+    extends	VariableAdapter
+{
+    private final FunctionType	funcType;
+    private final VectorAdapter	vectorAdapter;
+
+    private ListVariableAdapter(
+	    DList list, DAS das, VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	vectorAdapter =
+	    factory.vectorAdapter(list.getPrimitiveVector(), das);
+	funcType =
+	    new FunctionType(RealType.Generic, vectorAdapter.getMathType());
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link DList}.
+     *
+     * @param list		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @param factory		A factory for creating variable adapters.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static ListVariableAdapter listVariableAdapter(
+	    DList list, DAS das, VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new ListVariableAdapter(list, das, factory);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return funcType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				only under duress.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return vectorAdapter.getRepresentationalSets(copy);
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DList}.
+     *
+     * @param list	The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then the data values are copied.
+     * @return			The VisAD data object of this instance.
+     *				The class of the object will be {@link
+     *				visad.data.FileFlatField} {@link FlatField}, or
+     *				{@link FieldImpl}.
+     * @throws VisADException	VisAD failure.  Possibly the variable wasn't
+     *				compatible with the variable used to construct
+     *				this instance.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(DList list, boolean copy)
+	throws VisADException, RemoteException
+    {
+	SampledSet	domain = new Integer1DSet(list.getLength());
+	PrimitiveVector	vector = list.getPrimitiveVector();
+	FieldImpl	field;
+	if (funcType.getFlat())
+	{
+	    /*
+	     * TODO: Either modify FileFlatField or subclass it to support
+	     * a domainFactor(...) method that uses FileFlatField-s.
+	     */
+	    field =
+		new FileFlatField(
+		    new VectorAccessor(funcType, vectorAdapter, domain, vector),
+		    getCacheStrategy());
+	}
+	else
+	{
+	    field = new FieldImpl(funcType, domain);
+	    vectorAdapter.setField(vector, field, copy);
+	}
+	return field;
+    }
+}
diff --git a/visad/data/dods/NumericAttributeAdapter.java b/visad/data/dods/NumericAttributeAdapter.java
new file mode 100644
index 0000000..2184ce6
--- /dev/null
+++ b/visad/data/dods/NumericAttributeAdapter.java
@@ -0,0 +1,126 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.util.*;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS numeric attributes to the VisAD
+ * data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class NumericAttributeAdapter
+    extends	AttributeAdapter
+{
+    private final DataImpl	data;
+    private final RealType	realType;
+    private final String	name;
+
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    protected NumericAttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	this.name = name;
+	realType = RealType.getRealType(scalarName(name));
+	ArrayList	list = new ArrayList();
+	for (Enumeration en = attr.getValues(); en.hasMoreElements(); )
+	    list.add(number((String)en.nextElement()));
+	data =
+	    list.size() == 0
+		? (DataImpl)null
+		: list.size() == 1
+		    ? (DataImpl)new Real(
+			realType, ((Number)list.get(0)).doubleValue())
+		    : visadSet(list);
+    }
+
+    /**
+     * Returns the VisAD {@link RealType} of this instance.
+     *
+     * @return			The VisAD RealType of this instance.
+     */
+    public RealType getRealType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the name of the attribute used during construction of this
+     * instance.
+     *
+     * @return			The name of the attribute used during
+     *				construction of this instance.
+     */
+    public String getAttributeName()
+    {
+	return name;
+    }
+
+    /**
+     * Returns the numeric value corresponding to an appropriate string
+     * specification.
+     *
+     * @param spec		A string specification approrpriate to this
+     *				instance.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected abstract Number number(String spec);
+
+    /**
+     * Returns the VisAD {@link Set} corresponding to the metadata of the
+     * attribute used in constructing this instance and a list of numeric
+     * values.
+     * 
+     * @param list		A list of numeric values.  Each element must 
+     *				be of class {@link java.lang.Double}.
+     * @return			A VisAD set corresponding to the input.
+     * @throws VisADException	VisAD failure.
+     */
+    protected abstract visad.Set visadSet(List list)
+	throws VisADException;
+
+    /**
+     * Returns the VisAD data object corresponding to this instance.
+     *
+     * @param copy		If true, then a copy is returned.
+     * @return			The VisAD data object corresponding to this
+     *				instance.
+     */
+    public DataImpl data(boolean copy)
+    {
+	return copy ? (DataImpl)data.dataClone() : data;
+    }
+}
diff --git a/visad/data/dods/NumericVectorAdapter.java b/visad/data/dods/NumericVectorAdapter.java
new file mode 100644
index 0000000..bb832c1
--- /dev/null
+++ b/visad/data/dods/NumericVectorAdapter.java
@@ -0,0 +1,95 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS numeric primitive vectors to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class NumericVectorAdapter
+    extends VectorAdapter
+{
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected NumericVectorAdapter(
+	    PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(vector, das, factory);
+    }
+
+    /**
+     * Sets the range of a compatible VisAD {@link Field}.  The range values are
+     * taken from a DODS primitive vector whose metadata must be compatible with
+     * the metadata of the primitive vector used during construction of this
+     * instance.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public abstract void setField(
+	    PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException;
+
+    /**
+     * Returns the VisAD {@link GriddedSet} corresponding to the metadata of
+     * the DODS primitive vector used during construction of this instance and
+     * the data values of a compatible DODS primitive vector.
+     *
+     * @param vector		A DODS primitive vector whose metadata is
+     *				compatible with the metadata of the primitive
+     *				vector used in construting this instance.
+     * @return			A VisAD GriddedSet corresponding to the input.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public abstract GriddedSet griddedSet(PrimitiveVector vector)
+	throws VisADException, RemoteException;
+}
diff --git a/visad/data/dods/SequenceVariableAdapter.java b/visad/data/dods/SequenceVariableAdapter.java
new file mode 100644
index 0000000..591f140
--- /dev/null
+++ b/visad/data/dods/SequenceVariableAdapter.java
@@ -0,0 +1,317 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.Vector;
+import visad.*;
+import visad.data.*;
+
+/**
+ * Provides support for adapting DODS {@link DSequence} variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class SequenceVariableAdapter
+    extends	VariableAdapter
+{
+    private FunctionType	funcType;
+    private VariableAdapter[]	adapters;
+    private SimpleSet[]		repSets;
+
+    private SequenceVariableAdapter(
+	    DSequence sequence, DAS das, VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	int		count = sequence.elementCount();
+	ArrayList	setList = new ArrayList();
+	adapters = new VariableAdapter[count];
+	for (int i = 0; i < count; ++i)
+	{
+	    BaseType	template;
+	    try
+	    {
+		template = sequence.getVar(i);
+	    }
+	    catch (NoSuchVariableException e)
+	    {
+		throw new BadFormException(
+		    getClass().getName() + ".data(DSequence,...): " +
+		    "Couldn't get sequence-variable " + i);
+	    }
+	    adapters[i] = factory.variableAdapter(template, das);
+	    SimpleSet[]	setArray = adapters[i].getRepresentationalSets(false);
+	    for (int j = 0; j < setArray.length; ++j)
+		setList.add(setArray[j]);
+	}
+	funcType = new FunctionType(RealType.Generic, mathType(adapters));
+	repSets = (SimpleSet[])setList.toArray(new SimpleSet[0]);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link 
+     * DSequence}.
+     *
+     * @param sequence		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @param factory		A factory for creating variable adapters.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static SequenceVariableAdapter sequenceVariableAdapter(
+	    DSequence sequence, DAS das, VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new SequenceVariableAdapter(sequence, das, factory);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return funcType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				only under duress.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DSequence}.
+     *
+     * @param sequence		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.
+     *				The class of the object will be {@link
+     *				visad.data.FileFlatField}, {@link FlatField}, or
+     *				{@link FieldImpl}.
+     * @throws VisADException	VisAD failure.  Possibly the variable wasn't
+     *				compatible with the variable used to construct
+     *				this instance.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(DSequence sequence, boolean copy)
+	throws VisADException, RemoteException
+    {
+	SampledSet	domain = new Integer1DSet(sequence.getRowCount());
+	FieldImpl	field;
+	if (funcType.getFlat())
+	{
+	    /*
+	     * TODO: Either modify FileFlatField or subclass it to support
+	     * a domainFactor(...) method that uses FileFlatField-s.
+	     */
+	    field =
+		new FileFlatField(
+		    new SequenceAccessor(domain, sequence), getCacheStrategy());
+	}
+	else
+	{
+	    field = new FieldImpl(funcType, domain);
+	    setField(sequence, field, copy);
+	}
+	return field;
+    }
+
+    /**
+     * Sets the range of a compatible VisAD {@link FieldImpl} from a DODS
+     * {@link DSequence}.
+     *
+     * @param sequence		A DODS variable whose data values will be
+     *				used to set the VisAD Field.
+     * @param field		A VisAD field whose range values will be set.
+     *				The field must be compatible with the DODS
+     *				sequence.
+     * @param copy		If true, then data values are copied.
+     * @throws VisADException	VisAD failure.  Possibly the DODS variable and
+     *				the VisAD field are incompatible.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected void setField(DSequence sequence, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException
+    {
+	int		sampleCount = field.getLength();
+	DataImpl	data;
+	MathType	rangeType = funcType.getRange();
+	for (int i = 0; i < sampleCount; ++i)
+	{
+	    Vector	row = sequence.getRow(i);
+	    if (adapters.length == 1)
+	    {
+		data = adapters[0].data((BaseType)row.get(0), copy);
+	    }
+	    else if (rangeType instanceof RealTupleType)
+	    {
+		Real[]	components = new Real[adapters.length];
+		for (int j = 0; j < components.length; ++j)
+		    components[j] =
+			(Real)adapters[j].data((BaseType)row.get(j), copy);
+		data =
+		    new RealTuple(
+			(RealTupleType)rangeType, components, null);
+	    }
+	    else
+	    {
+		Data[]	components = new Data[adapters.length];
+		for (int j = 0; j < components.length; ++j)
+		    components[j] =
+			adapters[j].data((BaseType)row.get(j), copy);
+		data = new Tuple((TupleType)rangeType, components);
+	    }
+	    field.setSample(i, data, /*copy=*/false);
+	}
+    }
+
+    /**
+     * Provides support for accessing a DODS DSequence as a VisAD {@link 
+     * visad.data.FileFlatField}.
+     *
+     * <P>Instances are immutable.</P>
+     *
+     * @author Steven R. Emmerson
+     */
+    protected class SequenceAccessor
+	extends	FileAccessor
+    {
+	private final SampledSet	domain;
+	private final DSequence		sequence;
+
+	/**
+	 * Constructs from a domain and a DODS {@link DSequence}.
+	 *
+	 * @param domain		The domain for the FileFlatField.
+	 * @param sequence		The DODS variable.
+	 */
+	public SequenceAccessor(SampledSet domain, DSequence sequence)
+	{
+	    this.domain = domain;
+	    this.sequence = sequence;
+	}
+
+	/*
+	 * Returns the VisAD {@link FunctionType} of this instance.
+	 *
+	 * @return			The FunctionType of this instance.
+	 */
+	public FunctionType getFunctionType()
+	{
+	    return funcType;
+	}
+
+	/**
+	 * Returns a VisAD {@link FlatField} corresponding to this instance.
+	 *
+	 * @return			A FlatField corresponding to the
+	 *				construction arguments.
+	 * @throws VisADException	VisAD failure.
+	 * @throws RemoteException	Java RMI failure.
+	 */
+	public FlatField getFlatField()
+	    throws VisADException, RemoteException
+	{
+	    FlatField	field =
+		new FlatField(
+		    funcType,
+		    domain,
+		    (CoordinateSystem[])null,
+		    repSets,
+		    (Unit[])null);
+	    setField(sequence, field, false);
+	    return field;
+	}
+
+	/**
+	 * Throws a VisADError.
+	 *
+	 * @param values		Some values.
+	 * @param template		A template FlatField.
+	 * @param fileLocation		An array of positional parameters.
+	 * @throws VisADError		This method does nothing and should not
+	 *				have been invoked.  Always thrown.
+	 */
+	public void writeFlatField(
+	    double[][] values, FlatField template, int[] fileLocation)
+	{
+	    throw new VisADError(
+		getClass().getName() + ".writeFlatField(...): " +
+		"Unimplemented method");
+	}
+
+	/**
+	 * Throws a VisADError.
+	 *
+	 * @param template		A template FlatField.
+	 * @param fileLocation		An array of positional parameters.
+	 * @return			<code>null</code>.
+	 * @throws VisADError		This method does nothing and should not
+	 *				have been invoked.  Always thrown.
+	 */
+	public double[][] readFlatField(FlatField template, int[] fileLocation)
+	{
+	    throw new VisADError(
+		getClass().getName() + ".readFlatField(...): " +
+		"Unimplemented method");
+	}
+
+	/**
+	 * Throws a VisADError.
+	 *
+	 * @param fileLocation		An array of positional parameters.
+	 * @param range			The range of a FlatField.
+	 * @throws VisADError		This method does nothing and should not
+	 *				have been invoked.  Always thrown.
+	 */
+	public void writeFile(int[] fileLocation, Data range)
+	{
+	    throw new VisADError(
+		getClass().getName() + ".writeFile(...): " +
+		"Unimplemented method");
+	}
+    }
+}
diff --git a/visad/data/dods/SizeTest.java b/visad/data/dods/SizeTest.java
new file mode 100644
index 0000000..0ced06d
--- /dev/null
+++ b/visad/data/dods/SizeTest.java
@@ -0,0 +1,51 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+
+public class SizeTest
+{
+    public static void main(String[] args)
+	throws Exception
+    {
+	Runtime	runtime = Runtime.getRuntime();
+	long	free1 = runtime.freeMemory();
+	long	total1 = runtime.totalMemory();
+	long	used1 = total1 - free1;
+	System.out.println("Before DODS total/free/used memory: " + 
+	    total1 + '/' + free1 + '/' + used1);
+	DConnect	dConnect =
+	    new DConnect(
+		"http://www.unidata.ucar.edu/cgi-bin/dods/test2/nph-nc/" +
+		"packages/dods/data/nc_test/COADS-climatology.nc");
+	DAS		das = dConnect.getDAS();
+	DataDDS		dataDDS = dConnect.getData(null);
+	long	free2 = runtime.freeMemory();
+	long	total2 = runtime.totalMemory();
+	long	used2 = total2 - free2;
+	System.out.println("After DODS total/free/used memory:  " + 
+	    total2 + '/' + free2 + '/' + used2);
+	System.out.println("Used difference = " + (used2 - used1));
+    }
+}
diff --git a/visad/data/dods/StringAttributeAdapter.java b/visad/data/dods/StringAttributeAdapter.java
new file mode 100644
index 0000000..927456d
--- /dev/null
+++ b/visad/data/dods/StringAttributeAdapter.java
@@ -0,0 +1,77 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import java.util.*;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS {@link Attribute#STRING} attributes to
+ * the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class StringAttributeAdapter
+    extends	AttributeAdapter
+{
+    private final DataImpl	data;
+
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public StringAttributeAdapter(String name, Attribute attr)
+	throws VisADException, RemoteException
+    {
+	TextType	textType =
+	    TextType.getTextType(scalarName(name));
+	ArrayList	list = new ArrayList();
+	for (Enumeration en = attr.getValues(); en.hasMoreElements(); )
+	    list.add(new Text(textType, (String)en.nextElement()));
+	data = 
+	    list.size() == 1
+		? (DataImpl)list.get(0)
+		: new Tuple((Text[])list.toArray(new Text[0]), false);
+    }
+
+    /**
+     * Returns the VisAD data object corresponding to this instance.
+     *
+     * @param copy		If true, then a copy is returned.
+     * @return			The VisAD data object corresponding to this
+     *				instance.
+     */
+    public DataImpl data(boolean copy)
+    {
+	return copy ? (DataImpl)data.dataClone() : data;
+    }
+}
diff --git a/visad/data/dods/StringVariableAdapter.java b/visad/data/dods/StringVariableAdapter.java
new file mode 100644
index 0000000..740b8de
--- /dev/null
+++ b/visad/data/dods/StringVariableAdapter.java
@@ -0,0 +1,99 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS {@link DString} variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class StringVariableAdapter
+    extends	VariableAdapter
+{
+    private final TextType	textType;
+
+    private StringVariableAdapter(DString var)
+	throws VisADException
+    {
+	textType = TextType.getTextType(scalarName(var.getName()));
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link 
+     * DString}.
+     *
+     * @param var		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     */
+    public static StringVariableAdapter stringVariableAdapter(
+	    DString var, DAS das)
+	throws VisADException
+    {
+	return new StringVariableAdapter(var);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return textType;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to a DODS {@link 
+     * DString}.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link Text}.  The
+     *				VisAD {@link MathType} of the data object will
+     *				be based on the DODS variable used during
+     *				construction of this instance.
+     * @throws VisADException	VisAD failure.  Possibly the variable wasn't
+     *				compatible with the variable used to construct
+     *				this instance.
+     */
+    public DataImpl data(DString var, boolean copy)
+	throws VisADException
+    {
+	return new Text(textType, var.getValue());
+    }
+}
diff --git a/visad/data/dods/StructureVariableAdapter.java b/visad/data/dods/StructureVariableAdapter.java
new file mode 100644
index 0000000..6cb80b7
--- /dev/null
+++ b/visad/data/dods/StructureVariableAdapter.java
@@ -0,0 +1,186 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.util.ArrayList;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.BadFormException;
+
+/**
+ * Provides support for adapting DODS {@link DStructure} variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class StructureVariableAdapter
+    extends	VariableAdapter
+{
+    private final MathType		mathType;
+    private final VariableAdapter[]	adapters;
+    private final boolean		isFlat;
+    private final SimpleSet[]		repSets;
+
+    private StructureVariableAdapter(
+	    DStructure structure, DAS das, VariableAdapterFactory factory)
+	throws BadFormException, VisADException, RemoteException
+    {
+	ArrayList	setList = new ArrayList();
+	adapters = new VariableAdapter[structure.elementCount()];
+	for (int i = 0; i < adapters.length; ++i)
+	{
+	    BaseType	var;
+	    try
+	    {
+		var = structure.getVar(i);
+	    }
+	    catch (NoSuchVariableException e)
+	    {
+		throw new BadFormException(
+		    getClass().getName() + ".data(...): " +
+		    "DStructure is missing variable " + i + ": " + e);
+	    }
+	    adapters[i] = factory.variableAdapter(var, das);
+	    SimpleSet[]	setArray = adapters[i].getRepresentationalSets(false);
+	    for (int j = 0; j < setArray.length; ++j)
+		setList.add(setArray[j]);
+	}
+	mathType = mathType(adapters);
+	isFlat = isFlat(mathType);
+	repSets = (SimpleSet[])setList.toArray(new SimpleSet[0]);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link 
+     * DStructure}.
+     *
+     * @param structure		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @param factory		A factory for creating variable adapters.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static StructureVariableAdapter structureVariableAdapter(
+	    DStructure structure, DAS das, VariableAdapterFactory factory)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new StructureVariableAdapter(structure, das, factory);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return mathType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				only under duress.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to the values of a DODS
+     * {@link DStructure} and the DODS variable used during construction of this
+     * instance.
+     *
+     * @param structure		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be determined by the
+     *				components of the structure used during
+     *				construction of this instance.  Will be
+     *				<code>null</code> if the construction
+     *				structure had no components.
+     * @throws BadFormException	The DODS variable is corrupt.
+     * @throws VisADException	VisAD failure.  Possibly the variable wasn't
+     *				compatible with the variable used to construct
+     *				this instance.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(DStructure structure, boolean copy)
+	throws BadFormException, VisADException, RemoteException
+    {
+	DataImpl	data;
+	try
+	{
+	    if (adapters.length == 0)
+	    {
+		data = null;
+	    }
+	    else if (adapters.length == 1)
+	    {
+		data = adapters[0].data(structure.getVar(0), copy);
+	    }
+	    else
+	    {
+		if (isFlat)
+		{
+		    Real[]	components = new Real[adapters.length];
+		    for (int i = 0; i < adapters.length; ++i)
+			components[i] = 
+			    (Real)adapters[i].data(structure.getVar(i), copy);
+		    data = new RealTuple(components);
+		}
+		else
+		{
+		    DataImpl[]	components = new DataImpl[adapters.length];
+		    for (int i = 0; i < adapters.length; ++i)
+			components[i] =
+			    adapters[i].data(structure.getVar(i), copy);
+		    data = new Tuple(components);
+		}
+	    }
+	}
+	catch (NoSuchVariableException e)
+	{
+	    throw new BadFormException(
+		getClass().getName() + ".data(...): " +
+		"DStructure is missing variable: " + e);
+	}
+	return data;
+    }
+}
diff --git a/visad/data/dods/UByteValuator.java b/visad/data/dods/UByteValuator.java
new file mode 100644
index 0000000..81b1ad1
--- /dev/null
+++ b/visad/data/dods/UByteValuator.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for processing unsigned, 8-bit integer values in a DODS
+ * dataset.  Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class UByteValuator
+    extends	UIntValuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private UByteValuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table, 256);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new UByteValuator(table);
+    }
+}
diff --git a/visad/data/dods/UInt16AttributeAdapter.java b/visad/data/dods/UInt16AttributeAdapter.java
new file mode 100644
index 0000000..b7c9038
--- /dev/null
+++ b/visad/data/dods/UInt16AttributeAdapter.java
@@ -0,0 +1,52 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#UINT16} attribute to
+ * the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class UInt16AttributeAdapter
+    extends	Int32AttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    public UInt16AttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+}
diff --git a/visad/data/dods/UInt16Valuator.java b/visad/data/dods/UInt16Valuator.java
new file mode 100644
index 0000000..a636dba
--- /dev/null
+++ b/visad/data/dods/UInt16Valuator.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for processing unsigned, 16-bit integer values in a DODS
+ * dataset.  Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class UInt16Valuator
+    extends	UIntValuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private UInt16Valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table, 2*Short.MAX_VALUE+1);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new UInt16Valuator(table);
+    }
+}
diff --git a/visad/data/dods/UInt16VariableAdapter.java b/visad/data/dods/UInt16VariableAdapter.java
new file mode 100644
index 0000000..991396c
--- /dev/null
+++ b/visad/data/dods/UInt16VariableAdapter.java
@@ -0,0 +1,116 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS {DUInt16} variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class UInt16VariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final Valuator	valuator;
+    private final SimpleSet[]	repSets;
+
+    private UInt16VariableAdapter(DUInt16 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	AttributeTable	table = attributeTable(das, var);
+	realType = realType(var, table);
+	valuator = Valuator.valuator(table, Attribute.UINT16);
+	repSets = new SimpleSet[] {valuator.getRepresentationalSet(realType)};
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link
+     * DUInt16}.
+     *
+     * @param var		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static UInt16VariableAdapter uInt16VariableAdapter(
+	    DUInt16 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return new UInt16VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to the value of a DODS
+     * {@link DUInt16} and the DODS variable used during construction of this
+     * instance.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link Real}.  The
+     *				VisAD {@link MathType} of the data object will
+     *				be based on the DODS variable used during
+     *				construction of this instance.
+     */
+    public DataImpl data(DUInt16 var, boolean copy)
+    {
+	return new Real(realType, valuator.process(var.getValue()));
+    }
+}
diff --git a/visad/data/dods/UInt16VectorAdapter.java b/visad/data/dods/UInt16VectorAdapter.java
new file mode 100644
index 0000000..f24962f
--- /dev/null
+++ b/visad/data/dods/UInt16VectorAdapter.java
@@ -0,0 +1,85 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.BadFormException;
+
+/**
+ * Provides support for adapting a DODS {@link UInt16PrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class UInt16VectorAdapter
+    extends FloatVectorAdapter
+{
+    private final Valuator	valuator;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt16VectorAdapter(
+	    UInt16PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(vector, das, factory);
+	valuator =
+	    Valuator.valuator(
+		attributeTable(das, vector.getTemplate()), Attribute.UINT16);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public float[] getFloats(PrimitiveVector vec, boolean copy)
+    {
+	UInt16PrimitiveVector	vector = (UInt16PrimitiveVector)vec;
+	float[]			values = new float[vector.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i);
+	return valuator.process(values);
+    }
+}
diff --git a/visad/data/dods/UInt32AttributeAdapter.java b/visad/data/dods/UInt32AttributeAdapter.java
new file mode 100644
index 0000000..f35f2b7
--- /dev/null
+++ b/visad/data/dods/UInt32AttributeAdapter.java
@@ -0,0 +1,66 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#UINT32} attribute to
+ * the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class UInt32AttributeAdapter
+    extends	FloatAttributeAdapter
+{
+    /**
+     * Constructs from a name and an appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     * @throws VisADException	VisAD failure.  Probably the attribute has an
+     *				inappropriate type.
+     */
+    public UInt32AttributeAdapter(String name, Attribute attr)
+	throws VisADException
+    {
+	super(name, attr);
+    }
+
+    /**
+     * Returns the numeric value corresponding to an appropriate string
+     * specification.
+     *
+     * @param spec		A string specification appropriate to this
+     *				instance.
+     * @return			The numeric value corresponding to the
+     *				string specification.
+     */
+    protected float floatValue(String spec)
+    {
+	return Long.decode(spec).floatValue();
+    }
+}
diff --git a/visad/data/dods/UInt32Valuator.java b/visad/data/dods/UInt32Valuator.java
new file mode 100644
index 0000000..76e166e
--- /dev/null
+++ b/visad/data/dods/UInt32Valuator.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for processing unsigned, 32-bit integer values in a DODS
+ * dataset.  Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class UInt32Valuator
+    extends	UIntValuator
+{
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    private UInt32Valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table, 2*(long)Integer.MAX_VALUE+1);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	return new UInt32Valuator(table);
+    }
+}
diff --git a/visad/data/dods/UInt32VariableAdapter.java b/visad/data/dods/UInt32VariableAdapter.java
new file mode 100644
index 0000000..c551aa4
--- /dev/null
+++ b/visad/data/dods/UInt32VariableAdapter.java
@@ -0,0 +1,116 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS {DUInt32} variables to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class UInt32VariableAdapter
+    extends	VariableAdapter
+{
+    private final RealType	realType;
+    private final Valuator	valuator;
+    private final SimpleSet[]	repSets;
+
+    private UInt32VariableAdapter(DUInt32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	AttributeTable	table = attributeTable(das, var);
+	realType = realType(var, table);
+	valuator = Valuator.valuator(table, Attribute.UINT32);
+	repSets = new SimpleSet[] {valuator.getRepresentationalSet(realType)};
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a DODS {@link
+     * DUInt32}.
+     *
+     * @param var		The DODS variable.  Only the DODS metadata is 
+     *				used: the variable needn't have any actual data.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static UInt32VariableAdapter uInt32VariableAdapter(
+	    DUInt32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return new UInt32VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public MathType getMathType()
+    {
+	return realType;
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])repSets.clone() : repSets;
+    }
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to the value of a DODS
+     * {@link DUInt16} and the DODS variable used during construction of this
+     * instance.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			The VisAD data object of this instance.  The
+     *				class of the object will be {@link Real}.  The
+     *				VisAD {@link MathType} of the data object will
+     *				be based on the DODS variable used during
+     *				construction of this instance.
+     */
+    public DataImpl data(DUInt32 var, boolean copy)
+    {
+	return new Real(realType, valuator.process(var.getValue()));
+    }
+}
diff --git a/visad/data/dods/UInt32VectorAdapter.java b/visad/data/dods/UInt32VectorAdapter.java
new file mode 100644
index 0000000..f400ba4
--- /dev/null
+++ b/visad/data/dods/UInt32VectorAdapter.java
@@ -0,0 +1,84 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link UInt32PrimitiveVector} to the
+ * VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class UInt32VectorAdapter
+    extends FloatVectorAdapter
+{
+    private final Valuator	valuator;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt32VectorAdapter(
+	    UInt32PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	super(vector, das, factory);
+	valuator =
+	    Valuator.valuator(
+		attributeTable(das, vector.getTemplate()), Attribute.UINT32);
+    }
+
+    /**
+     * Returns the numeric values of a compatible DODS primitive vector.
+     *
+     * @param vec		A DODS primitive vector that is compatible with
+     *				the primitive vector used to construct this
+     *				instance.
+     * @param copy		If true, then a copy is returned.
+     * @return			The numeric values of the primitive vector.
+     */
+    public float[] getFloats(PrimitiveVector vec, boolean copy)
+    {
+	UInt32PrimitiveVector	vector = (UInt32PrimitiveVector)vec;
+	float[]			values = new float[vector.getLength()];
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = vector.getValue(i);
+	return valuator.process(values);
+    }
+}
diff --git a/visad/data/dods/UIntValuator.java b/visad/data/dods/UIntValuator.java
new file mode 100644
index 0000000..ab983ea
--- /dev/null
+++ b/visad/data/dods/UIntValuator.java
@@ -0,0 +1,106 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for processing unsigned integer values in a DODS
+ * dataset.  Processing includes checking for validity and unpacking.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class UIntValuator
+    extends	IntValuator
+{
+    private final float		floatFold;
+    private final double	doubleFold;
+
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @param upper		Natural upper limit on packed values.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected UIntValuator(AttributeTable table, long upper)
+	throws BadFormException, VisADException, RemoteException
+    {
+	super(table, 0, upper);
+	floatFold = (upper+1)/2;
+	doubleFold = (upper+1)/2;
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The packed value to be processed.
+     */
+    public float process(float value)
+    {
+	return super.process(value < 0 ? value + floatFold : value);
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param values		The packed values to be processed.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    if (values[i] < 0)
+		values[i] += floatFold;
+	return super.process(values);
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param value		The packed value to be processed.
+     */
+    public double process(double value)
+    {
+	return super.process(value < 0 ? value + doubleFold : value);
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The packed values to be processed.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    if (values[i] < 0)
+		values[i] += doubleFold;
+	return super.process(values);
+    }
+}
diff --git a/visad/data/dods/UnknownAttributeAdapter.java b/visad/data/dods/UnknownAttributeAdapter.java
new file mode 100644
index 0000000..86fb9d7
--- /dev/null
+++ b/visad/data/dods/UnknownAttributeAdapter.java
@@ -0,0 +1,68 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import visad.*;
+
+/**
+ * Provides support for adapting a DODS {@link Attribute#UNKNOWN} attribute to
+ * the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class UnknownAttributeAdapter
+    extends	AttributeAdapter
+{
+    private static final UnknownAttributeAdapter	instance =
+	new UnknownAttributeAdapter();
+
+    private UnknownAttributeAdapter()
+    {}
+
+    /**
+     * Returns an instance of this class corresponding to a name and
+     * appropriate attribute.
+     *
+     * @param name		The name of the attribute.
+     * @param attr		The attribute.  Must have the appropriate type.
+     */
+    public static UnknownAttributeAdapter unknownAttributeAdapter(
+	String name, Attribute attr)
+    {
+	return instance;
+    }
+
+    /**
+     * Returns <code>null</code>.
+     *
+     * @param copy		If true, then the data values are copied.
+     * @return			<code>null</code>.
+     */
+    public DataImpl data(boolean copy)
+    {
+	return null;
+    }
+}
diff --git a/visad/data/dods/Valuator.java b/visad/data/dods/Valuator.java
new file mode 100644
index 0000000..5f88f80
--- /dev/null
+++ b/visad/data/dods/Valuator.java
@@ -0,0 +1,306 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.data.in.*;
+import visad.*;
+
+/**
+ * Provides support for processing numeric values in a DODS dataset.  Processing
+ * includes checking for non-equality with "missing" or "fill" values, 
+ * unpacking into more capacious data types, and checking that the values lie
+ * within a valid range.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class Valuator
+{
+    protected final ValueVetter		vetter;
+    protected final ValueUnpacker	unpacker;
+    protected final ValueRanger		ranger;
+
+    /**
+     * Constructs from the attributes of a DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected Valuator(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	vetter = valueVetter(table);
+	unpacker = valueUnpacker(table);
+	ranger = valueRanger(table);
+    }
+
+    /**
+     * Returns an instance of this class corresponding to the attributes for a
+     * DODS variable.
+     *
+     * @param table		The attribute table for a DODS variable.
+     * @param type		The type of packed variable: {@link
+     *				Attribute#BYTE}, {@link Attribute#INT16}, etc.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static Valuator valuator(AttributeTable table, int type)
+	throws BadFormException, VisADException, RemoteException
+    {
+	Valuator	valuator;
+	switch (type)
+	{
+	    case Attribute.BYTE:
+		valuator = 
+		    valueRanger(table).getMin() >= 0
+			? UByteValuator.valuator(table)
+			: ByteValuator.valuator(table);
+		break;
+	    case Attribute.FLOAT32:
+		valuator = Float32Valuator.valuator(table);
+		break;
+	    case Attribute.FLOAT64:
+		valuator = Float64Valuator.valuator(table);
+		break;
+	    case Attribute.INT16:
+		valuator = Int16Valuator.valuator(table);
+		break;
+	    case Attribute.INT32:
+		valuator = Int32Valuator.valuator(table);
+		break;
+	    case Attribute.UINT16:
+		valuator = UInt16Valuator.valuator(table);
+		break;
+	    case Attribute.UINT32:
+		valuator = UInt32Valuator.valuator(table);
+		break;
+	    default:
+		throw new BadFormException(
+		    "Valuator.valuator(AttributeTable,int): " +
+		    "Unknown variable type: " + type);
+	}
+	return valuator;
+    }
+
+    /**
+     * Returns the set used to represent unpacked, numeric values associated
+     * with this instance in the range of a VisAD {@link FlatField}.
+     *
+     * @return realType		The VisAD real-type for the set.
+     * @return			The set used to represent numeric values
+     *				associated with this instance.
+     * @throws VisADException	VisAD failure.
+     */
+    public abstract SimpleSet getRepresentationalSet(RealType realType)
+	throws VisADException;
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The value to be processed.
+     */
+    public float process(float value)
+    {
+	return ranger.process(unpacker.process(vetter.process(value)));
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param values		The values to be processed.
+     */
+    public float[] process(float[] values)
+    {
+	return ranger.process(unpacker.process(vetter.process(values)));
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param value		The value to be processed.
+     */
+    public double process(double value)
+    {
+	return ranger.process(unpacker.process(vetter.process(value)));
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The values to be processed.
+     */
+    public double[] process(double[] values)
+    {
+	return ranger.process(unpacker.process(vetter.process(values)));
+    }
+
+    /**
+     * Decodes an attribute for a DODS variable.
+     *
+     * @param name		The name of the attribute.
+     * @param table		The attribute table of the DODS variable.
+     * @param index		The index of the attribute element to be 
+     *				decoded.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected static double decode(String name, AttributeTable table, int index)
+	throws BadFormException, VisADException, RemoteException
+    {
+	double		value = Double.NaN;	// default value
+	Attribute	attr = table.getAttribute(name);
+	if (attr != null)
+	{
+	    DataImpl	data =
+		AttributeAdapterFactory.attributeAdapterFactory()
+		    .attributeAdapter(name, attr).data(false);
+	    if (data instanceof Real && index == 0)
+		value = ((Real)data).getValue();
+	    else if (data instanceof Gridded1DDoubleSet)
+		value =
+		    ((Gridded1DSet)data).indexToDouble(new int[] {index})[0][0];
+	    else if (data instanceof Gridded1DSet)
+		value =
+		    ((Gridded1DSet)data).indexToValue(new int[] {index})[0][0];
+	    else
+		System.err.println(
+		    "ValueProcessor.decode(String,AttributeTable,int): " +
+		    "Attribute \"" + name + "\" has non-numeric type: " +
+		    attr.getTypeString());
+	}
+	return value;
+    }
+
+    /**
+     * Returns an instance of a value vetter corresponding to the attributes 
+     * of a DODS variable.
+     *
+     * @param table		The DODS attribute table.  May be 
+     *				<code>null</code>, in which case a trivial
+     *				vetter is returned.
+     * @return			A value vetter.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public static ValueVetter valueVetter(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	double	fill = Double.NaN;	// default
+	double	missing = Double.NaN;	// default
+	if (table != null)
+	{
+	    fill = decode("_FillValue", table, 0);
+	    missing = decode("missing_value", table, 0);
+	}
+	return ValueVetter.valueVetter(new double[] {fill, missing});
+    }
+
+    /**
+     * Returns an instance of a value unpacker corresponding to the attributes 
+     * of a DODS variable.
+     *
+     * @param table		A DODS attribute table.  May be 
+     *				<code>null</code>, in which case a trivial
+     *				unpacker is returned.
+     * @return			A value unpacker.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI exception.
+     */
+    public static ValueUnpacker valueUnpacker(
+	    AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	ValueUnpacker	unpacker;
+	if (table == null)
+	{
+	    unpacker = ValueUnpacker.valueUnpacker();
+	}
+	else
+	{
+	    double	scale = decode("scale_factor", table, 0);
+	    double	offset = decode("add_offset", table, 0);
+	    if (scale == scale && scale != 1 &&
+		offset == offset && offset != 0)
+	    {
+		unpacker = ScaleAndOffsetUnpacker.scaleAndOffsetUnpacker(
+		    scale, offset);
+	    }
+	    else if (scale == scale && scale != 1)
+	    {
+		unpacker = ScaleUnpacker.scaleUnpacker(scale);
+	    }
+	    else if (offset == offset && offset != 0)
+	    {
+		unpacker = OffsetUnpacker.offsetUnpacker(offset);
+	    }
+	    else
+	    {
+		unpacker = ValueUnpacker.valueUnpacker();
+	    }
+	}
+	return unpacker;
+    }
+
+    /**
+     * Returns an instance of a value ranger corresponding to the attributes of
+     * a DODS variable.
+     *
+     * @param table		A DODS attribute table.  May be 
+     *				<code>null</code>, in which case a trivial
+     *				ranger is returned.
+     * @return			A value ranger.
+     * @throws BadFormException	The attribute table is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI exception.
+     */
+    public static ValueRanger valueRanger(AttributeTable table)
+	throws BadFormException, VisADException, RemoteException
+    {
+	double	lower = Double.NEGATIVE_INFINITY;
+	double	upper = Double.POSITIVE_INFINITY;
+	if (table != null)
+	{
+	    if (table.getAttribute("valid_range") == null)
+	    {
+		lower = decode("valid_min", table, 0);
+		upper = decode("valid_max", table, 0);
+	    }
+	    else
+	    {
+		lower = decode("valid_range", table, 0);
+		upper = decode("valid_range", table, 1);
+	    }
+	}
+	return ValueRanger.valueRanger(lower, upper);
+    }
+}
diff --git a/visad/data/dods/VariableAdapter.java b/visad/data/dods/VariableAdapter.java
new file mode 100644
index 0000000..a90e155
--- /dev/null
+++ b/visad/data/dods/VariableAdapter.java
@@ -0,0 +1,472 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.BadFormException;
+
+/**
+ * Provides support for adapting DODS objects to the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class VariableAdapter
+    extends	Adapter
+{
+    private static final SimpleSet[]		defaultRepSets =
+	new SimpleSet[] {null};
+
+    /**
+     * Returns the VisAD {@link MathType} of this instance.
+     *
+     * @return			The MathType of this instance.
+     */
+    public abstract MathType getMathType();
+
+    /**
+     * Returns the VisAD {@link DataImpl} corresponding to the data of a DODS
+     * variable and the metaData of the DODS variable used during construction
+     * of this instance.
+     *
+     * @param baseType		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.  The
+     *				class of the object will depend upon the DODS
+     *				variable used during construction.
+     * @throws BadFormException The DODS variable is corrupt.
+     * @throws VisADException	VisAD failure.  Possibly the variable wasn't
+     *				compatible with the variable used to construct
+     *				this instance.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public DataImpl data(BaseType baseType, boolean copy)
+	throws BadFormException, VisADException, RemoteException
+    {
+	DataImpl	data;
+	if (baseType instanceof DString)
+	    data = data((DString)baseType, copy);
+	else if (baseType instanceof DBoolean)
+	    data = data((DBoolean)baseType, copy);
+	else if (baseType instanceof DByte)
+	    data = data((DByte)baseType, copy);
+	else if (baseType instanceof DUInt16)
+	    data = data((DUInt16)baseType, copy);
+	else if (baseType instanceof DInt16)
+	    data = data((DInt16)baseType, copy);
+	else if (baseType instanceof DUInt32)
+	    data = data((DUInt32)baseType, copy);
+	else if (baseType instanceof DInt32)
+	    data = data((DInt32)baseType, copy);
+	else if (baseType instanceof DFloat32)
+	    data = data((DFloat32)baseType, copy);
+	else if (baseType instanceof DFloat64)
+	    data = data((DFloat64)baseType, copy);
+	else if (baseType instanceof DStructure)
+	    data = data((DStructure)baseType, copy);
+	else if (baseType instanceof DList)
+	    data = data((DList)baseType, copy);
+	else if (baseType instanceof DSequence)
+	    data = data((DSequence)baseType, copy);
+	else if (baseType instanceof DArray)
+	    data = data((DArray)baseType, copy);
+	else if (baseType instanceof DGrid)
+	    data = data((DGrid)baseType, copy);
+	else
+	    throw new BadFormException(
+		getClass().getName() + ".data(BaseType,boolean): " +
+		"Unknown DODS type: " + baseType.getTypeName());
+	return data;
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DString var, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DString,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DBoolean var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DBoolean,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DByte var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DByte,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DUInt16 var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DUInt16,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DInt16 var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DInt16,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DUInt32 var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DUInt32,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DInt32 var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DInt32,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DFloat32 var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DFloat32,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DFloat64 var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DFloat64,boolean): " +
+	    "Can't make VisAD data object");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DStructure var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DStructure,boolean): " +
+	    "Can't make VisAD data object from DODS DStructure");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DList var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DList,boolean): " +
+	    "Can't make VisAD data object from DODS DList");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DArray var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DArray,boolean): " +
+	    "Can't make VisAD data object from DODS DArray");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DGrid var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DGrid,boolean): " +
+	    "Can't make VisAD data object from DODS DGrid");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override in subclasses where
+     * appropriate.
+     *
+     * @param var		The DODS variable to have the corresponding
+     *				VisAD data object returned.  The variable
+     *				must be compatible with the variable used to
+     *				construct this instance.
+     * @param copy		If true, then data values are copied.
+     * @return			A VisAD data object corresponding to the data of
+     *				the DODS variable and the metadata of the DODS
+     *				variable used during construction.
+     * @throws VisADException	Don't know how to create a VisAD data object
+     *				from the given DODS variable.
+     */
+    public DataImpl data(DSequence var, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".data(DSequence,boolean): " +
+	    "Can't make VisAD data object from DODS DSequence");
+    }
+
+    /**
+     * Returns the default VisAD {@link Set}s that will be used to represent
+     * this instances data values in the range of a VisAD {@link FlatField}.
+     *
+     * Override this method in subclasses where appropriate.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The default VisAD Sets used to represent the
+     *				data values in the range of a FlatField.
+     *				Will never be <code>null</code> -- though an
+     *				individual elements might be (e.g. for {@link
+     *				TextType} objects).
+     */
+    public SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return copy ? (SimpleSet[])defaultRepSets.clone() : defaultRepSets;
+    }
+
+    /**
+     * Returns the VisAD {@link MathType} corresponding to an array of adapters
+     * of DODS variables.  If the array has zero length, then the returned
+     * MathType will be <code>null</code>; otherwise, if the array has a single
+     * element, then a MathType corresponding to the element will be returned;
+     * otherwise, the returned MathType will be a {@link RealTupleType} or a
+     * {@link TupleType} as appropriate.
+     *
+     * @param adapters		An array of adapters of DODS variables.  May
+     *				not be <code>null</code>, nor may any element
+     *				be <code>null</code>.  May have zero length.
+     * @return			A VisAD MathType corresponding to the array 
+     *				of adapters.
+     */
+    protected static MathType mathType(VariableAdapter[] adapters)
+	throws VisADException, RemoteException
+    {
+	MathType[]	mathTypes = new MathType[adapters.length];
+	for (int i = 0; i < mathTypes.length; ++i)
+	    mathTypes[i] = adapters[i].getMathType();
+	return mathType(mathTypes);
+    }
+}
diff --git a/visad/data/dods/VariableAdapterFactory.java b/visad/data/dods/VariableAdapterFactory.java
new file mode 100644
index 0000000..f109aeb
--- /dev/null
+++ b/visad/data/dods/VariableAdapterFactory.java
@@ -0,0 +1,391 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Provides support for creating adapters that bridge between DODS variables
+ * and the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class VariableAdapterFactory
+{
+    private static final VariableAdapterFactory	instance =
+	new VariableAdapterFactory();
+    private static final VectorAdapterFactory	vectorAdapterFactory =
+	VectorAdapterFactory.vectorAdapterFactory();
+
+    /**
+     * Constructs from nothing.
+     */
+    protected VariableAdapterFactory()
+    {}
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static VariableAdapterFactory variableAdapterFactory()
+    {
+	return instance;
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS variable.
+     *
+     * @param var		A DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public VariableAdapter variableAdapter(BaseType var, DAS das)
+	throws BadFormException, VisADException, RemoteException
+    {
+	VariableAdapter	adapter;
+	if (var instanceof DString)
+	    adapter = stringVariableAdapter((DString)var, das);
+	else if (var instanceof DBoolean)
+	    adapter = booleanVariableAdapter((DBoolean)var, das);
+	else if (var instanceof DByte)
+	    adapter = byteVariableAdapter((DByte)var, das);
+	else if (var instanceof DUInt16)
+	    adapter = uInt16VariableAdapter((DUInt16)var, das);
+	else if (var instanceof DInt16)
+	    adapter = int16VariableAdapter((DInt16)var, das);
+	else if (var instanceof DUInt32)
+	    adapter = uInt32VariableAdapter((DUInt32)var, das);
+	else if (var instanceof DInt32)
+	    adapter = int32VariableAdapter((DInt32)var, das);
+	else if (var instanceof DFloat32)
+	    adapter = float32VariableAdapter((DFloat32)var, das);
+	else if (var instanceof DFloat64)
+	    adapter = float64VariableAdapter((DFloat64)var, das);
+	else if (var instanceof DStructure)
+	    adapter = structureVariableAdapter((DStructure)var, das);
+	else if (var instanceof DList)
+	    adapter = listVariableAdapter((DList)var, das);
+	else if (var instanceof DSequence)
+	    adapter = sequenceVariableAdapter((DSequence)var, das);
+	else if (var instanceof DArray)
+	    adapter = arrayVariableAdapter((DArray)var, das);
+	else if (var instanceof DGrid)
+	    adapter = gridVariableAdapter((DGrid)var, das);
+	else
+	    throw new BadFormException(
+		getClass().getName() + ".variableAdapter(...): " +
+		"Unknown DODS type: " + var.getTypeName());
+	return adapter;
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DString}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public StringVariableAdapter stringVariableAdapter(DString var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return StringVariableAdapter.stringVariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DBoolean}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public BooleanVariableAdapter booleanVariableAdapter(DBoolean var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return BooleanVariableAdapter.booleanVariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DByte}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ByteVariableAdapter byteVariableAdapter(DByte var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return ByteVariableAdapter.byteVariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DUInt16}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt16VariableAdapter uInt16VariableAdapter(DUInt16 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return UInt16VariableAdapter.uInt16VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DInt16}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int16VariableAdapter int16VariableAdapter(DInt16 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return Int16VariableAdapter.int16VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DUInt32}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt32VariableAdapter uInt32VariableAdapter(DUInt32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return UInt32VariableAdapter.uInt32VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DInt32}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int32VariableAdapter int32VariableAdapter(DInt32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return Int32VariableAdapter.int32VariableAdapter(var, das);
+    }
+    /**
+     * Returns the adapter corresponding to a DODS {@link DFloat32}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float32VariableAdapter float32VariableAdapter(DFloat32 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return Float32VariableAdapter.float32VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DFloat64}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float64VariableAdapter float64VariableAdapter(DFloat64 var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return Float64VariableAdapter.float64VariableAdapter(var, das);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DStructure}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public StructureVariableAdapter structureVariableAdapter(
+	    DStructure var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return
+	    StructureVariableAdapter.structureVariableAdapter(var, das, this);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DList}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ListVariableAdapter listVariableAdapter(
+	    DList var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return ListVariableAdapter.listVariableAdapter(var, das, this);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DSequence}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public SequenceVariableAdapter sequenceVariableAdapter(
+	    DSequence var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return
+	    SequenceVariableAdapter.sequenceVariableAdapter(var, das, this);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DArray}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ArrayVariableAdapter arrayVariableAdapter(DArray var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return ArrayVariableAdapter.arrayVariableAdapter(var, das, this);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link DGrid}.
+     *
+     * @param var		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				variable.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public GridVariableAdapter gridVariableAdapter(DGrid var, DAS das)
+	throws VisADException, RemoteException
+    {
+	return GridVariableAdapter.gridVariableAdapter(var, das, this);
+    }
+
+    /**
+     * Returns the adapter corresponding to the coordinate mapping-
+     * vectors of a DODS {@link DGrid}.
+     *
+     * @param array		The coordinate mapping vectors of a DODS {@link
+     *				DGrid}.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the coordinate
+     *				mapping-vectors of the DODS grid.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public GridVariableMapAdapter gridVariableMapAdapter(
+	    DArray array, DAS das)
+	throws VisADException, RemoteException
+    {
+	return 
+	    GridVariableMapAdapter.gridVariableMapAdapter(array, das, this);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link PrimitiveVector}.
+     *
+     * @param vector		An appropriate DODS variable.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS variable is embedded.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public VectorAdapter vectorAdapter(PrimitiveVector vector, DAS das)
+	throws VisADException, RemoteException
+    {
+	return vectorAdapterFactory.vectorAdapter(vector, das, this);
+    }
+}
diff --git a/visad/data/dods/VectorAccessor.java b/visad/data/dods/VectorAccessor.java
new file mode 100644
index 0000000..b9e0472
--- /dev/null
+++ b/visad/data/dods/VectorAccessor.java
@@ -0,0 +1,147 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.*;
+
+/**
+ * Provides support for accessing a DODS primitive vector as a VisAD {@link
+ * visad.data.FileFlatField}.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class VectorAccessor
+    extends	FileAccessor
+{
+    private final FunctionType		funcType;
+    private final VectorAdapter		vectorAdapter;
+    private final SampledSet		domain;
+    private final PrimitiveVector	vector;
+
+    /**
+     * Constructs from a function-type, a vector adapter, a domain and a DODS
+     * primitive vector.
+     *
+     * @param funcType		The function-type for the FlatField.
+     * @param vectorAdapter	The vector adapter corresponding to the DODS
+     *				primitive vector.
+     * @param domain		The domain for the FileFlatField.
+     * @param vector		The DODS primitive vector.
+     */
+    public VectorAccessor(
+	FunctionType funcType,
+	VectorAdapter vectorAdapter,
+	SampledSet domain,
+	PrimitiveVector vector)
+    {
+	this.funcType = funcType;
+	this.vectorAdapter = vectorAdapter;
+	this.domain = domain;
+	this.vector = vector;
+    }
+
+    /*
+     * Returns the VisAD {@link FunctionType} of this instance.
+     *
+     * @return			The FunctionType of this instance.
+     */
+    public FunctionType getFunctionType()
+    {
+	return funcType;
+    }
+
+    /**
+     * Returns a VisAD {@link FlatField} corresponding to this instance.
+     *
+     * @return			A FlatField corresponding to the
+     *				construction arguments.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public FlatField getFlatField()
+	throws VisADException, RemoteException
+    {
+	FlatField	field =
+	    new FlatField(
+		funcType,
+		domain,
+		(CoordinateSystem[])null,
+		vectorAdapter.getRepresentationalSets(false),
+		(Unit[])null);
+	vectorAdapter.setField(vector, field, false);
+	return field;
+    }
+
+    /**
+     * Throws a VisADError.
+     *
+     * @param values		Some values.
+     * @param template		A template FlatField.
+     * @param fileLocation	An array of positional parameters.
+     * @throws VisADError	This method does nothing and should not
+     *				have been invoked.  Always thrown.
+     */
+    public void writeFlatField(
+	double[][] values, FlatField template, int[] fileLocation)
+    {
+	throw new VisADError(
+	    getClass().getName() + ".writeFlatField(...): " +
+	    "Unimplemented method");
+    }
+
+    /**
+     * Throws a VisADError.
+     *
+     * @param template		A template FlatField.
+     * @param fileLocation	An array of positional parameters.
+     * @return			<code>null</code>.
+     * @throws VisADError	This method does nothing and should not
+     *				have been invoked.  Always thrown.
+     */
+    public double[][] readFlatField(FlatField template, int[] fileLocation)
+    {
+	throw new VisADError(
+	    getClass().getName() + ".readFlatField(...): " +
+	    "Unimplemented method");
+    }
+
+    /**
+     * Throws a VisADError.
+     *
+     * @param fileLocation	An array of positional parameters.
+     * @param range		The range of a FlatField.
+     * @throws VisADError	This method does nothing and should not
+     *				have been invoked.  Always thrown.
+     */
+    public void writeFile(int[] fileLocation, Data range)
+    {
+	throw new VisADError(
+	    getClass().getName() + ".writeFile(...): " +
+	    "Unimplemented method");
+    }
+}
diff --git a/visad/data/dods/VectorAdapter.java b/visad/data/dods/VectorAdapter.java
new file mode 100644
index 0000000..d40cc2a
--- /dev/null
+++ b/visad/data/dods/VectorAdapter.java
@@ -0,0 +1,349 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.*;
+
+/**
+ * Provides support for adapting DODS primitive vectors to the VisAD
+ * data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class VectorAdapter
+    extends	Adapter
+{
+    private final VariableAdapter	varAdapter;
+
+    /**
+     * Constructs from a DODS vector and a factory for creating DODS variable
+     * adapters.
+     *
+     * @param vector		A DODS vector to be adapted.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @throws BadFormException	The DODS information is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    protected VectorAdapter(
+	    PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws BadFormException, VisADException, RemoteException
+    {
+	varAdapter = factory.variableAdapter(vector.getTemplate(), das);
+    }
+
+    /**
+     * Returns the adapter of the DODS variable that underlies this instance.
+     *
+     * @return			The adapter of the DODS variable that underlies
+     *				this instance.
+     */
+    protected final VariableAdapter getVariableAdapter()
+    {
+	return varAdapter;
+    }
+
+    /**
+     * Returns the VisAD math-type of this instance.
+     *
+     * @return			The math-type this instance.
+     */
+    public final MathType getMathType()
+    {
+	return varAdapter.getMathType();
+    }
+
+    /**
+     * Indicates whether or not the VisAD {@link MathType} of this instance is
+     * "flat".  A MathType is flat if it comprises a VisAD {@link RealType},
+     * {@link RealTupleType}, or a {@link Tuple} of RealTypes and 
+     * RealTupleTypes.
+     *
+     * @return			<code>true</code> if and only if the MathType of
+     *				this instance is "flat".
+     */
+    public boolean isFlat()
+    {
+	return isFlat(getMathType());
+    }
+
+    /**
+     * Returns the VisAD {@link Set}s that will be used to represent this
+     * instance's data values in the range of a VisAD {@link FlatField}.
+     *
+     * @param copy		If true, then the array is cloned.
+     * @return			The VisAD Sets used to represent the data values
+     *				in the range of a FlatField.  WARNING: Modify
+     *				the returned array only under extreme duress.
+     */
+    public final SimpleSet[] getRepresentationalSets(boolean copy)
+    {
+	return varAdapter.getRepresentationalSets(copy);
+    }
+
+    /**
+     * Sets the range of a compatible VisAD {@link Field}.  The range values are
+     * taken from a DODS primitive vector whose metadata must be compatible with
+     * the metadata of the primitive vector used during construction of this
+     * instance.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public void setField(PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException
+    {
+	if (vector instanceof BooleanPrimitiveVector)
+	    setField((BooleanPrimitiveVector)vector, field, copy);
+	else if (vector instanceof BytePrimitiveVector)
+	    setField((BytePrimitiveVector)vector, field, copy);
+	else if (vector instanceof UInt16PrimitiveVector)
+	    setField((UInt16PrimitiveVector)vector, field, copy);
+	else if (vector instanceof Int16PrimitiveVector)
+	    setField((Int16PrimitiveVector)vector, field, copy);
+	else if (vector instanceof UInt32PrimitiveVector)
+	    setField((UInt32PrimitiveVector)vector, field, copy);
+	else if (vector instanceof Int32PrimitiveVector)
+	    setField((Int32PrimitiveVector)vector, field, copy);
+	else if (vector instanceof Float32PrimitiveVector)
+	    setField((Float32PrimitiveVector)vector, field, copy);
+	else if (vector instanceof Float64PrimitiveVector)
+	    setField((Float64PrimitiveVector)vector, field, copy);
+	else
+	    setField((BaseTypePrimitiveVector)vector, field, copy);
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    BooleanPrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(BooleanPrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @param field		A VisAD field to have its range values set.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    BytePrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(BytePrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    UInt16PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(UInt16PrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    Int16PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(Int16PrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    UInt32PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(UInt32PrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    Int32PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(Int32PrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    Float32PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(Float32PrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     */
+    public void setField(
+	    Float64PrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(Float64PrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @param field		A VisAD field to have its range values set.
+     * @param copy		If true, then the range values are copied from
+     *				the primitive vector.
+     * @throws VisADException	The vector has the wrong DODS type.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public void setField(
+	    BaseTypePrimitiveVector vector, FieldImpl field, boolean copy)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".setField(BaseTypePrimitiveVector,...): " +
+	    "Wrong type of vector");
+    }
+
+    /**
+     * Throws a {@link VisADException}.  Override this method in subclasses
+     * where appropriate.
+     *
+     * @param vector		A DODS primitive vector whose data values are
+     *				to be used to set the range of the VisAD field.
+     * @throws VisADException	The vector has the wrong DODS type.
+     * @throws RemoteException	Java RMI failure.
+     */
+    
+    public GriddedSet griddedSet(PrimitiveVector vector)
+	throws VisADException, RemoteException
+    {
+	throw new VisADException(
+	    getClass().getName() + ".griddedSet(PrimitiveVector): " +
+	    "Wrong type of vector");
+    }
+}
diff --git a/visad/data/dods/VectorAdapterFactory.java b/visad/data/dods/VectorAdapterFactory.java
new file mode 100644
index 0000000..b535a76
--- /dev/null
+++ b/visad/data/dods/VectorAdapterFactory.java
@@ -0,0 +1,324 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.dods;
+
+import dods.dap.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for creating adapters that bridge between DODS primitive
+ * vectors and the VisAD data-import context.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class VectorAdapterFactory
+{
+    private static final VectorAdapterFactory	instance =
+	new VectorAdapterFactory();
+
+    /**
+     * Constructs from nothing.
+     */
+    protected VectorAdapterFactory()
+    {}
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static VectorAdapterFactory vectorAdapterFactory()
+    {
+	return instance;
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS primitive vector.
+     *
+     * @param vector		A DODS primitive vector.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public VectorAdapter vectorAdapter(
+	    PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	VectorAdapter	adapter;
+	if (vector instanceof BooleanPrimitiveVector)
+	    adapter =
+		booleanVectorAdapter(
+		    (BooleanPrimitiveVector)vector, das, factory);
+	else if (vector instanceof BytePrimitiveVector)
+	    adapter =
+		byteVectorAdapter(
+		    (BytePrimitiveVector)vector, das, factory);
+	else if (vector instanceof UInt16PrimitiveVector)
+	    adapter =
+		uInt16VectorAdapter(
+		    (UInt16PrimitiveVector)vector, das, factory);
+	else if (vector instanceof Int16PrimitiveVector)
+	    adapter =
+		int16VectorAdapter(
+		    (Int16PrimitiveVector)vector, das, factory);
+	else if (vector instanceof UInt32PrimitiveVector)
+	    adapter =
+		uInt32VectorAdapter(
+		    (UInt32PrimitiveVector)vector, das, factory);
+	else if (vector instanceof Int32PrimitiveVector)
+	    adapter =
+		int32VectorAdapter(
+		    (Int32PrimitiveVector)vector, das, factory);
+	else if (vector instanceof Float32PrimitiveVector)
+	    adapter =
+		float32VectorAdapter(
+		    (Float32PrimitiveVector)vector, das, factory);
+	else if (vector instanceof Float64PrimitiveVector)
+	    adapter =
+		float64VectorAdapter(
+		    (Float64PrimitiveVector)vector, das, factory);
+	else
+	    adapter =
+		baseTypeVectorAdapter(
+		    (BaseTypePrimitiveVector)vector, das, factory);
+	return adapter;
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * BooleanPrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public BooleanVectorAdapter booleanVectorAdapter(
+	    BooleanPrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new BooleanVectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * BytePrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public ByteVectorAdapter byteVectorAdapter(
+	    BytePrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new ByteVectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * UInt16PrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt16VectorAdapter uInt16VectorAdapter(
+	    UInt16PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new UInt16VectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * Int16PrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int16VectorAdapter int16VectorAdapter(
+	    Int16PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new Int16VectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * UInt32PrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public UInt32VectorAdapter uInt32VectorAdapter(
+	    UInt32PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new UInt32VectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * Int32PrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Int32VectorAdapter int32VectorAdapter(
+	    Int32PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new Int32VectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * Float32PrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float32VectorAdapter float32VectorAdapter(
+	    Float32PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new Float32VectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * Float64PrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public Float64VectorAdapter float64VectorAdapter(
+	    Float64PrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return new Float64VectorAdapter(vector, das, factory);
+    }
+
+    /**
+     * Returns the adapter corresponding to a DODS {@link
+     * BaseTypePrimitiveVector}.
+     *
+     * @param vector		A DODS primitive vector of the appropriate type.
+     * @param das		The DODS DAS in which the attribute
+     *				table for the DODS vector is embedded.
+     * @param factory		A factory for creating adapters of DODS
+     *				variables.
+     * @return			The adapter corresponding to the DODS
+     *				primitive vector.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public BaseTypeVectorAdapter baseTypeVectorAdapter(
+	    BaseTypePrimitiveVector vector,
+	    DAS das,
+	    VariableAdapterFactory factory)
+	throws VisADException, RemoteException
+    {
+	return
+	    BaseTypeVectorAdapter.baseTypeVectorAdapter(vector, das, factory);
+    }
+}
diff --git a/visad/data/dods/package.html b/visad/data/dods/package.html
new file mode 100644
index 0000000..f080a7e
--- /dev/null
+++ b/visad/data/dods/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+Supports read-only access to datasets on DODS servers by
+importing such datasets as VisAD data objects.
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/data/fits/ConvertArray.java b/visad/data/fits/ConvertArray.java
new file mode 100644
index 0000000..eff6adf
--- /dev/null
+++ b/visad/data/fits/ConvertArray.java
@@ -0,0 +1,258 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+class BooleanArrayConverter
+	extends GenericArrayConverter
+{
+  public BooleanArrayConverter(int[] lengths)
+  {
+    super(Boolean.TYPE, lengths);
+  }
+
+  void assign(Object obj, int i, double v)
+  {
+    ((boolean[] )obj)[i] = v == 0.0 ? false : true;
+  }
+}
+
+class ByteArrayConverter
+	extends GenericArrayConverter
+{
+  boolean unsigned;
+
+  public ByteArrayConverter(int[] lengths, boolean unsigned)
+  {
+    super(Byte.TYPE, lengths);
+
+    this.unsigned = unsigned;
+  }
+
+  void assign(Object obj, int i, double v)
+  {
+    if (unsigned && v > Byte.MAX_VALUE) {
+      v = Byte.MAX_VALUE - v;
+    }
+
+    ((byte[] )obj)[i] = (byte )v;
+  }
+}
+
+class ShortArrayConverter
+	extends GenericArrayConverter
+{
+  boolean unsigned;
+
+  public ShortArrayConverter(int[] lengths, boolean unsigned)
+  {
+    super(Short.TYPE, lengths);
+
+    this.unsigned = unsigned;
+  }
+
+  void assign(Object obj, int i, double v)
+  {
+    if (unsigned && v > Short.MAX_VALUE) {
+      v = Short.MAX_VALUE - v;
+    }
+
+    ((short[] )obj)[i] = (short )v;
+  }
+}
+
+class IntegerArrayConverter
+	extends GenericArrayConverter
+{
+  boolean unsigned;
+
+  public IntegerArrayConverter(int[] lengths, boolean unsigned)
+  {
+    super(Integer.TYPE, lengths);
+
+    this.unsigned = unsigned;
+  }
+
+  void assign(Object obj, int i, double v)
+  {
+    if (unsigned && v > Integer.MAX_VALUE) {
+      v = Integer.MAX_VALUE - v;
+    }
+
+    ((int[] )obj)[i] = (int )v;
+  }
+}
+
+class LongArrayConverter
+	extends GenericArrayConverter
+{
+  public LongArrayConverter(int[] lengths)
+  {
+    super(Long.TYPE, lengths);
+  }
+
+  void assign(Object obj, int i, double v)
+  {
+    ((long[] )obj)[i] = (long )v;
+  }
+}
+
+class FloatArrayConverter
+	extends GenericArrayConverter
+{
+  public FloatArrayConverter(int[] lengths)
+  {
+    super(Float.TYPE, lengths);
+  }
+
+  void assign(Object obj, int i, double v)
+  {
+    ((float[] )obj)[i] = (float )v;
+  }
+}
+
+class DoubleArrayConverter
+	extends GenericArrayConverter
+{
+  public DoubleArrayConverter(int[] lengths)
+  {
+    super(Double.TYPE, lengths);
+  }
+
+  void assign(Object obj, int i, double v)
+  {
+    ((double[] )obj)[i] = v;
+  }
+}
+
+public abstract class ConvertArray
+{
+  private static final int UNSIGNED_ARRAY =	0x1000;
+  private static final int NONINTEGRAL_ARRAY =	0x2000;
+
+  private static final int BOOLEAN_ARRAY =	0x0001;
+  private static final int BYTE_ARRAY =		0x0002;
+  private static final int UBYTE_ARRAY =	0x1002;
+  private static final int SHORT_ARRAY =	0x0004;
+  private static final int USHORT_ARRAY =	0x1004;
+  private static final int INT_ARRAY =		0x0008;
+  private static final int UINT_ARRAY =		0x1008;
+  private static final int LONG_ARRAY =		0x0010;
+  private static final int FLOAT_ARRAY =	0x2001;
+  private static final int DOUBLE_ARRAY =	0x2002;
+
+  private boolean analyzed = false;
+
+  int[] lengths = null;
+
+  private int arrayType;
+
+  int getArrayType(double min, double max, boolean integral)
+  {
+    // is it an array of real numbers?
+    if (!integral) {
+      // WLH 2 May 2000
+      // if (min >= Float.MIN_VALUE && max <= Float.MAX_VALUE) {
+      if (min >= -Float.MAX_VALUE && max <= Float.MAX_VALUE) {
+	return FLOAT_ARRAY;
+      }
+
+      return DOUBLE_ARRAY;
+    }
+
+    // is it possibly unsigned?
+    if (min >= 0) {
+      if (max <= 1) {
+	return BOOLEAN_ARRAY;
+      }
+
+      if (max <= (Byte.MAX_VALUE * 2) + 1) {
+	return UBYTE_ARRAY;
+      }
+
+      if (max <= (Short.MAX_VALUE * 2) + 1) {
+	return USHORT_ARRAY;
+      }
+
+      if (max <= ((long )Integer.MAX_VALUE * 2) + 1) {
+	return UINT_ARRAY;
+      }
+    }
+
+    if (min >= Byte.MIN_VALUE && max <= Byte.MAX_VALUE) {
+      return BYTE_ARRAY;
+    }
+
+    if (min >= Short.MIN_VALUE && max <= Short.MAX_VALUE) {
+      return SHORT_ARRAY;
+    }
+
+    if (min >= Integer.MIN_VALUE && max <= Integer.MAX_VALUE) {
+      return INT_ARRAY;
+    }
+
+    return LONG_ARRAY;
+  }
+
+  abstract int analyzeArray();
+
+  private void analyze()
+  {
+    if (analyzed) {
+      return;
+    }
+
+    arrayType = analyzeArray();
+  }
+
+  public GenericArrayConverter getConverter()
+  {
+    analyze();
+
+    switch (arrayType) {
+    case BOOLEAN_ARRAY:
+      return new BooleanArrayConverter(lengths);
+    case BYTE_ARRAY:
+      return new ByteArrayConverter(lengths, false);
+    case UBYTE_ARRAY:
+      return new ByteArrayConverter(lengths, true);
+    case SHORT_ARRAY:
+      return new ShortArrayConverter(lengths, false);
+    case USHORT_ARRAY:
+      return new ShortArrayConverter(lengths, true);
+    case INT_ARRAY:
+      return new IntegerArrayConverter(lengths, false);
+    case UINT_ARRAY:
+      return new IntegerArrayConverter(lengths, true);
+    case LONG_ARRAY:
+      return new LongArrayConverter(lengths);
+    case FLOAT_ARRAY:
+      return new FloatArrayConverter(lengths);
+    case DOUBLE_ARRAY:
+      return new DoubleArrayConverter(lengths);
+    default:
+      break;
+    }
+
+    return null;
+  }
+}
diff --git a/visad/data/fits/ConvertDoubleArray.java b/visad/data/fits/ConvertDoubleArray.java
new file mode 100644
index 0000000..c37fd4d
--- /dev/null
+++ b/visad/data/fits/ConvertDoubleArray.java
@@ -0,0 +1,81 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import visad.FlatField;
+import visad.GriddedSet;
+import visad.Set;
+import visad.VisADException;
+
+public class ConvertDoubleArray
+	extends ConvertArray
+{
+  private double[][] values;
+
+  public ConvertDoubleArray(FlatField fld)
+	throws VisADException
+  {
+    Set set = fld.getDomainSet();
+    if (!(set instanceof GriddedSet)) {
+      throw new VisADException("Cannot convert non-GriddedSet FlatField");
+    }
+
+    lengths = ((GriddedSet )set).getLengths();
+    values = fld.getValues();
+  }
+
+  public ConvertDoubleArray(int[] lengths, double[][] values)
+	throws VisADException
+  {
+    this.lengths = lengths;
+    this.values = values;
+  }
+
+  int analyzeArray()
+  {
+    double max = values[0][0];
+    double min = values[0][0];
+
+    boolean integral = true;
+    for (int i = 0; i < values.length; i++) {
+      for (int j = 0; j < values[i].length; j++) {
+	double v = values[i][j];
+	if (v > max) {
+	  max = v;
+	}
+	if (v < min) {
+	  min = v;
+	}
+	if (v >= Long.MIN_VALUE && v <= Long.MAX_VALUE) {
+	  if (v % 1 > 0) {
+	    integral = false;
+	  }
+	} else {
+	  integral = false;
+	}
+      }
+    }
+
+    return getArrayType(min, max, integral);
+  }
+}
diff --git a/visad/data/fits/DumpHeader.java b/visad/data/fits/DumpHeader.java
new file mode 100644
index 0000000..ee090dd
--- /dev/null
+++ b/visad/data/fits/DumpHeader.java
@@ -0,0 +1,358 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import nom.tam.fits.BadHeaderException;
+import nom.tam.fits.BasicHDU;
+import nom.tam.fits.BinaryTableHDU;
+import nom.tam.fits.Data;
+import nom.tam.fits.ExtensionHDU;
+import nom.tam.fits.Fits;
+import nom.tam.fits.FitsException;
+import nom.tam.fits.ImageHDU;
+import nom.tam.fits.PrimaryHDU;
+import nom.tam.fits.RandomGroupsHDU;
+import nom.tam.fits.TruncatedFileException;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+import java.util.Date;
+
+public class DumpHeader
+{
+  private static void dumpBasic(PrintStream ps, String indentStr,
+				BasicHDU hdu)
+  {
+    int bitpix;
+    try {
+      bitpix = hdu.getBitPix();
+
+      String bpName;
+      switch (bitpix) {
+      case BasicHDU.BITPIX_BYTE:
+	bpName = "byte";
+	break;
+      case BasicHDU.BITPIX_SHORT:
+	bpName = "short";
+	break;
+      case BasicHDU.BITPIX_INT:
+	bpName = "int";
+	break;
+      case BasicHDU.BITPIX_FLOAT:
+	bpName = "float";
+	break;
+      case BasicHDU.BITPIX_DOUBLE:
+	bpName = "double";
+	break;
+      default:
+	bpName = "?" + bitpix + '?';
+	break;
+      }
+      ps.println(indentStr + "bitpix " + bpName);
+    } catch (FitsException e) {
+      ps.println(indentStr + "bitpix *** " + e.getMessage());
+    }
+
+    int[] axes;
+    try {
+      axes = hdu.getAxes();
+    } catch (FitsException e) {
+      System.err.println("Bad axes: " + e.getMessage());
+      axes = null;
+    }
+    if (axes != null) {
+      ps.print(indentStr + "axes ");
+      for (int i = 0; i < axes.length; i++) {
+	ps.print((i == 0 ? "" : "x") + axes[i]);
+      }
+      ps.println("");
+    }
+
+    int paramCount = hdu.getParameterCount();
+    if (paramCount != 0) {
+      ps.println(indentStr + "paramCount " + paramCount);
+    }
+
+    int groupCount = hdu.getGroupCount();
+    if (groupCount != 1) {
+      ps.println(indentStr + "groupCount " + groupCount);
+    }
+
+    double bzero = hdu.getBZero();
+    if (bzero != 0.0) {
+      ps.println(indentStr + "bzero " + bzero);
+    }
+
+    double bscale = hdu.getBScale();
+    if (bscale != 1.0) {
+      ps.println(indentStr + "bscale " + bscale);
+    }
+
+    String bunit = hdu.getBUnit();
+    if (bunit != null) {
+      ps.println(indentStr + "bunit " + bunit);
+    }
+
+    try {
+      int blankValue = hdu.getBlankValue();
+      ps.println(indentStr + "blank " + blankValue);
+    } catch (FitsException e) {
+    }
+
+    Date creation = hdu.getCreationDate();
+    if (creation != null) {
+      ps.println(indentStr + "creation date " + creation);
+    }
+
+    Date observation = hdu.getObservationDate();
+    if (observation != null) {
+      ps.println(indentStr + "observation date " + observation);
+    }
+
+    String origin = hdu.getOrigin();
+    if (origin != null) {
+      ps.println(indentStr + "origin " + origin);
+    }
+
+    String telescope = hdu.getTelescope();
+    if (telescope != null) {
+      ps.println(indentStr + "telescope " + telescope);
+    }
+
+    String instrument = hdu.getInstrument();
+    if (instrument != null) {
+      ps.println(indentStr + "instrument " + instrument);
+    }
+
+    String observer = hdu.getObserver();
+    if (observer != null) {
+      ps.println(indentStr + "observer " + observer);
+    }
+
+    String object = hdu.getObject();
+    if (object != null) {
+      ps.println(indentStr + "object " + object);
+    }
+
+    double equinox = hdu.getEquinox();
+    if (equinox != -1.0) {
+      ps.println(indentStr + "equinox " + equinox);
+    }
+
+    String author = hdu.getAuthor();
+    if (author != null) {
+      ps.println(indentStr + "author " + author);
+    }
+
+    String reference = hdu.getReference();
+    if (reference != null) {
+      ps.println(indentStr + "reference " + reference);
+    }
+
+    double maxValue = hdu.getMaximumValue();
+    if (maxValue != 0.0) {
+      ps.println(indentStr + "maximum value " + maxValue);
+    }
+
+    double minValue = hdu.getMinimumValue();
+    if (minValue != 0.0) {
+      ps.println(indentStr + "minimum value " + minValue);
+    }
+  }
+
+  private static void dumpPrimary(PrintStream ps, String indentStr,
+				  PrimaryHDU hdu)
+	throws IOException
+  {
+    dumpBasic(ps, indentStr, (BasicHDU )hdu);
+
+    Data foo = hdu.getData();
+  }
+
+  private static void dumpBinaryTable(PrintStream ps, String indentStr,
+				      BinaryTableHDU hdu)
+  {
+    int num = hdu.getNumColumns();
+    if (num == 0) {
+      ps.println(indentStr + "No columns");
+      return;
+    }
+
+    for (int i = 0; i < num; i++) {
+      String name, type;
+      try {
+	name = hdu.getColumnName(i);
+	type = hdu.getColumnFITSType(i);
+      } catch (FitsException e) {
+	break;
+      }
+
+      ps.println(indentStr + i + ": " + name + " = " + type);
+    }
+  }
+
+  private static void dumpExtension(PrintStream ps, String indentStr,
+				    ExtensionHDU hdu)
+	throws IOException
+  {
+    dumpBasic(ps, indentStr, (BasicHDU )hdu);
+
+    String name = hdu.getExtensionName();
+    if (name != null) {
+      ps.println(indentStr + "name " + name);
+    }
+
+    int vers = hdu.getExtensionVersion();
+    if (vers != 1) {
+      ps.println(indentStr + "version " + vers);
+    }
+
+    int level = hdu.getExtensionLevel();
+    if (level != 1) {
+      ps.println(indentStr + "level " + level);
+    }
+
+    if (hdu instanceof BinaryTableHDU) {
+      ps.println(indentStr + "Binary Table:");
+      dumpBinaryTable(ps, indentStr + indentStr, (BinaryTableHDU )hdu);
+    } else {
+      try {
+	String type = hdu.getExtensionType();
+	if (type == null) {
+	  ps.println(indentStr + "Null extension type");
+	} else {
+	  ps.println(indentStr + "type " + type);
+	}
+      } catch (FitsException e) {
+	ps.println(indentStr + indentStr + "Bad extension type: " +
+		   e.getMessage());
+      }
+    }
+
+    Data foo = hdu.getData();
+  }
+
+  private static void dumpImage(PrintStream ps, String indentStr, ImageHDU hdu)
+	throws IOException
+  {
+    dumpBasic(ps, indentStr, (BasicHDU )hdu);
+
+    ps.println(indentStr + "...");
+
+    Data foo = hdu.getData();
+  }
+
+  private static void dumpRandomGroups(PrintStream ps, String indentStr,
+				       RandomGroupsHDU hdu)
+	throws IOException
+  {
+    dumpBasic(ps, indentStr, (BasicHDU )hdu);
+
+    ps.println(indentStr + "...");
+
+    Data foo = hdu.getData();
+  }
+
+  public static void dump(PrintStream ps, String name)
+	throws FitsException, IOException
+  {
+    Fits fits;
+    try {
+      fits = new Fits(name);
+    } catch (FitsException e) {
+      System.err.println("Couldn't open \"" + name + "\": " + e.getMessage());
+      return;
+    }
+
+    ps.println(name + ':');
+
+    BasicHDU hdu;
+    for (int hduNum = 0; true; hduNum++) {
+      try {
+	hdu = fits.readHDU();
+      } catch (OutOfMemoryError e) {
+	System.err.println("  *** Out of memory for HDU #" + hduNum);
+	e.printStackTrace(System.err);
+	break;
+      } catch (TruncatedFileException e) {
+	System.err.println("  *** File truncated at HDU #" + hduNum + " (" + e.getMessage() + ")");
+	break;
+      } catch (IOException e) {
+	System.err.println("  *** I/O error at HDU #" + hduNum + " (" + e.getMessage() + ")");
+	break;
+      } catch (BadHeaderException e) {
+	System.err.println("  *** HDU #" + hduNum + " threw " + e.getMessage());
+	continue;
+      } catch (FitsException e) {
+	System.err.println("  *** HDU #" + hduNum + " threw " + e.getMessage());
+	continue;
+      }
+
+      if (hdu == null)  {
+	break;
+      }
+
+      String indentStr = "\t";
+
+      if (hdu instanceof PrimaryHDU) {
+	if (hduNum == 0) {
+	  ps.println(indentStr + "Primary:");
+	} else {
+	  ps.println(indentStr + "Primary " + hduNum + ':');
+	}
+	ps.flush();
+	dumpPrimary(ps, indentStr + indentStr, (PrimaryHDU )hdu);
+      } else if (hdu instanceof ExtensionHDU) {
+	ps.println(indentStr + "Extension " + hduNum + ':');
+	ps.flush();
+	dumpExtension(ps, indentStr + indentStr, (ExtensionHDU )hdu);
+      } else if (hdu instanceof ImageHDU) {
+	ps.println(indentStr + "Image " + hduNum + ':');
+	ps.flush();
+	dumpImage(ps, indentStr + indentStr, (ImageHDU )hdu);
+      } else if (hdu instanceof RandomGroupsHDU) {
+	ps.println(indentStr + "RandomGroups " + hduNum + ':');
+	ps.flush();
+	dumpRandomGroups(ps, indentStr + indentStr, (RandomGroupsHDU )hdu);
+      } else {
+	throw new FitsException("Unknown header found: " + hdu);
+      }
+    }
+  }
+
+  public static void main(String[] args)
+  {
+    for (int i = 0; i < args.length; i++) {
+      try {
+	dump(System.out, args[i]);
+      } catch (OutOfMemoryError e) {
+	e.printStackTrace(System.out);
+      } catch (FitsException e) {
+	e.printStackTrace(System.out);
+      } catch (IOException e) {
+	e.printStackTrace(System.out);
+      }
+    }
+  }
+}
diff --git a/visad/data/fits/ExceptionStack.java b/visad/data/fits/ExceptionStack.java
new file mode 100644
index 0000000..282d5e8
--- /dev/null
+++ b/visad/data/fits/ExceptionStack.java
@@ -0,0 +1,131 @@
+//
+// ExceptionStack.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+import visad.VisADException;
+
+public class ExceptionStack
+	extends VisADException
+{
+  Vector vec;
+
+  public ExceptionStack()
+  {
+    super("Exception stack thrown");
+    vec = new Vector();
+  }
+
+  public ExceptionStack(Exception e)
+  {
+    super("Exception stack thrown");
+    vec = new Vector();
+    vec.addElement(e);
+  }
+
+  public void addException(Exception e)
+  {
+    vec.addElement(e);
+  }
+
+  public int depth()
+  {
+    return vec.size();
+  }
+
+  public Enumeration exceptions()
+  {
+    return vec.elements();
+  }
+
+/*
+  public String getMessage()
+  {
+    StringBuffer buf = new StringBuffer();
+
+    Enumeration en = vec.elements();
+    while (en.hasMoreElements()) {
+      Exception e = (Exception )en.nextElement();
+
+      buf.append(e.getMessage());
+      buf.append('\n');
+    }
+
+    // delete final newline
+    buf.setLength(buf.length()-1);
+
+    return buf.toString();
+  }
+
+  public String getLocalizedMessage()
+  {
+    StringBuffer buf = new StringBuffer();
+
+    Enumeration en = vec.elements();
+    while (en.hasMoreElements()) {
+      Exception e = (Exception )en.nextElement();
+
+      buf.append(e.getLocalizedMessage());
+      buf.append('\n');
+    }
+
+    // delete final newline
+    buf.setLength(buf.length()-1);
+
+    return buf.toString();
+  }
+*/
+
+  public void printStackTrace()
+  {
+    printStackTrace(System.err);
+  }
+
+  public void printStackTrace(java.io.PrintStream ps)
+  {
+    Enumeration en = vec.elements();
+    while (en.hasMoreElements()) {
+      Exception e = (Exception )en.nextElement();
+
+      e.printStackTrace(ps);
+    }
+    super.printStackTrace(ps);
+  }
+
+  public void printStackTrace(java.io.PrintWriter pw)
+  {
+    Enumeration en = vec.elements();
+    while (en.hasMoreElements()) {
+      Exception e = (Exception )en.nextElement();
+
+      e.printStackTrace(pw);
+    }
+    super.printStackTrace(pw);
+  }
+}
diff --git a/visad/data/fits/FitsAdapter.java b/visad/data/fits/FitsAdapter.java
new file mode 100644
index 0000000..07e752a
--- /dev/null
+++ b/visad/data/fits/FitsAdapter.java
@@ -0,0 +1,567 @@
+//
+// FitsAdapter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import nom.tam.fits.BasicHDU;
+import nom.tam.fits.BinaryTableHDU;
+import nom.tam.fits.Fits;
+import nom.tam.fits.FitsException;
+import nom.tam.fits.ImageHDU;
+import nom.tam.fits.PrimaryHDU;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import java.lang.reflect.Array;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+import visad.Data;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.Integer2DSet;
+import visad.IntegerNDSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.VisADException;
+
+public class FitsAdapter
+{
+  Fits fits;
+  Data data[];
+  ExceptionStack stack;
+
+  public FitsAdapter()
+	throws VisADException
+  {
+    fits = null;
+    data = null;
+    stack = null;
+  }
+
+  public FitsAdapter(String filename)
+	throws VisADException
+  {
+    this();
+
+    try {
+      fits = new Fits(filename);
+    } catch (FitsException e) {
+      throw new VisADException(e.getClass().getName() + "(" + e.getMessage() +
+			       ")");
+    }
+  }
+
+  public FitsAdapter(URL url)
+	throws VisADException
+  {
+    this();
+
+    try {
+      fits = new Fits(url);
+    } catch (FitsException e) {
+      throw new VisADException(e.getClass().getName() + "(" + e.getMessage() +
+			       ")");
+    }
+  }
+
+  private int get1DLength(Object data)
+	throws VisADException
+  {
+    if (!data.getClass().isArray()) {
+      return 1;
+    }
+
+    int len = Array.getLength(data);
+
+    int total = 0;
+    for (int i = 0; i < len; i++) {
+      total += get1DLength(Array.get(data, i));
+    }
+
+    return total;
+  }
+
+  private int copyArray(Object data, double[] list, int offset)
+	throws VisADException
+  {
+    if (data instanceof byte[]) {
+      byte[] bl = (byte[] )data;
+      for (int i = 0; i < bl.length; i++) {
+	int val = (bl[i] >= 0 ? bl[i] :
+                   (((int )Byte.MAX_VALUE + 1) * 2 + (int )bl[i]));
+	list[offset++] = (double )val;
+      }
+    } else if (data instanceof short[]) {
+      short[] sl = (short[] )data;
+      for (int i = 0; i < sl.length; i++) {
+	int val = (sl[i] >= 0 ? sl[i] : ((Short.MAX_VALUE + 1) * 2) - sl[i]);
+	list[offset++] = (double )val;
+      }
+    } else if (data instanceof int[]) {
+      int[] il = (int[] )data;
+      for (int i = 0; i < il.length; i++) {
+	list[offset++] = (double )il[i];
+      }
+    } else if (data instanceof long[]) {
+      long[] ll = (long[] )data;
+      for (int i = 0; i < ll.length; i++) {
+	list[offset++] = (double )ll[i];
+      }
+    } else if (data instanceof float[]) {
+      float[] fl = (float[] )data;
+      for (int i = 0; i < fl.length; i++) {
+	list[offset++] = (double )fl[i];
+      }
+    } else if (data instanceof double[]) {
+      double[] dl = (double[] )data;
+      for (int i = 0; i < dl.length; i++) {
+	list[offset++] = dl[i];
+      }
+    } else {
+      throw new VisADException("type '" + data.getClass().getName() +
+			       "' not handled");
+    }
+
+    return offset;
+  }
+
+  private int decompose(Object data, double[] list, int offset)
+	throws VisADException
+  {
+    Class component = data.getClass().getComponentType();
+    if (component == null) {
+      return offset;
+    }
+
+    if (!component.isArray()) {
+      return copyArray(data, list, offset);
+    }
+
+    int len = Array.getLength(data);
+    for (int i = len - 1; i >= 0; i--) {
+      offset = decompose(Array.get(data, i), list, offset);
+    }
+
+    return offset;
+  }
+
+  private double[][] buildRange(Object data)
+	throws VisADException
+  {
+    int len = get1DLength(data);
+
+    double[] values = new double[len];
+
+    int offset = decompose(data, values, 0);
+    while (offset < len) {
+      values[offset++] = Double.NaN;
+    }
+
+    double[][] range = new double[1][];
+    range[0] = values;
+
+    return range;
+  }
+
+  private Data addPrimary(PrimaryHDU hdu)
+	throws FitsException, VisADException, RemoteException
+  {
+    int[] axes = hdu.getAxes();
+    if (axes == null || axes.length == 0) {
+      return null;
+    }
+
+    // reverse order of axes
+    for (int i = 0; i < axes.length / 2; i++) {
+      int j = axes.length - (i + 1);
+      int tmp = axes[j];
+
+      axes[j] = axes[i];
+      axes[i] = tmp;
+    }
+
+    Object fData = hdu.getData().getData();
+    if (fData == null) {
+      throw new VisADException("No HDU Data");
+    }
+    if (!fData.getClass().isArray()) {
+      throw new VisADException("Unknown HDU Data type: " +
+			       fData.getClass().getName());
+    }
+
+    RealType axisType[] = new RealType[axes.length];
+    for (int i = 0; i < axisType.length; i++) {
+      String name = "NAxis" + (i+1);
+
+      axisType[i] = RealType.getRealType(name, null, null);
+    }
+
+    RealTupleType type = new RealTupleType(axisType);;
+
+    RealType value = RealType.getRealType("value", null, null);
+
+    FunctionType func = new FunctionType(type, value);
+
+    IntegerNDSet iSet = new IntegerNDSet(type, axes);
+
+    FlatField fld = new FlatField(func, iSet);
+
+    fld.setSamples(buildRange(fData));
+
+    return fld;
+  }
+
+  private Data addImage(ImageHDU hdu)
+	throws VisADException, RemoteException
+  {
+    int[] axes;
+    try {
+      axes = hdu.getAxes();
+    } catch (FitsException e) {
+      axes = null;
+    }
+
+    if (axes == null) {
+      throw new VisADException("Couldn't get image axes");
+    }
+    if (axes.length != 2) {
+      throw new VisADException("Expected two-dimensional image, not " +
+                               axes.length +" dimensions");
+    }
+
+    Object fData = hdu.getData().getData();
+    if (fData == null) {
+      throw new VisADException("No HDU Data");
+    }
+    if (!fData.getClass().isArray()) {
+      throw new VisADException("Unknown HDU Data type: " +
+			       fData.getClass().getName());
+    }
+
+    RealTupleType type = RealTupleType.SpatialCartesian2DTuple;
+
+    RealType pixel = RealType.getRealType("pixel", null, null);
+
+    FunctionType func = new FunctionType(type, pixel);
+
+    Integer2DSet iSet = new Integer2DSet(type, axes[0], axes[1]);
+
+    FlatField fld = new FlatField(func, iSet);
+
+    fld.setSamples(buildRange(fData));
+
+    return fld;
+  }
+
+  private int copyColumn(Object data, double[] list, int offset)
+	throws VisADException
+  {
+    // punt if this isn't a 1D column
+    Object[] top = (Object[] )data;
+    if (top.length != 1 && !(top[0] instanceof byte[])) {
+      System.err.println("FitsAdapter.copyColumn: Punting on wide column (" +
+			 top[0].getClass().getName() + ")");
+      return offset;
+    }
+
+    if (top[0] instanceof byte[]) {
+      if (top.length != 1) {
+	System.err.println("Ignoring assumed " + top.length +
+			   "-char String column");
+	return offset;
+      } else {
+	byte[] bl = (byte[] )top[0];
+	for (int i = 0; i < bl.length; ) {
+	  list[offset++] = (double )bl[i++];
+	}
+      }
+    } else if (top[0] instanceof short[]) {
+      short[] sl = (short[] )top[0];
+      for (int i = 0; i < sl.length; ) {
+	list[offset++] = (double )sl[i++];
+      }
+    } else if (top[0] instanceof int[]) {
+      int[] il = (int[] )top[0];
+      for (int i = 0; i < il.length; ) {
+	list[offset++] = (double )il[i++];
+      }
+    } else if (top[0] instanceof long[]) {
+      long[] ll = (long[] )top[0];
+      for (int i = 0; i < ll.length; ) {
+	list[offset++] = (double )ll[i++];
+      }
+    } else if (top[0] instanceof float[]) {
+      float[] fl = (float[] )top[0];
+      for (int i = 0; i < fl.length; ) {
+	list[offset++] = (double )fl[i++];
+      }
+    } else if (top[0] instanceof double[]) {
+      double[] dl = (double[] )top[0];
+      for (int i = 0; i < dl.length; ) {
+	list[offset++] = dl[i++];
+      }
+    } else {
+      throw new VisADException("type '" + top[0].getClass().getName() +
+			       "' not handled");
+    }
+
+    return offset;
+  }
+
+  private double[][] buildBTRange(BinaryTableHDU hdu)
+	throws VisADException
+  {
+    int rows = hdu.getNumRows();
+    int cols = hdu.getNumColumns();
+
+    double[][] d = new double[cols][rows];
+    for (int i = 0; i < cols; i++) {
+
+      Object list;
+      try {
+	list = hdu.getColumn(i).getData();
+      } catch (FitsException e) {
+	throw new VisADException("Failed to get column " + i + " type: " +
+				 e.getMessage());
+      }
+
+      int len;
+      if (list instanceof byte[][]) {
+	len = copyColumn((byte[][] )list, d[i], 0);
+      } else if (list instanceof short[][]) {
+	len = copyColumn((short[][] )list, d[i], 0);
+      } else if (list instanceof int[][]) {
+	len = copyColumn((int[][] )list, d[i], 0);
+      } else if (list instanceof long[][]) {
+	len = copyColumn((long[][] )list, d[i], 0);
+      } else if (list instanceof float[][]) {
+	len = copyColumn((float[][] )list, d[i], 0);
+      } else if (list instanceof double[][]) {
+	len = copyColumn((double[][] )list, d[i], 0);
+      } else {
+	String type;
+	try {
+	  type = hdu.getColumnFITSType(i);
+	} catch (FitsException e) {
+	  type = "?Unknown FITS type?";
+	}
+	System.err.println("FitsAdapter.buildBTRange: Faking values for" +
+			   " column #" + i + " (" + type + "=>" +
+			   list.getClass().getName() + ")");
+	// fill with NaN
+	int c = i;
+	for (len = 0 ; len < rows; len++) {
+	  d[c][len] = Double.NaN;
+	}
+      }
+
+      if (len < rows) {
+	int c = i;
+	System.err.println("FitsAdapter.buildBTRange: Column " + i +
+			   " was short " + (rows - len) + " of " + rows +
+			   " rows");
+	while (len < rows) {
+	  d[c][len++] = Double.NaN;
+	}
+      }
+    }
+    return d;
+  }
+
+  private Data addBinaryTable(BinaryTableHDU hdu)
+	throws FitsException, VisADException, RemoteException
+  {
+    int[] axes = hdu.getAxes();
+    if (axes == null) {
+      throw new FitsException("Couldn't get binary table axes");
+    }
+    if (axes.length != 2) {
+      throw new FitsException("Not a two-dimensional binary table");
+    }
+
+    int numColumns = hdu.getNumColumns();
+
+    RealType index = RealType.getRealType("index", null, null);
+
+    boolean hasTextColumn = false;
+
+    RealType rowType[] = new RealType[numColumns];
+    for (int i = 0; i < numColumns; i++) {
+      String name = hdu.getColumnName(i);
+      if (name == null) {
+	name = "Column" + i;
+      }
+
+      String colType = hdu.getColumnFITSType(i);
+      if (colType.startsWith("A") || colType.endsWith("A")) {
+	hasTextColumn = true;
+      }
+
+      rowType[i] = RealType.getRealType(name, null, null);
+    }
+
+    RealTupleType row = new RealTupleType(rowType);;
+
+    FunctionType func = new FunctionType(index, row);
+
+    Integer1DSet iSet = new Integer1DSet(hdu.getNumRows());
+
+    FlatField fld = new FlatField(func, iSet);
+
+    fld.setSamples(buildBTRange(hdu));
+
+    return fld;
+  }
+
+  private Data convertHDU(BasicHDU hdu)
+	throws FitsException, VisADException, RemoteException
+  {
+    if (hdu instanceof ImageHDU) {
+      return addImage((ImageHDU )hdu);
+    }
+
+    if (hdu instanceof PrimaryHDU) {
+      return addPrimary((PrimaryHDU )hdu);
+    }
+
+    if (hdu instanceof BinaryTableHDU) {
+      return addBinaryTable((BinaryTableHDU )hdu);
+    }
+
+    return null;
+  }
+
+  public void buildData()
+  {
+    Vector vec = new Vector();
+
+    int startDepth;
+    if (stack == null) {
+      startDepth = 0;
+    } else {
+      startDepth = stack.depth();
+    }
+
+    for (int n = 0; true; n++) {
+      try {
+	BasicHDU hdu = fits.getHDU(n);
+	if (hdu == null) {
+	  break;
+	}
+
+	Data d = convertHDU(hdu);
+	if (d != null) {
+	  vec.addElement(d);
+	}
+      } catch (Exception e) {
+	if (stack == null) {
+	  stack = new ExceptionStack(e);
+	} else {
+	  stack.addException(e);
+	  if (stack.depth() > startDepth + 10) {
+	    break;
+	  }
+	}
+      }
+    }
+
+    if (vec.size() == 0) {
+      data = null;
+    } else {
+      data = new Data[vec.size()];
+      for (int i = 0; i < data.length; i++) {
+	data[i] = (Data )vec.elementAt(i);
+      }
+    }
+  }
+
+  public void clearExceptionStack()
+  {
+    stack = null;
+  }
+
+  Data[] getData()
+	throws ExceptionStack, RemoteException, VisADException
+  {
+    if (data == null) {
+      buildData();
+      if (data == null) {
+	throw new VisADException("No data");
+      }
+    }
+
+    if (stack != null) {
+      throw stack;
+    }
+
+    return data;
+  }
+
+  public void save(String name, Data data, boolean replace)
+	throws IOException, RemoteException, VisADException
+  {
+    File file = new File(name);
+    if (file.exists()) {
+      throw new IllegalArgumentException("File \"" + name + "\" exists");
+    }
+
+    FitsTourGuide guide;
+
+    // make sure this object can be saved as a FITS file
+    TourInspector count = new TourInspector(replace);
+    guide = new FitsTourGuide(data, count);
+    count = null;
+
+    // build the new FITS file
+    Fits f = new Fits();
+    TourWriter tw = new TourWriter(replace, f);
+    guide = new FitsTourGuide(data, tw);
+    tw = null;
+    guide = null;
+
+    // open the final destination
+    BufferedOutputStream bos;
+    bos = new BufferedOutputStream(new FileOutputStream(name));
+
+    // write the FITS file
+    try {
+      f.write(bos);
+    } catch (FitsException e) {
+      throw new VisADException(e.getClass().getName() + "(" +
+			       e.getMessage() + ")");
+    }
+    bos.close();
+  }
+}
diff --git a/visad/data/fits/FitsForm.java b/visad/data/fits/FitsForm.java
new file mode 100644
index 0000000..afc306d
--- /dev/null
+++ b/visad/data/fits/FitsForm.java
@@ -0,0 +1,150 @@
+//
+// FitsForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.Tuple;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormNode;
+import visad.data.FormFileInformer;
+
+public class FitsForm
+	extends Form
+	implements FormFileInformer
+{
+  public FitsForm()
+  {
+    super("FitsForm");
+  }
+
+  public boolean isThisType(String name)
+  {
+    return name.endsWith(".fits");
+  }
+
+  public boolean isThisType(byte[] block)
+  {
+    String front = new String(block, 0, 9);
+    if (!front.startsWith("SIMPLE  =")) {
+      return false;
+    }
+
+    String back = new String(block, 9, 71);
+    back = back.trim();
+    if (back.length() != 1 || back.charAt(0) != 'T') {
+      return false;
+    }
+
+    return true;
+  }
+
+  public String[] getDefaultSuffixes()
+  {
+    String[] suff = { "fits" };
+    return suff;
+  }
+
+  public synchronized void save(String id, Data data, boolean replace)
+	throws  BadFormException, IOException, RemoteException, VisADException
+  {
+    new FitsAdapter().save(id, data, replace);
+  }
+
+  public synchronized void add(String id, Data data, boolean replace)
+	throws BadFormException
+  {
+    throw new RuntimeException("Can't yet add FITS objects");
+  }
+
+  private DataImpl extractData(FitsAdapter fits)
+	throws RemoteException, VisADException
+  {
+    // save any exceptions
+    ExceptionStack eStack = null;
+
+    // convert the FITS object to a VisAD data object
+    Data[] data;
+    try {
+      data = fits.getData();
+    } catch (ExceptionStack e) {
+      eStack = e;
+      fits.clearExceptionStack();
+      data = fits.getData();
+    }
+
+    // throw away FitsAdapter object so we can reuse that memory
+    fits = null;
+
+    // if there's no data, we're done
+    if (data == null || data.length == 0) {
+      if (eStack != null) {
+	throw eStack;
+      }
+      return null;
+    }
+
+    // either grab solo Data object or wrap a Tuple around all the Data objects
+    DataImpl di;
+    if (data.length == 1) {
+      di = (DataImpl )data[0];
+    } else {
+      di = new Tuple(data);
+    }
+
+    // throw away Data array so we can reuse (a small bit of) that memory
+    data = null;
+
+    return di;
+  }
+
+  public synchronized DataImpl open(String path)
+	throws BadFormException, RemoteException, VisADException
+  {
+    return extractData(new FitsAdapter(path));
+  }
+
+  public synchronized DataImpl open(URL url)
+	throws BadFormException, VisADException, IOException
+  {
+    return extractData(new FitsAdapter(url));
+  }
+
+  public synchronized FormNode getForms(Data data)
+  {
+    throw new RuntimeException("Can't yet get FITS forms");
+  }
+}
diff --git a/visad/data/fits/FitsTourGuide.java b/visad/data/fits/FitsTourGuide.java
new file mode 100644
index 0000000..6260ef1
--- /dev/null
+++ b/visad/data/fits/FitsTourGuide.java
@@ -0,0 +1,77 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.Function;
+import visad.Scalar;
+import visad.Set;
+import visad.Tuple;
+import visad.VisADException;
+
+public class FitsTourGuide
+	extends TourGuide
+{
+  private boolean replace;
+
+  public FitsTourGuide(Data data, Tourist tourist)
+	throws RemoteException, VisADException
+  {
+    this.replace = replace;
+
+    show(data, tourist, 0);
+  }
+
+  public boolean show(Function func, Tourist tourist, int depth)
+	throws RemoteException, VisADException
+  {
+    return tourist.visit(func, depth);
+  }
+
+  public boolean show(Scalar scalar, Tourist tourist, int depth)
+	throws VisADException
+  {
+    return tourist.visit(scalar, depth);
+  }
+
+  public boolean show(Set set, Tourist tourist, int depth)
+	throws VisADException
+  {
+    return tourist.visit(set, depth);
+  }
+
+  public boolean show(Tuple tuple, Tourist tourist, int depth)
+	throws RemoteException, VisADException
+  {
+    boolean rtnval = true;
+
+    int dim = tuple.getDimension();
+    for (int i = 0; i < dim; i++) {
+      rtnval |= show(tuple.getComponent(i), tourist, depth+1);
+    }
+
+    return rtnval;
+  }
+}
diff --git a/visad/data/fits/GenericArrayConverter.java b/visad/data/fits/GenericArrayConverter.java
new file mode 100644
index 0000000..764e53b
--- /dev/null
+++ b/visad/data/fits/GenericArrayConverter.java
@@ -0,0 +1,136 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+public abstract class GenericArrayConverter
+{
+  Object o;
+  int[] lengths;
+
+  public GenericArrayConverter(Class type, int[] lengths)
+  {
+    o = java.lang.reflect.Array.newInstance(type, lengths);
+
+    this.lengths = lengths;
+  }
+
+  abstract void assign(Object obj, int index, double value);
+
+  private Object getBottomArray(Object o, int[] which)
+  {
+    int d = which.length - 1;
+
+    int i = 0;
+    while (i < d) {
+      o = java.lang.reflect.Array.get(o, which[i++]);
+    }
+
+    return o;
+  }
+
+  private Object getNextRMBottomArray(Object o, int[] coord)
+  {
+    int l = coord.length - 1;
+
+    while (coord[l] >= lengths[l]) {
+      coord[l] = 0;
+
+      l--;
+      if (l < 0) {
+	return null;
+      }
+
+      coord[l]++;
+    }
+
+    return getBottomArray(o, coord);
+  }
+
+  private Object getNextCMBottomArray(Object o, int[] coord)
+  {
+    int l = 0;
+
+    while (coord[l] >= lengths[lengths.length - (l+1)]) {
+      coord[l] = 0;
+
+      l++;
+      if (l >= coord.length) {
+	return null;
+      }
+
+      coord[l]++;
+    }
+
+    return getBottomArray(o, coord);
+  }
+
+  public Object getRowMajor(double[][] values)
+  {
+    int[] coord = new int[lengths.length];
+    for (int i = 0; i < lengths.length; i++) {
+      coord[i] = 0;
+    }
+
+    final int lastCoord = lengths.length - 1;
+
+    Object ra = getBottomArray(o, coord);
+    for (int i = 0; i < values.length; i++) {
+      for (int j = 0; j < values[i].length; j++) {
+        assign(ra, lengths[lastCoord] - ++coord[lastCoord], values[i][j]);
+
+	if (coord[lastCoord] >= lengths[lastCoord]) {
+	  ra = getNextRMBottomArray(o, coord);
+	  if (ra == null) {
+	    return o;
+	  }
+	}
+      }
+    }
+
+    return o;
+  }
+
+  public Object getColumnMajor(double[][] values)
+  {
+    int[] coord = new int[lengths.length];
+    for (int i = 0; i < lengths.length; i++) {
+      coord[i] = 0;
+    }
+
+    final int lastCoord = lengths.length - 1;
+
+    Object ra;
+    for (int i = 0; i < values[0].length; i++) {
+      assign(getBottomArray(o, coord), coord[lastCoord], values[0][i]);
+      coord[0]++;
+
+      if (coord[0] >= lengths[0]) {
+        if (getNextCMBottomArray(o, coord) == null) {
+          return o;
+	}
+      }
+    }
+
+    return o;
+  }
+}
diff --git a/visad/data/fits/Spasm.java b/visad/data/fits/Spasm.java
new file mode 100644
index 0000000..ea4bb68
--- /dev/null
+++ b/visad/data/fits/Spasm.java
@@ -0,0 +1,288 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.EtchedBorder;
+
+import java.awt.Cursor;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.io.IOException;
+
+import java.rmi.RemoteException;
+
+import visad.ConstantMap;
+import visad.Data;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.FunctionImpl;
+import visad.FunctionType;
+import visad.MathType;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.Tuple;
+import visad.VisADException;
+
+import visad.data.DefaultFamily;
+
+import visad.java3d.DisplayImplJ3D;
+
+public class Spasm
+{
+  private Data data;
+
+  // the width and height of the UI frame
+  public static int WIDTH = 600;
+  public static int HEIGHT = 600;
+
+  public Spasm(String filename)
+	throws VisADException, RemoteException, IOException
+  {
+    DefaultFamily dflt = new DefaultFamily("Default");
+
+    Data data;
+    try {
+      data = dflt.open(filename);
+    } catch (VisADException e) {
+      e.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    // throw away all but the object
+    if (data instanceof Tuple) {
+      boolean discard = false;
+
+      Tuple t = (Tuple )data;
+      try {
+	int i = 0;
+	Data d;
+
+	do {
+	  d = t.getComponent(i);
+	  if (discard) {
+	    System.err.println("Discarding VisAD Data object #" + i + ": " +
+				d.getType());
+	  } else if (d instanceof FunctionImpl) {
+	    data = d;
+	    discard = true;
+	  }
+	  i++;
+	} while (d != null);
+      } catch (VisADException e) {
+      } catch (ArrayIndexOutOfBoundsException e) {
+      }
+
+      // leave some whitespace after any error message(s)
+      if (discard) {
+	System.err.println("");
+      }
+    }
+
+    this.data = data;
+  }
+
+  public String toString()
+  {
+    try {
+      return data.getType().toString();
+    } catch (Exception e) {
+      return e.getClass().toString() + ": " + e.getMessage();
+    }
+  }
+
+  private void linkData(DisplayImpl display)
+	throws VisADException, RemoteException
+  {
+    // compute ScalarMaps from type components
+    FunctionType ftype = (FunctionType )data.getType();
+
+    // get domain and domain dimensions
+    RealTupleType dtype = ftype.getDomain();
+    int dims = dtype.getDimension();
+
+    // map domain to up to 3 dimensions
+    display.addMap(new ScalarMap((RealType )dtype.getComponent(0),
+				  Display.XAxis));
+    if (dims > 1) {
+      display.addMap(new ScalarMap((RealType )dtype.getComponent(1),
+				    Display.YAxis));
+      if (dims > 2) {
+	display.addMap(new ScalarMap((RealType )dtype.getComponent(2),
+				      Display.ZAxis));
+      }
+    }
+
+    // set up colors
+    display.addMap(new ConstantMap(0.5, Display.Red));
+    display.addMap(new ConstantMap(0.0, Display.Blue));
+
+    // get range values
+    MathType rtype = ftype.getRange();
+    RealType rg, rz;
+    if (rtype instanceof RealType) {
+      rg = rz = (RealType )rtype;
+    } else if (rtype instanceof RealTupleType) {
+      rg = (RealType )((RealTupleType )rtype).getComponent(0);
+      if (((RealTupleType )rtype).getDimension() > 1) {
+	rz = (RealType )((RealTupleType )rtype).getComponent(1);
+      } else {
+	rz = rg;
+      }
+    } else {
+      rg = rz = null;
+    }
+
+    // map range values to green
+    if (rg != null) {
+      display.addMap(new ScalarMap(rg, Display.Green));
+    }
+
+    // if Z axes isn't used yet, use it for range values
+    if (dims <= 2 && rz != null) {
+      display.addMap(new ScalarMap(rz, Display.ZAxis));
+    }
+
+    System.out.println(data.getType());
+    System.out.println(display);
+
+    // point display at data
+    DataReferenceImpl ref = new DataReferenceImpl("SpazData");
+    ref.setData(data);
+    display.addReference(ref, null);
+  }
+
+  public void showApp()
+	throws VisADException, RemoteException
+  {
+    DisplayImplJ3D display = new DisplayImplJ3D("display",
+						DisplayImplJ3D.APPLETFRAME);
+    linkData(display);
+  }
+
+  private JFrame mainFrame(String frameName)
+  {
+    // create a JFrame
+    JFrame frame = new JFrame(frameName);
+    frame.addWindowListener(new WindowAdapter() {
+				public void windowClosing(WindowEvent e) {
+				  System.exit(0);
+				}
+			    });
+
+    frame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+
+    return frame;
+  }
+
+  private JPanel textPanel()
+  {
+    // create text JPanel
+    JPanel p = new JPanel();
+    p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+    p.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    p.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    // construct JLabels
+    // (JTextArea does not align in BoxLayout well, so use JLabels)
+    p.add(new JLabel("Silly file viewer"));
+    p.add(new JLabel("using VisAD  -  see:"));
+    p.add(new JLabel("  "));
+    p.add(new JLabel("  http://www.ssec.wisc.edu/~billh/visad.html"));
+    p.add(new JLabel("  "));
+    p.add(new JLabel("for more information about VisAD."));
+    p.add(new JLabel("  "));
+
+    return p;
+  }
+
+  private void showSwing(String frameName)
+	throws VisADException, RemoteException
+  {
+    // construct Display 1 (using default DisplayRenderer);
+    // the text name is used only for debugging
+    DisplayImplJ3D display = new DisplayImplJ3D("display");
+    linkData(display);
+
+    JFrame frame = mainFrame(frameName);
+
+    // create mainPanel JPanel in frame
+    JPanel mainPanel = new JPanel();
+    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));
+    mainPanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    mainPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(mainPanel);
+
+    mainPanel.add(textPanel());
+
+    // get Display panel
+    JPanel displayPanel = (JPanel) display.getComponent();
+
+    // make borders for Display and embed in mainPanel
+    Border etchedBorder10 =
+      new CompoundBorder(new EtchedBorder(),
+                         new EmptyBorder(10, 10, 10, 10));
+    displayPanel.setBorder(etchedBorder10);
+
+    mainPanel.add(displayPanel);
+
+    // make the JFrame visible
+    frame.pack();
+    frame.setVisible(true);
+  }
+
+  public static void main(String args[])
+	throws VisADException, RemoteException, IOException
+  {
+    if (args.length != 1) {
+      System.err.println("Usage: Spasm file");
+      System.exit(1);
+      return;
+    }
+
+    Spasm spaz = new Spasm(args[0]);
+
+    try {
+      System.out.println("Spasm: " + spaz);
+    } catch (Exception e) {
+      System.err.println(args[0] + " print threw " + e.getMessage());
+      e.printStackTrace(System.err);
+      System.exit(1);
+      return;
+    }
+
+    spaz.showSwing("Spasm");
+  }
+}
diff --git a/visad/data/fits/ToFits.java b/visad/data/fits/ToFits.java
new file mode 100644
index 0000000..303826e
--- /dev/null
+++ b/visad/data/fits/ToFits.java
@@ -0,0 +1,66 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.io.IOException;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.VisADException;
+
+import visad.data.DefaultFamily;
+
+public class ToFits
+{
+  public static void main(String args[])
+	throws VisADException, RemoteException, IOException
+  {
+    DefaultFamily dflt = new DefaultFamily("default");
+
+    if (args.length == 0) {
+      args = new String[1];
+      args[0] = "testdata/sseclogo.fits";
+    }
+
+    for (int i = 0; i < args.length; i++) {
+      Data data = dflt.open(args[i]);
+
+      try {
+	System.out.println("ToFits " + args[i] + ": " + data.getType());
+      } catch (Exception e) {
+	System.err.println(args[i] + " print threw " + e.getMessage());
+	e.printStackTrace(System.err);
+	data = null;
+	continue;
+      }
+
+      String name = "foo" + i;
+      FitsForm form = new FitsForm();
+      form.save(name, data, true);
+      System.out.println("Wrote " + name);
+    }
+
+    System.exit(0);
+  }
+}
diff --git a/visad/data/fits/TourGuide.java b/visad/data/fits/TourGuide.java
new file mode 100644
index 0000000..4581f62
--- /dev/null
+++ b/visad/data/fits/TourGuide.java
@@ -0,0 +1,66 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.Function;
+import visad.Scalar;
+import visad.Set;
+import visad.Tuple;
+import visad.VisADException;
+
+public abstract class TourGuide
+{
+  public boolean show(Data data, Tourist tourist, int depth)
+	throws RemoteException, VisADException
+  {
+    if (data instanceof Function) {
+      return show((Function )data, tourist,  depth);
+    }
+
+    if (data instanceof Scalar) {
+      return show((Scalar )data, tourist,  depth);
+    }
+
+    if (data instanceof Set) {
+      return show((Set )data, tourist,  depth);
+    }
+
+    if (data instanceof Tuple) {
+      return show((Tuple )data, tourist,  depth);
+    }
+
+    throw new VisADException("Unknown datatype " + data.getClass().getName());
+  }
+
+  public abstract boolean show(Function func, Tourist tourist, int depth)
+	throws RemoteException, VisADException;
+  public abstract boolean show(Scalar scalar, Tourist tourist, int depth)
+	throws VisADException;
+  public abstract boolean show(Set set, Tourist tourist, int depth)
+	throws VisADException;
+  public abstract boolean show(Tuple tuple, Tourist tourist, int depth)
+	throws RemoteException, VisADException;
+}
diff --git a/visad/data/fits/TourInspector.java b/visad/data/fits/TourInspector.java
new file mode 100644
index 0000000..c6abf93
--- /dev/null
+++ b/visad/data/fits/TourInspector.java
@@ -0,0 +1,75 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.rmi.RemoteException;
+
+import visad.Function;
+import visad.Scalar;
+import visad.Set;
+import visad.VisADException;
+
+public class TourInspector
+	extends Tourist
+{
+  private int total;
+
+  public TourInspector(boolean replace)
+  {
+    super(replace);
+    total = 0;
+  }
+
+  public boolean visit(Function func, int depth)
+	throws RemoteException, VisADException
+  {
+    if (depth > 2) {
+      throw new VisADException("Too deep for FITS");
+    }
+
+    total++;
+    return true;
+  }
+
+  public boolean visit(Scalar scalar, int depth)
+	throws VisADException
+  {
+    throw new VisADException("Can't write a single scalar value as a FITS HDU");
+  }
+
+  public boolean visit(Set set, int depth)
+	throws VisADException
+  {
+    if (depth > 2) {
+      throw new VisADException("Too deep for FITS");
+    }
+
+    total++;
+    return true;
+  }
+
+  public int getTotal()
+  {
+    return total;
+  }
+}
diff --git a/visad/data/fits/TourWriter.java b/visad/data/fits/TourWriter.java
new file mode 100644
index 0000000..50d4304
--- /dev/null
+++ b/visad/data/fits/TourWriter.java
@@ -0,0 +1,314 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.rmi.RemoteException;
+
+import nom.tam.fits.BasicHDU;
+import nom.tam.fits.BinaryTableHDU;
+import nom.tam.fits.Column;
+import nom.tam.fits.Fits;
+import nom.tam.fits.FitsException;
+import nom.tam.fits.ImageHDU;
+import nom.tam.fits.PrimaryHDU;
+
+import nom.tam.util.ArrayFuncs;
+
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.Function;
+import visad.FunctionType;
+import visad.GriddedSet;
+import visad.MathType;
+import visad.RealTupleType;
+import visad.Scalar;
+import visad.ScalarType;
+import visad.Set;
+import visad.VisADException;
+import visad.UnimplementedException;
+
+public class TourWriter
+	extends Tourist
+{
+  private Fits fits;
+
+  public TourWriter(boolean replace, Fits fits)
+  {
+    super(replace);
+    this.fits = fits;
+  }
+
+  private String[] getNames(RealTupleType rtt)
+	throws VisADException
+  {
+    int dim = rtt.getDimension();
+    if (dim == 0) {
+      return null;
+    }
+
+    String[] list = new String[dim];
+    for (int i = 0; i < dim; i++) {
+      MathType type = rtt.getComponent(i);
+      if (!(type instanceof ScalarType)) {
+	throw new VisADException("Expected a ScalarType name, got " +
+				 type.getClass().getName());
+      }
+
+      list[i] = ((ScalarType )type).getName();
+    }
+
+    return list;
+  }
+
+  private String[] getNames(ScalarType st)
+	throws VisADException
+  {
+    String[] list = new String[1];
+    list[0] = st.getName();
+    return list;
+  }
+
+  private String[] getNames(MathType mt)
+	throws VisADException
+  {
+    if (mt instanceof RealTupleType) {
+      return getNames((RealTupleType )mt);
+    }
+    if (mt instanceof ScalarType) {
+      return getNames((ScalarType )mt);
+    }
+    throw new VisADException("Couldn't get list of names from " +
+			     mt.getClass().getName());
+  }
+
+  private void saveBinaryTable(FlatField fld, int domainDim, int rangeDim)
+	throws VisADException
+  {
+    System.err.println("TourWriter.saveBinaryTable(" + domainDim + ", " + rangeDim + "):");
+
+    FunctionType funcType = (FunctionType )fld.getType();
+
+    String[] rangeNames = getNames(funcType.getRange());
+
+    BinaryTableHDU hdu;
+    try {
+      hdu = new BinaryTableHDU();
+    } catch (FitsException e) {
+      throw new VisADException("Couldn't create BinaryTableHDU: " +
+			       e.getMessage());
+    }
+
+    double[][] values = fld.getValues();
+    System.err.println("\tvalues: " + values.length + "x" + values[0].length);
+
+    double[][] column = new double[1][];
+    int[] lengths = new int[2];
+
+    Object[][] table = new Object[values.length][];
+
+    lengths[0] = 1;
+    for (int i = 0; i < table.length; i++) {
+      column[0] = values[i];
+      lengths[1] = column[0].length;
+
+      ConvertDoubleArray cvtArray = new ConvertDoubleArray(lengths, column);
+      Object o = cvtArray.getConverter().getRowMajor(column);
+      if (o == null) {
+	throw new VisADException("Couldn't extract array from column #" + i);
+      }
+
+      try {
+	Column col = new Column();
+	col.setData((Object[] )o);
+
+	String num = "" + i;
+
+	StringBuffer buf = new StringBuffer(8);
+
+	if (rangeNames != null) {
+	  buf.setLength(0);
+	  buf.append("TTYPE");
+	  buf.append(num);
+	  buf.append("        ");
+	  buf.setLength(8);
+	  buf.append("= '");
+	  buf.append(rangeNames[i]);
+	  buf.append("'");
+	  col.addKey(buf.toString());
+	}
+
+	hdu.addColumn(col);
+      } catch (FitsException e) {
+	System.err.println("Couldn't add binary table column #" + i + ": " +
+			   e.getMessage());
+      }
+    }
+
+    try {
+      fits.addHDU(hdu);
+    } catch (FitsException e) {
+      throw new VisADException("Couldn't add FITS binary table HDU : " +
+			       e.getMessage());
+    }
+  }
+
+  private void saveImage(FlatField fld, int domainDim, int rangeDim)
+	throws VisADException
+  {
+    Set set = fld.getDomainSet();
+
+    if (!(set instanceof GriddedSet)) {
+      throw new VisADException("Cannot build FITS Image" +
+			       " from non-Gridded domain");
+    }
+
+    int size;
+    try {
+      size = fits.size();
+    } catch (FitsException e) {
+      System.err.println("TourWriter.saveImage: Yikes!  Fits.size() threw");
+      e.printStackTrace(System.err);
+      throw new VisADException("Couldn't get size of FITS file");
+    }
+
+    final int[] lengths = ((GriddedSet )set).getLengths();
+    if (lengths.length != 2) {
+      throw new VisADException("Don't know how to decipher " + lengths.length +
+                               "-dimension FlatField!");
+    }
+
+    double[][] values = fld.getValues();
+    if (values[0].length != lengths[0]*lengths[1]) {
+      throw new VisADException("Mismatch between FlatField length array" +
+                               " and value array length");
+    } else if (values.length != 1 && values.length != 3) {
+      throw new VisADException("Don't know how to decipher " + values.length +
+                               "-dimension FlatField values!");
+    }
+
+    // create 8-bit color value array
+    final int len = values[0].length;
+    byte[] colorVals = new byte[len];
+
+    // fill in 8-bit color values
+    int colRow, valIndex;
+    valIndex = 0;
+    for (int i = lengths[1] - 1; i >= 0; i--) {
+      colRow = i * lengths[0];
+      for (int j = 0; j < lengths[0]; j++) {
+        int v;
+        if (values.length == 3) {
+          // map to RGB
+          v = (int )((0.299 * values[0][valIndex]) +
+                     (0.587 * values[1][valIndex]) +
+                     (0.114 * values[2][valIndex]));
+        } else {
+          v = (int )values[0][valIndex];
+        }
+        colorVals[colRow+j] = (byte )v;
+
+        valIndex++;
+      }
+    }
+
+    byte[][] image = (byte[][] )ArrayFuncs.curl(colorVals, lengths);
+
+    try {
+      BasicHDU hdu;
+      if (size == 0) {
+	hdu = new PrimaryHDU((Object )image);
+      } else {
+	hdu = new ImageHDU((Object )image);
+      }
+
+      fits.addHDU(hdu);
+    } catch (FitsException e) {
+      throw new VisADException("Couldn't build " +
+			       (size == 0 ? "primary" : "image") +
+			       " FITS HDU : " + e.getMessage());
+    }
+  }
+
+  private void save(FlatField fld)
+	throws RemoteException, VisADException
+  {
+    MathType type = fld.getType();
+    if (!(type instanceof FunctionType)) {
+      throw new VisADException("Confused Data object" +
+			       " (FlatField with non-FunctionType)");
+    }
+
+    int domainDim = fld.getDomainSet().getDimension();
+    if (domainDim > 2) {
+      throw new VisADException("Can't write FITS file with" +
+			       " domain dimension of " + domainDim);
+    }
+
+    int rangeDim = fld.getRangeDimension();
+    if (rangeDim != 1 && rangeDim != 3) {
+      throw new VisADException("Can't write FITS file with" +
+			       " range dimension of " + rangeDim);
+    }
+
+    if (domainDim != 2) {
+      saveBinaryTable(fld, domainDim, rangeDim);
+    } else {
+      saveImage(fld, domainDim, rangeDim);
+    }
+  }
+
+  private void save(FieldImpl fld)
+	throws RemoteException, VisADException
+  {
+    if (fld instanceof FlatField) {
+      save((FlatField )fld);
+      return;
+    }
+
+    throw new UnimplementedException("Can only save FlatField data");
+  }
+
+  public boolean visit(Function func, int depth)
+	throws RemoteException, VisADException
+  {
+
+    if (!(func instanceof FieldImpl)) {
+      throw new UnimplementedException("Can only save FieldImpl data");
+    }
+
+    save((FieldImpl )func);
+    return true;
+  }
+
+  public boolean visit(Scalar scalar, int depth)
+	throws VisADException
+  {
+    return false;
+  }
+
+  public boolean visit(Set set, int depth)
+	throws VisADException
+  {
+    throw new UnimplementedException("Cannot write Set data to FITS files yet");
+  }
+}
diff --git a/visad/data/fits/Tourist.java b/visad/data/fits/Tourist.java
new file mode 100644
index 0000000..f0d0b92
--- /dev/null
+++ b/visad/data/fits/Tourist.java
@@ -0,0 +1,61 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.fits;
+
+import java.rmi.RemoteException;
+
+import visad.Function;
+import visad.Scalar;
+import visad.Set;
+import visad.VisADException;
+
+public class Tourist
+{
+  boolean replace;
+
+  public Tourist(boolean replace)
+  {
+    this.replace = replace;
+  }
+
+  public boolean visit(Function func, int depth)
+	throws RemoteException, VisADException
+  {
+//    System.err.println("Tourist.visit: function " + func.getType() + ", depth=" + depth);
+    return false;
+  }
+
+  public boolean visit(Scalar scalar, int depth)
+	throws VisADException
+  {
+//    System.err.println("Tourist.visit: scalar " + scalar.getType() + ", depth=" + depth);
+    return false;
+  }
+
+  public boolean visit(Set set, int depth)
+	throws VisADException
+  {
+//    System.err.println("Tourist.visit: set " + set.getType() + ", depth=" + depth);
+    return false;
+  }
+}
diff --git a/visad/data/fits/package.html b/visad/data/fits/package.html
new file mode 100644
index 0000000..13be674
--- /dev/null
+++ b/visad/data/fits/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides for importing a FITS dataset into VisAD.
+
+</body>
+</html>
+
diff --git a/visad/data/fits/testdata/sseclogo.fits b/visad/data/fits/testdata/sseclogo.fits
new file mode 100644
index 0000000..4497d54
Binary files /dev/null and b/visad/data/fits/testdata/sseclogo.fits differ
diff --git a/visad/data/gendcm.tcl b/visad/data/gendcm.tcl
new file mode 100644
index 0000000..e02aa72
--- /dev/null
+++ b/visad/data/gendcm.tcl
@@ -0,0 +1,145 @@
+##
+## This script generates all of the array specific APIs for the DataCacheManager
+##
+## Run it as:
+## tclsh gendcm.tcl > dcmapi
+##then include dcmapi into DataCacheManager
+##
+
+
+
+set template {
+
+  /**
+   * get the value from the cache
+   *
+   * @param cacheId  the cache id
+   *
+   * @return  the value
+   */
+    public %type%%brackets% get%Type%Array%dimension%D(Object cacheId) {
+        return (%type%%brackets%)getData(cacheId);
+    }
+
+  /**
+   * add the data to the cache
+   *
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(%type%%brackets% values) {
+    return addToCache(null, values, TYPE_%TYPE%%dimension%D, false);
+  }
+
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, %type%%brackets% values) {
+      return addToCache(what, values, TYPE_%TYPE%%dimension%D, false);
+  }
+  
+  /**
+   * add the data to the cache
+   *
+   * @param what the name of the item. used for tracking cache behavior
+   * @param values the values to add
+   * @param removeIfNeeded If true then this data will not be written to disk and will be removed from the cache
+   * when the cache is exceeding memory limits
+   *
+   * @return the cache id
+   */
+  public Object addToCache(String what, %type%%brackets% values, boolean removeIfNeeded) {
+    return addToCache(what, values, TYPE_%TYPE%%dimension%D, removeIfNeeded);
+  }
+
+
+}
+
+set sizeTemplate {
+   if (type == TYPE_%TYPE%%dimension%D) {
+        %type%%brackets% data= (%type%%brackets%) values;
+        %sizecheck%
+        return %bytes%*%sizecode%;
+
+   }
+}
+
+
+puts "/********\n  Begin generated access methods\n*****/"
+
+set procs "";
+set types ""
+set sizeMethod "/** Get the size of the array **/\nprivate static int getArraySize(int type, Object values) {\n";
+set nameMethod "/** Get the name of the type **/\nprivate static String getNameForType(int type) {\n";
+set cnt 0
+for {set dimension 1} {$dimension<4} {incr dimension} {
+    set brackets "";
+     for {set j 0} {$j<$dimension} {incr j} {
+         append brackets {[]}
+    }
+    foreach {type bytes} {double  8 float 4 int 4 short 2 byte 1} {
+        set Type "[string toupper [string range $type 0 0]][string range $type 1 end]"
+        set TYPE [string toupper $type]
+        set code $template
+        regsub -all %type% $code $type code
+        regsub -all %Type% $code $Type code
+        regsub -all %TYPE% $code $TYPE code
+        regsub -all %dimension% $code $dimension code
+        regsub -all %brackets% $code $brackets code
+
+
+        set sizeCode ""
+        set sizeCheck ""
+        if {$dimension == 1} {
+            set sizeCode {data.length}
+        } elseif {$dimension == 2} {
+            set sizeCheck {if (data[0]==null) return 0;}
+            set sizeCode {data.length * data[0].length}
+        } elseif {$dimension == 3} {
+            set sizeCheck {if (data[0]==null) return 0; if(data[0][0]==null) return 0;}
+            set sizeCode {data.length * data[0].length*data[0][0].length}
+        }
+        set tmp $sizeTemplate
+        regsub -all %sizecode% $tmp $sizeCode tmp
+        regsub -all %sizecheck% $tmp $sizeCheck tmp
+        regsub -all %bytes% $tmp $bytes tmp
+        regsub -all %type% $tmp $type tmp
+        regsub -all %TYPE% $tmp $TYPE tmp
+        regsub -all %dimension% $tmp $dimension tmp
+        regsub -all %brackets% $tmp $brackets tmp
+        append sizeMethod $tmp
+
+
+        set tmp {    if (type == TYPE_%TYPE%%dimension%D) {return "%type%%dimension%d";}}
+        regsub -all %type% $tmp $type tmp
+        regsub -all %TYPE% $tmp $TYPE tmp
+        regsub -all %dimension% $tmp $dimension tmp
+        append nameMethod  $tmp
+        append nameMethod  "\n"
+
+
+
+
+        append procs $code
+        set enum {private static final int TYPE_%TYPE%%dimension%D = %cnt%;}
+        regsub -all %TYPE% $enum $TYPE enum
+        regsub -all %dimension% $enum $dimension enum
+        regsub -all %cnt% $enum $cnt enum
+        append types $enum
+        append types "\n"
+        incr cnt
+    }
+}
+
+append sizeMethod "\n   throw new IllegalArgumentException(\"Unknown type:\" + type);\n}\n\n"
+append nameMethod " return \"unknown type\";\n}\n"
+puts $types
+puts $procs
+puts $sizeMethod
+puts $nameMethod
diff --git a/visad/data/gif/GIFAdapter.java b/visad/data/gif/GIFAdapter.java
new file mode 100644
index 0000000..85b8036
--- /dev/null
+++ b/visad/data/gif/GIFAdapter.java
@@ -0,0 +1,81 @@
+//
+// GIFAdapter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gif;
+
+import java.awt.Image;
+import java.awt.Toolkit;
+
+import java.awt.image.ImageProducer;
+
+import java.io.IOException;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import visad.FlatField;
+import visad.VisADException;
+
+import visad.util.DataUtility;
+
+/** this is an adapter for GIF and other images */
+public class GIFAdapter {
+
+  private FlatField field = null;
+
+  /** Create a VisAD FlatField from a local GIF, JPEG or PNG file
+    * @param filename name of local file.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public GIFAdapter(String filename)
+	throws IOException, VisADException
+  {
+    Image image = Toolkit.getDefaultToolkit().getImage(filename);
+    field = DataUtility.makeField(image);
+  }
+
+  /** Create a VisAD FlatField from a GIF, JPEG or PNG on the Web.
+    * @param url File URL.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public GIFAdapter(URL url)
+    throws IOException, VisADException
+  {
+    Object object = url.getContent();
+    if (object == null || !(object instanceof ImageProducer)) {
+      throw new MalformedURLException("URL does not point to an image");
+    }
+    ImageProducer producer = (ImageProducer) object;
+    Image image = Toolkit.getDefaultToolkit().createImage(producer);
+    field = DataUtility.makeField(image);
+  }
+
+  public FlatField getData() {
+    return field;
+  }
+}
diff --git a/visad/data/gif/GIFForm.java b/visad/data/gif/GIFForm.java
new file mode 100644
index 0000000..6d76a01
--- /dev/null
+++ b/visad/data/gif/GIFForm.java
@@ -0,0 +1,106 @@
+//
+// GIFForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gif;
+
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.UnimplementedException;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormNode;
+import visad.data.FormFileInformer;
+
+public class GIFForm
+	extends Form
+	implements FormFileInformer
+{
+  public GIFForm()
+  {
+    super("GIFForm");
+  }
+
+  public boolean isThisType(String name)
+  {
+    return (name.endsWith(".gif") || name.endsWith(".GIF") ||
+            name.endsWith(".jpg") || name.endsWith(".JPG") ||
+            name.endsWith(".jpeg") || name.endsWith(".JPEG") ||
+            name.endsWith(".png") || name.endsWith(".PNG"));
+  }
+
+  public boolean isThisType(byte[] block)
+  {
+    return false;
+  }
+
+  public String[] getDefaultSuffixes()
+  {
+    String[] suff = { "gif" };
+    return suff;
+  }
+
+  public synchronized void save(String id, Data data, boolean replace)
+	throws  BadFormException, IOException, RemoteException, VisADException
+  {
+    throw new UnimplementedException("Can't yet save GIF objects");
+  }
+
+  public synchronized void add(String id, Data data, boolean replace)
+	throws BadFormException
+  {
+    throw new RuntimeException("Can't yet add GIF objects");
+  }
+
+  public synchronized DataImpl open(String path)
+	throws BadFormException, RemoteException, VisADException
+  {
+    try {
+      return new GIFAdapter(path).getData();
+    } catch (IOException e) {
+      throw new VisADException("IOException: " + e.getMessage());
+    }
+  }
+
+  public synchronized DataImpl open(URL url)
+	throws BadFormException, VisADException, IOException
+  {
+    GIFAdapter ga = new GIFAdapter(url);
+    return ga.getData();
+  }
+
+  public synchronized FormNode getForms(Data data)
+  {
+    throw new RuntimeException("Can't yet get GIF forms");
+  }
+}
diff --git a/visad/data/gif/TestGIF.java b/visad/data/gif/TestGIF.java
new file mode 100644
index 0000000..f540a61
--- /dev/null
+++ b/visad/data/gif/TestGIF.java
@@ -0,0 +1,77 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gif;
+
+import java.io.IOException;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import visad.FlatField;
+import visad.VisADException;
+
+import visad.data.gif.GIFAdapter;
+
+public class TestGIF
+{
+  public static void main(String args[])
+  {
+    if (args.length == 0) {
+      args = new String[3];
+
+      args[0] = "sseclogo.gif";
+      args[1] = "http://www.ssec.wisc.edu/images/ssecsm.gif";
+      args[2] = "http://www.ssec.wisc.edu/";
+    }
+
+    for (int i = 0; i < args.length; i++) {
+      System.out.println("Testing \"" + args[i] + "\"");
+
+      GIFAdapter gif = null;
+      try {
+	try {
+	  gif = new GIFAdapter(new URL(args[i]));
+	} catch (MalformedURLException e) {
+	  gif = new GIFAdapter(args[i]);
+	}
+      } catch (IOException e) {
+	System.err.println("Caught IOException for \"" + args[i] + "\": " +
+			   e.getMessage());
+	continue;
+      } catch (VisADException e) {
+	System.err.println("Caught VisADException for \"" + args[i] + "\": " +
+			   e.getMessage());
+	continue;
+      }
+
+      FlatField ff = gif.getData();
+      if (ff == null) {
+	System.out.println("\tNULL FlatField!");
+      } else {
+	System.out.println("\t" + ff.getType());
+      }
+    }
+    System.out.println("Done");
+    System.exit(0);
+  }
+}
diff --git a/visad/data/gif/package.html b/visad/data/gif/package.html
new file mode 100644
index 0000000..8850532
--- /dev/null
+++ b/visad/data/gif/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides for importing GIF, JPEG and PNG files into VisAD.
+
+</body>
+</html>
+
diff --git a/visad/data/gif/sseclogo.gif b/visad/data/gif/sseclogo.gif
new file mode 100644
index 0000000..1f775c9
Binary files /dev/null and b/visad/data/gif/sseclogo.gif differ
diff --git a/visad/data/gis/ArcAsciiGridAdapter.java b/visad/data/gis/ArcAsciiGridAdapter.java
new file mode 100644
index 0000000..131cd67
--- /dev/null
+++ b/visad/data/gis/ArcAsciiGridAdapter.java
@@ -0,0 +1,744 @@
+//
+// AsciiArcGridAAdapter
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gis;
+
+import java.io.*;
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.util.zip.GZIPInputStream;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import visad.*;
+import visad.data.units.Parser;
+import java.rmi.RemoteException;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * AsciiArcGridAdapter converts an ASCII ArcGrid file into
+ * a VisAD Data object.
+ * @author Don Murray
+ */
+public class ArcAsciiGridAdapter {
+
+
+  /** Key for the western edge of the grid (lower left X position) */
+  private static final String XLLCORNER = "XLLCORNER";
+
+  /** Key for the southern edge of the grid (lower left Y position) */
+  private static final String YLLCORNER = "YLLCORNER";
+
+  /** Key for the center X position of the lower left grid cell */
+  private static final String XLLCENTER = "XLLCENTER";
+
+  /** Key for the center X position of the lower left grid cell */
+  private static final String YLLCENTER = "YLLCENTER";
+
+  /** Key for the number of columns in the grid */
+  private static final String NCOLS = "NCOLS";
+
+  /** Key for the number of rows in the grid */
+  private static final String NROWS = "NROWS";
+
+  /** Key for the resolution of the grid */
+  private static final String CELLSIZE = "CELLSIZE";
+
+  private static final String XCELLSIZE = "XCELLSIZE";
+
+  private static final String YCELLSIZE = "YCELLSIZE";
+
+  /** Alternate key for the missing data value */
+  private static final String NODATA = "NODATA";
+
+  /** Key for the missing data value */
+  private static final String NODATA_VALUE = "NODATA_VALUE";
+
+  /** Arrays of all the keys */
+  private static final String[] KNOWN_KEYS = { XLLCORNER, YLLCORNER,
+                                               XLLCENTER, YLLCENTER,
+                                               NCOLS,     NROWS,
+                                               CELLSIZE, XCELLSIZE, YCELLSIZE, NODATA, NODATA_VALUE };
+
+  /** Default spatial type */
+  public static final RealTupleType DEFAULT_SPATIAL_TYPE =
+      RealTupleType.SpatialCartesian2DTuple;
+
+  /** Default data type */
+  public static final RealType DEFAULT_DATA_TYPE = RealType.Altitude;
+
+  /** type for data */
+  private RealTupleType spatialType = DEFAULT_SPATIAL_TYPE;
+
+  /** type for data */
+  private RealType dataType = DEFAULT_DATA_TYPE;
+
+  /** unit for data */
+  private Unit dataUnit = CommonUnit.meter;
+
+  /** A BufferedReader used to read from ASCIIGRID files */
+  private BufferedReader in;
+  
+  /** Number of rows of profiles in the ASCIIGRID */
+  private int numRows;
+  
+  /** Number of columns in the ASCIIGRID */
+  private int numColumns;
+  
+  /** Size of the grid cell along x axis*/
+  private float cellSizeX;
+
+  /** Size of the grid cell along y axis*/
+  private float cellSizeY;
+  
+  /** lower left corner X position */
+  private float xllCorner;
+  
+  /** lower left corner Y position */
+  private float yllCorner;
+  
+  /** missing data value*/
+  private float missingData = -9999f;   // seems to be the accepted default
+
+  /** header table */
+  private Hashtable headerTable;
+
+  private String filename;
+  private DecimalFormat formatter =  new DecimalFormat();
+  private int numHeaderLines = 0;
+  private float[][] rangeVals;
+  private boolean readHeader = false;
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file.
+   * Data are assumed to be elevation values in meters
+   *
+   * @param filename  name of file to read
+   * @throws VisADException couldn't create VisAD object
+   */
+  public ArcAsciiGridAdapter(String filename)
+      throws VisADException {
+    this(filename, DEFAULT_DATA_TYPE);
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use the RealType specified for the data metadata.  Data are 
+   * assumed to be in the default units of the RealType.
+   * 
+   * @param filename  name of file to read
+   * @param dataType  RealType to use for range units
+   * @throws VisADException couldn't create VisAD object
+   */
+  public ArcAsciiGridAdapter(String filename, RealType dataType)
+      throws VisADException {
+    this(filename, DEFAULT_SPATIAL_TYPE, dataType);
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use the RealType specified for the data metadata.  Data are 
+   * assumed to be in the default units of the RealType.
+   * 
+   * @param filename  name of file to read
+   * @param spatialType  RealTupleType to use for spatial domain
+   * @throws VisADException couldn't create VisAD object
+   */
+  public ArcAsciiGridAdapter(String filename, RealTupleType spatialType)
+      throws VisADException {
+    this(filename, spatialType, DEFAULT_DATA_TYPE);
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use the RealType specified for the data metadata.  Data are 
+   * assumed to be in the default units of the RealType.
+   * 
+   * @param filename  name of file to read
+   * @param spatialType  RealTupleType to use for the spatial domain.
+   * @param dataType  RealType to use for range units
+   * @throws VisADException couldn't create VisAD object
+   */
+  public ArcAsciiGridAdapter(String filename, RealTupleType spatialType, 
+                             RealType dataType) 
+        throws VisADException {
+    this(filename, spatialType, dataType, dataType.getDefaultUnit());
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use units specified for the data.  Data are assumed to be
+   * altitudes.
+   * 
+   * @param filename  name of file to read
+   * @param dataUnit  Unit of data 
+   * @throws VisADException if unit is incompatible
+   */
+  public ArcAsciiGridAdapter(String filename, Unit dataUnit)
+      throws VisADException {
+    this(filename, DEFAULT_SPATIAL_TYPE, DEFAULT_DATA_TYPE, dataUnit);
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use the supplied RealType for the data values, and units
+   * if different from default from RealType.
+   * 
+   * @param filename  name of file to read
+   * @param dataName  name to use for creating RealType
+   * @throws VisADException if unit is incompatible, or problem with file
+   */
+  public ArcAsciiGridAdapter(String filename, String dataName)
+      throws VisADException {
+    this(filename, RealType.getRealType(dataName));
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use the supplied RealType for the data values, and units
+   * if different from default from RealType.
+   * 
+   * @param filename  name of file to read
+   * @param dataName  name for the data
+   * @param unitSpec  valid Unit specification
+   * @throws VisADException if unit is incompatible, or problem with file
+   */
+  
+  public ArcAsciiGridAdapter(String filename, String dataName, String unitSpec)
+      throws VisADException {
+    this(filename, RealType.getRealType(dataName, makeUnit(unitSpec)),
+         makeUnit(unitSpec));
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use the supplied RealType for the data values, and units
+   * if different from default from RealType.
+   * 
+   * @param filename  name of file to read
+   * @param dataType  RealType to use for range units
+   * @param dataUnit  Unit of data if different from <code>dataType</code>
+   *                  default units.
+   * @throws VisADException if unit is incompatible, or problem with file
+   */
+  public ArcAsciiGridAdapter(String filename, RealType dataType, Unit dataUnit)
+      throws VisADException {
+    this(filename, DEFAULT_SPATIAL_TYPE, dataType, dataUnit);
+  }
+
+  /**
+   * Create an ArcAsciiGridAdapter for the particular file and
+   * use the supplied RealType for the data values, and units
+   * if different from default from RealType.
+   * 
+   * @param filename  name of file to read
+   * @param spatialType  RealTupleType to use for the spatial domain.
+   * @param dataType  RealType to use for range units
+   * @param dataUnit  Unit of data if different from <code>dataType</code>
+   *                  default units.
+   * @throws VisADException if unit is incompatible, or problem with file
+   */
+  public ArcAsciiGridAdapter(String filename, RealTupleType spatialType, 
+                             RealType dataType, Unit dataUnit)
+      throws VisADException {
+    if (!Unit.canConvert(dataType.getDefaultUnit(), dataUnit))
+      throw new VisADException("dataUnit incompatible with dataType");
+    this.filename = filename;
+    this.spatialType = spatialType;
+    this.dataType = dataType;
+    this.dataUnit = dataUnit;
+    readHeader();
+  }
+
+  private void makeStream() throws VisADException {
+    try {
+      //ungzip
+      if (filename.endsWith(".gz"))
+        in = new BufferedReader(
+          new InputStreamReader(
+            new GZIPInputStream(new FileInputStream(filename))));
+      else {
+	  in = new BufferedReader(new FileReader(filename));
+      }
+    } catch (IOException e) {
+      throw new VisADException("Couldn't open file: " + filename);
+    }
+  }
+
+  private void readHeader() throws VisADException {
+    if (readHeader) return;
+    makeStream();
+    headerTable = new Hashtable();
+    boolean inHeader = true;
+    String line;
+    numHeaderLines = 0;
+
+    try {
+      while (inHeader) {
+        if ((line = in.readLine()) != null) {
+          StringTokenizer tok = new StringTokenizer(line);
+          if (tok.countTokens() == 2) {
+            String key = tok.nextToken().trim().toUpperCase();
+            if (isKnownKey(key)) {
+              String s = tok.nextToken().trim().toUpperCase();
+              try {
+                headerTable.put(key, new Float(parseValue(s)));
+                numHeaderLines++;
+              } catch (ParseException E) {
+                throw new VisADException(
+                    "Unable to parse value for key " + key + " " + s);
+              }
+            } else { // invalid key
+              throw new VisADException("Unknown header key " + key);
+            }
+          } else { // too many tokens, into data
+            inHeader = false;
+          }
+        } else { // null line
+          inHeader = false;
+        }
+      }
+      in.close();
+    } catch (IOException ioe) {
+      throw new VisADException("Problem reading in header line" + ioe);
+    }
+    if (!checkHeader()) {
+      throw new VisADException("Unable to find enough metadata " + headerTable);
+    }
+    // System.out.println(headerTable.toString());
+    readHeader = true;
+  }
+
+
+  private void readData(Gridded2DSet spatialSet) throws VisADException {
+    if(rangeVals!=null) return;
+    if (!readHeader) readHeader();
+    rangeVals = new float[1][spatialSet.getLength()];
+    makeStream();
+    String line;
+    try {
+      long t1 = System.currentTimeMillis();
+      //skip header
+      for (int i = 0; i < numHeaderLines; i++) line = in.readLine();
+      int index =0;
+      float[]tmpArray =rangeVals[0];
+      StreamTokenizer tok = new StreamTokenizer(in);
+      for (int row = 0; row < numRows; row++) {
+	  int colsRead = 0;
+	  for (int col = 0; col < numColumns; col++) {
+	      colsRead++;
+	      int nextTok = tok.nextToken();
+	      if(nextTok == StreamTokenizer.TT_EOF) break;
+	      if(nextTok != StreamTokenizer.TT_NUMBER) {
+		  throw new VisADException("Unknown value:" + tok.sval);
+	      }
+	      float value = (float)tok.nval;
+	      if (value != missingData) {
+		  tmpArray[index++] = value;
+	      } else {
+		  tmpArray[index++] = Float.NaN;
+	      }	
+	  }
+          if (numColumns != colsRead) {
+	      throw new VisADException(
+				       "Number of values ("+ colsRead + 
+				       ") < number of columns (" + numColumns + ")");
+	      
+	  }
+      }
+      if (index != numColumns*numRows) {
+	  throw new VisADException("Number of values read (" + index +") != expected ("+ (numColumns* numRows));
+	      
+      }
+      long t2 = System.currentTimeMillis();
+      //      System.err.println("time:" + (t2-t1));
+      in.close();
+    } catch (IOException ioe) {
+      throw new VisADException("Error reading data: " + ioe);
+    }
+    //System.out.println("minimum value = " + minimumValue);
+    //System.out.println("maximum value = " + maximumValue);
+  }
+
+
+
+
+
+  private float parseValue(String value) 
+      throws ParseException {
+    return formatter.parse(value).floatValue();
+  }
+
+  private boolean isKnownKey(String key) {
+    for (int i = 0; i < KNOWN_KEYS.length; i++) {
+      if (KNOWN_KEYS[i].equals(key)) return true;
+    } 
+    return false;
+  }
+
+  private static Unit makeUnit(String unitSpec) throws VisADException {
+    try {
+      return Parser.parse(unitSpec);
+    } catch (Exception e) {
+      throw new VisADException("Invalid unit specification " + unitSpec);
+    }
+  }
+
+  private boolean checkHeader() {
+    boolean hasCellSize = headerTable.containsKey(CELLSIZE);
+    if (!hasCellSize) {
+        hasCellSize = headerTable.containsKey(XCELLSIZE) && headerTable.containsKey(YCELLSIZE);
+    }      
+    if (!(headerTable.containsKey(NCOLS) &&
+          headerTable.containsKey(NROWS) &&
+          hasCellSize)
+       ) return false;
+    numRows = ((Float)headerTable.get(NROWS)).intValue();
+    numColumns = ((Float)headerTable.get(NCOLS)).intValue();
+    if(headerTable.containsKey(CELLSIZE)) {
+        cellSizeY = cellSizeX = ((Float)headerTable.get(CELLSIZE)).floatValue();
+    } else {
+        cellSizeX = ((Float)headerTable.get(XCELLSIZE)).floatValue();
+        cellSizeY = ((Float)headerTable.get(YCELLSIZE)).floatValue();
+    }
+    if (headerTable.containsKey(NODATA)) {
+       missingData = ((Float)headerTable.get(NODATA)).floatValue();
+    } else if (headerTable.containsKey(NODATA_VALUE)) {
+      missingData = ((Float)headerTable.get(NODATA_VALUE)).floatValue();
+    } else {
+      missingData = Float.NaN;
+    }
+    if (headerTable.containsKey(XLLCORNER) &&
+        headerTable.containsKey(YLLCORNER)) {
+      xllCorner = ((Float)headerTable.get(XLLCORNER)).floatValue();
+      yllCorner = ((Float)headerTable.get(YLLCORNER)).floatValue();
+    } else if (headerTable.containsKey(XLLCENTER) &&
+               headerTable.containsKey(YLLCENTER)) {
+      xllCorner = 
+        ((Float)headerTable.get(XLLCENTER)).floatValue() - cellSizeX/2;
+      yllCorner = 
+        ((Float)headerTable.get(YLLCENTER)).floatValue() - cellSizeY/2;
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  private Linear2DSet makeSpatialSet() throws VisADException {
+      return makeSpatialSet(getSpatialType());
+  }
+
+  private Linear2DSet makeSpatialSet(RealTupleType spatialType) 
+        throws VisADException {
+    if (!readHeader) readHeader();
+    Linear2DSet spatialSet =
+      new Linear2DSet(spatialType, 
+                      xllCorner, xllCorner+(cellSizeX*(numColumns-1)), numColumns,
+                      yllCorner+(cellSizeY*(numRows-1)), yllCorner, numRows,
+                      (CoordinateSystem) null,
+                      (Unit[]) null,
+                      (ErrorEstimate[]) null,
+                      true /*cache*/);
+    //System.out.println("spatialSet = " + spatialSet);
+    return spatialSet;
+  }
+
+  /**
+   * Make a FlatField using the default data types
+   */
+  private FlatField makeFlatField() throws VisADException {
+    return makeFlatField(getSpatialType(), getDataType());
+  }
+
+  /**
+   * Make a FlatField using the specified MathType.
+   * @param mathType  type to use.  If it's a FunctionType, it defines
+   *                  the return objects function type.  If it's a
+   *                  RealTupleType, it defines the spatial domain type,
+   *                  if it's a RealType, it defines the data type.
+   */
+  private FlatField makeFlatField(MathType mathType) throws VisADException {
+
+    if (mathType instanceof FunctionType) {
+      FunctionType ft = (FunctionType) mathType;
+      return makeFlatField(ft.getDomain(), (RealType)ft.getRange());
+    } else if (mathType instanceof RealTupleType) {
+      return makeFlatField((RealTupleType)mathType, getDataType());
+    } else if (mathType instanceof RealType) {
+      return makeFlatField(getSpatialType(), (RealType)mathType);
+    } else {
+      throw new VisADException("Unable to return data with type " + mathType);
+    }
+  }
+
+  /**
+   * Make a FlatField using the specified spatial domain and range types.
+   */
+  private FlatField makeFlatField(RealTupleType spatialType, RealType rangeType)
+      throws VisADException {
+    Gridded2DSet spatialSet = makeSpatialSet(spatialType);
+    readData(spatialSet);  // if already done, will just return
+    FunctionType ft = new FunctionType(spatialType, rangeType);
+    FlatField ff = new FlatField(ft, spatialSet,
+                                 (CoordinateSystem) null, 
+                                 (Set[]) null,
+                                 new Unit[] {dataUnit});
+    try {
+      ff.setSamples(rangeVals, false);
+    } catch (RemoteException re) {} // can't happen
+    return ff;
+  }
+
+  /**
+   * Get the ASCIIGRID as a VisAD data object
+   * @return a FlatField of type ((getSpatialType()) -> getDataType())
+   */
+  public FieldImpl getData() throws VisADException {
+    return makeFlatField(getSpatialType(), getDataType());
+  }
+  
+  /**
+   * Get the ASCIIGRID as a VisAD data object with the specified spatial domain
+   * and range.
+   * @param spatialType  type for spatial domain
+   * @param dataType   type for range
+   * @return a FlatField of type ((spatialType) -> dataType)
+   */
+  public FieldImpl getData(RealTupleType spatialType, RealType dataType) 
+      throws VisADException {
+    return makeFlatField(spatialType, dataType);
+  }
+  
+  /**
+   * Get the ASCIIGRID as a VisAD data object with the specified domain
+   * and range.
+   * @param mathType  type to use.  If it's a FunctionType, it defines
+   *                  the return objects function type.  If it's a
+   *                  RealTupleType, it defines the domain type,
+   *                  if it's a RealType, it defines the data type.
+   * @return a FlatField based on <code>mathType</code>
+   */
+  public FieldImpl getData(MathType mathType)
+      throws VisADException {
+    return makeFlatField(mathType);
+  }
+  
+  /**
+   * Get the domain set for this DEM as a Longitude, Latitude set
+   * @return Gridded2DSet for domain
+   */
+  public Gridded2DSet getSpatialSet() throws VisADException {
+    return getSpatialSet(getSpatialType());
+  }
+  
+  /**
+   * Get the spatial domain set for this ASCIIGRID with the specified type.
+   * @param spatialType  RealTupleType (dimension 2) to use for this domain
+   * @return Gridded2DSet for spatial domain
+   */
+  public Gridded2DSet getSpatialSet(RealTupleType spatialType) 
+      throws VisADException {
+    return getSpatialSet(spatialType);
+  }
+  
+  /**
+   * Get the x value of the lower left corner of the grid.
+   * @return x value of lower left corner.
+   */
+  public float getXLLCorner() {
+    return xllCorner;
+  }
+
+  /**
+   * Get the y value of the lower left corner of the grid.
+   * @return y value of lower left corner.
+   */
+  public float getYLLCorner() {
+    return yllCorner;
+  }
+
+  /**
+   * Get the cell size of this grid
+   * @return cell size
+   * @deprecated Use getCellSizeX and getCellSizeY
+   */
+  public float getCellSize() {
+    return cellSizeX;
+  }
+
+  /**
+   * Get the cell size of this grid
+   * @return cell size
+   */
+  public float getCellSizeX() {
+    return cellSizeX;
+  }
+
+  /**
+   * Get the cell size of this grid
+   * @return cell size
+   */
+  public float getCellSizeY() {
+    return cellSizeY;
+  }
+
+  /**
+   * Get the missing data value for this grid
+   * @return missing data value (or NaN if not specified)
+   */
+  public float getNoDataValue() {
+    return missingData;
+  }
+
+  /**
+   * Get the number of rows in this grid
+   * @return number of rows
+   */
+  public int getRows() {
+    return numRows;
+  }
+
+  /**
+   * Get the number of columns in this grid
+   * @return number of columns
+   */
+  public int getColumns() {
+    return numColumns;
+  }
+
+  /**
+   * Get the spatial domain type.
+   * @return type of the spatial set.
+   */
+  public RealTupleType getSpatialType() {
+    return spatialType;
+  }
+
+  /**
+   * Set the spatial domain type.
+   * @param newSpatialType  new type for 
+   */
+  public void setSpatialType(RealTupleType newSpatialType) {
+    spatialType = newSpatialType;
+  }
+
+  /**
+   * Set the range type.
+   * @param newType  new type for range
+   */
+  public void setDataType(RealType newType) {
+    dataType = newType;
+    if (!Unit.canConvert(getDataUnit(), dataType.getDefaultUnit())) {
+       dataUnit = dataType.getDefaultUnit();
+    }
+  }
+
+  /**
+   * Get the type of the Data.
+   * @return type of the data
+   */
+  public RealType getDataType() {
+    return dataType;
+  }
+
+  /**
+   * Set the data units
+   * @param newUnit new units for data
+   */
+  public void setDataUnit(Unit newUnit) {
+    dataUnit = newUnit;
+  }
+
+  /**
+   * Get the data units
+   * @return units of the data
+   */
+  public Unit getDataUnit() {
+    return dataUnit;
+  }
+
+  /**
+   * Get the bounds of this grid
+   * @return bounds as a rectangle.
+   */
+  public Rectangle2D getBounds() {
+    return new Rectangle2D.Float(xllCorner, yllCorner, 
+                                 cellSizeX*numColumns, cellSizeY*numRows);
+  }
+
+  /**
+   * Return a string representation of this grid as constructed.
+   * @return String representation:
+   */
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("File: ");
+    buf.append(filename);
+    buf.append("\n");
+    buf.append("Cell size X");
+    buf.append(getCellSizeX());
+    buf.append("Cell size Y");
+    buf.append(getCellSizeY());
+    buf.append("\n");
+    buf.append("Missing value: ");
+    buf.append(getNoDataValue());
+    buf.append("\n");
+    buf.append("Bounds: x=");
+    buf.append(getXLLCorner());
+    buf.append(" y=");
+    buf.append(getYLLCorner());
+    buf.append(" width=");
+    buf.append(getCellSizeX()*getColumns());
+    buf.append(" height=");
+    buf.append(getCellSizeY()*getRows());
+    buf.append("\nData type: " );
+    try {
+      buf.append(new FunctionType(getSpatialType(), getDataType()));
+    } catch (Exception excp) {
+      buf.append(getSpatialType());
+      buf.append(" -> ");
+      buf.append(getDataType());
+    }
+    return buf.toString();
+
+  }
+
+  /** test this class "java visad.data.gis.ArcAsciiGridAdpater <filename>" */
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0) {
+      System.out.println("must supply Arc ASCIIGRID file name");
+      System.exit(1);
+    }
+    ArcAsciiGridAdapter aga = 
+      (args.length == 1) 
+         ? new ArcAsciiGridAdapter(args[0])
+         : (args.length == 2)
+           ? new ArcAsciiGridAdapter(args[0], args[1])
+           : new ArcAsciiGridAdapter(args[0], args[1], args[2]);
+    System.out.println(aga);
+    aga.makeFlatField();
+  }
+
+}
diff --git a/visad/data/gis/ArcAsciiGridForm.java b/visad/data/gis/ArcAsciiGridForm.java
new file mode 100644
index 0000000..4f5def5
--- /dev/null
+++ b/visad/data/gis/ArcAsciiGridForm.java
@@ -0,0 +1,149 @@
+//
+// ArcAsciiGridForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gis;
+
+import java.io.IOException;
+import java.net.URL;
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.MathType;
+import visad.UnimplementedException;
+import visad.VisADException;
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormFileInformer;
+import visad.data.FormNode;
+
+/**
+ * ArcAsciiGridForm is the ARC/INFO ASCIIGRID data format adapter for
+ * serialized visad.Data objects.
+ * See http://www.climatesource.com/format/arc_asciigrid.html for more info
+ */
+
+public class ArcAsciiGridForm extends Form implements FormFileInformer {
+
+  /** counter @serialized*/
+  private static int num = 0;
+  private MathType mathType = null;
+
+  /**
+   * Construct a Form for reading in ARC/INFO ASCIIGRID files
+   */
+  public ArcAsciiGridForm() {
+    super("ArcAsciiGridForm" + num++);
+  }
+
+  /**
+   * Construct a Form for reading in Arc/Info ASCIIGRID files
+   */
+  public ArcAsciiGridForm(MathType dataType) {
+    super("ArcAsciiGridForm" + num++);
+    this.mathType = dataType;
+  }
+
+  /**
+   * Determines if this is a ARC/INFO ASCIIGRID file from the name
+   * @param  name  name of the file
+   * @return  true if it matches the pattern for ARC/INFO ASCIIGRID files
+   */
+  public boolean isThisType(String name) {
+    return (name.endsWith(".dem") ||
+            name.endsWith(".asc"));
+  }
+
+  /**
+   * Determines if this is a ARC/INFO ASCIIGRID file from the starting block
+   * @param  block  block of data to check
+   * @return  false  - there is no identifying block in a ARC/INFO ASCIIGRID file
+   */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /**
+   * Get a list of default suffixes for McIDAS map files
+   * @return  valid list of suffixes
+   */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { ".dem", ".asc" };
+    return suff;
+  }
+
+  /**
+   * Save a VisAD data object in this form
+   * @throws  UnimplementedException  - can't be done yet.
+   */
+  public synchronized void save(String id, Data data, boolean replace)
+         throws BadFormException, IOException, RemoteException, VisADException {
+    throw new UnimplementedException("Can't yet save ARC/INFO ASCIIGRID files");
+  }
+
+  /**
+   * Add data to an existing data object
+   * @throws BadFormException
+   */
+  public synchronized void add(String id, Data data, boolean replace)
+         throws BadFormException {
+    throw new BadFormException("ArcAsciiGridForm.add");
+  }
+
+  /**
+   * Open the file specified by the string
+   * @param  id   string representing the path to the file
+   * @return a Data object representing the map lines. 
+   */
+  public synchronized DataImpl open(String id)
+         throws BadFormException, VisADException {
+
+    ArcAsciiGridAdapter demAdapter = new ArcAsciiGridAdapter(id);
+    return 
+      (mathType == null) 
+          ? demAdapter.getData()
+          : demAdapter.getData(mathType);
+
+  }
+
+  /**
+   * Open the file specified by the URL
+   * @param  url   URL of the remote map file
+   * @return a Data object representing the map lines. 
+   */
+  public synchronized DataImpl open(URL url)
+         throws BadFormException, VisADException, IOException {
+    throw new UnimplementedException("Can't open ARC/INFO ASCIIGRID files from URL");
+  }
+
+  /**
+   * Return the data forms that are compatible with a data object
+   * @return null
+   */
+  public synchronized FormNode getForms(Data data) {
+    return null;
+  }
+}
diff --git a/visad/data/gis/DemFamily.java b/visad/data/gis/DemFamily.java
new file mode 100644
index 0000000..9ece13f
--- /dev/null
+++ b/visad/data/gis/DemFamily.java
@@ -0,0 +1,230 @@
+//
+// DemFamily.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gis;
+
+import java.io.IOException;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+
+import visad.DataImpl;
+
+import visad.*;
+
+import visad.data.*;
+
+import java.net.URL;
+
+/**
+ * A container for all the supported DEM types.  Currently, USGS
+ * DEM and Arc ASCIIGRID formats are supported.
+ * To read a <tt>Data</tt> object from a file or URL:<br>
+ * <pre>
+ *    Data data = new DemFamily("dems").open(string);
+ * </pre>
+ * @see  visad.data.gis.UsgsDemForm
+ * @see  visad.data.gis.ArcAsciiGridForm
+ * @author Don Murray
+ * @version $Revision: 1.6 $ $Date: 2009-03-02 23:35:48 $
+ */
+public class DemFamily extends FunctionFormFamily implements FormFileInformer{
+
+  /**
+   * List of all supported VisAD datatype Forms.
+   * @serial
+   */
+  private static FormNode[] list            = new FormNode[10];
+  private static boolean    listInitialized = false;
+  private static MathType dataType = null;
+
+  /**
+   * Build a list of all known file adapter Forms
+   */
+  private static void buildList() {
+
+    int i = 0;
+
+    try {
+      list[i] = new UsgsDemForm();
+
+      i++;
+    } catch (Throwable t) {}
+    try {
+      list[i] = new ArcAsciiGridForm(dataType);
+
+      i++;
+    } catch (Throwable t) {}
+
+    // throw an Exception if too many Forms for list
+    FormNode junk = list[i];
+
+    while (i < list.length) {
+      list[i++] = null;
+    }
+
+    listInitialized = true;    // WLH 24 Jan 2000
+  }
+
+  /**
+   * Add to the family of the supported map datatype Forms
+   * @param  form   FormNode to add to the list
+   *
+   * @exception ArrayIndexOutOfBoundsException
+   *                   If there is no more room in the list.
+   */
+  public static void addFormToList(FormNode form)
+          throws ArrayIndexOutOfBoundsException {
+
+    synchronized (list) {
+      if (!listInitialized) {
+        buildList();
+      }
+
+      int i = 0;
+
+      while (i < list.length) {
+        if (list[i] == null) {
+          list[i] = form;
+  
+          return;
+        }
+
+        i++;
+      }
+    }
+
+    throw new ArrayIndexOutOfBoundsException("Only " + list.length
+                                                 + " entries allowed");
+  }
+
+  /**
+   * Determines if this is a DEM file from the name
+   * @param  name  name of the file
+   * @return  true if it matches the pattern for USGS DEM files
+   */
+  public boolean isThisType(String name) {
+    return false;
+  }
+
+  /**
+   * Determines if this is a USGS DEM file from the starting block
+   * @param  block  block of data to check
+   * @return  false  - there is no identifying block in a USGS DEM file
+   */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /**
+   * Get a list of default suffixes for McIDAS map files
+   * @return  valid list of suffixes
+   */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { ".dem", ".asc" };
+    return suff;
+  }
+
+
+  /**
+   * Construct a family of the supported map datatype Forms
+   * @param  name   name of the family
+   */
+  public DemFamily(String name) {
+    this(name, null);
+  }
+
+  /**
+   * Construct a family of the supported map datatype Forms
+   * @param  name   name of the family
+   */
+  public DemFamily(String name, MathType dataFormat) {
+
+    super(name);
+    if (dataFormat != null) dataType = dataFormat;
+
+    synchronized (list) {
+      if (!listInitialized) {
+        buildList();
+      }
+    }
+
+    for (int i = 0; (i < list.length) && (list[i] != null); i++) {
+      forms.addElement(list[i]);
+    }
+  }
+
+  /**
+   * Open a local data object using the first appropriate map form.
+   * @param  id   String representing the path of the map file
+   * @throws  BadFormException  - no form is appropriate
+   * @throws  VisADException  - VisAD error
+   */
+  public DataImpl open(String id) throws BadFormException, VisADException {
+    return super.open(id);
+  }
+
+  /**
+   * Open a remote data object using the first appropriate map form.
+   * @param  url   URL representing the location of the map file
+   * @throws  BadFormException  - no form is appropriate
+   * @throws  VisADException  - VisAD error
+   * @throws  IOException  - file not found
+   */
+  public DataImpl open(URL url)
+          throws BadFormException, VisADException, IOException {
+    return super.open(url);
+  }
+
+  /**
+   * Test the DemFamily class
+   * run java visad.data.gis.DemFamily  dem1 dem2 ... demn
+   */
+  public static void main(String[] args)
+          throws BadFormException, IOException, RemoteException,
+                 VisADException {
+
+    if (args.length < 1) {
+      System.err.println("Usage: DemFamily infile [infile ...]");
+      System.exit(1);
+
+      return;
+    }
+
+    DemFamily fr = new DemFamily("DEM data");
+
+    for (int i = 0; i < args.length; i++) {
+      Data data;
+
+      System.out.println("Trying file " + args[i]);
+
+      data = fr.open(args[i]);
+
+      System.out.println(args[i] + ": " + data.getType().prettyString());
+    }
+  }
+}
diff --git a/visad/data/gis/UsgsDemAdapter.java b/visad/data/gis/UsgsDemAdapter.java
new file mode 100644
index 0000000..936daa4
--- /dev/null
+++ b/visad/data/gis/UsgsDemAdapter.java
@@ -0,0 +1,899 @@
+//
+// UsgsDemAdapter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gis;
+
+import java.io.*;
+import java.awt.geom.Rectangle2D;
+import java.util.zip.GZIPInputStream;
+import visad.*;
+import visad.georef.*;
+import visad.data.units.Parser;
+import visad.util.ReflectedUniverse;
+import java.util.Arrays;
+import java.rmi.RemoteException;
+
+/**
+ * UsgsDemAdapter converts the data in a USGS DEM file to a VisAD object.
+ * The specification for * the format can be found at 
+ * <a href="http://rockyweb.cr.usgs.gov/nmpstds/acrodocs/dem/2DEM0198.PDF">
+ * http://rockyweb.cr.usgs.gov/nmpstds/acrodocs/dem/2DEM0198.PDF</a>. Refer
+ * to this document for further explanation of the data elements of a DEM.
+ * <p>
+ * To simplify things, class UsgsDem changes the format of a USGS 7.5 minute
+ * DEM to a simpler "row by column" raster form rather than the 
+ * "row by column by subcolumn" format in the specification.
+ * </p>
+ * @author Don Murray
+ * @author Jeffrey Albert Bergamini 
+ *    (http://srproj.lib.calpoly.edu/projects/csc/jbergam/reports/final.html)
+ * @version $Revision: 1.10 $ $Date: 2009-03-02 23:35:48 $
+ */
+public class UsgsDemAdapter {
+
+  /**************************************************************
+   * Fields related to java implementation
+   **************************************************************/
+
+  /** Southwest corner index constant */
+  private static final int SW = 0;
+  /** Northwest corner index constant */
+  private static final int NW = 1;
+  /** Northeast corner index constant */
+  private static final int NE = 2;
+  /** Southeast corner index constant */
+  private static final int SE = 3;
+   
+  /** Geographic type reference system */
+  private static final int GEOGRAPHIC = 0;
+  /** UTM type reference system */
+  private static final int UTM = 1;
+  /** State type reference system */
+  private static final int STATE = 2;
+
+  /** Temporary storage string */
+  private String tempString;
+   
+  /** Total number of points (vertices) in the DEM */
+  private int numPoints = 0;
+
+  /** A BufferedReader used to read from DEM files */
+  private BufferedReader in;
+
+  /** Marker to keep track of position within a record */
+  private int pos;
+
+  /** Record block size */
+  private static final int BLOCK_SIZE = 1024;
+
+  /** Temporary storage array */
+  private char[] array = new char[BLOCK_SIZE];
+
+  /**************************************************************
+   * Variables related to USGS DEM format
+   **************************************************************/
+  
+  /** Measurement definition constant */
+  private static final int
+     RADIANS = 0,
+     FEET = 1,
+     METERS = 2,
+     ARCSECONDS = 3;
+     
+  private static RealType EASTING = UTMCoordinate.EASTING;
+  private static RealType NORTHING = UTMCoordinate.NORTHING;
+
+  private static Unit[] measureUnits = new Unit[4];
+  static {
+      measureUnits[RADIANS] = CommonUnit.radian;
+      measureUnits[METERS] = CommonUnit.meter;
+      try {
+          measureUnits[FEET] = Parser.parse("foot");
+          measureUnits[ARCSECONDS] = Parser.parse("arcseconds");
+      } catch (Exception e) {}
+  }
+
+  private static int MISSING_VALUE = -32767;
+
+  /**
+   * File Name -- USGS DEM Logical Record Type A, Data Element 1
+   * <br>
+   * The authorized digital cell name followed by a
+   * comma, space, and the two-character State
+   * designator(s) separated by hyphens. Abbreviations
+   * for other countries, such as Canada and Mexico,
+   * shall not be represented in the DEM header.
+   */
+  private String fileName;
+  
+  /**
+   * Free Format Text -- USGS DEM Logical Record Type A, Data Element 1
+   * <br>
+   * Free format descriptor field, contains useful information
+   * related to digital process such as digitizing instrument,
+   * photo codes, slot widths, etc.
+   */
+  private String freeFormatText;
+  
+  /**
+   * Origin Code -- USGS DEM Logical Record Type A, Data Element 2
+   * <br><pre>
+   * 1=Autocorrelation RESAMPLE Simple bilinear
+   * 2=Manual profile GRIDEM Simple bilinear
+   * 3=DLG/hypsography CTOG 8-direction linear
+   * 4=Interpolation from photogrammetric system contours DCASS 4-direction linear
+   * 5=DLG/hypsography LINETRACE, LT4X Complex linear
+   * 6=DLG/hypsography CPS-3, ANUDEM, GRASS Complex polynomial
+   * 7=Electronic imaging (non-photogrametric), active or passive, sensor systems.
+   * </pre>
+   */
+  private String originCode;
+   
+  /**
+   * DEM Level Code -- USGS DEM Logical Record Type A, Data Element 3
+   * <br><pre>
+   * Code 1=DEM-1
+   *      2=DEM-2
+   *      3=DEM-3
+   *      4=DEM-4
+   * </pre>
+   */
+  private int level;
+   
+  /**
+   * Code defining ground planimetric reference system -- USGS DEM Logical Record Type A, Data Element 5
+   * <br><pre>
+   * Code 0=Geographic
+   *      1=UTM
+   *      2=State plane
+   * For codes 3-20, see Appendix 2-G.
+   * Code 0 represents the geographic
+   * (latitude/longitude) system for 30-minute,
+   * 1-degree and Alaska DEM's. Code 1 represents
+   * the current use of the UTM coordinate system for
+   * 7.5-minute DEM's
+   * </pre>
+   */
+  private int referenceSystem;
+  
+  /** 
+   * Code defining zone in ground planimetric reference system.
+   * USGS DEM Logical Record Type A, Data Element 6 
+   */
+  private int zone;
+  
+  /**
+   * Code defining unit of measure for ground planimetric coordinates.
+   * USGS DEM Logical Record Type A, Data Element 8
+   * <pre>
+   * 0 = radians
+   * 1 = feet
+   * 2 = meters
+   * 3 = arc-seconds
+   */
+  private int groundMeasure;
+  
+  /**
+   * Code defining unit of measure for elevation coordinates.
+   * USGS DEM Logical Record Type A, Data Element 9
+   */
+  private int elevationMeasure;
+  
+  /**
+   * A 4-element array containing the ground coordinates of the quadrangle
+   * boundary for the DEM.
+   *
+   * USGS DEM Logical Record Type A, Data Element 11
+   */
+  private RealTuple[] corner = new RealTuple[4];
+   
+  /** Minimum elevation */
+  private float minElevation;
+  /** Maximum elevation */
+  private float maxElevation;
+
+  /** Rotation angle elevation */
+  private float rotationAngle = 0;
+
+  /** Minimum easting */
+  private float minX = Float.POSITIVE_INFINITY;
+  /** Maximum easting */
+  private float maxX = Float.NEGATIVE_INFINITY;
+
+  /** Minimum northing */
+  private float minY = Float.POSITIVE_INFINITY;
+  /** Maximum northing */
+  private float maxY = Float.NEGATIVE_INFINITY;
+
+  /** Range of elevation */
+  private float elevationRange;
+  /** Range of easting */
+  private float xRange;
+  /** Range of northing */
+  private float yRange;
+  
+  /** 
+   * Elevation accuracy code
+   * USGS DEM Logical Record Type A, Data Element 14
+   */
+  private int elevationAccuracy;
+  
+  /** 
+   * X (easting) spatial resolution
+   * USGS DEM Logical Record Type A, Data Element 15
+   */
+  private float xResolution;
+  /** 
+   * Y (northing) spatial resolution
+   * USGS DEM Logical Record Type A, Data Element 15
+   */
+  private float yResolution;
+  /** 
+   * Z (elevation) spatial resolution
+   * USGS DEM Logical Record Type A, Data Element 15
+   */
+  private float zResolution;
+  
+  /** Number of rows of profiles in the DEM */
+  // this is always 1 and superfluous
+  private int numRows;
+  
+  /** 
+   * Number of columns (in the northing dimension)
+   * of profiles in the DEM 
+   */
+  private int numColumns;
+  
+  /**
+   * Maximum number of data points per column (easting dimension)
+   */
+  private int maxRows;
+
+  /** 
+   * float array of points in the form [xyz][numpoints]
+   */
+  private float[][] rawCoords;
+
+  /**
+   * Vertical Datum code
+   */
+  private int verticalDatum;
+
+  /**
+   * Horizontal Datum code
+   */
+  private int horizontalDatum;
+
+  private Linear2DSet domainSet;
+  private FlatField data;
+  private boolean canDoGeotransform = true;
+  private ReflectedUniverse reflectUni;
+
+  /**
+   * Default constructor, everything remains null. Call load() to load a file.
+   */
+  public UsgsDemAdapter () {
+    try {
+      reflectUni = new ReflectedUniverse();
+      reflectUni.exec("import ucar.visad.UTMCoordinateSystem");
+      reflectUni.exec("import geotransform.ellipsoids.Ellipsoid");
+    } catch (VisADException exc) { canDoGeotransform = false; }
+  }
+
+  /**
+   * Constructs a new UsgsDemAdapter object with data read from the given
+   * (native format, non-SDTS) USGS DEM file
+   *
+   * @param filename the name of the DEM file
+   */
+  public UsgsDemAdapter (String filename) throws IOException, VisADException {
+    try {
+      reflectUni = new ReflectedUniverse();
+      reflectUni.exec("import ucar.visad.UTMCoordinateSystem");
+      reflectUni.exec("import geotransform.ellipsoids.Ellipsoid");
+    } catch (VisADException exc) { canDoGeotransform = false; }
+    if (filename != null) load(filename);
+  }
+
+  /**
+   * Reinitializes this UsgsDemAdapter object with data read from the given
+   * (non-SDTS) USGS DEM file.
+   *
+   * @param filename the name of the DEM file
+   * @throws IOException - If an I/O error occurs (invalid file)
+   */
+  public void load(String filename) throws IOException, VisADException {
+      
+    numPoints = 0;
+    data = null;
+    domainSet = null;
+    
+    try {
+      //ungzip
+      if (filename.endsWith(".gz"))
+        in = new BufferedReader(
+          new InputStreamReader(
+            new GZIPInputStream(new FileInputStream(filename))));
+      else
+        in = new BufferedReader(new FileReader(filename));
+    } catch (Exception e) {
+      throw new IOException("Couldn't open file: " + filename);
+    }
+     
+    processRecordTypeA();
+     
+    for (int j=0; j<numColumns; j++)  {
+      // System.out.println("Processing column " + j);
+      processRecordTypeB();
+    }
+     
+    elevationRange = maxElevation - minElevation;
+    xRange = maxX - minX;
+    yRange = maxY - minY;
+    // makeFlatField();
+     
+  }
+  
+  /**
+   * Reads in the data from a USGS DEM Logical Record Type A and
+   * sets the appropriate fields for this DEM object
+   */   
+  private void processRecordTypeA() throws IOException, VisADException {
+     
+    pos = 1;
+                                     // Elements:
+    fileName = getString(40);        // 1 get file name
+    freeFormatText = getString(40);  // 1 get format text
+    skip(29);                        // 1 skip filler
+    skip(26);                        // 1 skip SE geo corner
+    skip(1);                         // 1 skip process code
+    skip(1);                         // 1 skip filler
+    skip(3);                         // 1 skip sectional indicator
+    originCode = getString(4);       // 2 get origin code
+    level = parseInt(6);             // 3 get level code
+    skip(6);                         // 4 skip code defining elevation pattern (always 1)
+    referenceSystem = parseInt(6);   // 5 get code defining ground planimetric reference system
+    if (!(referenceSystem == GEOGRAPHIC ||
+          referenceSystem == UTM)) {
+      throw new VisADException(
+        "Unimplemented reference system " + referenceSystem);
+    }
+    zone = parseInt(6);              // 6 get code defining zone in ground planimetric ref. system
+    skip(360);                       // 7 skip map projection parameters (all zero)
+    groundMeasure = parseInt(6);     // 8 get code defining unit of measure for ground planimetric coords
+    elevationMeasure = parseInt(6);  // 9 get code defining unit of measure for elevation coords
+    skip(6);                         // 10 skip number of sides of polygon defining DEM coverage (always 4)
+    corner[SW] = parseCoordinate();  // 11 get SW corner of this quadrangle
+    corner[NW] = parseCoordinate();  // 11 get NW corner of this quadrangle
+    corner[NE] = parseCoordinate();  // 11 get NE corner of this quadrangle
+    corner[SE] = parseCoordinate();  // 11 get SE corner of this quadrangle
+    minElevation = parseFloat(24);   // 12 get minimum elevation
+    maxElevation = parseFloat(24);   // 12 get maximum elevation
+    rotationAngle = parseFloat(24);  // 13 skip angle thing which isn't used
+    elevationAccuracy = parseInt(6); // 14 get accuracy code for elevations
+    xResolution = parseFloat(12);    // 15 get x resolution (spacing)
+    yResolution = parseFloat(12);    // 15 get y resolution (spacing)
+    zResolution = parseFloat(12);    // 15 get z resolution (spacing)
+    numRows = parseInt(6);           // 16 get number of rows (always 1 -- that's kind of dumb)
+    if (numRows != 1) throw new VisADException("Can't handle " + numRows + " rows");
+    numColumns = parseInt(6);        // 16 get number of columns
+    skip(24);
+    try {
+      verticalDatum = parseInt(2);     // 17 get vertical datum
+      horizontalDatum = parseInt(2);   // 18 get horizontal datum
+    } catch (NumberFormatException nfe) { // if old format
+      verticalDatum = 2;
+      horizontalDatum = (referenceSystem == UTM)?1:2;
+    }
+    skip(BLOCK_SIZE-pos+1);          // skip to end of block
+    
+    // since numRows is always 1,
+    // coordinate array should be size (numColumns)x(numRows)
+    rawCoords = 
+      (referenceSystem == GEOGRAPHIC)
+         ? new float[3][numColumns*1201]
+         : new float[3][numColumns*2000];
+           
+    //System.out.println("DONE WITH A"); 
+  }
+   
+  /**
+   * Processes a Type B USGS DEM record
+   * (see specification
+   */  
+  private void processRecordTypeB() throws IOException, VisADException {
+     
+    pos = 1;
+     
+    float Xp, Yp, Xgp, Ygp, elevation;
+     
+    int row = parseInt(6);                 // 1 row number
+    int column = parseInt(6)-1;            // 1 column number (adjusted >= 0)
+    int m = parseInt(6);                   // 2 num elevations
+    int n = parseInt(6);                   // 2 n is always 1 -- hmm, useful
+    float Xgo = parseFloat(24);            // x coord of starting point
+    float Ygo = parseFloat(24);            // y coord of starting point
+    float localElevation = parseFloat(24); // elevation of local datum
+    float min = parseFloat(24);            // min elevation for the profile
+    float max = parseFloat(24);            // max elevation for the profile
+
+    if (m > maxRows) maxRows = m;
+
+    float costheta = (float) Math.cos(rotationAngle);
+    float sintheta = (float) Math.sin(rotationAngle);
+
+    /*
+     * Calculate profile coordinates
+     */
+    int block = 0;
+    for (int j=0; j<m; j++) {
+      for (int i = 0; i<n; i++) {
+
+        // see Figure 5 in docs for equation explanation
+        Xp = i*xResolution;
+        Yp = j*yResolution;
+        Xgp = Xgo + Xp*costheta + Yp*sintheta;
+        Ygp = Ygo + Xp*sintheta + Yp*costheta;
+
+        int rawElev = parseInt(6);
+        if (rawElev == MISSING_VALUE) {
+            elevation =  Float.NaN;
+        } else {
+            elevation = rawElev*zResolution+localElevation;
+        }
+        //update min and max x,y
+        minX = Math.min(Xgp, minX);
+        maxX = Math.max(Xgp, maxX);
+        minY = Math.min(Ygp, minY);
+        maxY = Math.max(Ygp, maxY);
+        if (!Float.isNaN(elevation)) {
+           minElevation = Math.min(minElevation, elevation);
+           maxElevation = Math.max(maxElevation, elevation);
+        }
+        /* 
+        if( j == m-1)  {
+          System.out.println(
+              "Start for column " + column + " = " + Xgo + "," + Ygo);
+          System.out.print("number of rows = " + m);
+          System.out.print(" number of columns = " + n);
+          System.out.print(" theta = " + rotationAngle);
+          System.out.print(" costheta = " + costheta);
+          System.out.print(" sintheta = " + sintheta);
+          System.out.print(" Xgp = " + Xgp);
+          System.out.println(" Ygp = " + Ygp);
+          System.out.print("minX = " + minX);
+          System.out.print(" maxX = " + maxX);
+          System.out.print(" minY = " + minY);
+          System.out.println(" maxY = " + maxY);
+        }
+        */
+        rawCoords[0][numPoints] = Xgp;
+        rawCoords[1][numPoints] = Ygp;
+        rawCoords[2][numPoints] = elevation;
+        numPoints++;
+        // make new size if we need to.
+        if (numPoints == rawCoords[0].length) {
+           int oldSize = rawCoords[0].length;
+           int newSize = oldSize + oldSize/2;
+           // System.out.println("old size = " + oldSize + " new = " + newSize);
+           float[][] tempCoords = rawCoords;
+           rawCoords = new float[3][newSize];
+           for (int l = 0; l < rawCoords.length; l++) {
+               System.arraycopy(tempCoords[l], 0, rawCoords[l], 0, oldSize);
+           }
+           tempCoords = null;
+        }
+        int numLeft = BLOCK_SIZE-(pos%BLOCK_SIZE)+1;
+        // System.out.println("numleft = " + numLeft);
+        if (numLeft < 6) skip(numLeft);
+      }
+    }
+    // System.out.println("done with loop, skipping " + (BLOCK_SIZE-(pos%BLOCK_SIZE)+1) + " chars");
+    int numToNextRecord = BLOCK_SIZE-(pos%BLOCK_SIZE)+1;
+    if (numToNextRecord > 0 && numToNextRecord < BLOCK_SIZE) {
+        skip(numToNextRecord);
+    }
+
+    // System.out.println("Size was " + pos);
+  
+    // System.out.println("DONE WITH B"); 
+  }
+
+  /**
+   * Multi-line string representation
+   * @return An info string suitable for printing to the console.
+   */
+  public String toString() {
+
+    StringBuffer s = new StringBuffer();
+    s.append(printElement("File name", fileName));
+    s.append(printElement("Free Format Text", freeFormatText));
+    s.append(printElement("Origin code", originCode));
+    s.append(printElement("Level code", level));
+    s.append(printElement("Reference system code", referenceSystem));
+    s.append(printElement("Reference system zone code", zone));
+    s.append(printElement("Ground measurement units", measureUnits[groundMeasure]));
+    s.append(printElement("Elev. measurement units", measureUnits[elevationMeasure]));
+    s.append(printElement("SW corner", corner[SW]));
+    s.append(printElement("NW corner", corner[NW]));
+    s.append(printElement("NE corner", corner[NE]));
+    s.append(printElement("SE corner", corner[SE]));
+    s.append(printElement("Min x", minX));
+    s.append(printElement("Max x", maxX));
+    s.append(printElement("Min y", minY));
+    s.append(printElement("Max y", maxY));
+    s.append(printElement("Min elevation", minElevation));
+    s.append(printElement("Max elevation", maxElevation));
+    s.append(printElement("Elevation accuracy code", elevationAccuracy));
+    s.append(printElement("X resolution", xResolution));
+    s.append(printElement("Y resolution", yResolution));
+    s.append(printElement("Z resolution", zResolution));
+    s.append(printElement("Columns of profiles", numColumns));
+    s.append(printElement("horizontal units", horizontalDatum));
+    s.append(printElement("vertical units", verticalDatum));
+    s.append(printElement("Max m in columns", maxRows));
+    s.append(printElement("Total data", numPoints));
+    return s.toString();
+  }
+
+  /**************************************************************
+   * Methods for gathering data elements from the file
+   **************************************************************/
+
+  /**
+   * Skips the specified number of bytes in the current file
+   */
+  private void skip (int length) throws IOException {
+     
+    try {
+      in.skip(length);
+    } catch (IOException e) {
+      throw new IOException("Could not skip " + length + " characters");
+    }
+     
+    pos += length;
+  }
+
+  /**
+   * Reads in and returns a string of the specified length from the current
+   * position
+   */
+  private String getString (int length) throws IOException {
+     
+    if (length > 1024)
+      throw new IOException("Attempt to read more than 1024 bytes from file");
+     
+    try {
+      in.read (array, 0, length);
+    } catch (IOException e) {
+      throw new IOException("Couldn't read string from file");
+    }
+     
+    pos += length;
+     
+    return (new String (array, 0 , length)).trim();
+  }
+
+  /**
+   * Reads in and returns a DEM-style integer from the current file position
+   * (taking up the specified number of bytes) as an int
+   */
+  private int parseInt (int digits) throws IOException {
+           
+    if (digits > 1024)
+       throw new IOException("Attempt to read more than 1024 bytes from file");
+
+    try {
+      in.read(array, 0, digits);
+    } catch (IOException e) {
+      throw new IOException("Couldn't read integer from file");
+    }
+     
+    pos += digits;
+
+    // System.out.println("\"" + new String(array, 0, digits) + "\"");
+     
+    return Integer.parseInt((new String(array, 0 , digits)).trim());
+  }
+
+  /**
+   * Reads in and returns a DEM-style integer of unknown length from the
+   * current position in the file as an int (necessary for non-standard DEMs)
+   */
+  private int parseInt () throws IOException {
+     
+    tempString = "";
+    char c;
+     
+    try {
+      do {
+        c = (char) in.read();
+        pos++;
+      } while(Character.isWhitespace(c));
+
+      tempString += c;
+        
+      do {
+        c = (char) in.read();
+        pos++;
+        tempString += c;
+      } while(!(Character.isWhitespace(c)));
+        
+    } catch (IOException e) {
+      throw new IOException("Couldn't read integer from file");
+    }
+     
+    tempString = tempString.substring(0,tempString.length()-1);
+    // System.out.println("tempString = " + tempString);
+    return Integer.parseInt(tempString);
+  }
+
+  /**
+   * Reads in and returns a DEM-style float from the current position in the
+   * file.
+   * @throws IOException If unable to read from file
+   */   
+  private float parseFloat (int digits) throws IOException {
+
+    //System.out.println("POS = " + pos);
+           
+    if (digits > 1024)
+      throw new IOException("Attempt to read more than 1024 bytes from file");
+
+    try {
+      in.read(array, 0, digits);
+    } catch (IOException e) {
+      throw new IOException("Couldn't read integer from file");
+    }
+     
+    pos += digits;
+
+    // System.out.println("\"" + new String(array, 0, digits) + "\"");
+     
+    return translateReal((new String(array, 0 , digits)).trim());
+  }
+  
+  /**
+   * Translates the String representation of a DEM-style real number
+   * into a float
+   */
+  private float translateReal (String real) {
+     
+    // System.out.println("\"" + real + "\"");
+     
+    int plus, exp;
+     
+    plus = real.indexOf('+');
+     
+    if (plus > 0)
+      exp = Integer.parseInt(real.substring(plus+1,plus+3));
+    else
+      return Float.parseFloat(real);
+     
+    if (real.charAt(0) != '-') {
+      tempString = 
+        real.charAt(0) + real.substring(2,exp+2) + "." + 
+          real.substring(exp+2,plus-1);
+    } else {
+      tempString = real.substring(0,2) + real.substring(3,exp+3) + 
+          "." + real.substring(exp+2,plus-1);
+    }
+
+    return Float.parseFloat(tempString);
+  }
+  
+  /**
+   * Loads a UTMCoordinate from the current position in the file
+   */
+  private RealTuple parseCoordinate() throws IOException, VisADException {
+           
+    if  (referenceSystem == GEOGRAPHIC) {
+       // LatLonTuple is lat,lon order
+       float lon = parseFloat(24);
+       return (RealTuple) new LatLonTuple (parseFloat(24), lon);
+    } else if (referenceSystem == UTM) {
+       return (RealTuple) 
+         new UTMCoordinate (parseFloat(24), parseFloat(24), zone);
+    } else {
+       throw new VisADException(
+         "Can't handle referenceSystem " + referenceSystem);
+    }
+  }
+  
+  /** formatting stuff */
+  private String printElement(String title, Object value) {
+    return padRight(title,30) + ": " + value + "\n";
+  }
+
+  /** formatting stuff */
+  private String printElement(String title, int value) {
+    return printElement(title, new Integer(value));
+  }
+
+  /** formatting stuff */
+  private String printElement(String title, float value) {
+    return printElement(title, new Float(value));
+  }
+
+  /**
+   * This method takes any Object and using its String representation
+   * provided by its toString() method, pads it with blank characters
+   * on the right, to a specified length.
+   * @param obj Object to be padded
+   * @param i padding length
+   */
+  public static String padRight(Object obj, int i) {
+    char filler = ' ';
+    String s = new String(obj.toString());
+    int j = s.length();
+    StringBuffer buf = new StringBuffer(s);
+    for(int k = 0; k < i - j; k++) buf.append(filler);
+    return buf.toString();
+  }
+
+  private Linear2DSet makeDomainSet() throws VisADException {
+    RealTupleType rtt = null;
+    Unit[] units = null;
+    switch (groundMeasure) {
+      case RADIANS:
+        units = new Unit[] {measureUnits[RADIANS], measureUnits[RADIANS]};
+        break;
+      case FEET:
+        units = new Unit[] {measureUnits[FEET], measureUnits[FEET]};
+        break;
+      case METERS:
+        units = new Unit[] {measureUnits[METERS], measureUnits[METERS]};
+        break;
+      case ARCSECONDS:
+        units = new Unit[] {measureUnits[ARCSECONDS], measureUnits[ARCSECONDS]};
+        break;
+    }
+    if (referenceSystem == UTM) {
+      // check to see if we can do uncompression
+      CoordinateSystem cs = null;
+      if (canDoGeotransform) {
+        try {
+          // the true says NH.  Can we figure this out somehow?
+          switch (horizontalDatum) {
+            case 1:
+              reflectUni.setVar(
+                "ellipsoid", reflectUni.getVar("UTMCoordinateSystem.CC"));
+              break;
+            case 2:
+              reflectUni.setVar(
+                "ellipsoid", reflectUni.getVar("UTMCoordinateSystem.WD"));
+              break;
+            case 3:
+              reflectUni.setVar(
+                "ellipsoid", reflectUni.getVar("UTMCoordinateSystem.WE"));
+              break;
+            case 4:
+              reflectUni.setVar(
+                "ellipsoid", reflectUni.getVar("UTMCoordinateSystem.RF"));
+              break;
+            default:
+              reflectUni.setVar(
+                "ellipsoid", reflectUni.getVar("UTMCoordinateSystem.RF"));
+          }
+          reflectUni.setVar(
+            "bounds", new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY));
+          reflectUni.setVar("zone", zone);
+          reflectUni.setVar("north", true);
+          cs = (CoordinateSystem)
+            reflectUni.exec(
+              "cs = new UTMCoordinateSystem(ellipsoid, zone, north, bounds)");
+           cs = new CachingCoordinateSystem(cs);
+         } catch (VisADException ve) {
+           System.err.println(
+           "ucar.visad.UTMCoordinateSystem not found for UTM->lat/lon conversion" + ve);
+           cs = null;
+         }
+         rtt = new RealTupleType(EASTING, NORTHING, cs, null);
+       }
+    } else {
+      rtt = RealTupleType.SpatialEarth2DTuple;
+    }
+    domainSet =
+      (referenceSystem == UTM)
+         ?  new Linear2DSet(rtt, 
+                            //minX, maxX, ((int)(xRange/xResolution))+1,
+                            minX, minX+(xResolution*(numColumns-1)), numColumns,
+                            minY, maxY, ((int)(yRange/yResolution))+1,
+                            (CoordinateSystem) null,  /* in rtt if used */
+                            units, (ErrorEstimate[]) null, 
+                            true /*cache*/)
+         /* GEOGRAPHIC */
+         : new LinearLatLonSet(rtt, 
+                               minX, minX+(xResolution*(numColumns-1)), numColumns,
+                               minY, minY+(yResolution*(maxRows-1)), maxRows,
+                               (CoordinateSystem) null,  /* in rtt if used */
+                               units, (ErrorEstimate[]) null, 
+                               true /*cache*/);
+    // System.out.println("domainSet = " + domainSet);
+    return domainSet;
+  }
+
+  private FlatField makeFlatField() throws VisADException {
+    makeDomainSet();
+    float[][] altitudes = new float[1][domainSet.getLength()];
+    Arrays.fill(altitudes[0], Float.NaN);
+    FunctionType ft = 
+      new FunctionType(
+        ((SetType)domainSet.getType()).getDomain(), RealType.Altitude);
+    FlatField ff = new FlatField(ft, domainSet, 
+                                 (CoordinateSystem) null, 
+                                 (Set[]) null,
+                                 new Unit[] {measureUnits[elevationMeasure]});
+    RealTuple coord = null;
+    int index = 0;
+    int[] indices = 
+      domainSet.valueToIndex(new float[][] {rawCoords[0], rawCoords[1]});
+    float alt;
+    int numMissing = 0;
+    for (int i = 0; i < numPoints; i++) {
+        alt = rawCoords[2][i];
+        if (indices[i] < 0) {
+           numMissing++;
+           altitudes[0][indices[i]] = Float.NaN;
+        } else {
+           altitudes[0][indices[i]] = rawCoords[2][i];
+        }
+    }
+    // System.out.println("Out of " +numPoints+ " points, " + numMissing + " were missing");
+    try {
+      ff.setSamples(altitudes, false);
+    } catch (RemoteException re) {} // can't happen
+    data = ff;
+    return data;
+  }
+
+  /**
+   * Get the DEM as a VisAD data object
+   * @return a FlatField of type ((x,y) -> altitude) where x,y is either
+   *         (Longitude, Latitude) or (EASTING, NORTHING)
+   */
+  public FieldImpl getData() throws VisADException {
+    return (data == null) ? makeFlatField(): data;
+  }
+  
+  /**
+   * Get the domain set for this DEM
+   * @return Gridded2DSet for domain
+   */
+  public Gridded2DSet getDomain() throws VisADException {
+    return (domainSet == null) ? makeDomainSet() : domainSet;
+  }
+  
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0) {
+      System.out.println("Need to supply a filename");
+      System.exit(1);
+    }
+    UsgsDemAdapter uda = new UsgsDemAdapter(args[0]);
+    System.out.println(uda);
+    System.out.println(uda.getData().getDomainSet());
+  }
+}
diff --git a/visad/data/gis/UsgsDemForm.java b/visad/data/gis/UsgsDemForm.java
new file mode 100644
index 0000000..7c2623f
--- /dev/null
+++ b/visad/data/gis/UsgsDemForm.java
@@ -0,0 +1,130 @@
+//
+// UsgsDemForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.gis;
+
+import visad.*;
+import visad.data.*;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.net.URL;
+
+
+/**
+   UsgsDemForm is the USGS DEM data format adapter for
+   serialized visad.Data objects.
+*/
+public class UsgsDemForm extends Form implements FormFileInformer {
+
+  /** counter @serialized*/
+  private static int num = 0;
+
+  /**
+   * Construct a Form for reading in USGS DEM files
+   */
+  public UsgsDemForm() {
+    super("UsgsDemForm" + num++);
+  }
+
+  /**
+   * Determines if this is a USGS DEM file from the name
+   * @param  name  name of the file
+   * @return  true if it matches the pattern for USGS DEM files
+   */
+  public boolean isThisType(String name) {
+    return (name.endsWith(".dem"));
+  }
+
+  /**
+   * Determines if this is a USGS DEM file from the starting block
+   * @param  block  block of data to check
+   * @return  false  - there is no identifying block in a USGS DEM file
+   */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /**
+   * Get a list of default suffixes for McIDAS map files
+   * @return  valid list of suffixes
+   */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { ".dem" };
+    return suff;
+  }
+
+  /**
+   * Save a VisAD data object in this form
+   * @throws  UnimplementedException  - can't be done yet.
+   */
+  public synchronized void save(String id, Data data, boolean replace)
+         throws BadFormException, IOException, RemoteException, VisADException {
+    throw new UnimplementedException("Can't yet save USGS DEM files");
+  }
+
+  /**
+   * Add data to an existing data object
+   * @throws BadFormException
+   */
+  public synchronized void add(String id, Data data, boolean replace)
+         throws BadFormException {
+    throw new BadFormException("UsgsDemForm.add");
+  }
+
+  /**
+   * Open the file specified by the string
+   * @param  id   string representing the path to the file
+   * @return a Data object representing the map lines. 
+   */
+  public synchronized DataImpl open(String id)
+         throws BadFormException, IOException, VisADException {
+    try {
+      UsgsDemAdapter demAdapter = new UsgsDemAdapter(id);
+      return demAdapter.getData();
+
+    } catch (IOException e) {
+      throw new VisADException("IOException: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Open the file specified by the URL
+   * @param  url   URL of the remote map file
+   * @return a Data object representing the map lines. 
+   */
+  public synchronized DataImpl open(URL url)
+         throws BadFormException, VisADException, IOException {
+    throw new UnimplementedException("Can't open USGS DEM files from URL");
+  }
+
+  /**
+   * Return the data forms that are compatible with a data object
+   * @return null
+   */
+  public synchronized FormNode getForms(Data data) {
+    return null;
+  }
+}
diff --git a/visad/data/hdf5/COPYING b/visad/data/hdf5/COPYING
new file mode 100644
index 0000000..5010636
--- /dev/null
+++ b/visad/data/hdf5/COPYING
@@ -0,0 +1,41 @@
+Copyright Notice and Statement for NCSA Hierarchical Data Format (HDF)
+Software Library and Utilities
+
+NCSA Hierarchical Data Format (HDF) Software Library and Utilities 
+Copyright 1998,1999,2000 the Board of Trustees of the University of Illinois. 
+All rights reserved.
+
+Contributors to the library: National Center for Supercomputing 
+Applications (NCSA) at the University of Illinois, Lawrence 
+Livermore Nat'l Laboratory (LLNL), Sandia National Laboratories (SNL), 
+Los Alamos National Laboratory (LANL).
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted for any purpose (including commercial purposes)
+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 materials provided with the distribution.
+
+3.  In addition, redistributions of modified forms of the source or binary
+    code must carry prominent notices stating that the original code was
+    changed and the date of the change.
+
+4.  All publications or advertising materials mentioning features or use of
+    this software must acknowledge that it was developed by the National
+    Center for Supercomputing Applications at the University of Illinois, and
+    credit the Contributors.
+
+5.  Neither the name of the University nor the names of the Contributors may
+    be used to endorse or promote products derived from this software without
+    specific prior written permission from the University or the Contributors.
+
+6.  THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND THE CONTRIBUTORS "AS IS"
+    WITH NO WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED.  In no event
+    shall the University or the Contributors be liable for any damages
+    suffered by the users arising out of the use of this software, even if
+    advised of the possibility of such damage.
diff --git a/visad/data/hdf5/HDF5AdapterException.java b/visad/data/hdf5/HDF5AdapterException.java
new file mode 100644
index 0000000..2bd7567
--- /dev/null
+++ b/visad/data/hdf5/HDF5AdapterException.java
@@ -0,0 +1,42 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package visad.data.hdf5;
+
+import visad.VisADException;
+
+public class HDF5AdapterException extends VisADException {
+	public HDF5AdapterException() { super(); }
+	public HDF5AdapterException(String s) { super(s); }
+}
diff --git a/visad/data/hdf5/HDF5DataAdaptable.java b/visad/data/hdf5/HDF5DataAdaptable.java
new file mode 100644
index 0000000..6860710
--- /dev/null
+++ b/visad/data/hdf5/HDF5DataAdaptable.java
@@ -0,0 +1,53 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package visad.data.hdf5;
+
+import java.rmi.RemoteException;
+import visad.VisADException;
+import visad.MathType;
+import visad.DataImpl;
+
+/**
+ * The interface for HDF5 data objects which can be adapted by the
+ * VisAD data objects
+ */
+public interface HDF5DataAdaptable
+{
+	MathType getMathType() throws VisADException;
+
+	DataImpl getAdaptedData() throws VisADException, RemoteException;
+
+	DataImpl getAdaptedData(int[] indexes) throws VisADException, RemoteException;
+}
diff --git a/visad/data/hdf5/HDF5DatasetAdapted.java b/visad/data/hdf5/HDF5DatasetAdapted.java
new file mode 100644
index 0000000..f18f635
--- /dev/null
+++ b/visad/data/hdf5/HDF5DatasetAdapted.java
@@ -0,0 +1,397 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package visad.data.hdf5;
+
+import java.lang.reflect.Array;
+import java.awt.image.*;
+import java.rmi.RemoteException;
+import visad.data.hdf5.hdf5objects.*;
+import ncsa.hdf.hdf5lib.exceptions.HDF5Exception;
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.HDF5Constants;
+import visad.*;
+
+/**
+	HDF5DatasetAdapted is the implementation of mapping from HDFDataset to
+    VisAD Data object. HDF5 Dataset with compound data type is mapped to
+    VisAD FieldImpl and HDF5 Dataset with simple data type is mapped to
+    FlatField. The current implementation does not include compound datatype.
+    <P> 
+ */
+public class HDF5DatasetAdapted
+	extends HDF5Dataset
+	implements HDF5DataAdaptable
+{
+	MathType field_type;
+	RealTupleType domain, range;
+	DataImpl dataField;
+
+	/** Constructs a HDF5DatasetAdapted */
+	public HDF5DatasetAdapted()
+	{
+		super();
+	}
+
+	/** Creates a dataset at the specified location
+	 *  @param loc_id Identifier of the file or group to create the dataset within.
+	 *  @param set_name The name of the dataset to create.
+	 *  @param type_id Identifier of the datatype to use when creating the dataset.
+	 *  @param space_id Identifier of the dataspace to use when creating the dataset.
+	 *  @param create_plist_id Identifier of the set creation property list.
+	 */
+	public HDF5DatasetAdapted(int loc_id, String set_name, int type_id, int space_id,
+		int create_plist_id)
+	{
+		super(loc_id, set_name, type_id, space_id, create_plist_id);
+	}
+
+	/**
+	 * Opens a HDF5DatasetAdapted
+	 * @param loc_id A file, group, or datatype identifier.
+	 * @param set_name A datatset name.
+	 */
+	public HDF5DatasetAdapted (int loc_id, String set_name)
+	{
+		super(loc_id, set_name);
+	}
+
+	/** initialize the HDF5DatasetAdapte:
+	    <OL>
+			<LI> Set the domain.
+			<LI> Set the range.
+			<LI> Set function type.
+		</OL>
+	 */
+	public void init () throws HDF5Exception
+	{
+		super.init();
+
+		if (rank <= 0) return;
+
+		if (data == null)
+		{
+			try { data = readData(); }
+			catch (Exception e) { System.err.println(e); }
+		}
+
+
+		// check if the dataset is an image
+		boolean isImage = false;
+		try {
+			int pal_id = H5.H5Aopen_name(id, "PALETTE");
+			int pal_type = H5.H5Aget_type( pal_id );
+			int pal_class = H5.H5Tget_class( pal_type );
+			if (pal_class == HDF5Constants.H5T_REFERENCE) isImage = true;
+			H5.H5Tclose(pal_type);
+			H5.H5Aclose(pal_id);
+		} catch (Exception e) { 
+		}
+
+		if (data.getClass().getName().endsWith("Ljava.lang.String;")) {
+
+			mapToText();
+		} else if (isImage)
+		{
+			try { mapToImage(); }
+			catch (Exception e ) { throw new HDF5Exception(e.toString()); }
+		}
+		else {
+			mapToFlatField();
+		}
+	}
+
+	/** map the HDF5 dataset into an Image */
+	private void mapToImage()  throws HDF5Exception, VisADException
+	{
+
+		int w = (int)dims[0];
+		int h = (int)dims[1];
+
+		int aid = H5.H5Aopen_name(id, "PALETTE");
+		byte [] ref_buf = new byte[8];
+		int atype = H5.H5Aget_type(aid);
+		H5.H5Aread( aid, atype, ref_buf);
+		H5.H5Tclose(atype);
+ 		int pal_id =  H5.H5Rdereference(id, HDF5Constants.H5R_OBJECT, ref_buf);
+		HDF5Datatype pal_datatype = new HDF5Datatype();
+		pal_datatype.setID(H5.H5Dget_type(pal_id));
+
+		HDF5Dataspace pal_dataspace = new HDF5Dataspace();
+		pal_dataspace.setID(H5.H5Dget_space(pal_id));
+
+		// load the image palette
+		byte[] palette = new byte[3*256];
+		H5.H5Dread(pal_id,
+			H5.H5Dget_type(pal_id),
+			HDF5Constants.H5S_ALL,
+			HDF5Constants.H5S_ALL,
+			HDF5Constants.H5P_DEFAULT,
+			palette);
+
+		// red, green and blue
+		byte[] r   = new byte[256];
+		byte[] g = new byte[256];
+		byte[] b  = new byte[256];
+
+		for (int i = 0; i < 256; i++)
+		{
+			r[i] = palette[3*i];
+			g[i] = palette[3*i+1];
+			b[i] = palette[3*i+2];
+		}
+
+		IndexColorModel icm = new IndexColorModel (8, 256, r, g, b,0);
+
+		int num_pixels = w*h;
+		int pixel_val = -1;
+		float[][] pixel_rgb = new float[3][num_pixels];
+		for (int i=0; i<num_pixels; i++)
+		{
+			pixel_val =  Integer.parseInt(Array.get(data,i).toString());
+			pixel_rgb[0][i] = (float)icm.getRed(pixel_val);
+			pixel_rgb[1][i] = (float)icm.getGreen(pixel_val);
+			pixel_rgb[2][i] = (float)icm.getBlue(pixel_val);
+		}
+
+		// construct FlatField for the image data
+
+		RealType line, element, c_red, c_green, c_blue;
+
+                line = RealType.getRealType("ImageLine");
+                element = RealType.getRealType("ImageElement");
+                c_red = RealType.getRealType("Red");
+                c_green = RealType.getRealType("Green");
+                c_blue = RealType.getRealType("Blue");
+
+		RealType[] c_all = {c_red, c_green, c_blue};
+		RealTupleType radiance = new RealTupleType(c_all);
+
+		RealType[] domain_components = {element, line};
+		RealTupleType image_domain = new RealTupleType(domain_components);
+		Linear2DSet domain_set = new Linear2DSet(image_domain,
+			0.0, (float) (w - 1.0), w, (float) (h - 1.0), 0.0, h);
+		FunctionType image_type = new FunctionType(image_domain, radiance);
+
+		FlatField image_field = new FlatField(image_type, domain_set);
+		try { image_field.setSamples(pixel_rgb, false); }
+		catch (RemoteException e) { throw new HDF5Exception("setSamples for image failed."); }
+
+		field_type = image_type;
+		domain = image_domain;
+		range = radiance;
+		dataField = image_field;
+
+		H5.H5Aclose(aid);
+	}
+
+	/** map the HDF5 String to ViasAD Text */
+	private void mapToText()  throws HDF5Exception
+	{
+		String text = "";
+		if (data.getClass().isArray())
+		{
+			int no_lines = Array.getLength(data);
+			for (int i=0; i<no_lines; i++)
+			{
+				text += "\n"+Array.get(data, i).toString();
+			}
+		}
+		else
+			text = data.toString();
+
+		TextType tt = null;
+
+		String tname = (new String(shortName)).replace('-', '_');
+		try {
+			tt = new TextType( tname );
+		}
+		catch (TypeException e) {
+			tt = (TextType)TextType.getScalarTypeByName(tname);
+		}
+	   	catch (VisADException e) {
+			throw new HDF5Exception(e.toString());
+		}
+
+		try {
+			dataField = new Text(tt, text);
+			field_type = dataField.getType();
+		}  catch (VisADException e) {{ throw new HDF5Exception(e.toString());}}
+	}
+
+	/** map HDF dataset to FlatField */
+	private void mapToFlatField() throws HDF5Exception
+	{
+		// make the domain for the dataset
+		try
+		{
+			RealType dimension_types[] = new RealType[rank];
+			String dname = "";
+
+			for (int i=0; i<rank; i++)
+			{
+				dname = "dim"+String.valueOf(i);
+                                dimension_types[i] = RealType.getRealType(dname);
+			}
+			domain = new RealTupleType(dimension_types);
+		} catch (VisADException e)
+		{
+			throw new HDF5Exception("Constructing the domain of HDF5DatasetAdapted failed. "+e);
+		}
+
+		// make the range for the data set
+		try
+		{
+			RealType[] range_types = null;
+			String rname = "";
+
+			// compound datatype
+			if (data instanceof java.util.Vector)
+			{
+				int num_members = member_names.size();
+				range_types = new RealType[num_members];
+				for (int i=0; i<num_members; i++)
+				{
+					rname = (String)member_names.elementAt(i);
+                                        range_types[i] = RealType.getRealType(rname);
+				}
+			}
+			else
+			{
+				range_types = new RealType[1];
+				rname = (new String(shortName)).replace('-', '_');
+                                range_types[0] = RealType.getRealType(rname);
+			}
+			range = new RealTupleType(range_types);
+		} catch (VisADException e)
+		{
+			throw new HDF5Exception("Constructing the range of HDF5DatasetAdapted failed. "+e);
+		}
+
+		// set the function type
+		try
+		{
+			field_type = new FunctionType(domain, range);
+ 		} catch (VisADException e)
+		{
+			throw new HDF5Exception("Constructing the field_type of HDF5DatasetAdapted failed. "+e);
+		}
+
+		// set the data field
+		try { dataField = defineDataField(); }
+		catch (Exception e)
+		{
+			throw new HDF5Exception("Constructing the data field of HDF5DatasetAdapted failed. "+e);
+		}
+	}
+
+	private FieldImpl defineDataField()
+	throws HDF5Exception, VisADException, RemoteException
+	{
+		FlatField ff = null;
+		boolean isSupportedType = false;
+		IntegerNDSet domain_set = null;
+		int data_type = datatype.get_class();
+
+		isSupportedType = (data_type == HDF5Constants.H5T_INTEGER ||
+			  data_type == HDF5Constants.H5T_FLOAT ||
+			  (data_type == HDF5Constants.H5T_COMPOUND && member_names.size()>0));
+
+		if ( isSupportedType)
+		{
+			int[] d = new int[rank];
+			for (int i=0; i<rank; i++) d[i] = (int)dims[i];
+			domain_set = new IntegerNDSet(domain, d);
+			ff = new FlatField((FunctionType)field_type, domain_set);
+		}
+		else
+		{
+			throw new HDF5AdapterException("constructing data field.");
+		}
+
+		if (data != null)
+		{
+			int number_of_range_components = 0;
+			int number_of_range_samples = 0;
+
+			if (data_type == HDF5Constants.H5T_COMPOUND)
+			{
+				java.util.Vector cdata = (java.util.Vector)data;
+				number_of_range_components = member_names.size();
+				number_of_range_samples = Array.getLength(cdata.elementAt(0));
+			}
+			else
+			{
+				number_of_range_components = 1;
+				number_of_range_samples = Array.getLength(data);
+			}
+
+			float[][] theRange = new float[number_of_range_components][number_of_range_samples];
+			Object theData = null;
+			for (int i=0; i<number_of_range_samples; i++)
+			{
+				if (data_type == HDF5Constants.H5T_COMPOUND)
+				{
+					java.util.Vector cdata = (java.util.Vector)data;
+					for (int k=0; k<number_of_range_components; k++)
+					{
+						theData = cdata.elementAt(k);
+						theRange[k][i] = Float.parseFloat(Array.get(theData, i).toString());
+					}
+				}
+				else
+					theRange[0][i] = Float.parseFloat(Array.get(data, i).toString());
+			}
+			ff.setSamples(theRange, false);
+		}
+
+		return ff;
+	}
+
+	public MathType getMathType() throws VisADException
+	{
+		return field_type;
+	}
+
+	public DataImpl getAdaptedData() throws VisADException, RemoteException
+	{
+		return (DataImpl)dataField;
+	}
+
+	public DataImpl getAdaptedData(int[] indexes) throws VisADException, RemoteException
+	{
+		return (DataImpl)dataField;
+	}
+
+}
diff --git a/visad/data/hdf5/HDF5FileAdapted.java b/visad/data/hdf5/HDF5FileAdapted.java
new file mode 100644
index 0000000..05e6a81
--- /dev/null
+++ b/visad/data/hdf5/HDF5FileAdapted.java
@@ -0,0 +1,133 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package visad.data.hdf5;
+
+import visad.data.hdf5.hdf5objects.*;
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.HDF5Constants;
+import ncsa.hdf.hdf5lib.exceptions.HDF5Exception;
+
+public class HDF5FileAdapted extends HDF5File
+{
+	/** data objects in the file */
+	private java.util.Vector datas = null;
+
+	/** Creates a new HDF5 file.
+	 *  @param filename The name of the HDF5 file.
+	 *  @param flags File access flags.
+	 *  @param create_id File creation property list identifier.
+	 *  @param access_id File access property list identifier.
+	 */
+	public HDF5FileAdapted(String filename, int flags, int create_id, int access_id)
+	throws HDF5Exception
+	{
+		super(filename, flags, create_id, access_id);
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			throw new HDF5Exception("HDF5FileAdapted: "+e); }
+	}
+
+	/** Opens an existing HDF5 file.
+	 *  @param filename The name of the HDF5 file.
+	 *  @param flags File access flags.
+	 *  @param access_id File access property list identifier.
+	 */
+	public HDF5FileAdapted(String filename, int flags, int access_id)
+	throws HDF5Exception	{
+		super(filename, flags, access_id);
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			throw new HDF5Exception("HDF5FileAdapted: "+e); }
+	}
+
+	/** initialize the HDF5FileAdapted:
+		load the table of content of for the top level objects.
+	 */
+	public void init () throws HDF5Exception
+	{
+		if (id < 0) return;
+
+		datas = new java.util.Vector();
+
+		int nelems = H5.H5Gn_members(id, "/");
+		if (nelems <=0) return;
+
+		int pid = id;
+		String gname = "/";  // for the root only
+		int [] oType = {0};
+		HDF5GroupAdapted g;
+		HDF5DatasetAdapted d;
+
+		String [] oName = {" "};
+		for ( int i = 0; i < nelems; i++) {
+			H5.H5Gget_obj_info_idx(pid, gname, i, oName, oType );
+
+			if (oType[0] == HDF5Constants.H5G_GROUP) {
+				g = new HDF5GroupAdapted(pid,  "/"+oName[0]);
+				datas.add(g);
+			}
+			else if (oType[0] == HDF5Constants.H5G_DATASET) {
+				d = new HDF5DatasetAdapted(pid, "/"+oName[0]);
+				datas.add(d);
+			}
+			else {
+				// do not know what to do with other objects in visad
+			} // end of switch (oType[0])
+			oName[0] = null;
+			oType[0] = -1;
+		} // for ( i = 0; i < nelems; i++) {
+	}
+
+
+	/** Returns the number of the data objects in the file */
+	public int getObjectCount()
+	{
+		if (datas == null)
+			return -1;
+		else
+			return datas.size();
+	}
+
+	/** Returns the data object with specified index */
+	public Object getDataObject(int index)
+	{
+		if (datas == null)
+			return null;
+		else
+        	return datas.elementAt(index);
+	}
+}
diff --git a/visad/data/hdf5/HDF5Form.java b/visad/data/hdf5/HDF5Form.java
new file mode 100644
index 0000000..b4642ef
--- /dev/null
+++ b/visad/data/hdf5/HDF5Form.java
@@ -0,0 +1,421 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package visad.data.hdf5;
+
+import visad.*;
+import visad.data.*;
+import visad.UnimplementedException;
+import java.rmi.*;
+import java.net.URL;
+
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.HDF5Constants;
+import ncsa.hdf.hdf5lib.HDF5CDataTypes;
+import ncsa.hdf.hdf5lib.exceptions.HDF5Exception;
+
+/**
+	HDF5Form is a data form adapter for HDF5 files. 
+ */
+public class HDF5Form
+	extends Form
+	implements FormFileInformer
+{
+	public HDF5Form()
+	{
+		this( "HDF5 Data" );
+	}
+
+	public HDF5Form(String name )
+	{
+		super( name );
+	}
+
+	public boolean isThisType(String name)
+	{
+		boolean retVal = false;
+
+		try { retVal = H5.H5Fis_hdf5(name); }
+		catch (Exception ex) {;}
+
+		return retVal;
+	}
+
+	public boolean isThisType(byte[] block)
+	{
+		int firstByte = (new Byte(block[0]).intValue());
+		if (firstByte != 137)
+			return false;
+
+		String bytes2to4 = new String( block, 1, 4 );
+		return bytes2to4.startsWith("HDF");
+	}
+
+	public String[] getDefaultSuffixes()
+	{
+		String[] suffs = { "hdf", "h5"};
+		return suffs;
+	}
+
+	public FormNode getForms( Data data )
+	{
+		return this;
+	}
+
+	public DataImpl open( String file_path )
+		throws VisADException, RemoteException
+	{
+		HDF5FileAdapted file = null;
+
+		try {
+			file = new HDF5FileAdapted(
+				file_path,
+				HDF5Constants.H5F_ACC_RDONLY,
+				HDF5Constants.H5P_DEFAULT );
+		} catch (HDF5Exception e) {System.err.println(e); }
+
+		return getFileData( file );
+	}
+
+	public DataImpl open( URL url )
+		throws VisADException
+	{
+		throw new UnimplementedException( "HDF5Form.open( URL )" );
+	}
+
+	public void add( String id, Data data, boolean replace )
+		throws BadFormException
+	{
+		throw new BadFormException( "HDF5Form.add( String, Data, boolean )" );
+	}
+
+	public void save( String filename, Data data, boolean replace )
+		throws BadFormException, RemoteException, VisADException
+	{
+		//System.out.println("\n\nHDF5Form.save called.");
+
+		int fid=0, did=0, gid=0;
+
+		try {
+			fid = H5.H5Fcreate(filename,
+				HDF5Constants.H5F_ACC_TRUNC,
+				HDF5Constants.H5P_DEFAULT,
+				HDF5Constants.H5P_DEFAULT);
+		} catch (HDF5Exception e) {
+			throw new HDF5AdapterException(
+			"HDF5Form.save() failed: cannot create file "+filename);
+		} catch (NoClassDefFoundError e) {
+			throw new HDF5AdapterException(
+			"HDF5Form.save() failed: cannot create file "+filename);
+                }
+
+		try { save(fid, data, 0, 0); }
+		catch (BadFormException e) {
+			throw e;
+		}
+		catch (RemoteException e) {
+			throw e;
+		}
+		catch (VisADException e) {
+			throw e;
+		}
+		catch (HDF5Exception e) {
+			throw new HDF5AdapterException(e.toString());
+		}
+		finally {
+			try{H5.H5Fclose(fid);} catch (Exception e) {}
+		}
+	}
+
+	/** Save only tuple and field.
+		Tuple is mapped to HDF5 groups and Field is mapped to
+		HDF5 dataset. Only the first range value, i.e. value[][0]
+		is written to the output file. We don't know how to deal
+		with compound data.
+	 */
+	private void save( int pid, Data data, int level, int index)
+		throws BadFormException, RemoteException, VisADException, HDF5Exception
+	{
+		if (data instanceof Tuple)
+		{
+			int g_idx=0, new_pid=0;
+			Data d = null;
+			Tuple tuple = (Tuple)data;
+
+			String gname = "Group"+index+"at"+level;
+
+			if (level==0 )
+			{
+				new_pid=pid;
+                g_idx = -1;
+			}
+			else
+			{
+				new_pid = H5.H5Gcreate(pid, gname, -1);
+			}
+
+			int	n = tuple.getDimension();
+			for (int i = 0; i < n; i++)
+			{
+				d = tuple.getComponent(i);
+				//if (data instanceof Tuple) g_idx++;
+				save(new_pid, d, level+1, g_idx++);
+			}
+		}
+		else if (data instanceof Field)
+		{
+			Field field = (Field)data;
+			RealType[] rTypes = ((FunctionType) field.getType()).getRealComponents();
+
+			Set dset = field.getDomainSet();
+			if (!(dset instanceof GriddedSet) ||
+				rTypes == null)
+				return;
+
+			GriddedSet domain = (GriddedSet)dset;
+			RealType rangeType = (RealType) rTypes[0];
+
+			int sid=0, did=0, tid=0;
+			int l = domain.getLength();
+			int[] ddims = domain.getLengths();
+			int rank = ddims.length;
+			long[] dims = new long[rank];
+			for (int i=0; i<rank; i++)
+				dims[i] = ddims[i];
+			sid = H5.H5Screate_simple(rank, dims, null);
+
+			int number_of_range_components = 1;
+			if (field.isFlatField())
+				number_of_range_components = ((FlatField)field).getRangeDimension();
+			else
+				number_of_range_components = ((Unit[]) field.getDefaultRangeUnits()).length;
+			float[] rangeValues = new float[l];
+			float[][] rValue = field.getFloats(false);
+
+ 			if (number_of_range_components==1)
+			{
+				for (int i=0; i<l; i++)
+					rangeValues[i] = rValue[0][i];
+				try {
+					did = H5.H5Dcreate(pid, rangeType.getName(),
+						H5.J2C(HDF5CDataTypes.JH5T_NATIVE_FLOAT),
+						sid, HDF5Constants.H5P_DEFAULT);
+					H5.H5Dwrite(did,
+						H5.J2C(HDF5CDataTypes.JH5T_NATIVE_FLOAT),
+						HDF5Constants.H5S_ALL,
+						HDF5Constants.H5S_ALL,
+						HDF5Constants.H5P_DEFAULT,
+						rangeValues);
+				} finally {
+					H5.H5Dclose(did);
+					H5.H5Sclose(sid);
+				}
+			}
+			else // write compound data
+			{
+				float[][] fValue = new float[l][number_of_range_components];
+				for (int i=0; i<l; i++)
+				{
+					for (int j=0; j<number_of_range_components; j++)
+						fValue[i][j] = rValue[j][i];
+				}
+
+				try {
+					tid = H5.H5Tcreate(HDF5Constants.H5T_COMPOUND,number_of_range_components*4);
+					for (int j=0; j<number_of_range_components; j++)
+					{
+						rangeType = (RealType) rTypes[j];
+						H5.H5Tinsert(tid, rangeType.getName(), j*4, H5.J2C(HDF5CDataTypes.JH5T_NATIVE_FLOAT));
+					}
+					String dname = "Compound"+index+"at"+level;
+					did = H5.H5Dcreate(pid, dname, tid, sid, HDF5Constants.H5P_DEFAULT);
+					H5.H5Dwrite(did, tid, sid, HDF5Constants.H5S_ALL, HDF5Constants.H5P_DEFAULT,fValue);
+				} finally {
+					H5.H5Tclose(tid);
+					H5.H5Dclose(did);
+					H5.H5Sclose(sid);
+				}
+			}
+		}
+		else if (data instanceof Text)
+		{
+			Text text = (Text) data;
+			String text_value = text.getValue();
+			TextType tt = (TextType)text.getType();
+			String text_name = tt.getName();
+
+			int max_length=text_value.length();
+			long[] dims_str = {1};
+			int dataspace = H5.H5Screate_simple(1, dims_str, null);
+
+			int datatype = H5.H5Tcopy(H5.J2C(HDF5CDataTypes.JH5T_C_S1));
+			H5.H5Tset_size(datatype,max_length);
+			H5.H5Tset_strpad(datatype,HDF5Constants.H5T_STR_NULLPAD);
+			int dataset = H5.H5Dcreate(pid, text_name,
+				datatype, dataspace, HDF5Constants.H5P_DEFAULT);
+			byte [][] bnotes = new byte[1][max_length];
+				bnotes[0] = text_value.getBytes();
+			H5.H5Dwrite(dataset,
+				datatype,
+				HDF5Constants.H5S_ALL,
+				HDF5Constants.H5S_ALL,
+				HDF5Constants.H5P_DEFAULT,
+				bnotes);
+			H5.H5Dclose(dataset);
+			bnotes = null;
+		}
+	}
+
+	public MathType getMathType( HDF5FileAdapted file )
+		throws VisADException, RemoteException
+	{
+		MathType mathType = null;
+		HDF5DataAdaptable data = null;
+
+		int n_structs = file.getObjectCount();
+		if ( n_structs <= 0 )
+		{
+			throw new HDF5AdapterException("no data object in file: "+file.getName());
+		}
+
+		MathType[] types = new MathType[ n_structs ];
+
+		for ( int i = 0; i < n_structs; i++ )
+		{
+			Object obj = file.getDataObject(i);
+
+			if ( obj instanceof HDF5GroupAdapted )
+			{
+				data = (HDF5GroupAdapted)obj;
+			}
+			else if ( obj instanceof HDF5DatasetAdapted )
+			{
+				data = (HDF5DatasetAdapted)obj;
+			}
+
+			mathType = data.getMathType();
+			types[i] = mathType;
+		}
+
+		TupleType t_type = new TupleType( types );
+
+		return (MathType) t_type;
+	}
+
+	public DataImpl getFileData( HDF5FileAdapted file )
+		throws VisADException, RemoteException
+	{
+//System.out.println("HDF5Form.getFileData() called");
+		DataImpl data = null;
+		HDF5DataAdaptable h5Data = null;
+
+		int n_structs = file.getObjectCount();
+		if ( n_structs <= 0 )
+		{
+			throw new HDF5AdapterException("no data object in file: "+file.getName());
+		}
+
+		HDF5DataAdaptable[] datas = new HDF5DataAdaptable[ n_structs ];
+
+		// only deal with Groups and datasets
+		int ndatas=0;
+		for ( int i = 0; i < n_structs; i++ )
+		{
+			Object obj = file.getDataObject(i);
+
+			if ( obj instanceof HDF5GroupAdapted )
+			{
+				datas[ndatas++] = (HDF5GroupAdapted)obj;
+			}
+			else if ( obj instanceof HDF5DatasetAdapted )
+			{
+				datas[ndatas++] = (HDF5DatasetAdapted)obj;
+			}
+		}
+
+		return assembleStructs( datas );
+	}
+
+	private DataImpl assembleStructs( HDF5DataAdaptable[] h_datas )
+		throws VisADException, RemoteException
+	{
+//System.out.println("HDF5Form.assembleStructs() called");
+		DataImpl fileData = null;
+		int n_structs = h_datas.length;
+
+		if ( n_structs == 1 )
+			return getVisADDataObject( h_datas[0] );
+
+		boolean types_equal = true;
+		MathType first_type = null;
+		MathType[] types = new MathType[ n_structs ];
+		DataImpl[] datas = new DataImpl[ n_structs ];
+
+		datas[0] = getVisADDataObject( h_datas[0] );
+		types[0] = datas[0].getType();
+		first_type = types[0];
+
+		for ( int i = 1; i < n_structs; i++ )
+		{
+			datas[i] = getVisADDataObject( h_datas[i] );
+			types[i] = datas[i].getType();
+			types_equal = types[i].equals(first_type);
+		}
+
+		if ( types_equal )
+		{
+			RealType struct_id = RealType.getRealType("struct_id");
+			Integer1DSet domain = new Integer1DSet(struct_id, n_structs);
+			FunctionType fType = new FunctionType((MathType) struct_id, first_type);
+			FieldImpl field = new FieldImpl(fType, domain);
+
+			for ( int i = 0; i < n_structs; i++ )
+				field.setSample(i, datas[i]);
+
+			fileData = field;
+		} else {
+			TupleType t_type = new TupleType( types );
+			fileData = new Tuple( t_type, datas, false );
+		}
+
+		return fileData;
+	}
+
+	public DataImpl getVisADDataObject( HDF5DataAdaptable h_data )
+		throws VisADException, RemoteException
+	{
+		return h_data.getAdaptedData();
+	}
+
+}
diff --git a/visad/data/hdf5/HDF5GroupAdapted.java b/visad/data/hdf5/HDF5GroupAdapted.java
new file mode 100644
index 0000000..35b46c7
--- /dev/null
+++ b/visad/data/hdf5/HDF5GroupAdapted.java
@@ -0,0 +1,205 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+
+package visad.data.hdf5;
+
+import java.rmi.RemoteException;
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.HDF5Constants;
+import visad.data.hdf5.hdf5objects.HDF5Group;
+import ncsa.hdf.hdf5lib.exceptions.HDF5Exception;
+import visad.*;
+
+public class HDF5GroupAdapted
+	extends HDF5Group
+	implements HDF5DataAdaptable
+{
+	private MathType mathtype = null;
+	private DataImpl tuple = null;
+	private DataImpl[] datas = null;
+
+	/** Constructs an HDF5Group */
+	public HDF5GroupAdapted()
+	{
+		super();
+	}
+
+	/** Creates a new HDF5 Group.
+	 *  @param loc_id The file or group identifier.
+	 *  @param gname The absolute or relative name of the new group.
+	 *  @param name_length The maximum length of the name.
+	 */
+	public HDF5GroupAdapted(int loc_id, String gname, int name_length)
+	throws HDF5Exception
+	{
+		super(loc_id, gname, name_length);
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			throw new HDF5Exception("HDF5GroupAdapted: "+e); }
+	}
+
+	/** Opens an existing HDF5 Group.
+	 *  @param loc_id The file or group identifier..
+	 *  @param name The absolute or relative name of the new group..
+	 */
+	public HDF5GroupAdapted(int loc_id, String name)
+	throws HDF5Exception
+	{
+		super(loc_id, name);
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			throw new HDF5Exception("HDF5GroupAdapted: "+e); }
+	}
+
+	/** initailize the HDF5GroupAdapted: fill the members of the group */
+	public void init () throws HDF5Exception
+	{
+		if (id < 0) return;
+
+		int pid=id, pgroup=-1;
+		String gname = name;
+		int [] oType = {0};
+		HDF5GroupAdapted g;
+		HDF5DatasetAdapted d;
+
+		int nelems = H5.H5Gn_members(pid, gname);
+		if (nelems <=0) return;
+
+		String [] oName = {" "};
+		for ( int i = 0; i < nelems; i++) {
+			H5.H5Gget_obj_info_idx(pid, gname, i, oName, oType );
+
+			if (oType[0] == HDF5Constants.H5G_GROUP) {
+				pgroup = H5.H5Gopen(pid,gname);
+				g = new HDF5GroupAdapted(pgroup, name+"/"+oName[0]);
+				addMember(g);
+			}
+			else if (oType[0] == HDF5Constants.H5G_DATASET) {
+				d = new HDF5DatasetAdapted(pid, name+"/"+oName[0]);
+				addMember(d);
+			}
+			else {
+				// do not know what to do with other objects in visad
+			} // end of switch (oType[0])
+			oName[0] = null;
+			oType[0] = -1;
+		} // for ( i = 0; i < nelems; i++) {
+
+		try { getMathType(); }
+		catch (VisADException e) { throw new HDF5Exception("HDF5GroupAdapted: "+e); }
+	}
+
+	public MathType getMathType() throws VisADException
+	{
+		MathType mt = null;
+		HDF5DataAdaptable theMember = null;
+		int size = getMemberCount();
+		int new_size = 0;
+
+		if (size <= 0 ) return (mathtype = null);
+
+		if (mathtype == null)
+		{
+    		MathType[] m_types = new MathType[size];
+    		for ( int i = 0; i < size; i++ ) {
+				theMember = (HDF5DataAdaptable)getMemberAt(i);
+				mt = theMember.getMathType();
+				if (mt == null)
+					removeMember(theMember);
+				else
+      				m_types[new_size++] = mt;
+	    	}
+
+			new_size = getMemberCount();
+			MathType[] new_types = m_types;
+
+			if (new_size < size)
+			{
+				if (new_size <= 0 ) return (mathtype = null);
+    			new_types = new MathType[new_size];
+   				for ( int i = 0; i < new_size; i++ )
+					new_types[i] = m_types[i];
+			}
+
+    		mathtype = (MathType) new TupleType( new_types );
+		}
+
+		return mathtype;
+	}
+
+   	public DataImpl getAdaptedData() throws VisADException, RemoteException
+	{
+		int size = getMemberCount();
+
+		if (size <= 0) return null;
+
+		if (datas == null)
+			datas = new DataImpl[size];
+
+		HDF5DataAdaptable theData = null;
+		if ( tuple == null )
+		{
+			for ( int i = 0; i < size; i++ ) {
+				theData = (HDF5DataAdaptable)getMemberAt(i);
+				datas[i] = theData.getAdaptedData();
+			}
+			tuple = (DataImpl) new Tuple( (TupleType)mathtype, datas, false );
+		}
+
+		return tuple;
+	}
+
+	public DataImpl getAdaptedData( int[] indexes ) throws VisADException, RemoteException
+	{
+		int size = getMemberCount();
+
+		if (size <= 0) return null;
+
+		for ( int i = 0; i < size; i++ ) {
+			datas[i] = ((HDF5DataAdaptable)getMemberAt(i)).getAdaptedData( indexes );
+		}
+
+		Tuple tuple = new Tuple( (TupleType)mathtype, datas, false );
+
+		return tuple;
+	}
+
+	public HDF5DataAdaptable getElement( int i )
+	{
+		return (HDF5DataAdaptable)getMemberAt(i);
+	}
+
+}
diff --git a/visad/data/hdf5/hdf5objects/HDF5Attribute.java b/visad/data/hdf5/hdf5objects/HDF5Attribute.java
new file mode 100644
index 0000000..1fde625
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5Attribute.java
@@ -0,0 +1,181 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package visad.data.hdf5.hdf5objects;
+
+import ncsa.hdf.hdf5lib.*;
+import ncsa.hdf.hdf5lib.exceptions.*;
+
+/**
+ *  <p>
+ *  An HDF5 attribute is a small datasets attached to primary datasets as
+ *  metadata information. Because attributes are intended to be small objects,
+ *  large datasets intended as additional information for a primary dataset
+ *  should be stored as supplemental datasets in a group with the primary
+ *  dataset.
+ *  <P>
+ *  Attributes are not seperate objects in the file, they are always contained
+ *  in the object header of the object they are attached to.
+ *  <P>
+ *  For details of the HDF5 libraries, see the HDF5 Documentation at:
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/doc/">http://hdf.ncsa.uiuc.edu/HDF5/doc/</a>
+ */
+
+public class HDF5Attribute extends HDF5Dataset
+{
+	/** cosntruct an HDF5Attribute */
+	public HDF5Attribute() {
+		super ();
+		type = ATTRIBUTE;
+	}
+
+	/**
+	 * Creates a new HDF5Attribute
+	 * @param loc_id The identifier of the object the attribute is attached to.
+	 * @param attr_name Name of attribute to create.
+	 * @param type_id The identifier of datatype for attribute.
+	 * @param space_id The identifier of dataspace for attribute.
+	 * @param create_plist The identifier of creation property list.
+	 */
+	public HDF5Attribute (int loc_id, String attr_name, int type_id, int space_id,
+	int create_plist)
+	{
+		super(attr_name);
+
+		type = ATTRIBUTE;
+
+		try {
+			id = H5.H5Acreate(loc_id,  attr_name, type_id, space_id, create_plist);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Attribute: "+e);
+			id = -1;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			System.err.println("HDF5Attribute.init(): "+e);
+		}
+	}
+
+	/**
+	 * Opens an HDF5Attribute specified by its name
+	 * @param loc_id The identifier of the object the attribute is attached to.
+	 * @param attr_name Name of attribute to create.
+	 */
+	public HDF5Attribute (int loc_id, String attr_name)
+	{
+		super(attr_name);
+
+		type = ATTRIBUTE;
+
+		try {
+			id = H5.H5Aopen_name(loc_id,  attr_name);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Attribute: "+e);
+			id = -1;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			System.err.println("HDF5Attribute.init(): "+e);
+		}
+	}
+
+	/**
+	 * Opens an HDF5Attribute specified by its index.
+	 * @param loc_id The identifier of the object the attribute is attached to.
+	 * @param idx The index of the attribute to open.
+	 */
+	public HDF5Attribute (int loc_id, int idx)
+	{
+		super();
+
+		type = ATTRIBUTE;
+
+		try {
+			id = H5.H5Aopen_idx(loc_id,  idx);
+			String n[] = {""};
+			H5.H5Aget_name(id, 80, n);
+			name = n[0];
+		} catch (Exception e) {
+			System.err.println("HDF5Attribute: "+e);
+			id = -1;
+			name = null;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			System.err.println("HDF5Attribute.init(): "+e);
+		}
+	}
+
+	/** initialize the HDF5Attribute:
+	    <OL>
+			<LI> Set up datatype and dataspace.
+			<LI> Set up data ranks and dimensions.
+		</OL>
+	 */
+	public void init () throws HDF5Exception
+	{
+		if (id < 0) return;
+
+		datatype = new HDF5Datatype();
+		datatype.setID(H5.H5Aget_type(id));
+		datatype.init();
+
+		dataspace = new HDF5Dataspace();
+		dataspace.setID(H5.H5Aget_space(id));
+		dataspace.init();
+
+		rank = dataspace.getRank();
+		dims = dataspace.getDims();
+		maxdims = dataspace.getMaxdims();
+		count = dataspace.getCount();
+	}
+
+	/**
+	 * finalize() is called by the garbage collector on the object when garbage
+	 * collection determines that there are no more references to the object. It
+	 * is used to dispose of system resources or to perform other cleanup as C++
+	 * destructors
+	 */
+	protected void finalize() throws Throwable
+	{
+		try { super.finalize(); }
+		finally { H5.H5Aclose(id); }
+	}
+}
+
+
+
+
diff --git a/visad/data/hdf5/hdf5objects/HDF5Dataset.java b/visad/data/hdf5/hdf5objects/HDF5Dataset.java
new file mode 100644
index 0000000..d99731d
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5Dataset.java
@@ -0,0 +1,513 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package visad.data.hdf5.hdf5objects;
+
+import java.util.*;
+import java.lang.reflect.Array;
+import ncsa.hdf.hdf5lib.*;
+import ncsa.hdf.hdf5lib.exceptions.*;
+
+/**
+ *  <p>
+ *  This class provides a mechanism to describe properties of datasets and to
+ *  transfer data between memory and disk. A dataset is composed of a collection
+ *  of raw data points and four classes of meta data to describe the data points.
+ *  <p>
+ *  The four classes of meta data are: 
+ *  <pre>
+	Constant Meta Data
+     	Meta data that is created when the dataset is created and exists unchanged
+		for the life of the dataset. For instance, the data type of stored array
+		elements is defined when the dataset is created and cannot be subsequently
+		changed.
+	Persistent Meta Data
+		Meta data that is an integral and permanent part of a dataset but can
+		change over time. For instance, the size in any dimension can increase
+		over time if such an increase is allowed when the dataset was created.
+	Memory Meta Data
+		Meta data that exists to describe how raw data is organized in the
+		application's memory space. For instance, the data type of elements in
+		an application array might not be the same as the data type of those
+		elements as stored in the HDF5 file.
+	Transport Meta Data
+		Meta data that is used only during the transfer of raw data from one
+		location to another. For instance, the number of processes participating
+		in a collective I/O request or hints to the library to control caching
+		of raw data.
+ *  </pre>
+ *  For details of the HDF5 libraries, see the HDF5 Documentation at:
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/doc/">http://hdf.ncsa.uiuc.edu/HDF5/doc/</a>
+ */
+
+public class HDF5Dataset extends HDF5Object
+{
+	/** the data array */
+	protected Object data;
+
+	/** the datatype */
+	protected HDF5Datatype datatype;
+
+	/** the dataspace */
+	protected HDF5Dataspace dataspace;
+
+	/** the rank of the dataset */
+	protected int rank;
+
+	/** the dimensions of the dataset */
+	protected long[] dims;
+
+	/** the maximum dimensions of the dataset */
+	protected long[] maxdims;
+
+	/** the selected subset of the dataset */
+	protected long[] count;
+
+	/** a list of member names of compound data */
+	protected Vector member_names;
+
+
+	/** Constructs a HDF5Dataset */
+	public HDF5Dataset()
+	{
+		super();
+
+		type = DATASET;
+	}
+
+	/** Constructs a HDF5Dataset */
+	public HDF5Dataset(String name)
+	{
+		super(name);
+
+		type = DATASET;
+	}
+
+	/** Creates a dataset at the specified location
+	 *  @param loc_id Identifier of the file or group to create the dataset within.
+	 *  @param set_name The name of the dataset to create.
+	 *  @param type_id Identifier of the datatype to use when creating the dataset.
+	 *  @param space_id Identifier of the dataspace to use when creating the dataset.
+	 *  @param create_plist_id Identifier of the set creation property list.
+	 */
+	public HDF5Dataset(int loc_id, String set_name, int type_id, int space_id,
+		int create_plist_id)
+	{
+		super(set_name);
+
+		type = DATASET;
+
+		try {
+			id = H5.H5Dcreate(loc_id, set_name, type_id, space_id, create_plist_id);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Dataset: "+e);
+			id = -1;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) { System.err.println("HDF5Dataset: "+e); }
+	}
+
+	/**
+	 * Opens a HDF5Dataset
+	 * @param loc_id A file, group, or datatype identifier.
+	 * @param set_name A datatset name.
+	 */
+	public HDF5Dataset (int loc_id, String set_name)
+	{
+		super(set_name);
+
+		type = DATASET;
+
+		try {
+			id = H5.H5Dopen(loc_id, set_name);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Dataset: "+e);
+			id = -1;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) { System.err.println("HDF5Dataset.init(): "+e); }
+
+	}
+
+	/** initialize the HDF5Dataset:
+	    <OL>
+			<LI> open HDF5 library.
+			<LI> Set up datatype and dataspace.
+			<LI> Set up data ranks and dimensions.
+		</OL>
+	 */
+	public void init () throws HDF5Exception
+	{
+		if (id < 0) return;
+
+		datatype = new HDF5Datatype();
+		datatype.setID(H5.H5Dget_type(id));
+
+		dataspace = new HDF5Dataspace();
+		dataspace.setID(H5.H5Dget_space(id));
+
+		rank = dataspace.getRank();
+		dims = dataspace.getDims();
+		maxdims = dataspace.getMaxdims();
+		count = dataspace.getCount();
+	}
+
+	/** Read the entire dataset from file
+	 * @return the data array
+	 */
+	public Object readData() throws HDF5Exception, NullPointerException
+	{
+		int space = HDF5Constants.H5S_ALL;
+		return readData(space, space);
+	}
+
+	/** Read the data with specified memory and file space
+	 * <P>
+	 * @param mspace the memory space id
+	 * @param fspace the file space id
+	 * @return the data array
+	 */
+	public Object readData(int mspace, int fspace)
+		throws HDF5Exception, NullPointerException
+	{
+		// clean the old data
+		data = null;
+		System.gc();
+
+		if (H5.H5Tget_class(datatype.getID())==HDF5Constants.H5T_COMPOUND)
+		{
+			data = readCompoundData(mspace, fspace);
+			return data;
+		}
+
+		// read dataset
+		data = datatype.defineData(count);
+
+		if (data == null) return null;
+
+		// read strings
+		if (data.getClass().getName().endsWith("Ljava.lang.String;"))
+		{
+			int no_lines = Array.getLength(data);
+			long tsize = H5.H5Dget_storage_size(id);
+			int max_length = (int)(tsize/no_lines);
+			byte [][] bdata = new byte[no_lines][max_length];
+
+ 			H5.H5Dread(id,
+				H5.H5Dget_type(id),
+ 				mspace,
+ 				fspace,
+ 				HDF5Constants.H5P_DEFAULT,
+ 				bdata);
+			for (int i=0; i < no_lines; i++)
+			{
+				Array.set(data, i, (new String(bdata[i])));
+			}
+		}
+		else
+		{
+			H5.H5Dread(id,
+				H5.H5Dget_type(id),//datatype.getDatatype(),
+				mspace,
+				fspace,
+				HDF5Constants.H5P_DEFAULT,
+				data);
+
+			// convert unsigned data because Java does not support unsigned integers
+			boolean isUnsigned = false;
+			int tid = datatype.getID();
+			int class_t = H5.H5Tget_class(tid);
+			if ( class_t == HDF5Constants.H5T_INTEGER)
+			{
+        		if (H5.H5Tget_sign(tid)==HDF5Constants.H5T_SGN_NONE)
+				{
+					Object new_data = convertUnsignedData(data);
+					data = new_data;
+				}
+			}
+		}
+
+		return data;
+	}
+
+	/** readCompoundData only works for flat compound, i.e.
+		all structure members are primitive data type. It does
+		not work for that case that member structures are arrays
+		or compound data types
+	 */
+	private Object readCompoundData(int mspace, int fspace)
+		throws HDF5Exception, NullPointerException
+	{
+		//System.out.println("HDF5Dataset.readCompoundData called.");
+
+		String member_name = "";
+		int member_tid = -1;
+		int read_tid = -1;
+		int member_class_t = -1;
+		int member_class_s = -1;
+		int member_sign = -1;
+		int p = HDF5Constants.H5P_DEFAULT;
+		Object member_data = null;
+
+		int size = 0;
+		long lsize = 1;
+		if (count == null ) return null;
+		for (int i=0; i<count.length; i++) lsize *= count[i];
+		size = (int)lsize;
+
+		int tid = datatype.getID();
+		int num_members = H5.H5Tget_nmembers(tid);
+
+		Vector theData = new Vector();
+		member_names = new Vector();
+
+		for (int i=0; i<num_members; i++)
+		{
+			member_data = null;
+			member_sign = -1;
+			member_name = H5.H5Tget_member_name(tid, i);
+			member_tid = H5.H5Tget_member_type(tid, i);
+			member_class_t = H5.H5Tget_class(member_tid);
+			member_class_s = H5.H5Tget_size(member_tid);
+	   	read_tid = H5.H5Tcreate(HDF5Constants.H5T_COMPOUND,member_class_s);
+
+			if (member_class_t == HDF5Constants.H5T_INTEGER) {
+				member_sign = H5.H5Tget_sign(member_tid);
+	  		if (member_class_s == 1) {
+	  			byte[] bdata = new byte[size];
+					H5.H5Tinsert(read_tid, member_name, 0, H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT8));
+					H5.H5Dread(id, read_tid, mspace, fspace, p, bdata);
+					member_data = bdata;
+	  		}
+	  		else if (member_class_s == 2) {
+	  			short[] sdata = new short[size];
+					H5.H5Tinsert(read_tid, member_name, 0, H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT16));
+					H5.H5Dread(id, read_tid, mspace, fspace, p, sdata);
+					member_data = sdata;
+	  		}
+	  		else if (member_class_s == 4) {
+	  			int[] idata = new int[size];
+					H5.H5Tinsert(read_tid, member_name, 0, H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT32));
+					H5.H5Dread(id, read_tid, mspace, fspace, p, idata);
+					member_data = idata;
+	  		}
+	  		else if (member_class_s == 8) {
+	  			long[] ldata = new long[size];
+					H5.H5Tinsert(read_tid, member_name, 0, H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT64));
+					H5.H5Dread(id, read_tid, mspace, fspace, p, ldata);
+					member_data = ldata;
+	  		}
+			}
+			else if (member_class_t == HDF5Constants.H5T_FLOAT) {
+  			if (member_class_s == 4) {
+  				float[] fdata = new float[size];
+					H5.H5Tinsert(read_tid, member_name, 0, H5.J2C(HDF5CDataTypes.JH5T_NATIVE_FLOAT));
+					H5.H5Dread(id, read_tid, mspace, fspace, p, fdata);
+					member_data = fdata;
+  			}
+  			else if (member_class_s == 8) {
+  				double[] ddata = new double[size];
+					H5.H5Tinsert(read_tid, member_name, 0, H5.J2C(HDF5CDataTypes.JH5T_NATIVE_DOUBLE));
+					H5.H5Dread(id, read_tid, mspace, fspace, p, ddata);
+					member_data = ddata;
+  			}
+			}
+			else {
+				member_data = null;
+	  	} // end of switch (member_class_t)
+
+			if (member_data != null)
+			{
+				member_names.add(member_name);
+				if (member_sign == HDF5Constants.H5T_SGN_NONE)
+			  		theData.add(convertUnsignedData(member_data));
+			  	else
+		  			theData.add(member_data);
+			}
+
+  	} // end of for (int i=0; i<num_members; i++)
+
+		return theData;
+	}
+
+	/**
+	  convert unsigned data because Java does not support unsigned integers.
+	 */
+	public static Object convertUnsignedData(Object data_in)
+		throws HDF5Exception
+	{
+		Object data_out = null;
+		String cname = data_in.getClass().getName();
+		char dname = cname.charAt(cname.lastIndexOf("[")+1);
+		int size = Array.getLength(data_in);
+
+		if (dname == 'B') {
+			short[] sdata = new short[size];
+			short value = 0;
+			for (int i=0; i<size; i++)
+			{
+				value = (short)Array.getByte(data_in, i);
+				if (value < 0) value += 256;
+				sdata[i] = value;
+			}
+			data_out = sdata;
+			data_in = null;
+		}
+		else if (dname == 'S') {
+			int[] idata = new int[size];
+			int value = 0;
+			for (int i=0; i<size; i++)
+			{
+				value = (int)Array.getShort(data_in, i);
+				if (value < 0) value += 65536;
+				idata[i] = value;
+			}
+			data_out = idata;
+			data_in = null;
+		}
+		else if (dname == 'I') {
+			long[] ldata = new long[size];
+			long value = 0;
+			for (int i=0; i<size; i++)
+			{
+				value = (long)Array.getInt(data_in, i);
+				if (value < 0) value += 4294967296L;
+				ldata[i] = value;
+			}
+			data_out = ldata;
+			data_in = null;
+		}
+		else data_out = data_in;
+		// Java does not support unsigned long
+
+		return data_out;
+	}
+
+	/** write the entire dataset from file */
+	public void writeData(Object buf) throws HDF5Exception, NullPointerException
+	{
+		int space = HDF5Constants.H5S_ALL;
+		writeData(space, space, buf);
+	}
+
+	/** write the data with specified memory and file space */
+	public void writeData(int mspace, int fspace, Object buf)
+		throws HDF5Exception, NullPointerException
+	{
+		// write dataset
+		int num_type=-1, plist=HDF5Constants.H5P_DEFAULT;
+		String cname = buf.getClass().getName();
+		char dname = cname.charAt(cname.lastIndexOf("[")+1);
+
+		if (dname == 'B')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT8);
+		else if (dname == 'C')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_CHAR);
+		else if (dname == 'D')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_DOUBLE);
+		else if (dname == 'F')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_FLOAT);
+		else if (dname == 'I')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT32);
+		else if (dname == 'J')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT64);
+		else if (dname == 'S')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_INT16);
+		else if (dname == 'Z')
+			num_type = H5.J2C(HDF5CDataTypes.JH5T_NATIVE_HBOOL);
+		else
+			num_type = H5.H5Dget_type(id);
+
+		int status = H5.H5Dwrite(id, num_type, mspace, fspace,
+			plist, buf);
+	}
+
+	/** Returns the data array */
+	public Object getData() { return data; }
+
+	/** Returns the datatype */
+	public HDF5Datatype getDatatype() { return datatype; }
+
+	/** Returns the dataspace */
+	public HDF5Dataspace getDataspace() { return dataspace; }
+
+	/** Returns the rank of the dataset */
+	public int getRank() { return rank; }
+
+	/** Returns the dimensions of the dataset */
+	public long[] getDims() { return dims; }
+
+	/** Returns the maximum dimensions of the dataspace */
+	public long[] getMaxdims() { return maxdims; }
+
+	/** Returns the selected counts of the data */
+	public long[] getCount() { return count; }
+
+	/**
+
+	 * Converts this object to a String representation.
+	 * @return a string representation of this object
+	 */
+	public String toString() {
+		if (datatype==null || dataspace==null)
+			return super.toString();
+
+		String d_str="";
+		for (int i=0; i<rank; i++)
+			d_str += dims[i]+"x";
+		int l = d_str.length();
+		if (l > 1) d_str = d_str.substring(0, l-1);
+
+		return getClass().getName() +
+			"[name=" + name +
+			",type=" + datatype+
+			",dimensions=" + d_str + "]";
+	}
+
+	/**
+	 * finalize() is called by the garbage collector on the object when garbage
+	 * collection determines that there are no more references to the object. It
+	 * is used to dispose of system resources or to perform other cleanup as C++
+	 * destructors
+	 */
+	protected void finalize() throws Throwable
+	{
+		try { super.finalize(); }
+		finally { H5.H5Dclose(id); }
+	}
+
+}
+
+
diff --git a/visad/data/hdf5/hdf5objects/HDF5Dataspace.java b/visad/data/hdf5/hdf5objects/HDF5Dataspace.java
new file mode 100644
index 0000000..eedc8cc
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5Dataspace.java
@@ -0,0 +1,228 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package visad.data.hdf5.hdf5objects;
+
+import ncsa.hdf.hdf5lib.*;
+import ncsa.hdf.hdf5lib.exceptions.*;
+
+/**
+ *  <p>
+ *  This class provides provides a mechanism to describe the positions of the
+ *  elements of a dataset.
+ *  <p>
+ *  A dataspace describes the locations that dataset elements are located at.
+ *  A dataspace is either a regular N-dimensional array of data points, called
+ *  a simple dataspace, or a more general collection of data points organized
+ *  in another manner, called a complex dataspace.
+ *  <P>
+ *  For details of the HDF5 libraries, see the HDF5 Documentation at:
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/doc/">http://hdf.ncsa.uiuc.edu/HDF5/doc/</a>
+ */
+
+public class HDF5Dataspace extends HDF5Object
+{
+	/** the rank of the dataset */
+	private int rank;
+
+	/** the dimensions of the dataset */
+	private long[] dims;
+
+	/** the maximum dimensions of the dataset */
+	private long[] maxdims;
+
+	/** the starting position of the selected subset */
+	private long[] start;
+
+	/** the stride of the selected subset */
+	private long[] stride;
+
+	/** the selected subset of the dataset */
+	private long[] count;
+
+	/** Constructs an HDF5Dataspace*/
+	public HDF5Dataspace ()
+	{
+		super();
+
+		type = DATASPACE;
+	}
+
+	/**
+	 * Creates a new HDF5Dataspace
+	 * @param space_type The type of dataspace to be created.
+	 */
+	public HDF5Dataspace (int space_type)
+	{
+		super();
+
+		type = DATASPACE;
+
+		try {
+			id = H5.H5Screate(space_type);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Dataspace: "+e);
+			id = -1;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			System.err.println("HDF5Dataspace.init(): "+e);
+		}
+	}
+
+	/**
+	 * Creates a new HDF5Dataspace
+	 * @param rank Number of dimensions of dataspace.
+	 * @param dims An array of the size of each dimension.
+	 * @param maxdims An array of the maximum size of each dimension.
+	 */
+	public HDF5Dataspace (int rank, long[] dims, long[] maxdims)
+	{
+		super();
+
+		type = DATASPACE;
+
+		try {
+			id = H5.H5Screate_simple(rank, dims, maxdims);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Dataspace: "+e);
+			id = -1;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			System.err.println("HDF5Dataspace.init(): "+e);
+		}
+	}
+
+
+	/** initialize the HDF5Dataspace:
+	    <OL>
+			<LI> open HDF5 library.
+			<LI> Set up data ranks and dimensions.
+			<LI> Set up start, stride and count.
+		</OL>
+	 */
+	public void init () throws HDF5Exception
+	{
+		if (id < 0) return;
+
+		rank = H5.H5Sget_simple_extent_ndims(id);
+		dims = new long[rank];
+		maxdims = new long[rank];
+		start = new long[rank];
+		stride = new long[rank];
+		count = new long[rank];
+
+		for (int i=0; i<rank; i++) {
+			start[i] = 0;
+			stride[i] = 1;
+		}
+
+		int status = H5.H5Sget_simple_extent_dims(id, dims, maxdims);
+		if (status < 0) return;
+
+		for (int i=0; i<rank; i++)
+			count[i] = dims[i];
+	}
+
+	/** select count[] points starting at start[] with stride[]
+	 *  @param start the starting points.
+	 *  @param stride the stride
+	 *  @param count the number of points.
+	 */
+	public void select(long[] start, long[] stride, long[] count)
+	throws HDF5LibraryException, NullPointerException, IllegalArgumentException
+	{
+		this.start = start;
+		this.stride = stride;
+		this.count = count;
+
+		long[] block = new long[rank];
+		for (int i=0; i<rank; i++)
+			block[i] = 1;
+
+		try {
+			H5.H5Sselect_hyperslab(id, HDF5Constants.H5S_SELECT_SET, start,stride, count, block);
+		}
+		catch (HDF5Exception exc) {
+			HDF5LibraryException e = new HDF5LibraryException();
+			e.initCause(exc);
+			throw e;
+		}
+	}
+
+
+	/** Returns the rank of the dataspace */
+	public int getRank() { return rank; }
+
+	/** Returns the dimensions of the dataspace */
+	public long[] getDims() { return dims; }
+
+	/** Returns the maximum dimensions of the dataspace */
+	public long[] getMaxdims() { return maxdims; }
+
+	/** Returns the selected counts of the data */
+	public long[] getCount() { return count; }
+
+	/**
+	 * Converts this object to a String representation.
+	 * @return a string representation of this object
+	 */
+	public synchronized String toString() {
+		String d_str="";
+
+		for (int i=0; i<rank; i++)
+			d_str += dims[i]+"x";
+
+		int l = d_str.length();
+		if (l > 1) d_str = d_str.substring(0, l-1);
+
+		return getClass().getName() + "[rank=" + rank + ",dimensions=" + d_str + "]";
+	}
+
+	/**
+	 * finalize() is called by the garbage collector on the object when garbage
+	 * collection determines that there are no more references to the object. It
+	 * is used to dispose of system resources or to perform other cleanup as C++
+	 * destructors
+	 */
+	protected void finalize() throws Throwable
+	{
+		try { super.finalize(); }
+		finally { H5.H5Sclose(id); }
+	}
+}
+
+
diff --git a/visad/data/hdf5/hdf5objects/HDF5Datatype.java b/visad/data/hdf5/hdf5objects/HDF5Datatype.java
new file mode 100644
index 0000000..9b44de2
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5Datatype.java
@@ -0,0 +1,362 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package visad.data.hdf5.hdf5objects;
+
+import ncsa.hdf.hdf5lib.*;
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.exceptions.*;
+
+/**
+ *  <p>
+ *  This class provides a mechanism to describe the storage format of individual
+ *  data points of a data set.
+ *  <p>
+ *  A data type is a collection of data type properties, all of which can be
+ *  stored on disk, and which when taken as a whole, provide complete information
+ *  for data conversion to or from that data type. The interface provides
+ *  functions to set and query properties of a data type.
+ *  <P>
+ *  A data point is an instance of a data type, which is an instance of a type
+ *  class. We have defined a set of type classes and properties which can be
+ *  extended at a later time. The atomic type classes are those which describe
+ *  types which cannot be decomposed at the data type interface level; all other
+ *  classes are compound.
+ *  <P>
+ *  For details of the HDF5 libraries, see the HDF5 Documentation at:
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/doc/">http://hdf.ncsa.uiuc.edu/HDF5/doc/</a>
+ */
+
+public class HDF5Datatype extends HDF5Object
+{
+
+	private static final long serialVersionUID = 1L;
+
+	/** Construct an HDF5Datatype*/
+	public HDF5Datatype ()
+	{
+		super();
+
+		type = DATATYPE;
+	}
+
+	/**
+	 * Creates a new HDF5Datatype
+	 * @param datatype_class Class of datatype to create.
+	 * @param size The number of bytes in the datatype to create.
+	 */
+	public HDF5Datatype (int datatype_class, int size)
+	{
+		super();
+
+		type = DATATYPE;
+
+		try {
+			id = H5.H5Tcreate(datatype_class, size);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Datatype: "+e);
+			id = -1;
+		}
+
+		try { init(); }
+		catch (HDF5Exception e) {
+			System.err.println("HDF5Datatype.init(): "+e);
+		}
+	}
+
+	/**
+	 * Opens a named HDF5Datatype
+	 * @param loc_id A file, group, or datatype identifier.
+	 * @param type_name A datatype name.
+	 */
+	public HDF5Datatype (int loc_id, String type_name)
+	{
+		super(type_name);
+
+		type = DATATYPE;
+
+		try {
+			id = H5.H5Topen(loc_id, name);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Datatype: "+e);
+			id = -1;
+		}
+
+	}
+
+	/**
+
+	 *  H5Tcommit commits a transient datatype to a file, turned it
+	 *  into a named datatype.
+	 *
+	 *  @param loc_id A file or group identifier.
+	 *  @param type_name A datatype name.
+	 *  @return a non-negative value if successful; otherwise returns a negative value.
+	**/
+	public int H5Tcommit(int loc_id, String type_name)
+		throws HDF5LibraryException, NullPointerException
+	{
+		name = type_name;
+		return H5.H5Tcommit(loc_id, type_name, id);
+	}
+
+
+	/** Returns the datatype class identifier */
+	public int get_class()
+		throws HDF5LibraryException
+	{
+		return H5.H5Tget_class(id);
+	}
+
+	/** Returns  the size of a datatype in bytes*/
+	public int get_size()
+		throws HDF5LibraryException
+	{
+		return H5.H5Tget_size(id);
+	}
+
+	/** define the data with specified data type.
+	 *  The maximum selected data size is limited to
+	 *  Integer.MAX_VALUE, which is 2,147,483,647 bytes
+	 *
+	 *  @param count the number of points of data
+	 */
+	public Object defineData(long[] count) throws HDF5Exception
+	{
+		Object data = null;
+		int size = 0;
+		long lsize = 1;
+
+		if (count == null ) return null;
+
+		for (int i=0; i<count.length; i++)
+		{
+			lsize *= count[i];
+			if (lsize > Integer.MAX_VALUE)
+				throw (new OutOfMemoryError("the size of data array > "+Integer.MAX_VALUE));
+		}
+
+		size = (int)lsize;
+
+		// data type information
+		int class_t = H5.H5Tget_class(id);
+		int class_s = H5.H5Tget_size(id);
+
+		if (class_t == HDF5Constants.H5T_INTEGER) {
+			if (class_s == 1) {
+				data = new byte[size];
+			}
+			else if (class_s == 2) {
+				data = new short[size];
+			}
+			else if (class_s == 4) {
+				data = new int[size];
+			}
+			else if (class_s == 8) {
+				data = new long[size];
+			}
+		}
+		else if (class_t == HDF5Constants.H5T_FLOAT) {
+			if (class_s == 4) {
+				data = new float[size];
+			}
+			else if (class_s == 8) {
+				data = new double[size];
+			}
+		}
+		else if (class_t == HDF5Constants.H5T_STRING) {
+			data = new String[size];
+		}
+		else if (class_t == HDF5Constants.H5T_COMPOUND) {
+		} // end of switch (class_t)
+
+		return data;
+	}
+
+	/** Gets the string representation of the data type
+	 *  @param data_type the type of the data.
+	 *  @return the string of the data type
+	 */
+	public static String getDatatype(int data_type)
+	{
+		if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_B16 ) ) return "HDF5CDataTypes.JH5T_ALPHA_B16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_B32 ) ) return "HDF5CDataTypes.JH5T_ALPHA_B32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_B64 ) ) return "HDF5CDataTypes.JH5T_ALPHA_B64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_B8 ) ) return "HDF5CDataTypes.JH5T_ALPHA_B8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_F32 ) ) return "HDF5CDataTypes.JH5T_ALPHA_F32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_F64 ) ) return "HDF5CDataTypes.JH5T_ALPHA_F64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_I16 ) ) return "HDF5CDataTypes.JH5T_ALPHA_I16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_I32 ) ) return "HDF5CDataTypes.JH5T_ALPHA_I32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_I64 ) ) return "HDF5CDataTypes.JH5T_ALPHA_I64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_I8 ) ) return "HDF5CDataTypes.JH5T_ALPHA_I8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_U16 ) ) return "HDF5CDataTypes.JH5T_ALPHA_U16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_U32 ) ) return "HDF5CDataTypes.JH5T_ALPHA_U32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_U64 ) ) return "HDF5CDataTypes.JH5T_ALPHA_U64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_ALPHA_U8 ) ) return "HDF5CDataTypes.JH5T_ALPHA_U8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_C_S1 ) ) return "HDF5CDataTypes.JH5T_C_S1";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_FORTRAN_S1 ) ) return "HDF5CDataTypes.JH5T_FORTRAN_S1";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_IEEE_F32BE ) ) return "HDF5CDataTypes.JH5T_IEEE_F32BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_IEEE_F32LE ) ) return "HDF5CDataTypes.JH5T_IEEE_F32LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_IEEE_F64BE ) ) return "HDF5CDataTypes.JH5T_IEEE_F64BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_IEEE_F64LE ) ) return "HDF5CDataTypes.JH5T_IEEE_F64LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_B16 ) ) return "HDF5CDataTypes.JH5T_INTEL_B16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_B32 ) ) return "HDF5CDataTypes.JH5T_INTEL_B32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_B64 ) ) return "HDF5CDataTypes.JH5T_INTEL_B64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_B8 ) ) return "HDF5CDataTypes.JH5T_INTEL_B8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_F32 ) ) return "HDF5CDataTypes.JH5T_INTEL_F32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_F64 ) ) return "HDF5CDataTypes.JH5T_INTEL_F64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_I16 ) ) return "HDF5CDataTypes.JH5T_INTEL_I16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_I32 ) ) return "HDF5CDataTypes.JH5T_INTEL_I32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_I64 ) ) return "HDF5CDataTypes.JH5T_INTEL_I64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_I8 ) ) return "HDF5CDataTypes.JH5T_INTEL_I8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_U16 ) ) return "HDF5CDataTypes.JH5T_INTEL_U16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_U32 ) ) return "HDF5CDataTypes.JH5T_INTEL_U32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_U64 ) ) return "HDF5CDataTypes.JH5T_INTEL_U64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_INTEL_U8 ) ) return "HDF5CDataTypes.JH5T_INTEL_U8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_B16 ) ) return "HDF5CDataTypes.JH5T_MIPS_B16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_B32 ) ) return "HDF5CDataTypes.JH5T_MIPS_B32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_B64 ) ) return "HDF5CDataTypes.JH5T_MIPS_B64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_B8 ) ) return "HDF5CDataTypes.JH5T_MIPS_B8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_F32 ) ) return "HDF5CDataTypes.JH5T_MIPS_F32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_F64 ) ) return "HDF5CDataTypes.JH5T_MIPS_F64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_I16 ) ) return "HDF5CDataTypes.JH5T_MIPS_I16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_I32 ) ) return "HDF5CDataTypes.JH5T_MIPS_I32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_I64 ) ) return "HDF5CDataTypes.JH5T_MIPS_I64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_I8 ) ) return "HDF5CDataTypes.JH5T_MIPS_I8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_U16 ) ) return "HDF5CDataTypes.JH5T_MIPS_U16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_U32 ) ) return "HDF5CDataTypes.JH5T_MIPS_U32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_U64  ) ) return "HDF5CDataTypes.JH5T_MIPS_U64 ";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_MIPS_U8 ) ) return "HDF5CDataTypes.JH5T_MIPS_U8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_B16 ) ) return "HDF5CDataTypes.JH5T_NATIVE_B16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_B32 ) ) return "HDF5CDataTypes.JH5T_NATIVE_B32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_B64 ) ) return "HDF5CDataTypes.JH5T_NATIVE_B64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_B8 ) ) return "HDF5CDataTypes.JH5T_NATIVE_B8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_CHAR ) ) return "HDF5CDataTypes.JH5T_NATIVE_CHAR";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_DOUBLE ) ) return "HDF5CDataTypes.JH5T_NATIVE_DOUBLE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_FLOAT ) ) return "HDF5CDataTypes.JH5T_NATIVE_FLOAT";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_HBOOL ) ) return "HDF5CDataTypes.JH5T_NATIVE_HBOOL";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_HERR ) ) return "HDF5CDataTypes.JH5T_NATIVE_HERR";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_HSIZE ) ) return "HDF5CDataTypes.JH5T_NATIVE_HSIZE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_HSSIZE ) ) return "HDF5CDataTypes.JH5T_NATIVE_HSSIZE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_FAST16 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_FAST16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_FAST32 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_FAST32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_FAST64 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_FAST64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_FAST8 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_FAST8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_LEAST16 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_LEAST16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_LEAST32 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_LEAST32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_LEAST64 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_LEAST64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT_LEAST8 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT_LEAST8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT16 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT32 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT64 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_INT8 ) ) return "HDF5CDataTypes.JH5T_NATIVE_INT8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_LDOUBLE ) ) return "HDF5CDataTypes.JH5T_NATIVE_LDOUBLE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_LLONG ) ) return "HDF5CDataTypes.JH5T_NATIVE_LLONG";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_LONG ) ) return "HDF5CDataTypes.JH5T_NATIVE_LONG";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_OPAQUE ) ) return "HDF5CDataTypes.JH5T_NATIVE_OPAQUE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_SCHAR ) ) return "HDF5CDataTypes.JH5T_NATIVE_SCHAR";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_SHORT ) ) return "HDF5CDataTypes.JH5T_NATIVE_SHORT";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UCHAR ) ) return "HDF5CDataTypes.JH5T_NATIVE_UCHAR";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_FAST16 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_FAST16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_FAST32 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_FAST32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_FAST64 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_FAST64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_FAST8 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_FAST8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST16 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST32 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST64 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST64";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST8 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT_LEAST8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT16 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT16";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT32 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT32";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT64  ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT64 ";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_UINT8 ) ) return "HDF5CDataTypes.JH5T_NATIVE_UINT8";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_ULLONG ) ) return "HDF5CDataTypes.JH5T_NATIVE_ULLONG";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_ULONG ) ) return "HDF5CDataTypes.JH5T_NATIVE_ULONG";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NATIVE_USHORT ) ) return "HDF5CDataTypes.JH5T_NATIVE_USHORT";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NCSET ) ) return "HDF5CDataTypes.JH5T_NCSET";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_NSTR ) ) return "HDF5CDataTypes.JH5T_NSTR";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B16BE ) ) return "HDF5CDataTypes.JH5T_STD_B16BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B16LE ) ) return "HDF5CDataTypes.JH5T_STD_B16LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B32BE ) ) return "HDF5CDataTypes.JH5T_STD_B32BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B32LE ) ) return "HDF5CDataTypes.JH5T_STD_B32LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B64BE ) ) return "HDF5CDataTypes.JH5T_STD_B64BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B64LE ) ) return "HDF5CDataTypes.JH5T_STD_B64LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B8BE ) ) return "HDF5CDataTypes.JH5T_STD_B8BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_B8LE ) ) return "HDF5CDataTypes.JH5T_STD_B8LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I16BE ) ) return "HDF5CDataTypes.JH5T_STD_I16BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I16LE ) ) return "HDF5CDataTypes.JH5T_STD_I16LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I32BE ) ) return "HDF5CDataTypes.JH5T_STD_I32BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I32LE ) ) return "HDF5CDataTypes.JH5T_STD_I32LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I64BE ) ) return "HDF5CDataTypes.JH5T_STD_I64BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I64LE ) ) return "HDF5CDataTypes.JH5T_STD_I64LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I8BE ) ) return "HDF5CDataTypes.JH5T_STD_I8BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_I8LE ) ) return "HDF5CDataTypes.JH5T_STD_I8LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_REF_DSETREG ) ) return "HDF5CDataTypes.JH5T_STD_REF_DSETREG";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_REF_OBJ ) ) return "HDF5CDataTypes.JH5T_STD_REF_OBJ";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U16BE ) ) return "HDF5CDataTypes.JH5T_STD_U16BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U16LE ) ) return "HDF5CDataTypes.JH5T_STD_U16LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U32BE ) ) return "HDF5CDataTypes.JH5T_STD_U32BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U32LE ) ) return "HDF5CDataTypes.JH5T_STD_U32LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U64BE ) ) return "HDF5CDataTypes.JH5T_STD_U64BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U64LE ) ) return "HDF5CDataTypes.JH5T_STD_U64LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U8BE ) ) return "HDF5CDataTypes.JH5T_STD_U8BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_STD_U8LE ) ) return "HDF5CDataTypes.JH5T_STD_U8LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_UNIX_D32BE ) ) return "HDF5CDataTypes.JH5T_UNIX_D32BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_UNIX_D32LE ) ) return "HDF5CDataTypes.JH5T_UNIX_D32LE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_UNIX_D64BE ) ) return "HDF5CDataTypes.JH5T_UNIX_D64BE";
+		else if ( data_type == H5.J2C( HDF5CDataTypes.JH5T_UNIX_D64LE ) ) return "HDF5CDataTypes.JH5T_UNIX_D64LE";
+		else if ( data_type == H5.J2C(HDF5CDataTypes.JH5T_NATIVE_OPAQUE)) return "H5T_NATIVE_OPAQUE";
+		else return "Unknown";
+	}
+
+	/**
+	 * finalize() is called by the garbage collector on the object when garbage
+	 * collection determines that there are no more references to the object. It
+	 * is used to dispose of system resources or to perform other cleanup as C++
+	 * destructors
+	 */
+	protected void finalize() throws Throwable
+	{
+		try { super.finalize(); }
+		finally { H5.H5Tclose(id); }
+	}
+
+}
+
+
+
+
+
+
+
diff --git a/visad/data/hdf5/hdf5objects/HDF5File.java b/visad/data/hdf5/hdf5objects/HDF5File.java
new file mode 100644
index 0000000..35700b0
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5File.java
@@ -0,0 +1,220 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package visad.data.hdf5.hdf5objects;
+
+import java.io.File;
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.HDF5Constants;
+import ncsa.hdf.hdf5lib.exceptions.HDF5Exception;
+
+/**
+ *  <p>
+ *  An HDF5File is designed to provide file-level access to HDF5 files.
+ *  <P>
+ *  For details of the HDF5 libraries, see the HDF5 Documentation at:
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/doc/">http://hdf.ncsa.uiuc.edu/HDF5/doc/</a>
+ */
+
+public class HDF5File extends HDF5Object
+{
+
+	private static final long serialVersionUID = 1L;
+
+	/** Creates a new HDF5 file.
+	 *  @param filename The name of the HDF5 file.
+	 *  @param flags File access flags.
+	 *  @param create_id File creation property list identifier.
+	 *  @param access_id File access property list identifier.
+	 */
+	public HDF5File(String filename, int flags, int create_id, int access_id)
+	throws HDF5Exception
+	{
+		super(filename);
+
+		type = HDF5FILE;
+
+		try {
+			id = H5.H5Fcreate(filename, flags, create_id, access_id);
+		} catch (HDF5Exception e) {
+			id = -1;
+			throw new HDF5Exception("HDF5File: "+e);
+		}
+	}
+
+	/** Opens an existing HDF5 file.
+	 *  @param filename The name of the HDF5 file.
+	 *  @param flags File access flags.
+	 *  @param access_id File access property list identifier.
+	 */
+	public HDF5File(String filename, int flags, int access_id)
+	throws HDF5Exception
+	{
+		super(filename);
+
+		type = HDF5FILE;
+
+		try {
+			id = H5.H5Fopen(filename, flags, access_id);
+		} catch (HDF5Exception e) {
+			id = -1;
+			throw new HDF5Exception("HDF5File: "+e);
+		}
+	}
+
+	/**
+	 * finalize() is called by the garbage collector on the object when garbage
+	 * collection determines that there are no more references to the object. It
+	 * is used to dispose of system resources or to perform other cleanup as C++
+	 * destructors
+	 */
+	protected void finalize() throws Throwable
+	{
+		try { super.finalize(); }
+		finally { H5.H5Fclose(id); }
+	}
+
+
+	public HDF5TreeNode loadTree()	{
+
+		int file;
+		int ret;
+		HDF5TreeNode root = null;
+
+		try {
+			file = H5.H5Fopen(name,
+				HDF5Constants.H5F_ACC_RDWR,
+				HDF5Constants.H5P_DEFAULT);
+		} catch (HDF5Exception ex) {
+				System.err.println("H5Fopen failed "+ex);
+				return root;
+		}
+
+		HDF5Group rootGroup = new HDF5Group(file, "/");
+		rootGroup.setShortName((new File(name)).getName());
+		root = new HDF5TreeNode(rootGroup);
+
+		depth_first(file,"/", root);
+
+		try {
+			ret = H5.H5Fclose(file);
+		} catch (HDF5Exception ex) {
+			System.err.println("H5Fopen failed "+ex);
+			return root;
+		}
+		return root;
+	}
+
+	private boolean depth_first( int pid, String gname, HDF5TreeNode pnode)
+	{
+		int nelems = 0;
+		int [] oType = new int[1];
+		String [] oName = new String[1];
+		oName[0] = new String(" ");
+		int i, ret;
+		HDF5TreeNode node;
+		HDF5Object o;
+		HDF5Group g;
+		HDF5Dataset d;
+		HDF5Datatype t;
+
+		HDF5Group pObject = (HDF5Group)(pnode.getUserObject());
+
+		String pPath = pObject.getName();
+		if (pPath.length() > 1) pPath += "/"; //do not need add "/" for the root
+
+ 		//Iterate through the file to see members of groups
+ 		nelems = 0;
+		try {
+			nelems = H5.H5Gn_members(pid, gname);
+		} catch (HDF5Exception ex) {
+			System.err.println("HDF5File.depth_first(): H5Gn_members() Failed, "+ex);
+			return false;
+		}
+		if (nelems < 0 ) {
+			return false;
+		}
+
+		for ( i = 0; i < nelems; i++) {
+			try {
+				ret = H5.H5Gget_obj_info_idx(pid, gname, i, oName, oType );
+			} catch (HDF5Exception ex) {
+				System.err.println("HDF5File.depth_first(): H5Gn_members() Failed, "+ex);
+				return false;
+			}
+
+			if (ret < 0)  {
+				continue;
+			}
+
+			if (oType[0] == HDF5Constants.H5G_GROUP) {
+				g = new HDF5Group(pid,  pPath+oName[0]);
+				g.setParent(pObject);
+				pObject.addMember(g);
+				node = new HDF5TreeNode(g);
+				pnode.add( node );
+				int pgroup = -1;
+				try {
+					pgroup = H5.H5Gopen(pid,gname);
+					depth_first(pgroup, oName[0], node);
+				} catch (HDF5Exception ex) {
+					System.err.println("HDF5File.depth_first(): H5Gopen() Failed, "+ex);
+				}
+			}
+			else if (oType[0] == HDF5Constants.H5G_DATASET) {
+				d = new HDF5Dataset(pid, pPath+oName[0]);
+				pObject.addMember(d);
+				node = new HDF5TreeNode(d);
+				pnode.add( node );
+			}
+			else if (oType[0] == HDF5Constants.H5G_TYPE) {
+				t = new HDF5Datatype(pid, pPath+oName[0]);
+				pObject.addMember(t);
+				node = new HDF5TreeNode(t);
+				pnode.add( node );
+			}
+			else {
+				o = new HDF5Object(pPath+oName[0]);
+				pObject.addMember(o);
+				node = new HDF5TreeNode(o);
+				pnode.add( node );
+			} // end of switch (oType[0])
+			oName[0] = null;
+			oType[0] = -1;
+		} // for ( i = 0; i < nelems; i++) {
+
+		return true;
+	} // private boolean depth_first
+
+}
+
diff --git a/visad/data/hdf5/hdf5objects/HDF5Group.java b/visad/data/hdf5/hdf5objects/HDF5Group.java
new file mode 100644
index 0000000..10e50ca
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5Group.java
@@ -0,0 +1,216 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package visad.data.hdf5.hdf5objects;
+
+import java.util.Vector;
+import ncsa.hdf.hdf5lib.H5;
+import ncsa.hdf.hdf5lib.exceptions.HDF5Exception;
+
+
+/**
+ *  <p>
+ *  This class is a container for the parameters to the HDF5 Group Object.
+ *  <P>
+ *  HDF5 group is a grouping structure containing instances of zero or more
+ *  groups or datasets, together with supporting metadata.
+ *  <P>
+ *  Working with groups and group members is similar in many ways to working
+ *  with directories and files in UNIX. As with UNIX directories and files,
+ *  objects in an HDF5 file are often described by giving their full (or
+ *  absolute) path names
+ *  <p>
+ *  For details of the HDF5 libraries, see the HDF5 Documentation at:
+ *  <a href="http://hdf.ncsa.uiuc.edu/HDF5/doc/">http://hdf.ncsa.uiuc.edu/HDF5/doc/</a>
+ */
+
+public class HDF5Group extends HDF5Object
+{
+	/** members of the group */
+	protected Vector members;
+
+	/** the parent group */
+	protected HDF5Group parent;
+
+	/** Constructs an HDF5Group */
+	public HDF5Group()
+	{
+		super();
+
+		type = GROUP;
+		members = new Vector();
+	}
+
+	/** Creates a new HDF5 Group.
+	 *  @param loc_id The file or group identifier.
+	 *  @param gname The absolute or relative name of the new group.
+	 *  @param name_length The maximum length of the name.
+	 */
+	public HDF5Group(int loc_id, String gname, int name_length)
+	{
+		super(gname);
+
+		type = GROUP;
+		members = new Vector();
+
+		if (name_length <=0)
+			name_length = gname.length();
+
+		try {
+			id = H5.H5Gcreate(loc_id, name, name_length);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Group: "+e);
+			id = -1;
+		}
+	}
+
+	/** Opens an existing HDF5 Group.
+	 *  @param loc_id The file or group identifier..
+	 *  @param name The absolute or relative name of the new group..
+	 */
+	public HDF5Group(int loc_id, String name)
+	{
+		super(name);
+
+		type = GROUP;
+		members = new Vector();
+
+		try {
+			id = H5.H5Gopen(loc_id, name);
+		} catch (HDF5Exception e) {
+			System.err.println("HDF5Group: "+e);
+			id = -1;
+		}
+	}
+
+	/** Sets the parent of this group
+	 *  @param p the parent of the HDF5Group
+	 */
+	public void setParent(HDF5Group p)
+	{
+		this.parent = p;
+	}
+
+	/** Tests if the specified object is a member of this group.
+	 *
+	 *  @param member a member
+	 *  @return true if the specified object is a member of the group;
+	 *  false otherwise.
+	 */
+	public boolean contains(Object member)
+	{
+		return members.contains(member);
+	}
+
+	/** Adds a new member to the group.
+	 *  @param member the new member to be added to the group
+	 */
+	public void addMember(Object member)
+	{
+		if (!contains(member))
+			members.addElement(member);
+	}
+
+	/** Deletes the component at the specified index. */
+	public void removeMemberAt(int index) {
+		members.removeElementAt(index);
+	}
+
+	/** Removes the member from this group
+	 *  @param member the member to be removed.
+	 *  @return true if the member was a component of this vector;
+	 *  false otherwise.
+	 */
+	public boolean removeMember(Object member) {
+		return members.removeElement(member);
+	}
+
+	/** Returns the members of the group */
+	public Vector getMembers() {
+		return members;
+	}
+
+	/** Returns the parent of the group */
+	public HDF5Group getParent() {
+		return parent;
+	}
+
+	/** Returns the member at index memberIndex
+	 *  @param memberIndex the index of the group member
+	 */
+	public Object getMemberAt(int memberIndex) {
+		return members.elementAt(memberIndex);
+	}
+
+	/** Returns the number of members the HDF5Group contains */
+	public int getMemberCount() {
+		if (members == null)
+			return -1;
+		else
+			return members.size();
+	}
+
+	/** Returns true if the HDF5Group has no member */
+	public boolean isEmpty() {
+		return (members == null || members.size()<=0);
+	}
+
+	/** Returns true if the HDF5Group is the root group */
+	public boolean isRoot() {
+		return (parent == null);
+	}
+
+	/**
+	 * Converts this object to a String representation.
+	 * @return a string representation of this object
+	 */
+	public synchronized String toString() {
+		return getClass().getName() + "[name=" + name+
+			",members=" + members.toString()+"]";
+	}
+
+	/**
+	 * finalize() is called by the garbage collector on the object when garbage
+	 * collection determines that there are no more references to the object. It
+	 * is used to dispose of system resources or to perform other cleanup as C++
+	 * destructors
+	 */
+	protected void finalize() throws Throwable
+	{
+		try { super.finalize(); }
+		finally { H5.H5Gclose(id); }
+	}
+
+}
+
+
diff --git a/visad/data/hdf5/hdf5objects/HDF5Object.java b/visad/data/hdf5/hdf5objects/HDF5Object.java
new file mode 100644
index 0000000..4cad169
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5Object.java
@@ -0,0 +1,175 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+
+package visad.data.hdf5.hdf5objects;
+
+import java.io.Serializable;
+import ncsa.hdf.hdf5lib.exceptions.HDF5Exception;
+
+/**
+ *  HDF5Object is an HDF5Object which is the super class of all the
+ *  HDF5 objects. Each HDF5 object inherits all the methods and fields of the
+ *  HDF5Object and may override inherited methods.
+ *  <P>
+ *  HDF5 files are organized in a hierarchical structure, with two primary
+ *  structures (or objects): groups and datasets.
+ *  <pre>
+ *    HDF5Group: a grouping structure containing instances of zero or more
+ *         groups or datasets, together with supporting metadata.
+ *    HDF5Dataset: a multidimensional array of data elements, together with
+ *         supporting metadata.
+ *  </pre>
+ *  Other HDF5 objects include
+ *  <ul>
+ *  <li>HDF5Attribute: small datasets to be attached to primary datasets as
+ *    metadata information.
+ *  <li>HDF5Dataspace: a dataspace describes the locations that dataset elements
+ *    are located at.
+ *  <li>HDF5Datatype: a data type is a collection of data type properties.
+ *  <li>HDF5Propertylist: a property list is a collection of name/value pairs
+ *    which can be passed to various other HDF5 functions.
+ *  </ul>
+ */
+
+public class HDF5Object implements Serializable
+{
+	/** Unknown object type */
+	public static final int UNKNOWN = -1;
+
+	/** Object is a symbolic link */
+	public static final int LINK = 0;
+
+	/** Object is a group */
+	public static final int GROUP = 1;
+
+	/** Object is a dataset */
+	public static final int DATASET = 2;
+
+	/* Object is a named data type */
+	public static final int DATATYPE = 3;
+
+	/* Object is a named data type */
+	public static final int DATASPACE = 4;
+
+	/* Object is a named data type */
+	public static final int ATTRIBUTE = 5;
+
+	/* HDF5 file */
+	public static final int HDF5FILE = 6;
+
+	/** the type of the object */
+	protected int type;
+
+	/** the full path name of the HDF5 object */
+	protected String name;
+
+	/** the short name for display: name without path */
+	protected String shortName;
+
+	/** the identifier of the HDF5 object */
+	protected int id;
+
+	/** the short description of the HDF5 object */
+	protected String description;
+
+	/** construct an HDF5 object with defaults */
+	public HDF5Object() { this (null); }
+
+	/** cosntruct an HDF5Object object name
+	 *  @param objName the name of the HDF5 data object
+	 */
+	public HDF5Object(String objName) {
+		name = objName;
+		type = UNKNOWN;
+		description = "";
+
+		if (name != null && !name.equals("/"))
+		{
+			int idx = name.lastIndexOf('/')+1;
+			shortName = name.substring(idx);
+		} else
+			shortName = name;
+	}
+
+	/** initialize the HDF5Object: open the HDF5 library.
+	 *  A subclass of HDF5Object should override this method
+	 *  if it has initialization to perform.
+	 */
+	public void init() throws HDF5Exception
+	{
+		if (id < 0) return;
+	}
+
+	/** reset the HDF5Object for a given id
+	 *  @param new_id the id of the object
+	 */
+	public void setID(int new_id) throws HDF5Exception
+	{
+		id = new_id;
+		init();
+	}
+
+	/** Sets the short name of the HDF5Object */
+	public void setShortName(String sname) { shortName = sname; }
+
+	/** Returns the type of the object */
+	public int getType() { return type; }
+
+	/** Sets the type of the object */
+	public void setType(int t) {type = t;}
+
+	/** Returns the full name of the HDF5Object */
+	public String getName() { return name; }
+
+	/** Returns the short name of the HDF5Object */
+	public String getShortName() { return shortName; }
+
+	/** Returns the description of the HDF5Object */
+	public String getDescription() { return description; }
+
+	/** Returns the identifier of the HDF5Object */
+	public int getID() { return id; }
+
+	/**
+	 * Converts this object to a String representation.
+	 * @return a string representation of this object
+	 */
+	public String toString() {
+		if (name == null)
+			return super.toString();
+		else
+			return getClass().getName() + "[name=" + name + "]";
+	}
+
+
+}
diff --git a/visad/data/hdf5/hdf5objects/HDF5TreeNode.java b/visad/data/hdf5/hdf5objects/HDF5TreeNode.java
new file mode 100644
index 0000000..cf0a628
--- /dev/null
+++ b/visad/data/hdf5/hdf5objects/HDF5TreeNode.java
@@ -0,0 +1,117 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/****************************************************************************
+ * NCSA HDF                                                                 *
+ * National Comptational Science Alliance                                   *
+ * University of Illinois at Urbana-Champaign                               *
+ * 605 E. Springfield, Champaign IL 61820                                   *
+ *                                                                          *
+ * For conditions of distribution and use, see the accompanying             *
+ * hdf/COPYING file.                                                        *
+ *                                                                          *
+ ****************************************************************************/
+package visad.data.hdf5.hdf5objects;
+
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class HDF5TreeNode extends DefaultMutableTreeNode
+{
+	/** the indent for printing the tree */
+	private final String INDENT = "  ";
+
+	/** Creates a tree node that has no parent and no children,
+		but which allows children.
+	 */
+	public HDF5TreeNode()
+	{
+		super();
+	}
+
+	/** Creates a tree node with no parent, no children, but which allows
+		children, and initializes it with the specified user object.
+	 */
+	public HDF5TreeNode(HDF5Object userObject)
+	{
+		super (userObject);
+	}
+
+	/** Creates a tree node with no parent, no children, initialized with the
+		specified user object, and that allows children only if specified.
+	 */
+	public HDF5TreeNode(Object userObject, boolean allowsChildren)
+	{
+		super(userObject, allowsChildren);
+	}
+
+	/** Print the tree information starting this node */
+	public void printTree()
+	{
+		String indent = INDENT;
+		printNodeDown(indent, this);
+	}
+
+
+	/** Print the tree information from starting node
+	 *  @param indent the indent of tree level
+	 *  @param sNode the starting node
+	 */
+	private void printNodeDown(String indent, TreeNode sNode)
+	{
+		System.out.print(indent);
+		indent = indent + INDENT;
+
+		if (sNode == null) {
+			System.out.println("null");
+			return;
+		}
+
+		System.out.println(sNode);
+
+		if (sNode.isLeaf() || sNode.getChildCount() == 0)
+			return;
+		else {
+			int nChildren = sNode.getChildCount();
+			for (int i=0; i< nChildren; i++)
+				printNodeDown(indent, sNode.getChildAt(i));
+		}
+	}
+
+	/**
+	 * Returns the result of sending <code>toString()</code> to this node's
+	 * user object, or null if this node has no user object.
+	 *
+	 * @see	#getUserObject
+	 */
+	public String toString() {
+		if (userObject == null) {
+			return "null";
+		} else if (userObject instanceof HDF5Object) {
+			return ((HDF5Object)userObject).getShortName();
+		} else {
+			return userObject.toString();
+		}
+	}
+
+
+}
diff --git a/visad/data/hdfeos/Calibration.java b/visad/data/hdfeos/Calibration.java
new file mode 100644
index 0000000..974333d
--- /dev/null
+++ b/visad/data/hdfeos/Calibration.java
@@ -0,0 +1,34 @@
+//
+// Calibration.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+abstract class Calibration
+{
+   public abstract void fromCalibration( short[] in, double[] out );
+   public abstract void fromCalibration( short[] in, float[] out );
+   public abstract void fromCalibration( byte[] in, float[] out );
+}
diff --git a/visad/data/hdfeos/CalibrationDefault.java b/visad/data/hdfeos/CalibrationDefault.java
new file mode 100644
index 0000000..d80e637
--- /dev/null
+++ b/visad/data/hdfeos/CalibrationDefault.java
@@ -0,0 +1,155 @@
+//
+// CalibrationDefault.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+class CalibrationDefault extends Calibration
+{
+  private static String scaleFactorName = "scale_factor";
+  private static String offsetName = "add_offset";
+  private static String fillValueName = "_FillValue";
+  private static String validRange = "valid_range";
+
+  private static String[] names = new String[4];
+
+  static
+  {
+    names[0] = scaleFactorName;
+    names[1] = offsetName;
+    names[2] = fillValueName;
+    names[3] = validRange;
+  }
+
+  double scale_factor;
+  double offset;
+  double fillvalue;
+  double v_range_low;
+  double v_range_high;
+
+  CalibrationDefault( double[][] constants )
+  {
+    this.scale_factor = constants[0][0];
+    this.offset = constants[1][0];
+    this.fillvalue = constants[2][0];
+    if ( constants[3][0] < constants[3][1] )
+    {
+      this.v_range_low = constants[3][0];
+      this.v_range_high = constants[3][1];
+    }
+    else
+    {
+      this.v_range_low = constants[3][1];
+      this.v_range_high = constants[3][0];
+    }
+  }
+
+  public static String[] getNames()
+  {
+    return names;
+  }
+
+  public void fromCalibration( short[] values, double[] out )
+  {
+    double d_value;
+
+    for ( int ii = 0; ii < values.length; ii++ )
+    {
+      d_value = (double) values[ii];
+
+      if ( d_value == fillvalue )
+      {
+        d_value = Double.NaN;
+      }
+      else if ( (d_value < v_range_low)&&( d_value > v_range_high) )
+      {
+        d_value = Double.NaN;
+      }
+      else
+      {
+        d_value = (values[ii] - offset)*scale_factor;
+      }
+
+      out[ii] = d_value;
+    }
+  }
+
+  public void fromCalibration( short[] values, float[] out )
+  {
+    float f_value;
+    float offset = (float)this.offset;
+    float scale_factor = (float)this.scale_factor;
+    float v_range_low = (float)this.v_range_low;
+    float v_range_high = (float)this.v_range_high;
+
+    for ( int ii = 0; ii < values.length; ii++ )
+    {
+      f_value = (float) values[ii];
+
+      if ( f_value == ((float) fillvalue) )
+      {
+        f_value = Float.NaN;
+      }
+      else if ( (f_value < v_range_low)&&(f_value > v_range_high) )
+      {
+        f_value = Float.NaN;
+      }
+      else
+      {
+        f_value = (values[ii] - offset)*(scale_factor);
+      }
+
+      out[ii] = f_value;
+    }
+  }
+  public void fromCalibration( byte[] values, float[] out )
+  {
+    float f_value;
+    float offset = (float)this.offset;
+    float scale_factor = (float)this.scale_factor;
+    float v_range_low = (float)this.v_range_low;
+    float v_range_high = (float)this.v_range_high;
+
+    for ( int ii = 0; ii < values.length; ii++ )
+    {
+      f_value = (float) values[ii];
+
+      if ( f_value == ((float) fillvalue) )
+      {
+        f_value = Float.NaN;
+      }
+      else if ( (f_value < v_range_low)&&(f_value > v_range_high) )
+      {
+        f_value = Float.NaN;
+      }
+      else
+      {
+        f_value = (values[ii] - offset)*(scale_factor);
+      }
+
+      out[ii] = f_value;
+    }
+  }
+}
diff --git a/visad/data/hdfeos/DimensionSet.java b/visad/data/hdfeos/DimensionSet.java
new file mode 100644
index 0000000..af8e6e8
--- /dev/null
+++ b/visad/data/hdfeos/DimensionSet.java
@@ -0,0 +1,190 @@
+//
+// DimensionSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+
+public class DimensionSet
+{
+  private Vector dimSet;
+  private boolean finished = false;
+
+  DimensionSet()
+  {
+    dimSet = new Vector();
+  }
+
+  DimensionSet( NamedDimension[] dims )
+  {
+    dimSet = new Vector();
+    for ( int ii = 0; ii < dims.length; ii++ ) {
+      dimSet.add(dims[ii]);
+    }
+  }
+
+  public void add( NamedDimension obj )
+  {
+    if (! finished )
+    {
+      dimSet.addElement( obj );
+    }
+    else
+    {
+      /* throw Exception: obj finished  */
+    }
+  }
+
+  public void setToFinished()
+  {
+    finished = true;
+  }
+
+  public int getSize()
+  {
+    int size = dimSet.size();
+    return size;
+  }
+
+  public NamedDimension getElement( int ii )
+  {
+    NamedDimension obj = (NamedDimension)dimSet.elementAt( ii );
+
+    return obj;
+  }
+
+  public NamedDimension[] getElements()
+  {
+    NamedDimension[] array = new NamedDimension[getSize()];
+    for ( int ii = 0; ii < getSize(); ii++ ) {
+      array[ii] = getElement(ii);
+    }
+    return array;
+  }
+
+  public int getIndexOf( NamedDimension dim )
+  {
+    for ( int ii = 0; ii < getSize(); ii++ ) {
+      if ( (getElement(ii)).equals(dim) ) {
+        return ii;
+      }
+    }
+    return -1;
+  }
+
+  public boolean sameSetSameOrder( DimensionSet  dimSet )
+  {
+    int size = this.getSize();
+
+    if ( size != dimSet.getSize() ) {
+      return false;
+    }
+
+    for ( int ii = 0; ii < size; ii++ )
+    {
+      if ( ! (this.getElement(ii).equals( dimSet.getElement(ii)))  ) {
+        return false;
+      }
+    }
+     return true;
+  }
+
+  public boolean subsetOfThis( DimensionSet dimSet )
+  {
+     int size = this.getSize();
+     int size_arg = dimSet.getSize();
+
+     if ( size_arg > size ) {
+        return false;
+     }
+     else {
+
+       for ( int ii = 0; ii < size_arg; ii++ )  {
+
+         NamedDimension obj = (NamedDimension)dimSet.getElement( ii );
+         boolean equal = false;
+
+         for ( int jj = 0; jj < size; jj++ )  {
+
+            if( obj.equals( (NamedDimension)this.getElement( jj ) )) {
+
+                equal = true;
+            }
+         }
+
+         if ( !equal ) {
+            return false;
+         }
+
+       }
+     }
+
+    return true;
+  }
+
+  public NamedDimension getByName( String dimName )
+  {
+    for ( int ii = 0; ii < this.getSize(); ii++ )
+    {
+      NamedDimension obj = (NamedDimension)this.getElement(ii);
+
+      String name = obj.getName();
+
+      if ( name.equals( dimName )) {
+        return obj;
+      }
+    }
+    return null;
+  }
+
+  public boolean isMemberOf( NamedDimension dim )
+  {
+    String in_name = dim.getName();
+
+    for ( int ii = 0; ii < this.getSize(); ii++ ) {
+
+      NamedDimension obj = (NamedDimension)this.getElement(ii);
+
+      String name = obj.getName();
+
+      if ( (in_name).equals( name )) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public String toString()
+  {
+     String str = "DimensionSet: \n";
+
+     for ( int ii = 0; ii < this.getSize(); ii++ )
+     {
+        str = str + "   "+((this.getElement(ii)).toString())+"\n";
+     }
+     return str;
+  }
+}
diff --git a/visad/data/hdfeos/EosGrid.java b/visad/data/hdfeos/EosGrid.java
new file mode 100644
index 0000000..e6a9c99
--- /dev/null
+++ b/visad/data/hdfeos/EosGrid.java
@@ -0,0 +1,240 @@
+//
+// EosGrid.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+import visad.data.hdfeos.hdfeosc.HdfeosLib;
+
+public class EosGrid extends EosStruct {
+
+  int grid_id;
+  int file_id;
+  int sd_id;
+  private String grid_name;
+
+  DimensionSet  D_Set = null;
+   VariableSet  DV_Set;
+      ShapeSet  DV_shapeSet;
+       GctpMap  gridMap;
+
+  EosGrid ( int file_id, int sd_id, String name )
+  throws HdfeosException
+  {
+     super();
+     this.file_id = file_id;
+     this.sd_id = sd_id;
+     grid_name = name;
+     grid_id = HdfeosLib.GDattach( file_id, name );
+     struct_id = grid_id;
+
+     if ( grid_id < 0 )
+     {
+       throw new HdfeosException("EosGrid cannot attatch Grid: "+name );
+     }
+
+
+/**-  Now make dimensionSet:  - - - - - - - - - - -  -*/
+
+      int[] stringSize = new int[1];
+      stringSize[0] = 0;
+
+      DimensionSet D_Set = new DimensionSet();
+
+      int n_dims = HdfeosLib.GDnentries( grid_id, HdfeosLib.N_DIMS, stringSize );
+
+      if ( n_dims > 0 )
+      {
+
+        String[] dimensionList = {"empty"};
+        int[] lengths = new int[ n_dims ];
+
+        n_dims = HdfeosLib.GDinqdims( grid_id, stringSize[0], dimensionList, lengths );
+
+        if ( n_dims <= 0 )
+        {
+           throw new HdfeosException("GDinqdims status: "+n_dims);
+        }
+
+        StringTokenizer listElements =
+                new StringTokenizer( dimensionList[0], ",", false );
+
+        int cnt = 0;
+
+        while ( listElements.hasMoreElements() ) {
+
+          name = (String) listElements.nextElement();
+          int len = lengths[cnt];
+          NamedDimension obj = new NamedDimension( grid_id, name, len, null );
+
+          D_Set.add( obj );
+          cnt++;
+        }
+      }
+
+      this.D_Set = D_Set;
+
+
+/**-  Done, now make VariableSets:  - - - - - - - - -*/
+
+       int n_flds = HdfeosLib.GDnentries( grid_id, HdfeosLib.D_FIELDS, stringSize );
+
+       if ( n_flds <= 0 )
+       {
+         throw new HdfeosException(" no data fields  ");
+       }
+
+       String[] D_List = {"empty"};
+
+       int[] dumA = new int[ n_flds ];
+       int[] dumB = new int[ n_flds ];
+
+       n_flds = HdfeosLib.GDinqfields( grid_id, stringSize[0], D_List, dumA, dumB);
+
+       if ( n_flds < 0 )
+       {
+          throw new HdfeosException("no data fields in grid struct: "+grid_id);
+       }
+
+       this.makeVariables( D_List[0] );
+
+
+/**-  Done, now make ShapeSet for data fields: - - - - - - - - - */
+
+        DV_shapeSet = new ShapeSet( DV_Set );
+
+/**-  Retrieve map projection type and paramters: - - - - - - -  */
+
+        int[] projcode = new int[1];
+        int[] zonecode = new int[1];
+        int[] sphrcode = new int[1];
+        double[] projparm = new double[16];
+
+        int stat = HdfeosLib.GDprojinfo( grid_id, projcode, zonecode, sphrcode, projparm );
+
+        if ( stat < 0 )
+        {
+            throw new HdfeosException(" GDprojinfo, status: "+stat);
+        }
+
+            int[] xdimsize = new int[1];
+            int[] ydimsize = new int[1];
+         double[] uprLeft = new double[2];
+         double[] lwrRight = new double[2];
+
+         stat = HdfeosLib.GDgridinfo( grid_id, xdimsize, ydimsize, uprLeft, lwrRight );
+
+         if ( stat < 0 )
+         {
+             throw new HdfeosException(" GDgridinfo, status: "+stat);
+         }
+
+         gridMap = new GctpMap( projcode[0], zonecode[0], sphrcode[0],
+                                xdimsize[0], ydimsize[0], projparm, uprLeft, lwrRight );
+
+ } /**-  end EosGrid constuctor  - - - - - - - - - - - - -*/
+
+
+  public int getStructId() {
+     return grid_id;
+  }
+
+  public GctpMap getMap() {
+     return gridMap;
+  }
+
+  public ShapeSet getShapeSet() {
+    return DV_shapeSet;
+  }
+
+  private void makeVariables( String fieldList )
+               throws HdfeosException
+  {
+
+      int[] rank = new int[ 1 ];
+      int[] type = new int[ 1 ];
+      int[] lengths = new int[ 10 ];
+
+      NamedDimension n_dim;
+      int cnt;
+
+      StringTokenizer listElements = new StringTokenizer( fieldList, ",", false );
+
+      VariableSet varSet = new VariableSet();
+
+      while ( listElements.hasMoreElements() )
+      {
+
+          String field = (String)listElements.nextElement();
+
+          String[] dim_list = {"empty"};
+
+          int stat = HdfeosLib.GDfieldinfo( grid_id, field, dim_list, rank, lengths, type );
+
+          if ( stat < 0 )
+          {
+            throw new HdfeosException(" GDfieldinfo, stat < 1 for: "+field );
+          }
+
+          StringTokenizer dimListElements = new StringTokenizer( dim_list[0], ",", false );
+
+          Vector dims = new Vector();
+          DimensionSet newSet = new DimensionSet();
+
+          cnt = 0;
+          while ( dimListElements.hasMoreElements() )
+          {
+              String dimName = (String) dimListElements.nextElement();
+
+              n_dim = D_Set.getByName( dimName );
+
+              if ( n_dim == null ) {
+
+                n_dim = new NamedDimension( grid_id, dimName, lengths[cnt], null);
+                D_Set.add( n_dim );
+              }
+
+              if ( n_dim.isUnlimited() )  {
+                n_dim.setLength( lengths[ cnt ] );
+              }
+
+              newSet.add( n_dim );
+              cnt++;
+          }
+              newSet.setToFinished();
+
+
+          Variable obj = new Variable(  field, newSet, rank[0], type[0], null );
+          varSet.add( obj );
+
+      }
+
+          varSet.setToFinished();
+
+          DV_Set = varSet;
+
+  }
+}
diff --git a/visad/data/hdfeos/EosStruct.java b/visad/data/hdfeos/EosStruct.java
new file mode 100644
index 0000000..129b9a1
--- /dev/null
+++ b/visad/data/hdfeos/EosStruct.java
@@ -0,0 +1,155 @@
+//
+// EosStruct.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.data.hdfeos.hdfeosc.HdfeosLib;
+
+public abstract class EosStruct
+{
+  static String G_TYPE = "Geolocation Fields";
+  static String D_TYPE = "Data Fields";
+
+  int struct_id;
+
+  public int getStructId()
+  {
+    return struct_id;
+  }
+
+  public void readData( String f_name, int[] start, int[] stride, int[] edge,
+                        int num_type, Calibration cal, float[] f_data )
+         throws HdfeosException
+  {
+    int status = 0;
+    int jj;
+    int n_values = f_data.length;
+
+    if ( num_type == HdfeosLib.FLOAT )
+    {
+      if ( this instanceof EosGrid )
+      {
+        status = HdfeosLib.GDreadfield( struct_id, f_name, start, stride, edge, f_data );
+      }
+      else if ( this instanceof EosSwath )
+      {
+        status = HdfeosLib.SWreadfield( struct_id, f_name, start, stride, edge, f_data );
+      }
+    }
+    else if ( num_type == HdfeosLib.DOUBLE )
+    {
+      double[] d_data = new double[ n_values ];
+      if( this instanceof EosGrid )
+      {
+        status = HdfeosLib.GDreadfield( struct_id, f_name, start, stride, edge, d_data );
+      }
+      else if ( this instanceof EosSwath )
+      {
+        status = HdfeosLib.SWreadfield( struct_id, f_name, start, stride, edge, d_data );
+      }
+
+      for ( jj = 0; jj < n_values; jj++ )
+      {
+        f_data[jj] = (float)d_data[jj];
+      }
+      d_data = null;
+    }
+    else if ( num_type == HdfeosLib.INT )
+    {
+      int[] i_data = new int[ n_values ];
+      if ( this instanceof EosGrid )
+      {
+        status = HdfeosLib.GDreadfield( struct_id, f_name, start, stride, edge, i_data );
+      }
+      else if ( this instanceof EosSwath )
+      {
+        status = HdfeosLib.SWreadfield( struct_id, f_name, start, stride, edge, i_data );
+      }
+
+      for ( jj = 0; jj < n_values; jj++ )
+      {
+        f_data[jj] = (float)i_data[jj];
+      }
+      i_data = null;
+    }
+    else if (( num_type == HdfeosLib.SHORT )||
+              ( num_type == HdfeosLib.U_SHORT))
+    {
+      short[] s_data = new short[ n_values ];
+      if ( this instanceof EosGrid )
+      {
+        status = HdfeosLib.GDreadfield( struct_id, f_name, start, stride, edge, s_data );
+      }
+      else if ( this instanceof EosSwath )
+      {
+        status = HdfeosLib.SWreadfield( struct_id, f_name, start, stride, edge, s_data );
+      }
+
+      if ( cal != null )
+      {
+        cal.fromCalibration( s_data, f_data );
+      }
+      else
+      {
+        for ( jj = 0; jj < n_values; jj++ )
+        {
+          f_data[jj] = (float)s_data[jj];
+        }
+      }
+      s_data = null;
+    }
+    else if (( num_type == HdfeosLib.BYTE )||
+             ( num_type == HdfeosLib.U_BYTE))
+    {
+      byte[] b_data = new byte[ n_values ];
+      if ( this instanceof EosGrid )
+      {
+        status = HdfeosLib.GDreadfield( struct_id, f_name, start, stride, edge, b_data );
+      }
+      else if ( this instanceof EosSwath )
+      {
+        status = HdfeosLib.SWreadfield( struct_id, f_name, start, stride, edge, b_data );
+      }
+
+      if ( cal != null )
+      {
+        cal.fromCalibration( b_data, f_data );
+      }
+      else
+      {
+        for ( jj = 0; jj < n_values; jj++ )
+        {
+          f_data[jj] = (float)b_data[jj];
+        }
+      }
+      b_data = null;
+    }
+    else
+    {
+      throw new HdfeosException(" number type not implemented: "+num_type );
+    }
+  }
+}
diff --git a/visad/data/hdfeos/EosSwath.java b/visad/data/hdfeos/EosSwath.java
new file mode 100644
index 0000000..b10c1e4
--- /dev/null
+++ b/visad/data/hdfeos/EosSwath.java
@@ -0,0 +1,307 @@
+//
+// EosSwath.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+import visad.data.hdfeos.hdfeosc.HdfeosLib;
+
+public class EosSwath extends EosStruct
+{
+  int swath_id;
+  int sd_id;
+  private String swath_name;
+
+  GeoMapSet  G_Set;
+  DimensionSet  D_Set;
+  VariableSet  DV_Set;
+  VariableSet  GV_Set;
+     ShapeSet  DV_shapeSet;
+     ShapeSet  GV_shapeSet;
+
+  EosSwath ( int file_id, int sd_id, String name )
+           throws HdfeosException
+  {
+     super();
+     swath_name = name;
+     this.sd_id = sd_id;
+     swath_id = HdfeosLib.SWattach( file_id, name );
+     struct_id = swath_id;
+
+     if ( swath_id < 0 )
+     {
+        throw new HdfeosException(" EosSwath cannot attach to swath: "+name );
+     }
+
+/**- make GeoMapSet:  - - - - - - - - - - - - - - - - -*/
+
+     int[] stringSize = new int[1];
+     stringSize[0] = 0;
+
+     int n_maps = HdfeosLib.SWnentries( swath_id, HdfeosLib.G_MAPS, stringSize );
+
+     if ( n_maps > 0 )
+     {
+        int[] offset = new int[ n_maps ];
+        int[] increment = new int[ n_maps ];
+        String[] map_list = {"empty"};
+
+        n_maps = HdfeosLib.SWinqmaps( swath_id, stringSize[0], map_list, offset, increment  );
+
+        G_Set = new GeoMapSet();
+
+        StringTokenizer mapElements = new StringTokenizer( map_list[0], ",", false );
+
+        int cnt = 0;
+
+        while ( mapElements.hasMoreElements() )
+        {
+           String map = (String) mapElements.nextElement();
+
+           StringTokenizer dims = new StringTokenizer( map, "/", false );
+
+           String[] S_array = new String[2];
+
+           int cnt2 = 0;
+           while ( dims.hasMoreElements() )
+           {
+              S_array[cnt2] = (String) dims.nextElement();
+              cnt2++;
+           }
+           String toDim = S_array[1];
+           String fromDim = S_array[0];
+           int off = offset[cnt];
+           int inc = increment[cnt];
+
+           GeoMap obj = new GeoMap( toDim, fromDim, off, inc );
+           G_Set.add( obj );
+        }
+     }
+     else
+     {
+        G_Set = new GeoMapSet();
+     }
+
+/**-  Done, now make DimensionSet:  - - - - - - - - - - -  -*/
+
+      int n_dims = HdfeosLib.SWnentries( swath_id, HdfeosLib.N_DIMS, stringSize );
+
+      if ( n_dims <= 0 )
+      {
+        throw new HdfeosException("no dimension defined");
+      }
+
+
+      DimensionSet D_Set = new DimensionSet();
+
+      String[] dimensionList = {"empty"};
+      int[] lengths = new int[ n_dims ];
+
+      n_dims = HdfeosLib.SWinqdims( swath_id, stringSize[0], dimensionList, lengths );
+
+      if ( n_dims <= 0 )
+      {
+        throw new HdfeosException("no dimension defined");
+      }
+
+      StringTokenizer listElements =
+              new StringTokenizer( dimensionList[0], ",", false );
+
+      int cnt = 0;
+
+      while ( listElements.hasMoreElements() )
+      {
+        name = (String) listElements.nextElement();
+        int len = lengths[cnt];
+        GeoMap g_map = G_Set.getGeoMap( name );
+        NamedDimension obj = new NamedDimension( swath_id, name, len, g_map );
+
+        D_Set.add( obj );
+        cnt++;
+      }
+
+      this.D_Set = D_Set;
+
+
+/**-  Done, now make VariableSets:  - - - - - - - - -*/
+
+       int n_flds = HdfeosLib.SWnentries( swath_id, HdfeosLib.D_FIELDS, stringSize );
+
+       if ( n_flds <= 0 )
+       {
+          throw new HdfeosException(" no Data Fields from SWnentries ");
+       }
+
+       String[] D_List = {"empty"};
+
+         int[] dumA = new int[ n_flds ];
+         int[] dumB = new int[ n_flds ];
+
+       n_flds = HdfeosLib.SWinqdatafields( swath_id, stringSize[0], D_List, dumA, dumB);
+
+       if ( n_flds < 0 )
+       {
+          throw new HdfeosException("no data fields in swath # "+swath_id);
+       }
+
+       this.makeVariables( D_List[0], D_TYPE );
+
+       n_flds = HdfeosLib.SWnentries( swath_id, HdfeosLib.G_FIELDS, stringSize );
+
+       String[] G_List = {"empty"};
+
+         int[] dumC = new int[ n_flds ];
+         int[] dumD = new int[ n_flds ];
+
+
+       n_flds = HdfeosLib.SWinqgeofields( swath_id, stringSize[0], G_List, dumC, dumD );
+
+       this.makeVariables( G_List[0], G_TYPE );
+
+
+/**-  Done, now make ShapeSets for both data and geo fields: - - - - - - - - - */
+
+      DV_shapeSet = new ShapeSet( DV_Set );
+
+      GV_shapeSet = new ShapeSet( GV_Set );
+
+
+ } /**-  end EosSwath constuctor  - - - - - - - - - - - - -*/
+
+  public int getStructId() {
+     return swath_id;
+  }
+
+  public ShapeSet getDV_shapeSet() {
+    return DV_shapeSet;
+  }
+
+  public ShapeSet getGV_shapeSet() {
+    return GV_shapeSet;
+  }
+
+  private void makeVariables( String fieldList, String f_type )
+               throws HdfeosException
+  {
+    int[] rank = new int[ 1 ];
+    int[] type = new int[ 1 ];
+    int[] lengths = new int[ 10 ];
+
+    NamedDimension n_dim;
+    Calibration calibration;
+    int cnt;
+    int stat;
+    boolean noAttr;
+    boolean noAttrValue;
+
+    StringTokenizer listElements = new StringTokenizer( fieldList, ",", false );
+
+    VariableSet varSet = new VariableSet();
+
+    String[] constantNames = CalibrationDefault.getNames();
+    double[][] constants = new double[ constantNames.length ][];
+
+    while ( listElements.hasMoreElements() )
+    {
+      noAttr = false;
+      noAttrValue = false;
+      String field = (String)listElements.nextElement();
+
+      for ( int ii = 0; ii < constants.length; ii++ )
+      {
+         cnt = HdfeosLib.SDattrinfo( sd_id, field, constantNames[ii] );
+         if ( cnt < 0 ) {
+           noAttr = true;
+           break;
+         }
+         else {
+           constants[ii] = new double[ cnt ];
+         }
+      }
+      if ( noAttr )
+      {
+        calibration = null;
+      }
+      else
+      {
+        for ( int ii = 0; ii < constants.length; ii++ )
+        {
+          stat = HdfeosLib.GetNumericAttr( sd_id, field, constantNames[ii], constants[ii] );
+          if ( stat < 0 ) {
+             noAttrValue = true;
+             break;
+          }
+        }
+        if ( noAttrValue ) {
+          calibration = null;
+        }
+        else {
+          calibration = new CalibrationDefault( constants );
+        }
+      }
+
+      String[] dim_list = {"empty"};
+
+      stat = HdfeosLib.SWfieldinfo( swath_id, field, dim_list, rank, lengths, type );
+
+      StringTokenizer dimListElements = new StringTokenizer( dim_list[0], ",", false );
+
+      Vector dims = new Vector();
+      DimensionSet newSet = new DimensionSet();
+
+      cnt = 0;
+      while ( dimListElements.hasMoreElements() )
+      {
+        String dimName = (String) dimListElements.nextElement();
+        n_dim = D_Set.getByName( dimName );
+
+         if ( n_dim.isUnlimited() )  {
+           n_dim.setLength( lengths[ cnt ] );
+         }
+
+         newSet.add( n_dim );
+         cnt++;
+      }
+      newSet.setToFinished();
+
+      Variable obj = new Variable(  field, newSet, rank[0], type[0], calibration );
+      varSet.add( obj );
+
+   }
+
+   varSet.setToFinished();
+
+
+   if ( f_type.equals( G_TYPE ))
+   {
+     GV_Set = varSet;
+   }
+   else
+   {
+     DV_Set = varSet;
+   }
+ }
+}
diff --git a/visad/data/hdfeos/GctpException.java b/visad/data/hdfeos/GctpException.java
new file mode 100644
index 0000000..ef3b2b9
--- /dev/null
+++ b/visad/data/hdfeos/GctpException.java
@@ -0,0 +1,36 @@
+//
+// GctpException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.VisADException;
+
+public class GctpException extends VisADException {
+
+  public GctpException() { super(); }
+  public GctpException(String s) { super(s); }
+
+}
diff --git a/visad/data/hdfeos/GctpFunction.java b/visad/data/hdfeos/GctpFunction.java
new file mode 100644
index 0000000..3b7b1a1
--- /dev/null
+++ b/visad/data/hdfeos/GctpFunction.java
@@ -0,0 +1,592 @@
+//
+// GctpFunction.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.lang.Math.*;
+
+public class GctpFunction
+{
+
+  static final int GEO = 0;
+  static final int UTM = 1;
+  static final int SPCS = 2;
+  static final int ALBERS = 3;
+  static final int LAMCC = 4;
+  static final int MERCAT = 5;
+  static final int PS = 6;
+  static final int POLYC = 7;
+  static final int EQUIDC = 8;
+  static final int TM = 9;
+  static final int STEREO = 10;
+  static final int LAMAZ = 11;
+  static final int AZMEQD = 12;
+  static final int GNOMON = 13;
+  static final int ORTHO = 14;
+  static final int GVNSP = 15;
+  static final int SNSOID = 16;
+  static final int EQRECT = 17;
+  static final int MILLER = 18;
+  static final int VGRINT = 19;
+  static final int HOM = 20;
+  static final int ROBIN = 21;
+  static final int SOM = 22;
+  static final int ALASKA = 23;
+  static final int GOOD = 24;
+  static final int MOLL = 25;
+  static final int IMOLL = 26;
+  static final int HAMMER = 27;
+  static final int WAGIV = 28;
+  static final int WAGVII = 29;
+  static final int OBEQA = 30;
+  static final int USDEF = 99;
+
+  static final int DATMCT = 20;
+
+  static final double PI = 3.141592653589793238;
+  static final double HALF_PI = PI*0.5;
+  static final double TWO_PI = PI*2.0;
+  static final double EPSLNI = 1.0e-10;
+  static final double R2D = 57.2957795131;
+  static final double D2R = 1.745329251994328e-2;
+  static final double S2R = 4.848136811095359e-6;
+  static final double EPSLN = 1.0e-10;
+
+  static final int MAX_VAL = 4;
+  static final long MAXLONG = 2147483647;
+  static final double DBLLONG = 4.61168601e18;
+
+
+/*******************************************************************************
+NAME                           SPHDZ
+
+PURPOSE:        This function assigns values to the semimajor axis, semiminor
+                axis, and radius of sphere.  If the datum code is negative,
+                the first two values in the parameter array (parm) are used
+                to define the values as follows:
+
+                --If parm[0] is a non-zero value and parm[1] is greater than
+                  one, the semimajor axis and radius are set to parm[0] and
+                  the semiminor axis is set to parm[1].
+
+                --If parm[0] is nonzero and parm[1] is greater than zero but
+                  less than or equal to one, the semimajor axis and radius
+                  are set to parm[0] and the semiminor axis is computed
+                  from the eccentricity squared value parm[1].  This
+                  algorithm is given below.
+
+                --If parm[0] is nonzero and parm[1] is equal to zero, the
+                  semimajor axis, radius, and semiminor axis are set to
+                  parm[0].
+
+                --If parm[0] equals zero and parm[1] is greater than zero,
+                  the default Clarke 1866 is used to assign values to the
+                  semimajor axis, radius and semiminor axis.
+
+                --If parm[0] and parm[1] equals zero, the semimajor axis
+                  and radius are set to 6370997.0 (This value is represented
+                  as the last value in the datum code array) and the
+                  semiminor axis is set to zero.
+
+                if a datum code is zero or greater, the semimajor and
+                semiminor axis are defined by the datum code as found
+                in Table A and the radius is set to 6370997.0.  If the
+                datum code is greater than DATMCT the default datum,
+                Clarke 1866, is used to define the semimajor
+                and semiminor axis and radius is set to 6370997.0.
+
+                The algorithm to define the semiminor axis using the
+                eccentricity squared value is as follows:
+
+                      semiminor = sqrt(1.0 - ES) * semimajor   where
+                      ES = eccentricity squared
+
+                Table A:
+                                SUPPORTED SPHEROIDS
+
+                 0: Clarke 1866 (default)        1: Clarke 1880
+                 2: Bessel                       3: International 1967
+                 4: International 1909           5: WGS 72
+                 6: Everest                      7: WGS 66
+                 8: GRS 1980                     9: Airy
+                10: Modified Everest            11: Modified Airy
+                12: WGS 84                      13: Southeast Asia
+                14: Australian National         15: Krassovsky
+                16: Hough                       17: Mercury 1960
+                18: Modified Mercury 1968       19: Sphere of Radius
+                                                    6370997 meters
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan             MARCH, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+static double major_data[] = {6378206.4, 6378249.145, 6377397.155, 6378157.5,
+                              6378388.0, 6378135.0, 6377276.3452, 6378145.0,
+                              6378137.0, 6377563.396, 6377304.063, 6377340.189,
+                              6378137.0, 6378155.0, 6378160.0, 6378245.0,
+                              6378270.0, 6378166.0, 6378150.0, 6370997.0};
+
+static double minor_data[] = {6356583.8, 6356514.86955, 6356078.96284, 6356772.2,
+                              6356911.94613, 6356750.519915, 6356075.4133,
+                              6356759.769356, 6356752.31414, 6356256.91,
+                              6356103.039, 6356034.448, 6356752.314245,
+                              6356773.3205, 6356774.719, 6356863.0188,
+                              6356794.343479, 6356784.283666, 6356768.337303,
+                              6370997.0};
+
+
+/* Finds the correct ellipsoid axis
+---------------------------------*/
+
+public static int sphdz( long isph,        // spheroid code number
+                         double[] parm,    // projection parameters
+                         double[] major,     // major axis
+                         double[] minor,     // minor axis
+                         double[] radius  )  // radius
+{
+
+double r_radius;
+double r_major;
+double t_major;         // temporary major axis
+double r_minor;
+double t_minor;         // temporary minor axis
+long jsph;              // spheroid code number
+
+if (isph < 0)
+   {
+   t_major = Math.abs(parm[0]);
+   t_minor = Math.abs(parm[1]);
+
+   if (t_major  > 0.0)
+     {
+     if (t_minor > 1.0)
+        {
+        r_major = t_major;
+        r_minor = t_minor;
+        r_radius = t_major;
+        }
+     else
+     if (t_minor > 0.0)
+        {
+        r_major = t_major;
+        r_radius = t_major;
+        r_minor = (Math.sqrt(1.0 - t_minor)) * t_major;
+        }
+     else
+        {
+        r_major = t_major;
+        r_radius = t_major;
+        r_minor = t_major;
+        }
+     }
+   else
+   if (t_minor > 0.0)   /* t_major = 0 */
+     {
+     r_major = major_data[0];
+     r_radius = major_data[0];
+     r_minor = minor_data[0];
+     }
+   else
+     {
+     r_major = major_data[DATMCT - 1];
+     r_radius = major_data[DATMCT - 1];
+     r_minor = 6370997.0;
+     }
+  }
+else            /* isph >= 0 */
+  {
+  jsph = Math.abs(isph);
+  if (jsph > 19)
+     {
+     // p_error("Invalid spheroid selection","INFORMATIONAL");
+     // p_error("Reset to 0","INFORMATIONAL");
+     isph = 1;
+     jsph = 0;
+     }
+  r_major = major_data[(int)jsph];
+  r_minor = minor_data[(int)jsph];
+  r_radius = major_data[DATMCT - 1];
+  }
+
+  major[0] = r_major;
+  minor[0] = r_minor;
+  radius[0] = r_radius;
+
+return(0);
+}
+
+/*******************************************************************************
+NAME                            PAKSZ
+
+PURPOSE:        This function converts a packed DMS angle to seconds.  The
+                standard packed DMS format is:
+
+                degrees * 1000000 + minutes * 1000 + seconds
+
+                Example:        ang = 120025045.25 yields
+                                deg = 120
+                                min = 25
+                                sec = 45.25
+
+                The algorithm used for the conversion is as follows:
+
+                1.  The absolute value of the angle is used.
+
+                2.  The degrees are separated out:
+                    deg = ang/1000000   (fractional portion truncated)
+
+                3.  The minutes are separated out:
+                    min = (ang - deg * 1000000) / 1000     (fractional
+                                                        portion truncated)
+
+                4.  The seconds are then computed:
+                    sec = ang - deg * 1000000 - min * 1000
+
+                5.  The total angle in seconds is computed:
+                    sec = deg * 3600.0 + min * 60.0 + sec
+
+                6.  The sign of sec is set to that of the input angle.
+
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan             MARCH, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Proffesional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+/* Convert DMS packed angle into deg
+----------------------------------*/
+
+static int paksz( double ang,        // angle in DMS
+                  double[] Ddeg        // fractional degrees
+                              )
+{
+
+double fac;             /* sign flag                    */
+double deg;             /* degree variable              */
+double min;             /* minute variable              */
+double sec;             /* seconds variable             */
+double tmp;             /* temporary variable           */
+long i;                 /* temporary variable           */
+
+
+if (ang < 0.0)
+   fac = -1;
+else
+   fac = 1;
+
+/* find degrees
+-------------*/
+sec = Math.abs(ang);
+tmp = 1000000.0;
+i = (long) (sec/tmp);
+if (i > 360)
+  {
+  //p_error("Illegal DMS field","paksz-deg");
+  return(-1);
+  }
+else
+  deg = i;
+
+/* find minutes
+-------------*/
+sec = sec - deg * tmp;
+tmp = 1000;
+i = (long) (sec / tmp);
+if (i > 60)
+  {
+  //p_error("Illegal DMS field","paksz-min");
+  return(-1);
+  }
+else
+  min = i;
+
+/* find seconds
+-------------*/
+sec = sec - min * tmp;
+if (sec > 60)
+  {
+  //p_error("Illegal DMS field","paksz-sec");
+  return(-1);
+  }
+else
+  sec = fac * (deg * 3600.0 + min * 60.0 + sec);
+deg = sec / 3600.0;
+
+Ddeg[0] = deg;
+return(0);
+}
+
+/* Function to return the sign of an argument
+  ------------------------------------------*/
+public static int sign( double x
+                                  )
+{
+  if (x < 0.0 ) {
+    return(-1);
+  }
+  else {
+    return(1);
+  }
+}
+
+/* Function to adjust a longitude angle to range from -180 to 180 radians
+   added if statments
+  -----------------------------------------------------------------------*/
+
+public static double[] adjust_lon( double[] x     // array of angles in radians
+                                              )
+{
+
+long temp;
+long count = 0;
+
+int length = x.length;
+
+for ( int ii = 0; ii < length; ii++ )
+{
+   for(;;)
+   {
+     if (Math.abs(x[ii])<=PI)
+        break;
+     else
+     if (((long) Math.abs(x[ii] / PI)) < 2)
+        x[ii] = x[ii]-(sign(x[ii]) *TWO_PI);
+     else
+     if (((long) Math.abs(x[ii] / TWO_PI)) < MAXLONG)
+        {
+        x[ii] = x[ii]-(((long)(x[ii] / TWO_PI))*TWO_PI);
+        }
+     else
+     if (((long) Math.abs(x[ii] / (MAXLONG * TWO_PI))) < MAXLONG)
+        {
+        x[ii] = x[ii]-(((long)(x[ii] / (MAXLONG * TWO_PI))) * (TWO_PI * MAXLONG));
+        }
+     else
+     if (((long) Math.abs(x[ii] / (DBLLONG * TWO_PI))) < MAXLONG)
+        {
+        x[ii] = x[ii]-(((long)(x[ii] / (DBLLONG * TWO_PI))) * (TWO_PI * DBLLONG));
+        }
+     else
+        x[ii] = x[ii]-(sign(x[ii]) *TWO_PI);
+     count++;
+     if (count > MAX_VAL)
+        break;
+   }
+}
+
+return(x);
+}
+
+public static double adjust_lon( double x     //  angle in radians
+                                           )
+{
+
+long temp;
+long count = 0;
+
+   for(;;)
+   {
+     if (Math.abs(x)<=PI)
+        break;
+     else
+     if (((long) Math.abs(x / PI)) < 2)
+        x = x - (sign(x) *TWO_PI);
+     else
+     if (((long) Math.abs(x / TWO_PI)) < MAXLONG)
+        {
+        x = x-(((long)(x / TWO_PI))*TWO_PI);
+        }
+     else
+     if (((long) Math.abs(x / (MAXLONG * TWO_PI))) < MAXLONG)
+        {
+        x = x - (((long)(x / (MAXLONG * TWO_PI))) * (TWO_PI * MAXLONG));
+        }
+     else
+     if (((long) Math.abs(x / (DBLLONG * TWO_PI))) < MAXLONG)
+        {
+        x = x - (((long)(x / (DBLLONG * TWO_PI))) * (TWO_PI * DBLLONG));
+        }
+     else
+        x = x - (sign(x) *TWO_PI);
+     count++;
+     if (count > MAX_VAL)
+        break;
+   }
+
+return(x);
+}
+
+public static void sincos( double[] val,
+                           double[] sin_val,
+                           double[] cos_val
+                                             )
+{
+
+  int length = val.length;
+
+  for ( int ii = 0; ii < length; ii++ )
+  {
+    sin_val[ii] = Math.sin(val[ii]);
+    cos_val[ii] = Math.cos(val[ii]);
+  }
+
+  return;
+}
+
+public static void sincos( double val,
+                           Double sin_val,
+                           Double cos_val
+                                             )
+{
+
+
+    double sin = Math.sin(val);
+    double cos = Math.cos(val);
+
+    sin_val = new Double( sin );
+    cos_val = new Double( cos );
+
+  return;
+}
+
+
+/* Function to eliminate roundoff errors in asin
+----------------------------------------------*/
+public static double asinz ( double con
+                                         )
+{
+ if (Math.abs(con) > 1.0)
+ {
+   if (con > 1.0) {
+     con = 1.0;
+   }
+   else {
+     con = -1.0;
+   }
+ }
+
+ con = Math.asin(con);
+
+ return con;
+}
+
+/* Function to compute the constant e4 from the input of the eccentricity
+   of the spheroid, x.  This constant is used in the Polar Stereographic
+   projection.
+--------------------------------------------------------------------*/
+public static double e4fn( double x         //eccentricity
+                                     )
+{
+ double con;
+ double com;
+ con = 1.0 + x;
+ com = 1.0 - x;
+ return (Math.sqrt((Math.pow(con,con))*(Math.pow(com,com))));
+}
+
+/* Function to compute the constant small m which is the radius of
+   a parallel of latitude, phi, divided by the semimajor axis.
+---------------------------------------------------------------*/
+public static double msfnz ( double eccent,
+                             double sinphi,
+                             double cosphi
+                                            )
+{
+  double con;
+
+  con = eccent * sinphi;
+  return((cosphi / (Math.sqrt (1.0 - con * con))));
+}
+
+/* Function to compute the constant small t for use in the forward
+   computations in the Lambert Conformal Conic and the Polar
+   Stereographic projections.
+--------------------------------------------------------------*/
+public static double tsfnz( double eccent,     // Eccentricity of the spheroid
+                            double phi,        // Latitude phi
+                            double sinphi      // Sine of the latitude
+                                            )
+{
+  double con;
+  double com;
+
+  con = eccent * sinphi;
+  com = .5 * eccent;
+  con = Math.pow(((1.0 - con) / (1.0 + con)),com);
+  return (Math.tan(.5 * (HALF_PI - phi))/con);
+}
+
+/* Function to compute the latitude angle, phi2, for the inverse of the
+   Lambert Conformal Conic and Polar Stereographic projections.
+----------------------------------------------------------------*/
+public static double phi2z( double eccent,       // Spheroid eccentricity
+                            double ts            // Constant value t
+                                           )
+{
+double eccnth;
+double phi;
+double con;
+double dphi;
+double sinpi;
+int i;
+  eccnth = .5 * eccent;
+  phi = HALF_PI - 2 * Math.atan(ts);
+  for (i = 0; i <= 15; i++)
+  {
+    sinpi = Math.sin(phi);
+    con = eccent * sinpi;
+    dphi = HALF_PI - 2 * Math.atan(ts *(Math.pow(((1.0 - con)/(1.0 + con)),eccnth))) -
+           phi;
+    phi += dphi;
+    if (Math.abs(dphi) <= .0000000001)
+       return(phi);
+  }
+
+  return Double.NaN;
+}
+
+} /* end class */
diff --git a/visad/data/hdfeos/GctpMap.java b/visad/data/hdfeos/GctpMap.java
new file mode 100644
index 0000000..302ac83
--- /dev/null
+++ b/visad/data/hdfeos/GctpMap.java
@@ -0,0 +1,175 @@
+//
+// GctpMap.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.*;
+
+public class GctpMap
+{
+  double[] projparms;
+  public int projcode;
+  Unit[] setUnits = null;
+  int zonecode;
+  int sphrcode;
+  int xdimsize;
+  int ydimsize;
+  double[] uprLeft;
+  double[] lwrRight;
+
+  public GctpMap(  int projcode,
+                   int zonecode,
+                   int sphrcode,
+                   int xdimsize,
+                   int ydimsize,
+                   double[] projparms,
+                   double[] uprLeft,
+                   double[] lwrRight )
+  {
+    this.projcode = projcode;
+    this.projparms = projparms;
+    this.zonecode = zonecode;
+    this.sphrcode = sphrcode;
+    this.xdimsize = xdimsize;
+    this.ydimsize = ydimsize;
+    this.uprLeft = uprLeft;
+    this.lwrRight = lwrRight;
+  }
+
+  public CoordinateSystem getVisADCoordinateSystem()
+         throws VisADException
+  {
+    CoordinateSystem coord_sys = null;
+    RealTupleType Reference;
+
+    double[] r_major = new double[1];
+    double[] r_minor = new double[1];
+    double[] radius =  new double[1];
+    double[] center_lon = new double[1];
+    double[] center_lat = new double[1];
+    double[] lat_1 = new double[1];
+    double[] lat_2 = new double[1];
+    int stat;
+
+    double false_easting = projparms[6];
+    double false_northing = projparms[7];
+
+    RealType r_lat = RealType.Latitude;
+    RealType r_lon = RealType.Longitude;
+    RealType[] components = { r_lon, r_lat };
+    Reference = new RealTupleType( components );
+
+    GctpFunction.sphdz( sphrcode, projparms, r_major, r_minor, radius );
+
+    switch  ( projcode )
+    {
+      case GctpFunction.LAMAZ:
+        stat = GctpFunction.paksz( projparms[4], center_lon );
+        if ( stat != 0 ) {
+           // error?
+        }
+        stat = GctpFunction.paksz( projparms[5], center_lat );
+        if ( stat != 0 ) {
+           // error?
+        }
+
+        coord_sys = new LambertAzimuthalEqualArea( Reference,
+                                                   radius[0],
+                                                   center_lon[0],
+                                                   center_lat[0],
+                                                   false_easting,
+                                                   false_northing );
+        break;
+      case GctpFunction.PS:
+
+        stat = GctpFunction.paksz( projparms[4], center_lon );
+        if ( stat !=0 ) {
+          // error
+        }
+        stat = GctpFunction.paksz( projparms[5], lat_1 );
+        if ( stat !=0 ) {
+          // error
+        }
+
+        coord_sys = new PolarStereographic( Reference,
+                                            r_major[0],
+                                            r_minor[0],
+                                            center_lon[0],
+                                            lat_1[0],
+                                            false_easting,
+                                            false_northing );
+        break;
+      case GctpFunction.LAMCC:
+
+        coord_sys = new LambertConformalConic( Reference,
+                                               r_major[0],
+                                               r_minor[0],
+                                               lat_1[0],
+                                               lat_2[0],
+                                               center_lon[0],
+                                               center_lat[0],
+                                               false_easting,
+                                               false_northing );
+        break;
+      case GctpFunction.GEO:
+
+        uprLeft[0] = uprLeft[0]*1E-06;
+        lwrRight[0] = lwrRight[0]*1E-06;
+        uprLeft[1] = uprLeft[1]*1E-06;
+        lwrRight[1] = lwrRight[1]*1E-06;
+        setUnits = new Unit[2];
+        setUnits[0] = CommonUnit.degree;
+        setUnits[1] = CommonUnit.degree;
+
+      default:
+
+    }
+    return coord_sys;
+  }
+
+  public Unit[] getUnits()
+  {
+    return setUnits;
+  }
+
+  public Set getVisADSet( MathType map )
+         throws VisADException
+  {
+    int length1 = xdimsize;
+    int length2 = ydimsize;
+    Set VisADset;
+
+    if ( projcode == GctpFunction.GEO ) {
+      VisADset = new LinearLatLonSet( map, uprLeft[0], lwrRight[0], length1,
+                                           lwrRight[1], uprLeft[1] , length2 );
+    }
+    else {
+      VisADset = new Linear2DSet( map, uprLeft[0], lwrRight[0], length1,
+                                       lwrRight[1], uprLeft[1] , length2 );
+    }
+    return VisADset;
+  }
+}
diff --git a/visad/data/hdfeos/GeoMap.java b/visad/data/hdfeos/GeoMap.java
new file mode 100644
index 0000000..9e67d0b
--- /dev/null
+++ b/visad/data/hdfeos/GeoMap.java
@@ -0,0 +1,42 @@
+//
+// GeoMap.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+public class GeoMap
+{
+  String  toDim;
+  String  fromDim;
+  int  offset;
+  int  increment;
+
+  GeoMap( String toDim, String fromDim, int offset, int increment ) {
+     this.toDim = toDim;
+     this.fromDim = fromDim;
+     this.offset = offset;
+     this.increment = increment;
+  }
+}
diff --git a/visad/data/hdfeos/GeoMapSet.java b/visad/data/hdfeos/GeoMapSet.java
new file mode 100644
index 0000000..d7bc226
--- /dev/null
+++ b/visad/data/hdfeos/GeoMapSet.java
@@ -0,0 +1,93 @@
+//
+// GeoMapSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+
+
+public class GeoMapSet
+{
+  Vector mapSet;
+
+  public GeoMapSet()
+  {
+    mapSet = new Vector();
+  }
+
+  public void add( GeoMap obj )
+  {
+    mapSet.addElement( obj );
+  }
+
+  public int getSize()
+  {
+    int size = mapSet.size();
+    return size;
+  }
+
+  public GeoMap getElement( int ii )
+  {
+    if ( mapSet.size() == 0 )
+    {
+      return null;
+    }
+    else
+    {
+      GeoMap obj = (GeoMap) mapSet.elementAt(ii);
+      return obj;
+    }
+  }
+
+  public GeoMap getGeoMap( NamedDimension obj )
+  {
+    String name = obj.getName();
+    return getGeoMap( name );
+  }
+
+  public GeoMap getGeoMap( String name )
+  {
+    int size = this.getSize();
+
+    if ( size == 0 )
+    {
+      return null;
+    }
+    else
+    {
+      for ( int ii = 0; ii < size; ii++ )
+      {
+         GeoMap obj = (GeoMap) mapSet.elementAt(ii);
+
+         if(( obj.toDim.equals( name ) ) || ( obj.fromDim.equals( name ) ))
+         {
+           return obj;
+         }
+      }
+      return null;
+    }
+  }
+}
diff --git a/visad/data/hdfeos/Hdfeos.java b/visad/data/hdfeos/Hdfeos.java
new file mode 100644
index 0000000..1e8af45
--- /dev/null
+++ b/visad/data/hdfeos/Hdfeos.java
@@ -0,0 +1,73 @@
+//
+// hdfeos.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.io.IOException;
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormFileInformer;
+import visad.DataImpl;
+import visad.VisADException;
+
+public abstract class Hdfeos
+       extends Form
+       implements FormFileInformer
+{
+  public Hdfeos( String name )
+  {
+    super( name );
+  }
+
+  public boolean isThisType(String name)
+  {
+    String[] suff_s = getDefaultSuffixes();
+    for ( int ii = 0; ii < suff_s.length; ii++ )
+    {
+      if ( name.endsWith("."+suff_s[ii]) ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public boolean isThisType(byte[] block)
+  {
+    String first_four_bytes = new String( block, 0, 20 );
+    return first_four_bytes.startsWith("ASCN");
+  }
+
+  public String[] getDefaultSuffixes()
+  {
+    String[] suffs = { "hdf",
+                       "hdfeos",
+                       "eos" };
+    return suffs;
+  }
+
+  public abstract DataImpl open( String file_path )
+     throws BadFormException, IOException, VisADException;
+}
diff --git a/visad/data/hdfeos/HdfeosAccessor.java b/visad/data/hdfeos/HdfeosAccessor.java
new file mode 100644
index 0000000..53f8f34
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosAccessor.java
@@ -0,0 +1,70 @@
+//
+// HdfeosAccessor.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.*;
+import visad.data.*;
+import java.rmi.*;
+
+public class HdfeosAccessor extends FileAccessor
+{
+   private HdfeosData data;
+   private int[] indexes;
+
+   public HdfeosAccessor( HdfeosData data, int[] indexes )
+   {
+     this.data = data;
+     this.indexes = indexes;
+   }
+
+   public FlatField getFlatField()
+          throws VisADException, RemoteException
+   {
+     return (FlatField) data.getData(indexes);
+   }
+
+   public FunctionType getFunctionType()
+          throws VisADException
+   {
+     return (FunctionType) data.getType();
+   }
+
+   public void writeFile( int[] fileLocations, Data range )
+   {
+
+   }
+
+   public double[][] readFlatField( FlatField template, int[] fileLocation )
+   {
+     return null;
+   }
+
+   public void writeFlatField( double[][] values, FlatField template, int[] fileLocation )
+   {
+
+   }
+}
diff --git a/visad/data/hdfeos/HdfeosAdaptedForm.java b/visad/data/hdfeos/HdfeosAdaptedForm.java
new file mode 100644
index 0000000..3463810
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosAdaptedForm.java
@@ -0,0 +1,45 @@
+//
+// HdfeosAdaptedForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.DataImpl;
+import visad.VisADException;
+import java.rmi.*;
+
+public class HdfeosAdaptedForm extends HdfeosForm
+{
+  public HdfeosAdaptedForm()
+  {
+    super("AdaptedDefault");
+  }
+
+  DataImpl getVisADDataObject( HdfeosData h_data )
+           throws VisADException, RemoteException
+  {
+    return h_data.getAdaptedData();
+  }
+}
diff --git a/visad/data/hdfeos/HdfeosData.java b/visad/data/hdfeos/HdfeosData.java
new file mode 100644
index 0000000..961056a
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosData.java
@@ -0,0 +1,48 @@
+//
+// HdfeosData.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.rmi.*;
+import visad.*;
+
+abstract class HdfeosData
+{
+  public abstract MathType getType()
+         throws VisADException;
+
+  public abstract DataImpl getData()
+         throws VisADException, RemoteException;
+
+  public abstract DataImpl getData(int[] indexes)
+         throws VisADException, RemoteException;
+
+  public abstract DataImpl getAdaptedData()
+         throws VisADException, RemoteException;
+
+  public abstract DataImpl getAdaptedData(int[] indexes)
+         throws VisADException, RemoteException;
+}
diff --git a/visad/data/hdfeos/HdfeosDomain.java b/visad/data/hdfeos/HdfeosDomain.java
new file mode 100644
index 0000000..c54a56a
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosDomain.java
@@ -0,0 +1,471 @@
+//
+// HdfeosDomain.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.Set;
+import visad.MathType;
+import visad.RealType;
+import visad.GriddedSet;
+import visad.Gridded1DSet;
+import visad.IntegerNDSet;
+import visad.LinearNDSet;
+import visad.SampledSet;
+import visad.ProductSet;
+import visad.SetException;
+import visad.VisADException;
+import visad.RealTupleType;
+import visad.CoordinateSystem;
+import visad.GridCoordinateSystem;
+import visad.Unit;
+
+class HdfeosDomain
+{
+  private EosStruct struct;
+
+  final static int INTEGER = 0;
+  final static int HYBRID = 1;         // product of next two
+  final static int FACTORED = 2;       // aligned with R^N
+  final static int FACTORED_ARITH = 3; //
+  final static int UNFACTORED = 4;     // non-aligned with R^N
+  final static int LINEAR = 5;
+  final static int SINGLE = 6;
+
+  private int op;
+  private NamedDimension[] domDims;
+  private DimensionSet domDimSet = null;
+
+  private Variable[] domVars;
+
+  private int domainDim;
+  private int manifoldDim;
+  private int[] lengths = null;
+  private int[] inv_lengths = null;
+  private int[] num_type = null;
+  private Calibration[] cal = null;
+  private int n_samples;
+  private float[][] samples;
+  private String[] name_s = null;
+  MathType mathtype = null;
+  private CoordinateSystem coord_sys = null;
+  private Unit[] units = null;
+  private Set domainSet = null;
+  private HdfeosDomain gridCoordSys = null;
+  private boolean subRank;
+
+  private int[] start = null;
+  private int[] edge = null;
+  private int[] stride = null;
+
+  public HdfeosDomain( EosStruct struct,
+                       DimensionSet dimSet)
+         throws VisADException
+  {
+    this(struct, dimSet.getElements(), null, null);
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       DimensionSet dimSet,
+                       CoordinateSystem coord_sys )
+         throws VisADException
+  {
+    this(struct, dimSet.getElements(), coord_sys, null);
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       DimensionSet dimSet,
+                       CoordinateSystem coord_sys,
+                       Unit[] units )
+         throws VisADException
+  {
+    this(struct, dimSet.getElements(), coord_sys, units);
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       NamedDimension dim )
+         throws VisADException
+  {
+    NamedDimension[] dims = {dim};
+    initializeNoVars( struct, dims );
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       NamedDimension[] dims,
+                       CoordinateSystem coord_sys,
+                       Unit[] units)
+         throws VisADException
+  {
+    this.coord_sys = coord_sys;
+    this.units = units;
+    initializeNoVars( struct, dims );
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       DimensionSet dimSet,
+                       HdfeosDomain gridCoordSys )
+         throws VisADException
+  {
+    this.coord_sys = getNullGridCoordinateSystem(gridCoordSys);
+    this.gridCoordSys = gridCoordSys;
+    initializeNoVars( struct, dimSet.getElements() );
+  }
+
+  public static GridCoordinateSystem
+         getNullGridCoordinateSystem( HdfeosDomain gridCoordSys )
+         throws VisADException
+  {
+    RealTupleType reference = (RealTupleType)gridCoordSys.getType();
+    int dim = reference.getDimension();
+    int[] lens = new int[dim];
+    for ( int ii = 0; ii < dim; ii++ ) {
+      lens[ii] = 2;
+    }
+    IntegerNDSet set = new IntegerNDSet(reference, lens);
+    GridCoordinateSystem c_sys = new GridCoordinateSystem(set);
+    return c_sys;
+  }
+
+  private void initializeNoVars( EosStruct struct,
+                                 NamedDimension[] dims)
+          throws VisADException
+  {
+    this.struct = struct;
+    domDims = dims;
+    domainDim = dims.length;
+    lengths = new int[ domainDim ];
+    inv_lengths = new int[ domainDim ];
+    manifoldDim = domainDim;
+    name_s = new String[ domainDim ];
+    this.domDimSet = new DimensionSet(dims);
+    if ( units == null ) {
+      units = new Unit[domainDim];
+    }
+
+    start = new int[ manifoldDim ];
+    stride = new int[ manifoldDim ];
+    edge = new int[ manifoldDim ];
+    n_samples = 1;
+    for ( int ii = 0; ii < domainDim; ii++ )
+    {
+      name_s[ii] = domDims[ii].getName();
+      lengths[ii] = domDims[ii].getLength();
+      n_samples *= lengths[ii];
+      start[ii] = 0;
+      edge[ii] = lengths[ii];
+      stride[ii] = 1;
+    }
+    for ( int kk = 0; kk < domainDim; kk++ )
+    {
+      inv_lengths[kk] = lengths[(domainDim-1)-kk];
+    }
+    op = LINEAR;
+    subRank = false;
+
+    mathtype = makeType(null);
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       Variable[] vars,
+                       NamedDimension[] dims )
+         throws VisADException
+  {
+    initialize( struct, vars, dims );
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       VariableSet v_set,
+                       DimensionSet d_set )
+         throws VisADException
+  {
+    domDimSet = d_set;
+    initialize(struct, v_set.getElements(), d_set.getElements());
+  }
+
+  public HdfeosDomain( EosStruct struct,
+                       Variable var )
+         throws VisADException
+  {
+    Variable[] vars = new Variable[1];
+    NamedDimension[] dims = new NamedDimension[1];
+    vars[0] = var;
+    dims[0] = var.getDim(0);
+
+    initialize(struct, vars, dims);
+  }
+
+  private void initialize( EosStruct struct,
+                           Variable[] vars,
+                           NamedDimension[] dims )
+          throws VisADException
+  {
+    this.struct = struct;
+    domVars = vars;
+    int n_vars = vars.length;
+    this.num_type = new int[n_vars];
+    this.cal = new Calibration[n_vars];
+    domDims = dims;
+    int n_dims = dims.length;
+    boolean all_1D = false;
+    boolean one_1D = false;
+    boolean all_eq = true;
+
+    domainDim = vars.length;
+    manifoldDim = dims.length;
+
+    if ( domDimSet == null ) {
+      domDimSet = new DimensionSet( dims );
+    }
+
+    name_s = new String[ domainDim ];
+    if ( units == null ) {
+      units = new Unit[domainDim];
+    }
+
+    if (domainDim == 1)
+    {
+      op = SINGLE;
+      subRank = false;
+      start = new int[1];
+      edge = new int[1];
+      stride = new int[1];
+      name_s[0] = domVars[0].getName();
+      num_type[0] = domVars[0].getNumberType();
+      cal[0] = domVars[0].getCalibration();
+    }
+    else
+    {
+      int v_rank = 1;
+      int v_rank0 = domVars[0].getRank();
+      for ( int ii = 0; ii < n_vars; ii++ )
+      {
+        v_rank = domVars[ii].getRank();
+        if ( n_dims != 1 )
+        {
+          all_1D = false;
+          if ( v_rank != v_rank0 )
+          {
+            all_eq = false;
+          }
+        }
+        else
+        {
+          one_1D = true;
+        }
+        name_s[ii] = domVars[ii].getName();
+        num_type[ii] = domVars[ii].getNumberType();
+        cal[ii] = domVars[ii].getCalibration();
+      }
+
+      if ( all_1D )
+      {
+        op = FACTORED;
+        subRank = false;
+        start = new int[1];
+        edge = new int[1];
+        stride = new int[1];
+      }
+      else if ( all_eq )
+      {
+        if ( v_rank > manifoldDim ) {
+          subRank = true;
+        }
+        else if ( v_rank == manifoldDim ) {
+          subRank = false;
+        }
+        else {
+          throw new HdfeosException("variables rank cannot be greater"+
+                                    " than manifoldDim" );
+        }
+        op = UNFACTORED;
+        start = new int[v_rank];
+        stride = new int[v_rank];
+        edge = new int[v_rank];
+      }
+      else
+      {
+        throw new HdfeosException("undefined domain case");
+      }
+    }
+
+    lengths = new int[ manifoldDim ];
+    inv_lengths = new int[ manifoldDim];
+    n_samples = 1;
+    for ( int ii = 0; ii < manifoldDim; ii++ )
+    {
+      lengths[ii] = domDims[ii].getLength();
+      n_samples *= lengths[ii];
+    }
+    for ( int kk = 0; kk < manifoldDim; kk++ )
+    {
+      inv_lengths[kk] = lengths[(manifoldDim-1)-kk];
+    }
+    samples = new float[domainDim][n_samples];
+    mathtype = makeType(null);
+  }
+
+  public DimensionSet getDimSet()
+  {
+    return domDimSet;
+  }
+
+  MathType makeType(CoordinateSystem coord_sys)
+           throws VisADException
+  {
+    int inv_ii;
+    RealType[] r_types = new RealType[domainDim];
+    for ( int ii = 0; ii < domainDim; ii++ ) {
+      inv_ii = (domainDim-1) - ii;
+      r_types[ii] = RealType.getRealType(name_s[inv_ii], units[inv_ii], null);
+    }
+    if ( r_types.length == 1 ) {
+      return r_types[0];
+    }
+    else {
+      if ( coord_sys == null ) {
+        return new RealTupleType(r_types, this.coord_sys, null);
+      }
+      else {
+        return new RealTupleType(r_types, coord_sys, null);
+      }
+    }
+  }
+
+  public MathType getType()
+         throws VisADException
+  {
+    return mathtype;
+  }
+
+  public Set getData()
+         throws VisADException
+  {
+    if ( !subRank ) {
+      return getData(null);
+    }
+    else {
+      throw new HdfeosException("getData(int[] indexes) must be used");
+    }
+  }
+
+  public Set getData( int[] indexes )
+         throws VisADException
+  {
+    int cnt = 0;
+    if ( indexes == null )
+    {
+      if ( subRank ) {
+        throw new HdfeosException("indexes cannot be null");
+      }
+      else if ( domainSet != null ) {
+        return domainSet;
+      }
+    }
+    else
+    {
+      if ( !subRank ) {
+        throw new HdfeosException("getData() must be used");
+      }
+      for ( int ii = 0; ii < indexes.length; ii++ ) {
+        start[cnt] = indexes[ii];
+        edge[cnt] = 1;
+        cnt++;
+      }
+    }
+    Set set = null;
+    switch (op)
+    {
+      case UNFACTORED:
+        for ( int kk = 0; kk < domainDim; kk++ )
+        {
+          for ( int ii = 0; ii < manifoldDim; ii++ ) {
+            start[cnt+ii] = 0;
+            edge[cnt+ii] = lengths[ii];
+            stride[cnt+ii] = 1;
+          }
+          struct.readData(name_s[kk], start, stride, edge,
+                          num_type[kk], cal[kk], samples[kk] );
+        }
+        set = GriddedSet.create(mathtype, samples, inv_lengths);
+     //-set = new GriddedSet(mathtype, samples, inv_lengths);
+        break;
+
+      case SINGLE:
+        struct.readData(name_s[0], start, stride, edge,
+                        num_type[0], cal[0], samples[0]);
+        set = new Gridded1DSet(mathtype, samples, lengths[0]);
+        break;
+
+      case LINEAR:
+        if ( gridCoordSys == null ) {
+          set = new IntegerNDSet(mathtype, lengths, null, null, null);
+        }
+        else {
+          GriddedSet geo_domain;
+          try {
+            geo_domain = (GriddedSet) gridCoordSys.getData(indexes);
+          }
+          catch ( SetException e ) {
+            System.out.println( (gridCoordSys.getType()).toString()+" "
+                               +e.getMessage());
+            set = new IntegerNDSet(mathtype, lengths, null, null, null);
+            break;
+          }
+          int[] lens = geo_domain.getLengths();
+          double[] firsts = new double[domainDim];
+          double[] lasts = new double[domainDim];
+          for ( int ii = 0; ii < domainDim; ii++ ) {
+            firsts[ii] = 0;
+             lasts[ii] = lens[ii];
+          }
+          GridCoordinateSystem c_sys = new GridCoordinateSystem(geo_domain);
+          set = new LinearNDSet(mathtype, firsts, lasts, lengths, c_sys, null, null);
+        }
+        break;
+
+      case FACTORED:
+        SampledSet[] sets = new SampledSet[domainDim];
+        for ( int kk = 0; kk < domainDim; kk++ ) {
+          start[0] = 0;
+          edge[0] = lengths[kk];
+          stride[0] = 1;
+          struct.readData(name_s[kk], start, stride, edge,
+                          num_type[kk], cal[kk], samples[kk]);
+          sets[kk] = new Gridded1DSet(mathtype, samples, lengths[kk]);
+        }
+        set = new ProductSet(mathtype, sets);
+        break;
+    }
+    if ( !subRank ) {
+      domainSet = set;
+    }
+    return set;
+  }
+
+  public EosStruct getStruct()
+  {
+     return struct;
+  }
+}
diff --git a/visad/data/hdfeos/HdfeosDomainMap.java b/visad/data/hdfeos/HdfeosDomainMap.java
new file mode 100644
index 0000000..7a5a4bd
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosDomainMap.java
@@ -0,0 +1,63 @@
+//
+// HdfeosDomainMap.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.Set;
+import visad.VisADException;
+
+public class HdfeosDomainMap extends HdfeosDomain
+{
+  private GctpMap gridMap;
+  private Set set = null;
+
+  public HdfeosDomainMap( EosStruct struct,
+                          DimensionSet dimSet,
+                          GctpMap gridMap  )
+         throws VisADException
+  {
+    super(struct, dimSet, gridMap.getVisADCoordinateSystem(),
+                          gridMap.getUnits());
+    this.gridMap = gridMap;
+  }
+
+  public Set getData( )
+         throws VisADException
+  {
+    set = gridMap.getVisADSet(mathtype);
+    return set;
+  }
+
+  public Set getData( int[] indexes )
+         throws VisADException
+  {
+    if ( set == null )
+    {
+      set = gridMap.getVisADSet(mathtype);
+    }
+    return set;
+  }
+}
diff --git a/visad/data/hdfeos/HdfeosException.java b/visad/data/hdfeos/HdfeosException.java
new file mode 100644
index 0000000..71fa777
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosException.java
@@ -0,0 +1,41 @@
+//
+// HdfeosException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+/**
+   HdfeosException is the superclass of all exceptions defined within the
+   hdfeos package.<P>
+*/
+
+import visad.VisADException;
+
+public class HdfeosException extends VisADException {
+
+  public HdfeosException() { super(); }
+  public HdfeosException(String s) { super(s); }
+
+}
diff --git a/visad/data/hdfeos/HdfeosField.java b/visad/data/hdfeos/HdfeosField.java
new file mode 100644
index 0000000..a10d0fb
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosField.java
@@ -0,0 +1,129 @@
+//
+// HdfeosField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.rmi.*;
+import visad.*;
+
+public class HdfeosField extends HdfeosData
+{
+  HdfeosDomain domain;
+  HdfeosData range;
+  FunctionType mathtype;
+  EosStruct struct;
+
+  HdfeosField( HdfeosDomain domain, HdfeosData range )
+    throws VisADException, RemoteException
+  {
+    this.domain = domain;
+    this.range = range;
+    this.struct = domain.getStruct();
+
+    mathtype = makeType();
+  }
+
+  public MathType getType()
+         throws VisADException
+  {
+    return mathtype;
+  }
+
+  public DataImpl getData()
+         throws VisADException, RemoteException
+  {
+    Set domain = (Set) (this.domain).getData();
+    int length = domain.getLength();
+    int[] indexes;
+
+    FieldImpl new_field = new FieldImpl(mathtype, domain);
+    for ( int ii = 0; ii < length; ii++ ) {
+      indexes = new int[1];
+      indexes[0] = ii;
+      new_field.setSample(ii, range.getData(indexes));
+    }
+    return new_field;
+  }
+
+  public DataImpl getData( int[] indexes )
+         throws VisADException, RemoteException
+  {
+    Set domain = (Set) this.domain.getData(indexes);
+    int length = domain.getLength();
+    int n_indexes = indexes.length;
+    int[] new_indexes = new int[n_indexes + 1];
+    System.arraycopy(indexes, 0, new_indexes, 0, n_indexes);
+
+    FieldImpl new_field = new FieldImpl(mathtype, domain);
+    for ( int ii = 0; ii < length; ii++ ) {
+      new_indexes[n_indexes] = ii;
+      new_field.setSample(ii, range.getData(new_indexes));
+    }
+    return new_field;
+  }
+
+  public DataImpl getAdaptedData()
+         throws VisADException, RemoteException
+  {
+    Set domain = (Set) this.domain.getData();
+    int length = domain.getLength();
+    int[] indexes;
+
+    FieldImpl new_field = new FieldImpl(mathtype, domain);
+    for ( int ii = 0; ii < length; ii++ ) {
+      indexes = new int[1];
+      indexes[0] = ii;
+      new_field.setSample(ii, range.getAdaptedData(indexes), false);
+    }
+    return new_field;
+  }
+
+  public DataImpl getAdaptedData( int[] indexes )
+         throws VisADException, RemoteException
+  {
+    Set domain = (Set) this.domain.getData(indexes);
+    int length = domain.getLength();
+    int n_indexes = indexes.length;
+    int[] new_indexes = new int[n_indexes + 1];
+    System.arraycopy(indexes, 0, new_indexes, 0, n_indexes);
+
+    FieldImpl new_field = new FieldImpl(mathtype, domain);
+    for ( int ii = 0; ii < length; ii++ ) {
+      new_indexes[n_indexes] = ii;
+      new_field.setSample(ii, range.getAdaptedData(new_indexes), false);
+    }
+    return new_field;
+  }
+
+  private FunctionType makeType()
+          throws VisADException, RemoteException
+  {
+    MathType domain_type = this.domain.getType();
+    MathType range_type = this.range.getType();
+    FunctionType f_type = new FunctionType(domain_type, range_type);
+    return f_type;
+  }
+}
diff --git a/visad/data/hdfeos/HdfeosFile.java b/visad/data/hdfeos/HdfeosFile.java
new file mode 100644
index 0000000..470dc01
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosFile.java
@@ -0,0 +1,145 @@
+//
+// HdfeosFile.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+import visad.data.hdfeos.hdfeosc.HdfeosLib;
+
+public class HdfeosFile
+{
+  private String filename;
+  private int  file_id;
+  private int[] sd_id = new int[1];
+  private int  n_structs;
+
+  private Vector Structs;
+
+  static Vector openedFiles = new Vector();          // all opened file objects
+
+  HdfeosFile( String filename )
+            throws HdfeosException
+  {
+    this.filename = filename;
+
+    String[] swath_list = {"empty"};
+    int n_swaths = HdfeosLib.SWinqswath( filename, swath_list );
+    n_structs = 0;
+    Structs = new Vector();
+
+    if ( n_swaths > 0 )
+    {
+      file_id = HdfeosLib.SWopen( filename, HdfeosLib.DFACC_READ );
+      if ( file_id < 0 ) {
+        throw new HdfeosException("SWopen:  "+file_id);
+      }
+
+      String struct_name = "Swath";
+      int[] hdf_id = new int[1];
+      byte[] access = new byte[1];
+
+      int stat = HdfeosLib.EHchkfid( file_id, struct_name, hdf_id, sd_id, access);
+      if ( stat < 0 )
+      {
+        throw new HdfeosException("---cannot obtain sdInterfaceId---" );
+      }
+
+      StringTokenizer swaths = new StringTokenizer( swath_list[0], ",", false );
+      while ( swaths.hasMoreElements() )
+      {
+        String swath = (String) swaths.nextElement();
+        EosSwath obj = new EosSwath( file_id, sd_id[0], swath );
+        Structs.addElement( (EosStruct)obj );
+        n_structs++;
+      }
+    }
+
+    String[] grid_list = {"empty"};
+    int n_grids = HdfeosLib.GDinqgrid( filename, grid_list );
+
+    if ( n_grids > 0 )
+    {
+      file_id = HdfeosLib.GDopen( filename, HdfeosLib.DFACC_READ );
+      if ( file_id < 0 ) {
+        throw new HdfeosException("GDopen: "+file_id);
+      }
+
+      StringTokenizer grids = new StringTokenizer( grid_list[0], ",", false );
+
+      while ( grids.hasMoreElements() )
+      {
+        String grid = (String) grids.nextElement();
+        EosGrid g_obj = new EosGrid( file_id, sd_id[0], grid );
+        Structs.addElement( (EosStruct)g_obj );
+        n_structs++;
+      }
+    }
+
+    if ( n_structs == 0 ) {
+      file_id = HdfeosLib.SWopen( filename, HdfeosLib.DFACC_READ );
+      if ( file_id < 0 ) {
+        throw new HdfeosException("can't open file: "+filename);
+      }
+    }
+    else {
+      openedFiles.addElement( this );
+    }
+  }
+
+  public int getNumberOfStructs()
+  {
+    return n_structs;
+  }
+
+  public EosStruct getStruct( int ii )
+  {
+    return (EosStruct) Structs.elementAt(ii);
+  }
+
+  public String getFileName()
+  {
+    return filename;
+  }
+
+  public void close()
+        throws HdfeosException
+  {
+    int status = HdfeosLib.EHclose( file_id );
+    if ( status < 0 ) {
+      throw new HdfeosException("--closing file, "+filename+
+                                ", returned status: "+status+" --");
+    }
+  }
+
+  public static void closeAll()
+         throws HdfeosException
+  {
+    for ( Enumeration e = openedFiles.elements(); e.hasMoreElements(); )
+    {
+      ((HdfeosFile)e.nextElement()).close();
+    }
+  }
+}
diff --git a/visad/data/hdfeos/HdfeosFlatField.java b/visad/data/hdfeos/HdfeosFlatField.java
new file mode 100644
index 0000000..97b8b3b
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosFlatField.java
@@ -0,0 +1,264 @@
+//
+// HdfeosFlatField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.rmi.*;
+import visad.*;
+import visad.data.FileFlatField;
+
+public class HdfeosFlatField extends HdfeosData
+{
+  HdfeosDomain domain;
+  Variable[] range_s;
+  int n_fields;
+  int r_rank;
+  int d_rank;
+  String[] name_s;
+  int num_type[];
+  Calibration[] cal;
+  FunctionType mathtype;
+  EosStruct struct;
+  boolean pointStruct = false;
+
+  int[] start = null;
+  int[] inv_start = null;
+  int[] stride = null;
+  int[] inv_stride = null;
+  int[] edge = null;
+  int[] inv_edge = null;
+
+  DimensionSet rangeDimSet = null;
+  DimensionSet domainDimSet = null;
+
+  FlatField data = null;
+
+  public HdfeosFlatField( HdfeosDomain domain, Variable[] range_s )
+         throws VisADException,
+                UnimplementedException,
+                HdfeosException
+  {
+    initialize( domain, range_s );
+  }
+
+  public HdfeosFlatField( HdfeosDomain domain, Variable range )
+         throws VisADException,
+                UnimplementedException,
+                HdfeosException
+  {
+    Variable[] range_s = { range };
+    initialize( domain, range_s );
+  }
+
+  public HdfeosFlatField( HdfeosDomain domain, VariableSet range_s )
+         throws VisADException,
+                UnimplementedException,
+                HdfeosException
+  {
+    initialize( domain, range_s.getElements() );
+  }
+
+  private void initialize( HdfeosDomain domain,
+                           Variable[] range_s )
+          throws VisADException,
+                 UnimplementedException,
+                 HdfeosException
+  {
+    this.domain = domain;
+    this.struct = domain.getStruct();
+    this.n_fields = range_s.length;
+    this.name_s = new String[n_fields];
+    this.num_type = new int[n_fields];
+    this.cal = new Calibration[n_fields];
+
+    for ( int ii = 0; ii < n_fields; ii++ ) {
+      name_s[ii] = range_s[ii].getName();
+      num_type[ii] = range_s[ii].getNumberType();
+      cal[ii] = range_s[ii].getCalibration();
+    }
+
+    mathtype = makeType();
+
+    if ( (struct instanceof EosSwath) ||
+         (struct instanceof EosGrid) )
+    {
+      this.rangeDimSet = range_s[0].getDimSet();
+      r_rank = rangeDimSet.getSize();
+      domainDimSet = domain.getDimSet();
+      d_rank = domainDimSet.getSize();
+
+      if ( d_rank > r_rank )
+      {
+        throw new HdfeosException("d_rank > r_rank");
+      }
+
+      start = new int[r_rank];
+      inv_start = new int[r_rank];
+      edge = new int[r_rank];
+      inv_edge = new int[r_rank];
+      stride = new int[r_rank];
+      inv_stride = new int[r_rank];
+
+      for ( int ii = 0; ii < r_rank; ii++ ) {
+        NamedDimension n_dim = rangeDimSet.getElement(ii);
+        start[ii] = 0;
+        edge[ii] = n_dim.getLength();
+        stride[ii] = 1;
+      }
+    }
+    else
+    {
+      pointStruct = true;
+      throw new UnimplementedException("ECS structmetadata: POINT");
+    }
+  }
+
+  public MathType getType()
+         throws VisADException
+  {
+    return mathtype;
+  }
+
+  public DataImpl getData()
+         throws VisADException, RemoteException
+  {
+    return getData(null);
+  }
+
+  public DataImpl getAdaptedData()
+         throws VisADException, RemoteException
+  {
+    HdfeosAccessor accessor =
+      new HdfeosAccessor( this, null );
+
+    FileFlatField f_field = new FileFlatField( accessor, HdfeosForm.c_strategy );
+    return f_field;
+  }
+
+  public DataImpl getData( int[] indexes )
+         throws VisADException, RemoteException
+  {
+    Set set = null;
+    if ( ! pointStruct )
+    {
+      if ( indexes == null )
+      {
+        if ( data != null )
+        {
+          return data;
+        }
+        else
+        {
+          set = domain.getData();
+        }
+      }
+      else
+      {
+        if ( d_rank == r_rank ) {
+          set = domain.getData(indexes);
+        }
+        else if ( d_rank < r_rank ) {
+          set = domain.getData();
+        }
+
+        int cnt = 0;
+        for ( int ii = 0; ii < indexes.length; ii++ )
+        {
+          start[cnt] = indexes[ii];
+          edge[cnt] = 1;
+          cnt++;
+        }
+      }
+
+      if ( struct instanceof EosSwath ) {
+        /**- invert dimension order  --*/
+        for ( int ii = 0; ii < r_rank; ii++ )
+        {
+          inv_start[ii] = start[(r_rank-1) - ii];
+          inv_edge[ii] = edge[(r_rank-1) - ii];
+          inv_stride[ii] = stride[(r_rank-1) - ii];
+        }
+      }
+      else {
+        for ( int ii = 0; ii < r_rank; ii++ )
+        {
+          inv_start[ii] = start[ii];
+          inv_edge[ii] = edge[ii];
+          inv_stride[ii] = stride[ii];
+        }
+      }
+
+      FlatField f_field = new FlatField( mathtype, set );
+      int n_samples = set.getLength();
+      float[][] f_array = new float[n_fields][n_samples];
+
+      /**- File I/O   ---*/
+      for ( int kk = 0; kk < n_fields; kk++ )
+      {
+        struct.readData(name_s[kk], inv_start, inv_stride, inv_edge,
+                        num_type[kk], cal[kk], f_array[kk] );
+      }
+
+      f_field.setSamples( f_array );
+      data = f_field;
+
+      return data;
+    }
+    return null;
+  }
+
+  public DataImpl getAdaptedData(int[] indexes)
+         throws VisADException
+  {
+    HdfeosAccessor accessor =
+      new HdfeosAccessor( this, indexes );
+    FileFlatField f_field = new FileFlatField( accessor, HdfeosForm.c_strategy );
+    return f_field;
+  }
+
+  private FunctionType makeType()
+          throws VisADException
+  {
+    MathType domain_type = this.domain.getType();
+    RealType r_type;
+    RealType[] r_types = new RealType[n_fields];
+
+    for ( int ii = 0; ii < n_fields; ii++ )
+    {
+      r_types[ii] = RealType.getRealType( name_s[ii], null, null );
+    }
+
+    MathType range_type;
+    if ( n_fields == 1 ) {
+      range_type = r_types[0];
+    }
+    else {
+      range_type = new RealTupleType(r_types);
+    }
+    FunctionType f_type = new FunctionType(domain_type, range_type);
+    return f_type;
+  }
+}
diff --git a/visad/data/hdfeos/HdfeosForm.java b/visad/data/hdfeos/HdfeosForm.java
new file mode 100644
index 0000000..02d66d7
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosForm.java
@@ -0,0 +1,527 @@
+//
+// HdfeosForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import visad.*;
+import visad.data.*;
+import visad.UnimplementedException;
+import java.rmi.*;
+import java.net.URL;
+
+public class HdfeosForm extends Hdfeos
+{
+  static CacheStrategy c_strategy = new CacheStrategy();
+
+  public HdfeosForm()
+  {
+    super("Default");
+  }
+
+  HdfeosForm( String formName )
+  {
+    super( formName );
+  }
+
+  public DataImpl open( String file_path )
+         throws VisADException, RemoteException
+  {
+    DataImpl data = null;
+
+    HdfeosFile file = new HdfeosFile( file_path );
+
+    data = getFileData( file );
+
+    return data;
+  }
+
+  public DataImpl open( URL url )
+         throws VisADException
+  {
+    throw new UnimplementedException( "HdfeosForm.open( URL url )" );
+  }
+
+  public void add( String id, Data data, boolean replace )
+              throws BadFormException
+  {
+    throw new BadFormException( "HdfeosForm.add" );
+  }
+
+  public void save( String id, Data data, boolean replace )
+         throws BadFormException, RemoteException, VisADException
+  {
+    throw new UnimplementedException( "HdfeosForm.save" );
+  }
+
+  public FormNode getForms( Data data )
+  {
+    return this;
+  }
+
+  MathType getMathType( HdfeosFile file )
+           throws VisADException, RemoteException
+  {
+    MathType M_type = null;
+    HdfeosData data = null;
+
+    int n_structs = file.getNumberOfStructs();
+    if ( n_structs == 0 )
+    {
+       throw new HdfeosException("no HDF-EOS data structures in file: "+file.getFileName());
+    }
+
+    MathType[] types = new MathType[ n_structs ];
+
+    for ( int ii = 0; ii < n_structs; ii++ )
+    {
+      EosStruct obj = file.getStruct(ii);
+
+      if ( obj instanceof EosGrid )
+      {
+        data = getGridData( (EosGrid)obj );
+      }
+      else if ( obj instanceof EosSwath )
+      {
+        data = getSwathData( (EosSwath)obj );
+      }
+
+      try
+      {
+        M_type = data.getType();
+      }
+      catch ( VisADException e )
+      {
+        System.out.println( e.getMessage() );
+      }
+      finally
+      {
+        types[ii] = M_type;
+      }
+    }
+
+    TupleType t_type = new TupleType( types );
+    return (MathType) t_type;
+  }
+
+  DataImpl getFileData( HdfeosFile file )
+           throws VisADException, RemoteException
+  {
+    DataImpl data = null;
+    HdfeosData f_data = null;
+
+    int n_structs = file.getNumberOfStructs();
+    if ( n_structs == 0 )
+    {
+      throw new HdfeosException("no EOS data structures in file: "+file.getFileName());
+    }
+
+    HdfeosData[] datas = new HdfeosData[ n_structs ];
+
+    for ( int ii = 0; ii < n_structs; ii++ )
+    {
+      EosStruct obj = file.getStruct(ii);
+
+      if ( obj instanceof EosGrid )
+      {
+        f_data = getGridData( (EosGrid)obj );
+      }
+      else if ( obj instanceof EosSwath )
+      {
+        f_data = getSwathData( (EosSwath)obj );
+      }
+
+      datas[ii] = f_data;
+    }
+    return assembleStructs( datas );
+  }
+
+  private DataImpl assembleStructs( HdfeosData[] h_datas )
+          throws VisADException, RemoteException
+  {
+    DataImpl fileData = null;
+    int n_structs = h_datas.length;
+
+    if ( n_structs == 1 )
+    {
+      return getVisADDataObject( h_datas[0] );
+    }
+    else
+    {
+      boolean types_equal = true;
+      MathType first_type;
+      MathType[] types = new MathType[ n_structs ];
+      DataImpl[] datas = new DataImpl[ n_structs ];
+
+      datas[0] = getVisADDataObject( h_datas[0] );
+      types[0] = datas[0].getType();
+      first_type = types[0];
+      for ( int ii = 1; ii < n_structs; ii++ ) {
+        datas[ii] = getVisADDataObject( h_datas[ii] );
+        types[ii] = datas[ii].getType();
+        if ( !(types[ii].equals(first_type)) ) {
+          types_equal = false;
+        }
+      }
+
+      if ( types_equal )
+      {
+        RealType struct_id = RealType.getRealType("struct_id");
+        Integer1DSet domain = new Integer1DSet(struct_id, n_structs);
+        FieldImpl field = new FieldImpl(new FunctionType((MathType) struct_id,
+                                                       first_type), domain);
+        for ( int ii = 0; ii < n_structs; ii++ ) {
+          field.setSample(ii, datas[ii]);
+        }
+        fileData = field;
+      }
+      else
+      {
+        TupleType t_type = new TupleType( types );
+        fileData = new Tuple( t_type, datas, false );
+      }
+
+      return fileData;
+    }
+  }
+
+  DataImpl getVisADDataObject( HdfeosData h_data )
+           throws VisADException, RemoteException
+  {
+    return h_data.getData();
+  }
+
+  HdfeosData getGridData( EosGrid grid )
+             throws HdfeosException,
+                    VisADException,
+                    RemoteException
+  {
+    Shape s_obj;
+    DimensionSet D_set;
+    DimensionSet G_dims;
+    DimensionSet D_dims;
+    NamedDimension dim;
+    Variable var;
+    HdfeosDomain geo_domain;
+    HdfeosDomain domain;
+    HdfeosFlatField f_field;
+    HdfeosField field;
+    Vector datas = new Vector();
+    int d_size;
+
+    ShapeSet DV_shapeSet = grid.getShapeSet();
+    GctpMap gridMap = grid.getMap();
+
+    VariableSet vars_1D = DV_shapeSet.get1DVariables();
+
+    for ( Enumeration e_out = DV_shapeSet.getEnum(); e_out.hasMoreElements(); )
+    {
+      s_obj = (Shape)e_out.nextElement();     // this particular data Variable group
+
+      D_set = s_obj.getShape();     // dimension set of this Variable group
+
+      d_size = D_set.getSize();     // # of dimensions in the set
+
+      VariableSet range_var = s_obj.getVariables();
+
+      G_dims = new DimensionSet();
+      D_dims = new DimensionSet();
+
+      for ( int ii = 0; ii < d_size; ii++ ) //-- separate dimensions first
+      {
+        dim = D_set.getElement(ii);
+
+        if( ((dim.getName()).equals("XDim")) ||
+            ((dim.getName()).equals("YDim")) )
+        {
+          G_dims.add( dim );
+        }
+        else
+        {
+          D_dims.add( dim );
+        }
+      }
+
+      if ( G_dims.getSize() != 2 )
+      {
+        domain = new HdfeosDomain( grid, D_set );
+        f_field = new HdfeosFlatField( domain, range_var );
+        datas.addElement(f_field);
+        continue;
+      }
+      else
+      {
+        geo_domain = new HdfeosDomainMap(grid, G_dims, gridMap);
+      }
+
+      f_field = new HdfeosFlatField( geo_domain, range_var );
+
+      if ( D_dims.getSize() == 0 )
+      {
+        datas.addElement(f_field);
+      }
+      else
+      {
+        field = makeField( grid, D_dims, f_field );
+        datas.addElement(field);
+      }
+    }
+
+    int n_datas = datas.size();
+    if ( n_datas == 0 ) {
+      return null;
+    }
+    if ( n_datas == 1 ) {
+      return (HdfeosData) datas.elementAt(0);
+    }
+    else {
+      HdfeosData[] array = new HdfeosData[n_datas];
+      for ( int ii = 0; ii < n_datas; ii++ ) {
+        array[ii] = (HdfeosData) datas.elementAt(ii);
+      }
+      return new HdfeosTuple(array);
+    }
+  }
+
+  HdfeosData getSwathData( EosSwath swath )
+             throws VisADException,
+                    RemoteException,
+                    HdfeosException
+  {
+    Shape s_obj;
+    DimensionSet D_set;
+    DimensionSet F_dims = null;
+    DimensionSet G_dims;
+    DimensionSet Geo_set = null;
+    DimensionSet D_dims;
+    Variable Latitude = null;
+    Variable Longitude = null;
+    Variable Time = null;
+    Variable var;
+    VariableSet range_var;
+    VariableSet v_set;
+    NamedDimension dim;
+    HdfeosDomain domain = null;
+    HdfeosDomain geo_domain = null;
+    HdfeosFlatField f_field = null;
+    HdfeosField field = null;
+    int d_size;
+    int op;
+    int g_rank;
+
+    ShapeSet DV_shapeSet = swath.getDV_shapeSet();
+    ShapeSet GV_shapeSet = swath.getGV_shapeSet();
+
+    Vector datas = new Vector();
+
+//*- look for Swath geo-spatial-time Variables - - - - - - - - - - - - -
+
+    for ( Enumeration e = GV_shapeSet.getEnum(); e.hasMoreElements(); )
+    {
+      s_obj = (Shape)e.nextElement();
+      v_set = s_obj.getVariables();
+
+      var = v_set.getByName("Latitude");
+      if ( var != null ) {
+        Latitude = var;
+      }
+      var = v_set.getByName("Longitude");
+      if ( var != null ) {
+        Longitude = var;
+      }
+      var = v_set.getByName("Time");
+      if ( Time != null ) {
+        Time = var;
+      }
+    }
+
+    if (( Latitude == null ) || ( Longitude == null ))
+    {
+      geo_domain = null;
+    }
+    else
+    {
+      Variable[] g_vars = { Longitude, Latitude };
+      Geo_set = Longitude.getDimSet();
+
+      if ( (Latitude.getDimSet()).sameSetSameOrder( Geo_set ) )
+      {
+        g_rank = Latitude.getDimSet().getSize();
+        if ( g_rank == 1 )
+        {
+          geo_domain = null;
+          domain = new HdfeosDomain(swath, Geo_set);
+          f_field = new HdfeosFlatField( domain, g_vars );
+          datas.addElement(f_field);
+        }
+        else if ( g_rank == 2 )
+        {
+          geo_domain = new HdfeosDomain(swath, g_vars, Geo_set.getElements());
+        }
+        else if ( g_rank > 2 )
+        {
+          geo_domain = null;
+          domain = new HdfeosDomain(swath, Geo_set);
+          f_field = new HdfeosFlatField( domain, g_vars );
+          datas.addElement(f_field);
+        }
+      }
+      else  //- Latitude/Longitude variables have different shapes
+      {
+        geo_domain = null;
+        domain = new HdfeosDomain(swath, Latitude.getDimSet());
+        f_field = new HdfeosFlatField( domain, Latitude );
+        datas.addElement(f_field);
+
+        domain = new HdfeosDomain(swath, Longitude.getDimSet());
+        f_field = new HdfeosFlatField( domain, Longitude );
+        datas.addElement(f_field);
+      }
+    }
+    if ( Time != null )
+    {
+      domain = new HdfeosDomain(swath, Time.getDimSet());
+      f_field = new HdfeosFlatField( domain, Time );
+      datas.addElement(f_field);
+    }
+
+    for ( Enumeration e_out = DV_shapeSet.getEnum(); e_out.hasMoreElements(); )
+    {
+      s_obj = (Shape)e_out.nextElement();     // this particular data Variable group
+      D_set = s_obj.getShape();     // dimension set of this Variable group
+
+      d_size = D_set.getSize();     // # of dimensions in the set
+
+      range_var = s_obj.getVariables();
+
+      if ( geo_domain == null )
+      {
+        domain = new HdfeosDomain( swath, D_set );
+        f_field = new HdfeosFlatField( domain, range_var );
+        datas.addElement(f_field);
+        continue;
+      }
+
+      G_dims = new DimensionSet();
+      D_dims = new DimensionSet();
+
+      for ( int ii = 0; ii < d_size; ii++ ) //- separate dimensions ( geo, non-geo )
+      {
+        dim = D_set.getElement( ii );
+
+        if ((dim.isGeoMapDefined())||(GV_shapeSet.isMemberOf(dim)))
+        {
+          G_dims.add( dim );
+        }
+        else
+        {
+          D_dims.add( dim );
+        }
+      }
+
+//- examine geo-dimension sets for this Variable group - - - - - - - - - - -
+      int g_size = G_dims.getSize();
+
+      if ( g_size == 0 || g_size == 1 )
+      {
+        domain = new HdfeosDomain(swath, D_set);
+        f_field = new HdfeosFlatField(domain, range_var);
+        datas.addElement(f_field);
+      }
+      else if ( g_size == 2 )
+      {
+        F_dims = D_dims;
+        HdfeosDomain img_domain = new HdfeosDomain( swath, G_dims, geo_domain );
+        f_field = new HdfeosFlatField( img_domain, range_var );
+        if ( F_dims.getSize() == 0 )
+        {
+          datas.add(f_field);
+        }
+        else
+        {
+          int len = F_dims.getSize();
+          boolean any = false;
+          for ( int ii = 0; ii < len; ii++ ) {
+            if ( (D_set.getIndexOf(F_dims.getElement(ii))) >
+                 (D_set.getIndexOf(G_dims.getElement(1))) )
+            {
+              any = true;
+            }
+          }
+          if ( any ) {
+            domain = new HdfeosDomain(swath, D_set);
+            f_field = new HdfeosFlatField(domain, range_var);
+            datas.addElement(f_field);
+          }
+          else {
+            field = makeField( swath, F_dims, f_field );
+            datas.add(field);
+          }
+        }
+      }
+      else if ( g_size >= 3 )
+      {
+        domain = new HdfeosDomain(swath, D_set);
+        f_field = new HdfeosFlatField(domain, range_var);
+        datas.addElement(f_field);
+      }
+    }
+    int n_datas = datas.size();
+    if ( n_datas == 0 ) {
+      return null;
+    }
+    if ( n_datas == 1 ) {
+      return (HdfeosData) datas.elementAt(0);
+    }
+    else {
+      HdfeosData[] array = new HdfeosData[n_datas];
+      for ( int ii = 0; ii < n_datas; ii++ ) {
+        array[ii] = (HdfeosData) datas.elementAt(ii);
+      }
+      return new HdfeosTuple(array);
+    }
+  }
+
+  HdfeosField makeField( EosStruct struct,
+                         DimensionSet F_dims,
+                         HdfeosData t_data )
+              throws VisADException,
+                     RemoteException,
+                     HdfeosException
+  {
+    HdfeosData range = t_data;
+    HdfeosDomain domain = null;
+    HdfeosField field = null;
+    for ( int ii = (F_dims.getSize() - 1); ii >= 0; ii-- )
+    {
+      domain = new HdfeosDomain( struct, F_dims.getElement(ii));
+      field = new HdfeosField( domain, range );
+      range = field;
+    }
+    return field;
+  }
+}
diff --git a/visad/data/hdfeos/HdfeosTuple.java b/visad/data/hdfeos/HdfeosTuple.java
new file mode 100644
index 0000000..6cbebd3
--- /dev/null
+++ b/visad/data/hdfeos/HdfeosTuple.java
@@ -0,0 +1,136 @@
+//
+// HdfeosTuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+import java.rmi.*;
+import visad.*;
+
+public class HdfeosTuple extends HdfeosData
+{
+  Vector dataSet;
+  HdfeosData[] elements = null;
+  int length;
+  private MathType mathtype;
+  private DataImpl tuple = null;
+  private DataImpl[] datas = null;
+
+  HdfeosTuple( HdfeosData[] elements )
+             throws VisADException, RemoteException
+  {
+    dataSet = new Vector();
+    length = elements.length;
+    this.elements = new HdfeosData[length];
+    this.datas = new DataImpl[length];
+    System.arraycopy( elements, 0, this.elements, 0, length );
+
+    MathType[] m_types = new MathType[length];
+    for ( int ii = 0; ii < length; ii++ ) {
+      m_types[ii] = elements[ii].getType();
+    }
+    mathtype = (MathType) new TupleType( m_types );
+  }
+
+  public MathType getType()
+         throws VisADException
+  {
+    return mathtype;
+  }
+
+  public DataImpl getData()
+         throws VisADException, RemoteException
+  {
+    if ( tuple == null )
+    {
+      for ( int ii = 0; ii < length; ii++ ) {
+        datas[ii] = elements[ii].getData();
+      }
+      tuple = (DataImpl) new Tuple( datas );
+    }
+    return tuple;
+  }
+
+  public DataImpl getData( int[] indexes )
+         throws VisADException, RemoteException
+  {
+    for ( int ii = 0; ii < length; ii++ ) {
+      datas[ii] = elements[ii].getData( indexes );
+    }
+    Tuple tuple = new Tuple( datas );
+    return tuple;
+  }
+
+  public DataImpl getAdaptedData()
+         throws VisADException, RemoteException
+  {
+    if ( tuple == null )
+    {
+      for ( int ii = 0; ii < length; ii++ ) {
+        datas[ii] = elements[ii].getAdaptedData();
+      }
+      tuple = (DataImpl) new Tuple( (TupleType)mathtype, datas, false );
+    }
+    return tuple;
+  }
+
+  public DataImpl getAdaptedData( int[] indexes )
+         throws VisADException, RemoteException
+  {
+    for ( int ii = 0; ii < length; ii++ ) {
+      datas[ii] = elements[ii].getAdaptedData( indexes );
+    }
+    Tuple tuple = new Tuple( (TupleType)mathtype, datas, false );
+    return tuple;
+  }
+
+  public int getSize()
+  {
+    return length;
+  }
+
+  public HdfeosData getElement( int ii )
+  {
+    HdfeosData data = elements[ii];
+    return data;
+  }
+
+  public Enumeration getEnum()
+  {
+    Enumeration e = dataSet.elements();
+    return e;
+  }
+
+  public String toString()
+  {
+    String str = "dataSet: \n";
+    for ( int ii = 0; ii < this.getSize(); ii++ )
+    {
+       str = str + "  "+((this.getElement(ii)).toString())+"\n";
+    }
+    return str;
+  }
+}
diff --git a/visad/data/hdfeos/LambertAzimuthalEqualArea.java b/visad/data/hdfeos/LambertAzimuthalEqualArea.java
new file mode 100644
index 0000000..c5234c7
--- /dev/null
+++ b/visad/data/hdfeos/LambertAzimuthalEqualArea.java
@@ -0,0 +1,282 @@
+//
+// LambertAzimuthalEqualArea.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.*;
+
+/**
+   LambertAzimuthalEqualArea is the VisAD class for coordinate
+   systems for ( X_map, Y_map ).<P>
+*/
+
+public class LambertAzimuthalEqualArea extends CoordinateSystem {
+
+  double R;
+  double lon_center;
+  double lat_center;
+  double false_easting;
+  double false_northing;
+  double sin_lat_o;
+  double cos_lat_o;
+  Unit[] reference_units;
+
+  private static Unit[] coordinate_system_units =
+    {null, null};
+
+  private static Unit[] default_reference_units =
+    {CommonUnit.radian, CommonUnit.radian};
+
+  public LambertAzimuthalEqualArea( RealTupleType reference,
+                                    double lon_center,
+                                    double lat_center       )
+         throws VisADException
+  {
+    this(reference, 6367470, lon_center, lat_center, 0, 0);
+  }
+
+  public LambertAzimuthalEqualArea( RealTupleType reference,
+                                    double R,
+                                    double lon_center,
+                                    double lat_center,
+                                    double false_easting,
+                                    double false_northing
+                                                              )
+  throws VisADException
+  {
+
+    super( reference, coordinate_system_units );
+
+    reference_units =
+      reference.getDefaultUnits();
+
+    if ( reference_units != null ) {
+      if (! Unit.canConvertArray(default_reference_units, reference_units)) {
+        throw new VisADException("not compatible with reference units");
+      }
+    }
+    else {
+      reference_units = default_reference_units;
+    }
+
+    this.R = R;
+    this.lon_center = lon_center;
+    this.lat_center = lat_center;
+    this.false_easting = false_easting;
+    this.false_northing = false_northing;
+    this.sin_lat_o = Math.sin( lat_center  );
+    this.cos_lat_o = Math.cos( lat_center  );
+  }
+
+  public double[][] toReference(double[][] tuples) throws VisADException {
+
+     double Rh;
+     double x;
+     double y;
+     double z;               // Great circle dist from proj center to given point
+     double sin_z;           // Sine of z
+     double cos_z;           // Cosine of z
+     double temp;            // Re-used temporary variable
+     double lon;
+     double lat;
+     double[] dum_1 = new double[1];
+     double[] dum_2 = new double[1];
+     double[] dum = new double[1];
+
+     int n_tuples = tuples[0].length;
+     int tuple_dim = tuples.length;
+
+     if ( tuple_dim != 2) {
+       throw new VisADException("LambertAzimuthalEqualArea: tuple dim != 2");
+     }
+
+     double t_tuples[][] = new double[2][n_tuples];
+
+     for ( int ii = 0; ii < n_tuples; ii++ ) {
+
+       x = tuples[0][ii] - false_easting;
+       y = tuples[1][ii] - false_northing;
+       Rh = Math.sqrt(x * x + y * y);
+       temp = Rh / (2.0 * R);
+       if (temp > 1)
+       {
+         // p_error("Input data error", "lamaz-inverse");
+       }
+       z = 2.0 * GctpFunction.asinz(temp);
+       dum[0] = z;
+       GctpFunction.sincos(dum, dum_1, dum_2);
+       sin_z = dum_1[0];
+       cos_z = dum_2[0];
+       lon = lon_center;
+       if ( Math.abs(Rh) > GctpFunction.EPSLN )
+       {
+         lat = GctpFunction.asinz(sin_lat_o * cos_z + cos_lat_o * sin_z * y / Rh);
+         temp = Math.abs(lat_center) - GctpFunction.HALF_PI;
+         if (Math.abs(temp) > GctpFunction.EPSLN)
+         {
+           temp = cos_z - sin_lat_o * Math.sin(lat);
+           if(temp!=0.0) {
+             lon = GctpFunction.adjust_lon(lon_center+Math.atan2(x*sin_z*cos_lat_o,temp*Rh));
+           }
+         }
+         else if (lat_center < 0.0) {
+           lon = GctpFunction.adjust_lon(lon_center - Math.atan2(-x, y));
+         }
+         else {
+           lon = GctpFunction.adjust_lon(lon_center + Math.atan2(x, -y));
+         }
+       }
+       else {
+         lat = lat_center;
+       }
+
+       t_tuples[0][ii] = lon;
+       t_tuples[1][ii] = lat;
+     }
+     return 
+       Unit.convertTuple(t_tuples, default_reference_units, reference_units);
+  }
+
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+
+     int n_tuples = tuples[0].length;
+     int tuple_dim = tuples.length;
+     double ksp;
+     double g;
+
+     if ( tuple_dim != 2) {
+        throw new VisADException("LambertAzimuthalEqualArea: tuple dim != 2");
+     }
+
+     tuples =
+       Unit.convertTuple(tuples, reference_units, default_reference_units);
+
+     double t_tuples[][] = new double[2][n_tuples];
+     double[] delta_lon = new double[n_tuples];
+     double[] sin_lat = new double[n_tuples];
+     double[] cos_lat = new double[n_tuples];
+     double[] sin_delta_lon = new double[n_tuples];
+     double[] cos_delta_lon = new double[n_tuples];
+
+     for ( int ii = 0; ii < n_tuples; ii++ ) {
+        delta_lon[ii] = tuples[0][ii] - lon_center;
+     }
+
+     GctpFunction.adjust_lon( delta_lon );
+
+     GctpFunction.sincos( tuples[1], sin_lat, cos_lat );
+     GctpFunction.sincos( delta_lon, sin_delta_lon, cos_delta_lon );
+
+
+     for ( int ii = 0; ii < n_tuples; ii++ ) {
+
+       g = sin_lat_o * sin_lat[ii] + cos_lat_o * cos_lat[ii] * cos_delta_lon[ii];
+       if ( g == -1 ) {
+          //throw new VisADException( "Point projects to a circle of radius = "+(2.*R) );
+          t_tuples[0][ii] = Double.NaN;
+          t_tuples[1][ii] = Double.NaN;
+       }
+
+       ksp = R * Math.sqrt(2.0 / (1.0 + g));
+
+       t_tuples[0][ii] = ksp * cos_lat[ii] * sin_delta_lon[ii] + false_easting;
+       t_tuples[1][ii] = ksp * (cos_lat_o * sin_lat[ii] -
+                         sin_lat_o * cos_lat[ii] * cos_delta_lon[ii]) +
+                         false_northing;
+     }
+
+
+     delta_lon = null;
+     sin_lat = null;
+     cos_lat = null;
+     sin_delta_lon = null;
+     cos_delta_lon = null;
+
+     return t_tuples;
+  }
+
+  public boolean equals(Object cs) {
+    if (cs instanceof LambertAzimuthalEqualArea) {
+       LambertAzimuthalEqualArea that = (LambertAzimuthalEqualArea) cs;
+       if ((this.R == that.R) && (this.lon_center == that.lon_center) && (this.lat_center == that.lat_center) && 
+           (this.false_easting == that.false_easting) && (this.false_northing == that.false_northing) ) {
+          return true;
+       }
+    }
+    return false;
+  }
+
+
+  public static void main(String args[]) throws VisADException 
+  {
+
+     double[][] values_in = { {-90*Data.DEGREES_TO_RADIANS, 
+                               -85*Data.DEGREES_TO_RADIANS,
+                               -80*Data.DEGREES_TO_RADIANS,
+                               -75*Data.DEGREES_TO_RADIANS},
+
+                              {42*Data.DEGREES_TO_RADIANS,
+                               42*Data.DEGREES_TO_RADIANS,
+                               42*Data.DEGREES_TO_RADIANS,
+                               42*Data.DEGREES_TO_RADIANS}  };
+
+     double earth_rad = 6367470;
+     double lon_center = -90*Data.DEGREES_TO_RADIANS;
+     double lat_center =  42*Data.DEGREES_TO_RADIANS;
+     double false_easting = 0;
+     double false_northing = 0;
+     
+     RealType reals[] = {RealType.Longitude,RealType.Latitude};
+     RealTupleType Reference = new RealTupleType(reals);
+
+     CoordinateSystem lamaz_cs = 
+        new LambertAzimuthalEqualArea( Reference, 
+                                       earth_rad,
+                                       lon_center,
+                                       lat_center,
+                                       false_easting,
+                                       false_northing );
+                               
+
+     for ( int i=0; i<values_in[0].length; i++) {
+        System.out.println(values_in[0][i]+",  "+values_in[1][i]);
+     }
+     System.out.println("");
+
+     double[][] values_out = lamaz_cs.fromReference( values_in );
+
+     for ( int i=0; i<values_out[0].length; i++) {
+        System.out.println(values_out[0][i]+",  "+values_out[1][i]);
+     }
+
+     double[][] values_inR = lamaz_cs.toReference( values_out );
+
+     System.out.println("");
+     for ( int i=0; i<values_inR[0].length; i++) {
+        System.out.println(values_inR[0][i]+",  "+values_inR[1][i]);
+     }
+  }
+}
diff --git a/visad/data/hdfeos/LambertConformalConic.java b/visad/data/hdfeos/LambertConformalConic.java
new file mode 100644
index 0000000..aa9bc0d
--- /dev/null
+++ b/visad/data/hdfeos/LambertConformalConic.java
@@ -0,0 +1,261 @@
+//
+// LambertAzimuthalEqualArea.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.*;
+
+/**
+   LambertConformalConic is the VisAD class for coordinate
+   systems for ( X_map, Y_map ).<P>
+*/
+
+public class LambertConformalConic extends CoordinateSystem
+{
+  double r_major;
+  double es;
+  double e;
+  double r_minor;
+  double lon_center;
+  double lat_center;
+  double ns;
+  double f0;
+  double rh;
+  double false_easting;
+  double false_northing;
+
+  private static Unit[] coordinate_system_units =
+   {SI.meter, SI.meter};
+
+  public LambertConformalConic( RealTupleType reference,  //- earth Reference
+                                double r_major,           //- semimajor axis
+                                double r_minor,           //- semiminor axis
+                                double s_lat1,            //- 1st standard latitude
+                                double s_lat2,            //- 2nd standard latitude
+                                double lon_center,        //- longitude of grid origin
+                                double lat_center,        //- latitude of grid origin
+                                double false_easting,     //- x offset
+                                double false_northing     //- y offset
+                                                        )
+  throws VisADException
+  {
+    super( reference, coordinate_system_units );
+
+    this.r_major = r_major;
+    this.r_minor = r_minor;
+    this.lon_center = lon_center;
+    this.lat_center = lat_center;
+    this.false_easting = false_easting;
+    this.false_northing = false_northing;
+
+    if (Math.abs(s_lat1+s_lat2) < GctpFunction.EPSLN)
+    {
+      throw new GctpException(
+         "Equal latitudes for st. paralles on opposite sides of equator");
+    }
+
+    double sin_po;
+    double cos_po;
+    double con;
+    double ms1, ms2;
+    double ts0, ts1, ts2;
+    double temp = r_minor/r_major;
+    es = 1d - temp*temp;
+    e = Math.sqrt(es);
+
+    sin_po = Math.sin(s_lat1);
+    cos_po = Math.cos(s_lat1);
+    con = sin_po;
+    ms1 = GctpFunction.msfnz(e, sin_po, cos_po);
+    ts1 = GctpFunction.tsfnz(e, s_lat1, sin_po);
+
+    sin_po = Math.sin(s_lat2);
+    cos_po = Math.cos(s_lat2);
+    ms2 = GctpFunction.msfnz(e, sin_po, cos_po);
+    ts2 = GctpFunction.tsfnz(e, s_lat2, sin_po);
+    sin_po = Math.sin(lat_center);
+    ts0 = GctpFunction.tsfnz(e, lat_center, sin_po);
+
+    if (Math.abs(s_lat1 - s_lat2) > GctpFunction.EPSLN) {
+      ns = Math.log (ms1/ms2)/ Math.log (ts1/ts2);
+    }
+    else {
+      ns = con;
+    }
+    f0 = ms1 / (ns * Math.pow(ts1,ns));
+    rh = r_major * f0 * Math.pow(ts0,ns);
+  }
+
+  public double[][] toReference(double[][] tuples)
+                    throws VisADException
+  {
+     double rh1;
+     double con;
+     double ts;
+     double theta;
+     double x;
+     double y;
+     double lon;
+     double lat;
+     long flag;
+
+     int n_tuples = tuples[0].length;
+     int tuple_dim = tuples.length;
+
+     if ( tuple_dim != 2) {
+       throw new VisADException("LambertConformalConic: tuple dim != 2");
+     }
+
+     double t_tuples[][] = new double[2][n_tuples];
+
+     for ( int ii = 0; ii < n_tuples; ii++ ) {
+       y = tuples[1][ii];
+       x = tuples[0][ii];
+       flag = 0;
+       x -= false_easting;
+       y = rh - y + false_northing;
+       if ( ns > 0 ) {
+         rh1 = Math.sqrt(x*x + y*y);
+         con = 1d;
+       }
+       else {
+         rh1 = -Math.sqrt(x*x + y*y);
+         con = -1d;
+       }
+       theta = 0d;
+       if (rh1 != 0) {
+         theta = Math.atan2((con*x),(con*y));
+       }
+       if ((rh1 != 0) || (ns > 0.0)) {
+         con = 1.0/ns;
+         ts = Math.pow((rh1/(r_major*f0)),con);
+         t_tuples[1][ii] = GctpFunction.phi2z(e, ts);
+       }
+       else {
+         t_tuples[1][ii] = -GctpFunction.HALF_PI;
+       }
+       t_tuples[0][ii] = GctpFunction.adjust_lon(theta/ns + lon_center);
+     }
+
+     return t_tuples;
+  }
+
+  public double[][] fromReference(double[][] tuples)
+                    throws VisADException
+  {
+    int n_tuples = tuples[0].length;
+    int tuple_dim = tuples.length;
+
+    if ( tuple_dim != 2) {
+      throw new VisADException("LambertConformalConic: tuple dim != 2");
+    }
+
+    double con;
+    double ts;
+    double sinphi;
+    double lat;
+    double lon;
+    double theta;
+    double[] rh1 = new double[n_tuples];
+    double t_tuples[][] = new double[2][n_tuples];
+
+    for ( int ii = 0; ii < n_tuples; ii++ )
+    {
+      lon = tuples[0][ii];
+      lat = tuples[1][ii];
+      con = Math.abs(Math.abs(lat) - GctpFunction.HALF_PI);
+      if ( con > GctpFunction.EPSLN ) {
+        sinphi = Math.sin(lat);
+        ts = GctpFunction.tsfnz(e, lat, sinphi);
+        rh1[ii] = r_major*f0*Math.pow(ts,ns);
+      }
+      else {
+        con = lat*ns;
+        if ( con <= 0 ) {
+          t_tuples[0][ii] = Double.NaN;
+          t_tuples[1][ii] = Double.NaN;
+        }
+        rh1[ii] = 0d;
+      }
+
+      theta = ns*GctpFunction.adjust_lon(lon - lon_center);
+      t_tuples[0][ii] = rh1[ii]*Math.sin(theta) + false_easting;
+      t_tuples[1][ii] = rh - rh1[ii]*Math.cos(theta) + false_northing;
+    }
+    return t_tuples;
+  }
+
+  public boolean equals(Object cs) {
+    return ( cs instanceof LambertConformalConic );
+  }
+
+  public static void main(String args[]) throws VisADException
+  {
+     CoordinateSystem cs_lamcc = null;
+     RealType real1;
+     RealType real2;
+     double[][] values_in = { {-2.3292989, -1.6580627, -1.6580627, -1.6580627},
+                              { 0.2127555, 0.4363323, 0.6981317, 0.8726646} };
+
+     real1 = RealType.getRealType("Theta", SI.radian);
+     real2 = RealType.getRealType("radius", SI.meter);
+     RealType reals[] = {RealType.Longitude,RealType.Latitude};
+
+  //-double r_major = 6378206d;
+  //-double r_minor = 6356584d;
+     double r_major = 6367470d;
+     double r_minor = 6367470d;
+  //-double r_major = 6378160d;
+  //-double r_minor = 6356775d;
+     double s_lat1 = 23*Data.DEGREES_TO_RADIANS;
+     double s_lat2 = 27*Data.DEGREES_TO_RADIANS;
+     double center_lat = 25*Data.DEGREES_TO_RADIANS;
+     double center_lon = -95*Data.DEGREES_TO_RADIANS;
+     double false_easting = 0;
+     double false_northing = 0;
+
+     RealTupleType Reference = new RealTupleType(reals);
+
+     cs_lamcc = new LambertConformalConic( Reference, r_major, r_minor,
+                                           s_lat1, s_lat2,
+                                           center_lon, center_lat,
+                                           false_easting, false_northing );
+
+     double[][] values_out = cs_lamcc.fromReference( values_in );
+
+     for ( int i=0; i<values_out[0].length; i++)
+     {
+       System.out.println(values_out[0][i]+",  "+values_out[1][i]);
+     }
+     System.out.println(" ");
+
+     double[][] values_inR = cs_lamcc.toReference( values_out );
+     for ( int i=0; i<values_inR[0].length; i++)
+     {
+       System.out.println(values_inR[0][i]+",  "+values_inR[1][i]);
+     }
+  }
+}
diff --git a/visad/data/hdfeos/NamedDimension.java b/visad/data/hdfeos/NamedDimension.java
new file mode 100644
index 0000000..aa8260e
--- /dev/null
+++ b/visad/data/hdfeos/NamedDimension.java
@@ -0,0 +1,101 @@
+//
+// NamedDimension.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+
+public class NamedDimension
+{
+  private String  name;
+  private int  length;
+  private GeoMap g_map;
+  private boolean unLimitFlag = false;
+
+  NamedDimension( int struct_id, String name, int length, GeoMap g_map )
+  {
+    this.name = name;
+    if ( length == 0 ) {
+      unLimitFlag = true;
+    }
+    this.length = length;
+    this.g_map = g_map;
+  }
+
+  public String getName()
+  {
+    return this.name;
+  }
+
+  public boolean equals( NamedDimension obj )
+  {
+    if( this.name.equals( obj.getName() ))
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  public void setLength( int len )
+  {
+    length = len;
+    return;
+  }
+
+  public int getLength()
+  {
+    return length;
+  }
+
+  public GeoMap getGeoMap()
+  {
+    return g_map;
+  }
+
+  public boolean isGeoMapDefined()
+  {
+    if ( g_map == null ) {
+      return false;
+    }
+    else {
+     return true;
+    }
+  }
+
+  public boolean isUnlimited()
+  {
+    return this.unLimitFlag;
+  }
+
+  public String toString()
+  {
+    String str = "dimension: "+name+"\n"+
+                 "   length: "+length+"\n";
+    return str;
+  }
+}
diff --git a/visad/data/hdfeos/PolarStereographic.java b/visad/data/hdfeos/PolarStereographic.java
new file mode 100644
index 0000000..b51f7b3
--- /dev/null
+++ b/visad/data/hdfeos/PolarStereographic.java
@@ -0,0 +1,343 @@
+//
+// PolarStereographic.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import visad.*;
+
+/**
+   PolarStereographic is the VisAD class for coordinate
+   systems for ( X_map, Y_map ).<P>
+*/
+
+public class PolarStereographic extends CoordinateSystem 
+{
+  double r_major;                // major axis
+  double r_minor;                // minor axis
+  double es;                     // eccentricity squared
+  double e;                      // eccentricity
+  double e4;                     // e4 calculated from eccentricity
+  double center_lon;             // center longitude
+  double center_lat;             // center latitude
+  double fac;                    // sign variable
+  double ind;                    // flag variable
+  double mcs;                    // small m
+  double tcs;                    // small t
+  double false_northing;         // y offset in meters
+  double false_easting;          // x offset in meters
+
+
+  private static Unit[] coordinate_system_units =
+    {null, null};
+
+  public PolarStereographic( double lon_center, 
+                             double lat_center 
+                                               )
+         throws VisADException
+  {
+    this(RealTupleType.SpatialEarth2DTuple, 6371230, 6371230,
+         lon_center, lat_center, 0, 0); 
+  }
+
+  public PolarStereographic( double r_major,
+                             double r_minor,
+                             double lon_center,
+                             double lat_center 
+                                               )
+         throws VisADException
+  {
+     this(RealTupleType.SpatialEarth2DTuple, 
+          r_major, r_minor, lon_center, lat_center, 0, 0);
+  }
+
+  public PolarStereographic( RealTupleType reference,
+                             double r_major,
+                             double r_minor,
+                             double lon_center,
+                             double lat_center
+                                                     )
+         throws VisADException
+  {
+    this(reference, r_major, r_minor, lon_center, lat_center, 0, 0);
+  }
+
+  /*-  GRIB/AWIPS friendly static methods. 
+       Note: Lon/Lat must be in unit radians
+   ---------------------------------------------*/
+
+  public static PolarStereographic 
+         makePolarStereographic( RealTupleType reference,  //- Earth Reference
+                                 double La1,               //- Latitude of first grid point
+                                 double Lo1,               //- Longitude of first grid point
+                                 double Lov                //- The orientation of grid
+                                                     )
+         throws VisADException
+  {
+    return makePolarStereographic(reference, 6371230, 6371230, La1, Lo1, Lov, 
+                                  60*Data.DEGREES_TO_RADIANS);
+  }
+                                         
+  public static PolarStereographic
+         makePolarStereographic( RealTupleType reference,  //- Earth Reference
+                                 double r_major,           //- Earth major axis
+                                 double r_minor,           //- Earth minor axis
+                                 double La1,               //- Latitude of first grid point
+                                 double Lo1,               //- Longitude of first grid point
+                                 double Lov,               //- The orientation of grid
+                                 double lat_center         //- Latitude of true scale
+                                                          )
+         throws VisADException
+  {
+    PolarStereographic ps =
+      new PolarStereographic(reference, r_major, r_minor, Lov, lat_center, 0, 0);
+
+    double[][] values =
+      ps.fromReference(new double[][] {{Lo1}, {La1}});
+    
+    double false_easting = values[0][0];
+    double false_northing = values[1][0];
+
+    return 
+      new PolarStereographic(reference,
+                             r_major, r_minor, Lov, lat_center,
+                             -false_easting, -false_northing);
+  }
+
+  public PolarStereographic( RealTupleType reference,   //- Earth Reference
+                             double r_major,            //- Earth major axis
+                             double r_minor,            //- Earth minor axis
+                             double lon_center,         //- Longitude down below pole of map
+                             double lat_center,         //- Latitude of true scale
+                             double false_easting,      //- x_axis offset
+                             double false_northing      //- y_axis offset
+                                                     )
+  throws VisADException
+  {
+    super( reference, coordinate_system_units );
+
+    this.r_major = r_major;
+    this.r_minor = r_minor;
+    this.center_lon = lon_center;
+    this.center_lat = lat_center;
+    this.false_easting = false_easting;
+    this.false_northing = false_northing;
+
+    double temp;                            // temporary variable
+    double con1;                            // temporary angle
+    double sinphi;                          // sin value
+    double cosphi;                          // cos value
+    double[] dum_1 = new double[1];
+    double[] dum_2 = new double[1];
+    double[] dum_3 = new double[1];
+
+    temp = r_minor / r_major;
+    es = 1.0 - temp*temp;
+    e = Math.sqrt(es);
+    e4 = GctpFunction.e4fn(e);
+
+    if ( lat_center < 0) {
+      fac = -1.0;
+    }
+    else {
+      fac = 1.0;
+    }
+
+    ind = 0;
+    if (Math.abs(Math.abs(lat_center) - GctpFunction.HALF_PI) > GctpFunction.EPSLN )
+    {
+      ind = 1;
+      con1 = fac * center_lat;
+      dum_1[0] = con1;
+      GctpFunction.sincos(dum_1, dum_2, dum_3);
+      sinphi = dum_2[0];
+      cosphi = dum_3[0];
+      mcs = GctpFunction.msfnz(e,sinphi,cosphi);
+      tcs = GctpFunction.tsfnz(e,con1,sinphi);
+    }
+  }
+
+  public double[][] toReference(double[][] tuples) 
+         throws VisADException 
+  {
+    double x;
+    double y;
+    double rh;                      // height above ellipsiod
+    double ts;                      // small value t
+    double temp;                    // temporary variable
+    long   flag;                    // error flag
+    double lon;
+    double lat;
+
+    int n_tuples = tuples[0].length;
+    int tuple_dim = tuples.length;
+
+    if ( tuple_dim != 2) {
+      throw new VisADException("PolarStereographic: tuple dim != 2");
+    }
+
+    double t_tuples[][] = new double[2][n_tuples];
+
+    for ( int ii = 0; ii < n_tuples; ii++ ) 
+    {
+      x = (tuples[0][ii] - false_easting)*fac;
+      y = (tuples[1][ii] - false_northing)*fac;
+      rh = Math.sqrt(x * x + y * y);
+
+      if (ind != 0) {
+        ts = rh * tcs/(r_major * mcs);
+      }
+      else {
+        ts = rh * e4 / (r_major * 2.0);
+      }
+
+      lat = GctpFunction.phi2z(e,ts);
+      if ( lat == Double.NaN ) {
+      }
+      else {
+        lat = lat*fac;
+      }
+
+      if (rh == 0) {
+        lon = fac * center_lon;
+      }
+      else {
+        temp = Math.atan2(x, -y);
+        lon = GctpFunction.adjust_lon(fac *temp + center_lon);
+      }
+
+      t_tuples[0][ii] = lon;
+      t_tuples[1][ii] = lat;
+    }
+    return t_tuples;
+  }
+
+  public double[][] fromReference(double[][] tuples) 
+         throws VisADException 
+  {
+    int n_tuples = tuples[0].length;
+    int tuple_dim = tuples.length;
+    double con1;                    // adjusted longitude
+    double con2;                    // adjusted latitude
+    double rh;                      // height above ellipsoid
+    double sinphi;                  // sin value
+    double ts;                      // value of small t
+    double x;
+    double y;
+    double lat;
+    double lon;
+
+    if ( tuple_dim != 2) {
+      throw new VisADException("PolarStereographic: tuple dim != 2");
+    }
+
+    double t_tuples[][] = new double[2][n_tuples];
+
+    for ( int ii = 0; ii < n_tuples; ii++ ) 
+    {
+      lon = tuples[0][ii];
+      lat = tuples[1][ii];
+
+      con1 = fac * GctpFunction.adjust_lon(lon - center_lon);
+      con2 = fac * lat;
+      sinphi = Math.sin(con2);
+      ts = GctpFunction.tsfnz(e,con2,sinphi);
+      if (ind != 0) {
+        rh = r_major * mcs * ts / tcs;
+      }
+      else {
+        rh = 2.0 * r_major * ts / e4;
+      }
+
+      t_tuples[0][ii] = fac * rh * Math.sin(con1) + false_easting;
+      t_tuples[1][ii] = -fac * rh * Math.cos(con1) + false_northing;
+    }
+    return t_tuples;
+  }
+
+  public boolean equals(Object cs) {
+    return (cs instanceof PolarStereographic);
+  }
+
+  public static void main(String args[]) throws VisADException 
+  {
+    double r_major = 6371230; 
+    double r_minor = 6371230;
+    double center_lat = 40*Data.DEGREES_TO_RADIANS;
+    double center_lon = -100*Data.DEGREES_TO_RADIANS;
+    double false_easting = 0;
+    double false_northing = 0;
+
+    double[][] values_in = { {-2.3292989, -1.6580627, -1.6580627, -1.6580627, center_lon},
+                             { 0.2127555, 0.4363323, 0.6981317, 0.8726646, 90*Data.DEGREES_TO_RADIANS} };
+
+    RealType[] reals = {RealType.Longitude, RealType.Latitude};
+    RealTupleType reference = new RealTupleType(reals);
+
+    CoordinateSystem cs = new PolarStereographic( reference,
+                                                  r_major,
+                                                  r_minor,
+                                                  center_lon,
+                                                  center_lat,
+                                                  false_easting,
+                                                  false_northing );
+
+    double[][] values = cs.fromReference(values_in);
+
+    for ( int i=0; i<values_in[0].length; i++) {
+       System.out.println(values_in[0][i]+",  "+values_in[1][i]);
+    }
+
+    System.out.println("--------------------------\n");
+
+    for ( int i=0; i<values[0].length; i++) {
+       System.out.println(values[0][i]+",  "+values[1][i]);
+    }
+
+    double[][] values_R = cs.toReference(values);
+
+    System.out.println("--------------------------\n");
+
+    for ( int i=0; i<values_R[0].length; i++) {
+      System.out.println(values_R[0][i]+",  "+values_R[1][i]);
+    }
+
+
+    PolarStereographic ps = 
+      makePolarStereographic(reference, -20.826*Data.DEGREES_TO_RADIANS,
+                             -150.000*Data.DEGREES_TO_RADIANS,
+                             -105.000*Data.DEGREES_TO_RADIANS);
+
+    values = ps.fromReference(new double[][] {{-150*Data.DEGREES_TO_RADIANS},
+                                              {-20.826*Data.DEGREES_TO_RADIANS}});
+    System.out.println("x: "+values[0][0]);
+    System.out.println("y: "+values[1][0]);
+
+    values = ps.fromReference(new double[][] {{-105*Data.DEGREES_TO_RADIANS},
+                                              {90*Data.DEGREES_TO_RADIANS}});
+    System.out.println("x: "+values[0][0]);
+    System.out.println("y: "+values[1][0]);
+  }
+}
diff --git a/visad/data/hdfeos/README.hdfeos b/visad/data/hdfeos/README.hdfeos
new file mode 100644
index 0000000..a714429
--- /dev/null
+++ b/visad/data/hdfeos/README.hdfeos
@@ -0,0 +1,71 @@
+       --** HDF-EOS installation instructions **--
+
+(1) obtain and install HDF4.1r1 or HDF4.1r2
+
+    HDF web:
+      http://hdf.ncsa.uiuc.edu/
+
+    To obtain pre-compiled binaries:
+      ftp://ftp.ncsa.uiuc.edu/HDF/HDF_Current/bin/solaris/
+
+    retrieve the zipped tar file (e.g., 4.1r2_solaris.tar.gz)
+    and the README file. Then open the README file and follow
+    the simple instructions.
+
+
+(2) obtain and install the HDF-EOS library
+
+    HDF-EOS web:
+      http://ulabibm.gsfc.nasa.gov/hdfeos/workshop.html
+
+      click on HDF-EOS Source Code and Documentation link
+      and download the following (near bottom of page,
+      under 'HDF-EOS Library:'):
+
+        HDF-EOS*v*.README
+        HDF-EOS*v*.tar
+        HDF-EOS*.*_TestDrivers.tar
+
+   Make or choose an HDF-EOS directory, copy HDF-EOS*v*.tar and
+   HDF-EOS*.*_TestDrivers.tar to that directory and unpack the
+   first of these:
+
+    zcat HDF-EOS2.3v1.00.tar.Z | tar xvf -
+
+   Next,
+
+    cd hdfeos
+
+   Now install hdfeos:
+
+    bin/INSTALL-HDFEOS, follow user input instructions
+
+   for example:  bin/INSTALL-HDFEOS -i/home/billh/java/visad/hdfeos/4.1r2_solaris/include -l/home/billh/java/visad/hdfeos/4.1r2_solaris/lib
+
+   Setup environment:
+
+    HDF_INC=HDFDIR/HDF4.1r2/include
+    HDF_LIB=HDFDIR/HDF4.1r2/lib
+    HDFEOS_INC=HDFEOSDIR/hdfeos/include
+    HDFEOS_LIB=HDFEOSDIR/hdfeos/lib/sun5
+
+    where HDFDIR is your hdf directory and HDFEOSDIR is
+    your hdf-eos directory.
+
+(3) Test the hdfeos library...
+
+      In your hdfeos directory:
+         zcat HDF-EOS2.3v1.00_TestDrivers.tar.Z | tar xvf -
+
+      next, cd hdfeos/testdrivers
+
+      Follow the instructions in the README file.
+
+(4) Setup environmet ( for VisAD HDF-EOS file adapter):
+
+    J_INC = JAVADIR/include  /* native method interface */
+    LD_LIBRARY_PATH = VISADDIR/data/hdfeos/hdfeosc
+
+    where JAVADIR is the JDK installation directory, and
+    VISADDIR is the VisAD installation directory.
+
diff --git a/visad/data/hdfeos/Shape.java b/visad/data/hdfeos/Shape.java
new file mode 100644
index 0000000..b4b1bef
--- /dev/null
+++ b/visad/data/hdfeos/Shape.java
@@ -0,0 +1,105 @@
+//
+// Shape.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+public class Shape
+{
+  private DimensionSet  dimSet;
+  private VariableSet  varSet;
+
+  public Shape( Variable var  )
+  {
+    varSet = new VariableSet();
+    varSet.add( var );
+
+    dimSet = var.getDimSet();
+  }
+
+  public void addVariable( Variable var )
+  {
+    varSet.add( var );
+  }
+
+  public DimensionSet getShape()
+  {
+    return dimSet;
+  }
+
+  public VariableSet getVariables()
+  {
+    return varSet;
+  }
+
+  public int getNumberOfVars()
+  {
+    return varSet.getSize();
+  }
+
+  public Variable getVariable( int index )
+  {
+    return varSet.getElement( index );
+  }
+
+  public boolean memberOf( Variable var )
+  {
+    DimensionSet d_set = var.getDimSet();
+
+    if( this.dimSet.sameSetSameOrder( d_set ) )
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  public Variable isCoordVar( int index )
+  {
+    if ( dimSet.getSize() == 1 )
+    {
+      return varSet.isCoordVar( index );
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+  public String toString()
+  {
+    String str = dimSet.toString()+"  Variables: \n";
+
+    for( int ii = 0; ii < varSet.getSize(); ii++ )
+    {
+      str = str + "       "+(varSet.getElement(ii)).getName() + "\n";
+    }
+      str = str + "- - - - - - - - - - - - - - - - - - - \n";
+
+    return str;
+  }
+}
diff --git a/visad/data/hdfeos/ShapeSet.java b/visad/data/hdfeos/ShapeSet.java
new file mode 100644
index 0000000..0fc338b
--- /dev/null
+++ b/visad/data/hdfeos/ShapeSet.java
@@ -0,0 +1,134 @@
+//
+// ShapeSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+
+public class ShapeSet
+{
+  private Vector S_Set;
+  private VariableSet c_vars;
+
+  ShapeSet( VariableSet varSet )
+  {
+    boolean found;
+
+    S_Set = new Vector();
+    c_vars = new VariableSet();
+
+    for ( Enumeration e = varSet.getEnum(); e.hasMoreElements(); )
+    {
+      Variable var = (Variable) e.nextElement();
+      if ( var.isCoordVar() )
+      {
+        c_vars.add(var);
+      }
+      else
+      {
+        int count = S_Set.size();
+
+        if ( count == 0 )
+        {
+          found = true;
+          Shape s_obj  = new Shape( var );
+          S_Set.addElement( s_obj );
+        }
+        else
+        {
+          found = false;
+
+          for ( int ii = 0; ii < count; ii++ )
+          {
+            Shape s_obj = (Shape)S_Set.elementAt(ii);
+            if( s_obj.memberOf( var ) )
+            {
+              s_obj.addVariable( var );
+              found = true;
+            }
+          }
+        }
+
+        if ( !found )
+        {
+          Shape s_obj = new Shape( var );
+          S_Set.addElement( s_obj );
+        }
+      }
+    }
+  }
+
+  public Shape getElement( int ii )
+  {
+    Shape obj = (Shape)S_Set.elementAt( ii );
+    return obj;
+  }
+
+  public int getSize()
+  {
+    int size = S_Set.size();
+    return size;
+  }
+
+  Enumeration getEnum()
+  {
+    Enumeration e = S_Set.elements();
+    return e;
+  }
+
+  public boolean isMemberOf( NamedDimension dim )
+  {
+    DimensionSet d_set;
+
+    for ( int ii = 0; ii < this.getSize(); ii++ )
+    {
+      d_set = (this.getElement(ii)).getShape();
+
+      if ( d_set.isMemberOf( dim ) ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public VariableSet get1DVariables()
+  {
+    return c_vars;
+  }
+
+  public String toString()
+  {
+    String str = " Shapes in this set:   \n";
+
+    for ( int ii = 0; ii < this.getSize(); ii++ )
+    {
+      str = str + (this.getElement(ii)).toString() + "\n";
+    }
+    str = str + " - - - - - - - - - - - - - - - - - - \n";
+
+    return str;
+  }
+}
diff --git a/visad/data/hdfeos/Variable.java b/visad/data/hdfeos/Variable.java
new file mode 100644
index 0000000..0e4b27d
--- /dev/null
+++ b/visad/data/hdfeos/Variable.java
@@ -0,0 +1,119 @@
+//
+// Variable.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+
+public class Variable
+{
+  String  name;
+  int rank;
+  int type;
+  DimensionSet  dimSet;
+  Calibration calibration;
+  boolean coordVar = false;
+
+  Variable( String name,
+            DimensionSet dimSet,
+            int rank,
+            int type,
+            Calibration calibration )
+  throws HdfeosException
+  {
+    if ( dimSet.getSize() != rank )
+    {
+      throw new HdfeosException(" rank and DimensionSet length don't match");
+    }
+
+    this.name = name;
+    this.dimSet = dimSet;
+    this.type = type;
+    this.rank = rank;
+    this.calibration = calibration;
+
+    if ( (rank == 1) &&
+         (name.equalsIgnoreCase((dimSet.getElement(0)).getName())) )
+    {
+      coordVar = true;
+    }
+  }
+
+  public String getName()
+  {
+    String name = this.name;
+    return name;
+  }
+
+  public int getRank()
+  {
+    return rank;
+  }
+
+  public Calibration getCalibration()
+  {
+    return calibration;
+  }
+
+  public boolean equals( Variable obj )
+  {
+    if( this.name.equals( obj.getName()) )
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  public DimensionSet getDimSet()
+  {
+    return dimSet;
+  }
+
+  public NamedDimension getDim( int ii )
+  {
+    return dimSet.getElement( ii );
+  }
+
+  public int getNumberType()
+  {
+    return this.type;
+  }
+
+  public boolean isCoordVar()
+  {
+    return coordVar;
+  }
+
+  public String toString()
+  {
+    String str = "Variable:  "+name+"\n"+
+                 "    rank:  "+rank+"\n"+
+                 "    type:  "+type+"\n"+"  "+dimSet.toString()+"\n";
+    return str;
+  }
+}
diff --git a/visad/data/hdfeos/VariableSet.java b/visad/data/hdfeos/VariableSet.java
new file mode 100644
index 0000000..983c202
--- /dev/null
+++ b/visad/data/hdfeos/VariableSet.java
@@ -0,0 +1,141 @@
+//
+// VariableSet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos;
+
+import java.util.*;
+
+public class VariableSet
+{
+  Vector varSet;
+  private boolean finished = false;
+
+  VariableSet()
+  {
+    varSet = new Vector();
+  }
+
+  public void add( Variable var )
+  {
+    varSet.addElement( var );
+  }
+
+  public void setToFinished()
+  {
+    finished = true;
+  }
+
+  public int getSize()
+  {
+    int size = varSet.size();
+    return size;
+  }
+
+  public Variable getElement( int ii )
+  {
+    Variable obj = (Variable)varSet.elementAt( ii );
+    return obj;
+  }
+
+  public Variable[] getElements()
+  {
+    Variable[] vars = new Variable[getSize()];
+    for ( int ii = 0; ii < getSize(); ii++ ) {
+      vars[ii] = getElement(ii);
+    }
+    return vars;
+  }
+
+  public Variable getByName( String varName )
+  {
+    int size = this.getSize();
+
+    for ( int ii = 0; ii < size; ii++ )
+    {
+      Variable obj = (Variable) varSet.elementAt(ii);
+
+      String name = obj.getName();
+
+      if (  name.equals( varName ) ) {
+        return obj;
+      }
+    }
+    return null;
+  }
+
+  public VariableSet getSubset( DimensionSet d_set )
+  {
+    VariableSet v_set = new VariableSet();
+
+    for ( int ii = 0; ii < this.getSize(); ii++ )
+    {
+      if( ((this.getElement(ii)).getDimSet()).sameSetSameOrder( d_set ) )
+      {
+        v_set.add( this.getElement(ii) );
+      }
+    }
+
+    if ( v_set.getSize() == 0 ) {
+      return null;
+    }
+    else {
+      return v_set;
+    }
+  }
+
+  public Variable isCoordVar( int index )
+  {
+    Variable var = getElement( index );
+    if ( var.isCoordVar() ) {
+      return var;
+    }
+    else {
+      return null;
+    }
+  }
+
+  public boolean isEmpty()
+  {
+    return varSet.isEmpty();
+  }
+
+  public Enumeration getEnum()
+  {
+    Enumeration e = varSet.elements();
+    return e;
+  }
+
+  public String toString()
+  {
+    String str = "VariableSet: \n";
+
+    for ( int ii = 0; ii < this.getSize(); ii++ )
+    {
+      str = str + "  "+((this.getElement(ii)).toString())+"\n";
+    }
+    return str;
+  }
+}
diff --git a/visad/data/hdfeos/hdfeosc/EHchkfidImp.c b/visad/data/hdfeos/hdfeosc/EHchkfidImp.c
new file mode 100644
index 0000000..479208a
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/EHchkfidImp.c
@@ -0,0 +1,65 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_EHchkfid
+( JNIEnv *env,
+  jclass class,
+  jint file_id,
+  jstring struct_name,
+  jintArray HDFfid,
+  jintArray sdInterfaceId,
+  jbyteArray access
+                     )
+{
+
+  char *f_name;
+  int32 stat;
+
+  jint *j_fid;
+  jint *j_sid;
+  jbyte *j_acc;
+  jboolean bb;
+
+     j_fid = (jint *) (*env)->GetIntArrayElements( env, HDFfid, &bb );
+     j_sid = (jint *) (*env)->GetIntArrayElements( env, sdInterfaceId, &bb );
+     j_acc = (jbyte *) (*env)->GetByteArrayElements( env, access, &bb );
+
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, struct_name, 0);
+
+     stat = EHchkfid( (int32)file_id, (char *)f_name, (int32 *)j_fid, (int32 *)j_sid, (uint8 *)j_acc );
+
+     (*env)->ReleaseStringUTFChars(env, struct_name, f_name );
+     (*env)->ReleaseIntArrayElements( env, HDFfid, j_fid, JNI_COMMIT);
+     (*env)->ReleaseIntArrayElements( env, sdInterfaceId, j_sid, JNI_COMMIT);
+     (*env)->ReleaseByteArrayElements( env, access, j_acc, JNI_COMMIT);
+
+
+   return stat;
+}
diff --git a/visad/data/hdfeos/hdfeosc/EHcloseImp.c b/visad/data/hdfeos/hdfeosc/EHcloseImp.c
new file mode 100644
index 0000000..0d2afe5
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/EHcloseImp.c
@@ -0,0 +1,43 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_EHclose
+( JNIEnv *env,
+  jclass class,
+  jint file_id
+               )
+{
+
+  int32 status;
+
+  status = EHclose( (int32)file_id );
+
+  return status;
+
+ }
diff --git a/visad/data/hdfeos/hdfeosc/EHgetcalImp.c b/visad/data/hdfeos/hdfeosc/EHgetcalImp.c
new file mode 100644
index 0000000..ebc3478
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/EHgetcalImp.c
@@ -0,0 +1,72 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_EHgetcal
+( JNIEnv *env,
+  jclass class,
+  jint sd_id,
+  jint sds_idx,
+  jdoubleArray cal,
+  jdoubleArray cal_err,
+  jdoubleArray off,
+  jdoubleArray off_err,
+  jintArray type
+                     )
+{
+
+  int32 sds_id;
+  int32 status;
+
+  jdouble *j_cal;
+  jdouble *j_cal_err;
+  jdouble *j_off;
+  jdouble *j_off_err;
+  jint *j_type;
+  jboolean bb;
+
+     j_cal = (jdouble *) (*env)->GetDoubleArrayElements( env, cal, &bb );
+     j_cal_err = (jdouble *) (*env)->GetDoubleArrayElements( env, cal_err, &bb );
+     j_off = (jdouble *) (*env)->GetDoubleArrayElements( env, off, &bb );
+     j_off_err = (jdouble *) (*env)->GetDoubleArrayElements( env, off_err, &bb );
+     j_type = (jint *) (*env)->GetIntArrayElements( env, type, &bb );
+
+     sds_id = SDselect( (int32)sd_id, (int32)sds_idx );
+
+     status = SDgetcal( (int32)sds_id, (double *)j_cal, (double *)j_cal_err,
+                                       (double *)j_off, (double *)j_off_err, (int32 *)j_type );
+
+     (*env)->ReleaseDoubleArrayElements( env, cal, j_cal, JNI_COMMIT);
+     (*env)->ReleaseDoubleArrayElements( env, cal_err, j_cal_err, JNI_COMMIT);
+     (*env)->ReleaseDoubleArrayElements( env, off, j_off, JNI_COMMIT);
+     (*env)->ReleaseDoubleArrayElements( env, off_err, j_off_err, JNI_COMMIT);
+     (*env)->ReleaseIntArrayElements( env, type, j_type, JNI_COMMIT);
+
+
+   return status;
+}
diff --git a/visad/data/hdfeos/hdfeosc/GDattachImp.c b/visad/data/hdfeos/hdfeosc/GDattachImp.c
new file mode 100644
index 0000000..c437292
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDattachImp.c
@@ -0,0 +1,44 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDattach
+( JNIEnv *env, jclass class, jint file_id, jstring grid_name )  {
+
+  char *f_name;
+  int32 grid_id;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, grid_name, 0);
+
+     grid_id = GDattach( (int32)file_id, (char *)f_name );
+
+     (*env)->ReleaseStringUTFChars(env, grid_name, f_name );
+
+   return grid_id;
+
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDfdims.c b/visad/data/hdfeos/hdfeosc/GDfdims.c
new file mode 100644
index 0000000..f5e3304
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDfdims.c
@@ -0,0 +1,230 @@
+/*
+Copyright (C) 1996 Hughes and Applied Research Corporation
+
+Permission to use, modify, and distribute this software and its documentation
+for any purpose without fee is hereby granted, provided that the above
+copyright notice appear in all copies and that both that copyright notice and
+this permission notice appear in supporting documentation.
+*/
+
+
+#include "mfhdf.h"
+#include "hcomp.h"
+#include <math.h>
+#include "cfortHdf.h"
+#include "HdfEosDef.h"
+
+#define GDIDOFFSET 4194304
+
+
+int32 GDXSDcomb[512*5];
+char  GDXSDname[HDFE_NAMBUFSIZE];
+char  GDXSDdims[HDFE_DIMBUFSIZE];
+
+
+#define NGRID 200
+/* Grid Structure External Arrays */
+struct gridStructure
+{
+    int32 active;
+    int32 IDTable;
+    int32 VIDTable[2];
+    int32 fid;
+    int32 nSDS;
+    int32 *sdsID;
+    int32 compcode;
+    intn  compparm[5];
+    int32 tilecode;
+    int32 tilerank;
+    int32 tiledims[8];
+};
+struct gridStructure GDXGrid[NGRID];
+
+
+
+#define NGRIDREGN 256
+struct gridRegion
+{
+    int32 fid;
+    int32 gridID;
+    int32 xStart;
+    int32 xCount;
+    int32 yStart;
+    int32 yCount;
+    float64 upleftpt[2];
+    float64 lowrightpt[2];
+    int32 StartVertical[8];
+    int32 StopVertical[8];
+    char *DimNamePtr[8];
+};
+struct gridRegion *GDXRegion[NGRIDREGN];
+
+
+
+/* Grid Function Prototypes (internal routines) */
+intn GDchkgdid(int32, char *, int32 *, int32 *, int32 *);
+intn GDfldinfo(int32, char *, int32 *, int32 [], int32 *, char *);
+intn GDdeffld(int32, char *, char *, int32, int32);
+intn GDwrmeta(int32, char *, char *, int32);
+intn GDSDfldsrch(int32, int32, char *, int32 *, int32 *,
+                 int32 *, int32 *, int32 [], int32 *);
+intn GDwrrdfield(int32, char *, char *,
+            int32 [], int32 [], int32 [], VOIDP datbuf);
+intn GDwrfld(int32, char *, int32 [], int32 [], int32 [], VOIDP);
+intn GDrdfld(int32, char *, int32 [], int32 [], int32 [], VOIDP);
+intn GDwrrdattr(int32, char *, int32, int32, char *, VOIDP);
+intn GDll2ij(int32, int32, float64 [], int32, int32, int32, float64 [],
+             float64 [], int32, float64 [], float64 [], int32 [], int32 [],
+             float64 [], float64 []);
+intn GDij2ll(int32, int32, float64 [], int32, int32, int32,
+             float64 [], float64 [], int32, int32 [], int32 [],
+             float64 [], float64 [], int32, int32);
+intn GDreginfo(int32, int32, char *, int32 *, int32 *, int32 [], int32 *,
+               float64 [], float64 []);
+intn  GDgetdefaults(int32, int32, float64 [], int32, float64 [], float64 []);
+int32 GDdefvrtreg(int32, int32, char *, float64 []);
+intn GDgetpix(int32, int32, float64 [], float64 [], int32 [], int32 []);
+int32 GDgetpixval(int32, int32, int32 [], int32 [], char *, VOIDP);
+intn GDtangentpnts(int32, float64 [], float64 [], float64 [], float64 [],
+                   float64 [], int32 *);
+intn GDwrrdtile(int32, char *, char *, int32 [], VOIDP);
+intn GDdeftle(int32, int32, int32, int32 []);
+intn GDtleinfo(int32, char *, int32 *, int32 *, int32 []);
+intn GDwrtle(int32, char *, int32 [], VOIDP);
+intn GDrdtle(int32, char *, int32 [],  VOIDP);
+
+/*----------------------------------------------------------------------------|
+|  BEGIN_PROLOG                                                               |
+|                                                                             |
+|  FUNCTION: GDfdims                                                          |
+|                                                                             |
+|  DESCRIPTION: Retrieve information about a specific geolocation or data     |
+|                field in the grid.                                           |
+|                                                                             |
+|                                                                             |
+|  Return Value    Type     Units     Description                             |
+|  ============   ======  =========   =====================================   |
+|  status         intn                return status (0) SUCCEED, (-1) FAIL    |
+|                                                                             |
+|  INPUTS:                                                                    |
+|  gridID         int32               grid structure id                       |
+|  fieldname      char                name of field                           |
+|                                                                             |
+|                                                                             |
+|  OUTPUTS:                                                                   |
+|  rank           int32               rank of field (# of dims)               |
+|  dims           int32               field dimensions                        |
+|  numbertype     int32               field number type                       |
+|  dimlist        char                field dimension list                    |
+|                                                                             |
+|                                                                             |
+|  OUTPUTS:                                                                   |
+|             None                                                            |
+|                                                                             |
+|  NOTES:                                                                     |
+|                                                                             |
+|                                                                             |
+|   Date     Programmer   Description                                         |
+|  ======   ============  =================================================   |
+|  Jun 96   Joel Gales    Original Programmer                                 |
+|  Aug 96   Joel Gales    Make metadata ODL compliant                         |
+|  Jan 97   Joel Gales    Check for metadata error status from EHgetmetavalue |
+|                                                                             |
+|  END_PROLOG                                                                 |
+-----------------------------------------------------------------------------*/
+int32
+GDfdims(int32 gridID, char *fieldname, int32 * strbufsize )
+
+{
+    intn            i;		/* Loop index */
+    intn            status;	/* routine return status variable */
+    intn            statmeta = 0;	/* EHgetmetavalue return status */
+
+    int32           fid;	/* HDF-EOS file ID */
+    int32           sdInterfaceID;	/* HDF SDS interface ID */
+    int32           idOffset = GDIDOFFSET;	/* Grid ID offset */
+    int32           ndims;	/* Number of dimensions */
+    int32           slen[8];	/* Length of each entry in parsed string */
+    int32           dum;	/* Dummy variable */
+    int32           xdim;	/* X dim size */
+    int32           ydim;	/* Y dim size */
+    int32           sdid;	/* SDS id */
+
+    char           *metabuf;	/* Pointer to structural metadata (SM) */
+    char           *metaptrs[2];/* Pointers to begin and end of SM section */
+    char            gridname[80];	/* Grid Name */
+    char            utlstr[80];	/* Utility string */
+    char           *ptr[8];	/* String pointers for parsed string */
+    char            dimstr[64];	/* Individual dimension entry string */
+
+    *strbufsize = -1;
+          ndims = -1;
+
+    status = GDchkgdid(gridID, "GDfieldinfo", &fid, &sdInterfaceID, &dum);
+
+    Vgetname(GDXGrid[gridID % idOffset].IDTable, gridname);
+
+	metabuf = (char *) EHmetagroup(sdInterfaceID, gridname, "g",
+				       "DataField", metaptrs);
+
+
+	/* Search for field */
+	sprintf(utlstr, "%s%s%s", "\"", fieldname, "\"\n");
+	metaptrs[0] = strstr(metaptrs[0], utlstr);
+
+	/* If field found ... */
+	if (metaptrs[0] < metaptrs[1] && metaptrs[0] != NULL)
+	{
+
+	    /*
+	     * Get DimList string and trim off leading and trailing parens
+	     * "()"
+	     */
+	    statmeta = EHgetmetavalue(metaptrs, "DimList", utlstr);
+
+	    if (statmeta == 0)
+	    {
+		memcpy(utlstr, utlstr + 1, strlen(utlstr) - 2);
+		utlstr[strlen(utlstr) - 2] = 0;
+
+		/* Parse trimmed DimList string and get rank */
+		ndims = EHparsestr(utlstr, ',', ptr, slen);
+
+            /*
+             * Copy each entry in DimList and remove leading and trailing quotes,
+             * Get dimension sizes and concatanate dimension names to dimension
+             * list
+             */
+            for (i = 0; i < ndims; i++)
+            {
+                memcpy(dimstr, ptr[i] + 1, slen[i] - 2);
+                dimstr[slen[i] - 2] = 0;
+
+                  if (i > 0)
+                  {
+                      *strbufsize += 1;
+                  }
+
+                *strbufsize += strlen( dimstr );
+             }
+
+	    }
+	    else
+	    {
+		status = -1;
+		HEpush(DFE_GENAPP, "GDfieldinfo", __FILE__, __LINE__);
+		HEreport(
+			 "\"DimList\" string not found in metadata.\n");
+	    }
+
+        }
+	free(metabuf);
+
+        if (*strbufsize == -1)
+        {
+	  ndims= -1;
+
+        }
+
+    return ( ndims );
+}
diff --git a/visad/data/hdfeos/hdfeosc/GDfdimsImp.c b/visad/data/hdfeos/hdfeosc/GDfdimsImp.c
new file mode 100644
index 0000000..eaa8e86
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDfdimsImp.c
@@ -0,0 +1,54 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDfdims
+( JNIEnv *env,
+  jclass class,
+  jint grid_id,
+  jstring fieldname,
+  jintArray strbufsize ) {
+
+  int32  size;
+  int32  n_dims;
+  char *f_name;
+  jint *body;
+  jboolean bb;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     n_dims = GDfdims( (int32)grid_id, (char *)f_name, (int32 *)&size );
+
+       body = (jint *) (*env)->GetIntArrayElements( env, strbufsize, &bb);
+       body[0] = size;
+
+       (*env)->ReleaseIntArrayElements( env, strbufsize, body, JNI_COMMIT);
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) n_dims;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDfieldinfoImp.c b/visad/data/hdfeos/hdfeosc/GDfieldinfoImp.c
new file mode 100644
index 0000000..caa68b3
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDfieldinfoImp.c
@@ -0,0 +1,74 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDfieldinfo
+(JNIEnv *env,
+ jclass class,
+ jint grid_id,
+ jstring fieldname,
+ jstring D_List,
+ jintArray rank,
+ jintArray lengths,
+ jintArray type  )  {
+
+  int32  status;
+  jint *j_rank;
+  jint *j_type;
+  jint *j_lengths;
+  jboolean bb;
+  jstring j_new;
+  char c_array[1024];
+  char *f_name;
+  char *name;
+  int32 ii;
+  int32 len = 0;
+
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_rank = (jint *) (*env)->GetIntArrayElements( env, rank, &bb);
+     j_type = (jint *) (*env)->GetIntArrayElements( env, type, &bb);
+     j_lengths = (jint *) (*env)->GetIntArrayElements( env, lengths, &bb);
+
+     status = GDfieldinfo( (int32)grid_id, (char *)f_name, (int32 *)j_rank,
+                           (int32 *)j_lengths, (int32 *)j_type, (char *)c_array );
+
+
+       j_new = (*env)->NewStringUTF(env, c_array );
+
+       (*env)->SetObjectArrayElement(env, D_List, 0, (jobject)j_new);
+
+       (*env)->ReleaseIntArrayElements( env, rank, j_rank, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, type, j_type, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, lengths, j_lengths, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDgridinfoImp.c b/visad/data/hdfeos/hdfeosc/GDgridinfoImp.c
new file mode 100644
index 0000000..15b72e0
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDgridinfoImp.c
@@ -0,0 +1,61 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDgridinfo
+(JNIEnv *env,
+ jclass class,
+ jint grid_id,
+ jintArray xdimsize,
+ jintArray ydimsize,
+ jdoubleArray upleftpt,
+ jdoubleArray lowrightpt )  {
+
+  int32  stat;
+  jint *j_xsiz;
+  jint *j_ysiz;
+  jdouble *j_uprR;
+  jdouble *j_lwrL;
+  jboolean bb;
+
+     j_xsiz = (jint *) (*env)->GetIntArrayElements( env, xdimsize, &bb );
+     j_ysiz = (jint *) (*env)->GetIntArrayElements( env, ydimsize, &bb );
+     j_uprR = (jdouble *) (*env)->GetDoubleArrayElements( env, upleftpt, &bb );
+     j_lwrL = (jdouble *) (*env)->GetDoubleArrayElements( env, lowrightpt, &bb );
+
+     stat = GDgridinfo( (int32)grid_id, (int32 *)j_xsiz, (int32 *)j_ysiz,
+                                        (double *)j_uprR, (double *)j_lwrL );
+
+
+       (*env)->ReleaseIntArrayElements( env, xdimsize, j_xsiz, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, ydimsize, j_ysiz, JNI_COMMIT);
+       (*env)->ReleaseDoubleArrayElements( env, upleftpt, j_uprR, JNI_COMMIT);
+       (*env)->ReleaseDoubleArrayElements( env, lowrightpt, j_lwrL, JNI_COMMIT);
+
+   return (jint) stat;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDinqattrsImp.c b/visad/data/hdfeos/hdfeosc/GDinqattrsImp.c
new file mode 100644
index 0000000..5a132b7
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDinqattrsImp.c
@@ -0,0 +1,61 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDinqattrs
+( JNIEnv *env,
+  jclass class,
+  jint grid_id,
+  jstring attr_list
+                    )
+{
+
+  int32 n_attrs;
+  char *grid_names;
+  int32 strbufsize;
+  jstring j_new;
+
+
+    n_attrs = GDinqattrs( (int32)grid_id, NULL, (int32 *)&strbufsize );
+
+    grid_names = (char *)malloc((size_t)strbufsize+1);
+
+    n_attrs = GDinqattrs( (int32)grid_id, (char *)grid_names, (int32 *)&strbufsize );
+
+    grid_names[ strbufsize ] = '\0';
+
+    j_new = (*env)->NewStringUTF( env, grid_names );
+
+    free( grid_names );
+
+    (*env)->SetObjectArrayElement(env, attr_list, 0, (jobject)j_new );
+
+
+   return n_attrs;
+
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDinqdimsImp.c b/visad/data/hdfeos/hdfeosc/GDinqdimsImp.c
new file mode 100644
index 0000000..369ce30
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDinqdimsImp.c
@@ -0,0 +1,59 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDinqdims
+( JNIEnv *env,
+  jclass class,
+  jint grid_id,
+  jint strbufsize,
+  jstring dimList,
+  jintArray dimLengths ) {
+
+  int32  n_dims;
+  jint *j_lengths;
+  jboolean bb;
+  jstring j_new;
+  char *c_ptr;
+  char *attr_list;
+  char *bufalloc;
+
+     c_ptr = (char *)malloc((size_t)strbufsize+1);
+
+     j_lengths = (jint *) (*env)->GetIntArrayElements( env, dimLengths, &bb );
+
+     n_dims = GDinqdims( (int32)grid_id, (char *)c_ptr, (int32 *)j_lengths );
+
+       j_new = (*env)->NewStringUTF(env, c_ptr );
+       free( c_ptr );
+
+       (*env)->SetObjectArrayElement(env, dimList, 0, (jobject)j_new);
+       (*env)->ReleaseIntArrayElements( env, dimLengths, j_lengths, JNI_COMMIT);
+
+   return (jint) n_dims;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDinqfieldsImp.c b/visad/data/hdfeos/hdfeosc/GDinqfieldsImp.c
new file mode 100644
index 0000000..5c454b5
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDinqfieldsImp.c
@@ -0,0 +1,72 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDinqfields
+(JNIEnv *env,
+ jclass class,
+ jint grid_id,
+ jint strbufsize,
+ jstring F_List,
+ jintArray F_ranks,
+ jintArray F_types  )  {
+
+  int32  n_fields;
+  jint *bodyA;
+  jint *bodyB;
+  jboolean bb;
+  jstring j_new;
+  char *c_ptr;
+  char *attr_list;
+  char *bufalloc;
+  char *Dfield_list;
+  char *Gfield_list;
+  char *name;
+  int32 *ranks;
+  int32 rank;
+  int32 type;
+  int32 *types;
+  int32 ii;
+
+
+     c_ptr = (char *)malloc((size_t)strbufsize+1);
+
+     bodyA = (jint *) (*env)->GetIntArrayElements( env, F_ranks, &bb);
+     bodyB = (jint *) (*env)->GetIntArrayElements( env, F_types, &bb);
+
+     n_fields = GDinqfields( (int32)grid_id, (char *)c_ptr, (int32 *)bodyA, (int32 *)bodyB );
+
+       j_new = (*env)->NewStringUTF(env, c_ptr );
+       free( c_ptr );
+
+       (*env)->SetObjectArrayElement(env, F_List, 0, (jobject)j_new);
+       (*env)->ReleaseIntArrayElements( env, F_ranks, bodyA, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, F_types, bodyB, JNI_COMMIT);
+
+   return (jint) n_fields;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDinqgridImp.c b/visad/data/hdfeos/hdfeosc/GDinqgridImp.c
new file mode 100644
index 0000000..3ea7232
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDinqgridImp.c
@@ -0,0 +1,60 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDinqgrid
+( JNIEnv *env, jclass class, jstring filename, jstring name_list )  {
+
+  int32 n_grids;
+  char *grid_names;
+  int32 strbufsize;
+  char *f_name;
+  jstring j_new;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, filename, 0);
+
+     n_grids = GDinqgrid( (char *)f_name, NULL, (int32 *)&strbufsize );
+
+     grid_names = (char *)malloc((size_t)strbufsize+1);
+
+     n_grids = GDinqgrid( (char *)f_name, (char *)grid_names, (int32 *)&strbufsize );
+
+     grid_names[ strbufsize ] = '\0';
+
+     j_new = (*env)->NewStringUTF( env, grid_names );
+
+     free( grid_names );
+
+    (*env)->SetObjectArrayElement(env, name_list,0,(jobject)j_new );
+
+    (*env)->ReleaseStringUTFChars(env, filename, f_name );
+
+
+   return n_grids;
+
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDnentriesImp.c b/visad/data/hdfeos/hdfeosc/GDnentriesImp.c
new file mode 100644
index 0000000..3b22004
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDnentriesImp.c
@@ -0,0 +1,50 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDnentries
+( JNIEnv *env,
+  jclass class,
+  jint grid_id,
+  jint HDFE_mode,
+  jintArray strbufsize ) {
+
+  int32  size;
+  int32  n_entries;
+  jint *body;
+  jboolean bb;
+
+     n_entries = GDnentries( (int32)grid_id, (int32)HDFE_mode, (int32 *)&size );
+
+       body = (jint *) (*env)->GetIntArrayElements( env, strbufsize, &bb);
+       body[0] = size;
+
+       (*env)->ReleaseIntArrayElements( env, strbufsize, body, JNI_COMMIT);
+
+   return (jint) n_entries;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDopenImp.c b/visad/data/hdfeos/hdfeosc/GDopenImp.c
new file mode 100644
index 0000000..b62fe5d
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDopenImp.c
@@ -0,0 +1,45 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDopen
+( JNIEnv *env, jclass class, jstring filename, jint access )  {
+
+  int32 file_id;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, filename, 0);
+
+     file_id = GDopen( (char *)f_name, (int32)access );
+
+     (*env)->ReleaseStringUTFChars(env, filename, f_name );
+
+
+   return file_id;
+
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDprojinfoImp.c b/visad/data/hdfeos/hdfeosc/GDprojinfoImp.c
new file mode 100644
index 0000000..f5d8cf1
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDprojinfoImp.c
@@ -0,0 +1,61 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDprojinfo
+(JNIEnv *env,
+ jclass class,
+ jint grid_id,
+ jintArray projcode,
+ jintArray zonecode,
+ jintArray spherecode,
+ jdoubleArray projparms  )  {
+
+  int32  stat;
+  jint *j_proj;
+  jint *j_zone;
+  jint *j_sphr;
+  jdouble *j_parm;
+  jboolean bb;
+
+     j_proj = (jint *) (*env)->GetIntArrayElements( env, projcode, &bb );
+     j_zone = (jint *) (*env)->GetIntArrayElements( env, zonecode, &bb );
+     j_sphr = (jint *) (*env)->GetIntArrayElements( env, spherecode, &bb );
+     j_parm = (jdouble *) (*env)->GetDoubleArrayElements( env, projparms, &bb );
+
+     stat = GDprojinfo( (int32)grid_id, (int32 *)j_proj, (int32 *)j_zone,
+                                        (int32 *)j_sphr, (double *)j_parm );
+
+
+       (*env)->ReleaseIntArrayElements( env, projcode, j_proj, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, zonecode, j_zone, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, spherecode, j_sphr, JNI_COMMIT);
+       (*env)->ReleaseDoubleArrayElements( env, projparms, j_parm, JNI_COMMIT);
+
+   return (jint) stat;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/GDreadfieldImp.c b/visad/data/hdfeos/hdfeosc/GDreadfieldImp.c
new file mode 100644
index 0000000..6c76dbb
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GDreadfieldImp.c
@@ -0,0 +1,228 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDreadfield__ILjava_lang_String_2_3I_3I_3I_3F
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jfloatArray data )  {
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jfloat *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jfloat *) (*env)->GetFloatArrayElements( env, data, &bb);
+
+     status = GDreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (float *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseFloatArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+  }
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDreadfield__ILjava_lang_String_2_3I_3I_3I_3D
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jdoubleArray data )  {
+
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jdouble *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jdouble *) (*env)->GetDoubleArrayElements( env, data, &bb);
+
+     status = GDreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (double *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseDoubleArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+  }
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDreadfield__ILjava_lang_String_2_3I_3I_3I_3I
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jintArray data )  {
+
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jint *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jint *) (*env)->GetIntArrayElements( env, data, &bb);
+
+     status = GDreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (int *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+  }
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDreadfield__ILjava_lang_String_2_3I_3I_3I_3S
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jshortArray data )  {
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jshort *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jshort *) (*env)->GetShortArrayElements( env, data, &bb);
+
+     status = GDreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (short *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseShortArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+}
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GDreadfield__ILjava_lang_String_2_3I_3I_3I_3B
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jbyteArray data )  {
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jbyte *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jbyte *) (*env)->GetByteArrayElements( env, data, &bb);
+
+     status = GDreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (char *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseByteArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+}
diff --git a/visad/data/hdfeos/hdfeosc/GetNumericAttrImp.c b/visad/data/hdfeos/hdfeosc/GetNumericAttrImp.c
new file mode 100644
index 0000000..6ceda90
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/GetNumericAttrImp.c
@@ -0,0 +1,134 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_GetNumericAttr
+( JNIEnv *env,
+  jclass class,
+  jint sd_id,
+  jstring sds_name,
+  jstring attr_name,
+  jdoubleArray attr
+                     )
+{
+
+  int32 sds_id;
+  int32 sds_idx;
+  int32 status;
+  int32 attr_idx;
+  char *a_name;
+  char *s_name;
+  void *data;
+  float *f_data;
+  double *d_data;
+  int32 *i_data;
+  int *l_data;
+  int ii;
+  short *s_data;
+  int32 n_type[1];
+  int32 count[1];
+
+  jdouble *j_attr;
+  jboolean bb;
+
+     a_name = (char *) (*env)->GetStringUTFChars( env, attr_name, 0);
+     s_name = (char *) (*env)->GetStringUTFChars( env, sds_name, 0);
+     j_attr = (jdouble *) (*env)->GetDoubleArrayElements( env, attr, &bb );
+
+     sds_idx = SDnametoindex( (int32)sd_id, s_name );
+       if ( sds_idx < 0 )
+       {
+          return ( -4 );
+       }
+     sds_id = SDselect( (int32)sd_id, (int32)sds_idx );
+       if ( sds_id < 0 ) {
+         return ( -1 );
+       }
+     attr_idx = SDfindattr( (int32)sds_id, (char *)a_name );
+       if ( attr_idx < 0 ) {
+         return ( -2 );
+       }
+     status = SDattrinfo( (int32)sds_id, (int32)attr_idx, (char *)a_name, (int32 *)n_type, (int32 *)count );
+       if ( status < 0 ) {
+         return ( -3 );
+       }
+
+
+     if ( n_type[0] == 5 ) {
+        f_data = ( float *) malloc( count[0]*sizeof( float ) );
+        status = SDreadattr( sds_id, attr_idx, (void *)f_data );
+        for ( ii = 0; ii < count[0]; ii++ ) {
+          j_attr[ii] = (jdouble) f_data[ii];
+        }
+        free( f_data );
+     }
+     else if ( n_type[0] == 6 ) {
+        d_data = ( double *) malloc( count[0]*sizeof( double ) );
+        status = SDreadattr( sds_id, attr_idx, (void *)d_data );
+        for ( ii = 0; ii < count[0]; ii++ ) {
+          j_attr[ii] = (jdouble) d_data[ii];
+        }
+        free( d_data );
+     }
+     else if (( n_type[0] == 22) || ( n_type[0] == 23)) {
+        s_data = ( short *) malloc( count[0]*sizeof( short ) );
+        status = SDreadattr( sds_id, attr_idx, (void *)s_data );
+        for ( ii = 0; ii < count[0]; ii++ ) {
+          j_attr[ii] = (jdouble) s_data[ii];
+        }
+        free( s_data );
+     }
+     else if (( n_type[0] == 24) || ( n_type[0] == 25 )) {
+        i_data = ( int32 *) malloc( count[0]*sizeof( int32 ) );
+        status = SDreadattr( sds_id, attr_idx, (void *)i_data );
+        for ( ii = 0; ii < count[0]; ii++ ) {
+          j_attr[ii] = (jdouble) i_data[ii];
+        }
+        free( i_data );
+     }
+     else if (( n_type[0] == 26) || (n_type[0] == 27 )) {
+        l_data = ( int *) malloc( count[0]*sizeof( int ) );
+        status = SDreadattr( sds_id, attr_idx, (void *)l_data );
+        for ( ii = 0; ii < count[0]; ii++ ) {
+          j_attr[0] = (jdouble) l_data[0];
+        }
+        free( l_data );
+     }
+     else {
+        return (-7);
+     }
+
+
+     (*env)->ReleaseDoubleArrayElements( env, attr, j_attr, JNI_COMMIT);
+
+     (*env)->ReleaseStringUTFChars( env, attr_name, a_name );
+     (*env)->ReleaseStringUTFChars( env, sds_name, s_name );
+
+
+   return (status);
+}
diff --git a/visad/data/hdfeos/hdfeosc/HdfeosLib.java b/visad/data/hdfeos/hdfeosc/HdfeosLib.java
new file mode 100644
index 0000000..1340624
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/HdfeosLib.java
@@ -0,0 +1,120 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hdfeos.hdfeosc;
+
+public class HdfeosLib
+{
+   static
+   {
+     System.loadLibrary("hdfeos");
+   }
+
+   public final static int DFACC_READ = 1;
+   public final static int G_MAPS = 1;
+   public final static int D_FIELDS = 4;
+   public final static int G_FIELDS = 3;
+   public final static int N_DIMS = 0;
+   public final static String G_TYPE = "Geolocation Fields";
+   public final static String D_TYPE = "Data Fields";
+   public final static int HDFE_mode = 4;
+   public final static int BYTE = 20;
+   public final static int U_BYTE = 21;
+   public final static int SHORT = 22;
+   public final static int U_SHORT = 23;
+   public final static int DOUBLE = 6;
+   public final static int INT = 24;
+   public final static int FLOAT = 5;
+
+   public native static int EHclose( int file_id );
+
+   public native static int EHchkfid( int file_id, String name, int[] HDFfid, int[] sd_id, byte[] acc );
+
+   public native static int EHgetcal( int sd_id, int sds_idx, double[] cal, double[] cal_err, double[] off, double[] off_err, int[] type );
+
+   public native static int GetNumericAttr( int sd_id, String sds_name, String attr_name, double[] value);
+
+   public native static int SDattrinfo( int sd_id, String sds_name, String attr_name );
+
+   public native static int GDinqattrs( int grid_id, String[] attr_list );
+
+   public native static int GDprojinfo( int grid_id, int[] proj, int[] zone, int[] sphr, double[] parm );
+
+   public native static int GDgridinfo( int grid_id, int[] xsiz, int[] ysiz, double[] uprL, double[] lwrR );
+
+   public native static int SWinqswath( String filename, String[] name_list );
+
+   public native static int GDinqgrid( String filename, String[] name_list );
+
+   public native static int SWinqdims( int swath_id, int size, String[] dimList, int[] lengths );
+
+   public native static int GDinqdims( int grid_id, int size, String[] dimList, int[] lengths );
+
+   public native static int SWopen( String filename, int access );
+
+   public native static int GDopen( String filename, int access );
+
+   public native static int SWattach( int file_id, String swath_name );
+
+   public native static int GDattach( int file_id, String grid_name );
+
+   public native static int SWinqdatafields( int swath_id, int size, String[] list, int[] ranks, int[] types );
+
+   public native static int SWinqgeofields( int swath_id, int size, String[] list, int[] ranks, int[] types );
+
+   public native static int GDinqfields( int grid_id, int size, String[] list, int[] ranks, int[] types );
+
+   public native static int SWinqmaps( int swath_id, int size, String[] maps, int[] offsets, int[] increments );
+
+   public native static int SWnentries( int swath_id, int HDFE_mode, int[] strSize );
+
+   public native static int GDnentries( int grid_id, int HDFE_mode, int[] strSize );
+
+   public native static int SWfieldinfo( int swath_id, String name, String[] list, int[] rank, int[] length, int[] type );
+
+   public native static int GDfieldinfo( int grid_id, String name, String[] list, int[] rank, int[] length, int[] type );
+
+   public native static int SWfdims( int swath_id, String type, String name, int[] strSize );
+
+   public native static int GDfdims( int swath_id, String name, int[] strSize );
+
+   public native static int SWreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, float[] data );
+
+   public native static int SWreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, double[] data );
+
+   public native static int SWreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, int[] data );
+
+   public native static int SWreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, short[] data );
+
+   public native static int SWreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, byte[] data );
+
+   public native static int GDreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, float[] data );
+
+   public native static int GDreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, double[] data );
+
+   public native static int GDreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, int[] data );
+
+   public native static int GDreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, short[] data );
+
+   public native static int GDreadfield( int swath_id, String name, int[] start, int[] stride, int[] edge, byte[] data );
+
+}
diff --git a/visad/data/hdfeos/hdfeosc/Makefile b/visad/data/hdfeos/hdfeosc/Makefile
new file mode 100644
index 0000000..f27f4f3
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/Makefile
@@ -0,0 +1,137 @@
+SLIB = hdfeos
+SLIBDIR = .
+
+# JAVADIR should be the top-level JDK1.2 directory
+# JAVAINCS should list all the necessary include files under JAVADIR
+#
+JAVADIR = /opt/java
+J_INC =  ${JAVADIR}/include
+JAVAINCS = -I${J_INC} -I${J_INC}/${ARCH}
+JH = javah
+
+HDFINCS = -I${HDF_INC}
+HDFEOSINCS = -I${HDFEOS_INC}
+
+HDFLIBS = $(HDF_LIB)/libmfhdf.a $(HDF_LIB)/libdf.a $(HDF_LIB)/libjpeg.a $(HDF_LIB)/libz.a
+HDFEOSLIBS = $(HDFEOS_LIB)/libhdfeos.a $(HDFEOS_LIB)/libGctp.a ./libhdfeos_ext.a
+
+SHCFLAGS =
+
+CC = cc
+AR = ar -r
+
+LD = $(CC)
+
+OBJECTS = SWopenImp.o \
+	SWattachImp.o SWinqswathImp.o SWinqdimsImp.o \
+	SWinqdatafieldsImp.o SWinqgeofieldsImp.o SWinqmapsImp.o \
+	SWnentriesImp.o SWfieldinfoImp.o SWfdimsImp.o \
+	SWreadfieldImp.o GDattachImp.o GDnentriesImp.o \
+	GDinqdimsImp.o GDinqfieldsImp.o GDfdimsImp.o \
+	GDfieldinfoImp.o GDopenImp.o GDinqgridImp.o \
+	GDprojinfoImp.o GDgridinfoImp.o GDinqattrsImp.o \
+	EHchkfidImp.o EHcloseImp.o GDreadfieldImp.o \
+	EHgetcalImp.o GetNumericAttrImp.o SDattrinfoImp.o
+
+.SUFFIXES : .java .class
+
+.java.class:
+	$(JC) $(JFLAGS) $<
+
+default: all
+
+all:: HdfeosLib.class
+
+all:: visad_data_hdfeos_hdfeosc_HdfeosLib.h
+
+visad_data_hdfeos_hdfeosc_HdfeosLib.h:
+	$(JH) -jni visad.data.hdfeos.hdfeosc.HdfeosLib
+
+all::
+	@UNAME=`uname -s`; \
+	case "$$UNAME" in \
+	IRIX*) make irix;; \
+	SunOS*) make solaris;; \
+	Linux*) make linux;; \
+	*) echo "Unknown system type $$UNAME"; exit 1;; \
+	esac
+
+hdfeos_ext: SWfdims.o GDfdims.o
+	$(AR) libhdfeos_ext.a SWfdims.o GDfdims.o
+
+SWfdims.o: SWfdims.c
+	$(CC) $(SHCFLAGS) -c SWfdims.c $(HDFINCS) $(HDFEOSINCS)
+
+GDfdims.o: GDfdims.c
+	$(CC) $(SHCFLAGS) -c GDfdims.c $(HDFINCS) $(HDFEOSINCS)
+
+.c.o:
+	$(CC) $(SHCFLAGS) -c $*.c $(JAVAINCS) $(HDFINCS) $(HDFEOSINCS)
+
+hdfeosLib: $(OBJECTS) hdfeos_ext
+	$(LD) $(LDOPT) $(OBJECTS) $(HDFEOSLIBS) $(HDFLIBS) $(SYSLIBS) -o $(SLIBDIR)/lib$(SLIB).so
+
+clean:
+	rm -f so_locations
+	rm -f $(SLIBDIR)/lib$(SLIB).so
+	rm -f libhdfeos_ext.a
+	rm -f *.o
+	rm -f *.class
+
+required_macros:
+	@if [ -z "$(HDF_INC)" ]; then \
+	  echo ""; \
+	  echo "  HDF_INC is undefined."; \
+	  echo ""; \
+	  echo "  (Don't worry, this is normal.  It just means the optional"; \
+	  echo "   HDF-EOSC library won't be supported.)"; \
+	  echo ""; \
+	  echo "  This 'make' will self-destruct in 1 second"; \
+	  echo ""; \
+	  exit 1; \
+	fi
+	@if [ -z "$(HDF_LIB)" ]; then \
+	  echo ""; \
+	  echo "  HDF_LIB is undefined."; \
+	  echo ""; \
+	  echo "  (Don't worry, this is normal.  It just means the optional"; \
+	  echo "   HDF-EOSC library won't be supported.)"; \
+	  echo ""; \
+	  echo "  This 'make' will self-destruct in 1 second"; \
+	  echo ""; \
+	  exit 1; \
+	fi
+	@if [ -z "$(HDFEOS_INC)" ]; then \
+	  echo ""; \
+	  echo "  HDFEOS_INC is undefined."; \
+	  echo ""; \
+	  echo "  (Don't worry, this is normal.  It just means the optional"; \
+	  echo "   HDF-EOSC library won't be supported.)"; \
+	  echo ""; \
+	  echo "  This 'make' will self-destruct in 1 second"; \
+	  echo ""; \
+	  exit 1; \
+	fi
+	@if [ -z "$(HDFEOS_LIB)" ]; then \
+	  echo ""; \
+	  echo "  HDFEOS_LIB is undefined."; \
+	  echo ""; \
+	  echo "  (Don't worry, this is normal.  It just means the optional"; \
+	  echo "   HDF-EOSC library won't be supported.)"; \
+	  echo ""; \
+	  echo "  This 'make' will self-destruct in 1 second"; \
+	  echo ""; \
+	  exit 1; \
+	fi
+
+solaris: required_macros
+	@make ARCH=solaris SYSLIBS="-lnsl -lm" LDOPT="-G" hdfeosLib
+
+irix: required_macros
+	@make ARCH=irix SYSLIBS="-lm" LDOPT="-shared -Wl,-x" hdfeosLib
+
+linux: required_macros
+	@make ARCH=linux SYSLIBS="-lm" SHCFLAGS="-fPIC -DPIC" LDOPT="-shared" hdfeosLib
+
+aix: required_macros
+	@make ARCH=aix SYSLIBS="-lm -lc" LDOPT="-bE:shrsub.exp -bM:SRE -bnoentry" hdfeosLib
diff --git a/visad/data/hdfeos/hdfeosc/SDattrinfoImp.c b/visad/data/hdfeos/hdfeosc/SDattrinfoImp.c
new file mode 100644
index 0000000..9ed2a04
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SDattrinfoImp.c
@@ -0,0 +1,78 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SDattrinfo
+( JNIEnv *env,
+  jclass class,
+  jint sd_id,
+  jstring sds_name,
+  jstring attr_name
+                     )
+{
+
+  int32 sds_id;
+  int32 sds_idx;
+  int32 status;
+  int32 attr_idx;
+  char *a_name;
+  char *s_name;
+  void *data;
+  int32 n_type[1];
+  int32 count[1];
+
+  jdouble *j_attr;
+  jboolean bb;
+
+     a_name = (char *) (*env)->GetStringUTFChars( env, attr_name, 0);
+     s_name = (char *) (*env)->GetStringUTFChars( env, sds_name, 0);
+
+     sds_idx = SDnametoindex( (int32)sd_id, s_name );
+       if ( sds_idx < 0 ) {
+         return ( -4 );
+       }
+     sds_id = SDselect( (int32)sd_id, (int32)sds_idx );
+       if ( sds_id < 0 ) {
+         return ( -1 );
+       }
+     attr_idx = SDfindattr( (int32)sds_id, (char *)a_name );
+       if ( attr_idx < 0 ) {
+         return ( -2 );
+       }
+     status = SDattrinfo( (int32)sds_id, (int32)attr_idx, (char *)a_name, (int32 *)n_type, (int32 *)count );
+       if ( status < 0 ) {
+         return ( -3 );
+       }
+
+
+     (*env)->ReleaseStringUTFChars( env, attr_name, a_name );
+     (*env)->ReleaseStringUTFChars( env, sds_name, s_name );
+
+
+   return ( count[0] );
+}
diff --git a/visad/data/hdfeos/hdfeosc/SWattachImp.c b/visad/data/hdfeos/hdfeosc/SWattachImp.c
new file mode 100644
index 0000000..3f9c316
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWattachImp.c
@@ -0,0 +1,48 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWattach
+( JNIEnv *env,
+  jclass class,
+  jint file_id,
+  jstring swath_name )
+{
+
+  char *f_name;
+  int32 swath_id;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, swath_name, 0);
+
+     swath_id = SWattach( (int32)file_id, (char *)f_name );
+
+     (*env)->ReleaseStringUTFChars(env, swath_name, f_name );
+
+   return swath_id;
+
+  }
diff --git a/visad/data/hdfeos/hdfeosc/SWfdims.c b/visad/data/hdfeos/hdfeosc/SWfdims.c
new file mode 100644
index 0000000..fa9b44e
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWfdims.c
@@ -0,0 +1,233 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include "mfhdf.h"
+#include "hcomp.h"
+#include "cfortHdf.h"
+#include "HdfEosDef.h"
+
+#define SWIDOFFSET 1048576
+
+
+int32 SWX1dcomb[512*3];
+int32 SWXSDcomb[512*5];
+char  SWXSDname[HDFE_NAMBUFSIZE];
+char  SWXSDdims[HDFE_DIMBUFSIZE];
+
+
+#define NSWATH 200
+/* Swath Structure External Arrays */
+struct swathStructure
+{
+    int32 active;
+    int32 IDTable;
+    int32 VIDTable[3];
+    int32 fid;
+    int32 nSDS;
+    int32 *sdsID;
+    int32 compcode;
+    intn  compparm[5];
+    int32 tilecode;
+    int32 tilerank;
+    int32 tiledims[8];
+};
+struct swathStructure SWXSwath[NSWATH];
+
+
+
+#define NSWATHREGN 256
+struct swathRegion
+{
+    int32 fid;
+    int32 swathID;
+    int32 nRegions;
+    int32 StartRegion[32];
+    int32 StopRegion[32];
+    int32 StartVertical[8];
+    int32 StopVertical[8];
+    char *DimNamePtr[8];
+};
+struct swathRegion *SWXRegion[NSWATHREGN];
+
+/* Swath Prototypes (internal routines) */
+intn SWchkswid(int32, char *, int32 *, int32 *, int32 *);
+int32 SWimapinfo(int32, char *, char *, int32 []);
+int32 SWfinfo(int32, char *, char *, int32 *, int32 [], int32 *, char *);
+intn SWfldinfo(int32, char *, int32 *, int32 [], int32 *, char *);
+intn SWdefimap(int32, char *, char *, int32 []);
+intn SWdefinefield(int32, char *, char *, char *, int32, int32);
+intn SWdefgfld(int32, char *, char *, int32, int32);
+intn SWdefdfld(int32, char *, char *, int32, int32);
+intn SWwrgmeta(int32, char *, char *, int32);
+intn SWwrdmeta(int32, char *, char *, int32);
+intn SWwrrdattr(int32, char *, int32, int32, char *, VOIDP);
+intn SW1dfldsrch(int32, int32, char *, char *, int32 *, int32 *, int32 *);
+intn SWSDfldsrch(int32, int32, char *, int32 *, int32 *,
+                 int32 *, int32 *, int32 [], int32 *);
+intn SWwrrdfield(int32, char *, char *, int32 [], int32 [], int32 [], VOIDP);
+intn SWwrfld(int32, char *, int32 [], int32 [], int32 [], VOIDP);
+intn SWrdfld(int32, char *, int32 [], int32 [], int32 [], VOIDP);
+intn SWreginfo(int32, int32, char *, int32 *, int32 *, int32 [], int32 *);
+intn SWperinfo(int32, int32, char *, int32 *, int32 *, int32 [], int32 *);
+int32 SWinqfields(int32, char *, char *, int32 [], int32 []);
+int32 SWdefvrtreg(int32, int32, char *, float64 []);
+
+/*----------------------------------------------------------------------------|
+|  BEGIN_PROLOG                                                               |
+|                                                                             |
+|  FUNCTION: SWfdims                                                          |
+|                                                                             |
+|  DESCRIPTION: Returns field info                                            |
+|                                                                             |
+|                                                                             |
+|  Return Value    Type     Units     Description                             |
+|  ============   ======  =========   =====================================   |
+|  ndims          int32               return status (0) SUCCEED, (-1) FAIL    |
+|                                                                             |
+|  INPUTS:                                                                    |
+|  swathID        int32               swath structure id                      |
+|  fieldtype      char                fieldtype (geo or data)                 |
+|  fieldname      char                name of field                           |
+|                                                                             |
+|                                                                             |
+|  OUTPUTS:                                                                   |
+|  strbufsize     int32               size of dimlist                         |
+|                                                                             |
+|  NOTES:                                                                     |
+|                                                                             |
+|                                                                             |
+|   Date     Programmer   Description                                         |
+|  ======   ============  =================================================   |
+|  Jun 96   Joel Gales    Original Programmer                                 |
+|  Aug 96   Joel Gales    Make metadata ODL compliant                         |
+|  Jan 97   Joel Gales    Check for metadata error status from EHgetmetavalue |
+|                                                                             |
+|  END_PROLOG                                                                 |
+-----------------------------------------------------------------------------*/
+int32
+SWfdims(int32 swathID, char *fieldtype, char *fieldname,
+        int32 * strbufsize )
+
+{
+    intn            i;		/* Loop index */
+    intn            j;		/* Loop index */
+    intn            status;	/* routine return status variable */
+    intn            statmeta = 0;	/* EHgetmetavalue return status */
+
+    int32           fid;	/* HDF-EOS file ID */
+    int32           sdInterfaceID;	/* HDF SDS interface ID */
+    int32           idOffset = SWIDOFFSET;	/* Swath ID offset */
+    int32           fsize;	/* field size in bytes */
+    int32           ndims;	/* Number of dimensions */
+    int32           slen[8];	/* Length of each entry in parsed string */
+    int32           dum;	/* Dummy variable */
+    int32           vdataID;	/* 1d field vdata ID */
+
+
+    char           *metabuf;	/* Pointer to structural metadata (SM) */
+    char           *metaptrs[2];/* Pointers to begin and end of SM section */
+    char            swathname[80];	/* Swath Name */
+    char            utlstr[80];	/* Utility string */
+    char           *ptr[8];	/* String pointers for parsed string */
+    char            dimstr[64];	/* Individual dimension entry string */
+
+
+    *strbufsize = -1;
+          ndims = -1;
+
+    /* Get HDF-EOS file ID and SDS interface ID */
+    status = SWchkswid(swathID, "SWfinfo", &fid, &sdInterfaceID, &dum);
+
+    /* Get swath name */
+    Vgetname(SWXSwath[swathID % idOffset].IDTable, swathname);
+
+    /* Get pointers to appropriate "Field" section within SM */
+    if (strcmp(fieldtype, "Geolocation Fields") == 0)
+    {
+	metabuf = (char *) EHmetagroup(sdInterfaceID, swathname, "s",
+				       "GeoField", metaptrs);
+    }
+    else
+    {
+	metabuf = (char *) EHmetagroup(sdInterfaceID, swathname, "s",
+				       "DataField", metaptrs);
+    }
+
+
+    /* Search for field */
+    sprintf(utlstr, "%s%s%s", "\"", fieldname, "\"\n");
+    metaptrs[0] = strstr(metaptrs[0], utlstr);
+
+    /* If field found ... */
+    if (metaptrs[0] < metaptrs[1] && metaptrs[0] != NULL)
+    {
+
+	/*
+	 * Get DimList string and trim off leading and trailing parens "()"
+	 */
+	statmeta = EHgetmetavalue(metaptrs, "DimList", utlstr);
+
+	if (statmeta == 0)
+	{
+	    memcpy(utlstr, utlstr + 1, strlen(utlstr) - 2);
+	    utlstr[strlen(utlstr) - 2] = 0;
+
+	    /* Parse trimmed DimList string and get rank */
+	    ndims = EHparsestr(utlstr, ',', ptr, slen);
+
+
+            /*
+             * Copy each entry in DimList and remove leading and trailing quotes,
+             * Get dimension sizes and concatanate dimension names to dimension
+             * list
+             */
+            for (i = 0; i < ndims; i++)
+            {
+                memcpy(dimstr, ptr[i] + 1, slen[i] - 2);
+                dimstr[slen[i] - 2] = 0;
+
+                  if (i > 0)
+                  {
+                      *strbufsize += 1;
+                  }
+
+                *strbufsize += strlen( dimstr );
+             }
+
+	}
+	else
+	{
+	    HEpush(DFE_GENAPP, "SWfieldinfo", __FILE__, __LINE__);
+	    HEreport(
+		     "\"DimList\" string not found in metadata.\n");
+	}
+
+    }
+    free(metabuf);
+
+    if (*strbufsize == -1)
+    {
+	ndims = -1;
+    }
+
+    return ( ndims );
+}
diff --git a/visad/data/hdfeos/hdfeosc/SWfdimsImp.c b/visad/data/hdfeos/hdfeosc/SWfdimsImp.c
new file mode 100644
index 0000000..b6cdc76
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWfdimsImp.c
@@ -0,0 +1,59 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWfdims
+( JNIEnv *env,
+  jclass class,
+  jint swath_id,
+  jstring fieldtype,
+  jstring fieldname,
+  jintArray strbufsize )
+{
+
+  int32  size;
+  int32  n_dims;
+  char *f_name;
+  char *f_type;
+  jint *body;
+  jboolean bb;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+     f_type = (char *) (*env)->GetStringUTFChars( env, fieldtype, 0);
+
+     n_dims = SWfdims( (int32)swath_id, (char *)f_type, (char *)f_name, (int32 *)&size );
+
+       body = (jint *) (*env)->GetIntArrayElements( env, strbufsize, &bb);
+       body[0] = size;
+
+       (*env)->ReleaseIntArrayElements( env, strbufsize, body, JNI_COMMIT);
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+       (*env)->ReleaseStringUTFChars( env, fieldtype, f_type );
+
+   return (jint) n_dims;
+}
diff --git a/visad/data/hdfeos/hdfeosc/SWfieldinfoImp.c b/visad/data/hdfeos/hdfeosc/SWfieldinfoImp.c
new file mode 100644
index 0000000..26912ee
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWfieldinfoImp.c
@@ -0,0 +1,72 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWfieldinfo
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring filename,
+ jstring D_List,
+ jintArray rank,
+ jintArray lengths,
+ jintArray type  )  {
+
+  int32  status;
+  jint *j_rank;
+  jint *j_type;
+  jint *j_lengths;
+  jboolean bb;
+  jstring j_new;
+  char c_array[1024];
+  char *f_name;
+  char *name;
+  int32 ii;
+
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, filename, 0);
+
+     j_rank = (jint *) (*env)->GetIntArrayElements( env, rank, &bb);
+     j_type = (jint *) (*env)->GetIntArrayElements( env, type, &bb);
+     j_lengths = (jint *) (*env)->GetIntArrayElements( env, lengths, &bb);
+
+     status = SWfieldinfo( (int32)swath_id, (char *)f_name, (int32 *)j_rank,
+                           (int32 *)j_lengths, (int32 *)j_type, (char *)c_array );
+
+       j_new = (*env)->NewStringUTF(env, c_array );
+
+       (*env)->SetObjectArrayElement(env, D_List, 0, (jobject)j_new);
+
+       (*env)->ReleaseIntArrayElements( env, rank, j_rank, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, type, j_type, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, lengths, j_lengths, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, filename, f_name );
+
+   return (jint) status;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/SWinqdatafieldsImp.c b/visad/data/hdfeos/hdfeosc/SWinqdatafieldsImp.c
new file mode 100644
index 0000000..df08a7e
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWinqdatafieldsImp.c
@@ -0,0 +1,73 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWinqdatafields
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jint strbufsize,
+ jstring F_List,
+ jintArray F_ranks,
+ jintArray F_types  )  {
+
+  int32  n_fields;
+  jint *bodyA;
+  jint *bodyB;
+  jboolean bb;
+  jstring j_new;
+  char *c_ptr;
+  char *swath_name;
+  char *attr_list;
+  char *bufalloc;
+  char *Dfield_list;
+  char *Gfield_list;
+  char *name;
+  int32 *ranks;
+  int32 rank;
+  int32 type;
+  int32 *types;
+  int32 ii;
+
+
+     c_ptr = (char *)malloc((size_t)strbufsize+1);
+
+     bodyA = (jint *) (*env)->GetIntArrayElements( env, F_ranks, &bb);
+     bodyB = (jint *) (*env)->GetIntArrayElements( env, F_types, &bb);
+
+     n_fields = SWinqdatafields( (int32)swath_id, (char *)c_ptr, (int32 *)bodyA, (int32 *)bodyB );
+
+       j_new = (*env)->NewStringUTF(env, c_ptr );
+       free( c_ptr );
+
+       (*env)->SetObjectArrayElement(env, F_List, 0, (jobject)j_new);
+       (*env)->ReleaseIntArrayElements( env, F_ranks, bodyA, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, F_types, bodyB, JNI_COMMIT);
+
+   return (jint) n_fields;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/SWinqdimsImp.c b/visad/data/hdfeos/hdfeosc/SWinqdimsImp.c
new file mode 100644
index 0000000..9c7b19e
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWinqdimsImp.c
@@ -0,0 +1,62 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWinqdims
+( JNIEnv *env,
+  jclass class,
+  jint swath_id,
+  jint strbufsize,
+  jstring dimList,
+  jintArray dimLengths ) {
+
+  int32  n_dims;
+  jint *j_lengths;
+  jboolean bb;
+  jstring j_new;
+  char *c_ptr;
+  char *swath_name;
+  char *attr_list;
+  char *bufalloc;
+  char *Dfield_list;
+  char *Gfield_list;
+
+     c_ptr = (char *)malloc((size_t)strbufsize+1);
+
+     j_lengths = (jint *) (*env)->GetIntArrayElements( env, dimLengths, &bb );
+
+     n_dims = SWinqdims( (int32)swath_id, (char *)c_ptr, (int32 *)j_lengths );
+
+       j_new = (*env)->NewStringUTF(env, c_ptr );
+       free( c_ptr );
+
+       (*env)->SetObjectArrayElement(env, dimList, 0, (jobject)j_new);
+       (*env)->ReleaseIntArrayElements( env, dimLengths, j_lengths, JNI_COMMIT);
+
+   return (jint) n_dims;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/SWinqgeofieldsImp.c b/visad/data/hdfeos/hdfeosc/SWinqgeofieldsImp.c
new file mode 100644
index 0000000..9e09735
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWinqgeofieldsImp.c
@@ -0,0 +1,68 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWinqgeofields
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jint strbufsize,
+ jstring F_List,
+ jintArray F_ranks,
+ jintArray F_types  )  {
+
+  int32  *i_ptrA;
+  int32  *i_ptrB;
+  int32  n_fields;
+  jint *bodyA;
+  jint *bodyB;
+  jboolean bb;
+  jstring j_new;
+  char *c_ptr;
+  int32 *ranks;
+  int32 rank;
+  int32 type;
+  int32 *types;
+  int32 ii;
+
+     c_ptr = (char *)malloc((size_t)strbufsize+1);
+
+     bodyA = (jint *) (*env)->GetIntArrayElements( env, F_ranks, &bb);
+     bodyB = (jint *) (*env)->GetIntArrayElements( env, F_types, &bb);
+
+     n_fields = SWinqgeofields( (int32)swath_id, (char *)c_ptr, (int32 *)bodyA, (int32 *)bodyB );
+
+       j_new = (*env)->NewStringUTF(env, c_ptr );
+       free( c_ptr );
+
+       (*env)->SetObjectArrayElement(env, F_List, 0, (jobject)j_new);
+       (*env)->ReleaseIntArrayElements( env, F_ranks, bodyA, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, F_types, bodyB, JNI_COMMIT);
+
+   return (jint) n_fields;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/SWinqmapsImp.c b/visad/data/hdfeos/hdfeosc/SWinqmapsImp.c
new file mode 100644
index 0000000..9cbd144
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWinqmapsImp.c
@@ -0,0 +1,68 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWinqmaps
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jint strbufsize,
+ jstring M_List,
+ jintArray offsets,
+ jintArray increments  )  {
+
+  int32  n_maps;
+  jint *j_off;
+  jint *j_inc;
+  jboolean bb;
+  jstring j_new;
+  char *c_ptr;
+  char *swath_name;
+  char *name;
+  int32 *ranks;
+  int32 rank;
+  int32 type;
+  int32 *types;
+  int32 ii;
+
+     c_ptr = (char *)malloc((size_t)strbufsize+1);
+
+     j_off = (jint *) (*env)->GetIntArrayElements( env, offsets, &bb );
+     j_inc = (jint *) (*env)->GetIntArrayElements( env, increments, &bb );
+
+     n_maps = SWinqmaps( (int32)swath_id, (char *)c_ptr, (int32 *)j_off, (int32 *)j_inc );
+
+       j_new = (*env)->NewStringUTF(env, c_ptr );
+       free( c_ptr );
+
+       (*env)->SetObjectArrayElement(env, M_List, 0, (jobject)j_new);
+       (*env)->ReleaseIntArrayElements( env, offsets, j_off, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, increments, j_inc, JNI_COMMIT);
+
+   return (jint) n_maps;
+  }
diff --git a/visad/data/hdfeos/hdfeosc/SWinqswathImp.c b/visad/data/hdfeos/hdfeosc/SWinqswathImp.c
new file mode 100644
index 0000000..871b1ca
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWinqswathImp.c
@@ -0,0 +1,64 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWinqswath
+( JNIEnv *env,
+  jclass class,
+  jstring filename,
+  jstring name_list )
+{
+
+  int32 n_swaths;
+  char *swath_names;
+  int32 strbufsize;
+  char *f_name;
+  jstring j_new;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, filename, 0);
+
+     n_swaths = SWinqswath( (char *)f_name, NULL, (int32 *)&strbufsize );
+
+     swath_names = (char *)malloc((size_t)strbufsize+1);
+
+     n_swaths = SWinqswath( (char *)f_name, (char *)swath_names, (int32 *)&strbufsize );
+
+     swath_names[ strbufsize ] = '\0';
+
+     j_new = (*env)->NewStringUTF( env, swath_names );
+
+     free( swath_names );
+
+    (*env)->SetObjectArrayElement(env, name_list,0,(jobject)j_new );
+
+    (*env)->ReleaseStringUTFChars(env, filename, f_name );
+
+
+   return n_swaths;
+
+}
diff --git a/visad/data/hdfeos/hdfeosc/SWnentriesImp.c b/visad/data/hdfeos/hdfeosc/SWnentriesImp.c
new file mode 100644
index 0000000..124360e
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWnentriesImp.c
@@ -0,0 +1,51 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWnentries
+( JNIEnv *env,
+  jclass class,
+  jint swath_id,
+  jint HDFE_mode,
+  jintArray strbufsize )
+{
+
+  int32  size;
+  int32  n_entries;
+  jint *body;
+  jboolean bb;
+
+     n_entries = SWnentries( (int32)swath_id, (int32)HDFE_mode, (int32 *)&size );
+
+       body = (jint *) (*env)->GetIntArrayElements( env, strbufsize, &bb);
+       body[0] = size;
+
+       (*env)->ReleaseIntArrayElements( env, strbufsize, body, JNI_COMMIT);
+
+   return (jint) n_entries;
+}
diff --git a/visad/data/hdfeos/hdfeosc/SWopenImp.c b/visad/data/hdfeos/hdfeosc/SWopenImp.c
new file mode 100644
index 0000000..40031fc
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWopenImp.c
@@ -0,0 +1,46 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWopen
+( JNIEnv *env, jclass class, jstring filename, jint access )
+{
+
+  int32 file_id;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, filename, 0);
+
+     file_id = SWopen( (char *)f_name, (int32)access );
+
+     (*env)->ReleaseStringUTFChars(env, filename, f_name );
+
+
+   return file_id;
+
+}
diff --git a/visad/data/hdfeos/hdfeosc/SWreadfieldImp.c b/visad/data/hdfeos/hdfeosc/SWreadfieldImp.c
new file mode 100644
index 0000000..ec5d973
--- /dev/null
+++ b/visad/data/hdfeos/hdfeosc/SWreadfieldImp.c
@@ -0,0 +1,229 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+#include <jni.h>
+#include "visad_data_hdfeos_hdfeosc_HdfeosLib.h"
+#include <stdio.h>
+#include "mfhdf.h"
+#include "HdfEosDef.h"
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWreadfield__ILjava_lang_String_2_3I_3I_3I_3F
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jfloatArray data )  {
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jfloat *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jfloat *) (*env)->GetFloatArrayElements( env, data, &bb);
+
+     status = SWreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (float *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseFloatArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+  }
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWreadfield__ILjava_lang_String_2_3I_3I_3I_3D
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jdoubleArray data )  {
+
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jdouble *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jdouble *) (*env)->GetDoubleArrayElements( env, data, &bb);
+
+     status = SWreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (double *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseDoubleArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+  }
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWreadfield__ILjava_lang_String_2_3I_3I_3I_3I
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jintArray data )  {
+
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jint *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jint *) (*env)->GetIntArrayElements( env, data, &bb);
+
+     status = SWreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (int *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+  }
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWreadfield__ILjava_lang_String_2_3I_3I_3I_3S
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jshortArray data )  {
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jshort *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jshort *) (*env)->GetShortArrayElements( env, data, &bb);
+
+     status = SWreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (short *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseShortArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+}
+
+JNIEXPORT jint JNICALL
+Java_visad_data_hdfeos_hdfeosc_HdfeosLib_SWreadfield__ILjava_lang_String_2_3I_3I_3I_3B
+(JNIEnv *env,
+ jclass class,
+ jint swath_id,
+ jstring fieldname,
+ jintArray start,
+ jintArray stride,
+ jintArray edge,
+ jbyteArray data )  {
+
+  int32  status;
+  jint *j_start;
+  jint *j_stride;
+  jint *j_edge;
+  jbyte *j_data;
+  jboolean bb;
+  char *f_name;
+
+     f_name = (char *) (*env)->GetStringUTFChars( env, fieldname, 0);
+
+     j_start = (jint *) (*env)->GetIntArrayElements( env, start, &bb);
+     j_stride = (jint *) (*env)->GetIntArrayElements( env, stride, &bb);
+     j_edge = (jint *) (*env)->GetIntArrayElements( env, edge, &bb);
+     j_data = (jbyte *) (*env)->GetByteArrayElements( env, data, &bb);
+
+     status = SWreadfield( (int32)swath_id, (char *)f_name, (int32 *)j_start,
+                           (int32 *)j_stride, (int32 *)j_edge, (char *)j_data );
+
+
+       (*env)->ReleaseIntArrayElements( env, start, j_start, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, stride, j_stride, JNI_COMMIT);
+       (*env)->ReleaseIntArrayElements( env, edge, j_edge, JNI_COMMIT);
+       (*env)->ReleaseByteArrayElements( env, data, j_data, JNI_COMMIT);
+
+       (*env)->ReleaseStringUTFChars( env, fieldname, f_name );
+
+   return (jint) status;
+}
diff --git a/visad/data/hdfeos/package.html b/visad/data/hdfeos/package.html
new file mode 100644
index 0000000..fd7b775
--- /dev/null
+++ b/visad/data/hdfeos/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides for importing an HDF-EOS dataset into VisAD.
+
+</body>
+</html>
+
diff --git a/visad/data/hrit/HRITAdapter.java b/visad/data/hrit/HRITAdapter.java
new file mode 100644
index 0000000..8970189
--- /dev/null
+++ b/visad/data/hrit/HRITAdapter.java
@@ -0,0 +1,722 @@
+//
+// HRITAdapter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.hrit;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import edu.wisc.ssec.mcidas.AREAnav;
+import edu.wisc.ssec.mcidas.Calibrator;
+import edu.wisc.ssec.mcidas.CalibratorException;
+import edu.wisc.ssec.mcidas.CalibratorMsg;
+
+import visad.CoordinateSystem;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Linear2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.util.Util;
+
+/** 
+ * This is an adapter for HRIT format data files 
+ * At present, it will only work on MSG2 data, but with 
+ * some work should be able to handle most HRIT data.
+ */
+
+public class HRITAdapter {
+
+  private FlatField field = null;
+  private static final int HEADER_TYPE_PRIMARY_HEADER = 0;
+  private static final int HEADER_TYPE_IMAGE_STRUCTURE = 1;
+  private static final int HEADER_TYPE_IMAGE_NAVIGATION = 2;
+  private static final int PRIMARY_HEADER_LENGTH = 16;
+  
+  // record sizes for prologue file sections - used to offset to cal
+  private static final int SAT_STAT_LEN = 60134;
+  private static final int IMG_ACQ_LEN = 700;
+  private static final int CEL_EVENTS_LEN = 326058;
+  private static final int IMG_DESC_LEN = 101;
+  private static final int CAL_OFFS = 72;
+  
+  private static final int SPACECRAFT_ID_MSG2 = 322;
+  private static final int SPACECRAFT_ID_MSG3 = 323;
+
+  /**
+   * Create a VisAD FlatField from local HRIT file(s).  This constructor 
+   * is included for backward compatibility but should not be used and 
+   * should be phased out in future revisions since it does not make a 
+   * valid band number determination.
+   * 
+   * @param filenames array of file names
+   * @param magFactor magnification factor
+   * @exception IOException if there was a problem reading the file(s).
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  
+  public HRITAdapter(String [] filenames, int magFactor) 
+  	throws IOException, VisADException
+  {
+	  this (
+		 filenames, magFactor, Calibrator.CAL_BRIT, 1
+	  );
+  }
+
+  /** 
+   * Create a VisAD FlatField from local HRIT file(s).
+   * @param filenames names of local files.
+   * @param magFactor magnification factor
+   * @param calType calibration type
+   * @param bandNum band number
+   * @exception IOException if there was a problem reading the file(s).
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  
+  public HRITAdapter(String [] filenames, int magFactor, int calType, int bandNum)
+	throws IOException, VisADException
+  {
+	  // set new mag factor if necessary
+	  if ((magFactor != 1) &&
+	      (magFactor != 2) &&
+	      (magFactor != 4) &&
+	      (magFactor != 8) &&
+	      (magFactor != 16)) {
+		  throw new VisADException("Invalid magnification factor for HRIT: " + magFactor);
+	  }
+	  
+	  // Initial sanity checks on input file names
+	  
+	  // null parameter
+	  if (filenames == null) {
+		  throw new IOException("No filenames specified");
+	  }
+	  
+	  // TODO: practical limit on number of input files?
+	  
+	  // HRIT filename syntax checks.  If those pass, do basic file integrity check
+	  for (int i = 0; i < filenames.length; i++) {
+		  
+		  // have to do the null check here too, since a single array element could be
+		  if (filenames[i] == null) {
+			  throw new IOException("File name in array position " + (i + 1) + " is null");
+		  }
+		  
+		  // TODO: determine the correct regular expression here
+		  //if (! filenames[i].matches("IMG.*")) {
+			//  throw new IOException("File: " + filenames[i] + " violates HRIT naming convention");
+		  //}
+		  
+		  // make sure each file exists - there is almost no I/O overhead to do this check
+		  File f = new File(filenames[i]);
+		  if (! f.exists()) {
+			  throw new IOException("File in array position " + (i + 1) + " does not exist");
+		  }
+		  
+	  }
+	  
+	  // at this point we have file(s) that initially look ok, time to look closer
+	  int [] imageSegmentLines = new int[filenames.length];
+	  int [] imageSegmentElements = new int[filenames.length];
+	  int [] imageBitsPerPixel = new int[filenames.length];
+	  int [] lengthAllHeaders = new int[filenames.length];
+	  int [] lineOffset = new int[filenames.length];
+	  int minLineOffset = Integer.MAX_VALUE;
+	  int columnOffset = -1;
+	  int lineScalingFactor = -1;
+	  int columnScalingFactor = -1;
+	  
+	  // only used if we find 10 bit data in a file
+	  byte [] tenBitInputArray = null;
+	  short [] tenBitOutputArray = null;
+	  for (int i = 0; i < filenames.length; i++) {
+		  
+		  // open a stream to the file 
+		  File f = new File(filenames[i]);
+		  FileInputStream fis = new FileInputStream(f);
+		  
+		  // try to pull out the primary header
+		  byte [] primaryHeader = new byte[PRIMARY_HEADER_LENGTH];
+		  int bytesRead = fis.read(primaryHeader);
+		  if ((bytesRead < 0) || (bytesRead != PRIMARY_HEADER_LENGTH)) {
+			  fis.close();
+			  throw new IOException("File " + filenames[i] + " is not an HRIT file");
+		  }
+		  
+		  // validate primary header contents
+		  int headerSize = bytesToShort(primaryHeader, 1);
+		  if (headerSize != PRIMARY_HEADER_LENGTH) {
+			  fis.close();
+			  throw new IOException("File " + filenames[i] + " is not a valid HRIT file");
+		  }
+		  
+		  // make sure file is at least as long as the claimed length of all headers
+		  lengthAllHeaders[i] = bytesToInt(primaryHeader, 4);
+		  if (f.length() < lengthAllHeaders[i]) {
+			  fis.close();
+			  throw new IOException("File " + filenames[i] + " is not a valid HRIT file");
+		  }
+		  
+		  // dumpHeader(primaryHeader);
+		  // ok, we got the primary header, moving along to the other headers...
+		  int headerBytesConsumed = PRIMARY_HEADER_LENGTH;
+		  byte [] headerType = new byte[1];
+		  byte [] headerLength = new byte[2];
+		  while (headerBytesConsumed < lengthAllHeaders[i]) {
+			  bytesRead = fis.read(headerType);
+			  headerBytesConsumed += bytesRead;
+			  bytesRead = fis.read(headerLength);
+			  headerBytesConsumed += bytesRead;
+			  headerSize = bytesToShort(headerLength, 0);
+			  byte [] header = new byte[headerSize - 3];
+			  bytesRead = fis.read(header);
+			  headerBytesConsumed += bytesRead;
+			  // System.out.println("Header type: " + unsignedByteToInt(headerType[0]));
+			  // System.out.println("Length of this header: " + headerSize);
+			  // for image structure headers, pull out image size
+			  if (Util.unsignedByteToInt(headerType[0]) == HEADER_TYPE_IMAGE_STRUCTURE) {
+				  imageSegmentLines[i] = bytesToShort(header, 3);
+				  imageSegmentElements[i] = bytesToShort(header, 1);
+				  imageBitsPerPixel[i] = Util.unsignedByteToInt(header[0]);
+				  // System.out.println("Image bits per pixel: " + imageBitsPerPixel[i]);
+				  // System.out.println("Image #Lines: " + imageSegmentLines[i] + ", #Elements: " + imageSegmentElements[i]);
+			  }
+			  // for navigation headers, print relevant data
+			  if (Util.unsignedByteToInt(headerType[0]) == HEADER_TYPE_IMAGE_NAVIGATION) {
+				  String projectionName = new String(header, 0, 32);
+				  projectionName = projectionName.trim();
+				  columnScalingFactor = bytesToInt(header, 32);
+				  if (columnScalingFactor < 0) columnScalingFactor = -columnScalingFactor;
+				  lineScalingFactor = bytesToInt(header, 36);
+				  if (lineScalingFactor < 0) lineScalingFactor = -lineScalingFactor;
+				  columnOffset = bytesToInt(header, 40);
+				  lineOffset[i] = bytesToInt(header, 44);
+				  // keep track of minimum line offset seen
+				  if (minLineOffset > lineOffset[i]) {
+					  minLineOffset = lineOffset[i];
+				  }
+				  // System.out.println("Projection: " + projectionName + 
+				  //	  ", lsf: " + lineScalingFactor + ", csf: " + columnScalingFactor +
+				  //	  ", co: " + columnOffset + ", lo: " + lineOffset[i]);
+			  }
+		  }
+		  
+		  fis.close();
+	  }
+	  
+	  // make the VisAD RealTypes for the dimension variables
+	  RealType line = RealType.getRealType("ImageLine", null, null);
+	  RealType element = RealType.getRealType("ImageElement", null, null);
+	  
+	  // the domain is (element,line) since elements (X) vary fastest
+	  RealType[] domainComponents = {element, line};
+	  int resMultiplier = 3;
+	  if (filenames[0].contains("HRV")) {
+		  resMultiplier = 1;
+	  }
+	  int [] iparms = new int[6];
+	  iparms[0] = AREAnav.GEOS;
+	  iparms[1] = columnOffset * resMultiplier * 10;
+	  iparms[2] = columnOffset * resMultiplier * 10;	  
+	  iparms[3] = lineScalingFactor * resMultiplier * 10;
+	  iparms[4] = columnScalingFactor * resMultiplier * 10;
+	  // XXX FIXME TJJ - hardcoding for now: 0 for MSG.  Should be able
+	  // to pull this out of the signal/segment data
+	  iparms[5] = 0;
+	  int [] dir = new int[64];
+	  //dir[5] = resMultiplier * minLineOffset;
+	  dir[5] = resMultiplier * (minLineOffset - 464) + 5568 + 1;
+	  if (filenames[0].contains("HRV")) {
+		  //dir[6] = 11136 - ((resMultiplier * columnOffset) + 5568);
+		  dir[6] = columnOffset + 1;
+	  } else {
+		  dir[6] = 1;
+	  }
+	  dir[8] = imageSegmentLines[0] * filenames.length;
+	  dir[9] = imageSegmentElements[0];
+	  dir[11] = resMultiplier;
+	  dir[12] = resMultiplier;
+	  CoordinateSystem cs = new HRITCoordinateSystem(iparms, dir, false);
+	  RealTupleType imageDomain = new RealTupleType(domainComponents, cs, null);
+	  
+	  // create calibration object
+	  double [][] calBlock = makeMSGCal(filenames[0]);
+	  CalibratorMsg cmsg = null;
+	  try {
+	    cmsg = new CalibratorMsg(calBlock);
+	  } catch (CalibratorException ce) {
+		ce.printStackTrace();
+	  }
+	  
+	  //  Image numbering is usually the first line is at the "top"
+	  //  whereas in VisAD, it is at the bottom.  So define the
+	  //  domain set of the FlatField to map the Y axis accordingly
+
+	  Linear2DSet domainSet = new Linear2DSet(imageDomain,
+	                                0, (imageSegmentElements[0] - 1), imageSegmentElements[0] / magFactor,
+	                                ((imageSegmentLines[0] * filenames.length) - 1), 
+	                                0, (imageSegmentLines[0] * filenames.length) / magFactor);
+	  // the range of the FunctionType is the band(s)
+	  int numBands = 1;
+	  RealType[] bands = new RealType[numBands];
+	  bands[0] = RealType.getRealType("Band" + bandNum);
+	  RealTupleType rtt = new RealTupleType(bands);
+	  FunctionType imageType = new FunctionType(imageDomain, rtt);
+	  Unit[] rangeUnits = null;
+	  field = new FlatField (
+		imageType,
+        domainSet,
+        (CoordinateSystem) null, null,
+        rangeUnits
+      );
+	  
+	  for (int i = 0; i < filenames.length; i++) {
+		  
+		  // open a stream to the file 
+		  File f = new File(filenames[i]);
+		  FileInputStream fis = new FileInputStream(f);
+		  fis.skip(lengthAllHeaders[i]);
+		  
+		  // if we found 10 bit data, we'll need to allocate input and output arrays to decompress
+		  tenBitInputArray = new byte[(int) f.length() - lengthAllHeaders[i] + 2];
+		  tenBitOutputArray = new short[imageSegmentLines[i] * imageSegmentElements[i]];
+		  
+		  double[][] samples = new double[numBands][imageSegmentElements[i]/magFactor * imageSegmentLines[i]/magFactor];
+		  byte[] sampleTwoByte = new byte[2];
+		  byte[] sampleOneByte = new byte[1];
+		  
+		  // set samples for one or two byte data
+		  if (imageBitsPerPixel[i] != 10) {
+			  for (int b = 0; b < numBands; b++) {
+				  for (int l=0; l < imageSegmentLines[i]; l++) {
+					  for (int j=0; j < imageSegmentElements[i]; j++) {
+						  if (imageBitsPerPixel[i] == 16) {
+							  fis.read(sampleTwoByte);
+							  samples[b][j + (imageSegmentElements[i] * l) ] = 
+								  (float) (bytesToShort(sampleTwoByte, 0));
+						  } else {
+							  fis.read(sampleOneByte);
+							  samples[b][j + (imageSegmentElements[i] * l) ] = 
+								  (float) (Util.unsignedByteToInt(sampleOneByte[0]));
+						  }
+					  }
+				  }
+			  }
+		  } else {
+			  int numRead = fis.read(tenBitInputArray, 0, tenBitInputArray.length - 2);
+			  // System.out.println("Count wanted: " + (tenBitInputArray.length - 2) + " , count got: " + numRead);
+			  if (numRead == tenBitInputArray.length - 2) {
+				int convert = Util.tenBitToTwoByte(tenBitInputArray, tenBitOutputArray);
+				if (convert == 0) {
+				// System.out.println("10 bit to 16 bit conversion successful!");
+					  int idx = 0;
+					  for (int b = 0; b < numBands; b++) {
+						  for (int l = imageSegmentLines[i]/magFactor - 1; l >= 0; l--) {
+							  for (int j = imageSegmentElements[i]/magFactor - 1; j >= 0; j--) {
+								  samples[b][j + ((imageSegmentElements[i]/magFactor) * l) ] = 
+									  cmsg.calibrateFromRaw((float) (tenBitOutputArray[idx]), bandNum, calType);
+								  idx += magFactor;
+							  }
+							  idx += imageSegmentElements[i] * (magFactor - 1);
+						  }
+					  }
+				}
+			  }
+		  }
+		  field.setSamples((samples[0].length * (filenames.length - (i + 1))), samples);
+		  fis.close();
+		  
+	  }
+	  
+  }
+  
+  /**
+   * Attempt to build a McIDAS-style calibration block.
+   * If unsuccessful, a warning is popped up that calibration 
+   * will be approximated, and should not be considered accurate.
+   * @param s an image segment file name for the data request
+   * @return a McIDAS-style calibration block
+   */
+  
+  private double[][] makeMSGCal(String s) {
+	  
+	  double [][] msgCal = new double[12][6];
+	  double [] waveNumMSG1  = new double[12];
+	  double [] waveNumMSG2  = new double[12];
+	  double [] waveNumMSG3  = new double[12];
+	  double [] alphaMSG1    = new double[12];
+	  double [] alphaMSG2    = new double[12];
+	  double [] alphaMSG3    = new double[12];
+	  double [] betaMSG1     = new double[12];
+	  double [] betaMSG2     = new double[12];
+	  double [] betaMSG3     = new double[12];
+	  double [] gain     = new double[12];
+	  double [] offset   = new double[12];
+
+	  // various constants.  I know this doesn't look good... for now we are
+	  // only covering MSG-1 and MSG-2.  Needs work, but at present this is
+	  // no different than the core McIDAS and ADDE server code!
+	  waveNumMSG1[0] = 0.0d;
+	  waveNumMSG1[1] = 0.0d;
+	  waveNumMSG1[2] = 0.0d;
+	  waveNumMSG1[3] = 2567.33d;
+	  waveNumMSG1[4] = 1598.103d;
+	  waveNumMSG1[5] = 1362.081d;
+	  waveNumMSG1[6] = 1149.069d;
+	  waveNumMSG1[7] = 1034.343d;
+	  waveNumMSG1[8] = 930.647d;
+	  waveNumMSG1[9] = 839.660d;
+	  waveNumMSG1[10] = 752.387d;
+	  waveNumMSG1[11] = 0.0d;
+	  
+	  waveNumMSG2[0] = 0.0d;
+	  waveNumMSG2[1] = 0.0d;
+	  waveNumMSG2[2] = 0.0d;
+	  waveNumMSG2[3] = 2568.832d;
+	  waveNumMSG2[4] = 1600.548d;
+	  waveNumMSG2[5] = 1360.330d;
+	  waveNumMSG2[6] = 1148.620d;
+	  waveNumMSG2[7] = 1035.289d;
+	  waveNumMSG2[8] = 931.700d;
+	  waveNumMSG2[9] = 836.445d;
+	  waveNumMSG2[10] = 751.792d;
+	  waveNumMSG2[11] = 0.0d;
+	  
+	  waveNumMSG3[0] = 0.0d;
+	  waveNumMSG3[1] = 0.0d;
+	  waveNumMSG3[2] = 0.0d;
+	  waveNumMSG3[3] = 2547.771d;
+	  waveNumMSG3[4] = 1595.621d;
+	  waveNumMSG3[5] = 1360.377d;
+	  waveNumMSG3[6] = 1148.130d;
+	  waveNumMSG3[7] = 1034.715d;
+	  waveNumMSG3[8] = 929.842d;
+	  waveNumMSG3[9] = 838.659d;
+	  waveNumMSG3[10] = 751.792d;
+	  waveNumMSG3[11] = 0.0d;
+
+	  alphaMSG1[0] = 0.0d;
+	  alphaMSG1[1] = 0.0d;
+	  alphaMSG1[2] = 0.0d;
+	  alphaMSG1[3] = 0.9956d;
+	  alphaMSG1[4] = 0.9962d;
+	  alphaMSG1[5] = 0.9991d;
+	  alphaMSG1[6] = 0.9996d;
+	  alphaMSG1[7] = 0.9999d;
+	  alphaMSG1[8] = 0.9983d;
+	  alphaMSG1[9] = 0.9988d;
+	  alphaMSG1[10] = 0.9981d;
+	  alphaMSG1[11] = 0.0d;
+	  
+	  alphaMSG2[0] = 0.0d;
+	  alphaMSG2[1] = 0.0d;
+	  alphaMSG2[2] = 0.0d;
+	  alphaMSG2[3] = 0.9954d;
+	  alphaMSG2[4] = 0.9963d;
+	  alphaMSG2[5] = 0.9991d;
+	  alphaMSG2[6] = 0.9996d;
+	  alphaMSG2[7] = 0.9999d;
+	  alphaMSG2[8] = 0.9983d;
+	  alphaMSG2[9] = 0.9988d;
+	  alphaMSG2[10] = 0.9981d;
+	  alphaMSG2[11] = 0.0d;
+	  
+	  alphaMSG3[0] = 0.0d;
+	  alphaMSG3[1] = 0.0d;
+	  alphaMSG3[2] = 0.0d;
+	  alphaMSG3[3] = 0.9915d;
+	  alphaMSG3[4] = 0.9960d;
+	  alphaMSG3[5] = 0.9991d;
+	  alphaMSG3[6] = 0.9996d;
+	  alphaMSG3[7] = 0.9999d;
+	  alphaMSG3[8] = 0.9983d;
+	  alphaMSG3[9] = 0.9988d;
+	  alphaMSG3[10] = 0.9982d;
+	  alphaMSG3[11] = 0.0d;
+
+	  betaMSG1[0] = 0.0d;
+	  betaMSG1[1] = 0.0d;
+	  betaMSG1[2] = 0.0d;
+	  betaMSG1[3] = 3.410d;
+	  betaMSG1[4] = 2.218d;
+	  betaMSG1[5] = 0.478d;
+	  betaMSG1[6] = 0.179d;
+	  betaMSG1[7] = 0.060d;
+	  betaMSG1[8] = 0.625d;
+	  betaMSG1[9] = 0.397d;
+	  betaMSG1[10] = 0.578d;
+	  betaMSG1[11] = 0.0d;
+	  
+	  betaMSG2[0] = 0.0d;
+	  betaMSG2[1] = 0.0d;
+	  betaMSG2[2] = 0.0d;
+	  betaMSG2[3] = 3.438d;
+	  betaMSG2[4] = 2.185d;
+	  betaMSG2[5] = 0.470d;
+	  betaMSG2[6] = 0.179d;
+	  betaMSG2[7] = 0.056d;
+	  betaMSG2[8] = 0.640d;
+	  betaMSG2[9] = 0.408d;
+	  betaMSG2[10] = 0.561d;
+	  betaMSG2[11] = 0.0d;
+	  
+	  betaMSG3[0] = 0.0d;
+	  betaMSG3[1] = 0.0d;
+	  betaMSG3[2] = 0.0d;
+	  betaMSG3[3] = 2.9002d;
+	  betaMSG3[4] = 2.0337d;
+	  betaMSG3[5] = 0.4340d;
+	  betaMSG3[6] = 0.1714d;
+	  betaMSG3[7] = 0.0527d;
+	  betaMSG3[8] = 0.6084d;
+	  betaMSG3[9] = 0.3882d;
+	  betaMSG3[10] = 0.5390d;
+	  betaMSG3[11] = 0.0d;
+	  
+	  // initialize with approximate values - this will get you a
+	  // pretty picture but should not be considered accurate
+	  gain[0] = 0.2331010000E-01d;
+	  gain[1] = 0.2540430000E-01d;
+	  gain[2] = 0.2187850000E-01d;
+	  gain[3] = 0.3742751227E-02d;
+	  gain[4] = 0.4641033727E-01d;
+	  gain[5] = 0.8197182308E-01d;
+	  gain[6] = 0.1256206112E+00d;
+	  gain[7] = 0.1523276370E+00d;
+	  gain[8] = 0.1959369086E+00d;
+	  gain[9] = 0.2145945762E+00d;
+	  gain[10] = 0.2091678681E+00d;
+	  gain[11] = 0.2800300000E-01d;
+	  
+	  offset[0] = -0.1188810000E+01d;
+	  offset[1] = -0.1295620000E+01d;
+	  offset[2] = -0.1115800000E+01d;
+	  offset[3] = -0.1908803126E+00d;
+	  offset[4] = -0.2366927201E+01d;
+	  offset[5] = -0.4180562977E+01d;
+	  offset[6] = -0.6406651170E+01d;
+	  offset[7] = -0.7768709489E+01d;
+	  offset[8] = -0.9992782340E+01d;
+	  offset[9] = -0.1094432338E+02d;
+	  offset[10] = -0.1066756128E+02d;
+	  offset[11] = -0.1428150000E+01d;	 
+	  
+	  // for now, assume we calibrate based on MSG-2, unless we detect otherwise
+	  int scId = SPACECRAFT_ID_MSG2;
+	  
+	  // now the real work - try to convert the image segment file name
+	  // to an MSG prologue file name (file with the cal slopes and offsets)
+	  boolean accurateCal = false;
+	  // to build the filename for the matching prologue file, swap out channel and segment
+	  // number sections with underscores and -PRO
+	  String plFileName = s.replaceFirst("......___-0000\\d\\d___", "_________-PRO______");
+	  File f = new File(plFileName);
+	  try {
+
+		  FileInputStream fis = new FileInputStream(f);
+		  // try to pull out the primary header
+		  byte [] primaryHeader = new byte[PRIMARY_HEADER_LENGTH];
+		  int bytesRead = fis.read(primaryHeader);
+		  if ((bytesRead < 0) || (bytesRead != PRIMARY_HEADER_LENGTH)) {
+			  fis.close();
+			  throw new IOException("File " + s + " is not an HRIT file");
+		  }
+		  // validate primary header contents
+		  int headerSize = bytesToShort(primaryHeader, 1);
+		  if (headerSize != PRIMARY_HEADER_LENGTH) {
+			  fis.close();
+			  throw new IOException("File " + s + " is not a valid HRIT file");
+		  }
+		  // make sure file is at least as long as the claimed length of all headers
+		  int lengthAllHeaders = -1;
+		  lengthAllHeaders = bytesToInt(primaryHeader, 4);
+		  if (f.length() < lengthAllHeaders) {
+			  fis.close();
+			  throw new IOException("File " + s + " is not a valid HRIT file");
+		  }
+		  // ok, we got the primary header, moving along to the other headers...
+		  int headerBytesConsumed = PRIMARY_HEADER_LENGTH;
+		  byte [] headerType = new byte[1];
+		  byte [] headerLength = new byte[2];
+		  while (headerBytesConsumed < lengthAllHeaders) {
+			  bytesRead = fis.read(headerType);
+			  headerBytesConsumed += bytesRead;
+			  bytesRead = fis.read(headerLength);
+			  headerBytesConsumed += bytesRead;
+			  headerSize = bytesToShort(headerLength, 0);
+			  byte [] header = new byte[headerSize - 3];
+			  bytesRead = fis.read(header);
+			  headerBytesConsumed += bytesRead;
+		  }
+		  // two-byte utility array for pulling out shorts
+		  byte [] b2 = new byte[2];
+		  // spacecraft id - will be used to further improve cal, as time permits
+		  fis.read(b2);
+		  scId = bytesToShort(b2, 0);
+		  long n = fis.skip((SAT_STAT_LEN - 2) + IMG_ACQ_LEN + CEL_EVENTS_LEN + IMG_DESC_LEN + CAL_OFFS);
+		  if (n != (SAT_STAT_LEN - 2) + IMG_ACQ_LEN + CEL_EVENTS_LEN + IMG_DESC_LEN + CAL_OFFS) {
+			  fis.close();
+			  throw new IOException("Failed to read calibration coefficients, corrupt file?");
+		  }
+		  for (int i = 0; i < 12; i++) {
+			  byte [] d1 = new byte[8];
+			  byte [] d2 = new byte[8];
+			  int count = fis.read(d1);
+			  if (count != 8) {
+				  fis.close();
+				  throw new IOException("Failed to read calibration coefficients, corrupt file?");
+			  }
+			  count = fis.read(d2);
+			  if (count != 8) {
+				  fis.close();
+				  throw new IOException("Failed to read calibration coefficients, corrupt file?");
+			  }
+			  long l1 = bytesToLong(d1, 0);
+			  long l2 = bytesToLong(d2, 0);
+			  gain[i] = Double.longBitsToDouble(l1);
+			  offset[i] = Double.longBitsToDouble(l2);
+			  // TODO: should probably add a sanity check on gain/offset values,
+			  // to make sure we found and will be using reasonable numbers.
+		  }
+		  // if we got this far, assume we have accurate calibration coefficients
+		  accurateCal = true;
+		  fis.close();
+
+	  } catch (FileNotFoundException e) {
+		  // Do nothing - we just won't have accurate calibration
+	  } catch (IOException e) {
+		  // Do nothing - we just won't have accurate calibration
+	  }
+	  
+	  if (! accurateCal) {
+		  System.err.println("WARNING: Data will be displayed, but calibration is approximate.");
+	  }
+
+	  double w  = 0.0d;
+	  double c1w3 = 0.0d;
+	  double c2w = 0.0d;
+	  double PLANCK = 6.626176E-34;
+	  double LIGHT  = 2.99792458E8;
+	  double BOLTZMAN = 1.380662E-23;
+	  double c1 = 2.0E5 * PLANCK * (LIGHT * LIGHT);
+	  double c2 = PLANCK * LIGHT / BOLTZMAN;
+	  for (int band = 0; band < 12; band++) {	 
+		  if (scId == SPACECRAFT_ID_MSG2) {
+			  w  = 1.0E2 * waveNumMSG2[band];
+			  msgCal[band][2] = alphaMSG2[band];
+			  msgCal[band][3] = betaMSG2[band];
+		  } else if (scId == SPACECRAFT_ID_MSG3) {
+			  w  = 1.0E2 * waveNumMSG3[band];
+			  msgCal[band][2] = alphaMSG3[band];
+			  msgCal[band][3] = betaMSG3[band];
+		  } else {
+		      w  = 1.0E2 * waveNumMSG1[band];
+		      msgCal[band][2] = alphaMSG1[band];
+		      msgCal[band][3] = betaMSG1[band];
+		  }
+	      c1w3 = c1 * w * w * w;
+	      c2w  = c2 * w;
+	      msgCal[band][0] = c1w3;
+	      msgCal[band][1] = c2w;
+	      msgCal[band][4] = gain[band];
+	      msgCal[band][5] = offset[band];
+	  }
+	  
+	  return msgCal;
+}
+
+  private void dumpHeader(byte [] header) {
+	  if ((header != null) && (header.length >= 3)) {
+		  System.out.println("Header type: " + header[0]);
+		  System.out.println("Length of this header: " + bytesToShort(header, 1));
+	  }
+
+	  switch (header[0]) {
+	  case HEADER_TYPE_PRIMARY_HEADER:
+		  System.out.println("Length of all headers: " + bytesToInt(header, 4));
+		  break;
+	  default:
+		  break;
+	  }
+  }
+
+  public FlatField getData() {
+	  return field;
+  }
+  
+  /**
+   * Converts a set of four consecutive bytes into a single long.
+   * @param data An array of bytes
+   * @param offset The array index to begin with
+   * @return The resulting int
+   */
+  public static long bytesToLong(byte[] data, int offset) {
+          long l = 0;
+          l += Util.unsignedByteToLong(data[offset + 0]) << 56;
+          l += Util.unsignedByteToLong(data[offset + 1]) << 48;
+          l += Util.unsignedByteToLong(data[offset + 2]) << 40;
+          l += Util.unsignedByteToLong(data[offset + 3]) << 32;
+          l += Util.unsignedByteToLong(data[offset + 4]) << 24;
+          l += Util.unsignedByteToLong(data[offset + 5]) << 16;
+          l += Util.unsignedByteToLong(data[offset + 6]) << 8;
+          l += Util.unsignedByteToLong(data[offset + 7]);
+          return l;
+  }
+
+  /**
+   * Converts a set of four consecutive bytes into a single int.
+   * @param data An array of bytes
+   * @param offset The array index to begin with
+   * @return The resulting int
+   */
+  public static int bytesToInt(byte[] data, int offset) {
+          int i = 0;
+          i += Util.unsignedByteToInt(data[offset]) << 24;
+          i += Util.unsignedByteToInt(data[offset + 1]) << 16;
+          i += Util.unsignedByteToInt(data[offset + 2]) << 8;
+          i += Util.unsignedByteToInt(data[offset + 3]);
+          return i;
+  }
+
+  /**
+   * Converts a set of two consecutive bytes into a single int.
+   * @param data An array of bytes
+   * @param offset The array index to begin with
+   * @return The resulting int
+   */
+  public static int bytesToShort(byte[] data, int offset) {
+          int i = 0;
+          i += Util.unsignedByteToInt(data[offset]) << 8;
+          i += Util.unsignedByteToInt(data[offset + 1]);
+          return i;
+  }
+  
+}
diff --git a/visad/data/hrit/HRITCoordinateSystem.java b/visad/data/hrit/HRITCoordinateSystem.java
new file mode 100644
index 0000000..e752ed7
--- /dev/null
+++ b/visad/data/hrit/HRITCoordinateSystem.java
@@ -0,0 +1,724 @@
+//
+// HRITCoordinateSystem.java
+//
+
+/*
+The software in this file is Copyright(C) 2011 by Tommy Jasmin.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.hrit;
+
+import edu.wisc.ssec.mcidas.AREAnav;
+import edu.wisc.ssec.mcidas.GEOSnav;
+
+import java.awt.geom.Rectangle2D;
+
+import visad.CoordinateSystemException;
+import visad.QuickSort;
+import visad.RealTupleType;
+import visad.Unit;
+import visad.VisADException;
+
+/**
+ * HRITCoordinateSystem is a VisAD CoordinateSystem class used by HRITAdapter
+ * for conversions to/from (Latitude, Longitude) and Cartesian (element,line),
+ * and with Latitude and Longitude in degrees. It is currently modeled mostly
+ * after AREACoordinateSystem, so there are strong similarities between the two.
+ * In fact, for now this class does navigation exclusively through the McIDAS
+ * GEOS nav class.
+ */
+
+public class HRITCoordinateSystem
+    extends visad.georef.MapProjection
+{
+
+  private static final long serialVersionUID = 1L;
+  protected AREAnav anav = null;
+  private int lines;
+  private int elements;
+  private int[] dirBlock;
+  private int[] navBlock;
+  private int[] auxBlock;
+  private boolean useSpline = true;
+
+  private static Unit[] coordinate_system_units = {null, null};
+
+  /** create an HRIT coordinate system with nothing initialized.
+    * This allows for derived classes to do a lazy initialization
+    * of the coordinate system. To do this they must override getAreaNav
+    * in order to create the nav.
+    */
+   protected HRITCoordinateSystem() throws VisADException {
+       super(RealTupleType.LatitudeLongitudeTuple,coordinate_system_units);
+   }
+
+  /** create an HRIT coordinate system from the provided array
+    * of navigation parameters, and partially filled AREA directory.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param iparms init params?
+    * @param dir is the AREA file directory block
+    * @param useSpline  use a spline approximation for speed
+    *
+    */
+  public HRITCoordinateSystem(int[] iparms, int[] dir, boolean useSpline) 
+  	throws VisADException {
+
+    super(RealTupleType.LatitudeLongitudeTuple, coordinate_system_units);
+    init(iparms, dir, useSpline);
+  }
+
+  /**
+   * This is used by the methods that do the work and can be overridden
+   * by a derived class to do a lazy instantiation of the coordinate system.
+   * 
+   * @return The area nav 
+   */
+  protected AREAnav getAreaNav() {
+      return anav;
+  }
+
+  /**
+   * Create and initialize the areanav.
+   * This used to be in the constructor is snow its own method to enable 
+   * derived classes to lazily create the areanav
+   *
+   * @param iparms init params?
+   * @param dir is the AREA file directory block
+   * @param useSpline  use a spline approximation for speed
+   */
+  protected void init(int[]iparms, int[]dir, boolean useSpline) throws VisADException {
+
+	  try {
+		  anav = new GEOSnav(iparms);
+	  } catch (IllegalArgumentException iae) {
+		  throw new CoordinateSystemException(
+				  "HRITCoordinateSystem: problem creating navigation" + iae);
+	  }
+	  dirBlock = null;
+	  navBlock = null;
+	  auxBlock = null;
+	  this.useSpline = !useSpline 
+	  ? false  // user overrode
+			  : anav.canApproximateWithSpline(); // let nav decide
+	  anav.setImageStart(dir[5], dir[6]);
+	  anav.setRes(dir[11], dir[12]);
+	  anav.setStart(0,0);
+	  anav.setMag(1,1);
+	  lines = dir[8];
+	  elements = dir[9];
+	  anav.setFlipLineCoordinates(dir[8]); // invert Y axis coordinates
+  }
+
+  /** Get the directory block used to initialize this HRITCoordinateSystem */
+  public int[] getDirBlock() {
+    getAreaNav();
+    return dirBlock;
+  }
+
+  /** Get the navigation block used to initialize this HRITCoordinateSystem */
+  public int[] getNavBlock() {
+    getAreaNav();
+    return navBlock;
+  }
+
+  /** Get the navigation block used to initialize this HRITCoordinateSystem */
+  public int[] getAuxBlock() {
+    getAreaNav();
+    return auxBlock;
+  }
+
+  /** get the subpoint if available
+  */
+  public double[] getSubpoint() {
+    return getAreaNav().getSubpoint();
+  }
+
+  /** 
+   * Get whether we are using a spline or not 
+   */
+  public boolean getUseSpline() {
+    return useSpline;
+  }
+
+  /** convert from image element,line to latitude,longitude
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("HRITCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    AREAnav  anav = getAreaNav();
+
+    if (anav == null) {
+      throw new CoordinateSystemException("HRIT O & A data not available");
+    }
+
+    int[] nums = new int[2];
+    double[] mins = new double[2];
+    double[] maxs = new double[2];
+    double[][] newval = makeSpline(tuples, mins, maxs, nums);
+    if (newval != null) {
+      double[][] newtrans = anav.toLatLon(newval);
+
+      int len = tuples[0].length;
+      double[][] misstrans = new double[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      double[][] val = applySpline(tuples, mins, maxs, nums, newtrans,
+                                   misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        double[][] newmiss = anav.toLatLon(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+      return val;
+    }
+    else {
+      return anav.toLatLon(tuples);
+    }
+  }
+
+  /** convert from latitude,longitude to image element,line
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("HRITCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+
+    AREAnav  anav = getAreaNav();
+    if (anav == null) {
+      throw new CoordinateSystemException("HRIT O & A data not availble");
+    }
+
+    int[] nums = new int[2];
+    double[] mins = new double[2];
+    double[] maxs = new double[2];
+    double[][] newval = makeSpline(tuples, mins, maxs, nums);
+    if (newval != null) {
+      double[][] newtrans = anav.toLinEle(newval);
+
+      int len = tuples[0].length;
+      double[][] misstrans = new double[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      double[][] val = applySpline(tuples, mins, maxs, nums, newtrans,
+                                   misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        double[][] newmiss = anav.toLinEle(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+      return val;
+    }
+    else {
+      return anav.toLinEle(tuples);
+    }
+
+  }
+
+  /** convert from image element,line to latitude,longitude
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("HRITCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    AREAnav  anav = getAreaNav();
+    if (anav == null) {
+      throw new CoordinateSystemException("HRIT O & A data not availble");
+    }
+
+    float[][] val = tuples;
+
+    int[] nums = new int[2];
+    float[] mins = new float[2];
+    float[] maxs = new float[2];
+    float[][] newval = makeSpline(val, mins, maxs, nums);
+    if (newval != null) {
+      float[][] newtrans = anav.toLatLon(newval);
+
+      int len = tuples[0].length;
+      float[][] misstrans = new float[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      val = applySpline(val, mins, maxs, nums, newtrans,
+                        misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        float[][] newmiss = anav.toLatLon(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+    }
+    else {
+      val = anav.toLatLon(val);
+    }
+    return val;
+  }
+
+  /** convert from latitude,longitude to image element,line
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("HRITCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+
+    AREAnav  anav = getAreaNav();
+    if (anav == null) {
+      throw new CoordinateSystemException("HRIT O & A data not availble");
+    }
+
+    float[][] val = tuples;
+
+    int[] nums = new int[2];
+    float[] mins = new float[2];
+    float[] maxs = new float[2];
+    float[][] newval = makeSpline(val, mins, maxs, nums);
+    if (newval != null) {
+      float[][] newtrans = anav.toLinEle(newval);
+
+      int len = tuples[0].length;
+      float[][] misstrans = new float[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      val = applySpline(val, mins, maxs, nums, newtrans,
+                        misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        float[][] newmiss = anav.toLinEle(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+    }
+    else {
+      val = anav.toLinEle(val);
+    }
+    return val;
+
+  }
+
+  // return reduced array for approximation by splines
+  private double[][] makeSpline(double[][] val, double[] mins,
+                                double[] maxs, int[] nums)
+         throws VisADException {
+    if (!useSpline) return null;
+    int len = val[0].length;
+    if (len < 1000) return null;
+    double reduction = 10.0;
+    if (len < 10000) reduction = 2.0;
+    else if (len < 100000) reduction = 5.0;
+
+    // compute ranges
+    mins[0] = Double.MAX_VALUE;
+    maxs[0] = -Double.MAX_VALUE;
+    mins[1] = Double.MAX_VALUE;
+    maxs[1] = -Double.MAX_VALUE;
+    for (int i=0; i<len; i++) {
+      if (val[0][i] == val[0][i]) {
+        if (val[0][i] < mins[0]) mins[0] = val[0][i];
+        if (val[0][i] > maxs[0]) maxs[0] = val[0][i];
+      }
+      if (val[1][i] == val[1][i]) {
+        if (val[1][i] < mins[1]) mins[1] = val[1][i];
+        if (val[1][i] > maxs[1]) maxs[1] = val[1][i];
+      }
+    }
+
+    // compute typical spacing between points
+    float[] norm = new float[len-1];
+    int k = 0;
+    // WLH 2 March 2000
+    // for (int i=0; k<3 && i<len; i++) {
+    for (int i=0; i<len-1; i++) {
+      float n = (float) Math.sqrt(
+        (val[0][i] - val[0][i+1]) * (val[0][i] - val[0][i+1]) +
+        (val[1][i] - val[1][i+1]) * (val[1][i] - val[1][i+1]) );
+        norm[k++] = n;
+    }
+    // WLH 2 March 2000
+    if (k < 3) return null;
+    float[] nnorm = new float[k];
+    System.arraycopy(norm, 0, nnorm, 0, k);
+
+    QuickSort.sort(nnorm);
+    double spacing = reduction * nnorm[k / 4];
+
+    // compute size of spline array
+    nums[0] = (int) ((maxs[0] - mins[0]) / spacing) + 1;
+    nums[1] = (int) ((maxs[1] - mins[1]) / spacing) + 1;
+
+    // test if it will be too coarse
+    if (nums[0] < 20 || nums[1] < 20) return null;
+    // test if its worth it
+    if ((nums[0] * nums[1]) > (len / 4)) return null;
+    // test to see if the product is greater than an int
+    if ((nums[0] * nums[1]) < 0) return null;
+
+    double spacing0 = (maxs[0] - mins[0]) / (nums[0] - 1);
+    double spacing1 = (maxs[1] - mins[1]) / (nums[1] - 1);
+
+    double[][] newval = new double[2][nums[0] * nums[1]];
+    k = 0;
+    for (int i=0; i<nums[0]; i++) {
+      for (int j=0; j<nums[1]; j++) {
+        newval[0][k] = mins[0] + i * spacing0;
+        newval[1][k] = mins[1] + j * spacing1;
+        k++;
+      }
+    }
+    return newval;
+  }
+
+  // use splines to approximate transform
+  private double[][] applySpline(double[][] val, double[] mins,
+                     double[] maxs, int[] nums, double[][] newtrans,
+                     double[][] misstrans, int[][] miss_to_trans)
+         throws VisADException {
+    int n0 = nums[0];
+    int n1 = nums[1];
+    double spacing0 = (maxs[0] - mins[0]) / (n0 - 1);
+    double spacing1 = (maxs[1] - mins[1]) / (n1 - 1);
+    int len = val[0].length;
+    double[][] trans = new double[2][len];
+
+    boolean[] lon_flags = new boolean[n0 * n1];
+    double[] min_trans = {Double.MAX_VALUE, Double.MAX_VALUE};
+    double[] max_trans = {-Double.MAX_VALUE, -Double.MAX_VALUE};
+    for (int i=0; i<n0*n1; i++) {
+      if (newtrans[0][i] == newtrans[0][i]) {
+        if (newtrans[0][i] < min_trans[0]) min_trans[0] = newtrans[0][i];
+        if (newtrans[0][i] > max_trans[0]) max_trans[0] = newtrans[0][i];
+      }
+      if (newtrans[1][i] == newtrans[1][i]) {
+        if (newtrans[1][i] < min_trans[1]) min_trans[1] = newtrans[1][i];
+        if (newtrans[1][i] > max_trans[1]) max_trans[1] = newtrans[1][i];
+      }
+      lon_flags[i] = false;
+    }
+    double minn0n1 = Math.min(n0, n1);
+    double mean0 = (max_trans[0] - min_trans[0]) / minn0n1;
+    double mean1 = (max_trans[1] - min_trans[1]) / minn0n1;
+
+    for (int i0=0; i0<n0-1; i0++) {
+      for (int i1=0; i1<n1-1; i1++) {
+        int ii = i1 + i0 * n1;
+        if (Math.abs(newtrans[0][ii + n1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + 1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + 1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + n1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[1][ii + n1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + 1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + 1]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + n1]) > 3.0 * mean1) {
+          lon_flags[ii] = true;
+        }
+      }
+    }
+
+    int nmiss = 0;
+
+    for (int i=0; i<len; i++) {
+      double a0 = (val[0][i] - mins[0]) / spacing0;
+      int i0 = (int) a0;
+      if (i0 < 0) i0 = 0;
+      if (i0 > (n0 - 2)) i0 = n0 - 2;
+      a0 -= i0;
+      double a1 = (val[1][i] - mins[1]) / spacing1;
+      int i1 = (int) a1;
+      if (i1 < 0) i1 = 0;
+      if (i1 > (n1 - 2)) i1 = n1 - 2;
+      a1 -= i1;
+      int ii = i1 + i0 * n1;
+      if (lon_flags[ii]) {
+        misstrans[0][nmiss] = val[0][i];
+        misstrans[1][nmiss] = val[1][i];
+        miss_to_trans[0][nmiss] = i;
+        nmiss++;
+      }
+      else {
+        trans[0][i] =
+          (1.0 - a0) * ((1.0 - a1) * newtrans[0][ii] + a1 * newtrans[0][ii+1]) +
+          a0 * ((1.0 - a1) * newtrans[0][ii+n1] + a1 * newtrans[0][ii+n1+1]);
+        trans[1][i] =
+          (1.0 - a0) * ((1.0 - a1) * newtrans[1][ii] + a1 * newtrans[1][ii+1]) +
+          a0 * ((1.0 - a1) * newtrans[1][ii+n1] + a1 * newtrans[1][ii+n1+1]);
+
+        if (trans[0][i] != trans[0][i] || trans[1][i] != trans[1][i]) {
+          misstrans[0][nmiss] = val[0][i];
+          misstrans[1][nmiss] = val[1][i];
+          miss_to_trans[0][nmiss] = i;
+          nmiss++;
+        }
+      }
+    }
+
+    if (nmiss == 0) {
+      miss_to_trans[0] = null;
+      misstrans[0] = null;
+      misstrans[1] = null;
+    }
+    else {
+      double[] xmisstrans = new double[nmiss];
+      System.arraycopy(misstrans[0], 0, xmisstrans, 0, nmiss);
+      misstrans[0] = xmisstrans;
+      xmisstrans = new double[nmiss];
+      System.arraycopy(misstrans[1], 0, xmisstrans, 0, nmiss);
+      misstrans[1] = xmisstrans;
+      int[] xmiss_to_trans = new int[nmiss];
+      System.arraycopy(miss_to_trans[0], 0, xmiss_to_trans, 0, nmiss);
+      miss_to_trans[0] = xmiss_to_trans;
+    }
+    return trans;
+  }
+
+  // return reduced array for approximation by splines
+  private float[][] makeSpline(float[][] val, float[] mins,
+                                float[] maxs, int[] nums)
+         throws VisADException {
+    int len = val[0].length;
+    if (len < 1000) return null;
+    float reduction = 10.0f;
+    if (len < 10000) reduction = 2.0f;
+    else if (len < 100000) reduction = 5.0f;
+
+    // compute ranges
+    mins[0] = Float.MAX_VALUE;
+    maxs[0] = -Float.MAX_VALUE;
+    mins[1] = Float.MAX_VALUE;
+    maxs[1] = -Float.MAX_VALUE;
+    for (int i=0; i<len; i++) {
+      if (val[0][i] == val[0][i]) {
+        if (val[0][i] < mins[0]) mins[0] = val[0][i];
+        if (val[0][i] > maxs[0]) maxs[0] = val[0][i];
+      }
+      if (val[1][i] == val[1][i]) {
+        if (val[1][i] < mins[1]) mins[1] = val[1][i];
+        if (val[1][i] > maxs[1]) maxs[1] = val[1][i];
+      }
+    }
+
+    // compute typical spacing between points
+    float[] norm = new float[len-1];
+    int k = 0;
+    // WLH 2 March 2000
+    // for (int i=0; k<3 && i<len; i++) {
+    for (int i=0; i<len-1; i++) {
+      float n = (float) Math.sqrt(
+        (val[0][i] - val[0][i+1]) * (val[0][i] - val[0][i+1]) +
+        (val[1][i] - val[1][i+1]) * (val[1][i] - val[1][i+1]) );
+        norm[k++] = n;
+    }
+    // WLH 2 March 2000
+    if (k < 3) return null;
+    float[] nnorm = new float[k];
+    System.arraycopy(norm, 0, nnorm, 0, k);
+
+    QuickSort.sort(nnorm);
+    float spacing = reduction * nnorm[k / 4];
+
+    // compute size of spline array
+    nums[0] = (int) ((maxs[0] - mins[0]) / spacing) + 1;
+    nums[1] = (int) ((maxs[1] - mins[1]) / spacing) + 1;
+
+    // test if it will be too coarse
+    if (nums[0] < 20 || nums[1] < 20) return null;
+    // test if its worth it
+    if ((nums[0] * nums[1]) > (len / 4)) return null;
+    // test to see if the product is greater than an int
+    if ((nums[0] * nums[1]) < 0) return null;
+
+    float spacing0 = (maxs[0] - mins[0]) / (nums[0] - 1);
+    float spacing1 = (maxs[1] - mins[1]) / (nums[1] - 1);
+
+    float[][] newval = new float[2][nums[0] * nums[1]];
+    k = 0;
+    for (int i=0; i<nums[0]; i++) {
+      for (int j=0; j<nums[1]; j++) {
+        newval[0][k] = mins[0] + i * spacing0;
+        newval[1][k] = mins[1] + j * spacing1;
+        k++;
+      }
+    }
+    return newval;
+  }
+
+  // use splines to approximate transform
+  private float[][] applySpline(float[][] val, float[] mins,
+                     float[] maxs, int[] nums, float[][] newtrans,
+                     float[][] misstrans, int[][] miss_to_trans)
+         throws VisADException {
+    int n0 = nums[0];
+    int n1 = nums[1];
+    float spacing0 = (maxs[0] - mins[0]) / (n0 - 1);
+    float spacing1 = (maxs[1] - mins[1]) / (n1 - 1);
+    int len = val[0].length;
+    float[][] trans = new float[2][len];
+
+    boolean[] lon_flags = new boolean[n0 * n1];
+    float[] min_trans = {Float.MAX_VALUE, Float.MAX_VALUE};
+    float[] max_trans = {-Float.MAX_VALUE, -Float.MAX_VALUE};
+    for (int i=0; i<n0*n1; i++) {
+      if (newtrans[0][i] == newtrans[0][i]) {
+        if (newtrans[0][i] < min_trans[0]) min_trans[0] = newtrans[0][i];
+        if (newtrans[0][i] > max_trans[0]) max_trans[0] = newtrans[0][i];
+      }
+      if (newtrans[1][i] == newtrans[1][i]) {
+        if (newtrans[1][i] < min_trans[1]) min_trans[1] = newtrans[1][i];
+        if (newtrans[1][i] > max_trans[1]) max_trans[1] = newtrans[1][i];
+      }
+      lon_flags[i] = false;
+    }
+    float minn0n1 = Math.min(n0, n1);
+    float mean0 = (max_trans[0] - min_trans[0]) / minn0n1;
+    float mean1 = (max_trans[1] - min_trans[1]) / minn0n1;
+
+    for (int i0=0; i0<n0-1; i0++) {
+      for (int i1=0; i1<n1-1; i1++) {
+        int ii = i1 + i0 * n1;
+        if (Math.abs(newtrans[0][ii + n1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + 1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + 1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + n1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[1][ii + n1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + 1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + 1]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + n1]) > 3.0 * mean1) {
+          lon_flags[ii] = true;
+        }
+      }
+    }
+
+    int nmiss = 0;
+
+    for (int i=0; i<len; i++) {
+      float a0 = (val[0][i] - mins[0]) / spacing0;
+      int i0 = (int) a0;
+      if (i0 < 0) i0 = 0;
+      if (i0 > (n0 - 2)) i0 = n0 - 2;
+      a0 -= i0;
+      float a1 = (val[1][i] - mins[1]) / spacing1;
+      int i1 = (int) a1;
+      if (i1 < 0) i1 = 0;
+      if (i1 > (n1 - 2)) i1 = n1 - 2;
+      a1 -= i1;
+      int ii = i1 + i0 * n1;
+      if (lon_flags[ii]) {
+        misstrans[0][nmiss] = val[0][i];
+        misstrans[1][nmiss] = val[1][i];
+        miss_to_trans[0][nmiss] = i;
+        nmiss++;
+      }
+      else {
+        trans[0][i] =
+          (1.0f - a0) * ((1.0f - a1) * newtrans[0][ii] + a1 * newtrans[0][ii+1]) +
+          a0 * ((1.0f - a1) * newtrans[0][ii+n1] + a1 * newtrans[0][ii+n1+1]);
+        trans[1][i] =
+          (1.0f - a0) * ((1.0f - a1) * newtrans[1][ii] + a1 * newtrans[1][ii+1]) +
+          a0 * ((1.0f - a1) * newtrans[1][ii+n1] + a1 * newtrans[1][ii+n1+1]);
+
+        if (trans[0][i] != trans[0][i] || trans[1][i] != trans[1][i]) {
+          misstrans[0][nmiss] = val[0][i];
+          misstrans[1][nmiss] = val[1][i];
+          miss_to_trans[0][nmiss] = i;
+          nmiss++;
+        }
+      }
+    }
+
+    if (nmiss == 0) {
+      miss_to_trans[0] = null;
+      misstrans[0] = null;
+      misstrans[1] = null;
+    }
+    else {
+      float[] xmisstrans = new float[nmiss];
+      System.arraycopy(misstrans[0], 0, xmisstrans, 0, nmiss);
+      misstrans[0] = xmisstrans;
+      xmisstrans = new float[nmiss];
+      System.arraycopy(misstrans[1], 0, xmisstrans, 0, nmiss);
+      misstrans[1] = xmisstrans;
+      int[] xmiss_to_trans = new int[nmiss];
+      System.arraycopy(miss_to_trans[0], 0, xmiss_to_trans, 0, nmiss);
+      miss_to_trans[0] = xmiss_to_trans;
+    }
+    return trans;
+  }
+
+  /**
+   * Get the bounds for this image
+   */
+  public Rectangle2D getDefaultMapArea() 
+  { 
+      return new Rectangle2D.Float(0, 0, elements, lines);
+  }
+
+  /**
+   * Determines whether or not the <code>Object</code> in question is
+   * the same as this <code>HRITCoordinateSystem</code>.  The specified
+   * <code>Object</code> is equal to this <CODE>HRITCoordinateSystem</CODE>
+   * if it is an instance of <CODE>HRITCoordinateSystem</CODE> and it has
+   * the same navigation module and default map area as this one.
+   *
+   * @param obj the Object in question
+   */
+  public boolean equals(Object obj)
+  {
+    if (!(obj instanceof HRITCoordinateSystem))
+        return false;
+    HRITCoordinateSystem that = (HRITCoordinateSystem) obj;
+    AREAnav  anav = getAreaNav();
+    return this == that ||
+          (anav.equals(that.getAreaNav()) && 
+           this.lines == that.lines &&
+           this.elements == that.elements);
+  }
+
+  /**
+   * Return a String which tells some info about this navigation
+   * @return wordy String
+   */
+  public String toString() {
+     if (anav == null) {
+        return "Image  Projection";
+     }
+     return "Image (" + anav.toString() + ") Projection";
+  }
+  
+}
diff --git a/visad/data/hrit/HRITForm.java b/visad/data/hrit/HRITForm.java
new file mode 100644
index 0000000..bb187ff
--- /dev/null
+++ b/visad/data/hrit/HRITForm.java
@@ -0,0 +1,151 @@
+//
+// HRITForm.java
+//
+
+/*
+The software in this file is Copyright(C) 2011 by Tommy Jasmin.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.hrit;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.UnimplementedException;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormNode;
+import visad.data.FormFileInformer;
+
+/** 
+ * to allow determination of whether a data file is of type HRIT
+ *
+ */
+public class HRITForm extends Form implements FormFileInformer {
+
+  private HRITAdapter ha;
+
+  public HRITForm() {
+    super("HRITForm");
+  }
+
+  /** 
+   * determine the file type by name. At present we are only
+   * checking for MSG2 HRIT files.  This will change.
+   *
+   * @param name is the filename in question
+   */
+  public boolean isThisType(String name) {
+	  File file = new File(name);
+	  if (file.exists()) name = file.getName();
+	  return (name.contains("MSG2"));
+  }
+
+  /** 
+   * This method will be used to identify an HRIT file by
+   * examining the contents of the first block of data values.
+   * Presently unimplemented.
+   *
+   * @param block is an array of ? length from the beginning
+   * of the file in question.
+   *
+   */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /** return a list of suffixes associated with this file type
+  *
+  */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { " " };
+    return suff;
+  }
+
+  /** save the file back to disk
+  *
+  * This has not been implemented yet
+  *
+  */
+  public synchronized void save(String id, Data data, boolean replace)
+	throws  BadFormException, IOException, RemoteException, VisADException
+  {
+    throw new UnimplementedException("Can't yet save HRIT objects");
+  }
+
+  /** This has not been implemented
+  *
+  */
+  public synchronized void add(String id, Data data, boolean replace)
+	throws BadFormException {
+
+    throw new RuntimeException("Can't yet add HRIT objects");
+  }
+
+  /** read the HRIT file from local disk, and return the HRIT data
+    * as a DataImpl object (a FlatField).
+    *
+    * @param path is the fully-qualified pathname
+    *
+    */
+  public synchronized DataImpl open(String path)
+	throws BadFormException, RemoteException, VisADException {
+
+    try {
+      String [] fileNames = new String[1];
+      fileNames[0] = path;
+      ha = new HRITAdapter(fileNames,1);
+      return ha.getData();
+    } catch (IOException e) {
+      throw new VisADException("IOException: " + e.getMessage());
+    }
+  }
+
+  /** read the HRIT file from a URL, and return the HRIT file
+    * as a DataImpl object (a FlatField).
+    *
+    * @param url is the fully-formed URL
+    *
+    */
+  public synchronized DataImpl open(URL url)
+	throws BadFormException, VisADException, IOException {
+
+    // HRITAdapter constructor decides if argument is a file or a URL
+	String [] urls = new String[1];
+	urls[0] = url.toString();
+    ha = new HRITAdapter(urls,1);
+    return ha.getData();
+  }
+
+  /** not implemented yet
+  *
+  */
+  public synchronized FormNode getForms(Data data) {
+
+    throw new RuntimeException("Can't yet get HRIT forms");
+  }
+}
diff --git a/visad/data/in/AndCondition.java b/visad/data/in/AndCondition.java
new file mode 100644
index 0000000..6ba3b2e
--- /dev/null
+++ b/visad/data/in/AndCondition.java
@@ -0,0 +1,83 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: AndCondition.java,v 1.8 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import visad.*;
+
+/**
+ * Provides support for complementary conditions for a VisAD data object.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class AndCondition
+    extends	Condition
+{
+    private final Condition	conditionA;
+    private final Condition	conditionB;
+
+    /**
+     * Constructs from two, necessary conditions for a VisAD data object.
+     * VisAD data objects that satisfy both conditions will satisfy this
+     * condition.
+     *
+     * @param conditionA	A condition for a VisAD data object.
+     * @param conditionB	A condition for a VisAD data object.
+     */
+    protected AndCondition(Condition conditionA, Condition conditionB)
+    {
+	this.conditionA = conditionA;
+	this.conditionB = conditionB;
+    }
+
+    /**
+     * Returns an instance of this class.  Constructs from two, necessary
+     * conditions for a VisAD data object.  VisAD data objects that satisfy both
+     * conditions will satisfy this condition.
+     *
+     * @param conditionA	A condition for a VisAD data object.
+     * @param conditionB	A condition for a VisAD data object.
+     * @return			An instance of this class.
+     */
+    public static AndCondition andCondition(
+	Condition conditionA, Condition conditionB)
+    {
+	return new AndCondition(conditionA, conditionB);
+    }
+
+    /**
+     * Indicates if a VisAD data object satisfies this condition.
+     *
+     * @param data		A VisAD data object.
+     * @return			<code>true</code> if and only if the VisAD data
+     *				object satisfies both the conditions used
+     *				during this instance's construction.
+     */
+    public boolean isSatisfied(DataImpl data)
+    {
+	return conditionA.isSatisfied(data) && conditionB.isSatisfied(data);
+    }
+}
diff --git a/visad/data/in/ArithProg.java b/visad/data/in/ArithProg.java
new file mode 100644
index 0000000..4abdd2e
--- /dev/null
+++ b/visad/data/in/ArithProg.java
@@ -0,0 +1,311 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: ArithProg.java,v 1.14 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import visad.VisADException;
+
+
+/**
+ * Provides support for determining if a sequence of numeric values corresponds
+ * to an arithmetic progression and, if so, the progression parameters.
+ *
+ * @author Steven R. Emmerson
+ */
+public class ArithProg
+{
+    protected long		n;
+    protected double		first = Double.NaN;
+    protected double		last = Double.NaN;
+    protected double            meanDel = Double.NaN;
+    protected boolean		isConsistent = true;
+    protected final double	fEps = 5e-5f;	// 5 * C FLT_EPS
+    protected final double	dEps = 5e-9;	// 5 * C DBL_EPS
+    private final double[]      dValues = new double[1];
+    private final float[]       fValues = new float[1];
+
+    /**
+     * Accumulates a set of floats.  Indicates whether or not the sequence is
+     * consistent with the arithmetic progression so far.
+     *
+     * @param values		The values to accumulate.
+     * @return			False if the difference between any current
+     *				and previous value normalized by the current
+     *				increment differs from unity by more than the
+     *				nearness threshold; otherwise, true.
+     * @throws VisADException	{@link #isConsistent()} was false before this
+     *				method was invoked.
+     * @precondition		isConsistent() is true.
+     * @postcondition		A subsequent getNumber() will return
+     *				<code>values.length</code> more than previously
+     *				if the function returns true.
+     * @postcondition		A subsequent getLast() will return the
+     *				value argument if the function returns true.
+     */
+    public synchronized boolean accumulate(float[] values)
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".accumulate(float[]): " +
+		"Sequence isn't an arithmetic progression");
+	for (int i = 0; i < values.length; i++)
+	{
+	    double value = values[i];
+	    if (n == 0)
+	    {
+		first = value;
+	    }
+	    else if (n == 1)
+	    {
+		meanDel = value - first;
+	    }
+	    else if (isConsistent)
+	    {
+		accum(value, fEps);
+	    }
+	    last = value;
+	    n++;
+	}
+	return isConsistent;
+    }
+
+    /**
+     * Accumulates a set of doubles.  Indicates whether or not the sequence is
+     * consistent with the arithmetic progression so far.
+     *
+     * @param values		The values to accumulate.
+     * @return			False if the difference between any current
+     *				and previous value normalized by the current
+     *				increment differs from unity by more than the
+     *				nearness threshold; otherwise, true.
+     * @throws VisADException	{@link #isConsistent()} was false before this
+     *				method was invoked.
+     * @precondition		isConsistent() is true.
+     * @postcondition		A subsequent getNumber() will return
+     *				<code>values.length</code> more than previously
+     *				if the function returns true.
+     * @postcondition		A subsequent getLast() will return the
+     *				value argument if the function returns true.
+     */
+    public synchronized boolean accumulate(double[] values)
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".accumulate(double[]): " +
+		"Sequence isn't an arithmetic progression");
+	for (int i = 0; i < values.length; i++)
+	{
+	    double value = values[i];
+	    if (n == 0)
+	    {
+		first = value;
+	    }
+	    else if (n == 1)
+	    {
+		meanDel = value - first;
+	    }
+	    else if (isConsistent)
+	    {
+		accum(value, dEps);
+	    }
+	    last = value;
+	    n++;
+	}
+	return isConsistent;
+    }
+
+    private void accum(double value, double eps)
+    {
+	double uncLast = last*eps;
+	double uncValue = value*eps;
+	double var = uncLast*uncLast + uncValue*uncValue;
+	double err = value - (last + meanDel);
+	if (err*err > var)
+	{
+	    isConsistent = false;
+	}
+	else
+	{
+	    meanDel = (value - first) / n;
+	}
+    }
+
+    /**
+     * Accumulates a value.  Indicate whether or not the value is
+     * consistent with the arithmetic progression so far.
+     *
+     * @param value		The value to accumulate.
+     * @return			False if the difference between any current
+     *				and previous value normalized by the current
+     *				increment differs from unity by more than the
+     *				nearness threshold; otherwise, true.
+     * @throws VisADException	The sequence isn't an arithmetic progression.
+     * @precondition		isConsistent() is true.
+     * @postcondition		A subsequent getNumber() will return
+     *				<code>values.length</code> more than previously
+     *				if the function returns true.
+     * @postcondition		A subsequent getLast() will return the
+     *				value argument if the function returns true.
+     */
+    public final synchronized boolean accumulate(float value)
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".accumulate(double): " +
+		"Sequence isn't an arithmetic progression");
+        fValues[0] = value;
+	return accumulate(fValues);
+    }
+
+    /**
+     * Accumulates a value.  Indicate whether or not the value is
+     * consistent with the arithmetic progression so far.
+     *
+     * @param value		The value to accumulate.
+     * @return			False if the difference between any current
+     *				and previous value normalized by the current
+     *				increment differs from unity by more than the
+     *				nearness threshold; otherwise, true.
+     * @throws VisADException	The sequence isn't an arithmetic progression.
+     * @precondition		isConsistent() is true.
+     * @postcondition		A subsequent getNumber() will return
+     *				<code>values.length</code> more than previously
+     *				if the function returns true.
+     * @postcondition		A subsequent getLast() will return the
+     *				value argument if the function returns true.
+     */
+    public final synchronized boolean accumulate(double value)
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".accumulate(double): " +
+		"Sequence isn't an arithmetic progression");
+        dValues[0] = value;
+	return accumulate(dValues);
+    }
+
+    /**
+     * Indicates whether or not the sequence so far is consistent with an
+     * arithmetic progression.
+     *
+     * @return			<code>true</code> if and only if the sequence
+     *				of values seen so far is consistent with an
+     *				arithmetic progression.
+     */
+    public synchronized final boolean isConsistent() 
+    {
+	return isConsistent;
+    }
+
+    /**
+     * Gets the number of values.  Only meaningfull if {@link #isConsistent()}
+     * is true.
+     *
+     * @return			The number of values accumulated so far.
+     * @throws VisADException	The sequence isn't an arithmetic progression.
+     * @require			isConsistent() is true.
+     */
+    public synchronized final long getNumber() 
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".getNumber(): " +
+		"Sequence isn't an arithmetic progression");
+	return n;
+    }
+
+    /**
+     * Gets the first value.
+     *
+     * @return			The first accumulated value.
+     * @throws VisADException	The sequence isn't an arithmetic progression.
+     * @require			isConsistent() is true.
+     */
+    public synchronized final double getFirst() 
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".getFirst(): " +
+		"Sequence isn't an arithmetic progression");
+	return first;
+    }
+
+    /**
+     * Returns the "last" value accumulated.  It is only meaningfull if
+     * {@link #isConsistent} is true.
+     *
+     * @return			The last accumulated value.
+     * @throws VisADException	The sequence isn't an arithmetic progression.
+     */
+    public synchronized final double getLast()
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".getLast(): " +
+		"Sequence isn't an arithmetic progression");
+	return last;
+    }
+
+    /**
+     * Gets the current common difference.  Only meaningfull if {@link 
+     * #isConsistent()} is true.
+     *
+     * @return			The computed common difference so far.
+     * @throws VisADException	The sequence isn't an arithmetic progression.
+     * @require			isConsistent() is true.
+     */
+    public synchronized final double getCommonDifference() 
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".getCommonDifference(): " +
+		"Sequence isn't an arithmetic progression");
+	return meanDel;
+    }
+
+    public static void main(String[] args)
+        throws Exception
+    {
+	double[] lats = {
+	    39.2, 39.21, 39.22, 39.23, 39.24, 39.25, 39.26, 39.27, 39.28, 39.29,
+	    39.3, 39.31, 39.32, 39.33, 39.34, 39.35, 39.36, 39.37, 39.38, 39.39,
+	    39.4, 39.41, 39.42, 39.43, 39.44, 39.45, 39.46, 39.47, 39.48, 39.49,
+	    39.5, 39.51, 39.52, 39.53, 39.54, 39.55, 39.56, 39.57, 39.58, 39.59,
+	    39.6, 39.61, 39.62, 39.63, 39.64, 39.65, 39.66, 39.67, 39.68, 39.69,
+	    39.7, 39.71, 39.72, 39.73, 39.74, 39.75, 39.76, 39.77, 39.78, 39.79,
+	    39.8, 39.81, 39.82, 39.83, 39.84, 39.85, 39.86, 39.87, 39.88, 39.89,
+	    39.9, 39.91, 39.92, 39.93, 39.94, 39.95, 39.96, 39.97, 39.98, 39.99,
+	    40 };
+	ArithProg ap = new ArithProg();
+	ap.accumulate(lats);
+    }
+}
diff --git a/visad/data/in/Condition.java b/visad/data/in/Condition.java
new file mode 100644
index 0000000..ffaf0a1
--- /dev/null
+++ b/visad/data/in/Condition.java
@@ -0,0 +1,60 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: Condition.java,v 1.8 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import visad.*;
+
+/**
+ * Provides support for applying arbitrary conditions to VisAD data objects.
+ * This class supports data filters like {@link Selector}.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class Condition
+{
+    /**
+     * The trivial condition.  The {@link #isSatisfied} method of this condition
+     * always returns <code>true</code>.
+     */
+    public static Condition	TRIVIAL_CONDITION = 
+	new Condition()
+	{
+	    public boolean isSatisfied(DataImpl data)
+	    {
+		return true;
+	    }
+	};
+
+    /**
+     * Indicates if a VisAD data object satisfies this condition.
+     *
+     * @param data		A VisAD data object.
+     * @return			<code>true</code> if and only if the VisAD data
+     *				object satisfies this instance's condition.
+     */
+    public abstract boolean isSatisfied(DataImpl data);
+}
diff --git a/visad/data/in/Consolidator.java b/visad/data/in/Consolidator.java
new file mode 100644
index 0000000..13368a9
--- /dev/null
+++ b/visad/data/in/Consolidator.java
@@ -0,0 +1,96 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: Consolidator.java,v 1.12 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import java.rmi.RemoteException;
+import java.util.*;
+import visad.*;
+
+/**
+ * Consolidates VisAD data objects together.  In general, an instance of this
+ * class will be the final module in a data-import pipe.
+ *
+ * @author Steven R. Emmerson
+ */
+public final class Consolidator
+    extends	DataInputFilter
+{
+    /**
+     * Constructs with a particular upstream data source.
+     *
+     * @param source		The upstream data source.  May not be
+     *				<code>null</code>.
+     * @throws VisADException	The upstream data source is <code>null</code>.
+     */
+    public Consolidator(DataInputStream source)
+	throws VisADException
+    {
+        super(source);
+    }
+
+    /**
+     * Returns the next VisAD data object in the input stream. Returns 
+     * <code>null</code> if there is no next object.
+     *
+     * @return			A VisAD data object or <code>null</code> if 
+     *				there are no more such objects.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public synchronized DataImpl readData()
+	throws VisADException, RemoteException
+    {
+	System.gc();
+	DataInputStream	source = getSource();
+	boolean		allReals = true;
+	DataImpl	data;
+	List		datums = new ArrayList();
+	while ((data = source.readData()) != null)
+	{
+	    allReals &= data instanceof Real;
+	    datums.add(data);
+	}
+	int	count = datums.size();
+	if (count == 0)
+	{
+	    data = null;
+	}
+	else if (count == 1)
+	{
+	    data = (DataImpl)datums.get(0);
+	}
+	else
+	{
+	    data =
+		allReals
+		    ? (DataImpl)
+			new RealTuple((Real[])datums.toArray(new Real[0]))
+		    : new Tuple(
+			(DataImpl[])datums.toArray(new DataImpl[0]),
+			/*copy=*/false);
+	}
+	return data;
+    }
+}
diff --git a/visad/data/in/DataInputFilter.java b/visad/data/in/DataInputFilter.java
new file mode 100644
index 0000000..2d73933
--- /dev/null
+++ b/visad/data/in/DataInputFilter.java
@@ -0,0 +1,67 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: DataInputFilter.java,v 1.7 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import visad.VisADException;
+
+/**
+ * Provides support for a filter-module in a data-import pipe.	In general,
+ * such a filter-module obtains VisAD data objects its upstream data source and
+ * transforms them in some way before passing them on.
+ *
+ * @author Steven R. Emmerson
+ */
+abstract public class DataInputFilter
+    implements	DataInputStream
+{
+    private final DataInputStream	source;
+
+    /**
+     * Constructs from an upstream data source.
+     *
+     * @param source		The upstream data source.  May not be
+     *				<code>null</code>.
+     * @throws VisADException	The upstream data source is <code>null</code>.
+     */
+    protected DataInputFilter(DataInputStream source)
+	throws VisADException
+    {
+	if (source == null)
+	    throw new VisADException(
+		getClass().getName() + ".<init>(DataInputStream): " +
+		"Null data source");
+        this.source = source;
+    }
+
+    /**
+     * Returns the upstream data source.
+     *
+     * @return			The upstream data source.
+     */
+    public final DataInputStream getSource()
+    {
+	return source;
+    }
+}
diff --git a/visad/data/in/DataInputSource.java b/visad/data/in/DataInputSource.java
new file mode 100644
index 0000000..2cc8e2b
--- /dev/null
+++ b/visad/data/in/DataInputSource.java
@@ -0,0 +1,50 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: DataInputSource.java,v 1.7 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import java.rmi.RemoteException;
+import visad.data.BadFormException;
+import visad.VisADException;
+
+/**
+ * Interface for sources of VisAD data objects.
+ *
+ * @author Steven R. Emmerson
+ */
+public interface DataInputSource
+    extends	DataInputStream
+{
+    /**
+     * Opens an existing dataset.
+     *
+     * @param spec		The specification of the existing dataset.
+     * @throws BadFormException	The DODS dataset is corrupt.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+	
+    void open(String spec)
+	throws BadFormException, RemoteException, VisADException;
+}
diff --git a/visad/data/in/DataInputStream.java b/visad/data/in/DataInputStream.java
new file mode 100644
index 0000000..54f5b9a
--- /dev/null
+++ b/visad/data/in/DataInputStream.java
@@ -0,0 +1,50 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: DataInputStream.java,v 1.7 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Interface for a filter-module in a data-import pipe.  In general, such
+ * a filter-module obtains VisAD data objects its upstream data source and
+ * transforms them in some way before passing them on.
+ *
+ * @author Steven R. Emmerson
+ */
+public interface DataInputStream 
+{
+    /**
+     * Returns the next VisAD data object in the input stream. Returns 
+     * <code>null</code> if there is no next object.
+     *
+     * @return			A VisAD data object or <code>null</code> if 
+     *				there are no more such objects.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    DataImpl readData()
+	throws VisADException, RemoteException;
+}
diff --git a/visad/data/in/DoubleValueVetter.java b/visad/data/in/DoubleValueVetter.java
new file mode 100644
index 0000000..848bea7
--- /dev/null
+++ b/visad/data/in/DoubleValueVetter.java
@@ -0,0 +1,190 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+ * Provides support for verifying data values (i.e. seeing that they aren't
+ * equal to either of two special values).
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+final class DoubleValueVetter
+    extends	ValueVetter
+{
+    private double			doubleValue1;
+    private double			doubleValue2;
+    private float			floatValue1;
+    private float			floatValue2;
+    private static final WeakHashMap	map = new WeakHashMap();
+
+    /**
+     * Constructs from two special values.
+     *
+     * @param value1		A special value.
+     * @param value2		The other special value.
+     */
+    private DoubleValueVetter(double value1, double value2)
+    {
+	doubleValue1 = value1;
+	doubleValue2 = value2;
+	floatValue1 = (float)value1;
+	floatValue2 = (float)value2;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to two special values.
+     *
+     * @param value1		A special value.
+     * @param value2		The other special value.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     */
+    static synchronized DoubleValueVetter doubleValueVetter(
+	double value1, double value2)
+    {
+	DoubleValueVetter	vetter = new DoubleValueVetter(value1, value2);
+	WeakReference	ref = (WeakReference)map.get(vetter);
+	if (ref == null)
+	{
+	    map.put(vetter, new WeakReference(vetter));
+	}
+	else
+	{
+	    DoubleValueVetter	oldVetter = (DoubleValueVetter)ref.get();
+	    if (oldVetter == null)
+		map.put(vetter, new WeakReference(vetter));
+	    else
+		vetter = oldVetter;
+	}
+	return vetter;
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The value to be processed.
+     * @return			If the value equals the special value,
+     *				then Float.NaN; otherwise, the original value.
+     */
+    public float process(float value)
+    {
+	return
+	    value == floatValue1 || value == floatValue2
+		? Float.NaN
+		: value;
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The value to be processed.
+     * @return			If the value equals the special value,
+     *				then Double.NaN; otherwise, the original value.
+     */
+    public double process(double value)
+    {
+	return
+	    value == doubleValue1 || value == doubleValue2
+		? Double.NaN
+		: value;
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The values to be processed.
+     * @return			Vetted values (same array as input).
+     *				If an element equals the special value,
+     *				then that element is set to Float.NaN.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	{
+	    float	value = values[i];
+	    if (value == floatValue1 || value == floatValue2)
+		values[i] = Float.NaN;
+	}
+	return values;
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The values to be processed.
+     * @return			Vetted values (same array as input).
+     *				If an element equals the special value,
+     *				then that element is set to Double.NaN.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	{
+	    double	value = values[i];
+	    if (value == doubleValue1 || value == doubleValue2)
+		values[i] = Double.NaN;
+	}
+	return values;
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     *
+     * @param			The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    DoubleValueVetter	that = (DoubleValueVetter)obj;
+	    equals = this == that || (
+		doubleValue1 == that.doubleValue1 &&
+		doubleValue2 == that.doubleValue2);
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	return 
+	    new Double(doubleValue1).hashCode() ^
+	    new Double(doubleValue2).hashCode();
+    }
+}
diff --git a/visad/data/in/LonArithProg.java b/visad/data/in/LonArithProg.java
new file mode 100644
index 0000000..eafbd9d
--- /dev/null
+++ b/visad/data/in/LonArithProg.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: LonArithProg.java,v 1.3 2001-11-06 17:37:41 steve Exp $
+ */
+
+package visad.data.in;
+
+import visad.VisADException;
+
+/**
+ * Provides support for determining if a sequence of values is an arithmetic
+ * progression of longitude values and, if so, the progression parameters.
+ *
+ * @author Steven R. Emmerson
+ */
+public final class LonArithProg
+    extends	ArithProg
+{
+    private double              sumDel = Double.NaN;
+
+    /**
+     * Accumulates a set of floats.  Indicates whether or not the sequence is
+     * consistent with the arithmetic progression so far.
+     *
+     * @param values		The values to accumulate.
+     * @return			False if the difference between any current
+     *				and previous value normalized by the current
+     *				increment differs from unity by more than the
+     *				nearness threshold; otherwise, true.
+     * @throws VisADException	{@link #isConsistent()} was false before this
+     *				method was invoked.
+     * @precondition		isConsistent() is true.
+     * @postcondition		A subsequent getNumber() will return
+     *				<code>values.length</code> more than previously
+     *				if the function returns true.
+     * @postcondition		A subsequent getLast() will return the
+     *				value argument if the function returns true.
+     */
+    public synchronized boolean accumulate(float[] values)
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".accumulate(float[]): " +
+		"Sequence isn't an arithmetic progression");
+	for (int i = 0; i < values.length; i++)
+	{
+	    double value = values[i];
+	    if (n == 0)
+	    {
+		first = value;
+	    }
+	    else if (n == 1)
+	    {
+		sumDel = meanDel = delta(first, value);
+	    }
+	    else if (isConsistent)
+	    {
+		accum(value, fEps);
+	    }
+	    last = value;
+	    n++;
+	}
+	return isConsistent;
+    }
+
+    /**
+     * Accumulates a set of doubles.  Indicates whether or not the sequence is
+     * consistent with the arithmetic progression so far.
+     *
+     * @param values		The values to accumulate.
+     * @return			False if the difference between any current
+     *				and previous value normalized by the current
+     *				increment differs from unity by more than the
+     *				nearness threshold; otherwise, true.
+     * @throws VisADException	{@link #isConsistent()} was false before this
+     *				method was invoked.
+     * @precondition		isConsistent() is true.
+     * @postcondition		A subsequent getNumber() will return
+     *				<code>values.length</code> more than previously
+     *				if the function returns true.
+     * @postcondition		A subsequent getLast() will return the
+     *				value argument if the function returns true.
+     */
+    public synchronized boolean accumulate(double[] values)
+	throws VisADException
+    {
+	if (!isConsistent())
+	    throw new VisADException(
+		getClass().getName() + ".accumulate(double[]): " +
+		"Sequence isn't an arithmetic progression");
+	for (int i = 0; i < values.length; i++)
+	{
+	    double value = values[i];
+	    if (n == 0)
+	    {
+		first = value;
+	    }
+	    else if (n == 1)
+	    {
+		sumDel = meanDel = delta(first, value);
+	    }
+	    else if (isConsistent)
+	    {
+		accum(value, dEps);
+	    }
+	    last = value;
+	    n++;
+	}
+	return isConsistent;
+    }
+
+    private void accum(double value, double eps)
+    {
+	double uncLast = last*eps;
+	double uncValue = value*eps;
+	double var = uncLast*uncLast + uncValue*uncValue;
+	double err = delta(last + meanDel, value);
+	if (err*err > var)
+	{
+	    isConsistent = false;
+	}
+	else
+	{
+	    sumDel += delta(last, value);
+	    meanDel = sumDel / n;
+	}
+    }
+
+    /**
+     * Returns the difference between two values.
+     *
+     * @param value1		The first value.
+     * @param value2		The second value.
+     * @return			The increment from the first value to the
+     *				second value.
+     */
+    private double delta(double value1, double value2)
+    {
+	double	delta = (value2 - value1) % 360.0;
+	if (delta < -180.0)
+	    delta += 360.0;
+	else if (delta > 180.0)
+	    delta -= 360.0;
+	return delta;
+    }
+
+    /**
+     * Tests this class.
+     *
+     * @param args		Runtime arguments.  Ignored.
+     * @throws Exception	Something went wrong.
+     */
+    public static void main(String[] args)
+	throws Exception
+    {
+	double[] lons = {
+	    179.2, 179.21, 179.22, 179.23, 179.24, 179.25, 179.26, 179.27, 179.28, 179.29,
+	    179.3, 179.31, 179.32, 179.33, 179.34, 179.35, 179.36, 179.37, 179.38, 179.39,
+	    179.4, 179.41, 179.42, 179.43, 179.44, 179.45, 179.46, 179.47, 179.48, 179.49,
+	    179.5, 179.51, 179.52, 179.53, 179.54, 179.55, 179.56, 179.57, 179.58, 179.59,
+	    179.6, 179.61, 179.62, 179.63, 179.64, 179.65, 179.66, 179.67, 179.68, 179.69,
+	    179.7, 179.71, 179.72, 179.73, 179.74, 179.75, 179.76, 179.77, 179.78, 179.79,
+	    179.8, 179.81, 179.82, 179.83, 179.84, 179.85, 179.86, 179.87, 179.88, 179.89,
+	    179.9, 179.91, 179.92, 179.93, 179.94, 179.95, 179.96, 179.97, 179.98, 179.99,
+	    180,
+	    -179.99, -179.98, -179.97, -179.96, -179.95, -179.94, -179.93, -179.92, -179.91, -179.9,
+	    -179.89, -179.88, -179.87, -179.86, -179.85, -179.84, -179.83, -179.82, -179.81, -179.8,
+	    -179.79, -179.78, -179.77, -179.76, -179.75, -179.74, -179.73, -179.72, -179.71, -179.7,
+	    -179.69, -179.68, -179.67, -179.66, -179.65, -179.64, -179.63, -179.62, -179.61, -179.6,
+	    -179.59, -179.58, -179.57, -179.56, -179.55, -179.54, -179.53, -179.52, -179.51, -179.5,
+	    -179.49, -179.48, -179.47, -179.46, -179.45, -179.44, -179.43, -179.42, -179.41, -179.4,
+	    -179.39, -179.38, -179.37, -179.36, -179.35, -179.34, -179.33, -179.32, -179.31, -179.3,
+	    -179.29, -179.28, -179.27, -179.26, -179.25, -179.24, -179.23, -179.22, -179.21, -179.2};
+	LonArithProg	ap = new LonArithProg();
+
+	ap.accumulate(lons);
+
+	System.out.println("ap.isConsistent()=" + ap.isConsistent());
+	System.out.println("ap.getFirst()=" + ap.getFirst());
+	System.out.println("ap.getLast()=" + ap.getLast());
+	System.out.println("ap.getNumber()=" + ap.getNumber());
+	System.out.println("ap.getCommonDifference()=" + 
+	    ap.getCommonDifference());
+    }
+}
diff --git a/visad/data/in/MD5Key.java b/visad/data/in/MD5Key.java
new file mode 100644
index 0000000..bceb20f
--- /dev/null
+++ b/visad/data/in/MD5Key.java
@@ -0,0 +1,131 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: MD5Key.java,v 1.9 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import java.io.*;
+import java.security.MessageDigest;
+
+/**
+ * Provides support for computing an MD5 key for an object.  Such a key may be
+ * used to obtain a hash code for an object and also supports implementation of
+ * a fast {@link #equals(Object)} method for objects whose natural method might
+ * be computationally expensive.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class MD5Key
+{
+    private final byte[]		checksum;
+
+    private static MessageDigest	digester;
+
+    static
+    {
+	try
+	{
+	    digester = MessageDigest.getInstance("MD5");
+	}
+	catch (Exception e)
+	{
+	    System.err.println(
+		"MD5Key.<clinit>: Couldn't initialize class: " + e);
+	    System.exit(1);
+	}
+    }
+
+    /**
+     * Constructs from an object.
+     *
+     * @param obj		The object to have an MD5 checksum computed for
+     *				it.
+     * @throws IOException	Couldn't compute MD5 checksum.
+     */
+    public MD5Key(Object obj)
+	throws IOException
+    {
+	this(new Object[] {obj});
+    }
+
+    /**
+     * Constructs from an array of objects.
+     *
+     * @param objs		The objects to have an MD5 checksum computed for
+     *				them.
+     * @throws IOException      Couldn't compute MD5 checksum.
+     */
+    public MD5Key(Object[] objs)
+	throws IOException
+    {
+	ByteArrayOutputStream	byteArrayOutputStream =
+	    new ByteArrayOutputStream();
+	ObjectOutputStream	objectOutputStream =
+	    new ObjectOutputStream(byteArrayOutputStream);
+	for (int i = 0; i < objs.length; ++i)
+	    objectOutputStream.writeObject(objs[i]);
+	objectOutputStream.flush();
+	byteArrayOutputStream.flush();
+	digester.update(byteArrayOutputStream.toByteArray());
+	checksum = digester.digest();
+	objectOutputStream.close();
+	byteArrayOutputStream.close();
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     * This will be the case if the other object is also of this class and its
+     * MD5 checksum is identical to this instances MD5 checksum.
+     *
+     * @param obj		The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    MD5Key	that = (MD5Key)obj;
+	    equals = this == that || checksum.equals(that.checksum);
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.  The hash code is the hash code
+     * of the MD5 checksum.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	return checksum.hashCode();
+    }
+}
diff --git a/visad/data/in/MathTypeCondition.java b/visad/data/in/MathTypeCondition.java
new file mode 100644
index 0000000..0af801d
--- /dev/null
+++ b/visad/data/in/MathTypeCondition.java
@@ -0,0 +1,79 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: MathTypeCondition.java,v 1.8 2009-03-02 23:35:48 curtis Exp $
+*/
+
+package visad.data.in;
+
+import visad.*;
+
+/**
+ * Provides support for matching the MathType of a VisAD data object.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class MathTypeCondition
+    extends	Condition
+{
+    private final MathType	mathType;
+
+    /**
+     * Constructs from a VisAD math-type.  VisAD data objects whose math-type
+     * equals the given one will satisfy this condition.
+     *
+     * @param mathType		The VisAD math-type to match against VisAD data
+     *				objects.
+     */
+    protected MathTypeCondition(MathType mathType)
+    {
+	this.mathType = mathType;
+    }
+
+    /**
+     * Returns an instance of this class.  Constructs from a VisAD math-type.
+     * VisAD data objects whose math-type equals the given one will satisfy this
+     * condition.
+     *
+     * @param mathType		The VisAD math-type to match against VisAD data
+     *				objects.
+     * @return			An instance of this class.
+     */
+    public static MathTypeCondition mathTypeCondition(MathType mathType)
+    {
+	return new MathTypeCondition(mathType);
+    }
+
+    /**
+     * Indicates if a VisAD data object satisfies this condition.
+     *
+     * @param data		A VisAD data object.
+     * @return			<code>true</code> if and only if the math-type
+     *				of the VisAD data object equals the math-type
+     *				used during this instance's construction.
+     */
+    public boolean isSatisfied(DataImpl data)
+    {
+	return data.getType().equals(mathType);
+    }
+}
diff --git a/visad/data/in/MultipleValueVetter.java b/visad/data/in/MultipleValueVetter.java
new file mode 100644
index 0000000..ca79d5c
--- /dev/null
+++ b/visad/data/in/MultipleValueVetter.java
@@ -0,0 +1,196 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.lang.ref.WeakReference;
+import java.util.*;
+
+/**
+ * Provides support for verifying data values (i.e. seeing that they aren't
+ * equal to any special values).
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+final class MultipleValueVetter
+    extends	ValueVetter
+{
+    private double[]			doubleValues;
+    private float[]			floatValues;
+    private static final WeakHashMap	map = new WeakHashMap();
+
+    /**
+     * Constructs from an array of special values.
+     *
+     * @param specialValues	The special values.  Elements may be NaN.
+     *				WARNING: The array isn't cloned, so don't
+     *				modify it.
+     */
+    private MultipleValueVetter(double[] values)
+    {
+	this.doubleValues = values;
+	floatValues = new float[values.length];
+	for (int i = 0; i < values.length; ++i)
+	    floatValues[i] = (float)values[i];
+    }
+
+    /**
+     * Returns an instance of this class corresponding to special values.
+     *
+     * @param specialValues	The special values.  Elements may be NaN.
+     *				WARNING: The array isn't cloned, so don't
+     *				modify it.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     */
+    static synchronized MultipleValueVetter multipleValueVetter(
+	double[] values)
+    {
+	MultipleValueVetter	vetter = new MultipleValueVetter(values);
+	WeakReference	ref = (WeakReference)map.get(vetter);
+	if (ref == null)
+	{
+	    map.put(vetter, new WeakReference(vetter));
+	}
+	else
+	{
+	    MultipleValueVetter	oldVetter = (MultipleValueVetter)ref.get();
+	    if (oldVetter == null)
+		map.put(vetter, new WeakReference(vetter));
+	    else
+		vetter = oldVetter;
+	}
+	return vetter;
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The value to be processed.
+     * @return			If the value equals one of the special values,
+     *				then Float.NaN; otherwise, the original value.
+     */
+    public float process(float value)
+    {
+	for (int i = 0; i < floatValues.length; ++i)
+	    if (value == floatValues[i])
+		return Float.NaN;
+	return value;
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The value to be processed.
+     * @return			If the value equals one of the special values,
+     *				then Double.NaN; otherwise, the original value.
+     */
+    public double process(double value)
+    {
+	for (int i = 0; i < doubleValues.length; ++i)
+	    if (value == doubleValues[i])
+		return Double.NaN;
+	return value;
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The values to be processed.
+     * @return			Vetted values (same array as input).
+     *				If an element equals one of the special values,
+     *				then that element is set to Float.NaN.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	{
+	    for (int j = 0; j < floatValues.length; ++j)
+		if (values[i] == floatValues[j])
+		{
+		    values[i] = Float.NaN;
+		    break;
+		}
+	}
+	return values;
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The values to be processed.
+     * @return			Vetted values (same array as input).
+     *				If an element equals one of the special values,
+     *				then that element is set to Float.NaN.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	{
+	    for (int j = 0; j < doubleValues.length; ++j)
+		if (values[i] == doubleValues[j])
+		{
+		    values[i] = Double.NaN;
+		    break;
+		}
+	}
+	return values;
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     *
+     * @param			The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    MultipleValueVetter	that = (MultipleValueVetter)obj;
+	    equals = this == that ||
+		Arrays.equals(doubleValues, that.doubleValues);
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	int	code = 0;
+	for (int i = 0; i < doubleValues.length; ++i)
+	    code ^= new Double(doubleValues[i]).hashCode();
+	return code;
+    }
+}
diff --git a/visad/data/in/NotCondition.java b/visad/data/in/NotCondition.java
new file mode 100644
index 0000000..eb5f7fb
--- /dev/null
+++ b/visad/data/in/NotCondition.java
@@ -0,0 +1,77 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: NotCondition.java,v 1.8 2009-03-02 23:35:49 curtis Exp $
+*/
+
+package visad.data.in;
+
+import visad.*;
+
+/**
+ * Provides support for negating the condition for a VisAD data object.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class NotCondition
+    extends	Condition
+{
+    private final Condition	condition;
+
+    /**
+     * Constructs from a condition for a VisAD data object.  VisAD data objects
+     * that do not satisfy the given condition will satisfy this condition.
+     *
+     * @param condition		A condition for a VisAD data object.
+     */
+    protected NotCondition(Condition condition)
+    {
+	this.condition = condition;
+    }
+
+    /**
+     * Returns an instance of this class.  Constructs from a condition for a
+     * VisAD data object.  VisAD data objects that do not satisfy the given
+     * condition will satisfy this condition.
+     *
+     * @param condition		A condition for a VisAD data object.
+     * @return			An instance of this class.
+     */
+    public static NotCondition notCondition(Condition condition)
+    {
+	return new NotCondition(condition);
+    }
+
+    /**
+     * Indicates if a VisAD data object satisfies this condition.
+     *
+     * @param data		A VisAD data object.
+     * @return			<code>true</code> if and only if the VisAD data
+     *				object doesn't satisfy the condition used during
+     *				this instance's construction.
+     */
+    public boolean isSatisfied(DataImpl data)
+    {
+	return !condition.isSatisfied(data);
+    }
+}
diff --git a/visad/data/in/OffsetUnpacker.java b/visad/data/in/OffsetUnpacker.java
new file mode 100644
index 0000000..eb0bda4
--- /dev/null
+++ b/visad/data/in/OffsetUnpacker.java
@@ -0,0 +1,159 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+ * Provides support for unpacking data values by adding a constant offset to 
+ * them.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class OffsetUnpacker
+    extends	ValueUnpacker
+{
+    private final float			floatOffset;
+    private final double		doubleOffset;
+    private static final WeakHashMap	map = new WeakHashMap();
+
+    private OffsetUnpacker(double offset)
+    {
+	floatOffset = (float)offset;
+	doubleOffset = offset;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to an offset value.
+     *
+     * @param offset		The numeric offset to be added to each value
+     *				during processing.
+     * @return			An instance of this class corresponding to the
+     *				input argument.
+     */
+    public static synchronized OffsetUnpacker offsetUnpacker(double offset)
+    {
+	OffsetUnpacker	unpacker = new OffsetUnpacker(offset);
+	WeakReference	ref = (WeakReference)map.get(unpacker);
+	if (ref == null)
+	{
+	    map.put(unpacker, new WeakReference(unpacker));
+	}
+	else
+	{
+	    OffsetUnpacker	oldUnpacker = (OffsetUnpacker)ref.get();
+	    if (oldUnpacker == null)
+		map.put(unpacker, new WeakReference(unpacker));
+	    else
+		unpacker = oldUnpacker;
+	}
+	return unpacker;
+    }
+
+    /**
+     * Process a value.
+     *
+     * @param value		The value to be processed.
+     * @return			The value with the construction offset added to
+     *				it.
+     */
+    public float process(float value)
+    {
+	return floatOffset + value;
+    }
+
+    /**
+     * Process a value.
+     *
+     * @param value		The value to be processed.
+     * @return			The value with the construction offset added to
+     *				it.
+     */
+    public double process(double value)
+    {
+	return doubleOffset + value;
+    }
+
+    /**
+     * Process values.
+     *
+     * @param values	The values to be processed.
+     * @return			The values with the construction offset added to
+     *				them.  The same array is returned.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    values[i] += floatOffset;
+	return values;
+    }
+
+    /**
+     * Process values.
+     *
+     * @param values	The values to be processed.
+     * @return			The values with the construction offset added to
+     *				them.  The same array is returned.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    values[i] += doubleOffset;
+	return values;
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     *
+     * @param obj		The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    OffsetUnpacker	that = (OffsetUnpacker)obj;
+	    equals = this == that || doubleOffset == that.doubleOffset;
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	return new Double(doubleOffset).hashCode();
+    }
+}
diff --git a/visad/data/in/OrCondition.java b/visad/data/in/OrCondition.java
new file mode 100644
index 0000000..058f85c
--- /dev/null
+++ b/visad/data/in/OrCondition.java
@@ -0,0 +1,83 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: OrCondition.java,v 1.8 2009-03-02 23:35:49 curtis Exp $
+*/
+
+package visad.data.in;
+
+import visad.*;
+
+/**
+ * Provides support for alternative conditions for a VisAD data object.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class OrCondition
+    extends	Condition
+{
+    private final Condition	conditionA;
+    private final Condition	conditionB;
+
+    /**
+     * Constructs from two, alternative conditions for a VisAD data object.
+     * VisAD data objects that satisfy either condition will satisfy this
+     * condition.
+     *
+     * @param conditionA	A condition for a VisAD data object.
+     * @param conditionB	A condition for a VisAD data object.
+     */
+    protected OrCondition(Condition conditionA, Condition conditionB)
+    {
+	this.conditionA = conditionA;
+	this.conditionB = conditionB;
+    }
+
+    /**
+     * Returns an instance of this class.  Constructs from two, alternative
+     * conditions for a VisAD data object.  VisAD data objects that satisfy
+     * either condition will satisfy this condition.
+     *
+     * @param conditionA	A condition for a VisAD data object.
+     * @param conditionB	A condition for a VisAD data object.
+     * @return			An instance of this class.
+     */
+    public static OrCondition orCondition(
+	Condition conditionA, Condition conditionB)
+    {
+	return new OrCondition(conditionA, conditionB);
+    }
+
+    /**
+     * Indicates if a VisAD data object satisfies this condition.
+     *
+     * @param data		A VisAD data object.
+     * @return			<code>true</code> if and only if the VisAD data
+     *				object satisfies one of the conditions used
+     *				during this instance's construction.
+     */
+    public boolean isSatisfied(DataImpl data)
+    {
+	return conditionA.isSatisfied(data) || conditionB.isSatisfied(data);
+    }
+}
diff --git a/visad/data/in/ScaleAndOffsetUnpacker.java b/visad/data/in/ScaleAndOffsetUnpacker.java
new file mode 100644
index 0000000..3f4c0ac
--- /dev/null
+++ b/visad/data/in/ScaleAndOffsetUnpacker.java
@@ -0,0 +1,183 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+ * Provides support for unpacking data values by multiplying them by a constant
+ * scale factor and then adding a constant offset value.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class ScaleAndOffsetUnpacker
+    extends	ValueUnpacker
+{
+    private final float			floatScale;
+    private final double		doubleScale;
+    private final float			floatOffset;
+    private final double		doubleOffset;
+    private static final WeakHashMap	map = new WeakHashMap();
+
+    private ScaleAndOffsetUnpacker(double scale, double offset)
+    {
+	floatScale = (float)scale;
+	doubleScale = scale;
+	floatOffset = (float)offset;
+	doubleOffset = offset;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to an offset value and
+     * scale factor.
+     *
+     * @param scale		The numeric amount to multiply each value by
+     *				during processing.
+     * @param offset		The numeric offset to be added to each value
+     *				during processing.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     */
+    public static synchronized ScaleAndOffsetUnpacker scaleAndOffsetUnpacker(
+	double scale, double offset)
+    {
+	ScaleAndOffsetUnpacker	unpacker =
+	    new ScaleAndOffsetUnpacker(scale, offset);
+	WeakReference	ref = (WeakReference)map.get(unpacker);
+	if (ref == null)
+	{
+	    map.put(unpacker, new WeakReference(unpacker));
+	}
+	else
+	{
+	    ScaleAndOffsetUnpacker	oldUnpacker =
+		(ScaleAndOffsetUnpacker)ref.get();
+	    if (oldUnpacker == null)
+		map.put(unpacker, new WeakReference(unpacker));
+	    else
+		unpacker = oldUnpacker;
+	}
+	return unpacker;
+    }
+
+    /**
+     * Returns the absolute value of the scale value.
+     *
+     * @return			The absolute value of the scale value.
+     */
+    public double getIncrement()
+    {
+	return Math.abs(doubleScale);
+    }
+
+    /**
+     * Process a value.
+     *
+     * @param value		The value to be processed.
+     * @return			The value with the construction offset added to
+     *				it.
+     */
+    public float process(float value)
+    {
+	return floatScale*value + floatOffset;
+    }
+
+    /**
+     * Process a value.
+     *
+     * @param value		The value to be processed.
+     * @return			The value with the construction offset added to
+     *				it.
+     */
+    public double process(double value)
+    {
+	return doubleScale*value + doubleOffset;
+    }
+
+    /**
+     * Process values.
+     *
+     * @param values	The values to be processed.
+     * @return			The values with the construction offset added to
+     *				them.  The same array is returned.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = values[i]*floatScale + floatOffset;
+	return values;
+    }
+
+    /**
+     * Process values.
+     *
+     * @param values	The values to be processed.
+     * @return			The values with the construction offset added to
+     *				them.  The same array is returned.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    values[i] = values[i]*doubleScale + doubleOffset;
+	return values;
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     *
+     * @param obj		The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    ScaleAndOffsetUnpacker	that = (ScaleAndOffsetUnpacker)obj;
+	    equals = this == that || (
+		doubleOffset == that.doubleOffset &&
+		doubleScale == that.doubleScale);
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	return
+	    new Double(doubleOffset).hashCode() ^
+	    new Double(doubleScale).hashCode();
+    }
+}
diff --git a/visad/data/in/ScaleUnpacker.java b/visad/data/in/ScaleUnpacker.java
new file mode 100644
index 0000000..9879e48
--- /dev/null
+++ b/visad/data/in/ScaleUnpacker.java
@@ -0,0 +1,169 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+ * Provides support for unpacking data values by scaling them (i.e. multiplying
+ * by a constant scale factor.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class ScaleUnpacker
+    extends	ValueUnpacker
+{
+    private final float			floatScale;
+    private final double		doubleScale;
+    private static final WeakHashMap	map = new WeakHashMap();
+
+    private ScaleUnpacker(double scale)
+    {
+	floatScale = (float)scale;
+	doubleScale = scale;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a scale factor.
+     *
+     * @param scale		The numeric amount to multiply each value by
+     *				during processing.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     */
+    public static synchronized ScaleUnpacker scaleUnpacker(double scale)
+    {
+	ScaleUnpacker	unpacker = new ScaleUnpacker(scale);
+	WeakReference	ref = (WeakReference)map.get(unpacker);
+	if (ref == null)
+	{
+	    map.put(unpacker, new WeakReference(unpacker));
+	}
+	else
+	{
+	    ScaleUnpacker	oldUnpacker = (ScaleUnpacker)ref.get();
+	    if (oldUnpacker == null)
+		map.put(unpacker, new WeakReference(unpacker));
+	    else
+		unpacker = oldUnpacker;
+	}
+	return unpacker;
+    }
+
+    /**
+     * Returns the absolute value of the scale factor.
+     *
+     * @return			The absolute value of the scale factor.
+     */
+    public double getIncrement()
+    {
+	return Math.abs(doubleScale);
+    }
+
+    /**
+     * Process a value.
+     *
+     * @param value		The value to be processed.
+     * @return			The value with the construction offset added to
+     *				it.
+     */
+    public float process(float value)
+    {
+	return floatScale*value;
+    }
+
+    /**
+     * Process a value.
+     *
+     * @param value		The value to be processed.
+     * @return			The value with the construction offset added to
+     *				it.
+     */
+    public double process(double value)
+    {
+	return doubleScale*value;
+    }
+
+    /**
+     * Process values.
+     *
+     * @param values	The values to be processed.
+     * @return			The values with the construction offset added to
+     *				them.  The same array is returned.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    values[i] *= floatScale;
+	return values;
+    }
+
+    /**
+     * Process values.
+     *
+     * @param values	The values to be processed.
+     * @return			The values with the construction offset added to
+     *				them.  The same array is returned.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    values[i] *= doubleScale;
+	return values;
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     *
+     * @param obj		The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    ScaleUnpacker	that = (ScaleUnpacker)obj;
+	    equals = this == that || doubleScale == that.doubleScale;
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	return new Double(doubleScale).hashCode();
+    }
+}
diff --git a/visad/data/in/Selector.java b/visad/data/in/Selector.java
new file mode 100644
index 0000000..749a105
--- /dev/null
+++ b/visad/data/in/Selector.java
@@ -0,0 +1,90 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: Selector.java,v 1.14 2009-03-02 23:35:49 curtis Exp $
+*/
+
+package visad.data.in;
+
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Provides support for removing unwanted VisAD data objects from
+ * a stream of VisAD data objects.
+ *
+ * <P>Instances are modifiable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class Selector
+    extends DataInputFilter
+{
+    private Condition	condition;
+
+    /**
+     * Constructs from an upstream data source.  The initial condition is set
+     * to the trivial condition {@link Condition#TRIVIAL_CONDITION}.
+     *
+     * @param source		The upstream data source.  May not be
+     *				<code>null</code>.
+     * @throws VisADException	The upstream data source is <code>null</code>.
+     */
+    public Selector(DataInputStream source)
+	throws VisADException
+    {
+	super(source);
+	condition = Condition.TRIVIAL_CONDITION;
+    }
+
+    /**
+     * Sets the condition for passing VisAD data objects.  A VisAD object that
+     * satisfies the condition will be passed on to the the downstream data
+     * sink.  All others will be rejected.
+     *
+     * @param condition		The pass/reject condition.
+     */
+    public void setCondition(Condition condition)
+    {
+	this.condition = condition;
+    }
+
+    /**
+     * Returns the next VisAD data object in the input stream that satisfies
+     * the selection condition. Returns <code>null</code> if there is no such
+     * object.
+     *
+     * @return			A VisAD data object or <code>null</code> if 
+     *				there are no more such objects.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public synchronized DataImpl readData()
+	throws VisADException, RemoteException
+    {
+	DataInputStream	source = getSource();
+	DataImpl	data;
+	while ((data = source.readData()) != null)
+	    if (condition.isSatisfied(data))
+		break;
+	return data;
+    }
+}
diff --git a/visad/data/in/SingleValueVetter.java b/visad/data/in/SingleValueVetter.java
new file mode 100644
index 0000000..22da6bb
--- /dev/null
+++ b/visad/data/in/SingleValueVetter.java
@@ -0,0 +1,167 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+ * Provides support for verifying data values (i.e. seeing that they aren't
+ * equal to a special value).
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+final class SingleValueVetter
+    extends	ValueVetter
+{
+    private double			doubleValue;
+    private float			floatValue;
+    private static final WeakHashMap	map = new WeakHashMap();
+
+    /**
+     * Constructs from a special value.
+     *
+     * @param value		The special value.
+     */
+    private SingleValueVetter(double value)
+    {
+	doubleValue = value;
+	floatValue = (float)value;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to a special value.
+     *
+     * @param value		The special value.
+     * @return			An instance of this class corresponding to the
+     *				input arguments.
+     */
+    static synchronized SingleValueVetter singleValueVetter(double value)
+    {
+	SingleValueVetter	vetter = new SingleValueVetter(value);
+	WeakReference	ref = (WeakReference)map.get(vetter);
+	if (ref == null)
+	{
+	    map.put(vetter, new WeakReference(vetter));
+	}
+	else
+	{
+	    SingleValueVetter	oldVetter = (SingleValueVetter)ref.get();
+	    if (oldVetter == null)
+		map.put(vetter, new WeakReference(vetter));
+	    else
+		vetter = oldVetter;
+	}
+	return vetter;
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The value to be processed.
+     * @return			If the value equals the special value,
+     *				then Float.NaN; otherwise, the original value.
+     */
+    public float process(float value)
+    {
+	return value == floatValue ? Float.NaN : value;
+    }
+
+    /**
+     * Processes a value.
+     *
+     * @param value		The value to be processed.
+     * @return			If the value equals the special value,
+     *				then Double.NaN; otherwise, the original value.
+     */
+    public double process(double value)
+    {
+	return value == doubleValue ? Double.NaN : value;
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The values to be processed.
+     * @return			Vetted values (same array as input).
+     *				If an element equals the special value,
+     *				then that element is set to Float.NaN.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    if (values[i] == floatValue)
+		values[i] = Float.NaN;
+	return values;
+    }
+
+    /**
+     * Processes values.
+     *
+     * @param values		The values to be processed.
+     * @return			Vetted values (same array as input).
+     *				If an element equals the special value,
+     *				then that element is set to Double.NaN.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	    if (values[i] == doubleValue)
+		values[i] = Double.NaN;
+	return values;
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     *
+     * @param			The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    SingleValueVetter	that = (SingleValueVetter)obj;
+	    equals = this == that || doubleValue == that.doubleValue;
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	return new Double(doubleValue).hashCode();
+    }
+}
diff --git a/visad/data/in/TimeFactorer.java b/visad/data/in/TimeFactorer.java
new file mode 100644
index 0000000..78604be
--- /dev/null
+++ b/visad/data/in/TimeFactorer.java
@@ -0,0 +1,105 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: TimeFactorer.java,v 1.12 2009-03-02 23:35:49 curtis Exp $
+*/
+
+package visad.data.in;
+
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Converts incoming VisAD Fields whose outermost dimension is time and
+ * can be factored out into a field-of-fields.  Sends the field-of-fields to the
+ * downstream data sink.  Passes all other VisAD data objects to the downstream
+ * data sink unchanged.
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class TimeFactorer
+    extends	DataInputFilter
+{
+    /**
+     * Constructs from a upstream data source.
+     *
+     * @param source		The upstream data source.  May not be
+     *				<code>null</code>.
+     * @throws VisADException	The upstream data source is <code>null</code>.
+     */
+    public TimeFactorer(DataInputStream source)
+	throws VisADException
+    {
+	super(source);
+    }
+
+    /**
+     * Returns the next VisAD data object in the input stream.	If the next
+     * object is a field and the field's outermost dimension is time and can be
+     * factored out, then the field will be converted into a field-of-fields
+     * (with time as the single dimension of the outermost field) before being
+     * returned.  Returns <code>null</code> if there are no more objects.
+     *
+     * @return			A VisAD data object or <code>null</code> if 
+     *				there are no more such objects.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public synchronized DataImpl readData()
+	throws VisADException, RemoteException
+    {
+	DataImpl	data = getSource().readData();
+	if (data instanceof FieldImpl)
+	{
+	    FieldImpl	field = (FieldImpl)data;
+	    RealTupleType	domainType =
+		((FunctionType)field.getType()).getDomain();
+	    int		dimensionCount = domainType.getDimension();
+	    if (dimensionCount > 1)
+	    {
+		RealType	outerDimensionType = (RealType)
+		    domainType.getComponent(dimensionCount - 1);
+		if (RealType.Time.equalsExceptNameButUnits(
+			outerDimensionType) ||
+		      RealType.TimeInterval.equalsExceptNameButUnits(
+			outerDimensionType))
+		{
+		    Set		domain = field.getDomainSet();
+		    if (domain instanceof ProductSet ||
+			domain instanceof LinearSet)
+		    {
+			try
+			{
+			    field = (FieldImpl)
+				field.domainFactor(outerDimensionType);
+			}
+			catch (DomainException e)
+			{}	// the domain of the field isn't factorable
+		    }
+		}
+	    }
+	    data = field;
+	}
+	return data;
+    }
+}
diff --git a/visad/data/in/ValueProcessor.java b/visad/data/in/ValueProcessor.java
new file mode 100644
index 0000000..5c41e7e
--- /dev/null
+++ b/visad/data/in/ValueProcessor.java
@@ -0,0 +1,101 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+
+$Id: ValueProcessor.java,v 1.9 2009-03-02 23:35:49 curtis Exp $
+*/
+
+package visad.data.in;
+
+/**
+ * Provides support for processing primitive data values (i.e. checking their
+ * values, converting them, etc.).
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class ValueProcessor
+{
+    /**
+     * The trivial processor (does nothing).
+     */
+    protected static final ValueProcessor	trivialProcessor =
+	new ValueProcessor()
+	{
+	    public float process(float value)
+	    {
+		return value;
+	    }
+
+	    public double process(double value)
+	    {
+		return value;
+	    }
+
+	    public float[] process(float[] values)
+	    {
+		return values;
+	    }
+
+	    public double[] process(double[] values)
+	    {
+		return values;
+	    }
+	};
+
+    /**
+     * Constructs from nothing.
+     */
+    protected ValueProcessor()
+    {}
+
+    /**
+     * Processes a float value.
+     *
+     * @param value		The value to be processed.
+     * @return			The processed value.
+     */
+    public abstract float process(float value);
+
+    /**
+     * Processes a double value.
+     *
+     * @param value		The value to be processed.
+     * @return			The processed value.
+     */
+    public abstract double process(double value);
+
+    /**
+     * Processes float values.
+     *
+     * @param values		The values to be processed.
+     * @return			The processed values (same array as input).
+     */
+    public abstract float[] process(float[] values);
+
+    /**
+     * Processes double values.
+     *
+     * @param values		The values to be processed.
+     * @return			The processed values (same array as input).
+     */
+    public abstract double[] process(double[] values);
+}
diff --git a/visad/data/in/ValueRanger.java b/visad/data/in/ValueRanger.java
new file mode 100644
index 0000000..51977e9
--- /dev/null
+++ b/visad/data/in/ValueRanger.java
@@ -0,0 +1,249 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+ * Provides support for ranging data values (i.e. checking to see that they
+ * lie within a valid range).
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public class ValueRanger
+    extends	ValueProcessor
+{
+    private float			floatLower = Float.NEGATIVE_INFINITY;
+    private float			floatUpper = Float.POSITIVE_INFINITY;
+    private double			doubleLower = Double.NEGATIVE_INFINITY;
+    private double			doubleUpper = Double.POSITIVE_INFINITY;
+    private static final WeakHashMap	map = new WeakHashMap();
+
+    private static final ValueRanger	trivialRanger =
+	new ValueRanger()
+	{
+	    public float process(float value)
+	    {
+		return value;
+	    }
+	    public double process(double value)
+	    {
+		return value;
+	    }
+	    public float[] process(float[] values)
+	    {
+		return values;
+	    }
+	    public double[] process(double[] values)
+	    {
+		return values;
+	    }
+	};
+
+    /**
+     * Constructs from nothing.
+     */
+    protected ValueRanger()
+    {}
+
+    /**
+     * Constructs from valid-range limits.
+     *
+     * @param lower		The lower limit of the valid range.  May be
+     *				NaN or Double.NEGATIVE_INFINITY to indicate
+     *				no limit.
+     * @param upper		The upper limit of the valid range.  May be
+     *				NaN or Double.POSITIVE_INFINITY to indicate
+     *				no limit.
+     */
+    private ValueRanger(double lower, double upper)
+    {
+	doubleLower = lower == lower ? lower : Double.NEGATIVE_INFINITY;
+	doubleUpper = upper == upper ? upper : Double.POSITIVE_INFINITY;
+	floatLower = (float)doubleLower;
+	floatUpper = (float)doubleUpper;
+    }
+
+    /**
+     * Returns an instance of this class corresponding to valid-range limits.
+     *
+     * @param lower		The lower limit of the valid range.  May be
+     *				NaN or Double.NEGATIVE_INFINITY to indicate
+     *				no limit.
+     * @param upper		The upper limit of the valid range.  May be
+     *				NaN or Double.POSITIVE_INFINITY to indicate
+     *				no limit.
+     */
+    public static ValueRanger valueRanger(double lower, double upper)
+    {
+	ValueRanger	ranger;
+	if ((lower != lower || lower == Double.NEGATIVE_INFINITY) &&
+	    (upper != upper || upper == Double.POSITIVE_INFINITY))
+	{
+	    ranger = trivialRanger;
+	}
+	else
+	{
+	    ranger = new ValueRanger(lower, upper);
+	    WeakReference	ref = (WeakReference)map.get(ranger);
+	    if (ref == null)
+	    {
+		map.put(ranger, new WeakReference(ranger));
+	    }
+	    else
+	    {
+		ValueRanger	oldRanger = (ValueRanger)ref.get();
+		if (oldRanger == null)
+		    map.put(ranger, new WeakReference(ranger));
+		else
+		    ranger = oldRanger;
+	    }
+	}
+	return ranger;
+    }
+
+    /**
+     * Returns the minimum, valid value.
+     *
+     * @return			The minimum, valid value.
+     */
+    public double getMin()
+    {
+	return doubleLower;
+    }
+
+    /**
+     * Returns the maximum, valid value.
+     *
+     * @return			The maximum, valid value.
+     */
+    public double getMax()
+    {
+	return doubleUpper;
+    }
+
+    /**
+     * Ranges a value.
+     *
+     * @param value		The value to be processed.
+     * @return			The original value if it lay within the valid
+     *				range; otherwise Float.NaN.
+     */
+    public float process(float value)
+    {
+	return
+	    value < floatLower || value > floatUpper
+		? Float.NaN
+		: value;
+    }
+
+    /**
+     * Ranges a value.
+     *
+     * @param value		The values to be processed.
+     * @return			The original value if it lay within the valid
+     *				range; otherwise Double.NaN.
+     */
+    public double process(double value)
+    {
+	return
+	    value < doubleLower || value > doubleUpper
+		? Double.NaN
+		: value;
+    }
+
+    /**
+     * Ranges values.
+     *
+     * @param values	The value to be processed.
+     * @return			The original array with values that fall outside
+     *				the valid range replaced with Float.NaN.
+     */
+    public float[] process(float[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	{
+	    double	value = values[i];
+	    if (value < floatLower || value > floatUpper)
+		values[i] = Float.NaN;
+	}
+	return values;
+    }
+
+    /**
+     * Ranges values.
+     *
+     * @param values		The values to be processed.
+     * @return			The original array with values that fall outside
+     *				the valid range replaced with Double.NaN.
+     */
+    public double[] process(double[] values)
+    {
+	for (int i = 0; i < values.length; ++i)
+	{
+	    double	value = values[i];
+	    if (value < doubleLower || value > doubleUpper)
+		values[i] = Double.NaN;
+	}
+	return values;
+    }
+
+    /**
+     * Indicates if this instance is semantically identical to another object.
+     *
+     * @param obj		The other object.
+     * @return			<code>true</code> if and only if this instance
+     *				is semantically identical to the other object.
+     */
+    public boolean equals(Object obj)
+    {
+	boolean	equals;
+	if (!getClass().isInstance(obj))
+	{
+	    equals = false;
+	}
+	else
+	{
+	    ValueRanger	that = (ValueRanger)obj;
+	    equals = this == that || (
+		doubleLower == that.doubleLower &&
+		doubleUpper == that.doubleUpper);
+	}
+	return equals;
+    }
+
+    /**
+     * Returns the hash code of this instance.
+     *
+     * @return			The hash code of this instance.
+     */
+    public int hashCode()
+    {
+	return
+	    new Double(doubleLower).hashCode() ^ 
+	    new Double(doubleUpper).hashCode();
+    }
+}
diff --git a/visad/data/in/ValueUnpacker.java b/visad/data/in/ValueUnpacker.java
new file mode 100644
index 0000000..39cd2bb
--- /dev/null
+++ b/visad/data/in/ValueUnpacker.java
@@ -0,0 +1,87 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+/**
+ * Provides support for unpacking data values (i.e. converting them from smaller
+ * types to bigger types).
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class ValueUnpacker
+    extends	ValueProcessor
+{
+    private static final ValueUnpacker	TRIVIAL_UNPACKER =
+	new ValueUnpacker()
+	{
+	    public float process(float value)
+	    {
+		return value;
+	    }
+	    public double process(double value)
+	    {
+		return value;
+	    }
+	    public float[] process(float[] values)
+	    {
+		return values;
+	    }
+	    public double[] process(double[] values)
+	    {
+		return values;
+	    }
+	};
+
+    /**
+     * Constructs from nothing.
+     */
+    protected ValueUnpacker()
+    {}
+
+    /**
+     * Returns the trivial value unpacker.  The trivial value unpacker does
+     * nothing to the value during processing and its {@link #getIncrement()}
+     * method returns NaN.
+     *
+     * @return			The trivial value unpacker.
+     */
+    public static ValueUnpacker valueUnpacker()
+    {
+	return TRIVIAL_UNPACKER;
+    }
+
+    /**
+     * Returns the minimum, potential increment between numeric values.
+     * Typically, this is the absolute magnitude of a "scale factor" attribute.
+     * If the increment is undefined or not applicable, then Double.NaN is
+     * returned.  This method should be overridden in appropriate subclasses.
+     *
+     * @return			Double.NaN.
+     */
+    public double getIncrement()
+    {
+	return Double.NaN;
+    }
+}
diff --git a/visad/data/in/ValueVetter.java b/visad/data/in/ValueVetter.java
new file mode 100644
index 0000000..77215ff
--- /dev/null
+++ b/visad/data/in/ValueVetter.java
@@ -0,0 +1,129 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.in;
+
+import java.util.Arrays;
+
+/**
+ * Provides support for verifying data values (i.e. seeing that they aren't
+ * equal to one or more special values).
+ *
+ * <P>Instances are immutable.</P>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class ValueVetter
+    extends	ValueProcessor
+{
+    private static final ValueVetter	trivialVetter =
+	new ValueVetter()
+	{
+	    public float process(float value)
+	    {
+		return value;
+	    }
+	    public double process(double value)
+	    {
+		return value;
+	    }
+	    public float[] process(float[] values)
+	    {
+		return values;
+	    }
+	    public double[] process(double[] values)
+	    {
+		return values;
+	    }
+	};
+
+    /**
+     * Constructs from nothing.
+     */
+    protected ValueVetter()
+    {}
+
+    /**
+     * Returns an instance of this class given an array of special values.
+     * If the trivial vetter can be returned, then it is.  During processing,
+     * if a value to be processed equals a special value, then it is replaced
+     * with a NaN.
+     *
+     * @param values		The special values.  May be <code>null</code>.
+     *				Elements may be NaN.  The array might not be
+     *				cloned and might be reordered.
+     */
+    public static ValueVetter valueVetter(double[] values)
+    {
+	ValueVetter	vetter;
+	if (values == null || values.length == 0)
+	{
+	    vetter = trivialVetter;
+	}
+	else
+	{
+	    int		count = 0;
+	    double	prev = Double.NaN;
+	    Arrays.sort(values);
+	    for (int i = 0; i < values.length; ++i)
+	    {
+		double	value = values[i];
+		if (value == value && !(value == prev))
+		{
+		    count++;
+		    prev = value;
+		}
+	    }
+	    if (count == 0)
+	    {
+		vetter = trivialVetter;
+	    }
+	    else
+	    {
+		if (count != values.length)
+		{
+		    double[]	vals = new double[count];
+		    count = 0;
+		    prev = Double.NaN;
+		    for (int i = 0; i < values.length; ++i)
+		    {
+			double	value = values[i];
+			if (value == value && !(value == prev))
+			{
+			    vals[count++] = value;
+			    prev = value;
+			}
+		    }
+		    values = vals;
+		}
+		if (values.length == 1)
+		    vetter = SingleValueVetter.singleValueVetter(values[0]);
+		else if (values.length == 2)
+		    vetter = DoubleValueVetter.doubleValueVetter(
+			values[0], values[1]);
+		else
+		    vetter = MultipleValueVetter.multipleValueVetter(values);
+	    }
+	}
+	return vetter;
+    }
+}
diff --git a/visad/data/in/package.html b/visad/data/in/package.html
new file mode 100644
index 0000000..5bca137
--- /dev/null
+++ b/visad/data/in/package.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+Supports the creation of form-specific, read-only, data-access packages
+that import external dataset into VisAD as VisaD data objects.
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/data/jai/JAIForm.java b/visad/data/jai/JAIForm.java
new file mode 100644
index 0000000..fa0fe5e
--- /dev/null
+++ b/visad/data/jai/JAIForm.java
@@ -0,0 +1,193 @@
+//
+// JAIForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.jai;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.URL;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.*;
+import visad.util.*;
+
+/**
+ * JAIForm is the VisAD data form for image formats supported by the Java
+ * Advanced Imaging API: BMP, GIF, FlashPix, JPEG, PNG, PNM, and TIFF.
+ */
+public class JAIForm extends Form implements FormFileInformer {
+
+  private static int num = 0;
+
+  private static final String[] suffixes = {
+    "bmp", "gif", "flashpix", "jpg", "jpeg", "jpe", "png", "pnm", "tif", "tiff"
+  };
+
+  private static boolean noJai = false;
+
+  private static ReflectedUniverse r = createReflectedUniverse();
+
+  private static ReflectedUniverse createReflectedUniverse() {
+    ReflectedUniverse r = null;
+    try {
+      r = new ReflectedUniverse();
+      r.exec("import javax.media.jai.JAI");
+      r.exec("import javax.media.jai.PlanarImage");
+    }
+    catch (VisADException exc) { noJai = true; }
+    return r;
+  }
+
+  private static BufferedImage createImage(String s, Object o) {
+    BufferedImage bi = null;
+    try {
+      r.setVar("s", s);
+      r.setVar("o", o);
+      r.exec("pi = JAI.create(s, o)");
+      bi = (BufferedImage) r.exec("pi.getAsBufferedImage()");
+    }
+    catch (VisADException exc) { }
+    return bi;
+  }
+
+  /** Constructs a new JAI file form. */
+  public JAIForm() {
+    super("JAIForm" + num++);
+  }
+
+  /** Checks if the given string is a valid filename for a JAI image file. */
+  public boolean isThisType(String name) {
+    if (noJai) return false;
+    for (int i=0; i<suffixes.length; i++) {
+      if (name.toLowerCase().endsWith(suffixes[i])) return true;
+    }
+    return false;
+  }
+
+  /** Checks if the given block is a valid header for a JAI image file. */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /** Returns the default file suffixes for the JAI image file formats. */
+  public String[] getDefaultSuffixes() {
+    String[] s = new String[suffixes.length];
+    System.arraycopy(suffixes, 0, s, 0, suffixes.length);
+    return s;
+  }
+
+  /**
+   * Saves a VisAD Data object to a JAI image format at the given location.
+   *
+   * @exception BadFormException Always thrown (method is not implemented).
+   */
+  public void save(String id, Data data, boolean replace)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    throw new BadFormException("JAIForm.save");
+  }
+
+  /**
+   * Adds data to an existing JAI image file.
+   *
+   * @exception BadFormException Always thrown (method is not implemented).
+   */
+  public void add(String id, Data data, boolean replace)
+    throws BadFormException
+  {
+    throw new BadFormException("JAIForm.add");
+  }
+
+  /**
+   * Opens an existing JAI image file from the given location.
+   *
+   * @return VisAD Data object containing JAI image data.
+   */
+  public DataImpl open(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    if (noJai) {
+      throw new BadFormException("you need to install JAI from " +
+        "http://java.sun.com/products/java-media/jai/index.html");
+    }
+    BufferedImage bi = createImage("fileload", id);
+    return open(bi);
+  }
+
+  /**
+   * Opens an existing JAI image file from the given URL.
+   *
+   * @return VisAD Data object containing JAI image data.
+   */
+  public DataImpl open(URL url)
+    throws BadFormException, IOException, VisADException
+  {
+    if (noJai) {
+      throw new BadFormException("you need to install JAI from " +
+        "http://java.sun.com/products/java-media/jai/index.html");
+    }
+    BufferedImage bi = createImage("URL", url);
+    return open(bi);
+  }
+
+  /** Converts the given image to a VisAD Data object. */
+  private DataImpl open(BufferedImage image)
+    throws BadFormException, IOException, VisADException
+  {
+    if (image == null) {
+      throw new BadFormException("JAI could not read the file as an image");
+    }
+    return DataUtility.makeField(image);
+  }
+
+  public FormNode getForms(Data data) {
+    return null;
+  }
+
+  /**
+   * Run 'java visad.data.visad.JAIForm in_file' to test read
+   * an image file supported by Java Advanced Imaging.
+   */
+  public static void main(String[] args)
+    throws VisADException, RemoteException, IOException
+  {
+    if (args == null || args.length < 1) {
+      System.out.println("To test read an image file, run:");
+      System.out.println("  java visad.data.jai.JAIForm in_file");
+      System.exit(2);
+    }
+
+    // Test read image file
+    JAIForm form = new JAIForm();
+    System.out.print("Reading " + args[0] + " ");
+    Data data = form.open(args[0]);
+    System.out.println("[done]");
+    System.out.println("MathType =\n" + data.getType().prettyString());
+    System.exit(0);
+  }
+
+}
+
diff --git a/visad/data/mcidas/AREACoordinateSystem.java b/visad/data/mcidas/AREACoordinateSystem.java
new file mode 100644
index 0000000..806cb4a
--- /dev/null
+++ b/visad/data/mcidas/AREACoordinateSystem.java
@@ -0,0 +1,865 @@
+//
+// AREACoordinateSystem.java
+//
+
+/*
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.mcidas;
+
+import edu.wisc.ssec.mcidas.AREAnav;
+import edu.wisc.ssec.mcidas.AreaFile;
+import edu.wisc.ssec.mcidas.McIDASException;
+import edu.wisc.ssec.mcidas.AreaFileException;
+
+import java.awt.geom.Rectangle2D;
+
+import visad.CoordinateSystemException;
+import visad.QuickSort;
+import visad.RealTupleType;
+import visad.Unit;
+import visad.VisADException;
+
+/**
+ * AREACoordinateSystem is the VisAD CoordinateSystem class
+ * for conversions to/from (Latitude, Longitude) and Cartesian (element,line),
+ * and with Latitude and Longitude in degrees.
+ */
+public class AREACoordinateSystem
+    extends visad.georef.MapProjection
+{
+
+  private static final long serialVersionUID = 1L;
+  protected AREAnav anav = null;
+  private int lines;
+  private int elements;
+  private int[] dirBlock;
+  private int[] navBlock;
+  private int[] auxBlock;
+  private boolean useSpline = true;
+
+  private static Unit[] coordinate_system_units =
+    {null, null};
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param af is the associated AreaFile 
+    *
+    */
+  public AREACoordinateSystem(AreaFile af) 
+                  throws VisADException, AreaFileException {
+    this(RealTupleType.LatitudeLongitudeTuple, 
+                 af.getDir(), af.getNav(), af.getAux());
+  }
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param ref	 the CoordinateSystem reference (must be equivalent
+    *                to RealTupleType.LatitudeLongitudeTuple)
+    * @param af is the associated AreaFile 
+    *
+    */
+  public AREACoordinateSystem(RealTupleType ref, AreaFile af) 
+                  throws VisADException, AreaFileException {
+    this(ref, af.getDir(), af.getNav(), af.getAux());
+  }
+
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param dir is the AREA file directory block
+    * @param nav is the AREA file navigation block
+    *
+    */
+  public AREACoordinateSystem(int[] dir, int[] nav) throws VisADException {
+      this(RealTupleType.LatitudeLongitudeTuple, dir, nav, null);
+  }
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param dir is the AREA file directory block
+    * @param nav is the AREA file navigation block
+    * @param aux is the AREA file auxillary block
+    *
+    */
+  public AREACoordinateSystem(int[] dir, int[] nav, int[] aux) 
+                                             throws VisADException {
+      this(dir, nav, aux, true);
+  }
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param dir is the AREA file directory block
+    * @param nav is the AREA file navigation block
+    * @param aux is the AREA file auxillary block
+    * @param useSpline  use a spline approximation for speed
+    *
+    */
+  public AREACoordinateSystem(int[] dir, int[] nav, int[] aux, boolean useSpline) 
+                                             throws VisADException {
+      this(RealTupleType.LatitudeLongitudeTuple, dir, nav, aux, useSpline);
+  }
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param reference the CoordinateSystem reference (must be equivalent
+    *                  to RealTupleType.LatitudeLongitudeTuple)
+    * @param dir is the AREA file directory block
+    * @param nav is the AREA file navigation block
+    *
+    */
+  public AREACoordinateSystem(RealTupleType reference, int[] dir,
+                                 int[] nav) throws VisADException {
+
+      this(reference, dir, nav, null);
+  }
+
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param reference the CoordinateSystem reference (must be equivalent
+    *                  to RealTupleType.LatitudeLongitudeTuple)
+    * @param dir is the AREA file directory block
+    * @param nav is the AREA file navigation block
+    * @param aux is the AREA file auxillary block
+    *
+    */
+
+  public AREACoordinateSystem(RealTupleType reference, int[] dir,
+                                 int[] nav, int[] aux) throws VisADException {
+      this(reference, dir, nav, aux, true);
+  }
+
+  /** create a AREA coordinate system with nothing initialized.
+    * This allows for derived classes to do a lazy initialization
+    * of the coordinate system. To do this they must overwrite getAreaNav
+    * in order to create the nav.
+    */
+   protected AREACoordinateSystem() throws VisADException {
+       super(RealTupleType.LatitudeLongitudeTuple,coordinate_system_units);
+   }
+
+
+  /** create a AREA coordinate system from the Area file's
+    * directory and navigation blocks.
+    *
+    * This routine uses a flipped Y axis (first line of
+    * the image file is number 0)
+    *
+    * @param reference the CoordinateSystem reference (must be equivalent
+    *                  to RealTupleType.LatitudeLongitudeTuple)
+    * @param dir is the AREA file directory block
+    * @param nav is the AREA file navigation block
+    * @param aux is the AREA file auxillary block
+    * @param useSpline  use a spline approximation for speed
+    *
+    */
+  public AREACoordinateSystem(RealTupleType reference, int[] dir,
+                                 int[] nav, int[] aux,
+                                 boolean useSpline) throws VisADException {
+
+
+    super(reference, coordinate_system_units);
+    init(dir,nav,aux,useSpline);
+  }
+
+
+  /**
+   * This is used by the methods that do the work and can be overwritten
+   * by a derived class to do a lazy instantiation of the coordinate system.
+   * 
+   * @return The area nav 
+   */
+  protected AREAnav getAreaNav() {
+      return anav;
+  }
+
+  /**
+   * Create and initialize the areanav.
+   * This used to be in the constructor is snow its own method to enable 
+   * derived classes to lazily create the areanav
+   *
+   * @param dir is the AREA file directory block
+   * @param nav is the AREA file navigation block
+   * @param aux is the AREA file auxillary block
+   * @param useSpline  use a spline approximation for speed
+   */
+  
+  protected void init(int[]dir, int[] nav, int[] aux, boolean useSpline) throws VisADException {
+    try {
+        anav = AREAnav.makeAreaNav(nav, aux);
+    } catch (McIDASException excp) {
+      throw new CoordinateSystemException(
+          "AREACoordinateSystem: problem creating navigation" + excp);
+    }
+    dirBlock = dir;
+    navBlock = nav;
+    auxBlock = aux;
+    this.useSpline = !useSpline 
+                       ? false  // user overrode
+                       : anav.canApproximateWithSpline(); // let nav decide
+    anav.setImageStart(dir[5], dir[6]);
+    anav.setRes(dir[11], dir[12]);
+    anav.setStart(0,0);
+    anav.setMag(1,1);
+    lines = dir[8];
+    elements = dir[9];
+    // the following is done because VisAD's (0,0) coordinate is in
+    // the lower-left corner; whereas, AREA files are in the upper-left
+
+    anav.setFlipLineCoordinates(dir[8]-1); // invert Y axis coordinates
+  }
+
+
+  /** Get the directory block used to initialize this AREACoordinateSystem */
+  public int[] getDirBlock() {
+    getAreaNav();
+    return dirBlock;
+  }
+
+  /** Get the navigation block used to initialize this AREACoordinateSystem */
+  public int[] getNavBlock() {
+    getAreaNav();
+    return navBlock;
+  }
+
+  /** Get the navigation block used to initialize this AREACoordinateSystem */
+  public int[] getAuxBlock() {
+    getAreaNav();
+    return auxBlock;
+  }
+
+  /** get the subpoint if available
+  */
+  public double[] getSubpoint() {
+    return getAreaNav().getSubpoint();
+  }
+
+  /** 
+   * Get whether we are using a spline or not 
+   */
+  public boolean getUseSpline() {
+    return useSpline;
+  }
+
+  /** convert from image element,line to latitude,longitude
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("AREACoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    AREAnav  anav = getAreaNav();
+
+    if (anav == null) {
+      throw new CoordinateSystemException("AREA O & A data not availble");
+    }
+
+    int[] nums = new int[2];
+    double[] mins = new double[2];
+    double[] maxs = new double[2];
+    double[][] newval = makeSpline(tuples, mins, maxs, nums);
+    if (newval != null) {
+// System.out.println("new 1 " + tuples[0].length + " " + newval[0].length);
+      double[][] newtrans = anav.toLatLon(newval);
+
+      int len = tuples[0].length;
+      double[][] misstrans = new double[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      double[][] val = applySpline(tuples, mins, maxs, nums, newtrans,
+                                   misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        double[][] newmiss = anav.toLatLon(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+      return val;
+    }
+    else {
+      return anav.toLatLon(tuples);
+    }
+  }
+
+  /** convert from latitude,longitude to image element,line
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("AREACoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+
+    AREAnav  anav = getAreaNav();
+    if (anav == null) {
+      throw new CoordinateSystemException("AREA O & A data not availble");
+    }
+
+    int[] nums = new int[2];
+    double[] mins = new double[2];
+    double[] maxs = new double[2];
+    double[][] newval = makeSpline(tuples, mins, maxs, nums);
+    if (newval != null) {
+// System.out.println("new 2 " + tuples[0].length + " " + newval[0].length);
+      double[][] newtrans = anav.toLinEle(newval);
+
+      int len = tuples[0].length;
+      double[][] misstrans = new double[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      double[][] val = applySpline(tuples, mins, maxs, nums, newtrans,
+                                   misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        double[][] newmiss = anav.toLinEle(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+      return val;
+    }
+    else {
+      return anav.toLinEle(tuples);
+    }
+
+  }
+
+  /** convert from image element,line to latitude,longitude
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public float[][] toReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("AREACoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    AREAnav  anav = getAreaNav();
+    if (anav == null) {
+      throw new CoordinateSystemException("AREA O & A data not availble");
+    }
+
+    //double[][] val = Set.floatToDouble(tuples);
+    float[][] val = tuples;
+
+    int[] nums = new int[2];
+    float[] mins = new float[2];
+    float[] maxs = new float[2];
+    float[][] newval = makeSpline(val, mins, maxs, nums);
+    if (newval != null) {
+// System.out.println("new 3");
+      float[][] newtrans = anav.toLatLon(newval);
+
+      int len = tuples[0].length;
+      float[][] misstrans = new float[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      val = applySpline(val, mins, maxs, nums, newtrans,
+                        misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        float[][] newmiss = anav.toLatLon(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+    }
+    else {
+      val = anav.toLatLon(val);
+    }
+    //return Set.doubleToFloat(val);
+    return val;
+  }
+
+  /** convert from latitude,longitude to image element,line
+    *
+    * @param tuples contains the element,line pairs to convert
+    *
+    */
+  public float[][] fromReference(float[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("AREACoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+
+
+    AREAnav  anav = getAreaNav();
+    if (anav == null) {
+      throw new CoordinateSystemException("AREA O & A data not availble");
+    }
+
+    //double[][] val = Set.floatToDouble(tuples);
+    float[][] val = tuples;
+
+    int[] nums = new int[2];
+    float[] mins = new float[2];
+    float[] maxs = new float[2];
+    float[][] newval = makeSpline(val, mins, maxs, nums);
+    if (newval != null) {
+// System.out.println("new 4");
+      float[][] newtrans = anav.toLinEle(newval);
+
+      int len = tuples[0].length;
+      float[][] misstrans = new float[2][len];
+      int[][] miss_to_trans = new int[1][len];
+      val = applySpline(val, mins, maxs, nums, newtrans,
+                        misstrans, miss_to_trans);
+      if (miss_to_trans[0] != null) {
+        float[][] newmiss = anav.toLinEle(misstrans);
+        for (int i=0; i<miss_to_trans[0].length; i++) {
+          val[0][miss_to_trans[0][i]] = newmiss[0][i];
+          val[1][miss_to_trans[0][i]] = newmiss[1][i];
+        }
+      }
+    }
+    else {
+      val = anav.toLinEle(val);
+    }
+    //return Set.doubleToFloat(val);
+    return val;
+
+  }
+
+  // return reduced array for approximatin by splines
+  private double[][] makeSpline(double[][] val, double[] mins,
+                                double[] maxs, int[] nums)
+         throws VisADException {
+//     System.out.println("using spline = " + useSpline);
+    if (!useSpline) return null;
+    int len = val[0].length;
+    if (len < 1000) return null;
+    double reduction = 10.0;
+    if (len < 10000) reduction = 2.0;
+    else if (len < 100000) reduction = 5.0;
+
+    // compute ranges
+    mins[0] = Double.MAX_VALUE;
+    maxs[0] = -Double.MAX_VALUE;
+    mins[1] = Double.MAX_VALUE;
+    maxs[1] = -Double.MAX_VALUE;
+    for (int i=0; i<len; i++) {
+      if (val[0][i] == val[0][i]) {
+        if (val[0][i] < mins[0]) mins[0] = val[0][i];
+        if (val[0][i] > maxs[0]) maxs[0] = val[0][i];
+      }
+      if (val[1][i] == val[1][i]) {
+        if (val[1][i] < mins[1]) mins[1] = val[1][i];
+        if (val[1][i] > maxs[1]) maxs[1] = val[1][i];
+      }
+    }
+
+    // compute typical spacing btween points
+    float[] norm = new float[len-1];
+    int k = 0;
+    // WLH 2 March 2000
+    // for (int i=0; k<3 && i<len; i++) {
+    for (int i=0; i<len-1; i++) {
+      float n = (float) Math.sqrt(
+        (val[0][i] - val[0][i+1]) * (val[0][i] - val[0][i+1]) +
+        (val[1][i] - val[1][i+1]) * (val[1][i] - val[1][i+1]) );
+      if (n == n) {
+        norm[k++] = n;
+      }
+    }
+    // WLH 2 March 2000
+    if (k < 3) return null;
+    float[] nnorm = new float[k];
+    System.arraycopy(norm, 0, nnorm, 0, k);
+
+    QuickSort.sort(nnorm);
+    double spacing = reduction * nnorm[k / 4];
+
+    // compute size of spline array
+    nums[0] = (int) ((maxs[0] - mins[0]) / spacing) + 1;
+    nums[1] = (int) ((maxs[1] - mins[1]) / spacing) + 1;
+
+    // test if it will be too coarse
+    if (nums[0] < 20 || nums[1] < 20) return null;
+    // test if its worth it
+    if ((nums[0] * nums[1]) > (len / 4)) return null;
+    // test to see if the product is greater than an int
+    if ((nums[0] * nums[1]) < 0) return null;
+
+    double spacing0 = (maxs[0] - mins[0]) / (nums[0] - 1);
+    double spacing1 = (maxs[1] - mins[1]) / (nums[1] - 1);
+
+    double[][] newval = new double[2][nums[0] * nums[1]];
+    k = 0;
+    for (int i=0; i<nums[0]; i++) {
+      for (int j=0; j<nums[1]; j++) {
+        newval[0][k] = mins[0] + i * spacing0;
+        newval[1][k] = mins[1] + j * spacing1;
+        k++;
+      }
+    }
+    return newval;
+  }
+
+  // use splines to approximate transform
+  private double[][] applySpline(double[][] val, double[] mins,
+                     double[] maxs, int[] nums, double[][] newtrans,
+                     double[][] misstrans, int[][] miss_to_trans)
+         throws VisADException {
+    int n0 = nums[0];
+    int n1 = nums[1];
+    double spacing0 = (maxs[0] - mins[0]) / (n0 - 1);
+    double spacing1 = (maxs[1] - mins[1]) / (n1 - 1);
+    int len = val[0].length;
+    double[][] trans = new double[2][len];
+
+    boolean[] lon_flags = new boolean[n0 * n1];
+    double[] min_trans = {Double.MAX_VALUE, Double.MAX_VALUE};
+    double[] max_trans = {-Double.MAX_VALUE, -Double.MAX_VALUE};
+    for (int i=0; i<n0*n1; i++) {
+      if (newtrans[0][i] == newtrans[0][i]) {
+        if (newtrans[0][i] < min_trans[0]) min_trans[0] = newtrans[0][i];
+        if (newtrans[0][i] > max_trans[0]) max_trans[0] = newtrans[0][i];
+      }
+      if (newtrans[1][i] == newtrans[1][i]) {
+        if (newtrans[1][i] < min_trans[1]) min_trans[1] = newtrans[1][i];
+        if (newtrans[1][i] > max_trans[1]) max_trans[1] = newtrans[1][i];
+      }
+      lon_flags[i] = false;
+    }
+    double minn0n1 = Math.min(n0, n1);
+    double mean0 = (max_trans[0] - min_trans[0]) / minn0n1;
+    double mean1 = (max_trans[1] - min_trans[1]) / minn0n1;
+
+    for (int i0=0; i0<n0-1; i0++) {
+      for (int i1=0; i1<n1-1; i1++) {
+        int ii = i1 + i0 * n1;
+        if (Math.abs(newtrans[0][ii + n1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + 1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + 1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + n1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[1][ii + n1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + 1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + 1]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + n1]) > 3.0 * mean1) {
+          lon_flags[ii] = true;
+        }
+      }
+    }
+
+    int nmiss = 0;
+
+    for (int i=0; i<len; i++) {
+      double a0 = (val[0][i] - mins[0]) / spacing0;
+      int i0 = (int) a0;
+      if (i0 < 0) i0 = 0;
+      if (i0 > (n0 - 2)) i0 = n0 - 2;
+      a0 -= i0;
+      double a1 = (val[1][i] - mins[1]) / spacing1;
+      int i1 = (int) a1;
+      if (i1 < 0) i1 = 0;
+      if (i1 > (n1 - 2)) i1 = n1 - 2;
+      a1 -= i1;
+      int ii = i1 + i0 * n1;
+      if (lon_flags[ii]) {
+        misstrans[0][nmiss] = val[0][i];
+        misstrans[1][nmiss] = val[1][i];
+        miss_to_trans[0][nmiss] = i;
+        nmiss++;
+      }
+      else {
+        trans[0][i] =
+          (1.0 - a0) * ((1.0 - a1) * newtrans[0][ii] + a1 * newtrans[0][ii+1]) +
+          a0 * ((1.0 - a1) * newtrans[0][ii+n1] + a1 * newtrans[0][ii+n1+1]);
+        trans[1][i] =
+          (1.0 - a0) * ((1.0 - a1) * newtrans[1][ii] + a1 * newtrans[1][ii+1]) +
+          a0 * ((1.0 - a1) * newtrans[1][ii+n1] + a1 * newtrans[1][ii+n1+1]);
+
+        if (trans[0][i] != trans[0][i] || trans[1][i] != trans[1][i]) {
+          misstrans[0][nmiss] = val[0][i];
+          misstrans[1][nmiss] = val[1][i];
+          miss_to_trans[0][nmiss] = i;
+          nmiss++;
+        }
+      }
+    }
+// System.out.println("len = " + len + " nmiss = " + nmiss);
+    if (nmiss == 0) {
+      miss_to_trans[0] = null;
+      misstrans[0] = null;
+      misstrans[1] = null;
+    }
+    else {
+      double[] xmisstrans = new double[nmiss];
+      System.arraycopy(misstrans[0], 0, xmisstrans, 0, nmiss);
+      misstrans[0] = xmisstrans;
+      xmisstrans = new double[nmiss];
+      System.arraycopy(misstrans[1], 0, xmisstrans, 0, nmiss);
+      misstrans[1] = xmisstrans;
+      int[] xmiss_to_trans = new int[nmiss];
+      System.arraycopy(miss_to_trans[0], 0, xmiss_to_trans, 0, nmiss);
+      miss_to_trans[0] = xmiss_to_trans;
+    }
+    return trans;
+  }
+
+  // return reduced array for approximatin by splines
+  private float[][] makeSpline(float[][] val, float[] mins,
+                                float[] maxs, int[] nums)
+         throws VisADException {
+    int len = val[0].length;
+    if (len < 1000) return null;
+    float reduction = 10.0f;
+    if (len < 10000) reduction = 2.0f;
+    else if (len < 100000) reduction = 5.0f;
+
+    // compute ranges
+    mins[0] = Float.MAX_VALUE;
+    maxs[0] = -Float.MAX_VALUE;
+    mins[1] = Float.MAX_VALUE;
+    maxs[1] = -Float.MAX_VALUE;
+    for (int i=0; i<len; i++) {
+      if (val[0][i] == val[0][i]) {
+        if (val[0][i] < mins[0]) mins[0] = val[0][i];
+        if (val[0][i] > maxs[0]) maxs[0] = val[0][i];
+      }
+      if (val[1][i] == val[1][i]) {
+        if (val[1][i] < mins[1]) mins[1] = val[1][i];
+        if (val[1][i] > maxs[1]) maxs[1] = val[1][i];
+      }
+    }
+
+    // compute typical spacing between points
+    float[] norm = new float[len-1];
+    int k = 0;
+    // WLH 2 March 2000
+    // for (int i=0; k<3 && i<len; i++) {
+    for (int i=0; i<len-1; i++) {
+      float n = (float) Math.sqrt(
+        (val[0][i] - val[0][i+1]) * (val[0][i] - val[0][i+1]) +
+        (val[1][i] - val[1][i+1]) * (val[1][i] - val[1][i+1]) );
+      if (n == n) {
+        norm[k++] = n;
+      }
+    }
+    // WLH 2 March 2000
+    if (k < 3) return null;
+    float[] nnorm = new float[k];
+    System.arraycopy(norm, 0, nnorm, 0, k);
+
+    QuickSort.sort(nnorm);
+    float spacing = reduction * nnorm[k / 4];
+
+    // compute size of spline array
+    nums[0] = (int) ((maxs[0] - mins[0]) / spacing) + 1;
+    nums[1] = (int) ((maxs[1] - mins[1]) / spacing) + 1;
+
+    // test if it will be too coarse
+    if (nums[0] < 20 || nums[1] < 20) return null;
+    // test if its worth it
+    if ((nums[0] * nums[1]) > (len / 4)) return null;
+    // test to see if the product is greater than an int
+    if ((nums[0] * nums[1]) < 0) return null;
+
+    float spacing0 = (maxs[0] - mins[0]) / (nums[0] - 1);
+    float spacing1 = (maxs[1] - mins[1]) / (nums[1] - 1);
+
+    float[][] newval = new float[2][nums[0] * nums[1]];
+    k = 0;
+    for (int i=0; i<nums[0]; i++) {
+      for (int j=0; j<nums[1]; j++) {
+        newval[0][k] = mins[0] + i * spacing0;
+        newval[1][k] = mins[1] + j * spacing1;
+        k++;
+      }
+    }
+    return newval;
+  }
+
+  // use splines to approximate transform
+  private float[][] applySpline(float[][] val, float[] mins,
+                     float[] maxs, int[] nums, float[][] newtrans,
+                     float[][] misstrans, int[][] miss_to_trans)
+         throws VisADException {
+    int n0 = nums[0];
+    int n1 = nums[1];
+    float spacing0 = (maxs[0] - mins[0]) / (n0 - 1);
+    float spacing1 = (maxs[1] - mins[1]) / (n1 - 1);
+    int len = val[0].length;
+    float[][] trans = new float[2][len];
+
+    boolean[] lon_flags = new boolean[n0 * n1];
+    float[] min_trans = {Float.MAX_VALUE, Float.MAX_VALUE};
+    float[] max_trans = {-Float.MAX_VALUE, -Float.MAX_VALUE};
+    for (int i=0; i<n0*n1; i++) {
+      if (newtrans[0][i] == newtrans[0][i]) {
+        if (newtrans[0][i] < min_trans[0]) min_trans[0] = newtrans[0][i];
+        if (newtrans[0][i] > max_trans[0]) max_trans[0] = newtrans[0][i];
+      }
+      if (newtrans[1][i] == newtrans[1][i]) {
+        if (newtrans[1][i] < min_trans[1]) min_trans[1] = newtrans[1][i];
+        if (newtrans[1][i] > max_trans[1]) max_trans[1] = newtrans[1][i];
+      }
+      lon_flags[i] = false;
+    }
+    float minn0n1 = Math.min(n0, n1);
+    float mean0 = (max_trans[0] - min_trans[0]) / minn0n1;
+    float mean1 = (max_trans[1] - min_trans[1]) / minn0n1;
+
+    for (int i0=0; i0<n0-1; i0++) {
+      for (int i1=0; i1<n1-1; i1++) {
+        int ii = i1 + i0 * n1;
+        if (Math.abs(newtrans[0][ii + n1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + 1] - newtrans[0][ii]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + 1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[0][ii + n1 + 1] - newtrans[0][ii + n1]) > 3.0 * mean0 ||
+            Math.abs(newtrans[1][ii + n1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + 1] - newtrans[1][ii]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + 1]) > 3.0 * mean1 ||
+            Math.abs(newtrans[1][ii + n1 + 1] - newtrans[1][ii + n1]) > 3.0 * mean1) {
+          lon_flags[ii] = true;
+        }
+      }
+    }
+
+    int nmiss = 0;
+
+    for (int i=0; i<len; i++) {
+      float a0 = (val[0][i] - mins[0]) / spacing0;
+      int i0 = (int) a0;
+      if (i0 < 0) i0 = 0;
+      if (i0 > (n0 - 2)) i0 = n0 - 2;
+      a0 -= i0;
+      float a1 = (val[1][i] - mins[1]) / spacing1;
+      int i1 = (int) a1;
+      if (i1 < 0) i1 = 0;
+      if (i1 > (n1 - 2)) i1 = n1 - 2;
+      a1 -= i1;
+      int ii = i1 + i0 * n1;
+      if (lon_flags[ii]) {
+        misstrans[0][nmiss] = val[0][i];
+        misstrans[1][nmiss] = val[1][i];
+        miss_to_trans[0][nmiss] = i;
+        nmiss++;
+      }
+      else {
+        trans[0][i] =
+          (1.0f - a0) * ((1.0f - a1) * newtrans[0][ii] + a1 * newtrans[0][ii+1]) +
+          a0 * ((1.0f - a1) * newtrans[0][ii+n1] + a1 * newtrans[0][ii+n1+1]);
+        trans[1][i] =
+          (1.0f - a0) * ((1.0f - a1) * newtrans[1][ii] + a1 * newtrans[1][ii+1]) +
+          a0 * ((1.0f - a1) * newtrans[1][ii+n1] + a1 * newtrans[1][ii+n1+1]);
+
+        if (trans[0][i] != trans[0][i] || trans[1][i] != trans[1][i]) {
+          misstrans[0][nmiss] = val[0][i];
+          misstrans[1][nmiss] = val[1][i];
+          miss_to_trans[0][nmiss] = i;
+          nmiss++;
+        }
+      }
+    }
+// System.out.println("len = " + len + " nmiss = " + nmiss);
+    if (nmiss == 0) {
+      miss_to_trans[0] = null;
+      misstrans[0] = null;
+      misstrans[1] = null;
+    }
+    else {
+      float[] xmisstrans = new float[nmiss];
+      System.arraycopy(misstrans[0], 0, xmisstrans, 0, nmiss);
+      misstrans[0] = xmisstrans;
+      xmisstrans = new float[nmiss];
+      System.arraycopy(misstrans[1], 0, xmisstrans, 0, nmiss);
+      misstrans[1] = xmisstrans;
+      int[] xmiss_to_trans = new int[nmiss];
+      System.arraycopy(miss_to_trans[0], 0, xmiss_to_trans, 0, nmiss);
+      miss_to_trans[0] = xmiss_to_trans;
+    }
+    return trans;
+  }
+
+  /**
+   * Get the bounds for this image
+   */
+  public Rectangle2D getDefaultMapArea() 
+  { 
+      return new Rectangle2D.Float(0, 0, elements, lines);
+  }
+
+  /**
+   * Determines whether or not the <code>Object</code> in question is
+   * the same as this <code>AREACoordinateSystem</code>.  The specified
+   * <code>Object</code> is equal to this <CODE>AREACoordinateSystem</CODE>
+   * if it is an instance of <CODE>AREACoordinateSystem</CODE> and it has
+   * the same navigation module and default map area as this one.
+   *
+   * @param obj the Object in question
+   */
+  public boolean equals(Object obj)
+  {
+    if (!(obj instanceof AREACoordinateSystem))
+        return false;
+    AREACoordinateSystem that = (AREACoordinateSystem) obj;
+    AREAnav  anav = getAreaNav();
+    return this == that ||
+          (anav.equals(that.getAreaNav()) && 
+           this.lines == that.lines &&
+           this.elements == that.elements);
+  }
+
+  /**
+   * Return a String which tells some info about this navigation
+   * @return wordy String
+   */
+  public String toString() {
+     if(anav==null) {
+        return "Image  Projection";
+     }
+     return "Image (" + anav.toString() + ") Projection";
+  }
+}
diff --git a/visad/data/mcidas/AddeTextAdapter.java b/visad/data/mcidas/AddeTextAdapter.java
new file mode 100644
index 0000000..e86e776
--- /dev/null
+++ b/visad/data/mcidas/AddeTextAdapter.java
@@ -0,0 +1,85 @@
+//
+// AddeTextAdapter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.mcidas;
+
+import edu.wisc.ssec.mcidas.adde.AddeTextReader;
+import visad.Text;
+import visad.TextType;
+import visad.VisADException;
+
+/** 
+ *  Adapts text read from an ADDE server into a VisAD Text object
+ */
+
+public class AddeTextAdapter {
+
+  private Text data = null;
+  private static int nextID = 0;
+
+  /** Create a VisAD Text object from an adde request
+    * @param textSource  ADDE URL of request
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public AddeTextAdapter(String textSource) throws VisADException {
+      AddeTextReader atr = new AddeTextReader(textSource);
+      String name = "AddeText_"+nextID++;
+      TextType type = TextType.getTextType(name);
+      data = new Text(type, atr.getText());
+  }
+
+  /**
+   * Return the Text object representing the request. 
+   * @return request as Text
+   */
+  public Text getData() {
+    return data;
+  }
+
+  /**
+   * Return the Text object representing the request with HTML formatting. 
+   * @return request as Text bracketed with <PRE> tags
+   */
+  public Text getDataAsHTML() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("<html>");
+    buf.append("\n");
+    buf.append("<pre>");
+    buf.append("\n");
+    buf.append(data.getValue());
+    buf.append("\n");
+    buf.append("</pre>");
+    buf.append("\n");
+    buf.append("</html>");
+    Text newText = null;
+    try {
+       newText = new Text((TextType) data.getType(), buf.toString());
+    }
+    catch (VisADException ve) {}
+    return newText;
+  }
+}
diff --git a/visad/data/mcidas/AreaAdapter.java b/visad/data/mcidas/AreaAdapter.java
new file mode 100644
index 0000000..c6c123f
--- /dev/null
+++ b/visad/data/mcidas/AreaAdapter.java
@@ -0,0 +1,742 @@
+//
+// AreaAdapter.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.mcidas;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+
+import visad.CoordinateSystem;
+import visad.DateTime;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.Linear2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.Set;
+import visad.Unit;
+import visad.UnitException;
+import visad.VisADException;
+import visad.meteorology.NavigatedImage;
+import visad.meteorology.SingleBandedImage;
+import visad.meteorology.SingleBandedImageImpl;
+import edu.wisc.ssec.mcidas.AreaDirectory;
+import edu.wisc.ssec.mcidas.AreaFile;
+import edu.wisc.ssec.mcidas.AreaFileFactory;
+import edu.wisc.ssec.mcidas.Calibrator;
+import edu.wisc.ssec.mcidas.McIDASException;
+
+/** this is an adapter for McIDAS AREA images */
+
+public class AreaAdapter {
+  
+  private FlatField field = null;
+  private AREACoordinateSystem cs;
+  private AreaDirectory areaDirectory;
+
+  /** Create a VisAD FlatField from a local McIDAS AREA file or a URL.
+    * @param imageSource name of local file or a URL to locate file.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public AreaAdapter(String imageSource) throws IOException, VisADException {
+    this(
+        imageSource, 
+        0, 0, 
+        0, 0, 
+        0, 0, 
+        Calibrator.CAL_NONE, 0, true
+    );
+  }
+
+  /** Create a VisAD FlatField from a local McIDAS AREA file or a URL.
+   * @param imageSource name of local file or a URL to locate file.
+   * @param cal type of calibration to perform on retrieved data; ignored if
+   * 'unit' is specified in <code>imageSource</code>.
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public AreaAdapter(String imageSource, int cal) throws IOException, VisADException {
+    this(
+        imageSource, 
+        0, 0, 
+        0, 0, 
+        0, 0, 
+        cal, 0, true
+    );
+  }
+  
+  /** Create a VisAD FlatField from a local McIDAS AREA file or a URL.
+    * @param imageSource name of local file or a URL to locate file.
+    * @param pack      pack data if possible.  If calibration is BRIT, 
+    *                  images are packed into bytes
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public AreaAdapter(String imageSource, boolean pack) 
+      throws IOException, VisADException {
+    this(
+        imageSource, 
+        0, 0, 
+        0, 0, 
+        0, 0, 
+        Calibrator.CAL_NONE, 0, pack
+    );
+  }
+
+  /** Create a VisAD FlatField from a local McIDAS AREA file using
+    * the subsecting information
+    * @param imageSource name of local file or a URL to locate file.
+    * @param startLine starting line from the file (AREA coordinates)
+    * @param startEle starting element from the file (AREA coordinates)
+    * @param numLines number of lines to read
+    * @param numEles number of elements to read
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public AreaAdapter(String imageSource, 
+                     int startLine, 
+                     int startEle, 
+                     int numLines, 
+                     int numEles ) throws IOException, VisADException {
+    this(
+        imageSource, 
+        startLine, startEle, 
+        numLines, numEles, 
+        0, 0, 
+        Calibrator.CAL_NONE, 0, true);
+  }
+
+  /** Create a VisAD FlatField from a local McIDAS AREA subsected
+    * according to the parameters
+    * @param imageSource name of local file or a URL to locate file.
+    * @param startLine starting line from the file (AREA coordinates)
+    * @param startEle starting element from the file (AREA coordinates)
+    * @param numLines number of lines to read
+    * @param numEles number of elements to read
+    * @param band band number to get
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public AreaAdapter(String imageSource, 
+                     int startLine, 
+                     int startEle, 
+                     int numLines, 
+                     int numEles, 
+                     int band ) throws IOException, VisADException {
+    this(
+        imageSource, 
+        startLine, startEle, 
+        numLines, numEles, 
+        0, 0, 
+        Calibrator.CAL_NONE, band, true
+    );
+  } 
+  
+  /** Create a VisAD FlatField from a local McIDAS AREA subsected
+   * according to the parameters
+   * @param imageSource name of local file or a URL to locate file.
+   * @param startLine starting line from the file (AREA coordinates)
+   * @param startEle  starting element from the file (AREA coordinates)
+   * @param numLines  number of lines to read
+   * @param numEles   number of elements to read
+   * @param band      band number to get
+   * @param pack      pack data if possible.  If calibration is BRIT, 
+   *                  images are packed into bytes
+   *                      
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public AreaAdapter(String imageSource, 
+      int startLine, 
+      int startEle, 
+      int numLines, 
+      int numEles,
+      int band,
+      boolean pack) throws IOException, VisADException {
+    this(
+        imageSource, 
+        startLine, startEle, 
+        numLines, numEles, 
+        0, 0, 
+        Calibrator.CAL_NONE, band, pack
+    );
+  }
+ 
+  /** Create a VisAD FlatField from a local McIDAS AREA subsected
+   * according to the parameters
+   * @param imageSource name of local file or a URL to locate file.
+   * @param startLine starting line from the file (AREA coordinates)
+   * @param startEle starting element from the file (AREA coordinates)
+   * @param numLines number of lines to read
+   * @param numEles number of elements to read
+   * @param band band number to get
+   * @param lineMag magnification for lines
+   * @param eleMag magnification for elements
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public AreaAdapter(String imageSource, 
+      int startLine, 
+      int startEle, 
+      int numLines, 
+      int numEles,
+      int lineMag,
+      int eleMag,
+      int band) throws IOException, VisADException {
+    
+    this(
+        imageSource, 
+        startLine, startEle, 
+        numLines, numEles, 
+        lineMag, eleMag, 
+        Calibrator.CAL_NONE, band, true
+    );
+  }
+
+  /** Create a VisAD FlatField from a local McIDAS AREA subsected
+   * according to the parameters
+   * @param imageSource name of local file or a URL to locate file.
+   * @param startLine starting line from the file (AREA coordinates)
+   * @param startEle starting element from the file (AREA coordinates)
+   * @param numLines number of lines to read
+   * @param numEles number of elements to read
+   * @param lineMag magnification for lines
+   * @param eleMag magnification for elements
+   * @param cal type of calibration to perform on retrieved data; ignored if
+   * 'unit' is specified in <code>imageSource</code>.
+   * @param band band number to get
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public AreaAdapter(String imageSource, 
+      int startLine, 
+      int startEle, 
+      int numLines, 
+      int numEles,
+      int lineMag,
+      int eleMag,
+      int cal,
+      int band) throws IOException, VisADException {
+    
+    this(
+        imageSource, 
+        startLine, startEle, 
+        numLines, numEles, 
+        lineMag, eleMag, 
+        cal, band, true
+    );
+  }
+  
+  /** Create a VisAD FlatField from a local McIDAS AREA subsected
+   * according to the parameters
+   * @param imageSource name of local file or a URL to locate file.
+   * @param startLine starting line from the file (AREA coordinates)
+   * @param startEle  starting element from the file (AREA coordinates)
+   * @param numLines  number of lines to read
+   * @param numEles   number of elements to read
+   * @param band      band number to get
+   * @param lineMag magnification for lines
+   * @param eleMag magnification for elements
+   * @param pack      pack data if possible.  If calibration is BRIT, 
+   *                  images are packed into bytes
+   *                      
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public AreaAdapter(String imageSource, 
+                     int startLine, 
+                     int startEle, 
+                     int numLines, 
+                     int numEles,
+                     int lineMag,
+                     int eleMag,
+                     int band,
+                     boolean pack) throws IOException, VisADException {
+    this(
+        imageSource, 
+        startLine, startEle, 
+        numLines, numEles,
+        lineMag, eleMag, 
+        Calibrator.CAL_NONE, band, pack
+    );
+  }
+  
+  private AreaFile af;
+
+  /** Get the instance of the AreaFile used herein.  Do NOT attempt
+    * to then use the AreaFile.getData() method!!  Use this AreaAdapter's 
+    * getData() method to fetch the FlatField containing the data values.
+    *
+    * @return the instance of the AreaFile
+    */
+  public AreaFile getAreaFile() {
+       return af;
+   }
+
+
+  /** Create a VisAD FlatField from a local McIDAS AREA subsected
+   * according to the parameters
+   * @param imageSource name of local file or a URL to locate file.
+   * @param startLine starting line from the file (AREA coordinates)
+   * @param startEle  starting element from the file (AREA coordinates)
+   * @param numLines  number of lines to read
+   * @param numEles   number of elements to read
+   * @param lineMag magnification for lines
+   * @param eleMag magnification for elements
+   * @param cal type of calibration to perform on retrieved data; ignored if
+   * 'unit' is specified in <code>imageSource</code>.
+   * @param band      band number to get
+   * @param pack      pack data if possible.  If calibration is BRIT, 
+   *                  images are packed into bytes
+   *                      
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public AreaAdapter(String imageSource, 
+                     int startLine, 
+                     int startEle, 
+                     int numLines, 
+                     int numEles,
+                     int lineMag,
+                     int eleMag,
+                     int cal,
+                     int band,
+                     boolean pack) throws IOException, VisADException {    
+
+    try {
+        af = AreaFileFactory.getAreaFileInstance(imageSource);
+      // cal type not set in the imageSource URL
+      if (af.getCalType() == Calibrator.CAL_NONE) {
+        af.setCalType(cal);
+      }
+      
+      AreaDirectory dir = af.getAreaDirectory();
+      
+      // indicates subsetting using the parameters
+      boolean paramSubset = numLines != 0 && numEles != 0 && band != 0;
+      
+      // subsetted using the URL in imageSource, ignore params
+      if (af.isSubsetted()) {
+        buildFlatField(
+            af,
+            0,
+            0,
+            dir.getLines(),
+            dir.getElements(),
+            dir.getBands()[0],
+            pack
+        );
+      
+      // subsetted in parameters, so subset manually
+      } else if (!af.isSubsetted() && paramSubset){
+        af = AreaFileFactory.getAreaFileInstance(
+                imageSource, 
+                startLine, 
+                numLines, 
+                lineMag, 
+                startEle, 
+                numEles, 
+                eleMag, 
+                band
+        );
+        // cal type not set in the imageSource URL
+        if (af.getCalType() == Calibrator.CAL_NONE) {
+          af.setCalType(cal);
+        }
+        dir = af.getAreaDirectory(); // be sure to re-get the directory
+        buildFlatField(
+            af, 
+            0, 
+            0, 
+            dir.getLines(), 
+            dir.getElements(),
+            dir.getBands()[0],
+            pack
+        );
+        
+      // getting the entire file including all bands
+      } else {
+        buildFlatField(
+            af, 
+            0, 
+            0, 
+            dir.getLines(), 
+            dir.getElements(),
+            0,
+            pack
+        );
+      }
+      
+    } catch (McIDASException afe) {
+         throw new VisADException("Problem with McIDAS AREA file: " + afe);
+    } finally {
+      if (af != null) {
+        af.close();
+      }
+    }
+  }
+
+  /** Build a FlatField from the image pixels
+    * @param af is the AreaFile
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  //private void buildFlatField(AreaFile af) throws VisADException {
+  private void buildFlatField(AreaFile af, 
+                              int startLine, 
+                              int startEle, 
+                              int numLines,
+                              int numEles, 
+                              int band,
+                              boolean pack) throws VisADException {
+
+    int[] nav=null;
+    int[] aux = null;
+
+    try {
+      areaDirectory = af.getAreaDirectory();
+      nav = af.getNav();
+      aux = af.getAux();
+    } catch (Exception rmd) {
+        throw new VisADException(
+            "Problem getting Area file directory or navigation: " + rmd);
+    }
+
+    // extract the size of each dimension from the directory
+    int nLines = (numLines == 0) ? areaDirectory.getLines() : numLines;
+    int nEles = (numEles == 0) ? areaDirectory.getElements(): numEles;
+
+    // make the VisAD RealTypes for the dimension variables
+    RealType line = RealType.getRealType("ImageLine", null, null);
+    RealType element = RealType.getRealType("ImageElement", null, null);
+
+    // extract the number of bands (sensors) and make the VisAD type
+    // NB: always zero now
+    int bandNums[] = areaDirectory.getBands();
+    int numBands = areaDirectory.getNumberOfBands();  // this might be different
+    
+    // create indicies into the data structure for the bands
+    int[] bandIndices = new int[numBands];
+    if (band != 0) { // specific bands requested
+        bandIndices[0] = -1;
+        for (int i = 0; i < numBands; i++) {
+           if (band == bandNums[i]) {
+              bandIndices[0] = i;
+              break;
+           }
+        }
+        if (bandIndices[0] == -1)  // not found
+            throw new VisADException("requested band number not in image");
+        bandNums = new int[] { band };
+        numBands = 1;
+    } else {  // all bands
+        for (int i = 0; i < numBands; i++) bandIndices[i] = i;
+    }
+    
+    RealType[] bands = new RealType[numBands];
+    // If we have calibration units, might as well use them.
+    Unit calUnit = null;
+    float calScale = 1.0f;
+    String unit = areaDirectory.getCalibrationUnitName();
+    if (unit != null) {
+        try {
+            String unitName = visad.jmet.MetUnits.makeSymbol(
+                    areaDirectory.getCalibrationUnitName());
+            calUnit = visad.data.units.Parser.parse(unitName);
+            // can't clone BaseUnit
+            try {
+                calUnit = calUnit.clone(unitName);
+            } catch (UnitException ue) {} // catch can't clone base unit
+        } catch (Exception e) {  // bad unit name
+           //e.printStackTrace();
+           calUnit = null;
+        }
+        calScale = (1.0f/areaDirectory.getCalibrationScaleFactor());
+    }
+    String calType = areaDirectory.getCalibrationType();
+
+    // first cut: the bands are named "Band##" where ## is the
+    // band number from the AREA file bandmap
+    for (int i = 0; i < numBands; i++)
+    {
+        bands[i] = 
+          (calUnit != null)
+             ? RealType.getRealType("Band"+bandNums[i]+"_"+calType, calUnit)
+             : RealType.getRealType("Band"+bandNums[i]);
+    }
+
+
+    // the range of the FunctionType is the band(s)
+    RealTupleType radiance = new RealTupleType(bands);
+
+    // the domain is (element,line) since elements (X) vary fastest
+    RealType[] domain_components = {element,line};
+
+    // Create the appropriate CoordinateSystem and attach it to
+    // the domain of the FlatField.  AREACoordinateSystem transforms
+    // from (ele,lin) -> (lat,lon)
+    try
+    {
+        // adjust the directory in case a subsection was requested
+        int[] dirBlock = (int[]) areaDirectory.getDirectoryBlock().clone();
+// BMF: directory modification is now handled in AreaFile
+//        dirBlock[5] = dirBlock[5] + (startLine * dirBlock[11]);
+//        dirBlock[6] = dirBlock[6] + (startEle  * dirBlock[12]);
+//        dirBlock[8] = nLines;
+//        dirBlock[9] = nEles;
+        cs = new AREACoordinateSystem( dirBlock, nav, aux);
+    }
+    catch (VisADException e)
+    {
+      System.out.println(e);
+      System.out.println("Using null CoordinateSystem");
+      cs = null;
+    }
+
+    RealTupleType image_domain =
+                new RealTupleType(domain_components, cs, null);
+
+    //  Image numbering is usually the first line is at the "top"
+    //  whereas in VisAD, it is at the bottom.  So define the
+    //  domain set of the FlatField to map the Y axis accordingly
+
+    Linear2DSet domain_set = new Linear2DSet(image_domain,
+                                0, (nEles - 1), nEles,
+                                (nLines - 1), 0, nLines );
+    FunctionType image_type =
+                        new FunctionType(image_domain, radiance);
+
+    // If calibrationType is brightnes (BRIT), then we can store
+    // the values as shorts.  To do this, we crunch the values down
+    // from 0-255 to 0-254 so we can have 255 left over for missing
+    // values.
+    Set[] rangeSets = null;
+    pack = pack && calType.equalsIgnoreCase("BRIT");
+    if (pack) {
+      rangeSets = new Set[numBands];
+      for (int i = 0; i < numBands; i++) {
+            rangeSets[i] = new Integer1DSet(bands[i], 255);
+      }
+    }
+    Unit[] rangeUnits = null;
+    if (calUnit != null) {
+      rangeUnits = new Unit[numBands];
+      for (int i = 0; i < numBands; i++) rangeUnits[i] = calUnit;
+    }
+
+    // finally, create the field.
+    field = new FlatField(image_type,
+                          domain_set,
+                          (CoordinateSystem[]) null,
+                          rangeSets,
+                          rangeUnits);
+
+    // since we are returning a SingleBandedImage in the getData 
+    // and getImage calls anyway, we might as well create the
+    // SingleBandedImage here.  We haven't setSamples so this shouldn't
+    // be expensive.
+    if (radiance.getDimension() == 1) {
+      field = 
+        (cs == null)
+          ?  new SingleBandedImageImpl(field, getNominalTime(), "McIDAS Image")
+          :  new NavigatedImage(field, getNominalTime(), "McIDAS Image");
+    }
+
+
+    // get the data, possibly calibrated
+    float[][][] flt_samples;
+    try {
+      flt_samples = af.getFloatData();
+    } catch (McIDASException samp) {
+      throw new VisADException("Problem reading AREA file: "+samp);
+    }
+
+    // for each band, create a sample array for the FlatField
+
+    try {
+      float[][] samples = new float[numBands][nEles*nLines];
+
+      //if (areaDirectory.getCalibrationType().equalsIgnoreCase("BRIT"))
+      if (pack) {
+
+        for (int b=0; b<numBands; b++) {
+          for (int i=0; i<nLines; i++) {
+            for (int j=0; j<nEles; j++) {
+  
+              float val = flt_samples[bandIndices[b]][startLine+i][startEle+j];
+              samples[b][j + (nEles * i) ] =
+                (val == 255)
+                   ? 254.0f                   // push 255 into 254 for BRIT
+                   : (float) val * calScale;
+            }
+          }
+        }
+      }
+      else {
+
+        for (int b=0; b<numBands; b++) {
+          for (int i=0; i<nLines; i++) {
+            for (int j=0; j<nEles; j++) {
+
+              samples[b][j + (nEles * i) ] = calScale * 
+                flt_samples[bandIndices[b]][startLine+i][startEle+j];
+
+            }
+          }
+        }
+      }
+      field.setSamples(samples, false);
+
+    } catch (RemoteException e) {
+        throw new VisADException("Couldn't finish image initialization");
+    }
+
+  }
+
+
+  /**
+    * get the dimensions of the image
+    *
+    * @return dim[0]=number of bands, dim[1] = number of elements,
+    *   dim[2] = number of lines
+   */
+  public int[] getDimensions() {
+    int[] dim = new int[3];
+    dim[0] = areaDirectory.getNumberOfBands();
+    dim[1] = areaDirectory.getElements();
+    dim[2] = areaDirectory.getLines();
+    return dim;
+  }
+
+  /**
+    * get the CoordinateSystem of the image
+    *
+    * @return the CoordinateSystem object
+   */
+  public CoordinateSystem getCoordinateSystem() {
+    return cs;
+  }
+
+  /**
+    * get the AreaDirectory of the image
+    *
+    * @return the AreaDirectory object
+   */
+  public AreaDirectory getAreaDirectory() {
+    return areaDirectory;
+  }
+
+  /**
+   * Return a FlatField representing the image.  The field will look
+   * like the following:<P>
+   * <UL>
+   * <LI>Domain - Linear2DSet of (ImageLine, ImageElement) with
+   *              AREACoordinateSystem to Lat/Lon (may be null).
+   * <LI>Range - One or more bands.  If calibration type is BRIT,
+   *             Integer1DSets are used as the range sets with length 255.
+   *             (brightness 255 is same as 254).
+   * </UL>
+   *
+   * @return image as a FlatField
+   */
+
+  public FlatField getData() {
+    /*
+    if (field.getRangeDimension() == 1) {
+       try {
+           return (FlatField) getImage();
+       } catch (VisADException ve) { ve.printStackTrace();}
+    } 
+    */
+    return (FlatField) field;
+  }
+
+  /**
+   * Retrieves the "nominal" time of the image as a VisAD DateTime.  This
+   * may or may not be the start of the image scan.  Values are derived from
+   * the 4th and 5th words in the AREA file directory.
+   * @see <a href="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+   * McIDAS Programmer's Manual</a>
+   * @see #getImageStartTime()
+   *
+   * @return  nominal image time
+   */
+  public DateTime getNominalTime()
+      throws VisADException
+  {
+      return new DateTime(areaDirectory.getNominalTime());
+  }
+
+  /**
+   * Retrieves the time of the start of the image scan as a VisAD DateTime.
+   * Values are derived from the 46th and 47th words in the AREA file directory.
+   * @see <a href="http://www.ssec.wisc.edu/mug/prog_man/prog_man.html">
+   * McIDAS Programmer's Manual</a>
+   * @see #getNominalTime()
+   *
+   * @return  time of the start of the image scan
+   */
+  public DateTime getImageStartTime()
+      throws VisADException
+  {
+      return new DateTime(areaDirectory.getStartTime());
+  }
+
+// WLH 24 August 2000
+  /**
+   * Retrieves the first (and/or only) band in an image as a SingleBandedImage
+   *
+   * @return  SingleBandedImage representation of the FlatField from getData().
+   *          If there is navigation associated with the image, the returned
+   *          image is a NavigatedImage.
+   */
+  public SingleBandedImage getImage() throws VisADException {
+
+    SingleBandedImage firstBand;
+    if (field.getRangeDimension() > 1) {
+      try {
+        firstBand = 
+          (cs == null)
+            ? (SingleBandedImage)
+              new SingleBandedImageImpl(((FlatField) field.extract(0)),
+                                         getNominalTime(),
+                                         "McIDAS Image", false)
+            : (SingleBandedImage)
+              new NavigatedImage(((FlatField) field.extract(0)),
+                                  getNominalTime(),
+                                  "McIDAS Image", false);
+      } catch (RemoteException excp) {
+        throw new VisADException("AreaAdapter.getImage(): RemoteException");
+      }
+
+    } else {
+      firstBand = (SingleBandedImage) field;
+    }
+    return firstBand;
+
+  }
+
+}
diff --git a/visad/data/mcidas/AreaForm.java b/visad/data/mcidas/AreaForm.java
new file mode 100644
index 0000000..6a48b86
--- /dev/null
+++ b/visad/data/mcidas/AreaForm.java
@@ -0,0 +1,155 @@
+//
+// AreaForm.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.mcidas;
+
+import edu.wisc.ssec.mcidas.*;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.UnimplementedException;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormNode;
+import visad.data.FormFileInformer;
+
+/** to allow determination of whether a file is of type McIDAS
+  * 'area'.
+  *
+  */
+public class AreaForm extends Form implements FormFileInformer {
+
+  private AreaAdapter aa;
+
+  public AreaForm() {
+    super("AreaForm");
+
+    // force AreaFile class to be loaded so 'adde:' handler is initialized
+    AreaFile.isURLHandlerLoaded();
+  }
+
+  /** determine the file type by name. McIDAS area files begin
+    *  with the letters: AREA (or area).
+    *
+    * @param name is the filename in question
+    */
+  public boolean isThisType(String name) {
+    File file = new File(name);
+    if (file.exists()) name = file.getName();
+    return name.startsWith("AREA") || 
+           name.endsWith("area")   || 
+           ( name.startsWith("adde://") && 
+           ( name.indexOf("/image?") > 0 ||
+             name.indexOf("/imageda") > 0) );
+  }
+
+  /** there is no unique way to identify an AREA file by
+    * examining the contents of the first block of data values
+    *
+    * @param block is an array of ? length from the beginning
+    * of the file in question.
+    *
+    */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /** return a list of suffixes associated with this file type
+  *
+  */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { " " };
+    return suff;
+  }
+
+  /** save the file back to disk
+  *
+  * This has not been implemented yet
+  *
+  */
+  public synchronized void save(String id, Data data, boolean replace)
+	throws  BadFormException, IOException, RemoteException, VisADException
+  {
+    throw new UnimplementedException("Can't yet save McIDAS AREA objects");
+  }
+
+  /** This has not been implemented
+  *
+  */
+  public synchronized void add(String id, Data data, boolean replace)
+	throws BadFormException {
+
+    throw new RuntimeException("Can't yet add McIDAS AREA objects");
+  }
+
+  /** read the area file from local disk, and return the Area file
+    * as a DataImpl object (a FlatField).
+    *
+    * @param path is the fully-qualified pathname
+    *
+    */
+  public synchronized DataImpl open(String path)
+	throws BadFormException, RemoteException, VisADException {
+
+    try {
+      aa = new AreaAdapter(path);
+      return aa.getData();
+
+    } catch (IOException e) {
+      throw new VisADException("IOException: " + e.getMessage());
+    }
+  }
+
+  /** read the area file from a URL,  and return the Area file
+    * as a DataImpl object (a FlatField).
+    *
+    * @param url is the fully-formed URL
+    *
+    */
+  public synchronized DataImpl open(URL url)
+	throws BadFormException, VisADException, IOException {
+
+    // AreaAdapter constructor decides if argument is a file or a URL
+    aa = new AreaAdapter(url.toString());
+    return aa.getData();
+  }
+
+  /** not implemented yet
+  *
+  */
+  public synchronized FormNode getForms(Data data) {
+
+    throw new RuntimeException("Can't yet get McIDAS AREA forms");
+  }
+}
diff --git a/visad/data/mcidas/BaseMapAdapter.java b/visad/data/mcidas/BaseMapAdapter.java
new file mode 100644
index 0000000..ab7122f
--- /dev/null
+++ b/visad/data/mcidas/BaseMapAdapter.java
@@ -0,0 +1,604 @@
+//
+// BaseMapAdapter.java
+//
+
+/*
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.mcidas;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Vector;
+import visad.CoordinateSystem;
+import visad.Gridded2DSet;
+import visad.Linear2DSet;
+import visad.MathType;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.UnionSet;
+import visad.VisADException;
+import java.awt.geom.Rectangle2D;
+
+/** this is an adapter for McIDAS Base Map files */
+
+public class BaseMapAdapter {
+  private boolean isCoordinateSystem = false;
+  private int latMax=900000, latMin=-900000;
+  private int lonMax=1800000, lonMin=-1800000;
+  private int segmentPointer = 0;  // index into array of segments
+  private int numEles=0;
+  private CoordinateSystem cs=null;
+  private DataInputStream din;
+  private MathType coordMathType;
+  private int position;    // position (bytes) where we are reading in the file
+  private int numSegments = 0;
+  private int[][] segList;
+  private boolean isEastPositive = true;
+  private int xfirst = 0;
+  private int xlast = 0;
+  private int yfirst = 0;
+  private int ylast = 0;
+  private int MAX_SEGMENTS = 100000;  // docs say 1000, but they lie!
+
+  /**
+   * Create a VisAD UnionSet from a local McIDAS Base Map file
+   *
+   * @param filename name of local file.
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public BaseMapAdapter(String filename) throws IOException, VisADException {
+    this(new FileInputStream(filename), null);
+  }
+
+
+  /**
+   * Create a VisAD UnionSet from a McIDAS Base Map file on the Web
+   *
+   * @param filename name of local file.
+   * @param bbox  lat/lon bounding box of map lines to include
+   *
+   * @exception IOException if there was a problem reading the file.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public BaseMapAdapter(String filename, Rectangle2D bbox)
+    throws IOException, VisADException {
+      this(new FileInputStream(filename), null);
+  }
+
+  /**
+   * Create a VisAD UnionSet from a McIDAS Base Map file on the Web
+   *
+   * @param url URL & filename name of remote file
+   *
+   * @exception IOException if there was a problem reading the URL.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public BaseMapAdapter(URL url) throws IOException, VisADException {
+    this (url.openStream(), null);
+  }
+
+  /**
+   * Create a VisAD UnionSet from a McIDAS Base Map file on the Web
+   *
+   * @param url  URL of remote file
+   * @param bbox  lat/lon bounding box of map lines to include
+   *
+   * @exception IOException if there was a problem reading the URL.
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public BaseMapAdapter(URL url, Rectangle2D bbox)
+    throws IOException, VisADException {
+      this (url.openStream(), bbox);
+  }
+
+  /**
+   * Create a VisAD UnionSet from a McIDAS Base Map file inputstream
+   *
+   * @param is input stream of mapfile
+   *
+   * @exception IOException if there was a problem reading the inputstream
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public BaseMapAdapter(InputStream is)
+    throws IOException, VisADException {
+    this(is, null);
+  }
+
+  /**
+   * Create a VisAD UnionSet from a McIDAS Base Map file inputstream
+   *
+   * @param is input stream of mapfile
+   * @param bbox  lat/lon bounding box
+   *
+   * @exception IOException if there was a problem reading the inputstream
+   * @exception VisADException if an unexpected problem occurs.
+   */
+  public BaseMapAdapter(InputStream is, Rectangle2D bbox)
+    throws IOException, VisADException {
+
+    din = new DataInputStream (new BufferedInputStream(is));
+    InitFile();
+    if (bbox != null)
+        setLatLonLimits((float) bbox.getMinY(), (float) bbox.getMaxY(),
+                        (float) bbox.getMinX(), (float) bbox.getMaxX());
+  }
+
+  /** set the limits of Lats and Lons; without this, the getData()
+   * will return ALL the points in the file.  When this method is
+   * used, the feature of the McIDAS map files that has the
+   * lat/lon extremes for each line segment will be used to
+   * coarsely cull points out of the returned VisAD UnionSet.<P>
+   *
+   * This may be used along with any other domain-setting routine,
+   * but should be invoked last.  Alternatively, pass in the
+   * bounding box in the constructor.
+   *
+   * @param bbox Rectangle2D representing the bounding box
+   */
+  public void setLatLonLimits(Rectangle2D bbox)
+  {
+      setLatLonLimits((float) bbox.getMinY(), (float) bbox.getMaxY(),
+                      (float) bbox.getMinX(), (float) bbox.getMaxX());
+  }
+
+  /** set the limits of Lats and Lons; without this, the getData()
+   * will return ALL the points in the file.  When this method is
+   * used, the feature of the McIDAS map files that has the
+   * lat/lon extremes for each line segment will be used to
+   * coarsely cull points out of the returned VisAD UnionSet.<P>
+   *
+   * This may be used along with any other domain-setting routine,
+   * but should be invoked last.
+   *
+   * @param latmin the minimum Latitude value
+   * @param latmax the maximum Latitude value
+   * @param lonmin the minimum Longitude value (-180 -- 180)
+   * @param lonmax the maximum Longitude value
+   *
+   */
+  public void setLatLonLimits(float latmin,
+                              float latmax,
+                              float lonmin,
+                              float lonmax) {
+    latMin = (latmin == Float.NaN)
+                ? -900000
+                : (int) (latmin * 10000.f);
+    latMax = (latmax == Float.NaN)
+                ? 900000
+                : (int) (latmax * 10000.f);
+    lonMin = (lonmin == Float.NaN)
+                ? -1800000
+                : (int) (lonmin * 10000.f);
+    lonMax = (lonmax == Float.NaN)
+                ? 1800000
+                : (int) (lonmax * 10000.f);
+    //System.out.println("Lat min/max = "+latMin+" "+latMax);
+    //System.out.println("Lon min/max = "+lonMin+" "+lonMax);
+    return;
+  }
+
+  /**
+   * Using the domain_set of the FlatField of an image (when
+   * one is available), extract the elements required.  This
+   * implies that a CoordinateSystem is available with a
+   * reference coordinate of Latitude,Longitude.
+   *
+   * @param domainSet The VisAD Linear2DSet domain_set used when the
+   *                  associated image FlatField was created
+   *
+   * @throws  VisADException  necessary VisAD object cannot be created
+   */
+
+  public void setDomainSet(Linear2DSet domainSet)
+     throws VisADException
+  {
+      coordMathType = domainSet.getType();
+      cs = domainSet.getCoordinateSystem();
+      numEles = ((Linear2DSet) domainSet).getX().getLength();
+
+      xfirst = (int) ((Linear2DSet) domainSet).getX().getFirst();
+      xlast = (int) ((Linear2DSet) domainSet).getX().getLast();
+      yfirst = (int) ((Linear2DSet) domainSet).getY().getFirst();
+      ylast = (int) ((Linear2DSet) domainSet).getY().getLast();
+
+      /*
+      System.out.println("coordMathType="+coordMathType);
+      System.out.println("cs="+cs);
+      System.out.println("numEles="+numEles);
+      System.out.println("numLines="+numLines);
+      System.out.println("xfirst="+xfirst);
+      System.out.println("xlast="+xlast);
+      System.out.println("yfirst="+yfirst);
+      System.out.println("ylast="+ylast);
+      */
+
+      computeLimits();
+  }
+
+  /**
+   * Define a CoordinateSystem whose fromReference() will
+   * be used to transform points from latitude/longitude
+   * into element,line.
+   *
+   * @param cs is that
+   * @param numEles is number of elements (x)
+   * @param numLines is number of lines (y)
+   * @param domain is the desired domain (ordered element, line)
+   *
+   * @exception  VisADException  a necessary VisAD object could not be created
+   */
+  public void setCoordinateSystem(CoordinateSystem cs, int numEles,
+                          int numLines, RealTupleType domain)
+                          throws VisADException {
+
+    this.numEles = numEles;
+    this.cs = cs;
+    coordMathType = domain;
+    xlast = numEles - 1;
+    ylast = numLines - 1;
+
+    computeLimits();
+  }
+
+  /**
+   * Set the MathType of the UnionSet to be lat/lon.
+   */
+  public void doByLatLon() {
+    isCoordinateSystem = false;
+    try {
+      coordMathType = new RealTupleType(RealType.Latitude, RealType.Longitude);
+    } catch (Exception ert) {;}
+  }
+
+  // compute the lat/long limits for fetching data; invert the Y axis, too.
+  private void computeLimits() {
+
+    // Now set lat/lon limits...
+
+    float[][] linele =
+        { {(float) xfirst, (float) xlast, (float)xlast, (float)xfirst} ,
+        {(float) yfirst, (float) yfirst, (float)ylast, (float)ylast} };
+
+    float[][] latlon;
+
+    try {
+
+      latlon = cs.toReference(linele);
+
+      if (Float.isNaN(latlon[0][0])) latlon[0][0] = 90.f;
+      if (Float.isNaN(latlon[1][0])) latlon[1][0] = 180.f;
+
+      if (Float.isNaN(latlon[0][1])) latlon[0][1] = 90.f;
+      if (Float.isNaN(latlon[1][1])) latlon[1][1] = -180.f;
+
+      if (Float.isNaN(latlon[0][2])) latlon[0][2] = -90.f;
+      if (Float.isNaN(latlon[1][2])) latlon[1][2] = 180.f;
+
+      if (Float.isNaN(latlon[0][3])) latlon[0][3] = -90.f;
+      if (Float.isNaN(latlon[1][3])) latlon[1][3] = -180.f;
+
+
+      /*
+      for (int i=0; i<4; i++) {
+        System.out.println("Point "+i+"  Line/Ele="+linele[0][i]+" "+
+          linele[1][i]+" Lat/long="+ latlon[0][i]+" "+latlon[1][i]);
+      }
+      */
+
+      setLatLonLimits(
+
+        Math.min(latlon[0][0],Math.min(latlon[0][1],
+                         Math.min(latlon[0][2],latlon[0][3]))),
+        Math.max(latlon[0][0],Math.max(latlon[0][1],
+                         Math.max(latlon[0][2],latlon[0][3]))),
+        Math.min(latlon[1][0],Math.min(latlon[1][1],
+                         Math.min(latlon[1][2],latlon[1][3]))),
+        Math.max(latlon[1][0],Math.max(latlon[1][1],
+                       Math.max(latlon[1][2],latlon[1][3])))
+      );
+    } catch (Exception ell) {System.out.println(ell);}
+
+    isCoordinateSystem = true;
+
+  }
+
+  // InitFile will initialize the file reading
+  private void InitFile() throws VisADException {
+    coordMathType = RealTupleType.LatitudeLongitudeTuple;
+
+    try {
+      numSegments = din.readInt();
+    } catch (IOException e) {
+      throw new VisADException("Error reading map file " + e);
+    }
+
+    // Begin - DRM 04-20-2000
+    if (numSegments <= 0 || numSegments > MAX_SEGMENTS)
+    {
+      throw new VisADException(
+          "McIDAS map file format error: number of segments = " + 
+          numSegments);
+    }
+    // End - DRM 04-20-2000
+
+    // already read first word, so move pointer to start of second word (4 bytes)
+    position = 4;
+    segList = new int[numSegments][6];
+
+    // read in the segment info
+    for (int i=0; i<numSegments; i++) {
+      try {
+        /* Each segement directory has 6 words:
+             0 - min lat
+             1 - max lat
+             2 - min lon
+             3 - max lon
+             4 - pointer (words) to start of data for segment
+             5 - number of words to read for the segment 
+                 (should be even lat/lon)
+        */
+        for (int j=0; j<6; j++) {
+          segList[i][j] = din.readInt();
+          // Begin - DRM 04-20-2000
+          if (j == 4 && segList[i][4] < 0)  // bad pointer to data
+          {
+              throw new VisADException(
+                  "McIDAS map file format error: Negative pointer (" +
+                  segList[i][4] + ") to start of data for segment " + i);
+          }
+          //bad number of words
+          if (j == 5 && (segList[i][5] < 0 || segList[i][5]%2 != 0)) 
+          {
+              throw new VisADException(
+                  "McIDAS map file format error: Wrong number of words (" + 
+                  segList[i][5] + ") to read for segment " + i);
+          }
+          // End - DRM 04-20-2000
+          position = position + 4;   // move the pointer to next word
+        }
+      } catch (IOException e) {
+        throw new VisADException("Base Map: Error reading map file: "+e);
+      }
+    }
+    segmentPointer = -1;   // HACK!  set to -1 so first segment is not skipped
+                           // in findNextSegment()
+    return;
+  }
+
+  /* 
+   * locate the next valid segment (based on lat/lon extremes)
+   * the return is 0 if we've read all the segments otherwise it is
+   * the number of pairs of lat/lon points
+   */
+  private int findNextSegment() throws VisADException {
+    while (true) {
+      segmentPointer++;   
+      // exit if we've read all the points
+      if (segmentPointer >= numSegments) {
+        return 0;
+      }
+
+      // check for lat/lon bounds...
+      if (segList[segmentPointer][0] > latMax ||
+            segList[segmentPointer][1] < latMin) {continue;}
+
+      if (isEastPositive) {
+        int mx = -segList[segmentPointer][2];
+        int mn = -segList[segmentPointer][3];
+
+        if (lonMax > 1800000 ) {
+          if ( mx < 0 && mx < lonMin) mx = mx + 3600000;
+          if ( mn < 0 && mn < lonMin) mn = mn + 3600000;
+        }
+
+        if ( mx > lonMax ) {continue;}
+        if ( mn < lonMin ) {continue;}
+
+      } else {
+        if (segList[segmentPointer][2] > lonMax ||
+            segList[segmentPointer][3] < lonMin) {continue; }
+      }
+
+      return segList[segmentPointer][5] / 2;
+
+    } // end while...
+  }
+
+  /* 
+   * Get the lat/lons for the current segment
+   */
+  private float[][] getLatLons() throws VisADException {
+
+    int numPairs = segList[segmentPointer][5] / 2;
+
+    // DRM 4/20/2000
+    if (numPairs < 0)
+        throw new VisADException(
+            "Error in map file: Negative number of lat/lon pairs");
+            
+    int lat;
+    int lon;
+    int skipByte;
+    long rc;
+    float[][] lalo;
+
+    float dLonMin = (float)lonMin/10000.0f;
+
+    try {
+      skipByte = segList[segmentPointer][4] * 4 - position;
+      try {
+        din.skipBytes(skipByte);
+      } catch (Exception e) {
+        throw new VisADException("Base Map: IOException in skip" + e);
+      }
+
+      lalo = new float[2][numPairs];
+      for (int i=0; i<numPairs; i++) {
+        lat = din.readInt();
+        lon = din.readInt();
+        lalo[0][i] = (float) lat/10000.f;
+        lalo[1][i] = (float) lon/10000.f;
+        if (isEastPositive) {
+          lalo[1][i] = -lalo[1][i];
+          if (lalo[1][i] < 0.0 && lalo[1][i] < dLonMin && lonMax > 1800000)
+              lalo[1][i] = 360.f+lalo[1][i];
+        }
+      }
+    } catch (IOException e) {
+          throw new VisADException("Base Map: read past EOF");
+    }
+    position = position + skipByte + (8 * numPairs);
+    return lalo;
+  }
+
+  /**
+    * getData creates a VisAD UnionSet type with the MathType
+    * specified thru one of the other methods.  By default,
+    * the MathType is a RealTupleType of Latitude,Longitude,
+    * so the UnionSet (a union of Gridded2DSets) will have
+    * lat/lon values.  Each Gridded2DSet is a line segment that
+    * is supposed to be drawn as a continuous line.  This should
+    * only be called once after construction.
+    *
+    * @return  UnionSet of maplines or null if there are no maplines
+    *          in the domain of the display.
+    */
+  public UnionSet getData() {
+
+    UnionSet maplines=null;
+    Gridded2DSet gs;
+
+    int st=1;
+    float[][] lalo, linele, llout;
+    int ll;
+
+    Vector sets = new Vector();
+    float maxEle = (float)numEles/2.0f;
+
+    try {
+      int inum = 0;
+      while (true) {
+        st = findNextSegment();
+        if (st == 0) break;   // we've read all the segments
+        lalo = getLatLons();
+        ll = lalo[0].length;
+        int lbeg = 0;
+        int lnum = 0;
+
+        if (isCoordinateSystem) {
+          linele = cs.fromReference(lalo);
+          boolean missing = false;
+
+          for (int i=0; i<ll; i++) {
+            if (Float.isNaN(linele[0][i])) {
+              missing=true;
+              break;
+            }
+
+            if (i > 0 && Math.abs(linele[0][i] - linele[0][i-1]) > maxEle) {
+
+              if (lnum > 1) {
+
+               llout = new float[2][lnum];
+               System.arraycopy(linele[0],lbeg,llout[0],0,lnum);
+               System.arraycopy(linele[1],lbeg,llout[1],0,lnum);
+               gs = new Gridded2DSet(coordMathType,llout,lnum);
+               sets.addElement(gs);
+              }
+
+              lnum = 0;
+              lbeg = i;
+            }
+
+            lnum ++;
+          }
+
+          if (missing) continue;
+
+          if (lnum == ll) {
+            gs = new Gridded2DSet(coordMathType,linele,ll);
+            sets.addElement(gs);
+          } else if (lnum > 1) {
+             llout = new float[2][lnum];
+             System.arraycopy(linele[0],lbeg,llout[0],0,lnum);
+             System.arraycopy(linele[1],lbeg,llout[1],0,lnum);
+
+             gs = new Gridded2DSet(coordMathType,llout,lnum);
+             sets.addElement(gs);
+          }
+
+
+        } else {
+
+          gs = new Gridded2DSet(coordMathType,lalo,ll);
+          sets.addElement(gs);
+        }
+        inum += ll;
+
+      }
+      //System.out.println("number of points = " + inum);
+      //System.out.println("number of sets = " + sets.size());
+
+      /* DRM - 03-Jan-2000
+      if (sets.size() > 1)  // need at least two for a UnionSet
+      */
+      if (!sets.isEmpty())  // need at least one for a UnionSet
+      {
+        Gridded2DSet[] basemaplines = new Gridded2DSet[sets.size()];
+        sets.copyInto(basemaplines);
+
+        maplines = new UnionSet(coordMathType,basemaplines, 
+                                null, null, null, false);
+      }
+
+    } catch (Exception em) {em.printStackTrace(); return null;}
+
+    return maplines;
+
+  }
+
+  /**
+   * set the sign of longitude convention.  By default, the
+   * longitudes are positive eastward
+   *
+   * @param value set to true for positive eastward, set to
+   *              false for positive westward.
+   */
+  public void setEastPositive(boolean value) {
+    isEastPositive = value;
+  }
+
+  /**
+   * determine what sign convention for longitude is currently
+   * being used.
+   *
+   * @return true if the convention is positive eastward; false if
+   *         positive westward.
+   */
+  public boolean isEastPositive() {
+    return (isEastPositive);
+  }
+}
diff --git a/visad/data/mcidas/GRIDCoordinateSystem.java b/visad/data/mcidas/GRIDCoordinateSystem.java
new file mode 100644
index 0000000..139c98e
--- /dev/null
+++ b/visad/data/mcidas/GRIDCoordinateSystem.java
@@ -0,0 +1,156 @@
+//
+// GRIDCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.mcidas;
+
+import visad.*;
+import edu.wisc.ssec.mcidas.*;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * GRIDCoordinateSystem is the VisAD CoordinateSystem class
+ * for conversions to/from (Latitude, Longitude) and Cartesian (col,row),
+ * and with Latitude and Longitude in degrees.
+ */
+public class GRIDCoordinateSystem
+    extends visad.georef.MapProjection {
+
+  private GRIDnav gnav = null;
+  private int rows;
+  private int columns;
+  private int[] dirBlock;
+
+  private static Unit[] coordinate_system_units =
+    {null, null};
+
+  /** 
+    * create a GRID coordinate system from a GridDirectory
+    *
+    * @param gridDirectory  directory to use
+    */
+  public GRIDCoordinateSystem(GridDirectory gridDirectory)
+        throws VisADException
+  {
+    this(gridDirectory.getDirBlock());
+  }
+
+  /** 
+    * create a GRID coordinate system from the GRID's
+    * directory block;
+    *
+    * @param dirBlock the grid's directory block
+    */
+  public GRIDCoordinateSystem(int[] dirBlock)
+        throws VisADException
+  {
+    super(RealTupleType.LatitudeLongitudeTuple, coordinate_system_units);
+    rows = dirBlock[GridDirectory.ROWS_INDEX];
+    columns = dirBlock[GridDirectory.COLS_INDEX];
+    try
+    {
+      gnav = new GRIDnav(dirBlock);
+      gnav.setStart(0,0);
+      gnav.setFlipRowCoordinates(rows);
+    }
+    catch (McIDASException excp)
+    {
+      throw new VisADException( "Grid cannot be navigated");
+    }
+    this.dirBlock = dirBlock;
+  }
+
+  /**
+   * Converts grid xy (col,row) to latitude/longitude
+   * @param rowcol  array containing the col/row pairs
+   * @return array containing the corresponding lat/lon pairs
+   * @throws  VisADException if input is invalid or there is no nav module
+   */
+  public double[][] toReference(double[][] rowcol) 
+        throws VisADException {
+    if (rowcol == null || rowcol.length != 2) {
+      throw new CoordinateSystemException("GRIDCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+    if (gnav == null) {
+      throw new CoordinateSystemException("GRID navigation data not available");
+    }
+    return gnav.toLatLon(rowcol);
+  }
+
+  /**
+   * Converts lat/lon to grid xy (col,row)
+   * @param  latlon  array containing the corresponding lat/lon pairs
+   * @return array containing the col/row pairs
+   * @throws  VisADException if input is invalid or there is no nav module
+   */
+  public double[][] fromReference(double[][] latlon) 
+        throws VisADException {
+    if (latlon == null || latlon.length != 2) {
+      throw new CoordinateSystemException("GRIDCoordinateSystem." +
+             "fromReference: tuples wrong dimension");
+    }
+    if (gnav == null) {
+      throw new CoordinateSystemException("GRID navigation data not available");
+    }
+    return gnav.toRowCol(latlon);
+  }
+
+  /**
+   * Get the bounds for this grid
+   */
+  public Rectangle2D getDefaultMapArea() 
+  { 
+      return new Rectangle2D.Double(0.0, 0.0, columns, rows);
+  }
+
+  /**
+   * Get the directory block used to initialize this GRIDCoordinateSystem
+   */
+  public int[] getDirBlock()
+  { 
+      return dirBlock;
+  }
+
+  /**
+   * Determines whether or not the <code>Object</code> in question is
+   * the same as this <code>AREACoordinateSystem</code>.  The specified
+   * <code>Object</code> is equal to this <CODE>GRIDCoordinateSystem</CODE>
+   * if it is an instance of <CODE>GRIDCoordinateSystem</CODE> and it has
+   * the same navigation module and default map area as this one.
+   *
+   * @param obj the Object in question
+   */
+  public boolean equals(Object obj)
+  {
+    if (!(obj instanceof GRIDCoordinateSystem))
+        return false;
+    GRIDCoordinateSystem that = (GRIDCoordinateSystem) obj;
+    return this == that ||
+           (gnav.equals(that.gnav) && 
+           this.rows == that.rows &&
+           this.columns == that.columns);
+  }
+}
diff --git a/visad/data/mcidas/MapForm.java b/visad/data/mcidas/MapForm.java
new file mode 100644
index 0000000..3776d36
--- /dev/null
+++ b/visad/data/mcidas/MapForm.java
@@ -0,0 +1,130 @@
+//
+// MapForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.mcidas;
+
+import visad.*;
+import visad.data.*;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.net.URL;
+
+/**
+   MapForm is the Map data format adapter for
+   serialized visad.Data objects.<P>
+*/
+public class MapForm extends Form implements FormFileInformer {
+
+  /** counter @serialized*/
+  private static int num = 0;
+
+  /**
+   * Construct a Form for reading in McIDAS map files
+   */
+  public MapForm() {
+    super("MapForm" + num++);
+  }
+
+  /**
+   * Determines if this is a McIDAS map file from the name
+   * @param  name  name of the file
+   * @return  true if it matches the pattern for McIDAS map files (OUTL*)
+   */
+  public boolean isThisType(String name) {
+    return (name.indexOf("OUTL") >= 0);
+  }
+
+  /**
+   * Determines if this is a McIDAS map file from the starting block
+   * @param  block  block of data to check
+   * @return  false  - there is no identifying block in a McIDAS map file
+   */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /**
+   * Get a list of default suffixes for McIDAS map files
+   * @return  valid list of suffixes
+   */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { " " };
+    return suff;
+  }
+
+  /**
+   * Save a VisAD data object in this form
+   * @throws  UnimplementedException  - can't be done yet.
+   */
+  public synchronized void save(String id, Data data, boolean replace)
+         throws BadFormException, IOException, RemoteException, VisADException {
+    throw new UnimplementedException("Can't yet save McIDAS map files");
+  }
+
+  /**
+   * Add data to an existing data object
+   * @throws BadFormException
+   */
+  public synchronized void add(String id, Data data, boolean replace)
+         throws BadFormException {
+    throw new BadFormException("MapForm.add");
+  }
+
+  /**
+   * Open the file specified by the string
+   * @param  id   string representing the path to the file
+   * @return a Data object representing the map lines. 
+   */
+  public synchronized DataImpl open(String id)
+         throws BadFormException, IOException, VisADException {
+    try {
+      BaseMapAdapter ba = new BaseMapAdapter(id);
+      return ba.getData();
+
+    } catch (IOException e) {
+      throw new VisADException("IOException: " + e.getMessage());
+    }
+  }
+
+  /**
+   * Open the file specified by the URL
+   * @param  url   URL of the remote map file
+   * @return a Data object representing the map lines. 
+   */
+  public synchronized DataImpl open(URL url)
+         throws BadFormException, VisADException, IOException {
+    BaseMapAdapter ba = new BaseMapAdapter(url);
+    return ba.getData();
+  }
+
+  /**
+   * Return the data forms that are compatible with a data object
+   * @return null
+   */
+  public synchronized FormNode getForms(Data data) {
+    return null;
+  }
+}
diff --git a/visad/data/mcidas/McIDASGridDirectory.java b/visad/data/mcidas/McIDASGridDirectory.java
new file mode 100644
index 0000000..87d7290
--- /dev/null
+++ b/visad/data/mcidas/McIDASGridDirectory.java
@@ -0,0 +1,165 @@
+//
+// McIDASGridDirectory.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.mcidas;
+
+import edu.wisc.ssec.mcidas.*;
+import visad.*;
+import visad.data.units.Parser;
+import visad.data.units.*;
+import visad.jmet.*;
+
+
+/**
+ * McIDASGridDirectory for McIDAS 'grid' directory entries
+ *
+ * @author Tom Whittaker
+ *
+ */
+public class McIDASGridDirectory extends visad.jmet.MetGridDirectory {
+
+  CoordinateSystem coordSystem = null;
+  GridDirectory directory = null;
+  private double paramScale;
+
+  /**
+   * Construct a McIDASGridDirectory from a GridDirectory
+   * @param directory  the grid directory  cannot be null
+   */
+  public McIDASGridDirectory(GridDirectory directory)
+  {
+    this.directory = directory;
+    if (directory != null) setParameters();
+  }
+  
+  /**
+   * Construct a McIDASGridDirectory from the byte representation of a
+   * the McIDAS grid directory block
+   * @param h  header as a byte array
+   */
+  public McIDASGridDirectory(byte[] h) {
+    int[] dirblock = new int[64];
+    for (int i = 0; i < 64; i++) dirblock[i] = McIDASUtil.bytesToInteger(h,i*4);
+    try {
+      directory = new GridDirectory(dirblock);
+    } catch (McIDASException excp) { 
+      directory = null; 
+    }
+    if (directory != null) setParameters();
+  }
+
+  private void setParameters() {
+ 
+    paramName = directory.getParamName();
+    rows = directory.getRows();
+    columns = directory.getColumns();
+    levels = 1;
+    validHour = directory.getForecastHour();
+    referenceTime = directory.getReferenceTime();
+    validTime = directory.getValidTime();
+    levelValue = directory.getLevelValue();
+
+    MetUnits mu = new MetUnits();
+    String su = directory.getParamUnitName();
+    String sl = directory.getLevelUnitName();
+    try {
+      paramUnit = Parser.parse(mu.makeSymbol(su));
+    } catch (ParseException pe) {
+      paramUnit = null;
+    }
+    try {
+      levelUnit = Parser.parse(mu.makeSymbol(sl));
+    } catch (ParseException pe) {
+      levelUnit = null;
+    }
+
+    secondLevelValue = directory.getSecondLevelValue();
+    secondTime = directory.getSecondTime();
+    paramScale = directory.getParamScale();
+
+  }
+
+  /**
+   * Get the scale of the parameter values
+   * @return parameter scale  (power of 10)
+   */
+  public double getParamScale() {
+    return paramScale;
+  }
+
+  /**
+   * Get the GRIDCoordinateSystem associated with this grid
+   * @return coordinate system  may be null if nav is unknown.
+   */
+  public CoordinateSystem getCoordinateSystem() {
+    if (coordSystem == null) {
+      try {
+        if (directory == null) throw new Exception("null directory");
+        coordSystem = new GRIDCoordinateSystem(directory);
+      } catch (Exception ev) { 
+        coordSystem = null;
+        System.out.println("No navigation available");
+      }
+    }
+    return coordSystem;
+  }
+
+  /**
+   * Get the raw navigation block from the directory.
+   * @return  raw int values  vary with grid type
+   */
+  public int[] getNavBlock() {
+     return (directory != null) ? directory.getNavBlock() : null;
+  }
+
+  /**
+   * Get the raw navigation block from the directory.
+   * @return  raw int values  vary with grid type (-1 == unknown)
+   */
+  public int getGridType() {
+    return (directory != null) ? directory.getNavType() : -1;
+  }
+
+  /**
+   * Get the grid directory used to create this beast.
+   * @return corresponding edu.wisc.ssec.mcidas.GridDirectory. (may be null)
+   */
+  public GridDirectory getGridDirectory() {
+    return directory;
+  }
+
+  /**
+   * Return a String representation of the McIDASGridDirectory
+   */
+  public String toString() {
+    return new String(paramName + " "+paramUnit+" "+rows+" "+
+    columns+" "+
+    levelValue+" "+levelUnit+" "+
+    referenceTime.toGMTString()+ " "+ (int) validHour
+    + " or "+validTime.toGMTString() );
+  }
+
+}
diff --git a/visad/data/mcidas/McIDASGridReader.java b/visad/data/mcidas/McIDASGridReader.java
new file mode 100644
index 0000000..b8f0764
--- /dev/null
+++ b/visad/data/mcidas/McIDASGridReader.java
@@ -0,0 +1,190 @@
+//
+// McIDASGridDirectory.java
+//
+
+/*
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+package visad.data.mcidas;
+
+import java.io.*;
+import java.util.*;
+import visad.*;
+import edu.wisc.ssec.mcidas.*;
+
+/**  Read grid(s) from a McIDAS grid file
+*/
+public class McIDASGridReader {
+  ArrayList gridH, gridD;
+  int[] entry;
+  RandomAccessFile fn;
+  boolean needToSwap = false;
+
+
+  public McIDASGridReader() {
+    gridD = null;
+    gridH = null;
+  }
+
+  /** read the first grid from the named file
+  *
+  * @return first grid
+  */
+  public ArrayList getGridData(String filename ) {
+
+    // open file and get pointer block
+    try {
+      fn = new RandomAccessFile(filename,"r");
+      int numEntries = Math.abs(readInt(10));
+      if (numEntries > 10000000) {
+          needToSwap = true;
+          numEntries = Math.abs(McIDASUtil.swbyt4(numEntries));
+      }
+      fn.seek(0);
+      // read the fileheader
+      int[] fileHeader = new int[8];
+      for (int i=0; i<8; i++) {
+        fileHeader[i] = fn.readInt();
+      }
+      System.out.println("head="+McIDASUtil.intBitsToString(fileHeader));
+
+      int project = readInt(8);
+      //System.out.println("Project = " + project);
+
+      int date = readInt(9);
+      //System.out.println("date = " + date);
+
+      entry = new int[numEntries];
+      for (int i=0; i<numEntries; i++) {
+        entry[i] = readInt(i + 11);
+      }
+
+      readEntry(0);
+
+    } catch (Exception e) {System.out.println("exp="+e);}
+
+    return gridD;
+  }
+
+  // internal method to fetch the 'ent'-th grid
+  private void readEntry(int ent) {
+    try {
+      int te = entry[ent] * 4;
+      System.out.println("Entry 0 = "+te);
+      int[] gridHeader = new int[64];
+      fn.seek(te);
+      for (int i = 0; i < 64; i++) {
+          gridHeader[i] = fn.readInt();
+      }
+      if (needToSwap) {
+          swapGridHeader(gridHeader);
+      }
+      McIDASGridDirectory mgd = 
+        new McIDASGridDirectory(new GridDirectory(gridHeader));
+      System.out.println("grid header ="+mgd.toString());
+      CoordinateSystem c = mgd.getCoordinateSystem();
+      int rows = mgd.getRows();
+      int cols = mgd.getColumns();
+      System.out.println("# rows & cols = "+rows+" "+cols);
+
+      double scale = mgd.getParamScale();
+      //System.out.println("param scale = "+scale+" gridType="+mgd.getGridType());
+
+      double[] data = new double[rows*cols];
+      int n = 0;
+            // store such that 0,0 is in lower left corner...
+      for (int nc=0; nc<cols; nc++) {
+        for (int nr=0; nr<rows; nr++) {
+         int temp = fn.readInt();           // check for missing value
+         if (needToSwap) temp = McIDASUtil.swbyt4(temp);
+         data[(rows-nr-1)*cols + nc] = 
+           (temp == McIDASUtil.MCMISSING)
+             ? Double.NaN
+             : ( (double) temp) / scale ;
+        }
+      }
+      gridH = new ArrayList();
+      gridD = new ArrayList();
+      gridH.add(mgd);
+      gridD.add(data);
+    } catch (Exception esc) {System.out.println(esc);}
+  }
+
+  /**
+   * Swap the grid header, avoiding strings
+   *
+   * @param gh   grid header to swap
+   */
+  private void swapGridHeader(int[] gh) {
+    McIDASUtil.flip(gh, 0, 5);
+    McIDASUtil.flip(gh, 7, 7);
+    McIDASUtil.flip(gh, 9, 10);
+    McIDASUtil.flip(gh, 12, 14);
+    McIDASUtil.flip(gh, 32, 51);
+  }
+
+  /** to get some grid, by index value, other than the first one
+  *
+  * @return ArrayList of the single grid
+  */
+  public ArrayList getGrid(int index) {
+    readEntry(index);
+    return gridD;
+  }
+
+  /** to get the grid header corresponding to the last grid read
+  *
+  * @return McIDASGridDirectory of the last grid read
+  */
+  public ArrayList getGridHeaders() {
+    return gridH;
+  }
+
+  /** for testing purposes
+  */
+  public  static void main(String[] args) {
+    String file = "/src/visad/data/mcidas/GRID1715";
+    if (args.length > 0) {
+      file = args[0];
+    }
+    McIDASGridReader mg = new McIDASGridReader();
+    mg.getGridData(file);
+  }
+    /**
+     * Read an integer
+     * @param word   word in file (0 based) to read
+     *
+     * @return  int read
+     *
+     * @throws IOException   problem reading file
+     */
+    private int readInt(int word) throws IOException {
+        if (fn == null) {
+            throw new IOException("no file to read from");
+        }
+        fn.seek(word * 4);
+        int idata = fn.readInt();
+        // set the order
+        if (needToSwap) {
+            idata = McIDASUtil.swbyt4(idata);
+        }
+        return idata;
+    }
+
+}
diff --git a/visad/data/mcidas/OUTLAUST b/visad/data/mcidas/OUTLAUST
new file mode 100644
index 0000000..ca7bd97
Binary files /dev/null and b/visad/data/mcidas/OUTLAUST differ
diff --git a/visad/data/mcidas/OUTLSUPW b/visad/data/mcidas/OUTLSUPW
new file mode 100644
index 0000000..486c721
Binary files /dev/null and b/visad/data/mcidas/OUTLSUPW differ
diff --git a/visad/data/mcidas/OUTLUSAM b/visad/data/mcidas/OUTLUSAM
new file mode 100644
index 0000000..41baceb
Binary files /dev/null and b/visad/data/mcidas/OUTLUSAM differ
diff --git a/visad/data/mcidas/PointDataAdapter.java b/visad/data/mcidas/PointDataAdapter.java
new file mode 100644
index 0000000..3a39c91
--- /dev/null
+++ b/visad/data/mcidas/PointDataAdapter.java
@@ -0,0 +1,393 @@
+//
+// PointDataAdapter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.mcidas;
+
+import edu.wisc.ssec.mcidas.*;
+import edu.wisc.ssec.mcidas.adde.*;
+import visad.*;
+import visad.data.units.*;
+import visad.jmet.MetUnits;
+import visad.util.DataUtility;
+import java.util.Vector;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * A class for adapting the results of an ADDE point data request into a 
+ * VisAD Data object.
+ *
+ * @author  Don Murray, Unidata
+ */
+public class PointDataAdapter {
+
+  AddePointDataReader reader;
+  FieldImpl field = null;
+  private boolean debug = false;
+  private boolean useAliases = true;
+  private boolean makeUniqueNames = false;
+
+  private static final String TEXT_EXT = "[Text]";
+
+  /**
+   * Construct a PointDataAdapter using the adde request passed as a string.
+   * This will take the data returned from the request and turn it into
+   * VisAD Data objects that can be returned by the getData() call.
+   *
+   * @param  addePointRequest  - string representing the ADDE request
+   * @throws VisADException  bad request, no data available, VisAD error
+   * @see #getData()
+   */
+  public PointDataAdapter(String addePointRequest)
+      throws VisADException
+  {
+      this(addePointRequest, true);
+  }
+
+  /**
+   * Construct a PointDataAdapter using the adde request passed as a string.
+   * This will take the data returned from the request and turn it into
+   * VisAD Data objects that can be returned by the getData() call.
+   *
+   * @param  addePointRequest  - string representing the ADDE request
+   * @param  useAliases        - for quantities like Latitude, Longitude,etc
+   *                             alias the RealTypes to the original McIDAS
+   *                             variable name.
+   * @throws VisADException  bad request, no data available, VisAD error
+   * @see #getData()
+   */
+  public PointDataAdapter(String addePointRequest, boolean useAliases)
+      throws VisADException
+  {
+      this(addePointRequest, useAliases, false);
+  }
+
+  /**
+   * Construct a PointDataAdapter using the adde request passed as a string.
+   * This will take the data returned from the request and turn it into
+   * VisAD Data objects that can be returned by the getData() call.
+   *
+   * @param  addePointRequest  - string representing the ADDE request
+   * @param  useAliases        - for quantities like Latitude, Longitude,etc
+   *                             alias the RealTypes to the original McIDAS
+   *                             variable name.
+   * @param  makeUniqueNames   - if true, make unique names to avoid null Types
+   * @throws VisADException  bad request, no data available, VisAD error
+   * @see #getData()
+   */
+  public PointDataAdapter(String addePointRequest, boolean useAliases, boolean makeUniqueNames)
+      throws VisADException
+  {
+    try
+    {
+      reader = new AddePointDataReader(addePointRequest);
+      debug = addePointRequest.indexOf("debug=true") > 0;
+      this.useAliases = useAliases;
+      this.makeUniqueNames = makeUniqueNames;
+    }
+    catch (AddeException excp)
+    {
+      throw new VisADException("Problem accessing data");
+    }
+    makeField();
+  }
+
+  // out of this will either come a FieldImpl, a ObservationDBImpl,
+  // or a StationObDBImpl
+  private void makeField()
+      throws VisADException
+  {
+    // First, let's make a generic FieldImpl from the data
+    // the structure will be index -> parameter tuple
+    // get all the stuff from the reader
+    int[][] data;
+    String[] units;
+    String[] params;
+    Unit[] defaultUnits;
+    int[] scalingFactors;
+    try
+    {
+      data = reader.getData(reader.OB_ORDER);
+      units = reader.getUnits();
+      params = reader.getParams();
+      scalingFactors = reader.getScales();
+    }
+    catch (AddeException ae)
+    {
+      throw new VisADException("Error retrieving data info");
+    }
+             
+    //int numObs = data[0].length;
+    int numObs = data.length;
+    if (numObs == 0)
+        throw new VisADException("No data available");
+    if (debug) System.out.println("Number of observations = " + numObs);
+
+    RealType domainType = RealType.getRealType("index");
+    Integer1DSet domain = new Integer1DSet(domainType, numObs);
+      
+    // now make range (Tuple) type
+    MetUnits unitTranslator = new MetUnits();
+    int numParams = params.length;
+    if (debug) System.out.println("Number of parameters = " + numParams);
+    ScalarType[] types = new ScalarType[numParams];
+    defaultUnits = new Unit[numParams];
+    Vector<Unit> usedUnits = new Vector<Unit>();
+    boolean noText = true;
+    int numDouble = 0;
+    int numString = 0;
+    List<RealType> realTypes = new ArrayList<RealType>();
+    List<TextType> textTypes = new ArrayList<TextType>();
+    for (int i = 0; i < numParams; i++)
+    {
+      // get the name
+      String name = params[i];
+
+      if (units[i].equalsIgnoreCase("CHAR"))
+      {
+        noText = false;
+        numString++;
+        if (debug) {
+          System.out.println(params[i] + " has units of CHAR");
+        }
+        TextType textType = TextType.getTextType(params[i]);
+        if (textType == null && makeUniqueNames) { // might be a RealType name
+          textType = TextType.getTextType(params[i]+TEXT_EXT);
+        }
+        if (textType == null) {
+          throw new VisADException("can't create TextType for " + params[i]);
+        }
+        textTypes.add(textType);
+        types[i] = textType;
+        defaultUnits[i] = null;
+      } 
+      else
+      {
+        // make the unit
+        Unit unit = null;
+        try
+        {
+           unit = 
+               (!name.equalsIgnoreCase("LON") )
+                   ? Parser.parse(unitTranslator.makeSymbol(units[i]))
+                   : Parser.parse("degrees_west");  // fix McIDAS conv.
+        }
+        catch (NoSuchUnitException ne) {
+           if (debug) 
+             System.out.println("Unknown unit: " + units[i] + " for " + name);
+           unit = null;
+        }
+        catch (ParseException pe) { unit = null;}
+        defaultUnits[i] = unit;
+
+        if (debug) {
+          System.out.println(params[i] + " has units " + unit);
+          System.out.println("scaling factor = " + scalingFactors[i]);
+        }
+        numDouble++;
+        types[i] = getQuantity(params[i], unit);
+        realTypes.add((RealType) types[i]);
+      }
+    }
+
+    TupleType rangeType;
+    if (noText)  // all Reals
+    {
+      RealType[] newTypes = new RealType[types.length];
+      for (int i = 0; i < types.length; i++) {
+        newTypes[i] = (RealType) types[i];
+      }
+      rangeType = new RealTupleType(newTypes);
+    }
+    else // all Texts or mixture of Text and Reals
+    {
+      rangeType = DoubleStringTuple.makeTupleType(realTypes, textTypes);
+    }
+
+    // make the field
+    FunctionType functionType = new FunctionType(domainType, rangeType);
+    field = new FieldImpl(functionType, domain);
+
+
+    if (debug) System.out.println("filling in data" );
+    long millis = System.currentTimeMillis();
+    // now, fill in the data
+    Scalar[]   firstTuple   = null;   // use this for saving memory/time
+    Unit[] actualUnits = null;
+    Real[] protos = (numDouble > 0) ? new Real[numDouble] : null;
+    for (int i = 0; i < numObs; i++)
+    {
+      double[] values = new double[numDouble];
+      String[] strings = new String[numString];
+      int stringIdx = 0;
+      int doubleIdx = 0;
+      for (int j = 0; j < numParams; j++)
+      {
+        if (types[j] instanceof TextType) {
+            String text = McIDASUtil.intBitsToString(data[i][j]);
+            strings[stringIdx++] = text;
+        } 
+        else
+        {
+            double value =
+                data[i][j] == McIDASUtil.MCMISSING
+                  ? Double.NaN
+                  : data[i][j]/Math.pow(10.0, (double) scalingFactors[j] );
+            values[doubleIdx] = value;
+            if (firstTuple == null) { // create the prototypes
+              try
+              {
+                protos[doubleIdx] =
+                  new Real(
+                      (RealType) types[j], value, defaultUnits[j]);
+              } catch (VisADException excp) {  // units problem
+                protos[doubleIdx] = new Real((RealType) types[j], value);
+  
+              }
+              usedUnits.add(((Real) protos[doubleIdx]).getUnit());
+            } 
+            doubleIdx++;
+        }
+      }
+      if (actualUnits == null && !usedUnits.isEmpty()) {
+        actualUnits = new Unit[usedUnits.size()];
+        for (int k = 0; k < usedUnits.size(); k++) {
+          actualUnits[k] = (Unit) usedUnits.get(k);
+        }
+      }
+      try
+      {
+        Data sample = 
+          (noText == true)
+             ? new DoubleTuple(
+                 (RealTupleType)rangeType, protos, values, actualUnits)
+             : new DoubleStringTuple(
+                 rangeType, protos, values, strings, actualUnits);
+
+        field.setSample(i, sample, false, (i==0)); // don't make copy, don't 
+                                                   // check type after first
+      }
+      catch (VisADException e) {e.printStackTrace();} 
+      catch (java.rmi.RemoteException e) {;}
+      if (firstTuple == null) 
+      {
+        firstTuple = protos;
+      }
+    }
+    if (debug) {
+      System.out.println("data fill took " + 
+        (System.currentTimeMillis() - millis) + " ms");
+    }
+  }
+
+  /**
+   * Get the VisAD Data object that represents the output from the
+   * request.  
+   *
+   * @return  requested data.  The format is a FieldImpl of
+   *          (obnum -> (tuple of parameters)
+   */
+  public DataImpl getData()
+  {
+    return field;
+  }
+
+  /**  
+   * test with 'java visad.data.mcidas.PointDataAdapter args' 
+   * @param args ADDE point data request
+   */
+  public static void main(String[] args)
+      throws Exception
+  {
+    if (args.length == 0) 
+    {
+      System.out.println("You must specify an ADDE Point Data URL");
+      System.exit(-1);
+    }
+    try
+    {
+      PointDataAdapter pda = new PointDataAdapter(args[0]);
+      Field data = (Field) pda.getData();
+      //System.out.println(data.getType());
+      visad.python.JPythonMethods.dumpTypes(data);
+      /*
+      int length = data.getDomainSet().getLength() - 1;
+      System.out.println(
+              "Sample "+ length + " = " + data.getSample(length));
+      */
+    }
+    catch (VisADException ve)
+    {
+      System.out.println("Error reading data");
+    }
+  }
+
+  /**
+   * First cut at a standard quantities database.
+   */
+  private RealType getQuantity(String name, Unit unit) 
+    throws VisADException
+  {
+    RealType type = null;
+    if (name.equalsIgnoreCase("lat")) {
+      type = RealType.Latitude;
+    } else if (name.equalsIgnoreCase("lon")) {
+      type = RealType.Longitude;
+    //} else if (name.equalsIgnoreCase("z") ||
+    //           name.equalsIgnoreCase("zs") ) {
+    } else if (name.equalsIgnoreCase("zs")) {
+      type = RealType.Altitude;
+    } else if (name.equalsIgnoreCase("z") && useAliases) {
+      type = RealType.Altitude;
+    } else {
+      type = RealType.getRealType(name, unit);
+      if (type == null) {
+        //System.err.println("Problem creating RealType with name " +
+        //                name + " and unit " + unit);
+        if (makeUniqueNames) {
+           type = DataUtility.getUniqueRealType(name, unit);
+        } else {
+           type = RealType.getRealTypeByName(name);
+        }
+        if (type == null) {  // Still a problem
+           throw new VisADException(
+              "getQuantity(): Couldn't create RealType for " + name);
+        }
+        //System.err.println("Using RealType with name " + name);
+      }
+    }
+    if (useAliases) {
+      if (RealType.getRealTypeByName(name) == null) {
+        type.alias(name);
+      } else if (!RealType.getRealTypeByName(name).equals(type)) { // alias used
+          throw new VisADException(
+            "getQuanity(): Two different variables can't have the same alias");
+      }
+    }
+    return type;
+  }
+
+}
diff --git a/visad/data/mcidas/PointForm.java b/visad/data/mcidas/PointForm.java
new file mode 100644
index 0000000..790bfd5
--- /dev/null
+++ b/visad/data/mcidas/PointForm.java
@@ -0,0 +1,136 @@
+
+//
+// PointForm.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.mcidas;
+
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.UnimplementedException;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormNode;
+import visad.data.FormFileInformer;
+
+/** to allow determination of whether a data request is for
+  * McIDAS ADDE for point type data
+  *
+  */
+public class PointForm extends Form implements FormFileInformer {
+
+  private PointDataAdapter pa;
+
+  public PointForm() {
+    super("PointForm");
+  }
+
+  /** determine the file type by name. Only ADDE requests are honored now
+    *
+    * @param name is the filename in question
+    */
+  public boolean isThisType(String name) {
+    return ( name.startsWith("adde://") && name.indexOf("/point") > 0 );
+  }
+
+  /** there is no unique way to identify these data by
+    * examning the contents of the first block of data values
+    *
+    * @param block is an array of ? length from the beginning
+    * of the file in question.
+    *
+    */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /** return a list of suffixes associated with this file type
+  *
+  */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { " " };
+    return suff;
+  }
+
+  /** save the file back to disk
+  *
+  * This has not been implemented yet
+  *
+  */
+  public synchronized void save(String id, Data data, boolean replace)
+	throws  BadFormException, IOException, RemoteException, VisADException
+  {
+    throw new UnimplementedException("Can't yet save McIDAS Point objects");
+  }
+
+  /** This has not been implemented
+  *
+  */
+  public synchronized void add(String id, Data data, boolean replace)
+	throws BadFormException {
+
+    throw new RuntimeException("Can't yet add McIDAS Point objects");
+  }
+
+  /** read the point file from a URL,  and return the point data
+    * as a DataImpl object (a FlatField).
+    *
+    * @param url is the fully-formed URL
+    *
+    */
+  public synchronized DataImpl open(URL url)
+	throws BadFormException, VisADException, IOException {
+
+    // PontDataAdapter constructor decides if argument is a file or a URL
+    pa = new PointDataAdapter(url.toString());
+    return  pa.getData();
+  }
+
+  /** cannot read the point file locally.
+    *
+    * @param filename is the local filename
+    *
+    */
+  public synchronized DataImpl open(String filename)
+	throws  BadFormException, IOException, RemoteException, VisADException {
+    // not available
+    throw new UnimplementedException("Cannot read point data from local files.");
+
+  }
+  /** not implemented yet
+  *
+  */
+  public synchronized FormNode getForms(Data data) {
+
+    throw new RuntimeException("Can't yet get McIDAS Point forms");
+  }
+}
diff --git a/visad/data/mcidas/README.mcidas b/visad/data/mcidas/README.mcidas
new file mode 100644
index 0000000..30f51ab
--- /dev/null
+++ b/visad/data/mcidas/README.mcidas
@@ -0,0 +1,56 @@
+(update 7.13.00)
+
+The classes in this directory and in the edu.wisc.ssec.mcidas and
+edu.wisc.ssec.mcidas.adde packages will support the import of data in
+some of the the McIDAS (http://www.ssec.wisc.edu/mcidas) formats.  
+You will find some code there to handle McIDAS GRID and MD files as
+well.
+
+Also, please look at the visad.examples package for more examples of
+using the ADDE protocol for images.
+
+Initially, the AreaAdapter class (and AreaForm) will read the McIDAS image
+file format known as AREAs.  This files may contain single or
+multi-banded data from any of several sources.
+
+The AREA format allows for optional navigation and calibration
+data to be included.  These data are supposed to then be used
+with runtime load modules to provide for the desired
+transformations.  
+
+In the VisAD implementation, we intend to support navigation by
+provided particular CoordinateSystem classes.  At this time, the
+AREACoordinateSystem only supports GVAR, MSAT and MOLL navigations.  
+It provides a reference coordinate system of Latitude and Longitude, 
+and will transform to/from that from image pixel locations.
+
+The McIDAS base map file format (OUTL) provides latitude and
+longitude data needed to construct basemap overlays for images
+and backgrounds for data plotting and contour drawing.  The
+BaseMapAdapter class is provided here to allow these OUTL files
+to be used.  We will probably make only a limitied number of
+these files available, and instead concentrate efforts for map
+drawing in the world of "shapefiles".
+
+In order to test the classes in the visad.data.mcidas directory,
+you should acquire an AREA file and probably a basemap file. I've
+provided samples at:
+
+ftp://www.ssec.wisc.edu/pub/visad-2.0/AREA0007
+ftp://www.ssec.wisc.edu/pub/visad-2.0/OUTLUSAM
+
+FTP these files (using binary mode) into the visad/data/mcidas
+directory, and then run:  java TestArea 
+
+Alternatively, you may use them directly over the Inet by:
+
+java TestArea ftp://www.ssec.wisc.edu/pub/visad-2.0/AREA0007 1 ftp://www.ssec.wisc.edu/pub/visad-2.0/OUTLUSAM
+
+This TestArea includes both the display of the image from
+AREA0007 and the overlay of the basemap from OUTLUSAM, correctly
+navigated into the GVAR coordinate system.
+
+Please send me your questions and comments.
+
+Tom Whittaker, SSEC
+tomw at ssec.wisc.edu
diff --git a/visad/data/mcidas/TestArea.java b/visad/data/mcidas/TestArea.java
new file mode 100644
index 0000000..2fdc2f3
--- /dev/null
+++ b/visad/data/mcidas/TestArea.java
@@ -0,0 +1,347 @@
+//
+// TestArea.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.data.mcidas;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.net.URL;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import visad.ColorControl;
+import visad.ConstantMap;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.DataRenderer;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Linear2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.java2d.DefaultRendererJ2D;
+import visad.java2d.DisplayImplJ2D;
+import visad.java3d.DefaultRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+
+/** This will test the Area File Adapter and Base Map (McIDAS
+ *  formats) Adapter for VisAD.  You need a sample AREA file,
+ *  along with a McIDAS "OUTL" format map file.  You may
+ *  get these from ftp://allegro.ssec.wisc.edu/visad/
+ *
+ *  At this point, only GVAR, MSAT and MOLL navigation is supported, and no
+ *  work has been done on calibration yet.
+ *
+ *  There is quite a bit of extra printout that is usually
+ *  commented out, but may provide some insight into the
+ *  Math and Data structures employed.
+ *
+ *  @author Tom Whittaker (SSEC)
+ */
+public class TestArea {
+
+  static boolean use2D = false;
+  static String imageSource = "AREA0007";
+  static String band = "1";
+  static String mapfile = "OUTLUSAM";
+
+  private static boolean getOptions(String[] args)
+  {
+    boolean defaultFile = true;
+    boolean defaultBand = true;
+    boolean defaultMap = true;
+
+    int keyNum = 0;
+    boolean gotValidOptions = true;
+    for (int i = 0; i < args.length; i++) {
+      if (args[i].charAt(0) == '-') {
+
+    // handle options
+    switch (args[i].charAt(1)) {
+    case '2':
+      use2D = true;
+      break;
+    case '3':
+      use2D = false;
+      break;
+    case 'f':
+      i++;
+      imageSource = args[i];
+      defaultFile = false;
+      break;
+    case 'b':
+      i++;
+      band = args[i];
+      defaultBand = false;
+      break;
+    case 'm':
+      i++;
+      mapfile = args[i];
+      defaultMap = false;
+      break;
+    default:
+      System.err.println("Unknown option \"" + args[i] + "\"");
+      gotValidOptions = true;
+      break;
+    }
+      } else {
+
+    // handle keywords (AKA positional parameters)
+    switch (keyNum) {
+    case 0:
+      imageSource = args[i];
+      defaultFile = false;
+      break;
+    case 1:
+      band = args[i];
+      defaultBand = false;
+      break;
+    case 2:
+      mapfile = args[i];
+      defaultMap = false;
+          break;
+        default:
+          System.err.println("Unknown keyword \"" + args[i] + "\"");
+          gotValidOptions = true;
+          break;
+        }
+        keyNum++;
+      }
+    }
+
+    if (!gotValidOptions) {
+      System.err.print("Usage: java TestArea ");
+      System.err.print(" <AREAfilename>");
+      System.err.print(" <band#>");
+      System.err.print(" <mapfilename>");
+      System.err.println("");
+
+      System.err.print("  or : java TestArea");
+      System.err.print(" [-2(D)|-3(D)]");
+      System.err.print(" [-f AREAfilename]");
+      System.err.print(" [-b band#]");
+      System.err.print(" [-m mapfilename]");
+      System.err.println("");
+
+      System.err.println("\t(filenames may also be URLs)");
+    }
+
+    if (defaultFile || defaultBand || defaultMap) {
+      boolean needComma = false;
+
+      System.out.print("Using default");
+      if (defaultFile) {
+        System.out.print((needComma ? "," : "") + " file " + imageSource);
+      }
+      if (defaultBand) {
+        System.out.print((needComma ? "," : "") + " band " + band);
+      }
+      if (defaultMap) {
+        System.out.print((needComma ? "," : "") + " map " + mapfile);
+      }
+
+      System.out.println("");
+    }
+
+    return gotValidOptions;
+  }
+
+  public static void main(String args[]) {
+
+    if (!getOptions(args)) {
+      System.exit(1);
+      return;
+    }
+
+    FlatField imaget = null;
+
+      System.out.println("Reading AREA file \"" + imageSource+ "\"");
+
+      AreaAdapter aa = null;
+
+      try {
+        aa = new AreaAdapter(imageSource);
+      } catch (Exception e) {
+        System.err.println("Caught IOException for \"" + imageSource + "\": " +
+                           e.getMessage());
+        System.exit(1);
+      }
+
+      imaget = aa.getData();
+      if (imaget == null) {
+        System.out.println("\tNULL FlatField!");
+      } else {
+        System.out.println("\t" + imaget.getType());
+      }
+
+      int bandNumber = Integer.parseInt(band.trim());
+
+    try {
+
+    //System.out.println("aa="+aa);
+    System.out.println("DateTime= "+aa.getNominalTime());
+    System.out.println("imaget.getDomainSet()="+imaget.getDomainSet() );
+    System.out.println("imaget.getDomain.getType="+imaget.getDomainSet().getType() );
+ /*  This is diagnostic output only...
+    System.out.println("imaget.getDomainSet.getDimension="+
+         imaget.getDomainSet().getDimension() );
+    System.out.println("imaget.getDomainSet.getCoordinateSystem="+
+         imaget.getDomainSet().getCoordinateSystem() );
+    System.out.println("imaget.getDomainSet.getLength="+
+         imaget.getDomainSet().getLength() );
+    System.out.println("imaget.getDomainSet.getX.getFirst="+
+        ( (Linear2DSet)imaget.getDomainSet()).getX().getFirst() );
+    System.out.println("imaget.getDomainSet.getX.getLast="+
+        ( (Linear2DSet)imaget.getDomainSet()).getX().getLast() );
+
+    System.out.println("imaget.getDomainSet.getY.getFirst="+
+        ( (Linear2DSet)imaget.getDomainSet()).getY().getFirst() );
+    System.out.println("imaget.getDomainSet.getY.getLast="+
+        ( (Linear2DSet)imaget.getDomainSet()).getY().getLast() );
+    FunctionType ft = (FunctionType) imaget.getType();
+    System.out.println("ft="+ft);
+    RealTupleType idom = ft.getDomain();
+    System.out.println("idom="+idom);
+    CoordinateSystem cs = idom.getCoordinateSystem();
+    System.out.println("cs="+cs);
+    Set ffds= imaget.getDomainSet();
+    System.out.println("image.getDomainSet()="+ffds );
+    CoordinateSystem dics = aa.getCoordinateSystem();
+    System.out.println("dics="+dics);
+
+ */
+
+      // In order to test AreaForm, use...but without a basemap!:
+      // AreaForm af = new AreaForm();
+      // imaget = (FlatField) af.open("AREA0007");
+
+    int[] dim = aa.getDimensions();
+    int numEles = dim[1];
+    int numLines = dim[2];
+
+      System.out.println("Creating basemap overlay from \"" + mapfile+ "\"");
+
+      BaseMapAdapter bma;
+
+      if (mapfile.indexOf("://") > 0) {
+        bma = new BaseMapAdapter(new URL(mapfile) );
+      } else {
+        bma = new BaseMapAdapter(mapfile);
+      }
+
+      bma.setDomainSet( (Linear2DSet) imaget.getDomainSet());
+
+      // other possible form of getting info into BaseMapAdapter:
+      // bma.setCoordinateSystem(dics, numEles, numLines, idom);
+
+      DataReference maplines_ref =  new DataReferenceImpl("MapLines");
+      maplines_ref.setData(bma.getData() );
+
+      DisplayImpl display;
+
+      if (use2D) {
+        display = new DisplayImplJ2D("display1");
+      } else {
+        display = new DisplayImplJ3D("display1");
+      }
+
+
+      System.out.println("Starting to render display");
+      FunctionType ftype = (FunctionType) imaget.getType();
+      RealTupleType dtype = ftype.getDomain();
+      RealTupleType rtype = (RealTupleType)ftype.getRange();
+
+      ScalarMap xaxis = new ScalarMap( (RealType) dtype.getComponent(0),
+              Display.XAxis);
+      xaxis.setRange( 0.d, (double) numEles);
+      ScalarMap yaxis = new ScalarMap( (RealType) dtype.getComponent(1),
+              Display.YAxis);
+      yaxis.setRange( 0.d, (double) numLines);
+
+      display.addMap(xaxis);
+      display.addMap(yaxis);
+
+      // select which band to show...
+      ScalarMap rgbMap =
+          new ScalarMap( (RealType) rtype.getComponent(bandNumber-1),
+              Display.RGB);
+      display.addMap(rgbMap);
+      ColorControl cc = (ColorControl) rgbMap.getControl();
+      cc.initGreyWedge();
+
+      DataReferenceImpl ref_image = new DataReferenceImpl("ref_image");
+      ref_image.setData(imaget);
+      display.addReference(ref_image,null);
+
+      // display.addReference(maplines_ref);
+
+      ConstantMap[] redMap;
+      if (use2D) {
+
+        redMap = new ConstantMap[3];
+        redMap[0] = new ConstantMap(0., Display.Blue);
+        redMap[1] = new ConstantMap(1., Display.Red);
+        redMap[2] = new ConstantMap(0., Display.Green);
+
+      } else {
+        redMap = new ConstantMap[4];
+        redMap[0] = new ConstantMap(0., Display.Blue);
+        redMap[1] = new ConstantMap(1., Display.Red);
+        redMap[2] = new ConstantMap(0., Display.Green);
+        redMap[3] = new ConstantMap(.01, Display.ZAxis);
+      }
+
+      DataRenderer drend;
+      if (use2D) {
+        drend = new DefaultRendererJ2D();
+      } else {
+        drend = new DefaultRendererJ3D();
+      }
+
+      display.addReferences( drend, maplines_ref, redMap);
+
+      JFrame jframe = new JFrame("McIDAS AREA in Java 3D");
+      jframe.addWindowListener(
+        new WindowAdapter() {
+          public void windowClosing(WindowEvent e) {System.exit(0);}
+        }
+      );
+
+      jframe.setContentPane( (JPanel) display.getComponent() );
+      jframe.setSize(numEles,numLines);
+      jframe.setVisible(true);
+    } catch (Exception xxx) {System.out.println("Ex: "+xxx);System.exit(1); }
+
+    while (true) {
+      try {
+        Thread.sleep(5000);
+      } catch (Exception e) {System.exit(0);}
+    }
+
+}
+
+}
diff --git a/visad/data/mcidas/package.html b/visad/data/mcidas/package.html
new file mode 100644
index 0000000..72e9838
--- /dev/null
+++ b/visad/data/mcidas/package.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides for importing McIDAS AREA files
+and McIDAS base map (OUTL) files into VisAD.
+
+</body>
+</html>
+
diff --git a/visad/data/netcdf/FileDialogPanel.java b/visad/data/netcdf/FileDialogPanel.java
new file mode 100644
index 0000000..aeea2d8
--- /dev/null
+++ b/visad/data/netcdf/FileDialogPanel.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: FileDialogPanel.java,v 1.2 2001-11-27 22:29:31 dglo Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.awt.BorderLayout;
+import java.awt.Button;
+import java.awt.Component;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Label;
+import java.awt.List;
+import java.awt.Panel;
+import java.awt.TextField;
+
+
+/**
+ * A non-Window equivalent of FileDialog.
+ */
+public class
+FileDialogPanel
+    extends	Panel
+{
+    /**
+     * The pathname.
+     */
+    private String	pathname;
+
+
+    /**
+     * Construct.
+     */
+    public
+    FileDialogPanel(String filter, String initialPathname)
+    {
+	super(new GridBagLayout());
+
+	pathname = initialPathname;
+
+	Component	folderComponent = newFolderComponent();
+	Component	filterComponent = newFilterComponent(filter);
+	Component	filesListComponent = newFilesListComponent();
+	Component	foldersListComponent = newFoldersListComponent();
+	Component	fileComponent = newFileComponent(initialPathname);
+	Component	buttonsComponent = newButtonsComponent();
+
+	addComponent(folderComponent,      0, 0, GridBagConstraints.HORIZONTAL);
+	addComponent(filterComponent,      0, 1, GridBagConstraints.HORIZONTAL);
+	addComponent(filesListComponent,   1, 1, GridBagConstraints.BOTH);
+	addComponent(foldersListComponent, 0, 2, GridBagConstraints.BOTH);
+	addComponent(fileComponent,        0, 3, GridBagConstraints.HORIZONTAL);
+	addComponent(buttonsComponent,     0, 4, GridBagConstraints.HORIZONTAL);
+
+	validate();
+	setSize(getPreferredSize());
+    }
+
+
+    /**
+     * Add a component.
+     */
+    protected void
+    addComponent(Component component, int gridx, int gridy, int fill)
+    {
+	GridBagConstraints	gbc = new GridBagConstraints();
+
+	gbc.anchor= GridBagConstraints.NORTHWEST;
+	gbc.gridx = gridx;
+	gbc.gridy = gridy;
+	gbc.fill= fill;
+
+	((GridBagLayout)getLayout()).setConstraints(component, gbc);
+	add(component);
+    }
+
+
+    /**
+     * Return a folder component.
+     */
+    protected Component
+    newFolderComponent()
+    {
+	return newTextComponent("Enter path or folder name:",
+	    System.getProperty("user.dir"));
+    }
+
+
+    /**
+     * Return a filter component.
+     */
+    protected Component
+    newFilterComponent(String pattern)
+    {
+	return newTextComponent("Filter", pattern);
+    }
+
+
+    /**
+     * Return a files list component.
+     */
+    protected Component
+    newFilesListComponent()
+    {
+	return newListComponent("Files", new String[] {"file1", "file2"});
+    }
+
+
+    /**
+     * Return a folders list component.
+     */
+    protected Component
+    newFoldersListComponent()
+    {
+	return newListComponent("Folders", new String[] {"folder1", "folder2"});
+    }
+
+
+    /**
+     * Return a file component.
+     */
+    protected Component
+    newFileComponent(String initialPathname)
+    {
+	return newTextComponent("Enter file name:", initialPathname);
+    }
+
+
+    /**
+     * Return a buttons component.
+     */
+    protected Component
+    newButtonsComponent()
+    {
+	Panel	panel = new Panel(new GridBagLayout());
+
+	addButton(panel, new Button("Update"));
+	addButton(panel, new Button("Reset"));
+
+	return panel;
+    }
+
+
+    /**
+     * Add a button to a panel.
+     */
+    protected void
+    addButton(Panel panel, Button button)
+    {
+	GridBagConstraints	gbc = new GridBagConstraints();
+
+	((GridBagLayout)panel.getLayout()).setConstraints(button, gbc);
+	panel.add(button);
+    }
+
+
+    /**
+     * Return a list component.
+     */
+    protected Component
+    newListComponent(String title, String[] items)
+    {
+	List	list = new List(items.length);
+
+	for (int i = 0; i < items.length; ++i)
+	    list.add(items[i]);
+
+	return newLabeledComponent(title, list);
+    }
+
+
+    /**
+     * Return a text component (label and text field).
+     */
+    protected Component
+    newTextComponent(String title, String initialText)
+    {
+	return newLabeledComponent(title, new TextField(initialText, 20));
+    }
+
+
+    /**
+     * Return a labeled component.
+     */
+    protected Component
+    newLabeledComponent(String title, Component component)
+    {
+	BorderLayout	lm = new BorderLayout();
+	Panel		panel = new Panel(lm);
+	Label		label = new Label(title, Label.LEFT);
+
+	lm.addLayoutComponent(label, BorderLayout.NORTH);
+	panel.add(label);
+
+	lm.addLayoutComponent(component, BorderLayout.SOUTH);
+	panel.add(component);
+
+	return panel;
+    }
+
+
+    /**
+     * Gets the pathname.
+     */
+    public String
+    getFile()
+    {
+	return pathname;
+    }
+
+
+    /**
+     * Sets the pathname.
+     */
+    public void
+    setFile(String pathname)
+    {
+	this.pathname = pathname;
+    }
+
+
+    /**
+     * Test this class.
+     */
+    public static void main(String[] args)
+    {
+	Frame		frame = new Frame("FileDialogPanel Test");
+	FileDialogPanel	fileDialog = new FileDialogPanel("*.*", "dummy.ext");
+
+	frame.add(fileDialog);
+
+	frame.show();
+    }
+}
diff --git a/visad/data/netcdf/InputNetcdf.java b/visad/data/netcdf/InputNetcdf.java
new file mode 100644
index 0000000..93d0fbc
--- /dev/null
+++ b/visad/data/netcdf/InputNetcdf.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: InputNetcdf.java,v 1.3 2002-09-20 18:16:33 steve Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.Serializable;
+import visad.DataImpl;
+import visad.VisADException;
+import visad.data.BadFormException;
+
+
+/**
+ * A Java bean for importing a netCDF file.
+ */
+public class
+InputNetcdf
+    implements	Serializable
+{
+    /**
+     * The pathname property of the netCDF dataset.
+     */
+    private String			pathname;
+
+    /**
+     * The VisAD data object property.
+     */
+    private DataImpl			data;
+
+    /**
+     * The quantity database property.
+     */
+    private QuantityDB                  quantityDB;
+
+    /**
+     * Support for property changes.
+     */
+    private final PropertyChangeSupport	changes;
+
+
+    /**
+     * Construct.  The pathname and data object properties will be 
+     * <code>null</code>; the quantity database property will be {@link
+     * StandardQuantityDB}.
+     */
+    public
+    InputNetcdf()
+    {
+	pathname = null;
+	data = null;
+	changes = new PropertyChangeSupport(this);
+	quantityDB = StandardQuantityDB.instance();
+    }
+
+
+    /**
+     * Set the quantity database property.  The quantity database is used
+     * to transform the incoming netCDF variables into their canonical
+     * {@link visad.RealType}s.  If no transformation is desired, then use {@link
+     * QuantityDB#emptyDB}.  A {@link java.beans.PropertyChangeEvent} for <code>quantityDB
+     * </code> will be fired, if appropriate.  If the pathname property is
+     * non-<code>null</code>, then the netCDF database will be read and a {@link
+     * java.beans.PropertyChangeEvent} for the data property will be fired, if appropriate.
+     *
+     * @param db                    The new quantity database.
+     * @throws NullPointerException if the argument is <code>null</code>.
+     * @throws BadFormException     if the netCDF dataset doesn't have the 
+     *                              right form.
+     * @throws IOException          if an error occurs while reading the netCDF
+     *                              dataset.
+     * @throws VisADException       if a VisAD failure occurs.
+     */
+    public void
+    setQuantityDB(QuantityDB db)
+	throws BadFormException, IOException, VisADException
+    {
+	if (db == null)
+	    throw new NullPointerException();
+
+	QuantityDB oldDB;
+	DataImpl oldData;
+	String   name;
+
+	synchronized(this) {
+	    oldDB = quantityDB;
+	    oldData = data;
+	    name = pathname;
+	}
+
+	DataImpl newData = new Plain(db).open(name);
+
+	synchronized(this) {
+	    quantityDB = db;
+	    data = newData;
+	}
+
+        changes.firePropertyChange("quantityDB", oldDB, db);
+        changes.firePropertyChange("data", oldData, newData);
+    }
+
+
+    /**
+     * Sets the dataset name property.  If the name is <code>null</code>,
+     * then the data property will be set to <code>null</code>; otherwise,
+     * the dataset will be read. {@link java.beans.PropertyChangeEvent}s for the
+     * pathname and data properties will be fired when appropriate.
+     *
+     * @param name                  The new name of the dataset or 
+     *                              <code>null</code>.
+     */
+    public void
+    setPathname(String name)
+	throws IOException, VisADException, BadFormException
+    {
+        String   oldName;
+	DataImpl oldData;
+        DataImpl newData;
+
+        if (name == null) {
+            synchronized(this) {
+		oldName = pathname;
+		oldData = data;
+	    }
+	    newData = null;
+        }
+        else {
+            QuantityDB      db;
+
+            synchronized(this) {
+                db = quantityDB;
+                oldData = data;
+                oldName = pathname;
+            }
+
+            newData = new Plain(db).open(name);
+
+            synchronized(this) {
+                pathname = name;
+                data = newData;
+            }
+        }
+
+        changes.firePropertyChange("pathname", oldName, name);
+        changes.firePropertyChange("data", oldData, newData);
+    }
+
+
+    /**
+     * Returns the dataset pathname property.  Returns <code>null</code> if the
+     * property has no value.
+     */
+    public synchronized String
+    getPathname()
+    {
+	return pathname;
+    }
+
+
+    /**
+     * Returns the VisAD data object property.  Returns <code>null</code> if the
+     * property has no value.
+     */
+    public synchronized DataImpl
+    getData()
+    {
+	return data;
+    }
+
+
+    /**
+     * Add a property change listener.
+     */
+    public synchronized void
+    addPropertyChangeListener(PropertyChangeListener p)
+    {
+	changes.addPropertyChangeListener(p);
+    }
+
+
+    /**
+     * Remove a property change listener.
+     */
+    public synchronized void
+    removePropertyChangeListener(PropertyChangeListener p)
+    {
+	changes.removePropertyChangeListener(p);
+    }
+}
diff --git a/visad/data/netcdf/InputNetcdfPathnameEditor.java b/visad/data/netcdf/InputNetcdfPathnameEditor.java
new file mode 100644
index 0000000..a0925e1
--- /dev/null
+++ b/visad/data/netcdf/InputNetcdfPathnameEditor.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: InputNetcdfPathnameEditor.java,v 1.3 1998-06-29 19:47:12 visad Exp $
+ */
+
+package visad.data.netcdf;
+
+
+public class
+InputNetcdfPathnameEditor
+    extends	InputPathnameEditor
+{
+    /**
+     * Construct.
+     */
+    public
+    InputNetcdfPathnameEditor()
+    {
+	super("*.nc", "dummy.nc");
+    }
+}
diff --git a/visad/data/netcdf/InputPathnameEditor.java b/visad/data/netcdf/InputPathnameEditor.java
new file mode 100644
index 0000000..62f0622
--- /dev/null
+++ b/visad/data/netcdf/InputPathnameEditor.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: InputPathnameEditor.java,v 1.5 2001-11-27 22:29:31 dglo Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.awt.Component;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.beans.PropertyEditorSupport;
+
+
+/**
+ * A JavaBean property editor for input pathnames.
+ */
+public abstract class
+InputPathnameEditor
+    extends	PropertyEditorSupport
+{
+    private FileDialogPanel	fileDialog;
+
+    /**
+     * Construct.
+     */
+    public
+    InputPathnameEditor(String filterPattern, String initialPathname)
+    {
+	fileDialog = new FileDialogPanel(filterPattern, initialPathname);
+    }
+
+
+    /**
+     * Indicate support for a custom editor.
+     */
+    public boolean
+    supportsCustomEditor()
+    {
+	return true;
+    }
+
+
+    /**
+     * Indicate support for painting the property value.
+     */
+    public boolean
+    isPaintable()
+    {
+	return true;
+    }
+
+
+    /**
+     * Paint a representation of the pathname in the given box.
+     */
+    public void
+    paintValue(Graphics graphics, Rectangle box)
+    {
+	FontMetrics	fm = graphics.getFontMetrics();
+
+	/*
+	 * Make the position of the reference point in the box congruent
+	 * to the position of the reference point in the font (i.e. same
+	 * proportional position).
+	 */
+	graphics.drawString(getAsText(), box.x,
+	    box.y +
+	    Math.round(box.height*fm.getAscent()/(float)fm.getHeight()));
+    }
+
+
+    /**
+     * Get the property as a text string.
+     */
+    public String
+    getAsText()
+    {
+	return fileDialog.getFile();
+    }
+
+
+    /**
+     * Set the property given a text string.
+     */
+    public void
+    setAsText(String pathname)
+    {
+	fileDialog.setFile(pathname);
+    }
+
+
+    /**
+     * Return the custom editor.
+     */
+    public Component
+    getCustomEditor()
+    {
+	return fileDialog;
+    }
+
+
+    /**
+     * Set the object to be edited.
+     */
+    public void
+    setValue(Object value)
+    {
+	if (value instanceof String)
+	    setAsText((String)value);
+    }
+}
diff --git a/visad/data/netcdf/NOTEBOOK b/visad/data/netcdf/NOTEBOOK
new file mode 100644
index 0000000..3c8a2b1
--- /dev/null
+++ b/visad/data/netcdf/NOTEBOOK
@@ -0,0 +1,35 @@
+Quantity database API:
+
+    public QuantityDB QuantityDBManager.instance()
+
+    public void QuantityDBManager.setInstance(QuantityDB instance)
+
+    // public void QuantityDBManager.setFactory(QuantityDBFactory factory)
+
+    MetQuantityDB.initialize()
+    {
+	String[]		definitions =
+	    new String[]
+	    {
+		"DewPoint", "Cel",
+		"PotentialTemperature", "K",
+		"SaturationEquivalentPotentialTemperature", "K",
+		"SaturationMixingRatio", "g/kg",
+		"U", "m/s",
+		"V", "m/s",
+		"W", "m/s",
+		"VirtualTemperature", "K",
+		...
+	    };
+	String[]		aliases =
+	    new String[]
+	    {
+		"PressureReducedToMSL", "Pressure",
+		"Theta", "PotentialTemperature",
+		"ThetaES", "SaturationEquivalentPotentialTemperature",
+		"Rsat", "SaturationMixingRatio",
+		...
+	    };
+	QuantityDBManager.setInstance(
+	    QuantityDBManager.instance().add(definitions, aliases));
+    }
diff --git a/visad/data/netcdf/NetCDF.java b/visad/data/netcdf/NetCDF.java
new file mode 100644
index 0000000..724d228
--- /dev/null
+++ b/visad/data/netcdf/NetCDF.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: NetCDF.java,v 1.6 2001-11-27 22:29:32 dglo Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.io.IOException;
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormFileInformer;
+import visad.DataImpl;
+import visad.VisADException;
+
+
+/**
+ * The NetCDF class provides an abstract class for the family of netCDF
+ * data forms for files in a local directory.
+ */
+public abstract class
+NetCDF
+    extends	Form
+    implements	FormFileInformer
+{
+    private final static String		SUFFIX = "nc";
+    private final static String		PERIOD_SUFFIX = "." + SUFFIX;
+
+    /**
+     * Construct a netCDF data form.
+     *
+     * @param name	The name for the family of netCDF data forms.
+     */
+    public
+    NetCDF(String name)
+    {
+	super(name);
+    }
+
+
+    /**
+     * Open an existing file.
+     *
+     * @param path			The pathname of the file.
+     * @exception BadFormException	netCDF couldn't handle VisAD object.
+     * @exception VisADException	Couldn't create necessary VisAD object.
+     * @exception IOException		I/O error.
+     */
+    public abstract DataImpl
+    open(String path)
+	throws BadFormException, IOException, VisADException;
+
+/*
+ * FormFileInformer method implementations:
+ */
+
+    /**
+     * Indicates if a dataset specification is consistent with a netCDF dataset
+     * specification.
+     *
+     * @param spec		A dataset specification.  NB: Not a URL.
+     * @return			<code>true</code> if and only if the dataset
+     *				specification is consistent with a netCDF
+     *				dataset specification.
+     */
+    public boolean isThisType(String spec)
+    {
+	return spec.toLowerCase().endsWith(PERIOD_SUFFIX);
+    }
+
+    /**
+     * Indicates if a given block of bytes is the start of a netCDF dataset.
+     *
+     * @param block		A block of data.
+     * @return			True if and only if the given block of bytes is
+     *				the start of a netCDF dataset.
+     */
+    public boolean isThisType(byte[] block)
+    {
+	return block[0] == 'C' && block[1] == 'D' && block[2] == 'F';
+    }
+
+    /**
+     * Returns the path-component suffixes that identifies a dataset
+     * specification as being a netCDF dataset specification.  The suffixes
+     * don't have a leading period.  The returned array can be safely modified.
+     *
+     * @return			A freshly-allocated array with the relevant 
+     *				suffixes.
+     */
+    public String[] getDefaultSuffixes()
+    {
+	return new String[] {SUFFIX};
+    }
+}
diff --git a/visad/data/netcdf/NetcdfInBean.java b/visad/data/netcdf/NetcdfInBean.java
new file mode 100644
index 0000000..e097aee
--- /dev/null
+++ b/visad/data/netcdf/NetcdfInBean.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: NetcdfInBean.java,v 1.2 2001-11-27 22:29:32 dglo Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.io.IOException;
+import java.io.Serializable;
+import visad.DataImpl;
+import visad.VisADException;
+import visad.data.BadFormException;
+
+
+/**
+ * Adapt a netCDF input file to a Java bean.
+ */
+public class
+NetcdfInBean
+    implements	Serializable
+{
+    /**
+     * The pathname property of the netCDF dataset.
+     */
+    private String			pathname = null;
+
+    /**
+     * The VisAD data object.
+     */
+    private DataImpl			data = null;
+
+    /**
+     * Support for property changes.
+     */
+    private PropertyChangeSupport	changes =
+	new PropertyChangeSupport(this);
+
+
+    /**
+     * Set the dataset pathname property.
+     */
+    public synchronized void
+    setPathname(String pathname)
+	throws IOException, VisADException, BadFormException
+    {
+	String		oldPathname = this.pathname;
+	DataImpl	oldData = data;
+	Plain		plain = new Plain();
+
+	data = plain.open(pathname);
+	this.pathname = pathname;
+
+	changes.firePropertyChange("pathname", oldPathname, this.pathname);
+	changes.firePropertyChange("data", oldData, this.data);
+    }
+
+
+    /**
+     * Get the dataset pathname property.
+     */
+    public synchronized String
+    getPathname()
+    {
+	return pathname;
+    }
+
+
+    /**
+     * Get the VisAD data object property.
+     */
+    public synchronized DataImpl
+    getData()
+    {
+	return data;
+    }
+
+
+    /**
+     * Add a property change listener.
+     */
+    public synchronized void
+    addPropertyChangeListener(PropertyChangeListener p)
+    {
+	changes.addPropertyChangeListener(p);
+    }
+
+
+    /**
+     * Remove a property change listener.
+     */
+    public synchronized void
+    removePropertyChangeListener(PropertyChangeListener p)
+    {
+	changes.removePropertyChangeListener(p);
+    }
+}
diff --git a/visad/data/netcdf/Plain.java b/visad/data/netcdf/Plain.java
new file mode 100644
index 0000000..ba34acf
--- /dev/null
+++ b/visad/data/netcdf/Plain.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: Plain.java,v 1.28 2002-10-21 20:07:44 donm Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.rmi.RemoteException;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+import ucar.netcdf.Netcdf;
+import ucar.netcdf.NetcdfFile;
+import ucar.netcdf.Schema;
+import ucar.netcdf.Variable;
+import ucar.netcdf.VariableIterator;
+import visad.data.BadFormException;
+import visad.data.FormNode;
+import visad.data.netcdf.in.*;
+import visad.data.netcdf.out.VisADAdapter;
+import visad.Data;
+import visad.DataImpl;
+import visad.UnimplementedException;
+import visad.VisADException;
+
+
+/**
+ * A moderately stupid implementation of a netCDF data form for the
+ * storage of persistent data objects on local disk.
+ */
+public class
+Plain
+    extends NetCDF
+{
+    /**
+     * The quantity database to use for mapping netCDF variables to
+     * VisAD Quantity-s.
+     */
+    private final QuantityDB    quantityDB;
+
+    /**
+     * The flag for transforming netCDF char variables to Text
+     */
+    private final boolean    charToText;
+
+
+    /**
+     * Constructs a default, netCDF data form.
+     *
+     * @throws VisADException   Couldn't create necessary VisAD object
+     */
+    public
+    Plain()
+        throws VisADException
+    {
+        this(QuantityDBManager.instance(), false);
+    }
+
+    /**
+     * Constructs a netCDF data form that converts
+     *
+     * @param charToText                The char to Text flag
+     */
+    public
+    Plain(boolean charToText)
+    {
+        this(QuantityDBManager.instance(), charToText);
+    }
+
+    /**
+     * Constructs a netCDF data form that uses the given quantity database.
+     *
+     * @param db                        The quantity database.
+     */
+    public
+    Plain(QuantityDB db)
+    {
+        this(db, false);
+    }
+
+    /**
+     * Constructs a netCDF data form that uses the given quantity database,
+     * and the flag for converting char to Text.
+     *
+     * @param db                        The quantity database.
+     * @param charToText                The char to Text flag
+     */
+    public
+    Plain(QuantityDB db, boolean charToText)
+    {
+        super("Plain");
+        quantityDB = db;
+        this.charToText = charToText;
+    }
+
+
+    /**
+     * Save a VisAD data object in this form.
+     *
+     * @param path                      The pathname of the netCDF file to
+     *                                  be created.
+     * @param data                      The data to be saved.
+     * @param replace                   Whether to replace an existing file.
+     * @exception BadFormException      netCDF can't handle data object
+     * @exception VisADException        Couldn't create necessary VisAD object
+     * @exception IOException           I/O error.  File might already exist.
+     * @exception RemoteException       Remote execution error
+     * @exception UnimplementedException
+     *                                  Not yet!
+     */
+    public synchronized void
+    save(String path, Data data, boolean replace)
+        throws BadFormException, IOException, RemoteException, VisADException,
+            UnimplementedException
+    {
+        VisADAdapter    adapter = new VisADAdapter(data);
+        Schema          schema = new Schema(adapter);
+        NetcdfFile      file = new NetcdfFile(path, replace, /*fill=*/false,
+                                        schema);
+
+        try
+        {
+            VariableIterator    iter = file.iterator();
+
+            while (iter.hasNext())
+            {
+                Variable        outVar = iter.next();
+                Variable        inVar = adapter.get(outVar.getName());
+                int             rank = outVar.getRank();
+                int[]           origin = new int[rank];
+
+                for (int i = 0; i < rank; ++i)
+                    origin[i] = 0;
+
+                outVar.copyin(origin, inVar);
+            }
+        }
+        finally
+        {
+            file.close();
+        }
+    }
+
+
+    /**
+     * Add data to an existing data object.
+     *
+     * @param id        Pathname of the existing netCDF file.
+     * @param data      Data to be saved.
+     * @param replace   Whether or not to replace duplicate, existing data.
+     * @exception BadFormException
+     *                  netCDF can't handle data object.
+     */
+    public synchronized void
+    add(String id, Data data, boolean replace)
+        throws BadFormException
+    {
+    }
+
+
+    /**
+     * Returns a VisAD data object corresponding to a netCDF dataset.
+     *
+     * @param spec              Specification of the existing netCDF dataset.
+     * @return                  A VisAD data object corresponding to the netCDF 
+     *                          dataset.
+     * @throws BadFormException if the netCDF dataset cannot be adapted to a
+     *                          VisAD data object.
+     * @throws VisADException   if a problem occurs in core VisAD.  Probably a
+     *                          VisAD object couldn't be created.
+     * @throws IOException      if an I/O failure occurs.
+     * @see NetcdfAdapter#getData()
+     */
+    public synchronized DataImpl
+    open(String spec)
+        throws BadFormException, IOException, VisADException
+    {
+        return
+            new NetcdfAdapter(
+                new NetcdfFile(spec, /*readonly=*/true),
+                quantityDB,
+                charToText
+            ).getData();
+    }
+
+
+    /**
+     * Returns a VisAD data object corresponding to a netCDF dataset
+     * and imported according to a given strategy.  Among the
+     * pre-defined import strategies are {@link Strategy#DEFAULT},
+     * {@link Strategy#MERGED_FILE_FLAT_FIELDS}, {@link
+     * Strategy#UNMERGED_FILE_FLAT_FIELDS}, and {@link Strategy#IN_MEMORY}.
+     *
+     * @param spec              Specification of the existing netCDF dataset.
+     * @param strategy          The data-import strategy.
+     * @return                  A VisAD data object corresponding to the netCDF 
+     *                          dataset.
+     * @throws BadFormException if the netCDF dataset cannot be adapted to a
+     *                          VisAD data object.
+     * @throws VisADException   if a problem occurs in core VisAD.  Probably a
+     *                          VisAD object couldn't be created.
+     * @throws IOException      if an I/O failure occurs.
+     * @see NetcdfAdapter#getData(Strategy)
+     */
+    public synchronized DataImpl
+    open(String spec, Strategy strategy)
+        throws BadFormException, IOException, VisADException
+    {
+        return
+            new NetcdfAdapter(
+                new NetcdfFile(spec, /*readonly=*/true),
+                quantityDB,
+                charToText
+            ).getData(strategy);
+    }
+
+
+    /**
+     * Open an existing netCDF file and return a proxy for a VisAD data object.
+     *
+     * @param path      Pathname of the existing netCDF file.
+     * @return          A VisAD object corresponding to the netCDF dataset.
+     * @exception BadFormException
+     *                  The netCDF variable cannot be adapted to a VisAD API.
+     * @exception VisADException
+     *                  Problem in core VisAD.  Probably some VisAD object
+     *                  couldn't be created.
+     * @exception IOException
+     *                  Data access I/O failure.
+     */
+    public synchronized DataImpl
+    openProxy(String path)
+        throws BadFormException, IOException, VisADException
+    {
+        NetcdfFile      file = new NetcdfFile(path, /*readonly=*/true);
+
+        return new NetcdfAdapter(file, quantityDB, charToText).getProxy();
+    }
+
+
+    /**
+     * Returns a VisAD data object corresponding to a URL.  If the query
+     * component of the URL is <code>null</code>, then the returned object will
+     * contain all the variables in the netCDF dataset.  If the query component
+     * is non-<code>null</code>, then it must comprise a comma-separated list of
+     * netCDF variable names; the returned VisAD data object will contain only
+     * those variables in the netCDF dataset that are also named in the list
+     * (i.e. the intersection is returned).  Consequently, if the list is empty,
+     * then <code>null</code> is returned.  For example, this form:
+     * <code> open(new URL("file://myfile.nc?var_one,var_two")); </code>
+     * will return a VisAD data object consisting only of the
+     * netCDF variables <code>var_one</code> and <code>var_two</code>
+     * assuming they are in the file.
+     *
+     * @param url             The URL of the netCDF dataset.
+     * @return                A VisAD object corresponding to the netCDF datset
+     *                        or <code>null</code>.
+     * @throws FileNotFoundException
+     *                        if the URL specifies a file that doesn't exist.
+     * @throws IOException    if an I/O failure occurs.
+     * @throws VisADException if a necessary VisAD object couldn't be created.
+     */
+    public synchronized DataImpl
+    open (URL url)
+        throws FileNotFoundException, IOException, VisADException
+    {
+        /*
+         * URL.getQuery() isn't used to accomodate JDK 1.2.
+         */
+        String query;
+        {
+            String file = url.getFile();
+            int    i = file.indexOf('?');
+            if (i == -1)
+            {
+                query = null;
+            }
+            else
+            {
+                query =
+                    i == file.length() - 1
+                        ? ""
+                        : file.substring(i+1);
+            }
+        }
+        Set    names = new TreeSet();
+        if (query != null)
+        {
+            for (StringTokenizer st = new StringTokenizer(query, ",");
+                st.hasMoreTokens(); )
+            {
+                names.add(st.nextToken());
+            }
+        }
+        Netcdf netcdf = new NetcdfFile(url);
+        return
+            new NetcdfAdapter(
+                query == null
+                    ? netcdf
+                    : new VariableFilter(netcdf, names),
+                quantityDB, charToText).getData();
+    }
+
+
+    /**
+     * Return the data forms that are compatible with a data object.
+     *
+     * @param data      The VisAD data object to be examined.
+     * @return          <code>this</code> if <code>data</code> is compatible;
+     *                  otherwise, <code>null</code>.
+     * @exception VisADException        Problem with core VisAD.
+     * @exception IOException           Problem with local data access.
+     * @exception RemoteException       Problem with remote data access.
+     */
+    public synchronized FormNode
+    getForms(Data data)
+        throws VisADException, RemoteException, IOException
+    {
+        FormNode        form;
+
+        try
+        {
+            VisADAdapter        adapter = new VisADAdapter(data);
+            form = this;
+        }
+        catch (BadFormException e)
+        {
+            form = null;
+        }
+
+        return form;
+    }
+
+
+    /**
+     * Test this class.
+     *
+     * @param args              Runtime arguments.  Ignored.
+     * @exception Exception     Something went wrong.
+     */
+    public static void main(String[] args)
+        throws Exception
+    {
+        String  inPath;
+        String  outPath = "plain.nc";
+
+        if (args.length == 0)
+            inPath = "test.nc";
+        else
+            inPath = args[0];
+
+        Plain   plain = new Plain(args.length > 1);
+
+        System.out.println("Opening netCDF dataset \"" + inPath + "\"");
+
+        Data    data;
+        try
+        {
+            URL url = new URL(inPath);
+            data = plain.open(url);
+        }
+        catch (MalformedURLException e)
+        {
+            data = plain.open(inPath);
+        }
+
+        if (data == null)
+        {
+            System.out.println("No data");
+        }
+        else
+        {
+            // System.out.println("Data:\n" + data);
+            System.out.println("data.getType().toString():\n" +
+                data.getType());
+            System.out.println("Writing netCDF dataset \"" + outPath + "\"");
+            plain.save(outPath, data, /*replace=*/true);
+        }
+    }
+}
diff --git a/visad/data/netcdf/Quantity.java b/visad/data/netcdf/Quantity.java
new file mode 100644
index 0000000..e1f2b3d
--- /dev/null
+++ b/visad/data/netcdf/Quantity.java
@@ -0,0 +1,149 @@
+//
+// Quantity.java
+//
+
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: Quantity.java,v 1.13 2002-02-18 17:21:01 dglo Exp $
+ */
+
+package visad.data.netcdf;
+
+import visad.RealType;
+import visad.SimpleSet;
+import visad.TypeException;
+import visad.Unit;
+import visad.VisADException;
+import visad.data.units.ParseException;
+import visad.data.units.Parser;
+
+
+/**
+ * The following class represents a quantity -- usually a physical one
+ * such as energy, viscosity, or velocity (although an artificial one
+ * such as "money" should be possible).  It extends RealType by carrying
+ * around a preferred unit as a string because a Unit is useless
+ * for doing that.
+ *
+ * A Quantity is immutable.
+ *
+ * @author Steven R. Emmerson
+ */
+public class Quantity
+  extends	RealType
+{
+  /**
+   * The specification of the preferred unit of the quantity.
+   */
+  protected final String	unitSpec;
+
+
+  /**
+   * Constructs from a name, a unit specification, and a sample set.
+   *
+   * @param name		The name of the quantity (e.g. "length").
+   * @param unitSpec		The preferred unit for the quantity
+   *				(e.g. "feet").
+   * @param set			The default sample set for the quantity.  May be
+   *                            <code>null</code>.
+   * @throws ParseException	Couldn't decode unit specification.
+   * @throws VisADException	ScalarType of same name already exists.
+   */
+  public Quantity(String name, String unitSpec, SimpleSet set)
+    throws VisADException, ParseException
+  {
+    super(name, Parser.parse(unitSpec), set, 0, false);
+
+    this.unitSpec = unitSpec;
+  }
+
+
+  /**
+   * Constructs from a name and a unit specification.
+   *
+   * @param name		The name of the quantity (e.g. "length").
+   * @param unitSpec		The preferred unit for the quantity
+   *				(e.g. "feet").
+   * @exception VisADException	Can't create necessary VisAD object.
+   * @exception ParseException	Couldn't decode unit specification.
+   */
+  public Quantity(String name, String unitSpec)
+    throws VisADException, ParseException
+  {
+    this(name, unitSpec, (SimpleSet)null);
+  }
+
+
+  /**
+   * Constructs from a VisAD RealType.
+   *
+   * @param realType		A VisAD realType.
+   * @throws VisADException	Can't create necessary VisAD object.
+   */
+  Quantity(RealType realType)
+    throws VisADException
+  {
+    /*
+     * The following will create a duplicate RealType.
+     */
+    // TODO: eliminate use of trusted constructor (e.g. by merging
+    // Quantity and RealType).
+    super(realType.getName(), realType.getDefaultUnit(), true);
+
+    Unit	unit = realType.getDefaultUnit();
+    this.unitSpec = unit == null
+	? null
+	: unit.toString();
+  }
+
+
+  /**
+   * Return the default unit of this quantity as a string.
+   *
+   * @return		The default unit of this quantity or
+   *			<code>null</code> if no such unit.
+   */
+  public String getDefaultUnitString()
+  {
+    return unitSpec;
+  }
+
+  /** create a new Quantity, or return it if it already exists */
+  public static Quantity getQuantity(String name, String unitSpec,
+                                     SimpleSet set)
+    throws ParseException
+  {
+    try {
+      return new Quantity(name, unitSpec, set);
+    }
+    catch (TypeException e) {
+      return getQuantityByName(name);
+    }
+    catch (VisADException e) {
+      return null;
+    }
+  }
+
+  /** create a new Quantity, or return it if it already exists */
+  public static Quantity getQuantity(String name, String unitSpec)
+    throws ParseException
+  {
+    return getQuantity(name, unitSpec, null);
+  }
+
+  /** return any Quantity constructed in this JVM with name,
+      or null */
+  public static Quantity getQuantityByName(String name) {
+    RealType quant = RealType.getRealTypeByName(name);
+    if (!(quant instanceof Quantity)) {
+      try {
+        return new Quantity(quant);
+      } catch (VisADException ve) {
+        return null;
+      }
+    }
+    return (Quantity) quant;
+  }
+}
diff --git a/visad/data/netcdf/QuantityCheck.java b/visad/data/netcdf/QuantityCheck.java
new file mode 100644
index 0000000..ba26314
--- /dev/null
+++ b/visad/data/netcdf/QuantityCheck.java
@@ -0,0 +1,53 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.netcdf;
+
+import java.util.Iterator;
+import visad.Unit;
+
+class
+QuantityCheck
+{
+    public static void
+    main(String[] args)
+	throws Exception
+    {
+	QuantityDB	db = QuantityDBManager.instance();
+	Iterator	iter = db.nameIterator();
+
+	while (iter.hasNext())
+	{
+	    String	name = (String)iter.next();
+	    Quantity	quantity = db.get(name);
+	    Unit	unit	= quantity.getDefaultUnit();
+	    Quantity[]	quantities = db.get(unit);
+
+	    if (quantities.length >= 2)
+	    {
+		for (int i = 0; i < quantities.length; ++i)
+		    System.out.print(quantities[i].toString() + " ");
+		System.out.println(" (" + unit + ")");
+	    }
+	}
+    }
+}
diff --git a/visad/data/netcdf/QuantityDB.java b/visad/data/netcdf/QuantityDB.java
new file mode 100644
index 0000000..d4a6f7a
--- /dev/null
+++ b/visad/data/netcdf/QuantityDB.java
@@ -0,0 +1,218 @@
+//
+// QuantityDB.java
+//
+
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: QuantityDB.java,v 1.12 2002-09-20 18:15:29 steve Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import visad.Unit;
+import visad.UnitException;
+import visad.VisADException;
+import visad.data.units.NoSuchUnitException;
+import visad.data.units.ParseException;
+import visad.data.units.Parser;
+
+/**
+ * Provides support for a database of quantities.
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class
+QuantityDB
+{
+    /**
+     * The empty quantity database.  This is useful if, for example, you do
+     * not want the netCDF import package to map incoming netCDF variables to
+     * canonical ones by, effectively, altering their names and units.  This
+     * database cannot be altered.
+     */
+    public static final QuantityDB emptyDB;
+
+    static {
+	try {
+	    emptyDB = 
+		new QuantityDB() {
+		    public Quantity get(String name) {
+			return null;
+		    }
+		    public Quantity[] get(Unit unit) {
+			return null;
+		    }
+		    public void add(String name, Quantity quantity) {
+			throw new UnsupportedOperationException();
+		    }
+		    public Iterator quantityIterator() {
+			return NilIterator.INSTANCE;
+		    }
+		    public Iterator nameIterator() {
+			return NilIterator.INSTANCE;
+		    }
+		};
+	}
+	catch (Exception ex) {
+	    throw new ExceptionInInitializerError();
+	}
+    }
+
+    /**
+     * Returns the quantity in the database whose name matches a
+     * given name.
+     *
+     * @param name	The name of the quantity.
+     * @return		The quantity in the loal database that matches
+     *			<code>name</code>.  Note that
+     *			RETURN_VALUE<code>.getName().equals(name)</code> can
+     *			be <code>false</code> due to aliasing.
+     */
+    public abstract Quantity
+    get(String name);
+
+
+    /**
+     * Returns all quantities in the database whose default unit is
+     * convertible with a given unit.
+     *
+     * @param unit	The unit of the quantity.
+     * @return		The quantities in the database whose unit is
+     *			convertible with <code>unit</code>.
+     */
+    public abstract Quantity[]
+    get(Unit unit);
+
+
+    /**
+     * Returns the quantity that matches the given name and unit.  If
+     * necessary, it creates the quantity and adds it to the database.
+     *
+     * @param name		The name of the quantity.
+     * @param unitSpec	The unit of the quantity.
+     * @return			The quantity in the database that matches
+     *				<code>name</code> and <code>unit</code>.  Note 
+     *				that RETURN_VALUE<code>.getName().equals(name)
+     *				</code> can be <code>false</code> due to
+     *				aliasing and RETURN_VALUE<code>.
+     *				getDefaultUnit().equals(unit)</code> can be 
+     *				<code>false</code> due to allowable unit
+     *				conversion.
+     * @throws ParseException	Couldn't decode <code>unitSpec</code>.
+     * @throws NoSuchUnitException
+     *				<code>unitSpec</code> not in unit database.
+     * @throws UnitException	The quantity already exists with an 
+     *				incompatible unit.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public synchronized Quantity
+    get(String name, String unitSpec)
+	throws ParseException, NoSuchUnitException, UnitException,
+	    VisADException
+    {
+	Quantity	quantity = get(name);
+	if (quantity == null)
+	{
+	    quantity = new Quantity(name, unitSpec);
+	    add(quantity);
+	}
+	else
+	{
+	    Unit	quantityUnit = quantity.getDefaultUnit();
+	    if (!Unit.canConvert(Parser.parse(unitSpec), quantityUnit))
+		throw new UnitException(
+		    "Quantity " + name + " already exists; its unit " +
+		    quantity.getDefaultUnitString() +
+		    " is inconvertible with " + unitSpec);
+	}
+	return quantity;
+    }
+
+
+    /**
+     * Adds a given Quantity to the database under a given name.
+     *
+     * @param name		The name under which the quantity is to be
+     *				added.  May be an alias.
+     * @param quantity		The quantity to be added.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public abstract void
+    add(String name, Quantity quantity)
+	throws VisADException;
+
+    /**
+     * Adds a given Quantity to the database.
+     *
+     * @param quantity		The quantity to be added.  The quantity will
+     *				be added under it own name.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    
+    public void add(Quantity quantity) throws VisADException
+    {
+    	add(quantity.getName(), quantity);
+    }
+
+    /**
+     * Returns an iterator of the quantities in the database.
+     */
+    public abstract Iterator
+    quantityIterator();
+
+
+    /**
+     * Returns an iterator of the names in the database.
+     */
+    public abstract Iterator
+    nameIterator();
+
+    /**
+     * A nil {@link Iterator}.  Such an {@link Iterator} iterates over nothing.
+     */
+    static class NilIterator implements Iterator {
+
+	static final NilIterator INSTANCE;
+
+	static {
+	    INSTANCE = new NilIterator();
+	}
+
+	private NilIterator() {
+	}
+
+	/**
+	 * Indicates if another element exists.  This implementation always 
+	 * returns <code>false</code>.
+	 *
+	 * @return                     <code>false</code>.
+	 */
+	public boolean hasNext() {
+	    return false;
+	}
+
+	/**
+	 * Returns the next element.  This implementation always throws a
+	 * {@link NoSuchElementException}.
+	 *
+	 * @throws NoSuchElementException if this method is invoked.
+	 */
+	public Object next() {
+	    throw new NoSuchElementException();
+	}
+
+	/**
+	 * Removes the current element.  This implementation always throws an
+	 * {@link UnsupportedOperationException}.
+	 *
+	 * @throws UnsupportedOperationException if this method is invoked.
+	 */
+	public void remove() {
+	    throw new UnsupportedOperationException();
+	}
+    }
+}
diff --git a/visad/data/netcdf/QuantityDBImpl.java b/visad/data/netcdf/QuantityDBImpl.java
new file mode 100644
index 0000000..c51b5e2
--- /dev/null
+++ b/visad/data/netcdf/QuantityDBImpl.java
@@ -0,0 +1,605 @@
+//
+// QuantityDBImpl.java
+//
+
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: QuantityDBImpl.java,v 1.7 2006-02-13 22:30:07 curtis Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.io.Serializable;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.NoSuchElementException;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import visad.DerivedUnit;
+import visad.PromiscuousUnit;
+import visad.QuantityDimension;
+import visad.RealType;
+import visad.TypeException;
+import visad.Unit;
+import visad.UnitException;
+import visad.VisADException;
+import visad.data.units.ParseException;
+import visad.data.units.Parser;
+
+/**
+ * Provides support for a database of quantities.
+ *
+ * Instances are modifiable.
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+QuantityDBImpl
+    extends	QuantityDB
+    implements	Serializable
+{
+    /**
+     * The set of quantities.
+     */
+    private final SortedSet		quantitySet = new TreeSet();
+
+    /**
+     * The (Name) -> Quantity map.
+     */
+    private final TreeMap		nameMap = new TreeMap();
+
+    /**
+     * The (Unit, Name) -> Quantity map (major key: unit).
+     */
+    private final TreeMap		unitMap = new TreeMap();
+
+    /**
+     * The minimum name value.
+     */
+    private static final String		minName = "";
+
+    /**
+     * The maximum name value.
+     */
+    private static final String		maxName = "zzz";
+
+    /**
+     * The minimum unit value.
+     */
+    private static final Unit		minUnit = new DerivedUnit();
+
+    /**
+     * The maximum unit value.
+     */
+    private static final Unit		maxUnit = new DerivedUnit();
+
+    /**
+     * The quantity database to search after this one.
+     */
+    private /*final*/ QuantityDB	nextDB;
+
+
+    /**
+     * Constructs with another quantity database as the successor database.
+     *
+     * @param nextDB		The quantity database to search after this one.
+     *				May be <code>null</code>.
+     */
+     public
+     QuantityDBImpl(QuantityDB nextDB)
+     {
+	this.nextDB = nextDB;
+     }
+
+
+    /**
+     * Adds the given quantities and aliases to the database.
+     *
+     * @param definitions	New quantities and their definitions.
+     *				<code>definitions[2*i]</code> contains the
+     *				name (e.g. "speed") of the quantity whose
+     *				preferred unit specification (e.g. "m/s") is
+     *				in <code>definitions[2*i+1]</code>.
+     * @param aliases		Aliases for quantities.  <code>aliases[2*i]
+     *				</code> contains the alias for the quantity
+     *				named in <code>aliases[2*i+1]</code>.
+     * @return			The database resulting from the addition.  May
+     *				or may not be the original object.
+     * @throws ParseException	A unit specification couldn't be parsed.
+     * @throws TypeException	An incompatible version of the quantity already
+     *				exists.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public QuantityDB
+    add(String[] definitions, String[] aliases)
+	throws ParseException, TypeException, VisADException
+    {
+	for (int i = 0; i < definitions.length; i += 2)
+	    add(definitions[i], definitions[i+1]);
+
+	for (int i = 0; i < aliases.length; i += 2)
+	    add(aliases[i], get(aliases[i+1]));
+
+	return this;
+    }
+
+
+    /**
+     * Adds a quantity to the database under a given name.
+     *
+     * @param name		The name of the quantity (e.g. "length").
+     *				May be an alias for the quantity.
+     * @param quantity		The quantity.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public synchronized void
+    add(String name, Quantity quantity)
+	throws VisADException
+    {
+	if (name == null || quantity == null)
+	    throw new VisADException("add(): null argument");
+
+	Unit	unit = quantity.getDefaultUnit();
+
+	quantitySet.add(quantity);
+	nameMap.put(new NameKey(name), quantity);
+	unitMap.put(new UnitKey(unit, quantity.getName()), quantity);
+    }
+
+
+    /**
+     * Adds given Quantity-s to the database.
+     *
+     * @param quantities	The quantities to be added.  The quantity will
+     *				be added under it own name.
+     * @return			The database resulting from the addition.  May
+     *				or may not be the original object.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public QuantityDB
+    add(Quantity[] quantities)
+	throws VisADException
+    {
+	for (int i = 0; i < quantities.length; i++)
+	{
+	    Quantity	quantity = quantities[i];
+	    add(quantity);
+	}
+	return this;
+    }
+
+
+    /**
+     * Adds a quantity to the database given a name and a display unit
+     * specification.
+     *
+     * @param name		The name of the quantity (e.g. "length").
+     * @param unitSpec		The preferred display unit for the
+     *				quantity (e.g. "feet").
+     * @throws ParseException	Couldn't decode unit specification.
+     * @throws TypeException	Incompatible ScalarType of same name already
+     *				exists.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    protected synchronized void
+    add(String name, String unitSpec)
+      throws ParseException, TypeException, VisADException
+    {
+	try
+	{
+	    add(name, new Quantity(name, unitSpec));
+	}
+	catch (VisADException e)
+	{
+	    if (!(e instanceof TypeException))
+		throw e;
+
+	    RealType	realType = RealType.getRealTypeByName(name);
+	    if (realType == null ||
+		!Unit.canConvert(
+		    realType.getDefaultUnit(), Parser.parse(unitSpec)))
+		throw (TypeException)e;
+	}
+    }
+
+
+    /**
+     * Provides support for iterating over the database.
+     */
+    protected abstract class
+    Iterator
+	implements	java.util.Iterator
+    {
+	/**
+	 * The private iterator.
+	 */
+	protected java.util.Iterator	iterator;
+
+	/**
+	 * Whether or not we can switch to the other database.
+	 */
+	private boolean			canSwitch = nextDB != null;
+
+	/*
+	 * Returns <code>true</code> if <code>next</code> will return an object.
+	 * @return		<code>true</code> if and only if <code>next()
+	 *			</code> will return a Quantity.
+	 */
+	public boolean
+	hasNext()
+	{
+	    boolean	have = iterator.hasNext();
+	    if (!have && doSwitch())
+		have = hasNext();
+	    return have;
+	}
+
+	protected abstract Object
+	nextObject();
+
+	/*
+	 * Returns the next thing in the database.
+	 * @return		The next thing in the database if and only
+	 *			if a prior <code>hasNext()</code> did or would
+	 *			have returned <code>true</code>; otherwise
+	 *			throws an exception.
+	 * @throws NoSuchElementException	No more things in the database.
+	 */
+	public Object
+	next()
+	{
+	    Object	object;
+	    try
+	    {
+		object = nextObject();
+	    }
+	    catch (NoSuchElementException e)
+	    {
+		if (!doSwitch())
+		    throw e;
+		object = next();
+	    }
+	    return object;
+	}
+
+	/**
+	 * Gets the iterator for the successor database.
+	 */
+	protected abstract java.util.Iterator
+	nextIterator();
+
+	/**
+	 * Switchs to the other database.
+	 * @return		<code>true</code> if an only if the other
+	 *			database exists and this is the first switch
+	 *			to it.
+	 */
+	protected boolean
+	doSwitch()
+	{
+	    boolean	goodSwitch;
+	    if (!canSwitch)
+	    {
+		goodSwitch = false;
+	    }
+	    else
+	    {
+		iterator = nextIterator();
+		canSwitch = false;
+		goodSwitch = true;
+	    }
+	    return goodSwitch;
+	}
+
+	/**
+	 * Remove the element returned by the last <code>next()</code>.
+	 * @throws UnsupportedOperationException
+				Operation not supported.
+	 */
+	public void
+	remove()
+	    throws UnsupportedOperationException
+	{
+	    throw new UnsupportedOperationException(
+		"remove(): Can't remove elements from quantity database");
+	}
+    }
+
+
+    /**
+     * Provides support for iterating over the quantities in the database.
+     */
+    protected class
+    QuantityIterator
+	extends	Iterator
+    {
+	/**
+	 * Constructs.
+	 */
+	protected
+	QuantityIterator()
+	{
+	    iterator = quantitySet.iterator();
+	}
+
+	/**
+	 * Returns the next quantity of the iterator.
+	 */
+	protected Object
+	nextObject()
+	{
+	    return iterator.next();
+	}
+
+	/**
+	 * Returns the iterator for the successor database.
+	 */
+	protected java.util.Iterator
+	nextIterator()
+	{
+	    return nextDB.quantityIterator();
+	}
+    }
+
+
+    /**
+     * Provides support for iterating over the names in the database.
+     * A name can only appear once in the database.
+     */
+    protected class
+    NameIterator
+	extends	Iterator
+    {
+	/**
+	 * Constructs.
+	 */
+	protected
+	NameIterator()
+	{
+	    iterator = nameMap.keySet().iterator();
+	}
+
+	/**
+	 * Returns the next name in the iterator.
+	 */
+	protected Object
+	nextObject()
+	{
+	    return ((NameKey)iterator.next()).getName();
+	}
+
+	/**
+	 * Gets the iterator for the successor database.
+	 */
+	protected java.util.Iterator
+	nextIterator()
+	{
+	    return nextDB.nameIterator();
+	}
+    }
+
+
+    /**
+     * Returns an iterator of the quantities in the database.
+     * @return		An iterator of the quantities in the database.
+     */
+    public java.util.Iterator
+    quantityIterator()
+    {
+	return new QuantityIterator();
+    }
+
+
+    /**
+     * Returns an iterator of the names in the database.
+     * @return		An iterator of the names in the database.
+     */
+    public java.util.Iterator
+    nameIterator()
+    {
+	return new NameIterator();
+    }
+
+
+    /**
+     * Returns the quantity in the database whose name matches a
+     * given name.
+     *
+     * @param name	The name of the quantity.
+     * @return		The quantity in the loal database with name
+     *			<code>name</code>.
+     */
+    public synchronized Quantity
+    get(String name)
+    {
+	Quantity	quantity = (Quantity)nameMap.get(new NameKey(name));
+	return quantity != null
+		? quantity
+		: nextDB == null
+		    ? null
+		    : nextDB.get(name);
+    }
+
+
+    /**
+     * Returns all quantities in the database whose default unit is
+     * convertible with a given unit.
+     *
+     * @param unit	The unit of the quantity.
+     * @return		The quantities in the database whose unit is
+     *			convertible with <code>unit</code>.
+     */
+    public synchronized Quantity[]
+    get(Unit unit)
+    {
+	Quantity[]	myQuantities = (Quantity[])unitMap.subMap
+	    (new UnitKey(unit, minName), new UnitKey(unit, maxName))
+		.values().toArray(new Quantity[0]);
+	Quantity[]	nextQuantities = nextDB == null
+					    ? new Quantity[] {}
+					    : nextDB.get(unit);
+	Quantity[]	quantities =
+	    new Quantity[myQuantities.length + nextQuantities.length];
+	System.arraycopy(myQuantities, 0, quantities, 0, myQuantities.length);
+	System.arraycopy(nextQuantities, 0, quantities, myQuantities.length,
+	    nextQuantities.length);
+	myQuantities = null;
+	nextQuantities = null;
+	return quantities;
+    }
+
+
+    /**
+     * Provides support for keys to the name map.
+     *
+     * Immutable.
+     */
+    protected static class
+    NameKey
+	implements	Serializable, Comparable
+    {
+	/**
+	 * The name of the quantity.
+	 */
+	private final String		name;
+
+	/**
+	 * The Collator for the name.
+	 */
+	private static final Collator	collator;
+
+	/**
+	 * The comparison value for the name of the quantity.
+	 */
+	private final CollationKey	nameCookie;
+
+
+	static
+	{
+	    collator = Collator.getInstance();
+	    collator.setStrength(Collator.PRIMARY);
+	}
+
+
+	/**
+	 * Constructs from the name of a quantity.
+	 *
+	 * @param name	The name of the quantity.
+	 */
+	protected NameKey(String name)
+	{
+	    this.name = name;
+	    nameCookie = collator.getCollationKey(name);
+	}
+
+
+	/**
+	 * Compare this key to another.
+	 */
+	public int compareTo(Object obj)
+	  throws ClassCastException
+	{
+	    return nameCookie.compareTo(((NameKey)obj).nameCookie);
+	}
+
+
+	/**
+	 * Returns the name of the quantity.
+	 */
+	public String
+	getName()
+	{
+	    return name;
+	}
+    }
+
+
+    /**
+     * Provides support for keys to the unit map.
+     *
+     * Immutable.
+     */
+    protected static final class
+    UnitKey
+	extends	NameKey
+    {
+	/**
+	 * The default unit of the quantity.
+	 */
+	protected final Unit		unit;
+
+
+	/**
+	 * Constructs from the unit of a quantity and its name.
+	 *
+	 * @param unit	The default unit of the quantity.
+	 * @param name	The name of the quantity.
+	 */
+	protected UnitKey(Unit unit, String name)
+	{
+	    super(name);
+	    this.unit = unit;
+	}
+
+
+	/**
+	 * Compare this key to another (unit first).
+	 */
+	public int compareTo(Object obj)
+	  throws ClassCastException
+	{
+	    UnitKey	that = (UnitKey)obj;
+	    int		i = compare(this.unit, that.unit);
+
+	    return i != 0
+		      ? i
+		      : super.compareTo(that);
+	}
+
+
+	/**
+	 * Compare one Unit to another.
+	 */
+	private int compare(Unit a, Unit b)
+	  throws ClassCastException
+	{
+	    int	comparison;
+
+	    if (a instanceof PromiscuousUnit || b instanceof PromiscuousUnit)
+	    {
+		comparison = 0;
+	    }
+	    else
+	    if (a == null || b == null)
+	    {
+		comparison = a == null && b == null
+				? 0
+				: a == null
+				    ? -1
+				    :  1;
+	    }
+	    else
+	    {
+		try
+		{
+		  comparison = (a == b)
+		      ? 0
+		      : (a == minUnit || b == maxUnit)
+			  ? -1
+			  : (a == maxUnit || b == minUnit)
+			      ? 1
+			      : new QuantityDimension(a).compareTo
+				  (new QuantityDimension(b));
+		}
+		catch (UnitException e)
+		{
+		  throw new ClassCastException(e.getMessage());
+		}
+	    }
+	    return comparison;
+	}
+    }
+}
diff --git a/visad/data/netcdf/QuantityDBManager.java b/visad/data/netcdf/QuantityDBManager.java
new file mode 100644
index 0000000..3a3000f
--- /dev/null
+++ b/visad/data/netcdf/QuantityDBManager.java
@@ -0,0 +1,127 @@
+//
+// QuantityDBManager.java
+//
+
+/*
+ * Copyright 1999, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: QuantityDBManager.java,v 1.3 2001-04-03 19:12:26 steve Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.io.Serializable;
+import visad.VisADError;
+import visad.VisADException;
+
+
+/**
+ * Provides support for managing a database of quantities.  This class
+ * insulates the user from knowing or caring, for example, whether or not
+ * the quantity database is unchanging or whether it's implemented using
+ * singletons.
+ *
+ * @author Steven R. Emmerson
+ */
+public final class
+QuantityDBManager
+    implements	Serializable
+{
+    /**
+     * The singleton instance of the current database.
+     */
+    private static QuantityDB	db;
+
+
+    static
+    {
+	try
+	{
+	    db = defaultInstance();
+	}
+	catch (Exception e)
+	{
+	    if (e instanceof RuntimeException)
+		throw (RuntimeException)e;
+	    throw new VisADError(
+		"visad.data.netcdf.QuantityDBManager.<clinit>: " +
+		"Couldn't initialize class" + e);
+	}
+    }
+
+
+    /**
+     * Constructs from nothing.  Private to prevent instantiation.
+     */
+    private
+    QuantityDBManager()
+    {
+    }
+
+
+    /**
+     * Returns the default quantity database.
+     *
+     * @return                  The default quantity database.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     */
+    protected static QuantityDB
+    defaultInstance()
+        throws VisADException
+    {
+        return StandardQuantityDB.instance();
+    }
+
+
+    /**
+     * Returns the current instance of the quantity database.
+     *
+     * @return			The current instance of the quantity database.
+     */
+    public static synchronized QuantityDB
+    instance()
+    {
+	return db;
+    }
+
+
+    /**
+     * Sets the current instance of the quantity database.
+     *
+     * @param db		The new current instance of the quantity
+     *				database.  May be <code>null</code>, in which
+     *				case the default instance is used.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public static synchronized void
+    setInstance(QuantityDB db)
+	throws VisADException
+    {
+	QuantityDBManager.db = db == null ? defaultInstance() : db;
+    }
+
+
+    /**
+     * Tests this class by listing the contents -- one per line --
+     * in the following format:
+     *	    <name> ( <CannonicalName> ) in <PreferredUnit>
+     * e.g.
+     *	    VolumicElectricCharge (ElectricChargeDensity) in C/m3
+     */
+    public static void
+    main(String[] args)
+      throws	Exception
+    {
+	QuantityDB	db = QuantityDBManager.instance();
+
+	for (java.util.Iterator iter = db.nameIterator(); iter.hasNext(); )
+	{
+	    String	name = (String)iter.next();
+	    Quantity	quantity = db.get(name);
+	    System.out.println(
+		name + " (" + quantity.getName() + ") in " +
+		quantity.getDefaultUnitString());
+	}
+    }
+}
diff --git a/visad/data/netcdf/README b/visad/data/netcdf/README
new file mode 100644
index 0000000..c77437e
--- /dev/null
+++ b/visad/data/netcdf/README
@@ -0,0 +1,2 @@
+This directory contains classes that implement a visad.data.Form for 
+importing and exporting data in a netCDF data-form.
diff --git a/visad/data/netcdf/StandardQuantityDB.java b/visad/data/netcdf/StandardQuantityDB.java
new file mode 100644
index 0000000..2c4fb0a
--- /dev/null
+++ b/visad/data/netcdf/StandardQuantityDB.java
@@ -0,0 +1,428 @@
+//
+// StandardQuantityDB.java
+//
+
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: StandardQuantityDB.java,v 1.17 2006-01-27 21:11:01 donm Exp $
+ */
+
+package visad.data.netcdf;
+
+import visad.data.units.ParseException;
+import visad.RealType;
+import visad.SI;
+import visad.TypeException;
+import visad.VisADError;
+import visad.VisADException;
+
+
+/**
+ * The following class implements a database of standard quantities.  It is
+ * implemented as a singleton.  Instances of the class are modifiable.
+ *
+ * @author Steven R. Emmerson
+ */
+public final class
+StandardQuantityDB
+    extends	QuantityDBImpl
+{
+    /**
+     * The singleton instance of this class.
+     */
+    private static /*final*/ StandardQuantityDB	db;
+
+
+    static
+    {
+	try
+	{
+	    db = new StandardQuantityDB();
+	}
+	catch (Exception e)
+	{
+	    if (e instanceof RuntimeException)
+		throw (RuntimeException)e;
+	    throw new VisADError(
+		"visad.data.netcdf.StandardQuantityDB.<clinit>: " +
+		"Couldn't initialize class: " + e);
+	}
+    }
+
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @throws VisADException	Couldn't instantiate.
+     */
+    public static StandardQuantityDB
+    instance()
+    {
+	return db;
+    }
+
+
+    /**
+     * Constucts from nothing.  Private to ensure use of the instance()
+     * method.
+     *
+     * @param otherDB		The quantity database for the get...()
+     *				methods to search after this one if no
+     *				entry found.  May be <code>null</code>.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    private
+    StandardQuantityDB()
+      throws VisADException
+    {
+	super(null);
+
+	String	name;
+	Quantity	quantity;
+
+	try {
+	    /*
+	     * From the VisAD RealType class:
+	     */
+	    add(RealType.Generic);
+	    add(RealType.Radius);
+	    add(RealType.XAxis);
+	    add(RealType.YAxis);
+	    add(RealType.ZAxis);
+	    add(RealType.Latitude);
+	    add(RealType.Longitude);
+            add(RealType.Altitude);
+	    add(RealType.Time);
+
+	    /*
+	     * From the SI class:
+	     */
+	    super.add(SI.ampere.quantityName(), "A");
+	    super.add(SI.candela.quantityName(), "cd");
+	    super.add(SI.kelvin.quantityName(), "K");
+	    super.add(SI.kilogram.quantityName(), "kg");
+	    super.add(SI.meter.quantityName(), "m");
+	    // super.add(SI.second.quantityName(), "s");RealType.Time already
+	    super.add(SI.mole.quantityName(), "mol");
+	    super.add(SI.radian.quantityName(), "rad");
+
+	    /*
+	     * Quasi-fundamental dimensions:
+	     */
+	    super.add("SolidAngle", "sr");
+
+	    /*
+	     * Derived dimensions.  The categories are somewhat arbitrary.
+	     */
+
+	    /*
+	     * Simple stuff:
+	     */
+	    super.add("Volume", "m3");
+	    super.add("VolumeFraction", "m3/m3");
+	    super.add("VolumeFlow", "m3/s");
+	    super.add("Acceleration", "m/s2");
+	    super.add("Area", "m2");
+	    super.add("Frequency", "Hz");
+	    super.add("WaveNumber", "m-1");
+	    super.add("Speed", "m/s");
+	    super.add("Velocity", "m/s");
+	    super.add("AngularVelocity", "rad/s");
+	    super.add("AngularAcceleration", "rad/s2");
+
+	    /*
+	     * Mass:
+	     */
+	    quantity = new Quantity("SurfaceMassDensity", "kg/m2");
+	    super.add("SurfaceMassDensity", quantity);
+	    super.add("AreicMass", quantity);
+	    super.add("MassPerArea", quantity);
+	    quantity = new Quantity("LinearMassDensity", "kg/m2");
+	    super.add("LinearMassDensity", quantity);
+	    super.add("LineicMass", quantity);
+	    super.add("MassPerLength", "kg/m");
+	    super.add("MassFraction", "kg/kg");
+	    quantity = new Quantity("MassFlow", "kg/s");
+	    super.add("MassFlux", quantity);
+	    super.add("MassFlow", quantity);
+	    quantity = new Quantity("Density", "kg/m3");
+	    super.add("MassDensity", quantity);
+	    super.add("Density", quantity);
+	    super.add("VolumicMass", quantity);
+	    quantity = new Quantity("SpecificVolume", "m3/kg");
+	    super.add("SpecificVolume", quantity);
+	    super.add("MassicVolume", quantity);
+
+	    /*
+	     * Force:
+	     */
+	    super.add("Force", "N");
+	    super.add("MomentOfForce", "N.m");
+	    super.add("SurfaceTension", "N/m");
+	    quantity = new Quantity("LinearForceDensity", "N/m");
+	    super.add("LinearForceDensity", quantity);
+	    super.add("LineicForce", quantity);
+	    super.add("ForcePerLength", quantity);
+	    super.add("Torque", "N.m");
+	    quantity = new Quantity("LinearTorqueDensity", "N");
+	    super.add("LinearTorqueDensity", quantity);
+	    super.add("LineicTorque", quantity);
+	    super.add("TorquePerlength", quantity);
+	    super.add("Pressure", "Pa");
+	    super.add("Stress", "Pa");
+
+	    /*
+	     * Viscosity:
+	     */
+	    super.add("DynamicViscosity", "Pa.s");
+	    super.add("KinematicViscosity", "m2/s");
+
+	    /*
+	     * Energy:
+	     */
+	    super.add("Energy", "J");
+	    super.add("Work", "J");
+	    super.add("QuantityOfHeat", "J");
+	    super.add("Power", "W");
+	    quantity = new Quantity("SurfacePowerDensity", "J/(m2.s)");
+	    super.add("SurfacePowerDensity", quantity);
+	    super.add("AreicPower", quantity);
+	    super.add("EnergyPerAreaTime", quantity);
+	    super.add("SpecificAvailableEnergy", "J/kg");
+	    super.add("SpecificEnergy", "J/kg");
+	    quantity = new Quantity("AvailableEnergyDensity", "J/m3");
+	    super.add("AvailableEnergyDensity", quantity);
+	    super.add("VolumicAvailableEnergy", quantity);
+	    quantity = new Quantity("EnergyDensity", "J/m3");
+	    super.add("EnergyDensity", quantity);
+	    super.add("VolumicEnergy", quantity);
+
+	    /*
+	     * Heat and temperature:
+	     */
+	    super.add("ThermalConductivity", "W/(m.K)");
+	    super.add("ThermalDiffusivity", "m2/s");
+	    super.add("ThermalInsulance", "(m3.K)/W");
+	    super.add("ThermalResistance", "K/W");
+	    super.add("ThermalResistivity", "(m.K)/W");
+	    super.add("CoefficientOfHeatTransfer", "W/(m2.K)");
+	    quantity = new Quantity("SurfaceHeatDensity", "J/m2");
+	    super.add("SurfaceHeatDensity", quantity);
+	    super.add("AreicHeat", quantity);
+	    super.add("DensityOfHeat", quantity);
+	    quantity = new Quantity("SurfaceHeatFlowDensity", "W/m2");
+	    super.add("SurfaceHeatFlowDensity", quantity);
+	    super.add("SurfaceHeatFluxDensity", quantity);
+	    super.add("AreicHeatFlow", quantity);
+	    super.add("AreicHeatFlux", quantity);
+	    super.add("DensityOfHeatFlowRate", quantity);
+	    super.add("HeatCapacity", "J/K");
+	    super.add("Entropy", "J/K");
+	    super.add("HeatFlowRate", "W");
+	    super.add("SpecificHeatCapcity", "J/(kg.K)");
+	    super.add("SpecificHeat", "J/(kg.K)");
+	    super.add("SpecificEntropy", "J/(kg.K)");
+
+	    /**
+	     * Electricity and magnetism:
+	     */
+	    super.add("Capacitance", "F");
+	    quantity = new Quantity("Permittivity", "F/m");
+	    super.add("Permittivity", quantity);
+	    super.add("MagneticPermittivity", quantity);
+	    super.add("MagneticPermeability", "H/m");
+	    super.add("ElectricCharge", "C");
+	    quantity = new Quantity("ElectricChargeDensity", "C/m3");
+	    super.add("ElectricChargeDensity", quantity);
+	    super.add("VolumicElectricCharge", quantity);
+	    quantity = new Quantity("ElectricFluxDensity", "C/m2");
+	    super.add("ElectricFluxDensity", quantity);
+	    super.add("SurfaceElectricChargeDensity", quantity);
+	    super.add("AreicElectricCharge", quantity);
+	    super.add("ElectricResistance", "Ohm");
+	    super.add("ElectricConductance", "S");
+	    quantity = new Quantity("EMF", "V");
+	    super.add("ElectricPotentialDifference", quantity);
+	    super.add("ElectromotiveForce", quantity);
+	    super.add("EMF", quantity);
+	    quantity = new Quantity("CurrentDensity", "A/m2");
+	    super.add("CurrentDensity", quantity);
+	    super.add("SurfaceCurrentDensity", quantity);
+	    super.add("AreicCurrent", quantity);
+	    super.add("Inductance", "H");
+	    super.add("MagneticFlux", "Wb");
+	    super.add("MagneticFluxDensity", "T");
+	    super.add("MagneticFieldStrength", "A/m");
+	    super.add("ElectricFieldStrength", "V/m");
+
+	    /*
+	     * Photometry and Radiometry:
+	     */
+	    super.add("Illuminance", "lx");
+	    super.add("Irradiance", "W/m2");
+	    super.add("RadiantEmittance", "W/m2");
+	    super.add("Radiance", "W/(m2.sr)");
+	    super.add("Luminance", "cd/m2");
+	    super.add("LuminousFlux", "lm");
+	    super.add("RadiantFlux", "W");
+	    super.add("RadiantIntensity", "W/sr");
+
+	    /*
+	     * Amount of substance:
+	     */
+	    super.add("AmountOfSubstanceFraction", "mol/mol");
+	    super.add("MolarVolume", "m3/mol");
+	    super.add("MolarMass", "kg/mol");
+	    super.add("AmountOfSubstanceConcentration", "mol/m3");
+	    super.add("Molality", "mol/kg");
+	    super.add("MolarEnergy", "J/mol");
+	    super.add("MolarEntropy", "J/(mol K)");
+	    super.add("MolarHeatCapacity", "J/(mol K)");
+
+	    /*
+	     * Flow permeability:
+	     */
+	    super.add("EquivalentPermeability", "m2");
+	    quantity = new Quantity(
+	      "SurfaceFlowPermeabilityDensity", "kg/(Pa.s.m2)");
+	    super.add("SurfaceFlowPermeabilityDensity", quantity);
+	    super.add("AreicFlowPermeability", quantity);
+	    quantity = new Quantity(
+		"LinearFlowPermeabilityDensity", "kg/(Pa.s.m)");
+	    super.add("LinearFlowPermeabilityDensity", quantity);
+	    super.add("LineicFlowPermeability", quantity);
+
+	    /*
+	     * Radioactivity & radiation:
+	     */
+	    super.add("AbsorbedDose", "Gy");
+	    super.add("AbsorbedDoseRate", "Gy/s");
+	    super.add("DoseEquivalent", "Sv");
+	    super.add("Activity", "Bq");
+	    super.add("Exposure", "C/kg");	// x & gamma rays
+
+	    /*
+	     * Fuel consumption:
+	     */
+	    super.add("VolumePerWorkFuelConsumption", "m3/J");
+	    quantity = new Quantity("DistancePerVolumeFuelConsumption", "m/m3");
+	    super.add("DistancePerVolumeFuelConsumption", quantity);
+	    super.add("DistanceDensityFuelConsumption", quantity);
+	    super.add("VolumicDistanceFuelConsumption", quantity);
+	    super.add("MassPerWorkFuelConsumption", "kg/J");
+
+	    /*
+	     * Geophysical sciences:
+	     */
+	    super.add("Direction", "deg");
+	    quantity = get("Latitude");		// from RealType.Latitude
+	    super.add("GeodeticLatitude", quantity);
+	    super.add("lat", quantity);
+	    quantity = get("Longitude");
+	    super.add(
+		"GeodeticLongitude", quantity);	// from RealType.Longitude
+	    super.add("Longitude", quantity);
+	    super.add("lon", quantity);
+            quantity = get("Altitude");		// from RealType.Altitude
+	    super.add("Elevation", quantity);
+	    super.add("Altitude", quantity);
+	    super.add("alt", quantity);
+	    super.add("Depth", "m");
+	} catch (ParseException e) {	// shouldn't happen
+	    throw new VisADException(e.getMessage());
+	}
+    }
+
+
+    /**
+     * Adds the given quantities and aliases to the database.
+     *
+     * @param definitions	New quantities and their definitions.
+     *				<code>definitions[2*i]</code> contains the
+     *				name (e.g. "speed") of the quantity whose
+     *				preferred unit specification (e.g. "m/s") is
+     *				in <code>definitions[2*i+1]</code>.
+     * @param aliases		Aliases for quantities.  <code>aliases[2*i]
+     *				</code> contains the alias for the quantity
+     *				named in <code>aliases[2*i+1]</code>.
+     * @return			The database resulting from the addition.  Will
+     *				not be the original object.
+     * @throws ParseException	A unit specification couldn't be parsed.
+     * @throws TypeException	An incompatible version of the quantity already
+     *				exists.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public QuantityDB
+    add(String[] definitions, String[] aliases)
+	throws ParseException, TypeException, VisADException
+    {
+	return new QuantityDBImpl(this).add(definitions, aliases);
+    }
+
+
+    /**
+     * Adds a quantity to the database given a name and a display unit
+     * specification.
+     *
+     * @param name		The name of the quantity (e.g. "length").
+     * @param unitSpec		The preferred display unit for the
+     *				quantity (e.g. "feet").
+     * @exception UnsupportedOperationException
+     *				Always thrown because a standard database
+     *				must be unmodifiable.
+     */
+    public void
+    add(String name, String unitSpec)
+    {
+	throw new UnsupportedOperationException(
+	    "Standard Quantity database is unmodifiable");
+    }
+
+
+    /**
+     * Adds a quantity to the database given a VisAD RealType.
+     *
+     * @param realType		The VisAD RealType to be added.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    private void
+    add(RealType realType)
+	throws VisADException
+    {
+	super.add(realType.getName(), new Quantity(realType));
+    }
+
+
+    /**
+     * Tests this class.  If the only argument is "list", then this method
+     * will print the list of standard quantities -- one per line --
+     * in the following format:
+     *	    name "(" CannonicalName ")" " in " PreferredUnit
+     * e.g.
+     *	    VolumicElectricCharge (ElectricChargeDensity) in C/m3
+     */
+    public static void
+    main(String[] args)
+      throws	Exception
+    {
+	StandardQuantityDB	db = StandardQuantityDB.instance();
+
+	if (args.length != 1 || !args[0].equals("list"))
+	{
+	    System.out.println("LaTiTuDe=<" + db.get("LaTiTuDe") + ">");
+	}
+	else
+	{
+	    for (java.util.Iterator iter = db.nameIterator(); iter.hasNext(); )
+	    {
+		String	name = (String)iter.next();
+		Quantity	quantity = db.get(name);
+		System.out.println(
+		    name + " (" + quantity.getName() + ") in " +
+		    quantity.getDefaultUnitString());
+	    }
+	}
+    }
+}
diff --git a/visad/data/netcdf/StandardQuantityDB.save b/visad/data/netcdf/StandardQuantityDB.save
new file mode 100644
index 0000000..4e246c2
--- /dev/null
+++ b/visad/data/netcdf/StandardQuantityDB.save
@@ -0,0 +1 @@
+LaTiTuDe=<Latitude>
diff --git a/visad/data/netcdf/UnsupportedOperationException.java b/visad/data/netcdf/UnsupportedOperationException.java
new file mode 100644
index 0000000..f44838e
--- /dev/null
+++ b/visad/data/netcdf/UnsupportedOperationException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: UnsupportedOperationException.java,v 1.4 1998-04-03 20:35:17 visad Exp $
+ */
+
+package visad.data.netcdf;
+
+
+/**
+ * The UnsupportedOperationException provides a way to flag methods that
+ * are not implemented.
+ */
+public class
+UnsupportedOperationException
+    extends NoSuchMethodError
+{
+    public
+    UnsupportedOperationException()
+    {
+	super();
+    }
+
+    /**
+     * Construct from a message.
+     *
+     * @param msg	The message.
+     */
+    public
+    UnsupportedOperationException(String msg)
+    {
+	super(msg);
+    }
+}
diff --git a/visad/data/netcdf/VariableFilter.java b/visad/data/netcdf/VariableFilter.java
new file mode 100644
index 0000000..5956a68
--- /dev/null
+++ b/visad/data/netcdf/VariableFilter.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 1998-2001, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VariableFilter.java,v 1.3 2001-09-11 16:39:09 steve Exp $
+ */
+
+package visad.data.netcdf;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+import ucar.netcdf.Netcdf;
+import ucar.netcdf.NetcdfWrapper;
+import ucar.netcdf.Variable;
+import ucar.netcdf.VariableIterator;
+
+/**
+ * Wrapper class for restricting the {@link Netcdf#iterator()} and {@link
+ * Netcdf#size()} methods to only those variables that are also in a
+ * client-specified list.  All other methods of the {@link Netcdf} API
+ * are those of {@link NetcdfWrapper}.  Thus, it is possible to bypass
+ * the variable-filtering provided by this class by directly invoking
+ * the single-variable methods
+ * (e.g. {@link Netcdf#get(String) Netcdf.get(String)},
+ * {@link Netcdf#contains(Object) Netcdf.contains(Object)}).
+ *
+ * @author Steven R. Emmerson
+ * @version $Revision: 1.3 $ $Date: 2001-09-11 16:39:09 $
+ */
+final class VariableFilter
+    extends NetcdfWrapper
+{
+    /**
+     * The set of variables to reveal.  Every name corresponds to a variable 
+     * that exists in the wrapped netCDF database.
+     */
+    private Set names;  // not "final" to accomodate JDK 1.2 bug
+
+    /**
+     * Constructs from a netCDF dataset and a set of variable names.  The
+     * dataset will appear to only contain the set of variables that is the
+     * intersection of the variables in the dataset and the named variables.
+     * @param netcdf               The netCDF dataset to be wrapped.
+     * @param names                A set of variable names.
+     * @param NullPointerException if either argument is <code>null</code>.
+     * @param ClassCastException   if an element of the set isn't a {@link
+     *                             String}.
+     */
+    protected VariableFilter(Netcdf netcdf, Set names)
+        throws NullPointerException
+    {
+        super(netcdf);
+        if (names == null)
+            throw new NullPointerException();
+        this.names = new TreeSet(names);
+        for (Iterator iter = this.names.iterator(); iter.hasNext(); )
+            if (!netcdf.contains((String)iter.next()))
+                iter.remove();
+    }
+
+    /**
+     * Returns the number of variables.  This is the number of variables that
+     * will be returned by the iterator obtained from the {@link #iterator()}
+     * method.
+     * @return                    The number of variables
+     */
+    public int size()
+    {
+        return names.size();
+    }
+
+    /**
+     * Returns an iterator over the variables.  The iterator will only return
+     * those variables in the underlying netCDF dataset whose names were in the
+     * set of names used during construction.  The number of such variables
+     * equals the return value of {@link #size()}.
+     * @return                    An iterator over the variables.
+     */
+    public VariableIterator iterator()
+    {
+        return new WrappedVariableIterator();
+    }
+
+    private class WrappedVariableIterator
+        implements VariableIterator
+    {
+        private final Iterator iter;
+        WrappedVariableIterator()   { iter = names.iterator(); }
+        public boolean hasNext()    { return iter.hasNext(); }
+        public Variable next()      { return get((String)iter.next()); }
+    }
+}
diff --git a/visad/data/netcdf/depend b/visad/data/netcdf/depend
new file mode 100644
index 0000000..37d8182
--- /dev/null
+++ b/visad/data/netcdf/depend
@@ -0,0 +1,16 @@
+InputNetcdf.class:	Plain.class
+InputNetcdfBeanInfo.class:	InputNetcdf.class
+InputNetcdfBeanInfo.class:	InputNetcdfPathnameEditor.class
+InputNetcdfPathnameEditor.class:	InputPathnameEditor.class
+InputPathnameEditor.class:	FileDialogPanel.class
+NetcdfInBean.class:	Plain.class
+Plain.class:	NetCDF.class
+Plain.class:	QuantityDBManager.class
+QuantityCheck.class:	QuantityDBManager.class
+QuantityDBImpl.class:	Quantity.class
+QuantityDBImpl.class:	QuantityDB.class
+QuantityDBImpl.class:	UnsupportedOperationException.class
+QuantityDBManager.class:	StandardQuantityDB.class
+StandardQuantityDB.class:	Quantity.class
+StandardQuantityDB.class:	QuantityDBImpl.class
+StandardQuantityDB.class:	UnsupportedOperationException.class
diff --git a/visad/data/netcdf/in/CfView.java b/visad/data/netcdf/in/CfView.java
new file mode 100644
index 0000000..ded26b3
--- /dev/null
+++ b/visad/data/netcdf/in/CfView.java
@@ -0,0 +1,1346 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: CfView.java,v 1.4 2002-10-21 20:07:45 donm Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+import java.util.WeakHashMap;
+import ucar.netcdf.Dimension;
+import ucar.netcdf.Netcdf;
+import ucar.netcdf.Variable;
+import ucar.netcdf.VariableIterator;
+import visad.CommonUnit;
+import visad.data.netcdf.QuantityDB;
+import visad.data.netcdf.QuantityDBImpl;
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.GriddedSet;
+import visad.MathType;
+import visad.OffsetUnit;
+import visad.ProductSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.SampledSet;
+import visad.SI;
+import visad.TextType;
+import visad.TypeException;
+import visad.Unit;
+import visad.VisADException;
+
+/**
+ * <p>
+ * A view of a netCDF dataset according to the Climate and Forecast (CF)
+ * conventions.
+ * </p>
+ *
+ * <p>If this class can't be initialized, then an error message is printed to
+ * {@link System#err} and the JVM is terminated.</p>
+ *
+ * @author Steven R. Emmerson
+ * @version $Revision: 1.4 $ $Date: 2002-10-21 20:07:45 $
+ * @see http://www.cgd.ucar.edu/cms/eaton/netcdf/CF-current.htm
+ */
+final class CfView
+    extends     View
+{
+    /*
+     * The following 6 fields are not "final" to accomodate a bug in JDK 1.2.
+     */
+    private SortedSet             auxCoordVars;
+    private SortedSet             boundaryVars;
+    private Map                   varToRealType;
+    private Map                   dimsToDomain;
+    private Map                   varToAuxCoordVars;
+    private Map                   varToUnitString;
+
+    /*
+     * The following 5 fields are not "final" to accomodate a bug in JDK 1.2.
+     */
+    private static String[]       CF_CONVENTIONS_STRINGS;
+    private static QuantityDBImpl cfQuantityDB;
+    private static Variable[]     nilVarArray;
+    private static Comparator     varComparator;
+
+    static
+    {
+        CF_CONVENTIONS_STRINGS =
+            new String[] {"CF-1.0", "COARDS/CF-1.0", "COARDS"};
+        nilVarArray = new Variable[0];
+        cfQuantityDB = new QuantityDBImpl((QuantityDB)null);
+        varComparator =
+            new Comparator()
+            {
+                public int compare(Object o1, Object o2)
+                {
+                    return
+                        ((Variable)o1).getName().compareTo(
+                        ((Variable)o2).getName());
+                }
+            };
+        try
+        {
+            cfQuantityDB.add(
+                new String[] {
+                    "pressure","Pa",
+                    "stress","Pa",
+                    "mass","kg",
+                    "area","m2",
+                    "volume","m3",
+                    "temperature","K",
+                    "thickness","m",
+                    "height","m",
+                    "altitude","m",
+                    "depth","m",
+                    "mass_fraction","1",
+                    "mass_mixing_ratio","1",
+                    "volume_fraction","1",
+                    "area_fraction","1",
+                    "heat_flux_density","W m-2",
+                    "heat_flux","W",
+                    "power","W",
+                    "mass_flux_density","kg m-2 s-1",
+                    "mass_flux","kg s-1",
+                    "volume_flux_density","m s-1",
+                    "volume_flux","m3 s-1",
+                    "energy","J",
+                    "energy_content","J m-2",
+                    "energy_density","J m-3",
+                    "content","kg m-2",
+                    "amount","kg m-2",
+                    "speed","m s-1",
+                    "velocity","m s-1",
+                    "mass","kg",
+                    "time","s",
+                    "period","s",
+                    "density","kg m-3",
+                    "longitude","degrees_E",
+                    "latitude","degrees_N",
+                    "binary_mask","1",
+                    "data_mask","1",
+                    "frequency","s-1",
+                    "frequency_of_occurrence","s-1",
+                    "probability","1",
+                    "sigma","1",
+                    "hybrid_sigma_pressure","1",
+                    "sigma_term_in_hybrid_sigma_pressure","1",
+                    "pressure_fraction_term_in_hybrid_sigma_pressure","1",
+                    "pressure_term_in_hybrid_sigma_pressure","Pa",
+                    "hybrid_height","m",
+                    "height_term_in_hybrid_height","1",
+                    "altitude_term_in_hybrid_height","1",
+                    "model_level_number","1",
+                    "forecast_reference_time","s",
+                    "forecast_period","s",
+                    "specific_eddy_kinetic_energy","m2 s-2",
+                    "sea_floor_depth","m",
+                    "partial_pressure","Pa",
+                    "surface_air_pressure","Pa",
+                    "air_pressure","Pa",
+                    "air_pressure_anomaly","Pa",
+                    "rate_of_change_of_air_pressure","Pa s-1",
+                    "air_density","kg m-3",
+                    "sea_water_density","kg m-3",
+                    "sea_water_potential_density","kg m-3",
+                    "wind_speed","m s-1",
+                    "eastward_wind","m s-1",
+                    "northward_wind","m s-1",
+                    "wind_direction","degree",
+                    "grid_eastward_wind","m s-1",
+                    "grid_northward_wind","m s-1",
+                    "air_potential_temperature","K",
+                    "soil_water_content","kg m-2",
+                    "specific_humidity","1",
+                    "mass_fraction_of_water_in_air","1",
+                    "cloud_area_fraction","1",
+                    "convective_cloud_area_fraction","1",
+                    "low_cloud_area_fraction","1",
+                    "medium_cloud_area_fraction","1",
+                    "high_cloud_area_fraction","1",
+                    "altitude_at_cloud_base","m",
+                    "air_pressure_at_cloud_base","Pa",
+                    "altitude_at_cloud_top","m",
+                    "air_pressure_at_cloud_top","Pa",
+                    "cloud_condensed_water_content","kg m-2",
+                    "atmosphere_water_content","kg m-2",
+                    "soil_temperature","K",
+                    "canopy_water_amount","kg m-2",
+                    "LWE_thickness_of_canopy_water_amount","m",
+                    "surface_snow_amount","kg m-2",
+                    "surface_snow_thickness","m",
+                    "LWE_thickness_of_surface_snow_amount","m",
+                    "surface_snow_area_fraction","1",
+                    "surface_temperature","K",
+                    "atmosphere_boundary_layer_thickness","m",
+                    "surface_roughness_length","m",
+                    "eastward_sea_water_velocity","m s-1",
+                    "northward_sea_water_velocity","m s-1",
+                    "sea_water_speed","m s-1",
+                    "direction_of_sea_water_velocity","degree",
+                    "land_binary_mask","1",
+                    "sea_ice_area_fraction","1",
+                    "sea_ice_thickness","m",
+                    "sea_ice_amount","kg m-2",
+                    "sea_ice_mass","kg",
+                    "sea_ice_area","m2",
+                    "sea_ice_extent","m2",
+                    "sea_ice_volume","m3",
+                    "sea_ice_freeboard","m",
+                    "sea_ice_draft","m",
+                    "surface_altitude","m",
+                    "surface_temperature_anomaly","K",
+                    "LWE_thickness_of_soil_water_content","m",
+                    "soil_water_content_at_field_capacity","kg m-2",
+                    "ratio_of_soil_water_content_to_soil_water_content_at_field_capacity","1",
+                    "vegetation_area_fraction","1",
+                    "root_depth","m",
+                    "surface_albedo","1",
+                    "surface_albedo_assuming_no_snow","1",
+                    "surface_albedo_assuming_deep_snow","1",
+                    "mass_fraction_of_O3_in_air","1",
+                    "molar_fraction_of_O3_in_air","1",
+                    "upward_wind","m s-1",
+                    "upward_wind_expressed_as_rate_of_change_of_sigma","s-1",
+                    "atmosphere_SO4_content","kg m-2",
+                    "land_area_fraction","1",
+                    "sea_area_fraction","1",
+                    "land_ice_area_fraction","1",
+                    "leaf_area_index","1",
+                    "canopy_height","m",
+                    "mass_fraction_of_unfrozen_water_in_soil_water","1",
+                    "mass_fraction_of_frozen_water_in_soil_water","1",
+                    "soil_frozen_water_content","kg m-2",
+                    "soil_albedo","1",
+                    "snow_soot_content","kg m-2",
+                    "atmosphere_energy_content","J m-2",
+                    "soil_carbon_content","kg m-2",
+                    "snow_grain_size","m",
+                    "snow_temperature","K",
+                    "air_temperature","K",
+                    "air_temperature_anomaly","K",
+                    "TOA_downward_radiative_heat_flux_density","W m-2",
+                    "surface_downward_shortwave_heat_flux_density","W m-2",
+                    "downward_shortwave_heat_flux_density","W m-2",
+                    "downward_longwave_heat_flux_density","W m-2",
+                    "TOA_downward_shortwave_heat_flux_density","W m-2",
+                    "TOA_incoming_shortwave_heat_flux_density","W m-2",
+                    "TOA_outgoing_shortwave_heat_flux_density","W m-2",
+                    "TOA_outgoing_shortwave_heat_flux_density_assuming_clear_sky","W m-2",
+                    "surface_incident_shortwave_heat_flux_density_assuming_clear_sky","W m-2",
+                    "surface_reflected_shortwave_heat_flux_density_assuming_clear_sky","W m-2",
+                    "surface_reflected_shortwave_heat_flux_density","W m-2",
+                    "large_scale_cloud_area_fraction","1",
+                    "rate_of_change_of_air_temperature_due_to_shortwave_heating","K s-1",
+                    "rate_of_change_of_air_temperature_due_to_shortwave_heating_assuming_clear_sky","K s-1",
+                    "surface_incident_shortwave_heat_flux_density","W m-2",
+                    "tropopause_downward_shortwave_heat_flux_density","W m-2",
+                    "tropopause_upward_shortwave_heat_flux_density_from_below","W m-2",
+                    "surface_downward_longwave_heat_flux_density","W m-2",
+                    "surface_emitted_longwave_heat_flux_density","W m-2",
+                    "surface_emitted_longwave_heat_flux_density_assuming_clear_sky","W m-2",
+                    "TOA_upward_longwave_heat_flux_density","W m-2",
+                    "TOA_downward_longwave_heat_flux_density","W m-2",
+                    "TOA_upward_longwave_heat_flux_density_assuming_clear_sky","W m-2",
+                    "surface_incident_longwave_heat_flux_density","W m-2",
+                    "surface_incident_longwave_heat_flux_density_assuming_clear_sky","W m-2",
+                    "rate_of_change_of_air_temperature_due_to_longwave_heating","K s-1",
+                    "rate_of_change_of_air_temperature_due_to_longwave_heating_assuming_clear_sky","K s-1",
+                    "tropopause_downward_longwave_heat_flux_density","W m-2",
+                    "tropopause_downward_longwave_heat_flux_density_from_above","W m-2",
+                    "downward_heat_flux_density_in_sea_ice","W m-2",
+                    "downward_heat_flux_density_in_soil","W m-2",
+                    "drag_coefficient","1",
+                    "derivative_of_wind_speed_wrt_altitude_in_constant_flux_layer","s-1",
+                    "downward_stress_in_constant_flux_layer","Pa",
+                    "bulk_Richardson_number","1",
+                    "upward_sensible_heat_flux_density_in_air","W m-2",
+                    "downward_eastward_stress_in_air","Pa",
+                    "downward_northward_stress_in_air","Pa",
+                    "upward_water_vapour_mass_flux_density_in_air","kg m-2 s-1",
+                    "wind_mixing_energy_flux_density_into_sea","W m-2",
+                    "surface_upward_sensible_heat_flux_density","W m-2",
+                    "surface_downward_sensible_heat_flux_density","W m-2",
+                    "surface_upward_sensible_heat_flux_density_from_sea","W m-2",
+                    "surface_upward_water_vapour_mass_flux_density","kg m-2 s-1",
+                    "surface_upward_latent_heat_flux_density","W m-2",
+                    "surface_downward_latent_heat_flux_density","W m-2",
+                    "mass_fraction_of_cloud_ice_in_air","1",
+                    "atmosphere_cloud_ice_content","kg m-2",
+                    "mass_fraction_of_cloud_liquid_water_in_air","1",
+                    "atmosphere_cloud_liquid_water_content","kg m-2",
+                    "visibility","m",
+                    "dew_point_temperature","K",
+                    "freezing_temperature_of_sea_water","K",
+                    "surface_snow_melt_amount","kg m-2",
+                    "surface_snow_melt_heat_flux_density","W m-2",
+                    "transpiration_amount","kg m-2",
+                    "transpiration_mass_flux_density","kg m-2 s-1",
+                    "gross_primary_productivity_of_carbon_amount","kg m-2 s-1",
+                    "net_primary_productivity_of_carbon_amount","kg m-2 s-1",
+                    "plant_respiration_mass_flux_density","kg m-2 s-1",
+                    "large_scale_rainfall_amount","kg m-2",
+                    "large_scale_snowfall_amount","kg m-2",
+                    "large_scale_rainfall_mass_flux_density","kg m-2 s-1",
+                    "large_scale_snowfall_mass_flux_density","kg m-2 s-1",
+                    "relative_humidity","1",
+                    "convective_rainfall_amount","kg m-2",
+                    "convective_snowfall_amount","kg m-2",
+                    "rate_of_change_of_specific_humidity_due_to_convection","s-1",
+                    "convective_rainfall_mass_flux_density","kg m-2 s-1",
+                    "convective_snowfall_mass_flux_density","kg m-2 s-1",
+                    "air_pressure_at_convective_cloud_base","Pa",
+                    "air_pressure_at_convective_cloud_top","Pa",
+                    "mass_fraction_of_convective_condensed_water_in_air","1",
+                    "rainfall_mass_flux_density","kg m-2 s-1",
+                    "snowfall_mass_flux_density","kg m-2 s-1",
+                    "precipitation_mass_flux_density","kg m-2 s-1",
+                    "specific_potential_energy","J kg-1",
+                    "specific_convectively_available_potential_energy","J kg-1",
+                    "precipitation_amount","kg m-2",
+                    "large_scale_precipitation_amount","kg m-2",
+                    "convective_precipitation_amount","kg m-2",
+                    "convective_precipitation_mass_flux_density","kg m-2 s-1",
+                    "rate_of_change_of_wind_due_to_convention","m s-2",
+                    "rate_of_change_of_specific_humidity_due_to_diabatic_processes","s-1",
+                    "rate_of_change_of_air_temperature_due_to_diabatic_processes","s-1",
+                    "rate_of_change_of_air_temperature_due_to_large_scale_precipitation","s-1",
+                    "rate_of_change_of_air_temperature_due_to_moist_convection","s-1",
+                    "rate_of_change_of_air_temperature_due_to_dry_convection","s-1",
+                    "surface_eastward_gravity_wave_stress","Pa",
+                    "surface_northward_gravity_wave_stress","Pa",
+                    "rate_of_change_of_wind_due_to_gravity_wave_drag","m s-2",
+                    "rate_of_change_of_eastward_wind_due_to_gravity_wave_drag","m s-2",
+                    "rate_of_change_of_northward_wind_due_to_gravity_wave_drag","m s-2",
+                    "surface_runoff_amount","kg m-2",
+                    "subsurface_runoff_amount","kg m-2",
+                    "surface_runoff_mass_flux_density","kg m-2 s-1",
+                    "subsurface_runoff_mass_flux_density","kg m-2 s-1",
+                    "runoff_mass_flux_density","kg m-2 s-1",
+                    "wet_bulb_temperature","K",
+                    "omega","Pa s-1",
+                    "Ertel_potential_vorticity","K m2 kg-1 s-1",
+                    "product_of_eastward_wind_and_northward_wind","m2 s-2",
+                    "product_of_air_temperature_and_eastwind_wind","K m s-1",
+                    "product_of_air_temperature_and_northward_wind","K m s-1",
+                    "square_of_air_temperature","K2",
+                    "square_of_eastward_wind","m2 s-2",
+                    "square_of_northward_wind","m2 s-2",
+                    "product_of_eastward_wind_and_omega","Pa m s-2",
+                    "product_of_northward_wind_and_omega","Pa m s-2",
+                    "product_of_eastward_wind_and_specific_humidity","m s-1",
+                    "product_of_northward_wind_and_specific_humidity","m s-1",
+                    "product_of_air_temperature_and_omega","K Pa s-1",
+                    "atmosphere_kinetic_energy_content","J m-2",
+                    "geopotential_height","m",
+                    "geopotential_height_anomaly","m",
+                    "product_of_eastward_wind_and_geopotential_height","m2 s-1",
+                    "product_of_northward_wind_and_geopotential_height","m2 s-1",
+                    "freezing_level_altitude","m",
+                    "freezing_level_air_pressure","Pa",
+                    "tropopause_air_pressure","Pa",
+                    "tropopause_air_temperature","K",
+                    "tropopause_altitude","m",
+                    "sea_level_air_pressure","Pa",
+                    "vegetation_carbon_content","kg m-2",
+                    "litter_carbon_mass_flux_density","kg m-2 s-1",
+                    "sea_water_temperature","K",
+                    "sea_water_potential_temperature","K",
+                    "sea_water_salinity","1",
+                    "baroclinic_eastward_sea_water_velocity","m s-1",
+                    "baroclinic_northward_sea_water_velocity","m s-1",
+                    "ocean_barotropic_streamfunction","m3 s-1",
+                    "rate_of_change_of_ocean_barotropic_streamfunction","m3 s-2",
+                    "sea_surface_elevation","m",
+                    "sea_surface_elevation_anomaly","m",
+                    "barotropic_eastward_sea_water_velocity","m s-1",
+                    "barotropic_northward_sea_water_velocity","m s-1",
+                    "ocean_mixed_layer_thickness","m",
+                    "eastward_stress_of_sea_ice_on_ocean","Pa",
+                    "northward_stress_of_sea_ice_on_ocean","Pa",
+                    "surface_snow_thickness_on_sea_ice","m",
+                    "upward_sensible_heat_flux_density_in_sea_water_at_sea_ice_base","W m-2",
+                    "sea_ice_speed","m s-1",
+                    "sea_ice_eastward_velocity","m s-1",
+                    "sea_ice_northward_velocity","m s-1",
+                    "direction_of_sea_ice_velocity","degree",
+                    "divergence_of_sea_ice_velocity","s-1",
+                    "rate_of_change_of_sea_ice_thickness_due_to_thermodynamics","m s-1",
+                    "surface_downward_eastward_stress","Pa",
+                    "surface_downward_northward_stress","Pa",
+                    "heat_flux_correction","W m-2",
+                    "water_flux_correction","kg m-2 s-1",
+                    "ocean_isopycnal_layer_thickness_diffusivity","m2 s-1",
+                    "sea_water_upward_velocity","m s-1",
+                    "northward_heat_flux_in_ocean","W",
+                    "northward_salt_mass_flux_in_ocean","kg s-1",
+                    "northward_fresh_water_mass_flux_in_ocean","kg s-1",
+                    "significant_height_of_wind_waves_and_swell_waves","m",
+                    "direction_of_wind_wave_velocity","degree",
+                    "significant_height_of_wind_waves","m",
+                    "wind_wave_period","s",
+                    "direction_of_swell_wave_velocity","degree",
+                    "significant_height_of_swell_waves","m",
+                    "swell_wave_period","s"},
+                new String[] {}
+            );
+        }
+        catch (Exception e)
+        {
+            System.err.println(
+                "ERROR: " +
+                "Couldn't initialize class visad.data.netcdf.in.CfView: " + e);
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Constructs from a netCDF dataset and a quantity database.  The quantity
+     * database will be supplemented with another database specific to this
+     * view.
+     *
+     * @param netcdf                The netCDF dataset.
+     * @param quantDb               The default quantity database.
+     * @throws NullPointerException if the netCDF dataset argument is
+     *                              <code>null</code>.
+     * @throws IllegalArgumentException
+     *                              if the netCDF dataset doesn't follow the
+     *                              conventions of this view.
+     */
+    CfView(Netcdf netcdf, QuantityDB quantDb)
+    {
+        this(netcdf, quantDb, false);
+    }
+
+    /**
+     * Constructs from a netCDF dataset and a quantity database.  The quantity
+     * database will be supplemented with another database specific to this
+     * view.
+     *
+     * @param netcdf                The netCDF dataset.
+     * @param quantDb               The default quantity database.
+     * @param charToText            Convert char variables to Text if true
+     * @throws NullPointerException if the netCDF dataset argument is
+     *                              <code>null</code>.
+     * @throws IllegalArgumentException
+     *                              if the netCDF dataset doesn't follow the
+     *                              conventions of this view.
+     */
+    CfView(Netcdf netcdf, QuantityDB quantDb, boolean charToText)
+    {
+        super(netcdf, quantDb, charToText);
+        /*
+         * Check the "Conventions" global attribute.
+         */
+        {
+            String conventions = getConventionsString(netcdf);
+            if (conventions == null)
+                throw new IllegalArgumentException(
+                    "No \"Conventions\" attribute in netCDF dataset");
+            int i;
+            for (i = 0; i < CF_CONVENTIONS_STRINGS.length; i++)
+                if (conventions.equals(CF_CONVENTIONS_STRINGS[i]))
+                    break;
+            if (i >= CF_CONVENTIONS_STRINGS.length)
+                throw new IllegalArgumentException(
+                    "Illegal \"Conventions\" attribute: \"" + conventions +
+                    "\"");
+        }
+        /*
+         * Allocate caches.
+         */
+        varToRealType = new WeakHashMap();
+        varToUnitString = new WeakHashMap();
+        dimsToDomain = new WeakHashMap();
+        varToAuxCoordVars = new WeakHashMap();
+        /*
+         * Build a database of all netCDF variables that contain metadata
+         * rather than data.
+         */
+        auxCoordVars = new TreeSet(varComparator);
+        boundaryVars = new TreeSet();
+        for (VariableIterator varIter = getNetcdf().iterator();
+            varIter.hasNext(); )
+        {
+            Variable   var = varIter.next();
+            Variable[] vars = getAuxCoordVars(var);
+            for (int i = 0; i < vars.length; i++)
+                auxCoordVars.add(vars[i]);
+            Variable   boundVar = getBoundaryVar(var);
+            if (boundVar != null)
+                boundaryVars.add(boundVar);
+        }
+    }
+
+    /**
+     * Returns the "coordinates" attribute of a variable.  Returns 
+     * <code>null</code> if the variable doesn't have such an attribute.
+     *
+     * @param var                   The variable.
+     * @return                      The value of the attribute or 
+     *                              <code>null</code>.
+     * @throws NullPointerException if the variable is <code>null</code>.
+     */
+    private String getAuxCoordVarString(Variable var)
+    {
+        return getAttributeString(var, "coordinates");
+    }
+
+    /**
+     * Indicates if a given variable is a CF auxilliary coordinate variable.
+     *
+     * @param var                    The variable.
+     * @return                       <code>true</code> if and only if the
+     *                               variable is an auxilliary coordinate 
+     *                               variable.
+     */
+    private boolean isAuxCoordVar(Variable var)
+    {
+        return auxCoordVars.contains(var);
+    }
+
+    /**
+     * Indicates if a given netCDF variable has any CF auxilliary
+     * coordinate variables.
+     *
+     * @param var                  The variable.
+     * @return                     True if and only if the variable has
+     *                             any auxilliary coordinate variables.
+     * @throw NullPointerException if the variable is <code>null</code>.
+     */
+    private boolean hasAuxCoordVars(Variable var)
+    {
+        return getAuxCoordVars(var).length > 0;
+    }
+
+    /**
+     * <p>Returns the CF auxilliary coordinate variables of a given netCDF
+     * variable in netCDF order (outermost dimension first).  The returned array
+     * will be empty if the netCDF variable doesn't have any CF auxilliary
+     * coordinate variables.</p>
+     *
+     * <p>This implementation uses {@link #getAuxCoordVarString(Variable)},
+     * {@link isNumeric(String)}, and {@link #getVariable(String)}.
+     *
+     * @param var                  The variable.
+     * @return                     The auxilliary coordinate variables of the
+     *                             variable.  Will have zero length if the
+     *                             variable doesn't have any.
+     * @throw NullPointerException if the variable is <code>null</code>.
+     */
+    private Variable[] getAuxCoordVars(Variable var)
+    {
+        if (var == null)
+            throw new NullPointerException();
+        /*
+         * This implementation caches results to improve performance.
+         */
+        synchronized(varToAuxCoordVars)
+        {
+            Variable[] vars = (Variable[])varToAuxCoordVars.get(var);
+            if (vars == null)
+            {
+                String    attrStr = getAuxCoordVarString(var);
+                if (attrStr == null)
+                {
+                    vars = nilVarArray;
+                }
+                else
+                {
+                    ArrayList list = new ArrayList(7);
+                    for (StringTokenizer st = new StringTokenizer(attrStr);
+                        st.hasMoreTokens(); )
+                    {
+                        String name = st.nextToken();
+                        // ignore non-mumeric "list" variables
+                        if (isNumeric(name))
+                            list.add(getVariable(name));
+                    }
+                    vars = (Variable[])list.toArray(nilVarArray);
+                }
+                varToAuxCoordVars.put(var, vars);
+            }
+            return (Variable[])vars.clone();
+        }
+    }
+
+    /**
+     * Returns the CF boundary variable of a given netCDF variable or
+     * <code>null</code> if the netCDF variable has no boundary variable.  If
+     * the variable referenced by the input variable's <code>bounds</code>
+     * attribute doesn't exist, then a warning message is printed to {@link
+     * System#err} and <code>null</code> is returned.
+     *
+     * @param var                  The variable.
+     * @return                     The associated CF boundary variable or
+     *                             <code>null</code>.
+     * @throw NullPointerException if the variable is <code>null</code>.
+     */
+    private Variable getBoundaryVar(Variable var)
+    {
+        if (var == null)
+            throw new NullPointerException();
+        String attrStr = getAttributeString(var, "bounds");
+        if (attrStr == null)
+            return null;
+        Variable boundVar = getVariable(attrStr);
+        if (boundVar == null)
+            System.err.println(
+                "WARNING: " +
+                "The boundary variable of variable \"" + var.getName() +
+                "\" doesn't exist");
+        return boundVar;
+    }
+
+    /**
+     * Indicates if a given variable is a CF boundary variable.
+     *
+     * @param var                    The variable.
+     * @return                       <code>true</code> if and only if the
+     *                               variable is a CF boundary variable.
+     */
+    private boolean isBoundaryVar(Variable var)
+    {
+        return boundaryVars.contains(var);
+    }
+
+    /**
+     * Gets the standard name of a netCDF variable from the
+     * <code>long_name</code> attribute.  If the attribute doesn't exist
+     * then <code>null</code> is returned.  If the the attribute value isn't
+     * a string, then and error message is printed and <code>null</code> is
+     * returned.
+     *
+     * @param var               A netCDF variable.
+     * @return                  The long name of <code>var</code> or 
+     *                          <code>null</code>.
+     */
+    private String getStandardName(Variable var)
+    {
+        return getAttributeString(var, "standard_name");
+    }
+
+    /**
+     * Returns the string value of the unit attribute of a netCDF variable.
+     * Returns <code>null</code> if the unit attribute is missing or invalid.
+     * Because the CF netCDF convention requires the use of the unit attribute,
+     * this method prints a warning message to {@link System#err} if the
+     * variable doesn't have a unit attribute.
+     *
+     * @param var               A netCDF variable.
+     * @return                  The unit of the values of <code>var</code> or
+     *                          <code>null</code>.
+     */
+    protected String getUnitString(Variable var)
+    {
+        /*
+         * This method caches results to improve performance and to reduce the
+         * number of warning messages.
+         */
+        String str;
+        synchronized(varToUnitString)
+        {
+            // NB: A null entry may exist.
+            str = (String)varToUnitString.get(var);
+            if (!varToUnitString.containsKey(var))
+            {
+                str = super.getUnitString(var);  // doesn't print message
+                if (str == null)
+                    System.err.println(
+                        "WARNING: " +
+                        "Variable \"" + var.getName() +
+                        "\" doesn't have a unit attribute.");
+                varToUnitString.put(var, str);  // cache result
+            }
+        }
+        return str;
+    }
+
+    /**
+     * <p>Returns the unit of a netCDF variable according to the variable's unit
+     * attribute.  Returns <code>null</code> if the unit attribute is missing or
+     * invalid.</p>
+     *
+     * This implementation uses {@link #getUnitString(Variable)} and {@link
+     * View#getUnitFromAttribute(Variable)}.
+     *
+     * @param var                   A netCDF variable.
+     * @return                      The unit of <code>var</code> or
+     *                              <code>null</code>.
+     * @throws NullPointerException if the variable is <code>null</code>.
+     */
+    protected Unit getUnitFromAttribute(Variable var)
+    {
+        String unitStr = getUnitString(var);
+        if (unitStr == null)
+            return null;
+        if (unitStr.equals("level") ||
+            unitStr.equals("layer") ||
+            unitStr.equals("sigma_level"))
+        {
+            return CommonUnit.dimensionless;
+        }
+        return super.getUnitFromAttribute(var);
+    }
+
+    /**
+     * <p>Return the VisAD RealType of a netCDF variable.  If the variable is a
+     * type of timestamp and references a non-supported calendar system, then a
+     * warning message is printed to {@link System#err} and an attempt is made
+     * to create a new {@link RealType} with a different name.</p>
+     *
+     * <p>This implementation uses {@link * View#getRealType(Variable)}.</p>
+     *
+     * @param var               The netCDF variable.
+     * @return                  The VisAD RealType of <code>var</code>.
+     * @throws TypeException    if a corresponding {@link RealType} needed
+     *                          to be created but couldn't.
+     */
+    protected RealType getRealType(Variable var)
+        throws TypeException
+    {
+        RealType type;
+        /*
+         * This method caches results to improve performance and to reduce
+         * the number of warning messages.
+         */
+        synchronized(varToRealType)
+        {
+            type = (RealType)varToRealType.get(var);
+            if (type == null)
+            {
+                type = getRealTypeFromStandardName(var);
+                if (type != null)
+                    varToRealType.put(var, type);  // cache result
+                else
+                    type = super.getRealType(var);
+                Unit unit = type.getDefaultUnit();
+                if (unit instanceof OffsetUnit &&
+                    unit.getAbsoluteUnit().isConvertible(SI.second))
+                {
+                    String str = getAttributeString(var, "calendar");
+                    if (str != null &&
+                        !str.equals("gregorian") && !str.equals("standard"))
+                    {
+                        String  newName = newName(var);
+                        System.err.println(
+                            "WARNING: " +
+                            "No support for \"" + str + 
+                            "\" calendar of variable \"" + var + "\".  " +
+                            "Attempting to create new quantity \"" + newName +
+                            "\" with non-timescale unit.");
+                        type =
+                            RealType.getRealType(
+                                newName, unit.getAbsoluteUnit());
+                        varToRealType.put(var, type);  // cache result
+                    }
+                }
+            }
+        }
+        return type;
+    }
+
+    /**
+     * If the unit attribute of the variable is inconvertible with the unit of
+     * the variables's standard quantity, then a warning message is printed
+     * to {@link System#err} and an attempt is made to create a new {@link
+     * RealType} with a different name.
+     *
+     * @return The corresponding {@link RealType} or <code>null</code>.
+     */
+    private RealType getRealTypeFromStandardName(Variable var)
+    {
+        RealType type;
+        String   name = getStandardName(var);
+        if (name == null)
+        {
+            type = null;
+        }
+        else
+        {
+            type = cfQuantityDB.get(name);
+            if (type != null)
+            {
+                Unit unit = getUnitFromAttribute(var);
+                if (unit != null &&
+                    !Unit.canConvert(unit, type.getDefaultUnit()))
+                {
+                    String  newName = newName(var);
+                    System.err.println(
+                        "WARNING: " +
+                        "The units attribute of variable " + var.getName() +
+                        " is incompatible with the unit of the quantity" +
+                        " referenced by the standard-name attribute.  " +
+                        "Attempting to create new quantity \"" + newName +
+                        "\".");
+                    type = RealType.getRealType(newName, unit);
+                }
+            }
+        }
+        return type;
+    }
+
+    /**
+     * Gets an iterator over the virtual VisAD data objects determined by
+     * this view.
+     *
+     * @return                  An iterator for the virtual VisAD data objects
+     *                          in the view.
+     */
+    public VirtualDataIterator getVirtualDataIterator()
+    {
+        return new DataIterator();
+    }
+
+    /**
+     * <p>Indicates if a given variable should be ignored during iteration.</p>
+     *
+     * <p>This implementation returns the logical "or" of {@link
+     * #isCoordinateVariable(Variable)}, {@link #isAuxCoordVar(Variable)}, and
+     * {@link #isBoundaryVar(Variable)}.</p>
+     *
+     * @return                    <code>true</code> if and only if the variable
+     *                            should be ignored.
+     */
+    protected boolean isIgnorable(Variable var)
+    {
+        return
+            isCoordinateVariable(var) ||
+            isAuxCoordVar(var) ||
+            isBoundaryVar(var);
+    }
+
+    /**
+     * Returns the domain of a netCDF variable.  This method supports CF
+     * auxilliary coordinate variables.
+     *
+     * @param var               A netCDF variable.
+     * @return                  The domain of the given variable.
+     * @throws NullPointerException
+     *                          if the variable is <code>null</code>.
+     * @throws IllegalArgumentException
+     *                          if the rank of the variable is zero.
+     * @throws TypeException    if a {@link RealType} needed to be created but
+     *                          couldn't.
+     * @throws IOException      if a netCDF read-error occurs.
+     */
+    protected Domain getDomain(Variable var)
+        throws TypeException, IOException
+    {
+        ArrayList   list = new ArrayList(7);
+        ArrayList   vars = new ArrayList(7);
+        Variable[]  auxVars = getAuxCoordVars(var);
+        {
+            Dimension[] dims = getDimensions(var);
+            list = new ArrayList(dims.length + auxVars.length);
+            for (int i = 0; i < dims.length; i++)
+                list.add(new SimpleDimension(dims[i]));
+        }
+        for (int iaux = 0; iaux < auxVars.length; )
+        {
+            Variable    auxVar = auxVars[iaux];
+            Dimension[] dims = getDimensions(auxVar);
+            vars.add(auxVar);
+            boolean     auxVarsHaveUnits = true;
+            for (int j = iaux+1;
+                j < auxVars.length &&
+                    Arrays.equals(dims, getDimensions(auxVars[j]));
+                j++)
+            {
+                vars.add(auxVars[j]);
+                auxVarsHaveUnits &=
+                    getRealType(auxVars[j]).getDefaultUnit() != null;
+            }
+            boolean dimsHaveUnits = true;
+            for (int j = 0; j < dims.length; j++)
+                dimsHaveUnits &= 
+                    getRealType(dims[j]).getDefaultUnit() != null;
+            /*
+             * Ignore CF auxilliary coordinate variables if they don't
+             * have units and the regular dimensions do because that
+             * probably indicates that the auxilliary coordinate variables
+             * are "alternative coordinates" and that the more important
+             * coordinates are the regular dimensions.  Otherwise, favor
+             * auxilliary coordinates variables.
+             */
+            if (!dimsHaveUnits || auxVarsHaveUnits)
+            {
+                int index = list.indexOf(new SimpleDimension(dims[0]));
+                try
+                {
+                    for (int j = 0; j < dims.length; j++)
+                        list.remove(
+                            list.indexOf(new SimpleDimension(dims[j])));
+                }
+                catch (IndexOutOfBoundsException e)
+                {
+                    throw new IllegalArgumentException(
+                        "Invalid dimensional structure: variable \"" +
+                        var.getName() + "\"");
+                }
+                list.add(
+                    index,
+                    new AuxCoordVarsDimension(
+                        (Variable[])vars.toArray(nilVarArray)));
+            }
+            iaux += vars.size();
+            vars.clear();
+        }
+        /*
+         * This implementation caches results to improve performance.
+         */
+        DimensionList domain;
+        synchronized (dimsToDomain)
+        {
+            domain = (DimensionList)dimsToDomain.get(list);
+            if (domain == null)
+            {
+                domain = new DimensionList(var, list);  // list not copied
+                /*
+                 * The clone() method is invoked to ensure that the
+                 * DimensionList value in the WeakHashMap doesn't reference
+                 * its key.
+                 */
+                dimsToDomain.put(list.clone(), domain);
+            }
+        }
+        return domain;
+    }
+
+    /**
+     * Iterates over the virtual VisAD data objects in a netCDF dataset
+     * according to the CF conventions.
+     */
+    final class DataIterator
+        extends VirtualDataIterator
+    {
+        /**
+         * The netCDF variable iterator.
+         */
+        private final VariableIterator  varIter;
+
+        /**
+         * Constructs from nothing.
+         *
+         * @param view          A view of a netCDF dataset.
+         */
+        DataIterator()
+        {
+            super(CfView.this);
+            varIter = CfView.this.getNetcdf().iterator();
+        }
+
+        /**
+         * Returns a clone of the next virtual VisAD data object.
+         *
+         * <p>This implementation uses {@link #isCharToText()},
+         * {@link #isNumeric(Variable)}, {@link #isIgnorable(Variable)}, 
+         * and {@link #getData(Variable)}.</p>
+         *
+         * @return                      A clone of the next virtual VisAD data
+         *                              object or <code> null</code> if there is
+         *                              no more data.
+         * @throws TypeException        if a {@link RealType} needed
+         *                              to be created but couldn't.
+         * @throws VisADException       Couldn't create necessary VisAD object.
+         */
+        protected VirtualData getData()
+            throws TypeException, VisADException, IOException
+        {
+            while (varIter.hasNext())
+            {
+                Variable        var = varIter.next();
+                // handle text only if charToText == true and rank <= 2
+                if (!isNumeric(var) && (!isCharToText() || var.getRank() > 2))
+                    continue;  // TODO: support arrays of text (Tuple?)
+                if (isIgnorable(var))
+                {
+                    /*
+                     * Ignore coordinate variables, auxilliary coordinate
+                     * variables, and boundary variables.
+                     */
+                    continue;
+                }
+                VirtualScalar   scalar =
+                    (isNumeric(var) == true)
+
+                        ? (VirtualScalar) 
+                            new VirtualReal(getRealType(var),
+                                            var,
+                                            getRangeSet(var),
+                                            getUnitFromAttribute(var),
+                                            getVetter(var))
+
+                        : (VirtualScalar)
+                            new VirtualText(getTextType(var), var);
+
+                return
+                    (var.getRank() == 0 || 
+                     (!isNumeric(var) && var.getRank() == 1))
+                        ? (VirtualData)scalar
+                        : getDomain(var).getVirtualField(
+                            new VirtualTuple(scalar));
+            }
+            return null;        // no more data
+        }
+    }
+
+    /**
+     * The CF domain of a netCDF variable.  A CF domain comprises a list of CF
+     * dimensions.  A CF dimension is either a netCDF dimension or a list of
+     * auxilliary coordinate variables of the same dimensionality (sequence of
+     * netCDF dimensions).
+     */
+    private final class DimensionList
+        extends Domain
+    {
+        /**
+         * Outermost dimension first; at least one element.
+         */
+        private final ArrayList     list;
+        private volatile int        hashCode;
+        private volatile SampledSet domain;
+
+        /**
+         * @param var                       The netCDF variable.
+         * @param list                      The list of CF dimensions of the
+         *                                  variable.
+         * @throws NullPointerException     if the variable or list is 
+         *                                  <code>null</code>.
+         * @throws IllegalArgumentException if the rank of the variable is 0 or
+         *                                  if the variable has auxilliaray
+         *                                  coordinate variables whose
+         *                                  dimensional structure is invalid.
+         * @throws TypeException            if a {@link RealType} needed to be
+         *                                  created but couldn't.
+         */
+        DimensionList(Variable var, ArrayList list)
+            throws TypeException
+        {
+            super(var);
+            this.list = list;  // NOTE: not copied
+        }
+
+        /**
+         * Returns a {@link VirtualField} corresponding to this domain and
+         * a given range.
+         *
+         * @param range                 The range for the {@link VirtualField}.
+         * @throws NullPointerException if the argument is <code>null</code>.
+         * @throws IOException          if a read error occurs.
+         * @throws VisADException       if a VisAD object can't be created.
+         */
+        protected VirtualField getVirtualField(VirtualTuple range)
+            throws VisADException, IOException
+        {
+            VirtualField field;
+            int          nCfDim = list.size();
+            if (nCfDim == 1)
+            {
+                field = VirtualField.newVirtualField(getDomainSet(list), range);
+            }
+            else
+            {
+                CfDimension outerDim = (CfDimension)list.get(0);
+                Unit[]      units = outerDim.getUnits();
+                if (nCfDim == 2 && range.getType() instanceof TextType) { //char
+                   field = 
+                       VirtualField.newVirtualField(
+                           getDomainSet(list.subList(0,1)), range);
+                }
+                else if (units.length > 1 || !CfView.this.isTime(units[0]))
+                {
+                    field =
+                        VirtualField.newVirtualField(getDomainSet(list), range);
+                }
+                else
+                {
+                    field =
+                        VirtualField.newVirtualField(
+                            getDomainSet(list.subList(0, 1)),
+                            new VirtualTuple(
+                                VirtualField.newVirtualField(
+                                    getDomainSet(list.subList(1, nCfDim)),
+                                    range)));
+                }
+            }
+            return field;
+        }
+
+        /**
+         * @throws VisADException   if a VisAD object can't be created.
+         */
+        private SampledSet getDomainSet(List cfDims)
+            throws VisADException, IOException
+        {
+            SampledSet[] sets = new SampledSet[cfDims.size()];
+            for (int i = 0, j = sets.length; i < sets.length; i++)
+                sets[i] = ((CfDimension)cfDims.get(--j)).getDomainSet();
+                    // reverse order
+            return
+                sets.length == 1
+                    ? sets[0]
+                        /*
+                         * WORKAROUND:  The product() method is invoked
+                         * because the VisAD display subsystem has problems
+                         * displaying ProductSet-s as of 2001-08-12.
+                         */
+                    : new ProductSet(sets).product();
+        }
+
+        public boolean equals(Object obj)
+        {
+            if (obj == this)
+                return true;
+            if (!(obj instanceof DimensionList))
+                return false;
+            return list.equals(((DimensionList)obj).list);
+        }
+
+        public int hashCode()
+        {
+            int hash = hashCode;
+            if (hash == 0)
+                hash = hashCode = list.hashCode();
+            return hash;
+        }
+    }
+
+    private abstract class CfDimension
+    {
+        /**
+         * Units are in netCDF order (unit of outermost dimension first).
+         */
+        abstract Unit[] getUnits()
+            throws TypeException;
+
+        abstract SampledSet getDomainSet()
+            throws VisADException, IOException;
+
+        public abstract boolean equals(Object obj);
+
+        public abstract int hashCode();
+    }
+
+    private final class SimpleDimension
+        extends CfDimension
+    {
+        private final Dimension               dim;
+        private transient volatile SampledSet domain;
+
+        SimpleDimension(Dimension dim)
+        {
+            this.dim = dim;
+        }
+
+        /**
+         * @throws TypeException if a {@link RealType} needed
+         *                       to be created but couldn't.
+         */
+        Unit[] getUnits()
+            throws TypeException
+        {
+            return new Unit[] {getRealType(dim).getDefaultUnit()};
+        }
+
+        /**
+         * @throws VisADException   if a VisAD object can't be created.
+         */
+        SampledSet getDomainSet()
+            throws VisADException, IOException
+        {
+            SampledSet set = domain;
+            if (set == null)
+                set = domain = CfView.this.getDomainSet(dim);
+            return set;
+        }
+
+        public boolean equals(Object obj)
+        {
+            if (obj == this)
+                return true;
+            if (!(obj instanceof SimpleDimension))
+                return false;
+            return dim.equals(((SimpleDimension)obj).dim);
+        }
+
+        public int hashCode()
+        {
+            return dim.hashCode();
+        }
+    }
+
+    private final class AuxCoordVarsDimension
+        extends CfDimension
+    {
+        /*
+         * Outermost dimension first; at least one element.
+         */
+        private final Variable[]              vars;
+        private transient volatile int        hashCode;
+        private transient volatile SampledSet domain;
+
+        /**
+         * WARNING: It is the responsibility of the client not to modify
+         * the input array.
+         *
+         * @param auxCoordVars    Auxilliary coordinate variable in netCDF
+         *                        order (outermost dimension first).
+         */
+        AuxCoordVarsDimension(Variable[] auxCoordVars)
+        {
+            if (auxCoordVars.length < 1)
+                throw new IllegalArgumentException();
+            vars = auxCoordVars;  // WARNING: not copied
+        }
+
+        /**
+         * @throws TypeException if a {@link RealType} needed
+         *                       to be created but couldn't.
+         */
+        Unit[] getUnits()
+            throws TypeException
+        {
+            Unit[] units = new Unit[vars.length];
+            for (int i = 0; i < units.length; i++)
+                units[i] = getRealType(vars[i]).getDefaultUnit();
+            return units;
+        }
+
+        /**
+         * @throws TypeException if a {@link RealType} needed
+         *                       to be created but couldn't.
+         */
+        SampledSet getDomainSet()
+            throws IOException, TypeException, VisADException
+        {
+            SampledSet set = domain;
+            if (set == null)
+            {
+                Variable var0 = vars[0];
+                int      nDim = var0.getRank();
+                int[]    lengths = var0.getLengths();
+                // reverse order
+                for (int i = 0, j = nDim; i < nDim/2; i++)
+                {
+                    int n = lengths[--j];
+                    lengths[j] = lengths[i];
+                    lengths[i] = n;
+                }
+                int        nVar = vars.length;
+                RealType[] types = new RealType[nVar];
+                Unit[]     units = new Unit[nVar];
+                float[][]  values = new float[nVar][];
+                for (int i = 0, j = nVar; i < nVar; i++)
+                {
+                    Variable var = vars[--j]; // reverse order
+                    types[i] = getRealType(var);
+                    values[i] = toFloat(var.toArray());
+                    units[i] = getUnitFromAttribute(var);
+                }
+                set = domain =
+                    GriddedSet.create(
+                        nVar == 1
+                            ? (MathType)types[0]
+                            : new RealTupleType(types),
+                        values,
+                        lengths,
+                        (CoordinateSystem)null,
+                        units,
+                        (ErrorEstimate[])null);
+            }
+            return set;
+        }
+
+        private float[] toFloat(Object obj)
+        {
+            if (obj instanceof byte[])
+                return toFloat((byte[])obj);
+            if (obj instanceof short[])
+                return toFloat((short[])obj);
+            if (obj instanceof int[])
+                return toFloat((int[])obj);
+            if (obj instanceof float[])
+                return toFloat((float[])obj);
+            return toFloat((double[])obj);
+        }
+
+        private float[] toFloat(byte[] values)
+        {
+            float[] vals = new float[values.length];
+            for (int i = 0; i < vals.length; i++)
+                vals[i] = values[i];
+            return vals;
+        }
+
+        private float[] toFloat(short[] values)
+        {
+            float[] vals = new float[values.length];
+            for (int i = 0; i < vals.length; i++)
+                vals[i] = values[i];
+            return vals;
+        }
+
+        private float[] toFloat(int[] values)
+        {
+            float[] vals = new float[values.length];
+            for (int i = 0; i < vals.length; i++)
+                vals[i] = values[i];
+            return vals;
+        }
+
+        private float[] toFloat(float[] values)
+        {
+            return values;  // WARNING: Not copied
+        }
+
+        private float[] toFloat(double[] values)
+        {
+            float[] vals = new float[values.length];
+            for (int i = 0; i < vals.length; i++)
+                vals[i] = (float)values[i];
+            return vals;
+        }
+
+        public boolean equals(Object obj)
+        {
+            if (obj == this)
+                return true;
+            if (!(obj instanceof AuxCoordVarsDimension))
+                return false;
+            AuxCoordVarsDimension that = (AuxCoordVarsDimension)obj;
+            if (vars == that.vars)
+                return true;
+            if (vars.length != that.vars.length)
+                return false;
+            for (int i = 0; i < vars.length; i++)
+                if (!vars[i].getName().equals(that.vars[i].getName()))
+                    return false;
+            return true;
+        }
+
+        public int hashCode()
+        {
+            int hash = hashCode;
+            if (hash == 0)
+            {
+                hash = 0;
+                for (int i = 0; i < vars.length; i++)
+                    hash ^= vars[i].getName().hashCode();
+                hashCode = hash;
+            }
+            return hash;
+        }
+    }
+}
diff --git a/visad/data/netcdf/in/CompositeStrategy.java b/visad/data/netcdf/in/CompositeStrategy.java
new file mode 100644
index 0000000..82925a6
--- /dev/null
+++ b/visad/data/netcdf/in/CompositeStrategy.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: CompositeStrategy.java,v 1.1 2001-12-19 20:55:28 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.VisADException;
+import visad.data.BadFormException;
+import visad.DataImpl;
+
+/**
+ * <p>Chains together import strategies for netCDF datasets.</p>
+ *
+ * <p>Instances of this class are immutable.</p>
+ *
+ * @author Steven R. Emmerson
+ */
+public final class CompositeStrategy extends Strategy
+{
+    /**
+     * The set of import strategies to use.  Will have 2 or more elements.
+     */
+    private final Strategy[] strategies;
+
+    /**
+     * Constructs from an array of import strategies.
+     *
+     * @param strategies                The import strategies to use.
+     * @throws NullPointerException     if the argument is <code>null</code>.
+     * @throws IllegalArgumentException if the number of strategies is 1.
+     */
+    private CompositeStrategy(Strategy[] strategies)
+    {
+        if (strategies.length < 2)
+            throw new IllegalArgumentException();
+        this.strategies = (Strategy[])strategies.clone();
+    }
+
+
+    /**
+     * Returns an import strategy.  The given strategies are tried in the order
+     * they appear in the array.  This method returns when a strategy succeeds.
+     *
+     * @param strategies                The import strategies to use.
+     * @return                          An import strategy.
+     * @throws NullPointerException     if the argument is <code>null</code>.
+     * @throws IllegalArgumentException if the number of strategies is 0.
+     */
+    public static Strategy instance(Strategy[] strategies)
+    {
+        return
+            strategies.length == 1
+                ? strategies[0]
+                : new CompositeStrategy(strategies);
+    }
+
+    /**
+     * <p>Returns a VisAD data object corresponding to the netCDF dataset.</p>
+     *
+     * @param adapter           The netCDF-to-VisAD adapter.
+     * @return                  The top-level, VisAD data object of the netCDF
+     *                          dataset.
+     * @throws VisADException   if a problem occurs in core VisAD -- probably 
+     *                          because a VisAD object couldn't be created.
+     * @throws IOException      if a data access I/O failure occurs.
+     * @throws BadFormException if the netCDF dataset doesn't conform to
+     *                          conventions implicit in constructing
+     *                          View.
+     * @throws OutOfMemoryError if the netCDF dataset couldn't be imported due
+     *                          to insufficient memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     */
+    public DataImpl getData(NetcdfAdapter adapter)
+        throws IOException, VisADException, RemoteException,
+            BadFormException, OutOfMemoryError
+    {
+        for (int i = 0; i < strategies.length - 1; i++) {
+            System.gc();
+            try
+            {
+                return strategies[i].getData(adapter);
+            }
+            catch (OutOfMemoryError memErr)
+            {
+                System.err.println(
+                    "Couldn't import netCDF dataset due to " +
+                    "insufficient memory.  Using different strategy...");
+            }
+        }
+        return strategies[strategies.length - 1].getData(adapter);
+    }
+}
diff --git a/visad/data/netcdf/in/Context.java b/visad/data/netcdf/in/Context.java
new file mode 100644
index 0000000..f4376b3
--- /dev/null
+++ b/visad/data/netcdf/in/Context.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: Context.java,v 1.2 2000-06-08 19:13:43 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+
+/**
+ * Provides support for the context in which data values are retrieved.
+ *
+ * Instances are mutable.
+ */
+public class
+Context
+{
+    /**
+     * The indexes of this context.
+     */
+    private int[]	indexes;
+
+
+    /**
+     * Constructs from nothing.
+     */
+    public
+    Context()
+    {
+	indexes = new int[0];
+    }
+
+
+    /**
+     * Constructs from the number of indexes.
+     */
+    private
+    Context(int n)
+    {
+	indexes = new int[n];
+    }
+
+
+    /**
+     * Gets a new (sub) context based on this context.
+     *
+     * @return			A (sub) context.
+     * @postcondition		<code>depth() == </code>INITIAL(<code>depth()
+     *				</code>)<code> + 1</code>
+     */
+    public Context
+    newSubContext()
+    {
+	Context	subContext = new Context(indexes.length + 1);
+
+	System.arraycopy(indexes, 0, subContext.indexes, 0, indexes.length);
+	subContext.indexes[indexes.length] = 0;
+
+	return subContext;
+    }
+
+
+    /**
+     * Sets the current (sub) context.
+     *
+     * @param index		The current (sub) context.
+     */
+    public void
+    setSubContext(int index)
+    {
+	indexes[indexes.length-1] = index;
+    }
+
+
+    /**
+     * Returns the current context.
+     *
+     * @return			The current context.
+     */
+    public int[]
+    getContext()
+    {
+	int[]	context = new int[indexes.length];
+
+	System.arraycopy(indexes, 0, context, 0, indexes.length);
+
+	return context;
+    }
+
+
+    /**
+     * Returns a string representation of this context.
+     */
+    public String
+    toString()
+    {
+	StringBuffer	buf = new StringBuffer(128);
+
+	buf.append("{");
+
+	for (int i = 0; i < indexes.length; ++i)
+	{
+	    if (i > 0)
+		buf.append(", ");
+
+	    buf.append(indexes[i]);
+	}
+
+	buf.append("}");
+
+	return buf.toString();
+    }
+
+
+    /**
+     * Returns a clone of this instance.
+     *
+     * @return			A clone of this instance.
+     */
+    public Object clone()
+    {
+	Context	clone = new Context();
+
+	clone.indexes = (int[])indexes.clone();
+	return clone;
+    }
+}
diff --git a/visad/data/netcdf/in/DataFactory.java b/visad/data/netcdf/in/DataFactory.java
new file mode 100644
index 0000000..e92d575
--- /dev/null
+++ b/visad/data/netcdf/in/DataFactory.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2000, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: DataFactory.java,v 1.4 2002-10-21 20:07:45 donm Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.*;
+
+
+/**
+ * Provides support for creating VisAD Data objects from VirtualData objects.
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+DataFactory
+{
+    private static DataFactory  instance;
+
+    static
+    {
+        instance = new DataFactory();
+    }
+
+
+    protected DataFactory()
+    {}
+
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return                  An instance of this class.
+     */
+    public static DataFactory instance()
+    {
+        return instance;
+    }
+
+
+    /**
+     * Creates a VisAD Data object from a netCDF indicial context and a 
+     * VirtualData object.
+     *
+     * @param context           The netCDF indicial context.
+     * @param virtualData       The virtual data.
+     * @return                  The VisAD Data object corresponding to the
+     *                          input.
+     * @throws InvalidContextException
+     *                          Invalid indicial context.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     * @throws IOException      I/O failure.
+     */
+    public DataImpl newData(Context context, VirtualData virtualData)
+        throws RemoteException, VisADException, InvalidContextException,
+            IOException
+    {
+        /*
+         * If the types of virtual data proliferate, then the following may be
+         * replaced by implementing the Visitor design pattern in VirtualData
+         * (e.g. VirtualData.accept(DataFactory)) and using double dispatch to
+         * invoke the proper method of this class.
+         */
+        return
+            virtualData instanceof VirtualScalar
+                ? (DataImpl)newData(context, (VirtualScalar)virtualData)
+                : virtualData instanceof VirtualFlatField
+                    ? (DataImpl)newData(context, (VirtualFlatField)virtualData)
+                    : virtualData instanceof VirtualField
+                        ? (DataImpl)newData(context, (VirtualField)virtualData)
+                        : (DataImpl)newData(context, (VirtualTuple)virtualData);
+    }
+
+
+    /**
+     * Creates a VisAD Scalar object from a netCDF indicial context and a 
+     * VirtualScalar.
+     *
+     * @param context           The netCDF indicial context.
+     * @param virtualScalar     The virtual data.
+     * @return                  The VisAD Real corresponding to the input.
+     * @throws InvalidContextException
+     *                          Invalid indicial context.
+     * @throws VisADException   VisAD failure.
+     * @throws IOException      I/O failure.
+     */
+    public Scalar newData(Context context, VirtualScalar virtualScalar)
+        throws VisADException, InvalidContextException, IOException
+    {
+        return virtualScalar.getScalar(context);
+    }
+
+    /**
+     * Creates a VisAD FlatField object from a netCDF indicial context and a 
+     * VirtualFlatField.
+     *
+     * @param context           The netCDF indicial context.
+     * @param virtualField      The virtual data.
+     * @return                  The VisAD FlatField corresponding to the input.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     * @throws IOException      I/O failure.
+     */
+    public FlatField newData(Context context, VirtualFlatField virtualField)
+        throws VisADException, RemoteException, IOException
+    {
+        FunctionType    funcType = virtualField.getFunctionType();
+        SampledSet      domainSet = virtualField.getDomainSet();
+        VirtualTuple    rangeTuple = virtualField.getRangeTuple();
+        int             componentCount = rangeTuple.size();
+        Set[]           rangeSets = new Set[componentCount];
+        Unit[]          rangeUnits = new Unit[componentCount];
+
+        for (int i = 0; i < componentCount; ++i)
+        {
+            VirtualScalar       component = (VirtualScalar)rangeTuple.get(i);
+
+            rangeSets[i] = component.getRangeSet();
+            rangeUnits[i] = component.getUnit();
+        }
+
+        FlatField       field =
+            new FlatField(
+                funcType,
+                domainSet,
+                (CoordinateSystem)null,
+                rangeSets,
+                rangeUnits);
+
+        double[][]      values = new double[componentCount][];
+
+        for (int i = 0; i < componentCount; ++i)
+            values[i] = ((VirtualScalar)rangeTuple.get(i)).getDoubles(context);
+
+        field.setSamples(values, /*copy=*/false);
+
+        return field;
+    }
+
+
+    /**
+     * Creates a VisAD Field object from a netCDF indicial context and a 
+     * VirtualField.
+     *
+     * @param context           The netCDF indicial context.
+     * @param virtualField      The virtual data.
+     * @return                  The VisAD Field corresponding to the input.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     * @throws IOException      I/O failure.
+     */
+    public FieldImpl newData(Context context, VirtualField virtualField)
+        throws VisADException, RemoteException, IOException
+    {
+        FieldImpl       field;
+        if (virtualField instanceof VirtualFlatField)
+        {
+            field = newData(context, (VirtualFlatField)virtualField);
+        }
+        else
+        {
+            FunctionType        funcType = virtualField.getFunctionType();
+            SampledSet          domainSet = virtualField.getDomainSet();
+            VirtualTuple        rangeTuple = virtualField.getRangeTuple();
+            int                 sampleCount = domainSet.getLength();
+
+            field = new FieldImpl(funcType, domainSet);
+            context = context.newSubContext();
+            for (int i = 0; i < sampleCount; ++i)
+            {
+                context.setSubContext(i);
+                field.setSample(
+                    i, newData(context, rangeTuple), /*copy=*/false);
+            }
+        }
+        return field;
+    }
+
+
+    /**
+     * Creates a VisAD Data object from a netCDF indicial context and a 
+     * VirtualTuple.
+     *
+     * @param context           The netCDF indicial context.
+     * @param virtualTuple      The virtual data.
+     * @return                  The VisAD Tuple corresponding to the input.
+     * @throws VisADException   VisAD failure.
+     * @throws RemoteException  Java RMI failure.
+     * @throws IOException      I/O failure.
+     */
+    public DataImpl newData(Context context, VirtualTuple virtualTuple)
+        throws RemoteException, VisADException, IOException
+    {
+        DataImpl        data = null;
+        int             size = virtualTuple.size();
+
+        if (size == 1)
+        {
+            data = newData(context, virtualTuple.get(0));
+        }
+        else if (size > 1)
+        {
+            MathType    type = virtualTuple.getType();
+
+            if (type instanceof RealTupleType)
+            {
+                Real[]  reals = new Real[size];
+
+                for (int i = 0; i < size; ++i)
+                    reals[i] = (Real)
+                        newData(context, (VirtualScalar)virtualTuple.get(i));
+
+                data = new RealTuple((RealTupleType)type, reals,
+                                     /*(CoordinateSystem)*/null);
+            }
+            else if (type instanceof TupleType)
+            {
+                DataImpl[]      datas = new DataImpl[size];
+
+                for (int i = 0; i < datas.length; ++i)
+                    datas[i] = newData(context, virtualTuple.get(i));
+
+                data = new Tuple((TupleType)type, datas, /*copy=*/false);
+            }
+        }
+
+        return data;
+    }
+}
diff --git a/visad/data/netcdf/in/DefaultView.java b/visad/data/netcdf/in/DefaultView.java
new file mode 100644
index 0000000..6d25287
--- /dev/null
+++ b/visad/data/netcdf/in/DefaultView.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: DefaultView.java,v 1.9 2004-11-19 23:31:08 donm Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import ucar.netcdf.Dimension;
+import ucar.netcdf.Netcdf;
+import ucar.netcdf.Variable;
+import ucar.netcdf.VariableIterator;
+import visad.data.netcdf.QuantityDB;
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.Gridded1DSet;
+import visad.GriddedSet;
+import visad.Integer1DSet;
+import visad.IntegerNDSet;
+import visad.Linear1DSet;
+import visad.LinearLatLonSet;
+import visad.LinearNDSet;
+import visad.LinearSet;
+import visad.MathType;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.SampledSet;
+import visad.SetType;
+import visad.TextType;
+import visad.TypeException;
+import visad.Unit;
+import visad.VisADException;
+
+/**
+ * Provides support for the default view of a netCDF dataset as documented
+ * in the netCDF User's Guide.
+ *
+ * @author Steven R. Emmerson
+ * @version $Revision: 1.9 $ $Date: 2004-11-19 23:31:08 $
+ */
+public class DefaultView
+    extends     View
+{
+    private final DimsToSet dimsToSet;
+
+    /**
+     * Constructs from a netCDF dataset and a quantity database.
+     *
+     * @param netcdf            The netCDF dataset.
+     * @param quantityDB        The quantity database to use to map netCDF
+     *                          variables to VisAD Quantity-s.
+     */
+    public DefaultView(Netcdf netcdf, QuantityDB quantityDB)
+    {
+        this(netcdf, quantityDB, false);
+    }
+
+    /**
+     * Constructs from a netCDF dataset and a quantity database.
+     *
+     * @param netcdf            The netCDF dataset.
+     * @param quantityDB        The quantity database to use to map netCDF
+     *                          variables to VisAD Quantity-s.
+     * @param charToText        Specifies whether the View should map char
+     *                          variables to VisAD Text objects
+     */
+    public DefaultView(Netcdf netcdf, QuantityDB quantityDB, boolean charToText)
+    {
+        super(netcdf, quantityDB, charToText);
+        dimsToSet = new DimsToSet();
+    }
+
+    /**
+     * <p>Indicates if a given variable should be ignored during iteration.</p>
+     *
+     * <p>This implementation returns the value of {@link 
+     * #isCoordinateVariable(Variable)}.</p>
+     *
+     * @return                    <code>true</code> if and only if the variable
+     *                            should be ignored.
+     */
+    protected boolean isIgnorable(Variable var)
+    {
+        return  isCoordinateVariable(var);
+    }
+
+    /**
+     * Returns the domain of a netCDF variable.
+     *
+     * @param var               A netCDF variable.
+     * @return                  The domain of the given variable.
+     * @throws IllegalArgumentException
+     *                          if the rank of the variable is zero.
+     * @throws TypeException    if a {@link RealType} needed to be created but
+     *                          couldn't.
+     * @throws IOException      if a netCDF read-error occurs.
+     */
+    protected Domain getDomain(Variable var)
+        throws TypeException, IOException
+    {
+        return new DefaultDomain(var);
+    }
+
+    /**
+     * <p>Returns the VisAD domain set corresponding to an array of netCDF 
+     * dimension in netCDF order (outermost dimension first).</p>
+     *
+     * <p>This implementation supports 1-dimensional coordinate variables,
+     * 1-dimensional longitude, and 2-dimensional latitude/longitude domains
+     * and assumes that each netCDF dimension is independent of all others.
+     * This implementation uses {@link #getDomainSet(Dimension)}, {@link
+     * #getDomainSet(Dimension)}, and {@link #getDomainType(Dimension[])}. </p>
+     *
+     * @param dims              A netCDF domain.  Must be in netCDF order
+     *                          (outer dimension first).
+     * @return                  The VisAD domain set corresponding to
+     *                          <code>dims</code>.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     * @exception IOException   if a netCDF read-error occurs.
+     */
+    protected SampledSet getDomainSet(Dimension[] dims)
+        throws IOException, VisADException
+    {
+        if (dims.length == 0)
+            return getDomainSet(dims[0]);
+        /*
+         * This implementation caches earlier results because this operation
+         * is potentially expensive and multiple netCDF variables can have the
+         * same domain.
+         */
+        GriddedSet      set;
+        synchronized(dimsToSet)
+        {
+            set = dimsToSet.get(dims);
+            if (set == null)
+            {
+                Gridded1DSet[] sets = new Gridded1DSet[dims.length];
+                int            j = dims.length;
+                for (int i = 0; i < dims.length; ++i)
+                    sets[--j] = getDomainSet(dims[i]);      // reverse order
+                boolean     allInteger1DSets = true;
+                for (int i = 0; allInteger1DSets && i < sets.length; ++i)
+                    allInteger1DSets = sets[i] instanceof Integer1DSet;
+                MathType    type = getDomainType(dims);
+                if (allInteger1DSets)
+                {
+                    set = (GriddedSet)getIntegerSet(sets, type);
+                }
+                else
+                {
+                    boolean allLinear1DSets = true;
+                    for (int i = 0; allLinear1DSets && i < sets.length; ++i)
+                        allLinear1DSets = sets[i] instanceof Linear1DSet;
+                    if (allLinear1DSets)
+                    {
+                        set = (GriddedSet)getLinearSet(sets, type);
+                    }
+                    else
+                    {
+                        set = getGriddedSet(sets, type);
+                    }
+                }
+                dimsToSet.put(dims, set);
+            }
+        }
+        return set;
+    }
+
+    /**
+     * Returns an {@link IntegerSet} corresponding to the product of one or more
+     * {@link Integer1DSet}s.
+     *
+     * @param sets              The {@link Integer1DSet}s to be multiplied
+     *                          together.
+     * @param type              The {@link MathType} for the result.
+     * @return                  The {@link IntegerSet} corresponding to the
+     *                          input.
+     * @throws VisADException   if a VisAD object couldn't be created.
+     */
+    private static GriddedSet getIntegerSet(Gridded1DSet[] sets, MathType type)
+        throws VisADException
+    {
+        int     rank = sets.length;
+        int[]   lengths = new int[rank];
+        for (int idim = 0; idim < rank; ++idim)
+            lengths[idim] = ((Integer1DSet)sets[idim]).getLength(0);
+        // TODO: add CoordinateSystem argument
+        return IntegerNDSet.create(type, lengths, (CoordinateSystem)null,
+                (Unit[])null, (ErrorEstimate[])null);
+    }
+
+    /**
+     * <p>Returns a {@link LinearSet} corresponding to the product of one or 
+     * more {@link Linear1DSet}s.</p>
+     *
+     * <p>This implementation uses {@link #isLongitude(RealType)} and {@link
+     * #isLatitude(RealType)}.
+     *
+     * @param sets              The {@link Linear1DSet}s to be multiplied
+     *                          together.
+     * @param type              The {@link MathType} for the result.
+     *                          NB: The units in the sets needn't be the
+     *                          same as the units in <code>type</code>.
+     * @return                  The {@link LinearSet} corresponding to the
+     *                          input.
+     * @throws VisADException   if a VisAD object couldn't be created.
+     */
+    private LinearSet getLinearSet(Gridded1DSet[] sets, MathType type)
+        throws VisADException
+    {
+        LinearSet       set = null;
+        int             rank = sets.length;
+        double[]        firsts = new double[rank];
+        double[]        lasts = new double[rank];
+        int[]           lengths = new int[rank];
+        Unit[]          units = new Unit[rank];
+        for (int idim = 0; idim < rank; ++idim)
+        {
+            Linear1DSet linear1DSet = (Linear1DSet)sets[idim];
+            firsts[idim] = linear1DSet.getFirst();
+            lengths[idim] = linear1DSet.getLength(0);
+            lasts[idim] = linear1DSet.getLast();
+            units[idim] = linear1DSet.getSetUnits()[0];
+        }
+        // TODO: add CoordinateSystem argument
+        if (rank == 2)
+        {
+            RealType[]  types = ((RealTupleType)type).getRealComponents();
+            if ((isLongitude(types[0]) && isLatitude(types[1])) ||
+                (isLongitude(types[1]) && isLatitude(types[0])))
+            {
+                set = new LinearLatLonSet(type,
+                                        firsts[0], lasts[0], lengths[0],
+                                        firsts[1], lasts[1], lengths[1],
+                                        (CoordinateSystem)null,
+                                        units,
+                                        (ErrorEstimate[])null);
+            }
+        }
+        if (set == null)
+        {
+            set = LinearNDSet.create(type,
+                                      firsts, lasts, lengths,
+                                      (CoordinateSystem)null,
+                                      units,
+                                      (ErrorEstimate[])null);
+        }
+        return set;
+    }
+
+    /**
+     * Returns a {@link GriddedSet} corresponding to one or more {@link
+     * Gridded1DSet}s.
+     *
+     * @param sets              The {@link Gridded1DSet}s to be multiplied
+     *                          together.
+     * @param type              The {@link MathType} for the result.
+     *                          NB: The units in the sets needn't be the
+     *                          same as the units in <code>type</code>.
+     * @return                  The {@link GriddedSet} corresponding to the
+     *                          input.
+     * @throws VisADException   if a VisAD object couldn't be created.
+     * @throws IOException      if a netCDF read-error occurs.
+     */
+    private static GriddedSet getGriddedSet(Gridded1DSet[] sets, MathType type)
+        throws VisADException, IOException
+    {
+        int             rank = sets.length;
+        // Handle the case where we only have one set.  Save us some work
+        // and keep the integrity of a Gridded1DDoubleSet for Time
+        if (rank == 1 && sets[0].getType().equals(new SetType(type))) {
+          return sets[0];
+        }
+        int[]           lengths = new int[rank];
+        float[][]       values = new float[rank][];
+        int             ntotal = 1;
+        for (int idim = 0; idim < rank; ++idim)
+        {
+            lengths[idim] = sets[idim].getLength(0);
+            ntotal *= lengths[idim];
+        }
+        int step = 1;
+        int laststep = 1;
+        for (int idim = 0; idim < rank; ++idim)
+        {
+            float[]     vals = sets[idim].getSamples(false)[0];
+            values[idim] = new float[ntotal];
+            step *= lengths[idim];
+            for (int i=0; i<lengths[idim]; i++) {
+              int istep = i * laststep;
+              for (int j=0; j<ntotal; j+=step) {
+                for (int k=0; k<laststep; k++) {
+                  values[idim][istep+j+k] = vals[i];
+                }
+              }
+            }
+            laststep = step;
+        }
+        Unit[]  units = new Unit[rank];
+        for (int idim = 0; idim < rank; ++idim)
+            units[idim] = sets[idim].getSetUnits()[0];
+        // TODO: add CoordinateSystem argument
+        return GriddedSet.create(type, values, lengths,
+                 (CoordinateSystem)null, units, (ErrorEstimate[])null);
+    }
+
+    /**
+     * <p>Returns the VisAD {@link MathType} corresponding to an array of netCDF
+     * dimensions.  If the array has zero length, then <code>null</code> is
+     * returned.</p>
+     *
+     * <p>This implementation uses {@link #getRealType(Dimension)}.</p>
+     *
+     * @param dims              netCDF dimensions in netCDF order (outermost
+     *                          dimension first).
+     * @return                  The type of the domain corresponding to
+     *                          <code>dims</code>.  RETURN_VALUE is
+     *                          <code>null</code>, a <code>RealType</code>,
+     *                          or a <code>RealTupleType</code> if
+     *                          <code>dims.length</code> is 0, 1, or greater
+     *                          than 1, respectively.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     */
+    protected MathType getDomainType(Dimension[] dims)
+        throws VisADException
+    {
+        MathType        type;
+        int             rank = dims.length;
+        if (rank == 0)
+        {
+            type = null;        // means scalar domain
+        }
+        else if ( rank == 1)
+        {
+            type = (MathType)getRealType(dims[0]);
+        }
+        else
+        {
+            RealType[]  types = new RealType[dims.length];
+            int j = dims.length;
+            for (int i = 0; i < dims.length; ++i)
+                types[--j] = getRealType(dims[i]);      // reverse order
+            type = new RealTupleType(types);
+        }
+        return type;
+    }
+
+    /**
+     * Iterates over the virtual VisAD data objects in a netCDF dataset.
+     */
+    final class DefaultDataIterator
+        extends VirtualDataIterator
+    {
+        /**
+         * The netCDF variable iterator.
+         */
+        private final VariableIterator  varIter;
+
+        /**
+         * Constructs from nothing.
+         */
+        DefaultDataIterator()
+        {
+            super(DefaultView.this);
+            varIter = DefaultView.this.getNetcdf().iterator();
+        }
+
+        /**
+         * Returns a clone of the next virtual VisAD data object.
+         *
+         * <p>This implementation uses {@link #isCharToText()},
+         * {@link #isNumeric(Variable)}, {@link #isIgnorable(Variable)}, 
+         * and {@link #getData(Variable)}.</p>
+         * 
+         * @return                      A clone of the next virtual VisAD data
+         *                              object or <code> null</code> if there is
+         *                              no more data.
+         * @throws TypeException        if a {@link ScalarType} needed
+         *                              to be created but couldn't.
+         * @throws VisADException       Couldn't create necessary VisAD object.
+         */
+        protected VirtualData getData()
+            throws TypeException, VisADException, IOException
+        {
+            while (varIter.hasNext())
+            {
+                Variable        var = varIter.next();
+                // handle text only if charToText == true and rank <= 2
+                if (!isNumeric(var) && (!isCharToText() || var.getRank() > 2))
+                    continue;  // TODO: support arrays of text (Tuple?)
+                if (isIgnorable(var)) 
+                    continue;  // ignore ignorable variables
+                VirtualScalar   scalar =
+                    (isNumeric(var) == true)
+
+                        ? (VirtualScalar) 
+                            new VirtualReal(getRealType(var),
+                                            var,
+                                            getRangeSet(var),
+                                            getUnitFromAttribute(var),
+                                            getVetter(var))
+
+                        : (VirtualScalar)
+                            new VirtualText(getTextType(var), var);
+                return
+                    (var.getRank() == 0 || 
+                     (!isNumeric(var) && var.getRank() == 1))
+                        ? (VirtualData)scalar
+                        : getDomain(var).getVirtualField(
+                            new VirtualTuple(scalar));
+            }
+            return null;        // no more data
+        }
+    }
+
+    /**
+     * The default domain of a netCDF variable.  A default domain comprises
+     * the variable's netCDF dimensions.
+     */
+    private final class DefaultDomain
+        extends Domain
+    {
+        /**
+         * Outermost dimension first; at least one element.
+         */
+        private final Dimension[]   dims;
+        private volatile int        hashCode;
+        private volatile SampledSet domainSet;
+
+        /**
+         * @throws IllegalArgumentException if the rank of the variable is 0.
+         */
+        DefaultDomain(Variable var)
+            throws TypeException
+        {
+            super(var);
+            dims = getDimensions(var);
+        }
+
+        /**
+         * Returns a {@link VirtualField} corresponding to this domain and
+         * a given range.
+         *
+         * @param range                 The range for the {@link VirtualField}.
+         * @throws NullPointerException if the argument is <code>null</code>.
+         * @throws IOException          if a read error occurs.
+         * @throws VisADException       if a VisAD object can't be created.
+         */
+        protected VirtualField getVirtualField(VirtualTuple range)
+            throws VisADException, IOException
+        {
+            VirtualField field;
+            int          rank = dims.length;
+            if (rank == 2 && range.getType() instanceof TextType) { // char
+               field = 
+                   VirtualField.newVirtualField(
+                       DefaultView.this.getDomainSet(dims[0]), range);
+            }
+            else if (rank == 1 || !DefaultView.this.isTime(dims[0]))
+            {
+                field =
+                    VirtualField.newVirtualField(
+                        DefaultView.this.getDomainSet(dims), range);
+            }
+            else
+            {
+                Dimension[] innerDims = new Dimension[rank-1];
+                System.arraycopy(dims, 1, innerDims, 0, innerDims.length);
+                field =
+                    VirtualField.newVirtualField(
+                        DefaultView.this.getDomainSet(dims[0]),
+                        new VirtualTuple(
+                            VirtualField.newVirtualField(
+                                DefaultView.this.getDomainSet(innerDims),
+                                range)));
+            }
+            return field;
+        }
+
+        public boolean equals(Object obj)
+        {
+            if (obj == this)
+                return true;
+            if (!(obj instanceof DefaultDomain))
+                return false;
+            return Arrays.equals(dims, ((DefaultDomain)obj).dims);
+        }
+
+        public int hashCode()
+        {
+            int hash = hashCode;
+            if (hash == 0)
+            {
+                hash = 1;
+                for (int i = 0; i < dims.length; i++)
+                    hash = hash*31 + dims[i].hashCode();
+                hashCode = hash;
+            }
+            return hash;
+        }
+    }
+
+    /**
+     * Cache of netCDF dimensions and their corresponding VisAD domain sets.
+     */
+    private static class DimsToSet
+    {
+        private Map map;
+
+        DimsToSet()
+        {
+            map = Collections.synchronizedMap(new WeakHashMap());
+        }
+
+        void put(Dimension[] dims, GriddedSet set)
+        {
+            if (dims.length == 1)
+                map.put(dims[0], set);
+            else
+                map.put(new DimArray(dims), set);
+        }
+
+        GriddedSet get(Dimension[] dims)
+        {
+            return
+                dims.length == 1
+                    ? (GriddedSet)map.get(dims[0])
+                    : (GriddedSet)map.get(new DimArray(dims));
+        }
+
+        private static class DimArray
+        {
+            private Dimension[]  dims;
+            private volatile int hashCode;
+
+            DimArray(Dimension[] dims)
+            {
+                this.dims = (Dimension[])dims.clone();  // defensive copy
+            }
+
+            public boolean equals(Object obj)
+            {
+                if (obj == this)
+                    return true;
+                if (!(obj instanceof DimArray))
+                    return false;
+                return Arrays.equals(dims, ((DimArray)obj).dims);
+            }
+
+            public int hashCode()
+            {
+                int hash = hashCode;
+                if (hash == 0)
+                {
+                    hash = 1;
+                    for (int i = 0; i < dims.length; i++)
+                        hash = hash*31 + dims[i].hashCode();
+                    hashCode = hash;
+                }
+                return hash;
+            }
+        }
+    }
+}
diff --git a/visad/data/netcdf/in/FileDataFactory.java b/visad/data/netcdf/in/FileDataFactory.java
new file mode 100644
index 0000000..1291716
--- /dev/null
+++ b/visad/data/netcdf/in/FileDataFactory.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2000, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: FileDataFactory.java,v 1.3 2001-01-08 17:10:31 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.*;
+
+
+/**
+ * Provides support for creating VisAD Data objects that use a file 
+ * backing-store from VirtualData objects.  Currently, the only supported
+ * in-file VisAD data object is the FileFlatField.
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+FileDataFactory
+    extends	DataFactory
+{
+    private static FileDataFactory	instance;
+
+    private static CacheStrategy	cacheStrategy = new CacheStrategy();
+
+    static
+    {
+	instance = new FileDataFactory();
+    }
+
+
+    private FileDataFactory()
+    {}
+
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static DataFactory instance()
+    {
+	return instance;
+    }
+
+
+    /**
+     * Creates a VisAD FlatField object from a netCDF indicial context and a 
+     * VirtualFlatField.  The returned FlatField object is, actually, a
+     * FileFlatField that uses the netCDF dataset as its (read-only)
+     * backing-store.
+     *
+     * @param context		The netCDF indicial context.
+     * @param virtualField	The virtual data.
+     * @return			The VisAD FileFlatField corresponding to the
+     *				input.
+     * @throws VisADException	VisAD failure.
+     * @throws RemoteException	Java RMI failure.
+     */
+    public FlatField newData(Context context, VirtualFlatField virtualField)
+	throws VisADException, RemoteException, IOException
+    {
+	return
+	    new FileFlatField(
+		new netCDFFlatFieldAccessor(context, virtualField),
+		cacheStrategy);
+    }
+
+
+    /**
+     * Provides support for reading a FlatField from a netCDF dataset.
+     *
+     * @author Steven R. Emmerson
+     */
+    protected class
+    netCDFFlatFieldAccessor
+	extends FileAccessor
+    {
+	private Context		context;
+	private VirtualField	virtualField;
+
+
+	/**
+	 * Constructs from a netCDF indicial context and a virtual Field.
+	 *
+	 * @param context	The netCDF indicial context.
+	 * @param virtualField	The virtual Field.
+	 */
+	public netCDFFlatFieldAccessor(
+	    Context context, VirtualField virtualField)
+	{
+	    this.context = (Context)context.clone();
+	    this.virtualField = virtualField;
+	}
+
+
+	/**
+	 * Returns the associated FlatField.
+	 *
+	 * @return			The associated FlatField.
+	 * @throws VisADException	VisAD failure.
+	 * @throws RemoteException	Java RMI failure.
+	 */
+	public FlatField getFlatField()
+	    throws VisADException, RemoteException
+	{
+	    try
+	    {
+		return (FlatField)
+		    DataFactory.instance().newData(context, virtualField);
+	    }
+	    catch (RemoteException e)
+	    {
+		throw e;
+	    }
+	    catch (IOException e)
+	    {
+		throw new RemoteException(e.getMessage());
+	    }
+	}
+
+
+	/**
+	 * Returns the VisAD FunctionType of the FlatField.
+	 *
+	 * @return			The VisAD FunctionType of the FlatField.
+	 */
+	public FunctionType getFunctionType()
+	{
+	    return virtualField.getFunctionType();
+	}
+
+
+	/**
+	 * Returns <code>null</code>.
+	 *
+	 * @return			<code>null</code>.
+	 */
+	public double[][] readFlatField(FlatField template, int[] fileLocation)
+	{
+	    return null;
+	}
+
+
+	/**
+	 * Does nothing.
+	 */
+	public void writeFile(int[] fileLocation, Data range)
+	{}
+
+
+	/**
+	 * Does nothing.
+	 */
+	public void writeFlatField(
+	    double[][] values, FlatField template, int[] fileLocation)
+	{}
+    }
+}
diff --git a/visad/data/netcdf/in/FileStrategy.java b/visad/data/netcdf/in/FileStrategy.java
new file mode 100644
index 0000000..a5fec6e
--- /dev/null
+++ b/visad/data/netcdf/in/FileStrategy.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: FileStrategy.java,v 1.7 2001-12-19 20:55:11 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import ucar.netcdf.NetcdfFile;
+import visad.*;
+import visad.data.BadFormException;
+import visad.data.netcdf.*;
+
+/**
+ * <p>
+ * Provides support for importing netCDF datasets using the strategy of
+ * employing {@link visad.data.FileFlatField}s wherever possible, but merging
+ * the data so as to keep the number of {@link visad.data.FileFlatField}s to a
+ * minimum.
+ * </p>
+ * 
+ * <p>
+ * This class may be subclassed in order to use a different data merger tactic
+ * -- one that maximizes the number of {@link visad.data.FileFlatField}s, for
+ * example (see {@link #getMerger()}).
+ * </p>
+ * 
+ * <p>
+ * Instances are immutable.
+ * </p>
+ * 
+ * @author Steven R. Emmerson
+ */
+
+public class FileStrategy
+    extends     Strategy
+{
+    /**
+     * The singleton instance of this class.
+     */
+    private static FileStrategy instance;
+
+    static
+    {
+        instance = new FileStrategy();
+    }
+
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return                  An instance of this class.
+     */
+    public static Strategy instance()
+    {
+        return instance;
+    }
+
+
+    /**
+     * Constructs from nothing.  Protected to ensure use of 
+     * <code>instance()</code> method.
+     *
+     * @see #instance()
+     */
+    protected FileStrategy()
+    {}
+
+
+    /**
+     * Returns a VisAD data object corresponding to the netCDF dataset.  This
+     * method uses the Merger returned by <code>getMerger()</code>.
+     *
+     * @param adapter           The netCDF-to-VisAD adapter.
+     * @return                  The top-level, VisAD data object in the
+     *                          netCDF dataset.
+     * @throws VisADException   if a problem occurs in core VisAD -- probably 
+     *                          because a VisAD object couldn't be created.
+     * @throws IOException      if a data access I/O failure occurs.
+     * @throws BadFormException if the netCDF dataset doesn't conform to
+     *                          conventions implicit in constructing
+     *                          View.
+     * @throws OutOfMemoryError if the netCDF dataset couldn't be imported into 
+     *                          memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @see #getMerger()
+     */
+    public DataImpl
+    getData(NetcdfAdapter adapter)
+        throws IOException, VisADException, RemoteException,
+            BadFormException, OutOfMemoryError
+    {
+        try
+        {
+            return
+                adapter.importData(
+                    adapter.getView(),
+                    getMerger(),
+                    FileDataFactory.instance());
+        }
+        catch (OutOfMemoryError e)
+        {
+            throw new OutOfMemoryError(
+                getClass().getName() + ".getData(): " +
+                "Couldn't import netCDF dataset: " + e.getMessage());
+        }
+    }
+
+
+    /**
+     * Returns the Merger for cosolidating virtual data objects together.  The
+     * Merger returned by this method is <code>Merger.instance()</code>.  This
+     * method may be overridden in subclasses to supply a different merger
+     * strategy (e.g. maximizing the number of FileFlatField-s).
+     * @return                  The Merger for cosolidating virtual data 
+     *                          objects together.
+     * @see Merger
+     */
+    protected Merger getMerger()
+    {
+        return Merger.instance();
+    }
+
+
+    /**
+     * Tests this class.
+     *
+     * @param args              File pathnames.
+     * @throws Exception        Something went wrong.
+     */
+    public static void
+    main(String[] args)
+        throws Exception
+    {
+        String[]        pathnames;
+
+        if (args.length == 0)
+            pathnames = new String[] {"test.nc"};
+        else
+            pathnames = args;
+
+        System.setProperty(
+            NetcdfAdapter.IMPORT_STRATEGY_PROPERTY,
+            FileStrategy.class.getName());
+
+        for (int i = 0; i < pathnames.length; ++i)
+        {
+            NetcdfFile          file = new NetcdfFile(pathnames[i],
+                                    /*readonly=*/true);
+            NetcdfAdapter       adapter =
+                new NetcdfAdapter(file, QuantityDBManager.instance());
+            DataImpl            data = adapter.getData();
+
+            System.out.println("data.getClass().getName() = " +
+                data.getClass().getName());
+
+            System.out.println("data.getType().prettyString():\n" +
+                data.getType().prettyString());
+        }
+    }
+}
diff --git a/visad/data/netcdf/in/FlatMerger.java b/visad/data/netcdf/in/FlatMerger.java
new file mode 100644
index 0000000..0a46f0b
--- /dev/null
+++ b/visad/data/netcdf/in/FlatMerger.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: FlatMerger.java,v 1.3 2001-11-27 22:29:35 dglo Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import visad.*;
+
+
+/**
+ * Provides support for merging of virtual data objects.  This class maximizes
+ * the number of virtual flat-fields by not merging them.  Consequently, this
+ * class supports a FileFlatField caching strategy.
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+FlatMerger
+    extends	Merger
+{
+    /**
+     * The singleton instance.
+     */
+    private static FlatMerger	instance;
+
+
+    /**
+     * Constructs from nothing.
+     */
+    protected FlatMerger()
+    {}
+
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static Merger instance()
+    {
+	if (instance == null)
+	{
+	    synchronized(FlatMerger.class)
+	    {
+		if (instance == null)
+		    instance = new FlatMerger();
+	    }
+	}
+
+	return instance;
+    }
+
+
+    /**
+     * Does not merge a virtual flat-field with a virtual field.
+     *
+     * @param field1		The virtual flat-field.
+     * @param field2		The virtual field.
+     * @return			A virtual field comprising the merger of
+     *				the input objects if possible; otherwise 
+     *				<code>null</code>.
+     * throws VisADException	VisAD failure.
+     * @see #merge(VirtualField, VirtualField)
+     */
+    protected VirtualField merge(VirtualFlatField field1, VirtualField field2)
+	throws VisADException
+    {
+	return null;
+    }
+
+
+    /**
+     * Does not merge a virtual flat-field with another virtual flat-field.
+     *
+     * @param field1		The virtual flat-field.
+     * @param field2		The other virtual flat-field.
+     * @return			A virtual flat-field comprising the merger of
+     *				the input objects if possible; otherwise 
+     *				<code>null</code>.  May be <code>field1</code>.
+     * throws VisADException	VisAD failure.
+     * @see #merge(VirtualFlatField, VirtualFlatField)
+     */
+    protected VirtualFlatField merge(
+	    VirtualFlatField field1, VirtualFlatField field2)
+	throws VisADException
+    {
+	return null;
+    }
+}
diff --git a/visad/data/netcdf/in/InMemoryStrategy.java b/visad/data/netcdf/in/InMemoryStrategy.java
new file mode 100644
index 0000000..57322b0
--- /dev/null
+++ b/visad/data/netcdf/in/InMemoryStrategy.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: InMemoryStrategy.java,v 1.1 2001-12-19 21:02:40 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.VisADException;
+import visad.data.BadFormException;
+import visad.DataImpl;
+
+/**
+ * <p>An import strategy that attempts to read the entire netCDF dataset into
+ * memory.</p>
+ *
+ * <p>Instances are immutable.</p>
+ *
+ * @author Steven R. Emmerson
+ */
+public class InMemoryStrategy extends Strategy
+{
+    /**
+     * The single instance of this class.
+     */
+    private static final InMemoryStrategy INSTANCE;
+
+    static
+    {
+        INSTANCE = new InMemoryStrategy();
+    }
+
+    /**
+     * Constructs from nothing.
+     */
+    private InMemoryStrategy()
+    {}
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return                      An instance of this class.
+     */
+    public static InMemoryStrategy instance()
+    {
+        return INSTANCE;
+    }
+
+    /**
+     * <p>Returns a VisAD data object corresponding to the netCDF dataset.</p>
+     *
+     * <p>This implementation uses the data-merging of {@link 
+     * Merger#instance()}.</p>
+     *
+     * @param adapter           The netCDF-to-VisAD adapter.
+     * @return                  The top-level, VisAD data object of the netCDF
+     *                          dataset.
+     * @throws VisADException   if a problem occurs in core VisAD -- probably 
+     *                          because a VisAD object couldn't be created.
+     * @throws IOException      if a data access I/O failure occurs.
+     * @throws BadFormException if the netCDF dataset doesn't conform to
+     *                          conventions implicit in constructing
+     *                          View.
+     * @throws OutOfMemoryError if the netCDF dataset couldn't be imported into 
+     *                          memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     */
+    public DataImpl
+    getData(NetcdfAdapter adapter)
+        throws IOException, VisADException, RemoteException,
+            BadFormException, OutOfMemoryError
+    {
+        return
+            adapter.importData(
+                adapter.getView(), Merger.instance(), DataFactory.instance());
+    }
+}
diff --git a/visad/data/netcdf/in/InvalidContextException.java b/visad/data/netcdf/in/InvalidContextException.java
new file mode 100644
index 0000000..5be312e
--- /dev/null
+++ b/visad/data/netcdf/in/InvalidContextException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: InvalidContextException.java,v 1.1 1998-09-23 17:31:32 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import visad.VisADException;
+
+
+/**
+ * Exception thrown when the I/O context is invalid for an operation.
+ */
+public class
+InvalidContextException
+    extends VisADException
+{
+    /**
+     * Constructs from nothing.
+     */
+    public
+    InvalidContextException()
+    {
+	super();
+    }
+
+
+    /**
+     * Constructs from a message.
+     */
+    public
+    InvalidContextException(String msg)
+    {
+	super(msg);
+    }
+
+
+    /**
+     * Constructs from a context.
+     */
+    public
+    InvalidContextException(Context context)
+    {
+	super(context.toString());
+    }
+}
diff --git a/visad/data/netcdf/in/MaxFileFieldStrategy.java b/visad/data/netcdf/in/MaxFileFieldStrategy.java
new file mode 100644
index 0000000..c5418e0
--- /dev/null
+++ b/visad/data/netcdf/in/MaxFileFieldStrategy.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: MaxFileFieldStrategy.java,v 1.6 2006-02-13 22:30:08 curtis Exp $
+ */
+
+package visad.data.netcdf.in;
+
+/**
+ * <p>Provides support for importing netCDF datasets using the strategy of
+ * employing FileFlatField-s wherever possible and not merging them so as to
+ * keep the number of FileFlatField-s to a maximum.</p>
+ *
+ * <p>Instances are immutable.</p>
+ *
+ * @author Steven R. Emmerson
+ */
+public class MaxFileFieldStrategy
+    extends     FileStrategy
+{
+    /**
+     * The singleton instance of this class.
+     */
+    private static MaxFileFieldStrategy instance = new MaxFileFieldStrategy();
+
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return                  An instance of this class.
+     */
+    public static Strategy instance()
+    {
+        return instance;
+    }
+
+
+    /**
+     * Constructs from nothing.  Protected to ensure use of 
+     * <code>instance()</code> method.
+     *
+     * @see #instance()
+     */
+    protected MaxFileFieldStrategy()
+    {}
+
+
+    /**
+     * Returns the Merger for cosolidating virtual data objects together.  The
+     * Merger returned by this method is that returned by {@link
+     * FlatMerger#instance()} -- which doesn't merge FlatFields together.
+     * @return                  The Merger for cosolidating virtual data 
+     *                          objects together.
+     * @see Merger
+     */
+    protected Merger getMerger()
+    {
+        return FlatMerger.instance();
+    }
+}
diff --git a/visad/data/netcdf/in/Merger.java b/visad/data/netcdf/in/Merger.java
new file mode 100644
index 0000000..13ef809
--- /dev/null
+++ b/visad/data/netcdf/in/Merger.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2000, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: Merger.java,v 1.3 2001-11-27 22:29:35 dglo Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import visad.*;
+
+
+/**
+ * Provides support for merging of virtual data objects.  This class merges
+ * virtual data objects to the maximum extent possible.
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+Merger
+{
+    /**
+     * The singleton instance.
+     */
+    private static Merger	instance;
+
+
+    /**
+     * Constructs from nothing.
+     */
+    protected Merger()
+    {}
+
+
+    /**
+     * Returns an instance of this class.
+     *
+     * @return			An instance of this class.
+     */
+    public static Merger instance()
+    {
+	if (instance == null)
+	{
+	    synchronized(Merger.class)
+	    {
+		if (instance == null)
+		    instance = new Merger();
+	    }
+	}
+
+	return instance;
+    }
+
+
+    /**
+     * Merges two virtual data objects.  The order of the objects sub-components
+     * is preserved.
+     *
+     * @param data1		The first virtual data object.
+     * @param data2		The second virtual data object.
+     * @return			A virtual data object comprising the ordered
+     *				merger of the two virtual data objects if 
+     *				possible; otherwise <code>null</code>.  May be
+     *				one of the input objects.
+     * throws VisADException	VisAD failure.
+     */
+    public VirtualData merge(VirtualData data1, VirtualData data2)
+	throws VisADException
+    {
+	return
+	    data1 instanceof VirtualTuple
+		? (VirtualData)merge((VirtualTuple)data1, data2)
+		: data1 instanceof VirtualField
+		    ? (VirtualData)merge((VirtualField)data1, data2)
+		    : (VirtualData)null;
+    }
+
+
+    /**
+     * Merges a virtual tuple with another virtual data object.  Order is
+     * preserved.
+     *
+     * @param tuple		The virtual tuple.
+     * @param data		The other virtual data object.
+     * @return			<code>tuple</code>.
+     * throws VisADException	VisAD failure.
+     */
+    protected VirtualTuple merge(VirtualTuple tuple, VirtualData data)
+	throws VisADException
+    {
+	if (data instanceof VirtualTuple)
+	{
+	    merge(tuple, (VirtualTuple)data);
+	}
+	else
+	{
+	    int		n = tuple.size();
+	    boolean	merged = false;
+
+	    for (int i = 0; i < n; ++i)
+	    {
+		VirtualData     element = tuple.get(i);
+		VirtualData     mergedElement = merge(element, data);
+
+		if (mergedElement != null)
+		{
+		    tuple.replace(i, mergedElement);
+		    merged = true;
+		    break;
+		}
+	    }
+
+	    if (!merged)
+		tuple.add(data);
+	}
+
+	return tuple;
+    }
+
+
+    /**
+     * Merges two virtual tuples.
+     *
+     * @param tuple1		The first virtual tuple.
+     * @param tuple2		The second virtual tuple.
+     * @return			<code>tuple1</code>.
+     * throws VisADException	VisAD failure.
+     */
+    protected VirtualTuple merge(VirtualTuple tuple1, VirtualTuple tuple2)
+	throws VisADException
+    {
+	int	n = tuple2.size();
+
+	for (int i = 0; i < n; ++i)
+	{
+	    VirtualData	element = tuple2.get(i);
+
+	    merge(tuple1, element);
+	}
+
+	return tuple1;		// always successful
+    }
+
+
+    /**
+     * Merges a virtual field with another virtual data object.  Order is
+     * preserved.
+     *
+     * @param field		The virtual field.
+     * @param data		The other virtual data object.
+     * @return			A virtual field comprising the merger of the
+     *				input objects if possible; otherwise 
+     *				<code>null</code>.  May be <code>field</code>.
+     * throws VisADException	VisAD failure.
+     */
+    protected VirtualField merge(VirtualField field, VirtualData data)
+	throws VisADException
+    {
+	return
+	    field instanceof VirtualFlatField
+		? (VirtualField)merge((VirtualFlatField)field, data)
+		: data instanceof VirtualField
+		    ? merge(field, (VirtualField)data)
+		    : (VirtualField)null;
+    }
+
+
+    /**
+     * Merges a virtual field with another virtual field.  Order is
+     * preserved.
+     *
+     * @param field1		The first virtual field.
+     * @param field2		The second virtual field.
+     * @return			A virtual field comprising the merger of the
+     *				input fields if possible; otherwise 
+     *				<code>null</code>.  May be <code>field1</code>.
+     * throws VisADException	VisAD failure.
+     */
+    protected VirtualField merge(VirtualField field1, VirtualField field2)
+	throws VisADException
+    {
+	SampledSet	domain1 = field1.getDomainSet();
+
+	return
+	    (field1.getFunctionType().getDomain().equals(
+		field2.getFunctionType().getDomain()) &&
+	    domain1.equals(field2.getDomainSet()))
+		? VirtualField.newVirtualField(
+		    domain1, 
+		    merge(field1.getRangeTuple(), field2.getRangeTuple()))
+		: (VirtualField)null;
+    }
+
+
+    /**
+     * Merges a virtual flat-field with another virtual data object.  Order is
+     * preserved.
+     *
+     * @param field		The virtual flat-field.
+     * @param data		The other virtual data object.
+     * @return			A virtual field comprising the merger of the
+     *				input objects if possible; otherwise 
+     *				<code>null</code>.  May be <code>field</code>.
+     * throws VisADException	VisAD failure.
+     */
+    protected VirtualField merge(VirtualFlatField field, VirtualData data)
+	throws VisADException
+    {
+	return
+	    data instanceof VirtualFlatField
+		? (VirtualField)merge(field, (VirtualFlatField)data)
+		: data instanceof VirtualField
+		    ? (VirtualField)merge(field, (VirtualField)data)
+		    : (VirtualField)null;
+    }
+
+
+    /**
+     * Merges a virtual flat-field with a virtual field.  Simply uses
+     * <code>merge(VirtualField, VirtualField)</code>.  This method may
+     * be overridden by subclasses.
+     *
+     * @param field1		The virtual flat-field.
+     * @param field2		The virtual field.
+     * @return			A virtual field comprising the merger of
+     *				the input objects if possible; otherwise 
+     *				<code>null</code>.
+     * throws VisADException	VisAD failure.
+     * @see #merge(VirtualField, VirtualField)
+     */
+    protected VirtualField merge(VirtualFlatField field1, VirtualField field2)
+	throws VisADException
+    {
+	return 
+	    merge((VirtualField)field1, (VirtualField)field2);
+    }
+
+
+    /**
+     * Merges a virtual flat-field with another virtual flat-field.  Simply uses
+     * <code>merge(VirtualFlatField, VirtualFlatField)</code>.  This method may
+     * be overridden by subclasses.
+     *
+     * @param field1		The virtual flat-field.
+     * @param field2		The other virtual flat-field.
+     * @return			A virtual flat-field comprising the merger of
+     *				the input objects if possible; otherwise 
+     *				<code>null</code>.  May be <code>field1</code>.
+     * throws VisADException	VisAD failure.
+     * @see #merge(VirtualFlatField, VirtualFlatField)
+     */
+    protected VirtualFlatField merge(
+	    VirtualFlatField field1, VirtualFlatField field2)
+	throws VisADException
+    {
+	return (VirtualFlatField)
+	    merge((VirtualField)field1, (VirtualField)field2);
+    }
+}
diff --git a/visad/data/netcdf/in/NetcdfAdapter.java b/visad/data/netcdf/in/NetcdfAdapter.java
new file mode 100644
index 0000000..906d4ad
--- /dev/null
+++ b/visad/data/netcdf/in/NetcdfAdapter.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: NetcdfAdapter.java,v 1.30 2002-10-21 20:07:46 donm Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import ucar.netcdf.Netcdf;
+import ucar.netcdf.NetcdfFile;
+import visad.*;
+import visad.data.BadFormException;
+import visad.data.netcdf.*;
+
+
+/**
+ * <p>A class for importing a netCDF dataset.</p>
+ *
+ * <p>This implementation uses a {@link Strategy} for importing netCDF datasets.
+ * The initial strategy is determined from the Java (not JavaBean) property
+ * <em>visad.data.netcdf.in.Strategy</em>.  If that property is set, then its
+ * values is used as the name of the initial {@link Strategy} class to use.
+ * If that property is not set, then the initial strategy is {@link
+ * Strategy#DEFAULT}.  See, however, {@link #setDefaultStrategy(Strategy)}.</p>
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+NetcdfAdapter
+{
+    /**
+     * The name of the import-strategy Java property.  (NOTE: A Java property is
+     * not a JavaBean property.)
+     */
+    public static final String  IMPORT_STRATEGY_PROPERTY =
+        "visad.data.netcdf.in.Strategy";
+
+    /**
+     * The default strategy to use for importing a netCDF dataset.
+     */
+    private static Strategy     strategy;
+
+    /**
+     * The view of the netCDF datset.
+     */
+    private View                view;
+
+    /**
+     * The top-level VisAD data object corresponding to the netCDF datset.
+     */
+    private DataImpl            data;
+
+    static {
+        String      strategyName =
+            System.getProperty(IMPORT_STRATEGY_PROPERTY);
+        try
+        {
+            strategy =
+                strategyName == null
+                    ? Strategy.DEFAULT
+                    : (Strategy)Class.forName(strategyName).getMethod(
+                        "instance", new Class[0])
+                        .invoke(null, new Object[0]);
+        }
+        catch (NoSuchMethodException e)
+        {
+            throw new Error(
+                "Import strategy \"" + strategyName + "\" doesn't have an "
+                + "\"instance()\" method");
+        }
+        catch (ClassNotFoundException e)
+        {
+            throw new Error(
+                "Import strategy \"" + strategyName + "\" not found");
+        }
+        catch (IllegalAccessException e)
+        {
+            throw new Error(
+                "Permission to access import strategy \"" + strategyName +
+                "\" denied");
+        }
+        catch (java.lang.reflect.InvocationTargetException e)
+        {
+            throw new Error(
+                "Import strategy's \"" +
+                strategyName + "\" \"instance()\" method threw exception: "
+                + e.getMessage());
+        }
+    }
+
+    /**
+     * Constructs from a netCDF dataset.
+     *
+     * @param netcdf            The netCDF dataset to be adapted.
+     * @param quantityDB        A quantity database to be used to map netCDF
+     *                          variables to VisAD {@link Quantity}s.
+     * @throws VisADException   Problem in core VisAD.  Probably some VisAD
+     *                          object couldn't be created.
+     * @throws RemoteException  Remote data access failure.
+     * @throws IOException      Data access I/O failure.
+     * @throws BadFormException Non-conforming netCDF dataset.
+     */
+    public
+    NetcdfAdapter(Netcdf netcdf, QuantityDB quantityDB)
+        throws VisADException, RemoteException, IOException, BadFormException
+    {
+        this(netcdf, quantityDB, false);
+    } 
+
+    /**
+     * Constructs from a netCDF dataset.
+     *
+     * @param netcdf            The netCDF dataset to be adapted.
+     * @param quantityDB        A quantity database to be used to map netCDF
+     *                          variables to VisAD {@link Quantity}s.
+     * @param charToText        Specifies whether the NetcdfAdapter should 
+     *                          map char variables to VisAD Text objects
+     * @throws VisADException   Problem in core VisAD.  Probably some VisAD
+     *                          object couldn't be created.
+     * @throws RemoteException  Remote data access failure.
+     * @throws IOException      Data access I/O failure.
+     * @throws BadFormException Non-conforming netCDF dataset.
+     */
+    public
+    NetcdfAdapter(Netcdf netcdf, QuantityDB quantityDB, boolean charToText)
+        throws VisADException, RemoteException, IOException, BadFormException
+    {
+        this(View.getInstance(netcdf, quantityDB, charToText));
+    }
+
+    /**
+     * Constructs from a view of a netCDF dataset.
+     *
+     * @param view              The view of the netCDF dataset to be adapted.
+     */
+    public
+    NetcdfAdapter(View view)
+    {
+        this.view = view;
+    }
+
+
+    /**
+     * Sets the default strategy used to import a netCDF dataset.  Subsequent
+     * use of the {@link #getData()} method will use the given strategy.
+     *
+     * @param strategy               The default strategy to use.
+     * @return                       The previous strategy.
+     * @throws NullPointerException if the strategy is <code>null</code>.
+     */
+    public static synchronized Strategy setDefaultStrategy(Strategy strategy) {
+        if (strategy == null)
+            throw new NullPointerException();
+        Strategy prev = NetcdfAdapter.strategy;
+        NetcdfAdapter.strategy = strategy;
+        return prev;
+    }
+
+
+    /**
+     * Returns the default strategy used to import a netCDF dataset.
+     *
+     * @return                       The default strategy.
+     */
+    public static synchronized Strategy getDefaultStrategy() {
+        return strategy;
+    }
+
+
+    /**
+     * <p>Gets the VisAD data object corresponding to the netCDF dataset.  This
+     * is a potentially expensive method in either time or space.</p>
+     *
+     * <p>This implementation invokes method {@link #getData(Strategy)} with the
+     * default {@link Strategy}.</p>
+     *
+     * @return                  The top-level, VisAD data object in the netCDF
+     *                          dataset.
+     * @throws VisADException   Problem in core VisAD.  Probably some VisAD
+     *                          object couldn't be created.
+     * @throws IOException      Data access I/O failure.
+     * @throws BadFormException netCDF dataset doesn't conform to conventions
+     *                          implicit in the View that was passed to the
+     *                          constructor.
+     * @throws OutOfMemoryError Couldn't read netCDF dataset into memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @see #getData(Strategy)
+     */
+    public synchronized DataImpl getData()
+        throws IOException, VisADException, RemoteException, BadFormException,
+            OutOfMemoryError
+    {
+        synchronized(getClass()) {
+            return getData(strategy);
+        }
+    }
+
+    /**
+     * Gets the VisAD data object corresponding to the netCDF dataset using a
+     * given strategy.  This is a potentially expensive method in either time or
+     * space.</p>
+     *
+     * @param strategy          The strategy to use for importing the data.
+     * @return                  The top-level, VisAD data object in the netCDF
+     *                          dataset.
+     * @throws VisADException   Problem in core VisAD.  Probably some VisAD
+     *                          object couldn't be created.
+     * @throws IOException      Data access I/O failure.
+     * @throws BadFormException netCDF dataset doesn't conform to conventions
+     *                          implicit in the View that was passed to the
+     *                          constructor.
+     * @throws OutOfMemoryError Couldn't read netCDF dataset into memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     */
+    public synchronized DataImpl getData(Strategy strategy)
+        throws IOException, VisADException, RemoteException, BadFormException,
+            OutOfMemoryError
+    {
+        if (data == null)
+        {
+            synchronized(getClass()) {
+                data = strategy.getData(this);
+            }
+        }
+
+        return data;
+    }
+
+
+    /**
+     * Returns a proxy for the VisAD data object corresponding to the netCDF 
+     * dataset.  Because of the way import strategies are used, this just
+     * invokes the <em>getData()</em> method.
+     *
+     * @return                  A proxy for the top-level, VisAD data object in
+     *                          the netCDF dataset.
+     * @throws VisADException   Problem in core VisAD.  Probably some VisAD
+     *                          object couldn't be created.
+     * @throws IOException      Data access I/O failure.
+     * @throws BadFormException netCDF dataset doesn't conform to conventions
+     *                          implicit in constructing View.
+     * @throws OutOfMemoryError Couldn't read netCDF dataset into memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @see Strategy#getData
+     */
+    public DataImpl
+    getProxy()
+        throws IOException, VisADException, RemoteException, BadFormException,
+            OutOfMemoryError
+    {
+        return getData();
+    }
+
+
+    /**
+     * Returns the VisAD data object corresponding to the netCDF dataset.  This
+     * is a potentially expensive method in either time or space.  This method
+     * is designed to be used by a <em>Strategy</em>.
+     *
+     * @param view              The view of the netCDF dataset.
+     * @param merger            The object that merges the data objects in the
+     *                          netCDF dataset.
+     * @param dataFactory       The factory that creates VisAD data objects from
+     *                          virtual data objects.
+     * @return                  The VisAD data object corresponding to the
+     *                          netCDF dataset.
+     * @throws VisADException   Problem in core VisAD.  Probably some VisAD
+     *                          object couldn't be created.
+     * @throws IOException      Data access I/O failure.
+     * @throws BadFormException netCDF dataset doesn't conform to conventions
+     *                          implicit in constructing View.
+     * @throws OutOfMemoryError Couldn't read netCDF dataset into memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @see Strategy
+     */
+    protected static DataImpl
+    importData(View view, Merger merger, DataFactory dataFactory)
+        throws IOException, VisADException, RemoteException, BadFormException,
+            OutOfMemoryError
+    {
+        VirtualTuple    topTuple = new VirtualTuple();
+
+        for (VirtualDataIterator iter = view.getVirtualDataIterator();
+            iter.hasNext(); )
+        {
+            merger.merge(topTuple, iter.next());
+        }
+
+        topTuple.setDataFactory(dataFactory);
+
+        return topTuple.getData();
+    }
+
+
+    /**
+     * Gets the view of the netCDF dataset.
+     *
+     * @return                  The view of the netCDF dataset.
+     */
+    protected View
+    getView()
+    {
+        return view;
+    }
+
+
+    /**
+     * Tests this class.
+     *
+     * @param args              File pathnames.
+     * @throws Exception        Something went wrong.
+     */
+    public static void
+    main(String[] args)
+        throws Exception
+    {
+        String[]        pathnames;
+
+        if (args.length == 0)
+            pathnames = new String[] {"test.nc"};
+        else
+            pathnames = args;
+
+        for (int i = 0; i < pathnames.length; ++i)
+        {
+            NetcdfFile  file;
+            try
+            {
+                URL     url = new URL(pathnames[i]);
+                file = new NetcdfFile(url);
+            }
+            catch (MalformedURLException e)
+            {
+                file = new NetcdfFile(pathnames[i], /*readonly=*/true);
+            }
+            NetcdfAdapter       adapter =
+                new NetcdfAdapter(file, QuantityDBManager.instance());
+            DataImpl            data = adapter.getData();
+
+            System.out.println("data.getClass().getName() = " +
+                data.getClass().getName());
+
+            System.out.println("data.getType().prettyString():\n" +
+                data.getType().prettyString());
+            // System.out.println("Domain set:\n" +
+                // ((FieldImpl)data).getDomainSet());
+            // System.out.println("Data:\n" + data);
+        }
+    }
+}
diff --git a/visad/data/netcdf/in/NetcdfQuantityDB.java b/visad/data/netcdf/in/NetcdfQuantityDB.java
new file mode 100644
index 0000000..f2f433e
--- /dev/null
+++ b/visad/data/netcdf/in/NetcdfQuantityDB.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: NetcdfQuantityDB.java,v 1.12 2001-11-27 22:29:35 dglo Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.util.Iterator;
+import visad.Unit;
+import visad.VisADException;
+import visad.data.netcdf.Quantity;
+import visad.data.netcdf.QuantityDB;
+
+
+/**
+ * Provides support for mapping netCDF elements to VisAD quantities by
+ * decorating a existing quantity database with methods specifically
+ * appropriate to netCDF variables.
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+NetcdfQuantityDB
+    extends	QuantityDB
+{
+    /**
+     * The quantity database to decorate.
+     */
+    private QuantityDB	db;
+
+
+    /**
+     * Constructs from another quantity database.
+     */
+    public
+    NetcdfQuantityDB(QuantityDB db)
+    {
+	this.db = db;
+    }
+
+
+    /**
+     * Adds a given Quantity to the database under a given name.
+     *
+     * @param name		The name under which the quantity is to be
+     *				added.  May be an alias.
+     * @param quantity		The quantity to be added.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    public void
+    add(String name, Quantity quantity)
+	throws VisADException
+    {
+	db.add(name, quantity);
+    }
+	    
+
+	
+    /**
+     * Return the VisAD quantity corresponding to the best combination of
+     * long name and name.
+     *
+     * @param longName	The long name of the quantity.  May be
+     *			<code>null</code>.
+     * @param name	The name of the quantity.
+     * @return		The corresponding, unique, VisAD quantity or
+     *			<code>null</code> if no such quantity exists.
+     */
+    public Quantity
+    getBest(String longName, String name)
+    {
+	Quantity	quantity = longName == null
+					? null
+					: get(longName);
+
+	return quantity != null
+		? quantity
+		: get(name);
+    }
+
+
+    /**
+     * Returns an iterator of the names in the database.
+     */
+    public Iterator
+    nameIterator()
+    {
+	return db.nameIterator();
+    }
+
+
+    /**
+     * Returns an iterator of the quantities in the database.
+     */
+    public Iterator
+    quantityIterator()
+    {
+	return db.quantityIterator();
+    }
+
+
+    /**
+     * Returns all quantities in the database whose default unit is
+     * convertible with a given unit.
+     *
+     * @param unit	The unit of the quantity.
+     * @return		The quantities in the database whose unit is
+     *			convertible with <code>unit</code>.
+     */
+    public Quantity[]
+    get(Unit unit)
+    {
+	return db.get(unit);
+    }
+
+
+    /**
+     * Returns the quantity in the database whose name matches a
+     * given name.
+     *
+     * @param name	The name of the quantity.
+     * @return		The quantity in the loal database that matches
+     *			<code>name</code>.  Note that
+     *			RETURN_VALUE<code>.getName().equals(name)</code> can
+     *			be <code>false due to aliasing.
+     */
+    public Quantity
+    get(String name)
+    {
+	return db.get(name);
+    }
+}
diff --git a/visad/data/netcdf/in/Strategy.java b/visad/data/netcdf/in/Strategy.java
new file mode 100644
index 0000000..b754daa
--- /dev/null
+++ b/visad/data/netcdf/in/Strategy.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: Strategy.java,v 1.1 2001-12-19 21:00:49 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.VisADException;
+import visad.data.BadFormException;
+import visad.DataImpl;
+
+/**
+ * <p>A strategy for importing a netCDF dataset.</p>
+ *
+ * @author Steven R. Emmerson
+ */
+public abstract class Strategy
+{
+    /**
+     * An import strategy that only tries the {@link MaxFileFieldStrategy}
+     * strategy.
+     */
+    public static final Strategy UNMERGED_FILE_FLAT_FIELDS;
+
+    /**
+     * An import strategy that first tries the {@link FileStrategy} strategy
+     * and then tries the {@link #UNMERGED_FILE_FLAT_FIELDS} strategy.
+     */
+    public static final Strategy MERGED_FILE_FLAT_FIELDS;
+
+    /**
+     * An import strategy that first tries the {@link InMemoryStrategy} strategy
+     * and then tries the {@link #MERGED_FILE_FLAT_FIELDS} strategy.
+     */
+    public static final Strategy IN_MEMORY;
+
+    /**
+     * The default import strategy.  The details of this strategy are 
+     * unspecified and subject to change.  Currently, it is identical to the
+     * {@link #MERGED_FILE_FLAT_FIELDS} strategy.
+     */
+    public static final Strategy DEFAULT;
+
+    /**
+     * The singleton instance of this class.
+     */
+    private static Strategy instance;
+
+    static
+    {
+        UNMERGED_FILE_FLAT_FIELDS = MaxFileFieldStrategy.instance();
+        MERGED_FILE_FLAT_FIELDS =
+            CompositeStrategy.instance(new Strategy[] {
+                FileStrategy.instance(),
+                UNMERGED_FILE_FLAT_FIELDS,
+            });
+        IN_MEMORY = 
+            CompositeStrategy.instance(new Strategy[] {
+                InMemoryStrategy.instance(),
+                FileStrategy.instance(),
+                MERGED_FILE_FLAT_FIELDS,
+            });
+        DEFAULT = MERGED_FILE_FLAT_FIELDS;
+    }
+
+
+    /**
+     * Constructs from nothing.
+     */
+    protected Strategy()
+    {}
+
+
+    /**
+     * <p>Returns a VisAD data object corresponding to the netCDF dataset.</p>
+     *
+     * @param adapter           The netCDF-to-VisAD adapter.
+     * @return                  The top-level, VisAD data object of the netCDF
+     *                          dataset.
+     * @throws VisADException   if a problem occurs in core VisAD -- probably 
+     *                          because a VisAD object couldn't be created.
+     * @throws IOException      if a data access I/O failure occurs.
+     * @throws BadFormException if the netCDF dataset doesn't conform to
+     *                          conventions implicit in constructing
+     *                          View.
+     * @throws OutOfMemoryError if the netCDF dataset couldn't be imported into 
+     *                          memory.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     */
+    public abstract DataImpl getData(NetcdfAdapter adapter)
+        throws IOException, VisADException, RemoteException,
+            BadFormException, OutOfMemoryError;
+}
diff --git a/visad/data/netcdf/in/Vetter.java b/visad/data/netcdf/in/Vetter.java
new file mode 100644
index 0000000..babe6a2
--- /dev/null
+++ b/visad/data/netcdf/in/Vetter.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: Vetter.java,v 1.8 2001-03-14 17:44:15 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import ucar.netcdf.Attribute;
+import ucar.netcdf.Variable;
+import visad.data.in.OffsetUnpacker;
+import visad.data.in.ScaleAndOffsetUnpacker;
+import visad.data.in.ScaleUnpacker;
+import visad.data.in.ValueRanger;
+import visad.data.in.ValueUnpacker;
+import visad.data.in.ValueVetter;
+
+
+/**
+ * The Vetter class vets netCDF values, replacing invalid values with their
+ * VisAD equivalents.
+ */
+final class
+Vetter
+{
+    /**
+     * The object that vets the raw data.
+     */
+    private ValueVetter		vetter;
+
+    /**
+     * The object that unpacks the vetted data.
+     */
+    private ValueUnpacker	unpacker;
+
+    /**
+     * The object that ranges the unpacked data.
+     */
+    private ValueRanger		ranger;
+
+    /**
+     * The type of the netCDF variable.
+     */
+    private Class		type;
+
+    /**
+     * The minimum, valid, external, netCDF value (won't be NaN).
+     */
+    private double		minValid;
+
+    /**
+     * The maximum, valid, external, netCDF value (won't be NaN).
+     */
+    private double		maxValid;
+
+    /**
+     * The fill-value value.
+     */
+    private double		fill;
+
+
+    /**
+     * Constructs from nothing.  Protected to ensure use by subclasses
+     * only.
+     */
+    protected
+    Vetter()
+    {}
+
+
+    /**
+     * Constructs from a netCDF variable type.
+     *
+     * @param type		The Java type of the netCDF variable (i.e.
+     *				float.class, char.class, etc.)
+     */
+    Vetter(Class type)
+    {
+	this.type = type;
+
+	if (type.equals(byte.class))
+	{
+	    fill = Double.NaN;		// i.e. no default fill-value
+	    minValid = Byte.MIN_VALUE;
+	    maxValid = Byte.MAX_VALUE;
+	}
+	else if (type.equals(short.class))
+	{
+	    fill = -32767;
+	    minValid = Short.MIN_VALUE;
+	    maxValid = Short.MAX_VALUE;
+	}
+	else if (type.equals(int.class))
+	{
+	    fill = -2147483647;
+	    minValid = Integer.MIN_VALUE;
+	    maxValid = Integer.MAX_VALUE;
+	}
+	else if (type.equals(float.class))
+	{
+	    fill = 9.9692099683868690e+36;
+	    minValid = Float.NEGATIVE_INFINITY;
+	    maxValid = Float.POSITIVE_INFINITY;
+	}
+	else if (type.equals(double.class))
+	{
+	    fill = 9.9692099683868690e+36;
+	    minValid = Double.NEGATIVE_INFINITY;
+	    maxValid = Double.POSITIVE_INFINITY;
+	}
+	else
+	{
+	    fill = 0;
+	    minValid = 0;
+	    maxValid = 0;
+	}
+    }
+
+
+    /**
+     * Constructs from a netCDF variable.
+     *
+     * @param var		The netCDF variable to be examined.
+     */
+    Vetter(Variable var)
+    {
+	this(var.getComponentType());	// set paramters to default values
+
+	Attribute	attr;
+	double		missing = Double.NaN;
+	double		lower = Double.NEGATIVE_INFINITY;
+	double		upper = Double.POSITIVE_INFINITY;
+
+	/*
+	 * Set the object that will vet the raw data.
+	 */
+	{
+	    attr = var.getAttribute("_FillValue");
+	    if (attr != null)
+	    {
+		fill = attr.getNumericValue().doubleValue();
+		if (fill < 0)
+		{
+		    lower =
+			(type.equals(float.class) || type.equals(double.class))
+			    ? fill/2
+			    : fill + 1;
+		}
+		else if (fill > 0)
+		{
+		    upper =
+			(type.equals(float.class) || type.equals(double.class))
+			    ? fill/2
+			    : fill - 1;
+		}
+	    }
+	    attr = var.getAttribute("missing_value");
+	    if (attr != null)
+		missing = attr.getNumericValue().doubleValue();
+	    vetter = ValueVetter.valueVetter(new double[] {fill, missing});
+	}
+
+	/*
+	 * Set the object that will unpack the vetted data.
+	 */
+	{
+	    attr = var.getAttribute("scale_factor");
+	    double	scale =
+		attr == null ? 1 : attr.getNumericValue().doubleValue();
+	    attr = var.getAttribute("add_offset");
+	    double	offset =
+		attr == null ? 0 : attr.getNumericValue().doubleValue();
+	    if (scale == scale && scale != 1 && offset == offset && offset != 0)
+	    {
+		unpacker = ScaleAndOffsetUnpacker.scaleAndOffsetUnpacker(
+		    scale, offset);
+	    }
+	    else if (scale == scale && scale != 1)
+	    {
+		unpacker = ScaleUnpacker.scaleUnpacker(scale);
+	    }
+	    else if (offset == offset && offset != 0)
+	    {
+		unpacker = OffsetUnpacker.offsetUnpacker(offset);
+	    }
+	    else
+	    {
+		unpacker = ValueUnpacker.valueUnpacker();
+	    }
+	}
+
+	/*
+	 * Set the object that will range the unpacked data.
+	 */
+	{
+	    attr = var.getAttribute("valid_range");
+	    if (attr != null)
+	    {
+		lower = attr.getNumericValue(0).doubleValue();
+		upper = attr.getNumericValue(1).doubleValue();
+	    }
+	    attr = var.getAttribute("valid_min");
+	    if (attr != null)
+		lower = attr.getNumericValue().doubleValue();
+	    attr = var.getAttribute("valid_max");
+	    if (attr != null)
+		upper = attr.getNumericValue().doubleValue();
+	    ranger = ValueRanger.valueRanger(lower, upper);
+	    /*
+	     * Account for NaN semantics in the following:
+	     */
+	    if (minValid < lower)
+		minValid = lower;
+	    if (maxValid > upper)
+		maxValid = upper;
+	}
+    }
+
+
+    /**
+     * Returns the minimum, valid, netCDF value.
+     *
+     * @return	The minimum, valid, value for the variable.
+     */
+    double
+    minValid()
+    {
+	return minValid;
+    }
+
+
+    /**
+     * Returns the maximum, valid, netCDF value.
+     *
+     * @return	The maximum, valid, value for the variable.
+     */
+    double
+    maxValid()
+    {
+	return maxValid;
+    }
+
+
+    /**
+     * Vets the given float values.
+     *
+     * @param values	The values to be vetted.
+     * @postcondition	All invalid values in <code>values</code> have been
+     *			replaced with NaN's.
+     */
+    public void
+    vet(float[] values)
+    {
+	ranger.process(unpacker.process(vetter.process(values)));
+    }
+
+
+    /**
+     * Vets the given double values.
+     *
+     * @param values	The values to be vetted.
+     * @postcondition	All invalid values in <code>values</code> have been
+     *			replaced with NaN's.
+     */
+    public void
+    vet(double[] values)
+    {
+	ranger.process(unpacker.process(vetter.process(values)));
+    }
+}
diff --git a/visad/data/netcdf/in/View.java b/visad/data/netcdf/in/View.java
new file mode 100644
index 0000000..6169211
--- /dev/null
+++ b/visad/data/netcdf/in/View.java
@@ -0,0 +1,1275 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: View.java,v 1.9 2006-02-13 22:30:08 curtis Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.WeakHashMap;
+import ucar.netcdf.Attribute;
+import ucar.netcdf.Dimension;
+import ucar.netcdf.DimensionIterator;
+import ucar.netcdf.Netcdf;
+import ucar.netcdf.Variable;
+import ucar.netcdf.VariableIterator;
+import visad.CoordinateSystem;
+import visad.data.in.ArithProg;
+import visad.data.netcdf.Quantity;
+import visad.data.netcdf.QuantityDB;
+import visad.data.units.Parser;
+import visad.DoubleSet;
+import visad.ErrorEstimate;
+import visad.FloatSet;
+import visad.Gridded1DDoubleSet;
+import visad.Gridded1DSet;
+import visad.Integer1DSet;
+import visad.Linear1DSet;
+import visad.RealType;
+import visad.ScalarType;
+import visad.SI;
+import visad.SimpleSet;
+import visad.TextType;
+import visad.TypeException;
+import visad.Unit;
+import visad.VisADException;
+
+/**
+ * A convention-dependent view of a netCDF dataset.
+ *
+ * @author Steven R. Emmerson
+ * @version $Revision: 1.9 $ $Date: 2006-02-13 22:30:08 $
+ */
+public abstract class View
+{
+    /**
+     * The netCDF dataset that is being viewed through these conventions.
+     */
+    private final Netcdf     netcdf;
+
+    /**
+     * The quantity database to use to map netCDF variables to VisAD
+     * Quantity-s.
+     */
+    private final QuantityDB quantityDB;
+
+    /**
+     * Flag for whether this View handles char variables as Text
+     */
+    private final boolean charToText;
+
+    /*
+     * Performance caches:
+     */
+    private final Map        varToRealType;
+    private final Map        varToTextType;
+    private final Map        varToUnit;
+    private final Map        dimToSet;
+    private final Map        dimToRealType;
+
+    /*
+     * Well-known quantities for comparison purposes:
+     */
+    private final Quantity   longitude;
+    private final Quantity   latitude;
+
+    /**
+     * Something for creating unique names.
+     */
+    private static int       nameCount;
+
+    /** Names of outer dimensions to be factored out
+    *
+    */
+
+    private java.util.Set factorNameSet = null;
+
+    /**
+     * Constructs from a netCDF dataset.
+     *
+     * @param netcdf                The netCDF dataset.
+     * @param quantityDB            The quantity database to use to map netCDF
+     *                              variables to VisAD Quantity-s.
+     * @throws NullPointerException if either argument is <code>null</code>.
+     */
+    protected View(Netcdf netcdf, QuantityDB quantityDB)
+    {
+        this(netcdf, quantityDB, false);
+    }
+
+    /**
+     * Constructs from a netCDF dataset.
+     *
+     * @param netcdf                The netCDF dataset.
+     * @param quantityDB            The quantity database to use to map netCDF
+     *                              variables to VisAD Quantity-s.
+     * @param charToText            Specifies whether the View should map char
+     *                              variables to VisAD Text objects
+     * @throws NullPointerException if either argument is <code>null</code>.
+     */
+    protected View(Netcdf netcdf, QuantityDB quantityDB, boolean charToText)
+    {
+        this.netcdf = netcdf;
+        this.quantityDB = quantityDB;
+        this.charToText = charToText;
+        varToUnit = new WeakHashMap();
+        dimToSet = new WeakHashMap();
+        varToRealType = new WeakHashMap();
+        varToTextType = new WeakHashMap();
+        dimToRealType = new WeakHashMap();
+        longitude = quantityDB.get("longitude");
+        latitude = quantityDB.get("latitude");
+    }
+
+    /**
+     * Returns a view of a netCDF dataset.  The exact view returned depends
+     * on the netCDF dataset.
+     *
+     * @param netcdf            The netCDF dataset.
+     * @param db		        A quantity database to be used to map netCDF
+     *                          variables to VisAD {@link Quantity}s.
+     * @return                  A view of the dataset.
+     */
+    public static View getInstance(Netcdf netcdf, QuantityDB db)
+    {
+        return getInstance(netcdf, db, false);
+    }
+
+    /**
+     * Returns a view of a netCDF dataset.  The exact view returned depends
+     * on the netCDF dataset.
+     *
+     * @param netcdf            The netCDF dataset.
+     * @param db		        A quantity database to be used to map netCDF
+     *                          variables to VisAD {@link Quantity}s.
+     * @param charToText        Specifies whether the View should map char
+     *                          variables to VisAD Text objects
+     * @return                  A view of the dataset.
+     */
+    public static View getInstance(
+        Netcdf netcdf, QuantityDB db, boolean charToText)
+    {
+        View   view;
+        String conventions = getConventionsString(netcdf);
+        if (conventions == null)
+        {
+            view = new DefaultView(netcdf, db, charToText);
+        }
+        else
+        {
+            try
+            {
+                if (conventions.equals("CF-1.0"))
+                    view = new CfView(netcdf, db, charToText);
+                else if (conventions.equals("COARDS"))
+                    view = new CfView(netcdf, db);
+                else if (conventions.equals("COARDS/CF-1.0"))
+                    view = new CfView(netcdf, db, charToText);
+                else
+                {
+                    System.err.println(
+                        "Unknown netCDF conventions attribute (" +
+                        conventions + ").  Using default view...");
+                    view = new DefaultView(netcdf, db, charToText);
+                }
+            }
+            catch (IllegalArgumentException e)
+            {
+                System.err.println(
+                    "netCDF dataset doesn't follow stated conventions (" +
+                    conventions + "): " + e.getMessage() + 
+                    "\nUsing default view...");
+                view = new DefaultView(netcdf, db, charToText);
+            }
+        }
+        return view;
+    }
+
+    /**
+     * Does this View handle text.
+     *
+     * @return true if text is handled
+     */
+    public boolean isCharToText() {
+       return charToText;
+    }
+
+    /**
+     * Returns the underlying netCDF dataset.
+     *
+     * @return                  The netCDF dataset.
+     */
+    public Netcdf getNetcdf()
+    {
+        return netcdf;
+    }
+
+    /**
+     * <p>Returns the value of the global "Conventions" attribute.  If the
+     * attribute doesn't exist or is invalid, then <code>null</code> is
+     * returned.  If the attribute exists but is not string-valued, then an
+     * error message is printed to {@link System#err} and <code>null</code> is
+     * returned.</p>
+     *
+     * @param netcdf                The netCDF dataset.
+     * @return                      The value of the attribute.
+     */
+    protected static String getConventionsString(Netcdf netcdf)
+    {
+        Attribute attr = netcdf.getAttribute("Conventions");
+        try
+        {
+            return attr != null ? attr.getStringValue() : null;
+        }
+        catch (ClassCastException e)
+        {
+            System.err.println("The \"Conventions\" attribute (" + attr + 
+                ") isn't a string");
+            return null;
+        }
+    }
+
+    /**
+     * Returns the named netCDF variable.  Returns <code>null</code> if the
+     * variable doesn't exist.
+     *
+     * @param name                  The name of the netCDF variable.
+     * @throws NullPointerException if the name is <code>null</code>.
+     * @return                      The named netCDF variable or 
+     *                              <code>null</code>.
+     */
+    protected Variable getVariable(String name)
+    {
+        return netcdf.get(name);
+    }
+
+    /**
+     * Indicates if the netCDF variable with a given name is numeric.
+     *
+     * @param name                   The name of the netCDF variable.
+     * @return                       <code>true</code> if and only if the
+     *                               variable exists has numeric values.
+     */
+    protected boolean isNumeric(String name)
+    {
+        Variable var = netcdf.get(name);
+        if (var == null)
+            return false;
+        return isNumeric(var);
+    }
+
+    /**
+     * Indicates if the given netCDF variable is numeric.
+     *
+     * @param var                    The netCDF variable.
+     * @return                       <code>true</code> if and only if the
+     *                               variable is numeric.
+     * @throws NullPointerException  if the argument is <code>null</code>.
+     */
+    protected boolean isNumeric(Variable var)
+    {
+        return !var.getComponentType().equals(char.class);
+    }
+
+    /**
+     * Indicates if a netCDF dimension represents longitude.  This method uses
+     * {@link #getRealType(Variable)} and {@link #isLongitude(RealType)}.
+     *
+     * @param var               A netCDF dimension.
+     * @return                  <code>true</code> if an only if <code>dim</code>
+     *                          represents longitude.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     */
+    protected boolean isLongitude(Variable var)
+        throws VisADException
+    {
+        RealType type = getRealType(var);
+        return type != null && type.equals(longitude);
+    }
+
+    /**
+     * Indicates if a VisAD {@link RealType} represents longitude.
+     *
+     * @param type              A VisAD {@link RealType}.  May be 
+     *                          <code>null</code>.
+     * @return                  <code>true</code> if an only if the VisAD
+     *                          {@link RealType} represents longitude.
+     */
+    protected boolean isLongitude(RealType type)
+    {
+        return type != null && type.equals(longitude);
+    }
+
+    /**
+     * Indicates if a VisAD {@link RealType} represents latitude.
+     *
+     * @param type              A VisAD {@link RealType}.  May be 
+     *                          <code>null</code>.
+     * @return                  <code>true</code> if an only if the VisAD
+     *                          {@link RealType} represents latitude.
+     */
+    protected boolean isLatitude(RealType type)
+    {
+        return type != null && type.equals(latitude);
+    }
+
+    /**
+     * <p>Returns the VisAD {@link visad.MathType} of the domain corresponding to a
+     * netCDF dimension.</p>
+     *
+     * <p>This implementation supports coordinate variables and uses {@link
+     * #getCoordinateVariable(Dimension)} and {@link #getRealType(Variable)}.
+     * </p>
+     *
+     * @param dim               A netCDF dimension.
+     * @return                  The VisAD MathType of the domain corresponding
+     *                          to <code>dim</code>. Won't be <code>null</code>.
+     * @throws TypeException    if a corresponding {@link visad.RealType} needed
+     *                          to be created but couldn't.
+     */
+    protected RealType getRealType(Dimension dim)
+        throws TypeException
+    {
+        RealType type;
+        Variable var = getCoordinateVariable(dim);
+        if (var != null)
+        {
+            type = getRealType(var);
+        }
+        else
+        {
+            synchronized(dimToRealType)
+            {
+                type = (RealType)dimToRealType.get(dim);
+                if (type == null)
+                {
+                    String name = dim.getName();
+                    type = quantityDB.get(name);
+                    if (type == null) {
+                        type = RealType.getRealType(name);
+                    }
+                    if (type == null) {
+                        throw new TypeException(
+                            "Couldn't create RealType for " + dim.getName());
+                    }
+                    dimToRealType.put(dim, type);
+                }
+            }
+        }
+        return type;
+    }
+
+    /**
+     * <p>Returns the VisAD {@link RealType} of a netCDF variable.</p>
+     *
+     * <p>This implementation returns the value of {@link
+     * #getRealTypeFromLongName(Variable)} if that is non-<code>null</code>;
+     * otherwise, the value of {@link #getRealTypeFromName(Variable)} is
+     * returned.</p>
+     *
+     * @param var                   The netCDF variable.
+     * @return                      The corresponding VisAD RealType.
+     * @throws NullPointerException if the argument is <code>null</code>.
+     * @throws TypeException        if a corresponding {@link RealType} needed 
+     *                              to be created but couldn't.
+     */
+    protected RealType getRealType(Variable var)
+        throws TypeException
+    {
+        if (var == null)
+            throw new NullPointerException();
+        RealType type;
+        /*
+         * To improve performance, this method caches results.
+         */
+        synchronized(varToRealType)
+        {
+            type = (RealType)varToRealType.get(var);
+            if (type == null)
+            {
+                type = getRealTypeFromLongName(var);
+                if (type == null)
+                    type = getRealTypeFromName(var);
+                varToRealType.put(var, type);  // cache result
+            }
+        }
+        return type;
+    }
+
+    /**
+     * Returns the VisAD RealType corresponding to the <code>long_name</code>
+     * attribute of a netCDF variable.  If the unit attribute of the variable
+     * is incompatible with the unit of the <code>long_name</code> attribute,
+     * then a new RealType is created whose default unit is that of the
+     * attribute.</p>
+     *
+     * <p>This implementation first checks if the variable has a
+     * <code>long_name</code> attribute via {@link #getLongName(Variable)},
+     * if it doesn't, then <code>null</code> is returned; otherwise, the
+     * long name is used to query the quantity database.  If the quantity
+     * database doesn't contain a match, then <code>null</code> is returned;
+     * otherwise, the variable's unit attribute -- obtained via {@link
+     * #getUnitFromAttribute(Variable)} -- is checked.  If the unit attribute
+     * doesn't exist, then the {@link RealType} is returned; otherwise, the unit
+     * attribute is compared against the default unit of the {@link RealType}.
+     * If the two are convertible, then the {@link RealType} is returned;
+     * otherwise, an attempt is made to create a new {@link RealType} with a
+     * slightly different name than the variable's but with the same unit as the
+     * variable's and that {@link RealType} is returned.</p>
+     *
+     * @param var                    The netCDF variable.
+     * @return                       The corresponding VisAD RealType or
+     *                               <code>null</code> if no corresponding type
+     *                               was found or could be created.
+     */
+    protected RealType getRealTypeFromLongName(Variable var)
+    {
+        RealType type;
+        String   name = getLongName(var);
+        if (name == null)
+        {
+            type = null;
+        }
+        else
+        {
+            type = quantityDB.get(name);
+            if (type != null)
+            {
+                Unit unit = getUnitFromAttribute(var);
+                if (!Unit.canConvert(unit, type.getDefaultUnit()))
+                {
+                    String  newName = newName(var);
+                    System.err.println(
+                        "The unit attribute (" + unit + ") " +
+                        "of variable \"" + var.getName() + "\" " + 
+                        "is incompatible with the unit (" +
+                        type.getDefaultUnit() + ") " +
+                        "of the quantity referenced by the long_name attribute "
+                        + "(" + name + ").  " +
+                        "Attempting to create new quantity \"" + newName +
+                        "\".");
+                    type = RealType.getRealType(newName, unit);
+                }
+            }
+        }
+        return type;
+    }
+
+    /**
+     * <p>Returns the VisAD {@link RealType} corresponding to the name of a
+     * netCDF variable. <code>null</code> is never returned.</p>
+     *
+     * <p>This implementation first obtains the variable's unit via {@link
+     * #getUnitFromAttribute(Variable)}.  It then queries the quantity database
+     * for a match to the variable's name.  If a match is found, then variable's
+     * unit is checked.  If the unit is <code>null</code>, then the {@link
+     * RealType} from the database is returned; otherwise, the unit is checked
+     * against the default unit of the obtained {@link RealType}.  If the two
+     * units are convertible, then the {@link RealType} is returned; otherwise,
+     * a new {@link RealType} is created that has a slightly different name than
+     * the variable's but with the variable's unit and that {@link RealType}
+     * is returned.  If the quantity database doesn't contain a match, then
+     * the variable's unit is checked.  If it's <code>null</code>, then
+     * the value of {@link RealType#getRealType(String)} -- when given the
+     * variable's name -- is returned; otherwise, the return value of {@link
+     * RealType#getRealType(String, Unit)} -- when invoked with the variable's
+     * name and unit -- is checked.  If it's non-<code>null</code>, then that
+     * {@link RealType} is returned; otherwise, a new {@link RealType} is
+     * created that has a slightly different name than the variable's but with
+     * the variable's unit and that {@link RealType} is returned.</p>
+     *
+     * @param var                    The netCDF variable.
+     * @return                       The corresponding VisAD RealType.
+     * @throws TypeException         if a corresponding {@link RealType} needed
+     *                               to be created but couldn't.
+     */
+    protected RealType getRealTypeFromName(Variable var)
+        throws TypeException
+    {
+        String   name = var.getName();
+        Unit     unit = getUnitFromAttribute(var);
+        RealType type = quantityDB.get(name);
+        if (type != null)
+        {
+            if (!Unit.canConvert(unit, type.getDefaultUnit()))
+                type = newQuantity(var, unit, type.getDefaultUnit());
+        }
+        else
+        {
+            if (unit == null)
+            {
+                type = RealType.getRealType(name);
+            }
+            else
+            {
+                type = RealType.getRealType(name, unit);
+                if (type == null)
+                {
+                    type =
+                        newQuantity(
+                            var,
+                            unit,
+                            RealType.getRealTypeByName(name).getDefaultUnit());
+                }
+            }
+        }
+        return type;
+    }
+
+    private RealType newQuantity(Variable var, Unit wantUnit, Unit haveUnit)
+        throws TypeException
+    {
+        String  newName = newName(var);
+        System.err.println(
+            "The unit attribute (" + wantUnit + ") " +
+            "of variable \"" + var.getName() + "\" " + 
+            "is incompatible with the unit (" +
+            haveUnit + ") of the RealType of the same name.  " +
+            "Attempting to create new RealType \"" + newName + "\".");
+        RealType type = RealType.getRealType(newName, wantUnit);
+        if (type == null)
+            throw new TypeException(newName);
+        return type;
+    }
+
+    /**
+     * Returns a name for a given variable that is slightly different that the
+     * variable's name and is guarenteed not to have been returned before.
+     *
+     * @param var                    The netCDF variable.
+     * @return                       A new and unique name based on the
+     *                               variable.
+     */
+    protected String newName(Variable var)
+    {
+        return var.getName() + "_" + nameCount++;
+            // getUnitFromAttribute(var).toString().replace(' ', '_')
+            // .replace('.', '_');
+    }
+
+    /**
+     * <p>Gets the type of the values of a netCDF variable.</p>
+     *
+     * <p>This implementation returns the value of #getRealType(Variable)}
+     * or {@link #getTextType(Variable)} -- depending on the value of {@link
+     * #isNumeric(Variable)}.</p>
+     *
+     * @param var               A netCDF variable.
+     * @throws TypeException    if a corresponding {@link RealType} needed
+     *                          to be created but couldn't.
+     * @throws VisADException   if a VisAD object can't be created.
+     */
+    protected ScalarType getScalarType(Variable var)
+        throws TypeException, VisADException
+    {
+        return
+            isNumeric(var)
+                ? (ScalarType)getRealType(var)
+                : (ScalarType)getTextType(var);
+    }
+
+    /**
+     * Return the VisAD TextType of a netCDF variable.
+     *
+     * @param var               The netCDF variable.
+     * @return                  The VisAD TextType of <code>var</code>.
+     * @throws VisADException   if a VisAD object couldn't be created.
+     * @throws IllegalArgumentException
+     *                          if the netCDF variable is not textual.
+     */
+    protected TextType getTextType(Variable var)
+        throws VisADException
+    {
+        if (var == null)
+            throw new NullPointerException();
+        if (isNumeric(var))
+            throw new IllegalArgumentException(var.toString());
+        TextType type;
+        /*
+         * To improve performance, this method caches results.
+         */
+        synchronized(varToTextType)
+        {
+            type = (TextType)varToTextType.get(var);
+            if (type == null)
+            {
+                type = TextType.getTextType(var.getName());
+                varToTextType.put(var, type);  // cache result
+            }
+        }
+        return type;
+    }
+
+    /**
+     * Gets the representational set for the values of a netCDF variable.  If 
+     * the variable isn't numeric, then <code>null</code> is returned.
+     *
+     * <p>This implementation uses {@link #getRealType(Variable)}, {@link 
+     * #getVetter(Variable)}, and {@link #getUnitFromAttribute(Variable)}.</p>
+     *
+     * @param var               A netCDF variable.
+     * @return                  The VisAD representational set for the values of
+     *                          the variable or <code>null</code>.
+     * @throws TypeException    if a corresponding {@link RealType} needed
+     *                          to be created but couldn't.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     */
+    protected SimpleSet getRangeSet(Variable var)
+        throws TypeException, VisADException
+    {
+        SimpleSet       set;
+        Class           cl = var.getComponentType();
+        if (cl.equals(char.class))
+        {
+            set = null;
+        }
+        else
+        {
+            RealType    type = getRealType(var);
+            if (cl.equals(byte.class))
+            {
+                set = new Linear1DSet(type,
+                                      Byte.MIN_VALUE+1, Byte.MAX_VALUE,
+                                      Byte.MAX_VALUE - Byte.MIN_VALUE);
+            }
+            else if (cl.equals(short.class))
+            {
+                set = new Linear1DSet(type,
+                                      Short.MIN_VALUE+1, Short.MAX_VALUE,
+                                      Short.MAX_VALUE - Short.MIN_VALUE);
+            }
+            else if (cl.equals(int.class))
+            {
+                /*
+                 * The following is complicated due to the fact that the last
+                 * argument to the Linear1DSet() constructor:
+                 *
+                 *     Linear1DSet(MathType type, double start, double stop,
+                 *                      int length)
+                 *
+                 * is an "int" -- and the number of Java "int" values cannot
+                 * be represented by a Java "int".
+                 */
+                Vetter  vetter = getVetter(var);
+                long    minValid = (long)vetter.minValid();
+                long    maxValid = (long)vetter.maxValid();
+                long    length  = maxValid - minValid + 1;
+                set = length <= Integer.MAX_VALUE
+                            ? (SimpleSet)(new Linear1DSet(type, minValid,
+                                            maxValid, (int)length))
+                            : (SimpleSet)(new FloatSet(type,
+                                            (CoordinateSystem)null,
+                                            new Unit[] {
+                                                getUnitFromAttribute(var)}));
+            }
+            else if (cl.equals(float.class))
+            {
+                set = new FloatSet(type, (CoordinateSystem)null,
+                                    new Unit[] {getUnitFromAttribute(var)});
+            }
+            else
+            {
+                set = (SimpleSet)new DoubleSet(type, (CoordinateSystem)null,
+                                    new Unit[] {getUnitFromAttribute(var)});
+            }
+        }
+        return set;
+    }
+
+    /**
+     * <p>Returns a string-valued global attribute.  If the attribute doesn't
+     * exist or is invalid, then <code>null</code> is returned.  If the
+     * attribute exists but is not string-valued, then an error message is
+     * printed to {@link System#err} and <code>null</code> is returned.</p>
+     *
+     * <p>This implementation uses {@link #getAttributeString(Variable, String)}.
+     * </p>
+     *
+     * @param name              The name of the attribute.
+     * @return                  The string value of the attribute or
+     *                          <code>null</code>.
+     */
+    protected String getAttributeString(String name)
+    {
+        return getAttributeString((Variable)null, name);
+    }
+
+    /**
+     * Returns a string-valued global attribute or a netCDF variable attribute.
+     * If the attribute doesn't exist or is invalid, then <code>null</code> is
+     * returned.  If the attribute exists but is not string-valued, then an
+     * error message is printed to {@link System#err} and <code>null</code> is
+     * returned.
+     *
+     * @param var               A netCDF variable or <code>null</code> to 
+     *                          indicate a global attribute.
+     * @param name              The name of the attribute.
+     * @return                  The string value of the attribute or
+     *                          <code>null</code>.
+     */
+    protected String getAttributeString(Variable var, String name)
+    {
+        Attribute attr =
+            var == null ? netcdf.getAttribute(name) : var.getAttribute(name);
+        try
+        {
+            return attr != null ? attr.getStringValue() : null;
+        }
+        catch (ClassCastException e)
+        {
+            System.err.println(
+                "Non-string attribute: " + var.getName() + ":" + name);
+            return null;
+        }
+    }
+
+    /**
+     * <p>Returns the long name of a netCDF variable according to the variable's
+     * <code>long_name</code> attribute.  If the attribute doesn't exist, then
+     * <code>null</code> is returned.</p>
+     *
+     * <p>This method uses {@link #getAttributeString(Variable, String)}.</p>
+     *
+     * @param var               A netCDF variable.
+     * @return                  The long name of <code>var</code> or 
+     *                          <code>null</code>.
+     * @throws ClassCastException
+     *                          if the attribute exists but its value isn't a
+     *                          String.
+     */
+    protected String getLongName(Variable var)
+    {
+        return getAttributeString(var, "long_name");
+    }
+
+    /**
+     * <p>Returns the string value of the unit attribute of a netCDF variable.
+     * Returns <code>null</code> if the unit attribute is missing or
+     * invalid.</p>
+     *
+     * <p>This method uses {@link #getAttributeString(Variable, String)} --
+     * first with the name "units" and then with the name "unit".</p>
+     *
+     * @param var               A netCDF variable.
+     * @return                  The unit of the values of <code>var</code> or
+     *                          <code>null</code>.
+     */
+    protected String getUnitString(Variable var)
+    {
+        String str = getAttributeString(var, "units");
+        if (str == null)
+            str = getAttributeString(var, "unit");
+        return str;
+    }
+
+    /**
+     * <p>Returns the unit of a netCDF variable according to the variable's unit
+     * attribute.  Returns <code>null</code> if the unit attribute is missing
+     * or invalid.  If a unit specification exists but can't be decoded, then
+     * a warning message is printed to {@link System#err}.</p>
+     *
+     * <p>This method uses {@link #getUnitString(Variable)}.</p>
+     *
+     * @param var               A netCDF variable.
+     * @return                  The unit of the values of <code>var</code> or
+     *                          <code>null</code>.
+     */
+    protected Unit getUnitFromAttribute(Variable var)
+    {
+        Unit unit;
+        /*
+         * This method caches results to improve performance.
+         */
+        synchronized(varToUnit)
+        {
+            /*
+             * The following two lines exist because the unit map is a
+             * WeakHashMap and may contain null values.
+             */
+            unit = (Unit)varToUnit.get(var);
+            if (!varToUnit.containsKey(var))
+            {
+                String spec = getUnitString(var);
+                if (spec != null)
+                {
+                    try
+                    {
+                        unit = Parser.parse(spec);
+                    }
+                    catch (Exception e)
+                    {
+                        System.err.println(
+                            "Couldn't decode unit attribute (" + spec + ")" +
+                            " of variable \"" + var.getName() + "\": " + 
+                            e.getMessage());
+                    }
+                }
+                varToUnit.put(var, unit);  // cache result
+            }
+        }
+        return unit;
+    }
+
+    /**
+     * Returns a value-vetter for a netCDF variable.
+     *
+     * @param var               A netCDF variable.
+     * @return                  A value-vetter for the variable.
+     */
+    protected Vetter getVetter(Variable var)
+    {
+        return new Vetter(var);
+    }
+
+    /**
+     * <p>Returns the VisAD {@link Gridded1DSet} corresponding to a netCDF
+     * dimension.</p>
+     *
+     * <p>This implementation supports coordinate variables, longitude,
+     * and the discovery of an arithmetic progression.  It uses {@link
+     * #isLongitude(Variable)}, {@link #getRealType(Dimension)}, and {@link
+     * #getUnitFromAttribute(Variable)}. </p>
+     *
+     * @param dim               A netCDF dimension.
+     * @return                  The VisAD {@link visad.GriddedSet} corresponding to
+     *                          the dimension.
+     * @throws VisADException   if a VisAD object couldn't be created.
+     * @throws IOException      if a netCDF read-error occurs.
+     * @throws ClassCastException
+     *                          if the dimension has a coordinate variable of
+     *                          improper type.
+     */
+    protected Gridded1DSet getDomainSet(Dimension dim)
+        throws VisADException, IOException
+    {
+        /*
+         * This implementation caches earlier results because this operation is
+         * potentially expensive and may be invoked many times for any given
+         * dimension.
+         */
+        Gridded1DSet    set = (Gridded1DSet)dimToSet.get(dim);
+        if (set == null)
+        {
+            Variable    coordVar = getCoordinateVariable(dim);
+            if (coordVar == null)
+            {
+                // TODO: add CoordinateSystem argument
+                set = new Integer1DSet(getRealType(dim), dim.getLength());
+            }
+            else
+            {
+                ArithProg       ap = isLongitude(coordVar)
+                                        ? new visad.data.in.LonArithProg()
+                                        : new visad.data.in.ArithProg();
+                Class           varType = coordVar.getComponentType();
+                boolean         isDouble = varType.equals(double.class);
+                Object          coordValues;
+                if (isDouble)
+                {
+                    coordValues = coordVar.toArray();
+                    ap.accumulate((double[])coordValues);
+                }
+                else if (varType.equals(float.class))
+                {
+                    coordValues = coordVar.toArray();
+                    ap.accumulate((float[])coordValues);
+                }
+                else
+                {
+                    int     length = 1;
+                    {
+                        int[] lengths = coordVar.getLengths();
+                        for (int i = 0; i < lengths.length; i++)
+                            length *= lengths[i];
+                    }
+                    float[] floatVals = new float[length];
+                    if (varType.equals(int.class))
+                    {
+                        int[] values = (int[])coordVar.toArray();
+                        for (int i = 0; i < values.length; i++)
+                            floatVals[i] = values[i];
+                    }
+                    else if (varType.equals(short.class))
+                    {
+                        short[] values = (short[])coordVar.toArray();
+                        for (int i = 0; i < values.length; i++)
+                            floatVals[i] = values[i];
+                    }
+                    else
+                    {
+                        byte[] values = (byte[])coordVar.toArray();
+                        for (int i = 0; i < values.length; i++)
+                            floatVals[i] = values[i];
+                    }
+                    ap.accumulate(floatVals);
+                    coordValues = floatVals;
+                }
+                if (ap.isConsistent())
+                {
+                    /*
+                     * The coordinate-variable is an arithmetic progression.
+                     */
+                    // TODO: add CoordinateSystem argument
+                    set = new Linear1DSet(
+                            getRealType(dim),
+                            ap.getFirst(),
+                            ap.getLast(),
+                            (int)ap.getNumber(),
+                            (CoordinateSystem)null,
+                            new Unit[] {getUnitFromAttribute(coordVar)},
+                            (ErrorEstimate[])null);
+                }
+                else
+                {
+                    /*
+                     * The coordinate-variable is not an arithmetic progression.
+                     */
+                    // TODO: add CoordinateSystem argument
+                    set =
+                        isDouble
+                            ? (Gridded1DSet)new Gridded1DDoubleSet(
+                                getRealType(dim),
+                                new double[][] {(double[])coordValues},
+                                dim.getLength(),
+                                (CoordinateSystem)null,
+                                new Unit[] {getUnitFromAttribute(coordVar)},
+                                (ErrorEstimate[])null)
+                            : new Gridded1DSet(
+                                getRealType(dim),
+                                new float[][] {(float[])coordValues},
+                                dim.getLength(),
+                                (CoordinateSystem)null,
+                                new Unit[] {getUnitFromAttribute(coordVar)},
+                                (ErrorEstimate[])null);
+                }
+            }
+            dimToSet.put(dim, set);
+        }
+        return set;
+    }
+
+    /**
+     * <p>Returns the netCDF coordinate variable associated with a netCDF 
+     * dimension.  If no such variable exists, then <code>null</code> is
+     * returned.</p>
+     *
+     * <p>This implementation uses {@link #isNumeric(Variable)}.</p>
+     *
+     * @param dim               A netCDF dimension.
+     * @return                  The netCDF coordinate variable associated
+     *                          with the dimension or <code>null</code>
+     *                          if there is no coordinate variable.
+     */
+    protected Variable getCoordinateVariable(Dimension dim)
+    {
+        Variable        var = netcdf.get(dim.getName());
+        if (var != null && !(var.getRank() == 1 && isNumeric(var)))
+            var = null;
+        return var;
+    }
+
+    /** <p> Defines the names of domain components to factor out. 
+    * This only works if this names correspond to the outermost
+    * dimension.  The list of names may be changed after calling
+    * this.</p>
+    *
+    * <p>The Set should contain only String(s).</p>
+    *
+    * <p>Typically, a TreeSet will be used. For example:</p>
+    * <code>TreeSet ts = new TreeSet();</code>
+    * <code>ts.add("myParameter");</code>
+    * <code>view.setOuterDimensionNameSet(ts);</code>
+    *
+    * @param nameSet    A Set containing the names (as Strings) of
+    * the dimensions to factor out.
+    *
+    */
+    public void setOuterDimensionNameSet(java.util.Set nameSet) {
+      factorNameSet = nameSet;
+    }
+
+    /** 
+    * <p> Returns the factorName object
+    *
+    * @return     The Set of factorNames.
+    */
+
+    public java.util.Set getOuterDimensionNameSet() { 
+      return factorNameSet;
+    }
+
+    /**
+     * <p>Indicates if a netCDF dimension represents time.</p>
+     *
+     * <p>This implementation supports coordinate variables and uses {@link
+     * #getRealType(Dimension)}.</p>
+     *
+     * <p>If setOuterDimensionNameSet() has been called, this list
+     * of names will also logically be considered factorable.</p>
+     *
+     * @param dim               A netCDF dimension.
+     * @return                  <code>true</code> if and only if the dimension
+     *                          represents time.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     * @throws IOException      I/O failure.
+     */
+    protected boolean isTime(Dimension dim)
+        throws VisADException, IOException
+    {
+        RealType rt = getRealType(dim);
+        if (factorNameSet != null) {
+          if (factorNameSet.contains(rt.getName()) ) {
+             return true;
+          }
+        }
+        
+        return (isTime(rt.getDefaultUnit()) );
+    }
+
+    /**
+     * Indicates if a unit is a unit of time.
+     *
+     * @param unit              A unit.
+     * @return                  <code>true</code> if and only if the unit
+     *                          is a unit of time.
+     */
+    protected boolean isTime(Unit unit)
+    {
+        return unit != null && SI.second.isConvertible(unit.getAbsoluteUnit());
+    }
+
+    /**
+     * Returns the netCDF dimensions of a netCDF variable.
+     *
+     * @param var               A netCDF variable.
+     * @return                  The dimensions of <code>var</code> in
+     *                          netCDF order.
+     */
+    protected Dimension[] getDimensions(Variable var)
+    {
+        int                     rank = var.getRank();
+        Dimension[]             dims = new Dimension[rank];
+        DimensionIterator       iter = var.getDimensionIterator();
+        for (int i = 0; i < rank; ++i)
+            dims[i] = iter.next();
+        return dims;
+    }
+
+    /**
+     * <p>Indicates if a netCDF variable is a coordinate variable (i.e. has only
+     * one netCDF dimension and that dimension has the same name).</p>
+     *
+     * <p>This implementation uses {@link #isNumeric(Variable)}.</p>
+     *
+     * @param var               A netCDF variable.
+     * @return                  <code>true</code> if and only if <code>
+     *                          var</code> is a coordinate variable.
+     */
+    protected boolean isCoordinateVariable(Variable var)
+    {
+        if (var.getRank() != 1 || !isNumeric(var))
+            return false;
+        return getDimensions(var)[0].getName().equals(var.getName());
+    }
+
+    /**
+     * Returns an iterator over the virtual VisAD data objects of this view.
+     *
+     * @return                  An iterator over the virtual VisAD data objects
+     *                          of the view.
+     */
+    public VirtualDataIterator getVirtualDataIterator()
+    {
+        return new DataIterator();
+    }
+
+    /**
+     * <p>Indicates if a given variable should be ignored by the {@link
+     * VirtualDataIterator} during iteration over the virtual VisAD data objects
+     * in the netCDF dataset.</p>
+     *
+     * @return                    <code>true</code> if and only if the variable
+     *                            should be ignored.
+     */
+    protected abstract boolean isIgnorable(Variable var);
+
+    /**
+     * Returns the domain of a netCDF variable.
+     *
+     * @param var               A netCDF variable.
+     * @return                  The domain of the netCDF variable.
+     * @throws IllegalArgumentException
+     *                          if the rank of the variable is zero.
+     * @throws TypeException    if a {@link RealType} needed to be created but
+     *                          couldn't.
+     * @throws IOException      if a netCDF read-error occurs.
+     */
+    protected abstract Domain getDomain(Variable var)
+        throws TypeException, IOException;
+
+    /**
+     * <p>Returns the virtual VisAD data object corresponding to a named netCDF
+     * variable.</p>
+     *
+     * <p>This implementation uses {@link #getData(Variable)}.</p>
+     *
+     * @param name                  The name of the netCDF variable.
+     * @return                      The corresponding virtual VisAD data object.
+     * @throws NullPointerException if the argument is <code>null</code>.
+     * @throws IllegalArgumentException
+     *                              if the netCDF variable doesn't exist.
+     * @throws TypeException        if a {@link RealType} needed to be created 
+     *                              but couldn't.
+     * @throws VisADException       if a VisAD object couldn't be created.
+     * @throws IOException          if a netCDF read-error occurs.
+     */
+    public VirtualData getData(String name)
+        throws TypeException, VisADException, IOException
+    {
+        if (name == null)
+            throw new NullPointerException();
+        return getData(netcdf.get(name));
+    }
+
+    /**
+     * <p>Returns the virtual VisAD data object corresponding to a netCDF
+     * variable.</p>
+     *
+     * <p>This implementation uses {@link #getRealType(Variable)}, {@link
+     * #getRangeSet(Variable)}, {@link #getUnitFromAttribute(Variable)}, {@link
+     * #getVetter(Variable)}, and {@link #getDomain(Variable)}.<p>
+     *
+     * @param var                   The netCDF variable.
+     * @return                      The corresponding virtual VisAD data object.
+     * @throws NullPointerException if the argument is <code>null</code>.
+     * @throws TypeException        if a {@link RealType} needed to be created 
+     *                              but couldn't.
+     * @throws VisADException       if a VisAD object couldn't be created.
+     * @throws IOException          if a netCDF read-error occurs.
+     */
+    protected VirtualData getData(Variable var)
+        throws TypeException, VisADException, IOException
+    {
+        VirtualScalar   scalar =
+            (isNumeric(var) == true)
+
+                ? (VirtualScalar) 
+                    new VirtualReal(getRealType(var),
+                                    var,
+                                    getRangeSet(var),
+                                    getUnitFromAttribute(var),
+                                    getVetter(var))
+
+                : (VirtualScalar)
+                    new VirtualText(getTextType(var), var);
+
+        return
+            (var.getRank() == 0 || (!isNumeric(var) && var.getRank() == 1))
+                ? (VirtualData)scalar
+                : getDomain(var).getVirtualField(
+                    new VirtualTuple(scalar));
+    }
+
+    /**
+     * Iterates over the virtual VisAD data objects of this view.
+     */
+    protected class DataIterator
+        extends VirtualDataIterator
+    {
+        /**
+         * The netCDF variable iterator.
+         */
+        private final VariableIterator  varIter;
+
+        /**
+         * Constructs from nothing.
+         */
+        DataIterator()
+        {
+            super(View.this);
+            varIter = View.this.getNetcdf().iterator();
+        }
+
+        /**
+         * <p>Returns a copy of the next virtual VisAD data object.</p>
+         *
+         * <p>This implementation uses {@link #isCharToText()},
+         * {@link #isNumeric(Variable)}, {@link #isIgnorable(Variable)}, 
+         * and {@link #getData(Variable)}.</p>
+         *
+         * @return                      A copy of the next virtual VisAD data
+         *                              object or <code> null</code> if there is
+         *                              no more data.
+         * @throws TypeException        if a {@link ScalarType} needed
+         *                              to be created but couldn't.
+         * @throws VisADException       Couldn't create necessary VisAD object.
+         * @throws IOException          if a netCDF read-error occurs.
+         */
+        protected VirtualData getData()
+            throws TypeException, VisADException, IOException
+        {
+            while (varIter.hasNext())
+            {
+                Variable        var = varIter.next();
+                // handle text only if charToText == true and rank <= 2
+                if (!isNumeric(var) && (!isCharToText() || var.getRank() > 2))
+                    continue;  // TODO: support arrays of text (Tuple?)
+                if (isIgnorable(var))
+                    continue;  // ignore what's ignorable
+                return View.this.getData(var);
+            }
+            return null;        // no more data
+        }
+    }
+
+    /**
+     * The convention-dependent domain of a netCDF variable.
+     */
+    protected abstract class Domain
+    {
+        /**
+         * Constructs from a netCDF variable.
+         *
+         * @throws NullPointerException     if the argument is
+         *                                  <code>null</code>.
+         * @throws IllegalArgumentException if the rank of the variable is 0.
+         */
+        protected Domain(Variable var)
+        {
+            if (var.getRank() == 0)
+                throw new IllegalArgumentException(var.toString());
+        }
+
+        /**
+         * Returns a {@link VirtualField} corresponding to this domain and
+         * a given range.
+         *
+         * @param range                 The range for the {@link VirtualField}.
+         * @throws NullPointerException if the argument is <code>null</code>.
+         * @throws IOException          if a netCDF read-error occurs.
+         * @throws VisADException       if a VisAD object can't be created.
+         */
+        protected abstract VirtualField getVirtualField(VirtualTuple range)
+            throws VisADException, IOException;
+
+        /**
+         * Indicates if this instance equals an object.
+         *
+         * @param obj                The object to be compared against.
+         * @return                   <code>true</code> if and only if this
+         *                           instance equals the object.
+         */
+        public abstract boolean equals(Object obj);
+
+        /**
+         * Returns the hash code of this instance.
+         *
+         * @return                   The hash code of this instance.
+         */
+        public abstract int hashCode();
+    }
+}
diff --git a/visad/data/netcdf/in/VirtualData.java b/visad/data/netcdf/in/VirtualData.java
new file mode 100644
index 0000000..1c5fb52
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualData.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualData.java,v 1.3 2000-06-08 19:13:45 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.*;
+
+
+/**
+ * Provides support for a virtual VisAD data object.
+ */
+public abstract class
+VirtualData
+{
+    /**
+     * Gets the VisAD MathType of this virtual, data object.
+     *
+     * @return			The VisAD MathType of this virtual, data object.
+     */
+    public abstract MathType
+    getType()
+	throws VisADException;
+
+
+    /**
+     * Gets the VisAD data object corresponding to this top-level, virtual,
+     * data object.
+     *
+     * @return			The VisAD data object corresponding to this
+     *				top-level, virtual, data object.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     * throws RemoteException	Remote access failure.
+     * throws IOException	I/O failure.
+     */
+    public DataImpl
+    getData()
+	throws VisADException, RemoteException, IOException
+    {
+	return getData(new Context());
+    }
+
+
+    /**
+     * Gets the string that represents this object.
+     *
+     * @return			The string that represents this object.
+     */
+    public String
+    toString()
+    {
+	String	string;
+
+	try
+	{
+	    string = getType().toString();
+	}
+	catch (VisADException e)
+	{
+	    string = "VisADException: " + e.getMessage();
+	}
+
+	return string;
+    }
+
+
+    /**
+     * Gets the VisAD data object corresponding to this virtual, data
+     * object, in context.
+     *
+     * @param context		The context in which the data is to be
+     *				gotten.
+     * @return			The VisAD data object corresponding to this
+     *				virtual, data object.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     * throws RemoteException	Remote access failure.
+     * throws IOException	I/O failure.
+     */
+    public abstract DataImpl
+    getData(Context context)
+	throws VisADException, RemoteException, IOException;
+
+
+    /**
+     * Sets the factory used to create VisAD data objects.
+     *
+     * @param factory		The factory for creating VisAD data objects.
+     */
+    public abstract void setDataFactory(DataFactory factory);
+
+
+    /**
+     * Returns the factory used to create VisAD data objects.
+     *
+     * @return			The factory for creating VisAD data objects.
+     */
+    public abstract DataFactory getDataFactory();
+
+
+    /**
+     * Clones this instance.
+     *
+     * @return			A (deep) clone of this instance.
+     */
+    public abstract Object clone();
+}
diff --git a/visad/data/netcdf/in/VirtualDataIterator.java b/visad/data/netcdf/in/VirtualDataIterator.java
new file mode 100644
index 0000000..2430d47
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualDataIterator.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualDataIterator.java,v 1.4 2001-11-27 22:29:36 dglo Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.util.NoSuchElementException;
+import visad.VisADException;
+import visad.data.BadFormException;
+
+
+/**
+ * Supports iteration over the virtual VisAD data objects in a
+ * netCDF dataset.
+ */
+public abstract class
+VirtualDataIterator
+{
+    /**
+     * The next virtal data object.
+     */
+    private VirtualData		data;
+
+    /**
+     * The view of the netCDF dataset that's being iterated over.
+     */
+    protected final View	view;
+
+
+    /**
+     * Constructs from a view of a netCDF dataset.
+     *
+     * @param view		A view of a netCDF dataset.
+     */
+    public
+    VirtualDataIterator(View view)
+    {
+	this.view = view;
+    }
+
+
+    /**
+     * Indicates if there's another virtual VisAD data object.
+     *
+     * @return			<code>true</code> <=> <code>hasNext()
+     *				</code> will return the next virtual
+     *				VisAD data object.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     * @throws IOException	I/O failure.
+     */
+    public boolean
+    hasNext()
+	throws VisADException, IOException
+    {
+	if (data == null)
+	    data = getData();
+
+	return data != null;
+    }
+
+
+    /**
+     * Gets the next virtual VisAD data object.  The next object will be either
+     * a VirtualScalar, a VirtualField, or a VirtualFlatField.
+     *
+     * @return			The next virtual VisAD data object.
+     * @throws BadFormException	Non-conforming netCDF dataset.
+     * @throws NoSuchElementException
+     *				No more virtual VisAD data objects.
+     * @throws IOException	I/O failure.
+     */
+    public VirtualData
+    next()
+	throws BadFormException, NoSuchElementException, VisADException,
+	    IOException
+    {
+	VirtualData	next = data == null
+				? getData()
+				: data;
+
+	data = null;
+
+	return next;
+    }
+
+
+    /**
+     * Gets a clone of the next virtual VisAD data object.  This method must be
+     * overridden in subclasses.
+     *
+     * @return			A clone of the next virtual VisAD data object or
+     *				<code>null</code> if there is none.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     */
+    protected abstract VirtualData
+    getData()
+	throws VisADException, IOException;
+
+
+    /**
+     * Gets the view of the netCDF dataset.
+     */
+    public View
+    getNetcdf()
+    {
+	return view;
+    }
+}
diff --git a/visad/data/netcdf/in/VirtualField.java b/visad/data/netcdf/in/VirtualField.java
new file mode 100644
index 0000000..97fbe9f
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualField.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualField.java,v 1.4 2001-01-08 17:12:59 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import visad.*;
+
+
+/**
+ * Provides support for a virtual VisAD Field.
+ */
+public class
+VirtualField
+    extends	VirtualData
+{
+    /**
+     * The VisAD FunctionType of this field.
+     */
+    private final FunctionType	functionType;
+
+    /**
+     * The VisAD domain sampling of this field.
+     */
+    private final SampledSet	domainSet;
+
+    /**
+     * The range tuple of this field.
+     */
+    private final VirtualTuple  rangeTuple;
+
+
+    /**
+     * Constructs from a function type, domain set, and range tuple.
+     *
+     * @param funcType		The VisAD FunctionType of the field.
+     * @param domainSet		The domain sampling set of the field.
+     * @param rangeTuple	The range of the field.
+     */
+    protected
+    VirtualField(FunctionType funcType, SampledSet domainSet,
+	VirtualTuple rangeTuple)
+    {
+	this.functionType = funcType;
+	this.domainSet = domainSet;
+	this.rangeTuple = rangeTuple;
+    }
+
+
+    /**
+     * Factory method for creating a new instance.
+     *
+     * @param domainSet		The domain sampling set of the field.
+     * @param rangeTuple	The range of the field.
+     * @return			The corresponding VirtualField.
+     * @throws VisADException	Couldn't created necessary VisAD object.
+     */
+    public static VirtualField
+    newVirtualField(SampledSet domainSet, VirtualTuple rangeTuple)
+	throws VisADException
+    {
+	return
+	    newVirtualField(
+		new FunctionType(
+		    ((SetType)domainSet.getType()).getDomain(),
+		    rangeTuple.getType()),
+		domainSet,
+		rangeTuple);
+    }
+
+
+    /**
+     * Factory method for creating a new instance.
+     *
+     * @param funcType		The VisAD FunctionType of the field.
+     * @param domainSet		The domain sampling set of the field.
+     * @param rangeTuple	The range of the field.
+     * @return			The corresponding VirtualField.
+     */
+     public static VirtualField
+     newVirtualField(FunctionType funcType, SampledSet domainSet,
+	VirtualTuple rangeTuple)
+     {
+	MathType	rangeType = funcType.getRange();
+
+	return (rangeType instanceof RealType ||
+		rangeType instanceof RealTupleType)
+		    ? new VirtualFlatField(funcType, domainSet, rangeTuple)
+		    : new VirtualField(funcType, domainSet, rangeTuple);
+     }
+
+
+    /**
+     * Gets the FunctionType of this virtual Field.
+     *
+     * @return			The FunctionType of this virtual Field.
+     */
+    public FunctionType
+    getFunctionType()
+    {
+	return functionType;
+    }
+
+
+    /**
+     * Gets the MathType of this virtual Field.
+     *
+     * @return			The FunctionType of this virtual Field.
+     */
+    public MathType
+    getType()
+    {
+	return getFunctionType();
+    }
+
+
+    /**
+     * Gets the domain sampling set of this virtual field.
+     *
+     * @return			The domain sampling set of this field.
+     */
+    public SampledSet
+    getDomainSet()
+    {
+	return domainSet;
+    }
+
+
+    /**
+     * Gets the range tuple of this virtual field.
+     *
+     * @return			The range tuple of this virtual field.
+     */
+    public VirtualTuple
+    getRangeTuple()
+    {
+	return rangeTuple;
+    }
+
+
+    /**
+     * Gets the VisAD data object corresponding to this virtual, data
+     * object.
+     *
+     * @param context		The context in which the data is to be
+     *				retrieved.
+     * @return			The VisAD Field corresponding to this
+     *				virtual Field.
+     * @throws VisADException	Couldn't created necessary VisAD object.
+     * @throws RemoteException	Remote access failure.
+     * @throws IOException	I/O failure.
+     */
+    public DataImpl
+    getData(Context context)
+	throws VisADException, RemoteException, IOException
+    {
+	return getDataFactory().newData(context, this);
+    }
+
+
+    /**
+     * Clones this instance.
+     *
+     * @return			A (deep) clone of this instance.
+     */
+    public Object clone()
+    {
+	return
+	    new VirtualField(
+		functionType, domainSet, (VirtualTuple)rangeTuple.clone());
+    }
+
+
+    /**
+     * Sets the factory used to create VisAD data objects.
+     *
+     * @param factory		The factory for creating VisAD data objects.
+     */
+    public void setDataFactory(DataFactory factory)
+    {
+	rangeTuple.setDataFactory(factory);
+    }
+
+
+    /**
+     * Returns the factory used to create VisAD data objects.
+     *
+     * @return factory		The factory for creating VisAD data objects.
+     */
+    public DataFactory getDataFactory()
+    {
+	return rangeTuple.getDataFactory();
+    }
+}
diff --git a/visad/data/netcdf/in/VirtualFlatField.java b/visad/data/netcdf/in/VirtualFlatField.java
new file mode 100644
index 0000000..43debae
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualFlatField.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualFlatField.java,v 1.3 2000-06-08 19:13:46 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import visad.*;
+
+
+/**
+ * Provides support for a virtual VisAD FlatField.
+ */
+public class
+VirtualFlatField
+    extends	VirtualField
+{
+    /**
+     * Constructs from a function type, domain set, and range tuple.
+     *
+     * @param functionType	The MathType of the FlatField.
+     * @param domainSet		The domain sampling set of the FlatField.
+     * @param rangeTuple	The range of the FlatField.
+     */
+    protected
+    VirtualFlatField(FunctionType functionType, SampledSet domainSet,
+	VirtualTuple rangeTuple)
+    {
+	super(functionType, domainSet, rangeTuple);
+    }
+
+
+    /**
+     * Gets the VisAD data object corresponding to this virtual, data
+     * object.
+     *
+     * @param context		The context in which the data is to be
+     *				retrieved.
+     * @return			The VisAD data object corresponding to this
+     *				virtual, data object.
+     * @throws VisADException	Couldn't created necessary VisAD object.
+     * @throws InvalidContextException
+     *				Invalid context.
+     * @throws IOException	I/O failure.
+     */
+    public DataImpl
+    getData(Context context)
+	throws VisADException, IOException
+    {
+	return getDataFactory().newData(context, this);
+    }
+
+
+    /**
+     * Clones this instance.
+     *
+     * @return			A (deep) clone of this instance.
+     */
+    public Object clone()
+    {
+	return
+	    new VirtualFlatField(
+		getFunctionType(),
+		getDomainSet(),
+		(VirtualTuple)getRangeTuple().clone());
+    }
+}
diff --git a/visad/data/netcdf/in/VirtualReal.java b/visad/data/netcdf/in/VirtualReal.java
new file mode 100644
index 0000000..3950b8e
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualReal.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualReal.java,v 1.2 2006-02-13 22:30:08 curtis Exp $
+ */
+
+package visad.data.netcdf.in;
+
+
+import java.io.IOException;
+import ucar.netcdf.Variable;
+import visad.*;
+
+
+/**
+ * Provides support for a virtual VisAD Real.
+ */
+public class
+VirtualReal
+    extends     VirtualScalar
+{
+
+    /**
+     * The range set of the scalar.
+     */
+    private SimpleSet           rangeSet;
+
+    /**
+     * The unit of the scalar.
+     */
+    private final Unit          unit;
+
+    /**
+     * The value vetter.
+     */
+    private final Vetter        vetter;
+
+    /**
+     * The shape of the netCDF variable.
+     */
+    private final int[]         lengths;
+
+    /**
+     * Constructs from a scalar type, a 1-D netCDF variable, a range set,
+     * a unit, and a value vetter.
+     *
+     * @param type              The type of the nested scalar.
+     * @param var               The 1-D netCDF variable.
+     * @param rangeSet          The range set of the values.
+     * @param unit              The unit of the values.
+     * @param vetter            The value vetter.
+     */
+    public
+    VirtualReal(RealType type, Variable var, SimpleSet rangeSet,
+        Unit unit, Vetter vetter)
+    {
+        super(type, var);
+        this.rangeSet = rangeSet;
+        this.unit = unit;
+        this.vetter = vetter;
+        lengths = var.getLengths();
+
+    }
+
+    /**
+     * Gets the range set of this scalar.
+     *
+     * @return                  The range set of this scalar.
+     */
+    public SimpleSet
+    getRangeSet()
+    {
+        return rangeSet;
+    }
+
+
+    /**
+     * Gets the unit of the value.
+     *
+     * @return                  The unit of the value.
+     */
+    public Unit
+    getUnit()
+    {
+        return unit;
+    }
+
+
+    /**
+     * Gets the value vetter.
+     *
+     * @return                  The value vetter.
+     */
+    public Vetter
+    getVetter()
+    {
+        return vetter;
+    }
+
+    /**
+     * Gets the Scalar object corresponding to this virtual, data
+     * object.
+     *
+     * @return                  The VisAD Scalar corresponding to this
+     *                          virtual, data object.
+     * @throws InvalidContextException
+     *                          if the indicial context is invalid.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @throws IOException      I/O failure.
+     */
+    protected Scalar getScalar(Context context) 
+        throws VisADException, InvalidContextException, IOException
+    {
+
+        double[]        values = getDoubles(context);
+
+        if (values.length != 1)
+            throw new InvalidContextException(context);
+
+        return (Scalar) new Real( (RealType)getScalarType(), 
+                                  values[0], 
+                                  getUnit());
+    }
+
+    /**
+     * Gets the double values corresponding to this virtual, data
+     * object at a given context.
+     *
+     * @return                  The double values of this virtual, data object.
+     * throws VisADException    Couldn't create necessary VisAD object.
+     * throws IOException       I/O failure.
+     */
+    public double[]
+    getDoubles(Context context)
+        throws IOException, VisADException
+    {
+        int     rank = lengths.length;
+        int[]   ioOrigin = new int[rank];
+        int[]   ioShape = new int[rank];
+        int[]   ioContext = context.getContext();
+
+        System.arraycopy(ioContext, 0, ioOrigin, 0, ioContext.length);
+
+        for (int i = 0; i < ioContext.length; ++i)
+            ioShape[i] = 1;
+
+        int     total = 1;
+
+        for (int i = ioContext.length; i < rank; ++i)
+        {
+            ioOrigin[i] = 0;
+            ioShape[i] = lengths[i];
+            total *= lengths[i];
+        }
+
+        double[]        values = new double[total];
+
+        toArray(getVariable(), values, ioOrigin, ioShape);
+
+        vetter.vet(values);
+
+        return values;
+    }
+
+
+    /**
+     * Determines if this is a VirtualReal or not.
+     *
+     * @return true if this is a VirtualReal
+     */
+    public boolean
+    isReal()
+    {
+        return true;
+    }
+
+    /**
+     * Clones this instance.
+     *
+     * @return                  A (deep) clone of this instance.
+     */
+    public Object clone()
+    {
+        return new VirtualReal((RealType)getScalarType(), getVariable(), 
+                               rangeSet, unit, vetter);
+    }
+}
diff --git a/visad/data/netcdf/in/VirtualScalar.java b/visad/data/netcdf/in/VirtualScalar.java
new file mode 100644
index 0000000..a693d63
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualScalar.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualScalar.java,v 1.7 2002-10-21 20:07:47 donm Exp $
+ */
+
+package visad.data.netcdf.in;
+
+
+import java.lang.reflect.Array;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import ucar.netcdf.Variable;
+import visad.*;
+
+
+/**
+ * Provides support for a virtual VisAD Scalar.
+ */
+public abstract class
+VirtualScalar
+    extends     VirtualData
+{
+    /**
+     * The factory for creating VisAD data objects.
+     */
+    private DataFactory         dataFactory = DataFactory.instance();
+
+    /**
+     * The VisAD MathType of the scalar.
+     */
+    private ScalarType          type;
+
+    /**
+     * The netCDF variable that constitutes the scalar.
+     */
+    private final Variable      var;
+
+    /**
+     * Constructs from a scalar type, a 1-D netCDF variable, a range set,
+     * a unit, and a value vetter.
+     *
+     * @param type              The type of the nested scalar.
+     * @param var               The 1-D netCDF variable.
+     * @param rangeSet          The range set of the values.
+     * @param unit              The unit of the values.
+     * @param vetter            The value vetter.
+     */
+    public
+    VirtualScalar(ScalarType type, Variable var, SimpleSet rangeSet,
+        Unit unit, Vetter vetter)
+    {
+        this(type, var);
+    }
+
+
+    /**
+     * Constructs from a scalar type and a 1-D netCDF variable
+     *
+     * @param type              The type of the nested scalar.
+     * @param var               The 1-D netCDF variable.
+     */
+    public
+    VirtualScalar(ScalarType type, Variable var)
+    {
+        this.type = type;
+        this.var = var;
+    }
+
+    /**
+     * Gets the ScalarType of this scalar.
+     *
+     * @return                  The ScalarType of this scalar.
+     */
+    public ScalarType
+    getScalarType()
+    {
+        return type;
+    }
+
+
+    /**
+     * Gets the MathType of this scalar.
+     *
+     * @return                  The ScalarType of this scalar.
+     */
+    public MathType
+    getType()
+    {
+        return getScalarType();
+    }
+
+
+    /**
+     * Determines if this is a VirtualReal or not.
+     *
+     * @return true if this is a VirtualReal
+     */
+    public boolean
+    isReal()
+    {
+        return false;
+    }
+
+    /**
+     * Gets the range set of this scalar.
+     *
+     * @return                  The range set of this scalar.
+     * @throws RuntimeException  if class doesn't support this.
+     */
+    public SimpleSet
+    getRangeSet()
+    {
+        throw new RuntimeException();
+    }
+
+
+    /**
+     * Gets the unit of the value.
+     *
+     * @return                  The unit of the value.
+     * @throws RuntimeException  if class doesn't support this.
+     */
+    public Unit
+    getUnit()
+    {
+        throw new RuntimeException();
+    }
+
+
+    /**
+     * Gets the netCDF variable.
+     *
+     * @return                  The netCDF variable.
+     */
+    public Variable
+    getVariable()
+    {
+        return var;
+    }
+
+
+    /**
+     * Gets the value vetter.
+     *
+     * @return                  The value vetter.
+     * @throws RuntimeException  if class doesn't support this.
+     */
+    public Vetter
+    getVetter()
+    {
+        throw new RuntimeException();
+    }
+
+
+    /**
+     * Gets the VisAD data object corresponding to this virtual, data
+     * object.
+     *
+     * @return                  The VisAD Scalar corresponding to this
+     *                          virtual, data object.
+     * throws InvalidContextException
+     *                          Invalid context.
+     * @throws InvalidContextException
+     *                          if the indicial context is invalid.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @throws IOException      I/O failure.
+     */
+    public DataImpl getData(Context context) 
+        throws InvalidContextException, VisADException, RemoteException, IOException
+    {
+        return getDataFactory().newData(context, this);
+    }
+
+
+    /**
+     * Gets the Scalar object corresponding to this virtual, data
+     * object.
+     *
+     * @return                  The VisAD Scalar corresponding to this
+     *                          virtual, data object.
+     * @throws InvalidContextException
+     *                          if the indicial context is invalid.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @throws IOException      I/O failure.
+     */
+    protected abstract Scalar getScalar(Context context)
+        throws VisADException, InvalidContextException, IOException;
+
+    /**
+     * Gets the double values corresponding to this virtual, data
+     * object at a given context.
+     *
+     * @return                  The double values of this virtual, data object.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     * @throws IOException      I/O failure.
+     * @throws RuntimeException  if class doesn't support this.
+     */
+    public double[]
+    getDoubles(Context context)
+        throws IOException, VisADException
+    {
+        throw new RuntimeException();
+    }
+
+    /**
+     * Gets data values of a netCDF variable and performs type conversion.
+     *
+     * @param var               A netCDF variable.
+     * @param values            The destination array for the data values.
+     *                          <code>values.length</code> must be >=
+     *                          the number of points represented by
+     *                          <code>shape</code>.
+     * @param origin            The origin vector for the values.
+     * @param shape             The shape of the I/O transfer.
+     * @return                  <code>values</code>.
+     * @throws IOException      I/O failure.
+     * @see ucar.netcdf.Variable#toArray(Object, int[], int[])
+     */
+    static Object
+    toArray(Variable var, double[] values, int[] origin, int[] shape)
+        throws IOException
+    {
+        // TODO: support text
+
+        if (var.getRank() == 0)
+        {
+            values[0] = var.getDouble(new int[] {});
+        }
+        else
+        {
+            Class       fromClass = var.getComponentType();
+
+            if (fromClass.equals(double.class))
+            {
+                var.toArray(values, origin, shape);
+            }
+            else
+            {
+                int     length = 1;
+
+                for (int i = 0; i < shape.length; ++i)
+                    length *= shape[i];
+
+                Object  dst = Array.newInstance(fromClass, length);
+
+                var.toArray(dst, origin, shape);
+
+                if (fromClass.equals(byte.class))
+                {
+                    byte[]      fromArray = (byte[])dst;
+
+                    for (int i = 0; i < fromArray.length; ++i)
+                        values[i] = fromArray[i];
+                }
+                else if (fromClass.equals(short.class))
+                {
+                    short[]     fromArray = (short[])dst;
+
+                    for (int i = 0; i < fromArray.length; ++i)
+                        values[i] = fromArray[i];
+                }
+                else if (fromClass.equals(int.class))
+                {
+                    int[]       fromArray = (int[])dst;
+
+                    for (int i = 0; i < fromArray.length; ++i)
+                        values[i] = fromArray[i];
+                }
+                else if (fromClass.equals(float.class))
+                {
+                    float[]     fromArray = (float[])dst;
+
+                    for (int i = 0; i < fromArray.length; ++i)
+                        values[i] = fromArray[i];
+                }
+            }
+        }
+
+        return values;
+    }
+
+    /**
+     * Sets the factory used to create VisAD data objects.
+     *
+     * @param factory           The factory for creating VisAD data objects.
+     */
+    public void setDataFactory(DataFactory factory)
+    {
+        dataFactory = factory;
+    }
+
+
+    /**
+     * Returns the factory used to create VisAD data objects.
+     *
+     * @return factory           The factory for creating VisAD data objects.
+     */
+    public DataFactory getDataFactory()
+    {
+        return dataFactory;
+    }
+}
diff --git a/visad/data/netcdf/in/VirtualText.java b/visad/data/netcdf/in/VirtualText.java
new file mode 100644
index 0000000..7211b2d
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualText.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualText.java,v 1.2 2006-02-13 22:30:08 curtis Exp $
+ */
+
+package visad.data.netcdf.in;
+
+
+import java.io.IOException;
+import ucar.netcdf.Variable;
+import ucar.multiarray.StringCharAdapter;
+import visad.*;
+
+
+/**
+ * Provides support for a virtual VisAD Scalar.
+ */
+public class
+VirtualText
+    extends     VirtualScalar
+{
+
+    StringCharAdapter stringVar = null;
+
+    /**
+     * Constructs from a scalar type and a 2-D char netCDF variable
+     *
+     * @param type              The type of the nested scalar.
+     * @param var               The 1-D netCDF variable.
+     */
+    public
+    VirtualText(ScalarType type, Variable var)
+    {
+        super(type, var);
+        stringVar = new StringCharAdapter(var, ' ');
+    }
+
+    /**
+     * Gets the Scalar object corresponding to this virtual, data
+     * object.
+     *
+     * @return                  The VisAD Scalar corresponding to this
+     *                          virtual, data object.
+     * @throws InvalidContextException
+     *                          if the indicial context is invalid.
+     * @throws VisADException   Couldn't create necessary VisAD object.
+     * @throws RemoteException  if a Java RMI failure occurs.
+     * @throws IOException      I/O failure.
+     */
+    protected Scalar getScalar(Context context) 
+        throws VisADException, InvalidContextException, IOException
+    {
+        String[]        values = getStrings(context);
+
+        if (values.length != 1) {
+            System.out.println(getScalarType());
+            throw new InvalidContextException(context);
+        }
+
+        return (Scalar) new Text( (TextType)getScalarType(), values[0].trim());
+        
+    }
+
+    /**
+     * Gets the String value corresponding to this virtual, data
+     * object at a given context.
+     *
+     * @return                  The String value of this virtual, data object.
+     * throws VisADException    Couldn't create necessary VisAD object.
+     * throws IOException       I/O failure.
+     */
+    private String[]
+    getStrings(Context context)
+        throws IOException, VisADException
+    {
+        int[]   lengths = stringVar.getLengths();
+        int     rank = lengths.length;
+        int[]   ioOrigin = new int[rank];
+        int[]   ioShape = new int[rank];
+        int[]   ioContext = context.getContext();
+
+        System.arraycopy(ioContext, 0, ioOrigin, 0, ioContext.length);
+
+        for (int i = 0; i < ioContext.length; ++i)
+            ioShape[i] = 1;
+
+        int     total = 1;
+
+        for (int i = ioContext.length; i < rank; ++i)
+        {
+            ioOrigin[i] = 0;
+            ioShape[i] = lengths[i];
+            total *= lengths[i];
+        }
+
+        String[] values = new String[total];
+
+        return (String[]) stringVar.toArray(values, ioOrigin, ioShape);
+    }
+
+    /**
+     * Clones this instance.
+     *
+     * @return                  A (deep) clone of this instance.
+     */
+    public Object clone()
+    {
+        return new VirtualText((TextType) getScalarType(), getVariable());
+    }
+
+}
diff --git a/visad/data/netcdf/in/VirtualTuple.java b/visad/data/netcdf/in/VirtualTuple.java
new file mode 100644
index 0000000..64f1f12
--- /dev/null
+++ b/visad/data/netcdf/in/VirtualTuple.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VirtualTuple.java,v 1.4 2001-01-08 17:13:16 steve Exp $
+ */
+
+package visad.data.netcdf.in;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.util.Vector;
+import visad.*;
+
+
+/**
+ * Provides support for a virtual VisAD Tuple.
+ *
+ * Instances are mutable.
+ */
+public class
+VirtualTuple
+    extends	VirtualData
+{
+    /**
+     * The factory for creating VisAD data objects.
+     */
+    private DataFactory		dataFactory = DataFactory.instance();
+
+    /**
+     * The components that constitute this virtual tuple.
+     */
+    private final Vector	components;
+
+    /**
+     * The VisAD MathType of the merged data items.
+     */
+    private MathType		mathType = null;
+
+    /**
+     * Whether or not the mathType needs to be recomputed.
+     */
+    private boolean		isDirty = true;
+
+
+    /**
+     * Constructs from nothing.
+     */
+    public
+    VirtualTuple()
+    {
+	this(0);
+    }
+
+
+    /**
+     * Constructs from an estimate of the number of elements to contain.
+     */
+    private
+    VirtualTuple(int n)
+    {
+	components = new Vector(n);
+    }
+
+
+    /**
+     * Constructs from a virtual data object.
+     *
+     * @param data		A virtual data object.
+     */
+    public
+    VirtualTuple(VirtualData data)
+    {
+	this(1);
+	add(data);
+    }
+
+
+    /**
+     * Constructs from a 1-D array of virtual data objects.  Order is preserved.
+     *
+     * @param datas		A 1-D array of virtual data objects.
+     */
+    public
+    VirtualTuple(VirtualData[] datas)
+    {
+	this(datas.length);
+	for (int i = 0; i < datas.length; ++i)
+	    add(datas[i]);
+    }
+
+
+    /**
+     * Returns the number of components in this tuple.
+     *
+     * @return			The number of components in this tuple.
+     */
+    public int
+    size()
+    {
+	return components.size();
+    }
+
+
+    /**
+     * Adds a component to this tuple.
+     *
+     * @param data		The component to be added.
+     */
+    public synchronized void
+    add(VirtualData data)
+    {
+	components.add(data);
+	isDirty = true;
+    }
+
+
+    /**
+     * Gets the VisAD MathType of this virtual tuple.
+     *
+     * @return			The VisAD MathType of the merged data
+     *				items or <code>null</code> if no data items.
+     * @throws VisADException	VisAD failure.
+     */
+    public MathType
+    getType()
+	throws VisADException
+    {
+	if (isDirty)
+	{
+	    int		componentCount = size();
+
+	    if (componentCount == 0)
+	    {
+		mathType = null;
+	    }
+	    else if (componentCount == 1)
+	    {
+		mathType = ((VirtualData)components.get(0)).getType();
+	    }
+	    else
+	    {
+		MathType[]	types = new MathType[componentCount];
+		boolean		allRealTypes = true;
+
+		for (int i = 0; i < componentCount; ++i)
+		{
+		    types[i] = ((VirtualData)components.get(i)).getType();
+			if (!(types[i] instanceof RealType))
+			    allRealTypes = false;
+		}
+
+		if (!allRealTypes)
+		{
+		    mathType = new TupleType(types);
+		}
+		else
+		{
+		    RealType[]	realTypes = new RealType[componentCount];
+
+		    for (int i = 0; i < componentCount; ++i)
+			realTypes[i] = (RealType)types[i];
+
+		    mathType = new RealTupleType(realTypes);
+		}
+	    }
+
+	    isDirty = false;
+	}
+
+	return mathType;
+    }
+
+
+    /**
+     * Gets a component of this tuple.
+     *
+     * @param index		The index of the component to get.
+     * @return			The <code>index</code>-th component.
+     * @throws java.lang.ArrayIndexOutOfBoundsException
+     */
+    public VirtualData
+    get(int index)
+	throws ArrayIndexOutOfBoundsException
+    {
+	return (VirtualData)components.get(index);
+    }
+
+
+    /**
+     * Replaces a component of this tuple.
+     *
+     * @param index		The index of the component to replace.
+     * @param data		The new component.
+     * @throws java.lang.ArrayIndexOutOfBoundsException
+     */
+    public synchronized void
+    replace(int index, VirtualData data)
+	throws ArrayIndexOutOfBoundsException
+    {
+	components.set(index, data);
+	isDirty = true;
+    }
+
+
+    /**
+     * Gets the VisAD data object of this tuple, in context.
+     *
+     * @param context		The context for retrieving the data object.
+     * @return			The VisAD data object or <code>null</code>
+     *				if there is no data.
+     * @throws VisADException	Couldn't create necessary VisAD object.
+     * @throws RemoteException	Remote access failure.
+     * @throws IOException	I/0 failure.
+     */
+    public DataImpl
+    getData(Context context)
+	throws VisADException, RemoteException, IOException
+    {
+	return getDataFactory().newData(context, this);
+    }
+
+
+    /**
+     * Clears this instance.
+     */
+    public synchronized void clear()
+    {
+	components.clear();
+	mathType = null;
+	isDirty = true;
+    }
+
+
+    /**
+     * Clones this instance.
+     *
+     * @return			A (deep) clone of this instance.
+     */
+    public synchronized Object clone()
+    {
+	int		n = size();
+	VirtualTuple	clone = new VirtualTuple(n);
+
+	for (int i = 0; i < n; ++i)
+	    clone.add((VirtualData)get(i).clone());
+
+	return clone;
+    }
+
+
+    /**
+     * Sets the factory used to create the VisAD data object corresponding to
+     * this tuple and contained elements.
+     *
+     * @param factory		The factory for creating VisAD data objects.
+     */
+    public void setDataFactory(DataFactory factory)
+    {
+	dataFactory = factory;
+    }
+
+
+    /**
+     * Returns the factory used to create VisAD data objects.
+     *
+     * @return factory		The factory for creating VisAD data objects.
+     */
+    public DataFactory getDataFactory()
+    {
+	return dataFactory;
+    }
+}
diff --git a/visad/data/netcdf/in/depend b/visad/data/netcdf/in/depend
new file mode 100644
index 0000000..a8c5618
--- /dev/null
+++ b/visad/data/netcdf/in/depend
@@ -0,0 +1,18 @@
+DefaultConsolidator.class:	VirtualField.class
+DefaultConsolidator.class:	VirtualTuple.class
+DefaultView.class:	Util.class
+DefaultView.class:	Vetter.class
+DefaultView.class:	VirtualField.class
+DefaultView.class:	VirtualFlatField.class
+DefaultView.class:	VirtualScalar.class
+DefaultView.class:	VirtualTuple.class
+NetcdfAdapter.class:	DefaultConsolidator.class
+NetcdfAdapter.class:	DefaultView.class
+Util.class:	ArithProg.class
+Util.class:	LonArithProg.class
+Util.class:	NetcdfQuantityDB.class
+Util.class:	Vetter.class
+VirtualData.class:	Context.class
+VirtualField.class:	VirtualFlatField.class
+VirtualScalar.class:	InvalidContextException.class
+VirtualScalar.class:	Util.class
diff --git a/visad/data/netcdf/in/package.html b/visad/data/netcdf/in/package.html
new file mode 100644
index 0000000..a08036a
--- /dev/null
+++ b/visad/data/netcdf/in/package.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html	1.60 98/01/27
+
+  Copyright 1998 Sun Microsystems, Inc. 901 San Antonio Road, 
+  Palo Alto, California, 94303, U.S.A.  All Rights Reserved.
+
+  This software is the confidential and proprietary information of Sun
+  Microsystems, Inc. ("Confidential Information").  You shall not
+  disclose such Confidential Information and shall use it only in
+  accordance with the terms of the license agreement you entered into
+  with Sun.
+
+  CopyrightVersion 1.2
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides for importing a netCDF dataset into VisAD.
+
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/data/netcdf/index.html b/visad/data/netcdf/index.html
new file mode 100644
index 0000000..4685375
--- /dev/null
+++ b/visad/data/netcdf/index.html
@@ -0,0 +1,32 @@
+<HTML>
+<HEAD>
+   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
+   <META NAME="GENERATOR" CONTENT="Mozilla/4.05 [en] (X11; U; SunOS 5.6 sun4u) [Netscape]">
+   <TITLE>VisAD netCDF Subsystem</TITLE>
+<HTMLPLUS>
+</HEAD>
+<BODY>
+
+<CENTER>
+<H1>
+VisAD netCDF Subsystem</H1></CENTER>
+
+<H2>
+Links</H2>
+
+<UL>
+<LI>
+Package <A HREF="packages.html">visad.data.netcdf</A>.</LI>
+
+<LI>
+Package <A HREF="units/index.html">visad.data.netcdf.units.</A></LI>
+
+<LI>
+Package <A HREF="multiarray-doc/packages.html">ucar.multiarray</A>.</LI>
+
+<LI>
+Package <A HREF="netcdf-doc/packages.html">ucar.netcdf</A>.</LI>
+</UL>
+
+</BODY>
+</HTML>
diff --git a/visad/data/netcdf/out/CoordVar.java b/visad/data/netcdf/out/CoordVar.java
new file mode 100644
index 0000000..e591856
--- /dev/null
+++ b/visad/data/netcdf/out/CoordVar.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: CoordVar.java,v 1.3 2000-04-26 15:45:24 dglo Exp $
+ */
+
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Attribute;
+import ucar.netcdf.Dimension;
+import visad.Gridded1DSet;
+import visad.Unit;
+import visad.data.BadFormException;
+
+
+/**
+ * The CoordVar class handles the exporting of netCDF coordinate variables
+ * from a VisAD data object.
+ */
+class CoordVar extends ExportVar
+{
+    /** The linear, sampling domain set. */
+    private final Gridded1DSet	set;
+
+    /** The unit in which the co-ordinate variable is measured. */
+    private final Unit		unit;
+
+    /**
+     * Construct.
+     *
+     * @param name	The name of the coordinate variable.
+     * @param dim	The netCDF dimension associated with the coordinate
+     *			variable.
+     * @param unit	The coordinate variable unit or <code>null</code>.
+     * @param set	The values of the coordinate variable.
+     * @exception BadFormException	A coordinate variable could not be
+     *					formed from the given information.
+     */
+    CoordVar(String name, Dimension dim, Unit unit, Gridded1DSet set)
+	    throws BadFormException {
+	super(name, Float.TYPE, new Dimension[] {dim}, myAttributes(unit));
+	this.set = set;
+	this.unit = unit;
+    }
+
+    /**
+     * Return my attributes for construction.
+     *
+     * @param unit	The coordinate variable unit to be made into a
+     *			netCDF attribute or <code>null</code>.
+     * @return		The array of netCDF attributes for the coordinate
+     *			variable.
+     */
+    protected static Attribute[] myAttributes(Unit unit) {
+	return unit == null
+		? null
+		: new Attribute[] { new Attribute("units", unit.toString()) };
+    }
+
+    /**
+     * Return an array element identified by position.
+     *
+     * @param indexes	The position of the element in netCDF indexes.
+     * @precondition	<code>indexes.length</code> == 1.
+     * @return		The coordinate value at <code>indexes</code>.
+     */
+     public Object get(int[] indexes) throws IOException {
+	int	index = indexes[indexes.length-1];
+
+	try {
+	    return new Float(set.indexToValue(new int[] {index})[0][0]);
+	} catch (Exception e) {
+	    throw new IOException(e.getMessage());
+	}
+     }
+
+    /**
+     * Indicate whether or not this co-ordinate variable is the same as
+     * another co-ordinate variable.
+     *
+     * @param that	The other coordinate variable.
+     * @return		<code>true</code> if and only if the coordinate
+     *			variables are semantically identical.
+     */
+    public boolean equals(CoordVar that) {
+	return getName().equals(that.getName())
+		&& getRank() == that.getRank()
+		&& getLengths()[0] == that.getLengths()[0]
+		&& (unit == that.unit || unit.equals(that.unit))
+		&& set.equals(that.set);
+    }
+}
diff --git a/visad/data/netcdf/out/DataAccessor.java b/visad/data/netcdf/out/DataAccessor.java
new file mode 100644
index 0000000..b4623d7
--- /dev/null
+++ b/visad/data/netcdf/out/DataAccessor.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: DataAccessor.java,v 1.4 2001-11-27 22:29:38 dglo Exp $
+ */
+
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Dimension;
+
+
+/**
+ * The DataAccessor class provides the top-level abstraction for accessing
+ * the data in a VisAD data object via the netCDF variable API.
+ */
+abstract class DataAccessor implements VisADAccessor
+{
+    /*
+     * The following fields are not private because I decided that they
+     * were used so much by the subclasses that using implementing
+     * access via get...()/set...() accessors would be too inefficient.
+     */
+
+    /** The number of netCDF dimensions in the VisAD data object. */
+    protected final int			localRank;
+
+    /**
+     * The netCDF Dimensions of the VisAD data object in netCDF order
+     * (outermost dimension first).
+     */
+    protected final Dimension[]		localDims;
+
+    /**
+     * The netCDF indexes for the local VisAD data object in netCDF order
+     * (outermost dimension first).
+     */
+    protected volatile int[]		localIndexes;
+
+    /** The VisADAccessor of the outer VisAD data object. */
+    protected final VisADAccessor	outerAccessor;
+
+    /** The number of netCDF dimensions in the outer VisAD data object. */
+    protected final int			outerRank;
+
+    /**
+     * The netCDF indexes for the outer VisAD data object in netCDF order
+     * (outermost dimension first).
+     */
+    protected volatile int[]		outerIndexes;
+
+    /**
+     * Construct from an outer VisADAccessor and netCDF Dimensions.
+     *
+     * @param localDims		The netCDF dimensions of the VisAD data
+     *				object being adapted (in netCDF API order).
+     * @param outerAccessor	The DataAccessor of the enclosing VisAD data
+     *				object (may not be <code>null</code>).
+     */
+    protected DataAccessor(Dimension[] localDims, VisADAccessor outerAccessor)
+    {
+	localRank = localDims.length;
+	outerRank = outerAccessor.getRank();
+	this.localDims = localDims;
+	localIndexes = new int[localRank];
+	outerIndexes = new int[outerRank];
+	this.outerAccessor = outerAccessor;
+    }
+
+    /**
+     * Return the number of netCDF dimensions at the current level.
+     *
+     * @return	The netCDF rank (i.e. the number of netCDF dimensions) of the
+     *		VisAD data object being adapted.  Includes the dimensions of
+     *		all enclosing VisAD data objects.
+     */
+    public int getRank() {
+	return outerRank + localRank;
+    }
+
+    /**
+     * Return the netCDF dimensions at the level of the data object.
+     * Include all dimensions in more outer data objects.
+     *
+     * @return	The array of netCDF Dimensions of the VisAD data object
+     *		being adapted.  Includes the dimensions of all enclosing
+     *		VisAD data objects.
+     * @postcondition	<code>getDimensions().length == getRank()</code>.
+     */
+    public Dimension[] getDimensions() {
+	Dimension[]	dims = new Dimension[getRank()];
+
+	System.arraycopy(outerAccessor.getDimensions(), 0, dims, 0, outerRank);
+	System.arraycopy(localDims, 0, dims, outerRank, localRank);
+
+	return dims;
+    }
+
+    /**
+     * Return the netCDF dimensional lengths.
+     *
+     * @return	The lengths of the netCDF Dimensions of the VisAD data object
+     *		being adapted.  Includes the dimensions of all enclosing
+     *		VisAD data objects.
+     * @postcondition	<code>getLengths().length == getRank()</code>.
+     * @postcondition	<code>getLengths()[i] ==
+     *			getDimensions()[i].getLength()</code>.
+     */
+    public int[] getLengths() {
+	int[]		lengths = new int[getRank()];
+	Dimension[]	outerDims = getDimensions();
+
+	for (int i = 0; i < lengths.length; ++i)
+	    lengths[i] = outerDims[i].getLength();
+
+	return lengths;
+    }
+
+    /**
+     * Return a datum given its location as netCDF indexes.
+     *
+     * @param indexes	The netCDF indexes for the datum.
+     * @precondition	<code>indexes</code> lies within the adapted object.
+     * @return	The VisAD data object (e.g. <code>Tuple</code>) or Java
+     *		primitive (e.g. <code>Double</code>) corresponding to
+     *		the given position.
+     * @exception IOException	The corresponding datum couldn't be accessed.
+     */
+    public Object get(int[] indexes) throws IOException {
+	System.arraycopy(indexes, 0, outerIndexes, 0, outerRank);
+	System.arraycopy(indexes, outerRank, localIndexes, 0, localRank);
+
+	return get();
+    }
+
+    /**
+     * Return a datum given the split, netCDF indexes.
+     *
+     * @precondition	The point given by <code>outerIndexes</code> and
+     *			<code>localIndexes</code> lies within the adapted
+     *			data object.
+     * @return	The VisAD data object (e.g. <code>Tuple</code>) or Java
+     *		primitive (e.g. <code>Double</code>) corresponding to
+     *		<code>outerIndexes</code> and <code>localIndexes</code>.
+     * @exception IOException	The corresponding datum couldn't be accessed.
+     */
+    protected abstract Object get() throws IOException;
+}
diff --git a/visad/data/netcdf/out/DependentRealVar.java b/visad/data/netcdf/out/DependentRealVar.java
new file mode 100644
index 0000000..16582aa
--- /dev/null
+++ b/visad/data/netcdf/out/DependentRealVar.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: DependentRealVar.java,v 1.4 2000-04-26 15:45:24 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Attribute;
+import visad.DoubleSet;
+import visad.FloatSet;
+import visad.Real;
+import visad.RealType;
+import visad.ScalarType;
+import visad.Set;
+import visad.Unit;
+import visad.VisADException;
+import visad.data.BadFormException;
+
+
+/**
+ * The DependentRealVar class adapts numeric data in a VisAD data object to
+ * a netCDF, dependent-variable, API for the purpose of exporting the data.
+ */
+class
+DependentRealVar
+    extends	DependentVar
+{
+    /**
+     * The fill-value object.
+     */
+    private final Number	fillValue;
+
+
+    /**
+     * Construct.
+     *
+     * @param real		The VisAD Real object to be adapted.
+     * @param accessor		The means for accessing the individual VisAD
+     *				<code>Real</code> objects of the enclosing
+     *				VisAD data object.
+     * @exception BadFormException		The VisAD data object cannot be
+     *		adapted to a netCDF API
+     * @exception VisADException		Problem in core VisAD.
+     *		Probably some VisAD object couldn't be created.
+     */
+    protected
+    DependentRealVar(Real real, VisADAccessor accessor)
+	throws VisADException, BadFormException
+    {
+	super(((ScalarType)real.getType()).getName(),
+	    getJavaClass(((RealType)real.getType()).getDefaultSet()),
+	    accessor.getDimensions(),
+	    myAttributes(real),
+	    accessor);
+
+	fillValue = getFillValue(getJavaClass(
+	    ((RealType)real.getType()).getDefaultSet()));
+    }
+
+
+    /**
+     * Get the netCDF attributes for a DependentRealVar.
+     *
+     * @param real	The VisAD data object for which netCDF Attribute
+     *			must be created.
+     * @return		An array of netCDF Attributes for <code>real</code>.
+     * @exception BadFormException		The VisAD data object cannot be
+     *		adapted to a netCDF API
+     * @exception VisADException		Problem in core VisAD.
+     *		Probably some VisAD object couldn't be created.
+     */
+    protected static Attribute[]
+    myAttributes(Real real)
+	throws VisADException, BadFormException
+    {
+	RealType	realType = (RealType)real.getType();
+	Number		fillNumber = getFillValue(getJavaClass(
+	    realType.getDefaultSet()));
+	Unit		unit = real.getUnit();
+	Attribute[]	attrs;
+
+	if (unit == null)
+	    attrs = new Attribute[]
+	    {
+		new Attribute("_FillValue", fillNumber)
+	    };
+	else
+	    attrs = new Attribute[]
+	    {
+		new Attribute("_FillValue", fillNumber),
+		new Attribute("units", unit.toString())
+	    };
+
+	return attrs;
+    }
+
+
+    /**
+     * Get the class of the Java primitive type that can contain the
+     * VisAD Set of a VisAD range value.
+     *
+     * @param set	The VisAD Set describing the range of the variable
+     *			data.
+     * @precondition	The set is that of a range value (i.e. DoubleSet,
+     *			FloatSet, Linear1DSet, etc.).
+     * @return		The Java class corresponding to the variable data
+     *			(i.e. Double, Float, Integer, etc.).
+     * @exception VisADException
+     *			Problem in core VisAD.  Probably some VisAD object
+     *			couldn't be created.
+     */
+    protected static Class
+    getJavaClass(Set set)
+	throws VisADException
+    {
+	if (set == null || set instanceof DoubleSet)
+	    return Double.TYPE;
+	if (set instanceof FloatSet)
+	    return Float.TYPE;
+
+	int	nelts = set.getLength();
+
+	return nelts >= 65536
+		    ? Integer.TYPE
+		    : nelts >= 256
+			? Short.TYPE
+			: Byte.TYPE;
+    }
+
+
+    /**
+     * Return the fill-value object for a numeric netCDF variable of the
+     * given type.
+     *
+     * @param type	netCDF type (e.g. <code>Character.TYPE</code>,
+     *			<code>Float.TYPE</code>).
+     * @return		The default fill-value object for the given netCDF
+     *			type.
+     * @exception BadFormException
+     *			Unknown netCDF type.
+     */
+    protected static Number
+    getFillValue(Class type)
+	throws BadFormException
+    {
+	Number	number;
+
+	if (type.equals(Byte.TYPE))
+	    number = new Byte(Byte.MIN_VALUE);
+	else
+	if (type.equals(Short.TYPE))
+	    number = new Short((short)-32767);
+	else
+	if (type.equals(Integer.TYPE))
+	    number = new Integer(-2147483647);
+	else
+	if (type.equals(Float.TYPE))
+	    number = new Float(9.9692099683868690e+36);
+	else
+	if (type.equals(Double.TYPE))
+	    number = new Double(9.9692099683868690e+36);
+	else
+	    throw new BadFormException("Unknown netCDF type: " + type);
+
+	return number;
+    }
+
+
+    /**
+     * Return a netCDF datum identified by position.
+     *
+     * @param indexes	The netCDF indexes of the desired datum.  Includes all
+     *			adapted dimensions -- including those of all enclosing
+     *			VisAD data objects.
+     * @return		A Java Double that contains the data value or NaN if
+     *			the data is missing.
+     * @exception IOException
+     *			Data access failure.
+     */
+    public Object
+    get(int[] indexes)
+	throws IOException
+    {
+	Double	value = (Double)getAccessor().get(indexes);
+
+	return value.isNaN()
+		    ? fillValue
+		    : value;
+    }
+}
diff --git a/visad/data/netcdf/out/DependentTextVar.java b/visad/data/netcdf/out/DependentTextVar.java
new file mode 100644
index 0000000..8c4fa3b
--- /dev/null
+++ b/visad/data/netcdf/out/DependentTextVar.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: DependentTextVar.java,v 1.3 2000-01-18 18:56:14 steve Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Attribute;
+import visad.ScalarType;
+import visad.Text;
+import visad.data.BadFormException;
+
+
+/**
+ * The DependentTextVar class adapts textual data in a VisAD data object to
+ * a netCDF, dependent-variable, API for the purpose of exporting the data.
+ * in an adapted VisAD data object.
+ */
+class
+DependentTextVar
+    extends	DependentVar
+{
+    /**
+     * The fill-value object.
+     */
+    private final Character	fillValue;
+
+
+    /**
+     * Construct.
+     *
+     * @param text	The VisAD Text object to be adapted.
+     * @param accessor	The means for accessing the individual VisAD
+     *			<code>Text</code> objects of the enclosing
+     *			VisAD data object.
+     * @exception BadFormException
+     *			The VisAD data object cannot be adapted to a netCDF API.
+     */
+    protected
+    DependentTextVar(Text text, VisADAccessor accessor)
+	throws BadFormException
+    {
+	super(((ScalarType)text.getType()).getName(), Character.TYPE,
+	    accessor.getDimensions(), myAttributes(), accessor);
+
+	fillValue = getFillValue();
+    }
+
+
+    /**
+     * Get the netCDF attributes for a DependentTextVar.
+     *
+     * @return	An array of netCDF Attributes for the variable.
+     */
+    protected static Attribute[]
+    myAttributes()
+    {
+	return new Attribute[]
+	    {new Attribute(
+		"_FillValue",
+		new String(new char[] {getFillValue().charValue()}))};
+    }
+
+
+    /**
+     * Return the fill-value.
+     *
+     * @return	The netCDF fill-value for netCDF character variables.
+     */
+    protected static Character
+    getFillValue()
+    {
+	return new Character('\000');
+    }
+
+
+    /**
+     * Return a netCDF datum identified by position.
+     *
+     * @param indexes	The netCDF indexes of the desired datum.  Includes all
+     *			adapted dimensions -- including those of all enclosing
+     *			VisAD data objects.
+     * @return		A Java Character that contains the data value or the
+     *			appropriate netCDF fill-value if the data is missing.
+     */
+    public Object
+    get(int[] indexes)
+	throws IOException
+    {
+	try
+	{
+	    return getAccessor().get(indexes);
+	}
+	catch (StringIndexOutOfBoundsException e)
+	{
+	    return fillValue;
+	}
+    }
+}
diff --git a/visad/data/netcdf/out/DependentVar.java b/visad/data/netcdf/out/DependentVar.java
new file mode 100644
index 0000000..1b7a653
--- /dev/null
+++ b/visad/data/netcdf/out/DependentVar.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: DependentVar.java,v 1.4 2001-11-27 22:29:38 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Attribute;
+import ucar.netcdf.Dimension;
+import visad.data.BadFormException;
+
+
+/**
+ * The DependentVar class provides an abstract class for adapting data in
+ * a VisAD data object to a netCDF, dependent-variable API for the purpose
+ * of exporting the data.
+ */
+abstract class
+DependentVar
+    extends	ExportVar
+{
+    /**
+     * The VisADAccessor.
+     */
+    private final VisADAccessor	accessor;
+
+
+    /**
+     * Construct.
+     *
+     * @param name	The name of the netCDF, dependent variable.
+     * @param type	The Java class of the type of the variable (i.e.
+     *			Double, Byte, Character, etc.).
+     * @param dims	The netCDF dimensions of the variable.
+     * @param attrs	The netCDF attributes of the variable.
+     * @exception BadFormException
+     *			The VisAD data object cannot be adapted to a netCDF API.
+     */
+    protected
+    DependentVar(String name, Class type, Dimension[] dims, Attribute[] attrs,
+	    VisADAccessor accessor)
+	throws BadFormException
+    {
+	super(name, type, dims, attrs);
+
+	this.accessor = accessor;
+    }
+
+
+    /**
+     * Return a netCDF datum identified by position.
+     *
+     * @param indexes	The netCDF indexes of the desired datum.  Includes all
+     *			adapted dimensions -- including those of all enclosing
+     *			VisAD data objects.
+     * @return		An Object that contains the data value or the
+     *			appropriate netCDF fill-value if the data is missing.
+     * @exception IOException
+     *			Data access failure.
+     */
+    public abstract Object
+    get(int[] indexes)
+	throws IOException;
+
+
+    /**
+     * Return the data accessor.
+     *
+     * @return	The data accessor that knows how to get the data from the
+     *		VisAD data object.
+     */
+    protected VisADAccessor
+    getAccessor()
+    {
+	return accessor;
+    }
+}
diff --git a/visad/data/netcdf/out/ExportVar.java b/visad/data/netcdf/out/ExportVar.java
new file mode 100644
index 0000000..421b8e3
--- /dev/null
+++ b/visad/data/netcdf/out/ExportVar.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: ExportVar.java,v 1.4 2001-11-27 22:29:38 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.multiarray.Accessor;
+import ucar.multiarray.MultiArray;
+import ucar.netcdf.Attribute;
+import ucar.netcdf.Dimension;
+import ucar.netcdf.ProtoVariable;
+import visad.data.BadFormException;
+
+
+/*
+ * The ExportVar class provides an abstract class for adapting VisAD data to
+ * a netCDF Variable.
+ */
+abstract class
+ExportVar
+    extends	ProtoVariable
+    implements	Accessor
+{
+    /**
+     * Construct from broken-out information.
+     *
+     * @param name	The name of the netCDF variable.
+     * @param type	The type of the netCDF variable (i.e. Double.TYPE,
+     *			Byte.TYPE, Character.TYPE, etc.).
+     * @param dims	The dimensions of the netCDF variable.
+     * @param attrs	The attributes of the netCDF variable.
+     * @exception BadFormException
+     *			The VisAD data object cannot be adapted to a netCDF API.
+     */
+    protected
+    ExportVar(String name, Class type, Dimension[] dims, Attribute[] attrs)
+	throws BadFormException
+    {
+	super(name, type, dims, attrs);
+    }
+
+
+    /**
+     * Return an array element identified by position.  This is the only
+     * method that needs to be implemented to support the saving of
+     * VisAD data in a netCDF dataset.
+     *
+     * @param indexes		The position of the array element as netCDF
+     *				indexes.
+     * @exception IOException	Data access I/O failure.
+     */
+     public abstract Object
+     get(int[] indexes)
+	throws IOException;
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    copyin(int[] origin, MultiArray multiArray)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    set(int[] index,  Object value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setBoolean(int[] index,  boolean value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setChar(int[] index,  char value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setByte(int[] index,  byte value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setShort(int[] index, short value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setInt(int[] index, int value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setLong(int[] index, long value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setFloat(int[] index, float value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Set an array element identified by position.  Not supported for
+     * read-only, VisAD data objects.
+     */
+    public void
+    setDouble(int[] index, double value)
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public boolean
+     getBoolean(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public char
+     getChar(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public byte
+     getByte(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public short
+     getShort(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public int
+     getInt(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public long
+     getLong(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public float
+     getFloat(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return an array element identified by position.  Not supported.
+     */
+     public double
+     getDouble(int[] indexes)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Return a MultiArray into a slice of the data.  Not supported.
+     */
+    public MultiArray
+    copyout(int[] origin, int[] shape)
+	throws IOException
+    {
+	throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Convert values to an array.  Not supported.
+     */
+    public Object
+    toArray()
+    {
+	throw new UnsupportedOperationException();
+    }
+
+
+    /**
+     * Convert values to an array.  Not supported.
+     */
+    public Object
+    toArray(Object obj, int[] dummy1, int[] dummy2)
+    {
+	throw new UnsupportedOperationException();
+    }
+}
diff --git a/visad/data/netcdf/out/FieldAccessor.java b/visad/data/netcdf/out/FieldAccessor.java
new file mode 100644
index 0000000..f625bba
--- /dev/null
+++ b/visad/data/netcdf/out/FieldAccessor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: FieldAccessor.java,v 1.3 2000-04-26 15:45:25 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Dimension;
+import visad.Field;
+
+
+/**
+ * The FieldAccessor class accesses data in a VisAD Field that's being
+ * adapted to a netCDF API.
+ */
+class
+FieldAccessor
+    extends	DataAccessor
+{
+    /**
+     * The shape of the netCDF variables of the VisAD Field in netCDF order
+     * (i.e. outermost dimension first).
+     */
+    private final int[]	shape;
+
+
+    /**
+     * Construct from netCDF Dimensions, and an outer VisADAccessor.
+     *
+     * @param localDims		The netCDF dimensions of the Field in netCDF.
+     *				order (outermost dimension first).
+     * @param outerAccessor	The DataAccessor for accessing the
+     *				<code>Field</code>s object of the enclosing,
+     *				VisAD data object.
+     */
+    protected
+    FieldAccessor(Dimension[] localDims, VisADAccessor outerAccessor)
+    {
+	super(localDims, outerAccessor);
+
+	shape = new int[localRank];
+	for (int idim = 0; idim < localRank; ++idim)
+	    shape[idim] = localDims[idim].getLength();
+    }
+
+
+    /**
+     * Return a datum given the split, netCDF indexes.
+     *
+     * @return	The Object at the position specified by
+     *		<code>localIndexes</code> and <code>outerIndexes</code>.
+     */
+    protected Object
+    get()
+	throws IOException
+    {
+	int	visadIndex = localIndexes[0];
+
+	for (int i = 1; i < localRank; ++i)
+	    visadIndex = visadIndex * shape[i] + localIndexes[i];
+
+	try
+	{
+	    return ((Field)outerAccessor.get(outerIndexes)).
+		getSample(visadIndex);
+	}
+	catch (Exception e)
+	{
+	    throw new IOException(e.getMessage());
+	}
+    }
+}
diff --git a/visad/data/netcdf/out/IndependentVar.java b/visad/data/netcdf/out/IndependentVar.java
new file mode 100644
index 0000000..6f17582
--- /dev/null
+++ b/visad/data/netcdf/out/IndependentVar.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: IndependentVar.java,v 1.4 2000-04-26 15:45:25 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Attribute;
+import ucar.netcdf.Dimension;
+import visad.SampledSet;
+import visad.Unit;
+import visad.data.BadFormException;
+
+
+/*
+ * The IndependentVar class provides a way to present an independent variable
+ * in a VisAD data object as a netCDF variable.  An independent variable is a
+ * netCDF variable that has been created because it was necessary to define
+ * dependent netCDF variables in terms of an "index" co-ordinate.  This can
+ * happen, for example, in a Fleld with a domain that's a SampledSet
+ * but not a LinearSet.
+ */
+class
+IndependentVar
+    extends ExportVar
+{
+    /**
+     * The dimension index of the "variable" in the domain.
+     */
+    private final int		idim;
+
+
+    /**
+     * The sampling domain set.
+     */
+    private final SampledSet	set;
+
+
+    /**
+     * Construct from broken-out information.
+     *
+     * @param name	The name for the netCDF variable.
+     * @param dim	The netCDF dimension for the independent variable.
+     * @param unit	The unit of the netCDF variable (may be
+     *			<code>null</code>).
+     * @param set	The VisAD SampledSet that "contains" the independent
+     *			variable.
+     * @param idim	The component index within the SampledSet associated
+     *			with the independent variable.
+     * @exception BadFormException
+     *			The netCDF variable cannot be represented.
+     */
+    protected
+    IndependentVar(String name, Dimension dim, Unit unit,
+	    SampledSet set, int idim)
+	throws BadFormException
+    {
+	super(name, Float.TYPE, new Dimension[] {dim}, myAttributes(unit));
+	this.set = set;
+	this.idim = idim;
+    }
+
+
+    /**
+     * Return my attributes for construction.
+     *
+     * @param unit	The unit of the netCDF variable (may be
+     *			<code>null</code>).
+     * @return		The attributes for the netCDF variable.
+     */
+    protected static Attribute[]
+    myAttributes(Unit unit)
+    {
+	return unit == null
+		? null
+		: new Attribute[] { new Attribute("units", unit.toString()) };
+    }
+
+
+    /**
+     * Return an array element identified by position.
+     *
+     * @param indexes		The netCDF indexes of the array element.
+     * @return			The array element at the given position.
+     * @exception IOException	Data access I/O failure.
+     */
+     public Object
+     get(int[] indexes)
+	throws IOException
+     {
+	try
+	{
+	    return new Float(
+		set.indexToValue(new int[] {indexes[indexes.length-1]})
+		    [idim][0]);
+	}
+	catch (Exception e)
+	{
+	    throw new IOException(e.getMessage());
+	}
+     }
+}
diff --git a/visad/data/netcdf/out/RealAccessor.java b/visad/data/netcdf/out/RealAccessor.java
new file mode 100644
index 0000000..655412b
--- /dev/null
+++ b/visad/data/netcdf/out/RealAccessor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: RealAccessor.java,v 1.3 2000-04-26 15:45:26 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Dimension;
+import visad.Real;
+
+
+/**
+ * The RealAccessor class accesses data in a VisAD Real that's been adapted
+ * to a netCDF API.  It's useful for exporting data to a netCDF dataset.
+ */
+class
+RealAccessor
+    extends	DataAccessor
+{
+    /**
+     * Construct from an outer accessor.
+     *
+     * @param outerAccessor	The DataAccessor for the encompassing VisAD
+     *				data object.  Returns VisAD Reals.
+     */
+    protected
+    RealAccessor(VisADAccessor outerAccessor)
+    {
+	super(new Dimension[0], outerAccessor);
+    }
+
+
+    /**
+     * Return a datum given the split, netCDF indexes.
+     *
+     * @return		The datum at the position given by
+     *			<code>localIndexes</code> and
+     *			<code>outerIndexes</code>.
+     * @exception IOException
+     *			Data access I/O failure.
+     */
+    protected Object
+    get()
+	throws IOException
+    {
+	try
+	{
+	    return new
+		Double(((Real)outerAccessor.get(outerIndexes)).getValue());
+	}
+	catch (Exception e)
+	{
+	    throw new IOException(e.getMessage());
+	}
+    }
+}
diff --git a/visad/data/netcdf/out/TextAccessor.java b/visad/data/netcdf/out/TextAccessor.java
new file mode 100644
index 0000000..1c7780e
--- /dev/null
+++ b/visad/data/netcdf/out/TextAccessor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: TextAccessor.java,v 1.3 2000-04-26 15:45:26 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Dimension;
+import visad.Text;
+
+
+/**
+ * The TextAccessor class accesses character data in a VisAD Text that's
+ * being adapted to a netCDF API.  It's useful for exporting data to a netCDF
+ * dataset.
+ */
+class
+TextAccessor
+    extends	DataAccessor
+{
+    /**
+     * The missing-value character.
+     */
+    private final Character		space = new Character(' ');
+
+
+    /**
+     * Construct from a netCDF Dimension and an outer VisADAccessor.
+     *
+     * @param charDim		The netCDF character dimension (i.e. innermost
+     *				netCDF dimension).
+     * @param outerAccessor	The DataAccessor for the encompasing VisAD
+     *				data object.  Returns Text objects.
+     */
+    protected
+    TextAccessor(Dimension charDim, VisADAccessor outerAccessor)
+    {
+	super(new Dimension[] {charDim}, outerAccessor);
+    }
+
+
+    /**
+     * Return a datum given the split, netCDF indexes.
+     *
+     * @return		The datum at the position given by
+     *			<code>localIndexes</code> and
+     *			<code>outerIndexes</code>.
+     * @exception IOException
+     *			Data access I/O failure.
+     * @exception StringIndexOutOfBoundsException
+     *			The character position given by
+     *			<code>localIndexes</code> was out-of-bounds.
+     */
+    protected Object
+    get()
+	throws IOException, StringIndexOutOfBoundsException
+    {
+	return new Character(((Text)outerAccessor.get(outerIndexes)).
+	    getValue().charAt(localIndexes[0]));
+    }
+}
diff --git a/visad/data/netcdf/out/TrivialAccessor.java b/visad/data/netcdf/out/TrivialAccessor.java
new file mode 100644
index 0000000..66610eb
--- /dev/null
+++ b/visad/data/netcdf/out/TrivialAccessor.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: TrivialAccessor.java,v 1.3 2000-04-26 15:45:26 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Dimension;
+import visad.Data;
+
+
+/**
+ * The TrivialAccessor class terminates the linked-list of
+ * DataAccessors at the outermost, VisAD data object.
+ */
+class
+TrivialAccessor
+    implements	VisADAccessor
+{
+    /**
+     * The VisAD data object.
+     */
+    private final Data	data;
+
+
+    /**
+     * Construct from a VisAD data object.
+     *
+     * @param data	The outermost VisAD data object.
+     */
+    protected
+    TrivialAccessor(Data data)
+    {
+	this.data = data;
+    }
+
+
+    /**
+     * Return the number of netCDF dimensions at the current level.
+     *
+     * @return		The rank of the data object.
+     */
+    public int
+    getRank()
+    {
+	return 0;
+    }
+
+
+    /**
+     * Return the netCDF dimensions at the level of the data object.
+     * Include all dimensions in more outer data objects.
+     *
+     * @return		The netCDF dimensions of the data object.
+     */
+    public Dimension[]
+    getDimensions()
+    {
+	return new Dimension[0];
+    }
+
+
+    /**
+     * Return the netCDF dimensional lengths.
+     *
+     * @return		The dimensional lengths of the data object.
+     */
+    public int[]
+    getLengths()
+    {
+	return new int[0];
+    }
+
+
+    /**
+     * Return a datum given its location as netCDF indexes.
+     *
+     * @return		The data object at the position given by
+     *			<code>localIndexes</code> and
+     *			<code>outerIndexes</code>.
+     * @exception IOException
+     *			Data access I/O failure.
+     */
+    public Object
+    get(int[] indexes)
+	throws IOException
+    {
+	return data;
+    }
+}
diff --git a/visad/data/netcdf/out/TupleAccessor.java b/visad/data/netcdf/out/TupleAccessor.java
new file mode 100644
index 0000000..2d0a5d5
--- /dev/null
+++ b/visad/data/netcdf/out/TupleAccessor.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: TupleAccessor.java,v 1.3 2000-04-26 15:45:26 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Dimension;
+import visad.Tuple;
+
+
+/**
+ * The TupleAccessor class accesses data in a VisAD Tuple that's being
+ * adapted to a netCDF API.  It's useful for exporting VisAD data to a
+ * netCDF dataset.
+ */
+class
+TupleAccessor
+    extends	DataAccessor
+{
+    /**
+     * The index of the relevant component.
+     */
+    private final int		index;
+
+
+    /**
+     * Construct from a component index and an outer VisADAccessor.
+     *
+     * @param index		The index of the Tuple component.
+     * @param outerAccessor	The DataAccessor of the enclosing VisAD data
+     *				object.  Returns a Tuple.
+     */
+    protected
+    TupleAccessor(int index, VisADAccessor outerAccessor)
+    {
+	super(new Dimension[0], outerAccessor);
+	this.index = index;
+    }
+
+
+    /**
+     * Return a datum given the split, netCDF indexes.
+     *
+     * @return		The data object at the position given by
+     *			<code>localIndexes</code> and
+     *			<code>outerIndexes</code>.
+     * @exception IOException
+     *			Data access I/O failure.
+     */
+    protected Object
+    get()
+	throws IOException
+    {
+	try
+	{
+	    return ((Tuple)outerAccessor.get(outerIndexes)).getComponent(index);
+	}
+	catch (Exception e)
+	{
+	    throw new IOException(e.getMessage());
+	}
+    }
+}
diff --git a/visad/data/netcdf/out/VisADAccessor.java b/visad/data/netcdf/out/VisADAccessor.java
new file mode 100644
index 0000000..bb0e19f
--- /dev/null
+++ b/visad/data/netcdf/out/VisADAccessor.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VisADAccessor.java,v 1.2 2001-11-27 22:29:38 dglo Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import ucar.netcdf.Dimension;
+
+
+/**
+ * The VisADAccessor interface provides an abstraction for accessing data
+ * in a VisAD data object that's being adapted to a netCDF variable API.
+ */
+interface
+VisADAccessor
+{
+    /**
+     * Return the number of netCDF dimensions at the current level.
+     *
+     * @return	The rank (i.e. number of netCDF dimensions) of the variable.
+     */
+    int
+    getRank();
+
+
+    /**
+     * Return the netCDF dimensions at the level of the data object.
+     * Include all dimensions in more outer data objects.
+     *
+     * @return		The dimensions of the variable.
+     * @postcondition	<code>getRank() == getDimensions().length</code>.
+     */
+    Dimension[]
+    getDimensions();
+
+
+    /**
+     * Return the netCDF dimensional lengths.
+     *
+     * @return		The dimensional lengths.
+     * @postcondition	<code>getRank() == getLengths().length</code>.
+     */
+    int[]
+    getLengths();
+
+
+    /**
+     * Return a datum given its location as netCDF indexes.
+     *
+     * @return		The data object at the given netCDF position.
+     */
+    Object
+    get(int[] indexes)
+	throws IOException;
+}
diff --git a/visad/data/netcdf/out/VisADAdapter.java b/visad/data/netcdf/out/VisADAdapter.java
new file mode 100644
index 0000000..b2efd87
--- /dev/null
+++ b/visad/data/netcdf/out/VisADAdapter.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * All Rights Reserved.
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: VisADAdapter.java,v 1.7 2002-10-15 18:15:15 donm Exp $
+ */
+
+package visad.data.netcdf.out;
+
+import java.io.IOException;
+import java.rmi.RemoteException;
+import ucar.multiarray.Accessor;
+import ucar.multiarray.IndexIterator;
+import ucar.netcdf.AbstractNetcdf;
+import ucar.netcdf.Dimension;
+import ucar.netcdf.ProtoVariable;
+import visad.Data;
+import visad.Field;
+import visad.Gridded1DSet;
+import visad.GriddedSet;
+import visad.Linear1DSet;
+import visad.LinearSet;
+import visad.Real;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.SampledSet;
+import visad.ScalarType;
+import visad.Set;
+import visad.SetType;
+import visad.Text;
+import visad.Tuple;
+import visad.UnimplementedException;
+import visad.Unit;
+import visad.VisADException;
+import visad.data.BadFormException;
+
+
+/**
+ * The VisADAdapter class adapts a VisAD data object to the AbstractNetcdf API.
+ */
+public class
+VisADAdapter
+    extends	AbstractNetcdf
+{
+    /**
+     * Construct from a generic VisAD data object.
+     *
+     * @param data	The VisAD data object to be adapted to a netCDF
+     *			API
+     * @exception UnimplementedException	Something that should be
+     *			implemented isn't yet.
+     * @exception BadFormException	The VisAD data object cannot be
+     *			adapted to a netCDF API
+     * @exception VisADException	Problem in core VisAD.  Some VisAD
+     *			object probably couldn't be created.
+     * @exception RemoteException	Remote data access failure.
+     * @exception IOException		Data access failure.
+     */
+    public
+    VisADAdapter(Data data)
+	throws BadFormException, VisADException, RemoteException, IOException
+    {
+	try
+	{
+	    visit(data, new TrivialAccessor(data));
+	}
+	catch (UnimplementedException e)
+	{
+	    throw new BadFormException(e.getMessage());
+	}
+    }
+
+
+    /**
+     * Visit a VisAD data object.
+     *
+     * @param data		The VisAD data object to be visited.
+     * @param outerAccessor	The means for accessing the individual VisAD
+     *				<code>data</code> objects of the enclosing
+     *				VisAD data object.
+     * @exception UnimplementedException	Something that should be
+     *		implemented isn't yet.
+     * @exception BadFormException		The VisAD data object cannot be
+     *		adapted to a netCDF API
+     * @exception VisADException		Problem in core VisAD.
+     *		Probably some VisAD object couldn't be created.
+     * @exception RemoteException		Remote data access failure.
+     * @exception IOException			Data access failure.
+     */
+    protected void
+    visit(Data data, VisADAccessor outerAccessor)
+	throws UnimplementedException, BadFormException, VisADException,
+	    RemoteException, IOException
+    {
+	/*
+	 * Watch the ordering in the following because the first match
+	 * will be used.
+	 */
+	if (data instanceof Text)
+	    visit((Text)data, outerAccessor);
+	else
+	if (data instanceof Real)
+	    visit((Real)data, outerAccessor);
+	else
+	if (data instanceof Tuple)
+	    visit((Tuple)data, outerAccessor);
+	else
+	if (data instanceof Field)
+	    visit((Field)data, outerAccessor);
+	else
+	    throw new UnimplementedException(
+		"VisAD data type not yet supported: " +
+		data.getClass().getName());
+    }
+
+
+    /**
+     * Visit a VisAD Text object.
+     *
+     * @param text		The VisAD Text object to be visited.
+     * @param outerAccessor	The means for accessing the individual VisAD
+     *				<code>Text</code> objects of the enclosing
+     *				VisAD data object.
+     * @exception BadFormException		The VisAD data object cannot be
+     *		adapted to a netCDF API
+     * @exception IOException			Data access failure.
+     */
+    protected void
+    visit(Text text, VisADAccessor outerAccessor)
+	throws BadFormException, IOException
+    {
+	/*
+         * Because netCDF text variables must have a "character length"
+         * dimension, we traverse all the strings in order to determine
+         * the maximum length.
+	 */
+	int	charLen = 1;   // gotta have at least 1 character
+	for (IndexIterator index =
+		new IndexIterator(outerAccessor.getLengths());
+	     index.notDone();
+	     index.incr())
+	{
+	    int	len = ((Text)outerAccessor.get(index.value()))
+		.getValue().length();
+	    if (len > charLen)
+		charLen = len;
+	}
+
+	/*
+	 * Define the new character dimension.
+	 */
+	String		dimName = ((ScalarType)text.getType()).getName() +
+	    "_len";
+	Dimension	charDim = new Dimension(dimName, charLen);
+	putDimension(charDim);
+
+	/*
+	 * Define the new netCDF character variable.
+	 */
+	DependentTextVar	var = new DependentTextVar(text,
+	    new TextAccessor(charDim, outerAccessor));
+	try
+	{
+	    add(var, var);
+	}
+	catch (Exception e)
+	{
+	    throw new BadFormException(e.getMessage());
+	}
+    }
+
+
+    /**
+     * Visit a VisAD Real object.
+     *
+     * @param real		The VisAD Real object to be visited.
+     * @param outerAccessor	The means for accessing the individual VisAD
+     *				<code>Real</code> objects of the enclosing
+     *				VisAD data object.
+     * @exception BadFormException		The VisAD data object cannot be
+     *		adapted to a netCDF API
+     * @exception VisADException		Problem in core VisAD.
+     *		Probably some VisAD object couldn't be created.
+     */
+    protected void
+    visit(Real real, VisADAccessor outerAccessor)
+	throws BadFormException, VisADException
+    {
+	DependentRealVar	var = new DependentRealVar(real,
+	    new RealAccessor(outerAccessor));
+
+	try
+	{
+	    add(var, var);
+	}
+	catch (Exception e)
+	{
+	    throw new BadFormException(e.getMessage());
+	}
+    }
+
+
+    /**
+     * Visit a VisAD Tuple object.
+     *
+     * @param tuple		The VisAD Tuple object to be visited.
+     * @param outerAccessor	The means for accessing the individual VisAD
+     *				<code>Tuple</code> objects of the enclosing
+     *				VisAD data object.
+     * @exception VisADException	Problem in core VisAD.  Probably some
+     *					VisAD object couldn't be created.
+     * @exception RemoteException	Remote data access failure.
+     * @exception IOException		Local data access failure.
+     */
+    protected void
+    visit(Tuple tuple, VisADAccessor outerAccessor)
+	throws VisADException, RemoteException, IOException
+    {
+	int	componentCount = tuple.getDimension();
+
+	for (int i = 0; i < componentCount; ++i)
+	    visit(tuple.getComponent(i), new TupleAccessor(i, outerAccessor));
+    }
+
+
+    /**
+     * Define the netCDF dimensions and variables of a VisAD Field object.
+     *
+     * @param field		The VisAD Field to be visited
+     * @param outerAccessor	The means for accessing the individual VisAD
+     *				<code>Field</code> objects of the enclosing
+     *				VisAD data object.
+     * @exception UnimplementedException
+     *					Something that should be implemented
+     *					isn't yet.
+     * @exception BadFormException	The VisAD data object cannot be adapted
+     *					to a netCDF API
+     * @exception VisADException	Problem in core VisAD.  Probably some
+     *					VisAD object couldn't be created.
+     * @exception RemoteException	Remote data access failure.
+     * @exception IOException		Local data access failure.
+     */
+    protected void
+    visit(Field field, VisADAccessor outerAccessor)
+	throws RemoteException, VisADException, BadFormException,
+	    UnimplementedException, IOException
+    {
+	Set		set = field.getDomainSet();
+	Dimension[]	dims;
+
+	if (set instanceof LinearSet)
+	{
+	    /*
+	     * The domain set is a cross-product of arithmetic
+	     * progressions.  This maps directly into the netCDF
+	     * data model -- possibly with coordinate variables.
+	     */
+	    dims = defineLinearSetDims((GriddedSet)set);
+	}				// the sample-domain is linear
+	else
+	if (set instanceof SampledSet)
+	{
+	    /*
+	     * The domain set is not an arithmetic progression.
+	     */
+	    dims = new Dimension[] {defineSampledSetDim((SampledSet)set)};
+	}
+	else
+	{
+	    throw new BadFormException(
+		"Can't handle a " + set.getClass().getName() + " domain set");
+	}
+
+	/*
+	 * Continue the definition process on the inner, VisAD data
+	 * objects by visiting the first sample.  The dimension array
+	 * is converted to netCDF order (outermost dimension first).
+	 */
+	// System.out.println("visit(Field,...): RangeType: " +
+	    // field.getSample(0).getType());
+	visit(field.getSample(0),
+	    new FieldAccessor(reverse(dims), outerAccessor));
+    }
+
+
+    /**
+     * Define the netCDF dimensions of a VisAD LinearSet, including any
+     * necessary coordinate variables..
+     *
+     * @param set	The VisAD GriddedSet to be examined, WHERE
+     * 			<code>set instanceof LinearSet</code>.
+     * @return		The netCDF dimensions of <code>set</code>.
+     */
+    protected Dimension[]
+    defineLinearSetDims(GriddedSet set)
+	throws VisADException, BadFormException
+    {
+	int		rank = set.getDimension();
+	Dimension[]	dims = new Dimension[rank];
+	RealTupleType	domainType = ((SetType)set.getType()).getDomain();
+	Unit[]		units = set.getSetUnits();
+
+	/*
+	 * Define any necessary coordinate-variables.
+	 */
+	for (int idim = 0; idim < rank; ++idim)
+	{
+	    Linear1DSet	linear1DSet =
+		((LinearSet)set).getLinear1DComponent(idim);
+	    int		length = linear1DSet.getLength(0);
+	    String	name =
+		((RealType)domainType.getComponent(idim)).getName();
+
+	    dims[idim] = new Dimension(name, length);
+
+	    if (linear1DSet.getFirst() != 0.0 ||
+		linear1DSet.getStep() != 1.0)
+	    {
+		/*
+		 * The domain sampling has associated coordinate
+		 * values; therefore, we define a corresponding
+		 * netCDF coordinate-variable.
+		 */
+		CoordVar var =
+		    new CoordVar(name, dims[idim], units[idim], linear1DSet);
+
+		try
+		{
+		    add(var, var);
+		}
+		catch (Exception e)
+		{
+		    throw new BadFormException(e.getMessage());
+		}
+	    }
+	}			// sample-domain dimension loop
+
+	return dims;
+    }
+
+
+    /**
+     * Define the netCDF dimensions and variables of a VisAD SampledSet.
+     *
+     * @param set	The VisAD SampledSet to be examined.
+     * @return		The netCDF dimension of <code>set</code>.
+     * @exception VisADException	Problem in core VisAD.
+     * @exception BadFormException	<code>set</code> cannot be represented
+     *					in a netCDF dataset.
+     **/
+    protected Dimension
+    defineSampledSetDim(SampledSet set)
+	throws VisADException, BadFormException
+    {
+	int	rank = set.getManifoldDimension();
+
+	return rank == 1
+		    ? define1DDim(set)
+		    : defineNDDim(set);
+    }
+
+
+    /**
+     * Define the netCDF dimension of a 1-D SampledSet.  This dimension will
+     * have an associated, netCDF coordinate variable.
+     *
+     * @param set	The set to have a netCDF dimension defined for it.
+     *			Precondition: <code>set.getDimension() == 1</code>.
+     * @return		The netCDF dimension corresponding to the 1-D
+     *			SampledSet.
+     * @exception VisADException	Problem in core VisAD.
+     * @exception BadFormException	<code>set</code> cannot be represented
+     *					in a netCDF dataset.
+     */
+    protected Dimension
+    define1DDim(SampledSet set)
+	throws VisADException, BadFormException
+    {
+	RealTupleType	domainType = ((SetType)set.getType()).getDomain();
+	String		name =
+	    ((RealType)domainType.getComponent(0)).getName();
+	Dimension	dim = new Dimension(name, set.getLength());
+	Unit[]		units = set.getSetUnits();
+	if (!(set instanceof Gridded1DSet))
+	    throw new BadFormException("Domain set not Gridded1DSet");
+	CoordVar	var =
+	    new CoordVar(name, dim, units[0], (Gridded1DSet)set);
+	try
+	{
+	    add(var, var);
+	}
+	catch (Exception e)
+	{
+	    throw new BadFormException(e.getMessage());
+	}
+
+	return dim;
+    }
+
+
+    /**
+     * Define the netCDF dimension of a multi-dimensional SampledSet.
+     * This will be an "index" dimension with associated netCDF variables
+     * that represent the independent coordinates of the domain set.
+     *
+     * @param set	The VisAD SampledSet to be examined and have
+     *			a corresponding netCDF "index" dimension created
+     *			together with netCDF variables for the independent
+     *			variables.  Precondition: <code>set.getDimension()
+     *			> 1</code>.
+     * @return		The netCDF dimension corresponding to the
+     *			SampledSet.
+     * @exception VisADException	Problem in core VisAD.
+     * @exception BadFormException	<code>set</code> cannot be represented
+     *					in a netCDF dataset.
+     */
+    protected Dimension
+    defineNDDim(SampledSet set)
+	throws VisADException, BadFormException
+    {
+	Dimension	dim;
+	RealTupleType	domainType = ((SetType)set.getType()).getDomain();
+	int		rank = domainType.getDimension();
+	Unit[]		units = set.getSetUnits();
+	String[]	names = new String[rank];
+
+	/*
+	 * Get the names.
+	 */
+	for (int idim = 0; idim < rank; ++idim)
+	    names[idim] = ((RealType)domainType.getComponent(idim)).getName();
+
+	/*
+	 * Define the "index" dimension.
+	 */
+	{
+	    int			len = names[0].length();
+
+	    for (int idim = 1; idim < rank; ++idim)
+		len += 1 + names[idim].length();
+	    len += 4;
+
+	    StringBuffer	name = new StringBuffer(len);
+
+	    name.append(names[0]);
+
+	    for (int idim = 1; idim < rank; ++idim)
+	    {
+		name.append("_");
+		name.append(names[idim]);
+	    }
+	    name.append("_ndx");
+
+	    dim = new Dimension(name.toString(), set.getLength());
+	}
+
+	/*
+	 * Define the independent variables.
+	 */
+	for (int idim = 0; idim < rank; ++idim)
+	{
+	    IndependentVar	var =
+		new IndependentVar(names[idim], dim, units[idim],
+				    (SampledSet)set, idim);
+
+	    try
+	    {
+		add(var, var);
+	    }
+	    catch (Exception e)
+	    {
+		throw new BadFormException(e.getMessage());
+	    }
+	}
+
+	return dim;
+    }
+
+
+    /**
+     * Reverse the dimensions in a 1-D array.
+     */
+    protected Dimension[]
+    reverse(Dimension[] inDims)
+    {
+	Dimension[]	outDims = new Dimension[inDims.length];
+
+	for (int i = 0; i < inDims.length; ++i)
+	    outDims[i] = inDims[inDims.length - 1 - i];
+
+	return outDims;
+    }
+
+
+    /**
+     * Return a MultiArray Accessor for a variable.
+     *
+     * This method is part of the AbstractNetcdf class and should never
+     * be called -- so it always throws an error.
+     */
+    public Accessor
+    ioFactory(ProtoVariable protoVar)
+    {
+	throw new UnsupportedOperationException();
+    }
+}
diff --git a/visad/data/netcdf/out/package.html b/visad/data/netcdf/out/package.html
new file mode 100644
index 0000000..4c99fb2
--- /dev/null
+++ b/visad/data/netcdf/out/package.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html	1.60 98/01/27
+
+  Copyright 1998 Sun Microsystems, Inc. 901 San Antonio Road, 
+  Palo Alto, California, 94303, U.S.A.  All Rights Reserved.
+
+  This software is the confidential and proprietary information of Sun
+  Microsystems, Inc. ("Confidential Information").  You shall not
+  disclose such Confidential Information and shall use it only in
+  accordance with the terms of the license agreement you entered into
+  with Sun.
+
+  CopyrightVersion 1.2
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides for exporting a VisAD data object to a netCDF dataset.
+
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/data/netcdf/package.html b/visad/data/netcdf/package.html
new file mode 100644
index 0000000..9e7ab77
--- /dev/null
+++ b/visad/data/netcdf/package.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+Provides for importing a netCDF dataset into VisAD and for exporting a VisAD
+data object to a netCDF dataset.
+
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/data/netcdf/small.nc b/visad/data/netcdf/small.nc
new file mode 100644
index 0000000..2ec09b6
Binary files /dev/null and b/visad/data/netcdf/small.nc differ
diff --git a/visad/data/netcdf/units/DefaultUnitsDB.java b/visad/data/netcdf/units/DefaultUnitsDB.java
new file mode 100644
index 0000000..d025148
--- /dev/null
+++ b/visad/data/netcdf/units/DefaultUnitsDB.java
@@ -0,0 +1,65 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.netcdf.units;
+
+import java.util.Enumeration;
+
+import visad.Unit;
+import visad.BaseUnit;
+
+/** @deprecated Use <tt>visad.data.units.DefaultUnitsDB</tt> instead */
+public class DefaultUnitsDB
+  implements UnitsDB, visad.data.units.UnitsDB
+{
+  private visad.data.units.DefaultUnitsDB dfltDB;
+
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB(db)</tt> instead */
+  public DefaultUnitsDB(visad.data.units.UnitsDB db)
+  {
+    dfltDB = (visad.data.units.DefaultUnitsDB )db;
+  }
+
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.instance()</tt> instead */
+  public static UnitsDB instance()
+    throws visad.UnitException
+  {
+    return new DefaultUnitsDB(visad.data.units.DefaultUnitsDB.instance());
+  }
+
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.get(name)</tt> instead */
+  public Unit get(String name) { return dfltDB.get(name); }
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.getNameEnumeration()</tt> instead */
+  public Enumeration getNameEnumeration() { return dfltDB.getNameEnumeration(); }
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.getSymbolEnumeration()</tt> instead */
+  public Enumeration getSymbolEnumeration() { return dfltDB.getSymbolEnumeration(); }
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.getUnitEnumeration()</tt> instead */
+  public Enumeration getUnitEnumeration() { return dfltDB.getUnitEnumeration(); }
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.list()</tt> instead */
+  public void list() { dfltDB.list(); }
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.put(bu)</tt> instead */
+  public void put(BaseUnit bu) { dfltDB.put(bu); }
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.putName(name, u)</tt> instead */
+  public void putName(String name, Unit u) { dfltDB.putName(name, u); }
+  /** @deprecated Use <tt>visad.data.units.DefaultUnitsDB.putSymbol(name, u)</tt> instead */
+  public void putSymbol(String name, Unit u) { dfltDB.putSymbol(name, u); }
+}
diff --git a/visad/data/netcdf/units/NoSuchUnitException.java b/visad/data/netcdf/units/NoSuchUnitException.java
new file mode 100644
index 0000000..a762355
--- /dev/null
+++ b/visad/data/netcdf/units/NoSuchUnitException.java
@@ -0,0 +1,31 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.netcdf.units;
+
+/** @deprecated Use <tt>visad.data.units.NoSuchUnitException</tt> instead */
+public class NoSuchUnitException
+  extends ParseException
+{
+  /** @deprecated Use <tt>visad.data.units.NoSuchUnitException(msg)</tt> instead */
+  public NoSuchUnitException(String msg) { super(msg); }
+}
diff --git a/visad/data/netcdf/units/ParseException.java b/visad/data/netcdf/units/ParseException.java
new file mode 100644
index 0000000..04dacf3
--- /dev/null
+++ b/visad/data/netcdf/units/ParseException.java
@@ -0,0 +1,33 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.netcdf.units;
+
+/** @deprecated Use <tt>visad.data.units.ParseException</tt> instead */
+public class ParseException
+  extends visad.data.units.ParseException
+{
+  /** @deprecated Use <tt>visad.data.units.ParseException()</tt> instead */
+  public ParseException() { super(); }
+  /** @deprecated Use <tt>visad.data.units.ParseException(msg)</tt> instead */
+  public ParseException(String msg) { super(msg); }
+}
diff --git a/visad/data/netcdf/units/Parser.java b/visad/data/netcdf/units/Parser.java
new file mode 100644
index 0000000..2405d17
--- /dev/null
+++ b/visad/data/netcdf/units/Parser.java
@@ -0,0 +1,43 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.netcdf.units;
+
+import visad.Unit;
+
+/** @deprecated Use <tt>visad.data.units.Parser</tt> instead */
+public class Parser
+  extends visad.data.units.Parser
+{
+  /** @deprecated Use <tt>visad.data.units.Parser.parse(spec)</tt> instead */
+  public static synchronized Unit parse(String spec)
+    throws ParseException, NoSuchUnitException
+  {
+    try {
+      return visad.data.units.Parser.parse(spec);
+    } catch (visad.data.units.NoSuchUnitException nsue) {
+      throw new NoSuchUnitException(nsue.getMessage());
+    } catch (visad.data.units.ParseException pe) {
+      throw new ParseException(pe.getMessage());
+    }
+  }
+}
diff --git a/visad/data/netcdf/units/UnitParser.java b/visad/data/netcdf/units/UnitParser.java
new file mode 100644
index 0000000..bbc2fb1
--- /dev/null
+++ b/visad/data/netcdf/units/UnitParser.java
@@ -0,0 +1,42 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.netcdf.units;
+
+/** @deprecated Use <tt>visad.data.units.UnitParser</tt> instead */
+public class UnitParser
+  extends visad.data.units.UnitParser
+{
+  /** @deprecated Use <tt>visad.data.units.UnitParser(stream)</tt> instead */
+  public UnitParser(java.io.InputStream stream)
+  {
+    super(stream);
+  }
+
+  /** @deprecated Use <tt>visad.data.units.UnitParser.encodeTimestamp()</tt> instead */
+  public static double encodeTimestamp(int year, int month, int day, int hour,
+                                       int minute, float second, int zone)
+  {
+    return visad.data.units.UnitParser.encodeTimestamp(year, month, day, hour,
+                                                       minute, second, zone);
+  }
+}
diff --git a/visad/data/netcdf/units/UnitsDB.java b/visad/data/netcdf/units/UnitsDB.java
new file mode 100644
index 0000000..610780e
--- /dev/null
+++ b/visad/data/netcdf/units/UnitsDB.java
@@ -0,0 +1,29 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.netcdf.units;
+
+/** @deprecated Use <tt>visad.data.units.UnitsDB</tt> instead */
+public interface UnitsDB
+  extends visad.data.units.UnitsDB
+{
+}
diff --git a/visad/data/package.html b/visad/data/package.html
new file mode 100644
index 0000000..339a630
--- /dev/null
+++ b/visad/data/package.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+
+  @(#)package.html	1.60 98/01/27
+
+  Copyright 1998 Sun Microsystems, Inc. 901 San Antonio Road, 
+  Palo Alto, California, 94303, U.S.A.  All Rights Reserved.
+
+  This software is the confidential and proprietary information of Sun
+  Microsystems, Inc. ("Confidential Information").  You shall not
+  disclose such Confidential Information and shall use it only in
+  accordance with the terms of the license agreement you entered into
+  with Sun.
+
+  CopyrightVersion 1.2
+
+-->
+</head>
+<body bgcolor="white">
+
+Provides for importing data to and exporting data from VisAD.
+
+The VisAD data backend subsystem is implemented as a heirarchy of <I>data
+forms</I>.  A data form is the form in which the data is contained. 
+I could be, for example, a netCDF or HDF-EOS dataset, or a FITS file. 
+It could also be local or remote.  The term <I>form</I> is used rather
+than <I>format</I> to encompase both data access methodologies (e.g. the
+netCDF API) and the more traditional data file formats.
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/data/qt/PictForm.java b/visad/data/qt/PictForm.java
new file mode 100644
index 0000000..2141395
--- /dev/null
+++ b/visad/data/qt/PictForm.java
@@ -0,0 +1,44 @@
+//
+// PictForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data. Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.qt;
+
+/**
+ * PictForm is the VisAD data adapter for PICT images.
+ *
+ * @deprecated Use visad.data.bio.LociForm with loci.formats.in.PictReader
+ */
+public class PictForm extends visad.data.bio.LociForm {
+
+  public PictForm() {
+    super(new loci.formats.in.PictReader());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new PictForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/qt/QTForm.java b/visad/data/qt/QTForm.java
new file mode 100644
index 0000000..e6398f4
--- /dev/null
+++ b/visad/data/qt/QTForm.java
@@ -0,0 +1,45 @@
+//
+// QTForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data. Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.qt;
+
+/**
+ * QTForm is the VisAD data adapter for QuickTime movies.
+ *
+ * @deprecated Use visad.data.bio.LociForm with
+ *   loci.formats.in.QTReader and loci.formats.out.QTWriter
+ */
+public class QTForm extends visad.data.bio.LociForm {
+
+  public QTForm() {
+    super(new loci.formats.in.QTReader(), new loci.formats.out.QTWriter());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new QTForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/text/README.text b/visad/data/text/README.text
new file mode 100644
index 0000000..32388a3
--- /dev/null
+++ b/visad/data/text/README.text
@@ -0,0 +1,413 @@
+                          The VisAD TextAdapter
+                             January, 2001
+
+                         updated: December, 2012
+
+Contents.
+
+Introduction
+   I. Text File Formats
+  II. Line 1
+ IIa. 2-D arrays
+ IIb. 1,2, or 3-D points
+ III. Line2
+IIIa. for 2-D arrays
+IIIb. for 1,2,or 3-D points
+  IV. Examples
+
+Introduction
+
+The VisAD TextAdapter is designed to allow you to quickly read in data
+that are in the form of an ASCII text file.  We fully expect this
+class to continue to grow to accommodate other, common variations of
+text file formats that might be encountered.  
+
+Two example files are also contained in the release.  It is most
+convenient to test these using the VisAD Spreadsheet or the Jython
+(Python) interface.  Fire up either the visad.python.JPythonFrame, or
+simply start it from the command line and use a sequence like:
+
+  >>> from visad.python.JPythonMethods import *
+  >>> a = load("example1.txt")
+  >>> plot(a)
+  >>> clearplot()
+  >>> b = load("example2.csv")
+  >>> plot(b)
+  >>> clearplot()
+
+Or simply load these files into the SpreadSheet, and then experiment
+with the mappings!
+
+
+I. Text file formats
+
+The text files usually consist of 2 header lines and then data.
+Optional comment lines may be interspersed throughout.  The data
+portion of the file may be either blank-, comma-, semicolon- or
+tab-separated values.  At present only numeric data can be read.  The
+model for these files is spreadsheet (Excel, etc) output -- that is,
+column-oriented values.
+
+Comment lines are any line that starts with either #, !, or %.
+
+The file extensions recognized by the VisAD DefaultFamily and the
+TextAdapter:  
+
+  .bsv -> blank-separated values
+  .tsv -> tab-separated values
+  .csv -> comma-separated values
+
+In addition, the VisAD DefaultFamily will recognize the extension .txt
+and invoke TextAdapter.  In this case, however, the TextAdapter
+attempts to sense the delimiter using the hierarchy:
+
+          tab,  semicolon,  comma,  blank
+
+That is, if a tab character appears in the line, tab will be used.  If
+not, then it looks for a semicolon.  Otherwise, if a comma appears in
+the line it will be used.  If neither a tab, semicolon, or comma appears,
+then blank will be used.
+
+We tried to keep the amount of modification you might have to make to
+existing files to a minimum.   The general layout is:
+
+Line 1:  functional description of the data in "VisAD" lingo (aka the
+         "MathType")
+
+Line 2:  column headers, which name each parameter and possibly give
+         them a physical unit, using the delimiter defined above  
+         (tab, semicolon, comma or blank).
+
+Line 3-n: the data values (with delimiters as define above; for
+          filenames without recognized extensions, the delimiter 
+          used in this data section does not have to be the same 
+          as the one used on Line 2)
+
+Please refer to the VisAD Library Developers Guide, section "3.1
+MathTypes" for information on how to define the functional
+description.  Also, take a look to the examples at the end of this
+file.
+
+Also, please note that if you are using the TextAdapter constructor
+directly, the "Line 1" and "Line 2" values do not have to be in the
+file - alternate signatures allow these values to be passed as
+arguments.  However, if your text file is used through the
+VisAD DefaultFamily (as in the SpreadSheet), you must provide the
+information right in the text file.
+
+
+II. Line 1  (ignoring any preceding comment lines...)
+
+This line specifies a functional description of the data, using the
+VisAD "MathType" string.  
+
+There are two categories of data that may be represented in these text
+of files:  
+
+   1) 2-D arrays of a single parameter, or 
+   2) 1,2,or 3-D (domain) points of one or more parameters.
+
+IIa.  2-D arrays
+
+  In this case, the "VisAD" functional description looks like:
+  
+     (x,y)->(temperature)
+     (Longitude, Latitude)->(speed)
+
+  And the data portion of the file contains "x" values per line,
+  and "y" lines of data.  See Examples #2 and Example #6, below.
+
+  Only the "y" domain component may have its values defined in the
+  file.  
+
+  2-D arrays are implied when:
+      * there are 2 domain components
+      * there is only one range component
+      * there is more than one domain sampling value for the first
+        domain component (that is, more than one data value on a line
+        in the text file)
+
+
+IIb.  1,2,or 3-D points
+
+  Just about every other form of a text file falls into this category.
+
+  Examples of VisAD functional descriptions:
+
+  (x)->(temperature, dewpoint, speed)
+  (x,y,z)->(temperature, speed)
+  
+   At least one of the domain variables (x,y,z) _must_ be defined
+   by data in the file.  See Examples #1, #3, #4 and #5, below.
+
+   You may also use Text types for these data.  For example:
+   (Latitude,Longitude)->(City(Text))
+
+   Strictly speaking, you may have a domain with more than
+   3 components.  If you do this, however, the TextAdapter
+   will not be able to optimize the construction of the
+   sampling set, and will use either a LinearNDSet (if
+   you supply simple ranges for all domain components) or
+   an IrregularSet (if one or more of the domain components
+   has values specified in the file).
+
+
+
+III. Line 2  (ignoring any comment lines that might come before)
+
+The second line of the text files defines which column of the data
+portion contains what parameters.  (Note that, as with the "Line 1",
+an alternate form of the constructor is available so this information
+can be passed as an argument rather than being read from the file.) 
+
+If you have other information that you need to specify for a parameter,
+you should use a blank-separated sequence of phrases in the form
+"key=value", to specify what you need.  Here are the possible keys:
+
+     key      value
+     ----     ---------------
+     unit     name of Unit (default = no unit)
+
+     miss     value to be treated as missing (default = no missing values)
+
+     scale    value that each datum is multiplied by (default = 1.0)
+
+     offset   value that is added to each scaled datum (default = 0.0)
+
+     error    value of the estimated error for this parameter (default = none)
+           ** In this release, only range error estimates are implemented.
+
+     interval either 'true' or 'false' to indicate that this parameter
+              is an _interval_, like a difference (default = false)
+
+     pos      column-oriented location of the data values in the form
+              first:last (if present for one item, this MUST be supplied
+              for all items!)
+
+     fmt      format pattern, using SimpleDateFormat type patterns 
+              (without spaces).
+              Commas should also be avoided in the format pattern, 
+              especially if comma is used as the header column delimiter
+
+     colspan  a value that indicates the number of columns (positions)
+              spanned by this parameter.
+             
+     value    a value that is used for this field. When a value is defined there 
+              should not be a correspoding value for this field in the data lines.
+              i.e., if you had 5 parameters defined in Line 2 (e.g., p1,p2,p3,p4,p5)
+              and one of them (e.g., p3) had a value attribute then the data lines
+              would only contain the values for the other parameters, e.g.:
+              p1,p2,p4,p5
+              p1,p2,p4,p5
+              ...
+
+              When using the value attribute you can also include in any subsequent lines a:
+              name=value
+              Where name is one of the parameter names. This allows you to reset the fixed value
+              that is used.
+
+              For example, this facility could be used if you had a set of observations from
+              a single station:
+
+              (index) -> (Longitude,Latitude,Time,T)
+              Longitude[unit="degrees west" value="110"],Latitude[unit="deg" value="40"],Time[fmt="yyyy-MM-dd HH:mm:ss z"],T[unit="celsius"]
+              2007-02-20 11:00:00 MST,13.3
+              2007-02-20 11:00:00 MST,-2.0
+              Latitude=30
+              Longitude=100
+              2007-02-20 11:00:00 MST,5.0
+              ...
+ 
+              Note: You don't have to have the value attribute in Line 2. You can just do:
+              (index) -> (Longitude,Latitude,Time,T)
+              Longitude[unit="degrees west" ],Latitude[unit="deg"],Time[fmt="yyyy-MM-dd HH:mm:ss z"],T[unit="celsius"]
+              Longitude=110
+              Latitude=40
+              2007-02-20 11:00:00 MST,13.3
+              2007-02-20 11:00:00 MST,-2.0
+              Latitude=30
+              Longitude=100
+              2007-02-20 11:00:00 MST,5.0
+              ...
+
+              
+
+Three short examples:
+     a, b, c, temperature[unit=degC err=.1 miss=999.9], speed[m/s]
+     Longitude[scale=-1], Latitude, temp[unit=degK miss=999.9], dewPoint[unit=degK miss=999.9]
+     Time[fmt=yyyy-MM-dd'T'HH:mm:ss'Z'], Longitude, Latitude, Pressure
+
+(Please note in the second example, the "scale=-1" for the Longitude serves
+to invert the sign of the values read from the file).
+
+(You might also note that "C" is not the VisAD unit name for degrees
+Celcius..."C" means Coulomb...)
+
+For some 'values' you might need to imbed a space.  You may do this
+by enclosing the entire 'value' in double quote marks, as in:  
+unit="international inch".  If you do this, however, you must be
+careful about ambiguities using "blank separated values" formats.
+
+As with Line 1, there are two cases to consider when defining the contents of
+this Line:
+
+
+IIIa. 2-D arrays
+
+  In this case, _only_ one range parameter is permitted.  You may have
+  0 or 1 domain parameter names, as well as any "column skip" dummy names
+  (these are only permitted _before_ the actual range parameter name,
+  though).  For this simplest example:
+
+    temperature[unit=degF]
+
+  Says that the data contains only values of temperature in degrees F.
+  The domain parameters defined in Line 1 will be computed based on
+  the number of items per line and the number of lines of text.
+
+  If you need to skip some columns before the values of the variable
+  start, just put in a "skip" name for each column.  For example:
+
+    skip, skip, skip, temperature[unit=degF]
+
+  indicates that the values in the first three columns should be
+  skipped, and the rest of the values on the line will make up the
+  "columns" of the 2-D array.   The name "skip" can be _any_ unused
+  name.
+
+  
+IIIb. 1,2,or 3-D points
+
+  In this case, you define which column corresponds to which
+  parameter you named on Line 1. The order doesn't matter, only that
+  the correct column is identified.  If there are columns of data that
+  are to be ignored, just use a "skip" name that was not defined on
+  Line 1.  For example:
+
+   x, y, skip, temperature[unit=degC], skip, pressure[unit=hPa]
+
+  In this case, the name "skip" is a filler to indicate what column(s)
+  should be skipped.
+
+  If you are dealing with Text type data, you must include the (Text)
+  phrase here as well:
+
+  Longitude, Latitude, skip, City(Text)
+
+    (Note that with this "Text" form, each data item MUST be enclosed in 
+    double-quote marks -- see example, below)
+  
+  
+In both cases, you may also use this line of text to define the values
+of the domain component samplings in the form:
+
+    name(first:last)
+
+This means that 
+   a) "name" is a domain component, and... 
+   b) the (sampling) values of "name" are NOT read from the file, but are
+      computed based on the range "first to last" and the number of lines
+      of text (number of samples), or (in the case of 2-D arrays)
+      possibly the number of range values on the line (see below), and....  
+   c) this name is ignored for the purposes of counting/locating, 
+      columns for other parameters.
+
+If the name of a domain component variable does NOT appear on Line
+2, it's values are assumed to be 0:(N-1)  where "N" is the number of
+samples (number of lines) in the file.  
+
+There is one exception to this:  in the case of 2-D arrays, the first 
+domain component is assumed to apply to the number of range values 
+on each line of text, _not_ the number of "samples" or lines of text.
+
+If you have only one value per input line of text, and you have a 2-D
+array, you may optionally add a third specification:
+    name(first:last:number)
+
+where 'number' is the number of values for this domain component.  See
+examples, below.
+
+Finally, if you need to combine the range with other information about
+the parameter, it would look like:
+          
+          x(1.0:13.7)[unit=cm]
+
+
+IV. Examples
+
+Here are a few examples taken from the beginning of some files:
+
+Example #1 - Simple CSV file, for a function value=f(x) <==
+
+(x)->(value)
+value
+0 
+7.2
+-9.1
+
+Example #2 -  CSV file of a 2-D array <==
+
+(x,y)->(value)
+skip,skip,skip,skip,value
+0 , 0 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 
+1 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 , 98 
+
+Example #3 - a ".txt" file of two range components (note that the
+delimiters used on "Line 2" are different that the ones used in the
+data) <==
+
+(x)->(value1, value2)
+skip  value1  skip  skip  skip  skip  value2
+100 , 0 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 
+101 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 , 98 
+
+Example #4 - CSV file of two range components located at 2-d coordinates <==
+
+(x,y)->(value_a, value_b)
+y,x,p,value_a[unit="degC"],p,p,p,value_b[unit=degF]
+0 , 0 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 
+1 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 , 98 
+
+Example #5 -  BSV file of real data <==
+
+% Retrieval statistics for mlw_K+ir3_2a.ret :
+% Zbottom threshold = 0.0 km  liqclouds=1
+%   IWP      IWP errors (dB)        Dme errors (dB) 
+% (g/m^2)   mean   rms   median    mean   rms    median 
+(IPW)->(IWP_Error, Dme_error, IWP_Error_mean, Dme_error_mean)
+IPW[g/m^2]   IWP_Error_mean p  IWP_Error   Dme_error_mean  p   Dme_error
+   1.41    7.092  7.890  6.831    0.746  1.768  1.139    717
+
+Example #6 - BSV file of a 2D grid, with the locations given Lat/Lon values <==
+
+(Longitude,Latitude)->(value)
+Longitude(-130:-40) Latitude(20:60) p value[unit=degC]
+0   0   17   34   50   64   76   86   93   98   99 
+1   17   34   50   64   76   86   93   98   99   98 
+
+Example #7 - TXT file of point (in situ) data with non-numeric range values <==
+
+(Longitude, Latitude) -> (City(Text))
+Latitude, Longitude, City(Text)
+-12.3, 130.8, "Darwin"
+-16.9, 146.5, "Cairns"
+-23.6, 133.9, "Alice Springs"
+-33.9, 151.2, "Sydney"
+-37.6, 144.9, "Melbourne"
+
+Example #8 - CSV file of time series point data with time format specified <==
+(Time->(Latitude,Longitude,Pressure))
+Time[fmt=yyyy-MM-dd'T'HH:mm:ss'Z'],Latitude,Longitude,Pressure
+2003-05-02T07:00:00Z,-10.0,150.0,998.0
+2003-05-02T10:00:00Z,-10.5,150.5,997.0
+2003-05-02T13:00:00Z,-11.0,151.0,996.0
+
+Example #9 - Text file of a 2-D grid, with one value per line <==
+(Longitude,Latitude)->Altitude
+Longitude(-107.495:-103.605:109),Latitude(38.5572:41.487:109), Altitude[unit=m]
+     3060.
+     3043.
+     2949.
+     2865.
+     .... 
+     ....
diff --git a/visad/data/text/TextAdapter.java b/visad/data/text/TextAdapter.java
new file mode 100644
index 0000000..a54829f
--- /dev/null
+++ b/visad/data/text/TextAdapter.java
@@ -0,0 +1,1846 @@
+//
+// TextAdapter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.text;
+
+import java.io.IOException;
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import visad.Set;
+
+import java.net.URL;
+
+import visad.*;
+import visad.VisADException;
+import visad.data.in.ArithProg;
+import visad.util.DataUtility;
+
+import java.util.regex.*;
+
+
+/** this is an VisAD file adapter for comma-, tab- and blank-separated
+  * ASCII text file data.  It will attempt to create a FlatField from
+  * the data and descriptions given in the file and/or the constructor.
+  *
+  * The text files contained delimited data.  The delimiter is 
+  * determined as follows:  if the file has a well-known extension
+  * (.csv, .tsv, .bsv) then the delimiter is implied by the extension.
+  * In all other cases, the delimiter for the data (and for the
+  * "column labels") is determined by reading the first line and
+  * looking, in order, for a tab, comma, or blank.  Which ever one
+  * is found first is taken as the delimiter.
+  *
+  * Two extra pieces of information are needed:  the VisAD "MathType"
+  * which is specified as a string (e.g., (x,y)->(temperature))
+  * and may either be the first line of the file or passed in through
+  * one of the constructors.
+  *
+  * The second item are the "column labels" which contain the names
+  * of each field in the data.  The names of all range components
+  * specified in the "MathType" must appear.  The names of domain
+  * components are optional.  The values in this string are separated
+  * by the delimiter, as defined above.
+  *
+  * See visad.data.text.README.text for more details.
+  * 
+  * @author Tom Whittaker
+  * 
+  */
+public class TextAdapter {
+
+ private static final String ATTR_COLSPAN = "colspan";
+ private static final String ATTR_VALUE   = "value";
+ private static final String ATTR_OFFSET = "off";
+ private static final String ATTR_ERROR  = "err";
+ private static final String ATTR_SCALE = "sca";
+ private static final String ATTR_POSITION ="pos";
+ private static final String ATTR_FORMAT = "fmt";
+ private static final String ATTR_TIMEZONE = "tz";
+ private static final String ATTR_UNIT= "unit";
+ private static final String ATTR_MISSING = "mis";
+ private static final String ATTR_INTERVAL = "int";
+
+
+  private static final String COMMA = ",";
+  private static final String SEMICOLON = ";";
+  private static final String TAB = "\t";
+  private static final String BLANK = " ";
+  private static final String BLANK_DELIM = "\\s+";
+
+
+
+  private FlatField ff = null;
+  private Field field = null;
+  private boolean debug = false;
+  private String DELIM;
+  private boolean DOQUOTE = true;
+  private boolean GOTTIME = false;
+
+
+  HeaderInfo []infos;
+
+  double[] rangeErrorEstimates;
+  Unit[] rangeUnits;
+  Set[] rangeSets;
+  double[] domainErrorEstimates;
+  Unit[] domainUnits;
+
+
+  int[][] hdrColumns;
+  int[][] values_to_index;
+
+  private Hashtable properties;
+
+
+  private boolean onlyReadOneLine = false;
+
+
+  private Pattern skipPattern;
+
+  /**If this is defined then when processing each tuple pass the processor the tuple
+     and do not try to create the field */
+  private StreamProcessor streamProcessor;
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param filename name of local file.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public TextAdapter(String filename) throws IOException, VisADException {
+    this(filename, null, null);
+  }
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param filename name of local file.
+    * @param map the VisAD "MathType" as a string defining the FlatField
+    * @param params the list of parameters used to define what columns
+    *  of the text file correspond to what MathType parameters.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public TextAdapter(String filename, String map, String params) 
+                         throws IOException, VisADException {
+    InputStream is = new FileInputStream(filename);
+    DELIM = getDelimiter(filename);
+    readit(is, map, params);
+  }
+
+  /** Create a VisAD FlatField from a remote Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    *
+    * @param url File URL.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public TextAdapter(URL url) throws IOException, VisADException {
+    this(url, null, null);
+  }
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param url File URL.
+    * @param map the VisAD "MathType" as a string defining the FlatField
+    * @param params the list of parameters used to define what columns
+    *  of the text file correspond to what MathType parameters.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public TextAdapter(URL url, String map, String params) 
+                        throws IOException, VisADException {
+    DELIM = getDelimiter(url.getFile());
+    InputStream is = url.openStream();
+    readit(is, map, params);
+  }
+
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param inputStream The input stream to read from
+    * @param delimiter the delimiter
+    * @param map the VisAD "MathType" as a string defining the FlatField
+    * @param params the list of parameters used to define what columns
+    *  of the text file correspond to what MathType parameters.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public TextAdapter(InputStream inputStream, String delimiter, String map, String params) 
+                         throws IOException, VisADException {
+      this(inputStream, delimiter, map,params,false);
+  }
+
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param inputStream The input stream to read from
+    * @param delimiter the delimiter
+    * @param map the VisAD "MathType" as a string defining the FlatField
+    * @param params the list of parameters used to define what columns
+    *  of the text file correspond to what MathType parameters.
+    * @param onlyReadOneLine If true then only read one line of data. This is used so client code can
+    * read the meta data.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+
+  public TextAdapter(InputStream inputStream, String delimiter, String map, String params,boolean onlyReadOneLine) 
+                         throws IOException, VisADException {
+      this(inputStream, delimiter, map, params, null, onlyReadOneLine);
+  }
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param inputStream The input stream to read from
+    * @param delimiter the delimiter
+    * @param map the VisAD "MathType" as a string defining the FlatField
+    * @param params the list of parameters used to define what columns
+    *  of the text file correspond to what MathType parameters.
+    * @param properties properties
+    * @param onlyReadOneLine If true then only read one line of data. This is used so client code can
+    * read the meta data.
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+
+  public TextAdapter(InputStream inputStream, String delimiter, String map, String params,Hashtable properties, boolean onlyReadOneLine) 
+                         throws IOException, VisADException {
+      this(inputStream, delimiter, map, params, properties, onlyReadOneLine, null);
+  }
+
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param inputStream The input stream to read from
+    * @param delimiter the delimiter
+    * @param map the VisAD "MathType" as a string defining the FlatField
+    * @param params the list of parameters used to define what columns
+    *  of the text file correspond to what MathType parameters.
+    * @param properties properties
+    * @param onlyReadOneLine If true then only read one line of data. This is used so client code can
+    * read the meta data.
+    * @param skipPatternString if non-null then skip any line that matches this pattern
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+  public TextAdapter(InputStream inputStream, String delimiter, String map, String params,Hashtable properties, boolean onlyReadOneLine,String skipPatternString) 
+                         throws IOException, VisADException {
+      this(inputStream, delimiter,  map, params, properties, onlyReadOneLine, skipPatternString,null); 
+  }
+
+
+
+  /** Create a VisAD FlatField from a local Text (comma-, tab- or 
+    * blank-separated values) ASCII file
+    * @param inputStream The input stream to read from
+    * @param delimiter the delimiter
+    * @param map the VisAD "MathType" as a string defining the FlatField
+    * @param params the list of parameters used to define what columns
+    *  of the text file correspond to what MathType parameters.
+    * @param properties properties
+    * @param onlyReadOneLine If true then only read one line of data. This is used so client code can
+    * read the meta data.
+    * @param skipPatternString if non-null then skip any line that matches this pattern
+    * @param streamProcessor Optional processor of the Tuple stream for point obs
+    * @exception IOException if there was a problem reading the file.
+    * @exception VisADException if an unexpected problem occurs.
+    */
+   public TextAdapter(InputStream inputStream, String delimiter, String map, String params,Hashtable properties, boolean onlyReadOneLine,String skipPatternString,StreamProcessor streamProcessor) 
+                         throws IOException, VisADException {
+    this.onlyReadOneLine = onlyReadOneLine;
+    this.streamProcessor = streamProcessor;
+    DELIM = delimiter;
+    this.properties = properties;
+    if(skipPatternString!=null && skipPatternString.length()>0) {
+        skipPattern = Pattern.compile(skipPatternString);
+    }
+    readit(inputStream, map, params);
+  }
+
+
+
+
+
+
+  public static  String getDelimiter(String filename) {
+    if(filename == null) return null;
+    filename = filename.trim().toLowerCase();
+    if (filename.endsWith(".csv")) return COMMA;
+    if (filename.endsWith(".tsv")) return TAB;
+    if (filename.endsWith(".bsv")) return BLANK;    
+    return null;
+  }
+
+
+  /**
+   * Is the given text line a comment
+   *
+   * @return is it a comment line
+   */
+  public static boolean isComment(String line) {
+    return (line.startsWith("#") || 
+            line.startsWith("!") || 
+            line.startsWith("%") || 
+            line.length() < 1);
+  }
+
+    public static  String readLine(BufferedReader bis) 
+        throws IOException {
+      while (true) {
+        String line = bis.readLine();
+        if (line == null) return null;
+        if (!isText(line)) return null;
+        if (isComment(line)) continue;
+        return line.trim();
+      }
+    }
+
+
+  void readit(InputStream is, String map, String params) 
+                              throws IOException, VisADException {
+    // read the ASCII file, using commas as field separators
+    // first line is a header line
+
+    List realTypes = new ArrayList();
+    ff = null;
+    field = null;
+
+    if (debug) System.out.println("####   Text Adapter v2.x running");
+
+    BufferedReader bis = new BufferedReader(new InputStreamReader(is));
+
+    // mapping defines how the names are mapped
+    // for example:   (x,y) => (one, two, three)
+
+    String maps = null;
+    if (map == null) {
+      maps = readLine(bis);
+      if(maps != null) {
+          maps = maps.trim();
+      }
+    } else {
+      maps = map;
+    }
+
+    if (maps != null) {
+       maps = makeMT(maps);
+    }
+    if (maps == null) {
+      throw new visad.data.BadFormException(
+        "TextAdapter: Invalid or missing MathType");
+    }
+
+    List<String[]>nameChanges = new ArrayList<String[]>();
+
+
+    if (debug) System.out.println("Specified MathType = "+maps);
+
+    // but first, we need to get the column headers because they
+    // may have [units] associated with them.  The column headers
+    // primarily define where the data are.
+
+    String hdr = null;
+    if (params == null) {
+      hdr = readLine(bis);
+    } else {
+      hdr = params;
+    }
+
+    String hdrDelim = DELIM;
+    if (DELIM == null) {
+      if (hdr.indexOf(BLANK) != -1) hdrDelim = BLANK_DELIM; 
+      if (hdr.indexOf(COMMA) != -1) hdrDelim = COMMA; 
+      if (hdr.indexOf(SEMICOLON) != -1) hdrDelim = SEMICOLON; 
+      if (hdr.indexOf(TAB) != -1) hdrDelim = TAB; 
+
+      if (debug) System.out.println("Using header delimiter = "+ hdrDelim + "("+
+                                     (hdrDelim.getBytes())[0] + ")");
+    }
+    
+    // squeeze out extra blank spaces
+    if (hdrDelim.equals(BLANK) || hdrDelim.equals(BLANK_DELIM)) {
+  	  //System.out.println("line before squeeze: " + line);
+        hdr = hdr.replaceAll("\\s++", " ").trim();
+  	  //System.out.println("line after squeeze: " + line);
+    }
+
+
+    String[] sthdr = hdr.split(hdrDelim);
+    // since blanks separate the metadata, if we have a blank 
+    // delimiter, we run into problems.  Loop through the header and
+    // put humpty dumpty back together again
+    if (hdrDelim.equals(BLANK_DELIM) || hdrDelim.equals(BLANK)) {
+        List<String> chunks = new ArrayList<String>();
+        for (int i = 0; i < sthdr.length; i++) {
+            String subchunk = sthdr[i].trim();
+            int m = subchunk.indexOf("[");
+            if (m == -1) {
+                chunks.add(subchunk);
+                continue;
+            }
+            // have "[", find "]"
+            int m2 = subchunk.indexOf("]");
+            while (m2 < 0 && i < sthdr.length) {
+               i++;
+               subchunk += " " +sthdr[i].trim();
+               m2 = subchunk.indexOf("]");
+            }
+            chunks.add(subchunk);
+        }
+        sthdr = (String[]) chunks.toArray(new String[chunks.size()]);
+    }
+    int nhdr = sthdr.length;
+    infos    = new HeaderInfo[nhdr];
+    for(int i=0;i<infos.length;i++) {
+      infos[i] = new HeaderInfo();
+    }
+    hdrColumns = new int[2][nhdr];
+    int numHdrValues=0;
+
+    // pre-scan of the header names to seek out Units
+    // since we cannot change a RealType once it's defined!!
+
+
+
+    for (int i=0; i<nhdr; i++) {
+      String name = sthdr[i].trim();
+      String hdrUnitString = null;
+      hdrColumns[0][i] = -1; // indicating no fixed columns
+      
+      int m = name.indexOf("[");
+
+      if (m == -1) {
+          infos[i].name = name;
+          hdrUnitString = null;
+      } else {
+        int m2 = name.indexOf("]");
+        if (m2 == -1) {
+          throw new VisADException("TextAdapter: Bad [descriptor] named in:"+name);
+        }
+
+        // now parse items: unit=xxx miss=xxx interval=xxx error=xxx
+
+        // 0. Remove any spaces around the "=" signs
+        // 1. tokenize on " "
+        // 2. scan each token, retokenizing on "="
+        // 3. if (has no "=") && (is first one) then treat as Unit
+        // 4. otherwise, look for keys "unit" "miss" "inter" "err" "scale" "offset" "pos"
+      
+        //    and fill in the values in array[i]
+
+        if (m2 >= name.length()) {
+          infos[i].name = name.substring(0,m).trim();
+        } else {
+          infos[i].name = (name.substring(0,m)+name.substring(m2+1)).trim();
+        }
+
+        // 0. Remove any spaces around the "=" signs
+        String cl = name.substring(m+1,m2).trim();
+        cl = cl.replaceAll(" +=","=");
+        cl = cl.replaceAll("= +","=");
+
+
+        String[] stcl = cl.split(BLANK_DELIM);
+        int ncl = stcl.length;
+
+        if (ncl == 1 && cl.indexOf("=") == -1) {
+          hdrUnitString = cl;  // backward compatible...
+
+        } else {
+          for (int l = 0; l  < ncl; l++) {
+            String s = stcl[l];
+            String[] sts = s.split("=");
+            if (sts.length != 2) {
+              throw new VisADException("TextAdapter: Invalid clause in: "+s);
+            }
+            String tok = sts[0];
+            String val = sts[1];
+            
+            // check for quoted strings
+            if (val.startsWith("\"")) {
+
+              // see if ending quote also fetched
+              if (val.endsWith("\"")) {
+                String v2 = val.substring(1,val.length()-1);
+                val = v2;
+
+              } else {
+                // if not, then reparse stcl to suck up spaces...
+                try {
+                  String v2="";
+                  for (int q=l+1; q < ncl; q++) {
+                      String  vTmp = stcl[q];
+                      // find next token that has a " in it
+                      int pos = vTmp.indexOf("\"");
+                      l++;
+                      if (pos < 0) {  // no "
+                          v2 = v2+" "+vTmp;
+                      } else {
+                          v2 = v2+" "+vTmp.substring(0,pos);
+                          break;
+                      }
+                  }
+                  String v3 = val.substring(1)+v2;
+                  val = v3;
+
+                //} catch (NoSuchElementException nse2) {
+                } catch (ArrayIndexOutOfBoundsException nse2) {
+                  val="";
+                }
+              }
+            }
+
+            if (debug) System.out.println("####   tok = "+tok+ " val = '"+val+"'");
+
+            if (tok.toLowerCase().startsWith(ATTR_UNIT)) {
+              hdrUnitString = val;
+
+            } else if (tok.toLowerCase().startsWith(ATTR_MISSING)) {
+                infos[i].missingString = val.trim();
+              try {
+                infos[i].missingValue = Double.parseDouble(val);
+              } catch (java.lang.NumberFormatException me) {
+                  infos[i].missingValue = Double.NaN;
+              }
+            } else if (tok.toLowerCase().startsWith(ATTR_INTERVAL)) {
+              infos[i].isInterval = -1;
+              if (val.toLowerCase().startsWith("t")) infos[i].isInterval = 1;
+              if (val.toLowerCase().startsWith("f")) infos[i].isInterval = 0;
+              if (infos[i].isInterval == -1) {
+                throw new VisADException("TextAdapter: Value of \'interval\' must be \'true\' or \'false\'");
+              }
+            } else if (tok.toLowerCase().startsWith(ATTR_ERROR)) {
+                infos[i].errorEstimate = Double.parseDouble(val);
+            } else if (tok.toLowerCase().startsWith(ATTR_SCALE)) {
+                infos[i].scale = Double.parseDouble(val);
+            } else if (tok.toLowerCase().startsWith(ATTR_OFFSET)) {
+              infos[i].offset = Double.parseDouble(val);
+            } else if (tok.toLowerCase().startsWith(ATTR_VALUE)) {
+              infos[i].fixedValue = val.trim();
+              numHdrValues++;
+            } else if (tok.toLowerCase().startsWith(ATTR_COLSPAN)) {
+              infos[i].colspan = (int)Double.parseDouble(val.trim());
+            } else if (tok.toLowerCase().startsWith(ATTR_POSITION)) {
+              String[] stp = val.split(":");
+              if (stp.length != 2) {
+                throw new VisADException("TextAdapter: invalid Position parameter in:"+s);
+              }
+              hdrColumns[0][i] = Integer.parseInt(stp[0].trim());
+              hdrColumns[1][i] = Integer.parseInt(stp[1].trim());
+
+            } else if (tok.toLowerCase().startsWith(ATTR_FORMAT)) {
+                infos[i].formatString = val.trim();
+            } else if (tok.toLowerCase().startsWith(ATTR_TIMEZONE)) {
+                infos[i].tzString = val.trim();
+            } else {
+              throw new VisADException("TextAdapter: invalid token name: "+s);
+            }
+
+          }
+        }
+
+      }
+
+
+
+      if(properties!=null) {
+          for(int headerIdx=0;headerIdx<infos.length;headerIdx++) {
+              String value = (String)properties.get(infos[headerIdx].name+".value");
+              if(value!=null) infos[headerIdx].fixedValue = value;
+          } 
+      }
+
+
+      if (debug) 
+            System.out.println("hdr name = "+infos[i]+" units="+
+             hdrUnitString+
+             " miss="+infos[i].missingValue+" interval="+infos[i].isInterval+ 
+             " errorest="+infos[i].errorEstimate+" scale="+infos[i].scale+
+             " offset="+infos[i].offset+" pos="+hdrColumns[0][i]+":"+
+             hdrColumns[1][i]);
+
+      Unit hdrUnit = null;
+ 
+      if (hdrUnitString != null && 
+                !hdrUnitString.trim().equalsIgnoreCase("null") ) {
+        hdrUnitString = hdrUnitString.trim();
+        try {
+            hdrUnit = visad.data.units.Parser.parse(hdrUnitString);
+        } catch (Exception ue) {
+          try {
+              hdrUnitString = hdrUnitString.replace(' ','_');
+              hdrUnit = visad.data.units.Parser.parse(hdrUnitString);
+          } catch (Exception ue2) {
+            System.out.println("Unit name problem:"+ue+" with: "+hdrUnitString);
+            hdrUnit = null;
+          }
+        }
+        if(hdrUnit!=null) {
+            //We clone this unit so it has the original unit string, not the SI unit we get from the parser
+            try {
+                hdrUnit = hdrUnit.clone(hdrUnitString);
+            } catch(Exception ignoreThis) {}
+        }
+      }
+
+      if (debug) System.out.println("####   assigned Unit as u="+hdrUnit);
+
+
+      String rttemp = infos[i].name.trim();
+      if (rttemp.indexOf("(Text)") == -1) {
+
+        int parenIndex = rttemp.indexOf("(");
+
+        if (parenIndex < 0) parenIndex = rttemp.indexOf("[");
+        if (parenIndex < 0) parenIndex = rttemp.indexOf("{");
+        if (parenIndex < 0) parenIndex = rttemp.indexOf(" ");
+        String rtname = parenIndex < 0 ? rttemp.trim() : rttemp.substring(0,parenIndex);
+
+        RealType rt = RealType.getRealType(rtname, hdrUnit, null, infos[i].isInterval);
+
+        //        System.err.println("rtname:" + rtname + " " + rt);
+        if (rt == null) {  // tried to re-use with different units
+          if (debug) System.out.println("####   rt was returned as null");
+          if (debug && hdrUnit != null) 
+              System.out.println("####  Could not make RealType using specified Unit ("+hdrUnitString+") for parameter name: "+rtname);
+
+          //Make the realType with just the name
+          rt = RealType.getRealType(rtname);
+
+          //Check if the realtype unit works with the unit from the header
+          if(rt.getDefaultUnit()!=null && hdrUnit!=null) {
+              if(!Unit.canConvert(rt.getDefaultUnit(), hdrUnit)) {
+                  rt = null;
+              } 
+          }  else if(hdrUnit!=null) {
+              rt = null;
+          }
+          
+          //If the realtype is bad then we make a new one with the unitsuffix and add
+          //a name change entry so later we change the mathtype string to have the new name
+          if(rt == null) {
+            rt = DataUtility.getUniqueRealType(rtname,hdrUnit, null, infos[i].isInterval);
+            if (rt != null) {
+              String newName  = rt.getName();
+              nameChanges.add(new String[]{rtname, newName});
+              infos[i].name = newName;
+              if(debug)
+                      System.out.println("made new realtype:" + rt + " unit:" + rt.getDefaultUnit());
+            }
+          }
+        }
+
+
+        //Add the realType here because its possible that it can be GC'ed
+        //and removed from the global list of realtypes before we 
+        //get back to it. Then the MathType.stringToType(maps) below 
+        //will produce a realtype with no units
+        realTypes.add(rt);
+
+        // get a compatible unit, if necessary
+
+        if (rt.equals(visad.RealType.Time)) {
+          GOTTIME = true;
+          if (debug) System.out.println("####  found a visad.RealType.Time component");
+        } else {
+          GOTTIME = false;
+        }
+
+
+        if (hdrUnit == null) hdrUnit = rt.getDefaultUnit();
+        if(debug) System.out.println("####  retrieve units from RealType = "+hdrUnit);
+      }
+
+      infos[i].unit = hdrUnit;
+    }
+
+
+    for(String[] tuple: nameChanges) {
+        if(debug) System.err.println ("changing mathtype component from:" + tuple[0] +"  to:" + tuple[1]);
+        maps = maps.replaceAll("(,|\\() *" + tuple[0]+" *(,|\\))", "$1" + tuple[1]+"$2");
+    }
+
+    // get the MathType of the function
+    MathType mt = null;
+    try {
+      mt = MathType.stringToType(maps);
+    } catch (Exception mte) {
+        mte.printStackTrace();
+      throw new VisADException("TextAdapter: MathType badly formed or missing: "+maps);
+    }
+
+    if (debug) {
+      System.out.println(mt);
+      new visad.jmet.DumpType().dumpMathType(mt,System.out);
+    }
+
+
+    //Note,  we need to have a reference to the realTypes list somewhere
+    //after the above call to stringToType so that the list doesn't get gc'ed
+    //and the realtypes it contains don't get gc'ed
+    if(realTypes.size()==0) {
+    }
+
+
+    // now get the names of the domain variables and range variables.
+    String[] domainNames = null;
+    String[] rangeNames = null;
+    int numDom = 0;
+    int numRng = 0;
+    RealTupleType domType;
+    TupleType rangeType;
+
+    if (mt instanceof FunctionType) {
+      domType = ((FunctionType)mt).getDomain();
+      numDom = domType.getDimension();
+      domainNames = new String[numDom];
+
+      for (int i=0; i<numDom; i++) {
+        MathType comp = domType.getComponent(i);
+        domainNames[i] = ((RealType)comp).toString().trim();
+        if (debug) System.out.println("dom "+i+" = "+domainNames[i]);
+      }
+
+      //      debug =true;
+      rangeType = (TupleType) ((FunctionType)mt).getRange();
+      numRng = rangeType.getDimension();
+      rangeNames = new String[numRng];
+      rangeSets = new Set[numRng];
+      for (int i=0; i<numRng; i++) {
+        MathType comp = rangeType.getComponent(i);
+        rangeNames[i] = (comp).toString().trim();
+        if (debug) System.out.println("range "+i+" = "+rangeNames[i]);
+        if (comp instanceof RealType) {
+          rangeSets[i] = ((RealType) comp).getDefaultSet();
+          if (rangeSets[i] == null) {
+            if (comp.equals(RealType.Time)) {
+              rangeSets[i] = new DoubleSet(new SetType(comp));
+            } else {
+              rangeSets[i] = new FloatSet(new SetType(comp));
+            }
+          }
+        } else {
+          rangeSets[i] = null;  // something else is wrong here...
+        }
+        if (debug) System.out.println("####  rangeSet = "+rangeSets[i]);
+;
+      }
+
+    } else { 
+      throw new visad.VisADException("TextAdapter: Math Type is not a simple FunctionType");
+    }
+
+
+// now for each header label, determine if it's a domain or
+// range component -- and if so, which one.
+
+// also, if it's a domain component, allow for name(first:last[:number])
+//
+// and if none of the domain components appear in the list, then
+// they are computed as name(0:N-1)
+
+    int[] domainPointer = new int[numDom];
+    double[][] domainRanges = new double[3][numDom]; // min, max, numb
+    boolean[] gotDomainRanges = new boolean[numDom];
+    domainErrorEstimates = new double[numDom];
+    domainUnits = new Unit[numDom];
+    rangeErrorEstimates = new double[numRng];
+    rangeUnits = new Unit[numRng];
+
+    int countDomain = 0;
+
+    for (int i=0; i<numDom; i++) {
+      domainPointer[i] = -1;
+      gotDomainRanges[i] = false;
+      domainErrorEstimates[i] = Double.NaN;
+      domainUnits[i] = null;
+    }
+
+    int[] rangePointer = new int[numRng];
+    int countRange = 0;
+
+    for (int i=0; i<numRng; i++) {
+      rangePointer[i] = -1;
+      rangeErrorEstimates[i] = Double.NaN;
+      rangeUnits[i] = null;
+    }
+
+    int countValues = -1;
+    values_to_index = new int[3][nhdr];
+
+    for (int i=0; i<nhdr; i++) {
+      values_to_index[0][i] = -1;  // points to domains
+      values_to_index[1][i] = -1;  // points to ranges
+      values_to_index[2][i] = -1;  // points to names/units/etc
+      countValues ++;
+
+      String name = infos[i].name;
+
+
+      // see if it's a domain name
+      boolean gotName = false;
+
+      // is there a "min:max" clause?
+      String test_name = name;
+      int n = test_name.indexOf("(");
+      if (n != -1) {
+        // but allow for "(Text)" 
+        if ((test_name.indexOf("(Text)")) == -1) {
+          test_name = name.substring(0,n).trim();
+          countValues --;  // this value wont appear in data!
+          countDomain --; // and is a pre-defined, linear set
+        }
+      }
+
+      // try to find the column header name in the domain name list
+      for (int k=0; k<numDom; k++) {
+
+        if (test_name.equals(domainNames[k]) ) { 
+          domainPointer[k] = countValues;
+          domainErrorEstimates[k] = infos[i].errorEstimate;
+          domainUnits[k] = infos[i].unit;
+          gotName = true;
+          countDomain ++;
+          // now see if a list is given...
+          if (n != -1) {
+
+            try {
+
+              String ss = name.substring(n+1,name.length()-1);
+              String[] sct = ss.split(":");
+              String first = sct[0].trim();
+              String second = sct[1].trim();
+              String third = "1";
+              if (sct.length == 3) third = sct[2].trim();
+              domainRanges[0][k] = Double.parseDouble(first);
+              domainRanges[1][k] = Double.parseDouble(second);
+              domainRanges[2][k] = Double.parseDouble(third);
+              gotDomainRanges[k] = true;
+
+            } catch (Exception ef) {
+              throw new VisADException(
+       "TextAdapter: Error while interpreting min:max values for domain "+name);
+            }
+
+          } else if (countValues > -1) { // if no list, get from file
+            values_to_index[0][countValues] = k;
+            values_to_index[2][countValues] = i;
+          }
+
+          break;
+       }
+
+    } 
+
+    if (gotName) continue;
+
+    // or see if its a range name...
+
+    for (int k=0; k<numRng; k++) {
+      if (name.equals(rangeNames[k]) ) {
+        rangePointer[k] = countValues;
+        rangeErrorEstimates[k] = infos[i].errorEstimate;
+        rangeUnits[k] = infos[i].unit;
+        countRange ++;
+        values_to_index[1][countValues] = k;
+        values_to_index[2][countValues] = i;
+        gotName = true;
+      }
+    }
+  }
+
+
+// huge debug printout...
+// *****************************************************************
+
+  if (debug) {
+    System.out.println("countDom/numDom="+countDomain+" "+numDom);
+
+    System.out.println("countRange/numRng="+countRange+" "+numRng);
+
+    System.out.println("Domain info:");
+    for (int i=0; i<numDom; i++) {
+      System.out.println("Dom name / index = "+domainNames[i]+"  "+
+             domainPointer[i]);
+
+      if (gotDomainRanges[i]) {
+        System.out.println("    ..."+domainRanges[0][i]+"  "+
+            domainRanges[1][i]+"    "+domainRanges[2][i]);
+      }
+    }
+
+    System.out.println("Range info:");
+    for (int i=0; i<numRng; i++) {
+      System.out.println("Rng name / index / error est = "+rangeNames[i]+"  "+
+             rangePointer[i]+ "  " + rangeErrorEstimates[i] +" "+
+             rangeUnits[i]);
+    }
+
+    System.out.println("values_to_index pointers = ");
+    for (int i=0; i<nhdr; i++) {
+      System.out.println(" inx / value = "+i+ 
+              " "+values_to_index[0][i]+"    "+values_to_index[1][i]+
+              " "+values_to_index[2][i]);
+    }
+  }
+
+// ***************************************************************
+
+
+    // for each line of text, put the values into the ArrayList
+    ArrayList domainValues = new ArrayList();
+    ArrayList rangeValues = new ArrayList();
+    ArrayList tupleValues = new ArrayList(); 
+    boolean tryToMakeTuple = true;
+    Tuple tuple = null;
+    
+    String dataDelim = DELIM;
+    boolean isRaster = false;
+    int numElements = 1;
+
+    // in the 'raster array' case, the numRng value will be 1,
+    // along with the countRange.  numDomain must be 2.
+
+    // if the domain is 2D, then get values from the first
+    // matching column to the end...
+    if (countRange == 1 && numRng == 1 && 
+                numDom == 2 && countDomain < 2) isRaster = true;
+
+    Real[] prototypeReals = new Real[nhdr];
+    TupleType tupleType = null;
+    int index;
+    int lineCnt = 0;
+    while (true) {
+      String line = readLine(bis);
+      if (debug) System.out.println("read:"+line);
+      if (line == null) break;
+      if(skipPattern!=null && skipPattern.matcher(line).find()) continue;
+      if((index=line.indexOf("="))>=0) {  // fixed value
+        String name  = line.substring(0,index).trim();
+        String value  = line.substring(index+1).trim();
+        boolean foundIt = false;
+        for(int paramIdx=0;paramIdx<infos.length;paramIdx++) {
+            if(infos[paramIdx].isParam(name)) {
+                if(infos[paramIdx].fixedValue==null) {
+                    numHdrValues++;
+                }
+                infos[paramIdx].fixedValue = value;
+                foundIt = true;
+                break;
+            }
+        }
+        if(!foundIt) {
+           throw new VisADException(
+                    "TextAdapter: Cannot find field with name:" +name +" from line:" + line);
+        }
+        continue;
+      }
+
+
+      if (dataDelim == null) {
+        if (line.indexOf(BLANK) != -1) dataDelim = BLANK_DELIM; 
+        if (line.indexOf(COMMA) != -1) dataDelim = COMMA; 
+        if (line.indexOf(SEMICOLON) != -1) dataDelim = SEMICOLON; 
+        if (line.indexOf(TAB) != -1) dataDelim = TAB; 
+
+        if (debug) System.out.println("Using data delimiter = "+
+                                       ((dataDelim == null) 
+                                           ? "null" 
+                                           : dataDelim  + " (" + (dataDelim.getBytes())[0] +")"));
+      }
+      // squeeze out extra blank spaces
+      if (dataDelim.equals(BLANK) || dataDelim.equals(BLANK_DELIM)) {
+    	  //System.out.println("line before squeeze: " + line);
+          line = line.replaceAll("\\s++", " ").trim();
+    	  //System.out.println("line after squeeze: " + line);
+      }
+
+      String[] tokens = line.split(dataDelim);
+      int n = tokens.length;
+      if (n < 1) continue; // something is wrong if this happens!
+      lineCnt++;
+      double [] dValues = null;
+      double [] rValues = null;
+      Data [] dataArray= null;
+
+
+      if (streamProcessor==null) {
+	  dValues = new double[numDom];
+      }
+
+
+      if (isRaster) {
+        if (debug) System.out.println("probably a raster...");
+        boolean gotFirst = false;
+        int rvaluePointer = 0;
+        int irange = 0;
+        for (int i=0; i<n; i++) {
+
+          String sa = tokens[i];
+          
+          if (i >= nhdr) {  // are we past where domain would be found?
+
+            if (!gotFirst) {
+              throw new VisADException(
+                        "TextAdapter: Cannot find first raster value");
+            }
+
+            rvaluePointer ++;
+            rValues[rvaluePointer] = getVal(sa, irange);
+
+          } else {  // or are we still looking for domain?
+          
+            if (values_to_index[0][i] != -1) {
+              dValues[values_to_index[0][i]] = getVal(sa, i);
+            }
+
+            if (gotFirst) {  // already gathering data
+              rvaluePointer ++;
+              rValues[rvaluePointer] = getVal(sa, irange);
+
+            } else {
+               if (values_to_index[1][i] != -1) {
+                 // cannot dimension the array until we have found
+                 // the first set of range values!!
+                 rValues = new double[n - i];
+                 irange = i;
+                 rValues[rvaluePointer] = getVal(sa, irange);
+                 gotFirst = true;
+               }
+            }
+
+          }
+        }
+         
+      } else {  // is probably NOT a raster
+        dataArray = new Data[numRng];
+        if (debug) System.out.println("probably not a raster...");
+	if (streamProcessor==null) {
+	    rValues = new double[numRng];
+	}
+        MathType thisMT;
+        if (n > nhdr) n = nhdr; // in case the # tokens > # parameters
+        n +=numHdrValues;
+
+        int tokenIdx = 0;   // token counter
+
+
+        for (int i=0; i<nhdr; i++) {   // loop over the columns
+          String sa=null;
+	  if(infos[i].fixedValue!=null) {
+	      sa = infos[i].fixedValue;
+	  }  else if (tokenIdx >= tokens.length) {   // more params than tokens
+	      sa = "";                    // need to have a missing value
+	  } else {
+	      sa = tokens[tokenIdx++].trim();
+	      int moreColumns = infos[i].colspan-1;
+	      while (moreColumns>0) {
+		  sa = sa + " " + tokens[tokenIdx++].trim();
+		  moreColumns--;
+	      }
+          }
+
+          String sThisText;
+
+          if (values_to_index[0][i] != -1) {
+	      if(dValues!=null)
+		  dValues[values_to_index[0][i]] = getVal(sa, i);
+          } else if (values_to_index[1][i] != -1) {
+            int tupleIndex = values_to_index[1][i];
+            int infosIndex = values_to_index[2][i];
+            thisMT = rangeType.getComponent(tupleIndex);
+            if (thisMT instanceof TextType) {
+              // if Text, then check for quoted string
+              if (sa.startsWith("\"")) {
+                if (sa.endsWith("\"")) {  // if single token ends with quote
+                  String sa2 = sa.substring(1,sa.length()-1);
+                  sThisText = sa2;
+                } else {
+                  // TODO:  work on this
+                  try {
+                    String delim = 
+                        dataDelim.equals(BLANK_DELIM) ? BLANK : dataDelim;
+                    String sa2="";
+                    for (int q=tokenIdx; q < tokens.length; q++) {
+                        String  saTmp = tokens[q];
+                        // find next token that has a " in it
+                        int pos = saTmp.indexOf("\"");
+                        tokenIdx++;
+                        if (pos < 0) {  // no dataDelim
+                            sa2 = sa2+delim+saTmp;
+                        } else {
+                            sa2 = sa2+saTmp.substring(0,pos);
+                            //tokens[tokenIdx] = saTmp.substring(pos+1);
+                            break;
+                        }
+                    }
+
+                    //sThisText = sa.substring(1)+sa2;
+                    sThisText = sa.substring(1)+delim+sa2;
+                  //} catch (NoSuchElementException nse) {
+                  } catch (ArrayIndexOutOfBoundsException nse) {
+                    sThisText = "";
+                  }
+                }
+
+                if (debug) System.out.println("####   Text value='"+sThisText+"'");
+
+              // if not quoted, then take "as is"
+              } else {
+                sThisText = sa;
+              }
+
+
+              // now make the VisAD Data 
+              try {
+                dataArray[tupleIndex] = 
+                        new Text((TextType)thisMT, sThisText);
+
+                if (debug) System.out.println("dataArray[" + 
+                          tupleIndex + "] = " + 
+                          dataArray[tupleIndex]);
+              } catch (Exception e) {
+                System.out.println(" Exception converting " + 
+                                       thisMT + " to TextType " + e);
+              }
+            // if not Text, then treat as numeric
+            } else {
+		//              if(true) continue;
+              double value = getVal(sa,i);
+
+	      if(rValues!=null)
+		  rValues[tupleIndex] = value;
+              try {
+                  if(prototypeReals[i]==null) {
+                      prototypeReals[i] =    new Real((RealType) thisMT, value, infos[infosIndex].unit);
+                  }
+                  dataArray[tupleIndex] = 
+                      prototypeReals[i].cloneButValue(value);
+                  if(debug)System.out.println("dataArray[" + 
+                    tupleIndex + "] = " + 
+                    dataArray[tupleIndex]);
+
+              } catch (Exception e) {
+                System.out.println(" Exception converting " + thisMT + " " + e);
+                e.printStackTrace();
+              }
+            }
+          }
+        }
+      }
+
+      if(tryToMakeTuple) {
+        try {
+            if (dataArray != null) {
+		if (streamProcessor!=null) {
+		    streamProcessor.processValues(dataArray);
+		} else {
+		    if(tupleType == null) {
+			tuple = new Tuple(dataArray);
+			tupleType = (TupleType)tuple.getType();
+		    } else {
+			tuple = new Tuple(tupleType, dataArray, false, false);
+		    }
+		}
+            }
+        } catch (visad.TypeException te) {
+          // do nothing: it means they are all reals
+          // tuple = new RealTuple(dataArray);
+          tuple = null;
+          tryToMakeTuple = false; 
+        } catch(NullPointerException npe) {
+            for(int i=0;i<dataArray.length;i++) {
+                if(dataArray[i] == null) {
+                    throw new IllegalArgumentException("An error occurred reading line number:" + lineCnt+" column number:" + (i+1)+"\n" +
+                                                       line);
+                }
+            }
+            throw npe;
+        }
+      }
+
+
+      if (streamProcessor==null) {
+	  if(dValues!=null)
+	      domainValues.add(dValues);
+	  if(rValues!=null)
+	      rangeValues.add(rValues);
+          if (tuple != null) 
+	      tupleValues.add(tuple); 
+      }
+      if (isRaster) numElements = rValues.length;
+      if(onlyReadOneLine) break;
+    }
+
+
+    if (streamProcessor!=null) {
+	bis.close();
+        return;
+    }
+    int numSamples = rangeValues.size(); // # lines of data
+
+    if (numSamples == 0) {
+        throw new VisADException("No data available to read");
+    }
+
+// ***********************************************************
+    if (debug) {
+      try {
+        System.out.println("domain size = "+domainValues.size());
+        double[] dt = (double[]) domainValues.get(1);
+        System.out.println("domain.array[0] = "+dt[0]);
+        System.out.println("range size = "+rangeValues.size());
+        System.out.println("# samples = "+numSamples);
+      } catch (Exception er) {System.out.println("out range");}
+    }
+// ***********************************************************
+
+
+    // make Linear1DSets for each possible domain component
+
+    Linear1DSet[] lset = new Linear1DSet[numDom];
+    boolean keepConstant = false;
+    int numVal = numRng; 
+    if (numDom == 1) numVal = numSamples;
+    if (numDom == 2 && numRng == 1 && numElements > 1) numVal = numElements;
+    if (numDom > 2 && numRng == 1 && numElements == 1) {
+      numVal = numSamples / (2 * numDom);
+      keepConstant = true;
+    }
+
+    for (int i=0; i<numDom; i++) {
+
+      if (gotDomainRanges[i]) {
+        // if domain was given with a count, use it for 'raster'-type
+        if (numDom == 2 && numRng == 1 && numElements == 1) 
+                                   numVal = (int) domainRanges[2][i]; 
+
+        lset[i] = new Linear1DSet(domType.getComponent(i), domainRanges[0][i], 
+                            domainRanges[1][i], numVal);
+
+        if (debug) System.out.println("lset from domain = "+lset[i]);
+
+      } else if (domainPointer[i] == -1 ) {
+        lset[i] = new Linear1DSet(0., (double)(numVal-1), numVal);
+
+        if (debug) System.out.println("lset from range = "+lset[i]);
+
+      } else {
+        lset[i] = null;
+      }
+
+      if (!keepConstant) numVal = numSamples; 
+    }
+
+
+    // now make up the actual domain sets for the function
+    Set domain = null;
+
+    if (numDom == 1) {  // for 1-D domains
+
+      if (lset[0] == null) {
+        domain = createAppropriate1DDomain(domType, numSamples, domainValues);
+
+      } else {
+        domain = lset[0];
+      }
+
+    } else if (numDom == 2) {  // for 2-D domains
+
+      if (lset[0] != null && lset[1] != null) {
+        domain = new Linear2DSet(domType, lset);
+
+      } else {
+        float[][] samples = new float[numDom][numSamples];
+
+        for (int k = 0; k < numDom; k++) {
+          if (lset[k] == null) {
+            samples[k] = (getDomSamples(k, numSamples, domainValues))[0];
+          } else {
+            samples[k] = (lset[k].getSamples())[0];
+          }
+
+        }
+
+        domain = (Set) new Irregular2DSet(domType, samples);
+      }
+        
+    } else if (numDom == 3) {  // for 3-D domains
+    
+      if (lset[0] != null && lset[1] != null && lset[2] != null) {
+        domain = new Linear3DSet(domType, lset);
+
+      } else {
+        float[][] samples = new float[numDom][numSamples];
+
+        for (int k = 0; k < numDom; k++) {
+          if (lset[k] == null) {
+            samples[k] = (getDomSamples(k, numSamples, domainValues))[0];
+          } else {
+            samples[k] = (lset[k].getSamples())[0];
+          }
+
+        }
+
+        domain = (Set) new Irregular3DSet(domType, samples);
+      }
+
+    } else {  // N-D domains (can only use LinearSets!!
+
+      boolean allLinear = true;
+      for (int k = 0; k<numDom; k++) {
+        if (lset[k] == null) allLinear = false;
+      }
+
+      if (allLinear) {
+        if (debug) System.out.println("####   Making LinearNDset");
+        domain = new LinearNDSet(domType, lset);
+
+      } else { 
+        if (debug) System.out.println("####   Making IrregularSet");
+        float[][] samples = new float[numDom][numSamples];
+
+        for (int k=0; k<numDom; k++) {
+          if (lset[k] == null) {
+            samples[k] = (getDomSamples(k, numSamples, domainValues))[0];
+          } else {
+            samples[k] = (lset[k].getSamples())[0];
+          }
+        }
+
+        domain = new IrregularSet(domType, samples);
+      }
+    }
+
+
+
+    try {
+      ff = new FlatField((FunctionType) mt, domain, 
+                                null, null, rangeSets, rangeUnits);
+
+    } catch (FieldException fe) {
+      field = new FieldImpl((FunctionType) mt, domain);
+    } catch (UnitException fe) {
+      System.out.println("####  Problem with Units; attempting to make Field anyway");
+      field = new FieldImpl((FunctionType) mt, domain);
+    }
+//*************************************************
+    if (debug) {
+      if (ff != null) {
+        System.out.println("ff.Length "+ff.getLength());
+        System.out.println("ff.getType "+ff.getType());
+      }
+      if (field != null) {
+        System.out.println("field.Length "+field.getLength());
+        System.out.println("field.getType "+field.getType());
+      }
+      System.out.println("domain = "+domain);
+      System.out.println("size of a = "+numRng+" x "+(numSamples*numElements));
+    }
+//*************************************************
+
+    double[][]a = new double[numRng][numSamples * numElements];
+    Tuple[] at = new Tuple[numSamples];
+    
+    // if this is a raster then the samples are in a slightly
+    // difielderent form ...
+
+    if (isRaster) {
+      int samPointer = 0;
+      for (int i=0; i<numSamples; i++) {
+        double[] rs = (double[])(rangeValues.get(i));
+        for (int j=0; j<numElements; j++) {
+          a[0][samPointer] = rs[j];
+          samPointer ++;
+        }
+      }
+    } else {
+      for (int i=0; i<numSamples; i++) {
+        double[] rs = (double[])(rangeValues.get(i));
+        for (int j=0; j<numRng; j++) {
+          a[j][i] = rs[j];
+        }
+        if (!tupleValues.isEmpty()) {
+          at[i] = (Tuple) tupleValues.get(i); 
+        }
+      }
+    }
+
+
+
+// set samples
+    if (debug) System.out.println("about to field.setSamples");
+    try {
+    if (ff != null) {
+      if (debug) System.out.println("####   ff is not null");
+      ff.setSamples(a, false);
+      field = (Field) ff;
+
+    } else {
+      if (debug) System.out.println("####   ff is null..use FieldImpl");
+      field.setSamples(at, false);
+    }
+    } catch (Exception ffe) {ffe.printStackTrace(); }
+      
+
+    // make up error estimates and set them
+    ErrorEstimate[] es = new ErrorEstimate[numRng];
+    for (int i=0; i<numRng; i++) {
+      es[i] = new ErrorEstimate(a[i], rangeErrorEstimates[i], rangeUnits[i]);
+    }
+    try {
+        ((FlatField) field).setRangeErrors(es); 
+    } catch (FieldException fe) {
+        if (debug) System.out.println("caught "+fe);
+        // not a flatfield
+        // don't setRangeErrors
+    } catch (ClassCastException cce) {
+        if (debug) System.out.println("caught "+cce);
+        // not a flatfield
+        // don't setRangeErrors
+    }
+
+    if (debug) {
+      new visad.jmet.DumpType().dumpDataType(field,System.out);
+      System.out.println("field = "+field);
+    }
+
+    bis.close();
+
+  }
+
+  // munges a pseudo MathType string into something legal
+
+  private String makeMT(String s) {
+
+    int k = s.indexOf("->");
+    if (k < 0) {
+        //      System.out.println("TextAdapter: invalid MathType form; -> required");
+      return null;
+    }
+
+    StringBuffer sb = new StringBuffer("");
+    for (int i=0; i<s.length(); i++) {
+      String r = s.substring(i,i+1);
+      if (!r.equals(" ") && !r.equals("\t") && !r.equals("\n")) {
+              sb.append(r);
+      }
+    }
+
+    String t = sb.toString();
+    k = t.indexOf("->");
+
+    if (t.charAt(k-1) != ')' ) {
+      if (t.charAt(k+2) != '(' ) {
+        String t2 = "("+t.substring(0,k) + ")->("+t.substring(k+2)+")";
+        t = t2;
+      } else {
+        String t2 = "("+t.substring(0,k) + ")"+t.substring(k);
+        t = t2;
+      }
+
+    } else if (t.charAt(k+2) != '(' ) {
+      String t2 = t.substring(0,k+2)+"("+t.substring(k+2)+")";
+      t = t2;
+    }
+
+    if (!t.startsWith("((") ) {
+      String t2= "("+t+")";
+      t = t2;
+    }
+
+    return t;
+  }
+
+  private static final boolean isText(String s)
+  {
+    final int len = (s == null ? -1 : s.length());
+
+    if (len <= 0) {
+      // well, it's not really *binary*, so pretend it's text
+      return true;
+    }
+
+    for (int i = 0; i < len; i++) {
+      final char ch = s.charAt(i);
+      if (Character.isISOControl(ch) && !Character.isWhitespace(ch)) {
+        // we might want to special-case formfeed/linefeed/newline here...
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  /** 
+   * generate a DateTime from a string
+   * @param string - Formatted date/time string
+   *
+   * @return - the equivalent VisAD DateTime for the string
+   *
+   * (lifted from au.gov.bom.aifs.common.ada.VisADXMLAdapter.java)
+   */
+  private static visad.DateTime makeDateTimeFromString(String string, 
+                                                       String format, String tz)
+    throws java.text.ParseException
+  {
+    visad.DateTime dt = null;
+    // try to parse the string using the supplied DateTime format
+    try {
+      if(dateParsers!=null) {
+         for(int i=0;i<dateParsers.size();i++) {
+             DateParser dateParser = (DateParser) dateParsers.get(i);
+             dt = dateParser.createDateTime(string, format, TimeZone.getTimeZone(tz));
+             if(dt !=null) {
+                 return dt;
+             }
+          }
+       }
+
+      String key = format+"__" + tz;
+      SimpleDateFormat sdf = (SimpleDateFormat) formats.get(key);
+      if(sdf == null) {
+          sdf = new SimpleDateFormat();
+          sdf.setTimeZone(TimeZone.getTimeZone(tz));
+          sdf.applyPattern(format);
+          formats.put(key,sdf);
+      }
+      Date d = sdf.parse(string);
+      dt = new DateTime(d);
+      //       dt = visad.DateTime.createDateTime(string, format, TimeZone.getTimeZone(tz));
+    } catch (VisADException e) {}
+    if (dt==null) {
+      throw new java.text.ParseException("Couldn't parse visad.DateTime from \""
+                                          +string+"\"", -1);
+    } else {
+      return dt;
+    }
+  }
+
+
+  /**  A set of cached simpledateformats  */
+  private static Hashtable formats = new Hashtable();
+
+  /** This list of DateFormatter-s will be checked when we are making a DateTime wiht a given format */
+  private static List dateParsers;
+
+  /** used to allow applications to define their own date parsing */
+  public static interface DateParser {
+        /** If this particular DateParser does not know how to handle the give  format then this method should return null */
+      public DateTime createDateTime(String value, String format, TimeZone timezone) throws VisADException;
+  }
+
+
+  /** used to allow applications to define their own date parsing */
+  public static void addDateParser(DateParser dateParser) {
+      if(dateParsers==null) {
+          dateParsers  = new ArrayList();
+      }
+      dateParsers.add(dateParser);
+  }
+
+
+
+  double getVal(String s, int k) {
+    int i = values_to_index[2][k];
+    if (i < 0 || s == null || s.length()<1 || (infos[i].missingString!=null && s.equals(infos[i].missingString))) {
+      return Double.NaN;
+    }
+    HeaderInfo info  = infos[i];
+
+    // try parsing as a double first
+    if (info.formatString == null) {
+      // no format provided : parse as a double
+      try {
+        double v;
+        try {
+          v = Double.parseDouble(s);
+        } catch (java.lang.NumberFormatException nfe1) {
+            //If units are degrees then try to decode this as a lat/lon
+            // We should probably not rely on throwing an exception to handle this but...
+            if(info.unit !=null && Unit.canConvert(info.unit, visad.CommonUnit.degree)) {
+                v=decodeLatLon(s);
+            } else {
+                throw nfe1;
+            }
+            if(v!=v) throw new java.lang.NumberFormatException(s);
+        }
+        if (v == info.missingValue) {
+          return Double.NaN;
+        }
+        v = v * info.scale + info.offset;
+        return v;
+      } catch (java.lang.NumberFormatException ne) {
+        System.out.println("Invalid number format for "+s);
+      }
+    } else {
+      // a format was specified: only support DateTime format 
+      // so try to parse as a DateTime
+      try{
+        visad.DateTime dt = makeDateTimeFromString(s, info.formatString, info.tzString);
+        return dt.getReal().getValue();
+      } catch (java.text.ParseException pe) {
+        System.out.println("Invalid number/time format for "+s);
+      }
+    }
+    return Double.NaN;
+  }
+
+  // get the samples from the ArrayList.
+  float[][] getDomSamples(int comp, int numDomValues, ArrayList domValues) {
+    float [][] a = new float[1][numDomValues];
+    for (int i=0; i<numDomValues; i++) {
+      double[] d = (double[])(domValues.get(i));
+      a[0][i] = (float)d[comp];
+    }
+    return a;
+  }
+
+  /** get the data
+  * @return a Field of the data read from the file
+  *
+  */
+  public Field getData() {
+    return field;
+  }
+
+  /**
+   * Returns an appropriate 1D domain.
+   *
+   * @param type the math-type of the domain
+   * @param numSamples the number of samples in the domain
+   * @param domValues domain values are extracted from this array list.
+   *
+   * @return a Linear1DSet if the domain samples form an arithmetic
+   *   progression, a Gridded1DDoubleSet if the domain samples are ordered
+   *   but do not form an arithmetic progression, otherwise an Irregular1DSet.
+   *
+   * @throws VisADException there was a problem creating the domain set.
+   */
+  private Set createAppropriate1DDomain(MathType type, int numSamples,
+                                       ArrayList domValues)
+                                         throws VisADException {
+
+    if (0 == numSamples) {
+      // Can't create a domain set with zero samples.
+      return null;
+    }
+
+    // Extract the first element from each element of the array list.
+    double[][] values = new double[1][numSamples];
+    for (int i=0; i<numSamples; ++i) {
+      double[] d = (double []) domValues.get(i);
+      values[0][i] = d[0];
+    }
+
+    // This implementation for testing that the values are ordered
+    // is based on visad.Gridded1DDoubleSet.java
+    boolean ordered = true;
+    boolean ascending = values[0][numSamples -1] > values[0][0];
+    if (ascending) {
+      for (int i=1; i<numSamples; ++i) {
+        if (values[0][i] < values[0][i - 1]) {
+          ordered = false;
+          break;
+        }
+      }
+    } else {
+      for (int i=1; i<numSamples; ++i) {
+        if (values[0][i] > values[0][i - 1]) {
+          ordered = false;
+          break; 
+        }
+      }
+    }
+
+    Set set = null;
+
+    if (ordered) {
+      ArithProg arithProg = new ArithProg();
+      if (arithProg.accumulate(values[0])) {
+        // The domain values form an arithmetic progression (ordered and
+        // equally spaced) so use a linear set.
+        set = new Linear1DSet(type, values[0][0], values[0][numSamples - 1],
+          numSamples);
+      } else {
+        // The samples are ordered, so use a gridded set.
+        set = new Gridded1DDoubleSet(type, values, numSamples);
+      }
+    } else {
+      set = new Irregular1DSet(type, Set.doubleToFloat(values));
+    }
+
+    return set;
+  }
+
+
+    private static class HeaderInfo {
+        String  name;
+        Unit    unit;
+        double  missingValue = Double.NaN;
+        String  missingString;
+        String  formatString; 
+        String  tzString = "GMT";
+        int     isInterval = 0;
+        double  errorEstimate=0;
+        double  scale=1.0;
+        double  offset=0.0;
+        String  fixedValue;
+        int     colspan = 1;
+	boolean isText = false;
+
+        public boolean isParam(String param) {
+            return name.equals(param)  || name.equals(param+"(Text)");
+        }
+        public String toString() {
+            return name;
+        }
+
+
+    }
+
+
+    /**
+     * Read in  the given file and return the processed data
+     *
+     * @param file The file to read in
+     * @return the data
+     */
+    public static Data processFile(String file) throws Exception {
+        TextAdapter ta = new TextAdapter(file);
+        System.out.println(ta.getData().getType());
+        return ta.getData();
+    }
+
+
+
+//  uncomment to test
+
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0) {
+      System.out.println("Must supply a filename");
+      System.exit(1);
+    }
+    TextAdapter ta = new TextAdapter(args[0]);
+    System.out.println(ta.getData().getType());
+    new visad.jmet.DumpType().dumpMathType(ta.getData().getType(),System.out);
+    new visad.jmet.DumpType().dumpDataType(ta.getData(),System.out);
+    System.out.println("####  Data = "+ta.getData());
+    System.out.println("EOF... ");
+  }
+
+
+
+
+    /**
+     * A cut-and-paste from the IDV Misc method
+     * Decodes a string representation of a latitude or longitude and
+     * returns a double version (in degrees).  Acceptible formats are:
+     * <pre>
+     * +/-  ddd:mm, ddd:mm:, ddd:mm:ss, ddd::ss, ddd.fffff ===>   [+/-] ddd.fffff
+     * +/-  ddd, ddd:, ddd::                               ===>   [+/-] ddd
+     * +/-  :mm, :mm:, :mm:ss, ::ss, .fffff                ===>   [+/-] .fffff
+     * +/-  :, ::                                          ===>       0.0
+     * Any of the above with N,S,E,W appended
+     * </pre>
+     *
+     * @param latlon  string representation of lat or lon
+     * @return the decoded value in degrees
+     */
+    public static double decodeLatLon(String latlon) {
+        // first check to see if there is a N,S,E,or W on this
+        latlon = latlon.trim();
+        int    dirIndex    = -1;
+        int    southOrWest = 1;
+        double value       = Double.NaN;
+        if (latlon.indexOf("S") > 0) {
+            southOrWest = -1;
+            dirIndex    = latlon.indexOf("S");
+        } else if (latlon.indexOf("W") > 0) {
+            southOrWest = -1;
+            dirIndex    = latlon.indexOf("W");
+        } else if (latlon.indexOf("N") > 0) {
+            dirIndex = latlon.indexOf("N");
+        } else if (latlon.indexOf("E") > 0) {
+            dirIndex = latlon.indexOf("E");
+        }
+        if (dirIndex > 0) {
+            latlon = latlon.substring(0, dirIndex).trim();
+        }
+
+        // now see if this is a negative value
+        if (latlon.indexOf("-") == 0) {
+            southOrWest *= -1;
+            latlon      = latlon.substring(latlon.indexOf("-") + 1).trim();
+        }
+
+        if (latlon.indexOf(":") >= 0) {  //have something like DD:MM:SS, DD::, DD:MM:, etc
+            int    firstIdx = latlon.indexOf(":");
+            String hours    = latlon.substring(0, firstIdx);
+            String minutes  = latlon.substring(firstIdx + 1);
+            String seconds  = "";
+            if (minutes.indexOf(":") >= 0) {
+                firstIdx = minutes.indexOf(":");
+                String temp = minutes.substring(0, firstIdx);
+                seconds = minutes.substring(firstIdx + 1);
+                minutes = temp;
+            }
+            try {
+
+                value = (hours.equals("") == true)
+                        ? 0
+                        : Double.parseDouble(hours);
+                if ( !minutes.equals("")) {
+                    value += Double.parseDouble(minutes) / 60.;
+                }
+                if ( !seconds.equals("")) {
+                    value += Double.parseDouble(seconds) / 3600.;
+                }
+            } catch (NumberFormatException nfe) {
+                value = Double.NaN;
+            }
+        } else {  //have something like DD.ddd
+            try {
+                value = Double.parseDouble(latlon);
+            } catch (NumberFormatException nfe) {
+                value = Double.NaN;
+            }
+        }
+        return value * southOrWest;
+    }
+
+
+    public interface StreamProcessor {
+        public void processValues(Data[] tuple) throws VisADException ;
+    }
+
+
+
+
+}
diff --git a/visad/data/text/TextForm.java b/visad/data/text/TextForm.java
new file mode 100644
index 0000000..50ac128
--- /dev/null
+++ b/visad/data/text/TextForm.java
@@ -0,0 +1,118 @@
+//
+// TextForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.text;
+
+import java.io.IOException;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.UnimplementedException;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.Form;
+import visad.data.FormNode;
+import visad.data.FormFileInformer;
+
+/** Defines the form of text data files for VisAD
+  * 
+  * These may have filename extentions of: 
+  * .csv - comma-separated values
+  * .tsv - tab-separated values
+  * .bsv - blank-separated values
+  * .txt - (delimiter chosen from the characters: tab, comma, blank
+  *        in that order)
+*/
+
+public class TextForm
+	extends Form
+	implements FormFileInformer
+{
+  public TextForm()
+  {
+    super("TextForm");
+  }
+
+  public boolean isThisType(String name)
+  {
+    return ( (name.endsWith(".csv") || name.endsWith(".CSV") )
+    || (name.endsWith(".tsv") || name.endsWith(".TSV") )
+    || (name.endsWith(".txt") || name.endsWith(".TXT") )
+    || (name.endsWith(".bsv") || name.endsWith(".BSV") ) );
+  }
+
+  public boolean isThisType(byte[] block)
+  {
+    return false;
+  }
+
+  public String[] getDefaultSuffixes()
+  {
+    String[] suff = { "csv","tsv","txt","bsv" };
+    return suff;
+  }
+
+  public synchronized void save(String id, Data data, boolean replace)
+	throws  BadFormException, IOException, RemoteException, VisADException
+  {
+    throw new UnimplementedException("Can't yet save Text objects");
+  }
+
+  public synchronized void add(String id, Data data, boolean replace)
+	throws BadFormException
+  {
+    throw new RuntimeException("Can't yet add Text objects");
+  }
+
+  public synchronized DataImpl open(String path)
+	throws BadFormException, RemoteException, VisADException
+  {
+    try {
+      // jk
+      return (DataImpl) new TextAdapter(path).getData();
+    } catch (IOException e) {
+      throw new VisADException("IOException: " + e.getMessage());
+    }
+  }
+
+  public synchronized DataImpl open(URL url)
+	throws BadFormException, VisADException, IOException
+  {
+    TextAdapter csva = new TextAdapter(url);
+    // jk
+    return (DataImpl) csva.getData();
+  }
+
+  public synchronized FormNode getForms(Data data)
+  {
+    throw new RuntimeException("Can't yet get Text File forms");
+  }
+}
diff --git a/visad/data/text/example1.txt b/visad/data/text/example1.txt
new file mode 100644
index 0000000..f531a29
--- /dev/null
+++ b/visad/data/text/example1.txt
@@ -0,0 +1,42 @@
+# In this example, a 2-D grid of values is specified. The domain
+# components use well-known types (Latitude, Longitude) which can
+# be used in conjuction with basemaps, for example.  Note also
+# that the "tvalue" is given a unit of degree Celcius.
+(Longitude,Latitude)->(tvalue)
+Longitude(-130:-40),  Latitude(20:60), skip, tvalue[unit=degC miss=99]
+0   0   17   34   50   64   76   86   93   98   99 
+1   17   34   50   64   76   86   93   98   99   98 
+2   34   50   64   76   86   93   98   99   98   93 
+3   50   64   76   86   93   98   99   98   93   86 
+4   64   76   86   93   98   99   98   93   86   76 
+5   76   86   93   98   99   98   93   86   76   64 
+6   86   93   98   99   98   93   86   76   64   49 
+7   93   98   99   98   93   86   76   64   49   34 
+8   98   99   98   93   86   76   64   49   34   17 
+9   99   98   93   86   76   64   49   34   17   0 
+10   98   93   86   76   64   49   34   17   0   -17 
+11   93   86   76   64   49   34   17   0   -17   -34 
+12   86   76   64   49   34   17   0   -17   -34   -50 
+13   76   64   49   34   17   0   -17   -34   -50   -64 
+14   64   49   34   17   0   -17   -34   -50   -64   -76 
+15   49   34   17   0   -17   -34   -50   -64   -76   -86 
+16   34   17   0   -17   -34   -50   -64   -76   -86   -93 
+17   17   0   -17   -34   -50   -64   -76   -86   -93   -98 
+18   0   -17   -34   -50   -64   -76   -86   -93   -98   -99 
+19   -17   -34   -50   -64   -76   -86   -93   -98   -99   -98 
+20   -34   -50   -64   -76   -86   -93   -98   -99   -98   -93 
+21   -50   -64   -76   -86   -93   -98   -99   -98   -93   -86 
+22   -64   -76   -86   -93   -98   -99   -98   -93   -86   -76 
+23   -76   -86   -93   -98   -99   -98   -93   -86   -76   -64 
+24   -86   -93   -98   -99   -98   -93   -86   -76   -64   -49 
+25   -93   -98   -99   -98   -93   -86   -76   -64   -49   -34 
+26   -98   -99   -98   -93   -86   -76   -64   -49   -34   -17 
+27   -99   -98   -93   -86   -76   -64   -49   -34   -17   0 
+28   -98   -93   -86   -76   -64   -49   -34   -17   0   17 
+29   -93   -86   -76   -64   -49   -34   -17   0   17   34 
+30   -86   -76   -64   -49   -34   -17   0   17   34   50 
+31   -76   -64   -49   -34   -17   0   17   34   50   64 
+32   -64   -49   -34   -17   0   17   34   50   64   76 
+33   -49   -34   -17   0   17   34   50   64   76   86 
+34   -34   -17   0   17   34   50   64   76   86   93 
+35   -17   0   17   34   50   64   76   86   93   98 
diff --git a/visad/data/text/example2.csv b/visad/data/text/example2.csv
new file mode 100644
index 0000000..61b1c79
--- /dev/null
+++ b/visad/data/text/example2.csv
@@ -0,0 +1,41 @@
+# In this example, the function has two range components, value1 and
+# value2.  These are defined in a 1D domain named x, and the values
+# of x are read from the file as well.
+(x)->(value1, value2)
+x,value1,skip,skip,skip,skip,value2
+100 , 0 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 
+101 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 , 98 
+102 , 34 , 50 , 64 , 76 , 86 , 93 , 98 , 99 , 98 , 93 
+103 , 50 , 64 , 76 , 86 , 93 , 98 , 99 , 98 , 93 , 86 
+104 , 64 , 76 , 86 , 93 , 98 , 99 , 98 , 93 , 86 , 76 
+105 , 76 , 86 , 93 , 98 , 99 , 98 , 93 , 86 , 76 , 64 
+106 , 86 , 93 , 98 , 99 , 98 , 93 , 86 , 76 , 64 , 49 
+107 , 93 , 98 , 99 , 98 , 93 , 86 , 76 , 64 , 49 , 34 
+108 , 98 , 99 , 98 , 93 , 86 , 76 , 64 , 49 , 34 , 17 
+109 , 99 , 98 , 93 , 86 , 76 , 64 , 49 , 34 , 17 , 0 
+110 , 98 , 93 , 86 , 76 , 64 , 49 , 34 , 17 , 0 , -17 
+111 , 93 , 86 , 76 , 64 , 49 , 34 , 17 , 0 , -17 , -34 
+112 , 86 , 76 , 64 , 49 , 34 , 17 , 0 , -17 , -34 , -50 
+113 , 76 , 64 , 49 , 34 , 17 , 0 , -17 , -34 , -50 , -64 
+114 , 64 , 49 , 34 , 17 , 0 , -17 , -34 , -50 , -64 , -76 
+115 , 49 , 34 , 17 , 0 , -17 , -34 , -50 , -64 , -76 , -86 
+116 , 34 , 17 , 0 , -17 , -34 , -50 , -64 , -76 , -86 , -93 
+117 , 17 , 0 , -17 , -34 , -50 , -64 , -76 , -86 , -93 , -98 
+118 , 0 , -17 , -34 , -50 , -64 , -76 , -86 , -93 , -98 , -99 
+119 , -17 , -34 , -50 , -64 , -76 , -86 , -93 , -98 , -99 , -98 
+120 , -34 , -50 , -64 , -76 , -86 , -93 , -98 , -99 , -98 , -93 
+121 , -50 , -64 , -76 , -86 , -93 , -98 , -99 , -98 , -93 , -86 
+122 , -64 , -76 , -86 , -93 , -98 , -99 , -98 , -93 , -86 , -76 
+123 , -76 , -86 , -93 , -98 , -99 , -98 , -93 , -86 , -76 , -64 
+124 , -86 , -93 , -98 , -99 , -98 , -93 , -86 , -76 , -64 , -49 
+125 , -93 , -98 , -99 , -98 , -93 , -86 , -76 , -64 , -49 , -34 
+126 , -98 , -99 , -98 , -93 , -86 , -76 , -64 , -49 , -34 , -17 
+127 , -99 , -98 , -93 , -86 , -76 , -64 , -49 , -34 , -17 , 0 
+128 , -98 , -93 , -86 , -76 , -64 , -49 , -34 , -17 , 0 , 17 
+129 , -93 , -86 , -76 , -64 , -49 , -34 , -17 , 0 , 17 , 34 
+130 , -86 , -76 , -64 , -49 , -34 , -17 , 0 , 17 , 34 , 50 
+131 , -76 , -64 , -49 , -34 , -17 , 0 , 17 , 34 , 50 , 64 
+132 , -64 , -49 , -34 , -17 , 0 , 17 , 34 , 50 , 64 , 76 
+133 , -49 , -34 , -17 , 0 , 17 , 34 , 50 , 64 , 76 , 86 
+134 , -34 , -17 , 0 , 17 , 34 , 50 , 64 , 76 , 86 , 93 
+135 , -17 , 0 , 17 , 34 , 50 , 64 , 76 , 86 , 93 , 98 
diff --git a/visad/data/text/example3.txt b/visad/data/text/example3.txt
new file mode 100644
index 0000000..80761c0
--- /dev/null
+++ b/visad/data/text/example3.txt
@@ -0,0 +1,14 @@
+# In this example, we create a simple "list of cities" which
+# can then be plotted on a basemap, for example.  Note in particular
+# the use of the "(Text)" tag, which denotes non-numeric range values
+# (which must be enclosed in quote marks in the data file)
+(Longitude, Latitude) -> (City(Text))
+Latitude, Longitude, City(Text)
+-12.4, 130.8, Darwin
+-16.9, 145.7, "Cairns"
+-23.8, 133.9, "Alice Springs"
+-31.9, 115.9, "Perth"
+-33.9, 151.2, "Sydney"
+-37.7, 144.9, "Melbourne"
+-35.3, 149.2, "Canberra"
+-34.8, 138.6, "Adelaide"
diff --git a/visad/data/text/example4.csv b/visad/data/text/example4.csv
new file mode 100644
index 0000000..c7cafa5
--- /dev/null
+++ b/visad/data/text/example4.csv
@@ -0,0 +1,9 @@
+# MathType of Data 
+# (ie a time seris of lat/lon/pressures, eg a Tropical Cyclone Track)
+(Time->(Latitude,Longitude,Pressure))
+# Column Headings, including Time with a SimpleDateFormat string specified
+Time[fmt=yyyy-MM-dd'T'HH:mm:ss'Z'],Latitude,Longitude,Pressure
+# Start of Data
+2003-05-02T07:00:00Z,-10.0,150.0,998.0
+2003-05-02T10:00:00Z,-10.5,150.5,997.0
+2003-05-02T13:00:00Z,-11.0,151.0,996.0
diff --git a/visad/data/text/testcolspan.csv b/visad/data/text/testcolspan.csv
new file mode 100644
index 0000000..5b74fef
--- /dev/null
+++ b/visad/data/text/testcolspan.csv
@@ -0,0 +1,9 @@
+##
+##This is an example of using the fixed values 
+##
+
+(index) -> (Longitude,Latitude,Time,SPD,DIR,TD,T)
+Longitude[unit="degrees west"],Latitude[unit="deg"],Time[fmt="yyyy-MM-dd HH:mm:ss z" colspan="2"],SPD[unit="m/s"],DIR[unit="deg"],TD[unit="celsius"],T[unit="celsius"]
+40,110,2007-02-20, 11:00:00 MST  ,0.0,0.0,8.9,13.3
+50,100,2007-02-20, 12:00:00 MST  ,0.0,0.0,11.9,15.0
+50,110,2007-02-20, 11:00:00 MST  ,10.8,230.0,-1.1,6.7
diff --git a/visad/data/text/testfixedvalue.csv b/visad/data/text/testfixedvalue.csv
new file mode 100644
index 0000000..1bc8fa0
--- /dev/null
+++ b/visad/data/text/testfixedvalue.csv
@@ -0,0 +1,23 @@
+##
+##This is an example of using the fixed values 
+##
+
+(index) -> (Longitude,Latitude,Time,ST(Text),SPD,DIR,TD,T)
+Longitude[unit="degrees west"],Latitude[unit="deg"],Time[fmt="yyyy-MM-dd HH:mm:ss z"],ST(Text),SPD[unit="m/s"],DIR[unit="deg"],TD[unit="celsius"],T[unit="celsius"]
+
+Longitude=117.1
+Latitude=32.9
+ST=CA
+2007-02-20 11:00:00 MST  ,0.0,0.0,8.9,13.3
+2007-02-20 12:00:00 MST  ,0.0,0.0,11.9,15.0
+
+Longitude=92.5
+Latitude=48.3
+ST=MN
+2007-02-20 11:00:00 MST  ,1.5,160.0,-7.0,-2.0
+2007-02-20 12:00:00 MST  ,1.5,160.0,-7.0,-2.0
+
+Longitude=121.2
+Latitude=44.3
+ST=OR
+2007-02-20 11:00:00 MST  ,10.8,230.0,-1.1,6.7
diff --git a/visad/data/tiff/LegacyBitBuffer.java b/visad/data/tiff/LegacyBitBuffer.java
new file mode 100644
index 0000000..8e5609c
--- /dev/null
+++ b/visad/data/tiff/LegacyBitBuffer.java
@@ -0,0 +1,201 @@
+//
+// LegacyBitBuffer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.tiff;
+
+import java.io.*;
+
+/**
+ * A class for reading arbitrary numbers of bits from an input stream.
+ * @author Eric Kjellman egkjellman at wisc.edu
+ *
+ * @deprecated Use loci.formats.codec.BitBuffer
+ */
+public class LegacyBitBuffer {
+
+  private static final int BUFFER_SIZE = 8192;
+
+  private InputStream in;
+  private int currentByte;
+  private int currentBit;
+  private byte[] byteBuffer;
+  private int eofByte;
+  private int[] backMask;
+  private int[] frontMask;
+  private boolean eofFlag;
+
+  public LegacyBitBuffer(InputStream i) throws IOException {
+    byteBuffer = new byte[BUFFER_SIZE];
+    in = i;
+    currentByte = 0;
+    currentBit = 0;
+    eofByte = in.read(byteBuffer);
+    // System.out.println(" eofByte: " + eofByte);
+    eofFlag = false;
+    if (eofByte < 1) {
+      eofFlag = true;
+    }
+    backMask = new int[] {0x0000, 0x0001, 0x0003, 0x0007,
+                          0x000F, 0x001F, 0x003F, 0x007F};
+    frontMask = new int[] {0x0000, 0x0080, 0x00C0, 0x00E0,
+                           0x00F0, 0x00F8, 0x00FC, 0x00FE};
+  }
+
+  public long skipBits(long bitsToSkip) throws IOException {
+    long skipBytes = (long) bitsToSkip / 8;
+    long skipBits = bitsToSkip % 8;
+    long newByte = currentByte + skipBytes;
+    long newBit = currentBit + skipBits;
+    long toReturn = bitsToSkip;
+    if (newBit > 8) {
+      newBit -= 8;
+      newByte++;
+    }
+    if (newByte >= eofByte) {
+      // The byte to skip to is out of the current block.
+      if (eofByte != BUFFER_SIZE) {
+        // meaning yeah, we actually reached the end of the file.
+//        System.out.println("1");
+        eofFlag = true;
+        currentByte = eofByte;
+        currentBit = 0;
+        toReturn = (8 - currentBit) + 8 * (eofByte - currentByte);
+      }
+      else {
+        // meaning maybe we haven't, but we don't know, so trying to skip the
+        // correct number of bytes.
+//        System.out.println("2");
+        newByte -= BUFFER_SIZE; // need to account for the current buffer.
+        long skipped = -1;
+        // This part may not suffice. Why would in.skip() fail?
+        while(skipped != 0) {
+          skipped = in.skip(newByte);
+          newByte -= skipped;
+        }
+        if (newByte != 0) {
+          // When we are unable to skip all of the bytes, the
+          // file is assumed to be finished.
+//          System.out.println("3");
+          eofFlag = true;
+        }
+        else {
+          // Otherwise, we have bytes we can still read:
+//          System.out.println("4");
+          currentByte = 0;
+          currentBit = (int) newBit;
+          eofByte = in.read(byteBuffer);
+        }
+      }
+    }
+    else {
+      // The byte to skip to is in the current block, and readable
+      currentByte = (int) newByte;
+      currentBit = (int) newBit;
+    }
+    return toReturn;
+  }
+
+  public int getBits(int bitsToRead)
+    throws IOException, FileNotFoundException
+  {
+    if (bitsToRead == 0) {
+      return 0;
+    }
+    if (eofFlag) {
+      return -1; // Already at end of file
+    }
+    int toStore = 0;
+    while(bitsToRead != 0  && !eofFlag) {
+//      System.out.println("byte: " + currentByte + " bit: " + currentBit);
+      if (bitsToRead >= 8 - currentBit) {
+        if (currentBit == 0) { // special
+          toStore = toStore << 8;
+          int cb = ((int) byteBuffer[currentByte]);
+          toStore += (cb<0 ? (int) 256 + cb : (int) cb);
+          bitsToRead -= 8;
+          currentByte++;
+        }
+        else {
+          toStore = toStore << (8 - currentBit);
+          toStore += ((int) byteBuffer[currentByte]) &
+            backMask[8 - currentBit];
+          bitsToRead -= (8 - currentBit);
+          currentBit = 0;
+          currentByte++;
+        }
+      }
+      else {
+//        System.out.println(bitsToRead);
+        toStore = toStore << bitsToRead;
+
+        int cb = ((int) byteBuffer[currentByte]);
+        cb = (cb<0 ? (int) 256 + cb : (int) cb);
+        toStore += ((cb) & (0x00FF - frontMask[currentBit])) >>
+          (8 - (currentBit + bitsToRead));
+//        System.out.println("Byte : " + cb);
+//        System.out.println("Mask : " + (0x00FF - frontMask[currentBit] -
+//          backMask[8 - (currentBit + bitsToRead)]));
+//        System.out.println("Shift: " + (8 - (currentBit + bitsToRead)));
+//        System.out.println("Res 1: " + ((cb) & (0x00FF -
+//          frontMask[currentBit] - backMask[8 - (currentBit + bitsToRead)])));
+//        System.out.println("Res 2: " + (((cb) & (0x00FF -
+//          frontMask[currentBit])) >> (8 - (currentBit + bitsToRead))));
+
+        currentBit += bitsToRead;
+        bitsToRead = 0;
+      }
+      if (currentByte == BUFFER_SIZE) {
+        eofByte = in.read(byteBuffer);
+        currentByte = 0;
+      }
+      if (currentByte == eofByte) {
+        eofFlag = true;
+        return toStore;
+      }
+    }
+    return toStore;
+  }
+
+
+  // -- Main method --
+
+  public static void main(String[] args) throws Exception {
+    LegacyBitBuffer b = new LegacyBitBuffer(
+      new FileInputStream(new File(args[0])));
+    int i = 1;
+    int current = 0;
+    int numBits = Integer.parseInt(args[1]);
+//    System.out.println(args[0]);
+//    System.out.println(args[1]);
+    while(current != -1) {
+      current = b.getBits(numBits);
+      System.out.println(i + ": " + current);
+      i++;
+//      b.skipBits(65536);
+    }
+  }
+
+}
diff --git a/visad/data/tiff/LegacyTiffForm.java b/visad/data/tiff/LegacyTiffForm.java
new file mode 100644
index 0000000..173af7a
--- /dev/null
+++ b/visad/data/tiff/LegacyTiffForm.java
@@ -0,0 +1,538 @@
+//
+// LegacyTiffForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.tiff;
+
+import java.awt.Image;
+import java.awt.image.*;
+import java.lang.reflect.*;
+import java.io.*;
+import java.net.URL;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.data.*;
+import visad.util.*;
+
+/**
+ * LegacyTiffForm is the old VisAD data form for the TIFF file format.
+ * It relies on either ImageJ or JAI being available in the class path,
+ * and is very inefficient when dealing with large multi-page TIFF files.
+ * The following table indicates features that the form supports:<p>
+ *
+ * <table border=1><tr>
+ * <td> </td>
+ * <td><b>uncompressed</b></td>
+ * <td><b>compressed (LZW)</b></td>
+ * </tr><tr>
+ * <td><b>single image</b></td>
+ * <td>read and write</td>
+ * <td>read only (with JAI)</td>
+ * </tr><tr>
+ * <td><b>multi-page</b></td>
+ * <td>read and write</td>
+ * <td>read only (with JAI)</td>
+ * </tr></table><p>
+ *
+ * This form requires ImageJ, available from the
+ * <a href="http://rsb.info.nih.gov/ij/download.html">ImageJ</a> web site.
+ *
+ * Note that features marked with "(with JAI)" also require
+ * the Java Advanced Imaging (JAI) package, available at Sun's
+ * <a href="http://java.sun.com/products/java-media/jai/index.html">
+ * Java Advanced Imaging</a> web site.
+ *
+ * Also, no support for reading TIFF data from URLs is provided.
+ * However, the visad.data.jai package provides limited support for
+ * importing single-image TIFF data from a URL.
+ *
+ * @deprecated Use TiffForm, or visad.data.bio.LociForm
+ *   with loci.formats.in.TiffReader and loci.formats.out.TiffWriter
+ */
+public class LegacyTiffForm extends Form
+  implements FormFileInformer, FormBlockReader, FormProgressInformer
+{
+
+  // -- Static fields --
+
+  /** Counter for TIFF form instantiation. */
+  private static int formCount = 0;
+
+  /** Legal TIFF SUFFIXES. */
+  private static final String[] SUFFIXES = { "tif", "tiff" };
+
+  /** Message produced when attempting to use ImageJ without it installed. */
+  private static final String NO_IJ = "This feature requires ImageJ, " +
+    "available online at http://rsb.info.nih.gov/ij/download.html";
+
+  /** Message produced when attempting to use JAI without it installed. */
+  private static final String NO_JAI = "This feature requires JAI, " +
+    "available from Sun at http://java.sun.com/products/java-media/jai/";
+
+
+  // -- Fields --
+
+  /** Reflection tool for ImageJ and JAI calls. */
+  private ReflectedUniverse r;
+
+  /** Flag indicating ImageJ is not installed. */
+  private boolean noImageJ = false;
+
+  /** Flag indicating JAI is not installed. */
+  private boolean noJai = false;
+
+  /** Filename of current TIFF stack. */
+  private String currentId;
+
+  /** Number of images in current TIFF stack. */
+  private int numImages;
+
+  /** Flag indicating whether ImageJ supports the current TIFF stack. */
+  private boolean canUseImageJ;
+
+  /** Percent complete with current operation. */
+  private double percent;
+
+
+  // -- Constructor --
+
+  /** Constructs a new TIFF file form. */
+  public LegacyTiffForm() {
+    super("LegacyTiffForm" + formCount++);
+    r = new ReflectedUniverse();
+
+    // ImageJ imports
+    try {
+      r.exec("import ij.ImagePlus");
+      r.exec("import ij.ImageStack");
+      r.exec("import ij.io.FileInfo");
+      r.exec("import ij.io.FileSaver");
+      r.exec("import ij.io.Opener");
+      r.exec("import ij.io.TiffDecoder");
+      r.exec("import ij.process.ByteProcessor");
+      r.exec("import ij.process.ColorProcessor");
+      r.exec("import ij.process.FloatProcessor");
+      r.exec("import ij.process.ImageProcessor");
+      r.exec("import ij.process.ShortProcessor");
+    }
+    catch (VisADException exc) { noImageJ = true; }
+
+    // JAI imports
+    try {
+      r.exec("import com.sun.media.jai.codec.ImageDecodeParam");
+      r.exec("import com.sun.media.jai.codec.ImageDecoder");
+      r.exec("import com.sun.media.jai.codec.ImageCodec");
+    }
+    catch (VisADException exc) { noJai = true; }
+  }
+
+
+  // -- FormFileInformer methods --
+
+  /** Checks if the given string is a valid filename for a TIFF file. */
+  public boolean isThisType(String name) {
+    for (int i=0; i<SUFFIXES.length; i++) {
+      if (name.toLowerCase().endsWith(SUFFIXES[i])) return true;
+    }
+    return false;
+  }
+
+  /** Checks if the given block is a valid header for a TIFF file. */
+  public boolean isThisType(byte[] block) {
+    return false;
+  }
+
+  /** Returns the default file SUFFIXES for the TIFF file format. */
+  public String[] getDefaultSuffixes() {
+    String[] s = new String[SUFFIXES.length];
+    System.arraycopy(SUFFIXES, 0, s, 0, SUFFIXES.length);
+    return s;
+  }
+
+  // -- API methods --
+
+  /**
+   * Saves a VisAD Data object to an uncompressed TIFF file.
+   *
+   * @param id        Filename of TIFF file to save.
+   * @param data      VisAD Data to convert to TIFF format.
+   * @param replace   Whether to overwrite an existing file.
+   */
+  public void save(String id, Data data, boolean replace)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    if (noImageJ) throw new BadFormException(NO_IJ);
+
+    percent = 0;
+    FlatField[] fields = DataUtility.getImageFields(data);
+    if (fields == null) {
+      throw new BadFormException(
+        "Data type must be image or time sequence of images");
+    }
+    r.setVar("id", id);
+    if (fields.length > 1) {
+      // save as multi-page TIFF
+      Object is = null;
+      for (int i=0; i<fields.length; i++) {
+        r.setVar("ips", extractImage(fields[i]));
+        if (is == null) {
+          r.exec("w = ips.getWidth()");
+          r.exec("h = ips.getHeight()");
+          r.exec("cm = ips.getColorModel()");
+          r.exec("is = new ImageStack(w, h, cm)");
+          is = r.getVar("is");
+        }
+        r.setVar("si", "" + i);
+
+        // UGLY HACK
+        //
+        // There are two methods:
+        //  - ImageStack.addSlice(String, Object)
+        //  - ImageStack.addSlice(String, ImageProcessor)
+        //
+        // But since addSlice(String, Object) is declared first,
+        // ReflectedUniverse always matches it first, and thus it is
+        // impossible to call addSlice(String, ImageProcessor).
+        //
+        // We must fall back to basic Java reflection to accomplish this...
+
+        //r.exec("is.addSlice(si, ips)");
+        try {
+          Class imageStack = Class.forName("ij.ImageStack");
+          Class imageProcessor = Class.forName("ij.process.ImageProcessor");
+          Method addSlice = imageStack.getMethod("addSlice",
+            new Class[] {String.class, imageProcessor});
+          addSlice.invoke(is, new Object[] {"" + i, r.getVar("ips")});
+        }
+        catch (ClassNotFoundException exc) {
+          throw new BadFormException(
+            "Reflection exception: class not found", exc);
+        }
+        catch (NoSuchMethodException exc) {
+          throw new BadFormException(
+            "Reflection exception: no such method", exc);
+        }
+        catch (IllegalAccessException exc) {
+          throw new BadFormException(
+            "Reflection exception: illegal access", exc);
+        }
+        catch (InvocationTargetException exc) {
+          throw new BadFormException(
+            "Reflection exception", exc.getTargetException());
+        }
+
+        percent = (double) (i + 1) / fields.length;
+      }
+      r.exec("image = new ImagePlus(id, is)");
+      r.exec("sav = new FileSaver(image)");
+      r.exec("sav.saveAsTiffStack(id)");
+    }
+    else {
+      // save as single image TIFF
+      r.setVar("ip", extractImage(fields[0]));
+      r.exec("image = new ImagePlus(id, ip)");
+      r.exec("sav = new FileSaver(image)");
+      r.exec("sav.saveAsTiff(id)");
+    }
+
+    percent = -1;
+  }
+
+  /**
+   * Adds data to an existing TIFF file.
+   *
+   * @exception BadFormException Always thrown (method is not implemented).
+   */
+  public void add(String id, Data data, boolean replace)
+    throws BadFormException
+  {
+    throw new BadFormException("LegacyTiffForm.add");
+  }
+
+  /**
+   * Opens an existing TIFF file from the given filename.
+   *
+   * @return VisAD Data object containing TIFF data.
+   */
+  public DataImpl open(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    percent = 0;
+    int nImages = getBlockCount(id);
+    FieldImpl[] fields = new FieldImpl[nImages];
+    for (int i=0; i<nImages; i++) {
+      fields[i] = (FieldImpl) open(id, i);
+      percent = (double) (i + 1) / nImages;
+    }
+
+    DataImpl data;
+    if (nImages == 1) data = fields[0];
+    else {
+      // combine data stack into time function
+      RealType time = RealType.getRealType("time");
+      FunctionType timeFunction = new FunctionType(time, fields[0].getType());
+      Integer1DSet timeSet = new Integer1DSet(nImages);
+      FieldImpl timeField = new FieldImpl(timeFunction, timeSet);
+      timeField.setSamples(fields, false);
+      data = timeField;
+    }
+    close();
+    percent = -1;
+    return data;
+  }
+
+  /**
+   * Opens an existing TIFF file from the given URL.
+   *
+   * @return VisAD Data object containing TIFF data.
+   *
+   * @exception BadFormException Always thrown (method is not implemented).
+   */
+  public DataImpl open(URL url)
+    throws BadFormException, IOException, VisADException
+  {
+    throw new BadFormException("LegacyTiffForm.open(URL)");
+  }
+
+  public FormNode getForms(Data data) {
+    return null;
+  }
+
+
+  // -- FormBlockReader methods --
+
+  public DataImpl open(String id, int block_number)
+    throws BadFormException, IOException, VisADException
+  {
+    if (!id.equals(currentId)) initFile(id);
+
+    if (block_number < 0 || block_number >= numImages) {
+      throw new BadFormException("Invalid image number: " + block_number);
+    }
+
+    Image img = null;
+
+    if (canUseImageJ) {
+      if (noImageJ) throw new BadFormException(NO_IJ);
+      r.exec("stack = image.getStack()");
+      r.setVar("bn1", block_number + 1);
+      r.exec("ip = stack.getProcessor(bn1)");
+      r.exec("img = ip.createImage()");
+      img = (Image) r.getVar("img");
+    }
+    else {
+      if (noJai) throw new BadFormException(NO_JAI);
+      try {
+        r.setVar("i", block_number);
+        RenderedImage ri =
+          (RenderedImage) r.exec("id.decodeAsRenderedImage(i)");
+        WritableRaster wr = ri.copyData(null);
+        ColorModel cm = ri.getColorModel();
+        img = new BufferedImage(cm, wr, false, null);
+      }
+      catch (VisADException exc) { throw new BadFormException(exc); }
+    }
+    return DataUtility.makeField(img);
+  }
+
+  public int getBlockCount(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    if (!id.equals(currentId)) initFile(id);
+    return numImages;
+  }
+
+  public void close() throws BadFormException, IOException, VisADException { }
+
+
+  // -- FormProgressInformer methods --
+
+  public double getPercentComplete() { return percent; }
+
+
+  // -- Helper methods --
+
+  /**
+   * Converts a FlatField of the form <tt>((x, y) -> value)</tt> or
+   * <tt>((x, y) -> (r, g, b))</tt> to an ImageJ ImageProcessor object.
+   */
+  private Object extractImage(FlatField field) throws VisADException {
+    GriddedSet set = (GriddedSet) field.getDomainSet();
+    int[] wh = set.getLengths();
+    int w = wh[0];
+    int h = wh[1];
+    float[][] samples = field.getFloats(false);
+    r.setVar("w", w);
+    r.setVar("h", h);
+
+    // HACK - detect "fake" 3-color images
+    boolean fake3 = samples.length == 3 &&
+      samples[0] == samples[1] && samples[0] == samples[2];
+
+    if (samples.length == 3 && !fake3) {
+      // 24-bit color is the best we can do
+      int[] pixels = new int[samples[0].length];
+      for (int i=0; i<pixels.length; i++) {
+        int red = (int) samples[0][i] & 0x000000ff;
+        int green = (int) samples[1][i] & 0x000000ff;
+        int blue = (int) samples[2][i] & 0x000000ff;
+        pixels[i] = red << 16 | green << 8 | blue;
+      }
+      r.setVar("pixels", pixels);
+      r.exec("proc = new ColorProcessor(w, h, pixels)");
+    }
+    else if (samples.length == 1 || fake3) {
+      // check for 8-bit, 16-bit or 32-bit grayscale
+      float lo = Float.POSITIVE_INFINITY, hi = Float.NEGATIVE_INFINITY;
+      for (int i=0; i<samples[0].length; i++) {
+        float value = samples[0][i];
+        if (value != (int) value) {
+          // force 32-bit floats
+          hi = Float.POSITIVE_INFINITY;
+          break;
+        }
+        if (value < lo) {
+          lo = value;
+          if (lo < 0) break; // need 32-bit floats
+        }
+        if (value > hi) {
+          hi = value;
+          if (hi >= 65536) break; // need 32-bit floats
+        }
+      }
+      if (lo >= 0 && hi < 256) {
+        // 8-bit grayscale
+        byte[] pixels = new byte[samples[0].length];
+        for (int i=0; i<pixels.length; i++) {
+          int val = (int) samples[0][i] & 0x000000ff;
+          pixels[i] = (byte) val;
+        }
+        r.setVar("pixels", pixels);
+        r.setVar("cm", null);
+        r.exec("proc = new ByteProcessor(w, h, pixels, cm)");
+      }
+      else if (lo >= 0 && hi < 65536) {
+        // 16-bit grayscale
+        short[] pixels = new short[samples[0].length];
+        for (int i=0; i<pixels.length; i++) {
+          int val = (int) samples[0][i];
+          pixels[i] = (short) val;
+        }
+        r.setVar("pixels", pixels);
+        r.setVar("cm", null);
+        r.exec("proc = new ShortProcessor(w, h, pixels, cm)");
+      }
+      else {
+        // 32-bit floating point grayscale
+        r.setVar("pixels", samples[0]);
+        r.setVar("cm", null);
+        r.exec("proc = new FloatProcessor(w, h, pixels, cm)");
+      }
+    }
+    return r.getVar("proc");
+  }
+
+  private void initFile(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    if (noImageJ) throw new BadFormException(NO_IJ);
+
+    // close any currently open files
+    close();
+
+    // determine whether ImageJ can handle the file
+    r.setVar("id", id);
+    r.setVar("empty", "");
+    r.exec("tdec = new TiffDecoder(empty, id)");
+    canUseImageJ = true;
+    try {
+      r.exec("info = tdec.getTiffInfo()");
+    }
+    catch (VisADException exc) {
+      canUseImageJ = false;
+    }
+
+    // determine number of images in TIFF file
+    if (canUseImageJ) {
+      r.exec("opener = new Opener()");
+      r.exec("image = opener.openImage(id)");
+      r.exec("numImages = image.getStackSize()");
+      numImages = ((Integer) r.getVar("numImages")).intValue();
+    }
+    else {
+      if (noJai) throw new BadFormException(NO_JAI);
+      try {
+        r.setVar("tiff", "tiff");
+        r.setVar("file", new File(id));
+        r.exec("id = ImageCodec.createImageDecoder(tiff, file, null)");
+        numImages = ((Integer) r.exec("id.getNumPages()")).intValue();
+      }
+      catch (VisADException exc) { throw new BadFormException(exc); }
+    }
+
+    currentId = id;
+  }
+
+
+  // -- Main method --
+
+  /**
+   * Run 'java visad.data.visad.LegacyTiffForm in_file out_file' to convert
+   * in_file to out_file in TIFF data format.
+   */
+  public static void main(String[] args)
+    throws VisADException, RemoteException, IOException
+  {
+    if (args == null || args.length < 1 || args.length > 2) {
+      System.out.println("To convert a file to TIFF, run:");
+      System.out.println("  java " +
+        "visad.data.tiff.LegacyTiffForm in_file out_file");
+      System.out.println("To test read a TIFF file, run:");
+      System.out.println("  java visad.data.tiff.LegacyTiffForm in_file");
+      System.exit(2);
+    }
+
+    if (args.length == 1) {
+      // Test read TIFF file
+      LegacyTiffForm form = new LegacyTiffForm();
+      System.out.print("Reading " + args[0] + " ");
+      Data data = form.open(args[0]);
+      System.out.println("[done]");
+      System.out.println("MathType =\n" + data.getType().prettyString());
+    }
+    else if (args.length == 2) {
+      // Convert file to TIFF format
+      System.out.print(args[0] + " -> " + args[1] + " ");
+      DefaultFamily loader = new DefaultFamily("loader");
+      DataImpl data = loader.open(args[0]);
+      loader = null;
+      LegacyTiffForm form = new LegacyTiffForm();
+      form.save(args[1], data, true);
+      System.out.println("[done]");
+    }
+    System.exit(0);
+  }
+
+}
diff --git a/visad/data/tiff/LegacyTiffTools.java b/visad/data/tiff/LegacyTiffTools.java
new file mode 100644
index 0000000..7329069
--- /dev/null
+++ b/visad/data/tiff/LegacyTiffTools.java
@@ -0,0 +1,374 @@
+//
+// LegacyTiffTools.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.tiff;
+
+import java.util.*;
+import java.io.*;
+import loci.formats.codec.LZWCodec;
+
+/**
+ * A utility class for manipulating TIFF files.
+ * @author Eric Kjellman egkjellman at wisc.edu
+ *
+ * @deprecated Use loci.formats.TiffTools
+ */
+public class LegacyTiffTools {
+
+  private static final int CLEAR_CODE = 256;
+  private static final int EOI_CODE = 257;
+  private static final int PHOTOMETRIC_INTERPRETATION_FIELD = 262;
+  private static final int IMPOSSIBLE_IFD = 424242;
+
+  public static Hashtable getIFDHash(RandomAccessFile readIn)
+    throws IOException
+  {
+    byte[] byteArray = new byte[4];
+    int nextOffset;
+    readIn.seek(4);
+    readIn.read(byteArray); // Gets the offset of the first IFD
+    readIn.seek(batoi(byteArray));
+    byteArray = new byte[2];
+    // Gets the number of directory entries in the IFD
+    readIn.read(byteArray);
+    Hashtable ifdEntries = new Hashtable();
+    Integer numEntries = new Integer(batoi(byteArray));
+    Integer entryTag, entryType, entrycount, entryOffset;
+    int frames = 1;
+    int length, offset;
+    Vector entryData;
+
+    // Iterate through the directory entries
+    for (int i = 0; i < numEntries.intValue(); i++) {
+      byteArray = new byte[2];
+      readIn.read(byteArray); // Get the entry tag
+      entryTag = new Integer(batoi(byteArray));
+      readIn.read(byteArray); // Get the entry type
+      entryType = new Integer(batoi(byteArray));
+      byteArray = new byte[4];
+      // Get the number of entries this offset points to.
+      readIn.read(byteArray);
+      entrycount = new Integer(batoi(byteArray));
+      readIn.read(byteArray); // Gets the offset for the entry
+      entryOffset = new Integer(batoi(byteArray));
+      // Adds the data to a vector, and then hashs it.
+      entryData = new Vector();
+      entryData.add(entryType);
+      entryData.add(entrycount);
+      entryData.add(entryOffset);
+      ifdEntries.put(entryTag, entryData);
+    }
+    readIn.read(byteArray);
+    nextOffset = batoi(byteArray);
+    ifdEntries.put(new Integer(IMPOSSIBLE_IFD), new Integer(nextOffset));
+    // 424242 is not possible as an IFD ID number, which are 16 bit
+    return ifdEntries;
+  }
+
+  public static Hashtable getIFDHash(RandomAccessFile readIn, int block_id)
+    throws IOException
+  {
+    Hashtable ifdEntries = new Hashtable();
+    Integer entryTag, entryType, entrycount, entryOffset;
+    int frames = 0;
+    int length, offset;
+    byte[] byteArray = new byte[4];
+    int nextOffset;
+    Vector entryData;
+    Integer numEntries;
+
+
+    readIn.seek(4);
+    readIn.read(byteArray); // Gets the offset of the first IFD
+    readIn.seek(batoi(byteArray));
+
+
+    // Get to the IFD we want.
+    while (frames != block_id) {
+      byteArray = new byte[2];
+      // Gets the number of directory entries in the IFD
+      readIn.read(byteArray);
+      numEntries = new Integer(batoi(byteArray));
+      // skips the IFD
+      readIn.skipBytes(12 * numEntries.intValue());
+      // Get the nextOffset
+      byteArray = new byte[4];
+      readIn.read(byteArray);
+      readIn.seek(batoi(byteArray));
+      frames++;
+    }
+
+    byteArray = new byte[2];
+    readIn.read(byteArray);
+    numEntries = new Integer(batoi(byteArray));
+
+    // Iterate through the directory entries
+    for (int i = 0; i < numEntries.intValue(); i++) {
+      byteArray = new byte[2];
+      readIn.read(byteArray); // Get the entry tag
+      entryTag = new Integer(batoi(byteArray));
+      readIn.read(byteArray); // Get the entry type
+      entryType = new Integer(batoi(byteArray));
+      byteArray = new byte[4];
+      // Get the number of entries this offset points to.
+      readIn.read(byteArray);
+      entrycount = new Integer(batoi(byteArray));
+      readIn.read(byteArray); // Gets the offset for the entry
+      entryOffset = new Integer(batoi(byteArray));
+      // Adds the data to a vector, and then hashs it.
+      entryData = new Vector();
+      entryData.add(entryType);
+      entryData.add(entrycount);
+      entryData.add(entryOffset);
+      ifdEntries.put(entryTag, entryData);
+    }
+    readIn.read(byteArray);
+    nextOffset = batoi(byteArray);
+    ifdEntries.put(new Integer(IMPOSSIBLE_IFD), new Integer(nextOffset));
+    // 424242 is not possible as an IFD ID number, which are 16 bit
+    return ifdEntries;
+  }
+
+  /**
+   * Items in an IFD can be pointers to arrays of data, and not just single
+   * items. This will return an array of int containing the data pointed to
+   * in the IFD. This does not currently handle the type RATIONAL.
+   */
+  public static int[] getIFDArray(RandomAccessFile readIn, Vector v)
+    throws IOException
+  {
+    int count = ((Integer) v.get(1)).intValue();
+    int type = ((Integer) v.get(0)).intValue();
+    if (count == 1) {
+      // if the count is 1, there is no pointer, it's actual data
+      return new int[] {((Integer) v.get(2)).intValue()};
+    }
+    else {
+      readIn.seek(((Integer) v.get(2)).intValue());
+      int[] toReturn = new int[count];
+      int bytesPerEntry = 1;
+      if (type == 1) { // BYTE
+        bytesPerEntry = 1;
+      }
+      if (type == 2) { // ASCII
+        bytesPerEntry = 1;
+      }
+      if (type == 3) { // SHORT
+        bytesPerEntry = 2;
+      }
+      if (type == 4) { // LONG
+        bytesPerEntry = 4;
+      }
+      //if (type == 5) { // RATIONAL, not supported right now.
+      //  bytesPerEntry = 4;
+      //}
+      byte[] data = new byte[count * bytesPerEntry];
+      readIn.read(data);
+      byte[] translate = new byte[bytesPerEntry];
+      for (int i = 0; i < count ; i++) {
+        System.arraycopy(data, i * bytesPerEntry, translate, 0, bytesPerEntry);
+        toReturn[i] = batoi(translate);
+      }
+      return toReturn;
+    }
+  }
+
+  /**
+   * Items in an IFD can be pointers to arrays of data, and not just single
+   * items. This will return an array of int containing the data pointed to
+   * in the IFD.
+   */
+  public static double[] getIFDRArray(RandomAccessFile readIn, Vector v)
+    throws IOException
+  {
+    int count = ((Integer) v.get(1)).intValue();
+    int type = ((Integer) v.get(0)).intValue();
+    if (count == 1) {
+      // if the count is 1, there is no pointer, it's actual data
+      // return new int[] {((Integer) v.get(2)).intValue()};
+      // This shouldn't happen: Rationals require 2 floats.
+      return new double[] {-1.0D}; // TODO: Change this.
+    }
+    else {
+      readIn.seek(((Integer) v.get(2)).intValue());
+      double[] toReturn = new double[count];
+      int bytesPerEntry = 8;
+      int num, denom;
+      if (type != 5) { // Not a rational!
+        return new double[] {-1.0D}; // TODO: Change this.
+      }
+      byte[] data = new byte[count * bytesPerEntry];
+      readIn.read(data);
+      byte[] translate = new byte[bytesPerEntry];
+      for (int i = 0; i < count ; i++) {
+        System.arraycopy(data, i * bytesPerEntry, translate, 0, 4);
+        num = batoi(translate);
+        System.arraycopy(data, i * bytesPerEntry + 4, translate, 0, 4);
+        denom = batoi(translate);
+        toReturn[i] = num/denom;
+      }
+      return toReturn;
+    }
+  }
+
+  public static byte[] lzwUncompress(byte[] input) throws IOException {
+    try {
+      return new LZWCodec().decompress(input);
+    }
+    catch (Exception exc) {
+      return null;
+    }
+  }
+
+  public static int getPhotometricInterpretation(RandomAccessFile in)
+    throws IOException
+  {
+    Hashtable ifdHash = getIFDHash(in);
+    Vector v = (Vector) ifdHash.get(
+      new Integer(PHOTOMETRIC_INTERPRETATION_FIELD));
+    return ((Integer) v.get(2)).intValue();
+  }
+
+  /** Translates up to the first 4 bytes of a byte array to an integer. */
+  public static int batoi(byte[] inp) {
+    int len = inp.length>4?4:inp.length;
+    int total = 0;
+    for (int i = 0; i < len; i++) {
+      total += ((inp[i]<0?256+inp[i]:(int)inp[i]) << (i * 8));
+    }
+    return total;
+  }
+
+  public static int[] getTIFFDimensions(RandomAccessFile readIn)
+    throws IOException
+  {
+    // For this one, we're going to read the entire IFD, get the x and y
+    // coordinates out of it, and then just pass through the other IFDs to get
+    // z. It is conceivable that the various images are of different sizes,
+    // but for now I'm going to assume that they are not.
+    byte[] byteArray;
+    int nextOffset;
+    int numEntries;
+    int frames = 1;
+    Integer width, length;
+    Vector entryData;
+    Hashtable ifdEntries = getIFDHash(readIn);
+
+    nextOffset =
+      ((Integer) ifdEntries.get(new Integer(IMPOSSIBLE_IFD))).intValue();
+
+    while (nextOffset != 0) {
+      frames++;
+      try {
+        readIn.seek(nextOffset);
+      }
+      catch (Exception e) {
+        e.printStackTrace();
+      }
+      byteArray = new byte[2];
+      readIn.read(byteArray); // Get the number of directory entries in the IFD
+      numEntries = batoi(byteArray);
+      readIn.skipBytes(12 * numEntries);
+      byteArray = new byte[4];
+      readIn.read(byteArray);
+      nextOffset = batoi(byteArray);
+    }
+
+    // This is the directory entry for width.
+    entryData = (Vector) ifdEntries.get(new Integer(256));
+    width = (Integer) entryData.get(2);
+    // This is the directory entry for height.
+    entryData = (Vector) ifdEntries.get(new Integer(257));
+    length = (Integer) entryData.get(2);
+    return new int[] {width.intValue(), length.intValue(), frames};
+  }
+
+  public static int getIFDValue(Hashtable h, int id) {
+    Integer k = new Integer(id);
+    Vector v = (Vector) h.get(k);
+    if (v == null) return -1;
+    Integer i = (Integer) v.get(2);
+    if (i == null) return -1;
+    return i.intValue();
+  }
+
+  public static boolean isIFDArray(Hashtable h, int id) {
+    return getIFDValue(h, id) == 1;
+  }
+
+
+  // -- Main method --
+
+  public static void main(String args[]) throws IOException {
+    Vector v;
+    Integer k;
+    // File f = new File(args[0]);
+    RandomAccessFile f = new RandomAccessFile(args[0], "r");
+    Hashtable h = new Hashtable();
+    int[] d = getTIFFDimensions(f);
+
+    for (int meh = 0; meh < d[2]; meh++) {
+
+      System.out.println("*** START HASH #" + meh);
+      h = getIFDHash(f, meh);
+
+
+      for(int i = 0; i < 65536; i++) {
+        k = new Integer(i);
+        if(h.containsKey(k)) {
+          v = (Vector) h.get(k);
+          System.out.print(k + ":");
+          System.out.print((Integer) v.get(0) + " ");
+          System.out.print((Integer) v.get(1) + " ");
+          System.out.println((Integer) v.get(2));
+          if (((Integer) v.get(1)).intValue() != 1) {
+            if(((Integer) v.get(1)).intValue() != 5) {
+              int[] a= getIFDArray(f, v);
+              System.out.print("  [ ");
+              for (int j = 0; j < a.length; j++) {
+                System.out.print(a[j] + " ");
+              }
+              System.out.println("]");
+            }
+            else {
+              double[] a= getIFDRArray(f, v);
+              System.out.print("  [ ");
+              for (int j = 0; j < a.length; j++) {
+                System.out.print(a[j] + " ");
+              }
+              System.out.println("]");
+            }
+          }
+        }
+      }
+      System.out.println("*** END HASH #" + meh);
+      System.out.println(" ");
+    }
+    int[] a = getTIFFDimensions(f);
+    System.out.println(a[0] + "x" + a[1] + "x" + a[2]);
+  }
+
+}
diff --git a/visad/data/tiff/TiffForm.java b/visad/data/tiff/TiffForm.java
new file mode 100644
index 0000000..b605439
--- /dev/null
+++ b/visad/data/tiff/TiffForm.java
@@ -0,0 +1,63 @@
+//
+// TiffForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.tiff;
+
+import java.util.Hashtable;
+import java.io.IOException;
+import loci.formats.FormatException;
+import loci.formats.in.TiffReader;
+import loci.formats.out.TiffWriter;
+import visad.FlatField;
+import visad.data.BadFormException;
+import visad.util.DataUtility;
+
+/**
+ * TiffForm is the VisAD data adapter for the TIFF file format.
+ *
+ * This class is just a wrapper for the TIFF logic in the loci.formats packages.
+ */
+public class TiffForm extends visad.data.bio.LociForm {
+
+  public TiffForm() {
+    super(new TiffReader(), new TiffWriter());
+  }
+
+  public void saveImage(String id, FlatField image, Hashtable ifd,
+    boolean last) throws BadFormException, IOException
+  {
+    try {
+      ((TiffWriter) writer).saveImage(id,
+        DataUtility.extractImage(image, false), ifd, last);
+    }
+    catch (FormatException e) { throw new BadFormException(e); }
+  }
+
+  public static void main(String[] args) throws Exception {
+    new TiffForm().testRead(args);
+  }
+
+}
diff --git a/visad/data/units/API_users_guide.html b/visad/data/units/API_users_guide.html
new file mode 100644
index 0000000..7839150
--- /dev/null
+++ b/visad/data/units/API_users_guide.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<HEAD>
+   <TITLE></TITLE>
+   <META NAME="GENERATOR" CONTENT="Mozilla/3.01Gold (X11; I; OSF1 V4.0 alpha) [Netscape]">
+</HEAD>
+<BODY>
+
+<H1 ALIGN=CENTER>User's Guide to the netCDF-units Package</H1>
+
+<CENTER><P>
+<HR WIDTH="100%"></P></CENTER>
+
+<H2 ALIGN=CENTER>Introduction</H2>
+
+<P>The netCDF-units package is for the decoding of netCDF unit specifications.
+The C UDUNITS package from the Unidata Program Center has been the de facto
+standard for unit specifications in netCDF datasets. This Java package
+understands and decodes the same syntax used by the UDUNITS package.</P>
+
+<P>
+<HR WIDTH="100%"></P>
+
+<H2 ALIGN=CENTER>Typical Use</H2>
+
+<P>Typical use of the netCDF-units package will simply consist of 
+repeted calls to the member function 
+of the parser to decode a unit specification. For example</P>
+
+<UL>
+<P><TT>import visad.Unit;<BR>
+import visad.data.units.Parser;<BR>
+<BR>
+while ((String unitSpec = getUnitSpecSomehow()) != null)<BR>
+{<BR>
+    Unit unit = Parser.parse(unitSpec);<BR>
+    ...<BR>
+}</TT></P>
+</UL>
+
+<P>For information on <TT>visad.Unit</TT>, see the VisAD package.</P>
+
+</BODY>
+</HTML>
diff --git a/visad/data/units/DefaultUnitsDB.java b/visad/data/units/DefaultUnitsDB.java
new file mode 100644
index 0000000..f0e9416
--- /dev/null
+++ b/visad/data/units/DefaultUnitsDB.java
@@ -0,0 +1,1105 @@
+/*
+ * Copyright 1999, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: DefaultUnitsDB.java,v 1.9 2010-05-19 12:29:03 donm Exp $
+ */
+
+package visad.data.units;
+
+import visad.BaseUnit;
+import visad.OffsetUnit;
+import visad.SI;
+import visad.ScaledUnit;
+import visad.Unit;
+import visad.UnitException;
+
+/**
+ * Default units database.
+ * 
+ * This database knows about approximately 500 different units. Users can also
+ * add new units to the database at runtime.
+ * 
+ * The basis for this units database is the International System of Units (SI).
+ * 
+ * This is a singleton class.
+ */
+public final class DefaultUnitsDB extends UnitTable {
+    /**
+     * The singleton instance of this class.
+     */
+    private static final DefaultUnitsDB   db;
+    
+    static {
+        try {
+		    db = new DefaultUnitsDB();
+        }
+        catch (UnitException e) {
+            throw (ExceptionInInitializerError) new ExceptionInInitializerError().initCause(e);
+        }
+    }
+
+    /**
+     * The unit prefix names in order of lexicographic length:
+     */
+    protected final UnitPrefix[]    prefixNames     = {
+            new UnitPrefix("centi", 1e-2),
+            new UnitPrefix("femto", 1e-15),
+            new UnitPrefix("hecto", 1e2),
+            new UnitPrefix("micro", 1e-6),
+            new UnitPrefix("milli", 1e-3),
+            new UnitPrefix("yocto", 1e-24),
+            new UnitPrefix("yotta", 1e24),
+            new UnitPrefix("zepto", 1e-21),
+            new UnitPrefix("zetta", 1e21),
+            new UnitPrefix("atto", 1e-18),
+            new UnitPrefix("deca", 1e1), // Spelling according to "ISO 2955:
+            // Information processing --
+            // Representation of SI and other units
+            // in systems with limited character
+            // sets"
+            new UnitPrefix("deci", 1e-1),
+            new UnitPrefix("deka", 1e1), // Spelling according to "ASTM
+            // Designation: E 380 - 85: Standard
+            // for METRIC PRACTICE", "ANSI/IEEE Std
+            // 260-1978 (Reaffirmed 1985): IEEE
+            // Standard Letter Symbols for Units of
+            // Measurement", and NIST Special
+            // Publication 811, 1995 Edition:
+            // "Guide for the Use of the
+            // International System of Units (SI)".
+            new UnitPrefix("giga", 1e9), // 1st syllable pronounced "jig"
+            // according to "ASTM Designation: E
+            // 380 - 85: Standard for METRIC
+            // PRACTICE".
+            new UnitPrefix("kilo", 1e3), new UnitPrefix("mega", 1e6),
+            new UnitPrefix("nano", 1e-9), new UnitPrefix("peta", 1e15),
+            new UnitPrefix("pico", 1e-12), new UnitPrefix("tera", 1e12),
+            new UnitPrefix("exa", 1e18),            };
+
+    /**
+     * The unit prefix symbols in order of lexicographic length:
+     */
+    protected final UnitPrefix[]    prefixSymbols   = {
+            new UnitPrefix("da", 1e1), new UnitPrefix("E", 1e18),
+            new UnitPrefix("G", 1e9), new UnitPrefix("M", 1e6),
+            new UnitPrefix("P", 1e15), new UnitPrefix("T", 1e12),
+            new UnitPrefix("Y", 1e24), new UnitPrefix("Z", 1e21),
+            new UnitPrefix("a", 1e-18), new UnitPrefix("c", 1e-2),
+            new UnitPrefix("d", 1e-1), new UnitPrefix("f", 1e-15),
+            new UnitPrefix("h", 1e2), new UnitPrefix("k", 1e3),
+            new UnitPrefix("m", 1e-3), new UnitPrefix("n", 1e-9),
+            new UnitPrefix("p", 1e-12), new UnitPrefix("u", 1e-6),
+            new UnitPrefix("y", 1e-24), new UnitPrefix("z", 1e-21), };
+
+    /**
+     * Constructs a default, units database.
+     * 
+     * @throws UnitException
+     *             Something went wrong in generating a unit for the database.
+     *             This should not occur and indicates an internal
+     *             inconsistancy.
+     */
+    private DefaultUnitsDB() throws UnitException {
+        /*
+         * Create a unit table of the proper size. Because increasing the size
+         * might be expensive, the initial size should be kept in sync with the
+         * actual number of entries (e.g. in vi: :.,$w !egrep 'pn\(' | wc -l
+         * (times 2 plus) :.,$w !egrep '(put|px)\(' | wc -l :.,$w !egrep 'ps\('
+         * | wc -l
+         */
+        super(677, 98);
+
+        /*
+         * The base units:
+         */
+        put(SI.ampere);
+        put(SI.candela);
+        put(SI.kelvin);
+        put(SI.kilogram);
+        put(SI.meter);
+        put(SI.mole);
+        put(SI.second);
+        put(SI.radian);
+        put(SI.steradian);
+
+        /*
+         * Constants:
+         */
+        ps("%", new ScaledUnit(0.01));
+        pn("percent", "%");
+        pn("PI", new ScaledUnit(Math.PI));
+        pn("bakersdozen", new ScaledUnit(13));
+        pn("pair", new ScaledUnit(2));
+        pn("ten", new ScaledUnit(10));
+        pn("dozen", new ScaledUnit(12));
+        pn("score", new ScaledUnit(20));
+        pn("hundred", new ScaledUnit(100));
+        pn("thousand", new ScaledUnit(1.0e3));
+        pn("million", new ScaledUnit(1.0e6));
+        // NB: "billion" is ambiguous (1e9 in U.S. but 1e12 in U.K.)
+
+        /*
+         * NB: All subsequent definitions must be given in terms of earlier
+         * definitions. Forward referencing is not permitted.
+         */
+
+        /*
+         * The following are non-base units of the fundamental quantities
+         */
+
+        /*
+         * UNITS OF ELECTRIC CURRENT
+         */
+        pn("amp", "ampere");
+        pn("abampere", get("A").scale(10)); // exact
+        pn("gilbert", get("A").scale(7.957747e-1));
+        pn("statampere", get("A").scale(3.335640e-10));
+        pn("biot", "abampere");
+
+        /*
+         * UNITS OF LUMINOUS INTENSITY
+         */
+        pn("candle", "candela");
+
+        /*
+         * UNITS OF THERMODYNAMIC TEMPERATURE
+         */
+        px("degree kelvin", "K");
+        px("degrees kelvin", "K");
+        ps("degK", "K");
+        px("degreeK", "K");
+        px("degreesK", "K");
+        px("deg K", "K");
+        px("degree K", "K");
+        px("degrees K", "K");
+
+        ps("Cel", new OffsetUnit(273.15, (BaseUnit) get("K")));
+        pn("celsius", "Cel");
+        px("degree celsius", "Cel");
+        px("degrees celsius", "Cel");
+        pn("centigrade", "Cel");
+        px("degree centigrade", "Cel");
+        px("degrees centigrade", "Cel");
+        px("degC", "Cel");
+        px("degreeC", "Cel");
+        px("degreesC", "Cel");
+        px("deg C", "Cel");
+        px("degree C", "Cel");
+        px("degrees C", "Cel");
+        // ps("C", "Cel"); // `C' means `coulomb'
+
+        pn("rankine", get("K").scale(1 / 1.8));
+        px("degree rankine", "rankine");
+        px("degrees rankine", "rankine");
+        px("degR", "rankine");
+        px("degreeR", "rankine");
+        px("degreesR", "rankine");
+        px("deg R", "rankine");
+        px("degree R", "rankine");
+        px("degrees R", "rankine");
+        // ps("R", "rankine"); // "R" means "roentgen"
+
+        pn("fahrenheit", get("Rankine").shift(459.67));
+        px("degree fahrenheit", "fahrenheit");
+        px("degrees fahrenheit", "fahrenheit");
+        px("degF", "fahrenheit");
+        px("degreeF", "fahrenheit");
+        px("degreesF", "fahrenheit");
+        px("deg F", "fahrenheit");
+        px("degree F", "fahrenheit");
+        px("degrees F", "fahrenheit");
+        // ps("F", "fahrenheit"); // "F" means "farad"
+
+        /*
+         * UNITS OF MASS
+         */
+        pn("assay ton", get("kg").scale(2.916667e-2));
+        pn("avoirdupois ounce", get("kg").scale(2.834952e-2));
+        pn("avoirdupois pound", get("kg").scale(4.5359237e-1)); // exact
+        pn("carat", get("kg").scale(2e-4));
+        ps("gr", get("kg").scale(6.479891e-5)); // exact
+        ps("g", get("kg").scale(1e-3)); // exact
+        pn("long hundredweight", get("kg").scale(5.080235e1));
+        ps("tne", get("kg").scale(1e3)); // exact
+        pn("pennyweight", get("kg").scale(1.555174e-3));
+        pn("short hundredweight", get("kg").scale(4.535924e1));
+        pn("slug", get("kg").scale(14.59390));
+        pn("troy ounce", get("kg").scale(3.110348e-2));
+        pn("troy pound", get("kg").scale(3.732417e-1));
+        pn("amu", get("kg").scale(1.66054e-27));
+        pn("scruple", get("gr").scale(20));
+        pn("apdram", get("gr").scale(60));
+        pn("apounce", get("gr").scale(480));
+        pn("appound", get("gr").scale(5760));
+
+        pn("gram", "g"); // was "gravity"
+        pn("tonne", "tne");
+        px("metric ton", "tne");
+        pn("apothecary ounce", "troy ounce");
+        pn("apothecary pound", "troy pound");
+        pn("pound", "avoirdupois pound");
+        pn("metricton", "tne");
+        ps("grain", "gr");
+        pn("atomicmassunit", "amu");
+        pn("atomic mass unit", "amu");
+
+        ps("t", "tne");
+        ps("lb", "avoirdupois pound");
+        pn("bag", get("pound").scale(94));
+        pn("short ton", get("pound").scale(2000));
+        pn("long ton", get("pound").scale(2240));
+
+        pn("ton", "short ton");
+        pn("shortton", "short ton");
+        pn("longton", "long ton");
+
+        /*
+         * UNITS OF LENGTH
+         */
+        pn("angstrom", get("m").scale(1e-10));
+        pn("au", get("m").scale(1.495979e11));
+        pn("fermi", get("m").scale(1e-15)); // exact
+        pn("light year", get("m").scale(9.46073e15));
+        pn("micron", get("m").scale(1e-6)); // exact
+        pn("mil", get("m").scale(2.54e-5)); // exact
+        pn("nautical mile", get("m").scale(1.852000e3)); // exact
+        pn("parsec", get("m").scale(3.085678e16));
+        pn("printers point", get("m").scale(3.514598e-4));
+
+        pn("metre", "m");
+        px("prs", "parsec");
+
+        /*
+         * God help us! There's an international foot and a US survey foot and
+         * they're not the same!
+         */
+
+        // US Survey foot stuff:
+        px("US survey foot", get("m").scale(1200 / 3937.)); // exact
+        pn("US survey yard", get("US survey foot").scale(3)); // exact
+        pn("US survey mile", get("US survey foot").scale(5280)); // exact
+        pn("rod", get("US survey foot").scale(16.5)); // exact
+        pn("furlong", get("US survey foot").scale(660)); // exact
+        pn("fathom", get("US survey foot").scale(6)); // exact
+
+        px("US survey feet", "US survey foot");
+        pn("US statute mile", "US survey mile");
+        pn("pole", "rod");
+        px("perch", "rod");
+        px("perches", "perch");
+
+        // International foot stuff:
+        px("international inch", get("m").scale(.0254)); // exact
+        px("international foot", get("international inch").scale(12));
+        // exact
+        pn("international yard", get("international foot").scale(3));
+        // exact
+        pn("international mile", get("international foot").scale(5280));
+        // exact
+        px("international inches", "international inch"); // alias
+        px("international feet", "international foot"); // alias
+
+        // Alias unspecified units to the international units:
+        px("inch", "international inch"); // alias
+        px("foot", "international foot"); // alias
+        pn("yard", "international yard"); // alias
+        pn("mile", "international mile"); // alias
+
+        // The following should hold regardless:
+        px("inches", "inch"); // alias
+        ps("in", "inches"); // alias
+        px("feet", "foot"); // alias
+        ps("ft", "feet"); // alias
+        ps("yd", "yard"); // alias
+        ps("mi", "mile"); // alias
+
+        pn("chain", get("m").scale(2.011684e1));
+
+        pn("pica", get("printers point").scale(12)); // exact
+        pn("printers pica", "pica");
+        pn("astronomicalunit", "au");
+        ps("astronomical unit", "au");
+        px("asu", "au");
+        pn("nmile", "nautical mile");
+        ps("nmi", "nautical mile");
+
+        pn("big point", get("inch").scale(1. / 72)); // exact
+        pn("barleycorn", get("inch").scale(1. / 3));
+
+        pn("arpentlin", get("foot").scale(191.835));
+
+        // The following is for Ozone measurements:
+        pn("Dobson", get("m").scale(.00001)); // exact
+        pn("DU", "Dobson");
+
+        /*
+         * UNITS OF TIME
+         */
+        /*
+         * Interval between 2 successive passages of sun through vernal equinox
+         * (365.242198781 days -- see
+         * http://www.ast.cam.ac.uk/pubinfo/leaflets/,
+         * http://aa.usno.navy.mil/AA/ and
+         * http://adswww.colorado.edu/adswww/astro coord.html):
+         */
+        pn("year", get("s").scale(3.15569259747e7));
+        ps("d", get("s").scale(8.64e4)); // exact
+        ps("h", get("s").scale(3.6e3)); // exact
+        ps("min", get("s").scale(60)); // exact
+        pn("shake", get("s").scale(1e-8)); // exact
+        pn("sidereal day", get("s").scale(8.616409e4));
+        pn("sidereal hour", get("s").scale(3.590170e3));
+        pn("sidereal minute", get("s").scale(5.983617e1));
+        pn("sidereal second", get("s").scale(0.9972696));
+        pn("sidereal year", get("s").scale(3.155815e7));
+
+        pn("day", "d");
+        pn("hour", "h");
+        pn("minute", "min");
+        pn("sec", "s"); // avoid
+        pn("lunar month", get("d").scale(29.530589));
+
+        pn("common year", get("d").scale(365));
+        // exact: 153600e7 seconds
+        pn("leap year", get("d").scale(366)); // exact
+        pn("Julian year", get("d").scale(365.25)); // exact
+        pn("Gregorian year", get("d").scale(365.2425)); // exact
+        pn("tropical year", "year");
+        pn("sidereal month", get("d").scale(27.321661));
+        pn("tropical month", get("d").scale(27.321582));
+        pn("fortnight", get("d").scale(14));
+        pn("week", get("d").scale(7)); // exact
+
+        pn("jiffy", get("s").scale(1e-2)); // it's true
+        pn("eon", get("year").scale(1e9)); // fuzzy
+        pn("month", get("year").scale(1. / 12)); // on average
+
+        pn("tropical year", "year");
+        pn("yr", "year");
+        ps("a", "year"); // "anno"
+        px("ann", "year"); // "anno"
+        pn("hr", "h");
+
+        /*
+         * UNITS OF PLANE ANGLE
+         */
+        pn("circle", get("radian").scale(2 * Math.PI));
+        pn("deg", get("radian").scale(Math.PI / 180.));
+        pn("'", get("deg").scale(1. / 60));
+        pn("\"", get("deg").scale(1. / 3600));
+        pn("grade", get("deg").scale(0.9)); // exact
+        pn("cycle", get("circle"));
+
+        pn("turn", "circle");
+        pn("revolution", "cycle");
+        px("gon", "grade");
+        pn("angular degree", "deg");
+        pn("angular minute", "'");
+        pn("angular second", "\"");
+        pn("arcdeg", "deg");
+        pn("degree", "deg");
+        pn("arcminute", "'");
+        px("mnt", "'");
+        pn("arcsecond", "\"");
+        // px("sec", "\""); // avoid
+        pn("arcmin", "'");
+        pn("arcsec", "\"");
+
+        px("degree true", get("deg"));
+        px("degrees true", get("deg"));
+        px("degrees north", get("deg"));
+        px("degrees east", get("deg"));
+        px("degrees south", get("degrees north").scale(-1));
+        px("degrees west", get("degrees east").scale(-1));
+
+        px("degree north", "degrees north");
+        px("degreeN", "degrees north");
+        px("degree N", "degrees north");
+        px("degreesN", "degrees north");
+        px("degrees N", "degrees north");
+
+        px("degree east", "degrees east");
+        px("degreeE", "degrees east");
+        px("degree E", "degrees east");
+        px("degreesE", "degrees east");
+        px("degrees E", "degrees east");
+
+        px("degree west", "degrees west");
+        px("degreeW", "degrees west");
+        px("degree W", "degrees west");
+        px("degreesW", "degrees west");
+        px("degrees W", "degrees west");
+
+        px("degree true", "degrees true");
+        px("degreeT", "degrees true");
+        px("degree T", "degrees true");
+        px("degreesT", "degrees true");
+        px("degrees T", "degrees true");
+
+        /*
+         * The following are derived units with special names. They are useful
+         * for defining other derived units.
+         */
+        ps("Hz", get("second").pow(-1));
+        ps("N", get("kg").multiply(get("m").divide(get("s").pow(2))));
+        ps("C", get("A").multiply(get("s")));
+        ps("lm", get("cd").multiply(get("sr")));
+        ps("Bq", get("Hz"));
+        // SI unit of activity of a radionuclide
+        px("standard free fall", get("m").divide(get("s").pow(2)).scale(
+                9.806650));
+        ps("Pa", get("N").divide(get("m").pow(2)));
+        ps("J", get("N").multiply(get("m")));
+        ps("lx", get("lm").divide(get("m").pow(2)));
+        pn("sphere", get("steradian").scale(4 * Math.PI));
+        ps("W", get("J").divide(get("s")));
+        ps("Gy", get("J").divide(get("kg")));
+        // absorbed dose. derived unit
+        ps("Sv", get("J").divide(get("kg")));
+        // dose equivalent. derived unit
+        ps("V", get("W").divide(get("A")));
+        ps("F", get("C").divide(get("V")));
+        ps("Ohm", get("V").divide(get("A")));
+        ps("S", get("A").divide(get("V")));
+        ps("Wb", get("V").multiply(get("s")));
+        ps("T", get("Wb").divide(get("m").pow(2)));
+        ps("H", get("Wb").divide(get("A")));
+
+        pn("newton", "N");
+        pn("hertz", "Hz");
+        pn("watt", "W");
+        px("force", "standard free fall");
+        px("gravity", "standard free fall");
+        px("free fall", "standard free fall");
+
+        px("conventional mercury", get("gravity").multiply(
+                get("kg").divide(get("m").pow(3))).scale(13595.10));
+        px("mercury 0C", get("gravity").multiply(
+                get("kg").divide(get("m").pow(3))).scale(13595.1));
+        px("mercury 60F", get("gravity").multiply(
+                get("kg").divide(get("m").pow(3))).scale(13556.8));
+        px("conventional water", get("gravity").multiply(
+                get("kg").divide(get("m").pow(3))).scale(1000)); // exact
+        px("water 4C", get("gravity").multiply(
+                get("kg").divide(get("m").pow(3))).scale(999.972));
+        px("water 60F", get("gravity").multiply(
+                get("kg").divide(get("m").pow(3))).scale(999.001));
+        // ps("g", get("gravity"))); // approx. should be `local'.
+        // avoid.
+
+        px("mercury 32F", "mercury 0C");
+        px("water 39F", "water 4C"); // actually 39.2 degF
+        px("mercury", "conventional mercury");
+        px("water", "conventional water");
+
+        pn("farad", "F");
+        ps("Hg", "mercury");
+        px("H2O", "water");
+
+        /*
+         * The following are compound units: units whose definitions consist of
+         * two or more base units. They may now be defined in terms of the
+         * preceding units.
+         */
+
+        /*
+         * ACCELERATION
+         */
+        ps("Gal", get("m").divide(get("s").pow(2)).scale(1e-2));
+        // avoid "gal" (gallon)
+        px("gals", "Gal"); // avoid "gal" (gallon)
+
+        /*
+         * AREA
+         */
+        pn("are", get("m").pow(2).scale(1e2)); // exact
+        pn("barn", get("m").pow(2).scale(1e-28)); // exact
+        pn("circular mil", get("m").pow(2).scale(5.067075e-10));
+        pn("darcy", get("m").pow(2).scale(9.869233e-13)); // permeability of
+        // porous solids
+        pn("hectare", get("hectoare")); // exact
+        px("har", "hectare"); // exact
+        pn("acre", get("rod").pow(2).scale(160)); // exact
+
+        ps("b", get("barn"));
+
+        /*
+         * ELECTRICITY AND MAGNETISM
+         */
+        pn("abfarad", get("F").scale(1e9)); // exact
+        pn("abhenry", get("H").scale(1e-9)); // exact
+        pn("abmho", get("S").scale(1e9)); // exact
+        pn("abohm", get("Ohm").scale(1e-9)); // exact
+        pn("megohm", get("Ohm").scale(1e6)); // exact
+        pn("kilohm", get("Ohm").scale(1e3)); // exact
+        pn("abvolt", get("V").scale(1e-8)); // exact
+        ps("e", get("C").scale(1.60217733 - 19));
+        pn("chemical faraday", get("C").scale(9.64957e4));
+        pn("physical faraday", get("C").scale(9.65219e4));
+        pn("C12 faraday", get("C").scale(9.648531e4));
+        pn("gamma", get("nT")); // exact
+        pn("gauss", get("T").scale(1e-4)); // exact
+        pn("maxwell", get("Wb").scale(1e-8)); // exact
+        ps("Oe", get("A").divide(get("m")).scale(7.957747e1));
+        pn("statcoulomb", get("C").scale(3.335640e-10));
+        pn("statfarad", get("F").scale(1.112650e-12));
+        pn("stathenry", get("H").scale(8.987554e11));
+        pn("statmho", get("S").scale(1.112650e-12));
+        pn("statohm", get("Ohm").scale(8.987554e11));
+        pn("statvolt", get("V").scale(2.997925e2));
+        pn("unit pole", get("Wb").scale(1.256637e-7));
+
+        pn("henry", "H");
+        pn("siemens", "S");
+        pn("ohm", "Ohm");
+        pn("tesla", "T");
+        pn("volt", "V");
+        pn("weber", "Wb");
+        pn("mho", "siemens");
+        pn("oersted", "Oe");
+        pn("faraday", "C12 faraday"); // charge of 1 mole of electrons
+        pn("coulomb", "C");
+
+        /*
+         * ENERGY (INCLUDES WORK)
+         */
+        ps("eV", get("J").scale(1.602177e-19));
+        ps("bev", get("eV").scale(1e9));
+        pn("erg", get("J").scale(1e-7)); // exact
+        pn("IT Btu", get("J").scale(1.05505585262e3)); // exact
+        pn("EC therm", get("J").scale(1.05506e8)); // exact
+        pn("thermochemical calorie", get("J").scale(4.184000)); // exact
+        pn("IT calorie", get("J").scale(4.1868)); // exact
+        px("ton TNT", get("J").scale(4.184e9));
+        pn("US therm", get("J").scale(1.054804e8)); // exact
+        ps("Wh", get("W").multiply(get("h")));
+
+        pn("joule", "J");
+        pn("therm", "US therm");
+        pn("watthour", "Wh");
+        ps("Btu", "IT Btu");
+        pn("calorie", "IT calorie");
+        pn("electronvolt", "eV");
+        pn("electron volt", "eV");
+        ps("thm", "therm");
+        ps("cal", "calorie");
+
+        /*
+         * FORCE
+         */
+        pn("dyne", get("N").scale(1e-5)); // exact
+        pn("pond", get("N").scale(9.806650e-3)); // exact
+        px("force kilogram", get("N").scale(9.806650)); // exact
+        px("force gram", get("N").scale(9.806650e-3)); // exact
+        px("force ounce", get("N").scale(2.780139e-1));
+        px("force pound", get("N").scale(4.4482216152605)); // exact
+        pn("poundal", get("N").scale(1.382550e-1));
+        pn("force ton", get("force pound").scale(2000)); // exact
+
+        ps("gf", "force gram");
+        ps("lbf", "force pound");
+        px("ounce force", "force ounce");
+        px("kilogram force", "force kilogram");
+        px("pound force", "force pound");
+        ps("ozf", "force ounce");
+        ps("kgf", "force kilogram");
+        px("ton force", "force ton");
+        px("gram force", "force gram");
+
+        pn("kip", get("lbf").scale(1e3));
+
+        /*
+         * HEAT
+         */
+        pn("clo", get("K").multiply(get("m").pow(2).divide(get("W"))).scale(
+                1.55e-1));
+
+        /*
+         * LIGHT
+         */
+        pn("lumen", "lm");
+        pn("lux", "lx");
+        pn("footcandle", get("lux").scale(1.076391e-1));
+        pn("footlambert", get("cd").divide(get("m").pow(2)).scale(3.426259));
+        pn("lambert", get("cd").divide(get("m").pow(2)).scale(1e4 / Math.PI)); // exact
+        pn("stilb", get("cd").divide(get("m").pow(2)).scale(1e4));
+        pn("phot", get("lm").divide(get("m").pow(2)).scale(1e4)); // exact
+        pn("nit", get("cd").multiply(get("m").pow(2))); // exact
+        pn("langley", get("J").divide(get("m").pow(2)).scale(4.184000e4)); // exact
+        pn("blondel", get("cd").divide(get("m").pow(2)).scale(1. / Math.PI));
+
+        pn("apostilb", "blondel");
+        ps("nt", "nit");
+        ps("ph", "phot");
+        ps("sb", "stilb");
+
+        /*
+         * MASS PER UNIT LENGTH
+         */
+        pn("denier", get("kg").divide(get("m")).scale(1.111111e-7));
+        pn("tex", get("kg").divide(get("m")).scale(1e-6));
+
+        /*
+         * MASS PER UNIT TIME (INCLUDES FLOW)
+         */
+        px("perm 0C", get("kg").divide(
+                get("Pa").multiply(get("s")).multiply(get("m").pow(2))).scale(
+                5.72135e-11));
+        px("perm 23C", get("kg").divide(
+                get("Pa").multiply(get("s")).multiply(get("m").pow(2))).scale(
+                5.74525e-11));
+
+        /*
+         * POWER
+         */
+        ps("VA", get("V").multiply(get("A")));
+        pn("voltampere", "VA");
+        pn("boiler horsepower", get("W").scale(9.80950e3));
+        pn("shaft horsepower", get("W").scale(7.456999e2));
+        pn("metric horsepower", get("W").scale(7.35499));
+        pn("electric horsepower", get("W").scale(7.460000e2)); // exact
+        pn("water horsepower", get("W").scale(7.46043e2));
+        pn("UK horsepower", get("W").scale(7.4570e2));
+        pn("refrigeration ton", get("Btu").divide(get("h")).scale(12000));
+
+        pn("horsepower", "shaft horsepower");
+        pn("ton of refrigeration", "refrigeration ton");
+        ps("hp", "horsepower");
+
+        /*
+         * PRESSURE OR STRESS
+         */
+        pn("bar", get("Pa").scale(1e5)); // exact
+        pn("standard atmosphere", get("Pa").scale(1.01325e5)); // exact
+        pn("technical atmosphere", get("kg").multiply(
+                get("gravity").divide(get("m").scale(.01).pow(2))));
+        px("inch H2O 39F", get("inch").multiply(get("water 39F")));
+        px("inch H2O 60F", get("inch").multiply(get("water 60F")));
+        px("inch Hg 32F", get("inch").multiply(get("mercury 32F")));
+        px("inch Hg 60F", get("inch").multiply(get("mercury 60F")));
+        px("mm Hg 0C", get("m").scale(1e-3).multiply(get("mercury 0C")));
+        ps("cmHg", get("m").scale(1e-2).multiply(get("Hg")));
+        ps("cmH2O", get("m").scale(1e-2).multiply(get("water")));
+        px("inch Hg", get("inch").multiply(get("Hg")));
+        px("torr", get("m").scale(1e-3).multiply(get("Hg")));
+        px("foot H2O", get("foot").multiply(get("water")));
+        ps("psi", get("pound").multiply(
+                get("gravity").divide(get("inch").pow(2))));
+        ps("ksi", get("kip").divide(get("inch").pow(2)));
+        pn("barie", get("N").divide(get("m").pow(2)).scale(0.1));
+
+        px("footH2O", "foot H2O");
+        ps("ftH2O", "foot H2O");
+        pn("millimeter Hg", "torr");
+        px("mm Hg", "torr");
+        px("mm Hg", "torr");
+        pn("pascal", "Pa");
+        px("pal", "Pa");
+        ps("inHg", "inch Hg");
+        px("in Hg", "inch Hg");
+        ps("at", "technical atmosphere");
+        pn("atmosphere", "standard atmosphere");
+        ps("atm", "standard atmosphere");
+        pn("barye", "barie");
+
+        /*
+         * RADIATION UNITS
+         */
+        ps("Ci", get("Bq").scale(3.7e10)); // exact
+        pn("rem", get("Sv").scale(1e-2)); // exact dose equivalent
+        ps("rd", get("Gy").scale(1e-2)); // absorbed dose. exact.
+        // use instead of "rad"
+        ps("R", get("C").divide(get("kg")).scale(2.58e-4));
+
+        ps("gray", "Gy");
+        px("sie", "Sv");
+        pn("becquerel", "Bq");
+        px("rads", "rd"); // avoid "rad" (radian)
+        pn("roentgen", "R");
+        pn("curie", "Ci");
+
+        /*
+         * VELOCITY (INCLUDES SPEED)
+         */
+        ps("c", get("m").divide(get("s")).scale(2.997925e+8));
+        pn("kt", get("nautical mile").divide(get("h")));
+
+        px("knot international", "kt");
+        px("international knot", "kt");
+        pn("knot", "kt");
+
+        /*
+         * VISCOSITY
+         */
+        ps("P", get("Pa").multiply(get("s")).scale(1e-1));
+        // exact
+        ps("St", get("m").pow(2).divide(get("s")).scale(1e-4));
+        // exact
+        ps("rhe", get("Pa").multiply(get("s")).pow(-1).scale(10));
+
+        pn("poise", "P");
+        pn("stokes", "St");
+
+        /*
+         * VOLUME (INCLUDES CAPACITY)
+         */
+        px("acre feet", get("m").pow(3).scale(1.233489e3));
+        // but `acre foot' is 1233.4867714897 m^3. Odd.
+        px("board feet", get("m").pow(3).scale(2.359737e-3));
+        pn("bushel", get("m").pow(3).scale(3.523907e-2));
+        pn("UK liquid gallon", get("m").pow(3).scale(4.546090e-3)); // exact
+        pn("Canadian liquid gallon", get("m").pow(3).scale(4.546090e-3)); // exact
+        pn("US dry gallon", get("m").pow(3).scale(4.404884e-3));
+        pn("US liquid gallon", get("m").pow(3).scale(3.785412e-3));
+        ps("cc", get("m").scale(.01).pow(3));
+        pn("liter", get("m").pow(3).scale(1e-3));
+        // exact. However, from 1901 to 1964, 1 liter = 1.000028 dm3
+        pn("stere", get("m").pow(3)); // exact
+        ps("Bz", get("m").scale(1e-6).pow(3).log(10.0));
+        pn("register ton", get("m").pow(3).scale(2.831685));
+        pn("US dry quart", get("US dry gallon").scale(1. / 4));
+        pn("US dry pint", get("US dry gallon").scale(1. / 8));
+        pn("US liquid quart", get("US liquid gallon").scale(1. / 4));
+        pn("US liquid pint", get("US liquid gallon").scale(1. / 8));
+        pn("US liquid cup", get("US liquid gallon").scale(1. / 16));
+        pn("US liquid gill", get("US liquid gallon").scale(1. / 32));
+        pn("US liquid ounce", get("US liquid gallon").scale(1. / 128));
+        pn("UK liquid quart", get("UK liquid gallon").scale(1. / 4));
+        pn("UK liquid pint", get("UK liquid gallon").scale(1. / 8));
+        pn("UK liquid cup", get("UK liquid gallon").scale(1. / 16));
+        pn("UK liquid gill", get("UK liquid gallon").scale(1. / 32));
+        pn("UK liquid ounce", get("UK liquid gallon").scale(1. / 160));
+
+        pn("US fluid ounce", "US liquid ounce");
+        pn("UK fluid ounce", "UK liquid ounce");
+        pn("liquid gallon", "US liquid gallon");
+        pn("fluid ounce", "US fluid ounce");
+        pn("dry quart", "US dry quart");
+        pn("dry pint", "US dry pint");
+
+        pn("liquid quart", get("liquid gallon").scale(1. / 4));
+        pn("liquid pint", get("liquid gallon").scale(1. / 8));
+        ps("bbl", get("US liquid gallon").scale(42));
+        // petroleum industry definition
+        ps("pt", get("liquid pint"));
+
+        pn("gallon", "liquid gallon");
+        pn("quart", "liquid quart");
+
+        pn("cup", get("liquid gallon").scale(1. / 16));
+        pn("gill", get("liquid gallon").scale(1. / 32));
+        pn("tablespoon", get("US fluid ounce").scale(0.5));
+        pn("teaspoon", get("tablespoon").scale(1. / 3));
+        pn("peck", get("bushel").scale(1. / 4));
+
+        px("acre foot", "acre feet");
+        px("board foot", "board feet");
+        pn("barrel", "bbl");
+
+        ps("gal", get("gallon")); // "gal" is also
+        // (unused) acceleration unit
+        ps("oz", "fluid ounce");
+        px("floz", "fluid ounce");
+        pn("Tbl", "tablespoon");
+        ps("Tbsp", "tablespoon");
+        ps("tbsp", "tablespoon");
+        ps("Tblsp", "tablespoon");
+        ps("tblsp", "tablespoon");
+        pn("litre", "liter");
+        ps("L", "liter");
+        ps("l", "liter");
+        px("tsp", "teaspoon");
+        ps("pk", "peck");
+        ps("bu", "bushel");
+
+        ps("fldr", get("floz").scale(1. / 8));
+        ps("dr", get("floz").scale(1. / 16));
+
+        pn("firkin", get("bbl").scale(1. / 4));
+        // exact but "barrel" is vague
+        pn("pint", "pt");
+        ps("dram", "dr");
+
+        /*
+         * VOLUME PER UNIT TIME
+         */
+        pn("sverdrup", get("m").pow(3).scale(1e6).divide(get("s"))); // oceanographic
+        // flow
+
+        /*
+         * COMPUTERS AND COMMUNICATION
+         */
+        pn("bit", new ScaledUnit(1)); // unit of information
+        ps("Bd", get("Hz"));
+        ps("bps", get("Hz"));
+        ps("cps", get("cycle").divide(get("s")));
+
+        pn("baud", "Bd");
+
+        /*
+         * MISC
+         */
+        pn("kayser", get("m").pow(-1).scale(1e2)); // exact
+        ps("rps", get("revolution").divide(get("s")));
+        ps("rpm", get("revolution").divide(get("min")));
+        px("geopotential", get("gravity"));
+        pn("work year", get("hours").scale(2056));
+        pn("work month", get("work year").scale(1. / 12));
+
+        pn("count", "");
+        ps("gp", "geopotential");
+        px("dynamic", "geopotential");
+        ps("gpm", get("geopotential").multiply(get("meter")));
+        // Potential vorticity unit:
+        ps("PVU", get("m").pow(2).divide(get("s")).multiply(get("K")).divide(get("kg")).scale(1e-6));
+    }
+
+    /**
+     * Gets an instance of this class.
+     * 
+     * This is the only way to obtain an instance of this class.
+     * 
+     * @throws UnitException
+     *             Something went wrong in generating the singleton instance of
+     *             the database. This should not occur and indicates an internal
+     *             inconsistancy.
+     */
+    public static UnitsDB instance() throws UnitException {
+        return db;
+    }
+
+    /**
+     * Get a unit.
+     * 
+     * @param name
+     *            The name of the unit to be retrieved. It may be the plural
+     *            form (e.g. "yards"). If an entry in the database corresponding
+     *            to the complete name is not found and the given name ends with
+     *            an `s', then a search will be made for the singular form (e.g.
+     *            "yard"). The matching entry will be returned only if the entry
+     *            permits a plural form. The entry may also have one or more SI
+     *            prefixes (e.g. "mega", "M").
+     * @return The appropriate unit or <code>null</code>. The unit will account
+     *         for any SI prefixes in the name.
+     * @require The argument is non-<code>null</code>.
+     */
+    @Override
+    public Unit get(final String name) {
+        Unit unit = super.get(name);
+
+        if (unit == null) {
+            // System.out.println("Entry \"" + name + "\" not found");
+            /*
+             * No entry by that name (including any possible plural form).
+             */
+
+            /*
+             * Strip prefix.
+             */
+            final Prefixer prefixer = new Prefixer(name);
+
+            if (prefixer.stripPrefix(prefixNames, prefixSymbols)) {
+                // System.out.println("Prefix found");
+                // System.out.println("Looking for \"" + prefixer.getString() +
+                // "\"");
+                /*
+                 * Prefix found. Recurse on the rest of the string.
+                 */
+                if ((unit = get(prefixer.getString())) != null) {
+                    try {
+                        unit = unit.scale(prefixer.getValue());
+                    }
+                    catch (final UnitException e) {
+                        unit = null;
+                    }
+                }
+            }
+        }
+
+        return unit;
+    }
+
+    /**
+     * Adds a symbol to the database for a unit already in the database.
+     */
+    protected void ps(final String symbol, final String unitID) {
+        putSymbol(symbol, super.get(unitID));
+    }
+
+    /**
+     * Adds a symbol and a new unit to the database.
+     * 
+     * @throws UnitException
+     */
+    protected void ps(final String symbol, final Unit unit)
+            throws UnitException {
+        putSymbol(symbol, unit.clone(symbol));
+    }
+
+    /**
+     * Adds a name, the plural form of the name, and a new unit to the database.
+     * 
+     * @throws UnitException
+     */
+    protected void pn(final String name, Unit unit) throws UnitException {
+        unit = unit.clone(name);
+        putName(name, unit);
+        putName(makePlural(name), unit);
+    }
+
+    /**
+     * Adds a name and it's regular plural form to the database for a unit
+     * that's already in the database.
+     */
+    protected void pn(final String name, final String unitID) {
+        final Unit unit = super.get(unitID);
+        putName(name, unit);
+        putName(makePlural(name), unit);
+    }
+
+    /**
+     * Adds a name that has no plural form and a new unit to the database.
+     * 
+     * @throws UnitException
+     */
+    protected void px(final String name, final Unit unit) throws UnitException {
+        putName(name, unit.clone(name));
+    }
+
+    /**
+     * Adds a name that has no plural form to the database for a unit that's
+     * already in the database.
+     */
+    protected void px(final String name, final String unitID) {
+        putName(name, super.get(unitID));
+    }
+
+    /**
+     * Inner (helper) class for parsing unit prefixes.
+     */
+    protected class Prefixer {
+        /**
+         * The string being parsed.
+         */
+        protected final String  string;
+
+        /**
+         * The current position within the string.
+         */
+        protected int           pos;
+
+        /**
+         * The current value of the prefix.
+         */
+        protected double        value;
+
+        /**
+         * Construct.
+         */
+        protected Prefixer(final String string) {
+            this.string = string;
+            this.pos = 0;
+            this.value = 1;
+        }
+
+        /**
+         * Strip leading prefix from the string.
+         */
+        protected boolean stripPrefix(final UnitPrefix[] names,
+                final UnitPrefix[] symbols) {
+            /*
+             * Perform a case-insensitive search on the names.
+             */
+            for (int icur = 0; icur < names.length; ++icur) {
+                final UnitPrefix prefix = names[icur];
+
+                if (string.regionMatches(true, pos, prefix.name, 0, prefix.name
+                        .length())) {
+                    value *= prefix.value;
+                    pos += prefix.name.length();
+                    return true;
+                }
+            }
+
+            /*
+             * Perform a case-sensitive search on the symbols.
+             */
+            for (int icur = 0; icur < symbols.length; ++icur) {
+                final UnitPrefix prefix = symbols[icur];
+
+                if (string.startsWith(prefix.name, pos)) {
+                    value *= prefix.value;
+                    pos += prefix.name.length();
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Indicate whether or not the beginning of the remainder of the string
+         * is less than a prefix.
+         */
+        protected boolean isLessThan(final UnitPrefix prefix) {
+            int icomp = 1;
+            final int n = Math.min(prefix.name.length(), string.length() - pos);
+
+            for (int i = 0; i < n; ++i) {
+                icomp = Character.getNumericValue(string.charAt(pos + i))
+                        - Character.getNumericValue(prefix.name.charAt(i));
+
+                if (icomp != 0) {
+                    break;
+                }
+            }
+
+            // System.out.println(string.substring(pos) +
+            // (icomp < 0 ? " < " : " >= ") + prefix.name);
+
+            return icomp < 0;
+        }
+
+        /**
+         * Return the current, remaining string.
+         */
+        protected String getString() {
+            return string.substring(pos);
+        }
+
+        /**
+         * Return the current prefix value.
+         */
+        protected double getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * Test this class.
+     * 
+     * @exception java.lang.Exception
+     *                A problem occurred.
+     */
+    public static void main(final String[] args) throws Exception {
+        final UnitsDB db = DefaultUnitsDB.instance();
+
+        System.out.println("% = " + db.get("%"));
+        System.out.println("abampere = " + db.get("abampere"));
+        System.out.println("firkin = " + db.get("firkin"));
+        System.out.println("MiCrOmEgAfirkin = " + db.get("MiCrOmEgAfirkin"));
+        System.out.println("celsius = " + db.get("celsius"));
+        System.out.println("fahrenheit = " + db.get("fahrenheit"));
+        System.out.println("m = " + db.get("m"));
+        System.out.println("mm = " + db.get("mm"));
+        System.out.println("dam = " + db.get("dam"));
+        System.out.println("million = " + db.get("million"));
+        System.out.println("pascal = " + db.get("pascal"));
+        System.out.println("Tperm_0C = " + db.get("Tperm_0C"));
+        System.out.println("MILLIpoundal = " + db.get("MILLIpoundal"));
+
+        System.out.println("");
+        db.list();
+    }
+}
diff --git a/visad/data/units/NoSuchUnitException.java b/visad/data/units/NoSuchUnitException.java
new file mode 100644
index 0000000..26fa461
--- /dev/null
+++ b/visad/data/units/NoSuchUnitException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: NoSuchUnitException.java,v 1.2 2001-11-27 22:29:42 dglo Exp $
+ */
+
+package visad.data.units;
+
+
+/**
+ * Exception thrown when a unit specification can't be parsed because of an
+ * unknown unit.
+ */
+public class
+NoSuchUnitException
+    extends ParseException
+{
+    /**
+     * Construct an exception with a message.
+     */
+    public NoSuchUnitException(String msg)
+    {
+	super(msg);
+    }
+}
diff --git a/visad/data/units/ParseException.java b/visad/data/units/ParseException.java
new file mode 100644
index 0000000..c43de0d
--- /dev/null
+++ b/visad/data/units/ParseException.java
@@ -0,0 +1,198 @@
+/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 4.1 */
+/* JavaCCOptions:KEEP_LINE_COL=null */
+package visad.data.units;
+
+/**
+ * This exception is thrown when parse errors are encountered.
+ * You can explicitly create objects of this exception type by
+ * calling the method generateParseException in the generated
+ * parser.
+ *
+ * You can modify this class to customize your error reporting
+ * mechanisms so long as you retain the public fields.
+ */
+public class ParseException extends Exception {
+
+  /**
+   * This constructor is used by the method "generateParseException"
+   * in the generated parser.  Calling this constructor generates
+   * a new object of this type with the fields "currentToken",
+   * "expectedTokenSequences", and "tokenImage" set.  The boolean
+   * flag "specialConstructor" is also set to true to indicate that
+   * this constructor was used to create this object.
+   * This constructor calls its super class with the empty string
+   * to force the "toString" method of parent class "Throwable" to
+   * print the error message in the form:
+   *     ParseException: <result of getMessage>
+   */
+  public ParseException(Token currentTokenVal,
+                        int[][] expectedTokenSequencesVal,
+                        String[] tokenImageVal
+                       )
+  {
+    super("");
+    specialConstructor = true;
+    currentToken = currentTokenVal;
+    expectedTokenSequences = expectedTokenSequencesVal;
+    tokenImage = tokenImageVal;
+  }
+
+  /**
+   * The following constructors are for use by you for whatever
+   * purpose you can think of.  Constructing the exception in this
+   * manner makes the exception behave in the normal way - i.e., as
+   * documented in the class "Throwable".  The fields "errorToken",
+   * "expectedTokenSequences", and "tokenImage" do not contain
+   * relevant information.  The JavaCC generated code does not use
+   * these constructors.
+   */
+
+  public ParseException() {
+    super();
+    specialConstructor = false;
+  }
+
+  /** Constructor with message. */
+  public ParseException(String message) {
+    super(message);
+    specialConstructor = false;
+  }
+
+  /**
+   * This variable determines which constructor was used to create
+   * this object and thereby affects the semantics of the
+   * "getMessage" method (see below).
+   */
+  protected boolean specialConstructor;
+
+  /**
+   * This is the last token that has been consumed successfully.  If
+   * this object has been created due to a parse error, the token
+   * followng this token will (therefore) be the first error token.
+   */
+  public Token currentToken;
+
+  /**
+   * Each entry in this array is an array of integers.  Each array
+   * of integers represents a sequence of tokens (by their ordinal
+   * values) that is expected at this point of the parse.
+   */
+  public int[][] expectedTokenSequences;
+
+  /**
+   * This is a reference to the "tokenImage" array of the generated
+   * parser within which the parse error occurred.  This array is
+   * defined in the generated ...Constants interface.
+   */
+  public String[] tokenImage;
+
+  /**
+   * This method has the standard behavior when this object has been
+   * created using the standard constructors.  Otherwise, it uses
+   * "currentToken" and "expectedTokenSequences" to generate a parse
+   * error message and returns it.  If this object has been created
+   * due to a parse error, and you do not catch it (it gets thrown
+   * from the parser), then this method is called during the printing
+   * of the final stack trace, and hence the correct error message
+   * gets displayed.
+   */
+  public String getMessage() {
+    if (!specialConstructor) {
+      return super.getMessage();
+    }
+    StringBuffer expected = new StringBuffer();
+    int maxSize = 0;
+    for (int i = 0; i < expectedTokenSequences.length; i++) {
+      if (maxSize < expectedTokenSequences[i].length) {
+        maxSize = expectedTokenSequences[i].length;
+      }
+      for (int j = 0; j < expectedTokenSequences[i].length; j++) {
+        expected.append(tokenImage[expectedTokenSequences[i][j]]).append(' ');
+      }
+      if (expectedTokenSequences[i][expectedTokenSequences[i].length - 1] != 0) {
+        expected.append("...");
+      }
+      expected.append(eol).append("    ");
+    }
+    String retval = "Encountered \"";
+    Token tok = currentToken.next;
+    for (int i = 0; i < maxSize; i++) {
+      if (i != 0) retval += " ";
+      if (tok.kind == 0) {
+        retval += tokenImage[0];
+        break;
+      }
+      retval += " " + tokenImage[tok.kind];
+      retval += " \"";
+      retval += add_escapes(tok.image);
+      retval += " \"";
+      tok = tok.next; 
+    }
+    retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn;
+    retval += "." + eol;
+    if (expectedTokenSequences.length == 1) {
+      retval += "Was expecting:" + eol + "    ";
+    } else {
+      retval += "Was expecting one of:" + eol + "    ";
+    }
+    retval += expected.toString();
+    return retval;
+  }
+
+  /**
+   * The end of line string for this machine.
+   */
+  protected String eol = System.getProperty("line.separator", "\n");
+ 
+  /**
+   * Used to convert raw characters to their escaped version
+   * when these raw version cannot be used as part of an ASCII
+   * string literal.
+   */
+  protected String add_escapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+}
+/* JavaCC - OriginalChecksum=ce3226cd83e3af798aebcf98b60f2bc6 (do not edit this line) */
diff --git a/visad/data/units/Parser.java b/visad/data/units/Parser.java
new file mode 100644
index 0000000..d560919
--- /dev/null
+++ b/visad/data/units/Parser.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: Parser.java,v 1.5 2009-09-28 19:25:58 donm Exp $
+ */
+
+package visad.data.units;
+
+import java.io.ByteArrayInputStream;
+
+import visad.Unit;
+import visad.UnitException;
+
+/**
+ * Class for parsing unit specifications.
+ */
+public class Parser {
+    /**
+     * The unit parser.
+     */
+    protected static final UnitParser   unitParser  = new UnitParser(System.in);
+
+    /**
+     * The singleton instance of this class.
+     */
+    protected static final Parser       parser      = new Parser();
+
+    /**
+     * Default constructor. Protected to ensure use of singleton.
+     */
+    protected Parser() {
+    }
+
+    /**
+     * Obtain the singleton instance of this class. Strictly speaking, this
+     * isn't necessary since <code>parse()</code> is a class method.
+     */
+    public static Parser instance() {
+        return parser;
+    }
+
+    /**
+     * Parse a string unit-specification.
+     * 
+     * @param spec
+     *            The string unit-specification.
+     * @precondition The specification is non-null.
+     * @exception ParseException
+     *                An error occurred while parsing the specification.
+     * @throws UnitException
+     *             if {@code spec} requires an unsupported operation.
+     */
+    public static synchronized Unit parse(final String spec)
+            throws ParseException, NoSuchUnitException {
+        unitParser.ReInit(new ByteArrayInputStream(spec.trim().getBytes()));
+
+        try {
+            return unitParser.unitSpec();
+        }
+        catch (final TokenMgrError e) {
+            throw new ParseException(e.getMessage());
+        }
+        catch (final UnitException e) {
+            throw new ParseException(e.getMessage());
+        }
+    }
+
+    /**
+     * Test this class.
+     */
+    public static void main(final String[] args) throws ParseException,
+            UnitException {
+        final Unit m = Parser.parse("m");
+        final Unit s = Parser.parse("s");
+        class Test {
+            String  spec;
+            Unit    unit;
+
+            Test(final String spec, final Unit unit) {
+                this.spec = spec;
+                this.unit = unit;
+            }
+        }
+        final Test[] tests = {
+                new Test("m", m),
+                new Test("2 m s", m.multiply(s).scale(2)),
+                new Test("3.14 m.s", m.multiply(s).scale(3.14)),
+                new Test("1e9 (m)", m.scale(1e9)),
+                new Test("(m s)2", m.multiply(s).pow(2)),
+                new Test("m2.s-1", m.pow(2).divide(s)),
+                new Test("m2 s^-1", m.pow(2).divide(s)),
+                new Test("(m/s)2", m.divide(s).pow(2)),
+                new Test("m2/s-1", m.pow(2).divide(s.pow(-1))),
+                new Test("m2/s^-1", m.pow(2).divide(s.pow(-1))),
+                new Test(".5 m/(.25 s)2", m.scale(.5).divide(
+                        s.scale(.25).pow(2))),
+                new Test("m.m-1.m", m.multiply(m.pow(-1)).multiply(m)),
+                new Test("2.0 m 1/2 s-1*(m/s^1)^-1 (1e9 m-1)(1e9 s-1)-1.m/s", m
+                        .scale(2).scale(1. / 2.).multiply(s.pow(-1)).multiply(
+                                m.divide(s.pow(1)).pow(-1)).multiply(
+                                m.pow(-1).scale(1e9)).multiply(
+                                s.pow(-1).scale(1e9).pow(-1)).multiply(m)
+                        .divide(s)), new Test("m/km", m.divide(m.scale(1e3))) };
+
+        for (int i = 0; i < tests.length; ++i) {
+            final Test test = tests[i];
+            final String spec = test.spec;
+            final Unit unit = test.unit;
+            if (!Parser.parse(spec).equals(unit)) {
+                throw new AssertionError(spec + " != " + unit);
+            }
+        }
+        try {
+            Parser.parse("unknown unit");
+            throw new AssertionError();
+        }
+        catch (final ParseException e) {
+        }
+        System.out.println("Done");
+    }
+}
diff --git a/visad/data/units/SimpleCharStream.java b/visad/data/units/SimpleCharStream.java
new file mode 100644
index 0000000..6028df4
--- /dev/null
+++ b/visad/data/units/SimpleCharStream.java
@@ -0,0 +1,472 @@
+/* Generated By:JavaCC: Do not edit this line. SimpleCharStream.java Version 4.1 */
+/* JavaCCOptions:STATIC=false */
+package visad.data.units;
+
+/**
+ * An implementation of interface CharStream, where the stream is assumed to
+ * contain only ASCII characters (without unicode processing).
+ */
+
+public class SimpleCharStream
+{
+/** Whether parser is static. */
+  public static final boolean staticFlag = false;
+  int bufsize;
+  int available;
+  int tokenBegin;
+/** Position in buffer. */
+  public int bufpos = -1;
+  protected int bufline[];
+  protected int bufcolumn[];
+
+  protected int column = 0;
+  protected int line = 1;
+
+  protected boolean prevCharIsCR = false;
+  protected boolean prevCharIsLF = false;
+
+  protected java.io.Reader inputStream;
+
+  protected char[] buffer;
+  protected int maxNextCharInd = 0;
+  protected int inBuf = 0;
+  protected int tabSize = 8;
+
+  protected void setTabSize(int i) { tabSize = i; }
+  protected int getTabSize(int i) { return tabSize; }
+
+
+  protected void ExpandBuff(boolean wrapAround)
+  {
+     char[] newbuffer = new char[bufsize + 2048];
+     int newbufline[] = new int[bufsize + 2048];
+     int newbufcolumn[] = new int[bufsize + 2048];
+
+     try
+     {
+        if (wrapAround)
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           System.arraycopy(buffer, 0, newbuffer,
+                                             bufsize - tokenBegin, bufpos);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos += (bufsize - tokenBegin));
+        }
+        else
+        {
+           System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin);
+           buffer = newbuffer;
+
+           System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin);
+           bufline = newbufline;
+
+           System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin);
+           bufcolumn = newbufcolumn;
+
+           maxNextCharInd = (bufpos -= tokenBegin);
+        }
+     }
+     catch (Throwable t)
+     {
+        throw new Error(t.getMessage());
+     }
+
+
+     bufsize += 2048;
+     available = bufsize;
+     tokenBegin = 0;
+  }
+
+  protected void FillBuff() throws java.io.IOException
+  {
+     if (maxNextCharInd == available)
+     {
+        if (available == bufsize)
+        {
+           if (tokenBegin > 2048)
+           {
+              bufpos = maxNextCharInd = 0;
+              available = tokenBegin;
+           }
+           else if (tokenBegin < 0)
+              bufpos = maxNextCharInd = 0;
+           else
+              ExpandBuff(false);
+        }
+        else if (available > tokenBegin)
+           available = bufsize;
+        else if ((tokenBegin - available) < 2048)
+           ExpandBuff(true);
+        else
+           available = tokenBegin;
+     }
+
+     int i;
+     try {
+        if ((i = inputStream.read(buffer, maxNextCharInd,
+                                    available - maxNextCharInd)) == -1)
+        {
+           inputStream.close();
+           throw new java.io.IOException();
+        }
+        else
+           maxNextCharInd += i;
+        return;
+     }
+     catch(java.io.IOException e) {
+        --bufpos;
+        backup(0);
+        if (tokenBegin == -1)
+           tokenBegin = bufpos;
+        throw e;
+     }
+  }
+
+/** Start. */
+  public char BeginToken() throws java.io.IOException
+  {
+     tokenBegin = -1;
+     char c = readChar();
+     tokenBegin = bufpos;
+
+     return c;
+  }
+
+  protected void UpdateLineColumn(char c)
+  {
+     column++;
+
+     if (prevCharIsLF)
+     {
+        prevCharIsLF = false;
+        line += (column = 1);
+     }
+     else if (prevCharIsCR)
+     {
+        prevCharIsCR = false;
+        if (c == '\n')
+        {
+           prevCharIsLF = true;
+        }
+        else
+           line += (column = 1);
+     }
+
+     switch (c)
+     {
+        case '\r' :
+           prevCharIsCR = true;
+           break;
+        case '\n' :
+           prevCharIsLF = true;
+           break;
+        case '\t' :
+           column--;
+           column += (tabSize - (column % tabSize));
+           break;
+        default :
+           break;
+     }
+
+     bufline[bufpos] = line;
+     bufcolumn[bufpos] = column;
+  }
+
+/** Read a character. */
+  public char readChar() throws java.io.IOException
+  {
+     if (inBuf > 0)
+     {
+        --inBuf;
+
+        if (++bufpos == bufsize)
+           bufpos = 0;
+
+        return buffer[bufpos];
+     }
+
+     if (++bufpos >= maxNextCharInd)
+        FillBuff();
+
+     char c = buffer[bufpos];
+
+     UpdateLineColumn(c);
+     return c;
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndColumn
+   */
+
+  public int getColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  /**
+   * @deprecated 
+   * @see #getEndLine
+   */
+
+  public int getLine() {
+     return bufline[bufpos];
+  }
+
+  /** Get token end column number. */
+  public int getEndColumn() {
+     return bufcolumn[bufpos];
+  }
+
+  /** Get token end line number. */
+  public int getEndLine() {
+     return bufline[bufpos];
+  }
+
+  /** Get token beginning column number. */
+  public int getBeginColumn() {
+     return bufcolumn[tokenBegin];
+  }
+
+  /** Get token beginning line number. */
+  public int getBeginLine() {
+     return bufline[tokenBegin];
+  }
+
+/** Backup a number of characters. */
+  public void backup(int amount) {
+
+    inBuf += amount;
+    if ((bufpos -= amount) < 0)
+       bufpos += bufsize;
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    available = bufsize = buffersize;
+    buffer = new char[buffersize];
+    bufline = new int[buffersize];
+    bufcolumn = new int[buffersize];
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.Reader dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.Reader dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+    inputStream = dstream;
+    line = startline;
+    column = startcolumn - 1;
+
+    if (buffer == null || buffersize != buffer.length)
+    {
+      available = bufsize = buffersize;
+      buffer = new char[buffersize];
+      bufline = new int[buffersize];
+      bufcolumn = new int[buffersize];
+    }
+    prevCharIsLF = prevCharIsCR = false;
+    tokenBegin = inBuf = maxNextCharInd = 0;
+    bufpos = -1;
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.Reader dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+  int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     this(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+  int startcolumn, int buffersize)
+  {
+     this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, startline, startcolumn, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, int startline,
+                          int startcolumn)
+  {
+     this(dstream, startline, startcolumn, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     this(dstream, encoding, 1, 1, 4096);
+  }
+
+  /** Constructor. */
+  public SimpleCharStream(java.io.InputStream dstream)
+  {
+     this(dstream, 1, 1, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                          int startcolumn, int buffersize) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(encoding == null ? new java.io.InputStreamReader(dstream) : new java.io.InputStreamReader(dstream, encoding), startline, startcolumn, buffersize);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, int startline,
+                          int startcolumn, int buffersize)
+  {
+     ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, String encoding) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, 1, 1, 4096);
+  }
+
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream)
+  {
+     ReInit(dstream, 1, 1, 4096);
+  }
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, String encoding, int startline,
+                     int startcolumn) throws java.io.UnsupportedEncodingException
+  {
+     ReInit(dstream, encoding, startline, startcolumn, 4096);
+  }
+  /** Reinitialise. */
+  public void ReInit(java.io.InputStream dstream, int startline,
+                     int startcolumn)
+  {
+     ReInit(dstream, startline, startcolumn, 4096);
+  }
+  /** Get token literal value. */
+  public String GetImage()
+  {
+     if (bufpos >= tokenBegin)
+        return new String(buffer, tokenBegin, bufpos - tokenBegin + 1);
+     else
+        return new String(buffer, tokenBegin, bufsize - tokenBegin) +
+                              new String(buffer, 0, bufpos + 1);
+  }
+
+  /** Get the suffix. */
+  public char[] GetSuffix(int len)
+  {
+     char[] ret = new char[len];
+
+     if ((bufpos + 1) >= len)
+        System.arraycopy(buffer, bufpos - len + 1, ret, 0, len);
+     else
+     {
+        System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0,
+                                                          len - bufpos - 1);
+        System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1);
+     }
+
+     return ret;
+  }
+
+  /** Reset buffer when finished. */
+  public void Done()
+  {
+     buffer = null;
+     bufline = null;
+     bufcolumn = null;
+  }
+
+  /**
+   * Method to adjust line and column numbers for the start of a token.
+   */
+  public void adjustBeginLineColumn(int newLine, int newCol)
+  {
+     int start = tokenBegin;
+     int len;
+
+     if (bufpos >= tokenBegin)
+     {
+        len = bufpos - tokenBegin + inBuf + 1;
+     }
+     else
+     {
+        len = bufsize - tokenBegin + bufpos + 1 + inBuf;
+     }
+
+     int i = 0, j = 0, k = 0;
+     int nextColDiff = 0, columnDiff = 0;
+
+     while (i < len &&
+            bufline[j = start % bufsize] == bufline[k = ++start % bufsize])
+     {
+        bufline[j] = newLine;
+        nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j];
+        bufcolumn[j] = newCol + columnDiff;
+        columnDiff = nextColDiff;
+        i++;
+     } 
+
+     if (i < len)
+     {
+        bufline[j] = newLine++;
+        bufcolumn[j] = newCol + columnDiff;
+
+        while (i++ < len)
+        {
+           if (bufline[j = start % bufsize] != bufline[++start % bufsize])
+              bufline[j] = newLine++;
+           else
+              bufline[j] = newLine;
+        }
+     }
+
+     line = bufline[j];
+     column = bufcolumn[j];
+  }
+
+}
+/* JavaCC - OriginalChecksum=8d8d56220c7197c919d317c10b1df483 (do not edit this line) */
diff --git a/visad/data/units/Token.java b/visad/data/units/Token.java
new file mode 100644
index 0000000..157b8ff
--- /dev/null
+++ b/visad/data/units/Token.java
@@ -0,0 +1,124 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 4.1 */
+/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null */
+package visad.data.units;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token {
+
+  /**
+   * An integer that describes the kind of this token.  This numbering
+   * system is determined by JavaCCParser, and a table of these numbers is
+   * stored in the file ...Constants.java.
+   */
+  public int kind;
+
+  /** The line number of the first character of this Token. */
+  public int beginLine;
+  /** The column number of the first character of this Token. */
+  public int beginColumn;
+  /** The line number of the last character of this Token. */
+  public int endLine;
+  /** The column number of the last character of this Token. */
+  public int endColumn;
+
+  /**
+   * The string image of the token.
+   */
+  public String image;
+
+  /**
+   * A reference to the next regular (non-special) token from the input
+   * stream.  If this is the last token from the input stream, or if the
+   * token manager has not read tokens beyond this one, this field is
+   * set to null.  This is true only if this token is also a regular
+   * token.  Otherwise, see below for a description of the contents of
+   * this field.
+   */
+  public Token next;
+
+  /**
+   * This field is used to access special tokens that occur prior to this
+   * token, but after the immediately preceding regular (non-special) token.
+   * If there are no such special tokens, this field is set to null.
+   * When there are more than one such special token, this field refers
+   * to the last of these special tokens, which in turn refers to the next
+   * previous special token through its specialToken field, and so on
+   * until the first special token (whose specialToken field is null).
+   * The next fields of special tokens refer to other special tokens that
+   * immediately follow it (without an intervening regular token).  If there
+   * is no such token, this field is null.
+   */
+  public Token specialToken;
+
+  /**
+   * An optional attribute value of the Token.
+   * Tokens which are not used as syntactic sugar will often contain
+   * meaningful values that will be used later on by the compiler or
+   * interpreter. This attribute value is often different from the image.
+   * Any subclass of Token that actually wants to return a non-null value can
+   * override this method as appropriate.
+   */
+  public Object getValue() {
+    return null;
+  }
+
+  /**
+   * No-argument constructor
+   */
+  public Token() {}
+
+  /**
+   * Constructs a new token for the specified Image.
+   */
+  public Token(int kind)
+  {
+     this(kind, null);
+  }
+
+  /**
+   * Constructs a new token for the specified Image and Kind.
+   */
+  public Token(int kind, String image)
+  {
+     this.kind = kind;
+     this.image = image;
+  }
+
+  /**
+   * Returns the image.
+   */
+  public String toString()
+  {
+     return image;
+  }
+
+  /**
+   * Returns a new Token object, by default. However, if you want, you
+   * can create and return subclass objects based on the value of ofKind.
+   * Simply add the cases to the switch for all those special cases.
+   * For example, if you have a subclass of Token called IDToken that
+   * you want to create if ofKind is ID, simply add something like :
+   *
+   *    case MyParserConstants.ID : return new IDToken(ofKind, image);
+   *
+   * to the following switch statement. Then you can cast matchedToken
+   * variable to the appropriate type and use sit in your lexical actions.
+   */
+  public static Token newToken(int ofKind, String image)
+  {
+     switch(ofKind)
+     {
+       default : return new Token(ofKind, image);
+     }
+  }
+
+  public static Token newToken(int ofKind)
+  {
+     return newToken(ofKind, null);
+  }
+
+}
+/* JavaCC - OriginalChecksum=f05bbd556aee21b2888cdbff8b522521 (do not edit this line) */
diff --git a/visad/data/units/TokenMgrError.java b/visad/data/units/TokenMgrError.java
new file mode 100644
index 0000000..be98be1
--- /dev/null
+++ b/visad/data/units/TokenMgrError.java
@@ -0,0 +1,140 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 4.1 */
+/* JavaCCOptions: */
+package visad.data.units;
+
+/** Token Manager Error. */
+public class TokenMgrError extends Error
+{
+
+   /*
+    * Ordinals for various reasons why an Error of this type can be thrown.
+    */
+
+   /**
+    * Lexical error occurred.
+    */
+   static final int LEXICAL_ERROR = 0;
+
+   /**
+    * An attempt was made to create a second instance of a static token manager.
+    */
+   static final int STATIC_LEXER_ERROR = 1;
+
+   /**
+    * Tried to change to an invalid lexical state.
+    */
+   static final int INVALID_LEXICAL_STATE = 2;
+
+   /**
+    * Detected (and bailed out of) an infinite loop in the token manager.
+    */
+   static final int LOOP_DETECTED = 3;
+
+   /**
+    * Indicates the reason why the exception is thrown. It will have
+    * one of the above 4 values.
+    */
+   int errorCode;
+
+   /**
+    * Replaces unprintable characters by their escaped (or unicode escaped)
+    * equivalents in the given string
+    */
+   protected static final String addEscapes(String str) {
+      StringBuffer retval = new StringBuffer();
+      char ch;
+      for (int i = 0; i < str.length(); i++) {
+        switch (str.charAt(i))
+        {
+           case 0 :
+              continue;
+           case '\b':
+              retval.append("\\b");
+              continue;
+           case '\t':
+              retval.append("\\t");
+              continue;
+           case '\n':
+              retval.append("\\n");
+              continue;
+           case '\f':
+              retval.append("\\f");
+              continue;
+           case '\r':
+              retval.append("\\r");
+              continue;
+           case '\"':
+              retval.append("\\\"");
+              continue;
+           case '\'':
+              retval.append("\\\'");
+              continue;
+           case '\\':
+              retval.append("\\\\");
+              continue;
+           default:
+              if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
+                 String s = "0000" + Integer.toString(ch, 16);
+                 retval.append("\\u" + s.substring(s.length() - 4, s.length()));
+              } else {
+                 retval.append(ch);
+              }
+              continue;
+        }
+      }
+      return retval.toString();
+   }
+
+   /**
+    * Returns a detailed message for the Error when it is thrown by the
+    * token manager to indicate a lexical error.
+    * Parameters : 
+    *    EOFSeen     : indicates if EOF caused the lexical error
+    *    curLexState : lexical state in which this error occurred
+    *    errorLine   : line number when the error occurred
+    *    errorColumn : column number when the error occurred
+    *    errorAfter  : prefix that was seen before this error occurred
+    *    curchar     : the offending character
+    * Note: You can customize the lexical error message by modifying this method.
+    */
+   protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+      return("Lexical error at line " +
+           errorLine + ", column " +
+           errorColumn + ".  Encountered: " +
+           (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+           "after : \"" + addEscapes(errorAfter) + "\"");
+   }
+
+   /**
+    * You can also modify the body of this method to customize your error messages.
+    * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+    * of end-users concern, so you can return something like : 
+    *
+    *     "Internal Error : Please file a bug report .... "
+    *
+    * from this method for such cases in the release version of your parser.
+    */
+   public String getMessage() {
+      return super.getMessage();
+   }
+
+   /*
+    * Constructors of various flavors follow.
+    */
+
+   /** No arg constructor. */
+   public TokenMgrError() {
+   }
+
+   /** Constructor with message and reason. */
+   public TokenMgrError(String message, int reason) {
+      super(message);
+      errorCode = reason;
+   }
+
+   /** Full Constructor. */
+   public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+      this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+   }
+}
+/* JavaCC - OriginalChecksum=21553b0b02daa0b16c6632c42c76618e (do not edit this line) */
diff --git a/visad/data/units/UnitParser.java b/visad/data/units/UnitParser.java
new file mode 100644
index 0000000..bccac80
--- /dev/null
+++ b/visad/data/units/UnitParser.java
@@ -0,0 +1,1994 @@
+/* Generated By:JavaCC: Do not edit this line. UnitParser.java */
+package visad.data.units;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import visad.DerivedUnit;
+import visad.SI;
+import visad.Unit;
+import visad.UnitException;
+
+/**
+ * A class for parsing string unit specifications.
+ * 
+ * Instances are thread-compatible but not thread-safe.
+ */
+public class UnitParser implements UnitParserConstants {
+    /**
+     * The units database.
+     */
+    protected static UnitsDB    unitsDB         = null;
+
+    static {
+        try {
+            unitsDB = DefaultUnitsDB.instance();
+        }
+        catch (final UnitException e) {
+        }
+    }
+
+    /**
+     * The canonical time unit.
+     */
+    protected static final Unit SECOND          = SI.second;
+
+    /**
+     * The Julian day number of the (artificial) time origin.
+     */
+    protected static final long julianDayOrigin = UnitParser.julianDay(2001, 1,
+                                                        1);
+
+    /**
+     * The dimensionless unit one.
+     */
+    private static final Unit   ONE             = new DerivedUnit();
+
+    /**
+     * Compute the Julian day number of a date.
+     */
+    public static long julianDay(int year, final int month, final int day) {
+        final long igreg = 15 + 31 * (10 + (12 * 1582));
+        int iy; // signed, origin-0 year
+        int ja; // Julian century
+        int jm; // Julian month
+        int jy; // Julian year
+        long julday; // returned Julian day number
+
+        /*
+         * Because there is no 0 BC or 0 AD, assume the user wants the start of
+         * the common era if they specify year 0.
+         */
+        if (year == 0) {
+            year = 1;
+        }
+
+        iy = year;
+        if (year < 0) {
+            iy++;
+        }
+        if (month > 2) {
+            jy = iy;
+            jm = month + 1;
+        }
+        else {
+            jy = iy - 1;
+            jm = month + 13;
+
+        }
+
+        julday = day + (int) (30.6001 * jm);
+        if (jy >= 0) {
+            julday += 365 * jy;
+            julday += 0.25 * jy;
+        }
+        else {
+            double xi = 365.25 * jy;
+
+            if ((int) xi != xi) {
+                xi -= 1;
+            }
+            julday += (int) xi;
+        }
+        julday += 1720995;
+
+        if (day + (31 * (month + (12 * iy))) >= igreg) {
+            ja = jy / 100;
+            julday -= ja;
+            julday += 2;
+            julday += ja / 4;
+        }
+
+        return julday;
+    }
+
+    /**
+     * Encode a timestamp as a double value in units of seconds.
+     */
+    public static double encodeTimestamp(final int year, final int month,
+            final int day, final int hour, final int minute,
+            final float second, final int zone) {
+        return (julianDay(year, month, day) - julianDayOrigin) * 86400.0
+                + (hour * 60 + minute - zone) * 60 + second;
+    }
+
+    private static void myAssert(final UnitParser parser, final String spec,
+            final Unit expect) throws UnitException, ParseException {
+        if (!parser.parse(spec).equals(expect)) {
+            throw new AssertionError("Got \"" + spec + "; expected \"" + expect
+                    + "\"");
+        }
+    }
+
+    /**
+     * Parses a string unit specification.
+     * 
+     * @param spec
+     *            The unit specification.
+     * @return A {@link Unit} corresponding to {@code spec}.
+     * @throws NullPointerException
+     *             if {@code spec} is {@code null}.
+     * @throws ParseException
+     *             if a parsing error occurred.
+     * @throws UnitException
+     *             if the string specifies an invalid unit operation.
+     */
+    public Unit parse(final String spec) throws ParseException, UnitException {
+        ReInit(new ByteArrayInputStream(spec.getBytes()));
+        return unitSpec();
+    }
+
+    /**
+     * Test this class.
+     */
+    public static void main(final String[] args) throws Exception {
+        final UnitParser parser = new UnitParser(System.in);
+        final LineNumberReader lineInput = new LineNumberReader(
+                new InputStreamReader(System.in));
+        final Unit s = unitsDB.get("s");
+        final Unit m = unitsDB.get("m");
+        final Unit kg = unitsDB.get("kg");
+        final Unit w = unitsDB.get("W");
+        final Unit epoch = s.shift(encodeTimestamp(1970, 1, 1, 0, 0, 0f, 0));
+
+        myAssert(parser, "m m", m.multiply(m));
+        myAssert(parser, "m.m", m.multiply(m));
+        myAssert(parser, "(m)(m)", m.pow(2));
+        myAssert(parser, "m/s/s", m.divide(s).divide(s));
+        myAssert(parser, "m2", m.pow(2));
+        myAssert(parser, "m2.s", m.pow(2).multiply(s));
+        myAssert(parser, "m2/s", m.pow(2).divide(s));
+        myAssert(parser, "m^2/s", m.pow(2).divide(s));
+        myAssert(parser, "m s @ 5", m.multiply(s).shift(5));
+        myAssert(parser, "m2 s @ 5", m.pow(2).multiply(s).shift(5));
+        myAssert(parser, "m2 s-1 @ 5", m.pow(2).divide(s).shift(5));
+        myAssert(parser, "m s from 5", m.multiply(s).shift(5));
+        myAssert(parser, "m s kg @ 5", m.multiply(s).multiply(kg).shift(5));
+        myAssert(parser, "s at 19700101", epoch);
+        myAssert(parser, "s at 19700101T000000", epoch);
+        myAssert(parser, "s at 19700101T000000.00", epoch);
+        myAssert(parser, "s @ 1970-01-01T00:00:00.00", epoch);
+        myAssert(parser, "s @ 1970-01-01 00:00:00.00", epoch);
+        myAssert(parser, "s @ 1970-01-01T00:00:00.00 -12", s
+                .shift(encodeTimestamp(1970, 1, 1, 12, 0, 0f, 0)));
+        myAssert(parser, "s @ 1970-01-01T00:00:00.00 -12", s
+                .shift(encodeTimestamp(1970, 1, 1, 0, 0, 0f, -12 * 60)));
+        myAssert(parser, "lg(re: 1)", ONE.log(10));
+        myAssert(parser, "0.1 lg(re 1 mW)", w.scale(1e-3).log(10).scale(0.1));
+        myAssert(parser, "m", m);
+        myAssert(parser, "2 m s", m.multiply(s).scale(2));
+        myAssert(parser, "3.14 m.s", m.multiply(s).scale(3.14));
+        myAssert(parser, "1e9 (m)", m.scale(1e9));
+        myAssert(parser, "(m s)2", m.multiply(s).pow(2));
+        myAssert(parser, "m2.s-1", m.pow(2).divide(s));
+        myAssert(parser, "m2 s^-1", m.pow(2).divide(s));
+        myAssert(parser, "(m/s)2", m.divide(s).pow(2));
+        myAssert(parser, "m2/s-1", m.pow(2).divide(s.pow(-1)));
+        myAssert(parser, "m2/s^-1", m.pow(2).divide(s.pow(-1)));
+        myAssert(parser, ".5 m/(.25 s)2", m.scale(.5).divide(
+                s.scale(.25).pow(2)));
+        myAssert(parser, "m.m-1.m", m.multiply(m.pow(-1)).multiply(m));
+        myAssert(parser, "2.0 m 1/2 s-1*(m/s^1)^-1 (1e9 m-1)(1e9 s-1)-1.m/s", m
+                .scale(2).scale(1. / 2.).multiply(s.pow(-1)).multiply(
+                        m.divide(s.pow(1)).pow(-1)).multiply(
+                        m.pow(-1).scale(1e9)).multiply(
+                        s.pow(-1).scale(1e9).pow(-1)).multiply(m).divide(s));
+        myAssert(parser, "m/km", m.divide(m.scale(1e3)));
+
+        for (;;) {
+            System.out.print("Enter a unit specification or ^D to quit: ");
+
+            String spec = lineInput.readLine();
+            if (spec == null) {
+                break;
+            }
+
+            spec = spec.trim();
+
+            if (spec.length() > 0) {
+                parser.ReInit(new ByteArrayInputStream(spec.getBytes()));
+
+                try {
+                    final Unit unit = parser.unitSpec();
+                    System.out.println("unit = " + unit);
+                    System.out.println("definition = " + unit.getDefinition());
+                }
+                catch (final ParseException e) {
+                    System.out.println(e.getMessage());
+                }
+            }
+        }
+        System.out.println("");
+    }
+
+    final public Unit unitSpec() throws ParseException, UnitException {
+        Unit unit = ONE;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+        case MINUS:
+        case UINT:
+        case LPAREN:
+        case PERIOD:
+        case SYMBOL:
+        case T:
+        case NAME:
+        case LB:
+        case LN:
+        case LG:
+            unit = shiftExpr();
+            break;
+        default:
+            jj_la1[0] = jj_gen;
+            ;
+        }
+        jj_consume_token(0);
+        {
+            if (true) {
+                return unit;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Unit shiftExpr() throws ParseException, UnitException {
+        Unit unit;
+        double origin;
+        unit = productExpr();
+        if (jj_2_1(2)) {
+            jj_consume_token(SHIFT);
+            if (Unit.canConvert(unit, SECOND)) {
+                origin = timeOriginExpr();
+                unit = unit.shift(unit.toThis(origin, SECOND));
+            }
+            else {
+                origin = number();
+                if (origin != 0) {
+                    unit = unit.shift(origin);
+                }
+            }
+        }
+        else {
+            ;
+        }
+        {
+            if (true) {
+                return unit;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Unit productExpr() throws ParseException, UnitException {
+        Unit unit, unit2;
+        unit = powerExpr();
+        label_1: while (true) {
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case SP:
+            case PLUS:
+            case MINUS:
+            case UINT:
+            case LPAREN:
+            case PERIOD:
+            case STAR:
+            case DIVIDE:
+            case SYMBOL:
+            case T:
+            case NAME:
+            case LB:
+            case LN:
+            case LG:
+                ;
+                break;
+            default:
+                jj_la1[1] = jj_gen;
+                break label_1;
+            }
+            if (jj_2_2(2)) {
+                jj_consume_token(DIVIDE);
+                unit2 = powerExpr();
+                unit = unit.divide(unit2);
+            }
+            else {
+                switch ((jj_ntk == -1)
+                        ? jj_ntk()
+                        : jj_ntk) {
+                case SP:
+                case PLUS:
+                case MINUS:
+                case UINT:
+                case LPAREN:
+                case PERIOD:
+                case STAR:
+                case SYMBOL:
+                case T:
+                case NAME:
+                case LB:
+                case LN:
+                case LG:
+                    switch ((jj_ntk == -1)
+                            ? jj_ntk()
+                            : jj_ntk) {
+                    case SP:
+                    case PERIOD:
+                    case STAR:
+                        switch ((jj_ntk == -1)
+                                ? jj_ntk()
+                                : jj_ntk) {
+                        case PERIOD:
+                            jj_consume_token(PERIOD);
+                            break;
+                        case STAR:
+                            jj_consume_token(STAR);
+                            break;
+                        case SP:
+                            jj_consume_token(SP);
+                            break;
+                        default:
+                            jj_la1[2] = jj_gen;
+                            jj_consume_token(-1);
+                            throw new ParseException();
+                        }
+                        break;
+                    default:
+                        jj_la1[3] = jj_gen;
+                        ;
+                    }
+                    unit2 = powerExpr();
+                    unit = unit.multiply(unit2);
+                    break;
+                default:
+                    jj_la1[4] = jj_gen;
+                    jj_consume_token(-1);
+                    throw new ParseException();
+                }
+            }
+        }
+        {
+            if (true) {
+                return unit;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Unit powerExpr() throws ParseException, UnitException {
+        Unit unit;
+        int exponent;
+        unit = basicExpr();
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+        case MINUS:
+        case UINT:
+        case RAISE:
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case RAISE:
+                jj_consume_token(RAISE);
+                break;
+            default:
+                jj_la1[5] = jj_gen;
+                ;
+            }
+            exponent = integer();
+            unit = unit.pow(exponent);
+            break;
+        default:
+            jj_la1[6] = jj_gen;
+            ;
+        }
+        {
+            if (true) {
+                return unit;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Unit basicExpr() throws ParseException, UnitException {
+        Unit unit;
+        double number;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+        case MINUS:
+        case UINT:
+        case PERIOD:
+            number = number();
+            unit = ONE.scale(number);
+            break;
+        case SYMBOL:
+        case T:
+        case NAME:
+            unit = unitIdentifier();
+            break;
+        case LB:
+        case LN:
+        case LG:
+            unit = logExpr();
+            break;
+        case LPAREN:
+            jj_consume_token(LPAREN);
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case SP:
+                jj_consume_token(SP);
+                break;
+            default:
+                jj_la1[7] = jj_gen;
+                ;
+            }
+            unit = shiftExpr();
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case SP:
+                jj_consume_token(SP);
+                break;
+            default:
+                jj_la1[8] = jj_gen;
+                ;
+            }
+            jj_consume_token(RPAREN);
+            break;
+        default:
+            jj_la1[9] = jj_gen;
+            jj_consume_token(-1);
+            throw new ParseException();
+        }
+        {
+            if (true) {
+                return unit;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Unit unitIdentifier() throws ParseException {
+        Token token;
+        Unit unit;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case T:
+            token = jj_consume_token(T);
+            break;
+        case NAME:
+            token = jj_consume_token(NAME);
+            break;
+        case SYMBOL:
+            token = jj_consume_token(SYMBOL);
+            break;
+        default:
+            jj_la1[10] = jj_gen;
+            jj_consume_token(-1);
+            throw new ParseException();
+        }
+        unit = unitsDB.get(token.image);
+        if (unit == null) {
+            {
+                if (true) {
+                    throw new NoSuchUnitException("Unit not in database");
+                }
+            }
+        }
+        {
+            if (true) {
+                return unit;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Unit logExpr() throws ParseException, UnitException {
+        double base;
+        Unit ref;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case LB:
+            jj_consume_token(LB);
+            base = 2;
+            break;
+        case LN:
+            jj_consume_token(LN);
+            base = Math.E;
+            break;
+        case LG:
+            jj_consume_token(LG);
+            base = 10;
+            break;
+        default:
+            jj_la1[11] = jj_gen;
+            jj_consume_token(-1);
+            throw new ParseException();
+        }
+        ref = productExpr();
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case SP:
+            jj_consume_token(SP);
+            break;
+        default:
+            jj_la1[12] = jj_gen;
+            ;
+        }
+        jj_consume_token(RPAREN);
+        {
+            if (true) {
+                return ref.log(base);
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public double number() throws ParseException {
+        double number;
+        if (jj_2_3(3)) {
+            number = real();
+        }
+        else {
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case PLUS:
+            case MINUS:
+            case UINT:
+                number = integer();
+                break;
+            default:
+                jj_la1[13] = jj_gen;
+                jj_consume_token(-1);
+                throw new ParseException();
+            }
+        }
+        {
+            if (true) {
+                return number;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public double real() throws ParseException {
+        int sign = 1;
+        double tenFactor = 1;
+        double udecimal;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+        case MINUS:
+            sign = sign();
+            break;
+        default:
+            jj_la1[14] = jj_gen;
+            ;
+        }
+        if (jj_2_4(2)) {
+            udecimal = unsignedDecimal();
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case REAL_EXP:
+                tenFactor = tenFactor();
+                break;
+            default:
+                jj_la1[15] = jj_gen;
+                ;
+            }
+        }
+        else {
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case UINT:
+                udecimal = unsignedInteger();
+                tenFactor = tenFactor();
+                break;
+            default:
+                jj_la1[16] = jj_gen;
+                jj_consume_token(-1);
+                throw new ParseException();
+            }
+        }
+        {
+            if (true) {
+                return sign * udecimal * tenFactor;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public int sign() throws ParseException {
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+            jj_consume_token(PLUS);
+            {
+                if (true) {
+                    return 1;
+                }
+            }
+            break;
+        case MINUS:
+            jj_consume_token(MINUS);
+            {
+                if (true) {
+                    return -1;
+                }
+            }
+            break;
+        default:
+            jj_la1[17] = jj_gen;
+            jj_consume_token(-1);
+            throw new ParseException();
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public double unsignedDecimal() throws ParseException {
+        int integer = 0;
+        Token token;
+        double fraction = 0;
+        if (jj_2_5(3)) {
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case UINT:
+                integer = unsignedInteger();
+                break;
+            default:
+                jj_la1[18] = jj_gen;
+                ;
+            }
+            jj_consume_token(PERIOD);
+            token = jj_consume_token(UINT);
+            fraction = Double.valueOf("." + token.image);
+        }
+        else {
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case UINT:
+                integer = unsignedInteger();
+                jj_consume_token(PERIOD);
+                break;
+            default:
+                jj_la1[19] = jj_gen;
+                jj_consume_token(-1);
+                throw new ParseException();
+            }
+        }
+        {
+            if (true) {
+                return integer + fraction;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public double tenFactor() throws ParseException {
+        Token token;
+        token = jj_consume_token(REAL_EXP);
+        {
+            if (true) {
+                return Double.valueOf("1" + token.image);
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public int integer() throws ParseException {
+        int magnitude;
+        int sign = 1;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+        case MINUS:
+            sign = sign();
+            break;
+        default:
+            jj_la1[20] = jj_gen;
+            ;
+        }
+        magnitude = unsignedInteger();
+        {
+            if (true) {
+                return sign * magnitude;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public int unsignedInteger() throws ParseException {
+        Token token;
+        token = jj_consume_token(UINT);
+        {
+            if (true) {
+                return Integer.valueOf(token.image);
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    /*
+     * See <http://www.cl.cam.ac.uk/~mgk25/iso-time.html> for a discussion of
+     * the relevant timestamp format or lookup "ISO 8601".
+     */
+    final public double timeOriginExpr() throws ParseException {
+        Calendar calendar;
+        calendar = dateExpr();
+        if (jj_2_7(2)) {
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case T:
+                jj_consume_token(T);
+                break;
+            case SP:
+                jj_consume_token(SP);
+                break;
+            default:
+                jj_la1[21] = jj_gen;
+                jj_consume_token(-1);
+                throw new ParseException();
+            }
+            clockExpr(calendar);
+            if (jj_2_6(2)) {
+                switch ((jj_ntk == -1)
+                        ? jj_ntk()
+                        : jj_ntk) {
+                case SP:
+                    jj_consume_token(SP);
+                    break;
+                default:
+                    jj_la1[22] = jj_gen;
+                    ;
+                }
+                zoneExpr(calendar);
+            }
+            else {
+                ;
+            }
+        }
+        else {
+            ;
+        }
+        {
+            if (true) {
+                return encodeTimestamp(calendar.get(Calendar.YEAR), calendar
+                        .get(Calendar.MONTH) + 1, calendar
+                        .get(Calendar.DAY_OF_MONTH), calendar
+                        .get(Calendar.HOUR_OF_DAY), calendar
+                        .get(Calendar.MINUTE), (float) (calendar
+                        .get(Calendar.SECOND) + calendar
+                        .get(Calendar.MILLISECOND) / 1e3), calendar
+                        .get(Calendar.ZONE_OFFSET)
+                        / (1000 * 60));
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Calendar dateExpr() throws ParseException {
+        int sign = 1;
+        int year;
+        int month = 1;
+        int day = 1;
+        boolean packed = true;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+        case MINUS:
+            sign = sign();
+            break;
+        default:
+            jj_la1[23] = jj_gen;
+            ;
+        }
+        year = unsignedInteger();
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case MINUS:
+            jj_consume_token(MINUS);
+            month = unsignedInteger();
+            packed = false;
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case MINUS:
+                jj_consume_token(MINUS);
+                day = unsignedInteger();
+                break;
+            default:
+                jj_la1[24] = jj_gen;
+                ;
+            }
+            break;
+        default:
+            jj_la1[25] = jj_gen;
+            ;
+        }
+        if (packed) {
+            if (year >= 10000101) {
+                day = year % 100;
+                year /= 100;
+            }
+            if (year >= 100001) {
+                month = year % 100;
+                year /= 100;
+            }
+            if (sign < 0) {
+                year = -year;
+            }
+        }
+        if (month < 1 || month > 12) {
+            if (true) {
+                throw new ParseException("invalid month in timestamp");
+            }
+        }
+        if (day < 1 || day > 31) {
+            if (true) {
+                throw new ParseException("invalid day in timestamp");
+            }
+        }
+        final Calendar calendar = Calendar.getInstance(TimeZone
+                .getTimeZone("UTC"));
+        calendar.clear();
+        calendar.set(year, month - 1, day);
+        {
+            if (true) {
+                return calendar;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Calendar clockExpr(final Calendar calendar)
+            throws ParseException {
+        double hour;
+        int minute = 0;
+        double seconds = 0;
+        boolean packed = true;
+        if (jj_2_8(2)) {
+            hour = unsignedDecimal();
+        }
+        else {
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case UINT:
+                hour = unsignedInteger();
+                break;
+            default:
+                jj_la1[26] = jj_gen;
+                jj_consume_token(-1);
+                throw new ParseException();
+            }
+        }
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case COLON:
+            jj_consume_token(COLON);
+            minute = unsignedInteger();
+            packed = false;
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case COLON:
+                jj_consume_token(COLON);
+                if (jj_2_9(2)) {
+                    seconds = unsignedDecimal();
+                }
+                else {
+                    switch ((jj_ntk == -1)
+                            ? jj_ntk()
+                            : jj_ntk) {
+                    case UINT:
+                        seconds = unsignedInteger();
+                        break;
+                    default:
+                        jj_la1[27] = jj_gen;
+                        jj_consume_token(-1);
+                        throw new ParseException();
+                    }
+                }
+                break;
+            default:
+                jj_la1[28] = jj_gen;
+                ;
+            }
+            break;
+        default:
+            jj_la1[29] = jj_gen;
+            ;
+        }
+        if (packed) {
+            if (hour >= 100000) {
+                seconds = hour % 100;
+                hour /= 100;
+            }
+            if (hour >= 1000) {
+                minute = (int) (hour % 100);
+                hour /= 100;
+            }
+        }
+        if (hour < 0 || hour > 23) {
+            if (true) {
+                throw new ParseException("invalid hour in timestamp");
+            }
+        }
+        if (minute < 0 || minute > 59) {
+            if (true) {
+                throw new ParseException("invalid minute in timestamp");
+            }
+        }
+        if (seconds < 0 || seconds > 61) {
+            if (true) {
+                throw new ParseException("invalid seconds in timestamp");
+            }
+        }
+        calendar.set(Calendar.HOUR_OF_DAY, (int) Math.round(hour));
+        calendar.set(Calendar.MINUTE, minute);
+        final int s = (int) seconds;
+        calendar.set(Calendar.SECOND, s);
+        final int ms = (int) ((seconds - s) * 1000);
+        calendar.set(Calendar.MILLISECOND, ms);
+        {
+            if (true) {
+                return calendar;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    final public Calendar zoneExpr(final Calendar calendar)
+            throws ParseException {
+        int sign = 1;
+        int zoneHour;
+        int zoneMinute = 0;
+        Token token;
+        TimeZone timeZone;
+        switch ((jj_ntk == -1)
+                ? jj_ntk()
+                : jj_ntk) {
+        case PLUS:
+        case MINUS:
+        case UINT:
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case PLUS:
+            case MINUS:
+                sign = sign();
+                break;
+            default:
+                jj_la1[30] = jj_gen;
+                ;
+            }
+            zoneHour = unsignedInteger();
+            switch ((jj_ntk == -1)
+                    ? jj_ntk()
+                    : jj_ntk) {
+            case COLON:
+            case UINT:
+                switch ((jj_ntk == -1)
+                        ? jj_ntk()
+                        : jj_ntk) {
+                case COLON:
+                    jj_consume_token(COLON);
+                    break;
+                default:
+                    jj_la1[31] = jj_gen;
+                    ;
+                }
+                zoneMinute = unsignedInteger();
+                break;
+            default:
+                jj_la1[32] = jj_gen;
+                ;
+            }
+            if (zoneHour >= 100) {
+                zoneMinute += zoneHour % 100;
+                zoneHour /= 100;
+            }
+            if (zoneHour > 23 || zoneMinute > 59) {
+                {
+                    if (true) {
+                        throw new ParseException(
+                                "invalid time-zone in timestamp");
+                    }
+                }
+            }
+            timeZone = TimeZone.getDefault();
+            timeZone.setRawOffset(sign * (zoneHour * 60 + zoneMinute) * 60
+                    * 1000);
+            break;
+        case NAME:
+            token = jj_consume_token(NAME);
+            timeZone = TimeZone.getTimeZone(token.image);
+            break;
+        default:
+            jj_la1[33] = jj_gen;
+            jj_consume_token(-1);
+            throw new ParseException();
+        }
+        calendar.setTimeZone(timeZone);
+        {
+            if (true) {
+                return calendar;
+            }
+        }
+        throw new Error("Missing return statement in function");
+    }
+
+    private boolean jj_2_1(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_1();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(0, xla);
+        }
+    }
+
+    private boolean jj_2_2(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_2();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(1, xla);
+        }
+    }
+
+    private boolean jj_2_3(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_3();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(2, xla);
+        }
+    }
+
+    private boolean jj_2_4(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_4();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(3, xla);
+        }
+    }
+
+    private boolean jj_2_5(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_5();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(4, xla);
+        }
+    }
+
+    private boolean jj_2_6(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_6();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(5, xla);
+        }
+    }
+
+    private boolean jj_2_7(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_7();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(6, xla);
+        }
+    }
+
+    private boolean jj_2_8(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_8();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(7, xla);
+        }
+    }
+
+    private boolean jj_2_9(final int xla) {
+        jj_la = xla;
+        jj_lastpos = jj_scanpos = token;
+        try {
+            return !jj_3_9();
+        }
+        catch (final LookaheadSuccess ls) {
+            return true;
+        }
+        finally {
+            jj_save(8, xla);
+        }
+    }
+
+    private boolean jj_3R_33() {
+        if (jj_scan_token(LG)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_35() {
+        if (jj_3R_20()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_32() {
+        if (jj_scan_token(LN)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_34() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_35()) {
+            jj_scanpos = xsp;
+        }
+        if (jj_3R_12()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_14() {
+        if (jj_scan_token(NAME)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3_2() {
+        if (jj_scan_token(DIVIDE)) {
+            return true;
+        }
+        if (jj_3R_2()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_31() {
+        if (jj_scan_token(LB)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_27() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_31()) {
+            jj_scanpos = xsp;
+            if (jj_3R_32()) {
+                jj_scanpos = xsp;
+                if (jj_3R_33()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3R_22() {
+        if (jj_scan_token(REAL_EXP)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_24() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_scan_token(4)) {
+            jj_scanpos = xsp;
+        }
+        if (jj_3R_12()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_23() {
+        if (jj_3R_20()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_13() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_23()) {
+            jj_scanpos = xsp;
+        }
+        if (jj_3R_12()) {
+            return true;
+        }
+        xsp = jj_scanpos;
+        if (jj_3R_24()) {
+            jj_scanpos = xsp;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_11() {
+        if (jj_3R_12()) {
+            return true;
+        }
+        if (jj_scan_token(PERIOD)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_5() {
+        if (jj_3R_12()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_6() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_13()) {
+            jj_scanpos = xsp;
+            if (jj_3R_14()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3_5() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_5()) {
+            jj_scanpos = xsp;
+        }
+        if (jj_scan_token(PERIOD)) {
+            return true;
+        }
+        if (jj_scan_token(UINT)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3_1() {
+        if (jj_scan_token(SHIFT)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_26() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_scan_token(17)) {
+            jj_scanpos = xsp;
+            if (jj_scan_token(18)) {
+                jj_scanpos = xsp;
+                if (jj_scan_token(16)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3R_4() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3_5()) {
+            jj_scanpos = xsp;
+            if (jj_3R_11()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3R_19() {
+        if (jj_scan_token(LPAREN)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_18() {
+        if (jj_3R_27()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_29() {
+        if (jj_scan_token(MINUS)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_17() {
+        if (jj_3R_26()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_28() {
+        if (jj_scan_token(PLUS)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_20() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_28()) {
+            jj_scanpos = xsp;
+            if (jj_3R_29()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3R_16() {
+        if (jj_3R_25()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3_6() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_scan_token(1)) {
+            jj_scanpos = xsp;
+        }
+        if (jj_3R_6()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_10() {
+        if (jj_3R_12()) {
+            return true;
+        }
+        if (jj_3R_22()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_21() {
+        if (jj_3R_22()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3_9() {
+        if (jj_3R_4()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3_4() {
+        if (jj_3R_4()) {
+            return true;
+        }
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_21()) {
+            jj_scanpos = xsp;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_8() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_16()) {
+            jj_scanpos = xsp;
+            if (jj_3R_17()) {
+                jj_scanpos = xsp;
+                if (jj_3R_18()) {
+                    jj_scanpos = xsp;
+                    if (jj_3R_19()) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3_7() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_scan_token(17)) {
+            jj_scanpos = xsp;
+            if (jj_scan_token(1)) {
+                return true;
+            }
+        }
+        if (jj_3R_7()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_9() {
+        if (jj_3R_20()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_3() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3R_9()) {
+            jj_scanpos = xsp;
+        }
+        xsp = jj_scanpos;
+        if (jj_3_4()) {
+            jj_scanpos = xsp;
+            if (jj_3R_10()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3R_15() {
+        if (jj_3R_12()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_30() {
+        if (jj_3R_34()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3_8() {
+        if (jj_3R_4()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3_3() {
+        if (jj_3R_3()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_2() {
+        if (jj_3R_8()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_12() {
+        if (jj_scan_token(UINT)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean jj_3R_7() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3_8()) {
+            jj_scanpos = xsp;
+            if (jj_3R_15()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean jj_3R_25() {
+        Token xsp;
+        xsp = jj_scanpos;
+        if (jj_3_3()) {
+            jj_scanpos = xsp;
+            if (jj_3R_30()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Generated Token Manager. */
+    public UnitParserTokenManager token_source;
+    SimpleCharStream              jj_input_stream;
+    /** Current token. */
+    public Token                  token;
+    /** Next token. */
+    public Token                  jj_nt;
+    private int                   jj_ntk;
+    private Token                 jj_scanpos, jj_lastpos;
+    private int                   jj_la;
+    private int                   jj_gen;
+    final private int[]           jj_la1 = new int[34];
+    static private int[]          jj_la1_0;
+    static {
+        jj_la1_init_0();
+    }
+
+    private static void jj_la1_init_0() {
+        jj_la1_0 = new int[] { 0x3f112c, 0x3f712e, 0x3002, 0x3002, 0x3f312e,
+                0x800, 0x82c, 0x2, 0x2, 0x3f112c, 0x70000, 0x380000, 0x2, 0x2c,
+                0xc, 0x400, 0x20, 0xc, 0x20, 0x20, 0xc, 0x20002, 0x2, 0xc, 0x8,
+                0x8, 0x20, 0x20, 0x10, 0x10, 0xc, 0x10, 0x30, 0x4002c, };
+    }
+
+    final private JJCalls[] jj_2_rtns = new JJCalls[9];
+    private boolean         jj_rescan = false;
+    private int             jj_gc     = 0;
+
+    /** Constructor with InputStream. */
+    public UnitParser(final java.io.InputStream stream) {
+        this(stream, null);
+    }
+
+    /** Constructor with InputStream and supplied encoding */
+    public UnitParser(final java.io.InputStream stream, final String encoding) {
+        try {
+            jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1);
+        }
+        catch (final java.io.UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        token_source = new UnitParserTokenManager(jj_input_stream);
+        token = new Token();
+        jj_ntk = -1;
+        jj_gen = 0;
+        for (int i = 0; i < 34; i++) {
+            jj_la1[i] = -1;
+        }
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+            jj_2_rtns[i] = new JJCalls();
+        }
+    }
+
+    /** Reinitialise. */
+    public void ReInit(final java.io.InputStream stream) {
+        ReInit(stream, null);
+    }
+
+    /** Reinitialise. */
+    public void ReInit(final java.io.InputStream stream, final String encoding) {
+        try {
+            jj_input_stream.ReInit(stream, encoding, 1, 1);
+        }
+        catch (final java.io.UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        token_source.ReInit(jj_input_stream);
+        token = new Token();
+        jj_ntk = -1;
+        jj_gen = 0;
+        for (int i = 0; i < 34; i++) {
+            jj_la1[i] = -1;
+        }
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+            jj_2_rtns[i] = new JJCalls();
+        }
+    }
+
+    /** Constructor. */
+    public UnitParser(final java.io.Reader stream) {
+        jj_input_stream = new SimpleCharStream(stream, 1, 1);
+        token_source = new UnitParserTokenManager(jj_input_stream);
+        token = new Token();
+        jj_ntk = -1;
+        jj_gen = 0;
+        for (int i = 0; i < 34; i++) {
+            jj_la1[i] = -1;
+        }
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+            jj_2_rtns[i] = new JJCalls();
+        }
+    }
+
+    /** Reinitialise. */
+    public void ReInit(final java.io.Reader stream) {
+        jj_input_stream.ReInit(stream, 1, 1);
+        token_source.ReInit(jj_input_stream);
+        token = new Token();
+        jj_ntk = -1;
+        jj_gen = 0;
+        for (int i = 0; i < 34; i++) {
+            jj_la1[i] = -1;
+        }
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+            jj_2_rtns[i] = new JJCalls();
+        }
+    }
+
+    /** Constructor with generated Token Manager. */
+    public UnitParser(final UnitParserTokenManager tm) {
+        token_source = tm;
+        token = new Token();
+        jj_ntk = -1;
+        jj_gen = 0;
+        for (int i = 0; i < 34; i++) {
+            jj_la1[i] = -1;
+        }
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+            jj_2_rtns[i] = new JJCalls();
+        }
+    }
+
+    /** Reinitialise. */
+    public void ReInit(final UnitParserTokenManager tm) {
+        token_source = tm;
+        token = new Token();
+        jj_ntk = -1;
+        jj_gen = 0;
+        for (int i = 0; i < 34; i++) {
+            jj_la1[i] = -1;
+        }
+        for (int i = 0; i < jj_2_rtns.length; i++) {
+            jj_2_rtns[i] = new JJCalls();
+        }
+    }
+
+    private Token jj_consume_token(final int kind) throws ParseException {
+        Token oldToken;
+        if ((oldToken = token).next != null) {
+            token = token.next;
+        }
+        else {
+            token = token.next = token_source.getNextToken();
+        }
+        jj_ntk = -1;
+        if (token.kind == kind) {
+            jj_gen++;
+            if (++jj_gc > 100) {
+                jj_gc = 0;
+                for (int i = 0; i < jj_2_rtns.length; i++) {
+                    JJCalls c = jj_2_rtns[i];
+                    while (c != null) {
+                        if (c.gen < jj_gen) {
+                            c.first = null;
+                        }
+                        c = c.next;
+                    }
+                }
+            }
+            return token;
+        }
+        token = oldToken;
+        jj_kind = kind;
+        throw generateParseException();
+    }
+
+    static private final class LookaheadSuccess extends java.lang.Error {
+    }
+
+    final private LookaheadSuccess jj_ls = new LookaheadSuccess();
+
+    private boolean jj_scan_token(final int kind) {
+        if (jj_scanpos == jj_lastpos) {
+            jj_la--;
+            if (jj_scanpos.next == null) {
+                jj_lastpos = jj_scanpos = jj_scanpos.next = token_source
+                        .getNextToken();
+            }
+            else {
+                jj_lastpos = jj_scanpos = jj_scanpos.next;
+            }
+        }
+        else {
+            jj_scanpos = jj_scanpos.next;
+        }
+        if (jj_rescan) {
+            int i = 0;
+            Token tok = token;
+            while (tok != null && tok != jj_scanpos) {
+                i++;
+                tok = tok.next;
+            }
+            if (tok != null) {
+                jj_add_error_token(kind, i);
+            }
+        }
+        if (jj_scanpos.kind != kind) {
+            return true;
+        }
+        if (jj_la == 0 && jj_scanpos == jj_lastpos) {
+            throw jj_ls;
+        }
+        return false;
+    }
+
+    /** Get the next Token. */
+    final public Token getNextToken() {
+        if (token.next != null) {
+            token = token.next;
+        }
+        else {
+            token = token.next = token_source.getNextToken();
+        }
+        jj_ntk = -1;
+        jj_gen++;
+        return token;
+    }
+
+    /** Get the specific Token. */
+    final public Token getToken(final int index) {
+        Token t = token;
+        for (int i = 0; i < index; i++) {
+            if (t.next != null) {
+                t = t.next;
+            }
+            else {
+                t = t.next = token_source.getNextToken();
+            }
+        }
+        return t;
+    }
+
+    private int jj_ntk() {
+        if ((jj_nt = token.next) == null) {
+            return (jj_ntk = (token.next = token_source.getNextToken()).kind);
+        }
+        else {
+            return (jj_ntk = jj_nt.kind);
+        }
+    }
+
+    private final java.util.List jj_expentries = new java.util.ArrayList();
+    private int[]                jj_expentry;
+    private int                  jj_kind       = -1;
+    private final int[]          jj_lasttokens = new int[100];
+    private int                  jj_endpos;
+
+    private void jj_add_error_token(final int kind, final int pos) {
+        if (pos >= 100) {
+            return;
+        }
+        if (pos == jj_endpos + 1) {
+            jj_lasttokens[jj_endpos++] = kind;
+        }
+        else if (jj_endpos != 0) {
+            jj_expentry = new int[jj_endpos];
+            for (int i = 0; i < jj_endpos; i++) {
+                jj_expentry[i] = jj_lasttokens[i];
+            }
+            jj_entries_loop: for (final java.util.Iterator it = jj_expentries
+                    .iterator(); it.hasNext();) {
+                final int[] oldentry = (int[]) (it.next());
+                if (oldentry.length == jj_expentry.length) {
+                    for (int i = 0; i < jj_expentry.length; i++) {
+                        if (oldentry[i] != jj_expentry[i]) {
+                            continue jj_entries_loop;
+                        }
+                    }
+                    jj_expentries.add(jj_expentry);
+                    break jj_entries_loop;
+                }
+            }
+            if (pos != 0) {
+                jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+            }
+        }
+    }
+
+    /** Generate ParseException. */
+    public ParseException generateParseException() {
+        jj_expentries.clear();
+        final boolean[] la1tokens = new boolean[23];
+        if (jj_kind >= 0) {
+            la1tokens[jj_kind] = true;
+            jj_kind = -1;
+        }
+        for (int i = 0; i < 34; i++) {
+            if (jj_la1[i] == jj_gen) {
+                for (int j = 0; j < 32; j++) {
+                    if ((jj_la1_0[i] & (1 << j)) != 0) {
+                        la1tokens[j] = true;
+                    }
+                }
+            }
+        }
+        for (int i = 0; i < 23; i++) {
+            if (la1tokens[i]) {
+                jj_expentry = new int[1];
+                jj_expentry[0] = i;
+                jj_expentries.add(jj_expentry);
+            }
+        }
+        jj_endpos = 0;
+        jj_rescan_token();
+        jj_add_error_token(0, 0);
+        final int[][] exptokseq = new int[jj_expentries.size()][];
+        for (int i = 0; i < jj_expentries.size(); i++) {
+            exptokseq[i] = (int[]) jj_expentries.get(i);
+        }
+        return new ParseException(token, exptokseq, tokenImage);
+    }
+
+    /** Enable tracing. */
+    final public void enable_tracing() {
+    }
+
+    /** Disable tracing. */
+    final public void disable_tracing() {
+    }
+
+    private void jj_rescan_token() {
+        jj_rescan = true;
+        for (int i = 0; i < 9; i++) {
+            try {
+                JJCalls p = jj_2_rtns[i];
+                do {
+                    if (p.gen > jj_gen) {
+                        jj_la = p.arg;
+                        jj_lastpos = jj_scanpos = p.first;
+                        switch (i) {
+                        case 0:
+                            jj_3_1();
+                            break;
+                        case 1:
+                            jj_3_2();
+                            break;
+                        case 2:
+                            jj_3_3();
+                            break;
+                        case 3:
+                            jj_3_4();
+                            break;
+                        case 4:
+                            jj_3_5();
+                            break;
+                        case 5:
+                            jj_3_6();
+                            break;
+                        case 6:
+                            jj_3_7();
+                            break;
+                        case 7:
+                            jj_3_8();
+                            break;
+                        case 8:
+                            jj_3_9();
+                            break;
+                        }
+                    }
+                    p = p.next;
+                } while (p != null);
+            }
+            catch (final LookaheadSuccess ls) {
+            }
+        }
+        jj_rescan = false;
+    }
+
+    private void jj_save(final int index, final int xla) {
+        JJCalls p = jj_2_rtns[index];
+        while (p.gen > jj_gen) {
+            if (p.next == null) {
+                p = p.next = new JJCalls();
+                break;
+            }
+            p = p.next;
+        }
+        p.gen = jj_gen + xla - jj_la;
+        p.first = token;
+        p.arg = xla;
+    }
+
+    static final class JJCalls {
+        int     gen;
+        Token   first;
+        int     arg;
+        JJCalls next;
+    }
+
+}
diff --git a/visad/data/units/UnitParser.jj b/visad/data/units/UnitParser.jj
new file mode 100644
index 0000000..1db2fb9
--- /dev/null
+++ b/visad/data/units/UnitParser.jj
@@ -0,0 +1,749 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2006 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+options
+{
+    STATIC = false;
+    DEBUG_PARSER = false;
+    DEBUG_TOKEN_MANAGER = false;
+    // LOOKAHEAD=2;
+    // FORCE_LA_CHECK=true;
+}
+
+PARSER_BEGIN(UnitParser)
+
+    package visad.data.units;
+
+    import java.io.ByteArrayInputStream;
+    import java.io.InputStreamReader;
+    import java.io.LineNumberReader;
+    import java.util.Calendar;
+    import java.util.TimeZone;
+    import visad.DerivedUnit;
+    import visad.SI;
+    import visad.Unit;
+    import visad.UnitException;
+
+    /**
+     * A class for parsing string unit specifications.
+     *
+     * Instances are thread-compatible but not thread-safe.
+     */
+    public class UnitParser
+    {
+        /**
+         * The units database.
+         */
+        protected static UnitsDB        unitsDB = null;
+
+        static
+        {
+            try
+            {
+                unitsDB = DefaultUnitsDB.instance();
+            }
+            catch(UnitException e)
+            {
+            }
+        }
+
+        /**
+         * The canonical time unit.
+         */
+        protected static final Unit     SECOND = SI.second;
+
+        /**
+         * The Julian day number of the (artificial) time origin.
+         */
+        protected static final long     julianDayOrigin = 
+                                            UnitParser.julianDay(2001, 1, 1);
+                                            
+        /**
+         * The dimensionless unit one.
+         */
+        private static final Unit ONE = new DerivedUnit();
+
+
+        /**
+         * Compute the Julian day number of a date.
+         */
+        public static long
+        julianDay(int year, int month, int day)
+        {
+            long        igreg = 15 + 31 * (10 + (12 * 1582));
+            int         iy;     // signed, origin-0 year
+            int         ja;     // Julian century
+            int         jm;     // Julian month
+            int         jy;     // Julian year
+            long        julday; // returned Julian day number
+
+            /*
+             * Because there is no 0 BC or 0 AD, assume the user wants
+             * the start of the common era if they specify year 0.
+             */
+            if (year == 0)
+                year = 1;
+
+            iy = year;
+            if (year < 0)
+                iy++;
+            if (month > 2)
+            {
+                jy = iy;
+                jm = month + 1;
+            }
+            else
+            {
+                jy = iy - 1;
+                jm = month + 13;
+
+            }
+
+            julday = day + (int)(30.6001 * jm);
+            if (jy >= 0)
+            {
+                julday += 365 * jy;
+                julday += 0.25 * jy;
+            }
+            else
+            {
+                double  xi = 365.25 * jy;
+
+                if ((int)xi != xi)
+                    xi -= 1;
+                julday += (int)xi;
+            }
+            julday += 1720995;
+
+            if (day + (31* (month + (12 * iy))) >= igreg)
+            {
+                ja = jy/100;
+                julday -= ja;
+                julday += 2;
+                julday += ja/4;
+            }
+
+            return julday;
+        }
+
+
+        /**
+         * Encode a timestamp as a double value in units of seconds.
+         */
+        public static double
+        encodeTimestamp(int year, int month, int day,
+            int hour, int minute, float second, int zone)
+        {
+            return (julianDay(year, month, day) - julianDayOrigin) *
+                86400.0 + (hour*60 + minute - zone)*60 + second;
+        }
+        
+        
+        private static void myAssert(UnitParser parser, final String spec,
+                Unit expect) throws UnitException, ParseException
+        {
+            if (!parser.parse(spec).equals(expect)) {
+                throw new AssertionError("Got \"" + spec + "; expected \"" +
+                    expect + "\"");
+            }
+            System.out.println(spec + " -> " + expect);
+        }
+        
+        
+        /**
+         * Parses a string unit specification.
+         *
+         * @param spec The unit specification.
+         * @return A {@link Unit} corresponding to {@code spec}.
+         * @throws NullPointerException if {@code spec} is {@code null}.
+         * @throws ParseException if a parsing error occurred.
+         * @throws UnitException if the string specifies an invalid unit
+         * operation.
+         */
+        public Unit parse(final String spec) throws ParseException,
+            UnitException
+        {
+            ReInit(new ByteArrayInputStream(spec.getBytes()));
+            return unitSpec();
+        }
+
+
+        /**
+         * Test this class.
+         */
+        public static void main(String[] args)
+            throws Exception
+        {
+            UnitParser          parser = new UnitParser(System.in);
+            LineNumberReader    lineInput = new LineNumberReader(
+                                    new InputStreamReader(System.in));
+            Unit    s = unitsDB.get("s");
+            Unit    m = unitsDB.get("m");
+            Unit    kg = unitsDB.get("kg");
+            Unit    w = unitsDB.get("W");
+            Unit    epoch = s.shift(encodeTimestamp(1970, 1, 1, 0, 0, 0f, 0));
+
+            myAssert(parser, "m m", m.multiply(m));
+            myAssert(parser, "m.m", m.multiply(m));
+            myAssert(parser, "(m)(m)", m.pow(2));
+            myAssert(parser, "m/s/s", m.divide(s).divide(s));
+            myAssert(parser, "m2", m.pow(2));
+            myAssert(parser, "m2.s", m.pow(2).multiply(s));
+            myAssert(parser, "m2/s", m.pow(2).divide(s));
+            myAssert(parser, "m^2/s", m.pow(2).divide(s));
+            myAssert(parser, "m s @ 5", m.multiply(s).shift(5));
+            myAssert(parser, "m2 s @ 5", m.pow(2).multiply(s).shift(5));
+            myAssert(parser, "m2 s-1 @ 5", m.pow(2).divide(s).shift(5)); 
+            myAssert(parser, "m s from 5", m.multiply(s).shift(5));
+            myAssert(parser, "m s kg @ 5", m.multiply(s).multiply(kg).shift(5));
+            myAssert(parser, "s at 19700101", epoch);
+            myAssert(parser, "s at 19700101T000000", epoch);
+            myAssert(parser, "s at 19700101T000000.00", epoch);
+            myAssert(parser, "s @ 1970-01-01T00:00:00.00", epoch);
+            myAssert(parser, "s @ 1970-01-01 00:00:00.00", epoch);
+            myAssert(parser, "s @ 1970-01-01T00:00:00.00 -12",
+                s.shift(encodeTimestamp(1970, 1, 1, 12, 0, 0f, 0)));
+            myAssert(parser, "s @ 1970-01-01T00:00:00.00 -12",
+                s.shift(encodeTimestamp(1970, 1, 1, 0, 0, 0f, -12*60)));
+            myAssert(parser, "lg(re: 1)", ONE.log(10));
+            myAssert(parser, "0.1 lg(re 1 mW)", w.scale(1e-3).log(10).scale(0.1));
+            myAssert(parser, "m", m);
+            myAssert(parser, "2 m s", m.multiply(s).scale(2));
+            myAssert(parser, "3.14 m.s", m.multiply(s).scale(3.14));
+            myAssert(parser, "1e9 (m)", m.scale(1e9));
+            myAssert(parser, "(m s)2", m.multiply(s).pow(2));
+            myAssert(parser, "m2.s-1", m.pow(2).divide(s));
+            myAssert(parser, "m2 s^-1", m.pow(2).divide(s));
+            myAssert(parser, "(m/s)2", m.divide(s).pow(2));
+            myAssert(parser, "m2/s-1", m.pow(2).divide(s.pow(-1)));
+            myAssert(parser, "m2/s^-1", m.pow(2).divide(s.pow(-1)));
+            myAssert(parser, ".5 m/(.25 s)2", m.scale(.5).divide(
+                    s.scale(.25).pow(2)));
+            myAssert(parser, "m.m-1.m", m.multiply(m.pow(-1)).multiply(m));
+            myAssert(parser, "2.0 m 1/2 s-1*(m/s^1)^-1 (1e9 m-1)(1e9 s-1)-1.m/s", m
+                    .scale(2).scale(1. / 2.).multiply(s.pow(-1)).multiply(
+                            m.divide(s.pow(1)).pow(-1)).multiply(
+                            m.pow(-1).scale(1e9)).multiply(
+                            s.pow(-1).scale(1e9).pow(-1)).multiply(m)
+                    .divide(s));
+            myAssert(parser, "m/km", m.divide(m.scale(1e3)));
+
+            for (;;)
+            {
+                System.out.print("Enter a unit specification or ^D to quit: ");
+
+                String  spec = lineInput.readLine();
+                if (spec == null)
+                    break;
+
+                spec = spec.trim();
+
+                if (spec.length() > 0)
+                {
+                    parser.ReInit(new ByteArrayInputStream(spec.getBytes()));
+
+                    try
+                    {
+                        Unit    unit = parser.unitSpec();
+                        System.out.println("unit = " + unit);
+                        System.out.println(
+                            "definition = " + unit.getDefinition());
+                    }
+                    catch (ParseException e)
+                    {
+                        System.out.println(e.getMessage());
+                    }
+                }
+            }
+            System.out.println("");
+        }
+    }
+
+PARSER_END(UnitParser)
+
+TOKEN [IGNORE_CASE] :
+{
+        < SP:       ([" ","\t","\n","\r"])+ >
+    |   < PLUS:     "+" >
+    |   < MINUS:    "-" >
+    |   < COLON:    ":" >
+    |   < UINT:     (["0"-"9"])+ >
+    |   <#SIGN:     <PLUS> | <MINUS> >
+    |   <#LETTER:   ["a"-"z","_"] >
+    |   < LPAREN:   "(" >
+    |   < RPAREN:   ")" >
+    |   < REAL_EXP: "e" (<SIGN>)? <UINT> >
+    |   < RAISE:    "^" >
+    |   < PERIOD:   "." >
+    |   < STAR:     "*" >
+    |   < DIVIDE:   "/" | (<SP>) "per" (<SP>) >
+    |   < SHIFT:    (<SP>)? "@" (<SP>)? | (<SP>) ("since" | "from") (<SP>) >
+    |   < SYMBOL:   ["'","\"","%"] >
+    |   < T:        "t" >
+    |   < NAME:     (<LETTER>)+ (<UINT> (<LETTER>)+)? >
+    |   < LB:       "lb(re" (":")? (<SP>)? >
+    |   < LN:       "ln(re" (":")? (<SP>)? >
+    |   < LG:       "lg(re" (":")? (<SP>)? >
+    |   < UTC:      "utc" | "gmt" | "zulu" >
+}
+
+
+Unit unitSpec() throws UnitException :
+{
+    Unit    unit = ONE;
+}
+{
+    [
+        unit=shiftExpr() 
+    ]
+    <EOF>
+    {
+        return unit;
+    }
+}
+
+
+Unit shiftExpr() throws UnitException :
+{
+    Unit        unit;
+    double      origin;
+}
+{
+    unit=productExpr()
+    [
+        LOOKAHEAD(2)
+        <SHIFT>
+        {
+            if (Unit.canConvert(unit, SECOND)) {
+                origin=timeOriginExpr();
+                unit = unit.shift(unit.toThis(origin, SECOND));
+            }
+            else {
+                origin=number();
+                if (origin != 0)
+                    unit = unit.shift(origin);
+            }
+        }
+    ]
+    {
+        return unit;
+    }
+}
+
+
+Unit productExpr() throws UnitException :
+{
+    Unit        unit, unit2;
+}
+{
+    unit=powerExpr()
+    (
+            LOOKAHEAD(2)
+            <DIVIDE>
+            unit2 = powerExpr()
+            {
+                unit = unit.divide(unit2);
+            }
+        |       
+            [ <PERIOD> | <STAR> | <SP> ]
+            unit2=powerExpr() 
+            {
+                unit = unit.multiply(unit2);
+            }
+    )*
+    {
+        return unit;
+    }
+}
+
+
+Unit powerExpr() throws UnitException :
+{
+    Unit    unit;
+    int     exponent;
+}
+{
+    unit=basicExpr()
+    [
+        [ <RAISE> ]
+        exponent=integer()
+        {
+            unit = unit.pow(exponent);
+        }
+    ]
+    {
+        return unit;
+    }
+}
+
+
+Unit basicExpr() throws UnitException :
+{
+    Unit    unit;
+    double  number;
+}
+{
+    (
+            number = number()
+            {
+                unit =  ONE.scale(number);
+            }
+        |                           
+            unit=unitIdentifier()
+        |
+            unit=logExpr()
+        |
+            <LPAREN> (<SP>)? unit=shiftExpr() (<SP>)? <RPAREN>
+    )
+    {
+        return unit;
+    }
+}
+
+
+Unit unitIdentifier() :
+{
+    Token   token;
+    Unit    unit;
+}
+{
+    (
+            token=<T>
+        |
+            token=<NAME>
+        |
+            token=<SYMBOL>
+    )
+    {
+        unit = unitsDB.get(token.image);
+        if (unit == null)
+        {
+            throw new NoSuchUnitException("Unit not in database");
+        }
+        return unit;
+    }
+}
+
+
+Unit logExpr() throws UnitException :
+{
+    double base;
+    Unit   ref;
+}
+{
+    (
+            <LB> {base = 2;}
+        |
+            <LN> {base = Math.E;}
+        |
+            <LG> {base = 10;}
+    )
+    ref = productExpr() [<SP>] <RPAREN>
+    {
+        return ref.log(base);
+    }
+}
+
+
+double number() :
+{
+    double  number;
+}
+{
+    (
+            LOOKAHEAD(3)
+            number = real()
+        |
+            number = integer()
+    )
+    {
+        return number;
+    }
+}
+
+
+double real() :
+{
+    int    sign = 1;
+    double tenFactor = 1;
+    double udecimal;
+}
+{
+    [sign = sign()]
+    (
+            LOOKAHEAD(2)
+            udecimal = unsignedDecimal()
+            [ tenFactor = tenFactor() ]
+        |
+            udecimal = unsignedInteger()
+            tenFactor = tenFactor()
+    )
+    {
+        return sign * udecimal * tenFactor;
+    }
+}
+
+
+int sign() :
+{}
+{
+        <PLUS> { return 1; }
+    |
+        <MINUS> { return -1; }
+}
+
+
+double unsignedDecimal() :
+{
+    int     integer = 0;
+    Token   token;
+    double  fraction = 0;
+}
+{
+    (
+            LOOKAHEAD(3)
+            [integer=unsignedInteger()]
+            <PERIOD>
+            token = <UINT>
+            {
+                fraction = Double.valueOf("." + token.image);
+            }
+        |
+            integer=unsignedInteger()
+            <PERIOD>
+    )
+    {
+        return integer + fraction;
+    }
+}
+
+
+double tenFactor() :
+{
+    Token  token;
+}
+{
+    token = <REAL_EXP>
+    {
+        return Double.valueOf("1" + token.image);
+    }
+}
+
+
+int integer() :
+{
+    int     magnitude;
+    int     sign = 1;
+}
+{
+    [ sign = sign() ]
+    magnitude = unsignedInteger()
+    {
+        return sign * magnitude;
+    }
+}
+
+
+int unsignedInteger() :
+{
+    Token       token;
+}
+{
+    token=<UINT>
+    {
+        return Integer.valueOf(token.image);
+    }
+}
+
+
+/*
+ * See <http://www.cl.cam.ac.uk/~mgk25/iso-time.html> for a discussion of the
+ * relevant timestamp format or lookup "ISO 8601".
+ */
+double timeOriginExpr() :
+{
+    Calendar   calendar;
+}
+{
+    calendar = dateExpr()
+    [
+        LOOKAHEAD(2)
+        (<T> | <SP>)
+        clockExpr(calendar)
+        [
+            LOOKAHEAD(2)
+            [<SP>]
+            zoneExpr(calendar)
+        ]
+    ]
+    {
+        return encodeTimestamp(calendar.get(Calendar.YEAR),
+            calendar.get(Calendar.MONTH)+1, calendar.get(Calendar.DAY_OF_MONTH),
+            calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE),
+            (float)(calendar.get(Calendar.SECOND) + 
+            calendar.get(Calendar.MILLISECOND)/1e3),
+            calendar.get(Calendar.ZONE_OFFSET)/(1000*60));
+    }
+}
+
+
+Calendar dateExpr() :
+{
+    int        sign = 1;
+    int        year;
+    int        month = 1;
+    int        day = 1;
+    boolean    packed = true;
+}
+{
+    [sign = sign() ]
+    year = unsignedInteger()
+    [
+        <MINUS>
+        month = unsignedInteger()
+        {
+            packed = false;
+        }
+        [
+            <MINUS>
+            day = unsignedInteger()
+        ]       
+    ]
+    {
+        if (packed) {
+            if (year >= 10000101) {
+                day = year % 100;
+                year /= 100;
+            }
+            if (year >= 100001) {
+                month = year % 100;
+                year /= 100;
+            }
+            if (sign < 0)
+                year = -year;
+        }
+        if (month < 1 || month > 12)
+            throw new ParseException("invalid month in timestamp");
+        if (day < 1 || day > 31)
+            throw new ParseException("invalid day in timestamp");
+        Calendar    calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        calendar.clear();
+        calendar.set(year, month-1, day);
+        return calendar;
+    }
+}
+
+
+Calendar clockExpr(Calendar calendar) :
+{
+    double     hour;
+    int        minute = 0;
+    double     seconds = 0;
+    boolean    packed = true;
+}
+{
+    (
+            LOOKAHEAD(2)
+            hour = unsignedDecimal()
+        |
+            hour = unsignedInteger()
+    )
+    [
+        <COLON>
+        minute = unsignedInteger()
+        {
+            packed = false;
+        }
+        [
+            <COLON>
+            (
+                    LOOKAHEAD(2)
+                    seconds = unsignedDecimal()
+                |
+                    seconds = unsignedInteger()
+            )
+        ]       
+    ]
+    {
+        if (packed) {
+            if (hour >= 100000) {
+                seconds = hour % 100;
+                hour /= 100;
+            }
+            if (hour >= 1000) {
+                minute = (int)(hour % 100);
+                hour /= 100;
+            }
+        }
+        if (hour < 0 || hour > 23)
+            throw new ParseException("invalid hour in timestamp");
+        if (minute < 0 || minute > 59)
+            throw new ParseException("invalid minute in timestamp");
+        if (seconds < 0 || seconds > 61)
+            throw new ParseException("invalid seconds in timestamp");
+        calendar.set(Calendar.HOUR_OF_DAY, (int)Math.round(hour));
+        calendar.set(Calendar.MINUTE, minute);
+        int s = (int)seconds;
+        calendar.set(Calendar.SECOND, s);
+        int ms = (int)((seconds - s) * 1000);
+        calendar.set(Calendar.MILLISECOND, ms);
+        return calendar;
+    }
+}
+
+Calendar zoneExpr(Calendar calendar) :
+{
+    int         sign = 1;
+    int         zoneHour;
+    int         zoneMinute = 0;
+    Token       token;
+    TimeZone    timeZone;
+}
+{
+    (
+            [sign=sign()]
+            zoneHour=unsignedInteger()
+            [[<COLON>] zoneMinute=unsignedInteger()]
+            {
+                if (zoneHour >= 100)
+                {
+                    zoneMinute += zoneHour % 100;
+                    zoneHour /= 100;
+                }
+                if (zoneHour > 23 || zoneMinute > 59)
+                {
+                    throw new ParseException("invalid time-zone in timestamp");
+                }
+                timeZone = TimeZone.getDefault();
+                timeZone.setRawOffset(sign*(zoneHour*60 + zoneMinute)*60*1000);
+            }
+        |
+            token = <NAME>
+            {
+                timeZone = TimeZone.getTimeZone(token.image);
+            }
+    )
+    {
+        calendar.setTimeZone(timeZone);
+        return calendar;
+    }
+}
diff --git a/visad/data/units/UnitParserConstants.java b/visad/data/units/UnitParserConstants.java
new file mode 100644
index 0000000..ec97994
--- /dev/null
+++ b/visad/data/units/UnitParserConstants.java
@@ -0,0 +1,88 @@
+/* Generated By:JavaCC: Do not edit this line. UnitParserConstants.java */
+package visad.data.units;
+
+
+/** 
+ * Token literal values and constants.
+ * Generated by org.javacc.parser.OtherFilesGen#start()
+ */
+public interface UnitParserConstants {
+
+  /** End of File. */
+  int EOF = 0;
+  /** RegularExpression Id. */
+  int SP = 1;
+  /** RegularExpression Id. */
+  int PLUS = 2;
+  /** RegularExpression Id. */
+  int MINUS = 3;
+  /** RegularExpression Id. */
+  int COLON = 4;
+  /** RegularExpression Id. */
+  int UINT = 5;
+  /** RegularExpression Id. */
+  int SIGN = 6;
+  /** RegularExpression Id. */
+  int LETTER = 7;
+  /** RegularExpression Id. */
+  int LPAREN = 8;
+  /** RegularExpression Id. */
+  int RPAREN = 9;
+  /** RegularExpression Id. */
+  int REAL_EXP = 10;
+  /** RegularExpression Id. */
+  int RAISE = 11;
+  /** RegularExpression Id. */
+  int PERIOD = 12;
+  /** RegularExpression Id. */
+  int STAR = 13;
+  /** RegularExpression Id. */
+  int DIVIDE = 14;
+  /** RegularExpression Id. */
+  int SHIFT = 15;
+  /** RegularExpression Id. */
+  int SYMBOL = 16;
+  /** RegularExpression Id. */
+  int T = 17;
+  /** RegularExpression Id. */
+  int NAME = 18;
+  /** RegularExpression Id. */
+  int LB = 19;
+  /** RegularExpression Id. */
+  int LN = 20;
+  /** RegularExpression Id. */
+  int LG = 21;
+  /** RegularExpression Id. */
+  int UTC = 22;
+
+  /** Lexical state. */
+  int DEFAULT = 0;
+
+  /** Literal token values. */
+  String[] tokenImage = {
+    "<EOF>",
+    "<SP>",
+    "\"+\"",
+    "\"-\"",
+    "\":\"",
+    "<UINT>",
+    "<SIGN>",
+    "<LETTER>",
+    "\"(\"",
+    "\")\"",
+    "<REAL_EXP>",
+    "\"^\"",
+    "\".\"",
+    "\"*\"",
+    "<DIVIDE>",
+    "<SHIFT>",
+    "<SYMBOL>",
+    "\"t\"",
+    "<NAME>",
+    "<LB>",
+    "<LN>",
+    "<LG>",
+    "<UTC>",
+  };
+
+}
diff --git a/visad/data/units/UnitParserTokenManager.java b/visad/data/units/UnitParserTokenManager.java
new file mode 100644
index 0000000..0dbddea
--- /dev/null
+++ b/visad/data/units/UnitParserTokenManager.java
@@ -0,0 +1,645 @@
+/* Generated By:JavaCC: Do not edit this line. UnitParserTokenManager.java */
+package visad.data.units;
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.util.Calendar;
+import java.util.TimeZone;
+import visad.DerivedUnit;
+import visad.SI;
+import visad.Unit;
+import visad.UnitException;
+
+/** Token Manager. */
+public class UnitParserTokenManager implements UnitParserConstants
+{
+
+  /** Debug output. */
+  public  java.io.PrintStream debugStream = System.out;
+  /** Set debug output. */
+  public  void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+   switch (pos)
+   {
+      default :
+         return -1;
+   }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+   return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private int jjStopAtPos(int pos, int kind)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   return pos + 1;
+}
+private int jjMoveStringLiteralDfa0_0()
+{
+   switch(curChar)
+   {
+      case 40:
+         return jjStopAtPos(0, 8);
+      case 41:
+         return jjStopAtPos(0, 9);
+      case 42:
+         return jjStopAtPos(0, 13);
+      case 43:
+         return jjStopAtPos(0, 2);
+      case 45:
+         return jjStopAtPos(0, 3);
+      case 46:
+         return jjStopAtPos(0, 12);
+      case 58:
+         return jjStopAtPos(0, 4);
+      case 84:
+         return jjStartNfaWithStates_0(0, 17, 59);
+      case 94:
+         return jjStopAtPos(0, 11);
+      case 116:
+         return jjStartNfaWithStates_0(0, 17, 59);
+      default :
+         return jjMoveNfa_0(1, 0);
+   }
+}
+private int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+   jjmatchedKind = kind;
+   jjmatchedPos = pos;
+   try { curChar = input_stream.readChar(); }
+   catch(java.io.IOException e) { return pos + 1; }
+   return jjMoveNfa_0(state, pos + 1);
+}
+private int jjMoveNfa_0(int startState, int curPos)
+{
+   int startsAt = 0;
+   jjnewStateCnt = 59;
+   int i = 1;
+   jjstateSet[0] = startState;
+   int kind = 0x7fffffff;
+   for (;;)
+   {
+      if (++jjround == 0x7fffffff)
+         ReInitRounds();
+      if (curChar < 64)
+      {
+         long l = 1L << curChar;
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 59:
+               case 9:
+                  if ((0x3ff000000000000L & l) != 0L)
+                     jjCheckNAddTwoStates(9, 10);
+                  break;
+               case 1:
+                  if ((0x3ff000000000000L & l) != 0L)
+                  {
+                     if (kind > 5)
+                        kind = 5;
+                     jjCheckNAdd(0);
+                  }
+                  else if ((0x100002600L & l) != 0L)
+                  {
+                     if (kind > 1)
+                        kind = 1;
+                     jjCheckNAddStates(0, 7);
+                  }
+                  else if ((0xa400000000L & l) != 0L)
+                  {
+                     if (kind > 16)
+                        kind = 16;
+                  }
+                  else if (curChar == 47)
+                  {
+                     if (kind > 14)
+                        kind = 14;
+                  }
+                  break;
+               case 0:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 5)
+                     kind = 5;
+                  jjCheckNAdd(0);
+                  break;
+               case 2:
+                  if ((0x280000000000L & l) != 0L)
+                     jjCheckNAdd(3);
+                  break;
+               case 3:
+                  if ((0x3ff000000000000L & l) == 0L)
+                     break;
+                  if (kind > 10)
+                     kind = 10;
+                  jjCheckNAdd(3);
+                  break;
+               case 4:
+                  if (curChar == 47)
+                     kind = 14;
+                  break;
+               case 6:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 15)
+                     kind = 15;
+                  jjstateSet[jjnewStateCnt++] = 6;
+                  break;
+               case 7:
+                  if ((0xa400000000L & l) != 0L)
+                     kind = 16;
+                  break;
+               case 21:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAddStates(0, 7);
+                  break;
+               case 22:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 1)
+                     kind = 1;
+                  jjCheckNAdd(22);
+                  break;
+               case 23:
+                  if ((0x100002600L & l) != 0L)
+                     jjCheckNAddTwoStates(23, 27);
+                  break;
+               case 25:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 14)
+                     kind = 14;
+                  jjstateSet[jjnewStateCnt++] = 25;
+                  break;
+               case 28:
+                  if ((0x100002600L & l) != 0L)
+                     jjCheckNAddTwoStates(28, 5);
+                  break;
+               case 29:
+                  if ((0x100002600L & l) != 0L)
+                     jjCheckNAddStates(8, 10);
+                  break;
+               case 31:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 15)
+                     kind = 15;
+                  jjstateSet[jjnewStateCnt++] = 31;
+                  break;
+               case 42:
+                  if (curChar != 58)
+                     break;
+                  if (kind > 19)
+                     kind = 19;
+                  jjCheckNAdd(43);
+                  break;
+               case 43:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 19)
+                     kind = 19;
+                  jjCheckNAdd(43);
+                  break;
+               case 45:
+                  if (curChar == 40)
+                     jjstateSet[jjnewStateCnt++] = 44;
+                  break;
+               case 48:
+                  if (curChar != 58)
+                     break;
+                  if (kind > 20)
+                     kind = 20;
+                  jjCheckNAdd(49);
+                  break;
+               case 49:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 20)
+                     kind = 20;
+                  jjCheckNAdd(49);
+                  break;
+               case 51:
+                  if (curChar == 40)
+                     jjstateSet[jjnewStateCnt++] = 50;
+                  break;
+               case 54:
+                  if (curChar != 58)
+                     break;
+                  if (kind > 21)
+                     kind = 21;
+                  jjCheckNAdd(55);
+                  break;
+               case 55:
+                  if ((0x100002600L & l) == 0L)
+                     break;
+                  if (kind > 21)
+                     kind = 21;
+                  jjCheckNAdd(55);
+                  break;
+               case 57:
+                  if (curChar == 40)
+                     jjstateSet[jjnewStateCnt++] = 56;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else if (curChar < 128)
+      {
+         long l = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               case 59:
+               case 8:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 18)
+                     kind = 18;
+                  jjCheckNAddTwoStates(8, 9);
+                  break;
+               case 1:
+                  if ((0x7fffffe87fffffeL & l) != 0L)
+                  {
+                     if (kind > 18)
+                        kind = 18;
+                     jjCheckNAddTwoStates(8, 9);
+                  }
+                  else if (curChar == 64)
+                  {
+                     if (kind > 15)
+                        kind = 15;
+                     jjstateSet[jjnewStateCnt++] = 6;
+                  }
+                  if ((0x100000001000L & l) != 0L)
+                     jjAddStates(11, 13);
+                  else if ((0x400000004000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 19;
+                  else if ((0x8000000080L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  else if ((0x20000000200000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 12;
+                  else if ((0x2000000020L & l) != 0L)
+                     jjAddStates(14, 15);
+                  break;
+               case 5:
+                  if (curChar != 64)
+                     break;
+                  kind = 15;
+                  jjstateSet[jjnewStateCnt++] = 6;
+                  break;
+               case 10:
+                  if ((0x7fffffe87fffffeL & l) == 0L)
+                     break;
+                  if (kind > 18)
+                     kind = 18;
+                  jjstateSet[jjnewStateCnt++] = 10;
+                  break;
+               case 11:
+                  if ((0x800000008L & l) != 0L && kind > 22)
+                     kind = 22;
+                  break;
+               case 12:
+                  if ((0x10000000100000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 11;
+                  break;
+               case 13:
+                  if ((0x20000000200000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 12;
+                  break;
+               case 14:
+                  if ((0x10000000100000L & l) != 0L && kind > 22)
+                     kind = 22;
+                  break;
+               case 15:
+                  if ((0x200000002000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 14;
+                  break;
+               case 16:
+                  if ((0x8000000080L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 15;
+                  break;
+               case 17:
+                  if ((0x20000000200000L & l) != 0L && kind > 22)
+                     kind = 22;
+                  break;
+               case 18:
+                  if ((0x100000001000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 17;
+                  break;
+               case 19:
+                  if ((0x20000000200000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 18;
+                  break;
+               case 20:
+                  if ((0x400000004000000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 19;
+                  break;
+               case 24:
+                  if ((0x4000000040000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 25;
+                  break;
+               case 26:
+                  if ((0x2000000020L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 24;
+                  break;
+               case 27:
+                  if ((0x1000000010000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 26;
+                  break;
+               case 30:
+                  if ((0x2000000020L & l) != 0L)
+                     jjCheckNAdd(31);
+                  break;
+               case 32:
+                  if ((0x800000008L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 30;
+                  break;
+               case 33:
+                  if ((0x400000004000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 32;
+                  break;
+               case 34:
+                  if ((0x20000000200L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 33;
+                  break;
+               case 35:
+                  if ((0x8000000080000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 34;
+                  break;
+               case 36:
+                  if ((0x200000002000L & l) != 0L)
+                     jjCheckNAdd(31);
+                  break;
+               case 37:
+                  if ((0x800000008000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 36;
+                  break;
+               case 38:
+                  if ((0x4000000040000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 37;
+                  break;
+               case 39:
+                  if ((0x4000000040L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 38;
+                  break;
+               case 40:
+                  if ((0x100000001000L & l) != 0L)
+                     jjAddStates(11, 13);
+                  break;
+               case 41:
+                  if ((0x2000000020L & l) == 0L)
+                     break;
+                  if (kind > 19)
+                     kind = 19;
+                  jjAddStates(16, 17);
+                  break;
+               case 44:
+                  if ((0x4000000040000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 41;
+                  break;
+               case 46:
+                  if ((0x400000004L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 45;
+                  break;
+               case 47:
+                  if ((0x2000000020L & l) == 0L)
+                     break;
+                  if (kind > 20)
+                     kind = 20;
+                  jjAddStates(18, 19);
+                  break;
+               case 50:
+                  if ((0x4000000040000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 47;
+                  break;
+               case 52:
+                  if ((0x400000004000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 51;
+                  break;
+               case 53:
+                  if ((0x2000000020L & l) == 0L)
+                     break;
+                  if (kind > 21)
+                     kind = 21;
+                  jjAddStates(20, 21);
+                  break;
+               case 56:
+                  if ((0x4000000040000L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 53;
+                  break;
+               case 58:
+                  if ((0x8000000080L & l) != 0L)
+                     jjstateSet[jjnewStateCnt++] = 57;
+                  break;
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      else
+      {
+         int i2 = (curChar & 0xff) >> 6;
+         long l2 = 1L << (curChar & 077);
+         do
+         {
+            switch(jjstateSet[--i])
+            {
+               default : break;
+            }
+         } while(i != startsAt);
+      }
+      if (kind != 0x7fffffff)
+      {
+         jjmatchedKind = kind;
+         jjmatchedPos = curPos;
+         kind = 0x7fffffff;
+      }
+      ++curPos;
+      if ((i = jjnewStateCnt) == (startsAt = 59 - (jjnewStateCnt = startsAt)))
+         return curPos;
+      try { curChar = input_stream.readChar(); }
+      catch(java.io.IOException e) { return curPos; }
+   }
+}
+static final int[] jjnextStates = {
+   22, 23, 27, 28, 5, 29, 35, 39, 29, 35, 39, 46, 52, 58, 2, 3, 
+   42, 43, 48, 49, 54, 55, 
+};
+
+/** Token literal values. */
+public static final String[] jjstrLiteralImages = {
+"", null, "\53", "\55", "\72", null, null, null, "\50", "\51", null, "\136", 
+"\56", "\52", null, null, null, null, null, null, null, null, null, };
+
+/** Lexer state names. */
+public static final String[] lexStateNames = {
+   "DEFAULT", 
+};
+protected SimpleCharStream input_stream;
+private final int[] jjrounds = new int[59];
+private final int[] jjstateSet = new int[118];
+protected char curChar;
+/** Constructor. */
+public UnitParserTokenManager(SimpleCharStream stream){
+   if (SimpleCharStream.staticFlag)
+      throw new Error("ERROR: Cannot use a static CharStream class with a non-static lexical analyzer.");
+   input_stream = stream;
+}
+
+/** Constructor. */
+public UnitParserTokenManager(SimpleCharStream stream, int lexState){
+   this(stream);
+   SwitchTo(lexState);
+}
+
+/** Reinitialise parser. */
+public void ReInit(SimpleCharStream stream)
+{
+   jjmatchedPos = jjnewStateCnt = 0;
+   curLexState = defaultLexState;
+   input_stream = stream;
+   ReInitRounds();
+}
+private void ReInitRounds()
+{
+   int i;
+   jjround = 0x80000001;
+   for (i = 59; i-- > 0;)
+      jjrounds[i] = 0x80000000;
+}
+
+/** Reinitialise parser. */
+public void ReInit(SimpleCharStream stream, int lexState)
+{
+   ReInit(stream);
+   SwitchTo(lexState);
+}
+
+/** Switch to specified lex state. */
+public void SwitchTo(int lexState)
+{
+   if (lexState >= 1 || lexState < 0)
+      throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+   else
+      curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+   final Token t;
+   final String curTokenImage;
+   final int beginLine;
+   final int endLine;
+   final int beginColumn;
+   final int endColumn;
+   String im = jjstrLiteralImages[jjmatchedKind];
+   curTokenImage = (im == null) ? input_stream.GetImage() : im;
+   beginLine = input_stream.getBeginLine();
+   beginColumn = input_stream.getBeginColumn();
+   endLine = input_stream.getEndLine();
+   endColumn = input_stream.getEndColumn();
+   t = Token.newToken(jjmatchedKind, curTokenImage);
+
+   t.beginLine = beginLine;
+   t.endLine = endLine;
+   t.beginColumn = beginColumn;
+   t.endColumn = endColumn;
+
+   return t;
+}
+
+int curLexState = 0;
+int defaultLexState = 0;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+/** Get the next Token. */
+public Token getNextToken() 
+{
+  Token matchedToken;
+  int curPos = 0;
+
+  EOFLoop :
+  for (;;)
+  {   
+   try   
+   {     
+      curChar = input_stream.BeginToken();
+   }     
+   catch(java.io.IOException e)
+   {        
+      jjmatchedKind = 0;
+      matchedToken = jjFillToken();
+      return matchedToken;
+   }
+
+   jjmatchedKind = 0x7fffffff;
+   jjmatchedPos = 0;
+   curPos = jjMoveStringLiteralDfa0_0();
+   if (jjmatchedKind != 0x7fffffff)
+   {
+      if (jjmatchedPos + 1 < curPos)
+         input_stream.backup(curPos - jjmatchedPos - 1);
+         matchedToken = jjFillToken();
+         return matchedToken;
+   }
+   int error_line = input_stream.getEndLine();
+   int error_column = input_stream.getEndColumn();
+   String error_after = null;
+   boolean EOFSeen = false;
+   try { input_stream.readChar(); input_stream.backup(1); }
+   catch (java.io.IOException e1) {
+      EOFSeen = true;
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+      if (curChar == '\n' || curChar == '\r') {
+         error_line++;
+         error_column = 0;
+      }
+      else
+         error_column++;
+   }
+   if (!EOFSeen) {
+      input_stream.backup(1);
+      error_after = curPos <= 1 ? "" : input_stream.GetImage();
+   }
+   throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+  }
+}
+
+private void jjCheckNAdd(int state)
+{
+   if (jjrounds[state] != jjround)
+   {
+      jjstateSet[jjnewStateCnt++] = state;
+      jjrounds[state] = jjround;
+   }
+}
+private void jjAddStates(int start, int end)
+{
+   do {
+      jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+   } while (start++ != end);
+}
+private void jjCheckNAddTwoStates(int state1, int state2)
+{
+   jjCheckNAdd(state1);
+   jjCheckNAdd(state2);
+}
+
+private void jjCheckNAddStates(int start, int end)
+{
+   do {
+      jjCheckNAdd(jjnextStates[start]);
+   } while (start++ != end);
+}
+
+}
diff --git a/visad/data/units/UnitPrefix.java b/visad/data/units/UnitPrefix.java
new file mode 100644
index 0000000..cd6f5af
--- /dev/null
+++ b/visad/data/units/UnitPrefix.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: UnitPrefix.java,v 1.1 2000-11-17 18:54:46 dglo Exp $
+ */
+
+package visad.data.units;
+
+
+/**
+ * Class for representing unit prefixes.
+ */
+public class
+UnitPrefix
+    implements	java.io.Serializable
+{
+    /**
+     * The name of the prefix:
+     */
+    public final String	name;
+
+    /**
+     * The value of the prefix:
+     */
+    public final double	value;
+
+
+    /**
+     * Construct.
+     *
+     * @param name	The name of the prefix (e.g. "mega", "M").
+     * @param value	The value of the prefix (e.g. 1e6).
+     * @require		The arguments shall be non-null.
+     * @exception IllegalArgumentException	One of the arguments was null.
+     */
+    public
+    UnitPrefix(String name, double value)
+    {
+	if (name == null)
+	    throw new IllegalArgumentException("Null prefix name");
+
+	this.name = name;
+	this.value = value;
+    }
+}
diff --git a/visad/data/units/UnitTable.java b/visad/data/units/UnitTable.java
new file mode 100644
index 0000000..66a4556
--- /dev/null
+++ b/visad/data/units/UnitTable.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: UnitTable.java,v 1.6 2009-04-21 20:15:10 steve Exp $
+ */
+
+package visad.data.units;
+
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import visad.BaseUnit;
+import visad.DerivedUnit;
+import visad.OffsetUnit;
+import visad.SI;
+import visad.ScaledUnit;
+import visad.Unit;
+
+
+/**
+ * Provides support for a table of units.
+ *
+ * @author Steven R. Emmerson
+ */
+public class
+UnitTable
+    implements  UnitsDB, java.io.Serializable
+{
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+     * Name-to-unit map.
+     * @serial
+     */
+    private final Hashtable nameMap;
+
+    /**
+     * Symbol-to-unit map.
+     * @serial
+     */
+    private final Hashtable symbolMap;
+
+    /**
+     * The unit set.
+     * @serial
+     */
+    private final SortedSet unitSet;
+
+
+    /**
+     * Construct.
+     *
+     * @param numNames      Anticipated minimum number of names in the
+     *              database.
+     * @throws IllegalArgumentException <code>numNames < 0</code>.
+     */
+    public
+    UnitTable(int numNames)
+    throws IllegalArgumentException
+    {
+    this(numNames, 0);
+    }
+
+
+    /**
+     * Construct.
+     *
+     * @param numNames      Anticipated minimum number of names in the
+     *              database.
+     * @param numSymbols    Anticipated minimum number of symbols in the
+     *              database.
+     * @throws IllegalArgumentException <code>numNames < 0 || numSymbols < 0
+     *                  </code>.
+     */
+    public
+    UnitTable(int numNames, int numSymbols)
+    throws IllegalArgumentException
+    {
+    if (numNames < 0 || numSymbols < 0)
+        throw new IllegalArgumentException("Negative hashtable size");
+    nameMap = new Hashtable(numNames);
+    symbolMap = new Hashtable(numSymbols);
+    unitSet =
+        Collections.synchronizedSortedSet(
+        new TreeSet(
+            new Comparator()
+            {
+            public int
+            compare(Object o1, Object o2)
+            {
+                return ((Unit)o1).toString().compareToIgnoreCase(
+                ((Unit)o2).toString());
+            }
+            }));
+    }
+
+
+    /**
+     * Get a unit.
+     *
+     * @param name  The exact name of the unit to be retrieved.  If it is
+     *          the empty string, then the dimensionless, unity
+     *          unit
+     *          will be returned.
+     * @return      The unit of the matching entry or null if not found.
+     * @require     <code>name</code> is non-null.
+     */
+    public Unit
+    get(String name)
+    {
+    Unit    unit = null;    // default
+    if (name.length() == 0)
+    {
+        // Return a unity, dimensionless unit.
+        unit = new DerivedUnit();
+    }
+    else
+    {
+        /*
+         * Try the symbol table first because symbols are case-sensitive.
+         */
+        unit = getBySymbol(name);
+        if (unit == null)
+        unit = getByName(name);
+    }
+    return unit;
+    }
+
+
+    /**
+     * Get a unit by name.
+     *
+     * @param name  The name of the unit to be retrieved.  If it is
+     *          the empty string, then the dimensionless, unity
+     *          unit
+     *          will be returned.
+     * @return      The unit of the matching entry or null if not found.
+     * @require     <code>name</code> is non-null.
+     */
+    protected Unit
+    getByName(String name)
+    {
+    return (Unit)nameMap.get(name.toLowerCase());
+    }
+
+
+    /**
+     * Get a unit by symbol.
+     *
+     * @param symbol    The exact symbol of the unit to be retrieved.  If it is
+     *          the empty string, then the dimensionless, unity
+     *          unit
+     *          will be returned.
+     * @return      The unit of the matching entry or null if not found.
+     * @require     <code>name</code> is non-null.
+     */
+    protected Unit
+    getBySymbol(String symbol)
+    {
+    return (Unit)symbolMap.get(symbol);
+    }
+
+
+    /**
+     * Adds a base unit.
+     *
+     * @param unit  The base unit to be added.
+     * @throws IllegalArgumentException
+     *          The base unit argument is invalid.
+     */
+    public void
+    put(BaseUnit unit)
+    throws IllegalArgumentException
+    {
+    String  name = unit.unitName();
+    putName(name, unit);
+    putName(makePlural(name), unit);
+    putSymbol(unit.unitSymbol(), unit);
+    }
+
+
+    /**
+     * Returns the plural form of a name.  Regular rules are used to generate
+     * the plural form.
+     * @param name      The name.
+     * @return          The plural form of the name.
+     */
+    protected String
+    makePlural(String name)
+    {
+    String  plural;
+    int length = name.length();
+    char    lastChar = name.charAt(length-1);
+    if (lastChar != 'y')
+    {
+        plural = name +
+        (lastChar == 's' || lastChar == 'x' ||
+         lastChar == 'z' || name.endsWith("ch")
+            ? "es"
+            : "s");
+    }
+    else
+    {
+        if (length == 1)
+        {
+        plural = name + "s";
+        }
+        else
+        {
+        char    penultimateChar = name.charAt(length-2);
+        plural =
+            (penultimateChar == 'a' || penultimateChar == 'e' ||
+             penultimateChar == 'i' || penultimateChar == 'o' ||
+             penultimateChar == 'u')
+            ? name + "s"
+            : name.substring(0, length-1) + "ies";
+        }
+    }
+    return plural;
+    }
+
+
+    /**
+     * Adds a name and a unit to the name table.
+     * @param name      The name to be added.
+     * @param unit      The unit to be added.
+     * @throws IllegalArgumentException Invalid argument.
+     */
+    public void
+    putName(String name, Unit unit)
+    {
+    if (name == null)
+        throw new IllegalArgumentException(this.getClass().getName() +
+        ".putName(String,Unit): <null> unit name");
+    if (unit == null)
+        throw new IllegalArgumentException(this.getClass().getName() +
+        ".putName(String,Unit): <null> unit");
+    name = name.toLowerCase();
+    String[]    names =
+        (name.indexOf(' ') == -1 && name.indexOf('_') == -1)
+        ? new String[] {name}
+        : new String[] {
+            name.replace('_', ' '),
+            name.replace(' ', '_')};
+    for (int i = 0; i < names.length; i++)
+    {
+        Unit    prevUnit = (Unit)nameMap.get(names[i]);
+        if (prevUnit != null && !prevUnit.equals(unit))
+        throw new IllegalArgumentException(
+            "Attempt to replace unit \"" + prevUnit + " with unit \"" +
+            unit + '"');
+        nameMap.put(names[i], unit);
+    }
+    unitSet.add(unit);
+    }
+
+
+    /**
+     * Adds a symbol and a unit to the symbol table.
+     * @param symbol        The symbol to be added.
+     * @param unit      The unit to be added.
+     * @throws IllegalArgumentException Invalid argument.
+     */
+    public void
+    putSymbol(String symbol, Unit unit)
+    {
+    if (symbol == null)
+        throw new IllegalArgumentException(this.getClass().getName() +
+        ".putName(String,Unit): <null> unit symbol");
+    if (unit == null)
+        throw new IllegalArgumentException(this.getClass().getName() +
+        ".putName(String,Unit): <null> unit");
+    Unit    prevUnit = (Unit)symbolMap.get(symbol);
+    if (prevUnit != null && !prevUnit.equals(unit))
+        throw new IllegalArgumentException(
+        "Attempt to replace unit \"" + prevUnit + " with unit \"" +
+        unit + '"');
+    symbolMap.put(symbol, unit);
+    unitSet.add(unit);
+    }
+
+
+    /**
+     * Get an enumeration of the unit names in the table.  The Object returned
+     * by nextElement() is a String.
+     */
+    public Enumeration
+    getNameEnumeration()
+    {
+    return nameMap.keys();
+    }
+
+
+    /**
+     * Get an enumeration of the unit symbols in the table.  The Object returned
+     * by nextElement() is a String.
+     */
+    public Enumeration
+    getSymbolEnumeration()
+    {
+    return symbolMap.keys();
+    }
+
+
+    /**
+     * Get an enumeration of the units in the table.  The Object returned
+     * by nextElement() is a Unit.
+     */
+    public Enumeration
+    getUnitEnumeration()
+    {
+    return new Enumeration()
+    {
+        private final Iterator  iter = unitSet.iterator();
+        public boolean hasMoreElements()
+        {
+        return iter.hasNext();
+        }
+        public Object nextElement()
+        {
+        return iter.next();
+        }
+    };
+    }
+
+
+    /**
+     * Return a string representation of this instance.
+     */
+    public String
+    toString()
+    {
+    return nameMap.toString() + symbolMap.toString();
+    }
+
+
+    /**
+     * Test this class.
+     * @exception java.lang.Exception   A problem occurred.
+     */
+    public static void main(String[] args)
+    throws Exception
+    {
+    UnitTable   db = new UnitTable(13);
+    db.put(SI.ampere);
+    db.put(SI.candela);
+    db.put(SI.kelvin);
+    db.put(SI.kilogram);
+    db.put(SI.meter);
+    db.put(SI.mole);
+    db.put(SI.second);
+    db.put(SI.radian);
+    db.putName("amp", SI.ampere);   // alias
+    db.putName("celsius", new OffsetUnit(273.15, SI.kelvin));
+    db.putName("newton",  
+        SI.kilogram.multiply(SI.meter).divide(SI.second.pow(2)));
+    db.putName("rankine", new ScaledUnit(1/1.8, SI.kelvin));
+    db.putName("fahrenheit",
+        new OffsetUnit(459.67, (ScaledUnit)db.get("rankine")));
+    System.out.println("db:");
+    System.out.println(db.toString());
+    }
+
+
+    /**
+     * List the units in the database.
+     */
+    public void
+    list()
+    {
+    Enumeration en = getUnitEnumeration();
+    
+    while (en.hasMoreElements())
+    {
+        Unit    unit = (Unit)en.nextElement();
+        System.out.println(unit.getIdentifier() + " = " + 
+        unit.getDefinition());
+    }
+    }
+}
diff --git a/visad/data/units/UnitsDB.java b/visad/data/units/UnitsDB.java
new file mode 100644
index 0000000..20aa968
--- /dev/null
+++ b/visad/data/units/UnitsDB.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 1998, University Corporation for Atmospheric Research
+ * See file LICENSE for copying and redistribution conditions.
+ *
+ * $Id: UnitsDB.java,v 1.3 2009-04-21 20:15:10 steve Exp $
+ */
+
+package visad.data.units;
+
+
+import java.util.Enumeration;
+import visad.BaseUnit;
+import visad.Unit;
+
+
+/**
+ * The units database interface.
+ *
+ * This class exists to allow the user to construct their own units database.
+ */
+public interface
+UnitsDB
+{
+    /**
+     * Adds a base unit.
+     *
+     * @param unit  The base unit to be added.
+     * @throws java.lang.IllegalArgumentException
+     *          The base unit argument is invalid.
+     */
+    void
+    put(BaseUnit unit)
+    throws IllegalArgumentException;
+
+
+    /**
+     * Adds a name and a unit to the name table.
+     * @param name      The name to be added.
+     * @param unit      The unit to be added.
+     * @throws IllegalArgumentException Different unit with the same name is
+     *                  already in the table.
+     */
+    void
+    putName(String name, Unit unit)
+    throws IllegalArgumentException;
+
+
+    /**
+     * Adds a symbol and a unit to the symbol table.
+     * @param symbol        The symbol to be added.
+     * @param unit      The unit to be added.
+     * @throws IllegalArgumentException Different unit with the same symbol is
+     *                  already in the table.
+     */
+    void
+    putSymbol(String symbol, Unit unit)
+    throws IllegalArgumentException;
+
+
+    /**
+     * Get a unit.
+     *
+     * @param name  The name of the unit to be retrieved from the database.
+     * @return      The matching unit entry in the database.
+     * @require     <code>name</code> shall be non-null.
+     */
+    Unit
+    get(String name);
+
+
+    /**
+     * Get an enumeration of the unit names in the database.
+     */
+    Enumeration
+    getNameEnumeration();
+
+
+    /**
+     * Get an enumeration of the unit symbols in the database.
+     */
+    Enumeration
+    getSymbolEnumeration();
+
+
+    /**
+     * Get an enumeration of the units in the database.
+     */
+    Enumeration
+    getUnitEnumeration();
+
+
+    /**
+     * List the units in the database.
+     */
+    void
+    list();
+}
diff --git a/visad/data/units/index.html b/visad/data/units/index.html
new file mode 100644
index 0000000..22d27a4
--- /dev/null
+++ b/visad/data/units/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE htmlplus PUBLIC "-//Internet/RFC xxxx//EN">
+<HTMLPLUS>
+<HEAD>
+<TITLE>VisAD units Subsystem</TITLE>
+</HEAD>
+
+<BODY>
+<H1 align=center>VisAD units Subsystem</H1>
+
+<h2>Links</h2>
+
+<UL>
+<li>The <a href="packages.html">visad.data.units</a> package.</li>
+</UL>
+
+</BODY>
diff --git a/visad/data/vis5d/V5DStruct.java b/visad/data/vis5d/V5DStruct.java
new file mode 100644
index 0000000..6323167
--- /dev/null
+++ b/visad/data/vis5d/V5DStruct.java
@@ -0,0 +1,2601 @@
+//
+// V5DStruct.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.data.BadFormException;
+import ucar.netcdf.*;
+import java.io.IOException;
+import java.net.URL;
+
+/** An object representing the structure of a .v5d file.<P> */
+public class V5DStruct {
+
+  /** Amount of physical RAM in megabytes.
+      Vis5D normally uses a bounded amount of memory to avoid swapping.
+      When the limit is reached, the least-recently-viewed graphics will
+      be deallocated.  If MBS is set to 0, however, vis5d will use ordinary
+      malloc/free and not deallocate graphics (ok for systems with a lot
+      of memory (>=128MB)).
+  */
+  private static final int MBS = 32;
+
+  /** Default topography file */
+  private static final String TOPOFILE = "EARTH.TOPO";
+
+  /** Default world map lines file */
+  private static final String WORLDFILE = "OUTLSUPW";
+
+  /** Default USA map lines file */
+  private static final String USAFILE = "OUTLUSAM";
+
+  /** Default filename of Tcl startup commands */
+  private static final String TCL_STARTUP_FILE = "vis5d.tcl";
+
+  /** Default directory to search for user functions */
+  private static final String FUNCTION_PATH = "userfuncs";
+
+  /** Default animation rate in milliseconds */
+  private static final int ANIMRATE = 100;
+
+  /** Default scale value for logarithmic vertical coordinate system */
+  private static final double DEFAULT_LOG_SCALE = 1012.5;
+
+  /** Default exponent value for logarithmic vertical coordinate system */
+  private static final double DEFAULT_LOG_EXP = -7.2;
+
+
+  // ******************************************************************** //
+  // ******************************************************************** //
+  // **          USERS:  DON'T CHANGE THE FOLLOWING CONSTANTS          ** //
+  // ******************************************************************** //
+  // ******************************************************************** //
+
+  /** Define BIG_GFX to allow larger isosurfaces, contour slices, etc., if
+      there's enough memory.
+  */
+  private static final boolean BIG_GFX = true;
+
+  /** Shared by code above and below API */
+  private static final int MAX_LABEL = 1000;
+
+  /** Shared by code above and below API */
+  private static final int MAX_FUNCS = 100;
+
+
+  /** A numeric version number which we can test for in utility programs which
+      use the v5d functions. If V5D_VERSION is not defined, then its value
+      is considered to be zero.
+  */
+  private static final int V5D_VERSION = 42;
+
+  /** Represents a missing value */
+  private static final float MISSING = Float.NaN;
+
+  /** Tests whether a given value is missing */
+  private static boolean IS_MISSING(float x) {
+    return Float.isNaN(x) || x >= 1.0e30;
+  }
+
+  /** Limit on 5-D grid variables */
+  private static final int MAXVARS = 200;
+
+  /** Limit on 5-D grid times */
+  private static final int MAXTIMES = 400;
+
+  /** Limit on 5-D grid rows */
+  private static final int MAXROWS = 400;
+
+  /** Limit on 5-D grid columns */
+  private static final int MAXCOLUMNS = 400;
+
+  /** Limit on 5-D grid levels */
+  private static final int MAXLEVELS = 400;
+
+
+  // ********************************************************************** //
+  // **                                                                  ** //
+  // ** Definition of v5d struct and related information.                ** //
+  // **                                                                  ** //
+  // ********************************************************************** //
+
+  private static final int MAXPROJARGS = (MAXROWS+MAXCOLUMNS+1);
+  private static final int MAXVERTARGS = (MAXLEVELS+1);
+
+  /** File version. This should be updated when the file version changes. */
+  private static final String FILE_VERSION = "4.3";
+
+  // TODO: find optimal value of default buffer size
+  private static final int DEFAULT_FILE_BUFFER = 204800;
+  private static final int DEFAULT_HTTP_BUFFER = 204800;
+
+  /*
+   * New grid file format for VIS-5D:
+   *
+   * The header is a list of tagged items.  Each item has 3 parts:
+   *    1. A tag which is a 4-byte integer identifying the type of item.
+   *    2. A 4-byte integer indicating how many bytes of data follow.
+   *    3. The binary data.
+   *
+   * If we need to add new information to a file header we just create a
+   * new tag and add the code to read/write the information.
+   *
+   * If we're reading a header and find an unknown tag, we can use the
+   * length field to skip ahead to the next tag.  Therefore, the file
+   * format is forward (and backward) compatible.
+   *
+   * Grid data is stored as either:
+   *     1-byte unsigned integers  (255=missing)
+   *     2-byte unsigned integers  (65535=missing)
+   *     4-byte IEEE floats        (>1.0e30 = missing)
+   *
+   * All numeric values are stored in big endian order.  All floating point
+   * values are in IEEE format.
+   */
+
+  /*
+   * Currently defined tags:
+   * Note:  the notation a[i] doesn't mean a is an array of i elements,
+   * rather it just refers to the ith element of a[].
+   *
+   * Tags marked as PHASED OUT should be readable but are no longer written.
+   * Old tag numbers can't be reused!
+   *
+   */
+
+  /** hex encoding of "V5D\n" */
+  public static final int TAG_ID              = 0x5635440a;
+
+  // general stuff 1000+
+
+  /** char*10 FileVersion */
+  public static final int TAG_VERSION         = 1000;
+
+  /** int*4 NumTimes */
+  public static final int TAG_NUMTIMES        = 1001;
+
+  /** int*4 NumVars */
+  public static final int TAG_NUMVARS         = 1002;
+
+  /** int*4 var; char*10 VarName[var] */
+  public static final int TAG_VARNAME         = 1003;
+
+  /** int*4 Nr */
+  public static final int TAG_NR              = 1004;
+
+  /** int*4 Nc */
+  public static final int TAG_NC              = 1005;
+
+  /** int*4 Nl  (Nl for all vars) */
+  public static final int TAG_NL              = 1006;
+
+  /** int*4 var; int*4 Nl[var] */
+  public static final int TAG_NL_VAR          = 1007;
+
+  /** int*4 var; int*4 LowLev[var] */
+  public static final int TAG_LOWLEV_VAR      = 1008;
+
+  /** int*4 t; int*4 TimeStamp[t] */
+  public static final int TAG_TIME            = 1010;
+
+  /** int*4 t; int*4 DateStamp[t] */
+  public static final int TAG_DATE            = 1011;
+
+  /** int*4 var; real*4 MinVal[var] */
+  public static final int TAG_MINVAL          = 1012;
+
+  /** int*4 var; real*4 MaxVal[var] */
+  public static final int TAG_MAXVAL          = 1013;
+
+  /** int*4 CompressMode; (#bytes/grid) */
+  public static final int TAG_COMPRESS        = 1014;
+
+  /** int *4 var; char*20 Units[var] */
+  public static final int TAG_UNITS           = 1015;
+
+  // vertical coordinate system 2000+
+
+  /** int*4 VerticalSystem */
+  public static final int TAG_VERTICAL_SYSTEM = 2000;
+  /** int*4 n;  real*4 VertArgs[0..n-1] */
+  public static final int TAG_VERT_ARGS       = 2100;
+
+  /** real*4 BottomBound (PHASED OUT) */
+  public static final int TAG_BOTTOMBOUND     = 2001;
+  /** real*4 LevInc (PHASED OUT) */
+  public static final int TAG_LEVINC          = 2002;
+  /** int*4 l;  real*4 Height[l] (PHASED OUT) */
+  public static final int TAG_HEIGHT          = 2003;
+
+  // projection 3000+
+
+  /** int*4 projection.
+      <li> 0 = generic linear
+      <li> 1 = cylindrical equidistant
+      <li> 2 = Lambert conformal/Polar Stereo
+      <li> 3 = rotated equidistant
+  */
+  public static final int TAG_PROJECTION      = 3000;
+
+  /** int*4 n; real*4 ProjArgs[0..n-1] */
+  public static final int TAG_PROJ_ARGS       = 3100;
+
+  /** real*4 NorthBound (PHASED OUT) */
+  public static final int TAG_NORTHBOUND      = 3001;
+
+  /** real*4 WestBound (PHASED OUT) */
+  public static final int TAG_WESTBOUND       = 3002;
+
+  /** real*4 RowInc (PHASED OUT) */
+  public static final int TAG_ROWINC          = 3003;
+
+  /** real*4 ColInc (PHASED OUT) */
+  public static final int TAG_COLINC          = 3004;
+
+  /** real*4 Lat1 (PHASED OUT) */
+  public static final int TAG_LAT1            = 3005;
+
+  /** real*4 Lat2 (PHASED OUT) */
+  public static final int TAG_LAT2            = 3006;
+
+  /** real*4 PoleRow (PHASED OUT) */
+  public static final int TAG_POLE_ROW        = 3007;
+
+  /** real*4 PoleCol (PHASED OUT) */
+  public static final int TAG_POLE_COL        = 3008;
+
+  /** real*4 CentralLon (PHASED OUT) */
+  public static final int TAG_CENTLON         = 3009;
+
+  /** real*4 CentralLat (PHASED OUT) */
+  public static final int TAG_CENTLAT         = 3010;
+
+  /** real*4 CentralRow (PHASED OUT) */
+  public static final int TAG_CENTROW         = 3011;
+
+  /** real*4 CentralCol (PHASED OUT) */
+  public static final int TAG_CENTCOL         = 3012;
+
+  /** real*4 Rotation (PHASED OUT) */
+  public static final int TAG_ROTATION        = 3013;
+
+  public static final int TAG_END             = 9999;
+
+
+  // ******************************* //
+  // *** Start of main v5dstruct *** //
+  // ******************************* //
+
+  // PUBLIC (user can freely read, sometimes write, these fields)
+
+  /** Number of time steps */
+  public int NumTimes;
+
+  /** Number of variables */
+  public int NumVars;
+
+  /** Number of rows */
+  public int Nr;
+
+  /** Number of columns */
+  public int Nc;
+
+  /** Number of levels per variable */
+  public int[] Nl = new int[MAXVARS];
+
+  /** Lowest level per variable */
+  public int[] LowLev = new int[MAXVARS];
+
+  /** 9-character variable names */
+  public char[][] VarName = new char[MAXVARS][10];
+
+  /** 19-character units for variables */
+  public char[][] Units = new char[MAXVARS][20];
+
+  /** Time in HHMMSS format */
+  public int[] TimeStamp = new int[MAXTIMES];
+
+  /** Date in YYDDD format */
+  public int[] DateStamp = new int[MAXTIMES];
+
+  /** Minimum variable data values */
+  public float[] MinVal = new float[MAXVARS];
+
+  /** Maximum variable data values */
+  public float[] MaxVal = new float[MAXVARS];
+
+  // This info is used for external function computation
+
+  /** McIDAS file number in 1..9999 */
+  public short[][] McFile = new short[MAXTIMES][MAXVARS];
+
+  /** McIDAS grid number in 1..? */
+  public short[][] McGrid = new short[MAXTIMES][MAXVARS];
+
+  /** Which vertical coordinate system */
+  public int VerticalSystem;
+
+  /** Vertical Coordinate System arguments. <br><br>
+      <pre>
+      IF VerticalSystem==0 THEN
+          -- Linear scale, equally-spaced levels in generic units
+          VertArgs[0] = Height of bottom-most grid level in generic units
+          VertArgs[1] = Increment between levels in generic units
+      ELSE IF VerticalSystem==1 THEN
+          -- Linear scale, equally-spaced levels in km
+          VertArgs[0] = Height of bottom grid level in km
+          VertArgs[1] = Increment between levels in km
+      ELSE IF VerticalSystem==2 THEN
+          -- Linear scale, Unequally spaced levels in km
+          VertArgs[0] = Height of grid level 0 (bottom) in km
+          ...                ...
+          VertArgs[n] = Height of grid level n in km
+      ELSE IF VerticalSystem==3 THEN
+          -- Linear scale, Unequally spaced levels in mb
+          VertArgs[0] = Pressure of grid level 0 (bottom) in mb
+          ...             ...
+          VertArgs[n] = Pressure of grid level n in mb
+      ENDIF
+      </pre>
+  */
+  public float[] VertArgs = new float[MAXVERTARGS];
+
+
+  /** Which map projection */
+  public int Projection;
+
+  /** Map projection arguments. <br><br>
+      <pre>
+      IF Projection==0 THEN
+          -- Rectilinear grid, generic units
+          ProjArgs[0] = North bound, Y coordinate of grid row 0
+          ProjArgs[1] = West bound, X coordiante of grid column 0
+          ProjArgs[2] = Increment between rows
+          ProjArgs[3] = Increment between colums
+          NOTES: X coordinates increase to the right, Y increase upward.
+          NOTES: Coordinate system is right-handed.
+     ELSE IF Projection==1 THEN
+          -- Cylindrical equidistant (Old VIS-5D)
+          -- Rectilinear grid in lat/lon
+          ProjArgs[0] = Latitude of grid row 0, north bound, in degrees
+          ProjArgs[1] = Longitude of grid column 0, west bound, in deg.
+          ProjArgs[2] = Increment between rows in degrees
+          ProjArgs[3] = Increment between rows in degrees
+          NOTES: Coordinates (degrees) increase to the left and upward.
+     ELSE IF Projection==2 THEN
+          -- Lambert conformal
+          ProjArgs[0] = Standared Latitude 1 of conic projection
+          ProjArgs[1] = Standared Latitude 2 of conic projection
+          ProjArgs[2] = Row of North/South pole
+          ProjArgs[3] = Column of North/South pole
+          ProjArgs[4] = Longitude which is parallel to columns
+          ProjArgs[5] = Increment between grid columns in km
+      ELSE IF Projection==3 THEN
+          -- Polar Stereographic
+          ProjArgs[0] = Latitude of center of projection
+          ProjArgs[1] = Longitude of center of projection
+          ProjArgs[2] = Grid row of center of projection
+          ProjArgs[3] = Grid column of center of projection
+          ProjArgs[4] = Increment between grid columns at center in km
+      ELSE IF Projection==4 THEN
+          -- Rotated
+          ProjArgs[0] = Latitude on rotated globe of grid row 0
+          ProjArgs[1] = Longitude on rotated globe of grid column 0
+          ProjArgs[2] = Degrees of latitude on rotated globe between
+                        grid rows
+          ProjArgs[3] = Degrees of longitude on rotated globe between
+                        grid columns
+          ProjArgs[4] = Earth latitude of (0, 0) on rotated globe
+          ProjArgs[5] = Earth longitude of (0, 0) on rotated globe
+          ProjArgs[6] = Clockwise rotation of rotated globe in degrees
+      ENDIF
+      </pre>
+  */
+  public float[] ProjArgs = new float[MAXPROJARGS];
+
+  /** 1, 2 or 4 = # bytes per grid point */
+  public int CompressMode;
+
+  /** 9-character version number */
+  public String FileVersion;
+
+  // PRIVATE (not to be touched by user code)
+
+  /** COMP5D file version or 0 if .v5d */
+  private int FileFormat;
+
+  /** Java file descriptor */
+  private RandomAccessFile FileDesc;
+
+  /** 'r' = read, 'w' = write */
+  char Mode;
+
+  /** current position of file pointer */
+  int CurPos;
+
+  /** position of first grid in file */
+  int FirstGridPos;
+
+  /** size of each grid */
+  int[] GridSize = new int[MAXVARS];
+
+  /** sum of GridSize[0..NumVars-1] */
+  int SumGridSizes;
+
+
+  // ************************* //
+  // *** GATEWAY FUNCTIONS *** //
+  // ************************* //
+
+  /** Open a Vis5D file */
+  public 
+    static 
+      V5DStruct v5d_open( byte[] name, int name_length, int[] sizes,
+                          int[] n_levels,
+                          String[] var_names, String[] var_units,
+                          int[] map_proj, float[] projargs,
+                          int[] vert_sys, float[] vert_args, double[] times)
+    throws IOException, BadFormException
+  {
+    int i, j, k, k2, m, m2;
+    int day, time, first_day, first_time;
+    // char[] filename = new char[200];
+    byte[] varnames = new byte[10*MAXVARS];
+    byte[] varunits = new byte[20*MAXVARS];
+
+    // open file
+    // Modified 01-Feb-2005 DRM:  caused problems on Mac-OSX
+    //for (i=0; i<name_length; i++) filename[i] = (char) name[i];
+    //filename[name_length] = 0;
+    //V5DStruct v = v5dOpenFile(new String(filename));
+    V5DStruct v = v5dOpenFile(new String( name, 0, name_length)); 
+
+    if (v != null) {
+      // get basic sizes
+      sizes[0] = v.Nr;
+      sizes[1] = v.Nc;
+      sizes[3] = v.NumTimes;
+      sizes[4] = v.NumVars;
+
+      // compute varnames
+      for (j=0; j<v.NumVars; j++) {
+        k = 10 * j;
+        for (i=0; i<10; i++) {
+          if (v.VarName[j][i] != 0 && i < 9) {
+            varnames[k + i] = (byte) v.VarName[j][i];
+          }
+          else {
+            varnames[k + i] = 0;
+            break;
+          }
+        }
+      }
+
+      // compute varunits
+      for (j=0; j<v.NumVars; j++) {
+        k = 20 * j;
+        for (i=0; i<20; i++) {
+          if (v.Units[j][i] != 0 && i < 19) {
+            varunits[k + i] = (byte) v.Units[j][i];
+          }
+          else {
+            varunits[k + i] = 0;
+            break;
+          }
+        }
+      }
+
+    //- make var/unit Strings:
+
+      for (i=0; i<v.NumVars; i++) {
+        k = 10 * i;
+        k2 = 20 * i;
+        m = k;
+        m2 = k2;
+        while (varnames[m] != 0) {m++;}
+        while (varunits[m2] != 0) {m2++;}
+        var_names[i] = new String(varnames, k, m - k);
+        var_units[i] = new String(varunits, k2, m2 - k2);
+      }
+
+
+      // compute maximum level, and make sure all levels are equal
+      int maxNl = v.Nl[0];
+      for (i=0; i<v.NumVars; i++) {
+        if (v.Nl[i] > maxNl) maxNl = v.Nl[i];
+  /*----if (v.Nl[i] != maxNl) sizes[0] = -1; */
+      }
+ 
+      sizes[2] = maxNl;
+      for (i = 0; i < v.NumVars; i++) {
+        n_levels[i] = v.Nl[i];
+      }
+
+      vert_sys[0] = v.VerticalSystem;
+
+      for ( int kk = 0; kk < maxNl; kk++) {
+        vert_args[kk] = v.VertArgs[kk];
+      }
+
+      // compute times
+      first_day = v5dYYDDDtoDays(v.DateStamp[0]);
+      first_time = v5dHHMMSStoSeconds(v.TimeStamp[0]);
+      for (i=0; i<v.NumTimes; i++) {
+        day = v5dYYDDDtoDays(v.DateStamp[i]);
+        time = v5dHHMMSStoSeconds(v.TimeStamp[i]);
+        /*-TDR
+        times[i] = (day - first_day) * 24 * 60 * 60 + (time - first_time);
+         */
+     //-float ff = (((float)day)*24f*60f*60f + (float)time);
+        double ff = (((double)day)*24.0*60.0*60.0 + (double)time);
+        times[i] = ff;
+      }
+
+      map_proj[0] = v.Projection;
+
+      for (int kk = 0; kk < MAXPROJARGS; kk++) {
+        projargs[kk] = v.ProjArgs[kk];
+      }
+    }
+    else {  //- trouble with file
+      // v == null
+      sizes[0] = -1;
+    }
+
+    return v;
+  }
+
+  /** Read from a Vis5D file */
+  public void v5d_read(int time, int vr, float[] ranges, float[] data)
+    throws IOException, BadFormException
+  {
+
+    boolean status;
+
+    ranges[0] = MinVal[vr];
+    ranges[1] = MaxVal[vr];
+    status = v5dReadGrid(time, vr, data);
+    if (!status) {
+      ranges[0] = 1.0f;
+      ranges[1] = -1.0f;
+    }
+  }
+
+
+  // ******************************************************************** //
+  // ****                  Miscellaneous Functions                   **** //
+  // ******************************************************************** //
+
+  private static boolean SIMPLE_COMPRESSION = false;
+  private static boolean KLUDGE = false;
+  private static boolean ORIGINAL = false;
+
+  /** Convert a signed byte to an unsigned one, and return it in an int */
+  public static int getUnsignedByte(byte b) {
+    int i = (b >= 0 ? (int) b : (int) b + 256);
+    return i;
+  }
+
+  /** Convert two signed bytes to an unsigned short, and return it in an int */
+  public static int getUnsignedShort(byte b1, byte b2) {
+    int i1 = getUnsignedByte(b1);
+    int i2 = getUnsignedByte(b2);
+    return 256 * i1 + i2;
+  }
+  /** Convert four signed bytes to an unsigned short, and return it in an int */
+  public static int getUnsignedInt(byte b1, byte b2, byte b3, byte b4) {
+    int i1 = getUnsignedByte(b1);
+    int i2 = getUnsignedByte(b2);
+    int i3 = getUnsignedByte(b3);
+    int i4 = getUnsignedByte(b4);
+    return 16777216*i1 + 65536*i2 + 256*i3 + i4;
+  }
+
+  private static float pressure_to_height(float pressure) {
+    return (float) (DEFAULT_LOG_EXP *
+      Math.log((double) pressure / DEFAULT_LOG_SCALE));
+  }
+
+  private static float height_to_pressure(float height) {
+    return (float) (DEFAULT_LOG_SCALE *
+      Math.exp((double) height / DEFAULT_LOG_EXP));
+  }
+
+  /** Copy up to maxlen characters from src to dst stopping upon whitespace
+      in src. Terminate dst with null character.
+      @return length of dst
+  */
+  private static int copy_string2(char[] dst, char[] src, int maxlen) {
+    int i;
+
+    for (i=0;i<maxlen;i++) dst[i] = src[i];
+    for (i=maxlen-1; i>=0; i--) {
+      if (dst[i] == ' ' || i == maxlen - 1) dst[i] = 0;
+      else break;
+    }
+    return new String(dst).length();
+  }
+
+  /** Copy up to maxlen characters from src to dst stopping upon whitespace
+      in src. Terminate dst with null character.
+      @return length of dst
+  */
+  private static int copy_string(char[] dst, char[] src, int maxlen) {
+    int i;
+
+    for (i=0; i<maxlen; i++) {
+      if (src[i] == ' ' || i == maxlen - 1) {
+        dst[i] = 0;
+        break;
+      }
+      else dst[i] = src[i];
+    }
+    return i;
+  }
+
+  /** Convert a date from YYDDD format to days since Jan 1, 1900. */
+  private static int v5dYYDDDtoDays(int yyddd) {
+    int iy, id, idays;
+
+    iy = yyddd / 1000;
+    id = yyddd - 1000 * iy;
+    // DRM - 17-Nov-2001 handle YYYYDDD form dates 
+    if (iy >= 1900)
+      iy -= 1900;
+    else if (iy < 50)
+      // WLH 31 July 96 << 31 Dec 99
+      iy += 100; 
+    // Updated from Vis5D+ (sub 1 since they do days from 12/31/1899)
+    //idays = 365 * iy + (iy - 1) / 4 + id;
+    idays = (365*iy + (iy-1)/4 - (iy-1)/100 + (iy+299)/400 + id) - 1;
+
+    return idays;
+  }
+
+  /** Convert a time from HHMMSS format to seconds since midnight. */
+  private static int v5dHHMMSStoSeconds(int hhmmss) {
+    int h, m, s;
+
+    h = hhmmss / 10000;
+    m = (hhmmss / 100) % 100;
+    s = hhmmss % 100;
+
+    return s + m * 60 + h * 60 * 60;
+  }
+
+  /** Convert a day since Jan 1, 1900 to YYDDD format. */
+  private static int v5dDaysToYYDDD(int days) {
+    int iy, id, iyyddd;
+
+    iy = (4 * days) / 1461;
+    id = days - (365 * iy + (iy - 1) / 4);
+    // WLH 31 July 96 << 31 Dec 99
+    // iy = iy + 1900; is the right way to fix this, but requires
+    // changing all places where dates are printed - procrastinate
+    if (iy > 99) iy = iy - 100;
+    iyyddd = iy * 1000 + id;
+
+    return iyyddd;
+  }
+
+  /** Convert a time in seconds since midnight to HHMMSS format. */
+  private static int v5dSecondsToHHMMSS(int seconds) {
+    int hh, mm, ss;
+
+    hh = seconds / (60 * 60);
+    mm = (seconds / 60) % 60;
+    ss = seconds % 60;
+    return hh * 10000 + mm * 100 + ss;
+  }
+
+  /** Open a v5d file for reading.
+      @return null if error, else a pointer to a new V5DStruct
+  */
+  private static V5DStruct v5dOpenFile(String filename)
+          throws IOException, BadFormException {
+    RandomAccessFile fd = 
+      (filename.toLowerCase().startsWith("http"))
+          ? new HTTPRandomAccessFile(new URL(filename), DEFAULT_HTTP_BUFFER )
+          : new RandomAccessFile(filename, "r", DEFAULT_FILE_BUFFER);
+
+    if (fd == null) {
+      // error
+      return null;
+    }
+
+    V5DStruct v = new V5DStruct();
+
+    v.FileDesc = fd;
+    v.Mode = 'r';
+    return (v.read_v5d_header() ? v : null);
+  }
+
+  /** Compute the ga and gb (de)compression values for a grid.
+      @param nr            number of rows of grid
+      @param nc            number of columns of grid
+      @param nl            number of levels of grid
+      @param data          the grid data
+      @param ga            array to store results
+      @param gb            array to store results
+      @param minval        one-element float array for storing min value
+      @param maxval        one-element float array for storing max value
+      @param compressmode  1, 2 or 4 bytes per grid point
+  */
+  private static void compute_ga_gb(int nr, int nc, int nl, float[] data,
+    int compressmode, float[] ga, float[] gb, float[] minval, float[] maxval)
+          throws BadFormException{
+    if (SIMPLE_COMPRESSION) {
+      // compute ga, gb values for whole grid
+      int i, lev, num;
+      boolean allmissing;
+      float min, max, a, b;
+
+      min = 1.0e30f;
+      max = -1.0e30f;
+      num = nr * nc * nl;
+      allmissing = true;
+      for (i=0; i<num; i++) {
+        if (!IS_MISSING(data[i])) {
+          if (data[i] < min)  min = data[i];
+          if (data[i] > max)  max = data[i];
+          allmissing = false;
+        }
+      }
+      if (allmissing) {
+        a = 1.0f;
+        b = 0.0f;
+      }
+      else {
+        a = (float) ((max - min) / 254.0);
+        b = min;
+      }
+
+      // return results
+      for (i=0; i<nl; i++) {
+        ga[i] = a;
+        gb[i] = b;
+      }
+
+      minval[0] = min;
+      maxval[0] = max;
+    }
+    else {
+      // compress grid on level-by-level basis
+      final float SMALLVALUE = -1.0e30f;
+      final float BIGVALUE = 1.0e30f;
+      float gridmin, gridmax;
+      float[] levmin = new float[MAXLEVELS];
+      float[] levmax = new float[MAXLEVELS];
+      float[] d = new float[MAXLEVELS];
+      float dmax;
+      float ival, mval;
+      int j, k, lev, nrnc;
+
+      nrnc = nr * nc;
+
+      // find min and max for each layer and the whole grid
+      gridmin = BIGVALUE;
+      gridmax = SMALLVALUE;
+      j = 0;
+      for (lev=0; lev<nl; lev++) {
+        float min, max;
+        min = BIGVALUE;
+        max = SMALLVALUE;
+        for (k=0; k<nrnc; k++) {
+          if (!IS_MISSING(data[j]) && data[j] < min) min = data[j];
+          if (!IS_MISSING(data[j]) && data[j] > max) max = data[j];
+          j++;
+        }
+        if (min < gridmin) gridmin = min;
+        if (max > gridmax) gridmax = max;
+        levmin[lev] = min;
+        levmax[lev] = max;
+      }
+
+      // WLH 2-2-95
+      if (KLUDGE) {
+        // if the grid minimum is within delt of 0.0, fudge all values
+        // within delt of 0.0 to delt, and recalculate mins and maxes
+        float delt;
+        int nrncnl = nrnc * nl;
+
+        delt = (float) ((gridmax - gridmin) / 100000.0);
+        if (Math.abs(gridmin) < delt && gridmin != 0.0 && compressmode != 4) {
+          float min, max;
+          for (j=0; j<nrncnl; j++) {
+            if (!IS_MISSING(data[j]) && data[j] < delt) data[j] = delt;
+          }
+          // re-calculate min and max for each layer and the whole grid
+          gridmin = delt;
+          for (lev=0; lev<nl; lev++) {
+            if (Math.abs(levmin[lev]) < delt) levmin[lev] = delt;
+            if (Math.abs(levmax[lev]) < delt) levmax[lev] = delt;
+          }
+        }
+      }
+
+      // find d[lev] and dmax = MAX(d[0], d[1], ... d[nl-1])
+      dmax = 0.0f;
+      for (lev=0; lev<nl; lev++) {
+        if (levmin[lev] >= BIGVALUE && levmax[lev] <= SMALLVALUE) {
+          // all values in the layer are MISSING
+          d[lev] = 0.0f;
+        }
+        else {
+          d[lev] = levmax[lev] - levmin[lev];
+        }
+        if (d[lev] > dmax) dmax = d[lev];
+      }
+
+      // compute ga (scale) and gb (bias) for each grid level
+      if (dmax == 0.0) {
+        // special cases
+        if (gridmin == gridmax) {
+          // whole grid is of same value
+          for (lev=0; lev<nl; lev++) {
+            ga[lev] = gridmin;
+            gb[lev] = 0.0f;
+          }
+        }
+        else {
+          // every layer is of a single value
+          for (lev=0; lev<nl; lev++) {
+            ga[lev] = levmin[lev];
+            gb[lev] = 0.0f;
+          }
+        }
+      }
+      else {
+        // normal cases
+        if (compressmode == 1) {
+          ORIGINAL = true;
+          if (ORIGINAL) {
+            ival = dmax / 254.0f;
+            mval = gridmin;
+            for (lev=0; lev<nl; lev++) {
+              ga[lev] = ival;
+              gb[lev] = mval + ival * (int) ((levmin[lev] - mval) / ival);
+            }
+          }
+          else {
+            for (lev=0; lev<nl; lev++) {
+              if (d[lev] == 0.0) ival = 1.0f;
+              else ival = d[lev] / 254.0f;
+              ga[lev] = ival;
+              gb[lev] = levmin[lev];
+            }
+          }
+        }
+        else if (compressmode == 2) {
+          ival = dmax / 65534.0f;
+          mval = gridmin;
+          for (lev=0; lev<nl; lev++) {
+            ga[lev] = ival;
+            gb[lev] = mval + ival * (int) ((levmin[lev] - mval) / ival);
+          }
+        }
+        else {
+          V5Dassert(compressmode == 4);
+          for (lev=0; lev<nl; lev++) {
+            ga[lev] = 1.0f;
+            gb[lev] = 0.0f;
+          }
+        }
+      }
+
+      // update min, max values
+      minval[0] = gridmin;
+      maxval[0] = gridmax;
+    }
+  }
+
+  /** Decompress a 3-D grid from 1-byte integers to 4-byte floats.
+      @param nr            number of rows of grid
+      @param nc            number of columns of grid
+      @param nl            number of levels of grid
+      @param compdata1     array of [nr*nc*nl*compressmode] bytes
+      @param ga            array of decompression factors
+      @param gb            array of decompression factors
+      @param data          array to put decompressed values
+      @param compressmode  1, 2 or 4 bytes per grid point
+  */
+  private void v5dDecompressGrid(int nr, int nc, int nl, int compressmode,
+    byte[] compdata1, float[] ga, float[] gb, float[] data)
+  {
+    int nrnc = nr * nc;
+    int nrncnl = nr * nc * nl;
+
+    if (compressmode == 1) {
+      int p, i, lev;
+      p = 0;
+      for (lev=0; lev<nl; lev++) {
+        float a = ga[lev];
+        float b = gb[lev];
+
+        // WLH 2-2-95
+        float d = 0f;
+        float aa = 0f;
+        int id;
+        if (a > 0.0000000001) {
+          d = b / a;
+          id = (int) Math.floor(d);
+          d = d - id;
+          aa = (float) (a * 0.000001);
+        }
+        else id = 1;
+        if (-254 <= id && id <= 0 && d < aa) {
+          for (i=0; i<nrnc; i++, p++) {
+            int cd1p = getUnsignedByte(compdata1[p]);
+            if (cd1p == 255) data[p] = MISSING;
+            else {
+              data[p] = (float) cd1p * a + b;
+              if (Math.abs(data[p]) < aa) data[p] = aa;
+            }
+          }
+        }
+        else {
+          for (i=0; i<nrnc; i++, p++) {
+            int cd1p = getUnsignedByte(compdata1[p]);
+            if (cd1p == 255) data[p] = MISSING;
+            else data[p] = (float) cd1p * a + b;
+          }
+        }
+        // end of WLH 2-2-95
+      }
+    }
+    else if (compressmode == 2) {
+      int p, i, lev;
+      p = 0;
+      for (lev=0; lev<nl; lev++) {
+        float a = ga[lev];
+        float b = gb[lev];
+        // sizeof(short)==2!
+        for (i=0; i<nrnc; i++, p++) {
+          int cd1p = getUnsignedShort(compdata1[2 * p], compdata1[2 * p + 1]);
+          if (cd1p == 65535) data[p] = MISSING;
+          else data[p] = (float) cd1p * a + b;
+        }
+      }
+    }
+    else {
+      // compressmode==4
+      // other machines: just copy 4-byte IEEE floats
+      // CTR: I have no idea if this works properly in Java...
+      /*-TDR: Nope this don't work, throws ArrayStoreException
+      System.arraycopy(data, 0, compdata1, 0, nrncnl*4);
+       */
+      for (int i=0; i<nrncnl; i++) {
+        int a = getUnsignedInt(compdata1[i*4], compdata1[i*4 + 1],
+                               compdata1[i*4 + 2], compdata1[i*4 + 3]);
+        data[i] = Float.intBitsToFloat(a);
+      }
+    }
+  }
+
+  /** Verifies that a certain condition holds */
+  private static final void V5Dassert(boolean b)
+          throws BadFormException {
+    if (!b) {
+      throw new BadFormException("Warning: assert failed");
+    }
+  }
+
+  /** Read a block of memory.
+      @param f         file descriptor
+      @param data      address of first byte
+      @param elements  number of elements to read
+      @param elsize    size of each element to read (1, 2 or 4)
+      @return number of elements written
+  */
+  private static int read_block(RandomAccessFile f, byte[] data,
+    int elements, int elsize) throws IOException
+  {
+    int n;
+    if (elsize == 1) {
+      n = f.read(data, 0, elements);
+    }
+    else if (elsize == 2) {
+      n = f.read(data, 0, elements*2) / 2;
+    }
+    else if (elsize == 4) {
+      n = f.read(data, 0, elements*4) / 4;
+    }
+    else {
+      throw new IOException("Fatal error in read_block(): " +
+        "bad elsize (" + elsize + ")");
+    }
+    return n;
+  }
+
+  /** Read an array of 4-byte IEEE floats.
+      @param f  file descriptor
+      @param x  address to put floats
+      @param n  number of floats to read
+      @return number of floats read
+  */
+  private static int read_float4_array(RandomAccessFile f, float[] x, int n)
+    throws IOException
+  {
+     for (int i=0; i<n; i++) x[i] = f.readFloat();
+     return n;
+  }
+
+  /** Compress a 3-D grid from floats to 1-byte unsigned integers.
+      @param nr            number of rows of grid
+      @param nc            number of columns of grid
+      @param nl            number of levels of grid
+      @param compressmode  1, 2 or 4 bytes per grid point
+      @param data          array of [nr*nc*nl] floats
+      @param compdata      array of [nr*nc*nl*compressmode] bytes for results
+      @param ga            array to put ga decompression values
+      @param gb            array to put gb decompression values
+      @param minval        one-element float array to put min value
+      @param maxval        one-element float array to put max value
+  */
+  private static void v5dCompressGrid(int nr, int nc, int nl,
+    int compressmode, float[] data, byte[] compdata1, float ga[], float gb[],
+    float[] minval, float[] maxval)
+          throws BadFormException {
+    int nrnc = nr * nc;
+    int nrncnl = nr * nc * nl;
+
+    // compute ga, gb values
+    compute_ga_gb(nr, nc, nl, data, compressmode, ga, gb, minval, maxval);
+
+    // compress the data
+    if (compressmode == 1) {
+      int i, lev, p;
+      p = 0;
+      for (lev=0; lev<nl; lev++) {
+        float one_over_a, b;
+        // subtract an epsilon so the expr below doesn't get mis-truncated
+        b = gb[lev] - 0.0001f;
+        if (ga[lev] == 0.0f) {
+          one_over_a = 1.0f;
+        }
+        else {
+          one_over_a = 1.0f / ga[lev];
+        }
+        for (i=0; i<nrnc; i++, p++) {
+          // CTR: this section is messy and untested
+          if (IS_MISSING(data[p])) compdata1[p] = -1;
+          else compdata1[p] = (byte) (int) ((data[p] - b) * one_over_a);
+        }
+      }
+    }
+
+    else if (compressmode == 2) {
+      int i, lev, p;
+      p = 0;
+      for (lev=0; lev<nl; lev++) {
+        float one_over_a, b;
+        b = gb[lev] - 0.0001f;
+        if (ga[lev] == 0.0f) {
+          one_over_a = 1.0f;
+        }
+        else {
+          one_over_a = 1.0f / ga[lev];
+        }
+        for (i=0; i<nrnc; i++, p++) {
+          // CTR: this section is messy and untested
+          if (IS_MISSING(data[p])) {
+            compdata1[2 * p] = -1;
+            compdata1[2 * p + 1] = -1;
+            // compdata2[p] = 65535;
+          }
+          else {
+            short s = (short) (int) ((data[p] - b) * one_over_a);
+            compdata1[2 * p] = (byte) (s / 256);
+            compdata1[2 * p + 1] = (byte) (s % 256);
+            // compdata2[p] = (short) (int) ((data[p] - b) * one_over_a);
+          }
+        }
+      }
+    }
+
+    else {
+      // compressmode==4
+      // other machines: just copy 4-byte IEEE floats
+      // CTR: I have no idea if this works properly in Java...
+      System.arraycopy(compdata1, 0, data, 0, nrncnl*4);
+    }
+  }
+
+  /** Write a block of memory.
+      @param f         file descriptor
+      @param data      address of first byte
+      @param elements  number of elements to write
+      @param elsize    size of each element to write (1, 2 or 4)
+      @return number of elements written
+  */
+  private static int write_block(RandomAccessFile f, byte[] data,
+    int elements, int elsize) throws IOException
+  {
+    if (elsize == 1) f.write(data, 0, elements);
+    else if (elsize == 2) f.write(data, 0, elements*2);
+    else if (elsize == 4) f.write(data, 0, elements*4);
+    else {
+      throw new IOException("Fatal error in write_block(): " +
+        "bad elsize (" + elsize +")");
+    }
+    return elements;
+  }
+
+
+  // ************************ //
+  // ****   CONSTRUCTOR  **** //
+  // ************************ //
+
+  /** Construct and initialize a V5DStruct to reasonable initial values. */
+  V5DStruct() {
+    // special cases
+    Projection = -1;
+    VerticalSystem = -1;
+
+    for (int i=0;i<MAXVARS;i++) {
+      MinVal[i] = MISSING;
+      MaxVal[i] = -MISSING;
+    }
+
+    // set file version
+    FileVersion = FILE_VERSION;
+
+    CompressMode = 1;
+    FileDesc = null;
+  }
+
+  /** Return the size (in bytes) of the 3-D grid specified by time and var.
+      @param time  timestep
+      @param vr    variable
+      @return number of data points
+  */
+  int v5dSizeofGrid(int time, int vr) {
+    return Nr * Nc * Nl[vr] * CompressMode;
+  }
+
+  /** Compute the location of a compressed grid within a file.
+      @param time  timestep
+             vr    variable
+      @return file offset in bytes
+  */
+  int grid_position(int time, int vr)
+      throws BadFormException {
+    int pos, i;
+
+    V5Dassert(time >= 0);
+    V5Dassert(vr >= 0);
+    V5Dassert(time < NumTimes);
+    V5Dassert(vr < NumVars);
+
+    pos = FirstGridPos + time * SumGridSizes;
+    for (i=0; i<vr; i++) pos += GridSize[i];
+
+    return pos;
+  }
+
+  /** Do some checking that the information in a V5DStruct is valid.
+      @return true if V5DStruct is ok, false if V5DStruct is invalid
+  */
+  boolean v5dVerifyStruct() {
+    int vr, i, maxnl;
+
+    boolean valid = true;
+
+    // Number of variables
+    if (NumVars < 0) {
+      System.err.println("Invalid number of variables: " + NumVars);
+      valid = false;
+    }
+    else if (NumVars > MAXVARS) {
+      System.err.println("Too many variables: " + NumVars +
+        "  (Maximum is " + MAXVARS + ")");
+      valid = false;
+    }
+
+    // Variable Names
+    for (i=0; i<NumVars; i++) {
+      if (VarName[i][0] == 0) {
+        System.err.println("Missing variable name: VarName[" + i + "]=\"\"");
+        valid = false;
+      }
+    }
+
+    // Number of timesteps
+    if (NumTimes < 0) {
+      System.err.println("Invalid number of timesteps: " + NumTimes);
+      valid = false;
+    }
+    else if (NumTimes>MAXTIMES) {
+      System.err.println("Too many timesteps: " + NumTimes +
+        "  (Maximum is " + MAXTIMES + ")");
+      valid = false;
+    }
+
+    // Make sure timestamps are increasing
+    for (i=1; i<NumTimes; i++) {
+      int date0 = v5dYYDDDtoDays(DateStamp[i - 1]);
+      int date1 = v5dYYDDDtoDays(DateStamp[i]);
+      int time0 = v5dHHMMSStoSeconds(TimeStamp[i - 1]);
+      int time1 = v5dHHMMSStoSeconds(TimeStamp[i]);
+      // WLH 19 Sept 2001
+      // hack dates and times if out of order, in order to accept
+      // 'invalid' files that Vis5D will accept
+      if (date1 < date0 || (date1 == date0 && time1 <= time0)) {
+        int inc = 1;
+        if (i > 1) {
+          int j = (v5dHHMMSStoSeconds(TimeStamp[i - 1]) -
+                   v5dHHMMSStoSeconds(TimeStamp[i - 2])) +
+                  86400 * (v5dYYDDDtoDays(DateStamp[i - 1]) -
+                           v5dYYDDDtoDays(DateStamp[i - 2]));
+          if (j > 0) inc = j;
+        }
+        time1 = time0 + inc;
+        date1 = date0;
+        if (time1 >= 86400) {
+          time1 = 0;
+          date1++;
+        }
+        DateStamp[i] = v5dDaysToYYDDD(date1);
+        TimeStamp[i] = v5dSecondsToHHMMSS(time1);
+      }
+    }
+
+    // Rows
+    if (Nr < 2) {
+      System.err.println("Too few rows: " + Nr + " (2 is minimum)");
+      valid = false;
+    }
+    /* Don't check on max rows in case user overrode defaults
+    else if (Nr > MAXROWS) {
+      System.err.println("Too many rows: " + Nr +
+        " (" + MAXROWS + " is maximum)");
+      valid = false;
+    }
+    */
+
+    // Columns
+    if (Nc < 2) {
+      System.err.println("Too few columns: " + Nc + " (2 is minimum)");
+      valid = false;
+    }
+    /* Don't check on max columns in case user overrode defaults
+    else if (Nc > MAXCOLUMNS) {
+      System.err.println("Too many columns: " + Nc +
+        " (" + MAXCOLUMNS + " is maximum)");
+      valid = false;
+    }
+    */
+
+    // Levels
+    maxnl = 0;
+    for (vr=0; vr<NumVars; vr++) {
+      if (LowLev[vr] < 0) {
+        System.err.println("Low level cannot be negative for var " +
+          VarName[vr] + ": " + LowLev[vr]);
+        valid = false;
+      }
+      if (Nl[vr] < 1) {
+        System.err.println("Too few levels for var " + VarName[vr] + ": " +
+          Nl[vr] + " (1 is minimum)");
+        valid = false;
+      }
+      if (Nl[vr] + LowLev[vr] > MAXLEVELS) {
+        System.err.println("Too many levels for var " + VarName[vr] + ": " +
+          (Nl[vr] + LowLev[vr]) + " (" + MAXLEVELS + " is maximum)");
+        valid = false;
+      }
+      if (Nl[vr] + LowLev[vr] > maxnl) {
+        maxnl = Nl[vr] + LowLev[vr];
+      }
+    }
+
+    if (CompressMode != 1 && CompressMode != 2 && CompressMode != 4) {
+      System.err.println("Bad CompressMode: " + CompressMode +
+        " (must be 1, 2 or 4)");
+      valid = false;
+    }
+
+    switch (VerticalSystem) {
+      case 0:
+      case 1:
+        if (VertArgs[1] == 0.0) {
+          System.err.println("Vertical level increment is zero, " +
+            "must be non-zero");
+          valid = false;
+        }
+        break;
+      case 2:
+        // Check that Height values increase upward
+        for (i=1; i<maxnl; i++) {
+          if (VertArgs[i] <= VertArgs[i - 1]) {
+            System.err.println("Height[" + i + "]=" + VertArgs[i] +
+              " <= Height[" + (i - 1) + "]=" + VertArgs[i-1] +
+              ", level heights must increase");
+            valid = false;
+            break;
+          }
+        }
+        break;
+      case 3:
+        // Check that Pressure values decrease upward
+        for (i=1; i<maxnl; i++) {
+          if (VertArgs[i] <= VertArgs[i - 1]) {
+            System.err.println("Pressure[" + i + "]=" +
+              height_to_pressure(VertArgs[i]) + " >= Pressure[" + (i-1) +
+              "]=" + height_to_pressure(VertArgs[i-1]) +
+              ", level pressures must decrease");
+            valid = false;
+            break;
+          }
+        }
+        break;
+      default:
+        System.err.println("VerticalSystem = " + VerticalSystem +
+          ", must be in 0..3");
+        valid = false;
+    }
+
+    switch (Projection) {
+      case 0:  // Generic
+        if (ProjArgs[2] == 0.0) {
+          System.err.println("Row Increment (ProjArgs[2]) can't be zero");
+          valid = false;
+        }
+        if (ProjArgs[3] == 0.0) {
+          System.err.println("Column increment (ProjArgs[3]) can't be zero");
+          valid = false;
+        }
+        break;
+      case 1:  // Cylindrical equidistant
+        if (ProjArgs[2] < 0.0) {
+          System.err.println("Row Increment (ProjArgs[2]) = " +
+            ProjArgs[2] + "  (must be >=0.0)");
+          valid = false;
+        }
+        if (ProjArgs[3] <= 0.0) {
+          System.err.println("Column Increment (ProjArgs[3]) = " +
+            ProjArgs[3] + "  (must be >=0.0)");
+          valid = false;
+        }
+        break;
+      case 2:  // Lambert Conformal
+        if (ProjArgs[0] < -90.0 || ProjArgs[0] > 90.0) {
+          System.err.println("Lat1 (ProjArgs[0]) out of range: " + ProjArgs[0]);
+          valid = false;
+        }
+        if (ProjArgs[1] < -90.0 || ProjArgs[1] > 90.0) {
+          System.err.println("Lat2 (ProjArgs[1] out of range: " + ProjArgs[1]);
+          valid = false;
+        }
+        if (ProjArgs[5] <= 0.0) {
+          System.err.println("ColInc (ProjArgs[5]) = " +
+            ProjArgs[5] + "  (must be >=0.0)");
+          valid = false;
+        }
+        break;
+      case 3:  // Stereographic
+        if (ProjArgs[0] < -90.0 || ProjArgs[0] > 90.0) {
+          System.err.println("Central Latitude (ProjArgs[0]) out of range: " +
+            ProjArgs[0] + "  (must be in +/-90)");
+          valid = false;
+        }
+        if (ProjArgs[1] < -180.0 || ProjArgs[1] > 180.0) {
+          System.err.println("Central Longitude (ProjArgs[1]) out of range: " +
+            ProjArgs[1] + "  (must be in +/-180)");
+          valid = false;
+        }
+        if (ProjArgs[4] < 0) {
+          System.err.println("Column spacing (ProjArgs[4]) = " +
+            ProjArgs[4] + "  (must be positive)");
+          valid = false;
+        }
+        break;
+      case 4:  // Rotated
+        // WLH 4-21-95
+        if (ProjArgs[2] <= 0.0) {
+          System.err.println("Row Increment (ProjArgs[2]) = " +
+            ProjArgs[2] + "  (must be >=0.0)");
+          valid = false;
+        }
+        if (ProjArgs[3] <= 0.0) {
+          System.err.println("Column Increment = (ProjArgs[3]) " +
+            ProjArgs[3] + "  (must be >=0.0)");
+          valid = false;
+        }
+        if (ProjArgs[4] < -90.0 || ProjArgs[4] > 90.0) {
+          System.err.println("Central Latitude (ProjArgs[4]) out of range: " +
+            ProjArgs[4] + "  (must be in +/-90)");
+          valid = false;
+        }
+        if (ProjArgs[5] < -180.0 || ProjArgs[5] > 180.0) {
+          System.err.println("Central Longitude (ProjArgs[5]) out of range: " +
+            ProjArgs[5] + "  (must be in +/-180)");
+          valid = false;
+        }
+        if (ProjArgs[6] < -180.0 || ProjArgs[6] > 180.0) {
+          System.err.println("Central Longitude (ProjArgs[6]) out of range: " +
+            ProjArgs[6] + "  (must be in +/-180)");
+          valid = false;
+        }
+        break;
+      default:
+        System.err.println("Projection = " + Projection + ", must be in 0..4");
+        valid = false;
+    }
+
+    return valid;
+  }
+
+  /** Get the McIDAS file number and grid number associated with the grid
+      identified by time and var.
+      @param time    timestep
+      @param vr      variable
+      @param mcfile  one-element int array for storing McIDAS grid file number
+      @param mcgrid  one-element int array for storing McIDAS grid number
+  */
+  boolean v5dGetMcIDASgrid(int time, int vr, int[] mcfile, int[] mcgrid) {
+    if (time < 0 || time >= NumTimes) {
+      System.err.println("Bad time argument to v5dGetMcIDASgrid: " + time);
+      return false;
+    }
+    if (vr < 0 || vr >= NumVars) {
+      System.err.println("Bad var argument to v5dGetMcIDASgrid: " + vr);
+      return false;
+    }
+
+    mcfile[0] = (int) McFile[time][vr];
+    mcgrid[0] = (int) McGrid[time][vr];
+    return true;
+  }
+
+  /** Set the McIDAS file number and grid number associated with the grid
+      identified by time and var.
+      @param time    timestep
+      @param vr      variable
+      @param mcfile  McIDAS grid file number
+      @param mcgrid  McIDAS grid number
+      @return true = ok, false = error (bad time or var)
+  */
+  boolean v5dSetMcIDASgrid(int time, int vr, int mcfile, int mcgrid) {
+     if (time < 0 || time >= NumTimes) {
+        System.err.println("Bad time argument to v5dSetMcIDASgrid: " + time);
+        return false;
+     }
+     if (vr < 0 || vr >= NumVars) {
+        System.err.println("Bad var argument to v5dSetMcIDASgrid: " + vr);
+        return false;
+     }
+
+     McFile[time][vr] = (short) mcfile;
+     McGrid[time][vr] = (short) mcgrid;
+     return true;
+  }
+
+
+  // ******************************************************************** //
+  // ****                    Input Functions                         **** //
+  // ******************************************************************** //
+
+  /** Read the header from a COMP* file and return results in the V5DStruct.
+      @return true = ok, false = error.
+  */
+  boolean read_comp_header() throws IOException {
+    int id;
+    RandomAccessFile f = FileDesc;
+
+    // reset file position to start of file
+    f.seek(0);
+
+    // read file ID
+    id = f.readInt();
+
+    if (id == 0x80808080 || id == 0x80808081) {
+      // Older COMP5D format
+      int gridtimes, gridparms;
+      int i, j, it, iv, nl;
+      int gridsize;
+      float hgttop, hgtinc;
+
+      if (id == 0x80808080) {
+        // 20 vars, 300 times
+        gridtimes = 300;
+        gridparms = 20;
+      }
+      else {
+        // 30 vars, 400 times
+        gridtimes = 400;
+        gridparms = 30;
+      }
+
+      FirstGridPos = 12 * 4 + 8 * gridtimes + 4 * gridparms;
+
+      NumTimes = f.readInt();
+      NumVars = f.readInt();
+      Nr = f.readInt();
+      Nc = f.readInt();
+      nl = f.readInt();
+      for (i=0; i<NumVars; i++) {
+        Nl[i] = nl;
+        LowLev[i] = 0;
+      }
+      ProjArgs[0] = f.readFloat();
+      ProjArgs[1] = f.readFloat();
+      hgttop = f.readFloat();
+      ProjArgs[2] = f.readFloat();
+      ProjArgs[3] = f.readFloat();
+      hgtinc = f.readFloat();
+      VerticalSystem = 1;
+      VertArgs[0] = hgttop - hgtinc * (nl - 1);
+      VertArgs[1] = hgtinc;
+
+      // read dates and times
+      for (i=0; i<gridtimes; i++) {
+        j = f.readInt();
+        DateStamp[i] = v5dDaysToYYDDD(j);
+      }
+      for (i=0; i<gridtimes; i++) {
+        j = f.readInt();
+        TimeStamp[i] = v5dSecondsToHHMMSS(j);
+      }
+
+      // read variable names
+      for (i=0; i<gridparms; i++) {
+        char[] name = new char[4];
+        for (int q=0; q<4; q++) name[q] = (char) f.readByte();
+
+        // remove trailing spaces, if any
+        for (j=3; j>0; j--) {
+          if (name[j] == ' ' || name[j] == 0) name[j] = 0;
+          else break;
+        }
+        System.arraycopy(name, 0, VarName[i], 0, 4);
+        VarName[i][4] = 0;
+      }
+
+      gridsize = ((Nr * Nc * nl + 3) / 4) * 4;
+      for (i=0; i<NumVars; i++) {
+        GridSize[i] = 8 + gridsize;
+      }
+      SumGridSizes = (8 + gridsize) * NumVars;
+
+      // read the grids and their ga,gb values to find min and max values
+
+      for (i=0; i<NumVars; i++) {
+        MinVal[i] = 999999.9f;
+        MaxVal[i] = -999999.9f;
+      }
+
+      for (it=0; it<NumTimes; it++) {
+        for (iv=0; iv<NumVars; iv++) {
+          float ga, gb;
+          float min, max;
+
+          ga = f.readFloat();
+          gb = f.readFloat();
+
+          // skip ahead by gridsize bytes
+          f.skipBytes(gridsize);
+          min = -(125.0f + gb) / ga;
+          max = (125.0f-gb) / ga;
+          if (min < MinVal[iv])  MinVal[iv] = min;
+          if (max > MaxVal[iv])  MaxVal[iv] = max;
+        }
+      }
+
+      // done
+    }
+    else if (id == 0x80808082 || id == 0x80808083) {
+      // Newer COMP5D format
+      int gridtimes, gridsize;
+      int it, iv, nl, i, j;
+      float delta = 0f;
+
+      gridtimes = f.readInt();
+      NumVars = f.readInt();
+      NumTimes = f.readInt();
+      Nr = f.readInt();
+      Nc = f.readInt();
+      nl = f.readInt();
+      for (i=0; i<NumVars; i++) {
+        Nl[i] = nl;
+      }
+
+      ProjArgs[2] = f.readFloat();
+      ProjArgs[3] = f.readFloat();
+
+      // Read height and determine if equal spacing
+      VerticalSystem = 1;
+      for (i=0; i<nl; i++) {
+        VertArgs[i] = f.readFloat();
+        if (i == 1) {
+          delta = VertArgs[1] - VertArgs[0];
+        }
+        else if (i > 1) {
+          if (delta != (VertArgs[i] - VertArgs[i - 1])) {
+            VerticalSystem = 2;
+          }
+        }
+      }
+      if (VerticalSystem == 1) VertArgs[1] = delta;
+
+      // read variable names
+      for (iv=0; iv<NumVars; iv++) {
+        char[] name = new char[8];
+
+        for (int q=0; q<8; q++) name[q] = (char) f.readByte();
+
+        // remove trailing spaces, if any
+        for (j=7; j>0; j--) {
+          if (name[j] == ' ' || name[j] == 0) name[j] = 0;
+          else break;
+        }
+        System.arraycopy(name, 0, VarName[iv], 0, 8);
+        VarName[iv][8] = 0;
+      }
+
+      for (iv=0; iv<NumVars; iv++) {
+        MinVal[iv] = f.readFloat();
+      }
+      for (iv=0; iv<NumVars; iv++) {
+        MaxVal[iv] = f.readFloat();
+      }
+      for (it=0; it<gridtimes; it++) {
+        j = f.readInt();
+        TimeStamp[it] = v5dSecondsToHHMMSS(j);
+      }
+      for (it=0; it<gridtimes; it++) {
+        j = f.readInt();
+        DateStamp[it] = v5dDaysToYYDDD(j);
+      }
+      for (it=0; it<gridtimes; it++) {
+        float nlat;
+        nlat = f.readFloat();
+        if (it == 0) ProjArgs[0] = nlat;
+      }
+      for (it=0; it<gridtimes; it++) {
+        float wlon;
+        wlon = f.readFloat();
+        if (it == 0) ProjArgs[1] = wlon;
+      }
+
+      // calculate grid storage sizes
+      if (id == 0x80808082) {
+        gridsize = nl * 2 * 4 + ((Nr * Nc * nl + 3) / 4) * 4;
+      }
+      else {
+        // McIDAS grid and file numbers present
+        gridsize = 8 + nl * 2 * 4 + ((Nr * Nc * nl + 3) / 4) * 4;
+      }
+      for (i=0; i<NumVars; i++) {
+        GridSize[i] = gridsize;
+      }
+      SumGridSizes = gridsize * NumVars;
+
+      // read McIDAS numbers???
+
+      // size (in bytes) of all header info
+      FirstGridPos = 9 * 4 + Nl[0] * 4 + NumVars * 16 + gridtimes * 16;
+    }
+
+    // one byte per grid point
+    CompressMode = 1;
+
+    // Cylindrical equidistant
+    Projection = 1;
+
+    FileVersion = "";
+
+    return true;
+  }
+
+  /** Read a compressed grid from a COMP* file.
+      @return true = ok, false = error.
+  */
+  boolean read_comp_grid(int time, int vr, float[] ga, float[] gb,
+    byte[] compdata1) throws IOException, BadFormException
+  {
+    long pos;
+    short bias;
+    int i, n, nl;
+    RandomAccessFile f = FileDesc;
+
+    // move to position in file
+    pos = grid_position(time, vr);
+    f.seek(pos);
+
+    if (FileFormat == 0x80808083) {
+      // read McIDAS grid and file numbers
+      int mcfile, mcgrid;
+      mcfile = f.readInt();
+      mcgrid = f.readInt();
+      McFile[time][vr] = (short) mcfile;
+      McGrid[time][vr] = (short) mcgrid;
+    }
+
+    nl = Nl[vr];
+
+    if (FileFormat == 0x80808080 || FileFormat == 0x80808081) {
+      // single ga, gb pair for whole grid
+      float a, b;
+      a = f.readFloat();
+      b = f.readFloat();
+      // convert a, b to new v5d ga, gb values
+      for (i=0; i<nl; i++) {
+        if (a == 0.0) {
+          ga[i] = gb[i] = 0.0f;
+        }
+        else {
+          gb[i] = (b + 128.0f) / -a;
+          ga[i] = 1.0f / a;
+        }
+      }
+      bias = 128;
+    }
+    else {
+      // read ga, gb arrays
+      read_float4_array(f, ga, Nl[vr]);
+      read_float4_array(f, gb, Nl[vr]);
+
+      // convert ga, gb values to v5d system
+      for (i=0; i<nl; i++) {
+        if (ga[i] == 0.0) {
+          ga[i] = gb[i] = 0.0f;
+        }
+        else {
+          // gb[i] = (gb[i]+125.0) / -ga[i];
+          gb[i] = (gb[i] + 128.0f) / -ga[i];
+          ga[i] = 1.0f / ga[i];
+        }
+      }
+      bias = 128;  // 125 ???
+    }
+
+    // read compressed grid data
+    n = Nr * Nc * Nl[vr];
+    if (f.read(compdata1, 0, n) != n) return false;
+
+    // convert data values to v5d system
+    n = Nr * Nc * Nl[vr];
+    for (i=0; i<n; i++) compdata1[i] += bias;
+
+    return true;
+  }
+
+  /** Read a v5d file header.
+      @return true = ok, false = error.
+  */
+  boolean read_v5d_header() throws IOException, BadFormException {
+    boolean end_of_header = false;
+    int id;
+    int idlen, vr, numargs;
+    RandomAccessFile f;
+
+    f = FileDesc;
+
+    // first try to read the header id
+    id = f.readInt();
+    idlen = f.readInt();
+    if (id == TAG_ID && idlen == 0) {
+      // this is a v5d file
+      FileFormat = 0;
+    }
+    else if (id >= 0x80808080 && id <= 0x80808083) {
+      // this is an old COMP* file
+      FileFormat = id;
+      return read_comp_header();
+    }
+    else {
+      // unknown file type
+      return false;
+    }
+
+    // default
+    CompressMode = 1;
+
+    while (!end_of_header) {
+      int tag, length;
+      int i, time, nl, lev;
+
+      tag = f.readInt();
+      length = f.readInt();
+
+      switch (tag) {
+        case TAG_VERSION:
+          V5Dassert(length == 10);
+          byte[] b = new byte[10];
+          f.read(b, 0, 10);
+          int index = 10;
+          for (int q=0; q<10; q++) {
+            if (b[q] == 0) {
+              index = q;
+              break;
+            }
+          }
+          FileVersion = new String(b, 0, index);
+          // Check if reading a file made by a future version of Vis5D
+          if (FileVersion.compareTo(FILE_VERSION) > 0) {
+            System.err.println("Warning: Trying to read a version " +
+              FileVersion + " file, you should upgrade Vis5D.");
+          }
+          break;
+        case TAG_NUMTIMES:
+          V5Dassert(length == 4);
+          NumTimes = f.readInt();
+          break;
+        case TAG_NUMVARS:
+          V5Dassert(length == 4);
+          NumVars = f.readInt();
+          break;
+        case TAG_VARNAME:
+          // 1 int + 10 char
+          V5Dassert(length == 14);
+          vr = f.readInt();
+          for (int q=0; q<10; q++) VarName[vr][q] = (char) f.readByte();
+          break;
+        case TAG_NR:
+          // Number of rows for all variables
+          V5Dassert(length == 4);
+          Nr = f.readInt();
+          break;
+        case TAG_NC:
+          // Number of columns for all variables
+          V5Dassert(length == 4);
+          Nc = f.readInt();
+          break;
+        case TAG_NL:
+          // Number of levels for all variables
+          V5Dassert(length == 4);
+          nl = f.readInt();
+          for (i=0; i<NumVars; i++) {
+            Nl[i] = nl;
+          }
+          break;
+        case TAG_NL_VAR:
+          // Number of levels for one variable
+          V5Dassert(length == 8);
+          vr = f.readInt();
+          Nl[vr] = f.readInt();
+          break;
+        case TAG_LOWLEV_VAR:
+          // Lowest level for one variable
+          V5Dassert(length == 8);
+          vr = f.readInt();
+          LowLev[vr] = f.readInt();
+          break;
+
+        case TAG_TIME:
+          // Time stamp for 1 timestep
+          V5Dassert(length == 8);
+          time = f.readInt();
+          TimeStamp[time] = f.readInt();
+          break;
+        case TAG_DATE:
+          // Date stamp for 1 timestep
+          V5Dassert(length == 8);
+          time = f.readInt();
+          DateStamp[time] = f.readInt();
+          break;
+
+        case TAG_MINVAL:
+          // Minimum value for a variable
+          V5Dassert(length == 8);
+          vr = f.readInt();
+          MinVal[vr] = f.readFloat();
+          break;
+        case TAG_MAXVAL:
+          // Maximum value for a variable
+          V5Dassert(length == 8);
+          vr = f.readInt();
+          MaxVal[vr] = f.readFloat();
+          break;
+        case TAG_COMPRESS:
+          // Compress mode
+          V5Dassert(length == 4);
+          CompressMode = f.readInt();
+          break;
+        case TAG_UNITS:
+          // physical units
+          V5Dassert(length == 24);
+          vr = f.readInt();
+          for (int q=0; q<20; q++) Units[vr][q] = (char) f.readByte();
+          break;
+
+        // Vertical coordinate system
+        case TAG_VERTICAL_SYSTEM:
+          V5Dassert(length == 4);
+          VerticalSystem = f.readInt();
+          if (VerticalSystem < 0 || VerticalSystem > 3) {
+            System.err.println("Error: bad vertical coordinate system: " +
+              VerticalSystem);
+          }
+          break;
+        case TAG_VERT_ARGS:
+          numargs = f.readInt();
+          V5Dassert(numargs <= MAXVERTARGS);
+          for (int q=0; q<numargs; q++) VertArgs[q] = f.readFloat();
+          V5Dassert(length == numargs * 4 + 4);
+          break;
+        case TAG_HEIGHT:
+          // height of a grid level
+          V5Dassert(length == 8);
+          lev = f.readInt();
+          VertArgs[lev] = f.readFloat();
+          break;
+        case TAG_BOTTOMBOUND:
+          V5Dassert(length == 4);
+          VertArgs[0] = f.readFloat();
+          break;
+        case TAG_LEVINC:
+          V5Dassert(length == 4);
+          VertArgs[1] = f.readFloat();
+          break;
+
+        // Map projection information
+        case TAG_PROJECTION:
+          V5Dassert(length == 4);
+          Projection = f.readInt();
+          // WLH 4-21-95
+          if (Projection < 0 || Projection > 4) {
+            System.err.println("Error while reading header, bad projection (" +
+              Projection + ")");
+            return false;
+          }
+          break;
+        case TAG_PROJ_ARGS:
+          numargs = f.readInt();
+          V5Dassert(numargs <= MAXPROJARGS);
+          for (int q=0; q<numargs; q++) ProjArgs[q] = f.readFloat();
+          V5Dassert(length == 4 * numargs + 4);
+          break;
+        case TAG_NORTHBOUND:
+          V5Dassert(length == 4);
+          if (Projection == 0 || Projection == 1 || Projection == 4) {
+            ProjArgs[0] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_WESTBOUND:
+          V5Dassert(length == 4);
+          if (Projection == 0 || Projection == 1 || Projection == 4) {
+            ProjArgs[1] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_ROWINC:
+          V5Dassert(length == 4);
+          if (Projection == 0 || Projection == 1 || Projection == 4) {
+            ProjArgs[2] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_COLINC:
+          V5Dassert(length == 4);
+          if (Projection == 0 || Projection == 1 || Projection == 4) {
+            ProjArgs[3] = f.readFloat();
+          }
+          else if (Projection == 2) {
+            ProjArgs[5] = f.readFloat();
+          }
+          else if (Projection == 3) {
+            ProjArgs[4] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_LAT1:
+          V5Dassert(length == 4);
+          if (Projection == 2) {
+            ProjArgs[0] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_LAT2:
+          V5Dassert(length == 4);
+          if (Projection == 2) {
+            ProjArgs[1] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_POLE_ROW:
+          V5Dassert(length == 4);
+          if (Projection == 2) {
+            ProjArgs[2] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_POLE_COL:
+          V5Dassert(length == 4);
+          if (Projection == 2) {
+            ProjArgs[3] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_CENTLON:
+          V5Dassert(length == 4);
+          if (Projection == 2) {
+            ProjArgs[4] = f.readFloat();
+          }
+          else if (Projection == 3) {
+            ProjArgs[1] = f.readFloat();
+          }
+          // WLH 4-21-95
+          else if (Projection == 4) {
+            ProjArgs[5] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_CENTLAT:
+          V5Dassert(length == 4);
+          if (Projection == 3) {
+            ProjArgs[0] = f.readFloat();
+          }
+          // WLH 4-21-95
+          else if (Projection == 4) {
+            ProjArgs[4] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_CENTROW:
+          V5Dassert(length == 4);
+          if (Projection == 3) {
+            ProjArgs[2] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_CENTCOL:
+          V5Dassert(length == 4);
+          if (Projection == 3) {
+            ProjArgs[3] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+        case TAG_ROTATION:
+          V5Dassert(length == 4);
+          // WLH 4-21-95
+          if (Projection == 4) {
+            ProjArgs[6] = f.readFloat();
+          }
+          else {
+            f.skipBytes(4);
+          }
+          break;
+
+        case TAG_END:
+          // end of header
+          end_of_header = true;
+          f.skipBytes(length);
+          break;
+
+        default:
+          // unknown tag, skip to next tag
+          System.err.println("Unknown tag: " + tag + "  length=" + length);
+          f.skipBytes(length);
+          break;
+      }
+
+    }
+
+    v5dVerifyStruct();
+
+    // Now we're ready to read the grid data
+
+    // Save current file pointer
+    FirstGridPos = (int) f.getFilePointer();
+
+    // compute grid sizes
+    SumGridSizes = 0;
+    for (vr=0;vr<NumVars;vr++) {
+      GridSize[vr] = 8 * Nl[vr] + v5dSizeofGrid(0, vr);
+      SumGridSizes += GridSize[vr];
+    }
+
+    return true;
+  }
+
+  /** Read a compressed grid from a v5d file.
+      @param time      timestep
+      @param vr        variable
+      @param ga        array to store grid (de)compression values
+      @param gb        array to store grid (de)compression values
+      @param compdata  address of where to store compressed grid data
+      @return true = ok, false = error
+  */
+  boolean v5dReadCompressedGrid(int time, int vr, float[] ga, float[] gb,
+    byte[] compdata) throws IOException, BadFormException
+  {
+    int pos, n;
+    boolean k = false;
+
+    if (time < 0 || time >= NumTimes) {
+      throw new IOException("Error in v5dReadCompressedGrid: " +
+        "bad timestep argument (" + time + ")");
+    }
+    if (vr < 0 || vr >= NumVars) {
+      throw new IOException("Error in v5dReadCompressedGrid: " +
+        "bad var argument (" + vr + ")");
+    }
+
+    if (FileFormat != 0) {
+      // old COMP* file
+      return read_comp_grid(time, vr, ga, gb, compdata);
+    }
+
+    // move to position in file
+    pos = grid_position(time, vr);
+    FileDesc.seek(pos);
+
+    // read ga, gb arrays
+    read_float4_array(FileDesc, ga, Nl[vr]);
+    read_float4_array(FileDesc, gb, Nl[vr]);
+
+    // read compressed grid data
+    n = Nr * Nc * Nl[vr];
+    if (CompressMode == 1) {
+      k = read_block(FileDesc, compdata, n, 1) == n;
+    }
+    else if (CompressMode == 2) {
+      k = read_block(FileDesc, compdata, n, 2) == n;
+    }
+    else if (CompressMode == 4) {
+      k = read_block(FileDesc, compdata, n, 4) == n;
+    }
+    if (!k) {
+      // error
+      System.err.println("Error in v5dReadCompressedGrid: " +
+        "read failed, bad file?");
+    }
+
+    // n = Nr * Nc * Nl[vr] * CompressMode;
+    // if (FileDesc.read(compdata, 0, n) != n)
+    //   throw new IOException("Error in v5dReadCompressedGrid: read failed");
+
+    return k;
+  }
+
+
+  /** Read a grid from a v5d file, decompress it and return it.
+      @param time  timestep
+      @param vr    variable
+      @param data  array to put grid data
+      @return true = ok, false = error.
+  */
+  boolean v5dReadGrid(int time, int vr, float[] data)
+          throws IOException, BadFormException {
+    float[] ga = new float[MAXLEVELS];
+    float[] gb = new float[MAXLEVELS];
+    byte[] compdata;
+    int bytes;
+
+    if (time < 0 || time >= NumTimes) {
+      System.err.println("Error in v5dReadGrid: " +
+        "bad timestep argument (" + time + ")");
+      return false;
+    }
+    if (vr < 0 || vr >= NumVars) {
+      System.err.println("Error in v5dReadGrid: " +
+        "bad variable argument (" + vr + ")");
+      return false;
+    }
+
+    // allocate compdata buffer
+    if (CompressMode == 1) {
+      /*-TDR, bug? factor should be 1 
+      bytes = Nr * Nc * Nl[vr] * 2; // sizeof(unsigned char);
+       */
+      bytes = Nr * Nc * Nl[vr] * 1; // sizeof(unsigned char);
+    }
+    else if (CompressMode == 2) {
+      bytes = Nr * Nc * Nl[vr] * 2; // sizeof(unsigned short);
+    }
+    else if (CompressMode == 4) {
+      bytes = Nr * Nc * Nl[vr] * 4; // sizeof(float);
+    }
+    else {
+      System.err.println("Error in v5dReadGrid: " +
+        "bad compression mode (" + CompressMode + ")");
+      return false;
+    }
+    compdata = new byte[bytes];
+
+    // read the compressed data
+    if (!v5dReadCompressedGrid(time, vr, ga, gb, compdata)) return false;
+
+    // decompress the data
+    v5dDecompressGrid(Nr, Nc, Nl[vr], CompressMode, compdata, ga, gb, data);
+
+    return true;
+  }
+
+
+  // ******************************************************************** //
+  // ****                   Output Functions                         **** //
+  // ******************************************************************** //
+
+  boolean write_tag(int tag, int length, boolean newfile) throws IOException {
+    if (!newfile) {
+      // have to check that there's room in header to write this tagged item
+      if (CurPos+8+length > FirstGridPos) {
+        System.err.println("Error: out of header space!");
+        // Out of header space!
+        return false;
+      }
+    }
+
+    FileDesc.writeInt(tag);
+    FileDesc.writeInt(length);
+    CurPos += 8 + length;
+    return true;
+  }
+
+  /** Write the information in the given V5DStruct as a v5d file header.
+      Note that the current file position is restored when this function
+      returns normally.
+      @return true = ok, false = error.
+  */
+  boolean write_v5d_header() throws IOException {
+    int vr, time, filler, maxnl;
+    RandomAccessFile f;
+    boolean newfile;
+
+    if (FileFormat != 0) {
+      System.err.println("Error: " +
+        "v5d library can't write comp5d format files.");
+      return false;
+    }
+
+    f = FileDesc;
+
+    if (!v5dVerifyStruct()) return false;
+
+    // Determine if we're writing to a new file
+    newfile = (FirstGridPos == 0);
+
+    // compute grid sizes
+    SumGridSizes = 0;
+    for (vr=0; vr<NumVars; vr++) {
+      GridSize[vr] = 8 * Nl[vr] + v5dSizeofGrid(0, vr);
+      SumGridSizes += GridSize[vr];
+    }
+
+    // set file pointer to start of file
+    f.seek(0);
+    CurPos = 0;
+
+    // Write the tagged header info
+
+    // ID
+    if (!write_tag(TAG_ID, 0, newfile)) return false;
+
+    // File Version
+    if (!write_tag(TAG_VERSION, 10, newfile)) return false;
+    f.write(FILE_VERSION.getBytes(), 0, 10);
+
+    // Number of timesteps
+    if (!write_tag(TAG_NUMTIMES, 4, newfile)) return false;
+    f.writeInt(NumTimes);
+
+    // Number of variables
+    if (!write_tag(TAG_NUMVARS, 4, newfile)) return false;
+    f.writeInt(NumVars);
+
+    // Names of variables
+    for (vr=0; vr<NumVars; vr++) {
+      if (!write_tag(TAG_VARNAME, 14, newfile)) return false;
+      f.writeInt(vr);
+      for (int q=0; q<10; q++) f.writeByte((byte) VarName[vr][q]);
+    }
+
+    // Physical Units
+    for (vr=0; vr<NumVars; vr++) {
+      if (!write_tag(TAG_UNITS, 24, newfile)) return false;
+      f.writeInt(vr);
+      for (int q=0; q<20; q++) f.writeByte((byte) Units[vr][q]);
+    }
+
+    // Date and time of each timestep
+    for (time=0; time<NumTimes; time++) {
+      if (!write_tag(TAG_TIME, 8, newfile)) return false;
+      f.writeInt(time);
+      f.writeInt(TimeStamp[time]);
+      if (!write_tag(TAG_DATE, 8, newfile)) return false;
+      f.writeInt(time);
+      f.writeInt(DateStamp[time]);
+    }
+
+    // Number of rows
+    if (!write_tag(TAG_NR, 4, newfile)) return false;
+    f.writeInt(Nr);
+
+    // Number of columns
+    if (!write_tag(TAG_NC, 4, newfile)) return false;
+    f.writeInt(Nc);
+
+    // Number of levels, compute maxnl
+    maxnl = 0;
+    for (vr=0; vr<NumVars; vr++) {
+      if (!write_tag(TAG_NL_VAR, 8, newfile)) return false;
+      f.writeInt(vr);
+      f.writeInt(Nl[vr]);
+      if (!write_tag(TAG_LOWLEV_VAR, 8, newfile)) return false;
+      f.writeInt(vr);
+      f.writeInt(LowLev[vr]);
+      if (Nl[vr] + LowLev[vr] > maxnl) maxnl = Nl[vr]+LowLev[vr];
+    }
+
+    // Min/Max values
+    for (vr=0; vr<NumVars; vr++) {
+      if (!write_tag(TAG_MINVAL, 8, newfile)) return false;
+      f.writeInt(vr);
+      f.writeFloat(MinVal[vr]);
+      if (!write_tag(TAG_MAXVAL, 8, newfile)) return false;
+      f.writeInt(vr);
+      f.writeFloat(MaxVal[vr]);
+    }
+
+    // Compress mode
+    if (!write_tag(TAG_COMPRESS, 4, newfile)) return false;
+    f.writeInt(CompressMode);
+
+    // Vertical Coordinate System
+    if (!write_tag(TAG_VERTICAL_SYSTEM, 4, newfile)) return false;
+    f.writeInt(VerticalSystem);
+    if (!write_tag(TAG_VERT_ARGS, 4+4*MAXVERTARGS, newfile)) return false;
+    f.writeInt(MAXVERTARGS);
+    for (int q=0; q<MAXVERTARGS; q++) f.writeFloat(VertArgs[q]);
+
+    // Map Projection
+    if (!write_tag(TAG_PROJECTION, 4, newfile)) return false;
+    f.writeInt(Projection);
+    if (!write_tag(TAG_PROJ_ARGS, 4+4*MAXPROJARGS, newfile)) return false;
+    f.writeInt(MAXPROJARGS);
+    for (int q=0; q<MAXPROJARGS; q++) f.writeFloat(ProjArgs[q]);
+
+    // write END tag
+    if (newfile) {
+      // We're writing to a brand new file.
+      // Reserve 10000 bytes for future header growth.
+      if (!write_tag(TAG_END, 10000, newfile)) return false;
+      f.skipBytes(10000);
+
+      // Let file pointer indicate where first grid is stored
+      FirstGridPos = (int) f.getFilePointer();
+    }
+    else {
+      // we're rewriting a header
+      filler = FirstGridPos - (int) f.getFilePointer();
+      if (!write_tag(TAG_END, filler - 8, newfile)) return false;
+    }
+
+    return true;
+  }
+
+  /** Open a v5d file for writing.  If the named file already exists,
+      it will be deleted.
+      @param filename  name of v5d file to create
+      @return true = ok, false = error.
+  */
+  boolean v5dCreateFile(String filename) throws IOException {
+    RandomAccessFile fd = new RandomAccessFile(filename, "rw");
+
+    if (fd == null) {
+      System.err.println("Error in v5dCreateFile: open failed");
+      FileDesc = null;
+      Mode = 0;
+      return false;
+    }
+    else {
+      // ok
+      FileDesc = fd;
+      Mode = 'w';
+      // write header and return status
+      return write_v5d_header();
+    }
+  }
+
+  /** Write a compressed grid to a v5d file.
+      @param time      timestep
+      @param vr        variable
+      @param ga        the GA (de)compression value array
+      @param gb        the GB (de)compression value array
+      @param compdata  array of compressed data values
+      @return true = ok, false = error
+  */
+  boolean v5dWriteCompressedGrid(int time, int vr, float[] ga, float[] gb,
+    byte[] compdata) throws IOException, BadFormException
+  {
+    int pos, n;
+    boolean k;
+
+    // simple error checks
+    if (Mode != 'w') {
+      System.err.println("Error in v5dWriteCompressedGrid: " +
+        "file opened for reading, not writing.");
+      return false;
+    }
+    if (time < 0 || time >= NumTimes) {
+      System.err.println("Error in v5dWriteCompressedGrid: " +
+        "bad timestep argument (" + time + ")");
+      return false;
+    }
+    if (vr < 0 || vr >= NumVars) {
+      System.err.println("Error in v5dWriteCompressedGrid: " +
+        "bad variable argument (" + vr + ")");
+      return false;
+    }
+
+    // move to position in file
+    pos = grid_position(time, vr);
+    FileDesc.seek(pos);
+
+    // write ga, gb arrays
+    k = false;
+    for (int q=0; q<Nl[vr]; q++) FileDesc.writeFloat(ga[q]);
+    for (int q=0; q<Nl[vr]; q++) FileDesc.writeFloat(gb[q]);
+
+    // write compressed grid data (k=true=OK, k=false=Error)
+    n = Nr * Nc * Nl[vr];
+    if (CompressMode == 1) {
+      k = write_block(FileDesc, compdata, n, 1) == n;
+    }
+    else if (CompressMode == 2) {
+      k = write_block(FileDesc, compdata, n, 2) == n;
+    }
+    else if (CompressMode == 4) {
+      k = write_block(FileDesc, compdata, n, 4) == n;
+    }
+
+    if (!k) {
+      // Error while writing
+      System.err.println("Error in v5dWrite[Compressed]Grid: " +
+        "write failed, disk full?");
+    }
+    return k;
+
+    // n = Nr * Nc * Nl[vr] * CompressMode;
+    // if (write_bytes(FileDesc, compdata, n) != n) {
+    //   System.err.println("Error in v5dWrite[Compressed]Grid: " +
+    //     "write failed, disk full?");
+    //   return false;
+    // }
+    // else return true;
+  }
+
+  /** Compress a grid and write it to a v5d file.
+      @param time  timestep
+      @param vr    variable
+      @param data  array of uncompressed grid data
+      @return true = ok, false = error
+  */
+  boolean v5dWriteGrid(int time, int vr, float[] data)
+          throws IOException, BadFormException {
+    float[] ga = new float[MAXLEVELS];
+    float[] gb = new float[MAXLEVELS];
+    byte[] compdata;
+    int n, bytes;
+    float min, max;
+
+    if (Mode != 'w') {
+      System.err.println("Error in v5dWriteGrid: " +
+       "file opened for reading, not writing.");
+      return false;
+    }
+    if (time < 0 || time >= NumTimes) {
+      System.err.println("Error in v5dWriteGrid: " +
+       "bad timestep argument (" + time + ")");
+      return false;
+    }
+    if (vr < 0 || vr >= NumVars) {
+      System.err.println("Error in v5dWriteGrid: " +
+       "bad variable argument (" + vr + ")");
+      return false;
+    }
+
+    // allocate compdata buffer
+    if (CompressMode == 1) {
+      bytes = Nr * Nc * Nl[vr] * 2; // sizeof(unsigned char);
+    }
+    else if (CompressMode == 2) {
+      bytes = Nr * Nc * Nl[vr] * 2; // sizeof(unsigned short);
+    }
+    else if (CompressMode == 4) {
+      bytes = Nr * Nc * Nl[vr] * 4; // sizeof(float);
+    }
+    else {
+      System.err.println("Error in v5dWriteGrid: " +
+       "bad compression mode (" + CompressMode + ")");
+      return false;
+    }
+    compdata = new byte[bytes];
+
+    // compress the grid data
+    float[] min1 = new float[1];
+    float[] max1 = new float[1];
+    v5dCompressGrid(Nr, Nc, Nl[vr], CompressMode, data, compdata,
+     ga, gb, min1, max1);
+    min = min1[0];
+    max = max1[0];
+
+    // update min and max value
+    if (min < MinVal[vr]) MinVal[vr] = min;
+    if (max > MaxVal[vr]) MaxVal[vr] = max;
+
+    // write the compressed grid
+    return v5dWriteCompressedGrid(time, vr, ga, gb, compdata);
+  }
+
+  /** Close a v5d file which was opened with open_v5d_file() or
+      create_v5d_file().
+      @return true = ok, false = error
+  */
+  boolean v5dCloseFile() throws IOException {
+    boolean status = true;
+
+    if (Mode == 'w') {
+      // rewrite header because writing grids updates minval and maxval fields
+      FileDesc.seek(0);
+      status = write_v5d_header();
+      // CTR: is this seek necessary?
+      FileDesc.seek(FileDesc.length());
+      FileDesc.close();
+    }
+    else if (Mode == 'r') {
+      // just close the file
+      FileDesc.close();
+    }
+    else {
+      System.err.println("Error in v5dCloseFile: bad V5DStruct argument");
+      return false;
+    }
+    FileDesc = null;
+    Mode = 0;
+    return status;
+  }
+
+}
+
diff --git a/visad/data/vis5d/Vis5DAdaptedForm.java b/visad/data/vis5d/Vis5DAdaptedForm.java
new file mode 100644
index 0000000..9e9f059
--- /dev/null
+++ b/visad/data/vis5d/Vis5DAdaptedForm.java
@@ -0,0 +1,47 @@
+//
+// Vis5DAdaptedForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.FlatField;
+import visad.VisADException;
+import visad.data.*;
+
+public class Vis5DAdaptedForm extends Vis5DForm
+{
+  static CacheStrategy c_strategy = new CacheStrategy();
+
+  public Vis5DAdaptedForm()
+  {
+  }
+
+  public FlatField getFlatField(Vis5DFile file, int time_idx)
+         throws VisADException
+  {
+    Vis5DFileAccessor v5dfa = new Vis5DFileAccessor(file, time_idx);
+    return new FileFlatField(v5dfa, c_strategy);
+  }
+}
diff --git a/visad/data/vis5d/Vis5DCoordinateSystem.java b/visad/data/vis5d/Vis5DCoordinateSystem.java
new file mode 100644
index 0000000..91b547a
--- /dev/null
+++ b/visad/data/vis5d/Vis5DCoordinateSystem.java
@@ -0,0 +1,560 @@
+//
+// Vis5DCooridinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.*;
+import visad.georef.MapProjection;
+import java.awt.geom.Rectangle2D;
+
+/**
+   Vis5DCoordinateSystem is the VisAD class for coordinate
+   systems for ( row, col ).<P>
+*/
+
+public class Vis5DCoordinateSystem extends MapProjection
+{
+  private static final int  PROJ_GENERIC      =   0;
+  private static final int  PROJ_LINEAR       =   1;
+  private static final int  PROJ_CYLINDRICAL  =  20;
+  private static final int  PROJ_SPHERICAL    =  21;
+  private static final int  PROJ_LAMBERT      =   2;
+  private static final int  PROJ_STEREO       =   3;
+  private static final int  PROJ_ROTATED      =   4;
+
+  private static final double  RADIUS = 6371.23;  /* KM */
+
+  /*- negate Vis5D/McIDAS longitude west positive
+      convention */
+  private static final double WEST_POSITIVE = -1.0;
+
+  private int    Projection;
+  int     REVERSE_POLES = 1;
+  double  NorthBound;
+  double  SouthBound;
+  double  WestBound;
+  double  EastBound;
+  double  RowInc;
+  double  ColInc;
+  double  Lat1;
+  double  Lat2;
+  double  PoleRow;
+  double  PoleCol;
+  double  CentralLat;
+  double  CentralLon;
+  double  CentralRow;
+  double  CentralCol;
+  double  Rotation;             /* radians */
+  double  Cone;
+  double  Hemisphere;
+  double  ConeFactor;
+  double  CosCentralLat;
+  double  SinCentralLat;
+  double  StereoScale;
+  double  InvScale;
+  double  CylinderScale;
+  double  Nr;
+  double  Nc;
+  double[] projargs;
+
+  private static Unit[] coordinate_system_units =
+    {null, null};
+
+  public Vis5DCoordinateSystem(int Projection,
+                               double[] projargs,
+                               double Nr,
+                               double Nc)
+       throws VisADException
+  {
+    super( RealTupleType.LatitudeLongitudeTuple, coordinate_system_units );
+
+    this.Projection = Projection;
+    this.Nr = Nr;
+    this.Nc = Nc;
+    this.projargs = projargs;
+    switch ( Projection )
+    {
+      case PROJ_GENERIC:
+      case PROJ_LINEAR:
+      case PROJ_CYLINDRICAL:
+      case PROJ_SPHERICAL:
+         NorthBound = projargs[0];
+         WestBound  = projargs[1];
+         RowInc     = projargs[2];
+         ColInc     = projargs[3];
+         break;
+      case PROJ_ROTATED:
+         NorthBound = projargs[0];
+         WestBound  = projargs[1];
+         RowInc     = projargs[2];
+         ColInc     = projargs[3];
+         CentralLat = Data.DEGREES_TO_RADIANS * projargs[4];
+         CentralLon = Data.DEGREES_TO_RADIANS * projargs[5];
+         Rotation   = Data.DEGREES_TO_RADIANS * projargs[6];
+         break;
+      case PROJ_LAMBERT:
+         Lat1       = projargs[0];
+         Lat2       = projargs[1];
+         PoleRow    = projargs[2];
+         PoleCol    = projargs[3];
+         CentralLon = projargs[4];
+         ColInc     = projargs[5];
+         break;
+      case PROJ_STEREO:
+         CentralLat = projargs[0];
+         CentralLon = projargs[1];
+         CentralRow = projargs[2];
+         CentralCol = projargs[3];
+         ColInc     = projargs[4];
+         break;
+      default:
+         throw new VisADException("Projection unknown");
+    }
+
+   /*
+    * Precompute useful values for coordinate transformations.
+    */
+   switch (Projection) {
+      case PROJ_GENERIC:
+      case PROJ_LINEAR:
+         SouthBound = NorthBound - RowInc * (Nr-1);
+         EastBound = WestBound - ColInc * (Nc-1);
+
+         break;
+      case PROJ_LAMBERT:
+         double lat1, lat2;
+         if (Lat1==Lat2) {
+            /* polar stereographic? */
+            if (Lat1>0.0) {
+               lat1 = (90.0 - Lat1) * Data.DEGREES_TO_RADIANS;
+            }
+            else {
+               lat1 = (90.0 + Lat1) * Data.DEGREES_TO_RADIANS;
+            }
+            Cone = Math.cos( lat1 );
+            Hemisphere = 1.0;
+
+         }
+         else {
+            /* general Lambert conformal */
+            double a, b;
+            if (sign(Lat1) != sign(Lat2)) {
+              throw 
+                new 
+                  VisADException("Error: standard latitudes must have the same sign.\n");
+            }
+            if (Lat1<Lat2) {
+              throw 
+                new
+                  VisADException("Error: Lat1 must be >= Lat2\n");
+            }
+            Hemisphere = 1.0;
+
+            lat1 = (90.0 - Lat1) * Data.DEGREES_TO_RADIANS;
+            lat2 = (90.0 - Lat2) * Data.DEGREES_TO_RADIANS;
+            a = Math.log(Math.sin(lat1)) - Math.log(Math.sin(lat2));
+            b = Math.log( Math.tan(lat1/2.0) ) - Math.log( Math.tan(lat2/2.0) );
+            Cone = a / b;
+
+         }
+         /* Cone is in [-1,1] */
+         ConeFactor = RADIUS * Math.sin(lat1)
+                          / (ColInc * Cone
+                             * Math.pow(Math.tan(lat1/2.0), Cone) );
+
+         break;
+      case PROJ_STEREO:
+         CosCentralLat = Math.cos( CentralLat * Data.DEGREES_TO_RADIANS );
+         SinCentralLat = Math.sin( CentralLat * Data.DEGREES_TO_RADIANS );
+         StereoScale = (2.0 * RADIUS / ColInc);
+         InvScale = 1.0 / StereoScale;
+
+         break;
+      case PROJ_ROTATED:
+         SouthBound = NorthBound - RowInc * (Nr-1);
+         EastBound = WestBound - ColInc * (Nc-1);
+
+         break;
+      case PROJ_CYLINDRICAL:
+         if (REVERSE_POLES==-1){
+            CylinderScale = 1.0 / (-1.0*(-90.0-NorthBound));
+         }
+         else{
+            CylinderScale = 1.0 / (90.0-SouthBound);
+         }
+         SouthBound = NorthBound - RowInc * (Nr-1);
+         EastBound = WestBound - ColInc * (Nc-1);
+
+         break;
+      case PROJ_SPHERICAL:
+         SouthBound = NorthBound - RowInc * (Nr-1);
+         EastBound = WestBound - ColInc * (Nc-1);
+
+         break;
+   }
+
+   if (Projection != PROJ_GENERIC) {
+     if (SouthBound < -90.0) {
+       throw new VisADException("SouthBound less than -90.0");
+     }
+     if (NorthBound < SouthBound) {
+       throw new VisADException("NorthBound less than SouthBound");
+     }
+     if (90.0 < NorthBound) {
+       throw new VisADException("NorthBound greater than 90.0");
+     }
+   }
+  }
+
+  /**
+   * Get the bounds for this image
+   */
+  public Rectangle2D getDefaultMapArea() 
+  { 
+      return new Rectangle2D.Double(0, 0, Nc, Nr);
+  }
+
+  /**
+   * Get the Projection type
+   */
+  public int getProjection() { return Projection; }
+
+  /**
+   * Get the number of Rows
+   */
+  public double getRows() { return Nr; }
+
+  /**
+   * Get the number of Columns
+   */
+  public double getColumns() { return Nc; }
+
+  /**
+   * Get the projection args
+   */
+  public double[] getProjectionParams() { return projargs; }
+
+  /**
+   * Override from super class since toRef and fromRef use rowcol (yx) order
+   * instead of colrow (xy) order.
+   * @return false
+   */
+  public boolean isXYOrder() { return false; }
+
+  public double[][] toReference(double[][] rowcol)
+         throws VisADException
+  {
+    int length = rowcol[0].length;
+    double[][] latlon = new double[2][length];
+
+    switch (Projection) {
+      case PROJ_GENERIC:
+      case PROJ_LINEAR:
+      case PROJ_CYLINDRICAL:
+      case PROJ_SPHERICAL:
+        for (int kk = 0; kk < length; kk++) {
+
+       //-latlon[0][kk] = NorthBound - rowcol[0][kk] * (NorthBound-SouthBound)
+          latlon[0][kk] = NorthBound - (Nr-1-rowcol[0][kk]) * (NorthBound-SouthBound)
+                    / (double) (Nr-1);
+          latlon[1][kk] = WestBound - rowcol[1][kk] * (WestBound-EastBound)
+                    / (double) (Nc-1);
+          latlon[1][kk] *= WEST_POSITIVE;
+        }
+        break;
+      case PROJ_LAMBERT:
+         {
+           double xldif, xedif, xrlon, radius, lon, lat;
+           for (int kk = 0; kk < length; kk++) {
+
+          //-xldif = Hemisphere * (rowcol[0][kk]-PoleRow) / ConeFactor;
+             xldif = Hemisphere * ((Nr-1-rowcol[0][kk])-PoleRow) / ConeFactor;
+             xedif = (PoleCol-rowcol[1][kk]) / ConeFactor;
+             if (xldif==0.0 && xedif==0.0)
+               xrlon = 0.0;
+             else
+               xrlon = Math.atan2( xedif, xldif );
+             lon = xrlon / Cone * Data.RADIANS_TO_DEGREES + CentralLon;
+             if (lon > 180.0)
+                lon -= 360.0;
+
+             radius = Math.sqrt( xldif*xldif + xedif*xedif );
+             if (radius < 0.0001)
+               lat = 90.0 * Hemisphere;   /* +/-90 */
+             else
+               lat = Hemisphere
+                      * (90.0 - 2.0*Math.atan(Math.exp(Math.log(radius)/Cone))*
+                         Data.RADIANS_TO_DEGREES);
+
+             latlon[0][kk] = lat;
+             latlon[1][kk] = WEST_POSITIVE*lon;
+           }
+         }
+         break;
+      case PROJ_STEREO:
+         {
+            double xrow, xcol, rho, c, cc, sc, lon, lat;
+            for ( int kk = 0; kk < length; kk++) {
+           //-xrow = CentralRow - rowcol[0][kk] - 1;
+              xrow = CentralRow - (Nr-1-rowcol[0][kk]) - 1;
+              xcol = CentralCol - rowcol[1][kk] - 1;
+              rho = xrow*xrow + xcol*xcol;
+              if (rho<1.0e-20) {
+                lat = CentralLat;
+                lon = CentralLon;
+              }
+              else {
+                rho = Math.sqrt( rho );
+                c = 2.0 * Math.atan( rho * InvScale);
+                cc = Math.cos(c);
+                sc = Math.sin(c);
+                lat = Data.RADIANS_TO_DEGREES
+                     * Math.asin( cc*SinCentralLat
+                            + xrow*sc*CosCentralLat / rho );
+                lon = CentralLon + Data.RADIANS_TO_DEGREES * Math.atan2( xcol * sc,
+                         (rho * CosCentralLat * cc
+                      - xrow * SinCentralLat * sc) );
+                if (lon < -180.0)  lon += 360.0;
+                else if (lon > 180.0)  lon -= 360.0;
+            }
+            latlon[0][kk] = lat;
+            latlon[1][kk] = WEST_POSITIVE*lon;
+           }
+         }
+         break;
+      case PROJ_ROTATED:
+         {
+           for (int kk = 0; kk < length; kk++) {
+          //-latlon[0][kk] = NorthBound - rowcol[0][kk]
+             latlon[0][kk] = NorthBound - (Nr-1-rowcol[0][kk])
+                     * (NorthBound-SouthBound) / (double) (Nr-1);
+             latlon[1][kk] = WestBound - rowcol[1][kk]
+                     * (WestBound-EastBound) / (double) (Nc-1);
+           }
+           pandg_back(latlon, CentralLat, CentralLon, Rotation);
+         }
+         break;
+      default:
+         throw new VisADException("projection unknown");
+   }
+
+   return latlon;
+  }
+
+  public double[][] fromReference(double[][] latlon)
+         throws VisADException
+  {
+   int length = latlon[0].length;
+   double[][] rowcol = new double[2][length];
+
+   switch (Projection) {
+      case PROJ_GENERIC:
+      case PROJ_LINEAR:
+      case PROJ_CYLINDRICAL:
+      case PROJ_SPHERICAL:
+         for ( int kk = 0; kk < length; kk++ ) {
+           rowcol[0][kk] = (NorthBound - latlon[0][kk])/RowInc;
+           rowcol[0][kk] = (Nr-1) - rowcol[0][kk];
+           rowcol[1][kk] = (WestBound - latlon[1][kk]*WEST_POSITIVE)/ColInc;
+         }
+         break;
+      case PROJ_LAMBERT:
+         {
+            double rlon, rlat, r, lat, lon;
+            for (int kk = 0; kk < length; kk++) {
+              lat = latlon[0][kk];
+              lon = latlon[1][kk]*WEST_POSITIVE;
+
+              rlon = lon - CentralLon;
+              rlon = rlon * Cone * Data.DEGREES_TO_RADIANS;
+
+              if (lat < -85.0) {
+                /* infinity */
+                r = 10000.0;
+              }
+              else {
+                rlat = (90.0 - Hemisphere * lat) * Data.DEGREES_TO_RADIANS * 0.5;
+                r = ConeFactor * Math.pow(Math.tan(rlat), Cone);
+              }
+              rowcol[0][kk] = PoleRow + r * Math.cos(rlon);
+              rowcol[0][kk] = (Nr-1) - rowcol[0][kk];
+              rowcol[1][kk] = PoleCol - r * Math.sin(rlon);
+            }
+         }
+         break;
+      case PROJ_STEREO:
+         {
+            double rlat, rlon, clon, clat, k, lat, lon;
+            
+            for (int kk = 0; kk < length; kk++) {
+              lat = latlon[0][kk];
+              lon = latlon[1][kk]*WEST_POSITIVE;
+
+              rlat = Data.DEGREES_TO_RADIANS * lat;
+              rlon = Data.DEGREES_TO_RADIANS * (CentralLon - lon);
+              clon = Math.cos(rlon);
+              clat = Math.cos(rlat);
+              k = StereoScale
+                / (1.0 + SinCentralLat*Math.sin(rlat)
+                       + CosCentralLat*clat*clon);
+              rowcol[1][kk] = (CentralCol-1) + k * clat * Math.sin(rlon);
+              rowcol[0][kk] = (CentralRow-1)
+                   - k * (CosCentralLat * Math.sin(rlat)
+                       - SinCentralLat * clat * clon);
+              rowcol[0][kk] = (Nr-1) - rowcol[0][kk];
+            }
+         }
+         break;
+      case PROJ_ROTATED:
+         {
+            pandg_for(latlon, CentralLat, CentralLon, Rotation);
+            for (int kk = 0; kk < length; kk++) {
+              rowcol[0][kk] = (NorthBound - latlon[0][kk])/RowInc;
+              rowcol[0][kk] = (Nr-1) - rowcol[0][kk];
+              rowcol[1][kk] = (WestBound - latlon[1][kk])/ColInc;
+            }
+         }
+         break;
+      default:
+         throw new VisADException("Projection unknown");
+   }
+   return rowcol;
+  }
+
+  /*
+    Pete and Greg parameters:
+      Pete rotated sphere lat 0, lon 0 -> Earth lat a, lon b
+      r = East angle between North half of lon = 0 line on Pete rotated
+          sphere and lon = b on Earth
+
+    coordinates:
+      lat p1, lon g1 on Earth
+      lat pr, lon gr on Pete rotated sphere
+  */
+
+  /* Pete rotated sphere to Earth */
+  private static void pandg_back( double[][] latlon, double a, double b, double r )
+  {
+    double pr, gr, pm, gm;
+
+    /* NOTE - longitude sign switches - b too! */
+
+    for (int kk = 0; kk < latlon[0].length; kk++) { 
+
+      pr = Data.DEGREES_TO_RADIANS * latlon[0][kk];
+      gr = -Data.DEGREES_TO_RADIANS * latlon[1][kk];
+      pm = Math.asin( Math.cos(pr) * Math.cos (gr) );
+      gm = Math.atan2(Math.cos(pr) * Math.sin (gr), -Math.sin(pr) );
+
+      latlon[0][kk] =
+         Data.RADIANS_TO_DEGREES *
+           Math.asin( Math.sin(a) * Math.sin(pm) - Math.cos(a) * Math.cos(pm) * Math.cos(gm - r) );
+      latlon[1][kk] =
+        -Data.RADIANS_TO_DEGREES * (-b + Math.atan2(Math.cos(pm) * Math.sin(gm - r),
+           Math.sin(a) * Math.cos(pm) * Math.cos(gm - r) + Math.cos(a) * Math.sin(pm)));
+      latlon[1][kk] *= WEST_POSITIVE;
+    }
+    return;
+  }
+
+/* Earth to Pete rotated sphere */
+  private static void pandg_for( double[][] latlon, double a, double b, double r )
+  {
+    double p1, g1, p, g;
+
+    /* NOTE - longitude sign switches - b too! */
+   
+    for (int kk = 0; kk < latlon[0].length; kk++) {
+
+      p1 = Data.DEGREES_TO_RADIANS * latlon[0][kk];
+      g1 = -Data.DEGREES_TO_RADIANS * latlon[1][kk]*WEST_POSITIVE;
+      p = Math.asin( Math.sin(a) * Math.sin(p1) + Math.cos(a) * Math.cos(p1) * Math.cos(g1 + b) );
+      g = r + Math.atan2(Math.cos(p1) * Math.sin (g1 + b),
+              Math.sin(a) * Math.cos(p1) * Math.cos(g1 + b) - Math.cos(a) * Math.sin(p1) );
+
+      latlon[0][kk] =
+        Data.RADIANS_TO_DEGREES * Math.asin( -Math.cos(p) * Math.cos(g) );
+      latlon[1][kk] =
+       -Data.RADIANS_TO_DEGREES * Math.atan2(Math.cos(p) * Math.sin(g), Math.sin(p) );
+    }
+    return;
+  }
+
+  private static boolean sign(double dub) 
+  {
+    if ( dub < 0.0 ) {
+      return false;
+    }
+    else {
+      return true;
+    }
+  }
+
+  public boolean equals(Object cs) 
+  {
+    if ( !(cs instanceof Vis5DCoordinateSystem)) return false;
+    Vis5DCoordinateSystem that = (Vis5DCoordinateSystem) cs;
+    return (this.Projection == that.Projection &&
+            Double.doubleToLongBits(this.Nr) == 
+                Double.doubleToLongBits(that.Nr) &&
+            Double.doubleToLongBits(this.Nc) == 
+                Double.doubleToLongBits(that.Nc) &&
+            java.util.Arrays.equals(this.projargs, that.projargs));
+  }
+
+  public static void main(String args[]) throws VisADException
+  {
+    int proj = 3;
+    double[] projargs =
+     {90, 100, 50, 50, 100};
+
+    Vis5DCoordinateSystem v5dcs =
+      new Vis5DCoordinateSystem(proj, projargs, 100, 100);
+
+    double[][] latlon =
+     {{89, 42, 60}, {-100, -100, -180}};
+
+    double[][] rowcol = v5dcs.fromReference(latlon);
+ // System.out.println(rowcol[0][0]+", "+rowcol[1][0]+" : "+rowcol[0][2]+", "+rowcol[1][2]);
+
+    double[][] latlon_t = v5dcs.toReference(rowcol);
+    System.out.println(latlon_t[0][0]+", "+latlon_t[1][0]+" : "+latlon_t[0][2]+", "+latlon_t[1][2]);
+
+    proj = 2;
+    double[] projargs_lam =
+     {60, 30, 0, 50, 100, 100};
+    
+    v5dcs =
+      new Vis5DCoordinateSystem(proj, projargs_lam, 100, 100);
+
+    double[][] latlon2 =
+     {{90, 40, 50}, {-100, -100, -180}};
+    rowcol = v5dcs.fromReference(latlon2);
+ // System.out.println(rowcol[0][0]+", "+rowcol[1][0]+" : "+rowcol[0][2]+", "+rowcol[1][2]);
+    latlon_t = v5dcs.toReference(rowcol);
+    System.out.println(latlon_t[0][0]+", "+latlon_t[1][0]+" : "+latlon_t[0][2]+", "+latlon_t[1][2]);
+  }
+}
diff --git a/visad/data/vis5d/Vis5DFamily.java b/visad/data/vis5d/Vis5DFamily.java
new file mode 100644
index 0000000..47455cb
--- /dev/null
+++ b/visad/data/vis5d/Vis5DFamily.java
@@ -0,0 +1,201 @@
+//
+// Vis5DFamily.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+
+import java.io.IOException;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+
+import visad.DataImpl;
+
+import visad.VisADException;
+
+import visad.data.*;
+
+import java.net.URL;
+
+
+/**
+ * A container for Vis5D file types - regular using
+ * Vis5DAdaptedForm and Vis5D TOPO files.
+ * To read a <tt>Data</tt> object from a file or URL:<br>
+ * <pre>
+ *    Data data = new Vis5DFamily("vis5d").open(string);
+ * </pre>
+ * @see  visad.data.vis5d.Vis5DAdaptedForm
+ * @see  visad.data.vis5d.Vis5DTopoForm
+ * @author Don Murray
+ */
+public class Vis5DFamily extends FunctionFormFamily {
+
+    /**
+      * List of all supported VisAD datatype Forms.
+      * @serial
+      */
+    private static FormNode[] list            = new FormNode[5];
+    private static boolean    listInitialized = false;
+
+    /**
+     * Build a list of all known file adapter Forms
+     */
+    private static void buildList() {
+
+        int i = 0;
+
+        try {
+            list[i] = new Vis5DAdaptedForm();
+
+            i++;
+        } catch (Throwable t) {}
+
+        try {
+            list[i] = new Vis5DTopoForm();
+
+            i++;
+        } catch (Throwable t) {}
+
+        // throw an Exception if too many Forms for list
+        FormNode junk = list[i];
+
+        while (i < list.length) {
+            list[i++] = null;
+        }
+
+        listInitialized = true;
+    }
+
+    /**
+     * Add to the family of the supported map datatype Forms
+     * @param  form   FormNode to add to the list
+     *
+     * @exception ArrayIndexOutOfBoundsException
+     *                   If there is no more room in the list.
+     */
+    public static void addFormToList(FormNode form)
+            throws ArrayIndexOutOfBoundsException {
+
+        synchronized (list) {
+            if (!listInitialized) {
+                buildList();
+            }
+
+            int i = 0;
+
+            while (i < list.length) {
+                if (list[i] == null) {
+                    list[i] = form;
+
+                    return;
+                }
+
+                i++;
+            }
+        }
+
+        throw new ArrayIndexOutOfBoundsException("Only " + list.length
+                                                 + " entries allowed");
+    }
+
+    /**
+     * Construct a family of the supported map datatype Forms
+     * @param  name   name of the family
+     */
+    public Vis5DFamily(String name) {
+
+        super(name);
+
+        synchronized (list) {
+            if (!listInitialized) {
+                buildList();
+            }
+        }
+
+        for (int i = 0; (i < list.length) && (list[i] != null); i++) {
+            forms.addElement(list[i]);
+        }
+    }
+
+    /**
+     * Open a local data object using the first appropriate map form.
+     * @param  id   String representing the path of the map file
+     * @throws  BadFormException  - no form is appropriate
+     * @throws  VisADException  - VisAD error
+     */
+    public DataImpl open(String id) throws BadFormException, VisADException {
+        return super.open(id);
+    }
+
+    /**
+     * Open a remote data object using the first appropriate map form.
+     * @param  url   URL representing the location of the map file
+     * @throws  BadFormException  - no form is appropriate
+     * @throws  VisADException  - VisAD error
+     * @throws  IOException  - file not found
+     */
+    public DataImpl open(URL url)
+            throws BadFormException, VisADException, IOException {
+        return super.open(url);
+    }
+
+    /**
+     * Test the Vis5DFamily class
+     * run java visad.data.vis5d.Vis5DFamily  v5dfile1 v5dfile2 ... v5dfilen
+     */
+    public static void main(String[] args)
+            throws BadFormException, IOException, RemoteException,
+                   VisADException {
+
+        if (args.length < 1) {
+            System.err.println("Usage: Vis5DFamily infile [infile ...]");
+            System.exit(1);
+
+            return;
+        }
+
+        Vis5DFamily fr = new Vis5DFamily("sample");
+
+        for (int i = 0; i < args.length; i++) {
+            Data data;
+
+            System.out.println("Trying file " + args[i]);
+
+            data = fr.open(args[i]);
+
+            System.out.println(args[i] + ": "
+                               + data.getType().prettyString());
+        }
+    }
+}
+
+
+/*--- Formatted 2002-02-04 16:10:31 MST in Sun Java Convention Style ---*/
+
+
+/*------ Formatted by Jindent 3.24 Basic 1.0 --- http://www.jindent.de ------*/
diff --git a/visad/data/vis5d/Vis5DFile.java b/visad/data/vis5d/Vis5DFile.java
new file mode 100644
index 0000000..10cb90e
--- /dev/null
+++ b/visad/data/vis5d/Vis5DFile.java
@@ -0,0 +1,62 @@
+//
+// Vis5DFile.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.FunctionType;
+import visad.RealType;
+import visad.Set;
+
+public class Vis5DFile
+{
+   String filename;
+   V5DStruct vv;
+   Set space_set;
+   FunctionType grid_type;
+   RealType[] vars;
+   int[] vars_indexes;
+   int nvars;
+   int grid_size;
+
+
+   public Vis5DFile(String filename,
+                    V5DStruct  vv,
+                    Set space_set,
+                    FunctionType grid_type,
+                    RealType[] vars,
+                    int[] vars_indexes,
+                    int grid_size )
+   {
+     this.filename = filename;
+     this.vv = vv;
+     this.space_set = space_set;
+     this.grid_type = grid_type;
+     this.vars = vars;
+     this.nvars = vars.length;
+     this.vars_indexes = vars_indexes;
+     this.grid_size = grid_size;
+   }
+}
diff --git a/visad/data/vis5d/Vis5DFileAccessor.java b/visad/data/vis5d/Vis5DFileAccessor.java
new file mode 100644
index 0000000..71006ce
--- /dev/null
+++ b/visad/data/vis5d/Vis5DFileAccessor.java
@@ -0,0 +1,77 @@
+//
+// Vis5DFileAccessor.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.*;
+import visad.data.*;
+import java.rmi.*;
+
+public class Vis5DFileAccessor extends FileAccessor
+{
+   private int time_idx;
+   private Vis5DFile v5dfile;
+
+   public Vis5DFileAccessor(Vis5DFile v5dfile, int time_idx)
+   {
+     this.v5dfile = v5dfile;
+     this.time_idx = time_idx;
+   }
+
+   public FlatField getFlatField()
+          throws VisADException, RemoteException
+   {
+     FlatField ff = null;
+     try {
+       ff = Vis5DForm.makeFlatField(v5dfile, time_idx);
+     }
+     catch (java.io.IOException e) {
+       System.out.println(e.getMessage());
+     }
+     return ff;
+   }
+
+   public FunctionType getFunctionType()
+          throws VisADException
+   {
+     return v5dfile.grid_type;
+   }
+
+   public void writeFile( int[] fileLocations, Data range )
+   {
+
+   }
+
+   public double[][] readFlatField( FlatField template, int[] fileLocation )
+   {
+     return null;
+   }
+
+   public void writeFlatField( double[][] values, FlatField template, int[] fileLocation )
+   {
+
+   }
+}
diff --git a/visad/data/vis5d/Vis5DForm.java b/visad/data/vis5d/Vis5DForm.java
new file mode 100644
index 0000000..b5b9fa3
--- /dev/null
+++ b/visad/data/vis5d/Vis5DForm.java
@@ -0,0 +1,819 @@
+//
+// Vis5DForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.*;
+import visad.java3d.*;
+import visad.data.*;
+import visad.data.units.Parser;
+import visad.data.units.ParseException;
+import visad.util.*;
+import visad.jmet.DumpType;
+import java.io.IOException;
+import java.rmi.RemoteException;
+import java.net.URL;
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+// JFC packages
+import javax.swing.*;
+import javax.swing.event.*;
+
+// AWT packages
+import java.awt.*;
+import java.awt.event.*;
+
+/**
+   Vis5DForm is the VisAD data format adapter for Vis5D files.<P>
+*/
+public class Vis5DForm extends Form implements FormFileInformer {
+
+  /** from vis5d-4.3/src/v5d.h */
+  private final int MAXVARS     = 200;
+  private final int MAXTIMES    = 400;
+  private final int MAXROWS     = 400;
+  private final int MAXCOLUMNS  = 400;
+  private final int MAXLEVELS   = 400;
+  private final int MAXPROJARGS = MAXROWS+MAXCOLUMNS+1;
+  private final int MAXVERTARGS = MAXLEVELS+1;
+
+  private static int num = 0;
+
+  private static boolean loaded = false;
+
+
+  public Vis5DForm() {
+    super("Vis5DForm" + num++);
+  }
+
+  public boolean isThisType(String name) {
+    return name.endsWith(".v5d");
+  }
+
+  public boolean isThisType(byte[] block) {
+    String v5d = new String(block, 0, 3);
+    return v5d.equals("V5D");
+  }
+
+  public String[] getDefaultSuffixes() {
+    String[] suff = { "v5d" };
+    return suff;
+  }
+
+  public synchronized void save(String id, Data data, boolean replace)
+         throws BadFormException, IOException, RemoteException, VisADException {
+    throw new UnimplementedException("Vis5DForm.save");
+  }
+
+  public synchronized void add(String id, Data data, boolean replace)
+         throws BadFormException {
+    throw new BadFormException("Vis5DForm.add");
+  }
+
+  public synchronized DataImpl open(String id)
+         throws BadFormException, IOException, VisADException {
+
+    Set space_set;
+    V5DStruct vv;
+    FunctionType v5d_type;
+    int nvars;
+    int grid_size;
+    RealType[] vars;
+
+    if (id == null) {
+      throw new BadFormException("Vis5DForm.open: null name String");
+    }
+
+    byte[] name = id.getBytes();
+    int[] sizes = new int[5];
+    int[] map_proj = new int[1];
+    String[] varnames = new String[MAXVARS];
+    String[] varunits = new String[MAXVARS];
+    int[] n_levels = new int[MAXVARS];
+    int[]  vert_sys = new int[1];
+    float[]  vertargs = new float[MAXVERTARGS];
+ //-float[] times = new float[MAXTIMES];
+    double[] times = new double[MAXTIMES];
+    float[] projargs = new float[MAXPROJARGS];
+
+    vv = V5DStruct.v5d_open(name,
+                            name.length,
+                            sizes,
+                            n_levels,
+                            varnames,
+                            varunits,
+                            map_proj,
+                            projargs,
+                            vert_sys,
+                            vertargs,
+                            times);
+
+    if (sizes[0] < 1) {
+      throw new BadFormException("Vis5DForm.open: bad file");
+    }
+    //-System.out.println("proj: "+map_proj[0]);
+
+    int nr = sizes[0];
+    int nc = sizes[1];
+    int nl = sizes[2];
+    int ntimes = sizes[3];
+    nvars = sizes[4];
+
+    //-System.out.println("nr: "+nr);
+    //-System.out.println("nc: "+nc);
+    //-System.out.println("nl: "+nl);
+    //-System.out.println("ntimes: "+ntimes);
+    //-System.out.println("nvars: "+nvars);
+
+    RealType time = RealType.Time;
+
+    RealType row = RealType.getRealType("row");
+    RealType col = RealType.getRealType("col");
+    //RealType lev = RealType.getRealType("lev");  not used
+
+    vars = new RealType[nvars];
+
+    for (int i=0; i<nvars; i++) {
+      String unit_spec = varunits[i];
+      Unit unit = null;
+      if ( unit_spec != null ) {
+        try {
+          unit = Parser.parse(unit_spec);
+        }
+        catch (ParseException e) {
+          System.out.println(e.getMessage());
+        }
+      }
+      vars[i] = RealType.getRealType(varnames[i], unit);
+      if ( vars[i] == null ) {
+        vars[i] = RealType.getRealType("var"+i);
+      }
+    }
+
+    double[][] proj_args =
+      Set.floatToDouble(new float[][] {projargs});
+    double[][] vert_args =
+      Set.floatToDouble(new float[][] {vertargs});
+
+    CoordinateSystem coord_sys =
+      new Vis5DCoordinateSystem(map_proj[0], proj_args[0], nr, nc);
+
+    RealTupleType domain;
+    Vis5DVerticalSystem vert_coord_sys = null;
+
+
+   /*----------------------------------------------------------
+      Sort variables by n_levels.  There should only be two
+      possibilities:
+       (1) all variables with same n_levels.
+       (2) some variables all with same n_levels, the rest with
+           only one level.
+
+      Throw BadFormException otherwise. */
+    
+
+    Hashtable var_table = new Hashtable();
+    for (int i = 0; i < nvars; i++) {
+      var_table.put(new Integer(n_levels[i]), new Object());
+    }
+    int n_var_groups = var_table.size();
+
+    if ( n_var_groups > 2 ) {
+      throw
+        new BadFormException("more than two variable groups by n_levels");
+    }
+    else if ( n_var_groups == 0 ) {
+      throw
+        new BadFormException("n_var_groups == 0");
+    }
+
+    RealType[][] var_grps = new RealType[n_var_groups][];
+    RealType[] tmp_r = new RealType[nvars];
+    int[][] var_grps_indexes = new int[n_var_groups][];
+    int[] tmp_i = new int[nvars];
+    int[] var_grps_nlevels = new int[n_var_groups];
+    
+
+    Enumeration en = var_table.keys();
+    for ( int grp = 0; grp < n_var_groups; grp++)
+    {
+      Integer key = (Integer)en.nextElement();
+      int cnt = 0;
+      for (int i = 0; i < nvars; i++) {
+        if ( n_levels[i] == key.intValue() ) {
+           tmp_r[cnt] = vars[i];
+           tmp_i[cnt] = i;
+           cnt++;
+        }
+      }
+      var_grps[grp] = new RealType[cnt];
+      System.arraycopy(tmp_r, 0, var_grps[grp], 0, cnt);
+
+      var_grps_indexes[grp] = new int[cnt];
+      System.arraycopy(tmp_i, 0, var_grps_indexes[grp], 0, cnt);
+      var_grps_nlevels[grp] = key.intValue();
+    }
+    /*---------------------------------------------------------*/
+
+
+  FunctionType[][] grid_type = new FunctionType[n_var_groups][];
+  Vis5DFile[][] v5dfile_s = new Vis5DFile[n_var_groups][];
+
+  int n_comps = 0;
+
+  for ( int grp = 0; grp < n_var_groups; grp++ )
+  {
+    RealType[] sub_vars = var_grps[grp];
+    int[] sub_vars_indexes = var_grps_indexes[grp];
+    nl = var_grps_nlevels[grp];
+
+    if (nl > 1) {
+      vert_coord_sys =
+        new Vis5DVerticalSystem(vert_sys[0], nl, vert_args[0]);
+
+      RealType height = vert_coord_sys.vert_type;
+
+      CoordinateSystem pcs =
+        new CachingCoordinateSystem(
+          new CartesianProductCoordinateSystem(
+            new CoordinateSystem[]
+              {coord_sys, vert_coord_sys.vert_cs}));
+             
+
+      domain =
+        new RealTupleType( new RealType[] {row, col, height}, pcs, null);
+        /*
+    }
+    else {
+      domain = new RealTupleType(new RealType[] {row, col}, 
+        new CachingCoordinateSystem(coord_sys), null);
+    }
+
+    if (nl > 1)
+    {
+    */
+      SampledSet vert_set = vert_coord_sys.vertSet;
+
+     /**-  Maybe sometime in the future
+      RealTupleType row_col = new RealTupleType(new RealType[] {row, col});
+      SampledSet row_col_set = new Integer2DSet(row_col, nr, nc);
+      space_set = new ProductSet(domain,
+        new SampledSet[] {row_col_set, vert_set});
+      */
+
+      if (vert_set instanceof Linear1DSet) {
+        space_set =
+          new Linear3DSet(domain, 
+                          new Linear1DSet[] {
+                              new Integer1DSet(row, nr),
+                              new Integer1DSet(col, nc),
+                              (Linear1DSet) vert_set},
+                           (CoordinateSystem) null,
+                           new Unit[] {null, null, vert_coord_sys.vert_unit},
+                           (ErrorEstimate[]) null);
+      }
+      else {  // Gridde1DSet
+        float[][] vert_samples = vert_set.getSamples();
+        float[][] domain_samples = new float[3][nr*nc*nl];
+        int idx = 0;
+        for (int kk = 0; kk < nl; kk++) {
+          for (int jj = 0; jj < nc; jj++) {
+            for ( int ii = 0; ii < nr; ii++) {
+              domain_samples[0][idx] = ii;
+              domain_samples[1][idx] = jj;
+              domain_samples[2][idx] = vert_samples[0][kk];
+              idx++;
+            }
+          }
+        }
+        space_set =
+          //new Gridded3DSet(domain, domain_samples, nr, nc, nl);
+          new Gridded3DSet(
+                  domain, domain_samples, nr, nc, nl,
+                  (CoordinateSystem) null,
+                  //new Unit[] {null, null, height.getDefaultUnit()},
+                  new Unit[] {null, null, vert_coord_sys.vert_unit},
+                  (ErrorEstimate[]) null);
+      }
+
+      
+      grid_type[grp] = new FunctionType[sub_vars.length];
+      v5dfile_s[grp] = new Vis5DFile[sub_vars.length];
+      
+      grid_size = nr * nc * nl;
+
+      for (int k = 0; k < sub_vars.length; k++) {
+        grid_type[grp][k] = new FunctionType(domain, sub_vars[k]);
+        v5dfile_s[grp][k] =
+          new Vis5DFile(id, vv, space_set,
+                        grid_type[grp][k],
+                        new RealType[] {sub_vars[k]},
+                        new int[] {sub_vars_indexes[k]}, grid_size);
+      }
+      n_comps += grid_type[grp].length;
+    }
+    else 
+    {
+      domain = new RealTupleType(new RealType[] {row, col}, 
+        new CachingCoordinateSystem(coord_sys), null);
+      space_set = new Integer2DSet(domain, nr, nc);
+
+      grid_type[grp] = new FunctionType[1];
+      v5dfile_s[grp] = new Vis5DFile[1];
+
+      RealTupleType range = new RealTupleType(sub_vars);
+      grid_type[grp][0] = new FunctionType(domain, range);
+
+      grid_size = nr * nc * nl;
+
+      v5dfile_s[grp][0] =
+        new Vis5DFile(id, vv, space_set,
+           grid_type[grp][0], sub_vars, sub_vars_indexes, grid_size);
+
+      n_comps += grid_type[grp].length;
+    }
+  }
+
+
+    RealTupleType time_domain = new RealTupleType(time);
+
+    MathType v5d_range;
+    MathType[] range_comps = new MathType[n_comps];
+    int cnt = 0;
+    for ( int grp = 0; grp < grid_type.length; grp++) {
+      for (int i = 0; i < grid_type[grp].length; i++) {
+        range_comps[cnt++] = grid_type[grp][i];
+      }
+    }
+    if (range_comps.length == 1) {
+      v5d_range = range_comps[0];
+    }
+    else {
+      v5d_range = new TupleType(range_comps);
+    }
+    v5d_type = new FunctionType(time_domain, v5d_range);
+
+
+
+    double[][] timeses = new double[1][ntimes];
+    for (int i=0; i<ntimes; i++)  {
+      timeses[0][i] = times[i];
+    }
+    Unit v5d_time_unit = new OffsetUnit(
+                             visad.data.units.UnitParser.encodeTimestamp(
+                                1900, 1, 1, 0, 0, 0, 0), SI.second);
+    Gridded1DDoubleSet time_set =
+      new Gridded1DDoubleSet(time, timeses, ntimes,
+                             null, new Unit[] {v5d_time_unit}, null);
+
+
+    FieldImpl v5d = new FieldImpl(v5d_type, time_set);
+
+    DataImpl range_data;
+    for (int i=0; i<ntimes; i++)
+    {
+      if (range_comps.length == 1) {
+        range_data = getFlatField(v5dfile_s[0][0], i);
+      }
+      else {
+        DataImpl[] datas = new DataImpl[range_comps.length];
+        cnt = 0;
+        for (int j = 0; j < v5dfile_s.length; j++) {
+          for (int k = 0; k < v5dfile_s[j].length; k++) {
+            datas[cnt++] = getFlatField(v5dfile_s[j][k], i); 
+          }
+        }
+        range_data = new Tuple(datas, false);
+      }
+      v5d.setSample(i, range_data, false);
+    }
+
+    return v5d;
+  }
+
+  public FlatField getFlatField(Vis5DFile v5dfile, int time_idx)
+         throws VisADException, IOException, BadFormException
+  {
+    return makeFlatField(v5dfile, time_idx);
+  }
+
+  public static FlatField makeFlatField(Vis5DFile v5dfile, int time_idx)
+         throws VisADException, IOException, BadFormException
+  {
+    int nvars = v5dfile.nvars;
+    int grid_size = v5dfile.grid_size;
+    FunctionType grid_type = v5dfile.grid_type;
+    Set space_set = v5dfile.space_set;
+    V5DStruct vv = v5dfile.vv;
+    RealType[] vars = v5dfile.vars;
+    int[] vars_indexes = v5dfile.vars_indexes;
+
+
+    float[][] data = new float[nvars][grid_size];
+    Linear1DSet[] range_sets = new Linear1DSet[nvars];
+    for (int j=0; j<nvars; j++) {
+      float[] ranges = new float[2];
+      vv.v5d_read(time_idx, vars_indexes[j], ranges, data[j]);
+      if (ranges[0] >= 0.99E30 && ranges[1] <= -0.99E30) {
+        range_sets[j] = new Linear1DSet(0.0, 1.0, 255);
+      }
+      else {
+        if (ranges[0] > ranges[1]) {
+          throw new BadFormException("Vis5DForm.open: bad read " +
+                                       vars[j].getName());
+        }
+        range_sets[j] =
+          new Linear1DSet((double) ranges[0], (double) ranges[1], 255);
+      }
+
+
+      //- invert rows
+      float[] tmp_data = new float[grid_size];
+      int[] lens = ((GriddedSet)space_set).getLengths();
+
+      if ( lens.length == 2 ) {
+        int cnt = 0;
+        for ( int mm = 0; mm < lens[1]; mm++ ) {
+          int start = (mm+1)*lens[0] - 1;
+          for ( int nn = 0; nn < lens[0]; nn++ ) {
+            tmp_data[cnt++] = data[j][start--];
+          }
+        }
+      }
+      else if ( lens.length == 3 ) {
+        int cnt = 0;
+        for ( int ll = 0; ll < lens[2]; ll++ ) {
+          for ( int mm = 0; mm < lens[1]; mm++ ) {
+            int start = ((mm+1)*lens[0] - 1) + lens[0]*lens[1]*ll;
+            for ( int nn = 0; nn < lens[0]; nn++ ) {
+              tmp_data[cnt++] = data[j][start--];
+            }
+          }
+        }
+      }
+      System.arraycopy(tmp_data, 0, data[j], 0, grid_size);
+      tmp_data = null;
+
+
+
+      for (int k=0; k<grid_size; k++) {
+        if (data[j][k] > 0.5e35) data[j][k] = Float.NaN;
+      }
+    }
+    // FlatField grid =
+    //   new FlatField(grid_type, space_set, null, null, range_sets, null);
+    FlatField grid =
+      new FlatField(grid_type, space_set);
+    grid.setSamples(data, false);
+
+    return grid;
+  }
+
+
+  public synchronized DataImpl open(URL url)
+         throws BadFormException, VisADException, IOException {
+    return open(url.toString());
+  }
+
+  public synchronized FormNode getForms(Data data) {
+    return null;
+  }
+
+  /** the width and height of the UI frame */
+  public static int WIDTH = 800;
+  public static int HEIGHT = 600;
+
+  /** run 'java visad.data.vis5d.Vis5DForm QLQ.v5d' to test */
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+    if (args == null || args.length < 1) {
+      System.out.println("run 'java visad.data.vis5d.Vis5DForm file.v5d'");
+    }
+    Vis5DForm form = new Vis5DAdaptedForm();
+    FieldImpl vis5d = null;
+    try {
+      vis5d = (FieldImpl) form.open(args[0]);
+      DumpType.dumpMathType(vis5d.getType());
+    }
+    catch (Exception e) {
+      System.out.println(e.getMessage());
+      return;
+    }
+    if (vis5d == null) {
+      System.out.println("bad Vis5D file read");
+      return;
+    }
+    FunctionType type = (FunctionType) vis5d.getType();
+    FieldImpl new_vis5d;
+    if ( type.getRange() instanceof TupleType ) {
+   //-new_vis5d = (FieldImpl)vis5d.extract(20);
+      new_vis5d = vis5d;
+    }
+    else {
+      new_vis5d = vis5d;
+    }
+    FunctionType vis5d_type = (FunctionType) new_vis5d.getType();
+    System.out.println(vis5d_type);
+    DataReference vis5d_ref = new DataReferenceImpl("vis5d_ref");
+    vis5d_ref.setData(new_vis5d);
+
+    //
+    // construct JFC user interface with JSliders linked to
+    // Data objects, and embed Displays into JFC JFrame
+    //
+
+    // create a JFrame
+    JFrame frame = new JFrame("Vis5D");
+    WindowListener l = new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    };
+    frame.addWindowListener(l);
+    frame.setSize(WIDTH, HEIGHT);
+    frame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+
+    // create big_panel JPanel in frame
+    JPanel big_panel = new JPanel();
+    big_panel.setLayout(new BoxLayout(big_panel, BoxLayout.X_AXIS));
+    big_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    big_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(big_panel);
+
+ // frame.setVisible(true);
+
+    // create left hand side JPanel for sliders and text
+    JPanel left = new JPanel(); // FlowLayout and double buffer
+    left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
+    left.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    left.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    big_panel.add(left);
+
+    // construct JLabels
+    // (JTextArea does not align in BoxLayout well, so use JLabels)
+    left.add(new JLabel("Simple Vis5D File Viewer using VisAD - See:"));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("  http://www.ssec.wisc.edu/~billh/visad.html"));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("for more information about VisAD."));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("Space Science and Engineering Center"));
+    left.add(new JLabel("University of Wisconsin - Madison"));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("Move sliders to adjust iso-surface levels"));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("Click Animate button to toggle animation"));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("Rotate scenes with left mouse button."));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("  "));
+    left.add(new JLabel("  "));
+
+    // create sliders JPanel
+    JPanel sliders = new JPanel();
+    sliders.setName("GoesRetrieval Sliders");
+    sliders.setFont(new Font("Dialog", Font.PLAIN, 12));
+    sliders.setLayout(new BoxLayout(sliders, BoxLayout.Y_AXIS));
+    sliders.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    sliders.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    left.add(sliders);
+
+    // construct JPanel and sub-panels for Displays
+    JPanel display_panel = new JPanel();
+    display_panel.setLayout(new BoxLayout(display_panel,
+                                          BoxLayout.X_AXIS));
+    display_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    display_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    big_panel.add(display_panel);
+
+    // create a Display and add it to panel
+    DisplayImpl display = new DisplayImplJ3D("image display");
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+    display_panel.add(display.getComponent());
+
+
+    // extract RealType components from vis5d_type and use
+    // them to determine how data are displayed
+
+    // map time to Animation
+    RealType time = (RealType) vis5d_type.getDomain().getComponent(0);
+    ScalarMap animation_map = new ScalarMap(time, Display.Animation);
+    display.addMap(animation_map);
+    // default is ON
+    final AnimationControl animation_control =
+      (AnimationControl) animation_map.getControl();
+
+    // get grid type
+
+    /*
+ //-RealTupleType reference = (domain.getCoordinateSystem()).getReference();
+ // domain = reference;
+    // map grid coordinates to display coordinates
+ //-display.addMap(new ScalarMap((RealType) domain.getComponent(1),
+    display.addMap(new ScalarMap(RealType.getRealType("col"),
+                                 Display.XAxis));
+ //-display.addMap(new ScalarMap((RealType) domain.getComponent(0),
+    display.addMap(new ScalarMap(RealType.getRealType("row"),
+                                 Display.YAxis));
+//-display.addMap(new ScalarMap((RealType) domain.getComponent(2),
+   display.addMap(new ScalarMap(RealType.getRealType("lev"),
+                                   Display.ZAxis));
+   */
+   display.addMap(new ScalarMap(RealType.Latitude,  Display.YAxis));
+   display.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+   display.addMap(new ScalarMap(RealType.Altitude,  Display.ZAxis));
+
+    // map grid values to IsoContour
+
+    int n_range_real_types = 0;
+    MathType v5d_range = vis5d_type.getRange();
+    RealType[] tmp = new RealType[200];
+    if ( v5d_range instanceof TupleType ) {
+      for ( int ii = 0; ii < ((TupleType)v5d_range).getDimension(); ii++) {
+        FunctionType f_type = 
+          (FunctionType)
+            ((TupleType)v5d_range).getComponent(ii);
+        MathType mtype = f_type.getRange();
+        if (mtype instanceof TupleType) {
+          int nn = ((TupleType)mtype).getDimension();
+          for ( int kk = 0; kk < nn; kk++) {
+            tmp[n_range_real_types++] = (RealType)((TupleType)mtype).getComponent(kk);
+          }
+        }
+        else {
+          tmp[n_range_real_types++] = (RealType)mtype;
+        }
+      }
+    }
+    else {
+      MathType mtype = ((FunctionType)v5d_range).getRange();
+      if (mtype instanceof TupleType) {
+        int nn = ((TupleType)mtype).getDimension();
+        for ( int kk = 0; kk < nn; kk++) {
+          tmp[n_range_real_types++] = (RealType)((TupleType)mtype).getComponent(kk);
+        }
+      }
+      else {
+        tmp[n_range_real_types++] = (RealType)mtype;
+      }
+    }
+
+    int dim = n_range_real_types;
+    RealType[] range_types = new RealType[dim];
+    ScalarMap[] contour_maps = new ScalarMap[dim];
+    ContourControl[] contour_controls = new ContourControl[dim];
+    DataReference[] range_refs = new DataReferenceImpl[dim];
+    for (int i=0; i<dim; i++) {
+   //-range_types[i] = (RealType) range.getComponent(i);
+      range_types[i] = (RealType) tmp[i];
+      contour_maps[i] = new ScalarMap(range_types[i], Display.IsoContour);
+      try {
+          display.addMap(contour_maps[i]);
+          contour_controls[i] = (ContourControl) contour_maps[i].getControl();
+          contour_controls[i].enableContours(false);
+      } catch (BadMappingException bme) {;} // handle case of duplicate names
+      range_refs[i] = new DataReferenceImpl(range_types[i].getName() + "_ref");
+    }
+
+/* WLH - uncomment these for color demo images from the QLQ.v5d data set
+
+    ScalarMap color_map = new ScalarMap(range_types[1], Display.Green);
+    display.addMap(color_map);
+    color_map.setRange(23.5, 0.0);
+    display.addMap(new ConstantMap(0.5, Display.Red));
+    display.addMap(new ConstantMap(0.5, Display.Blue));
+*/
+
+    // now Display vis5d data
+    display.addReference(vis5d_ref);
+
+    // wait for auto-scaling
+    boolean scaled = false;
+    double[][] ranges = new double[dim][];
+    while (!scaled) {
+      try {
+        Thread.sleep(1000);
+      }
+      catch (InterruptedException e) {
+      }
+      scaled = true;
+      for (int i=0; i<dim; i++) {
+        ranges[i] = contour_maps[i].getRange();
+        if (ranges[i][0] != ranges[i][0] ||
+            ranges[i][1] != ranges[i][1]) {
+          scaled = false;
+          // System.out.println("tick");
+          break;
+        }
+      }
+    }
+    for (int i=0; i<dim; i++) {
+      double scale = (ranges[i][1] - ranges[i][0]) / 255.0;
+      int low = (int) (ranges[i][0] / scale);
+      int hi = (int) (ranges[i][1] / scale);
+      range_refs[i].setData(new Real(range_types[i], scale * low));
+      sliders.add(new VisADSlider(range_types[i].getName(), low, hi, low, scale,
+                                  range_refs[i], range_types[i]));
+      sliders.add(new JLabel("  "));
+
+      ContourCell cell =
+        form. new ContourCell(contour_controls[i], range_refs[i]);
+      cell.addReference(range_refs[i]);
+    }
+
+    final JToggleButton button = new JToggleButton("Animate", false);
+    button.addChangeListener(new ChangeListener() {
+      public void stateChanged(ChangeEvent e) {
+        try {
+          // boolean state = ((ToggleButtonModel) button.getModel()).isSelected();
+          boolean state = button.getModel().isSelected();
+          animation_control.setOn(state);
+        }
+        catch (VisADException ee) {
+        }
+        catch (RemoteException ee) {
+        }
+      }
+    });
+    sliders.add(button);
+
+    // make the JFrame visible
+    frame.setVisible(true);
+  }
+
+  class ContourCell extends CellImpl {
+    ContourControl control;
+    DataReference ref;
+    double value;
+
+    ContourCell(ContourControl c, DataReference r)
+           throws VisADException, RemoteException {
+      control = c;
+      ref = r;
+      value = ((Real) ref.getData()).getValue();
+    }
+
+    public void doAction() throws VisADException, RemoteException {
+      double val = ((Real) ref.getData()).getValue();
+      if (val == val && val != value) {
+        control.setSurfaceValue((float) ((Real) ref.getData()).getValue());
+        control.enableContours(true);
+        value = val;
+      }
+    }
+
+  }
+
+/* here's the output:
+
+demedici% java visad.data.vis5d.Vis5DForm SCHL.v5d
+FunctionType: (time) -> FunctionType (Real): (row, col, lev) -> (U, V, W, QL, TH, Q, P, ED, F)
+
+demedici% java visad.data.vis5d.Vis5DForm QLQ.v5d
+FunctionType: (time) -> FunctionType (Real): (row, col, lev) -> (QL, Q)
+demedici%
+
+*/
+
+
+  /** native method declarations */
+  /** calls v5dOpenFile in v5d.c */
+/*
+  private native void v5d_open(byte[] name, int name_length, int[] sizes,
+                               byte[] varnames, float[] times);
+*/
+
+  /** calls v5dReadGrid in v5d.c */
+/*
+  private native void v5d_read(int time, int var, float[] ranges, float[] data);
+*/
+
+}
+
diff --git a/visad/data/vis5d/Vis5DTopoForm.java b/visad/data/vis5d/Vis5DTopoForm.java
new file mode 100644
index 0000000..c00df77
--- /dev/null
+++ b/visad/data/vis5d/Vis5DTopoForm.java
@@ -0,0 +1,210 @@
+//
+// Vis5DTopoForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.*;
+import visad.data.*;
+import java.io.*;
+import java.net.URL;
+import java.rmi.RemoteException;
+
+/**
+   Vis5DTopoForm is the VisAD data format adapter for Vis5D topography files.<P>
+*/
+public class Vis5DTopoForm extends Form implements FormFileInformer {
+
+  private static int num = 0;
+
+  /**
+   * Create a new Vis5DTopoForm
+   */
+  public Vis5DTopoForm() {
+    super("Vis5DTopoForm" + num++);
+  }
+
+  /**
+   * Check to see if the file name might be right for this form.
+   * @param name   name of the file
+   * @return  true if it might be a Vis5D topography file based on the name.
+   */
+  public boolean isThisType(String name) {
+    return name.endsWith(".v5d") || name.endsWith("TOPO");
+  }
+
+  /**
+   * Check to see if the block contains the magic number
+   * @param block   block of bytes from file
+   * @return  true if the first 4 bytes correspond to "TOPO"
+   */
+  public boolean isThisType(byte[] block) {
+    String topo = new String(block, 0, 4);
+    return topo.equals("TOPO");
+  }
+
+  /**
+   * Get default suffixes for Vis5D topography files
+   * @return  array of suffixes (.v5d, .TOPO)
+   */
+  public String[] getDefaultSuffixes() {
+    String[] suff = { "v5d", "TOPO" };
+    return suff;
+  }
+
+  /**
+   * Save a VisAD data object in this form.
+   * @param id  file id
+   * @param data  Data object to save
+   * @param replace  true to replace the existing file
+   * @throws UnimplementedException  not implemented for this form
+   */
+  public synchronized void save(String id, Data data, boolean replace)
+         throws BadFormException, IOException, RemoteException, VisADException {
+    throw new UnimplementedException("Vis5DTopoForm.save");
+  }
+
+  /**
+   * Add data to an existing data object.
+   * @param id  file id
+   * @param data  Data object to append to
+   * @param replace  true to replace the existing file
+   * @throws BadFormException  not applicable to this form
+   */
+  public synchronized void add(String id, Data data, boolean replace)
+         throws BadFormException {
+    throw new BadFormException("Vis5DTopoForm.add");
+  }
+
+  /**
+   * Return the data forms that are compatible with a data object.
+   * @param  data  Data object in question
+   * @return null for this Form since it doesn't support save.
+   */
+  public synchronized FormNode getForms(Data data) {
+    return null;
+  }
+
+  /**
+   * Returns a VisAD data object corresponding to a URL pointing to a 
+   * Vis5D topography file.
+   *
+   * @param url               URL pointing to the Vis5D topography
+   * @return                  A VisAD data object corresponding to the Vis5D
+   *                          topography file.
+   * @throws BadFormException if not a Vis5D topo file.
+   * @throws VisADException   if a problem occurs in core VisAD.  Probably a
+   *                          VisAD object couldn't be created.
+   * @throws IOException      if an I/O failure occurs.
+   */
+  public synchronized DataImpl open(URL url)
+         throws BadFormException, VisADException, IOException {
+    return open(url.openStream());
+  }
+
+  /**
+   * Returns a VisAD data object corresponding to a Vis5D topography file.
+   *
+   * @param id                path to the existing Vis5D file.
+   * @return                  A VisAD data object corresponding to the Vis5D
+   *                          dataset.
+   * @throws BadFormException if not a Vis5D topo file.
+   * @throws VisADException   if a problem occurs in core VisAD.  Probably a
+   *                          VisAD object couldn't be created.
+   * @throws IOException      if an I/O failure occurs.
+   */
+  public synchronized DataImpl open(String id)
+         throws BadFormException, IOException, VisADException {
+    return open(new FileInputStream(id));
+  }
+
+  /**
+   * Returns a VisAD data object corresponding to an input stream for
+   * a Vis5DTopography file.
+   *
+   * @param in              Input stream
+   * @return                  A VisAD data object corresponding to the Vis5D
+   *                          topo file.
+   * @throws BadFormException if not a Vis5D topo file.
+   * @throws VisADException   if a problem occurs in core VisAD.  Probably a
+   *                          VisAD object couldn't be created.
+   * @throws IOException      if an I/O failure occurs.
+   */
+  public synchronized DataImpl open(InputStream in)
+         throws BadFormException, IOException, VisADException {
+
+    DataInputStream din = new DataInputStream (new BufferedInputStream(in));
+    byte[] type = new byte[40];
+    int ok = din.read(type, 0, 40);
+    String header = new String(type);
+    boolean oldStyle;
+    if (header.startsWith("TOPO2")) {
+      oldStyle = false;
+    } else if (header.startsWith("TOPO")) {
+      oldStyle = true;
+    } else {
+      throw new BadFormException("Vis5DTopoForm.open: not a Vis5D TOPO file");
+    }
+    float westLon, eastLon, northLat, southLat;
+    if (oldStyle) {
+      westLon = din.readInt()/100.f;
+      eastLon = din.readInt()/100.f;
+      northLat = din.readInt()/100.f;
+      southLat = din.readInt()/100.f;
+    } else {
+      westLon = din.readFloat();
+      eastLon = din.readFloat();
+      northLat = din.readFloat();
+      southLat = din.readFloat();
+    }
+    int rows = din.readInt();
+    int cols = din.readInt();
+    /*
+    System.out.println(
+       "Bounds: " +
+          "\n\tWestern Longitude = " + westLon +
+          "\n\tEastern Longitude = " + eastLon +
+          "\n\tNorthern Latitude = " + northLat +
+          "\n\tSouthern Latitude = " + southLat +
+          "\n\trows = " + rows + " cols = " + cols);
+    */
+    Linear2DSet domain = 
+      new LinearLatLonSet(RealTupleType.SpatialEarth2DTuple,
+                          -westLon, -eastLon, cols,   // Vis5D west positive
+                          northLat, southLat, rows);  // Vis5D rows upside down
+    FunctionType ftype =
+      new FunctionType(((SetType)domain.getType()).getDomain(), 
+                         RealType.Altitude);
+    FlatField data = new FlatField(ftype, domain);
+    float[][] samples = new float[1][rows*cols];
+    for (int i = 0; i < rows*cols; i++) {
+      short s = (short) (din.readShort()/2);
+      samples[0][i] = new Short(s).floatValue();
+    } 
+    data.setSamples(samples, false);
+    return data;
+
+  }
+}
diff --git a/visad/data/vis5d/Vis5DVerticalSystem.java b/visad/data/vis5d/Vis5DVerticalSystem.java
new file mode 100644
index 0000000..73810c4
--- /dev/null
+++ b/visad/data/vis5d/Vis5DVerticalSystem.java
@@ -0,0 +1,314 @@
+//
+// Vis5DVerticalSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.vis5d;
+
+import visad.CommonUnit;
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.Gridded1DSet;
+import visad.IdentityCoordinateSystem;
+import visad.Linear1DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.SampledSet;
+import visad.Set;
+import visad.Unit;
+import visad.VisADException;
+import visad.data.units.ParseException;
+import visad.data.units.Parser;
+
+/**
+ * Class for encapsulating the Vis5D vertical system as VisAD
+ * MathTypes and Data objects
+ */
+public class Vis5DVerticalSystem
+{
+  private static int counter = 0;
+
+  /** Unit used for the vertical system */
+  Unit  vert_unit = null;
+  /** Sampled Set of values */
+  SampledSet vertSet;
+  /** RealType of the vertical system parameter */
+  RealType vert_type;
+  /** CoordinateSystem to transform to Reference values */
+  CoordinateSystem vert_cs;
+  /** Reference RealTupleType for the CoordinateSystem */
+  RealTupleType reference;
+  
+  /**
+   * Construct the VisAD MathTypes and Data objects that relate
+   * to the Vis5D vertical system parameters.
+   * @param vert_sys  Vis5D vertical System 
+   * @param n_levels  number of levels in vert_args
+   * @param vert_args array of vertical level values
+   * @throws VisADException  unknown vert_sys or problem creating VisAD
+   *                         objects.
+   * @see visad.data.vis5d.V5DStruct#VertArgs for explanation of vertical
+   *      coordinates.
+   */
+  public Vis5DVerticalSystem( int vert_sys,
+                              int n_levels,
+                              double[] vert_args)
+         throws VisADException
+  {
+
+    switch ( vert_sys )
+    {
+      case (0):
+        vert_unit = CommonUnit.promiscuous;
+        vert_type = makeRealType("Height", vert_unit);
+        reference = new RealTupleType(RealType.Generic);
+        vert_cs = new IdentityCoordinateSystem(reference);
+        break;
+      case (1):
+      case (2):
+        try {
+          vert_unit = Parser.parse("km");
+        }
+        catch (ParseException e) {
+        }
+        vert_type = makeRealType("Height", vert_unit);
+        reference = new RealTupleType(RealType.Altitude);
+        vert_cs = new IdentityCoordinateSystem(reference);
+        break;
+      case (3):
+        try {
+          vert_unit = Parser.parse("mbar");
+        }
+        catch (ParseException e) {
+        }
+        vert_type = makeRealType("Pressure", vert_unit);
+        reference = new RealTupleType(RealType.Altitude);
+        vert_cs = new Vis5DVerticalCoordinateSystem();
+        break;
+      default:
+        throw new VisADException("vert_sys unknown");
+    }
+
+    switch ( vert_sys )
+    {
+      case (0):
+      case (1):
+        double first = vert_args[0];
+        double last = first + vert_args[1]*(n_levels-1);
+        vertSet = new Linear1DSet(vert_type, first, last, n_levels,
+                           (CoordinateSystem) null, new Unit[] {vert_unit}, 
+                           (ErrorEstimate[]) null);
+        break;
+      case (2):  // Altitude in km - non-linear
+        double[][] values = new double[1][n_levels];
+        System.arraycopy(vert_args, 0, values[0], 0, n_levels);
+        vertSet =
+          new Gridded1DSet(vert_type, Set.doubleToFloat(values), n_levels,
+                           (CoordinateSystem) null, new Unit[] {vert_unit}, 
+                           (ErrorEstimate[]) null);
+        break;
+      case (3):  // heights of pressure surfaces in km - non-linear
+        double[][] pressures = new double[1][n_levels];
+        System.arraycopy(vert_args, 0, pressures[0], 0, n_levels);
+        for (int i = 0; i < n_levels; i++) pressures[0][i] *=1000; // km->m
+        pressures = vert_cs.fromReference(pressures); // convert to pressures
+        vertSet =
+          new Gridded1DSet(vert_type, Set.doubleToFloat(pressures), n_levels,
+                           (CoordinateSystem) null, new Unit[] {vert_unit}, 
+                           (ErrorEstimate[]) null);
+        break;
+      default:
+         throw new VisADException("vert_sys unknown");
+    }
+  }
+
+  /** create a unique RealType for the specified name and unit */
+  private RealType makeRealType(String name, Unit unit) 
+      throws VisADException {
+    RealType rt = null;
+    rt = RealType.getRealType(name, unit);
+    if (rt == null) {
+      rt = RealType.getRealType(name+"_"+counter++, unit);
+      if (rt == null) {
+        throw new VisADException(
+          "Unable to create a unique RealType named " + name + 
+          " with unit " + unit);
+      }
+    }
+    return rt;
+  }
+
+  /**
+   * Vis5DVerticalCoordinateSystem is the VisAD class for coordinate
+   * systems for transforming pressure in millibars to Altitude
+   * in m.  It uses the standard Vis5D climate formulas:
+   * <pre>
+   *         P = 1012.5 * e^( H / -7.2 )        (^ denotes exponentiation)
+   * 
+   *         H = -7.2 * Ln( P / 1012.5 )        (Ln denotes natural log)
+   * </pre>
+   * for the transformations (in this case H is in km).
+   * <P>
+   */
+  
+  public static class Vis5DVerticalCoordinateSystem extends CoordinateSystem
+  {
+  
+    /** Default scale value for logarithmic vertical coordinate system */
+    private static final double DEFAULT_LOG_SCALE = 1012.5;
+  
+    /** Default exponent value for logarithmic vertical coordinate system */
+    private static final double DEFAULT_LOG_EXP = -7.2;
+  
+    private static Unit[] csUnits;
+  
+    static {
+      try {
+         csUnits = new Unit[] {Parser.parse("mbar")};
+      }
+      catch (ParseException pe) {;} // can't happen?
+    }
+  
+    /**
+     * Construct a new vertical transformation system
+     */
+    public Vis5DVerticalCoordinateSystem()
+         throws VisADException
+    {
+      super( new RealTupleType(RealType.Altitude), csUnits );
+    }
+  
+    /**
+     * Converts pressures in millibars to altitude in meters.
+     * @param  pressures  array of pressures
+     * @return array of corresponding altitudes
+     * @throws VisADException  illegal input
+     */
+    public double[][] toReference(double[][] pressures)
+           throws VisADException
+    {
+      int length = pressures[0].length;
+      double[][] alts = new double[1][length];
+  
+      for (int kk = 0; kk < length; kk++) {
+        alts[0][kk] = pressureToAltitude(pressures[0][kk]);
+      }
+      return alts;
+    }
+  
+    /**
+     * Converts altitudes in m to pressure in millibars.
+     * @param  alts  array of altitudes
+     * @return array of corresponding pressures
+     * @throws VisADException  illegal input
+     */
+    public double[][] fromReference(double[][] alts)
+           throws VisADException
+    {
+      int length = alts[0].length;
+      double[][] pressures = new double[1][length];
+  
+      for (int kk = 0; kk < length; kk++) {
+        pressures[0][kk] = altitudeToPressure(alts[0][kk]);
+      }
+      return pressures;
+    }
+  
+    /**
+     * Converts pressures in millibars to altitude in meters.
+     * @param  pressures  array of pressures
+     * @return array of corresponding altitudes
+     * @throws VisADException  illegal input
+     */
+    public float[][] toReference(float[][] pressures)
+           throws VisADException
+    {
+      int length = pressures[0].length;
+      float[][] alts = new float[1][length];
+  
+      for (int kk = 0; kk < length; kk++) {
+        alts[0][kk] = (float) pressureToAltitude(pressures[0][kk]);
+      }
+      return alts;
+    }
+  
+    /**
+     * Converts altitudes in m to pressure in millibars.
+     * @param  alts  array of altitudes
+     * @return array of corresponding pressures
+     * @throws VisADException  illegal input
+     */
+    public float[][] fromReference(float[][] alts)
+           throws VisADException
+    {
+      int length = alts[0].length;
+      float[][] pressures = new float[1][length];
+  
+      for (int kk = 0; kk < length; kk++) {
+        pressures[0][kk] = (float) altitudeToPressure(alts[0][kk]);
+      }
+      return pressures;
+    }
+  
+    /** 
+     * Checks the equality of o against this coordinate system
+     * @param o object in question
+     * @return true if o is a Vis5DVerticalCoordinateSystem
+     */
+    public boolean equals(Object o) {
+      return (o instanceof Vis5DVerticalCoordinateSystem);
+    }
+
+    /**
+     * Converts an altitude value in meters to a pressure value in
+     * millibars. It uses the standard Vis5D climate formula:
+     * <pre>
+     *         P = 1012.5 * e^( H / -7.2 )     (^ denotes exponentiation)
+     *
+     * (H is in km in this formula, but input value is meters)
+     * </pre>
+     * @param  alt	 value to convert
+     * @return  corresponding pressure value
+     */
+    public static double altitudeToPressure(double alt) {
+      return (DEFAULT_LOG_SCALE * Math.exp((alt/1000.) / DEFAULT_LOG_EXP));
+    }
+  
+    /**
+     * Converts a pressure value in millibars to an altitude in
+     * meters. It uses the standard Vis5D climate formula:
+     * <pre>
+     *         H = -7.2 * Ln( P / 1012.5 )        (Ln denotes natural log)
+     *
+     * (H is in km in this formula, but returned value is meters)
+     * </pre>
+     * @param  pressure value to convert
+     * @return  corresponding altitude value
+     */
+    public static double pressureToAltitude(double pressure) {
+      return (DEFAULT_LOG_EXP * 
+                   Math.log( pressure / DEFAULT_LOG_SCALE)) * 1000.;
+    }
+  }
+}
diff --git a/visad/data/vis5d/package.html b/visad/data/vis5d/package.html
new file mode 100644
index 0000000..203e76f
--- /dev/null
+++ b/visad/data/vis5d/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides for importing a Vis5D dataset into VisAD.
+
+</body>
+</html>
+
diff --git a/visad/data/visad/BinaryFile.java b/visad/data/visad/BinaryFile.java
new file mode 100644
index 0000000..7d2b963
--- /dev/null
+++ b/visad/data/visad/BinaryFile.java
@@ -0,0 +1,166 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+/**
+ * Constant values used by both
+ * {@link visad.data.visad.BinaryReader BinaryReader}
+ * and
+ * {@link visad.data.visad.BinaryWriter BinaryWriter}<br>
+ * <br>
+ * <tt>MAGIC_STR</tt> and <tt>FORMAT_VERSION</tt> are used
+ * to mark the file as a VisAD binary file.<br>
+ * <tt>OBJ_</tt> constants indicate the type of the next
+ * object in the file.<br>
+ * <tt>FLD_</tt> constants indicate the type of the next
+ * field for the current object in the file.<br>
+ * <tt>MATH_</tt> constants indicate the type of <tt>FLD_MATH</tt>
+ * objects.<br>
+ * <tt>DATA_</tt> constants indicate the type of <tt>FLD_DATA</tt>
+ * objects.
+ */
+public interface BinaryFile
+{
+  String MAGIC_STR = "VisADBin";
+  int FORMAT_VERSION = 1;
+
+  byte OBJ_COORDSYS = 1;
+  byte OBJ_DATA = 2;
+  byte OBJ_DATA_SERIAL = 3;
+  byte OBJ_ERROR = 4;
+  byte OBJ_MATH = 5;
+  byte OBJ_MATH_SERIAL = 6;
+  byte OBJ_UNIT = 7;
+
+  byte FLD_FIRSTS = 1;
+  byte FLD_LASTS = 2;
+  byte FLD_LENGTHS = 3;
+  byte FLD_FLOAT_LIST = 4;
+  byte FLD_SAMPLE = 5;
+  byte FLD_FLOAT_SAMPLES = 6;
+  byte FLD_DOUBLE_SAMPLES = 7;
+  byte FLD_DATA_SAMPLES = 8;
+  byte FLD_REAL_SAMPLES = 9;
+  byte FLD_TRIVIAL_SAMPLES = 10;
+  byte FLD_SET_SAMPLES = 11;
+  byte FLD_SET = 12;
+  byte FLD_LINEAR_SETS = 13;
+  byte FLD_INTEGER_SETS = 14;
+  byte FLD_SET_LIST = 15;
+
+  byte FLD_COORDSYS_SERIAL = 20;
+  byte FLD_DELAUNAY_SERIAL = 21;
+
+  byte FLD_INDEX_UNIT = 30;
+  byte FLD_INDEX_ERROR = 31;
+  byte FLD_INDEX_COORDSYS = 32;
+
+  byte FLD_INDEX_UNITS = 40;
+  byte FLD_INDEX_ERRORS = 41;
+
+  byte FLD_RANGE_COORDSYSES = 50;
+
+  byte FLD_DELAUNAY = 60;
+  byte FLD_DELAUNAY_TRI = 61;
+  byte FLD_DELAUNAY_VERTICES = 62;
+  byte FLD_DELAUNAY_WALK = 63;
+  byte FLD_DELAUNAY_EDGES = 64;
+  byte FLD_DELAUNAY_NUM_EDGES = 65;
+
+  byte FLD_SET_FOLLOWS_TYPE = 70;
+
+  byte FLD_END = 80;
+
+  byte MATH_FUNCTION = 1;
+  byte MATH_REAL = 2;
+  byte MATH_REAL_TUPLE = 3;
+  byte MATH_SET = 4;
+  byte MATH_TEXT = 5;
+  byte MATH_TUPLE = 6;
+  byte MATH_QUANTITY = 7;
+  // byte MATH_DISPLAY_TUPLE = 8;
+  // byte MATH_REAL_VECTOR = 9;
+  // byte MATH_EARTH_VECTOR = 10;
+  // byte MATH_GRID_VECTOR = 11;
+  // byte MATH_DISPLAY_REAL = 12;
+
+  // byte DATA_SCALAR = 1;
+  byte DATA_TEXT = 2;
+  byte DATA_REAL = 3;
+
+  byte DATA_TUPLE = 10;
+  byte DATA_REAL_TUPLE = 11;
+
+  byte DATA_FIELD = 20;
+  byte DATA_FLAT_FIELD = 21;
+
+  // byte DATA_SET = 30;
+  // byte DATA_SIMPLE_SET = 31;
+  byte DATA_DOUBLE_SET = 32;
+  byte DATA_FLOAT_SET = 33;
+  byte DATA_LIST1D_SET = 34;
+  // byte DATA_SAMPLED_SET = 35;
+  byte DATA_SINGLETON_SET = 36;
+  byte DATA_UNION_SET = 37;
+  byte DATA_PRODUCT_SET = 38;
+  byte DATA_IRREGULAR_SET = 39;
+  byte DATA_IRREGULAR_1D_SET = 40;
+  byte DATA_IRREGULAR_2D_SET = 41;
+  byte DATA_IRREGULAR_3D_SET = 42;
+  byte DATA_GRIDDED_SET = 43;
+  byte DATA_GRIDDED_1D_SET = 44;
+  byte DATA_GRIDDED_2D_SET = 45;
+  byte DATA_GRIDDED_3D_SET = 46;
+  byte DATA_GRIDDED_1D_DOUBLE_SET = 47;
+  byte DATA_GRIDDED_2D_DOUBLE_SET = 48;
+  byte DATA_GRIDDED_3D_DOUBLE_SET = 49;
+  byte DATA_LINEAR_1D_SET = 50;
+  byte DATA_LINEAR_2D_SET = 51;
+  byte DATA_LINEAR_3D_SET = 52;
+  byte DATA_LINEAR_ND_SET = 53;
+  byte DATA_LINEAR_LATLON_SET = 54;
+  byte DATA_INTEGER_1D_SET = 55;
+  byte DATA_INTEGER_2D_SET = 56;
+  byte DATA_INTEGER_3D_SET = 57;
+  byte DATA_INTEGER_ND_SET = 58;
+
+  byte DATA_NONE = 60;
+
+  boolean DEBUG_RD_CSYS = false;
+  boolean DEBUG_RD_DATA = false;
+  boolean DEBUG_RD_DATA_DETAIL = false;
+  boolean DEBUG_RD_ERRE = false;
+  boolean DEBUG_RD_MATH = false;
+  boolean DEBUG_RD_STR = false;
+  boolean DEBUG_RD_TIME = false;
+  boolean DEBUG_RD_UNIT = false;
+
+  boolean DEBUG_WR_CSYS = false;
+  boolean DEBUG_WR_DATA = false;
+  boolean DEBUG_WR_DATA_DETAIL = false;
+  boolean DEBUG_WR_ERRE = false;
+  boolean DEBUG_WR_MATH = false;
+  boolean DEBUG_WR_STR = false;
+  boolean DEBUG_WR_TIME = false;
+  boolean DEBUG_WR_UNIT = false;
+}
diff --git a/visad/data/visad/BinaryObjectCache.java b/visad/data/visad/BinaryObjectCache.java
new file mode 100644
index 0000000..538c1ca
--- /dev/null
+++ b/visad/data/visad/BinaryObjectCache.java
@@ -0,0 +1,144 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import java.util.ArrayList;
+
+/**
+ * A list which allows objects to be added at a specific index,
+ * padding with <tt>null</tt>s if necessary.
+ */
+public class BinaryObjectCache
+{
+  private ArrayList cache = null;
+
+  /**
+   * Create an empty list.
+   */
+  public BinaryObjectCache() { }
+
+  /**
+   * Add an object at the specified index.
+   *
+   * @param obj Object to be added.
+   *
+   * @return <tt>-1</tt> if the object is <tt>null</tt>, or
+   *         the index at which the object was added.
+   */
+  public int add(Object obj)
+  {
+    return add(-1, obj);
+  }
+
+  /**
+   * Add an object to the end of the list.
+   * If the index is less than zero, the object will be
+   * added to the end of the list.
+   *
+   * @param obj Object to be added.
+   *
+   * @return <tt>-1</tt> if the object is <tt>null</tt>, or
+   *         the index at which the object was added.
+   */
+  public int add(int index, Object obj)
+  {
+    // don't bother adding null objects
+    if (obj == null) {
+      return -1;
+    }
+
+    // build a new list if necessary
+    if (cache == null) {
+      cache = new ArrayList();
+    }
+
+    final int cacheLen = cache.size();
+    if (index < 0 || index == cacheLen) {
+      // add to end of list, then find out where it was added
+      cache.add(obj);
+      index = cache.lastIndexOf(obj);
+    } else if (index < cacheLen) {
+      // overwrite the current entry
+      cache.set(index, obj);
+    } else {
+      // pad with nulls
+      for (int i = cacheLen; i < index; i++) {
+        cache.add(null);
+      }
+
+      // add to end of list
+      cache.add(obj);
+    }
+
+    return index;
+  }
+
+  /**
+   * Return the object found at the specified index.
+   *
+   * @param index Object index.
+   *
+   * @return The requested object.
+   * @exception IndexOutOfBoundsException If the index is outside the
+   *                                      list bounds.
+   */
+  public Object get(int index)
+    throws IndexOutOfBoundsException
+  {
+    // if they're asking for an invalid index, don't give 'em anything
+    if (index < 0) {
+      throw new IndexOutOfBoundsException("Negative index");
+    }
+
+    // if there's no list, there's nothing to return
+    if (cache == null) {
+      throw new IndexOutOfBoundsException("No entries in cache");
+    }
+
+    return cache.get(index);
+  }
+
+  /**
+   * Return the index of the specified object.
+   *
+   * @param obj Object to find in the list,
+   *
+   * @return <tt>-1</tt> if the object was not found,
+   *         or the index of the object.
+   */
+  public int getIndex(Object obj)
+  {
+    // don't bother looking for null objects
+    if (obj == null) {
+      return -1;
+    }
+
+    // if there's no list, there's nothing to find
+    if (cache == null) {
+      return -1;
+    }
+
+    // return index (if any)
+    return cache.indexOf(obj);
+  }
+}
diff --git a/visad/data/visad/BinaryReader.java b/visad/data/visad/BinaryReader.java
new file mode 100644
index 0000000..049c178
--- /dev/null
+++ b/visad/data/visad/BinaryReader.java
@@ -0,0 +1,571 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import visad.*;
+
+import visad.data.visad.object.*;
+
+/**
+ * Read a {@link visad.Data Data} object in VisAD's binary format.
+ *
+ * @see <a href="http://www.ssec.wisc.edu/~dglo/binary_file_format.html">Binary File Format Spec</a>
+ */
+public class BinaryReader
+  implements BinaryFile
+{
+  private DataInput file;
+  private boolean isRandom;
+
+  private int version;
+
+  private BinaryObjectCache unitCache, errorCache, cSysCache, typeCache;
+
+  /**
+   * Open the named file.
+   * <br><br>
+   * The first few bytes will be read to verify that the file starts
+   * with the appropriate <tt>MAGIC_STR</tt> characters and that this
+   * class can read the format version used by the file.
+   *
+   * @param name Name of file to be read.
+   *
+   * @exception IOException If the file cannot be opened.
+   */
+  public BinaryReader(String name)
+    throws IOException
+  {
+    this(new File(name));
+  }
+
+  /**
+   * Open the referenced file.
+   * <br><br>
+   * The first few bytes will be read to verify that the file starts
+   * with the appropriate <tt>MAGIC_STR</tt> characters and that this
+   * class can read the format version used by the file.
+   *
+   * @param ref File to be read.
+   *
+   * @exception IOException If the file cannot be opened.
+   */
+  public BinaryReader(File ref)
+    throws IOException
+  {
+    this(new FileInputStream(ref));
+  }
+
+  /**
+   * Prepare to read a binary object from the specified stream.
+   * <br><br>
+   * The first few bytes will be read to verify that the stream starts
+   * with the appropriate <tt>MAGIC_STR</tt> characters and that this
+   * class can read the format version used by the file.
+   *
+   * @param stream Stream to read.
+   *
+   * @exception IOException If the file cannot be opened.
+   */
+  public BinaryReader(InputStream stream)
+    throws IOException
+  {
+    file = new DataInputStream(new BufferedInputStream(stream));
+    isRandom = false;
+
+    version = checkMagic(file);
+
+    unitCache = new BinaryObjectCache();
+    errorCache = new BinaryObjectCache();
+    cSysCache = new BinaryObjectCache();
+    typeCache = new BinaryObjectCache();
+  }
+
+  /**
+   * Prepare to read a binary object from the specified stream.
+   * <br><br>
+   * The first few bytes will be read to verify that the stream starts
+   * with the appropriate <tt>MAGIC_STR</tt> characters and that this
+   * class can read the format version used by the file.
+   *
+   * @param raf File to read.
+   *
+   * @exception IOException If the file cannot be opened.
+   */
+  public BinaryReader(java.io.RandomAccessFile raf)
+    throws IOException
+  {
+    file = raf;
+    isRandom = true;
+
+    version = checkMagic(file);
+
+    unitCache = new BinaryObjectCache();
+    errorCache = new BinaryObjectCache();
+    cSysCache = new BinaryObjectCache();
+    typeCache = new BinaryObjectCache();
+  }
+
+  /**
+   * Prepare to read a binary object from the specified stream.
+   * <br><br>
+   * The first few bytes will be read to verify that the stream starts
+   * with the appropriate <tt>MAGIC_STR</tt> characters and that this
+   * class can read the format version used by the file.
+   *
+   * @param raf File to read.
+   *
+   * @exception IOException If the file cannot be opened.
+   */
+  public BinaryReader(ucar.netcdf.RandomAccessFile raf)
+    throws IOException
+  {
+    file = raf;
+    isRandom = true;
+
+    version = checkMagic(file);
+
+    unitCache = new BinaryObjectCache();
+    errorCache = new BinaryObjectCache();
+    cSysCache = new BinaryObjectCache();
+    typeCache = new BinaryObjectCache();
+  }
+
+  private int checkMagic(DataInput file)
+    throws IOException
+  {
+
+    final int version = readMagic(file);
+    if (version < 1) {
+      throw new IOException("File is not in VisAD binary format");
+    }
+
+    // validate the format version number
+    if (version > FORMAT_VERSION) {
+      throw new IOException("Don't understand VisAD Binary format version " +
+                            version);
+    }
+
+    return version;
+  }
+
+  public void close()
+    throws IOException
+  {
+    if (file instanceof InputStream) {
+      ((InputStream )file).close();
+    } else if (file instanceof java.io.RandomAccessFile) {
+      ((java.io.RandomAccessFile )file).close();
+    } else if (file instanceof ucar.netcdf.RandomAccessFile) {
+      ((ucar.netcdf.RandomAccessFile )file).close();
+    } else {
+      throw new IOException("Unknown file class \"" +
+                            file.getClass().getName() + "\"");
+    }
+  }
+
+  public DataImpl getData()
+    throws IOException, VisADException
+  {
+long totStart, csTime, dTime, dsTime, eTime, mTime, uTime;
+totStart = csTime = dTime = dsTime = eTime = mTime = uTime = 0;
+
+totStart = System.currentTimeMillis();
+    DataImpl data = null;
+    while (data == null) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        break;
+      }
+
+long tmpStart = System.currentTimeMillis();
+      switch (directive) {
+      case OBJ_COORDSYS:
+if(DEBUG_RD_MATH)System.err.println("getData: OBJ_COORDSYS (" + OBJ_COORDSYS + ")");
+        BinaryCoordinateSystem.read(this);
+if(DEBUG_RD_TIME)csTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case OBJ_DATA:
+if(DEBUG_RD_MATH)System.err.println("getData: OBJ_DATA (" + OBJ_DATA + ")");
+        data = readData();
+if(DEBUG_RD_TIME)dTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case OBJ_DATA_SERIAL:
+if(DEBUG_RD_MATH)System.err.println("getData: OBJ_DATA_SERIAL (" + OBJ_DATA_SERIAL + ")");
+        data = (DataImpl )BinarySerializedObject.read(file);
+if(DEBUG_RD_TIME)dsTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case OBJ_ERROR:
+if(DEBUG_RD_MATH)System.err.println("getData: OBJ_ERROR (" + OBJ_ERROR + ")");
+        BinaryErrorEstimate.read(this);
+if(DEBUG_RD_TIME)eTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case OBJ_MATH:
+if(DEBUG_RD_MATH)System.err.println("getData: OBJ_MATH (" + OBJ_MATH + ")");
+        BinaryMathType.read(this);
+if(DEBUG_RD_TIME)mTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case OBJ_UNIT:
+if(DEBUG_RD_MATH)System.err.println("getData: OBJ_UNIT (" + OBJ_UNIT + ")");
+        BinaryUnit.read(this);
+if(DEBUG_RD_TIME)uTime += System.currentTimeMillis() - tmpStart;
+        break;
+      default:
+        throw new IOException("Unknown directive " + directive);
+      }
+    }
+
+if(DEBUG_RD_TIME){
+  long totTime = System.currentTimeMillis() - totStart;
+  if (totTime > 0 && totTime != dTime) {
+    System.err.print("gD: tot "+totTime);
+    if (csTime > 0) System.err.print(" cs "+csTime);
+    if (dTime > 0) System.err.print(" d "+dTime);
+    if (dsTime > 0) System.err.print(" ds "+dsTime);
+    if (eTime > 0) System.err.print(" e "+eTime);
+    if (mTime > 0) System.err.print(" m "+mTime);
+    if (uTime > 0) System.err.print(" u "+uTime);
+    System.err.println();
+  }
+}
+    return data;
+  }
+
+  public final BinaryObjectCache getCoordinateSystemCache()
+  {
+    return cSysCache;
+  }
+
+  public final BinaryObjectCache getErrorEstimateCache() { return errorCache; }
+
+  public final long getFilePointer()
+    throws IOException
+  {
+    if (file instanceof java.io.RandomAccessFile) {
+      return ((java.io.RandomAccessFile )file).getFilePointer();
+    } else if (file instanceof ucar.netcdf.RandomAccessFile) {
+      return ((ucar.netcdf.RandomAccessFile )file).getFilePointer();
+    }
+
+    return -1;
+  }
+
+  public final DataInput getInput() { return file; }
+  public final BinaryObjectCache getTypeCache() { return typeCache; }
+  public final BinaryObjectCache getUnitCache() { return unitCache; }
+
+  public static boolean isMagic(byte[] block)
+  {
+    DataInputStream dis;
+    java.io.ByteArrayInputStream bs;
+    bs = new java.io.ByteArrayInputStream(block);
+    dis = new java.io.DataInputStream(bs);
+    try {
+      return (readMagic(dis) <= FORMAT_VERSION);
+    } catch (IOException ioe) {
+      return false;
+    }
+  }
+
+  public final boolean isRandom() { return isRandom; }
+
+  public DataImpl readData()
+    throws IOException, VisADException
+  {
+long totStart, dsTime, fTime, ffTime, fsTime;
+long g1dsTime, g2dsTime, g3dsTime, gsTime, g1sTime, g2sTime, g3sTime;
+long i1sTime, i2sTime, i3sTime, iNsTime;
+long ir1sTime, ir2sTime, ir3sTime, irsTime;
+long l1sTime, l2sTime, l3sTime, lNsTime, llsTime;
+long liTime, psTime, rTime, rtTime, ssTime, tTime, tuTime, usTime;
+
+totStart = dsTime = fTime = ffTime = fsTime = 0;
+g1dsTime = g2dsTime = g3dsTime = gsTime = g1sTime = g2sTime = g3sTime = 0;
+i1sTime = i2sTime = i3sTime = iNsTime = 0;
+ir1sTime = ir2sTime = ir3sTime = irsTime = 0;
+l1sTime = l2sTime = l3sTime = lNsTime = llsTime = 0;
+liTime = psTime = rTime = rtTime = ssTime = tTime = tuTime = usTime = 0;
+
+totStart = System.currentTimeMillis();
+    final int objLen = file.readInt();
+    final byte dataType = file.readByte();
+
+long tmpStart = System.currentTimeMillis();
+    DataImpl data;
+    switch (dataType) {
+    case DATA_DOUBLE_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_DOUBLE_SET (" + dataType + ")");
+      data = BinarySimpleSet.read(this, dataType);
+if(DEBUG_RD_TIME)dsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_FIELD:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_FIELD (" + dataType + ")");
+      data = BinaryFieldImpl.read(this);
+if(DEBUG_RD_TIME)fTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_FLAT_FIELD:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_FLAT_FIELD (" + dataType + ")");
+      data = BinaryFlatField.read(this, objLen - 6, isRandom());
+if(DEBUG_RD_TIME)ffTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_FLOAT_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_FLOAT_SET (" + dataType + ")");
+      data = BinarySimpleSet.read(this, dataType);
+if(DEBUG_RD_TIME)fsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_GRIDDED_1D_DOUBLE_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_GRIDDED_1D_DOUBLE_SET (" + dataType + ")");
+      data = BinaryGriddedDoubleSet.read(this, dataType);
+if(DEBUG_RD_TIME)g1dsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_GRIDDED_2D_DOUBLE_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_GRIDDED_2D_DOUBLE_SET (" + dataType + ")");
+      data = BinaryGriddedDoubleSet.read(this, dataType);
+if(DEBUG_RD_TIME)g2dsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_GRIDDED_3D_DOUBLE_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_GRIDDED_3D_DOUBLE_SET (" + dataType + ")");
+      data = BinaryGriddedDoubleSet.read(this, dataType);
+if(DEBUG_RD_TIME)g3dsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_GRIDDED_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_GRIDDED_SET (" + dataType + ")");
+      data = BinaryGriddedSet.read(this, dataType);
+if(DEBUG_RD_TIME)gsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_GRIDDED_1D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_GRIDDED_1D_SET (" + dataType + ")");
+      data = BinaryGriddedSet.read(this, dataType);
+if(DEBUG_RD_TIME)g1sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_GRIDDED_2D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_GRIDDED_2D_SET (" + dataType + ")");
+      data = BinaryGriddedSet.read(this, dataType);
+if(DEBUG_RD_TIME)g2sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_GRIDDED_3D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_GRIDDED_3D_SET (" + dataType + ")");
+      data = BinaryGriddedSet.read(this, dataType);
+if(DEBUG_RD_TIME)g3sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_INTEGER_1D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_INTEGER_1D_SET (" + dataType + ")");
+      data = BinaryIntegerSet.read(this, dataType);
+if(DEBUG_RD_TIME)i1sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_INTEGER_2D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_INTEGER_2D_SET (" + dataType + ")");
+      data = BinaryIntegerSet.read(this, dataType);
+if(DEBUG_RD_TIME)i2sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_INTEGER_3D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_INTEGER_3D_SET (" + dataType + ")");
+      data = BinaryIntegerSet.read(this, dataType);
+if(DEBUG_RD_TIME)i3sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_INTEGER_ND_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_INTEGER_ND_SET (" + dataType + ")");
+      data = BinaryIntegerSet.read(this, dataType);
+if(DEBUG_RD_TIME)iNsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_IRREGULAR_1D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_IRREGULAR_1D_SET (" + dataType + ")");
+      data = BinaryIrregularSet.read(this, dataType);
+if(DEBUG_RD_TIME)ir1sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_IRREGULAR_2D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_IRREGULAR_2D_SET (" + dataType + ")");
+      data = BinaryIrregularSet.read(this, dataType);
+if(DEBUG_RD_TIME)ir2sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_IRREGULAR_3D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_IRREGULAR_3D_SET (" + dataType + ")");
+      data = BinaryIrregularSet.read(this, dataType);
+if(DEBUG_RD_TIME)ir3sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_IRREGULAR_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_IRREGULAR_SET (" + dataType + ")");
+      data = BinaryIrregularSet.read(this, dataType);
+if(DEBUG_RD_TIME)irsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_LINEAR_1D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_LINEAR_1D_SET (" + dataType + ")");
+      data = BinaryLinearSet.read(this, dataType);
+if(DEBUG_RD_TIME)l1sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_LINEAR_2D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_LINEAR_2D_SET (" + dataType + ")");
+      data = BinaryLinearSet.read(this, dataType);
+if(DEBUG_RD_TIME)l2sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_LINEAR_3D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_LINEAR_3D_SET (" + dataType + ")");
+      data = BinaryLinearSet.read(this, dataType);
+if(DEBUG_RD_TIME)l3sTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_LINEAR_ND_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_LINEAR_ND_SET (" + dataType + ")");
+      data = BinaryLinearSet.read(this, dataType);
+if(DEBUG_RD_TIME)lNsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_LINEAR_LATLON_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_LINEAR_LATLON_SET (" + dataType + ")");
+      data = BinaryLinearSet.read(this, dataType);
+if(DEBUG_RD_TIME)llsTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_LIST1D_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_LIST1D_SET (" + dataType + ")");
+      data = BinaryList1DSet.read(this);
+if(DEBUG_RD_TIME)liTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_NONE:
+      data = null;
+      break;
+    case DATA_PRODUCT_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_PRODUCT_SET (" + dataType + ")");
+      data = BinaryProductSet.read(this);
+if(DEBUG_RD_TIME)psTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_REAL:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_REAL (" + dataType + ")");
+      data = BinaryReal.read(this);
+if(DEBUG_RD_TIME)rTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_REAL_TUPLE:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_REAL_TUPLE (" + dataType + ")");
+      data = BinaryRealTuple.read(this);
+if(DEBUG_RD_TIME)rtTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_SINGLETON_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_SINGLETON_SET (" + dataType + ")");
+      data = BinarySingletonSet.read(this);
+if(DEBUG_RD_TIME)ssTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_TEXT:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_TEXT (" + dataType + ")");
+      data = BinaryText.read(this);
+if(DEBUG_RD_TIME)tTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_TUPLE:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_TUPLE (" + dataType + ")");
+      data = BinaryTuple.read(this);
+if(DEBUG_RD_TIME)tuTime += System.currentTimeMillis() - tmpStart;
+      break;
+    case DATA_UNION_SET:
+if(DEBUG_RD_DATA)System.err.println("rdData: objLen (" + objLen + ")\nrdData: DATA_UNION_SET (" + dataType + ")");
+      data = BinaryUnionSet.read(this);
+if(DEBUG_RD_TIME)usTime += System.currentTimeMillis() - tmpStart;
+      break;
+    default:
+      throw new IOException("Unknown Data type " + dataType);
+    }
+
+if(DEBUG_RD_TIME){
+  long totTime = System.currentTimeMillis() - totStart;
+  if (totTime > 0) {
+    System.err.print("rD: tot "+totTime);
+    if (dsTime > 0) System.err.print(" ds "+dsTime);
+    if (fTime > 0) System.err.print(" f "+fTime);
+    if (ffTime > 0) System.err.print(" ff "+ffTime);
+    if (fsTime > 0) System.err.print(" fs "+fsTime);
+    if (g1dsTime > 0) System.err.print(" g1ds "+g1dsTime);
+    if (g2dsTime > 0) System.err.print(" g2ds "+g2dsTime);
+    if (g3dsTime > 0) System.err.print(" g3ds "+g3dsTime);
+    if (gsTime > 0) System.err.print(" gs "+gsTime);
+    if (g1sTime > 0) System.err.print(" g1s "+g1sTime);
+    if (g2sTime > 0) System.err.print(" g2s "+g2sTime);
+    if (g3sTime > 0) System.err.print(" g3s "+g3sTime);
+    if (i1sTime > 0) System.err.print(" i1s "+i1sTime);
+    if (i2sTime > 0) System.err.print(" i2s "+i2sTime);
+    if (i3sTime > 0) System.err.print(" i3s "+i3sTime);
+    if (iNsTime > 0) System.err.print(" iNs "+iNsTime);
+    if (ir1sTime > 0) System.err.print(" ir1s "+ir1sTime);
+    if (ir2sTime > 0) System.err.print(" ir2s "+ir2sTime);
+    if (ir3sTime > 0) System.err.print(" ir3s "+ir3sTime);
+    if (irsTime > 0) System.err.print(" irs "+irsTime);
+    if (l1sTime > 0) System.err.print(" l1s "+l1sTime);
+    if (l2sTime > 0) System.err.print(" l2s "+l2sTime);
+    if (l3sTime > 0) System.err.print(" l3s "+l3sTime);
+    if (lNsTime > 0) System.err.print(" lNs "+lNsTime);
+    if (llsTime > 0) System.err.print(" lls "+llsTime);
+    if (liTime > 0) System.err.print(" li "+liTime);
+    if (psTime > 0) System.err.print(" ps "+psTime);
+    if (rTime > 0) System.err.print(" r "+rTime);
+    if (rtTime > 0) System.err.print(" rt "+rtTime);
+    if (ssTime > 0) System.err.print(" ss "+ssTime);
+    if (tTime > 0) System.err.print(" t "+tTime);
+    if (tuTime > 0) System.err.print(" tu "+tuTime);
+    if (usTime > 0) System.err.print(" us "+usTime);
+    System.err.println();
+  }
+}
+    return data;
+  }
+
+  private final static int readMagic(DataInput stream)
+    throws IOException
+  {
+    byte[] magic = MAGIC_STR.getBytes();
+
+    // try to read magic chars from beginning of file
+    try {
+      for (int i = 0; i < magic.length; i++) {
+        if (stream.readByte() != magic[i]) {
+          return -1;
+        }
+      }
+    } catch (IOException ioe) {
+      return -1;
+    }
+
+    // read the format version number
+    try {
+      return stream.readInt();
+    } catch (IOException ioe) {
+      return -1;
+    }
+  }
+
+  public final void seek(long pos)
+    throws IOException
+  {
+    if (file instanceof java.io.RandomAccessFile) {
+      ((java.io.RandomAccessFile )file).seek(pos);
+    } else if (file instanceof ucar.netcdf.RandomAccessFile) {
+      ((ucar.netcdf.RandomAccessFile )file).seek(pos);
+    } else {
+      throw new IOException("Seek not supported for " +
+                            file.getClass().getName());
+    }
+  }
+}
diff --git a/visad/data/visad/BinarySizer.java b/visad/data/visad/BinarySizer.java
new file mode 100644
index 0000000..cf60652
--- /dev/null
+++ b/visad/data/visad/BinarySizer.java
@@ -0,0 +1,451 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import visad.*;
+
+import visad.data.BaseDataProcessor;
+
+import visad.data.visad.object.*;
+
+public class BinarySizer
+  extends BaseDataProcessor
+{
+  private BinarySize size;
+
+  public BinarySizer()
+  {
+    size = new BinarySize();
+  }
+
+  public final int getSize() { return size.get(); }
+
+  public void processDoubleSet(SetType type, CoordinateSystem cs,
+                               Unit[] units, DoubleSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinarySimpleSet.computeBytes(cs, units);
+    size.add(bytes);
+  }
+
+  public void processFieldImpl(FunctionType type, Set set, FieldImpl fld,
+                               Object token)
+    throws VisADException
+  {
+    int bytes = BinaryFieldImpl.computeBytes(fld);
+    size.add(bytes);
+  }
+
+  public void processFlatField(FunctionType type, Set domainSet,
+                               CoordinateSystem cs,
+                               CoordinateSystem[] rangeCS, Set[] rangeSets,
+                               Unit[] units, FlatField fld, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryFlatField.computeBytes(domainSet, cs, rangeCS,
+                                             rangeSets, units, fld);
+    size.add(bytes);
+  }
+
+  public void processFloatSet(SetType type, CoordinateSystem cs,
+                              Unit[] units, FloatSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinarySimpleSet.computeBytes(cs, units);
+    size.add(bytes);
+  }
+
+  public void processGridded1DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded1DDoubleSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryGriddedDoubleSet.computeBytes(samples, lengths, cs,
+                                                    units, errors);
+    size.add(bytes);
+  }
+
+  public void processGridded2DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded2DDoubleSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryGriddedDoubleSet.computeBytes(samples, lengths, cs,
+                                                    units, errors);
+    size.add(bytes);
+  }
+
+  public void processGridded3DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded3DDoubleSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryGriddedDoubleSet.computeBytes(samples, lengths, cs, units,
+                                                    errors);
+    size.add(bytes);
+  }
+
+  public void processGridded1DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded1DSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryGriddedSet.computeBytes(samples, lengths, cs, units,
+                                              errors);
+    size.add(bytes);
+  }
+
+  public void processGridded2DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded2DSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryGriddedSet.computeBytes(samples, lengths, cs, units,
+                                              errors);
+    size.add(bytes);
+  }
+
+  public void processGridded3DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded3DSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryGriddedSet.computeBytes(samples, lengths, cs, units,
+                                              errors);
+    size.add(bytes);
+  }
+
+  public void processGriddedSet(SetType type, float[][] samples,
+                                int[] lengths, CoordinateSystem cs,
+                                Unit[] units, ErrorEstimate[] errors,
+                                GriddedSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryGriddedSet.computeBytes(samples, lengths, cs, units,
+                                              errors);
+    size.add(bytes);
+  }
+
+  public void processInteger1DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer1DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    int bytes = BinaryIntegerSet.computeBytes(true, lengths, null, cs, units,
+                                              errors);
+    size.add(bytes);
+  }
+
+  public void processInteger2DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer2DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    Integer1DSet[] comps = BinaryIntegerSet.getComponents(set);
+
+    boolean matchedTypes = BinaryIntegerSet.hasMatchedTypes(type, comps);
+
+    int bytes = BinaryIntegerSet.computeBytes(matchedTypes, lengths, comps,
+                                              cs, units, errors);
+    size.add(bytes);
+  }
+
+  public void processInteger3DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer3DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    Integer1DSet[] comps = BinaryIntegerSet.getComponents(set);
+
+    boolean matchedTypes = BinaryIntegerSet.hasMatchedTypes(type, comps);
+
+    int bytes = BinaryIntegerSet.computeBytes(matchedTypes, lengths, comps,
+                                              cs, units, errors);
+    size.add(bytes);
+  }
+
+  public void processIntegerNDSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, IntegerNDSet set,
+                                  Object token)
+    throws VisADException
+  {
+    Integer1DSet[] comps = BinaryIntegerSet.getComponents(set);
+
+    boolean matchedTypes = BinaryIntegerSet.hasMatchedTypes(type, comps);
+
+    int bytes = BinaryIntegerSet.computeBytes(matchedTypes, lengths, comps,
+                                              cs, units, errors);
+    size.add(bytes);
+  }
+
+  public void processIrregular1DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors,
+                                    Irregular1DSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryIrregularSet.computeBytes(samples, cs, units, errors,
+                                                null);
+    size.add(bytes);
+  }
+
+  public void processIrregular2DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors, Delaunay delaunay,
+                                    Irregular2DSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryIrregularSet.computeBytes(samples, cs, units, errors,
+                                                delaunay);
+    size.add(bytes);
+  }
+
+  public void processIrregular3DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors, Delaunay delaunay,
+                                    Irregular3DSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryIrregularSet.computeBytes(samples, cs, units, errors,
+                                                delaunay);
+    size.add(bytes);
+  }
+
+  public void processIrregularSet(SetType type, float[][] samples,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Delaunay delaunay,
+                                  IrregularSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryIrregularSet.computeBytes(samples, cs, units, errors,
+                                                delaunay);
+    size.add(bytes);
+  }
+
+  public void processLinear1DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear1DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    int bytes = BinaryLinearSet.computeBytes(true, firsts, lasts, lengths,
+                                             null, cs, units, errors);
+    size.add(bytes);
+  }
+
+  public void processLinear2DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear2DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    Linear1DSet[] comps = new Linear1DSet[2];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    boolean matchedTypes = BinaryLinearSet.hasMatchedTypes(type, comps);
+
+    int bytes = BinaryLinearSet.computeBytes(matchedTypes, firsts, lasts,
+                                             lengths, comps, cs, units,
+                                             errors);
+    size.add(bytes);
+  }
+
+  public void processLinear3DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear3DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    Linear1DSet[] comps = new Linear1DSet[3];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    boolean matchedTypes = BinaryLinearSet.hasMatchedTypes(type, comps);
+
+    int bytes = BinaryLinearSet.computeBytes(matchedTypes, firsts, lasts,
+                                             lengths, comps, cs, units,
+                                             errors);
+    size.add(bytes);
+  }
+
+  public void processLinearLatLonSet(SetType type, double[] firsts,
+                                     double[] lasts, int[] lengths,
+                                     CoordinateSystem cs, Unit[] units,
+                                     ErrorEstimate[] errors,
+                                     LinearLatLonSet set, Object token)
+    throws VisADException
+  {
+    Linear1DSet[] comps = new Linear1DSet[2];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    boolean matchedTypes = BinaryLinearSet.hasMatchedTypes(type, comps);
+
+    int bytes = BinaryLinearSet.computeBytes(matchedTypes, firsts, lasts,
+                                             lengths, comps, cs, units,
+                                             errors);
+    size.add(bytes);
+  }
+
+  public void processLinearNDSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, LinearNDSet set,
+                                 Object token)
+    throws VisADException
+  {
+    Linear1DSet[] comps = new Linear1DSet[set.getDimension()];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    boolean matchedTypes = BinaryLinearSet.hasMatchedTypes(type, comps);
+
+    int bytes = BinaryLinearSet.computeBytes(matchedTypes, firsts, lasts,
+                                             lengths, comps, cs, units,
+                                             errors);
+    size.add(bytes);
+  }
+
+  public void processList1DSet(SetType type, float[] list,
+                               CoordinateSystem cs, Unit[] units,
+                               List1DSet set, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryList1DSet.computeBytes(list, cs, units);
+    size.add(bytes);
+  }
+
+  public void processProductSet(SetType type, SampledSet[] sets,
+                                CoordinateSystem cs, Unit[] units,
+                                ErrorEstimate[] errors, ProductSet set,
+                                Object token)
+    throws VisADException
+  {
+    int bytes = BinaryProductSet.computeBytes(sets, cs, units, errors);
+    size.add(bytes);
+  }
+
+  public void processReal(RealType type, double value, Unit unit,
+                          ErrorEstimate error, Real real, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryReal.computeBytes(unit, error);
+    size.add(bytes);
+  }
+
+  public void processRealTuple(RealTupleType type, Real[] components,
+                               CoordinateSystem cs, RealTuple rt,
+                               Object token)
+    throws VisADException
+  {
+    boolean trivialTuple = BinaryRealTuple.isTrivialTuple(type, components);
+
+    int bytes = BinaryRealTuple.computeBytes(components, cs, trivialTuple);
+    size.add(bytes);
+  }
+
+  public void processSampledSet(SetType st, int manifold_dimension,
+                                CoordinateSystem cs, Unit[] units,
+                                ErrorEstimate[] errors, SampledSet set,
+                                Object token)
+    throws VisADException
+  {
+    throw new UnimplementedException();
+  }
+
+  public void processSimpleSet(SetType st, int manifold_dimension,
+                               CoordinateSystem cs, Unit[] units,
+                               ErrorEstimate[] errors, SimpleSet set,
+                               Object token)
+    throws VisADException
+  {
+    int bytes = BinarySimpleSet.computeBytes(cs, units);
+    size.add(bytes);
+  }
+
+  public void processSingletonSet(RealTuple sample, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  SingletonSet set, Object token)
+    throws VisADException
+  {
+    RealTupleType sampleType = (RealTupleType )sample.getType();
+    Real[] sampleReals = BinarySingletonSet.getSampleReals(sample);
+
+    int bytes = BinarySingletonSet.computeBytes(sampleType, sampleReals, cs,
+                                                units, errors);
+    size.add(bytes);
+  }
+
+  public void processText(TextType type, String value, boolean missing,
+                          Text text, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryText.computeBytes(value);
+    size.add(bytes);
+  }
+
+  public void processTuple(TupleType type, Data[] components, Tuple t,
+                           Object token)
+    throws VisADException
+  {
+    int bytes = BinaryTuple.computeBytes(components);
+    size.add(bytes);
+  }
+
+  public void processUnionSet(SetType type, SampledSet[] sets, UnionSet set,
+                              Object token)
+    throws VisADException
+  {
+    int bytes = BinaryUnionSet.computeBytes(sets);
+    size.add(bytes);
+  }
+
+  public void processUnknownData(DataImpl data, Object token)
+    throws VisADException
+  {
+    int bytes = BinaryUnknown.computeBytes(data);
+    size.add(bytes);
+  }
+
+  public final void reset() { size.reset(); }
+
+  public final void setSize(int newSize) { size.set(newSize); }
+}
diff --git a/visad/data/visad/BinaryWriter.java b/visad/data/visad/BinaryWriter.java
new file mode 100644
index 0000000..4ff3296
--- /dev/null
+++ b/visad/data/visad/BinaryWriter.java
@@ -0,0 +1,1002 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import java.io.BufferedOutputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import visad.*;
+
+import visad.data.BaseDataProcessor;
+import visad.data.DataWriter;
+
+import visad.data.visad.object.*;
+
+/**
+ * Write a {@link visad.Data Data} object in VisAD's binary format.
+ *
+ * @see <a href="http://www.ssec.wisc.edu/~dglo/binary_file_format.html">Binary File Format Spec</a>
+ */
+public class BinaryWriter
+  extends BaseDataProcessor
+  implements BinaryFile, DataWriter
+{
+  private boolean initialized;
+  private DataOutputStream file;
+
+  private BinaryObjectCache unitCache, errorCache, cSysCache, typeCache;
+
+  public BinaryWriter()
+  {
+    file = null;
+  }
+
+  public BinaryWriter(String name)
+    throws IOException
+  {
+    this(new File(name));
+  }
+
+  public BinaryWriter(File ref)
+    throws IOException
+  {
+    this(new BufferedOutputStream(new FileOutputStream(ref)));
+  }
+
+  public BinaryWriter(OutputStream stream)
+    throws IOException
+  {
+    setOutputStream(stream);
+  }
+
+  public void close()
+    throws IOException
+  {
+    file.close();
+    file = null;
+  }
+
+  public void flush()
+    throws IOException
+  {
+    if (file == null) {
+      throw new IOException("No active file");
+    }
+
+    file.flush();
+  }
+
+  public final BinaryObjectCache getCoordinateSystemCache() { return cSysCache; }
+  public final BinaryObjectCache getErrorEstimateCache() { return errorCache; }
+  public final DataOutput getOutput() { return file; }
+  public final BinaryObjectCache getTypeCache() { return typeCache; }
+  public final BinaryObjectCache getUnitCache() { return unitCache; }
+
+  private final void initVars()
+  {
+    if (!initialized) {
+      this.file = null;
+    }
+
+    this.unitCache = new BinaryObjectCache();
+    this.errorCache = new BinaryObjectCache();
+    this.cSysCache = new BinaryObjectCache();
+    this.typeCache = new BinaryObjectCache();
+  }
+
+  public void processDoubleSet(SetType type, CoordinateSystem cs,
+                               Unit[] units, DoubleSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinarySimpleSet.write(this, type, cs, units, set, DoubleSet.class,
+                            DATA_DOUBLE_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + set.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processFieldImpl(FunctionType type, Set set, FieldImpl fld,
+                               Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (fld == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryFieldImpl.write(this, type, set, fld, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + fld.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processFlatField(FunctionType type, Set domainSet,
+                               CoordinateSystem cs,
+                               CoordinateSystem[] rangeCS, Set[] rangeSets,
+                               Unit[] units, FlatField fld, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (fld == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryFlatField.write(this, type, domainSet, cs, rangeCS, rangeSets,
+                            units, fld, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + fld.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processFloatSet(SetType type, CoordinateSystem cs,
+                              Unit[] units, FloatSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinarySimpleSet.write(this, type, cs, units, set, FloatSet.class,
+                            DATA_FLOAT_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + set.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processGridded1DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded1DDoubleSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryGriddedDoubleSet.write(this, type, samples, lengths, cs, units,
+                                   errors, set, Gridded1DDoubleSet.class,
+                                   DATA_GRIDDED_1D_DOUBLE_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Gridded1DDoubleSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processGridded2DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded2DDoubleSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryGriddedDoubleSet.write(this, type, samples, lengths, cs, units,
+                                   errors, set, Gridded2DDoubleSet.class,
+                                   DATA_GRIDDED_2D_DOUBLE_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Gridded2DDoubleSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processGridded3DDoubleSet(SetType type, double[][] samples,
+                                        int[] lengths, CoordinateSystem cs,
+                                        Unit[] units, ErrorEstimate[] errors,
+                                        Gridded3DDoubleSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryGriddedDoubleSet.write(this, type, samples, lengths, cs, units,
+                                   errors, set, Gridded3DDoubleSet.class,
+                                   DATA_GRIDDED_3D_DOUBLE_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Gridded3DDoubleSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processGridded1DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded1DSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryGriddedSet.write(this, type, samples, lengths, cs, units, errors,
+                             set, Gridded1DSet.class, DATA_GRIDDED_1D_SET,
+                             token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Gridded1DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processGridded2DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded2DSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryGriddedSet.write(this, type, samples, lengths, cs, units, errors,
+                             set, Gridded2DSet.class, DATA_GRIDDED_2D_SET,
+                             token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Gridded2DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processGridded3DSet(SetType type, float[][] samples,
+                                  int[] lengths, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  Gridded3DSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryGriddedSet.write(this, type, samples, lengths, cs, units, errors,
+                             set, Gridded3DSet.class, DATA_GRIDDED_3D_SET,
+                             token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Gridded3DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processGriddedSet(SetType type, float[][] samples,
+                                int[] lengths, CoordinateSystem cs,
+                                Unit[] units, ErrorEstimate[] errors,
+                                GriddedSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryGriddedSet.write(this, type, samples, lengths, cs, units, errors,
+                             set, GriddedSet.class, DATA_GRIDDED_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write GriddedSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processInteger1DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer1DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryIntegerSet.write(this, type, lengths, null, cs, units, errors,
+                             set, Integer1DSet.class, DATA_INTEGER_1D_SET,
+                             token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Integer1DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processInteger2DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer2DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    Integer1DSet[] comps = BinaryIntegerSet.getComponents(set);
+
+    try {
+      BinaryIntegerSet.write(this, type, lengths, comps, cs, units, errors,
+                             set, Integer2DSet.class, DATA_INTEGER_2D_SET,
+                             token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Integer2DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processInteger3DSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Integer3DSet set,
+                                  Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    Integer1DSet[] comps = BinaryIntegerSet.getComponents(set);
+
+    try {
+      BinaryIntegerSet.write(this, type, lengths, comps, cs, units, errors,
+                             set, Integer3DSet.class, DATA_INTEGER_3D_SET,
+                             token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Integer3DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processIntegerNDSet(SetType type, int[] lengths,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, IntegerNDSet set,
+                                  Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    Integer1DSet[] comps = BinaryIntegerSet.getComponents(set);
+
+    try {
+      BinaryIntegerSet.write(this, type, lengths, comps, cs, units, errors,
+                             set, IntegerNDSet.class, DATA_INTEGER_ND_SET,
+                             token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write IntegerNDSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processIrregular1DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors,
+                                    Irregular1DSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryIrregularSet.write(this, type, samples, cs, units, errors, null,
+                               set, Irregular1DSet.class,
+                               DATA_IRREGULAR_1D_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Irregular1DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processIrregular2DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors, Delaunay delaunay,
+                                    Irregular2DSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryIrregularSet.write(this, type, samples, cs, units, errors,
+                               delaunay, set, Irregular2DSet.class,
+                               DATA_IRREGULAR_2D_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Irregular2DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processIrregular3DSet(SetType type, float[][] samples,
+                                    CoordinateSystem cs, Unit[] units,
+                                    ErrorEstimate[] errors, Delaunay delaunay,
+                                    Irregular3DSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryIrregularSet.write(this, type, samples, cs, units, errors,
+                               delaunay, set, Irregular3DSet.class,
+                               DATA_IRREGULAR_3D_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Irregular3DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processIrregularSet(SetType type, float[][] samples,
+                                  CoordinateSystem cs, Unit[] units,
+                                  ErrorEstimate[] errors, Delaunay delaunay,
+                                  IrregularSet set, Object token)
+    throws VisADException
+  {
+    try {
+      BinaryIrregularSet.write(this, type, samples, cs, units, errors,
+                               delaunay, set, IrregularSet.class,
+                               DATA_IRREGULAR_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write IrregularSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processLinear1DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear1DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryLinearSet.write(this, type, firsts, lasts, lengths, null, cs,
+                            units, errors, set, Linear1DSet.class,
+                            DATA_LINEAR_1D_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Linear1DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processLinear2DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear2DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    Linear1DSet[] comps = new Linear1DSet[2];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    try {
+      BinaryLinearSet.write(this, type, firsts, lasts, lengths, comps, cs,
+                            units, errors, set, Linear2DSet.class,
+                            DATA_LINEAR_2D_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Linear2DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processLinear3DSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, Linear3DSet set,
+                                 Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    Linear1DSet[] comps = new Linear1DSet[3];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    try {
+      BinaryLinearSet.write(this, type, firsts, lasts, lengths, comps, cs,
+                            units, errors, set, Linear3DSet.class,
+                            DATA_LINEAR_3D_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Linear3DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processLinearLatLonSet(SetType type, double[] firsts,
+                                     double[] lasts, int[] lengths,
+                                     CoordinateSystem cs, Unit[] units,
+                                     ErrorEstimate[] errors,
+                                     LinearLatLonSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    Linear1DSet[] comps = new Linear1DSet[2];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    try {
+      BinaryLinearSet.write(this, type, firsts, lasts, lengths, comps, cs,
+                            units, errors, set, LinearLatLonSet.class,
+                            DATA_LINEAR_LATLON_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write LinearLatLonSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processLinearNDSet(SetType type, double[] firsts,
+                                 double[] lasts, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, LinearNDSet set,
+                                 Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    Linear1DSet[] comps = new Linear1DSet[set.getDimension()];
+    for (int i = 0; i < comps.length; i++) {
+      comps[i] = set.getLinear1DComponent(i);
+    }
+
+    try {
+      BinaryLinearSet.write(this, type, firsts, lasts, lengths, comps, cs,
+                            units, errors, set, LinearNDSet.class,
+                            DATA_LINEAR_ND_SET, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write LinearNDSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processList1DSet(SetType type, float[] list,
+                               CoordinateSystem cs, Unit[] units,
+                               List1DSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryList1DSet.write(this, type, list, cs, units, set, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write List1DSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processProductSet(SetType type, SampledSet[] sets,
+                                CoordinateSystem cs, Unit[] units,
+                                ErrorEstimate[] errors, ProductSet set,
+                                Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryProductSet.write(this, type, sets, cs, units, errors, set, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + set.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processReal(RealType type, double value, Unit unit,
+                          ErrorEstimate error, Real real, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    try {
+      BinaryReal.write(this, type, value, unit, error, real, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + real.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processRealTuple(RealTupleType type, Real[] components,
+                               CoordinateSystem cs, RealTuple rt,
+                               Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (rt == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryRealTuple.write(this, type, components, cs, rt, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + rt.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processSampledSet(SetType st, int manifold_dimension,
+                                CoordinateSystem cs, Unit[] units,
+                                ErrorEstimate[] errors, SampledSet set,
+                                Object token)
+    throws VisADException
+  {
+    try {
+      BinaryUnknown.write(this, set, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write SampledSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processSimpleSet(SetType st, int manifold_dimension,
+                               CoordinateSystem cs, Unit[] units,
+                               ErrorEstimate[] errors, SimpleSet set,
+                               Object token)
+    throws VisADException
+  {
+    try {
+      BinaryUnknown.write(this, set, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write SimpleSet object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processSingletonSet(RealTuple sample, CoordinateSystem cs,
+                                  Unit[] units, ErrorEstimate[] errors,
+                                  SingletonSet set, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinarySingletonSet.write(this, sample, cs, units, errors, set, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + set.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processText(TextType type, String value, boolean missing,
+                          Text text, Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    try {
+      BinaryText.write(this, type, value, missing, text, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + text.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processTuple(TupleType type, Data[] components, Tuple t,
+                           Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (t == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryTuple.write(this, type, components, t, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + t.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processUnionSet(SetType type, SampledSet[] sets, UnionSet set,
+                              Object token)
+    throws VisADException
+  {
+    if (file == null) {
+      throw new VisADException("No active file");
+    }
+
+    if (set == null) {
+      throw new VisADException("Null data object");
+    }
+
+    try {
+      BinaryUnionSet.write(this, type, sets, set, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write " + set.getClass().getName() +
+                               ": " + ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  public void processUnknownData(DataImpl data, Object token)
+    throws VisADException
+  {
+    try {
+      BinaryUnknown.write(this, data, token);
+    } catch (IOException ioe) {
+      throw new VisADException("Couldn't write Data object: " +
+                               ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+  }
+
+  /**
+   * Save a Data object to the file.
+   *
+   * @param data <tt>Data</tt> object to save
+   *
+   * @exception VisADException if the save fails.
+   */
+  public void save(DataImpl data)
+    throws VisADException
+  {
+    save(data, false);
+  }
+
+  /**
+   * Save a big Data object to the file.
+   * If called with <tt>bigObject</tt> set to <tt>true</tt>,
+   * special measures will be taken to make sure that only
+   * the necessary parts of a <tt>Data</tt> object are
+   * loaded, so that objects too large to fit in memory have
+   * a better chance of being saved.
+   *
+   * @param data <tt>Data</tt> object to save
+   * @param bigObject <tt>true</tt> if this is a really big object
+   *
+   * @exception VisADException if the save fails.
+   */
+  public void save(DataImpl data, boolean bigObject)
+    throws VisADException
+  {
+    Object dependToken;
+    if (bigObject) {
+      dependToken = BinaryObject.SAVE_DEPEND_BIG;
+    } else {
+      dependToken = BinaryObject.SAVE_DEPEND;
+    }
+
+    process(data, dependToken);
+    process(data, BinaryObject.SAVE_DATA);
+  }
+
+  public void setFile(String name)
+    throws IOException
+  {
+    setFile(new File(name));
+  }
+
+  public void setFile(File ref)
+    throws IOException
+  {
+    setOutputStream(new FileOutputStream(ref));
+  }
+
+  public void setOutputStream(OutputStream stream)
+    throws IOException
+  {
+    if (file != null) {
+      file.flush();
+      file.close();
+      file = null;
+    }
+
+    initVars();
+
+    if (stream == null) {
+      throw new IOException("Null OutputStream");
+    }
+
+    file = new DataOutputStream(new BufferedOutputStream(stream));
+
+    file.writeBytes(MAGIC_STR);
+    file.writeInt(FORMAT_VERSION);
+  }
+}
diff --git a/visad/data/visad/FakeData.java b/visad/data/visad/FakeData.java
new file mode 100644
index 0000000..6d0206d
--- /dev/null
+++ b/visad/data/visad/FakeData.java
@@ -0,0 +1,863 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import java.rmi.RemoteException;
+
+import java.util.ArrayList;
+
+import visad.*;
+
+class FakeCoordinateSystem
+  extends CoordinateSystem
+{
+  public FakeCoordinateSystem(RealTupleType rtt, Unit[] unit)
+    throws VisADException
+  {
+    super(rtt, unit);
+  }
+
+  public double[][] toReference(double[][] d) { return null; }
+  public double[][] fromReference(double[][] d) { return null; }
+
+  public boolean equals(Object obj)
+  {
+    if (!(obj instanceof FakeCoordinateSystem)) {
+      return false;
+    }
+
+    FakeCoordinateSystem fcs = (FakeCoordinateSystem )obj;
+
+    if (fcs.getDimension() != getDimension()) {
+      return false;
+    }
+
+    if (!getReference().equals(fcs.getReference())) {
+      return false;
+    }
+
+    Unit[] units, fcsUnits;
+    units = getCoordinateSystemUnits();
+    fcsUnits = fcs.getCoordinateSystemUnits();
+    if (units == null) {
+      if (fcsUnits != null) {
+        return false;
+      }
+    } else if (fcsUnits == null) {
+      return false;
+    } else if (units.length != fcsUnits.length) {
+      return false;
+    } else {
+      for (int i = 0; i < units.length; i++) {
+        if (units[i] == null) {
+          if (fcsUnits[i] != null) {
+            return false;
+          }
+        } else if (fcsUnits[i] == null) {
+          return false;
+        } else if (!units[i].equals(fcsUnits[i])) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+}
+
+/**
+ * This class creates a list containing all(?) permutations of 
+ * VisAD Data objects, which is used by the TestBinary class
+ * to the the binary file writer & reader classes.
+ */
+class FakeData
+{
+  private Unit foot, pound;
+  private RealType meHgt, meWgt, enHgt, enWgt;
+
+  private FakeCoordinateSystem cSys1D, cSys2D, cSys3D;
+  private Unit[] enUnits1D, enUnits2D, enUnits3D;
+  private RealTupleType tuple1D, tuple2D, tuple3D;
+
+  FakeData()
+    throws VisADException
+  {
+    foot = new ScaledUnit(0.3048, SI.meter, "foot");
+    pound = new ScaledUnit(2.204622, SI.kilogram, "pound");
+
+    enHgt = RealType.getRealType("EnglishHeight", foot, null);
+    enWgt = RealType.getRealType("EnglishWeight", pound, null);
+
+    meHgt = RealType.getRealType("MetricHeight", SI.meter, null);
+    meWgt = RealType.getRealType("MetricWeight", SI.kilogram, null);
+
+    enUnits1D = new Unit[] { foot };
+    cSys1D = new FakeCoordinateSystem(new RealTupleType(meHgt), enUnits1D);
+    tuple1D = new RealTupleType(meHgt, cSys1D, null);
+
+    enUnits2D = new Unit[] { foot, pound };
+    cSys2D = new FakeCoordinateSystem(new RealTupleType(meHgt, meWgt),
+                                      enUnits2D);
+    tuple2D = new RealTupleType(meHgt, meWgt, cSys2D, null);
+
+    enUnits3D = new Unit[] { foot, pound, SI.second };
+    cSys3D = new FakeCoordinateSystem(new RealTupleType(meHgt, meWgt,
+                                                        RealType.Time),
+                                      enUnits3D);
+    tuple3D = new RealTupleType(meHgt, meWgt, RealType.Time, cSys3D, null);
+  }
+
+  private void fakeFunctions(ArrayList list)
+  {
+    try {
+      FunctionType ft = new FunctionType(RealType.Time, enHgt);
+      Set set = new Integer1DSet(RealType.Time, 15);
+      list.add(new FieldImpl(ft, set));
+      list.add(new FlatField(ft, set));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Function");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+  }
+
+  private void fakeGriddedSets(ArrayList list)
+  {
+    float[][] data;
+    int[] lengths;
+    ErrorEstimate[] errors;
+
+    data = new float[][] { { 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f } };
+    lengths = new int[] { 10 };
+    errors = new ErrorEstimate[] { new ErrorEstimate(1.23, 0.04, foot) };
+
+    try {
+      list.add(new GriddedSet(RealType.Altitude, data, lengths));
+      list.add(new GriddedSet(tuple1D, data, lengths, cSys1D, enUnits1D,
+                              errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build GriddedSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    fakeGridded1DSets(list);
+    fakeGridded2DSets(list);
+    fakeGridded3DSets(list);
+    fakeGriddedNDSets(list);
+  }
+
+  private void fakeGridded1DSets(ArrayList list)
+  {
+    float[][] data;
+    double[][] dblData;
+    ErrorEstimate[] errors;
+
+    data = new float[][] { { 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f } };
+    dblData = new double[][] { { 1., 2., 3., 4., 5., 6., 7., 8. } };
+    errors = new ErrorEstimate[] { new ErrorEstimate(1.23, 0.04, foot) };
+
+    try {
+      list.add(new Gridded1DSet(RealType.Altitude, data, data[0].length));
+      list.add(new Gridded1DSet(tuple1D, data, data[0].length, cSys1D,
+                                enUnits1D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Gridded1DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Gridded1DDoubleSet(RealType.Altitude, data,
+                                      data[0].length));
+      list.add(new Gridded1DDoubleSet(tuple1D, data, data[0].length, cSys1D,
+                                      enUnits1D, errors));
+      list.add(new Gridded1DDoubleSet(RealType.Altitude, dblData,
+                                      dblData[0].length));
+      list.add(new Gridded1DDoubleSet(tuple1D, dblData, dblData[0].length,
+                                      cSys1D, enUnits1D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Gridded1DDoubleSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Linear1DSet(-1.23, 4.56, 10));
+      list.add(new Linear1DSet(RealType.TimeInterval, 1.35, 7.9, 11));
+      list.add(new Linear1DSet(tuple1D, 3.21, 6.66, 5, cSys1D, enUnits1D,
+                               errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Linear1DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Integer1DSet(15));
+      list.add(new Integer1DSet(RealType.TimeInterval, 7));
+      list.add(new Integer1DSet(tuple1D, 5, cSys1D, enUnits1D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Integer1DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+  }
+
+  private void fakeGridded2DSets(ArrayList list)
+  {
+    float[][] data;
+    double[][] dblData;
+    ErrorEstimate[] errors;
+    Linear1DSet[] lset;
+
+    data = new float[][] { { 0.1f, 0.2f, 0.3f, 0.4f,
+                             0.5f, 0.6f, 0.7f, 0.8f,
+                             0.9f, 1.0f, 1.1f, 1.2f },
+                           { 0.1f, 0.2f, 0.3f, 0.4f,
+                             0.5f, 0.6f, 0.7f, 0.8f,
+                             0.9f, 1.0f, 1.1f, 1.2f } };
+
+    dblData = new double[][] { { 1., 2., 3.,
+                                 4., 5., 6.,
+                                 7., 8., 9.,
+                                 10., 11., 12. },
+                               { 11., 10., 9.,
+                                 8., 7., 6.,
+                                 5., 4., 3.,
+                                 2., 1., 0. } };
+
+    errors = new ErrorEstimate[] {
+      new ErrorEstimate(1.23, 0.04, foot),
+      new ErrorEstimate(0.56, 7.8, pound)
+        };
+
+    try {
+      lset = new Linear1DSet[2];
+      lset[0] = new Linear1DSet(1., 4., 3);
+      lset[1] = new Linear1DSet(5., 8., 4);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Linear1DSet arguments" +
+                         " for 2D set tests");
+      ve.printStackTrace();
+      lset = null;
+    }
+
+    try {
+      list.add(new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
+                                data, data[0].length));
+      list.add(new Gridded2DSet(tuple2D, data, data[0].length, cSys2D,
+                                enUnits2D, errors));
+
+      list.add(new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
+                                data, 3, 4));
+      list.add(new Gridded2DSet(tuple2D, data, 3, 4, cSys2D,
+                                enUnits2D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Gridded2DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Gridded2DDoubleSet(RealTupleType.SpatialCartesian2DTuple,
+                                      data, data[0].length));
+      list.add(new Gridded2DDoubleSet(tuple2D, data, data[0].length,
+                                      cSys2D, enUnits2D, errors));
+      list.add(new Gridded2DDoubleSet(RealTupleType.SpatialCartesian2DTuple,
+                                      data, 3, 4));
+      list.add(new Gridded2DDoubleSet(tuple2D, data, 3, 4, cSys2D,
+                                      enUnits2D, errors));
+
+      list.add(new Gridded2DDoubleSet(RealTupleType.SpatialCartesian2DTuple,
+                                      dblData, dblData[0].length));
+      list.add(new Gridded2DDoubleSet(tuple2D, dblData, dblData[0].length,
+                                      cSys2D, enUnits2D, errors));
+      list.add(new Gridded2DDoubleSet(RealTupleType.SpatialCartesian2DTuple,
+                                      dblData, 4, 3));
+      list.add(new Gridded2DDoubleSet(tuple2D, dblData, 4, 3, cSys2D,
+                                      enUnits2D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Gridded2DDoubleSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Linear2DSet(-1.23, 4.56, 5, 7.89, 12.34, 5));
+      list.add(new Linear2DSet(RealTupleType.SpatialCartesian2DTuple,
+                               1., 5., 5, 1., 8., 8));
+      list.add(new Linear2DSet(tuple2D, 3., 9., 3, 3., 7., 4, cSys2D,
+                               enUnits2D, errors));
+
+      if (lset != null) {
+        list.add(new Linear2DSet(RealTupleType.SpatialCartesian2DTuple,
+                                 lset));
+        list.add(new Linear2DSet(tuple2D, lset, cSys2D, enUnits2D, errors));
+      }
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Linear2DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Integer2DSet(5, 3));
+      list.add(new Integer2DSet(RealTupleType.SpatialCartesian2DTuple,
+                                7, 2));
+      list.add(new Integer2DSet(tuple2D, 5, 4, cSys2D, enUnits2D, errors));
+
+      Integer1DSet[] iset = new Integer1DSet[2];
+      iset[0] = new Integer1DSet(5);
+      iset[1] = new Integer1DSet(4);
+
+      list.add(new Integer2DSet(RealTupleType.SpatialCartesian2DTuple,
+                                iset));
+      list.add(new Integer2DSet(tuple2D, iset, cSys2D, enUnits2D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Integer2DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new LinearLatLonSet(RealTupleType.SpatialEarth2DTuple,
+                                   4., 6., 5, 1.23, 4.56, 5));
+      /* XXX
+         list.add(new LinearLatLonSet(tuple2D, 1., 4., 3, 1., 4., 6, cSys2D,
+         enUnits2D, errors));
+      */
+
+      if (lset != null) {
+        list.add(new LinearLatLonSet(RealTupleType.SpatialEarth2DTuple,
+                                     lset));
+        /* XXX
+           list.add(new LinearLatLonSet(tuple2D, lset, cSys2D, enUnits2D,
+           errors));
+        */
+      }
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build LinearLatLonSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+  }
+
+  private void fakeGridded3DSets(ArrayList list)
+  {
+    float[][] data;
+    double[][] dblData;
+    ErrorEstimate[] errors;
+    Linear1DSet[] lset;
+
+    data = new float[][] { { 0.1f, 0.2f, 0.3f, 0.4f,
+                             0.5f, 0.6f, 0.7f, 0.8f,
+                             0.9f, 1.0f, 1.1f, 1.2f },
+                           { 0.31f, 0.32f, 0.33f, 0.34f,
+                             0.35f, 0.36f, 0.37f, 0.38f,
+                             0.39f, 0.40f, 0.41f, 0.42f },
+                           { 0.2f, 0.4f, 0.6f, 0.8f,
+                             1.0f, 1.2f, 1.4f, 1.6f,
+                             1.8f, 2.0f, 2.2f, 2.4f }
+    };
+
+    dblData = new double[][] { { 1., 2., 3.,
+                                 4., 5., 6.,
+                                 7., 8., 9.,
+                                 10., 11., 12. },
+                               { 11., 10., 9.,
+                                 8., 7., 6.,
+                                 5., 4., 3.,
+                                 2., 1., 0. },
+                               { 4., 4.5f, 5.f,
+                                 5.5f, 6.f, 6.5f,
+                                 7.f, 7.5f, 8.f,
+                                 8.5f, 9.f, 9.5f }
+    };
+
+    errors = new ErrorEstimate[] {
+      new ErrorEstimate(1.23, 0.04, foot),
+      new ErrorEstimate(0.56, 7.8, pound),
+      new ErrorEstimate(0.9, 0.9, SI.second)
+        };
+
+    try {
+      lset = new Linear1DSet[3];
+      lset[0] = new Linear1DSet(1., 4., 3);
+      lset[1] = new Linear1DSet(5., 8., 4);
+      lset[2] = new Linear1DSet(9., 123.4, 5);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Linear1DSet arguments" +
+                         " for 3D set tests");
+      ve.printStackTrace();
+      lset = null;
+    }
+
+    try {
+      list.add(new Gridded3DSet(RealTupleType.SpatialCartesian3DTuple,
+                                data, data[0].length));
+      list.add(new Gridded3DSet(tuple3D, data, data[0].length, cSys3D,
+                                enUnits3D, errors));
+
+      list.add(new Gridded3DSet(RealTupleType.SpatialCartesian3DTuple,
+                                data, 3, 4));
+      list.add(new Gridded3DSet(tuple3D, data, 3, 4, cSys3D,
+                                enUnits3D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Gridded3DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Gridded3DDoubleSet(RealTupleType.SpatialCartesian3DTuple,
+                                      data, data[0].length));
+      list.add(new Gridded3DDoubleSet(tuple3D, data, data[0].length,
+                                      cSys3D, enUnits3D, errors));
+      list.add(new Gridded3DDoubleSet(RealTupleType.SpatialCartesian3DTuple,
+                                      data, 3, 4));
+      list.add(new Gridded3DDoubleSet(tuple3D, data, 3, 4, cSys3D,
+                                      enUnits3D, errors));
+
+      list.add(new Gridded3DDoubleSet(RealTupleType.SpatialCartesian3DTuple,
+                                      dblData, dblData[0].length));
+      list.add(new Gridded3DDoubleSet(tuple3D, dblData, dblData[0].length,
+                                      cSys3D, enUnits3D, errors));
+      list.add(new Gridded3DDoubleSet(RealTupleType.SpatialCartesian3DTuple,
+                                      dblData, 4, 3));
+      list.add(new Gridded3DDoubleSet(tuple3D, dblData, 4, 3, cSys3D,
+                                      enUnits3D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Gridded3DDoubleSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Linear3DSet(-1.23, 4.56, 5, 7.89, 12.34, 5, 6.7, 8.9, 5));
+      list.add(new Linear3DSet(RealTupleType.SpatialCartesian3DTuple,
+                               1., 5., 4, 1., 7., 7, 1., 9., 4));
+      list.add(new Linear3DSet(tuple3D, 3., 9., 3, 3., 7., 4, 3., 5., 5,
+                               cSys3D, enUnits3D, errors));
+
+      if (lset != null) {
+        list.add(new Linear3DSet(RealTupleType.SpatialCartesian3DTuple,
+                                 lset));
+        list.add(new Linear3DSet(tuple3D, lset, cSys3D, enUnits3D, errors));
+      }
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Linear3DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Integer3DSet(5, 3, 1));
+      list.add(new Integer3DSet(RealTupleType.SpatialCartesian3DTuple,
+                                7, 2, 4));
+      list.add(new Integer3DSet(tuple3D, 5, 4, 3, cSys3D, enUnits3D,
+                                errors));
+
+      Integer1DSet[] iset = new Integer1DSet[3];
+      iset[0] = new Integer1DSet(5);
+      iset[1] = new Integer1DSet(4);
+      iset[2] = new Integer1DSet(3);
+
+      list.add(new Integer3DSet(RealTupleType.SpatialCartesian3DTuple,
+                                iset));
+      list.add(new Integer3DSet(tuple3D, iset, cSys3D, enUnits3D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Integer3DSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+  }
+
+  private void fakeGriddedNDSets(ArrayList list)
+  {
+    Unit[] enUnits5D;
+    enUnits5D = new Unit[] { foot, pound, SI.second,
+                             CommonUnit.degree, CommonUnit.degree };
+
+    RealType[] typeList = new RealType[] { meHgt, meWgt, RealType.Time,
+                                           RealType.Latitude,
+                                           RealType.Longitude };
+
+    RealTupleType tmpTuple;
+    try {
+      tmpTuple = new RealTupleType(typeList);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build temporary 5D tuple type");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    CoordinateSystem cSys5D;
+    try {
+      cSys5D = new FakeCoordinateSystem(tmpTuple, enUnits5D);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build 5D coordinate system");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    RealTupleType tuple5D;
+    try {
+      tuple5D = new RealTupleType(typeList, cSys5D, null);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build 5D tuple type");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    double[] firsts = new double[] { 1., 0., 2., 123., 42. };
+    double[] lasts = new double[] { 2., 100., 16., 246., 49. };
+    int[] lengths = new int[] { 3, 2, 3, 2, 3 };
+
+    ErrorEstimate[] errors;
+    errors = new ErrorEstimate[] {
+      new ErrorEstimate(1.23, 0.04, foot),
+      new ErrorEstimate(0.56, 7.8, pound),
+      new ErrorEstimate(0.9, 0.9, SI.second),
+      new ErrorEstimate(3.14, 1.59, CommonUnit.degree),
+      new ErrorEstimate(5.2, 8.0, CommonUnit.degree)
+        };
+
+    Linear1DSet[] lset;
+    try {
+      lset = new Linear1DSet[5];
+      lset[0] = new Linear1DSet(1., 2., 3);
+      lset[1] = new Linear1DSet(0., 100., 2);
+      lset[2] = new Linear1DSet(2., 16.4, 3);
+      lset[3] = new Linear1DSet(123., 246., 2);
+      lset[4] = new Linear1DSet(42., 49., 3);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Linear1DSet arguments" +
+                         " for ND set tests");
+      ve.printStackTrace();
+      lset = null;
+    }
+
+    try {
+      list.add(new LinearNDSet(tuple5D, firsts, lasts, lengths));
+      list.add(new LinearNDSet(tuple5D, firsts, lasts, lengths,
+                               cSys5D, enUnits5D, errors));
+
+      list.add(new LinearNDSet(tuple5D, lset));
+      list.add(new LinearNDSet(tuple5D, lset, cSys5D, enUnits5D, errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build LinearNDSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+  }
+
+  private void fakeIrregularSets(ArrayList list)
+  {
+    Delaunay del;
+    try {
+      del = new DelaunayFast(new float[][] { { 1.f, 2.f, 3.f },
+                                             { 1.f, 2.f, 3.f} });
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build DelaunayFast");
+      ve.printStackTrace();
+      del = null;
+    }
+
+    float[][] data;
+    ErrorEstimate[] errors;
+
+    try {
+      data = new float[][] { { 1.23f, 4.56f, 7.89f } };
+      errors = new ErrorEstimate[] { new ErrorEstimate(1.23, 0.04, foot) };
+
+      list.add(new IrregularSet(RealType.Altitude, data));
+      list.add(new IrregularSet(RealType.XAxis, data, del));
+      list.add(new IrregularSet(tuple1D, data, 1, cSys1D, enUnits1D,
+                                errors, del));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build IrregularSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new Irregular1DSet(RealTupleType.Time1DTuple, data));
+      list.add(new Irregular1DSet(tuple1D, data, cSys1D, enUnits1D,
+                                  errors));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Irregular1DSet");
+      ve.printStackTrace();
+      System.exit(1);
+    }
+
+    try {
+      data = new float[][] { { 1.23f, 4.56f, 7.89f },
+                             { 9.87f, 6.54f, 3.21f } };
+      errors = new ErrorEstimate[] {
+        new ErrorEstimate(4.56, 0.05, foot),
+        new ErrorEstimate(7.89, 0.67, pound) };
+
+      list.add(new Irregular2DSet(RealTupleType.LatitudeLongitudeTuple,
+                                  data));
+      list.add(new Irregular2DSet(tuple2D, data, cSys2D, enUnits2D,
+                                  errors, del));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Irregular2DSet");
+      ve.printStackTrace();
+      System.exit(1);
+    }
+
+    try {
+      data = new float[][] { { 1.23f, 4.56f, 7.89f },
+                             { 9.87f, 6.54f, 3.21f },
+                             { 5.43f, 2.19f, 8.76f } };
+      errors = new ErrorEstimate[] {
+        new ErrorEstimate(4.56, 0.05, foot),
+        new ErrorEstimate(7.89, 0.67, pound),
+        new ErrorEstimate(1.23, 1.23, SI.second) };
+
+      list.add(new Irregular3DSet(RealTupleType.SpatialEarth3DTuple, data));
+      list.add(new Irregular3DSet(tuple3D, data, cSys3D, enUnits3D,
+                                  errors, del));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Irregular3DSet");
+      ve.printStackTrace();
+      System.exit(1);
+    }
+  }
+
+  private void fakeSampledSets(ArrayList list)
+  {
+    try {
+      Real[] singles = new Real[] { new Real(0.123), new Real(1.234) };
+      list.add(new SingletonSet(new RealTuple(singles)));
+
+      singles = new Real[] { new Real(RealType.Time, 6.66),
+                             new Real(RealType.Time, 9.99, SI.second) };
+      list.add(new SingletonSet(new RealTuple(singles)));
+    } catch (RemoteException re) {
+      System.err.println("Couldn't build SingletonSet");
+      re.printStackTrace();
+      System.exit(1);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build SingletonSet");
+      ve.printStackTrace();
+      System.exit(1);
+    }
+
+    Integer1DSet iset;
+    Linear1DSet lset;
+
+    try {
+      iset = new Integer1DSet(10);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Integer1DSet for UnionSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      lset = new Linear1DSet(-15., 15., 10);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build Linear1DSet for UnionSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    SampledSet[] sets = new SampledSet[] { iset, lset };
+
+    try {
+      list.add(new UnionSet(sets));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build UnionSet");
+      ve.printStackTrace();
+      System.exit(1);
+    }
+
+    sets = new SampledSet[2];
+
+    int[] lengths = new int[] { 3 };
+
+    try {
+      sets[0] = new GriddedSet(RealType.TimeInterval,
+                               new float[][] { { 0f, 1f, 2f } }, lengths);
+      sets[1] = new GriddedSet(RealType.TimeInterval,
+                               new float[][] { { 6f, 5f, 4f } }, lengths);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build GriddedSets for ProductSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    UnionSet uset;
+    try {
+      uset = new UnionSet(sets);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build second UnionSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+    list.add(uset);
+
+    try {
+      list.add(uset.product(sets[0]));
+    } catch (VisADException ve) {
+      System.err.println("Couldn't get product of UnionSet");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    fakeGriddedSets(list);
+    fakeIrregularSets(list);
+  }
+
+  private void fakeSets(ArrayList list)
+  {
+    RealTupleType rttHgt;
+    try {
+      rttHgt = new RealTupleType(new RealType[] { meHgt }, cSys1D, null);
+    } catch (VisADException ve) {
+      System.err.println("Couldn't build RealTupleType for height!");
+      ve.printStackTrace();
+      System.exit(1);
+      return;
+    }
+
+    try {
+      list.add(new DoubleSet(RealType.Time));
+      list.add(new FloatSet(rttHgt, cSys1D, null));
+      list.add(new FloatSet(rttHgt, cSys1D, new Unit[] { SI.meter }));
+      list.add(new FloatSet(RealType.XAxis));
+      list.add(new FloatSet(rttHgt, cSys1D, null));
+      list.add(new FloatSet(rttHgt, cSys1D, new Unit[] { SI.meter }));
+      list.add(new List1DSet(new float[] { 1.1f, 2.2f, 3.3f }, rttHgt,
+      cSys1D, null));
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+
+    fakeSampledSets(list);
+  }
+
+  private void fakeTuples(ArrayList list)
+  {
+    TextType tt;
+    try {
+      tt = new TextType("Note");
+    } catch (VisADException ve) {
+      tt = TextType.Generic;
+    }
+
+    MathType[] mtypes = {RealType.Latitude, RealType.Longitude, tt};
+
+    try {
+      list.add(new Tuple(new TupleType(mtypes),
+                         new Data[] {new Real(RealType.Latitude, -60.0),
+                                     new Real(RealType.Longitude, 60.0),
+                                     new Text(tt, "Some text")}));
+    } catch (RemoteException re) {
+    } catch (VisADException ve) {
+    }
+
+    try {
+      Real[] vals = new Real[2];
+      vals[0] = new Real(meHgt, (double )4.56);
+      vals[1] = new Real(meWgt, (double )1.23);
+
+      list.add(new RealTuple(tuple2D, vals, cSys2D));
+    } catch (RemoteException re) {
+      re.printStackTrace();
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+  }
+
+  DataImpl[] getList()
+  {
+    ArrayList list = new ArrayList();
+
+    // add Text varieties
+    list.add(new Text("g>a\"r&b<a'ge"));
+    try {
+      list.add(new Text(new TextType("'Ru&de=>Ty<pe\"")));
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+
+    // add Real varieties
+    list.add(new Real(RealType.XAxis, 123.456));
+    try {
+      list.add(new Real(RealType.Altitude, 123.456, SI.meter, 43.21));
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+    try {
+      list.add(new Real(RealType.TimeInterval, Double.NaN, SI.second, 43.21));
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+
+    // add Tuple varieties
+    fakeTuples(list);
+
+    // add Set varieties
+    fakeSets(list);
+
+    // add Function varieties
+    fakeFunctions(list);
+
+    // contruct final list
+    DataImpl[] dataList = new DataImpl[list.size()];
+    for (int i = 0; i < dataList.length; i++) {
+      dataList[i] = (DataImpl )list.get(i);
+    }
+
+    return dataList;
+  }
+}
diff --git a/visad/data/visad/Saveable.java b/visad/data/visad/Saveable.java
new file mode 100644
index 0000000..9f81bda
--- /dev/null
+++ b/visad/data/visad/Saveable.java
@@ -0,0 +1,41 @@
+//
+// Saveable.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+/**
+ * This interface is a "marker" used to indicate to the VisAD
+ * binary file code that an object should be saved in binary
+ * format.<br>
+ * <br>
+ * Any class which extends one of the base VisAD Data classes can
+ * implement Saveable to indicate that it should be saved as if
+ * it were the parent Data class.<br>
+ * <br>
+ * If a class which extends a base Data class does not implement
+ * Saveable, it will be saved as a serialized object.
+ */
+public interface Saveable { }
diff --git a/visad/data/visad/TestBinary.java b/visad/data/visad/TestBinary.java
new file mode 100644
index 0000000..935890a
--- /dev/null
+++ b/visad/data/visad/TestBinary.java
@@ -0,0 +1,254 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.ArrayList;
+
+import visad.DataImpl;
+import visad.VisADException;
+
+import visad.data.DefaultFamily;
+import visad.data.Form;
+
+public class TestBinary
+{
+  private static final String OUTPUT_SUBDIRECTORY = "tstout";
+
+  private String progName;
+  private boolean allowBinary, verbose;
+  private String[] files;
+
+  public TestBinary(String[] args)
+    throws VisADException
+  {
+    initArgs();
+
+    if (!processArgs(args)) {
+      System.err.println("Exiting...");
+      System.exit(1);
+    }
+
+    DefaultFamily df = new DefaultFamily("DefaultFamily");
+
+    Form form = new VisADForm(allowBinary);
+
+    if (!makeSubdirectory(OUTPUT_SUBDIRECTORY)) {
+      throw new VisADException("Couldn't create test subdirectory \"" +
+                               OUTPUT_SUBDIRECTORY + "\"");
+    }
+
+    boolean success = true;
+    if (files != null) {
+      for (int i = 0; i < files.length; i++) {
+        DataImpl data;
+        try {
+          data = df.open(files[i]);
+        } catch (visad.data.BadFormException bfe) {
+          System.err.println("Couldn't read " + files[i] + ": " +
+                             bfe.getMessage());
+          success = false;
+          continue;
+        }
+
+        System.out.println(files[i]);
+
+        success &= writeData(form, data, i);
+
+        if (verbose) {
+          System.out.println("-- ");
+        }
+      }
+    } else {
+      DataImpl[] dataList = new FakeData().getList();
+
+      for (int i = 0; i < dataList.length; i++) {
+        success &= writeData(form, dataList[i], i);
+
+        if (verbose) {
+          System.out.println("-- ");
+        }
+      }
+    }
+
+    if (success) {
+      System.out.println("All tests succeeded!");
+    }
+  }
+
+  private int getSerializedSize(Object obj)
+  {
+    java.io.ByteArrayOutputStream outBytes;
+    outBytes = new java.io.ByteArrayOutputStream();
+
+    java.io.ObjectOutputStream outStream;
+    try {
+      outStream = new java.io.ObjectOutputStream(outBytes);
+      outStream.writeObject(obj);
+      outStream.flush();
+      outStream.close();
+    } catch (IOException ioe) {
+      return 0;
+    }
+
+    return outBytes.size();
+  }
+
+  public void initArgs()
+  {
+    allowBinary = verbose = false;
+    files = null;
+  }
+
+  private boolean makeSubdirectory(String name)
+  {
+    File subdir = new File(name);
+    if (subdir.isDirectory()) {
+      return true;
+    }
+
+    if (subdir.exists()) {
+      System.err.println(progName + ": Subdirectory \"" + name +
+                         "\" exists but is not a directory");
+      return false;
+    }
+
+    return subdir.mkdir();
+  }
+
+  public boolean processArgs(String[] args)
+  {
+    boolean usage = false;
+
+    String className = getClass().getName();
+    int pt = className.lastIndexOf('.');
+    final int ds = className.lastIndexOf('$');
+    if (ds > pt) {
+      pt = ds;
+    }
+    progName = className.substring(pt == -1 ? 0 : pt + 1);
+
+    ArrayList fileList = null;
+
+    for (int i = 0; args != null && i < args.length; i++) {
+      if (args[i].length() > 0 && args[i].charAt(0) == '-') {
+        char ch = args[i].charAt(1);
+
+        String str, result;
+
+        switch (ch) {
+        case 'a':
+          allowBinary = true;
+          break;
+        case 'v':
+          verbose = true;
+          break;
+        default:
+          System.err.println(progName +
+                             ": Unknown option \"-" + ch + "\"");
+          usage = true;
+          break;
+        }
+      } else {
+        if (fileList == null) {
+          fileList = new ArrayList();
+        }
+        fileList.add(args[i]);
+      }
+    }
+
+    if (usage) {
+      System.err.println("Usage: " + getClass().getName() +
+                         " [-a(llowBinary)]" +
+                         " [-v(erbose)]" +
+                         "");
+    }
+
+    if (fileList != null) {
+      files = new String[fileList.size()];
+      for (int i = 0; i < files.length; i++) {
+        files[i] = (String )fileList.get(i);
+      }
+
+      fileList.clear();
+    }
+
+    return !usage;
+  }
+
+  private boolean writeData(Form form, DataImpl data, int num)
+  {
+    String path = OUTPUT_SUBDIRECTORY + File.separatorChar + "binary" +
+      num + ".vad";
+
+    if (verbose) {
+      System.out.println("Writing " + data.getClass().getName() + " to " +
+                         path);
+    }
+
+    try {
+      form.save(path, data, true);
+    } catch (Throwable t) {
+      t.printStackTrace();
+      new File(path).delete();
+      return false;
+    } 
+
+    if (verbose) {
+      System.out.println("Reading " + data.getClass().getName());
+    }
+
+    DataImpl newData;
+    try {
+      newData = form.open(path);
+    } catch (Throwable t) {
+      t.printStackTrace();
+      return false;
+    }
+
+    if (newData == null) {
+      System.err.println("Got null Data while reading " + data);
+      return false;
+    }
+
+    if (!data.equals(newData)) {
+      System.err.println("MISMATCH");
+      return false;
+    }
+
+    return true;
+  }
+
+  public static void main(String[] args)
+  {
+    try {
+      new TestBinary(args);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+
+    System.exit(0);
+  }
+}
diff --git a/visad/data/visad/VisADCachingForm.java b/visad/data/visad/VisADCachingForm.java
new file mode 100644
index 0000000..6e92625
--- /dev/null
+++ b/visad/data/visad/VisADCachingForm.java
@@ -0,0 +1,109 @@
+//
+// VisADCachingForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import java.net.URL;
+
+import ucar.netcdf.RandomAccessFile;
+
+import visad.DataImpl;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+
+/**
+ * VisADCachingForm is the VisAD data format adapter
+ * for large binary visad.Data objects which may not
+ * fit in memory.<P>
+ */
+public class VisADCachingForm
+  extends VisADForm
+{
+  public VisADCachingForm()
+  {
+    super(true);
+  }
+
+  public boolean isThisType(String name) { return false; }
+  public boolean isThisType(byte[] block) { return false; }
+
+  public String[] getDefaultSuffixes() { return null; }
+
+  public synchronized DataImpl open(URL url)
+    throws BadFormException, VisADException
+  {
+    throw new VisADException("Cannot cache URL " + url);
+  }
+
+  public synchronized DataImpl open(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    IOException savedIOE = null;
+    VisADException savedVE = null;
+
+    // try to read a binary object
+    try {
+      return readData(new BinaryReader(new RandomAccessFile(id, "r")));
+    } catch (IOException ioe) {
+      savedIOE = ioe;
+    } catch (VisADException ve) {
+      savedVE = ve;
+    }
+
+    // maybe it's a serialized object
+    try {
+      return readSerial(new FileInputStream(id));
+    } catch (ClassNotFoundException cnfe) {
+      if (savedIOE != null) {
+        throw savedIOE;
+      } else if (savedVE != null) {
+        throw savedVE;
+      }
+
+      throw new BadFormException("Could not read file \"" + id + "\": " +
+                                 cnfe.getMessage());
+    } catch (IOException ioe) {
+      if (savedIOE != null) {
+        throw savedIOE;
+      } else if (savedVE != null) {
+        throw savedVE;
+      }
+
+      throw ioe;
+    }
+  }
+
+  public DataImpl readData(BinaryReader rdr)
+    throws IOException, VisADException
+  {
+    // don't close the file here, it might be needed by a FileFlatField
+    return rdr.getData();
+  }
+}
diff --git a/visad/data/visad/VisADForm.java b/visad/data/visad/VisADForm.java
new file mode 100644
index 0000000..b800204
--- /dev/null
+++ b/visad/data/visad/VisADForm.java
@@ -0,0 +1,326 @@
+//
+// VisADForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import java.net.URL;
+
+import java.rmi.RemoteException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.VisADException;
+
+import visad.data.BadFormException;
+import visad.data.DefaultFamily;
+import visad.data.Form;
+import visad.data.FormFileInformer;
+import visad.data.FormNode;
+
+/**
+   VisADForm is the VisAD data format adapter for
+   binary visad.Data objects.<P>
+*/
+public class VisADForm extends Form implements FormFileInformer {
+
+  private static int num = 0;
+
+  private boolean allowBinary = false;
+
+  /**
+   * If <tt>allowBinary</tt> is <tt>true</tt>, read/write a VisAD
+   * Data object in VisAD's
+   * <a href="http://www.ssec.wisc.edu/~dglo/binary_file_format.html">binary file format</a>.<br>
+   * <br>
+   * If <tt>allowBinary</tt> is <tt>false</tt>, read/write a VisAD
+   * Data object using Java serialization.
+   *
+   * @param allowBinary if <tt>true</tt> use VisAD's binary file format,
+   *                    otherwise use Java serialization.
+   */
+  public VisADForm(boolean allowBinary)
+  {
+    this();
+
+    this.allowBinary = allowBinary;
+  }
+
+  /**
+   * Read/write a VisAD Data object using Java serialization.
+   */
+  public VisADForm()
+  {
+    super("VisADForm" + num++);
+  }
+
+  public boolean isThisType(String name)
+  {
+    return name.endsWith(".vad") || name.endsWith(".VAD");
+  }
+
+  public boolean isThisType(byte[] block)
+  {
+    return BinaryReader.isMagic(block);
+  }
+
+  public String[] getDefaultSuffixes()
+  {
+    String[] suff = { "vad" };
+    return suff;
+  }
+
+  public synchronized void add(String id, Data data, boolean replace)
+    throws BadFormException
+  {
+    throw new BadFormException("VisADForm.add");
+  }
+
+  public synchronized FormNode getForms(Data data)
+  {
+    return null;
+  }
+
+  public synchronized DataImpl open(String id)
+    throws BadFormException, IOException, VisADException
+  {
+    IOException savedIOE = null;
+    VisADException savedVE = null;
+
+    // try to read a binary object
+    try {
+      return readData(new BinaryReader(id));
+    } catch (IOException ioe) {
+      savedIOE = ioe;
+    } catch (VisADException ve) {
+      savedVE = ve;
+    }
+
+    // maybe it's a serialized object
+    try {
+      return readSerial(new FileInputStream(id));
+    } catch (ClassNotFoundException cnfe) {
+      if (savedIOE != null) {
+        throw savedIOE;
+      } else if (savedVE != null) {
+        throw savedVE;
+      }
+
+      throw new BadFormException("Could not read file \"" + id + "\": " +
+                                 cnfe.getMessage());
+    } catch (IOException ioe) {
+      if (savedIOE != null) {
+        throw savedIOE;
+      } else if (savedVE != null) {
+        throw savedVE;
+      }
+
+      throw ioe;
+    }
+  }
+
+  public synchronized DataImpl open(URL url)
+    throws BadFormException, IOException, VisADException
+  {
+    IOException savedIOE = null;
+    VisADException savedVE = null;
+
+    // try to read a binary object
+    try {
+      return readData(new BinaryReader(url.openStream()));
+    } catch (IOException ioe) {
+      savedIOE = ioe;
+    } catch (VisADException ve) {
+      savedVE = ve;
+    }
+
+    // maybe it's a serialized object
+    try {
+      return readSerial(url.openStream());
+    } catch (ClassNotFoundException cnfe) {
+      if (savedIOE != null) {
+        throw savedIOE;
+      } else if (savedVE != null) {
+        throw savedVE;
+      }
+
+      throw new BadFormException("Could not read URL " + url + ": " +
+                                 cnfe.getMessage());
+    } catch (IOException ioe) {
+      if (savedIOE != null) {
+        throw savedIOE;
+      } else if (savedVE != null) {
+        throw savedVE;
+      }
+
+      throw ioe;
+    }
+  }
+
+  public DataImpl readData(BinaryReader rdr)
+    throws IOException, VisADException
+  {
+    DataImpl di = rdr.getData();
+    try { rdr.close(); } catch (IOException ioe) { }
+    return di;
+  }
+
+  public DataImpl readSerial(InputStream inputStream)
+    throws ClassNotFoundException, IOException
+  {
+    BufferedInputStream bufferedStream = new BufferedInputStream(inputStream);
+    ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
+    return (DataImpl )objectStream.readObject();
+  }
+
+  /**
+   * Save a <tt>Data</tt> object in VisAD's binary format.
+   *
+   * @param id file name
+   * @param data <tt>Data</tt> object
+   * @param replace <tt>true</tt> if any existing file should be overwritten
+   * @param bigObject <tt>true</tt> if the <tt>Data</tt> object is larger
+   *                  than the computer's memory, in which case special
+   *                  measures will be taken to converse memory usage.
+   */
+  private synchronized void saveBinary(String id, Data data, boolean replace,
+                                       boolean bigObject)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    File file = new File(id);
+    if (!replace && file.exists()) {
+      throw new IllegalArgumentException("File \"" + id + "\" exists");
+    }
+
+    BinaryWriter writer = new BinaryWriter(file);
+    writer.save((DataImpl )data, bigObject);
+    writer.close();
+  }
+
+  /**
+   * Save a <tt>Data</tt> object in serialized Java format.
+   *
+   * @param id file name
+   * @param data <tt>Data</tt> object
+   * @param replace <tt>true</tt> if any existing file should be overwritten
+   */
+  private synchronized void saveSerial(String id, Data data, boolean replace)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    FileOutputStream fileStream = null;
+    if (replace) {
+      fileStream = new FileOutputStream(id);
+    }
+    else {
+      File file = new File(id);
+      if (file.exists()) {
+        throw new BadFormException("VisADSerialForm.save(" + id + "): exists");
+      }
+      fileStream = new FileOutputStream(file);
+    }
+    BufferedOutputStream bufferedStream = new BufferedOutputStream(fileStream);
+    ObjectOutputStream objectStream = new ObjectOutputStream(bufferedStream);
+    DataImpl local_data = data.local();
+    objectStream.writeObject(local_data);
+    objectStream.flush();
+    fileStream.close();
+  }
+
+  /**
+   * Save a <tt>Data</tt> object.
+   *
+   * @param id file name
+   * @param data <tt>Data</tt> object
+   * @param replace <tt>true</tt> if any existing file should be overwritten
+   */
+  public void save(String id, Data data, boolean replace)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    save(id, data, replace, false);
+  }
+
+  /**
+   * Save a <tt>Data</tt> object.
+   *
+   * @param id file name
+   * @param data <tt>Data</tt> object
+   * @param replace <tt>true</tt> if any existing file should be overwritten
+   * @param bigObject <tt>true</tt> if the <tt>Data</tt> object is larger
+   *                  than the computer's memory, in which case special
+   *                  measures will be taken to converse memory usage.
+   */
+  public synchronized void save(String id, Data data, boolean replace,
+                                boolean bigObject)
+    throws BadFormException, IOException, RemoteException, VisADException
+  {
+    if (allowBinary) {
+      saveBinary(id, data, replace, bigObject);
+    } else {
+      saveSerial(id, data, replace);
+    }
+  }
+
+  /** run 'java visad.data.visad.VisADForm in_file out_file' to
+      convert in_file to out_file in VisAD serialized data format */
+  public static void main(String args[])
+    throws VisADException, RemoteException, IOException
+  {
+    if (args == null || args.length < 1 || args.length > 2) {
+      System.out.println("to convert a file to a VisAD binary file, run:");
+      System.out.println("  java visad.data.visad.VisADForm in_file out_file");
+      System.out.println("to test read a binary or serial VisAD file, run:");
+      System.out.println("or  'java visad.data.visad.VisADForm in_file'");
+    }
+    else if (args.length == 1) {
+      VisADForm form = new VisADForm();
+      if (args[0].startsWith("http://")) {
+        // with "ftp://" this throws "sun.net.ftp.FtpProtocolException: RETR ..."
+        URL url = new URL(args[0]);
+        form.open(url);
+      }
+      else {
+        form.open(args[0]);
+      }
+    }
+    else if (args.length == 2) {
+      DefaultFamily loader = new DefaultFamily("loader");
+      DataImpl data = loader.open(args[0]);
+      loader = null;
+      VisADForm form = new VisADForm();
+      form.save(args[1], data, true);
+    }
+    System.exit(0);
+  }
+}
diff --git a/visad/data/visad/VisADSerialForm.java b/visad/data/visad/VisADSerialForm.java
new file mode 100644
index 0000000..14fa7b9
--- /dev/null
+++ b/visad/data/visad/VisADSerialForm.java
@@ -0,0 +1,39 @@
+//
+// VisADSerialForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad;
+
+/**
+   VisADSerialForm is the VisAD data format adapter for
+   serialized visad.Data objects.<P>
+*/
+public class VisADSerialForm
+  extends VisADForm
+{
+  public VisADSerialForm() {
+    super(false);
+  }
+}
diff --git a/visad/data/visad/binary_file_format.html b/visad/data/visad/binary_file_format.html
new file mode 100644
index 0000000..7c8960d
--- /dev/null
+++ b/visad/data/visad/binary_file_format.html
@@ -0,0 +1,573 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+  <head>
+    <title>VisAD Binary File Format</title>
+  </head>
+
+  <body  text="#000000" link="#0000ff" bgcolor="#ffffff">
+    <h1>VisAD Binary File Format</h1>
+    This document describes Version 1 of the VisAD binary file
+    format.<br><br>
+    All VisAD binary files must start with an 8-byte "magic"
+    string <tt><b>VisADBin</b></tt>, followed by a 4-byte
+    integer holding the version number.
+    <table>
+      <tr>
+	<td>    </td>
+	<td>
+	  <i>
+	    Note that Java is "big-endian", meaning that if
+	    the 4-byte version number is 1, it would be stored
+	    in the file as
+	  </i>
+	  <tt><b>00 00 00 01</b></tt>.
+	</td>
+      </tr>
+    </table>
+    <br><br>
+    Following the 12-byte header are a series of objects which
+    all start with a byte which indicates the type of that object
+    followed by a 4-byte integer holding length of the object
+    (i.e. the number of bytes.of data remaining in the object,
+    not counting the type byte and the 4-byte length).
+    The current list of object types and their associated byte
+    values can be found in <a href="#ApdxA">Appendix A</a>.<br><br>
+    <hr>
+    <div>
+      <h2>Objects</h2>
+      <h3><a name="OBJ_COORDSYS">OBJ_COORDSYS</a></h3>
+      A VisAD coordinate system object.  For version 1, these are all
+      serialized.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt>OBJ_COORDSYS</tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>1</td><td><tt>FLD_COORDSYS_SERIAL</tt></td><td>Serialized <tt>CoordinateSystem</tt> marker</td></tr>
+	<tr><td>len</td><td>serialized object</td><td>A serialized <tt>CoordinateSystem</tt> object</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      <h3><a name="OBJ_DATA">OBJ_DATA</a></h3>
+      A VisAD <tt>Data</tt> object.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt>OBJ_DATA</tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC"><tt>Data</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of <tt>Data</tt> object</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      See the <a href="#DATA">DATA</a> section below for the formats of the
+      various standard Data objects.
+      <h3><a name="OBJ_DATA_SERIAL">OBJ_DATA_SERIAL</a></h3>
+      A non-standard <tt>Data</tt> object, written as a serialized Java object.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt>OBJ_DATA_SERIAL</tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>len</td><td>serialized object</td><td>A serialized <tt>Data</tt> object</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      <h3><a name="OBJ_ERROR">OBJ_ERROR</a></h3>
+      A VisAD error estimate.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt>OBJ_ERROR</tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>8</td><td>double</td><td>Error value</td></tr>
+	<tr><td>8</td><td>double</td><td>Mean</td></tr>
+	<tr><td>8</td><td>long</td><td>Number of values</td></tr>
+	<tr><td><i>1</i></td><td><tt>FLD_INDEX_UNIT</tt></td><td rowspan=2>If CoordinateSystem has a Unit</td></tr>
+	<tr><td><i>4</i></td><td>Unit index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      <h3><a name="OBJ_MATH">OBJ_MATH</a></h3>
+      A VisAD <tt>MathType</tt>.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt>OBJ_MATH</tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxB"><tt>MathType</tt> byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of MathType object</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      See the <a href="#MATH">MATH</a> section below for the formats of the
+      various standard MathTypes
+      <h3><a name="OBJ_MATH_SERIAL">OBJ_MATH_SERIAL</a></h3>
+      A non-standard <tt>MathType</tt>, written as a serialized Java object.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt>OBJ_MATH_SERIAL</tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Serialized object length</td></tr>
+	<tr><td>len</td><td>serialized object</td><td>A serialized <tt>MathType</tt> object</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      <h3><a name="OBJ_UNIT">OBJ_UNIT</a></h3>
+      A VisAD unit specification.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt>OBJ_UNIT</tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>4</td><td>int</td><td>Identifier string length</td></tr>
+	<tr><td>len</td><td>string</td><td>Identifier string</td></tr>
+	<tr><td>4</td><td>int</td><td>Description string length</td></tr>
+	<tr><td>len</td><td>string</td><td>Description string</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+    </div>
+    <hr>
+    <div>
+      <h2><a name="MATH"><tt>MathType</tt>s</a></h2>
+      <h3><a name="MATH_FUNCTION">MATH_FUNCTION</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_MATH">OBJ_MATH</a></tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object (should always be <tt>14</tt>)</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>1</td><td><tt>MATH_FUNCTION</tt></td><td>FunctionType marker</td></tr>
+	<tr><td>4</td><td><tt>int</tt></td><td>Function domain MathType index</td></tr>
+	<tr><td>4</td><td><tt>int</tt></td><td>Function range MathType index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      <h3><a name="MATH_QUANTITY">MATH_QUANTITY</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th>Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_MATH">OBJ_MATH</a></tt></td><td>Start-of-object marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td><td> </td></tr>
+	<tr><td>1</td><td><tt>MATH_QUANTITY</tt></td><td>Quantity MathType marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Name string length</td><td> </td></tr>
+	<tr><td>len</td><td>string</td><td>Name string</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Unit specification string length</td><td> </td></tr>
+	<tr><td>len</td><td>string</td><td>Unit specification string</td><td> </td></tr>
+	<tr><td><i>1</i></td><td><a href="#FLD_SET_FOLLOWS_TYPE"><tt>FLD_SET_FOLLOWS_TYPE</tt></a></td><td>The Set associated with this object follows immediately after this object's <tt>FLD_END</tt> byte</td><td>Only specified if there is a <tt>Set</tt> associated with this <tt>MathType</tt></td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="MATH_REAL_TUPLE">MATH_REAL_TUPLE</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th>Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_MATH">OBJ_MATH</a></tt></td><td>Start-of-object marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td><td> </td></tr>
+	<tr><td>1</td><td><tt>MATH_REAL_TUPLE</tt></td><td>RealTupleType marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of <tt>MathType</tt> tuples</td><td rowspan="4">The tuple dimension is followed by the appropriate number of <tt>int</tt>s to hold the list of <tt>MathType</tt> indices</td></tr>
+	<tr><td>4</td><td>int</td><td>First tuple member index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last tuple member index</td></tr>
+	<tr><td><i>1</i></td><td><a href="#FLD_INDEX_COORDSYS"><tt>FLD_INDEX_COORDSYS</tt></a></td><td>Marker for <tt>MathType</tt> <tt>CoordinateSystem</tt> index.</td><td rowspan="2">Only specified if there is a <tt>CoordinateSystem</tt> associated with this <tt>MathType</tt></td></tr>
+	<tr><td><i>4</i></td><td>int</td><td><tt>CoordinateSystem</tt> index</td></tr>
+	<tr><td><i>1</i></td><td><a href="#FLD_SET_FOLLOWS_TYPE"><tt>FLD_SET_FOLLOWS_TYPE</tt></a></td><td>The Set associated with this object follows immediately after this object's <tt>FLD_END</tt> byte</td><td>Only specified if there is a <tt>Set</tt> associated with this <tt>MathType</tt></td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="MATH_REAL">MATH_REAL</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th>Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_MATH">OBJ_MATH</a></tt></td><td>Start-of-object marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td><td> </td></tr>
+	<tr><td>1</td><td><tt>MATH_REAL</tt></td><td>RealType marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Attribute mask</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Name string length</td><td> </td></tr>
+	<tr><td>len</td><td>string</td><td>Name string</td><td> </td></tr>
+	<tr><td><i>1</i></td><td><a href="#FLD_INDEX_UNIT"><tt>FLD_INDEX_UNIT</tt></a></td><td>Marker for <tt>MathType</tt> <tt>Unit</tt> index</td><td rowspan="2">Only specified if there is a <tt>Unit</tt> associated with this <tt>MathType</tt></td></tr>
+	<tr><td><i>4</i></td><td>int</td><td><tt>Unit</tt> index</td></tr>
+	<tr><td><i>1</i></td><td><a href="#FLD_SET_FOLLOWS_TYPE"><tt>FLD_SET_FOLLOWS_TYPE</tt></a></td><td>The Set associated with this object follows immediately after this object's <tt>FLD_END</tt> byte</td><td>Only specified if there is a <tt>Set</tt> associated with this <tt>MathType</tt></td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="MATH_SET">MATH_SET</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_MATH">OBJ_MATH</a></tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object (should always be <tt>10</tt>)</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>1</td><td><tt>MATH_SET</tt></td><td>SetType marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Domain <tt>MathType</tt> index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      <h3><a name="MATH_TEXT">MATH_TEXT</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_MATH">OBJ_MATH</a></tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>1</td><td><tt>MATH_TEXT</tt></td><td>TextType marker</td></tr>
+	<tr><td>len</td><td>string</td><td>Name string</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+      <h3><a name="MATH_TUPLE">MATH_TUPLE</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_MATH">OBJ_MATH</a></tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>4</td><td>int</td><td>Index number</td></tr>
+	<tr><td>1</td><td><tt>MATH_TUPLE</tt></td><td>TupleType marker</td></tr>
+	<tr><td>4</td><td>int</td><td>First tuple member index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last tuple member index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td></tr>
+      </table>
+    </div>
+    <hr>
+    <div>
+      <h2><a name="DATA"><tt>Data</tt> Types</a></h2>
+      Note that for all Data types which contain one or more embedded
+      data objects, the <tt>OBJ_DATA</tt> marker may be preceeded by
+      any number of <tt>OBJ_COORDSYS</tt>, <tt>OBJ_ERROR</tt>,
+      <tt>OBJ_MATH</tt>, <tt>OBJ_MATH_SERIAL</tt> or <tt>OBJ_UNIT</tt>
+      objects.<br>
+      <br>
+      Also, any field marked as containing an <tt>OBJ_DATA</tt> object
+      may instead contain an <tt>OBJ_DATA_SERIAL</tt> object.
+      <h3><a name="DATA_DOUBLE_SET">DATA_DOUBLE_SET</a></h3>
+      <i><b>Coming soon.</b></i>
+      <h3><a name="DATA_FIELD">DATA_FIELD</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th>Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>1</td><td><tt>DATA_FIELD</tt></td><td>FieldImpl/FileField marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>FunctionType index</td><td> </td></tr>
+	<tr><td>1</td><td><a href="#FLD_SET"><tt>FLD_SET</tt></a></td><td>Marker for default <tt>Set</tt> object.</td><td rowspan="5">Only specified if there is a default <tt>Set</tt>.</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC"><tt>Set</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of <tt>Set</tt> object</td></tr>
+	<tr><td>1</td><td><a href="#FLD_DATA_SAMPLES"><tt>FLD_DATA_SAMPLES</tt></a></td><td>Marker for list of <tt>Data</tt> fields.</td><td rowspan="11">Only specified if there are <tt>Data</tt> fields.</td></tr>
+	<tr><td>4</td><td>int</td><td><tt>Number of <tt>Data</tt> fields</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">First field <tt>Data</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of first field <tt>Data</tt> object</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">Last field <tt>Data</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of last field <tt>Data</tt> object</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="DATA_FLAT_FIELD">DATA_FLAT_FIELD</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th>Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>1</td><td><tt>DATA_FLAT_FIELD</tt></td><td>FlatField/FileFlatField marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>FunctionType index</td><td> </td></tr>
+	<tr><td>1</td><td><a href="#FLD_SET"><tt>FLD_SET</tt></a></td><td>Marker for domain <tt>Set</tt> object.</td><td rowspan="5">Only specified if there is a domain <tt>Set</tt>.</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC"><tt>Set</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of <tt>Set</tt> object</td></tr>
+	<tr><td>1</td><td><a href="#FLD_DATA_SAMPLES"><tt>FLD_DATA_SAMPLES</tt></a></td><td>Marker for list of <tt>Data</tt> fields.</td><td rowspan="11">Only specified if there are <tt>Data</tt> fields.</td></tr>
+	<tr><td>4</td><td>int</td><td><tt>Number of <tt>Data</tt> fields</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">First field <tt>Data</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of first field <tt>Data</tt> object</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">Last field <tt>Data</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of last field <tt>Data</tt> object</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_COORDSYS"><tt>FLD_INDEX_COORDSYS</tt></a></td><td>Marker for <tt>FlatField</tt> <tt>CoordinateSystem</tt> index.</td><td rowspan="2">Only specified if there is a <tt>CoordinateSystem</tt> associated with this <tt>FlatField</tt></td></tr>
+	<tr><td>4</td><td>int</td><td><tt>CoordinateSystem</tt> index</td></tr>
+	<tr><td>1</td><td><a href="#FLD_RANGE_COORDSYSES"><tt>FLD_RANGE_COORDSYS</tt></a></td><td>Marker for list of range <tt>CoordinateSystem</tt>s</td><td rowspan="5">Only specified if there is are <tt>CoordinateSystem</tt>s associated with this <tt>FlatField</tt>'s range.</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>CoordinateSystem</tt> index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>CoordinateSystem</tt> index</td></tr>
+	<tr><td>1</td><td><a href="#FLD_SET_LIST"><tt>FLD_SET_LIST</tt></a></td><td>Marker for list of range default <tt>Set</tt>s.</td><td rowspan="11">Only specified if there are default <tt>Set</tt>s for this <tt>FlatField</tt>'s range.</td></tr>
+	<tr><td>4</td><td>int</td><td><tt>Number of range <tt>Set</tt>s.</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">First <tt>Set</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of first <tt>Set</tt> object</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">Last <tt>Set</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of last <tt>Set</tt> object</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_UNITS"><tt>FLD_INDEX_UNITS</tt></a></td><td>Marker for list of range <tt>Unit</tt>s</td><td rowspan="5">Only specified if there is are <tt>Unit</tt>s associated with this <tt>FlatField</tt>'s range.</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>Unit</tt> index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>Unit</tt> index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="DATA_FLOAT_SET">DATA_FLOAT_SET</a></h3>
+      <i><b>Coming soon.</b></i>
+      <h3>
+	<a name="DATA_GRIDDED_1D_DOUBLE_SET">DATA_GRIDDED_1D_DOUBLE_SET,</a><br>
+	<a name="DATA_GRIDDED_2D_DOUBLE_SET">DATA_GRIDDED_2D_DOUBLE_SET,</a><br>
+	and <a name="DATA_GRIDDED_3D_DOUBLE_SET">DATA_GRIDDED_3D_DOUBLE_SET</a>
+      </h3>
+      <i><b>Coming soon.</b></i>
+      <h3>
+	<a name="DATA_GRIDDED_SET">DATA_GRIDDED_SET,</a><br>
+	<a name="DATA_GRIDDED_1D_SET">DATA_GRIDDED_1D_SET,</a><br>
+	<a name="DATA_GRIDDED_2D_SET">DATA_GRIDDED_2D_SET,</a><br>
+	and <a name="DATA_GRIDDED_3D_SET">DATA_GRIDDED_3D_SET</a>
+      </h3>
+      <i><b>Coming soon.</b></i>
+      <h3>
+	<a name="DATA_INTEGER_1D_SET">DATA_INTEGER_1D_SET,</a><br>
+	<a name="DATA_INTEGER_2D_SET">DATA_INTEGER_2D_SET,</a><br>
+	<a name="DATA_INTEGER_3D_SET">DATA_INTEGER_3D_SET,</a><br>
+	and <a name="DATA_INTEGER_ND_SET">DATA_INTEGER_ND_SET</a>
+      </h3>
+      All four of these Data objects use the same format, differing
+      only in the dimension of their <tt>FLD_LENGTHS</tt> or
+      <tt>FLD_INTEGER_SETS</tt> list.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th colspan="2">Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td><td colspan="2"> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>1</td><td><tt>byte</tt></td><td>Set marker</td><td colspan="2">One of <tt>DATA_INTEGER_1D_SET</tt>, <tt>DATA_INTEGER_2D_SET</tt>, <tt>DATA_INTEGER_3D_SET</tt> or <tt>DATA_INTEGER_ND_SET</tt>, </td></tr>
+	<tr><td>4</td><td>int</td><td>SetType index</td><td colspan="2"> </td></tr>
+	<tr><td>1</td><td><a href="#FLD_LENGTHS"><tt>FLD_LENGTHS</tt></a></td><td>Marker for list of lengths</td><td rowspan="5"> </td><td rowspan="16">At most one of <tt>FLD_LENGTHS</tt> and <tt>FLD_INTEGER_SETS</tt> will be present</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>Set</tt> length</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>Set</tt> length</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INTEGER_SETS"><tt>FLD_INTEGER_SETS</tt></a></td><td>Marker for list of <tt>Integer1DSet</tt>s.</td><td rowspan="11">It is not legal for FLD_INTEGER_1D_SET to contain this list.</td></tr>
+	<tr><td>4</td><td>int</td><td><tt>Number of <tt>Integer1DSet</tt>s</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">First <tt>Integer1DSet</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of first <tt>Integer1DSet</tt> object</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">Last <tt>Integer1DSet</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of last <tt>Integer1DSet</tt> object</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_COORDSYS"><tt>FLD_INDEX_COORDSYS</tt></a></td><td>Marker for integer <tt>Set</tt> <tt>CoordinateSystem</tt> index.</td><td colspan="2" rowspan="2">Only specified if there is a <tt>CoordinateSystem</tt> associated with this integer <tt>Set</tt></td></tr>
+	<tr><td>4</td><td>int</td><td><tt>CoordinateSystem</tt> index</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_UNITS"><tt>FLD_INDEX_UNITS</tt></a></td><td>Marker for list of range <tt>Unit</tt>s</td><td colspan="2" rowspan="5">Only specified if there is are <tt>Unit</tt>s associated with this integer <tt>Set</tt> range.</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>Unit</tt> index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>Unit</tt> index</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_ERRORS"><tt>FLD_INDEX_ERRORS</tt></a></td><td>Marker for list of range <tt>Error</tt>s</td><td colspan="2" rowspan="5">Only specified if there is are <tt>Error</tt>s associated with this integer <tt>Set</tt>.</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>Error</tt> index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>Error</tt> index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td colspan="2"> </td></tr>
+      </table>
+      <h3>
+	<a name="DATA_IRREGULAR_1D_SET">DATA_IRREGULAR_1D_SET,</a><br>
+	<a name="DATA_IRREGULAR_2D_SET">DATA_IRREGULAR_2D_SET,</a><br>
+	and <a name="DATA_IRREGULAR_3D_SET">DATA_IRREGULAR_3D_SET</a>
+      </h3>
+      <i><b>Coming soon.</b></i>
+      <h3>
+	<a name="DATA_LINEAR_1D_SET">DATA_LINEAR_1D_SET,</a><br>
+	<a name="DATA_LINEAR_2D_SET">DATA_LINEAR_2D_SET,</a><br>
+	<a name="DATA_LINEAR_3D_SET">DATA_LINEAR_3D_SET,</a><br>
+	<a name="DATA_LINEAR_ND_SET">DATA_LINEAR_ND_SET,</a><br>
+	and <a name="DATA_LINEAR_LATLON_SET">DATA_LINEAR_LATLON_SET</a>
+      </h3>
+      All five of these Data objects use the same format, differing
+      only in the dimension of their
+      <tt>FLD_FIRSTS</tt>/<tt>FLD_LASTS</tt>/<tt>FLD_LENGTHS</tt> or
+      <tt>FLD_LINEAR_SETS</tt> list.
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th colspan="2">Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td><td colspan="2"> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td colspan="2"> </td></tr>
+	<tr><td>1</td><td><tt>byte</tt></td><td>Set marker</td><td colspan="2">One of <tt>DATA_LINEAR_1D_SET</tt>, <tt>DATA_LINEAR_2D_SET</tt>, <tt>DATA_LINEAR_3D_SET</tt> or <tt>DATA_LINEAR_ND_SET</tt>, </td></tr>
+	<tr><td>4</td><td>int</td><td>SetType index</td><td colspan="2"> </td></tr>
+	<tr><td>1</td><td><a href="#FLD_FIRSTS"><tt>FLD_FIRSTS</tt></a></td><td>Marker for list of lengths</td><td rowspan="5"> </td><td rowspan="26">At most one of <tt>FLD_FIRSTS</tt>/<tt>FLD_LASTS</tt>/<tt>FLD_LENGTHS</tt> and <tt>FLD_LINEAR_SETS</tt> will be present</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First initial <tt>Set</tt> value</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last initial <tt>Set</tt> value</td></tr>
+	<tr><td>1</td><td><a href="#FLD_LASTS"><tt>FLD_LASTS</tt></a></td><td>Marker for list of final <tt>Set</tt> values.</td><td rowspan="5"> </td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First final <tt>Set</tt> value</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last final <tt>Set</tt> value</td></tr>
+	<tr><td>1</td><td><a href="#FLD_LENGTHS"><tt>FLD_LENGTHS</tt></a></td><td>Marker for list of lengths</td><td rowspan="5"> </td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>Set</tt> length</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>Set</tt> length</td></tr>
+	<tr><td>1</td><td><a href="#FLD_LINEAR_SETS"><tt>FLD_LINEAR_SETS</tt></a></td><td>Marker for list of <tt>Linear1DSet</tt>s.</td><td rowspan="11">It is not legal for FLD_LINEAR_1D_SET to contain this list.</td></tr>
+	<tr><td>4</td><td>int</td><td><tt>Number of <tt>Linear1DSet</tt>s</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">First <tt>Linear1DSet</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of first <tt>Linear1DSet</tt> object</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">Last <tt>Linear1DSet</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of last <tt>Linear1DSet</tt> object</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_COORDSYS"><tt>FLD_INDEX_COORDSYS</tt></a></td><td>Marker for integer <tt>Set</tt> <tt>CoordinateSystem</tt> index.</td><td colspan="2" rowspan="2">Only specified if there is a <tt>CoordinateSystem</tt> associated with this integer <tt>Set</tt></td></tr>
+	<tr><td>4</td><td>int</td><td><tt>CoordinateSystem</tt> index</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_UNITS"><tt>FLD_INDEX_UNITS</tt></a></td><td>Marker for list of range <tt>Unit</tt>s</td><td colspan="2" rowspan="5">Only specified if there is are <tt>Unit</tt>s associated with this integer <tt>Set</tt> range.</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>Unit</tt> index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>Unit</tt> index</td></tr>
+	<tr><td>1</td><td><a href="#FLD_INDEX_ERRORS"><tt>FLD_INDEX_ERRORS</tt></a></td><td>Marker for list of range <tt>Error</tt>s</td><td colspan="2" rowspan="5">Only specified if there is are <tt>Error</tt>s associated with this integer <tt>Set</tt>.</td></tr>
+	<tr><td>4</td><td>int</td><td>List length</td></tr>
+	<tr><td>4</td><td>int</td><td>First <tt>Error</tt> index</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>4</td><td>int</td><td>Last <tt>Error</tt> index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td colspan="2"> </td></tr>
+      </table>
+      <h3><a name="DATA_LIST1D_SET">DATA_LIST1D_SET</a></h3>
+      <i><b>Coming soon.</b></i>
+      <h3><a name="DATA_PRODUCT_SET">DATA_PRODUCT_SET</a></h3>
+      <i><b>Coming soon.</b></i>
+      <h3><a name="DATA_REAL">DATA_REAL</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th>Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>1</td><td><tt>DATA_REAL</tt></td><td>Real marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>RealType index</td><td> </td></tr>
+	<tr><td>8</td><td>double</td><td>Value</td><td> </td></tr>
+	<tr><td><i>1</i></td><td><a href="#FLD_INDEX_UNIT"><tt>FLD_INDEX_UNIT</tt></a></td><td>Marker for <tt>Real</tt> <tt>Unit</tt> index.</td><td rowspan="2">Only specified if there is a <tt>Unit</tt> associated with this <tt>Real</tt></td></tr>
+	<tr><td><i>4</i></td><td>int</td><td><tt>Unit</tt> index</td></tr>
+	<tr><td><i>1</i></td><td><a href="#FLD_INDEX_ERROR"><tt>FLD_INDEX_ERROR</tt></a></td><td>Marker for <tt>Real</tt> <tt>ErrorEstimate</tt> index.</td><td rowspan="2">Only specified if there is an <tt>ErrorEstimate</tt> associated with this <tt>Real</tt></td></tr>
+	<tr><td><i>4</i></td><td>int</td><td><tt>ErrorEstimate</tt> index</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="DATA_REAL_TUPLE">DATA_REAL_TUPLE</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th colspan="2">Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td><td colspan="2"> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td colspan="2"> </td></tr>
+	<tr><td>1</td><td><tt>DATA_REAL_TUPLE</tt></td><td>RealTuple marker</td><td colspan="2"> </td></tr>
+	<tr><td>4</td><td>int</td><td>TupleType index</td><td colspan="2"> </td></tr>
+	<tr><td>1</td><td><a href="#FLD_REAL_SAMPLES"><tt>FLD_REAL_SAMPLES</tt></a></td><td>Marker for list of <tt>Real</tt> tuples.</td><td rowspan="11">Only specified if there are non-trivial <tt>Real</tt> tuples</td><td rowspan="16">At most one of <tt>FLD_REAL_SAMPLES</tt> and <tt>FLD_TRIVIAL_SAMPLES</tt> will be present, and it's possible for neither to be present.</tr>
+	<tr><td>4</td><td>int</td><td><tt>Number of <tt>Real</tt> tuples</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td>First <a href="#DATA_REAL"><tt>DATA_REAL</tt></a> marker</td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of first <tt>Real</tt> object</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td>Last <a href="#DATA_REAL"><tt>DATA_REAL</tt></a> marker</td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of last <tt>Real</tt> object</td></tr>
+	<tr><td>1</td><td><a href="#FLD_TRIVIAL_SAMPLES"><tt>FLD_TRIVIAL_SAMPLES</tt></a></td><td>Marker for list of <tt>Real</tt> values.</td><td rowspan="5">Only specified if there are trivial <tt>Real</tt> tuples</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of <tt>Real</tt> values.</td></tr>
+	<tr><td>8</td><td>double</td><td>First tuple value</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>8</td><td>double</td><td>Last tuple value</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td><td> </td></tr>
+      </table>
+      <h3><a name="DATA_SINGLETON_SET">DATA_SINGLETON_SET</a></h3>
+      <i><b>Coming soon.</b></i>
+      <h3><a name="DATA_TEXT">DATA_TEXT</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td><tt>DATA_TEXT</tt></td><td>Text marker</td></tr>
+	<tr><td>4</td><td>int</td><td>TextType index</td></tr>
+	<tr><td>4</td><td>int</td><td>Text string length</td></tr>
+	<tr><td>len</td><td>string</td><td>Text string</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="DATA_TUPLE">DATA_TUPLE</a></h3>
+      <table border=1>
+	<tr><th>Bytes</th><th>Type</th><th>Description</th><th>Comment</th></tr>
+	<tr><td>1</td><td><tt><a href="#OBJ_DATA">OBJ_DATA</a></tt></td><td>Start-of-object marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td><td> </td></tr>
+	<tr><td>1</td><td><tt>DATA_TUPLE</tt></td><td>Tuple marker</td><td> </td></tr>
+	<tr><td>4</td><td>int</td><td>TupleType index</td><td> </td></tr>
+	<tr><td>1</td><td><a href="#FLD_DATA_SAMPLES"><tt>FLD_DATA_SAMPLES</tt></a></td><td>Marker for list of <tt>Data</tt> tuples.</td><td rowspan="11">Only specified if there are <tt>Data</tt> tuples</td></tr>
+	<tr><td>4</td><td>int</td><td><tt>Number of <tt>Data</tt> tuples</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">First tuple <tt>Data</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of first tuple <tt>Data</tt> object</td></tr>
+	<tr><td colspan="3">...</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#OBJ_DATA">OBJ_DATA</a></td></tr>
+	<tr><td>4</td><td>int</td><td>Number of bytes before end of object</td></tr>
+	<tr><td>1</td><td>byte</td><td><a href="#ApdxC">Last Tuple <tt>Data</tt> object byte value</a></td></tr>
+	<tr><td>?</td><td>--</td><td>Remainder of last Tuple <tt>Data</tt> object</td></tr>
+	<tr><td>1</td><td><tt>FLD_END</tt></td><td>End-of-object marker</td><td> </td></tr>
+      </table>
+      <h3><a name="DATA_UNION_SET">DATA_UNION_SET</a></h3>
+      <i><b>Coming soon.</b></i>
+    </div>
+    <hr>
+    <div>
+      <h2><tt>Data</tt> Field Notes</h2>
+      <h3><a name="FLD_SET_FOLLOWS_TYPE">FLD_SET_FOLLOWS_TYPE</a></h3>
+      Because some <tt>MathType</tt>s contain a default <tt>Set</tt>
+      with uses that <tt>MathType</tt> in the definition of its
+      <tt>SetType</tt>, that <tt>Set</tt> cannot be encapsulated
+      within the <tt>MathType</tt>'s object.  Instead, a
+      <tt>FLD_SET_FOLLOWS_TYPE</tt> byte indicates that, immediately
+      after the end of the <tt>MATH_*</tt> object's <tt>FLD_END</tt>
+      marker, an </tt>OBJ_DATA</tt> object can be found which will
+      be used to build this <tt>MathType</tt>'s default <tt>Set</tt>
+      object.
+    </div>
+    <h2><a name="ApdxA">Appendix A: Object Type Byte Values</a></h2>
+    <table border=1>
+      <tr><th>Name</th><th>Value</th><th>    </th><th>Name</th><th>Value</th></tr>
+      <tr><td>OBJ_COORDSYS</td><td>1</td><td></td><td>OBJ_ERROR</td><td>4</td></tr>
+      <tr><td>OBJ_DATA</td><td>2</td><td></td><td>OBJ_MATH</td><td>5</td></tr>
+      <tr><td>OBJ_DATA_SERIAL</td><td>3</td><td></td><td>OBJ_MATH_SERIAL</td><td>6</td></tr>
+      <tr><td>OBJ_ERROR</td><td>4</td><td></td><td>OBJ_UNIT</td><td>7</td></tr>
+    </table>
+    <h2><a name="ApdxB">Appendix B: MathType Byte Values</a></h2>
+    <table border=1>
+      <tr><th>Name</th><th>Value</th><th>    </th><th>Name</th><th>Value</th></tr>
+      <tr><td>MATH_FUNCTION</td><td>1</td><td></td><td>MATH_SET</td><td>4</td></tr>
+      <tr><td>MATH_REAL</td><td>2</td><td></td><td>MATH_TEXT</td><td>5</td></tr>
+      <tr><td>MATH_REAL_TUPLE</td><td>3</td><td></td><td>MATH_TUPLE</td><td>6</td></tr>
+      <tr><td>MATH_SET</td><td>4</td><td></td><td>MATH_QUANTITY</td><td>7</td></tr>
+    </table>
+    <h2><a name="ApdxC">Appendix C: Data Object Byte Values</a></h2>
+    <table border=1>
+      <tr><th>Name</th><th>Value</th><th>    </th><th>Name</th><th>Value</th></tr>
+      <tr><td>DATA_TEXT</td><td>1</td><td></td><td>DATA_GRIDDED_SET</td><td>40</td></tr>
+      <tr><td>DATA_REAL</td><td>2</td><td></td><td>DATA_GRIDDED_1D_SET</td><td>41</td></tr>
+      <tr><td>DATA_TUPLE</td><td>10</td><td></td><td>DATA_GRIDDED_2D_SET</td><td>42</td></tr>
+      <tr><td>DATA_REAL_TUPLE</td><td>11</td><td></td><td>DATA_GRIDDED_3D_SET</td><td>43</td></tr>
+      <tr><td>DATA_FIELD</td><td>20</td><td></td><td>DATA_GRIDDED_1D_DOUBLE_SET</td><td>44</td></tr>
+      <tr><td>DATA_FLAT_FIELD</td><td>21</td><td></td><td>DATA_GRIDDED_2D_DOUBLE_SET</td><td>45</td></tr>
+      <tr><td>DATA_DOUBLE_SET</td><td>30</td><td></td><td>DATA_GRIDDED_3D_DOUBLE_SET</td><td>46</td></tr>
+      <tr><td>DATA_FLOAT_SET</td><td>31</td><td></td><td>DATA_LINEAR_1D_SET</td><td>47</td></tr>
+      <tr><td>DATA_LIST1D_SET</td><td>32</td><td></td><td>DATA_LINEAR_2D_SET</td><td>48</td></tr>
+      <tr><td>DATA_SINGLETON_SET</td><td>33</td><td></td><td>DATA_LINEAR_3D_SET</td><td>49</td></tr>
+      <tr><td>DATA_UNION_SET</td><td>34</td><td></td><td>DATA_LINEAR_ND_SET</td><td>50</td></tr>
+      <tr><td>DATA_PRODUCT_SET</td><td>35</td><td></td><td>DATA_LINEAR_LATLON_SET</td><td>51</td></tr>
+      <tr><td>DATA_IRREGULAR_SET</td><td>36</td><td></td><td>DATA_INTEGER_1D_SET</td><td>52</td></tr>
+      <tr><td>DATA_IRREGULAR_1D_SET</td><td>37</td><td></td><td>DATA_INTEGER_2D_SET</td><td>53</td></tr>
+      <tr><td>DATA_IRREGULAR_2D_SET</td><td>38</td><td></td><td>DATA_INTEGER_3D_SET</td><td>54</td></tr>
+      <tr><td>DATA_IRREGULAR_3D_SET</td><td>39</td><td></td><td>DATA_INTEGER_ND_SET</td><td>55</td></tr>
+      <tr><td>DATA_GRIDDED_SET</td><td>40</td><td></td><td>DATA_NONE</td><td>60</td></tr>
+    </table>
+    <h2><a name="ApdxD">Appendix D: Object Field Byte Values</a></h2>
+    <table border=1>
+      <tr><th>Name</th><th>Value</th><th>    </th><th>Name</th><th>Value</th></tr>
+      <tr><td>FLD_FIRSTS</td><td>1</td><td></td><td>FLD_COORDSYS_SERIAL</td><td>20</td></tr>
+      <tr><td>FLD_LASTS</td><td>2</td><td></td><td>FLD_DELAUNAY_SERIAL</td><td>21</td></tr>
+      <tr><td>FLD_LENGTHS</td><td>3</td><td></td><td>FLD_INDEX_UNIT</td><td>30</td></tr>
+      <tr><td>FLD_FLOAT_LIST</td><td>4</td><td></td><td>FLD_INDEX_ERROR</td><td>31</td></tr>
+      <tr><td>FLD_SAMPLE</td><td>5</td><td></td><td>FLD_INDEX_COORDSYS</td><td>32</td></tr>
+      <tr><td>FLD_FLOAT_SAMPLES</td><td>6</td><td></td><td>FLD_INDEX_UNITS</td><td>40</td></tr>
+      <tr><td>FLD_DOUBLE_SAMPLES</td><td>7</td><td></td><td>FLD_INDEX_ERRORS</td><td>41</td></tr>
+      <tr><td>FLD_DATA_SAMPLES</td><td>8</td><td></td><td>FLD_RANGE_COORDSYSES</td><td>50</td></tr>
+      <tr><td>FLD_REAL_SAMPLES</td><td>9</td><td></td><td>FLD_DELAUNAY</td><td>60</td></tr>
+      <tr><td>FLD_TRIVIAL_SAMPLES</td><td>10</td><td></td><td>FLD_DELAUNAY_TRI</td><td>61</td></tr>
+      <tr><td>FLD_SET_SAMPLES</td><td>11</td><td></td><td>FLD_DELAUNAY_VERTICES</td><td>62</td></tr>
+      <tr><td>FLD_SET</td><td>12</td><td></td><td>FLD_DELAUNAY_WALK</td><td>63</td></tr>
+      <tr><td>FLD_LINEAR_SETS</td><td>13</td><td></td><td>FLD_DELAUNAY_EDGES</td><td>64</td></tr>
+      <tr><td>FLD_INTEGER_SETS</td><td>14</td><td></td><td>FLD_DELAUNAY_NUM_EDGES</td><td>65</td></tr>
+      <tr><td>FLD_SET_LIST</td><td>15</td><td></td><td>FLD_SET_FOLLOWS_TYPE</td><td>70</td></tr>
+      <tr><td>FLD_COORDSYS_SERIAL</td><td>20</td><td></td><td>FLD_END</td><td>80</td></tr>
+    </table>
+  </body>
+</html>
diff --git a/visad/data/visad/object/BinaryCoordinateSystem.java b/visad/data/visad/object/BinaryCoordinateSystem.java
new file mode 100644
index 0000000..706be34
--- /dev/null
+++ b/visad/data/visad/object/BinaryCoordinateSystem.java
@@ -0,0 +1,209 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryCoordinateSystem
+  implements BinaryObject
+{
+  public static final int computeBytes(CoordinateSystem cSys)
+  {
+    try {
+      return computeBytes(BinarySerializedObject.getBytes(cSys));
+    } catch (IOException ioe) {
+      return 0;
+    }
+  }
+
+  private static final int computeBytes(byte[] serialObj)
+  {
+    return 4 + 1 +
+      serialObj.length +
+      1;
+  }
+
+  public static final int computeBytes(CoordinateSystem[] array)
+  {
+    return BinaryIntegerArray.computeBytes(array);
+  }
+
+  public static final int[] lookupList(BinaryObjectCache cache,
+                                       CoordinateSystem[] cSys)
+  {
+    // make sure there's something to write
+    boolean empty = true;
+    for (int i = 0; i < cSys.length; i++) {
+      if (cSys[i] != null) {
+        empty = false;
+        break;
+      }
+    }
+    if (empty) {
+      return null;
+    }
+
+    int[] indices = new int[cSys.length];
+
+    for (int i = 0; i < cSys.length; i++) {
+      if (cSys[i] == null) {
+        indices[i] = -1;
+      } else {
+        indices[i] = cache.getIndex(cSys[i]);
+      }
+    }
+
+    return indices;
+  }
+
+  public static final CoordinateSystem read(BinaryReader reader)
+    throws IOException
+  {
+    BinaryObjectCache cache = reader.getCoordinateSystemCache();
+    DataInput file = reader.getInput();
+
+    final int objLen = file.readInt();
+if(DEBUG_RD_CSYS)System.err.println("cchCS: objLen (" + objLen + ")");
+    final int index = file.readInt();
+if(DEBUG_RD_CSYS)System.err.println("cchCS: index (" + index + ")");
+
+    final byte cSysSerial = file.readByte();
+    if (cSysSerial != FLD_COORDSYS_SERIAL) {
+      throw new IOException("Corrupted file (no CoordinateSystem serial marker)");
+    }
+if(DEBUG_RD_CSYS)System.err.println("cchCS: FLD_COORDSYS_SERIAL (" + FLD_COORDSYS_SERIAL + ")");
+
+    // read the CoordinateSystem data
+if(DEBUG_RD_CSYS)System.err.println("cchCS: serialObj (" + (objLen-6) + " bytes)");
+    CoordinateSystem cs;
+    cs = (CoordinateSystem )BinarySerializedObject.read(file, (objLen-6)+1);
+
+    cache.add(index, cs);
+
+    return cs;
+  }
+
+  public static final CoordinateSystem[] readList(BinaryReader reader)
+    throws IOException
+  {
+    BinaryObjectCache cache = reader.getCoordinateSystemCache();
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_CSYS)System.err.println("rdCSysS: len ("+len+")");
+    if (len < 1) {
+      throw new IOException("Corrupted file" +
+                            " (bad CoordinateSystem array length " + len +
+                            ")");
+    }
+
+    CoordinateSystem[] cSys = new CoordinateSystem[len];
+    for (int i = 0; i < len; i++) {
+      final int uIndex = file.readInt();
+if(DEBUG_RD_CSYS)System.err.println("rdCSysS: cSys index ("+uIndex+")");
+      cSys[i] = (CoordinateSystem )cache.get(uIndex);
+if(DEBUG_RD_CSYS)System.err.println("rdCSysS: === #"+i+": "+cSys[i]+")");
+    }
+
+    return cSys;
+  }
+
+  public static final int write(BinaryWriter writer, CoordinateSystem cSys,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getCoordinateSystemCache();
+
+    int index = cache.getIndex(cSys);
+    if (index >= 0) {
+      return index;
+    }
+
+    // cache the CoordinateSystem so we can find its index number
+    index = cache.add(cSys);
+    if (index < 0) {
+      throw new IOException("Couldn't cache CoordinateSystem " + cSys);
+    }
+
+    DataOutput file = writer.getOutput();
+
+    byte[] serialObj = BinarySerializedObject.getBytes(cSys);
+
+    // this copies the code from computeBytes()
+    final int objLen = computeBytes(serialObj);
+
+if(DEBUG_WR_CSYS)System.err.println("wrCSys: OBJ_COORDSYS (" + OBJ_COORDSYS + ")");
+    file.writeByte(OBJ_COORDSYS);
+if(DEBUG_WR_CSYS)System.err.println("wrCSys: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_CSYS)System.err.println("wrCSys: index (" + index + ")");
+    file.writeInt(index);
+
+if(DEBUG_WR_CSYS)System.err.println("wrCSys: FLD_COORDSYS_SERIAL (" + FLD_COORDSYS_SERIAL + ")");
+    file.writeByte(FLD_COORDSYS_SERIAL);
+if(DEBUG_WR_CSYS)System.err.println("wrCSys: serialObj (" + serialObj.length + " bytes)");
+    file.write(serialObj);
+
+if(DEBUG_WR_CSYS)System.err.println("wrCSys: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+
+    return index;
+  }
+
+  public static final int[] writeList(BinaryWriter writer,
+                                      CoordinateSystem[] cSys, Object token)
+    throws IOException
+  {
+    // make sure there's something to write
+    boolean empty = true;
+    for (int i = 0; i < cSys.length; i++) {
+      if (cSys[i] != null) {
+        empty = false;
+        break;
+      }
+    }
+    if (empty) {
+      return null;
+    }
+
+    int[] indices = new int[cSys.length];
+
+    for (int i = 0; i < cSys.length; i++) {
+      if (cSys[i] == null) {
+        indices[i] = -1;
+      } else {
+        indices[i] = write(writer, cSys[i], token);
+      }
+    }
+
+    return indices;
+  }
+}
diff --git a/visad/data/visad/object/BinaryDataArray.java b/visad/data/visad/object/BinaryDataArray.java
new file mode 100644
index 0000000..cc68b04
--- /dev/null
+++ b/visad/data/visad/object/BinaryDataArray.java
@@ -0,0 +1,112 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.VisADException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryDataArray
+  implements BinaryObject
+{
+  public static final int computeBytes(Data[] array)
+  {
+    int len = 4;
+    for (int i = 0; i < array.length; i++) {
+      len += BinaryGeneric.computeBytes((DataImpl )array[i]);
+    }
+    return len;
+  }
+
+  public static final Data[] read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdDataRA: len (" + len + ")");
+    if (len < 1) {
+      throw new IOException("Corrupted file (bad Data array length " +
+                            len + ")");
+    }
+
+long t = (DEBUG_RD_TIME ? System.currentTimeMillis() : 0);
+    Data[] array = new Data[len];
+    for (int i = 0; i < len; i++) {
+
+if(DEBUG_WR_DATA)System.err.println("rdDataRA#"+i);
+      array[i] = BinaryGeneric.read(reader);
+if(DEBUG_WR_DATA_DETAIL)System.err.println("rdDataRA: #" + i + " (" + array[i] + ")");
+
+if(DEBUG_WR_DATA)System.err.println("rdDataRA#"+i+": "+array[i].getClass().getName());
+    }
+if(DEBUG_RD_TIME)System.err.println("rdDataRA: "+len+" elements "+(System.currentTimeMillis()-t));
+
+    return array;
+  }
+
+  private static final void writeDependentData(BinaryWriter writer,
+                                               Data[] array, Object token)
+    throws IOException
+  {
+    if (token != SAVE_DEPEND_BIG) {
+      token = SAVE_DEPEND;
+    }
+
+    if (array != null) {
+      for (int i = 0; i < array.length; i++) {
+        BinaryGeneric.write(writer, (DataImpl )array[i], token);
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, Data[] array,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, array, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrDataRA: len (" + array.length + ")");
+    file.writeInt(array.length);
+    for (int i = 0; i < array.length; i++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrDataRA: #" + i + " (" + array[i] + ")");
+
+if(DEBUG_WR_DATA)System.err.println("wrDataRA#"+i+": "+array[i].getClass().getName());
+      BinaryGeneric.write(writer, (DataImpl )array[i], token);
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryDelaunay.java b/visad/data/visad/object/BinaryDelaunay.java
new file mode 100644
index 0000000..d2a1207
--- /dev/null
+++ b/visad/data/visad/object/BinaryDelaunay.java
@@ -0,0 +1,152 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.Delaunay;
+import visad.DelaunayClarkson;
+import visad.DelaunayCustom;
+import visad.DelaunayFast;
+import visad.DelaunayOverlap;
+import visad.DelaunayWatson;
+import visad.VisADException;
+
+import visad.data.visad.BinaryWriter;
+
+public class BinaryDelaunay
+  implements BinaryObject
+{
+  public static final int computeBytes(Delaunay d)
+  {
+    if (!isKnownClass(d)) {
+      return BinarySerializedObject.computeBytes(d);
+    }
+
+    return (1 +
+            1 + BinaryIntegerMatrix.computeBytes(d.Tri) +
+            1 + BinaryIntegerMatrix.computeBytes(d.Vertices) +
+            1 + BinaryIntegerMatrix.computeBytes(d.Walk) +
+            1 + BinaryIntegerMatrix.computeBytes(d.Edges) +
+            6);
+  }
+
+  public static final Delaunay read(DataInput file)
+    throws IOException, VisADException
+  {
+    int[][] tri = null;
+    int[][] verts = null;
+    int[][] walk = null;
+    int[][] edges = null;
+    int numEdges = -1;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_DELAUNAY_TRI:
+if(DEBUG_RD_DATA)System.err.println("rdDel: FLD_DELAUNAY_TRI (" + FLD_DELAUNAY_TRI + ")");
+        tri = BinaryIntegerMatrix.read(file);
+        break;
+      case FLD_DELAUNAY_VERTICES:
+if(DEBUG_RD_DATA)System.err.println("rdDel: FLD_DELAUNAY_VERTICES (" + FLD_DELAUNAY_VERTICES + ")");
+        verts = BinaryIntegerMatrix.read(file);
+        break;
+      case FLD_DELAUNAY_WALK:
+if(DEBUG_RD_DATA)System.err.println("rdDel: FLD_DELAUNAY_WALK (" + FLD_DELAUNAY_WALK + ")");
+        walk = BinaryIntegerMatrix.read(file);
+        break;
+      case FLD_DELAUNAY_EDGES:
+if(DEBUG_RD_DATA)System.err.println("rdDel: FLD_DELAUNAY_EDGES (" + FLD_DELAUNAY_EDGES + ")");
+        edges = BinaryIntegerMatrix.read(file);
+        break;
+      case FLD_DELAUNAY_NUM_EDGES:
+if(DEBUG_RD_DATA)System.err.println("rdDel: FLD_DELAUNAY_NUM_EDGES (" + FLD_DELAUNAY_NUM_EDGES + ")");
+        numEdges = file.readInt();
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdDel: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown Delaunay directive " +
+                              directive);
+      }
+    }
+
+    return new DelaunayCustom(null, tri, verts, walk, edges, numEdges);
+  }
+
+  private static final boolean isKnownClass(Delaunay delaunay)
+  {
+    final Class dClass = delaunay.getClass();
+
+    return (dClass.equals(DelaunayClarkson.class) ||
+            dClass.equals(DelaunayCustom.class) ||
+            dClass.equals(DelaunayFast.class) ||
+            dClass.equals(DelaunayOverlap.class) ||
+            dClass.equals(DelaunayWatson.class));
+  }
+
+  public static final void write(BinaryWriter writer, Delaunay delaunay,
+                                 Object token)
+    throws IOException
+  {
+    if (!isKnownClass(delaunay)) {
+      /* serialize non-standard Delaunay object */
+      BinarySerializedObject.write(writer, FLD_DELAUNAY_SERIAL, delaunay,
+                                   token);
+      return;
+    }
+
+    DataOutput file = writer.getOutput();
+
+    file.writeByte(FLD_DELAUNAY);
+
+    file.writeByte(FLD_DELAUNAY_TRI);
+    BinaryIntegerMatrix.write(file, delaunay.Tri);
+
+    file.writeByte(FLD_DELAUNAY_VERTICES);
+    BinaryIntegerMatrix.write(file, delaunay.Vertices);
+
+    file.writeByte(FLD_DELAUNAY_WALK);
+    BinaryIntegerMatrix.write(file, delaunay.Walk);
+
+    file.writeByte(FLD_DELAUNAY_EDGES);
+    BinaryIntegerMatrix.write(file, delaunay.Edges);
+
+    file.writeByte(FLD_DELAUNAY_NUM_EDGES);
+    file.writeInt(delaunay.NumEdges);
+
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryDisplayRealType.java b/visad/data/visad/object/BinaryDisplayRealType.java
new file mode 100644
index 0000000..b16ea5f
--- /dev/null
+++ b/visad/data/visad/object/BinaryDisplayRealType.java
@@ -0,0 +1,59 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.IOException;
+
+import visad.DisplayRealType;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryDisplayRealType
+  implements BinaryObject
+{
+  public static final int computeBytes(DisplayRealType drt)
+  {
+    return BinarySerializedObject.computeBytes(drt);
+  }
+
+  public static final int write(BinaryWriter writer, DisplayRealType drt,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(drt);
+    if (index < 0) {
+      index = cache.add(drt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache DisplayRealType " + drt);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrDpyRTy: serialized DisplayRealType");
+      BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, drt, token);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryDisplayTupleType.java b/visad/data/visad/object/BinaryDisplayTupleType.java
new file mode 100644
index 0000000..f9dbb01
--- /dev/null
+++ b/visad/data/visad/object/BinaryDisplayTupleType.java
@@ -0,0 +1,54 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.IOException;
+
+import visad.DisplayTupleType;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryDisplayTupleType
+  implements BinaryObject
+{
+  public static final int write(BinaryWriter writer, DisplayTupleType dtt,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(dtt);
+    if (index < 0) {
+      index = cache.add(dtt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache DisplayTupleType " + dtt);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrDpyTuTy: serialized DisplayTupleType");
+      BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, dtt, token);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryDoubleArray.java b/visad/data/visad/object/BinaryDoubleArray.java
new file mode 100644
index 0000000..b0a57dc
--- /dev/null
+++ b/visad/data/visad/object/BinaryDoubleArray.java
@@ -0,0 +1,105 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryDoubleArray
+  implements BinaryObject
+{
+  public static final int computeBytes(double[] array)
+  {
+    return (array == null ? 0 : 4 + (array.length * 8));
+  }
+
+  public static final double[] read(BinaryReader reader)
+    throws IOException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdDblRA: len (" + len + ")");
+    if (len < 1) {
+      throw new IOException("Corrupted file (bad double array length " +
+                            len + ")");
+    }
+
+    double[] array = new double[len];
+    for (int i = 0; i < len; i++) {
+      array[i] = file.readDouble();
+if(DEBUG_RD_DATA_DETAIL)System.err.println("rdDblRA: #" + i +" (" + array[i] + ")");
+    }
+
+    return array;
+  }
+
+  private static final boolean fasterButUglier = true;
+
+  public static final void write(BinaryWriter writer, double[] array,
+                                 Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    if (fasterButUglier) {
+      byte[] buf = new byte[computeBytes(array)];
+      int bufIdx = 0;
+
+if(DEBUG_WR_DATA)System.err.println("wrDblRA: len (" + array.length + ")");
+      for (int b = 3, l = array.length; b >= 0; b--) {
+        buf[bufIdx + b] = (byte )(l & 0xff);
+        l >>= 8;
+      }
+      bufIdx += 4;
+
+      for (int i = 0; i < array.length; i++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrDblRA: #" + i + " (" + array[i] + ")");
+        long x = Double.doubleToLongBits(array[i]);
+        for (int b = 7; b >= 0; b--) {
+          buf[bufIdx + b] = (byte )(x & 0xff);
+          x >>= 8;
+        }
+        bufIdx += 8;
+      }
+
+      if (bufIdx < buf.length) {
+        System.err.println("BinaryDoubleArray: Missing " +
+                           (buf.length - bufIdx) + " bytes");
+      }
+
+      file.write(buf);
+    } else { // !fasterButUglier
+if(DEBUG_WR_DATA)System.err.println("wrDblRA: len (" + array.length + ")");
+      file.writeInt(array.length);
+      for (int i = 0; i < array.length; i++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrDblRA: #" + i + " (" + array[i] + ")");
+        file.writeDouble(array[i]);
+      }
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryDoubleMatrix.java b/visad/data/visad/object/BinaryDoubleMatrix.java
new file mode 100644
index 0000000..f9c429e
--- /dev/null
+++ b/visad/data/visad/object/BinaryDoubleMatrix.java
@@ -0,0 +1,138 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryDoubleMatrix
+  implements BinaryObject
+{
+  public static final int computeBytes(double[][] matrix)
+  {
+    if (matrix == null) {
+      return 4;
+    }
+
+    int len = 4;
+    for (int i = 0; i < matrix.length; i++) {
+      len += 4 + (matrix[i].length * 8);
+    }
+
+    return len;
+  }
+
+  public static final double[][] read(BinaryReader reader)
+    throws IOException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdDblMtx: len (" + len + ")");
+    if (len < 0) {
+      return null;
+    }
+
+    double[][] matrix = new double[len][];
+    for (int i = 0; i < len; i++) {
+      final int len2 = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdDblMtx: #" + i + " len (" + len2 + ")");
+      matrix[i] = new double[len2];
+      for (int j = 0; j < len2; j++) {
+        matrix[i][j] = file.readDouble();
+if(DEBUG_RD_DATA_DETAIL)System.err.println("rdDblMtx: #" + i + "," + j +" (" + matrix[i][j] + ")");
+      }
+    }
+
+    return matrix;
+  }
+
+  private static final boolean fasterButUglier = true;
+
+  public static final void write(BinaryWriter writer, double[][] matrix,
+                                 Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    if (matrix == null) {
+if(DEBUG_WR_DATA)System.err.println("wrDblMtx: null (" + -1 + ")");
+      file.writeInt(-1);
+    } else {
+      if (fasterButUglier) {
+        byte[] buf = new byte[computeBytes(matrix)];
+        int bufIdx = 0;
+
+if(DEBUG_WR_DATA)System.err.println("wrDblMtx: row len (" + matrix.length + ")");
+        for (int b = 3, l = matrix.length; b >= 0; b--) {
+          buf[bufIdx + b] = (byte )(l & 0xff);
+          l >>= 8;
+        }
+        bufIdx += 4;
+
+        for (int i = 0; i < matrix.length; i++) {
+          final int len = matrix[i].length;
+if(DEBUG_WR_DATA)System.err.println("wrDblMtx: #" + i + " len (" + matrix[i].length + ")");
+          for (int b = 3, l = len; b >= 0; b--) {
+            buf[bufIdx + b] = (byte )(l & 0xff);
+            l >>= 8;
+          }
+          bufIdx += 4;
+
+          for (int j = 0; j < len; j++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrDblMtx: #" + i + "," + j + " (" + matrix[i][j] + ")");
+            long x = Double.doubleToLongBits(matrix[i][j]);
+            for (int b = 7; b >= 0; b--) {
+              buf[bufIdx + b] = (byte )(x & 0xff);
+              x >>= 8;
+            }
+            bufIdx += 8;
+          }
+        }
+
+        if (bufIdx < buf.length) {
+          System.err.println("BinaryDoubleMatrix: Missing " +
+                             (buf.length - bufIdx) + " bytes");
+        }
+
+        file.write(buf);
+      } else { // !fasterButUglier
+if(DEBUG_WR_DATA)System.err.println("wrDblMtx: row len (" + matrix.length + ")");
+        file.writeInt(matrix.length);
+        for (int i = 0; i < matrix.length; i++) {
+          final int len = matrix[i].length;
+if(DEBUG_WR_DATA)System.err.println("wrDblMtx: #" + i + " len (" + matrix[i].length + ")");
+          file.writeInt(len);
+          for (int j = 0; j < len; j++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrDblMtx: #" + i + "," + j + " (" + matrix[i][j] + ")");
+            file.writeDouble(matrix[i][j]);
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryErrorEstimate.java b/visad/data/visad/object/BinaryErrorEstimate.java
new file mode 100644
index 0000000..68b3c38
--- /dev/null
+++ b/visad/data/visad/object/BinaryErrorEstimate.java
@@ -0,0 +1,243 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.ErrorEstimate;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryErrorEstimate
+  implements BinaryObject
+{
+  public static final int computeBytes(int uIndex)
+  {
+    return 28 + (uIndex < 0 ? 0 : 5) + 1;
+  }
+
+  public static final int computeBytes(ErrorEstimate[] array)
+  {
+    return BinaryIntegerArray.computeBytes(array);
+  }
+
+  public static final int[] lookupList(BinaryObjectCache cache,
+                                       ErrorEstimate[] errors)
+  {
+    // make sure there's something to write
+    boolean empty = true;
+    for (int i = 0; i < errors.length; i++) {
+      if (errors[i] != null) {
+        empty = false;
+        break;
+      }
+    }
+    if (empty) {
+      return null;
+    }
+
+    int[] indices = new int[errors.length];
+
+    for (int i = 0; i < errors.length; i++) {
+      if (errors[i] == null) {
+        indices[i] = -1;
+      } else {
+        indices[i] = cache.getIndex(errors[i]);
+      }
+    }
+
+    return indices;
+  }
+
+  public static final ErrorEstimate read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache errorCache = reader.getErrorEstimateCache();
+    BinaryObjectCache unitCache = reader.getUnitCache();
+    DataInput file = reader.getInput();
+
+    final int objLen = file.readInt();
+if(DEBUG_RD_ERRE)System.err.println("cchErrEst: objLen (" + objLen + ")");
+    // read the index number for this ErrorEstimate
+    final int index = file.readInt();
+if(DEBUG_RD_ERRE)System.err.println("cchErrEst: index (" + index + ")");
+
+    // read the ErrorEstimate data
+    final double errValue = file.readDouble();
+if(DEBUG_RD_ERRE)System.err.println("cchErrEst: value (" + errValue + ")");
+    final double mean = file.readDouble();
+if(DEBUG_RD_ERRE)System.err.println("cchErrEst: mean (" + mean + ")");
+    final long number = file.readLong();
+if(DEBUG_RD_ERRE)System.err.println("cchErrEst: number (" + number + ")");
+
+    Unit u = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive = file.readByte();
+
+      switch (directive) {
+      case FLD_INDEX_UNIT:
+        final int uIndex = file.readInt();
+if(DEBUG_RD_ERRE&&DEBUG_RD_UNIT)System.err.println("cchErrEst: unit index ("+uIndex+")");
+        u = (Unit )unitCache.get(uIndex);
+if(DEBUG_RD_ERRE&&!DEBUG_RD_UNIT)System.err.println("cchErrEst: unit index ("+uIndex+"="+u+")");
+        break;
+      case FLD_END:
+if(DEBUG_RD_ERRE)System.err.println("cchErrEst: FLD_END ("+FLD_END+")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown ErrorEstimate directive " + directive);
+      }
+    }
+
+    ErrorEstimate err = new ErrorEstimate(errValue, mean, number, u);
+
+    errorCache.add(index, err);
+
+    return err;
+  }
+
+  public static final ErrorEstimate[] readList(BinaryReader reader)
+    throws IOException
+  {
+    BinaryObjectCache cache = reader.getErrorEstimateCache();
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_ERRE)System.err.println("rdErrEstS: len (" + len + ")");
+    if (len < 1) {
+      throw new IOException("Corrupted file" +
+                            " (bad ErrorEstimate array length " + len + ")");
+    }
+
+    ErrorEstimate[] errs = new ErrorEstimate[len];
+    for (int i = 0; i < len; i++) {
+      final int index = file.readInt();
+if(DEBUG_RD_ERRE)System.err.println("rdErrEstS:    #"+i+" index ("+index+")");
+
+      if (index < 0) {
+        errs[i] = null;
+      } else {
+        errs[i] = (ErrorEstimate )cache.get(index);
+      }
+if(DEBUG_RD_ERRE)System.err.println("rdErrEstS:    === #"+i+" ErrorEstimate ("+errs[i]+")");
+    }
+
+    return errs;
+  }
+
+  public static final int write(BinaryWriter writer, ErrorEstimate error,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getErrorEstimateCache();
+
+    int index = cache.getIndex(error);
+    if (index >= 0) {
+      return index;
+    }
+
+    // cache the ErrorEstimate so we can find its index number
+    index = cache.add(error);
+    if (index < 0) {
+      throw new IOException("Couldn't cache ErrorEstimate " + error);
+    }
+
+    double errValue = error.getErrorValue();
+    double mean = error.getMean();
+    long number = error.getNumberNotMissing();
+    Unit unit = error.getUnit();
+
+    int uIndex = -1;
+    if (unit != null) {
+      uIndex = BinaryUnit.write(writer, unit, token);
+    }
+
+    final int objLen = computeBytes(uIndex);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: OBJ_ERROR (" + OBJ_ERROR + ")");
+    file.writeByte(OBJ_ERROR);
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: index (" + index + ")");
+    file.writeInt(index);
+
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: error value (" + errValue + ")");
+    file.writeDouble(errValue);
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: error mean (" + mean + ")");
+    file.writeDouble(mean);
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: error number (" + number + ")");
+    file.writeLong(number);
+
+    if (uIndex >= 0) {
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: FLD_INDEX_UNIT (" + FLD_INDEX_UNIT + ")");
+      file.writeByte(FLD_INDEX_UNIT);
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: unit index (" + uIndex + ")");
+      file.writeInt(uIndex);
+    }
+
+if(DEBUG_WR_ERRE)System.err.println("wrErrEst: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+
+    return index;
+  }
+
+  public static final int[] writeList(BinaryWriter writer,
+                                      ErrorEstimate[] errors, Object token)
+    throws IOException
+  {
+    // make sure there's something to write
+    boolean empty = true;
+    for (int i = 0; i < errors.length; i++) {
+      if (errors[i] != null) {
+        empty = false;
+        break;
+      }
+    }
+    if (empty) {
+      return null;
+    }
+
+    int[] indices = new int[errors.length];
+
+    for (int i = 0; i < errors.length; i++) {
+      if (errors[i] == null) {
+        indices[i] = -1;
+      } else {
+        indices[i] = BinaryErrorEstimate.write(writer, errors[i], token);
+      }
+    }
+
+    return indices;
+  }
+}
diff --git a/visad/data/visad/object/BinaryFieldImpl.java b/visad/data/visad/object/BinaryFieldImpl.java
new file mode 100644
index 0000000..0b16dde
--- /dev/null
+++ b/visad/data/visad/object/BinaryFieldImpl.java
@@ -0,0 +1,258 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.Data;
+import visad.DataImpl;
+import visad.FieldImpl;
+import visad.FunctionType;
+import visad.Set;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryFieldImpl
+  implements BinaryObject
+{
+  public static final int computeBytes(FieldImpl fld)
+  {
+    try {
+      return processDependentData(null, null, fld.getDomainSet(), fld,
+                                  SAVE_DEPEND);
+    } catch (IOException ioe) {
+      return 0;
+    }
+  }
+
+  public static final int processDependentData(BinaryWriter writer,
+                                               FunctionType ft, Set set,
+                                               FieldImpl fld, Object token)
+    throws IOException
+  {
+    if (!fld.getClass().equals(FieldImpl.class) &&
+        !(fld instanceof FieldImpl && fld instanceof Saveable))
+    {
+      return 0;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+    int numBytes = 1 + 4;
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrFldI: type (" + ft + ")");
+    if (writer != null) {
+      BinaryFunctionType.write(writer, ft, SAVE_DATA);
+    }
+    numBytes += 1 + 4;
+
+    if (set != null) {
+      if (writer != null) {
+        BinaryGeneric.write(writer, set, dependToken);
+      }
+
+      int setBytes = BinaryGeneric.computeBytes(set);
+      if (setBytes > 0) {
+        numBytes += 1 + setBytes;
+      }
+    }
+
+    final int numSamples = (fld.isMissing() ? 0 : fld.getLength());
+    if (numSamples > 0) {
+      numBytes += 1;
+
+      boolean metadataOnly = (token == SAVE_DEPEND_BIG);
+      for (int i = 0; i < numSamples; i++) {
+        DataImpl sample;
+        try {
+          sample = (DataImpl )fld.getSample(i, metadataOnly);
+        } catch (VisADException ve) {
+          continue;
+        }
+
+        if (writer != null) {
+          BinaryGeneric.write(writer, sample, dependToken);
+        }
+
+        numBytes += BinaryGeneric.computeBytes(sample);
+      }
+    }
+
+    return numBytes;
+  }
+
+  public static final FieldImpl read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdFldI: type index (" + typeIndex + ")");
+    FunctionType ft = (FunctionType )cache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdFldI: type index (" + typeIndex + "=" + ft + ")");
+
+    Set set = null;
+    Data[] samples = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_SET:
+if(DEBUG_RD_DATA)System.err.println("rdFldI: FLD_SET (" + FLD_SET + ")");
+        set = (Set )BinaryGeneric.read(reader);
+        break;
+      case FLD_DATA_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdFldI: FLD_DATA_SAMPLES (" + FLD_DATA_SAMPLES + ")");
+        final int numSamples = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdFldI: numSamples (" + numSamples + ")");
+        if (numSamples <= 0) {
+          throw new IOException("Corrupted file (bad Field sample length " +
+                                numSamples + ")");
+        }
+
+        samples = new Data[numSamples];
+        for (int i = 0; i < numSamples; i++) {
+if(DEBUG_WR_DATA)System.err.println("rdFldI#"+i);
+          samples[i] = BinaryGeneric.read(reader);
+if(DEBUG_WR_DATA_DETAIL)System.err.println("rdFldI: #" + i + " (" + samples[i] + ")");
+        }
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdFldI: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown FieldImpl directive " +
+                              directive);
+      }
+    }
+
+    if (ft == null) {
+      throw new IOException("No FunctionType found for FieldImpl");
+    }
+
+    FieldImpl fld = (set == null ? new FieldImpl(ft) :
+                     new FieldImpl(ft, set));
+    if (samples != null) {
+      final int len = samples.length;
+      for (int i = 0; i < len; i++) {
+        fld.setSample(i, samples[i]);
+      }
+    }
+
+    return fld;
+  }
+
+  public static final int writeDependentData(BinaryWriter writer,
+                                             FunctionType ft, Set set,
+                                             FieldImpl fld, Object token)
+    throws IOException
+  {
+    return processDependentData(writer, ft, set, fld, token);
+  }
+
+  public static final void write(BinaryWriter writer, FunctionType ft,
+                                 Set set, FieldImpl fld, Object token)
+    throws IOException
+  {
+    final int objLen = writeDependentData(writer, ft, set, fld, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!fld.getClass().equals(FieldImpl.class) &&
+        !(fld instanceof FieldImpl && fld instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrFldI: punt "+fld.getClass().getName());
+      BinaryUnknown.write(writer, fld, token);
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(ft);
+    if (typeIndex < 0) {
+      throw new IOException("FunctionType " + ft + " not cached");
+    }
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrFldI: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrFldI: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrFldI: DATA_FIELD (" + DATA_FIELD + ")");
+    file.writeByte(DATA_FIELD);
+
+if(DEBUG_WR_DATA)System.err.println("wrFldI: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    if (set != null) {
+if(DEBUG_WR_DATA)System.err.println("wrFldI: FLD_SET (" + FLD_SET + ")");
+      file.writeByte(FLD_SET);
+      BinaryGeneric.write(writer, set, token);
+    }
+
+    final int numSamples = (fld.isMissing() ? 0 : fld.getLength());
+    if (numSamples > 0) {
+if(DEBUG_WR_DATA)System.err.println("wrFldI: FLD_DATA_SAMPLES (" + FLD_DATA_SAMPLES + ")");
+      file.writeByte(FLD_DATA_SAMPLES);
+if(DEBUG_WR_DATA)System.err.println("wrFldI: numSamples (" + numSamples + ")");
+      file.writeInt(numSamples);
+      for (int i = 0; i < numSamples; i++) {
+        DataImpl sample;
+        try {
+          sample = (DataImpl )fld.getSample(i);
+        } catch (VisADException ve) {
+          writer.getOutput().writeByte(DATA_NONE);
+          continue;
+        }
+
+        BinaryGeneric.write(writer, sample, token);
+      }
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrFldI: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryFlatField.java b/visad/data/visad/object/BinaryFlatField.java
new file mode 100644
index 0000000..c44e848
--- /dev/null
+++ b/visad/data/visad/object/BinaryFlatField.java
@@ -0,0 +1,541 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import java.rmi.RemoteException;
+
+import visad.CoordinateSystem;
+import visad.Data;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Set;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.CacheStrategy;
+import visad.data.FileAccessor;
+import visad.data.FileFlatField;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+class BinaryAccessor
+  extends FileAccessor
+{
+  private transient BinaryReader rdr;
+  private transient long filePtr;
+  private transient FunctionType ft;
+
+  public BinaryAccessor(BinaryReader rdr, long filePtr, FunctionType ft)
+  {
+    this.rdr = rdr;
+    this.filePtr = filePtr;
+    this.ft = ft;
+  }
+
+  public FlatField getFlatField()
+    throws RemoteException, VisADException
+  {
+    FlatField ff;
+    try {
+      final long curPtr = rdr.getFilePointer();
+
+      rdr.seek(filePtr);
+      ff = BinaryFlatField.read(rdr, 0, false);
+      rdr.seek(curPtr);
+    } catch (IOException ioe) {
+      throw new VisADException(ioe.getClass().getName() + ": " +
+                               ioe.getMessage());
+    }
+
+    return ff;
+  }
+
+  public FunctionType getFunctionType()
+    throws VisADException
+  {
+    return ft;
+  }
+
+  public double[][] readFlatField(FlatField template, int[] fileLocations)
+  {
+    throw new RuntimeException("Unimplemented");
+  }
+
+  public void writeFile(int[] fileLocations, Data range)
+  {
+    throw new RuntimeException("Unimplemented");
+  }
+
+  public void writeFlatField(double[][] values, FlatField template,
+                             int[] fileLocations)
+  {
+    throw new RuntimeException("Unimplemented");
+  }
+}
+
+public class BinaryFlatField
+  implements BinaryObject
+{
+  private static CacheStrategy strategy = new CacheStrategy();
+
+  public static final int computeBytes(Set domainSet, CoordinateSystem cs,
+                                       CoordinateSystem[] rangeCS,
+                                       Set[] rangeSets, Unit[] units,
+                                       FlatField fld)
+  {
+    int samplesLen = 0;
+    if (!fld.isMissing()) {
+      final int dim = fld.getRangeDimension();
+      final int len = fld.getLength();
+
+      if (dim > 0 && len > 0) {
+        samplesLen = 4 + dim * (4 + len * 8);
+      }
+    }
+
+    int rangeSetsLen;
+    if (rangeSets == null) {
+      rangeSetsLen = 0;
+    } else {
+      rangeSetsLen = 1 + 4;
+      for (int i = 0; i < rangeSets.length; i++) {
+        int len = BinaryGeneric.computeBytes(rangeSets[i]);
+        if (len < 0) {
+          return -1;
+        }
+
+        rangeSetsLen += len;
+      }
+    }
+
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    return 1 + 4 + 1 + 4 +
+      (domainSet == null ? 0 : 1 + BinaryGeneric.computeBytes(domainSet)) +
+      samplesLen +
+      (cs == null ? 0 : 5) +
+      (rangeCS == null ? 0 :
+       1 + BinaryCoordinateSystem.computeBytes(rangeCS)) +
+      rangeSetsLen +
+      (unitsLen == 0 ? 0 : 1 + unitsLen) +
+      1;
+  }
+
+  private static FileFlatField createFileFlatField(BinaryReader rdr,
+                                                   int objLen)
+    throws IOException, VisADException
+  {
+    final long filePtr = rdr.getFilePointer();
+
+    BinaryObjectCache typeCache = rdr.getTypeCache();
+    DataInput file = rdr.getInput();
+
+    final int typeIndex = file.readInt();
+
+    FunctionType ft = (FunctionType )typeCache.get(typeIndex);
+
+if(DEBUG_RD_DATA){
+  final int partLen = objLen - 4;
+
+  byte[] b = new byte[partLen];
+  file.readFully(b);
+
+if(DEBUG_RD_MATH)System.err.println("rdFlFld: type index (" + typeIndex + ")");
+  System.err.println("rdFlFld: Skipping " + objLen + " bytes");
+
+  System.err.print("  ");
+  int cols = 2;
+
+  for (int i = 0; i < partLen; i++) {
+    final int bVal;
+    if (b[i] < 0) {
+      bVal = 256 - b[i];
+    } else {
+      bVal = b[i];
+    }
+
+    final int bCols;
+    if (bVal < 10) {
+      bCols = 2;
+    } else if (bVal < 100) {
+      bCols = 3;
+    } else {
+      bCols = 4;
+    }
+
+    if (cols + bCols < 80) {
+      cols += bCols;
+    } else {
+      System.err.println();
+      System.err.print("  ");
+      cols = 2 + bCols;
+    }
+
+    System.err.print(" " + bVal);
+  }
+  System.err.println();
+
+  final long expectedPtr = filePtr + (long )objLen;
+  final long postPtr = rdr.getFilePointer();
+  if (postPtr != expectedPtr) {
+    System.err.println("Expected ptr " + expectedPtr + ", got " + postPtr);
+  }
+}
+    // skip to the end of this object
+    rdr.seek(filePtr + (long )objLen);
+
+    return new FileFlatField(new BinaryAccessor(rdr, filePtr, ft), strategy);
+  }
+
+  private static final Set[] readSetArray(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdSetRA: len (" + len + ")");
+    Set[] sets = new Set[len];
+    for (int i = 0; i < sets.length; i++) {
+      sets[i] = (Set )BinaryGeneric.read(reader);
+    }
+    return sets;
+  }
+
+  public static final FlatField read(BinaryReader reader, int objLen,
+                                     boolean cacheFile)
+    throws IOException, VisADException
+  {
+    if (cacheFile) {
+      return createFileFlatField(reader, objLen);
+    }
+
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+long totStart, sTime, dsTime, dbTime, icsTime, rcsTime, slTime, uTime;
+totStart = sTime = dsTime = dbTime = icsTime = rcsTime = slTime = uTime = 0;
+
+totStart = System.currentTimeMillis();
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdFlFld: type index (" + typeIndex + ")");
+    FunctionType ft = (FunctionType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdFlFld: type index (" + typeIndex + "=" + ft + ")");
+
+    Set domainSet = null;
+    Data[] oldSamples = null;
+    CoordinateSystem cs = null;
+    CoordinateSystem[] rangeCS = null;
+    Set[] rangeSets = null;
+    Unit[] units = null;
+    double[][] samples = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+long tmpStart = System.currentTimeMillis();
+      switch (directive) {
+      case FLD_SET:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_SET (" + FLD_SET + ")");
+        domainSet = (Set )BinaryGeneric.read(reader);
+if(DEBUG_RD_TIME)sTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case FLD_DATA_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_DATA_SAMPLES (" + FLD_DATA_SAMPLES + ")");
+        oldSamples = BinaryDataArray.read(reader);
+if(DEBUG_RD_TIME)dsTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case FLD_DOUBLE_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_DOUBLE_SAMPLES (" + FLD_DOUBLE_SAMPLES + ")");
+        samples = BinaryDoubleMatrix.read(reader);
+if(DEBUG_RD_TIME)dbTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdFlFld: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdFlFld: cSys index (" + index + "=" + cs + ")");
+if(DEBUG_RD_TIME)icsTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case FLD_RANGE_COORDSYSES:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_RANGE_COORDSYSES (" + FLD_RANGE_COORDSYSES + ")");
+        rangeCS = BinaryCoordinateSystem.readList(reader);
+if(DEBUG_RD_TIME)rcsTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case FLD_SET_LIST:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_SET_LIST (" + FLD_SET_LIST + ")");
+        rangeSets = readSetArray(reader);
+if(DEBUG_RD_TIME)slTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+if(DEBUG_RD_TIME)uTime += System.currentTimeMillis() - tmpStart;
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdFlFld: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown FlatField directive " +
+                              directive);
+      }
+    }
+
+    if (ft == null) {
+      throw new IOException("No FunctionType found for FlatField");
+    }
+
+long tmpStart = System.currentTimeMillis();
+    FlatField fld = new FlatField(ft, domainSet, rangeCS, rangeSets, units);
+long c1Time = System.currentTimeMillis() - tmpStart;
+tmpStart = System.currentTimeMillis();
+    if (samples != null) {
+      fld.setSamples(0, samples);
+    } else if (oldSamples != null) {
+      final int len = oldSamples.length;
+      for (int i = 0; i < len; i++) {
+        fld.setSample(i, oldSamples[i]);
+      }
+    }
+long c2Time = System.currentTimeMillis() - tmpStart;
+tmpStart = System.currentTimeMillis();
+    if (samples != null) {
+      fld.setSamples(samples, false);
+    }
+long c3Time = System.currentTimeMillis() - tmpStart;
+
+if(DEBUG_RD_TIME){
+  long totTime = System.currentTimeMillis() - totStart;
+  System.err.print("rdFlFld: tot "+totTime);
+  if (sTime > 0) System.err.print(" s "+sTime);
+  if (dsTime > 0) System.err.print(" ds "+dsTime);
+  if (dbTime > 0) System.err.print(" db "+dbTime);
+  if (icsTime > 0) System.err.print(" ics "+icsTime);
+  if (rcsTime > 0) System.err.print(" rcs "+rcsTime);
+  if (slTime > 0) System.err.print(" sl "+slTime);
+  if (uTime > 0) System.err.print(" u "+uTime);
+  if (c1Time > 0) System.err.print(" c1 "+c1Time);
+  if (c2Time > 0) System.err.print(" c2 "+c2Time);
+  if (c3Time > 0) System.err.print(" c3 "+c2Time);
+  System.err.println();
+}
+    return fld;
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              FunctionType type,
+                                              Set domainSet,
+                                              CoordinateSystem cs,
+                                              CoordinateSystem[] rangeCS,
+                                              Set[] rangeSets, Unit[] units,
+                                              FlatField fld, Object token)
+    throws IOException
+  {
+    if (!fld.getClass().equals(FlatField.class) &&
+        !fld.getClass().equals(FileFlatField.class) &&
+        !(fld instanceof FlatField && fld instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrFlFld: type (" + type + ")");
+    BinaryFunctionType.write(writer, type, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrFlFld: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (rangeCS != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS){
+  System.err.println("wrFlFld: List of " + rangeCS.length + " CoordSys");
+  for(int x=0;x<rangeCS.length;x++){
+    System.err.println("wrFlFld:    #"+x+": "+rangeCS[x]);
+  }
+}
+      BinaryCoordinateSystem.writeList(writer, rangeCS, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrFlFld: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrFlFld:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (domainSet != null) {
+      BinaryGeneric.write(writer, domainSet, dependToken);
+    }
+
+    if (rangeSets != null) {
+      for (int i = 0; i < rangeSets.length; i++) {
+        BinaryGeneric.write(writer, rangeSets[i], dependToken);
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, FunctionType type,
+                                 Set domainSet, CoordinateSystem cs,
+                                 CoordinateSystem[] rangeCS, Set[] rangeSets,
+                                 Unit[] units, FlatField fld, Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, domainSet, cs, rangeCS, rangeSets,
+                       units, fld, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!fld.getClass().equals(FlatField.class) &&
+        !fld.getClass().equals(FileFlatField.class) &&
+        !(fld instanceof FlatField && fld instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: punt "+fld.getClass().getName());
+      BinaryUnknown.write(writer, fld, token);
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("FunctionType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] csList = null;
+    if (rangeCS != null) {
+      csList = BinaryCoordinateSystem.lookupList(writer.getCoordinateSystemCache(),
+                                                 rangeCS);
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    final int objLen = computeBytes(domainSet, cs, rangeCS, rangeSets, units,
+                                    fld);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: DATA_FLAT_FIELD (" + DATA_FLAT_FIELD + ")");
+    file.writeByte(DATA_FLAT_FIELD);
+
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    if (domainSet != null) {
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: FLD_SET (" + FLD_SET + ")");
+      file.writeByte(FLD_SET);
+      BinaryGeneric.write(writer, domainSet, token);
+    }
+
+    if (!fld.isMissing() && fld.getLength() > 0) {
+      double[][] dblSamples;
+      try {
+        dblSamples = fld.unpackValues();
+      } catch (NullPointerException npe) {
+        npe.printStackTrace();
+        dblSamples = null;
+      } catch (VisADException ve) {
+        ve.printStackTrace();
+        dblSamples = null;
+      }
+
+      if (dblSamples != null) {
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: FLD_DOUBLE_SAMPLES (" + FLD_DOUBLE_SAMPLES + ")");
+        file.writeByte(FLD_DOUBLE_SAMPLES);
+        BinaryDoubleMatrix.write(writer, dblSamples, token);
+      }
+    }
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (csList != null) {
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: FLD_RANGE_COORDSYSES (" + FLD_RANGE_COORDSYSES + ")");
+      file.writeByte(FLD_RANGE_COORDSYSES);
+      BinaryIntegerArray.write(writer, csList, token);
+    }
+
+    if (rangeSets != null) {
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: FLD_SET_LIST (" + FLD_SET_LIST + ")");
+      file.writeByte(FLD_SET_LIST);
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: len (" + rangeSets.length + ")");
+      file.writeInt(rangeSets.length);
+      for (int i = 0; i < rangeSets.length; i++) {
+        BinaryGeneric.write(writer, rangeSets[i], token);
+      }
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrFlFld: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryFloatArray.java b/visad/data/visad/object/BinaryFloatArray.java
new file mode 100644
index 0000000..23a1e5a
--- /dev/null
+++ b/visad/data/visad/object/BinaryFloatArray.java
@@ -0,0 +1,105 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryFloatArray
+  implements BinaryObject
+{
+  public static final int computeBytes(float[] array)
+  {
+    return (array == null ? 0 : 4 + (array.length * 4));
+  }
+
+  public static final float[] read(BinaryReader reader)
+    throws IOException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdFltRA: len (" + len + ")");
+    if (len < 1) {
+      throw new IOException("Corrupted file (bad float array length " +
+                            len + ")");
+    }
+
+    float[] array = new float[len];
+    for (int i = 0; i < len; i++) {
+      array[i] = file.readFloat();
+if(DEBUG_RD_DATA_DETAIL)System.err.println("rdFltRA: #" + i +" (" + array[i] + ")");
+    }
+
+    return array;
+  }
+
+  private static final boolean fasterButUglier = true;
+
+  public static final void write(BinaryWriter writer, float[] array,
+                                 Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    if (fasterButUglier) {
+      byte[] buf = new byte[computeBytes(array)];
+      int bufIdx = 0;
+
+if(DEBUG_WR_DATA)System.err.println("wrFltRA: len (" + array.length + ")");
+      for (int b = 3, l = array.length; b >= 0; b--) {
+        buf[bufIdx + b] = (byte )(l & 0xff);
+        l >>= 8;
+      }
+      bufIdx += 4;
+
+      for (int i = 0; i < array.length; i++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrFltRA: #" + i + " (" + array[i] + ")");
+        long x = Float.floatToIntBits(array[i]);
+        for (int b = 3; b >= 0; b--) {
+          buf[bufIdx + b] = (byte )(x & 0xff);
+          x >>= 8;
+        }
+        bufIdx += 4;
+      }
+
+      if (bufIdx < buf.length) {
+        System.err.println("BinaryFloatArray: Missing " +
+                           (buf.length - bufIdx) + " bytes");
+      }
+
+      file.write(buf);
+    } else { // !fasterButUglier
+if(DEBUG_WR_DATA)System.err.println("wrFltRA: len (" + array.length + ")");
+      file.writeInt(array.length);
+      for (int i = 0; i < array.length; i++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrFltRA: #" + i + " (" + array[i] + ")");
+        file.writeFloat(array[i]);
+      }
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryFloatMatrix.java b/visad/data/visad/object/BinaryFloatMatrix.java
new file mode 100644
index 0000000..af05224
--- /dev/null
+++ b/visad/data/visad/object/BinaryFloatMatrix.java
@@ -0,0 +1,133 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryFloatMatrix
+  implements BinaryObject
+{
+  public static final int computeBytes(float[][] matrix)
+  {
+    if (matrix == null) {
+      return 4;
+    }
+
+    int len = 4;
+    for (int i = 0; i < matrix.length; i++) {
+      len += 4 + (matrix[i].length * 4);
+    }
+
+    return len;
+  }
+
+  public static final float[][] read(BinaryReader reader)
+    throws IOException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdFltMtx: len (" + len + ")");
+    if (len < 0) {
+      return null;
+    }
+
+    float[][] matrix = new float[len][];
+    for (int i = 0; i < len; i++) {
+      final int len2 = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdFltMtx: #" + i + " len (" + len2 + ")");
+      matrix[i] = new float[len2];
+      for (int j = 0; j < len2; j++) {
+        matrix[i][j] = file.readFloat();
+if(DEBUG_RD_DATA_DETAIL)System.err.println("rdFltMtx: #" + i + "," + j +" (" + matrix[i][j] + ")");
+      }
+    }
+
+    return matrix;
+  }
+
+  private static final boolean fasterButUglier = true;
+
+  public static final void write(BinaryWriter writer, float[][] matrix,
+                                 Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    if (matrix == null) {
+if(DEBUG_WR_DATA)System.err.println("wrFltMtx: null (" + -1 + ")");
+      file.writeInt(-1);
+    } else {
+      if (fasterButUglier) {
+        byte[] buf = new byte[4+matrix.length*(4+4*matrix[0].length)];
+        int bufIdx = 0;
+
+if(DEBUG_WR_DATA)System.err.println("wrFltMtx: row len (" + matrix.length + ")");
+        for (int b = 3, l = matrix.length; b >= 0; b--) {
+          buf[bufIdx + b] = (byte )(l & 0xff);
+          l >>= 8;
+        }
+        bufIdx += 4;
+
+        for (int i = 0; i < matrix.length; i++) {
+          final int len = matrix[i].length;
+if(DEBUG_WR_DATA)System.err.println("wrFltMtx: #" + i + " len (" + matrix[i].length + ")");
+          for (int b = 3, l = len; b >= 0; b--) {
+            buf[bufIdx + b] = (byte )(l & 0xff);
+            l >>= 8;
+          }
+          bufIdx += 4;
+
+          for (int j = 0; j < len; j++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrFltMtx: #" + i + "," + j + " (" + matrix[i][j] + ")");
+            int x = Float.floatToIntBits(matrix[i][j]);
+            for (int b = 3; b >= 0; b--) {
+              buf[bufIdx + b] = (byte )(x & 0xff);
+              x >>= 8;
+            }
+            bufIdx += 4;
+          }
+        }
+
+        file.write(buf);
+      } else { // !fasterButUglier
+if(DEBUG_WR_DATA)System.err.println("wrFltMtx: row len (" + matrix.length + ")");
+        file.writeInt(matrix.length);
+        for (int i = 0; i < matrix.length; i++) {
+          final int len = matrix[i].length;
+if(DEBUG_WR_DATA)System.err.println("wrFltMtx: #" + i + " len (" + matrix[i].length + ")");
+          file.writeInt(len);
+          for (int j = 0; j < len; j++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrFltMtx: #" + i + "," + j + " (" + matrix[i][j] + ")");
+            file.writeFloat(matrix[i][j]);
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryFunctionType.java b/visad/data/visad/object/BinaryFunctionType.java
new file mode 100644
index 0000000..dc31f17
--- /dev/null
+++ b/visad/data/visad/object/BinaryFunctionType.java
@@ -0,0 +1,112 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.FunctionType;
+import visad.MathType;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryFunctionType
+  implements BinaryObject
+{
+  public static final int computeBytes(FunctionType ft) { return 14; }
+
+  public static final FunctionType read(BinaryReader reader, int index)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int domainIndex = file.readInt();
+if(DEBUG_RD_MATH)System.err.println("rdFuTy: domain index (" + domainIndex + ")");
+    MathType domain = (MathType )cache.get(domainIndex);
+if(DEBUG_RD_MATH)System.err.println("rdFuTy: === read domain " + domain);
+    final int rangeIndex = file.readInt();
+if(DEBUG_RD_MATH)System.err.println("rdFuTy: range index (" + rangeIndex + ")");
+    MathType range = (MathType )cache.get(rangeIndex);
+if(DEBUG_RD_MATH)System.err.println("rdFuTy: === read range " + range);
+
+    final byte endByte = file.readByte();
+    if (endByte != FLD_END) {
+if(DEBUG_RD_MATH)System.err.println("rdFuTy: read " + endByte + " (wanted FLD_END)");
+      throw new IOException("Corrupted file (no TupleType end-marker)");
+    }
+if(DEBUG_RD_MATH)System.err.println("rdFuTy: FLD_END (" + endByte + ")");
+
+    FunctionType ft = new FunctionType(domain, range);
+
+    cache.add(index, ft);
+
+    return ft;
+  }
+
+  public static final int write(BinaryWriter writer, FunctionType ft,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(ft);
+    if (index < 0) {
+      index = cache.add(ft);
+      if (index < 0) {
+        throw new IOException("Couldn't cache FunctionType " + ft);
+      }
+
+      int dIndex = BinaryMathType.write(writer, ft.getDomain(), token);
+      int rIndex = BinaryMathType.write(writer, ft.getRange(), token);
+
+      // total number of bytes written for this object
+      final int objLen = computeBytes(ft);
+
+      DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_MATH)System.err.println("wrFuTy: OBJ_MATH (" + OBJ_MATH + ")");
+      file.writeByte(OBJ_MATH);
+if(DEBUG_WR_MATH)System.err.println("wrFuTy: objLen (" + objLen + ")");
+      file.writeInt(objLen);
+if(DEBUG_WR_MATH)System.err.println("wrFuTy: index (" + index + ")");
+      file.writeInt(index);
+if(DEBUG_WR_MATH)System.err.println("wrFuTy: MATH_FUNCTION (" + MATH_FUNCTION + ")");
+      file.writeByte(MATH_FUNCTION);
+
+if(DEBUG_WR_MATH)System.err.println("wrFuTy: domain index (" + dIndex + ")");
+      file.writeInt(dIndex);
+if(DEBUG_WR_MATH)System.err.println("wrFuTy: range index (" + rIndex + ")");
+      file.writeInt(rIndex);
+
+if(DEBUG_WR_MATH)System.err.println("wrFuTy: FLD_END (" + FLD_END + ")");
+      file.writeByte(FLD_END);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryGeneric.java b/visad/data/visad/object/BinaryGeneric.java
new file mode 100644
index 0000000..c9d8593
--- /dev/null
+++ b/visad/data/visad/object/BinaryGeneric.java
@@ -0,0 +1,69 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.IOException;
+
+import visad.DataImpl;
+import visad.VisADException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinarySizer;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryGeneric
+  implements BinaryObject
+{
+  public static final int computeBytes(DataImpl data)
+  {
+    BinarySizer sizer = new BinarySizer();
+    try {
+      sizer.process(data, null);
+    } catch (VisADException ve) {
+      return -1;
+    }
+    return sizer.getSize();
+  }
+
+  public static final DataImpl read(BinaryReader reader)
+    throws IOException
+  {
+    try {
+      return reader.getData();
+    } catch (VisADException ve) {
+      throw new IOException("Couldn't read file: " + ve.getMessage());
+    }
+  }
+
+  public static final void write(BinaryWriter writer, DataImpl data,
+                                 Object token)
+    throws IOException
+  {
+    try {
+      writer.process(data, token);
+    } catch (VisADException ve) {
+      throw new IOException("Couldn't write " + data.getClass().getName() +
+                            ": " + ve.getMessage());
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryGriddedDoubleSet.java b/visad/data/visad/object/BinaryGriddedDoubleSet.java
new file mode 100644
index 0000000..93d370a
--- /dev/null
+++ b/visad/data/visad/object/BinaryGriddedDoubleSet.java
@@ -0,0 +1,350 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.GriddedSet;
+import visad.GriddedDoubleSet;
+import visad.Gridded1DDoubleSet;
+import visad.Gridded2DDoubleSet;
+import visad.Gridded3DDoubleSet;
+import visad.SetType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryGriddedDoubleSet
+  implements BinaryObject
+{
+  public static final int computeBytes(double[][] samples, int[] lengths,
+                                       CoordinateSystem cs, Unit[] units,
+                                       ErrorEstimate[] errors)
+  {
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    final int errorsLen = BinaryErrorEstimate.computeBytes(errors);
+    return 1 + 4 + 1 + 4 +
+      1 + BinaryDoubleMatrix.computeBytes(samples) +
+      1 + BinaryIntegerArray.computeBytes(lengths) +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      (errorsLen == 0 ? 0 : errorsLen + 1) +
+      1;
+  }
+
+  public static final GriddedSet read(BinaryReader reader, byte dataType)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdGrDblSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdGrDblSet: type index (" + typeIndex + "=" + st + ")");
+
+    double[][] samples = null;
+    int[] lengths = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+    ErrorEstimate[] errs = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_DOUBLE_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdGrDblSet: FLD_DOUBLE_SAMPLES (" + FLD_DOUBLE_SAMPLES + ")");
+        samples = BinaryDoubleMatrix.read(reader);
+        break;
+      case FLD_LENGTHS:
+if(DEBUG_RD_DATA)System.err.println("rdGrDblSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+        lengths = BinaryIntegerArray.read(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdGrDblSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdGrDblSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdGrDblSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdGrDblSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_INDEX_ERRORS:
+if(DEBUG_RD_DATA)System.err.println("rdGrDblSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+        errs = BinaryErrorEstimate.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdGrDblSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown GriddedDoubleSet directive " +
+                              directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for GriddedDoubleSet");
+    }
+    if (lengths == null) {
+      throw new IOException("No lengths found for GriddedDoubleSet");
+    }
+
+    int dim;
+    switch (dataType) {
+    case DATA_GRIDDED_1D_DOUBLE_SET:
+      dim = 1;
+      break;
+    case DATA_GRIDDED_2D_DOUBLE_SET:
+      dim = 2;
+      break;
+    case DATA_GRIDDED_3D_DOUBLE_SET:
+      dim = 3;
+      break;
+    default:
+      throw new IOException("Unknown GriddedDoubleSet type " + dataType);
+    }
+
+    if (samples != null && samples.length != dim) {
+      throw new VisADException("Expected " + dim +
+                               "D sample array, not " +
+                               samples.length + "D");
+    }
+
+    switch (dataType) {
+    case DATA_GRIDDED_1D_DOUBLE_SET:
+      return new Gridded1DDoubleSet(st, samples, lengths[0], cs, units, errs);
+    case DATA_GRIDDED_2D_DOUBLE_SET:
+      if (lengths.length == 1) {
+        return new Gridded2DDoubleSet(st, samples, lengths[0],
+                                      cs, units, errs);
+      } else {
+        return new Gridded2DDoubleSet(st, samples, lengths[0], lengths[1],
+                                      cs, units, errs);
+      }
+    case DATA_GRIDDED_3D_DOUBLE_SET:
+      if (lengths.length == 1) {
+        return new Gridded3DDoubleSet(st, samples, lengths[0],
+                                      cs, units, errs);
+      } else if (lengths.length == 2) {
+        return new Gridded3DDoubleSet(st, samples, lengths[0], lengths[1],
+                                      cs, units, errs);
+      } else {
+        return new Gridded3DDoubleSet(st, samples,
+                                      lengths[0], lengths[1], lengths[2],
+                                      cs, units, errs);
+      }
+    default:
+      throw new IOException("Unknown GriddedDoubleSet type " + dataType);
+    }
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              GriddedSet set,
+                                              Class canonicalClass,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof GriddedDoubleSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrGrDblSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrGrDblSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrGrDblSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrGrDblSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (errors != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE){
+  System.err.println("wrGrDblSet: List of " + errors.length + " ErrorEstimates");
+  for(int x=0;x<errors.length;x++){
+    System.err.println("wrGrDblSet:    #"+x+": "+errors[x]);
+  }
+}
+      BinaryErrorEstimate.writeList(writer, errors, SAVE_DATA);
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 double[][] samples, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, GriddedSet set,
+                                 Class canonicalClass, byte dataType,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, cs, units, errors, set,
+                       canonicalClass, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof GriddedDoubleSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    if (lengths == null) {
+      throw new IOException("Null " + canonicalClass.getName() + " lengths");
+    }
+
+    final int validLen;
+    switch (dataType) {
+    case DATA_GRIDDED_1D_DOUBLE_SET:
+      validLen = 1;
+      break;
+    case DATA_GRIDDED_2D_DOUBLE_SET:
+      validLen = 2;
+      break;
+    case DATA_GRIDDED_3D_DOUBLE_SET:
+      validLen = 3;
+      break;
+    default:
+      throw new IOException("Invalid type " + dataType);
+    }
+
+    if (samples != null && samples.length != validLen) {
+      throw new IOException("Expected " + validLen + " sample list" +
+                            (validLen > 1 ? "s" : "") + ", not " +
+                            samples.length);
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    int[] errorsIndex = null;
+    if (errors != null) {
+      errorsIndex = BinaryErrorEstimate.lookupList(writer.getErrorEstimateCache(),
+                                                   errors);
+    }
+
+    final int objLen = computeBytes(samples, lengths, cs, units, errors);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: " +
+                                 (dataType == DATA_GRIDDED_1D_DOUBLE_SET ?
+                                  "DATA_GRIDDED_1D_DOUBLE" :
+                                  (dataType == DATA_GRIDDED_2D_DOUBLE_SET ?
+                                   "DATA_GRIDDED_2D_DOUBLE" :
+                                   (dataType == DATA_GRIDDED_3D_DOUBLE_SET ?
+                                    "DATA_GRIDDED_3D_DOUBLE" : "DATA_???"))) +
+                                 "(" + dataType + ")");
+    file.writeByte(dataType);
+
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: FLD_DOUBLE_SAMPLES (" + FLD_DOUBLE_SAMPLES + ")");
+    file.writeByte(FLD_DOUBLE_SAMPLES);
+    BinaryDoubleMatrix.write(writer, samples, token);
+
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+    file.writeByte(FLD_LENGTHS);
+    BinaryIntegerArray.write(writer, lengths, token);
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+    if (errorsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+      file.writeByte(FLD_INDEX_ERRORS);
+      BinaryIntegerArray.write(writer, errorsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrGrDblSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryGriddedSet.java b/visad/data/visad/object/BinaryGriddedSet.java
new file mode 100644
index 0000000..b72a365
--- /dev/null
+++ b/visad/data/visad/object/BinaryGriddedSet.java
@@ -0,0 +1,357 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.GriddedSet;
+import visad.Gridded1DSet;
+import visad.Gridded2DSet;
+import visad.Gridded3DSet;
+import visad.SetType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryGriddedSet
+  implements BinaryObject
+{
+  public static final int computeBytes(float[][] samples, int[] lengths,
+                                       CoordinateSystem cs, Unit[] units,
+                                       ErrorEstimate[] errors)
+  {
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    final int errorsLen = BinaryErrorEstimate.computeBytes(errors);
+    return 1 + 4 + 1 + 4 +
+      1 + BinaryFloatMatrix.computeBytes(samples) +
+      1 + BinaryIntegerArray.computeBytes(lengths) +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      (errorsLen == 0 ? 0 : errorsLen + 1) +
+      1;
+  }
+
+  public static final GriddedSet read(BinaryReader reader, byte dataType)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdGrSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdGrSet: type index (" + typeIndex + "=" + st + ")");
+
+    float[][] samples = null;
+    int[] lengths = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+    ErrorEstimate[] errs = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_FLOAT_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdGrSet: FLD_FLOAT_SAMPLES (" + FLD_FLOAT_SAMPLES + ")");
+        samples = BinaryFloatMatrix.read(reader);
+        break;
+      case FLD_LENGTHS:
+if(DEBUG_RD_DATA)System.err.println("rdGrSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+        lengths = BinaryIntegerArray.read(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdGrSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdGrSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdGrSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdGrSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_INDEX_ERRORS:
+if(DEBUG_RD_DATA)System.err.println("rdGrSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+        errs = BinaryErrorEstimate.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdGrSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown GriddedSet directive " +
+                              directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for GriddedSet");
+    }
+    if (lengths == null) {
+      throw new IOException("No lengths found for GriddedSet");
+    }
+
+    int dim;
+    switch (dataType) {
+    case DATA_GRIDDED_1D_SET:
+      dim = 1;
+      break;
+    case DATA_GRIDDED_2D_SET:
+      dim = 2;
+      break;
+    case DATA_GRIDDED_3D_SET:
+      dim = 3;
+      break;
+    case DATA_GRIDDED_SET:
+      dim = -1;
+      break;
+    default:
+      throw new IOException("Unknown GriddedSet type " + dataType);
+    }
+
+    if (samples != null && dim > 0 && samples.length != dim) {
+      throw new VisADException("Expected " + dim +
+                               "D sample array, not " +
+                               samples.length + "D");
+    }
+
+    switch (dataType) {
+    case DATA_GRIDDED_1D_SET:
+      return new Gridded1DSet(st, samples, lengths[0], cs, units, errs);
+    case DATA_GRIDDED_2D_SET:
+      if (lengths.length == 1) {
+        return new Gridded2DSet(st, samples, lengths[0], cs, units, errs);
+      } else {
+        return new Gridded2DSet(st, samples, lengths[0], lengths[1],
+                                cs, units, errs);
+      }
+    case DATA_GRIDDED_3D_SET:
+      if (lengths.length == 1) {
+        return new Gridded3DSet(st, samples, lengths[0], cs, units, errs);
+      } else if (lengths.length == 2) {
+        return new Gridded3DSet(st, samples, lengths[0], lengths[1],
+                                cs, units, errs);
+      } else {
+        return new Gridded3DSet(st, samples,
+                                lengths[0], lengths[1], lengths[2],
+                                cs, units, errs);
+      }
+    case DATA_GRIDDED_SET:
+      return new GriddedSet(st, samples, lengths, cs, units, errs);
+    default:
+      throw new IOException("Unknown GriddedSet type " + dataType);
+    }
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              GriddedSet set,
+                                              Class canonicalClass,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof GriddedSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrGrSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrGrSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrGrSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrGrSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (errors != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE){
+  System.err.println("wrGrSet: List of " + errors.length + " ErrorEstimates");
+  for(int x=0;x<errors.length;x++){
+    System.err.println("wrGrSet:    #"+x+": "+errors[x]);
+  }
+}
+      BinaryErrorEstimate.writeList(writer, errors, SAVE_DATA);
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 float[][] samples, int[] lengths,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, GriddedSet set,
+                                 Class canonicalClass, byte dataType,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, cs, units, errors, set,
+                       canonicalClass, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof GriddedSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    if (lengths == null) {
+      throw new IOException("Null " + canonicalClass.getName() + " lengths");
+    }
+
+    final int validLen;
+    switch (dataType) {
+    case DATA_GRIDDED_1D_SET:
+      validLen = 1;
+      break;
+    case DATA_GRIDDED_2D_SET:
+      validLen = 2;
+      break;
+    case DATA_GRIDDED_3D_SET:
+      validLen = 3;
+      break;
+    case DATA_GRIDDED_SET:
+      validLen = -1;
+      break;
+    default:
+      throw new IOException("Invalid type " + dataType);
+    }
+
+    if (samples != null && validLen > 0 && samples.length != validLen) {
+      throw new IOException("Expected " + validLen + " sample list" +
+                            (validLen > 1 ? "s" : "") + ", not " +
+                            samples.length);
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    int[] errorsIndex = null;
+    if (errors != null) {
+      errorsIndex = BinaryErrorEstimate.lookupList(writer.getErrorEstimateCache(),
+                                                   errors);
+    }
+
+    final int objLen = computeBytes(samples, lengths, cs, units, errors);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: " +
+                                    (dataType == DATA_GRIDDED_SET ?
+                                     "DATA_GRIDDED" :
+                                     (dataType == DATA_GRIDDED_1D_SET ?
+                                      "DATA_GRIDDED_1D" :
+                                      (dataType == DATA_GRIDDED_2D_SET ?
+                                       "DATA_GRIDDED_2D" :
+                                       (dataType == DATA_GRIDDED_3D_SET ?
+                                        "DATA_GRIDDED_3D" : "DATA_???")))) +
+                                    "(" + dataType + ")");
+    file.writeByte(dataType);
+
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: FLD_FLOAT_SAMPLES (" + FLD_FLOAT_SAMPLES + ")");
+    file.writeByte(FLD_FLOAT_SAMPLES);
+    BinaryFloatMatrix.write(writer, samples, token);
+
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+    file.writeByte(FLD_LENGTHS);
+    BinaryIntegerArray.write(writer, lengths, token);
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+    if (errorsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+      file.writeByte(FLD_INDEX_ERRORS);
+      BinaryIntegerArray.write(writer, errorsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrGrSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryIntegerArray.java b/visad/data/visad/object/BinaryIntegerArray.java
new file mode 100644
index 0000000..c29185e
--- /dev/null
+++ b/visad/data/visad/object/BinaryIntegerArray.java
@@ -0,0 +1,124 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryIntegerArray
+  implements BinaryObject
+{
+  public static final int computeBytes(int[] array)
+  {
+    return (array == null ? 0 : 4 + (array.length * 4));
+  }
+
+  public static final int computeBytes(Object[] array)
+  {
+    if (array != null) {
+      boolean empty = true;
+      for (int i = 0; i < array.length; i++) {
+        if (array[i] != null) {
+          empty = false;
+          break;
+        }
+      }
+
+      if (empty) {
+        array = null;
+      }
+    }
+
+    return (array == null ? 0 : 4 + (array.length * 4));
+  }
+
+  public static final int[] read(BinaryReader reader)
+    throws IOException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdIntRA: len (" + len + ")");
+    if (len < 1) {
+      throw new IOException("Corrupted file (bad double array length " +
+                            len + ")");
+    }
+
+    int[] array = new int[len];
+    for (int i = 0; i < len; i++) {
+      array[i] = file.readInt();
+if(DEBUG_RD_DATA_DETAIL)System.err.println("rdIntRA: #" + i +" (" + array[i] + ")");
+    }
+
+    return array;
+  }
+
+  private static final boolean fasterButUglier = true;
+
+  public static final void write(BinaryWriter writer, int[] array,
+                                 Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    if (fasterButUglier) {
+      byte[] buf = new byte[computeBytes(array)];
+      int bufIdx = 0;
+
+if(DEBUG_WR_DATA)System.err.println("wrFltRA: len (" + array.length + ")");
+      for (int b = 3, l = array.length; b >= 0; b--) {
+        buf[bufIdx + b] = (byte )(l & 0xff);
+        l >>= 8;
+      }
+      bufIdx += 4;
+
+      for (int i = 0; i < array.length; i++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrFltRA: #" + i + " (" + array[i] + ")");
+        int x = array[i];
+        for (int b = 3; b >= 0; b--) {
+          buf[bufIdx + b] = (byte )(x & 0xff);
+          x >>= 8;
+        }
+        bufIdx += 4;
+      }
+
+      if (bufIdx < buf.length) {
+        System.err.println("BinaryIntegerArray: Missing " +
+                           (buf.length - bufIdx) + " bytes");
+      }
+
+      file.write(buf);
+    } else { // !fasterButUglier
+if(DEBUG_WR_DATA)System.err.println("wrIntRA: len (" + array.length + ")");
+      file.writeInt(array.length);
+      for (int i = 0; i < array.length; i++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrIntRA: #" + i + " (" + array[i] + ")");
+        file.writeInt(array[i]);
+      }
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryIntegerMatrix.java b/visad/data/visad/object/BinaryIntegerMatrix.java
new file mode 100644
index 0000000..3a516cd
--- /dev/null
+++ b/visad/data/visad/object/BinaryIntegerMatrix.java
@@ -0,0 +1,136 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryWriter;
+
+public class BinaryIntegerMatrix
+  implements BinaryObject
+{
+  static final int computeBytes(int[][] matrix)
+  {
+    if (matrix == null) {
+      return 4;
+    }
+
+    int len = 4;
+    for (int i = 0; i < matrix.length; i++) {
+      len += 4 + (matrix[i].length * 4);
+    }
+
+    return len;
+  }
+
+  static final int[][] read(DataInput file)
+    throws IOException
+  {
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdIntMtx: len (" + len + ")");
+    if (len < 0) {
+      return null;
+    }
+
+    int[][] matrix = new int[len][];
+    for (int i = 0; i < len; i++) {
+      final int len2 = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdIntMtx: #" + i + " len (" + len2 + ")");
+      matrix[i] = new int[len2];
+      for (int j = 0; j < len2; j++) {
+        matrix[i][j] = file.readInt();
+if(DEBUG_RD_DATA_DETAIL)System.err.println("rdIntMtx: #" + i + "," + j +" (" + matrix[i][j] + ")");
+      }
+    }
+
+    return matrix;
+  }
+
+  private static final boolean fasterButUglier = true;
+
+  static final void write(DataOutput file, int[][] matrix)
+    throws IOException
+  {
+    if (matrix == null) {
+if(DEBUG_WR_DATA)System.err.println("wrIntMtx: null (" + -1 + ")");
+      file.writeInt(-1);
+    } else {
+      if (fasterButUglier) {
+        byte[] buf = new byte[4+matrix.length*(4+4*matrix[0].length)];
+        int bufIdx = 0;
+
+if(DEBUG_WR_DATA)System.err.println("wrIntMtx: row len (" + matrix.length + ")");
+        for (int b = 3, l = matrix.length; b >= 0; b--) {
+          buf[bufIdx + b] = (byte )(l & 0xff);
+          l >>= 8;
+        }
+        bufIdx += 4;
+
+        for (int i = 0; i < matrix.length; i++) {
+          final int len = matrix[i].length;
+if(DEBUG_WR_DATA)System.err.println("wrIntMtx: #" + i + " len (" + matrix[i].length + ")");
+          for (int b = 3, l = len; b >= 0; b--) {
+            buf[bufIdx + b] = (byte )(l & 0xff);
+            l >>= 8;
+          }
+          bufIdx += 4;
+
+          for (int j = 0; j < len; j++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrIntMtx: #" + i + "," + j + " (" + matrix[i][j] + ")");
+            int x = matrix[i][j];
+            for (int b = 3; b >= 0; b--) {
+              buf[bufIdx + b] = (byte )(x & 0xff);
+              x >>= 8;
+            }
+            bufIdx += 4;
+          }
+        }
+
+        file.write(buf);
+      } else { // !fasterButUglier
+if(DEBUG_WR_DATA)System.err.println("wrIntMtx: row len (" + matrix.length + ")");
+        file.writeInt(matrix.length);
+        for (int i = 0; i < matrix.length; i++) {
+          final int len = matrix[i].length;
+if(DEBUG_WR_DATA)System.err.println("wrIntMtx: #" + i + " len (" + matrix[i].length + ")");
+          file.writeInt(len);
+          for (int j = 0; j < len; j++) {
+if(DEBUG_WR_DATA_DETAIL)System.err.println("wrIntMtx: #" + i + "," + j + " (" + matrix[i][j] + ")");
+            file.writeInt(matrix[i][j]);
+          }
+        }
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, int[][] matrix,
+                                 Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    write(file, matrix);
+  }
+}
diff --git a/visad/data/visad/object/BinaryIntegerSet.java b/visad/data/visad/object/BinaryIntegerSet.java
new file mode 100644
index 0000000..ad24fe1
--- /dev/null
+++ b/visad/data/visad/object/BinaryIntegerSet.java
@@ -0,0 +1,472 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.GriddedSet;
+import visad.IntegerSet;
+import visad.Integer1DSet;
+import visad.Integer2DSet;
+import visad.Integer3DSet;
+import visad.IntegerNDSet;
+import visad.Linear1DSet;
+import visad.LinearSet;
+import visad.MathType;
+import visad.SetType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryIntegerSet
+  implements BinaryObject
+{
+  public static final int computeBytes(boolean matchedTypes, int[] lengths,
+                                       Integer1DSet[] comps,
+                                       CoordinateSystem cs, Unit[] units,
+                                       ErrorEstimate[] errors)
+  {
+    int compsLen;
+    if (matchedTypes) {
+      compsLen = 1 + BinaryIntegerArray.computeBytes(lengths);
+    } else {
+      compsLen = 1 + 4;
+      for (int i = 0; i < comps.length; i++) {
+        int size = BinaryGeneric.computeBytes(comps[i]);
+        if (size < 0) {
+          compsLen = -1;
+          break;
+        }
+
+        compsLen += size;
+      }
+    }
+
+    if (compsLen < 0) {
+      return compsLen;
+    }
+
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    final int errorsLen = BinaryErrorEstimate.computeBytes(errors);
+    return 1 + 4 + 1 + 4 +
+      compsLen +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      (errorsLen == 0 ? 0 : errorsLen + 1) +
+      1;
+  }
+
+  public static final Integer1DSet[] getComponents(LinearSet set)
+  {
+    final int dim = ((GriddedSet )set).getDimension();
+
+    Integer1DSet[] comps = new Integer1DSet[dim];
+    for (int i = 0; i < dim; i++) {
+      Linear1DSet comp = set.getLinear1DComponent(i);
+      if (comp instanceof Integer1DSet) {
+        comps[i] = (Integer1DSet )comp;
+      } else if (comp.getFirst() == 0.0) {
+        // had to put this in because an old serialized object
+        // had Linear1DSets instead of Integer1DSets
+        try {
+          comps[i] = new Integer1DSet(comp.getType(), comp.getLength(),
+                                      comp.getCoordinateSystem(),
+                                      comp.getSetUnits(),
+                                      comp.getSetErrors());
+        } catch (VisADException ve) {
+          return null;
+        }
+      } else {
+        // XXX what happens here?
+        System.err.println("Ignoring comp#" + i + ": " + comp);
+        comps[i] = null;
+      }
+    }
+
+    return comps;
+  }
+
+  public static boolean hasMatchedTypes(SetType type, Integer1DSet[] comps)
+  {
+    if (comps == null) {
+      return true;
+    }
+
+    MathType[] dComp = type.getDomain().getComponents();
+    if (dComp == null || dComp.length != comps.length) {
+      return false;
+    }
+
+    boolean matchedTypes = true;
+    for (int i = 0; i < dComp.length; i++) {
+      if (!dComp[i].equals(comps[i].getType())) {
+        matchedTypes = false;
+        break;
+      }
+    }
+
+    return matchedTypes;
+  }
+
+  private static final Integer1DSet[] readInteger1DSets(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdI1DSetS: len (" + len + ")");
+    Integer1DSet[] sets = new Integer1DSet[len];
+    for (int i = 0; i < len; i++) {
+      sets[i] = (Integer1DSet )BinaryGeneric.read(reader);
+    }
+    return sets;
+  }
+
+  public static final GriddedSet read(BinaryReader reader, byte dataType)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdIntSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdIntSet: type index (" + typeIndex + "=" + st + ")");
+
+    int[] lengths = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+    ErrorEstimate[] errs = null;
+    Integer1DSet[] comps = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_LENGTHS:
+if(DEBUG_RD_DATA)System.err.println("rdIntSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+        lengths = BinaryIntegerArray.read(reader);
+        break;
+      case FLD_INTEGER_SETS:
+if(DEBUG_RD_DATA)System.err.println("rdIntSet: FLD_INTEGER_SETS (" + FLD_INTEGER_SETS + ")");
+        comps = readInteger1DSets(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdIntSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdIntSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdIntSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdIntSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_INDEX_ERRORS:
+if(DEBUG_RD_DATA)System.err.println("rdIntSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+        errs = BinaryErrorEstimate.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdIntSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown IntegerSet directive " + directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for IntegerSet");
+    }
+
+    if (comps != null) {
+      if (lengths != null) {
+        throw new IOException("Both components and lengths found for IntegerSet");
+      }
+
+      switch (dataType) {
+      case DATA_INTEGER_1D_SET:
+        throw new IOException("Components specified for Integer1DSet");
+      case DATA_INTEGER_2D_SET:
+        return new Integer2DSet(st, comps, cs, units, errs);
+      case DATA_INTEGER_3D_SET:
+        return new Integer3DSet(st, comps, cs, units, errs);
+      case DATA_INTEGER_ND_SET:
+        return new IntegerNDSet(st, comps, cs, units, errs);
+      default:
+        throw new IOException("Unknown IntegerSet type " + dataType);
+      }
+    } else {
+      if (lengths == null) {
+        throw new IOException("No lengths found for IntegerSet");
+      }
+
+      final int dim;
+      switch (dataType) {
+      case DATA_INTEGER_1D_SET:
+        dim = 1;
+        break;
+      case DATA_INTEGER_2D_SET:
+        dim = 2;
+        break;
+      case DATA_INTEGER_3D_SET:
+        dim = 3;
+        break;
+      default:
+        dim = -1;
+        break;
+      }
+
+      if (dim > 0 && lengths.length != dim) {
+        throw new VisADException("Expected " + dim + " length" +
+                                 (dim > 1 ? "s" : "") + ", not " +
+                                 lengths.length);
+      }
+
+      switch (dataType) {
+      case DATA_INTEGER_1D_SET:
+        return new Integer1DSet(st, lengths[0], cs, units, errs);
+      case DATA_INTEGER_2D_SET:
+        return new Integer2DSet(st, lengths[0], lengths[1], cs, units, errs);
+      case DATA_INTEGER_3D_SET:
+        return new Integer3DSet(st, lengths[0], lengths[1], lengths[2],
+                                cs, units, errs);
+      case DATA_INTEGER_ND_SET:
+        return new IntegerNDSet(st, lengths, cs, units, errs);
+      default:
+        throw new IOException("Unknown IntegerSet type " + dataType);
+      }
+    }
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              Integer1DSet[] comps,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              GriddedSet set,
+                                              Class canonicalClass,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof IntegerSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrIntSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrIntSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrIntSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrIntSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (errors != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE){
+  System.err.println("wrIntSet: List of " + errors.length + " ErrorEstimates");
+  for(int x=0;x<errors.length;x++){
+    System.err.println("wrIntSet:    #"+x+": "+errors[x]);
+  }
+}
+      BinaryErrorEstimate.writeList(writer, errors, SAVE_DATA);
+    }
+
+    if (comps != null) {
+      for (int i = 0; i < comps.length; i++) {
+        BinaryGeneric.write(writer, comps[i], dependToken);
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 int[] lengths, Integer1DSet[] comps,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, GriddedSet set,
+                                 Class canonicalClass, byte dataType,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, comps, cs, units, errors, set,
+                       canonicalClass, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof IntegerSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    // see if domain types and component types match
+    boolean matchedTypes = hasMatchedTypes(type, comps);
+
+    final int dim = set.getDimension();
+
+    if (!matchedTypes) {
+      if (dataType == DATA_INTEGER_1D_SET) {
+        throw new IOException("Components specified for Integer1DSet");
+      }
+
+      if (comps.length != dim) {
+        throw new IOException("Expected " + dim + " IntegerSet component" +
+                              (dim > 1 ? "s" : "") + ", not " + comps.length);
+      }
+    } else {
+      if (lengths == null) {
+        throw new IOException("Null " + canonicalClass.getName() +
+                              " lengths");
+      }
+
+      if (lengths.length != dim) {
+        throw new IOException("Expected " + dim + " IntegerSet length" +
+                              (dim > 1 ? "s" : "") + ", not " +
+                              lengths.length);
+      }
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    int[] errorsIndex = null;
+    if (errors != null) {
+      errorsIndex = BinaryErrorEstimate.lookupList(writer.getErrorEstimateCache(),
+                                                   errors);
+    }
+
+    final int objLen = computeBytes(matchedTypes, lengths, comps, cs, units,
+                                    errors);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: " +
+                                    (dataType == DATA_INTEGER_1D_SET ?
+                                     "DATA_INTEGER_1D_SET" :
+                                     (dataType == DATA_INTEGER_2D_SET ?
+                                      "DATA_INTEGER_2D_SET" :
+                                      (dataType == DATA_INTEGER_3D_SET ?
+                                       "DATA_INTEGER_3D_SET" :
+                                       (dataType == DATA_INTEGER_ND_SET ?
+                                        "DATA_INTEGER_ND_SET" : "DATA_???")))) +
+                                    "(" + dataType + ")");
+    file.writeByte(dataType);
+
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    if (matchedTypes) {
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+      file.writeByte(FLD_LENGTHS);
+      BinaryIntegerArray.write(writer, lengths, token);
+    } else {
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: FLD_INTEGER_SETS (" + FLD_INTEGER_SETS + ")");
+      file.writeByte(FLD_INTEGER_SETS);
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: set length (" + comps.length + ")");
+      file.writeInt(comps.length);
+      for (int i = 0; i < comps.length; i++) {
+        BinaryGeneric.write(writer, comps[i], token);
+      }
+    }
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+    if (errorsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+      file.writeByte(FLD_INDEX_ERRORS);
+      BinaryIntegerArray.write(writer, errorsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrIntSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryIrregularSet.java b/visad/data/visad/object/BinaryIrregularSet.java
new file mode 100644
index 0000000..ff92a54
--- /dev/null
+++ b/visad/data/visad/object/BinaryIrregularSet.java
@@ -0,0 +1,350 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.Delaunay;
+import visad.ErrorEstimate;
+import visad.IrregularSet;
+import visad.Irregular1DSet;
+import visad.Irregular2DSet;
+import visad.Irregular3DSet;
+import visad.SetType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryIrregularSet
+  implements BinaryObject
+{
+  public static final int computeBytes(float[][] samples,
+                                       CoordinateSystem cs, Unit[] units,
+                                       ErrorEstimate[] errors,
+                                       Delaunay delaunay)
+  {
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    final int errorsLen = BinaryErrorEstimate.computeBytes(errors);
+    return 1 + 4 + 1 + 4 +
+      1 + BinaryFloatMatrix.computeBytes(samples) +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      (errorsLen == 0 ? 0 : errorsLen + 1) +
+      (delaunay == null ? 0 : BinaryDelaunay.computeBytes(delaunay)) +
+      1;
+  }
+
+  public static final IrregularSet read(BinaryReader reader, byte dataType)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdIrrSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdIrrSet: type index (" + typeIndex + "=" + st + ")");
+
+    float[][] samples = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+    ErrorEstimate[] errs = null;
+    Delaunay delaunay = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_FLOAT_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdIrrSet: FLD_FLOAT_SAMPLES (" + FLD_FLOAT_SAMPLES + ")");
+        samples = BinaryFloatMatrix.read(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdIrrSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdIrrSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdIrrSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdIrrSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_INDEX_ERRORS:
+if(DEBUG_RD_DATA)System.err.println("rdIrrSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+        errs = BinaryErrorEstimate.readList(reader);
+        break;
+      case FLD_DELAUNAY_SERIAL:
+if(DEBUG_RD_DATA)System.err.println("rdIrrSet: FLD_DELAUNAY_SERIAL (" + FLD_DELAUNAY_SERIAL + ")");
+        delaunay = (Delaunay )BinarySerializedObject.read(file);
+        break;
+      case FLD_DELAUNAY:
+if(DEBUG_RD_DATA)System.err.println("rdIrrSet: FLD_DELAUNAY (" + FLD_DELAUNAY + ")");
+        delaunay = BinaryDelaunay.read(file);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdIrrSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown IrregularSet directive " +
+                              directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for IrregularSet");
+    }
+
+    int dim;
+    switch (dataType) {
+    case DATA_IRREGULAR_1D_SET:
+      dim = 1;
+      break;
+    case DATA_IRREGULAR_2D_SET:
+      dim = 2;
+      break;
+    case DATA_IRREGULAR_3D_SET:
+      dim = 3;
+      break;
+    case DATA_IRREGULAR_SET:
+      dim = -1;
+      break;
+    default:
+      throw new IOException("Unknown IrregularSet type " + dataType);
+    }
+
+    if (dim > 0 && samples.length != dim) {
+      throw new VisADException("Expected " + dim +
+                               "D sample array, not " +
+                               samples.length + "D");
+    }
+    if (dataType == DATA_IRREGULAR_1D_SET && delaunay != null) {
+      System.err.println("Delaunay ignored for Irregular1DSet");
+    }
+
+    switch (dataType) {
+    case DATA_IRREGULAR_1D_SET:
+      return new Irregular1DSet(st, samples, cs, units, errs);
+    case DATA_IRREGULAR_2D_SET:
+      return new Irregular2DSet(st, samples, cs, units, errs, delaunay);
+    case DATA_IRREGULAR_3D_SET:
+      return new Irregular3DSet(st, samples, cs, units, errs, delaunay);
+    case DATA_IRREGULAR_SET:
+      return new IrregularSet(st, samples, cs, units, errs, delaunay);
+    default:
+      throw new IOException("Unknown IrregularSet type " + dataType);
+    }
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              IrregularSet set,
+                                              Class canonicalClass,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof IrregularSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrIrrSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrIrrSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrIrrSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrIrrSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (errors != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE){
+  System.err.println("wrIrrSet: List of " + errors.length + " ErrorEstimates");
+  for(int x=0;x<errors.length;x++){
+    System.err.println("wrIrrSet:    #"+x+": "+errors[x]);
+  }
+}
+      BinaryErrorEstimate.writeList(writer, errors, SAVE_DATA);
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 float[][] samples, CoordinateSystem cs,
+                                 Unit[] units, ErrorEstimate[] errors,
+                                 Delaunay delaunay, IrregularSet set,
+                                 Class canonicalClass, byte dataType,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, cs, units, errors, set,
+                       canonicalClass, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof IrregularSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    if (samples == null) {
+      throw new IOException("Null " + canonicalClass.getName() + " samples");
+    }
+
+    final int validLen;
+    switch (dataType) {
+    case DATA_IRREGULAR_1D_SET:
+      validLen = 1;
+      break;
+    case DATA_IRREGULAR_2D_SET:
+      validLen = 2;
+      break;
+    case DATA_IRREGULAR_3D_SET:
+      validLen = 3;
+      break;
+    case DATA_IRREGULAR_SET:
+      validLen = -1;
+      break;
+    default:
+      throw new IOException("Invalid type " + dataType);
+    }
+
+    if (validLen > 0 && samples.length != validLen) {
+      throw new IOException("Expected " + validLen + " sample list" +
+                            (validLen > 1 ? "s" : "") + ", not " +
+                            samples.length);
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    int[] errorsIndex = null;
+    if (errors != null) {
+      errorsIndex = BinaryErrorEstimate.lookupList(writer.getErrorEstimateCache(),
+                                                   errors);
+    }
+
+    final int objLen = computeBytes(samples, cs, units, errors, delaunay);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: " +
+                                 (dataType == DATA_IRREGULAR_1D_SET ?
+                                  "DATA_IRREGULAR_1D" :
+                                  (dataType == DATA_IRREGULAR_2D_SET ?
+                                   "DATA_IRREGULAR_2D" :
+                                   (dataType == DATA_IRREGULAR_3D_SET ?
+                                    "DATA_IRREGULAR_3D" :
+                                    (dataType == DATA_IRREGULAR_SET ?
+                                     "DATA_IRREGULAR" :
+                                     "DATA_???")))) +
+                                 "(" + dataType + ")");
+    file.writeByte(dataType);
+
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: FLD_FLOAT_SAMPLES (" + FLD_FLOAT_SAMPLES + ")");
+    file.writeByte(FLD_FLOAT_SAMPLES);
+    BinaryFloatMatrix.write(writer, samples, token);
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: csIndex (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+    if (errorsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+      file.writeByte(FLD_INDEX_ERRORS);
+      BinaryIntegerArray.write(writer, errorsIndex, token);
+    }
+
+    if (delaunay != null) {
+      BinaryDelaunay.write(writer, delaunay, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrIrrSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryLinearSet.java b/visad/data/visad/object/BinaryLinearSet.java
new file mode 100644
index 0000000..9a1d9c3
--- /dev/null
+++ b/visad/data/visad/object/BinaryLinearSet.java
@@ -0,0 +1,511 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.GriddedSet;
+import visad.LinearSet;
+import visad.Linear1DSet;
+import visad.Linear2DSet;
+import visad.Linear3DSet;
+import visad.LinearLatLonSet;
+import visad.LinearNDSet;
+import visad.MathType;
+import visad.SetType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryLinearSet
+  implements BinaryObject
+{
+  public static final int computeBytes(boolean matchedTypes, double[] firsts,
+                                       double[] lasts, int[] lengths,
+                                       Linear1DSet[] comps,
+                                       CoordinateSystem cs, Unit[] units,
+                                       ErrorEstimate[] errors)
+  {
+    int compsLen;
+    if (matchedTypes) {
+      compsLen = 1 + BinaryDoubleArray.computeBytes(firsts) +
+        1 + BinaryDoubleArray.computeBytes(lasts) +
+        1 + BinaryIntegerArray.computeBytes(lengths);
+    } else {
+      compsLen = 1 + 4;
+      for (int i = 0; i < comps.length; i++) {
+        int size = BinaryGeneric.computeBytes(comps[i]);
+        if (size < 0) {
+          return -1;
+        }
+
+        compsLen += size;
+      }
+    }
+
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    final int errorsLen = BinaryErrorEstimate.computeBytes(errors);
+    return 1 + 4 + 1 + 4 +
+      compsLen +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      (errorsLen == 0 ? 0 : errorsLen + 1) +
+      1;
+  }
+
+  public static boolean hasMatchedTypes(SetType type, Linear1DSet[] comps)
+  {
+    if (comps == null) {
+      return true;
+    }
+
+    MathType[] dComp = type.getDomain().getComponents();
+    if (dComp == null || dComp.length != comps.length) {
+      return false;
+    }
+
+    boolean matchedTypes = true;
+    for (int i = 0; i < dComp.length; i++) {
+      if (!dComp[i].equals(comps[i].getType())) {
+        matchedTypes = false;
+        break;
+      }
+    }
+
+    return matchedTypes;
+  }
+
+  private static final Linear1DSet[] readLinear1DSets(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    DataInput file = reader.getInput();
+
+    Linear1DSet[] sets = new Linear1DSet[file.readInt()];
+    for (int i = 0; i < sets.length; i++) {
+      sets[i] = (Linear1DSet )BinaryGeneric.read(reader);
+    }
+    return sets;
+  }
+
+  public static final GriddedSet read(BinaryReader reader, byte dataType)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdLinSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdLinSet: type index (" + typeIndex + "=" + st + ")");
+
+    double[] firsts = null;
+    double[] lasts = null;
+    int[] lengths = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+    ErrorEstimate[] errs = null;
+    Linear1DSet[] comps = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_FIRSTS:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_FIRSTS (" + FLD_FIRSTS + ")");
+        firsts = BinaryDoubleArray.read(reader);
+        break;
+      case FLD_LASTS:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_LASTS (" + FLD_LASTS + ")");
+        lasts = BinaryDoubleArray.read(reader);
+        break;
+      case FLD_LENGTHS:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+        lengths = BinaryIntegerArray.read(reader);
+        break;
+      case FLD_LINEAR_SETS:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_LINEAR_SETS (" + FLD_LINEAR_SETS + ")");
+        comps = readLinear1DSets(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdLinSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdLinSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_INDEX_ERRORS:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+        errs = BinaryErrorEstimate.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown LinearSet directive " + directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for LinearSet");
+    }
+
+    if (comps != null) {
+      if (firsts != null) {
+        throw new IOException("Both components and firsts found for LinearSet");
+      }
+      if (lasts != null) {
+        throw new IOException("Both components and lasts found for LinearSet");
+      }
+      if (lengths != null) {
+        throw new IOException("Both components and lengths found for LinearSet");
+      }
+
+      switch (dataType) {
+      case DATA_LINEAR_1D_SET:
+        throw new IOException("Components specified for Linear1DSet");
+      case DATA_LINEAR_2D_SET:
+        return new Linear2DSet(st, comps, cs, units, errs);
+      case DATA_LINEAR_3D_SET:
+        return new Linear3DSet(st, comps, cs, units, errs);
+      case DATA_LINEAR_ND_SET:
+        return new LinearNDSet(st, comps, cs, units, errs);
+      case DATA_LINEAR_LATLON_SET:
+        return new LinearLatLonSet(st, comps, cs, units, errs);
+      default:
+        throw new IOException("Unknown LinearSet type " + dataType);
+      }
+    } else {
+
+      if (firsts == null) {
+        throw new IOException("No firsts found for LinearSet");
+      }
+      if (lasts == null) {
+        throw new IOException("No lasts found for LinearSet");
+      }
+      if (lengths == null) {
+        throw new IOException("No lengths found for LinearSet");
+      }
+
+      final int dim;
+      switch (dataType) {
+      case DATA_LINEAR_1D_SET:
+        dim = 1;
+        break;
+      case DATA_LINEAR_2D_SET:
+        dim = 2;
+        break;
+      case DATA_LINEAR_3D_SET:
+        dim = 3;
+        break;
+      case DATA_LINEAR_LATLON_SET:
+        dim = 2;
+        break;
+      default:
+        dim = -1;
+        break;
+      }
+
+      if (dim > 0 && firsts.length != dim) {
+        throw new VisADException("Expected " + dim + " first value" +
+                                 (dim > 1 ? "s" : "") + ", not " +
+                                 firsts.length);
+      }
+      if (dim > 0 && lasts.length != dim) {
+        throw new VisADException("Expected " + dim + " last value" +
+                                 (dim > 1 ? "s" : "") + ", not " +
+                                 lasts.length);
+      }
+      if (dim > 0 && lengths.length != dim) {
+        throw new VisADException("Expected " + dim + " length" +
+                                 (dim > 1 ? "s" : "") + ", not " +
+                                 lengths.length);
+      }
+
+      switch (dataType) {
+      case DATA_LINEAR_1D_SET:
+        return new Linear1DSet(st, firsts[0], lasts[0], lengths[0], cs, units,
+                               errs);
+      case DATA_LINEAR_2D_SET:
+        return new Linear2DSet(st, firsts[0], lasts[0], lengths[0],
+                               firsts[1], lasts[1], lengths[1], cs, units, errs);
+      case DATA_LINEAR_3D_SET:
+        return new Linear3DSet(st, firsts[0], lasts[0], lengths[0],
+                               firsts[1], lasts[1], lengths[1],
+                               firsts[2], lasts[2], lengths[2], cs, units, errs);
+      case DATA_LINEAR_ND_SET:
+        return new LinearNDSet(st, firsts, lasts, lengths, cs, units, errs);
+      case DATA_LINEAR_LATLON_SET:
+        return new LinearLatLonSet(st, firsts[0], lasts[0], lengths[0],
+                                   firsts[1], lasts[1], lengths[1],
+                                   cs, units, errs);
+      default:
+        throw new IOException("Unknown LinearSet type " + dataType);
+      }
+    }
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              Linear1DSet[] comps,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              GriddedSet set,
+                                              Class canonicalClass,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof LinearSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrLinSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrLinSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrLinSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrLinSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (errors != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE){
+  System.err.println("wrLinSet: List of " + errors.length + " ErrorEstimates");
+  for(int x=0;x<errors.length;x++){
+    System.err.println("wrLinSet:    #"+x+": "+errors[x]);
+  }
+}
+      BinaryErrorEstimate.writeList(writer, errors, SAVE_DATA);
+    }
+
+    if (comps != null) {
+      for (int i = 0; i < comps.length; i++) {
+        BinaryGeneric.write(writer, comps[i], dependToken);
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 double[] firsts, double[] lasts,
+                                 int[] lengths, Linear1DSet[] comps,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, GriddedSet set,
+                                 Class canonicalClass, byte dataType,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, comps, cs, units, errors, set,
+                       canonicalClass, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof LinearSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    // see if domain types and component types match
+    boolean matchedTypes = hasMatchedTypes(type, comps);
+
+    final int dim = set.getDimension();
+
+    if (!matchedTypes) {
+      if (dataType == DATA_LINEAR_1D_SET) {
+        throw new IOException("Components specified for Linear1DSet");
+      }
+
+      if (comps.length != dim) {
+        throw new IOException("Expected " + dim + " LinearSet component" +
+                              (dim > 1 ? "s" : "") + ", not " +
+                              comps.length);
+      }
+    } else {
+      if (firsts == null) {
+        throw new IOException("Null " + canonicalClass.getName() + " firsts");
+      }
+      if (lasts == null) {
+        throw new IOException("Null " + canonicalClass.getName() + " lasts");
+      }
+      if (lengths == null) {
+        throw new IOException("Null " + canonicalClass.getName() +
+                              " lengths");
+      }
+
+      if (firsts.length != dim) {
+        throw new IOException("Expected " + dim + " LinearSet first value" +
+                              (dim > 1 ? "s" : "") + ", not " +
+                              firsts.length);
+      }
+      if (lasts.length != dim) {
+        throw new IOException("Expected " + dim + " LinearSet last value" +
+                              (dim > 1 ? "s" : "") + ", not " +
+                              lasts.length);
+      }
+      if (lengths.length != dim) {
+        throw new IOException("Expected " + dim + " LinearSet length" +
+                              (dim > 1 ? "s" : "") + ", not " +
+                              lengths.length);
+      }
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    int[] errorsIndex = null;
+    if (errors != null) {
+      errorsIndex = BinaryErrorEstimate.lookupList(writer.getErrorEstimateCache(),
+                                                   errors);
+    }
+
+    final int objLen = computeBytes(matchedTypes, firsts, lasts, lengths,
+                                    comps, cs, units, errors);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: " +
+                                 (dataType == DATA_LINEAR_1D_SET ?
+                                  "DATA_LINEAR_1D" :
+                                  (dataType == DATA_LINEAR_2D_SET ?
+                                   "DATA_LINEAR_2D" :
+                                   (dataType == DATA_LINEAR_3D_SET ?
+                                    "DATA_LINEAR_3D" :
+                                    (dataType == DATA_LINEAR_ND_SET ?
+                                     "DATA_LINEAR_ND" :
+                                     (dataType == DATA_LINEAR_LATLON_SET ?
+                                      "DATA_LINEAR_LATLON" :
+                                      "DATA_???"))))) +
+                                 "(" + dataType + ")");
+    file.writeByte(dataType);
+
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    if (matchedTypes) {
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_FIRSTS (" + FLD_FIRSTS + ")");
+      file.writeByte(FLD_FIRSTS);
+      BinaryDoubleArray.write(writer, firsts, token);
+
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_LASTS (" + FLD_LASTS + ")");
+      file.writeByte(FLD_LASTS);
+      BinaryDoubleArray.write(writer, lasts, token);
+
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_LENGTHS (" + FLD_LENGTHS + ")");
+      file.writeByte(FLD_LENGTHS);
+      BinaryIntegerArray.write(writer, lengths, token);
+    } else {
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_LINEAR_SETS (" + FLD_LINEAR_SETS + ")");
+      file.writeByte(FLD_LINEAR_SETS);
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: length (" + comps.length + ")");
+      file.writeInt(comps.length);
+      for (int i = 0; i < comps.length; i++) {
+        BinaryGeneric.write(writer, comps[i], token);
+      }
+    }
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: csIndex (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+    if (errorsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+      file.writeByte(FLD_INDEX_ERRORS);
+      BinaryIntegerArray.write(writer, errorsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrLinSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryList1DSet.java b/visad/data/visad/object/BinaryList1DSet.java
new file mode 100644
index 0000000..e4e71b4
--- /dev/null
+++ b/visad/data/visad/object/BinaryList1DSet.java
@@ -0,0 +1,225 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.List1DSet;
+import visad.MathType;
+import visad.SetType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryList1DSet
+  implements BinaryObject
+{
+  public static final int computeBytes(float[] list, CoordinateSystem cs,
+                                       Unit[] units)
+  {
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    return 1 + 4 + 1 + 4 +
+      1 + BinaryFloatArray.computeBytes(list) +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      1;
+  }
+
+  public static final List1DSet read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdL1DSet: type index (" + typeIndex + ")");
+    MathType mt = (MathType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdL1DSet: type index (" + typeIndex + "=" + mt + ")");
+
+    float[] list = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_FLOAT_LIST:
+if(DEBUG_RD_DATA)System.err.println("rdL1DSet: FLD_FLOAT_LIST (" + FLD_FLOAT_LIST + ")");
+        list = BinaryFloatArray.read(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdL1DSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdL1DSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdL1DSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdL1DSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdL1DSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown List1DSet directive " +
+                              directive);
+      }
+    }
+
+    if (mt == null) {
+      throw new IOException("No MathType found for List1DSet");
+    }
+    if (list == null) {
+      throw new IOException("No list found for List1DSet");
+    }
+
+    return new List1DSet(list, mt, cs, units);
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              CoordinateSystem cs,
+                                              Unit[] units, List1DSet set,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(List1DSet.class) &&
+        !(set instanceof List1DSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrL1DSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrL1DSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrL1DSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrL1DSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 float[] list, CoordinateSystem cs,
+                                 Unit[] units, List1DSet set, Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, cs, units, set, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(List1DSet.class) &&
+        !(set instanceof List1DSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    if (list == null) {
+      throw new IOException("Null List1DSet list");
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    final int objLen = computeBytes(list, cs, units);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: DATA_LIST1D_SET (" + DATA_LIST1D_SET + ")");
+    file.writeByte(DATA_LIST1D_SET);
+
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: FLD_FLOAT_LIST (" + FLD_FLOAT_LIST + ")");
+    file.writeByte(FLD_FLOAT_LIST);
+    BinaryFloatArray.write(writer, list, token);
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrL1DSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryMathType.java b/visad/data/visad/object/BinaryMathType.java
new file mode 100644
index 0000000..3c4f49b
--- /dev/null
+++ b/visad/data/visad/object/BinaryMathType.java
@@ -0,0 +1,133 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.IOException;
+
+import visad.FunctionType;
+import visad.MathType;
+import visad.ScalarType;
+import visad.SetType;
+import visad.TupleType;
+import visad.VisADException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryMathType
+  implements BinaryObject
+{
+  public static final MathType read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    DataInput file = reader.getInput();
+
+    final int objLen = file.readInt();
+if(DEBUG_RD_MATH)System.err.println("cchTy: objLen (" + objLen + ")");
+
+    // read the index number for this MathType
+    final int index = file.readInt();
+if(DEBUG_RD_MATH)System.err.println("cchTy: index (" + index + ")");
+
+    final byte mathType = file.readByte();
+    switch (mathType) {
+    case MATH_FUNCTION:
+if(DEBUG_RD_MATH)System.err.println("rdMthTy: MATH_FUNCTION (" + MATH_FUNCTION + ")");
+      return BinaryFunctionType.read(reader, index);
+    case MATH_QUANTITY:
+if(DEBUG_RD_MATH)System.err.println("rdMthTy: MATH_QUANTITY (" + MATH_QUANTITY + ")");
+      return BinaryQuantity.read(reader, index);
+    case MATH_REAL_TUPLE:
+if(DEBUG_RD_MATH)System.err.println("rdMthTy: MATH_REAL_TUPLE (" + MATH_REAL_TUPLE + ")");
+      return BinaryRealTupleType.read(reader, index);
+    case MATH_REAL:
+if(DEBUG_RD_MATH)System.err.println("rdMthTy: MATH_REAL (" + MATH_REAL + ")");
+      return BinaryRealType.read(reader, index);
+    case MATH_SET:
+if(DEBUG_RD_MATH)System.err.println("rdMthTy: MATH_SET (" + MATH_SET + ")");
+      return BinarySetType.read(reader, index);
+    case MATH_TEXT:
+if(DEBUG_RD_MATH)System.err.println("rdMthTy: MATH_TEXT (" + MATH_TEXT + ")");
+      return BinaryTextType.read(reader, index);
+    case MATH_TUPLE:
+if(DEBUG_RD_MATH)System.err.println("rdMthTy: MATH_TUPLE (" + MATH_TUPLE + ")");
+      return BinaryTupleType.read(reader, index, objLen - 5);
+    default:
+      throw new VisADException("Unknown Math type " + mathType);
+    }
+  }
+
+  public static final MathType[] readList(BinaryReader reader, int dim)
+    throws IOException, VisADException
+  {
+    if (dim < 1) {
+      throw new IOException("Corrupted file (bad MathType list length)");
+    }
+
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    MathType[] list = new MathType[dim];
+    for (int i = 0; i < dim; i++) {
+      final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdMTyS: type #" + i + " index (" + typeIndex + ")");
+      list[i] = (MathType )cache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdMTyS: type #" + i + " index (" + typeIndex + "=" + list[i] + ")");
+    }
+
+    return list;
+  }
+
+  public static final int write(BinaryWriter writer, MathType mt,
+                                Object token)
+    throws IOException
+  {
+    int index;
+
+    if (mt instanceof FunctionType) {
+      index = BinaryFunctionType.write(writer, (FunctionType )mt, token);
+    } else if (mt instanceof ScalarType) {
+      index = BinaryScalarType.write(writer, (ScalarType )mt, token);
+    } else if (mt instanceof SetType) {
+      index = BinarySetType.write(writer, (SetType )mt, null, token);
+    } else if (mt instanceof TupleType) {
+      index = BinaryTupleType.write(writer, (TupleType )mt, token);
+    } else {
+      BinaryObjectCache cache = writer.getTypeCache();
+
+      index = cache.getIndex(mt);
+      if (index < 0) {
+        index = cache.add(mt);
+        if (index < 0) {
+          throw new IOException("Couldn't cache MathType " + mt);
+        }
+
+        BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, mt, token);
+      }
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryObject.java b/visad/data/visad/object/BinaryObject.java
new file mode 100644
index 0000000..45a115f
--- /dev/null
+++ b/visad/data/visad/object/BinaryObject.java
@@ -0,0 +1,33 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import visad.data.visad.BinaryFile;
+
+public interface BinaryObject
+  extends BinaryFile
+{
+  Object SAVE_DEPEND = new Integer(1);
+  Object SAVE_DEPEND_BIG = new Integer(5);
+  Object SAVE_DATA = new Integer(100);
+}
diff --git a/visad/data/visad/object/BinaryProductSet.java b/visad/data/visad/object/BinaryProductSet.java
new file mode 100644
index 0000000..a2b8efb
--- /dev/null
+++ b/visad/data/visad/object/BinaryProductSet.java
@@ -0,0 +1,266 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.ErrorEstimate;
+import visad.ProductSet;
+import visad.SampledSet;
+import visad.SetType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryProductSet
+  implements BinaryObject
+{
+  public static final int computeBytes(SampledSet[] sets, CoordinateSystem cs,
+                                       Unit[] units, ErrorEstimate[] errors)
+  {
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    final int errorsLen = BinaryErrorEstimate.computeBytes(errors);
+    return 1 + 4 + 1 + 4 +
+      BinarySampledSet.computeBytes(sets) +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      (errorsLen == 0 ? 0 : errorsLen + 1) +
+      1;
+  }
+
+  public static final ProductSet read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdPrSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdPrSet: type index (" + typeIndex + "=" + st + ")");
+
+    SampledSet[] sets = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+    ErrorEstimate[] errs = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_SET_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdPrSet: FLD_SET_SAMPLES (" + FLD_SET_SAMPLES + ")");
+        sets = BinarySampledSet.readList(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdPrSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdPrSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdPrSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdPrSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_INDEX_ERRORS:
+if(DEBUG_RD_DATA)System.err.println("rdPrSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+        errs = BinaryErrorEstimate.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdPrSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown ProductSet directive " +
+                              directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for ProductSet");
+    }
+    if (sets == null) {
+      throw new IOException("No sets found for ProductSet");
+    }
+
+    return new ProductSet(st, sets, cs, units, errs);
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              SampledSet[] sets,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              ProductSet set,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(ProductSet.class) &&
+        !(set instanceof ProductSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrPrSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrPrSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrPrSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrPrSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (errors != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE){
+  System.err.println("wrPrSet: List of " + errors.length + " ErrorEstimates");
+  for(int x=0;x<errors.length;x++){
+    System.err.println("wrPrSet:    #"+x+": "+errors[x]);
+  }
+}
+      BinaryErrorEstimate.writeList(writer, errors, SAVE_DATA);
+    }
+
+    if (sets != null) {
+      for (int i = 0; i < sets.length; i++) {
+        BinaryGeneric.write(writer, sets[i], dependToken);
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 SampledSet[] sets, CoordinateSystem cs,
+                                 Unit[] units, ErrorEstimate[] errors,
+                                 ProductSet set, Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, sets, cs, units, errors, set, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(ProductSet.class) &&
+        !(set instanceof ProductSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    int[] errorsIndex = null;
+    if (errors != null) {
+      errorsIndex = BinaryErrorEstimate.lookupList(writer.getErrorEstimateCache(),
+                                                   errors);
+    }
+
+    final int objLen = computeBytes(sets, cs, units, errors);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: DATA_PRODUCT_SET (" + DATA_PRODUCT_SET + ")");
+    file.writeByte(DATA_PRODUCT_SET);
+
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    BinarySampledSet.writeList(writer, sets, token);
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: csIndex (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+    if (errorsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+      file.writeByte(FLD_INDEX_ERRORS);
+      BinaryIntegerArray.write(writer, errorsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrPrSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryQuantity.java b/visad/data/visad/object/BinaryQuantity.java
new file mode 100644
index 0000000..3050c2d
--- /dev/null
+++ b/visad/data/visad/object/BinaryQuantity.java
@@ -0,0 +1,181 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.Set;
+import visad.SimpleSet;
+import visad.TypeException;
+import visad.VisADException;
+
+import visad.data.netcdf.Quantity;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryQuantity
+  implements BinaryObject
+{
+  public static final int computeBytes(Quantity qt)
+  {
+    return 5 +
+      BinaryString.computeBytes(qt.getName()) +
+      BinaryString.computeBytes(qt.getDefaultUnitString()) +
+      (qt.getDefaultSet() == null ? 0 : 1) +
+      1;
+  }
+
+  public static final Quantity read(BinaryReader reader, int index)
+    throws IOException, VisADException
+  {
+    // read the name
+    String name = BinaryString.read(reader);
+if(DEBUG_RD_MATH&&!DEBUG_RD_STR)System.err.println("rdQuant: name (" + name + ")");
+
+    // read the name
+    String unitSpec = BinaryString.read(reader);
+if(DEBUG_RD_MATH&&!DEBUG_RD_STR)System.err.println("rdQuant: unitSpec (" + unitSpec + ")");
+
+    boolean setFollowsType = false;
+
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_SET_FOLLOWS_TYPE:
+if(DEBUG_RD_MATH)System.err.println("rdQuant: FLD_SET_FOLLOWS_TYPE (" + FLD_SET_FOLLOWS_TYPE + ")");
+        setFollowsType = true;
+        break;
+      case FLD_END:
+if(DEBUG_RD_MATH)System.err.println("rdQuant: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown RealType directive " + directive);
+      }
+    }
+
+    Quantity q;
+    try {
+      q = Quantity.getQuantity(name, unitSpec);
+    } catch (visad.data.units.ParseException pe) {
+      throw new VisADException("Couldn't parse Quantity unitSpec \"" +
+                               unitSpec + "\"");
+    }
+
+    if (q == null) {
+      throw new VisADException("Couldn't create Quantity named \"" + name +
+                               "\"");
+    }
+
+    cache.add(index, q);
+
+    if (setFollowsType) {
+      SimpleSet set = (SimpleSet )BinaryGeneric.read(reader);
+      try {
+        q.setDefaultSet(set);
+      } catch (TypeException te) {
+        // ignore failure to set type
+      }
+    }
+
+    return q;
+  }
+
+  public static final int write(BinaryWriter writer, Quantity qt,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(qt);
+    if (index < 0) {
+      index = cache.add(qt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache Quantity " + qt);
+      }
+
+      if (!qt.getClass().equals(Quantity.class) &&
+          !(qt instanceof Quantity && qt instanceof Saveable))
+      {
+if(DEBUG_WR_MATH)System.err.println("wrQuant: serialized Quantity (" + qt.getClass().getName() + ")");
+        BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, qt, token);
+        return index;
+      }
+
+      DataOutput file = writer.getOutput();
+
+      String nameStr = qt.getName();
+      String unitStr = qt.getDefaultUnitString();
+
+      Set dfltSet = qt.getDefaultSet();
+
+      // total number of bytes written for this object
+      final int objLen = computeBytes(qt);
+
+if(DEBUG_WR_MATH)System.err.println("wrQuant: OBJ_MATH (" + OBJ_MATH + ")");
+      file.writeByte(OBJ_MATH);
+if(DEBUG_WR_MATH)System.err.println("wrQuant: objLen (" + objLen + ")");
+      file.writeInt(objLen);
+if(DEBUG_WR_MATH)System.err.println("wrQuant: index (" + index + ")");
+      file.writeInt(index);
+if(DEBUG_WR_MATH)System.err.println("wrQuant: MATH_QUANTITY (" + MATH_QUANTITY + ")");
+      file.writeByte(MATH_QUANTITY);
+
+if(DEBUG_WR_MATH)System.err.println("wrQuant: name (" + nameStr + ")");
+      BinaryString.write(writer, nameStr, token);
+
+if(DEBUG_WR_MATH)System.err.println("wrQuant: unitSpec (" + unitStr + ")");
+      BinaryString.write(writer, unitStr, token);
+
+      if (dfltSet != null) {
+if(DEBUG_WR_MATH)System.err.println("wrQuant: FLD_SET_FOLLOWS_TYPE (" + FLD_SET_FOLLOWS_TYPE + ")");
+        file.writeByte(FLD_SET_FOLLOWS_TYPE);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrQuant: FLD_END (" + FLD_END + ")");
+      file.writeByte(FLD_END);
+
+      if (dfltSet != null) {
+        BinaryGeneric.write(writer, dfltSet, SAVE_DATA);
+      }
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryReal.java b/visad/data/visad/object/BinaryReal.java
new file mode 100644
index 0000000..49f6b74
--- /dev/null
+++ b/visad/data/visad/object/BinaryReal.java
@@ -0,0 +1,189 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.ErrorEstimate;
+import visad.Real;
+import visad.RealType;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryReal
+  implements BinaryObject
+{
+  public static final int computeBytes(Unit u, ErrorEstimate err)
+  {
+    return 1 + 4 + 1 + 4 + 8 +
+        (u == null ? 0 : 5) +
+        (err == null ? 0 : 5) +
+        1;
+  }
+
+  public static final Real read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache errorCache = reader.getErrorEstimateCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    BinaryObjectCache unitCache = reader.getUnitCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdRl: type index (" + typeIndex + ")");
+    RealType rt = (RealType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdRl: type index (" + typeIndex + "=" + rt + ")");
+
+    // read the value
+    final double value = file.readDouble();
+if(DEBUG_RD_DATA)System.err.println("rdRl: value (" + value + ")");
+
+    Unit u = null;
+    ErrorEstimate error = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_INDEX_UNIT:
+if(DEBUG_RD_DATA)System.err.println("rdRl: FLD_INDEX_UNIT (" + FLD_INDEX_UNIT + ")");
+        final int uIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_UNIT)System.err.println("rdRl: unit index (" + uIndex + ")");
+        u = (Unit )unitCache.get(uIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_UNIT)System.err.println("rdRl: unit index (" + uIndex + "=" + u + ")");
+        break;
+      case FLD_INDEX_ERROR:
+if(DEBUG_RD_DATA)System.err.println("rdRl: FLD_INDEX_ERROR (" + FLD_INDEX_ERROR + ")");
+        final int eIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_ERRE)System.err.println("rdRl: error index (" + eIndex + ")");
+        error = (ErrorEstimate )errorCache.get(eIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_ERRE)System.err.println("rdRl: error index (" + eIndex + "=" + error + ")");
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdRl: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown Real directive " + directive);
+      }
+    }
+
+    return new Real(rt, value, u, error);
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              RealType type, Unit unit,
+                                              ErrorEstimate error, Real real,
+                                              Object token)
+    throws IOException
+  {
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrRl: MathType (" + type + ")");
+    BinaryRealType.write(writer, type, SAVE_DATA);
+
+    if (unit != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT)System.err.println("wrRl: Unit (" + unit + ")");
+      BinaryUnit.write(writer, unit, SAVE_DATA);
+    }
+
+    if (error != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE)System.err.println("wrRl: ErrEst (" + error + ")");
+      BinaryErrorEstimate.write(writer, error, SAVE_DATA);
+    }
+  }
+
+  public static final void write(BinaryWriter writer, RealType type,
+                                 double value, Unit unit, ErrorEstimate error,
+                                 Real real, Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, unit, error, real, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    int typeIndex;
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrRl: MathType (" + type + ")");
+    typeIndex = BinaryRealType.write(writer, type, token);
+
+    int uIndex = -1;
+    if (unit != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT)System.err.println("wrRl: Unit (" + unit + ")");
+      uIndex = BinaryUnit.write(writer, unit, token);
+    }
+
+    int errIndex = -1;
+    if (error != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE)System.err.println("wrRl: ErrEst (" + error + ")");
+      errIndex = BinaryErrorEstimate.write(writer, error, token);
+    }
+
+    final int objLen = computeBytes(unit, error);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrRl: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrRl: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrRl: DATA_REAL (" + DATA_REAL + ")");
+    file.writeByte(DATA_REAL);
+
+if(DEBUG_WR_DATA)System.err.println("wrRl: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+if(DEBUG_WR_DATA)System.err.println("wrRl: value (" + value + ")");
+    file.writeDouble(value);
+
+    if (uIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrRl: FLD_INDEX_UNIT (" + FLD_INDEX_UNIT + ")");
+      file.writeByte(FLD_INDEX_UNIT);
+if(DEBUG_WR_DATA)System.err.println("wrRl: unit index (" + uIndex + ")");
+      file.writeInt(uIndex);
+    }
+
+    if (errIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrRl: FLD_INDEX_ERROR (" + FLD_INDEX_ERROR + ")");
+      file.writeByte(FLD_INDEX_ERROR);
+if(DEBUG_WR_DATA)System.err.println("wrRl: err index (" + errIndex + ")");
+      file.writeInt(errIndex);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrRl: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryRealTuple.java b/visad/data/visad/object/BinaryRealTuple.java
new file mode 100644
index 0000000..9b1c05d
--- /dev/null
+++ b/visad/data/visad/object/BinaryRealTuple.java
@@ -0,0 +1,306 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.MathType;
+import visad.Real;
+import visad.RealTuple;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryRealTuple
+  implements BinaryObject
+{
+  public static final int computeBytes(Real[] components, CoordinateSystem cs,
+                                       boolean trivialTuple)
+  {
+    int compsLen;
+    if (components == null) {
+      compsLen = 0;
+    } else {
+      if (trivialTuple) {
+        compsLen = 1 + 4 + (components.length * 8);
+      } else {
+        compsLen = 1 + 4;
+        for (int i = 0; i < components.length; i++) {
+          compsLen += BinaryReal.computeBytes(components[i].getUnit(),
+                                              components[i].getError());
+        }
+      }
+    }
+
+    return 1 + 4 + 1 + 4 +
+      compsLen +
+      (cs == null ? 0 : 5) +
+      1;
+  }
+
+  public static final boolean isTrivialTuple(RealTupleType type,
+                                             Real[] components)
+  {
+    if (components == null) {
+      return true;
+    }
+
+    for (int i = 0; i < components.length; i++) {
+      if (components[i] != null) {
+        MathType comp;
+        try {
+          comp = type.getComponent(i);
+        } catch (VisADException ve) {
+          return false;
+        }
+
+        if (!comp.equals(components[i].getType()) ||
+            components[i].getUnit() != null ||
+            components[i].getError() != null)
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  public static final RealTuple read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdRlTpl: type index (" + typeIndex + ")");
+    RealTupleType rtt = (RealTupleType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdRlTpl: type index (" + typeIndex + "=" + rtt + ")");
+
+    Real[] components = null;
+    double[] values = null;
+    CoordinateSystem cs = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdRlTpl: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdRlTpl: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdRlTpl: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_REAL_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdRlTpl: FLD_REAL_SAMPLES (" + FLD_REAL_SAMPLES + ")");
+        components = readRealArray(reader);
+        break;
+      case FLD_TRIVIAL_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdRlTpl: FLD_TRIVIAL_SAMPLES (" + FLD_TRIVIAL_SAMPLES + ")");
+        values = BinaryDoubleArray.read(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdRlTpl: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown RealTuple directive " + directive);
+      }
+    }
+
+    if (components != null && values != null) {
+      throw new IOException("Found both RealTuple Real[] and double[] values");
+    }
+
+    if (values != null) {
+      if (cs == null) {
+        return new RealTuple(rtt, values);
+      }
+
+      // build a Real[] array from values,
+      components = new Real[values.length];
+      for (int i = 0; i < values.length; i++) {
+        components[i] = new Real((RealType )rtt.getComponent(i), values[i],
+                                 null, null);
+      }
+    }
+
+    return new RealTuple(rtt, components, cs);
+  }
+
+  private static final Real[] readRealArray(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdRlRA: len (" + len + ")");
+    if (len < 1) {
+      throw new IOException("Corrupted file (bad Real array length " +
+                            len + ")");
+    }
+
+long t = (DEBUG_RD_TIME ? System.currentTimeMillis() : 0);
+    Real[] array = new Real[len];
+    for (int i = 0; i < len; i++) {
+      array[i] = (Real )BinaryGeneric.read(reader);
+    }
+if(DEBUG_RD_TIME)System.err.println("rdRlRA: "+len+" arrays "+(System.currentTimeMillis()-t));
+
+    return array;
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              RealTupleType type,
+                                              Real[] components,
+                                              CoordinateSystem cs,
+                                              RealTuple rt, Object token)
+    throws IOException
+  {
+    if (!rt.getClass().equals(RealTuple.class) &&
+        !(rt instanceof RealTuple && rt instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrRlTpl: MathType (" + type + ")");
+    BinaryRealTupleType.write(writer, type, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrRlTpl: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (components != null) {
+      for (int i = 0; i < components.length; i++) {
+        BinaryGeneric.write(writer, components[i], dependToken);
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, RealTupleType type,
+                                 Real[] components, CoordinateSystem cs,
+                                 RealTuple rt, Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, components, cs, rt, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!rt.getClass().equals(RealTuple.class) &&
+        !(rt instanceof RealTuple && rt instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: punt "+rt.getClass().getName());
+      BinaryUnknown.write(writer, rt, token);
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("RealTupleType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    boolean trivialTuple = isTrivialTuple(type, components);
+
+    final int objLen = computeBytes(components, cs, trivialTuple);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: DATA_REAL_TUPLE (" + DATA_REAL_TUPLE + ")");
+    file.writeByte(DATA_REAL_TUPLE);
+
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    if (components != null) {
+      if (trivialTuple) {
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: FLD_TRIVIAL_SAMPLES (" + FLD_REAL_SAMPLES + ")");
+        file.writeByte(FLD_TRIVIAL_SAMPLES);
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: len (" + components.length + ")");
+        file.writeInt(components.length);
+        for (int i = 0; i < components.length; i++) {
+          file.writeDouble(components[i].getValue());
+        }
+      } else {
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: FLD_REAL_SAMPLES (" + FLD_REAL_SAMPLES + ")");
+        file.writeByte(FLD_REAL_SAMPLES);
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: len (" + components.length + ")");
+        file.writeInt(components.length);
+        for (int i = 0; i < components.length; i++) {
+          BinaryReal.write(writer, (RealType )components[i].getType(),
+                           components[i].getValue(), components[i].getUnit(),
+                           components[i].getError(), components[i], token);
+        }
+      }
+    }
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrRlTpl: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryRealTupleType.java b/visad/data/visad/object/BinaryRealTupleType.java
new file mode 100644
index 0000000..09187f3
--- /dev/null
+++ b/visad/data/visad/object/BinaryRealTupleType.java
@@ -0,0 +1,226 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.DisplayTupleType;
+import visad.MathType;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.RealVectorType;
+import visad.Set;
+import visad.TypeException;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryRealTupleType
+  implements BinaryObject
+{
+  public static final int computeBytes(RealTupleType rtt)
+  {
+    return 9 +
+      (rtt.getDimension() * 4) +
+      (rtt.getCoordinateSystem() == null ? 0 : 5) +
+      (rtt.getDefaultSet() == null ? 0 : 1) +
+      1;
+  }
+
+  public static final RealTupleType read(BinaryReader reader, int index)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int dim = file.readInt();
+if(DEBUG_RD_MATH)System.err.println("rdRlTuTy: dim (" + dim + ")");
+    MathType[] mtList = BinaryMathType.readList(reader, dim);
+
+    RealType[] list = new RealType[mtList.length];
+    for (int i = 0; i < mtList.length; i++) {
+      list[i] = (RealType )mtList[i];
+    }
+
+    CoordinateSystem cs = null;
+    boolean setFollowsType = false;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_MATH)System.err.println("rdRlTuTy: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int csIndex = file.readInt();
+if(DEBUG_RD_MATH&&DEBUG_RD_CSYS)System.err.println("rdRlTuTy: cSys index (" + csIndex + ")");
+        cs = (CoordinateSystem )cSysCache.get(csIndex);
+if(DEBUG_RD_MATH&&!DEBUG_RD_CSYS)System.err.println("rdRlTuTy: cSys index (" + csIndex + "=" + cs + ")");
+        break;
+      case FLD_SET_FOLLOWS_TYPE:
+if(DEBUG_RD_MATH)System.err.println("rdRlYuTy: FLD_SET_FOLLOWS_TYPE (" + FLD_SET_FOLLOWS_TYPE + ")");
+        setFollowsType = true;
+        break;
+      case FLD_END:
+if(DEBUG_RD_MATH)System.err.println("rdRlTuTy: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown RealTupleType directive " + directive);
+      }
+    }
+
+    RealTupleType rtt = new RealTupleType(list, cs, null);
+
+    typeCache.add(index, rtt);
+
+    if (setFollowsType) {
+      Set set = (Set )BinaryGeneric.read(reader);
+      try {
+        rtt.setDefaultSet(set);
+      } catch (TypeException te) {
+        // ignore failure to set type
+      }
+    }
+
+    return rtt;
+  }
+
+  public static final int write(BinaryWriter writer, RealTupleType rtt,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    if (rtt instanceof DisplayTupleType) {
+      return BinaryDisplayTupleType.write(writer, (DisplayTupleType )rtt,
+                                          token);
+    } else if (rtt instanceof RealVectorType) {
+      return BinaryRealVectorType.write(writer, (RealVectorType )rtt, token);
+    }
+
+    int index = cache.getIndex(rtt);
+    if (index < 0) {
+      index = cache.add(rtt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache RealTupleType " + rtt);
+      }
+
+      if (!rtt.getClass().equals(RealTupleType.class) &&
+          !(rtt instanceof RealTupleType && rtt instanceof Saveable))
+      {
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: serialized RealTupleType (" + rtt.getClass().getName() + ")");
+        BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, rtt, token);
+        return index;
+      }
+
+      final int dim = rtt.getDimension();
+
+      Set dfltSet = rtt.getDefaultSet();
+
+      int[] types = new int[dim];
+      for (int i = 0; i < dim; i++) {
+        RealType comp;
+        try {
+          comp = (RealType )rtt.getComponent(i);
+        } catch (VisADException ve) {
+          throw new IOException("Couldn't get RealTupleType component #" + i +
+                                ": " + ve.getMessage());
+        }
+
+        types[i] = BinaryRealType.write(writer, comp, token);
+      }
+
+      CoordinateSystem cs = rtt.getCoordinateSystem();
+
+      int csIndex = -1;
+      if (cs != null) {
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: coordSys (" + cs + ")");
+        csIndex = BinaryCoordinateSystem.write(writer, cs, token);
+      }
+
+      final int objLen = computeBytes(rtt);
+
+      DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: OBJ_MATH (" + OBJ_MATH + ")");
+      file.writeByte(OBJ_MATH);
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: objLen (" + objLen + ")");
+      file.writeInt(objLen);
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: index (" + index + ")");
+      file.writeInt(index);
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: MATH_REAL_TUPLE (" + MATH_REAL_TUPLE + ")");
+      file.writeByte(MATH_REAL_TUPLE);
+
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: dim (" + dim + ")");
+      file.writeInt(dim);
+
+      for (int i = 0; i < dim; i++) {
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: tuple #" + i + " index (" + types[i] + ")");
+        file.writeInt(types[i]);
+      }
+
+      if (csIndex >= 0) {
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: coordSys index (" + csIndex + ")");
+        file.writeInt(csIndex);
+      }
+
+      if (dfltSet != null) {
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: FLD_SET_FOLLOWS_TYPE (" + FLD_SET_FOLLOWS_TYPE + ")");
+        file.writeByte(FLD_SET_FOLLOWS_TYPE);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrRlTuTy: FLD_END (" + FLD_END + ")");
+      file.writeByte(FLD_END);
+
+      if (dfltSet != null) {
+        Object dependToken;
+        if (token == SAVE_DEPEND_BIG) {
+          dependToken = token;
+        } else {
+          dependToken = SAVE_DEPEND;
+        }
+
+        BinaryGeneric.write(writer, dfltSet, dependToken);
+        BinaryGeneric.write(writer, dfltSet, SAVE_DATA);
+      }
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryRealType.java b/visad/data/visad/object/BinaryRealType.java
new file mode 100644
index 0000000..c2b0e3e
--- /dev/null
+++ b/visad/data/visad/object/BinaryRealType.java
@@ -0,0 +1,205 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.EOFException;
+
+import visad.DisplayRealType;
+import visad.RealType;
+import visad.Set;
+import visad.TypeException;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.netcdf.Quantity;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryRealType
+  implements BinaryObject
+{
+  public static final int computeBytes(RealType rt)
+  {
+    return 9 +
+      BinaryString.computeBytes(rt.getName()) +
+      (rt.getDefaultUnit() == null ? 0 : 5) +
+      (rt.getDefaultSet() == null ? 0 : 1) +
+      1;
+  }
+
+  public static final RealType read(BinaryReader reader, int index)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    BinaryObjectCache unitCache = reader.getUnitCache();
+    DataInput file = reader.getInput();
+
+    final int attrMask = file.readInt();
+if(DEBUG_RD_MATH)System.err.println("rdRlTy: attrMask (" + attrMask + ")");
+
+    // read the name
+    String name = BinaryString.read(reader);
+if(DEBUG_RD_MATH&&!DEBUG_RD_STR)System.err.println("rdRlTy: name (" + name + ")");
+
+    Unit u = null;
+    boolean setFollowsType = false;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_INDEX_UNIT:
+if(DEBUG_RD_MATH)System.err.println("rdRlTy: FLD_INDEX_UNIT (" + FLD_INDEX_UNIT + ")");
+        final int uIndex = file.readInt();
+if(DEBUG_RD_MATH&&DEBUG_RD_UNIT)System.err.println("rdRlTy: unit index (" + index + ")");
+        u = (Unit )unitCache.get(uIndex);
+if(DEBUG_RD_MATH&&!DEBUG_RD_UNIT)System.err.println("rdRlTy: unit index (" + index + "=" + u + ")");
+        break;
+      case FLD_SET_FOLLOWS_TYPE:
+if(DEBUG_RD_MATH)System.err.println("rdRlTy: FLD_SET_FOLLOWS_TYPE (" + FLD_SET_FOLLOWS_TYPE + ")");
+        setFollowsType = true;
+        break;
+      case FLD_END:
+if(DEBUG_RD_MATH)System.err.println("rdRlTy: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown RealType directive " + directive);
+      }
+    }
+
+    RealType rt = RealType.getRealType(name, u, null, attrMask);
+
+    typeCache.add(index, rt);
+
+    if (setFollowsType) {
+      Set set = (Set )BinaryGeneric.read(reader);
+      try {
+        rt.setDefaultSet(set);
+      } catch (TypeException te) {
+        // ignore failure to set type
+      }
+    }
+
+    return rt;
+  }
+
+  public static final int write(BinaryWriter writer, RealType rt, Object token)
+    throws IOException
+  {
+    if (rt instanceof DisplayRealType) {
+      return BinaryDisplayRealType.write(writer, (DisplayRealType )rt, token);
+    } else if (rt instanceof Quantity) {
+      return BinaryQuantity.write(writer, (Quantity )rt, token);
+    }
+
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(rt);
+    if (index < 0) {
+      index = cache.add(rt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache RealType " + rt);
+      }
+
+      if (!rt.getClass().equals(RealType.class) &&
+          !(rt instanceof RealType && rt instanceof Saveable))
+      {
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: serialized RealType (" + rt.getClass().getName() + ")");
+        BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, rt, token);
+        return index;
+      }
+
+      String name = rt.getName();
+
+      Set dfltSet = rt.getDefaultSet();
+
+      int uIndex = -1;
+      Unit u = rt.getDefaultUnit();
+      if (u != null) {
+if(DEBUG_WR_MATH&&!DEBUG_WR_UNIT)System.err.println("wrRlTy: Unit (" + u + ")");
+        uIndex = BinaryUnit.write(writer, u, token);
+      }
+
+      final int objLen = computeBytes(rt);
+
+      DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: OBJ_MATH (" + OBJ_MATH + ")");
+      file.writeByte(OBJ_MATH);
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: objLen (" + objLen + ")");
+      file.writeInt(objLen);
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: index (" + index + ")");
+      file.writeInt(index);
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: MATH_REAL (" + MATH_REAL + ")");
+      file.writeByte(MATH_REAL);
+
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: attrMask (" + rt.getAttributeMask() + ")");
+      file.writeInt(rt.getAttributeMask());
+
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: name (" + name + ")");
+      BinaryString.write(writer, name, token);
+
+      if (uIndex >= 0) {
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: FLD_INDEX_UNIT (" + FLD_INDEX_UNIT + ")");
+        file.writeByte(FLD_INDEX_UNIT);
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: unit index ("+uIndex+"="+u+")");
+        file.writeInt(uIndex);
+      }
+
+      if (dfltSet != null) {
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: FLD_SET_FOLLOWS_TYPE (" + FLD_SET_FOLLOWS_TYPE + ")");
+        file.writeByte(FLD_SET_FOLLOWS_TYPE);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrRlTy: FLD_END (" + FLD_END + ")");
+      file.writeByte(FLD_END);
+
+      if (dfltSet != null) {
+        Object dependToken;
+        if (token == SAVE_DEPEND_BIG) {
+          dependToken = token;
+        } else {
+          dependToken = SAVE_DEPEND;
+        }
+
+        BinaryGeneric.write(writer, dfltSet, dependToken);
+        BinaryGeneric.write(writer, dfltSet, SAVE_DATA);
+      }
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryRealVectorType.java b/visad/data/visad/object/BinaryRealVectorType.java
new file mode 100644
index 0000000..abbafbd
--- /dev/null
+++ b/visad/data/visad/object/BinaryRealVectorType.java
@@ -0,0 +1,54 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.IOException;
+
+import visad.RealVectorType;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryRealVectorType
+  implements BinaryObject
+{
+  public static final int write(BinaryWriter writer, RealVectorType rvt,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(rvt);
+    if (index < 0) {
+      index = cache.add(rvt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache RealVectorType " + rvt);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrRlVeTy: serialized RealVectorType (" + rvt.getClass().getName() + ")");
+      BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, rvt, token);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinarySampledSet.java b/visad/data/visad/object/BinarySampledSet.java
new file mode 100644
index 0000000..dd566e8
--- /dev/null
+++ b/visad/data/visad/object/BinarySampledSet.java
@@ -0,0 +1,91 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.SampledSet;
+import visad.VisADException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinarySampledSet
+  implements BinaryObject
+{
+  public static final int computeBytes(SampledSet[] sets)
+  {
+    if (sets == null) {
+      return 0;
+    }
+
+    int setsLen = 1 + 4;
+    for (int i = 0; i < sets.length; i++) {
+      int len = BinaryGeneric.computeBytes(sets[i]);
+      if (len < 0) {
+        return -1;
+      }
+
+      setsLen += len;
+    }
+
+    return setsLen;
+  }
+
+  public static final SampledSet[] readList(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_DATA)System.err.println("rdSplSetS: len (" + len + ")");
+
+    SampledSet[] sets = new SampledSet[len];
+    for (int i = 0; i < sets.length; i++) {
+      sets[i] = (SampledSet )BinaryGeneric.read(reader);
+    }
+
+    return sets;
+  }
+
+  public static final void writeList(BinaryWriter writer, SampledSet[] sets,
+                                     Object token)
+    throws IOException
+  {
+    if (sets == null) {
+      return;
+    }
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrSampSet: FLD_SET_SAMPLES (" + FLD_SET_SAMPLES + ")");
+    file.writeByte(FLD_SET_SAMPLES);
+if(DEBUG_WR_DATA)System.err.println("wrSampSet: len (" + sets.length + ")");
+    file.writeInt(sets.length);
+    for (int i = 0; i < sets.length; i++) {
+      BinaryGeneric.write(writer, sets[i], token);
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryScalarType.java b/visad/data/visad/object/BinaryScalarType.java
new file mode 100644
index 0000000..6b7c985
--- /dev/null
+++ b/visad/data/visad/object/BinaryScalarType.java
@@ -0,0 +1,73 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.IOException;
+
+import visad.RealType;
+import visad.ScalarType;
+import visad.TextType;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryScalarType
+  implements BinaryObject
+{
+  public static final int computeBytes(ScalarType st)
+  {
+    if (st instanceof RealType) {
+      return BinaryRealType.computeBytes((RealType )st);
+    } else if (st instanceof TextType) {
+      return BinaryTextType.computeBytes((TextType )st);
+    }
+
+    return BinarySerializedObject.computeBytes(st);
+  }
+
+  public static final int write(BinaryWriter writer, ScalarType st,
+                                Object token)
+    throws IOException
+  {
+    if (st instanceof RealType) {
+      return BinaryRealType.write(writer, (RealType )st, token);
+    } else if (st instanceof TextType) {
+      return BinaryTextType.write(writer, (TextType )st, token);
+    }
+
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(st);
+    if (index < 0) {
+      index = cache.add(st);
+      if (index < 0) {
+        throw new IOException("Couldn't cache ScalarType " + st);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrScTy: serialized ScalarType (" + st.getClass().getName() + ")");
+      BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, st, token);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinarySerializedObject.java b/visad/data/visad/object/BinarySerializedObject.java
new file mode 100644
index 0000000..858c8cb
--- /dev/null
+++ b/visad/data/visad/object/BinarySerializedObject.java
@@ -0,0 +1,117 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryWriter;
+
+public class BinarySerializedObject
+  implements BinaryObject
+{
+  public static final int computeBytes(Object obj)
+  {
+    byte[] bytes;
+    try {
+      bytes = getBytes(obj);
+    } catch (IOException ioe) {
+      return 0;
+    }
+
+    return 5 + bytes.length + 1;
+  }
+
+  public static byte[] getBytes(Object obj)
+    throws IOException
+  {
+    java.io.ByteArrayOutputStream outBytes;
+    outBytes = new java.io.ByteArrayOutputStream();
+
+    java.io.ObjectOutputStream outStream;
+    outStream = new java.io.ObjectOutputStream(outBytes);
+
+    outStream.writeObject(obj);
+    outStream.flush();
+    outStream.close();
+
+    return outBytes.toByteArray();
+  }
+
+  public static final Object read(DataInput file)
+    throws IOException
+  {
+    final int len = file.readInt();
+    return read(file, len);
+  }
+
+  public static final Object read(DataInput file, int len)
+    throws IOException
+  {
+    if (len <= 1) {
+      throw new IOException("Corrupted file (bad serialized object length)");
+    }
+
+    byte[] bytes = new byte[len - 1];
+    file.readFully(bytes);
+
+    // make sure we see the FLD_END marker byte
+    final byte endByte = file.readByte();
+    if (endByte != FLD_END) {
+      throw new IOException("Corrupted file (no serialized object end-marker)");
+    }
+
+    java.io.ByteArrayInputStream inBytes;
+    inBytes = new java.io.ByteArrayInputStream(bytes);
+
+    java.io.ObjectInputStream inStream;
+    inStream = new java.io.ObjectInputStream(inBytes);
+
+    Object obj;
+    try {
+      obj = inStream.readObject();
+    } catch (ClassNotFoundException cnfe) {
+      throw new IOException("Couldn't read serialized object: " +
+                            cnfe.getMessage());
+    }
+
+    inStream.close();
+
+    return obj;
+  }
+
+  public static final void write(BinaryWriter writer, byte objType,
+                                 Object obj, Object token)
+    throws IOException
+  {
+    byte[] bytes = getBytes(obj);
+
+    DataOutput file = writer.getOutput();
+
+    file.writeByte(objType);
+    file.writeInt(bytes.length + 1);
+    file.write(bytes);
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinarySetType.java b/visad/data/visad/object/BinarySetType.java
new file mode 100644
index 0000000..836c4b0
--- /dev/null
+++ b/visad/data/visad/object/BinarySetType.java
@@ -0,0 +1,144 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.MathType;
+import visad.RealTupleType;
+import visad.SetType;
+import visad.Set;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinarySetType
+  implements BinaryObject
+{
+  public static final int computeBytes(SetType st) { return 10; }
+
+  public static final SetType read(BinaryReader reader, int index)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int dIndex = file.readInt();
+if(DEBUG_RD_MATH&&DEBUG_RD_MATH)System.err.println("rdSetTy: domain index (" + dIndex + ")");
+    MathType dom = (MathType )cache.get(dIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdSetTy: domain index (" + dIndex + "=" + dom + ")");
+
+    final byte endByte = file.readByte();
+    if (endByte != FLD_END) {
+if(DEBUG_RD_MATH)System.err.println("rdSetTy: read " + endByte + " (wanted FLD_END)");
+      throw new IOException("Corrupted file (no SetType end-marker)");
+    }
+if(DEBUG_RD_MATH)System.err.println("rdSetTy: FLD_END (" + endByte + ")");
+
+    SetType st = new SetType(dom);
+
+    cache.add(index, st);
+
+    return st;
+  }
+
+  public static final int write(BinaryWriter writer, SetType st, Set set,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(st);
+    if (index < 0) {
+      index = cache.add(st);
+      if (index < 0) {
+        throw new IOException("Couldn't cache SetType " + st);
+      }
+
+      if (!st.getClass().equals(SetType.class) &&
+          !(st instanceof SetType && st instanceof Saveable))
+      {
+if(DEBUG_WR_MATH)System.err.println("wrSetTy: serialized SetType (" + st.getClass().getName() + ")");
+        BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, st, token);
+        return index;
+      }
+
+      int dIndex;
+      MathType domain = st.getDomain();
+
+      boolean isRealTupleType = false;
+      if (domain instanceof RealTupleType) {
+        RealTupleType rtt = (RealTupleType )domain;
+
+        if (rtt.getDimension() == 1 &&
+            rtt.getCoordinateSystem() == null &&
+            rtt.getDefaultSet() == null)
+        {
+          // just use the RealType
+          try {
+            domain = rtt.getComponent(0);
+          } catch (VisADException ve) {
+            throw new IOException("Couldn't get SetType domain: " +
+                                  ve.getMessage());
+          }
+        } else {
+          // must really be a multi-dimensional RealTupleType
+          isRealTupleType = true;
+        }
+      }
+
+      if (isRealTupleType) {
+        dIndex = BinaryRealTupleType.write(writer, (RealTupleType )domain,
+                                           token);
+      } else {
+        dIndex = BinaryMathType.write(writer, domain, token);
+      }
+
+      final int objLen = computeBytes(st);
+
+      DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_MATH)System.err.println("wrSetTy: OBJ_MATH (" + OBJ_MATH + ")");
+      file.writeByte(OBJ_MATH);
+if(DEBUG_WR_MATH)System.err.println("wrSetTy: objLen (" + objLen + ")");
+      file.writeInt(objLen);
+if(DEBUG_WR_MATH)System.err.println("wrSetTy: index (" + index + ")");
+      file.writeInt(index);
+if(DEBUG_WR_MATH)System.err.println("wrSetTy: MATH_SET (" + MATH_SET + ")");
+      file.writeByte(MATH_SET);
+
+if(DEBUG_WR_MATH)System.err.println("wrSetTy: domain index (" + dIndex + ")");
+      file.writeInt(dIndex);
+
+if(DEBUG_WR_MATH)System.err.println("wrSetTy: FLD_END (" + FLD_END + ")");
+      file.writeByte(FLD_END);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinarySimpleSet.java b/visad/data/visad/object/BinarySimpleSet.java
new file mode 100644
index 0000000..11eaa16
--- /dev/null
+++ b/visad/data/visad/object/BinarySimpleSet.java
@@ -0,0 +1,228 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.CoordinateSystem;
+import visad.DoubleSet;
+import visad.FloatSet;
+import visad.SetType;
+import visad.SimpleSet;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinarySimpleSet
+  implements BinaryObject
+{
+  public static final int computeBytes(CoordinateSystem cs, Unit[] units)
+  {
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    return 1 + 4 + 1 + 4 +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      1;
+  }
+
+  public static final SimpleSet read(BinaryReader reader, byte dataType)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache typeCache = reader.getTypeCache();
+    BinaryObjectCache cSysCache = reader.getCoordinateSystemCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdSimSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )typeCache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdSimSet: type index (" + typeIndex + "=" + st + ")");
+
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdSimSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdSimSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cSysCache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdSimSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdSimSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+if(DEBUG_RD_DATA&&!DEBUG_RD_UNIT){System.err.println("rdSimSet: array length ("+units.length+")");for(int i=0;i<units.length;i++)System.err.println("rdSimSet:    #"+i+" unit ("+units[i]+")");}
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdSimSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown SimpleSet directive " +
+                              directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for SimpleSet");
+    }
+
+    switch (dataType) {
+    case DATA_FLOAT_SET:
+      return new FloatSet(st, cs, units);
+    case DATA_DOUBLE_SET:
+      return new DoubleSet(st, cs, units);
+    default:
+      throw new IOException("Unknown SimpleSet type " + dataType);
+    }
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type,
+                                              CoordinateSystem cs,
+                                              Unit[] units, SimpleSet set,
+                                              Class canonicalClass,
+                                              Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof SimpleSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrSimSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrSimSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrSimSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrSimSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 CoordinateSystem cs, Unit[] units,
+                                 SimpleSet set, Class canonicalClass,
+                                 byte dataType, Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, cs, units, set, canonicalClass, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(canonicalClass) &&
+        !(set instanceof SimpleSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getCoordinateSystemCache(),
+                                         units);
+    }
+
+    final int objLen = computeBytes(cs, units);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: " +
+                                    (dataType == DATA_DOUBLE_SET ?
+                                     "DATA_DOUBLE_SET" :
+                                     (dataType == DATA_FLOAT_SET ?
+                                      "DATA_FLOAT_SET" : "DATA_???")) +
+                                    "(" + dataType + ")");
+    file.writeByte(dataType);
+
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: array length ("+unitsIndex.length+")");
+if(DEBUG_WR_DATA)for(int i=0;i<unitsIndex.length;i++)System.err.println("wrSimSet:    unit #"+i+" index ("+unitsIndex[i]+"="+units[i]+")");
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrSimSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinarySingletonSet.java b/visad/data/visad/object/BinarySingletonSet.java
new file mode 100644
index 0000000..28fe7d2
--- /dev/null
+++ b/visad/data/visad/object/BinarySingletonSet.java
@@ -0,0 +1,273 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.EOFException;
+
+import visad.CoordinateSystem;
+import visad.Data;
+import visad.ErrorEstimate;
+import visad.Real;
+import visad.RealTuple;
+import visad.RealTupleType;
+import visad.SingletonSet;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinarySingletonSet
+  implements BinaryObject
+{
+  public static final int computeBytes(RealTupleType sampleType,
+                                       Real[] sampleReals,
+                                       CoordinateSystem cs, Unit[] units,
+                                       ErrorEstimate[] errors)
+  {
+    final boolean trivialTuple = BinaryRealTuple.isTrivialTuple(sampleType,
+                                                                sampleReals);
+
+    final int unitsLen = BinaryUnit.computeBytes(units);
+    final int errorsLen = BinaryErrorEstimate.computeBytes(errors);
+    return 1 + 4 + 1 +
+      1 + BinaryRealTuple.computeBytes(sampleReals, cs, trivialTuple) +
+      (cs == null ? 0 : 5) +
+      (unitsLen == 0 ? 0 : unitsLen + 1) +
+      (errorsLen == 0 ? 0 : errorsLen + 1) +
+      1;
+  }
+
+  public static final Real[] getSampleReals(RealTuple sample)
+  {
+    Data[] comps = sample.getComponents();
+    if (comps == null) {
+      return null;
+    }
+
+    Real[] sampleReals = new Real[comps.length];
+    for (int i = 0; i < comps.length; i++) {
+      sampleReals[i] = (Real )comps[i];
+    }
+
+    return sampleReals;
+  }
+
+  public static SingletonSet read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getCoordinateSystemCache();
+    DataInput file = reader.getInput();
+
+    RealTuple sample = null;
+    CoordinateSystem cs = null;
+    Unit[] units = null;
+    ErrorEstimate[] errs = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_SAMPLE:
+        sample = (RealTuple )BinaryGeneric.read(reader);
+        break;
+      case FLD_INDEX_COORDSYS:
+if(DEBUG_RD_DATA)System.err.println("rdSglSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+        final int index = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_CSYS)System.err.println("rdSglSet: cSys index (" + index + ")");
+        cs = (CoordinateSystem )cache.get(index);
+if(DEBUG_RD_DATA&&!DEBUG_RD_CSYS)System.err.println("rdSglSet: cSys index (" + index + "=" + cs + ")");
+        break;
+      case FLD_INDEX_UNITS:
+if(DEBUG_RD_DATA)System.err.println("rdSglSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+        units = BinaryUnit.readList(reader);
+        break;
+      case FLD_INDEX_ERRORS:
+if(DEBUG_RD_DATA)System.err.println("rdSglSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+        errs = BinaryErrorEstimate.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdSglSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown SingletonSet directive " +
+                              directive);
+      }
+    }
+
+    if (sample == null) {
+      throw new IOException("No sample found for SingletonSet");
+    }
+
+    return new SingletonSet(sample, cs, units, errs);
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              RealTuple sample,
+                                              CoordinateSystem cs,
+                                              Unit[] units,
+                                              ErrorEstimate[] errors,
+                                              SingletonSet set,
+                                              Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(SingletonSet.class) &&
+        !(set instanceof SingletonSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+    if (cs != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_CSYS)System.err.println("wrSglSet: coordSys (" + cs + ")");
+      BinaryCoordinateSystem.write(writer, cs, SAVE_DATA);
+    }
+
+    if (units != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_UNIT){
+  System.err.println("wrSglSet: List of " + units.length + " Units");
+  for(int x=0;x<units.length;x++){
+    System.err.println("wrSglSet:    #"+x+": "+units[x]);
+  }
+}
+      BinaryUnit.writeList(writer, units, SAVE_DATA);
+    }
+
+    if (errors != null) {
+if(DEBUG_WR_DATA&&!DEBUG_WR_ERRE){
+  System.err.println("wrSglSet: List of " + errors.length + " ErrorEstimates");
+  for(int x=0;x<errors.length;x++){
+    System.err.println("wrSglSet:    #"+x+": "+errors[x]);
+  }
+}
+      BinaryErrorEstimate.writeList(writer, errors, SAVE_DATA);
+    }
+
+    BinaryGeneric.write(writer, sample, dependToken);
+  }
+
+  public static final void write(BinaryWriter writer, RealTuple sample,
+                                 CoordinateSystem cs, Unit[] units,
+                                 ErrorEstimate[] errors, SingletonSet set,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, sample, cs, units, errors, set, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(SingletonSet.class) &&
+        !(set instanceof SingletonSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    if (sample == null) {
+      throw new IOException("Null SingletonSet sample");
+    }
+
+    int csIndex = -1;
+    if (cs != null) {
+      csIndex = writer.getCoordinateSystemCache().getIndex(cs);
+      if (csIndex < 0) {
+        throw new IOException("CoordinateSystem " + cs + " not cached");
+      }
+    }
+
+    int[] unitsIndex = null;
+    if (units != null) {
+      unitsIndex = BinaryUnit.lookupList(writer.getUnitCache(), units);
+    }
+
+    int[] errorsIndex = null;
+    if (errors != null) {
+      errorsIndex = BinaryErrorEstimate.lookupList(writer.getErrorEstimateCache(),
+                                                   errors);
+    }
+
+    RealTupleType sampleType = (RealTupleType )sample.getType();
+    Real[] sampleReals = getSampleReals(sample);
+
+    final int objLen = computeBytes(sampleType, sampleReals, cs, units,
+                                    errors);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: DATA_SINGLETON_SET (" + DATA_SINGLETON_SET + ")");
+    file.writeByte(DATA_SINGLETON_SET);
+
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: FLD_SAMPLE (" + FLD_SAMPLE + ")");
+    file.writeByte(FLD_SAMPLE);
+    BinaryRealTuple.write(writer, sampleType, sampleReals,
+                          sample.getCoordinateSystem(), sample, token);
+
+    if (csIndex >= 0) {
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: FLD_INDEX_COORDSYS (" + FLD_INDEX_COORDSYS + ")");
+      file.writeByte(FLD_INDEX_COORDSYS);
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: coord sys Index (" + csIndex + ")");
+      file.writeInt(csIndex);
+    }
+
+    if (unitsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: FLD_INDEX_UNITS (" + FLD_INDEX_UNITS + ")");
+      file.writeByte(FLD_INDEX_UNITS);
+      BinaryIntegerArray.write(writer, unitsIndex, token);
+    }
+
+    if (errorsIndex != null) {
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: FLD_INDEX_ERRORS (" + FLD_INDEX_ERRORS + ")");
+      file.writeByte(FLD_INDEX_ERRORS);
+      BinaryIntegerArray.write(writer, errorsIndex, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrSglSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinarySize.java b/visad/data/visad/object/BinarySize.java
new file mode 100644
index 0000000..230048b
--- /dev/null
+++ b/visad/data/visad/object/BinarySize.java
@@ -0,0 +1,47 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+public class BinarySize
+{
+  private int size;
+
+  public BinarySize() { reset(); }
+
+  public final void add(int size)
+  {
+    if (this.size != -1) {
+      if (size == -1) {
+        this.size = -1;
+      } else {
+        this.size += size;
+      }
+    }
+  }
+
+  public final int get() { return size; }
+
+  public final void reset() { size = 0; }
+
+  public final void set(int newSize) { size = newSize; }
+}
diff --git a/visad/data/visad/object/BinaryString.java b/visad/data/visad/object/BinaryString.java
new file mode 100644
index 0000000..f6ac179
--- /dev/null
+++ b/visad/data/visad/object/BinaryString.java
@@ -0,0 +1,79 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryString
+  implements BinaryObject
+{
+  public static final int computeBytes(String str)
+  {
+    return 4 + (str == null ? 0 : str.getBytes().length);
+  }
+
+  public static final String read(BinaryReader reader)
+    throws IOException
+  {
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_STR)System.err.println("rdStr: len (" + len + ")");
+    if (len < 0) {
+      return null;
+    } else if (len == 0) {
+      return "";
+    }
+
+    byte[] buf = new byte[len];
+    file.readFully(buf);
+if(DEBUG_RD_STR)System.err.println("rdStr: str (" + new String(buf) + ")");
+
+    return new String(buf);
+  }
+
+  public static final void write(BinaryWriter writer, String str,
+                                 Object token)
+    throws IOException
+  {
+    DataOutput file = writer.getOutput();
+
+    if (str == null) {
+      file.writeInt(-1);
+    } else {
+      byte[] bytes = str.getBytes();
+
+if(DEBUG_WR_DATA)System.err.println("wrStr: num bytes (" + bytes.length + ")");
+      file.writeInt(bytes.length);
+      if (bytes.length > 0) {
+if(DEBUG_WR_DATA)System.err.println("wrStr: str (" + str + ")");
+        file.write(bytes);
+      }
+    }
+  }
+}
diff --git a/visad/data/visad/object/BinaryText.java b/visad/data/visad/object/BinaryText.java
new file mode 100644
index 0000000..09abc5e
--- /dev/null
+++ b/visad/data/visad/object/BinaryText.java
@@ -0,0 +1,116 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.Text;
+import visad.TextType;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryText
+  implements BinaryObject
+{
+  public static final int computeBytes(String value)
+  {
+    return 1 + 4 + 1 + 4 +
+      BinaryString.computeBytes(value) +
+      1;
+  }
+
+  public static final Text read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdTxt: type index (" + typeIndex + ")");
+    TextType tt = (TextType )cache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdTxt: type index (" + typeIndex + "=" + tt + ")");
+
+    // read the value
+    String value = BinaryString.read(reader);
+if(DEBUG_RD_DATA&&!DEBUG_RD_STR)System.err.println("rdTxt: value (" + value + ")");
+
+    final byte endByte = file.readByte();
+    if (endByte != FLD_END) {
+if(DEBUG_RD_MATH)System.err.println("rdTxt: read " + endByte + " (wanted FLD_END)");
+      throw new IOException("Corrupted file (no Text end-marker)");
+    }
+if(DEBUG_RD_MATH)System.err.println("rdTxt: FLD_END (" + endByte + ")");
+
+    return new Text(tt, value);
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              TextType type, Object token)
+    throws IOException
+  {
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrTxt: MathType (" + type + ")");
+    BinaryTextType.write(writer, type, SAVE_DATA);
+  }
+
+  public static final void write(BinaryWriter writer, TextType type,
+                                 String value, boolean missing, Text text,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("TextType " + type + " not cached");
+    }
+
+    final int objLen = computeBytes(value);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrTxt: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrTxt: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrTxt: DATA_TEXT (" + DATA_TEXT + ")");
+    file.writeByte(DATA_TEXT);
+
+if(DEBUG_WR_DATA)System.err.println("wrTxt: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+if(DEBUG_WR_DATA)System.err.println("wrTxt: value (" + value + ")");
+    BinaryString.write(writer, value, token);
+
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryTextType.java b/visad/data/visad/object/BinaryTextType.java
new file mode 100644
index 0000000..da00149
--- /dev/null
+++ b/visad/data/visad/object/BinaryTextType.java
@@ -0,0 +1,117 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.TextType;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryTextType
+  implements BinaryObject
+{
+  public static final int computeBytes(TextType tt)
+  {
+    return 5 +
+      BinaryString.computeBytes(tt.getName()) +
+      1;
+  }
+
+  public static final TextType read(BinaryReader reader, int index)
+    throws IOException, VisADException
+  {
+    // read the name
+    String name = BinaryString.read(reader);
+if(DEBUG_RD_MATH&&!DEBUG_RD_STR)System.err.println("rdTxTy: name (" + name + ")");
+
+    DataInput file = reader.getInput();
+
+    final byte endByte = file.readByte();
+    if (endByte != FLD_END) {
+if(DEBUG_RD_MATH)System.err.println("rdTxTy: read " + endByte + " (wanted FLD_END)");
+      throw new IOException("Corrupted file (no TextType end-marker)");
+    }
+if(DEBUG_RD_MATH)System.err.println("rdTxTy: FLD_END (" + endByte + ")");
+
+    TextType tt = TextType.getTextType(name);
+
+    BinaryObjectCache cache = reader.getTypeCache();
+
+    cache.add(index, tt);
+
+    return tt;
+  }
+
+  public static final int write(BinaryWriter writer, TextType tt,
+                                Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(tt);
+    if (index < 0) {
+      index = cache.add(tt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache TextType " + tt);
+      }
+
+      if (!tt.getClass().equals(TextType.class) &&
+          !(tt instanceof TextType && tt instanceof Saveable))
+      {
+if(DEBUG_WR_MATH)System.err.println("wrTxTy: serialized TextType (" + tt.getClass().getName() + ")");
+        BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, tt, token);
+        return index;
+      }
+
+      String name = tt.getName();
+
+      final int objLen = computeBytes(tt);
+
+      DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_MATH)System.err.println("wrTxTy: OBJ_MATH (" + OBJ_MATH + ")");
+      file.writeByte(OBJ_MATH);
+if(DEBUG_WR_MATH)System.err.println("wrTxTy: objLen (" + objLen + ")");
+      file.writeInt(objLen);
+if(DEBUG_WR_MATH)System.err.println("wrTxTy: index (" + index + ")");
+      file.writeInt(index);
+if(DEBUG_WR_MATH)System.err.println("wrTxTy: MATH_TEXT (" + MATH_TEXT + ")");
+      file.writeByte(MATH_TEXT);
+
+if(DEBUG_WR_MATH)System.err.println("wrTxTy: name (" + name + ")");
+      BinaryString.write(writer, name, token);
+
+if(DEBUG_WR_MATH)System.err.println("wrTxTy: FLD_END (" + FLD_END + ")");
+      file.writeByte(FLD_END);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryTuple.java b/visad/data/visad/object/BinaryTuple.java
new file mode 100644
index 0000000..48a5c4f
--- /dev/null
+++ b/visad/data/visad/object/BinaryTuple.java
@@ -0,0 +1,171 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.EOFException;
+
+import visad.Data;
+import visad.Tuple;
+import visad.TupleType;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryTuple
+  implements BinaryObject
+{
+  public static final int computeBytes(Data[] components)
+  {
+    final int compsLen;
+    if (components == null) {
+      compsLen = 0;
+    } else {
+      compsLen = 1 + BinaryDataArray.computeBytes(components);
+    }
+
+    return 1 + 4 + 1 + 4 +
+      compsLen +
+      1;
+  }
+
+  public static final Tuple read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdTpl: type index (" + typeIndex + ")");
+    TupleType tt = (TupleType )cache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdTpl: type index (" + typeIndex + "=" + tt + ")");
+
+    Data[] components = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_DATA_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdTpl: FLD_DATA_SAMPLES (" + FLD_DATA_SAMPLES + ")");
+        components = BinaryDataArray.read(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdTpl: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown Tuple directive " + directive);
+      }
+    }
+
+    return new Tuple(tt, components);
+  }
+
+  private static final void writeDependentData(BinaryWriter writer,
+                                               TupleType type,
+                                               Data[] components, Tuple t,
+                                               Object token)
+    throws IOException
+  {
+    if (!t.getClass().equals(Tuple.class) &&
+        !(t instanceof Tuple && t instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrTpl: type (" + type + ")");
+    BinaryTupleType.write(writer, type, SAVE_DATA);
+
+
+    if (components != null) {
+      BinaryDataArray.write(writer, components, dependToken);
+    }
+  }
+
+  public static final void write(BinaryWriter writer, TupleType type,
+                                 Data[] components, Tuple t, Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, components, t, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!t.getClass().equals(Tuple.class) &&
+        !(t instanceof Tuple && t instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrTup: punt "+t.getClass().getName());
+      BinaryUnknown.write(writer, t, token);
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("TupleType " + type + " not cached");
+    }
+
+    final int objLen = computeBytes(components);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrTup: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrTup: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrTup: DATA_TUPLE (" + DATA_TUPLE + ")");
+    file.writeByte(DATA_TUPLE);
+
+if(DEBUG_WR_DATA)System.err.println("wrTup: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    if (components != null) {
+if(DEBUG_WR_DATA)System.err.println("wrTup: FLD_DATA_SAMPLES (" + FLD_DATA_SAMPLES + ")");
+      file.writeByte(FLD_DATA_SAMPLES);
+      BinaryDataArray.write(writer, components, token);
+    }
+
+if(DEBUG_WR_DATA)System.err.println("wrTup: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryTupleType.java b/visad/data/visad/object/BinaryTupleType.java
new file mode 100644
index 0000000..165866a
--- /dev/null
+++ b/visad/data/visad/object/BinaryTupleType.java
@@ -0,0 +1,136 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.MathType;
+import visad.RealTupleType;
+import visad.TupleType;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryTupleType
+  implements BinaryObject
+{
+  public static final int computeBytes(TupleType tt)
+  {
+    return 5 +
+      (tt.getDimension() * 4) +
+      1;
+  }
+
+  public static final TupleType read(BinaryReader reader, int index,
+                                     int objLen)
+    throws IOException, VisADException
+  {
+    MathType[] list = BinaryMathType.readList(reader, (objLen - 1) / 4);
+
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final byte endByte = file.readByte();
+    if (endByte != FLD_END) {
+if(DEBUG_RD_MATH)System.err.println("rdTuTy: read " + endByte + " (wanted FLD_END)");
+      throw new IOException("Corrupted file (no TupleType end-marker)");
+    }
+if(DEBUG_RD_MATH)System.err.println("rdTuTy: FLD_END (" + endByte + ")");
+
+    TupleType tt = new TupleType(list);
+
+    cache.add(index, tt);
+
+    return tt;
+  }
+
+  public static final int write(BinaryWriter writer, TupleType tt,
+                                Object token)
+    throws IOException
+  {
+    if (tt instanceof RealTupleType) {
+      return BinaryRealTupleType.write(writer, (RealTupleType )tt, token);
+    }
+
+    final int dim = tt.getDimension();
+
+    int[] types = new int[dim];
+    for (int i = 0; i < dim; i++) {
+      MathType comp;
+      try {
+        comp = tt.getComponent(i);
+      } catch (VisADException ve) {
+        throw new IOException("Couldn't get TupleType component #" + i +
+                              ": " + ve.getMessage());
+      }
+
+      types[i] = BinaryMathType.write(writer, comp, token);
+    }
+
+    BinaryObjectCache cache = writer.getTypeCache();
+
+    int index = cache.getIndex(tt);
+    if (index < 0) {
+      index = cache.add(tt);
+      if (index < 0) {
+        throw new IOException("Couldn't cache TupleType " + tt);
+      }
+
+      if (!tt.getClass().equals(TupleType.class) &&
+          !(tt instanceof TupleType && tt instanceof Saveable))
+      {
+if(DEBUG_WR_MATH)System.err.println("wrTuTy: serialized TupleType (" + tt.getClass().getName() + ")");
+        BinarySerializedObject.write(writer, OBJ_MATH_SERIAL, tt, token);
+        return index;
+      }
+
+      final int objLen = computeBytes(tt);
+
+      DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_MATH)System.err.println("wrTuTy: OBJ_MATH (" + OBJ_MATH + ")");
+      file.writeByte(OBJ_MATH);
+if(DEBUG_WR_MATH)System.err.println("wrTuTy: objLen (" + objLen + ")");
+      file.writeInt(objLen);
+if(DEBUG_WR_MATH)System.err.println("wrTuTy: index (" + index + ")");
+      file.writeInt(index);
+if(DEBUG_WR_MATH)System.err.println("wrTuTy: MATH_TUPLE (" + MATH_TUPLE + ")");
+      file.writeByte(MATH_TUPLE);
+
+      for (int i = 0; i < dim; i++) {
+if(DEBUG_WR_MATH)System.err.println("wrTuTy: type index #" + i + " (" + types[i] + ")");
+        file.writeInt(types[i]);
+      }
+
+if(DEBUG_WR_MATH)System.err.println("wrTuTy: FLD_END (" + FLD_END + ")");
+      file.writeByte(FLD_END);
+    }
+
+    return index;
+  }
+}
diff --git a/visad/data/visad/object/BinaryUnionSet.java b/visad/data/visad/object/BinaryUnionSet.java
new file mode 100644
index 0000000..290f50e
--- /dev/null
+++ b/visad/data/visad/object/BinaryUnionSet.java
@@ -0,0 +1,179 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+import visad.SampledSet;
+import visad.SetType;
+import visad.UnionSet;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+import visad.data.visad.Saveable;
+
+public class BinaryUnionSet
+  implements BinaryObject
+{
+  public static final int computeBytes(SampledSet[] sets)
+  {
+    int setsLen = 1 + 4;
+    for (int i = 0; i < sets.length; i++) {
+      int len = BinaryGeneric.computeBytes(sets[i]);
+      if (len < 0) {
+        return -1;
+      }
+
+      setsLen += len;
+    }
+
+    return 1 + 4 + 1 + 4 +
+      BinarySampledSet.computeBytes(sets) +
+      1;
+  }
+
+  public static final UnionSet read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getTypeCache();
+    DataInput file = reader.getInput();
+
+    final int typeIndex = file.readInt();
+if(DEBUG_RD_DATA&&DEBUG_RD_MATH)System.err.println("rdUSet: type index (" + typeIndex + ")");
+    SetType st = (SetType )cache.get(typeIndex);
+if(DEBUG_RD_DATA&&!DEBUG_RD_MATH)System.err.println("rdUSet: type index (" + typeIndex + "=" + st + ")");
+
+    SampledSet[] sets = null;
+
+    boolean reading = true;
+    while (reading) {
+      final byte directive;
+      try {
+        directive = file.readByte();
+      } catch (EOFException eofe) {
+        return null;
+      }
+
+      switch (directive) {
+      case FLD_SET_SAMPLES:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_SET_SAMPLES (" + FLD_SET_SAMPLES + ")");
+        sets = BinarySampledSet.readList(reader);
+        break;
+      case FLD_END:
+if(DEBUG_RD_DATA)System.err.println("rdLinSet: FLD_END (" + FLD_END + ")");
+        reading = false;
+        break;
+      default:
+        throw new IOException("Unknown UnionSet directive " +
+                              directive);
+      }
+    }
+
+    if (st == null) {
+      throw new IOException("No SetType found for UnionSet");
+    }
+    if (sets == null) {
+      throw new IOException("No sets found for UnionSet");
+    }
+
+    return new UnionSet(st, sets);
+  }
+
+  public static final void writeDependentData(BinaryWriter writer,
+                                              SetType type, SampledSet[] sets,
+                                              UnionSet set, Object token)
+    throws IOException
+  {
+    if (!set.getClass().equals(UnionSet.class) &&
+        !(set instanceof UnionSet && set instanceof Saveable))
+    {
+      return;
+    }
+
+    Object dependToken;
+    if (token == SAVE_DEPEND_BIG) {
+      dependToken = token;
+    } else {
+      dependToken = SAVE_DEPEND;
+    }
+
+if(DEBUG_WR_DATA&&!DEBUG_WR_MATH)System.err.println("wrUSet: type (" + type + ")");
+    BinarySetType.write(writer, type, set, SAVE_DATA);
+
+    if (sets != null) {
+      for (int i = 0; i < sets.length; i++) {
+        BinaryGeneric.write(writer, sets[i], dependToken);
+      }
+    }
+  }
+
+  public static final void write(BinaryWriter writer, SetType type,
+                                 SampledSet[] sets, UnionSet set,
+                                 Object token)
+    throws IOException
+  {
+    writeDependentData(writer, type, sets, set, token);
+
+    // if we only want to write dependent data, we're done
+    if (token == SAVE_DEPEND || token == SAVE_DEPEND_BIG) {
+      return;
+    }
+
+    if (!set.getClass().equals(UnionSet.class) &&
+        !(set instanceof UnionSet && set instanceof Saveable))
+    {
+if(DEBUG_WR_DATA)System.err.println("wrUSet: punt "+set.getClass().getName());
+      BinaryUnknown.write(writer, set, token);
+      return;
+    }
+
+    int typeIndex = writer.getTypeCache().getIndex(type);
+    if (typeIndex < 0) {
+      throw new IOException("SetType " + type + " not cached");
+    }
+
+    final int objLen = computeBytes(sets);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_DATA)System.err.println("wrUSet: OBJ_DATA (" + OBJ_DATA + ")");
+    file.writeByte(OBJ_DATA);
+if(DEBUG_WR_DATA)System.err.println("wrUSet: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_DATA)System.err.println("wrUSet: DATA_UNION_SET (" + DATA_UNION_SET + ")");
+    file.writeByte(DATA_UNION_SET);
+
+if(DEBUG_WR_DATA)System.err.println("wrUSet: type index (" + typeIndex + ")");
+    file.writeInt(typeIndex);
+
+    BinarySampledSet.writeList(writer, sets, token);
+
+if(DEBUG_WR_DATA)System.err.println("wrUSet: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+  }
+}
diff --git a/visad/data/visad/object/BinaryUnit.java b/visad/data/visad/object/BinaryUnit.java
new file mode 100644
index 0000000..6faf9df
--- /dev/null
+++ b/visad/data/visad/object/BinaryUnit.java
@@ -0,0 +1,234 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+import visad.BaseUnit;
+import visad.CommonUnit;
+import visad.Unit;
+import visad.VisADException;
+
+import visad.data.visad.BinaryObjectCache;
+import visad.data.visad.BinaryReader;
+import visad.data.visad.BinaryWriter;
+
+public class BinaryUnit
+  implements BinaryObject
+{
+  public static final int computeBytes(Unit u)
+  {
+    return 4 +
+      BinaryString.computeBytes(u.getIdentifier()) +
+      BinaryString.computeBytes(u.getDefinition().trim()) +
+      1;
+  }
+
+  public static final int computeBytes(Unit[] array)
+  {
+    return BinaryIntegerArray.computeBytes(array);
+  }
+
+  public static final int[] lookupList(BinaryObjectCache cache, Unit[] units)
+  {
+    // make sure there's something to write
+    boolean empty = true;
+    for (int i = 0; i < units.length; i++) {
+      if (units[i] != null) {
+        empty = false;
+        break;
+      }
+    }
+    if (empty) {
+      return null;
+    }
+
+    int[] indices = new int[units.length];
+
+    for (int i = 0; i < units.length; i++) {
+      if (units[i] == null) {
+        indices[i] = -1;
+      } else {
+        indices[i] = cache.getIndex(units[i]);
+      }
+    }
+
+    return indices;
+  }
+
+  public static final Unit read(BinaryReader reader)
+    throws IOException, VisADException
+  {
+    BinaryObjectCache cache = reader.getUnitCache();
+    DataInput file = reader.getInput();
+
+    final int objLen = file.readInt();
+if(DEBUG_RD_UNIT)System.err.println("cchU: objLen (" + objLen + ")");
+
+    // read the index number for this Unit
+    final int index = file.readInt();
+if(DEBUG_RD_UNIT)System.err.println("cchU: index (" + index + ")");
+
+    // read the Unit identifier
+    String idStr = BinaryString.read(reader);
+if(DEBUG_RD_UNIT&&!DEBUG_RD_STR)System.err.println("cchU: identifier (" + idStr + ")");
+
+    // read the Unit description
+    String defStr = BinaryString.read(reader);
+if(DEBUG_RD_UNIT&&!DEBUG_RD_STR)System.err.println("cchU: definition (" + defStr + ")");
+
+    final byte endByte = file.readByte();
+    if (endByte != FLD_END) {
+if(DEBUG_RD_MATH)System.err.println("cchU: read " + endByte + " (wanted FLD_END)");
+      throw new IOException("Corrupted file (no Unit end-marker)");
+    }
+if(DEBUG_RD_MATH)System.err.println("cchU: FLD_END (" + endByte + ")");
+
+    Unit u;
+    if (defStr.equals("promiscuous") || defStr.equals("UniversalUnit")) {
+      u = CommonUnit.promiscuous;
+    } else {
+      try {
+        u = visad.data.units.Parser.parse(defStr);
+      } catch (Exception e) {
+        throw new VisADException("Couldn't parse Unit specification \"" +
+                                 defStr + "\"");
+      }
+
+      if (!(u instanceof BaseUnit)) {
+        try {
+          Unit namedUnit = u.clone(idStr);
+          u = namedUnit;
+        } catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    }
+
+    cache.add(index, u);
+
+    return u;
+  }
+
+  public static final Unit[] readList(BinaryReader reader)
+    throws IOException
+  {
+    BinaryObjectCache cache = reader.getUnitCache();
+    DataInput file = reader.getInput();
+
+    final int len = file.readInt();
+if(DEBUG_RD_UNIT)System.err.println("rdUnits: len (" + len + ")");
+    if (len < 1) {
+      throw new IOException("Corrupted file" +
+                            " (bad Unit array length " + len + ")");
+    }
+
+    Unit[] units = new Unit[len];
+    for (int i = 0; i < len; i++) {
+      final int index = file.readInt();
+if(DEBUG_RD_UNIT)System.err.println("rdUnits:    #"+i+" index ("+index+")");
+
+      if (index < 0) {
+        units[i] = null;
+      } else {
+        units[i] = (Unit )cache.get(index);
+      }
+if(DEBUG_RD_UNIT)System.err.println("rdUnits:    === #"+i+" Unit ("+units[i]+")");
+    }
+
+    return units;
+  }
+
+  public static final int write(BinaryWriter writer, Unit u, Object token)
+    throws IOException
+  {
+    BinaryObjectCache cache = writer.getUnitCache();
+
+    int index = cache.getIndex(u);
+    if (index >= 0) {
+      return index;
+    }
+
+    String uDef = u.getDefinition().trim();
+
+    String uId = u.getIdentifier();
+
+    // cache the Unit so we can find its index number
+    index = cache.add(u);
+    if (index < 0) {
+      throw new IOException("Couldn't cache Unit " + u);
+    }
+
+    final int objLen = computeBytes(u);
+
+    DataOutput file = writer.getOutput();
+
+if(DEBUG_WR_UNIT)System.err.println("wrU: OBJ_UNIT (" + OBJ_UNIT + ")");
+    file.writeByte(OBJ_UNIT);
+if(DEBUG_WR_UNIT)System.err.println("wrU: objLen (" + objLen + ")");
+    file.writeInt(objLen);
+if(DEBUG_WR_UNIT)System.err.println("wrU: index (" + index + ")");
+    file.writeInt(index);
+
+if(DEBUG_WR_UNIT)System.err.println("wrU: identifier (" + uId + ")");
+    BinaryString.write(writer, uId, token);
+if(DEBUG_WR_UNIT)System.err.println("wrU: definition (" + uDef + ")");
+    BinaryString.write(writer, uDef, token);
+
+if(DEBUG_WR_UNIT)System.err.println("wrU: FLD_END (" + FLD_END + ")");
+    file.writeByte(FLD_END);
+
+    return index;
+  }
+
+  public static final int[] writeList(BinaryWriter writer, Unit[] units,
+                                      Object token)
+    throws IOException
+  {
+    // make sure there's something to write
+    boolean empty = true;
+    for (int i = 0; i < units.length; i++) {
+      if (units[i] != null) {
+        empty = false;
+        break;
+      }
+    }
+    if (empty) {
+      return null;
+    }
+
+    int[] indices = new int[units.length];
+
+    for (int i = 0; i < units.length; i++) {
+      if (units[i] == null) {
+        indices[i] = -1;
+      } else {
+        indices[i] = write(writer, units[i], token);
+      }
+    }
+
+    return indices;
+  }
+}
diff --git a/visad/data/visad/object/BinaryUnknown.java b/visad/data/visad/object/BinaryUnknown.java
new file mode 100644
index 0000000..e510af0
--- /dev/null
+++ b/visad/data/visad/object/BinaryUnknown.java
@@ -0,0 +1,45 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.data.visad.object;
+
+import java.io.IOException;
+
+import visad.DataImpl;
+
+import visad.data.visad.BinaryWriter;
+
+public class BinaryUnknown
+  implements BinaryObject
+{
+  public static final int computeBytes(DataImpl data)
+  {
+    return BinarySerializedObject.computeBytes(data);
+  }
+
+  public static final void write(BinaryWriter writer, DataImpl data,
+                                 Object token)
+    throws IOException
+  {
+    BinarySerializedObject.write(writer, OBJ_DATA_SERIAL, data, token);
+  }
+}
diff --git a/visad/data/visad/package.html b/visad/data/visad/package.html
new file mode 100644
index 0000000..9305701
--- /dev/null
+++ b/visad/data/visad/package.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides for importing and exporting serialized Java object files
+into and out of VisAD.  These files are effective for short-term,
+cross-platform storage of data, but should not be used for
+long-term data storage.
+
+</body>
+</html>
+
diff --git a/visad/data/visad/util/build_binary_file.pl b/visad/data/visad/util/build_binary_file.pl
new file mode 100644
index 0000000..3c67614
--- /dev/null
+++ b/visad/data/visad/util/build_binary_file.pl
@@ -0,0 +1,115 @@
+#!/usr/local/bin/perl -w
+#
+# Build a BinaryFile.java file using data in the
+# 'datas', 'flds', 'maths', 'objs', and 'debugs' files, which
+# correspond to the DATA_*, FLD_*, MATH_*, OBJ_*, and DEBUG_*
+# constants.
+
+use strict;
+
+sub print_header {
+  print "/*\n";
+  print "VisAD system for interactive analysis and visualization of numerical\n";
+  print "data.  Copyright (C) 1996 - 2007 Bill Hibbard, Curtis Rueden, Tom\n";
+  print "Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and\n";
+  print "Tommy Jasmin.\n";
+  print "\n";
+  print "This library is free software; you can redistribute it and/or\n";
+  print "modify it under the terms of the GNU Library General Public\n";
+  print "License as published by the Free Software Foundation; either\n";
+  print "version 2 of the License, or (at your option) any later version.\n";
+  print "\n";
+  print "This library is distributed in the hope that it will be useful,\n";
+  print "but WITHOUT ANY WARRANTY; without even the implied warranty of\n";
+  print "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n";
+  print "Library General Public License for more details.\n";
+  print "\n";
+  print "You should have received a copy of the GNU Library General Public\n";
+  print "License along with this library; if not, write to the Free\n";
+  print "Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,\n";
+  print "MA 02111-1307, USA\n";
+  print "*/\n";
+  print "\n";
+  print "package visad.data.visad;\n";
+  print "\n";
+  print "/**\n";
+  print " * Constant values used by both\n";
+  print ' * {@link visad.data.visad.BinaryReader BinaryReader}',"\n";
+  print " * and\n";
+  print ' * {@link visad.data.visad.BinaryWriter BinaryWriter}<br>',"\n";
+  print " * <br>\n";
+  print " * <tt>MAGIC_STR</tt> and <tt>FORMAT_VERSION</tt> are used\n";
+  print " * to mark the file as a VisAD binary file.<br>\n";
+  print " * <tt>OBJ_</tt> constants indicate the type of the next\n";
+  print " * object in the file.<br>\n";
+  print " * <tt>FLD_</tt> constants indicate the type of the next\n";
+  print " * field for the current object in the file.<br>\n";
+  print " * <tt>MATH_</tt> constants indicate the type of <tt>FLD_MATH</tt>\n";
+  print " * objects.<br>\n";
+  print " * <tt>DATA_</tt> constants indicate the type of <tt>FLD_DATA</tt>\n";
+  print " * objects.\n";
+  print " */\n";
+  print "public interface BinaryFile\n{\n";
+  print "  String MAGIC_STR = \"VisADBin\";\n";
+  print "  int FORMAT_VERSION = 1;\n";
+}
+
+sub print_file {
+  my $fileName = shift;
+  my $prefix = shift;
+  my $type = shift;
+
+  if (!open(FILE, $fileName)) {
+    print STDERR "Couldn't open '$fileName'\n";
+    return;
+  }
+  print "\n";
+
+  if (!defined($type)) {
+    $type = 'byte';
+  }
+
+  my $num = 1;
+  while (<FILE>) {
+    chomp;
+
+    if ($_ eq '') {
+      print "\n";
+
+      my $low = $num % 10;
+      if ($low != 0) {
+	$num = ($num + 10) - $low;
+      }
+    } else {
+
+      my $comment = '';
+      $comment = '// ' if (s/^\/\/\s+//);
+
+      my $val;
+      if ($type eq 'boolean') {
+        $val = 'false';
+      } else {
+        $val = $num;
+        $num++;
+      }
+
+      print '  ',$comment,$type,' ',$prefix,'_',$_,' = ',$val,";\n";
+    }
+  }
+
+  close(FILE);
+}
+
+sub print_footer {
+  print "}\n";
+}
+
+print_header;
+print_file('objs', 'OBJ');
+print_file('flds', 'FLD');
+print_file('maths', 'MATH');
+print_file('datas', 'DATA');
+print_file('debugs', 'DEBUG', 'boolean');
+print_footer;
+
+exit 0;
diff --git a/visad/data/visad/util/build_html_tbls.pl b/visad/data/visad/util/build_html_tbls.pl
new file mode 100644
index 0000000..2a2789d
--- /dev/null
+++ b/visad/data/visad/util/build_html_tbls.pl
@@ -0,0 +1,61 @@
+#!/usr/local/bin/perl -w
+#
+# Build tables for binary_file_format.html using data in the
+# 'datas', 'flds', 'maths' and 'objs' files, which
+# correspond to the DATA_*, FLD_*, MATH_* and OBJ_*
+# constants.
+
+use strict;
+
+sub print_data {
+  my $name = shift;
+  my $value = shift;
+
+  print '<td>',$name,'</td><td>',$value,'</td>';
+}
+
+sub print_file {
+  my $fileName = shift;
+  my $prefix = shift;
+
+  if (!open(FILE, $fileName)) {
+    print STDERR "Couldn't open '$fileName'\n";
+    return;
+  }
+  print "\n";
+
+  my $num = 1;
+  my %list = ();
+  while (<FILE>) {
+    chomp;
+
+    if ($_ eq '') {
+      my $low = $num % 10;
+      if ($low != 0) {
+	$num = ($num + 10) - $low;
+      }
+    } elsif (!/^\/\//) {
+      $list{$_} = $num++;
+    }
+  }
+
+  my @keys = sort {$list{$a} <=> $list{$b}} keys(%list);
+  my $half = @keys / 2;
+
+  for (my $i = 0; $i < $half; $i++) {
+    print '      <tr>';
+    print_data $prefix.'_'.$keys[$i],$list{$keys[$i]};
+    print '<td></td>';
+    print_data $prefix.'_'.$keys[$i+$half],$list{$keys[$i+$half]} if $half+$i < @keys;
+    print "</tr>\n";
+  }
+
+  close(FILE);
+}
+
+print_file('objs', 'OBJ');
+print_file('flds', 'FLD');
+print_file('maths', 'MATH');
+print_file('datas', 'DATA');
+
+exit 0;
diff --git a/visad/data/visad/util/datas b/visad/data/visad/util/datas
new file mode 100644
index 0000000..8fd8bb4
--- /dev/null
+++ b/visad/data/visad/util/datas
@@ -0,0 +1,41 @@
+// SCALAR
+TEXT
+REAL
+
+TUPLE
+REAL_TUPLE
+
+FIELD
+FLAT_FIELD
+
+// SET
+// SIMPLE_SET
+DOUBLE_SET
+FLOAT_SET
+LIST1D_SET
+// SAMPLED_SET
+SINGLETON_SET
+UNION_SET
+PRODUCT_SET
+IRREGULAR_SET
+IRREGULAR_1D_SET
+IRREGULAR_2D_SET
+IRREGULAR_3D_SET
+GRIDDED_SET
+GRIDDED_1D_SET
+GRIDDED_2D_SET
+GRIDDED_3D_SET
+GRIDDED_1D_DOUBLE_SET
+GRIDDED_2D_DOUBLE_SET
+GRIDDED_3D_DOUBLE_SET
+LINEAR_1D_SET
+LINEAR_2D_SET
+LINEAR_3D_SET
+LINEAR_ND_SET
+LINEAR_LATLON_SET
+INTEGER_1D_SET
+INTEGER_2D_SET
+INTEGER_3D_SET
+INTEGER_ND_SET
+
+NONE
diff --git a/visad/data/visad/util/debugs b/visad/data/visad/util/debugs
new file mode 100644
index 0000000..b72f64c
--- /dev/null
+++ b/visad/data/visad/util/debugs
@@ -0,0 +1,17 @@
+RD_CSYS
+RD_DATA
+RD_DATA_DETAIL
+RD_ERRE
+RD_MATH
+RD_STR
+RD_TIME
+RD_UNIT
+
+WR_CSYS
+WR_DATA
+WR_DATA_DETAIL
+WR_ERRE
+WR_MATH
+WR_STR
+WR_TIME
+WR_UNIT
diff --git a/visad/data/visad/util/flds b/visad/data/visad/util/flds
new file mode 100644
index 0000000..53fefec
--- /dev/null
+++ b/visad/data/visad/util/flds
@@ -0,0 +1,38 @@
+FIRSTS
+LASTS
+LENGTHS
+FLOAT_LIST
+SAMPLE
+FLOAT_SAMPLES
+DOUBLE_SAMPLES
+DATA_SAMPLES
+REAL_SAMPLES
+TRIVIAL_SAMPLES
+SET_SAMPLES
+SET
+LINEAR_SETS
+INTEGER_SETS
+SET_LIST
+
+COORDSYS_SERIAL
+DELAUNAY_SERIAL
+
+INDEX_UNIT
+INDEX_ERROR
+INDEX_COORDSYS
+
+INDEX_UNITS
+INDEX_ERRORS
+
+RANGE_COORDSYSES
+
+DELAUNAY
+DELAUNAY_TRI
+DELAUNAY_VERTICES
+DELAUNAY_WALK
+DELAUNAY_EDGES
+DELAUNAY_NUM_EDGES
+
+SET_FOLLOWS_TYPE
+
+END
diff --git a/visad/data/visad/util/maths b/visad/data/visad/util/maths
new file mode 100644
index 0000000..e4731dd
--- /dev/null
+++ b/visad/data/visad/util/maths
@@ -0,0 +1,12 @@
+FUNCTION
+REAL
+REAL_TUPLE
+SET
+TEXT
+TUPLE
+QUANTITY
+// DISPLAY_TUPLE
+// REAL_VECTOR
+// EARTH_VECTOR
+// GRID_VECTOR
+// DISPLAY_REAL
diff --git a/visad/data/visad/util/objs b/visad/data/visad/util/objs
new file mode 100644
index 0000000..581a95a
--- /dev/null
+++ b/visad/data/visad/util/objs
@@ -0,0 +1,7 @@
+COORDSYS
+DATA
+DATA_SERIAL
+ERROR
+MATH
+MATH_SERIAL
+UNIT
diff --git a/visad/depend b/visad/depend
new file mode 100644
index 0000000..e69de29
diff --git a/visad/formula/FormulaException.java b/visad/formula/FormulaException.java
new file mode 100644
index 0000000..6dc447d
--- /dev/null
+++ b/visad/formula/FormulaException.java
@@ -0,0 +1,35 @@
+//
+// FormulaException.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.formula;
+
+/** The type of exception thrown by the formula package.<P> */
+public class FormulaException extends visad.VisADException {
+  public FormulaException(String s) {
+    super(s);
+  }
+}
+
diff --git a/visad/formula/FormulaManager.java b/visad/formula/FormulaManager.java
new file mode 100644
index 0000000..4c577cf
--- /dev/null
+++ b/visad/formula/FormulaManager.java
@@ -0,0 +1,383 @@
+//
+// FormulaManager.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.formula;
+
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+import java.util.Vector;
+import visad.*;
+
+/** The FormulaManager class is the gateway into the visad.formula package,
+    a general-purpose formula parser and evaluator.  Variables update
+    automatically when the variables upon which they depend change.
+    For an example of usage, see the FormulaUtil.createStandardManager()
+    method.<P> */
+public class FormulaManager {
+
+  /** binary operators */
+  String[] bOps;
+
+  /** binary operator precedences */
+  int[] bPrec;
+
+  /** binary method signatures */
+  Method[] bMethods;
+
+  /** unary operators */
+  String[] uOps;
+
+  /** unary operator precedences */
+  int[] uPrec;
+
+  /** unary method signatures */
+  Method[] uMethods;
+
+  /** function names */
+  String[] funcs;
+
+  /** function method signatures */
+  Method[] fMethods;
+
+  /** implicit function precedence */
+  int iPrec;
+
+  /** implicit function methods */
+  Method[] iMethods;
+
+  /** formula text pre-parsing method.  For an application to support unusual
+      non-standard syntaxes, such as brackets (e.g., VAR[0]), that application
+      should supply a "pre-parsing" method in the preParseMethod
+      argument, which converts non-standard syntax to standard syntax (e.g.,
+      VAR[0] could become getElement(VAR, 0)).  The supplied method should be
+      a static method taking a String argument (the formula) and a
+      FormulaManager argument (this FormulaManager).  If the application does
+      not need such functionality, the preParseMethod argument can be null. */
+  Method ppMethod;
+
+  /** construct a new FormulaManager object */
+  public FormulaManager(String[] binOps, int[] binPrec, String[] binMethods,
+    String[] unaryOps, int[] unaryPrec, String[] unaryMethods,
+    String[] functions, String[] funcMethods, int implicitPrec,
+    String[] implicitMethods, String preParseMethod) throws FormulaException
+  {
+    bOps = binOps;
+    bPrec = binPrec;
+    bMethods = FormulaUtil.stringsToMethods(binMethods);
+    uOps = unaryOps;
+    uPrec = unaryPrec;
+    uMethods = FormulaUtil.stringsToMethods(unaryMethods);
+    funcs = functions;
+    fMethods = FormulaUtil.stringsToMethods(funcMethods);
+    iPrec = implicitPrec;
+    iMethods = FormulaUtil.stringsToMethods(implicitMethods);
+    String[] s = new String[1];
+    if (preParseMethod == null) ppMethod = null;
+    else {
+      String[] pps = new String[] {preParseMethod};
+      Method[] ppm = FormulaUtil.stringsToMethods(pps);
+      ppMethod = ppm[0];
+    }
+
+    // check that parallel arrays are really parallel
+    int l1 = bOps.length;
+    int l2 = bPrec.length;
+    int l3 = bMethods.length;
+    if (l1 != l2 || l1 != l3) {
+      throw new FormulaException("Binary arrays must have equal lengths");
+    }
+    l1 = uOps.length;
+    l2 = uPrec.length;
+    l3 = uMethods.length;
+    if (l1 != l2 || l1 != l3) {
+      throw new FormulaException("Unary arrays must have equal lengths");
+    }
+    l1 = funcs.length;
+    l2 = fMethods.length;
+    if (l1 != l2) {
+      throw new FormulaException("Function arrays must have equal lengths");
+    }
+
+    // check that all operators are one character in length, all operators are
+    // legal, and all duplicate operators have equal operator precedences
+    for (int i=0; i<bOps.length; i++) {
+      if (bOps[i].length() > 1) {
+        throw new FormulaException("All operators must be one character " +
+                                   "in length");
+      }
+      char c = bOps[i].charAt(0);
+      if (c == '(' || c == ')' || c == ',' || (c >= '0' && c <= '9') ||
+                                              (c >= 'a' && c <= 'z') ||
+                                              (c >= 'A' && c <= 'Z')) {
+        throw new FormulaException("The character \"" + c + "\" cannot be " +
+                                   "used as an operator");
+      }
+      for (int j=i+1; j<bOps.length; j++) {
+        if (bOps[i].charAt(0) == bOps[j].charAt(0) && bPrec[i] != bPrec[j]) {
+          throw new FormulaException("Duplicate operators must have equal " +
+                                     "operator precedences");
+        }
+      }
+    }
+    for (int i=0; i<uOps.length; i++) {
+      if (uOps[i].length() > 1) {
+        throw new FormulaException("All operators must be one character " +
+                                   "in length");
+      }
+      char c = uOps[i].charAt(0);
+      if (c == '(' || c == ')' || c == ',' || (c >= '0' && c <= '9') ||
+                                              (c >= 'a' && c <= 'z') ||
+                                              (c >= 'A' && c <= 'Z')) {
+        throw new FormulaException("The character \"" + c + "\" cannot be " +
+                                   "used as an operator");
+      }
+      for (int j=i+1; j<uOps.length; j++) {
+        if (uOps[i].charAt(0) == uOps[j].charAt(0) && uPrec[i] != uPrec[j]) {
+          throw new FormulaException("Duplicate operators must have equal " +
+                                     "operator precedences");
+        }
+      }
+    }
+
+    // check that all function names start with a letter
+    for (int i=0; i<functions.length; i++) {
+      char c = functions[i].charAt(0);
+      if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) {
+        throw new FormulaException("All functions must begin with a letter");
+      }
+    }
+
+    // check that all methods are legal
+    for (int i=0; i<bMethods.length; i++) {
+      if (bMethods[i] == null) {
+        throw new FormulaException("The method \"" + binMethods[i] +
+                                   "\" is not valid");
+      }
+    }
+    for (int i=0; i<uMethods.length; i++) {
+      if (uMethods[i] == null) {
+        throw new FormulaException("The method \"" + unaryMethods[i] +
+                                   "\" is not valid");
+      }
+    }
+    for (int i=0; i<fMethods.length; i++) {
+      if (fMethods[i] == null) {
+        throw new FormulaException("The method \"" + funcMethods[i] +
+                                   "\" is not valid");
+      }
+    }
+    for (int i=0; i<iMethods.length; i++) {
+      if (iMethods[i] == null) {
+        throw new FormulaException("The method \"" + implicitMethods[i] +
+                                   "\" is not valid");
+      }
+    }
+  }
+
+  /** add a variable to the database that uses tr as its ThingReference */
+  public void createVar(String name, ThingReference tr) throws VisADException {
+    FormulaVar v;
+    try {
+      v = getVarByName(name);
+    }
+    catch (FormulaException exc) {
+      v = null;
+    }
+    if (v != null) {
+      throw new FormulaException("The variable " + name + " already exists.");
+    }
+    Vars.add(new FormulaVar(name, this, tr));
+  }
+
+  /** assign a formula to a variable */
+  public void assignFormula(String name, String formula)
+    throws VisADException
+  {
+    FormulaVar v = getVarByNameOrCreate(name);
+    v.setFormula(formula);
+  }
+
+  /** blocks until this variable's formula is finished computing */
+  public void waitForFormula(String name) throws VisADException {
+    FormulaVar v = getVarByName(name);
+    v.waitForFormula();
+  }
+
+  /** set a variable to auto-update its formula based on a Text object
+      referenced by a ThingReference (useful for remote formula updates) */
+  public void setTextRef(String name, ThingReference textRef)
+    throws VisADException, RemoteException
+  {
+    FormulaVar v = getVarByNameOrCreate(name);
+    v.setTextRef(textRef);
+  }
+
+  /** get the current list of errors that occurred when evaluating
+      "name" and clear the list */
+  public String[] getErrors(String name) {
+    try {
+      FormulaVar v = getVarByNameOrCreate(name);
+      String[] s = v.getErrors();
+      v.clearErrors();
+      return s;
+    }
+    catch (FormulaException exc) {
+      return null;
+    }
+    catch (VisADException exc) {
+      return null;
+    }
+  }
+
+  /** check whether it is safe to remove a variable from the database */
+  public boolean canBeRemoved(String name) throws FormulaException {
+    FormulaVar v = getVarByName(name);
+    return !v.othersDepend();
+  }
+
+  /** check whether a given variable is currently in the database */
+  public boolean exists(String name) {
+    boolean exists = false;
+    try {
+      FormulaVar v = getVarByName(name);
+      exists = true;
+    }
+    catch (FormulaException exc) { }
+    return exists;
+  }
+
+  /** remove a variable from the database */
+  public void remove(String name) throws FormulaException {
+    if (canBeRemoved(name)) Vars.remove(getVarByName(name));
+    else {
+      throw new FormulaException("Cannot remove variable " + name + " " +
+                                 "because other variables depend on it!");
+    }
+  }
+
+  /** set a variable's value directly */
+  public void setThing(String name, Thing t)
+    throws VisADException, RemoteException
+  {
+    FormulaVar v = getVarByNameOrCreate(name);
+    v.setThing(t);
+  }
+
+  /** set a variable's ThingReference */
+  public void setReference(String name, ThingReference tr)
+    throws VisADException
+  {
+    FormulaVar v = getVarByNameOrCreate(name);
+    v.setReference(tr);
+  }
+
+  /** get a variable's current value */
+  public Thing getThing(String name) throws FormulaException {
+    FormulaVar v = getVarByName(name);
+    return v.getThing();
+  }
+
+  /** get a variable's associated ThingReference */
+  public ThingReference getReference(String name) throws FormulaException {
+    FormulaVar v = getVarByName(name);
+    return v.getReference();
+  }
+
+  /** get a variable's current formula */
+  public String getFormula(String name) throws FormulaException {
+    FormulaVar v = getVarByName(name);
+    return v.getFormula();
+  }
+
+  /** list of all variables in this FormulaManager object */
+  private Vector Vars = new Vector();
+
+  /** return the variable "name" */
+  FormulaVar getVarByName(String name) throws FormulaException {
+    for (int i=0; i<Vars.size(); i++) {
+      FormulaVar v = (FormulaVar) Vars.elementAt(i);
+      if (v.name.equalsIgnoreCase(name)) return v;
+    }
+    throw new FormulaException("The variable " + name + " does not exist.");
+  }
+
+  /** return the variable "name", creating it if necessary */
+  FormulaVar getVarByNameOrCreate(String name) throws VisADException {
+    FormulaVar v;
+    try {
+      v = getVarByName(name);
+    }
+    catch (FormulaException exc) {
+      v = new FormulaVar(name, this);
+      Vars.add(v);
+    }
+    return v;
+  }
+
+  /** identify whether a given token is a unary operator */
+  boolean isUnaryOp(String op) {
+    for (int i=0; i<uOps.length; i++) {
+      if (uOps[i].equals(op)) return true;
+    }
+    return false;
+  }
+
+  /** identify whether a given token is a binary operator */
+  boolean isBinaryOp(String op) {
+    for (int i=0; i<bOps.length; i++) {
+      if (bOps[i].equals(op)) return true;
+    }
+    return false;
+  }
+
+  /** identify whether a given token is a defined function */
+  boolean isFunction(String token) {
+    for (int i=0; i<funcs.length; i++) {
+      if (funcs[i].equalsIgnoreCase(token)) return true;
+    }
+    return false;
+  }
+
+  /** returns a unary operator's level of precedence */
+  int getUnaryPrec(String op) {
+    for (int i=0; i<uOps.length; i++) {
+      if (uOps[i].equals(op)) return uPrec[i];
+    }
+    return -1;
+  }
+
+  /** returns a binary operator's level of precedence */
+  int getBinaryPrec(String op) {
+    if (op.equals("(")) return Integer.MAX_VALUE;
+    if (op.equals(",")) return Integer.MAX_VALUE - 1;
+    for (int i=0; i<bOps.length; i++) {
+      if (bOps[i].equals(op)) return bPrec[i];
+    }
+    return -1;
+  }
+
+}
+
diff --git a/visad/formula/FormulaUtil.java b/visad/formula/FormulaUtil.java
new file mode 100644
index 0000000..92dfb2b
--- /dev/null
+++ b/visad/formula/FormulaUtil.java
@@ -0,0 +1,599 @@
+//
+// FormulaUtil.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.formula;
+
+import java.lang.reflect.*;
+import java.rmi.RemoteException;
+import java.util.*;
+import visad.*;
+
+/** Contains a variety of useful methods related to the
+    visad.formula package.<P> */
+public class FormulaUtil {
+
+  /** create a FormulaManager object with many commonly desired features */
+  public static FormulaManager createStandardManager() {
+    String[] binOps = {".", "^", "*", "/", "%", "+", "-"};
+    int[] binPrec =   {200, 400, 600, 600, 600, 800, 800};
+    String[] binMethods = {
+      "visad.formula.FormulaUtil.dot(visad.TupleIface, visad.Real)",
+      "visad.Data.pow(visad.Data)",
+      "visad.Data.multiply(visad.Data)",
+      "visad.Data.divide(visad.Data)",
+      "visad.Data.remainder(visad.Data)",
+      "visad.Data.add(visad.Data)",
+      "visad.Data.subtract(visad.Data)"
+    };
+    String[] unaryOps = {"-"};
+    int[] unaryPrec =   {500};
+    String[] unaryMethods = {"visad.Data.negate()"};
+    String[] functions = {
+      "abs", "acos", "acosDegrees", "asin", "asinDegrees", "atan", "atan2",
+      "atanDegrees", "atan2Degrees", "ceil", "combine", "cos", "cosDegrees",
+      "derive", "domainMultiply", "domainFactor", "exp", "extract", "floor",
+      "getSample", "linkx", "log", "max", "min", "negate", "rint", "round",
+      "sin", "sinDegrees", "sqrt", "tan", "tanDegrees"
+    };
+    String[] funcMethods = {
+      "visad.Data.abs()",
+      "visad.Data.acos()",
+      "visad.Data.acosDegrees()",
+      "visad.Data.asin()",
+      "visad.Data.asinDegrees()",
+      "visad.Data.atan()",
+      "visad.Data.atan2(visad.Data)",
+      "visad.Data.atanDegrees()",
+      "visad.Data.atan2Degrees(visad.Data)",
+      "visad.Data.ceil()",
+      "visad.FieldImpl.combine(visad.Field[])",
+      "visad.Data.cos()",
+      "visad.Data.cosDegrees()",
+      "visad.formula.FormulaUtil.derive(visad.Function, " +
+        "visad.formula.VRealType)",
+      "visad.FieldImpl.domainMultiply()",
+      "visad.formula.FormulaUtil.factor(" +
+      "visad.FieldImpl, visad.formula.VRealType)",
+      "visad.Data.exp()",
+      "visad.formula.FormulaUtil.extract(visad.Field, visad.Real)",
+      "visad.Data.floor()",
+      "visad.formula.FormulaUtil.brackets(visad.Field, visad.Real)",
+      "visad.formula.FormulaUtil.link(visad.formula.VMethod, " +
+        "java.lang.Object[])",
+      "visad.Data.log()",
+      "visad.Data.max(visad.Data)",
+      "visad.Data.min(visad.Data)",
+      "visad.Data.negate()",
+      "visad.Data.rint()",
+      "visad.Data.round()",
+      "visad.Data.sin()",
+      "visad.Data.sinDegrees()",
+      "visad.Data.sqrt()",
+      "visad.Data.tan()",
+      "visad.Data.tanDegrees()"
+    };
+    int implicitPrec = 200;
+    String[] implicitMethods = {
+      "visad.formula.FormulaUtil.implicit(visad.Function, visad.Real)",
+      "visad.Function.evaluate(visad.RealTuple)"
+    };
+    String preParseMethod = "visad.formula.FormulaUtil.preParse(" +
+      "java.lang.String, visad.formula.FormulaManager)";
+    FormulaManager f;
+    try {
+      f = new FormulaManager(binOps, binPrec, binMethods,
+        unaryOps, unaryPrec, unaryMethods, functions, funcMethods,
+        implicitPrec, implicitMethods, preParseMethod);
+    }
+    catch (FormulaException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+      return null;
+    }
+    return f;
+  }
+
+  /** evaluate the dot operator */
+  public static Data dot(TupleIface t, Real r) {
+    Data d = null;
+    try {
+      d = t.getComponent((int) r.getValue());
+    }
+    catch (VisADException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    return d;
+  }
+
+  /** evaluate the derive function */
+  public static Data derive(Function f, VRealType rt) {
+    Data val = null;
+    try {
+      val = f.derivative(rt.getRealType(), Data.NO_ERRORS);
+    }
+    catch (VisADException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    return val;
+  }
+
+  /** evaluate the domainFactor function */
+  public static visad.Field factor(FieldImpl f, VRealType rt) {
+    visad.Field val = null;
+    try {
+      val = f.domainFactor(rt.getRealType());
+    }
+    catch (VisADException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    return val;
+  }
+
+  /** evaluate the extract function */
+  public static Data extract(visad.Field f, Real r) {
+    Data d = null;
+    try {
+      d = f.extract((int) r.getValue());
+    }
+    catch (VisADException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    return d;
+  }
+
+  /** evaluate the link function */
+  public static Data link(VMethod m, Object[] o) throws VisADException {
+    Data ans = null;
+    if (o != null) {
+      for (int i=0; i<o.length; i++) {
+        // convert VRealTypes to RealTypes
+        if (o[i] instanceof VRealType) {
+          o[i] = ((VRealType) o[i]).getRealType();
+        }
+      }
+    }
+    try {
+      ans = (Data) FormulaUtil.invokeMethod(m.getMethod(), o);
+    }
+    catch (ClassCastException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+      throw new VisADException("Link error: invalid linked method");
+    }
+    catch (IllegalAccessException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+      throw new VisADException("Link error: cannot access linked method");
+    }
+    catch (IllegalArgumentException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+      throw new VisADException("Link error: bad method argument");
+    }
+    catch (InvocationTargetException exc) {
+      if (FormulaVar.DEBUG) exc.getTargetException().printStackTrace();
+      throw new VisADException("Link error: linked method threw an exception");
+    }
+    if (ans == null) {
+      throw new VisADException("Link error: linked method returned null data");
+    }
+    return ans;
+  }
+
+  /** evaluate implicit function syntax; e.g., A1(5) or A1(A2) */
+  public static Data implicit(Function f, Real r) {
+    Data value = null;
+    try {
+      value = f.evaluate(r);
+    }
+    catch (VisADException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    return value;
+  }
+
+  /** evaluate the bracket function; e.g., A1[5] or A1[A2] */
+  public static Data brackets(visad.Field f, Real r) {
+    Data value = null;
+    try {
+      RealType rt = (RealType) r.getType();
+      value = f.getSample((int) r.getValue());
+    }
+    catch (VisADException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (FormulaVar.DEBUG) exc.printStackTrace();
+    }
+    return value;
+  }
+
+  /** number of link variables that have been created */
+  private static int linkNum = 0;
+
+  /** do some pre-computation parsing to a formula */
+  public static String preParse(String f, FormulaManager fm) {
+    // remove spaces
+    StringTokenizer t = new StringTokenizer(f, " ", false);
+    String s = "";
+    while (t.hasMoreTokens()) s = s + t.nextToken();
+    if (s.equals("")) return s;
+
+    // multi-pass pre-parse sequence
+    String os;
+    do {
+      os = s;
+      s = preParseOnce(os, fm);
+    }
+    while (!s.equals(os));
+    return s;
+  }
+
+  /** used by preParse */
+  private static String preParseOnce(String s, FormulaManager fm) {
+    // convert to lower case
+    String l = s.toLowerCase();
+
+    // scan entire string
+    int len = l.length();
+    boolean letter = false;
+    String ns = "";
+    for (int i=0; i<len; i++) {
+      if (!letter && i < len - 1 && l.substring(i, i+2).equals("d(")) {
+        // convert d(x)/d(y) notation to standard derive(x, y) notation
+        i += 2;
+        int s1 = i;
+        for (int paren=1; paren>0; i++) {
+          // check for correct syntax
+          if (i >= len) return s;
+          char c = l.charAt(i);
+          if (c == '(') paren++;
+          if (c == ')') paren--;
+        }
+        int e1 = i-1;
+        // check for correct syntax
+        if (i > len - 3 || !l.substring(i, i+3).equals("/d(")) return s;
+        i += 3;
+        int s2 = i;
+        for (int paren=1; paren>0; i++) {
+          // check for correct syntax
+          if (i >= len) return s;
+          char c = l.charAt(i);
+          if (c == '(') paren++;
+          if (c == ')') paren--;
+        }
+        int e2 = i-1;
+        ns = ns + "derive(" + s.substring(s1, e1) +
+                        "," + s.substring(s2, e2) + ")";
+        i--;
+      }
+      else if (!letter && i < len - 4 && l.substring(i, i+5).equals("link(")) {
+        // evaluate link(code) notation and replace with link variable
+        i += 5;
+        int s1 = i;
+        try {
+          while (l.charAt(i) != '(') i++;
+        }
+        catch (ArrayIndexOutOfBoundsException exc) {
+          // incorrect syntax
+          return s;
+        }
+        i++;
+        int e1 = i-1;
+        int s2 = i;
+        for (int paren=2; paren>1; i++) {
+          // check for correct syntax
+          if (i >= len) return s;
+          char c = l.charAt(i);
+          if (c == '(') paren++;
+          if (c == ')') paren--;
+        }
+        int e2 = i-1;
+        // check for correct syntax
+        if (i >= len || l.charAt(i) != ')') return s;
+        String prestr = s.substring(s1, e1) + "(";
+        String str = prestr;
+
+        // parse method's arguments; determine if they are Data or RealType
+        String sub = s.substring(s2, e2);
+        StringTokenizer st = new StringTokenizer(sub, ",", false);
+        boolean first = true;
+        Vector v = new Vector();
+        while (st.hasMoreTokens()) {
+          String token = st.nextToken();
+          if (first) first = false;
+          else str = str + ",";
+          RealType rt = RealType.getRealTypeByName(token);
+          String sv = (rt == null ? "visad.Data" : "visad.RealType");
+          v.add(sv);
+          str = str + sv;
+        }
+        str = str + ")";
+
+        // obtain Method object
+        Method[] meths = FormulaUtil.stringsToMethods(new String[] {str});
+
+        if (meths[0] == null) {
+          // attempt to identify any matching methods by compressing
+          // some or all of the arguments into array form
+          int vlen = v.size();
+          Vector vstrs = new Vector();
+          for (int iv=0; iv<vlen; iv++) {
+            String si = (String) v.elementAt(iv);
+            int lv = iv;
+            String sl;
+            while (lv < vlen) {
+              sl = (String) v.elementAt(lv++);
+              if (!sl.equals(si)) {
+                break;
+              }
+              str = prestr;
+              first = true;
+              for (int j=0; j<vlen; j++) {
+                if (first) first = false;
+                else str = str + ",";
+                String sj = (String) v.elementAt(j);
+                str = str + sj;
+                if (iv == j) {
+                  str = str + "[]";
+                  j = lv - 1;
+                }
+              }
+              str = str + ")";
+              vstrs.add(str);
+            }
+          }
+          String[] strlist = new String[vstrs.size()];
+          vstrs.toArray(strlist);
+          meths = FormulaUtil.stringsToMethods(strlist);
+          int found = -1;
+          for (int j=0; j<meths.length && found < 0; j++) {
+            if (meths[j] != null) found = j;
+          }
+          if (found >= 0) meths[0] = meths[found];
+          else {
+            // could not find a matching method
+            return s;
+          }
+        }
+
+        // store method object in a link variable
+        String link = "link" + (++linkNum);
+        try {
+          fm.setThing(link, new VMethod(meths[0]));
+        }
+        // catch any errors setting the link variable
+        catch (FormulaException exc) {
+          return s;
+        }
+        catch (VisADException exc) {
+          return s;
+        }
+        catch (RemoteException exc) {
+          return s;
+        }
+        ns = ns + "linkx(" + link + "," + s.substring(s2, e2) + ")";
+      }
+      else if (!letter) {
+        int j = i;
+        char c = l.charAt(j++);
+        while (j < len && ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))) {
+          c = l.charAt(j++);
+        }
+        // check for end-of-string
+        if (j == len) return ns + s.substring(i, len);
+        if (c == '[') {
+          // convert x[y] notation to standard getSample(x, y) notation
+          int k = j;
+          for (int paren=1; paren>0; k++) {
+            // check for correct syntax
+            if (k >= len) return s;
+            c = l.charAt(k);
+            if (c == '[') paren++;
+            if (c == ']') paren--;
+          }
+          ns = ns + "getSample(" + s.substring(i, j-1) +
+                             "," + s.substring(j, k-1) + ")";
+          i = k-1;
+        }
+        else ns = ns + s.charAt(i);
+      }
+      else {
+        // append character to new string
+        ns = ns + s.charAt(i);
+      }
+      char c = (i < len) ? l.charAt(i) : '\0';
+      letter = (c >= 'a' && c <= 'z');
+    }
+    return ns;
+  }
+
+  // useful methods for advanced reflection:
+
+  /** convert an array of strings of the form
+      "package.Class.method(Class, Class, ...)"
+      to an array of Method objects */
+  public static Method[] stringsToMethods(String[] strings) {
+    int len = strings.length;
+    Method[] methods = new Method[len];
+    for (int j=0; j<len; j++) {
+      // remove spaces
+      StringTokenizer t = new StringTokenizer(strings[j], " ", false);
+      String s = "";
+      while (t.hasMoreTokens()) s = s + t.nextToken();
+
+      // separate into two strings
+      t = new StringTokenizer(s, "(", false);
+      String pre = t.nextToken();
+      String post = t.nextToken();
+
+      // separate first string into class and method strings
+      t = new StringTokenizer(pre, ".", false);
+      String c = t.nextToken();
+      int count = t.countTokens();
+      for (int i=0; i<count-1; i++) c = c + "." + t.nextToken();
+      String m = t.nextToken();
+
+      // get argument array of strings
+      t = new StringTokenizer(post, ",)", false);
+      count = t.countTokens();
+      String[] a;
+      if (count == 0) a = null;
+      else a = new String[count];
+      int x = 0;
+      while (t.hasMoreTokens()) a[x++] = t.nextToken();
+
+      // convert result to Method object
+      Class clas = null;
+      try {
+        clas = Class.forName(c);
+      }
+      catch (ClassNotFoundException exc) {
+        if (FormulaVar.DEBUG) {
+          System.out.println("ERROR: Class " + c + " does not exist!");
+        }
+        methods[j] = null;
+        continue;
+      }
+      Class[] param;
+      if (a == null) param = null;
+      else param = new Class[a.length];
+      for (int i=0; i<count; i++) {
+        // hack to convert array arguments to correct form
+        if (a[i].endsWith("[]")) {
+          a[i] = "[L" + a[i].substring(0, a[i].length()-2);
+          while (a[i].endsWith("[]")) {
+            a[i] = "[" + a[i].substring(0, a[i].length()-2);
+          }
+          a[i] = a[i] + ";";
+        }
+
+        try {
+          param[i] = Class.forName(a[i]);
+        }
+        catch (ClassNotFoundException exc) {
+          if (FormulaVar.DEBUG) {
+            System.out.println("ERROR: Class " + a[i] +
+              " (" + i + ") does not exist!");
+          }
+          methods[j] = null;
+          continue;
+        }
+      }
+      Method method = null;
+      try {
+        method = clas.getMethod(m, param);
+      }
+      catch (NoSuchMethodException exc) {
+        if (FormulaVar.DEBUG) {
+          System.out.println("ERROR: Method " + m + " does not exist!");
+        }
+        methods[j] = null;
+        continue;
+      }
+      methods[j] = method;
+    }
+    return methods;
+  }
+
+  /** attempt to invoke a Method with the given Object arguments, performing
+      static method auto-detection and automatic array compression */
+  public static Object invokeMethod(Method m, Object[] o)
+    throws IllegalAccessException, IllegalArgumentException,
+    InvocationTargetException
+  {
+    Object obj;
+    Object[] args;
+    Class[] c = m.getParameterTypes();
+    int num = (o == null) ? 0 : o.length;
+    int len = -1;
+    int a = -1;
+    if (c != null) {
+      len = c.length;
+      for (int i=0; i<len; i++) {
+        if (c[i].isArray()) a = i;
+      }
+    }
+    if (Modifier.isStatic(m.getModifiers())) {
+      // static method
+      obj = null;
+      if (num > 0) {
+        if (a < 0) {
+          args = new Object[num];
+          System.arraycopy(o, 0, args, 0, num);
+        }
+        else {
+          // compress some of the arguments into array form
+          args = new Object[len];
+          if (a > 0) System.arraycopy(o, 0, args, 0, a);
+          Object array = Array.newInstance(c[a].getComponentType(), num-len+1);
+          System.arraycopy(o, a, array, 0, num-len+1);
+          args[a] = array;
+          if (a < len-1) System.arraycopy(o, num-len+a+1, args, a+1, len-a-1);
+        }
+      }
+      else args = null;
+    }
+    else {
+      // object method
+      if (num > 0) obj = o[0];
+      else {
+        // invalid object method
+        return null;
+      }
+      if (num > 1) {
+        if (a < 0) {
+          args = new Object[num-1];
+          System.arraycopy(o, 1, args, 0, num-1);
+        }
+        else {
+          // compress some of the arguments into array form
+          args = new Object[len];
+          if (a > 0) System.arraycopy(o, 1, args, 0, a);
+          Object array = Array.newInstance(c[a].getComponentType(), num-len);
+          System.arraycopy(o, a+1, array, 0, num-len);
+          args[a+1] = array;
+          if (a < len-1) System.arraycopy(o, num-len+a+1, args, a+1, len-a-1);
+        }
+      }
+      else args = null;
+    }
+    return m.invoke(obj, args);
+  }
+
+}
+
diff --git a/visad/formula/FormulaVar.java b/visad/formula/FormulaVar.java
new file mode 100644
index 0000000..4ea021b
--- /dev/null
+++ b/visad/formula/FormulaVar.java
@@ -0,0 +1,707 @@
+//
+// FormulaVar.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.formula;
+
+import java.lang.reflect.InvocationTargetException;
+import java.rmi.RemoteException;
+import java.util.*;
+import visad.*;
+
+/** Represents a variable.<P> */
+public class FormulaVar extends ActionImpl {
+
+  /** for testing purposes */
+  public static boolean DEBUG = false;
+
+  /** constant tag */
+  public static RealType CONSTANT =
+    RealType.getRealType("visad/formula/constant");
+
+  /** associated FormulaManager object */
+  private FormulaManager fm;
+
+  /** name of this variable */
+  String name;
+
+  /** formula associated with this variable, if any */
+  private String formula;
+
+  /** formula in postfix notation, if it has been converted */
+  private Postfix postfix;
+
+  /** reference of this variable */
+  private ThingReference tref;
+
+  /** list of errors that have occurred during computation, in String form */
+  private Vector errors = new Vector();
+
+  /** for synchronization */
+  private Object Lock = new Object();
+
+  /** whether the formula is currently being computed */
+  private boolean computing = false;
+
+  /** constructor without specified ThingReference */
+  FormulaVar(String n, FormulaManager f) throws VisADException {
+    this(n, f, null);
+  }
+
+  /** constructor with specified ThingReference */
+  FormulaVar(String n, FormulaManager f, ThingReference t)
+    throws VisADException
+  {
+    super(n);
+    fm = f;
+    name = n;
+    tref = (t == null) ? new ThingReferenceImpl(name) : t;
+    for (int i=0; i<fm.bOps.length; i++) {
+      if (name.indexOf(fm.bOps[i]) >= 0) {
+        throw new FormulaException("variable names cannot contain operators");
+      }
+    }
+    for (int i=0; i<fm.uOps.length; i++) {
+      if (name.indexOf(fm.uOps[i]) >= 0) {
+        throw new FormulaException("variable names cannot contain operators");
+      }
+    }
+  }
+
+  /** vector of variables on which this one depends */
+  private Vector depend = new Vector();
+
+  /** vector of variables that require this variable (depend on it) */
+  private Vector required = new Vector();
+
+  /** return whether any other variables depend on this one */
+  boolean othersDepend() {
+    return !required.isEmpty();
+  }
+
+  /** return whether this variable depends on v */
+  boolean isDependentOn(FormulaVar v) {
+    if (v == this || depend.contains(v)) return true;
+    for (int i=0; i<depend.size(); i++) {
+      FormulaVar vi = (FormulaVar) depend.elementAt(i);
+      if (vi.isDependentOn(v)) return true;
+    }
+    return false;
+  }
+
+  /** add a dependency for this variable */
+  private void setDependentOn(FormulaVar v) {
+    if (!depend.contains(v)) {
+      depend.add(v);
+      try {
+        addReference(v.getReference());
+        v.required.add(this);
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+    }
+  }
+
+  /** clear this variable's dependency list */
+  private void clearDependencies() {
+    int len = depend.size();
+    for (int i=0; i<len; i++) {
+      FormulaVar v = (FormulaVar) depend.elementAt(i);
+      v.required.remove(this);
+    }
+    depend.removeAllElements();
+    try {
+      removeAllReferences();
+    }
+    catch (VisADException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+  }
+
+  /** rebuild this variable's dependency list, then recompute this variable */
+  private void rebuildDependencies() throws FormulaException {
+    disableAction();
+    synchronized (Lock) {
+      clearDependencies();
+      if (formula != null) {
+        if (postfix == null) {
+          try {
+            // compute postfix expression
+            Object[] o = new Object[2];
+            o[0] = formula;
+            o[1] = fm;
+            String pf = formula;
+            try {
+              // run formula through user's pre-processing function
+              pf = (String) FormulaUtil.invokeMethod(fm.ppMethod, o);
+            }
+            catch (IllegalAccessException exc) {
+              if (DEBUG) exc.printStackTrace();
+              evalError("Preparsing access exception", exc);
+            }
+            catch (IllegalArgumentException exc) {
+              if (DEBUG) exc.printStackTrace();
+              evalError("Preparsing argument exception", exc);
+            }
+            catch (InvocationTargetException exc) {
+              Throwable t = exc.getTargetException();
+              if (DEBUG) t.printStackTrace();
+              evalError("Preparsing exception", t);
+            }
+            postfix = new Postfix(pf, fm);
+            int len = (postfix.tokens == null ? 0 : postfix.tokens.length);
+            for (int i=0; i<len; i++) {
+              String token = postfix.tokens[i];
+              if (postfix.codes[i] == Postfix.OTHER) {
+                Double d = null;
+                try {
+                  d = Double.valueOf(token);
+                }
+                catch (NumberFormatException exc) { }
+                if (d == null) {
+                  // token is a variable name
+                  FormulaVar v = null;
+                  try {
+                    v = fm.getVarByNameOrCreate(token);
+                  }
+                  catch (FormulaException exc) {
+                    evalError("\"" + token + "\" is an illegal variable name");
+                  }
+                  catch (VisADException exc) {
+                    evalError("Internal VisAD error", exc);
+                  }
+                  if (v != null) {
+                    if (v.isDependentOn(this)) {
+                      clearDependencies();
+                      throw new FormulaException(
+                        "This formula creates an infinite loop");
+                    }
+                    setDependentOn(v);
+                  }
+                }
+              }
+            }
+          }
+          catch (FormulaException exc) {
+            evalError("Syntax error in formula", exc);
+            try {
+              tref.setThing(null);
+            }
+            catch (VisADException exc2) {
+              evalError("Internal VisAD error", exc2);
+            }
+            catch (RemoteException exc2) {
+              evalError("Internal remote error", exc2);
+            }
+          }
+        }
+      }
+    }
+    enableAction();
+    if (depend.isEmpty()) {
+      // formula has no dependencies; trigger doAction manually
+      doAction();
+    }
+  }
+
+  /** set the formula for this variable */
+  void setFormula(String f) throws FormulaException {
+    formula = f;
+    if (textRef != null) {
+      Text text = new Text(formula);
+      try {
+        textRef.setThing(text);
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+    }
+    postfix = null;
+    computing = true;
+    rebuildDependencies();
+  }
+
+  /** waits for this formula to recompute */
+  void waitForFormula() {
+    synchronized (Lock) {
+      if (computing) {
+        try {
+          Lock.wait();
+        }
+        catch (InterruptedException exc) { }
+      }
+    }
+  }
+
+  /** reference to a Text object equal to this variable's formula */
+  ThingReference textRef = null;
+
+  /** cell for recomputing formula from referenced Text object */
+  CellImpl textCell = new CellImpl() {
+    public void doAction() {
+      boolean textChanged = false;
+      if (textRef != null) {
+        try {
+          Thing thing = textRef.getThing();
+          if (thing instanceof Text) {
+            Text t = (Text) thing;
+            String newForm = t.getValue();
+            if (newForm == null) newForm = "";
+            if (!newForm.equals(formula)) {
+              textChanged = true;
+              setFormula(newForm);
+            }
+          }
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+    }
+  };
+
+  RemoteCellImpl rtCell = null;
+
+  /** set the formula to be dependent on a Text object referenced by tr */
+  void setTextRef(ThingReference tr) throws VisADException, RemoteException {
+    if (textRef == tr) return;
+    if (textRef != null) {
+      if (textRef instanceof RemoteDataReference) {
+        rtCell.removeReference(textRef);
+        rtCell = null;
+      }
+      else textCell.removeReference(textRef);
+    }
+    textRef = tr;
+    if (textRef != null) {
+      if (textRef instanceof RemoteDataReference) {
+        rtCell = new RemoteCellImpl(textCell);
+        rtCell.addReference(textRef);
+      }
+      else textCell.addReference(textRef);
+    }
+  }
+
+  /** set the Thing for this variable directly */
+  void setThing(Thing t) throws VisADException, RemoteException {
+    synchronized (Lock) {
+      formula = null;
+      postfix = null;
+      clearDependencies();
+      if (t == null || t != tref.getThing()) tref.setThing(t);
+    }
+  }
+
+  /** set the ThingReference for this variable */
+  void setReference(ThingReference tr) {
+    if (tref == tr) return;
+    synchronized (Lock) {
+      int len = required.size();
+      for (int i=0; i<len; i++) {
+        FormulaVar v = (FormulaVar) required.elementAt(i);
+        try {
+          v.removeReference(tref);
+          v.addReference(tr);
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+      tref = tr;
+    }
+  }
+
+  /** get the Thing for this variable */
+  Thing getThing() {
+    try {
+      return tref.getThing();
+    }
+    catch (VisADException exc) {
+      return null;
+    }
+    catch (RemoteException exc) {
+      return null;
+    }
+  }
+
+  /** get the ThingReference for this variable */
+  ThingReference getReference() {
+    return tref;
+  }
+
+  /** get the formula for this variable */
+  String getFormula() {
+    return formula;
+  }
+
+  /** get an array of Strings representing any errors that have occurred
+      during formula evaluation */
+  String[] getErrors() {
+    synchronized (errors) {
+      int len = errors.size();
+      if (len == 0) return null;
+      String[] s = new String[len];
+      for (int i=0; i<len; i++) {
+        s[i] = (String) errors.elementAt(i);
+      }
+      return s;
+    }
+  }
+
+  /** clear the list of errors that have occurred during formula evaluation */
+  void clearErrors() {
+    synchronized (errors) {
+      errors.clear();
+    }
+  }
+
+  /** add an error to the list of errors that have occurred during
+      formula evaluation */
+  private void evalError(String s) {
+    synchronized (errors) {
+      errors.add(s);
+    }
+  }
+
+  /** add an error to the list of errors that have occurred during formula
+      evaluation, appending the given exception's message if any */
+  private void evalError(String s, Throwable t) {
+    String msg = (t == null ? null : t.getMessage());
+    evalError(s + (msg == null ? "" : ": " + msg));
+  }
+
+  /** recompute this variable */
+  public void doAction() {
+    synchronized (Lock) {
+      try {
+        if (postfix != null) tref.setThing(compute(postfix));
+      }
+      catch (VisADException exc) {
+        evalError("Could not store final value in variable");
+      }
+      catch (RemoteException exc) {
+        evalError("Could not store final value in variable (remote)");
+      }
+      computing = false;
+      Lock.notifyAll();
+    }
+  }
+
+  /** used by compute method for convenience */
+  private Thing popStack(Stack s) {
+    if (s.empty()) {
+      evalError("Syntax error in formula (stack empty)");
+      return null;
+    }
+    else return (Thing) s.pop();
+  }
+
+  /** compute the solution to this variable's postfix formula */
+  private Thing compute(Postfix formula) {
+    if (formula.tokens == null) return null;
+    int len = formula.tokens.length;
+    Stack stack = new Stack();
+    for (int i=0; i<len; i++) {
+      String token = formula.tokens[i];
+      String op = "\"" + token + "\"";
+      int code = formula.codes[i];
+      if (code == Postfix.BINARY) {
+        Object[] o = new Object[2];
+        o[1] = popStack(stack);
+        o[0] = popStack(stack);
+        Thing ans = null;
+        if (o[0] != null && o[1] != null) {
+          for (int j=0; j<fm.bMethods.length; j++) {
+            // support for overloaded operators
+            if (ans == null && fm.bOps[j].equals(token)) {
+              try {
+                ans = (Thing) FormulaUtil.invokeMethod(fm.bMethods[j], o);
+              }
+              catch (IllegalAccessException exc) {
+                if (DEBUG) exc.printStackTrace();
+                evalError(
+                  "Cannot access binary method for operator " + op, exc);
+              } // no access
+              catch (IllegalArgumentException exc) {
+                if (DEBUG) exc.printStackTrace();
+                evalError(
+                  "Invalid argument to binary method for operator " + op, exc);
+              } // wrong type of method
+              catch (InvocationTargetException exc) {
+                Throwable t = exc.getTargetException();
+                if (DEBUG) t.printStackTrace();
+                evalError("Binary method for operator " + op +
+                  " threw an exception", t);
+              } // method threw exception
+            }
+          }
+        }
+        if (ans == null) {
+          evalError("Could not evaluate binary operator " + op);
+          stack.push(null);
+        }
+        else stack.push(ans);
+      }
+      else if (code == Postfix.UNARY) {
+        Object[] o = new Object[1];
+        o[0] = popStack(stack);
+        Thing ans = null;
+        if (o[0] != null) {
+          for (int j=0; j<fm.uMethods.length; j++) {
+            // support for overloaded operators
+            if (ans == null && fm.uOps[j].equals(token)) {
+              try {
+                ans = (Thing) FormulaUtil.invokeMethod(fm.uMethods[j], o);
+              }
+              catch (IllegalAccessException exc) {
+                if (DEBUG) exc.printStackTrace();
+                evalError(
+                  "Cannot access unary method for operator " + op, exc);
+              } // no access
+              catch (IllegalArgumentException exc) {
+                if (DEBUG) exc.printStackTrace();
+                evalError(
+                  "Invalid argument to unary method for operator " + op, exc);
+              } // wrong type of method
+              catch (InvocationTargetException exc) {
+                Throwable t = exc.getTargetException();
+                if (DEBUG) t.printStackTrace();
+                evalError("Unary method for operator " + op +
+                  " threw an exception", t);
+              } // method threw exception
+            }
+          }
+        }
+        if (ans == null) {
+          evalError("Could not evaluate unary operator " + op);
+          stack.push(null);
+        }
+        else stack.push(ans);
+      }
+      else if (code == Postfix.FUNC) {
+        Thing ans = null;
+        if (fm.isFunction(token)) {
+          // defined function - token is the function name
+          int num = -1;
+          try {
+            Real r = (Real) popStack(stack);
+            num = (int) r.getValue();
+          }
+          catch (ClassCastException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+          if (num < 0) {
+            evalError("Syntax error in formula (invalid function arg length)");
+            num = 1;
+          }
+          Object[] o;
+          if (num > 0) o = new Object[num];
+          else o = null;
+          boolean eflag = false;
+          for (int j=num-1; j>=0; j--) {
+            o[j] = popStack(stack);
+            if (o[j] == null) eflag = true;
+          }
+          if (!eflag) {
+            for (int j=0; j<fm.funcs.length; j++) {
+              // support for overloaded defined functions
+              if (ans == null && fm.funcs[j].equalsIgnoreCase(token)) {
+                try {
+                  ans = (Thing) FormulaUtil.invokeMethod(fm.fMethods[j], o);
+                }
+                catch (IllegalAccessException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                  evalError("Cannot access method for function " + op, exc);
+                } // no access
+                catch (IllegalArgumentException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                  evalError(
+                    "Invalid argument to method for function " + op, exc);
+                } // wrong type of method
+                catch (InvocationTargetException exc) {
+                  Throwable t = exc.getTargetException();
+                  if (DEBUG) t.printStackTrace();
+                  evalError(
+                    "Method for function " + op + " threw an exception", t);
+                } // method threw exception
+              }
+            }
+          }
+          if (ans == null) {
+            evalError("Could not evaluate function " + op);
+            stack.push(null);
+          }
+          else stack.push(ans);
+        }
+        else {
+          // implicit function - token is a non-negative integer
+          int num = 0;
+          try {
+            num = Integer.parseInt(token) + 1;
+          }
+          catch (NumberFormatException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+          if (num <= 0) {
+            evalError("Syntax error in formula (invalid implicit arg length)");
+            num = 1;
+          }
+          Object[] o = new Object[num];
+          boolean eflag = false;
+          for (int j=num-1; j>=0; j--) {
+            o[j] = popStack(stack);
+            if (o[j] == null) eflag = true;
+          }
+          if (!eflag) {
+            for (int j=0; j<fm.iMethods.length; j++) {
+              // support for overloaded implicit functions
+              if (ans == null) {
+                try {
+                  ans = (Thing) FormulaUtil.invokeMethod(fm.iMethods[j], o);
+                }
+                catch (IllegalAccessException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                  evalError("Cannot access method for implicit function", exc);
+                } // no access
+                catch (IllegalArgumentException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                  evalError(
+                    "Invalid argument to method for implicit function", exc);
+                } // wrong type of method
+                catch (InvocationTargetException exc) {
+                  Throwable t = exc.getTargetException();
+                  if (DEBUG) t.printStackTrace();
+                  evalError(
+                    "Method for implicit function threw an exception", t);
+                } // method threw exception
+              }
+            }
+          }
+          if (ans == null) {
+            evalError("Could not evaluate implicit function");
+            stack.push(null);
+          }
+          else stack.push(ans);
+        }
+      }
+      else { // code == Postfix.OTHER
+        Double d = null;
+        try {
+          d = Double.valueOf(token);
+        }
+        catch (NumberFormatException exc) { }
+        if (d == null) {
+          // token is a variable name
+          FormulaVar v = null;
+          try {
+            v = fm.getVarByNameOrCreate(token);
+          }
+          catch (FormulaException exc) {
+            evalError(op + " is an illegal variable name");
+            stack.push(null);
+          }
+          catch (VisADException exc) {
+            evalError("Internal error", exc);
+            stack.push(null);
+          }
+          if (v != null) {
+            ThingReference r = v.getReference();
+            Thing t = null;
+            if (r != null) {
+              try {
+                t = r.getThing();
+              }
+              catch (VisADException exc) {
+                if (DEBUG) exc.printStackTrace();
+              }
+              catch (RemoteException exc) {
+                if (DEBUG) exc.printStackTrace();
+              }
+            }
+            if (t == null) {
+              evalError("Variable " + op + " has no value");
+              stack.push(null);
+            }
+            else stack.push(t);
+          }
+        }
+        else {
+          // token is a constant
+          if (code == Postfix.OTHER) {
+            // convert constant to Real object with "CONSTANT" RealType
+            stack.push(new Real(CONSTANT, d.doubleValue()));
+          }
+          else {
+            // constant is a function counter
+            stack.push(new Real(d.doubleValue()));
+          }
+        }
+      }
+    }
+
+    // return the final answer
+    Thing answer = popStack(stack);
+    if (!stack.empty()) {
+      evalError("Syntax error in formula (leftover objects on stack)");
+    }
+    // return answer in local form
+    if (answer instanceof Data) {
+      try {
+        answer = ((Data) answer).local();
+        // remove "constant" tag if final answer is a constant
+        if (answer instanceof Real && ((Real) answer).getType() == CONSTANT) {
+          answer = new Real(((Real) answer).getValue());
+        }
+      }
+      catch (VisADException exc) {
+        evalError("The answer could not be converted to local data");
+        answer = null;
+      }
+      catch (RemoteException exc) {
+        evalError("The answer could not be converted to local data (remote)");
+        answer = null;
+      }
+    }
+    return answer;
+  }
+
+}
+
diff --git a/visad/formula/Postfix.java b/visad/formula/Postfix.java
new file mode 100644
index 0000000..7cbc87d
--- /dev/null
+++ b/visad/formula/Postfix.java
@@ -0,0 +1,352 @@
+//
+// Postfix.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.formula;
+
+import java.util.*;
+
+/** represents a formula in postfix notation.<P> */
+class Postfix {
+
+  /** code for binary operator */
+  static final int BINARY = 0;
+
+  /** code for unary operator */
+  static final int UNARY = 1;
+
+  /** code for function name */
+  static final int FUNC = 2;
+
+  /** code for constant that represents number of function arguments */
+  static final int FUNCCONST = 3;
+
+  /** code for variable, constant, or other */
+  static final int OTHER = 4;
+
+  /** String representation of an implicit function */
+  private static final String IMPLICIT = " ";
+
+  /** postfix tokens */
+  String[] tokens = null;
+
+  /** postfix codes representing token types */
+  int[] codes = null;
+
+  /** construct a Postfix object by converting infix formula */
+  Postfix(String formula, FormulaManager fm) throws FormulaException {
+    // convert expression to postfix notation
+    String[] postfix = null;
+    int[] pfixcode = null;
+    String infix;
+
+    // convert string to char array
+    char[] charStr = formula.toCharArray();
+
+    // remove spaces and check parentheses
+    int numSpaces = 0;
+    int paren = 0;
+    for (int i=0; i<charStr.length; i++) {
+      if (charStr[i] == ' ') numSpaces++;
+      if (charStr[i] == '(') paren++;
+      if (charStr[i] == ')') paren--;
+      if (paren < 0) {
+        throw new FormulaException("Unable to convert to postfix notation: " +
+                                   "illegal placement of parentheses");
+      }
+    }
+    if (paren != 0) {
+      throw new FormulaException("Unable to convert to postfix notation: " +
+                                 "parentheses are mismatched!");
+    }
+    int j = 0;
+    int newlen = charStr.length - numSpaces;
+    if (newlen == 0) return;
+    char[] exp = new char[newlen];
+    for (int i=0; i<charStr.length; i++) {
+      if (charStr[i] != ' ') exp[j++] = charStr[i];
+    }
+    infix = new String(exp);
+
+    // tokenize string
+    String ops = "(,)";
+    for (int i=0; i<fm.uOps.length; i++) ops = ops + fm.uOps[i];
+    for (int i=0; i<fm.bOps.length; i++) ops = ops + fm.bOps[i];
+    StringTokenizer tokenizer = new StringTokenizer(infix, ops, true);
+    int numTokens = tokenizer.countTokens();
+
+    // set up stacks
+    String[] funcStack = new String[numTokens];    // function stack
+    String[] opStack = new String[numTokens];      // operator stack
+    int[] opCodes = new int[numTokens];            // operator code stack
+    String[] pfix = new String[numTokens];         // final postfix ordering
+    int[] pcode = new int[numTokens];              // final postfix codes
+    int opPt = 0;                                  // pointer into opStack
+    int funcPt = 0;                                // pointer into funcStack
+    int pfixlen = 0;                               // pointer into pfix
+
+    // flag for detecting unary operators
+    boolean unary = true;
+
+    // flag for detecting no-argument functions (e.g., x())
+    boolean zero = false;
+
+    // flag for detecting floating point numbers
+    boolean numeral = false;
+
+    // convert to postfix
+    String ntoken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+    String token = ntoken;
+    while (token != null) {
+      ntoken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+
+      if (token.equals(")")) {
+        // right paren - pop ops until left paren reached (inclusive)
+        if (opPt < 1) {
+          throw new FormulaException("Unable to convert to postfix " +
+                                     "notation: operator stack " +
+                                     "unexpectedly empty");
+        }
+        int opcode = opCodes[--opPt];
+        String op = opStack[opPt];
+        while (!op.equals("(")) {
+          pcode[pfixlen] = opcode;
+          pfix[pfixlen++] = "" + op;
+          if (opPt < 1) {
+            throw new FormulaException("Unable to convert to postfix " +
+                                       "notation: operator stack " +
+                                       "unexpectedly empty");
+          }
+          opcode = opCodes[opPt-1];
+          op = opStack[--opPt];
+        }
+        if (opcode == FUNC) {
+          if (funcPt < 1) {
+            throw new FormulaException("Unable to convert to postfix " +
+                                       "notation: function stack " +
+                                       "unexpectedly empty");
+          }
+          String f = funcStack[--funcPt];
+          boolean implicit;
+          if (zero) {
+            implicit = f.equals(IMPLICIT);
+            pcode[pfixlen] = implicit ? FUNC : FUNCCONST;
+            pfix[pfixlen++] = "0";
+          }
+          else {
+            int n = 1;
+            while (f.equals(",")) {
+              n++;
+              if (funcPt < 1) {
+                throw new FormulaException("Unable to convert to postfix " +
+                                           "notation: function stack " +
+                                           "unexpectedly empty");
+              }
+              f = funcStack[--funcPt];
+            }
+            implicit = f.equals(IMPLICIT);
+            pcode[pfixlen] = implicit ? FUNC : FUNCCONST;
+            pfix[pfixlen++] = "" + n;
+          }
+          if (!implicit) {
+            pcode[pfixlen] = FUNC;
+            pfix[pfixlen++] = f;
+          }
+        }
+        unary = false;
+        zero = false;
+        numeral = false;
+      }
+      if (token.equals("(")) {
+        // left paren - push onto operator stack
+        opCodes[opPt] = OTHER;
+        opStack[opPt++] = "(";
+        unary = true;
+        zero = false;
+        numeral = false;
+      }
+      else if (token.equals(",")) {
+        // comma - pop ops until left paren reached (exclusive), push comma
+        if (opPt < 1) {
+          throw new FormulaException("Unable to convert to postfix " +
+                                     "notation: operator stack " +
+                                     "unexpectedly empty");
+        }
+        int opcode = opCodes[opPt-1];
+        String op = opStack[opPt-1];
+        while (!op.equals("(")) {
+          pcode[pfixlen] = opcode;
+          pfix[pfixlen++] = "" + op;
+          opPt--;
+          if (opPt < 1) {
+            throw new FormulaException("Unable to convert to postfix " +
+                                       "notation: operator stack " +
+                                       "unexpectedly empty");
+          }
+          opcode = opCodes[opPt-1];
+          op = opStack[opPt-1];
+        }
+        funcStack[funcPt++] = ",";
+        unary = true;
+        zero = false;
+        numeral = false;
+      }
+      else if ((unary && fm.isUnaryOp(token)) || fm.isBinaryOp(token)) {
+        int num = -1;
+        if (numeral && token.equals(".") && ntoken != null) {
+          // special case for detecting floating point numbers
+          try {
+            num = Integer.parseInt(ntoken);
+          }
+          catch (NumberFormatException exc) { }
+        }
+        if (num > 0) {
+          pfix[pfixlen-1] = pfix[pfixlen-1] + "." + ntoken;
+          token = ntoken;
+          ntoken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+          unary = false;
+          zero = false;
+          numeral = false;
+        }
+        else {
+          // operator - pop ops with higher precedence, push op
+          boolean isUnary = (unary && fm.isUnaryOp(token));
+          int prec = (isUnary ? fm.getUnaryPrec(token)
+                              : fm.getBinaryPrec(token));
+          String sop;
+          int scode;
+          if (opPt < 1) {
+            sop = null;
+            scode = 0;
+          }
+          else {
+            sop = opStack[opPt-1];
+            scode = opCodes[opPt-1];
+          }
+          while (sop != null &&
+                 prec >= (scode == UNARY ? fm.getUnaryPrec(sop)
+                                         : fm.getBinaryPrec(sop))) {
+            opPt--;
+            pcode[pfixlen] = scode;
+            pfix[pfixlen++] = "" + sop;
+            if (opPt < 1) {
+              sop = null;
+              scode = 0;
+            }
+            else {
+              sop = opStack[opPt-1];
+              scode = opCodes[opPt-1];
+            }
+          }
+          opCodes[opPt] = (isUnary ? UNARY : BINARY);
+          opStack[opPt++] = token;
+          unary = true;
+          zero = false;
+          numeral = false;
+        }
+      }
+      else if (ntoken != null && ntoken.equals("(")) {
+        // function - push function name and left paren
+        if (fm.isFunction(token)) funcStack[funcPt++] = token;
+        else {
+          // implicit function - append token to postfix expression
+          funcStack[funcPt++] = IMPLICIT;
+          if (!token.equals(")")) {
+            pcode[pfixlen] = OTHER;
+            pfix[pfixlen++] = token;
+          }
+          // pop ops with higher precedence
+          String sop;
+          int scode;
+          if (opPt < 1) {
+            sop = null;
+            scode = 0;
+          }
+          else {
+            sop = opStack[opPt-1];
+            scode = opCodes[opPt-1];
+          }
+          while (sop != null &&
+                 fm.iPrec >= (scode == UNARY ? fm.getUnaryPrec(sop)
+                                             : fm.getBinaryPrec(sop))) {
+            opPt--;
+            pcode[pfixlen] = scode;
+            pfix[pfixlen++] = "" + sop;
+            if (opPt < 1) {
+              sop = null;
+              scode = 0;
+            }
+            else {
+              sop = opStack[opPt-1];
+              scode = opCodes[opPt-1];
+            }
+          }
+        }
+        opCodes[opPt] = FUNC;
+        opStack[opPt++] = "(";
+        token = ntoken;
+        ntoken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
+        unary = true;
+        zero = true;
+        numeral = false;
+      }
+      else if (!token.equals(")")) {
+        // variable - append token to postfix expression
+        pcode[pfixlen] = OTHER;
+        pfix[pfixlen++] = token;
+        unary = false;
+        zero = false;
+        try {
+          int num = Integer.parseInt(token);
+          numeral = true;
+        }
+        catch (NumberFormatException exc) {
+          numeral = false;
+        }
+      }
+
+      token = ntoken;
+    }
+    // pop remaining ops from stack
+    while (opPt > 0) {
+      pcode[pfixlen] = opCodes[opPt-1];
+      pfix[pfixlen++] = "" + opStack[--opPt];
+    }
+
+    // make sure stacks are empty
+    if (opPt != 0 || funcPt != 0) {
+      throw new FormulaException("Unable to convert to postfix notation: " +
+                                 "stacks are not empty");
+    }
+
+    // return postfix array of tokens
+    tokens = new String[pfixlen];
+    codes = new int[pfixlen];
+    System.arraycopy(pfix, 0, tokens, 0, pfixlen);
+    System.arraycopy(pcode, 0, codes, 0, pfixlen);
+  }
+
+}
+
diff --git a/visad/formula/VMethod.java b/visad/formula/VMethod.java
new file mode 100644
index 0000000..c8a7596
--- /dev/null
+++ b/visad/formula/VMethod.java
@@ -0,0 +1,48 @@
+//
+// VMethod.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.formula;
+
+import java.lang.reflect.Method;
+import visad.ThingImpl;
+
+/** Thing wrapper for java.lang.reflect.Method.<P> */
+public class VMethod extends ThingImpl {
+
+  private Method method;
+
+  /** constructor */
+  public VMethod(Method rt) {
+    method = rt;
+  }
+
+  /** return the wrapper's Method */
+  public Method getMethod() {
+    return method;
+  }
+
+}
+
diff --git a/visad/formula/VRealType.java b/visad/formula/VRealType.java
new file mode 100644
index 0000000..47e6b1c
--- /dev/null
+++ b/visad/formula/VRealType.java
@@ -0,0 +1,66 @@
+//
+// VRealType.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.formula;
+
+import java.util.Vector;
+import visad.*;
+
+/** Thing wrapper for visad.RealType.<P> */
+public class VRealType extends ThingImpl {
+
+  private static Vector realTypes = new Vector();
+
+  private RealType realType;
+
+  /** gets the VRealType corresponding to the specified RealType,
+      creating it if necessary */
+  public static VRealType get(RealType rt) {
+    synchronized (realTypes) {
+      int len = realTypes.size();
+      for (int i=0; i<len; i++) {
+        VRealType vrt = (VRealType) realTypes.elementAt(i);
+        if (vrt.getRealType() == rt) return vrt;
+      }
+      return new VRealType(rt);
+    }
+  }
+
+  /** constructor */
+  public VRealType(RealType rt) {
+    realType = rt;
+    synchronized (realTypes) {
+      realTypes.add(this);
+    }
+  }
+
+  /** return the wrapper's RealType */
+  public RealType getRealType() {
+    return realType;
+  }
+
+}
+
diff --git a/visad/formula/package.html b/visad/formula/package.html
new file mode 100644
index 0000000..7ca5ceb
--- /dev/null
+++ b/visad/formula/package.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides an interface for automatically evaluating formulas based on
+user-defined operators and functions.
+
+The VisAD formula package is accessed through the FormulaManager class.
+The constructor for the FormulaManager class takes user-defined operators,
+operator precedences, and functions as arguments.  Then, variables can be
+bound to pre-existing ThingReferences with the FormulaManager.createVar()
+method, bound to formulas with the FormulaManager.assignFormula() method,
+or set directly with the FormulaManager.setThing() method.
+
+The FormulaManager.getThing() method returns the current value of a
+variable, and the FormulaManager.remove() method deletes a variable.
+
+Variables update automatically when the variables upon which they depend
+change.  For an example of usage, see the visad.ss package.
+
+</body>
+</html>
+
diff --git a/visad/georef/EarthLocation.java b/visad/georef/EarthLocation.java
new file mode 100644
index 0000000..f82c12f
--- /dev/null
+++ b/visad/georef/EarthLocation.java
@@ -0,0 +1,50 @@
+//
+//  EarthLocation.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package  visad.georef;
+
+import visad.Real;
+
+/**
+ * Interface for specifying a point on the earth's surface in terms 
+ * of latitude, longitude and altitude above sea level.
+ */
+public interface EarthLocation extends LatLonPoint
+{
+    /**
+     * Get the latitude of this point
+     *
+     * @return  Real representing the latitude
+     */
+    Real getAltitude();
+
+    /**
+     * Get the latitude/longitude of this point as a LatLonPoint
+     *
+     * @return  LatLonPoint representing the latitude/longitude
+     */
+    LatLonPoint getLatLonPoint();
+}
diff --git a/visad/georef/EarthLocationLite.java b/visad/georef/EarthLocationLite.java
new file mode 100644
index 0000000..77b45cd
--- /dev/null
+++ b/visad/georef/EarthLocationLite.java
@@ -0,0 +1,270 @@
+// EarthLocationLite.java
+//
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+
+import visad.*;
+
+import java.rmi.RemoteException;
+
+
+/**
+ * This provides a LoCal EarthLocation that is much faster to create than the
+ * EarthLocationTuple.  Assumes units of lat/lon are degrees and Altitude
+ * is meters.
+ *
+ * @author Jeff McWhirter
+ */
+public class EarthLocationLite extends RealTuple implements EarthLocation {
+
+  /** The lat */
+  Real lat;
+
+  /** The lon */
+  Real lon;
+
+  /** The alt */
+  Real alt;
+
+  /** The LatLonPoint */
+  RealTuple latlon;
+
+  /** Holds the components as we create them */
+  Data[] components;
+
+
+  /**
+   * Construct a new EarthLocationLite
+   *
+   * @param lat latitude
+   * @param lon longitude
+   * @param alt altitude
+   *
+   * @throws VisADException On badness
+   */
+  public EarthLocationLite(double lat, double lon, double alt)
+          throws VisADException {
+    this(new Real(RealType.Latitude, lat), new Real(RealType.Longitude, lon),
+         new Real(RealType.Altitude, alt));
+  }
+
+
+  /**
+   * Construct a new EarthLocationLite
+   * @param lat latitude
+   * @param lon longitude
+   * @param alt altitude
+   */
+  public EarthLocationLite(Real lat, Real lon, Real alt) {
+    super(RealTupleType.LatitudeLongitudeAltitude);
+    this.lat = lat;
+    this.lon = lon;
+    this.alt = alt;
+  }
+
+
+  /**
+   * is missing
+   *
+   * @return is missing
+   */
+  public boolean isMissing() {
+    return lat.isMissing() || lon.isMissing() || alt.isMissing();
+  }
+
+  /**
+   * get latitude
+   *
+   * @return latitude
+   */
+  public Real getLatitude() {
+    return lat;
+  }
+
+  /**
+   * get longitude
+   *
+   * @return longitude
+   */
+  public Real getLongitude() {
+    return lon;
+  }
+
+  /**
+   * get altitude
+   *
+   * @return altitude
+   */
+  public Real getAltitude() {
+    return alt;
+  }
+
+  /**
+   * This is an EarthLocation interface method. It just a LatLonTuple
+   * made from getLatitude() and getLongitude();
+   *
+   * @return this
+   */
+  public LatLonPoint getLatLonPoint() {
+    if (latlon == null) {
+      try {
+        latlon = new LatLonTuple(lat, lon);
+      } catch (Exception e) {  // shouldn't happen
+	  latlon = this;
+	  throw new RuntimeException(e);      
+      }
+    }
+    return (LatLonPoint) latlon;
+  }
+
+  /**
+   * Get the i'th component.
+   *
+   * @param i Which one
+   *
+   * @return The component
+   *
+   * @throws RemoteException On badness
+   * @throws VisADException On badness
+   */
+  public Data getComponent(int i) throws VisADException, RemoteException {
+    if (i == 0) {
+      return lat;
+    }
+
+    if (i == 1) {
+      return lon;
+    }
+
+    if (i == 2) {
+      return alt;
+    }
+
+    throw new IllegalArgumentException("Wrong component number:"+i);
+  }
+
+
+
+  /**
+   * Create, if needed, and return the component array.
+   *
+   * @return components
+   */
+  public Data[] getComponents(boolean copy) {
+    //Create the array and populate it if needed
+    if (components == null) {
+	Data []tmp = new Data[getDimension()];
+	tmp[0] = lat;
+	tmp[1] = lon;
+	tmp[2] = alt;
+	components = tmp;
+    }
+    return components;
+  }
+
+
+  /**
+   * Indicates if this Tuple is identical to an object.
+   *
+   * @param obj         The object.
+   * @return            <code>true</code> if and only if the object is
+   *                    a Tuple and both Tuple-s have identical component
+   *                    sequences.
+   */
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (!(obj instanceof EarthLocationLite)) {
+      return false;
+    }
+
+    EarthLocationLite that = (EarthLocationLite) obj;
+
+    return lat.equals(that.lat) && lon.equals(that.lon)
+           && alt.equals(that.alt);
+  }
+
+
+  /**
+   * Returns the hash code of this object.
+   * @return            The hash code of this object.
+   */
+  public int hashCode() {
+    return lat.hashCode() ^ lon.hashCode() & alt.hashCode();
+  }
+
+
+  /**
+   * to string
+   *
+   * @return string of me
+   */
+  public String toString() {
+    return getLatitude()+" "+getLongitude()+" "+getAltitude();
+  }
+
+  /**
+   * run 'java ucar.visad.EarthLocationLite' to test the RealTuple class.
+   * This does a performance comparison of creating this object and the
+   * EarthLocationTuple
+   *
+   * @param args  ignored
+   *
+   * @throws RemoteException  Java RMI problem
+   * @throws VisADException   Unable to create the VisAD objects
+   */
+  public static void main(String args[])
+          throws VisADException, RemoteException {
+    Real lat = new Real(RealType.Latitude);
+    Real lon = new Real(RealType.Longitude);
+    Real alt = new Real(RealType.Altitude);
+    for (int j = 0; j < 10; j++) {
+      long t1 = System.currentTimeMillis();
+      for (int i = 0; i < 100000; i++) {
+        EarthLocationTuple elt = new EarthLocationTuple(lat, lon, alt);
+      }
+
+      long t2 = System.currentTimeMillis();
+
+      long t3 = System.currentTimeMillis();
+      for (int i = 0; i < 100000; i++) {
+        EarthLocationLite elt = new EarthLocationLite(lat, lon, alt);
+      }
+
+      long t4 = System.currentTimeMillis();
+      System.err.println("time EathLocationTuple:"+(t2-t1)+
+                         " EarthLocationLite:"+(t4-t3));
+    }
+
+
+
+  }
+
+}
+
diff --git a/visad/georef/EarthLocationTuple.java b/visad/georef/EarthLocationTuple.java
new file mode 100644
index 0000000..fc4d89b
--- /dev/null
+++ b/visad/georef/EarthLocationTuple.java
@@ -0,0 +1,238 @@
+//
+//  EarthLocationTuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+import java.rmi.RemoteException;
+
+/**
+ * RealTuple implementation of EarthLocation for representing a 
+ * location on the earth's surface in terms of latitude,  longitude 
+ * and altitude above sea level.  In this implementation, the RealTuple
+ * is (latitude, longitude, altitude) and has a MathType of
+ * RealTupleType.LatitudeLongitudeAltitude.
+ *
+ * @author  Don Murray, Unidata
+ */
+public class EarthLocationTuple extends RealTuple
+    implements EarthLocation
+{
+
+    LatLonTuple latlon;
+    Real alt;
+
+    /* Default units (degree, degree, meter) */
+    public static final Unit[] DEFAULT_UNITS = 
+      new Unit[] {CommonUnit.degree, CommonUnit.degree, CommonUnit.meter};
+
+    /**
+     * Construct an EarthLocationTuple with missing values
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public EarthLocationTuple()
+        throws VisADException, RemoteException
+    {
+        this(Double.NaN, Double.NaN, Double.NaN);
+    }
+
+    /**
+     * Construct an EarthLocationTuple from Reals of lat, lon, alt
+     *
+     * @param  lat   Real representing the latitude
+     * @param  lon   Real representing the longitude
+     * @param  alt   Real representing the altitude
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public EarthLocationTuple(Real lat, Real lon, Real alt)
+        throws VisADException, RemoteException
+    {
+        this(lat, lon, alt, (Unit[]) null, true);
+    }
+
+    /**
+     * Construct an EarthLocationTuple from Reals of lat, lon, alt
+     *
+     * @param  lat   Real representing the latitude
+     * @param  lon   Real representing the longitude
+     * @param  alt   Real representing the altitude
+     * @param  units   array of Units.  Must be same as Real units or null
+     * @param  checkUnits   true if should check the units
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public EarthLocationTuple(Real lat, Real lon, Real alt, Unit[] units, boolean checkUnits)
+        throws VisADException, RemoteException
+    {
+        this(lat, lon, alt, units, checkUnits, false);
+    }
+
+    /**
+     * Trusted Construct an EarthLocationTuple from Reals of lat, lon, alt
+     *
+     * @param  lat   Real representing the latitude
+     * @param  lon   Real representing the longitude
+     * @param  alt   Real representing the altitude
+     * @param  units   array of Units.  Must be same as Real units or null
+     * @param  checkUnits   true if should check the units
+     * @param  useLLTUnits   true to use the LatLonTuple units 
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    EarthLocationTuple(Real lat, Real lon, Real alt, Unit[] units, boolean checkUnits, boolean useLLTUnits)
+        throws VisADException, RemoteException
+    {
+        super(RealTupleType.LatitudeLongitudeAltitude,
+              new Real[] {lat, lon, alt}, 
+              (CoordinateSystem) null, units, checkUnits);
+        latlon = (useLLTUnits)
+            ? new LatLonTuple(lat, lon, LatLonTuple.DEFAULT_UNITS, checkUnits)
+            : new LatLonTuple(lat, lon);
+        this.alt = alt;
+    }
+
+    /**
+     * Construct an EarthLocationTuple from double values of lat, lon, alt
+     *
+     * @param  lat   latitude (degrees North positive)
+     * @param  lon   longitude (degrees East positive)
+     * @param  alt   altitude (meters above sea level)
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public EarthLocationTuple(double lat, double lon, double alt)
+        throws VisADException, RemoteException
+    {
+        this(new Real(RealType.Latitude, lat),
+             new Real(RealType.Longitude, lon),
+             new Real(RealType.Altitude, alt), 
+             DEFAULT_UNITS, false, true);
+    }
+
+    /**
+     * Construct an EarthLocationTuple from a LatLonPoint and an altitude
+     *
+     * @param  latlon   LatLonPoint
+     * @param  alt      Real representing the altitude
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public EarthLocationTuple(LatLonPoint latlon, Real alt)
+        throws VisADException, RemoteException
+    {
+        this(latlon.getLatitude(), latlon.getLongitude(), alt);
+    }
+
+    /**
+     * Get the latitude of this location
+     *
+     * @return  Real representing the latitude
+     */
+    public Real getLatitude()
+    {
+        return latlon.getLatitude();
+    }
+
+    /**
+     * Get the longitude of this location
+     *
+     * @return  Real representing the longitude
+     */
+    public Real getLongitude()
+    {
+        return latlon.getLongitude();
+    }
+
+    /**
+     * Get the altitude of this location
+     *
+     * @return  Real representing the altitude
+     */
+    public Real getAltitude()
+    {
+        return alt;
+    }
+
+    /**
+     * Get the lat/lon of this location as a LatLonPoint
+     *
+     * @return  location of this point.
+     */
+    public LatLonPoint getLatLonPoint()
+    {
+        return (LatLonPoint) latlon;
+    }
+
+    /*   Uncomment to test
+    public static void main (String[] args)
+        throws VisADException, RemoteException
+    {
+        double lat = 40.1;
+        double lon = -105.5;
+        double alt = 1660.0;
+        double newLat = 
+            (args.length > 0) ? new Double(args[0]).doubleValue() : lat;
+        double newLon = 
+            (args.length > 1) ? new Double(args[1]).doubleValue() : lon;
+        double newAlt = 
+            (args.length > 2) ? new Double(args[2]).doubleValue() : alt;
+
+        EarthLocationTuple elt = new EarthLocationTuple(lat, lon, alt);
+        System.out.println("EarthLocation 1 = " + elt);
+
+        EarthLocationTuple newelt = 
+            new EarthLocationTuple(newLat, newLon, newAlt);
+        System.out.println("EarthLocation 2 = " + newelt);
+
+        System.out.println("Points are " +
+                           (elt.equals(newelt) ? "" : "NOT ")  +
+                           "equal");
+
+    }
+    */
+
+    public String toString() {
+       StringBuffer buf = new StringBuffer();
+       buf.append(latlon.toString());
+       buf.append(" Alt: ");
+       try {
+         buf.append(
+           visad.browser.Convert.shortString(alt.getValue(CommonUnit.meter)));
+       } catch (VisADException ve) {
+         buf.append(
+           visad.browser.Convert.shortString(alt.getValue()));
+       }
+       return buf.toString();
+    }
+}
diff --git a/visad/georef/LatLonPoint.java b/visad/georef/LatLonPoint.java
new file mode 100644
index 0000000..a775e85
--- /dev/null
+++ b/visad/georef/LatLonPoint.java
@@ -0,0 +1,60 @@
+//
+//  LatLonPoint.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.Real;
+import visad.Data;
+
+/**
+ * Interface for supporting latitude/longitude points.
+ */
+public interface LatLonPoint extends Data
+{
+
+    /**
+     * Get the latitude of this point
+     *
+     * @return  Real representing the latitude
+     */
+    Real getLatitude();
+
+    /**
+     * Get the longitude of this point
+     *
+     * @return  Real representing the longitude
+     */
+    Real getLongitude();
+
+    /**
+     * See if this LatLonPoint is equal to the object in question.
+     * Two points are equal if they are the same object, or if their
+     * lat/lon componets are equal.
+     *
+     * @param  obj  object in question
+     */
+    boolean equals(Object obj);
+}
diff --git a/visad/georef/LatLonTuple.java b/visad/georef/LatLonTuple.java
new file mode 100644
index 0000000..affe063
--- /dev/null
+++ b/visad/georef/LatLonTuple.java
@@ -0,0 +1,181 @@
+//
+//  LatLonTuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+import java.rmi.RemoteException;
+
+/**
+ * RealTuple implementation of LatLonPoint for defining lat/lon points
+ */
+public class LatLonTuple extends RealTuple
+    implements LatLonPoint
+{
+    private Real lat;
+    private Real lon;
+
+    /* Default units (degree, degree, meter) */
+    public static final Unit[] DEFAULT_UNITS = 
+      new Unit[] {CommonUnit.degree, CommonUnit.degree};
+
+    /**
+     * Construct a LatLonTuple with missing values
+     *
+     * @throws  VisADException   couldn't create the necessary VisAD object
+     * @throws  RemoteException  couldn't create the necessary remote object
+     */
+    public LatLonTuple()
+        throws VisADException, RemoteException
+    {
+        this(Double.NaN, Double.NaN);
+    }
+
+    /**
+     * Construct a LatLonTuple from double values of latitude and
+     * longitude.
+     *
+     * @param  lat  latitude (degrees North positive)
+     * @param  lon  longitude (degrees East positive)
+     *
+     * @throws  VisADException   couldn't create the necessary VisAD object
+     * @throws  RemoteException  couldn't create the necessary remote object
+     */
+    public LatLonTuple(double lat, double lon)
+        throws VisADException, RemoteException
+    {
+        this(new Real(RealType.Latitude, lat),
+             new Real(RealType.Longitude, lon),
+             DEFAULT_UNITS, false);
+    }
+
+    /**
+     * Construct a LatLonTuple from Reals representing the latitude and
+     * longitude.
+     *
+     * @param  lat  Real representing latitude 
+     *              (must have MathType RealType.Latitude)
+     * @param  lon  Real representing longitude 
+     *              (must have MathType RealType.Longitude)
+     *
+     * @throws  VisADException   couldn't create the necessary VisAD object
+     * @throws  RemoteException  couldn't create the necessary remote object
+     */
+    public LatLonTuple(Real lat, Real lon)
+        throws VisADException, RemoteException
+    {
+        this( lat, lon, (Unit[]) null, true);
+    }
+
+    /**
+     * Construct a LatLonTuple from Reals representing the latitude and
+     * longitude.
+     *
+     * @param  lat  Real representing latitude 
+     *              (must have MathType RealType.Latitude)
+     * @param  lon  Real representing longitude 
+     *              (must have MathType RealType.Longitude)
+     * @param  units  units for the reals (can be null)
+     * @param  checkUnits  true to make sure units is convertible with lat/lon
+     *
+     * @throws  VisADException   couldn't create the necessary VisAD object
+     * @throws  RemoteException  couldn't create the necessary remote object
+     */
+    public LatLonTuple(Real lat, Real lon, Unit[] units, boolean checkUnits)
+        throws VisADException, RemoteException
+    {
+        super( RealTupleType.LatitudeLongitudeTuple,
+              new Real[] { lat, lon}, 
+              (CoordinateSystem) null, units, checkUnits);
+        this.lat = lat;
+        this.lon = lon;
+    }
+
+    /**
+     * Get the latitude of this point
+     *
+     * @return  Real representing the latitude
+     */
+    public Real getLatitude()
+    {
+        return lat;
+    }
+
+    /**
+     * Get the longitude of this point
+     *
+     * @return  Real representing the longitude
+     */
+    public Real getLongitude()
+    {
+        return lon;
+    }
+
+
+    public String toString() {
+       StringBuffer buf = new StringBuffer();
+       buf.append("Lat: ");
+       try {
+         buf.append(
+           visad.browser.Convert.shortString(lat.getValue(CommonUnit.degree)));
+       } catch (VisADException ve) {
+         buf.append(
+           visad.browser.Convert.shortString(lat.getValue()));
+       }
+       buf.append(" Lon: ");
+       try {
+         buf.append(
+           visad.browser.Convert.shortString(lon.getValue(CommonUnit.degree)));
+       } catch (VisADException ve) {
+         buf.append(
+           visad.browser.Convert.shortString(lon.getValue()));
+       }
+       return buf.toString();
+    }
+
+    /* uncomment to test 
+    public static void main(String[] args)
+        throws VisADException, RemoteException
+    {
+        double lat = 40.1;
+        double lon = -105.5;
+        double newLat = 
+            (args.length > 0) ? new Double(args[0]).doubleValue() : lat;
+        double newLon = 
+            (args.length > 1) ? new Double(args[1]).doubleValue() : lon;
+
+        LatLonTuple ll = new LatLonTuple(lat, lon);
+        System.out.println("Point 1 = " + ll);
+
+        LatLonTuple newll = new LatLonTuple(newLat, newLon);
+        System.out.println("Point 2 = " + newll);
+
+        System.out.println("Points are " +
+                           (ll.equals(newll) ? "" : "NOT ")  +
+                           "equal");
+    }
+    */
+}
diff --git a/visad/georef/LongitudeLatitudeInterpCS.java b/visad/georef/LongitudeLatitudeInterpCS.java
new file mode 100644
index 0000000..7d496f5
--- /dev/null
+++ b/visad/georef/LongitudeLatitudeInterpCS.java
@@ -0,0 +1,196 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.CoordinateSystem;
+import visad.GridCoordinateSystem;
+import visad.VisADException;
+import visad.SetType;
+import visad.RealTupleType;
+import visad.Linear2DSet;
+import visad.Gridded2DSet;
+import visad.Linear1DSet;
+import visad.Unit;
+import visad.Set;
+import visad.georef.MapProjection;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * For 2D arrays of earth observations when the navigation is not provided analytically,
+ * but a set of navigated points is given to interpolate.  Particularly useful for
+ * polar swath observations with point-by-point navigation. 
+ */
+public class LongitudeLatitudeInterpCS extends MapProjection {
+
+   Linear2DSet domainSet;   // The CoordinateSystem
+   Linear2DSet transSet;    // Set to transform between domainSet and lonlatSet
+   Gridded2DSet lonlatSet;  // Set of earth locations (the Reference, to be interpolated)
+
+   //- assumes incoming GriddedSet is (longitude,latitude) with range (-180,+180)
+   boolean neg180pos180 = true;  //false: longitude range (0,+360)
+   
+   boolean extNeg180pos180 = false; 
+
+   int lonIdx = 0;
+
+   /**
+    *
+    * @param domainSet  The CoordinateSystem, eg. (line, element), (Track, XTrack) 
+    *
+    * @param lonlatSet  The set of earth locations to interpolate for (x,y) to/from (lon,lat).
+    *                   May be sampled w/respect to domainSet, eg. every fifth point. 
+    *                   SetType must be SpatialEarth2DTuple or LatitudeLongitudeTuple.
+    *
+    * @param neg180pos180  The range of longitude values in the lonlatSet. Default is true,
+    *                      false is (0,+360).
+    *                   
+    * @exception VisADException  Couldn't create necessary VisAD object or reference type
+    *                            (the lonlatSet type) is not SpatialEarth2DTuple or LatitudeLongitudeTuple
+    *
+    */
+   public LongitudeLatitudeInterpCS(Linear2DSet domainSet, Gridded2DSet lonlatSet, boolean neg180pos180) throws VisADException {
+     super(((SetType)domainSet.getType()).getDomain(), null);
+     this.lonlatSet = lonlatSet;
+     this.domainSet = domainSet;
+     this.neg180pos180 = neg180pos180;
+     int[] lengths = domainSet.getLengths();
+     int[] gsetLens = lonlatSet.getLengths();
+     transSet = new Linear2DSet(0.0, gsetLens[0]-1, lengths[0],
+                                0.0, gsetLens[1]-1, lengths[1]);
+     lonIdx = getLongitudeIndex();
+   }
+
+   public LongitudeLatitudeInterpCS(Linear2DSet domainSet, Gridded2DSet lonlatSet) throws VisADException {
+     this(domainSet, lonlatSet, true);
+   }
+
+   public float[][] toReference(float[][] values) throws VisADException {
+     float[][] coords = domainSet.valueToGrid(values);
+     coords = transSet.gridToValue(coords);
+     float[][] lonlat = lonlatSet.gridToValue(coords);
+
+     if (!(neg180pos180 && extNeg180pos180)) { // if true lonRanges are same so don't do anything
+       if (neg180pos180) {
+         for (int t=0; t<lonlat[lonIdx].length; t++) {
+           if (lonlat[lonIdx][t] > 180f) {
+             lonlat[lonIdx][t] -= 360f;
+           }
+         }
+       }
+       else {
+         for (int t=0; t<lonlat[lonIdx].length; t++) {
+           if (lonlat[lonIdx][t] < 180f) {
+             lonlat[lonIdx][t] += 360f;
+           }
+         }
+       }
+     }
+
+     return lonlat;
+   }
+
+   public double[][] toReference(double[][] values) throws VisADException {
+     return Set.floatToDouble(toReference(Set.doubleToFloat(values)));
+   }
+
+   public float[][] fromReference(float[][] lonlat) throws VisADException {
+     if (!(neg180pos180 && extNeg180pos180)) { // if true lonRanges are same so don't do anything
+       if (neg180pos180) {
+         for (int t=0; t<lonlat[lonIdx].length; t++) {
+           if (lonlat[lonIdx][t] > 180f) {
+             lonlat[lonIdx][t] -= 360f;
+           }
+         }
+       } 
+       else {
+         for (int t=0; t<lonlat[lonIdx].length; t++) {
+           if (lonlat[lonIdx][t] < 180f) {
+             lonlat[lonIdx][t] += 360f;
+           }
+         }
+       }
+     }
+
+     float[][] grid_vals = lonlatSet.valueToGrid(lonlat);
+     float[][] coords = transSet.valueToGrid(grid_vals);
+     coords = domainSet.gridToValue(coords);
+     return coords;
+   }
+
+   public double[][] fromReference(double[][] lonlat) throws VisADException {
+     return Set.floatToDouble(fromReference(Set.doubleToFloat(lonlat)));
+   }
+
+   public void setExternalLongitudeRange(boolean isExtNeg180pos180) {
+     this.extNeg180pos180 = isExtNeg180pos180;
+   }
+
+   public Rectangle2D getDefaultMapArea() {
+     float[] lo = domainSet.getLow();
+     float[] hi = domainSet.getHi();
+     return new Rectangle2D.Float(lo[0], lo[1], hi[0] - lo[0], hi[1] - lo[1]);
+   }
+
+   public Linear2DSet getDomainSet() {
+     return domainSet;
+   }
+
+   public Gridded2DSet getLonLatSet() {
+     return lonlatSet;
+   }
+
+   public boolean getIsNeg180pos180() {
+     return neg180pos180;
+   }
+
+   /**
+    *  Will convert to external (incoming/outgoing) longitude 
+    *  range, ie. -180 to 180 back/forth to 0 to 360.  Default is:
+    *  external range is 0 to 360   
+    */
+   public void setIsExtNeg180pos180(boolean yesno) {
+     extNeg180pos180 = yesno;
+   }
+
+   public boolean getIsExtNeg180pos180() {
+     return extNeg180pos180;
+   }
+
+   public boolean equals(Object cs) {
+     if ( !(cs instanceof LongitudeLatitudeInterpCS)) {
+       return false;
+     }
+
+     LongitudeLatitudeInterpCS that = (LongitudeLatitudeInterpCS) cs;
+
+     // Compare sets.  Note: comparing the lonlat GriddedSet(s) could be very costly
+     if (that.getDomainSet().equals(domainSet) && that.getLonLatSet().equals(lonlatSet)) {
+       return true;
+     }
+     if ((that.getIsNeg180pos180() == neg180pos180) && (that.getIsExtNeg180pos180() == extNeg180pos180)) {
+       return true;
+     }
+
+     return false;
+   } 
+}
diff --git a/visad/georef/MapProjection.java b/visad/georef/MapProjection.java
new file mode 100644
index 0000000..16351fe
--- /dev/null
+++ b/visad/georef/MapProjection.java
@@ -0,0 +1,177 @@
+//
+// MapProjection.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+
+/**
+ * Abstract class for coordinate systems that support (lat,lon) <-> (x,y)
+ * with a reference coordinate system of (lat, lon) or (lon, lat).
+ *
+ * @author Don Murray
+ */
+public abstract class MapProjection extends NavigatedCoordinateSystem
+{
+
+  /**
+   * Constructs from the type of the reference coordinate system and 
+   * units for values in this coordinate system. The reference coordinate
+   * system must contain RealType.Latitude and RealType.Longitude only.
+   *
+   * @param reference  The type of the reference coordinate system. The
+   *                   reference must contain RealType.Latitude and
+   *                   RealType.Longitude.  Values in the reference 
+   *                   coordinate system shall be in units of 
+   *                   reference.getDefaultUnits() unless specified 
+   *                   otherwise.
+   * @param units      The default units for this coordinate system. 
+   *                   Numeric values in this coordinate system shall be 
+   *                   in units of units unless specified otherwise. 
+   *                   May be null or an array of null-s.
+   * @exception VisADException  Couldn't create necessary VisAD object or
+   *                            reference does not contain RealType.Latitude
+   *                            and/or RealType.Longitude or the reference
+   *                            dimension is greater than 2.
+   */
+  public MapProjection(RealTupleType reference, Unit[] units)
+      throws VisADException
+  {
+    super(reference, units);
+    if ( !(reference.equals(RealTupleType.LatitudeLongitudeTuple) ||
+           reference.equals(RealTupleType.SpatialEarth2DTuple)))
+      throw new CoordinateSystemException(
+        "MapProjection: Reference must be LatitudeLongitudeTuple or " +
+        "SpatialEarth2DTuple");
+  }
+
+  /**
+   * Get a reasonable bounding box in this coordinate system. MapProjections 
+   * are typically specific to an area of the world; there's no bounding 
+   * box that works for all projections so each subclass must implement
+   * this method. For example, the bounding box for a satellite image 
+   * MapProjection might have an upper left corner of (0,0) and the width 
+   * and height of the Rectangle2D would be the number of elements and 
+   * lines, respectively.
+   *
+   * @return the bounding box of the MapProjection
+   *
+   */
+  public abstract java.awt.geom.Rectangle2D getDefaultMapArea();
+
+
+  /**
+   * Determine if the input to the toReference and output from the
+   * fromReference is (x,y) or (y,x).  Subclasses should override
+   * if (y,x).
+   * @return true if (x,y)
+   */
+  public boolean isXYOrder() { return true;}
+
+  /**
+   * Determine if the fromReference and toReference expect the
+   * to get the output and input values to be row/col ordered
+   * or
+   */
+  public boolean isLatLonOrder() { return (getLatitudeIndex() == 0); }
+
+  /**
+   * Get the center lat/lon point for this MapProjection.
+   * @return the lat/lon point at the center of the projection.
+   */
+  public LatLonPoint getCenterLatLon() 
+      throws VisADException {
+    java.awt.geom.Rectangle2D rect = getDefaultMapArea();
+    return getLatLon(new double[][] { {rect.getCenterX()}, {rect.getCenterY()}});
+  }
+
+  /**
+   * Get the X index
+   * @return the index
+   */
+  public int getXIndex() {
+     return isXYOrder() ? 0 : 1;
+  }
+
+  /**
+   * Get the Y index
+   * @return the index
+   */
+  public int getYIndex() {
+     return isXYOrder() ? 1 : 0;
+  }
+
+
+  /**
+   * Get the  lat/lon point for the given xy pairs.
+   * this method will flip the order of the xy if it is not in xyorder
+   * @return the lat/lon point for the given xy pairs
+   */
+  public LatLonPoint getLatLon(double[][]xy) 
+      throws VisADException {
+      //Flip them if needed
+      if(!isXYOrder()) {
+          double tmp;
+          tmp = xy[0][0];
+          xy[0][0] = xy[0][1];
+          xy[0][1] = tmp;
+
+          tmp = xy[1][0];
+          xy[1][0] = xy[1][1];
+          xy[1][1] = tmp;
+      }
+
+
+    double[][] latlon = toReference(xy);
+    double lat = 
+      (isLatLonOrder())
+        ? CommonUnit.degree.toThis(latlon[0][0], getReferenceUnits()[0])
+        : CommonUnit.degree.toThis(latlon[1][0], getReferenceUnits()[1]);
+    double lon = 
+      (isLatLonOrder())
+        ? CommonUnit.degree.toThis(latlon[1][0], getReferenceUnits()[1])
+        : CommonUnit.degree.toThis(latlon[0][0], getReferenceUnits()[0]);
+     LatLonPoint llp = null;
+     try {
+       llp = new LatLonTuple(lat, lon);
+     } catch (java.rmi.RemoteException re) {} // can't happen
+     return llp;
+  }
+
+  /**
+   * Print out a string representation of this MapProjection
+   */
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("MapProjection: \n");
+    buf.append("  Reference = ");
+    buf.append(getReference());
+    buf.append("\n");
+    buf.append("  DefaultMapArea = ");
+    buf.append(getDefaultMapArea());
+    return buf.toString();
+  }
+}
diff --git a/visad/georef/NamedLocation.java b/visad/georef/NamedLocation.java
new file mode 100644
index 0000000..b687918
--- /dev/null
+++ b/visad/georef/NamedLocation.java
@@ -0,0 +1,53 @@
+//
+//  NamedLocation.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.Text;
+
+/**
+ *  An interface for a named earth location.
+ */
+public interface NamedLocation extends EarthLocation
+{
+    /**
+     * Return a unique identifier. This might be a Text object 
+     * representing the name of the station (e.g.: "Denver"), the ICAO 
+     * 4 letter id (e.g., "KDEN"), a WMO block and station number 
+     * as a string (e.g., "72565"), or some other identifying string 
+     * (i.e., "intersection of Mitchell and 47th" or "Point A", or "A")
+     *
+     * @return  Text whose getValue() method returns the identifier
+     */
+    Text getIdentifier();
+
+    /**
+     * Get the lat/lon/altitude as an EarthLocation.
+     *
+     * @return  EarthLocation for this object
+     */
+    EarthLocation getEarthLocation();
+}
diff --git a/visad/georef/NamedLocationTuple.java b/visad/georef/NamedLocationTuple.java
new file mode 100644
index 0000000..2985b55
--- /dev/null
+++ b/visad/georef/NamedLocationTuple.java
@@ -0,0 +1,294 @@
+//
+//  NamedLocationTuple.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+import java.rmi.RemoteException;
+
+/**
+ * <p>Tuple implementation of NamedLocation for representing a 
+ * location on the earth's surface in terms of latitude,  longitude 
+ * and altitude above sea level and some sort of identifier.  In
+ * this implementation, the Tuple has two components - the identifier
+ * and an {@link EarthLocationTuple}.</p>
+ *
+ * <p>Instances of this class are immutable.</p>
+ *
+ * @author  Don Murray, Unidata
+ */
+public class NamedLocationTuple extends Tuple
+    implements NamedLocation
+{
+    /** 
+     * TextType associated with the identifier that is returned by
+     * getIdentifier().
+     */
+    public static TextType IDENTIFIER_TYPE;
+
+    /* 
+    Instantiate the IDENTIFIER_TYPE.  Wish I could do this some other way
+    because what happens if there is an exception? 
+    */
+    static
+    {
+        try
+        {
+            IDENTIFIER_TYPE = TextType.getTextType("Identifier");
+        }
+        catch (Exception e)
+        {
+            System.err.println("NamedLocationTuple: Can't instatiate type");
+        }
+    }
+        
+    public NamedLocationTuple() 
+        throws VisADException, RemoteException
+    {
+        this("", Double.NaN, Double.NaN, Double.NaN);
+    }
+
+    /**
+     * Construct an NamedLocationTuple from a Text and Reals of Latitude,
+     * Longitude, and Altitude.
+     *
+     * @param  id	   Text representing the identifier
+     *                 (must be of type NamedLocation.IDENTIFIER_TYPE)
+     * @param  lat   Real representing the latitude
+     * @param  lon   Real representing the longitude
+     * @param  alt   Real representing the altitude
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public NamedLocationTuple(Text id, Real lat, Real lon, Real alt)
+        throws VisADException, RemoteException
+    {
+        this(id, new EarthLocationTuple(lat, lon, alt));
+    }
+
+    /**
+     * Construct an NamedLocationTuple from a Text and an EarthLocation
+     *
+     * @param  identifier   Text representing the identifier
+     *                      (must be of type NamedLocation.IDENTIFIER_TYPE)
+     * @param  location     EarthLocation
+     *
+     * @throws NullPointerException if the location is <code>null</code>.
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public NamedLocationTuple(Text identifier, EarthLocation location)
+        throws VisADException, RemoteException
+    {
+        super(new TupleType(
+                new MathType[]
+                    {IDENTIFIER_TYPE, 
+                     RealTupleType.LatitudeLongitudeAltitude}),
+              new Data[] {
+                  identifier,
+                  new EarthLocationTuple(location.getLatitude(),
+                                         location.getLongitude(),
+                                         location.getAltitude())}
+              );
+    }
+
+    /**
+     * Construct an NamedLocationTuple from an identifier and values of 
+     * lat, lon, alt
+     *
+     * @param  id    identifier
+     * @param  lat   latitude (degrees North positive)
+     * @param  lon   longitude (degrees East positive)
+     * @param  alt   altitude (meters above sea level)
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public NamedLocationTuple(String id, double lat, double lon, double alt)
+        throws VisADException, RemoteException
+    {
+        this(new Text(IDENTIFIER_TYPE, id),
+             new EarthLocationTuple(lat, lon, alt));
+    }
+
+    /**
+     * Construct an NamedLocationTuple from an identifier and an EarthLocation
+     *
+     * @param  id    identifier
+     * @param  location     EarthLocation
+     *
+     * @throws  VisADException   unable to create necessary VisAD object
+     * @throws  RemoteException  unable to create necessary remote object
+     */
+    public NamedLocationTuple(String id, EarthLocation location)
+        throws VisADException, RemoteException
+    {
+        this(new Text(IDENTIFIER_TYPE, id), location);
+    }
+
+    /**
+     * Get the latitude of this location
+     *
+     * @return  Real representing the latitude
+     */
+    public Real getLatitude()
+    {
+        return getEarthLocation().getLatitude();
+    }
+
+    /**
+     * Get the longitude of this location
+     *
+     * @return  Real representing the longitude
+     */
+    public Real getLongitude()
+    {
+        return getEarthLocation().getLongitude();
+    }
+
+    /**
+     * Get the altitude of this location
+     *
+     * @return  Real representing the altitude
+     */
+    public Real getAltitude()
+    {
+        return getEarthLocation().getAltitude();
+    }
+
+    /**
+     * Get the lat/lon of this location as a LatLonPoint
+     *
+     * @return  location of this point.
+     */
+    public LatLonPoint getLatLonPoint()
+    {
+       return getEarthLocation().getLatLonPoint();
+    }
+
+    /**
+     * Get the lat/lon/alt of this location as an EarthLocation
+     *
+     * @return  location of this point.
+     */
+    public EarthLocation getEarthLocation()
+    {
+        try {
+            return (EarthLocationTuple)getComponent(1);
+        }
+        catch (Exception ex) {
+            throw new RuntimeException("Assertion failure");
+        }
+    }
+
+    /**
+     * Return a unique identifier. This might be a Text object 
+     * representing the name of the station (e.g.: "Denver"), the ICAO 
+     * 4 letter id (e.g., "KDEN"), a WMO block and station number 
+     * as a string (e.g., "72565"), or some other identifying string 
+     * (i.e., "intersection of Mitchell and 47th" or "Point A", or "A")
+     * The TextType for this object is <CODE>IDENTIFIER_TYPE</CODE>.
+     *
+     * @return  Text whose getValue() method returns the identifier
+     */
+    public Text getIdentifier()
+    {
+        try {
+            return (Text)getComponent(0);
+        }
+        catch (Exception ex) {
+            throw new RuntimeException("Assertion failure");  // can't happen
+        }
+    }
+
+    /**
+     * Clones this instance.
+     *
+     * @return                    A clone of this instance.
+     */
+    public final Object clone() {
+      /*
+       * Steve Emmerson believes that this implementation should return 
+       * "this" to reduce the memory-footprint but Bill believes that doing so
+       * is counter-intuitive and might harm applications.
+       */
+      try {
+        return super.clone();
+      }
+      catch (CloneNotSupportedException ex) {
+        throw new RuntimeException("Assertion failure");
+      }
+    }
+
+    public String toString() {
+       StringBuffer buf = new StringBuffer();
+       buf.append("Name: ");
+       buf.append(getIdentifier().toString());
+       buf.append(" ");
+       buf.append(getEarthLocation().toString());
+       return buf.toString();
+    }
+
+    /*   Uncomment to test 
+    public static void main (String[] args)
+        throws VisADException, RemoteException
+    {
+        double lat = 40.1;
+        double lon = -105.5;
+        double alt = 1660.0;
+        String name = "KDEN";
+        double newLat = 
+            (args.length > 0) ? new Double(args[0]).doubleValue() : lat;
+        double newLon = 
+            (args.length > 1) ? new Double(args[1]).doubleValue() : lon;
+        double newAlt = 
+            (args.length > 2) ? new Double(args[2]).doubleValue() : alt;
+        String newName = 
+            (args.length > 3) ? args[3] : name;
+
+        NamedLocationTuple nlt = new NamedLocationTuple(name, lat, lon, alt);
+        System.out.println("NamedLocation 1 = " + nlt);
+
+        NamedLocationTuple newnlt = 
+            new NamedLocationTuple(newName, newLat, newLon, newAlt);
+        System.out.println("NamedLocation 2 = " + newnlt);
+
+        System.out.println("Points are " +
+                           (nlt.equals(newnlt) ? "" : "NOT ")  +
+                           "equal");
+
+        Text t = new Text(IDENTIFIER_TYPE, newnlt.getIdentifier().getValue());
+        newnlt = new NamedLocationTuple(t, nlt.getEarthLocation());
+        System.out.println("\nNamedLocation 3 = " + newnlt);
+
+        System.out.println("Points are " +
+                           (nlt.equals(newnlt) ? "" : "NOT ")  +
+                           "equal");
+
+    }
+    */
+}
diff --git a/visad/georef/NavigatedCoordinateSystem.java b/visad/georef/NavigatedCoordinateSystem.java
new file mode 100644
index 0000000..d301023
--- /dev/null
+++ b/visad/georef/NavigatedCoordinateSystem.java
@@ -0,0 +1,97 @@
+//
+// NavigatedCoordinateSystem.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+
+/**
+ * Abstract class for CoordinateSystems that have RealType.Latitude
+ * and RealType.Longitude in their reference RealTupleType.
+ *
+ * @author Don Murray
+ */
+public abstract class NavigatedCoordinateSystem extends CoordinateSystem
+{
+    private final int latIndex;
+    private final int lonIndex;
+
+    /**
+     * Constructs from the type of the reference coordinate system and 
+     * units for values in this coordinate system. The reference coordinate
+     * system must contain RealType.Latitude and RealType.Longitude.
+     *
+     * @param reference  The type of the reference coordinate system. The
+     *                   reference must contain RealType.Latitude and
+     *                   RealType.Longitude.  Values in the reference 
+     *                   coordinate system shall be in units of 
+     *                   reference.getDefaultUnits() unless specified 
+     *                   otherwise.
+     * @param units      The default units for this coordinate system. 
+     *                   Numeric values in this coordinate system shall be 
+     *                   in units of units unless specified otherwise. 
+     *                   May be null or an array of null-s.
+     * @exception VisADException  Couldn't create necessary VisAD object or
+     *                            reference does not contain RealType.Latitude
+     *                            or RealType.Longitude.
+     */
+    public NavigatedCoordinateSystem(RealTupleType reference, Unit[] units)
+        throws VisADException
+    {
+        super(reference, units);
+        latIndex = reference.getIndex(RealType.Latitude);
+        if (latIndex == -1)
+            throw new CoordinateSystemException(
+                "NavigatedCoordinateSystem: Reference must contain " + 
+                "RealType.Latitude");
+        lonIndex = reference.getIndex(RealType.Longitude);
+        if (lonIndex == -1)
+            throw new CoordinateSystemException(
+                "NavigatedCoordinateSystem: Reference must contain " + 
+                "RealType.Longitude");
+    }
+
+    /**
+     * Get the index of RealType.Latitude in the reference RealTupleType.
+     *
+     * @return  index of RealType.Latitude in the reference
+     */
+    public int getLatitudeIndex()
+    {
+        return latIndex;
+    }
+
+    /**
+     * Get the index of RealType.Longitude in the reference RealTupleType.
+     *
+     * @return  index of RealType.Longitude in the reference
+     */
+    public int getLongitudeIndex()
+    {
+        return lonIndex;
+    }
+
+}
diff --git a/visad/georef/NavigatedField.java b/visad/georef/NavigatedField.java
new file mode 100644
index 0000000..c1d8964
--- /dev/null
+++ b/visad/georef/NavigatedField.java
@@ -0,0 +1,44 @@
+//
+//  NavigatedField.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+
+/**
+ * A particular type of Field whose Domain Set contains values of
+ * Latitude and Longitude, or whose CoordinateSystem can transform
+ * to RealType.Latitude and RealType.Longitude
+ */
+public interface NavigatedField extends Field
+{
+    /**
+     * Get the coordinate system representing the navigation for the domain.
+     *
+     * @return NavigatedCoordinateSystem for the domain of this field.
+     */
+    NavigatedCoordinateSystem getNavigation();
+}
diff --git a/visad/georef/TrivialMapProjection.java b/visad/georef/TrivialMapProjection.java
new file mode 100644
index 0000000..e2891ab
--- /dev/null
+++ b/visad/georef/TrivialMapProjection.java
@@ -0,0 +1,197 @@
+//
+// TrivialMapProjection.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.RealTupleType;
+import visad.VisADException;
+import java.awt.geom.Rectangle2D;
+/**
+ * A trivial implementation for a MapProjection which provides an
+ * identity coordinate system with a default bounding box.  This is
+ * useful for defining an area in LatLon or LonLat space with a 
+ * bounding box.  What goes in, comes right back out.
+ */
+public class TrivialMapProjection extends MapProjection
+{
+
+	private static final long serialVersionUID = 1L;
+	private float x, y, width, height;
+
+    /**
+     * Create a MapProjection that just returns the input tuple.
+     * Default Map area is set to be from (-180,-90) to (180, 90)
+     *
+     * @throws VisADException  reference does not contain Latitude/Longitude
+     *                         or couldn't create the necessary VisAD object
+     */
+    public TrivialMapProjection()
+        throws VisADException
+    {
+        this(RealTupleType.SpatialEarth2DTuple, 
+             new Rectangle2D.Float(-180, -90, 360, 180));
+    }
+
+    /**
+     * Create a MapProjection that just returns the input tuple.
+     *
+     * @param  reference  reference RealTupleType
+     *
+     * @throws VisADException  reference does not contain Latitude/Longitude
+     *                         or couldn't create the necessary VisAD object
+     */
+    public TrivialMapProjection(RealTupleType reference)
+        throws VisADException
+    {
+        this(reference, new Rectangle2D.Float(-180, -90, 360, 180));
+    }
+
+    /**
+     * Create a MapProjection that just returns the input tuple.
+     *
+     * @param  type  reference RealTupleType
+     * @param  bounds  rectangle bounds
+     *
+     * @throws VisADException  reference does not contain Latitude/Longitude
+     *                         or couldn't create the necessary VisAD object
+     */
+    public  TrivialMapProjection(RealTupleType type, Rectangle2D bounds) 
+      throws VisADException {
+        super(type, type.getDefaultUnits());
+        // have to do this because Rectangle2D is not Serializable
+        x = (float) bounds.getX();
+        y = (float) bounds.getY();
+        width = (float) bounds.getWidth();
+        height = (float) bounds.getHeight();
+    }
+
+    /** 
+     * Transform to the reference coordinates
+     *
+     * @param  tuple  array of values
+     * @return  input array
+     *
+     * @throws VisADException  tuple is null or wrong dimension
+     */
+    public double[][] toReference(double[][] tuple)
+        throws VisADException
+    {
+        if (tuple == null || getDimension() != tuple.length)
+            throw new VisADException(
+                "Values are null or wrong dimension");
+        return tuple;
+    }
+
+    /** 
+     * Transform from the reference coordinates
+     *
+     * @param  refTuple  array of values
+     * @return  input array
+     *
+     * @throws VisADException  tuple is null or wrong dimension
+     */
+    public double[][] fromReference(double[][] refTuple)
+        throws VisADException
+    {
+        if (refTuple == null || getDimension() != refTuple.length)
+            throw new VisADException(
+                "Values are null or wrong dimension");
+        return refTuple;
+    }
+
+    /** 
+     * Transform to the reference coordinates
+     *
+     * @param  tuple  array of values
+     * @return  input array
+     *
+     * @throws VisADException  tuple is null or wrong dimension
+     */
+    public float[][] toReference(float[][] tuple)
+        throws VisADException
+    {
+        if (tuple == null || getDimension() != tuple.length)
+            throw new VisADException(
+                "Values are null or wrong dimension");
+        return tuple;
+    }
+
+    /** 
+     * Transform from the reference coordinates
+     *
+     * @param  refTuple  array of values
+     * @return  input array
+     *
+     * @throws VisADException  tuple is null or wrong dimension
+     */
+    public float[][] fromReference(float[][] refTuple)
+        throws VisADException
+    {
+        if (refTuple == null || getDimension() != refTuple.length)
+            throw new VisADException(
+                "Values are null or wrong dimension");
+        return refTuple;
+    }
+
+    /**
+     * See if the object in question is equal to this CoordinateSystem.
+     * The two objects are equal if they are the same object or if they
+     * are both TrivialMapProjection and have the same dimension.
+     *
+     * @param  o  Object in question
+     * @return  true if they are considered equal, otherwise false.
+     */
+    public boolean equals(Object o) {
+        if (!(o instanceof TrivialMapProjection)) return false;
+        TrivialMapProjection that = (TrivialMapProjection) o;
+        return (this == that) ||
+               (that.getReference().equals(this.getReference()) &&
+               Double.doubleToLongBits(that.x) ==
+                   Double.doubleToLongBits(this.x) &&
+               Double.doubleToLongBits(that.y) ==
+                   Double.doubleToLongBits(this.y) &&
+               Double.doubleToLongBits(that.width) ==
+                   Double.doubleToLongBits(this.width) &&
+               Double.doubleToLongBits(that.height) ==
+                   Double.doubleToLongBits(this.height)) ;
+    }
+
+    /**
+     * Get a reasonable bounding box in this coordinate system. MapProjections 
+     * are typically specific to an area of the world; there's no bounding 
+     * box that works for all projections so each subclass must implement
+     * this method. For example, the bounding box for a satellite image 
+     * MapProjection might have an upper left corner of (0,0) and the width 
+     * and height of the Rectangle2D would be the number of elements and 
+     * lines, respectively.
+     *
+     * @return the bounding box of the MapProjection
+     *
+     */
+    public Rectangle2D getDefaultMapArea() {
+        return new Rectangle2D.Float(x,y,width,height);
+    }
+}
diff --git a/visad/georef/TrivialNavigation.java b/visad/georef/TrivialNavigation.java
new file mode 100644
index 0000000..1cbf33f
--- /dev/null
+++ b/visad/georef/TrivialNavigation.java
@@ -0,0 +1,99 @@
+//
+// TrivialNavigation.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+
+public class TrivialNavigation extends NavigatedCoordinateSystem
+{
+
+    /**
+     * Create a NavigationCoordinateSystem that just returns
+     * the input tuple.
+     *
+     * @param  reference  reference RealTupleType
+     *
+     * @throws VisADException  reference does not contain Latitude/Longitude
+     *                         or couldn't create the necessary VisAD object
+     */
+    public TrivialNavigation(RealTupleType reference)
+        throws VisADException
+    {
+        super(reference, reference.getDefaultUnits());
+    }
+
+    /** 
+     * Transform to the reference coordinates
+     *
+     * @param  tuple  array of values
+     * @return  input array
+     *
+     * @throws VisADException  tuple is null or wrong dimension
+     */
+    public double[][] toReference(double[][] tuple)
+        throws VisADException
+    {
+        if (tuple == null || getDimension() != tuple.length)
+            throw new VisADException(
+                "Values are null or wrong dimension");
+        return tuple;
+    }
+
+    /** 
+     * Transform from the reference coordinates
+     *
+     * @param  refTuple  array of values
+     * @return  input array
+     *
+     * @throws VisADException  tuple is null or wrong dimension
+     */
+    public double[][] fromReference(double[][] refTuple)
+        throws VisADException
+    {
+        if (refTuple == null || getDimension() != refTuple.length)
+            throw new VisADException(
+                "Values are null or wrong dimension");
+        return refTuple;
+    }
+
+    /**
+     * See if the object in question is equal to this CoordinateSystem.
+     * The two objects are equal if they are the same object or if they
+     * are both TrivialNavigations and have the same dimension.
+     *
+     * @param  cs  Object in question
+     * @return  true if they are considered equal, otherwise false.
+     */
+    public boolean equals(Object cs)
+    {
+        if ((cs instanceof TrivialNavigation && 
+               ((TrivialNavigation) cs).getDimension() == getDimension()) ||
+            cs == this) return true;
+        else
+            return false;
+    }
+}
diff --git a/visad/georef/UTMCoordinate.java b/visad/georef/UTMCoordinate.java
new file mode 100644
index 0000000..4232d27
--- /dev/null
+++ b/visad/georef/UTMCoordinate.java
@@ -0,0 +1,400 @@
+//
+//  UTMCoordinate.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.georef;
+
+import visad.*;
+import java.rmi.RemoteException;
+
+/**
+ * RealTuple implementation of a Universal Transverse Mercator
+ * (UTM) coordinate
+ */
+public class UTMCoordinate extends RealTuple {
+
+  /**
+   * The <code>RealType</code> for the easting component of the UTM
+   * grid.
+   */
+  public static RealType EASTING = 
+      RealType.getRealType("UTM_Easting", CommonUnit.meter);
+
+  /**
+   * The <code>RealType</code> for the easting component of the UTM
+   * grid.
+   */
+  public static RealType NORTHING =
+      RealType.getRealType("UTM_Northing", CommonUnit.meter);
+
+  /**
+   * The <code>RealType</code> for the zone component of the UTM
+   * grid.
+   */
+  public static RealType ZONE = RealType.getRealType("UTM_Zone");
+  
+  /**
+   * The <code>RealType</code> for the zone component of the UTM
+   * grid.
+   */
+  public static RealType HEMISPHERE = RealType.getRealType("UTM_Hemisphere");
+  
+  /**
+   * The hemisphere identifier for the northern hemisphere
+   */
+  public static final int NORTH = 0;
+
+  /**
+   * The hemisphere identifier for the southern hemisphere
+   */
+  public static final int SOUTH = 1;
+
+  private Real easting;
+  private Real northing;
+  private Real altitude;
+  private Real zone;
+  private Real hemisphere;
+
+  /**
+   * Construct a UTMCoordinate with missing values
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate() throws VisADException, RemoteException
+  {
+    this(Double.NaN, Double.NaN);
+  }
+
+  /**
+   * Construct a UTMCoordinate from double values of easting and
+   * northing.
+   *
+   * @param  east   easting component (meters) in the zone
+   * @param  north  northing component (meters) in the zone
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(double east, double north)
+      throws VisADException, RemoteException
+  {
+    this(east, north, 0.);
+  }
+
+  /**
+   * Construct a UTMCoordinate from double values of easting and
+   * northing and an altitude.
+   *
+   * @param  east   easting component (meters) in the zone
+   * @param  north  northing component (meters) in the zone
+   * @param  alt    altitude of the point
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(double east, double north, double alt)
+      throws VisADException, RemoteException
+  {
+    this(east, north, Double.NaN, 0);
+  }
+
+  /**
+   * Construct a UTMCoordinate from double values of easting and
+   * northing.  Unknown zone and northern hemisphere used
+   *
+   * @param  east   easting component (meters) in the zone
+   * @param  north  northing component (meters) in the zone
+   * @param  alt    altitude of the point
+   * @param  zone   UTM zone
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(double east, double north, double alt, int zone)
+      throws VisADException, RemoteException
+  {
+    this(east, north, alt, zone, NORTH);
+  }
+
+  /**
+   * Construct a UTMCoordinate from double values of easting and
+   * northing, the zone and the hemisphere.
+   *
+   * @param  east   easting component (meters) in the zone
+   * @param  north  northing component (meters) in the zone
+   * @param  alt    altitude of the point
+   * @param  zone   UTM zone
+   * @param  hemi   UTM hemisphere
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(double east, double north, double alt, 
+                       int zone, int hemi)
+      throws VisADException, RemoteException
+  {
+    this(new Real(EASTING, east),
+         new Real(NORTHING, north),
+         new Real(RealType.Altitude, alt),
+         (zone == 0) ? new Real(ZONE) :new Real(ZONE, zone),
+         new Real(HEMISPHERE, hemi));
+  }
+
+  /**
+   * Construct a UTMCoordinate from Reals representing the easting and
+   * northing.
+   *
+   * @param  east   Real representing easting (must have MathType EASTING)
+   * @param  north  Real representing northing (must have MathType NORTHING)
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(Real east, Real north)
+      throws VisADException, RemoteException
+  {
+    this(east, north, new Real(ZONE));
+  }
+
+  /**
+   * Construct a UTMCoordinate from Reals representing the easting and
+   * northing and the zone.
+   *
+   * @param  east   Real representing easting (must have MathType EASTING)
+   * @param  north  Real representing northing (must have MathType NORTHING)
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(Real east, Real north, Real alt)
+      throws VisADException, RemoteException
+  {
+    this(east, north, alt, new Real(ZONE, 0));
+  }
+
+  /**
+   * Construct a UTMCoordinate from Reals representing the easting and
+   * northing and the zone.
+   *
+   * @param  east   Real representing easting (must have MathType EASTING)
+   * @param  north  Real representing northing (must have MathType NORTHING)
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(Real east, Real north, Real alt, Real zone)
+      throws VisADException, RemoteException
+  {
+    this(east, north, alt, zone, new Real(HEMISPHERE, NORTH));
+  }
+
+  /**
+   * Construct a UTMCoordinate from Reals representing the easting and
+   * northing, the zone and the hemisphere.
+   *
+   * @param  east   Real representing easting (must have MathType EASTING)
+   * @param  north  Real representing northing (must have MathType NORTHING)
+   * @param  zone   Real representing UTM zone
+   * @param  hemi   Real representing hemisphere
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(Real east, Real north, Real alt, Real zone, Real hemi)
+      throws VisADException, RemoteException
+  {
+    this(east, north, alt, zone, hemi, null);
+  }
+
+  /**
+   * Construct a UTMCoordinate from Reals representing the easting and
+   * northing, the zone and the hemisphere.  Use the CoordinateSystem
+   * supplied to do any transforms.
+   *
+   * @param  east   Real representing easting (must have MathType EASTING)
+   * @param  north  Real representing northing (must have MathType NORTHING)
+   * @param  zone   Real representing UTM zone
+   * @param  hemi   Real representing hemisphere
+   * @param  cs     CoordinateSystem
+   *
+   * @throws  VisADException   couldn't create the necessary VisAD object
+   * @throws  RemoteException  couldn't create the necessary remote object
+   */
+  public UTMCoordinate(Real east, Real north, Real alt, Real zone, Real hemi,
+      CoordinateSystem cs)
+      throws VisADException, RemoteException
+  {
+    super (new RealTupleType(new RealType[] {EASTING, NORTHING, 
+                            RealType.Altitude, ZONE, HEMISPHERE}),
+        new Real[] {east, north, alt, zone, hemi}, (CoordinateSystem) cs);
+    this.easting = east;
+    this.northing = north;
+    this.altitude = alt;
+    this.zone = zone;
+    this.hemisphere = hemi;
+  }
+
+  /**
+   * Get the easting value of this point as a Real
+   *
+   * @return  Real representing the easting
+   */
+  public Real getEasting()
+  {
+    return easting;
+  }
+
+  /**
+   * Get the northing of this point as a Real
+   *
+   * @return  Real representing the northing
+   */
+  public Real getNorthing()
+  {
+    return northing;
+  }
+
+  /**
+   * Get the altitude of this point as a Real
+   *
+   * @return  Real representing the altitude.  May be missing.
+   */
+  public Real getAltitude()
+  {
+    return altitude;
+  }
+
+
+  /**
+   * Get the UTM zone of this point as a Real
+   *
+   * @return  Real representing the UTM zone
+   */
+  public Real getZone()
+  {
+    return zone;
+  }
+
+  /**
+   * Get the UTM hemisphere of this point as a Real
+   *
+   * @return  Real representing the UTM hemisphere
+   */
+  public Real getHemisphere()
+  {
+    return zone;
+  }
+
+  /**
+   * Get the easting value of this point as a Real
+   *
+   * @return  double representing the easting in meters
+   */
+  public double getEastingValue()
+  {
+    try {
+      return easting.getValue(CommonUnit.meter);
+    } catch (VisADException ve) {
+      return easting.getValue();
+    }
+  }
+
+  /**
+   * Get the northing of this point
+   *
+   * @return  double representing the northing
+   */
+  public double getNorthingValue()
+  {
+    try {
+      return northing.getValue(CommonUnit.meter);
+    } catch (VisADException ve) {
+      return northing.getValue();
+    }
+  }
+
+  /**
+   * Get the altitude value of this point.
+   *
+   * @return  double representing the altitude in meters
+   */
+  public double getAltitudeValue()
+  {
+    try {
+      return altitude.getValue(CommonUnit.meter);
+    } catch (VisADException ve) {
+      return altitude.getValue();
+    }
+  }
+
+  /**
+   * Get the UTM zone of this point
+   *
+   * @return  int representing the UTM zone
+   */
+  public int getZoneValue()
+  {
+    return (int) zone.getValue();
+  }
+
+  /**
+   * Get the UTM zone of this point
+   *
+   * @return  int representing the UTM zone
+   */
+  public int getHemisphereValue()
+  {
+    return (int) hemisphere.getValue();
+  }
+
+
+  public String toString() {
+    StringBuffer buf = new StringBuffer();
+    buf.append("East: ");
+    try {
+      buf.append(
+        visad.browser.Convert.shortString(easting.getValue(CommonUnit.degree)));
+    } catch (VisADException ve) {
+      buf.append(
+        visad.browser.Convert.shortString(easting.getValue()));
+    }
+    buf.append(" North: ");
+    try {
+      buf.append(
+        visad.browser.Convert.shortString(northing.getValue(CommonUnit.degree)));
+    } catch (VisADException ve) {
+      buf.append(
+        visad.browser.Convert.shortString(northing.getValue()));
+    }
+    buf.append(" Zone: ");
+    buf.append(getZoneValue());
+    buf.append(" Hemisphere: ");
+    buf.append(
+      (getHemisphereValue() == NORTH)?"Northern":"Southern");
+    return buf.toString();
+  }
+
+}
diff --git a/visad/georef/package.html b/visad/georef/package.html
new file mode 100644
index 0000000..4ce1ff7
--- /dev/null
+++ b/visad/georef/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+Provides classes for geo-referencing.
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/gifts/Gifts.java b/visad/gifts/Gifts.java
new file mode 100644
index 0000000..cd434f3
--- /dev/null
+++ b/visad/gifts/Gifts.java
@@ -0,0 +1,859 @@
+//
+// Gifts.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+ 
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.gifts;
+
+import visad.*;
+import visad.java3d.DisplayImplJ3D;
+import visad.java2d.DisplayImplJ2D;
+import visad.data.mcidas.BaseMapAdapter;
+import visad.data.mcidas.AreaForm;
+import visad.bom.WindPolarCoordinateSystem;
+import visad.bom.BarbRendererJ2D;
+import visad.util.SelectRangeWidget;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+
+import java.rmi.*;
+import java.io.*;
+
+public class Gifts
+       implements ItemListener, ScalarMapListener
+{
+    // brightness of AREA image
+    private static final int BRIGHT = 3;
+
+    String filename = null;
+    DataImpl data;
+    MathType type;
+    MathType range;
+    Set domainSet = null;
+    MathType R_type;
+    MathType D_type;
+    RealType xaxis = null;
+    RealType yaxis = null;
+    RealType zaxis = null;
+    RealType flowx;
+    RealType flowy;
+    EarthVectorType flowxy = null;
+    RealType flow_degree;
+    RealType flow_speed;
+    RealTupleType domain;
+    RealTupleType reference;
+    CoordinateSystem coord_sys;
+    FunctionType f_type;
+    DisplayImpl display;
+    DisplayImpl display2;
+    float lonmin, lonmax;
+    float latmin, latmax;
+    double[] x_range;
+    double[] y_range;
+    double[][] dir_spd = new double[2][];
+    double[][] uv = new double[2][];
+    boolean slice3d = true;
+    ConstantMap[] map_constMap, map_constMap2, img_constMap, rect_constMap;
+    ConstantMap[][] wnd_constMap, clone_wnd_constMap;
+    DataReference maplines_ref, rect_ref;
+    DataReference[] winds_ref;
+    DataReference[] clone_winds_ref;
+    DataReference image_ref;
+    ScalarMap xmap1, xmap2, xmap3;
+    ScalarMap ymap1, ymap2, ymap3;
+    ScalarMap zmap, zmap2;
+    ScalarMap flowx_map, flowx_map2;
+    ScalarMap flowy_map, flowy_map2;
+    ScalarMap sel_map, rgb_map, slice_map;
+    boolean xmapEvent = false;
+    boolean ymapEvent = false;
+    boolean firstEvent = false;
+    BaseMapAdapter baseMap;
+    JCheckBox multi_color;
+
+  public static void main(String[] args)
+         throws VisADException, RemoteException, IOException 
+  {
+    Gifts gifts = new Gifts(args); // WLH
+  }
+
+  public Gifts(String[] args) // WLH
+         throws VisADException, RemoteException, IOException
+  {
+    if (args.length > 0) slice3d = false; // WLH
+    flowx = new RealType("flowx", CommonUnit.meterPerSecond, null);
+    flowy = new RealType("flowy", CommonUnit.meterPerSecond, null);
+    flow_degree = new RealType("flow_degree", CommonUnit.degree, null);
+    flow_speed = new RealType("flow_speed", CommonUnit.meterPerSecond, null);
+    maplines_ref = new DataReferenceImpl("maplines");
+    image_ref = new DataReferenceImpl("image_ref");
+
+/**------  main gui panel  ------*/
+
+    Border etchedBorder =
+           new CompoundBorder(new EtchedBorder(),
+                              new EmptyBorder(5, 5, 5, 5));
+
+    JFrame frame = new JFrame("Gifts");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    JPanel big_panel = new JPanel();
+    big_panel.setLayout(new BoxLayout(big_panel, BoxLayout.X_AXIS));
+    big_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+
+/**------   File I/O, data initialization    -------*/
+
+    baseMap = new BaseMapAdapter("./data/OUTLHRES");
+
+    if ( baseMap.isEastPositive() ) 
+    {
+      baseMap.setEastPositive(true);
+    }
+
+
+/**------   Get wind data   ----*/
+
+    TextForm form = new TextForm(false, "lon", "lat");
+
+    String[][] filenames = { { "./data/REALGI250",
+                               "./data/REALGI300",
+                               "./data/REALGI350",
+                               "./data/REALGI400",
+                               "./data/REALGI430",
+                               "./data/REALGI475",
+                               "./data/REALGI500",
+                               "./data/REALGI570",
+                               "./data/REALGI620",
+                               "./data/REALGI670",
+                               "./data/REALGI700",
+                               "./data/REALGI780",
+                               "./data/REALGI850",
+                               "./data/REALGI920",
+                               "./data/REALGI950",
+                               "./data/REALGI1000" },
+                             { "./data/REALGR250",
+                               "./data/REALGR300",
+                               "./data/REALGR350",
+                               "./data/REALGR400",
+                               "./data/REALGR430",
+                               "./data/REALGR475",
+                               "./data/REALGR500",
+                               "./data/REALGR570",
+                               "./data/REALGR620",
+                               "./data/REALGR670",
+                               "./data/REALGR700",
+                               "./data/REALGR780",
+                               "./data/REALGR850",
+                               "./data/REALGR920",
+                               "./data/REALGR950",
+                               "./data/REALGR1000" },
+                             { "./data/TRUTH250",
+                               "./data/TRUTH300",
+                               "./data/TRUTH350",
+                               "./data/TRUTH400",
+                               "./data/TRUTH430",
+                               "./data/TRUTH475",
+                               "./data/TRUTH500",
+                               "./data/TRUTH570",
+                               "./data/TRUTH620",
+                               "./data/TRUTH670",
+                               "./data/TRUTH700",
+                               "./data/TRUTH780",
+                               "./data/TRUTH850",
+                               "./data/TRUTH920",
+                               "./data/TRUTH950",
+                               "./data/TRUTH1000" } };
+
+    DataImpl[] winds = getWinds(filenames, form);
+    int len = winds.length;
+    winds_ref = new DataReference[len];
+    for (int i=0; i<len; i++) {
+      winds_ref[i] = new DataReferenceImpl("winds_ref" + i);
+      winds_ref[i].setData(winds[i]);
+    }
+
+
+/**-----   Get image data  ------*/
+
+    AreaForm area_form = new AreaForm();
+    FieldImpl image = (FieldImpl) area_form.open("./data/AREA5800");
+    float[][] img_floats = image.getFloats(false);
+    for (int i=0; i<img_floats.length; i++) {
+      for (int j=0; j<img_floats[i].length; j++) img_floats[i][j] *= BRIGHT;
+    }
+    image.setSamples(img_floats);
+
+    FunctionType image_type = (FunctionType)image.getType();
+    RealTupleType rtt = (RealTupleType) image_type.getRange();
+    RealType image_range = (RealType) rtt.getComponent(0);
+    image_ref.setData(image);
+
+
+/**------  winds/image display panel set-up  ----*/
+
+    map_constMap = new ConstantMap[] {
+                       new ConstantMap(0., Display.Red),
+                       new ConstantMap(1., Display.Green),
+                       new ConstantMap(0., Display.Blue),
+                       new ConstantMap(-.99, Display.ZAxis) };
+    map_constMap2 = new ConstantMap[] {
+                       new ConstantMap(0., Display.Red),
+                       new ConstantMap(1., Display.Green),
+                       new ConstantMap(0., Display.Blue),
+                       new ConstantMap(-.99, Display.ZAxis) };
+
+    wnd_constMap = new ConstantMap[][] {
+                       { new ConstantMap(1., Display.Red),
+                         new ConstantMap(1., Display.Green),
+                         new ConstantMap(0., Display.Blue) },
+                       { new ConstantMap(0., Display.Red),
+                         new ConstantMap(1., Display.Green),
+                         new ConstantMap(1., Display.Blue) },
+                       { new ConstantMap(1., Display.Red),
+                         new ConstantMap(0., Display.Green),
+                         new ConstantMap(1., Display.Blue) } };
+
+    clone_wnd_constMap = new ConstantMap[][] {
+                       { new ConstantMap(1., Display.Red),
+                         new ConstantMap(1., Display.Green),
+                         new ConstantMap(0., Display.Blue) },
+                       { new ConstantMap(0., Display.Red),
+                         new ConstantMap(1., Display.Green),
+                         new ConstantMap(1., Display.Blue) },
+                       { new ConstantMap(1., Display.Red),
+                         new ConstantMap(0., Display.Green),
+                         new ConstantMap(1., Display.Blue) } };
+
+    img_constMap = new ConstantMap[] { new ConstantMap(-1., Display.ZAxis) };
+
+    display = new DisplayImplJ3D("display");
+    DisplayRenderer d1_render = display.getDisplayRenderer();
+    d1_render.setBackgroundColor(0.5f, 0.5f, 0.5f);
+    d1_render.setBoxColor(0f, 0f, 0f);
+    d1_render.setCursorColor(0f, 0f, 0f);
+
+    JPanel l_panel = new JPanel();
+    l_panel.setLayout(new BoxLayout(l_panel, BoxLayout.Y_AXIS));
+    l_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+
+    JPanel s_panel = new JPanel();
+    s_panel.setLayout(new BoxLayout(s_panel, BoxLayout.X_AXIS));
+    s_panel.add(display.getComponent());
+    s_panel.setBorder(etchedBorder);
+    l_panel.add(s_panel);
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    xmap1 = new ScalarMap(xaxis, Display.XAxis);
+    xmap1.setScaleColor(new float[] {0f, 0f, 0f});
+    xmap1.addScalarMapListener(this);
+    display.addMap(xmap1);
+    ymap1 = new ScalarMap(yaxis, Display.YAxis);
+    ymap1.setScaleColor(new float[] {0f, 0f, 0f});
+    ymap1.addScalarMapListener(this);
+    display.addMap(ymap1);
+    zmap = new ScalarMap(zaxis, Display.ZAxis);
+    zmap.setScaleColor(new float[] {0f, 0f, 0f});
+    display.addMap(zmap);
+    zmap.setRange(1000., 200.);
+    flowx_map = new ScalarMap(flowx, Display.Flow1X);
+    display.addMap(flowx_map);
+    flowx_map.setRange(-1.0, 1.0);
+    flowy_map = new ScalarMap(flowy, Display.Flow1Y);
+    display.addMap(flowy_map);
+    flowy_map.setRange(-1.0, 1.0);
+    FlowControl flow_control = (FlowControl) flowy_map.getControl();
+    flow_control.setFlowScale(0.005f);
+
+    // set up checkbox buttons data selector panel
+    JPanel option_panel = new JPanel();
+    option_panel.setLayout(new BoxLayout(option_panel, BoxLayout.X_AXIS));
+    multi_color = new JCheckBox("Multi-color", false);
+    multi_color.addItemListener(this);
+    multi_color.setEnabled(true);
+    JCheckBox jcb_gifts = new JCheckBox("GIFTS", true);
+    jcb_gifts.addItemListener(this);
+    JCheckBox jcb_goes = new JCheckBox("GOES", false);
+    jcb_goes.addItemListener(this);
+    JCheckBox jcb_truth = new JCheckBox("TRUTH", false);
+    jcb_truth.addItemListener(this);
+
+    option_panel.add(multi_color);
+    option_panel.add(Box.createRigidArea(new Dimension(15, 0)));
+    option_panel.add(jcb_gifts);
+    option_panel.add(jcb_goes);
+    option_panel.add(jcb_truth);
+    l_panel.add(option_panel);
+
+    sel_map = new ScalarMap(zaxis, Display.SelectRange);
+    display.addMap(sel_map);
+
+    rgb_map = new ScalarMap(image_range, Display.RGB);
+    display.addMap(rgb_map);
+
+
+    SelectRangeWidget sw = new SelectRangeWidget(sel_map);
+    s_panel = new JPanel();
+    s_panel.setLayout(new BoxLayout(s_panel, BoxLayout.X_AXIS));
+    s_panel.setBorder(etchedBorder);
+    s_panel.add(sw);
+    l_panel.add(s_panel);
+
+/**-----   slice display set-up   ----*/
+
+    map_constMap[0] =  new ConstantMap(0., Display.Blue);
+    map_constMap[1] =  new ConstantMap(0., Display.Red);
+    map_constMap[2] =  new ConstantMap(1., Display.Green); 
+    map_constMap[3] =  new ConstantMap(-.99, Display.ZAxis);
+    map_constMap2[0] =  new ConstantMap(0., Display.Blue);
+    map_constMap2[1] =  new ConstantMap(0., Display.Red);
+    map_constMap2[2] =  new ConstantMap(1., Display.Green); 
+    map_constMap2[3] =  new ConstantMap(-.99, Display.ZAxis);
+
+    DataImpl[] new_winds = cloneWinds(winds);
+    TupleType tt = (TupleType) new_winds[0].getType();
+    FunctionType ft = (FunctionType) tt.getComponent(0);
+    TupleType new_range = (TupleType) ft.getRange();
+
+    int index = new_range.getIndex("Latitude_winds");
+    RealType yaxis_winds = (RealType) new_range.getComponent(index);
+    index = new_range.getIndex("Longitude_winds");
+    RealType xaxis_winds = (RealType) new_range.getComponent(index);
+
+    if ( !slice3d ) {
+      display2 = new DisplayImplJ2D("slice_display"); 
+    }
+    else {
+      display2 = new DisplayImplJ3D("slice_display");
+    }
+    DisplayRenderer d2_render = display2.getDisplayRenderer();
+    d2_render.setBackgroundColor(0.5f, 0.5f, 0.5f);
+    d2_render.setBoxColor(0f, 0f, 0f);
+    d2_render.setCursorColor(0f, 0f, 0f);
+
+    JPanel r_panel = new JPanel();
+    r_panel.setLayout(new BoxLayout(r_panel, BoxLayout.Y_AXIS));
+
+    s_panel = new JPanel();
+    s_panel.setLayout(new BoxLayout(s_panel, BoxLayout.X_AXIS));
+    s_panel.add(display2.getComponent());
+    s_panel.setBorder(etchedBorder);
+    r_panel.add(s_panel);
+
+    mode = display2.getGraphicsModeControl();
+    mode.setScaleEnable(true);
+
+    if ( !slice3d) {
+      ymap2 = new ScalarMap(zaxis, Display.YAxis);
+      ymap2.setScaleColor(new float[] {0f, 0f, 0f});
+      display2.addMap(ymap2);
+      ymap2.setRange(1000., 200.);
+    }
+    else {
+      ymap2 = new ScalarMap(yaxis, Display.YAxis);
+      ymap2.setScaleColor(new float[] {0f, 0f, 0f});
+      ymap3 = new ScalarMap(yaxis_winds, Display.YAxis);
+      ymap3.setScaleColor(new float[] {0f, 0f, 0f});
+      display2.addMap(ymap2);
+      display2.addMap(ymap3);
+
+      zmap2 = new ScalarMap(zaxis, Display.ZAxis);
+      display2.addMap(zmap2);
+      zmap2.setRange(1000., 200.);
+    }
+
+    xmap2 = new ScalarMap(xaxis, Display.XAxis);
+    xmap2.setScaleColor(new float[] {0f, 0f, 0f});
+    xmap3 = new ScalarMap(xaxis_winds, Display.XAxis);
+    xmap3.setScaleColor(new float[] {0f, 0f, 0f});
+    display2.addMap(xmap2);
+    display2.addMap(xmap3);
+
+    ScalarMap flowx_map2 = new ScalarMap(flowx, Display.Flow1X);
+    display2.addMap(flowx_map2);
+    flowx_map2.setRange(-1.0, 1.0);
+    ScalarMap flowy_map2 = new ScalarMap(flowy, Display.Flow1Y);
+    display2.addMap(flowy_map2);
+    flowy_map2.setRange(-1.0, 1.0);
+    flow_control = (FlowControl) flowy_map2.getControl();
+    if (!slice3d) {
+      flow_control.setFlowScale(0.045f); // WLH
+    }
+    else {
+      flow_control.setFlowScale(0.005f);
+    }
+
+    ScalarMap slice_map = new ScalarMap(yaxis_winds, Display.SelectRange);
+    display2.addMap(slice_map);
+    final RangeControl control = (RangeControl) slice_map.getControl();
+
+    // set up white rectangle in left-hand display
+    final RealType rect_x = RealType.getRealTypeByName("Longitude");
+    final RealType rect_y = RealType.getRealTypeByName("Latitude");
+    final RealTupleType rect_type = new RealTupleType(rect_x, rect_y);
+    rect_ref = new DataReferenceImpl("rect_ref");
+    double[] x_range = xmap1.getRange();
+    double[] y_range = ymap1.getRange();
+    float xmn = (float) x_range[0];
+    float xmx = (float) x_range[1];
+    float ymn = (float) y_range[0];
+    float ymx = (float) y_range[1];
+    float[][] samps = { {xmn, xmn, xmx, xmx, xmn},
+                        {ymn, ymx, ymx, ymn, ymn} };
+    rect_ref.setData(new Gridded2DSet(rect_type, samps, 5));
+    control.addControlListener(new ControlListener() {
+      public void controlChanged(ControlEvent e) {
+        // draw red rectangle in left-hand display
+        double[] xr = xmap1.getRange();
+        float[] yr = control.getRange();
+        float xmin = (float) xr[0];
+        float xmax = (float) xr[1];
+        float ymin = yr[0];
+        float ymax = yr[1];
+        float[][] samples = { {xmin, xmin, xmax, xmax, xmin},
+                              {ymin, ymax, ymax, ymin, ymin} };
+        try {
+          Gridded2DSet set = new Gridded2DSet(rect_type, samples, 5);
+          rect_ref.setData(set);
+        }
+        catch (VisADException exc) { /* CTR: TEMP */ exc.printStackTrace(); }
+        catch (RemoteException exc) { /* CTR: TEMP */ exc.printStackTrace(); }
+      }
+    });
+    rect_constMap = new ConstantMap[] {
+                        new ConstantMap(1, Display.Red),
+                        new ConstantMap(1, Display.Green),
+                        new ConstantMap(1, Display.Blue),
+                        new ConstantMap(-.99, Display.ZAxis) };
+
+    // finish left-hand display set-up
+    display.addReference(rect_ref, rect_constMap);
+    display.addReference(image_ref, img_constMap);
+    display.addReference(maplines_ref, map_constMap);
+    display.addReference(winds_ref[0], wnd_constMap[0]);
+
+    SelectRangeWidget slice_sw = new SelectRangeWidget(slice_map);
+    s_panel = new JPanel();
+    s_panel.setLayout(new BoxLayout(s_panel, BoxLayout.X_AXIS));
+    s_panel.setBorder(etchedBorder);
+    s_panel.add(slice_sw);
+    r_panel.add(s_panel);
+
+    len = new_winds.length;
+    clone_winds_ref = new DataReferenceImpl[len];
+    for (int i=0; i<len; i++) {
+      clone_winds_ref[i] = new DataReferenceImpl("slice_ref" + i);
+      clone_winds_ref[i].setData(new_winds[i]);
+    }
+
+    if ( !slice3d ) {
+      display2.addReferences(new BarbRendererJ2D(), clone_winds_ref[0],
+                             clone_wnd_constMap[0]);
+    }
+    else {
+      display2.addReference(clone_winds_ref[0], clone_wnd_constMap[0]);
+      display2.addReference(maplines_ref, map_constMap2);
+    }
+    
+
+    big_panel.add(l_panel);
+    big_panel.add(r_panel);
+
+    frame.getContentPane().add(big_panel);
+    frame.pack();
+    frame.setVisible(true);
+  }
+
+  DataImpl[] getWinds( String[][] filenames, TextForm form )
+             throws VisADException, RemoteException, IOException
+  {
+    int n_groups = filenames.length;
+    Tuple[] winds = new Tuple[n_groups];
+
+    for (int g=0; g<n_groups; g++) {
+      int n_files = filenames[g].length;
+      Data[] field_s = new Data[n_files];
+
+      flowxy = new EarthVectorType(flowx, flowy);
+      coord_sys = new WindPolarCoordinateSystem(flowxy);
+
+      for ( int kk = 0; kk < n_files; kk++ )
+      {
+        data = form.open( filenames[g][kk] );
+        type = data.getType();
+
+        FunctionType ft = (FunctionType) type;
+        RealTupleType range_tuple = (RealTupleType) ft.getRange();
+        int index = range_tuple.getIndex("Longitude");
+        xaxis = (RealType) range_tuple.getComponent(index);
+        index = range_tuple.getIndex("Latitude");
+        yaxis = (RealType) range_tuple.getComponent(index);
+        index = range_tuple.getIndex("hpa");
+        zaxis = (RealType) range_tuple.getComponent(index);
+
+
+        int tup_dim = range_tuple.getDimension();
+        int n_samples = ((FlatField)data).getLength();
+        double[][] new_values = new double[tup_dim][];
+        double[][] values = ((FlatField)data).getValues();
+        MathType[] types = new MathType[tup_dim];
+        int new_dim = 0;
+        for ( int ii = 0; ii < tup_dim; ii++ )
+        {
+          RealType comp = (RealType)range_tuple.getComponent(ii);
+          String name = comp.getName();
+          if ( name.equals("spd") ) {
+            dir_spd[1] = values[ii];
+          }
+          else if ( name.equals("dir") ) {
+            dir_spd[0] = values[ii];
+          }
+          else {
+            types[new_dim] = comp;
+            new_values[new_dim] = values[ii];
+            new_dim++;
+          }
+        }
+
+        uv = coord_sys.toReference(dir_spd);
+
+        int idx = new_dim;
+        int idx2 = new_dim;
+
+        types[idx++] = flowxy;
+        new_values[idx2++] = uv[0];
+        new_values[idx2++] = uv[1];
+
+        MathType[] new_types = new MathType[idx];
+        System.arraycopy(types, 0, new_types, 0, idx);
+        MathType new_range = new TupleType(new_types);
+
+        FunctionType func_type = new FunctionType(ft.getDomain(), new_range);
+        Set set = ((FlatField) data).getDomainSet();
+        FlatField new_field = new FlatField(func_type, set);
+
+        new_field.setSamples(new_values);
+        field_s[kk] = new_field;
+      }
+
+      winds[g] = new Tuple(field_s);
+    }
+    return winds;
+  }
+
+  // this table may be overkill, but hey, it works
+  float[][] table = {
+    {0.363f, 0.364f, 0.364f, 0.365f, 0.365f, 0.366f, 0.366f, 0.367f,
+     0.368f, 0.368f, 0.369f, 0.369f, 0.37f, 0.37f, 0.371f, 0.372f,
+     0.372f, 0.373f, 0.374f, 0.374f, 0.375f, 0.376f, 0.376f, 0.377f,
+     0.378f, 0.378f, 0.379f, 0.38f, 0.381f, 0.381f, 0.382f, 0.383f,
+     0.384f, 0.385f, 0.386f, 0.387f, 0.387f, 0.388f, 0.389f, 0.39f,
+     0.391f, 0.392f, 0.393f, 0.394f, 0.395f, 0.397f, 0.398f, 0.399f,
+     0.4f, 0.401f, 0.402f, 0.404f, 0.405f, 0.406f, 0.408f, 0.409f,
+     0.41f, 0.412f, 0.413f, 0.415f, 0.416f, 0.418f, 0.42f, 0.421f,
+     0.423f, 0.425f, 0.425f, 0.425f, 0.426f, 0.426f, 0.426f, 0.427f,
+     0.427f, 0.427f, 0.427f, 0.428f, 0.428f, 0.429f, 0.430f, 0.431f,
+     0.432f, 0.433f, 0.434f, 0.435f, 0.436f, 0.437f, 0.438f, 0.439f,
+     0.440f, 0.441f, 0.442f, 0.443f, 0.444f, 0.445f, 0.45f, 0.451f,
+     0.452f, 0.453f, 0.454f, 0.455f, 0.456f, 0.457f, 0.458f, 0.459f,
+     0.46f, 0.461f, 0.462f, 0.463f, 0.464f, 0.465f, 0.466f, 0.47f,
+     0.471f, 0.472f, 0.473f, 0.474f, 0.475f, 0.476f, 0.477f, 0.478f,
+     0.48f, 0.481f, 0.482f, 0.483f, 0.485f, 0.489f, 0.495f, 0.497f,
+     0.506f, 0.518f, 0.53f, 0.542f, 0.554f, 0.566f, 0.577f, 0.589f,
+     0.6f, 0.611f, 0.622f, 0.632f, 0.642f, 0.652f, 0.661f, 0.671f,
+     0.679f, 0.688f, 0.696f, 0.704f, 0.712f, 0.719f, 0.727f, 0.733f,
+     0.74f, 0.746f, 0.753f, 0.758f, 0.764f, 0.77f, 0.775f, 0.78f,
+     0.785f, 0.789f, 0.794f, 0.798f, 0.803f, 0.807f, 0.81f, 0.814f,
+     0.818f, 0.821f, 0.825f, 0.828f, 0.831f, 0.834f, 0.837f, 0.84f,
+     0.843f, 0.846f, 0.848f, 0.851f, 0.853f, 0.856f, 0.858f, 0.86f,
+     0.862f, 0.864f, 0.866f, 0.868f, 0.87f, 0.872f, 0.874f, 0.876f,
+     0.878f, 0.879f, 0.881f, 0.883f, 0.884f, 0.886f, 0.887f, 0.889f,
+     0.89f, 0.891f, 0.893f, 0.894f, 0.895f, 0.897f, 0.898f, 0.899f,
+     0.9f, 0.901f, 0.902f, 0.904f, 0.905f, 0.906f, 0.907f, 0.908f,
+     0.909f, 0.91f, 0.911f, 0.912f, 0.912f, 0.913f, 0.914f, 0.915f,
+     0.916f, 0.917f, 0.918f, 0.918f, 0.919f, 0.92f, 0.921f, 0.921f,
+     0.922f, 0.923f, 0.923f, 0.924f, 0.925f, 0.925f, 0.926f, 0.927f,
+     0.927f, 0.928f, 0.929f, 0.929f, 0.93f, 0.93f, 0.931f, 0.931f,
+     0.932f, 0.933f, 0.933f, 0.934f, 0.934f, 0.935f, 0.935f, 0.936f},
+
+    {0.032f, 0.034f, 0.036f, 0.037f, 0.04f, 0.042f, 0.044f, 0.046f,
+     0.049f, 0.051f, 0.054f, 0.057f, 0.059f, 0.062f, 0.066f, 0.069f,
+     0.072f, 0.076f, 0.079f, 0.083f, 0.087f, 0.091f, 0.095f, 0.099f,
+     0.104f, 0.108f, 0.113f, 0.118f, 0.123f, 0.129f, 0.134f, 0.14f,
+     0.145f, 0.151f, 0.158f, 0.164f, 0.17f, 0.177f, 0.184f, 0.191f,
+     0.198f, 0.206f, 0.213f, 0.221f, 0.229f, 0.237f, 0.246f, 0.254f,
+     0.263f, 0.272f, 0.281f, 0.29f,0.3f, 0.31f, 0.319f, 0.329f, 0.34f,
+     0.35f, 0.36f, 0.371f, 0.382f, 0.393f, 0.404f, 0.415f, 0.427f,
+     0.438f, 0.45f, 0.461f, 0.473f, 0.485f, 0.497f, 0.509f, 0.522f,
+     0.534f, 0.546f, 0.559f, 0.571f, 0.583f, 0.596f, 0.608f, 0.621f,
+     0.633f, 0.646f, 0.658f, 0.67f, 0.683f, 0.695f, 0.707f, 0.719f,
+     0.731f, 0.743f, 0.754f, 0.766f, 0.777f, 0.789f, 0.8f, 0.811f,
+     0.821f, 0.832f, 0.842f, 0.852f, 0.862f, 0.871f, 0.881f, 0.89f,
+     0.898f, 0.907f, 0.915f, 0.922f, 0.93f, 0.937f, 0.944f, 0.95f,
+     0.956f, 0.962f, 0.967f, 0.972f, 0.977f, 0.981f, 0.984f, 0.988f,
+     0.991f, 0.993f, 0.995f, 0.997f, 0.998f, 0.999f, 0.999f, 0.999f,
+     0.999f, 0.998f, 0.997f, 0.995f, 0.993f, 0.991f, 0.988f, 0.984f,
+     0.981f, 0.977f, 0.972f, 0.967f, 0.962f, 0.956f, 0.95f, 0.944f,
+     0.937f, 0.93f, 0.922f, 0.915f, 0.907f, 0.898f, 0.89f, 0.881f,
+     0.871f, 0.862f, 0.852f, 0.842f, 0.832f, 0.821f, 0.811f, 0.8f,
+     0.789f, 0.777f, 0.766f, 0.754f, 0.743f, 0.731f, 0.719f, 0.707f,
+     0.695f, 0.683f, 0.67f, 0.658f, 0.646f, 0.633f, 0.621f, 0.608f,
+     0.596f, 0.583f, 0.571f, 0.559f, 0.546f, 0.534f, 0.522f, 0.509f,
+     0.497f, 0.485f, 0.473f, 0.461f, 0.45f, 0.438f, 0.427f, 0.415f,
+     0.404f, 0.393f, 0.382f, 0.371f, 0.36f, 0.35f, 0.34f, 0.329f,
+     0.319f, 0.31f, 0.3f, 0.29f, 0.281f, 0.272f, 0.263f, 0.254f,
+     0.246f, 0.237f, 0.229f, 0.221f, 0.213f, 0.206f, 0.198f, 0.191f,
+     0.184f, 0.177f, 0.17f, 0.164f, 0.158f, 0.151f, 0.145f, 0.14f,
+     0.134f, 0.129f, 0.123f, 0.118f, 0.113f, 0.108f, 0.104f, 0.099f,
+     0.095f, 0.091f, 0.087f, 0.083f, 0.079f, 0.076f, 0.072f, 0.069f,
+     0.065f, 0.062f, 0.059f, 0.057f, 0.054f, 0.051f, 0.049f, 0.046f,
+     0.044f, 0.042f, 0.04f, 0.037f, 0.036f, 0.034f, 0.032f},
+
+    {0.936f, 0.935f, 0.935f, 0.934f, 0.934f, 0.933f, 0.933f, 0.932f,
+     0.931f, 0.931f, 0.93f, 0.93f, 0.929f, 0.929f, 0.928f, 0.927f,
+     0.927f, 0.926f, 0.925f, 0.925f, 0.924f, 0.923f, 0.923f, 0.922f,
+     0.921f, 0.921f, 0.92f, 0.919f, 0.918f, 0.918f, 0.917f, 0.916f,
+     0.915f, 0.914f, 0.913f, 0.912f, 0.912f, 0.911f, 0.91f, 0.909f,
+     0.908f, 0.907f, 0.906f, 0.905f, 0.904f, 0.902f, 0.901f, 0.9f,
+     0.899f, 0.898f, 0.897f, 0.895f, 0.894f, 0.893f, 0.891f, 0.89f,
+     0.889f, 0.887f, 0.886f, 0.884f, 0.883f, 0.881f, 0.879f, 0.878f,
+     0.876f, 0.874f, 0.872f, 0.87f, 0.868f, 0.866f, 0.864f, 0.862f,
+     0.86f, 0.858f, 0.856f, 0.853f, 0.851f, 0.848f, 0.846f, 0.843f,
+     0.84f, 0.837f, 0.834f, 0.831f, 0.828f, 0.825f, 0.821f, 0.818f,
+     0.814f, 0.81f, 0.807f, 0.803f, 0.798f, 0.794f, 0.789f, 0.785f,
+     0.78f, 0.775f, 0.77f, 0.764f, 0.758f, 0.753f, 0.746f, 0.74f,
+     0.733f, 0.727f, 0.719f, 0.712f, 0.704f, 0.696f, 0.688f, 0.679f,
+     0.671f, 0.661f, 0.652f, 0.642f, 0.632f, 0.622f, 0.611f, 0.6f,
+     0.589f, 0.577f, 0.566f, 0.554f, 0.542f, 0.53f, 0.518f, 0.506f,
+     0.493f, 0.481f, 0.469f, 0.457f, 0.445f, 0.433f, 0.422f, 0.41f,
+     0.399f, 0.388f, 0.377f, 0.367f, 0.357f, 0.347f, 0.338f, 0.328f,
+     0.32f, 0.311f, 0.303f, 0.295f, 0.287f, 0.28f, 0.272f, 0.266f,
+     0.259f, 0.253f, 0.246f, 0.241f, 0.235f, 0.229f, 0.224f, 0.219f,
+     0.214f, 0.21f, 0.205f, 0.201f, 0.196f, 0.192f, 0.189f, 0.185f,
+     0.181f, 0.178f, 0.174f, 0.171f, 0.168f, 0.165f, 0.162f, 0.159f,
+     0.156f, 0.153f, 0.151f, 0.148f, 0.146f, 0.143f, 0.141f, 0.139f,
+     0.137f, 0.135f, 0.133f, 0.131f, 0.129f, 0.127f, 0.125f, 0.123f,
+     0.121f, 0.12f, 0.118f, 0.116f, 0.115f, 0.113f, 0.112f, 0.11f,
+     0.109f, 0.108f, 0.106f, 0.105f, 0.104f, 0.102f, 0.101f, 0.1f,
+     0.099f, 0.098f, 0.097f, 0.095f, 0.094f, 0.093f, 0.092f, 0.091f,
+     0.09f, 0.089f, 0.088f, 0.087f, 0.087f, 0.086f, 0.085f, 0.084f,
+     0.083f, 0.082f, 0.081f, 0.081f, 0.08f, 0.079f, 0.078f, 0.078f,
+     0.077f, 0.076f, 0.076f, 0.075f, 0.074f, 0.074f, 0.073f, 0.072f,
+     0.072f, 0.071f, 0.07f, 0.07f, 0.069f, 0.069f, 0.068f, 0.068f,
+     0.067f, 0.066f, 0.066f, 0.065f, 0.065f, 0.064f, 0.064f, 0.063f}
+  };
+
+  int numSelected = 1;
+  boolean gifts_checked = true;
+  boolean goes_checked = false;
+  boolean truth_checked = false;
+
+  public void itemStateChanged(ItemEvent e) {
+    JCheckBox source = (JCheckBox) e.getItemSelectable();
+    String text = source.getText();
+    boolean checked = (e.getStateChange() == ItemEvent.SELECTED);
+
+    if (source == multi_color) {
+      try {
+        display.removeAllReferences();
+        display2.removeAllReferences();
+        if (checked) {
+          // add "rainbow" RGB maps
+          ScalarMap map = new ScalarMap(zaxis, Display.RGB);
+          display.addMap(map);
+          ScalarMap map2 = new ScalarMap(zaxis, Display.RGB);
+          display2.addMap(map2);
+
+          // set up color table
+          ((ColorControl) map.getControl()).setTable(table);
+          ((ColorControl) map2.getControl()).setTable(table);
+        }
+        else {
+          // remove "rainbow" RGB map
+          display.clearMaps();
+          display.addMap(xmap1);
+          display.addMap(ymap1);
+          display.addMap(zmap);
+          display.addMap(flowx_map);
+          display.addMap(flowy_map);
+          display.addMap(sel_map);
+          display.addMap(rgb_map);
+          display2.clearMaps();
+          display2.addMap(xmap2);
+          display2.addMap(xmap3);
+          display2.addMap(ymap2);
+          display2.addMap(ymap3);
+          display2.addMap(zmap2);
+          display2.addMap(flowx_map2);
+          display2.addMap(flowy_map2);
+          display2.addMap(slice_map);
+        }
+
+        // add old references back
+        display.addReference(rect_ref, rect_constMap);
+        display.addReference(image_ref, img_constMap);
+        display.addReference(maplines_ref, map_constMap);
+        int i = -1;
+        if (gifts_checked) i = 0;
+        else if (goes_checked) i = 1;
+        else if (truth_checked) i = 2;
+        if (i >= 0) {
+          display.addReference(winds_ref[i], wnd_constMap[i]);
+          if ( !slice3d ) {
+            display2.addReferences(new BarbRendererJ2D(), clone_winds_ref[i],
+                                   clone_wnd_constMap[i]);
+          }
+          else {
+            display2.addReference(clone_winds_ref[i], clone_wnd_constMap[i]);
+            display2.addReference(maplines_ref, map_constMap2);
+          }
+        }
+      }
+      catch (VisADException exc) { /* CTR: TEMP */ exc.printStackTrace(); }
+      catch (RemoteException exc) { /* CTR: TEMP */ exc.printStackTrace(); }
+    }
+    else if (multi_color.isSelected()) {
+      // turn off multi-color display first
+      multi_color.setSelected(false);
+    }
+
+    int i = -1;
+    if (text.equals("GIFTS")) {
+      i = 0;
+      gifts_checked = checked;
+    }
+    else if (text.equals("GOES")) {
+      i = 1;
+      goes_checked = checked;
+    }
+    else if (text.equals("TRUTH")) {
+      i = 2;
+      truth_checked = checked;
+    }
+    if (i >= 0) {
+      try {
+        if (checked) {
+          display.addReference(winds_ref[i], wnd_constMap[i]);
+          if ( !slice3d ) {
+            display2.addReferences(new BarbRendererJ2D(), clone_winds_ref[i],
+                                   clone_wnd_constMap[i]);
+          }
+          else {
+            display2.addReference(clone_winds_ref[i], clone_wnd_constMap[i]);
+          }
+          numSelected++;
+        }
+        else {
+          display.removeReference(winds_ref[i]);
+          display2.removeReference(clone_winds_ref[i]);
+          numSelected--;
+        }
+      }
+      catch (VisADException exc) { /* CTR: TEMP */ exc.printStackTrace(); }
+      catch (RemoteException exc) { /* CTR: TEMP */ exc.printStackTrace(); }
+
+      multi_color.setEnabled(numSelected == 1);
+    }
+  }
+
+  public void mapChanged(ScalarMapEvent e)
+       throws VisADException, RemoteException
+  {
+    if ( xmap1.equals(e.getScalarMap()) ) { 
+      xmapEvent = true;
+    }
+    else if ( ymap1.equals(e.getScalarMap()) ) {
+      ymapEvent = true;
+    }
+    if (( xmapEvent && ymapEvent ) && !(firstEvent) ) {
+      x_range = xmap1.getRange();
+      y_range = ymap1.getRange();
+      latmin = (float)y_range[0];
+      latmax = (float)y_range[1];
+      lonmin = (float)x_range[0];
+      lonmax = (float)x_range[1];
+      baseMap.setLatLonLimits(latmin, latmax, lonmin, lonmax);
+      DataImpl map = baseMap.getData();
+      maplines_ref.setData(map);
+      firstEvent = true;
+    }
+  }
+
+  public void controlChanged(ScalarMapControlEvent e) { }
+
+  DataImpl[] cloneWinds(DataImpl[] winds)
+             throws VisADException, RemoteException
+  {
+    int n_groups = winds.length;
+    DataImpl[] cloned_winds = new DataImpl[n_groups];
+
+    RealType Latitude_winds = new RealType("Latitude_winds",
+                                  CommonUnit.degree, null);
+    RealType Longitude_winds = new RealType("Longitude_winds",
+                                   CommonUnit.degree, null);
+
+    for (int g=0; g<n_groups; g++) {
+      int n_comps = ((TupleType)winds[g].getType()).getDimension();
+      MathType[] new_type_s = new MathType[n_comps];
+
+      for ( int ii = 0; ii < n_comps; ii++ ) {
+        Data d = ((Tuple) winds[g]).getComponent(ii);
+        FunctionType org_type = (FunctionType) d.getType();
+        TupleType org_range = (TupleType)org_type.getRange();
+        int n_range = org_range.getDimension();
+        MathType[] new_range = new MathType[n_range];
+        for ( int jj = 0; jj < n_range; jj++ ) {
+          MathType r_type = org_range.getComponent(jj);
+          if (r_type instanceof RealType) {
+            if ( ((RealType)r_type).getName().equals("Latitude") ) {
+              new_range[jj] = Latitude_winds;
+            }
+            else if ( ((RealType)r_type).getName().equals("Longitude") ) {
+              new_range[jj] = Longitude_winds;
+            }
+            else {
+              new_range[jj] = r_type;
+            }
+          }
+          else {
+            new_range[jj] = r_type;
+          }
+        }
+        new_type_s[ii] = new FunctionType(org_type.getDomain(), 
+                             new TupleType(new_range) );
+      }
+      TupleType new_type = new TupleType(new_type_s);
+
+      cloned_winds[g] = (DataImpl) winds[g].changeMathType(new_type);
+    }
+
+    return cloned_winds;
+  }
+}
diff --git a/visad/gifts/TextForm.java b/visad/gifts/TextForm.java
new file mode 100644
index 0000000..a6bdb96
--- /dev/null
+++ b/visad/gifts/TextForm.java
@@ -0,0 +1,208 @@
+//
+// TextForm.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.gifts;
+
+import java.util.Vector; 
+import java.io.FileReader;
+import java.io.LineNumberReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.StringTokenizer;
+import visad.*;
+import java.rmi.*;
+
+public class TextForm
+{
+  boolean eastLongitudePositive = true;
+  String longitudeName = null;
+  String latitudeName = null;
+
+  public TextForm()
+  {
+    this( true, "Longitude", "Latitude");
+  }
+
+  public TextForm(String lon_name, String lat_name)
+  {
+    this(true, lon_name, lat_name);
+  }
+  
+  public TextForm(boolean elp, String lon_name, String lat_name)
+  {
+    eastLongitudePositive = elp;
+    longitudeName = lon_name;
+    latitudeName = lat_name;
+  }
+
+  public DataImpl open( String file_path ) 
+         throws VisADException, RemoteException 
+  {
+    DataImpl data = null;
+    try 
+    {
+      data = getFileData(file_path);
+    }
+    catch ( FileNotFoundException e1 ) 
+    {
+      System.out.println(e1.getMessage());
+    }
+    catch ( IOException e2 ) 
+    {
+      System.out.println(e2.getMessage());
+    }
+
+    if ( !eastLongitudePositive ) {  //--  switch to eastLongitudePositive
+      return switchLongitudeSign(data);
+    }
+    else {
+      return data;
+    }
+  }
+
+  private DataImpl getFileData(String file_path) 
+          throws VisADException, RemoteException,
+                 FileNotFoundException, IOException 
+  {
+    DataImpl data = null;
+    MathType type = null;
+
+    String line = null;
+    String token;
+    Vector lines = new Vector();
+    String header = null;
+    int n_lines = 0;
+    FileReader reader = new FileReader(file_path);
+
+    LineNumberReader line_reader = new LineNumberReader(reader);
+
+    header = line_reader.readLine();
+    lines.addElement(header);
+
+    String line_1 = line_reader.readLine();
+    lines.addElement(line_1);
+
+    int[][] indexes = new int[1][];
+    type = getFileType(header, line_1, indexes);
+    int tup_dim = ((RealTupleType)((FunctionType)type).getRange()).getDimension();
+  
+    while ( (line = line_reader.readLine()) != null )
+    {
+      lines.addElement(line);
+    }
+
+    int n_samples = lines.size() - 1;
+    float[][] values = new float[tup_dim][n_samples];
+
+    for (int ii = 0; ii < n_samples; ii++) 
+    { 
+      StringTokenizer tokens = new StringTokenizer((String)lines.elementAt(ii+1));
+      int cnt = 0;
+      while ( tokens.hasMoreElements() )
+      {
+        token = tokens.nextToken();
+        for ( int jj = 0; jj < tup_dim; jj++ ) {
+          if ( cnt == indexes[0][jj] ) {
+            values[jj][ii] = (Float.valueOf(token)).floatValue();
+          }
+        }
+        cnt++;
+      }
+    }
+
+    Integer1DSet domainSet = new Integer1DSet(((FunctionType)type).getDomain(), n_samples);
+    FlatField f_field = new FlatField((FunctionType)type, domainSet);
+    f_field.setSamples(values);
+    return f_field;
+  }
+
+  private MathType getFileType(String header, String line_1, int[][] indexes)
+          throws VisADException, RemoteException
+  {
+    StringTokenizer tokens = new StringTokenizer(header);
+    StringTokenizer values = new StringTokenizer(line_1);
+    String token;
+    Vector names = new Vector();
+    Vector types = new Vector();
+    Vector comps = new Vector();
+    MathType type;
+    float value;
+
+    while ( tokens.hasMoreElements() ) 
+    { 
+      token = tokens.nextToken();
+      names.addElement(token);
+    }
+    
+    int tup_dim = names.size();
+    indexes[0] = new int[tup_dim];
+    int cnt_num = 0;
+    RealType r_type = null;
+    for ( int ii = 0; ii < tup_dim; ii++ ) {
+      try {
+        Float.valueOf((String)values.nextToken());
+        String name = (String)names.elementAt(ii);
+        if ( name.equals(longitudeName) ) {
+          r_type = RealType.Longitude;
+        }
+        else if ( name.equals(latitudeName) ) {
+          r_type = RealType.Latitude;
+        }
+        else {
+          r_type = RealType.getRealType(name);
+        }
+        types.addElement(r_type);
+        indexes[0][cnt_num++] = ii;
+      }
+      catch ( NumberFormatException e ) {
+      }
+    }
+
+    RealType[] r_types = new RealType[types.size()];
+    for ( int ii = 0; ii < r_types.length; ii++ ) {
+      r_types[ii] = (RealType) types.elementAt(ii);
+    }
+
+    RealTupleType range = new RealTupleType(r_types);
+    RealType domain = RealType.getRealType("index");
+    
+    type = new FunctionType(domain, range);
+    return type;
+  }
+
+  private DataImpl switchLongitudeSign(DataImpl data)
+          throws VisADException, RemoteException
+  {
+     int idx = ((RealTupleType)((FunctionType)data.getType()).getRange()).getIndex("Longitude");
+     double[][] values = ((FlatField)data).getValues();
+
+     for ( int ii = 0; ii < values[idx].length; ii++ ) {
+       values[idx][ii] = -1d*values[idx][ii];
+     }
+     ((FlatField)data).setSamples(values);
+     return data;
+  }
+}
diff --git a/visad/install/ChooserList.java b/visad/install/ChooserList.java
new file mode 100644
index 0000000..ab91b63
--- /dev/null
+++ b/visad/install/ChooserList.java
@@ -0,0 +1,162 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import java.io.File;
+
+import javax.swing.JFileChooser;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+
+import javax.swing.border.TitledBorder;
+
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * An auxiliary widget which displays a pre-built list of file selections.
+ */
+class FileListAccessory
+  extends JPanel
+{
+  private JList fileList;
+
+  FileListAccessory(String sectionName, File[] list)
+  {
+    setPreferredSize(new Dimension(200, 50));
+
+    setBorder(new TitledBorder(sectionName));
+
+    setLayout(new BorderLayout());
+
+    fileList = new JList(list);
+    fileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+    add(new JScrollPane(fileList));
+  }
+
+  public void addListSelectionListener(ListSelectionListener listener)
+  {
+    fileList.addListSelectionListener(listener);
+  }
+
+  public void removeListSelectionListener(ListSelectionListener listener)
+  {
+    fileList.removeListSelectionListener(listener);
+  }
+
+  public void setListData(File[] list)
+  {
+    fileList.setListData(list);
+  }
+}
+
+/**
+ * A JFileChooser widget which includes a pre-built list of choices.
+ */
+public class ChooserList
+  extends JFileChooser
+  implements ListSelectionListener
+{
+  private FileListAccessory accessory;
+
+  public ChooserList()
+  {
+    accessory = null;
+  }
+
+  /**
+   * Set the list of choices.
+   *
+   * @param list list of File objects
+   */
+  public void setList(File[] list)
+  {
+    if (list == null) {
+      setAccessory(null);
+      updateSelectedFile(null);
+    }else {
+      updateSelectedFile(list[0]);
+
+      if (accessory == null) {
+        accessory = new FileListAccessory("Found", list);
+      } else {
+        accessory.setListData(list);
+      }
+
+      accessory.addListSelectionListener(this);
+
+      setAccessory(accessory);
+    } 
+  }
+
+  /**
+   * Update widget to point to the selected file.
+   */
+  private final void updateSelectedFile(File file)
+  {
+    if (file != null) {
+
+      // make sure filter doesn't exclude this file
+      FileFilter filter = getFileFilter();
+      if (filter != null) {
+        if (!filter.accept(file)) {
+          setFileFilter(getAcceptAllFileFilter());
+        }
+      }
+
+      // point to the appropriate directory
+      File parent = file.getParentFile();
+      if (parent != null) {
+        setCurrentDirectory(parent);
+      }
+
+      // clear out the current choice
+      setSelectedFile(null);
+    }
+
+    // set the new choice
+    setSelectedFile(file);
+  }
+
+  public void valueChanged(ListSelectionEvent evt)
+  {
+    if (!evt.getValueIsAdjusting()) {
+      JList source = (JList )evt.getSource();
+      File sel = (File )source.getSelectedValue();
+
+      updateSelectedFile(sel);
+
+      // refresh
+      invalidate();
+      repaint();
+    }
+  }
+}
diff --git a/visad/install/Download.java b/visad/install/Download.java
new file mode 100644
index 0000000..abac092
--- /dev/null
+++ b/visad/install/Download.java
@@ -0,0 +1,190 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Download a file from a URL to a local directory.
+ */
+public abstract class Download
+{
+  /**
+   * Save the file found at the URL to the specified directory.<br>
+   * <br>
+   * If <tt>saveFile</tt> exists and is a file, it is overwritten.
+   *
+   * @param url the file to download
+   * @param saveFile the directory or file to which the downloaded
+   *                 file is written
+   * @param verbose <tt>true</tt> if a running commentary of the
+   *                download's progress is desired.
+   */
+  public static void getFile(URL url, File saveFile, boolean verbose)
+  {
+    getFile(url, saveFile, false, verbose);
+  }
+
+  /**
+   * Save the file found at the URL to the specified directory.
+   *
+   * @param url the file to download
+   * @param saveFile the directory or file to which the downloaded
+   *                 file is written
+   * @param backUpExisting <tt>true</tt> if any existing <tt>saveFile</tt>
+   *                       should be backed up
+   * @param verbose <tt>true</tt> if a running commentary of the
+   *                download's progress is desired.
+   */
+  public static void getFile(URL url, File saveFile, boolean backUpExisting,
+                             boolean verbose)
+  {
+    if (verbose) {
+      System.err.println("Downloading " + url + " to " + saveFile);
+    }
+
+    File target;
+    String baseName;
+
+    // get the target file and base name
+    if (!saveFile.isDirectory()) {
+      target = saveFile;
+      baseName = saveFile.getName();
+    } else {
+      File baseFile = new File(url.getFile());
+      baseName = baseFile.getName();
+
+      // check for specified file
+      if (baseName.length() == 0) {
+        baseName = "file";
+      }
+      target = new File(saveFile, baseName);
+    }
+
+    // open the URL connection
+    URLConnection conn;
+    try {
+      conn = url.openConnection();
+    } catch (IOException ioe) {
+      System.err.println("Couldn't open \"" + url + "\"");
+      return;
+    }
+
+    // if file exists, only get it if there's a newer version
+    if (target.exists()) {
+      conn.setIfModifiedSince(target.lastModified());
+    }
+
+    // if content length is less than 0, we didn't fetch the file
+    if (conn.getContentLength() < 0) {
+      if (verbose) {
+        System.err.println(url + " is not newer than " + target);
+      }
+      return;
+    }
+
+    // if a file by that name already exists,
+    //  build a usable name
+    if (backUpExisting && target.exists()) {
+
+      int idx = 0;
+      while (true) {
+        File tmpFile = new File(saveFile, baseName + "." + idx);
+        if (!tmpFile.exists()) {
+          if (!target.renameTo(tmpFile)) {
+            System.err.println("Couldn't rename \"" + target + "\" to \"" +
+                               tmpFile + "\"");
+            target.delete();
+          }
+          break;
+        }
+        idx++;
+      }
+    }
+
+    // open URL for reading
+    BufferedInputStream in;
+    try {
+      InputStream uIn = conn.getInputStream();
+      in = new BufferedInputStream(uIn);
+    } catch (IOException ioe) {
+      System.err.println("Couldn't read \"" + url + "\"");
+      return;
+    }
+
+    // open file for writing
+    BufferedOutputStream out;
+    try {
+      FileOutputStream fOut = new FileOutputStream(target);
+      out = new BufferedOutputStream(fOut);
+    } catch (IOException ioe) {
+      System.err.println("Couldn't write \"" + target + "\"");
+      return;
+    }
+
+    // copy URL to file
+    byte[] block = new byte[1024];
+    while (true) {
+      int len;
+      try {
+        len = in.read(block);
+      } catch (IOException ioe) {
+        ioe.printStackTrace();
+        break;
+      }
+
+      if (len < 0) {
+        break;
+      }
+
+      try {
+        out.write(block, 0, len);
+      } catch (IOException ioe) {
+        ioe.printStackTrace();
+        break;
+      }
+    }
+
+    // close up shop
+    try { out.close(); } catch (IOException ioe) { }
+    try { in.close(); } catch (IOException ioe) { }
+
+    // try to set the last-modified time appropriately
+    long connMod = conn.getLastModified();
+    if (connMod != 0) {
+      target.setLastModified(connMod);
+    }
+
+    if (verbose) {
+      System.out.println("Successfully updated " + target);
+    }
+  }
+}
diff --git a/visad/install/JavaFile.java b/visad/install/JavaFile.java
new file mode 100644
index 0000000..4919001
--- /dev/null
+++ b/visad/install/JavaFile.java
@@ -0,0 +1,248 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import gnu.regexp.RE;
+import gnu.regexp.REException;
+import gnu.regexp.REMatch;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * A {@link java.io.File File} object for a <tt>java</tt> executable
+ * which also extracts the JVM's version information.
+ */
+public class JavaFile
+  extends File
+{
+  // list of regular expressions used to extract version info
+  private static final String[] regexpStr = new String[] {
+    "^java version \"(\\d+)\\.(\\d+).*\"",
+    "^java version \"HP-UX Java [A-Z]\\.\\d+\\.(\\d)(\\d+).*\"",
+  };
+
+  // list of regular expressions built from regexpStr above
+  private static RE[] regexp = null;
+
+  // the Runtime object used to run 'java -version'
+  private static Runtime runtime = null;
+
+  // extracted version info
+  private String fullString;
+  private int major, minor;
+
+  /**
+   * @see java.io.File#File(String)
+   */
+  public JavaFile(String path)
+  {
+    this(new File(path));
+  }
+
+  /**
+   * @see java.io.File#File(String)
+   */
+  public JavaFile(File path)
+  {
+    this(path.getParent(), path.getName());
+  }
+
+  /**
+   * @see java.io.File#File(String, String)
+   */
+  public JavaFile(String parent, String child)
+  {
+    this(new File(parent), child);
+  }
+
+  /**
+   * @see java.io.File#File(File, String)
+   */
+  public JavaFile(File path, String name)
+  {
+    super(path, name);
+
+    findVersion();
+  }
+
+  /**
+   * Extract this JVM's version information.<br>
+   * Try to find something meaningful in the output of
+   * 'java -version'.
+   */
+  private final void findVersion()
+  {
+    if (regexp == null) {
+      initRegExp();
+    }
+
+    if (runtime == null) {
+      runtime = Runtime.getRuntime();
+    }
+
+    Process proc;
+    try {
+      proc = runtime.exec(this + " -version");
+    } catch (IOException ioe) {
+      System.err.println("While running '" + this + " -version':");
+      ioe.printStackTrace();
+      proc = null;
+    }
+
+    boolean found = false;
+    if (proc != null) {
+      InputStream is = proc.getErrorStream();
+      BufferedReader in = new BufferedReader(new InputStreamReader(is));
+
+      while (!found) {
+        String line;
+        try {
+          line = in.readLine();
+        } catch (IOException ioe) {
+          ioe.printStackTrace();
+          break;
+        }
+
+        if (line == null) {
+          break;
+        }
+
+        for (int i = 0; i < regexp.length; i++) {
+          REMatch match = regexp[i].getMatch(line);
+          if (match != null) {
+            fullString = line.substring(match.getStartIndex(),
+                                        match.getEndIndex());
+            major = parseInt(line.substring(match.getSubStartIndex(1),
+                                            match.getSubEndIndex(1)));
+            minor = parseInt(line.substring(match.getSubStartIndex(2),
+                                            match.getSubEndIndex(2)));
+            found = true;
+            break;
+          }
+        }
+      }
+    }
+
+    if (!found) {
+      fullString = null;
+      major = minor = -1;
+    }
+  }
+
+  /**
+   * Get the version string returned by this JVM.
+   * For a 1.3.2 JVM, this method might return something like
+   * <tt>"1.3.2_01"</tt>.
+   *
+   * @return the version string for this JVM.
+   */
+  public String getVersionString() { return fullString; }
+
+  /**
+   * Extract an integer from a String.
+   *
+   * @param intStr the string to parse.
+   *
+   * @return the extracted integer, or <tt>-1</tt> if there
+   *         was a parsing error.
+   */
+  private static final int parseInt(String intStr)
+  {
+    try {
+      return Integer.parseInt(intStr);
+    } catch (NumberFormatException nfe) {
+    }
+
+    return -1;
+  }
+
+  /**
+   * Get the major version number for this JVM.
+   * For a 1.3.2 JVM, this method would return <tt>1</tt>.
+   *
+   * @return the major version number for this JVM.
+   */
+  public int getMajor() { return major; }
+
+  /**
+   * Get the minor version number for this JVM.
+   * For a 1.3.2 JVM, this method would return <tt>3</tt>.
+   *
+   * @return the minor version number for this JVM.
+   */
+  public int getMinor() { return minor; }
+
+  /**
+   * Initialize the list of regular expressions.
+   */
+  private final synchronized void initRegExp()
+  {
+    synchronized (regexpStr) {
+      regexp = new RE[regexpStr.length];
+
+      int bad = 0;
+      for (int i = 0; i < regexpStr.length; i++) {
+        try {
+          regexp[i] = new RE(regexpStr[i]);
+        } catch (REException ree) {
+          System.err.println("For regexp \"" + regexpStr[i] + "\":");
+          ree.printStackTrace();
+          bad++;
+          regexp[i] = null;
+        }
+      }
+
+      if (bad > 0) {
+        RE[] tmpRE = new RE[regexp.length - bad];
+        for (int i = 0, j = 0; i < regexp.length; i++) {
+          if (regexp[i] != null) {
+            tmpRE[j++] = regexp[i];
+          }
+        }
+        regexp = tmpRE;
+      }
+    }
+  }
+
+  /**
+   * See if this JVM is at least as recent as the requested
+   * major/minor pair.<br>
+   * <br>
+   * For example, if a Java2 JVM is required, this routine
+   * would be called with <tt>major=1</tt> and <tt>minor=2</tt>.
+   *
+   * @param major JVM major version number
+   * @param minor JVM minor version number
+   *
+   * @return true if the JVM is at least the requested version.
+   */
+  public final boolean matchMinimum(int major, int minor)
+  {
+    return (this.major > major ||
+            (this.major == major && this.minor >= minor));
+  }
+}
diff --git a/visad/install/Main.java b/visad/install/Main.java
new file mode 100644
index 0000000..d5d95a8
--- /dev/null
+++ b/visad/install/Main.java
@@ -0,0 +1,1051 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import java.net.URL;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import javax.swing.JOptionPane;
+
+import visad.util.CmdlineGenericConsumer;
+import visad.util.CmdlineParser;
+
+class ClusterInstaller
+{
+  private Runtime runtime = null;
+  private String[] argList = null;
+
+  private String cPush;
+
+  public ClusterInstaller(String cPush)
+  {
+    this.cPush = cPush;
+  }
+
+  public Process push(String target)
+    throws IOException
+  {
+    return push(target, target);
+  }
+
+  public Process push(String source, String target)
+    throws IOException
+  {
+    if (runtime == null) {
+      runtime = Runtime.getRuntime();
+    }
+
+    if (argList == null) {
+      argList = new String[] { cPush, null, null };
+    }
+
+    argList[1] = source;
+    argList[2] = target;
+
+    return runtime.exec(argList);
+  }
+}
+
+public class Main
+  extends CmdlineGenericConsumer
+{
+  private static final String CLASSPATH_PROPERTY = "java.class.path";
+
+  private static final String ARCH_PROPERTY = "visad.install.arch";
+  private static final String HOME_PROPERTY = "visad.install.home";
+  private static final String PATH_PROPERTY = "visad.install.path";
+
+  private static final String SPLASH_NAME = "visad-splash.jpg";
+
+  private static final String JAR_NAME = "visad.jar";
+  private static final String VISAD_JAR_URL =
+    "ftp://ftp.ssec.wisc.edu/pub/visad-2.0/" + JAR_NAME;
+
+  private boolean debug;
+
+  private URL jarURL;
+  private ChooserList chooser;
+  private Path classpath, path;
+  private ArrayList jarList, javaList;
+  private File installerJar;
+  private JavaFile installerJava;
+  private File installerJavaDir, installerJavaJar;
+  private String archStr;
+  private String cPushStr;
+
+  private boolean useSuppliedJava, downloadLatestJar;
+//  private File jvmToUse;
+  private File javaInstallDir, jarInstallDir;
+  private ClusterInstaller clusterInstaller;
+
+  public Main(String[] args)
+  {
+    CmdlineParser cmdline = new CmdlineParser(this);
+    if (!cmdline.processArgs(args)) {
+      System.exit(1);
+      return;
+    }
+
+    File distDir;
+
+    // build File object from distribution directory property
+    String ddStr = System.getProperty(HOME_PROPERTY);
+    if (ddStr == null) {
+      distDir = null;
+    } else {
+      distDir = new File(ddStr);
+    }
+
+    // if no distribution directory, use current directory
+    if (distDir == null || !distDir.exists()) {
+      distDir = new File(".");
+    }
+
+    SplashScreen ss = null;
+
+    File splashFile = new File(distDir, SPLASH_NAME);
+    if (splashFile.exists()) {
+      ss = new SplashScreen(getPath(splashFile));
+      ss.setVisible(true);
+    }
+
+    boolean initResult = initialize(distDir);
+
+    if (ss != null) {
+      ss.setVisible(false);
+    }
+
+    if (!initResult) {
+      System.exit(1);
+      return;
+    }
+
+    if (debug) {
+      dumpInitialState(distDir);
+    }
+
+    useSuppliedJava = downloadLatestJar = false;
+//    jvmToUse = null;
+    javaInstallDir = jarInstallDir = null;
+    clusterInstaller = null;
+
+    queryUser();
+
+    if (debug) {
+      dumpInstallState();
+    }
+
+    install();
+  }
+
+
+  /**
+   * Check all java executables for minimum version.
+   *
+   * @param list list of File objects
+   * @param major major number of java version
+   * @param minor minor number of java version
+   */
+  private static final void checkJavaVersions(ArrayList list,
+                                              int major, int minor)
+  {
+    int i = 0;
+    while (i < list.size()) {
+      JavaFile f = new JavaFile((File )list.get(i));
+
+      if (f.matchMinimum(major, minor)) {
+        list.set(i++, f);
+      } else {
+        list.remove(i);
+      }
+    }
+  }
+
+  public int checkOption(String mainName, char ch, String arg)
+  {
+    if (ch == 'x') {
+      debug = true;
+      return 1;
+    }
+
+    return 0;
+  }
+
+  /**
+   * Push installed files out to cluster.
+   *
+   * @param mon progress monitor (ignored if <tt>null</tt>.)
+   * @param source source file/directory
+   * @param target directory on cluster machine where source is installed.
+   */
+  private final void clusterPush(ProgressMonitor mon, String source,
+                                 String target)
+  {
+    Process p;
+    try {
+      p = clusterInstaller.push(source, target);
+    } catch (IOException ioe) {
+      ioe.printStackTrace();
+      return;
+    }
+
+    try { p.getOutputStream().close(); } catch (IOException ioe) { }
+
+    BufferedReader in,err;
+    in = new BufferedReader(new InputStreamReader(p.getInputStream()));
+    err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+
+    // !! WARNING !!
+    //
+    // This loop will hang if too much output is sent to 'err' 
+    // without anything being sent to 'in'
+    //
+    boolean looping = true;
+    while (looping) {
+      if (in != null) {
+        try {
+          String line = in.readLine();
+          if (line == null) {
+            in = null;
+            looping = (err != null);
+          } else if (mon != null) {
+            mon.setDetail(line);
+          }
+        } catch (IOException ioe) {
+          ioe.printStackTrace();
+          break;
+        }
+      }
+
+      if (err != null) {
+        try {
+          if (err.ready() || in == null) {
+            String line = err.readLine();
+            if (line == null) {
+              err = null;
+              looping = (in != null);
+            } else if (mon != null) {
+              mon.setDetail(line);
+            }
+          }
+        } catch (IOException ioe) {
+          ioe.printStackTrace();
+          break;
+        }
+      }
+    }
+
+    try {
+      p.waitFor();
+    } catch (InterruptedException ie) {
+      ie.printStackTrace();
+    }
+  }
+
+  /**
+   * Ugly hack to make JVM binaries executable.
+   */
+  private final void makeJVMBinariesExecutable()
+  {
+    // if we don't know the path, we can't find the executable
+    if (path == null) {
+      return;
+    }
+
+    // find all instances of 'chmod'
+    ArrayList chmodList = path.find("chmod");
+    if (chmodList == null || chmodList.size() == 0) {
+      // if no 'chmod' found, we're done
+      return;
+    }
+
+    // only care about one of them, so arbitrarily grab the first one
+    File chmod = (File )chmodList.get(0);
+
+    // get a handle for the JVM executable directory
+    File jvmBin = new File(javaInstallDir, "bin");
+    if (!jvmBin.exists()) {
+      // if JVM binary directory doesn't exist, we're done
+      return;
+    }
+
+    // get the list of JVM executables
+    String[] binFiles = jvmBin.list();
+    if (binFiles == null || binFiles.length == 0) {
+      // if JVM binary directory is empty, we're done
+      return;
+    }
+
+    // create an array holding the command to be executed
+    String[] cmd = new String[2 + binFiles.length];
+    cmd[0] = chmod.toString();
+    cmd[1] = "555";
+
+    // preload the directory path into the stringbuffer
+    StringBuffer buf = new StringBuffer(jvmBin.toString());
+    buf.append('/');
+
+    // remember the buffer length
+    final int len = buf.length();
+
+    // add all executables to the command list
+    for (int i = 0; i < binFiles.length; i++) {
+      buf.setLength(len);
+      buf.append(binFiles[i]);
+
+      cmd[i+2] = buf.toString();
+    }
+
+    Process p;
+    try {
+      p = Runtime.getRuntime().exec(cmd);
+    } catch (IOException ioe) {
+      ioe.printStackTrace();
+      return;
+    }
+
+    try {
+      p.waitFor();
+    } catch (InterruptedException ie) {
+      ie.printStackTrace();
+    }
+  }
+
+  /**
+   * Pop up directory chooser dialog and process user's response.
+   *
+   * @param chooser previously created directory chooser window
+   * @param list list of directory choices
+   * @param title directory chooser window title
+   */
+  private final static File chooseDirectory(ChooserList chooser,
+                                            ArrayList list, String title)
+  {
+    final int listLen = (list == null ? 0 : list.size());
+
+    boolean allDirs = true;
+    for (int i = 0; i < listLen; i++) {
+      File f = (File )list.get(i);
+      if (!f.isDirectory()) {
+        allDirs = false;
+        break;
+      }
+    }
+
+    File[] dList;
+    if (!allDirs) {
+      // build list containing only directories
+      dList = new File[listLen];
+      for (int i = 0; i < listLen; i++) {
+        File f = (File )list.get(i);
+        if (f.isDirectory()) {
+          dList[i] = f;
+        } else {
+          dList[i] = new File(f.getParent());
+        }
+      }
+    } else if (listLen > 0) {
+      // build array from ArrayList
+      dList = (File[] )list.toArray(new File[list.size()]);
+    } else {
+      // empty/null list
+      dList = null;
+    }
+
+    chooser.setList(dList);
+    chooser.setFileSelectionMode(ChooserList.DIRECTORIES_ONLY);
+    chooser.setDialogTitle(title);
+    chooser.setApproveButtonToolTipText("Select directory");
+    chooser.setApproveButtonText("Select...");
+
+    int option = chooser.showDialog(null, "Select directory");
+    if (option == ChooserList.CANCEL_OPTION) {
+      return null;
+    }
+
+    File choice = chooser.getSelectedFile();
+    if (!choice.exists()) {
+      return choice;
+    }
+
+    if (choice.isDirectory()) {
+      return choice;
+    }
+
+    return new File(choice.getParent());
+  }
+
+  /**
+   * Pop up file chooser dialog and process user's response.
+   *
+   * @param chooser previously created file chooser window
+   * @param list list of file choices
+   * @param title file chooser window title
+   */
+  private static final File chooseFile(ChooserList chooser,
+                                       ArrayList list, String title)
+  {
+    if (list == null) {
+      chooser.setList(null);
+    } else {
+      chooser.setList((File[] )list.toArray(new File[list.size()]));
+    }
+    chooser.setFileSelectionMode(ChooserList.FILES_ONLY);
+    chooser.setDialogTitle(title);
+    chooser.setApproveButtonToolTipText("Choose file");
+
+    int option = chooser.showDialog(null, "Choose file");
+    if (option == ChooserList.CANCEL_OPTION) {
+      return null;
+    }
+
+    return chooser.getSelectedFile();
+  }
+
+  /**
+   * Dump post-initialization state for debugging
+   */
+  private final void dumpInitialState(File distDir)
+  {
+    if (distDir != null && distDir.exists()) {
+      System.out.println("Distribution directory: " + distDir);
+    } else {
+      System.out.println("Distribution directory: UNKNOWN!");
+    }
+
+    if (installerJavaDir != null) {
+      System.out.println("Supplied java directory: " + installerJavaDir);
+    }
+
+    if (installerJavaJar != null) {
+      System.out.println("Supplied java jar file: " + installerJavaJar);
+    }
+
+    if (installerJava != null) {
+      System.out.println("Supplied java: " + installerJava);
+    }
+
+    if (installerJar != null) {
+      System.out.println("Supplied visad.jar: " + installerJar);
+    }
+
+    if (jarList == null || jarList.size() == 0) {
+      System.err.println("No " + JAR_NAME + " found in " + classpath);
+    } else {
+      System.err.println("== jar file list ==");
+      for (int i = 0; i < jarList.size(); i++) {
+        System.out.println("#" + i + ": " +
+                           getPath((File )jarList.get(i)));
+      }
+    }
+
+    if (javaList == null || javaList.size() == 0) {
+      System.err.println("No java executable found in path " + path);
+    } else {
+      System.err.println("== java executable list ==");
+      for (int i = 0; i < javaList.size(); i++) {
+        System.out.println("#" + i + ": " +
+                           getPath((File )javaList.get(i)));
+      }
+    }
+
+    if (cPushStr == null) {
+      System.err.println("No cluster executable found in path " + path);
+    } else {
+      System.err.println("== cluster executable ==");
+      System.out.println(cPushStr);
+    }
+  }
+
+  /**
+   * Dump pre-installation state for debugging.
+   */
+  private final void dumpInstallState()
+  {
+    if (useSuppliedJava) {
+      System.err.println("Install java in " + javaInstallDir);
+//      if (jvmToUse != null) {
+//        System.err.println("!! 'jvmToUse' is set !!");
+//      }
+//    } else if (jvmToUse != null) {
+//      System.err.println("Use jvm in " + jvmToUse);
+//      if (javaInstallDir != null) {
+//        System.err.println("!! 'javaInstallDir' is set !!");
+//      }
+    }
+
+    if (downloadLatestJar) {
+      System.err.println("Download latest " + JAR_NAME);
+    }
+    System.err.println("Install " + JAR_NAME + " in " + jarInstallDir);
+
+    if (clusterInstaller != null) {
+      System.err.println("Push installed files out to cluster");
+    }
+  }
+
+  /**
+   * Remove and return the installer executable from the
+   * list of java executable JavaFile objects.
+   *
+   * @param list list of JavaFile objects
+   *
+   * @return <tt>null</tt> if installer-supplied java executable
+   *         was not found
+   */
+  private static final File extractInstallerFile(File distDir,
+                                                 ArrayList javaList)
+  {
+    String distPath = getPath(distDir);
+
+    java.util.Iterator iter = javaList.iterator();
+    while (iter.hasNext()) {
+      File thisFile = (File )iter.next();
+      if (getPath(thisFile).startsWith(distPath)) {
+        iter.remove();
+        return thisFile;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Return either the canonical path or, if that is not possible,
+   * the specified path.
+   *
+   * @param f File object
+   *
+   * @return either the canonical path or the originally specified path.
+   */
+  private static final String getPath(File f)
+  {
+    try {
+      return f.getCanonicalPath();
+    } catch (IOException ioe) {
+      return f.getPath();
+    }
+  }
+
+  /**
+   * Initialize internal state.
+   */
+  private final boolean initialize(File distDir)
+  {
+    // create a ChooserList (for speed purposes)
+    chooser = new ChooserList();
+
+    // set everything to null
+    classpath = path = null;
+    jarURL = null;
+    jarList = javaList = null;
+
+    // get class path elements
+    try {
+      classpath = new Path(System.getProperty(CLASSPATH_PROPERTY));
+    } catch (IllegalArgumentException iae) {
+      System.err.println(getClass().getName() +
+                         ": Couldn't get Java class path");
+      return false;
+    }
+
+    // get executable path elements
+    String pathStr = System.getProperty(PATH_PROPERTY);
+    if (pathStr == null) {
+      path = null;
+    } else {
+      try {
+        path = new Path(pathStr);
+      } catch (IllegalArgumentException iae) {
+        path = null;
+      }
+    }
+
+    // build the URL for the jar file
+    try {
+      jarURL = new URL(VISAD_JAR_URL);
+    } catch (java.net.MalformedURLException mue) {
+      jarURL = null;
+    }
+
+    // no installer-supplied jar file found yet
+    installerJar = null;
+
+    // find all visad jar files
+    jarList = classpath.findMatch(JAR_NAME);
+    if (jarList != null) {
+      loseDuplicates(jarList);
+      installerJar = extractInstallerFile(distDir, jarList);
+    }
+
+    // die if we can't install anything
+    if (installerJar == null && jarURL == null) {
+      System.err.println("Couldn't find either distributed jar file" +
+                         " or jar file URL!");
+      System.exit(1);
+    }
+
+    // no installer-supplied java found yet
+    installerJava = null;
+    installerJavaDir = null;
+
+    // find all java executables
+    if (path == null) {
+      javaList = null;
+    } else {
+      javaList = path.find("java");
+      if (javaList != null) {
+        loseDuplicates(javaList);
+        checkJavaVersions(javaList, 1, 2);
+        installerJava = (JavaFile )extractInstallerFile(distDir, javaList);
+
+        if (installerJava != null && installerJava.getName().equals("java")) {
+          File canonJava = new File(getPath(installerJava));
+
+          installerJavaDir = new File(canonJava.getParent());
+          if (installerJavaDir.getName().equals("bin")) {
+            installerJavaDir = new File(installerJavaDir.getParent());
+          }
+        }
+      }
+    }
+
+    // fetch the architecture string
+    archStr = System.getProperty(ARCH_PROPERTY);
+    if (archStr != null && archStr.length() == 0) {
+      archStr = null;
+    }
+
+    // no installer-supplied java jar file found yet
+    installerJavaJar = null;
+
+    // if no supplied java dir was found, look for java jar file
+    if (installerJavaDir == null && archStr != null) {
+      File tmpFile = new File(distDir, "jdk-" + archStr + ".jar");
+      if (tmpFile.exists()) {
+        installerJavaJar = tmpFile;
+      }
+    }
+
+    // no cluster installation executable found yet
+    cPushStr = null;
+
+    // find all cluster installation executables
+    if (path != null) {
+      ArrayList c3List = path.find("cpush");
+      if (c3List != null) {
+        loseDuplicates(c3List);
+        if (c3List != null && c3List.size() > 0) {
+          cPushStr = getPath((File )c3List.get(0));
+        }
+      }
+    }
+
+    return true;
+  }
+
+  public void initializeArgs()
+  {
+    debug = false;
+  }
+
+  /**
+   * Install VisAD.
+   */
+  private final void install()
+  {
+    ProgressMonitor mon = new ProgressMonitor();
+    mon.setPhase("Starting install");
+    mon.setVisible(true);
+
+    // install jar
+    if (downloadLatestJar) {
+      mon.setPhase("Downloading jar file");
+      Download.getFile(jarURL, jarInstallDir, false);
+    } else {
+      mon.setPhase("Copying jar file");
+      Util.copyFile(mon, installerJar, jarInstallDir, ".old");
+    }
+
+    // if they want a cluster install, push the jar file out to the nodes
+    if (clusterInstaller != null) {
+        mon.setPhase("Pushing jar file to cluster");
+      clusterPush(null, getPath(new File(jarInstallDir, JAR_NAME)),
+                  getPath(jarInstallDir));
+    }
+
+    // install JVM
+    if (useSuppliedJava) {
+      mon.setPhase("Copying JVM");
+      if (installerJavaDir != null) {
+        // install unpacked JVM
+        if (javaInstallDir.exists()) {
+          javaInstallDir = new File(javaInstallDir,
+                                    installerJavaDir.getName());
+        }
+
+        Util.copyDirectory(mon, installerJavaDir, javaInstallDir);
+      } else {
+        // install JVM from jar file
+        String jarTop = getJarTopDir(installerJavaJar);
+        if (jarTop == null && archStr != null) {
+          jarTop = "jdk-" + archStr;
+        }
+
+        if (jarTop != null) {
+          javaInstallDir = new File(javaInstallDir, jarTop);
+        }
+
+        Util.copyJar(mon, installerJavaJar, javaInstallDir);
+      }
+
+      mon.setPhase("Setting JVM executable bits");
+      makeJVMBinariesExecutable();
+
+      // if they want a cluster install, push the JVM out to the nodes
+      if (clusterInstaller != null) {
+        mon.setPhase("Pushing JVM to cluster");
+        clusterPush(mon, javaInstallDir.toString(),
+                    javaInstallDir.getParent().toString());
+      }
+    }
+
+    // all done
+    mon.setPhase("Install finished!");
+    try { Thread.sleep(2000); } catch (InterruptedException ie) { }
+  }
+
+  /**
+   * Return the top directory contained in the specified jar file
+   *
+   * @param source jar file to examine
+   *
+   * @return either the sole top-level directory in the jar file
+   *         or <tt>null</tt> if the jar file contains multiple
+   *         top-level files/directories.
+   */
+  private String getJarTopDir(File source)
+  {
+    // try to open the jar file
+    JarFile jar;
+    try {
+      jar = new JarFile(source);
+    } catch (IOException ioe) {
+      return null;
+    }
+
+    String topDir = null;
+
+    Enumeration en = jar.entries();
+    while (en.hasMoreElements()) {
+      JarEntry entry = (JarEntry )en.nextElement();
+
+      String entryName = entry.getName();
+
+      // skip manifest files
+      if (JarFile.MANIFEST_NAME.startsWith(entryName)) {
+        continue;
+      }
+
+      // get the top directory for this entry
+      String dirName;
+      int dirIdx = entryName.indexOf(File.separatorChar);
+      if (dirIdx < 0) {
+        dirName = entryName;
+      } else {
+        dirName = entryName.substring(0, dirIdx);
+      }
+
+      if (topDir == null) {
+        // if we haven't seen a top-level directory, save this
+        topDir = dirName;
+      } else if (!topDir.equals(dirName)) {
+        // we already have a different to-level dir, so give up
+        topDir = null;
+        break;
+      }
+    }
+
+    return topDir;
+  }
+
+  /**
+   * Remove duplicate objects from the list.
+   *
+   * @param list of Objects
+   */
+  private static final void loseDuplicates(ArrayList list)
+  {
+    for (int i = 0; i < list.size(); i++) {
+      Object objI = list.get(i);
+
+      int j = i + 1;
+      while (j < list.size()) {
+        Object objJ = list.get(j);
+
+        if (!objI.equals(objJ)) {
+          j++;
+        } else {
+          list.remove(j);
+        }
+      }
+    }
+  }
+
+  public String optionUsage()
+  {
+    return super.optionUsage() + " [-x(debug)]";
+  }
+
+  /**
+   * Query user about installation options.
+   */
+  private final void queryUser()
+  {
+    final int STEP_INSTALL_JAR = 0;
+    final int STEP_DOWNLOAD_JAR = 1;
+    final int STEP_USE_SUPPLIED = 2;
+    final int STEP_INSTALL_JAVA = 3;
+    final int STEP_CLUSTER = 4;
+    final int STEP_FINISHED = 5;
+
+    int step = 0;
+    while (step < STEP_FINISHED) {
+      switch (step) {
+      case STEP_USE_SUPPLIED:
+        if (queryUserUseSuppliedJava()) {
+          step++;
+        } else {
+          step--;
+        }
+        break;
+      case STEP_INSTALL_JAVA:
+        step += queryUserInstallJava();
+        break;
+      case STEP_INSTALL_JAR:
+        step += queryUserInstallJar();
+        break;
+      case STEP_DOWNLOAD_JAR:
+        if (queryUserDownloadJar()) {
+          step++;
+        } else {
+          step--;
+        }
+        break;
+      case STEP_CLUSTER:
+        if (queryUserClusterPush()) {
+          step++;
+        } else {
+          step--;
+        }
+        break;
+      }
+
+      if (step < 0) {
+        if (queryUserCancelInstall()) {
+          System.exit(0);
+          return;
+        }
+
+        // don't go negative
+        step = 0;
+      }
+    }
+  }
+
+  /**
+   * Ask user if they want to cancel the install.
+   *
+   * @return <tt>true</tt> if user wants to cancel.
+   */
+  private final boolean queryUserCancelInstall()
+  {
+    String canMsg = "Do you want to cancel this install?";
+
+    int n = JOptionPane.showConfirmDialog(null, canMsg,
+                                          "Cancel install?",
+                                          JOptionPane.YES_NO_OPTION);
+    return (n == JOptionPane.YES_OPTION);
+  }
+
+  /**
+   * Ask user if they want to install everything on the cluster.
+   *
+   * @return false if [Cancel] button was pressed,
+   *         true if another choice was made.
+   */
+  private final boolean queryUserClusterPush()
+  {
+    int result = JOptionPane.NO_OPTION;
+    if (cPushStr != null) {
+      String msg = "Would you like to push everything out to the cluster?";
+      String title = "Push files to cluster?";
+
+      result = JOptionPane.showConfirmDialog(null, msg, title,
+                                             JOptionPane.YES_NO_CANCEL_OPTION);
+    }
+
+    if (result != JOptionPane.YES_OPTION) {
+      clusterInstaller = null;
+    } else {
+      clusterInstaller = new ClusterInstaller(cPushStr);
+    }
+
+    return (result != JOptionPane.CANCEL_OPTION);
+  }
+
+  /**
+   * Ask user if they want to download the latest visad.jar
+   *
+   * @return false if [Cancel] button was pressed,
+   *         true if another choice was made.
+   */
+  private final boolean queryUserDownloadJar()
+  {
+    int result = JOptionPane.YES_OPTION;
+    if (installerJar != null) {
+      String msg = "Would you like to download the latest " + JAR_NAME + "?";
+      String title = "Download latest " + JAR_NAME + "?";
+
+      result = JOptionPane.showConfirmDialog(null, msg, title,
+                                             JOptionPane.YES_NO_CANCEL_OPTION);
+    }
+
+    downloadLatestJar = (result == JOptionPane.YES_OPTION);
+
+    return (result != JOptionPane.CANCEL_OPTION);
+  }
+
+  /**
+   * Ask user to specify the directory in which the visad.jar file
+   * should be installed.
+   *
+   * @return -1 if [Cancel] button was pressed,
+   *          0 if a bad directory was selected,
+   *          1 if a valid choice was made.
+   */
+  private final int queryUserInstallJar()
+  {
+    jarInstallDir = chooseDirectory(chooser, jarList,
+                                    "Select the directory where the VisAD jar file should be installed");
+
+    if (jarInstallDir == null) {
+      return -1;
+    }
+
+    if (!jarInstallDir.canWrite()) {
+      JOptionPane.showMessageDialog(null,
+                                    "Cannot write to that directory!",
+                                    "Bad directory?",
+                                    JOptionPane.ERROR_MESSAGE);
+      return 0;
+    }
+
+    return 1;
+  }
+
+  /**
+   * Ask user to specify the directory in which the supplied
+   * JDK should be installed.
+   *
+   * @return -1 if [Cancel] button was pressed,
+   *          0 if a bad directory was selected,
+   *          1 if a valid choice was made.
+   */
+  private final int queryUserInstallJava()
+  {
+    javaInstallDir = null;
+//    jvmToUse = null;
+    if (useSuppliedJava) {
+      javaInstallDir = chooseDirectory(chooser, null,
+                                       "Select the directory in which the JDK should be installed");
+      if (javaInstallDir == null) {
+        return -1;
+      }
+
+      if (!javaInstallDir.canWrite()) {
+        JOptionPane.showMessageDialog(null,
+                                      "Cannot write to that directory!",
+                                      "Bad directory?",
+                                      JOptionPane.ERROR_MESSAGE);
+        return 0;
+      }
+//    } else {
+//      jvmToUse = chooseFile(chooser, javaList,
+//                            "Select the java program to use");
+//      if (jvmToUse == null) {
+//        return -1;
+//      }
+    }
+
+    return 1;
+  }
+
+  /**
+   * Ask user if they'd like to have the supplied JDK installed.
+   *
+   * @return false if [Cancel] button was pressed,
+   *         true if another choice was made.
+   */
+  private final boolean queryUserUseSuppliedJava()
+  {
+    int result = JOptionPane.NO_OPTION;
+    if (installerJavaDir != null || installerJavaJar != null) {
+      String msg;
+      if (installerJavaDir != null) {
+        msg = "Would you like to install the supplied " +
+          " Java Development Kit " + installerJava.getMajor() + "." +
+          installerJava.getMinor() + " (" +
+          installerJava.getVersionString() + ")?";
+      } else {
+        msg = "Would you like to install the supplied " +
+          " Java Development Kit?";
+      }
+
+      String title = "Install supplied JDK?";
+
+      result = JOptionPane.showConfirmDialog(null, msg, title,
+                                             JOptionPane.YES_NO_CANCEL_OPTION);
+    }
+
+    useSuppliedJava = (result == JOptionPane.YES_OPTION);
+
+    return (result != JOptionPane.CANCEL_OPTION);
+  }
+
+  public static final void main(String[] args)
+  {
+    new Main(args);
+    System.exit(0);
+  }
+}
diff --git a/visad/install/Path.java b/visad/install/Path.java
new file mode 100644
index 0000000..92211ef
--- /dev/null
+++ b/visad/install/Path.java
@@ -0,0 +1,156 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.io.File;
+
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+public class Path
+{
+  private ArrayList path;
+
+  public Path(String pathStr)
+    throws IllegalArgumentException
+  {
+    if (pathStr == null) {
+      throw new IllegalArgumentException("Null path string");
+    }
+    if (pathStr.length() == 0) {
+      throw new IllegalArgumentException("Empty path string");
+    }
+
+    StringTokenizer tok = new StringTokenizer(pathStr, File.pathSeparator);
+
+    final int numTokens = tok.countTokens();
+    if (numTokens == 0) {
+      throw new IllegalArgumentException("Empty path string");
+    }
+
+    path = new ArrayList();
+
+    int i = 0;
+    while (tok.hasMoreTokens()) {
+      path.add(tok.nextToken());
+    }
+  }
+
+  public ArrayList find(String file)
+  {
+    if (file == null || file.length() == 0) {
+      return null;
+    }
+
+    final int pathLen = path.size();
+
+    ArrayList list = null;
+    for (int i = 0; i < pathLen; i++) {
+      File f = new File((String )path.get(i), file);
+      if (f.isFile()) {
+        if (list == null) {
+          list = new ArrayList();
+        }
+
+        list.add(f);
+      }
+    }
+
+    if (list == null) {
+      return null;
+    }
+
+    return list;
+  }
+
+  public ArrayList findMatch(String file)
+  {
+    if (file == null || file.length() == 0) {
+      return null;
+    }
+
+    final int pathLen = path.size();
+
+    ArrayList list = null;
+    for (int i = 0; i < pathLen; i++) {
+      String pElem = (String )path.get(i);
+
+      if (pElem.endsWith(File.separator + file)) {
+        File f = new File(pElem);
+        if (f.exists()) {
+          if (list == null) {
+            list = new ArrayList();
+          }
+
+          list.add(f);
+        }
+      }
+    }
+
+    return list;
+  }
+
+  public String toString()
+  {
+    if (path == null || path.size() == 0) {
+      return null;
+    }
+
+    StringBuffer buf = new StringBuffer((String )path.get(0));
+    for (int i = 1; i < path.size(); i++) {
+      buf.append(File.pathSeparator);
+      buf.append(path.get(i));
+    }
+
+    return buf.toString();
+  }
+
+  public static final void main(String[] args)
+    throws IllegalArgumentException
+  {
+    if (args.length < 2) {
+      System.err.println("Usage: java Path pathString file [file ...]");
+      System.exit(1);
+      return;
+    }
+
+    Path p = new Path(args[0]);
+    for (int i = 1; i < args.length; i++) {
+      ArrayList l = p.findMatch(args[i]);
+      if (l == null) {
+        l = p.find(args[i]);
+        if (l == null) {
+          System.err.println("Couldn't find \"" + args[i] + "\"");
+          continue;
+        }
+      }
+
+      System.out.println(args[i] + ":");
+      for (int j = 0; j < l.size(); j++) {
+        System.out.println("  " + l.get(j));
+      }
+    }
+
+    System.exit(0);
+  }
+}
diff --git a/visad/install/ProgressMonitor.java b/visad/install/ProgressMonitor.java
new file mode 100644
index 0000000..9d2913b
--- /dev/null
+++ b/visad/install/ProgressMonitor.java
@@ -0,0 +1,237 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JCheckBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/**
+ * Widget showing general and detailed progress.
+ */
+public class ProgressMonitor
+  extends JFrame
+{
+  /**
+   * Label used to track and display detailed progress information.
+   */
+  class DetailLabel
+    extends JLabel
+  {
+    private String text;
+    private boolean show;
+
+    DetailLabel(String text, boolean show)
+    {
+      super(text);
+
+      this.text = text;
+      this.show = show;
+    }
+
+    /**
+     * Indicate whether detailed progress is shown.
+     *
+     * @return true if detailed progress is shown.
+     */
+    final boolean isShown() { return show; }
+
+    /**
+     * Set new detailed progress text.
+     *
+     * @param text detailed progress text
+     */
+    public final void setText(String text)
+    {
+      this.text = text;
+
+      if (show) {
+        super.setText(text);
+      }
+    }
+
+    /**
+     * Toggle visibility of detailed progress text.
+     */
+    final void toggleShown()
+    {
+      show = !show;
+      if (show) {
+        super.setText(text);
+      } else {
+        super.setText("");
+        this.invalidate();
+      }
+    }
+  }
+
+  // font used to draw text
+  private Font labelFont;
+
+  // internal widgets
+  private JLabel phaseLabel;
+  private DetailLabel detailLabel;
+  private JCheckBox detailBox;
+
+  /**
+   * Create a progress monitor which uses a 12 pt Sans Serif font and
+   * initially displays the detailed progress text.
+   */
+  public ProgressMonitor()
+  {
+    this(true);
+  }
+
+  /**
+   * Create a progress monitor which uses a 12 pt Sans Serif font.
+   *
+   * @param showDetails <tt>true</tt> if details are initially displayed.
+   */
+  public ProgressMonitor(boolean showDetails)
+  {
+    this(new Font("sansserif", Font.PLAIN, 12), showDetails);
+  }
+
+  /**
+   * Create a progress monitor which
+   * initially displays the detailed progress text.
+   *
+   * @param labelFont the font used to draw all text
+   */
+  public ProgressMonitor(Font labelFont)
+  {
+    this(labelFont, true);
+  }
+
+  /**
+   * Create a progress monitor.
+   *
+   * @param showDetails <tt>true</tt> if details are initially displayed.
+   * @param labelFont the font used to draw all text
+   */
+  public ProgressMonitor(Font labelFont, boolean showDetails)
+  {
+    super("Installation Progress Monitor");
+
+    //
+    // compute label height & width
+    //
+
+    final String w40 = "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW";
+    final String longStr = w40 + w40 + w40;
+
+    FontMetrics fm = getFontMetrics(labelFont);
+
+    final int width = fm.stringWidth(w40);
+    final int height = fm.getHeight();
+
+    Dimension d = new Dimension(width, height);
+
+    //
+    // build UI elements
+    //
+
+    phaseLabel = new JLabel("Phase");
+    phaseLabel.setFont(labelFont);
+    phaseLabel.setMinimumSize(d);
+    phaseLabel.setPreferredSize(d);
+    phaseLabel.setMaximumSize(d);
+
+    detailLabel = new DetailLabel("", showDetails);
+    detailLabel.setFont(labelFont);
+    detailLabel.setMinimumSize(d);
+    detailLabel.setPreferredSize(d);
+    detailLabel.setMaximumSize(d);
+
+    detailBox = new JCheckBox("Show details", showDetails);
+    detailBox.setFont(labelFont);
+    detailBox.addItemListener(new ItemListener()
+      {
+        public void itemStateChanged(ItemEvent e)
+        {
+          detailLabel.toggleShown();
+          if (detailLabel.isShown()) {
+            pack();
+          }
+        }
+      });
+
+    //
+    // Add UI elements
+    //
+
+    JPanel pane = new JPanel();
+    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+    pane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+    pane.add(Box.createHorizontalStrut(10));
+    pane.add(phaseLabel);
+    pane.add(Box.createHorizontalStrut(10));
+    pane.add(detailLabel);
+    pane.add(Box.createHorizontalStrut(10));
+    pane.add(detailBox);
+    pane.add(Box.createHorizontalStrut(10));
+
+    setContentPane(pane);
+    pack();
+  }
+
+  /**
+   * Indicate whether detailed progress is shown.
+   *
+   * @return true if detailed progress is shown.
+   */
+  public final boolean isDetailShown() { return detailLabel.isShown(); }
+
+  /**
+   * Set new detailed progress text.
+   *
+   * @param detail detailed progress text
+   */
+  public final void setDetail(String detail)
+  {
+    detailLabel.setText(detail);
+  }
+
+  /**
+   * Set new progress phase text.
+   *
+   * @param phase	 progress phase text
+   */
+  public final void setPhase(String phase)
+  {
+    phaseLabel.setText(phase);
+    detailLabel.setText("");
+  }
+}
diff --git a/visad/install/README b/visad/install/README
new file mode 100644
index 0000000..99d9c3e
--- /dev/null
+++ b/visad/install/README
@@ -0,0 +1,56 @@
+To install VisAD-in-a-Box:
+1) If you've received an visad-in-a-box.tar file,
+   run 'tar xvf visad-in-a-box.tar'
+2) If you've received an visad-in-a-box.tar.gz file,
+   run 'gzcat -c visad-in-a-box.tar | tar xvf -'
+3) Move into the extracted 'visad-in-a-box'
+   distribution directory and run 'install_visad'
+4) Answer the few installation questions, then wait
+   for everything to finish installing.
+5) You can remove the 'visad-in-a-box' distribution
+   directory after the installation is complete.
+
+Note: The 'cpush' utility must be available in order
+      to install on all nodes in a cluster.
+
+
+To keep your visad.jar file up to date:
+   Simply run 'java visad.install.UpdateJar <dir>'
+   where <dir> is the directory in which you've
+   installed your 'visad.jar' file.
+
+   This will only update the local 'visad.jar' file
+   if it is older than the distributed 'visad.jar'
+   file.
+
+To create a new VisAD-in-a-Box distribution:
+1) Create a distribution directory (in this example,
+   the distribution directory will be named
+   'visad-in-a-box')
+2) Copy 'visad/install/install_visad' to the
+   'visad-in-a-box' directory.
+3) Copy a 'visad.jar' file to the 'visad-in-a-box'
+   directory.
+ a) If you have a VisAD source distribution, you can
+    run 'make compile' (or 'make recompile') to
+    compile all the VisAD sources, then run
+    'make classes' to create 'visad.jar' in the
+    directory about the Makefile.
+4) If you want to include Java executables, you can
+   do either of the following:
+ a) Copy a JDK directory tree into the
+    'visad-in-a-box' directory and name it
+    'jdk-<OS>-<MACH>' where <OS> is whatever is
+    printed by 'uname -s' and <MACH> is whatever is
+    printed by 'uname -p'.
+
+    If you do this, the included java executable will
+    be used to run the installer on machines which
+    match this machine's OS and MACH.
+ b) Package up a JDK directory tree in a jar file
+    named 'jdk-<OS>-<MACH>.jar' (where <OS> and
+    <MACH> are as in the item above.)
+5) In the directory above the 'visad-in-a-box'
+   directory, package everything up with
+   'tar cvf visad-in-a-box.tar visad-in-a-box' (or
+   whatever you've named your distribution directory.)
diff --git a/visad/install/README.html b/visad/install/README.html
new file mode 100644
index 0000000..d7be911
--- /dev/null
+++ b/visad/install/README.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<html>
+<head>
+<title>VisAD-In-A-Box</title>
+</head>
+<body>
+<h1>VisAD-in-a-Box:</h1>
+<h2>To download VisAD-in-a-Box:</h2>
+<a href="ftp://ftp.ssec.wisc.edu/pub/visad-2.0/visad-in-a-box.tar.gz">VisAD-in-a-Box</a><br>
+<br>
+<b>Note:</b> This currently contains only the Solaris JDK.
+<h2>To install VisAD-in-a-Box:</h2>
+<ol>
+ <li>If you've received an <tt>visad-in-a-box.tar</tt> file,
+     run <tt>tar xvf visad-in-a-box.tar</tt>
+ <li>If you've received an <tt>visad-in-a-box.tar.gz</tt> file,
+     run <tt>gzcat -c visad-in-a-box.tar | tar xvf -</tt>
+ <li>Move into the extracted <tt>visad-in-a-box</tt> distribution
+     directory and run <tt>install_visad</tt>
+ <li>Answer the few installation questions,
+     then wait for everything to finish installing.
+ <li>You can remove the <tt>visad-in-a-box</tt> distribution
+     directory after the installation is complete.
+</ol>
+
+<b>Note:</b> The <tt>cpush</tt> utility must be available in order
+             to install on all nodes in a cluster.
+<br>
+<h2>To keep your visad.jar file up to date:</h2>
+Simply run <tt>java visad.install.UpdateJar <i>dir</i></tt>
+where <i>dir</i> is the directory in which you've installed
+your visad.jar file.<br>
+<br>
+This will only update the local <tt>visad.jar</tt> file if it is older
+than the distributed <tt>visad.jar</tt> file.
+
+<h2>To create a new VisAD-in-a-Box distribution:</h2>
+
+<ol>
+ <li>Create a distribution directory (in this example, the distribution
+     directory will be named <tt>visad-in-a-box</tt>.)
+ <li>Copy visad/install/install_visad to the <tt>visad-in-a-box</tt>
+     directory
+ <li>Copy a visad.jar file to the <tt>visad-in-a-box</tt> directory.
+ <ul>
+  <li>If you have a VisAD source distribution,
+      you can run <tt>make compile</tt> (or <tt>make recompile</tt>)
+      to compile all the VisAD sources, then run
+      <tt>make classes</tt> to create visad.jar in the
+      directory about the Makefile.
+ </ul>
+ <li>If you want to include Java executables, you can
+     do either of the following:
+  <ul>
+   <li>Copy a JDK directory tree into the <tt>visad-in-a-box</tt>
+       directory and name it jdk-<i>OS</i>-<i>MACH</i>
+       where <i>OS</i> is whatever is printed by <tt>uname -s</tt>
+       and <i>MACH</i> is whatever is printed by <tt>uname -p</tt>.
+   <br>
+       If you do this, the included <tt>java</tt> executable
+       will be used to run the installer on machines which
+       match this machine's <i>OS</i> and <i>MACH</i>.
+   <li>Package up a JDK directory tree in a jar file
+       named jdk-<i>OS</i>-<i>MACH</i>.jar (where <i>OS</i> and <i>MACH</i>
+       are as in the item above.)
+  </ul>
+ <li>In the directory above the <tt>visad-in-a-box</tt> directory,
+     package everything up with
+     <tt>tar cvf visad-in-a-box.tar visad-in-a-box</tt>
+     (or whatever you've named your distribution directory.)
+</ol>
+</body>
+</html>
diff --git a/visad/install/SplashScreen.java b/visad/install/SplashScreen.java
new file mode 100644
index 0000000..6dee469
--- /dev/null
+++ b/visad/install/SplashScreen.java
@@ -0,0 +1,296 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Toolkit;
+
+import javax.swing.ImageIcon;
+import javax.swing.JPanel;
+import javax.swing.JWindow;
+
+/**
+ * a simple splash screen that displays an image with a black border.
+ */
+public class SplashScreen
+  extends JWindow
+{
+  private static final int BORDER_WIDTH = 5;
+  private static final int WIDTH_PAD = 2;
+  private static final int HEIGHT_PAD = 2;
+
+  private ImageIcon image;
+  private String[] names, values;
+  private Font textFont;
+
+  private int nameLen;
+  private int  lineHeight;
+  private int textX, textY, textWidth, textHeight;
+
+  /**
+   * Construct a splash screen which displays the specified image.
+   *
+   * @param imageName the image file name.
+   */
+  public SplashScreen(String imageName)
+  {
+    this(new ImageIcon(imageName), null, null);
+  }
+
+  /**
+   * Construct a splash screen which displays the specified image.
+   *
+   * @param image the image.
+   */
+  public SplashScreen(ImageIcon image)
+  {
+    this(image, null, null);
+  }
+
+  /**
+   * Construct a splash screen which displays the specified text.
+   *
+   * @param names list of names.
+   * @param values list of values.
+   */
+  public SplashScreen(String[] names, String[] values)
+  {
+    this((ImageIcon )null, names, values);
+  }
+
+  /**
+   * Construct a splash screen with both an image and some text.
+   *
+   * @param imageName the image file name.
+   * @param names list of names.
+   * @param values list of values.
+   */
+  public SplashScreen(String imageName, String[] names, String[] values)
+  {
+    this(new ImageIcon(imageName), names, values);
+  }
+
+  /**
+   * Construct a splash screen with both an image and some text.
+   *
+   * @param image the image.
+   * @param names list of names.
+   * @param values list of values.
+   */
+  public SplashScreen(ImageIcon image, String[] names, String[] values)
+  {
+    this.image = image;
+    this.names = names;
+    this.values = values;
+
+    textFont = new Font("sansserif", Font.PLAIN, 12);
+
+    setContentPane(createSplash());
+
+    pack();
+    center();
+  }
+
+  private void center()
+  {
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    Dimension splashSize = getContentPane().getPreferredSize();
+
+    int x = (screenSize.width - splashSize.width) / 2;
+    int y = (screenSize.height - splashSize.height) / 2;
+
+    setBounds(x, y, splashSize.width, splashSize.height);
+  }
+
+  private JPanel createSplash()
+  {
+    JPanel panel = new JPanel();
+
+    Dimension d;
+    if (image == null) {
+      d = new Dimension(400, 200);
+    } else {
+      d = new Dimension(image.getIconWidth() + (BORDER_WIDTH * 2),
+                        image.getIconHeight() + (BORDER_WIDTH * 2));
+    }
+
+    panel.setMinimumSize(d);
+    panel.setPreferredSize(d);
+    panel.setMaximumSize(d);
+
+    return panel;
+  }
+
+  private void drawTextArea(Graphics g)
+  {
+    // blank out the text area
+    g.setColor(Color.white);
+    g.fillRect(textX, textY, textWidth, textHeight);
+
+    // fill in text area
+    g.setColor(Color.black);
+    int yPos = textY + (lineHeight - HEIGHT_PAD);
+    for (int i = 0; i < values.length; i++) {
+      if (values[i] != null) {
+        g.drawString(names[i], textX+WIDTH_PAD, yPos);
+        g.drawString(values[i], textX+WIDTH_PAD+nameLen+WIDTH_PAD, yPos);
+        yPos += lineHeight;
+      }
+    }
+  }
+
+private void dumpFonts()
+{
+  java.awt.GraphicsEnvironment ge;
+  ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
+
+  Font[] allFonts = ge.getAllFonts();
+  for (int i = 0; i < allFonts.length; i++) {
+    System.out.println("#"+i+": " + allFonts[i]);
+  }
+}
+
+  private boolean initializeTextArea(Graphics g)
+  {
+    if (names == null || values == null) {
+      return false;
+    }
+
+    java.awt.FontMetrics fm = g.getFontMetrics();
+
+    int numLines, valueLen;
+    numLines = nameLen = valueLen = 0;
+    for (int i = 0; i < values.length; i++) {
+      if (values[i] != null) {
+        int l = fm.stringWidth(names[i]);
+        if (l > nameLen) {
+          nameLen = l;
+        }
+
+        l = fm.stringWidth(values[i]);
+        if (l > valueLen) {
+          valueLen = l;
+        }
+
+        numLines++;
+      }
+    }
+
+    // if there's nothing to write, we're done
+    if (valueLen == 0) {
+      return false;
+    }
+
+    int lineWidth = nameLen + WIDTH_PAD + valueLen;
+    lineHeight = fm.getHeight() + HEIGHT_PAD;
+
+    // compute the text area size
+    textWidth = WIDTH_PAD + lineWidth + WIDTH_PAD;
+    textHeight = HEIGHT_PAD + numLines * lineHeight;
+    textX = 20;
+    textY = getHeight() - (20 + textHeight);
+
+    return true;
+  }
+
+  public void paint(Graphics g)
+  {
+    g.setFont(textFont);
+
+    boolean drawText = initializeTextArea(g);
+
+    final int width = getWidth();
+    final int height = getHeight();
+
+    // load either the image or a magenta blob
+    if (image != null) {
+      g.drawImage(image.getImage(), BORDER_WIDTH, BORDER_WIDTH,
+                  getBackground(), this);
+    } else {
+      g.setColor(Color.magenta); 
+      g.fillRect(0, 0, width, height);
+    }
+
+    // draw a black border around the image
+    g.setColor(Color.black);
+    g.fillRect(0, 0, width, BORDER_WIDTH);
+    g.fillRect(0, 0, BORDER_WIDTH, height);
+    g.fillRect(width-BORDER_WIDTH, BORDER_WIDTH,
+               BORDER_WIDTH, height-BORDER_WIDTH);
+    g.fillRect(BORDER_WIDTH, height-BORDER_WIDTH,
+               width-BORDER_WIDTH, BORDER_WIDTH);
+
+    if (drawText) {
+      drawTextArea(g);
+    }
+  }
+
+  public void setTextFont(Font f) { textFont = f; }
+
+  public static final void main(String[] args)
+  {
+    Font f = null;
+    if (args.length > 0) {
+      int size = 12;
+      if (args.length > 1) {
+        try {
+          size = Integer.parseInt(args[1]);
+        } catch (NumberFormatException nfe) {
+          System.err.println("Bad font size \"" + args[1] + "\"");
+          System.exit(1);
+        }
+      }
+
+      f = new Font(args[0], Font.PLAIN, size);
+    }
+
+    ImageIcon img = new ImageIcon("visad-splash.jpg");
+    String[] names = new String[] { "Name:", "Machine:" };
+    String[] values = new String[] { "dglo", "hyde.ssec.wisc.edu" };
+
+    SplashScreen ss;
+
+    ss = new SplashScreen(img);
+    if (f != null) ss.setTextFont(f);
+    ss.setVisible(true);
+    try { Thread.sleep(7000); } catch (InterruptedException ie) { }
+    ss.setVisible(false);
+
+    ss = new SplashScreen(names, values);
+    if (f != null) ss.setTextFont(f);
+    ss.setVisible(true);
+    try { Thread.sleep(7000); } catch (InterruptedException ie) { }
+    ss.setVisible(false);
+
+    ss = new SplashScreen(img, names, values);
+    if (f != null) ss.setTextFont(f);
+    ss.setVisible(true);
+    try { Thread.sleep(7000); } catch (InterruptedException ie) { }
+    ss.setVisible(false);
+
+    System.exit(0);
+  }
+}
diff --git a/visad/install/TestDownload.java b/visad/install/TestDownload.java
new file mode 100644
index 0000000..ccf2271
--- /dev/null
+++ b/visad/install/TestDownload.java
@@ -0,0 +1,115 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.io.File;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import java.util.ArrayList;
+
+import visad.util.CmdlineConsumer;
+import visad.util.CmdlineParser;
+
+public class TestDownload
+  extends Download
+  implements CmdlineConsumer
+{
+  private File saveDir;
+  private ArrayList urlList;
+
+  public TestDownload(String[] args)
+  {
+    CmdlineParser cmdline = new CmdlineParser(this);
+    if (!cmdline.processArgs(args)) {
+      System.err.println("Exiting...");
+      System.exit(1);
+    }
+
+    if (urlList != null) {
+      for (int i = 0; i < urlList.size(); i++) {
+        getFile((URL )urlList.get(i), saveDir, true);
+      }
+    }
+  }
+
+  public int checkKeyword(String mainName, int thisArg, String[] args)
+  {
+    URL url;
+    try {
+      url = new URL(args[thisArg]);
+    } catch (MalformedURLException me) {
+      System.err.println(mainName + ": Bad URL \"" + args[thisArg] +
+                         "\": " + me.getMessage());
+      return -1;
+    }
+
+    if (urlList == null) {
+      urlList = new ArrayList();
+    }
+
+    urlList.add(url);
+    return 1;
+  }
+
+  public int checkOption(String mainName, char ch, String arg)
+  {
+    if (ch == 'd') {
+      saveDir = new File(arg);
+      if (!saveDir.isDirectory()) {
+        System.err.println(mainName + ": \"" + arg + "\" is not a directory");
+        return -1;
+      }
+
+      return 2;
+    }
+
+    return 0;
+  }
+
+  public String keywordUsage() { return " url [url ...]"; }
+
+  public String optionUsage() { return " -d saveDir"; }
+
+  public boolean finalizeArgs(String mainName)
+  {
+    if (saveDir == null) {
+      System.err.println(mainName + ": Please specify a save directory");
+      return false;
+    }
+
+    return true;
+  }
+
+  public void initializeArgs()
+  {
+    saveDir = null;
+    urlList = null;
+  }
+
+  public static final void main(String[] args)
+  {
+    new TestDownload(args);
+  }
+}
diff --git a/visad/install/TestUtil.java b/visad/install/TestUtil.java
new file mode 100644
index 0000000..a31e075
--- /dev/null
+++ b/visad/install/TestUtil.java
@@ -0,0 +1,188 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.io.File;
+
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import visad.install.ProgressMonitor;
+import visad.install.Util;
+
+import visad.util.CmdlineGenericConsumer;
+import visad.util.CmdlineParser;
+
+public class TestUtil
+  extends CmdlineGenericConsumer
+{
+  private File fromFile, toFile;
+  private String suffix;
+  private boolean trackProgress;
+
+  public class MakeCopy
+    extends Thread
+  {
+    private ProgressMonitor mon;
+    private File fromFile, toFile;
+    private String suffix;
+    private boolean result;
+
+    MakeCopy(ProgressMonitor mon, File fromFile, File toFile, String suffix)
+    {
+      this.mon = mon;
+      this.fromFile = fromFile;
+      this.toFile = toFile;
+      this.suffix = suffix;
+      this.result = false;
+    }
+
+    public void run()
+    {
+      if (fromFile.isDirectory()) {
+        result = Util.copyDirectory(mon, fromFile, toFile, suffix);
+      } else {
+        result = Util.copyJar(mon, fromFile, toFile, suffix);
+        if (!result) {
+          result = Util.copyFile(mon, fromFile, toFile, suffix);
+        }
+      }
+      System.err.println("Result was " + result);
+    }
+
+    boolean getResult() { return result; }
+  }
+
+  public TestUtil(String[] args)
+  {
+    CmdlineParser cmdline = new CmdlineParser(this);
+    if (!cmdline.processArgs(args)) {
+      System.err.println("Exiting...");
+      System.exit(1);
+    }
+
+    ProgressMonitor mon = null;
+    if (trackProgress) {
+      JFrame win = new JFrame("Frame-o-licious");
+
+      mon = new ProgressMonitor();
+      win.getContentPane().add(buildProgress("Copying " + fromFile + " to " +
+                                             toFile, mon));
+      win.pack();
+      win.setVisible(true);
+    }
+
+    MakeCopy cp = new MakeCopy(mon, fromFile, toFile, suffix);
+    cp.start();
+    while (cp.isAlive()) {
+      try {
+        cp.join();
+      } catch (InterruptedException ie) {
+        ie.printStackTrace();
+      }
+    }
+
+    if (!cp.getResult()) {
+      System.err.println("Copy failed!");
+      System.exit(1);
+    }
+
+    System.out.println("Copied \"" + fromFile + "\" to \"" + toFile + "\"");
+    System.exit(0);
+  }
+
+  private JPanel buildProgress(String label, ProgressMonitor mon)
+  {
+    JPanel panel = new JPanel();
+
+    panel.setLayout(new java.awt.BorderLayout());
+
+    panel.add("North", new JLabel(label));
+    panel.add("South", mon);
+
+    return panel;
+  }
+
+  public int checkKeyword(String mainName, int thisArg, String[] args)
+  {
+    if (fromFile == null) {
+      fromFile = new File(args[thisArg]);
+      if (!fromFile.exists()) {
+        System.err.println(mainName + ": File \"" + fromFile +
+                           "\" does not exist");
+        return -1;
+      }
+
+      return 1;
+    }
+
+    if (toFile == null) {
+      toFile = new File(args[thisArg]);
+      return 1;
+    }
+
+    return super.checkKeyword(mainName, thisArg, args);
+  }
+
+  public int checkOption(String mainName, char ch, String arg)
+  {
+    if (ch == 'p') {
+      trackProgress = true;
+      return 1;
+    }
+
+    if (ch == 's') {
+      suffix = arg;
+      return 2;
+    }
+
+    return super.checkOption(mainName, ch, arg);
+  }
+
+  public boolean finalizeArgs(String mainName)
+  {
+    if (fromFile == null || toFile == null) {
+      System.err.println(mainName + ": Please specify two file names!");
+      return false;
+    }
+
+    return true;
+  }
+
+  public void initializeArgs()
+  {
+    fromFile = toFile = null;
+    suffix = null;
+    trackProgress = false;
+  }
+
+  public String keywordUsage() { return " fromFile toFile"; }
+
+  public String optionUsage() { return " [-p(rogressBar)]"; }
+
+  public static final void main(String[] args)
+  {
+    new TestUtil(args);
+  }
+}
diff --git a/visad/install/UpdateJar.java b/visad/install/UpdateJar.java
new file mode 100644
index 0000000..b28be53
--- /dev/null
+++ b/visad/install/UpdateJar.java
@@ -0,0 +1,214 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.io.File;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import java.util.ArrayList;
+
+import visad.util.CmdlineGenericConsumer;
+import visad.util.CmdlineParser;
+
+public class UpdateJar
+  extends CmdlineGenericConsumer
+{
+  private static final String CLASSPATH_PROPERTY = "java.class.path";
+
+  // XXX TJJ where does the distribution live as of 2012?
+  // I doubt this app is still used, but if so, it should at
+  // least get the correct, current, production .jar!
+  private static final String VISAD_JAR_URL =
+    "ftp://ftp.ssec.wisc.edu/pub/visad-2.0/visad.jar";
+
+  private File installJar;
+  private URL jarURL;
+  private boolean verbose;
+
+  public UpdateJar(String[] args)
+  {
+    CmdlineParser cmdline = new CmdlineParser(this);
+    if (!cmdline.processArgs(args)) {
+      System.exit(1);
+      return;
+    }
+
+    Download.getFile(jarURL, installJar, verbose);
+  }
+
+  public int checkKeyword(String mainName, int thisArg, String[] args)
+  {
+    // try to convert argument to a URL
+    URL tmpURL;
+    try {
+      tmpURL = new URL(args[thisArg]);
+    } catch (MalformedURLException mfe) {
+      // must not be a URL
+      tmpURL = null;
+    }
+
+    // if it's a URL...
+    if (tmpURL != null) {
+      if (jarURL != null) {
+        System.err.println(mainName + ": Too many URLs specified!");
+        return -1;
+      }
+
+      // save the URL
+      jarURL = tmpURL;
+      return 1;
+    }
+
+    // whine if we've already got a jar directory/file
+    if (installJar != null) {
+      System.err.println(mainName +
+                         ": Too many jar install directories specified!");
+      return -1;
+    }
+
+    final int thisLen = args[thisArg].length();
+    final int suffixLen = 4;
+
+    installJar = new File(args[thisArg]);
+
+    File dir = installJar;
+
+    if (thisLen > suffixLen) {
+      String suffix = args[thisArg].substring(thisLen - suffixLen, thisLen);
+      if (suffix.toLowerCase().equals(".jar")) {
+        dir = new File(installJar.getParent());
+      }
+    }
+
+    if (!dir.exists()) {
+      System.err.println(mainName + ": Directory \"" + dir +
+                         "\" does not exist!");
+      installJar = null;
+      return -1;
+    }
+
+    if (!dir.isDirectory()) {
+      System.err.println(mainName + ": \"" + dir + "\" is not a directory!");
+      installJar = null;
+      return -1;
+    }
+
+    if (!dir.canWrite()) {
+      System.err.println(mainName + ": Cannot write to directory \"" + dir +
+                         "\"!");
+      installJar = null;
+      return -1;
+    }
+
+    if (installJar != dir && installJar.exists() && !installJar.canWrite()) {
+      System.err.println(mainName + ": Cannot write to jar file \"" +
+                         installJar + "\"!");
+      installJar = null;
+      return -1;
+    }
+
+    return 1;
+  }
+
+  public int checkOption(String mainName, char ch, String arg)
+  {
+    if (ch == 'v') {
+      verbose = true;
+      return 1;
+    }
+
+    return 0;
+  }
+
+  public boolean finalizeArgs(String mainName)
+  {
+    // build the URL for the jar file
+    if (jarURL == null) {
+      try {
+        jarURL = new URL(VISAD_JAR_URL);
+      } catch (java.net.MalformedURLException mue) {
+        System.err.println(mainName + ": Couldn't build URL from \"" +
+                           VISAD_JAR_URL + "\"");
+        return false;
+      }
+    }
+
+    // if user didn't specify a jar file...
+    if (installJar == null) {
+
+      File urlFile = new File(jarURL.getFile());
+
+      // ...search for one in their classpath
+      installJar = findJar(urlFile.getName());
+    }
+
+    // if no jar file was found, whine and quit
+    if (installJar == null) {
+      System.err.println(mainName + ": Couldn't determine" +
+                         " where jar file should be installed");
+      return false;
+    }
+
+    return true;
+  }
+
+  private File findJar(String jarName)
+  {
+    Path classpath;
+
+    // get class path elements
+    try {
+      classpath = new Path(System.getProperty(CLASSPATH_PROPERTY));
+    } catch (IllegalArgumentException iae) {
+      System.err.println(getClass().getName() +
+                         ": Couldn't get Java class path");
+      return null;
+    }
+
+    // find all visad jar files
+    ArrayList jarList = classpath.findMatch(jarName);
+    if (jarList == null || jarList.size() == 0) {
+      return null;
+    }
+
+    // use first valid jar file
+    return (File )jarList.get(0);
+  }
+
+  public void initializeArgs()
+  {
+    installJar = null;
+    jarURL = null;
+  }
+
+  public String keywordUsage() { return " [url] [jarLocation]"; }
+  public String optionUsage() { return " [-v(erbose)]"; }
+
+  public static final void main(String[] args)
+  {
+    new UpdateJar(args);
+    System.exit(0);
+  }
+}
diff --git a/visad/install/Util.java b/visad/install/Util.java
new file mode 100644
index 0000000..4b0c790
--- /dev/null
+++ b/visad/install/Util.java
@@ -0,0 +1,393 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.install;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.Enumeration;
+
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class Util
+{
+  /**
+   * @see Util#copyDirectory(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyDirectory(File source, File target)
+  {
+    return copyDirectory(null, source, target, null);
+  }
+
+  /**
+   * @see Util#copyDirectory(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyDirectory(ProgressMonitor progress,
+                                            File source, File target)
+  {
+    return copyDirectory(progress, source, target, null);
+  }
+
+  /**
+   * @see Util#copyDirectory(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyDirectory(File source, File target,
+                                            String saveSuffix)
+  {
+    return copyDirectory(null, source, target, saveSuffix);
+  }
+
+  /**
+   * Copy files under the <i>source</i> directory to the <i>target</i>
+   * directory.  If necessary, <i>target</i> directory is created.<br>
+   * <br>
+   * For example, if this method is called with a <i>source</i> of
+   * <tt>/foo</tt> (which contains <tt>/foo/a</tt> and <tt>/foo/b</tt>)
+   * and a <i>target</i> of <tt>/bar</tt>, when this method exits
+   * <tt>/bar</tt> will contain <tt>/bar/a</tt> and <tt>/bar/b</tt>.
+   * Note that <tt>foo</tt> itself is not copied.
+   *
+   * @param progress if non-null, this progress monitor is updated
+   *                with the name of each file as it is copied.
+   * @param source source directory
+   * @param target directory
+   * @param saveSuffix if non-null, pre-existing files under <i>target</i>
+   *                   whose paths match files to be copied from
+   *                   <i>source</i> will be renamed to
+   *                   <tt>name + saveSuffix</tt>.
+   *
+   * @return false if any problems were encountered.
+   */
+  public static final boolean copyDirectory(ProgressMonitor progress,
+                                            File source, File target,
+                                            String saveSuffix)
+  {
+    // source must be a directory
+    if (!source.isDirectory() || (target.exists() && !target.isDirectory())) {
+      return false;
+    }
+
+    // if source and target are the same, we're done
+    if (getPath(source).equals(getPath(target))) {
+      return false;
+    }
+
+    // if the target doesn't exist yet, create it
+    if (!target.exists()) {
+      target.mkdirs();
+    }
+
+    boolean result = true;
+
+    String[] list = source.list();
+    for (int i = 0; i < list.length; i++) {
+      File srcFile = new File(source, list[i]);
+      File tgtFile = new File(target, list[i]);
+
+      if (srcFile.isDirectory()) {
+        result |= copyDirectory(progress, srcFile, tgtFile, saveSuffix);
+      } else {
+        result |= copyFile(progress, srcFile, tgtFile, saveSuffix);
+      }
+    }
+
+    // if source was read-only, the target should be as well
+    if (!source.canWrite()) {
+      target.setReadOnly();
+    }
+
+    // sync up last-modified time
+    target.setLastModified(source.lastModified());
+
+    return result;
+  }
+
+  /**
+   * @see Util#copyFile(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyFile(File source, File target)
+  {
+    return copyFile(null, source, target, null);
+  }
+
+  /**
+   * @see Util#copyFile(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyFile(ProgressMonitor progress,
+                                       File source, File target)
+  {
+    return copyFile(progress, source, target, null);
+  }
+
+  /**
+   * @see Util#copyFile(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyFile(File source, File target,
+                                       String saveSuffix)
+  {
+    return copyFile(null, source, target, saveSuffix);
+  }
+
+  /**
+   * Copy the <i>source</i> file to <i>target</i>.  If <i>target</i>
+   * does not exist, it is assumed to be the name of the copied file.
+   * If <i>target</i> is a directory, the file will be copied
+   * into that directory.
+   *
+   * @param progress if non-null, this progress monitor is updated
+   *                with the name of each file as it is copied.
+   * @param source source directory
+   * @param target target file/directory
+   * @param saveSuffix if non-null and <i>target</i> exists,
+   *                   <i>target</i> will be renamed to
+   *                   <tt>name + saveSuffix</tt>.
+   *
+   * @return false if any problems were encountered.
+   */
+  public static final boolean copyFile(ProgressMonitor progress,
+                                       File source, File target,
+                                       String saveSuffix)
+  {
+    // don't copy directories
+    if (source.isDirectory()) {
+      return false;
+    }
+
+    // if source and target are the same, we're done
+    if (getPath(source).equals(getPath(target))) {
+      return false;
+    }
+
+    if (target.isDirectory()) {
+      target = new File(target, source.getName());
+    }
+
+    FileInputStream  in;
+    try {
+      in = new FileInputStream(source);
+    } catch (IOException ioe) {
+      System.err.println("Couldn't open source file " + source);
+      return false;
+    }
+
+    copyStreamToFile(progress, in, target, saveSuffix);
+
+    try { in.close(); } catch (Exception e) { ; }
+
+    // if source was read-only, the target should be as well
+    if (!source.canWrite()) {
+      target.setReadOnly();
+    }
+
+    // sync up last-modified time
+    target.setLastModified(source.lastModified());
+
+    return true;
+  }
+
+  /**
+   * @see Util#copyJar(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyJar(File source, File target)
+  {
+    return copyJar(null, source, target, null);
+  }
+
+  /**
+   * @see Util#copyJar(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyJar(ProgressMonitor progress,
+                                       File source, File target)
+  {
+    return copyJar(progress, source, target, null);
+  }
+
+  /**
+   * @see Util#copyJar(ProgressMonitor, File, File, String)
+   */
+  public static final boolean copyJar(File source, File target,
+                                       String saveSuffix)
+  {
+    return copyJar(null, source, target, saveSuffix);
+  }
+
+  /**
+   * Extract files from the <i>source</i> jar file to the <i>target</i>
+   * directory.  If necessary, the <i>target</i> directory is created.<br>
+   * <br>
+   * For example, if this method is called with a <i>source</i> of
+   * <tt>foo.jar</tt> (which contains <tt>a</tt> and <tt>b</tt>)
+   * and a <i>target</i> of <tt>/bar</tt>, when this method exits
+   * <tt>/bar</tt> will contain <tt>/bar/a</tt> and <tt>/bar/b</tt>.
+   *
+   * @param progress if non-null, this progress monitor is updated
+   *                 with the name of each file as it is copied.
+   * @param source source jar file
+   * @param target directory
+   * @param saveSuffix if non-null, pre-existing files in <i>target</i>
+   *                   whose paths match files to be copied from
+   *                   <i>source</i> will be renamed to
+   *                   <tt>name + saveSuffix</tt>.
+   *
+   * @return false if any problems were encountered.
+   */
+  
+  public static final boolean copyJar(ProgressMonitor progress,
+                                      File source, File target,
+                                      String saveSuffix)
+  {
+    // if target exists, it must be a directory
+    if (target.exists() && !target.isDirectory()) {
+      return false;
+    }
+
+    // if the target doesn't exist yet, create it
+    if (!target.exists()) {
+      target.mkdirs();
+    }
+
+    // try to open the jar file
+    JarFile jar;
+    try {
+      jar = new JarFile(source);
+    } catch (IOException ioe) {
+      return false;
+    }
+
+    boolean result = true;
+
+    Enumeration en = jar.entries();
+    while (en.hasMoreElements()) {
+      JarEntry entry = (JarEntry )en.nextElement();
+
+      final String entryName = entry.getName();
+
+      // skip manifest files
+      if (JarFile.MANIFEST_NAME.startsWith(entryName)) {
+        continue;
+      }
+
+      File newFile = new File(target, entryName);
+      newFile.mkdirs();
+
+      if (!entry.isDirectory()) {
+
+        InputStream in;
+        try {
+          in = jar.getInputStream(entry);
+        } catch (IOException ioe) {
+          System.err.println("Couldn't copy entry " + entryName);
+          continue;
+        }
+
+        copyStreamToFile(progress, in, newFile, saveSuffix);
+
+        try { in.close(); } catch (Exception e) { ; }
+      }
+
+      newFile.setLastModified(entry.getTime());
+    }
+
+    return result;
+  }
+
+  private static final boolean copyStreamToFile(ProgressMonitor progress,
+                                                InputStream in, File target,
+                                                String saveSuffix)
+  {
+    // if the target already exists and we need to save the existing file...
+    if (target.exists()) {
+      if (saveSuffix == null) {
+        if (progress != null) {
+          progress.setDetail("Deleting existing " + target);
+        }
+
+        // out with the old...
+        target.delete();
+      } else {
+        if (progress != null) {
+          progress.setDetail("Backing up existing " + target);
+        }
+
+        File saveFile = new File(target.getPath() + saveSuffix);
+
+        // delete the old savefile
+        if (saveFile.exists()) {
+          saveFile.delete();
+        }
+
+        // save the existing target file
+        target.renameTo(saveFile);
+      }
+    }
+
+    if (progress != null) {
+      progress.setDetail("Installing " + target);
+    }
+
+    FileOutputStream out;
+    try {
+      out = new FileOutputStream(target);
+    } catch (IOException ioe) {
+      System.err.println("Couldn't open output file " + target);
+      return false;
+    }
+
+    byte buffer[]  = new byte[1024];
+    try {
+      while (true) {
+        int n = in.read(buffer);
+
+        if (n < 0) {
+          break;
+        }
+
+        out.write(buffer, 0, n);
+      }
+    } catch (IOException ioe) {
+      ioe.printStackTrace();
+      return false;
+    } finally {
+      try { out.close(); } catch (Exception e) { ; }
+    }
+
+    return true;
+  }
+
+  /**
+   * @return either the canonical path or, if that is not
+   *         available, the absolute path.
+   */
+  public static final String getPath(File f)
+  {
+    try {
+      return f.getCanonicalPath();
+    } catch (IOException ioe) {
+      return f.getAbsolutePath();
+    }
+  }
+}
diff --git a/visad/install/install_visad b/visad/install/install_visad
new file mode 100644
index 0000000..81ab27c
--- /dev/null
+++ b/visad/install/install_visad
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# this script should be in the root directory of the distribution
+
+# name of various properties passed to install app
+#
+ARCHPROP="visad.install.arch"
+HOMEPROP="visad.install.home"
+PATHPROP="visad.install.path"
+
+# figure out where this script lives
+#
+DISTTMP=`echo $0 | sed 's/\/[^\/]*$/\//'`
+DISTDIR=`cd $DISTTMP && pwd`
+
+# get OS/machine info
+#
+OS=`uname -s`
+MACH=`uname -p`
+
+# try to guess name of OS/machine distribution file/directory
+#
+ARCH=
+case "$OS-$MACH" in
+FreeBSD*-i*86) ARCH="freebsd-x86";;
+Linux*-i*86) ARCH="linux-x86";;
+SunOS-sparc) ARCH="solaris-sparc";;
+esac
+
+# if we guessed an OS/machine architecture...
+#
+if [ ! -z "$ARCH" ]; then
+  JBIN="$DISTDIR/jdk-$ARCH/bin"
+
+  # if JDK dir exists...
+  #
+  if [ -d "$JBIN" ]; then
+
+    # ...add directory to PATH envvar
+    #
+    if [ -z "$PATH" ]; then
+      PATH="$JBIN"
+    else
+      PATH="$JBIN:$PATH"
+    fi
+    export PATH
+  fi
+fi
+
+# make sure visad.jar is in the CLASSPATH
+#
+CPARG=
+if [ -f $DISTDIR/visad.jar ]; then
+  CPARG="-cp $DISTDIR/visad.jar"
+fi
+
+# and away we go!
+#
+exec java $CPARG \
+	"-D$ARCHPROP=$ARCH" \
+	"-D$HOMEPROP=$DISTDIR" \
+	"-D$PATHPROP=$PATH" \
+		visad.install.Main
diff --git a/visad/java2d/AVControlJ2D.java b/visad/java2d/AVControlJ2D.java
new file mode 100644
index 0000000..1c0b0a2
--- /dev/null
+++ b/visad/java2d/AVControlJ2D.java
@@ -0,0 +1,153 @@
+//
+// AVControlJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+/**
+   AVControlJ2D is the VisAD abstract superclass for AnimationControlJ2D
+   and ValueControlJ2D.<P>
+*/
+public abstract class AVControlJ2D extends Control implements AVControl {
+
+  transient Vector switches = new Vector();
+
+  public AVControlJ2D(DisplayImplJ2D d) {
+    super(d);
+  }
+
+  void addPair(VisADSwitch sw, Set se, DataRenderer re) {
+    switches.addElement(new SwitchSet(sw, se, re));
+  }
+
+  abstract void init() throws VisADException;
+
+  public void selectSwitches(double value, Set animation_set)
+       throws VisADException {
+    // check for missing
+    if (value != value) return;
+    double[][] fvalues = new double[1][1];
+    fvalues[0][0] = value;
+    Enumeration pairs = ((Vector) switches.clone()).elements();
+    while (pairs.hasMoreElements()) {
+      SwitchSet ss = (SwitchSet) pairs.nextElement();
+      Set set = ss.set;
+      double[][] values = null;
+      RealTupleType out = ((SetType) set.getType()).getDomain();
+      if (animation_set != null) {
+        RealTupleType in =
+          ((SetType) animation_set.getType()).getDomain();
+        values = CoordinateSystem.transformCoordinates(
+                             out, set.getCoordinateSystem(),
+                             set.getSetUnits(), null /* errors */,
+                             in, animation_set.getCoordinateSystem(),
+                             animation_set.getSetUnits(),
+                             null /* errors */, fvalues);
+      }
+      else {
+        // use RealType for value Unit and CoordinateSystem
+        // for SelectValue
+        values = CoordinateSystem.transformCoordinates(
+                             out, set.getCoordinateSystem(),
+                             set.getSetUnits(), null /* errors */,
+                             out, out.getCoordinateSystem(),
+                             out.getDefaultUnits(), null /* errors */,
+                             fvalues);
+      }
+      // compute set index from converted value
+      int [] indices;
+      if (set.getLength() == 1) {
+        indices = new int[] {0};
+      }
+      else {
+        indices = set.doubleToIndex(values);
+      }
+/*
+System.out.println("selectSwitches: value = " + value +
+                   " indices[0] = " + indices[0] +
+                   " values[0][0] = " + values[0][0]);
+*/
+      ss.swit.setWhichChild(indices[0]);
+    }
+  }
+
+  /** clear all 'pairs' in switches that involve re */
+  public void clearSwitches(DataRenderer re) {
+    Enumeration pairs = ((Vector) switches.clone()).elements();
+    while (pairs.hasMoreElements()) {
+      SwitchSet ss = (SwitchSet) pairs.nextElement();
+      if (ss.renderer.equals(re)) {
+        switches.removeElement(ss);
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    AVControlJ2D av = (AVControlJ2D )o;
+
+    if (switches == null) {
+      return (av.switches == null);
+    } else if (av.switches == null) {
+      // don't assume anything, since it could be a remote control
+    } else {
+      if (switches.size() != av.switches.size()) {
+        return false;
+      }
+      for (int i = switches.size() - 1; i > 0; i--) {
+        if (!switches.elementAt(i).equals(av.switches.elementAt(i))) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /** SwitchSet is an inner class of AVControlJ2D for
+      (VisADSwitch, Set, DataRenderer) structures */
+  private class SwitchSet extends Object {
+    VisADSwitch swit;
+    Set set;
+    DataRenderer renderer;
+
+    SwitchSet(VisADSwitch sw, Set se, DataRenderer re) {
+      swit = sw;
+      set = se;
+      renderer = re;
+    }
+  }
+
+}
+
diff --git a/visad/java2d/AnimationControlJ2D.java b/visad/java2d/AnimationControlJ2D.java
new file mode 100644
index 0000000..b7acf91
--- /dev/null
+++ b/visad/java2d/AnimationControlJ2D.java
@@ -0,0 +1,636 @@
+//
+// AnimationControlJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+import visad.browser.Convert;
+import visad.util.Delay;
+
+import java.rmi.*;
+import java.util.StringTokenizer;
+
+/**
+   AnimationControlJ2D is the VisAD class for controlling Animation
+   display scalars under Java2D.<P>
+
+   WLH - manipulate a list of VisADSwitch nodes in scene graph.<P>
+*/
+public class AnimationControlJ2D extends AVControlJ2D
+       implements Runnable, AnimationControl {
+
+  private int current = 0;
+  private boolean direction; // true = forward
+  private long step;    // time in milliseconds for the current step
+  private long[] stepValues = {500}; // times in milliseconds between animation steps
+  private transient AnimationSetControlJ2D animationSet;
+  private ToggleControl animate;
+  private RealType real;
+  private boolean no_tick = false;
+  private boolean computeSet = true;
+
+  private transient VisADCanvasJ2D canvas;
+
+  /** AnimationControlJ2D is Serializable, mark as transient */
+  private transient Thread animationThread;
+
+  public AnimationControlJ2D(DisplayImplJ2D d, RealType r) {
+    super(d);
+    real = r;
+    current = 0;
+    direction = true;
+    step = 500;
+    stepValues = new long[] {step};
+    if (d != null) {
+      canvas = ((DisplayRendererJ2D) d.getDisplayRenderer()).getCanvas();
+      animationSet = new AnimationSetControlJ2D(d, this);
+      d.addControl(animationSet);
+      animate = new ToggleControl(d, this);
+      d.addControl(animate);
+      try {
+        animate.setOn(false);
+      }
+      catch (VisADException v) {
+      }
+      catch (RemoteException v) {
+      }
+
+      new Delay();
+      // initialize the stepValues array
+      try
+      {
+          Set set = animationSet.getSet();
+          if (set != null) stepValues = new long[set.getLength()];
+      }
+      catch (VisADException v) {;}
+      for (int i = 0; i<stepValues.length; i++)
+      {
+          stepValues[i] = step;
+      }
+      animationThread = new Thread(this);
+      animationThread.start();
+    }
+  }
+
+  public void nullControl() {
+    stop();
+    super.nullControl();
+  }
+
+  public void stop() {
+    animationThread = null;
+  }
+
+  public void run() {
+    Thread me = Thread.currentThread();
+    while (animationThread == me) {
+      try {
+        if (animate != null && animate.getOn() && !no_tick) {
+          takeStep();
+        }
+      }
+      catch (VisADException v) {
+        v.printStackTrace();
+        throw new VisADError("AnimationControlJ2D.run: " + v.toString());
+      }
+      catch (RemoteException v) {
+        v.printStackTrace();
+        throw new VisADError("AnimationControlJ2D.run: " + v.toString());
+      }
+      try {
+        synchronized (this) {
+          if (0 <= current && current < stepValues.length) {
+            wait(stepValues[current]);
+          }
+          else {
+            wait(500);
+          }
+        }
+      }
+      catch(InterruptedException e) {
+        // control doesn't normally come here
+      }
+    }
+  }
+
+  void setNoTick(boolean nt) {
+    no_tick = nt;
+  }
+
+  /** get the current ordinal step number */
+  public int getCurrent() {
+    return current;
+  }
+
+  /** set the current ordinal step number = c */
+  public void setCurrent(int c)
+         throws VisADException, RemoteException {
+    if (animationSet != null) {
+      current = animationSet.clipCurrent(c);
+/* WLH 26 June 98
+      init();
+*/
+      canvas.renderTrigger();
+    }
+    else {
+      current = 0;
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /** set the current step by the value of the RealType
+      mapped to Display.Animation */
+  public void setCurrent(double value)
+         throws VisADException, RemoteException {
+    if (animationSet != null) {
+      current = animationSet.getIndex(value);
+      canvas.renderTrigger();
+    }
+    else {
+      current = 0;
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /**
+   * Set the animation direction.
+   *
+   * @param    dir     true for forward, false for backward
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           direction remains unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setDirection(boolean dir)
+         throws VisADException, RemoteException {
+    direction = dir;
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /** Get the animation direction.
+   *
+   *  @return   true for forward, false for backward
+   */
+  public boolean getDirection()
+  {
+    return direction;
+  }
+
+  /**
+   * Return the dwell time for the current step
+   */
+  public long getStep() {
+    if (stepValues == null || current < 0 ||
+        stepValues.length <= current) return 500;
+    else return stepValues[current];
+  }
+
+  /**
+   * return an array of the dwell times for all the steps.
+   */
+  public long[] getSteps()
+  {
+      return stepValues;
+  }
+
+  /**
+   * Set the dwell rate between animation steps to a constant value
+   *
+   * @param  st   dwell time in milliseconds
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           dwell time remains unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setStep(int st) throws VisADException, RemoteException {
+    if (st <= 0) {
+      throw new DisplayException("AnimationControlJ2D.setStep: " +
+                                 "step must be > 0");
+    }
+    step = st;
+    for (int i=0; i < stepValues.length; i++)
+    {
+        stepValues[i] = step;
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /**
+   * set the dwell time for individual steps.
+   *
+   * @param   steps   an array of dwell rates for each step in the animation
+   *                  If the length of the array is less than the number of
+   *                  frames in the animation, the subsequent step values will
+   *                  be set to the value of the last step.
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           dwell times remain unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setSteps(int[] steps)
+  throws VisADException, RemoteException
+  {
+    // verify that the values are valid
+    for (int i = 0; i < stepValues.length; i++)
+    {
+        stepValues[i] =
+            (i < steps.length) ? steps[i] : steps[steps.length-1];
+        if (stepValues[i] <= 0)
+            throw new DisplayException("AnimationControlJ2D.setSteps: " +
+                                 "step " + i + " must be > 0");
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /**
+   * advance one step (forward or backward)
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           step is taken.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void takeStep() throws VisADException, RemoteException {
+    if (direction) current++;
+    else current--;
+    if (animationSet != null) {
+      current = animationSet.clipCurrent(current);
+      canvas.renderTrigger();
+    }
+    changeControl(false);
+  }
+
+  public void init() throws VisADException {
+    if (animationSet != null &&
+        animationSet.getSet() != null) {
+      double value = animationSet.getValue(current);
+      Set set = animationSet.getSet();
+
+      animation_string(real, set, value, current);
+      selectSwitches(value, set);
+    }
+  }
+
+  public Set getSet() {
+    if (animationSet != null) {
+      return animationSet.getSet();
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * <p>Sets the set of times in this animation control.  If the argument 
+   * set is equal to the current set, then nothing is done.</p>
+   *
+   * @param s                     The set of times.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  public void setSet(Set s)
+         throws VisADException, RemoteException {
+    if (s == null && animationSet != null && 
+        animationSet.getSet() == null) return;  // check for null/null
+    if (animationSet == null || s == null ||
+        (s != null && !s.equals(animationSet.getSet()))) {
+      setSet(s, false);
+      // have to do this i animationSet == null
+      if (s == null) {
+        stepValues = new long[] {step};
+        current = 0;
+      } else if (s.getLength() != stepValues.length) {
+        stepValues = new long[s.getLength()];
+        for (int i = 0; i < stepValues.length; i++)
+        {
+          stepValues[i] = step;
+        }
+      }
+    }
+  }
+
+  /**
+   * <p>Sets the set of times in this animation control.  If the argument 
+   * set is equal to the current set, then nothing is done.</p>
+   *
+   * @param s                     The set of times.
+   * @param noChange              changeControl(!noChange) to not trigger 
+   *                              re-transform, used by ScalarMap.setRange
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  public void setSet(Set s, boolean noChange)
+         throws VisADException, RemoteException {
+    if (animationSet != null) {
+      if (s == null) {
+          stepValues = new long[] {step};
+          current = 0;
+      } else if (s.getLength() != stepValues.length) {
+          stepValues = new long[s.getLength()];
+          for (int i = 0; i < stepValues.length; i++)
+          {
+              stepValues[i] = step;
+          }
+      }
+      animationSet.setSet(s, noChange);
+    }
+  }
+
+  /** return true if automatic stepping is on */
+  public boolean getOn() {
+    if (animate != null) {
+      return animate.getOn();
+    }
+    else {
+      return false;
+    }
+  }
+
+  /**
+   * Set automatic stepping on or off.
+   *
+   * @param  o  true = turn stepping on, false = turn stepping off
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           change in automatic stepping occurs.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setOn(boolean o)
+         throws VisADException, RemoteException {
+    if (animate != null) {
+      animate.setOn(o);
+    }
+  }
+
+  /**
+   * toggle automatic stepping between off and on
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           change in automatic stepping occurs.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void toggle()
+         throws VisADException, RemoteException {
+    if (animate != null) {
+      animate.setOn(!animate.getOn());
+    }
+  }
+
+  public void subSetTicks() {
+    if (animationSet != null) {
+      animationSet.setTicks();
+    }
+    if (animate != null) {
+      animate.setTicks();
+    }
+  }
+
+  public boolean subCheckTicks(DataRenderer r, DataDisplayLink link) {
+    boolean flag = false;
+    if (animationSet != null) {
+      flag |= animationSet.checkTicks(r, link);
+    }
+    if (animate != null) {
+      flag |= animate.checkTicks(r, link);
+    }
+    return flag;
+  }
+
+  public boolean subPeekTicks(DataRenderer r, DataDisplayLink link) {
+    boolean flag = false;
+    if (animationSet != null) {
+      flag |= animationSet.peekTicks(r, link);
+    }
+    if (animate != null) {
+      flag |= animate.peekTicks(r, link);
+    }
+    return flag;
+  }
+
+  public void subResetTicks() {
+    if (animationSet != null) {
+      animationSet.resetTicks();
+    }
+    if (animate != null) {
+      animate.resetTicks();
+    }
+  }
+
+  /** get a String that can be used to reconstruct this
+      AnimationControl later */
+  public String getSaveString() {
+    int numSteps;
+    long[] steps;
+    if (stepValues == null) {
+      numSteps = 1;
+      steps = new long[1];
+      steps[0] = 500;
+    }
+    else {
+      numSteps = stepValues.length;
+      steps = stepValues;
+    }
+    StringBuffer sb = new StringBuffer(35 + 12 * numSteps);
+    sb.append(animate != null && animate.getOn());
+    sb.append(' ');
+    sb.append(direction);
+    sb.append(' ');
+    sb.append(current);
+    sb.append(' ');
+    sb.append(numSteps);
+    for (int i=0; i<numSteps; i++) {
+      sb.append(' ');
+      sb.append((int) steps[i]);
+    }
+    sb.append(' ');
+    sb.append(computeSet);
+    return sb.toString();
+  }
+
+  /** reconstruct this AnimationControl using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save);
+    int numTokens = st.countTokens();
+    if (numTokens < 4) throw new VisADException("Invalid save string");
+
+    // get animation settings
+    boolean on = Convert.getBoolean(st.nextToken());
+    boolean dir = Convert.getBoolean(st.nextToken());
+    int cur = Convert.getInt(st.nextToken());
+    int numSteps = Convert.getInt(st.nextToken());
+    if (numSteps <= 0) {
+      throw new VisADException("Number of steps is not positive");
+    }
+    if (numTokens < 4 + numSteps) {
+      throw new VisADException("Not enough step entries");
+    }
+    int[] steps = new int[numSteps];
+    for (int i=0; i<numSteps; i++) {
+      steps[i] = Convert.getInt(st.nextToken());
+      if (steps[i] <= 0) {
+        throw new VisADException("Step #" + (i + 1) + "is not positive");
+      }
+    }
+    boolean cs = st.hasMoreTokens() ? Convert.getBoolean(st.nextToken()) : getComputeSet();
+
+    // set values
+    setOn(on);
+    setDirection(dir);
+    setSteps(steps);
+    setCurrent(cur);
+    setComputeSet(cs);
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof AnimationControlJ2D)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    AnimationControlJ2D ac = (AnimationControlJ2D )rmt;
+
+    boolean changed = false;
+
+    /* *** DON'T TRY TO SYNC CURRENT FRAME!!! *** */
+    // if (current != ac.current) {
+    //   changed = true;
+    //   if (animationSet != null) {
+    //     current = animationSet.getIndex(ac.current);
+    //     canvas.renderTrigger();
+    //   } else {
+    //     current = 0;
+    //   }
+    // }
+    if (direction != ac.direction) {
+      changed = true;
+      direction = ac.direction;
+    }
+    if (animate != ac.animate) {
+      changed = true;
+      animate = ac.animate;
+    }
+    if (real != ac.real) {
+      changed = true;
+      real = ac.real;
+    }
+    if (no_tick != ac.no_tick) {
+      changed = true;
+      no_tick = ac.no_tick;
+    }
+
+    if (computeSet != ac.computeSet) {
+      changed = true;
+      computeSet = ac.computeSet;
+    }
+
+    if (changed) {
+      try {
+        // WLH 5 May 2000
+        // changeControl(true);
+        changeControl(false);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    AnimationControlJ2D ac = (AnimationControlJ2D )o;
+    /**** IGNORE FRAME POSITION ****/
+    // if (current != ac.current) {
+    //   return false;
+    // }
+    if (direction != ac.direction) {
+      return false;
+    }
+    if (animate != ac.animate) {
+      return false;
+    }
+    if (real != ac.real) {
+      return false;
+    }
+    if (no_tick != ac.no_tick) {
+      return false;
+    }
+    if (computeSet != ac.computeSet) {
+      return false;
+    }
+
+    return true;
+  }
+
+  public String toString() {
+    return "AnimationControlJ2D: current = " + current +
+           " set = " + animationSet.getSet();
+  }
+
+  /**
+   * Set the flag to automatically compute the animation set if it is
+   * null
+   * @param compute   false to allow application to control set computation
+   *                  if set is null.
+   */
+  public void setComputeSet(boolean compute) {
+      computeSet = compute;
+  }
+
+  /**
+   * Get the flag to automatically compute the animation set if it is
+   * null
+   * 
+   * @return true if should compute
+   */
+  public boolean getComputeSet() {
+    return computeSet;
+  }
+
+}
diff --git a/visad/java2d/AnimationSetControlJ2D.java b/visad/java2d/AnimationSetControlJ2D.java
new file mode 100644
index 0000000..ce87ba6
--- /dev/null
+++ b/visad/java2d/AnimationSetControlJ2D.java
@@ -0,0 +1,55 @@
+//
+// AnimationSetControlJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   AnimationSetControlJ2D is the VisAD class for sampling Animation
+   steps under Java2D.<P>
+*/
+public class AnimationSetControlJ2D extends AnimationSetControl {
+
+  private transient VisADCanvasJ2D canvas;
+
+  public AnimationSetControlJ2D(DisplayImpl d, AnimationControl p) {
+    super(d, p);
+    if (d != null) {
+      canvas = ((DisplayRendererJ2D) d.getDisplayRenderer()).getCanvas();
+    }
+  }
+
+  public void setSet(Set s, boolean noChange)
+         throws VisADException, RemoteException {
+    super.setSet(s, noChange);
+    canvas.createImages((s==null)? -1 : s.getLength());
+  }
+
+}
+
diff --git a/visad/java2d/DefaultDisplayRendererJ2D.java b/visad/java2d/DefaultDisplayRendererJ2D.java
new file mode 100644
index 0000000..bba58ff
--- /dev/null
+++ b/visad/java2d/DefaultDisplayRendererJ2D.java
@@ -0,0 +1,205 @@
+//
+// DefaultDisplayRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import java.lang.reflect.Constructor;
+
+import visad.Display;
+import visad.DisplayException;
+import visad.DisplayRealType;
+import visad.VisADAppearance;
+import visad.VisADError;
+import visad.VisADGroup;
+import visad.VisADLineArray;
+
+/**
+ * <CODE>DefaultDisplayRendererJ2D</CODE> is the VisAD class for
+ * default background and metadata rendering under Java2D.<P>
+ */
+public class DefaultDisplayRendererJ2D extends DisplayRendererJ2D {
+
+  /** box outline for data */
+  private VisADAppearance box = null;
+  /** cursor */
+  private VisADAppearance cursor = null;
+
+  private Class mouseBehaviorJ2DClass = null;
+
+  /** Behavior for mouse interactions */
+  private MouseBehaviorJ2D mouse = null;
+
+  /**
+   * This is the default <CODE>DisplayRenderer</CODE> used by the
+   * <CODE>DisplayImplJ2D</CODE> constructor.
+   * It draws a 2-D box around the scene and a 2-D cursor.<P>
+   * The left mouse button controls the projection as follows:
+   * <UL>
+   *  <LI>mouse drag or mouse drag with Ctrl translates the scene sideways
+   *  <LI>mouse drag with Shift down zooms the scene
+   * </UL>
+   * The center mouse button activates and controls the 2-D cursor as
+   * follows:
+   * <UL>
+   *  <LI>mouse drag translates the cursor sideways
+   * </UL>
+   * The right mouse button is used for direct manipulation by clicking on
+   * the depiction of a <CODE>Data</CODE> object and dragging or re-drawing
+   * it.<P>
+   * Cursor and direct manipulation locations are displayed in RealType
+   * values<P>
+   * <CODE>BadMappingExceptions</CODE> and
+   * <CODE>UnimplementedExceptions</CODE> are displayed<P>
+   * No RealType may be mapped to ZAxis, Latitude or Alpha.
+   */
+  public DefaultDisplayRendererJ2D () {
+    super();
+    mouseBehaviorJ2DClass = MouseBehaviorJ2D.class;
+  }
+
+  /**
+   * @param mbj2dClass - sub Class of MouseBehaviorJ2D
+   */
+  
+  public DefaultDisplayRendererJ2D (Class mbj2dClass) {
+    super();
+    mouseBehaviorJ2DClass = mbj2dClass;
+  }
+
+  public boolean getMode2D() {
+    return true;
+  }
+
+  public boolean legalDisplayScalar(DisplayRealType type) {
+    if (Display.ZAxis.equals(type) ||
+        Display.Latitude.equals(type) ||
+        Display.Alpha.equals(type)) return false;
+    else return super.legalDisplayScalar(type);
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root;
+   * create 3-D box, lights and <CODE>MouseBehaviorJ2D</CODE> for
+   * embedded user interface.
+   * @param c
+   * @return Scene graph root.
+   * @exception DisplayException
+   */
+  public VisADGroup createSceneGraph(VisADCanvasJ2D c)
+         throws DisplayException {
+    VisADGroup root = getRoot();
+    if (root != null) return root;
+
+    // create MouseBehaviorJ2D for mouse interactions
+    try {
+      Class[] param = new Class[] {DisplayRendererJ2D.class};
+      Constructor mbConstructor =
+        mouseBehaviorJ2DClass.getConstructor(param);
+      mouse = (MouseBehaviorJ2D) mbConstructor.newInstance(new Object[] {this});
+    }
+    catch (Exception e) {
+      throw new VisADError("cannot construct " + mouseBehaviorJ2DClass);
+    }
+    // mouse = new MouseBehaviorJ2D(this);
+
+    getDisplay().setMouseBehavior(mouse);
+    box = new VisADAppearance();
+    cursor = new VisADAppearance();
+    root = createBasicSceneGraph(c, mouse, box, cursor);
+
+    // create the box containing data depictions
+    VisADLineArray box_array = new VisADLineArray();
+    box_array.coordinates = box_verts;
+    box_array.vertexCount = 8;
+
+    float[] ctlBox = getRendererControl().getBoxColor();
+    box.red = ctlBox[0];
+    box.green = ctlBox[1];
+    box.blue = ctlBox[2];
+    box.color_flag = true;
+    box.array = box_array;
+    // add box to root
+/* WLH 5 Feb 99
+    root.addChild(box);
+*/
+    VisADGroup box_on = getBoxOnBranch();
+    box_on.addChild(box);
+
+    // create cursor
+    VisADLineArray cursor_array = new VisADLineArray();
+    cursor_array.coordinates = cursor_verts;
+    cursor_array.vertexCount = 4;
+
+    float[] ctlCursor = getRendererControl().getCursorColor();
+    cursor.red = ctlCursor[0];
+    cursor.green = ctlCursor[1];
+    cursor.blue = ctlCursor[2];
+    cursor.color_flag = true;
+    cursor.array = cursor_array;
+    // add cursor to cursor_on branch
+    VisADGroup cursor_on = getCursorOnBranch();
+    cursor_on.addChild(cursor);
+
+    return root;
+  }
+
+  /**
+   * set the aspect for the containing box
+   * aspect double[3] array used to scale x, y and z box sizes
+   */
+  public void setBoxAspect(double[] aspect) {
+    float[] new_verts = new float[box_verts.length];
+    for (int i=0; i<box_verts.length; i+=3) {
+      new_verts[i] = (float) (box_verts[i] * aspect[0]);
+      new_verts[i+1] = (float) (box_verts[i+1] * aspect[1]);
+      new_verts[i+2] = (float) (box_verts[i+2] * aspect[2]);
+    }
+    VisADLineArray box_array = (VisADLineArray) box.array;
+    box_array.coordinates = new_verts;
+  }
+
+  // WLH 2 Dec 2002 in response to qomo2.txt
+  public void setLineWidth(float width) {
+    box.lineWidth = width;
+    cursor.lineWidth = width;
+  }
+
+  private static final float[] box_verts = {
+     // front face
+         -1.0f, -1.0f,  0.0f,                       -1.0f,  1.0f,  0.0f,
+         -1.0f,  1.0f,  0.0f,                        1.0f,  1.0f,  0.0f,
+          1.0f,  1.0f,  0.0f,                        1.0f, -1.0f,  0.0f,
+          1.0f, -1.0f,  0.0f,                       -1.0f, -1.0f,  0.0f
+  };
+
+  private static final float[] cursor_verts = {
+          0.0f,  0.1f,  0.0f,                        0.0f, -0.1f,  0.0f,
+          0.1f,  0.0f,  0.0f,                       -0.1f,  0.0f,  0.0f
+  };
+
+}
+
diff --git a/visad/java2d/DefaultRendererJ2D.java b/visad/java2d/DefaultRendererJ2D.java
new file mode 100644
index 0000000..b5b7a09
--- /dev/null
+++ b/visad/java2d/DefaultRendererJ2D.java
@@ -0,0 +1,130 @@
+//
+// DefaultRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   DefaultRendererJ2D is the VisAD class for the default graphics
+   rendering algorithm under Java2D.<P>
+*/
+public class DefaultRendererJ2D extends RendererJ2D {
+
+  DataDisplayLink link;
+
+  /** this is the default DataRenderer used by the addReference method
+      for DisplayImplJ2D */
+  public DefaultRendererJ2D () {
+    super();
+  }
+
+  public void setLinks(DataDisplayLink[] links, DisplayImpl d)
+       throws VisADException {
+    if (links == null || links.length != 1) {
+      throw new DisplayException("DefaultRendererJ2D.setLinks: must be " +
+                                 "exactly one DataDisplayLink");
+    }
+    super.setLinks(links, d);
+  }
+
+  /** create a VisADGroup scene graph for Data in links[0] */
+  public VisADGroup doTransform() throws VisADException, RemoteException { // J2D
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      link = null;
+      return null;
+    }
+    link = Links[0];
+
+    ShadowTypeJ2D type = (ShadowTypeJ2D) link.getShadow();
+
+    VisADGroup branch = new VisADGroup();
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return null;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      addException(
+        new DisplayException("Data is null: DefaultRendererJ2D.doTransform"));
+    }
+    else {
+      link.start_time = System.currentTimeMillis();
+      link.time_flag = false;
+      type.preProcess();
+      boolean post_process;
+      try {
+        post_process =
+          type.doTransform(branch, data, valueArray,
+                           link.getDefaultValues(), this);
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+      if (post_process) type.postProcess(branch);
+    }
+    link.clearData();
+    return branch;
+  }
+
+  void addSwitch(DisplayRendererJ2D displayRenderer, VisADGroup branch)
+       throws VisADException {
+    displayRenderer.addSceneGraphComponent(branch);
+  }
+
+  public DataDisplayLink getLink() {
+    return link;
+  }
+
+  public Object clone() {
+    return new DefaultRendererJ2D();
+  }
+
+}
+
diff --git a/visad/java2d/DirectManipulationRendererJ2D.java b/visad/java2d/DirectManipulationRendererJ2D.java
new file mode 100644
index 0000000..fb7fd74
--- /dev/null
+++ b/visad/java2d/DirectManipulationRendererJ2D.java
@@ -0,0 +1,204 @@
+//
+// DirectManipulationRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+
+/**
+   DirectManipulationRendererJ2D is the VisAD class for direct
+   manipulation rendering under Java2D.<P>
+
+   This DataRenderer supports direct manipulation for Real, RealTuple
+   and Field Data objects (Field data objects must have RealType or
+   RealTupleType ranges and Gridded1DSet domain Sets); no RealType may
+   be mapped to multiple spatial DisplayRealTypes; the RealType of a
+   Real object must be mapped to XAxis, YAxis or YAxis; at least one
+   of the RealType components of a RealTuple object must be mapped to
+   XAxis, YAxis or YAxis; the domain RealType and at least one RealType
+   range component of a Field object must be mapped to XAxis or YAxis
+*/
+public class DirectManipulationRendererJ2D extends RendererJ2D {
+
+  VisADGroup branch = null;
+  VisADGroup extra_branch = null;
+
+  /** this DataRenderer supports direct manipulation for Real,
+      RealTuple and Field Data objects (Field data objects must
+      have RealType or RealTupleType ranges and Gridded1DSet
+      domain Sets); no RealType may be mapped to multiple spatial
+      DisplayRealTypes; the RealType of a Real object must be
+      mapped to XAxis, YAxis or YAxis; at least one of the
+      RealType components of a RealTuple object must be mapped
+      to XAxis, YAxis or YAxis; the domain RealType and at
+      least one RealType range component of a Field object
+      must be mapped to XAxis, YAxis or YAxis */
+  public DirectManipulationRendererJ2D () {
+    super();
+  }
+
+  public void setLinks(DataDisplayLink[] links, DisplayImpl d)
+       throws VisADException {
+    if (links == null || links.length != 1) {
+      throw new DisplayException("DirectManipulationRendererJ2D.setLinks: " +
+                                 "must be exactly one DataDisplayLink");
+    }
+    super.setLinks(links, d);
+  }
+
+  public void checkDirect() throws VisADException, RemoteException {
+    realCheckDirect();
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    int count = x.length / 3;
+    VisADGeometryArray array = null;
+    if (count == 1) {
+      array = new VisADPointArray();
+    }
+    else if (count == 2) {
+      array = new VisADLineArray();
+    }
+    else {
+      return;
+    }
+    array.coordinates = x;
+    array.vertexCount = count;
+    VisADAppearance appearance = new VisADAppearance();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return;
+    }
+    DataDisplayLink link = Links[0];
+
+    float[] default_values = link.getDefaultValues();
+    DisplayImpl display = getDisplay();
+    appearance.pointSize =
+      default_values[display.getDisplayScalarIndex(Display.PointSize)];
+    appearance.lineWidth =
+      default_values[display.getDisplayScalarIndex(Display.LineWidth)];
+    appearance.lineStyle = (int)
+      default_values[display.getDisplayScalarIndex(Display.LineStyle)];
+/* WLH 21 Aug 98
+    GraphicsModeControl mode = getDisplay().getGraphicsModeControl();
+    appearance.pointSize = mode.getPointSize();
+    appearance.lineWidth = mode.getLineWidth();
+    appearance.lineStyle = mode.getLineStyle();
+*/
+    appearance.red = 1.0f;
+    appearance.green = 1.0f;
+    appearance.blue = 1.0f;
+    appearance.array = array;
+    extra_branch.addChild(appearance);
+  }
+
+  public VisADGroup getExtraBranch() {
+    return extra_branch;
+  }
+
+  /** create a VisADGroup scene graph for Data in links[0] */
+  public synchronized VisADGroup doTransform()
+         throws VisADException, RemoteException {
+    branch = new VisADGroup();
+    extra_branch = new VisADGroup();
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return null;
+    }
+    DataDisplayLink link = Links[0];
+
+    // values needed by drag_direct, which cannot throw Exceptions
+    ShadowTypeJ2D shadow = (ShadowTypeJ2D) link.getShadow();
+
+    // check type and maps for valid direct manipulation
+    if (!getIsDirectManipulation()) {
+      throw new BadDirectManipulationException(getWhyNotDirect() +
+        ": DirectManipulationRendererJ2D.doTransform");
+    }
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return null;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      extra_branch = null;
+      addException(
+        new DisplayException("Data is null: DirectManipulationRendererJ2D." +
+                             "doTransform"));
+    }
+    else {
+      try {
+        // no preProcess or postProcess for direct manipulation */
+        shadow.doTransform(branch, data, valueArray,
+                           link.getDefaultValues(), this);
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+    }
+    return branch;
+  }
+
+  void addSwitch(DisplayRendererJ2D displayRenderer, VisADGroup branch)
+       throws VisADException {
+    displayRenderer.addDirectManipulationSceneGraphComponent(branch, this);
+  }
+
+  public boolean isLegalTextureMap() {
+    return false;
+  }
+
+  public Object clone() {
+    return new DirectManipulationRendererJ2D();
+  }
+
+}
+
diff --git a/visad/java2d/DisplayImplJ2D.java b/visad/java2d/DisplayImplJ2D.java
new file mode 100644
index 0000000..6782878
--- /dev/null
+++ b/visad/java2d/DisplayImplJ2D.java
@@ -0,0 +1,342 @@
+//
+// DisplayImplJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+
+visad.java2d design:
+
+0. common code in ClassNameJ2D and ClassNameJ3D
+     DirectManipulationRendererJ2/3D
+       common methods in visad.DataRenderer
+     GraphicsModeControlJ2/3D
+       why isn't GraphicsModeControl a class
+       where we can put common methods ? ? ? ?
+       all *ControlJ3D extend Control and implement interfaces
+       --> looks like interfaces could be abstract classes
+             extending Control
+       ** done for GraphicsModeControl and ProjectionControl
+       ** not for AnimationControl and ValueControl
+     MouseBehaviorJ2/3D
+       extend visad.MouseBehavior
+       visad.MouseHelper.processEvents
+       remove Transform3D constructor from make_matrix
+     DefaultDisplayRendererJ2D/TwoDDisplayRendererJ3D
+       common methods in visad.DisplayRenderer
+     ShadowFunctionOrSetTypeJ2/3D, ShadowRealTypeJ2/3D and
+       ShadowTupleTypeJ2/3D ShadowTypeJ2/3D adapt common
+       methods in visad.Shadow*Type
+
+1. add VisAD-specific scene graph classes:
+
+     canvas, root, trans, direct, cursor_trans & other
+       scene graph stuff in DisplayRendererJ2D
+
+     ** done
+     VisADRay
+     VisADSceneGraphObject
+       VisADGroup
+         VisADSwitch
+       VisADAppearance
+         incl VisADGeometryArray
+         incl Image "texture"
+         incl red, green, blue, alpha
+         linewidth and pointsize in GraphicsModeControl
+       (VisADTexture2D not needed; Image in VisADAppearance)
+         texture.setImage(0, image2d);
+         new Shape3D(geometry, appearance);
+         appearance.setTexture(texture);
+       (VisADShape not needed; VisADGeometryArray in VisADAppearance)
+       (VisADTransform not needed; trans in DisplayRendererJnD)
+       (hence VisADBranchGroup not needed; a VisADBranchGroup
+        is a VisADGroup that is not a VisADSwitch)
+
+
+2. add VisADSceneGraphObject as parent of
+   existing VisAD-specific scene graph classes:
+
+     ** done
+     VisADSceneGraphObject
+       VisADGeometryArray
+         VisADIndexedTriangleStripArray
+         VisADLineArray
+         VisADLineStripArray
+         VisADPointArray
+         VisADTriangleArray
+         VisADQuadArray
+
+3. VisADCanvasJ2D
+     add BufferedImage[] array with element for each animation step
+     AnimationControlJ2D.init() invoked in paint
+
+4. DisplayImplJ2D.doAction
+     super.doAction()
+     if any scene graph changes canvas.scratchImages()
+
+5. AnimationControlJ2D.selectSwitches()
+     invoke canvas.renderTrigger() instead of init()
+
+6. ValueControlJ2D.setValue()
+     invoke canvas.scratchImages()
+
+7. ProjectionControlJ2D.setMatrix()
+     invoke canvas.scratchImages()
+
+8. VisADCanvasJ2D.paint()
+     invokes DisplayRendererJ2D.drawCursorStringVector()
+     which draws cursor strings, Exception strings,
+       WaitFlag & Animation string
+     add draw of extra_branch from
+       DirectManipulationRendererJ2D.addPoint
+
+9. DirectManipulationRendererJ2D
+     doTransform: create branch and extra_branch
+     addPoint: add to extra_branch
+
+10. MouseBehaviorJ2D
+     just do AWTEvent's
+
+11. DefaultDisplayRendererJ2D = TwoDDisplayRendererJ2D
+     legalDisplayScalar?
+
+12. DisplayAppletJ2D delete
+
+13. DisplayImplJ2D.makeGeometry() delete
+
+14. RemoveBehaviorJ2D delete
+
+15. UniverseBuilderJ2D delete
+
+16. resize event on Display component, and
+      rebuild BufferedImage[] array
+
+17. AnimationSetControlJ2D.setSet() (new class)
+      canvas.createImages(s.getLength())
+
+18. VisADCanvasJ2D renderThread
+
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+import java.awt.*;
+
+/**
+   DisplayImplJ2D is the VisAD class for displays that use
+   Java 3D.  It is runnable.<P>
+
+   DisplayImplJ2D is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public class DisplayImplJ2D extends DisplayImpl {
+
+  /** legal values for api */
+  public static final int UNKNOWN = 0;
+  public static final int JPANEL = 1;
+  public static final int OFFSCREEN = 2;
+
+  private ProjectionControlJ2D projection = null;
+  private GraphicsModeControlJ2D mode = null;
+  private int apiValue = UNKNOWN;
+
+  /** flag to scratch images in VisADCanvasJ2D */
+  private boolean scratch;
+
+  public DisplayImplJ2D(RemoteDisplay rmtDpy)
+         throws VisADException, RemoteException {
+    this(rmtDpy, null);
+  }
+
+  public DisplayImplJ2D(RemoteDisplay rmtDpy, DisplayRendererJ2D renderer)
+         throws VisADException, RemoteException {
+    super(rmtDpy, renderer);
+
+    initialize(rmtDpy.getDisplayAPI(), 300, 300);
+
+    syncRemoteData(rmtDpy);
+  }
+
+  /** construct a DisplayImpl for Java2D with the
+      default DisplayRenderer, in a JFC JPanel */
+  public DisplayImplJ2D(String name)
+         throws VisADException, RemoteException {
+    this(name, null, JPANEL);
+  }
+
+  /** construct a DisplayImpl for Java2D with a non-default
+      DisplayRenderer, in a JFC JPanel */
+  public DisplayImplJ2D(String name, DisplayRendererJ2D renderer)
+         throws VisADException, RemoteException {
+    this(name, renderer, JPANEL);
+  }
+
+  /** constructor with default DisplayRenderer */
+  public DisplayImplJ2D(String name, int api)
+         throws VisADException, RemoteException {
+    this(name, null, api);
+  }
+
+  /** construct a DisplayImpl for Java2D with a non-default
+      DisplayRenderer;
+      in a JFC JPanel if api == DisplayImplJ2D.JPANEL */
+  public DisplayImplJ2D(String name, DisplayRendererJ2D renderer, int api)
+         throws VisADException, RemoteException {
+    this(name, renderer, api, 300, 300);
+  }
+
+  /** construct a DisplayImpl for Java2D for offscreen rendering,
+      with size given by width and height; getComponent() of this
+      returns null, but display is accesible via getImage() */
+  public DisplayImplJ2D(String name, int width, int height)
+         throws VisADException, RemoteException {
+    this(name, null, OFFSCREEN, width, height);
+  }
+
+  /** offscreen constructor with non-default DisplayRenderer */
+  public DisplayImplJ2D(String name, DisplayRendererJ2D renderer,
+                        int width, int height)
+         throws VisADException, RemoteException {
+    this(name, renderer, OFFSCREEN, width, height);
+  }
+
+  /** most general constructor */
+  public DisplayImplJ2D(String name, DisplayRendererJ2D renderer, int api,
+                        int width, int height)
+         throws VisADException, RemoteException {
+    super(name, renderer);
+
+    initialize(api, width, height);
+  }
+
+  private void initialize(int api, int width, int height)
+	throws VisADException
+  {
+    // a GraphicsModeControl always exists
+    mode = new GraphicsModeControlJ2D(this);
+    addControl(mode);
+    // a ProjectionControl always exists
+    projection = new ProjectionControlJ2D(this);
+    addControl(projection);
+
+    if (api == JPANEL) {
+      Component component = new DisplayPanelJ2D(this);
+      setComponent(component);
+      apiValue = JPANEL;
+    }
+    else if (api == OFFSCREEN) {
+      Component component = null;
+      DisplayRendererJ2D renderer = (DisplayRendererJ2D )getDisplayRenderer();
+      VisADCanvasJ2D canvas = new VisADCanvasJ2D(renderer, width, height);
+      VisADGroup scene = renderer.createSceneGraph(canvas);
+      apiValue = OFFSCREEN;
+    }
+    else {
+      throw new DisplayException("DisplayImplJ2D: bad graphics API");
+    }
+
+/* WLH 8 March 99
+    // a GraphicsModeControl always exists
+    mode = new GraphicsModeControlJ2D(this);
+    addControl(mode);
+    // a ProjectionControl always exists
+    projection = new ProjectionControlJ2D(this);
+    addControl(projection);
+*/
+  }
+
+  protected DisplayRenderer getDefaultDisplayRenderer() {
+    return new DefaultDisplayRendererJ2D();
+  }
+
+  public ProjectionControl getProjectionControl() {
+    return projection;
+  }
+
+  public GraphicsModeControl getGraphicsModeControl() {
+    return mode;
+  }
+
+  /**
+   * Return the API used for this display
+   *
+   * @return  the mode being used (UNKNOWN, JPANEL, OFFSCREEN)
+   * @throws  VisADException
+   */
+  public int getAPI()
+	throws VisADException
+  {
+    return apiValue;
+  }
+
+  public void setScratch() {
+    scratch = true;
+  }
+
+  public void clearMaps() throws VisADException, RemoteException {
+    super.clearMaps();
+    DisplayRendererJ2D displayRenderer =
+      (DisplayRendererJ2D) getDisplayRenderer();
+    VisADCanvasJ2D canvas = displayRenderer.getCanvas();
+    if (canvas != null) canvas.scratchImages();
+  }
+
+  public void doAction() throws VisADException, RemoteException {
+    scratch = false;
+    super.doAction();
+    if (scratch) {
+/*
+System.out.println("DisplayImplJ2D.doAction: scratch = " + scratch);
+*/
+      VisADCanvasJ2D canvas =
+        ((DisplayRendererJ2D) getDisplayRenderer()).getCanvas();
+      canvas.scratchImages();
+    }
+  }
+
+  /* CTR 14 Nov 2000 - support for auto-aspect to canvas size */
+
+  public boolean getAutoAspect() {
+    DisplayRendererJ2D dr = (DisplayRendererJ2D) getDisplayRenderer();
+    VisADCanvasJ2D canvas = dr.getCanvas();
+    return canvas.getAutoAspect();
+  }
+
+  public void setAutoAspect(boolean auto) {
+    DisplayRendererJ2D dr = (DisplayRendererJ2D) getDisplayRenderer();
+    VisADCanvasJ2D canvas = dr.getCanvas();
+    canvas.setAutoAspect(auto);
+  }
+
+  public void destroy() throws VisADException, RemoteException {
+    ((DisplayRendererJ2D) getDisplayRenderer()).getCanvas().stop();
+    super.destroy();
+  }
+
+}
+
diff --git a/visad/java2d/DisplayPanelJ2D.java b/visad/java2d/DisplayPanelJ2D.java
new file mode 100644
index 0000000..a40816a
--- /dev/null
+++ b/visad/java2d/DisplayPanelJ2D.java
@@ -0,0 +1,57 @@
+//
+// DisplayPanelJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+// GUI handling
+import javax.swing.*;
+
+public class DisplayPanelJ2D extends JPanel {
+
+  private DisplayImplJ2D display;
+  private DisplayRendererJ2D renderer;
+
+  public DisplayPanelJ2D(DisplayImplJ2D d)
+         throws VisADException {
+    display = d;
+    renderer = (DisplayRendererJ2D) display.getDisplayRenderer();
+    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+/* WLH 19 April 99
+    setAlignmentY(TOP_ALIGNMENT);
+    setAlignmentX(LEFT_ALIGNMENT);
+*/
+    VisADCanvasJ2D canvas = new VisADCanvasJ2D(renderer, this);
+    add(canvas);
+    VisADGroup scene = renderer.createSceneGraph(canvas);
+
+    setPreferredSize(new java.awt.Dimension(256, 256));
+    setMinimumSize(new java.awt.Dimension(0, 0));
+  }
+
+}
+
diff --git a/visad/java2d/DisplayRendererJ2D.java b/visad/java2d/DisplayRendererJ2D.java
new file mode 100644
index 0000000..eb3908e
--- /dev/null
+++ b/visad/java2d/DisplayRendererJ2D.java
@@ -0,0 +1,952 @@
+//
+// DisplayRendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.awt.geom.AffineTransform;
+
+import java.rmi.*;
+import java.util.*;
+import java.io.*;
+import javax.imageio.*;
+
+
+import visad.util.Util;
+
+/**
+ * <CODE>DisplayRendererJ2D</CODE> is the VisAD abstract super-class for
+ * background and metadata rendering algorithms.  These complement
+ * depictions of <CODE>Data</CODE> objects created by
+ * <CODE>DataRenderer</CODE> objects.<P>
+ *
+ * <CODE>DisplayRendererJ2D</CODE> also manages the overall relation of
+ * <CODE>DataRenderer</CODE> output to Java2D and manages the VisAD scene
+ * graph.<P>
+ *
+ * It creates the binding between <CODE>Control</CODE> objects and scene
+ * graph <CODE>Behavior</CODE> objects for direct manipulation of
+ * <CODE>Control</CODE> objects.<P>
+ *
+ * <CODE>DisplayRendererJ2D</CODE> is not <CODE>Serializable</CODE> and
+ * should not be copied between JVMs.<P>
+*/
+public abstract class DisplayRendererJ2D
+  extends DisplayRenderer
+  implements RendererSourceListener
+{
+
+  private VisADCanvasJ2D canvas;
+
+  /** root VisADGroup of scene graph under Locale */
+  private VisADGroup root = null;
+  /** single AffineTransform applied to all
+   *  Data depictions */
+  private AffineTransform trans = null;
+  /** VisADGroup between root and all direct manipulation
+   *  Data depictions */
+  private VisADGroup direct = null;
+  /** VisADGroup between root and all non-direct-manipulation
+   *  Data depictions */
+  private VisADGroup non_direct = null;
+
+  /** MouseBehaviorJ2D */
+  private MouseBehaviorJ2D mouse = null;
+
+  /** box outline for data */
+  private VisADAppearance box = null;
+  /** cursor */
+  private VisADAppearance cursor = null;
+
+  /** AffineTransform between trans and cursor */
+  private AffineTransform cursor_trans = null;
+  /** single VisADSwitch between root and cursor */
+  private VisADSwitch cursor_switch = null;
+  /** children of cursor_switch */
+  private VisADGroup cursor_on = null, cursor_off = null;
+  /** on / off state of cursor */
+  private boolean cursorOn = false;
+  /** on / off state of direct manipulation location display */
+  private boolean directOn = false;
+  /** on / off state of axis scale */
+  private boolean scaleOn = false;
+
+  /** single VisADSwitch between root and box */
+  private VisADSwitch box_switch = null;
+  /** children of box_switch */
+  private VisADGroup box_on = null, box_off = null;
+  /** on / off state of box */
+  private boolean boxOn = false;
+
+  /** single VisADSwitch between root and scales */
+  private VisADSwitch scale_switch = null;
+  /** children of scale_switch */
+  private VisADGroup scale_on = null, scale_off = null;
+  /** on / off state of cursor in GraphicsModeControl */
+
+  /** Vector of DirectManipulationRenderers */
+  private Vector directs = new Vector();
+
+  /** cursor location */
+  private float cursorX, cursorY, cursorZ;
+  /** normalized direction perpendicular to current cursor plane */
+  private float line_x, line_y, line_z;
+  /** start value for cursor */
+  private float point_x, point_y, point_z;
+
+  public DisplayRendererJ2D () {
+    super();
+  }
+
+  /**
+   * Specify <CODE>DisplayImpl</CODE> to be rendered.
+   * @param dpy <CODE>Display</CODE> to render.
+   * @exception VisADException If a <CODE>DisplayImpl</CODE> has already
+   *                           been specified.
+   */
+  public void setDisplay(DisplayImpl dpy)
+    throws VisADException
+  {
+    super.setDisplay(dpy);
+    dpy.addRendererSourceListener(this);
+    boxOn = getRendererControl().getBoxOn();
+  }
+
+  public VisADGroup getRoot() {
+    return root;
+  }
+
+  public void setClip(float xlow, float xhi, float ylow, float yhi) {
+    canvas.setClip(xlow, xhi, ylow, yhi);
+  }
+
+  public void unsetClip() {
+    canvas.unsetClip();
+  }
+
+  /**
+   * Internal method used to initialize newly created
+   * <CODE>RendererControl</CODE> with current renderer settings
+   * before it is actually connected to the renderer.  This
+   * means that changes will not generate <CODE>MonitorEvent</CODE>s.
+   * @param ctl RendererControl to initialize
+   */
+  public void initControl(RendererControl ctl)
+  {
+    // initialize box colors
+    if (box != null) {
+      try {
+        ctl.setBoxColor(box.red, box.green, box.blue);
+      } catch (Throwable t) {
+        // ignore any initialization problems
+      }
+    }
+
+    // initialize cursor colors
+    if (cursor != null) {
+      try {
+        ctl.setBoxColor(cursor.red, cursor.green, cursor.blue);
+      } catch (Throwable t) {
+        // ignore any initialization problems
+      }
+    }
+
+    // initialize background colors
+    if (canvas != null) {
+      float[] ca = canvas.getBackgroundColor();
+      try {
+        ctl.setBackgroundColor(ca[0], ca[1], ca[2]);
+      } catch (Throwable t) {
+        // ignore any initialization problems
+      }
+    }
+
+    // update box visibility
+    try {
+      ctl.setBoxOn(boxOn);
+    } catch (Throwable t) {
+      // ignore any initialization problems
+    }
+  }
+
+  /**
+   * Utility routine which updates a <CODE>VisADAppearance</CODE> object
+   * to use the colors specified in the <CODE>float[3]</CODE> array.
+   * @param appear Currently used colors.
+   * @param colors New colors.
+   * @return <CODE>true</CODE> if any color was updated.
+   */
+  private final boolean updateColors(VisADAppearance appear, float[] colors)
+  {
+    if (appear == null) {
+      return false;
+    }
+
+    boolean fixed = false;
+    for (int i = 0; i < 3; i++) {
+
+      float a;
+      switch (i) {
+      case 0: a = appear.red; break;
+      case 1: a = appear.green; break;
+      default:
+      case 2: a = appear.blue; break;
+      }
+
+      if (!Util.isApproximatelyEqual(a, colors[i])) {
+        switch (i) {
+        case 0: appear.red = colors[i]; break;
+        case 1: appear.green = colors[i]; break;
+        default:
+        case 2: appear.blue = colors[i]; break;
+        }
+        fixed = true;
+      }
+    }
+
+    return fixed;
+  }
+
+  /**
+   * Update internal values from those in the <CODE>RendererControl</CODE>.
+   * @param evt <CODE>ControlEvent</CODE> generated by a change to the
+   *            <CODE>RendererControl</CODE>
+   */
+  public void controlChanged(ControlEvent evt)
+    throws VisADException, RemoteException
+  {
+    RendererControl ctl = (RendererControl )evt.getControl();
+
+    float[] color;
+
+    // update box colors
+    color = ctl.getBoxColor();
+    if (updateColors(box, color)) {
+      getCanvas().scratchImages();
+    }
+
+    // update cursor colors
+    color = ctl.getCursorColor();
+    if (updateColors(cursor, color)) {
+      render_trigger();
+    }
+
+    // update background colors
+    float[] ca = canvas.getBackgroundColor();
+    float[] ct = ctl.getBackgroundColor();
+    if (!Util.isApproximatelyEqual(ca[0], ct[0]) ||
+        !Util.isApproximatelyEqual(ca[1], ct[1]) ||
+        !Util.isApproximatelyEqual(ca[2], ct[2]))
+    {
+      canvas.setBackgroundColor(ct[0], ct[1], ct[2]);
+      canvas.scratchImages();
+    }
+
+    // update box visibility
+    boolean on = ctl.getBoxOn();
+    if (on != boxOn) {
+      boxOn = on;
+      box_switch.setWhichChild(boxOn ? 1 : 0);
+      canvas.scratchImages();
+    }
+  }
+
+  public AffineTransform getTrans() {
+    return trans;
+  }
+
+  public VisADCanvasJ2D getCanvas() {
+    return canvas;
+  }
+
+  public BufferedImage getImage() {
+    BufferedImage image = null;
+    while (image == null) {
+      try {
+        synchronized (this) {
+          canvas.captureFlag = true;
+          canvas.renderTrigger();
+// System.out.println("getImage wait");
+          wait();
+        }
+      }
+      catch(InterruptedException e) {
+        // note notify generates a normal return from wait rather
+        // than an Exception - control doesn't normally come here
+      }
+      image = canvas.captureImage;
+      canvas.captureImage = null;
+// System.out.println("getImage (image == null) = " + (image == null));
+    }
+
+    if (getDisplay().getComponent() == null) {
+      // offscreen
+      // this is a total hack; works for reasons not understood
+      for (int i=0; i<2; i++) {
+        try {
+          ByteArrayOutputStream bout = new ByteArrayOutputStream();
+          ImageIO.write(image, "image/jpeg", bout);
+          bout.flush();
+          bout.close();
+        }
+        catch (IOException e) {
+        }
+      }
+// System.out.println("ByteArrayOutputStream done");
+    }
+
+    return image;
+  }
+
+  void notifyCapture() {
+    synchronized (this) {
+      notify();
+    }
+  }
+
+  public VisADGroup getCursorOnBranch() {
+    return cursor_on;
+  }
+
+  public VisADGroup getBoxOnBranch() {
+    return box_on;
+  }
+
+  public void setCursorOn(boolean on) {
+    cursorOn = on;
+    if (on) {
+      cursor_switch.setWhichChild(1); // set cursor on
+      setCursorStringVector();
+    }
+    else {
+      cursor_switch.setWhichChild(0); // set cursor off
+      setCursorStringVector(null);
+    }
+    render_trigger();
+  }
+
+  public void setDirectOn(boolean on) {
+    directOn = on;
+    if (!on) {
+      setCursorStringVector(null);
+      render_trigger();
+    }
+  }
+
+  public VisADGroup getDirect() {
+    return direct;
+  }
+
+  public VisADGroup getNonDirect() {
+    return non_direct;
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform,
+   * direct manipulation root, and non-direct-manipulation root;
+   * create special graphics (e.g., 3-D box, SkewT background),
+   * any lights, any user interface embedded in scene.
+   * @param c
+   * @return Scene graph root.
+   * @exception DisplayException
+   */
+  public abstract VisADGroup createSceneGraph(VisADCanvasJ2D c)
+         throws DisplayException;
+
+  /** @deprecated use createBasicSceneGraph(VisADCanvasJ2D c,
+         MouseBehaviorJ2D m, VisADAppearance bx, VisADAppearance cr)
+      instead */
+  public VisADGroup createBasicSceneGraph(VisADCanvasJ2D c,
+         MouseBehaviorJ2D m) throws DisplayException {
+    VisADAppearance box = new VisADAppearance();
+    VisADAppearance cursor = new VisADAppearance();
+    return createBasicSceneGraph(c, m, box, cursor);
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root.
+   * @param c
+   * @param m
+   * @return Scene graph root.
+   * @exception DisplayException
+   */
+  public VisADGroup createBasicSceneGraph(VisADCanvasJ2D c,
+         MouseBehaviorJ2D m, VisADAppearance bx, VisADAppearance cr)
+         throws DisplayException {
+    if (root != null) return root;
+    mouse = m;
+    canvas = c;
+    box = bx;
+    cursor = cr;
+    canvas.addMouseBehavior(mouse);
+    // Create the root of the branch graph
+    root = new VisADGroup();
+
+    // create the VisADGroup that is the parent of direct
+    // manipulation Data object VisADGroup objects
+    direct = new VisADGroup();
+    root.addChild(direct);
+    directOn = false;
+
+    // create the VisADGroup that is the parent of non-direct-
+    // manipulation Data object VisADGroup objects
+    non_direct = new VisADGroup();
+    root.addChild(non_direct);
+
+    canvas.setDirect(direct, non_direct);
+
+    cursor_trans = new AffineTransform();
+    cursor_switch = new VisADSwitch();
+    cursor_on = new VisADGroup();
+    cursor_off = new VisADGroup();
+    cursor_switch.addChild(cursor_off);
+    cursor_switch.addChild(cursor_on);
+    cursor_switch.setWhichChild(0); // initially off
+    cursorOn = false;
+
+    box_switch = new VisADSwitch();
+    box_on = new VisADGroup();
+    box_off = new VisADGroup();
+    box_switch.addChild(box_off);
+    box_switch.addChild(box_on);
+    box_switch.setWhichChild(1); // initially on
+    root.addChild(box_switch);
+    try {
+      setBoxOn(true);
+    } catch (Exception e) {
+    }
+
+    scale_switch = new VisADSwitch();
+    root.addChild(scale_switch);
+    scale_on = new VisADGroup();
+    scale_off = new VisADGroup();
+    scale_switch.addChild(scale_off);
+    scale_switch.addChild(scale_on);
+    scale_switch.setWhichChild(0); // initially off
+    scaleOn = false;
+
+    // set background color
+    float[] ctlBg = getRendererControl().getBackgroundColor();
+    canvas.setBackgroundColor(ctlBg[0], ctlBg[1], ctlBg[2]);
+
+    return root;
+  }
+
+  public MouseBehavior getMouseBehavior() {
+    return mouse;
+  }
+
+  public void addSceneGraphComponent(VisADGroup group)
+         throws DisplayException {
+    non_direct.addChild(group);
+  }
+
+  public void addDirectManipulationSceneGraphComponent(VisADGroup group,
+         DirectManipulationRendererJ2D renderer) throws DisplayException {
+    direct.addChild(group);
+    directs.addElement(renderer);
+  }
+
+  public void clearScene(DataRenderer renderer) {
+    directs.removeElement(renderer);
+  }
+
+  /**
+   * Returns the location of the last unmodified, middle mouse button press.
+   * @return			The location of the last unmodified, middle
+   *				mouse button press as (Display.XAxis,
+   *				Display.YAxis, 0.0).
+   */
+  public double[] getCursor() {
+    double[] cursor = new double[3];
+    cursor[0] = cursorX;
+    cursor[1] = cursorY;
+    cursor[2] = cursorZ;
+    return cursor;
+  }
+
+  public void depth_cursor(VisADRay ray) {
+    line_x = (float) ray.vector[0];
+    line_y = (float) ray.vector[1];
+    line_z = (float) ray.vector[2];
+    point_x = cursorX;
+    point_y = cursorY;
+    point_z = cursorZ;
+  }
+
+  public void drag_depth(float diff) {
+    cursorX = point_x + diff * line_x;
+    cursorY = point_y + diff * line_y;
+    cursorZ = point_z + diff * line_z;
+    setCursorLoc();
+  }
+
+  public void drag_cursor(VisADRay ray, boolean first) {
+    float o_x = (float) ray.position[0];
+    float o_y = (float) ray.position[1];
+    float o_z = (float) ray.position[2];
+    float d_x = (float) ray.vector[0];
+    float d_y = (float) ray.vector[1];
+    float d_z = (float) ray.vector[2];
+    if (first) {
+      line_x = d_x;
+      line_y = d_y;
+      line_z = d_z;
+    }
+    float dot = (cursorX - o_x) * line_x +
+                (cursorY - o_y) * line_y +
+                (cursorZ - o_z) * line_z;
+    float dot2 = d_x * line_x + d_y * line_y + d_z * line_z;
+    if (dot2 == 0.0) return;
+    dot = dot / dot2;
+    // new cursor location is intersection
+    cursorX = o_x + dot * d_x;
+    cursorY = o_y + dot * d_y;
+    cursorZ = o_z + dot * d_z;
+    setCursorLoc();
+  }
+
+  private void setCursorLoc() {
+    cursor_trans.setToTranslation((double) cursorX, (double) cursorY);
+    if (cursorOn) {
+      setCursorStringVector();
+    }
+    else {
+      render_trigger();
+    }
+  }
+
+  public void render_trigger() {
+    canvas.renderTrigger();
+  }
+
+  public boolean anyCursorStringVector() {
+    if (cursorOn) return true;
+
+    Enumeration renderers = ((Vector) directs.clone()).elements();
+    while (renderers.hasMoreElements()) {
+      DirectManipulationRendererJ2D r =
+        (DirectManipulationRendererJ2D) renderers.nextElement();
+      VisADGroup extra_branch = r.getExtraBranch();
+      if (extra_branch != null) return true;
+    }
+
+    if (cursorOn || directOn) {
+      if (!getCursorStringVector().isEmpty()) return true;
+    }
+
+    Vector rendererVector = getDisplay().getRendererVector();
+    renderers = rendererVector.elements();
+    while (renderers.hasMoreElements()) {
+      DataRenderer renderer = (DataRenderer) renderers.nextElement();
+      if (!renderer.getExceptionVector().isEmpty()) return true;
+    }
+
+    if (getWaitFlag() && getWaitMessageVisible()) return true;
+    return false;
+  }
+
+  /**
+   * Whenever <CODE>cursorOn</CODE> or <CODE>directOn</CODE> is
+   * <CODE>true</CODE>, display Strings in cursorStringVector.
+   * @param graphics
+   * @param tgeometry
+   * @param width
+   * @param height
+   */
+  public void drawCursorStringVector(Graphics graphics,
+              AffineTransform tgeometry, int width, int height) {
+    // draw cursor
+    AffineTransform t = new AffineTransform(tgeometry);
+    if (cursorOn) {
+      t.concatenate(cursor_trans);
+      VisADAppearance appearance = (VisADAppearance) cursor_on.getChild(0);
+      if (appearance != null) {
+        VisADCanvasJ2D.drawAppearance(graphics, appearance, t, null);
+      }
+      t = new AffineTransform(tgeometry);
+    }
+
+    // draw direct manipulation extra_branch's
+    Enumeration renderers = ((Vector) directs.clone()).elements();
+    while (renderers.hasMoreElements()) {
+      DirectManipulationRendererJ2D r =
+        (DirectManipulationRendererJ2D) renderers.nextElement();
+      VisADGroup extra_branch = r.getExtraBranch();
+      if (extra_branch != null) {
+        Vector children = ((VisADGroup) extra_branch).getChildren();
+        Enumeration childs = children.elements();
+        while (childs.hasMoreElements()) {
+          VisADAppearance child =
+            (VisADAppearance) childs.nextElement();
+          VisADCanvasJ2D.drawAppearance(graphics, child, t, null);
+        }
+      }
+    }
+
+    // set cursor color
+    float[] c3;
+    try {
+      c3 = getCursorColor();
+    } catch (Exception e) {
+      System.err.println("Yikes!  Couldn't get cursor color");
+      // default to white
+      c3 = new float[] {1.0f, 1.0f, 1.0f};
+    }
+    graphics.setColor(new Color(c3[0], c3[1], c3[2]));
+
+    // draw cursor strings in upper left corner of screen
+    graphics.setFont(new Font("Times New Roman", Font.PLAIN, 10));
+    int x = 1;
+    int y = 10;
+    if (cursorOn || directOn) {
+      Enumeration strings = getCursorStringVector().elements();
+      while(strings.hasMoreElements()) {
+        String string = (String) strings.nextElement();
+        graphics.drawString(string, x, y);
+        y += 12;
+      }
+    }
+
+    // draw Exception strings in lower left corner of screen
+    x = 1;
+    y = height - 2;
+    Vector rendererVector = getDisplay().getRendererVector();
+    renderers = rendererVector.elements();
+    while (renderers.hasMoreElements()) {
+      DataRenderer renderer = (DataRenderer) renderers.nextElement();
+      Vector exceptionVector = renderer.getExceptionVector();
+      Enumeration exceptions = exceptionVector.elements();
+      while (exceptions.hasMoreElements()) {
+        Exception error = (Exception) exceptions.nextElement();
+        String string = error.getMessage();
+        graphics.drawString(string, x, y);
+        y -= 12;
+      }
+    }
+
+    // draw wait flag in lower left corner of screen
+    if (getWaitFlag() && getWaitMessageVisible()) {
+      graphics.drawString("please wait . . .", x, y);
+      y -= 12;
+    }
+  }
+
+  public DataRenderer findDirect(VisADRay ray, int mouseModifiers) {
+    DirectManipulationRendererJ2D renderer = null;
+    float distance = Float.MAX_VALUE;
+    Enumeration renderers = ((Vector) directs.clone()).elements();
+    while (renderers.hasMoreElements()) {
+      DirectManipulationRendererJ2D r =
+        (DirectManipulationRendererJ2D) renderers.nextElement();
+      if (r.getEnabled()) {
+        r.setLastMouseModifiers(mouseModifiers);
+        float d = r.checkClose(ray.position, ray.vector);
+        if (d < distance) {
+          distance = d;
+          renderer = r;
+        }
+      }
+    }
+    if (distance < getPickThreshhold()) {
+      return renderer;
+    }
+    else {
+      return null;
+    }
+  }
+
+  public boolean anyDirects() {
+    return !directs.isEmpty();
+  }
+
+  /**
+   * Allow scales to be displayed if they are set on.
+   * @param  on   true to turn them on, false to set them invisible
+   */
+  public void setScaleOn(boolean on) {
+    boolean oldOn = scaleOn;
+    scaleOn = on;
+    if (on) {
+      scale_switch.setWhichChild(1); // on
+    }
+    else {
+      scale_switch.setWhichChild(0); // off
+    }
+    if (scaleOn != oldOn) {
+      canvas.scratchImages();
+    }
+  }
+
+  /**
+   * Set the scale for the appropriate axis.
+   * @param  axisScale  AxisScale for this scale
+   * @throws  VisADException  couldn't set the scale
+   */
+  public void setScale(AxisScale axisScale)
+         throws VisADException {
+    setScale(axisScale.getAxis(),
+             axisScale.getAxisOrdinal(),
+             axisScale.getScaleArray(),
+             axisScale.getLabelArray(),
+             axisScale.getColor().getColorComponents(null));
+  }
+
+  /**
+   * Set the scale for the appropriate axis.
+   * @param  axis  axis for this scale (0 = XAxis, 1 = YAxis, 2 = ZAxis)
+   * @param  axis_ordinal  position along the axis
+   * @param  array   <CODE>VisADLineArray</CODE> representing the scale plot
+   * @param  scale_color   array (dim 3) representing the red, green and blue
+   *                       color values.
+   * @throws  VisADException  couldn't set the scale
+   */
+  public void setScale(int axis, int axis_ordinal,
+              VisADLineArray array, float[] scale_color)
+         throws VisADException {
+    setScale(axis, axis_ordinal, array, null, scale_color);
+  }
+
+  /**
+   * Set the scale for the appropriate axis.
+   * @param  axis  axis for this scale (0 = XAxis, 1 = YAxis, 2 = ZAxis)
+   * @param  axis_ordinal  position along the axis
+   * @param  array   <CODE>VisADLineArray</CODE> representing the scale plot
+   * @param  labels  <CODE>VisADTriangleArray</CODE> representing the labels
+   *                 created using a font (can be null)
+   * @param  scale_color   array (dim 3) representing the red, green and blue
+   *                       color values.
+   * @throws  VisADException  couldn't set the scale
+   */
+  public void setScale(int axis, int axis_ordinal,
+              VisADLineArray array, VisADTriangleArray labels,
+              float[] scale_color)
+         throws VisADException {
+    // add array to scale_on
+    // replace any existing at axis, axis_ordinal
+    VisADAppearance appearance = new VisADAppearance();
+    appearance.red = scale_color[0];
+    appearance.green = scale_color[1];
+    appearance.blue = scale_color[2];
+    appearance.color_flag = true;
+    appearance.array = array;
+    VisADGroup group = new VisADGroup();
+    group.addChild(appearance);
+    if (labels != null)
+    {
+      VisADAppearance labelAppearance = new VisADAppearance();
+      labelAppearance.red = scale_color[0];
+      labelAppearance.green = scale_color[1];
+      labelAppearance.blue = scale_color[2];
+      labelAppearance.color_flag = true;
+      labelAppearance.array = labels;
+      group.addChild(labelAppearance);
+    }
+
+    // may only add VisADGroup to 'live' scale_on
+    int dim = getMode2D() ? 2 : 3;
+    synchronized (scale_on) {
+      int n = scale_on.numChildren();
+      int m = dim * axis_ordinal + axis;
+      if (m >= n) {
+        for (int i=n; i<=m; i++) {
+          VisADGroup empty = new VisADGroup();
+          scale_on.addChild(empty);
+        }
+      }
+      scale_on.setChild(group, m);
+    }
+    canvas.scratchImages(); // WLH 26 Jan 2001
+  }
+
+  /**
+   * Remove all the scales being rendered.
+   */
+  public void clearScales() {
+    if (scale_on != null) {
+      synchronized (scale_on) {
+        int n = scale_on.numChildren();
+        for (int i=n-1; i>=0; i--) {
+          scale_on.removeChild(i);
+        }
+      }
+    }
+  }
+
+  /**
+   * Remove a particular scale being rendered.
+   * @param axisScale  scale to be removed
+   */
+  public void clearScale(AxisScale axisScale) {
+    int axis = axisScale.getAxis();
+    int axis_ordinal = axisScale.getAxisOrdinal();
+    int dim = getMode2D() ? 2 : 3;
+    try {
+      synchronized (scale_on) {
+        int n = scale_on.numChildren();
+        int m = dim * axis_ordinal + axis;
+        if (m >= n) {
+          for (int i=n; i<=m; i++) {
+            VisADGroup empty = new VisADGroup();
+            scale_on.addChild(empty);
+          }
+        }
+        VisADGroup empty = new VisADGroup();
+        scale_on.setChild(empty, m);
+        canvas.scratchImages(); 
+      }
+    } catch (VisADException ve) {;}
+  }
+
+  public void setTransform2D(AffineTransform t) {
+    trans = new AffineTransform(t);
+  }
+
+  /**
+   * Factory for constructing a subclass of <CODE>Control</CODE>
+   * appropriate for the graphics API and for this
+   * <CODE>DisplayRenderer</CODE>; invoked by <CODE>ScalarMap</CODE>
+   * when it is <CODE>addMap()</CODE>ed to a <CODE>Display</CODE>.
+   * @param map The <CODE>ScalarMap</CODE> for which a <CODE>Control</CODE>
+   *            should be built.
+   * @return The appropriate <CODE>Control</CODE>.
+   */
+  public Control makeControl(ScalarMap map) {
+    DisplayRealType type = map.getDisplayScalar();
+    DisplayImplJ2D display = (DisplayImplJ2D) getDisplay();
+    if (type == null) return null;
+    if (type.equals(Display.XAxis) ||
+        type.equals(Display.YAxis) ||
+        type.equals(Display.ZAxis) ||
+        type.equals(Display.Latitude) ||
+        type.equals(Display.Longitude) ||
+        type.equals(Display.Radius)) {
+      return (ProjectionControlJ2D) display.getProjectionControl();
+    }
+    else if (type.equals(Display.RGB) ||
+             type.equals(Display.HSV) ||
+             type.equals(Display.CMY)) {
+      return new ColorControl(display);
+    }
+    else if (type.equals(Display.RGBA)) {
+      return new ColorAlphaControl(display);
+    }
+    else if (type.equals(Display.Animation)) {
+      // note only one RealType may be mapped to Animation
+      // so control must be null
+      Control control = display.getControl(AnimationControlJ2D.class);
+      if (control != null) return control;
+      else return new AnimationControlJ2D(display, (RealType) map.getScalar());
+    }
+    else if (type.equals(Display.SelectValue)) {
+      return new ValueControlJ2D(display);
+    }
+    else if (type.equals(Display.SelectRange)) {
+      return new RangeControl(display);
+    }
+    else if (type.equals(Display.IsoContour)) {
+      return new ContourControl(display);
+    }
+    else if (type.equals(Display.Flow1X) ||
+             type.equals(Display.Flow1Y) ||
+             type.equals(Display.Flow1Z) ||
+             type.equals(Display.Flow1Elevation) ||
+             type.equals(Display.Flow1Azimuth) ||
+             type.equals(Display.Flow1Radial)) {
+      Control control = display.getControl(Flow1Control.class);
+      if (control != null) return control;
+      else return new Flow1Control(display);
+    }
+    else if (type.equals(Display.Flow2X) ||
+             type.equals(Display.Flow2Y) ||
+             type.equals(Display.Flow2Z) ||
+             type.equals(Display.Flow2Elevation) ||
+             type.equals(Display.Flow2Azimuth) ||
+             type.equals(Display.Flow2Radial)) {
+      Control control = display.getControl(Flow2Control.class);
+      if (control != null) return control;
+      else return new Flow2Control(display);
+    }
+    else if (type.equals(Display.Shape)) {
+      return new ShapeControl(display);
+    }
+    else if (type.equals(Display.Text)) {
+      return new TextControl(display);
+    }
+    else {
+      return null;
+    }
+  }
+
+  public DataRenderer makeDefaultRenderer() {
+    return new DefaultRendererJ2D();
+  }
+
+  public boolean legalDataRenderer(DataRenderer renderer) {
+    return (renderer instanceof RendererJ2D);
+  }
+
+  public void rendererDeleted(DataRenderer renderer)
+  {
+    clearScene(renderer);
+  }
+
+  public void setLineWidth(float width) {
+  }
+
+  /**
+   * Add a <CODE>KeyboardBehavior</CODE> for keyboard control of translation 
+   * and zoom.  This adds a <CODE>KeyListener</CODE> to the VisADCanvasJ2D to 
+   * handle the behaviors for the arrow keys.  Do not use this in conjunction 
+   * with other <CODE>KeyListener</CODE>s that handle events for the arrow keys.
+   * @param  behavior  keyboard behavior to add
+   */
+  public void addKeyboardBehavior(KeyboardBehaviorJ2D behavior)
+  {
+    getCanvas().addKeyboardBehavior(behavior);
+  }
+
+  public void setWaitFlag(boolean b) {
+    boolean old = getWaitFlag();
+    super.setWaitFlag(b);
+    if (b != old) {
+      if (canvas != null) canvas.renderTrigger();
+    }
+  }
+
+  public int getTextureWidthMax() {
+    return Integer.MAX_VALUE;
+  }
+
+  public int getTextureHeightMax() {
+    return Integer.MAX_VALUE;
+  }
+
+}
diff --git a/visad/java2d/GraphicsModeControlJ2D.java b/visad/java2d/GraphicsModeControlJ2D.java
new file mode 100644
index 0000000..3be6f0d
--- /dev/null
+++ b/visad/java2d/GraphicsModeControlJ2D.java
@@ -0,0 +1,891 @@
+//
+// GraphicsModeControlJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import java.rmi.RemoteException;
+
+import visad.Control;
+import visad.DisplayException;
+import visad.DisplayImpl;
+import visad.GraphicsModeControl;
+import visad.VisADException;
+import visad.util.Util;
+
+/**
+   GraphicsModeControlJ2D is the VisAD class for controlling various
+   mode settings for rendering.<P>
+
+   A GraphicsModeControlJ2D is not linked to any DisplayRealType or
+   ScalarMap.  It is linked to a DisplayImpl.<P>
+*/
+public class GraphicsModeControlJ2D extends GraphicsModeControl {
+
+  static final String[] LINE_STYLE = {
+    "solid", "dash", "dot", "dash-dot"
+  };
+
+  private float lineWidth; // for LineAttributes; >= 1.0
+  private float pointSize; // for PointAttributes; >= 1.0
+  private int lineStyle; // for LineAttributes
+  private int colorMode;
+  private boolean pointMode; // true => points in place of lines and surfaces
+  private boolean textureEnable; // true => allow use of texture mapping
+  private boolean scaleEnable; // true => display X, Y and Z scales
+
+  private int transparencyMode = 0;
+  private int projectionPolicy = 0;
+  private int polygonMode = 0;
+
+  private boolean missingTransparent = true;
+  private int curvedSize = 10;
+  private float polygonOffset = Float.NaN;
+  private float polygonOffsetFactor = 0;
+  private boolean adjustProjectionSeam = true;
+
+  /** mode for Texture3D */
+  private int texture3DMode = STACK2D;
+
+  /** for caching Appearances*/
+  private boolean cacheAppearances = false;
+
+  /** for merging geometries */
+  private boolean mergeGeometries = false;
+
+  public GraphicsModeControlJ2D(DisplayImpl d) {
+    super(d);
+    lineWidth = 1.0f;
+    pointSize = 1.0f;
+    lineStyle = SOLID_STYLE;
+    pointMode = false;
+    textureEnable = true;
+    scaleEnable = false;
+  }
+
+  public boolean getMode2D() {
+    return getDisplayRenderer().getMode2D();
+  }
+
+  /**
+   * Get the current line width used for LineAttributes.  The default
+   * is 1.0.
+   *
+   * @return  line width (>= 1.0)
+   */
+  public float getLineWidth() {
+    return lineWidth;
+  }
+
+  /**
+   * Set the line width used for LineAttributes.  Calls changeControl
+   * and resets the display.
+   *
+   * @param width  width to use (>= 1.0)
+   *
+   * @throws  VisADException   couldn't set the line width on local display
+   * @throws  RemoteException  couldn't set the line width on remote display
+   */
+  public void setLineWidth(float width)
+         throws VisADException, RemoteException {
+    if (width < 1.0f) {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                                 "setLineWidth: width < 1.0");
+    }
+    if (Util.isApproximatelyEqual(width, lineWidth)) return;
+    lineWidth = width;
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    DisplayRendererJ2D dr = (DisplayRendererJ2D) getDisplayRenderer();
+    dr.setLineWidth(width);
+
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set the line width used for LineAttributes.   Does not update
+   * display.
+   *
+   * @param width  width to use (>= 1.0)
+   */
+  public void setLineWidth(float width, boolean noChange) {
+    if (width >= 1.0f) {
+      lineWidth = width;
+    }
+  }
+
+  /**
+   * Get the current point size used for PointAttributes.  The default
+   * is 1.0.
+   *
+   * @return  point size  (>= 1.0)
+   */
+  public float getPointSize() {
+    return pointSize;
+  }
+
+  /**
+   * Set the point size used for PointAttributes.  Calls changeControl
+   * and updates the display.
+   *
+   * @param size  size to use (>= 1.0)
+   *
+   * @throws  VisADException   couldn't set the point size on local display
+   * @throws  RemoteException  couldn't set the point size on remote display
+   */
+  public void setPointSize(float size)
+         throws VisADException, RemoteException {
+    if (size < 1.0f) {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                                 "setPointSize: size < 1.0");
+    }
+    if (Util.isApproximatelyEqual(size, pointSize)) return;
+    pointSize = size;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set the point size used for PointAttributes.  Doesn't update
+   * the display.
+   *
+   * @param size  size to use (>= 1.0)
+   */
+  public void setPointSize(float size, boolean noChange) {
+    if (size >= 1.0f) {
+      pointSize = size;
+    }
+  }
+
+  /**
+   * Get the current line style used for LineAttributes.  The default
+   * is GraphicsModeControl.SOLID_STYLE.
+   *
+   * @return  line style (SOLID_STYLE, DASH_STYLE, DOT_STYLE or DASH_DOT_STYLE)
+   */
+  public int getLineStyle() {
+    return lineStyle;
+  }
+
+  /**
+   * Set the line style used for LineAttributes.  Calls changeControl
+   * and resets the display.
+   *
+   * @param style  style to use (SOLID_STYLE, DASH_STYLE,
+   *               DOT_STYLE or DASH_DOT_STYLE)
+   *
+   * @throws  VisADException   couldn't set the line style on local display
+   * @throws  RemoteException  couldn't set the line style on remote display
+   */
+  public void setLineStyle(int style)
+         throws VisADException, RemoteException {
+    if (style != SOLID_STYLE && style != DASH_STYLE &&
+      style != DOT_STYLE && style != DASH_DOT_STYLE)
+    {
+      style = SOLID_STYLE;
+    }
+    if (style == lineStyle) return;
+    lineStyle = style;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set the line style used for LineAttributes.   Does not update display.
+   *
+   * @param style  style to use (SOLID_STYLE, DASH_STYLE,
+   *               DOT_STYLE or DASH_DOT_STYLE)
+   */
+  public void setLineStyle(int style, boolean noChange) {
+    if (style != SOLID_STYLE && style != DASH_STYLE &&
+      style != DOT_STYLE && style != DASH_DOT_STYLE)
+    {
+      style = SOLID_STYLE;
+    }
+    lineStyle = style;
+  }
+
+  /**
+   * Get the color mode used for combining color values.  The default
+   * is GraphicsModeControl.AVERAGE_COLOR_MODE.
+   *
+   * @return  color mode (AVERAGE_COLOR_MODE or SUM_COLOR_MODE)
+   */
+  public int getColorMode() {
+    return colorMode;
+  }
+
+  /**
+   * Set the color mode used for combining color values.
+   *
+   * @param mode  mode to use (AVERAGE_COLOR_MODE or SUM_COLOR_MODE)
+   */
+  public void setColorMode(int mode)
+         throws VisADException, RemoteException {
+    if (mode != AVERAGE_COLOR_MODE && mode != SUM_COLOR_MODE) {
+      mode = AVERAGE_COLOR_MODE;
+    }
+    if (mode == colorMode) return;
+    colorMode = mode;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Gets the point mode.
+   *
+   * @return  True if the display is using points rather than connected
+   *          lines or surfaces for rendering.
+   */
+  public boolean getPointMode() {
+    return pointMode;
+  }
+
+  /**
+   * Sets the point mode and updates the display.
+   *
+   * @param mode         true if the display should use points rather
+   *                     than connected lines or surfaces for rendering.
+   */
+  public void setPointMode(boolean mode)
+         throws VisADException, RemoteException {
+    if (mode == pointMode) return;
+    pointMode = mode;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set whether texture mapping should be used or not.
+   *
+   * @param  enable   true to use texture mapping (the default)
+   */
+  public void setTextureEnable(boolean enable)
+         throws VisADException, RemoteException {
+    if (enable == textureEnable) return;
+    textureEnable = enable;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * See if texture mapping is enabled or not
+   *
+   * @return    true if texture mapping is enabled.
+   */
+  public boolean getTextureEnable() {
+    return textureEnable;
+  }
+
+  /**
+   *  Toggle the axis scales in the display
+   *
+   * @param  enable    true to enable, false to disable
+   *
+   * @throws  VisADException   couldn't change state of scale enablement
+   * @throws  RemoteException  couldn't change state of scale enablement on
+   *                           remote display
+   */
+  public void setScaleEnable(boolean enable)
+         throws VisADException, RemoteException {
+    if (enable == scaleEnable) return;
+    scaleEnable = enable;
+    getDisplayRenderer().setScaleOn(enable);
+    changeControl(true);
+  }
+
+  /**
+   * Get whether display scales are enabled or not
+   *
+   * @return  true if enabled, otherwise false
+   */
+  public boolean getScaleEnable() {
+    return scaleEnable;
+  }
+
+  /**
+   * Get the current transparency mode
+   *
+   * @return  transparency mode
+   */
+  public int getTransparencyMode() {
+    return transparencyMode;
+  }
+
+  /**
+   * Sets the transparency mode.
+   *
+   * @param   mode   transparency mode to use.  Legal value = 0.
+   * @throws  VisADException    bad mode or couldn't create necessary VisAD
+   *                            object
+   * @throws  RemoteException   couldn't create necessary remote object
+   */
+  public void setTransparencyMode(int mode)
+         throws VisADException, RemoteException {
+    if (mode == 0) {
+      transparencyMode = mode;
+    }
+    else {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                                 "setTransparencyMode: bad mode");
+    }
+  }
+
+  /**
+   * Sets the projection policy for the display.  
+   *
+   * @param   policy      policy to be used 
+   *
+   * @throws  VisADException   bad policy or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  change policy on remote display
+   */
+  public void setProjectionPolicy(int policy)
+         throws VisADException, RemoteException {
+    if (policy == 0) {
+      projectionPolicy = policy;
+    }
+    else {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                                 "setProjectionPolicy: bad policy");
+    }
+  }
+
+  /**
+   * Get the current projection policy for the display.
+   *
+   * @return  policy
+   */
+  public int getProjectionPolicy() {
+    return projectionPolicy;
+  }
+
+  /**
+   * Sets the polygon mode.
+   *
+   * @param  mode   the polygon mode to be used
+   *
+   * @throws  VisADException   bad mode or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  can't change mode on remote display
+   */
+  public void setPolygonMode(int mode)
+         throws VisADException, RemoteException {
+    if (mode == 0) {
+      polygonMode = mode;
+    }
+    else {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                                 "setPolygonMode: bad mode");
+    }
+  }
+
+  /**
+   * Sets the polygon mode.  Does not update the display
+   *
+   * @param  mode   the polygon mode to be used
+   *
+   * @throws  VisADException   bad mode or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  can't change mode on remote display
+   */
+  public void setPolygonMode(int mode, boolean noChange) {
+    if (mode == 0) {
+      polygonMode = mode;
+    }
+  }
+
+  /**
+   * Get the current polygon mode.
+   *
+   * @return  mode
+   */
+  public int getPolygonMode() {
+    return polygonMode;
+  }
+
+  /**
+   * Sets the polygon offset and updates the display.  
+   *
+   * @param  polygonOffset  the polygon offset to be used
+   *
+   * @throws  VisADException   bad offset 
+   * @throws  RemoteException  can't change offset on remote display
+   */
+  
+  public void setPolygonOffset(float polygonOffset)
+         throws VisADException, RemoteException {
+    if (polygonOffset == 0 || Float.isNaN(polygonOffset)) {
+      this.polygonOffset = polygonOffset;
+    }
+    else 
+    {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                                 "setPolygonOffset not supported");
+    }
+  }
+
+  /**
+   * Sets the polygon offset. 
+   *
+   * @param  polygonOffset  the polygon offset to be used
+   * @param  noChange   dummy variable
+   */
+  
+  public void setPolygonOffset(float polygonOffset, boolean noChange) {
+    if (polygonOffset == 0 || Float.isNaN(polygonOffset)) {
+      this.polygonOffset = polygonOffset;
+    }
+  }
+
+  /**
+   * Get the current polygon offset.
+   *
+   * @return  offset
+   */
+  public float getPolygonOffset() {
+    return polygonOffset;
+  }
+
+  /**
+   * Sets the polygon offset factor. 
+   *
+   * @param  polygonOffsetFactor   the polygon offset factor to be used
+   *
+   * @throws  VisADException   bad offset factor with change
+   * @throws  RemoteException  can't change mode on remote display
+   */
+  public void setPolygonOffsetFactor(float polygonOffsetFactor)
+         throws VisADException, RemoteException {
+    if (polygonOffsetFactor == 0) {
+      this.polygonOffsetFactor = polygonOffsetFactor;
+    }
+    else 
+    {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                               "setPolygonOffsetFactor not supported");
+    }
+  }
+
+  /**
+   * Sets the polygon offset factor.  
+   *
+   * @param  polygonOffsetFactor   the polygon offset factor to be used
+   * @param  noChange   dummy variable
+   *
+   * @throws  VisADException   bad offset factor with change
+   * @throws  RemoteException  can't change offset on remote display
+   */
+  
+  public void setPolygonOffsetFactor(float polygonOffsetFactor, boolean noChange) {
+    if (polygonOffsetFactor == 0) {
+      this.polygonOffsetFactor = polygonOffsetFactor;
+    }
+  }
+
+  /**
+   * Get the current polygon offset factor.
+   *
+   * @return  offset
+   */
+  public float getPolygonOffsetFactor() {
+    return polygonOffsetFactor;
+  }
+
+
+  /**
+   * See whether missing values are rendered as transparent or not.
+   *
+   * @return  true if missing values are transparent.
+   */
+  public boolean getMissingTransparent() {
+    return missingTransparent;
+  }
+
+  /**
+   * Set the transparency of missing values.
+   *
+   * @param  missing   true if missing values should be rendered transparent.
+   */
+  public void setMissingTransparent(boolean missing)
+         throws VisADException, RemoteException {
+    if (!missing) {
+      missingTransparent = missing;
+    }
+    else {
+      throw new DisplayException("GraphicsModeControlJ2D." +
+                                 "setMissingTransparent: must be false");
+    }
+  }
+
+  /**
+   * Get the undersampling factor of surface shape for curved texture maps
+   *
+   * @return  undersampling factor (default 10)
+   */
+  public int getCurvedSize() {
+    return curvedSize;
+  }
+
+  /**
+   * Set the undersampling factor of surface shape for curved texture maps
+   *
+   * @param  curved_size  undersampling factor (default 10)
+   */
+  public void setCurvedSize(int curved_size) {
+    curvedSize = curved_size;
+  }
+
+  /**
+   * Set whether VisADGeometryArray.adjustLongitude/Seam should be used.
+   *
+   * @param  adjust   true to use adjust methods
+   */
+  public void setAdjustProjectionSeam(boolean adjust)
+         throws VisADException, RemoteException {
+    if (adjust == adjustProjectionSeam) return;
+    adjustProjectionSeam = adjust;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * See if VisADGeometryArray.adjustLongitude/Seam should be used
+   *
+   * @return    true if adjust methods should be used
+   */
+  public boolean getAdjustProjectionSeam() {
+    return adjustProjectionSeam;
+  }
+
+  /**
+   * Set the mode for Texture3D for volume rendering
+   *
+   * @param  mode   mode for Texture3D (STACK2D or TEXTURE3D)
+   *
+   * @throws  VisADException   Unable to change Texture3D mode
+   * @throws  RemoteException  can't change Texture3D mode on remote display
+   */
+  public void setTexture3DMode(int mode)
+         throws VisADException, RemoteException {
+    if (texture3DMode == mode) return;
+    texture3DMode = mode;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set whether the Appearances are reused
+   *
+   * @param  cache   true to cache and reuse appearances
+   *
+   * @throws  VisADException   Unable to change caching
+   * @throws  RemoteException  can't change caching on remote display
+   */
+  public void setCacheAppearances(boolean cache) {
+    cacheAppearances = cache;
+  }
+
+  /**
+   * Get whether Appearances are cached or not
+   *
+   * @return  true if caching
+   */
+  public boolean getCacheAppearances() {
+    return cacheAppearances;
+  }
+
+  /**
+   * Set whether Geometries for shapes should be merged into Group if
+   * possible to reduce memory use.
+   *
+   * @param  merge   true to merge geometries if possible
+   *
+   * @throws  VisADException   Unable to change caching
+   * @throws  RemoteException  can't change caching on remote display
+   */
+  public void setMergeGeometries(boolean merge) {
+    mergeGeometries = merge;
+  }
+
+  /**
+   * Set whether Geometries for shapes should be merged into Group
+   *
+   * @return  true if merging is used
+   */
+  public boolean getMergeGeometries() {
+    return mergeGeometries;
+  }
+
+  /**
+   * Get the mode for Texture3D for volume rendering
+   *
+   * @return  mode for Texture3D (e.g., STACK2D or TEXTURE3D)
+   */
+  public int getTexture3DMode() {
+    return texture3DMode;
+  }
+
+  public Object clone() {
+    GraphicsModeControlJ2D mode =
+      new GraphicsModeControlJ2D(getDisplay());
+    mode.lineWidth = lineWidth;
+    mode.pointSize = pointSize;
+    mode.lineStyle = lineStyle;
+    mode.pointMode = pointMode;
+    mode.textureEnable = textureEnable;
+    mode.scaleEnable = scaleEnable;
+    mode.transparencyMode = transparencyMode;
+    mode.projectionPolicy = projectionPolicy;
+    mode.missingTransparent = missingTransparent;
+    mode.polygonMode = polygonMode;
+    mode.curvedSize = curvedSize;
+    mode.polygonOffset = polygonOffset;
+    mode.polygonOffsetFactor = polygonOffsetFactor;
+    mode.adjustProjectionSeam = adjustProjectionSeam;
+    mode.texture3DMode = texture3DMode;
+    mode.cacheAppearances = cacheAppearances;
+    mode.mergeGeometries = mergeGeometries;
+    return mode;
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof GraphicsModeControlJ2D)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    GraphicsModeControlJ2D rmtCtl = (GraphicsModeControlJ2D )rmt;
+
+    boolean changed = false;
+    boolean redisplay = false;
+
+    if (!Util.isApproximatelyEqual(lineWidth, rmtCtl.lineWidth)) {
+      changed = true;
+      redisplay = true;
+      lineWidth = rmtCtl.lineWidth;
+    }
+    if (!Util.isApproximatelyEqual(pointSize, rmtCtl.pointSize)) {
+      changed = true;
+      redisplay = true;
+      pointSize = rmtCtl.pointSize;
+    }
+    if (lineStyle != rmtCtl.lineStyle) {
+      changed = true;
+      redisplay = true;
+      lineStyle = rmtCtl.lineStyle;
+    }
+
+    if (pointMode != rmtCtl.pointMode) {
+      changed = true;
+      redisplay = true;
+      pointMode = rmtCtl.pointMode;
+    }
+    if (textureEnable != rmtCtl.textureEnable) {
+      changed = true;
+      redisplay = true;
+      textureEnable = rmtCtl.textureEnable;
+    }
+    if (scaleEnable != rmtCtl.scaleEnable) {
+      changed = true;
+      scaleEnable = rmtCtl.scaleEnable;
+      getDisplayRenderer().setScaleOn(scaleEnable);
+    }
+
+    if (transparencyMode != rmtCtl.transparencyMode) {
+      changed = true;
+      transparencyMode = rmtCtl.transparencyMode;
+    }
+    if (projectionPolicy != rmtCtl.projectionPolicy) {
+      changed = true;
+      projectionPolicy = rmtCtl.projectionPolicy;
+    }
+    if (polygonMode != rmtCtl.polygonMode) {
+      changed = true;
+      polygonMode = rmtCtl.polygonMode;
+    }
+
+    if (missingTransparent != rmtCtl.missingTransparent) {
+      changed = true;
+      missingTransparent = rmtCtl.missingTransparent;
+    }
+
+    if (curvedSize != rmtCtl.curvedSize) {
+      changed = true;
+      curvedSize = rmtCtl.curvedSize;
+    }
+
+    if (adjustProjectionSeam != rmtCtl.adjustProjectionSeam) {
+      changed = true;
+      redisplay = true;
+      adjustProjectionSeam = rmtCtl.adjustProjectionSeam;
+    }
+
+    if (texture3DMode != rmtCtl.texture3DMode) {
+      changed = true;
+      redisplay = true;
+      texture3DMode = rmtCtl.texture3DMode;
+    }
+
+    if (cacheAppearances != rmtCtl.cacheAppearances) {
+      changed = true;
+      cacheAppearances = rmtCtl.cacheAppearances;
+    }
+
+    if (mergeGeometries != rmtCtl.mergeGeometries) {
+      changed = true;
+      mergeGeometries = rmtCtl.mergeGeometries;
+    }
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+    if (redisplay) {
+      getDisplay().reDisplayAll();
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    GraphicsModeControlJ2D gmc = (GraphicsModeControlJ2D )o;
+
+    boolean changed = false;
+
+    if (!Util.isApproximatelyEqual(lineWidth, gmc.lineWidth)) {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(pointSize, gmc.pointSize)) {
+      return false;
+    }
+    if (lineStyle != gmc.lineStyle) {
+      return false;
+    }
+
+    if (pointMode != gmc.pointMode) {
+      return false;
+    }
+    if (textureEnable != gmc.textureEnable) {
+      return false;
+    }
+    if (scaleEnable != gmc.scaleEnable) {
+      return false;
+    }
+
+    if (transparencyMode != gmc.transparencyMode) {
+      return false;
+    }
+    if (projectionPolicy != gmc.projectionPolicy) {
+      return false;
+    }
+    if (polygonMode != gmc.polygonMode) {
+      return false;
+    }
+
+    if (missingTransparent != gmc.missingTransparent) {
+      return false;
+    }
+
+    if (curvedSize != gmc.curvedSize) {
+      return false;
+    }
+
+    if (adjustProjectionSeam != gmc.adjustProjectionSeam) {
+      return false;
+    }
+
+    if (texture3DMode != gmc.texture3DMode) {
+      return false;
+    }
+
+    if (cacheAppearances != gmc.cacheAppearances) {
+      return false;
+    }
+
+    if (mergeGeometries != gmc.mergeGeometries) {
+      return false;
+    }
+
+
+    return true;
+  }
+
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("GraphicsModeControlJ2D[");
+
+    buf.append("lw ");
+    buf.append(lineWidth);
+    buf.append(",ps ");
+    buf.append(pointSize);
+    buf.append(",ls ");
+    buf.append(LINE_STYLE[lineStyle]);
+
+    buf.append(pointMode ? "pm" : "!pm");
+    buf.append(textureEnable ? "te" : "!te");
+    buf.append(scaleEnable ? "se" : "!se");
+    buf.append(missingTransparent ? "mt" : "!mt");
+
+    buf.append(",tm ");
+    buf.append(transparencyMode);
+    buf.append(",pp ");
+    buf.append(projectionPolicy);
+    buf.append(",pm ");
+    buf.append(polygonMode);
+    buf.append(",cs ");
+    buf.append(curvedSize);
+    buf.append(",po ");
+    buf.append(polygonOffset);
+    buf.append(",pof ");
+    buf.append(polygonOffsetFactor);
+    buf.append(adjustProjectionSeam ? "as" : "!as");
+    buf.append(",t3dm ");
+    buf.append(texture3DMode);
+    buf.append(",ca ");
+    buf.append(cacheAppearances);
+    buf.append(",mg ");
+    buf.append(mergeGeometries);
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/java2d/KeyboardBehaviorJ2D.java b/visad/java2d/KeyboardBehaviorJ2D.java
new file mode 100644
index 0000000..faf1619
--- /dev/null
+++ b/visad/java2d/KeyboardBehaviorJ2D.java
@@ -0,0 +1,242 @@
+//
+//  KeyboardBehaviorJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.rmi.RemoteException;
+
+import visad.DisplayEvent;
+import visad.DisplayImpl;
+import visad.DisplayRenderer;
+import visad.KeyboardBehavior;
+import visad.MouseBehavior;
+import visad.ProjectionControl;
+import visad.VisADException;
+
+/**
+ *  KeyboardBehaviorJ2D is the VisAD class for keyboard control of
+ *  translate (pan) and zoom in Java2D.
+ *  @author Troy Sandblom   NCAR/RAP May, 2000
+ *  @author Don Murray (adapted to VisAD with help from Curtis)
+ */
+public class KeyboardBehaviorJ2D 
+  implements KeyboardBehavior {
+
+  private ProjectionControl proj;
+  private DisplayRenderer displayRenderer;
+  private MouseBehavior mouseBehavior;
+
+  private double rotateAmount = 5.0;
+  private double scaleAmount = .05;
+  private double transAmount = .1;
+
+  /** Identifier for function to rotate positively around the Z viewing axis
+  (perpendicular to the screen plane) */
+  public static final int ROTATE_Z_POS = 7;
+
+  /** Identifier for function to rotate negatively around the Z viewing axis
+  (perpendicular to the screen plane) */
+  public static final int ROTATE_Z_NEG = 8;
+
+  /** Maximum number of functions for this behavior */
+  private final int MAX_FUNCTIONS = 9;
+
+  // Should someday make an object that encapsulates both and use that
+  // for checking.
+  private int[] functionKeys = new int[MAX_FUNCTIONS];
+  private int[] functionMods = new int[MAX_FUNCTIONS];
+
+  /**
+   * Construct a new keyboard behavior for the DisplayRenderer.  You
+   * need to add the behavior to the DisplayRenderer if you want to 
+   * use it.  Default keys for manipulations are as follows:<BR>
+   * Translation
+   * <UL>
+   * <LI>Arrow keys - translate up, down, left, right
+   * </UL>
+   * Zoom
+   * <UL>
+   * <LI>Shift + Up arrow - zoom in (ZOOM_IN)
+   * <LI>Shift + Down arrow - zoom out (ZOOM_OUT)
+   * </UL>
+   * Rotate
+   * <UL>
+   * <LI>Shift + Left arrow - rotate left  (ROTATE_Z_POS)
+   * <LI>Shift + Right arrow - rotate right (ROTATE_Z_NEG)
+   * </UL>
+   * Reset
+   * <UL>
+   * <LI>Ctrl + R key - reset to original projection (RESET)
+   * </UL>
+   * <P>
+   * @see  DisplayRendererJ2D#addKeyboardBehavior(KeyboardBehaviorJ2D behavior)
+   * @param  r  DisplayRenderer to use.
+   */
+  public KeyboardBehaviorJ2D(DisplayRendererJ2D r) {
+    displayRenderer = r;
+    proj = displayRenderer.getDisplay().getProjectionControl();
+    mouseBehavior = displayRenderer.getMouseBehavior();
+    // initialize array functions
+    mapKeyToFunction(TRANSLATE_UP, KeyEvent.VK_UP, NO_MASK);
+    mapKeyToFunction(TRANSLATE_DOWN, KeyEvent.VK_DOWN, NO_MASK);
+    mapKeyToFunction(TRANSLATE_LEFT, KeyEvent.VK_LEFT, NO_MASK);
+    mapKeyToFunction(TRANSLATE_RIGHT, KeyEvent.VK_RIGHT, NO_MASK);
+    mapKeyToFunction(ZOOM_IN, KeyEvent.VK_UP, InputEvent.SHIFT_MASK);
+    mapKeyToFunction(ZOOM_OUT, KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK);
+    mapKeyToFunction(RESET, KeyEvent.VK_R, InputEvent.CTRL_MASK);
+    mapKeyToFunction(ROTATE_Z_POS, KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK);
+    mapKeyToFunction(ROTATE_Z_NEG, KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK);
+  }
+
+  /**
+   * Maps key represented by keycode & modifiers to the given function.
+   * Each function can only have one key/modifier combination assigned 
+   * to it at a time.
+   * @see java.awt.event.KeyEvent
+   * @see java.awt.event.InputEvent
+   * @param  function  keyboard function (ROTATE_X_POS, ZOOM_IN, etc)
+   * @param  keycode   <CODE>KeyEvent</CODE> virtual keycodes 
+   * @param  modifiers <CODE>InputEvent</CODE> key mask
+   */
+  public void mapKeyToFunction(int function, int keycode, int modifiers) {
+     if (function < 0 || function >= MAX_FUNCTIONS) return;
+     functionKeys[function] = keycode;
+     functionMods[function] = modifiers;
+  }
+
+  /**
+   *  Process a key event.  Determines whether a meaningful key was pressed.
+   *  @param  event  KeyEvent stimulus
+   */
+  
+  public void processKeyEvent(KeyEvent event) {
+    int id = event.getID();
+
+    if (id == KeyEvent.KEY_PRESSED) {
+      int modifiers = event.getModifiers();
+      int keyCode = event.getKeyCode();
+
+      // determine whether a meaningful key was pressed
+      for (int i=0; i<MAX_FUNCTIONS; i++) {
+        if (functionKeys[i] == keyCode && (modifiers == functionMods[i])) {
+          execFunction(i);
+          break;
+        }
+      }
+    }
+
+    // notify DisplayListeners of key event
+    int d_id = -1;
+    if (id == KeyEvent.KEY_PRESSED) d_id = DisplayEvent.KEY_PRESSED;
+    else if (id == KeyEvent.KEY_RELEASED) d_id = DisplayEvent.KEY_RELEASED;
+    if (d_id != -1) {
+      try {
+        DisplayImpl display = displayRenderer.getDisplay();
+        DisplayEvent e = new DisplayEvent(display, d_id, event);
+        display.notifyListeners(e);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+  }
+
+  /** 
+   * Executes the given function. 
+   * @param  function   function to perform (TRANSLATE_UP, ZOOM_IN, etc)
+   */
+  public void execFunction(int function) {
+
+    double transx = 0.0;
+    double transy = 0.0;
+    double scale = 1.0;
+    double anglez = 0.0;
+    double [] t1 = null;
+    double [] tstart = proj.getMatrix();
+
+    switch (function) {
+
+      case TRANSLATE_UP: // NB: down is up in Java2D compared with Java3D
+        transy -= transAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case TRANSLATE_DOWN:
+        transy += transAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case TRANSLATE_LEFT:
+        transx -= transAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case TRANSLATE_RIGHT:
+        transx += transAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case RESET:
+        tstart = proj.getSavedProjectionMatrix();
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case ZOOM_IN:
+        scale += scaleAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0);
+        break;
+      case ZOOM_OUT:
+        scale -= scaleAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0);
+        break;
+      case ROTATE_Z_NEG:
+        anglez -= rotateAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, anglez, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case ROTATE_Z_POS:
+        anglez += rotateAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, anglez, 1.0, 0.0, 0.0, 0.0);
+        break;
+      default:
+        break;
+    }
+
+    if (t1 != null) {
+      t1 = mouseBehavior.multiply_matrix(t1, tstart);
+      try {
+        proj.setMatrix(t1);
+      } catch (VisADException e) {
+      } catch (RemoteException e) {
+      }
+    }
+    
+  }
+}
diff --git a/visad/java2d/MouseBehaviorJ2D.java b/visad/java2d/MouseBehaviorJ2D.java
new file mode 100644
index 0000000..c8aad20
--- /dev/null
+++ b/visad/java2d/MouseBehaviorJ2D.java
@@ -0,0 +1,411 @@
+//
+// MouseBehaviorJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.lang.reflect.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+
+/**
+   MouseBehaviorJ2D is the VisAD class for mouse behaviors for Java2D
+*/
+
+public class MouseBehaviorJ2D implements MouseBehavior {
+
+  /** DisplayRenderer for Display */
+  DisplayRendererJ2D display_renderer;
+  DisplayImpl display;
+
+  private MouseHelper helper = null;
+
+  /**
+   * Construct a MouseBehavior for the DisplayRenderer specified
+   * @param  r  DisplayRenderer to use
+   */
+  public MouseBehaviorJ2D(DisplayRendererJ2D r) {
+    this(r, MouseHelper.class);
+  }
+
+  /**
+   * Construct a MouseBehavior for the DisplayRenderer specified
+   * @param  r  DisplayRenderer to use
+   * @param  mhClass  MouseHelper subclass to use
+   */
+  public MouseBehaviorJ2D(DisplayRendererJ2D r, Class mhClass) {
+    try {
+      Class[] param = new Class[] {DisplayRenderer.class, MouseBehavior.class};
+      Constructor mhConstructor =
+        mhClass.getConstructor(param);
+      helper = (MouseHelper) mhConstructor.newInstance(new Object[] {r, this});
+    }
+    catch (Exception e) {
+      throw new VisADError("cannot construct " + mhClass);
+    }
+    // helper = new MouseHelper(r, this);
+
+    display_renderer = r;
+    display = display_renderer.getDisplay();
+  }
+
+  /**
+   * Get the helper class used by this MouseBehavior.  
+   * The <CODE>MouseHelper</CODE> defines the actions taken based
+   * on <CODE>MouseEvent</CODE>s.
+   * @return  <CODE>MouseHelper</CODE> being used.
+   */
+  public MouseHelper getMouseHelper() {
+    return helper;
+  }
+
+  /**
+   * Return the VisAD ray corresponding to the component coordinates.
+   * @param  screen_x  x coordinate of the component
+   * @param  screen_y  y coordinate of the component
+   * @return  corresponding VisADRay
+   * @see visad.VisADRay
+   * @see visad.LocalDisplay#getComponent()
+   */
+  public VisADRay findRay(int screen_x, int screen_y) {
+    // System.out.println("findRay " + screen_x + " " + screen_y);
+    VisADCanvasJ2D canvas = display_renderer.getCanvas();
+    AffineTransform trans = canvas.getTransform();
+
+    if (trans == null) return null;
+
+    double[] coords = {(float) screen_x, (float) screen_y};
+    double[] newcoords = new double[2];
+    try {
+      trans.inverseTransform(coords, 0, newcoords, 0, 1);
+    }
+    catch (NoninvertibleTransformException e) {
+      throw new VisADError("MouseBehaviorJ2D.findRay: " +
+                           "non-invertable transform");
+    }
+
+    VisADRay ray = new VisADRay();
+    ray.position[0] = newcoords[0];
+    ray.position[1] = newcoords[1];
+    ray.position[2] = 0.0;
+    ray.vector[0] = 0.0;
+    ray.vector[1] = 0.0;
+    ray.vector[2] = -1.0;
+    return ray;
+  }
+
+  /**
+   * Return the VisAD ray corresponding to the VisAD cursor coordinates.
+   * @param  cursor  array (x,y) of cursor location
+   * @return  corresponding VisADRay
+   * @see visad.VisADRay
+   * @see visad.DisplayRenderer#getCursor()
+   */
+  public VisADRay cursorRay(double[] cursor) {
+    VisADRay ray = new VisADRay();
+    ray.position[0] = cursor[0];
+    ray.position[1] = cursor[1];
+    ray.position[2] = 0.0;
+    ray.vector[0] = 0.0;
+    ray.vector[1] = 0.0;
+    ray.vector[2] = -1.0;
+    return ray;
+  }
+
+  /**
+   * Return the screen coordinates corresponding to the VisAD coordinates.
+   * @param  position  array of VisAD coordinates
+   * @return  corresponding (x, y) screen coordinates
+   */
+  public int[] getScreenCoords(double[] position) {
+    VisADCanvasJ2D canvas = display_renderer.getCanvas();
+    AffineTransform trans = canvas.getTransform();
+
+    if (trans == null) return null;
+
+    double[] newcoords = new double[2];
+    trans.transform(position, 0, newcoords, 0, 1);
+
+    int[] coords = new int[2];
+    for (int i=0; i<2; i++) coords[i] = (int) newcoords[i];
+    return coords;
+  }
+
+  /**
+   * Create a translation matrix.
+   * @param  transx   x translation amount
+   * @param  transy   y translation amount
+   * @param  transz   z translation amount
+   * @return  new translation matrix.  This can be used to translate
+   *          the current matrix
+   * @see #multiply_matrix(double[] a, double[] b)
+   */
+  public double[] make_translate(double transx, double transy, double transz) {
+    return make_matrix(0.0, 0.0, 0.0, 1.0, transx, -transy, transz);
+  }
+
+  /**
+   * Create a translation matrix.  
+   * @param  transx   x translation amount
+   * @param  transy   y translation amount
+   * @return  new translation matrix.  This can be used to translate
+   *          the current matrix
+   * @see #multiply_matrix(double[] a, double[] b)
+   */
+  public double[] make_translate(double transx, double transy) {
+    return make_translate(transx, transy, 0.0);
+  }
+
+  /**
+   * Multiply the two matrices together.
+   * @param  a  first matrix
+   * @param  b  second matrix
+   * @return  new resulting matrix
+   */
+  public double[] multiply_matrix(double[] a, double[] b) {
+    AffineTransform ta = new AffineTransform(a);
+    AffineTransform tb = new AffineTransform(b);
+    ta.concatenate(tb);
+    double[] c = new double[6];
+    ta.getMatrix(c);
+    return c;
+  }
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  This function uses the fast matrix post-concatenation
+   * techniques from Graphics Gems.  
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scale  scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  public double[] make_matrix(double rotx, double roty, double rotz,
+         double scale, double transx, double transy, double transz) {
+     return make_matrix(rotx, roty, rotz, scale, scale, scale,
+                        transx, transy, transz);
+  }
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  This function uses the fast matrix post-concatenation
+   * techniques from Graphics Gems.  
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scalex  x scaling factor
+   * @param scaley  y scaling factor
+   * @param scalez  z scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  public double[] make_matrix(double rotx, double roty, double rotz,
+         double scalex, double scaley, double scalez, 
+         double transx, double transy, double transz) {
+
+    double sx, sy, sz, cx, cy, cz, t;
+    int i, j, k;
+    double deg2rad = 1.0 / 57.2957;
+    double[] matrix = new double[6];
+    double[][] mat = new double[4][4];
+
+    /* Get sin and cosine values */
+    sx = Math.sin(rotx * deg2rad);
+    cx = Math.cos(rotx * deg2rad);
+    sy = Math.sin(roty * deg2rad);
+    cy = Math.cos(roty * deg2rad);
+    sz = Math.sin(rotz * deg2rad);
+    cz = Math.cos(rotz * deg2rad);
+
+    /* Start with identity matrix */
+    mat[0][0] = 1.0;  mat[0][1] = 0.0;  mat[0][2] = 0.0;  mat[0][3] = 0.0;
+    mat[1][0] = 0.0;  mat[1][1] = 1.0;  mat[1][2] = 0.0;  mat[1][3] = 0.0;
+    mat[2][0] = 0.0;  mat[2][1] = 0.0;  mat[2][2] = 1.0;  mat[2][3] = 0.0;
+    mat[3][0] = 0.0;  mat[3][1] = 0.0;  mat[3][2] = 0.0;  mat[3][3] = 1.0;
+
+    /* Z Rotation */
+    for (i=0;i<4;i++) {
+      t = mat[i][0];
+      mat[i][0] = t*cz - mat[i][1]*sz;
+      mat[i][1] = t*sz + mat[i][1]*cz;
+    }
+
+    /* X rotation */
+    for (i=0;i<4;i++) {
+      t = mat[i][1];
+      mat[i][1] = t*cx - mat[i][2]*sx;
+      mat[i][2] = t*sx + mat[i][2]*cx;
+    }
+
+    /* Y Rotation */
+    for (i=0;i<4;i++) {
+      t = mat[i][0];
+      mat[i][0] = mat[i][2]*sy + t*cy;
+      mat[i][2] = mat[i][2]*cy - t*sy;
+    }
+
+    /* Scale */
+    for (i=0;i<3;i++) {
+      mat[i][0] *= scalex;
+      mat[i][1] *= scaley;
+      mat[i][2] *= scalez;
+    }
+
+    /* Translation */
+    mat[0][3] = transx;
+    mat[1][3] = transy;
+    mat[2][3] = transz;
+
+    matrix[0] = mat[0][0];
+    matrix[1] = mat[1][0];
+    matrix[2] = mat[0][1];
+    matrix[3] = mat[1][1];
+    matrix[4] = mat[0][3];
+    matrix[5] = mat[1][3];
+    return matrix;
+  }
+
+  static final double EPS = 0.000001;
+
+  /**
+   * Get the rotation, scale and translation parameters for the specified
+   * matrix.  Results are not valid for non-uniform aspect (scale).
+   * @param  rot  array to hold x,y,z rotation values
+   * @param  scale  array to hold scale value
+   * @param  trans  array to hold x,y,z translation values
+   */
+  public void instance_unmake_matrix(double[] rot, double[] scale,
+                                     double[] trans, double[] matrix) {
+    double  sx, sy, sz, cx, cy, cz;
+    int i, j;
+    double[][] mat = new double[4][4];
+    double[][] nat = new double[4][4];
+
+    double scalex, scaley, scalez, cxa, cxb, cxinv;
+    double[] scaleinv = new double[3];
+
+    if (rot == null || rot.length != 3) return;
+    if (scale == null || !(scale.length != 1 || scale.length !=3)) return;
+    if (trans == null || trans.length != 3) return;
+    if (matrix == null || matrix.length != 6) return;
+
+    /* Start with identity matrix */
+    mat[0][0] = 1.0;  mat[0][1] = 0.0;  mat[0][2] = 0.0;  mat[0][3] = 0.0;
+    mat[1][0] = 0.0;  mat[1][1] = 1.0;  mat[1][2] = 0.0;  mat[1][3] = 0.0;
+    mat[2][0] = 0.0;  mat[2][1] = 0.0;  mat[2][2] = 1.0;  mat[2][3] = 0.0;
+    mat[3][0] = 0.0;  mat[3][1] = 0.0;  mat[3][2] = 0.0;  mat[3][3] = 1.0;
+
+    mat[0][0] = matrix[0];
+    mat[1][0] = matrix[1];
+    mat[0][1] = matrix[2];
+    mat[1][1] = matrix[3];
+    mat[0][3] = matrix[4];
+    mat[1][3] = matrix[5];
+
+    /* translation */
+/* WLH 24 March 2000, for consistency with change
+                      of 22 Dec 97 in static_make_matrix
+    trans[0] = mat[3][0];
+    trans[1] = mat[3][1];
+    trans[2] = mat[3][2];
+*/
+    trans[0] = mat[0][3];
+    trans[1] = mat[1][3];
+    trans[2] = mat[2][3];
+
+    /* scale */
+    scalex = scaley = scalez = 0.0;
+    for (i=0; i<3; i++) {
+      scalex += mat[0][i] * mat[0][i];
+      scaley += mat[1][i] * mat[1][i];
+      scalez += mat[2][i] * mat[2][i];
+    }
+    if (Math.abs(scalex - scaley) > EPS || Math.abs(scalex - scalez) > EPS) {
+      // System.out.println("problem " + scalex + " " + scaley + " " + scalez);
+    }
+    if (scale.length == 1) {
+      scale[0] = Math.sqrt((scalex + scaley + scalez)/3.0);
+      scaleinv[0] = Math.abs(scale[0]) > EPS ? 1.0 / scale[0] : 1.0 / EPS;
+      scaleinv[1] = scaleinv[2] = scaleinv[0];
+    } else {
+      scale[0] = Math.sqrt(scalex);
+      scale[1] = Math.sqrt(scaley);
+      scale[2] = Math.sqrt(scalez);
+      for (i=0; i<3; i++) {
+        scaleinv[i] = Math.abs(scale[i]) > EPS ? 1.0 / scale[i] : 1.0 / EPS;
+      }
+    }
+
+    for (i=0; i<3; i++) {
+      for (j=0; j<3; j++) {
+        nat[j][i] = scaleinv[j] * mat[j][i];
+      }
+    }
+
+    /* rotation */
+    sx = -nat[2][1];
+
+    cxa = Math.sqrt(nat[2][0]*nat[2][0] + nat[2][2]*nat[2][2]);
+    cxb = Math.sqrt(nat[0][1]*nat[0][1] + nat[1][1]*nat[1][1]);
+
+    if (Math.abs(cxa - cxb) > EPS) {
+      // System.out.println("problem2 " + cxa + " " + cxb);
+    }
+    /* the sign of cx does not matter;
+       it is an ambiguity in 3-D rotations:
+       (rotz, rotx, roty) = (180+rotz, 180-rotx, 180+roty) */
+    cx = (cxa + cxb) / 2.0;
+    if (Math.abs(cx) > EPS) {
+      cxinv = 1.0 / cx;
+      sy = nat[2][0] * cxinv;
+      cy = nat[2][2] * cxinv;
+      sz = nat[0][1] * cxinv;
+      cz = nat[1][1] * cxinv;
+    }
+    else {
+      /* if cx == 0 then roty and rotz are ambiguous:
+         assume rotx = 0.0 */
+      sy = 0.0;
+      cy = 1.0;
+      sz = nat[0][2];
+      cz = nat[1][2];
+    }
+
+    rot[0] = 57.2957 * Math.atan2(sx, cx);
+    rot[1] = 57.2957 * Math.atan2(sy, cy);
+    rot[2] = 57.2957 * Math.atan2(sz, cz);
+    return;
+  }
+
+}
+
diff --git a/visad/java2d/ProjectionControlJ2D.java b/visad/java2d/ProjectionControlJ2D.java
new file mode 100644
index 0000000..8910cb1
--- /dev/null
+++ b/visad/java2d/ProjectionControlJ2D.java
@@ -0,0 +1,115 @@
+//
+// ProjectionControlJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.awt.geom.AffineTransform;
+
+import java.rmi.*;
+
+/**
+   ProjectionControlJ2D is the VisAD class for controlling the Projection
+   from 3-D to 2-D.  It manipulates a TransformGroup node in the
+   scene graph.<P>
+*/
+public class ProjectionControlJ2D extends ProjectionControl {
+
+  private transient AffineTransform Matrix;
+
+  private transient VisADCanvasJ2D canvas;
+
+  /** 
+   * Construct a new ProjectionControl for the display in question.
+   * @param d  display to control.
+   * @throws  VisADException    some VisAD error
+   */
+  public ProjectionControlJ2D(DisplayImpl d) throws VisADException {
+    super(d);
+/* WLH 5 April 99
+    Matrix = new AffineTransform();
+*/
+    Matrix = init();
+    matrix = new double[MATRIX2D_LENGTH];
+    Matrix.getMatrix(matrix);
+    ((DisplayRendererJ2D) getDisplayRenderer()).setTransform2D(Matrix);
+    canvas = null;
+    saveProjection();
+  }
+
+  /** 
+   * Set the matrix that defines the graphics projection 
+   * @param m array of the matrix values (6 elements in Java2D case) 
+   * @throws VisADException   some VisAD error
+   * @throws RemoteException  Java RMI failure.
+   */
+  public void setMatrix(double[] m)
+         throws VisADException, RemoteException {
+    super.setMatrix(m);
+    Matrix = new AffineTransform(matrix);
+    DisplayRendererJ2D dr = (DisplayRendererJ2D) getDisplayRenderer();
+    dr.setTransform2D(Matrix);
+    if (canvas == null) {
+      canvas = dr.getCanvas();
+    }
+    canvas.scratchImages();
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /** 
+   * Set aspect ratio of axes.
+   * @param aspect ratios; 2 elements for Java2D 
+   * @throws VisADException   invalid array length or some other VisAD failure.
+   * @throws RemoteException  Java RMI failure.
+   */
+  public void setAspect(double[] aspect)
+         throws VisADException, RemoteException {
+    if (aspect == null || aspect.length != 2) {
+      throw new DisplayException("aspect array must be length = 2");
+    }
+    AffineTransform transform = new AffineTransform();
+    transform.setToScale(aspect[0], aspect[1]);
+    double[] mult = new double[MATRIX2D_LENGTH];
+    transform.getMatrix(mult);
+    AffineTransform mat = init();
+    double[] m = new double[MATRIX2D_LENGTH];
+    mat.getMatrix(m);
+    setMatrix(getDisplay().multiply_matrix(mult, m));
+    saveProjection();
+  }
+
+  private AffineTransform init() {
+    AffineTransform mat = new AffineTransform();
+    // SWAP flip y
+    AffineTransform t1 = new AffineTransform(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
+    mat.concatenate(t1);
+    return mat;
+  }
+
+}
diff --git a/visad/java2d/RendererJ2D.java b/visad/java2d/RendererJ2D.java
new file mode 100644
index 0000000..c5c3ffa
--- /dev/null
+++ b/visad/java2d/RendererJ2D.java
@@ -0,0 +1,245 @@
+//
+// RendererJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+
+/**
+   RendererJ2D is the VisAD abstract super-class for graphics
+   rendering algorithms under Java2D.  These transform Data
+   objects into 2-D depictions in a Display window.<P>
+
+   RendererJ2D is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public abstract class RendererJ2D extends DataRenderer {
+
+  /** parent of branch made by doAction */
+  VisADGroup swParent;
+
+  VisADSwitch swit;
+
+  public RendererJ2D() {
+    super();
+  }
+
+  public void setLinks(DataDisplayLink[] links, DisplayImpl d)
+       throws VisADException {
+    if (getDisplay() != null || getLinks() != null) {
+      throw new DisplayException("RendererJ2D.setLinks: already set");
+    }
+    if (!(d instanceof DisplayImplJ2D)) {
+      throw new DisplayException("RendererJ2D.setLinks: must be DisplayImplJ2D");
+    }
+    setDisplay(d);
+    setDisplayRenderer(d.getDisplayRenderer());
+    setLinks(links);
+
+    swParent = new VisADGroup();
+    // addSwitch((DisplayRendererJ2D) getDisplayRenderer(), swParent);
+
+    swit = new VisADSwitch();
+    VisADGroup empty = new VisADGroup();
+    swit.addChild(swParent);
+    swit.addChild(empty);
+    swit.setWhichChild(0);
+    addSwitch((DisplayRendererJ2D) getDisplayRenderer(), swit);
+    toggle(getEnabled());
+  }
+
+  public void toggle(boolean on) {
+    if (swit != null) {
+      swit.setWhichChild(on ? 0 : 1);
+      VisADCanvasJ2D canvas =
+        ((DisplayRendererJ2D) getDisplayRenderer()).getCanvas();
+      canvas.scratchImages();
+    }
+    super.toggle(on);
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowFunctionTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowRealTupleTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowRealTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowSetTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTextType(
+         TextType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowTextTypeJ2D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowTupleTypeJ2D(type, link, parent);
+  }
+
+  abstract void addSwitch(DisplayRendererJ2D displayRenderer,
+                          VisADGroup branch) throws VisADException;
+
+  /** re-transform if needed;
+      return false if not done */
+  public boolean doAction() throws VisADException, RemoteException {
+    VisADGroup branch;
+    boolean all_feasible = get_all_feasible();
+    boolean any_changed = get_any_changed();
+    boolean any_transform_control = get_any_transform_control();
+    boolean scratch = false;
+    if (all_feasible && (any_changed || any_transform_control)) {
+      // exceptionVector.removeAllElements();
+      clearAVControls();
+      try {
+        // doTransform creates a VisADGroup from a Data object
+        branch = doTransform();
+      }
+      catch (OutOfMemoryError e) {
+        // System.out.println("OutOfMemoryError, try again ...");
+        try {
+          if (swParent.numChildren() > 0) {
+            swParent.removeChild(0);
+          }
+          branch = null;
+          Runtime.getRuntime().gc();
+          Runtime.getRuntime().runFinalization();
+          branch = doTransform();
+        }
+        catch (BadMappingException ee) {
+          addException(ee);
+          branch = null;
+        }
+        catch (UnimplementedException ee) {
+          addException(ee);
+          branch = null;
+        }
+        catch (RemoteException ee) {
+          addException(ee);
+          branch = null;
+        }
+        catch (DisplayInterruptException ee) {
+          branch = null;
+        }
+      }
+      catch (BadMappingException e) {
+        addException(e);
+        branch = null;
+      }
+      catch (UnimplementedException e) {
+        addException(e);
+        branch = null;
+      }
+      catch (RemoteException e) {
+        addException(e);
+        branch = null;
+      }
+      catch (DisplayInterruptException e) {
+        branch = null;
+      }
+
+      if (branch != null) {
+        swParent.setChild(branch, 0);
+        scratch = true;
+      }
+      else { // if (branch == null)
+        if (swParent.numChildren() > 0) {
+          swParent.removeChild(0);
+          scratch = true;
+        }
+        all_feasible = false;
+        set_all_feasible(all_feasible);
+      }
+    }
+    else { // !(all_feasible && (any_changed || any_transform_control))
+      DataDisplayLink[] links = getLinks();
+      for (int i=0; i<links.length; i++) {
+        links[i].clearData();
+      }
+    }
+    if (scratch) {
+      ((DisplayImplJ2D) getDisplay()).setScratch();
+    }
+
+/* WLH 28 Oct 98
+    return all_feasible;
+*/
+    /* WLH 28 Oct 98 */
+    return (all_feasible && (any_changed || any_transform_control));
+
+  }
+
+  public void clearBranch() {
+    if (swParent.numChildren() > 0) {
+      swParent.removeChild(0);
+      VisADCanvasJ2D canvas =
+        ((DisplayRendererJ2D) getDisplayRenderer()).getCanvas();
+      canvas.scratchImages();
+    }
+  }
+
+  public void clearScene() {
+    swParent.detach();
+    ((DisplayRendererJ2D) getDisplayRenderer()).clearScene(this);
+    VisADCanvasJ2D canvas =
+      ((DisplayRendererJ2D) getDisplayRenderer()).getCanvas();
+    canvas.scratchImages();
+    super.clearScene();
+  }
+
+  /** create a VisADGroup scene graph for Data in links;
+      this can put Behavior objects in the scene graph for
+      DataRenderer classes that implement direct manipulation widgets;
+      may reduce work by only changing scene graph for Data and
+      Controls that have changed:
+      1. use boolean[] changed to determine which Data objects have changed
+      2. if Data has not changed, then use Control.checkTicks loop like in
+         prepareAction to determine which Control-s have changed */
+  public abstract VisADGroup doTransform()
+         throws VisADException, RemoteException; // J2D
+
+}
+
diff --git a/visad/java2d/ShadowFunctionOrSetTypeJ2D.java b/visad/java2d/ShadowFunctionOrSetTypeJ2D.java
new file mode 100644
index 0000000..f530980
--- /dev/null
+++ b/visad/java2d/ShadowFunctionOrSetTypeJ2D.java
@@ -0,0 +1,207 @@
+//
+// ShadowFunctionOrSetTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.util.Vector;
+import java.rmi.*;
+
+import java.awt.image.*;
+
+/**
+   The ShadowFunctionOrSetTypeJ2D is an abstract parent for
+   ShadowFunctionTypeJ2D and ShadowSetTypeJ2D.<P>
+*/
+public class ShadowFunctionOrSetTypeJ2D extends ShadowTypeJ2D {
+
+  ShadowRealTupleTypeJ2D Domain;
+  ShadowTypeJ2D Range; // null for ShadowSetTypeJ2D
+
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowFunctionOrSetTypeJ2D(MathType t, DataDisplayLink link,
+                                    ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+    if (this instanceof ShadowFunctionTypeJ2D) {
+      Domain = (ShadowRealTupleTypeJ2D)
+               ((FunctionType) Type).getDomain().buildShadowType(link, this);
+      Range = (ShadowTypeJ2D)
+              ((FunctionType) Type).getRange().buildShadowType(link, this);
+      adaptedShadowType =
+        new ShadowFunctionType(t, link, getAdaptedParent(parent),
+                       (ShadowRealTupleType) Domain.getAdaptedShadowType(),
+                       Range.getAdaptedShadowType());
+    }
+    else {
+      Domain = (ShadowRealTupleTypeJ2D)
+               ((SetType) Type).getDomain().buildShadowType(Link, this);
+      Range = null;
+      adaptedShadowType =
+        new ShadowSetType(t, link, getAdaptedParent(parent),
+                       (ShadowRealTupleType) Domain.getAdaptedShadowType());
+    }
+  }
+
+  public ShadowRealTupleTypeJ2D getDomain() {
+    return Domain;
+  }
+
+  public ShadowTypeJ2D getRange() {
+    return Range;
+  }
+
+  /** clear AccumulationVector */
+  public void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+    if (this instanceof ShadowFunctionTypeJ2D) {
+      Range.preProcess();
+    }
+  }
+
+
+  /** transform data into a VisADSceneGraphObject;
+      add generated scene graph components as children of group;
+      value_array are inherited valueArray values;
+      default_values are defaults for each display.DisplayRealTypeVector;
+      return true if need post-process */
+  public boolean doTransform(VisADGroup group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+
+    return ((ShadowFunctionOrSetType) adaptedShadowType).
+                             doTransform(group, data, value_array,
+                                         default_values, renderer, this);
+  }
+
+  public void setTexCoords(float[] texCoords, float ratiow, float ratioh) {
+    // corner 0
+    texCoords[0] = 0.0f;
+    texCoords[1] = 1.0f - ratioh;  // = 0.0f
+    // corner 1
+    texCoords[2] = ratiow;         // = 1.0f
+    texCoords[3] = 1.0f - ratioh;  // = 0.0f
+    // corner 2
+    texCoords[4] = ratiow;         // = 1.0f
+    texCoords[5] = 1.0f;
+    // corner 3
+    texCoords[6] = 0.0f;
+    texCoords[7] = 1.0f;
+  }
+
+  public Vector getTextMaps(int i, int[] textIndices) {
+    if (i < 0) {
+      return ((ShadowTextTypeJ2D) Range).getSelectedMapVector();
+    }
+    else {
+      ShadowTextTypeJ2D text = (ShadowTextTypeJ2D)
+        ((ShadowTupleTypeJ2D) Range).getComponent(textIndices[i]);
+      return text.getSelectedMapVector();
+    }
+  }
+
+  public void textureToGroup(Object group, VisADGeometryArray array,
+                            BufferedImage image, GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color,
+                            int texture_width, int texture_height)
+         throws VisADException {
+    // create basic Appearance
+    VisADAppearance appearance =
+      makeAppearance(mode, constant_alpha, constant_color, array);
+    appearance.image = image;
+    ((VisADGroup) group).addChild(appearance);
+  }
+
+  public Object makeSwitch() {
+    return new VisADSwitch();
+  }
+
+  public Object makeBranch() {
+    VisADGroup branch = new VisADGroup();
+    return branch;
+  }
+
+  public void addToGroup(Object group, Object branch)
+         throws VisADException {
+    ((VisADGroup) group).addChild((VisADGroup) branch);
+  }
+
+  public void addToSwitch(Object swit, Object branch)
+         throws VisADException {
+    ((VisADSwitch) swit).addChild((VisADGroup) branch);
+  }
+
+  public void addSwitch(Object group, Object swit, Control control,
+                        Set domain_set, DataRenderer renderer)
+         throws VisADException {
+    ((AVControlJ2D) control).addPair((VisADSwitch) swit, domain_set, renderer);
+    ((AVControlJ2D) control).init();
+    ((VisADGroup) group).addChild((VisADSwitch) swit);
+  }
+
+  public boolean recurseRange(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    return Range.doTransform((VisADGroup) group, data, value_array,
+                             default_values, renderer);
+  }
+
+  public boolean wantIndexed() {
+    return true;
+  }
+
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  public void postProcess(VisADGroup group) throws VisADException {
+    if (((ShadowFunctionOrSetType) adaptedShadowType).getFlat()) {
+      int LevelOfDifficulty = getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+/*
+        VisADGroup data_group = null;
+        // transform AccumulationVector
+        group.addChild(data_group);
+*/
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowFunctionOrSetTypeJ2D.postProcess");
+      }
+      else {
+        // includes !isTerminal
+        // nothing to do
+      }
+    }
+    else {
+      if (this instanceof ShadowFunctionTypeJ2D) {
+        Range.postProcess(group);
+      }
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java2d/ShadowFunctionTypeJ2D.java b/visad/java2d/ShadowFunctionTypeJ2D.java
new file mode 100644
index 0000000..8f8861b
--- /dev/null
+++ b/visad/java2d/ShadowFunctionTypeJ2D.java
@@ -0,0 +1,46 @@
+//
+// ShadowFunctionTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowFunctionTypeJ2D class shadows the FunctionType class,
+   within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowFunctionTypeJ2D extends ShadowFunctionOrSetTypeJ2D {
+
+  public ShadowFunctionTypeJ2D(MathType t, DataDisplayLink link,
+                               ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+}
+
diff --git a/visad/java2d/ShadowRealTupleTypeJ2D.java b/visad/java2d/ShadowRealTupleTypeJ2D.java
new file mode 100644
index 0000000..03f5f20
--- /dev/null
+++ b/visad/java2d/ShadowRealTupleTypeJ2D.java
@@ -0,0 +1,62 @@
+//
+// ShadowRealTupleTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowRealTupleTypeJ2D class shadows the RealTupleType class,
+   within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowRealTupleTypeJ2D extends ShadowTupleTypeJ2D {
+
+  public ShadowRealTupleTypeJ2D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+        int n = ((TupleType) t).getDimension();
+    tupleComponents = new ShadowRealTypeJ2D[n];
+    ShadowRealType[] components = new ShadowRealType[n];
+    for (int i=0; i<n; i++) {
+      ShadowRealTypeJ2D shadow = (ShadowRealTypeJ2D)
+        ((TupleType) Type).getComponent(i).buildShadowType(Link, this);
+      tupleComponents[i] = shadow;
+      components[i] = (ShadowRealType) shadow.getAdaptedShadowType();
+    }
+    adaptedShadowType =
+      new ShadowRealTupleType(t, link, getAdaptedParent(parent),
+                              components, this);
+  }
+
+  public ShadowRealTupleType getReference() {
+    return ((ShadowRealTupleType) adaptedShadowType).getReference();
+  }
+
+}
+
diff --git a/visad/java2d/ShadowRealTypeJ2D.java b/visad/java2d/ShadowRealTypeJ2D.java
new file mode 100644
index 0000000..f9ee680
--- /dev/null
+++ b/visad/java2d/ShadowRealTypeJ2D.java
@@ -0,0 +1,90 @@
+//
+// ShadowRealTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowRealTypeJ2D class shadows the RealType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowRealTypeJ2D extends ShadowScalarTypeJ2D {
+
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowRealTypeJ2D(MathType type, DataDisplayLink link,
+                           ShadowType parent)
+         throws VisADException, RemoteException {
+    super(type, link, parent);
+    adaptedShadowType =
+      new ShadowRealType(type, link, getAdaptedParent(parent));
+  }
+
+  /** clear AccumulationVector */
+  void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+  }
+
+  /** transform data into a Java2D VisADSceneGraphObject;
+      return true if need post-process */
+  public boolean doTransform(VisADGroup group, Data data, float[] value_array,
+                      float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    return ((ShadowRealType) adaptedShadowType).
+                       doTransform(group, data, value_array,
+                                   default_values, renderer, this);
+  }
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  void postProcess(VisADGroup group) throws VisADException {
+    if (adaptedShadowType.getIsTerminal()) {
+      int LevelOfDifficulty = adaptedShadowType.getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+/*
+        VisADGroup data_group = null;
+        // transform AccumulationVector
+        group.addChild(data_group);
+*/
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowRealTypeJ2D.postProcess");
+      }
+      else {
+        // nothing to do
+      }
+    }
+    else {
+      // nothing to do
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java2d/ShadowScalarTypeJ2D.java b/visad/java2d/ShadowScalarTypeJ2D.java
new file mode 100644
index 0000000..fa8e99d
--- /dev/null
+++ b/visad/java2d/ShadowScalarTypeJ2D.java
@@ -0,0 +1,67 @@
+//
+// ShadowScalarTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowScalarTypeJ2D class shadows the ScalarType class,
+   within a DataDisplayLink.<P>
+*/
+public abstract class ShadowScalarTypeJ2D extends ShadowTypeJ2D {
+
+  public ShadowScalarTypeJ2D(MathType type, DataDisplayLink link,
+                           ShadowType parent)
+         throws VisADException, RemoteException {
+    super(type, link, parent);
+  }
+
+  public boolean getMappedDisplayScalar() {
+    return adaptedShadowType.getMappedDisplayScalar();
+  }
+
+  public DisplayTupleType getDisplaySpatialTuple() {
+    return ((ShadowScalarType) adaptedShadowType).getDisplaySpatialTuple();
+  }
+
+  public int[] getDisplaySpatialTupleIndex() {
+    return ((ShadowScalarType) adaptedShadowType).getDisplaySpatialTupleIndex();
+  }
+
+  public int getIndex() {
+    return ((ShadowScalarType) adaptedShadowType).getIndex();
+  }
+
+  public Vector getSelectedMapVector() {
+    return ((ShadowScalarType) adaptedShadowType).getSelectedMapVector();
+  }
+
+}
+
diff --git a/visad/java2d/ShadowSetTypeJ2D.java b/visad/java2d/ShadowSetTypeJ2D.java
new file mode 100644
index 0000000..57e1c7c
--- /dev/null
+++ b/visad/java2d/ShadowSetTypeJ2D.java
@@ -0,0 +1,46 @@
+//
+// ShadowSetTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowSetTypeJ2D class shadows the SetType class,
+   within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowSetTypeJ2D extends ShadowFunctionOrSetTypeJ2D {
+
+  public ShadowSetTypeJ2D(MathType t, DataDisplayLink link,
+                          ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+}
+
diff --git a/visad/java2d/ShadowTextTypeJ2D.java b/visad/java2d/ShadowTextTypeJ2D.java
new file mode 100644
index 0000000..d7d03b2
--- /dev/null
+++ b/visad/java2d/ShadowTextTypeJ2D.java
@@ -0,0 +1,90 @@
+//
+// ShadowTextTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowTextTypeJ2D class shadows the TextType class,
+   within a DataDisplayLink, under Java2D.<P>
+*/
+public class ShadowTextTypeJ2D extends ShadowScalarTypeJ2D {
+
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowTextTypeJ2D(MathType t, DataDisplayLink link,
+                           ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+    adaptedShadowType =
+      new ShadowTextType(t, link, getAdaptedParent(parent));
+  }
+
+  /** clear AccumulationVector */
+  void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+  }
+
+  /** transform data into a Java2D VisADSceneGraphObject;
+      return true if need post-process */
+  public boolean doTransform(VisADGroup group, Data data, float[] value_array,
+                      float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    return ((ShadowTextType) adaptedShadowType).
+                       doTransform(group, data, value_array,
+                                   default_values, renderer, this);
+  }
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  void postProcess(VisADGroup group) throws VisADException {
+    if (adaptedShadowType.getIsTerminal()) {
+      int LevelOfDifficulty = adaptedShadowType.getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+/*
+        VisADGroup data_group = null;
+        // transform AccumulationVector
+        group.addChild(data_group);
+*/
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowTextTypeJ2D.postProcess");
+      }
+      else {
+        // nothing to do
+      }
+    }
+    else {
+      // nothing to do
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java2d/ShadowTupleTypeJ2D.java b/visad/java2d/ShadowTupleTypeJ2D.java
new file mode 100644
index 0000000..472d17b
--- /dev/null
+++ b/visad/java2d/ShadowTupleTypeJ2D.java
@@ -0,0 +1,131 @@
+//
+// ShadowTupleTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowTupleTypeJ2D class shadows the TupleType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowTupleTypeJ2D extends ShadowTypeJ2D {
+
+  ShadowTypeJ2D[] tupleComponents;
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowTupleTypeJ2D(MathType t, DataDisplayLink link,
+                            ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+    if (this instanceof ShadowRealTupleTypeJ2D) return;
+
+    int n = ((TupleType) t).getDimension();
+    tupleComponents = new ShadowTypeJ2D[n];
+    ShadowType[] components = new ShadowType[n];
+    for (int i=0; i<n; i++) {
+      ShadowTypeJ2D shadow = (ShadowTypeJ2D)
+        ((TupleType) Type).getComponent(i).buildShadowType(Link, this);
+      tupleComponents[i] = shadow;
+      components[i] = shadow.getAdaptedShadowType();
+    }
+    adaptedShadowType =
+      new ShadowTupleType(t, link, getAdaptedParent(parent),
+                          components);
+  }
+
+  /** get number of components */
+  public int getDimension() {
+    return tupleComponents.length;
+  }
+
+  public ShadowType getComponent(int i) {
+    return tupleComponents[i];
+  }
+
+  boolean isFlat() {
+    return ((ShadowTupleType) adaptedShadowType).isFlat();
+  }
+
+  /** clear AccumulationVector */
+  public void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+/*
+    for (int i=0; i<num_components; i++) {
+      component_type.preProcess();
+    }
+*/
+  }
+
+  /** transform data into a VisADSceneGraphObject;
+      return true if need post-process */
+  public boolean doTransform(VisADGroup group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    return ((ShadowTupleType) adaptedShadowType).
+                       doTransform(group, data, value_array,
+                                   default_values, renderer, this);
+  }
+
+  public boolean recurseComponent(int i, Object group, Data data,
+             float[] value_array, float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    return tupleComponents[i].doTransform((VisADGroup) group, data, value_array,
+                                          default_values, renderer);
+  }
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  public void postProcess(VisADGroup group) throws VisADException {
+    if (adaptedShadowType.getIsTerminal()) {
+      int LevelOfDifficulty = adaptedShadowType.getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+/*
+        Group data_group = null;
+        // transform AccumulationVector
+        group.addChild(data_group);
+*/
+              throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowTupleTypeJ2D.postProcess");}
+      else {
+        // nothing to do
+      }
+    }
+    else {
+/*
+      for (int i=0; i<num_components; i++) {
+        component_type.postProcess(group);
+      }
+*/
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java2d/ShadowTypeJ2D.java b/visad/java2d/ShadowTypeJ2D.java
new file mode 100644
index 0000000..427f065
--- /dev/null
+++ b/visad/java2d/ShadowTypeJ2D.java
@@ -0,0 +1,390 @@
+//
+// ShadowTypeJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+
+import java.util.Vector;
+import java.rmi.*;
+
+/**
+   The ShadowTypeJ2D hierarchy shadows the MathType hierarchy,
+   within a DataDisplayLink, under Java2D.<P>
+*/
+public abstract class ShadowTypeJ2D extends ShadowType {
+
+  /** basic information about this ShadowTypeJ2D */
+  MathType Type; // MathType being shadowed
+  transient DataDisplayLink Link;
+  transient DisplayImplJ2D display;
+  transient private Data data; // from Link.getData()
+  private ShadowTypeJ2D Parent;
+
+  // String and TextControl to pass on to children
+  String inheritedText = null;
+  TextControl inheritedTextControl = null;
+
+  ShadowType adaptedShadowType;
+
+  public ShadowTypeJ2D(MathType type, DataDisplayLink link,
+                       ShadowType parent)
+         throws VisADException, RemoteException {
+    super(type, link, getAdaptedParent(parent));
+    Type = type;
+    Link = link;
+    display = (DisplayImplJ2D) link.getDisplay();
+    Parent = (ShadowTypeJ2D) parent;
+    data = link.getData();
+  }
+
+  public static ShadowType getAdaptedParent(ShadowType parent) {
+    if (parent == null) return null;
+    else return parent.getAdaptedShadowType();
+  }
+
+  public ShadowType getAdaptedShadowType() {
+    return adaptedShadowType;
+  }
+
+  public ShadowRealType[] getComponents(ShadowType type, boolean doRef)
+          throws VisADException {
+    return adaptedShadowType.getComponents(type, doRef);
+  }
+
+  public String getParentText() {
+    if (Parent != null && Parent.inheritedText != null &&
+        Parent.inheritedTextControl != null) {
+      return Parent.inheritedText;
+    }
+    else {
+      return null;
+    }
+  }
+
+  public TextControl getParentTextControl() {
+    if (Parent != null && Parent.inheritedText != null &&
+        Parent.inheritedTextControl != null) {
+      return Parent.inheritedTextControl;
+    }
+    else {
+      return null;
+    }
+  }
+
+  public void setText(String text, TextControl control) {
+    inheritedText = text;
+    inheritedTextControl = control;
+  }
+
+  public Data getData() {
+    return data;
+  }
+
+  public DisplayImpl getDisplay() {
+    return display;
+  }
+
+  public MathType getType() {
+    return Type;
+  }
+
+  public int getLevelOfDifficulty() {
+    return adaptedShadowType.getLevelOfDifficulty();
+  }
+
+  public boolean getMultipleDisplayScalar() {
+    return adaptedShadowType.getMultipleDisplayScalar();
+  }
+
+  public boolean getMappedDisplayScalar() {
+    return adaptedShadowType.getMappedDisplayScalar();
+  }
+
+  public int[] getDisplayIndices() {
+    return adaptedShadowType.getDisplayIndices();
+  }
+
+  public int[] getValueIndices() {
+    return adaptedShadowType.getValueIndices();
+  }
+
+  /** checkIndices: check for rendering difficulty, etc */
+  public int checkIndices(int[] indices, int[] display_indices,
+             int[] value_indices, boolean[] isTransform, int levelOfDifficulty)
+      throws VisADException, RemoteException {
+    return adaptedShadowType.checkIndices(indices, display_indices, value_indices,
+                                          isTransform, levelOfDifficulty);
+  }
+
+  /** clear AccumulationVector */
+  void preProcess() throws VisADException {
+  }
+
+  /** transform data into a VisADSceneGraphObject;
+      add generated scene graph components as children of group;
+      value_array are inherited valueArray values;
+      default_values are defaults for each display.DisplayRealTypeVector;
+      return true if need post-process;
+      this is default (for ShadowTextType) */
+  boolean doTransform(VisADGroup group, Data data, float[] value_array,
+                      float[] default_values, DataRenderer renderer)
+          throws VisADException, RemoteException {
+    return false;
+  }
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  void postProcess(VisADGroup group) throws VisADException {
+  }
+
+
+  /* helpers for doTransform */
+
+  /** map values into display_values according to ScalarMap-s in reals */
+  public  static void mapValues(float[][] display_values, double[][] values,
+                               ShadowRealType[] reals) throws VisADException {
+    ShadowType.mapValues(display_values, values, reals);
+  }
+
+  /** map values into display_values according to ScalarMap-s in reals */
+  public static void mapValues(float[][] display_values, float[][] values,
+                               ShadowRealType[] reals) throws VisADException {
+    mapValues(display_values, values, reals, true);
+  }
+
+  /** map values into display_values according to ScalarMap-s in reals */
+  public static void mapValues(float[][] display_values, float[][] values,
+                               ShadowRealType[] reals, boolean copy) 
+                               throws VisADException {
+    ShadowType.mapValues(display_values, values, reals, copy);
+  }
+
+  public static VisADGeometryArray makePointGeometry(float[][] spatial_values,
+                byte[][] color_values) throws VisADException {
+    return ShadowType.makePointGeometry(spatial_values, color_values);
+  }
+
+  /** construct an VisADAppearance object */
+  static VisADAppearance makeAppearance(GraphicsModeControl mode,
+                      float constant_alpha,
+                      float[] constant_color,
+                      VisADGeometryArray array) {
+    VisADAppearance appearance = new VisADAppearance();
+    appearance.pointSize = mode.getPointSize();
+    appearance.lineWidth = mode.getLineWidth();
+    appearance.lineStyle = mode.getLineStyle();
+
+    appearance.alpha = constant_alpha; // may be Float.NaN
+    if (constant_color != null && constant_color.length == 3) {
+      appearance.color_flag = true;
+      appearance.red = constant_color[0];
+      appearance.green = constant_color[1];
+      appearance.blue = constant_color[2];
+    }
+    appearance.array = array; // may be null
+    return appearance;
+  }
+
+  /** collect and transform Shape DisplayRealType values from display_values;
+      offset by spatial_values, selected by range_select */
+  public VisADGeometryArray[] assembleShape(float[][] display_values,
+                int valueArrayLength, int[] valueToMap, Vector MapVector,
+                int[] valueToScalar, DisplayImpl display,
+                float[] default_values, int[] inherited_values,
+                float[][] spatial_values, byte[][] color_values,
+                boolean[][] range_select, int index, ShadowType shadow_api)
+         throws VisADException, RemoteException {
+    return adaptedShadowType.assembleShape(display_values, valueArrayLength,
+           valueToMap, MapVector, valueToScalar, display, default_values,
+           inherited_values, spatial_values, color_values, range_select, index,
+           shadow_api);
+  }
+
+  /** collect and transform spatial DisplayRealType values from display_values;
+      add spatial offset DisplayRealType values;
+      adjust flow1_values and flow2_values for any coordinate transform;
+      if needed, return a spatial Set from spatial_values, with the same topology
+      as domain_set (or an appropriate Irregular topology);
+      domain_set = null and allSpatial = false if not called from
+      ShadowFunctionType */
+  public Set assembleSpatial(float[][] spatial_values,
+                float[][] display_values, int valueArrayLength,
+                int[] valueToScalar, DisplayImpl display,
+                float[] default_values, int[] inherited_values,
+                Set domain_set, boolean allSpatial, boolean set_for_shape,
+                int[] spatialDimensions, boolean[][] range_select,
+                float[][] flow1_values, float[][] flow2_values,
+                float[] flowScale, boolean[] swap, DataRenderer renderer,
+                ShadowType shadow_api)
+         throws VisADException, RemoteException {
+    return adaptedShadowType.assembleSpatial(spatial_values, display_values,
+           valueArrayLength, valueToScalar, display, default_values,
+           inherited_values, domain_set, allSpatial, set_for_shape,
+           spatialDimensions, range_select, flow1_values, flow2_values,
+           flowScale, swap, renderer, shadow_api);
+  }
+
+  /** assemble Flow components;
+      Flow components are 'single', so no compositing is required */
+  public void assembleFlow(float[][] flow1_values,
+                float[][] flow2_values, float[] flowScale,
+                float[][] display_values, int valueArrayLength,
+                int[] valueToScalar, DisplayImpl display,
+                float[] default_values, boolean[][] range_select,
+                DataRenderer renderer, ShadowType shadow_api)
+         throws VisADException, RemoteException {
+    adaptedShadowType.assembleFlow(flow1_values, flow2_values, flowScale,
+                      display_values, valueArrayLength, valueToScalar,
+                      display, default_values, range_select, renderer,
+                      shadow_api);
+  }
+
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    return adaptedShadowType.makeFlow(which, flow_values, flowScale,
+           spatial_values, color_values, range_select);
+  }
+
+  public VisADGeometryArray[] makeStreamline(int which, float[][] flow_values,
+                float flowScale, float[][] spatial_values, Set spatial_set,
+                int spatialManifoldDimension,
+                byte[][] color_values, boolean[][] range_select,
+                int valueArrayLength, int[] valueToMap, Vector MapVector)
+         throws VisADException {
+    return adaptedShadowType.makeStreamline(which, flow_values, flowScale,
+           spatial_values, spatial_set, spatialManifoldDimension,
+           color_values, range_select, 
+           valueArrayLength, valueToMap, MapVector);
+  }
+
+  public boolean makeContour(int valueArrayLength, int[] valueToScalar,
+                       float[][] display_values, int[] inherited_values,
+                       Vector MapVector, int[] valueToMap, int domain_length,
+                       boolean[][] range_select, int spatialManifoldDimension,
+                       Set spatial_set, byte[][] color_values, boolean indexed,
+                       Object group, GraphicsModeControl mode, boolean[] swap,
+                       float constant_alpha, float[] constant_color,
+                       ShadowType shadow_api, ShadowRealTupleType Domain, 
+                       ShadowRealType[] DomainReferenceComponents,
+                       Set domain_set, Unit[] domain_units, CoordinateSystem dataCoordinateSystem)
+         throws VisADException {
+    return adaptedShadowType.makeContour(valueArrayLength, valueToScalar,
+                       display_values, inherited_values, MapVector, valueToMap,
+                       domain_length, range_select, spatialManifoldDimension,
+                       spatial_set, color_values, indexed, group, mode,
+                       swap, constant_alpha, constant_color, shadow_api, Domain, 
+                       DomainReferenceComponents,
+                       domain_set, domain_units, dataCoordinateSystem);
+  }
+
+  public void addLabelsToGroup(Object group, VisADGeometryArray[] arrays,
+                               GraphicsModeControl mode, ContourControl control,
+                               ProjectionControl p_cntrl, int[] cnt_a,
+                               float constant_alpha, float[] constant_color)
+         throws VisADException 
+  {
+    int n_labels = arrays.length;
+
+    for ( int ii = 0; ii < n_labels; ii++ )
+    {
+      addToGroup(group, ((ContourLabelGeometry)arrays[ii]).label, mode,
+                 constant_alpha, constant_color);
+    }
+  }
+
+  public VisADGeometryArray makeText(String[] text_values,
+                TextControl text_control, float[][] spatial_values,
+                byte[][] color_values, boolean[][] range_select)
+         throws VisADException {
+    return adaptedShadowType.makeText(text_values, text_control, spatial_values,
+                                      color_values, range_select);
+  }
+
+  /** composite and transform color and Alpha DisplayRealType values
+      from display_values, and return as (Red, Green, Blue, Alpha) */
+  public byte[][] assembleColor(float[][] display_values,
+                int valueArrayLength, int[] valueToScalar,
+                DisplayImpl display, float[] default_values,
+                boolean[][] range_select, boolean[] single_missing,
+                ShadowType shadow_api)
+         throws VisADException, RemoteException {
+    return adaptedShadowType.assembleColor(display_values, valueArrayLength,
+           valueToScalar, display, default_values, range_select,
+           single_missing, shadow_api);
+  }
+
+  /** return a composite of SelectRange DisplayRealType values from
+      display_values, as 0.0 for select and Double.Nan for no select
+      (these values can be added to other DisplayRealType values) */
+  public boolean[][] assembleSelect(float[][] display_values,
+                             int domain_length, int valueArrayLength,
+                             int[] valueToScalar, DisplayImpl display,
+                             ShadowType shadow_api)
+         throws VisADException {
+    return adaptedShadowType.assembleSelect(display_values, domain_length,
+           valueArrayLength, valueToScalar, display, shadow_api);
+  }
+
+  public boolean terminalTupleOrScalar(VisADGroup group, float[][] display_values,
+                                String text_value, TextControl text_control,
+                                int valueArrayLength, int[] valueToScalar,
+                                float[] default_values, int[] inherited_values,
+                                DataRenderer renderer)
+          throws VisADException, RemoteException {
+
+    return adaptedShadowType.terminalTupleOrScalar(group, display_values,
+                       text_value, text_control, valueArrayLength, valueToScalar,
+                       default_values, inherited_values, renderer, this);
+  }
+
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+                            GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color)
+         throws VisADException {
+    if (array != null) {
+      VisADAppearance appearance =
+        makeAppearance(mode, constant_alpha, constant_color, array);
+      ((VisADGroup) group).addChild(appearance);
+      return true;
+    }
+    else {
+      return false;
+    }
+  }
+
+  public boolean allowCurvedTexture() {
+    return false;
+  }
+
+  public String toString() {
+    return (adaptedShadowType == null ? null : adaptedShadowType.toString());
+  }
+
+}
+
diff --git a/visad/java2d/ValueControlJ2D.java b/visad/java2d/ValueControlJ2D.java
new file mode 100644
index 0000000..c2be55c
--- /dev/null
+++ b/visad/java2d/ValueControlJ2D.java
@@ -0,0 +1,133 @@
+//
+// ValueControlJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+import visad.browser.Convert;
+
+import java.rmi.*;
+
+/**
+   ValueControlJ2D is the VisAD class for controlling SelectValue
+   display scalars under Java2D.<P>
+*/
+public class ValueControlJ2D extends AVControlJ2D
+       implements ValueControl {
+
+  private double Value;
+
+  private VisADCanvasJ2D canvas;
+
+  public ValueControlJ2D(DisplayImplJ2D d) {
+    super(d);
+    Value = 0.0;
+    if (d != null) {
+      canvas = ((DisplayRendererJ2D) d.getDisplayRenderer()).getCanvas();
+    }
+  }
+
+  public void setValue(double value)
+         throws VisADException, RemoteException {
+    Value = value;
+    selectSwitches(Value, null);
+    canvas.scratchImages();
+    changeControl(true);
+  }
+
+  public void init() throws VisADException {
+    selectSwitches(Value, null);
+  }
+
+  public double getValue() {
+    return Value;
+  }
+
+  /** get a String that can be used to reconstruct this ValueControl later */
+  public String getSaveString() {
+    return "" + Value;
+  }
+
+  /** reconstruct this ValueControl using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    setValue(Convert.getDouble(save.trim()));
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+        throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof ValueControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    ValueControl vc = (ValueControl )rmt;
+
+    boolean changed = false;
+
+    double v = getValue();
+    double rv = vc.getValue();
+    if (Math.abs(v - rv) > 0.001) {
+      try {
+        setValue(rv);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not set value: " + re.getMessage());
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    ValueControlJ2D vc = (ValueControlJ2D )o;
+
+    double v = getValue();
+    double rv = vc.getValue();
+    if (Math.abs(v - rv) > 0.001) {
+      return false;
+    }
+
+    return true;
+  }
+
+  public String toString() {
+    return "ValueControlJ2D: Value = " + Value;
+  }
+
+}
+
diff --git a/visad/java2d/VisADCanvasJ2D.java b/visad/java2d/VisADCanvasJ2D.java
new file mode 100644
index 0000000..0e048a9
--- /dev/null
+++ b/visad/java2d/VisADCanvasJ2D.java
@@ -0,0 +1,1342 @@
+//
+// VisADCanvasJ2D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java2d;
+
+import visad.*;
+import visad.util.Delay;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.image.BufferedImage;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import javax.swing.*;
+import java.rmi.RemoteException;
+
+
+import java.util.*;
+
+/**
+ * <CODE>VisADCanvasJ2D</CODE> is the VisAD "Canvas" for Java2D.  But
+ * not really a <CODE>Canvas</CODE>, since <CODE>Canvas</CODE> is
+ * heavyweight.<P>
+*/
+
+public class VisADCanvasJ2D extends JPanel
+       implements Runnable {
+
+  /** line patterns for use with BasicStroke */
+  private float[][] LINE_PATTERN = {
+    null, {8}, {1, 7}, {7, 4, 1, 4}
+  };
+
+  private DisplayRendererJ2D displayRenderer;
+  private DisplayImplJ2D display;
+  private Component component;
+  Dimension prefSize = new Dimension(0, 0);
+
+  // parent nodes of all data depictions
+  private VisADGroup direct = null;
+  private VisADGroup non_direct = null;
+  // Shape to clip against, if any
+  private Rectangle2D.Float clip_rectangle = null;
+
+  private transient Thread renderThread;
+
+  private BufferedImage[] images; // animation sequence
+  private boolean[] valid_images;
+  private int width, height; // size of images
+  private int length; // length of images & valid_images
+  private AffineTransform tgeometry; // transform for current display
+  private Image aux_image;
+
+  boolean captureFlag = false;
+  BufferedImage captureImage = null;
+
+  MouseHelper helper;
+
+  // wake up flag for renderTrigger
+  boolean wakeup = false;
+
+  // flag set if images not created
+  boolean timeout = false;
+
+  public VisADCanvasJ2D(DisplayRendererJ2D renderer, Component c) {
+    displayRenderer = renderer;
+    display = (DisplayImplJ2D) renderer.getDisplay();
+    component = c;
+
+    width = getSize().width;
+    height = getSize().height;
+
+    if (width <= 0) width = 1;
+    if (height <= 0) height = 1;
+
+    length = 1;
+    images = new BufferedImage[] {(BufferedImage) createImage(width, height)};
+    aux_image = createImage(width, height);
+    valid_images = new boolean[] {false};
+
+    int w = width;
+    int h = height;
+    AffineTransform trans = displayRenderer.getTrans();
+    tgeometry = new AffineTransform();
+    tgeometry.setToTranslation(0.5 * w, 0.5 * h);
+    AffineTransform s1 = new AffineTransform();
+    int wh = (w < h) ? w : h;
+    s1.setToScale(0.33 * wh, 0.33 * wh);
+    tgeometry.concatenate(s1);
+    tgeometry.concatenate(trans);
+
+    ComponentListener cl = new ComponentAdapter() {
+      public void componentResized(ComponentEvent e) {
+        createImages(-1);
+      }
+    };
+    addComponentListener(cl);
+
+    setBackground(Color.black);
+    setForeground(Color.white);
+
+    new Delay();
+
+    if (images[0] == null) {
+      images =
+        new BufferedImage[] {(BufferedImage) createImage(width, height)};
+      aux_image = createImage(width, height);
+      valid_images = new boolean[] {false};
+    }
+
+    renderThread = new Thread(this);
+    renderThread.start();
+  }
+
+  /**
+   * Constructor for offscreen rendering.
+   * @param renderer
+   * @param w
+   * @param h
+   */
+  public VisADCanvasJ2D(DisplayRendererJ2D renderer, int w, int h) {
+    displayRenderer = renderer;
+    display = (DisplayImplJ2D) renderer.getDisplay();
+    component = null;
+
+    width = w;
+    height = h;
+    length = 1;
+    images = new BufferedImage[]
+      {new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB)};
+    aux_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+    valid_images = new boolean[] {false};
+
+    AffineTransform trans = displayRenderer.getTrans();
+    tgeometry = new AffineTransform();
+    tgeometry.setToTranslation(0.5 * w, 0.5 * h);
+    AffineTransform s1 = new AffineTransform();
+    int wh = (w < h) ? w : h;
+    s1.setToScale(0.33 * wh, 0.33 * wh);
+    tgeometry.concatenate(s1);
+    tgeometry.concatenate(trans);
+
+    setBackground(Color.black);
+    setForeground(Color.white);
+
+    renderThread = new Thread(this);
+    renderThread.start();
+  }
+
+  /**
+   * Return the background color.
+   * @return A 3 element array of <CODE>float</CODE> values
+   *         in the range <CODE>[0.0f - 1.0f]</CODE>
+   *         in the order <I>(Red, Green, Blue)</I>.
+   */
+  public float[] getBackgroundColor() {
+    Color color = getBackground();
+    float[] list = new float[3];
+    list[0] = (float )color.getRed() / 255.0f;
+    list[1] = (float )color.getGreen() / 255.0f;
+    list[2] = (float )color.getBlue() / 255.0f;
+    return list;
+  }
+
+  /**
+   * Set the background color.  All values should be in the range
+   * <CODE>[0.0f - 1.0f]</CODE>.
+   * @param r Red value.
+   * @param g Green value.
+   * @param b Blue value.
+   */
+  public void setBackgroundColor(float r, float g, float b) {
+    setBackground(new Color(r, g, b));
+  }
+
+  void setDirect(VisADGroup d, VisADGroup nd) {
+    direct = d;
+    non_direct = nd;
+  }
+
+  void setClip(float xlow, float xhi, float ylow, float yhi) {
+    if (xhi > xlow && yhi > ylow) {
+      clip_rectangle =
+        new Rectangle2D.Float(xlow, ylow, xhi-xlow, yhi-ylow);
+    }
+  }
+
+  void unsetClip() {
+    clip_rectangle = null;
+  }
+
+  /**
+   * Add a <CODE>MouseBehavior</CODE> for mouse control of translation 
+   * and zoom.  This adds <CODE>MouseListener</CODE>s to the VisADCanvasJ2D to 
+   * handle the behaviors for the mouse events.  Do not use this in conjunction 
+   * with other <CODE>MouseListener</CODE>s that handle events for the default
+   * VisAD mouse controls.
+   * @param  mouse  mouse behavior to add
+   */
+  public void addMouseBehavior(MouseBehaviorJ2D mouse) {
+    helper = mouse.getMouseHelper();
+
+    MouseListener ml = new MouseAdapter() {
+      public void mouseEntered(MouseEvent e) {
+        helper.processEvent(e);
+      }
+      public void mouseExited(MouseEvent e) {
+        helper.processEvent(e);
+      }
+      public void mousePressed(MouseEvent e) {
+        helper.processEvent(e);
+      }
+      public void mouseReleased(MouseEvent e) {
+        helper.processEvent(e);
+      }
+      public void mouseClicked(MouseEvent e) {
+        requestFocus();
+      }
+    };
+    addMouseListener(ml);
+
+    MouseMotionListener mml = new MouseMotionAdapter() {
+      public void mouseDragged(MouseEvent e) {
+        helper.processEvent(e);
+      }
+      public void mouseMoved(MouseEvent e) {
+        helper.processEvent(e);
+      }
+    };
+    addMouseMotionListener(mml);
+  }
+
+  /**
+   * Add a <CODE>KeyboardBehavior</CODE> for keyboard control of translation 
+   * and zoom.  This adds a <CODE>KeyListener</CODE> to the VisADCanvasJ2D to 
+   * handle the behaviors for the arrow keys.  Do not use this in conjunction 
+   * with other <CODE>KeyListener</CODE>s that handle events for the arrow keys.
+   * @param  behavior  keyboard behavior to add
+   */
+  public void addKeyboardBehavior(final KeyboardBehaviorJ2D behavior) {
+    KeyListener kl = new KeyAdapter() {
+        public void keyPressed(KeyEvent e) {
+          behavior.processKeyEvent(e);
+        }
+        public void keyReleased(KeyEvent e) {
+          behavior.processKeyEvent(e);
+        }
+      };
+    addKeyListener(kl);
+  }
+
+  public void createImages(int len) {
+    synchronized (images) {
+      length = (len < 0) ? images.length : len;
+      if (component != null) {
+        width = getSize().width;
+        height = getSize().height;
+      }
+      if (width <= 0) width = 1;
+      if (height <= 0) height = 1;
+      BufferedImage[] new_images = new BufferedImage[length];
+      boolean[] new_valid_images = new boolean[length];
+      for (int i=0; i<length; i++) {
+        if (component != null) {
+          new_images[i] = (BufferedImage) createImage(width, height);
+        }
+        else {
+          new_images[i] =
+            new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        }
+        new_valid_images[i] = false;
+      }
+      if (aux_image != null) aux_image.flush();
+      if (component != null) {
+        aux_image = createImage(width, height);
+      }
+      else {
+        aux_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+      }
+
+      valid_images = new_valid_images;
+      if (images != null) {
+        for (int i=0; i<images.length; i++) {
+          if (images[i] != null) images[i].flush();
+        }
+      }
+      images = new_images;
+/*
+System.out.println("VisADCanvasJ2D.createImages: len = " + len +
+                   " length = " + length + " width, height = " +
+                   width + " " + height);
+*/
+    }
+    renderTrigger();
+  }
+
+  public void scratchImages() {
+    synchronized (images) {
+      for (int i=0; i<length; i++) valid_images[i] = false;
+    }
+    renderTrigger();
+  }
+
+  /** trigger render to screen */
+  public void renderTrigger() {
+    synchronized (this) {
+      wakeup = true;
+      notify();
+    }
+  }
+
+  public void stop() {
+    renderThread = null;
+  }
+
+  public void run() {
+    Thread me = Thread.currentThread();
+    while (renderThread == me) {
+      timeout = false;
+      if (component != null) {
+        Graphics g = getGraphics();
+        if (g != null) {
+          // paintComponent(g);
+          repaint();
+          g.dispose();
+        }
+      }
+      else {
+        paintComponent(null);
+      }
+
+      try {
+        synchronized (this) {
+          if (!wakeup) {
+            if (timeout) {
+              wait(1000);
+            }
+            else {
+              wait();
+            }
+          }
+        }
+      }
+      catch(InterruptedException e) {
+        // note notify generates a normal return from wait rather
+        // than an Exception - control doesn't normally come here
+      }
+      synchronized (this) {
+        wakeup = false;
+      }
+    }
+  }
+
+  public void paintComponent(Graphics g) {
+    AffineTransform tsave = null;
+    BufferedImage image = null;
+    boolean valid = false;
+    int w = 0, h = 0;
+    int current_image = 0;
+    AnimationControlJ2D animate_control = null;
+    try {
+      animate_control = (AnimationControlJ2D)
+        display.getControl(AnimationControlJ2D.class);
+      if (animate_control != null) {
+        animate_control.setNoTick(true);
+        current_image = animate_control.getCurrent();
+      }
+    }
+    catch (Exception e) {
+      if (animate_control != null) animate_control.setNoTick(false);
+    }
+/*
+System.out.println("VisADCanvasJ2D.paint: current = " + current_image +
+                   " (animate_control == null) = " + (animate_control == null));
+*/
+    synchronized (images) {
+      if (0 <= current_image && current_image < length) {
+        image = images[current_image];
+        if (image == null) {
+          createImages(-1);
+          image = images[current_image];
+        }
+        valid = valid_images[current_image];
+        w = width;
+        h = height;
+        tsave = (tgeometry == null) ? null : new AffineTransform(tgeometry);
+        if (image != null && !valid) {
+          valid_images[current_image] = true;
+        }
+      }
+    }
+
+/*
+System.out.println("VisADCanvasJ2D.paint: current_image = " + current_image +
+                   " length = " + length + " w, h = " + w + " " + h +
+                   " valid = " + valid + " image != null " + (image != null));
+*/
+    timeout = false;
+    if (image != null) {
+      if (!valid) {
+        VisADGroup root = displayRenderer.getRoot();
+        AffineTransform trans = displayRenderer.getTrans();
+        Graphics ggg = image.createGraphics(); // ordinary Graphics for fast lines
+        Graphics2D g2 = image.createGraphics(); // Graphics2D for the fancy stuff
+// System.out.println("(g2 == null) = " + (g2 == null));
+        g2.setBackground(getBackground());
+        g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+                            RenderingHints.VALUE_RENDER_SPEED);
+        g2.clearRect(0, 0, width, height);
+
+        // render into image;
+        synchronized (images) {
+          tgeometry = new AffineTransform();
+          tgeometry.setToTranslation(0.5 * w, 0.5 * h);
+          AffineTransform s1 = new AffineTransform();
+          int wh = (w < h) ? w : h;
+          s1.setToScale(0.33 * wh, 0.33 * wh);
+          tgeometry.concatenate(s1);
+          tgeometry.concatenate(trans);
+          tsave = new AffineTransform(tgeometry);
+          g2.setTransform(tgeometry);
+        }
+        try {
+          if (animate_control != null) animate_control.init();
+          render(g2, ggg, root, 0, null);
+          render(g2, ggg, root, 1, null);
+          // draw Animation string in upper right corner of screen
+          String[] animation_string = displayRenderer.getAnimationString();
+          if (animation_string[0] != null) {
+/*
+System.out.println("VisADCanvasJ2D.paint: " + animation_string[0] +
+                   " " + animation_string[1]);
+*/
+            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                RenderingHints.VALUE_ANTIALIAS_ON);
+            g2.setFont(new Font("Times New Roman", Font.PLAIN, 12));
+            g2.setTransform(new AffineTransform());
+            // hack for mystery NullPointerException
+            try {
+              int nchars = animation_string[0].length();
+              if (nchars < 12) nchars = 12;
+              float x = w - 7 * nchars;
+              float y = h - 12;
+              g2.drawString(animation_string[0], x, y);
+              g2.drawString(animation_string[1], x, y+10);
+            }
+            catch (NullPointerException e) {
+            }
+          }
+        }
+        catch (VisADException e) {
+        }
+        g2.dispose();
+        ggg.dispose();
+      } // end if (!valid)
+
+      if (tsave == null || !displayRenderer.anyCursorStringVector()) {
+        if (g != null) {
+          g.drawImage(image, 0, 0, this);
+        }
+        if (captureFlag || display.hasSlaves()) {
+// System.out.println("image capture " + width + " " + height);
+          captureFlag = false;
+          if (component != null) {
+            captureImage = (BufferedImage) createImage(width, height);
+          }
+          else {
+            captureImage =
+              new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+              // new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+          }
+          // CTR 2 June 2000 - captureImage can be null
+          if (captureImage != null) {
+            Graphics gc = captureImage.getGraphics();
+            gc.drawImage(image, 0, 0, this);
+            gc.dispose();
+            captureImage.flush();
+            displayRenderer.notifyCapture();
+// System.out.println("image capture end");
+
+            // CTR 21 Sep 99 - send BufferedImage to attached slaved displays
+            if (display.hasSlaves()) display.updateSlaves(captureImage);
+          }
+        }
+      }
+      else {
+        Image aux_copy = null;
+        synchronized (images) {
+          if (aux_image == null) {
+            // if we got here before the image buffer was initialized,
+            //  skip this round of drawing
+            return;
+          }
+          aux_copy = aux_image;
+        }
+        Graphics ga = aux_copy.getGraphics();
+        ga.drawImage(image, 0, 0, this);
+
+        displayRenderer.drawCursorStringVector(ga, tsave, w, h);
+        ga.dispose();
+        if (g != null) {
+          g.drawImage(aux_copy, 0, 0, this);
+        }
+        if (captureFlag || display.hasSlaves()) {
+// System.out.println("aux_copy capture " + width + " " + height);
+          captureFlag = false;
+          if (component != null) {
+            captureImage = (BufferedImage) createImage(width, height);
+          }
+          else {
+            captureImage =
+              new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+              // new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+          }
+          Graphics gc = captureImage.getGraphics();
+          gc.drawImage(aux_copy, 0, 0, this);
+          gc.dispose();
+          captureImage.flush();
+          displayRenderer.notifyCapture();
+// System.out.println("aux_copy capture end");
+          // WLH 14 Oct 99 - send BufferedImage to any attached slaved displays
+          if (display.hasSlaves()) display.updateSlaves(captureImage);
+        }
+      }
+      try {
+        display.notifyListeners(DisplayEvent.FRAME_DONE, 0, 0);
+      }
+      catch (VisADException e) {}
+      catch (RemoteException e) {}
+    } // end if (image != null)
+    else { // image == null
+      timeout = true;
+    }
+    if (animate_control != null) animate_control.setNoTick(false);
+    return;
+  }
+
+  private void render(Graphics2D g2, Graphics ggg,
+                      VisADSceneGraphObject scene, int pass,
+                      Rectangle2D.Float clip)
+          throws VisADException {
+    if (scene == null) return;
+    if (scene instanceof VisADSwitch) {
+      VisADSceneGraphObject child =
+        ((VisADSwitch) scene).getSelectedChild();
+      if (child != null) render(g2, ggg, child, pass, clip);
+    }
+    else if (scene instanceof VisADGroup) {
+      if (clip_rectangle != null &&
+          (scene.equals(direct) || scene.equals(non_direct))) {
+        clip = clip_rectangle;
+      }
+      Vector children = ((VisADGroup) scene).getChildren();
+      for (int i=children.size()-1; i>=0; i--) {
+        VisADSceneGraphObject child =
+          (VisADSceneGraphObject) children.elementAt(i);
+        if (child != null) render(g2, ggg, child, pass, clip);
+      }
+    }
+    else { // scene instanceof VisADAppearance
+      g2.setClip(clip);
+      VisADAppearance appearance = (VisADAppearance) scene;
+      VisADGeometryArray array = appearance.array;
+      if (array == null) return;
+      BufferedImage image = (BufferedImage) appearance.image;
+      AffineTransform tg = g2.getTransform();
+      if (image != null) {
+        if (pass != 0) return; // non-lines on first pass
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                            RenderingHints.VALUE_ANTIALIAS_OFF);
+        if (!(array instanceof VisADQuadArray)) {
+          throw new VisADError("VisADCanvasJ2D.render: array must be quad");
+        }
+        float x00 = array.coordinates[0];
+        float y00 = array.coordinates[1];
+        float xw0 = array.coordinates[3];
+        float yw0 = array.coordinates[4];
+        float xwh = array.coordinates[6];
+        float ywh = array.coordinates[7];
+        float x0h = array.coordinates[9];
+        float y0h = array.coordinates[10];
+        int width = image.getWidth();
+        int height = image.getHeight();
+
+/*
+now:
+  x00 = m00 * 0 + m01 * 0 + m02
+  y00 = m10 * 0 + m11 * 0 + m12
+
+  xw0 = m00 * width + m01 * 0 + m02
+  yw0 = m10 * width + m11 * 0 + m12
+
+  xwh = m00 * width + m01 * height + m02
+  ywh = m10 * width + m11 * height + m12
+
+  x0h = m00 * 0 + m01 * height + m02
+  y0h = m10 * 0 + m11 * height + m12
+so:
+*/
+        float m02 = x00;
+        float m12 = y00;
+        float m00 = (xw0 - x00) / width;
+        float m10 = (yw0 - y00) / width;
+        float m01 = (x0h - x00) / height;
+        float m11 = (y0h - y00) / height;
+        float xerr = xwh - (m00 * width + m01 * height + m02);
+        float yerr = ywh - (m10 * width + m11 * height + m12);
+
+        AffineTransform timage = new AffineTransform(m00, m10, m01, m11, m02, m12);
+        g2.transform(timage); // concatenate timage onto tg
+        try {
+          g2.drawImage(image, 0, 0, this);
+        } catch (java.awt.image.ImagingOpException e) { }
+        g2.setTransform(tg); // restore tg
+      }
+      else { // image == null
+        if (array instanceof VisADPointArray ||
+            array instanceof VisADLineArray ||
+            array instanceof VisADLineStripArray) {
+          if (pass != 1) return; // lines on second pass
+        }
+        else {
+          if (pass != 0) return; // non-lines on first pass
+        }
+        int count = array.vertexCount;
+        if (count == 0) return;
+        float[] coordinates = array.coordinates;
+        byte[] colors = array.colors;
+        if (colors == null) {
+          if (appearance.color_flag) {
+            float red = (float) Math.max(Math.min(appearance.red, 1.0f), 0.0f);
+            float green = (float) Math.max(Math.min(appearance.green, 1.0f), 0.0f);
+            float blue = (float) Math.max(Math.min(appearance.blue, 1.0f), 0.0f);
+            g2.setColor(new Color(red, green, blue));
+          }
+          else {
+            g2.setColor(new Color(1.0f, 1.0f, 1.0f));
+          }
+        }
+        else {
+        }
+        if (array instanceof VisADPointArray ||
+            array instanceof VisADLineArray ||
+            array instanceof VisADLineStripArray) {
+          float fsize = (array instanceof VisADPointArray) ?
+                           appearance.pointSize :
+                           appearance.lineWidth;
+          double dsize = fsize;
+          if (dsize < 1.05) dsize = 1.05; // hack for Java2D problem
+          double[] pts = {0.0, 0.0, 0.0, dsize, dsize, 0.0};
+          double[] newpts = new double[6];
+          double xx = 0.0, yy = 0.0;
+          try {
+            tg.inverseTransform(pts, 0, newpts, 0, 3);
+            xx = (newpts[2] - newpts[0]) * (newpts[2] - newpts[0]) +
+                 (newpts[3] - newpts[1]) * (newpts[3] - newpts[1]);
+            yy = (newpts[4] - newpts[0]) * (newpts[4] - newpts[0]) +
+                 (newpts[5] - newpts[1]) * (newpts[5] - newpts[1]);
+          }
+          catch (NoninvertibleTransformException e) {
+            xx = 1.05;
+            yy = 1.05;
+          }
+          float size = (float) (0.5 * (Math.sqrt(xx) + Math.sqrt(yy)));
+          float hsize = 0.5f * size;
+          float[] style = LINE_PATTERN[appearance.lineStyle];
+          float[] pattern = null;
+          if (style != null) {
+            pattern = new float[style.length];
+            float scale = size / appearance.lineWidth;
+            for (int i=0; i<style.length; i++) pattern[i] = scale * style[i];
+          }
+          g2.setStroke(new BasicStroke(size, BasicStroke.CAP_SQUARE,
+            BasicStroke.JOIN_MITER, 10, pattern, 0));
+
+/*
+System.out.println("dsize = " + dsize + " size = " + size + " xx, yy = " +
+                   xx + " " + yy +
+                   (array instanceof VisADPointArray ? " point" : " line"));
+*/
+          if (array instanceof VisADPointArray) {
+            if (Math.abs(fsize - 1.0f) < 0.1f) {
+              drawAppearance(ggg, appearance, tg, clip);
+            }
+            else {
+              g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                  RenderingHints.VALUE_ANTIALIAS_OFF);
+              if (colors == null) {
+                for (int i=0; i<3*count; i += 3) {
+                  if (coordinates[i] == coordinates[i] &&
+                      coordinates[i+1] == coordinates[i+1]) {
+                    g2.fill(new Rectangle2D.Float(coordinates[i]-hsize,
+                                                  coordinates[i+1]-hsize,
+                                                  size, size));
+                  }
+                }
+              }
+              else { // colors != null
+                int j = 0;
+                int jinc = (colors.length == coordinates.length) ? 3 : 4;
+                for (int i=0; i<3*count; i += 3) {
+                  if (coordinates[i] == coordinates[i] &&
+                      coordinates[i+1] == coordinates[i+1]) {
+                    g2.setColor(new Color(
+                      ((colors[j] < 0) ? (((int) colors[j]) + 256) :
+                                         ((int) colors[j]) ),
+                      ((colors[j+1] < 0) ? (((int) colors[j+1]) + 256) :
+                                         ((int) colors[j+1]) ),
+                      ((colors[j+2] < 0) ? (((int) colors[j+2]) + 256) :
+                                         ((int) colors[j+2]) ) ));
+                    g2.fill(new Rectangle2D.Float(coordinates[i]-hsize,
+                                                  coordinates[i+1]-hsize,
+                                                  size, size));
+                  }
+                  j += jinc;
+                }
+              }
+            }
+          }
+          else if (array instanceof VisADLineArray) {
+            if (Math.abs(fsize - 1.0f) < 0.1f) {
+              drawAppearance(ggg, appearance, tg, clip);
+            }
+            else {
+              g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                  RenderingHints.VALUE_ANTIALIAS_ON);
+              if (colors == null) {
+                for (int i=0; i<3*count; i += 6) {
+                  g2.draw(new Line2D.Float(coordinates[i], coordinates[i+1],
+                                           coordinates[i+3], coordinates[i+4]));
+                }
+              }
+              else { // colors != null
+                int j = 0;
+                int jinc = (colors.length == coordinates.length) ? 3 : 4;
+                for (int i=0; i<3*count; i += 6) {
+                  g2.setColor(new Color(
+                    (((colors[j] < 0) ? (((int) colors[j]) + 256) :
+                                        ((int) colors[j]) ) +
+                     ((colors[j+jinc] < 0) ? (((int) colors[j+jinc]) + 256) :
+                                        ((int) colors[j+jinc]) ) ) / 2,
+                    (((colors[j+1] < 0) ? (((int) colors[j+1]) + 256) :
+                                        ((int) colors[j+1]) ) +
+                     ((colors[j+jinc+1] < 0) ? (((int) colors[j+jinc+1]) + 256) :
+                                        ((int) colors[j+jinc+1]) ) ) / 2,
+                    (((colors[j+2] < 0) ? (((int) colors[j+2]) + 256) :
+                                        ((int) colors[j+2]) ) +
+                     ((colors[j+jinc+2] < 0) ? (((int) colors[j+jinc+2]) + 256) :
+                                        ((int) colors[j+jinc+2]) ) ) / 2 ));
+                  j += 2 * jinc;
+                  g2.draw(new Line2D.Float(coordinates[i], coordinates[i+1],
+                                           coordinates[i+3], coordinates[i+4]));
+                }
+              }
+            }
+          }
+          else { // (array instanceof VisADLineStripArray)
+            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                RenderingHints.VALUE_ANTIALIAS_ON);
+
+            int[] stripVertexCounts =
+               ((VisADLineStripArray) array).stripVertexCounts;
+            int base = 0;
+            int basec = 0;
+            int jinc = 0;
+            if (colors != null) {
+              jinc = (colors.length == coordinates.length) ? 3 : 4;
+            }
+            for (int strip=0; strip<stripVertexCounts.length; strip++) {
+              count = stripVertexCounts[strip];
+
+              float lastx = coordinates[base];
+              float lasty = coordinates[base+1];
+              int lastr = 0, lastg = 0, lastb = 0;
+              int thisr, thisg, thisb;
+              if (colors != null) {
+                lastr = (colors[basec] < 0) ? (((int) colors[basec]) + 256) :
+                                             ((int) colors[basec]);
+                lastg = (colors[basec+1] < 0) ? (((int) colors[basec+1]) + 256) :
+                                               ((int) colors[basec+1]);
+                lastb = (colors[basec+2] < 0) ? (((int) colors[basec+2]) + 256) :
+                                               ((int) colors[basec+2]);
+              }
+              if (colors == null) {
+                for (int i=3; i<3*count; i += 3) {
+                  g2.draw(new Line2D.Float(lastx, lasty,
+                                           coordinates[base+i],
+                                           coordinates[base+i+1]));
+                  lastx = coordinates[base+i];
+                  lasty = coordinates[base+i+1];
+                }
+              }
+              else {
+                int j = jinc;
+                for (int i=3; i<3*count; i += 3) {
+                  thisr = (colors[basec+j] < 0) ? (((int) colors[basec+j]) + 256) :
+                                               ((int) colors[basec+j]);
+                  thisg = (colors[basec+j+1] < 0) ? (((int) colors[basec+j+1]) + 256) :
+                                                 ((int) colors[basec+j+1]);
+                  thisb = (colors[basec+j+2] < 0) ? (((int) colors[basec+j+2]) + 256) :
+                                                 ((int) colors[basec+j+2]);
+                  g2.setColor(new Color((lastr + thisr) / 2,
+                                        (lastg + thisg) / 2,
+                                        (lastb + thisb) / 2));
+                  lastr = thisr;
+                  lastg = thisg;
+                  lastb = thisb;
+                  j += jinc;
+                  g2.draw(new Line2D.Float(lastx, lasty,
+                                           coordinates[base+i],
+                                           coordinates[base+i+1]));
+                  lastx = coordinates[base+i];
+                  lasty = coordinates[base+i+1];
+                }
+              }
+              base += 3 * count;
+              basec += jinc * count;
+            } // end for (int strip=0; strip<stripVertexCounts.length; strip++)
+          } // end if (array instanceof VisADLineStripArray)
+        }
+        else if (array instanceof VisADTriangleArray) {
+          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                              RenderingHints.VALUE_ANTIALIAS_OFF);
+
+          if (colors == null) {
+            for (int i=0; i<3*count; i += 9) {
+              GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+              path.moveTo(coordinates[i], coordinates[i+1]);
+              path.lineTo(coordinates[i+3], coordinates[i+4]);
+              path.lineTo(coordinates[i+6], coordinates[i+7]);
+              path.closePath();
+              g2.fill(path);
+            }
+          }
+          else { // colors != null
+            int j = 0;
+            int jinc = (colors.length == coordinates.length) ? 3 : 4;
+            for (int i=0; i<3*count; i += 9) {
+              g2.setColor(new Color(
+                (((colors[j] < 0) ? (((int) colors[j]) + 256) :
+                                    ((int) colors[j]) ) +
+                 ((colors[j+jinc] < 0) ? (((int) colors[j+jinc]) + 256) :
+                                    ((int) colors[j+jinc]) ) +
+                 ((colors[j+2*jinc] < 0) ? (((int) colors[j+2*jinc]) + 256) :
+                                    ((int) colors[j+2*jinc]) ) ) / 3,
+                (((colors[j+1] < 0) ? (((int) colors[j+1]) + 256) :
+                                    ((int) colors[j+1]) ) +
+                 ((colors[j+jinc+1] < 0) ? (((int) colors[j+jinc+1]) + 256) :
+                                    ((int) colors[j+jinc+1]) ) +
+                 ((colors[j+2*jinc+1] < 0) ? (((int) colors[j+2*jinc+1]) + 256) :
+                                    ((int) colors[j+2*jinc+1]) ) ) / 3,
+                (((colors[j+2] < 0) ? (((int) colors[j+2]) + 256) :
+                                    ((int) colors[j+2]) ) +
+                 ((colors[j+jinc+2] < 0) ? (((int) colors[j+jinc+2]) + 256) :
+                                    ((int) colors[j+jinc+2]) ) +
+                 ((colors[j+2*jinc+2] < 0) ? (((int) colors[j+2*jinc+2]) + 256) :
+                                    ((int) colors[j+2*jinc+2]) ) ) / 3 ));
+              j += 3 * jinc;
+              GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+              path.moveTo(coordinates[i], coordinates[i+1]);
+              path.lineTo(coordinates[i+3], coordinates[i+4]);
+              path.lineTo(coordinates[i+6], coordinates[i+7]);
+              path.closePath();
+              g2.fill(path);
+            }
+          }
+        }
+        else if (array instanceof VisADQuadArray) {
+          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                              RenderingHints.VALUE_ANTIALIAS_OFF);
+          if (colors == null) {
+            for (int i=0; i<3*count; i += 12) {
+              GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+              path.moveTo(coordinates[i], coordinates[i+1]);
+              path.lineTo(coordinates[i+3], coordinates[i+4]);
+              path.lineTo(coordinates[i+6], coordinates[i+7]);
+              path.lineTo(coordinates[i+9], coordinates[i+10]);
+              path.closePath();
+              g2.fill(path);
+            }
+          }
+          else { // colors != null
+            int j = 0;
+            int jinc = (colors.length == coordinates.length) ? 3 : 4;
+            for (int i=0; i<3*count; i += 12) {
+              g2.setColor(new Color(
+                (((colors[j] < 0) ? (((int) colors[j]) + 256) :
+                                    ((int) colors[j]) ) +
+                 ((colors[j+jinc] < 0) ? (((int) colors[j+jinc]) + 256) :
+                                    ((int) colors[j+jinc]) ) +
+                 ((colors[j+2*jinc] < 0) ? (((int) colors[j+2*jinc]) + 256) :
+                                    ((int) colors[j+2*jinc]) ) +
+                 ((colors[j+3*jinc] < 0) ? (((int) colors[j+3*jinc]) + 256) :
+                                    ((int) colors[j+3*jinc]) ) ) / 4,
+                (((colors[j+1] < 0) ? (((int) colors[j+1]) + 256) :
+                                    ((int) colors[j+1]) ) +
+                 ((colors[j+jinc+1] < 0) ? (((int) colors[j+jinc+1]) + 256) :
+                                    ((int) colors[j+jinc+1]) ) +
+                 ((colors[j+2*jinc+1] < 0) ? (((int) colors[j+2*jinc+1]) + 256) :
+                                    ((int) colors[j+2*jinc+1]) ) +
+                 ((colors[j+3*jinc+1] < 0) ? (((int) colors[j+3*jinc+1]) + 256) :
+                                    ((int) colors[j+3*jinc+1]) ) ) / 4,
+                (((colors[j+2] < 0) ? (((int) colors[j+2]) + 256) :
+                                    ((int) colors[j+2]) ) +
+                 ((colors[j+jinc+2] < 0) ? (((int) colors[j+jinc+2]) + 256) :
+                                    ((int) colors[j+jinc+2]) ) +
+                 ((colors[j+2*jinc+2] < 0) ? (((int) colors[j+2*jinc+2]) + 256) :
+                                    ((int) colors[j+2*jinc+2]) ) +
+                 ((colors[j+3*jinc+2] < 0) ? (((int) colors[j+3*jinc+2]) + 256) :
+                                    ((int) colors[j+3*jinc+2]) ) ) / 4 ));
+              j += 4 * jinc;
+              GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+              path.moveTo(coordinates[i], coordinates[i+1]);
+              path.lineTo(coordinates[i+3], coordinates[i+4]);
+              path.lineTo(coordinates[i+6], coordinates[i+7]);
+              path.lineTo(coordinates[i+9], coordinates[i+10]);
+              path.closePath();
+              g2.fill(path);
+            }
+          }
+        }
+        else if (array instanceof VisADIndexedTriangleStripArray) {
+          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                              RenderingHints.VALUE_ANTIALIAS_OFF);
+          int[] indices = ((VisADIndexedTriangleStripArray) array).indices;
+          int indexCount = ((VisADIndexedTriangleStripArray) array).indexCount;
+          int[] stripVertexCounts =
+             ((VisADIndexedTriangleStripArray) array).stripVertexCounts;
+          int base = 0;
+          for (int strip=0; strip<stripVertexCounts.length; strip++) {
+            count = stripVertexCounts[strip];
+            int index0 = indices[base];
+            int index1 = indices[base+1];
+            if (colors == null) {
+              for (int i=base+2; i<base+count; i++) {
+                int index2 = indices[i];
+                GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+                path.moveTo(coordinates[3*index0], coordinates[3*index0+1]);
+                path.lineTo(coordinates[3*index1], coordinates[3*index1+1]);
+                path.lineTo(coordinates[3*index2], coordinates[3*index2+1]);
+                path.closePath();
+                g2.fill(path);
+                index0 = index1;
+                index1 = index2;
+              }
+            }
+            else { // colors != null
+              int jinc = (colors.length == coordinates.length) ? 3 : 4;
+              for (int i=base+2; i<base+count; i++) {
+                int index2 = indices[i];
+                g2.setColor(new Color(
+                  (((colors[jinc*index0] < 0) ? (((int) colors[jinc*index0]) + 256) :
+                                      ((int) colors[jinc*index0]) ) +
+                   ((colors[jinc*index1] < 0) ? (((int) colors[jinc*index1]) + 256) :
+                                      ((int) colors[jinc*index1]) ) +
+                   ((colors[jinc*index2] < 0) ? (((int) colors[jinc*index2]) + 256) :
+                                      ((int) colors[jinc*index2]) ) ) / 3,
+                  (((colors[jinc*index0+1] < 0) ? (((int) colors[jinc*index0+1]) + 256) :
+                                      ((int) colors[jinc*index0+1]) ) +
+                   ((colors[jinc*index1+1] < 0) ? (((int) colors[jinc*index1+1]) + 256) :
+                                      ((int) colors[jinc*index1+1]) ) +
+                   ((colors[jinc*index2+1] < 0) ? (((int) colors[jinc*index2+1]) + 256) :
+                                      ((int) colors[jinc*index2+1]) ) ) / 3,
+                  (((colors[jinc*index0+2] < 0) ? (((int) colors[jinc*index0+2]) + 256) :
+                                      ((int) colors[jinc*index0+2]) ) +
+                   ((colors[jinc*index1+2] < 0) ? (((int) colors[jinc*index1+2]) + 256) :
+                                      ((int) colors[jinc*index1+2]) ) +
+                   ((colors[jinc*index2+2] < 0) ? (((int) colors[jinc*index2+2]) + 256) :
+                                      ((int) colors[jinc*index2+2]) ) ) / 3 ));
+                GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+                path.moveTo(coordinates[3*index0], coordinates[3*index0+1]);
+                path.lineTo(coordinates[3*index1], coordinates[3*index1+1]);
+                path.lineTo(coordinates[3*index2], coordinates[3*index2+1]);
+                path.closePath();
+                g2.fill(path);
+                index0 = index1;
+                index1 = index2;
+              }
+            }
+            base += count;
+          }
+        } // end if (array instanceof VisADIndexedTriangleStripArray)
+        else if (array instanceof VisADTriangleStripArray) {
+          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                              RenderingHints.VALUE_ANTIALIAS_OFF);
+          int[] stripVertexCounts =
+             ((VisADTriangleStripArray) array).stripVertexCounts;
+          int base = 0;
+          int basec = 0;
+          int jinc = 0;
+          if (colors != null) {
+            jinc = (colors.length == coordinates.length) ? 3 : 4;
+          }
+          for (int strip=0; strip<stripVertexCounts.length; strip++) {
+            count = stripVertexCounts[strip];
+
+            float oldx = coordinates[base];
+            float oldy = coordinates[base+1];
+            float lastx = coordinates[base+3];
+            float lasty = coordinates[base+4];
+            int oldr = 0, oldg = 0, oldb = 0;
+            int lastr = 0, lastg = 0, lastb = 0;
+            int thisr, thisg, thisb;
+
+            if (colors != null) {
+              oldr = (colors[basec] < 0) ? (((int) colors[basec]) + 256) :
+                                           ((int) colors[basec]);
+              oldg = (colors[basec+1] < 0) ? (((int) colors[basec+1]) + 256) :
+                                             ((int) colors[basec+1]);
+              oldb = (colors[basec+2] < 0) ? (((int) colors[basec+2]) + 256) :
+                                             ((int) colors[basec+2]);
+              lastr = (colors[basec+jinc] < 0) ? (((int) colors[basec+jinc]) + 256) :
+                                           ((int) colors[basec+jinc]);
+              lastg = (colors[basec+jinc+1] < 0) ? (((int) colors[basec+jinc+1]) + 256) :
+                                             ((int) colors[basec+jinc+1]);
+              lastb = (colors[basec+jinc+2] < 0) ? (((int) colors[basec+jinc+2]) + 256) :
+                                             ((int) colors[basec+jinc+2]);
+            }
+
+            if (colors == null) {
+              for (int i=6; i<3*count; i+=3) {
+                GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+                path.moveTo(oldx, oldy);
+                path.lineTo(lastx, lasty);
+                path.lineTo(coordinates[base+i], coordinates[base+i+1]);
+                path.closePath();
+                g2.fill(path);
+                oldx = lastx;
+                oldy = lasty;
+                lastx = coordinates[base+i];
+                lasty = coordinates[base+i+1];
+              } // end for (int i=6; i<3*count; i+=3)
+            }
+            else { // colors != null
+              int j = 2 * jinc;
+/*
+System.out.println(j + " " + jinc + " " + basec);
+*/
+              for (int i=6; i<3*count; i+=3) {
+                thisr = (colors[basec+j] < 0) ? (((int) colors[basec+j]) + 256) :
+                                                ((int) colors[basec+j]);
+                thisg = (colors[basec+j+1] < 0) ? (((int) colors[basec+j+1]) + 256) :
+                                                  ((int) colors[basec+j+1]);
+                thisb = (colors[basec+j+2] < 0) ? (((int) colors[basec+j+2]) + 256) :
+                                                  ((int) colors[basec+j+2]);
+                g2.setColor(new Color((thisr + lastr + oldr)/3,
+                                      (thisg + lastg + oldg)/3,
+                                      (thisb + lastb + oldb)/3));
+/*
+System.out.println(i + " " + oldr + " " + oldg + " " + oldb + " " + lastr + " " +
+                   lastg + " " + lastb + " " + thisr + " " + thisg + " " + thisb);
+*/
+                oldr = lastr;
+                oldg = lastg;
+                oldb = lastb;
+                lastr = thisr;
+                lastg = thisg;
+                lastb = thisb;
+                j += jinc;
+                GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+                path.moveTo(oldx, oldy);
+                path.lineTo(lastx, lasty);
+                path.lineTo(coordinates[base+i], coordinates[base+i+1]);
+                path.closePath();
+                g2.fill(path);
+/*
+System.out.println(i + " " + oldx + " " + oldy + " " + lastx + " " + lasty +
+                   " " + coordinates[base+i] + " " + coordinates[base+i+1]);
+*/
+                oldx = lastx;
+                oldy = lasty;
+                lastx = coordinates[base+i];
+                lasty = coordinates[base+i+1];
+              } // end for (int i=6; i<3*count; i+=3)
+            }
+            base += 3 * count;
+            basec += jinc * count;
+          } // end for (int strip=0; strip<stripVertexCounts.length; strip++)
+        } // end if (array instanceof VisADTriangleStripArray)
+        else {
+          throw new VisADError("VisADCanvasJ2D.render: bad array class");
+        }
+      } // end if (image == null)
+    } // end if (scene instanceof VisADAppearance)
+  }
+
+  /**
+   * This assumes only VisADPointArray or VisADLineArray.
+   * @param graphics
+   * @param appearance
+   * @param t
+   * @param clip
+   */
+  public static void drawAppearance(Graphics graphics, VisADAppearance appearance,
+                                    AffineTransform t, Rectangle2D.Float clip) {
+    VisADGeometryArray array = appearance.array;
+    if (array == null) return;
+    byte[] colors = array.colors;
+    if (colors == null) {
+      if (appearance.color_flag) {
+        graphics.setColor(new Color(appearance.red, appearance.green,
+                                    appearance.blue));
+/*
+System.out.println("drawAppearance: color = " + appearance.red + " " +
+                   appearance.green + " " + appearance.blue);
+*/
+      }
+      else {
+        graphics.setColor(new Color(1.0f, 1.0f, 1.0f));
+      }
+    }
+    int count = array.vertexCount;
+    float[] coordinates = array.coordinates;
+    float[] oldcoords = new float[2*count];
+    int j = 0;
+    for (int i=0; i<3*count; i += 3) {
+      oldcoords[j++] = coordinates[i];
+      oldcoords[j++] = coordinates[i+1];
+    }
+    float[] newcoords = new float[2 * count];
+    t.transform(oldcoords, 0, newcoords, 0, count);
+
+    if (clip == null) {
+      graphics.setClip(null);
+    }
+    else {
+      // transform clip
+      float x = (float) clip.getX();
+      float y = (float) clip.getY();
+      float width = (float) clip.getWidth();
+      float height = (float) clip.getHeight();
+      float[] oldclip =
+        {x, y, x, y+height, x+width, y+height, x+width, y};
+      float[] newclip = new float[2 * 4];
+      t.transform(oldclip, 0, newclip, 0, 4);
+      GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+      path.moveTo(newclip[0], newclip[1]);
+      path.lineTo(newclip[2], newclip[3]);
+      path.lineTo(newclip[4], newclip[5]);
+      path.lineTo(newclip[6], newclip[7]);
+      path.closePath();
+      graphics.setClip(path);
+    }
+
+    if (array instanceof VisADPointArray) {
+/*
+System.out.println("drawAppearance: VisADPointArray, count = " + count);
+*/
+      if (colors == null) {
+        for (int i=0; i<2*count; i += 2) {
+          graphics.drawLine((int) newcoords[i], (int) newcoords[i+1],
+                            (int) newcoords[i], (int) newcoords[i+1]);
+        }
+      }
+      else { // colors != null
+        int jinc = (colors.length == coordinates.length) ? 3 : 4;
+        j = 0;
+        for (int i=0; i<2*count; i += 2) {
+          graphics.setColor(new Color(
+            ((colors[j] < 0) ? (((int) colors[j]) + 256) :
+                               ((int) colors[j]) ),
+            ((colors[j+1] < 0) ? (((int) colors[j+1]) + 256) :
+                                 ((int) colors[j+1]) ),
+            ((colors[j+2] < 0) ? (((int) colors[j+2]) + 256) :
+                                 ((int) colors[j+2]) ) ));
+          j += jinc;
+          graphics.drawLine((int) newcoords[i], (int) newcoords[i+1],
+                            (int) newcoords[i], (int) newcoords[i+1]);
+        }
+      }
+    }
+    else if (array instanceof VisADLineArray) {
+/*
+System.out.println("drawAppearance: VisADLineArray, count = " + count +
+                   " colors == null " + (colors == null));
+*/
+      if (colors == null) {
+        for (int i=0; i<2*count; i += 4) {
+          int width = (int) appearance.lineWidth;
+          int x1 = (int) newcoords[i];
+          int y1 = (int) newcoords[i+1];
+          int x2 = (int) newcoords[i+2];
+          int y2 = (int) newcoords[i+3];
+          if (width <= 1 || (x1 != x2 && y1 != y2)) {
+            graphics.drawLine(x1, y1, x2, y2);
+          }
+          else { // (width > 1 && (x1 == x2 || y1 == y2))
+            int hw1 = width / 2;
+            int hw2 = width - hw1;
+            int[] xPoints = null;
+            int[] yPoints = null;
+            if (x1 == x2) {
+              xPoints = new int[] {x1 - hw1, x1 + hw2, x1 + hw2, x1 - hw1};
+              yPoints = new int[] {y1, y1, y2, y2};
+            }
+            else { // y1 == y2
+              xPoints = new int[] {x1, x1, x2, x2};
+              yPoints = new int[] {y1 - hw1, y1 + hw2, y1 + hw2, y1 - hw1};
+            }
+            graphics.fillPolygon(xPoints, yPoints, 4);
+          }
+/*
+          graphics.drawLine((int) newcoords[i], (int) newcoords[i+1],
+                            (int) newcoords[i+2], (int) newcoords[i+3]);
+*/
+/*
+System.out.println(" " + newcoords[i] + " " + newcoords[i+1] + " " +
+                   newcoords[i+2] + " " + newcoords[i+3]);
+*/
+        }
+      }
+      else { // colors != null
+        int jinc = (colors.length == coordinates.length) ? 3 : 4;
+        j = 0;
+        for (int i=0; i<2*count; i += 4) {
+          graphics.setColor(new Color(
+            (((colors[j] < 0) ? (((int) colors[j]) + 256) :
+                                ((int) colors[j]) ) +
+             ((colors[j+jinc] < 0) ? (((int) colors[j+jinc]) + 256) :
+                                     ((int) colors[j+jinc]) ) ) / 2,
+            (((colors[j+1] < 0) ? (((int) colors[j+1]) + 256) :
+                                  ((int) colors[j+1]) ) +
+             ((colors[j+jinc+1] < 0) ? (((int) colors[j+jinc+1]) + 256) :
+                                       ((int) colors[j+jinc+1]) ) ) / 2,
+            (((colors[j+2] < 0) ? (((int) colors[j+2]) + 256) :
+                                  ((int) colors[j+2]) ) +
+             ((colors[j+jinc+2] < 0) ? (((int) colors[j+jinc+2]) + 256) :
+                                       ((int) colors[j+jinc+2]) ) ) / 2 ));
+          j += 2 * jinc;
+
+          int width = (int) appearance.lineWidth;
+          int x1 = (int) newcoords[i];
+          int y1 = (int) newcoords[i+1];
+          int x2 = (int) newcoords[i+2];
+          int y2 = (int) newcoords[i+3];
+          if (width <= 1 || (x1 != x2 && y1 != y2)) {
+            graphics.drawLine(x1, y1, x2, y2);
+          }
+          else { // (width > 1 && (x1 == x2 || y1 == y2))
+            int hw1 = width / 2;
+            int hw2 = width - hw1;
+            int[] xPoints = null;
+            int[] yPoints = null;
+            if (x1 == x2) {
+              xPoints = new int[] {x1 - hw1, x1 + hw2, x1 + hw2, x1 - hw1};
+              yPoints = new int[] {y1, y1, y2, y2};
+            }
+            else { // y1 == y2
+              xPoints = new int[] {x1, x1, x2, x2};
+              yPoints = new int[] {y1 - hw1, y1 + hw2, y1 + hw2, y1 - hw1};
+            }
+            graphics.fillPolygon(xPoints, yPoints, 4);
+          }
+/*
+          graphics.drawLine((int) newcoords[i], (int) newcoords[i+1],
+                            (int) newcoords[i+2], (int) newcoords[i+3]);
+*/
+        }
+      }
+    }
+    else {
+      throw new VisADError("DisplayRendererJ2D.drawAppearance: " +
+                           "bad VisADGeometryArray type");
+    }
+  }
+
+  public AffineTransform getTransform() {
+    synchronized (images) {
+      return tgeometry;
+    }
+  }
+
+  public Dimension getPreferredSize() {
+    return prefSize;
+  }
+
+  public void setPreferredSize(Dimension size) {
+    prefSize = size;
+  }
+
+  // CTR 14 Nov 2000 - support for auto-aspect to canvas size
+
+  private boolean autoAspect = false;
+
+  public boolean getAutoAspect() {
+    return autoAspect;
+  }
+
+  public void setAutoAspect(boolean auto) {
+    autoAspect = auto;
+  }
+
+  public void setBounds(int x, int y, int width, int height) {
+    if (width < 1 || height < 1) return;
+    super.setBounds(x, y, width, height);
+    if (autoAspect) {
+      ProjectionControl pc = display.getProjectionControl();
+      try {
+        double a, b;
+        if (height > width) {
+          a = 1.0;
+          b = ((double) height) / ((double) width);
+        }
+        else {
+          a = ((double) width) / ((double) height);
+          b = 1.0;
+        }
+        pc.setAspectCartesian(new double[] {a, b});
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+  }
+
+}
+
diff --git a/visad/java2d/package.html b/visad/java2d/package.html
new file mode 100644
index 0000000..e40520f
--- /dev/null
+++ b/visad/java2d/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides support for two-dimensional VisAD Displays using Java2D.
+
+</body>
+</html>
+
diff --git a/visad/java3d/AVControlJ3D.java b/visad/java3d/AVControlJ3D.java
new file mode 100644
index 0000000..ebb3b0f
--- /dev/null
+++ b/visad/java3d/AVControlJ3D.java
@@ -0,0 +1,236 @@
+//
+// AVControlJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+/**
+   AVControlJ3D is the VisAD abstract superclass for AnimationControlJ3D
+   and ValueControlJ3D.<P>
+*/
+public abstract class AVControlJ3D extends Control implements AVControl {
+
+  transient Vector switches = new Vector();
+
+  private int interval = -1;
+
+  public AVControlJ3D(DisplayImplJ3D d) {
+    super(d);
+  }
+
+  public void addPair(Switch sw, Set se, DataRenderer re) {
+    switches.addElement(new SwitchSet(sw, se, re));
+  }
+
+  public void nullControl() {
+    switches.removeAllElements();
+    super.nullControl();
+  }
+
+  public abstract void init() throws VisADException;
+
+  /**
+   * Sets the time between two steps in the animation set. This value
+   * is then used to determine the range of acceptable samples to view
+   * out of each of the renderers associated with the AV_Control
+   *
+   * @param interval the time interval in minutes between steps in the
+   * animation set
+   */
+  public void setInterval(int interval)
+  {
+    this.interval = interval;
+  }
+
+  /**
+   * Method that returns an index in the set that is between the
+   * lower and upper bounds
+   */
+  private int getIndexForRange(Set set, double lower, double upper)
+    throws VisADException
+  {
+    double [][] values = set.getDoubles(false);
+
+    for(int i=0; i<values[0].length; i++) {
+      if(values[0][i] >= lower && values[0][i] < upper) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  public void selectSwitches(double value, Set animation_set)
+       throws VisADException {
+    // check for missing
+    if (value != value) return;
+    double[][] fvalues = new double[1][1];
+    fvalues[0][0] = value;
+    Enumeration pairs = ((Vector) switches.clone()).elements();
+    while (pairs.hasMoreElements()) {
+      SwitchSet ss = (SwitchSet) pairs.nextElement();
+      Set set = ss.set;
+      double[][] values = null;
+      RealTupleType out = ((SetType) set.getType()).getDomain();
+      if (animation_set != null) {
+        RealTupleType in =
+          ((SetType) animation_set.getType()).getDomain();
+        values = CoordinateSystem.transformCoordinates(
+                             out, set.getCoordinateSystem(),
+                             set.getSetUnits(), null /* errors */,
+                             in, animation_set.getCoordinateSystem(),
+                             animation_set.getSetUnits(),
+                             null /* errors */, fvalues);
+/*
+System.out.println("animation_set = " + animation_set);
+System.out.println("transformCoordinates\n" + out + "\n" +
+set.getCoordinateSystem() + "\n" + set.getSetUnits()[0] + "\n" +
+in + "\n" + animation_set.getCoordinateSystem() + "\n" +
+animation_set.getSetUnits()[0] + "\n" + fvalues[0][0] + "\n" +
+values[0][0]);
+*/
+      }
+      else {
+        // use RealType for value Unit and CoordinateSystem
+        // for SelectValue
+        values = CoordinateSystem.transformCoordinates(
+                             out, set.getCoordinateSystem(),
+                             set.getSetUnits(), null /* errors */,
+                             out, out.getCoordinateSystem(),
+                             out.getDefaultUnits(), null /* errors */,
+                             fvalues);
+      }
+      // compute set index from converted value
+      int [] indices;
+
+      // from Luke Matthews of BOM
+      if(interval == -1) {
+        //default behaviour
+        if (set.getLength() == 1) {
+          indices = new int[] {0};
+        }
+        else {
+          indices = set.doubleToIndex(values);
+        }
+      }
+      else {
+        double resInSecs = (double)interval*60;
+        double lower = values[0][0] - (resInSecs/2.0);
+        double upper = values[0][0] + (resInSecs/2.0);
+        indices = new int[] {getIndexForRange(set, lower, upper)};
+      }
+
+// System.out.println("indices = " + indices[0] + " value = " + value);
+// System.out.println("numChildren = " + ss.swit.numChildren());
+      if (0 <= indices[0] && indices[0] < ss.swit.numChildren()) {
+        ss.swit.setWhichChild(indices[0]);
+// DisplayImpl.printStack("child " + indices[0]);
+/*
+if (animation_set == null) {
+// System.out.println("selectSwitches: ss.swit.setWhichChild(" +
+//                    indices[0] + ")");
+DisplayImpl.printStack("selectSwitches: ss.swit.setWhichChild(" +
+                       indices[0] + ")");
+System.out.println("ss.swit.numChildren() = " + ss.swit.numChildren());
+}
+*/
+      }
+      else {
+        ss.swit.setWhichChild(Switch.CHILD_NONE);
+        // DisplayImpl.printStack("CHILD_NONE");
+/*
+if (animation_set == null) {
+// System.out.println("selectSwitches: ss.swit.setWhichChild(Switch.CHILD_NONE)");
+DisplayImpl.printStack("selectSwitches: ss.swit.setWhichChild(Switch.CHILD_NONE)");
+}
+*/
+      }
+    } // end while (pairs.hasMoreElements())
+// DisplayImpl.printStack("selectSwitches " + value);
+  }
+
+  /** clear all 'pairs' in switches that involve re */
+  public void clearSwitches(DataRenderer re) {
+    Enumeration pairs = ((Vector) switches.clone()).elements();
+    while (pairs.hasMoreElements()) {
+      SwitchSet ss = (SwitchSet) pairs.nextElement();
+      if (ss.renderer.equals(re)) {
+        switches.removeElement(ss);
+      }
+    }
+  }
+
+  public Vector getSwitches() {
+    return switches;
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    AVControlJ3D av = (AVControlJ3D )o;
+
+    if (switches == null) {
+      return (av.switches == null);
+    } else if (av.switches == null) {
+      // don't assume anything, since it could be a remote control
+    } else {
+      if (switches.size() != av.switches.size()) {
+        return false;
+      }
+      for (int i = switches.size() - 1; i > 0; i--) {
+        if (!switches.elementAt(i).equals(av.switches.elementAt(i))) {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /** SwitchSet is an inner class of AVControlJ3D for
+      (Switch, Set, DataRenderer) structures */
+  protected class SwitchSet extends Object {
+    Switch swit;
+    Set set;
+    DataRenderer renderer;
+
+    SwitchSet(Switch sw, Set se, DataRenderer re) {
+      swit = sw;
+      set = se;
+      renderer = re;
+    }
+  }
+
+}
+
diff --git a/visad/java3d/AnimationControlJ3D.java b/visad/java3d/AnimationControlJ3D.java
new file mode 100644
index 0000000..a4d5198
--- /dev/null
+++ b/visad/java3d/AnimationControlJ3D.java
@@ -0,0 +1,671 @@
+//
+// AnimationControlJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+
+import javax.media.j3d.Behavior;
+import javax.media.j3d.BoundingSphere;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.WakeupOnElapsedTime;
+import javax.vecmath.Point3d;
+
+import visad.AnimationControl;
+import visad.AnimationSetControl;
+import visad.Control;
+import visad.DataDisplayLink;
+import visad.DataRenderer;
+import visad.DisplayException;
+import visad.RealType;
+import visad.Set;
+import visad.ToggleControl;
+import visad.VisADError;
+import visad.VisADException;
+import visad.browser.Convert;
+
+/**
+   AnimationControlJ3D is the VisAD class for controlling Animation
+   display scalars under Java3D.<P>
+
+   WLH - manipulate a list of Switch nodes in scene graph.<P>
+*/
+public class AnimationControlJ3D extends AVControlJ3D
+       implements AnimationControl {
+
+  private static final long serialVersionUID = 7763197458917167330L;
+  private static final long DEFAULT_DWELL = 500;
+  protected int current = 0;//DML: made protected so subclass can use it.
+  private boolean direction; // true = forward
+  private long step = DEFAULT_DWELL; // time in milliseconds between animation steps
+  private WakeupOnElapsedTime[] stepValues; // times in milliseconds between animation steps
+  private transient AnimationSetControl animationSet;
+  private ToggleControl animate;
+  private RealType real;
+  private boolean computeSet = true;
+
+  /** Behavior for initiating time stepping. */
+  private transient Behavior steppingBehaviour;
+
+  public AnimationControlJ3D(DisplayImplJ3D d, RealType r) {
+    super(d);
+    real = r;
+    current = 0;
+    direction = true;
+    animationSet = new AnimationSetControl(d, this);
+    // initialize the stepValues array
+    stepValues = new WakeupOnElapsedTime[]{
+        new WakeupOnElapsedTime(DEFAULT_DWELL)
+    };
+    try {
+      Set set = animationSet.getSet();
+      if (set != null)
+        stepValues = new WakeupOnElapsedTime[set.getLength()];
+    } catch (VisADException v) {
+    }
+    for (int i = 0; i < stepValues.length; i++) {
+      stepValues[i] = new WakeupOnElapsedTime(DEFAULT_DWELL);
+    }
+    d.addControl(animationSet);
+    animate = new ToggleControl(d, this);
+    d.addControl(animate);
+    try {
+      animate.setOn(false);
+    } catch (VisADException v) {
+    } catch (RemoteException v) {
+    }
+
+    // add stepping behavior near the root of the scene graph
+    steppingBehaviour = new TakeStepBehavior();
+    BranchGroup bg = new BranchGroup();
+    bg.addChild(steppingBehaviour);
+    DisplayRendererJ3D rend = (DisplayRendererJ3D) d.getDisplayRenderer();
+    BranchGroup root = rend.getRoot();
+    root.addChild(bg);
+  }
+
+  AnimationControlJ3D() {
+    this(null, null);
+  }
+  
+  public int getCurrent() {
+    return current;
+  }
+
+  /** set the current ordinal step number = c */
+  public void setCurrent(int c)
+         throws VisADException, RemoteException {
+    if (animationSet != null) {
+      current = animationSet.clipCurrent(c);
+      init();
+    }
+    else {
+      current = 0;
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /** set the current step by the value of the RealType
+      mapped to Display.Animation */
+  public void setCurrent(double value)
+         throws VisADException, RemoteException {
+    if (animationSet != null) {
+      current = animationSet.getIndex(value);
+      init();
+    }
+    else {
+      current = 0;
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /**
+   * Set the animation direction.
+   *
+   * @param    dir     true for forward, false for backward
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           direction remains unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setDirection(boolean dir)
+         throws VisADException, RemoteException {
+    direction = dir;
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /** Get the animation direction.
+   *
+   *  @return   true for forward, false for backward
+   */
+  public boolean getDirection()
+  {
+    return direction;
+  }
+
+  /**
+   * Return the dwell time for the current step
+   */
+  public long getStep() {
+    if (stepValues == null || current < 0 ||
+        stepValues.length <= current) return DEFAULT_DWELL;
+    else return stepValues[current].getElapsedFrameTime();
+  }
+
+  /**
+   * return an array of the dwell times for all the steps.
+   */
+  public long[] getSteps()
+  {
+    long[] steps = new long[stepValues.length];
+    for (int i=0; i<steps.length; i++) {
+      steps[i] = stepValues[i].getElapsedFrameTime();
+    }
+    return steps;
+  }
+  
+  /**
+   * set the dwell time for all steps
+   *
+   * @param  st   dwell time in milliseconds
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           dwell time remains unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setStep(int st) throws VisADException, RemoteException {
+    if (st <= 0) {
+      throw new DisplayException("AnimationControlJ3D.setStep: " +
+                                 "step must be > 0");
+    }
+    step = st;
+    for (int i=0; i < stepValues.length; i++)
+    {
+        stepValues[i] = new WakeupOnElapsedTime(st);
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /**
+   * set the dwell time for individual steps.
+   *
+   * @param   steps   an array of dwell rates for each step in the animation
+   *                  If the length of the array is less than the number of
+   *                  frames in the animation, the subsequent step values will
+   *                  be set to the value of the last step.
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  The
+   *                           dwell times remain unchanged.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setSteps(int[] steps)
+         throws VisADException, RemoteException {
+    // verify that the values are valid
+    for (int i = 0; i < stepValues.length; i++)
+    {
+        long step = (i < steps.length) ? steps[i] : steps[steps.length-1];
+        stepValues[i] = new WakeupOnElapsedTime(step);
+        if (step <= 0)
+            throw new DisplayException("AnimationControlJ3D.setSteps: " +
+                                 "step " + i + " must be > 0");
+    }
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(true);
+  }
+
+  /**
+   * advance one step (forward or backward)
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           step is taken.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void takeStep() throws VisADException, RemoteException {
+    if (direction) current++;
+    else current--;
+    if (animationSet != null) {
+      current = animationSet.clipCurrent(current);
+      init();
+    }
+    getDisplayRenderer().render_trigger();
+    changeControl(false);
+  }
+
+  public void init() throws VisADException {
+    if (animationSet != null) { 
+      double value = animationSet.getValue(current);
+      Set set = animationSet.getSet();
+ 
+      animation_string(real, set, value, current);
+      selectSwitches(value, set);
+    }
+  }
+
+  public Set getSet() {
+    if (animationSet != null) {
+      return animationSet.getSet();
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * <p>Sets the set of times in this animation control.  If the argument 
+   * set is equal to the current set, then nothing is done.</p>
+   *
+   * @param s                     The set of times.
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  public void setSet(Set s)
+         throws VisADException, RemoteException {
+    if (s == null && animationSet != null && 
+        animationSet.getSet() == null) return;  // check for null/null
+    if (animationSet == null || s == null || 
+        (s != null && !s.equals(animationSet.getSet()))) {
+      setSet(s, false);
+      // have to do this if animationSet == null
+      if (s == null) {
+        stepValues = new WakeupOnElapsedTime[]{new WakeupOnElapsedTime(DEFAULT_DWELL)};
+        current = 0;
+      } else if (s.getLength() != stepValues.length) {
+        stepValues = new WakeupOnElapsedTime[s.getLength()];
+        for (int i = 0; i < stepValues.length; i++)
+        {
+          stepValues[i] = new WakeupOnElapsedTime(step);
+        }
+      }
+    }
+  }
+
+  /**
+   * <p>Sets the set of times in this animation control.  If the argument 
+   * set is equal to the current set, then nothing is done.</p>
+   *
+   * @param s                     The set of times.
+   * @param noChange              changeControl(!noChange) to not trigger 
+   *                              re-transform, used by ScalarMap.setRange
+   * @throws VisADException       if a VisAD failure occurs.
+   * @throws RemoteException      if a Java RMI failure occurs.
+   */
+  public void setSet(Set s, boolean noChange)
+         throws VisADException, RemoteException {
+    if (animationSet != null) {
+      if (s == null) {
+          stepValues = new WakeupOnElapsedTime[]{new WakeupOnElapsedTime(DEFAULT_DWELL)};
+          current = 0;
+      } else if (s.getLength() != stepValues.length) {
+          stepValues = new WakeupOnElapsedTime[s.getLength()];
+          for (int i = 0; i < stepValues.length; i++)
+          {
+              stepValues[i] = new WakeupOnElapsedTime(step);
+          }
+      }
+      animationSet.setSet(s, noChange);
+    }
+  }
+
+  /** return true if automatic stepping is on */
+  public boolean getOn() {
+    if (animate != null) {
+      return animate.getOn();
+    }
+    else {
+      return false;
+    }
+  }
+
+  /**
+   * Set automatic stepping on or off.
+   *
+   * @param  o  true = turn stepping on, false = turn stepping off
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           change in automatic stepping occurs.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void setOn(boolean o)
+         throws VisADException, RemoteException {
+    if (animate != null) {
+      animate.setOn(o);
+    }
+  }
+
+  /**
+   * toggle automatic stepping between off and on
+   *
+   * @throws  VisADException   Couldn't create necessary VisAD object.  No
+   *                           change in automatic stepping occurs.
+   * @throws  RemoteException  Java RMI exception
+   */
+  public void toggle()
+         throws VisADException, RemoteException {
+    if (animate != null) {
+      animate.setOn(!animate.getOn());
+    }
+  }
+
+  public RealType getRealType() {
+    return real;
+  }
+
+  public void subSetTicks() {
+    if (animationSet != null) {
+      animationSet.setTicks();
+    }
+    if (animate != null) {
+      animate.setTicks();
+    }
+  }
+
+  public boolean subCheckTicks(DataRenderer r, DataDisplayLink link) {
+    boolean flag = false;
+    if (animationSet != null) {
+      flag |= animationSet.checkTicks(r, link);
+    }
+    if (animate != null) {
+      flag |= animate.checkTicks(r, link);
+    }
+    return flag;
+  }
+
+  public boolean subPeekTicks(DataRenderer r, DataDisplayLink link) {
+    boolean flag = false;
+    if (animationSet != null) {
+      flag |= animationSet.peekTicks(r, link);
+    }
+    if (animate != null) {
+      flag |= animate.peekTicks(r, link);
+    }
+    return flag;
+  }
+
+  public void subResetTicks() {
+    if (animationSet != null) {
+      animationSet.resetTicks();
+    }
+    if (animate != null) {
+      animate.resetTicks();
+    }
+  }
+
+  /** get a String that can be used to reconstruct this
+      AnimationControl later */
+  public String getSaveString() {
+    int numSteps;
+    long[] steps;
+    if (stepValues == null) {
+      numSteps = 1;
+      steps = new long[1];
+      steps[0] = DEFAULT_DWELL;
+    }
+    else {
+      numSteps = stepValues.length;
+      steps = new long[numSteps];
+      for (int i = 0; i < numSteps; i++)
+        steps[i] = stepValues[i].getElapsedFrameTime();
+    }
+    StringBuffer sb = new StringBuffer(35 + 12 * numSteps);
+    sb.append(animate != null && animate.getOn());
+    sb.append(' ');
+    sb.append(direction);
+    sb.append(' ');
+    sb.append(current);
+    sb.append(' ');
+    sb.append(numSteps);
+    for (int i=0; i<numSteps; i++) {
+      sb.append(' ');
+      sb.append((int) steps[i]);
+    }
+    sb.append(' ');
+    sb.append(computeSet);
+    return sb.toString();
+  }
+
+  /** reconstruct this AnimationControl using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    StringTokenizer st = new StringTokenizer(save);
+    int numTokens = st.countTokens();
+    if (numTokens < 4) throw new VisADException("Invalid save string");
+
+    // get animation settings
+    boolean on = Convert.getBoolean(st.nextToken());
+    boolean dir = Convert.getBoolean(st.nextToken());
+    int cur = Convert.getInt(st.nextToken());
+    int numSteps = Convert.getInt(st.nextToken());
+    if (numSteps <= 0) {
+      throw new VisADException("Number of steps is not positive");
+    }
+    if (numTokens < 4 + numSteps) {
+      throw new VisADException("Not enough step entries");
+    }
+    int[] steps = new int[numSteps];
+    for (int i=0; i<numSteps; i++) {
+      steps[i] = Convert.getInt(st.nextToken());
+      if (steps[i] <= 0) {
+        throw new VisADException("Step #" + (i + 1) + "is not positive");
+      }
+    }
+    boolean cs = st.hasMoreTokens() ? Convert.getBoolean(st.nextToken()) : getComputeSet();
+
+    // set values
+    setOn(on);
+    setDirection(dir);
+    setSteps(steps);
+    setCurrent(cur);
+    setComputeSet(cs);
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof AnimationControlJ3D)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    AnimationControlJ3D ac = (AnimationControlJ3D )rmt;
+
+    boolean changed = false;
+
+    /* *** DON'T TRY TO SYNC CURRENT FRAME!!! *** */
+    // if (current != ac.current) {
+    //   changed = true;
+    //   if (animationSet != null) {
+    //     current = animationSet.getIndex(ac.current);
+    //     canvas.renderTrigger();
+    //   } else {
+    //     current = 0;
+    //   }
+    // }
+    if (direction != ac.direction) {
+      changed = true;
+      direction = ac.direction;
+    }
+    if (step != ac.step) {
+      changed = true;
+      step = ac.step;
+    }
+    if (animate != ac.animate) {
+      changed = true;
+      animate = ac.animate;
+    }
+    if (real != ac.real) {
+      changed = true;
+      real = ac.real;
+    }
+
+    if (computeSet != ac.computeSet) {
+      changed = true;
+      computeSet = ac.computeSet;
+    }
+
+    if (changed) {
+      try {
+        // WLH 5 May 2000
+        // changeControl(true);
+        changeControl(false);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    AnimationControlJ3D ac = (AnimationControlJ3D )o;
+    /**** IGNORE FRAME POSITION ****/
+    // if (current != ac.current) {
+    //   return false;
+    // }
+    if (direction != ac.direction) {
+      return false;
+    }
+    if (step != ac.step) {
+      return false;
+    }
+    if (animate != ac.animate) {
+      return false;
+    }
+    if (real != ac.real) {
+      return false;
+    }
+
+    if (computeSet != ac.computeSet) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Set the flag to automatically compute the animation set if it is
+   * null
+   * @param compute   false to allow application to control set computation
+   *                  if set is null.
+   */
+  public void setComputeSet(boolean compute) {
+      computeSet = compute;
+  }
+
+  /**
+   * Get the flag to automatically compute the animation set if it is
+   * null
+   * 
+   * @return true if should compute set automatically when null
+   */
+  public boolean getComputeSet() {
+    return computeSet;
+  }
+  
+  /**
+   * Java3D <code>Behavior</code> which initiates animation stepping based on
+   * elapsed time.
+   */
+  private class TakeStepBehavior extends Behavior {
+
+    public TakeStepBehavior() {
+      setSchedulingBounds(new BoundingSphere(new Point3d(0,0,0), 10000000));
+    }
+
+    // initialize dwell when added to universe
+    public void initialize() {
+      updateDwell();
+    }
+
+    /**
+     * Unconditionally attempt to take the next step.
+     */
+    public void processStimulus(Enumeration criteria) {
+      try {
+        if (animate != null && animate.getOn()) {
+          takeStep();
+        }
+      } catch (VisADException v) {
+        throw new VisADError("Unable to take animation step", v);
+      } catch (RemoteException v) {
+        throw new VisADError("Unable to take animation step", v);
+      }
+      updateDwell();
+    }
+
+    /**
+     * Set the wake up criteria for the current step value (dwell).
+     */
+    public void updateDwell() {
+      // set the wake up criteria with the dwell for the current step
+      if (0 <= current && current < stepValues.length) {
+        wakeupOn(stepValues[current]);
+      } else {
+        wakeupOn(new WakeupOnElapsedTime(DEFAULT_DWELL));
+      }
+
+    }
+  }
+
+  /**
+   * Stop animating.
+   */
+  @Override
+  public void stop() {
+    steppingBehaviour.setEnable(false);
+  }
+
+  /**
+   * Start animating.
+   */
+  @Override
+  public void run() {
+    steppingBehaviour.setEnable(true);
+  }
+  
+}
diff --git a/visad/java3d/AnimationRendererJ3D.java b/visad/java3d/AnimationRendererJ3D.java
new file mode 100644
index 0000000..3fa2f65
--- /dev/null
+++ b/visad/java3d/AnimationRendererJ3D.java
@@ -0,0 +1,294 @@
+//
+// AnimationRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+import visad.util.Delay;
+
+import javax.media.j3d.*;
+
+import java.rmi.*;
+import java.io.IOException;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.Vector;
+
+public class AnimationRendererJ3D extends DefaultRendererJ3D {
+
+  boolean animation1D;
+  String  nameMappedToAnimation = null;
+
+  private boolean reUseFrames = false;
+
+  private boolean setSetOnReUseFrames = true;
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowAnimationFunctionTypeJ3D(type, link, parent);
+  }
+
+  public void setReUseFrames(boolean reuse) {
+    reUseFrames = reuse;
+  }
+
+  public void setReUseFrames() {
+    setReUseFrames(true);
+  }
+
+  public boolean getReUseFrames() {
+    return reUseFrames;
+  }
+
+  public void setSetSetOnReUseFrames(boolean ss) {
+    setSetOnReUseFrames = ss;
+  }
+
+  public boolean getSetSetOnReUseFrames() {
+    return setSetOnReUseFrames;
+  }
+
+  // logic to 'mark' missing frames
+  private VisADBranchGroup vbranch = null;
+
+  public void clearScene() {
+    vbranch = null;
+    super.clearScene();
+  }
+
+  void setVisADBranch(VisADBranchGroup branch) {
+    vbranch = branch;
+  }
+
+  void markMissingVisADBranch() {
+    if (vbranch != null) vbranch.scratchTime();
+  }
+  // end of logic to 'mark' missing frames
+
+  public BranchGroup doTransform() throws VisADException, RemoteException {
+    BranchGroup branch = getBranch();
+    if (branch == null) {
+      branch = new BranchGroup();
+      branch.setCapability(BranchGroup.ALLOW_DETACH);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+      branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+    }
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return null;
+    }
+
+    DataDisplayLink link = Links[0];
+    ShadowTypeJ3D type = (ShadowTypeJ3D) link.getShadow();
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return null;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      addException(
+        new DisplayException("Data is null: AnimationRendererJ3D.doTransform"));
+    }
+    else {
+      animation1D = false;
+      MathType mtype = link.getType();
+      if (mtype instanceof FunctionType) {
+        FunctionType function = (FunctionType) mtype;
+        RealTupleType functionD = function.getDomain();
+        Vector scalarMaps = link.getSelectedMapVector();
+        for (int kk = 0; kk < scalarMaps.size(); kk++) {
+          ScalarMap scalar_map = (ScalarMap)scalarMaps.elementAt(kk);
+          String scalar_name = scalar_map.getScalarName();
+          if (scalar_name.equals(((RealType)functionD.getComponent(0)).getName())) {
+            if (((scalar_map.getDisplayScalar()).equals(Display.Animation))&&
+                 (functionD.getDimension() == 1)) {
+              animation1D = true;
+              nameMappedToAnimation = scalar_name;
+            }
+          }
+        }
+      }
+     
+      link.start_time = System.currentTimeMillis();
+      link.time_flag = false;
+      vbranch = null;
+
+
+      if (!animation1D) {
+        // TDR, 3-2003: 
+        // make sure branch not live for default logic, ie. super.doTransform()
+        branch = new BranchGroup();
+        branch.setCapability(BranchGroup.ALLOW_DETACH);
+        branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+        branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+        branch.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+      }
+
+      // transform data into a depiction under branch
+      try {
+        type.doTransform(branch, data, valueArray,
+                         link.getDefaultValues(), this);
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+    }
+    link.clearData();
+    return branch;
+  }
+
+  public Object clone() {
+    return new AnimationRendererJ3D();
+  }
+
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+
+    String test = "new";
+    if (args.length > 0) {
+      test = args[0];
+      if (!(test.equals("new") || test.equals("old"))) {
+        System.out.println("arg must be 'old' or 'new'");
+        System.exit(0);
+      }
+    }
+    
+    int size = 80;
+    int nr = size;
+    int nc = size;
+    int nz = size;
+    double ang = 2*Math.PI/nr;
+
+    RealType[] types = {RealType.Latitude, RealType.Longitude, RealType.Altitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType radiance = RealType.getRealType("radiance", null, null);
+    RealType index    = RealType.getRealType("index", null, null);
+    FunctionType image_type = new FunctionType(earth_location, radiance);
+
+    Integer3DSet image_domain_set =
+      new Integer3DSet(RealTupleType.SpatialCartesian3DTuple, nr, nc, nz);
+    FunctionType field_type = new FunctionType(index, image_type);
+    Integer1DSet field_domain_set = new Integer1DSet(index, 6);
+    FieldImpl field = new FieldImpl(field_type, field_domain_set);
+
+    for (int tt = 0; tt < field_domain_set.getLength(); tt++) {
+
+    float[][] values = new float[1][nr*nc*nz];
+    for ( int kk = 0; kk < nz; kk++) {
+      for ( int jj = 0; jj < nc; jj++ ) {
+        for ( int ii = 0; ii < nr; ii++ ) {
+          int idx = kk*nr*nc + jj*nr + ii;
+          values[0][idx] =
+            (tt+1)*(2f*((float)Math.sin(2*ang*ii)) + 2f*((float)Math.sin(2*ang*jj))) + kk;
+        }
+      }
+    }
+    FlatField image = new FlatField(image_type, image_domain_set);
+    image.setSamples(values);
+    field.setSample(tt, image, false);
+    }
+
+    DisplayImplJ3D dpys = new DisplayImplJ3D("AnimationRendererJ3D Test");
+
+    JFrame jframe  = new JFrame("AnimationRendererTest");
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+    jframe.setContentPane((JPanel) dpys.getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+    ScalarMap xmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    dpys.addMap(xmap);
+    ScalarMap ymap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    dpys.addMap(ymap);
+    ScalarMap zmap = new ScalarMap(RealType.Altitude, Display.ZAxis);
+    dpys.addMap(zmap);
+    ScalarMap rgbaMap = new ScalarMap(radiance, Display.RGBA);
+    //-ScalarMap rgbaMap = new ScalarMap(RealType.Altitude, Display.RGBA);
+    dpys.addMap(rgbaMap);
+    ScalarMap amap = new ScalarMap(index, Display.Animation);
+    dpys.addMap(amap);
+  
+    ScalarMap map1contour = new ScalarMap(radiance, Display.IsoContour);
+    dpys.addMap(map1contour);
+    ContourControl ctr_cntrl = (ContourControl) map1contour.getControl();
+    ctr_cntrl.setSurfaceValue(24f);
+
+    AnimationControl acontrol = (AnimationControl) amap.getControl();
+    acontrol.setOn(false);
+    acontrol.setStep(1000);
+
+    DataReferenceImpl ref = new DataReferenceImpl("field_ref");
+    ref.setData(field);
+    AnimationRendererJ3D renderer = new AnimationRendererJ3D();
+    renderer.setReUseFrames(true);
+    if (test.equals("old")) {
+      dpys.addReference(ref);
+    }
+    else {
+      dpys.addReferences(renderer, new DataReferenceImpl[] {ref}, null);
+    }
+
+    System.out.println("replace test in 40 sec...");
+    new Delay(50000);
+
+    Linear1DSet new_set = new Linear1DSet(index, 1, 6, 6);
+    FieldImpl new_field = new FieldImpl(field_type, new_set);
+    for (int i=0; i<field_domain_set.getLength(); i++) {
+      new_field.setSample(i,
+        field.getSample((i + 1) % field_domain_set.getLength()));
+    }
+
+    if (test.equals("old")) {
+      dpys.reAutoScale();
+    }
+    System.out.println("replace test start");
+    ref.setData(new_field);
+  }
+}
diff --git a/visad/java3d/DefaultDisplayRendererJ3D.java b/visad/java3d/DefaultDisplayRendererJ3D.java
new file mode 100644
index 0000000..818b049
--- /dev/null
+++ b/visad/java3d/DefaultDisplayRendererJ3D.java
@@ -0,0 +1,327 @@
+//
+// DefaultDisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import java.lang.reflect.Constructor;
+
+import javax.media.j3d.AmbientLight;
+import javax.media.j3d.Appearance;
+import javax.media.j3d.BoundingSphere;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.ColoringAttributes;
+import javax.media.j3d.DirectionalLight;
+import javax.media.j3d.GeometryArray;
+import javax.media.j3d.Light;
+import javax.media.j3d.LineArray;
+import javax.media.j3d.LineAttributes;
+import javax.media.j3d.Shape3D;
+import javax.media.j3d.TransformGroup;
+import javax.media.j3d.View;
+import javax.vecmath.Color3f;
+import javax.vecmath.Point3d;
+import javax.vecmath.Vector3f;
+
+import visad.VisADError;
+
+/**
+ * <CODE>DefaultDisplayRendererJ3D</CODE> is the VisAD class for the
+ * default background and metadata rendering algorithm under Java3D.<P>
+ */
+public class DefaultDisplayRendererJ3D extends DisplayRendererJ3D {
+
+  private Object not_destroyed = new Object();
+
+  /** color of box and cursor */
+  private ColoringAttributes box_color = null;
+  private ColoringAttributes cursor_color = null;
+
+  /** line of box and cursor */
+  private LineAttributes box_line = null;
+  private LineAttributes cursor_line = null;
+
+  private Class mouseBehaviorJ3DClass = null;
+
+  private MouseBehaviorJ3D mouse = null; // Behavior for mouse interactions
+
+  /**
+   * This is the default <CODE>DisplayRenderer</CODE> used by the
+   * <CODE>DisplayImplJ3D</CODE> constructor.
+   * It draws a 3-D cube around the scene.<P>
+   * The left mouse button controls the projection as follows:
+   * <UL>
+   *  <LI>mouse drag rotates in 3-D
+   *  <LI>mouse drag with Shift down zooms the scene
+   *  <LI>mouse drag with Ctrl translates the scene sideways
+   * </UL>
+   * The center mouse button activates and controls the
+   * 3-D cursor as follows:
+   * <UL>
+   *  <LI>mouse drag translates the cursor sideways
+   *  <LI>mouse drag with Shift translates the cursor in and out
+   *  <LI>mouse drag with Ctrl rotates scene in 3-D with cursor on
+   * </UL>
+   * The right mouse button is used for direct manipulation by clicking on
+   * the depiction of a <CODE>Data</CODE> object and dragging or re-drawing
+   * it.<P>
+   * Cursor and direct manipulation locations are displayed in RealType
+   * values.<P>
+   * <CODE>BadMappingExceptions</CODE> and
+   * <CODE>UnimplementedExceptions</CODE> are displayed<P>
+   */
+  public DefaultDisplayRendererJ3D () {
+    super();
+    mouseBehaviorJ3DClass = MouseBehaviorJ3D.class;
+  }
+
+  /**
+   * @param mbj3dClass - sub Class of MouseBehaviorJ3D
+   */
+  
+  public DefaultDisplayRendererJ3D (Class mbj3dClass) {
+    super();
+    mouseBehaviorJ3DClass = mbj3dClass;
+  }
+
+  public void destroy() {
+    not_destroyed = null;
+    box_color = null;
+    cursor_color = null;
+    mouse = null; 
+    super.destroy();
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root;
+   * create 3-D box, lights and <CODE>MouseBehaviorJ3D</CODE> for
+   * embedded user interface.
+   * @param v
+   * @param vpt
+   * @param c
+   * @return Scene graph root.
+   */
+  public BranchGroup createSceneGraph(View v, TransformGroup vpt,
+                                      VisADCanvasJ3D c) {
+    if (not_destroyed == null) return null;
+    BranchGroup root = getRoot();
+    if (root != null) return root;
+
+    // create MouseBehaviorJ3D for mouse interactions
+    try {
+      Class[] param = new Class[] {DisplayRendererJ3D.class};
+      Constructor mbConstructor =
+        mouseBehaviorJ3DClass.getConstructor(param);
+      mouse = (MouseBehaviorJ3D) mbConstructor.newInstance(new Object[] {this});
+    }
+    catch (Exception e) {
+      throw new VisADError("cannot construct " + mouseBehaviorJ3DClass);
+    }
+    // mouse = new MouseBehaviorJ3D(this);
+
+    getDisplay().setMouseBehavior(mouse);
+    box_color = new ColoringAttributes();
+    cursor_color = new ColoringAttributes();
+    root = createBasicSceneGraph(v, vpt, c, mouse, box_color, cursor_color);
+    TransformGroup trans = getTrans();
+
+    // create the box containing data depictions
+    LineArray box_geometry = new LineArray(24, LineArray.COORDINATES);
+
+    // WLH 24 Nov 2000
+    box_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE);
+    box_geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // box_geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+
+    box_geometry.setCoordinates(0, box_verts);
+    Appearance box_appearance = new Appearance();
+    box_appearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
+    box_appearance.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
+    box_appearance.setCapability(Appearance.ALLOW_MATERIAL_READ);
+    box_appearance.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_READ);
+    box_appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
+    box_appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
+    box_appearance.setCapability(Appearance.ALLOW_TEXGEN_READ);
+    box_appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
+    box_appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+    // box_appearance.setCapability(Appearance.ALLOW_TEXTURE_UNIT_STATE_READ);
+    box_appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    box_line = new LineAttributes();
+    box_line.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
+    box_appearance.setLineAttributes(box_line);
+
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    float[] ctlBox = getRendererControl().getBoxColor();
+    box_color.setColor(ctlBox[0], ctlBox[1], ctlBox[2]);
+    box_appearance.setColoringAttributes(box_color);
+    Shape3D box = new Shape3D(box_geometry, box_appearance);
+    box.setCapability(Shape3D.ALLOW_GEOMETRY_READ); // WLH 24 Nov 2000
+    box.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    box.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
+    BranchGroup box_on = getBoxOnBranch();
+    box_on.addChild(box);
+
+    Appearance cursor_appearance = new Appearance();
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    cursor_line = new LineAttributes();
+    cursor_line.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
+    cursor_appearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_MATERIAL_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_TEXGEN_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+    // cursor_appearance.setCapability(Appearance.ALLOW_TEXTURE_UNIT_STATE_READ);
+    cursor_appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    cursor_appearance.setLineAttributes(cursor_line);
+
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    float[] ctlCursor = getRendererControl().getCursorColor();
+    cursor_color.setColor(ctlCursor[0], ctlCursor[1], ctlCursor[2]);
+    cursor_appearance.setColoringAttributes(cursor_color);
+
+    BranchGroup cursor_on = getCursorOnBranch();
+    LineArray cursor_geometry = new LineArray(6, LineArray.COORDINATES);
+    cursor_geometry.setCoordinates(0, cursor_verts);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // cursor_geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+    Shape3D cursor = new Shape3D(cursor_geometry, cursor_appearance);
+    cursor.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    cursor.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    cursor_on.addChild(cursor);
+
+    // insert MouseBehaviorJ3D into scene graph
+    BoundingSphere bounds =
+      new BoundingSphere(new Point3d(0.0,0.0,0.0), 2000000.0);
+    mouse.setSchedulingBounds(bounds);
+    trans.addChild(mouse);
+
+    // create ambient light, directly under root (not transformed)
+    Color3f color = new Color3f(0.6f, 0.6f, 0.6f);
+    AmbientLight light = new AmbientLight(color);
+    light.setCapability(Light.ALLOW_COLOR_READ);
+    light.setCapability(Light.ALLOW_INFLUENCING_BOUNDS_READ);
+    light.setCapability(Light.ALLOW_SCOPE_READ);
+    light.setCapability(Light.ALLOW_STATE_READ);
+    light.setInfluencingBounds(bounds);
+    root.addChild(light);
+
+    // create directional lights, directly under root (not transformed)
+    Color3f dcolor = new Color3f(0.9f, 0.9f, 0.9f);
+    Vector3f direction1 = new Vector3f(0.0f, 0.0f, 1.0f);
+    Vector3f direction2 = new Vector3f(0.0f, 0.0f, -1.0f);
+    DirectionalLight light1 =
+      new DirectionalLight(true, dcolor, direction1);
+    light1.setCapability(DirectionalLight.ALLOW_DIRECTION_READ);
+    light1.setCapability(Light.ALLOW_COLOR_READ);
+    light1.setCapability(Light.ALLOW_INFLUENCING_BOUNDS_READ);
+    light1.setCapability(Light.ALLOW_SCOPE_READ);
+    light1.setCapability(Light.ALLOW_STATE_READ);
+    light1.setInfluencingBounds(bounds);
+    DirectionalLight light2 =
+      new DirectionalLight(true, dcolor, direction2);
+    light2.setCapability(DirectionalLight.ALLOW_DIRECTION_READ);
+    light2.setCapability(Light.ALLOW_COLOR_READ);
+    light2.setCapability(Light.ALLOW_INFLUENCING_BOUNDS_READ);
+    light2.setCapability(Light.ALLOW_SCOPE_READ);
+    light2.setCapability(Light.ALLOW_STATE_READ);
+    light2.setInfluencingBounds(bounds);
+    root.addChild(light1);
+    root.addChild(light2);
+
+    return root;
+  }
+
+  /**
+   * set the aspect for the containing box
+   * aspect double[3] array used to scale x, y and z box sizes
+   */
+  public void setBoxAspect(double[] aspect) {
+    if (not_destroyed == null) return;
+    float[] new_verts = new float[box_verts.length];
+    for (int i=0; i<box_verts.length; i+=3) {
+      new_verts[i] = (float) (box_verts[i] * aspect[0]);
+      new_verts[i+1] = (float) (box_verts[i+1] * aspect[1]);
+      new_verts[i+2] = (float) (box_verts[i+2] * aspect[2]);
+    }
+    BranchGroup box_on = getBoxOnBranch();
+    Shape3D box = (Shape3D) box_on.getChild(0);
+    LineArray box_geometry = (LineArray) box.getGeometry();
+    box_geometry.setCoordinates(0, new_verts);
+  }
+
+  // WLH 2 Dec 2002 in response to qomo2.txt
+  public void setLineWidth(float width) {
+    box_line.setLineWidth(width);
+    cursor_line.setLineWidth(width);
+  }
+
+  private static final float[] box_verts = {
+     // front face
+         -1.0f, -1.0f,  1.0f,                       -1.0f,  1.0f,  1.0f,
+         -1.0f,  1.0f,  1.0f,                        1.0f,  1.0f,  1.0f,
+          1.0f,  1.0f,  1.0f,                        1.0f, -1.0f,  1.0f,
+          1.0f, -1.0f,  1.0f,                       -1.0f, -1.0f,  1.0f,
+     // back face
+         -1.0f, -1.0f, -1.0f,                       -1.0f,  1.0f, -1.0f,
+         -1.0f,  1.0f, -1.0f,                        1.0f,  1.0f, -1.0f,
+          1.0f,  1.0f, -1.0f,                        1.0f, -1.0f, -1.0f,
+          1.0f, -1.0f, -1.0f,                       -1.0f, -1.0f, -1.0f,
+     // connectors
+         -1.0f, -1.0f,  1.0f,                       -1.0f, -1.0f, -1.0f,
+         -1.0f,  1.0f,  1.0f,                       -1.0f,  1.0f, -1.0f,
+          1.0f,  1.0f,  1.0f,                        1.0f,  1.0f, -1.0f,
+          1.0f, -1.0f,  1.0f,                        1.0f, -1.0f, -1.0f
+  };
+
+  private static final float[] cursor_verts = {
+          0.0f,  0.0f,  0.1f,                        0.0f,  0.0f, -0.1f,
+          0.0f,  0.1f,  0.0f,                        0.0f, -0.1f,  0.0f,
+          0.1f,  0.0f,  0.0f,                       -0.1f,  0.0f,  0.0f
+  };
+
+}
+
diff --git a/visad/java3d/DefaultRendererJ3D.java b/visad/java3d/DefaultRendererJ3D.java
new file mode 100644
index 0000000..7ab9a83
--- /dev/null
+++ b/visad/java3d/DefaultRendererJ3D.java
@@ -0,0 +1,268 @@
+//
+// DefaultRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+import java.rmi.RemoteException;
+
+import javax.media.j3d.BranchGroup;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.AnimationControl;
+import visad.ContourControl;
+import visad.Data;
+import visad.DataDisplayLink;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayException;
+import visad.DisplayImpl;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.Integer3DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.VisADException;
+
+
+/**
+   DefaultRendererJ3D is the VisAD class for the default graphics
+   rendering algorithm under Java3D.<P>
+*/
+public class DefaultRendererJ3D extends RendererJ3D {
+
+  DataDisplayLink link = null;
+
+  /** this is the default DataRenderer used by the addReference method
+      for DisplayImplJ3D */
+  public DefaultRendererJ3D () {
+    super();
+  }
+
+  public void setLinks(DataDisplayLink[] links, DisplayImpl d)
+       throws VisADException {
+    if (links == null || links.length != 1) {
+      throw new DisplayException("DefaultRendererJ3D.setLinks: must be " +
+                                 "exactly one DataDisplayLink");
+    }
+    super.setLinks(links, d);
+    link = links[0];
+  }
+
+  /** create a BranchGroup scene graph for Data in links[0] */
+  public BranchGroup doTransform() throws VisADException, RemoteException {
+    if (link == null) return null;
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND); // BMF
+    ShadowTypeJ3D type = (ShadowTypeJ3D) link.getShadow();
+
+
+    /** TDR, if a scalarMap to Animation, make make the Data's
+        node live, ie add it to Display via setBranchEarly. */
+    boolean isAnimation = false;
+    java.util.Vector scalarMaps = link.getSelectedMapVector();
+    for (int kk = 0; kk < scalarMaps.size(); kk++) {
+      ScalarMap scalarMap = (ScalarMap) scalarMaps.elementAt(kk);
+      if ( (scalarMap.getDisplayScalar()).equals(Display.Animation) ) {
+              isAnimation = true;
+      }
+    }
+    if (isAnimation) setBranchEarly(branch);
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return null;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      addException(
+        new DisplayException("Data is null: DefaultRendererJ3D.doTransform"));
+    }
+    else {
+      link.start_time = System.currentTimeMillis();
+      link.time_flag = false;
+      type.preProcess();
+      boolean post_process;
+      try {
+        post_process =
+          type.doTransform(branch, data, valueArray,
+                           link.getDefaultValues(), this);
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+      if (post_process) type.postProcess(branch);
+    }
+    link.clearData();
+    return branch;
+  }
+
+  public void addSwitch(DisplayRendererJ3D displayRenderer,
+                        BranchGroup branch) {
+    displayRenderer.addSceneGraphComponent(branch);
+  }
+
+  public DataDisplayLink getLink() {
+    return link;
+  }
+
+  public void clearScene() {
+    link = null;
+    super.clearScene();
+  }
+
+  public Object clone() throws CloneNotSupportedException {
+    return new DefaultRendererJ3D();
+  }
+
+  public static void main(String args[]) throws VisADException,
+      RemoteException, IOException {
+
+    String test = "new";
+    if (args.length > 0) {
+      test = args[0];
+      if (!test.equals("default")) {
+        System.out.println("args: 'default' for Default logic, None for Animation");
+        System.exit(0);
+      }
+    }
+
+    int size = 160;
+    int nr = size;
+    int nc = size;
+    int nz = size;
+    double ang = 2 * Math.PI / nr;
+
+    RealType[] types = { 
+        RealType.Latitude, 
+        RealType.Longitude,
+        RealType.Altitude 
+    };
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType radiance = RealType.getRealType("radiance", null, null);
+    RealType index = RealType.getRealType("index", null, null);
+    FunctionType image_type = new FunctionType(earth_location, radiance);
+
+    Integer3DSet image_domain_set = new Integer3DSet(
+        RealTupleType.SpatialCartesian3DTuple, nr, nc, nz);
+    FunctionType field_type = new FunctionType(index, image_type);
+    Integer1DSet field_domain_set = new Integer1DSet(index, 6);
+    FieldImpl field = new FieldImpl(field_type, field_domain_set);
+
+    FlatField image = null;
+    for (int tt = 0; tt < field_domain_set.getLength(); tt++) {
+      float[][] values = new float[1][nr * nc * nz];
+      for (int kk = 0; kk < nz; kk++) {
+        for (int jj = 0; jj < nc; jj++) {
+          for (int ii = 0; ii < nr; ii++) {
+            int idx = kk * nr * nc + jj * nr + ii;
+            values[0][idx] = 
+              (tt + 1)*(2f*((float) Math.sin(2*ang*ii)) + 
+                  2f*((float) Math.sin(2 * ang * jj))) + kk;
+          }
+        }
+      }
+      image = new FlatField(image_type, image_domain_set);
+      image.setSamples(values);
+      field.setSample(tt, image, false);
+    }
+
+    DisplayImplJ3D dpys = new DisplayImplJ3D("AnimationRendererJ3D Test");
+
+    JFrame jframe = new JFrame("AnimationRendererTest");
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    jframe.setContentPane((JPanel) dpys.getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+    
+    ScalarMap xmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    dpys.addMap(xmap);
+    
+    ScalarMap ymap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    dpys.addMap(ymap);
+    
+    ScalarMap zmap = new ScalarMap(RealType.Altitude, Display.ZAxis);
+    dpys.addMap(zmap);
+    
+    ScalarMap rgbaMap = new ScalarMap(radiance, Display.RGBA);
+    dpys.addMap(rgbaMap);
+    
+    ScalarMap amap = new ScalarMap(index, Display.Animation);
+    dpys.addMap(amap);
+
+    ScalarMap map1contour = new ScalarMap(radiance, Display.IsoContour);
+    dpys.addMap(map1contour);
+    ContourControl ctr_cntrl = (ContourControl) map1contour.getControl();
+    ctr_cntrl.setSurfaceValue(24f);
+
+    AnimationControl acontrol = (AnimationControl) amap.getControl();
+    //acontrol.setOn(true);
+    acontrol.setStep(500);
+
+    DataReferenceImpl ref = new DataReferenceImpl("field_ref");
+
+    if (test.equals("default")) {
+      ref.setData(image);
+    } else {
+      ref.setData(field);
+    }
+    
+    dpys.addReference(ref);
+  }
+  
+}
+
diff --git a/visad/java3d/DirectManipulationRendererJ3D.java b/visad/java3d/DirectManipulationRendererJ3D.java
new file mode 100644
index 0000000..e22ae2f
--- /dev/null
+++ b/visad/java3d/DirectManipulationRendererJ3D.java
@@ -0,0 +1,210 @@
+//
+// DirectManipulationRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+
+import java.rmi.*;
+
+
+/**
+   DirectManipulationRendererJ3D is the VisAD class for direct
+   manipulation rendering under Java3D.<P>
+*/
+public class DirectManipulationRendererJ3D extends RendererJ3D {
+
+  BranchGroup branch = null;
+
+  /** this DataRenderer supports direct manipulation for Real,
+      RealTuple and Field Data objects (Field data objects must
+      have RealType or RealTupleType ranges and Gridded1DSet
+      domain Sets); no RealType may be mapped to multiple spatial
+      DisplayRealTypes; the RealType of a Real object must be
+      mapped to XAxis, YAxis or YAxis; at least one of the
+      RealType components of a RealTuple object must be mapped
+      to XAxis, YAxis or YAxis; the domain RealType and at
+      least one RealType range component of a Field object
+      must be mapped to XAxis, YAxis or ZAxis
+*/
+  public DirectManipulationRendererJ3D () {
+    super();
+  }
+
+  public void setLinks(DataDisplayLink[] links, DisplayImpl d)
+       throws VisADException {
+    if (links == null || links.length != 1) {
+      throw new DisplayException("DirectManipulationRendererJ3D.setLinks: " +
+                                 "must be exactly one DataDisplayLink");
+    }
+    super.setLinks(links, d);
+  }
+
+  public void checkDirect() throws VisADException, RemoteException {
+    realCheckDirect();
+  }
+
+  public void addPoint(float[] x) throws VisADException {
+    if (branch == null) return;
+    int count = x.length / 3;
+    VisADGeometryArray array = null;
+    if (count == 1) {
+      array = new VisADPointArray();
+    }
+    else if (count == 2) {
+      array = new VisADLineArray();
+    }
+    else {
+      return;
+    }
+    array.coordinates = x;
+    array.vertexCount = count;
+    DisplayImplJ3D display = (DisplayImplJ3D) getDisplay();
+    if (display == null) return;
+    GeometryArray geometry = display.makeGeometry(array);
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return;
+    }
+    DataDisplayLink link = Links[0];
+
+    float[] default_values = link.getDefaultValues();
+    GraphicsModeControl mode = (GraphicsModeControl)
+      display.getGraphicsModeControl().clone();
+    float pointSize =
+      default_values[display.getDisplayScalarIndex(Display.PointSize)];
+    float lineWidth =
+      default_values[display.getDisplayScalarIndex(Display.LineWidth)];
+    int lineStyle = (int)
+      default_values[display.getDisplayScalarIndex(Display.LineStyle)];
+    mode.setPointSize(pointSize, true);
+    mode.setLineWidth(lineWidth, true);
+    mode.setLineStyle(lineStyle, true);
+    Appearance appearance =
+      ShadowTypeJ3D.staticMakeAppearance(mode, null, null, geometry, false);
+
+    Shape3D shape = new Shape3D(geometry, appearance);
+    shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    BranchGroup group = new BranchGroup();
+    group.setCapability(Group.ALLOW_CHILDREN_READ);
+    group.addChild(shape);
+    branch.addChild(group);
+  }
+
+  /** create a BranchGroup scene graph for Data in links[0] */
+  public synchronized BranchGroup doTransform()
+         throws VisADException, RemoteException {
+    branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(Group.ALLOW_CHILDREN_READ);
+    branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+
+    DataDisplayLink[] Links = getLinks();
+    if (Links == null || Links.length == 0) {
+      return null;
+    }
+    DataDisplayLink link = Links[0];
+
+    // values needed by drag_direct, which cannot throw Exceptions
+    ShadowTypeJ3D shadow = (ShadowTypeJ3D) link.getShadow();
+
+    // check type and maps for valid direct manipulation
+    if (!getIsDirectManipulation()) {
+      throw new BadDirectManipulationException(getWhyNotDirect() +
+        ": DirectManipulationRendererJ3D.doTransform");
+    }
+
+    // initialize valueArray to missing
+    int valueArrayLength = getDisplay().getValueArrayLength();
+    float[] valueArray = new float[valueArrayLength];
+    for (int i=0; i<valueArrayLength; i++) {
+      valueArray[i] = Float.NaN;
+    }
+
+    Data data;
+    try {
+      data = link.getData();
+    } catch (RemoteException re) {
+      if (visad.collab.CollabUtil.isDisconnectException(re)) {
+        getDisplay().connectionFailed(this, link);
+        removeLink(link);
+        return null;
+      }
+      throw re;
+    }
+
+    if (data == null) {
+      branch = null;
+      addException(
+        new DisplayException("Data is null: DirectManipulationRendererJ3D." +
+                             "doTransform"));
+    }
+    else {
+      try {
+        // no preProcess or postProcess for direct manipulation */
+        shadow.doTransform(branch, data, valueArray,
+                           link.getDefaultValues(), this);
+      } catch (RemoteException re) {
+        if (visad.collab.CollabUtil.isDisconnectException(re)) {
+          getDisplay().connectionFailed(this, link);
+          removeLink(link);
+          return null;
+        }
+        throw re;
+      }
+    }
+    return branch;
+  }
+
+  /** for use by sub-classes that override doTransform() */
+  public void setBranch(BranchGroup b) {
+    branch = b;
+  }
+
+  void addSwitch(DisplayRendererJ3D displayRenderer, BranchGroup branch) {
+    displayRenderer.addDirectManipulationSceneGraphComponent(branch, this);
+  }
+
+  public boolean isLegalTextureMap() {
+    return false;
+  }
+
+  public void clearScene() {
+    branch = null;
+    super.clearScene();
+  }
+
+  public Object clone() {
+    return new DirectManipulationRendererJ3D();
+  }
+
+}
+
diff --git a/visad/java3d/DisplayAppletJ3D.java b/visad/java3d/DisplayAppletJ3D.java
new file mode 100644
index 0000000..13dc882
--- /dev/null
+++ b/visad/java3d/DisplayAppletJ3D.java
@@ -0,0 +1,67 @@
+//
+// DisplayAppletJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import java.applet.Applet;
+import java.awt.*;
+import java.awt.BorderLayout;
+
+import javax.media.j3d.*;
+
+public class DisplayAppletJ3D extends Applet {
+
+  private DisplayImplJ3D display;
+  private DisplayRendererJ3D renderer;
+  private UniverseBuilderJ3D universe;
+
+  public DisplayAppletJ3D(DisplayImplJ3D d) {
+    this(d, null);
+  }
+
+  public DisplayAppletJ3D(DisplayImplJ3D d, GraphicsConfiguration config) {
+    display = d;
+    renderer = (DisplayRendererJ3D) display.getDisplayRenderer();
+    setLayout(new BorderLayout());
+    VisADCanvasJ3D canvas = new VisADCanvasJ3D(renderer, config);
+    canvas.setComponent(this);
+    add("Center", canvas);
+
+    UniverseBuilderJ3D universe = new UniverseBuilderJ3D(canvas);
+    BranchGroup scene =
+      renderer.createSceneGraph(universe.view, universe.vpTrans, canvas);
+    universe.addBranchGraph(scene);
+  }
+
+  // WLH 17 Dec 2001
+  public void destroy() {
+    display = null;
+    renderer = null;
+    if (universe != null) universe.destroy();
+  }
+
+}
+
diff --git a/visad/java3d/DisplayImplJ3D.java b/visad/java3d/DisplayImplJ3D.java
new file mode 100644
index 0000000..3ec75c9
--- /dev/null
+++ b/visad/java3d/DisplayImplJ3D.java
@@ -0,0 +1,762 @@
+//
+// DisplayImplJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+MEMORY LEAK
+
+DisplayImplJ3D.destrory()
+  DisplayRendererJ3D.destroy()
+    VisADCanvasJ3D.stop()
+      stopRenderer()
+      DisplayPanelJ3D.destroy()
+        display, renderer = null;
+        universe.destroy()
+      DisplayAppletJ3D.destroy()
+        display, renderer = null;
+        universe.destroy()
+      display, component = null
+    MouseBehaviorJ3D.destroy()
+      helper, display, display_renderer = null
+    root.detach()
+    root, trans, vpTrans, non_direct, view, canvas = null
+  DisplayImpl.destroy()
+    stop()
+      removeThingChangedListener() for all links
+      LinkVector.removeAllElements()
+      pool.queue(this)
+      run_links = null
+    DisplayActivity.destroy()
+    notify listeners
+    remove all listeners
+    clearMaps()
+      check RendererVector empty
+      MapVector.removeAllElements()
+      ConstantMapVector.removeAllElements()
+      RealTypeVector.removeAllElements()
+    AnimationControlJ3D.stop()
+      animationThread = null // causes run() to exit
+  applet, projection, mode = null;
+
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.rmi.*;
+
+import java.awt.*;
+
+import javax.media.j3d.*;
+import com.sun.j3d.utils.applet.MainFrame;
+// import com.sun.j3d.utils.applet.AppletFrame;
+
+/**
+   DisplayImplJ3D is the VisAD class for displays that use
+   Java 3D.  It is runnable.<P>
+
+   DisplayImplJ3D is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public class DisplayImplJ3D extends DisplayImpl {
+
+  /** distance behind for surfaces in 2-D mode */
+  // WLH 25 March 2003 (at BOM)
+  // public static final float BACK2D = -2.0f;
+  public static final float BACK2D = -0.01f;
+
+  /**
+   * Use a parallel projection view
+   * @see GraphicsModeControlJ3D#setProjectionPolicy
+   */
+  public static final int PARALLEL_PROJECTION =
+    javax.media.j3d.View.PARALLEL_PROJECTION;
+
+  /**
+   * Use a perspective projection view. This is the default.
+   * @see GraphicsModeControlJ3D#setProjectionPolicy
+   */
+  public static final int PERSPECTIVE_PROJECTION =
+    javax.media.j3d.View.PERSPECTIVE_PROJECTION;
+
+  /** Render polygonal primitives by filling the interior of the polygon
+      @see GraphicsModeControlJ3D#setPolygonMode */
+  public static final int POLYGON_FILL =
+    javax.media.j3d.PolygonAttributes.POLYGON_FILL;
+
+  /**
+   * Render polygonal primitives as lines drawn between consecutive vertices
+   * of the polygon.
+   * @see GraphicsModeControlJ3D#setPolygonMode
+   */
+  public static final int POLYGON_LINE =
+    javax.media.j3d.PolygonAttributes.POLYGON_LINE;
+
+  /**
+   * Render polygonal primitives as points drawn at the vertices of
+   * the polygon.
+   * @see GraphicsModeControlJ3D#setPolygonMode
+   */
+  public static final int POLYGON_POINT =
+    javax.media.j3d.PolygonAttributes.POLYGON_POINT;
+
+  /**
+   * Use the nicest available method for transparency.
+   * @see GraphicsModeControlJ3D#setTransparencyMode
+   */
+  public static final int NICEST =
+    javax.media.j3d.TransparencyAttributes.NICEST;
+
+  /**
+   * Use the fastest available method for transparency.
+   * @see GraphicsModeControlJ3D#setTransparencyMode
+   */
+  public static final int FASTEST =
+    javax.media.j3d.TransparencyAttributes.FASTEST;
+
+  /** Field for specifying unknown API type */
+  public static final int UNKNOWN = 0;
+  /** Field for specifying that the DisplayImpl be created in a JPanel */
+  public static final int JPANEL = 1;
+  /** Field for specifying that the DisplayImpl does not have a screen Component */
+  public static final int OFFSCREEN = 2;
+  /** Field for specifying that the DisplayImpl be created in an Applet */
+  public static final int APPLETFRAME = 3;
+  /** Field for specifying that the DisplayImpl transforms but does not render */
+  public static final int TRANSFORM_ONLY = 4;
+
+  /** 
+   * Property name for setting whether to use geometry by reference.
+   * @see #GEOMETRY_BY_REF
+   */
+  public static final String PROP_GEOMETRY_BY_REF = "visad.java3d.geometryByRef";
+  /**
+   * Indicates whether to use geometry by reference when creating geometry arrays.
+   * @see javax.media.j3d.GeometryArray#BY_REFERENCE
+   */
+  public static final boolean GEOMETRY_BY_REF;
+  static {
+    GEOMETRY_BY_REF = Boolean.parseBoolean(System.getProperty(PROP_GEOMETRY_BY_REF, "true"));
+  }
+
+  /**
+   * Property name for enabling the use of non-power of two textures.
+   * @see #TEXTURE_NPOT
+   */
+  public static final String PROP_TEXTURE_NPOT = "visad.java3d.textureNpot";
+
+  /**
+   * Indicates whether to allow non-power of two textures. This has been known
+   * to cause some issues with Apple 32bit Macs eventhough the Canvas3D
+   * properties indicate that NPOT is supported.
+   * @see javax.media.j3d.Canvas3D#queryProperties()
+   */
+  // FIXME:
+  // This works with the Java3D 1.5.2 example TextureImageNPOT but does not work
+  // with the VisAD library image rednering. On initial testing it behaves as if
+  // there may be threading issues.  This requires more investigation before we
+  // can enable this based on the Canvas3D properties.
+  public static final boolean TEXTURE_NPOT;
+  static {
+    TEXTURE_NPOT = Boolean.parseBoolean(System.getProperty(PROP_TEXTURE_NPOT, "false"));
+    //System.err.println("TEXTURE_NPOT:"+TEXTURE_NPOT);
+  }
+
+  /** this is used for APPLETFRAME */
+  private DisplayAppletJ3D applet = null;
+
+  private ProjectionControlJ3D projection = null;
+  private GraphicsModeControlJ3D mode = null;
+  private int apiValue = UNKNOWN;
+
+  private UniverseBuilderJ3D universe = null;
+
+  /** construct a DisplayImpl for Java3D with the
+      default DisplayRenderer, in a JFC JPanel */
+  public DisplayImplJ3D(String name)
+         throws VisADException, RemoteException {
+    this(name, null, JPANEL, null);
+  }
+
+  /** construct a DisplayImpl for Java3D with a non-default
+      DisplayRenderer, in a JFC JPanel */
+  public DisplayImplJ3D(String name, DisplayRendererJ3D renderer)
+         throws VisADException, RemoteException {
+    this(name, renderer, JPANEL, null);
+  }
+
+  /** constructor with default DisplayRenderer */
+  public DisplayImplJ3D(String name, int api)
+         throws VisADException, RemoteException {
+    this(name, null, api, null);
+  }
+
+  /** construct a DisplayImpl for Java3D with a non-default
+      GraphicsConfiguration, in a JFC JPanel */
+  public DisplayImplJ3D(String name, GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(name, null, JPANEL, config);
+  }
+
+  /** construct a DisplayImpl for Java3D with a non-default
+      DisplayRenderer;
+      in a JFC JPanel if api == DisplayImplJ3D.JPANEL and
+      in an AppletFrame if api == DisplayImplJ3D.APPLETFRAME */
+  public DisplayImplJ3D(String name, DisplayRendererJ3D renderer, int api)
+         throws VisADException, RemoteException {
+    this(name, renderer, api, null);
+  }
+
+  /** construct a DisplayImpl for Java3D with a non-default
+      DisplayRenderer and GraphicsConfiguration, in a JFC JPanel */
+  public DisplayImplJ3D(String name, DisplayRendererJ3D renderer,
+                        GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(name, renderer, JPANEL, config);
+  }
+
+  /** constructor with default DisplayRenderer and a non-default
+      GraphicsConfiguration */
+  public DisplayImplJ3D(String name, int api, GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(name, null, api, config);
+  }
+
+  public DisplayImplJ3D(String name, DisplayRendererJ3D renderer, int api,
+                        GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(name, renderer, api, config, null);
+  }
+
+  /** the 'c' argument is intended to be an extension class of
+      VisADCanvasJ3D (or null); if it is non-null, then api must
+      be JPANEL and its super() constructor for VisADCanvasJ3D
+      must be 'super(renderer, config)' */
+  public DisplayImplJ3D(String name, DisplayRendererJ3D renderer, int api,
+                        GraphicsConfiguration config, VisADCanvasJ3D c)
+         throws VisADException, RemoteException {
+    super(name, renderer);
+
+    initialize(api, config, c);
+  }
+
+  /** constructor for off screen */
+  public DisplayImplJ3D(String name, int width, int height)
+         throws VisADException, RemoteException {
+    this(name, null, width, height);
+  }
+
+  /** constructor for off screen */
+  public DisplayImplJ3D(String name, DisplayRendererJ3D renderer,
+                        int width, int height)
+         throws VisADException, RemoteException {
+    this(name, renderer, width, height, null);
+  }
+
+  /** constructor for off screen;
+      the 'c' argument is intended to be an extension class of
+      VisADCanvasJ3D (or null); if it is non-null, then its super()
+      constructor for VisADCanvasJ3D must be
+      'super(renderer, width, height)' */
+  public DisplayImplJ3D(String name, DisplayRendererJ3D renderer,
+                        int width, int height, VisADCanvasJ3D c)
+         throws VisADException, RemoteException {
+    super(name, renderer);
+
+    initialize(OFFSCREEN, null, width, height, c);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy)
+         throws VisADException, RemoteException {
+    this(rmtDpy, null, rmtDpy.getDisplayAPI(), null);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, DisplayRendererJ3D renderer)
+         throws VisADException, RemoteException {
+    this(rmtDpy, renderer, rmtDpy.getDisplayAPI(), null);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, int api)
+         throws VisADException, RemoteException {
+    this(rmtDpy, null, api, null);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(rmtDpy, null, rmtDpy.getDisplayAPI(), config);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, DisplayRendererJ3D renderer,
+			int api)
+         throws VisADException, RemoteException {
+    this(rmtDpy, renderer, api, null);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, DisplayRendererJ3D renderer,
+                        GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(rmtDpy, renderer, rmtDpy.getDisplayAPI(), config);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, int api,
+                        GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(rmtDpy, null, api, config);
+  }
+
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, DisplayRendererJ3D renderer,
+                        int api, GraphicsConfiguration config)
+         throws VisADException, RemoteException {
+    this(rmtDpy, renderer, api, config, null);
+  }
+
+  /** the 'c' argument is intended to be an extension class of
+      VisADCanvasJ3D (or null); if it is non-null, then api must
+      be JPANEL and its super() constructor for VisADCanvasJ3D
+      must be 'super(renderer, config)' */
+  public DisplayImplJ3D(RemoteDisplay rmtDpy, DisplayRendererJ3D renderer,
+                        int api, GraphicsConfiguration config,
+                        VisADCanvasJ3D c)
+         throws VisADException, RemoteException {
+    super(rmtDpy,
+          ((renderer == null && api == TRANSFORM_ONLY) ?
+             new TransformOnlyDisplayRendererJ3D() : renderer));
+
+    // use this for testing cluster = true in ordinary collab programs
+    // super(rmtDpy,
+    //       ((renderer == null && api == TRANSFORM_ONLY) ?
+    //          new TransformOnlyDisplayRendererJ3D() : renderer), true);
+
+    initialize(api, config, c);
+
+    syncRemoteData(rmtDpy);
+  }
+
+  private void initialize(int api, GraphicsConfiguration config)
+          throws VisADException, RemoteException {
+    initialize(api, config, -1, -1, null);
+  }
+
+  private void initialize(int api, GraphicsConfiguration config,
+                          VisADCanvasJ3D c)
+          throws VisADException, RemoteException {
+    initialize(api, config, -1, -1, c);
+  }
+
+  private void initialize(int api, GraphicsConfiguration config,
+                          int width, int height)
+          throws VisADException, RemoteException {
+    initialize(api, config, width, height, null);
+  }
+
+  private void initialize(int api, GraphicsConfiguration config,
+                          int width, int height, VisADCanvasJ3D c)
+          throws VisADException, RemoteException {
+    // a ProjectionControl always exists
+    projection = new ProjectionControlJ3D(this);
+    addControl(projection);
+
+    if (api == APPLETFRAME) {
+      applet = new DisplayAppletJ3D(this, config);
+      Component component = new MainFrame(applet, 256, 256);
+      // Component component = new AppletFrame(applet, 256, 256);
+      setComponent(component);
+      // component.setTitle(name);
+      apiValue = api;
+    }
+    else if (api == JPANEL) {
+      Component component = new DisplayPanelJ3D(this, config, c);
+      setComponent(component);
+      apiValue = api;
+    }
+    else if (api == TRANSFORM_ONLY) {
+      if (!(getDisplayRenderer() instanceof TransformOnlyDisplayRendererJ3D)) {
+        throw new DisplayException("must be TransformOnlyDisplayRendererJ3D " +
+                                   "for api = TRANSFORM_ONLY");
+      }
+      setComponent(null);
+      apiValue = api;
+    }
+    else if (api == OFFSCREEN) {
+      DisplayRendererJ3D renderer = (DisplayRendererJ3D) getDisplayRenderer();
+      VisADCanvasJ3D canvas = (c != null) ? c :
+                              new VisADCanvasJ3D(renderer, width, height);
+      universe = new UniverseBuilderJ3D(canvas);
+      BranchGroup scene =
+        renderer.createSceneGraph(universe.view, universe.vpTrans, canvas);
+      universe.addBranchGraph(scene);
+
+      setComponent(null);
+      apiValue = api;
+    }
+    else {
+      throw new DisplayException("DisplayImplJ3D: bad graphics API " + api);
+    }
+    if (api != TRANSFORM_ONLY) {
+      // initialize projection and set Display in Canvas
+      projection.setAspect(new double[] {1.0, 1.0, 1.0});
+      ((DisplayRendererJ3D) getDisplayRenderer()).getCanvas().setDisplay();
+    }
+
+    // a GraphicsModeControl always exists
+    mode = new GraphicsModeControlJ3D(this);
+    addControl(mode);
+  }
+
+  /** return a DefaultDisplayRendererJ3D */
+  protected DisplayRenderer getDefaultDisplayRenderer() {
+    return new DefaultDisplayRendererJ3D();
+  }
+
+  public void setScreenAspect(double height, double width) {
+    DisplayRendererJ3D dr = (DisplayRendererJ3D) getDisplayRenderer();
+    Screen3D screen = dr.getCanvas().getScreen3D();
+    screen.setPhysicalScreenHeight(height);
+    screen.setPhysicalScreenWidth(width);
+  }
+
+  /**
+   * Get the projection control associated with this display
+   * @see ProjectionControlJ3D
+   *
+   * @return  this display's projection control
+   */
+  public ProjectionControl getProjectionControl() {
+    return projection;
+  }
+
+  /**
+   * Get the graphics mode control associated with this display
+   * @see GraphicsModeControlJ3D
+   *
+   * @return  this display's graphics mode control
+   */
+  public GraphicsModeControl getGraphicsModeControl() {
+    return mode;
+  }
+
+  /**
+   * Return the applet associated with this display
+   *
+   * @return the applet or null if API != APPLETFRAME
+   */
+  public DisplayAppletJ3D getApplet() {
+    return applet;
+  }
+
+  /**
+   * Return the API used for this display
+   *
+   * @return  the mode being used (UNKNOWN, JPANEL, APPLETFRAME,
+   *                               OFFSCREEN, TRANSFORM_ONLY)
+   * @throws  VisADException
+   */
+  public int getAPI()
+	throws VisADException
+  {
+    return apiValue;
+  }
+
+  private void setGeometryCapabilities(GeometryArray array) {
+    array.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    array.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    array.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    array.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    array.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    array.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+    /* TDR (2013-10-12): Should only be used in conjunction with GeometryArray.updateData 
+       which is not currently implemented
+    array.setCapability(GeometryArray.ALLOW_REF_DATA_WRITE);
+    */
+
+    // only used when using BY_REFERENCE, so just set it anyways
+    //array.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+  }
+  
+  public GeometryArray makeGeometry(VisADGeometryArray vga) throws VisADException {
+    if (vga == null) return null;
+    
+    boolean mode2d = getDisplayRenderer().getMode2D();
+    int vertexFormat = makeFormat(vga);
+
+    if (vga instanceof VisADIndexedTriangleStripArray) {
+      /* this is the 'normal' makeGeometry */
+      VisADIndexedTriangleStripArray vgb = (VisADIndexedTriangleStripArray) vga;
+      if (vga.vertexCount == 0) return null;
+      IndexedTriangleStripArray array =
+        new IndexedTriangleStripArray(vga.vertexCount, vertexFormat,
+                                      vgb.indexCount, vgb.stripVertexCounts);
+     
+      setGeometryCapabilities(array);
+     
+      basicGeometry(vga, array, mode2d);
+      if (vga.coordinates != null) {
+        array.setCoordinateIndices(0, vgb.indices);
+      }
+      if (vga.colors != null) {
+        array.setColorIndices(0, vgb.indices);
+      }
+      if (vga.normals != null) {
+        array.setNormalIndices(0, vgb.indices);
+      }
+      if (vga.texCoords != null) {
+        array.setTextureCoordinateIndices(0, vgb.indices);
+      }
+      return array;
+
+/* this expands indices
+      if (vga.vertexCount == 0) return null;
+      //
+      // expand vga.coordinates, vga.colors, vga.normals and vga.texCoords
+      //
+      int count = vga.indices.length;
+      int len = 3 * count;
+
+      int sum = 0;
+      for (int i=0; i<vga.stripVertexCounts.length; i++) sum += vga.stripVertexCounts[i];
+      System.out.println("vga.indexCount = " + vga.indexCount + " sum = " + sum +
+                         " count = " + count + " vga.stripVertexCounts.length = " +
+                         vga.stripVertexCounts.length);
+      // int[] strip_counts = new int[1];
+      // strip_counts[0] = count;
+      // TriangleStripArray array =
+      //   new TriangleStripArray(count, makeFormat(vga), strip_counts);
+
+      TriangleStripArray array =
+        new TriangleStripArray(count, makeFormat(vga), vga.stripVertexCounts);
+
+      if (vga.coordinates != null) {
+        System.out.println("expand vga.coordinates");
+        float[] coords = new float[len];
+        for (int k=0; k<count; k++) {
+          int i = 3 * k;
+          int j = 3 * vga.indices[k];
+          coords[i] = vga.coordinates[j];
+          coords[i + 1] = vga.coordinates[j + 1];
+          coords[i + 2] = vga.coordinates[j + 2];
+        }
+        array.setCoordinates(0, coords);
+      }
+      if (vga.colors != null) {
+        System.out.println("expand vga.colors");
+        byte[] cols = new float[len];
+        for (int k=0; k<count; k++) {
+          int i = 3 * k;
+          int j = 3 * vga.indices[k];
+          cols[i] = vga.colors[j];
+          cols[i + 1] = vga.colors[j + 1];
+          cols[i + 2] = vga.colors[j + 2];
+        }
+        array.setColors(0, cols);
+      }
+      if (vga.normals != null) {
+        System.out.println("expand vga.normals");
+        float[] norms = new float[len];
+        for (int k=0; k<count; k++) {
+          int i = 3 * k;
+          int j = 3 * vga.indices[k];
+          norms[i] = vga.normals[j];
+          norms[i + 1] = vga.normals[j + 1];
+          norms[i + 2] = vga.normals[j + 2];
+        }
+        array.setNormals(0, norms);
+      }
+      if (vga.texCoords != null) {
+        System.out.println("expand vga.texCoords");
+        float[] tex = new float[len];
+        for (int k=0; k<count; k++) {
+          int i = 3 * k;
+          int j = 3 * vga.indices[k];
+          tex[i] = vga.texCoords[j];
+          tex[i + 1] = vga.texCoords[j + 1];
+          tex[i + 2] = vga.texCoords[j + 2];
+        }
+        array.setTextureCoordinates(0, tex);
+      }
+      return array;
+*/
+
+/* this draws normal vectors
+      if (vga.vertexCount == 0) return null;
+      LineArray array = new LineArray(2 * vga.vertexCount, LineArray.COORDINATES);
+      float[] new_coords = new float[6 * vga.vertexCount];
+      int i = 0;
+      int j = 0;
+      for (int k=0; k<vga.vertexCount; k++) {
+        new_coords[j] = vga.coordinates[i];
+        new_coords[j+1] = vga.coordinates[i+1];
+        new_coords[j+2] = vga.coordinates[i+2];
+        j += 3;
+        new_coords[j] = vga.coordinates[i] + 0.05f * vga.normals[i];
+        new_coords[j+1] = vga.coordinates[i+1] + 0.05f * vga.normals[i+1];
+        new_coords[j+2] = vga.coordinates[i+2] + 0.05f * vga.normals[i+2];
+        i += 3;
+        j += 3;
+      }
+      array.setCoordinates(0, new_coords);
+      return array;
+*/
+
+/* this draws the 'dots'
+      if (vga.vertexCount == 0) return null;
+      PointArray array =
+        new PointArray(vga.vga.vertexCount, makeFormat(vga));
+      basicGeometry(vga, array, false);
+      return array;
+*/
+    }
+    if (vga instanceof VisADTriangleStripArray) {
+      VisADTriangleStripArray vgb = (VisADTriangleStripArray) vga;
+      if (vga.vertexCount == 0) return null;
+      TriangleStripArray array =
+        new TriangleStripArray(vga.vertexCount, vertexFormat, vgb.stripVertexCounts);
+      setGeometryCapabilities(array);
+      basicGeometry(vga, array, mode2d);
+      return array;
+    }
+    else if (vga instanceof VisADLineArray) {
+      if (vga.vertexCount == 0) return null;
+      LineArray array = new LineArray(vga.vertexCount, vertexFormat);
+      setGeometryCapabilities(array);
+      basicGeometry(vga, array, false);
+      return array;
+    }
+    else if (vga instanceof VisADLineStripArray) {
+      if (vga.vertexCount == 0) return null;
+      VisADLineStripArray vgb = (VisADLineStripArray) vga;
+      LineStripArray array =
+        new LineStripArray(vga.vertexCount, vertexFormat, vgb.stripVertexCounts);
+      setGeometryCapabilities(array);
+      basicGeometry(vga, array, false);
+      return array;
+    }
+    else if (vga instanceof VisADPointArray) {
+      if (vga.vertexCount == 0) return null;
+      PointArray array = new PointArray(vga.vertexCount, vertexFormat);
+      setGeometryCapabilities(array);
+      basicGeometry(vga, array, false);
+      return array;
+    }
+    else if (vga instanceof VisADTriangleArray) {
+      if (vga.vertexCount == 0) return null;
+      TriangleArray array = new TriangleArray(vga.vertexCount, vertexFormat);
+      setGeometryCapabilities(array);
+      basicGeometry(vga, array, mode2d);
+      return array;
+    }
+    else if (vga instanceof VisADQuadArray) {
+      if (vga.vertexCount == 0) return null;
+      QuadArray array = new QuadArray(vga.vertexCount, vertexFormat);
+      setGeometryCapabilities(array);
+      basicGeometry(vga, array, mode2d);
+      return array;
+    }
+    else {
+      throw new DisplayException("DisplayImplJ3D.makeGeometry");
+    }
+  }
+  
+  private void basicGeometry(VisADGeometryArray vga, GeometryArray array, boolean mode2d) {
+    
+    if (mode2d) {
+      if (vga.coordinates != null) {
+        int len = vga.coordinates.length;
+        float[] coords = new float[len];
+        System.arraycopy(vga.coordinates, 0, coords, 0, len);
+        for (int i=2; i<len; i+=3) coords[i] = BACK2D;
+        if (GEOMETRY_BY_REF) array.setCoordRefFloat(coords);
+        else array.setCoordinates(0, coords);
+      }
+    }
+    else {
+      if (vga.coordinates != null) {
+        if (GEOMETRY_BY_REF) array.setCoordRefFloat(vga.coordinates);
+        else array.setCoordinates(0, vga.coordinates);
+      }
+    }
+    if (vga.colors != null) {
+      if (GEOMETRY_BY_REF) array.setColorRefByte(vga.colors);
+      else array.setColors(0, vga.colors);
+    }
+    if (vga.normals != null) {
+      if (GEOMETRY_BY_REF) array.setNormalRefFloat(vga.normals);
+      else array.setNormals(0, vga.normals);
+    }
+    if (vga.texCoords != null) {
+      if (GEOMETRY_BY_REF) array.setTexCoordRefFloat(0, vga.texCoords);
+      else array.setTextureCoordinates(0, vga.texCoords);
+    }
+  }
+  
+  private static int makeFormat(VisADGeometryArray vga) {
+    int format = 0;
+    if (vga.coordinates != null) format |= GeometryArray.COORDINATES;
+    if (vga.colors != null) {
+      if (vga.colors.length == 3 * vga.vertexCount) {
+        format |= GeometryArray.COLOR_3;
+      }
+      else {
+        format |= GeometryArray.COLOR_4;
+      }
+    }
+    if (vga.normals != null) format |= GeometryArray.NORMALS;
+    if (vga.texCoords != null) {
+      if (vga.texCoords.length == 2 * vga.vertexCount) {
+        format |= GeometryArray.TEXTURE_COORDINATE_2;
+      }
+      else {
+        format |= GeometryArray.TEXTURE_COORDINATE_3;
+      }
+    }
+    if (GEOMETRY_BY_REF) format |= GeometryArray.BY_REFERENCE;
+    return format;
+  }
+
+  public void destroyUniverse() {
+    if (universe != null) universe.destroy();
+    universe = null;
+  }
+
+
+  public void destroy() throws VisADException, RemoteException {
+    if(isDestroyed())return;
+
+    ((DisplayRendererJ3D) getDisplayRenderer()).destroy();
+    if (apiValue == OFFSCREEN) {
+      destroyUniverse();
+    }
+    MouseBehavior mouse =  getMouseBehavior();
+    if(mouse!=null && mouse instanceof MouseBehaviorJ3D) {
+        ((MouseBehaviorJ3D) mouse).destroy();
+    }
+    super.destroy();
+    applet = null;
+    projection = null;
+    mode = null;
+  }
+
+}
+
diff --git a/visad/java3d/DisplayPanelJ3D.java b/visad/java3d/DisplayPanelJ3D.java
new file mode 100644
index 0000000..74f6b0f
--- /dev/null
+++ b/visad/java3d/DisplayPanelJ3D.java
@@ -0,0 +1,85 @@
+//
+// DisplayPanelJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+// GUI handling
+import java.awt.*;
+import javax.swing.*;
+
+import javax.media.j3d.*;
+
+public class DisplayPanelJ3D extends JPanel {
+
+  private DisplayImplJ3D display;
+  private DisplayRendererJ3D renderer;
+  private UniverseBuilderJ3D universe;
+  private VisADCanvasJ3D canvas;
+
+  public DisplayPanelJ3D(DisplayImplJ3D d) {
+    this(d, null, null);
+  }
+
+  public DisplayPanelJ3D(DisplayImplJ3D d, GraphicsConfiguration config,
+                         VisADCanvasJ3D c) {
+    display = d;
+    renderer = (DisplayRendererJ3D) display.getDisplayRenderer();
+    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+/* WLH 26 March 99
+    setAlignmentY(TOP_ALIGNMENT);
+    setAlignmentX(LEFT_ALIGNMENT);
+*/
+    canvas = (c != null) ? c : new VisADCanvasJ3D(renderer, config);
+    canvas.setComponent(this);
+    add(canvas);
+
+    universe = new UniverseBuilderJ3D(canvas);
+    BranchGroup scene =
+      renderer.createSceneGraph(universe.view, universe.vpTrans, canvas);
+    universe.addBranchGraph(scene);
+
+    setPreferredSize(new java.awt.Dimension(256, 256));
+    setMinimumSize(new java.awt.Dimension(0, 0));
+  }
+
+  public void setVisible(boolean v){
+    super.setVisible(v);
+    canvas.setVisible(v);
+  }
+
+  // WLH 17 Dec 2001
+  public void destroy() {
+    canvas = null;
+    display = null;
+    renderer = null;
+    if (universe != null) {
+      universe.destroy();
+      universe = null;
+    }
+  }
+
+}
+
diff --git a/visad/java3d/DisplayRendererJ3D.java b/visad/java3d/DisplayRendererJ3D.java
new file mode 100644
index 0000000..b5cb52c
--- /dev/null
+++ b/visad/java3d/DisplayRendererJ3D.java
@@ -0,0 +1,1648 @@
+//
+// DisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.media.j3d.Appearance;
+import javax.media.j3d.Background;
+import javax.media.j3d.BoundingSphere;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Canvas3D;
+import javax.media.j3d.ColoringAttributes;
+import javax.media.j3d.GeometryArray;
+import javax.media.j3d.GraphicsContext3D;
+import javax.media.j3d.Group;
+import javax.media.j3d.Node;
+import javax.media.j3d.OrderedGroup;
+import javax.media.j3d.PolygonAttributes;
+import javax.media.j3d.SceneGraphObject;
+import javax.media.j3d.Shape3D;
+import javax.media.j3d.Switch;
+import javax.media.j3d.Transform3D;
+import javax.media.j3d.TransformGroup;
+import javax.media.j3d.View;
+import javax.vecmath.Color3f;
+import javax.vecmath.Point3d;
+import javax.vecmath.Vector3f;
+import javax.vecmath.Vector4d;
+
+import visad.AxisScale;
+import visad.ColorAlphaControl;
+import visad.ColorControl;
+import visad.ContourControl;
+import visad.Control;
+import visad.ControlEvent;
+import visad.DataRenderer;
+import visad.Display;
+import visad.DisplayException;
+import visad.DisplayImpl;
+import visad.DisplayRealType;
+import visad.DisplayRenderer;
+import visad.Flow1Control;
+import visad.Flow2Control;
+import visad.GraphicsModeControl;
+import visad.MouseBehavior;
+import visad.KeyboardBehavior;
+import visad.PlotText;
+import visad.ProjectionControl;
+import visad.RangeControl;
+import visad.RealType;
+import visad.RendererControl;
+import visad.RendererSourceListener;
+import visad.ScalarMap;
+import visad.ShapeControl;
+import visad.TextControl;
+import visad.VisADException;
+import visad.VisADLineArray;
+import visad.VisADRay;
+import visad.VisADTriangleArray;
+import visad.util.Util;
+
+/**
+ * <CODE>DisplayRendererJ3D</CODE> is the VisAD abstract super-class for
+ * background and metadata rendering algorithms.  These complement
+ * depictions of <CODE>Data</CODE> objects created by
+ * <CODE>DataRenderer</CODE> objects.<P>
+ *
+ * <CODE>DisplayRendererJ3D</CODE> also manages the overall relation of
+ * <CODE>DataRenderer</CODE> output to Java3D and manages the scene graph.<P>
+ *
+ * It creates the binding between <CODE>Control</CODE> objects and scene
+ * graph <CODE>Behavior</CODE> objects for direct manipulation of
+ * <CODE>Control</CODE> objects.<P>
+ *
+ * <CODE>DisplayRendererJ3D</CODE> is not <CODE>Serializable</CODE> and
+ * should not be copied between JVMs.<P>
+*/
+public abstract class DisplayRendererJ3D
+  extends DisplayRenderer
+  implements RendererSourceListener
+{
+
+  /**
+   * Set the name of a <code>SceneGraphObject</code>.
+   * If <code>SceneGraphObject</code> does not have a <code>setName</code>
+   * (J3D pre v1.4) this is a no-op.
+   * @param name
+   */
+  public static void setSceneGraphObjectName(SceneGraphObject obj, String name) {
+    Util.setName(obj, name);
+  }
+
+  private Object not_destroyed = new Object();
+
+  // for screen locked
+  private OrderedGroup screen_locked = null;
+  private TransformGroup locked_trans = null;
+
+  /** View associated with this VirtualUniverse */
+  private View view;
+  /** VisADCanvasJ3D associated with this VirtualUniverse */
+  private VisADCanvasJ3D canvas;
+
+  /** root BranchGroup of scene graph under Locale */
+  private BranchGroup root = null;
+  /** single TransformGroup between root and BranchGroups for all
+   *  Data depictions */
+  private TransformGroup trans = null;
+  /** BranchGroup between trans and all direct manipulation
+   *  Data depictions */
+  // WLH 13 March 2000
+  // private BranchGroup direct = null;
+
+  // WLH 10 March 2000
+  private OrderedGroup non_direct = null;
+
+  /** TransformGroup for ViewPlatform */
+  private TransformGroup vpTrans = null;
+
+  /** MouseBehaviorJ3D */
+  private MouseBehaviorJ3D mouse = null;
+
+  /** KeyboardBehaviorJ3D */
+  private KeyboardBehaviorJ3D keyboard = null;
+
+  /** color of box and cursor */
+  private ColoringAttributes box_color = null;
+  private ColoringAttributes cursor_color = null;
+
+  /** background attached to root */
+  private Background background = null;
+
+  /** TransformGroup between trans and cursor */
+  private TransformGroup cursor_trans = null;
+  /** single Switch between cursor_trans and cursor */
+  private Switch cursor_switch = null;
+  /** children of cursor_switch */
+  private BranchGroup cursor_on = null, cursor_off = null;
+  /** on / off state of cursor */
+  private boolean cursorOn = false;
+  /** on / off state of direct manipulation location display */
+  private boolean directOn = false;
+
+  /** single Switch between trans and box */
+  private Switch box_switch = null;
+  /** children of box_switch */
+  private BranchGroup box_on = null, box_off = null;
+  /** on / off state of box */
+  private boolean boxOn = false;
+
+  /** single Switch between trans and scales */
+  private Switch scale_switch = null;
+  /** children of scale_switch */
+  private BranchGroup scale_on = null, scale_off = null;
+  /** Vector of screen based AxisScales */
+  private Vector axis_vector = new Vector();
+
+  /** on / off state of cursor in GraphicsModeControl */
+
+  /** Vector of DirectManipulationRenderers */
+  private Vector directs = new Vector();
+
+  /** cursor location */
+  private float cursorX, cursorY, cursorZ;
+  /** normalized direction perpendicular to current cursor plane */
+  private float line_x, line_y, line_z;
+  /** start value for cursor */
+  private float point_x, point_y, point_z;
+
+  /** ModelClip stuff, done by reflection */
+  private Method modelClipSetEnable = null;
+  private Method modelClipSetPlane = null;
+  private Method modelClipAddScope = null;
+  private Object modelClip = null;
+  private boolean[] modelClipEnables =
+    {false, false, false, false, false, false};
+
+  public DisplayRendererJ3D () {
+    super();
+  }
+
+  // WLH 17 Dec 2001
+  public void destroy() {
+    not_destroyed = null;
+
+    if (canvas != null) canvas.stop();
+    if (mouse != null) mouse.destroy();
+    if (root != null) {
+      root.detach();
+      root = null;
+    }
+
+    axis_vector.removeAllElements();
+    directs.removeAllElements();
+
+    screen_locked = null;
+    locked_trans = null;
+
+    trans = null;
+    vpTrans = null;
+    non_direct = null;
+    view = null;
+    canvas = null;
+    mouse = null;
+    box_color = null;
+    cursor_color = null;
+    background = null;
+    cursor_trans = null;
+    cursor_switch = null;
+    cursor_on = null; 
+    cursor_off = null;
+    box_switch = null;
+    box_on = null; 
+    box_off = null;
+    scale_switch = null;
+    scale_on = null; 
+    scale_off = null;
+  }
+
+  /**
+   * Specify <CODE>DisplayImpl</CODE> to be rendered.
+   * @param dpy <CODE>Display</CODE> to render.
+   * @exception VisADException If a <CODE>DisplayImpl</CODE> has already
+   *                           been specified.
+   */
+  public void setDisplay(DisplayImpl dpy)
+    throws VisADException
+  {
+    if (not_destroyed == null) return;
+    super.setDisplay(dpy);
+    dpy.addRendererSourceListener(this);
+    boxOn = getRendererControl().getBoxOn();
+  }
+
+  public View getView() {
+    return view;
+  }
+
+  public TransformGroup getViewTrans() {
+    return vpTrans;
+  }
+
+  /**
+   * Get the canvas for this renderer
+   * @return  <CODE>VisADCanvasJ3D</CODE> that this renderer uses.
+   */
+  public VisADCanvasJ3D getCanvas() {
+    return canvas;
+  }
+
+  /**
+   * Capture the display rendition as an image.
+   * @return  image of the display.
+   */
+  
+  public BufferedImage getImage() {
+    if (not_destroyed == null) return null;
+    BufferedImage image = null;
+    canvas.captureImage = null;
+    ProjectionControl proj = getDisplay().getProjectionControl();
+    double[] matrix= proj.getMatrix();
+    while (image == null) {
+      try {
+        synchronized (this) {
+          canvas.setDoubleBufferEnable(false);
+          canvas.captureFlag = true;
+          hasNotifyBeenCalled = false;
+          if (canvas.getOffscreen()) {
+              try {
+              Method renderMethod =
+                Canvas3D.class.getMethod("renderOffScreenBuffer",
+                                         new Class[] {});
+              renderMethod.invoke(canvas, new Object[] {});
+              /*        Method waitMethod =
+                Canvas3D.class.getMethod("waitForOffScreenRendering", new Class[] {});
+                waitMethod.invoke(canvas, new Object[] {});*/
+            }
+            catch (NoSuchMethodException e) {}
+            catch (IllegalAccessException e) {}
+            catch (InvocationTargetException e) {}
+          }
+          try {
+              proj.setMatrix(matrix);
+          } catch (RemoteException e) { 
+              e.printStackTrace();
+          } catch (VisADException e) { 
+              e.printStackTrace();
+          }
+          //Make sure the notify has not been called. There is the possbility that the above renderOffScreenBuffer call
+          //gets completed before we get to this wait, resulting in a starvation lockup here because the canvas already 
+          //notifies the display renderer and when we get to the wait nothing is going to notify this object.
+          image = canvas.captureImage;
+          if(image == null && !hasNotifyBeenCalled) {
+              waitingOnImageCapture = true;
+              wait();
+              waitingOnImageCapture = false;
+          } 
+        }
+      } catch(InterruptedException e) {
+        // note notify generates a normal return from wait rather
+        // than an Exception - control doesn't normally come here
+        canvas.setDoubleBufferEnable(true); //- just in case
+        e.printStackTrace();
+      }
+      if(image==null) {
+          image = canvas.captureImage;
+      }
+      canvas.captureImage = null;
+      canvas.setDoubleBufferEnable(true);
+      if(image == null) {
+          //What do we do here?
+          //          break?;
+      }
+    }
+    return image;
+  }
+
+  /** used for doing offscreen capture to prevent a starvation lockup */
+  private boolean hasNotifyBeenCalled = false;
+
+  /** used for doing offscreen capture to prevent the extra notify */
+  private boolean waitingOnImageCapture = false;
+
+  void notifyCapture() {
+      hasNotifyBeenCalled = true;
+      if(waitingOnImageCapture) {
+         waitingOnImageCapture = false;
+         synchronized (this) {
+             notify(); 
+         }
+      } else {
+      }
+      //    }
+  }
+
+  public BranchGroup getRoot() {
+    return root;
+  }
+
+  /**
+   * Internal method used to initialize newly created
+   * <CODE>RendererControl</CODE> with current renderer settings
+   * before it is actually connected to the renderer.  This
+   * means that changes will not generate <CODE>MonitorEvent</CODE>s.
+   * @param ctl RendererControl to initialize
+   */
+  public void initControl(RendererControl ctl)
+  {
+    if (not_destroyed == null) return;
+    Color3f c3f = new Color3f();
+
+    // initialize box colors
+    if (box_color != null) {
+      box_color.getColor(c3f);
+      try {
+        ctl.setBoxColor(c3f.x, c3f.y, c3f.z);
+      } catch (Throwable t) {
+        // ignore any initialization problems
+      }
+    }
+
+    // initialize cursor colors
+    if (cursor_color != null) {
+      cursor_color.getColor(c3f);
+      try {
+        ctl.setCursorColor(c3f.x, c3f.y, c3f.z);
+      } catch (Throwable t) {
+        // ignore any initialization problems
+      }
+    }
+
+    // initialize background colors
+    if (background != null) {
+      background.getColor(c3f);
+      try {
+        ctl.setBackgroundColor(c3f.x, c3f.y, c3f.z);
+      } catch (Throwable t) {
+        // ignore any initialization problems
+      }
+    }
+
+    // initialize box visibility
+    try {
+      ctl.setBoxOn(boxOn);
+    } catch (Throwable t) {
+      // ignore any initialization problems
+    }
+  }
+
+  /**
+   * Update internal values from those in the <CODE>RendererControl</CODE>.
+   * @param evt <CODE>ControlEvent</CODE> generated by a change to the
+   *            <CODE>RendererControl</CODE>
+   */
+  public void controlChanged(ControlEvent evt)
+  {
+    if (not_destroyed == null) return;
+    RendererControl ctl = (RendererControl )evt.getControl();
+
+    float[] ct;
+    Color3f c3f = new Color3f();
+
+    // update box colors
+    if (box_color != null) {
+      ct = ctl.getBoxColor();
+      box_color.getColor(c3f);
+      if (!Util.isApproximatelyEqual(ct[0], c3f.x) ||
+          !Util.isApproximatelyEqual(ct[1], c3f.y) ||
+          !Util.isApproximatelyEqual(ct[2], c3f.z))
+      {
+        box_color.setColor(ct[0], ct[1], ct[2]);
+      }
+    }
+
+    // update cursor colors
+    if (cursor_color != null) {
+      ct = ctl.getCursorColor();
+      cursor_color.getColor(c3f);
+      if (!Util.isApproximatelyEqual(ct[0], c3f.x) ||
+          !Util.isApproximatelyEqual(ct[1], c3f.y) ||
+          !Util.isApproximatelyEqual(ct[2], c3f.z))
+      {
+        cursor_color.setColor(ct[0], ct[1], ct[2]);
+      }
+    }
+
+    // update background colors
+    ct = ctl.getBackgroundColor();
+    background.getColor(c3f);
+    if (!Util.isApproximatelyEqual(ct[0], c3f.x) ||
+        !Util.isApproximatelyEqual(ct[1], c3f.y) ||
+        !Util.isApproximatelyEqual(ct[2], c3f.z))
+    {
+      background.setColor(ct[0], ct[1], ct[2]);
+    }
+
+    // update box visibility
+    boolean on = ctl.getBoxOn();
+    if (on != boxOn) {
+      boxOn = on;
+      box_switch.setWhichChild(boxOn ? 1 : 0);
+    }
+  }
+
+  public TransformGroup getTrans() {
+    return trans;
+  }
+
+  public BranchGroup getCursorOnBranch() {
+    return cursor_on;
+  }
+
+  public BranchGroup getBoxOnBranch() {
+    return box_on;
+  }
+
+  /**
+   * Toggle the cursor in the display
+   * @param  on   true to display the cursor, false to hide it.
+   */
+  public void setCursorOn(boolean on) {
+    if (not_destroyed == null) return;
+    cursorOn = on;
+    if (on) {
+      cursor_switch.setWhichChild(1); // set cursor on
+      setCursorStringVector();
+    }
+    else {
+      cursor_switch.setWhichChild(0); // set cursor off
+      setCursorStringVector(null);
+    }
+  }
+
+  /**
+   * Set the flag for direct manipulation
+   * @param  on  true for enabling direct manipulation, false to disable
+   */
+  public void setDirectOn(boolean on) {
+    if (not_destroyed == null) return;
+    directOn = on;
+    if (!on) {
+      setCursorStringVector(null);
+    }
+  }
+
+/* WLH 13 March 2000
+  public BranchGroup getDirect() {
+    return direct;
+  }
+*/
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root;
+   * create special graphics (e.g., 3-D box, SkewT background),
+   * any lights, any user interface embedded in scene.
+   * @param v
+   * @param vpt
+   * @param c
+   * @return Scene graph root.
+   */
+  public abstract BranchGroup createSceneGraph(View v, TransformGroup vpt,
+                                               VisADCanvasJ3D c);
+
+  /** @deprecated use createBasicSceneGraph(View v, TransformGroup vpt,
+         VisADCanvasJ3D c, MouseBehaviorJ3D m, ColoringAttributes bc,
+         ColoringAttributes cc)
+      instead */
+  public BranchGroup createBasicSceneGraph(View v, TransformGroup vpt,
+         VisADCanvasJ3D c, MouseBehaviorJ3D m) {
+    if (not_destroyed == null) return null;
+    box_color = new ColoringAttributes();
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    cursor_color = new ColoringAttributes();
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    return createBasicSceneGraph(v, vpt, c, m, box_color, cursor_color);
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root.
+   * @param v
+   * @param vpt
+   * @param c
+   * @param m
+   * @return Scene graph root.
+   */
+  public BranchGroup createBasicSceneGraph(View v, TransformGroup vpt,
+         VisADCanvasJ3D c, MouseBehaviorJ3D m, ColoringAttributes bc,
+         ColoringAttributes cc) {
+    if (root != null) return root;
+    if (not_destroyed == null) return null;
+
+    mouse = m;
+    view = v;
+    vpTrans = vpt;
+    box_color = bc;
+    cursor_color = cc;
+
+    // WLH 14 April 98
+    v.setDepthBufferFreezeTransparent(false);
+    canvas = c;
+    // Create the root of the branch graph
+    root = new BranchGroup();
+    setSceneGraphObjectName(root, "Root");
+    root.setCapability(BranchGroup.ALLOW_DETACH);
+    root.setCapability(Group.ALLOW_CHILDREN_READ);
+    root.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    root.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    // create the TransformGroup that is the parent of
+    // Data object Group objects
+    setTransform3D(null);
+    root.addChild(trans);
+
+    // create background
+    background = new Background();
+    setSceneGraphObjectName(background, "Background");
+    background.setCapability(Background.ALLOW_COLOR_WRITE);
+    background.setCapability(Background.ALLOW_COLOR_READ);
+    float[] ctlBg = getRendererControl().getBackgroundColor();
+    background.setColor(ctlBg[0], ctlBg[1], ctlBg[2]);
+    BoundingSphere bound2 = new BoundingSphere(new Point3d(0.0,0.0,0.0),2000000.0);
+    background.setApplicationBounds(bound2);
+    root.addChild(background);
+
+/* WLH 13 April 99 - does nothing
+    BoundingBox boundingbox =
+      new BoundingBox(new Point3d(-1.0, -1.0, -1.0),
+                      new Point3d(1.0, 1.0, 1.0));
+    trans.addChild(new BoundingLeaf(boundingbox));
+*/
+
+/* WLH 13 Macrh 2000
+    // create the BranchGroup that is the parent of direct
+    // manipulation Data object BranchGroup objects
+    direct = new BranchGroup();
+    direct.setCapability(Group.ALLOW_CHILDREN_READ);
+    direct.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    direct.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    direct.setCapability(Node.ENABLE_PICK_REPORTING);
+    trans.addChild(direct);
+*/
+
+    // WLH 10 March 2000
+    non_direct = new OrderedGroup();
+    setSceneGraphObjectName(non_direct, "NonDirect");
+    non_direct.setCapability(Group.ALLOW_CHILDREN_READ);
+    non_direct.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    non_direct.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    non_direct.setCapability(Node.ENABLE_PICK_REPORTING);
+    trans.addChild(non_direct);
+
+    cursor_trans = new TransformGroup();
+    setSceneGraphObjectName(cursor_trans, "CursorTrans");
+    cursor_trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
+    cursor_trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
+    cursor_trans.setCapability(Group.ALLOW_CHILDREN_READ);
+    cursor_trans.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    cursor_trans.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    trans.addChild(cursor_trans);
+    cursor_switch = new Switch();
+    setSceneGraphObjectName(cursor_switch, "CursorSwitch");
+    cursor_switch.setCapability(Switch.ALLOW_SWITCH_READ);
+    cursor_switch.setCapability(Switch.ALLOW_SWITCH_WRITE);
+    cursor_switch.setCapability(Group.ALLOW_CHILDREN_READ);
+    cursor_trans.addChild(cursor_switch);
+    cursor_on = new BranchGroup();
+    setSceneGraphObjectName(cursor_on, "CursorOn");
+    cursor_on.setCapability(Group.ALLOW_CHILDREN_READ);
+    cursor_on.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    cursor_off = new BranchGroup();
+    setSceneGraphObjectName(cursor_off, "CursorOff");
+    cursor_off.setCapability(Group.ALLOW_CHILDREN_READ);
+    cursor_switch.addChild(cursor_off);
+    cursor_switch.addChild(cursor_on);
+    cursor_switch.setWhichChild(0); // initially off
+    cursorOn = false;
+
+    box_switch = new Switch();
+    setSceneGraphObjectName(box_switch, "BoxSwitch");
+    box_switch.setCapability(Switch.ALLOW_SWITCH_READ);
+    box_switch.setCapability(Switch.ALLOW_SWITCH_WRITE);
+    box_switch.setCapability(Group.ALLOW_CHILDREN_READ);
+    trans.addChild(box_switch);
+    box_on = new BranchGroup();
+    setSceneGraphObjectName(box_on, "BoxOn");
+    box_on.setCapability(Group.ALLOW_CHILDREN_READ);
+    box_on.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    box_off = new BranchGroup();
+    setSceneGraphObjectName(box_off, "BoxOff");
+    box_off.setCapability(Group.ALLOW_CHILDREN_READ);
+    box_switch.addChild(box_off);
+    box_switch.addChild(box_on);
+    box_switch.setWhichChild(1); // initially on
+    try {
+      setBoxOn(true);
+    } catch (Exception e) {
+    }
+
+    scale_switch = new Switch();
+    setSceneGraphObjectName(scale_switch, "ScaleSwitch");
+    scale_switch.setCapability(Switch.ALLOW_SWITCH_READ);
+    scale_switch.setCapability(Switch.ALLOW_SWITCH_WRITE);
+    scale_switch.setCapability(Group.ALLOW_CHILDREN_READ);
+    trans.addChild(scale_switch);
+    scale_on = new BranchGroup();
+    setSceneGraphObjectName(scale_on, "ScaleOn");
+    scale_on.setCapability(Group.ALLOW_CHILDREN_READ);
+    scale_on.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    scale_on.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    scale_off = new BranchGroup();
+    setSceneGraphObjectName(scale_off, "ScaleOff");
+    scale_off.setCapability(Group.ALLOW_CHILDREN_READ);
+    scale_switch.addChild(scale_off);
+    scale_switch.addChild(scale_on);
+    scale_switch.setWhichChild(0); // initially off
+
+    // WLH 23 Oct 2001
+    try {
+      Class modelClipClass = Class.forName("javax.media.j3d.ModelClip");
+      Class[] param = new Class[] {};
+      Constructor modelClipConstructor = modelClipClass.getConstructor(param);
+      param = new Class[] {int.class, boolean.class};
+      modelClipSetEnable = modelClipClass.getMethod("setEnable", param);
+      param = new Class[] {int.class, javax.vecmath.Vector4d.class};
+      modelClipSetPlane = modelClipClass.getMethod("setPlane", param);
+      param = new Class[] {javax.media.j3d.Group.class};
+      modelClipAddScope = modelClipClass.getMethod("addScope", param);
+      param = new Class[] {int.class};
+      Method modelClipSetCapability =
+        modelClipClass.getMethod("setCapability", param);
+      param = new Class[] {javax.media.j3d.Bounds.class};
+      Method modelClipSetInfluencingBounds =
+        modelClipClass.getMethod("setInfluencingBounds", param);
+      modelClip = modelClipConstructor.newInstance(new Object[] {});
+      int ALLOW_PLANE_WRITE =
+        modelClipClass.getField("ALLOW_PLANE_WRITE").getInt(modelClip);
+      modelClipSetCapability.invoke(modelClip,
+                         new Object[] {new Integer(ALLOW_PLANE_WRITE)});
+      int ALLOW_ENABLE_WRITE =
+        modelClipClass.getField("ALLOW_ENABLE_WRITE").getInt(modelClip);
+      modelClipSetCapability.invoke(modelClip,
+                         new Object[] {new Integer(ALLOW_ENABLE_WRITE)});
+      Boolean f = new Boolean(false);
+      for (int i=0; i<6; i++) {
+        modelClipSetEnable.invoke(modelClip, new Object[] {new Integer(i), f});
+      }
+      BoundingSphere bound3 =
+        new BoundingSphere(new Point3d(0.0,0.0,0.0),2000000.0);
+      modelClipSetInfluencingBounds.invoke(modelClip, new Object[] {bound3});
+      background.setApplicationBounds(bound2);
+      modelClipAddScope.invoke(modelClip, new Object[] {non_direct});
+      setSceneGraphObjectName(((Node) modelClip), "ModelClip");
+      trans.addChild((Node) modelClip);
+    }
+    catch (ClassNotFoundException e) {
+    }
+    catch (NoSuchMethodException e) {
+    }
+    catch (InstantiationException e) {
+    }
+    catch (IllegalAccessException e) {
+    }
+    catch (InvocationTargetException e) {
+    }
+    catch (NoSuchFieldException e) {
+    }
+
+    return root;
+  }
+
+  // WLH 23 Oct 2001
+  /** 
+   * Define a clipping plane in (XAxis, YAxis, ZAxis) space.  Allows
+   * up to 6 arbitrary planes.  Each clip plane is defined by the equation:
+   * <PRE>
+   *      aX + bY + cZ + d <= 0
+   * </PRE>
+   * <p>Example useage:</p>
+   * To clip to the usual VisAD cube (i.e., x, y and z values in the 
+   * range -1.0 to +1.0) (see Test35.java), call:
+   * <PRE>
+   *    DisplayRendererJ3D dr = 
+   *             (DisplayRendererJ3D) display.getDisplayRenderer();
+   *    dr.setClip(0, true,  1.0f,  0.0f,  0.0f, -1.01f);  // X_POS face
+   *    dr.setClip(1, true, -1.0f,  0.0f,  0.0f, -1.01f);  // X_NEG face
+   *    dr.setClip(2, true,  0.0f,  1.0f,  0.0f, -1.01f);  // Y_POS face
+   *    dr.setClip(3, true,  0.0f, -1.0f,  0.0f, -1.01f);  // Y_NEG face
+   *    dr.setClip(4, true,  0.0f,  0.0f,  1.0f, -1.01f);  // Z_POS face
+   *    dr.setClip(5, true,  0.0f,  0.0f, -1.0f, -1.01f);  // Z_NEG face
+   * </PRE>
+   * <b>Note:</b> d value is slightly less than -1.0 so items in the plane
+   *              are not clipped.
+   * @param  plane  plane number must be in (0, ..., 5)).
+   * @param  enable true to enable clipping on this plane, false to disable
+   * @param  a      x coefficent
+   * @param  b      y coefficent
+   * @param  c      z coefficent
+   * @param  d      constant
+   * @throws  VisADException  illegal plane argument or 
+   *                          unsupported (< 1.2) version of Java 3D
+   */
+  public void setClip(int plane, boolean enable, float a, float b, float c, float d)
+         throws VisADException {
+    if (not_destroyed == null) return;
+    if (plane < 0 || 5 < plane) {
+      throw new DisplayException("plane must be in 0,...,5 range " + plane);
+    }
+    if (modelClip == null ||
+        modelClipSetEnable == null ||
+        modelClipSetPlane == null) {
+      throw new DisplayException("model clipping not supported in this " +
+                                 "version of Java3D");
+    }
+    Vector4d vect = new Vector4d((double) a, (double) b, (double) c, (double) d);
+    try {
+      Object[] params = {new Integer(plane), new Boolean(enable)};
+      modelClipSetEnable.invoke(modelClip, params);
+      params = new Object[] {new Integer(plane), vect};
+      modelClipSetPlane.invoke(modelClip, params);
+      modelClipEnables[plane] = enable;
+    }
+    catch (IllegalAccessException e) {
+      e.printStackTrace();
+    }
+    catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void clipOff() {
+    if (not_destroyed == null) return;
+    try {
+      for (int i=0; i<6; i++) {
+        if (modelClipEnables[i]) {
+          Object[] params = {new Integer(i), new Boolean(false)};
+          modelClipSetEnable.invoke(modelClip, params);
+        }
+      }
+    }
+    catch (IllegalAccessException e) {
+      e.printStackTrace();
+    }
+    catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void clipOn() {
+    if (not_destroyed == null) return;
+    try {
+      for (int i=0; i<6; i++) {
+        if (modelClipEnables[i]) {
+          Object[] params = {new Integer(i), new Boolean(true)};
+          modelClipSetEnable.invoke(modelClip, params);
+        }
+      }
+    }
+    catch (IllegalAccessException e) {
+      e.printStackTrace();
+    }
+    catch (InvocationTargetException e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Get the <CODE>MouseBehavior</CODE> associated with this renderer.
+   * @return  the <CODE>MouseBehavior</CODE> used by this renderer to handle
+   *          mouse events.
+   */
+  public MouseBehavior getMouseBehavior() {
+    return mouse;
+  }
+
+  /**
+   * Get the <CODE>KeyboardBehavior</CODE> associated with this renderer.
+   * 
+   * @return the <CODE>KeyboardBehavior</CODE> used by this renderer to handle
+   *         mouse events.
+   */
+  public KeyboardBehavior getKeyboardBehavior() {
+    return keyboard;
+  }
+
+  public void addSceneGraphComponent(Group group) {
+    if (not_destroyed == null) return;
+    non_direct.addChild(group);
+  }
+
+  public void addLockedSceneGraphComponent(Group group) {
+    if (not_destroyed == null || screen_locked == null) return;
+    screen_locked.addChild(group);
+  }
+
+  //- TDR, Hydra stuff
+  public void addLockedSceneGraphComponent(Group group, boolean initWithProj) {
+    if (not_destroyed == null || screen_locked == null) return;
+    if (initWithProj) {
+      ProjectionControl proj = getDisplay().getProjectionControl();
+      locked_trans.setTransform(new Transform3D(proj.getMatrix()));
+    }
+    screen_locked.addChild(group);
+  }
+                                                                                                                                         
+  public void updateLockedTrans(double[] matrix) {
+    if (locked_trans != null) {
+      locked_trans.setTransform(new Transform3D(matrix));
+    }
+  }
+
+
+  public void addDirectManipulationSceneGraphComponent(Group group,
+                         DirectManipulationRendererJ3D renderer) {
+    if (not_destroyed == null) return;
+    // WLH 13 March 2000
+    // direct.addChild(group);
+    non_direct.addChild(group);
+    directs.addElement(renderer);
+  }
+
+
+  public void clearScene(DataRenderer renderer) {
+    if (not_destroyed == null) return;
+    directs.removeElement(renderer);
+  }
+
+  /** 
+   * Get the cusor location.
+   * @return  cursor location as an array of x, y, and z values
+   */
+  public double[] getCursor() {
+    double[] cursor = new double[3];
+    cursor[0] = cursorX;
+    cursor[1] = cursorY;
+    cursor[2] = cursorZ;
+    return cursor;
+  }
+
+  public void depth_cursor(VisADRay ray) {
+    line_x = (float) ray.vector[0];
+    line_y = (float) ray.vector[1];
+    line_z = (float) ray.vector[2];
+    point_x = cursorX;
+    point_y = cursorY;
+    point_z = cursorZ;
+  }
+
+  public void drag_depth(float diff) {
+    if (not_destroyed == null) return;
+    cursorX = point_x + diff * line_x;
+    cursorY = point_y + diff * line_y;
+    cursorZ = point_z + diff * line_z;
+    setCursorLoc();
+  }
+
+  public void drag_cursor(VisADRay ray, boolean first) {
+    if (not_destroyed == null) return;
+    float o_x = (float) ray.position[0];
+    float o_y = (float) ray.position[1];
+    float o_z = (float) ray.position[2];
+    float d_x = (float) ray.vector[0];
+    float d_y = (float) ray.vector[1];
+    float d_z = (float) ray.vector[2];
+/*
+    Point3d origin = new Point3d();
+    Vector3d direction = new Vector3d();
+    ray.get(origin, direction);
+    float o_x = (float) origin.x;
+    float o_y = (float) origin.y;
+    float o_z = (float) origin.z;
+    float d_x = (float) direction.x;
+    float d_y = (float) direction.y;
+    float d_z = (float) direction.z;
+*/
+    if (first) {
+      line_x = d_x;
+      line_y = d_y;
+      line_z = d_z;
+    }
+    float dot = (cursorX - o_x) * line_x +
+                (cursorY - o_y) * line_y +
+                (cursorZ - o_z) * line_z;
+    float dot2 = d_x * line_x + d_y * line_y + d_z * line_z;
+    if (dot2 == 0.0) return;
+    dot = dot / dot2;
+    // new cursor location is intersection
+    cursorX = o_x + dot * d_x;
+    cursorY = o_y + dot * d_y;
+    cursorZ = o_z + dot * d_z;
+    setCursorLoc();
+  }
+
+  private void setCursorLoc() {
+    if (not_destroyed == null) return;
+    Transform3D t = new Transform3D();
+    t.setTranslation(new Vector3f(cursorX, cursorY, cursorZ));
+    cursor_trans.setTransform(t);
+    if (cursorOn) {
+      setCursorStringVector();
+    }
+  }
+
+  /**
+   * Set the cursor location
+   * @param  x  x location
+   * @param  y  y location
+   * @param  z  z location
+   */
+  public void setCursorLoc(float x, float y, float z) {
+    if (not_destroyed == null) return;
+    Transform3D t = new Transform3D();
+    t.setTranslation(new Vector3f(x, y, z));
+    cursor_trans.setTransform(t);
+    if (cursorOn) {
+      setCursorStringVector();
+    }
+  }
+
+  /**
+   * Whenever <CODE>cursorOn</CODE> or <CODE>directOn</CODE> is true,
+   * display Strings in cursorStringVector.
+   * @param canvas
+   */
+  
+  public void drawCursorStringVector(VisADCanvasJ3D canvas) {
+    
+	if (not_destroyed == null) return;
+
+    GraphicsContext3D graphics = canvas.getGraphicsContext3D();
+    // graphics.setModelClip(null);
+    // causes NullPointerException at GraphicsContext3D.java:689
+
+    // set cursor color, if possible
+    try {
+      float[] c3 = getCursorColor();
+      Appearance appearance = new Appearance();
+      ColoringAttributes color = new ColoringAttributes();
+      color.setColor(new Color3f(c3));
+      appearance.setColoringAttributes(color);
+      graphics.setAppearance(appearance);
+    } catch (Exception e) {
+    }
+
+    Point3d position1 = new Point3d();
+    Point3d position2 = new Point3d();
+    Point3d position3 = new Point3d();
+    canvas.getPixelLocationInImagePlate(1, 10, position1);
+    canvas.getPixelLocationInImagePlate(10, 10, position2);
+    canvas.getPixelLocationInImagePlate(1, 1, position3);
+
+    DisplayImpl display = getDisplay();
+    if (display != null && display.getGraphicsModeControl() != null) {
+      // hack to move text closer to eye
+      if (getDisplay().getGraphicsModeControl().getProjectionPolicy() ==
+          View.PERSPECTIVE_PROJECTION) {
+        Point3d left_eye = new Point3d();
+        Point3d right_eye = new Point3d();
+        canvas.getLeftEyeInImagePlate(left_eye);
+        canvas.getRightEyeInImagePlate(right_eye);
+        Point3d eye = new Point3d((left_eye.x + right_eye.x)/2.0,
+                                  (left_eye.y + right_eye.y)/2.0,
+                                  (left_eye.z + right_eye.z)/2.0);
+        double alpha = 0.3;
+        position1.x = alpha * position1.x + (1.0 - alpha) * eye.x;
+        position1.y = alpha * position1.y + (1.0 - alpha) * eye.y;
+        position1.z = alpha * position1.z + (1.0 - alpha) * eye.z;
+        position2.x = alpha * position2.x + (1.0 - alpha) * eye.x;
+        position2.y = alpha * position2.y + (1.0 - alpha) * eye.y;
+        position2.z = alpha * position2.z + (1.0 - alpha) * eye.z;
+        position3.x = alpha * position3.x + (1.0 - alpha) * eye.x;
+        position3.y = alpha * position3.y + (1.0 - alpha) * eye.y;
+        position3.z = alpha * position3.z + (1.0 - alpha) * eye.z;
+      }
+    }
+// end of hack to move text closer to eye
+
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    t.transform(position1);
+    t.transform(position2);
+    t.transform(position3);
+
+    // draw cursor strings in upper left corner of screen
+    double[] start = {(double) position1.x,
+                      (double) position1.y,
+                      (double) position1.z};
+    double[] base =  {(double) (position2.x - position1.x),
+                      (double) (position2.y - position1.y),
+                      (double) (position2.z - position1.z)};
+    double[] up =    {(double) (position3.x - position1.x),
+                      (double) (position3.y - position1.y),
+                      (double) (position3.z - position1.z)};
+    if (cursorOn || directOn) {
+    	Enumeration strings = getCursorStringVector().elements();
+    	while (strings.hasMoreElements()) {
+    		String string = (String) strings.nextElement();
+    		if ((string != null) && (! string.trim().isEmpty())) {
+    			try {
+    				VisADLineArray array =
+    						PlotText.render_label(string, start, base, up, false);
+    				graphics.draw(((DisplayImplJ3D) getDisplay()).makeGeometry(array));
+    				start[1] -= 1.2 * up[1];
+    			}
+    			catch (VisADException e) {
+    			}
+    		}
+    	}
+    }
+
+    // draw Exception strings in lower left corner of screen
+    double[] startl = {(double) position3.x,
+                       (double) -position3.y,
+                       (double) position3.z};
+    Vector rendererVector = getDisplay().getRendererVector();
+    Enumeration renderers = rendererVector.elements();
+    while (renderers.hasMoreElements()) {
+      DataRenderer renderer = (DataRenderer) renderers.nextElement();
+      Vector exceptionVector = renderer.getExceptionVector();
+      Enumeration exceptions = exceptionVector.elements();
+      while (exceptions.hasMoreElements()) {
+        Exception error = (Exception) exceptions.nextElement();
+        String string = error.getMessage();
+        try {
+          VisADLineArray array =
+            PlotText.render_label(string, startl, base, up, false);
+          graphics.draw(((DisplayImplJ3D) getDisplay()).makeGeometry(array));
+          startl[1] += 1.2 * up[1];
+        }
+        catch (VisADException e) {
+        }
+      }
+    }
+
+    // draw wait flag in lower left corner of screen
+    if (getWaitFlag() && getWaitMessageVisible()) {
+      try {
+        VisADLineArray array =
+          PlotText.render_label("please wait . . .", startl, base, up, false);
+        graphics.draw(((DisplayImplJ3D) getDisplay()).makeGeometry(array));
+        startl[1] += 1.2 * up[1];
+      }
+      catch (VisADException e) {
+      }
+    }
+
+    // draw Animation string in lower right corner of screen
+    String[] animation_string = getAnimationString();
+    if (animation_string[0] != null) {
+      int nchars = animation_string[0].length();
+      if (nchars < 12) nchars = 12;
+      double[] starta = {(double) (-position2.x - nchars *
+                                        (position2.x - position1.x)),
+                         (double) -position3.y + 1.2 * up[1],
+                         // (double) position2.y, WLH 30 April 99
+                         (double) position2.z};
+      try {
+        VisADLineArray array =
+          PlotText.render_label(animation_string[0], starta, base, up, false);
+        graphics.draw(((DisplayImplJ3D) getDisplay()).makeGeometry(array));
+        starta[1] -= 1.2 * up[1];
+        if (animation_string[1] != null) {
+          array =
+            PlotText.render_label(animation_string[1], starta, base, up, false);
+          graphics.draw(((DisplayImplJ3D) getDisplay()).makeGeometry(array));
+          starta[1] -= 1.2 * up[1];
+        }
+      }
+      catch (VisADException e) {
+      }
+    }
+
+    if (scale_switch != null && scale_switch.getWhichChild() == 1) {
+      Dimension d = canvas.getSize();
+      int w = d.width;
+      int h = d.height;
+
+      double MUL = 3.0 * w / 256.0;
+      double XMAX = Math.abs(MUL * position2.x - (MUL - 1.0) * position3.x);
+      double YMAX = Math.abs(MUL * position2.y - (MUL - 1.0) * position3.y);
+      double XMIN = -XMAX;
+      double YMIN = -YMAX;
+
+      TransformGroup trans = getTrans();
+      Transform3D tt = new Transform3D();
+      trans.getTransform(tt);
+      tt.invert();
+      Point3d positionx = new Point3d(XMAX, YMAX, 0.0);
+      Point3d positionn = new Point3d(XMIN, YMIN, 0.0);
+      tt.transform(positionx);
+      tt.transform(positionn);
+
+      double XTMAX = positionx.x;
+      double YTMAX = positionx.y;
+      double XTMIN = positionn.x;
+      double YTMIN = positionn.y;
+
+      Enumeration axes = axis_vector.elements();
+      while (axes.hasMoreElements()) {
+        AxisScale axisScale = (AxisScale) axes.nextElement();
+        try {
+          boolean success =
+            axisScale.makeScreenBasedScale(XMIN, YMIN, XMAX, YMAX,
+                                           XTMIN, YTMIN, XTMAX, YTMAX);
+          if (success) {
+// System.out.println("makeScreenBasedScale success");
+            int axis = axisScale.getAxis();
+            int axis_ordinal = axisScale.getAxisOrdinal();
+            VisADLineArray array = axisScale.getScaleArray();
+            VisADTriangleArray labels = axisScale.getLabelArray();
+            float[] scale_color = axisScale.getColor().getColorComponents(null);
+  
+            // set cursor color, if possible
+            Appearance appearance = new Appearance();
+            ColoringAttributes color = new ColoringAttributes();
+            color.setColor(new Color3f(scale_color));
+            appearance.setColoringAttributes(color);
+            graphics.setAppearance(appearance);
+            graphics.draw(((DisplayImplJ3D) getDisplay()).makeGeometry(array));
+
+            if (labels != null) {
+              GeometryArray labelGeometry = 
+                ((DisplayImplJ3D) getDisplay()).makeGeometry(labels);
+              Appearance labelAppearance =
+                ShadowTypeJ3D.staticMakeAppearance(
+                    getDisplay().getGraphicsModeControl(), null, null, 
+                    labelGeometry, true);
+              graphics.setAppearance(labelAppearance);
+              graphics.draw(labelGeometry);
+            }
+          }
+          else {
+//  System.out.println("makeScreenBasedScale fail");
+          }
+        } catch (Exception e) {
+        }
+      }
+    }
+    // graphics.flush(true); doesn't help
+    // clipOn(); doesn't work
+  }
+
+  /**
+   * Find the <CODE>DataRenderer</CODE> that is closest to the ray and
+   * uses the specified mouse modifiers for direct manipulation.
+   * @param  ray  position to check
+   * @param  mouseModifiers  modifiers for mouse clicks
+   * @return  closest DataRenderer that uses the specified mouse click
+   *          modifiers for direct manipulation or null if there is none.
+   */
+  public DataRenderer findDirect(VisADRay ray, int mouseModifiers) {
+    if (not_destroyed == null) return null;
+    DirectManipulationRendererJ3D renderer = null;
+    float distance = Float.MAX_VALUE;
+    Enumeration renderers = ((Vector) directs.clone()).elements();
+    while (renderers.hasMoreElements()) {
+      DirectManipulationRendererJ3D r =
+        (DirectManipulationRendererJ3D) renderers.nextElement();
+      if (r.getEnabled()) {
+        r.setLastMouseModifiers(mouseModifiers);
+        float d = r.checkClose(ray.position, ray.vector);
+        if (d < distance) {
+          distance = d;
+          renderer = r;
+        }
+      }
+    }
+    if (distance < getPickThreshhold()) {
+      return renderer;
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * Check to see if there are any <CODE>DirectManipulationRenderer</CODE>s
+   * in this display.
+   * @return  true if there are any
+   */
+  public boolean anyDirects() {
+    if (not_destroyed == null) return false;
+    return !directs.isEmpty();
+  }
+
+  /**
+   * Set the scales on.
+   * @param  on   turn on if true, otherwise turn them off
+   */
+  public void setScaleOn(boolean on) {
+    if (not_destroyed == null) return;
+    if (on) {
+      scale_switch.setWhichChild(1); // on
+    }
+    else {
+      scale_switch.setWhichChild(0); // off
+    }
+  }
+
+  /**
+   * Set the scale for the appropriate axis.
+   * @param  axisScale  AxisScale for this scale
+   * @throws  VisADException  couldn't set the scale
+   */
+  public void setScale(AxisScale axisScale)
+         throws VisADException {
+    if (not_destroyed == null) return;
+    if (axisScale.getScreenBased() && getMode2D()) {
+      if (!axis_vector.contains(axisScale)) {
+        axis_vector.addElement(axisScale);
+
+        clearScale(axisScale);
+	/*  Replaced by clearScale()  2001-08-08  DRM
+        // eliminate any non-screen-based scale for this AxisScale
+        int axis = axisScale.getAxis();
+        int axis_ordinal = axisScale.getAxisOrdinal();
+        int dim = getMode2D() ? 2 : 3;
+        synchronized (scale_on) {
+          int n = scale_on.numChildren();
+          int m = dim * axis_ordinal + axis;
+          if (m >= n) {
+            for (int i=n; i<=m; i++) {
+              BranchGroup empty = new BranchGroup();
+              empty.setCapability(BranchGroup.ALLOW_DETACH);
+              empty.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+              scale_on.addChild(empty);
+            }
+          }
+          BranchGroup empty = new BranchGroup();
+          empty.setCapability(BranchGroup.ALLOW_DETACH);
+          empty.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          scale_on.setChild(empty, m);
+        }
+	*/
+      }
+    }
+    else {
+      setScale(axisScale.getAxis(),
+               axisScale.getAxisOrdinal(),
+               axisScale.getScaleArray(),
+               axisScale.getLabelArray(),
+               axisScale.getColor().getColorComponents(null));
+    }
+  }
+
+  /**
+   * Set the scale for the appropriate axis.
+   * @param  axis  axis for this scale (0 = XAxis, 1 = YAxis, 2 = ZAxis)
+   * @param  axis_ordinal  position along the axis
+   * @param  array   <CODE>VisADLineArray</CODE> representing the scale plot
+   * @param  scale_color   array (dim 3) representing the red, green and blue
+   *                       color values.
+   * @throws  VisADException  couldn't set the scale
+   */
+  public void setScale(int axis, int axis_ordinal,
+              VisADLineArray array, float[] scale_color)
+         throws VisADException {
+    if (not_destroyed == null) return;
+    setScale(axis, axis_ordinal, array, null, scale_color);
+  }
+
+  /**
+   * Set the scale for the appropriate axis.
+   * @param  axis  axis for this scale (0 = XAxis, 1 = YAxis, 2 = ZAxis)
+   * @param  axis_ordinal  position along the axis
+   * @param  array   <CODE>VisADLineArray</CODE> representing the scale plot
+   * @param  labels  <CODE>VisADTriangleArray</CODE> representing the labels
+   *                 created using a font (can be null)
+   * @param  scale_color   array (dim 3) representing the red, green and blue
+   *                       color values.
+   * @throws  VisADException  couldn't set the scale
+   */
+  public void setScale(int axis, int axis_ordinal,
+              VisADLineArray array, VisADTriangleArray labels,
+              float[] scale_color)
+         throws VisADException {
+    if (not_destroyed == null) return;
+// DisplayImpl.printStack("setScale");
+    // add array to scale_on
+    // replace any existing at axis, axis_ordinal
+    DisplayImplJ3D display = (DisplayImplJ3D) getDisplay();
+    GeometryArray geometry = display.makeGeometry(array);
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    ColoringAttributes color = new ColoringAttributes();
+    color.setColor(scale_color[0], scale_color[1], scale_color[2]);
+    Appearance appearance =
+      ShadowTypeJ3D.staticMakeAppearance(mode, null, color, geometry, false);
+    Shape3D shape = new Shape3D(geometry, appearance);
+    shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    BranchGroup group = new BranchGroup();
+    group.setCapability(BranchGroup.ALLOW_DETACH);
+    group.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    group.addChild(shape);
+    if (labels != null) {
+      GeometryArray labelGeometry = display.makeGeometry(labels);
+      Appearance labelAppearance =
+        ShadowTypeJ3D.staticMakeAppearance(mode, null, null,
+                                           labelGeometry, true);
+      Shape3D labelShape = new Shape3D(labelGeometry, labelAppearance);
+      labelShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      labelShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      
+      group.addChild(labelShape);
+
+
+      if (labels instanceof VisADTriangleArray) {
+        GeometryArray labelGeometry2 = display.makeGeometry(labels);
+        Appearance labelAppearance2 =
+          ShadowTypeJ3D.staticMakeAppearance(mode, null, null,
+                                             labelGeometry2, true);
+
+        // LineAttributes la = labelAppearance2.getLineAttributes();
+        // better without anti-aliasing
+        // la.setLineAntialiasingEnable(true);
+
+        PolygonAttributes pa = labelAppearance2.getPolygonAttributes();
+        pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
+        Shape3D labelShape2 = new Shape3D(labelGeometry2, labelAppearance2);
+        labelShape2.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+        labelShape2.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+        group.addChild(labelShape2);
+      }
+
+
+    }
+    // may only add BranchGroup to 'live' scale_on
+    int dim = getMode2D() ? 2 : 3;
+    synchronized (scale_on) {
+      int n = scale_on.numChildren();
+      int m = dim * axis_ordinal + axis;
+      if (m >= n) {
+        for (int i=n; i<=m; i++) {
+          BranchGroup empty = new BranchGroup();
+          empty.setCapability(BranchGroup.ALLOW_DETACH);
+          empty.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          scale_on.addChild(empty);
+        }
+      }
+      scale_on.setChild(group, m);
+    }
+  }
+
+  /**
+   * Remove all the scales being rendered.
+   */
+  public void clearScales() {
+    if (not_destroyed == null) return;
+    if (scale_on != null) {
+      synchronized (scale_on) {
+        int n = scale_on.numChildren();
+        for (int i=n-1; i>=0; i--) {
+          scale_on.removeChild(i);
+        }
+      }
+    }
+    axis_vector.removeAllElements();
+  }
+
+  /**
+   * Remove a particular scale being rendered.
+   * @param axisScale  AxisScale to remove
+   */
+  
+  public void clearScale(AxisScale axisScale) {
+    if (not_destroyed == null) return;
+    // eliminate any non-screen-based scale for this AxisScale
+    int axis = axisScale.getAxis();
+    int axis_ordinal = axisScale.getAxisOrdinal();
+    int dim = getMode2D() ? 2 : 3;
+    synchronized (scale_on) {
+      int n = scale_on.numChildren();
+      int m = dim * axis_ordinal + axis;
+      if (m >= n) {
+        for (int i=n; i<=m; i++) {
+          BranchGroup empty = new BranchGroup();
+          empty.setCapability(BranchGroup.ALLOW_DETACH);
+          empty.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          scale_on.addChild(empty);
+        }
+      }
+      BranchGroup empty = new BranchGroup();
+      empty.setCapability(BranchGroup.ALLOW_DETACH);
+      empty.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+      scale_on.setChild(empty, m);
+    }
+  }
+
+  public void setTransform3D(Transform3D t) {
+    if (not_destroyed == null) return;
+    if (trans == null) {
+      trans = new TransformGroup();
+      setSceneGraphObjectName(trans, "Trans");
+      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
+      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
+      trans.setCapability(Group.ALLOW_CHILDREN_READ);
+      trans.setCapability(Group.ALLOW_CHILDREN_WRITE);
+      trans.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    }
+    if (t != null) {
+      trans.setTransform(t);
+      if (locked_trans == null && root != null) {
+        locked_trans = new TransformGroup();
+        setSceneGraphObjectName(locked_trans, "LockedTrans");
+        locked_trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
+        locked_trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
+        locked_trans.setCapability(Group.ALLOW_CHILDREN_READ);
+        locked_trans.setCapability(Group.ALLOW_CHILDREN_WRITE);
+        locked_trans.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+        locked_trans.setTransform(t);
+        screen_locked = new OrderedGroup();
+        setSceneGraphObjectName(screen_locked, "ScreenLocked");
+        screen_locked.setCapability(Group.ALLOW_CHILDREN_READ);
+        screen_locked.setCapability(Group.ALLOW_CHILDREN_WRITE);
+        screen_locked.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+        screen_locked.setCapability(Node.ENABLE_PICK_REPORTING);
+        locked_trans.addChild(screen_locked);
+        BranchGroup bgroup = new BranchGroup();
+        setSceneGraphObjectName(bgroup, "LockedGroup");
+        bgroup.setCapability(Group.ALLOW_CHILDREN_READ);
+        bgroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
+        bgroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+        bgroup.addChild(locked_trans);
+        root.addChild(bgroup);
+      }
+    }
+  }
+
+  /**
+   * Factory for constructing a subclass of <CODE>Control</CODE>
+   * appropriate for the graphics API and for this
+   * <CODE>DisplayRenderer</CODE>; invoked by <CODE>ScalarMap</CODE>
+   * when it is <CODE>addMap()</CODE>ed to a <CODE>Display</CODE>.
+   * @param map The <CODE>ScalarMap</CODE> for which a <CODE>Control</CODE>
+   *            should be built.
+   * @return The appropriate <CODE>Control</CODE>.
+   */
+  public Control makeControl(ScalarMap map) {
+    if (not_destroyed == null) return null;
+    DisplayRealType type = map.getDisplayScalar();
+    DisplayImplJ3D display = (DisplayImplJ3D) getDisplay();
+    if (type == null) return null;
+    if (type.equals(Display.XAxis) ||
+        type.equals(Display.YAxis) ||
+        type.equals(Display.ZAxis) ||
+        type.equals(Display.Latitude) ||
+        type.equals(Display.Longitude) ||
+        type.equals(Display.Radius)) {
+      return (ProjectionControlJ3D) display.getProjectionControl();
+    }
+    else if (type.equals(Display.RGB) ||
+             type.equals(Display.HSV) ||
+             type.equals(Display.CMY)) {
+      return new ColorControl(display);
+    }
+    else if (type.equals(Display.RGBA)) {
+      return new ColorAlphaControl(display);
+    }
+    else if (type.equals(Display.Animation)) {
+      // note only one RealType may be mapped to Animation
+      // so control must be null
+      Control control = display.getControl(AnimationControlJ3D.class);
+      if (control != null) return control;
+      else return new AnimationControlJ3D(display, (RealType) map.getScalar());
+    }
+    else if (type.equals(Display.SelectValue)) {
+      return new ValueControlJ3D(display);
+    }
+    else if (type.equals(Display.SelectRange)) {
+      return new RangeControl(display);
+    }
+    else if (type.equals(Display.IsoContour)) {
+      return new ContourControl(display);
+    }
+    else if (type.equals(Display.Flow1X) ||
+             type.equals(Display.Flow1Y) ||
+             type.equals(Display.Flow1Z) ||
+             type.equals(Display.Flow1Elevation) ||
+             type.equals(Display.Flow1Azimuth) ||
+             type.equals(Display.Flow1Radial)) {
+      Control control = display.getControl(Flow1Control.class);
+      if (control != null) return control;
+      else return new Flow1Control(display);
+    }
+    else if (type.equals(Display.Flow2X) ||
+             type.equals(Display.Flow2Y) ||
+             type.equals(Display.Flow2Z) ||
+             type.equals(Display.Flow2Elevation) ||
+             type.equals(Display.Flow2Azimuth) ||
+             type.equals(Display.Flow2Radial)) {
+      Control control = display.getControl(Flow2Control.class);
+      if (control != null) return control;
+      else return new Flow2Control(display);
+    }
+    else if (type.equals(Display.Shape)) {
+      return new ShapeControl(display);
+    }
+    else if (type.equals(Display.Text)) {
+      return new TextControl(display);
+    }
+    else {
+      return null;
+    }
+  }
+
+  /**
+   * Create the default <CODE>DataRenderer</CODE> for this type of 
+   * <CODE>DisplayRenderer</CODE>
+   * @return  new default renderer
+   */
+  public DataRenderer makeDefaultRenderer() {
+    return new DefaultRendererJ3D();
+  }
+
+  /**
+   * Check if the <CODE>DataRenderer</CODE> in question is legal for this
+   * <CODE>DisplayRenderer</CODE>
+   * @param renderer  <CODE>DataRenderer</CODE> to check
+   * @return  true if renderer is a subclass of <CODE>RendererJ3D</CODE>
+   */
+  public boolean legalDataRenderer(DataRenderer renderer) {
+    return (renderer instanceof RendererJ3D);
+  }
+
+  public void rendererDeleted(DataRenderer renderer)
+  {
+    if (not_destroyed == null) return;
+    clearScene(renderer);
+  }
+
+  public void setLineWidth(float width) {
+  }
+
+  /**
+   * Add a <CODE>KeyboardBehavior</CODE> for keyboard control of rotation,
+   * translation and zoom.  
+   * @param  behavior  keyboard behavior to add
+   */
+  public void addKeyboardBehavior(KeyboardBehaviorJ3D behavior)
+  {
+    if (not_destroyed == null) return;
+    BranchGroup bg = new BranchGroup();
+    bg.setCapability(Group.ALLOW_CHILDREN_READ);
+    bg.addChild(behavior);
+    trans.addChild(bg);
+  }
+
+  public void render_trigger() {
+    ProjectionControl proj = getDisplay().getProjectionControl();
+    try {
+      if (proj != null) proj.setMatrix(proj.getMatrix());
+    }
+    catch (VisADException e) { }
+    catch (RemoteException e) { }
+  }
+
+  public void setWaitFlag(boolean b) {
+    if (not_destroyed == null) return;
+    boolean old = getWaitFlag();
+    super.setWaitFlag(b);
+    if (b != old) {
+      render_trigger();
+    }
+  }
+
+  public int getTextureWidthMax() {
+    return VisADCanvasJ3D.getTextureWidthMax();
+  }
+
+  public int getTextureHeightMax() {
+    return VisADCanvasJ3D.getTextureWidthMax();
+  }
+
+}
+
diff --git a/visad/java3d/DownRoundingAnimationControlJ3D.java b/visad/java3d/DownRoundingAnimationControlJ3D.java
new file mode 100644
index 0000000..15adac7
--- /dev/null
+++ b/visad/java3d/DownRoundingAnimationControlJ3D.java
@@ -0,0 +1,165 @@
+//
+// DownRoundingAnimationControlJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+import java.rmi.*;
+
+/**
+   DownRoundingAnimationControlJ3D extends AnimationControlJ3D to provide a
+   different sampling behavior. Instead of nearest neighbor, the nearest sample
+   LESS THAN the current value will be displayed.<P>
+   Doug Lindholm (DML) - Dec 2001
+*/
+public class DownRoundingAnimationControlJ3D extends AnimationControlJ3D
+       implements Runnable, AnimationControl {
+
+  public DownRoundingAnimationControlJ3D(DisplayImplJ3D d, RealType r) {
+    super(d, r);
+  }
+
+  DownRoundingAnimationControlJ3D() {
+    this(null, null);
+  }
+
+  //override to get down rounding behavior
+  public void setCurrent( int c ) throws VisADException, RemoteException {
+    Set set = getSet();
+
+    if ( set == null ) {
+      current = -1;
+    }
+    else {
+      int n = set.getLength();
+      if ( c >= n ) c = n-1;
+      current = c;
+    }
+
+    init();
+    changeControl(false);
+  }
+
+  //override to get down rounding behavior
+  public void setCurrent( double value ) throws VisADException, RemoteException {
+    Set set = getSet();
+    current = getIndexLessThanValue( set, value );
+    init();
+    changeControl(false);
+  }
+
+  /**
+   * Return the index of the sample with the nearest value less than or equal
+   * to the given value, -1 if no earlier samples.
+   */
+  protected int getIndexLessThanValue( Set set, double value )
+         throws VisADException {
+    int index = -1;
+    if ( set != null ) {
+      double[][] values = set.getDoubles();
+      int n = values[0].length;
+      for ( int i=0; i<n; i++ ) {
+        if ( values[0][i] > value ) break;//gone too far, stick with previous
+        index = i;
+      }
+    }
+
+    return index;
+  }
+
+  //override - superclass will clip current - add support for current = -1
+  public void init() throws VisADException {
+    Set set = getSet();
+    if ( set != null ) {
+      double value = Double.NaN;
+      if ( current != -1 ) {
+        value = (set.indexToDouble( new int[] {current} ))[0][0];
+      }
+
+      RealType real = getRealType();
+      animation_string(real, set, value, current);
+      selectSwitches(value, set);
+    }
+  }
+
+  //overrides AVControlJ3D to get down rounding behavior
+  // if value is NaN, show nothing
+  public void selectSwitches(double value, Set animation_set)
+       throws VisADException {
+
+    double[][] fvalues = new double[1][1];
+    fvalues[0][0] = value;
+    Enumeration pairs = ((Vector) getSwitches().clone()).elements();
+    while (pairs.hasMoreElements()) {
+      SwitchSet ss = (SwitchSet) pairs.nextElement();
+
+      if ( value != value ) { //if value is NaN
+        ss.swit.setWhichChild( Switch.CHILD_NONE );
+        continue;
+      }
+
+      Set set = ss.set;
+      double[][] values = null;
+      RealTupleType out = ((SetType) set.getType()).getDomain();
+      if (animation_set != null) {
+        RealTupleType in =
+          ((SetType) animation_set.getType()).getDomain();
+        values = CoordinateSystem.transformCoordinates(
+                             out, set.getCoordinateSystem(),
+                             set.getSetUnits(), null /* errors */,
+                             in, animation_set.getCoordinateSystem(),
+                             animation_set.getSetUnits(),
+                             null /* errors */, fvalues);
+      }
+      else {
+        // use RealType for value Unit and CoordinateSystem
+        // for SelectValue
+        values = CoordinateSystem.transformCoordinates(
+                             out, set.getCoordinateSystem(),
+                             set.getSetUnits(), null /* errors */,
+                             out, out.getCoordinateSystem(),
+                             out.getDefaultUnits(), null /* errors */,
+                             fvalues);
+      }
+
+      int index = getIndexLessThanValue( set, values[0][0] );
+
+      int numc = ss.swit.numChildren();
+      if ( index >= numc ) index = numc-1;
+
+      if ( index == -1 ) ss.swit.setWhichChild( Switch.CHILD_NONE );
+      else ss.swit.setWhichChild( index );
+
+    } // end while (pairs.hasMoreElements())
+  }
+
+}
diff --git a/visad/java3d/GraphicsModeControlJ3D.java b/visad/java3d/GraphicsModeControlJ3D.java
new file mode 100644
index 0000000..5b4e428
--- /dev/null
+++ b/visad/java3d/GraphicsModeControlJ3D.java
@@ -0,0 +1,1076 @@
+//
+// GraphicsModeControlJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.rmi.*;
+
+import javax.media.j3d.*;
+
+import visad.util.Util;
+
+/**
+   GraphicsModeControlJ3D is the VisAD class for controlling various
+   mode settings for rendering.<P>
+
+   A GraphicsModeControlJ3D is not linked to any DisplayRealType or
+   ScalarMap.  It is linked to a DisplayImpl.<P>
+*/
+public class GraphicsModeControlJ3D extends GraphicsModeControl {
+
+  /** maps GraphicsModeControl line styles to LineAttributes line patterns */
+  static final int[] LINE_PATTERN = {
+    LineAttributes.PATTERN_SOLID, LineAttributes.PATTERN_DASH,
+    LineAttributes.PATTERN_DOT, LineAttributes.PATTERN_DASH_DOT
+  };
+
+  /** for LineAttributes; >= 1.0  @serial */
+  private float lineWidth;
+  /** for PointAttributes; >= 1.0  @serial */
+  private float pointSize;
+  /** for LineAttributes; = solid, dash, dot or dash-dot  @serial */
+  private int lineStyle;
+
+  /** color combination mode for multiple color mappings */
+  private int colorMode;
+  /** true => points in place of lines and surfaces, @serial */
+  private boolean pointMode;
+  /** true => allow use of texture mapping @serial*/
+  private boolean textureEnable;
+  /** true => display X, Y and Z scales @serial*/
+  private boolean scaleEnable;
+
+  /** for TransparencyAttributes; see list below in setTransparencyMode
+      @serial */
+  private int transparencyMode;
+  /** View.PARALLEL_PROJECTION or View.PERSPECTIVE_PROJECTION @serial */
+  private int projectionPolicy;
+  private boolean anti_alias_flag = false;
+  /** PolygonAttributes.POLYGON_FILL, PolygonAttributes.POLYGON_LINE
+      or PolygonAttributes.POLYGON_POINT @serial */
+  private int polygonMode;
+
+  private float polygonOffset;
+
+  private float polygonOffsetFactor;
+
+  /** for rendering missing data as transparent  @serial */
+  private boolean missingTransparent = true;
+  /** for undersampling of curved texture maps @serial */
+  private int curvedSize = 10;
+
+  /** true to adjust projection seam */
+  private boolean adjustProjectionSeam;
+
+  /** mode for Texture3D */
+  private int texture3DMode;
+
+  /** for caching Appearances*/
+  private boolean cacheAppearances = false;
+
+  /** for merging geometries */
+  private boolean mergeGeometries = false;
+
+  /** for depth buffer enabling */
+  private boolean depthBufferEnable = true;
+
+  /**
+   * Construct a GraphicsModeControlJ3D associated with the input display
+   *
+   * @param  d  display associated with this GraphicsModeControlJ3D
+   */
+  public GraphicsModeControlJ3D(DisplayImpl d) {
+    super(d);
+    lineWidth = 1.0f;
+    pointSize = 1.0f;
+    lineStyle = SOLID_STYLE;
+    pointMode = false;
+    textureEnable = true;
+    scaleEnable = false;
+    // NICEST, FASTEST and BLENDED do not solve the depth precedence problem
+    // note SCREEN_DOOR does not seem to work with variable transparency
+    // transparencyMode = TransparencyAttributes.NICEST;
+    transparencyMode = TransparencyAttributes.FASTEST;
+    // transparencyMode = TransparencyAttributes.BLENDED;
+    // transparencyMode = TransparencyAttributes.SCREEN_DOOR;
+    polygonMode = PolygonAttributes.POLYGON_FILL;
+    polygonOffset = Float.NaN;
+    polygonOffsetFactor = 0f;
+    adjustProjectionSeam = true;
+    texture3DMode = STACK2D;
+
+    projectionPolicy = View.PERSPECTIVE_PROJECTION;
+    DisplayRendererJ3D displayRenderer =
+      (DisplayRendererJ3D) getDisplayRenderer();
+    if (displayRenderer != null) {
+      if (displayRenderer.getMode2D()) {
+        projectionPolicy = View.PARALLEL_PROJECTION;
+        // for some strange reason, if we set PERSPECTIVE_PROJECTION at this
+        // point, we can never set PARALLEL_PROJECTION
+        displayRenderer.getView().setProjectionPolicy(projectionPolicy);
+      }
+    }
+  }
+
+  /**
+   * See if the display is being rendered in 2D mode
+   * @see visad.DisplayRenderer#getMode2D
+   *
+   * @return  true if display is rendered as 2D
+   */
+  public boolean getMode2D() {
+    return getDisplayRenderer().getMode2D();
+  }
+
+  /**
+   * Get the current line width used for LineAttributes.  The default
+   * is 1.0.
+   *
+   * @return  line width (>= 1.0)
+   */
+  public float getLineWidth() {
+    return lineWidth;
+  }
+
+  /**
+   * Set the line width used for LineAttributes.  Calls changeControl
+   * and resets the display.
+   *
+   * @param width  width to use (>= 1.0)
+   *
+   * @throws  VisADException   couldn't set the line width on local display
+   * @throws  RemoteException  couldn't set the line width on remote display
+   */
+  public void setLineWidth(float width)
+         throws VisADException, RemoteException {
+    if (width < 1.0f) width = 1.0f;
+    if (Util.isApproximatelyEqual(lineWidth, width)) return;
+    lineWidth = width;
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    DisplayRendererJ3D dr = (DisplayRendererJ3D) getDisplayRenderer();
+    dr.setLineWidth(width);
+
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set the line width used for LineAttributes.   Does not update
+   * display.
+   *
+   * @param width  width to use (>= 1.0)
+   * @param noChange dummy variable
+   */
+  public void setLineWidth(float width, boolean noChange) {
+    if (width < 1.0f) width = 1.0f;
+    lineWidth = width;
+  }
+
+  /**
+   * Get the current point size used for PointAttributes.  The default
+   * is 1.0.
+   *
+   * @return  point size  (>= 1.0)
+   */
+  public float getPointSize() {
+    return pointSize;
+  }
+
+  /**
+   * Set the point size used for PointAttributes.  Calls changeControl
+   * and updates the display.
+   *
+   * @param size  size to use (>= 1.0)
+   *
+   * @throws  VisADException   couldn't set the point size on local display
+   * @throws  RemoteException  couldn't set the point size on remote display
+   */
+  public void setPointSize(float size)
+         throws VisADException, RemoteException {
+    if (size < 1.0f) size = 1.0f;
+    if (Util.isApproximatelyEqual(size, pointSize)) return;
+    pointSize = size;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set the point size used for PointAttributes.  Doesn't update
+   * the display.
+   *
+   * @param size  size to use (>= 1.0)
+   * @param noChange dummy variable
+   */
+  public void setPointSize(float size, boolean noChange) {
+    if (size < 1.0f) size = 1.0f;
+    pointSize = size;
+  }
+
+  /**
+   * Get the current line style used for LineAttributes.  The default
+   * is GraphicsModeControl.SOLID_STYLE.
+   *
+   * @return  line style (SOLID_STYLE, DASH_STYLE, DOT_STYLE or DASH_DOT_STYLE)
+   */
+  public int getLineStyle() {
+    return lineStyle;
+  }
+
+  /**
+   * Set the line style used for LineAttributes.  Calls changeControl
+   * and resets the display.
+   *
+   * @param style  style to use (SOLID_STYLE, DASH_STYLE,
+   *               DOT_STYLE or DASH_DOT_STYLE)
+   *
+   * @throws  VisADException   couldn't set the line style on local display
+   * @throws  RemoteException  couldn't set the line style on remote display
+   */
+  public void setLineStyle(int style)
+         throws VisADException, RemoteException {
+    if (style == lineStyle) return;
+    if (style != SOLID_STYLE && style != DASH_STYLE &&
+      style != DOT_STYLE && style != DASH_DOT_STYLE)
+    {
+      style = SOLID_STYLE;
+    }
+    lineStyle = style;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set the line style used for LineAttributes.   Does not update display.
+   *
+   * @param style  style to use (SOLID_STYLE, DASH_STYLE,
+   *               DOT_STYLE or DASH_DOT_STYLE)
+   * @param noChange dummy variable
+   */
+  public void setLineStyle(int style, boolean noChange) {
+    if (style != SOLID_STYLE && style != DASH_STYLE &&
+      style != DOT_STYLE && style != DASH_DOT_STYLE)
+    {
+      style = SOLID_STYLE;
+    }
+    lineStyle = style;
+  }
+
+  /**
+   * Get the color mode used for combining color values.  The default
+   * is GraphicsModeControl.AVERAGE_COLOR_MODE.
+   *
+   * @return  color mode (AVERAGE_COLOR_MODE or SUM_COLOR_MODE)
+   */
+  public int getColorMode() {
+    return colorMode;
+  }
+
+  /**
+   * Set the color mode used for combining color values.
+   *
+   * @param mode  mode to use (AVERAGE_COLOR_MODE or SUM_COLOR_MODE)
+   */
+  public void setColorMode(int mode)
+         throws VisADException, RemoteException {
+    if (mode != AVERAGE_COLOR_MODE && mode != SUM_COLOR_MODE) {
+      mode = AVERAGE_COLOR_MODE;
+    }
+    if (mode == colorMode) return;
+    colorMode = mode;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Gets the point mode.
+   *
+   * @return  True if the display is using points rather than connected
+   *          lines or surfaces for rendering.
+   */
+  public boolean getPointMode() {
+    return pointMode;
+  }
+
+  /**
+   * Sets the point mode and updates the display.
+   *
+   * @param mode         true if the display should use points rather
+   *                     than connected lines or surfaces for rendering.
+   */
+  public void setPointMode(boolean mode)
+         throws VisADException, RemoteException {
+    if (mode == pointMode) return;
+    pointMode = mode;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set whether texture mapping should be used or not.
+   *
+   * @param  enable   true to use texture mapping (the default)
+   */
+  public void setTextureEnable(boolean enable)
+         throws VisADException, RemoteException {
+    if (enable == textureEnable) return;
+    textureEnable = enable;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * See if texture mapping is enabled or not
+   *
+   * @return    true if texture mapping is enabled.
+   */
+  public boolean getTextureEnable() {
+    return textureEnable;
+  }
+
+  /**
+   *  Toggle the axis scales in the display
+   *
+   * @param  enable    true to enable, false to disable
+   *
+   * @throws  VisADException   couldn't change state of scale enablement
+   * @throws  RemoteException  couldn't change state of scale enablement on
+   *                           remote display
+   */
+  public void setScaleEnable(boolean enable)
+         throws VisADException, RemoteException {
+    if (enable == scaleEnable) return;
+    scaleEnable = enable;
+    getDisplayRenderer().setScaleOn(enable);
+    changeControl(true);
+  }
+
+  /**
+   * Get whether display scales are enabled or not
+   *
+   * @return  true if enabled, otherwise false
+   */
+  public boolean getScaleEnable() {
+    return scaleEnable;
+  }
+
+  /**
+   * Get the current transparency mode
+   *
+   * @return  DisplayImplJ3D.FASTEST, DisplayImplJ3D.NICEST
+   */
+  public int getTransparencyMode() {
+    return transparencyMode;
+  }
+
+  /**
+   * Sets the transparency mode.
+   *
+   * @param   mode   transparency mode to use.   Legal values are
+   *                 DisplayImplJ3D.FASTEST, DisplayImplJ3D.NICEST
+   * @throws  VisADException    bad mode or couldn't create necessary VisAD
+   *                            object
+   * @throws  RemoteException   couldn't create necessary remote object
+   */
+  public void setTransparencyMode(int mode)
+         throws VisADException, RemoteException {
+    if (mode == transparencyMode) return;
+    if (mode == TransparencyAttributes.SCREEN_DOOR ||
+        mode == TransparencyAttributes.BLENDED ||
+        mode == TransparencyAttributes.NONE ||
+        mode == TransparencyAttributes.FASTEST ||
+        mode == TransparencyAttributes.NICEST) {
+      transparencyMode = mode;
+      changeControl(true);
+      getDisplay().reDisplayAll();
+    }
+    else {
+      throw new DisplayException("GraphicsModeControlJ3D." +
+                                 "setTransparencyMode: bad mode");
+    }
+  }
+
+  /**
+   * Sets the projection policy for the display.  PARALLEL_PROJECTION will
+   * display a parallel view while PERSPECTIVE_PROJECTION will create a
+   * perspective view.  The default is a perspective view.
+   *
+   * @param   policy      policy to be used (DisplayImplJ3D.PARALLEL_PROJECTION
+   *                      or DisplayImplJ3D.PERSPECTIVE_PROJECTION
+   *
+   * @throws  VisADException   bad policy or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  change policy on remote display
+   */
+  public void setProjectionPolicy(int policy)
+         throws VisADException, RemoteException {
+    if (policy == projectionPolicy) return;
+    if (policy == View.PARALLEL_PROJECTION ||
+        policy == View.PERSPECTIVE_PROJECTION) {
+      projectionPolicy = policy;
+      DisplayRendererJ3D displayRenderer =
+        (DisplayRendererJ3D) getDisplayRenderer();
+      if (displayRenderer != null) {
+        displayRenderer.getView().setProjectionPolicy(projectionPolicy);
+      }
+      changeControl(true);
+      getDisplay().reDisplayAll();
+    }
+    else {
+      throw new DisplayException("GraphicsModeControlJ3D." +
+                                 "setProjectionPolicy: bad policy");
+    }
+  }
+
+  /**
+   * Get the current projection policy for the display.
+   *
+   * @return  DisplayImplJ3D.PARALLEL_PROJECTION or
+   *          DisplayImplJ3D.PERSPECTIVE_PROJECTION
+   */
+  public int getProjectionPolicy() {
+    return projectionPolicy;
+  }
+
+  /**
+   * Sets the antialiasing flag for the display.
+   *
+   * @param   flag      true to enable antialiasing
+   *
+   * @throws  VisADException   a VisAD error occurred
+   * @throws  RemoteException  change policy on remote display
+   */
+  public void setSceneAntialiasingEnable(boolean flag)
+         throws VisADException, RemoteException {
+    if (flag == anti_alias_flag) return;
+    anti_alias_flag = flag;
+    DisplayRendererJ3D displayRenderer =
+      (DisplayRendererJ3D) getDisplayRenderer();
+    if (displayRenderer != null) {
+      displayRenderer.getView().setSceneAntialiasingEnable(anti_alias_flag);
+    }
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Get the current antialiasing flag for the display.
+   *
+   * @return  true or false
+   */
+  public boolean getSceneAntialiasingEnable() {
+    return anti_alias_flag;
+  }
+
+  /**
+   * Sets the polygon rasterization mode.
+   *
+   * @param  mode   the polygon rasterization mode to be used; one of
+   *                DisplayImplJ3D.POLYGON_FILL, DisplayImplJ3D.POLYGON_LINE,
+   *                or DisplayImplJ3D.POLYGON_POINT
+   *
+   * @throws  VisADException   bad mode or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  can't change mode on remote display
+   */
+  public void setPolygonMode(int mode)
+         throws VisADException, RemoteException {
+    if (mode == polygonMode) return;
+    if (mode == PolygonAttributes.POLYGON_FILL ||
+        mode == PolygonAttributes.POLYGON_LINE ||
+        mode == PolygonAttributes.POLYGON_POINT) {
+      polygonMode = mode;
+      changeControl(true);
+      getDisplay().reDisplayAll();
+    }
+    else {
+      throw new DisplayException("GraphicsModeControlJ3D." +
+                                 "setPolygonMode: bad mode");
+    }
+  }
+
+  /**
+   * Sets the polygon rasterization mode. Does not update display
+   *
+   * @param  mode   the polygon rasterization mode to be used; one of
+   *                DisplayImplJ3D.POLYGON_FILL, DisplayImplJ3D.POLYGON_LINE,
+   *                or DisplayImplJ3D.POLYGON_POINT
+   * @param noChange dummy variable
+   *
+   * @throws  VisADException   bad mode or can't create the necessary VisAD
+   *                           object
+   * @throws  RemoteException  can't change mode on remote display
+   */
+  public void setPolygonMode(int mode, boolean noChange)
+         throws VisADException, RemoteException {
+    if (mode == polygonMode) return;
+    if (mode == PolygonAttributes.POLYGON_FILL ||
+        mode == PolygonAttributes.POLYGON_LINE ||
+        mode == PolygonAttributes.POLYGON_POINT) {
+      polygonMode = mode;
+    }
+  }
+
+  /**
+   * Get the current polygon rasterization mode.
+   *
+   * @return  DisplayImplJ3D.POLYGON_FILL, DisplayImplJ3D.POLYGON_LINE,
+   *          or DisplayImplJ3D.POLYGON_POINT
+   */
+  public int getPolygonMode() {
+    return polygonMode;
+  }
+
+  /**
+   * Sets the polygon offset and updates the display.
+   *
+   * @param  polygonOffset   the polygon offset to be used
+   *
+   * @throws  VisADException   Unable to change offset
+   * @throws  RemoteException  can't change offset on remote display
+   */
+  public void setPolygonOffset(float polygonOffset) 
+         throws VisADException, RemoteException {
+    if (Float.isNaN(polygonOffset)) return;
+    if (Util.isApproximatelyEqual(this.polygonOffset, polygonOffset)) return;
+    this.polygonOffset = polygonOffset;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Sets the polygon offset.  Does not update the display.
+   *
+   * @param  polygonOffset   the polygon offset to be used
+   * @param  noChange   dummy variable
+   *
+   * @throws  VisADException   Unable to change offset
+   * @throws  RemoteException  can't change offset on remote display
+   */
+  public void setPolygonOffset(float polygonOffset, boolean noChange) {
+    this.polygonOffset = polygonOffset;
+  }
+
+  /**
+   * Get the current polygon offset.
+   *
+   * @return  offset 
+   */
+  public float getPolygonOffset() {
+     return polygonOffset;
+  }
+
+  /**
+   * Sets the polygon offset factor and updates the display.
+   *
+   * @param  polygonOffsetFactor   the polygon offset factor to be used
+   *
+   * @throws  VisADException   Unable to change offset factor
+   * @throws  RemoteException  can't change offset factor on remote display
+   */
+  public void setPolygonOffsetFactor(float polygonOffsetFactor) 
+         throws VisADException, RemoteException {
+    if (Float.isNaN(polygonOffsetFactor)) return;
+    if (Util.isApproximatelyEqual(this.polygonOffsetFactor, polygonOffsetFactor)) return;
+    this.polygonOffsetFactor = polygonOffsetFactor;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Sets the polygon offset factor, does not update display.
+   *
+   * @param  polygonOffsetFactor  the polygon offset to be used
+   * @param  noChange   dummy variable
+   *
+   * @throws  VisADException   Unable to change offset factor
+   * @throws  RemoteException  can't change offset factor on remote display
+   */
+  public void setPolygonOffsetFactor(float polygonOffsetFactor, boolean noChange)  {
+    this.polygonOffsetFactor = polygonOffsetFactor;
+  }
+
+  /**
+   * Get the current polygon offset factor.
+   *
+   * @return  offset factor
+   */
+  public float getPolygonOffsetFactor() {
+    return polygonOffsetFactor;
+  }
+
+  public void setDepthBufferEnable(boolean enable) 
+    throws VisADException, RemoteException {
+    this.depthBufferEnable = enable;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  public void setDepthBufferEnable(boolean enable, boolean noChange) {
+    this.depthBufferEnable = enable;
+  }
+
+  public boolean getDepthBufferEnable() {
+    return depthBufferEnable;
+  }
+
+  /**
+   * See whether missing values are rendered as transparent or not.
+   *
+   * @return  true if missing values are transparent.
+   */
+  public boolean getMissingTransparent() {
+    return missingTransparent;
+  }
+
+  /**
+   * Set the transparency of missing values.
+   *
+   * @param  missing   true if missing values should be rendered transparent.
+   */
+  public void setMissingTransparent(boolean missing) {
+    missingTransparent = missing;
+  }
+
+  /**
+   * Get the undersampling factor of surface shape for curved texture maps
+   *
+   * @return  undersampling factor (default 10)
+   */
+  public int getCurvedSize() {
+    return curvedSize;
+  }
+
+  /**
+   * Set the undersampling factor of surface shape for curved texture maps
+   *
+   * @param  curved_size  undersampling factor (default 10)
+   */
+  public void setCurvedSize(int curved_size) {
+    curvedSize = curved_size;
+  }
+
+  /**
+   * See whether VisADGeometryArray.adjustLongitude/Seam should be called.
+   *
+   * @return  true if adjust methods should be called
+   */
+  public boolean getAdjustProjectionSeam() {
+    return adjustProjectionSeam;
+  }
+
+  /**
+   * Set whether VisADGeometryArray.adjustLongitude/adjustSeam should be called.
+   *
+   * @param  adjust  true if adjust methods should be called
+   */
+  public void setAdjustProjectionSeam(boolean adjust) 
+         throws VisADException, RemoteException {
+    if (adjustProjectionSeam == adjust) return;
+    adjustProjectionSeam = adjust;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Set the mode for Texture3D for volume rendering
+   *
+   * @param  mode   mode for Texture3D (STACK2D or TEXTURE3D)
+   *
+   * @throws  VisADException   Unable to change Texture3D mode
+   * @throws  RemoteException  can't change Texture3D mode on remote display
+   */
+  public void setTexture3DMode(int mode)
+         throws VisADException, RemoteException {
+    if (texture3DMode == mode) return;
+    texture3DMode = mode;
+    changeControl(true);
+    getDisplay().reDisplayAll();
+  }
+
+  /**
+   * Get the mode for Texture3D for volume rendering
+   *
+   * @return  mode for Texture3D (e.g., STACK2D or TEXTURE3D)
+   */
+  public int getTexture3DMode() {
+    return texture3DMode;
+  }
+
+  /**
+   * Set whether the Appearances are reused
+   *
+   * @param  cache   true to cache and reuse appearances
+   *
+   * @throws  VisADException   Unable to change caching
+   * @throws  RemoteException  can't change caching on remote display
+   */
+  public void setCacheAppearances(boolean cache) {
+    cacheAppearances = cache;
+  }
+
+  /**
+   * Get whether Appearances are cached or not
+   *
+   * @return  true if caching
+   */
+  public boolean getCacheAppearances() {
+    return cacheAppearances;
+  }
+
+  /**
+   * Set whether Geometries for shapes should be merged into Group if
+   * possible to reduce memory use.
+   *
+   * @param  merge   true to merge geometries if possible
+   *
+   * @throws  VisADException   Unable to change caching
+   * @throws  RemoteException  can't change caching on remote display
+   */
+  public void setMergeGeometries(boolean merge) {
+    mergeGeometries = merge;
+  }
+
+  /**
+   * Set whether Geometries for shapes should be merged into Group
+   *
+   * @return  true if merging is used
+   */
+  public boolean getMergeGeometries() {
+    return mergeGeometries;
+  }
+
+  /** clone this GraphicsModeControlJ3D */
+  public Object clone() {
+    GraphicsModeControlJ3D mode =
+      new GraphicsModeControlJ3D(getDisplay());
+    mode.lineWidth = lineWidth;
+    mode.pointSize = pointSize;
+    mode.pointMode = pointMode;
+    mode.textureEnable = textureEnable;
+    mode.scaleEnable = scaleEnable;
+    mode.transparencyMode = transparencyMode;
+    mode.projectionPolicy = projectionPolicy;
+    mode.missingTransparent = missingTransparent;
+    mode.polygonMode = polygonMode;
+    mode.polygonOffset = polygonOffset;
+    mode.polygonOffsetFactor = polygonOffsetFactor;
+    mode.curvedSize = curvedSize;
+    mode.lineStyle = lineStyle;
+    mode.anti_alias_flag = anti_alias_flag;
+    mode.adjustProjectionSeam = adjustProjectionSeam;
+    mode.texture3DMode = texture3DMode;
+    mode.cacheAppearances = cacheAppearances;
+    mode.mergeGeometries = mergeGeometries;
+    mode.depthBufferEnable = depthBufferEnable;
+    return mode;
+  }
+
+  /**
+   * Copy the state of a remote control to this control
+   *
+   * @param  rmt   remote control to sync with this one
+   * @throws  VisADException  rmt == null or rmt is not a
+   *                          GraphicsModeControlJ3D or couldn't tell if
+   *                          control was changed.
+   */
+  public void syncControl(Control rmt)
+    throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof GraphicsModeControlJ3D)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    GraphicsModeControlJ3D rmtCtl = (GraphicsModeControlJ3D )rmt;
+
+    boolean changed = false;
+    boolean redisplay = false;
+
+    if (!Util.isApproximatelyEqual(lineWidth, rmtCtl.lineWidth)) {
+      changed = true;
+      redisplay = true;
+      lineWidth = rmtCtl.lineWidth;
+    }
+    if (!Util.isApproximatelyEqual(pointSize, rmtCtl.pointSize)) {
+      changed = true;
+      redisplay = true;
+      pointSize = rmtCtl.pointSize;
+    }
+
+    if (pointMode != rmtCtl.pointMode) {
+      changed = true;
+      redisplay = true;
+      pointMode = rmtCtl.pointMode;
+    }
+    if (textureEnable != rmtCtl.textureEnable) {
+      changed = true;
+      redisplay = true;
+      textureEnable = rmtCtl.textureEnable;
+    }
+    if (scaleEnable != rmtCtl.scaleEnable) {
+      changed = true;
+      getDisplayRenderer().setScaleOn(scaleEnable);
+      scaleEnable = rmtCtl.scaleEnable;
+    }
+
+    if (transparencyMode != rmtCtl.transparencyMode) {
+      changed = true;
+      redisplay = true;
+      transparencyMode = rmtCtl.transparencyMode;
+    }
+
+    if (adjustProjectionSeam != rmtCtl.adjustProjectionSeam) {
+      changed = true;
+      redisplay = true;
+      adjustProjectionSeam = rmtCtl.adjustProjectionSeam;
+    }
+
+    if (texture3DMode != rmtCtl.texture3DMode) {
+      changed = true;
+      redisplay = true;
+      texture3DMode = rmtCtl.texture3DMode;
+    }
+
+    if (cacheAppearances != rmtCtl.cacheAppearances) {
+      changed = true;
+      cacheAppearances = rmtCtl.cacheAppearances;
+    }
+
+    if (mergeGeometries != rmtCtl.mergeGeometries) {
+      changed = true;
+      mergeGeometries = rmtCtl.mergeGeometries;
+    }
+
+    if (projectionPolicy != rmtCtl.projectionPolicy) {
+      changed = true;
+      redisplay = true;
+      projectionPolicy = rmtCtl.projectionPolicy;
+
+      DisplayRendererJ3D displayRenderer;
+      displayRenderer = (DisplayRendererJ3D) getDisplayRenderer();
+      if (displayRenderer != null) {
+        displayRenderer.getView().setProjectionPolicy(projectionPolicy);
+      }
+    }
+    if (polygonMode != rmtCtl.polygonMode) {
+      changed = true;
+      polygonMode = rmtCtl.polygonMode;
+    }
+
+    if (missingTransparent != rmtCtl.missingTransparent) {
+      changed = true;
+      missingTransparent = rmtCtl.missingTransparent;
+    }
+
+    if (curvedSize != rmtCtl.curvedSize) {
+      changed = true;
+      curvedSize = rmtCtl.curvedSize;
+    }
+
+    if (!Util.isApproximatelyEqual(polygonOffset, rmtCtl.polygonOffset)) {
+      changed = true;
+      polygonOffset = rmtCtl.polygonOffset;
+    }
+
+    if (!Util.isApproximatelyEqual(polygonOffsetFactor, rmtCtl.polygonOffsetFactor)) {
+      changed = true;
+      polygonOffsetFactor = rmtCtl.polygonOffsetFactor;
+    }
+
+    if (anti_alias_flag != rmtCtl.anti_alias_flag) {
+      changed = true;
+      anti_alias_flag = rmtCtl.anti_alias_flag;
+    }
+
+    if (colorMode != rmtCtl.colorMode) {
+      changed = true;
+      colorMode = rmtCtl.colorMode;
+    }
+
+    if (lineStyle != rmtCtl.lineStyle) {
+      changed = true;
+      lineStyle = rmtCtl.lineStyle;
+    }
+
+    if (changed) {
+      try {
+        changeControl(true);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not indicate that control" +
+                                 " changed: " + re.getMessage());
+      }
+    }
+    if (redisplay) {
+      getDisplay().reDisplayAll();
+    }
+  }
+
+  /**
+   * Check to see if this GraphicsModeControlJ3D is equal to the object
+   * in question.
+   *
+   * @param   o   object in question
+   *
+   * @return  false if the objects are not the same and/or their states
+   *          are not equal.
+   */
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    GraphicsModeControlJ3D gmc = (GraphicsModeControlJ3D )o;
+
+    boolean changed = false;
+
+    if (!Util.isApproximatelyEqual(lineWidth, gmc.lineWidth)) {
+      return false;
+    }
+    if (!Util.isApproximatelyEqual(pointSize, gmc.pointSize)) {
+      return false;
+    }
+
+    if (pointMode != gmc.pointMode) {
+      return false;
+    }
+    if (textureEnable != gmc.textureEnable) {
+      return false;
+    }
+    if (scaleEnable != gmc.scaleEnable) {
+      return false;
+    }
+
+    if (transparencyMode != gmc.transparencyMode) {
+      return false;
+    }
+    if (projectionPolicy != gmc.projectionPolicy) {
+      return false;
+    }
+    if (polygonMode != gmc.polygonMode) {
+      return false;
+    }
+
+    if (missingTransparent != gmc.missingTransparent) {
+      return false;
+    }
+
+    if (curvedSize != gmc.curvedSize) {
+      return false;
+    }
+
+    if (anti_alias_flag != gmc.anti_alias_flag) {
+      return false;
+    }
+
+    if (colorMode != colorMode) {
+      return false;
+    }
+
+    if (lineStyle != lineStyle) {
+      return false;
+    }
+
+    if (!Util.isApproximatelyEqual(polygonOffset, gmc.polygonOffset)) {
+      return false;
+    }
+
+    if (!Util.isApproximatelyEqual(polygonOffsetFactor, gmc.polygonOffsetFactor)) {
+      return false;
+    }
+
+    if (adjustProjectionSeam != gmc.adjustProjectionSeam) {
+      return false;
+    }
+
+    if (texture3DMode != gmc.texture3DMode) {
+      return false;
+    }
+
+    if (cacheAppearances != gmc.cacheAppearances) {
+      return false;
+    }
+
+    if (mergeGeometries != gmc.mergeGeometries) {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Return a string representation of this GraphicsModeControlJ3D
+   *
+   * @return  string that represents the state of this object.
+   */
+  public String toString()
+  {
+    StringBuffer buf = new StringBuffer("GraphicsModeControlJ3D[");
+
+    buf.append("lw ");
+    buf.append(lineWidth);
+    buf.append(",ps ");
+    buf.append(pointSize);
+
+    buf.append(pointMode ? "pm" : "!pm");
+    buf.append(textureEnable ? "te" : "!te");
+    buf.append(scaleEnable ? "se" : "!se");
+    buf.append(missingTransparent ? "mt" : "!mt");
+
+    buf.append(",tm ");
+    buf.append(transparencyMode);
+    buf.append(",pp ");
+    buf.append(projectionPolicy);
+    buf.append(",pm ");
+    buf.append(polygonMode);
+    buf.append(",cm ");
+    buf.append(colorMode);
+    buf.append(",cs ");
+    buf.append(curvedSize);
+    buf.append(",po ");
+    buf.append(polygonOffset);
+    buf.append(",pof ");
+    buf.append(polygonOffsetFactor);
+    buf.append(adjustProjectionSeam ? "as" : "!as");
+    buf.append(",t3dm ");
+    buf.append(texture3DMode);
+    buf.append(",ca ");
+    buf.append(cacheAppearances);
+    buf.append(",mg ");
+    buf.append(mergeGeometries);
+
+    buf.append(']');
+    return buf.toString();
+  }
+}
diff --git a/visad/java3d/ImmersaDeskDisplayRendererJ3D.java b/visad/java3d/ImmersaDeskDisplayRendererJ3D.java
new file mode 100644
index 0000000..d3e5a39
--- /dev/null
+++ b/visad/java3d/ImmersaDeskDisplayRendererJ3D.java
@@ -0,0 +1,299 @@
+//
+// ImmersaDeskDisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+
+/*
+WandBehaviorJ3D extends MouseBehaviorJ3D:
+  how does it get wake up? must poll trackd
+
+  draw ray from wand for direct manipulation (right button)
+  also to drive cursor location from wand (center button)
+  translate (left button)
+
+connect head tracker to DisplayRendererJ3D.vpTrans
+*/
+
+/**
+ * <CODE>ImmersaDeskDisplayRendererJ3D</CODE> is the VisAD class for the
+ * ImmersaDesk background and metadata rendering algorithm under Java3D.<P>
+ */
+public class ImmersaDeskDisplayRendererJ3D extends DisplayRendererJ3D {
+
+  /** color of box and cursor */
+  private ColoringAttributes box_color = null;
+  private ColoringAttributes cursor_color = null;
+
+  /** line of box and cursor */
+  private LineAttributes box_line = null;
+  private LineAttributes cursor_line = null;
+
+  /** ray information */
+  private LineArray ray_geometry = null;
+  private ColoringAttributes ray_color = null;
+  private Switch ray_switch = null;
+  private BranchGroup ray_on = null, ray_off = null;
+  /** on / off state of ray */
+  private boolean rayOn = false;
+
+  private WandBehaviorJ3D wand = null; // for wand interactions
+
+  /**
+   * This is the ImmersaDesk <CODE>DisplayRenderer</CODE>
+   * for <CODE>DisplayImplJ3D</CODE>.
+   * It draws a 3-D cube around the scene.<P>
+   * The left wand button controls the projection by
+   * translating in direction wand is pointing
+   * The center wand button activates and controls the
+   * 3-D cursor by translating the cursor with wand
+   * The right wand button is used for direct manipulation by clicking on
+   * the depiction of a <CODE>Data</CODE> object and dragging or re-drawing
+   * it.<P>
+   * Cursor and direct manipulation locations are displayed in RealType
+   * values.<P>
+   * <CODE>BadMappingExceptions</CODE> and
+   * <CODE>UnimplementedExceptions</CODE> are displayed<P>
+   */
+  public ImmersaDeskDisplayRendererJ3D (int tracker_shmkey, int controller_shmkey)
+         throws VisADException {
+    super();
+    // create WandBehaviorJ3D for wand interactions
+    wand = new WandBehaviorJ3D(this, tracker_shmkey, controller_shmkey);
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root;
+   * create 3-D box, lights and <CODE>MouseBehaviorJ3D</CODE> for
+   * embedded user interface.
+   * @param v
+   * @param vpt
+   * @param c
+   * @return Scene graph root.
+   */
+  public BranchGroup createSceneGraph(View v, TransformGroup vpt,
+                                      VisADCanvasJ3D c) {
+    BranchGroup root = getRoot();
+    if (root != null) return root;
+
+    getDisplay().setMouseBehavior(wand); // OK - just for transforms
+    box_color = new ColoringAttributes();
+    cursor_color = new ColoringAttributes();
+    root = createBasicSceneGraph(v, vpt, c, wand, box_color, cursor_color); // OK
+    TransformGroup trans = getTrans();
+    wand.initialize();
+
+    // create the box containing data depictions
+    LineArray box_geometry = new LineArray(24, LineArray.COORDINATES);
+
+    // WLH 24 Nov 2000
+    box_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE);
+    box_geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // box_geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+
+    box_geometry.setCoordinates(0, box_verts);
+    Appearance box_appearance = new Appearance();
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    box_line = new LineAttributes();
+    box_line.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
+    box_appearance.setLineAttributes(box_line);
+
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    float[] ctlBox = getRendererControl().getBoxColor();
+    box_color.setColor(ctlBox[0], ctlBox[1], ctlBox[2]);
+    box_appearance.setColoringAttributes(box_color);
+    Shape3D box = new Shape3D(box_geometry, box_appearance);
+    box.setCapability(Shape3D.ALLOW_GEOMETRY_READ); // WLH 24 Nov 2000
+    box.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    BranchGroup box_on = getBoxOnBranch();
+    box_on.addChild(box);
+
+    Appearance cursor_appearance = new Appearance();
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    cursor_line = new LineAttributes();
+    cursor_line.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
+    cursor_appearance.setLineAttributes(cursor_line);
+
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    float[] ctlCursor = getRendererControl().getCursorColor();
+    cursor_color.setColor(ctlCursor[0], ctlCursor[1], ctlCursor[2]);
+    cursor_appearance.setColoringAttributes(cursor_color);
+
+    BranchGroup cursor_on = getCursorOnBranch();
+    LineArray cursor_geometry = new LineArray(6, LineArray.COORDINATES);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // cursor_geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+    
+    cursor_geometry.setCoordinates(0, cursor_verts);
+    Shape3D cursor = new Shape3D(cursor_geometry, cursor_appearance);
+    cursor.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    cursor.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    cursor_on.addChild(cursor);
+
+    // create the ray
+    ray_switch = new Switch();
+    ray_switch.setCapability(Switch.ALLOW_SWITCH_READ);
+    ray_switch.setCapability(Switch.ALLOW_SWITCH_WRITE);
+    trans.addChild(ray_switch);
+    ray_on = new BranchGroup();
+    ray_on.setCapability(Group.ALLOW_CHILDREN_READ);
+    ray_on.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    ray_off = new BranchGroup();
+    ray_off.setCapability(Group.ALLOW_CHILDREN_READ);
+    ray_switch.addChild(ray_off);
+    ray_switch.addChild(ray_on);
+    ray_switch.setWhichChild(1); // initially on
+    ray_geometry = new LineArray(2, LineArray.COORDINATES);
+    ray_geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    ray_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    ray_geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    ray_geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    ray_geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // ray_geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    ray_geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+    ray_geometry.setCoordinates(0, init_ray_verts);
+    ray_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    ray_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE);
+    Appearance ray_appearance = new Appearance();
+    ray_color = new ColoringAttributes();
+    ray_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    ray_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    ray_color.setColor(1.0f, 1.0f, 1.0f); // white ray
+    ray_appearance.setColoringAttributes(ray_color);
+    Shape3D ray = new Shape3D(ray_geometry, ray_appearance);
+    ray.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    ray.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    ray_on.addChild(ray);
+
+    // create ambient light, directly under root (not transformed)
+/* WLH 27 Jan 98
+    Color3f color = new Color3f(0.4f, 0.4f, 0.4f);
+*/
+    Color3f color = new Color3f(0.6f, 0.6f, 0.6f);
+    AmbientLight light = new AmbientLight(color);
+    BoundingSphere bounds =
+      new BoundingSphere(new Point3d(0.0,0.0,0.0), 2000000.0);
+    light.setInfluencingBounds(bounds);
+    root.addChild(light);
+
+    // create directional lights, directly under root (not transformed)
+    Color3f dcolor = new Color3f(0.9f, 0.9f, 0.9f);
+    Vector3f direction1 = new Vector3f(0.0f, 0.0f, 1.0f);
+    Vector3f direction2 = new Vector3f(0.0f, 0.0f, -1.0f);
+    DirectionalLight light1 =
+      new DirectionalLight(true, dcolor, direction1);
+    light1.setInfluencingBounds(bounds);
+    DirectionalLight light2 =
+      new DirectionalLight(true, dcolor, direction2);
+    light2.setInfluencingBounds(bounds);
+    root.addChild(light1);
+    root.addChild(light2);
+
+    return root;
+  }
+
+  public void setRayOn(boolean on, float[] ray_verts) {
+    rayOn = on;
+    if (ray_switch != null) {
+      if (on) {
+        ray_geometry.setCoordinates(0, ray_verts);
+        ray_switch.setWhichChild(1); // set ray on
+      }
+      else {
+        ray_switch.setWhichChild(0); // set ray off
+      }
+    }
+  }
+
+  // WLH 24 Nov 2000
+  public void setBoxAspect(double[] aspect) {
+    float[] new_verts = new float[box_verts.length];
+    for (int i=0; i<box_verts.length; i+=3) {
+      new_verts[i] = (float) (box_verts[i] * aspect[0]);
+      new_verts[i+1] = (float) (box_verts[i+1] * aspect[1]);
+      new_verts[i+2] = (float) (box_verts[i+2] * aspect[2]);
+    }
+    BranchGroup box_on = getBoxOnBranch();
+    Shape3D box = (Shape3D) box_on.getChild(0);
+    LineArray box_geometry = (LineArray) box.getGeometry();
+    box_geometry.setCoordinates(0, new_verts);
+  }
+
+  // WLH 2 Dec 2002 in response to qomo2.txt
+  public void setLineWidth(float width) {
+    box_line.setLineWidth(width);
+    cursor_line.setLineWidth(width);
+  }
+
+  private static final float[] box_verts = {
+     // front face
+         -1.0f, -1.0f,  1.0f,                       -1.0f,  1.0f,  1.0f,
+         -1.0f,  1.0f,  1.0f,                        1.0f,  1.0f,  1.0f,
+          1.0f,  1.0f,  1.0f,                        1.0f, -1.0f,  1.0f,
+          1.0f, -1.0f,  1.0f,                       -1.0f, -1.0f,  1.0f,
+     // back face
+         -1.0f, -1.0f, -1.0f,                       -1.0f,  1.0f, -1.0f,
+         -1.0f,  1.0f, -1.0f,                        1.0f,  1.0f, -1.0f,
+          1.0f,  1.0f, -1.0f,                        1.0f, -1.0f, -1.0f,
+          1.0f, -1.0f, -1.0f,                       -1.0f, -1.0f, -1.0f,
+     // connectors
+         -1.0f, -1.0f,  1.0f,                       -1.0f, -1.0f, -1.0f,
+         -1.0f,  1.0f,  1.0f,                       -1.0f,  1.0f, -1.0f,
+          1.0f,  1.0f,  1.0f,                        1.0f,  1.0f, -1.0f,
+          1.0f, -1.0f,  1.0f,                        1.0f, -1.0f, -1.0f
+  };
+
+  private static final float[] cursor_verts = {
+          0.0f,  0.0f,  0.1f,                        0.0f,  0.0f, -0.1f,
+          0.0f,  0.1f,  0.0f,                        0.0f, -0.1f,  0.0f,
+          0.1f,  0.0f,  0.0f,                       -0.1f,  0.0f,  0.0f
+  };
+
+  private static final float[] init_ray_verts = {
+          0.0f,  0.0f,  0.0f,                        0.0f,  0.0f, -10.0f
+  };
+
+}
+
diff --git a/visad/java3d/KeyboardBehaviorJ3D.java b/visad/java3d/KeyboardBehaviorJ3D.java
new file mode 100644
index 0000000..7f82333
--- /dev/null
+++ b/visad/java3d/KeyboardBehaviorJ3D.java
@@ -0,0 +1,442 @@
+//
+//  KeyboardBehaviorJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import java.awt.AWTEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.rmi.RemoteException;
+import java.util.Enumeration;
+
+import javax.media.j3d.Behavior;
+import javax.media.j3d.BoundingSphere;
+import javax.media.j3d.Bounds;
+import javax.media.j3d.WakeupCondition;
+import javax.media.j3d.WakeupCriterion;
+import javax.media.j3d.WakeupOnAWTEvent;
+import javax.media.j3d.WakeupOr;
+import javax.vecmath.Point3d;
+
+import visad.DisplayEvent;
+import visad.DisplayImpl;
+import visad.DisplayRenderer;
+import visad.KeyboardBehavior;
+import visad.MouseBehavior;
+import visad.ProjectionControl;
+import visad.VisADException;
+
+/**
+ *  KeyboardBehaviorJ3D is the VisAD class for keyboard control of
+ *  rotation, translation, and zoom of display in Java3D.
+ *  @author Troy Sandblom   NCAR/RAP May, 2000
+ *  @author Don Murray (adapted to VisAD with input from Curtis)
+ */
+public class KeyboardBehaviorJ3D extends Behavior 
+  implements KeyboardBehavior 
+{
+
+  private ProjectionControl proj;
+  private DisplayRenderer displayRenderer;
+  private MouseBehavior mouseBehavior;
+
+  private double rotateAmount = 5.0;
+  private double scaleAmount = .05;
+  private double transAmount = .1;
+
+  /** Identifier for function to rotate positively around the Z viewing axis*/
+  public static final int ROTATE_Z_POS = 7;
+
+  /** Identifier for function to rotate negatively around the Z viewing axis*/
+  public static final int ROTATE_Z_NEG = 8;
+
+  /** Identifier for function to rotate positively around the X viewing axis*/
+  public static final int ROTATE_X_POS = 9;
+
+  /** Identifier for function to rotate negatively around the X viewing axis*/
+  public static final int ROTATE_X_NEG = 10;
+
+  /** Identifier for function to rotate positively around the Y viewing axis*/
+  public static final int ROTATE_Y_POS = 11;
+
+  /** Identifier for function to rotate negatively around the Y viewing axis*/
+  public static final int ROTATE_Y_NEG = 12;
+
+/* WLH 19 Feb 2001
+  // Maximum number of functions for this behavior
+  private final int MAX_FUNCTIONS = 13;
+
+  private int[] functionKeys = new int[MAX_FUNCTIONS];
+  private int[] functionMods = new int[MAX_FUNCTIONS];
+*/
+  private int MAX_FUNCTIONS;
+  private int[] functionKeys = null;
+  private int[] functionMods = null;
+ 
+  /**
+   *  Condition that causes this Behavior to wake up.
+   */
+  protected WakeupCondition wakeupCondition = null;
+    
+  /**
+   * Construct a new keyboard behavior for the DisplayRenderer.  You
+   * need to add the behavior to the DisplayRenderer if you want to 
+   * use it.  Default key assignments use the arrow keys to simulate
+   * the default VisAD mouse behavior.<P>
+   * Rotation (3D renderer only):
+   * <UL>
+   * <LI>Arrow keys 
+   *     - rotate up (X_NEG), down(X_POS), left(Y_NEG), right(Y_POS)
+   * </UL>
+   * Zoom:
+   * <UL>
+   * <LI>Shift + Up arrow - zoom in (ZOOM_IN)
+   * <LI>Shift + Down arrow - zoom out  (ZOOM_OUT)
+   * </UL>
+   * Rotate:
+   * <UL>
+   * <LI>Shift + Left arrow - rotate left around axis perpendicular to screen
+   *                          (Z_POS)
+   * <LI>Shift + Right arrow - rotate right around axis perpendicular 
+   *                           to screen (Z_NEG)
+   * </UL>
+   * Translate:
+   * <UL>
+   * <LI>Ctrl + Arrow keys - translate up, down, left, right
+   * </UL>
+   * Reset:
+   * <UL>
+   * <LI>Ctrl + R key - reset to original projection  (RESET)
+   * </UL>
+   * <P>
+   * @see  DisplayRendererJ3D#addKeyboardBehavior(KeyboardBehaviorJ3D behavior)
+   * @see  #mapKeyToFunction(int function, int keycode, int modifiers)  to 
+   *       change default key to function mappings
+   * @param  r  DisplayRenderer to use.
+   */
+  public KeyboardBehaviorJ3D(DisplayRendererJ3D r) {
+    displayRenderer = r;
+    boolean mode2D = displayRenderer.getMode2D();
+    proj = displayRenderer.getDisplay().getProjectionControl();
+    mouseBehavior = displayRenderer.getMouseBehavior();
+    
+    WakeupCriterion[] wakeupCriteria = {
+      new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED),
+      new WakeupOnAWTEvent(KeyEvent.KEY_RELEASED),
+    };
+    wakeupCondition = new WakeupOr(wakeupCriteria);
+
+    Bounds bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
+                                       2000000.0);
+    this.setSchedulingBounds(bounds);
+
+    // WLH 19 Feb 2001
+    MAX_FUNCTIONS = 13;
+    functionKeys = new int[MAX_FUNCTIONS];
+    functionMods = new int[MAX_FUNCTIONS];
+
+    // initialize array functions
+    mapKeyToFunction(
+      TRANSLATE_UP, KeyEvent.VK_UP, 
+        (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK);
+    mapKeyToFunction(
+      TRANSLATE_DOWN, KeyEvent.VK_DOWN, 
+        (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK);
+    mapKeyToFunction(
+      TRANSLATE_LEFT, KeyEvent.VK_LEFT, 
+        (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK);
+    mapKeyToFunction(
+      TRANSLATE_RIGHT, KeyEvent.VK_RIGHT, 
+        (mode2D == true) ? NO_MASK : InputEvent.CTRL_MASK);
+    mapKeyToFunction(ZOOM_IN, KeyEvent.VK_UP, InputEvent.SHIFT_MASK);
+    mapKeyToFunction(ZOOM_OUT, KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK);
+    mapKeyToFunction(RESET, KeyEvent.VK_R, InputEvent.CTRL_MASK);
+    mapKeyToFunction(ROTATE_X_POS, KeyEvent.VK_DOWN, NO_MASK);
+    mapKeyToFunction(ROTATE_X_NEG, KeyEvent.VK_UP, NO_MASK);
+    mapKeyToFunction(ROTATE_Y_POS, KeyEvent.VK_LEFT, NO_MASK);
+    mapKeyToFunction(ROTATE_Y_NEG, KeyEvent.VK_RIGHT, NO_MASK);
+    mapKeyToFunction(
+      ROTATE_Z_POS, KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK);
+    mapKeyToFunction(
+      ROTATE_Z_NEG, KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK);
+  }
+
+  // WLH 19 Feb 2001
+  public KeyboardBehaviorJ3D(DisplayRendererJ3D r, int num_functions) {
+    displayRenderer = r;
+    boolean mode2D = displayRenderer.getMode2D();
+    proj = displayRenderer.getDisplay().getProjectionControl();
+    mouseBehavior = displayRenderer.getMouseBehavior();
+
+    WakeupCriterion[] wakeupCriteria = {
+      new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED),
+      new WakeupOnAWTEvent(KeyEvent.KEY_RELEASED),
+    };
+    wakeupCondition = new WakeupOr(wakeupCriteria);
+
+    Bounds bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),
+                                       2000000.0);
+    this.setSchedulingBounds(bounds);
+
+    MAX_FUNCTIONS = num_functions;
+    functionKeys = new int[MAX_FUNCTIONS];
+    functionMods = new int[MAX_FUNCTIONS];
+  }
+
+  /**
+   * Initialize this behavior. NOTE: Applications should not call 
+   * this method. It is called by the Java 3D behavior scheduler.
+   */
+  public void initialize() {
+    wakeupOn(wakeupCondition);
+  }
+
+  /**
+   * Process a stimulus meant for this behavior.  This method is
+   * invoked when a key is pressed. NOTE: Applications should not 
+   * call this method. It is called by the Java 3D behavior scheduler.
+   * @param criteria  an enumeration of triggered wakeup criteria
+   */
+  public void processStimulus(Enumeration criteria) {
+    WakeupOnAWTEvent event;
+    WakeupCriterion genericEvent;
+    AWTEvent[] events;
+
+    while (criteria.hasMoreElements()) {
+      genericEvent = (WakeupCriterion) criteria.nextElement();
+      if (genericEvent instanceof WakeupOnAWTEvent) {
+        event = (WakeupOnAWTEvent) genericEvent;
+        events = event.getAWTEvent();
+
+        //  Process each event
+        for (int i = 0; i < events.length; i++) {
+          if (events[i] instanceof KeyEvent)
+            processKeyEvent((KeyEvent)events[i]);
+        }
+      }
+      wakeupOn(wakeupCondition);
+    }
+  }
+
+  /**
+   * Maps key represented by keycode & modifiers to the given function.
+   * Each function can only have one key/modifier combination assigned 
+   * to it at a time.
+   * @see java.awt.event.KeyEvent
+   * @see java.awt.event.InputEvent
+   * @param  function  keyboard function (ROTATE_X_POS, ZOOM_IN, etc)
+   * @param  keycode   <CODE>KeyEvent</CODE> virtual keycodes 
+   * @param  modifiers <CODE>InputEvent</CODE> key mask
+   */
+  public void mapKeyToFunction(int function, int keycode, int modifiers) {
+     if (function < 0 || function >= MAX_FUNCTIONS) return;
+     functionKeys[function] = keycode;
+     functionMods[function] = modifiers;
+  }
+
+  /**
+   *  Process a key event.  Determines whether a meaningful key was pressed.
+   *  @param  event  KeyEvent stimulus
+   */
+  
+  public void processKeyEvent(KeyEvent event) {
+    int id = event.getID();
+
+    if (id == KeyEvent.KEY_PRESSED) {
+      int modifiers = event.getModifiers();
+      int keyCode = event.getKeyCode();
+
+      // determine whether a meaningful key was pressed
+      for (int i=0; i<MAX_FUNCTIONS; i++) {
+         if (functionKeys[i] == keyCode && (modifiers == functionMods[i])) {
+          execFunction(i);
+          break;
+         }
+      }
+    }
+
+    // notify DisplayListeners of key event
+    int d_id = -1;
+    if (id == KeyEvent.KEY_PRESSED) d_id = DisplayEvent.KEY_PRESSED;
+    else if (id == KeyEvent.KEY_RELEASED) d_id = DisplayEvent.KEY_RELEASED;
+    if (d_id != -1) {
+      try {
+        DisplayImpl display = displayRenderer.getDisplay();
+        DisplayEvent e = new DisplayEvent(display, d_id, event);
+        display.notifyListeners(e);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+  }
+
+  /** 
+   * Executes the given function. 
+   * @param  function   function to perform (TRANSLATE_UP, ZOOM_IN, etc)
+   */
+  public void execFunction(int function) {
+
+    double transx = 0.0;
+    double transy = 0.0;
+    double scale = 1.0;
+    double anglex = 0.0;
+    double angley = 0.0;
+    double anglez = 0.0;
+
+    double [] t1 = null;
+    double [] tstart = proj.getMatrix();
+
+    double[] transA         = { 0.0, 0.0, 0.0 };
+    double[] rotA           = { 0.0, 0.0, 0.0 };
+    double[] scaleA         = { 0.0, 0.0, 0.0 };
+
+    MouseBehaviorJ3D.unmake_matrix(rotA,  scaleA,
+                                   transA,tstart);
+
+    double rotateAngle = rotateAmount;
+    if(displayRenderer.getScaleRotation()) {
+    //Scale down the rotation angle when we are zoomed in
+        rotateAngle = rotateAmount/scaleA[0];
+    }
+
+
+
+    boolean wasRotate = false;
+    switch (function) {
+
+      case ROTATE_X_NEG:
+        if (displayRenderer.getMode2D()) break;
+        wasRotate = true;
+        anglex += rotateAngle;
+        t1 = mouseBehavior.make_matrix(
+                  anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case ROTATE_X_POS:
+        if (displayRenderer.getMode2D()) break;
+        wasRotate = true;
+        anglex -= rotateAngle;
+        t1 = mouseBehavior.make_matrix(
+                  anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case ROTATE_Y_POS:
+        if (displayRenderer.getMode2D()) break;
+        wasRotate = true;
+        angley += rotateAngle;
+        t1 = mouseBehavior.make_matrix(
+                  anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case ROTATE_Y_NEG:
+        if (displayRenderer.getMode2D()) break;
+        wasRotate = true;
+        angley -= rotateAngle;
+        t1 = mouseBehavior.make_matrix(
+                  anglex, angley, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case ROTATE_Z_POS:
+        wasRotate = true;
+        anglez -= rotateAngle;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, anglez, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case ROTATE_Z_NEG:
+        wasRotate = true;
+        anglez += rotateAngle;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, anglez, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case TRANSLATE_UP:
+        transy += transAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case TRANSLATE_DOWN:
+        transy -= transAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case TRANSLATE_LEFT:
+        transx -= transAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case TRANSLATE_RIGHT:
+        transx += transAmount;
+        t1 = mouseBehavior.make_matrix( 
+                 0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+        break;
+      case ZOOM_IN:
+        scale += scaleAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0);
+        break;
+      case ZOOM_OUT:
+        scale -= scaleAmount;
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0);
+        break;
+      case RESET:  
+        tstart = proj.getSavedProjectionMatrix();
+        t1 = mouseBehavior.make_matrix(
+                  0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      default:
+        break;
+    }
+
+
+    //        t1 = mouseBehavior.make_matrix(
+    //                  0.0, 0.0, 0.0, 1.0, transx, transy, 0.0);
+
+
+    double[] t2 = null;
+
+
+    if (t1 != null) {
+
+        if(displayRenderer.getRotateAboutCenter() && wasRotate) {
+            t2 = mouseBehavior.make_translate(-transA[0], -transA[1], -transA[2]);
+            tstart = mouseBehavior.multiply_matrix(t2, tstart);
+            t2 = mouseBehavior.make_translate(transA[0], transA[1], transA[2]);
+        }
+
+
+        t1 = mouseBehavior.multiply_matrix(t1, tstart);
+
+
+        if(t2!=null) {
+            t1 = mouseBehavior.multiply_matrix(t2,t1);
+        }
+
+
+        try {
+            proj.setMatrix(t1);
+        } catch (VisADException e) {
+        } catch (RemoteException e) {
+        }
+    }
+    
+  }
+}
diff --git a/visad/java3d/MouseBehaviorJ3D.java b/visad/java3d/MouseBehaviorJ3D.java
new file mode 100644
index 0000000..88511c8
--- /dev/null
+++ b/visad/java3d/MouseBehaviorJ3D.java
@@ -0,0 +1,818 @@
+//
+// MouseBehaviorJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.lang.reflect.*;
+import java.awt.event.*;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+
+import java.awt.*;
+import java.util.*;
+
+/**
+   MouseBehaviorJ3D is the VisAD class for mouse behaviors for Java3D
+*/
+
+/*
+   On MOUSE_PRESSED event with left button,
+   ((InputEvent) events[i]).getModifiers() returns 0 and
+   should return 16 ( = InputEvent.BUTTON1_MASK ).
+   ((InputEvent) events[i]).getModifiers() correctly
+   returns 16 on MOUSE_RELEASED with left button.
+*/
+public class MouseBehaviorJ3D extends Behavior
+       implements MouseBehavior {
+
+  /** wakeup condition for MouseBehaviorJ3D */
+  private WakeupOr wakeup;
+  /** DisplayRenderer for Display */
+  DisplayRendererJ3D display_renderer;
+  DisplayImpl display;
+
+  MouseHelper helper = null;
+
+  /**
+   * Default Constructor
+   */
+  public MouseBehaviorJ3D() {
+  }
+
+  /**
+   * Construct a MouseBehavior for the DisplayRenderer specified
+   * @param  r  DisplayRenderer to use
+   */
+  public MouseBehaviorJ3D(DisplayRendererJ3D r) {
+    this(r, MouseHelper.class);
+  }
+
+  /**
+   * Construct a MouseBehavior for the DisplayRenderer specified
+   * @param  r  DisplayRenderer to use
+   * @param  mhClass  MouseHelper subclass to use
+   */
+  public MouseBehaviorJ3D(DisplayRendererJ3D r, Class mhClass) {
+    try {
+      Class[] param = new Class[] {DisplayRenderer.class, MouseBehavior.class};
+      Constructor mhConstructor =
+        mhClass.getConstructor(param);
+      helper = (MouseHelper) mhConstructor.newInstance(new Object[] {r, this});
+    }
+    catch (Exception e) {
+      throw new VisADError("cannot construct " + mhClass);
+    }
+    // helper = new MouseHelper(r, this);
+
+    display_renderer = r;
+    display = display_renderer.getDisplay();
+
+    WakeupCriterion[] conditions = new WakeupCriterion[6];
+    conditions[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED);
+    conditions[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_ENTERED);
+    conditions[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_EXITED);
+    conditions[3] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED);
+    conditions[4] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED);
+    conditions[5] = new WakeupOnAWTEvent(MouseEvent.MOUSE_MOVED);
+    wakeup = new WakeupOr(conditions);
+  }
+
+  // WLH 17 Dec 2001
+  public void destroy() {
+    helper = null;
+    display = null;
+    display_renderer = null;
+  }
+
+  /**
+   * Get the helper class used by this MouseBehavior.  
+   * The <CODE>MouseHelper</CODE> defines the actions taken based
+   * on <CODE>MouseEvent</CODE>s.
+   * @return  <CODE>MouseHelper</CODE> being used.
+   */
+  public MouseHelper getMouseHelper() {
+    return helper;
+  }
+
+  /**
+   * Initialize this behavior. NOTE: Applications should not call 
+   * this method. It is called by the Java 3D behavior scheduler.
+   */
+  public void initialize() {
+    setWakeup();
+  }
+
+  /**
+   * Process a stimulus meant for this behavior.  This method is
+   * invoked when a key is pressed. NOTE: Applications should not 
+   * call this method. It is called by the Java 3D behavior scheduler.
+   * @param criteria  an enumeration of triggered wakeup criteria
+   */
+  public void processStimulus(Enumeration criteria) {
+    while (criteria.hasMoreElements()) {
+      WakeupCriterion wakeup = (WakeupCriterion) criteria.nextElement();
+      if (!(wakeup instanceof WakeupOnAWTEvent)) {
+        System.out.println("MouseBehaviorJ3D.processStimulus: non-" +
+                            "WakeupOnAWTEvent");
+      }
+      else {
+        AWTEvent[] events = ((WakeupOnAWTEvent) wakeup).getAWTEvent();
+        for (int i=0; i<events.length; i++) {
+          helper.processEvent(events[i]);
+        }
+      }
+    }
+    setWakeup();
+  }
+
+  /**
+   * Return the VisAD ray corresponding to the component coordinates.
+   * @param  screen_x  x coordinate of the component
+   * @param  screen_y  y coordinate of the component
+   * @return  corresponding VisADRay
+   * @see visad.VisADRay
+   * @see visad.LocalDisplay#getComponent()
+   */
+  public VisADRay findRay(int screen_x, int screen_y) {
+    // System.out.println("findRay " + screen_x + " " + screen_y);
+    View view = display_renderer.getView();
+    Canvas3D canvas = display_renderer.getCanvas();
+    Point3d position = new Point3d();
+    canvas.getPixelLocationInImagePlate(screen_x, screen_y, position);
+    Point3d eye_position = new Point3d();
+    canvas.getCenterEyeInImagePlate(eye_position);
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    t.transform(position);
+    t.transform(eye_position);
+
+    if (display.getGraphicsModeControl().getProjectionPolicy() ==
+        View.PARALLEL_PROJECTION) {
+      eye_position = new Point3d(position.x, position.y,
+                                 position.z + 1.0f);
+    }
+
+    TransformGroup trans = display_renderer.getTrans();
+    Transform3D tt = new Transform3D();
+    trans.getTransform(tt);
+    tt.invert();
+    tt.transform(position);
+    tt.transform(eye_position);
+
+    // new eye_position = 2 * position - old eye_position
+    Vector3d vector = new Vector3d(position.x - eye_position.x,
+                                   position.y - eye_position.y,
+                                   position.z - eye_position.z);
+    vector.normalize();
+    VisADRay ray = new VisADRay();
+    ray.position[0] = position.x;
+    ray.position[1] = position.y;
+    ray.position[2] = position.z;
+    ray.vector[0] = vector.x;
+    ray.vector[1] = vector.y;
+    ray.vector[2] = vector.z;
+    // PickRay ray = new PickRay(position, vector);
+    return ray;
+  }
+
+  /**
+   * Return the VisAD ray corresponding to the VisAD cursor coordinates.
+   * @param  cursor  array (x,y,z) of cursor location
+   * @return  corresponding VisADRay
+   * @see visad.VisADRay
+   * @see visad.DisplayRenderer#getCursor()
+   */
+  public VisADRay cursorRay(double[] cursor) {
+    View view = display_renderer.getView();
+    Canvas3D canvas = display_renderer.getCanvas();
+    // note position already in Vworld coordinates
+    Point3d position = new Point3d(cursor[0], cursor[1], cursor[2]);
+    Point3d eye_position = new Point3d();
+    canvas.getCenterEyeInImagePlate(eye_position);
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    t.transform(eye_position);
+
+    TransformGroup trans = display_renderer.getTrans();
+    Transform3D tt = new Transform3D();
+    trans.getTransform(tt);
+    tt.transform(position);
+
+    if (display.getGraphicsModeControl().getProjectionPolicy() ==
+        View.PARALLEL_PROJECTION) {
+      eye_position = new Point3d(position.x, position.y,
+                                 position.z + 1.0f);
+    }
+
+    tt.invert();
+    tt.transform(position);
+    tt.transform(eye_position);
+
+    // new eye_position = 2 * position - old eye_position
+    Vector3d vector = new Vector3d(position.x - eye_position.x,
+                                   position.y - eye_position.y,
+                                   position.z - eye_position.z);
+    vector.normalize();
+    VisADRay ray = new VisADRay();
+    ray.position[0] = eye_position.x;
+    ray.position[1] = eye_position.y;
+    ray.position[2] = eye_position.z;
+    ray.vector[0] = vector.x;
+    ray.vector[1] = vector.y;
+    ray.vector[2] = vector.z;
+    // PickRay ray = new PickRay(eye_position, vector);
+    return ray;
+  }
+
+  private Method getPixelLocationFromImagePlateMethod = null;
+
+  /**
+   * Return the screen coordinates corresponding to the VisAD coordinates.
+   * @param  position  array of VisAD coordinates
+   * @return  corresponding (x, y) screen coordinates
+   */
+  public int[] getScreenCoords(double[] position) {
+    if (getPixelLocationFromImagePlateMethod == null) {
+      try {
+        Class canvas3DClass = Class.forName("javax.media.j3d.Canvas3D");
+        Class[] param =
+          {javax.vecmath.Point3d.class, javax.vecmath.Point2d.class};
+        getPixelLocationFromImagePlateMethod =
+          canvas3DClass.getMethod("getPixelLocationFromImagePlate", param);
+        if (getPixelLocationFromImagePlateMethod == null) return null;
+      }
+      catch (Exception e) {
+        return null;
+      }
+    }
+    // get transforms
+    Canvas3D canvas = display_renderer.getCanvas();
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    TransformGroup trans = display_renderer.getTrans();
+    Transform3D tt = new Transform3D();
+    trans.getTransform(tt);
+
+    // compute image plate location
+    Point3d pos = new Point3d(position);
+    tt.transform(pos);
+    t.invert();
+    t.transform(pos);
+
+    // get screen coordinates
+    Point2d coords = new Point2d();
+    // CTR: unfortunately, the following method is Java3D 1.2 only
+    // canvas.getPixelLocationFromImagePlate(pos, coords);
+    // return new int[] {(int) coords.x, (int) coords.y};
+    try {
+      getPixelLocationFromImagePlateMethod.invoke(canvas,
+        new Object[] {pos, coords});
+    }
+    catch (Exception e) {
+      return null;
+    }
+    return new int[] {(int) coords.x, (int) coords.y};
+  }
+
+
+  /**
+   * Return the screen coordinates corresponding to the VisAD coordinates.
+   * @param  position  array of VisAD coordinates
+   * @return  corresponding (x, y) plate coordinates (in meters)
+   */
+  public double[] getPlateCoords(double[] position) {
+    if (getPixelLocationFromImagePlateMethod == null) {
+      try {
+        Class canvas3DClass = Class.forName("javax.media.j3d.Canvas3D");
+        Class[] param =
+          {javax.vecmath.Point3d.class, javax.vecmath.Point2d.class};
+        getPixelLocationFromImagePlateMethod =
+          canvas3DClass.getMethod("getPixelLocationFromImagePlate", param);
+        if (getPixelLocationFromImagePlateMethod == null) return null;
+      }
+      catch (Exception e) {
+        return null;
+      }
+    }
+    // get transforms
+    Canvas3D canvas = display_renderer.getCanvas();
+    Transform3D t = new Transform3D();
+    canvas.getImagePlateToVworld(t);
+    TransformGroup trans = display_renderer.getTrans();
+    Transform3D tt = new Transform3D();
+    trans.getTransform(tt);
+
+    // compute image plate location
+    Point3d pos = new Point3d(position);
+    tt.transform(pos);
+    t.invert();
+    t.transform(pos);
+
+    return new double[] {pos.x, pos.y};
+  }
+
+
+  /**
+   * Wakeup when necessary
+   */
+  void setWakeup() {
+    wakeupOn(wakeup);
+  }
+
+  /**
+   * Create a translation matrix.
+   * @param  transx   x translation amount
+   * @param  transy   y translation amount
+   * @param  transz   z translation amount
+   * @return  new translation matrix.  This can be used to translate
+   *          the current matrix
+   * @see #multiply_matrix(double[] a, double[] b)
+   */
+  public double[] make_translate(double transx, double transy, double transz) {
+    return make_matrix(0.0, 0.0, 0.0, 1.0, transx, transy, transz);
+  }
+
+  /**
+   * Create a translation matrix.  no translation in Z direction (useful
+   * for 2D in Java 3D.
+   * @param  transx   x translation amount
+   * @param  transy   y translation amount
+   * @return  new translation matrix.  This can be used to translate
+   *          the current matrix
+   * @see #multiply_matrix(double[] a, double[] b)
+   */
+  public double[] make_translate(double transx, double transy) {
+    return make_translate(transx, transy, 0.0);
+  }
+
+  /**
+   * Multiply the two matrices together.
+   * @param  a  first matrix
+   * @param  b  second matrix
+   * @return  new resulting matrix
+   * @see #static_multiply_matrix
+   */
+  public double[] multiply_matrix(double[] a, double[] b) {
+    return static_multiply_matrix(a, b);
+  }
+
+  /**
+   * Multiply the two matrices together. Static version of multiply_matrix.
+   * @param  a  first matrix
+   * @param  b  second matrix
+   * @return  new resulting matrix
+   * @see #multiply_matrix(double[] a, double[] b)
+   */
+  public static double[] static_multiply_matrix(double[] a, double[] b) {
+    Transform3D ta = new Transform3D(a);
+    Transform3D tb = new Transform3D(b);
+    ta.mul(tb);
+    double[] c = new double[16];
+    ta.get(c);
+    return c;
+  }
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  This function uses the fast matrix post-concatenation
+   * techniques from Graphics Gems.
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scale  scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  public double[] make_matrix(double rotx, double roty, double rotz,
+         double scale, double transx, double transy, double transz) {
+    return make_matrix(rotx, roty, rotz, scale, scale, scale, transx, transy, transz);
+  }
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  This function uses the fast matrix post-concatenation
+   * techniques from Graphics Gems.
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scalex  x scaling factor
+   * @param scaley  y scaling factor
+   * @param scalez  z scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  public double[] make_matrix(double rotx, double roty, double rotz,
+                              double scalex, double scaley, double scalez, 
+                              double transx, double transy, double transz) {
+    return static_make_matrix(rotx, roty, rotz, scalex, scaley, scalez, transx, transy, transz);
+  }
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  This function uses the fast matrix post-concatenation
+   * techniques from Graphics Gems.  Static version of make_matrix.
+   * @see #make_matrix(double rotx, double roty, double rotz,
+   *         double scale, double transx, double transy, double transz)
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scale  scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  public static double[] static_make_matrix(
+         double rotx, double roty, double rotz,
+         double scale, double transx, double transy, double transz) {
+    return static_make_matrix(rotx, roty, rotz, scale, scale, scale, transx, transy, transz);
+  }
+
+  /**
+   * Make a transformation matrix to perform the given rotation, scale and
+   * translation.  This function uses the fast matrix post-concatenation
+   * techniques from Graphics Gems.  Static version of make_matrix.
+   * @see #make_matrix(double rotx, double roty, double rotz,
+   *         double scale, double transx, double transy, double transz)
+   * @param rotx  x rotation
+   * @param roty  y rotation
+   * @param rotz  z rotation
+   * @param scalex  x scaling factor
+   * @param scaley  y scaling factor
+   * @param scalez  z scaling factor
+   * @param transx  x translation
+   * @param transy  y translation
+   * @param transz  z translation
+   * @return  new matrix
+   */
+  public static double[] static_make_matrix(
+         double rotx, double roty, double rotz,
+         double scalex, double scaley, double scalez, 
+         double transx, double transy, double transz) {
+    double sx, sy, sz, cx, cy, cz, t;
+    int i, j, k;
+    double deg2rad = 1.0 / 57.2957;
+    double[] matrix = new double[16];
+    double[][] mat = new double[4][4];
+
+    /* Get sin and cosine values */
+    sx = Math.sin(rotx * deg2rad);
+    cx = Math.cos(rotx * deg2rad);
+    sy = Math.sin(roty * deg2rad);
+    cy = Math.cos(roty * deg2rad);
+    sz = Math.sin(rotz * deg2rad);
+    cz = Math.cos(rotz * deg2rad);
+
+    /* Start with identity matrix */
+    mat[0][0] = 1.0;  mat[0][1] = 0.0;  mat[0][2] = 0.0;  mat[0][3] = 0.0;
+    mat[1][0] = 0.0;  mat[1][1] = 1.0;  mat[1][2] = 0.0;  mat[1][3] = 0.0;
+    mat[2][0] = 0.0;  mat[2][1] = 0.0;  mat[2][2] = 1.0;  mat[2][3] = 0.0;
+    mat[3][0] = 0.0;  mat[3][1] = 0.0;  mat[3][2] = 0.0;  mat[3][3] = 1.0;
+
+    /* Z Rotation */
+    for (i=0;i<4;i++) {
+      t = mat[i][0];
+      mat[i][0] = t*cz - mat[i][1]*sz;
+      mat[i][1] = t*sz + mat[i][1]*cz;
+    }
+
+    /* X rotation */
+    for (i=0;i<4;i++) {
+      t = mat[i][1];
+      mat[i][1] = t*cx - mat[i][2]*sx;
+      mat[i][2] = t*sx + mat[i][2]*cx;
+    }
+
+    /* Y Rotation */
+    for (i=0;i<4;i++) {
+      t = mat[i][0];
+      mat[i][0] = mat[i][2]*sy + t*cy;
+      mat[i][2] = mat[i][2]*cy - t*sy;
+    }
+
+    /* Scale */
+    for (i=0;i<3;i++) {
+      mat[i][0] *= scalex;
+      mat[i][1] *= scaley;
+      mat[i][2] *= scalez;
+    }
+
+    /* Translation */
+/* should be mat[0][3], mat[1][3], mat[2][3]
+   WLH 22 Dec 97 */
+    mat[0][3] = transx;
+    mat[1][3] = transy;
+    mat[2][3] = transz;
+/*
+    mat[3][0] = transx;
+    mat[3][1] = transy;
+    mat[3][2] = transz;
+*/
+    k = 0;
+    for (i=0; i<4; i++) {
+      for (j=0; j<4; j++) {
+        matrix[k] = mat[i][j];
+        k++;
+      }
+    }
+    return matrix;
+  }
+
+  static final double EPS = 0.000001;
+
+  /**
+   * Get the rotation, scale and translation parameters for the specified
+   * matrix.  Results are not valid for non-uniform aspect (scale).
+   * @param  rot  array to hold x,y,z rotation values
+   * @param  scale  array to hold scale value
+   * @param  trans  array to hold x,y,z translation values
+   */
+  public void instance_unmake_matrix(double[] rot, double[] scale,
+                            double[] trans, double[] matrix) {
+    unmake_matrix(rot, scale, trans, matrix);
+  }
+
+  /**
+   * Get the rotation, scale and translation parameters for the specified
+   * matrix.  Results are not valid for non-uniform aspect (scale).
+   * Static version of <CODE>instance_unmake_matrix</CODE>.
+   * @param  rot  array to hold x,y,z rotation values
+   * @param  scale  array to hold scale value(s). If length == 1, assumes
+   *                uniform scaling.
+   * @param  trans  array to hold x,y,z translation values
+   */
+  public static void unmake_matrix(double[] rot, double[] scale,
+                                   double[] trans, double[] matrix) {
+    double  sx, sy, sz, cx, cy, cz;
+    int i, j;
+    double[][] mat = new double[4][4];
+    double[][] nat = new double[4][4];
+
+    double scalex, scaley, scalez, cxa, cxb, cxinv;
+    double[] scaleinv = new double[3];
+
+    if (rot == null || rot.length != 3) return;
+    if (scale == null || !(scale.length == 1 || scale.length == 3)) return;
+    if (trans == null || trans.length != 3) return;
+    if (matrix == null || matrix.length != 16) return;
+
+    int k = 0;
+    for (i=0; i<4; i++) {
+      for (j=0; j<4; j++) {
+        mat[i][j] = matrix[k];
+        k++;
+      }
+    }
+
+    /* translation */
+/* WLH 24 March 2000, for consistency with change
+                      of 22 Dec 97 in static_make_matrix
+    trans[0] = mat[3][0];
+    trans[1] = mat[3][1];
+    trans[2] = mat[3][2];
+*/
+    trans[0] = mat[0][3];
+    trans[1] = mat[1][3];
+    trans[2] = mat[2][3];
+
+    /* scale */
+    scalex = scaley = scalez = 0.0;
+    for (i=0; i<3; i++) {
+      scalex += mat[0][i] * mat[0][i];
+      scaley += mat[1][i] * mat[1][i];
+      scalez += mat[2][i] * mat[2][i];
+    }
+    if (Math.abs(scalex - scaley) > EPS || Math.abs(scalex - scalez) > EPS) {
+      // System.out.println("problem " + scalex + " " + scaley + " " + scalez);
+    }
+    if (scale.length == 1) {
+      scale[0] = Math.sqrt((scalex + scaley + scalez)/3.0);
+      scaleinv[0] = Math.abs(scale[0]) > EPS ? 1.0 / scale[0] : 1.0 / EPS;
+      scaleinv[1] = scaleinv[2] = scaleinv[0];
+    } else {
+      scale[0] = Math.sqrt(scalex);
+      scale[1] = Math.sqrt(scaley);
+      scale[2] = Math.sqrt(scalez);
+      for (i=0; i<3; i++) {
+        scaleinv[i] = Math.abs(scale[i]) > EPS ? 1.0 / scale[i] : 1.0 / EPS;
+      }
+    }
+        
+
+    for (i=0; i<3; i++) {
+      for (j=0; j<3; j++) {
+        nat[j][i] = scaleinv[j] * mat[j][i];
+      }
+    }
+
+    /* rotation */
+    sx = -nat[2][1];
+
+    cxa = Math.sqrt(nat[2][0]*nat[2][0] + nat[2][2]*nat[2][2]);
+    cxb = Math.sqrt(nat[0][1]*nat[0][1] + nat[1][1]*nat[1][1]);
+
+    if (Math.abs(cxa - cxb) > EPS) {
+      // System.out.println("problem2 " + cxa + " " + cxb);
+    }
+    /* the sign of cx does not matter;
+       it is an ambiguity in 3-D rotations:
+       (rotz, rotx, roty) = (180+rotz, 180-rotx, 180+roty) */
+    cx = (cxa + cxb) / 2.0;
+    if (Math.abs(cx) > EPS) {
+      cxinv = 1.0 / cx;
+      sy = nat[2][0] * cxinv;
+      cy = nat[2][2] * cxinv;
+      sz = nat[0][1] * cxinv;
+      cz = nat[1][1] * cxinv;
+    }
+    else {
+      /* if cx == 0 then roty and rotz are ambiguous:
+         assume rotx = 0.0 */
+      sy = 0.0;
+      cy = 1.0;
+      sz = nat[0][2];
+      cz = nat[1][2];
+    }
+
+    rot[0] = 57.2957 * Math.atan2(sx, cx);
+    rot[1] = 57.2957 * Math.atan2(sy, cy);
+    rot[2] = 57.2957 * Math.atan2(sz, cz);
+    return;
+  }
+
+
+/*
+   homogeneous coordinates:
+   point (x, y, z) is represented as (w*x, w*y, w*z, w) for w != 0
+   point (x, y, z, 1) transforms to
+         x' = x*mat[0]  + y*mat[1]  + z*mat[2]  + mat[3]
+         y' = x*mat[4]  + y*mat[5]  + z*mat[6]  + mat[7]
+         z' = x*mat[8]  + y*mat[9]  + z*mat[10] + mat[11]
+         w' = x*mat[12] + y*mat[13] + z*mat[14] + mat[15]
+*/
+
+/*
+   see ViewPlatform.TransformGroup in UniverseBuilderJ3D constructor
+
+   View.setProjectionPolicy
+     default is View.PERSPECTIVE_PROJECTION (vs PARALLEL_PROJECTION)
+   View.getVpcToEc(Transform3D vpcToEc)
+     "Compatibility mode method that retrieves the current ViewPlatform
+     Coordinates (VPC) system to Eye Coordinates (EC) transform and copies
+     it into the specified object."
+   View.getLeftProjection(Transform3D projection)
+   View.getRightProjection(Transform3D projection)
+     default is View.CYCLOPEAN_EYE_VIEW
+     require:
+   View.setCompatibilityModeEnable(true)
+     default is disabled
+   View.getLeftProjection(Transform3D projection)
+     "Compatibility mode method that specifies a viewing frustum for the
+     left eye that transforms points in Eye Coordinates (EC) to Clipping
+     Coordinates (CC)."
+
+   NOTE have View in UniverseBuilderJ3D
+
+   Canvas3D.getImagePlateToVworld(Transform3D t)
+   Canvas3D.getPixelLocationInImagePlate(int x, int y, Point3d position)
+   Canvas3D.getVworldToImagePlate(Transform3D t)
+   Canvas3D.getCenterEyeInImagePlate(Point3d position)
+
+  spec page 176: "final transform for rendering shapes to Canvas3D is
+    P * E * inv(ViewPlatform.TransformGroup) * trans
+    use Transform3D.invert for inv
+  where P = projection matrix from eye coords to clipping coords
+    i.e., projection
+  and E = matrix from ViewPlatform to eye coords
+    i.e., vpcToEc
+*/
+
+/*
+   is the mouse a Sensor or InputDevice?
+   if so, see spec page 369 for sensor to World transform
+*/
+
+/*
+import javax.vecmath.*;
+import javax.media.j3d.*;
+
+public class TestTransform3D extends Object {
+
+  public static void main(String args[]) {
+    Transform3D trans_frustum = new Transform3D();
+    Transform3D trans_perspective = new Transform3D();
+    Transform3D trans_ortho = new Transform3D();
+    Transform3D trans_ortho2 = new Transform3D();
+
+    System.out.println("initial trans_frustum =\n" + trans_frustum);
+
+    trans_frustum.frustum(-1.0, 1.0, -1.0, 1.0, 1.0, 2.0);
+    System.out.println("trans_frustum =\n" + trans_frustum);
+
+    trans_perspective.perspective(0.5, 1.0, 1.0, 2.0);
+    System.out.println("trans_perspective =\n" + trans_perspective);
+
+    trans_ortho.ortho(-1.0, 1.0, -1.0, 1.0, 1.0, 2.0);
+    System.out.println("trans_ortho =\n" + trans_ortho);
+
+    trans_ortho2.ortho(-1.0, 1.0, -1.0, 1.0, 1.0, 3.0);
+    System.out.println("trans_ortho2 =\n" + trans_ortho2);
+
+    Transform3D trans_translate_scale =
+      new Transform3D(new Matrix3d(1.0, 1.1, 1.2,
+                                   1.3, 1.4, 1.5,
+                                   1.6, 1.7, 1.8),
+                      new Vector3d(2.1, 2.2, 2.3), 3.0);
+    System.out.println("trans_translate_scale =\n" + trans_translate_scale);
+
+    double[] matrix = {1.0, 1.1, 1.2, 1.3,
+                       1.4, 1.5, 1.6, 1.7,
+                       1.8, 1.9, 2.0, 2.1,
+                       2.2, 2.3, 2.4, 2.5};
+    Transform3D trans_matrix = new Transform3D(matrix);
+    System.out.println("trans_matrix =\n" + trans_matrix);
+  }
+
+// here's the output:
+verner 25% java TestTransform3D
+initial trans_frustum =
+1.0, 0.0, 0.0, 0.0
+0.0, 1.0, 0.0, 0.0
+0.0, 0.0, 1.0, 0.0
+0.0, 0.0, 0.0, 1.0
+
+trans_frustum =
+1.0, 0.0, 0.0, 0.0
+0.0, 1.0, 0.0, 0.0
+0.0, 0.0, 3.0, 4.0
+0.0, 0.0, -1.0, 0.0
+
+trans_perspective =
+3.91631736464594, 0.0, 0.0, 0.0
+0.0, 3.91631736464594, 0.0, 0.0
+0.0, 0.0, 3.0, 4.0
+0.0, 0.0, -1.0, 0.0
+
+trans_ortho =
+1.0, 0.0, 0.0, -0.0
+0.0, 1.0, 0.0, -0.0
+0.0, 0.0, 2.0, 3.0
+0.0, 0.0, 0.0, 1.0
+
+trans_ortho2 =
+1.0, 0.0, 0.0, -0.0
+0.0, 1.0, 0.0, -0.0
+0.0, 0.0, 1.0, 2.0
+0.0, 0.0, 0.0, 1.0
+
+trans_translate_scale =
+3.0, 3.3000000000000003, 3.5999999999999996, 2.1
+3.9000000000000004, 4.199999999999999, 4.5, 2.2
+4.800000000000001, 5.1, 5.4, 2.3
+0.0, 0.0, 0.0, 1.0
+
+trans_matrix =
+1.0, 1.1, 1.2, 1.3
+1.4, 1.5, 1.6, 1.7
+1.8, 1.9, 2.0, 2.1
+2.2, 2.3, 2.4, 2.5
+
+verner 26%
+//
+
+}
+
+*/
+
+}
+
diff --git a/visad/java3d/ProjectionControlJ3D.java b/visad/java3d/ProjectionControlJ3D.java
new file mode 100644
index 0000000..2de8278
--- /dev/null
+++ b/visad/java3d/ProjectionControlJ3D.java
@@ -0,0 +1,288 @@
+//
+// ProjectionControlJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.rmi.*;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+
+import java.util.Vector;
+import java.util.Enumeration;
+
+import java.lang.reflect.*;
+
+/**
+   ProjectionControlJ3D is the VisAD class for controlling the Projection
+   from 3-D to 2-D.  It manipulates a TransformGroup node in the
+   scene graph.<P>
+*/
+/* WLH 17 June 98
+public class ProjectionControlJ3D extends Control
+       implements ProjectionControl {
+*/
+public class ProjectionControlJ3D extends ProjectionControl {
+
+  private transient Transform3D Matrix;
+
+  // Vector of Switch nodes for volume rendering
+  transient Vector switches = new Vector();
+  int which_child = 2; // initial view along Z axis (?)
+
+  // DRM 6 Nov 2000
+  /** View of the postive X face of the display cube */
+  public static final int X_PLUS = 0;
+  /** View of the negative X face of the display cube */
+  public static final int X_MINUS = 1;
+  /** View of the postive Y face of the display cube */
+  public static final int Y_PLUS = 2;
+  /** View of the negative Y face of the display cube */
+  public static final int Y_MINUS = 3;
+  /** View of the postive Z face of the display cube */
+  public static final int Z_PLUS = 4;
+  /** View of the negative Z face of the display cube */
+  public static final int Z_MINUS = 5;
+
+  /**
+   * Construct a new ProjectionControl for the display.  The initial
+   * projection is saved so it can be reset with resetProjection().
+   * @see #resetProjection().
+   * @param  d  display whose projection will be controlled by this
+   * @throws VisADException
+   */
+  public ProjectionControlJ3D(DisplayImpl d) throws VisADException {
+    super(d);
+    // WLH 8 April 99
+    // Matrix = new Transform3D();
+    Matrix = init();
+    matrix = new double[MATRIX3D_LENGTH];
+    Matrix.get(matrix);
+    saveProjection();   // DRM 6 Nov 2000
+/* WLH 8 April 99
+    Matrix = init();
+    matrix = new double[MATRIX3D_LENGTH];
+    Matrix.get(matrix);
+    ((DisplayRendererJ3D) getDisplayRenderer()).setTransform3D(Matrix);
+*/
+  }
+
+  /**
+   * Set the projection matrix.
+   * @param m new projection matrix
+   * @throws VisADException  VisAD error
+   * @throws RemoteException  remote error
+   */
+  public void setMatrix(double[] m)
+         throws VisADException, RemoteException {
+    super.setMatrix(m);
+    Matrix = new Transform3D(matrix);
+
+    VisADCanvasJ3D canvas =
+      ((DisplayRendererJ3D) getDisplayRenderer()).getCanvas();
+    if (canvas != null && canvas.getOffscreen()) {
+      try {
+        Method renderMethod =
+          Canvas3D.class.getMethod("renderOffScreenBuffer",
+                                   new Class[] {});
+        renderMethod.invoke(canvas, new Object[] {});
+        Method waitMethod =
+          Canvas3D.class.getMethod("waitForOffScreenRendering",
+                                   new Class[] {});
+        waitMethod.invoke(canvas, new Object[] {});
+      }
+      catch (NoSuchMethodException e) {
+        // System.out.println(e);
+      }
+      catch (IllegalAccessException e) {
+        // System.out.println(e);
+      }
+      catch (InvocationTargetException e) {
+        // System.out.println(e + "\n" +
+        //    ((InvocationTargetException) e).getTargetException());
+      }
+      // canvas.renderOffScreenBuffer();
+    }
+
+    ((DisplayRendererJ3D) getDisplayRenderer()).setTransform3D(Matrix);
+    if (!switches.isEmpty()) selectSwitches();
+    // WLH 5 May 2000
+    // changeControl(true);
+    changeControl(false);
+  }
+
+  /**
+   * Set the aspect for the axes.  Default upon initialization is 
+   * 1.0, 1.0, 1.0.  Invokes saveProjection to set this as the new default.
+   * @see #saveProjection()
+   * @param aspect  ratios (dimension 3) for the X, Y, and Z axes
+   * @throws VisADException  aspect is null or wrong dimension or other error
+   * @throws RemoteException  remote error
+   */
+  public void setAspect(double[] aspect)
+         throws VisADException, RemoteException {
+    if (aspect == null || aspect.length != 3) {
+      throw new DisplayException("aspect array must be length = 3");
+    }
+    Transform3D transform = new Transform3D();
+    transform.setScale(new javax.vecmath.Vector3d(aspect[0], aspect[1], aspect[2]));
+    double[] mult = new double[MATRIX3D_LENGTH];
+    transform.get(mult);
+    Transform3D mat = init();
+    double[] m = new double[MATRIX3D_LENGTH];
+    mat.get(m);
+    setMatrix(getDisplay().multiply_matrix(mult, m));
+    saveProjection();   // DRM 6 Nov 2000
+  }
+
+  private Transform3D init() {
+    Transform3D mat = new Transform3D();
+    // initialize scale
+    double scale = 0.5;
+    if (getDisplayRenderer().getMode2D()) scale = ProjectionControl.SCALE2D;
+    Transform3D t1 = new Transform3D(
+      MouseBehaviorJ3D.static_make_matrix(0.0, 0.0, 0.0, scale, 0.0, 0.0, 0.0) );
+    mat.mul(t1);
+    return mat;
+  }
+
+  public void addPair(Switch sw, DataRenderer re) {
+    switches.addElement(new SwitchProjection(sw, re));
+    sw.setWhichChild(which_child);
+  }
+
+  private void selectSwitches() {
+    int old_which_child = which_child;
+    // calculate which axis is most parallel to eye direction
+    Transform3D tt = new Transform3D(Matrix);
+    tt.invert();
+    Point3d origin = new Point3d(0.0, 0.0, 0.0);
+    Point3d eye = new Point3d(0.0, 0.0, 1.0);
+    tt.transform(origin);
+    tt.transform(eye);
+    double dx = eye.x - origin.x;
+    double dy = eye.y - origin.y;
+    double dz = eye.z - origin.z;
+    double ax = Math.abs(dx);
+    double ay = Math.abs(dy);
+    double az = Math.abs(dz);
+    if (az >= ay && az >= ax) {
+      which_child = (dz > 0) ? 2 : 5;
+    }
+    else if (ay >= ax) {
+      which_child = (dy > 0) ? 1 : 4;
+    }
+    else {
+      which_child = (dx > 0) ? 0 : 3;
+    }
+
+    // axis did not change, so no need to change Switches
+    if (old_which_child == which_child) return;
+/*
+System.out.println("which_child = " + which_child + "  " + dx +
+                  " " + dy + " " + dz);
+*/
+    // axis changed, so change Switches
+    Enumeration pairs = ((Vector) switches.clone()).elements();
+    while (pairs.hasMoreElements()) {
+      SwitchProjection ss = (SwitchProjection) pairs.nextElement();
+      ss.swit.setWhichChild(which_child);
+    }
+  }
+
+  /** clear all 'pairs' in switches that involve re */
+  public void clearSwitches(DataRenderer re) {
+    Enumeration pairs = ((Vector) switches.clone()).elements();
+    while (pairs.hasMoreElements()) {
+      SwitchProjection ss = (SwitchProjection) pairs.nextElement();
+      if (ss.renderer.equals(re)) {
+        switches.removeElement(ss);
+      }
+    }
+  }
+
+  /**
+   * Set the projection so the requested view is displayed.
+   * @param  view  one of the static view fields (X_PLUS, X_MINUS, etc).  This
+   *               will set the view so the selected face is orthogonal to
+   *               the display.
+   * @throws VisADException   VisAD failure.
+   * @throws RemoteException  Java RMI failure.
+   */
+  public void setOrthoView(int view)
+    throws VisADException, RemoteException 
+  {
+    double[] viewMatrix;
+    if (getDisplayRenderer().getMode2D()) return;
+    switch (view)
+    {
+      case Z_PLUS: // Top
+        viewMatrix = 
+          getDisplay().make_matrix(0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case Z_MINUS: // Bottom
+        viewMatrix = 
+          getDisplay().make_matrix(0.0, 180.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case Y_PLUS: // North
+        viewMatrix = 
+          getDisplay().make_matrix(-90.0, 180.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case Y_MINUS: // South
+        viewMatrix = 
+          getDisplay().make_matrix(90.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case X_PLUS: // East
+        viewMatrix = 
+          getDisplay().make_matrix(0.0, 90.0, 90.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      case X_MINUS: // West
+        viewMatrix = 
+          getDisplay().make_matrix(0.0, -90.0, -90.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+      default:   // no change
+        viewMatrix = 
+          getDisplay().make_matrix(0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0);
+        break;
+   }
+   setMatrix(
+     getDisplay().multiply_matrix(viewMatrix, getSavedProjectionMatrix()));
+ }
+
+  /** SwitchProjection is an inner class of ProjectionControlJ3D for
+      (Switch, DataRenderer) structures */
+  private class SwitchProjection extends Object {
+    Switch swit;
+    DataRenderer renderer;
+
+    SwitchProjection(Switch sw, DataRenderer re) {
+      swit = sw;
+      renderer = re;
+    }
+  }
+}
diff --git a/visad/java3d/RendererJ3D.java b/visad/java3d/RendererJ3D.java
new file mode 100644
index 0000000..f1dc2da
--- /dev/null
+++ b/visad/java3d/RendererJ3D.java
@@ -0,0 +1,370 @@
+//
+// RendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+import visad.util.Delay;
+
+import javax.media.j3d.*;
+
+import java.util.*;
+import java.rmi.*;
+import java.awt.Image;
+
+
+/**
+   RendererJ3D is the VisAD abstract super-class for graphics rendering
+   algorithms under Java3D.  These transform Data objects into 3-D
+   (or 2-D) depictions in a Display window.<P>
+
+   RendererJ3D is not Serializable and should not be copied
+   between JVMs.<P>
+*/
+public abstract class RendererJ3D extends DataRenderer {
+
+  /** switch is parent of any BranchGroups created by this */
+  Switch sw = null;
+  /** parent of sw for 'detach' */
+  BranchGroup swParent = null;
+  /** index of current 'intended' child of Switch sw;
+      not necessarily == sw.getWhichChild() */
+  /** currentIndex is always = 0; this logic is a vestige of a
+      workaround for an old (circa 1998) bug in Java3D */
+  private static final int currentIndex = 0;
+  BranchGroup[] branches = null;
+  boolean[] switchFlags = {false, false, false};
+  boolean[] branchNonEmpty = {false, false, false};
+
+  public RendererJ3D() {
+    super();
+  }
+
+  public void setLinks(DataDisplayLink[] links, DisplayImpl d)
+       throws VisADException {
+    if (getDisplay() != null || getLinks() != null) {
+      throw new DisplayException("RendererJ3D.setLinks: already set\n" +
+                                 "you are probably re-using a DataRenderer");
+    }
+    if (!(d instanceof DisplayImplJ3D)) {
+      throw new DisplayException("RendererJ3D.setLinks: must be DisplayImplJ3D");
+    }
+    setDisplay(d);
+    setDisplayRenderer(d.getDisplayRenderer());
+    setLinks(links);
+
+    // set up switch logic for clean BranchGroup replacement
+    Switch swt = new Switch(); // J3D
+    swt.setCapability(Group.ALLOW_CHILDREN_READ);
+    swt.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    swt.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    swt.setCapability(Switch.ALLOW_SWITCH_READ);
+    swt.setCapability(Switch.ALLOW_SWITCH_WRITE);
+
+    swParent = new BranchGroup();
+    swParent.setCapability(BranchGroup.ALLOW_DETACH);
+    swParent.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    swParent.addChild(swt);
+    // make it 'live'
+    addSwitch((DisplayRendererJ3D) getDisplayRenderer(), swParent);
+
+    branches = new BranchGroup[3];
+    for (int i=0; i<3; i++) {
+      branches[i] = new BranchGroup();
+      branches[i].setCapability(Group.ALLOW_CHILDREN_READ);
+      branches[i].setCapability(Group.ALLOW_CHILDREN_WRITE);
+      branches[i].setCapability(Group.ALLOW_CHILDREN_EXTEND);
+      swt.addChild(branches[i]);
+    }
+/*
+System.out.println("setLinks: sw.setWhichChild(" + currentIndex + ")");
+*/
+    swt.setWhichChild(currentIndex);
+    sw = swt; // avoid IndexOutOfBoundsException in toggle()
+    toggle(getEnabled());
+  }
+
+  public void toggle(boolean on) {
+    if (sw != null) sw.setWhichChild(on ? currentIndex : ((currentIndex+1)%3));
+    super.toggle(on);
+  }
+
+  public ShadowType makeShadowFunctionType(
+         FunctionType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowFunctionTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealTupleType(
+         RealTupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowRealTupleTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowRealType(
+         RealType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowRealTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowSetType(
+         SetType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowSetTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTextType(
+         TextType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowTextTypeJ3D(type, link, parent);
+  }
+
+  public ShadowType makeShadowTupleType(
+         TupleType type, DataDisplayLink link, ShadowType parent)
+         throws VisADException, RemoteException {
+    return new ShadowTupleTypeJ3D(type, link, parent);
+  }
+
+  abstract void addSwitch(DisplayRendererJ3D displayRenderer,
+                          BranchGroup branch);
+
+  /** re-transform if needed;
+      return false if not done */
+  public boolean doAction() throws VisADException, RemoteException {
+    if (branches == null) return false;
+    BranchGroup branch; // J3D
+    boolean all_feasible = get_all_feasible();
+    boolean any_changed = get_any_changed();
+    boolean any_transform_control = get_any_transform_control();
+/*
+System.out.println("doAction " + getDisplay().getName() + " " +
+                   getLinks()[0].getThingReference().getName() +
+                   " any_changed = " + any_changed +
+                   " all_feasible = " + all_feasible +
+                   " any_transform_control = " + any_transform_control);
+*/
+    if (all_feasible && (any_changed || any_transform_control)) {
+/*
+System.out.println("doAction " + getDisplay().getName() + " " +
+                   getLinks()[0].getThingReference().getName() +
+                   " any_changed = " + any_changed +
+                   " all_feasible = " + all_feasible +
+                   " any_transform_control = " + any_transform_control);
+*/
+      // exceptionVector.removeAllElements();
+      clearAVControls();
+      try {
+        // doTransform creates a BranchGroup from a Data object
+        branch = doTransform();
+      }
+      catch (OutOfMemoryError e) {
+        // System.out.println("OutOfMemoryError, try again ...");
+        clearBranch();
+        branch = null;
+        new Delay(250);
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        try {
+          branch = doTransform();
+        }
+        catch (BadMappingException ee) {
+          addException(ee);
+          branch = null;
+        }
+        catch (UnimplementedException ee) {
+          addException(ee);
+          branch = null;
+        }
+        catch (RemoteException ee) {
+          addException(ee);
+          branch = null;
+        }
+        catch (DisplayInterruptException ee) {
+          branch = null;
+        }
+      }
+      catch (BadMappingException e) {
+        addException(e);
+        branch = null;
+      }
+      catch (UnimplementedException e) {
+        addException(e);
+        branch = null;
+      }
+      catch (RemoteException e) {
+        addException(e);
+        branch = null;
+      }
+      catch (DisplayInterruptException e) {
+        branch = null;
+      }
+
+      if (branch != null) {
+        synchronized (this) {
+          if (!branchNonEmpty[currentIndex] ||
+              branches[currentIndex].numChildren() == 0) {
+            /* WLH 18 Nov 98 */
+            branches[currentIndex].addChild(branch);
+            branchNonEmpty[currentIndex] = true;
+          }
+          else { // if (branchNonEmpty[currentIndex])
+            if (!(branches[currentIndex].getChild(0) == branch)) {// TDR, Nov 02
+              flush(branches[currentIndex]);
+              branches[currentIndex].setChild(branch, 0);
+            }
+          } // end if (branchNonEmpty[currentIndex])
+        } // end synchronized (this)
+      }
+      else { // if (branch == null)
+
+        // WLH 31 March 99
+        clearBranch();
+
+        all_feasible = false;
+        set_all_feasible(all_feasible);
+      }
+    }
+    else { // !(all_feasible && (any_changed || any_transform_control))
+      DataDisplayLink[] links = getLinks();
+      for (int i=0; i<links.length; i++) {
+        links[i].clearData();
+      }
+    }
+    return (all_feasible && (any_changed || any_transform_control));
+  }
+
+  public BranchGroup getBranch() {
+    synchronized (this) {
+      if (branches != null && branchNonEmpty[currentIndex] &&
+          branches[currentIndex].numChildren() > 0) {
+        return (BranchGroup) branches[currentIndex].getChild(0);
+      }
+      else {
+        return null;
+      }
+    }
+  }
+
+  public void setBranchEarly(BranchGroup branch) {
+    if (branches == null) return;
+    // needed (?) to avoid NullPointerException
+    ShadowTypeJ3D shadow = (ShadowTypeJ3D) (getLinks()[0].getShadow());
+    shadow.ensureNotEmpty(branch);
+
+    synchronized (this) {
+      if (!branchNonEmpty[currentIndex] ||
+          branches[currentIndex].numChildren() == 0) {
+        /* WLH 18 Nov 98 */
+        branches[currentIndex].addChild(branch);
+        branchNonEmpty[currentIndex] = true;
+      }
+      else { // if (branchNonEmpty[currentIndex])
+        if (!(branches[currentIndex].getChild(0) == branch)) {
+          flush(branches[currentIndex]);
+          branches[currentIndex].setChild(branch, 0);
+        }
+      } // end if (branchNonEmpty[currentIndex])
+    } // end synchronized (this)
+  }
+
+  public void clearBranch() {
+    if (branches == null) return;
+    synchronized (this) {
+      if (branchNonEmpty[currentIndex]) {
+        flush(branches[currentIndex]);
+        Enumeration ch = branches[currentIndex].getAllChildren();
+        while(ch.hasMoreElements()) {
+          BranchGroup b = (BranchGroup) ch.nextElement();
+          b.detach();
+        }
+      }
+      branchNonEmpty[currentIndex] = false;
+    }
+  }
+
+  public void flush(Group branch) {
+    if (branches == null) return;
+    Enumeration ch = branch.getAllChildren();
+    while(ch.hasMoreElements()) {
+      Node n = (Node) ch.nextElement();
+      if (n instanceof Group) {
+        flush((Group) n);
+      }
+      else if (n instanceof Shape3D &&
+               ((Shape3D) n).getCapability(Shape3D.ALLOW_APPEARANCE_READ)) {
+        Appearance appearance = ((Shape3D) n).getAppearance();
+        if (appearance != null &&
+            appearance.getCapability(Appearance.ALLOW_TEXTURE_READ)) {
+          Texture texture = appearance.getTexture();
+          if (texture != null &&
+              texture.getCapability(Texture.ALLOW_IMAGE_READ)) {
+            ImageComponent ic = texture.getImage(0);
+            if (ic != null && ic.getCapability(ImageComponent.ALLOW_IMAGE_READ)) {
+              if (ic instanceof ImageComponent2D) {
+                Image image = ((ImageComponent2D) ic).getImage();
+                if (image != null) image.flush();
+// System.out.println("flush");
+              }
+              else if (ic instanceof ImageComponent3D) {
+                Image[] images = ((ImageComponent3D) ic).getImage();
+                if (images != null) {
+                  for (int j=0; j<images.length; j++) {
+                    if (images[j] != null) images[j].flush();
+// System.out.println("flush");
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  public void clearScene() {
+    if (branches == null) return;
+    flush(swParent);
+    swParent.detach();
+    ((DisplayRendererJ3D) getDisplayRenderer()).clearScene(this);
+    branches = null;
+    sw = null;
+    swParent = null;
+    super.clearScene();
+  }
+
+  /** create a BranchGroup scene graph for Data in links;
+      this can put Behavior objects in the scene graph for
+      DataRenderer classes that implement direct manipulation widgets;
+      may reduce work by only changing scene graph for Data and
+      Controls that have changed:
+      1. use boolean[] changed to determine which Data objects have changed
+      2. if Data has not changed, then use Control.checkTicks loop like in
+         prepareAction to determine which Control-s have changed */
+  public abstract BranchGroup doTransform()
+         throws VisADException, RemoteException;
+
+}
+
diff --git a/visad/java3d/ShadowAnimationFunctionTypeJ3D.java b/visad/java3d/ShadowAnimationFunctionTypeJ3D.java
new file mode 100644
index 0000000..dbbf04c
--- /dev/null
+++ b/visad/java3d/ShadowAnimationFunctionTypeJ3D.java
@@ -0,0 +1,231 @@
+//
+// ShadowAnimationFunctionTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+
+import java.util.Vector;
+import java.rmi.*;
+
+/**
+   The ShadowAnimationFunctionTypeJ3D class shadows the FunctionType class for
+   AnimationRendererJ3D, within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowAnimationFunctionTypeJ3D extends ShadowFunctionTypeJ3D {
+
+  private static final int MISSING1 = Byte.MIN_VALUE;      // least byte
+
+  public ShadowAnimationFunctionTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+
+    DataDisplayLink link = renderer.getLink();
+
+    // return if data is missing or no ScalarMaps
+    if (data.isMissing()) {
+      ((AnimationRendererJ3D) renderer).markMissingVisADBranch();
+      return false;
+    }
+    if (getLevelOfDifficulty() == NOTHING_MAPPED) return false;
+
+    ShadowFunctionOrSetType adaptedShadowType =
+      (ShadowFunctionOrSetType) getAdaptedShadowType();
+    DisplayImpl display = getDisplay();
+
+    // analyze data's domain (its a Field)
+    Set domain_set = ((Field) data).getDomainSet();
+
+    // ShadowRealTypes of Domain
+    ShadowRealType[] DomainComponents = adaptedShadowType.getDomainComponents();
+
+
+    //if (((AnimationRendererJ3D)renderer).animation1D &&
+    //     domain_set.getDimension() == 1) {
+    if (((AnimationRendererJ3D)renderer).animation1D) {
+
+      Vector domain_maps = DomainComponents[0].getSelectedMapVector();
+      ScalarMap amap = null;
+      if (domain_set.getDimension() == 1 && domain_maps.size() == 1) {
+        ScalarMap map = (ScalarMap) domain_maps.elementAt(0);
+        if (Display.Animation.equals(map.getDisplayScalar())) {
+          amap = map;
+        }
+      }
+      if (amap == null) {
+        throw new BadMappingException("time must be mapped to Animation");
+      }
+      AnimationControlJ3D control = (AnimationControlJ3D) amap.getControl();
+      ((AnimationRendererJ3D)renderer).animation1D = false;
+
+      // get any usable frames from the old scene graph
+      Switch old_swit = null;
+      BranchGroup[] old_nodes = null;
+      double[] old_times = null;
+      boolean[] old_mark = null;
+      int old_len = 0;
+      boolean reuse = ((AnimationRendererJ3D) renderer).getReUseFrames();
+      if (group instanceof BranchGroup &&
+          ((BranchGroup) group).numChildren() > 0) {
+        Node g = ((BranchGroup) group).getChild(0);
+
+        // WLH 06 Feb 06 - support switch in a branch group.
+        if (g instanceof BranchGroup &&
+            ((BranchGroup) g).numChildren() > 0) {
+            g = ((BranchGroup) g).getChild(0);
+        }
+
+        if (g instanceof Switch) {
+          old_swit = (Switch) g;
+
+          old_len = old_swit.numChildren();
+          if (old_len > 0) {
+            old_nodes = new BranchGroup[old_len];
+            for (int i=0; i<old_len; i++) {
+              old_nodes[i] = (BranchGroup) old_swit.getChild(i);
+            }
+            // remove old_nodes from old_swit
+            for (int i=0; i<old_len; i++) {
+              old_nodes[i].detach();
+            }
+            old_times = new double[old_len];
+            old_mark = new boolean[old_len];
+            for (int i=0; i<old_len; i++) {
+              old_mark[i] = false;
+              if (old_nodes[i] instanceof VisADBranchGroup && reuse) {
+                old_times[i] = ((VisADBranchGroup) old_nodes[i]).getTime();
+              }
+              else {
+                old_times[i] = Double.NaN;
+              }
+            }
+          }
+        }
+      } // end if (((BranchGroup) group).numChildren() > 0)
+
+      // create frames for new scene graph
+      double[][] values = domain_set.getDoubles();
+      double[] times = values[0];
+      int len = times.length;
+      double delta = Math.abs((times[len-1] - times[0]) / (1000.0 * len));
+
+      // create new Switch and make live
+      // control.clearSwitches(this); // already done in DataRenderer.doAction
+      Switch swit = null;
+      if (old_swit != null) {
+        swit = old_swit;
+        ((AVControlJ3D) control).addPair((Switch) swit, domain_set, renderer);
+        ((AVControlJ3D) control).init();
+      }
+      else {
+        swit = (Switch) makeSwitch();
+        swit.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+        swit.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+        swit.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+
+        addSwitch(group, swit, control, domain_set, renderer);
+      }
+
+      // insert old frames into new scene graph, and make
+      // new (blank) VisADBranchGroups for rendering new frames
+      VisADBranchGroup[] nodes = new VisADBranchGroup[len];
+      boolean[] mark = new boolean[len];
+      for (int i=0; i<len; i++) {
+        for (int j=0; j<old_len; j++) {
+          if (!old_mark[j] && Math.abs(times[i] - old_times[j]) < delta) {
+            old_mark[j] = true;
+            nodes[i] = (VisADBranchGroup) old_nodes[j];
+            break;
+          }
+        }
+        if (nodes[i] != null) {
+          mark[i] = true;
+        }
+        else {
+          mark[i] = false;
+          nodes[i] = new VisADBranchGroup(times[i]);
+          nodes[i].setCapability(BranchGroup.ALLOW_DETACH);
+          nodes[i].setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+          nodes[i].setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+          nodes[i].setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+          ensureNotEmpty(nodes[i]);
+        }
+        addToSwitch(swit, nodes[i]);
+      }
+      for (int j=0; j<old_len; j++) {
+        if (!old_mark[j]) {
+          ((RendererJ3D) renderer).flush(old_nodes[j]);
+          old_nodes[j] = null;
+        }
+      }
+      // make sure group is live
+      if (group instanceof BranchGroup) {
+        ((AnimationRendererJ3D) renderer).setBranchEarly((BranchGroup) group);
+      }
+      // change animation sampling, but don't trigger re-transform
+      if (((AnimationRendererJ3D) renderer).getReUseFrames() &&
+          ((AnimationRendererJ3D) renderer).getSetSetOnReUseFrames()) {
+        control.setSet(domain_set, true);
+      }
+      else {
+        control.setCurrent(0);
+      }
+      old_nodes = null;
+      old_times = null;
+      old_mark = null;
+
+      // render new frames
+      for (int i=0; i<len; i++) {
+        if (!mark[i]) {
+          // not necessary, but perhaps if this is modified
+          // int[] lat_lon_indices = renderer.getLatLonIndices();
+          BranchGroup branch = (BranchGroup) makeBranch();
+          ((AnimationRendererJ3D) renderer).setVisADBranch(nodes[i]);
+          recurseRange(branch, ((Field) data).getSample(i),
+                       value_array, default_values, renderer);
+          ((AnimationRendererJ3D) renderer).setVisADBranch(null);
+          nodes[i].addChild(branch);
+          // not necessary, but perhaps if this is modified
+          // renderer.setLatLonIndices(lat_lon_indices);
+        }
+      }
+    }
+    else {
+      super.doTransform(group, data, value_array, default_values, renderer);
+    }
+
+    ensureNotEmpty(group);
+    return false;
+  }
+}
diff --git a/visad/java3d/ShadowFunctionOrSetTypeJ3D.java b/visad/java3d/ShadowFunctionOrSetTypeJ3D.java
new file mode 100644
index 0000000..eefb770
--- /dev/null
+++ b/visad/java3d/ShadowFunctionOrSetTypeJ3D.java
@@ -0,0 +1,1283 @@
+//
+// ShadowFunctionOrSetTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+import visad.util.ThreadManager;
+
+import javax.media.j3d.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import java.rmi.*;
+
+import java.awt.image.*;
+
+
+/**
+   The ShadowFunctionOrSetTypeJ3D is an abstract parent for
+   ShadowFunctionTypeJ3D and ShadowSetTypeJ3D.<P>
+*/
+public class ShadowFunctionOrSetTypeJ3D extends ShadowTypeJ3D {
+
+  ShadowRealTupleTypeJ3D Domain;
+  ShadowTypeJ3D Range; // null for ShadowSetTypeJ3D
+
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowFunctionOrSetTypeJ3D(MathType t, DataDisplayLink link,
+                                    ShadowType parent)
+      throws VisADException, RemoteException {
+    super(t, link, parent);
+    if (this instanceof ShadowFunctionTypeJ3D) {
+      Domain = (ShadowRealTupleTypeJ3D)
+               ((FunctionType) Type).getDomain().buildShadowType(link, this);
+      Range = (ShadowTypeJ3D)
+              ((FunctionType) Type).getRange().buildShadowType(link, this);
+      adaptedShadowType =
+        new ShadowFunctionType(t, link, getAdaptedParent(parent),
+                       (ShadowRealTupleType) Domain.getAdaptedShadowType(),
+                       Range.getAdaptedShadowType());
+    }
+    else {
+      Domain = (ShadowRealTupleTypeJ3D)
+               ((SetType) Type).getDomain().buildShadowType(Link, this);
+      Range = null;
+      adaptedShadowType =
+        new ShadowSetType(t, link, getAdaptedParent(parent),
+                       (ShadowRealTupleType) Domain.getAdaptedShadowType());
+    }
+  }
+
+  public ShadowRealTupleTypeJ3D getDomain() {
+    return Domain;
+  }
+
+  public ShadowTypeJ3D getRange() {
+    return Range;
+  }
+
+  /** clear AccumulationVector */
+  public void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+    if (this instanceof ShadowFunctionTypeJ3D) {
+      Range.preProcess();
+    }
+  }
+
+  /** transform data into a Java3D scene graph;
+      add generated scene graph components as children of group;
+      value_array are inherited valueArray values;
+      default_values are defaults for each display.DisplayRealTypeVector;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, final float[] value_array,
+                             final float[] default_values, final DataRenderer renderer)
+         throws VisADException, RemoteException {
+
+    boolean post = true; // FIXME what value for animation?
+    boolean isAnimation1d = false;
+    boolean isTerminal = adaptedShadowType.getIsTerminal();
+    
+    ScalarMap timeMap = null; // used in the animation case to get control
+    DataDisplayLink[] link_s = renderer.getLinks();
+    DataDisplayLink link = link_s[0];
+    Vector scalarMaps = link.getSelectedMapVector();
+    
+    // only determine if it's an animation if non-terminal. isTerminal will
+    // only be determined if there are scalar maps - defaults to false
+    if (!isTerminal && !scalarMaps.isEmpty()) {
+      // determine if it's an animation
+      MathType mtype = data.getType();
+      if (mtype instanceof FunctionType) {
+        int ani_map_idx = 0;
+        FunctionType function = (FunctionType) mtype;
+        RealTupleType functionD = function.getDomain();
+        for (int kk = 0; kk < scalarMaps.size(); kk++) {
+          ScalarMap scalarMap = (ScalarMap) scalarMaps.elementAt(kk);
+          String scalar_name = scalarMap.getScalarName();
+          if (scalar_name.equals(((RealType) functionD.getComponent(0)).getName())) {
+            if (((scalarMap.getDisplayScalar()).equals(Display.Animation))
+                && (functionD.getDimension() == 1)) {
+              isAnimation1d = true;
+              ani_map_idx = kk;
+            }
+          }
+        }
+        // animation domain
+        timeMap = (ScalarMap) scalarMaps.elementAt(ani_map_idx);
+      }
+    }
+    // animation logic
+    if (isAnimation1d){
+      
+      // analyze data's domain (its a Field)
+      Set domainSet = ((Field) data).getDomainSet();
+      
+      // create and add switch with nodes for animation images
+      int domainLength = domainSet.getLength(); // num of domain nodes
+      Switch swit = (Switch) makeSwitch(domainLength);
+      AnimationControlJ3D control = (AnimationControlJ3D)timeMap.getControl();
+      
+      addSwitch(group, swit, control, domainSet, renderer);
+
+
+      /***
+          Old code:
+       // render frames
+      for (int i=0; i<domainLength; i++) {
+        BranchGroup node = (BranchGroup) swit.getChild(i);
+        // not necessary, but perhaps if this is modified
+        // int[] lat_lon_indices = renderer.getLatLonIndices();
+        BranchGroup branch = (BranchGroup) makeBranch();
+        recurseRange(branch, ((Field) data).getSample(i),
+                     value_array, default_values, renderer);
+        node.addChild(branch);
+        // not necessary, but perhaps if this is modified
+        // renderer.setLatLonIndices(lat_lon_indices);
+      }
+      ****/
+
+      //jeffmc:First construct the branches
+      List<BranchGroup> branches = new ArrayList<BranchGroup>();
+      for (int i=0; i<domainLength; i++) {
+          BranchGroup node = (BranchGroup) swit.getChild(i);
+          BranchGroup branch = (BranchGroup) makeBranch();
+          branches.add(branch);
+      }
+
+      ThreadManager threadManager = new ThreadManager("animation rendering");
+      for (int i=0; i<domainLength; i++) {
+          final BranchGroup branch = (BranchGroup) branches.get(i);
+          final Data sample  = ((Field) data).getSample(i);
+          final BranchGroup node = (BranchGroup) swit.getChild(i);
+          threadManager.addRunnable(new ThreadManager.MyRunnable() {
+                  public void run()  throws Exception {
+                      recurseRange(branch, sample,
+                                   value_array, default_values, renderer);
+                      node.addChild(branch);          
+                  }
+              });
+      }
+
+      threadManager.runInParallel();
+    } 
+    else {
+      ShadowFunctionOrSetType shadow = (ShadowFunctionOrSetType)adaptedShadowType;
+      post = shadow.doTransform(group, data, value_array, default_values, renderer, this); 
+    }
+    
+    ensureNotEmpty(group);
+    return post;
+  }
+
+  /**
+   * Get the possibly adjusted texture width.
+   * @param data_width The initial texture width.
+   * @return If <code>DisplayImplJ3D.TEXTURE_NPOT</code> then return
+   *  <code>data_width</code>, otherwise return the minimum power of two greater
+   *  than <code>data_width</code>.
+   * @see DisplayImplJ3D#TEXTURE_NPOT
+   */
+  public int textureWidth(int data_width) {
+    if (DisplayImplJ3D.TEXTURE_NPOT) return data_width;
+    // must be a power of 2 in Java3D
+    int texture_width = 1;
+    while (texture_width < data_width) texture_width *= 2;
+    return texture_width;
+  }
+
+  /**
+   * Get the possibly adjusted texture height.
+   * @param data_height The initial texture height.
+   * @return If <code>DisplayImplJ3D.TEXTURE_NPOT</code> then return
+   *  <code>data_height</code>, otherwise return the minimum power of two greater
+   *  than <code>data_height</code>.
+   * @see DisplayImplJ3D#TEXTURE_NPOT
+   */
+  public int textureHeight(int data_height) {
+    if (DisplayImplJ3D.TEXTURE_NPOT) return data_height;
+    // must be a power of 2 in Java3D
+    int texture_height = 1;
+    while (texture_height < data_height) texture_height *= 2;
+    return texture_height;
+  }
+
+  /**
+   * Get the possibly adjusted texture depth.
+   * @param data_depth The initial texture depth.
+   * @return If <code>DisplayImplJ3D.TEXTURE_NPOT</code> then return
+   *  <code>data_depth</code>, otherwise return the minimum power of two greater
+   *  than <code>data_depth</code>.
+   * @see DisplayImplJ3D#TEXTURE_NPOT
+   */
+  public int textureDepth(int data_depth) {
+    if (DisplayImplJ3D.TEXTURE_NPOT) return data_depth;
+    // must be a power of 2 in Java3D
+    int texture_depth = 1;
+    while (texture_depth < data_depth) texture_depth *= 2;
+    return texture_depth;
+  }
+
+  public void adjustZ(float[] coordinates) {
+    if (display.getDisplayRenderer().getMode2D()) {
+      for (int i=2; i<coordinates.length; i+=3) {
+        coordinates[i] = DisplayImplJ3D.BACK2D;
+      }
+    }
+  }
+
+  public int getImageComponentType(int buffImgType) {
+    if (buffImgType == BufferedImage.TYPE_4BYTE_ABGR) {
+      return ImageComponent2D.FORMAT_RGBA8;
+    }
+    else if (buffImgType == BufferedImage.TYPE_3BYTE_BGR) {
+      return ImageComponent2D.FORMAT_RGB8;
+    }
+    else if (buffImgType == BufferedImage.TYPE_BYTE_GRAY) {
+      return ImageComponent2D.FORMAT_CHANNEL8;
+    }
+    return ImageComponent2D.FORMAT_RGBA8;
+  }
+
+  public int getTextureType(int buffImgType) {
+    if (buffImgType == BufferedImage.TYPE_4BYTE_ABGR) {
+      return Texture2D.RGBA;
+    }
+    else if (buffImgType == BufferedImage.TYPE_3BYTE_BGR) {
+      return Texture2D.RGB;
+    }
+    else if (buffImgType == BufferedImage.TYPE_BYTE_GRAY) {
+      return Texture2D.LUMINANCE;
+    }
+    return Texture2D.RGBA;
+  }
+
+  public void setTexCoords(float[] texCoords, float ratiow, float ratioh) {
+    setTexCoords(texCoords, ratiow, ratioh, false);
+  }
+
+  public void setTexCoords(float[] texCoords, float ratiow, float ratioh, boolean yUp) {
+    if (!yUp) { // the default
+      // corner 0
+      texCoords[0] = 0.0f;
+      texCoords[1] = 1.0f;
+      // corner 1
+      texCoords[2] = ratiow;
+      texCoords[3] = 1.0f;
+      // corner 2
+      texCoords[4] = ratiow;
+      texCoords[5] = 1.0f - ratioh;
+      // corner 3
+      texCoords[6] = 0.0f;
+      texCoords[7] = 1.0f - ratioh;
+    }
+    else {  // yUp = true, for imageByReference=true
+      // corner 0
+      texCoords[0] = 0.0f;
+      texCoords[1] = 0.0f;
+      // corner 1
+      texCoords[2] = 0.0f;
+      texCoords[3] = ratioh;
+      // corner 2
+      texCoords[4] = ratiow;
+      texCoords[5] = ratioh;
+      // corner 3
+      texCoords[6] = ratiow;
+      texCoords[7] = 0.0f;
+    }
+  }
+
+  public float[] setTex3DCoords(int length, int axis, float ratiow,
+                                float ratioh, float ratiod) {
+    // need to flip Y and Z in X and Y views?
+    float[] texCoords = new float[12 * length];
+    if (axis == 2) {
+      for (int i=0; i<length; i++) {
+        int i12 = i * 12;
+        float depth = 0.0f + (ratiod - 0.0f) * i / (length - 1.0f);
+        // corner 0
+        texCoords[i12] = 0.0f;
+        texCoords[i12 + 1] = 1.0f;
+        texCoords[i12 + 2] = depth;
+        // corner 1
+        texCoords[i12 + 3] = ratiow;
+        texCoords[i12 + 4] = 1.0f;
+        texCoords[i12 + 5] = depth;
+        // corner 2
+        texCoords[i12 + 6] = ratiow;
+        texCoords[i12 + 7] = 1.0f - ratioh;
+        texCoords[i12 + 8] = depth;
+        // corner 3
+        texCoords[i12 + 9] = 0.0f;
+        texCoords[i12 + 10] = 1.0f - ratioh;
+        texCoords[i12 + 11] = depth;
+      }
+    }
+    else if (axis == 1) {
+      for (int i=0; i<length; i++) {
+        int i12 = i * 12;
+        float height = 1.0f - ratioh * i / (length - 1.0f);
+        // corner 0
+        texCoords[i12] = 0.0f;
+        texCoords[i12 + 1] = height;
+        texCoords[i12 + 2] = 0.0f;
+        // corner 1
+        texCoords[i12 + 3] = ratiow;
+        texCoords[i12 + 4] = height;
+        texCoords[i12 + 5] = 0.0f;
+        // corner 2
+        texCoords[i12 + 6] = ratiow;
+        texCoords[i12 + 7] = height;
+        texCoords[i12 + 8] = ratiod;
+        // corner 3
+        texCoords[i12 + 9] = 0.0f;
+        texCoords[i12 + 10] = height;
+        texCoords[i12 + 11] = ratiod;
+      }
+    }
+    else if (axis == 0) {
+      for (int i=0; i<length; i++) {
+        int i12 = i * 12;
+        float width = 0.0f + (ratiow - 0.0f) * i / (length - 1.0f);
+        // corner 0
+        texCoords[i12] = width;
+        texCoords[i12 + 1] = 1.0f;
+        texCoords[i12 + 2] = 0.0f;
+        // corner 1
+        texCoords[i12 + 3] = width;
+        texCoords[i12 + 4] = 1.0f - ratioh;
+        texCoords[i12 + 5] = 0.0f;
+        // corner 2
+        texCoords[i12 + 6] = width;
+        texCoords[i12 + 7] = 1.0f - ratioh;
+        texCoords[i12 + 8] = ratiod;
+        // corner 3
+        texCoords[i12 + 9] = width;
+        texCoords[i12 + 10] = 1.0f;
+        texCoords[i12 + 11] = ratiod;
+      }
+    }
+    return texCoords;
+  }
+
+  // WLH 17 March 2000
+  private static float EPS = 0.00f;
+
+  public float[] setTexStackCoords(int length, int axis, float ratiow,
+                                   float ratioh, float ratiod) {
+    float[] texCoords = new float[8 * length];
+    if (axis == 2) {
+      for (int i=0; i<length; i++) {
+        int i8 = i * 8;
+        // corner 0
+        texCoords[i8] = 0.0f + EPS;
+        texCoords[i8 + 1] = 1.0f - EPS;
+        // corner 1
+        texCoords[i8 + 2] = ratiow - EPS;
+        texCoords[i8 + 3] = 1.0f - EPS;
+        // corner 2
+        texCoords[i8 + 4] = ratiow - EPS;
+        texCoords[i8 + 5] = 1.0f - ratioh + EPS;
+        // corner 3
+        texCoords[i8 + 6] = 0.0f + EPS;
+        texCoords[i8 + 7] = 1.0f - ratioh + EPS;
+      }
+    }
+    else if (axis == 1) {
+      // WLH 23 Feb 2000 - flip Z
+      for (int i=0; i<length; i++) {
+        int i8 = i * 8;
+        // corner 0
+        texCoords[i8] = 0.0f + EPS;
+        texCoords[i8 + 1] = 1.0f - EPS;
+        // corner 1
+        texCoords[i8 + 2] = ratiow - EPS;
+        texCoords[i8 + 3] = 1.0f - EPS;
+        // corner 2
+        texCoords[i8 + 4] = ratiow - EPS;
+        texCoords[i8 + 5] = 1.0f - ratiod + EPS;
+        // corner 3
+        texCoords[i8 + 6] = 0.0f + EPS;
+        texCoords[i8 + 7] = 1.0f - ratiod + EPS;
+      }
+    }
+    else if (axis == 0) {
+      // WLH 23 Feb 2000 - flip Y and Z
+      for (int i=0; i<length; i++) {
+        int i8 = i * 8;
+        // corner 0
+        texCoords[i8] = 0.0f + EPS;
+        texCoords[i8 + 1] = 1.0f - EPS;
+        // corner 1
+        texCoords[i8 + 2] = ratioh - EPS;
+        texCoords[i8 + 3] = 1.0f - EPS;
+        // corner 2
+        texCoords[i8 + 4] = ratioh - EPS;
+        texCoords[i8 + 5] = 1.0f - ratiod + EPS;
+        // corner 3
+        texCoords[i8 + 6] = 0.0f + EPS;
+        texCoords[i8 + 7] = 1.0f - ratiod + EPS;
+      }
+    }
+/* WLH 17 March 2000
+    if (axis == 2) {
+      for (int i=0; i<length; i++) {
+        int i8 = i * 8;
+        // corner 0
+        texCoords[i8] = 0.0f;
+        texCoords[i8 + 1] = 1.0f;
+        // corner 1
+        texCoords[i8 + 2] = ratiow;
+        texCoords[i8 + 3] = 1.0f;
+        // corner 2
+        texCoords[i8 + 4] = ratiow;
+        texCoords[i8 + 5] = 1.0f - ratioh;
+        // corner 3
+        texCoords[i8 + 6] = 0.0f;
+        texCoords[i8 + 7] = 1.0f - ratioh;
+      }
+    }
+    else if (axis == 1) {
+      // WLH 23 Feb 2000 - flip Z
+      for (int i=0; i<length; i++) {
+        int i8 = i * 8;
+        // corner 0
+        texCoords[i8] = 0.0f;
+        texCoords[i8 + 1] = 1.0f;
+        // corner 1
+        texCoords[i8 + 2] = ratiow;
+        texCoords[i8 + 3] = 1.0f;
+        // corner 2
+        texCoords[i8 + 4] = ratiow;
+        texCoords[i8 + 5] = 1.0f - ratiod;
+        // corner 3
+        texCoords[i8 + 6] = 0.0f;
+        texCoords[i8 + 7] = 1.0f - ratiod;
+      }
+    }
+    else if (axis == 0) {
+      // WLH 23 Feb 2000 - flip Y and Z
+      for (int i=0; i<length; i++) {
+        int i8 = i * 8;
+        // corner 0
+        texCoords[i8] = 0.0f;
+        texCoords[i8 + 1] = 1.0f;
+        // corner 1
+        texCoords[i8 + 2] = ratioh;
+        texCoords[i8 + 3] = 1.0f;
+        // corner 2
+        texCoords[i8 + 4] = ratioh;
+        texCoords[i8 + 5] = 1.0f - ratiod;
+        // corner 3
+        texCoords[i8 + 6] = 0.0f;
+        texCoords[i8 + 7] = 1.0f - ratiod;
+      }
+    }
+*/
+    return texCoords;
+  }
+
+  public Vector getTextMaps(int i, int[] textIndices) {
+    if (i < 0) {
+      return ((ShadowTextTypeJ3D) Range).getSelectedMapVector();
+    }
+    else {
+      ShadowTextTypeJ3D text = (ShadowTextTypeJ3D)
+        ((ShadowTupleTypeJ3D) Range).getComponent(textIndices[i]);
+      return text.getSelectedMapVector();
+    }
+  }
+
+  public void textureToGroup(Object group, VisADGeometryArray array,
+                            BufferedImage image, GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color,
+                            int texture_width, int texture_height, boolean byReference, boolean yUp, VisADImageTile tile) throws VisADException {
+    textureToGroup(group, array, image, mode, constant_alpha, constant_color, texture_width, texture_height, byReference, yUp, tile, false);
+  }
+
+  public void textureToGroup(Object group, VisADGeometryArray array,
+                            BufferedImage image, GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color,
+                            int texture_width, int texture_height) throws VisADException {
+    textureToGroup(group, array, image, mode, constant_alpha, constant_color, texture_width, texture_height, false, false, null, false);
+  }
+
+  public void textureToGroup(Object group, VisADGeometryArray array,
+                            BufferedImage image, GraphicsModeControl mode,
+                            float constant_alpha, float[] constant_color,
+                            int texture_width, int texture_height, 
+                            boolean byReference, boolean yUp, VisADImageTile tile, boolean smoothen)
+         throws VisADException {
+    GeometryArray geometry = display.makeGeometry(array);
+    // System.out.println("texture geometry");
+    // create basic Appearance
+    TransparencyAttributes c_alpha = null;
+
+    if (constant_alpha == 1.0f) {
+      // constant opaque alpha = NONE
+      c_alpha = null;
+    }
+    else if (constant_alpha == constant_alpha) {
+      	//12NOV2012: GHANSHAM Use inversed alpha for 3 byte buffered images too
+        int image_type = image.getType();
+        boolean inversed_alpha = (image_type == BufferedImage.TYPE_3BYTE_BGR) || (image_type == BufferedImage.TYPE_BYTE_GRAY);
+      	c_alpha = new TransparencyAttributes(TransparencyAttributes.BLENDED,
+                                (inversed_alpha)? (1.0f - constant_alpha): constant_alpha);
+      c_alpha.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE); //REUSE GEOMETRY/COLORBYTE REQUIREMENT
+    }
+    else {
+      c_alpha = new TransparencyAttributes();
+      c_alpha.setTransparencyMode(TransparencyAttributes.BLENDED);
+      c_alpha.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE); //REUSE GEOMETRY/COLORBYTE REQUIREMENT
+    }
+    ColoringAttributes c_color = null;
+    if (constant_color != null && constant_color.length == 3) {
+      c_color = new ColoringAttributes();
+      c_color.setColor(constant_color[0], constant_color[1], constant_color[2]);
+    }
+    Appearance appearance =
+      makeAppearance(mode, c_alpha, null, geometry, false);
+    appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE); //REUSE GEOMETRY/COLORBYTE REQUIREMENT
+    // create TextureAttributes
+    TextureAttributes texture_attributes = new TextureAttributes();
+
+    // WLH 20 June 2001
+    if (smoothen) {
+      texture_attributes.setTextureMode(TextureAttributes.MODULATE);
+    } else {
+      texture_attributes.setTextureMode(TextureAttributes.REPLACE);
+    }
+
+    texture_attributes.setPerspectiveCorrectionMode(TextureAttributes.NICEST);
+    appearance.setTextureAttributes(texture_attributes);
+    // create Texture2D
+// TextureLoader uses 1st argument = 1
+/*
+System.out.println("Texture.BASE_LEVEL = " + Texture.BASE_LEVEL); // 1
+System.out.println("Texture.RGBA = " + Texture.RGBA); // 6
+*/
+    Texture2D texture = new Texture2D(Texture.BASE_LEVEL, getTextureType(image.getType()),
+                                      texture_width, texture_height);
+    texture.setCapability(Texture.ALLOW_IMAGE_READ);
+    ImageComponent2D image2d =
+      new ImageComponent2D(getImageComponentType(image.getType()), image, byReference, yUp);
+    image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+    if (byReference) {
+      image2d.setCapability(ImageComponent.ALLOW_IMAGE_WRITE);
+    }
+    texture.setImage(0, image2d);
+
+    //
+    // from TextureLoader
+    // TextureLoader uses 3 for both setMinFilter and setMagFilter
+/*
+System.out.println("Texture.FASTEST = " + Texture.FASTEST); // 0
+System.out.println("Texture.NICEST = " + Texture.NICEST); // 1
+System.out.println("Texture.BASE_LEVEL_POINT = " + Texture.BASE_LEVEL_POINT); // 2
+System.out.println("Texture.BASE_LEVEL_LINEAR = " + Texture.BASE_LEVEL_LINEAR); // 3
+*/
+/* for interpolation:
+    texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+    texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+*/
+    if (smoothen) {
+      texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+      texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+    } else {
+      texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+      texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+    }
+
+    texture.setEnable(true);
+    // end of from TextureLoader
+    //
+    Shape3D shape = new Shape3D(geometry, appearance);
+    shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);	//REUSE GEOMETRY/COLORBYTE REQUIREMENT
+    shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    appearance.setTexture(texture);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE);  //REUSE GEOMETRY/COLORBYTE REQUIREMENT
+
+    // WLH 6 April 2000
+    // ((Group) group).addChild(shape);
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    branch.addChild(shape);
+    if (((Group) group).numChildren() > 0) {
+      ((Group) group).setChild(branch, 0);
+    }
+    else {
+      ((Group) group).addChild(branch);
+    }
+
+    if (tile != null) {
+      tile.setImageComponent(image2d);
+    }
+  }
+
+  public void texture3DToGroup(Object group, VisADGeometryArray arrayX,
+                    VisADGeometryArray arrayY, VisADGeometryArray arrayZ,
+                    VisADGeometryArray arrayXrev,
+                    VisADGeometryArray arrayYrev,
+                    VisADGeometryArray arrayZrev,
+                    BufferedImage[] images, GraphicsModeControl mode,
+                    float constant_alpha, float[] constant_color,
+                    int texture_width, int texture_height,
+                    int texture_depth, DataRenderer renderer)
+         throws VisADException {
+
+    GeometryArray geometryX = display.makeGeometry(arrayX);
+    GeometryArray geometryY = display.makeGeometry(arrayY);
+    GeometryArray geometryZ = display.makeGeometry(arrayZ);
+    GeometryArray geometryXrev = display.makeGeometry(arrayXrev);
+    GeometryArray geometryYrev = display.makeGeometry(arrayYrev);
+    GeometryArray geometryZrev = display.makeGeometry(arrayZrev);
+    // System.out.println("texture geometry");
+    // create basic Appearance
+    TransparencyAttributes c_alpha = null;
+
+    if (constant_alpha == 1.0f) {
+      // constant opaque alpha = NONE
+      c_alpha = null;
+    }
+    else if (constant_alpha == constant_alpha) {
+      // c_alpha = new TransparencyAttributes(mode.getTransparencyMode(),
+      c_alpha = new TransparencyAttributes(TransparencyAttributes.BLENDED,
+                                           constant_alpha);
+    }
+    else {
+      c_alpha = new TransparencyAttributes();
+      c_alpha.setTransparencyMode(TransparencyAttributes.BLENDED);
+    }
+    ColoringAttributes c_color = null;
+    if (constant_color != null && constant_color.length == 3) {
+      c_color = new ColoringAttributes();
+      c_color.setColor(constant_color[0], constant_color[1], constant_color[2]);
+    }
+    Appearance appearance =
+      makeAppearance(mode, c_alpha, null, geometryX, true);
+    // create TextureAttributes
+    TextureAttributes texture_attributes = new TextureAttributes();
+    // texture_attributes.setTextureMode(TextureAttributes.REPLACE);
+    texture_attributes.setTextureMode(TextureAttributes.MODULATE);
+    texture_attributes.setPerspectiveCorrectionMode(
+                          TextureAttributes.NICEST);
+    appearance.setTextureAttributes(texture_attributes);
+    // create Texture2D
+// TextureLoader uses 1st argument = 1
+/*
+System.out.println("Texture.BASE_LEVEL = " + Texture.BASE_LEVEL); // 1
+System.out.println("Texture.RGBA = " + Texture.RGBA); // 6
+*/
+    Texture3D texture = new Texture3D(Texture.BASE_LEVEL, Texture.RGBA,
+                          texture_width, texture_height, texture_depth);
+    texture.setCapability(Texture.ALLOW_IMAGE_READ);
+    ImageComponent3D image3d =
+      new ImageComponent3D(ImageComponent.FORMAT_RGBA, texture_width,
+                           texture_height, texture_depth);
+    image3d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+    for (int i=0; i<texture_depth; i++) {
+      image3d.set(i, images[i]);
+      images[i] = null; // take out the garbage
+    }
+    texture.setImage(0, image3d);
+    //
+    // from TextureLoader
+    // TextureLoader uses 3 for both setMinFilter and setMagFilter
+/*
+System.out.println("Texture.FASTEST = " + Texture.FASTEST); // 0
+System.out.println("Texture.NICEST = " + Texture.NICEST); // 1
+System.out.println("Texture.BASE_LEVEL_POINT = " + Texture.BASE_LEVEL_POINT); // 2
+System.out.println("Texture.BASE_LEVEL_LINEAR = " + Texture.BASE_LEVEL_LINEAR); // 3
+*/
+/* for interpolation: */
+    texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+    texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+/* for sampling:
+    texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+    texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+*/
+    texture.setEnable(true);
+    // end of from TextureLoader
+
+    // OK to share appearance ??
+    Shape3D shapeX = new Shape3D(geometryX, appearance);
+    shapeX.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    Shape3D shapeY = new Shape3D(geometryY, appearance);
+    shapeY.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    Shape3D shapeZ = new Shape3D(geometryZ, appearance);
+    shapeZ.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    Shape3D shapeXrev = new Shape3D(geometryXrev, appearance);
+    Shape3D shapeYrev = new Shape3D(geometryYrev, appearance);
+    Shape3D shapeZrev = new Shape3D(geometryZrev, appearance);
+    appearance.setTexture(texture);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+
+    shapeX.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shapeX.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    shapeY.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shapeY.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    shapeZ.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shapeZ.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    shapeXrev.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shapeXrev.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    shapeYrev.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shapeYrev.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    shapeZrev.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shapeZrev.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    
+    Switch swit = (Switch) makeSwitch();
+    swit.addChild(shapeX);
+    swit.addChild(shapeY);
+    swit.addChild(shapeZ);
+    swit.addChild(shapeXrev);
+    swit.addChild(shapeYrev);
+    swit.addChild(shapeZrev);
+
+    // WLH 6 April 2000
+    // ((Group) group).addChild(swit);
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    branch.addChild(swit);
+    if (((Group) group).numChildren() > 0) {
+      ((Group) group).setChild(branch, 0);
+    }
+    else {
+      ((Group) group).addChild(branch);
+    }
+
+    ProjectionControlJ3D control =
+      (ProjectionControlJ3D) display.getProjectionControl();
+    control.addPair(swit, renderer);
+  }
+
+  public void textureStackToGroup(Object group, VisADGeometryArray arrayX,
+                    VisADGeometryArray arrayY, VisADGeometryArray arrayZ,
+                    VisADGeometryArray arrayXrev,
+                    VisADGeometryArray arrayYrev,
+                    VisADGeometryArray arrayZrev,
+                    BufferedImage[] imagesX,
+                    BufferedImage[] imagesY,
+                    BufferedImage[] imagesZ,
+                    GraphicsModeControl mode,
+                    float constant_alpha, float[] constant_color,
+                    int texture_width, int texture_height,
+                    int texture_depth, DataRenderer renderer)
+         throws VisADException {
+
+    GeometryArray[] geometryX = makeGeometrys(arrayX);
+    GeometryArray[] geometryY = makeGeometrys(arrayY);
+    GeometryArray[] geometryZ = makeGeometrys(arrayZ);
+/* not needed ??
+    GeometryArray[] geometryXrev = makeGeometrys(arrayXrev);
+    GeometryArray[] geometryYrev = makeGeometrys(arrayYrev);
+    GeometryArray[] geometryZrev = makeGeometrys(arrayZrev);
+*/
+
+    int nx = arrayX.coordinates.length;
+    boolean flipX = (arrayX.coordinates[0] > arrayX.coordinates[nx-3]);
+    int ny = arrayY.coordinates.length;
+    boolean flipY = (arrayY.coordinates[1] > arrayY.coordinates[ny-2]);
+    int nz = arrayZ.coordinates.length;
+    boolean flipZ = (arrayZ.coordinates[2] > arrayZ.coordinates[nz-1]);
+    // System.out.println("flipX = " + flipX + " flipY = " + flipY +
+    //                    " flipZ = " + flipZ);
+
+    // create Attributes for Appearances
+    TransparencyAttributes c_alpha = null;
+    if (constant_alpha == 1.0f) {
+      // constant opaque alpha = NONE
+      c_alpha = null;
+    }
+    else if (constant_alpha == constant_alpha) {
+      // c_alpha = new TransparencyAttributes(mode.getTransparencyMode(),
+      c_alpha = new TransparencyAttributes(TransparencyAttributes.BLENDED,
+                                           constant_alpha);
+    }
+    else {
+      c_alpha = new TransparencyAttributes();
+      c_alpha.setTransparencyMode(TransparencyAttributes.BLENDED);
+    }
+    ColoringAttributes c_color = null;
+    if (constant_color != null && constant_color.length == 3) {
+      c_color = new ColoringAttributes();
+      c_color.setColor(constant_color[0], constant_color[1], constant_color[2]);
+    }
+    TextureAttributes texture_attributes = new TextureAttributes();
+
+    // WLH 17 March 2000
+    // texture_attributes.setTextureMode(TextureAttributes.MODULATE);
+    texture_attributes.setTextureMode(TextureAttributes.REPLACE);
+
+    texture_attributes.setPerspectiveCorrectionMode(
+                          TextureAttributes.NICEST);
+
+    int transparencyMode = mode.getTransparencyMode();
+
+    OrderedGroup branchX = new OrderedGroup();
+    branchX.setCapability(Group.ALLOW_CHILDREN_READ);
+    int data_depth = geometryX.length;
+    Shape3D[] shapeX = new Shape3D[data_depth];
+    for (int ii=0; ii<data_depth; ii++) {
+      int i = flipX ? data_depth-1-ii : ii;
+      int width = imagesX[i].getWidth();
+      int height = imagesX[i].getHeight();
+      Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                        width, height);
+      texture.setCapability(Texture.ALLOW_IMAGE_READ);
+      ImageComponent2D image2d =
+        new ImageComponent2D(ImageComponent.FORMAT_RGBA, imagesX[i]);
+      image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+      texture.setImage(0, image2d);
+      Appearance appearance =
+        makeAppearance(mode, c_alpha, null, geometryX[i], true);
+      appearance.setTextureAttributes(texture_attributes);
+      // WLH 17 March 2000
+      if (transparencyMode == TransparencyAttributes.FASTEST) {
+        texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+        texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+      }
+      else {
+        texture.setBoundaryModeS(Texture.CLAMP);
+        texture.setBoundaryModeT(Texture.CLAMP);
+        texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+        texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+      }
+      texture.setEnable(true);
+      appearance.setTexture(texture);
+      appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+      shapeX[i] = new Shape3D(geometryX[i], appearance);
+      shapeX[i].setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shapeX[i].setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      branchX.addChild(shapeX[i]);
+    }
+    OrderedGroup branchXrev = new OrderedGroup();
+    branchXrev.setCapability(Group.ALLOW_CHILDREN_READ);
+    for (int ii=data_depth-1; ii>=0; ii--) {
+      int i = flipX ? data_depth-1-ii : ii;
+      int width = imagesX[i].getWidth();
+      int height = imagesX[i].getHeight();
+      Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                        width, height);
+      texture.setCapability(Texture.ALLOW_IMAGE_READ);
+      ImageComponent2D image2d =
+        new ImageComponent2D(ImageComponent.FORMAT_RGBA, imagesX[i]);
+      image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+      texture.setImage(0, image2d);
+      Appearance appearance =
+        makeAppearance(mode, c_alpha, null, geometryX[i], true);
+      appearance.setTextureAttributes(texture_attributes);
+      // WLH 17 March 2000
+      if (transparencyMode == TransparencyAttributes.FASTEST) {
+        texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+        texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+      }
+      else {
+        texture.setBoundaryModeS(Texture.CLAMP);
+        texture.setBoundaryModeT(Texture.CLAMP);
+        texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+        texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+      }
+      texture.setEnable(true);
+      appearance.setTexture(texture);
+      appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+      shapeX[i] = new Shape3D(geometryX[i], appearance);
+      shapeX[i].setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shapeX[i].setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      branchXrev.addChild(shapeX[i]);
+    }
+    shapeX = null;
+
+    OrderedGroup branchY = new OrderedGroup();
+    branchY.setCapability(Group.ALLOW_CHILDREN_READ);
+    int data_height = geometryY.length;
+    Shape3D[] shapeY = new Shape3D[data_height];
+    for (int ii=0; ii<data_height; ii++) {
+      int i = flipY ? data_height-1-ii : ii;
+      int width = imagesY[i].getWidth();
+      int height = imagesY[i].getHeight();
+      Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                        width, height);
+      texture.setCapability(Texture.ALLOW_IMAGE_READ);
+      // flip texture on Y axis
+      ImageComponent2D image2d =
+        new ImageComponent2D(ImageComponent.FORMAT_RGBA, imagesY[i]);
+      image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+      texture.setImage(0, image2d);
+      Appearance appearance =
+        makeAppearance(mode, c_alpha, null, geometryY[i], true);
+      appearance.setTextureAttributes(texture_attributes);
+      // WLH 17 March 2000
+      if (transparencyMode == TransparencyAttributes.FASTEST) {
+        texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+        texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+      }
+      else {
+        texture.setBoundaryModeS(Texture.CLAMP);
+        texture.setBoundaryModeT(Texture.CLAMP);
+        texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+        texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+      }
+      texture.setEnable(true);
+      appearance.setTexture(texture);
+      appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+      shapeY[i] = new Shape3D(geometryY[i], appearance);
+      shapeY[i].setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shapeY[i].setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      branchY.addChild(shapeY[i]);
+    }
+    OrderedGroup branchYrev = new OrderedGroup();
+    branchYrev.setCapability(Group.ALLOW_CHILDREN_READ);
+    for (int ii=data_height-1; ii>=0; ii--) {
+      int i = flipY ? data_height-1-ii : ii;
+      int width = imagesY[i].getWidth();
+      int height = imagesY[i].getHeight();
+      Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                        width, height);
+      texture.setCapability(Texture.ALLOW_IMAGE_READ);
+      // flip texture on Y axis
+      ImageComponent2D image2d =
+        new ImageComponent2D(ImageComponent.FORMAT_RGBA, imagesY[i]);
+      image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+      texture.setImage(0, image2d);
+      Appearance appearance =
+        makeAppearance(mode, c_alpha, null, geometryY[i], true);
+      appearance.setTextureAttributes(texture_attributes);
+      // WLH 17 March 2000
+      if (transparencyMode == TransparencyAttributes.FASTEST) {
+        texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+        texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+      }
+      else {
+        texture.setBoundaryModeS(Texture.CLAMP);
+        texture.setBoundaryModeT(Texture.CLAMP);
+        texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+        texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+      }
+      texture.setEnable(true);
+      appearance.setTexture(texture);
+      appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+      shapeY[i] = new Shape3D(geometryY[i], appearance);
+      shapeY[i].setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shapeY[i].setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      branchYrev.addChild(shapeY[i]);
+    }
+    shapeY = null;
+
+    OrderedGroup branchZ = new OrderedGroup();
+    branchZ.setCapability(Group.ALLOW_CHILDREN_READ);
+    int data_width = geometryZ.length;
+    Shape3D[] shapeZ = new Shape3D[data_width];
+    for (int ii=0; ii<data_width; ii++) {
+      int i = flipZ ? data_width-1-ii : ii;
+      int width = imagesZ[i].getWidth();
+      int height = imagesZ[i].getHeight();
+      Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                        width, height);
+      texture.setCapability(Texture.ALLOW_IMAGE_READ);
+      ImageComponent2D image2d =
+        new ImageComponent2D(ImageComponent.FORMAT_RGBA, imagesZ[i]);
+      image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+      texture.setImage(0, image2d);
+      Appearance appearance =
+        makeAppearance(mode, c_alpha, null, geometryZ[i], true);
+      appearance.setTextureAttributes(texture_attributes);
+      // WLH 17 March 2000
+      if (transparencyMode == TransparencyAttributes.FASTEST) {
+        texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+        texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+      }
+      else {
+        texture.setBoundaryModeS(Texture.CLAMP);
+        texture.setBoundaryModeT(Texture.CLAMP);
+        texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+        texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+      }
+      texture.setEnable(true);
+      appearance.setTexture(texture);
+      appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+      shapeZ[i] = new Shape3D(geometryZ[i], appearance);
+      shapeZ[i].setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shapeZ[i].setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      branchZ.addChild(shapeZ[i]);
+    }
+    OrderedGroup branchZrev = new OrderedGroup();
+    branchZrev.setCapability(Group.ALLOW_CHILDREN_READ);
+    for (int ii=data_width-1; ii>=0; ii--) {
+      int i = flipZ ? data_width-1-ii : ii;
+      int width = imagesZ[i].getWidth();
+      int height = imagesZ[i].getHeight();
+      Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA,
+                                        width, height);
+      texture.setCapability(Texture.ALLOW_IMAGE_READ);
+      ImageComponent2D image2d =
+        new ImageComponent2D(ImageComponent.FORMAT_RGBA, imagesZ[i]);
+      image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+      texture.setImage(0, image2d);
+      Appearance appearance =
+        makeAppearance(mode, c_alpha, null, geometryZ[i], true);
+      appearance.setTextureAttributes(texture_attributes);
+      // WLH 17 March 2000
+      if (transparencyMode == TransparencyAttributes.FASTEST) {
+        texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+        texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+      }
+      else {
+        texture.setBoundaryModeS(Texture.CLAMP);
+        texture.setBoundaryModeT(Texture.CLAMP);
+        texture.setMinFilter(Texture.BASE_LEVEL_LINEAR);
+        texture.setMagFilter(Texture.BASE_LEVEL_LINEAR);
+      }
+      texture.setEnable(true);
+      appearance.setTexture(texture);
+      appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+      shapeZ[i] = new Shape3D(geometryZ[i], appearance);
+      shapeZ[i].setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shapeZ[i].setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      branchZrev.addChild(shapeZ[i]);
+    }
+    shapeZ = null;
+
+    Switch swit = (Switch) makeSwitch();
+    swit.addChild(branchX);
+    swit.addChild(branchY);
+    swit.addChild(branchZ);
+    swit.addChild(branchXrev);
+    swit.addChild(branchYrev);
+    swit.addChild(branchZrev);
+
+    // WLH 6 April 2000
+    // ((Group) group).addChild(swit);
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    branch.addChild(swit);
+    if (((Group) group).numChildren() > 0) {
+      ((Group) group).setChild(branch, 0);
+    }
+    else {
+      ((Group) group).addChild(branch);
+    }
+
+    ProjectionControlJ3D control =
+      (ProjectionControlJ3D) display.getProjectionControl();
+    control.addPair(swit, renderer);
+  }
+
+/*
+  GeometryArray[] makeGeometrys(VisADGeometryArray array)
+                  throws VisADException {
+    int count = array.vertexCount;
+    int depth = count / 4;
+    int color_length = array.colors.length / count;
+    int tex_length = array.texCoords.length / count;
+
+    GeometryArray[] geometrys = new GeometryArray[depth];
+    for (int d=0; d<depth; d++) {
+      int i12 = d * 4 * 3;
+      int i4c = d * 4 * color_length;
+      int i4t = d * 4 * tex_length;
+      VisADQuadArray qarray = new VisADQuadArray();
+      qarray.vertexCount = 4;
+      qarray.coordinates = new float[12];
+      qarray.texCoords = new float[tex_length * 4];
+      qarray.colors = new byte[color_length * 4];
+      qarray.normals = new float[12];
+      for (int i=0; i<12; i++) {
+        qarray.coordinates[i] = array.coordinates[i12 + i];
+        qarray.normals[i] = array.normals[i12 + i];
+      }
+      for (int i=0; i<4*color_length; i++) {
+        qarray.colors[i] = array.colors[i4c + i];
+      }
+      for (int i=0; i<4*tex_length; i++) {
+        qarray.texCoords[i] = array.texCoords[i4t + i];
+      }
+      geometrys[d] = display.makeGeometry(qarray);
+    }
+    return geometrys;
+  }
+*/
+
+  public GeometryArray[] makeGeometrys(VisADGeometryArray array)
+                  throws VisADException {
+    int count = array.vertexCount;
+    int depth = count / 4;
+    VisADGeometryArray[] qarrays = makeVisADGeometrys(array);
+    GeometryArray[] geometrys = new GeometryArray[depth];
+    for (int d=0; d<depth; d++) {
+      geometrys[d] = display.makeGeometry(qarrays[d]);
+    }
+    return geometrys;
+  }
+
+  public VisADGeometryArray[] makeVisADGeometrys(VisADGeometryArray array)
+                  throws VisADException {
+    int count = array.vertexCount;
+    int depth = count / 4;
+    int color_length = array.colors.length / count;
+    int tex_length = array.texCoords.length / count;
+
+    VisADGeometryArray[] geometrys = new VisADGeometryArray[depth];
+    for (int d=0; d<depth; d++) {
+      int i12 = d * 4 * 3;
+      int i4c = d * 4 * color_length;
+      int i4t = d * 4 * tex_length;
+      VisADQuadArray qarray = new VisADQuadArray();
+      qarray.vertexCount = 4;
+      qarray.coordinates = new float[12];
+      qarray.texCoords = new float[tex_length * 4];
+      qarray.colors = new byte[color_length * 4];
+      qarray.normals = new float[12];
+      for (int i=0; i<12; i++) {
+        qarray.coordinates[i] = array.coordinates[i12 + i];
+        qarray.normals[i] = array.normals[i12 + i];
+      }
+      for (int i=0; i<4*color_length; i++) {
+        qarray.colors[i] = array.colors[i4c + i];
+      }
+      for (int i=0; i<4*tex_length; i++) {
+        qarray.texCoords[i] = array.texCoords[i4t + i];
+      }
+      geometrys[d] = qarray;
+    }
+    return geometrys;
+  }
+
+  public Object makeSwitch() {
+    Switch swit = new Switch();
+    swit.setCapability(Switch.ALLOW_SWITCH_READ);
+    swit.setCapability(Switch.ALLOW_SWITCH_WRITE);
+    swit.setCapability(BranchGroup.ALLOW_DETACH);
+    swit.setCapability(Group.ALLOW_CHILDREN_READ);
+    swit.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    return swit;
+  }
+
+  public Object makeSwitch(int length) throws VisADException {
+    Switch swit = (Switch)makeSwitch();
+
+//  -TDR
+    swit.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+    for (int i=0; i<length; i++) {
+      BranchGroup node = new BranchGroup();
+      node.setCapability(BranchGroup.ALLOW_DETACH);
+      node.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
+      node.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+      node.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
+      ensureNotEmpty(node);
+      addToSwitch(swit, node);
+    }
+    return swit;
+  }
+  
+  public Object makeBranch() {
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    return branch;
+  }
+
+  public void addToGroup(Object group, Object branch)
+         throws VisADException {
+/* WLH 18 Aug 98
+   empty BranchGroup or Shape3D may cause NullPointerException
+   from Shape3DRetained.setLive
+*/
+    ensureNotEmpty((BranchGroup) branch);
+    ((BranchGroup) group).addChild((BranchGroup) branch);
+  }
+
+  public void addToSwitch(Object swit, Object branch)
+         throws VisADException {
+/* WLH 18 Aug 98
+   empty BranchGroup or Shape3D may cause NullPointerException
+   from Shape3DRetained.setLive
+*/
+    ensureNotEmpty((BranchGroup) branch);
+    ((Switch) swit).addChild((BranchGroup) branch);
+  }
+
+  public void addSwitch(Object group, Object swit, Control control,
+                        Set domain_set, DataRenderer renderer)
+         throws VisADException {
+    ((AVControlJ3D) control).addPair((Switch) swit, domain_set, renderer);
+    ((AVControlJ3D) control).init();
+    // WLH 06 Feb 06 - fix problem adding a new switch to an existing group
+    // ((Group) group).addChild((Switch) swit);
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    branch.addChild((Switch) swit);
+    ((Group) group).addChild(branch);
+
+  }
+
+  public boolean recurseRange(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    return Range.doTransform(group, data, value_array,
+                             default_values, renderer);
+  }
+
+  public boolean wantIndexed() {
+/* doesn't seem to matter to memory use
+    return true;
+*/
+    return false;
+  }
+
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  public void postProcess(Object group) throws VisADException {
+    if (((ShadowFunctionOrSetType) adaptedShadowType).getFlat()) {
+      int LevelOfDifficulty = getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowFunctionOrSetTypeJ3D.postProcess");
+      }
+      else {
+        // includes !isTerminal
+        // nothing to do
+      }
+    }
+    else {
+      if (this instanceof ShadowFunctionTypeJ3D) {
+        Range.postProcess(group);
+      }
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java3d/ShadowFunctionTypeJ3D.java b/visad/java3d/ShadowFunctionTypeJ3D.java
new file mode 100644
index 0000000..3607b98
--- /dev/null
+++ b/visad/java3d/ShadowFunctionTypeJ3D.java
@@ -0,0 +1,46 @@
+//
+// ShadowFunctionTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowFunctionTypeJ3D class shadows the FunctionType class,
+   within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowFunctionTypeJ3D extends ShadowFunctionOrSetTypeJ3D {
+
+  public ShadowFunctionTypeJ3D(MathType t, DataDisplayLink link,
+                               ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+}
+
diff --git a/visad/java3d/ShadowRealTupleTypeJ3D.java b/visad/java3d/ShadowRealTupleTypeJ3D.java
new file mode 100644
index 0000000..a986c15
--- /dev/null
+++ b/visad/java3d/ShadowRealTupleTypeJ3D.java
@@ -0,0 +1,62 @@
+//
+// ShadowRealTupleTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowRealTupleTypeJ3D class shadows the RealTupleType class,
+   within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowRealTupleTypeJ3D extends ShadowTupleTypeJ3D {
+
+  public ShadowRealTupleTypeJ3D(MathType t, DataDisplayLink link,
+                                ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+        int n = ((TupleType) t).getDimension();
+    tupleComponents = new ShadowRealTypeJ3D[n];
+    ShadowRealType[] components = new ShadowRealType[n];
+    for (int i=0; i<n; i++) {
+      ShadowRealTypeJ3D shadow = (ShadowRealTypeJ3D)
+        ((TupleType) Type).getComponent(i).buildShadowType(Link, this);
+      tupleComponents[i] = shadow;
+      components[i] = (ShadowRealType) shadow.getAdaptedShadowType();
+    }
+    adaptedShadowType =
+      new ShadowRealTupleType(t, link, getAdaptedParent(parent),
+                              components, this);
+  }
+
+  public ShadowRealTupleType getReference() {
+    return ((ShadowRealTupleType) adaptedShadowType).getReference();
+  }
+
+}
+
diff --git a/visad/java3d/ShadowRealTypeJ3D.java b/visad/java3d/ShadowRealTypeJ3D.java
new file mode 100644
index 0000000..a3a32bf
--- /dev/null
+++ b/visad/java3d/ShadowRealTypeJ3D.java
@@ -0,0 +1,87 @@
+//
+// ShadowRealTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowRealTypeJ3D class shadows the RealType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowRealTypeJ3D extends ShadowScalarTypeJ3D {
+
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowRealTypeJ3D(MathType type, DataDisplayLink link,
+                           ShadowType parent)
+         throws VisADException, RemoteException {
+    super(type, link, parent);
+    adaptedShadowType =
+      new ShadowRealType(type, link, getAdaptedParent(parent));
+  }
+
+  /** clear AccumulationVector */
+  public void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+  }
+
+  /** transform data into a Java3D scene graph;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                      float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    boolean post = ((ShadowRealType) adaptedShadowType).
+                        doTransform(group, data, value_array,
+                                    default_values, renderer, this);
+    ensureNotEmpty(group);
+    return post;
+  }
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  public void postProcess(Object group) throws VisADException {
+    if (adaptedShadowType.getIsTerminal()) {
+      int LevelOfDifficulty = adaptedShadowType.getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowRealTypeJ3D.postProcess");
+      }
+      else {
+        // nothing to do
+      }
+    }
+    else {
+      // nothing to do
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java3d/ShadowScalarTypeJ3D.java b/visad/java3d/ShadowScalarTypeJ3D.java
new file mode 100644
index 0000000..a6ab525
--- /dev/null
+++ b/visad/java3d/ShadowScalarTypeJ3D.java
@@ -0,0 +1,67 @@
+//
+// ShadowScalarTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowScalarTypeJ3D class shadows the ScalarType class,
+   within a DataDisplayLink.<P>
+*/
+public abstract class ShadowScalarTypeJ3D extends ShadowTypeJ3D {
+
+  public ShadowScalarTypeJ3D(MathType type, DataDisplayLink link,
+                           ShadowType parent)
+         throws VisADException, RemoteException {
+    super(type, link, parent);
+  }
+
+  public boolean getMappedDisplayScalar() {
+    return adaptedShadowType.getMappedDisplayScalar();
+  }
+
+  public DisplayTupleType getDisplaySpatialTuple() {
+    return ((ShadowScalarType) adaptedShadowType).getDisplaySpatialTuple();
+  }
+
+  public int[] getDisplaySpatialTupleIndex() {
+    return ((ShadowScalarType) adaptedShadowType).getDisplaySpatialTupleIndex();
+  }
+
+  public int getIndex() {
+    return ((ShadowScalarType) adaptedShadowType).getIndex();
+  }
+
+  public Vector getSelectedMapVector() {
+    return ((ShadowScalarType) adaptedShadowType).getSelectedMapVector();
+  }
+
+}
+
diff --git a/visad/java3d/ShadowSetTypeJ3D.java b/visad/java3d/ShadowSetTypeJ3D.java
new file mode 100644
index 0000000..93d737b
--- /dev/null
+++ b/visad/java3d/ShadowSetTypeJ3D.java
@@ -0,0 +1,46 @@
+//
+// ShadowSetTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+   The ShadowSetTypeJ3D class shadows the SetType class,
+   within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowSetTypeJ3D extends ShadowFunctionOrSetTypeJ3D {
+
+  public ShadowSetTypeJ3D(MathType t, DataDisplayLink link,
+                          ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+  }
+
+}
+
diff --git a/visad/java3d/ShadowTextTypeJ3D.java b/visad/java3d/ShadowTextTypeJ3D.java
new file mode 100644
index 0000000..d32ac5b
--- /dev/null
+++ b/visad/java3d/ShadowTextTypeJ3D.java
@@ -0,0 +1,87 @@
+//
+// ShadowTextTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowTextTypeJ3D class shadows the TextType class,
+   within a DataDisplayLink, under Java3D.<P>
+*/
+public class ShadowTextTypeJ3D extends ShadowScalarTypeJ3D {
+
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowTextTypeJ3D(MathType t, DataDisplayLink link,
+                           ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+    adaptedShadowType =
+      new ShadowTextType(t, link, getAdaptedParent(parent));
+  }
+
+  /** clear AccumulationVector */
+  public void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+  }
+
+  /** transform data into a Java3D scene graph;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                      float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    boolean post = ((ShadowTextType) adaptedShadowType).
+                        doTransform(group, data, value_array,
+                                    default_values, renderer, this);
+    ensureNotEmpty(group);
+    return post;
+  }
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  public void postProcess(Object group) throws VisADException {
+    if (adaptedShadowType.getIsTerminal()) {
+      int LevelOfDifficulty = adaptedShadowType.getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowTextTypeJ3D.postProcess");
+      }
+      else {
+        // nothing to do
+      }
+    }
+    else {
+      // nothing to do
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java3d/ShadowTupleTypeJ3D.java b/visad/java3d/ShadowTupleTypeJ3D.java
new file mode 100644
index 0000000..aa09730
--- /dev/null
+++ b/visad/java3d/ShadowTupleTypeJ3D.java
@@ -0,0 +1,137 @@
+//
+// ShadowTupleTypeJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+
+import java.util.*;
+import java.rmi.*;
+
+/**
+   The ShadowTupleTypeJ3D class shadows the TupleType class,
+   within a DataDisplayLink.<P>
+*/
+public class ShadowTupleTypeJ3D extends ShadowTypeJ3D {
+
+  ShadowTypeJ3D[] tupleComponents;
+  private Vector AccumulationVector = new Vector();
+
+  public ShadowTupleTypeJ3D(MathType t, DataDisplayLink link,
+                            ShadowType parent)
+         throws VisADException, RemoteException {
+    super(t, link, parent);
+    if (this instanceof ShadowRealTupleTypeJ3D) return;
+
+    int n = ((TupleType) t).getDimension();
+    tupleComponents = new ShadowTypeJ3D[n];
+    ShadowType[] components = new ShadowType[n];
+    for (int i=0; i<n; i++) {
+      ShadowTypeJ3D shadow = (ShadowTypeJ3D)
+        ((TupleType) Type).getComponent(i).buildShadowType(Link, this);
+      tupleComponents[i] = shadow;
+      components[i] = shadow.getAdaptedShadowType();
+    }
+    adaptedShadowType =
+      new ShadowTupleType(t, link, getAdaptedParent(parent),
+                          components);
+  }
+
+  /** get number of components */
+  public int getDimension() {
+    return tupleComponents.length;
+  }
+
+  public ShadowType getComponent(int i) {
+    return tupleComponents[i];
+  }
+
+  boolean isFlat() {
+    return ((ShadowTupleType) adaptedShadowType).isFlat();
+  }
+
+  /** clear AccumulationVector */
+  public void preProcess() throws VisADException {
+    AccumulationVector.removeAllElements();
+/*
+    for (int i=0; i<num_components; i++) {
+      component_type.preProcess();
+    }
+*/
+  }
+
+  /** transform data into a Java3D scene graph;
+      return true if need post-process */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+                             float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    boolean post = ((ShadowTupleType) adaptedShadowType).
+                        doTransform(group, data, value_array,
+                                    default_values, renderer, this);
+    ensureNotEmpty(group);
+    return post;
+  }
+
+  public boolean recurseComponent(int i, Object group, Data data,
+             float[] value_array, float[] default_values, DataRenderer renderer)
+         throws VisADException, RemoteException {
+    BranchGroup new_group = new BranchGroup();
+    new_group.setCapability(BranchGroup.ALLOW_DETACH);
+    new_group.setCapability(Group.ALLOW_CHILDREN_READ);
+    new_group.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    new_group.setCapability(Group.ALLOW_CHILDREN_EXTEND);
+    ((Group) group).addChild(new_group);
+    return tupleComponents[i].doTransform(new_group, data, value_array,
+                                          default_values, renderer);
+  }
+
+  /** render accumulated Vector of value_array-s to
+      and add to group; then clear AccumulationVector */
+  public void postProcess(Object group) throws VisADException {
+    if (adaptedShadowType.getIsTerminal()) {
+      int LevelOfDifficulty = adaptedShadowType.getLevelOfDifficulty();
+      if (LevelOfDifficulty == LEGAL) {
+        throw new UnimplementedException("terminal LEGAL unimplemented: " +
+                                         "ShadowTupleTypeJ3D.postProcess");
+      }
+      else {
+        // nothing to do
+      }
+    }
+    else {
+/*
+      for (int i=0; i<num_components; i++) {
+        component_type.postProcess(group);
+      }
+*/
+    }
+    AccumulationVector.removeAllElements();
+  }
+
+}
+
diff --git a/visad/java3d/ShadowTypeJ3D.java b/visad/java3d/ShadowTypeJ3D.java
new file mode 100644
index 0000000..75f021b
--- /dev/null
+++ b/visad/java3d/ShadowTypeJ3D.java
@@ -0,0 +1,1709 @@
+//
+// ShadowTypeJ3D.java
+//
+
+/*
+ VisAD system for interactive analysis and visualization of numerical
+ data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+ Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ Tommy Jasmin.
+
+ 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., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+
+import javax.vecmath.*;
+
+import java.util.Vector;
+
+import java.rmi.*;
+
+/**
+ * The ShadowTypeJ3D hierarchy shadows the MathType hierarchy, within a
+ * DataDisplayLink, under Java3D.
+ * <P>
+ */
+public abstract class ShadowTypeJ3D extends ShadowType {
+
+  /** This holds the cached java3d Appearances */
+  private static java.util.Hashtable appearanceCache = new java.util.Hashtable();
+
+  /** This holds the cached java3d ColoringAttributes */
+  private static java.util.Hashtable coloringAttributesCache = new java.util.Hashtable();
+
+  /** This holds the cached java3d TransparencyAttributes */
+  private static java.util.Hashtable transparencyAttributesCache = new java.util.Hashtable();
+
+  /** Do we try to cache appearances */
+  private boolean cacheAppearances = false;
+
+  /**
+   * Do we try to merge Geometries into existings Shape3D scene graph components
+   */
+  private boolean mergeShapes = false;
+
+  /** For logging the number of Appearance objects created */
+  public static int appearanceCnt = 0;
+
+  /** For logging the number of Shape3D objects created */
+  public static int shape3DCnt = 0;
+
+  /** basic information about this ShadowTypeJ3D */
+  MathType Type; // MathType being shadowed
+
+  /**  */
+  transient DataDisplayLink Link;
+
+  /**  */
+  transient DisplayImplJ3D display;
+
+  /**  */
+  transient private Data data; // from Link.getData()
+
+  /**  */
+  private ShadowTypeJ3D Parent;
+
+  // String and TextControl to pass on to children
+
+  /**  */
+  String inheritedText = null;
+
+  /**  */
+  TextControl inheritedTextControl = null;
+
+  /**  */
+  ShadowType adaptedShadowType;
+
+  /**  */
+  ProjectionControlListener projListener = null;
+
+  /**
+   * Create a new ShadowTypeJ3D
+   *
+   * @param type
+   *          MathType of the data
+   * @param link
+   *          the data/display link
+   * @param parent
+   *          parent ShadowType
+   *
+   * @throws RemoteException
+   *           problem creating remote instance
+   * @throws VisADException
+   *           problem creating local instance
+   */
+  public ShadowTypeJ3D(MathType type, DataDisplayLink link, ShadowType parent)
+      throws VisADException, RemoteException {
+    super(type, link, getAdaptedParent(parent));
+    Type = type;
+    Link = link;
+    display = (DisplayImplJ3D) link.getDisplay();
+    Parent = (ShadowTypeJ3D) parent;
+    data = link.getData();
+  }
+
+  /**
+   * Get the adapted ShadowType for the parent
+   *
+   * @param parent
+   *          the parent object to test
+   *
+   * @return the adapted ShadowType
+   */
+  public static ShadowType getAdaptedParent(ShadowType parent) {
+    if (parent == null)
+      return null;
+    else
+      return parent.getAdaptedShadowType();
+  }
+
+  /**
+   * Get the adapted ShadowType for this instance
+   *
+   * @return the adapted ShadowType
+   */
+  public ShadowType getAdaptedShadowType() {
+    return adaptedShadowType;
+  }
+
+  /**
+   *
+   *
+   * @param type
+   * @param doRef
+   *
+   * @return component array
+   *
+   * @throws VisADException
+   */
+  
+  public ShadowRealType[] getComponents(ShadowType type, boolean doRef)
+      throws VisADException {
+    return adaptedShadowType.getComponents(type, doRef);
+  }
+
+  /**
+   *
+   *
+   * @return inherited text
+   */
+  
+  public String getParentText() {
+    if (Parent != null && Parent.inheritedText != null
+        && Parent.inheritedTextControl != null) {
+      return Parent.inheritedText;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   *
+   *
+   * @return inherited text control
+   */
+  public TextControl getParentTextControl() {
+    if (Parent != null && Parent.inheritedText != null
+        && Parent.inheritedTextControl != null) {
+      return Parent.inheritedTextControl;
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   *
+   *
+   * @param text
+   * @param control
+   */
+  public void setText(String text, TextControl control) {
+    inheritedText = text;
+    inheritedTextControl = control;
+  }
+
+  /**
+   * Get the data
+   *
+   * @return the data
+   */
+  public Data getData() {
+    return data;
+  }
+
+  /**
+   * Get the display
+   *
+   * @return the display
+   */
+  public DisplayImpl getDisplay() {
+    return display;
+  }
+
+  /**
+   * Get the MathType of the Data
+   *
+   * @return the MathType
+   */
+  public MathType getType() {
+    return Type;
+  }
+
+  /**
+   * Get the level of difficulty for this transform
+   *
+   * @return the level of difficulty
+   */
+  public int getLevelOfDifficulty() {
+    return adaptedShadowType.getLevelOfDifficulty();
+  }
+
+  /**
+   *
+   *
+   * @return boolean TBD
+   */
+  public boolean getMultipleDisplayScalar() {
+    return adaptedShadowType.getMultipleDisplayScalar();
+  }
+
+  /**
+   *
+   *
+   * @return boolean TBD
+   */
+  public boolean getMappedDisplayScalar() {
+    return adaptedShadowType.getMappedDisplayScalar();
+  }
+
+  /**
+   *
+   *
+   * @return int array display indices
+   */
+  public int[] getDisplayIndices() {
+    return adaptedShadowType.getDisplayIndices();
+  }
+
+  /**
+   *
+   *
+   * @return int array value indices
+   */
+  public int[] getValueIndices() {
+    return adaptedShadowType.getValueIndices();
+  }
+
+  /**
+   * checkIndices: check for rendering difficulty, etc
+   *
+   * @param indices
+   * @param display_indices
+   * @param value_indices
+   * @param isTransform
+   * @param levelOfDifficulty
+   *
+   * @return num occcurrences RealType and DisplayRealType
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  public int checkIndices(int[] indices, int[] display_indices,
+      int[] value_indices, boolean[] isTransform, int levelOfDifficulty)
+      throws VisADException, RemoteException {
+    return adaptedShadowType.checkIndices(indices, display_indices,
+        value_indices, isTransform, levelOfDifficulty);
+  }
+
+  /**
+   * clear AccumulationVector
+   *
+   * @throws VisADException
+   */
+  public void preProcess() throws VisADException {
+  }
+
+  /**
+   * transform data into a Java3D scene graph; add generated scene graph
+   * components as children of group; value_array are inherited valueArray
+   * values; default_values are defaults for each display.DisplayRealTypeVector;
+   * return true if need post-process; this is default (for ShadowTextType)
+   *
+   * @param group
+   *          group to add to
+   * @param data
+   *          the data to transform
+   * @param value_array
+   *          the values
+   * @param default_values
+   *          the default values
+   * @param renderer
+   *          the renderer
+   *
+   * @return false
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  public boolean doTransform(Object group, Data data, float[] value_array,
+      float[] default_values, DataRenderer renderer) throws VisADException,
+      RemoteException {
+    return false;
+  }
+
+  /**
+   * render accumulated Vector of value_array-s to and add to group; then clear
+   * AccumulationVector
+   *
+   * @param group
+   *
+   * @throws VisADException
+   */
+  public void postProcess(Object group) throws VisADException {
+  }
+
+  /* helpers for doTransform */
+
+  /**
+   * map values into display_values according to ScalarMap-s in reals
+   *
+   * @param display_values
+   * @param values
+   * @param reals
+   *
+   * @throws VisADException
+   */
+  public static void mapValues(float[][] display_values, double[][] values,
+      ShadowRealType[] reals) throws VisADException {
+    ShadowType.mapValues(display_values, values, reals);
+  }
+
+  /**
+   * map values into display_values according to ScalarMap-s in reals
+   *
+   * @param display_values
+   * @param values
+   * @param reals
+   *
+   * @throws VisADException
+   */
+  public static void mapValues(float[][] display_values, float[][] values,
+      ShadowRealType[] reals) throws VisADException {
+    mapValues(display_values, values, reals, true);
+  }
+
+  /**
+   * map values into display_values according to ScalarMap-s in reals
+   *
+   * @param display_values
+   * @param values
+   * @param reals
+   * @param copy
+   *
+   * @throws VisADException
+   */
+  public static void mapValues(float[][] display_values, float[][] values,
+      ShadowRealType[] reals, boolean copy) throws VisADException {
+    ShadowType.mapValues(display_values, values, reals, copy);
+  }
+
+  /**
+   *
+   *
+   * @param spatial_values
+   * @param color_values
+   *
+   * @return point geometry array
+   *
+   * @throws VisADException
+   */
+  public static VisADGeometryArray makePointGeometry(float[][] spatial_values,
+      byte[][] color_values) throws VisADException {
+    return ShadowType.makePointGeometry(spatial_values, color_values);
+  }
+
+  /**
+   * Make an Appearance object for this display
+   *
+   * @param mode
+   *          GraphicsModeControl
+   * @param constant_alpha
+   *          the constant alpha value
+   * @param constant_color
+   *          the constant color value
+   * @param geometry
+   *          the GeometryArray
+   * @param no_material
+   *          flag for material
+   *
+   * @return appearance object constructed from geometry array
+   */
+  
+  public Appearance makeAppearance(GraphicsModeControl mode,
+      TransparencyAttributes constant_alpha, ColoringAttributes constant_color,
+      GeometryArray geometry, boolean no_material) {
+    return ShadowTypeJ3D.staticMakeCachedAppearance(mode, constant_alpha,
+        constant_color, geometry, no_material, false);
+  }
+
+  /**
+   * Make an Appearance that may be cached or not, depending on the okToCache
+   * and the cache flag
+   *
+   * @param mode
+   *          GraphicsModeControl
+   * @param constant_alpha
+   *          the constant alpha value
+   * @param constant_color
+   *          the constant color value
+   * @param geometry
+   *          the GeometryArray
+   * @param no_material
+   *          flag for material
+   * @param okToCache
+   *          flag for caching checked with mode.getCacheAppearances
+   *
+   * @return appearance object constructed from geometry array
+   */
+  
+  private Appearance makeCachedAppearance(GraphicsModeControl mode,
+      TransparencyAttributes constant_alpha, ColoringAttributes constant_color,
+      GeometryArray geometry, boolean no_material, boolean okToCache) {
+    return ShadowTypeJ3D.staticMakeCachedAppearance(mode, constant_alpha,
+        constant_color, geometry, no_material, okToCache);
+  }
+
+  /**
+   * A utility method to add the given geometry into the given group with the
+   * given appearance. If the mergeShapes flag is true then this will look for
+   * an existing child Shape3D node of the group that has the same Appearance
+   * and contains geometries of the same type. If found it will add the geometry
+   * to that Shape3D node. If not found or if mergeShapes is false then this
+   * behaves normally, creating a new Shape3D node and adding it to the group.
+   *
+   * @param group
+   *          group to add to
+   * @param geometry
+   *          geometry to add
+   * @param appearance
+   *          appearance
+   */
+  private void addToShape(Group group, GeometryArray geometry,
+      Appearance appearance) {
+    Shape3D shape = null;
+    if (mergeShapes) {
+      int cnt = group.numChildren();
+      for (int i = 0; i < cnt; i++) {
+        Node node = group.getChild(i);
+        if (!(node instanceof Shape3D))
+          continue;
+        Shape3D s = (Shape3D) node;
+        // Make sure the geometries are the same
+        int subcnt = s.numGeometries();
+        if (subcnt > 0) {
+          if (!(s.getGeometry(0).getClass().equals(geometry.getClass()))) {
+            continue;
+          }
+        }
+        // Make sure the appearance is the same
+        if (s.getAppearance().equals(appearance)) {
+          shape = s;
+          break;
+        }
+      }
+    }
+
+    if (shape == null) {
+      shape = new Shape3D(geometry, appearance);
+      shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      group.addChild(shape);
+      shape3DCnt++;
+    } else {
+      shape.addGeometry(geometry);
+    }
+  }
+
+  /**
+   * A utility method to create a ColoringAttribute with the given colors If the
+   * cacheAppearances flag is true then this will try to find the
+   * ColoringAttribute in the cache. If false it just creates a new one. Note:
+   * If caching is on then the result object should be treated as being
+   * immutable
+   *
+   * @param red
+   *          red value
+   * @param green
+   *          green value
+   * @param blue
+   *          blue value
+   *
+   * @return the ColoringAttributes
+   */
+  private ColoringAttributes getColoringAttributes(float red, float green,
+      float blue) {
+    ColoringAttributes ca = null;
+    String key = null;
+    if (cacheAppearances) {
+      key = red + "," + green + "," + blue;
+      ca = (ColoringAttributes) coloringAttributesCache.get(key);
+    }
+    if (ca == null) {
+      ca = new ColoringAttributes(red, green, blue,
+          ColoringAttributes.SHADE_GOURAUD);
+      ca.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+      ca.setCapability(ColoringAttributes.ALLOW_SHADE_MODEL_READ);
+      if (cacheAppearances) {
+        coloringAttributesCache.put(key, ca);
+      }
+    }
+    return ca;
+  }
+
+  /**
+   * A utility method to create a TransparencyAttributes with the given mode and
+   * value. If the flag cacheAppearances is true then this will try to find the
+   * TransparencyAttributes in the cache. If false it just creates a new one.
+   * Note: If caching is on then the result object should be treated as being
+   * immutable
+   *
+   * @param mode
+   *          Transparency mode
+   * @param value
+   *          transparancy value
+   *
+   * @return cached or new TransparencyAttributes object
+   */
+  
+  private TransparencyAttributes getTransparencyAttributes(int mode, float value) {
+    String key = null;
+    TransparencyAttributes ta = null;
+    if (cacheAppearances) {
+      key = mode + "_" + value;
+      ta = (TransparencyAttributes) transparencyAttributesCache.get(key);
+    }
+    if (ta == null) {
+      ta = new TransparencyAttributes(mode, value);
+      ta.setCapability(TransparencyAttributes.ALLOW_MODE_READ);
+      ta.setCapability(TransparencyAttributes.ALLOW_VALUE_READ);
+      if (cacheAppearances) {
+        transparencyAttributesCache.put(key, ta);
+      }
+    }
+    return ta;
+  }
+
+  /**
+   * Construct an Appearance object from a GeometryArray
+   *
+   * @param mode
+   *          GraphicsModeControl
+   * @param constant_alpha
+   *          transparency attributes
+   * @param constant_color
+   *          color to use
+   * @param geometry
+   *          geometry to use for the appearance
+   * @param no_material
+   *          true to not use a Material for illumination, false to use it for
+   *          2-D geometries
+   *
+   * @return The new appearance
+   */
+  public static Appearance staticMakeAppearance(GraphicsModeControl mode,
+      TransparencyAttributes constant_alpha, ColoringAttributes constant_color,
+      GeometryArray geometry, boolean no_material) {
+
+    return staticMakeCachedAppearance(mode, constant_alpha, constant_color,
+        geometry, no_material, false);
+  }
+
+  /**
+   * Construct an Appearance object from a GeometryArray
+   *
+   * @param mode
+   *          GraphicsModeControl
+   * @param constant_alpha
+   *          transparency attributes
+   * @param constant_color
+   *          color to use
+   * @param geometry
+   *          geometry to use for the appearance
+   * @param no_material
+   *          true to not use a Material for illumination, false to use it for
+   *          2-D geometries
+   * @param okToCache
+   *          If true and if the mode's cacheAppearances flag is true then we
+   *          will use the appearance cache.
+   *
+   * @return The new appearance or, if available a previously cached one
+   */
+  private static Appearance staticMakeCachedAppearance(
+      GraphicsModeControl mode, TransparencyAttributes constant_alpha,
+      ColoringAttributes constant_color, GeometryArray geometry,
+      boolean no_material, boolean okToCache) {
+
+    boolean doMaterial = false;
+    if (!(geometry instanceof LineArray || geometry instanceof PointArray
+        || geometry instanceof IndexedLineArray
+        || geometry instanceof IndexedPointArray
+        || geometry instanceof IndexedLineStripArray || geometry instanceof LineStripArray)) {
+      if (!no_material) {
+        doMaterial = true;
+      }
+    }
+
+    Object cacheKey = null;
+    Appearance appearance = null;
+
+    if (mode.getCacheAppearances() && okToCache) {
+	cacheKey = mode.getSaveString() + "_" + (constant_alpha==null?"null" :(constant_alpha.getTransparency()+"_" + constant_alpha.getTransparencyMode()))  + "_"
+          + constant_color + "_" + new Boolean(doMaterial);
+      appearance = (Appearance) appearanceCache.get(cacheKey);
+      if (appearance != null) {
+        return appearance;
+      }
+    }
+    appearanceCnt++;
+    appearance = new Appearance();
+    appearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_MATERIAL_READ);
+    appearance.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_TEXGEN_READ);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+    /* TDR (2013-10-16): Can possibly cause problems on ATI graphics
+    appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE);
+    */
+    // appearance.setCapability(Appearance.ALLOW_TEXTURE_UNIT_STATE_READ);
+    appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
+
+    LineAttributes line = new LineAttributes();
+    line.setCapability(LineAttributes.ALLOW_ANTIALIASING_READ);
+    line.setCapability(LineAttributes.ALLOW_PATTERN_READ);
+    line.setCapability(LineAttributes.ALLOW_WIDTH_READ);
+    line.setLineWidth(mode.getLineWidth());
+    int pattern = GraphicsModeControlJ3D.LINE_PATTERN[mode.getLineStyle()];
+    line.setLinePattern(pattern);
+    appearance.setLineAttributes(line);
+
+    PointAttributes point = new PointAttributes();
+    point.setCapability(PointAttributes.ALLOW_ANTIALIASING_READ);
+    point.setCapability(PointAttributes.ALLOW_SIZE_READ);
+    point.setPointSize(mode.getPointSize());
+    appearance.setPointAttributes(point);
+
+    PolygonAttributes polygon = new PolygonAttributes();
+    polygon.setCapability(PolygonAttributes.ALLOW_CULL_FACE_READ);
+    polygon.setCapability(PolygonAttributes.ALLOW_MODE_READ);
+    polygon.setCapability(PolygonAttributes.ALLOW_NORMAL_FLIP_READ);
+    polygon.setCapability(PolygonAttributes.ALLOW_OFFSET_READ);
+    polygon.setCullFace(PolygonAttributes.CULL_NONE);
+    polygon.setPolygonMode(mode.getPolygonMode());
+
+    try {
+      float polygonOffset = mode.getPolygonOffset();
+      if (polygonOffset == polygonOffset)
+        polygon.setPolygonOffset(polygonOffset);
+    } catch (Exception e) {
+    }
+
+    // - TDR, use reflection since setPolygonOffsetFactor is not available in
+    // earlier versions of Java3D.
+
+    try {
+      java.lang.reflect.Method method = polygon.getClass().getMethod(
+          "setPolygonOffsetFactor", new Class[] { float.class });
+      float polygonOffsetFactor = mode.getPolygonOffsetFactor();
+      if (polygonOffsetFactor == polygonOffsetFactor) {
+        method.invoke(polygon, new Object[] { new Float(polygonOffsetFactor) });
+      }
+    } catch (Exception e) {
+    }
+
+    appearance.setPolygonAttributes(polygon);
+
+    RenderingAttributes rendering = new RenderingAttributes();
+    rendering.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_FUNCTION_READ);
+    rendering.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_VALUE_READ);
+    rendering.setCapability(RenderingAttributes.ALLOW_DEPTH_ENABLE_READ);
+    /* TDR (2013-10-16): Can possibly cause problems on ATI graphics
+    rendering.setCapability (RenderingAttributes.ALLOW_VISIBLE_WRITE);
+    */
+
+    //rendering.setCapability(RenderingAttributes.ALLOW_IGNORE_VERTEX_COLORS_READ
+    // );
+    // rendering.setCapability(RenderingAttributes.ALLOW_RASTER_OP_READ);
+    // rendering.setCapability(RenderingAttributes.ALLOW_VISIBLE_READ);
+    rendering.setDepthBufferEnable(((GraphicsModeControlJ3D)mode).getDepthBufferEnable());
+    appearance.setRenderingAttributes(rendering);
+
+    if (constant_color != null) {
+      // constant_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+      //constant_color.setCapability(ColoringAttributes.ALLOW_SHADE_MODEL_READ);
+      appearance.setColoringAttributes(constant_color);
+    }
+    // only do Material if geometry is 2-D (not 0-D points or 1-D lines)
+    if (doMaterial) {
+      Material material = new Material();
+      material.setCapability(Material.ALLOW_COMPONENT_READ);
+      material.setSpecularColor(0.0f, 0.0f, 0.0f);
+
+      // no lighting in 2-D mode
+      if (!mode.getMode2D())
+        material.setLightingEnable(true);
+      appearance.setMaterial(material);
+    }
+    if (constant_alpha != null) {
+      // constant_alpha.setCapability(TransparencyAttributes.
+      // ALLOW_BLEND_FUNCTION_READ);
+      // constant_alpha.setCapability(TransparencyAttributes.ALLOW_MODE_READ);
+      // constant_alpha.setCapability(TransparencyAttributes.ALLOW_VALUE_READ);
+      appearance.setTransparencyAttributes(constant_alpha);
+    }
+    if (cacheKey != null) {
+      appearanceCache.put((Object) cacheKey, appearance);
+    }
+    return appearance;
+  }
+
+
+  /*
+   * public static Appearance staticMakeAppearance(GraphicsModeControl mode,
+   * TransparencyAttributes constant_alpha, ColoringAttributes constant_color,
+   * GeometryArray geometry, boolean no_material) { Appearance appearance = new
+   * Appearance();
+   * appearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
+   * appearance.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
+   * appearance.setCapability(Appearance.ALLOW_MATERIAL_READ);
+   * appearance.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_READ);
+   * appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
+   * appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
+   * appearance.setCapability(Appearance.ALLOW_TEXGEN_READ);
+   * appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
+   * appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+   * appearance.setCapability(Appearance.ALLOW_TEXTURE_UNIT_STATE_READ);
+   * appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
+   *
+   * LineAttributes line = new LineAttributes();
+   * line.setLineWidth(mode.getLineWidth()); int pattern =
+   * GraphicsModeControlJ3D.LINE_PATTERN[mode.getLineStyle()];
+   * line.setLinePattern(pattern); appearance.setLineAttributes(line);
+   *
+   * PointAttributes point = new PointAttributes();
+   * point.setPointSize(mode.getPointSize());
+   * appearance.setPointAttributes(point);
+   *
+   * PolygonAttributes polygon = new PolygonAttributes();
+   * polygon.setCullFace(PolygonAttributes.CULL_NONE);
+   * polygon.setPolygonMode(mode.getPolygonMode());
+   *
+   * try { float polygonOffset = mode.getPolygonOffset(); if (polygonOffset ==
+   * polygonOffset) polygon.setPolygonOffset(polygonOffset); } catch (Exception
+   * e) { }
+   *
+   * appearance.setPolygonAttributes(polygon);
+   *
+   * RenderingAttributes rendering = new RenderingAttributes();
+   * rendering.setDepthBufferEnable(true);
+   * appearance.setRenderingAttributes(rendering);
+   *
+   * if (constant_color != null) {
+   * appearance.setColoringAttributes(constant_color); } // only do Material if
+   * geometry is 2-D (not 0-D points or 1-D lines) if (!(geometry instanceof
+   * LineArray || geometry instanceof PointArray || geometry instanceof
+   * IndexedLineArray || geometry instanceof IndexedPointArray || geometry
+   * instanceof IndexedLineStripArray || geometry instanceof LineStripArray)) {
+   * if (!no_material) { Material material = new Material();
+   * material.setSpecularColor(0.0f, 0.0f, 0.0f); // no lighting in 2-D mode if
+   * (!mode.getMode2D()) material.setLightingEnable(true);
+   * appearance.setMaterial(material); //TEST
+   * appearance.setCapability(Appearance.ALLOW_MATERIAL_READ); } if
+   * (constant_alpha != null) {
+   * appearance.setTransparencyAttributes(constant_alpha); } }
+   *
+   * return appearance; }
+   */
+
+  /**
+   * collect and transform Shape DisplayRealType values from display_values;
+   * offset by spatial_values, selected by range_select
+   *
+   * @param display_values
+   * @param valueArrayLength
+   * @param valueToMap
+   * @param MapVector
+   * @param valueToScalar
+   * @param display
+   * @param default_values
+   * @param inherited_values
+   * @param spatial_values
+   * @param color_values
+   * @param range_select
+   * @param index
+   * @param shadow_api
+   *
+   * @return transformed Shape values
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  
+  public VisADGeometryArray[] assembleShape(float[][] display_values,
+      int valueArrayLength, int[] valueToMap, Vector MapVector,
+      int[] valueToScalar, DisplayImpl display, float[] default_values,
+      int[] inherited_values, float[][] spatial_values, byte[][] color_values,
+      boolean[][] range_select, int index, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+    return adaptedShadowType.assembleShape(display_values, valueArrayLength,
+        valueToMap, MapVector, valueToScalar, display, default_values,
+        inherited_values, spatial_values, color_values, range_select, index,
+        shadow_api);
+  }
+
+  /**
+   * collect and transform spatial DisplayRealType values from display_values;
+   * add spatial offset DisplayRealType values; adjust flow1_values and
+   * flow2_values for any coordinate transform; if needed, return a spatial Set
+   * from spatial_values, with the same topology as domain_set (or an
+   * appropriate Irregular topology); domain_set = null and allSpatial = false
+   * if not called from ShadowFunctionType
+   *
+   * @param spatial_values
+   * @param display_values
+   * @param valueArrayLength
+   * @param valueToScalar
+   * @param display
+   * @param default_values
+   * @param inherited_values
+   * @param domain_set
+   * @param allSpatial
+   * @param set_for_shape
+   * @param spatialDimensions
+   * @param range_select
+   * @param flow1_values
+   * @param flow2_values
+   * @param flowScale
+   * @param swap
+   * @param renderer
+   * @param shadow_api
+   *
+   * @return spatial Set from spatial values
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  
+  public Set assembleSpatial(float[][] spatial_values,
+      float[][] display_values, int valueArrayLength, int[] valueToScalar,
+      DisplayImpl display, float[] default_values, int[] inherited_values,
+      Set domain_set, boolean allSpatial, boolean set_for_shape,
+      int[] spatialDimensions, boolean[][] range_select,
+      float[][] flow1_values, float[][] flow2_values, float[] flowScale,
+      boolean[] swap, DataRenderer renderer, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+    return adaptedShadowType.assembleSpatial(spatial_values, display_values,
+        valueArrayLength, valueToScalar, display, default_values,
+        inherited_values, domain_set, allSpatial, set_for_shape,
+        spatialDimensions, range_select, flow1_values, flow2_values, flowScale,
+        swap, renderer, shadow_api);
+  }
+
+  /**
+   * assemble Flow components; Flow components are 'single', so no compositing
+   * is required
+   *
+   * @param flow1_values
+   * @param flow2_values
+   * @param flowScale
+   * @param display_values
+   * @param valueArrayLength
+   * @param valueToScalar
+   * @param display
+   * @param default_values
+   * @param range_select
+   * @param renderer
+   * @param shadow_api
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  public void assembleFlow(float[][] flow1_values, float[][] flow2_values,
+      float[] flowScale, float[][] display_values, int valueArrayLength,
+      int[] valueToScalar, DisplayImpl display, float[] default_values,
+      boolean[][] range_select, DataRenderer renderer, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+    adaptedShadowType.assembleFlow(flow1_values, flow2_values, flowScale,
+        display_values, valueArrayLength, valueToScalar, display,
+        default_values, range_select, renderer, shadow_api);
+  }
+
+  /**
+   *
+   *
+   * @param which
+   * @param flow_values
+   * @param flowScale
+   * @param spatial_values
+   * @param color_values
+   * @param range_select
+   *
+   * @return flow geometry array
+   *
+   * @throws VisADException
+   */
+  
+  public VisADGeometryArray[] makeFlow(int which, float[][] flow_values,
+      float flowScale, float[][] spatial_values, byte[][] color_values,
+      boolean[][] range_select) throws VisADException {
+    return adaptedShadowType.makeFlow(which, flow_values, flowScale,
+        spatial_values, color_values, range_select);
+  }
+
+  /**
+   *
+   *
+   * @param which
+   * @param flow_values
+   * @param flowScale
+   * @param spatial_values
+   * @param spatial_set
+   * @param spatialManifoldDimension
+   * @param color_values
+   * @param range_select
+   * @param valueArrayLength
+   * @param valueToMap
+   * @param MapVector
+   *
+   * @return streamline geometry array
+   *
+   * @throws VisADException
+   */
+  
+  public VisADGeometryArray[] makeStreamline(int which, float[][] flow_values,
+      float flowScale, float[][] spatial_values, Set spatial_set,
+      int spatialManifoldDimension, byte[][] color_values,
+      boolean[][] range_select, int valueArrayLength, int[] valueToMap,
+      Vector MapVector) throws VisADException {
+    return adaptedShadowType.makeStreamline(which, flow_values, flowScale,
+        spatial_values, spatial_set, spatialManifoldDimension, color_values,
+        range_select, valueArrayLength, valueToMap, MapVector);
+  }
+
+  /**
+   *
+   *
+   * @param valueArrayLength
+   * @param valueToScalar
+   * @param display_values
+   * @param inherited_values
+   * @param MapVector
+   * @param valueToMap
+   * @param domain_length
+   * @param range_select
+   * @param spatialManifoldDimension
+   * @param spatial_set
+   * @param color_values
+   * @param indexed
+   * @param group
+   * @param mode
+   * @param swap
+   * @param constant_alpha
+   * @param constant_color
+   * @param shadow_api
+   * @param Domain
+   * @param DomainReferenceComponents
+   * @param domain_set
+   * @param domain_units
+   * @param dataCoordinateSystem
+   *
+   * @return true if any contours created
+   *
+   * @throws VisADException
+   */
+  
+  public boolean makeContour(int valueArrayLength, int[] valueToScalar,
+      float[][] display_values, int[] inherited_values, Vector MapVector,
+      int[] valueToMap, int domain_length, boolean[][] range_select,
+      int spatialManifoldDimension, Set spatial_set, byte[][] color_values,
+      boolean indexed, Object group, GraphicsModeControl mode, boolean[] swap,
+      float constant_alpha, float[] constant_color, ShadowType shadow_api,
+      ShadowRealTupleType Domain, ShadowRealType[] DomainReferenceComponents,
+      Set domain_set, Unit[] domain_units, CoordinateSystem dataCoordinateSystem)
+      throws VisADException {
+    return adaptedShadowType.makeContour(valueArrayLength, valueToScalar,
+        display_values, inherited_values, MapVector, valueToMap, domain_length,
+        range_select, spatialManifoldDimension, spatial_set, color_values,
+        indexed, group, mode, swap, constant_alpha, constant_color, shadow_api,
+        Domain, DomainReferenceComponents, domain_set, domain_units,
+        dataCoordinateSystem);
+  }
+
+  private Object MUTEX = new Object();
+
+  public void addLabelsToGroup(Object group, VisADGeometryArray[] arrays,
+      GraphicsModeControl mode, ContourControl control,
+      ProjectionControl p_cntrl, int[] cnt_a, float constant_alpha,
+      float[] constant_color) throws VisADException {
+
+      //Make this thread safe
+      synchronized(MUTEX) {
+    int cnt = cnt_a[0];
+
+    if (cnt == 0) {
+      projListener = new ProjectionControlListener(p_cntrl, control);
+    }
+
+    int n_labels = arrays.length;
+
+    // add the stretchy line segments if we are not filling
+    if (!control.contourFilled() && arrays != null) {
+      projListener.LT_array[cnt] = new LabelTransform[3][n_labels];
+
+      GraphicsModeControl styledMode = (GraphicsModeControl) mode.clone();
+      styledMode.setLineStyle(control.getDashedStyle(), false);
+
+      for (int ii = 0; ii < n_labels; ii++) {
+        ContourLabelGeometry array = (ContourLabelGeometry) arrays[ii];
+
+        TransformGroup segL_trans_group = new TransformGroup();
+        TransformGroup segR_trans_group = new TransformGroup();
+
+        segL_trans_group.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
+        segL_trans_group.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
+        segL_trans_group.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
+        segR_trans_group.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
+        segR_trans_group.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
+        segR_trans_group.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
+
+        if (control.getAutoSizeLabels()) {
+
+          LabelTransform lbl_trans = new LabelTransform(segL_trans_group, p_cntrl,
+              new VisADGeometryArray[] { array.expSegLeft, array.segLeftAnchor },
+              array.segLeftScaleInfo, 1);
+          projListener.LT_array[cnt][1][ii] = lbl_trans;
+
+          lbl_trans = new LabelTransform(segR_trans_group, p_cntrl,
+              new VisADGeometryArray[] { array.expSegRight, array.segRightAnchor },
+              array.segRightScaleInfo, 1);
+          projListener.LT_array[cnt][2][ii] = lbl_trans;
+        }
+        ((Group) group).addChild(segL_trans_group);
+        ((Group) group).addChild(segR_trans_group);
+
+        if (array.isStyled) {
+          addToGroup(segL_trans_group, array.expSegLeft, styledMode,
+                     constant_alpha, constant_color);
+          addToGroup(segR_trans_group, array.expSegRight, styledMode,
+                     constant_alpha, constant_color);
+        }
+        else {
+          addToGroup(segL_trans_group, array.expSegLeft, mode,
+                     constant_alpha, constant_color);
+          addToGroup(segR_trans_group, array.expSegRight, mode,
+                     constant_alpha, constant_color);
+        }
+      }
+
+    } else {
+      projListener.LT_array[cnt] = new LabelTransform[1][n_labels];
+    }
+
+    cnt = cnt_a[0];
+
+    for (int ii = 0; ii < n_labels; ii++) {
+      TransformGroup lbl_trans_group = new TransformGroup();
+      lbl_trans_group.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
+      lbl_trans_group.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
+      lbl_trans_group.setCapability(TransformGroup.ALLOW_CHILDREN_READ);
+
+      ContourLabelGeometry array = (ContourLabelGeometry) arrays[ii];
+
+      if (control.getAutoSizeLabels()) {
+        LabelTransform lbl_trans = new LabelTransform(lbl_trans_group, p_cntrl,
+            new VisADGeometryArray[] { array.label, array.labelAnchor }, null, 0);
+        projListener.LT_array[cnt][0][ii] = lbl_trans;
+      }
+
+      ((Group) group).addChild(lbl_trans_group);
+
+      addToGroup(lbl_trans_group, array.label, mode, constant_alpha,
+          constant_color);
+    }
+    cnt++;
+    projListener.cnt = cnt;
+    cnt_a[0] = cnt;
+                               }
+  }
+
+
+  /**
+   *
+   *
+   * @param text_values
+   * @param text_control
+   * @param spatial_values
+   * @param color_values
+   * @param range_select
+   *
+   * @return text geometry array
+   *
+   * @throws VisADException
+   */
+  
+  public VisADGeometryArray makeText(String[] text_values,
+      TextControl text_control, float[][] spatial_values,
+      byte[][] color_values, boolean[][] range_select) throws VisADException {
+    return adaptedShadowType.makeText(text_values, text_control,
+        spatial_values, color_values, range_select);
+  }
+
+  /**
+   * composite and transform color and Alpha DisplayRealType values from
+   * display_values, and return as (Red, Green, Blue, Alpha)
+   *
+   * @param display_values
+   * @param valueArrayLength
+   * @param valueToScalar
+   * @param display
+   * @param default_values
+   * @param range_select
+   * @param single_missing
+   * @param shadow_api
+   *
+   * @return byte color array
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  
+  public byte[][] assembleColor(float[][] display_values, int valueArrayLength,
+      int[] valueToScalar, DisplayImpl display, float[] default_values,
+      boolean[][] range_select, boolean[] single_missing, ShadowType shadow_api)
+      throws VisADException, RemoteException {
+    return adaptedShadowType.assembleColor(display_values, valueArrayLength,
+        valueToScalar, display, default_values, range_select, single_missing,
+        shadow_api);
+  }
+
+  /**
+   * return a composite of SelectRange DisplayRealType values from
+   * display_values, as 0.0 for select and Double.Nan for no select (these
+   * values can be added to other DisplayRealType values)
+   *
+   * @param display_values
+   * @param domain_length
+   * @param valueArrayLength
+   * @param valueToScalar
+   * @param display
+   * @param shadow_api
+   *
+   * @return array of booleans as described above
+   *
+   * @throws VisADException
+   */
+  
+  public boolean[][] assembleSelect(float[][] display_values,
+      int domain_length, int valueArrayLength, int[] valueToScalar,
+      DisplayImpl display, ShadowType shadow_api) throws VisADException {
+    return adaptedShadowType.assembleSelect(display_values, domain_length,
+        valueArrayLength, valueToScalar, display, shadow_api);
+  }
+
+  /**
+   *
+   *
+   * @param group
+   * @param display_values
+   * @param text_value
+   * @param text_control
+   * @param valueArrayLength
+   * @param valueToScalar
+   * @param default_values
+   * @param inherited_values
+   * @param renderer
+   *
+   * @return true if post-processing needed
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  
+  public boolean terminalTupleOrScalar(Object group, float[][] display_values,
+      String text_value, TextControl text_control, int valueArrayLength,
+      int[] valueToScalar, float[] default_values, int[] inherited_values,
+      DataRenderer renderer) throws VisADException, RemoteException {
+
+    boolean post = adaptedShadowType.terminalTupleOrScalar(group,
+        display_values, text_value, text_control, valueArrayLength,
+        valueToScalar, default_values, inherited_values, renderer, this);
+    ensureNotEmpty(group);
+    return post;
+  }
+
+  /**
+   * this is a work-around for the NullPointerException at
+   * javax.media.j3d.Shape3DRetained.setLive(Shape3DRetained.java:448)
+   *
+   * @param obj
+   */
+  public void ensureNotEmpty(Object obj) {
+    ensureNotEmpty(obj, display);
+  }
+
+  /**
+   *
+   *
+   * @param obj
+   * @param display
+   */
+  public static void ensureNotEmpty(Object obj, DisplayImpl display) {
+    if (!(obj instanceof Group))
+      return;
+    Group group = (Group) obj;
+    if (group.numChildren() > 0)
+      return;
+    GeometryArray geometry = new PointArray(1, GeometryArray.COORDINATES
+        | GeometryArray.COLOR_3);
+    geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    float[] coordinates = new float[3];
+    coordinates[0] = 1000000.0f;
+    coordinates[1] = 1000000.0f;
+    coordinates[2] = 1000000.0f;
+    geometry.setCoordinates(0, coordinates);
+    float[] colors = new float[3];
+    colors[0] = 0.0f;
+    colors[1] = 0.0f;
+    colors[2] = 0.0f;
+    geometry.setColors(0, colors);
+    Appearance appearance = staticMakeCachedAppearance(display
+        .getGraphicsModeControl(), null, null, geometry, false, true);
+    Shape3D shape = new Shape3D(geometry, appearance);
+    shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    group.addChild(shape);
+  }
+
+  /**
+   * Add the GeometryArray to the group
+   *
+   *
+   * @param group
+   *          Group
+   * @param array
+   *          array to add
+   * @param mode
+   *          GraphicsModeControl
+   * @param constant_alpha
+   *          constant alpha value
+   * @param constant_color
+   *          constant color value
+   *
+   * @return true if successful
+   *
+   * @throws VisADException
+   *           unable to add the array to the group
+   */
+  public boolean addToGroup(Object group, VisADGeometryArray array,
+      GraphicsModeControl mode, float constant_alpha, float[] constant_color)
+      throws VisADException {
+    cacheAppearances = mode.getCacheAppearances();
+    mergeShapes = mode.getMergeGeometries();
+    if (array != null && array.vertexCount > 0) {
+      float af = 0.0f;
+      TransparencyAttributes c_alpha = null;
+      if (constant_alpha == 1.0f) {
+        // constant opaque alpha = NONE
+        c_alpha = getTransparencyAttributes(TransparencyAttributes.NONE, 0.0f);
+      } else if (constant_alpha == constant_alpha) {
+        c_alpha = getTransparencyAttributes(mode.getTransparencyMode(),
+            constant_alpha);
+        af = constant_alpha;
+      } else {
+        // WLH - 18 Aug 99 - how could this have gone undetected for so long?
+        // c_alpha = c_alpha = (mode.getTransparencyMode(),
+        // 0.0f);
+        c_alpha = getTransparencyAttributes(mode.getTransparencyMode(), 0.0f);
+      }
+      ColoringAttributes c_color = null;
+      if (constant_color != null && constant_color.length == 3) {
+        // c_color = new ColoringAttributes();
+        // c_color.setColor(constant_color[0], constant_color[1],
+        // constant_color[2]);
+        c_color = getColoringAttributes(constant_color[0], constant_color[1],
+            constant_color[2]);
+
+        // WLH 16 Oct 2001
+        if (!(array instanceof VisADLineArray
+            || array instanceof VisADPointArray || array instanceof VisADLineStripArray)
+            && array.colors == null) {
+          int color_len = 3;
+          if (af != 0.0f) {
+            color_len = 4;
+          }
+          byte r = ShadowType.floatToByte(constant_color[0]);
+          byte g = ShadowType.floatToByte(constant_color[1]);
+          byte b = ShadowType.floatToByte(constant_color[2]);
+          int len = array.vertexCount * color_len;
+          byte[] colors = new byte[len];
+          int k = 0;
+          if (color_len == 3) {
+            for (int i = 0; i < len; i += 3) {
+              colors[i] = r;
+              colors[i + 1] = g;
+              colors[i + 2] = b;
+            }
+          } else {
+            byte a = ShadowType.floatToByte(af);
+            for (int i = 0; i < len; i += 4) {
+              colors[i] = r;
+              colors[i + 1] = g;
+              colors[i + 2] = b;
+              colors[i + 3] = a;
+            }
+          }
+          array.colors = colors;
+        }
+
+      }
+      // MEM - for coordinates if mode2d
+      GeometryArray geometry = display.makeGeometry(array);
+      Appearance appearance = makeCachedAppearance(mode, c_alpha, c_color,
+          geometry, false, true);
+
+      addToShape((Group) group, geometry, appearance);
+
+      // Shape3D shape = new Shape3D(geometry, appearance);
+      // shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      // shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      // ((Group) group).addChild(shape);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   *
+   *
+   * @param group
+   * @param array
+   * @param mode
+   * @param constant_alpha
+   * @param constant_color
+   *
+   * @return true if added
+   *
+   * @throws VisADException
+   */
+  
+  public boolean addTextToGroup(Object group, VisADGeometryArray array,
+      GraphicsModeControl mode, float constant_alpha, float[] constant_color)
+      throws VisADException {
+    if (array != null && array.vertexCount > 0) {
+      float af = 0.0f;
+      TransparencyAttributes c_alpha = null;
+      if (constant_alpha == 1.0f) {
+        // constant opaque alpha = NONE
+        c_alpha = getTransparencyAttributes(TransparencyAttributes.NONE, 0.0f);
+      } else if (constant_alpha == constant_alpha) {
+        c_alpha = getTransparencyAttributes(mode.getTransparencyMode(),
+            constant_alpha);
+        af = constant_alpha;
+      } else {
+        // WLH - 18 Aug 99 - how could this have gone undetected for so long?
+        // c_alpha = getTransparencyAttributes(mode.getTransparencyMode(),
+        // 1.0f);
+        c_alpha = getTransparencyAttributes(mode.getTransparencyMode(), 0.0f);
+      }
+      ColoringAttributes c_color = null;
+      if (constant_color != null && constant_color.length == 3) {
+        c_color = getColoringAttributes(constant_color[0], constant_color[1],
+            constant_color[2]);
+
+        // WLH 16 Oct 2001 (really 10 Dec 2001)
+        if (!(array instanceof VisADLineArray
+            || array instanceof VisADPointArray || array instanceof VisADLineStripArray)
+            && array.colors == null) {
+          int color_len = 3;
+          if (af != 0.0f) {
+            color_len = 4;
+          }
+          byte r = ShadowType.floatToByte(constant_color[0]);
+          byte g = ShadowType.floatToByte(constant_color[1]);
+          byte b = ShadowType.floatToByte(constant_color[2]);
+          int len = array.vertexCount * color_len;
+          byte[] colors = new byte[len];
+
+          if (color_len == 3) {
+            for (int i = 0; i < len; i += 3) {
+              colors[i] = r;
+              colors[i + 1] = g;
+              colors[i + 2] = b;
+            }
+          } else {
+            byte a = ShadowType.floatToByte(af);
+            for (int i = 0; i < len; i += 4) {
+              colors[i] = r;
+              colors[i + 1] = g;
+              colors[i + 2] = b;
+              colors[i + 3] = a;
+            }
+          }
+          array.colors = colors;
+        }
+
+      }
+      // MEM - for coordinates if mode2d
+      GeometryArray geometry = display.makeGeometry(array);
+      Appearance appearance = makeCachedAppearance(mode, c_alpha, c_color,
+          geometry, false, true);
+      Shape3D shape = new Shape3D(geometry, appearance);
+      shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+      shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+      ((Group) group).addChild(shape);
+
+      if (array instanceof VisADTriangleArray) {
+        GeometryArray geometry2 = display.makeGeometry(array);
+        // Don't cache the appearance
+        Appearance appearance2 = makeCachedAppearance(mode, c_alpha, c_color,
+            geometry2, false, false);
+        // LineAttributes la = appearance2.getLineAttributes();
+        // better without anti-aliasing
+        // la.setLineAntialiasingEnable(true);
+        PolygonAttributes pa = appearance2.getPolygonAttributes();
+        pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
+        Shape3D shape2 = new Shape3D(geometry2, appearance2);
+        shape2.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+        shape2.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+        ((Group) group).addChild(shape2);
+      }
+
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Do we allo constant color surfaces?
+   *
+   * @return false
+   */
+  public boolean allowConstantColorSurfaces() {
+    return false;
+  }
+
+  /**
+   * Get a String representation of this object
+   *
+   * @return the String representation of this object
+   */
+  public String toString() {
+    return adaptedShadowType.toString();
+  }
+
+}
+
+/**
+ * Class ProjectionControlListener
+ */
+class ProjectionControlListener implements ControlListener {
+
+  /**  */
+  LabelTransform[][][] LT_array = null;
+
+  /**  */
+  ProjectionControl p_cntrl = null;
+
+  /**  */
+  ContourControl c_cntrl = null;
+
+  /**  */
+  double last_scale;
+
+  /**  */
+  double first_scale;
+
+  /**  */
+  int cnt = 0;
+
+  /**  */
+  double last_time;
+
+  /**
+   *
+   *
+   * @param p_cntrl
+   * @param c_cntrl
+   */
+  ProjectionControlListener(ProjectionControl p_cntrl, ContourControl c_cntrl) {
+    this.p_cntrl = p_cntrl;
+    this.c_cntrl = c_cntrl;
+    double[] matrix = p_cntrl.getMatrix();
+    double[] rot_a = new double[3];
+    double[] trans_a = new double[3];
+    double[] scale_a = new double[1];
+    MouseBehaviorJ3D.unmake_matrix(rot_a, scale_a, trans_a, matrix);
+    last_scale = scale_a[0];
+    first_scale = last_scale;
+    LT_array = new LabelTransform[1000][][];
+    last_time = System.currentTimeMillis();
+    p_cntrl.addControlListener(this);
+    c_cntrl.addProjectionControlListener(this, p_cntrl);
+  }
+
+  /**
+   *
+   *
+   * @param e
+   *
+   * @throws RemoteException
+   * @throws VisADException
+   */
+  public synchronized void controlChanged(ControlEvent e)
+      throws VisADException, RemoteException {
+    double[] matrix = p_cntrl.getMatrix();
+    double[] rot_a = new double[3];
+    double[] trans_a = new double[3];
+    double[] scale_a = new double[1];
+
+    MouseBehaviorJ3D.unmake_matrix(rot_a, scale_a, trans_a, matrix);
+
+    // - identify scale change events.
+    if (!visad.util.Util.isApproximatelyEqual(scale_a[0], last_scale)) {
+      double current_time = System.currentTimeMillis();
+      if (scale_a[0] / last_scale > 1.15 || scale_a[0] / last_scale < 1 / 1.15) {
+        // BMF 2006-10-11 added loop for iterating controlChanged(...) calls to
+        // avoid null pointer exceptions
+        if (current_time - last_time < 3000) {
+          if (LT_array != null) {
+            for (int ii = 0; ii < cnt; ii++) {
+              for (int kk = 0; kk < LT_array[ii][0].length; kk++) {
+                for (int jj = 0; jj < LT_array[0].length; jj++)
+                  if (LT_array[ii][jj][kk] != null)
+                    LT_array[ii][jj][kk].controlChanged(first_scale, scale_a);
+              }
+            }
+          }
+        } else {
+          if (LT_array != null) {
+            for (int ii = 0; ii < cnt; ii++) {
+              for (int kk = 0; kk < LT_array[ii][0].length; kk++) {
+                for (int jj = 0; jj < LT_array[0].length; jj++) {
+                  if (LT_array[ii][jj][kk] != null)
+                    LT_array[ii][jj][kk].controlChanged(first_scale, scale_a);
+                }
+              }
+            }
+          }
+          c_cntrl.reLabel();
+        }
+        last_scale = scale_a[0];
+      }
+      last_time = current_time;
+    }
+  }
+}
+
+/**
+ * Class LabelTransform
+ */
+class LabelTransform {
+
+  /**  */
+  TransformGroup trans;
+
+  /**  */
+  Transform3D t3d;
+
+  /**  */
+  ProjectionControl proj;
+
+  /**  */
+  VisADGeometryArray label_array;
+
+  /**  */
+  VisADGeometryArray anchr_array;
+
+  /**  */
+  double[] matrix;
+
+  /**  */
+  double last_scale;
+
+  /**  */
+  double first_scale;
+
+  /**  */
+  float[] vertex;
+
+  /**  */
+  float[] anchr_vertex;
+
+  /**  */
+  double[] rot_a;
+
+  /**  */
+  double[] trans_a;
+
+  /**  */
+  double[] scale_a;
+
+  /**  */
+  int flag;
+
+  /**  */
+  float[] f_array;
+
+  /**
+   *
+   *
+   * @param trans
+   * @param proj
+   * @param label_array
+   * @param f_array
+   * @param flag
+   */
+  LabelTransform(TransformGroup trans, ProjectionControl proj,
+      VisADGeometryArray[] label_array, float[] f_array, int flag) {
+    this.trans = trans;
+    this.proj = proj;
+    this.label_array = label_array[0];
+    this.anchr_array = label_array[1];
+    this.flag = flag;
+    this.f_array = f_array;
+
+    t3d = new Transform3D();
+    matrix = proj.getMatrix();
+    rot_a = new double[3];
+    trans_a = new double[3];
+    scale_a = new double[1];
+    MouseBehaviorJ3D.unmake_matrix(rot_a, scale_a, trans_a, matrix);
+    last_scale = scale_a[0];
+    first_scale = last_scale;
+
+    vertex = this.label_array.coordinates;
+    anchr_vertex = this.anchr_array.coordinates;
+    int vertexCount = this.label_array.vertexCount;
+  }
+
+  /**
+   *
+   *
+   * @param first_scale
+   * @param scale_a
+   */
+  public void controlChanged(double first_scale, double[] scale_a) {
+    trans.getTransform(t3d);
+
+    double factor = 0;
+    float f_scale = 0;
+
+    if (flag == 0) { // -- label
+      double k = first_scale; // - final scale
+      factor = k / scale_a[0];
+      f_scale = (float) ((scale_a[0] - k) / scale_a[0]);
+    } else { // -- expanding line segments
+      double k = (f_array[0] / (f_array[1] - f_array[0]))
+          * (f_array[1] / f_array[0] - first_scale / scale_a[0]) * scale_a[0];
+      factor = k / scale_a[0];
+      f_scale = (float) ((scale_a[0] - k) / scale_a[0]);
+    }
+
+    Vector3f trans_vec = new Vector3f(f_scale * anchr_vertex[0], f_scale
+        * anchr_vertex[1], f_scale * anchr_vertex[2]);
+
+    //  These can't all be zero: non-affine transform
+    if (!(factor == 0.0 && (trans_vec.x == 0.0 && trans_vec.y == 0.0 && trans_vec.z == 0.0))) {
+      t3d.set((float) factor, trans_vec);
+      trans.setTransform(t3d);
+    }
+  }
+}
diff --git a/visad/java3d/TrackdAPI.c b/visad/java3d/TrackdAPI.c
new file mode 100644
index 0000000..43d5814
--- /dev/null
+++ b/visad/java3d/TrackdAPI.c
@@ -0,0 +1,115 @@
+/*
+ * VisAD system for interactive analysis and visualization of numerical
+ * data.  Copyright (C) 1996 - 2009 Bill Hibbard, Curtis Rueden, Tom
+ * Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+ * Tommy Jasmin.
+ * 
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA
+ */
+
+/* TrackdAPI.c */
+
+#include <jni.h>
+#include "visad_java3d_TrackdJNI.h"
+
+#include "trackdAPI.h"
+
+void *tracker;
+void *controller;
+
+/*
+ * Class:     visad_java3d_TrackdJNI
+ * Method:    get_trackd_c
+ * Signature: ([I[F[F[F[I[I)V
+ */
+  JNIEXPORT void JNICALL Java_visad_java3d_TrackdJNI_get_1trackd_1c
+    (JNIEnv *env, jobject obj, jintArray number_of_sensors_j,
+     jfloatArray sensor_positions_j, jfloatArray sensor_angles_j,
+     jfloatArray sensor_matrices_j, jintArray number_of_buttons_j,
+     jintArray button_states_j) {
+
+     int ns, nb, is, ib, i, j, k;
+     float pos[3];
+     float mat[4][4];
+
+     jint *number_of_sensors =
+       (*env)->GetIntArrayElements(env, number_of_sensors_j, 0);
+     jfloat *sensor_positions =
+       (*env)->GetFloatArrayElements(env, sensor_positions_j, 0);
+     jfloat *sensor_angles =
+       (*env)->GetFloatArrayElements(env, sensor_angles_j, 0);
+     jfloat *sensor_matrices =
+       (*env)->GetFloatArrayElements(env, sensor_matrices_j, 0);
+     jint *number_of_buttons =
+       (*env)->GetIntArrayElements(env, number_of_buttons_j, 0);
+     jint *button_states =
+       (*env)->GetIntArrayElements(env, button_states_j, 0);
+
+     ns = trackdGetNumberOfSensors(tracker);
+     if (ns > number_of_sensors[0]) ns = number_of_sensors[0];
+     number_of_sensors[0] = ns;
+     for (is=0; is<ns; is++) {
+       trackdGetPosition(tracker, is, pos);
+       sensor_positions[3*is] = pos[0];
+       sensor_positions[3*is+1] = pos[1];
+       sensor_positions[3*is+2] = pos[2];
+       trackdGetEulerAngles(tracker, is, pos);
+       sensor_angles[3*is] = pos[0];
+       sensor_angles[3*is+1] = pos[1];
+       sensor_angles[3*is+2] = pos[2];
+       trackdGetMatrix(tracker, is, mat);
+       k = 0;
+       for (i=0; i<4; i++) {
+         for (j=0; j<4; j++) {
+           sensor_matrices[16*is+k] = mat[i][j];
+           k++;
+         }
+       }
+     }
+
+     nb = trackdGetNumberOfButtons(controller);
+     if (nb > number_of_buttons[0]) nb = number_of_buttons[0];
+     number_of_buttons[0] = nb;
+     for (ib=0; ib<nb; ib++) {
+       button_states[ib] = trackdGetButton(controller, ib);
+     }
+     (*env)->ReleaseIntArrayElements(env, number_of_sensors_j, number_of_sensors, 0);
+     (*env)->ReleaseFloatArrayElements(env, sensor_positions_j, sensor_positions, 0);
+     (*env)->ReleaseFloatArrayElements(env, sensor_angles_j, sensor_angles, 0);
+     (*env)->ReleaseFloatArrayElements(env, sensor_matrices_j, sensor_matrices, 0);
+     (*env)->ReleaseIntArrayElements(env, number_of_buttons_j, number_of_buttons, 0);
+     (*env)->ReleaseIntArrayElements(env, button_states_j, button_states, 0);
+  }
+
+
+/*
+ * Class:     visad_java3d_TrackdJNI
+ * Method:    init_trackd_c
+ * Signature: (II[I)V
+ */
+  JNIEXPORT void JNICALL Java_visad_java3d_TrackdJNI_init_1trackd_1c
+    (JNIEnv *env, jobject obj, jint tracker_shmkey,
+     jint controller_shmkey, jintArray status_j) {
+
+    jint *status = (*env)->GetIntArrayElements(env, status_j, 0);
+    tracker = (void *) trackdInitTrackerReader(tracker_shmkey);
+    controller = (void *) trackdInitControllerReader(controller_shmkey);
+    status[0] = 0;
+    if (tracker == NULL) status[0] += 1;
+    if (controller == NULL) status[0] += 2;
+    (*env)->ReleaseIntArrayElements(env, status_j, status, 0);
+  }
+
diff --git a/visad/java3d/TrackdJNI.java b/visad/java3d/TrackdJNI.java
new file mode 100644
index 0000000..273c1bc
--- /dev/null
+++ b/visad/java3d/TrackdJNI.java
@@ -0,0 +1,68 @@
+//
+// TrackdJNI.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+/**
+   TrackdJNI is the VisAD class for connecting WandBehaviorJ3D
+   to the trackd native methods.  It is in a seperate class because javah
+   cannot find the Java3D classes that are imported by WandBehaviorJ3D.
+*/
+
+public class TrackdJNI {
+
+  public TrackdJNI(int tracker_shmkey, int controller_shmkey)
+         throws VisADException {
+    System.loadLibrary("TrackdAPI");
+    int[] status = {0};
+    init_trackd_c(tracker_shmkey, controller_shmkey, status);
+    if (status[0] != 0) {
+      throw new DisplayException("unable to connect to trackd " + status[0]);
+    }
+  }
+
+  public void getTrackd(int[] number_of_sensors, float[] sensor_positions,
+                        float[] sensor_angles, float[] sensor_matrices,
+                        int[] number_of_buttons, int[] button_states) {
+    get_trackd_c(number_of_sensors, sensor_positions, sensor_angles,
+                 sensor_matrices, number_of_buttons, button_states);
+  }
+
+  private native void init_trackd_c(int tracker_shmkey,
+                                    int controller_shmkey,
+                                    int[] status);
+
+  private native void get_trackd_c(int[] number_of_sensors,
+                                   float[] sensor_positions,
+                                   float[] sensor_angles,
+                                   float[] sensor_matrices,
+                                   int[] number_of_buttons,
+                                   int[] button_states);
+
+}
+
diff --git a/visad/java3d/TransformOnlyDisplayRendererJ3D.java b/visad/java3d/TransformOnlyDisplayRendererJ3D.java
new file mode 100644
index 0000000..e374f18
--- /dev/null
+++ b/visad/java3d/TransformOnlyDisplayRendererJ3D.java
@@ -0,0 +1,145 @@
+//
+// TransformOnlyDisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import java.awt.image.BufferedImage;
+
+import javax.media.j3d.*;
+
+/**
+ * <CODE>TransformOnlyDisplayRendererJ3D</CODE> is the VisAD class for the
+ * transforming but not rendering under Java3D.<P>
+ */
+public class TransformOnlyDisplayRendererJ3D extends DisplayRendererJ3D {
+
+  /**
+   * This is the <CODE>DisplayRenderer</CODE> used for the
+   * <CODE>TRANSFORM_ONLY</CODE> api.
+   * It is used for only transforming data into VisADSceneGraphObject
+   * but not rendering (and hence no interaction).
+   */
+  public TransformOnlyDisplayRendererJ3D () {
+    super();
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root;
+   * create 3-D box, lights and <CODE>MouseBehaviorJ3D</CODE> for
+   * embedded user interface.
+   * @param v
+   * @param vpt
+   * @param c
+   * @return Scene graph root.
+   */
+  public BranchGroup createSceneGraph(View v, TransformGroup vpt,
+                                      VisADCanvasJ3D c) {
+    return null;
+  }
+
+  public BufferedImage getImage() {
+    return null;
+  }
+
+  void notifyCapture() {
+  }
+
+  public void setCursorOn(boolean on) {
+  }
+
+  public void setDirectOn(boolean on) {
+  }
+
+  public void addSceneGraphComponent(Group group) {
+  }
+
+  public void addDirectManipulationSceneGraphComponent(Group group,
+                         DirectManipulationRendererJ3D renderer) {
+  }
+
+  public void clearScene(DataRenderer renderer) {
+  }
+
+  public void drag_depth(float diff) {
+  }
+
+  public void drag_cursor(VisADRay ray, boolean first) {
+  }
+
+  public void setCursorLoc(float x, float y, float z) {
+  }
+
+  public void drawCursorStringVector(VisADCanvasJ3D canvas) {
+  }
+
+  public DataRenderer findDirect(VisADRay ray, int mouseModifiers) {
+    return null;
+  }
+
+  public boolean anyDirects() {
+    return false;
+  }
+
+  public void setScaleOn(boolean on) {
+  }
+
+  public void setScale(int axis, int axis_ordinal,
+              VisADLineArray array, float[] scale_color)
+         throws VisADException {
+  }
+
+  public void setScale(int axis, int axis_ordinal,
+              VisADLineArray array, VisADTriangleArray labels,
+              float[] scale_color)
+         throws VisADException {
+  }
+
+  public void clearScales() {
+  }
+
+  public void setTransform3D(Transform3D t) {
+  }
+
+  public void setBoxAspect(double[] aspect) {
+  }
+
+
+// must override these:
+
+
+  public DataRenderer makeDefaultRenderer() {
+    return new DefaultRendererJ3D();
+  }
+
+  public boolean legalDataRenderer(DataRenderer renderer) {
+    return (renderer instanceof RendererJ3D);
+  }
+
+}
+
diff --git a/visad/java3d/TwoDDisplayRendererJ3D.java b/visad/java3d/TwoDDisplayRendererJ3D.java
new file mode 100644
index 0000000..e4047e6
--- /dev/null
+++ b/visad/java3d/TwoDDisplayRendererJ3D.java
@@ -0,0 +1,271 @@
+//
+// TwoDDisplayRendererJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import java.lang.reflect.Constructor;
+
+import javax.media.j3d.AmbientLight;
+import javax.media.j3d.Appearance;
+import javax.media.j3d.BoundingSphere;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.ColoringAttributes;
+import javax.media.j3d.DirectionalLight;
+import javax.media.j3d.GeometryArray;
+import javax.media.j3d.LineArray;
+import javax.media.j3d.LineAttributes;
+import javax.media.j3d.Shape3D;
+import javax.media.j3d.TransformGroup;
+import javax.media.j3d.View;
+import javax.vecmath.Color3f;
+import javax.vecmath.Point3d;
+import javax.vecmath.Vector3f;
+
+import visad.Display;
+import visad.DisplayRealType;
+import visad.VisADError;
+
+/**
+ * <CODE>TwoDDisplayRendererJ3D</CODE> is the VisAD class for 2-D background
+ * and metadata rendering under Java3D.<P>
+ */
+public class TwoDDisplayRendererJ3D extends DisplayRendererJ3D {
+
+  private Object not_destroyed = new Object();
+
+  /** color of box and cursor */
+  private ColoringAttributes box_color = null;
+  private ColoringAttributes cursor_color = null;
+
+  /** line of box and cursor */
+  private LineAttributes box_line = null;
+  private LineAttributes cursor_line = null;
+
+  private Class mouseBehaviorJ3DClass = null;
+
+  private MouseBehaviorJ3D mouse = null; // Behavior for mouse interactions
+
+  /**
+   * This <CODE>DisplayRenderer</CODE> supports 2-D only rendering.
+   * It is easiest to describe in terms of differences from
+   * <CODE>DefaultDisplayRendererJ3D</CODE>.  The cursor and box
+   * around the scene are 2-D, the scene cannot be rotated,
+   * the cursor cannot be translated in and out, and the
+   * scene can be translated sideways with the left mouse
+   * button with or without pressing the Ctrl key.<P>
+   * No RealType may be mapped to ZAxis or Latitude.
+   */
+  public TwoDDisplayRendererJ3D () {
+    super();
+    mouseBehaviorJ3DClass = MouseBehaviorJ3D.class;
+  }
+
+  /**
+   * @param mbj3dClass - sub Class of MouseBehaviorJ3D
+   */
+  
+  public TwoDDisplayRendererJ3D (Class mbj3dClass) {
+    super();
+    mouseBehaviorJ3DClass = mbj3dClass;
+  }
+
+  public void destroy() {
+    not_destroyed = null;
+    box_color = null;
+    cursor_color = null;
+    mouse = null;
+    super.destroy();
+  }
+
+  public boolean getMode2D() {
+    return true;
+  }
+
+  public boolean legalDisplayScalar(DisplayRealType type) {
+    if (Display.ZAxis.equals(type) ||
+        Display.Latitude.equals(type)) return false;
+    else return super.legalDisplayScalar(type);
+  }
+
+  /**
+   * Create scene graph root, if none exists, with Transform
+   * and direct manipulation root;
+   * create 3-D box, lights and MouseBehaviorJ3D for
+   * embedded user interface.
+   * @param v
+   * @param vpt
+   * @param c
+   * @return Scene graph root.
+   */
+  public BranchGroup createSceneGraph(View v, TransformGroup vpt,
+                                      VisADCanvasJ3D c) {
+    if (not_destroyed == null) return null;
+    BranchGroup root = getRoot();
+    if (root != null) return root;
+
+    // create MouseBehaviorJ3D for mouse interactions
+    try {
+      Class[] param = new Class[] {DisplayRendererJ3D.class};
+      Constructor mbConstructor =
+        mouseBehaviorJ3DClass.getConstructor(param);
+      mouse = (MouseBehaviorJ3D) mbConstructor.newInstance(new Object[] {this});
+    }
+    catch (Exception e) {
+      throw new VisADError("cannot construct " + mouseBehaviorJ3DClass);
+    }
+    // mouse = new MouseBehaviorJ3D(this);
+
+    getDisplay().setMouseBehavior(mouse);
+    box_color = new ColoringAttributes();
+    cursor_color = new ColoringAttributes();
+    root = createBasicSceneGraph(v, vpt, c, mouse, box_color, cursor_color);
+    TransformGroup trans = getTrans();
+
+    // create the box containing data depictions
+    LineArray box_geometry = new LineArray(8, LineArray.COORDINATES);
+    box_geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // box_geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    box_geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+    
+
+    // WLH 24 Nov 2000
+    box_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_WRITE);
+
+    box_geometry.setCoordinates(0, box_verts);
+    Appearance box_appearance = new Appearance();
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    box_line = new LineAttributes();
+    box_line.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
+    box_appearance.setLineAttributes(box_line);
+
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    box_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    float[] ctlBox = getRendererControl().getBoxColor();
+    box_color.setColor(ctlBox[0], ctlBox[1], ctlBox[2]);
+    box_appearance.setColoringAttributes(box_color);
+    Shape3D box = new Shape3D(box_geometry, box_appearance);
+    box.setCapability(Shape3D.ALLOW_GEOMETRY_READ); // WLH 24 Nov 2000
+    box.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    BranchGroup box_on = getBoxOnBranch();
+    box_on.addChild(box);
+
+    Appearance cursor_appearance = new Appearance();
+
+    // WLH 2 Dec 2002 in response to qomo2.txt
+    cursor_line = new LineAttributes();
+    cursor_line.setCapability(LineAttributes.ALLOW_WIDTH_WRITE);
+    cursor_appearance.setLineAttributes(cursor_line);
+
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_READ);
+    cursor_color.setCapability(ColoringAttributes.ALLOW_COLOR_WRITE);
+    float[] ctlCursor = getRendererControl().getCursorColor();
+    cursor_color.setColor(ctlCursor[0], ctlCursor[1], ctlCursor[2]);
+    cursor_appearance.setColoringAttributes(cursor_color);
+
+    BranchGroup cursor_on = getCursorOnBranch();
+    LineArray cursor_geometry = new LineArray(4, LineArray.COORDINATES);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    // cursor_geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
+    cursor_geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+    
+    cursor_geometry.setCoordinates(0, cursor_verts);
+    Shape3D cursor = new Shape3D(cursor_geometry, cursor_appearance);
+    cursor.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    cursor.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    cursor_on.addChild(cursor);
+
+    // insert MouseBehaviorJ3D into scene graph
+    BoundingSphere bounds =
+      new BoundingSphere(new Point3d(0.0,0.0,0.0), 2000000.0);
+    mouse.setSchedulingBounds(bounds);
+    trans.addChild(mouse);
+
+    // create ambient light, directly under root (not transformed)
+    Color3f color = new Color3f(0.6f, 0.6f, 0.6f);
+    AmbientLight light = new AmbientLight(color);
+    light.setInfluencingBounds(bounds);
+    root.addChild(light);
+
+    // create directional lights, directly under root (not transformed)
+    Color3f dcolor = new Color3f(0.9f, 0.9f, 0.9f);
+    Vector3f direction1 = new Vector3f(0.0f, 0.0f, 1.0f);
+    Vector3f direction2 = new Vector3f(0.0f, 0.0f, -1.0f);
+    DirectionalLight light1 =
+      new DirectionalLight(true, dcolor, direction1);
+    light1.setInfluencingBounds(bounds);
+    DirectionalLight light2 =
+      new DirectionalLight(true, dcolor, direction2);
+    light2.setInfluencingBounds(bounds);
+    root.addChild(light1);
+    root.addChild(light2);
+
+    return root;
+  }
+
+  // WLH 24 Nov 2000
+  public void setBoxAspect(double[] aspect) {
+    if (not_destroyed == null) return;
+    float[] new_verts = new float[box_verts.length];
+    for (int i=0; i<box_verts.length; i+=3) {
+      new_verts[i] = (float) (box_verts[i] * aspect[0]);
+      new_verts[i+1] = (float) (box_verts[i+1] * aspect[1]);
+      new_verts[i+2] = (float) (box_verts[i+2] * aspect[2]);
+    }
+    BranchGroup box_on = getBoxOnBranch();
+    Shape3D box = (Shape3D) box_on.getChild(0);
+    LineArray box_geometry = (LineArray) box.getGeometry();
+    box_geometry.setCoordinates(0, new_verts);
+  }
+
+  // WLH 2 Dec 2002 in response to qomo2.txt
+  public void setLineWidth(float width) {
+    box_line.setLineWidth(width);
+    cursor_line.setLineWidth(width);
+  }
+
+  private static final float[] box_verts = {
+     // front face
+         -1.0f, -1.0f,  0.0f,                       -1.0f,  1.0f,  0.0f,
+         -1.0f,  1.0f,  0.0f,                        1.0f,  1.0f,  0.0f,
+          1.0f,  1.0f,  0.0f,                        1.0f, -1.0f,  0.0f,
+          1.0f, -1.0f,  0.0f,                       -1.0f, -1.0f,  0.0f
+  };
+
+  private static final float[] cursor_verts = {
+          0.0f,  0.1f,  0.0f,                        0.0f, -0.1f,  0.0f,
+          0.1f,  0.0f,  0.0f,                       -0.1f,  0.0f,  0.0f
+  };
+}
+
diff --git a/visad/java3d/UniverseBuilderJ3D.java b/visad/java3d/UniverseBuilderJ3D.java
new file mode 100644
index 0000000..200148a
--- /dev/null
+++ b/visad/java3d/UniverseBuilderJ3D.java
@@ -0,0 +1,125 @@
+
+//
+// UniverseBuilderJ3D.java
+//
+
+/*
+   copied from Sun's Java 3D API Specification. version 1.0
+*/
+
+package visad.java3d;
+
+import java.lang.reflect.Method;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+
+public class UniverseBuilderJ3D extends Object {
+
+    // User-specified canvas
+    private Canvas3D canvas;
+
+    // Scene graph elements that the user may want access to
+    private VirtualUniverse universe;
+    private Locale locale;
+    TransformGroup vpTrans;
+    View view;
+    private BranchGroup vpRoot;
+    private ViewPlatform vp;
+
+    public UniverseBuilderJ3D(Canvas3D c) {
+      canvas = c;
+
+      // Establish a virtual universe, with a single hi-res Locale
+      universe = new VirtualUniverse();
+      locale = new Locale(universe);
+
+      // Create a PhysicalBody and Physical Environment object
+      PhysicalBody body = new PhysicalBody();
+      PhysicalEnvironment environment = new PhysicalEnvironment();
+
+      // Create a View and attach the Canvas3D and the physical
+      // body and environment to the view.
+      view = new View();
+      view.addCanvas3D(c);
+      view.setPhysicalBody(body);
+      view.setPhysicalEnvironment(environment);
+      if(minimumFrameCycleTime!=0) {
+          view.setMinimumFrameCycleTime(minimumFrameCycleTime);
+      }
+
+      // Create a branch group node for the view platform
+      vpRoot = new BranchGroup();
+      vpRoot.setCapability(BranchGroup.ALLOW_DETACH);
+      vpRoot.setCapability(Group.ALLOW_CHILDREN_READ);
+
+      // Create a ViewPlatform object, and its associated
+      // TransformGroup object, and attach it to the root of the
+      // subgraph.  Attach the view to the view platform.
+      Transform3D t = new Transform3D();
+      t.set(new Vector3f(0.0f, 0.0f, 2.0f));
+      vp = new ViewPlatform();
+      vpTrans = new TransformGroup(t);
+      vpTrans.setCapability(Group.ALLOW_CHILDREN_READ);
+      vpTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
+      vpTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
+
+      vpTrans.addChild(vp);
+      vpRoot.addChild(vpTrans);
+
+      view.attachViewPlatform(vp);
+
+      // Attach the branch graph to the universe, via the Locale.
+      // The scene graph is now live!
+      locale.addBranchGraph(vpRoot);
+    }
+
+    private static long minimumFrameCycleTime=0;
+
+    /**
+     * This lets client code set the framecycletime on the View
+     *
+     * @param ms The frame cycle time in milliseconds
+    */
+    public static void setMinimumFrameCycleTime(long ms) {
+        minimumFrameCycleTime = ms;        
+    }
+
+
+
+    public void addBranchGraph(BranchGroup bg) {
+      if (locale != null) locale.addBranchGraph(bg);
+    }
+
+    /**
+     * Clean up resources according to 
+     * http://wiki.java.net/bin/view/Javadesktop/Java3DApplicationDevelopment#Releasing_Canvas3D_View_and_Virt
+     */
+    public void destroy() {
+    	
+      // clean up resources in a way compatible back to Java3D 1.2
+      for (int idx = 0; idx < view.numCanvas3Ds(); idx++) {
+    	Canvas3D cvs = view.getCanvas3D(idx);
+    	if (cvs.isOffScreen()) {
+    		cvs.setOffScreenBuffer(null);
+    	}
+        view.removeCanvas3D(cvs);
+      }
+      try {
+        view.attachViewPlatform(null);
+      } catch (RuntimeException why) {
+    	// Apparently this might throw a NPE.
+    	// Ignore because we're just trying to conform to best practice.  
+      }
+      universe.removeAllLocales();
+
+      canvas = null;
+      universe = null;
+      locale = null;
+      vpTrans = null;
+      view = null;
+      vpRoot = null;
+      vp = null;
+    }
+}
+
diff --git a/visad/java3d/ValueControlJ3D.java b/visad/java3d/ValueControlJ3D.java
new file mode 100644
index 0000000..4ff4797
--- /dev/null
+++ b/visad/java3d/ValueControlJ3D.java
@@ -0,0 +1,121 @@
+//
+// ValueControlJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+import visad.browser.Convert;
+
+import java.rmi.*;
+
+/**
+   ValueControlJ3D is the VisAD class for controlling SelectValue
+   display scalars under Java3D.<P>
+*/
+public class ValueControlJ3D extends AVControlJ3D
+       implements ValueControl {
+
+  private double Value;
+
+  public ValueControlJ3D(DisplayImplJ3D d) {
+    super(d);
+    Value = 0.0;
+  }
+
+  public void setValue(double value)
+         throws VisADException, RemoteException {
+    Value = value;
+    selectSwitches(Value, null);
+    changeControl(true);
+  }
+
+  public void init() throws VisADException {
+    selectSwitches(Value, null);
+  }
+
+  public double getValue() {
+    return Value;
+  }
+
+  /** get a String that can be used to reconstruct this ValueControl later */
+  public String getSaveString() {
+    return "" + Value;
+  }
+
+  /** reconstruct this ValueControl using the specified save string */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    if (save == null) throw new VisADException("Invalid save string");
+    setValue(Convert.getDouble(save.trim()));
+  }
+
+  /** copy the state of a remote control to this control */
+  public void syncControl(Control rmt)
+        throws VisADException
+  {
+    if (rmt == null) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with null Control object");
+    }
+
+    if (!(rmt instanceof ValueControl)) {
+      throw new VisADException("Cannot synchronize " + getClass().getName() +
+                               " with " + rmt.getClass().getName());
+    }
+
+    ValueControl vc = (ValueControl )rmt;
+
+    boolean changed = false;
+
+    double v = getValue();
+    double rv = vc.getValue();
+    if (Math.abs(v - rv) > 0.001) {
+      try {
+        setValue(rv);
+      } catch (RemoteException re) {
+        throw new VisADException("Could not set value: " + re.getMessage());
+      }
+    }
+  }
+
+  public boolean equals(Object o)
+  {
+    if (!super.equals(o)) {
+      return false;
+    }
+
+    ValueControlJ3D vc = (ValueControlJ3D )o;
+
+    double v = getValue();
+    double rv = vc.getValue();
+    if (Math.abs(v - rv) > 0.001) {
+      return false;
+    }
+
+    return true;
+  }
+}
diff --git a/visad/java3d/VisADBranchGroup.java b/visad/java3d/VisADBranchGroup.java
new file mode 100644
index 0000000..9819d8b
--- /dev/null
+++ b/visad/java3d/VisADBranchGroup.java
@@ -0,0 +1,47 @@
+//
+// VisADBranchGroup.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+import javax.media.j3d.*;
+
+public class VisADBranchGroup extends BranchGroup {
+  private double time;
+
+  public VisADBranchGroup(double t) {
+    super();
+    time = t;
+  }
+
+  public double getTime() {
+    return time;
+  }
+
+  public void scratchTime() {
+    time = Double.NaN;
+  }
+
+}
+
diff --git a/visad/java3d/VisADCanvasJ3D.java b/visad/java3d/VisADCanvasJ3D.java
new file mode 100644
index 0000000..ba9e69d
--- /dev/null
+++ b/visad/java3d/VisADCanvasJ3D.java
@@ -0,0 +1,513 @@
+//
+// VisADCanvasJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+
+import visad.*;
+
+import javax.media.j3d.*;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+
+import java.rmi.RemoteException;
+
+import javax.swing.BoxLayout;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import java.awt.Graphics;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import com.sun.j3d.utils.universe.SimpleUniverse;
+
+
+/**
+   VisADCanvasJ3D is the VisAD extension of Canvas3D
+*/
+public class VisADCanvasJ3D extends Canvas3D {
+
+  /**           */
+  private DisplayRendererJ3D displayRenderer;
+
+  /**           */
+  private DisplayImplJ3D display;
+
+  /**           */
+  private Component component;
+
+  /**           */
+  Dimension prefSize = new Dimension(0, 0);
+
+  /**           */
+  boolean captureFlag = false;
+
+  /**           */
+  BufferedImage captureImage = null;
+
+  // size of image for off screen rendering
+
+  /**           */
+  private int width;
+
+  /**           */
+  private int height;
+
+  /**           */
+  private static int textureWidthMax = 0;
+
+  /**           */
+  private static int textureHeightMax = 0;
+
+  /**           */
+  private final static int textureWidthMaxDefault = 1024;
+
+  /**           */
+  private final static int textureHeightMaxDefault = 1024;
+
+  /**           */
+  private boolean offscreen = false;
+
+  /**           */
+  private static GraphicsConfiguration defaultConfig = null;
+
+  /**           */
+  private GraphicsConfiguration myConfig = null;
+
+  /** tracks whether stop has been called           */
+  private boolean stopCalled = false;
+
+
+
+  /**
+   * Make the graphics configuration
+   * @param offscreen  true if this is offscreen rendering (sets double
+   *                   buffering to UNNECESSARY)
+   * @return the graphics configuration
+   */
+  private static GraphicsConfiguration makeConfig(boolean offscreen) {
+    GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice d = e.getDefaultScreenDevice();
+    // GraphicsConfiguration c = d.getDefaultConfiguration();
+/* fix suggested by John Brecht from http://www.j3d.org/faq/running.html#flicker
+    GraphicsConfigTemplate3D gct3d = new GraphicsConfigTemplate3D();
+    GraphicsConfiguration c = gct3d.getBestConfiguration(d.getConfigurations());
+*/
+    GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D();
+    if (offscreen) {
+      template.setDoubleBuffer(GraphicsConfigTemplate3D.UNNECESSARY);
+    }
+    GraphicsConfiguration c = d.getBestConfiguration(template);
+    return c;
+  }
+
+  /**
+   * Set up the texture properties from the GraphicsConfiguration
+   * @param c  GraphicsConfiguration
+   */
+  private void setTextureProperties() {
+    //- see if we've already set this
+    if (!((textureHeightMax == 0) && (textureWidthMax == 0))) return;
+    //- determine textureWidthMax ---------------------------------------
+    //- first check user-defined cmd-line specs:
+    String prop = null;
+    try {
+      prop = System.getProperty("textureWidthMax");
+    }
+    catch (Exception exp) {
+      prop = null;
+    }
+    textureWidthMax = (prop == null)
+                      ? textureWidthMax
+                      : Integer.parseInt(prop);
+
+    // no user defined values, so query Java3D, or set to defaults
+    if ((textureHeightMax == 0) && (textureWidthMax == 0)) {
+      Integer wProp = null;
+      Integer hProp = null;
+      Canvas3D cnvs = new Canvas3D(myConfig);
+      try {
+        java.lang.reflect.Method method =
+          cnvs.getClass().getMethod("queryProperties", (Class[])null);
+        java.util.Map propertiesMap = (java.util.Map)method.invoke(cnvs,
+                                        (Object[])null);
+        wProp = (Integer)propertiesMap.get("textureWidthMax");
+        hProp = (Integer)propertiesMap.get("textureHeightMax");
+      }
+      catch (Exception exc) {
+      }
+
+      if ((wProp == null) || (hProp == null)) {
+        textureWidthMax = textureWidthMaxDefault;
+        textureHeightMax = textureHeightMaxDefault;
+        System.out.println(
+          "This version of Java3D can't query \"textureWidthMax/textureHeightMax\"\n" +
+          "so they are being assigned the default values: \n" +
+          "textureWidthMax:  " + textureWidthMaxDefault + "\n" +
+          "textureHeightMax:  " + textureHeightMaxDefault);
+        System.out.println(
+          "If images render as a 'grey-box', try setting these parameters\n" +
+          "to a lower value, eg. 512, with '-DtextureWidthMax=512'\n" +
+          "Otherwise check your graphics environment specifications");
+      }
+      else {
+        textureWidthMax = wProp.intValue();
+        textureHeightMax = hProp.intValue();
+      }
+    }
+
+  }
+
+  /**
+   * Get the default configuration.
+   * @return the default configuration
+   */
+  public static GraphicsConfiguration getDefaultConfig() {
+    return defaultConfig;
+  }
+
+  /**
+   * Create the canvase for the renderer.
+   * @param renderer  the renderer for this canvas
+   */
+  public VisADCanvasJ3D(DisplayRendererJ3D renderer) {
+    this(renderer, null);
+  }
+
+  /**
+   * Create the canvase for the renderer with the specified configuration.
+   * @param renderer  the renderer for this canvas
+   * @param config  GraphicsConfiguration (may be null - in which case
+   *                a default configuration is used)
+   */
+  public VisADCanvasJ3D(DisplayRendererJ3D renderer,
+                        GraphicsConfiguration config) {
+    super(config == null
+          ? defaultConfig = (defaultConfig == null
+                             ? makeConfig(false)
+                             : defaultConfig)
+          : config);
+    myConfig = (config == null)
+               ? defaultConfig
+               : config;
+
+    setTextureProperties();
+    displayRenderer = renderer;
+    display = (DisplayImplJ3D)renderer.getDisplay();
+    component = null;
+  }
+
+  /**
+   * Set the component for this canvas.
+   * @param c Component to use
+   */
+  void setComponent(Component c) {
+    component = c;
+  }
+
+  /**           */
+  private static final double METER_RATIO = (0.0254 / 90.0); // from Java3D docs
+
+  /**
+   * Constructor for offscreen rendering.
+   * @param renderer   renderer to use
+   * @param w          width of canvas
+   * @param h          height of canvas
+   *
+   * @throws VisADException 
+   */
+  public VisADCanvasJ3D(DisplayRendererJ3D renderer, int w, int h)
+          throws VisADException {
+
+// to disable off screen rendering (if you have lower than Java3D
+// version 1.2.1 installed), uncomment out the following six lines (the
+// super and throw statements)
+
+    /**
+     * super(defaultConfig);
+     * throw new VisADException("\n\nFor off screen rendering in Java3D\n" +
+     *      "please edit visad/java3d/VisADCanvasJ3D.java as follows:\n" +
+     *      "remove or comment-out \"super(defaultConfig);\" and the\n" +
+     *      "  throw statement for this Exception,\n" +
+     *      "and un-comment the body of this constructor\n");
+     */
+// AND comment out the rest of this constructor,
+    super(defaultConfig = (defaultConfig == null
+                           ? makeConfig(true)
+                           : defaultConfig), true);
+    myConfig = defaultConfig;
+    setTextureProperties();
+    displayRenderer = renderer;
+    display = (DisplayImplJ3D)renderer.getDisplay();
+    component = null;
+    offscreen = true;
+    width = w;
+    height = h;
+    BufferedImage image = new BufferedImage(width, height,
+                                            BufferedImage.TYPE_INT_RGB);
+    ImageComponent2D image2d =
+      new ImageComponent2D(ImageComponent2D.FORMAT_RGB, image);
+    setOffScreenBuffer(image2d);
+    Screen3D screen = getScreen3D();
+    int screen_width = 1280;
+    int screen_height = 1024;
+    screen.setSize(screen_width, screen_height);
+    double width_in_meters = screen_width * METER_RATIO;
+    double height_in_meters = screen_height * METER_RATIO;
+    screen.setPhysicalScreenWidth(width_in_meters);
+    screen.setPhysicalScreenHeight(height_in_meters);
+  }
+
+  /**
+   * Set the display associated with this canvas (from renderer)
+   */
+  void setDisplay() {
+    if (display == null) {
+      display = (DisplayImplJ3D)displayRenderer.getDisplay();
+    }
+  }
+
+  /**
+   * See if this is an offscreen rendering.
+   * @return true if offscreen.
+   */
+  public boolean getOffscreen() {
+    return offscreen;
+  }
+
+  /**
+   * Render the readout for the field at index.
+   * @param i index.
+   */
+  public void renderField(int i) {
+    displayRenderer.drawCursorStringVector(this);
+  }
+
+  /**
+   * Override base class method for grabbing image.
+   */
+  public void postSwap() {
+    // make sure stop() wasn't called before callback completed
+    if (display == null) return;
+
+    if (captureFlag || display.hasSlaves()) {
+      // WLH 18 March 99 - SRP suggests that in some implementations
+      // this may need to be in postRender (invoked before buffer swap)
+      captureFlag = false;
+
+      int width = getSize().width;
+      int height = getSize().height;
+      GraphicsContext3D ctx = getGraphicsContext3D();
+      Raster ras = new Raster();
+      ras.setType(Raster.RASTER_COLOR);
+      ras.setSize(width, height);
+      ras.setOffset(0, 0);
+      BufferedImage image = new BufferedImage(width, height,
+                              BufferedImage.TYPE_INT_RGB);
+      ImageComponent2D image2d =
+        new ImageComponent2D(ImageComponent2D.FORMAT_RGB, image);
+      ras.setImage(image2d);
+
+      ctx.readRaster(ras);
+
+      // Now strip out the image info
+      ImageComponent2D img_src = ras.getImage();
+      if (captureImage != null) captureImage.flush();
+      captureImage = img_src.getImage();
+      displayRenderer.notifyCapture();
+
+      // CTR 21 Sep 99 - send BufferedImage to any attached slaved displays
+      if (display.hasSlaves()) display.updateSlaves(captureImage);
+    }
+    // WLH 15 March 99
+    if (offscreen) {
+      Runnable notify = new Runnable() {
+        public void run() {
+          try {
+            //Check if the display is null. We get this when doing off screen
+            //image capture from the IDV
+            DisplayImplJ3D tmpDisplay = display;
+            if (tmpDisplay != null) {
+              tmpDisplay.notifyListeners(DisplayEvent.FRAME_DONE, 0, 0);
+            }
+          }
+          catch (VisADException e) {
+          }
+          catch (RemoteException e) {
+          }
+        }
+      };
+      Thread t = new Thread(notify);
+      t.start();
+    }
+    else {
+      try {
+        display.notifyListeners(DisplayEvent.FRAME_DONE, 0, 0);
+      }
+      catch (VisADException e) {
+      }
+      catch (RemoteException e) {
+      }
+    }
+  }
+
+  /**
+   * Get the preferred size of this canvas
+   * @return the preferred size
+   */
+  public Dimension getPreferredSize() {
+    return prefSize;
+  }
+
+  /**
+   * Set the preferred size of this canvas
+   * @param size the preferred size
+   */
+  public void setPreferredSize(Dimension size) {
+    prefSize = size;
+  }
+
+  /**
+   * Get the maximum texture width supported by this display
+   * @return the maximum texture width
+   */
+  public static int getTextureWidthMax() {
+    return textureWidthMax;
+  }
+
+  /**
+   * Get the maximum texture height supported by this display
+   * @return the maximum texture height
+   */
+  public static int getTextureHeightMax() {
+    return textureHeightMax;
+  }
+
+  /**
+   * Method to test this class
+   *
+   * @param args 
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public static void main(String[] args)
+          throws RemoteException, VisADException {
+    DisplayImplJ3D display = new DisplayImplJ3D("offscreen", 300, 300);
+
+    RealType[] types = {RealType.Latitude, RealType.Longitude};
+    RealTupleType earth_location = new RealTupleType(types);
+    RealType vis_radiance = RealType.getRealType("vis_radiance");
+    RealType ir_radiance = RealType.getRealType("ir_radiance");
+    RealType[] types2 = {vis_radiance, ir_radiance};
+    RealTupleType radiance = new RealTupleType(types2);
+    FunctionType image_tuple = new FunctionType(earth_location, radiance);
+
+    int size = 32;
+    FlatField imaget1 = FlatField.makeField(image_tuple, size, false);
+
+    display.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    display.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    display.addMap(new ScalarMap(vis_radiance, Display.RGB));
+    display.addMap(new ScalarMap(vis_radiance, Display.IsoContour));
+
+    DataReferenceImpl ref_imaget1 = new DataReferenceImpl("ref_imaget1");
+    ref_imaget1.setData(imaget1);
+    display.addReference(ref_imaget1, null);
+
+    JFrame jframe1 = new JFrame("test off screen");
+    jframe1.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+
+    JPanel panel1 = new JPanel();
+    panel1.setLayout(new BoxLayout(panel1, BoxLayout.X_AXIS));
+    panel1.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    panel1.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    jframe1.setContentPane(panel1);
+    jframe1.pack();
+    jframe1.setSize(300, 300);
+    jframe1.setVisible(true);
+
+    while(true) {
+      Graphics gp = panel1.getGraphics();
+      BufferedImage image = display.getImage();
+      gp.drawImage(image, 0, 0, panel1);
+      System.out.println("drawImage");
+      gp.dispose();
+      try {
+        Thread.sleep(1000);
+      }
+      catch (InterruptedException e) {
+      }
+    }
+
+  }
+
+
+
+  /**
+   * Stop the applet
+   */
+  public void stop() {
+    //If we have already been called then return
+    if (stopCalled) {
+      return;
+    }
+    stopCalled = true;
+
+    if (!offscreen) {
+      stopRenderer();
+    }
+    display = null;
+    displayRenderer = null;
+    if (component != null) {
+      try {
+        if (component instanceof DisplayPanelJ3D) {
+          ((DisplayPanelJ3D)component).destroy();
+        }
+        else if (component instanceof DisplayAppletJ3D) {
+          ((DisplayAppletJ3D)component).destroy();
+        }
+      }
+      catch (Exception exc) {
+        //jeffmc: we kept getting these exceptions so for now
+        //just print out the error cond continue on
+        System.err.println("Error destroying java3d component");
+        exc.printStackTrace();
+      }
+      component = null; // WLH 17 Dec 2001
+    }
+    captureImage = null;
+    myConfig = null;
+  }
+
+}
+
diff --git a/visad/java3d/VisADImageNode.java b/visad/java3d/VisADImageNode.java
new file mode 100644
index 0000000..2c675ef
--- /dev/null
+++ b/visad/java3d/VisADImageNode.java
@@ -0,0 +1,151 @@
+package visad.java3d;
+import visad.*;
+import visad.data.CachedBufferedByteImage;
+
+import javax.media.j3d.ImageComponent2D;
+import javax.media.j3d.ImageComponent2D.Updater;
+import javax.media.j3d.Behavior;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Switch;
+import javax.media.j3d.BoundingSphere;
+
+import javax.vecmath.Point3d;
+import java.awt.image.*;
+import java.awt.color.*;
+import java.util.Enumeration;
+import java.util.ArrayList;
+import java.util.Iterator;
+import javax.media.j3d.WakeupCriterion;
+import javax.media.j3d.WakeupOnElapsedFrames;
+import javax.media.j3d.WakeupOnElapsedTime;
+import javax.media.j3d.WakeupOnBehaviorPost;
+
+
+public class VisADImageNode {
+
+   VisADImageTile[] images;
+   public ArrayList<VisADImageTile> imageTiles = new ArrayList<VisADImageTile>(); 
+   public int numChildren = 0;
+   public BranchGroup branch;
+   Switch swit;
+   public int current_index = 0;
+
+   public int numImages;
+   public int data_width;
+   public int data_height;
+
+   AnimateBehavior animate = null; 
+
+   public VisADImageNode() {
+   }
+
+   public VisADImageNode(BranchGroup branch, Switch swit) {
+     this.branch = branch;
+     this.swit = swit;
+   }
+
+   public void addTile(VisADImageTile tile) {
+    imageTiles.add(tile);
+    numChildren++;
+   }
+
+   public VisADImageTile getTile(int index) {
+     return imageTiles.get(index);
+   }
+
+   public Iterator getTileIterator() {
+     return imageTiles.iterator();
+   }
+
+   public int getNumTiles() {
+     return numChildren;
+   }
+
+   /**
+   //- for implementing Updater
+   public void updateData(ImageComponent2D imageC2d, int x, int y, int lenx, int leny) {
+     if (images != null) {
+       //-imageComp.set(images[current_index]); // This should probably not be done in updateData
+     }
+   }
+   **/
+
+
+   public void setCurrent(int idx) {
+     current_index = idx;
+
+     //images[i].setCurrent(idx);
+     for (int i=0; i<numChildren; i++) {
+       imageTiles.get(i).setCurrent(idx);
+     }
+
+     /** use if stepping via a Behavior
+     if (animate != null) {
+       animate.setCurrent(idx);
+       animate.postId(777);
+     }
+     */
+
+   }
+
+
+/** use these with custom Behavior below **/
+   public void initialize() {
+     animate = new AnimateBehavior(this);
+     animate.setEnable(true);
+   }
+
+   public void update(int index) {
+     // need to iterate over children (tiles)
+     /**
+     if (images != null && imageComp != null) {
+       imageComp.set(images[index]);
+     }
+     **/
+   }
+
+   public void setBranch(BranchGroup branch) {
+     this.branch = branch;
+   }
+
+   public void setSwitch(Switch swit) {
+     this.swit = swit;
+   }
+
+   public Switch getSwitch() {
+     return swit;
+   }
+
+   public BranchGroup getBranch() {
+     return branch;
+   }
+}
+
+
+class AnimateBehavior extends Behavior {
+  private WakeupCriterion wakeupC;
+  int current = 0;
+  VisADImageNode imageNode;
+
+  AnimateBehavior(VisADImageNode imgNode) {
+    this.imageNode = imgNode;
+    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0);
+    this.setSchedulingBounds(bounds);
+    imageNode.branch.addChild(this);
+    wakeupC = new WakeupOnBehaviorPost(null, 777);
+  }
+
+  public void initialize() {
+    wakeupOn(wakeupC);
+  }
+
+  // procesStimulus changes the ImageComponent of the texture
+  public void processStimulus(Enumeration criteria) {
+    imageNode.update(current);
+    wakeupOn(wakeupC);
+  }
+
+  public void setCurrent(int idx) {
+    current = idx;
+  }
+}
diff --git a/visad/java3d/VisADImageTile.java b/visad/java3d/VisADImageTile.java
new file mode 100644
index 0000000..b600473
--- /dev/null
+++ b/visad/java3d/VisADImageTile.java
@@ -0,0 +1,127 @@
+package visad.java3d;
+import visad.*;
+import visad.data.CachedBufferedByteImage;
+
+import javax.media.j3d.ImageComponent2D;
+import javax.media.j3d.ImageComponent2D.Updater;
+import javax.media.j3d.Behavior;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Switch;
+import javax.media.j3d.BoundingSphere;
+
+import javax.vecmath.Point3d;
+import java.awt.image.*;
+import java.awt.color.*;
+import java.util.Enumeration;
+import javax.media.j3d.WakeupCriterion;
+import javax.media.j3d.WakeupOnElapsedFrames;
+import javax.media.j3d.WakeupOnElapsedTime;
+import javax.media.j3d.WakeupOnBehaviorPost;
+
+
+public class VisADImageTile implements ImageComponent2D.Updater {
+
+   BufferedImage[] images;
+   int numImages;
+   public ImageComponent2D imageComp;
+   public int current_index = 0;
+   private boolean doingPrefetch = false;
+   
+   public int height;
+   public int width;
+   public int yStart;
+   public int xStart;
+
+
+   public VisADImageTile(int numImages, int height, int yStart, int width, int xStart) {
+     this.numImages = numImages;
+     this.height = height;
+     this.yStart = yStart;
+     this.width = width;
+     this.xStart = xStart;
+     images = new BufferedImage[numImages];
+   }
+
+   public void setImages(BufferedImage[] images) {
+     this.images = images;
+     this.numImages = images.length;
+   }
+
+   public BufferedImage[] getImages() {
+     return this.images;
+   }
+
+   public BufferedImage getImage(int index) {
+     return images[index];
+   }
+
+   public void setImage(int index, BufferedImage image) {
+     images[index] = image;
+   }
+
+   public void setImageComponent(ImageComponent2D imageComp) {
+     this.imageComp = imageComp;
+   }
+
+   public void updateData(ImageComponent2D imageC2d, int x, int y, int lenx, int leny) {
+     if (images != null) {
+       //-imageComp.set(images[current_index]); // This should probably not be done in updateData
+     }
+   }
+
+
+   private int lookAheadIndexBaseIndex = 0;
+
+   public void setCurrent(int idx) {
+     current_index = idx;
+
+     //Have a local array here in case the images array changes in another thread
+     BufferedImage[] theImages = images;
+
+     ImageComponent2D theImageComp = imageComp;
+
+     if (theImageComp != null && theImages != null && idx>=0 && idx< theImages.length) {
+      //-imageComp.updateData(this, 0, 0, 0, 0); // See note above
+
+       BufferedImage image = theImages[idx];
+       if(image == null) {
+           //      System.err.println ("Animate image is null for index:" + idx);
+       } else {
+           theImageComp.set(image);
+           //Do the lookahead
+           if(image instanceof CachedBufferedByteImage) {
+               //Find the next image
+               CachedBufferedByteImage nextImage = null;
+               //If we are at the end of the loop then go to the beginning
+               int nextIdx = idx+1;
+               if(nextIdx>=theImages.length)
+                   nextIdx = 0;
+               nextImage = (CachedBufferedByteImage)theImages[nextIdx];
+               if(!doingPrefetch && nextImage!=null && !nextImage.inMemory()) {
+                   final CachedBufferedByteImage imageToLoad = nextImage;
+                   Runnable r = new Runnable() {
+                           public  void run() {
+                               doingPrefetch = true;
+                               try {
+                                   imageToLoad.getBytesFromCache();
+                               } finally {
+                                   doingPrefetch = false;
+                               }
+
+                           }
+                       };
+                   Thread t = new Thread(r);
+                   t.start();
+               }
+           }
+       }
+     }
+
+     /** use if stepping via a Behavior
+     if (animate != null) {
+       animate.setCurrent(idx);
+       animate.postId(777);
+     }
+     */
+   }
+}
diff --git a/visad/java3d/WandBehaviorJ3D.java b/visad/java3d/WandBehaviorJ3D.java
new file mode 100644
index 0000000..d7ca926
--- /dev/null
+++ b/visad/java3d/WandBehaviorJ3D.java
@@ -0,0 +1,317 @@
+//
+// WandBehaviorJ3D.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.java3d;
+
+import visad.*;
+
+import javax.media.j3d.*;
+import javax.vecmath.*;
+
+import java.util.*;
+
+/**
+   WandBehaviorJ3D is the VisAD class for wand behaviors for Java3D
+   it works with ImmersaDeskDisplayRendererJ3D
+*/
+
+/* extend MouseBehaviorJ3D to inherit multiply_matrix, make_matrix, etc */
+public class WandBehaviorJ3D extends MouseBehaviorJ3D
+       implements Runnable, MouseBehavior {
+
+  /** DisplayRenderer for Display */
+  ImmersaDeskDisplayRendererJ3D display_renderer;
+  DisplayImpl display;
+
+  private Thread wandThread;
+
+  // use vpTrans for head motion from tracker,
+  // and for left-button wand motion
+  private TransformGroup vpTrans;
+
+  // initial Transform3D from vpTrans
+  private Transform3D init_trans;
+
+  private float[] head_position = new float[3];
+  private float[] wand_position = new float[3];
+  private float[] wand_vector = new float[3];
+  // integral of wand_vector during left == true
+  private float[] travel_position = new float[3];
+  // wand button states
+  private boolean left = false, center = false, right = false;
+  // previous right wand button state
+  private boolean last_right;
+
+  private DataRenderer direct_renderer = null;
+
+  private TrackdJNI hack;
+
+  public WandBehaviorJ3D(ImmersaDeskDisplayRendererJ3D r,
+                         int tracker_shmkey, int controller_shmkey)
+         throws VisADException {
+    super();
+    display_renderer = r;
+    display = display_renderer.getDisplay();
+
+    hack = new TrackdJNI(tracker_shmkey, controller_shmkey);
+
+    last_right = false;
+  }
+
+  /* override MouseBehaviorJ3D.initialize() to do start Thread */
+  public void initialize() {
+    wandThread = new Thread(this);
+    wandThread.start();
+    vpTrans = display_renderer.getViewTrans();
+
+    init_trans = new Transform3D();
+    vpTrans.getTransform(init_trans);
+  }
+
+  /* override MouseBehaviorJ3D.processStimulus() to do nothing */
+  public void processStimulus(Enumeration criteria) {
+  }
+
+  public void stop() {
+    wandThread = null;
+  }
+
+
+  // QUESTION? values of these BOLD variables QUESTION?
+  private static int DELAY = 50; // ms
+  private static int NSENSORS = 4;
+  private static int NBUTTONS = 3;
+
+  // sensor numbers
+  private static int HEAD = 0;
+  private static int WAND = 1;
+
+  // button numbers
+  private static int LEFT = 0;
+  private static int CENTER = 1;
+  private static int RIGHT = 2;
+
+  // angle numbers
+  private static int ELEVATION = 0;
+  private static int AZIMUTH = 1;
+  private static int ROLL = 2;
+
+  // graphics distance (feet?) per second
+  private float TRAVEL_SPEED = 4.0f;
+
+  // scale factors for head / wand translation (negative?)
+  // NOTE - you may need to adjust these for your ImmersaDesk
+  private float HEAD_SCALE = 0.10f;
+  private float WAND_SCALE = 1.5f;
+
+  // offsets for head / wand translation
+  // NOTE - you may need to adjust these for your ImmersaDesk
+  private float HEADX_OFFSET = 0.0f;
+  private float HEADY_OFFSET = -3.0f;
+  private float HEADZ_OFFSET = 5.0f;
+  private float WANDX_OFFSET = -0.5f;
+  private float WANDY_OFFSET = -2.0f;
+  private float WANDZ_OFFSET = 0.0f;
+
+  // length of direct manipulation ray
+  private float RAY_LENGTH = 2.0f;
+
+
+  public void run() {
+    Thread me = Thread.currentThread();
+    int[] number_of_sensors = new int[1];
+    float[] sensor_positions = new float[NSENSORS * 3];
+    float[] sensor_angles = new float[NSENSORS * 3];
+    float[] sensor_matrices = new float[NSENSORS * 4 * 4];
+    int[] number_of_buttons = new int[1];
+    int[] button_states = new int[NBUTTONS];
+    int nprint = 1000 / DELAY;
+    travel_position[0] = 0.0f;
+    travel_position[1] = 0.0f;
+    travel_position[2] = 0.0f;
+    while (wandThread == me) {
+      try {
+        synchronized (this) {
+          wait(DELAY);
+        }
+      }
+      catch(InterruptedException e) {
+        // control doesn't normally come here
+      }
+      number_of_sensors[0] = NSENSORS;
+      number_of_buttons[0] = NBUTTONS;
+      hack.getTrackd(number_of_sensors, sensor_positions, sensor_angles,
+                     sensor_matrices, number_of_buttons, button_states);
+/*
+      nprint--;
+      if (nprint <= 0) {
+        nprint = 1000 / DELAY;
+        for (int i=0; i<number_of_sensors[0]; i++) {
+          int i3 = i * 3;
+          System.out.println("sensor " + i + " " + sensor_positions[i3] + " " +
+                             sensor_positions[i3 + 1] + " " +
+                             sensor_positions[i3 + 2] + " " +
+                             sensor_angles[i3] + " " + sensor_angles[i3 + 1] + " " +
+                             sensor_angles[i3 + 2]);
+        }
+        System.out.println(number_of_buttons[0] + " buttons: " + button_states[0] +
+                           " " + button_states[1] + " " + button_states[2]);
+      }
+*/
+
+      last_right = right;
+      left = (button_states[LEFT] != 0);
+      center = (button_states[CENTER] != 0);
+      right = (button_states[RIGHT] != 0);
+
+      head_position[0] = sensor_positions[3 * HEAD];
+      head_position[1] = sensor_positions[3 * HEAD + 1];
+      head_position[2] = sensor_positions[3 * HEAD + 2];
+      wand_position[0] = sensor_positions[3 * WAND];
+      wand_position[1] = sensor_positions[3 * WAND + 1];
+      wand_position[2] = sensor_positions[3 * WAND + 2];
+
+      float elevation = (float)
+        (sensor_angles[3 * WAND + ELEVATION] * Data.DEGREES_TO_RADIANS);
+      float azimuth = (float)
+        (sensor_angles[3 * WAND + AZIMUTH] * Data.DEGREES_TO_RADIANS);
+      float roll = (float)
+        (sensor_angles[3 * WAND + ROLL] * Data.DEGREES_TO_RADIANS);
+
+      // QUESTION? angles all 0.0 == wand pointed forward QUESTION?
+      // start with unit vector in (0, 0, 0) wand orientation
+      float x = 0.0f;
+      float y = 0.0f;
+      float z = -1.0f;
+
+      // QUESTION? is order of rotations: x, y then z QUESTION?
+      // rotate elevation
+      float xx = x;
+      float yy = (float) (Math.cos(elevation) * y - Math.sin(elevation) * z);
+      float zz = (float) (Math.cos(elevation) * z + Math.sin(elevation) * y);
+      // rotate azimuth
+      x = (float) (Math.cos(azimuth) * xx + Math.sin(azimuth) * zz);
+      y = yy;
+      z = (float) (Math.cos(azimuth) * zz - Math.sin(azimuth) * xx);
+      // don't rotate roll
+      wand_vector[0] = x;
+      wand_vector[1] = y;
+      wand_vector[2] = z;
+
+      // move along wand direction when left button pressed
+      if (left) {
+        float increment = TRAVEL_SPEED * DELAY / 1000.0f;
+        travel_position[0] += increment * wand_vector[0];
+        travel_position[1] += increment * wand_vector[1];
+        travel_position[2] += increment * wand_vector[2];
+      }
+
+      // change vpTrans based on head_position and travel_position
+      double headx =
+        HEAD_SCALE * (head_position[0] + travel_position[0] + HEADX_OFFSET);
+      double heady =
+        HEAD_SCALE * (head_position[1] + travel_position[1] + HEADY_OFFSET);
+      double headz =
+        HEAD_SCALE * (head_position[2] + travel_position[2] + HEADZ_OFFSET);
+      double[] matrix =
+        MouseBehaviorJ3D.static_make_matrix(0.0, 0.0, 0.0, 1.0, headx, heady, headz);
+
+      Transform3D temp = new Transform3D(init_trans);
+      Transform3D tm = new Transform3D(matrix);
+      temp.mul(tm);
+      vpTrans.setTransform(temp);
+
+      Transform3D t3d = new Transform3D();
+      display_renderer.getTrans().getTransform(t3d);
+
+      // QUESTION? + or - travel_position QUESTION?
+      float wandx =
+        WAND_SCALE * (wand_position[0] + travel_position[0] + WANDX_OFFSET);
+      float wandy =
+        WAND_SCALE * (wand_position[1] + travel_position[1] + WANDY_OFFSET);
+      float wandz =
+        WAND_SCALE * (wand_position[2] + travel_position[2] + WANDZ_OFFSET);
+
+      Point3f p3f = new Point3f(wandx, wandy, wandz);
+      t3d.transform(p3f);
+      wandx = p3f.x;
+      wandy = p3f.y;
+      wandz = p3f.z;
+
+      display_renderer.setCursorOn(center);
+      if (center) {
+        display_renderer.setCursorLoc(wandx, wandy, wandz);
+      }
+
+      if (right && display_renderer.anyDirects()) {
+        float wand_endx = wandx +
+          WAND_SCALE * (RAY_LENGTH * wand_vector[0]);
+        float wand_endy = wandy +
+          WAND_SCALE * (RAY_LENGTH * wand_vector[1]);
+        float wand_endz = wandz +
+          WAND_SCALE * (RAY_LENGTH * wand_vector[2]);
+
+        p3f = new Point3f(wand_endx, wand_endy, wand_endz);
+        t3d.transform(p3f);
+        wand_endx = p3f.x;
+        wand_endy = p3f.y;
+        wand_endz = p3f.z;
+
+        float[] ray_verts = {wandx, wandy, wandz, wand_endx, wand_endy, wand_endz};
+        display_renderer.setRayOn(true, ray_verts);
+        VisADRay direct_ray = new VisADRay();
+        direct_ray.position[0] = wandx;
+        direct_ray.position[1] = wandy;
+        direct_ray.position[2] = wandz;
+        // QUESTION? should ray.vector simply equal wand_vector QUESTION?
+        direct_ray.vector[0] = wand_vector[0];
+        direct_ray.vector[1] = wand_vector[1];
+        direct_ray.vector[2] = wand_vector[2];
+
+        if (!last_right) {
+          // first point
+          direct_renderer = display_renderer.findDirect(direct_ray, 0);
+          if (direct_renderer != null) {
+            display_renderer.setDirectOn(true);
+            direct_renderer.drag_direct(direct_ray, true, 0);
+          }
+        }
+        else {
+          if (direct_renderer != null) {
+            direct_renderer.drag_direct(direct_ray, false, 0);
+          }
+        }
+      }
+      else { // !right || !display_renderer.anyDirects()
+        display_renderer.setRayOn(false, null);
+        direct_renderer = null;
+      }
+
+    } // end while (wandThread == me)
+  }
+
+}
+
diff --git a/visad/java3d/package.html b/visad/java3d/package.html
new file mode 100644
index 0000000..3d03803
--- /dev/null
+++ b/visad/java3d/package.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides support for two- and three-dimensional VisAD Displays using Java3D.
+
+</body>
+</html>
+
diff --git a/visad/java3d/rmic_script b/visad/java3d/rmic_script
new file mode 100644
index 0000000..cd77a0c
--- /dev/null
+++ b/visad/java3d/rmic_script
@@ -0,0 +1 @@
+rmic visad.java3d.RemoteServerTestImpl visad.java3d.RemoteClientTestImpl
diff --git a/visad/jmet/AlbersCoordinateSystem.java b/visad/jmet/AlbersCoordinateSystem.java
new file mode 100644
index 0000000..8ee085d
--- /dev/null
+++ b/visad/jmet/AlbersCoordinateSystem.java
@@ -0,0 +1,182 @@
+//
+// AlbersCoordinateSystem.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+This is from the site at:
+<http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html>
+
+*/
+
+package visad.jmet;
+
+import visad.*;
+import java.awt.geom.Rectangle2D;
+
+public class AlbersCoordinateSystem extends visad.CoordinateSystem {
+  
+
+  private static Unit[] coordinate_system_units = {SI.meter, SI.meter};
+  private CoordinateSystem c;
+  private double n, C, rho0;
+  private double lat0, lon0;
+  private double par1, par2;
+  private double false_easting, false_northing;
+
+  private double R = 6371229; 
+
+
+  /** Albers Equal Area projection
+  *
+  * @param la0 is the latitude of the origin (deg)
+  * @param lo0 is the longitude of the origin (deg)
+  * @param p1 is latitude of the first parallel (deg)
+  * @param p2 is latitude of the second parallel (deg)
+  * @param false_easting is the value added to the x map coordinate
+  *   so that x is returned positive (m) -- may be zero...
+  * @param false_northing is the value added to the y map coordinate
+  *   so that y is returned positive (m) -- may be zero...
+  *
+  * equations taken
+  * from <http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html>
+  */
+
+  public AlbersCoordinateSystem (double la0, double lo0, double p1, double p2, double false_easting, double false_northing) throws visad.VisADException {
+     
+    super(RealTupleType.LatitudeLongitudeTuple, coordinate_system_units);
+
+    lat0 = la0 * Data.DEGREES_TO_RADIANS;
+    lon0 = lo0 * Data.DEGREES_TO_RADIANS;
+    par1 = p1 * Data.DEGREES_TO_RADIANS;
+    par2 = p2 * Data.DEGREES_TO_RADIANS;
+    this.false_easting = false_easting;
+    this.false_northing = false_northing;
+
+    n = (Math.sin(par1) + Math.sin(par2) ) / 2.0;
+
+    C = Math.pow(Math.cos(par1), 2) + 2.0 * n * Math.sin(par1);
+
+    rho0 = R * Math.sqrt(C-2.0*n*Math.sin(lat0)) / n;
+
+  }
+
+  /** convert from x,y to lat,lon
+  *
+  * @param tuples contains the x,y coordinates (grid col, row)
+  * @return tuple of (lat,lon);
+  *
+  */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("AlbersCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    double [][] latlon = new double[2][tuples[0].length];
+    double x,y,rho,rrho0, yd, theta;
+
+    for (int i=0; i<tuples[0].length; i++) {
+      
+      // tuples[0][] = x, tuples[1][] = y
+      x = tuples[0][i] - false_easting;
+      y = tuples[1][i] - false_northing;
+      rrho0 = rho0;
+      if (n < 0) {
+        x = -1.0;
+        y = -1.0;
+        rrho0 = -1.0;
+      }
+
+      yd = rrho0 - y;
+      rho = Math.sqrt( x * x + yd * yd);
+      theta = Math.atan2(x, yd);
+      if (n < 0) rho = rho - 1.0;
+
+      latlon[0][i]  = Data.RADIANS_TO_DEGREES * 
+           (Math.asin((C - Math.pow((rho * n / R), 2)) / (2 * n)));
+
+      latlon[1][i]  = Data.RADIANS_TO_DEGREES * (theta / n + lon0);
+
+    }
+
+    return latlon;
+
+  }
+
+  /** convert from lat,lon to x,y
+  *
+  * @param tuples contains the lat,lon coordinates
+  *
+  */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("AlbersCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    double theta, rho;
+    double [][] xy = new double[2][tuples[0].length];
+
+    for (int i=0; i<tuples[0].length; i++) {
+
+      rho = R * Math.sqrt(C-2.0*n*Math.sin(tuples[0][i]*Data.DEGREES_TO_RADIANS)) / n;
+      theta = n * (tuples[1][i]*Data.DEGREES_TO_RADIANS - lon0);
+
+      xy[0][i] = false_easting + rho * Math.sin(theta); 
+      xy[1][i] = false_northing + rho0 - rho*Math.cos(theta);
+    }
+
+    return xy;
+
+  }
+
+  /** determine if the Coordinate System in question is an AlbersCoordinateSystem
+  *
+  */
+  public boolean equals(Object cs) {
+    return (cs instanceof AlbersCoordinateSystem);
+  }
+
+
+  public static void main(String args[] ) {
+    try {
+
+      AlbersCoordinateSystem nc = new AlbersCoordinateSystem(23., -96., 29.5, 45.5,0,0);
+
+      double[][] latlon = new double[2][1];
+      double[][] xy = new double[2][1];
+
+      latlon[0][0] = 35.;
+      latlon[1][0] = -75.;
+      xy = nc.fromReference(latlon);
+      System.out.println(" ll=35,75 .. x="+xy[0][0]+" y="+xy[1][0]);
+
+      latlon = nc.toReference(xy);
+      System.out.println(" inverse lat="+latlon[0][0]+" lon="+latlon[1][0]);
+
+
+    } catch (Exception e) {e.printStackTrace();}
+
+  }
+
+}
diff --git a/visad/jmet/DumpType.java b/visad/jmet/DumpType.java
new file mode 100644
index 0000000..544af8c
--- /dev/null
+++ b/visad/jmet/DumpType.java
@@ -0,0 +1,545 @@
+//
+// DumpType.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.jmet;
+
+
+import visad.*;
+import visad.Set;
+
+import java.io.*;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import visad.data.*;
+
+
+/**
+ * Class of static methods for printing out information about VisAD
+ * {@link Data} objects.
+ *
+ * @version $Revision: 1.14 $
+ */
+public class DumpType {
+
+  /** initialization flag */
+  private static boolean init = true;
+
+  /** output stream to write to */
+  private static OutputStream os;
+
+
+  /**
+   * Decomposes a VisAD Data object and lists out information
+   * about its components.
+   *
+   * @param d      the VisAD Data object to analyze
+   */
+  public static void dumpDataType(Data d) {
+    dumpDataType(d, System.out);
+  }
+
+  /**
+   * Decomposes a VisAD Data object and lists out information
+   * about its components.
+   *
+   * @param d      the VisAD Data object to analyze
+   * @param uos    the OutputStream to send the text output to
+   *               (usually use "System.out")
+   *
+   */
+  public static void dumpDataType(Data d, OutputStream uos) {
+    os = uos;
+    dumpDT(d, " ");
+    return;
+  }
+
+  /**
+   * Internal method to actually do the dumping
+   *
+   * @param d         the VisAD Data object to analyze
+   * @param prefix    prefix string for any output
+   */
+  private static void dumpDT(Data d, String prefix) {
+    PrintStream out = os instanceof PrintStream ?
+      (PrintStream) os : new PrintStream(os);
+    if (init) {
+      out.println("VisAD Data analysis");
+    }
+    init = false;
+
+    try {
+
+      if (d instanceof FlatField) {
+        int nr = ((FlatField) d).getRangeDimension();
+        int nd = ((FlatField) d).getDomainDimension();
+        Set ds = ((FlatField) d).getDomainSet();
+        prefix = prefix + "  ";
+        out.println(prefix + " FlatField of length = "
+                    + ((FlatField) d).getLength());
+        out.println(prefix + " "
+                    + ((FlatField) d).getType().prettyString());
+        prefix = prefix + "  ";
+        out.println(prefix + " Domain has " + nd + " components:");
+        dumpDT(ds, prefix + "  ");
+        dumpDomainCS(ds, prefix + "  ");
+        out.println(prefix + " Range has " + nr + " components:");
+        Set[]     dr      = ((FlatField) d).getRangeSets();
+        float[][] samples = ((FlatField) d).getFloats(false);
+        for (int i = 0; i < dr.length; i++) {
+          dumpDT(dr[i], prefix + "   " + i + ".");
+          int nmiss = 0;
+          if (samples[i] == null) {
+            nmiss = ((FlatField) d).getLength();
+          } else {
+            for (int j = 0; j < samples[i].length; j++) {
+              if (samples[i][j] != samples[i][j]) {
+                nmiss++;
+              }
+            }
+          }
+          out.println(prefix + "   " + i + ". number missing = "
+                      + nmiss);
+        }
+
+      } else if (d instanceof FieldImpl) {
+        int nd = ((FieldImpl) d).getDomainDimension();
+        Set ds = ((FieldImpl) d).getDomainSet();
+        out.println(prefix + " FieldImpl of length = "
+                    + ((FieldImpl) d).getLength());
+        out.println(prefix + " "
+                    + ((FieldImpl) d).getType().prettyString());
+        out.println(prefix + " Domain has " + nd + " components:");
+        dumpDT(ds, prefix + "  ");
+        out.println(prefix + " first sample = ");
+        dumpDT(((FieldImpl) d).getSample(0, false), prefix + "   " + 0 + ".");
+
+      } else if (d instanceof Field) {
+        out.println(prefix + " Field: ");
+
+      } else if (d instanceof Function) {
+        out.println(prefix + " Function: ");
+        out.println(prefix + "    Domain dimension= "
+                    + ((Function) d).getDomainDimension());
+
+
+      } else if (d instanceof Irregular3DSet) {
+        out.println(prefix + " Irregular3DSet "
+                    + name(((Irregular3DSet) d).getType().toString())
+                    + " Length = " + ((Irregular3DSet) d).getLength());
+
+      } else if (d instanceof Irregular2DSet) {
+        out.println(prefix + " Irregular2DSet "
+                    + name(((Irregular2DSet) d).getType().toString())
+                    + " Length = " + ((Irregular2DSet) d).getLength());
+
+      } else if (d instanceof Irregular1DSet) {
+        out.println(prefix + " Irregular1DSet "
+                    + name(((Irregular1DSet) d).getType().toString())
+                    + " Length = " + ((Irregular1DSet) d).getLength());
+
+      } else if (d instanceof IrregularSet) {
+        out.println(prefix + " IrregularSet "
+                    + name(((IrregularSet) d).getType().toString())
+                    + " Length = " + ((IrregularSet) d).getLength());
+
+      } else if (d instanceof Integer3DSet) {
+        out.println(prefix + " Integer3DSet: Length = "
+                    + ((Integer3DSet) d).getLength());
+
+        for (int i = 0; i < 3; i++) {
+          dumpDT(((Linear3DSet) d).getLinear1DComponent(i),
+                 prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof Linear3DSet) {
+        out.println(prefix + " Linear3DSet: Length = "
+                    + ((Linear3DSet) d).getLength());
+
+        for (int i = 0; i < 3; i++) {
+          dumpDT(((Linear3DSet) d).getLinear1DComponent(i),
+                 prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof Gridded3DDoubleSet) {
+        out.println(prefix + " Gridded3DDoubleSet "
+                    + name(((Gridded3DDoubleSet) d).getType().toString())
+                    + " Length = " + ((Gridded3DDoubleSet) d).getLength());
+
+      } else if (d instanceof Gridded3DSet) {
+        out.println(prefix + " Gridded3DSet "
+                    + name(((Gridded3DSet) d).getType().toString())
+                    + " Length = " + ((Gridded3DSet) d).getLength());
+
+      } else if (d instanceof Integer2DSet) {
+        out.println(prefix + " Integer2DSet: Length = "
+                    + ((Integer2DSet) d).getLength());
+
+        for (int i = 0; i < 2; i++) {
+          dumpDT(((Linear2DSet) d).getLinear1DComponent(i),
+                 prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof Linear2DSet) {
+        out.println(prefix + " Linear2DSet: Length = "
+                    + ((Linear2DSet) d).getLength());
+
+        for (int i = 0; i < 2; i++) {
+          dumpDT(((Linear2DSet) d).getLinear1DComponent(i),
+                 prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof Gridded2DDoubleSet) {
+        out.println(prefix + " Gridded2DDoubleSet "
+                    + name(((Gridded2DDoubleSet) d).getType().toString())
+                    + " Length = " + ((Gridded2DDoubleSet) d).getLength());
+
+      } else if (d instanceof Gridded2DSet) {
+        out.println(prefix + " Gridded2DSet "
+                    + name(((Gridded2DSet) d).getType().toString())
+                    + " Length = " + ((Gridded2DSet) d).getLength());
+
+      } else if (d instanceof Integer1DSet) {
+        out.println(prefix + " Integer1DSet "
+                    + name(((Integer1DSet) d).getType().toString())
+                    + " Range = 0 to "
+                    + (((Integer1DSet) d).getLength() - 1));
+
+      } else if (d instanceof Linear1DSet) {
+        out.println(prefix + " Linear1DSet "
+                    + name(((Linear1DSet) d).getType().toString())
+                    + " Range = " + ((Linear1DSet) d).getFirst()
+                    + " to " + ((Linear1DSet) d).getLast() + " step "
+                    + ((Linear1DSet) d).getStep());
+
+      } else if (d instanceof Gridded1DDoubleSet) {
+        out.println(prefix + " Gridded1DDoubleSet "
+                    + name(((Gridded1DDoubleSet) d).getType().toString())
+                    + "  Length = " + ((Gridded1DDoubleSet) d).getLength());
+
+      } else if (d instanceof Gridded1DSet) {
+        out.println(prefix + " Gridded1DSet "
+                    + name(((Gridded1DSet) d).getType().toString())
+                    + "  Length = " + ((Gridded1DSet) d).getLength());
+
+      } else if (d instanceof IntegerNDSet) {
+        out.println(prefix + " IntegerNDSet: Dimension = "
+                    + ((IntegerNDSet) d).getDimension());
+
+        for (int i = 0; i < ((IntegerNDSet) d).getDimension(); i++) {
+          dumpDT(((LinearNDSet) d).getLinear1DComponent(i),
+                 prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof LinearNDSet) {
+        out.println(prefix + " LinearNDSet: Dimension = "
+                    + ((LinearNDSet) d).getDimension());
+
+        for (int i = 0; i < ((LinearNDSet) d).getDimension(); i++) {
+          dumpDT(((LinearNDSet) d).getLinear1DComponent(i),
+                 prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof GriddedSet) {
+        out.println(prefix + " GriddedSet "
+                    + name(((GriddedSet) d).getType().toString())
+                    + "  Dimension = "
+                    + ((GriddedSet) d).getDimension());
+
+      } else if (d instanceof UnionSet) {
+        out.println(prefix + " UnionSet "
+                    + name(((UnionSet) d).getType().toString())
+                    + "  Dimension = "
+                    + ((UnionSet) d).getDimension());
+
+      } else if (d instanceof ProductSet) {
+        out.println(prefix + " ProductSet "
+                    + name(((ProductSet) d).getType().toString())
+                    + "  Dimension = "
+                    + ((ProductSet) d).getDimension());
+
+      } else if (d instanceof SampledSet) {
+        out.println(prefix + " SampledSet "
+                    + name(((SampledSet) d).getType().toString())
+                    + "  Dimension = "
+                    + ((SampledSet) d).getDimension());
+
+      } else if (d instanceof FloatSet) {
+        out.println(prefix + " FloatSet "
+                    + name(((FloatSet) d).getType().toString())
+                    + " Dimension = " + ((FloatSet) d).getDimension());
+
+      } else if (d instanceof DoubleSet) {
+        out.println(prefix + " DoubleSet "
+                    + name(((DoubleSet) d).getType().toString())
+                    + "  Dimension = "
+                    + ((DoubleSet) d).getDimension());
+
+      } else if (d instanceof SimpleSet) {
+        out.println(prefix + " SimpleSet: ");
+
+      } else if (d instanceof Set) {
+        out.println(prefix + " Set: ");
+
+      } else if (d instanceof RealTuple) {
+        int n = ((RealTuple) d).getDimension();
+        out.println(prefix + " RealTuple has " + n + " components:");
+        Tuple df = (RealTuple) d;
+        for (int i = 0; i < n; i++) {
+          dumpDT(((RealTuple) d).getComponent(i), prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof Tuple) {
+        int n = ((Tuple) d).getDimension();
+        out.println(prefix + " Tuple has " + n + " components:");
+        Tuple df = (Tuple) d;
+        for (int i = 0; i < n; i++) {
+          out.println("  ");
+          dumpDT(((Tuple) d).getComponent(i), prefix + "   " + i + ".");
+        }
+
+      } else if (d instanceof Text) {
+        out.println(prefix + " Text: " + d);
+
+      } else if (d instanceof Real) {
+        out.println(prefix + " Real: " + d);
+
+      } else {
+        out.println("Unknown type for " + d);
+      }
+
+    } catch (Exception e) {
+      out.println("Exception:" + e);
+      //System.exit(1);
+      return;
+    }
+  }
+
+  /**
+   * Find the name of an Object in a String.  Looks for the last
+   * index of "(".
+   *
+   * @param n    String to search
+   *
+   * @return substring up to the last index of "("
+   */
+  private static String name(String n) {
+    // get stuff in parens...
+
+    return (String) n.substring(n.lastIndexOf("("));
+  }
+
+
+  /**
+   * Decomposes a VisAD MathType and lists out information
+   * about its components
+   *
+   * @param t MathType to dump
+   */
+  public static void dumpMathType(MathType t) {
+    dumpMathType(t, System.out);
+  }
+
+  /**
+   * Decomposes a VisAD MathType and lists out information
+   * about its components
+   *
+   * @param t     the VisAD MathType object to analyze
+   * @param uos   the OutputStream to send the text output to
+   *              (usually use "System.out")
+   */
+  public static void dumpMathType(MathType t, OutputStream uos) {
+    os = uos;
+    dumpMT(t, " ");
+    return;
+  }
+
+  /**
+   * Method to actually do the dumping
+   *
+   * @param t         MathType to dump.
+   * @param prefix   prefix string for any output
+   */
+  private static void dumpMT(MathType t, String prefix) {
+    PrintStream out = os instanceof PrintStream ?
+      (PrintStream) os : new PrintStream(os);
+    if (init) {
+      out.println("VisAD MathType analysis");
+    }
+    init = false;
+
+    try {
+
+      if (t instanceof FunctionType) {
+        out.println(prefix + " FunctionType: ");
+        RealTupleType domain = ((FunctionType) t).getDomain();
+        int           num    = domain.getDimension();
+        out.println(prefix + " Domain has " + num + " components:");
+        for (int i = 0; i < num; i++) {
+          MathType comp = domain.getComponent(i);
+          dumpMT(comp, prefix + "  " + i + ".");
+        }
+
+        out.println(prefix + " Range:");
+        MathType range = ((FunctionType) t).getRange();
+        dumpMT(range, prefix + "  ");
+
+
+      } else if (t instanceof SetType) {
+        out.println(prefix + " SetType: " + t);
+
+      } else if (t instanceof RealTupleType) {
+        int num = ((RealTupleType) t).getDimension();
+        out.println(prefix + " RealTupleType has " + num
+                           + " components:");
+        for (int i = 0; i < num; i++) {
+          MathType comp = ((RealTupleType) t).getComponent(i);
+          dumpMT(comp, prefix + "  " + i + ".");
+        }
+
+
+      } else if (t instanceof TupleType) {
+        int num = ((TupleType) t).getDimension();
+        out.println(prefix + " TupleType has " + num + " components:");
+        for (int i = 0; i < num; i++) {
+          MathType comp = ((TupleType) t).getComponent(i);
+          dumpMT(comp, prefix + "  " + i + ".");
+        }
+
+      } else if (t instanceof TextType) {
+        out.println(prefix + " TextType: " + t);
+
+      } else if (t instanceof RealType) {
+        out.println(prefix + " RealType: " + t);
+        prefix = prefix + "  ";
+        out.println(prefix + " Name = " + ((RealType) t).toString());
+        Unit   du = ((RealType) t).getDefaultUnit();
+        String s  = null;
+        if (du != null) {
+          s = du.toString();
+        }
+        if (s != null) {
+          out.println(prefix + " Unit: " + s);
+        }
+
+        Set ds = ((RealType) t).getDefaultSet();
+        if (ds != null) {
+          MathType dsmt = ds.getType();
+          out.println(prefix + " Set: " + dsmt);
+          // dumpMT(dsmt,prefix+"  ");
+        }
+
+      } else if (t instanceof ScalarType) {
+        out.println(prefix + " ScaleType: " + t);
+
+      } else {
+        out.println("Unknown type for " + t);
+      }
+
+    } catch (Exception e) {
+      out.println("Exception:" + e);
+      //System.exit(1);
+
+      return;
+    }
+
+  }
+
+  /**
+   * Print out a String representation of the CoordinateSystem of a
+   * Domain set.
+   *
+   * @param s        Set to check
+   * @param prefix   prefix for any output
+   */
+  private static void dumpDomainCS(Set s, String prefix) {
+    RealTupleType    rtt = ((SetType) s.getType()).getDomain();
+    CoordinateSystem cs  = s.getCoordinateSystem();
+    if (cs != null) {
+      RealTupleType ref = cs.getReference();
+      if (ref != null) {
+        PrintStream out = os instanceof PrintStream ?
+          (PrintStream) os : new PrintStream(os);
+        out.println(prefix + " CoordinateSystem: "
+                    + rtt.prettyString() + " ==> "
+                    + ref.prettyString());
+      }
+    }
+  }
+
+  /**
+   * Test this class by running 'java visad.jmet.DumpType'.  Reads
+   * in a data file using the default VisAD data reader family and
+   * dumps out the type of the data object.
+   *
+   * @param args name of file or URL to read in and analyze
+   */
+  public static void main(String args[]) {
+
+    if (args.length < 1) {
+      System.err.println("Usage: visad.jmet.DumpType <infile> ");
+      System.exit(1);
+      return;
+    }
+
+    DefaultFamily fr  = new DefaultFamily("sample");
+
+    URL           url = null;
+    try {
+      url = new URL(args[0]);
+    } catch (MalformedURLException exc) {
+      ;
+    }
+
+    try {
+      Data data;
+      if (url != null) {
+        System.out.println("Trying URL " + url.toString());
+      } else {
+        System.out.println("Trying file " + args[0]);
+      }
+      if (url == null) {
+        data = fr.open(args[0]);
+      } else {
+        data = fr.open(url);
+      }
+      System.out.println(args[0] + ": " + data.getType().prettyString());
+      System.out.println("  ");
+      if (data != null) {
+
+        dumpDataType(data, System.out);
+        MathType t = data.getType();
+        init = true;
+        System.out.println("  ");
+        dumpMathType(t, System.out);
+      }
+    } catch (Exception e) {
+      System.out.println(e);
+      System.exit(1);
+    }
+    System.exit(0);
+  }
+}
diff --git a/visad/jmet/EASECoordinateSystem.java b/visad/jmet/EASECoordinateSystem.java
new file mode 100644
index 0000000..3db65d3
--- /dev/null
+++ b/visad/jmet/EASECoordinateSystem.java
@@ -0,0 +1,170 @@
+
+//
+// EASECoordinateSystem.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+This is from the site at:
+http://nsidc.org/data/ease/ease_grid.html
+
+*/
+
+package visad.jmet;
+
+import visad.*;
+import visad.data.hdfeos.*;
+import java.awt.geom.Rectangle2D;
+
+public class EASECoordinateSystem extends visad.georef.MapProjection {
+  
+
+  private static Unit[] coordinate_system_units = {null, null};
+  private CoordinateSystem c;
+  private double spac, row0, col0, scale;
+  private int nrows, ncols;
+  private double R = 6371.228; 
+  private double cos30;
+
+
+  /** ctor
+  *
+  * @param spacing is the spacing in km between grid points
+  * @param row_origin is the row origin
+  * @param column_origin is the column origin
+  * @param number_rows is the number of rows
+  * @param number_columns is the number of columns
+  *
+  */
+
+  public EASECoordinateSystem(double spacing, double row_origin, double
+      column_origin, int number_rows, int number_columns) 
+      throws visad.VisADException {
+     
+    super(RealTupleType.LatitudeLongitudeTuple, coordinate_system_units);
+
+    spac = spacing;
+    scale = R / spac;
+    row0 = row_origin;
+    col0 = column_origin;
+    nrows = number_rows;
+    ncols = number_columns;
+    cos30 = Math.cos(30. * Data.DEGREES_TO_RADIANS);
+
+  }
+
+  /** convert from x,y to lat,lon
+  *
+  * @param tuples contains the x,y coordinates (grid col, row)
+  * @return tuple of (lat,lon);
+  *
+  */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("EASECoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    double [][] latlon = new double[2][tuples[0].length];
+
+    for (int i=0; i<tuples[0].length; i++) {
+      latlon[0][i] = -(Math.asin( (tuples[1][i] - row0)*cos30/scale))*
+          Data.RADIANS_TO_DEGREES;
+
+      latlon[1][i] = ((tuples[0][i] - col0)/(scale * cos30))*
+          Data.RADIANS_TO_DEGREES;
+    }
+
+    return latlon;
+
+  }
+
+  /** convert from lat,lon to x,y
+  *
+  * @param tuples contains the lat,lon coordinates
+  *
+  */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("EASECoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    double [][] xy = new double[2][tuples[0].length];
+
+    for (int i=0; i<tuples[0].length; i++) {
+      xy[0][i] = col0 + scale * tuples[1][i]*Data.DEGREES_TO_RADIANS * cos30;
+      xy[1][i] = row0 - scale * Math.sin(tuples[0][i]*Data.DEGREES_TO_RADIANS)/cos30;
+    }
+
+    return xy;
+
+  }
+
+  /** determine if the Coordinate System in question is an EASECoordinateSystem
+  *
+  */
+  public boolean equals(Object cs) {
+    return (cs instanceof EASECoordinateSystem);
+  }
+
+  /**
+   *  return the bounding box for this projection
+   */
+  public Rectangle2D getDefaultMapArea() { 
+    return new Rectangle2D.Double(0., 0., (double)ncols, (double)nrows); 
+  }
+
+  /** return the ratio of the grid spacing between rows and columns
+  */
+  public double getAspectRatio() {
+    return (1.0);
+  }
+
+
+  public static void main(String args[] ) {
+    try {
+
+      EASECoordinateSystem nc = new EASECoordinateSystem(3.13344, 1538., 3122., 721, 721);
+      //EASECoordinateSystem nc = new EASECoordinateSystem(25., 292.5, 691., 586, 1383);
+
+      double[][] latlon = new double[2][1];
+      double[][] xy = new double[2][1];
+
+      xy[0][0] = 0.;
+      xy[1][0] = 0.;
+      latlon = nc.toReference(xy);
+      System.out.println(" (0,0) lat="+latlon[0][0]+" lon="+latlon[1][0]);
+
+      xy[0][0] = 720.;
+      xy[1][0] = 720.;
+      latlon = nc.toReference(xy);
+      System.out.println(" (720,720) lat="+latlon[0][0]+" lon="+latlon[1][0]);
+
+      xy = nc.fromReference(latlon);
+      System.out.println(" inverse at above, x="+xy[0][0]+" y="+xy[1][0]);
+
+    } catch (Exception e) {e.printStackTrace();}
+
+  }
+
+}
diff --git a/visad/jmet/GRIBCoordinateSystem.java b/visad/jmet/GRIBCoordinateSystem.java
new file mode 100644
index 0000000..8a7d035
--- /dev/null
+++ b/visad/jmet/GRIBCoordinateSystem.java
@@ -0,0 +1,599 @@
+//
+// GRIBCoordinateSystem.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.jmet;
+
+import java.awt.geom.Rectangle2D;
+
+import visad.CoordinateSystem;
+import visad.CoordinateSystemException;
+import visad.Data;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.Unit;
+import visad.VisADException;
+import visad.data.hdfeos.LambertConformalConic;
+import visad.data.hdfeos.PolarStereographic;
+
+public class GRIBCoordinateSystem extends visad.georef.MapProjection {
+
+  private static Unit[] coordinate_system_units = {null, null};
+  private CoordinateSystem c;
+  private double spacing;
+  private boolean isLambert=false;
+  private boolean isLatLon=false;
+  private boolean isPolarStereo=false;
+  private double La1, Lo1, LoMax, Di, Dj;
+  private double aspectRatio = 1.0;
+  //private double earth = 6367470.0;
+  private double[] range = new double[4];
+
+  /**
+   * Constructor for a Polar Stereographic (GRIB type code = 5) with
+   * RealTupleType.LatitudeLongitudeTuple as a reference.
+   *
+   * @param  gridTypeCode    GRIB-1 grid type
+   * @param  La1             latitude of first grid point (degrees)
+   * @param  Lo1             longitude of first grid point (degrees)
+   * @param  DxDy            spacing between grid points at 60N
+   * @param  lov             orientation of the grid (degrees)
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+  public GRIBCoordinateSystem(int gridTypeCode, 
+    double La1, double Lo1, double DxDy, double lov)
+                   throws VisADException {
+    this(RealTupleType.LatitudeLongitudeTuple, gridTypeCode, 
+         La1, Lo1, DxDy, lov);
+  }
+
+  /**
+   * Constructor for a Polar Stereographic (GRIB type code = 5) with
+   * RealTupleType.LatitudeLongitudeTuple as a reference.
+   *
+   * @param  ref             reference RealTupleType (should be lat/lon)
+   * @param  gridTypeCode    GRIB-1 grid type
+   * @param  La1             latitude of first grid point (degrees)
+   * @param  Lo1             longitude of first grid point (degrees)
+   * @param  DxDy            spacing between grid points at 60N
+   * @param  lov             orientation of the grid (degrees)
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+  public GRIBCoordinateSystem(RealTupleType ref, int gridTypeCode,
+    double La1, double Lo1, double DxDy, double lov)
+                   throws VisADException {
+
+    super(ref, coordinate_system_units);
+
+    if (gridTypeCode == 5) {
+      doPolarStereo(ref, 2, 2, La1, Lo1, DxDy, lov); 
+    } else {
+
+        System.out.println("GRIB Grid type not Polar Stereographic = "+
+                                            gridTypeCode);
+        throw new VisADException
+               ("Invalid grid type for PolarStereographic = "+gridTypeCode);
+    }
+  }
+
+  /**
+   * Constructor for a Lambert conformal (GRIB type code = 3)
+   *
+   * @param  ref             reference RealTupleType (should be lat/lon)
+   * @param  gridTypeCode    GRIB-1 grid type
+   * @param  La1             latitude of first grid point (degrees)
+   * @param  Lo1             longitude of first grid point (degrees)
+   * @param  DxDy            ?
+   * @param  Latin1          first intersecting latitude (degrees)
+   * @param  Latin2          second intersecting latitude (degrees)
+   * @param  Lov             orientation of the grid (degrees)
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+  
+  public GRIBCoordinateSystem(RealTupleType ref, int gridTypeCode,
+    double La1, double Lo1, double DxDy,
+    double Latin1, double Latin2, double Lov)
+                   throws VisADException {
+
+    this(ref, gridTypeCode, 2, 2,
+         La1, Lo1, DxDy, Latin1, Latin2, Lov);
+  }
+
+  /**
+   * Constructor for a Lambert conformal (GRIB type code = 3) with
+   * RealTupleType.LatitudeLongitudeTuple as a reference.
+   *
+   * @param  gridTypeCode    GRIB-1 grid type
+   * @param  La1             latitude of first grid point (degrees)
+   * @param  Lo1             longitude of first grid point (degrees)
+   * @param  DxDy            ?
+   * @param  Latin1          first intersecting latitude (degrees)
+   * @param  Latin2          second intersecting latitude (degrees)
+   * @param  Lov             orientation of the grid (degrees)
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+  
+  public GRIBCoordinateSystem(int gridTypeCode,
+    double La1, double Lo1, double DxDy,
+    double Latin1, double Latin2, double Lov)
+                   throws VisADException {
+    this(RealTupleType.LatitudeLongitudeTuple, gridTypeCode, 2, 2,
+         La1, Lo1, DxDy, Latin1, Latin2, Lov);
+  }
+
+  /**
+   * Constructor for a latitude-longitude (GRIB type code = 0) with
+   * RealTupleType.LatitudeLongitudeTuple as a reference.
+   *
+   * @param  gridTypeCode    GRIB-1 grid type
+   * @param  Ni              number of points (W-E) along a latitude circle
+   * @param  Nj              number of points (N-S) along a longitude circle
+   * @param  La1             latitude of first grid point (degrees)
+   * @param  Lo1             longitude of first grid point (degrees)
+   * @param  La2             latitude of last grid point (degrees)
+   * @param  Lo2             longitude of last grid point (degrees)
+   * @param  Di              longitudinal direction increment (degrees)
+   * @param  Dj              latitudinal direction increment (degrees)
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+  
+  public GRIBCoordinateSystem(int gridTypeCode,
+    int Ni, int Nj, double La1, double Lo1, double La2, double Lo2,
+    double Di, double Dj) throws VisADException {
+
+    this(RealTupleType.LatitudeLongitudeTuple, gridTypeCode,
+         Ni, Nj, La1, Lo1, La2, Lo2,  Di, Dj);
+  }
+
+  /**
+   * constructor for a latitude-longitude (GRIB type code = 0). Reference
+   * should be RealTupleType.LatitudeLongitudeTuple.
+   *
+   * [this is overloaded to also handle LambertConformal (above, type = 3)]
+   *
+   * @param  ref             reference RealTupleType (should be lat/lon)
+   * @param  gridTypeCode    GRIB-1 grid type
+   * @param  Ni              number of points (W-E) along a latitude circle
+   * @param  Nj              number of points (N-S) along a longitude circle
+   * @param  La1             latitude of first grid point (degrees)
+   * @param  Lo1             longitude of first grid point (degrees)
+   * @param  La2             latitude of last grid point (degrees)
+   * @param  Lo2             longitude of last grid point (degrees)
+   * @param  Di              longitudinal direction increment (degrees)
+   * @param  Dj              latitudinal direction increment (degrees)
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   * 
+   * When type=3, then the parameters are (see construtor for 3):
+   * ref = ref, Ni = Ni, Nj = Nj, La1 = La1, Lo1 = Lo1, La2 = DxDy,
+   * Lo2 = Latin1, Di = Latin2, Dj = lov 
+   */
+  
+  public GRIBCoordinateSystem(RealTupleType ref, int gridTypeCode,
+    int Ni, int Nj, double La1, double Lo1, double La2, double Lo2,
+    double Di, double Dj) throws VisADException {
+
+    super(ref, coordinate_system_units);
+
+    if (gridTypeCode == 0) {
+      doLatLon(ref, Ni, Nj, La1, Lo1, La2, Lo2, Di, Dj) ;
+
+    } else if (gridTypeCode == 3) {
+      //note the meaning of these parameters is defined in the actual
+      // Lambert constructor signature!
+      doLambert(ref, Ni, Nj, La1, Lo1, La2, Lo2, Di, Dj);
+
+    } else {
+        System.out.println("GRIB Grid type not Lat/Lon = "+gridTypeCode);
+        throw new VisADException
+               ("Invalid grid type for Lat/Lon = "+gridTypeCode);
+    }
+
+  }
+
+  /**
+   * Constructor for a simple latitude-longitude (GRIB type code = 0) with
+   * RealTupleType.LatitudeLongitudeTuple as a reference.
+   *
+   * @param  Ni              number of points (W-E) along a latitude circle
+   * @param  Nj              number of points (N-S) along a longitude circle
+   * @param  La1             latitude of first grid point (degrees)
+   * @param  Lo1             longitude of first grid point (degrees)
+   * @param  Di              longitudinal direction increment (degrees)
+   * @param  Dj              latitudinal direction increment (degrees)
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+
+  public GRIBCoordinateSystem(int Ni, int Nj, double La1, double Lo1, 
+            double Di, double Dj) throws VisADException {
+
+    this(RealTupleType.LatitudeLongitudeTuple, 0,
+           Ni, Nj, La1, Lo1, 0., 0., Di, Dj) ;
+  }
+
+
+
+  /**
+   * constructor for well-known grid numbers. Uses
+   * RealTupleType.LatitudeLongitudeTuple for a reference.
+   *
+   * @param  gridNumber  GRIB grid number
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+  public GRIBCoordinateSystem(int gridNumber)
+                   throws VisADException {
+    this(RealTupleType.LatitudeLongitudeTuple, gridNumber);
+  }
+
+  /** constructor for well-known grid numbers
+   *
+   * @param  ref             reference RealTupleType (should be lat/lon)
+   * @param  gridNumber  GRIB grid number
+   *
+   * @exception VisADException  couldn't create the necessary VisAD object
+   */
+  public GRIBCoordinateSystem(RealTupleType ref, int gridNumber)
+                   throws VisADException {
+
+    super(ref, coordinate_system_units);
+
+    if (gridNumber == 201) {
+      doPolarStereo(ref, 65,65,-20.826, -150.0, 381.0, -105.0);
+
+    } else if (gridNumber == 202) {
+      doPolarStereo(ref, 65,43, 7.838, -141.028, 190.5, -105.0);
+
+
+    } else if (gridNumber == 203) {
+      doPolarStereo(ref, 45,39, 19.132, -185.837, 190.5, -150.0);
+
+    } else if (gridNumber == 211) {
+      doLambert(ref, 93, 65, 12.190, -133.459, 81.2705, 25.0, 25.0, -95.0);
+
+    } else if (gridNumber == 221) {
+      doLambert(ref, 349, 277, 1.0000, -145.000, 32.46341, 50.0, 50.0, -107.0);
+
+    } else if (gridNumber == 226) {
+      doLambert(ref, 737, 517, 12.190, -133.459, 10.1588125, 25.0, 25.0, -95.0);
+
+    } else if (gridNumber == 227) {
+      doLambert(ref, 1473, 1025, 12.190, -133.459, 5.07940625, 25.0, 25.0, -95.0);
+
+    } else if (gridNumber == 241) {
+      doLambert(ref, 549, 445, -4.850, -151.100, 22.000, 45.0, 45.0, -111.0);
+
+    } else if (gridNumber == 252) {
+      doLambert(ref, 301, 225, 16.281, -126.138, 20.317625, 25.0, 25.0, -95.0);
+
+    } else if (gridNumber == 2525) { // 5km FAA PDT grid pseudo-number 
+      doLambert(ref, 1200, 896, 16.30812906980,  -126.1204451156423, 5.07940 , 25.0, 25.0, -95.0);
+
+    } else if (gridNumber == 212) {
+      doLambert(ref, 185, 129, 12.190, -133.459, 40.63525, 25.0, 25.0, -95.0);
+
+    } else if (gridNumber == 213) {
+      doPolarStereo(ref, 129, 85, 7.838, -141.028, 95.250, -105.0);
+
+    } else if (gridNumber == 215) {
+      doLambert(ref, 369, 257, 12.190, -133.459, 20.317625, 25.0, 25.0, -95.0);
+
+    } else if (gridNumber == 236) {
+      doLambert(ref, 151, 113, 16.281, 233.862, 40.635, 25.0, 25.0, 265.0);
+
+    } else {
+
+        System.out.println("GRIB Grid type unknown = "+gridNumber);
+        throw new VisADException("Unknown grid number = "+gridNumber);
+    }
+
+  }
+
+  private void doLatLon(RealTupleType ref, int Ni, int Nj, double La1,
+     double Lo1, double La2, double Lo2, double Di, double Dj) throws
+     VisADException {
+
+     isLambert = false;
+     isLatLon = true;
+     this.La1 = La1;
+     this.Lo1 = Lo1;
+     this.Di = Di;
+     this.Dj = Dj;
+     LoMax = Lo1 + Di*(Ni - 1);
+     aspectRatio = (Di/Dj);
+     range[0] = 0.0;
+     range[1] = 0.0;
+     range[2] = (double) Ni;
+     range[3] = (double) Nj;
+  }
+
+  private void doPolarStereo( RealTupleType ref, int Nx, int Ny, 
+      double La1, double Lo1, double DxDy, double Lov) throws VisADException {
+
+    isPolarStereo = true;
+    spacing = DxDy * 1000.0;
+    aspectRatio = 1.0;
+    range[0] = 0.0;
+    range[1] = 0.0;
+    range[2] = (double) Nx;
+    range[3] = (double) Ny;
+
+    c = PolarStereographic.makePolarStereographic( ref,
+         La1*Data.DEGREES_TO_RADIANS, Lo1*Data.DEGREES_TO_RADIANS,
+         Lov*Data.DEGREES_TO_RADIANS);
+  }    
+
+  private void doLambert( RealTupleType ref, int Nx, int Ny, double La1, 
+     double Lo1, double DxDy, double Latin1, double Latin2, double Lov) 
+              throws VisADException {
+
+    isLambert = true;
+    spacing = DxDy*1000.0;
+    //double earth = 6371229.0;  // this is the GRADS value
+    //double earth = 6367470.0;  // this is the GRIB document value
+    double earth = 6371230.0;  // this is the one to use...
+    aspectRatio = 1.0;
+    range[0] = 0.0;
+    range[1] = 0.0;
+    range[2] = (double) Nx;
+    range[3] = (double) Ny;
+
+    c = new LambertConformalConic(ref,
+      earth, earth,
+      Latin1*Data.DEGREES_TO_RADIANS, Latin2*Data.DEGREES_TO_RADIANS,
+      Lov*Data.DEGREES_TO_RADIANS, Latin1*Data.DEGREES_TO_RADIANS,
+      0, 0);
+
+    double[][] lonlat = new double[2][1];
+    double[][] xy = new double[2][1];
+
+    lonlat[0][0] = Lo1*Data.DEGREES_TO_RADIANS;
+    lonlat[1][0] = La1*Data.DEGREES_TO_RADIANS;
+
+    xy = c.fromReference(lonlat);
+
+    c = new LambertConformalConic(ref,
+      earth, earth,
+      Latin1*Data.DEGREES_TO_RADIANS, Latin2*Data.DEGREES_TO_RADIANS,
+      Lov*Data.DEGREES_TO_RADIANS, Latin1*Data.DEGREES_TO_RADIANS,
+      -xy[0][0], -xy[1][0]);
+
+  }
+
+
+
+  /** convert from x,y to lat,lon
+  *
+  * @param tuples contains the x,y coordinates (grid col, row)
+  * @return tuple of (lat,lon);
+  *
+  */
+  public double[][] toReference(double[][] tuples) throws VisADException {
+
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("GRIBCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    if (isLambert || isPolarStereo) {
+      for (int i=0; i<tuples[0].length; i++) {
+        tuples[0][i] = tuples[0][i]*spacing;
+        tuples[1][i] = tuples[1][i]*spacing;
+      }
+
+
+      double[][] latlon = c.toReference(tuples);
+
+      for (int i=0; i<latlon[0].length; i++) {
+        double t = latlon[0][i]*Data.RADIANS_TO_DEGREES;
+        latlon[0][i] = latlon[1][i]*Data.RADIANS_TO_DEGREES;
+        latlon[1][i] = t;
+        //latlon[1][i] = -t;
+      }
+
+      return latlon;
+
+    } else if (isLatLon) {
+
+      double [][] latlon = new double[2][tuples[0].length];
+
+      for (int i=0; i<tuples[0].length; i++) {
+        latlon[0][i] = La1 + Dj*tuples[1][i];
+        latlon[1][i] = Lo1 + Di*tuples[0][i];
+      }
+
+      return latlon;
+
+    } else {
+
+      return null;
+    }
+
+  }
+
+  /** convert from lat,lon to x,y
+  *
+  * @param tuples contains the lat,lon coordinates
+  *
+  */
+  public double[][] fromReference(double[][] tuples) throws VisADException {
+    if (tuples == null || tuples.length != 2) {
+      throw new CoordinateSystemException("GRIBCoordinateSystem." +
+             "toReference: tuples wrong dimension");
+    }
+
+    if (isLambert || isPolarStereo) {
+
+      for (int i=0; i<tuples[0].length; i++) {
+        double t = tuples[0][i]*Data.DEGREES_TO_RADIANS;
+        tuples[0][i] = tuples[1][i]*Data.DEGREES_TO_RADIANS;
+        //tuples[0][i] = -tuples[1][i]*Data.DEGREES_TO_RADIANS;
+        tuples[1][i] = t;
+      }
+
+      double[][] xy = c.fromReference(tuples);
+
+      for (int i=0; i<xy[0].length; i++) {
+        xy[0][i] = xy[0][i]/spacing;
+        xy[1][i] = xy[1][i]/spacing;
+      }
+
+      return xy;
+
+    } else if (isLatLon) {
+
+      double [][] xy = new double[2][tuples[0].length];
+
+      for (int i=0; i<tuples[0].length; i++) {
+        double Lo = tuples[1][i];
+        if (LoMax > 180.0 && Lo < 0.0 && Lo < Lo1) Lo = 360. + Lo;
+        xy[0][i] = (Lo - Lo1)/Di;
+        xy[1][i] = (tuples[0][i] - La1)/Dj;
+      }
+
+      return xy;
+
+    } else {
+      return null;
+    }
+
+  }
+
+  /** determine if the Coordinate System in question is an GRIBCoordinateSystem
+  *
+  */
+  public boolean equals(Object cs) {
+    return (cs instanceof GRIBCoordinateSystem);
+  }
+
+  /**
+   *  return the bounding box for this projection
+   */
+  public Rectangle2D getDefaultMapArea() { 
+    return new Rectangle2D.Double(range[0], range[1], range[2], range[3]); 
+  }
+
+  /** return the ratio of the grid spacing between rows and columns
+  */
+  public double getAspectRatio() {
+    return aspectRatio;
+  }
+
+  /** find out if the specified grid number (GRIB code) is known
+  */
+  public static boolean isGridNumberKnown(int gridNumber) {
+    final int[] knownGrids = {201,202,203,211,212,215,236};
+    for (int i=0; i<knownGrids.length; i++) {
+      if (knownGrids[i] == gridNumber) return (true);
+    }
+    return (false);
+  }
+
+
+
+  public static void main(String args[] ) {
+    try {
+      RealTupleType ref = new RealTupleType
+           (RealType.Latitude, RealType.Longitude);
+
+      GRIBCoordinateSystem nc = new GRIBCoordinateSystem(211);
+      System.out.println("isSerializable? "+
+                  visad.util.DataUtility.isSerializable(nc,true));
+
+      double[][] latlon = new double[2][1];
+      double[][] xy = new double[2][1];
+
+      xy[0][0] = 92.;
+      xy[1][0] = 64.;
+
+      latlon = nc.toReference(xy);
+      System.out.println(" (93,65) lat="+latlon[0][0]+" lon="+latlon[1][0]);
+
+      // first, the (1,1) point...
+      latlon[0][0] = 12.190;        // lat
+      latlon[1][0] = -133.459;      // lon
+
+      xy = nc.fromReference(latlon);
+      System.out.println(" at (1,1) x="+xy[0][0]+" y="+xy[1][0]);
+      //now the upper right..
+      latlon[0][0] = 57.290;       // lat
+      latlon[1][0] = -49.385;      // lon
+
+      xy = nc.fromReference(latlon);
+      System.out.println(" max row/col x="+xy[0][0]+" y="+xy[1][0]);
+
+      //now the given point at 35n, 95w
+
+      latlon[0][0] = 35.;        // lat
+      latlon[1][0] = -95.;       // lon
+
+      xy = nc.fromReference(latlon);
+      System.out.println(" at 35N/95W x="+xy[0][0]+" y="+xy[1][0]);
+
+
+      nc = new GRIBCoordinateSystem(202);
+      System.out.println("PolarStereo.......isSerializable? "+
+                  visad.util.DataUtility.isSerializable(nc,true));
+
+      latlon = new double[2][1];
+      xy = new double[2][1];
+
+      xy[0][0] = 64.;
+      xy[1][0] = 42.;
+
+      latlon = nc.toReference(xy);
+      System.out.println(" (65,42) lat="+latlon[0][0]+" lon="+latlon[1][0]);
+
+      // first, the (1,1) point...
+      latlon[0][0] = 7.838;        // lat
+      latlon[1][0] = -1141.028;      // lon
+
+      xy = nc.fromReference(latlon);
+      System.out.println(" at (1,1) x="+xy[0][0]+" y="+xy[1][0]);
+
+      //now the upper right..
+      latlon[0][0] = 35.617;       // lat
+      latlon[1][0] = -18.576;      // lon
+
+      xy = nc.fromReference(latlon);
+      System.out.println(" max row/col x="+xy[0][0]+" y="+xy[1][0]);
+
+      //now the given point at 35n, 95w
+
+      latlon[0][0] = 35.;        // lat
+      latlon[1][0] = -95.;       // lon
+
+      xy = nc.fromReference(latlon);
+      System.out.println(" at 35N/95W x="+xy[0][0]+" y="+xy[1][0]);
+    } catch (Exception e) {e.printStackTrace();}
+
+  }
+
+}
diff --git a/visad/jmet/MetGrid.java b/visad/jmet/MetGrid.java
new file mode 100644
index 0000000..2c6e8a9
--- /dev/null
+++ b/visad/jmet/MetGrid.java
@@ -0,0 +1,106 @@
+//
+// MetGrid.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.jmet;
+
+import visad.*;
+import java.util.*;
+
+/**
+ * defines a 2D grid for meteorology.  This needs to be expanded
+ *
+ * @author Tom Whittaker
+ */
+public class MetGrid {
+
+  protected MetGridDirectory dir;
+  protected FlatField data;
+  protected CoordinateSystem coordinateSystem;
+  protected Date referenceTime;
+  protected Date validTime;
+
+  RealTupleType grid_domain;
+  Integer2DSet dom_set;
+
+  /**
+  * @param d - the MetGridDirectory for this data
+  * @param gpData - the array of data values, ordered
+  * as (x,y) with x varying fastest.
+  *
+  */
+  public MetGrid( MetGridDirectory d, double[] gpData) {
+
+    coordinateSystem = d.getCoordinateSystem();
+    dir = d;
+
+    try {
+      RealType Nx = RealType.getRealType("Nx");
+      RealType  Ny = RealType.getRealType("Ny");
+
+      String param = d.getParamName().trim();
+      RealType value_type = RealType.getRealType(param);
+
+      RealType [] domain_components = {Nx,Ny};
+      grid_domain =
+             new RealTupleType(domain_components, coordinateSystem, null);
+      dom_set = new Integer2DSet(grid_domain, dir.getColumns(), dir.getRows());
+      FunctionType grid_type = new FunctionType(grid_domain, value_type);
+      data = new FlatField(grid_type, dom_set);
+      double [][] gdata = new double[1][];
+      gdata[0] = gpData;
+      data.setSamples(gdata,false);
+
+    } catch (Exception e) {System.out.println(e);}
+
+  }
+
+  /** return the DomainSet for this grid
+  *
+  */
+  public Integer2DSet getDomainSet() {
+    return dom_set;
+  }
+
+  /** return the MetGridDirectory for this grid
+  */
+  public MetGridDirectory getGridDirectory() {
+    return dir;
+  }
+
+  /** return the CoordinateSystem for this grid (could also get from
+  *   the MetGridDirectory, too)
+  */
+  public CoordinateSystem getCoordinateSystem() {
+    return coordinateSystem;
+  }
+
+  /** return the DataImpl (FlatField) for this grid
+  */
+  public DataImpl getData() {
+    return data;
+  }
+
+
+}
diff --git a/visad/jmet/MetGridDirectory.java b/visad/jmet/MetGridDirectory.java
new file mode 100644
index 0000000..6f39143
--- /dev/null
+++ b/visad/jmet/MetGridDirectory.java
@@ -0,0 +1,115 @@
+//
+// MetGridDirectory.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.jmet;
+import java.util.*;
+import visad.*;
+import visad.Unit;
+
+/**
+ * defines an abstract grid directory (meta data)
+ *
+ * @author Tom Whittaker
+ */
+public abstract class MetGridDirectory {
+  protected String paramName;
+  protected String paramLongName;
+  protected Unit paramUnit;
+  protected Date referenceTime;
+  protected Date validTime;
+  protected Date secondTime;
+  protected double validHour;
+  protected CoordinateSystem coordSystem;
+  protected double levelValue;
+  protected Unit levelUnit;
+  protected String levelName;
+  protected double secondLevelValue;
+  protected String secondLevelName;
+  protected int rows, columns, levels;
+  protected RealType xType, yType;
+
+  public String getParamName() {
+    return paramName;
+  }
+
+  public String getParamLongName() {
+    return paramLongName;
+  }
+
+  public Unit getParamUnit() {
+    return paramUnit;
+  }
+
+  public Date getReferenceTime() {
+    return referenceTime;
+  }
+
+  public Date getValidTime() {
+    return validTime;
+  }
+
+  public Date getSecondTime() {
+    return secondTime;
+  }
+
+  public double getValidHour() {
+    return validHour;
+  }
+
+  public abstract CoordinateSystem getCoordinateSystem();
+
+  public double getLevelValue() {
+    return levelValue;
+  }
+
+  public Unit getLevelUnit() {
+    return levelUnit;
+  }
+
+  public String getLevelName() {
+    return levelName;
+  }
+
+  public double getSecondLevelValue() {
+    return secondLevelValue;
+  }
+
+  public String getSecondLevelName() {
+    return secondLevelName;
+  }
+
+  public int getRows() {
+    return rows;
+  }
+
+  public int getColumns() {
+    return columns;
+  }
+
+  public int getLevels() {
+    return levels;
+  }
+
+}
diff --git a/visad/jmet/MetUnits.java b/visad/jmet/MetUnits.java
new file mode 100644
index 0000000..3089714
--- /dev/null
+++ b/visad/jmet/MetUnits.java
@@ -0,0 +1,160 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.jmet;
+
+import visad.*;
+import visad.data.units.*;
+
+/** 
+ * Class for defining a few common atmospheric science units which don't
+ * conform to the standards used by the VisAD netCDF Units package.
+ * Use the makeSymbol method to return a "proper" unit symbol from a
+ * common one (ex: input mph to get mi/h).  Also allows one to input
+ * a common symbol in upper case (MPH) and get the proper one back (mi/h).
+ * @author Tom Whittaker, SSEC
+ */
+public class MetUnits {
+
+  static {
+    try {
+        UnitsDB du = DefaultUnitsDB.instance();
+        Unit hpa = du.get("hPa");
+        hpa = hpa.clone("hPa");
+        du.putSymbol("HPA", hpa);
+        du.putSymbol("hPa", hpa);
+        du.putSymbol("MB", hpa);
+        du.putSymbol("mb", hpa);
+        Unit pvu = Parser.parse("10-6 m2 s-1K kg-1");
+        pvu = pvu.clone("PVU");
+        du.putSymbol("PVU",pvu);
+        du.putSymbol("pvu",pvu);
+    } catch (Exception e) {
+        System.err.println("Unable to update UnitsDB");
+    }
+  }
+
+ /** 
+  * Create a 'proper' unit symbol from a common one (ie: mph -> mi/h instead
+  * of milliphots)
+  *
+  * @param commonSymbol is the String of the original unit name
+  *
+  * @return commonSymbol converted to "proper" symbol or commonSymbol if 
+  *         unknown
+  */
+  public static String makeSymbol(String commonSymbol) {
+    if (commonSymbol == null) return null;
+    String in = commonSymbol.trim();
+    String out = in;
+    if      (in.equalsIgnoreCase("m"))    { out = "m"; }
+    else if (in.equalsIgnoreCase("sec"))  { out = "s"; }
+    else if (in.equalsIgnoreCase("mps"))  { out = "m/s"; }
+    else if (in.equalsIgnoreCase("mph"))  { out = "mi/h"; }
+    else if (in.equalsIgnoreCase("kph"))  { out = "km/h"; }
+    else if (in.equalsIgnoreCase("fps"))  { out = "ft/s"; }
+    else if (in.equalsIgnoreCase("km"))   { out = "km"; }
+    else if (in.equalsIgnoreCase("dm"))   { out = "dm"; }
+    else if (in.equalsIgnoreCase("cm"))   { out = "cm"; }
+    else if (in.equalsIgnoreCase("mm"))   { out = "mm"; }
+    else if (in.equalsIgnoreCase("mi"))   { out = "mi"; }
+    else if (in.equalsIgnoreCase("pa"))   { out = "Pa"; }
+    else if (in.equalsIgnoreCase("nmi"))  { out = "nmi"; }
+    else if (in.equalsIgnoreCase("in"))   { out = "in"; }
+    else if (in.equalsIgnoreCase("deg"))  { out = "deg"; }
+    else if (in.equalsIgnoreCase("yd"))   { out = "yd"; }
+    else if (in.equalsIgnoreCase("ft"))   { out = "ft"; }
+    else if (in.equalsIgnoreCase("f"))    { out = "degF"; }
+    else if (in.equalsIgnoreCase("c"))    { out = "degC"; }
+    else if (in.equalsIgnoreCase("k"))    { out = "K"; }
+    else if (in.equalsIgnoreCase("inhg")) { out = "inhg"; }
+    else if (in.equalsIgnoreCase("kt"))   { out = "kt"; }
+    else if (in.equalsIgnoreCase("kts"))  { out = "kt"; }
+    else if (in.equalsIgnoreCase("g/kg")) { out = "g/kg"; }
+    else if (in.equalsIgnoreCase("degrees n")) { out = "degrees_north"; }
+    else if (in.equalsIgnoreCase("degrees e")) { out = "degrees_east"; }
+    else if (in.equalsIgnoreCase("degree n")) { out = "degrees_north"; }
+    else if (in.equalsIgnoreCase("degree e")) { out = "degrees_east"; }
+    else if (in.equalsIgnoreCase("degree e")) { out = "degrees_east"; }
+    else if (in.equalsIgnoreCase("degree k")) { out = "K"; }
+    else if (in.equalsIgnoreCase("degrees k")) { out = "K"; }
+    else if (in.equalsIgnoreCase("degree c")) { out = "Celsius"; }
+    else if (in.equalsIgnoreCase("degrees c")) { out = "Celsius"; }
+    else if (in.equalsIgnoreCase("degree f")) { out = "Fahrenheit"; }
+    else if (in.equalsIgnoreCase("degrees f")) { out = "Fahrenheit"; }
+    else if (in.equalsIgnoreCase("gp m")) { out = "gpm"; }
+    else if (in.equalsIgnoreCase("gp_m")) { out = "gpm"; }
+    else if (in.equalsIgnoreCase("kg"))  { out = "kg"; }  // handle Kg
+
+    // the following are decidedly McIDAS
+    else if (in.equalsIgnoreCase("paps")) { out = "Pa/s"; }
+    else if (in.equalsIgnoreCase("mgps")) { out = "km^2(kg/s)"; }
+    else if (in.equalsIgnoreCase("m2s2")) { out = "m^2/s^2"; }
+    else if (in.equalsIgnoreCase("kpm"))  { out = "K/m"; }
+    else if (in.equalsIgnoreCase("m2ps")) { out = "m^2/s"; }
+    else if (in.equalsIgnoreCase("gpkg")) { out = "g/kg"; }
+    else if (in.equalsIgnoreCase("kgm2")) { out = "mm"; }
+    else if (in.equalsIgnoreCase("kgm3")) { out = "kg/m^3"; }
+    else if (in.equalsIgnoreCase("mmps")) { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("ps"))   { out = "s-1"; }
+    else if (in.equalsIgnoreCase("wpm2")) { out = "W/m^2"; }
+    else if (in.equalsIgnoreCase("nm2"))  { out = "N/m^2"; }
+    else if (in.equalsIgnoreCase("kgps")) { out = "kg/kg/s"; }
+    else if (in.equalsIgnoreCase("ps2"))  { out = "s^-2"; }
+    else if (in.equalsIgnoreCase("pspm")) { out = "s^-1m^-1"; }
+    else if (in.equalsIgnoreCase("jpkg")) { out = "J/kg"; }
+    else if (in.equalsIgnoreCase("kgkg")) { out = "kg/kg"; }
+    else if (in.equalsIgnoreCase("psps")) { out = "s^-1"; }
+    else if (in.equalsIgnoreCase("kps"))  { out = "K/s"; }
+    else if (in.equalsIgnoreCase("k ft")) { out ="k ft"; }
+    else if (in.equalsIgnoreCase("1/sr"))  { out = "sr-1"; }
+    else if (in.equalsIgnoreCase("mw**"))  { out = "mW/m^2/sr/cm-1"; }
+    else if (in.equalsIgnoreCase("mwm2"))  { out = "mW/m^2/sr/cm-1"; }
+    else if (in.equalsIgnoreCase("wm**"))  { out = "W/m^2/sr/micron"; }
+    else if (in.equalsIgnoreCase("wp**"))  { out = "W/m^2/sr"; }
+    else if (in.equalsIgnoreCase("mb"))  { out = "hPa"; }
+    else if (in.equalsIgnoreCase("mbag"))  { out = "hPa"; }
+    else if (in.equalsIgnoreCase("dbz"))  { out = "dBz"; }
+
+    // the following handle (rightly or wrongly) converting mass/area to length
+    else if (in.equalsIgnoreCase("kg/m**2"))  { out = "mm"; }
+    else if (in.equalsIgnoreCase("kg/m2"))  { out = "mm"; }
+    else if (in.equalsIgnoreCase("kg/m^2"))  { out = "mm"; }
+    else if (in.equalsIgnoreCase("kg m-2"))  { out = "mm"; }
+    else if (in.equalsIgnoreCase("kg.m-2"))  { out = "mm"; }
+    else if (in.equalsIgnoreCase("kg/m**2/s"))  { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("kg/m**2s"))  { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("kg/m2/s"))  { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("kg/m2s"))  { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("kg/m^2/s"))  { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("kg/m^2s"))  { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("kg m-2 s-1"))  { out = "mm/s"; }
+    else if (in.equalsIgnoreCase("kg.m-2.s-1"))  { out = "mm/s"; }
+
+    // and a few more
+    else if (in.equalsIgnoreCase("W/m**2"))  { out = "W/m^2"; }
+    else if (in.equalsIgnoreCase("W/m**2s"))  { out = "W/m^2/s"; }
+
+    return out;
+    
+  }
+}
diff --git a/visad/jmet/NCEPPanel.java b/visad/jmet/NCEPPanel.java
new file mode 100644
index 0000000..f8b4ba4
--- /dev/null
+++ b/visad/jmet/NCEPPanel.java
@@ -0,0 +1,442 @@
+//
+// NCEPPanel.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.jmet;
+
+import visad.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.border.*;
+
+public class NCEPPanel extends JPanel implements
+        ActionListener, ChangeListener {
+    static int instance = 0;
+    private int myInstance;
+    private JSlider levelSlider, speedSlider;
+    private JTextField intervalText;
+    private JLabel levelSliderLabel, intervalUnits;
+    private int levelValue;
+    private double intervalValue;
+    private JComboBox paramBox;
+    private DataReference ref;
+    private ContourControl ci;
+    private RealType Values;
+    private Tuple[] tup;
+    private NetcdfGrids ncg=null;
+    private JLabel statLabel;
+    private JTabbedPane tabby;
+    private JButton butColor;
+    private float[][] colorTable;
+    private String paramName;
+    private JCheckBox showHide;
+    private DisplayImpl di;
+    private ValueControl control;
+    private ColorControl ccon;
+    private Color nc;
+    private ScalarMap map;
+    private RealType enable;
+    private FieldImpl field;
+    private String valueName, enableName, dataName;
+    private ScalarMap valueMap, valueColorMap;
+    private boolean isAloft;
+    private int ndx;
+    private Vector paramInfo;
+    private double[] pressureLevels;
+    private double[][] range;
+    private double cbeg;
+
+
+  public static void main(String args[]) {
+
+    JFrame f = new JFrame();
+    JLabel sl = new JLabel("The status label");
+    NCEPPanel ss = new NCEPPanel(true, null, sl, null, "Title");
+    Container cf = f.getContentPane();
+
+    cf.add(ss);
+
+    sl.setAlignmentX(Component.CENTER_ALIGNMENT);
+    cf.add(sl);
+    f.setSize(600,600);
+    f.setVisible(true);
+
+    /*
+    String filename = "97032712_eta.nc";
+    NetcdfGrids ng = new NetcdfGrids(filename);
+    Vector v = ng.get4dVariables();
+    ss.setParams(v);
+    */
+
+  }
+
+  /** set up a panel
+  *
+  * @param isAloft is false if this is the surface level, true otherwise
+  * @param di is the associated DisplayImpl
+  * @param statLabel is the status label from the Frame
+  * @param tabby is the JTabbedPane that this may be in (if non-null)
+  * @param title is the, well, title
+  *
+  */
+  public NCEPPanel
+     (boolean isAloft, DisplayImpl di, JLabel statLabel, JTabbedPane tabby, String title) {
+
+    super();
+    this.di = di;
+    this.statLabel = statLabel;
+    this.isAloft = isAloft;
+    this.tabby = tabby;
+
+    myInstance = getNextInstance();
+    valueName = "Value"+(myInstance+1);
+    enableName = "enable"+(myInstance+1);
+    dataName = "data"+(myInstance+1);
+    range = new double[1][2];
+    range[0][0] = -20000.;
+    cbeg = 0.f;
+    range[0][1] = 20000.;
+    colorTable = new float[3][256];
+
+    if (di != null) try {
+
+      Values = RealType.getRealType(valueName);
+      nc = Color.white;
+
+      if (isAloft) {
+        valueMap = new ScalarMap(Values, Display.IsoContour);
+        valueColorMap = new ScalarMap(Values, Display.RGB);
+
+        di.addMap(valueMap);
+        di.addMap(valueColorMap);
+        ci = (ContourControl) valueMap.getControl();
+        ci.setContourInterval(30.f, 0.f, 20000.f,   60.f);
+        ci.enableLabels(true);
+        for (int i=0; i<256; i++) {
+          colorTable[0][i] = 1.0f;
+          colorTable[1][i] = 1.0f;
+          colorTable[2][i] = 1.0f;
+        }
+
+        ccon = (ColorControl) (valueColorMap.getControl());
+        ccon.setTable(colorTable);
+
+      } else {
+        valueMap = new ScalarMap(Values, Display.RGB);
+        di.addMap(valueMap );
+        for (int i=0; i<256; i++) {
+          colorTable[0][i] = (float)i/255.f;
+          colorTable[1][i] = (float)i/255.f;
+          colorTable[2][i] = (float)i/255.f;
+        }
+
+        ccon = (ColorControl) (valueMap.getControl());
+        ccon.setTable(colorTable);
+
+      }
+
+      enable = RealType.getRealType(enableName);
+      map = new ScalarMap(enable, Display.SelectValue);
+      di.addMap(map);
+
+
+    } catch (Exception ec) {ec.printStackTrace(); }
+
+    setLayout (new BoxLayout(this, BoxLayout.Y_AXIS) );
+    setAlignmentX(Component.CENTER_ALIGNMENT);
+
+    TitledBorder border = new TitledBorder(
+      new LineBorder(Color.black), title,
+      TitledBorder.CENTER,  TitledBorder.BELOW_TOP);
+
+    border.setTitleColor(Color.black);
+    setBorder(border);
+
+    add(Box.createRigidArea(new Dimension(10,10) ) );
+
+    levelValue = 0;
+    if (isAloft) {
+      levelSliderLabel = new JLabel("Pressure level = 1000",JLabel.CENTER);
+      levelSliderLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+      levelSlider = new JSlider(JSlider.HORIZONTAL,0,100,0);
+      levelSlider.setMaximumSize(new Dimension(200,50) );
+      levelSlider.setAlignmentX(Component.CENTER_ALIGNMENT);
+      levelSlider.addChangeListener(this);
+
+      add(levelSliderLabel);
+      add(levelSlider);
+      add(Box.createRigidArea(new Dimension(10,10) ) );
+    }
+
+    String[] s = {"Choose a data file first"};
+    paramBox = new JComboBox(s);
+    paramBox.setMaximumSize(new Dimension(250,50) );
+    paramBox.setAlignmentX(Component.CENTER_ALIGNMENT);
+    paramBox.addActionListener(this);
+    paramBox.setActionCommand("paramBox");
+    JLabel paramBoxLabel = new JLabel("Selected parameter:");
+    paramBoxLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+    add(paramBoxLabel);
+    add(paramBox);
+
+    intervalValue = 60.;
+    if (isAloft) {
+      add(Box.createRigidArea(new Dimension(10,10)) );
+      JLabel intervalTextLabel = new JLabel("Contour interval:");
+      intervalTextLabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
+      add(intervalTextLabel);
+
+      JPanel p = new JPanel();
+      p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS) );
+      p.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+      intervalText = new JTextField("Enter value", 8);
+      intervalText.setMaximumSize(intervalText.getPreferredSize() );
+      intervalText.setActionCommand("intervaltext");
+      intervalText.addActionListener(this);
+      intervalUnits = new JLabel("<units>");
+      p.add(intervalText);
+      p.add(intervalUnits);
+
+      add(p);
+    }
+    add(Box.createRigidArea(new Dimension(10,10) ) );
+
+    JPanel pb = new JPanel();
+    pb.setLayout(new BoxLayout(pb, BoxLayout.X_AXIS) );
+    pb.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+    butColor = new JButton("Color");
+    butColor.addActionListener(this);
+    butColor.setActionCommand("butColor");
+    butColor.setBackground(nc);
+    pb.add(butColor);
+
+    showHide = new JCheckBox("Make visible");
+    showHide.addActionListener(this);
+    showHide.setActionCommand("showbutton");
+    showHide.setSelected(true);
+    pb.add(Box.createRigidArea(new Dimension(20,10) ) );
+    pb.add(showHide);
+    add(pb);
+
+  }
+
+  private static synchronized int getNextInstance() { return instance++; }
+
+  public void setNetcdfGrid(NetcdfGrids n) {
+    try {
+      if (ref != null && di != null) di.removeReference(ref);
+    } catch (Exception rmrf) {rmrf.printStackTrace(); }
+    ncg = n;
+    if (isAloft) {
+      pressureLevels = ncg.getPressureLevels();
+      int num_levels = ncg.getNumberOfLevels();
+      range = new double[num_levels][2];
+      levelSlider.setMinimum(0);
+      levelSlider.setMaximum(num_levels-1);
+      levelSlider.setValue(0);
+      levelValue = 0;
+      intervalValue = 60.;
+      intervalText.setText("Enter value");
+    }
+    if (tabby != null) {
+       tabby.setTitleAt(myInstance, "Data");
+       tabby.setBackgroundAt(myInstance, nc);
+    }
+  }
+
+  public void setParams(Vector v) {
+    paramInfo = v;
+    paramBox.removeAllItems();
+    paramBox.addItem("-- none --");
+    for (int m=0; m<paramInfo.size(); m=m+3) {
+      String miName = (String) paramInfo.elementAt(m+1);
+      paramBox.addItem(miName);
+    }
+    revalidate();
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    //System.out.println("Action cmd:"+cmd);
+
+    if (cmd.equals("paramBox") ) {
+      paramName = (String) paramBox.getSelectedItem();
+      ndx = paramBox.getSelectedIndex();
+
+      if (paramName != null && ndx > 0) {
+
+      paramName = (String) paramInfo.elementAt( (ndx - 1)*3 );
+
+      Thread t = new Thread() {
+        public void run() {
+          try {
+            statLabel.setText("Reading data...please wait!");
+            tup = ncg.getGrids(paramName, Values, range);
+            statLabel.setText("Done reading data...");
+
+            di.disableAction();
+            FunctionType type =
+                    new FunctionType(enable, tup[levelValue].getType());
+            Integer1DSet set = new Integer1DSet(enable, 2);
+            field = new FieldImpl(type, set);
+            field.setSample(0, tup[levelValue]);
+            if (ref != null) di.removeReference(ref);
+            control = (ValueControl) map.getControl();
+            ref = new DataReferenceImpl(dataName);
+
+            valueMap.setRange(range[levelValue][0], range[levelValue][1]);
+            if (isAloft) {
+              intervalUnits.setText(
+                   (String) paramInfo.elementAt( (ndx-1)*3 +2) );
+              setContInterval(range[levelValue]);
+            }
+
+            ref.setData(field);
+            di.addReference(ref);
+            di.enableAction();
+            statLabel.setText("Rendering display...please wait!");
+            if (tabby != null) {
+              tabby.setTitleAt(myInstance, paramName);
+              tabby.setBackgroundAt(myInstance, nc);
+            }
+
+          } catch (Exception ep) {ep.printStackTrace() ;}
+        }
+      };
+
+      t.start();
+
+      }
+    } else if (cmd.equals("showbutton") ) {
+      try {
+        if (showHide.isSelected() ) {
+            control.setValue(0.0);
+        } else {
+            control.setValue(1.0);
+        }
+
+      } catch (Exception selb) {selb.printStackTrace(); }
+
+    } else if (cmd.equals("intervaltext") ) {
+      try {
+        String v = intervalText.getText();
+        intervalValue = Double.valueOf(v).doubleValue();
+        if (ci != null & ref != null)
+          ci.setContourInterval((float)intervalValue, (float)range[levelValue][0],
+                                (float)range[levelValue][1], (float) cbeg);
+          statLabel.setText("Rendering display...please wait!");
+
+      } catch (NumberFormatException nivt) {
+        statLabel.setText("Invalid number...try again!");
+        getToolkit().beep();
+
+      } catch (Exception ivt) {ivt.printStackTrace(); }
+
+    } else if (cmd.equals("butColor")) {
+      JColorChooser cc = new JColorChooser();
+      nc = cc.showDialog(this, "Choose contour color", Color.white);
+      if (nc != null) {
+        butColor.setBackground(nc);
+        if (tabby != null) tabby.setBackgroundAt(myInstance, nc);
+        try {
+          for (int i=0; i<256; i++) {
+            if (isAloft) {
+              colorTable[0][i] = (float) nc.getRed()/255.f;
+              colorTable[1][i] = (float) nc.getGreen()/255.f;
+              colorTable[2][i] = (float) nc.getBlue()/255.f;
+
+            } else {
+              colorTable[0][i] = ( (float) nc.getRed()/255.f)*(float)i/255.f;
+              colorTable[1][i] = ((float) nc.getGreen()/255.f)*(float)i/255.f;
+              colorTable[2][i] = ((float) nc.getBlue()/255.f)*(float)i/255.f;
+            }
+
+          }
+
+          ccon.setTable(colorTable);
+
+        } catch (Exception ce) {ce.printStackTrace(); }
+      }
+
+    }
+
+  }
+  public void stateChanged(ChangeEvent e) {
+    Object source = e.getSource();
+
+    if (source.equals(levelSlider) && ncg != null ){
+
+      int val = levelSlider.getValue();
+      if (val != levelValue)
+          levelSliderLabel.setText("Pressure level = "+pressureLevels[val]);
+      if (val != levelValue & !levelSlider.getValueIsAdjusting() ) {
+        levelValue = val;
+        if (ref != null) try {
+
+          // WLH 27 April 99
+          di.disableAction();
+
+          valueMap.setRange(range[levelValue][0], range[levelValue][1]);
+          setContInterval(range[levelValue]);
+          field.setSample(0, tup[levelValue]);
+
+          // WLH 27 April 99
+          di.enableAction();
+
+          statLabel.setText("Rendering display...please wait!");
+        } catch (Exception sl) {sl.printStackTrace();}
+
+      }
+
+    }
+  }
+
+  void setContInterval(double[] range) {
+    double cint = (range[1] - range[0]) *.11;
+    long lscal = (long) (Math.log(cint)/Math.log(10.0));
+    if (cint < 1.0) lscal = lscal - 1;
+    double cscal = Math.pow(10., (double)lscal);
+    cint = Math.floor(cint/cscal)*cscal;
+
+    cbeg = Math.floor(range[0]/cint)*cint;
+    try {
+      // System.out.println("range="+range[0]+" to "+range[1]+"  cbeg="+cbeg);
+      ci.setContourInterval((float)cint,
+                  (float)range[0], (float)range[1], (float)cbeg );
+
+      intervalText.setText(cint+" ");
+
+    } catch (Exception sci) {sci.printStackTrace();}
+    return ;
+  }
+
+
+}
diff --git a/visad/jmet/NetcdfGrids.java b/visad/jmet/NetcdfGrids.java
new file mode 100644
index 0000000..e47cb2f
--- /dev/null
+++ b/visad/jmet/NetcdfGrids.java
@@ -0,0 +1,811 @@
+
+//
+// NetcdfGrids.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.jmet;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.TreeMap;
+import java.util.Vector;
+
+import ucar.netcdf.Attribute;
+import ucar.netcdf.AttributeSet;
+import ucar.netcdf.DimensionIterator;
+import ucar.netcdf.DimensionSet;
+import ucar.netcdf.NetcdfFile;
+import ucar.netcdf.Variable;
+import ucar.netcdf.VariableIterator;
+import visad.CoordinateSystem;
+import visad.Data;
+import visad.DateTime;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Gridded1DDoubleSet;
+import visad.Integer2DSet;
+import visad.Real;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.Set;
+import visad.Tuple;
+import visad.Unit;
+import visad.data.units.Parser;
+
+/**
+ * Reads data from netCDF NCEP model grids one parameter at a time
+ * instead of the whole file (see Plain).  This is pretty focused on
+ * delivering grids that are renderable in 3D.
+ *
+ * @author Tom Whittaker
+ */
+public class NetcdfGrids {
+  File filename;
+  NetcdfFile nc = null;
+  double[] pressureLevels;
+  double[] valtime;
+  double[][] times;
+  Set time_set;
+  int[] timeIndex;
+  DateTime[] validDateTime;
+  int num_levels, num_records, xval, yval;
+  RealType x,y,level,time_type,pres;
+  RealTupleType grid_domain;
+  Integer2DSet dom_set;
+  CoordinateSystem gridCoord;
+  int gridNumber, gridTypeCode;
+
+  /** set up to read grids from a netCDF file
+  *
+  * @param filename is the name of the netCDF file to read from
+  *
+  */
+
+
+  public NetcdfGrids(String filename) {
+    this(new File(filename) );
+  }
+
+  public NetcdfGrids(File filename) {
+    this.filename = filename;
+    dom_set = null;
+    gridCoord = null;
+
+    try {
+      nc = new NetcdfFile(filename, true);
+      AttributeSet as = null;
+      DimensionSet ds = null;
+
+      // This block is just for me to look at the file...
+
+/* *****************************************************************
+
+      // global attributes
+      as = nc.getAttributes();
+      AttributeIterator ai = as.iterator();
+      int num = as.size();
+      System.out.println("number of attributes = "+num);
+      while (ai.hasNext() ) {
+        Attribute a = ai.next();
+        System.out.println("Attribute "+a);
+      }
+
+      // dimensions
+      ds = nc.getDimensions();
+      DimensionIterator di = ds.iterator();
+      int numDims = ds.size();
+      while (di.hasNext() ) {
+        ucar.netcdf.Dimension d = di.next();
+        System.out.println("Dimension "+d.getName()+" has length="+
+                d.getLength() );
+      }
+
+      // variables
+      VariableIterator vi = nc.iterator();
+      int numVars = nc.size();
+      while (vi.hasNext() ) {
+
+        Variable v = vi.next();
+        System.out.println("Variable "+v.getName()+
+              " has rank = "+v.getRank() );
+
+          as = v.getAttributes() ;
+          ai = as.iterator();
+          num = as.size();
+          System.out.println("   Number of attributes = "+num);
+          while (ai.hasNext() ) {
+            Attribute a = ai.next();
+            System.out.println("   Attribute "+a);
+          }
+
+          di = v.getDimensionIterator();
+          numDims = v.getRank();
+          while (di.hasNext() ) {
+            ucar.netcdf.Dimension d = di.next();
+            System.out.println("   Dimension "+d.getName()+" has length = "+
+                    d.getLength() );
+          }
+
+      }
+************************************************************* */
+
+      // get the pressure levels values
+
+      int index[] = new int[1];
+      Variable p = nc.get("level");
+      if (p != null) {
+        int temp[] = p.getLengths();
+        num_levels = temp[0];
+        pressureLevels = new double[num_levels];
+        for (int i=0; i<num_levels; i++) {
+          index[0] = i;
+          pressureLevels[i] = p.getDouble(index);
+        }
+      } else {
+        num_levels = 1;
+        pressureLevels = new double[num_levels];
+        pressureLevels[0] = 1020.0;
+      }
+
+
+      // get the number of records
+      num_records = (nc.getDimensions()).get("record").getLength();
+
+      // get the pressure levels values
+
+      Variable t = nc.get("valtime");
+      valtime = new double[num_records];
+      validDateTime = new DateTime[num_records];
+      timeIndex = new int[num_records];
+      times = new double[1][num_records];
+      TreeMap orderTimes = new TreeMap();
+
+      if (t != null) {
+	Unit baseTimeUnit =
+	    Parser.parse(t.getAttribute("units").getStringValue());
+        int temp[] = t.getLengths();
+        for (int i=0; i<num_records; i++) {
+          index[0] = i;
+          valtime[i] = t.getDouble(index);
+	  orderTimes.put(new Double(valtime[i]), new Integer(i));
+	  validDateTime[i] = new DateTime(new Real(RealType.Time,valtime[i],
+	    baseTimeUnit) );
+        }
+	//java.util.Set timeSet = orderTimes.keySet();
+	Iterator setIt = orderTimes.keySet().iterator();
+
+	for (int i=0; i<num_records; i++) {
+	  Double sv = (Double) setIt.next();
+	  Integer isv = (Integer) orderTimes.get(sv);
+	  timeIndex[i] = isv.intValue();
+	  times[0][i] = validDateTime[timeIndex[i]].getValue();
+	}
+
+      } else {
+	System.out.println("cannot find valtime-s...using linear steps");
+	valtime = null;
+	for (int i=0; i<num_records; i++) {
+	  timeIndex[i] = i;
+	  times[0][i] = (double)i;
+	}
+      }
+
+
+      p = nc.get("grid_number");
+      int index2[] = new int[p.getRank()];
+      for (int i=0; i<index2.length; i++) {index2[i] = 0; }
+
+      gridNumber = p.getInt(index2);
+      System.out.println("grid number = "+gridNumber);
+
+      p = nc.get("grid_type_code");
+      index[0] = 0;
+      gridTypeCode = p.getInt(index);
+      System.out.println("grid type code = "+gridTypeCode);
+
+    } catch (Exception ne) {ne.printStackTrace(); System.exit(1); }
+
+  }
+
+  /** set the RealTypes to use for coordinates (x,y,z,t) and pressure
+  *   values
+  *
+  * @param x X-coordinate
+  * @param y Y-coordinate
+  * @param level level dimension
+  * @param time_type forecast valid time
+  * @param pres MathType of vertical coordinate in display
+  */
+
+  public void setRealTypes(RealType x, RealType y, RealType level,
+    RealType time_type, RealType pres) {
+      this.x = x;
+      this.y = y;
+      this.level = level;
+      this.time_type = time_type;
+      this.pres = pres;
+
+      // define the VisAD type for (x,y) -> values
+
+      try {
+
+        Variable p = nc.get("Nx");
+        if (p == null) p = nc.get("Ni");
+
+        int[] index = new int[1];
+        index[0] = 0;
+        xval = p.getInt(index);
+
+        p = nc.get("Ny");
+        if (p == null) p = nc.get("Nj");
+        yval = p.getInt(index);
+
+        System.out.println("x,y dimensions = "+xval+" "+yval);
+
+        RealType[] domain_components = {x,y};
+
+        if (GRIBCoordinateSystem.isGridNumberKnown(gridNumber) ) {
+
+          gridCoord = new GRIBCoordinateSystem(gridNumber);
+
+        } else {
+
+          if (gridTypeCode == 0) {
+            int[] inx = new int[1];
+            inx[0] = 0;
+            p = nc.get("Ni");
+            int Ni = p.getInt(inx);
+            p = nc.get("Nj");
+            int Nj = p.getInt(inx);
+            p = nc.get("La1");
+            double La1 = p.getDouble(inx);
+            p = nc.get("Lo1");
+            double Lo1 = p.getDouble(inx);
+            p = nc.get("La2");
+            double La2 = p.getDouble(inx);
+            p = nc.get("Lo2");
+            double Lo2 = p.getDouble(inx);
+            p = nc.get("Di");
+            double Di = p.getDouble(inx);
+            p = nc.get("Dj");
+            double Dj = p.getDouble(inx);
+            gridCoord = new
+                 GRIBCoordinateSystem(0,Ni,Nj,La1,Lo1,La2,Lo2,Di,Dj);
+          }
+
+        }
+
+        grid_domain = new RealTupleType(domain_components, gridCoord, null);
+        dom_set = new Integer2DSet(grid_domain, xval, yval);
+        time_set = new Gridded1DDoubleSet(time_type, times, num_records);
+
+      } catch (Exception et) {et.printStackTrace(); }
+  }
+
+
+  /** fetch a list of 4D Variable names, long_names, and units
+  * for those parameters that have dimensions (x,y,level,record)
+  *
+  * @return names[0][k] is name; [1][k] is long_name, [2][k] is unit
+  *
+  */
+  public synchronized Vector get4dVariables() {
+    Vector retVec = new Vector();
+
+    VariableIterator vi = nc.iterator();
+    int numVars = nc.size();
+
+    for (int k=0; k<numVars; k++) {
+
+      Variable v = vi.next();
+      DimensionIterator di = v.getDimensionIterator();
+      int numDims = v.getRank();
+
+      if (numDims != 4) continue;
+
+      int[] dims = v.getLengths();
+
+      String dimNames[] = new String[numDims];
+      int xDim = -1;
+      int yDim = -1;
+      int levelDim = -1;
+      int recordDim = -1;
+
+      for (int i=0; i<numDims; i++) {
+        dimNames[i] = (di.next().getName()).trim();
+        if (dimNames[i] == "x" || dimNames[i] == "lon") xDim = i;
+        if (dimNames[i] == "y" || dimNames[i] == "lat") yDim = i;
+        if (dimNames[i] == "level") levelDim = i;
+        if (dimNames[i] == "record") recordDim = i;
+      }
+      if (xDim != -1 && yDim != -1 && levelDim != -1 && recordDim != -1) {
+        retVec.addElement( v.getName() );
+        retVec.addElement( v.getAttribute("long_name").getStringValue());
+        retVec.addElement( v.getAttribute("units").getStringValue());
+      }
+
+
+    }
+
+    return retVec;
+  }
+
+  /** fetch a list of 3D Variable names, long_names, and units
+  * for those parameters that have dimensions (x,y,level,record)
+  *
+  * @return names[0][k] is name; [1][k] is long_name, [2][k] is unit
+  *
+  */
+  public synchronized Vector get3dVariables() {
+    Vector retVec = new Vector();
+
+    VariableIterator vi = nc.iterator();
+    int numVars = nc.size();
+
+    for (int k=0; k<numVars; k++) {
+
+      Variable v = vi.next();
+      DimensionIterator di = v.getDimensionIterator();
+      int numDims = v.getRank();
+
+      if (numDims != 3) continue;
+
+      int[] dims = v.getLengths();
+
+      String dimNames[] = new String[numDims];
+      int xDim = -1;
+      int yDim = -1;
+      int recordDim = -1;
+
+      for (int i=0; i<numDims; i++) {
+        dimNames[i] = (di.next().getName()).trim();
+        if (dimNames[i] == "x" || dimNames[i] == "lon") xDim = i;
+        if (dimNames[i] == "y" || dimNames[i] == "lat") yDim = i;
+        if (dimNames[i] == "record") recordDim = i;
+      }
+      if (xDim != -1 && yDim != -1 && recordDim != -1) {
+        retVec.addElement( v.getName() );
+        retVec.addElement( v.getAttribute("long_name").getStringValue());
+        Attribute uni = v.getAttribute("units");
+        if (uni != null) {
+          retVec.addElement( uni.getStringValue());
+        } else {
+          retVec.addElement("null");
+        }
+      }
+
+
+    }
+
+    return retVec;
+  }
+
+
+  /** fetch a list of Variable names, long_names, and units
+  *
+  * @return names[0][k] is name; [1][k] is long_name, [2][k] is unit
+  *
+  */
+  public synchronized String[][] getVariableNames() {
+    VariableIterator vi = nc.iterator();
+    int numVars = nc.size();
+    String names[][] = new String[3][numVars];
+
+    for (int i=0; i<numVars; i++) {
+
+      Variable v = vi.next();
+      names[0][i] = v.getName();
+      names[1][i] = (v.getAttribute("long_name")).getStringValue();
+      names[2][i] = (v.getAttribute("units")).getStringValue();
+    }
+
+    return names;
+  }
+
+  public double getAspect() {
+    if (gridCoord != null) {
+      return ( ( (GRIBCoordinateSystem)gridCoord).getAspectRatio());
+    } else {
+      return (1.0);
+    }
+  }
+
+  public int getNumberOfLevels() {
+    return(num_levels);
+  }
+  public double[] getPressureLevels() {
+    return(pressureLevels);
+  }
+
+  public int getNumberOfTimes() {
+    return(num_records);
+  }
+
+  public java.awt.Dimension getDimension() {
+    return (new java.awt.Dimension(xval, yval) );
+  }
+
+  public Integer2DSet getDomainSet() {
+    return dom_set;
+  }
+
+
+  /** fetch the grids for one parameter
+  * @param name the name of the parameter
+  * @param values the RealType associated with name
+  *
+  * @return Tuple[2] where Tuple[0] is the vertical coordinate
+  *         value and Tuple[1] is the FlatField(s) of data
+  *
+  */
+  public synchronized Tuple[] getGrids(String name, RealType values,
+    double[][] range) {
+
+    // find the named variable
+    Variable v = nc.get(name);
+    if (v == null) {
+      System.out.println("could not find "+v+" in netCDF file");
+      System.exit(1);
+    }
+
+    // get long_name and units attributes
+    Attribute long_name = v.getAttribute("long_name");
+    Attribute units = v.getAttribute("units");
+    Attribute fillvalue = v.getAttribute("_FillValue");
+    double fill = fillvalue.getNumericValue().doubleValue();
+
+    // now get the dimensions, and look for "x", "y", "level" and "record"
+    // these are the key dimension names
+
+    DimensionIterator di = v.getDimensionIterator();
+    int numDims = v.getRank();
+    int[] dims = v.getLengths();
+
+    String dimNames[] = new String[numDims];
+    int xDim = -1;
+    int yDim = -1;
+    int levelDim = -1;
+    int recordDim = -1;
+
+    // locate the dimensions and record their values...
+
+    for (int i=0; i<numDims; i++) {
+      dimNames[i] = (di.next().getName()).trim();
+      if (dimNames[i] == "x" || dimNames[i] == "lon") xDim = i;
+      if (dimNames[i] == "y" || dimNames[i] == "lat") yDim = i;
+      if (dimNames[i] == "level") levelDim = i;
+      if (dimNames[i] == "record") recordDim = i;
+    }
+
+    // we need to accomodate 'ls_bndy' and the like as well...
+
+    if (xDim == -1 || yDim == -1 ) {
+      System.out.println("one or more x-y dimensions missing");
+      System.exit(1);
+    }
+
+    int index[] = new int[numDims];
+
+    Tuple tup[] = null;
+
+    try {
+
+      FunctionType grid_type = new FunctionType(grid_domain, values);
+
+      // now make a function time record -> (x,y) -> values
+      FunctionType withTime_type = new FunctionType(time_type, grid_type);
+
+      // make a Tuple to hold the (level, withTime) values for each level
+      tup = new Tuple[num_levels];
+
+      int gridDim = dims[xDim] * dims[yDim];
+
+      double grids[][] = new double[1][gridDim];
+      Data griddata[] = new Data[2];
+
+      if (levelDim != -1 && recordDim != -1) {
+        for (int p=0; p<num_levels; p++) {
+          index[levelDim] = p;
+          range[p][0] = Double.MAX_VALUE;
+          // WLH 2 May 2000
+          // range[p][1] = Double.MIN_VALUE;
+          range[p][1] = -Double.MAX_VALUE;
+
+          FieldImpl withTime = new FieldImpl(withTime_type, time_set);
+
+          for (int t=0; t<num_records; t++) {
+	    index[recordDim] = timeIndex[t];
+
+            for (int i=0; i<dims[yDim]; i++) {
+              index[yDim] = i;
+                for (int j=0; j<dims[xDim]; j++) {
+                  index[xDim] = j;
+                  double value = v.getDouble(index);
+                  if (value == fill) {
+                    value = Double.NaN;
+                  } else {
+                    if (value < range[p][0]) range[p][0] = value;
+                    if (value > range[p][1]) range[p][1] = value;
+                  }
+
+                  grids[0][i*dims[xDim] + j] = value;
+                }
+            }
+
+            FlatField nffg = new FlatField(grid_type, dom_set);
+            nffg.setSamples(grids, false);
+            withTime.setSample(t, nffg);
+          }
+
+          griddata[0] = new visad.Real(pres, pressureLevels[p] );
+          griddata[1] = withTime;
+          tup[p] = new Tuple(griddata);
+        }
+
+      } else if (recordDim != -1) {
+          range[0][0] = Double.MAX_VALUE;
+          // WLH 2 May 2000
+          // range[0][1] = Double.MIN_VALUE;
+          range[0][1] = -Double.MAX_VALUE;
+
+          FieldImpl withTime = new FieldImpl(withTime_type, time_set);
+
+          for (int t=0; t<num_records; t++) {
+	    index[recordDim] = timeIndex[t];
+
+            for (int i=0; i<dims[yDim]; i++) {
+              index[yDim] = i;
+                for (int j=0; j<dims[xDim]; j++) {
+                  index[xDim] = j;
+                  double value = v.getDouble(index);
+                  if (value == fill) {
+                    value = Double.NaN;
+                  } else {
+                    if (value < range[0][0]) range[0][0] = value;
+                    if (value > range[0][1]) range[0][1] = value;
+                  }
+                  grids[0][i*dims[xDim] + j] = value;
+                }
+            }
+
+            FlatField nffg = new FlatField(grid_type, dom_set);
+            nffg.setSamples(grids, false);
+            withTime.setSample(t, nffg);
+          }
+
+          griddata[0] = new visad.Real(pres, 1020. );
+          griddata[1] = withTime;
+          tup[0] = new Tuple(griddata);
+
+      } else {
+            for (int i=0; i<dims[yDim]; i++) {
+              index[yDim] = i;
+                for (int j=0; j<dims[xDim]; j++) {
+                  index[xDim] = j;
+                  double value = v.getDouble(index);
+                  if (value == fill) {
+                    value = Double.NaN;
+                  } else {
+                    if (value < range[0][0]) range[0][0] = value;
+                    if (value > range[0][1]) range[0][1] = value;
+                  }
+                  grids[0][i*dims[xDim] + j] = value;
+                }
+            }
+
+          FlatField nffg = new FlatField(grid_type, dom_set);
+          nffg.setSamples(grids, false);
+
+          griddata[0] = new visad.Real(pres, 1020. );
+          griddata[1] = nffg;
+          tup[0] = new Tuple(griddata);
+      }
+
+    } catch (Exception ve) {ve.printStackTrace(); System.exit(1);}
+
+
+    return tup;
+
+  }
+
+  /** Test routine: java NetcdfGrids <name of netCDF file>
+  */
+
+  public static void main(String args[]) {
+
+    try {
+    NetcdfGrids ng = new NetcdfGrids("97032712_eta.nc");
+    RealType x = RealType.getRealType("x");
+    RealType y = RealType.getRealType("y");
+    RealType level = RealType.getRealType("level");
+    RealType time_type = RealType.Time;
+    RealType pres = RealType.getRealType("pres");
+    RealType Z = RealType.getRealType("Z");
+    int num_levels = ng.getNumberOfLevels();
+    double [][] range = new double[num_levels][2];
+
+    ng.setRealTypes(x,y,level,time_type,pres);
+    Tuple d[] = ng.getGrids("Z", Z, range);
+    System.out.println("range = "+range[0][0]+" - "+range[0][1]);
+
+    } catch (Exception me) {me.printStackTrace(); System.exit(1); }
+
+  }
+
+  /** fetches grids are returns a Tuple [level][record] of FlatFields
+  *
+  * this was implemented to test the offset between mapping to
+  * animator or just doing one at a time...
+  *
+  */
+
+  public synchronized Tuple[][] getGridsWithTime(String name, RealType values,
+  double[][] range) {
+
+    // find the named variable
+    Variable v = nc.get(name);
+    if (v == null) {
+      System.out.println("could not find "+v+" in netCDF file");
+      System.exit(1);
+    }
+
+    // get long_name and units attributes
+    Attribute long_name = v.getAttribute("long_name");
+    Attribute units = v.getAttribute("units");
+    Attribute fillvalue = v.getAttribute("_FillValue");
+
+    // now get the dimensions, and look for "x", "y", "level" and "record"
+    // these are the key dimension names
+    // allow "lat" to be used for "y" and "lon" in place of "x"...sad
+
+    DimensionIterator di = v.getDimensionIterator();
+    int numDims = v.getRank();
+    int[] dims = v.getLengths();
+
+    String dimNames[] = new String[numDims];
+    int xDim = -1;
+    int yDim = -1;
+    int levelDim = -1;
+    int recordDim = -1;
+
+    for (int i=0; i<numDims; i++) {
+      dimNames[i] = (di.next().getName()).trim();
+      if (dimNames[i] == "x") xDim = i;
+      if (dimNames[i] == "y") yDim = i;
+      if (dimNames[i] == "level") levelDim = i;
+      if (dimNames[i] == "record") recordDim = i;
+    }
+
+    // we need to accomodate 'ls_bndy' and the like as well...
+
+    if (xDim == -1 || yDim == -1 ) {
+      System.out.println("one or more x-y dimensions missing");
+      System.exit(1);
+    }
+
+    int index[] = new int[numDims];
+
+    Tuple tup[][] = null;
+
+    try {
+
+      // define the VisAD type for (x,y) -> values
+
+      RealType[] domain_components = {x,y};
+
+      gridCoord = new GRIBCoordinateSystem(gridNumber);
+
+      RealTupleType grid_domain =
+          new RealTupleType(domain_components, gridCoord, null);
+
+      dom_set = new Integer2DSet(grid_domain,
+                                      dims[xDim], dims[yDim]);
+
+      FunctionType grid_type = new FunctionType(grid_domain, values);
+
+      // make a Tuple to hold the (level, withTime) values for each level
+
+      tup = new Tuple[num_levels][num_records];
+
+      int gridDim = dims[xDim] * dims[yDim];
+
+      double grids[][] = new double[1][gridDim];
+      Data griddata[] = new Data[2];
+
+      if (levelDim != -1 && recordDim != -1) {
+        for (int p=0; p<num_levels; p++) {
+          index[levelDim] = p;
+
+          for (int t=0; t<num_records; t++) {
+	    index[recordDim] = timeIndex[t];
+
+            for (int i=0; i<dims[yDim]; i++) {
+              index[yDim] = i;
+                for (int j=0; j<dims[xDim]; j++) {
+                  index[xDim] = j;
+                  grids[0][i*dims[xDim] + j] = v.getDouble(index);
+                }
+            }
+
+            FlatField nffg = new FlatField(grid_type, dom_set);
+            nffg.setSamples(grids, false);
+
+            griddata[0] = new visad.Real(pres, pressureLevels[p] );
+            griddata[1] = nffg;
+            tup[p][t] = new Tuple(griddata);
+          }
+        }
+
+      } else if (recordDim != -1) {
+
+          for (int t=0; t<num_records; t++) {
+	    index[recordDim] = timeIndex[t];
+
+            for (int i=0; i<dims[yDim]; i++) {
+              index[yDim] = i;
+                for (int j=0; j<dims[xDim]; j++) {
+                  index[xDim] = j;
+                  grids[0][i*dims[xDim] + j] = v.getDouble(index);
+                  //grids[0][j*dims[yDim] + i] = v.getDouble(index);
+                }
+            }
+
+            FlatField nffg = new FlatField(grid_type, dom_set);
+            nffg.setSamples(grids, false);
+
+            griddata[0] = new visad.Real(pres, 1020. );
+            griddata[1] = nffg;
+            tup[0][t] = new Tuple(griddata);
+          }
+
+      } else {
+            for (int i=0; i<dims[yDim]; i++) {
+              index[yDim] = i;
+                for (int j=0; j<dims[xDim]; j++) {
+                  index[xDim] = j;
+                  grids[0][i*dims[xDim] + j] = v.getDouble(index);
+                  //grids[0][j*dims[yDim] + i] = v.getDouble(index);
+                }
+            }
+
+          FlatField nffg = new FlatField(grid_type, dom_set);
+          nffg.setSamples(grids, false);
+
+          griddata[0] = new visad.Real(pres, 1020. );
+          griddata[1] = nffg;
+
+          for (int t=0; t<num_records; t++) {
+            tup[0][t] = new Tuple(griddata);
+          }
+      }
+
+    } catch (Exception ve) {ve.printStackTrace(); System.exit(1);}
+
+    return tup;
+
+  }
+
+
+}
diff --git a/visad/jmet/ShowNCEPModel.java b/visad/jmet/ShowNCEPModel.java
new file mode 100644
index 0000000..0c1bc13
--- /dev/null
+++ b/visad/jmet/ShowNCEPModel.java
@@ -0,0 +1,683 @@
+//
+// ShowNCEPModel.java
+//
+
+/*
+
+The software in this file is Copyright(C) 2011 by Tom Whittaker.
+It is designed to be used with the VisAD system for interactive
+analysis and visualization of numerical data.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.jmet;
+
+import visad.java3d.*;
+
+import visad.*;
+import visad.util.*;
+import visad.VisADException;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.*;
+
+import java.io.*;
+
+import visad.data.mcidas.*;
+
+import java.rmi.RemoteException;
+
+public class ShowNCEPModel
+       extends JFrame implements ActionListener, ChangeListener,
+       DisplayListener {
+
+  private BaseMapAdapter baseMap;
+  private ColorControl ccmap;
+  private float[][] colorTable;
+  private Container cf;
+  private JSlider speedSlider;
+  private JLabel speedSliderLabel;
+  private int speedValue, frameValue;
+  private int maxFrames;
+  private JButton start_stop, snapButton, forward, backward, mapColor;
+  private boolean isLooping;
+  private ContourControl ci;
+  private ProjectionControl pc;
+  private AnimationControl ca;
+  private double[] pcMatrix;
+  private GraphicsModeControl gmc;
+  private LocalDisplay display;
+  private NetcdfGrids ng;
+  private JPanel vdisplay;
+  private RealType x,y,level,time_type,pres;
+  private RealType Values, SfcValues;
+  private boolean firstFile;
+  private boolean gotSfcGrids, gotAloftGrids;
+  private JLabel statLabel;
+  private String cmd;
+  private NCEPPanel[] ncp;
+  private JTabbedPane tabby;
+  private FieldImpl mapField;
+  private ValueControl mapControl;
+  private DataReference mapRef;
+  private RealType enableMap;
+  private ScalarMap mapMap, xAxis, yAxis;
+  private JCheckBox showMap;
+  private String directory;
+  private String MapFile;
+
+  private boolean isServer = false;
+  private boolean isClient = false;
+  private String clientHost = null;
+
+  public static void main(String args[]) {
+
+    int num=1;
+    boolean srvr = false;
+    boolean clnt = false;
+    String fileName = null;
+    String host = null;
+    if (args != null && args.length > 0) {
+      boolean killMe = false;
+      boolean gotNum = false;
+      for (int i = 0; i < args.length; i++) {
+        if (args[i].charAt(0) == '-' && args[i].length() > 1) {
+          switch (args[i].charAt(1)) {
+          case 'c':
+            if (srvr) {
+              System.out.println("Cannot specify both '-c' and '-s'");
+              killMe = true;
+            }
+            clnt = true;
+            if (args[i].length() > 2) host = args[i].substring(2).trim();
+            break;
+          case 's':
+            if (clnt) {
+              System.out.println("Cannot specify both '-c' and '-s'");
+              killMe = true;
+            }
+            srvr = true;
+            break;
+          default:
+            System.out.println("Unknown argument \"" + args[i] + "\"");
+            killMe = true;
+          }
+        } else {
+          boolean triedNum = false;
+          if (!gotNum) {
+            try {
+              num = Integer.parseInt(args[i]);
+              triedNum = true;
+            } catch (NumberFormatException nex) {
+            }
+
+            if (triedNum) {
+              if (num >= 1 && num <= 9) {
+                gotNum = true;
+              } else {
+                System.out.println("invalid number of tabs (1-9) = "+num);
+                killMe = true;
+              }
+              continue;
+            }
+          }
+
+          if (fileName == null) {
+            fileName = args[i];
+          } else {
+            System.out.println("Ignoring extra argument \"" + args[i] + "\"");
+          }
+        }
+      }
+
+      if (killMe) {
+        System.out.println("Usage: ShowNCEPModel [-c|-s] # [fileName]");
+        System.out.println("Usage: ShowNCEPModel [-chostname|-s] # [fileName]");
+        System.exit(1);
+      }
+    }
+
+    try {
+      new ShowNCEPModel(num, srvr, clnt, host, fileName);
+    } catch (Exception e) {
+      e.printStackTrace(System.out);
+      System.exit(1);
+    }
+  }
+
+  public ShowNCEPModel(int numPanels)
+    throws RemoteException, VisADException
+  {
+    this(numPanels, false, false, null, null);
+  }
+
+  public ShowNCEPModel(int numPanels, boolean srvr, boolean clnt)
+    throws RemoteException, VisADException
+  {
+    this(numPanels, srvr, clnt, null, null);
+  }
+
+  public ShowNCEPModel (int numPanels, String fileName)
+    throws RemoteException, VisADException
+  {
+    this(numPanels, false, false, null, fileName);
+  }
+
+  public ShowNCEPModel(int numPanels, boolean srvr, boolean clnt,
+                       String host, String fileName)
+    throws RemoteException, VisADException
+  {
+    super("Show NCEP Model Data");
+
+    isServer = srvr;
+    isClient = clnt;
+    clientHost = host;
+    if (clientHost == null) clientHost = "localhost";
+
+    addWindowListener( new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0); }
+    } );
+
+    frameValue = 0;
+    maxFrames = -1;
+    firstFile = true;
+    gotSfcGrids = false;
+    gotAloftGrids = false;
+    directory = ".";
+    ng = null;
+
+    //MapFile = "../data/mcidas/OUTLAUST";
+    MapFile = "../data/mcidas/OUTLUSAM";
+
+    String serviceName = getClass().getName();
+
+    if (!isClient) {
+      DisplayImpl di = buildData(numPanels);
+      if (isServer) {
+        RemoteServerImpl server = ClientServer.startServer(serviceName);
+
+        server.addDisplay(new RemoteDisplayImpl(di));
+      }
+      display = di;
+    } else {
+      RemoteServer client;
+      client = ClientServer.connectToServer(clientHost, serviceName);
+
+      LocalDisplay[] lh = ClientServer.getClientDisplays(client);
+      display = lh[0];
+    }
+
+    buildUI();
+
+    if (fileName != null) {
+      try { setLooping(false); } catch (Throwable t) { }
+      getNewFile(".", fileName);
+    }
+  }
+
+  private DisplayImpl buildData(int numPanels)
+    throws RemoteException, VisADException
+  {
+    // define the VisAD mappings for the Data and Display
+    DisplayImpl di = new DisplayImplJ3D("display1");
+    di.addDisplayListener(this);
+    pc = di.getProjectionControl();
+
+    // make the cube the size of the window...for 'snapping'
+    pcMatrix = pc.getMatrix();
+    pcMatrix[0] = .95;
+    pcMatrix[5] = .95;
+    pcMatrix[10] = .95;
+    pc.setMatrix(pcMatrix);
+
+    x = RealType.getRealType("x");
+    y = RealType.getRealType("y");
+    level = RealType.getRealType("level");
+    time_type = RealType.Time;
+    //time_type = RealType.getRealType("Valid_time", CommonUnit.secondsSinceTheEpoch);
+    pres = RealType.getRealType("pres");
+
+    gmc = di.getGraphicsModeControl();
+    gmc.setProjectionPolicy(0);
+
+    xAxis = new ScalarMap(x, Display.XAxis);
+    yAxis = new ScalarMap(y, Display.YAxis);
+    di.addMap(xAxis);
+    di.addMap(yAxis);
+    ScalarMap lvl = new ScalarMap(pres, Display.ZAxis);
+    lvl.setRange(1020., 10.);
+    di.addMap(lvl);
+
+    ScalarMap ani = new ScalarMap(time_type, Display.Animation);
+    di.addMap(ani);
+    ca = (AnimationControl) ani.getControl();
+    ca.setOn(false);
+
+    statLabel = new JLabel("Please choose a data file...");
+    statLabel.setForeground(Color.black);
+
+    // make the ncep Panel(s) here because they do 'addMaps'
+
+    tabby = new JTabbedPane();
+
+    ncp = new NCEPPanel[numPanels+1];
+
+    ncp[0] = new NCEPPanel(false, di, statLabel, tabby,  "Single-level Data");
+    for (int i=1; i<ncp.length; i++) {
+     ncp[i] = new NCEPPanel(true, di, statLabel, tabby,  "Data Aloft");
+    }
+
+    enableMap = RealType.getRealType("enableMap");
+    mapMap = new ScalarMap(enableMap, Display.SelectValue);
+    di.addMap(mapMap);
+    ScalarMap scalarMapColor = new ScalarMap(enableMap, Display.RGB);
+    di.addMap(scalarMapColor);
+
+    ccmap = (ColorControl) (scalarMapColor.getControl() );
+    colorTable = new float[3][256];
+    for (int i=0; i<256; i++) {
+      colorTable[0][i] = .6f;
+      colorTable[1][i] = 0.f;
+      colorTable[2][i] = .6f;
+    }
+    ccmap.setTable(colorTable);
+
+    return di;
+  }
+
+  private void buildMenuBar()
+  {
+    JMenuBar mb = new JMenuBar();
+    JMenu fileMenu = new JMenu("File");
+
+    JMenuItem menuFile = new JMenuItem("Open File");
+    menuFile.addActionListener(this);
+    menuFile.setActionCommand("menuFile");
+    fileMenu.add(menuFile);
+
+    JMenuItem menuQuit = new JMenuItem("Exit");
+    menuQuit.addActionListener(this);
+    menuQuit.setActionCommand("menuQuit");
+    fileMenu.add(menuQuit);
+
+    ButtonGroup menuSelectors = new ButtonGroup();
+
+    JMenu mapMenu = new JMenu("Map");
+    JRadioButtonMenuItem menuNA = new JRadioButtonMenuItem("North America",true);
+    menuNA.addActionListener(this);
+    menuNA.setActionCommand("menuNA");
+    mapMenu.add(menuNA);
+    menuSelectors.add(menuNA);
+
+    JRadioButtonMenuItem menuWorld = new JRadioButtonMenuItem("World",false);
+    menuWorld.addActionListener(this);
+    menuWorld.setActionCommand("menuWorld");
+    mapMenu.add(menuWorld);
+    menuSelectors.add(menuWorld);
+
+    mb.add(fileMenu);
+    mb.add(mapMenu);
+    setJMenuBar(mb);
+  }
+
+  private Component buildMapControls()
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS) );
+    panel.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+    panel.add(mapColor);
+    panel.add(Box.createRigidArea(new Dimension(10,10) ) );
+    panel.add(showMap);
+
+    return panel;
+  }
+
+  private Component buildSpeedControl()
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS) );
+    panel.setAlignmentX(Component.CENTER_ALIGNMENT);
+    panel.setAlignmentY(Component.CENTER_ALIGNMENT);
+
+    speedSlider = new JSlider(JSlider.HORIZONTAL,1,20,10);
+    speedSlider.setMaximumSize(speedSlider.getPreferredSize() );
+    speedSlider.setAlignmentX(Component.CENTER_ALIGNMENT);
+    speedSlider.addChangeListener(this);
+
+    speedValue = 3;
+
+    panel.add(new JLabel("Speed"));
+    panel.add(speedSlider);
+
+    return panel;
+  }
+
+  private Component buildAnimationControls()
+  {
+    backward = new JButton(" < ");
+    backward.setAlignmentX(Component.CENTER_ALIGNMENT);
+    backward.setMaximumSize(backward.getMaximumSize() );
+    backward.setMinimumSize(backward.getMaximumSize() );
+    backward.setPreferredSize(backward.getMaximumSize() );
+    backward.addActionListener(this);
+    backward.setActionCommand("backward");
+
+    start_stop = new JButton("Animate");
+    start_stop.setAlignmentX(Component.CENTER_ALIGNMENT);
+    start_stop.setMaximumSize(start_stop.getMaximumSize() );
+    start_stop.setMinimumSize(start_stop.getMaximumSize() );
+    start_stop.setPreferredSize(start_stop.getMaximumSize() );
+    start_stop.addActionListener(this);
+    start_stop.setActionCommand("start_stop");
+
+    isLooping = false;
+
+    forward = new JButton(" > ");
+    forward.setAlignmentX(Component.CENTER_ALIGNMENT);
+    forward.setMaximumSize(forward.getMaximumSize() );
+    forward.setMinimumSize(forward.getMaximumSize() );
+    forward.setPreferredSize(forward.getMaximumSize() );
+    forward.addActionListener(this);
+    forward.setActionCommand("forward");
+
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS) );
+    panel.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+    panel.add(backward);
+    panel.add(start_stop);
+    panel.add(forward);
+    panel.add(buildSpeedControl());
+
+    return panel;
+  }
+
+  private Component buildControlPanel()
+  {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS) );
+    panel.setAlignmentX(Component.CENTER_ALIGNMENT);
+    panel.setAlignmentY(Component.TOP_ALIGNMENT);
+    panel.setMaximumSize(new Dimension(300,Short.MAX_VALUE) );
+    panel.setMinimumSize(new Dimension(300,400) );
+    panel.setPreferredSize(new Dimension(300,400) );
+
+    mapColor = new JButton("Map Color");
+    mapColor.addActionListener(this);
+    mapColor.setActionCommand("mapColor");
+    mapColor.setBackground(new Color(154,0,154));
+
+    showMap = new JCheckBox("Make map visible");
+    showMap.setAlignmentX(Component.CENTER_ALIGNMENT);
+    showMap.addActionListener(this);
+    showMap.setActionCommand("showmap");
+    showMap.setSelected(true);
+
+    snapButton = new JButton("Snap!");
+    snapButton.setAlignmentX(Component.CENTER_ALIGNMENT);
+    snapButton.setMaximumSize(snapButton.getMaximumSize() );
+    snapButton.setMinimumSize(snapButton.getMaximumSize() );
+    snapButton.setPreferredSize(snapButton.getMaximumSize() );
+    snapButton.addActionListener(this);
+    snapButton.setActionCommand("snapButton");
+
+    panel.add(Box.createRigidArea(new Dimension(10,10) ) );
+    panel.add(snapButton);
+    panel.add(Box.createRigidArea(new Dimension(10,10) ) );
+
+    panel.add(buildMapControls());
+    panel.add(buildAnimationControls());
+
+    for (int i=0; i<ncp.length; i++) {
+      tabby.addTab("Data", ncp[i]);
+    }
+
+    panel.add(Box.createRigidArea(new Dimension(10,30) ) );
+    panel.add(tabby);
+
+    panel.add(Box.createVerticalGlue() );
+    panel.add(Box.createRigidArea(new Dimension(10,10) ) );
+    statLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+    panel.add(statLabel);
+
+    return panel;
+  }
+
+  private void buildUI()
+  {
+    buildMenuBar();
+
+    vdisplay = (JPanel) display.getComponent();
+    vdisplay.setPreferredSize(new Dimension(700,700) );
+    vdisplay.setAlignmentX(Component.LEFT_ALIGNMENT);
+    vdisplay.setAlignmentY(Component.TOP_ALIGNMENT);
+
+    cf = getContentPane();
+    cf.setLayout(new BoxLayout(cf, BoxLayout.X_AXIS) );
+
+    if (!isClient) {
+      cf.add(buildControlPanel());
+    }
+    cf.add(vdisplay);
+    setSize(1024,768);
+    setVisible(true);
+  }
+
+  public void actionPerformed(ActionEvent e) {
+    cmd = e.getActionCommand();
+    //System.out.println("Action command: "+cmd);
+
+    if (cmd.equals("menuFile") ) {
+      try {
+        setLooping(false);
+      } catch (Exception mfs) {mfs.printStackTrace(); System.exit(1); }
+
+      FileDialog fileBox = new FileDialog(this);
+      fileBox.setDirectory(directory);
+      fileBox.setMode(FileDialog.LOAD);
+      fileBox.setVisible(true);
+
+      getNewFile(fileBox.getDirectory(), fileBox.getFile());
+
+    } else if (cmd.equals("menuQuit") ) {
+      System.exit(0);
+
+    } else if (cmd.equals("menuNA") ) {
+      MapFile = "../data/mcidas/OUTLUSAM";
+      doBaseMap();
+
+    } else if (cmd.equals("menuWorld") ) {
+      MapFile = "../data/mcidas/OUTLSUPW";
+      doBaseMap();
+
+    } else if (cmd.startsWith("Param:") ) {
+
+       System.out.println("not implemented");
+
+    } else if (cmd.equals("start_stop") ) {
+
+      try {
+        setLooping(!isLooping);
+      } catch (Exception sse) {sse.printStackTrace(); System.exit(1); }
+
+    } else if (cmd.equals("backward")) {
+      try {
+        ca.setDirection(false);
+        ca.takeStep();
+      } catch (Exception eb) {eb.printStackTrace(); }
+
+    } else if (cmd.equals("forward")) {
+      try {
+        ca.setDirection(true);
+        ca.takeStep();
+      } catch (Exception eb) {eb.printStackTrace(); }
+
+    } else if (cmd.equals("snapButton") ) {
+
+      try {
+        pc.setMatrix(pcMatrix);
+      } catch (Exception sse) {sse.printStackTrace(); System.exit(1); }
+
+    } else if (cmd.equals("mapColor")) {
+
+      JColorChooser cc = new JColorChooser();
+      Color nc = cc.showDialog(this, "Choose contour color", Color.white);
+      if (nc != null) {
+        mapColor.setBackground(nc);
+        try {
+          for (int i=0; i<256; i++) {
+            colorTable[0][i] = (float) nc.getRed()/255.f;
+            colorTable[1][i] = (float) nc.getGreen()/255.f;
+            colorTable[2][i] = (float) nc.getBlue()/255.f;
+          }
+          ccmap.setTable(colorTable);
+
+       }  catch (Exception mce) {mce.printStackTrace(); }
+     }
+
+    } else if (cmd.equals("showmap") ) {
+      try {
+        if (showMap.isSelected() ) {
+          mapControl.setValue(0.0);
+        } else {
+          mapControl.setValue(1.0);
+        }
+      } catch (Exception mcon) {mcon.printStackTrace(); }
+
+    }
+
+  }
+
+  public void displayChanged(DisplayEvent e) {
+
+    if (e.getId() == DisplayEvent.TRANSFORM_DONE) {
+      statLabel.setText("Display Frame done...");
+    }
+  }
+
+
+  public void stateChanged(ChangeEvent e) {
+    Object source = e.getSource();
+
+    if (source.equals(speedSlider) ) {
+
+      int val = (speedSlider.getValue()) ;
+      if (val != speedValue ) {
+        speedValue = val;
+               try {
+          ca.setStep(50*(21 - speedValue) );
+        } catch (Exception slis) {slis.printStackTrace(); System.exit(1);}
+      }
+    }
+
+  }
+
+  void getNewFile(String directory, String filename) {
+
+    if (filename == null || directory == null) return;
+
+    try {
+
+      File file = new File(directory, filename);
+
+      // remove the DataReference for new files...
+
+      ng = new NetcdfGrids(file);
+
+      setTitle("Show NCEP Model Data from "+filename);
+
+      ng.setRealTypes(x,y,level,time_type,pres);
+
+      Dimension dim = ng.getDimension();
+      xAxis.setRange(0, dim.width-1);
+      yAxis.setRange(0, dim.height-1);
+
+      // set up aspect ratio for square-ness
+      double aspect = ng.getAspect();
+      aspect = aspect * ( (double)dim.width/(double)dim.height) ;
+      if (aspect >= 1.0) {
+        pcMatrix[0] = .95;
+        pcMatrix[5] = .95/aspect;
+      } else {
+        pcMatrix[0] = .95 * aspect;
+        pcMatrix[5] = .95;
+      }
+
+      pc.setMatrix(pcMatrix);
+
+      for (int i=0; i<ncp.length; i++) {
+        ncp[i].setNetcdfGrid(ng);
+      }
+
+      maxFrames = ng.getNumberOfTimes() - 1;
+
+      Vector vSfc = ng.get3dVariables();
+      ncp[0].setParams(vSfc);
+
+      Vector v = ng.get4dVariables();
+      for (int i=1; i<ncp.length; i++) {
+       ncp[i].setParams(v);
+      }
+
+      // can do map now, since grid geometry is known...
+
+      statLabel.setText("Rendering base map...");
+      doBaseMap();
+
+    } catch (Exception op) {op.printStackTrace(); System.exit(1); }
+
+  }
+  private void doBaseMap() {
+    if (ng == null) return;
+    try {
+      baseMap = new BaseMapAdapter(MapFile);
+      baseMap.setDomainSet((Linear2DSet)ng.getDomainSet() );
+      Data mapData = baseMap.getData();
+
+      // set up so map can be toggled on/off
+
+      FunctionType mapType =
+        new FunctionType(enableMap, mapData.getType() );
+      Integer1DSet mapSet= new Integer1DSet(enableMap, 2);
+      mapField = new FieldImpl(mapType, mapSet);
+      mapField.setSample(0,mapData);
+      mapControl = (ValueControl) mapMap.getControl();
+      if (mapRef != null) display.removeReference(mapRef);
+      mapRef = new DataReferenceImpl("mapData");
+      mapRef.setData(mapField);
+      ConstantMap[] rendMap;
+      rendMap = new ConstantMap[1];
+      rendMap[0] = new ConstantMap(-.99, Display.ZAxis);
+      display.addReference(mapRef, rendMap);
+      //display.addReference(mapRef);
+      mapControl.setValue(0.0);
+
+    } catch (Exception mapop) {mapop.printStackTrace(); System.exit(1); }
+  }
+
+  private void setLooping(boolean on)
+    throws RemoteException, VisADException
+  {
+    ca.setOn(on);
+    start_stop.setText(on ? "Stop" : "Animate");
+    isLooping = on;
+  }
+}
diff --git a/visad/math/FFT.java b/visad/math/FFT.java
new file mode 100644
index 0000000..b3faacf
--- /dev/null
+++ b/visad/math/FFT.java
@@ -0,0 +1,799 @@
+//
+// FFT.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.math;
+
+import visad.*;
+
+import java.rmi.*;
+
+/**
+ FFT is the VisAD class for Fourier Transforms, using
+ the Fast Fourier Transform when the domain length is
+ a power of two.<p>
+*/
+
+public class FFT {
+
+  /** 
+   * for use by SpreadSheet only - ordinary applications
+   * should use other method signatures;
+   * invoke in SpreadSheet by:
+   * link(visad.math.FFT.forwardFT(A1))
+  */
+  public static FlatField forwardFT(Data[] datums) {
+    FlatField result = null;
+    try {
+      result = fourierTransform((Field) datums[0], true);
+    }
+    catch (VisADException e) {
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      e.printStackTrace();
+    }
+    if (result == null) {
+      System.out.println("result == null");
+    }
+    return result;
+  }
+
+  /**
+   * for use by SpreadSheet only - ordinary applications
+   * should use other method signatures;
+   * invoke in SpreadSheet by:
+   * link(visad.math.FFT.backwardFT(A1))
+  */
+  public static FlatField backwardFT(Data[] datums) {
+    FlatField result = null;
+    try {
+      result = fourierTransform((Field) datums[0], false);
+    }
+    catch (VisADException e) {
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      e.printStackTrace();
+    }
+    if (result == null) {
+      System.out.println("result == null");
+    }
+    return result;
+  }
+
+  /**
+   * return Fourier Transform of field, use FFT if domain dimension(s)
+   * are powers of 2
+   * @param field Field with domain dimension = 1 (1-D FT) or 2 (2-D FT)
+   *              and 1 (real part) or 2 (real & imaginary) range RealTypes
+   * @param forward true for forward and false for backward
+   * @return Fourier transform of field
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public static FlatField fourierTransform(Field field, boolean forward)
+         throws VisADException, RemoteException {
+    return fourierTransform(field, forward, null, null, null, null, null);
+  }
+
+  /**
+   * return Fourier Transform of field, use FFT if domain dimension(s)
+   * are powers of 2
+   * @param field Field with domain dimension = 1 (1-D FT) or 2 (2-D FT)
+   *              and 1 (real part) or 2 (real & imaginary) range RealTypes
+   * @param forward true for forward and false for backward
+   * @param ftype use for return Field (may be null)
+   * @param domain_set use for return Field (may be null)
+   * @param range_coord_sys use for return Field (may be null)
+   * @param range_sets use for return Field (may be null)
+   * @param units use for return Field (may be null)
+   * @return Fourier transform of field
+   * @throws VisADException  a VisAD error occurred
+   * @throws RemoteException  an RMI error occurred
+   */
+  public static FlatField fourierTransform(Field field, boolean forward,
+                     FunctionType ftype, GriddedSet domain_set,
+                     CoordinateSystem range_coord_sys,
+                     Set[] range_sets, Unit[] units)
+         throws VisADException, RemoteException {
+    if (field == null) return null;
+    FunctionType type = (FunctionType) field.getType();
+    RealTupleType dtype = type.getDomain();
+    int ddim = dtype.getDimension();
+    MathType rtype = type.getRange();
+    RealType[] realComponents = null;
+    int rdim = 0;
+    if (rtype instanceof RealType) {
+      realComponents = new RealType[] {(RealType) rtype};
+      rdim = 1;
+    }
+    else if (rtype instanceof RealTupleType) {
+      realComponents = ((RealTupleType) rtype).getRealComponents();
+      rdim = realComponents.length;
+    }
+    else {
+      throw new FieldException("bad range type " + rtype);
+    }
+    if (ddim != 1 && ddim != 2) {
+      throw new FieldException("bad domain dimension " + dtype);
+    }
+    if (rdim != 1 && rdim != 2) {
+      throw new FieldException("bad range dimension " + rtype);
+    }
+
+    if (units == null || units.length == 0) {
+      units = new Unit[] {null, null};
+    }
+    else if (units.length == 1) {
+      units = new Unit[] {units[0], null};
+    }
+
+    if (ftype == null) {
+      RealType[] newReals = new RealType[2];
+      if (rdim == 2) {
+        if (forward) {
+          String name = realComponents[0].getName() + "_FT";
+          newReals[0] = RealType.getRealType(name, units[0], null);
+          name = realComponents[1].getName() + "_FT";
+          newReals[1] = RealType.getRealType(name, units[1], null);
+        }
+        else {
+          String name = realComponents[0].getName();
+          if (name.endsWith("_FT")) {
+            name = name.substring(0, name.length() - 3);
+          }
+          else if (name.endsWith("_FT_real")) { 
+            name = name.substring(0, name.length() - 8);
+          }
+          else if (name.endsWith("_RFT_real")) { 
+            name = name.substring(0, name.length() - 9);
+          }
+          newReals[0] = RealType.getRealType(name, units[0], null);
+          name = realComponents[1].getName();
+          if (name.endsWith("_FT")) {
+            name = name.substring(0, name.length() - 3);
+          }
+          else if (name.endsWith("_FT_imag")) { 
+            name = name.substring(0, name.length() - 8) + "_imag";
+          }
+          else if (name.endsWith("_RFT_imag")) { 
+            name = name.substring(0, name.length() - 9) + "_imag";
+          }
+          newReals[1] = RealType.getRealType(name, units[1], null);
+        }
+      }
+      else { // rdim == 1
+        if (forward) {
+          String name = realComponents[0].getName() + "_FT";
+          newReals[0] = RealType.getRealType(name + "_real", units[0], null);
+          newReals[1] = RealType.getRealType(name + "_imag", units[1], null);
+        }
+        else {
+          String name = realComponents[0].getName() + "_RFT";
+          newReals[0] = RealType.getRealType(name + "_real", units[0], null);
+          newReals[1] = RealType.getRealType(name + "_imag", units[1], null);
+        }
+      }
+      RealTupleType rftype = new RealTupleType(newReals, range_coord_sys, null);
+      ftype = new FunctionType(dtype, rftype);
+    }
+    else { // ftype != null
+      RealTupleType dftype = ftype.getDomain();
+      if (dftype.getDimension() != ddim) {
+        throw new FieldException("bad domain dimension " + dftype);
+      }
+      MathType rftype = ftype.getRange();
+      if (!(rftype instanceof RealTupleType) ||
+          ((RealTupleType) rftype).getDimension() != 2) {
+        throw new FieldException("bad range type " + rftype);
+      }
+    }
+
+    Set field_set = field.getDomainSet();
+    if (!(field_set instanceof GriddedSet)) {
+      throw new FieldException("field domain set must be Gridded2DSet " + field_set);
+    }
+
+    int[] field_lens = ((GriddedSet) field_set).getLengths();
+    if (domain_set == null) {
+      domain_set = (GriddedSet) field_set;
+    }
+    else {
+      if (domain_set.getDimension() != ddim) {
+        throw new FieldException("domain_set bad dimension " + domain_set);
+      }
+      int[] domain_lens = domain_set.getLengths();
+      for (int i=0; i<ddim; i++) {
+        if (field_lens[i] != domain_lens[i]) {
+          throw new FieldException("domain_set size must match field domain " +
+                                   "set " + domain_set + "\n" + field_set);
+        }
+      }
+    }
+
+    boolean use_double = true;
+    if (field instanceof FlatField) {
+      use_double = false;
+      Set[] fsets = ((FlatField) field).getRangeSets();
+      for (int i=0; i<fsets.length; i++) {
+        if (fsets[i] instanceof DoubleSet) use_double = true;
+      }
+    }
+    boolean doub = false;
+    if (range_sets != null) {
+      if (range_sets.length != 2) {
+        throw new FieldException("bad range_sets length" + range_sets.length);
+      }
+      for (int i=0; i<2; i++) {
+        if (range_sets[i] instanceof DoubleSet) doub = true;
+      }
+    }
+    use_double = use_double && doub;
+
+    FlatField new_field = new FlatField(ftype, domain_set, range_coord_sys,
+                                        null, range_sets, units);
+    if (use_double) {
+      double[][] values = field.getValues(false);
+      if (values.length == 1) {
+        int n = values[0].length;
+        double[][] new_values = new double[2][n];
+        System.arraycopy(values[0], 0, new_values[0], 0, n);
+        for (int i=0; i<n; i++) new_values[1][i] = 0.0;
+        values = new_values;
+      }
+      if (ddim == 1) {
+        values = FT1D(values, forward);
+      }
+      else { // ddim == 2
+        values = FT2D(field_lens[0], field_lens[1], values, forward);
+      }
+      new_field.setSamples(values, false);
+    }
+    else { // !use_double
+      float[][] values = field.getFloats(false);
+      if (values.length == 1) {
+        int n = values[0].length;
+        float[][] new_values = new float[2][n];
+        System.arraycopy(values[0], 0, new_values[0], 0, n);
+        for (int i=0; i<n; i++) new_values[1][i] = 0.0f;
+        values = new_values;
+      }
+      if (ddim == 1) {
+        values = FT1D(values, forward);
+      }
+      else { // ddim == 2
+        values = FT2D(field_lens[0], field_lens[1], values, forward);
+      }
+      new_field.setSamples(values, false);
+    }
+    return new_field;
+  }
+
+
+  /**
+   * compute 2-D Fourier transform, calling 1-D FT twice
+   * use FFT if rows and cols are powers of 2
+   * @param rows first dimension for 2-D
+   * @param cols second dimension for 2-D
+   * @param x array for take Fourier transform of, dimensioned
+   *          [2][length] where length = rows * cols, and the
+   *          first index (2) is over real & imaginary parts
+   * @param forward true for forward and false for backward
+   * @return Fourier transform of x
+   * @throws VisADException  a VisAD error occurred
+   */
+  public static float[][] FT2D(int rows, int cols, float[][] x,
+                                boolean forward)
+         throws VisADException {
+    if (x == null) return null;
+    if (x.length != 2 || x[0].length != x[1].length) {
+      throw new FieldException("bad x lengths");
+    }
+    int n = x[0].length;
+    if (rows * cols != n) {
+      throw new FieldException(rows + " * " + cols + " must equal " + n);
+    }
+    float[][] y = new float[2][n];
+    float[][] z = new float[2][rows];
+    for (int c=0; c<cols; c++) {
+      int i = c * rows;
+      for (int r=0; r<rows; r++) {
+        z[0][r] = x[0][i + r];
+        z[1][r] = x[1][i + r];
+      }
+      z = FT1D(z, forward);
+      for (int r=0; r<rows; r++) {
+        y[0][i + r] = z[0][r];
+        y[1][i + r] = z[1][r];
+      }
+    }
+
+    float[][] u = new float[2][n];
+    float[][] v = new float[2][cols];
+    for (int r=0; r<rows; r++) {
+      int i = r;
+      for (int c=0; c<cols; c++) {
+        v[0][c] = y[0][i + c * rows];
+        v[1][c] = y[1][i + c * rows];
+      }
+      v = FT1D(v, forward);
+      for (int c=0; c<cols; c++) {
+        u[0][i + c * rows] = v[0][c];
+        u[1][i + c * rows] = v[1][c];
+      }
+    }
+    return u;
+  }
+
+  /**
+   * compute 2-D Fourier transform, calling 1-D FT twice
+   * use FFT if rows and cols are powers of 2
+   * @param rows first dimension for 2-D
+   * @param cols second dimension for 2-D
+   * @param x array for take Fourier transform of, dimensioned
+   *          [2][length] where length = rows * cols, and the
+   *          first index (2) is over real & imaginary parts
+   * @param forward true for forward and false for backward
+   * @return Fourier transform of x
+   * @throws VisADException  a VisAD error occurred
+   */
+  public static double[][] FT2D(int rows, int cols, double[][] x,
+                                boolean forward)
+         throws VisADException {
+    if (x == null) return null;
+    if (x.length != 2 || x[0].length != x[1].length) {
+      throw new FieldException("bad x lengths");
+    }
+    int n = x[0].length;
+    if (rows * cols != n) {
+      throw new FieldException(rows + " * " + cols + " must equal " + n);
+    }
+    double[][] y = new double[2][n];
+    double[][] z = new double[2][rows];
+    for (int c=0; c<cols; c++) {
+      int i = c * rows;
+      for (int r=0; r<rows; r++) {
+        z[0][r] = x[0][i + r];
+        z[1][r] = x[1][i + r];
+      }
+      z = FT1D(z, forward);
+      for (int r=0; r<rows; r++) {
+        y[0][i + r] = z[0][r];
+        y[1][i + r] = z[1][r];
+      }
+    }
+
+    double[][] u = new double[2][n];
+    double[][] v = new double[2][cols];
+    for (int r=0; r<rows; r++) {
+      int i = r;
+      for (int c=0; c<cols; c++) {
+        v[0][c] = y[0][i + c * rows];
+        v[1][c] = y[1][i + c * rows];
+      }
+      v = FT1D(v, forward);
+      for (int c=0; c<cols; c++) {
+        u[0][i + c * rows] = v[0][c];
+        u[1][i + c * rows] = v[1][c];
+      }
+    }
+    return u;
+  }
+
+  /**
+   * compute 1-D Fourier transform
+   * use FFT if length (2nd dimension of x) is a power of 2
+   * @param x array for take Fourier transform of, dimensioned
+   *          [2][length], the first index (2) is over real &
+   *          imaginary parts
+   * @param forward true for forward and false for backward
+   * @return Fourier transform of x
+   * @throws VisADException  a VisAD error occurred
+   */
+  public static float[][] FT1D(float[][] x, boolean forward)
+         throws VisADException {
+    if (x == null) return null;
+    if (x.length != 2 || x[0].length != x[1].length) {
+      throw new FieldException("bad x lengths");
+    }
+    int n = x[0].length;
+    int n2 = 1;
+    boolean fft = true;
+    while (n2 < n) {
+      n2 *= 2;
+      if (n2 > n) {
+        fft = false;
+      }
+    }
+    if (fft) return FFT1D(x, forward);
+
+    float[][] temp = new float[2][n];
+    float angle = (float) (-2.0 * Math.PI / n);
+    if (!forward) angle = -angle;
+    for (int i=0; i<n; i++) {
+      temp[0][i] = (float) Math.cos(i * angle);
+      temp[1][i] = (float) Math.sin(i * angle);
+    }
+    float[][] y = new float[2][n];
+    for (int i=0; i<n; i++) {
+      float re = 0.0f;
+      float im = 0.0f;
+      for (int j=0; j<n; j++) {
+        int m = (i * j) % n;
+        re += x[0][j] * temp[0][m] - x[1][j] * temp[1][m];
+        im += x[0][j] * temp[1][m] + x[1][j] * temp[0][m];
+      }
+      y[0][i] = re;
+      y[1][i] = im;
+    }
+    if (!forward) {
+      for(int i=0; i<n; i++) {
+        y[0][i] /= n;
+        y[1][i] /= n;
+      }
+    }
+    return y;
+  }
+
+  /**
+   * compute 1-D FFT transform
+   * length (2nd dimension of x) must be a power of 2
+   * @param x array for take Fourier transform of, dimensioned
+   *          [2][length], the first index (2) is over real &
+   *          imaginary parts
+   * @param forward true for forward and false for backward
+   * @return Fourier transform of x
+   * @throws VisADException  a VisAD error occurred
+   */
+  public static float[][] FFT1D(float[][] x, boolean forward)
+         throws VisADException {
+    if (x == null) return null;
+    if (x.length != 2 || x[0].length != x[1].length) {
+      throw new FieldException("bad x lengths");
+    }
+    int n = x[0].length;
+    int n2 = 1;
+    while (n2 < n) {
+      n2 *= 2;
+      if (n2 > n) {
+        throw new FieldException("x length must be power of 2");
+      }
+    }
+    n2 = n/2;
+    float[][] temp = new float[2][n2];
+    float angle = (float) (-2.0 * Math.PI / n);
+    if (!forward) angle = -angle;
+    for (int i=0; i<n2; i++) { 
+      temp[0][i] = (float) Math.cos(i * angle);
+      temp[1][i] = (float) Math.sin(i * angle);
+    }
+    float[][] y = FFT1D(x, temp);
+    if (!forward) {
+      for(int i=0; i<n; i++) {
+        y[0][i] /= n;
+        y[1][i] /= n; 
+      }
+    }
+    return y; 
+  }
+
+  /** inner function for 1-D Fast Fourier Transform */
+  private static float[][] FFT1D(float[][] x, float[][] temp) {
+    int n = x[0].length;
+    int n2 = n/2;
+    int k=0;
+    int butterfly;
+    int buttered=0; 
+    if (n==1) {
+      float[][] z1 = {{x[0][0]}, {x[1][0]}};
+      return z1;
+    }
+
+    butterfly= (temp[0].length/n2);
+
+    float[][] z = new float[2][n2];
+    float[][] w = new float[2][n2];
+
+    for (k=0; k<n/2; k++) {  
+      int k2 = 2*k;
+      z[0][k] = x[0][k2];
+      z[1][k] = x[1][k2];
+      w[0][k] = x[0][k2 + 1];
+      w[1][k] = x[1][k2 + 1];
+    }
+
+    z = FFT1D(z, temp);
+    w = FFT1D(w, temp);
+
+    float[][] y = new float[2][n];
+    for (k=0; k<n2;k++) {
+      y[0][k] = z[0][k];
+      y[1][k] = z[1][k];
+
+      float re = w[0][k] * temp[0][buttered] - w[1][k] * temp[1][buttered];
+      float im = w[0][k] * temp[1][buttered] + w[1][k] * temp[0][buttered];
+      w[0][k] = re;
+      w[1][k] = im;
+
+      y[0][k] += w[0][k];
+      y[1][k] += w[1][k];
+      y[0][k + n2] = z[0][k] - w[0][k];
+      y[1][k + n2] = z[1][k] - w[1][k];
+      buttered += butterfly;
+    }
+    return y;
+  }
+
+  /**
+   * compute 1-D Fourier transform
+   * use FFT if length (2nd dimension of x) is a power of 2
+   * @param x array for take Fourier transform of, dimensioned
+   *          [2][length], the first index (2) is over real &
+   *          imaginary parts
+   * @param forward true for forward and false for backward
+   * @return Fourier transform of x
+   * @throws VisADException  a VisAD error occurred
+   */
+  public static double[][] FT1D(double[][] x, boolean forward)
+         throws VisADException {
+    if (x == null) return null;
+    if (x.length != 2 || x[0].length != x[1].length) {
+      throw new FieldException("bad x lengths");
+    }
+    int n = x[0].length;
+    int n2 = 1;
+    boolean fft = true;
+    while (n2 < n) {
+      n2 *= 2;
+      if (n2 > n) {
+        fft = false;
+      }
+    }
+    if (fft) return FFT1D(x, forward);
+
+    double[][] temp = new double[2][n];
+    double angle = -2.0 * Math.PI / n;
+    if (!forward) angle = -angle;
+    for (int i=0; i<n; i++) {
+      temp[0][i] = Math.cos(i * angle);
+      temp[1][i] = Math.sin(i * angle);
+    }
+    double[][] y = new double[2][n];
+    for (int i=0; i<n; i++) {
+      double re = 0.0;
+      double im = 0.0;
+      for (int j=0; j<n; j++) {
+        int m = (i * j) % n;
+        re += x[0][j] * temp[0][m] - x[1][j] * temp[1][m];
+        im += x[0][j] * temp[1][m] + x[1][j] * temp[0][m];
+      }
+      y[0][i] = re;
+      y[1][i] = im;
+    }
+    if (!forward) {
+      for(int i=0; i<n; i++) {
+        y[0][i] /= n;
+        y[1][i] /= n;
+      }
+    }
+    return y;
+  }
+
+  /**
+   * compute 1-D FFT transform
+   * length (2nd dimension of x) must be a power of 2
+   * @param x array for take Fourier transform of, dimensioned
+   *          [2][length], the first index (2) is over real &
+   *          imaginary parts
+   * @param forward true for forward and false for backward
+   * @return Fourier transform of x
+   * @throws VisADException  a VisAD error occurred
+   */
+  public static double[][] FFT1D(double[][] x, boolean forward)
+         throws VisADException {
+    if (x == null) return null;
+    if (x.length != 2 || x[0].length != x[1].length) {
+      throw new FieldException("bad x lengths");
+    }
+    int n = x[0].length;
+    int n2 = 1;
+    while (n2 < n) {
+      n2 *= 2;
+      if (n2 > n) {
+        throw new FieldException("x length must be power of 2");
+      }
+    }
+    n2 = n/2;
+    double[][] temp = new double[2][n2];
+    double angle = (double) (-2.0 * Math.PI / n);
+    if (!forward) angle = -angle;
+    for (int i=0; i<n2; i++) {
+      temp[0][i] = (double) Math.cos(i * angle);
+      temp[1][i] = (double) Math.sin(i * angle);
+    }
+    double[][] y = FFT1D(x, temp);
+    if (!forward) {
+      for(int i=0; i<n; i++) {
+        y[0][i] /= n;
+        y[1][i] /= n; 
+      }
+    }
+    return y; 
+  }
+
+  /** inner function for 1-D Fast Fourier Transform */
+  private static double[][] FFT1D(double[][] x, double[][] temp) {
+    int n = x[0].length;
+    int n2 = n/2;
+    int k=0;
+    int butterfly;
+    int buttered=0; 
+    if (n==1) {
+      double[][] z1 = {{x[0][0]}, {x[1][0]}};
+      return z1;
+    }
+
+    butterfly= (temp[0].length/n2);
+
+    double[][] z = new double[2][n2];
+    double[][] w = new double[2][n2];
+
+    for (k=0; k<n2; k++) {
+      int k2 = 2 * k;
+      z[0][k] = x[0][k2];
+      z[1][k] = x[1][k2];
+      w[0][k] = x[0][k2 + 1];
+      w[1][k] = x[1][k2 + 1];
+    }
+
+    z = FFT1D(z, temp);
+    w = FFT1D(w, temp);
+
+    double[][] y = new double[2][n];
+    for (k=0; k<n2;k++) {
+      y[0][k] = z[0][k];
+      y[1][k] = z[1][k];
+
+      double re = w[0][k] * temp[0][buttered] - w[1][k] * temp[1][buttered];
+      double im = w[0][k] * temp[1][buttered] + w[1][k] * temp[0][buttered];
+      w[0][k] = re;
+      w[1][k] = im;
+
+      y[0][k] += w[0][k];
+      y[1][k] += w[1][k];
+      y[0][k + n2] = z[0][k] - w[0][k];
+      y[1][k + n2] = z[1][k] - w[1][k];
+      buttered += butterfly;
+    }
+    return y;
+  }
+
+  /** test Fourier Transform methods */
+  public static void main(String args[])
+         throws VisADException {
+    int n = 16;
+    int rows = 1, cols = 1;
+    boolean twod = false;
+
+/*
+    if (args.length > 0 && args[0].startsWith("AREA")) {
+      DisplayImpl display1 = new DisplayImplJ3D("display");
+      DisplayImpl display1 = new DisplayImplJ3D("display");
+      AreaAdapter areaAdapter = new AreaAdapter(args[0]);
+      Data image = areaAdapter.getData();
+      FunctionType imageFunctionType = (FunctionType) image.getType();
+      RealType radianceType = (RealType)
+        ((RealTupleType) imageFunctionType.getRange()).getComponent(0);
+      display.addMap(new ScalarMap(RealType.Latitude, Display.Latitude));
+      display.addMap(new ScalarMap(RealType.Longitude, Display.Longitude));
+      ScalarMap rgbMap = new ScalarMap(radianceType, Display.RGB);
+      display.addMap(rgbMap);
+
+    }
+*/
+
+    if (args.length > 0) n = Integer.valueOf(args[0]).intValue();
+    if (args.length > 1) {
+      rows = Integer.valueOf(args[0]).intValue();
+      cols = Integer.valueOf(args[1]).intValue();
+      n = rows * cols;
+      twod = true;
+    }
+
+    float[][] x = new float[2][n];
+    // double[][] x = new double[2][n];
+    System.out.println("  initial values");
+    if (twod) {
+      int i = 0;
+      for (int c=0; c<cols; c++) {
+        for (int r=0; r<rows; r++) {
+          x[0][i] = (float) (Math.sin(2 * Math.PI * r / rows) *
+                             Math.sin(2 * Math.PI * c / cols));
+          x[1][i] = 0.0f;
+          // x[0][i] = (Math.sin(2 * Math.PI * r / rows) *
+          //            Math.sin(2 * Math.PI * c / cols));
+          // x[1][i] = 0.0;
+          System.out.println("x[" + r + "][" + c + "] = " +
+                             PlotText.shortString(x[0][i]) + " " + 
+                             PlotText.shortString(x[1][i]));
+          i++;
+        }
+      }
+    }
+    else {
+      for (int i=0; i<n; i++) {
+        x[0][i] = (float) Math.sin(2 * Math.PI * i / n);
+        x[1][i] = 0.0f;
+        // x[0][i] = Math.sin(2 * Math.PI * i / n);
+        // x[1][i] = 0.0;
+        System.out.println("x[" + i + "] = " +
+                           PlotText.shortString(x[0][i]) + " " +
+                           PlotText.shortString(x[1][i]));
+      }
+    }
+    x = twod ? FT2D(rows, cols, x, true) : FT1D(x, true);
+    System.out.println("\n  fft");
+    if (twod) {
+      int i = 0;
+      for (int c=0; c<cols; c++) {
+        for (int r=0; r<rows; r++) {
+          System.out.println("x[" + r + "][" + c + "] = " +
+                             PlotText.shortString(x[0][i]) + " " + 
+                             PlotText.shortString(x[1][i]));
+          i++;
+        }
+      }
+    }
+    else {
+      for (int i=0; i<n; i++) {
+        System.out.println("x[" + i + "] = " +
+                           PlotText.shortString(x[0][i]) + " " +
+                           PlotText.shortString(x[1][i]));
+      }
+    }
+    x = twod ? FT2D(rows, cols, x, false) : FT1D(x, false);
+    System.out.println("\n  back fft");
+    if (twod) {
+      int i = 0;
+      for (int c=0; c<cols; c++) {
+        for (int r=0; r<rows; r++) {
+          System.out.println("x[" + r + "][" + c + "] = " +
+                             PlotText.shortString(x[0][i]) + " " + 
+                             PlotText.shortString(x[1][i]));
+          i++;
+        }
+      }
+    }
+    else {
+      for (int i=0; i<n; i++) {
+        System.out.println("x[" + i + "] = " +
+                           PlotText.shortString(x[0][i]) + " " +
+                           PlotText.shortString(x[1][i]));
+      }
+    }
+  }
+}
+
diff --git a/visad/math/Histogram.java b/visad/math/Histogram.java
new file mode 100644
index 0000000..04b8104
--- /dev/null
+++ b/visad/math/Histogram.java
@@ -0,0 +1,185 @@
+//
+// Histogram.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.math;
+
+import visad.*;
+import visad.java3d.*;
+
+import java.rmi.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ Histogram is the VisAD class for creating histograms of Field
+ values.<p>
+*/
+
+public class Histogram {
+
+  /** invoke in SpreadSheet by:
+      link(visad.math.Histogram.makeHistogram(A1, A2))
+  */
+  public static FlatField makeHistogram(Data[] datums) {
+    FlatField result = null;
+    try {
+      if (datums == null || datums.length != 2) {
+        throw new VisADException("bad arguments");
+      }
+      if (!(datums[0] instanceof Field)) {
+        throw new VisADException("first argument must be a Field");
+      }
+      if (!(datums[1] instanceof Set)) {
+        throw new VisADException("second argument must be a Set");
+      }
+      result = makeHistogram((Field) datums[0], (Set) datums[1]);
+    }
+    catch (VisADException e) {
+      e.printStackTrace();
+    }
+    catch (RemoteException e) {
+      e.printStackTrace();
+    }
+    if (result == null) {
+      System.out.println("result == null");
+    }
+    return result;
+  }
+
+  /** return a histogram of field range values in "bins"
+      defined by the samples of set */
+  public static FlatField makeHistogram(Field field, Set set)
+         throws VisADException, RemoteException {
+    FunctionType ftype = (FunctionType) field.getType();
+    RealType[] frealComponents = ftype.getRealComponents();
+
+    RealTupleType stype = ((SetType) set.getType()).getDomain();
+    RealType[] srealComponents = stype.getRealComponents();
+
+    RealType count = RealType.getRealType("count");
+    FunctionType htype = new FunctionType(stype, count);
+
+    int dim = srealComponents.length;
+    float[][] field_values = field.getFloats(false);
+    float[][] set_values = new float[dim][];
+    for (int i=0; i<dim; i++) {
+      for (int j=0; j<frealComponents.length; j++) {
+        if (srealComponents[i].equals(frealComponents[j])) {
+          set_values[i] = field_values[j];
+          break;
+        }
+      }
+      if (set_values[i] == null) {
+        throw new TypeException("set component " + srealComponents[i] +
+                                " does not occur in " + ftype);
+      }
+    }
+    int[] indices = set.valueToIndex(set_values);
+    int len = set.getLength();
+    float[][] hist_values = new float[1][len];
+    for (int i=0; i<len; i++) hist_values[0][i] = 0.0f;
+    for (int j=0; j<indices.length; j++) {
+      if (indices[j] >= 0) hist_values[0][indices[j]]++;
+    }
+
+    FlatField result = new FlatField(htype, set);
+    result.setSamples(hist_values, false);
+    return result;
+  }
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+
+    int SIZE = 64;
+
+    RealType X = RealType.getRealType("X");
+    RealType Y = RealType.getRealType("Y");
+
+    RealType A = RealType.getRealType("A");
+    RealType B = RealType.getRealType("B");
+
+    RealType[] domain2d = {X, Y};
+    RealTupleType Domain2d = new RealTupleType(domain2d);
+    Integer2DSet Domain2dSet = new Integer2DSet(Domain2d, SIZE, SIZE);
+
+    RealType[] range2d = {A, B};
+    RealTupleType Range2d = new RealTupleType(range2d);
+
+    FunctionType Field2d2 = new FunctionType(Domain2d, Range2d);
+
+    FlatField image = new FlatField(Field2d2, Domain2dSet);
+    int len = Domain2dSet.getLength();
+    int ADD = len * len / 16;
+    float[][] values = new float[2][len];
+    for (int i=0; i<len; i++) {
+      values[0][i] = (float) i;
+      values[1][i] = (float) ((i * i + ADD) * Math.random());
+    }
+    image.setSamples(values, false);
+
+    Linear2DSet histSet =
+      new Linear2DSet(Range2d, 0.5 * SIZE, len - 0.5 * SIZE, len / SIZE,
+           0.5 * SIZE * len, len * len + ADD - 0.5 * SIZE * len, len / SIZE);
+
+    FlatField hist = Histogram.makeHistogram(image, histSet);
+
+    RealType count = RealType.getRealType("count");
+
+    DisplayImplJ3D display1 = new DisplayImplJ3D("display1");
+    display1.addMap(new ScalarMap(A, Display.XAxis));
+    display1.addMap(new ScalarMap(B, Display.YAxis));
+    if (args.length == 0) display1.addMap(new ScalarMap(count, Display.ZAxis));
+    display1.addMap(new ScalarMap(count, Display.RGB));
+
+    GraphicsModeControl gmc = display1.getGraphicsModeControl();
+    gmc.setScaleEnable(true);
+    // gmc.setTextureEnable(false);
+    gmc.setCurvedSize(1);
+
+    DataReference hist_ref = new DataReferenceImpl("hist_ref");
+    hist_ref.setData(hist);
+    display1.addReference(hist_ref);
+
+    JFrame frame = new JFrame("VisAD HSV Color Coordinates");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    frame.getContentPane().add(display1.getComponent());
+
+    int WIDTH = 500;
+    int HEIGHT = 600;
+
+    frame.setSize(WIDTH, HEIGHT);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+    frame.setVisible(true);
+  }
+
+}
+
diff --git a/visad/matrix/JamaCholeskyDecomposition.java b/visad/matrix/JamaCholeskyDecomposition.java
new file mode 100644
index 0000000..80ab110
--- /dev/null
+++ b/visad/matrix/JamaCholeskyDecomposition.java
@@ -0,0 +1,214 @@
+//
+// JamaCholeskyDecomposition.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.matrix;
+
+import java.lang.reflect.*;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+/**
+ * JamaCholeskyDecomposition is a VisAD wrapper for JAMA CholeskyDecompositions.
+ * This class requires the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA package</a>.
+ */
+public class JamaCholeskyDecomposition extends Tuple {
+
+  private static final RealType cholesky_row =
+    RealType.getRealType("choleskyL_row");
+
+  private static final RealType cholesky_column =
+    RealType.getRealType("choleskyL_column");
+
+  private static final RealType cholesky_value =
+    RealType.getRealType("choleskyL_value");
+
+  private static final FunctionType choleskyLType = constructFunction();
+
+  private static FunctionType constructFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(cholesky_row, cholesky_column);
+      FunctionType function = new FunctionType(tuple, cholesky_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final Class[] classes = constructClasses();
+
+  private static Class[] constructClasses() {
+    Class[] cs = new Class[6];
+    try {
+      cs[0] = Class.forName("Jama.Matrix");
+      cs[1] = Class.forName("Jama.CholeskyDecomposition");
+      cs[2] = Class.forName("Jama.EigenvalueDecomposition");
+      cs[3] = Class.forName("Jama.LUDecomposition");
+      cs[4] = Class.forName("Jama.QRDecomposition");
+      cs[5] = Class.forName("Jama.SingularValueDecomposition");
+    }
+    catch (ClassNotFoundException e) {
+      throw new RuntimeException("you need to install Jama from " +
+                                 "http://math.nist.gov/javanumerics/jama/");
+    }
+    return cs;
+  }
+
+  private static final Class classMatrix = classes[0];
+  private static final Class classCholeskyDecomposition = classes[1];
+  private static final Class classEigenvalueDecomposition = classes[2];
+  private static final Class classLUDecomposition = classes[3];
+  private static final Class classQRDecomposition = classes[4];
+  private static final Class classSingularValueDecomposition = classes[5];
+
+  /** associated JAMA CholeskyDecomposition object */
+  private Object cd;
+
+  /** useful methods from Jama.CholeskyDecomposition class */
+  private static final Method[] methods =
+    constructMethods();
+
+  private static Method[] constructMethods() {
+    Method[] ms = new Method[3];
+    try {
+      Class[] param = new Class[] {};
+      ms[0] = classCholeskyDecomposition.getMethod("getL", param);
+      ms[1] = classCholeskyDecomposition.getMethod("isSPD", param);
+      param = new Class[] {classMatrix};
+      ms[2] = classCholeskyDecomposition.getMethod("solve", param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+    }
+    return ms;
+  }
+
+  private static final Method getL = methods[0];
+  private static final Method isSPD = methods[1];
+  private static final Method solve = methods[2];
+
+  private static final Constructor matrixCholeskyDecomposition =
+    constructConstructor();
+
+  private static Constructor constructConstructor() {
+    try {
+      Class[] param = new Class[] {classMatrix};
+      return classCholeskyDecomposition.getConstructor(param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  // Constructors
+
+  /**
+   * Construct a new JamaCholeskyDecomposition from a JamaMatrix.
+   */
+  public JamaCholeskyDecomposition(JamaMatrix matrix)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrixCholeskyDecomposition.newInstance(new Object[] {matrix.getMatrix()}),
+         false);
+  }
+
+  JamaCholeskyDecomposition(Object c, boolean copy)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    super(makeDatums(c), copy);
+    cd = ((JamaMatrix) getComponent(0)).getStash();
+  }
+
+  private static Data[] makeDatums(Object c)
+          throws VisADException, IllegalAccessException,
+                 InstantiationException, InvocationTargetException {
+    Object m = getL.invoke(c, new Object[] {});
+
+    JamaMatrix jm =
+      new JamaMatrix(m, choleskyLType, null, null, null, null, null);
+
+    jm.setStash(c);
+    return new Data[] {jm};
+  }
+
+
+  // New methods
+
+  /**
+   * Return the associated JAMA CholeskyDecomposition object.
+   */
+  public Object getCholeskyDecomposition() {
+    return cd;
+  }
+
+  // Method wrappers for JAMA Matrix functionality
+
+  /**
+   * Get L
+   * @return     L matrix
+   */
+  public JamaMatrix getL() throws VisADException, RemoteException {
+    if (classCholeskyDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(0);
+  }
+
+  public boolean isSPD()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classCholeskyDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    boolean spd = ((Boolean)
+      isSPD.invoke(cd, new Object[] {})).booleanValue();
+    return spd;
+  }
+
+  /**
+   * Solve A*X = B
+   * @param B    right hand side
+   * @return     solution if A is square, least squares solution otherwise
+   */
+  public JamaMatrix solve(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classCholeskyDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = solve.invoke(cd, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+}
+
diff --git a/visad/matrix/JamaEigenvalueDecomposition.java b/visad/matrix/JamaEigenvalueDecomposition.java
new file mode 100644
index 0000000..cb2e171
--- /dev/null
+++ b/visad/matrix/JamaEigenvalueDecomposition.java
@@ -0,0 +1,266 @@
+//
+// JamaEigenvalueDecomposition.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.matrix;
+
+import java.lang.reflect.*;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+/**
+ * JamaEigenvalueDecomposition is a VisAD wrapper for JAMA
+ * EigenvalueDecompositions.
+ * This class requires the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA package</a>.
+ */
+public class JamaEigenvalueDecomposition extends Tuple {
+
+  private static final RealType eigenV_row =
+    RealType.getRealType("eigenV_row");
+
+  private static final RealType eigenV_column =
+    RealType.getRealType("eigenV_column");
+
+  private static final RealType eigenV_value =
+    RealType.getRealType("eigenV_value");
+
+  private static final FunctionType eigenVType = constructEVFunction();
+
+  private static FunctionType constructEVFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(eigenV_row, eigenV_column);
+      FunctionType function = new FunctionType(tuple, eigenV_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType eigen_domain =
+    RealType.getRealType("eigen_domain");
+
+  private static final RealType eigen_real =
+    RealType.getRealType("eigen_real");
+
+  private static final RealType eigen_imaginary =
+    RealType.getRealType("eigen_imaginary");
+
+  private static final FunctionType eigenRType = constructERFunction();
+
+  private static FunctionType constructERFunction() {
+    try {
+      FunctionType function = new FunctionType(eigen_domain, eigen_real);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final FunctionType eigenIType = constructEIFunction();
+
+  private static FunctionType constructEIFunction() {
+    try {
+      FunctionType function = new FunctionType(eigen_domain, eigen_imaginary);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final Class[] classes = constructClasses();
+
+  private static Class[] constructClasses() {
+    Class[] cs = new Class[6];
+    try {
+      cs[0] = Class.forName("Jama.Matrix");
+      cs[1] = Class.forName("Jama.CholeskyDecomposition");
+      cs[2] = Class.forName("Jama.EigenvalueDecomposition");
+      cs[3] = Class.forName("Jama.LUDecomposition");
+      cs[4] = Class.forName("Jama.QRDecomposition");
+      cs[5] = Class.forName("Jama.SingularValueDecomposition");
+    }
+    catch (ClassNotFoundException e) {
+      throw new RuntimeException("you need to install Jama from " +
+                                 "http://math.nist.gov/javanumerics/jama/");
+    }
+    return cs;
+  }
+
+  private static final Class classMatrix = classes[0];
+  private static final Class classCholeskyDecomposition = classes[1];
+  private static final Class classEigenvalueDecomposition = classes[2];
+  private static final Class classLUDecomposition = classes[3];
+  private static final Class classQRDecomposition = classes[4];
+  private static final Class classSingularValueDecomposition = classes[5];
+
+  /** associated JAMA EigenvalueDecomposition object */
+  private Object ed;
+
+  /** useful methods from Jama.EigenvalueDecomposition class */
+  private static final Method[] methods =
+    constructMethods();
+
+  private static Method[] constructMethods() {
+    Method[] ms = new Method[4];
+    try {
+      Class[] param = new Class[] {};
+      ms[0] = classEigenvalueDecomposition.getMethod("getD", param);
+      ms[1] = classEigenvalueDecomposition.getMethod("getV", param);
+      ms[2] =
+        classEigenvalueDecomposition.getMethod("getImagEigenvalues", param);
+      ms[3] =
+        classEigenvalueDecomposition.getMethod("getRealEigenvalues", param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+    }
+    return ms;
+  }
+
+  private static final Method getD = methods[0];
+  private static final Method getV = methods[1];
+  private static final Method getImagEigenvalues = methods[2];
+  private static final Method getRealEigenvalues = methods[3];
+
+  private static final Constructor matrixEigenvalueDecomposition =
+    constructConstructor();
+
+  private static Constructor constructConstructor() {
+    try {
+      Class[] param = new Class[] {classMatrix};
+      return classEigenvalueDecomposition.getConstructor(param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  // Constructors
+
+  /**
+   * Construct a new JamaEigenvalueDecomposition from a JamaMatrix.
+   */
+  public JamaEigenvalueDecomposition(JamaMatrix matrix)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrixEigenvalueDecomposition.newInstance(new Object[] {matrix.getMatrix()}),
+         false);
+  }
+
+  JamaEigenvalueDecomposition(Object e, boolean copy)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    super(makeDatums(e), copy);
+    ed = ((JamaMatrix) getComponent(0)).getStash();
+  }
+
+  private static Data[] makeDatums(Object e)
+          throws VisADException, RemoteException, IllegalAccessException,
+                 InstantiationException, InvocationTargetException {
+    Object m = getV.invoke(e, new Object[] {});
+    JamaMatrix jm =
+      new JamaMatrix(m, eigenVType, null, null, null, null, null);
+    jm.setStash(e);
+
+    double[] rs = (double[]) getRealEigenvalues.invoke(e, new Object[] {});
+    FlatField rf = new FlatField(eigenRType, new Integer1DSet(rs.length));
+    rf.setSamples(new double[][] {rs});
+    double[] ims = (double[]) getImagEigenvalues.invoke(e, new Object[] {});
+    FlatField imf = new FlatField(eigenIType, new Integer1DSet(ims.length));
+    imf.setSamples(new double[][] {ims});
+
+    return new Data[] {jm, rf, imf};
+  }
+
+
+  // New methods
+
+  /**
+   * Return the associated JAMA EigenvalueDecomposition object.
+   */
+  public Object getEigenvalueDecomposition() {
+    return ed;
+  }
+
+  // Method wrappers for JAMA Matrix functionality
+
+  /**
+   * Get V
+   * @return     V matrix
+   */
+  public JamaMatrix getV() throws VisADException, RemoteException {
+    if (classEigenvalueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(0);
+  }
+
+  /**
+   * Get D
+   * @return     D matrix
+   */
+  public JamaMatrix getD()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classEigenvalueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = getD.invoke(ed, new Object[] {});
+    return new JamaMatrix(m);
+  }
+
+  public double[] getRealEigenvalues() throws VisADException, RemoteException {
+    if (classEigenvalueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    FlatField rf = (FlatField) getComponent(1);
+    double[][] v = rf.getValues(false);
+    return v[0];
+  }
+
+  public double[] getImagEigenvalues() throws VisADException, RemoteException {
+    if (classEigenvalueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    FlatField imf = (FlatField) getComponent(2);
+    double[][] v = imf.getValues(false);
+    return v[0];
+  }
+
+}
+
diff --git a/visad/matrix/JamaLUDecomposition.java b/visad/matrix/JamaLUDecomposition.java
new file mode 100644
index 0000000..e552779
--- /dev/null
+++ b/visad/matrix/JamaLUDecomposition.java
@@ -0,0 +1,316 @@
+//
+// JamaLUDecomposition.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.matrix;
+
+import java.lang.reflect.*;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+/**
+ * JamaLUDecomposition is a VisAD wrapper for JAMA LUDecompositions.
+ * This class requires the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA package</a>.
+ */
+public class JamaLUDecomposition extends Tuple {
+
+  private static final RealType LUL_row =
+    RealType.getRealType("LU_L_row");
+
+  private static final RealType LUL_column =
+    RealType.getRealType("LU_L_column");
+
+  private static final RealType LUL_value =
+    RealType.getRealType("LU_L_value");
+
+  private static final FunctionType LULType = constructLFunction();
+
+  private static FunctionType constructLFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(LUL_row, LUL_column);
+      FunctionType function = new FunctionType(tuple, LUL_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType LUU_row =
+    RealType.getRealType("LU_U_row");
+
+  private static final RealType LUU_column =
+    RealType.getRealType("LU_U_column");
+
+  private static final RealType LUU_value =
+    RealType.getRealType("LU_U_value");
+
+  private static final FunctionType LUUType = constructUFunction();
+
+  private static FunctionType constructUFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(LUU_row, LUU_column);
+      FunctionType function = new FunctionType(tuple, LUU_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType pivot_domain =
+    RealType.getRealType("pivot_domain");
+
+  private static final RealType pivot_value =
+    RealType.getRealType("pivot_value");
+
+  private static final FunctionType pivotType = constructPFunction();
+
+  private static FunctionType constructPFunction() {
+    try {
+      FunctionType function = new FunctionType(pivot_domain, pivot_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final Class[] classes = constructClasses();
+
+  private static Class[] constructClasses() {
+    Class[] cs = new Class[6];
+    try {
+      cs[0] = Class.forName("Jama.Matrix");
+      cs[1] = Class.forName("Jama.CholeskyDecomposition");
+      cs[2] = Class.forName("Jama.EigenvalueDecomposition");
+      cs[3] = Class.forName("Jama.LUDecomposition");
+      cs[4] = Class.forName("Jama.QRDecomposition");
+      cs[5] = Class.forName("Jama.SingularValueDecomposition");
+    }
+    catch (ClassNotFoundException e) {
+      throw new RuntimeException("you need to install Jama from " +
+                                 "http://math.nist.gov/javanumerics/jama/");
+    }
+    return cs;
+  }
+
+  private static final Class classMatrix = classes[0];
+  private static final Class classCholeskyDecomposition = classes[1];
+  private static final Class classEigenvalueDecomposition = classes[2];
+  private static final Class classLUDecomposition = classes[3];
+  private static final Class classQRDecomposition = classes[4];
+  private static final Class classSingularValueDecomposition = classes[5];
+
+  /** associated JAMA LUDecomposition object */
+  private Object lud;
+
+  /** useful methods from Jama.LUDecomposition class */
+  private static final Method[] methods =
+    constructMethods();
+
+  private static Method[] constructMethods() {
+    Method[] ms = new Method[7];
+    try {
+      Class[] param = new Class[] {};
+      ms[0] = classLUDecomposition.getMethod("getL", param);
+      ms[1] = classLUDecomposition.getMethod("getU", param);
+      ms[2] = classLUDecomposition.getMethod("getPivot", param);
+      ms[3] = classLUDecomposition.getMethod("getDoublePivot", param);
+      ms[4] = classLUDecomposition.getMethod("det", param);
+      ms[5] = classLUDecomposition.getMethod("isNonsingular", param);
+      param = new Class[] {classMatrix};
+      ms[6] = classLUDecomposition.getMethod("solve", param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+    }
+    return ms;
+  }
+
+  private static final Method getL = methods[0];
+  private static final Method getU = methods[1];
+  private static final Method getPivot = methods[2];
+  private static final Method getDoublePivot = methods[3];
+  private static final Method det = methods[4];
+  private static final Method isNonsingular = methods[5];
+  private static final Method solve = methods[6];
+
+  private static final Constructor matrixLUDecomposition =
+    constructConstructor();
+
+  private static Constructor constructConstructor() {
+    try {
+      Class[] param = new Class[] {classMatrix};
+      return classLUDecomposition.getConstructor(param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  // Constructors
+
+  /**
+   * Construct a new JamaLUDecomposition from a JamaMatrix.
+   */
+  public JamaLUDecomposition(JamaMatrix matrix)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrixLUDecomposition.newInstance(new Object[] {matrix.getMatrix()}),
+         false);
+  }
+
+  JamaLUDecomposition(Object lu, boolean copy)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    super(makeDatums(lu), copy);
+    lud = ((JamaMatrix) getComponent(0)).getStash();
+  }
+
+  private static Data[] makeDatums(Object lu)
+          throws VisADException, RemoteException, IllegalAccessException,
+                 InstantiationException, InvocationTargetException {
+    Object l = getL.invoke(lu, new Object[] {});
+    JamaMatrix jl =
+      new JamaMatrix(l, LULType, null, null, null, null, null);
+    jl.setStash(lu);
+
+    Object u = getU.invoke(lu, new Object[] {});
+    JamaMatrix ju =
+      new JamaMatrix(u, LUUType, null, null, null, null, null);
+
+
+    double[] pivot = (double[]) getDoublePivot.invoke(lu, new Object[] {});
+    FlatField pf = new FlatField(pivotType, new Integer1DSet(pivot.length));
+    pf.setSamples(new double[][] {pivot});
+
+    return new Data[] {jl, ju, pf};
+  }
+
+
+  // New methods
+
+  /**
+   * Return the associated JAMA LUDecomposition object.
+   */
+  public Object getLUDecomposition() {
+    return lud;
+  }
+
+  // Method wrappers for JAMA Matrix functionality
+
+  /**
+   * Get L
+   * @return     L matrix
+   */
+  public JamaMatrix getL() throws VisADException, RemoteException {
+    if (classLUDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(0);
+  }
+
+  /**
+   * Get U
+   * @return     U matrix
+   */
+  public JamaMatrix getU() throws VisADException, RemoteException {
+    if (classLUDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(1);
+  }
+
+  public double det()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classLUDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double) det.invoke(lud, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  public double[] getDoublePivot() throws VisADException, RemoteException {
+    if (classLUDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    FlatField pf = (FlatField) getComponent(2);
+    double[][] p = pf.getValues(false);
+    return p[0];
+  }
+
+  public int[] getPivot()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classLUDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    int[] p = (int[]) getPivot.invoke(lud, new Object[] {});
+    return p;
+  }
+
+  public boolean isNonsingular()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classLUDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    boolean ns =
+      ((Boolean) isNonsingular.invoke(lud, new Object[] {})).booleanValue();
+    return ns;
+  }
+
+  /**
+   * Solve A*X = B
+   * @param B    right hand side
+   * @return     solution if A is square, least squares solution otherwise
+   */
+  public JamaMatrix solve(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classLUDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = solve.invoke(lud, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+}
+
diff --git a/visad/matrix/JamaMatrix.java b/visad/matrix/JamaMatrix.java
new file mode 100644
index 0000000..737c05d
--- /dev/null
+++ b/visad/matrix/JamaMatrix.java
@@ -0,0 +1,1454 @@
+//
+// JamaMatrix.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.matrix;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+import java.text.NumberFormat;
+
+import visad.CoordinateSystem;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Gridded1DSet;
+import visad.Gridded2DSet;
+import visad.GriddedSet;
+import visad.Integer2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.Set;
+import visad.SetType;
+import visad.TupleType;
+import visad.Unit;
+import visad.VisADException;
+
+/**
+ * JamaMatrix is a VisAD wrapper for JAMA matrices. This class requires the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA package</a>.
+ */
+public class JamaMatrix extends FlatField {
+
+  private static final RealType matrix_row =
+    RealType.getRealType("matrix_row");
+
+  private static final RealType matrix_column =
+    RealType.getRealType("matrix_column");
+
+  private static final RealType matrix_value =
+    RealType.getRealType("matrix_value");
+
+  private static final FunctionType matrixType = constructFunction();
+
+  private static FunctionType constructFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(matrix_row, matrix_column);
+      FunctionType function = new FunctionType(tuple, matrix_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final Class[] classes = constructClasses();
+
+  private static Class[] constructClasses() {
+    Class[] cs = new Class[6];
+    try {
+      cs[0] = Class.forName("Jama.Matrix");
+      cs[1] = Class.forName("Jama.CholeskyDecomposition");
+      cs[2] = Class.forName("Jama.EigenvalueDecomposition");
+      cs[3] = Class.forName("Jama.LUDecomposition");
+      cs[4] = Class.forName("Jama.QRDecomposition");
+      cs[5] = Class.forName("Jama.SingularValueDecomposition");
+    }
+    catch (ClassNotFoundException e) {
+      throw new RuntimeException("you need to install Jama from " +
+                                 "http://math.nist.gov/javanumerics/jama/");
+    }
+    return cs;
+  }
+
+  private static final Class classMatrix = classes[0];
+  private static final Class classCholeskyDecomposition = classes[1];
+  private static final Class classEigenvalueDecomposition = classes[2];
+  private static final Class classLUDecomposition = classes[3];
+  private static final Class classQRDecomposition = classes[4];
+  private static final Class classSingularValueDecomposition = classes[5];
+
+  private static Class constructMatrixClass() {
+    try {
+      Class c = Class.forName("Jama.Matrix");
+      return c;
+    }
+    catch (ClassNotFoundException e) {
+      throw new RuntimeException("you need to install Jama from " +
+                                 "http://math.nist.gov/javanumerics/jama/");
+    }
+  }
+
+  /** This matrix's associated JAMA Matrix object */
+  private Object matrix;
+
+  /** useful methods from Jama.Matrix class */
+  private static final Method[] methods =
+    constructMethods();
+
+  private static Method[] constructMethods() {
+    Method[] ms = new Method[51];
+    try {
+      Class[] param = new Class[] {};
+      ms[0] = classMatrix.getMethod("getColumnDimension", param);
+      ms[1] = classMatrix.getMethod("getRowDimension", param);
+      ms[2] = classMatrix.getMethod("getArray", param);
+      param = new Class[] {int.class, int.class};
+      ms[3] = classMatrix.getMethod("get", param);
+      param = new Class[] {int.class, int.class, int.class, int.class};
+      ms[4] = classMatrix.getMethod("getMatrix", param);
+      param = new Class[] {int[].class, int[].class};
+      ms[5] = classMatrix.getMethod("getMatrix", param);
+      param = new Class[] {int.class, int.class, int[].class};
+      ms[6] = classMatrix.getMethod("getMatrix", param);
+      param = new Class[] {int[].class, int.class, int.class};
+      ms[7] = classMatrix.getMethod("getMatrix", param);
+      param = new Class[] {int.class, int.class, double.class};
+      ms[8] = classMatrix.getMethod("set", param);
+      param = new Class[] {int.class, int.class, int.class, int.class,
+                           classMatrix};
+      ms[9] = classMatrix.getMethod("setMatrix", param);
+      param = new Class[] {int[].class, int[].class, classMatrix};
+      ms[10] = classMatrix.getMethod("setMatrix", param);
+      param = new Class[] {int.class, int.class, int[].class, classMatrix};
+      ms[11] = classMatrix.getMethod("setMatrix", param);
+      param = new Class[] {int[].class, int.class, int.class, classMatrix};
+      ms[12] = classMatrix.getMethod("setMatrix", param);
+      param = new Class[] {};
+      ms[13] = classMatrix.getMethod("transpose", param);
+      ms[14] = classMatrix.getMethod("norm1", param);
+      ms[15] = classMatrix.getMethod("norm2", param);
+      ms[16] = classMatrix.getMethod("normInf", param);
+      ms[17] = classMatrix.getMethod("normF", param);
+      ms[18] = classMatrix.getMethod("uminus", param);
+      param = new Class[] {classMatrix};
+      ms[19] = classMatrix.getMethod("plus", param);
+      ms[20] = classMatrix.getMethod("plusEquals", param);
+      ms[21] = classMatrix.getMethod("minus", param);
+      ms[22] = classMatrix.getMethod("minusEquals", param);
+      ms[23] = classMatrix.getMethod("arrayTimes", param);
+      ms[24] = classMatrix.getMethod("arrayTimesEquals", param);
+      ms[25] = classMatrix.getMethod("arrayRightDivide", param);
+      ms[26] = classMatrix.getMethod("arrayRightDivideEquals", param);
+      ms[27] = classMatrix.getMethod("arrayLeftDivide", param);
+      ms[28] = classMatrix.getMethod("arrayLeftDivideEquals", param);
+      param = new Class[] {double.class};
+      ms[29] = classMatrix.getMethod("times", param);
+      ms[30] = classMatrix.getMethod("timesEquals", param);
+      param = new Class[] {classMatrix};
+      ms[31] = classMatrix.getMethod("times", param);
+      ms[32] = classMatrix.getMethod("solve", param);
+      ms[33] = classMatrix.getMethod("solveTranspose", param);
+      param = new Class[] {};
+      ms[34] = classMatrix.getMethod("inverse", param);
+      ms[35] = classMatrix.getMethod("det", param);
+      ms[36] = classMatrix.getMethod("rank", param);
+      ms[37] = classMatrix.getMethod("cond", param);
+      ms[38] = classMatrix.getMethod("trace", param);
+      param = new Class[] {int.class, int.class};
+      ms[39] = classMatrix.getMethod("random", param);
+      ms[40] = classMatrix.getMethod("identity", param);
+      ms[41] = classMatrix.getMethod("print", param);
+      param = new Class[] {PrintWriter.class, int.class, int.class};
+      ms[42] = classMatrix.getMethod("print", param);
+      param = new Class[] {NumberFormat.class, int.class};
+      ms[43] = classMatrix.getMethod("print", param);
+      param = new Class[] {PrintWriter.class, NumberFormat.class, int.class};
+      ms[44] = classMatrix.getMethod("print", param);
+      param = new Class[] {BufferedReader.class};
+      ms[45] = classMatrix.getMethod("read", param);
+      param = new Class[] {};
+      ms[46] = classMatrix.getMethod("chol", param);
+      ms[47] = classMatrix.getMethod("eig", param);
+      ms[48] = classMatrix.getMethod("lu", param);
+      ms[49] = classMatrix.getMethod("qr", param);
+      ms[50] = classMatrix.getMethod("svd", param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+    }
+    return ms;
+  }
+
+  private static final Method getColumnDimension = methods[0];
+  private static final Method getRowDimension = methods[1];
+  private static final Method getArray = methods[2];
+  private static final Method get = methods[3];
+  private static final Method getMatrix1 = methods[4];
+  private static final Method getMatrix2 = methods[5];
+  private static final Method getMatrix3 = methods[6];
+  private static final Method getMatrix4 = methods[7];
+  private static final Method set = methods[8];
+  private static final Method setMatrix1 = methods[9];
+  private static final Method setMatrix2 = methods[10];
+  private static final Method setMatrix3 = methods[11];
+  private static final Method setMatrix4 = methods[12];
+  private static final Method transpose = methods[13];
+  private static final Method norm1 = methods[14];
+  private static final Method norm2 = methods[15];
+  private static final Method normInf = methods[16];
+  private static final Method normF = methods[17];
+  private static final Method uminus = methods[18];
+  private static final Method plus = methods[19];
+  private static final Method plusEquals = methods[20];
+  private static final Method minus = methods[21];
+  private static final Method minusEquals = methods[22];
+  private static final Method arrayTimes = methods[23];
+  private static final Method arrayTimesEquals = methods[24];
+  private static final Method arrayRightDivide = methods[25];
+  private static final Method arrayRightDivideEquals = methods[26];
+  private static final Method arrayLeftDivide = methods[27];
+  private static final Method arrayLeftDivideEquals = methods[28];
+  private static final Method times1 = methods[29];
+  private static final Method timesEquals = methods[30];
+  private static final Method times2 = methods[31];
+  private static final Method solve = methods[32];
+  private static final Method solveTranspose = methods[33];
+  private static final Method inverse = methods[34];
+  private static final Method det = methods[35];
+  private static final Method rank = methods[36];
+  private static final Method cond = methods[37];
+  private static final Method trace = methods[38];
+  private static final Method random = methods[39];
+  private static final Method identity = methods[40];
+  private static final Method print1 = methods[41];
+  private static final Method print2 = methods[42];
+  private static final Method print3 = methods[43];
+  private static final Method print4 = methods[44];
+  private static final Method read = methods[45];
+  private static final Method chol = methods[46];
+  private static final Method eig = methods[47];
+  private static final Method lu = methods[48];
+  private static final Method qr = methods[49];
+  private static final Method svd = methods[50];
+
+  /** constructors from Jama.Matrix class */
+  private static final Constructor[] constructors =
+    constructConstructors();
+
+  private static Constructor[] constructConstructors() {
+    Constructor[] cs = new Constructor[2];
+    try {
+      Class[] param = new Class[] {int.class, int.class};
+      cs[0] = classMatrix.getConstructor(param);
+      param = new Class[] {double[][].class};
+      cs[1] = classMatrix.getConstructor(param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+    }
+    return cs;
+  }
+
+  private static final Constructor intintMatrix = constructors[0];
+  private static final Constructor doubleMatrix = constructors[1];
+
+  // Static methods
+
+  /**
+   * Attempt to convert the given VisAD FlatField to a VisAD JamaMatrix
+   * Data object.
+   * @return The converted object, or null if it could not be converted
+   */
+  public static JamaMatrix convertToMatrix(FlatField field)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+
+    // if field is already a JamaMatrix the task is trivial
+    if (field instanceof JamaMatrix) return (JamaMatrix) field;
+
+    FunctionType ftype = (FunctionType) field.getType();
+
+    // get domain set (must be 2-D ordered samples)
+    Set set = field.getDomainSet();
+    Gridded2DSet grid = null;
+    if (set instanceof Gridded2DSet) {
+      grid = (Gridded2DSet) set;
+    }
+    else if (set instanceof GriddedSet &&
+             set.getDimension() == 2) {
+      int[] lengths = ((GriddedSet) set).getLengths();
+      float[][] samples = set.getSamples(false);
+      grid = new Gridded2DSet(set.getType(), samples, lengths[0],
+                              lengths[1], set.getCoordinateSystem(),
+                              set.getSetUnits(), set.getSetErrors());
+    }
+    else if (set instanceof Gridded1DSet) {
+      int[] lengths = ((Gridded1DSet) set).getLengths();
+      float[][] samples = set.getSamples(false);
+      float[] dummies = new float[lengths[0]];
+      for (int i=0; i<lengths[0]; i++) dummies[i] = 0.0f;
+      samples = new float[][] {samples[0], dummies};
+      RealType rt1 =
+        (RealType) ((SetType) set.getType()).getDomain().getComponent(0);
+      RealType rt0 = RealType.getRealType("dummy");
+      RealTupleType rtt = new RealTupleType(rt1, rt0);
+      grid = new Gridded2DSet(rtt, samples, lengths[0], 1, null, null, null);
+      ftype = new FunctionType(rtt, ftype.getRange());
+    }
+    else {
+      return null;
+    }
+
+    // construct matrix entry array
+    int[] dims = grid.getLengths();
+    int rows = dims[0];
+    int cols = dims[1];
+    double[][] entries = new double[rows][cols];
+
+    // get range values
+    double[][] range;
+    try {
+      range = field.getValues(false);
+    }
+    catch (VisADException exc) {
+      return null;
+    }
+
+    // unrasterize range values into matrix entry array
+    for (int i=0; i<rows; i++) {
+      for (int j=0; j<cols; j++) {
+        entries[i][j] = range[0][cols * i + j];
+      }
+    }
+
+    CoordinateSystem rc = null;
+    try {
+      CoordinateSystem[] r = field.getRangeCoordinateSystem();
+      rc = r[0];
+    }
+    catch (Exception e) {
+    }
+    CoordinateSystem[] rcs = null;
+    try {
+      int n = ((TupleType) ftype.getRange()).getDimension();
+      rcs = new CoordinateSystem[n];
+      for (int i=0; i<n; i++) {
+        CoordinateSystem[] r = field.getRangeCoordinateSystem(i);
+        rcs[i] = r[0];
+      }
+    }
+    catch (Exception e) {
+    }
+    Set[] rangeSets = field.getRangeSets();
+    Unit[] units = null;
+    try {
+      Unit[][] us = field.getRangeUnits();
+      if (us != null) {
+        int n = us.length;
+        for (int i=0; i<n; i++) {
+          units[i] = us[i][0];
+        }
+      }
+    }
+    catch (Exception e) {
+    }
+
+    Object m = doubleMatrix.newInstance(new Object[] {entries});
+    return new JamaMatrix(m, ftype, grid, rc, rcs, rangeSets, units);
+  }
+
+  /**
+   * Test the JamaMatrix class.
+   */
+  public static void main(String[] args)
+         throws VisADException, RemoteException {
+    double[][] e1 = { {3, 4, 5},
+                      {10, 18, 6},
+                      {2, -1, 0} };
+    double[][] e2 = { {6, 4, 2},
+                      {-4, -3, -2},
+                      {1, 1, 1} };
+    try {
+      System.out.println("get = " + get);
+      System.out.println("getMatrix2 = " + getMatrix2);
+      JamaMatrix m1 = new JamaMatrix(e1);
+      JamaMatrix m2 = new JamaMatrix(e2);
+      JamaMatrix m3 = convertToMatrix((FlatField) m1.add(m2));
+      JamaMatrix m4 = m1.plus(m2);
+      System.out.println("m1.get(1, 1) = " + m1.get(1, 1));
+      System.out.println("m1:");
+      m1.print(1, 0);
+      System.out.println("m2:");
+      m2.print(1, 0);
+      System.out.println("m3 = m1 + m2 (VisAD):");
+      m3.print(1, 0);
+      System.out.println("m4 = m1 + m2 (JAMA):");
+      m4.print(1, 0);
+      System.out.println("m4 = " + m4);
+
+      JamaSingularValueDecomposition svd4 = m4.svd();
+      System.out.println("m4 svd U:");
+      svd4.getU().print(1, 0);
+      System.out.println("m4 svd S:");
+      svd4.getS().print(1, 0);
+      System.out.println("m4 svd V:");
+      svd4.getV().print(1, 0);
+
+      JamaQRDecomposition qr4 = m4.qr();
+      System.out.println("m4 qr Q:");
+      qr4.getQ().print(1, 0);
+      System.out.println("m4 qr R:");
+      qr4.getR().print(1, 0);
+
+      JamaLUDecomposition lu4 = m4.lu();
+      System.out.println("m4 lu L:");
+      lu4.getL().print(1, 0);
+      System.out.println("m4 lu U:");
+      lu4.getU().print(1, 0);
+
+      JamaEigenvalueDecomposition ev4 = m4.eig();
+      System.out.println("m4 eig D:");
+      ev4.getD().print(1, 0);
+      System.out.println("m4 eig V:");
+      ev4.getV().print(1, 0);
+
+      JamaCholeskyDecomposition chol4 = m4.chol();
+      System.out.println("m4 chol L:");
+      chol4.getL().print(1, 0);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+
+  // Constructors
+
+  /**
+   * Constructs a domain set appropriate for the given matrix.
+   * @return An Integer2DSet with dimensions equal to those of the matrix
+   */
+  private static Integer2DSet getDomainSet(Object matrix)
+          throws VisADException, IllegalAccessException,
+                 InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    if (!classMatrix.isInstance(matrix)) {
+      throw new VisADException("matrix must be an instance of Jama.Matrix");
+    }
+    int rows = ((Integer)
+      getRowDimension.invoke(matrix, new Object[] {})).intValue();
+    int cols = ((Integer)
+      getColumnDimension.invoke(matrix, new Object[] {})).intValue();
+    return new Integer2DSet(rows, cols);
+  }
+
+  /**
+   * Construct a new JamaMatrix from the given matrix dimensions.
+   */
+  public JamaMatrix(int rows, int cols)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(intintMatrix.newInstance(new Object[] {new Integer(rows),
+                                                new Integer(cols)}),
+         null, null, null, null, null, null);
+  }
+
+  /**
+   * Construct a new JamaMatrix from the given matrix entries.
+   */
+  public JamaMatrix(double[][] entries)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(doubleMatrix.newInstance(new Object[] {entries}),
+         null, null, null, null, null, null);
+  }
+
+  /**
+   * Construct a new JamaMatrix from the given JAMA Matrix.
+   */
+  public JamaMatrix(Object matrix)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrix, null, null, null, null, null, null);
+  }
+
+  /**
+   * Construct a new JamaMatrix from the given JAMA Matrix,
+   * MathType and domain set.
+   */
+  public JamaMatrix(Object matrix, FunctionType type, Gridded2DSet domain_set)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrix, type, domain_set, null, null, null, null);
+  }
+
+  /**
+   * Construct a new JamaMatrix from the specified JAMA Matrix,
+   * coordinate systems, range sets and units.
+   */
+  public JamaMatrix(Object matrix, CoordinateSystem range_coord_sys,
+                    CoordinateSystem[] range_coord_syses, Set[] range_sets,
+                    Unit[] units)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrix, null, null, range_coord_sys, range_coord_syses, range_sets,
+      units);
+  }
+
+  /**
+   * Construct a new JamaMatrix from the specified JAMA Matrix, MathType,
+   * domain set, coordinate systems, range sets and units.
+   */
+  public JamaMatrix(Object matrix, FunctionType type, Gridded2DSet domain_set,
+                    CoordinateSystem range_coord_sys,
+                    CoordinateSystem[] range_coord_syses,
+                    Set[] range_sets, Unit[] units)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    super(type == null ? matrixType : type,
+      domain_set == null ? getDomainSet(matrix) : domain_set,
+      range_coord_sys, range_coord_syses, range_sets, units);
+    if (type != null && !matrixType.equalsExceptName(type)) {
+      throw new VisADException("JamaMatrix: " +
+        "MathType must be of the form ((x, y) -> z)");
+    }
+    setMatrix(matrix);
+  }
+
+  // place for JamaCholeskyDecomposition, etc to store their
+  // CholeskyDecomposition during construction
+  private Object stash = null;
+
+  void setStash(Object s) {
+    stash = s;
+  }
+
+  Object getStash() {
+    return stash;
+  }
+
+  // New methods
+
+  /**
+   * Return the associated JAMA Matrix object.
+   */
+  public Object getMatrix() {
+    return matrix;
+  }
+
+  /**
+   * Set this matrix's samples to correspond to those of the given
+   * JAMA Matrix.
+   */
+  public void setMatrix(Object matrix)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    if (!classMatrix.isInstance(matrix)) {
+      throw new VisADException("matrix must be an instance of Jama.Matrix");
+    }
+    // convert matrix entries into range sample values
+    int rows = ((Integer)
+      getRowDimension.invoke(matrix, new Object[] {})).intValue();
+    int cols = ((Integer)
+      getColumnDimension.invoke(matrix, new Object[] {})).intValue();
+    double[][] entries =
+      (double[][]) getArray.invoke(matrix, new Object[] {});
+    double[][] range = new double[1][rows * cols];
+    for (int i=0; i<rows; i++) {
+      for (int j=0; j<cols; j++) {
+        range[0][cols * i + j] = entries[i][j];
+      }
+    }
+
+    // set range samples to new values
+    try {
+      setSamples(range, false);
+    }
+    catch (RemoteException exc) { }
+
+    this.matrix = matrix;
+  }
+
+  /**
+   * Set this matrix's samples to correspond to the given entries.
+   */
+  public void setMatrix(double[][] entries)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    setMatrix(doubleMatrix.newInstance(new Object[] {entries}));
+  }
+
+
+  // Method wrappers for JAMA Matrix functionality
+
+  /**
+   * Get row dimension.
+   * @return     The number of rows
+   */
+  public int getRowDimension()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    int rows = ((Integer)
+      getRowDimension.invoke(matrix, new Object[] {})).intValue();
+    return rows;
+  }
+
+  /**
+   * Get column dimension.
+   * @return     The number of columns
+   */
+  public int getColumnDimension()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    int cols = ((Integer)
+      getColumnDimension.invoke(matrix, new Object[] {})).intValue();
+    return cols;
+  }
+
+  /**
+   * Get a single element.
+   * @param i    Row index
+   * @param j    Column index
+   * @return     A(i,j)
+   * @exception  ArrayIndexOutOfBoundsException
+   */
+  public double get(int i, int j)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      get.invoke(matrix, new Object[] {new Integer(i),
+                                       new Integer(j)})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Get a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @return     A(i0:i1,j0:j1)
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public JamaMatrix getMatrix(int i0, int i1, int j0, int j1)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = getMatrix1.invoke(matrix, new Object[] {new Integer(i0),
+                   new Integer(i1), new Integer(j0), new Integer(j1)});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Get a submatrix.
+   * @param r    Array of row indices
+   * @param c    Array of column indices
+   * @return     A(r(:),c(:))
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public JamaMatrix getMatrix(int[] r, int[] c)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = getMatrix2.invoke(matrix, new Object[] {r, c});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Get a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param c    Array of column indices
+   * @return     A(i0:i1,c(:))
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public JamaMatrix getMatrix(int i0, int i1, int[] c)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = getMatrix1.invoke(matrix, new Object[] {new Integer(i0),
+                                              new Integer(i1), c});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Get a submatrix.
+   * @param r    Array of row indices
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @return     A(r(:),j0:j1)
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  
+  public JamaMatrix getMatrix(int[] r, int j0, int j1)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = getMatrix1.invoke(matrix, new Object[] {r, new Integer(j0),
+                                                       new Integer(j1)});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Set a single element.
+   * @param i    Row index
+   * @param j    Column index
+   * @param s    A(i,j)
+   * @exception  ArrayIndexOutOfBoundsException
+   */
+  public void set(int i, int j, double s)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    set.invoke(matrix, new Object[] {new Integer(i),
+                                     new Integer(j),
+                                     new Double(s)});
+    setMatrix(matrix);
+  }
+
+  /**
+   * Set a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @param X    A(i0:i1,j0:j1)
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int i0, int i1, int j0, int j1, JamaMatrix X)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    setMatrix1.invoke(matrix, new Object[] {new Integer(i0),
+                   new Integer(i1), new Integer(j0), new Integer(j1),
+                   X.getMatrix()});
+    setMatrix(matrix);
+  }
+
+  /**
+   * Set a submatrix.
+   * @param r    Array of row indices
+   * @param c    Array of column indices
+   * @param X    A(r(:),c(:))
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int[] r, int[] c, JamaMatrix X)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    setMatrix2.invoke(matrix, new Object[] {r, c, X.getMatrix()});
+    setMatrix(matrix);
+  }
+
+  /**
+   * Set a submatrix.
+   * @param r    Array of row indices
+   * @param j0   Initial column index
+   * @param j1   Final column index
+   * @param X    A(r(:),j0:j1)
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int[] r, int j0, int j1, JamaMatrix X)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    setMatrix3.invoke(matrix, new Object[] {r, new Integer(j0),
+                   new Integer(j1), X.getMatrix()});
+    setMatrix(matrix);
+  }
+
+  /**
+   * Set a submatrix.
+   * @param i0   Initial row index
+   * @param i1   Final row index
+   * @param c    Array of column indices
+   * @param X    A(i0:i1,c(:))
+   * @exception  ArrayIndexOutOfBoundsException Submatrix indices
+   */
+  public void setMatrix(int i0, int i1, int[] c, JamaMatrix X)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    setMatrix4.invoke(matrix, new Object[] {new Integer(i0),
+                   new Integer(i1), c, X.getMatrix()});
+    setMatrix(matrix);
+  }
+
+  /**
+   * Matrix transpose.
+   * @return    A'
+   */
+  public JamaMatrix transpose()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = transpose.invoke(matrix, new Object[] {});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * One norm.
+   * @return    maximum column sum
+   */
+  public double norm1()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      norm1.invoke(matrix, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Two norm.
+   * @return    maximum singular value
+   */
+  public double norm2()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      norm2.invoke(matrix, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Infinity norm.
+   * @return    maximum row sum
+   */
+  public double normInf()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      normInf.invoke(matrix, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Frobenius norm.
+   * @return    sqrt of sum of squares of all elements
+   */
+  public double normF()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      normF.invoke(matrix, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Unary minus.
+   * @return    -A
+   */
+  public JamaMatrix uminus()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = uminus.invoke(matrix, new Object[] {});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * C = A + B
+   * @param B    another matrix
+   * @return     A + B
+   */
+  public JamaMatrix plus(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = plus.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * A = A + B
+   * @param B    another matrix
+   * @return     A + B
+   */
+  public JamaMatrix plusEquals(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    plusEquals.invoke(matrix, new Object[] {B.getMatrix()});
+    setMatrix(matrix);
+    return this;
+  }
+
+  /**
+   * C = A - B
+   * @param B    another matrix
+   * @return     A - B
+   */
+  public JamaMatrix minus(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = minus.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * A = A - B
+   * @param B    another matrix
+   * @return     A - B
+   */
+  public JamaMatrix minusEquals(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    minusEquals.invoke(matrix, new Object[] {B.getMatrix()});
+    setMatrix(matrix);
+    return this;
+  }
+
+  /**
+   * Element-by-element multiplication, C = A.*B
+   * @param B    another matrix
+   * @return     A.*B
+   */
+  public JamaMatrix arrayTimes(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = arrayTimes.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Element-by-element multiplication in place, A = A.*B
+   * @param B    another matrix
+   * @return     A.*B
+   */
+  public JamaMatrix arrayTimesEquals(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    arrayTimesEquals.invoke(matrix, new Object[] {B.getMatrix()});
+    setMatrix(matrix);
+    return this;
+  }
+
+  /**
+   * Element-by-element right division, C = A./B
+   * @param B    another matrix
+   * @return     A./B
+   */
+  public JamaMatrix arrayRightDivide(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = arrayRightDivide.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Element-by-element right division in place, A = A./B
+   * @param B    another matrix
+   * @return     A./B
+   */
+  public JamaMatrix arrayRightDivideEquals(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    arrayRightDivideEquals.invoke(matrix, new Object[] {B.getMatrix()});
+    setMatrix(matrix);
+    return this;
+  }
+
+  /**
+   * Element-by-element left division, C = A.\B
+   * @param B    another matrix
+   * @return     A.\B
+   */
+  public JamaMatrix arrayLeftDivide(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = arrayLeftDivide.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Element-by-element left division in place, A = A.\B
+   * @param B    another matrix
+   * @return     A.\B
+   */
+  public JamaMatrix arrayLeftDivideEquals(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    arrayLeftDivideEquals.invoke(matrix, new Object[] {B.getMatrix()});
+    setMatrix(matrix);
+    return this;
+  }
+
+  /**
+   * Multiply a matrix by a scalar, C = s*A
+   * @param s    scalar
+   * @return     s*A
+   */
+  public JamaMatrix times(double s)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = times1.invoke(matrix, new Object[] {new Double(s)});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Multiply a matrix by a scalar in place, A = s*A
+   * @param s    scalar
+   * @return     replace A by s*A
+   */
+  public JamaMatrix timesEquals(double s)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    timesEquals.invoke(matrix, new Object[] {new Double(s)});
+    setMatrix(matrix);
+    return this;
+  }
+
+  /**
+   * Linear algebraic matrix multiplication, A * B
+   * @param B    another matrix
+   * @return     Matrix product, A * B
+   * @exception  IllegalArgumentException Matrix inner dimensions must agree
+   */
+  public JamaMatrix times(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = times2.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Solve A*X = B
+   * @param B    right hand side
+   * @return     solution if A is square, least squares solution otherwise
+   */
+  public JamaMatrix solve(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+
+    Object m = solve.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Solve X*A = B, which is also A'*X' = B'
+   * @param B    right hand side
+   * @return     solution if A is square, least squares solution otherwise
+   */
+  public JamaMatrix solveTranspose(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = solveTranspose.invoke(matrix, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Matrix inverse or pseudoinverse.
+   * @return     inverse(A) if A is square, pseudoinverse otherwise
+   */
+  public JamaMatrix inverse()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = inverse.invoke(matrix, new Object[] {});
+    return new JamaMatrix(m);
+  }
+
+  /**
+   * Matrix determinant.
+   * @return     determinant
+   */
+  public double det()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      det.invoke(matrix, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Matrix rank.
+   * @return     effective numerical rank, obtained from SVD
+   */
+  public int rank()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    int val = ((Integer)
+      rank.invoke(matrix, new Object[] {})).intValue();
+    return val;
+  }
+
+  /**
+   * Matrix condition (2 norm).
+   * @return     ratio of largest to smallest singular value
+   */
+  public double cond()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      cond.invoke(matrix, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Matrix trace.
+   * @return     sum of the diagonal elements
+   */
+  public double trace()
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val = ((Double)
+      trace.invoke(matrix, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  /**
+   * Generate matrix with random elements.
+   * @param m    Number of rows
+   * @param n    Number of colums
+   * @return     An m-by-n matrix with uniformly distributed random elements
+   */
+  public static JamaMatrix random(int m, int n)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object mat = random.invoke(null, new Object[] {new Integer(m),
+                                                   new Integer(n)});
+    return new JamaMatrix(mat);
+  }
+
+  /**
+   * Generate identity matrix.
+   * @param m    Number of rows
+   * @param n    Number of colums
+   * @return     An m-by-n matrix with ones on the diagonal and zeros elsewhere
+   */
+  public static JamaMatrix identity(int m, int n)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object mat = identity.invoke(null, new Object[] {new Integer(m),
+                                                     new Integer(n)});
+    return new JamaMatrix(mat);
+  }
+
+  /**
+   * Print the matrix to stdout.   Line the elements up in columns
+   * with a Fortran-like 'Fw.d' style format
+   * @param w    Column width
+   * @param d    Number of digits after the decimal
+   */
+  public void print(int w, int d)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    print1.invoke(matrix, new Object[] {new Integer(w), new Integer(d)});
+  }
+
+  /**
+   * Print the matrix to the output stream.   Line the elements up in
+   * columns with a Fortran-like 'Fw.d' style format.
+   * @param output Output stream
+   * @param w      Column width
+   * @param d      Number of digits after the decimal
+   */
+  public void print(PrintWriter output, int w, int d)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    print2.invoke(matrix, new Object[] {output, new Integer(w), new Integer(d)});
+  }
+
+  /**
+   * Print the matrix to stdout.  Line the elements up in columns.
+   * Use the format object, and right justify within columns of width
+   * characters.
+   * @param format Formatting object for individual elements
+   * @param width  Field width for each column
+   */
+  public void print(NumberFormat format, int width)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    print3.invoke(matrix, new Object[] {format, new Integer(width)});
+  }
+
+  /**
+   * Print the matrix to the output stream.  Line the elements up in columns.
+   * Use the format object, and right justify within columns of width
+   * characters.
+   * @param output the output stream
+   * @param format A formatting object to format the matrix elements 
+   * @param width  Column width
+   */
+  public void print(PrintWriter output, NumberFormat format, int width)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    print4.invoke(matrix, new Object[] {output, format, new Integer(width)});
+  }
+
+  /**
+   * Read a matrix from a stream.  The format is the same the print method,
+   * so printed matrices can be read back in.  Elements are separated by
+   * whitespace, all the elements for each row appear on a single line,
+   * the last row is followed by a blank line.
+   * @param input the input stream
+   */
+  public static JamaMatrix read(BufferedReader input)
+         throws IOException, VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = read.invoke(null, new Object[] {input});
+    return new JamaMatrix(m);
+  }
+
+  public JamaCholeskyDecomposition chol()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object c = chol.invoke(matrix, new Object[] {});
+    return new JamaCholeskyDecomposition(c, false);
+  }
+
+  public JamaEigenvalueDecomposition eig()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object e = eig.invoke(matrix, new Object[] {});
+    return new JamaEigenvalueDecomposition(e, false);
+  }
+
+  public JamaLUDecomposition lu()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object l = lu.invoke(matrix, new Object[] {});
+    return new JamaLUDecomposition(l, false);
+  }
+
+  public JamaQRDecomposition qr()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object q = qr.invoke(matrix, new Object[] {});
+    return new JamaQRDecomposition(q, false);
+  }
+
+  public JamaSingularValueDecomposition svd()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classMatrix == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object s = svd.invoke(matrix, new Object[] {});
+    return new JamaSingularValueDecomposition(s, false);
+  }
+
+}
+
diff --git a/visad/matrix/JamaQRDecomposition.java b/visad/matrix/JamaQRDecomposition.java
new file mode 100644
index 0000000..bb85bfe
--- /dev/null
+++ b/visad/matrix/JamaQRDecomposition.java
@@ -0,0 +1,290 @@
+//
+// JamaQRDecomposition.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.matrix;
+
+import java.lang.reflect.*;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+/**
+ * JamaQRDecomposition is a VisAD wrapper for JAMA QRDecompositions.
+ * This class requires the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA package</a>.
+ */
+public class JamaQRDecomposition extends Tuple {
+
+  private static final RealType QRQ_row =
+    RealType.getRealType("QR_Q_row");
+
+  private static final RealType QRQ_column =
+    RealType.getRealType("QR_Q_column");
+
+  private static final RealType QRQ_value =
+    RealType.getRealType("QR_Q_value");
+
+  private static final FunctionType QRQType = constructQFunction();
+
+  private static FunctionType constructQFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(QRQ_row, QRQ_column);
+      FunctionType function = new FunctionType(tuple, QRQ_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType QRR_row =
+    RealType.getRealType("QR_R_row");
+
+  private static final RealType QRR_column =
+    RealType.getRealType("QR_R_column");
+
+  private static final RealType QRR_value =
+    RealType.getRealType("QR_R_value");
+
+  private static final FunctionType QRRType = constructRFunction();
+
+  private static FunctionType constructRFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(QRR_row, QRR_column);
+      FunctionType function = new FunctionType(tuple, QRR_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType QRH_row =
+    RealType.getRealType("QR_H_row");
+
+  private static final RealType QRH_column =
+    RealType.getRealType("QR_H_column");
+
+  private static final RealType QRH_value =
+    RealType.getRealType("QR_H_value");
+
+  private static final FunctionType QRHType = constructHFunction();
+
+  private static FunctionType constructHFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(QRH_row, QRH_column);
+      FunctionType function = new FunctionType(tuple, QRH_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final Class[] classes = constructClasses();
+
+  private static Class[] constructClasses() {
+    Class[] cs = new Class[6];
+    try {
+      cs[0] = Class.forName("Jama.Matrix");
+      cs[1] = Class.forName("Jama.CholeskyDecomposition");
+      cs[2] = Class.forName("Jama.EigenvalueDecomposition");
+      cs[3] = Class.forName("Jama.LUDecomposition");
+      cs[4] = Class.forName("Jama.QRDecomposition");
+      cs[5] = Class.forName("Jama.SingularValueDecomposition");
+    }
+    catch (ClassNotFoundException e) {
+      throw new RuntimeException("you need to install Jama from " +
+                                 "http://math.nist.gov/javanumerics/jama/");
+    }
+    return cs;
+  }
+
+  private static final Class classMatrix = classes[0];
+  private static final Class classCholeskyDecomposition = classes[1];
+  private static final Class classEigenvalueDecomposition = classes[2];
+  private static final Class classLUDecomposition = classes[3];
+  private static final Class classQRDecomposition = classes[4];
+  private static final Class classSingularValueDecomposition = classes[5];
+
+  /** associated JAMA QRDecomposition object */
+  private Object qrd;
+
+  /** useful methods from Jama.QRDecomposition class */
+  private static final Method[] methods =
+    constructMethods();
+
+  private static Method[] constructMethods() {
+    Method[] ms = new Method[5];
+    try {
+      Class[] param = new Class[] {};
+      ms[0] = classQRDecomposition.getMethod("getH", param);
+      ms[1] = classQRDecomposition.getMethod("getQ", param);
+      ms[2] = classQRDecomposition.getMethod("getR", param);
+      ms[3] = classQRDecomposition.getMethod("isFullRank", param);
+      param = new Class[] {classMatrix};
+      ms[4] = classQRDecomposition.getMethod("solve", param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+    }
+    return ms;
+  }
+
+  private static final Method getH = methods[0];
+  private static final Method getQ = methods[1];
+  private static final Method getR = methods[2];
+  private static final Method isFullRank = methods[3];
+  private static final Method solve = methods[4];
+
+  private static final Constructor matrixQRDecomposition =
+    constructConstructor();
+
+  private static Constructor constructConstructor() {
+    try {
+      Class[] param = new Class[] {classMatrix};
+      return classQRDecomposition.getConstructor(param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  // Constructors
+
+  /**
+   * Construct a new JamaQRDecomposition from a JamaMatrix.
+   */
+  public JamaQRDecomposition(JamaMatrix matrix)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrixQRDecomposition.newInstance(new Object[] {matrix.getMatrix()}),
+         false);
+  }
+
+  JamaQRDecomposition(Object qr, boolean copy)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    super(makeDatums(qr), copy);
+    qrd = ((JamaMatrix) getComponent(0)).getStash();
+  }
+
+  private static Data[] makeDatums(Object qr)
+          throws VisADException, RemoteException, IllegalAccessException,
+                 InstantiationException, InvocationTargetException {
+    Object q = getQ.invoke(qr, new Object[] {});
+    JamaMatrix jq =
+      new JamaMatrix(q, QRQType, null, null, null, null, null);
+    jq.setStash(qr);
+
+    Object r = getR.invoke(qr, new Object[] {});
+    JamaMatrix jr =
+      new JamaMatrix(r, QRQType, null, null, null, null, null);
+
+    return new Data[] {jq, jr};
+  }
+
+
+  // New methods
+
+  /**
+   * Return the associated JAMA QRDecomposition object.
+   */
+  public Object getQRDecomposition() {
+    return qrd;
+  }
+
+  // Method wrappers for JAMA Matrix functionality
+
+  /**
+   * Get Q
+   * @return     Q matrix
+   */
+  public JamaMatrix getQ() throws VisADException, RemoteException {
+    if (classQRDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(0);
+  }
+
+  /**
+   * Get R
+   * @return     R matrix
+   */
+  public JamaMatrix getR() throws VisADException, RemoteException {
+    if (classQRDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(1);
+  }
+
+  public JamaMatrix getH()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classQRDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = getH.invoke(qrd, new Object[] {});
+    return new JamaMatrix(m, QRHType, null, null, null, null, null);
+  }
+
+  public boolean isFullRank()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classQRDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    boolean fr =
+      ((Boolean) isFullRank.invoke(qrd, new Object[] {})).booleanValue();
+    return fr;
+  }
+
+  /**
+   * Solve A*X = B
+   * @param B    right hand side
+   * @return     solution if A is square, least squares solution otherwise
+   */
+  public JamaMatrix solve(JamaMatrix B)
+         throws VisADException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classQRDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = solve.invoke(qrd, new Object[] {B.getMatrix()});
+    return new JamaMatrix(m);
+  }
+
+}
+
diff --git a/visad/matrix/JamaSingularValueDecomposition.java b/visad/matrix/JamaSingularValueDecomposition.java
new file mode 100644
index 0000000..54e41f2
--- /dev/null
+++ b/visad/matrix/JamaSingularValueDecomposition.java
@@ -0,0 +1,335 @@
+//
+// JamaSingularValueDecomposition.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.matrix;
+
+import java.lang.reflect.*;
+import java.rmi.RemoteException;
+
+import visad.*;
+
+/**
+ * JamaSingularValueDecomposition is a VisAD wrapper for JAMA
+ * SingularValueDecompositions.
+ * This class requires the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA package</a>.
+ */
+public class JamaSingularValueDecomposition extends Tuple {
+
+  private static final RealType SVS_row =
+    RealType.getRealType("SV_S_row");
+
+  private static final RealType SVS_column =
+    RealType.getRealType("SV_S_column");
+
+  private static final RealType SVS_value =
+    RealType.getRealType("SV_S_value");
+
+  private static final FunctionType SVSType = constructSFunction();
+
+  private static FunctionType constructSFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(SVS_row, SVS_column);
+      FunctionType function = new FunctionType(tuple, SVS_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType SVV_row =
+    RealType.getRealType("SV_V_row");
+
+  private static final RealType SVV_column =
+    RealType.getRealType("SV_V_column");
+
+  private static final RealType SVV_value =
+    RealType.getRealType("SV_V_value");
+
+  private static final FunctionType SVVType = constructVFunction();
+
+  private static FunctionType constructVFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(SVV_row, SVV_column);
+      FunctionType function = new FunctionType(tuple, SVV_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType SVU_row =
+    RealType.getRealType("SV_H_row");
+
+  private static final RealType SVU_column =
+    RealType.getRealType("SV_H_column");
+
+  private static final RealType SVU_value =
+    RealType.getRealType("SV_H_value");
+
+  private static final FunctionType SVUType = constructHFunction();
+
+  private static FunctionType constructHFunction() {
+    try {
+      RealTupleType tuple = new RealTupleType(SVU_row, SVU_column);
+      FunctionType function = new FunctionType(tuple, SVU_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final RealType singular_domain =
+    RealType.getRealType("singular_domain");
+
+  private static final RealType singular_value =
+    RealType.getRealType("singular_value");
+
+  private static final FunctionType singularType = constructSVFunction();
+
+  private static FunctionType constructSVFunction() {
+    try {
+      FunctionType function = new FunctionType(singular_domain, singular_value);
+      return function;
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+      return null;
+    }
+  }
+
+  private static final Class[] classes = constructClasses();
+
+  private static Class[] constructClasses() {
+    Class[] cs = new Class[6];
+    try {
+      cs[0] = Class.forName("Jama.Matrix");
+      cs[1] = Class.forName("Jama.CholeskyDecomposition");
+      cs[2] = Class.forName("Jama.EigenvalueDecomposition");
+      cs[3] = Class.forName("Jama.LUDecomposition");
+      cs[4] = Class.forName("Jama.QRDecomposition");
+      cs[5] = Class.forName("Jama.SingularValueDecomposition");
+    }
+    catch (ClassNotFoundException e) {
+      throw new RuntimeException("you need to install Jama from " +
+                                 "http://math.nist.gov/javanumerics/jama/");
+    }
+    return cs;
+  }
+
+  private static final Class classMatrix = classes[0];
+  private static final Class classCholeskyDecomposition = classes[1];
+  private static final Class classEigenvalueDecomposition = classes[2];
+  private static final Class classLUDecomposition = classes[3];
+  private static final Class classQRDecomposition = classes[4];
+  private static final Class classSingularValueDecomposition = classes[5];
+
+  /** associated JAMA SingularValueDecomposition object */
+  private Object svd;
+
+  /** useful methods from Jama.SVDecomposition class */
+  private static final Method[] methods =
+    constructMethods();
+
+  private static Method[] constructMethods() {
+    Method[] ms = new Method[7];
+    try {
+      Class[] param = new Class[] {};
+      ms[0] = classSingularValueDecomposition.getMethod("getU", param);
+      ms[1] = classSingularValueDecomposition.getMethod("getS", param);
+      ms[2] = classSingularValueDecomposition.getMethod("getV", param);
+      ms[3] = classSingularValueDecomposition.getMethod("getSingularValues", param);
+      ms[4] = classSingularValueDecomposition.getMethod("cond", param);
+      ms[5] = classSingularValueDecomposition.getMethod("norm2", param);
+      ms[6] = classSingularValueDecomposition.getMethod("rank", param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+    }
+    return ms;
+  }
+
+  private static final Method getU = methods[0];
+  private static final Method getS = methods[1];
+  private static final Method getV = methods[2];
+  private static final Method getSingularValues = methods[3];
+  private static final Method cond = methods[4];
+  private static final Method norm2 = methods[4];
+  private static final Method rank = methods[4];
+
+  private static final Constructor matrixSVDecomposition =
+    constructConstructor();
+
+  private static Constructor constructConstructor() {
+    try {
+      Class[] param = new Class[] {classMatrix};
+      return classSingularValueDecomposition.getConstructor(param);
+    }
+    catch (NoSuchMethodException e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  // Constructors
+
+  /**
+   * Construct a new JamaSingularValueDecomposition from a JamaMatrix.
+   */
+  public JamaSingularValueDecomposition(JamaMatrix matrix)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    this(matrixSVDecomposition.newInstance(new Object[] {matrix.getMatrix()}),
+         false);
+  }
+
+  JamaSingularValueDecomposition(Object sv, boolean copy)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    super(makeDatums(sv), copy);
+    svd = ((JamaMatrix) getComponent(0)).getStash();
+  }
+
+  private static Data[] makeDatums(Object sv)
+          throws VisADException, RemoteException, IllegalAccessException,
+                 InstantiationException, InvocationTargetException {
+    Object u = getU.invoke(sv, new Object[] {});
+    JamaMatrix ju =
+      new JamaMatrix(u, SVUType, null, null, null, null, null);
+    ju.setStash(sv);
+
+    Object v = getV.invoke(sv, new Object[] {});
+    JamaMatrix jv =
+      new JamaMatrix(v, SVVType, null, null, null, null, null);
+
+    double[] singular = (double[]) getSingularValues.invoke(sv, new Object[] {});
+    FlatField sf = new FlatField(singularType, new Integer1DSet(singular.length));
+    sf.setSamples(new double[][] {singular});
+
+    return new Data[] {ju, jv, sf};
+  }
+
+
+  // New methods
+
+  /**
+   * Return the associated JAMA SVDecomposition object.
+   */
+  public Object getSVDecomposition() {
+    return svd;
+  }
+
+  // Method wrappers for JAMA Matrix functionality
+
+  /**
+   * Get U
+   * @return     U matrix
+   */
+  public JamaMatrix getU() throws VisADException, RemoteException {
+    if (classSingularValueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(0);
+  }
+
+  /**
+   * Get V
+   * @return     V matrix
+   */
+  public JamaMatrix getV() throws VisADException, RemoteException {
+    if (classSingularValueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    return (JamaMatrix) getComponent(1);
+  }
+
+  public JamaMatrix getS()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classSingularValueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    Object m = getS.invoke(svd, new Object[] {});
+    return new JamaMatrix(m, SVSType, null, null, null, null, null);
+  }
+
+  public double[] getSingularValues() throws VisADException, RemoteException {
+    if (classSingularValueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    FlatField sf = (FlatField) getComponent(2);
+    double[][] s = sf.getValues(false);
+    return s[0];
+  }
+
+  public double cond()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classSingularValueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val =
+      ((Double) cond.invoke(svd, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  public double norm2() 
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classSingularValueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    double val =
+      ((Double) norm2.invoke(svd, new Object[] {})).doubleValue();
+    return val;
+  }
+
+  public int rank()
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    if (classSingularValueDecomposition == null) {
+      throw new VisADException("you need to install Jama from " +
+                               "http://math.nist.gov/javanumerics/jama/");
+    }
+    int val =
+      ((Integer) rank.invoke(svd, new Object[] {})).intValue();
+    return val;
+  }
+
+}
+
diff --git a/visad/meteorology/ImageSequence.java b/visad/meteorology/ImageSequence.java
new file mode 100644
index 0000000..2c3c01d
--- /dev/null
+++ b/visad/meteorology/ImageSequence.java
@@ -0,0 +1,70 @@
+//
+//  ImageSequence.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+import java.rmi.RemoteException;
+
+/**
+ * Interface for representing a time sequence of single-banded images.
+ */
+public interface ImageSequence extends Field
+{
+    /**
+     * Return the list of times associated with this sequence.
+     * @return  array of image start times.
+     */
+    DateTime[] getImageTimes()
+        throws VisADException;
+
+    /**
+     * Return the number of images in the sequence.
+     * @return number of images
+     */
+    int getImageCount()
+        throws VisADException;
+
+    /**
+     * Get the image at the specified time
+     * @param dt  image time
+     * @return single banded image at that time.  
+     * @throws VisADException  no image at that time in the set.
+     * @throws RemoteException can't get remote image
+     */
+    SingleBandedImage getImage(DateTime dt)
+        throws VisADException, RemoteException;
+
+    /**
+     * Return the image at the index'th position in the sequence.
+     * @param  index  index in the sequence
+     * @return single banded image at that index
+     * @throws VisADException  no image at that index in the set.
+     * @throws RemoteException can't get remote image
+     */
+    SingleBandedImage getImage(int index)
+        throws VisADException, RemoteException;
+}
diff --git a/visad/meteorology/ImageSequenceImpl.java b/visad/meteorology/ImageSequenceImpl.java
new file mode 100644
index 0000000..1947351
--- /dev/null
+++ b/visad/meteorology/ImageSequenceImpl.java
@@ -0,0 +1,168 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+import java.rmi.RemoteException;
+import visad.util.DataUtility;
+
+/**
+ * Implementation of an ImageSequence.  The images in this Field are
+ * sorted by time.
+ */
+public class ImageSequenceImpl extends FieldImpl
+    implements ImageSequence
+{
+    //SingleBandedImage[] images;
+
+    /**
+     * Create an image sequence with the specified FunctionType.
+     *
+     * @param  type   new type for the sequence.  The FunctionType of all
+     *                the images must be the same as the range of type
+     *                and the domain must be RealType.Time.
+     * @param  images array of images to be in the sequence
+     *
+     * @throws VisADException  not the correct MathType or images have different
+     *                         MathTypes.
+     * @throws RemoteException couldn't create the remote object
+     */
+    public ImageSequenceImpl(FunctionType type, SingleBandedImage[] images)
+        throws VisADException, RemoteException
+    {
+        super(ensureFunctionType(type), makeDomainSet(images));
+        //this.images = images;
+        FunctionType rangeType = (FunctionType) type.getRange();
+        for (int i = 0; i < images.length; i++)
+        {
+            RealTuple timeTuple = 
+                new RealTuple(new Real[] {images[i].getStartTime()});
+            FunctionType imageRange = (FunctionType) images[i].getType();
+            SingleBandedImage image = 
+                (imageRange.equals(rangeType))
+                    ? images[i]
+                    : (SingleBandedImage) images[i].changeMathType(rangeType);
+            setSample(timeTuple, image, false);
+        }
+    }
+
+    /**
+     * Create an image sequence from an array of images
+     *
+     * @param  images   array of images to be in the sequence. The FunctionType 
+     *                  of all the images must be the same.
+     *
+     * @throws VisADException  images have different FunctionTypes.
+     * @throws RemoteException couldn't create the remote object
+     */
+    public ImageSequenceImpl(SingleBandedImage[] images)
+        throws VisADException, RemoteException
+    {
+        this( new FunctionType(RealType.Time, 
+                              (FunctionType) images[0].getType()), images);
+    }
+
+    private static SampledSet makeDomainSet(SingleBandedImage[] images)
+        throws VisADException, RemoteException
+    {
+        if (images == null) 
+            throw new VisADException("images can't be null");
+        DateTime[] startTimes = new DateTime[images.length];
+        for (int i = 0; i < images.length; i++)
+        {
+            startTimes[i] = images[i].getStartTime();
+        }
+        
+        return 
+            (startTimes.length > 1)
+               ? (SampledSet) DateTime.makeTimeSet(startTimes)
+               : (SampledSet) 
+                   new SingletonSet(new RealTuple(new Real[] {startTimes[0]}));
+    }
+
+    /**
+     * Return the list of times associated with this sequence.
+     * @return  array of image start times.
+     */
+    public DateTime[] getImageTimes()
+        throws VisADException
+    {
+        DateTime[] times = null;
+        if (getDomainSet().getLength() > 1)
+           times = DateTime.timeSetToArray( 
+               (Gridded1DDoubleSet) getDomainSet());
+        else
+           times = new DateTime[] {new DateTime( 
+               ((SingletonSet) getDomainSet()).getDoubles()[0][0])};
+        return times == null ? new DateTime[0] : times;
+    }
+
+    /**
+     * Return the number of images in the sequence.
+     * @return number of images
+     */
+    public int getImageCount()
+        throws VisADException
+    {
+        return ((SampledSet) getDomainSet()).getLength();
+    }
+
+    /**
+     * Get the image at the specified time
+     * @param dt  image time
+     * @return single banded image at that time.  
+     * @throws  VisADException  no image in the sequence at the requested time
+     */
+    public SingleBandedImage getImage(DateTime dt)
+        throws VisADException, RemoteException
+    {
+        return (SingleBandedImage) evaluate(dt);
+    }
+
+    /**
+     * Return the image at the index'th position in the sequence.
+     * @param  index in the sequence
+     * @return single banded image at that index
+     * @throws  VisADException  no image in the sequence at the requested index
+     */
+    public SingleBandedImage getImage(int index)
+        throws VisADException, RemoteException
+    {
+        return (SingleBandedImage) getSample(index);
+    }
+
+    private static FunctionType ensureFunctionType(FunctionType type)
+        throws VisADException
+    {
+        if (type.getDomain().equals(
+             DataUtility.ensureRealTupleType(RealType.Time)) &&
+             type.getRange() instanceof FunctionType &&
+             ((RealTupleType) ((FunctionType) 
+                 type.getRange()).getFlatRange()).getDimension() == 1)
+            return type;
+        else
+           throw new VisADException(
+               "Not a valid ImageSequence type: " + type);
+    }
+
+}
diff --git a/visad/meteorology/ImageSequenceManager.java b/visad/meteorology/ImageSequenceManager.java
new file mode 100644
index 0000000..9d0ca78
--- /dev/null
+++ b/visad/meteorology/ImageSequenceManager.java
@@ -0,0 +1,211 @@
+//
+//  ImageSequenceManager.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+import java.util.TreeMap;
+import java.util.List;
+import java.util.Collection;
+import java.rmi.RemoteException;
+
+/**
+ * A class to create and manage image sequences.
+ *
+ * @author  Don Murray, Unidata
+ */
+public class ImageSequenceManager extends Object
+{
+    private ImageSequence sequence = null;
+    private TreeMap imageMap;
+
+    /**
+     * Create an empty image sequence.
+     *
+     * @throws VisADException   Couldn't create an empty sequence.
+     * @throws RemoteException  Couldn't create remote object.
+     */
+    public ImageSequenceManager()
+        throws VisADException, RemoteException
+    {
+        this(new SingleBandedImage[0]);
+    }
+
+    /**
+     * Create an image sequence from the array of images.
+     *
+     * @param  images  array of images for the sequence.
+     * @throws VisADException   Couldn't create the necessary VisAD object
+     * @throws RemoteException  Couldn't create remote object.
+     */
+    public ImageSequenceManager(SingleBandedImage[] images)
+        throws VisADException, RemoteException
+    {
+        imageMap = new TreeMap();
+        for (int i = 0; i < images.length; i++)
+            imageMap.put(images[i].getStartTime(), images[i]);
+        makeNewSequence();
+    }
+
+    /**
+     * Create an image sequence from an array of images.
+     *
+     * @param  images  array of images for the sequence.
+     * @return  an ImageSequence
+     * @throws VisADException   Couldn't create the sequence.
+     * @throws RemoteException  Couldn't create remote object.
+     */
+    public static ImageSequence createImageSequence(SingleBandedImage[] images)
+        throws VisADException, RemoteException
+    {
+        return new ImageSequenceImpl(images);
+    }
+
+    /**
+     * Add an image to the the sequence this object is managing.
+     *
+     * @param  image  image to add
+     * @return  sequence containing the new image.
+     * @throws VisADException   Couldn't create the sequence.
+     * @throws RemoteException  Couldn't create remote object.
+     */
+    public ImageSequence addImageToSequence(SingleBandedImage image)
+        throws VisADException, RemoteException
+    {
+        return addImagesToSequence(new SingleBandedImage[] {image});
+    }
+
+    /**
+     * Add an array of images to the the sequence this object is managing.
+     *
+     * @param  images  images to add
+     * @return  sequence containing the new images.
+     * @throws VisADException   Couldn't create the sequence.
+     * @throws RemoteException  Couldn't create remote object.
+     */
+    public ImageSequence addImagesToSequence(SingleBandedImage[] images)
+        throws VisADException, RemoteException
+    {
+        for (int i = 0; i < images.length; i++)
+            imageMap.put(images[i].getStartTime(), images[i]);
+        makeNewSequence();
+        return sequence;
+    }
+
+
+    public ImageSequence addImagesToSequence(List<SingleBandedImage> images)
+        throws VisADException, RemoteException
+    {
+        for (int i = 0; i < images.size(); i++)
+            imageMap.put(images.get(i).getStartTime(), images.get(i));
+        makeNewSequence();
+        return sequence;
+    }
+
+    /**
+     * Remove an image from the sequence.
+     *
+     * @param  time   time of image to remove
+     * @throws VisADException   Couldn't create the sequence.
+     */
+    public ImageSequence removeImageAtTime(DateTime time)
+        throws VisADException
+    {
+        if (time == null)
+            throw new VisADException("Time can't be null");
+        try
+        {
+            imageMap.remove(time);
+            makeNewSequence();
+        }
+        catch (Exception excp)
+        {
+            throw new VisADException(
+                "Unable to remove image at " + time + " from sequence");
+        }
+        return sequence;
+    }
+
+    /**
+     * Remove all images from the sequence.
+     *
+     */ 
+    public void clearSequence()
+    {
+        imageMap.clear();
+        sequence = null;
+    }
+
+    /**
+     * Set the sequence that this object is to manage.
+     *
+     * @param  newSequence  sequence to use (can't be null)
+     * @throws VisADException   Couldn't create the sequence.
+     * @throws RemoteException  Couldn't create remote object.
+     */
+    public void setImageSequence(ImageSequence newSequence)
+        throws VisADException, RemoteException
+    {
+        if (newSequence == null) 
+            throw new VisADException("New sequence can't be null");
+        clearSequence();
+        int numImages = newSequence.getDomainSet().getLength();
+        for (int i = 0; i < numImages; i++)
+        {
+            SingleBandedImage image = 
+                (SingleBandedImage) newSequence.getSample(i);
+            imageMap.put(image.getStartTime(), image);
+        }
+        sequence = newSequence;
+    }
+
+    /**
+     * Get the sequence that this object is to manage.
+     *
+     * @return  sequence that is being managed.
+     */
+    public ImageSequence getImageSequence()
+    {
+        return sequence;
+    }
+
+    private void makeNewSequence()
+        throws VisADException, RemoteException
+    {
+        if (imageMap.isEmpty()) 
+            sequence = null;
+        else
+        {
+            Collection imageSet = imageMap.values();
+            SingleBandedImage[] images =
+                (SingleBandedImage[]) imageSet.toArray(
+                    new SingleBandedImage[imageSet.size()]);
+            FunctionType imageFunction = (FunctionType) images[0].getType();
+            FunctionType ftype = new FunctionType(RealType.Time, imageFunction);
+            sequence = new ImageSequenceImpl(ftype, images);
+        }
+    }
+}
diff --git a/visad/meteorology/NavigatedImage.java b/visad/meteorology/NavigatedImage.java
new file mode 100644
index 0000000..a72eda6
--- /dev/null
+++ b/visad/meteorology/NavigatedImage.java
@@ -0,0 +1,173 @@
+//
+//  NavigatedImage.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+import visad.georef.*;
+
+/**
+ * An implementation for representing single-banded planar 
+ * satellite or radar imagery.  This specific type of SingleBandedImage has a 
+ * navigation that will map domain values to latitude and longitude.
+ */
+public class NavigatedImage
+    extends SingleBandedImageImpl
+    implements NavigatedField
+    
+{
+    private NavigatedCoordinateSystem navigation;
+
+    /**
+     * Construct a NavigatedImage without any data.  
+     *
+     * @param  function  FunctionType for this image.   It must have a domain
+     *                   with a NavigatedCoordinateSystem and a Range that
+     *                   has only one (Real) component.
+     * @param  domain    DomainSet for this image.  The domain must have
+     *                   mappings to Latitude/Longitude and or have a
+     *                   NavigatedCoordinateSystem associated with it.
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     *
+     * @throws  VisADException  couldn't create the NavigatedImage
+     */
+    public NavigatedImage(FunctionType function, 
+                          Set domain, 
+                          DateTime startTime, 
+                          String desc)
+        throws VisADException
+    {
+        this(new FlatField(function, domain), 
+             startTime, 
+             desc);
+    }
+
+    /**
+     * Construct a NavigatedImage from a FlatField.
+     *
+     * @param  image     FlatField representing an image.  It must
+     *                   have a domain with a NavigatedCoordinateSystem
+     *                   and a Range that only has one (Real) component.
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     *
+     * @throws  VisADException  couldn't create the NavigatedImage
+     */
+    public NavigatedImage(FlatField image, 
+                          DateTime startTime, 
+                          String desc)
+        throws VisADException
+    {
+        this(image, startTime, desc, true);
+    }
+
+    /**
+     * Construct a NavigatedImage from a FlatField.
+     *
+     * @param  image     FlatField representing an image.  It must
+     *                   have a domain with a NavigatedCoordinateSystem
+     *                   and a Range that only has one (Real) component.
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     * @param  copyData  make a copy of the samples
+     *
+     * @throws  VisADException  couldn't create the NavigatedImage
+     */
+    public NavigatedImage(FlatField image, 
+                          DateTime startTime, 
+                          String desc,
+                          boolean copyData)
+        throws VisADException
+    {
+        super(image, startTime, desc);
+
+        // make sure the domain is okay
+        vetDomain();
+    }
+    
+    /**
+     * Get the coordinate system representing the navigation for the domain.
+     *
+     * @return NavigatedCoordinateSystem for the domain of this field.
+     */
+    public NavigatedCoordinateSystem getNavigation()
+    {
+        return navigation;
+    }
+
+    private void vetDomain()
+        throws VisADException
+    {
+        CoordinateSystem cs = getDomainCoordinateSystem();
+        if (cs != null && !(cs instanceof NavigatedCoordinateSystem))
+        {
+            throw new VisADException(
+                "NavigatedImage: Domain CoordinateSystem must be " +
+                "a NavigatedCoordinateSystem");
+        }
+        else if (cs == null)
+        {
+            if (getDomainSet().getDimension() < 2 ||
+               !hasLatLon( (RealTupleType) getDomainSet().getType()))
+            throw new VisADException(
+                "NavigatedImage: Domain set must have " +
+                "a Lat/Lon reference");
+            cs = 
+                new TrivialNavigation(((FunctionType) getType()).getDomain());
+        }
+        navigation = (NavigatedCoordinateSystem) cs;
+    } 
+
+    private boolean hasLatLon(RealTupleType type)
+    {
+        return (type.getIndex(RealType.Latitude) > -1 &&
+                type.getIndex(RealType.Longitude) > -1);
+    }
+
+    /**
+     * Check to see if this image has a domain that can map to Latitude
+     * and Longitude.
+     *
+     * @return true if it has navigation, otherwise false
+     */
+    public boolean isNavigated()
+    {
+        return true;
+    }
+
+    /** return new NavigatedImage with value 'op this' */
+    public Data unary(int op, MathType new_type, 
+                      int sampling_mode, int error_mode)
+                  throws VisADException
+    {
+        return 
+            new NavigatedImage(
+                (FlatField) 
+                    super.unary(op, new_type, sampling_mode, error_mode),
+                getStartTime(), getDescription(), false);
+    }
+}
diff --git a/visad/meteorology/SatelliteData.java b/visad/meteorology/SatelliteData.java
new file mode 100644
index 0000000..a7660da
--- /dev/null
+++ b/visad/meteorology/SatelliteData.java
@@ -0,0 +1,43 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+
+/**
+ * An interface for defining properties associated with satellite data.
+ */
+public interface SatelliteData extends Data
+{
+    /**
+     * Get the name of the sensor.
+     * @return  sensor description
+     */
+    String getSensorName();
+
+    /**
+     * Get a description of this satellite data.
+     * @return  description
+     */
+    String getDescription();
+}
diff --git a/visad/meteorology/SatelliteImage.java b/visad/meteorology/SatelliteImage.java
new file mode 100644
index 0000000..e8186ba
--- /dev/null
+++ b/visad/meteorology/SatelliteImage.java
@@ -0,0 +1,108 @@
+//
+//  SatelliteImage.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+
+/**
+ * An implementation for representing single-banded planar satellite
+ * that has navigation.  
+ */
+public class SatelliteImage extends SingleBandedImageImpl
+    implements SatelliteData
+    
+{
+    private String sensorName;
+
+    /**
+     * Construct a SatelliteImage without any data.  
+     *
+     * @param  function  FunctionType for this image.   It must have a 
+     *                   Range that has only one (Real) component.
+     * @param  domain    DomainSet for this image.  
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     * @param  sensor    sensor description
+     *
+     * @throws  VisADException  couldn't create the SatelliteImage
+     */
+    public SatelliteImage(FunctionType function, 
+                          Set domain, 
+                          DateTime startTime, 
+                          String desc,
+                          String sensor)
+        throws VisADException
+    {
+        this(new FlatField(function, domain), 
+             startTime, 
+             desc,  
+             sensor);
+    }
+
+    
+    /**
+     * Construct a Satellite Image from a FlatField.
+     *
+     * @param  image     FlatField representing an image.  It must
+     *                   have a Range that only has one (Real) component.
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     * @param  sensor    sensor name
+     *
+     * @throws  VisADException  couldn't create the NavigatedImage
+     */
+    public SatelliteImage(FlatField image, 
+                          DateTime startTime, 
+                          String desc,
+                          String sensor)
+        throws VisADException
+    {
+        super(image, startTime, desc);
+        sensorName = sensor;
+    }
+    
+    /**
+     * Get a description of the sensor.
+     * @return  sensor description
+     */
+    public String getSensorName()
+    {
+        return sensorName;
+    }
+        
+    /** return new SatelliteImage with value 'op this' */
+    public Data unary(int op, MathType new_type, 
+                      int sampling_mode, int error_mode)
+                  throws VisADException
+    {
+        return 
+            new SatelliteImage(
+                (FlatField)
+                    super.unary(op, new_type, sampling_mode, error_mode),
+                getStartTime(), getDescription(), sensorName);
+    }
+}
diff --git a/visad/meteorology/SingleBandedImage.java b/visad/meteorology/SingleBandedImage.java
new file mode 100644
index 0000000..745bdf4
--- /dev/null
+++ b/visad/meteorology/SingleBandedImage.java
@@ -0,0 +1,73 @@
+//
+//  SingleBandedImage.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+
+/**
+ * An interface for representing single--banded planar satellite or 
+ * radar imagery.  This type of Field has a Range Dimension of 1 with
+ * a range Type being a RealType.
+ */
+public interface SingleBandedImage extends Field
+{
+    /**
+     * Get the start time of the image.
+     * @return  DateTime representing the start time of the image.
+     */
+    DateTime getStartTime();
+
+    /**
+     * Return a descriptive string for this image.
+     * @return description
+     */
+    String getDescription();
+
+    /**
+     * Get the minimum possible value for this image
+     * @return  a Real representing the minimum possible value.  Using a
+     *          Real allows us to associate units and error estimates with
+     *          the value
+     */
+    Real getMinRangeValue();
+
+    /**
+     * Get the maximum possible value for this image
+     * @return  a Real representing the maximum possible value.  Using a
+     *          Real allows us to associate units and error estimates with
+     *          the value
+     */
+    Real getMaxRangeValue();
+
+    /**
+     * Check to see if this image has a domain that can map to Latitude
+     * and Longitude.
+     *
+     * @return true if it has navigation, otherwise false
+     */
+    boolean isNavigated();
+}
diff --git a/visad/meteorology/SingleBandedImageImpl.java b/visad/meteorology/SingleBandedImageImpl.java
new file mode 100644
index 0000000..a55ba22
--- /dev/null
+++ b/visad/meteorology/SingleBandedImageImpl.java
@@ -0,0 +1,508 @@
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import java.rmi.RemoteException;
+
+import visad.*;
+import visad.georef.*;
+import java.rmi.RemoteException;
+import visad.util.DataUtility;
+
+/**
+ * An implementation for representing single-banded planar 
+ * satellite or radar imagery.  
+ */
+public class SingleBandedImageImpl
+    extends FlatField
+    implements SingleBandedImage
+    
+{
+    private DateTime startTime;
+    private String description;
+    private Real minValue;
+    private Real maxValue;
+    private boolean copyOnClone = true;
+
+    /**
+     * Construct a SingleBandedImageImpl without any data.  
+     *
+     * @param  function  FunctionType for this image.   It must have a 
+     *                   Range that has only one (Real) component.
+     * @param  domain    DomainSet for this image.  
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     *
+     * @throws  VisADException  couldn't create the SingleBandedImageImpl
+     */
+    public SingleBandedImageImpl(FunctionType function, 
+                          Set domain, 
+                          DateTime startTime, 
+                          String desc)
+        throws VisADException
+    {
+        this(new FlatField(function, domain), startTime, desc);
+    }
+
+
+    /**
+     * Construct a SingleBandedImageImpl from a FlatField.
+     *
+     * @param  image     FlatField representing an image.  It must
+     *                   have a Range that only has one (Real) component.
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     *
+     * @throws  VisADException  couldn't create the SingleBandedImageImpl
+     */
+    public SingleBandedImageImpl(FlatField image, 
+                          DateTime startTime, 
+                          String desc)
+        throws VisADException
+    {
+        this(image, startTime, desc, true);
+    }
+
+    /**
+     * Construct a SingleBandedImage from the FlatField specified.
+     *
+     * @param  image     FlatField representing an image.  It must
+     *                   have a Range that only has one (Real) component.
+     * @param  startTime starting time of the image.
+     * @param  desc      description
+     * @param  copyData  make a copy of the data on setSample call
+     *
+     * @throws  VisADException  couldn't create the SingleBandedImageImpl
+     */
+    public SingleBandedImageImpl(FlatField image, 
+                          DateTime startTime, 
+                          String desc,
+                          boolean copyData)
+        throws VisADException
+    {
+        super((FunctionType) image.getType(), image.getDomainSet(),
+              image.getRangeCoordinateSystem()[0],
+              image.getRangeSets(), 
+              DataUtility.getRangeUnits(image));
+
+        // vet the range
+        if (((FunctionType) 
+              getType()).getFlatRange().getNumberOfRealComponents() > 1)
+        {
+            throw new VisADException(
+                "SingleBandedImageImpl: Range must be a RealType or " +
+                "RealTupleType with one component");
+        }
+
+        // add in the data
+        try
+        {
+            if (!image.isMissing()) 
+            {
+                setSamples(
+                   //image.getFloats(false), image.getRangeErrors(), copyData);
+                   image.getFloats(false), copyData);
+            }
+        }
+        catch (java.rmi.RemoteException re) {;}  // can't happen since local
+
+        // set some local variables
+        this.startTime = startTime;
+        description = desc;
+    }
+    
+    /**
+     * Get the start time of the image.
+     * @return  DateTime representing the start time of the image.
+     */
+    public DateTime getStartTime()
+    {
+        return startTime;
+    }
+
+    /**
+     * Return a descriptive string for this image.
+     * @return description
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * Get the minimum possible value for this image
+     * @return  a Real representing the minimum possible value.  Using a
+     *          Real allows us to associate units and error estimates with
+     *          the value
+     */
+    public Real getMinRangeValue()
+    {
+        checkMaxMinValues();
+        return minValue;
+    }
+
+    /**
+     * Get the maximum possible value for this image
+     * @return  a Real representing the maximum possible value.  Using a
+     *          Real allows us to associate units and error estimates with
+     *          the value
+     */
+    public Real getMaxRangeValue()
+    {
+        checkMaxMinValues();
+        return maxValue;
+    }
+
+    /**
+     * Check to see if this image has a domain that can map to Latitude
+     * and Longitude.
+     *
+     * @return true if it has navigation, otherwise false
+     */
+    public boolean isNavigated()
+    {
+        return getDomainCoordinateSystem() instanceof NavigatedCoordinateSystem;
+    }
+
+    /** 
+     * Set the range values of the function including ErrorEstimate-s;
+     * the order of range values must be the same as the order of
+     * domain indices in the DomainSet.  Overridden so we can set
+     * max and min values.
+     *
+     * @param  range    pixel values as doubles
+     * @param  errors   ErrorEstimates for values (may be null);
+     * @param  copy     flag to make a copy of value array or not
+     *
+     * @throws VisADException  couldn't set values
+     * @throws RemoteException couldn't set remote object
+     */
+    public void setSamples(float[][] range, 
+                           ErrorEstimate[] errors, 
+                           boolean copy) 
+        throws VisADException, RemoteException 
+    {
+        super.setSamples(range, errors, copy);
+        minValue = maxValue = null;
+    }
+
+    /** 
+     * Set the range values of the function including ErrorEstimate-s;
+     * the order of range values must be the same as the order of
+     * domain indices in the DomainSet.  Overridden so we can set
+     * max and min values.
+     *
+     * @param  range    pixel values as doubles
+     * @param  errors   ErrorEstimates for values (may be null);
+     * @param  copy     flag to make a copy of value array or not
+     *
+     * @throws VisADException  couldn't set values
+     * @throws RemoteException couldn't set remote object
+     */
+    public void setSamples(double[][] range, 
+                           ErrorEstimate[] errors, 
+                           boolean copy) 
+        throws VisADException, RemoteException 
+    {
+        super.setSamples(range, errors, copy);
+        minValue = maxValue = null;
+    }
+
+    /** return new SingleBandedImageImpl with value 'op this' */
+    public Data unary(int op, MathType new_type, 
+                      int sampling_mode, int error_mode)
+                  throws VisADException
+    {
+        return 
+            new SingleBandedImageImpl(
+                (FlatField) 
+                    super.unary(op, new_type, sampling_mode, error_mode),
+                startTime, description, false);
+    }
+
+    private void checkMaxMinValues() {
+        if(minValue == null) {
+            try {
+                setMaxMinValues();
+            } catch(Exception exc) {
+                System.err.println ("error:" + exc);
+                exc.printStackTrace();
+            }
+        }
+    }
+
+
+
+    private void setMaxMinValues()
+        throws VisADException
+    {
+        Unit units = null;
+        RealType type = RealType.Generic;
+        ErrorEstimate errors = null;
+        float min = Float.POSITIVE_INFINITY;
+        float max = Float.NEGATIVE_INFINITY;
+        try
+        {
+            Set rangeSet = getRangeSets()[0];
+            units = getRangeUnits()[0][0];
+            type = (RealType) 
+                ((RealTupleType) 
+                ((SetType) rangeSet.getType()).getDomain()).getComponent(0);
+            errors = getRangeErrors()[0];
+            if (rangeSet instanceof SampledSet)
+            {
+                min = ((SampledSet) rangeSet).getLow()[0];
+                max = ((SampledSet) rangeSet).getHi()[0];
+            }
+            else
+            {
+                float[] values = getFloats(false)[0];
+                final int len = values.length;
+                for (int i = 0; i < len; i++)
+                {
+                    float value = values[i];
+                    if (value < min)  min = value;
+                    if (value > max)  max = value;
+                }
+            }
+        }
+        catch (Exception e) {;}
+        minValue =  new Real(type, min, units, errors);
+        maxValue =  new Real(type, max, units, errors);
+    }
+
+    /**
+     * Return the result of a binary operation between this instance and another
+     * operand.  If the other operand is an object of this class and the result
+     * of the {@link FlatField#binary(Data, int, int, int)} method is a
+     * {@link FlatField} from which an object of this class can be constructed,
+     * then this method returns an instance of this class with a description
+     * determined by the input descriptions and the operation and a time equal
+     * to the average times of the input; otherwise, the object resulting from
+     * the {@link FlatField#binary} method is returned.
+     *
+     * @param data                 The other operand of the operation.
+     * @param op                   The operation to perform (e.g. {@link
+     *                             Data#ADD}, {@link Data#DIVIDE}, etc.).
+     * @param samplingMode         The sampling mode.  One of {@link 
+     *                             Data#NEAREST_NEIGHBOR} or {@link
+     *                             Data#WEIGHTED_AVERAGE}.
+     * @param errorMode            The error propagation mode.  One of {@link
+     *                             Data#NO_ERRORS}, {@link Data#INDEPENDENT},
+     *                             or {@link Data#DEPENDENT}.
+     * @return                     The result of the operation on this
+     *                             instance and the other operand.
+     * @throws VisADException      if a VisAD failure occurs.
+     * @throws RemovetException    if a Java RMI failure occurs.
+     */
+    public Data binary(Data data, int op, int samplingMode, int errorMode) 
+        throws VisADException, RemoteException {
+
+        Data result = super.binary(data, op, samplingMode, errorMode);
+
+        if (data instanceof SingleBandedImage && result instanceof FlatField) {
+
+            SingleBandedImage that = (SingleBandedImage)data;
+            double            time1 = startTime.getReal().getValue();
+            double            time2 = that.getStartTime().getReal().getValue();
+            DateTime          time = new DateTime((time1 + time2) / 2);
+            String            desc;
+            String            desc1 = description;
+            String            desc2 = that.getDescription();
+
+            if (desc1.indexOf(' ') != -1)
+                desc1 = "(" + desc1 + ")";
+
+            if (desc2.indexOf(' ') != -1)
+                desc2 = "(" + desc2 + ")";
+
+            switch (op) {
+                case ADD:
+                    desc = desc1 + " + " + desc2;
+                    break;
+                case SUBTRACT:
+                    desc = desc1 + " - " + desc2;
+                    break;
+                case MULTIPLY:
+                    desc = desc1 + " * " + desc2;
+                    break;
+                case DIVIDE:
+                    desc = desc1 + " / " + desc2;
+                    break;
+                case POW:
+                    desc = "POW(" + desc1 + ", " + desc2 + ")";
+                    break;
+                case MAX:
+                    desc = "MAX(" + desc1 + ", " + desc2 + ")";
+                    break;
+                case MIN:
+                    desc = "MIN(" + desc1 + ", " + desc2 + ")";
+                    break;
+                case ATAN:
+                    desc = "ATAN2(" + desc1 + ", " + desc2 + ")";
+                    break;
+                case ATAN2_DEGREES:
+                    desc = "ATAN2_DEGREES(" + desc1 + ", " + desc2 + ")";
+                    break;
+                case REMAINDER:
+                    desc = "REMAINDER(" + desc1 + ", " + desc2 + ")";
+                    break;
+                default:
+                    throw new Error("Assertion Failure");
+            }
+
+            try {
+                result = 
+                    new SingleBandedImageImpl((FlatField)result, time, desc, false);
+            }
+            catch (VisADException ex) {
+                // do nothing: return the original result
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Return the result of a unary operation on this instance.  If the result
+     * of the {@link FlatField#unary(int, int, int)} method is a {@link
+     * FlatField} from which an object of this class can be constructed,
+     * then this method returns an instance of this class with a description
+     * determined by the input description and the operation and a time equal
+     * to the time of this instance; otherwise, the object resulting from the
+     * {@link FlatField#unary} method is returned.
+     *
+     * @param op                   The operation to perform (e.g. {@link
+     *                             Data#ABS}, {@link Data#COS}, etc.).
+     * @param samplingMode         The sampling mode.  One of {@link 
+     *                             Data#NEAREST_NEIGHBOR} or {@link
+     *                             Data#WEIGHTED_AVERAGE}.
+     * @param errorMode            The error propagation mode.  One of {@link
+     *                             Data#NO_ERRORS}, {@link Data#INDEPENDENT},
+     *                             or {@link Data#DEPENDENT}.
+     * @return                     The result of the operation on this instance.
+     * @throws VisADException      if a VisAD failure occurs.
+     * @throws RemovetException    if a Java RMI failure occurs.
+     */
+    public Data unary(int op, int samplingMode, int errorMode) 
+        throws VisADException, RemoteException {
+
+        Data result = super.unary(op, samplingMode, errorMode);
+
+        if (result instanceof FlatField) {
+
+            String            desc = description;
+
+            switch (op) {
+                case ABS:
+                    desc = "ABS(" + desc + ")";
+                    break;
+                case ACOS:
+                    desc = "ACOS(" + desc + ")";
+                    break;
+                case ACOS_DEGREES:
+                    desc = "ACOS_DEGREES(" + desc + ")";
+                    break;
+                case ASIN:
+                    desc = "ASIN(" + desc + ")";
+                    break;
+                case ASIN_DEGREES:
+                    desc = "ASIN_DEGREES(" + desc + ")";
+                    break;
+                case ATAN:
+                    desc = "ATAN(" + desc + ")";
+                    break;
+                case ATAN_DEGREES:
+                    desc = "ATAN_DEGREES(" + desc + ")";
+                    break;
+                case CEIL:
+                    desc = "CEIL(" + desc + ")";
+                    break;
+                case COS:
+                    desc = "COS(" + desc + ")";
+                    break;
+                case COS_DEGREES:
+                    desc = "COS_DEGREES(" + desc + ")";
+                    break;
+                case EXP:
+                    desc = "EXP(" + desc + ")";
+                    break;
+                case FLOOR:
+                    desc = "FLOOR(" + desc + ")";
+                    break;
+                case LOG:
+                    desc = "LOG(" + desc + ")";
+                    break;
+                case RINT:
+                    desc = "RINT(" + desc + ")";
+                    break;
+                case ROUND:
+                    desc = "ROUND(" + desc + ")";
+                    break;
+                case SIN:
+                    desc = "SIN(" + desc + ")";
+                    break;
+                case SIN_DEGREES:
+                    desc = "SIN_DEGREES(" + desc + ")";
+                    break;
+                case SQRT:
+                    desc = "SQRT(" + desc + ")";
+                    break;
+                case TAN:
+                    desc = "TAN(" + desc + ")";
+                    break;
+                case TAN_DEGREES:
+                    desc = "TAN_DEGREES(" + desc + ")";
+                    break;
+                case NEGATE:
+                    desc = "NEGATE(" + desc + ")";
+                    break;
+                default:
+                    throw new Error("Assertion Failure");
+            }
+
+            try {
+                result = 
+                    new SingleBandedImageImpl((FlatField)result, startTime, desc, false);
+            }
+            catch (VisADException ex) {
+                // do nothing: return the original result
+            }
+        }
+
+        return result;
+    }
+
+
+
+    /**
+     * for effeciency provide access to the uncopied floats
+     */
+    public float[][] getImageData() throws VisADException {
+	return unpackFloats(false);
+    }
+
+
+}
diff --git a/visad/meteorology/WeatherSymbols.java b/visad/meteorology/WeatherSymbols.java
new file mode 100644
index 0000000..070defa
--- /dev/null
+++ b/visad/meteorology/WeatherSymbols.java
@@ -0,0 +1,579 @@
+//
+//  WeatherSymbols.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.meteorology;
+
+import visad.*;
+import visad.util.HersheyFont;
+import visad.java2d.DisplayImplJ2D;
+import javax.swing.JFrame;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+/**
+ * Utility class to create shapes for weather symbols.  Shapes are
+ * stroke generated from the "wmo" HersheyFont.
+ * @see "<a href="http://www.wmo.ch">WMO Manual on Codes</a> for a 
+ *       description of WMO weather codes."
+ */
+public class WeatherSymbols {
+
+  private static HersheyFont wmoFont = new HersheyFont("wmo");
+  private static final int numMetSymbols = 205;
+
+  private static VisADLineArray[] metSymbols = 
+      new VisADLineArray[numMetSymbols];
+
+
+  /** starting index of present weather symbols in the whole list */
+  private final static int PRESENTWX_INDEX = 0;
+  /** starting index of low cloud symbols in the whole list */
+  private final static int LOCLD_INDEX = 104;
+  /** starting index of mid cloud symbols in the whole list */
+  private final static int MIDCLD_INDEX = 113;
+  /** starting index of high cloud symbols in the whole list */
+  private final static int HICLD_INDEX = 122;
+  /** starting index of pressure tendency symbols in the whole list */
+  private final static int TNDCY_INDEX = 142;
+  /** starting index of cloud coverage symbols in the whole list */
+  private final static int SKY_INDEX = 131;
+  /** starting index of icing symbols in the whole list */
+  private final static int TURB_INDEX = 151;
+  /** starting index of icing symbols in the whole list */
+  private final static int ICING_INDEX = 160;
+  /** starting index of misc symbols in the whole list */
+  private final static int MISC_INDEX = 171;
+
+  /** Number of WMO weather symbols */
+  public final static int PRESENTWX_NUM = 104;
+
+  /** Number of low cloud symbols */
+  public final static int LOCLD_NUM = 9;
+
+  /** Number of mid cloud symbols */
+  public final static int MIDCLD_NUM  = 9;
+
+  /** Number of high cloud symbols */
+  public final static int HICLD_NUM = 9;
+
+  /** Number of pressure tendency symbols */
+  public final static int TNDCY_NUM = 9;
+
+  /** Number of cloud coverage symbols */
+  public final static int SKY_NUM = 11;
+
+  /** Number of icing symbols */
+  public final static int ICING_NUM = 9;
+
+  /** Number of turbulence symbols */
+  public final static int TURB_NUM = 11;
+
+  /** Number of miscellaneous symbols */
+  public final static int MISC_NUM = 34;
+
+  /** Number of lightning symbols */
+  public final static int LIGHTNING_NUM = 2;
+
+  private static VisADLineArray[] lightningSymbols = 
+      new VisADLineArray[LIGHTNING_NUM];
+
+  static {
+
+    // set up WMO symbols
+    double[] start = { 0, -.5, 0 };
+    double[] base = { 1, 0, 0 };
+    double[] up = { 0, 1., 0 };
+    try {
+      for (int i = 0; i < numMetSymbols; i++) {
+        metSymbols[i] = 
+          PlotText.render_font( 
+            new String(new byte[] {(byte) (i+32)}, 0), wmoFont, 
+                                      start, base, up, true);
+      }
+    } catch (Exception excp) {
+       System.err.println("Unable to intialize symbols properly");
+    }
+
+      // set up lightning symbols
+    VisADLineArray flash = new VisADLineArray();
+    flash.coordinates = new float[]
+    { 0.25f, 0.8f, 0.0f,   -0.25f,  0.0f,  0.0f,
+     -0.25f, 0.0f, 0.0f,    0.25f, -0.8f,  0.0f,
+      0.0f, -0.8f, 0.0f,    0.25f, -0.8f,  0.0f,
+      0.25f,-0.8f, 0.0f,    0.25f, -0.55f, 0.0f};
+    flash.vertexCount = flash.coordinates.length / 3;
+    lightningSymbols[0] = flash;
+
+    flash = new VisADLineArray();
+    flash.coordinates = new float[]
+    { -0.25f, 0.8f, 0.0f,    0.25f,  0.0f,  0.0f,
+       0.25f, 0.0f, 0.0f,   -0.25f, -0.8f,  0.0f,
+       0.0f, -0.8f, 0.0f,   -0.25f, -0.8f,  0.0f,
+      -0.25f,-0.8f, 0.0f,   -0.25f, -0.55f, 0.0f};
+    flash.vertexCount = flash.coordinates.length / 3;
+    lightningSymbols[1] = flash;
+  }
+
+  /**
+   * Default constructor.
+   */
+  public WeatherSymbols() {}
+
+  /**
+   * Get the array of shapes corresponding to the
+   * WMO present weather codes, plus the GEMPAK extensions.
+   * @return the array of shapes
+   */
+  public static VisADLineArray[] getPresentWeatherSymbols() {
+    return subsetArray(metSymbols, PRESENTWX_INDEX, PRESENTWX_NUM);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the
+   * WMO present weather codes, plus the GEMPAK extensions.
+   * @return the array of shapes
+   */
+  public static VisADLineArray[] getAllMetSymbols() {
+    return subsetArray(metSymbols, 0, numMetSymbols);
+  }
+
+  /**
+   * Get the shape corresponding to a particular present weather code.
+   * The number of codes are too numerous to list here.
+   * @param  wxCode  weather code
+   * @return shape for code
+   */
+  public static VisADLineArray getPresentWeatherSymbol(int wxCode) {
+    if (wxCode < 0 || wxCode >= PRESENTWX_NUM) {
+      throw new IllegalArgumentException( "unknown weather symbol: " + wxCode);
+    }
+    return getVLAClone(metSymbols, PRESENTWX_INDEX+wxCode);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the
+   * pressure tendency symbol codes.
+   * @return the array of shapes
+   * @see #getPressureTendencySymbol for the indices
+   */
+  public static VisADLineArray[] getPressureTendencySymbols() {
+    return subsetArray(metSymbols, TNDCY_INDEX, TNDCY_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular pressure
+   * tendency code.  Codes are:
+   * <pre>
+   *   0  -  rising then falling
+   *   1  -  rising then steady; or rising, then rising more slowly
+   *   2  -  rising steadily or unsteadily
+   *   3  -  falling or steady, then rising; or rising, 
+   *           then rising more quickly
+   *   4  -  steady, same as 3 hours ago
+   *   5  -  falling then rising, same or lower than 3 hours ago
+   *   6  -  falling then steady; or falling, then falling more slowly
+   *   7  -  falling steadily, or unsteadily
+   *   8  -  steady or rising, then falling; or falling, 
+   *           then falling more quickly
+   * </pre>
+   * @param  tendencyCode tendency code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getPressureTendencySymbol(int tendencyCode) {
+    if (tendencyCode < 0 || tendencyCode >= TNDCY_NUM) {
+       throw new IllegalArgumentException(
+             "unknown pressure tendency symbol: " + tendencyCode);
+    }
+    return getVLAClone(metSymbols, TNDCY_INDEX+tendencyCode);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the cloud coverage codes.
+   * @return the array of shapes
+   * @see #getCloudCoverageSymbol(int) for codes
+   */
+  public static VisADLineArray[] getCloudCoverageSymbols() {
+    return subsetArray(metSymbols, SKY_INDEX, SKY_NUM);
+  }
+
+  /**
+   * Look up the symbol directly in the full array
+   *
+   * @param index array index
+   * @return The symbol
+   */
+  public static VisADLineArray getSymbol(int index) {
+      if(index<0 || index>= metSymbols.length) {
+       throw new IllegalArgumentException(
+             "bad symbol index: " + index);
+      }
+      return getVLAClone(metSymbols,index);
+  }
+
+
+  /**
+   * Get the shape corresponding to a total sky cover code.
+   * Codes are:
+   * <pre>
+   *   0  -  No clouds
+   *   1  -  Less than one-tenth or one-tenth
+   *   2  -  two-tenths or three-tenths
+   *   3  -  four-tenths
+   *   4  -  five-tenths
+   *   5  -  six-tenths
+   *   6  -  seven-tenths or eight-tenths
+   *   7  -  nine-tenths or overcast with openings
+   *   8  -  completely overcast
+   *   9  -  sky obscured
+   *  10  -  missing
+   * </pre>
+   * @param   ccCode     cloud coverage code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getCloudCoverageSymbol(int ccCode) {
+    if (ccCode < 0 || ccCode >= SKY_NUM) {
+       throw new IllegalArgumentException(
+             "unknown cloud coverage symbol: " + ccCode);
+    }
+    return getVLAClone(metSymbols, SKY_INDEX+ccCode);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the low cloud codes.
+   * @return the array of shapes
+   * @see #getLowCloudSymbol(int) for codes
+   */
+  public static VisADLineArray[] getLowCloudSymbols() {
+    return subsetArray(metSymbols, LOCLD_INDEX, LOCLD_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular low cloud code.
+   * Codes are:
+   * <pre>
+   *   1  -  Cu of fair weather, little vertical development,
+   *            seemingly flattened
+   *   2  -  Cu of considerable development, generally towering,
+   *            with or without other Cu or Sc bases all at same level
+   *   3  -  Cb with tops lacking clear cut outlines, but distinctly
+   *            no cirriform or anvil-shaped; with or without Cu, Sc, or St
+   *   4  -  Sc formed by spreading out of Cu; Cu often present also
+   *   5  -  Sc not formed by spreading out of Cu
+   *   6  -  St or Fs or both, but no Fs of bad weather
+   *   7  -  Fs and/or Fc of bad weather (scud)
+   *   8  -  Cu and Sc (not formed by spreading out of Cu) with bases 
+   *            at different levels
+   *   9  -  Cb having a clearly fibrous (cirriform) top, often anvil-shaped,
+   *            with or without Cu, Sc, St, or scud
+   * </pre>
+   * @param   lcCode     low cloud code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getLowCloudSymbol(int lcCode) {
+    if (lcCode < 1 || lcCode > LOCLD_NUM) {
+       throw new IllegalArgumentException(
+           "unknown low cloud symbol: " + lcCode);
+    }
+    return getVLAClone(metSymbols, LOCLD_INDEX+lcCode-1);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the mid cloud codes.
+   * @return the array of shapes
+   * @see #getMidCloudSymbol(int) for codes
+   */
+  public static VisADLineArray[] getMidCloudSymbols() {
+    return subsetArray(metSymbols, MIDCLD_INDEX, MIDCLD_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular mid level cloud code.
+   * Codes are:
+   * <pre>
+   *   1  -  Thin As (most of cloud layer semi-transparent)
+   *   2  -  Thick As, greater part sufficiently dense to hide sun 
+   *           (or moon), or Ns
+   *   3  -  Thin Ac, mostly semi-transparent; cloud elements not changing
+   *           much and at a single level
+   *   4  -  Thin Ac in patches; cloud elements continually changing and/or
+   *           occurring at more than one level
+   *   5  -  Thin Ac in bands or in a layer gradually spreading over sky
+   *           and usually thickening as a whole
+   *   6  -  Ac formed by spreading out of Cu
+   *   7  -  Double-layered Ac, or a thick layer of Ac, not increasing;
+   *           or Ac with As and/or Ns
+   *   8  -  Ac in the form of Cu-shaped tufts or Ac with turrets
+   *   9  -  Ac of a chaotic sky, usually at different levels; patches of
+   *           dense Ci are usually present also
+   * </pre>
+   * @param   mcCode     mid cloud code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getMidCloudSymbol(int mcCode) {
+    if (mcCode < 1 || mcCode > MIDCLD_NUM) {
+       throw new IllegalArgumentException(
+           "unknown mid cloud symbol: " + mcCode );
+    }
+    return getVLAClone(metSymbols, MIDCLD_INDEX+mcCode-1);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the high cloud codes.
+   * @return the array of shapes
+   * @see #getHighCloudSymbol(int) for codes
+   */
+  public static VisADLineArray[] getHighCloudSymbols() {
+    return subsetArray(metSymbols, HICLD_INDEX, HICLD_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular high cloud code.
+   * Codes are:
+   * <pre>
+   *   1  -  Filaments of Ci, or "mares tails", scattered and not increasing
+   *   2  -  Dense Ci in patches or twisted sheaves, usually not increasing,
+   *           sometimes like remains of Cb; or towers or tufts
+   *   3  -  Dense Ci, often anvil shaped derived from or associated with Cb
+   *   4  -  Ci, often hook shaped, spreading over the sky and usually
+   *           thickening as a whole
+   *   5  -  Ci and Cs, often in converging bands, or Cs alone; generally
+   *           overspreading and growing denser; the continuous layer 
+   *           not reaching 45 degrees altitude
+   *   6  -  Ci and Cs, often in converging bands, or Cs alone; generally
+   *           overspreading and growing denser; the continuous layer 
+   *           exceeding 45 degrees altitude
+   *   7  -  Veil of Cs covering the entire sky
+   *   8  -  Cs not increasing and not covering the entire sky
+   *   9  -  Cc alone or Cc with some Ci or Cs, but the Cc being the main
+   *           cirriform cloud
+   * </pre>
+   * @param   hcCode     high cloud code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getHighCloudSymbol(int hcCode) {
+    if (hcCode < 1 || hcCode > HICLD_NUM) {
+       throw new IllegalArgumentException(
+           "unknown high cloud symbol: " + hcCode );
+    }
+    return getVLAClone(metSymbols, HICLD_INDEX+hcCode-1);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the icing codes.
+   * @return the array of shapes
+   * @see #getIcingSymbol(int) for codes
+   */
+  public static VisADLineArray[] getIcingSymbols() {
+    return subsetArray(metSymbols, ICING_INDEX, ICING_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular icing symbol code.
+   * Codes are:
+   * <pre>
+   *   0  -  No icing
+   *   1  -  Trace icing
+   *   2  -  Trace to light icing
+   *   3  -  Light icing
+   *   4  -  Light to moderate icing
+   *   5  -  Moderate icing
+   *   6  -  Moderate to heavy icing
+   *   7  -  Heavy or moderate to severe icing
+   *   8  -  Severe icing
+   *   9  -  Light superstructure icing
+   *  10  -  Heavy superstructure icing
+   * </pre>
+   * @param   icingCode     icing code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getIcingSymbol(int icingCode) {
+    if (icingCode < 0 || icingCode >= ICING_NUM) {
+       throw new IllegalArgumentException(
+           "unknown icing symbol: " + icingCode );
+    }
+    return getVLAClone(metSymbols, ICING_INDEX+icingCode);
+  }
+
+  /**
+   * Get the array of shapes corresponding to the turbulence codes.
+   * @return the array of shapes
+   * @see #getTurbulenceSymbol(int) for codes
+   */
+  public static VisADLineArray[] getTurbulenceSymbols() {
+    return subsetArray(metSymbols, TURB_INDEX, TURB_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular turbulence symbol code.
+   * Codes are:
+   * <pre>
+   *   0  -  No turbulence
+   *   1  -  Light turbulence
+   *   2  -  Light turbulence
+   *   3  -  Light to moderate turbulence
+   *   4  -  Moderate turbulence
+   *   5  -  Moderate to severe turbulence
+   *   6  -  Severe turbulence
+   *   7  -  Extreme turbulence
+   *   8  -  Extreme turbulence
+   * </pre>
+   * @param   turbCode     turbulence code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getTurbulenceSymbol(int turbCode) {
+    if (turbCode < 0 || turbCode >= TURB_NUM) {
+       throw new IllegalArgumentException(
+           "unknown turbulence symbol: " + turbCode );
+    }
+    return getVLAClone(metSymbols, TURB_INDEX+turbCode);
+  }
+
+
+  /**
+   * Get the array of shapes corresponding to the miscellaneous codes.
+   * @return the array of shapes
+   * @see #getMiscSymbol(int) for codes
+   */
+  public static VisADLineArray[] getMiscSymbols() {
+    return subsetArray(metSymbols, MISC_INDEX, MISC_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular miscellaneous symbol.
+   * Codes are:
+   * <pre>
+   *   0  -  Square (outline)            16 - Tropical Storm (NH)
+   *   1  -  Square (filled)             17 - Hurricane (NH)
+   *   2  -  Circle (outline)            18 - Tropical Storm (SH)
+   *   3  -  Circle (filled)             19 - Hurricane (SH)
+   *   4  -  Triangle (outline)          20 - Triangle with antenna
+   *   5  -  Triangle (filled)           21 - Mountain obscuration
+   *   6  -  Diamond (outline)           22 - Slash
+   *   7  -  Diamond (filled)            23 - Storm Center
+   *   8  -  Star (outline)              24 - Tropical Depression
+   *   9  -  Star (filled)               25 - Tropical Cyclone
+   *  10  - High Pressure (outline)      26 - Flame
+   *  11  - Low Pressure (outline)       27 - "X" Cross
+   *  12  - High Pressure (filled)       28 - Low pressure with X (outline)
+   *  13  - Low Pressure (filled)        29 - Low pressure with X (filled)
+   *  14  - Plus sign                    30 - Tropical Storm (NH)
+   *  15  - Minus sign                   31 - Tropical Storm (SH)
+   *  32  - Volcanic activity            33 - Blowing spray
+   * </pre>
+   * @param   miscCode     miscellaneous code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getMiscSymbol(int miscCode) {
+    if (miscCode < 0 || miscCode >= MISC_NUM) {
+       throw new IllegalArgumentException(
+           "unknown turbulence symbol: " + miscCode );
+    }
+    return getVLAClone(metSymbols, MISC_INDEX+miscCode);
+  }
+
+  /**
+   * Get the array of shapes corresponding to lightning symbols
+   * @return the array of shapes
+   * @see #getLightningSymbol(int) for codes
+   */
+  public static VisADLineArray[] getLightningSymbols() {
+    return subsetArray(lightningSymbols, 0, LIGHTNING_NUM);
+  }
+
+  /**
+   * Get the shape corresponding to a particular lightning code.
+   * Codes are:
+   * <pre>
+   *   0  -  Negative flash
+   *   1  -  Positive flash
+   * </pre>
+   * @param   lghtCode     lightning code to use
+   * @return  corresponding shape
+   */
+  public static VisADLineArray getLightningSymbol(int lghtCode) {
+    if (lghtCode < 0 || lghtCode >= LIGHTNING_NUM) {
+       throw new IllegalArgumentException(
+           "unknown lightning symbol: " + lghtCode );
+    }
+    return getVLAClone(lightningSymbols, lghtCode);
+  }
+
+  private static VisADLineArray[] subsetArray(VisADLineArray[] array,
+                                              int start, int number) {
+    VisADLineArray[] retArray = new VisADLineArray[number];
+    for (int i = 0; i < number; i++) {
+      retArray[i] = (VisADLineArray) array[start+i].clone();
+    }
+    return retArray;
+  }
+
+  private static VisADLineArray getVLAClone(VisADLineArray[] array, int index) {
+    return (VisADLineArray) array[index].clone();
+  }
+
+  public static void main( String[] args) throws Exception {
+
+    DisplayImpl display = new DisplayImplJ2D("display");
+    display.getDisplayRenderer().setBoxOn(false);
+    double[] matrix = display.getProjectionControl().getMatrix();
+    matrix[0] = 1.25;
+    matrix[3] = -1.25;
+    display.getProjectionControl().setMatrix(matrix);
+    display.addMap(new ScalarMap(RealType.YAxis, Display.YAxis));
+    display.addMap(new ScalarMap(RealType.XAxis, Display.XAxis));
+    float[][] values = new float[3][220];
+    int l = 0;
+    for (int x = 0; x < 11; x++) {
+      for (int y = 0; y < 20; y++) {
+        values[0][l] = -1.f + y/10.f;
+        values[1][l] = 1.f - x/4.f;
+        values[2][l] = l++;
+      }
+    }
+    Gridded3DSet set =
+      new Gridded3DSet(RealTupleType.SpatialCartesian3DTuple, values, l);
+    ScalarMap shapeMap = new ScalarMap(RealType.ZAxis, Display.Shape);
+    display.addMap(shapeMap);
+    ShapeControl sc = (ShapeControl) shapeMap.getControl();
+    sc.setShapeSet(new Integer1DSet(l));
+    sc.setShapes(WeatherSymbols.getAllMetSymbols());
+    sc.setScale(0.1f);
+    DataReference ref = new DataReferenceImpl("ref");
+    ref.setData(set);
+    display.addReference(ref);
+    JFrame frame = new JFrame("Weather Symbol Plot Test");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+    frame.getContentPane().add(display.getComponent());
+    frame.pack();
+    frame.setSize(500, 500);
+    frame.setVisible(true);
+
+  }
+}
diff --git a/visad/meteorology/package.html b/visad/meteorology/package.html
new file mode 100644
index 0000000..629cb6b
--- /dev/null
+++ b/visad/meteorology/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+</head>
+<body bgcolor="white">
+
+Provides classes that are useful in the field of meteorology.
+
+<h2>Related Documentation</h2>
+
+For overviews, tutorials, examples, guides, and tool documentation, please see:
+<ul>
+  <li><a href="http://www.ssec.wisc.edu/~billh/visad.html">
+  The VisAD Java Class Library Developer's Guide</a>
+</ul>
+
+
+</body>
+</html>
diff --git a/visad/overview.html b/visad/overview.html
new file mode 100644
index 0000000..2e276d8
--- /dev/null
+++ b/visad/overview.html
@@ -0,0 +1,12 @@
+<! See the latest documentation on the javadoc utility for information on the
+"overview.html" file>
+<HTML>
+<HEAD>
+   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
+   <META NAME="GENERATOR" CONTENT="Mozilla/4.05 [en] (X11; U; SunOS 5.6 sun4u) [Netscape]">
+</HEAD>
+<BODY>
+This is placeholder commentary for an overview of the Java VisAD packages.
+It should be filled-in as time goes by.
+</BODY>
+</HTML>
diff --git a/visad/package.html b/visad/package.html
new file mode 100644
index 0000000..f797a7f
--- /dev/null
+++ b/visad/package.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+The core VisAD package, providing support for VisAD's Data & MathType
+hierarchy, as well as for VisAD Displays and remote collaboration.
+
+</body>
+</html>
+
diff --git a/visad/python/JPythonEditor.java b/visad/python/JPythonEditor.java
new file mode 100644
index 0000000..bfcedb8
--- /dev/null
+++ b/visad/python/JPythonEditor.java
@@ -0,0 +1,281 @@
+//
+// JPythonEditor.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.python;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.util.*;
+import javax.swing.*;
+import visad.VisADException;
+import visad.util.*;
+
+/** An editor for writing and executing JPython code in Java runtime. */
+public class JPythonEditor extends CodeEditor {
+
+  /** text to be prepended to all JPython documents */
+  private static final String PREPENDED_TEXT =
+    "from visad.python.JPythonMethods import *";
+
+  /** monospaced font to use for error message reporting */
+  private static final Font MONO_FONT = new Font("Monospaced", Font.PLAIN, 11);
+
+  /** wrapper for PythonInterpreter object */
+  protected RunJPython python = null;
+
+  /** flag indicating whether to warn before auto-saving */
+  protected boolean warnBeforeSave = true;
+  
+  /** flag indicating whether to launch scripts in a separate process */
+  protected boolean runSeparate = true;
+
+  /** run menu item */
+  private JMenuItem runItem;
+
+
+  /** runs the given command in a separate process */
+  public static String[] runCommand(String cmd)
+    throws IOException, VisADException
+  {
+    ArrayList list = new ArrayList();
+    Process proc = Runtime.getRuntime().exec(cmd);
+
+    // capture program output
+    InputStream istr = proc.getInputStream();
+    BufferedReader br = new BufferedReader(new InputStreamReader(istr));
+    String str;
+    while ((str = br.readLine()) != null) list.add(str);
+
+    // wait for command to terminate
+    try { proc.waitFor(); }
+    catch (InterruptedException e) { e.printStackTrace(); }
+
+    br.close();
+
+    // check exit value
+    if (proc.exitValue() != 0) {
+      throw new VisADException("exit value was non-zero");
+    }
+
+    // return output as a list of strings
+    return (String[]) list.toArray(new String[0]);
+  }
+
+
+  /** constructs a JPythonEditor */
+  public JPythonEditor() throws VisADException {
+    this(null);
+  }
+
+  /** constructs a JPythonEditor containing text from the given filename */
+  public JPythonEditor(String filename) throws VisADException {
+    super(filename);
+    python = new RunJPython();
+
+
+  }
+
+  /** Create and initialize the the file chooser */
+  protected JFileChooser doMakeFileChooser() {
+      JFileChooser tmpFileChooser = super.doMakeFileChooser();
+      // add JPython files to file chooser dialog box
+      tmpFileChooser.addChoosableFileFilter(
+          new ExtensionFileFilter("py", "JPython source code"));
+      return tmpFileChooser;
+  }
+
+
+  /** adjusts the line number of the given error to match the displayed text,
+      and highlights that line of code to indicate the source of error */
+  private String handleError(String err, String filename) {
+    // convert eol character sequences to newlines
+    StringTokenizer st = new StringTokenizer(err, "\r\n");
+    StringBuffer sbuf = new StringBuffer(err.length());
+    while (st.hasMoreTokens()) {
+      String line = st.nextToken();
+      // convert tabs to spaces
+      int tab = line.indexOf("\t");
+      while (tab >= 0) {
+        line = line.substring(0, tab) + "        " + line.substring(tab + 1);
+        tab = line.indexOf("\t");
+      }
+      sbuf.append(line + "\n");
+    }
+    String nerr = sbuf.toString();
+
+    // adjust error's line number to match displayed line numbers
+    String toLine = filename + "\", ";
+    int index = nerr.indexOf(toLine + "line ");
+    if (index >= 0) {
+      index += toLine.length() + 5;
+      int nline = nerr.indexOf("\n", index);
+      int lineNum = -1;
+      try {
+        lineNum = Integer.parseInt(nerr.substring(index, nline));
+      }
+      catch (NumberFormatException exc) { }
+      lineNum--;
+      if (lineNum >= 1) {
+        highlightLine(lineNum);
+        nerr = nerr.substring(0, index) + lineNum + nerr.substring(nline);
+      }
+    }
+
+    // set up error message dialog
+    Component c = getRootPane().getParent();
+    final JDialog dialog =
+      c instanceof Dialog ?
+        new JDialog((Dialog) c, "JPython script error", true) :
+      c instanceof Frame ?
+        new JDialog((Frame) c, "JPython script error", true) :
+        new JDialog((Frame) null, "JPython script error", true);
+
+    // create dialog components
+    JLabel label = new JLabel("An error in the script occurred:");
+    label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    JTextArea area = new JTextArea(nerr, 16, 80);
+    area.setEditable(false);
+    area.setFont(MONO_FONT);
+    area.setWrapStyleWord(true);
+    JButton button = new JButton("OK");
+
+    // define dialog actions
+    button.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        // dismiss dialog box
+        dialog.hide();
+      }
+    });
+
+    // do dialog layout
+    JPanel p = new JPanel();
+    p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+    p.add(label);
+    p.add(area);
+    p.add(button);
+    dialog.setContentPane(p);
+
+    // pop up dialog with error message
+    dialog.pack();
+    dialog.show();
+
+    return nerr;
+  }
+
+  /** executes a line of JPython code */
+  public void exec(String line) throws VisADException {
+    python.exec(line);
+  }
+
+  /** executes the document as JPython source code */
+  public void execfile(String filename) throws VisADException {
+    try {
+      python.execfile(filename);
+    }
+    catch (VisADException exc) {
+      String error = handleError(exc.getMessage(), filename);
+      throw new VisADException(error);
+    }
+  }
+
+  /** returns a string containing the text of the document */
+  public String getText() {
+    return PREPENDED_TEXT +
+      System.getProperty("line.separator") + super.getText();
+  }
+
+  /** sets the text of the document to the current string */
+  public void setText(String text) {
+    if (text.startsWith(PREPENDED_TEXT)) {
+      // strip off prepended text
+      text = text.substring(PREPENDED_TEXT.length()).trim();
+    }
+    super.setText(text);
+  }
+
+  /** sets whether editor should warn user before auto-saving */
+  public void setWarnBeforeSave(boolean warn) { warnBeforeSave = warn; }
+
+  /** sets whether editor should run scripts in a separate process */
+  public void setRunSeparateProcess(boolean separate) {
+    runSeparate = separate;
+  }
+  
+  /** sets the run menu item */
+  public void setRunItem(JMenuItem run) { runItem = run; }
+
+  /** executes the JPython script */
+  public void run() {
+    if (hasChanged()) {
+      if (warnBeforeSave) {
+        int ans = JOptionPane.showConfirmDialog(this,
+          "A save is required before execution. Okay to save?",
+          "VisAD JPython Editor", JOptionPane.YES_NO_OPTION);
+        if (ans != JOptionPane.YES_OPTION) return;
+      }
+      boolean success = saveFile();
+      if (!success) return;
+    }
+    Thread t = new Thread(new Runnable() {
+      public void run() {
+        String name = getFilename();
+        if (runSeparate) {
+          String[] out;
+          try {
+            out = runCommand("java visad.python.RunJPython " + name);
+          }
+          catch (IOException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+          catch (VisADException exc) {
+            if (DEBUG) exc.printStackTrace();
+            String error = handleError(exc.getMessage(), name);
+          }
+        }
+        else {
+          runItem.setEnabled(false);
+          runItem.setText("Running...");
+          try {
+            execfile(name);
+          }
+          catch (VisADException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+          runItem.setText("Run");
+          runItem.setEnabled(true);
+        }
+      }
+    });
+    t.start();
+  }
+
+  /** compiles the JPython script to a Java class */
+  public void compile() throws VisADException {
+    throw new VisADException("Not yet implemented!");
+  }
+  
+}
diff --git a/visad/python/JPythonFrame.java b/visad/python/JPythonFrame.java
new file mode 100644
index 0000000..8775d0e
--- /dev/null
+++ b/visad/python/JPythonFrame.java
@@ -0,0 +1,118 @@
+//
+// JPythonFrame.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.python;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.IOException;
+import javax.swing.JCheckBoxMenuItem;
+import visad.VisADException;
+import visad.util.*;
+
+/** A GUI frame for editing JPython code in Java runtime. */
+public class JPythonFrame extends CodeFrame {
+
+  /** flag for java shell availability */
+  protected boolean canRunSeparate = true;
+
+  /** menu item for running JPython in a separate process */
+  protected JCheckBoxMenuItem separateProcess;
+
+  /** constructs a JPythonFrame */
+  public JPythonFrame() throws VisADException {
+    this((String) null);
+  }
+
+  /** constructs a JPythonFrame containing text from the given filename */
+  public JPythonFrame(String filename) throws VisADException {
+    this(new JPythonEditor(filename));
+  }
+
+  /** constructs a JPythonFrame from the given JPythonEditor object */
+  public JPythonFrame(JPythonEditor editor) throws VisADException {
+    super(editor);
+    ((JPythonEditor) textPane).setRunItem(getMenuItem("Command", "Run"));
+
+    // determine external java shell availability
+    try {
+      JPythonEditor.runCommand("java visad.python.RunJPython");
+    }
+    catch (IOException exc) { canRunSeparate = false; }
+    catch (VisADException exc) { canRunSeparate = false; }
+    ((JPythonEditor) textPane).setRunSeparateProcess(false);
+
+    // setup menu bar
+    getMenuItem("Command", "Compile").setEnabled(false);
+    addMenuSeparator("Command");
+    separateProcess = new JCheckBoxMenuItem(
+      "Launch JPython in a separate process", false);
+    addMenuItem("Command", separateProcess,
+      "commandSeparate", 'l', canRunSeparate);
+  }
+
+  /** sets up the GUI */
+  public void commandSeparate() {
+    boolean separate = separateProcess.getState();
+    ((JPythonEditor) textPane).setRunSeparateProcess(separate);
+  }
+
+  /** sets whether editor should warn user before auto-saving */
+  public void setWarnBeforeSave(boolean warn) {
+    ((JPythonEditor) textPane).setWarnBeforeSave(warn);
+  }
+
+  /** tests the JPythonFrame class */
+  public static void main(String[] args) {
+    boolean warn = true;
+    if (args.length > 0) {
+      if ("-nowarn".equalsIgnoreCase(args[0])) warn = false;
+    }
+    try {
+      final JPythonFrame frame = new JPythonFrame();
+      frame.setWarnBeforeSave(warn);
+
+      // close program if frame is closed
+      frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {
+          frame.fileExit();
+        }
+      });
+
+      // display frame onscreen
+      Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+      int w = screenSize.width > 550 ? 500 : screenSize.width - 50;
+      int h = screenSize.height > 850 ? 800 : screenSize.height - 50;
+      frame.setSize(w, h);
+      Util.centerWindow(frame);
+      frame.setVisible(true);
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+    }
+  }
+
+}
diff --git a/visad/python/JPythonMethods.java b/visad/python/JPythonMethods.java
new file mode 100644
index 0000000..0b9bdb6
--- /dev/null
+++ b/visad/python/JPythonMethods.java
@@ -0,0 +1,4556 @@
+//
+// JPythonMethods.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.python;
+
+import java.awt.Font;
+import java.awt.Dimension;
+import java.awt.event.*;
+import java.lang.reflect.InvocationTargetException;
+import java.io.File;
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.TreeSet;
+
+import javax.swing.*;
+
+import visad.*;
+import visad.math.*;
+import visad.matrix.*;
+import visad.data.*;
+import visad.data.netcdf.Plain;
+import visad.ss.*;
+import ucar.netcdf.NetcdfFile;
+import visad.data.netcdf.QuantityDBManager;
+import visad.data.netcdf.in.DefaultView;
+import visad.data.netcdf.in.NetcdfAdapter;
+
+
+/**
+ * A collection of methods for working with VisAD, callable from the
+ * JPython editor.
+ */
+public abstract class JPythonMethods {
+
+  /** TODO */
+  private static final String DEFAULT_NAME = "Jython";
+
+  /** TODO */
+  private static final String ID = JPythonMethods.class.getName();
+
+  /** TODO */
+  private static DefaultFamily form = new DefaultFamily(ID);
+
+  /** TODO */
+  private static final String[] ops = {"gt","ge","lt","le","eq","ne","ne","ge","le"};
+  
+  /** TODO */
+  private static final String[] ops_sym = {">",">=","<","<=","==","!=","<>","=>","=<"};
+
+  /** Make a Hashtable available for everyone */
+  public static Hashtable JyVars = new Hashtable();
+
+  /** TODO */
+  private static Hashtable<String, JFrame> frames = new Hashtable<String, JFrame>();
+
+  /**
+   * Reads in data from the given location (filename or URL).
+   * 
+   * @param location 
+   * 
+   * @return DataImpl for the specified location/type
+   * 
+   * @throws VisADException 
+   */
+  public static DataImpl load(String location) throws VisADException {
+    return form.open(location);
+  }
+
+  /**
+   * Displays the given data onscreen.
+   *
+   * @param   data            VisAD data object to plot; alternatively
+   *                          this may be a float[] or float[][].
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(final float[] data)
+    throws VisADException, RemoteException
+  {
+    plot(null, field(data), false, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final float[][] data)
+    throws VisADException, RemoteException
+  {
+    plot(null, field(data), false, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final DataImpl data)
+    throws VisADException, RemoteException
+  {
+    plot(null, data, false, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * Displays the given data onscreen.
+   *
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   maps            ScalarMaps for the display
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(final float[] data, final ScalarMap[] maps)
+    throws VisADException, RemoteException {
+    plot(null, field(data), false, 1.0, 1.0, 1.0, maps);
+  }
+
+  /**
+   * 
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   maps            ScalarMaps for display
+   * 
+   * @throws  VisADException
+   * @throws  RemoteException
+   */
+  public static void plot(final float[][] data, final ScalarMap[] maps)
+    throws VisADException, RemoteException {
+    plot(null, field(data), false, 1.0, 1.0, 1.0, maps);
+  }
+
+  /**
+   * 
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   maps            ScalarMaps for display
+   * 
+   * @throws  VisADException
+   * @throws  RemoteException
+   */
+  public static void plot(final DataImpl data, final ScalarMap[] maps)
+    throws VisADException, RemoteException {
+    plot(null, data, false, 1.0, 1.0, 1.0, maps);
+  }
+
+  /**
+   * Displays the given data onscreen,
+   * displaying the edit mappings dialog if specified.
+   *
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(float[] data, boolean editMaps)
+    throws VisADException, RemoteException
+  {
+    plot(null, field(data), editMaps, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final float[][] data, final boolean editMaps)
+    throws VisADException, RemoteException
+  {
+    plot(null, field(data), editMaps, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final DataImpl data, final boolean editMaps)
+    throws VisADException, RemoteException
+  {
+    plot(null, data, editMaps, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * Displays the given data onscreen.
+   *
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(final String name, final float[] data)
+    throws VisADException, RemoteException
+  {
+    plot(name, field(data), false, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+public static void plot(final String name, final float[][] data)
+    throws VisADException, RemoteException
+  {
+    plot(name, field(data), false, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final String name, final DataImpl data)
+    throws VisADException, RemoteException
+  {
+    plot(name, data, false, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * Displays the given data onscreen.
+   *
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   maps            ScalarMaps for display
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(final String name, final float[] data, final ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    plot(name, field(data), false, 1.0, 1.0, 1.0, maps);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   maps            ScalarMaps for display
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final String name, final float[][] data, final ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    plot(name, field(data), false, 1.0, 1.0, 1.0, maps);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   maps            ScalarMaps for display
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final String name, final DataImpl data, final ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    plot(name, data, false, 1.0, 1.0, 1.0, maps);
+  }
+
+  /**
+   * Displays the given data onscreen in a display with the given name,
+   * displaying the edit mappings dialog if specified.
+   *
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(String name, float[] data, boolean editMaps)
+    throws VisADException, RemoteException
+  {
+    plot(name, field(data), editMaps, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final String name, final float[][] data, final boolean editMaps)
+    throws VisADException, RemoteException
+  {
+    plot(name, field(data), editMaps, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final String name, final DataImpl data, final boolean editMaps)
+    throws VisADException, RemoteException
+  {
+    plot(name, data, editMaps, 1.0, 1.0, 1.0);
+  }
+
+  /**
+   * Displays the given data onscreen, using given color default.
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   red             red component of default color to use if there
+   *                          are no color mappings from data's RealTypes;
+   *                          color component values between 0.0 and 1.0
+   * @param   green           green component of default color
+   * @param   blue            blue component of default color
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(final float[] data, final double red, final double green, final double blue)
+    throws VisADException, RemoteException
+  {
+    plot(null, field(data), false, red, green, blue);
+  }
+
+  /**
+   * 
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   red             red component of default color to use if there
+   *                          are no color mappings from data's RealTypes;
+   *                          color component values between 0.0 and 1.0
+   * @param   green           green component of default color
+   * @param   blue            blue component of default color
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(final float[][] data, final double red, final double green, final double blue)
+    throws VisADException, RemoteException
+  {
+    plot(null, field(data), false, red, green, blue);
+  }
+
+  /**
+   * 
+   * 
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   red             red component of default color to use if there
+   *                          are no color mappings from data's RealTypes;
+   *                          color component values between 0.0 and 1.0
+   * @param   green           green component of default color
+   * @param   blue            blue component of default color
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(DataImpl data, double red, double green, double blue)
+    throws VisADException, RemoteException
+  {
+    plot(null, data, false, red, green, blue);
+  }
+
+  /**
+   * Displays the given data onscreen in a display with the given name, using
+   * the given color default and displaying the edit mappings dialog if
+   * specified.
+   *
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * @param   red             red component of default color to use if there
+   *                          are no color mappings from data's RealTypes;
+   *                          color component values between 0.0 and 1.0
+   * @param   green           green component of default color
+   * @param   blue            blue component of default color
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void plot(String name, float[] data,
+    boolean editMaps, double red, double green, double blue)
+    throws VisADException, RemoteException {
+       plot(name, field(data), editMaps,red, green, blue, null);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * @param   red             red component of default color to use if there
+   *                          are no color mappings from data's RealTypes;
+   *                          color component values between 0.0 and 1.0
+   * @param   green           green component of default color
+   * @param   blue            blue component of default color
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(String name, float[][] data,
+    boolean editMaps, double red, double green, double blue)
+    throws VisADException, RemoteException {
+       plot(name, field(data), editMaps,red, green, blue, null);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * @param   red             red component of default color to use if there
+   *                          are no color mappings from data's RealTypes;
+   *                          color component values between 0.0 and 1.0
+   * @param   green           green component of default color
+   * @param   blue            blue component of default color
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(String name, DataImpl data,
+    boolean editMaps, double red, double green, double blue)
+    throws VisADException, RemoteException {
+       plot(name, data, editMaps,red, green, blue, null);
+  }
+
+  /**
+   * 
+   * 
+   * @param   name            name of display in which to plot data
+   * @param   data            VisAD data object to plot; may also be
+   *                          a float[] or float[][]
+   * @param   editMaps        whether to initially display edit mappings dialog
+   * @param   red             red component of default color to use if there
+   *                          are no color mappings from data's RealTypes;
+   *                          color component values between 0.0 and 1.0
+   * @param   green           green component of default color
+   * @param   blue            blue component of default color
+   * @param   maps            ScalarMaps for display
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static void plot(String name, DataImpl data,
+    boolean editMaps, double red, double green, double blue, ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+
+    if (data == null) {
+      throw new VisADException("Data cannot be null");
+    }
+    final String title = (name == null) ? DEFAULT_NAME : name;
+    BasicSSCell display;
+
+    synchronized (frames) {
+      display = BasicSSCell.getSSCellByName(title);
+      JFrame frame;
+      if (display == null) {
+        display = new FancySSCell(title);
+        display.setDimension(BasicSSCell.JAVA3D_3D);
+        //display.setDimension(BasicSSCell.JAVA2D_2D);
+        display.setPreferredSize(new Dimension(256, 256));
+        frame = new JFrame("VisAD Display Plot (" + title + ")");
+        frames.put(title, frame);
+        JPanel pane = new JPanel();
+        pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+        frame.setContentPane(pane);
+        pane.add(display);
+
+        // add buttons to cell layout
+        JButton mapping = new JButton("Mappings");
+        JButton controls = new JButton("Controls");
+        JButton clear = new JButton("Clear");
+        JButton close = new JButton("Close");
+        JPanel buttons = new JPanel();
+        buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
+        buttons.add(mapping);
+        buttons.add(controls);
+        buttons.add(clear);
+        buttons.add(close);
+        pane.add(buttons);
+        final FancySSCell fdisp = (FancySSCell) display;
+        fdisp.setAutoShowControls(false);
+        if (maps != null) {
+          display.setMaps(maps);
+          fdisp.setAutoDetect(false);
+        } else {
+          fdisp.setAutoDetect(!editMaps);
+        }
+
+        mapping.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            fdisp.addMapDialog();
+          }
+        });
+        controls.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            fdisp.showWidgetFrame();
+          }
+        });
+        close.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            try { fdisp.smartClear(); clearplot(title);}
+            catch (Exception ec) {;}
+          }
+        });
+        clear.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            try { fdisp.smartClear(); }
+            catch (VisADException exc) { }
+            catch (RemoteException exc) { }
+          }
+        });
+
+        frame.pack();
+      }
+      else {
+        frame = (JFrame) frames.get(title);
+      }
+      frame.setVisible(true);
+      frame.toFront();
+    }
+
+    ConstantMap[] cmaps = {
+      new ConstantMap(red, Display.Red),
+      new ConstantMap(green, Display.Green),
+      new ConstantMap(blue, Display.Blue)
+    };
+    display.addData(data, cmaps);
+    if (editMaps) ((FancySSCell)display).addMapDialog();
+  }
+
+  /**
+   * clear the onscreen data display
+   *
+   * @throws  VisADException  part of data and display APIs, shouldn't occur
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void clearplot() throws VisADException, RemoteException {
+    clearplot(null);
+  }
+
+  /**
+   * clear the onscreen data display with the given name
+   *
+   * @param   name            name of the display to clear
+   *
+   * @throws  VisADException  part of data and display APIs, shouldn't occur
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static void clearplot(final String name)
+    throws VisADException, RemoteException
+  {
+//    if (name == null) name = DEFAULT_NAME;
+    final String title = (name == null) ? DEFAULT_NAME : name;
+    BasicSSCell display = BasicSSCell.getSSCellByName(title);
+    if (display != null) {
+      JFrame frame = (JFrame)frames.get(title);
+      display.clearCell();
+      display.clearMaps();
+      frame.setVisible(false);
+      frame.dispose();
+      frame = null;
+      display.destroyCell();
+      display = null;
+    }
+  }
+
+  /** 
+   * Save the Data in a netcdf file
+   * 
+   * @param fn 
+   * @param d 
+   * 
+   * @throws VisADException 
+   * @throws RemoteException 
+   * @throws IOException 
+   */
+  public static void saveNetcdf(String fn, Data d) 
+                 throws VisADException, RemoteException, IOException {
+    new Plain().save(fn, d, false);
+  }
+
+  /** 
+   * Save the display genreated by a quick graph or showDisplay
+   * 
+   * @param disp is the DisplayImpl to save
+   * @param filename is the name of the JPG file to write
+   * 
+   * @throws VisADException 
+   * @throws RemoteException 
+   * @throws IOException 
+   */
+  public static void saveplot(DisplayImpl disp, String filename) 
+                 throws VisADException, RemoteException, IOException {
+    visad.util.Util.captureDisplay(disp, filename);
+  }
+
+  /**
+   * save the onscreen data display generated by plot()
+   * 
+   * @param filename Name of the file to save display into.
+   * 
+   * @throws  VisADException  part of data and display APIs, shouldn't occur
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws IOException 
+   */
+  public static void saveplot(String filename) 
+                 throws VisADException, RemoteException, IOException {
+    saveplot((String)null, filename);
+  }
+
+  /**
+   * clear the onscreen data display with the given name
+   *
+   * @param   name            name of the display to clear
+   * @param   filename        name of the file to save display into
+   *
+   * @throws  VisADException  part of data and display APIs, shouldn't occur
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IOException part of data and display APIs, shouldn't occur
+   */
+  public static void saveplot(String name, String filename)
+    throws VisADException, RemoteException, IOException {
+
+    if (name == null) name = DEFAULT_NAME;
+    final BasicSSCell sscell = BasicSSCell.getSSCellByName(name);
+    final String fn = filename;
+    if (sscell != null) {
+      Runnable captureDisp = new Runnable() {
+        public void run() {
+          try {
+            sscell.captureImage(new File(fn));
+          } catch (Exception se) {
+            System.out.println("Error saving plot = "+se);
+          }
+        }
+      };
+
+      Thread ts = new Thread(captureDisp);
+      ts.start();
+    }
+  }
+
+  /**
+   * Return point-wise absolute value of data
+   * name changed 1/11/02 to avoid conflicts with Jython built-in
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise absolute value of the given data object.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data abs_data(Data data) 
+           throws VisADException, RemoteException {
+    return data.abs();
+  }
+
+  /**
+   * Return point-wise absolute value of data
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise absolute value of the given data object.
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   * 
+   * @deprecated Consider using {@link #abs_data(Data)} instead.
+   */
+  @Deprecated
+  public static Data abs(Data data) 
+           throws VisADException, RemoteException {
+    return data.abs();
+  }
+  
+  /**
+   * Return absolute value of value
+   * 
+   * @param   value     value
+   * 
+   * @return Absolute value of {@code value}.
+   * 
+   * @deprecated Consider using Jython's builtin function or 
+   * {@link Math#abs(double)}.
+   */
+  @Deprecated
+  public static double abs(double value) {
+    return Math.abs(value);
+  }
+
+  /**
+   * return absolute value of value
+   * 
+   * @param   value     value
+   * 
+   * @return Absolute value of {@code value}.
+   * 
+   * @deprecated Consider using Jython's builtin function or 
+   * {@link Math#abs(int)}.
+   */
+  @Deprecated
+  public static int abs(int value) {
+    return Math.abs(value);
+  }
+
+  /**
+   * return absolute value of value
+   *
+   * @param   value     value
+   * 
+   * @return Absolute value of {@code value}.
+   * 
+   * @deprecated Consider using Jython's builtin function or 
+   * {@link Math#abs(long)}.
+   */
+  @Deprecated
+  public static long abs(long value) {
+    return Math.abs(value);
+  }
+
+  /**
+   * Return point-wise arccosine value of {@code data}, in radians.
+   *
+   * @param   data            VisAD data object
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data acos(Data data) throws VisADException, RemoteException {
+    return data.acos();
+  }
+
+  /**
+   * return point-wise arccosine value of data, in degrees.
+   *
+   * @param   data            VisAD data object
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data acosDegrees(Data data) throws VisADException, RemoteException {
+    return data.acosDegrees();
+  }
+
+  /**
+   * return point-wise arcsine value of {@code data}, in radians
+   *
+   * @param   data            VisAD data object
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data asin(Data data) throws VisADException, RemoteException {
+    return data.asin();
+  }
+
+  /**
+   * return point-wise arcsine value of {@code data}, in degrees.
+   *
+   * @param   data            VisAD data object
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data asinDegrees(Data data) throws VisADException, RemoteException {
+    return data.asinDegrees();
+  }
+
+  /**
+   * return point-wise arctangent value of {@code data}, in radians.
+   * 
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise arctangent of {@code data}, as radians.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atan(Data data) throws VisADException, RemoteException {
+    return data.atan();
+  }
+
+  /**
+   * return point-wise arctangent value of {@code data}, in degrees.
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise arctangent in degrees
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atanDegrees(Data data) throws VisADException, RemoteException {
+    return data.atanDegrees();
+  }
+
+  /**
+   * return point-wise ceil value of {@code data} (smallest integer not less than).
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise ceiling value of {@code data}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data ceil(Data data) throws VisADException, RemoteException {
+    return data.ceil();
+  }
+
+  /**
+   * return point-wise cosine value of {@code data}, assuming input values are 
+   * in radians unless they have units convertable with radians, in which case 
+   * those units are converted to radians
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise cosine value of {@code data} as radians.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data cos(Data data) throws VisADException, RemoteException {
+    return data.cos();
+  }
+
+  /**
+   * return point-wise cosine value of data, assuming input values are in degrees
+   * unless they have units convertable with degrees, in which case those
+   * units are converted to degrees
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise cosine value of {@code data} as degrees.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data cosDegrees(Data data) throws VisADException, RemoteException {
+    return data.cosDegrees();
+  }
+
+  /**
+   * return point-wise exp value of {@code data}.
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise exp value of {@code data}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data exp(Data data) throws VisADException, RemoteException {
+    return data.exp();
+  }
+
+  /**
+   * return point-wise floor value of data (largest integer not greater than)
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise floor of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data floor(Data data) throws VisADException, RemoteException {
+    return data.floor();
+  }
+
+  /**
+   * return point-wise log value of data
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise log of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data log(Data data) throws VisADException, RemoteException {
+    return data.log();
+  }
+
+  /**
+   * return point-wise rint value of data (closest integer)
+   *
+   * @param   data            VisAD data object
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   *
+   * @return Point-wise rint of {@code data}
+   */
+  public static Data rint(Data data) throws VisADException, RemoteException {
+    return data.rint();
+  }
+
+  /**
+   * return point-wise round value of {@code data} (closest integer).
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise rounding of {@code data}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data round(Data data) throws VisADException, RemoteException {
+    return data.round();
+  }
+
+  /**
+   * Return rounded value of {@code value} (closest integer).
+   *
+   * @param   value  value 
+   * @param   digits Length of the fractional part of the result.
+   * 
+   * @return Rounded {@code value}.
+   */
+  public static double round(double value, int digits) {
+    boolean neg = value < 0;
+    double multiple = Math.pow(10., digits);
+    if (neg) {
+        value = -value;
+    }
+    double tmp = Math.floor(value * multiple + 0.5);
+    if (neg) {
+        tmp = -tmp;
+    }
+    return (tmp / multiple);
+  }
+
+  /**
+   * Return round value of {@code value} (closest integer).
+   * 
+   * @param   value Value to round to closest integer. 
+   * 
+   * @return Closest integer to {@code value}.
+   * 
+   * @see #round(double, int)
+   */
+  public static double round(double value) {
+    return round(value, 0);
+  }
+
+  /**
+   * return point-wise sine value of data, assuming input values are in radians
+   * unless they have units convertable with radians, in which case those
+   * units are converted to radians
+   *
+   * @param   data            VisAD data object
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data sin(Data data) throws VisADException, RemoteException {
+    return data.sin();
+  }
+
+  /**
+   * return point-wise sine value of data, assuming input values are in degrees
+   * unless they have units convertable with degrees, in which case those
+   * units are converted to degrees
+   *
+   * @param   data            VisAD data object
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data sinDegrees(Data data) throws VisADException, RemoteException {
+    return data.sinDegrees();
+  }
+
+  /**
+   * return point-wise square root value of data
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise square root of {@code data}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data sqrt(Data data) throws VisADException, RemoteException {
+    return data.sqrt();
+  }
+
+  /**
+   * return point-wise tan value of data, assuming input values are in radians
+   * unless they have units convertable with radians, in which case those
+   * units are converted to radians
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise tangent of {@code data}, as radians.
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data tan(Data data) throws VisADException, RemoteException {
+    return data.tan();
+  }
+
+  /**
+   * return point-wise tangent value of data, assuming input values are in degrees
+   * unless they have units convertable with degrees, in which case those
+   * units are converted to degrees
+   *
+   * @param   data            VisAD data object
+   * 
+   * @return Point-wise tangent of {@code data}, as degrees.
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data tanDegrees(Data data) throws VisADException, RemoteException {
+    return data.tanDegrees();
+  }
+
+  /**
+   * return point-wise arc tangent value of data1 / data2 over 
+   * full (-pi, pi) range, returned in radians.
+   * 
+   * @param   data1           VisAD data object
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise arctangent value of {@code data1 / data2} over full
+   * {@code (-pi, pi)} range, as radians.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atan2(Data data1, Data data2)
+         throws VisADException, RemoteException {
+    return data1.atan2(data2);
+  }
+
+  /**
+   * return point-wise arc tangent value of data1 / data2 over 
+   * full (-pi, pi) range, returned in degrees.
+   *
+   * @param   data1           VisAD data object
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise arctangent value of {@code data1 / data2} over full
+   * {@code (-pi, pi)} range, as degrees.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atan2Degrees(Data data1, Data data2)
+         throws VisADException, RemoteException {
+    return data1.atan2Degrees(data2);
+  }
+
+  /**
+   * Return point-wise arc tangent value of data1 / data2 over 
+   * full (-pi, pi) range, returned in radians.
+   *
+   * @param   data1           VisAD data object
+   * @param   data2           double value
+   * 
+   * @return Point-wise arctangent value of {@code data1 / data2} over full
+   * {@code (-pi, pi)} range, as radians.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atan2(Data data1, double data2) 
+         throws VisADException, RemoteException {
+    return data1.atan2(new Real(data2));
+  }
+
+  /**
+   * Return point-wise arc tangent value of data1 / data2 over 
+   * full (-pi, pi) range, returned in degrees.
+   *
+   * @param   data1           VisAD data object
+   * @param   data2           double value
+   * 
+   * @return Point-wise arctangent value of {@code data1 / data2} over full
+   * {@code (-pi, pi)} range, as degrees.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atan2Degrees(Data data1, double data2) 
+         throws VisADException, RemoteException {
+    return data1.atan2Degrees(new Real(data2));
+  }
+
+
+  /**
+   * Return point-wise maximum value of data1 and data2.
+   * name changed 1/11/02 to avoid conflicts with Jython built-in
+   *
+   * @param   data1           VisAD data object
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise maximum value of {@code data1} and {@code data2}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data max_data(Data data1, Data data2)
+         throws VisADException, RemoteException {
+    return data1.max(data2);
+  }
+
+  /**
+   * Return point-wise maximum value of data1 and data2. 
+   * name changed 1/11/02 to avoid conflicts with Jython built-in
+   *
+   * @param   data1           VisAD data object
+   * @param   data2           double value
+   * 
+   * @return Point-wise maximum value of {@code data1} and {@code data2}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data max_data(Data data1, double data2) 
+         throws VisADException, RemoteException {
+    return data1.max(new Real(data2));
+  }
+
+  /**
+   * Return point-wise maximum value of data1 and data2.
+   * name changed 1/11/02 to avoid conflicts with Jython built-in
+   *
+   * @param   data1           double value
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise maximum value of {@code data1} and {@code data2}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data max_data(double data1, Data data2) 
+         throws VisADException, RemoteException {
+    return new Real(data1).max(data2);
+  }
+
+  /**
+   * return point-wise minimum value of data1 and data2 
+   * name changed 1/11/02 to avoid conflicts with Jython built-in
+   *
+   * @param   data1           VisAD data object
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise minimum value of {@code data1} and {@code data2}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data min_data(Data data1, Data data2)
+         throws VisADException, RemoteException {
+    return data1.min(data2);
+  }
+
+  /**
+   * return point-wise minimum value of data1 and data2 
+   * name changed 1/11/02 to avoid conflicts with Jython built-in
+   *
+   * @param   data1           VisAD data object
+   * @param   data2           double value
+   * 
+   * @return Point-wise minimum value of {@code data1} and {@code data2}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data min_data(Data data1, double data2) 
+         throws VisADException, RemoteException {
+    return data1.min(new Real(data2));
+  }
+
+  /**
+   * Return point-wise minimum value of {@code data1} and {@code data2}.
+   * name changed 1/11/02 to avoid conflicts with Jython built-in
+   *
+   * @param   data1           double value
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise minimum value of {@code data1} and {@code data2}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data min_data(double data1, Data data2) 
+         throws VisADException, RemoteException {
+    return new Real(data1).min(data2);
+  }
+
+  /**
+   * Return point-wise arctangent value of data1 / data2 over 
+   * full (-pi, pi) range, returned in radians.
+   *
+   * @param   data1           double value
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise arctangent of {@code data1 / data2}
+   *
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atan2(double data1, Data data2) 
+         throws VisADException, RemoteException {
+    return new Real(data1).atan2(data2);
+  }
+
+  /**
+   * Return point-wise arctangent value of data1 / data2 over 
+   * full (-pi, pi) range, returned in degrees.
+   *
+   * @param   data1           double value
+   * @param   data2           VisAD data object
+   * 
+   * @return Point-wise arctangent of {@code data1 / data2} over full 
+   * {@code (-pi, pi)} range, as degrees.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote data
+   */
+  public static Data atan2Degrees(double data1, Data data2)
+         throws VisADException, RemoteException {
+    return new Real(data1).atan2Degrees(data2);
+  }
+
+  /**
+   * return forward Fourier transform of {@code field}, which should have
+   * either a 1-D or 2-D gridded domain; uses FFT when domain size
+   * is a power of two; returns real and imaginary parts
+   *
+   * @param   field           VisAD Field data object
+   * 
+   * @return forward Fourier transform of {@code field}.
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote field
+   */
+  public static FlatField fft(Field field)
+         throws VisADException, RemoteException {
+    return FFT.fourierTransform(field, true);
+  }
+
+  /**
+   * return backward Fourier transform of field, which should have 
+   * either a 1-D or 2-D gridded domain; uses fft when domain size 
+   * is a power of two; returns real and imaginary parts
+   * 
+   * @param   field           VisAD Field data object
+   * 
+   * @return Backward Fourier transform of {@code field}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote field
+   */
+  public static FlatField ifft(Field field)
+         throws VisADException, RemoteException {
+    return FFT.fourierTransform(field, false);
+  }
+
+  /**
+   * return matrix multiply of data1 * data2, which should have
+   * either 1-D or 2-D gridded domains
+   * 
+   * @param   data1           VisAD FlatField data object
+   * @param   data2           VisAD FlatField data object
+   * 
+   * @return Matrix multiply of {@code data1 x data2}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaMatrix matrixMultiply(FlatField data1, FlatField data2)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix1 = JamaMatrix.convertToMatrix(data1);
+    JamaMatrix matrix2 = JamaMatrix.convertToMatrix(data2);
+    return matrix1.times(matrix2);
+  }
+
+  /**
+   * return matrix soluton X of data1 * X = data2; data12 and data2 should
+   * have either 1-D or 2-D gridded domains; return solution if data1 is
+   * is square, least squares solution otherwise
+   *
+   * @param   data1           VisAD FlatField data object
+   * @param   data2           VisAD FlatField data object
+   * 
+   * @return Matrix solution {@code X of: data1 * X = data2}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaMatrix solve(FlatField data1, FlatField data2)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix1 = JamaMatrix.convertToMatrix(data1);
+    JamaMatrix matrix2 = JamaMatrix.convertToMatrix(data2);
+    return matrix1.solve(matrix2);
+  }
+
+  /**
+   * return matrix inverse of data, which should have either a
+   * 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix inverse of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaMatrix inverse(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.inverse();
+  }
+
+  /**
+   * return matrix transpose of data, which should have either a
+   * 1-D or 2-D gridded domain
+   *
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix transpose of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaMatrix transpose(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.transpose();
+  }
+
+  /**
+   * return matrix determinant of data, which should have either a
+   * 1-D or 2-D gridded domain
+   *
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix determinant of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double det(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.det();
+  }
+
+  /**
+   * return matrix one norm of data (maximum column sum), which
+   * should have either a 1-D or 2-D gridded domain
+   *
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix one norm of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double norm1(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.norm1();
+  }
+
+  /**
+   * return matrix two norm of data (maximum singular value), which 
+   * should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix two norm of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double norm2(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.norm2();
+  }
+
+  /**
+   * return matrix infinity norm of data (maximum row sum), which
+   * should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return matrix infiinity norm of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double normInf(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.normInf();
+  }
+
+  /**
+   * return matrix Frobenius norm of data (sqrt of sum of squares of all
+   * elements), which should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix Frobenius norm of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double normF(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.normF();
+  }
+
+  /**
+   * return matrix effective numerical rank (from SVD) of data, which
+   * should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return matrix effective numerical rank of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double rank(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.rank();
+  }
+
+  /**
+   * return matrix condition of data (ratio of largest to smallest singular
+   * value), which should have either a 1-D or 2-D gridded domain
+   *
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return matrix condition of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double cond(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.cond();
+  }
+
+  /**
+   * return matrix trace of data (sum of the diagonal elements),
+   * which should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return matrix trace of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static double trace(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.trace();
+  }
+
+  /**
+   * return matrix Cholesky Decomposition of data, as a 1-Tuple
+   * (lower_triangular_factor);
+   * data should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix Cholesky decomponsition of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaCholeskyDecomposition chol(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.chol();
+  }
+
+  /**
+   * return matrix Eigenvalue Decomposition of data, as a 3-Tuple
+   * (eigenvector_matrix, real_eigenvalue_components,
+   *  imaginary_eigenvalue_components);
+   * data should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return matrix Eigenvalue decomposition of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaEigenvalueDecomposition eig(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.eig();
+  }
+
+  /**
+   * return matrix LU Decomposition of data, as a 3-Tuple
+   * (lower_triangular_factor, upper_triangular_factor,
+   *  pivot_permutation_vector);
+   * data should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return Matrix LU decomposition of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaLUDecomposition lu(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.lu();
+  }
+
+  /**
+   * return matrix QR Decomposition of data, as a 2-Tuple
+   * (orthogonal_factor, upper_triangular_factor);
+   * data should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return matrix QR decomposition of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaQRDecomposition qr(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.qr();
+  }
+
+  /**
+   * return matrix Singular Value Decomposition of data, as a 3-Tuple
+   * (left_singular_vectors, right_singular_vectors, singular_value_vector);
+   * data should have either a 1-D or 2-D gridded domain
+   * 
+   * @param   data            VisAD FlatField data object
+   * 
+   * @return matrix singular value decomposition of {@code data}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   * @throws  IllegalAccessException Jama not installed
+   * @throws  InstantiationException Jama not installed
+   * @throws  InvocationTargetException Jama not installed
+   */
+  public static JamaSingularValueDecomposition svd(FlatField data)
+         throws VisADException, RemoteException, IllegalAccessException,
+                InstantiationException, InvocationTargetException {
+    JamaMatrix matrix = JamaMatrix.convertToMatrix(data);
+    return matrix.svd();
+  }
+
+  /**
+   * return histogram of range values of field selected by set, with
+   * dimension and bin sampling defined by set
+   * 
+   * @param   field           VisAD Field data object whose range values
+   *                          are analyzed in histogram
+   * @param   set             VisAD Set data object that defines dimension
+   *                          and bin sampling for histogram
+   * 
+   * @return histogram of range values of {@code field}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote field
+   */
+  public static FlatField hist(Field field, Set set)
+         throws VisADException, RemoteException {
+    return Histogram.makeHistogram(field, set);
+  }
+
+  /**
+   * Return histogram of range values of field selected by ranges array,
+   * with dimension = ranges.length, and 64 equally spaced bins in each
+   * dimension
+   * 
+   * @param   field           VisAD Field data object whose range values
+   *                          are analyzed in histogram
+   * @param   ranges          int[] array whose elements are indices of into
+   *                          the range Tuple of field, selecting range
+   *                          components as dimensions of the histogram
+   * 
+   * @return histogram of range values of {@code field}
+   * 
+   * @throws  VisADException  invalid data 
+   * @throws  RemoteException unable to access remote field
+   */
+  public static FlatField hist(Field field, int[] ranges)
+         throws VisADException, RemoteException {
+    if (ranges == null || ranges.length == 0) {
+      throw new VisADException("bad ranges");
+    }
+    int dim = ranges.length;
+    int[] sizes = new int[dim];
+    for (int i=0; i<dim; i++) sizes[i] = 64;
+    return hist(field, ranges, sizes);
+  }
+
+  /**
+   * Return histogram of range values of field selected by ranges array,
+   * with dimension = ranges.length, and with number of equally spaced bins
+   * in each dimension determined by sizes array
+   * 
+   * @param   field           VisAD Field data object whose range values
+   *                          are analyzed in histogram
+   * @param   ranges          int[] array whose elements are indices of into
+   *                          the range Tuple of field, selecting range
+   *                          components as dimensions of the histogram
+   * @param   sizes           int[] array whose elements are numbers of
+   *                          equally spaced bins for each dimension
+   * 
+   * @return histogram of range value of {@code field}
+   * 
+   * @throws  VisADException  invalid data
+   * @throws  RemoteException unable to access remote field
+   */
+  public static FlatField hist(Field field, int[] ranges, int[] sizes)
+         throws VisADException, RemoteException {
+
+    if (ranges == null || ranges.length == 0) {
+      throw new VisADException("bad ranges");
+    }
+    if (sizes == null || sizes.length != ranges.length) {
+      throw new VisADException("bad sizes");
+    }
+    if (field == null) {
+      throw new VisADException("bad field");
+    }
+    FunctionType ftype = (FunctionType) field.getType();
+    RealType[] frealComponents = ftype.getRealComponents();
+    int n = frealComponents.length;
+
+    int dim = ranges.length;
+    RealType[] srealComponents = new RealType[dim];
+    for (int i=0; i<dim; i++) {
+      if (0 <= ranges[i] && ranges[i] < n) {
+        srealComponents[i] = frealComponents[ranges[i]];
+      }
+      else {
+        throw new VisADException("range index out of range " + ranges[i]);
+      }
+    }
+    RealTupleType rtt = new RealTupleType(srealComponents);
+/* WLH 21 Feb 2003
+    double[][] data_ranges = field.computeRanges(srealComponents);
+*/
+    float[][] values = field.getFloats(false);
+    int nn = values.length;
+    double[][] data_ranges = new double[dim][2];
+    for (int i=0; i<dim; i++) {
+      if (0 <= ranges[i] && ranges[i] < nn) {
+        data_ranges[i][0] = Double.MAX_VALUE;
+        data_ranges[i][1] = -Double.MAX_VALUE;
+        float[] v = values[ranges[i]];
+        for (int j=0; j<v.length; j++) {
+          if (v[j] < data_ranges[i][0]) data_ranges[i][0] = v[j];
+          if (v[j] > data_ranges[i][1]) data_ranges[i][1] = v[j];
+        }
+      }
+      else {
+        throw new VisADException("range index out of range " + ranges[i]);
+      }
+    }
+
+    Set set = null;
+    if (dim == 1) {
+      set = new Linear1DSet(rtt, data_ranges[0][0], data_ranges[0][1], sizes[0]);
+    }
+    else if (dim == 2) {
+      set = new Linear2DSet(rtt, data_ranges[0][0], data_ranges[0][1], sizes[0],
+                                 data_ranges[1][0], data_ranges[1][1], sizes[1]);
+    }
+    else if (dim == 3) {
+      set = new Linear3DSet(rtt, data_ranges[0][0], data_ranges[0][1], sizes[0],
+                                 data_ranges[1][0], data_ranges[1][1], sizes[1],
+                                 data_ranges[2][0], data_ranges[2][1], sizes[2]);
+    }
+    else {
+
+      double[] firsts = new double[dim];
+      double[] lasts = new double[dim];
+      for (int i=0; i<dim; i++) {
+        firsts[i] = data_ranges[i][0];
+        lasts[i] = data_ranges[i][1];
+      }
+      set = new LinearNDSet(rtt, firsts, lasts, sizes);
+    }
+    FlatField result = Histogram.makeHistogram(field, set);
+    
+    return result;
+  }
+
+  /**
+   * Return a VisAD FlatField with default 1-D domain and with range
+   * values given by values array
+   *
+   * @param   values          float[] array defining range values of field
+   * 
+   * @return the new FlatField
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(final float[] values)
+         throws VisADException, RemoteException {
+    return field("value", values);
+  }
+
+  /**
+   * Return a VisAD FlatField with default 1-D domain, with range values
+   * given by values array, and with given range RealType name
+   *
+   * @param   name            String defining range RealType name
+   * @param   values          float[] array defining range values of field
+   * 
+   * @return the new FlatField
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(final String name, final float[] values)
+         throws VisADException, RemoteException {
+    return field("domain", name, values);
+  }
+
+  /**
+   * Return a VisAD FlatField with default 1-D domain, with range values
+   * given by values array, and with given range RealType name
+   *
+   * @param   dom0            String defining domain RealType name
+   * @param   name            String defining range RealType name
+   * @param   values          float[] array defining range values of field
+   * 
+   * @return the new FlatField
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(final String dom0, final String name, final float[] values)
+         throws VisADException, RemoteException {
+    if (values == null || values.length == 0) {
+      throw new VisADException("bad values");
+    }
+    RealType domain = RealType.getRealType(dom0);
+    return field(new Integer1DSet(domain, values.length), name, values);
+  }
+
+  /**
+   * Return a VisAD FlatField with given 1-D domain set, with range
+   * values given by values array, and with given range RealType name
+   *
+   * @param   set             VisAD Set defining 1-D domain
+   * @param   name            String defining range RealType name 
+   * @param   values          float[] array defining range values of field
+   * 
+   * @return the new FlatField
+   *  
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(Set set, String name, float[] values)
+         throws VisADException, RemoteException {
+    if (values == null) {
+      throw new VisADException("bad values");
+    }
+    if (set == null || set.getLength() < values.length) {
+      throw new VisADException("bad set " + set);
+    }
+    if (name == null) {
+      throw new VisADException("bad name");
+    }
+    MathType domain = ((SetType) set.getType()).getDomain();
+    if (((RealTupleType) domain).getDimension() == 1) {
+      domain = ((RealTupleType) domain).getComponent(0);
+    }
+    RealType range = RealType.getRealType(name);
+    FunctionType ftype = new FunctionType(domain, range);
+    FlatField field = new FlatField(ftype, set);
+    int len = set.getLength();
+    boolean copy = true;
+    if (values.length < len) {
+      float[] new_values = new float[len];
+      System.arraycopy(values, 0, new_values, 0, len);
+      for (int i=values.length; i<len; i++) new_values[i] = Float.NaN;
+      values = new_values;
+      copy = false;
+    }
+    float[][] field_values = {values};
+    field.setSamples(field_values, copy);
+    return field;
+  }
+
+  /**
+   * Return a VisAD FlatField with default 2-D domain and with range
+   * values given by values array
+   *
+   * @param   values          float[][] array defining range values of field
+   * 
+   * @return the new FlatField
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(final float[][] values)
+         throws VisADException, RemoteException {
+    return field("value", values);
+  }
+
+  /**
+   * Return a VisAD FlatField with default 2-D domain, with range values
+   * given by values array, and with given range RealType name
+   *
+   * @param   name            String defining range RealType name
+   * @param   values          float[][] array defining range values of field
+   * 
+   * @return the new FlatField
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(final String name, final float[][] values)
+         throws VisADException, RemoteException {
+    return field("ImageLine", "ImageElement", name, values);
+  }
+
+  /**
+   * Return a VisAD FlatField with named default 2-D domain, with range values
+   * given by values array and with given range RealType name
+   * 
+   * @param   dom0            String defines first domain component
+   * @param   dom1            String defines second domain component
+   * @param   rng             String defining range RealType name
+   * @param   values          float[][] array defining range values of field
+   * 
+   * @return the new FlatField
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(
+       String dom0, String dom1, String rng, float[][] values)
+         throws VisADException, RemoteException {
+    int[] temps = getValuesLengths(values);
+    int values_len = temps[0];
+    int min = temps[1];
+    int max = temps[2];
+    RealType first = RealType.getRealType(dom0);
+    RealType second = RealType.getRealType(dom1);
+    RealTupleType domain = new RealTupleType(first, second);
+    return field(new Integer2DSet(domain, max, values_len), rng, values);
+  }
+
+  /**
+   * return a VisAD FlatField with given 2-D domain set, with range 
+   * values given by values array, and with given range RealType name
+   * 
+   * @param   set             VisAD Set defining 2-D domain 
+   * @param   name            String defining range RealType name 
+   * @param   values          float[][] array defining range values of field
+   * 
+   * @return the new FlatField
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static FlatField field(Set set, String name, float[][] values)
+         throws VisADException, RemoteException {
+    int[] temps = getValuesLengths(values);
+    int values_len = temps[0];
+    int min = temps[1];
+    int max = temps[2];
+
+    if (set == null || !(set instanceof GriddedSet) ||
+        set.getManifoldDimension() != 2) {
+      throw new VisADException("bad set " + set);
+    }
+    int len0 = ((GriddedSet) set).getLength(0);
+    int len1 = ((GriddedSet) set).getLength(1);
+    if (len0 < max || len1 < values_len) {
+      throw new VisADException("bad set length " + len0 + " " + len1);
+    }
+    if (name == null) {
+      throw new VisADException("bad name");
+    }
+
+    MathType domain = ((SetType) set.getType()).getDomain();
+    if (((RealTupleType) domain).getDimension() == 1) {
+      domain = ((RealTupleType) domain).getComponent(0);
+    }
+    RealType range = RealType.getRealType(name);
+    FunctionType ftype = new FunctionType(domain, range);
+    FlatField field = new FlatField(ftype, set);
+    int len = len0 * len1; // == set.getLength()
+
+    float[] new_values = new float[len];
+    for (int j=0; j<values_len; j++) {
+      int m = j * len0;
+      int n = values[j].length;
+      if (n > 0) System.arraycopy(values[j], 0, new_values, m, n);
+      for (int i=(m+n); i<(m+len0); i++) new_values[i] = Float.NaN;
+    }
+    for (int j=values_len; j<len1; j++) {
+      int m = j * len0;
+      for (int i=m; i<(m+len0); i++) new_values[i] = Float.NaN;
+    }
+    float[][] field_values = {new_values};
+    field.setSamples(field_values, false);
+    return field;
+  }
+
+  /**
+   * Get the lengths of the values of the input array
+   * 
+   * @param values
+   * 
+   * @return the array of lengths (sizes)
+   * 
+   * @throws VisADException
+   */
+  public static int[] getValuesLengths(final float[][] values)
+          throws VisADException {
+    if (values == null) {
+      throw new VisADException("bad values");
+    }
+    int values_len = values.length;
+    int min = Integer.MAX_VALUE;
+    int max = 0;
+    for (int j=0; j<values_len; j++) {
+      if (values[j] == null) {
+        throw new VisADException("bad values");
+      }
+      int n = values[j].length;
+      if (n > max) max = n;
+      if (n < min) min = n;
+    }
+    if (max < min) {
+      min = 0;
+    }
+    return new int[] {values_len, min, max};
+  }
+
+  /**
+   * Get the number of domain components from a given Data object.
+   * 
+   * @param   data            VisAD Data object
+   * 
+   * @return                  Number of domain components
+   * 
+   * @throws  VisADException  Unable to construct field
+   * @throws  RemoteException Part of data and display APIs, shouldn't occur
+   */
+  public static int getDomainDimension(Data data)
+                  throws VisADException, RemoteException {
+     return domainDimension(data);
+  }
+
+  /**
+   * Get the domain dimension of the Data object
+   * 
+   * @param data
+   * 
+   * @return the domain dimension
+   * 
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static int domainDimension (final Data data) 
+                  throws VisADException, RemoteException {
+    return (int) ((RealTupleType)
+        ((FunctionType)data.getType()).getDomain()).getDimension();
+  }
+
+  /**
+   * Get the number of range components from a given Data object.
+   * 
+   * @param  data            VisAD Data object
+   * 
+   * @return                 Number of range components.
+   * 
+   * @throws VisADException  unable to construct field
+   * @throws RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static int getRangeDimension(final Data data)
+                  throws VisADException, RemoteException {
+     return rangeDimension(data);
+  }
+
+  /**
+   * get the number of range components of the Data object
+   * 
+   * @param  data            VisAD Data object
+   * 
+   * @return                 Number of range components
+   *
+   * @throws VisADException  unable to construct field
+   * @throws RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static int rangeDimension(final Data data) 
+                  throws VisADException, RemoteException {
+    int nr = 1;
+    if ( data instanceof FlatField) {
+      nr = ((FlatField) data).getRangeDimension();
+    }
+    return nr;
+  }
+
+
+  /** 
+   * Get the domain Type for the Data object
+   * 
+   * @param  data is the field to get the domain Type for
+   * 
+   * @return the domain type of the {@code data}
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static RealTupleType getDomainType(Data data)
+                  throws VisADException, RemoteException {
+     return domainType(data);
+  }
+
+  /** 
+   * Get the domain Type for the Data object
+   * 
+   * @param data is the field to get the domain Type for
+   * 
+    * @return the domain type of the {@code data}
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static RealTupleType domainType(Data data) 
+                  throws VisADException, RemoteException {
+    return (RealTupleType) ((FunctionType)data.getType()).getDomain();
+  }
+
+  /** 
+   * Get the domain Type for the FunctionType
+   * 
+   * @param type is the FunctionType
+   * 
+   * @return the domain type 
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static RealTupleType getDomainType(FunctionType type)
+                  throws VisADException, RemoteException {
+     return type.getDomain();
+  }
+
+  /** 
+   * Get the range Type for the field
+   *
+   * @param data is the field to get the range Type for
+   *
+   * @return the range type
+   *
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static MathType getRangeType(Data data)
+                  throws VisADException, RemoteException {
+     return rangeType(data);
+  }
+
+  /** get the range Type for the field
+  *
+  * @param data is the field to get the range Type for
+  *
+  * @return the range type
+  *
+  * @throws  VisADException  unable to construct field
+  * @throws  RemoteException part of data and display APIs, shouldn't occur
+  */
+  public static MathType rangeType(Data data) 
+                  throws VisADException, RemoteException {
+    return (MathType) ((FunctionType)data.getType()).getRange();
+  
+  }
+
+  /** get the range Type for the FunctionType
+  *
+  * @param type is the FunctionType
+  *
+  * @return the range Type
+  *
+  * @throws  VisADException  unable to construct field
+  * @throws  RemoteException part of data and display APIs, shouldn't occur
+  */
+  public static MathType getRangeType(FunctionType type)
+                  throws VisADException, RemoteException {
+     return type.getRange();
+  }
+
+  /**
+   * Get the name of the given component of the domain RealType.
+   * 
+   * @param   data            VisAD Data object
+   * @param   comp            the domain component index (0...)
+   * 
+   * @return Name of the Type of the selected domain component
+   *
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static String domainType (Data data,int comp) 
+                   throws VisADException, RemoteException {
+    return (String) ((RealTupleType)
+        ((FunctionType)data.getType()).getDomain()).
+                            getComponent(comp).toString();
+  }
+
+  /**
+   * Get the name of the given component of the range RealType.
+   * 
+   * @param   data            VisAD Data object
+   * @param   comp            the component index (0...)
+   * 
+   * @return the name of the RealType of the range component
+   *
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static String rangeType (Data data,int comp) 
+                   throws VisADException, RemoteException {
+    MathType rt = rangeType(data);
+    int rd = rangeDimension(data);
+    String dt = rt.toString();
+    if (rd > 1) dt = ((TupleType)rt).getComponent(comp).toString();
+    return dt;
+  }
+
+  /**
+   * get a VisAD Unit from the name given
+   * 
+   * @param   name            name of unit desired (degC, etc)
+   * 
+   * @return the Unit object corresponding to the name
+   * 
+   * @throws visad.data.units.NoSuchUnitException 
+   * @throws visad.data.units.ParseException 
+   *
+   */
+  public static Unit makeUnit(final String name) 
+         throws visad.data.units.NoSuchUnitException,
+         visad.data.units.ParseException {
+    return visad.data.units.Parser.parse(name);
+  }
+
+  /** 
+   * Make an Integer1DSet of given length
+   * 
+   * @param length is the desired length of the 1D Integer Set
+   * 
+   * @return the Integer1DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Integer1DSet makeDomain(final int length) 
+                       throws VisADException {
+    return new Integer1DSet(length);
+  }
+
+  /** 
+   * Make an Integer1DSet of given length and MathType
+   * 
+   * @param type is the MathType of the Set
+   * @param length is the desired length of the 1D Integer Set
+   * 
+   * @return the Integer1DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Integer1DSet makeDomain(final MathType type, final int length) 
+                       throws VisADException {
+    return new Integer1DSet(type, length);
+  }
+
+  /** 
+   * Make an Integer1DSet of given length and make a MathType 
+   * 
+   * @param name is the MathType name to use to create the MathType
+   * @param length is the desired length of the 1D Integer Set
+   * 
+   * @return the Integer1DSet
+   * 
+   * @throws VisADException 
+  */
+  public static Integer1DSet makeDomain(final String name, final int length) 
+                       throws VisADException {
+    return new Integer1DSet(RealType.getRealType(name), length);
+  }
+
+  /** 
+   * Make an Integer2DSet of given lengths.
+   * 
+   * @param lengthX is the desired length of the 2D Integer Set x
+   * @param lengthY is the desired length of the 2D Integer Set y
+   * 
+   * @return the Integer2DSet
+   * @throws VisADException 
+   */
+  public static Integer2DSet makeDomain(final int lengthX, final int lengthY) 
+                       throws VisADException {
+    return new Integer2DSet(lengthX, lengthY);
+  }
+
+  /** 
+   * Make an Integer2DSet of given lengths
+   * 
+   * @param type is the MathType of the Set
+   * @param lengthX is the desired length of the 2D Integer Set x
+   * @param lengthY is the desired length of the 2D Integer Set y
+   * 
+   * @return the Integer2DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Integer2DSet makeDomain(MathType type, int lengthX, int lengthY) 
+                       throws VisADException {
+    return new Integer2DSet(type, lengthX, lengthY);
+  }
+
+  /** 
+   * Make an Integer2DSet of given lengths.
+   * 
+   * @param name is the MathType name to use to create the MathType 
+   * (should be in the form:  "(xx,yy)" )
+   * @param lengthX is the desired length of the 2D Integer Set x
+   * @param lengthY is the desired length of the 2D Integer Set y
+   * 
+   * @return the Integer2DSet
+   * 
+   * @throws VisADException 
+   * @throws RemoteException 
+  */
+  public static Integer2DSet makeDomain 
+                      (String name, int lengthX, int lengthY) 
+                         throws VisADException, RemoteException {
+    return new Integer2DSet((RealTupleType) makeType(name), lengthX, lengthY);
+  }
+
+  /** 
+   * Create a Linear1DSet for domain samples
+   * 
+   * @param first is the first value in the linear set
+   * @param last is the last value in the linear set
+   * @param length is the number of values in the set
+   * 
+   * @return the created visad.Linear1DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear1DSet makeDomain 
+                       (double first, double last, int length) 
+                       throws VisADException {
+    return new Linear1DSet(first, last, length );
+  }
+
+  /**
+   * Create a Linear1DSet for domain samples
+   * 
+   * @param type is the VisAD MathType of this set
+   * @param first is the first value in the linear set
+   * @param last is the last value in the linear set
+   * @param length is the number of values in the set
+   * 
+   * @return the created visad.Linear1DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear1DSet makeDomain
+           (MathType type, double first, double last, int length) 
+           throws VisADException {
+    return new Linear1DSet(type, first, last, length);
+  }
+
+  /**
+   * Create a Linear1DSet for domain samples
+   * 
+   * @param name is the name of the VisAD MathType of this set
+   * @param first is the first value in the linear set
+   * @param last is the last value in the linear set
+   * @param length is the number of values in the set
+   * 
+   * @return the created visad.Linear1DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear1DSet makeDomain
+           (String name, double first, double last, int length) 
+           throws VisADException {
+    return new Linear1DSet(RealType.getRealType(name),first, last, length);
+  }
+
+  /*
+  public static Linear1DSet makeDomain(double[] vals) {
+    //if vals is sorted, make a Gridded1DSet; otherwise, Irregular1DSet
+    return (Linear1DSet) null;
+  }
+  public static Linear1DSet makeDomain(MathType type, double[] vals) {
+    //if vals is sorted, make a Gridded1DSet; otherwise, Irregular1DSet
+    return (Linear1DSet) null;
+  }
+  */
+
+  /** 
+   * Create a Linear2DSet for domain samples
+   * 
+   * @param first1 is the first value in the linear set's 1st dimension
+   * @param last1 is the last value in the linear set's 1st dimension
+   * @param length1 is the number of values in the set's 1st dimension
+   * @param first2 is the first value in the linear set's 2nd dimension
+   * @param last2 is the last value in the linear set's 2nd dimension
+   * @param length2 is the number of values in the set's 2nd dimension
+   * 
+   * @return the created visad.Linear2DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear2DSet makeDomain
+                    (double first1, double last1, int length1,
+                     double first2, double last2, int length2) 
+                     throws VisADException {
+    return new Linear2DSet(first1, last1, length1, 
+                            first2, last2, length2);
+  }
+
+  /** 
+   * Create a Linear2DSet for domain samples
+   * 
+   * @param type is the VisAD MathType of this set
+   * @param first1 is the first value in the linear set's 1st dimension
+   * @param last1 is the last value in the linear set's 1st dimension
+   * @param length1 is the number of values in the set's 1st dimension
+   * @param first2 is the first value in the linear set's 2nd dimension
+   * @param last2 is the last value in the linear set's 2nd dimension
+   * @param length2 is the number of values in the set's 2nd dimension
+   * 
+   * @return the created visad.Linear2DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear2DSet makeDomain (MathType type, 
+                         double first1, double last1, int length1, 
+                         double first2, double last2, int length2) 
+                         throws VisADException {
+    return new Linear2DSet(type, first1, last1, length1, 
+                                  first2, last2, length2);
+  }
+
+  /** 
+   * Create a Linear2DSet for domain samples
+   * 
+   * @param name is the name of the VisAD MathType of this set
+   * @param first1 is the first value in the linear set's 1st dimension
+   * @param last1 is the last value in the linear set's 1st dimension
+   * @param length1 is the number of values in the set's 1st dimension
+   * @param first2 is the first value in the linear set's 2nd dimension
+   * @param last2 is the last value in the linear set's 2nd dimension
+   * @param length2 is the number of values in the set's 2nd dimension
+   * 
+   * @return the created visad.Linear2DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear2DSet makeDomain (String name, 
+                         double first1, double last1, int length1, 
+                         double first2, double last2, int length2) 
+                         throws VisADException, RemoteException {
+
+    return new Linear2DSet((RealTupleType) (makeType(name)), 
+                                  first1, last1, length1, 
+                                  first2, last2, length2);
+  }
+
+  /** 
+   * Create a Linear3DSet for domain samples
+   * 
+   * @param first1 is the first value in the linear set's 1st dimension
+   * @param last1 is the last value in the linear set's 1st dimension
+   * @param length1 is the number of values in the set's 1st dimension
+   * @param first2 is the first value in the linear set's 2nd dimension
+   * @param last2 is the last value in the linear set's 2nd dimension
+   * @param length2 is the number of values in the set's 2nd dimension
+   * @param first3 is the first value in the linear set's 3rd dimension
+   * @param last3 is the last value in the linear set's 3rd dimension
+   * @param length3 is the number of values in the set's 3rd dimension
+   * @return the created visad.Linear3DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear3DSet makeDomain 
+                    (double first1, double last1, int length1,
+                     double first2, double last2, int length2,
+                     double first3, double last3, int length3) 
+                     throws VisADException {
+    return new Linear3DSet(first1, last1, length1, 
+                            first2, last2, length2,
+                            first3, last3, length3);
+  }
+
+  /**
+   * Create a Linear3DSet for domain samples
+   * 
+   * @param type is the VisAD MathType of this set
+   * @param first1 is the first value in the linear set's 1st dimension
+   * @param last1 is the last value in the linear set's 1st dimension
+   * @param length1 is the number of values in the set's 1st dimension
+   * @param first2 is the first value in the linear set's 2nd dimension
+   * @param last2 is the last value in the linear set's 2nd dimension
+   * @param length2 is the number of values in the set's 2nd dimension
+   * @param first3 is the first value in the linear set's 3rd dimension
+   * @param last3 is the last value in the linear set's 3rd dimension
+   * @param length3 is the number of values in the set's 3rd dimension
+   * @return the created visad.Linear3DSet
+   * 
+   * @throws VisADException 
+   */
+  public static Linear3DSet makeDomain (MathType type, 
+                         double first1, double last1, int length1, 
+                         double first2, double last2, int length2,
+                         double first3, double last3, int length3) 
+                         throws VisADException {
+
+    return new Linear3DSet(type,  first1, last1, length1, 
+                                  first2, last2, length2,
+                                  first3, last3, length3);
+  }
+
+  /**
+   * Create a Linear3DSet for domain samples
+   * 
+   * @param name is the name of the VisAD MathType of this set
+   * @param first1 is the first value in the linear set's 1st dimension
+   * @param last1 is the last value in the linear set's 1st dimension
+   * @param length1 is the number of values in the set's 1st dimension
+   * @param first2 is the first value in the linear set's 2nd dimension
+   * @param last2 is the last value in the linear set's 2nd dimension
+   * @param length2 is the number of values in the set's 2nd dimension
+   * @param first3 is the first value in the linear set's 3rd dimension
+   * @param last3 is the last value in the linear set's 3rd dimension
+   * @param length3 is the number of values in the set's 3rd dimension
+   * @return the created visad.Linear3DSet
+   * 
+   * @throws RemoteException
+   * @throws VisADException 
+   */
+  public static Linear3DSet makeDomain (String name,
+                         double first1, double last1, int length1, 
+                         double first2, double last2, int length2,
+                         double first3, double last3, int length3) 
+                         throws VisADException, RemoteException {
+
+    return new Linear3DSet((RealTupleType) (makeType(name)), 
+                                  first1, last1, length1, 
+                                  first2, last2, length2,
+                                  first3, last3, length3);
+  }
+
+  /** 
+   * Return the sampling set for the domain of the Data object
+   * 
+   * @param data is the VisAD data object
+   * 
+   * @return the sampling Set
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static Set getDomainSet(Data data) 
+             throws VisADException, RemoteException {
+    return (Set) ((Field)data).getDomainSet();
+  }
+
+  /** 
+   * Return the sampling set for the domain of the Data object
+   * 
+   * @param data is the VisAD data object
+   * 
+   * @return the sampling Set
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static Set getDomain(Data data) 
+             throws VisADException, RemoteException {
+    return (Set) ((Field)data).getDomainSet();
+  }
+
+  /**
+   * Return the lengths of the components of the sampling set 
+   * 
+   * @param data is the VisAD data object (Field or Set or Tuple; Scalars return 1.
+   * 
+   * @return an int[] of the length(s)
+   * 
+   * @throws  VisADException  unable to construct field
+   * @throws  RemoteException part of data and display APIs, shouldn't occur
+   */
+  public static int[] getDomainSizes(Data data)
+             throws VisADException, RemoteException {
+    if (data instanceof Field) {
+      Set set = ((Field)data).getDomainSet();
+      if (set instanceof GriddedSet) {
+        return ((GriddedSet)set).getLengths();
+      } else {
+        return new int[]{set.getLength()};
+      }
+    } else if (data instanceof GriddedSet) {
+      return ((GriddedSet)data).getLengths();
+    } else if (data instanceof Set) {
+        return new int[]{((Set)data).getLength()};
+    } else if (data instanceof Tuple) {
+        return new int[]{((Tuple)data).getLength()};
+    } else if (data instanceof Scalar) {
+        return new int[]{1};
+    } else {
+      throw new VisADException("Cannot get domain sizes for this data.");
+    }
+  }
+
+  /**
+  * Replaces specified values in a FlatField with the constant given
+  * 
+  * @param f is the input FlatField
+  * @param list is the int[] list of indecies into f to replace
+  * @param v is the value to insert into f.
+  * 
+  * @return a new FlatField 
+  * 
+  * @throws RemoteException 
+  * @throws VisADException 
+  */
+  public static FlatField replace(FieldImpl f, int[] list, Real v) 
+             throws VisADException, RemoteException {
+    return replace(f, list, v.getValue());
+  }
+
+  /**
+  * Replaces specified values in a FlatField with the constant given
+  *
+  * @param f is the input FlatField
+  * @param list is the int[] list of indecies into f to replace
+  * @param v is the value to insert into f.
+   * @return the new FlatField
+   * @throws VisADException 
+   * @throws RemoteException 
+  */
+  public static FlatField replace(FieldImpl f, int[] list, double v) 
+             throws VisADException, RemoteException {
+    FlatField ff;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else {
+      ff = (FlatField)((FlatField)f.getSample(0)).clone();
+    }
+    float [][] dv = ff.getFloats(false);
+
+    for (int i=0; i<list.length; i++) {
+      dv[0][list[i]] = (float)v;
+    }
+
+    ff.setSamples(dv,false);
+    return ff;
+
+  }
+
+  /**
+  * Return a mask for points with navigation (1) or not (0)
+  *
+  * @param f is the input FieldImpl -- if not a FlatField, then try to
+  *    get one!
+  * @return the FlatField of mask values
+  * @throws VisADException 
+  * @throws RemoteException 
+  */
+  public static FlatField maskNoNavigation(FieldImpl f) 
+             throws VisADException, RemoteException {
+    FlatField ff;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else {
+      ff = (FlatField)((FlatField)f.getSample(0)).clone();
+    }
+
+    GriddedSet ds = null;
+    if (f instanceof FlatField) {
+      ds = (GriddedSet)((FlatField)f).getDomainSet();
+    } else {
+      ds = (GriddedSet)( (FlatField)(f.getSample(0))).getDomainSet();
+    }
+
+    float[][] latlon;
+    try {
+      latlon = getLatLons(ds);
+    } catch (VisADException noll) {
+      latlon = null;
+    }
+
+    float [][] dv = ff.getFloats(false);  // lazy way to dimension
+
+    for (int k=0; k<dv[0].length; k++) {
+      if ( (latlon == null) || (latlon[0][k] != latlon[0][k]) ) {
+          dv[0][k] = 0.f;
+      } else {
+          dv[0][k] = 1.f;
+      }
+    }
+
+    ff.setSamples(dv,false);
+    return ff;
+    
+  }
+  /**
+  * For all non-navigatable points in the given FlatField, replace the
+  *   FF's values with missing (Float.NaN).
+  *
+  * This is useful when an AREA file is off the planet, since there is
+  * no explicit "missing" value in AREA files, and it only indicated
+  * by "missing" navigation (lat/lon) information.
+  *
+  * @param f is the input FieldImpl -- if not a FlatField, then try to
+  *    get one!
+  * @return the FlatField with missing values where there is no navigation
+  * @throws VisADException 
+  * @throws RemoteException 
+  */
+  public static FlatField setMissingNoNavigation(FieldImpl f) 
+             throws VisADException, RemoteException {
+    FlatField ff;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else {
+      ff = (FlatField)((FlatField)f.getSample(0)).clone();
+    }
+
+    GriddedSet ds = null;
+    if (f instanceof FlatField) {
+      ds = (GriddedSet)((FlatField)f).getDomainSet();
+    } else {
+      ds = (GriddedSet)( (FlatField)(f.getSample(0))).getDomainSet();
+    }
+
+    float[][] latlon;
+    try {
+      latlon = getLatLons(ds);
+    } catch (VisADException noll) {
+      latlon = null;
+    }
+
+    float [][] dv = ff.getFloats(false);
+
+    for (int k=0; k<dv[0].length; k++) {
+      if ( (latlon == null) || (latlon[0][k] != latlon[0][k]) ) {
+          dv[0][k] = Float.NaN;
+      }
+    }
+
+    ff.setSamples(dv,false);
+    return ff;
+    
+  }
+
+  /**
+  * Replaces all the given values in a FlatField with the missing value (Float.NaN);
+  *
+  * @param f is the input FlatField
+  * @param v is the value to replace with NaN.
+   * @return the new FlatField
+   * @throws VisADException 
+   * @throws RemoteException 
+  */
+  public static FlatField setToMissing(FieldImpl f, double v) 
+             throws VisADException, RemoteException {
+    FlatField ff;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else {
+      ff = (FlatField)((FlatField)f.getSample(0)).clone();
+    }
+    float [][] dv = ff.getFloats(false);
+    for (int i=0; i<dv[0].length; i++) {
+      if (dv[0][i] == v) dv[0][i] = Float.NaN;
+    }
+
+    ff.setSamples(dv,false);
+    return ff;
+
+  }
+
+  /**
+  * Replaces all the missing values in a FlatField with the constant given
+  *
+  * @param f is the input FlatField
+  * @param v is the value to insert into f.
+   * @return the new FlatField
+   * @throws VisADException 
+   * @throws RemoteException 
+  */
+  public static FlatField replaceMissing(FieldImpl f, double v) 
+             throws VisADException, RemoteException {
+    FlatField ff;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else {
+      ff = (FlatField)((FlatField)f.getSample(0)).clone();
+    }
+    float [][] dv = ff.getFloats(false);
+    for (int i=0; i<dv[0].length; i++) {
+      if (dv[0][i] != dv[0][i]) dv[0][i] = (float)v;
+    }
+
+    ff.setSamples(dv,false);
+    return ff;
+
+  }
+
+  /**
+  * Replaces all the values in a FlatField with the constant given
+  *
+  * @param f is the input FlatField
+  * @param v is the value to insert into f.
+   * @return the new FlatField
+   * @throws VisADException 
+   * @throws RemoteException 
+  */
+  public static FlatField replace(FieldImpl f, double v) 
+             throws VisADException, RemoteException {
+    FlatField ff;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else {
+      ff = (FlatField)((FlatField)f.getSample(0)).clone();
+    }
+    float [][] dv = ff.getFloats(false);
+    for (int i=0; i<dv[0].length; i++) {
+      dv[0][i] = (float)v;
+    }
+
+    ff.setSamples(dv,false);
+    return ff;
+
+  }
+
+  /**
+  * Replaces all the values in a FlatField with the constant given
+  *
+  * @param f is the input FlatField
+  * @param v is the value to insert into f.
+  */
+  public static FlatField replace(FieldImpl f, Real v) 
+             throws VisADException, RemoteException {
+    FlatField ff;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else {
+      ff = (FlatField)((FlatField)f.getSample(0)).clone();
+    }
+    float [][] dv = ff.getFloats(false);
+
+    float vv = (float)(v.getValue());
+    for (int i=0; i<dv[0].length; i++) {
+      dv[0][i] = vv;
+    }
+
+    ff.setSamples(dv,false);
+    return ff;
+
+  }
+
+  /**
+  * Find the minium and maximum values of the FlatField or
+  * a sequence within a FieldImpl.  
+  *
+  * @param f the FlatField (or FieldImpl - for a sequence)
+  *
+  * @return double[2].  double[0] = min, double[1] = max
+  *   if the fields are all missing, then return min = max = Double.NaN
+  *
+  * @throws VisADException 
+  * @throws RemoteException 
+  */
+
+  public static double[] getMinMax(FieldImpl f)
+       throws VisADException, RemoteException {
+
+    boolean isFI = false;
+    int numItems;
+    if (f instanceof FlatField) {
+      numItems = 1;
+     
+    } else if (domainDimension(f) == 1) {
+      isFI = true;
+      numItems = getDomainSet(f).getLength();
+
+    } else {
+      throw new VisADException("Cannot rescale the data - unknown structure");
+    }
+
+    double [] minmax = new double[2];
+    minmax[0] = Double.POSITIVE_INFINITY;
+    minmax[1] = Double.NEGATIVE_INFINITY;
+    float[][] dv;
+
+    for (int m=0; m<numItems; m++) {
+      if (isFI) {
+        dv = ( (FlatField)(f.getSample(m))).getFloats(false);
+      } else {
+        dv = f.getFloats(false);
+      }
+
+      for (int i=0; i<dv.length; i++) {
+        for (int k=0; k<dv[i].length; k++) {
+          if (dv[i][k] < minmax[0]) minmax[0] = dv[i][k];
+          if (dv[i][k] > minmax[1]) minmax[1] = dv[i][k];
+        }
+      }
+    }
+
+    // if fields were all NaN, return NaN's as well....
+
+    if (minmax[0] > minmax[1]) {
+      minmax[0] = Double.NaN;
+      minmax[1] = Double.NaN;
+    }
+
+    return minmax;
+  }
+
+  /**
+  * Re-scale the values in a FieldImpl using auto-scaling
+  *
+  * @param f the FlatField (or FieldImpl sequence)
+  * @param outlo the output low-range value
+  * @param outhi the output high range value
+  *
+  * Values of the original field will be linearly
+  *    scaled from their "min:max" to "outlo:outhi"
+  *
+  * If input FieldImpl is a sequence, then all items in sequence are done
+  * but the "min" and "max" are computed from all members of the sequence! 
+  *
+  * @return new FieldImpl
+  * @throws VisADException 
+  * @throws RemoteException 
+  *
+  */
+  public static FieldImpl rescale(FieldImpl f, double outlo, double outhi)
+       throws VisADException, RemoteException {
+    double [] minmax = getMinMax(f);
+    return rescale(f,minmax[0], minmax[1], outlo, outhi);
+  }
+
+  /**
+  * Re-scale the values in a FieldIimpl
+  *
+  * @param f the FieldImpl or FlatField
+  * @param inlo the input low-range value
+  * @param inhi the input high-range value
+  * @param outlo the output low-range value
+  * @param outhi the output high range value
+  *
+  * Values of the original field will be linearly
+  *    scaled from "inlo:inhi" to "outlo:outhi"
+  * 
+  * Values < inlo will be set to outlo; values > inhi set to outhi
+  *
+  * If input FieldImpl is a sequence, then all items in sequence are done
+  *
+  * @return values in new FieldImpl
+  * @throws VisADException 
+  * @throws RemoteException 
+  */
+
+  public static FieldImpl rescale(FieldImpl f, 
+    double inlo, double inhi, double outlo, double outhi)
+             throws VisADException, RemoteException {
+        
+    FlatField ff = null;
+    FieldImpl fi = null;
+    boolean isFI = false;
+    int numItems = 1;
+    if (f instanceof FlatField) {
+      try {
+      ff = (FlatField)f.clone();
+      } catch (CloneNotSupportedException cns) {
+        throw new VisADException ("Cannot clone field object");
+      }
+     
+    } else if (domainDimension(f) == 1) {
+      isFI = true;
+      try {
+        fi = (FieldImpl)f.clone();
+        numItems = getDomainSet(f).getLength();
+      } catch (CloneNotSupportedException cnsfi) {
+        throw new VisADException ("Cannot clone FieldImpl object");
+      }
+    } else {
+      throw new VisADException("Cannot rescale the data - unknown structure");
+    }
+
+    float [][] dv;
+    for (int m=0; m<numItems; m++) {
+      if (isFI) {
+        dv = ( (FlatField)(fi.getSample(m))).getFloats(false);
+      } else {
+        dv = ff.getFloats(false);
+      }
+
+      double outrange = outhi - outlo;
+      double inrange = inhi - inlo;
+      for (int i=0; i<dv.length; i++) {
+        for (int k=0; k<dv[i].length; k++) {
+          dv[i][k] = (float)(outlo + outrange * (dv[i][k] - inlo)/inrange);
+          if (dv[i][k] < outlo) dv[i][k] = (float)outlo;
+          if (dv[i][k] > outhi) dv[i][k] = (float)outhi;
+        }
+      }
+
+      if (isFI) {
+        ( (FlatField)(fi.getSample(m))).setSamples(dv,false);
+      } else {
+        ff.setSamples(dv,false);
+      }
+    }
+
+    if (isFI) {
+      return fi;
+    } else {
+      return (FieldImpl)ff;
+    }
+  }
+
+  /**
+   * Convert the domain to the reference earth located points.
+   * If the domain is not in lat/lon order then reset the order so
+   * that result[0] is the latitudes, result[1] is the longitudes
+   *
+   * by Don Murray
+   *
+   * @param domain  the domain set
+   * @param index is the (optional) array of indecies to get points at
+   *
+   * @return  the lat/lon/(alt) points
+   *
+   * @throws VisADException  problem converting points
+   */
+  public static float[][] getLatLons(GriddedSet domain, int[] index)
+          throws VisADException {
+      boolean   isLatLon = isLatLonOrder(domain);
+      float[][] values   = getEarthLocationPoints(domain);
+      if ( !isLatLon) {
+          float[] tmp = values[0];
+          values[0] = values[1];
+          values[1] = tmp;
+      }
+
+      if (index == null) return values;
+
+      float vals[][] = new float[2][index.length];
+      for (int i=0; i<index.length; i++) {
+        vals[0][i] = values[0][index[i]];
+        vals[1][i] = values[1][index[i]];
+      }
+      return vals;
+  }
+
+  /**
+   * Convert the domain to the reference earth located points.
+   * If the domain is not in lat/lon order then reset the order so
+   * that result[0] is the latitudes, result[1] is the longitudes
+   *
+   * by Don Murray
+   *
+   * @param domain  the domain set
+   *
+   * @return  the lat/lon/(alt) points
+   *
+   * @throws VisADException  problem converting points
+   */
+  public static float[][] getLatLons(GriddedSet domain)
+         throws VisADException {
+     return getLatLons(domain, null);
+  }
+
+  /**
+   * Convert the domain to the reference earth located points.
+   * If the domain is not in lat/lon order then reset the order so
+   * that result[0] is the latitudes, result[1] is the longitudes
+   *
+   * by Tom Whittaker
+   *
+   * @param domain  the domain set
+   *
+   * @return  the lat/lon/(alt) points
+   *
+   * @throws VisADException  problem converting points
+   */
+  public static float[][][] getLatLons2D(GriddedSet domain) 
+         throws VisADException {
+    int[] nxy = domain.getLengths();
+    int nx = nxy[0];
+    int ny = nxy[1];
+    float[][] latlon;
+    try {
+      latlon = getLatLons(domain);
+    } catch (VisADException noll) {
+      return null;
+    }
+
+    float[][][] vals = new float[2][nx][ny];
+    int k = 0;
+    for (int x=1; x<nx-1; x++) {
+      for (int y=1; y<ny-1; y++) {
+        k = x + nx*y;
+        vals[0][x][y] = latlon[0][k];
+        vals[1][x][y] = latlon[1][k];
+      }
+    }
+    return vals;
+
+  }
+
+  /**
+   * Convert the domain to the reference earth located points
+   *
+   * @param domain  the domain set
+   *
+   * @return  the lat/lon/(alt) points
+   *
+   * @throws VisADException  problem converting points
+   *
+   * by Don Murray
+   */
+  public static float[][] getEarthLocationPoints(GriddedSet domain)
+          throws VisADException {
+      CoordinateSystem cs = domain.getCoordinateSystem();
+      if (cs == null) {
+          return domain.getSamples();
+      }
+      RealTupleType refType  = cs.getReference();
+      Unit[]        refUnits = cs.getReferenceUnits();
+      float[][]     points   = CoordinateSystem.transformCoordinates(refType,
+                             null, refUnits, null,
+                             ((SetType) domain.getType()).getDomain(), cs,
+                             domain.getSetUnits(), domain.getSetErrors(),
+                             domain.getSamples(), false);
+      return points;
+  }
+
+  /**
+   * Check to see if this is a navigated domain (can be converted to
+   * lat/lon)
+   *
+   * @param spatialSet   spatial domain of the grid
+   *
+   * @return  true if the domain of the grid is in or has a reference to
+   *               Latitude/Longitude
+   *
+   * @throws VisADException   can't get at VisAD objects
+   *
+   * by Don Murray
+   */
+
+  public static boolean isLatLonOrder(SampledSet spatialSet)
+          throws VisADException {
+      RealTupleType spatialType =
+          ((SetType) spatialSet.getType()).getDomain();
+      RealTupleType spatialReferenceType =
+          (spatialSet.getCoordinateSystem() != null)
+          ? spatialSet.getCoordinateSystem().getReference()
+          : null;
+      return (spatialType.equals(RealTupleType.LatitudeLongitudeTuple)
+              || spatialType.equals(RealTupleType.LatitudeLongitudeAltitude) 
+              || ((spatialReferenceType != null) && 
+                  (spatialReferenceType.equals(RealTupleType.LatitudeLongitudeTuple) 
+              || spatialReferenceType.equals(RealTupleType.LatitudeLongitudeAltitude))));
+  }
+
+
+  /** construct a Field containing the computed area
+  *   of each data point
+  * @param f VisAD data object (FlatField or FieldImpl) as source
+  * 
+  * @return FlatField of the computed areas
+  *
+  */
+  public static FlatField createAreaField(FieldImpl f)
+        throws VisADException, RemoteException {
+
+    GriddedSet ds = null;
+    if (f instanceof FlatField) {
+      ds = (GriddedSet)((FlatField)f).getDomainSet();
+    } else {
+      ds = (GriddedSet)( (FlatField)(f.getSample(0))).getDomainSet();
+    }
+
+    int[] nxy = ds.getLengths();
+    int nx = nxy[0];
+    int ny = nxy[1];
+
+    float[][] latlon;
+    try {
+      latlon = getLatLons(ds);
+    } catch (VisADException noll) {
+      return null;
+    }
+
+    float[][] area = new float[1][nx * ny];
+
+    int k = 0;
+    for (int x=1; x<nx-1; x++) {
+      for (int y=1; y<ny-1; y++) {
+        k = x + nx*y;
+
+        // a = cos(lat)*(dlon) * (dlat) * 111.1^2  (km per degree of lat)
+        // dlat and dlon are over 2 points...so 111.1/2 * 111.1/2 = 3085.8025
+        
+        //area[0][k] = (float) Math.abs(3085.8025 * (lats[x][y-1] - lats[x][y+1]) * Math.cos(lats[x][y]*.01745329252) * (lons[x+1][y] - lons[x-1][y]));
+
+        area[0][k] = (float) Math.abs(3085.8025 * 
+          (latlon[0][k-nx] - latlon[0][k+nx]) * 
+          Math.cos(latlon[0][k]*.01745329252) * 
+          (latlon[1][k+1] - latlon[1][k-1]));
+      }
+    }
+
+    // now fix up the edges
+    k = nx*(ny-1);
+    for (int x=0; x<nx; x++) {
+      area[0][x] = area[0][x+nx];
+      area[0][x+k] = area[0][x+k-nx];
+    }
+    for (int y=0; y<ny; y++) {
+      k = nx*y;
+      area[0][k] = area[0][k+1];
+      area[0][k+nx-1] = area[0][k+nx-2];
+    }
+
+    // Now create the VisAD FlatField...
+    MathType domain = ((SetType) ds.getType()).getDomain();
+    Unit u = null;
+    try { u = makeUnit("km2"); } 
+    catch (Exception e) { }
+
+    RealType range = makeRealType("area",u);
+    FunctionType ftype = new FunctionType(domain, range);
+    FlatField field = new FlatField(ftype, ds);
+    field.setSamples(area,false);
+    latlon = null;
+    return field;
+  }
+
+  /** Sum up the values of each point named in the list (see
+  *   "createAreaField" method)
+  *
+  * @param f VisAD FlatField containing the values to be summed at each point
+  * @param list an array of points to be used to sum up the values
+  *    (see the "find" method)
+  * 
+  * @return the summation of the data values defined in the list or -1
+  * if unable to compute.
+  *
+  */
+  public static double computeSum(FlatField f, int[] list) 
+             throws VisADException, RemoteException {
+
+    if (f == null) return Double.NaN;
+
+    float [][] dv = f.getFloats(false);
+
+    double sum = 0.0;
+    for (int i=0; i<list.length; i++) {
+      if (!Float.isNaN(dv[0][list[i]])) sum = sum + dv[0][list[i]];
+    }
+    return sum;
+  }
+
+  /** Compute the average of each point named in the list (see
+  *   "createArea" method)
+  *
+  * @param f VisAD FlatField containing the values to be averaged at each point
+  * @param list an array of points to be used to compute the average values
+  *    (see the "find" method)
+  * 
+  * @return the average of the data values defined in the list
+  *
+  */
+  public static double computeAverage(FlatField f, int[] list) 
+             throws VisADException, RemoteException {
+
+    if (f == null) return Double.NaN;
+
+    float [][] dv = f.getFloats(false);
+
+    double sum = 0.0;
+    double count =0.0;
+    for (int i=0; i<list.length; i++) {
+      if (!Float.isNaN(dv[0][list[i]])) {
+        sum = sum + dv[0][list[i]];
+        count = count + 1;
+      }
+    }
+
+    if (count > 0.0) {
+      return (sum/count);
+    } else {
+      return Double.NaN;
+    }
+  }
+
+  /**
+  * Mask out values outside testing limits in a FieldImpl
+  *
+  * @param f  VisAD data object (FlatField or FieldImpl) as source
+  * @param op  Comparison operator as string ('gt','le',...)
+  * @param v  Numeric operand for comparison
+  *
+  * @return a FieldImpl with values of either 0 (did not meet
+  * criterion) or 1 (met criteron).
+  *
+  * Example:  b = mask(a, 'gt', 100)
+  * if 'a' is an image, 'b' will be an image with values of
+  * 1 where 'a' was > 100, and zero elsewhere.
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static FieldImpl mask(FieldImpl f, String op, double v) 
+             throws VisADException, RemoteException {
+    return mask(f, op, new Real(v), false);
+  }
+
+  /**
+  * Mask out values outside testing limits in a FieldImpl
+  *
+  * @param f  VisAD data object (FlatField or FieldImpl) as source
+  * @param op  Comparison operator as string ('gt','le',...)
+  * @param v  Numeric operand for comparison
+  * @param useNaN  if true, then NaN is used instead of zero for result. 
+  *
+  * @return a FieldImpl with values of either 0 (did not meet
+  * criterion) or 1 (met criteron).
+  *
+  * Example:  b = mask(a, 'gt', 100)
+  * if 'a' is an image, 'b' will be an image with values of
+  * 1 where 'a' was > 100, and zero elsewhere.
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static FieldImpl mask(FieldImpl f, String op, double v, boolean useNaN) 
+             throws VisADException, RemoteException {
+    return mask(f, op, new Real(v), useNaN);
+  }
+
+
+  /**
+   *  Mask out values outside testing limits...
+   * 
+   * @param f  VisAD data object (FlatField or FieldImpl) as source
+   * @param op  Comparison operator as string ('gt','le',...)
+   * @param v  Numeric operand for comparison
+   * @return a FieldImpl with values of either 0 (did not meet
+   * criterion) or 1 (met criteron).
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static FieldImpl mask(Data f, String op, Data v) 
+             throws VisADException, RemoteException {
+    if (! (f instanceof FieldImpl) ) {
+      throw new VisADException("Data must be a FieldImpl or FlatField");
+    }
+    return mask((FieldImpl)f, op, v, false);
+  }
+
+
+  /**
+  * Mask out values outside testing limits in a FieldImpl
+  *
+  * @param f  VisAD data object (FlatField or FieldImpl) as source
+  * @param op  Comparison operator as string ('gt','le',...)
+  * @param v  VisAd operand for comparison.
+  *
+  * If the value of 'v' is a Field, then it will be resampled
+  * to the domain of 'f' is possible before the comparison.
+  *
+  * @return a FieldImpl with values of either 0 (did not meet
+  * criterion) or 1 (met criteron).
+  *
+  * Example:  b = mask(a, 'gt', c)
+  * if 'a' is an image, 'b' will be an image with values of
+  * 1 where 'a' was > the corresponding value of 'c', and zero 
+  * elsewhere.
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static FieldImpl mask(FieldImpl f, String op, Data v)
+             throws VisADException, RemoteException {
+
+     return mask(f, op, v, false);
+  }
+
+
+  /**
+  * Mask out values outside testing limits in a FieldImpl
+  *
+  * @param f  VisAD data object (FlatField or FieldImpl) as source
+  * @param op  Comparison operator as string ('gt','le',...)
+  * @param v  VisAd operand for comparison.
+  * @param useNaN if true, then NaN will be used for "false"
+  *
+  * If the value of 'v' is a Field, then it will be resampled
+  * to the domain of 'f' is possible before the comparison.
+  *
+  * @return a FieldImpl with values of either 0 or NaN (did not meet
+  * criterion) or 1 (met criteron).
+  *
+  * Example:  b = mask(a, 'gt', c)
+  * if 'a' is an image, 'b' will be an image with values of
+  * 1 where 'a' was > the corresponding value of 'c', and zero or NaN
+  * elsewhere.
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static FieldImpl mask(FieldImpl f, String op, Data v, boolean useNaN)
+             throws VisADException, RemoteException {
+    FlatField ff = null;
+    FieldImpl fi = null;
+    boolean isFI = false;
+    int numItems;
+    Set ds = null;
+    float fail = 0.0f;
+    if (useNaN) fail = Float.NaN;
+
+    if (f instanceof FlatField) {
+      numItems = 1;
+      ds = ((FlatField)f).getDomainSet();
+     
+    } else if (domainDimension(f) == 1) {
+      isFI = true;
+      numItems = getDomain(f).getLength();
+      ds = ( (FlatField)(f.getSample(0))).getDomainSet();
+
+    } else {
+      throw new VisADException("Cannot rescale the data - unknown structure");
+    }
+
+    int oper = -1;
+    for (int i=0; i<ops.length; i++) {
+      if (ops[i].equalsIgnoreCase(op)) oper = i;
+      if (ops_sym[i].equalsIgnoreCase(op)) oper = i;
+    }
+    if (oper < 0) throw new VisADException("Invalid operator: "+op);
+
+    MathType domain = ((SetType) ds.getType()).getDomain();
+    Unit u = null;
+    try { u = makeUnit(""); } 
+    catch (Exception e) { }
+
+    RealType range = makeRealType("mask",u);
+    FunctionType ftype = new FunctionType(domain, range);
+    FlatField field = new FlatField(ftype, ds);
+
+    if (isFI) {
+      Set dsfi = f.getDomainSet();
+      MathType dsdom = ((SetType) f.getDomainSet().getType()).getDomain();
+      FunctionType dsft = new FunctionType(dsdom, ftype);
+      fi = new FieldImpl(dsft, dsfi);
+    }
+    
+    float[][] dv;
+    boolean isReal = false;
+    float vv = 0.0f;
+
+    if (v.getType() == visad.RealType.Generic) {
+      isReal = true;
+      vv = (float)((Real)v).getValue();
+    }
+
+    for (int m=0; m<numItems; m++) {
+      
+      if (isReal) {
+        if (isFI) {
+          ff =  (FlatField)(f.getSample(m));
+
+        } else {
+          ff = (FlatField)f;
+        }
+
+      } else {
+
+        if (isFI) {
+          ff =  (FlatField)((f.getSample(m)).subtract(v));
+
+        } else {
+          ff = (FlatField) f.subtract(v);
+        }
+
+      }
+
+      // get copy of values for comparison...will replace with results.
+      dv = ff.getFloats(true);
+
+      for (int i=0; i<dv.length; i++) {
+        for (int k=0; k<dv[i].length; k++) {
+          if (oper == 0) {
+            if (dv[i][k] > vv) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+          } else if (oper == 1 || oper == 7) {
+            if (dv[i][k] >= vv) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+          } else if (oper == 2) {
+            if (dv[i][k] < vv) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+          } else if (oper == 3 || oper == 8) {
+            if (dv[i][k] <= vv) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+          } else if (oper == 4) {
+            if (dv[i][k] == vv) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+          } else {  // only 5 or 6 are left
+            if (dv[i][k] != vv) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+          }
+        }
+      }
+
+      field.setSamples(dv,false);
+
+      if (isFI) {
+        fi.setSample(m, field);
+      }
+    }
+
+    if (isFI) {
+      return fi;
+    } else {
+      return (FieldImpl)field;
+    }
+  }
+
+  /**
+  * Mask out with 1.0's those values inside the given range;  set
+  *  values outside the range with zero or NaN.
+  *
+  * @param f  VisAD data object (FlatField or FieldImpl) as source
+  * @param vmin The lower limit of the range
+  * @param vmax  The upper limit of the range
+  * @param useNaN  Set to true to use NaN as the "outside the range" value; otherwise, use zero.
+  *
+  * The range is exclusive; that is if vmin < values < vmax then 1.0
+  * is returned.
+  *
+  * @return a FieldImpl with values of either 0 (or NaN, meaning: did not meet
+  * criterion) or 1 (met criteron).
+  *
+  * Example:  b = maskWithinRange(a, 100, 200, true)
+  * if 'a' is an image, 'b' will be an image with values of
+  * 1 where values in 'a' were between 'vmin' and 'vmax' and zero (or NaN)
+  * elsewhere.
+  *
+  * @throws VisADException 
+  * @throws RemoteException 
+  *
+  */
+  public static FieldImpl maskWithinRange(FieldImpl f, double vmin, double vmax, boolean useNaN) throws VisADException, RemoteException {
+    return maskRange(f, vmin, vmax, useNaN, true);
+  }
+
+  /**
+  * Mask out with 1.0's those values outside the given range;
+  *  otherwise, set the values to zero or NaN.
+  *
+  * @param f  VisAD data object (FlatField or FieldImpl) as source
+  * @param vmin The lower limit of the range
+  * @param vmax  The upper limit of the range
+  * @param useNaN  Set to true to use NaN as the "not outside the range" value; otherwise, use zero.
+  *
+  * The range is exclusive; that is for values < vmin or values > vmax,
+  * the returned value will be 1.0.
+  *
+  * @return a FieldImpl with values of either 0 (or NaN, meaning: did not meet
+  * criterion) or 1 (met criteron).
+  *
+  * Example:  b = maskOutsideRange(a, 100, 200, true)
+  * if 'a' is an image, 'b' will be an image with values of
+  * 1 where values in 'a' were less than 'vmin' or greater than 'vmax' 
+  * and zero (or NaN) elsewhere.
+  *
+  * @throws VisADException 
+  * @throws RemoteException 
+  *
+  */
+
+  public static FieldImpl maskOutsideRange(FieldImpl f, double vmin, double vmax, boolean useNaN) throws VisADException, RemoteException {
+    return maskRange(f, vmin, vmax, useNaN, false);
+  }
+
+  /**  
+  *  mask range (within or not within)
+  *
+  * @param f  VisAD data object (FlatField or FieldImpl) as source
+  * @param vmin The lower limit of the range
+  * @param vmax  The upper limit of the range
+  * @param useNaN  Set to true to use NaN as the non-mask value; otherwise, use zero.
+  * @param doWithin  Set to true to do "within range", or false for "outside range"
+  *
+  * @return a FieldImpl with values of either 0 (or NaN, meaning: did not meet
+  * criterion) or 1 (met criteron).
+  *
+  */
+  private static FieldImpl maskRange(FieldImpl f, double vmin, double vmax, boolean useNaN, boolean doWithin) throws VisADException, RemoteException {
+
+    FieldImpl fi = null;
+    boolean isFI = false;
+    int numItems;
+    Set ds = null;
+    float fail = 0.0f;
+    if (useNaN) fail = Float.NaN;
+
+    if (f instanceof FlatField) {
+      numItems = 1;
+      ds = ((FlatField)f).getDomainSet();
+     
+    } else if (domainDimension(f) == 1) {
+      isFI = true;
+      numItems = getDomain(f).getLength();
+      ds = ( (FlatField)(f.getSample(0))).getDomainSet();
+
+    } else {
+      throw new VisADException("Cannot rescale the data - unknown structure");
+    }
+
+    MathType domain = ((SetType) ds.getType()).getDomain();
+    Unit u = null;
+    try { u = makeUnit(""); } 
+    catch (Exception e) { }
+
+    RealType range = makeRealType("mask",u);
+    FunctionType ftype = new FunctionType(domain, range);
+    FlatField field = new FlatField(ftype, ds);
+
+    if (isFI) {
+      Set dsfi = f.getDomainSet();
+      MathType dsdom = ((SetType) f.getDomainSet().getType()).getDomain();
+      FunctionType dsft = new FunctionType(dsdom, ftype);
+      fi = new FieldImpl(dsft, dsfi);
+    }
+    
+    float[][] dv;
+
+    for (int m=0; m<numItems; m++) {
+      
+      if (isFI) {
+        dv = ((FlatField)(f.getSample(m))).getFloats(true);
+
+      } else {
+        dv = ((FlatField)f).getFloats(true);
+      }
+
+      // get copy of values for comparison...will replace with results.
+
+      for (int i=0; i<dv.length; i++) {
+        for (int k=0; k<dv[i].length; k++) {
+          if (doWithin) {
+            if (dv[i][k] > vmin && dv[i][k] < vmax) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+
+          } else {
+            if (dv[i][k] < vmin || dv[i][k] > vmax) {
+              dv[i][k] = 1.0f;
+            } else {
+              dv[i][k] = fail;
+            }
+          }
+        }
+      }
+
+      field.setSamples(dv,false);
+      if (isFI) {
+        fi.setSample(m, field);
+      }
+    }
+
+    if (isFI) {
+      return fi;
+    } else {
+      return (FieldImpl)field;
+    }
+  }
+
+
+  /**
+  * Get a list of points where a comparison is true.
+  *
+  * @param f  VisAD data object (FlatField) as source
+  * @param op  Comparison operator as string ('gt','le',...)
+  * @param v  Numeric operand for comparison
+  *
+  * @return an int[] containing the sampling indecies where
+  * the criterion was met.
+  *
+  * Example:  b = find(a, 'gt', 100)
+  * if 'a' is an image, 'b' will be a list of indecies in
+  * 'a' where the values are > 100.
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static int[] find(FieldImpl f, String op, double v) 
+             throws VisADException, RemoteException {
+    return find(f, op, new Real(v));
+  }
+
+
+  /**
+  * Get a list of points where a comparison is true.
+  *
+  * @param f  VisAD data object (usually FlatField) as source
+  * @param op  Comparison operator as string ('gt','le',...)
+  * @param v  VisAd operand for comparison.
+  *
+  * @return an int[] containing the sampling indecies where
+  * the criterion was met.
+  *
+  * If the value of 'v' is a Field, then it will be resampled
+  * to the domain of 'f' is possible before the comparison.
+  *
+  * Example:  b = find(a, 'gt', c)
+  * if 'a' is an image, 'b' will be a list of indecies in
+  * 'a' where the values are greater than the corresponding
+  * values of 'c'.
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static int[] find(Data f, String op, Data v)
+             throws VisADException, RemoteException {
+    FlatField fv;
+    if (f instanceof FlatField) {
+      fv = (FlatField) f.subtract(v);
+    } else {
+      fv = (FlatField) (((FieldImpl)f).getSample(0)).subtract(v);
+    }
+    float [][] dv = fv.getFloats(false);
+    List<Integer> zz = new ArrayList<Integer>(fv.getLength());
+    int oper = -1;
+    for (int i=0; i<ops.length; i++) {
+      if (ops[i].equalsIgnoreCase(op)) oper = i;
+      if (ops_sym[i].equalsIgnoreCase(op)) oper = i;
+    }
+    if (oper < 0) throw new VisADException("Invalid operator: "+op);
+
+    for (int i=0; i<1; i++) {
+      for (int k=0; k<dv[i].length; k++) {
+
+        if (oper == 0) {
+          if (dv[i][k] > 0.0f) zz.add(Integer.valueOf(k));
+        } else if (oper == 1 || oper == 7) {
+          if (dv[i][k] >= 0.0f) zz.add(Integer.valueOf(k));
+        } else if (oper == 2) {
+          if (dv[i][k] < 0.0f) zz.add(Integer.valueOf(k));
+        } else if (oper == 3 || oper == 8) {
+          if (dv[i][k] <= 0.0f) zz.add(Integer.valueOf(k));
+        } else if (oper == 4) {
+          if (dv[i][k] == 0.0f) zz.add(Integer.valueOf(k));
+        } else {  // only 5,6 are left
+          if (dv[i][k] != 0.0f) zz.add(Integer.valueOf(k));
+        }
+      }
+    }
+
+    int m = zz.size();
+    int[] rv = new int[m];
+    for (int i=0; i<m; i++) {
+      rv[i] = zz.get(i).intValue();
+    }
+    return rv;
+  }
+
+  /**
+  * Get a list of points where values fall within the given range
+  *
+  * @param f  VisAD data object (usually FlatField) as source
+  * @param vmin The minimum value for the range
+  * @param vmax  The maximum value for the range
+  *
+  * @return an int[] containing the sampling indecies where
+  * the values fall within the range ( vmin < value < vmax )
+  *
+  * Example:  b = findWithinRange(a, 100, 200)
+  * if 'a' is an image, 'b' will be a list of indecies in
+  * 'a' where the values are greater than 'vmin' and less than 'vmax'
+  *
+  * @throws VisADException 
+  * @throws RemoteException 
+  *
+  */
+  public static int[] findWithinRange(FieldImpl f, double vmin, double vmax)
+             throws VisADException, RemoteException {
+    return findByRange(f, vmin, vmax, true);
+  }
+
+
+  /**
+  * Get a list of points where values fall outside the given range
+  *
+  * @param f  VisAD data object (usually FlatField) as source
+  * @param vmin The minimum value for the range
+  * @param vmax  The maximum value for the range
+  *
+  * @return an int[] containing the sampling indecies where
+  * the values fall within the range (value < vmin or value > vmax )
+  *
+  * Example:  b = findOutsideRange(a, 100, 200)
+  * if 'a' is an image, 'b' will be a list of indecies in
+  * 'a' where the values are less than 'vmin' or greater than 'vmax'
+  *
+  * @throws VisADException 
+  * @throws RemoteException 
+  *
+  */
+  public static int[] findOutsideRange(FieldImpl f, double vmin, double vmax)
+             throws VisADException, RemoteException {
+    return findByRange(f, vmin, vmax, false);
+  }
+
+  /**
+  * Get a list of point that are inside or outside the given range
+  *
+  * @param f the VisAD Object
+  * @param vmin The minimum value for the range
+  * @param vmax  The maximum value for the range
+  * @param doWith  Set to true to do within the range; false otherwise
+  *
+  * @return an int[] containing the sampling indecies where
+  * the values fall within the range (that is, vmin > value > vmax )
+  * the values fall outside the range (value < vmin or value > vmax )
+  *
+  * @throws VisADException 
+  * @throws RemoteException 
+  *
+  */
+  private static int[] findByRange(FieldImpl f, double vmin, double vmax, boolean doWithin) 
+             throws VisADException, RemoteException {
+    float[][] dv;
+    if (f instanceof FlatField) {
+      dv = ((FlatField)f).getFloats(false);
+    } else {
+      dv = ((FlatField)(f.getSample(0))).getFloats(false);
+    }
+    List<Integer> zz = new ArrayList<Integer>(dv[0].length);
+
+    for (int i=0; i<1; i++) {
+      for (int k=0; k<dv[i].length; k++) {
+        if (doWithin) {
+          if (dv[i][k] > vmin && dv[i][k] < vmax) zz.add(Integer.valueOf(k));
+        } else {
+          if (dv[i][k] < vmin || dv[i][k] > vmax) zz.add(Integer.valueOf(k));
+        }
+      }
+    }
+
+    int m = zz.size();
+    int[] rv = new int[m];
+    for (int i=0; i<m; i++) {
+      rv[i] = zz.get(i).intValue();
+    }
+    return rv;
+  }
+
+  /** resample the data field into the defined domain set
+  *
+  * @param data is the input Field
+  * @param s is the Set which must have a domain MathType identical
+  *   to data's original
+  *
+  * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static Field resample(Field data, Set s) 
+             throws VisADException, RemoteException {
+    return data.resample(s,Data.NEAREST_NEIGHBOR,Data.NO_ERRORS);
+  }
+
+  /** resample the data field into the defined domain set
+  *
+  * @param data is the input Field
+  * @param s is the Set which must have a domain MathType identical
+  *   to data's original
+  * @param mode is the sampling mode (e.g. Data.NEAREST_NEIGHBOR)
+  *
+  * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static Field resample(Field data, Set s, int mode) 
+             throws VisADException, RemoteException {
+    return data.resample(s,mode,Data.NO_ERRORS);
+  }
+
+  /** returns the double value of a Real value.
+  *
+  * @param r is the Real
+  *
+  * @return double value of the Real
+  *
+  */
+  public static double getValue(Real r) {
+    return r.getValue();
+  }
+
+  /** returns the double values of the range
+  *
+  * @param data is the Field from which to get the numeric values
+  *
+  * @return values for all range components in the Field
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static double[][] getValues(Field data) 
+             throws VisADException, RemoteException {
+    return data.getValues();
+  }
+
+  /** 
+   * Sets the sample values into the Field 
+   *
+   * @param f     is the Field to put the samples into
+   * @param vals  are the values for all range components in the Field
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static void setValues(Field f, double[][] vals) 
+             throws VisADException, RemoteException {
+    f.setSamples(vals);
+    return; 
+  }
+
+  /** 
+   * combines fields
+   *
+   * @param fields array of fields
+   *
+   * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static Field combine(Field[] fields)
+             throws VisADException, RemoteException {
+    return (FieldImpl.combine(fields) );
+
+  }
+
+  /** 
+   * Extracts a component of the Field
+   *
+   * @param data the field with multiple range componenents
+   * @param t the MathType of the field to extract
+   *
+   * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static Field extract(Field data, MathType t) 
+             throws VisADException, RemoteException {
+    return ((FieldImpl)data).extract(t);
+  }
+
+  /** 
+   * Extracts a component of the Field
+   *
+   * @param data the field with multiple range componenents
+   * @param s the name of the components to extract
+   *
+   * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static Field extract(Field data, String s) 
+             throws VisADException, RemoteException {
+    return ((FieldImpl)data).extract(s);
+  }
+
+  /** 
+   * Extracts a component of the Field
+   *
+   * @param data the field with multiple range componenents
+   * @param comp the index of the component to extract
+   *
+   * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static Field extract(Field data, int comp) 
+             throws VisADException, RemoteException {
+    return ((FieldImpl)data).extract(comp);
+  }
+
+  /** 
+   * Factors out the given MathType from the domain of the data object. 
+   * <p>For example, if the data has a
+   * {@code MathType: (Line, Element)->(value)}
+   * then factoring out {@code Element} creates a new data
+   * object with a {@code MathType: Element->(Line->(value))}</p>
+   *
+   * @param data   is the Field Data object
+   * @param factor is the domain component Type to factor out
+   *
+   * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static Field domainFactor(Field data, RealType factor) 
+             throws VisADException, RemoteException {
+    return ((FieldImpl)data).domainFactor(factor);
+  }
+
+
+  /** 
+   * Factors out the given domain component (by index) and creates a new 
+   * data object.
+   * 
+   * @param data is the Field Data object
+   * @param comp is the domain component index
+   * 
+   * @return the new Field
+   * @throws VisADException 
+   * @throws RemoteException 
+   * 
+   * @see #domainFactor(Field, RealType)
+   */
+  public static Field domainFactor(Field data, int comp) 
+             throws VisADException, RemoteException {
+
+    RealType mt = (RealType) (
+            (RealTupleType) (
+            (FunctionType)data.getType()).getDomain()).getComponent(comp);
+
+    return ((FieldImpl)data).domainFactor(mt);
+  }
+
+  /** 
+   * Creates a VisAD Data by evaluating the Field at the point given in the 
+   * domain.
+   * 
+   * @param data is the field
+   * @param domain is the Real domain where the field should be evaluated
+   * @return the Data object for the evaluated point
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static Data evaluate(Field data, Real domain) 
+             throws VisADException, RemoteException {
+    return data.evaluate(domain);
+  }
+
+  /**
+   * 
+   * 
+   * @param data is the field
+   * @param domain is the Real domain where the field should be evaluated
+   * @return the Data object for the evaluated point
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static Data evaluate(Field data, double domain) 
+             throws VisADException, RemoteException {
+    return data.evaluate(new Real(domain));
+  }
+
+  /**
+   * Creates a VisAD MathType from the given string
+   *
+   * @param s is the string describing the names in
+   * the form: {@code (x,y)->(a)}  for a Field.
+   *
+   * <p>Forms allowed:<ul>
+   * <li>{@literal "Foo"} will make and return a RealType</li>
+   * <li>{@literal "(Foo)"} makes a RealType and returns a RealTupleType</li>
+   * <li>{@literal "Foo,Bar"} will make two RealTypes and return a RealTupleType</li>
+   * <li>{@literal "(Foo,Bar)"} does the same thing</li>
+   * <li>{@code "(Foo,Bar)->val"} makes 3 RealTypes and returns a FunctionType
+   * (use getDomainType(type) and getRangeType(type) to get the parts</li>
+   * </ul></p>
+   *
+   * @return the MathType
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static MathType makeType(String s) 
+             throws VisADException, RemoteException {
+    String ss = s.trim();
+
+    if ((ss.indexOf(',') != -1 || ss.indexOf('>') != -1 ) && 
+                 (!ss.startsWith("(") || !ss.endsWith(")") ) ) {
+      ss = "(" + s.trim() + ")";
+    }
+    return MathType.stringToType(ss);
+  }
+
+  /** make a MathType with a Coordinate System. This is just
+  * a short-hand for visad.RealTupleType(RealType[], CS, Set)
+  *
+  * @param s is an array of names for (or of) the RealType
+  * @param c is a CoordinateSystem
+  *
+  * @return RealTupleType of the input RealTypes and CoordinateSystem
+   * @throws VisADException 
+   * @throws RemoteException 
+  */
+  public static RealTupleType makeType(String[] s, CoordinateSystem c) 
+             throws VisADException, RemoteException {
+    RealType[] rt = new RealType[s.length];
+    for (int i=0; i< s.length; i++) {
+      rt[i] = visad.RealType.getRealType(s[i]);
+    }
+    return new visad.RealTupleType(rt, c, null);
+  }
+
+  /** 
+   * Make or get the RealType corresponding to the name; if none exists, 
+   * make one and return it.
+   * 
+   * @param name is the name of the RealType type.
+   * @return the RealType for the given name
+   */
+  public static RealType makeRealType(String name) {
+    return (visad.RealType.getRealType(name));
+  }
+
+  /**
+   * 
+   * Make or get the RealType corresponding to the name; if none exists, 
+   * make one and return it.
+   * 
+   * @param name is the name of the RealType type.
+   * @return the RealType for the given name
+   * 
+   */
+  public static RealType getRealType(String name) {
+    return (visad.RealType.getRealType(name));
+  }
+
+  /** 
+   * Make or get the RealType corresponding to the name; if none exists, make
+   * one and return it.
+   * 
+   * @param name is the name of the RealType type.
+   * @param unit is the new Unit to associate with this (must
+   * be compatible with any existing Unit)
+   * @return the RealType
+   */
+  public static RealType makeRealType(String name, Unit unit) {
+    return (visad.RealType.getRealType(name, unit));
+  }
+
+  /**
+   * 
+   * Make or get the RealType corresponding to the name; if none exists, make
+   * one and return it.
+   * 
+   * @param name is the name of the RealType type.
+   * @param unit is the new Unit to associate with this (must
+   * be compatible with any existing Unit)
+   * @return the RealType
+   * 
+   */
+  public static RealType getRealType(String name, Unit unit) {
+    return (visad.RealType.getRealType(name, unit));
+  }
+
+  /** 
+   * Get the MathType of the named VisAD data object
+   * 
+   * @param data is the VisAD Data object
+   * @return the MathType
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static MathType getType(Data data) 
+             throws VisADException, RemoteException {
+    return (data.getType());
+  }
+
+  /** 
+   * Turn on/off the axes labels & scales on a Display
+   * 
+   * @param d the DisplayImpl to address
+   * @param on whether to turn the axes labels on (true)
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static void showAxesScales(DisplayImpl d, boolean on)
+             throws VisADException, RemoteException {
+    d.getGraphicsModeControl().setScaleEnable(on);
+  }
+
+  /** 
+   * Set the Label to be used for the axes
+   *
+   * @param sm the array of ScalarMaps
+   * @param labels the array of strings to use for labels
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static void setAxesScalesLabel(ScalarMap [] sm, String[] labels)
+             throws VisADException, RemoteException {
+      if (sm.length != labels.length) {
+        throw new VisADException("number of ScalarMaps must match number of labels");
+      }
+      for (int i=0; i<sm.length; i++) {
+        AxisScale scale = sm[i].getAxisScale();
+        if (scale != null) {
+//          scale.setLabel(labels[i]);
+          scale.setTitle(labels[i]);
+        }
+      }
+   }
+
+  /** 
+   * Set the font to be used for the axes labels and scales
+   * 
+   * @param sm the array of ScalarMaps
+   * @param f the java.awt.Font to use
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static void setAxesScalesFont(ScalarMap [] sm, Font f) 
+             throws VisADException, RemoteException {
+      for (int i=0; i<sm.length; i++) {
+        AxisScale scale = sm[i].getAxisScale();
+        if (scale != null) {
+          scale.setFont(f);
+        }
+      }
+   }
+
+   /**
+   * Construct a UnionSet of the given MathType for the pairs of 
+   * points given
+   * 
+   * @param mt the MathType for the UnionSet
+   * @param points the point paris to use
+   * @return the UnionSet
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static UnionSet makePairedLines(MathType mt, double[][] points) 
+             throws VisADException, RemoteException {
+     int dim = points.length;
+     int len = points[0].length;
+     UnionSet us = null;
+     if (dim == 2) {
+       float[][] samples = new float[2][2];
+       Gridded2DSet[] gs2 = new Gridded2DSet[len/2];
+       for (int k=0; k < len; k=k+2) {
+         samples[0][0] = (float) points[0][k];
+         samples[0][1] = (float) points[0][k+1];
+         samples[1][0] = (float) points[1][k];
+         samples[1][1] = (float) points[1][k+1];
+         gs2[k/2] = new Gridded2DSet(mt, samples, 2);
+       }
+       us = new UnionSet(gs2);
+
+     } else if (dim == 3) {
+       float[][] samples = new float[3][2];
+       Gridded3DSet[] gs3 = new Gridded3DSet[len/2];
+       for (int k=0; k < len; k=k+2) {
+         samples[0][0] = (float) points[0][k];
+         samples[0][1] = (float) points[0][k+1];
+         samples[1][0] = (float) points[1][k];
+         samples[1][1] = (float) points[1][k+1];
+         samples[2][0] = (float) points[2][k];
+         samples[2][1] = (float) points[2][k+1];
+         gs3[k/2] = new Gridded3DSet(mt, samples, 3);
+       }
+       us = new UnionSet(gs3);
+     }
+     return us;
+   }
+
+  /** 
+   * Helper method for the dump(Data|Math)Type() methods. This will list both 
+   * the MathType and DataType information to stdout.
+   * 
+   * @param d is the Data object
+   * @throws VisADException 
+   * @throws RemoteException 
+   */
+  public static void dumpTypes(Data d) 
+             throws VisADException, RemoteException {
+      MathType t = d.getType();
+      visad.jmet.DumpType.dumpMathType(t);
+      System.out.println("- - - - - - - - - - - - - - - - - - - - - - - ");
+      System.out.println("DataType analysis...");
+      visad.jmet.DumpType.dumpDataType(d);
+  }
+
+  /** helper method for the dump(Data|Math)Type() methods
+  *   this will list both the MathType and DataType information
+  *   to a ByteArrayOutputStream which is returned.
+  *
+  * @param d is the Data object
+   * @return the printable ByteArrayOutputStream
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static ByteArrayOutputStream whatTypes(Data d) 
+             throws VisADException, RemoteException {
+      MathType t = d.getType();
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      visad.jmet.DumpType.dumpMathType(t,bos);
+      visad.jmet.DumpType.dumpDataType(d,bos);
+      return bos;
+  }
+
+  /** helper method for dumpMathType() only
+  * This just dumps out the MathType of the Data object.
+  * @param d is the Data object
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  */
+  public static void dumpType(Data d) 
+             throws VisADException, RemoteException {
+      MathType t = d.getType();
+      visad.jmet.DumpType.dumpMathType(t);
+  }
+
+  /** helper method for dumpMathType() only
+  * This just dumps out the MathType of the Data object into
+  * a ByteArrayOutputStream which is returned.
+  *
+  * @param d is the Data object
+   * @return the printable ByteArrayOutputStream
+   * @throws VisADException 
+   * @throws RemoteException 
+  *
+  *
+  */
+  public static ByteArrayOutputStream whatType(Data d) 
+             throws VisADException, RemoteException {
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      MathType t = d.getType();
+      visad.jmet.DumpType.dumpMathType(t,bos);
+      return bos;
+  }
+
+  /**
+   * Get a PointDataAdapter for the given string (file, url, etc)
+   * 
+   * @param request
+   * @return the new PointDataAdapter
+   * @throws VisADException
+   * @throws RemoteException
+   */
+  public static 
+       visad.data.mcidas.PointDataAdapter getPointDataAdapter(String request) 
+             throws VisADException, RemoteException {
+       return (new visad.data.mcidas.PointDataAdapter(request));
+  }
+
+  /** 
+   * Helper method to read netcdf files with possible factor
+   * 
+   * @param filename 
+   * 
+   * @return the Data object
+   * @throws VisADException 
+   * @throws RemoteException 
+   * @throws IOException 
+   */
+  public static Data getNetcdfData(String filename) 
+             throws VisADException, RemoteException, IOException {
+    return getNetcdfData(filename, null);
+  }
+
+  /**
+   * Try to create a Data object from a NetCDF file
+   * 
+   * @param filename
+   * @param factor
+   * @return the Data object
+   * @throws VisADException
+   * @throws RemoteException
+   * @throws IOException
+   */
+  public static Data getNetcdfData(String filename, String factor) 
+             throws VisADException, RemoteException, IOException {
+    NetcdfFile nf = new NetcdfFile(filename, true);
+    DefaultView dv = new DefaultView(nf,QuantityDBManager.instance(),true);
+    if (factor != null) {
+      TreeSet<String> ts = new TreeSet<String>();
+      ts.add(factor);
+      dv.setOuterDimensionNameSet(ts);
+    }
+    NetcdfAdapter na=new NetcdfAdapter(dv);
+    return na.getData();
+  }
+
+  /**
+   * Helper method for {@link visad.ScalarMap#getScale(double[], double[], double[])}.
+   * 
+   * @param smap the ScalarMap to get the values from
+   * 
+   * @return get the getScale() results:
+   *  so = scale and offset
+   *  data = the data range
+   *  display = the display range
+   */
+  public static double[][] getScale(ScalarMap smap) {
+    double[] so      = new double[2];
+    double[] data    = new double[2];
+    double[] display = new double[2];
+    smap.getScale(so, data, display);
+    return new double[][] {so, data, display};
+  }
+}
+
diff --git a/visad/python/README.python b/visad/python/README.python
new file mode 100644
index 0000000..12bd314
--- /dev/null
+++ b/visad/python/README.python
@@ -0,0 +1,648 @@
+
+                       VisAD Python Scripts
+
+
+1. Overview
+
+The visad.python package defines a Python-based language for using
+VisAD.  The classes in this package are:
+
+  JPythonEditor - an editor for VisAD Python scripts that can be
+                  incorporated in application programs
+  JPythonFrame - a program and GUI for editing and running
+                 VisAD Python scripts
+  JPythonMethods - a set of functions for accessing VisAD that can
+                   be called from VisAD Python scripts
+
+Python scripts are usually stored in files with the .py extension.  The
+package includes a few example script files, such as mcidas_test.py.
+
+VisAD is a Java API and its full power is only accessible by writing
+Java programs.  However, Python scripts can be much simpler so VisAD
+includes this package.  For example, the following script computes and
+displays a 2-D histogram of 2 bands of a McIDAS area file:
+
+  # load a McIDAS area file
+  area = load("../examples/AREA0008")
+
+  # compute a 2-D histogram of the first two bands of the area
+  histogram = hist(area, [0, 1])
+
+  # plot the histogram
+  clearplot()
+  plot(histogram)
+
+Python is an object-oriented language so it is also possible to write
+quite complex VisAD Python programs.  However, the main purpose of this
+package is to support simple scripts.
+
+
+2. Installation
+
+To run VisAD Python scripts, you nee to install:
+
+  VisAD from http://www.ssec.wisc.edu/~billh/visad.html
+
+  Jython from http://jython.sourceforge.net/
+
+  Java 2 and Java3D (see the Prerequisites section of the VisAD web page)
+
+  Jama from http://math.nist.gov/javanumerics/jama/
+    if you want to use the VisAD Python matrix functions
+
+
+3. Editing and Running VisAD Python Scripts
+
+To edit and run VisAD Python scripts, run the command:
+
+  java visad.python.JPythonFrame
+
+If you think you'll be using a lot of memory, run:
+
+  java -mx256m visad.python.JPythonFrame
+
+or something similar (the -mx256m means use 256 MB of memory).
+
+Note that VisAD's JPythonEditor puts an implicit line:
+
+  from visad.python.JPythonMethods import *
+
+at the start of every VisAD Python script.  This is invisible if
+you are using the JPythonEditor (e.g., by running JPythonFrame)
+but allows you to run your scripts from any JPython interpreter.
+
+
+4. The VisAD Python Language
+
+VisAD uses the great Jython interpreter, written in Java, as the basis
+for its Python language.  See:
+
+  http://jython.sourceforge.net/
+  
+for more information about it.  And see:
+
+  http://www.python.org/doc/current/tut/tut.html
+
+for a tutorial on the Python language.
+
+
+4.1 Using VisAD data in Python scripts
+
+Python programs can manipulate all kinds of data.  The key for
+VisAD Python scripts is manipulating VisAD data objects.  These
+are created when you load a file, and when you call VisAD Python
+functions.
+
+You can write ordinary arithmetic expressions involving VisAD
+data object.  For example, you can load two McIDAS area files:
+
+  area1 = load("AREA0001")
+  area2 = load("AREA0002")
+
+then compute their difference by:
+
+  difference = area1 - area2
+
+This is like array operations in IDL or Matlab, except that the
+images will be georeferenced before their difference is computed.
+That is, it is a pointwise difference, but first the image in
+area2 is resampled to the pixel locations in area1.
+
+You can access the pixels in area1 like an array.  For example
+you can compute the total of pixel values in area1 by:
+
+  sum = 0
+  for i = range(area.length):
+	sum = sum + area[i]
+
+Note that range(area.length) is standard Python and means
+that i takes the values 0, 1, 2, ..., area.length - 1.
+
+
+4.2 VisAD data objects
+
+Not all VisAD data objects are images.  In fact, one of the key
+ideas in VisAD is that its data objects can be virtually any
+numerical data.  Every VisAD data object has a "MathType" that
+describes its organization.  You can find out its MathType by:
+
+  print data.getType()
+
+A second key idea in VisAD is that metadata are integrated into
+data operations.  As for example the McIDAS areas were implicitly
+georeferenced when we computed their difference.  VisAD metadata
+include MathTypes, units, coordinate systems, sampling geometry
+and topology, missing data indicators and error estimates.  Unit
+conversions, coordinate transforms, resampling, and propagation
+of missing data and error estimates are implicit in data
+operations.
+
+Here are a few example of VisAD data objects and their MathTypes.
+A pixel radiance might have MathType:
+
+  band1
+
+In VisAD the radiance object is called a Real.  It includes a
+real value, the name "band1", possibly a unit such as w/m^2, and
+possibly an estimate of the variance of error of the real value.
+
+An earth location may have MathType:
+
+  (latitude, longitude)
+
+In VisAD this is called a Tuple.  Tuple components can be any
+VisAD data objects.  If they are all reals, then the Tuple is a
+RealTuple.  Note that latitude and longitude may have degrees or
+radians as units.
+
+The coordinates of a pixel in an image may be a RealTuple with
+MathType:
+
+  (line, element)
+
+If the image has earth navigation, then this RealTuple may have
+a CoordinateSystem that defines an invertable mapping:
+
+  (line, element) <--> (latitude, longitude)
+
+An image defines a function from image coordinates to radiances,
+sampled at a finite number of pixels.  In VisAD this is called a
+Field, and may have a MathType:
+
+  ((line, element) -> band1)
+
+You get a Field with this sort of MathType when you load a McIDAS
+area file by a statement like:
+
+  area1 = load("AREA0001")
+
+Note that Fields can be mappings from any RealTuple to any MathType
+at all.  For example, a time sequence of images may have MathType:
+
+  (time -> ((line, element) -> band1))
+
+Here time may have units of "seconds since 0Z, 1 Jan 1970", and
+the field defines images at a finite number of time samples.
+
+VisAD includes one other type of data, Sets.  These are finite
+sets of points in some real vector space defined by a RealTuple.
+For example, map outlines are a Set with MathType:
+
+  Set(latitude, longitude)
+
+and in fact you can such a data object when you load a McIDAS map
+outline file by a statement like:
+
+  map = load("OUTLUSAM")
+
+Sets are also included as metadata in Fields, to define the
+locations of samples.
+
+The power of VisAD data objects comes because you can combine Real,
+Text, Tuple, Field and Set objects in any complex way you like.
+You can learn more about VisAD data objects in the Tutorials and
+Developer's Guide available on the VisAD web page.
+
+
+4.3 More about accessing VisAD data from Python scripts
+
+Given a Tuple data object, you can find out how many components it
+has by:
+
+  number_of_components = tuple_data.length
+
+and you can access the components by:
+
+  component_0 = tuple_data[0]
+  component_1 = tuple_data[1]
+  . . .
+  component_last = tuple_data[number_of_components - 1]
+
+Given a Field data object, you can find out how many samples it
+has by:
+
+  number_of_samples = field_data.length;
+
+and you can get the samples by:
+
+  for i in range(number_of_samples):
+	sample = field_data[i]
+
+You can get the number of components in the domain by:
+
+  n_domain = domainDimension(field_data);
+
+and the name of a component by (#1 in this case):
+
+  domainName_1 = domainType(field_data, 1);
+
+Similarly, for the range components, the number of components is:
+
+  n_range = rangeDimension(field_data);
+
+and the name of a component by (#0 in this case):
+
+  rangeName_0 = rangeType(field_data, domainType0);
+
+You can also get the Set of sample locations of the Field by:
+
+  set = field_data.getDomainSet()
+
+You can get the number of locations in the set, and access the
+locations, by:
+
+  number_of_locations = set.length
+  for i in range(number_of_locations):
+	location = set[i]
+
+You can access Field samples by location as well as by index, so we
+could replace the difference:
+
+  area1 = area1 - area2
+
+by the code:
+
+  set = area1.getDomainSet()
+  for i in range(set.length):
+	area1[i] = area1[i] - area2[set[i]]
+
+The expression "area2[set[i]]" is the radiance of area2 at the
+location of the i-th sample of area1.  Please note that this way
+of computing the difference is much slower than "area1 - area2".
+In general, your scripts will run much faster if you can avoid
+explicitly looping over all the points in a Set or Field, but
+rather do it implicitly.
+
+Note also "area[i] = ..." indicates that you can set values in
+Fields by index.  But you cannot set Tuple components this way.
+
+Just for kicks, you could compute image radiances along a map
+outline by:
+
+  map = load("OUTLUSAM")
+  area1 = load("AREA0001")
+  for i in range(map.length):
+	print area1[map[i]]
+
+
+5. The VisAD Python Library Functions
+
+This is a list of functions specially designed for accessing VisAD
+from Python scripts.  For a more complete description, see
+visad.python.JPythonMethods in the VisAD JavaDoc, available
+on-line at:
+
+  http://www.ssec.wisc.edu/~dglo/visad/
+
+Note that any VisAD class or method is accessible from Python scripts,
+so you are not restricted to just these functions.
+
+
+5.1 File loading function
+
+  load(String location) - reads in data from the given location
+                          (filename or URL)
+
+Note that VisAD currently knows how to read the following kinds of
+files: McIDAS AREA and map OUTLine files (including from ADDE),
+netCDF, HDF-5, HDF-EOS (HDF-5 and HDF-EOS require native code to be
+installed, as described on the VisAD web page), Vis5D, FITS, GIF
+and JPEG.  Some users have written VisAD adapters for other formats
+such as Shape and ARCGRID ASCII files.
+
+The load function will determine the format of the named file and
+read it approproiately.
+
+
+5.2 Plotting functions
+
+  clearplot() - clear the onscreen data display
+  
+  plot(data) - plot data
+  
+  plot(data, red, green, blue) - plot data
+  
+Controls for how data are plotted are brought up in a GUI.  The
+plot function includes implicit georeferencing, for example to
+overlay a McIDAS area with a map outline.  We are currently
+adding improvements to the ViaAD Python plot function, which
+should be available soon.
+
+
+5.3 Pointwise math library functions
+
+  abs(data) - return pointwise absolute value of data
+
+  acos(data) - return pointwise arccos value of data, in radians
+
+  acosDegrees(data) - return pointwise arccos value of data, in degrees
+  
+  asin(data) - return pointwise arcsin value of data, in radians
+  
+  asinDegrees(data) - return pointwise arcsin value of data, in degrees
+  
+  atan(data) - return pointwise arctan value of data, in radians
+  
+  atanDegrees(data) - return pointwise arctan value of data, in degrees
+  
+  atan2(data1, data2) - return pointwise tan value of data1 / data2,
+                        in radians
+  
+  atan2Degrees(data1, data2) - return pointwise tan value of
+                               data1 / data2, in degrees
+  
+  ceil(data) - return pointwise ceil value of data (smallest integer not
+               less than)
+  
+  cos(data) - return pointwise cos value of data
+  
+  cosDegrees(data) - return pointwise cos value of data
+  
+  exp(data) - return pointwise exp value of data
+  
+  floor(data) - return pointwise floor value of data (largest integer
+                not greater than)
+  
+  log(data) - return pointwise log value of data
+  
+  max(data1, data2) - return pointwise maximum value of data1 and data2
+  
+  min(data1, data2) - return pointwise minimum value of data1 and data2
+  
+  round(data) - return pointwise round value of data (closest integer)
+  
+  sin(data) - return pointwise sin value of data, assuming radians
+  
+  sinDegrees(data) - return pointwise sin value of data, assuming degrees
+  
+  sqrt(data) - return pointwise square root value of data
+  
+  tan(data) - return pointwise tan value of data, assuming radians
+  
+  tanDegrees(data) - return pointwise tan value of data, assuming degrees
+
+The arguments to these functions may be any VisAD data object.
+They are augmented by support for python infix expressions for
++ (addition), - (subtraction), * (multiplication), / (division)
+and % (remainder).  Binary operators (e.g., + and max) require
+consistency between the MathTypes of the operands, although Reals
+can be combined with any other data.
+
+Literal constants can be used with binary operators, as long as
+they do not occur first for infix operators.  That is, "data + 1"
+is legal but "1 + data" is not.
+
+The forward trig functions (e.g., sin but not asin) assume their
+default units of either radians (e.g., sin) or degrees (e.g.,
+sinDegrees) only if data values do not have units convertable to
+radians.  If they do then data units are used.
+
+
+5.4 Fourier transform functions
+
+  fft(field) - return forward Fourier transform of field
+  
+  ifft(field) - return backward Fourier transform of field
+
+
+5.5 Field creation functions
+
+  field(float[] values) - return a VisAD Field
+  
+  field(float[][] values) - return a VisAD Field
+  
+  field(set, String name, float[] values) - return a VisAD Field
+  
+  field(set, String name, float[][] values) - return a VisAD Field
+  
+  field(String name, float[] values) - return a VisAD Field
+  
+  field(String name, float[][] values) - return a VisAD Field
+  
+
+5.6 Histogram functions
+
+  hist(field, int[] ranges) - return histogram of field values
+  
+  hist(field, int[] ranges, int[] sizes) - return histogram of field
+                                           values
+  
+  hist(field, set) - return histogram of field values
+  
+
+5.7 Matrix functions
+
+  chol(data) - return matrix Cholesky Decomposition of data
+  
+  cond(data) - return matrix condition of data (ratio of largest to
+               smallest singular value)
+  
+  det(data) - return matrix determinant of data
+  
+  eig(data) - return matrix Eigenvalue Decomposition of data
+  
+  inverse(data) - return matrix inverse of data
+  
+  lu(data) - return matrix LU Decomposition of data
+  
+  matrixMultiply(data1, data2) - return matrix multiply of data1 * data2
+  
+  norm1(data) - return matrix one norm of data (maximum column sum)
+  
+  norm2(data) - return matrix two norm of data (maximum singular value)
+  
+  normF(data) - return matrix Frobenius norm of data (sqrt of sum of
+                squares of all elements)
+  
+  normInf(data) - return matrix infinity norm of data (maximum row sum)
+  
+  qr(data) - return matrix QR Decomposition of data
+  
+  rank(data) - return matrix effective numerical rank (from SVD) of data
+  
+  solve(data1, data2) - return matrix soluton X of data1 * X = data2
+  
+  svd(data) - return matrix Singular Value Decomposition of data
+  
+  trace(data) - return matrix trace of data (sum of the diagonal elements)
+  
+  transpose(data) - return matrix transpose of data
+
+
+5.8 Misc. Field functions
+
+  makeUnit(name) - makes a Unit object from name (like "degC")
+
+  rangeType(data, component) - returns String name of component
+
+  domainType(data, component) - returns String name of component
+
+  domainDimension(data) - returns integer number of domain components
+
+  rangeDimension(data) - returns integer number of rangecomponents
+
+
+6. Example VisAD Python Scripts
+
+A number of VisAD Python scripts are distributed with the VisAD
+source code.  They are the *.py files in the visad/python
+directory.  Here are some of them.
+
+
+# area_test.py
+#
+# load two McIDAS area files
+area7 = load("../examples/AREA0007")
+area8 = load("../examples/AREA0008")
+
+# extract one band from area8
+area8 = area8.extract(0)
+
+# subtract one area from the other, georeferenced
+difference = area8 - area7
+
+# plot area difference
+clearplot()
+plot(difference)
+
+
+
+
+# fft_test.py
+#
+# load a netCDF file containg a NAST-I spectrum
+data = load("../examples/b2rlc.nc")
+# extract the spectrum
+data2 = data[2][0]
+spectrum = data2[data2.length-1]
+
+# print the VisAD MathType of the spectrum
+print spectrum.getType()
+
+# compute the Fourier transform of the spectrum
+ft = fft(spectrum)
+
+# plot the Fourier transform in red = 1, green = 0, blue = 0
+# i.e., red
+clearplot()
+plot(ft, 1, 0, 0)
+
+
+
+
+# hist_test.py
+#
+# load a McIDAS area file
+area = load("../examples/AREA0008")
+
+# compute a 2-D histogram of the first two bands of the area
+histogram = hist(area, [0, 1])
+
+# plot the histogram
+clearplot()
+plot(histogram)
+
+
+
+
+# matrix_test.py
+#
+# construct a 2 x 2 matrix in a VisAD Field
+matrix = field([[1, 2], [1, 3]])
+# construct a 2 vector in a VisAD Field
+vector = field([2, 1])
+
+# solve the linear system
+solution = solve(matrix, vector)
+
+# print the solution
+print solution[0], solution[1]
+
+# prints 4.0 -1.0
+#
+# note
+# 1  2       4       2
+#        *       =
+# 1  3      -1       1
+
+
+
+
+# mcidas_test.py
+#
+# load a McIDAS area file
+area = load("../examples/AREA2001")
+print area.length
+
+# make a scratch across the top of the area
+for i in range(20000, 21000):
+	area[i] = 0
+
+# load a McIDAS map file
+map = load("../examples/OUTLSUPW")
+print map.length
+
+# print area pixel values at some map locations
+for j in range(map.length/200):
+	i = 200 * j
+	print "area = ", area[map[i]], " at ", map[i]
+
+# plot the area overlaid with the map
+clearplot()
+plot(area)
+plot(map)
+
+
+
+
+# resample_test.py
+#
+# load two McIDAS area files
+area7 = load("../examples/AREA0007")
+area8 = load("../examples/AREA0008")
+
+# extract one band from area8
+area8 = area8.extract(0)
+
+# get set of area8 pixel locations
+set = area8.getDomainSet()
+
+# resample area7 to area8 locations
+area9 = area7.resample(set)
+
+# resample area7 to area8 locations one pixel at a time
+# and compute difference with all at once resample
+#   NOTE - this is slow
+for i in range(set.length):
+	area9[i] = area9[i] - area7[set[i]]
+
+clearplot()
+plot(area9)
+
+
+
+
+"""\
+vis_test.py
+
+An example of a JPython script that utilizes
+VisAD functionality.
+
+To execute at the command prompt, type:
+  jpython vis_test.py
+
+To execute within the JPython editor, launch
+the editor with:
+  java visad.python.JPythonFrame
+Then open this file and choose "Command", "Run"
+"""
+
+# load a GIF image file
+data = load("../ss/cut.gif")
+
+# plot the GIF image
+clearplot()
+plot(data)
+
+
+
diff --git a/visad/python/RunJPython.java b/visad/python/RunJPython.java
new file mode 100644
index 0000000..bc6ef03
--- /dev/null
+++ b/visad/python/RunJPython.java
@@ -0,0 +1,196 @@
+//
+// RunJPython.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.python;
+
+import java.lang.reflect.*;
+import visad.VisADException;
+import visad.formula.FormulaUtil;
+
+/** A stand-alone wrapper for launching JPython code. */
+public class RunJPython {
+
+  /** debugging flag */
+  private static final boolean DEBUG = false;
+
+  /** name of JPython interpreter class */
+  private static final String interp = "org.python.util.PythonInterpreter";
+
+  /** JPython interpreter class */
+  private static final Class interpClass = constructInterpClass();
+
+  /** obtains the JPython interpreter class */
+  private static Class constructInterpClass() {
+    Class c = null;
+    try {
+      c = Class.forName(interp);
+    }
+    catch (ClassNotFoundException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    return c;
+  }
+
+  /** names of useful methods from JPython interpreter class */
+  private static final String[] methodNames = {
+    interp + ".eval(java.lang.String)",
+    interp + ".exec(java.lang.String)",
+    interp + ".execfile(java.lang.String)",
+    interp + ".set(java.lang.String, org.python.core.PyObject)",
+    interp + ".get(java.lang.String)"
+  };
+
+  /** useful methods from JPython interpreter class */
+  private static final Method[] methods =
+    FormulaUtil.stringsToMethods(methodNames);
+
+  /** method for evaluating a line of JPython code */
+  private static final Method eval = methods[0];
+
+  /** method for executing a line of JPython code */
+  private static final Method exec = methods[1];
+
+  /** method for executing a document containing JPython code */
+  private static final Method execfile = methods[2];
+
+  /** method for setting a JPython variable's value */
+  private static final Method set = methods[3];
+
+  /** method for getting a JPython variable's value */
+  private static final Method get = methods[4];
+
+  /** PythonInterpreter object */
+  protected Object python = null;
+
+
+  /** constructs a RunJPython object */
+  public RunJPython() throws VisADException {
+    try {
+      python = interpClass.newInstance();
+    }
+    catch (NullPointerException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    catch (IllegalAccessException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    catch (InstantiationException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    if (python == null) {
+      throw new VisADException("JPython library not found - " +
+        "install Jython from http://jython.sourceforge.net/");
+    }
+  }
+
+  /** evaluates a line of JPython code */
+  public Object eval(String line) throws VisADException {
+    try {
+      return eval.invoke(python, new Object[] {line});
+    }
+    catch (IllegalAccessException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (IllegalArgumentException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (InvocationTargetException exc) {
+      throw new VisADException(exc.getTargetException().toString());
+    }
+  }
+
+  /** executes a line of JPython code */
+  public void exec(String line) throws VisADException {
+    try {
+      exec.invoke(python, new Object[] {line});
+    }
+    catch (IllegalAccessException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (IllegalArgumentException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (InvocationTargetException exc) {
+      throw new VisADException(exc.getTargetException().toString());
+    }
+  }
+
+  /** executes the document as JPython source code */
+  public void execfile(String filename) throws VisADException {
+    try {
+      execfile.invoke(python, new Object[] {filename});
+    }
+    catch (IllegalAccessException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (IllegalArgumentException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (InvocationTargetException exc) {
+      throw new VisADException(exc.getTargetException().toString());
+    }
+  }
+
+  /** sets a JPython variable's value */
+  public void set(String name, Object value) throws VisADException {
+    try {
+      set.invoke(python, new Object[] {name, value});
+    }
+    catch (IllegalAccessException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (IllegalArgumentException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (InvocationTargetException exc) {
+      throw new VisADException(exc.getTargetException().toString());
+    }
+  }
+
+  /** gets a JPython variable's value */
+  public Object get(String name) throws VisADException {
+    try {
+      return get.invoke(python, new Object[] {name});
+    }
+    catch (IllegalAccessException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (IllegalArgumentException exc) {
+      throw new VisADException(exc.toString());
+    }
+    catch (InvocationTargetException exc) {
+      throw new VisADException(exc.getTargetException().toString());
+    }
+  }
+
+  /** launches a JPython script */
+  public static void main(String[] args) throws VisADException {
+    if (args.length < 1) return;
+    try { new RunJPython().execfile(args[0]); }
+    catch (VisADException exc) { exc.printStackTrace(System.out); }
+  }
+
+}
diff --git a/visad/python/area_test.py b/visad/python/area_test.py
new file mode 100644
index 0000000..b4c9f16
--- /dev/null
+++ b/visad/python/area_test.py
@@ -0,0 +1,15 @@
+from visad.python.JPythonMethods import *
+# load two McIDAS area files
+area7 = load("../examples/AREA0007")
+area8 = load("../examples/AREA0008")
+
+# extract one band from area8
+area8 = area8.extract(0)
+
+# subtract one area from the other, georeferenced
+difference = area8 - area7
+
+# plot area difference
+clearplot()
+plot(difference)
+
diff --git a/visad/python/cheatsheet.txt b/visad/python/cheatsheet.txt
new file mode 100644
index 0000000..3595866
--- /dev/null
+++ b/visad/python/cheatsheet.txt
@@ -0,0 +1,442 @@
+       Quick Reference to VisAD Python Interface Functions
+                         November 15, 2001
+                     (update: February 26th, 2003) 
+
+from JPythonMethods...
+
+data = load(string)
+  Load a data file into 'a' from the named source 'string'
+  a = load("myfile")
+  a = load("adde://image?")
+  a = load("http://host/mynetcdffile.nc")
+
+data = getNetcdfData(string_name, string_factor)
+  Invokes the Netcdf File Adapter with an optional
+  parameter (domain variable) to factor out.  That is,
+  the MathType of ( (a,b,c) -> v)  can be pre-factored
+  to (c-> ((a,b)->v)).  This can be useful for large
+  files.
+  
+plot(data)
+  Plot the data into a cell of the VisAD SpreadSheet - mappings
+  will be guessed.
+plot(data,maps)
+  Plot the data into a cell of the VisAD SpreadSheet - specifying
+  the mappings.
+plot(data,editMaps)
+  Plot the data into a cell of the VisAD SpreadSheet - if
+  editMaps = 1, then the Mappings Dialog will pop up.
+  
+plot(name, data)
+plot(name, data, maps)
+plot(name, data, editMaps)
+  Plot as above, but in the named Window.
+
+plot(data, red, green, blue)
+plot(name, data, editMaps, red, green, blue)
+plot(name, data, editMaps, red, green, blue, maps)
+  Plot as above, but use the colors specified
+
+clearplot()
+clearplot(name)
+  Clear out the plot.
+
+saveplot(filename)
+saveplot(name, filename)
+  Save the Plot into a JPEG file.
+saveplot(display, filename)
+  Save a quick graph into a JPEG file.
+
+showAxesScales(display, boolean)
+setAxesScalesFont(maps, font)
+  Things for the axes of graphs.
+
+data = hist(data, (dim, bins))
+data = hist(data, (ranges))
+data = hist(data, (ranges), (sizes))
+  Compute a binned dataset (useful for historgram-type analysis)
+  See quickgraphs.
+
+data = field(values[])
+data = field(range_name, values[])
+data = field(domain_name, range_name, values[])
+data = field(set, range_name, values[])
+data = field(values[][])
+data = field(range_name, values[][])
+data = field(domain_1_name, domain_2_name, range_name, values[][])
+data = field(set, range_name, values[][])
+  Make a data object from arrays of values (1D and 2D).
+
+data = replace(field, int[], Real)
+data = replace(field, int[], double)
+  replace the values in each field(int[]) with the Real or double
+  value.
+
+int = getDomainDimension(data)
+int = getRangeDimension(data)
+  Get the number of components of the data's domain and range
+
+int[] = getDomainSizes(data)
+  Get the dimension(s) of the component(s) of the domain sampling for data
+
+type = getDomainType(data)
+type = getRangeType(data)
+  Get the Types of the domain and range for the data
+
+type = getType(data)
+  Get the Type of the whole data object
+
+type = makeRealType(string)
+type = makeRealType(string, unit)
+type = makeType(string)
+  Create new Type (or obtain an existing one with the string
+  name); e.g., makeType(" (time->( (x,y)->value ) )" )
+  
+type = getDomainType(function_type)
+type = getRangeType(function_type)
+  Get the Types of the domain and range for the given function type
+  (useful when the function has been defined using makeType)
+
+string = domainType(data, int)
+string = rangeType(data, int)
+  Get the name of the Types of the specific components of the
+  domain or range.
+
+Unit = makeUnit(string)
+  Create a Unit.
+
+set = makeDomain(int)
+set = makeDomain(type, int)
+set = makeDomain(double, double, int)
+set = makeDomain(type, double, double, int)
+  make 1D domain set
+
+set = makeDomain(int, int)
+set = makeDomain(type, int, int)
+set = makeDomain(double, double, int,  double, double, int)
+set = makeDomain(type, double, double, int,  double, double, int)
+  make 2D domain set
+
+set = makeDomain(double, double, int,  double, double, int, double, double, int)
+set = makeDomain(type,double, double, int,  double, double, int, double, double, int)
+  make 3D domain set
+
+set = getDomainSet(data)
+  Get the domain sampling set for this data object.
+
+data = combine(field[])
+  combine the array of fields into one data object.  All fields
+  must have the same sampling domain.
+
+data = resample(data, set)
+  Resample the data into a new domain (specified by the sampling
+  set)
+  
+double = getValue(data)
+  Get a single value from a Real object
+
+double[][] = getValues(data)
+  Get an array of the range values of a Field -
+  [component][sample]
+
+data = extract(data, type)
+data = extract(data, string)
+data = extract(data, int)
+  Extract one range component from the data.
+
+data = domainFactor(data, type)
+data = domainFactor(data, int)
+  Factor out one domain component (that is:  ( (t,x,y->v ) can be
+  factored to ( t-> ( (x,y)->v ))
+
+data = evaluate(data, Real)
+data = evaluate(data, double)
+  Evaluate the data at the given point in the domain. 
+
+int[] = find(data, string, double)
+int[] = find(data, string, data)
+  Return pointers to samples where the data meets the criteria.
+  For example:  b = find(image, "gt", 100) returns a list of
+  points (in b) of where image[k] > 100.
+
+data = mask(data, string, double)
+data = mask(data, string, data)
+  Returns a new data (like an image or grid) of 1.0 at each point
+  where the criteria is met; 0.0 at all other points.  For example:
+  d = mask(image, 'gt', 200) 
+
+dumpTypes(data)
+  General-purpose Type and Data information dump
+
+dumpType(data)
+  Only lists information about MathTypes (useful for getting
+  variable names from large objects on disk).
+
+Various math operations:
+data = abs(data)
+data = acos(data)
+data = acosDegrees(data)
+data = asin(data)
+data = asinDegrees(data)
+data = atan(data)
+data = atanDegrees(data)
+data = ceil(data)
+data = cos(data)
+data = cosDegrees(data)
+data = floor(data)
+data = log(data)
+data = rint(data)
+data = round(data)
+data = sin(data)
+data = sinDegrees(data)
+data = sqrt(data)
+data = tan(data)
+data = tanDegrees(data)
+data = max_data(data, data)
+data = min_data(data, data)
+data = atan2(data, data)
+data = atan2Degrees(data, data)
+data = max_data(data, double)
+data = min_data(data, double)
+data = atan2(data, double)
+data = atan2Degrees(data, double)
+data = max_data(double, data)
+data = min_data(double, data)
+data = atan2(double, data)
+data = atan2Degrees(double, data)
+data = fft(data)    [data must be a Field]
+data = ifft(data)
+matrix = maxtrixMultiply(data, data)
+matrix = solve(data, data)
+matrix = inverse(data)
+matrix = transpose(data)
+double = det(data)
+double = norm1(data)
+double = norm2(data)
+double = normInf(data)
+double = normF(data)
+double = rank(data)
+double = cond(data)
+double = trace(data)
+JamaCholeskyDecomposition = chol(data)
+JamaEigenvalueDecomposition = eig(data)
+JamaLUDecomposition = lu(data)
+JamaQRDecomposition = qr(data)
+JamaSingularValueDecomposition = svd(data)
+
+
+from 'subs.py'.............
+(instance methods associated with the display 
+are preceded by "disp." in their signature)
+
+disp = makeDisplay(maps)
+  create (and return) a VisAD DisplayImpl and add the ScalarMaps, if any
+  the VisAD box is resized to about 95% of the window.  Use 3D if
+  availble.
+
+disp = makeDisplay3D(maps)
+  create (and return) a VisAD DisplayImplJ3D and add the ScalarMaps, if any
+  the VisAD box is resized to about 95% of the window
+  
+disp = makeDisplay2D(maps)"
+  create (and return) a VisAD DisplayImplJ2D and add the ScalarMaps, if any
+  the VisAD box is resized to about 95% of the window
+
+disp.saveDisplay(filename)
+saveDisplay(display, filename)
+  save the display as a JPEG
+
+disp.addData(name, data, constantMaps=None, renderer=None, ref=None)
+addData(name, data, display, constantMaps=None, renderer=None, ref=None)
+  add a Data object to a Display, and return a reference to the Data
+
+disp.setPointSize(size)
+setPointSize(display, size)
+  set the size of points for point-type plots
+
+disp.setAspectRatio(ratio)
+setAspectRatio(display, ratio)
+  define the aspects of width and height, as a ratio: width/height
+
+disp.setAspects(x, y, z)
+setAspects(display, x, y, z)
+  define the relative sizes of the axes
+
+disp.maximizeBox()
+maximizeBox(display)
+  a simple method for making the VisAD "box" 95% of the window size
+
+disp.setBoxSize(percent=.70)
+setBoxSize(display, percent=.70)
+  a simple method for making the VisAD "box" some % of the window size
+
+disp.setBackgroundColor(color)
+setBackgroundColor(display,color)
+  sets the display background color to 'c', which may be a string
+  (like "white") or a java.awt.Color object.
+
+disp.setForegroundColor(color)
+setForegroundColor(display,color)
+  sets the display foreground color to 'c', which may be a string
+  (like "white") or a java.awt.Color object.
+
+disp.setCursorColor(color)
+setCursorColor(display,color)
+  sets the display cursor color to 'c', which may be a string
+  (like "white") or a java.awt.Color object.
+
+disp.setBoxColor(color)
+setBoxColor(display,color)
+  sets the display VisAD "box" color to 'c', which may be a string
+  (like "white") or a java.awt.Color object.
+
+disp.setBoxOn(on_off)
+setBoxOn(display,on_off)
+  turns the 'wire frame VisAD box' on or off.
+
+x,y,z,disp = getDisplayMaps(display)
+  return the x,y,z scalar maps for the display
+
+set = makeLine(domainType, points)
+  make a 2D or 3D line, returns the Set so it can
+  be added to the display or changed as needed
+
+ref = disp.drawLine(points[], color=None, mathtype=None)
+ref = drawLine(display, points[], color=None, mathtype=None)
+  draw a line directly into the display; also return reference
+  drawLine(display, domainType, points[], color=Color, mathtype=domainType)
+  drawLine(name|display, points[], color=Color)
+  "Color" is java.awt.Color
+
+disp.moveLine(ref, points[])
+  move the line referred to by 'ref' to the new location.
+
+ref = disp.drawString(string, point, color=None, center=0, font='futural')
+ref = drawString(display, string, point, color=None, center=0, font='futural')
+  draw a string on the display
+
+disp.addMaps(maps[])
+addMaps(display, maps[])
+  add an array of ScalarMaps to a Display
+
+disp.showDisplay(width=300, height=300, title=, bottom=, top=)
+showDisplay(display, width=300, height=300, title=, bottom=, top=)
+  quick display of a Display object in a separate JFrame
+  you can set the size and title, if you want...  Use the bottom=
+  and top= keywords to add these componenets (or panels) to the bottom
+  and top of the VisAD display (which always is put in the Center).
+
+disp.addShape(type, scale=.1, color=None, index=None, autoScale=1)
+addShape(type, scale=.1, color=None, index=None, autoScale=1)
+  Add a shape to the current display (see Shapes class, below). "type" 
+  can be 'cross', 'triangle', 'solid_square', or 'cube'.  It may also
+  be a user-defined VisADGeometryArray.  Return value is the shape
+  index (used for moveShape).
+
+disp.moveShape(index, coord)
+moveShape(index, coord)
+  Move shape # 'index' to the coordinates 'coord', which is a list or
+  tuple of coordinates in the display space, in the order (x,y,z).
+ 
+maps = makeMaps(RealType, name, RealType, name, ....)
+  define ScalarMap(s) given pairs of (Type, name)
+  where "name" is taken from the list, below.
+  Return is a list of maps to use in constructing a display.
+
+maps = makeMaps(type_name, name, type_name, name, ....)
+  define ScalarMap(s) given pairs of (type_name, name)
+  where "type_name" is the (String) name of a valid ScalarType
+  (RealType or TextType), and "name" is taken from the
+  list, below.  Return is a list of maps to use in constructing 
+  a display.
+
+  ("x","y","z","lat","lon","rad","list","red","green",
+  "blue","rgb","rgba","hue","saturation","value","hsv","cyan",
+  "magenta","yellow","cmy","alpha","animation","selectvalue",
+  "selectrange","contour","flow1x","flow1y","flow1z",
+  "flow2x","flow2y","flow2z","xoffset","yoffset","zoffset",
+  "shape","text","shapescale","linewidth","pointsize",
+  "cylradius","cylazimuth","cylzaxis",
+  "flow1elev","flow1azimuth","flow1radial",
+  "flow2elev","flow2azimuth","flow2radial")
+
+
+data = changeRangeName(data, name)
+  changes the name of the (single) Range Component in the <data>
+  object to be <name>.  The Unit of the original is inherited.
+
+
+class Shapes(display, shapemap)
+  A class useful for manipulating many shapes on multiple displays.
+  'shapemap' is the name of the ScalarMap that has a RealType mapped
+  to Display.Shape.  This class has two methods:
+    addShape(type, scale=.1, color=None, index=None, autoScale=1)
+    moveShape(index, coord)
+
+class SelectField(selectMapName, data)
+  SelectField aids in showing a series of data objects
+  using the Display.SelectValue.  'data' should be
+  an array of data with same MathTypes.  This class has 3 methods:
+    showIt(this, index) - shows field number 'index'
+    getScalarMap(this) - returns the ScalarMap to add to display
+       for this SelectField object
+    getSelectField(this) - returns the Field to add to the display.
+
+class HandlePickEvent(display, handler)
+   Helper class for interfacing to the VisAD Display when
+   the user drags the mouse around with both buttons
+   pressed (which causes a cursor to appear and the domain
+   readout values to be shown).  When the mouse buttons
+   are released, the applications 'handler' will be called.
+   'handler' method should have two parameters: x,y that
+   will get the domain coordinate values on the x and y
+   axes.
+
+class RubberBandZoomer(display, requireKey, callback=callname)
+  Class to define a Rubber Band Box zoom capability
+  for a display.  Once invoked, a drag with right mouse
+  button creates a RubberBandBox.  When released, the
+  image is moved and zoomed to fill the window.  The
+  'display' is just that.  The 'requireKey' is 0 for
+  no simultaneous key, =1 for CTRL, =2 for SHIFT. Useful
+  because other manipulation renderers might get events
+  as well.  If the 'callname' is given, then when the
+  box is zoomed, the samples array (x,y values of the
+  upper left and lower right corners) will be passed
+  to it.
+
+  def zoomit(samples)
+    Will force the display associated with this RBZ to
+    be zoomed according to the samples given.  This
+    (along with the callback=) can be used to allow
+    one display to zoom another.
+
+LinkBoxControl(myDisplay, otherDisplay)
+  this class will link the actions (roam, zoom, etc) of one display
+  to another.  
+
+from 'graph.py'.....
+
+(Note that each of these returns a 'display' object.)
+
+mapimage(data, mapfilename):
+
+addeimage():
+
+image(data, panel=None, colortable=None, width=400, height=400, title="VisAD Image"):
+
+scatter(data_1, data_2, panel=None, pointsize=None, width=400, height=400, xlabel=None, ylabel=None, title="VisAD Scatter"):
+
+histogram(data, bins=20, width=400, height=400, title="VisAD Histogram", color=None, panel=None):
+
+lineplot(data, panel=None, color=None, width=400, height=400, title="Line Plot"):
+
+contour(data, panel=None, enableLabels=1, interval=None, width=400, height=400, title="VisAD Contour Plot", interval = [int, low, high, base]):
+
+animation(data, panel=None, width=400, height=500, title="VisAD Animation"):
+
+
+The "panel=" keyword allows you to supply a Java container for
+the graph (like a panel or window).  If you do, then the graphic
+will be placed into your container.  Otherwise, a new frame is
+created and the display produced.
diff --git a/visad/python/dna.py b/visad/python/dna.py
new file mode 100644
index 0000000..10ca513
--- /dev/null
+++ b/visad/python/dna.py
@@ -0,0 +1,116 @@
+from visad.python.JPythonMethods import *
+from visad import *
+from visad.util import Delay
+import string
+from subs import *
+import math
+
+# number of atoms in input file
+# determined beforehand
+# there should be a better way to derive through data read
+n_atoms = 656
+
+# make type for x,y,z positions of atoms of specified id
+# independent variable is id_num
+atom_type = makeType("(index -> (x, y, z, id))");
+atom_range = atom_type.getRange()
+
+# Set range for atoms as total number read in
+atom_dom = makeDomain("index", 1, n_atoms, n_atoms)
+
+# open dna molecule location array with identifiers
+molecule=open("dna_molecule.txt","r")
+
+# string positions of x, y and z atom locations
+# determined by looking at incoming data set
+# there should be a better way to read data with separators
+xpos=5
+ypos=6
+zpos=7
+idpos = 2
+
+# create a temporary value list to put atom coordinates in
+dna_x=[]
+dna_y=[]
+dna_z=[]
+dna_id=[]
+
+print "start"
+# loop from 0 to number of atoms - 1
+for i in range(n_atoms):
+
+   # create a temporary expandable list to put string data in
+   values = []
+   # get one line at a time
+   values=string.split(molecule.readline())
+   # print values
+
+   # convert the x, y and z coodinates for atom "i"
+   # append x, y and z values to a triple
+   temp=float(values[xpos])
+   dna_x.append(temp)
+
+   temp=float(values[ypos])
+   dna_y.append(temp)
+
+   temp=float(values[zpos])
+   dna_z.append(temp)
+
+   # set id to constant for now
+   id_str = values[idpos][0]
+   if id_str == "O":
+     id_type = 0
+   elif id_str == "N":
+     id_type = 1
+   elif id_str == "C":
+     id_type = 2
+   elif id_str == "P":
+     id_type = 3
+   else:
+     id_type = -1
+   # append id to location triple
+   dna_id.append(id_type)
+
+locs = FlatField(atom_type, atom_dom)
+locs.setSamples([dna_x, dna_y, dna_z, dna_id])
+
+maps = makeMaps(atom_range[0], "x", atom_range[1], "y", atom_range[2], "z",
+                atom_range[3], "shape")
+maps[0].setRange(-20, 20)
+maps[1].setRange(-20, 20)
+maps[2].setRange(-20, 20)
+plot (locs, maps)
+
+control = maps[3].getControl()
+normals = [0.0,  0.0,  1.0,   1.0,  0.0,  0.0,   0.0,  1.0,  0.0,
+           0.0,  0.0,  1.0,   0.0,  1.0,  0.0,  -1.0,  0.0,  0.0,
+           0.0,  0.0,  1.0,  -1.0,  0.0,  0.0,   0.0, -1.0,  0.0,
+           0.0,  0.0,  1.0,   0.0, -1.0,  0.0,   1.0,  0.0,  0.0,
+           0.0,  0.0, -1.0,   1.0,  0.0,  0.0,   0.0,  1.0,  0.0,
+           0.0,  0.0, -1.0,   0.0,  1.0,  0.0,  -1.0,  0.0,  0.0,
+           0.0,  0.0, -1.0,  -1.0,  0.0,  0.0,   0.0, -1.0,  0.0,
+           0.0,  0.0, -1.0,   0.0, -1.0,  0.0,   1.0,  0.0,  0.0]
+coords = []
+for i in range(72):
+  coords.append(0.05 * normals[i])
+
+cols = [1.0, 0.0, 0.0,  0.0, 0.0, 1.0,  0.25, 0.75, 0.75,  0.5, 0.5, 0.2]
+
+atoms = []
+for i in range(4):
+  atoms.append(VisADTriangleArray())
+  atoms[i].vertexCount = 24
+  atoms[i].coordinates = coords
+  atoms[i].normals = normals
+  colors = []
+  for j in range(72):
+    colors.append(int(255.0 * cols[3*i + j%3]))
+  atoms[i].colors = colors
+
+control.setShapeSet(Integer1DSet(4))
+control.setShapes(atoms)
+
+# close the file for other uses
+molecule.close()
+
+print "done"
\ No newline at end of file
diff --git a/visad/python/dna_molecule.txt b/visad/python/dna_molecule.txt
new file mode 100644
index 0000000..f585e2f
--- /dev/null
+++ b/visad/python/dna_molecule.txt
@@ -0,0 +1,657 @@
+ATOM      1  P   UNK     1       5.973  -6.848  27.430  1.00  0.00
+ATOM      2  O1P UNK     1       6.257  -8.279  27.210  1.00  0.00
+ATOM      3  O2P UNK     1       6.791  -5.896  26.640  1.00  0.00
+ATOM      4  O5' UNK     1       4.427  -6.552  27.180  1.00  0.00
+ATOM      5  C5' UNK     1       3.455  -7.076  28.120  1.00  0.00
+ATOM      6  C4' UNK     1       2.183  -7.444  27.390  1.00  0.00
+ATOM      7  O4' UNK     1       1.397  -6.228  27.180  1.00  0.00
+ATOM      8  C1' UNK     1       1.462  -5.844  25.820  1.00  0.00
+ATOM      9  C2' UNK     1       2.419  -6.792  25.110  1.00  0.00
+ATOM     10  C3' UNK     1       2.350  -8.030  25.990  1.00  0.00
+ATOM     11  O3' UNK     1       1.231  -8.821  25.600  1.00  0.00
+ATOM     12  N9  UNK     1       1.883  -4.418  25.770  1.00  0.00
+ATOM     13  C8  UNK     1       3.150  -3.905  25.850  1.00  0.00
+ATOM     14  N7  UNK     1       3.191  -2.608  25.780  1.00  0.00
+ATOM     15  C5  UNK     1       1.865  -2.243  25.630  1.00  0.00
+ATOM     16  N6  UNK     1       1.887   0.170  25.490  1.00  0.00
+ATOM     17  C6  UNK     1       1.233  -0.989  25.500  1.00  0.00
+ATOM     18  N1  UNK     1      -0.103  -0.979  25.380  1.00  0.00
+ATOM     19  C2  UNK     1      -0.758  -2.136  25.390  1.00  0.00
+ATOM     20  N3  UNK     1      -0.291  -3.360  25.510  1.00  0.00
+ATOM     21  C4  UNK     1       1.050  -3.337  25.630  1.00  0.00
+ATOM     22  P   UNK     1       0.912  -9.017  24.050  1.00  0.00
+ATOM     23  O1P UNK     1       0.300 -10.342  23.830  1.00  0.00
+ATOM     24  O2P UNK     1       2.133  -8.727  23.260  1.00  0.00
+ATOM     25  O5' UNK     1      -0.166  -7.869  23.800  1.00  0.00
+ATOM     26  C5' UNK     1      -1.260  -7.722  24.740  1.00  0.00
+ATOM     27  C4' UNK     1      -2.504  -7.272  24.010  1.00  0.00
+ATOM     28  O4' UNK     1      -2.426  -5.826  23.800  1.00  0.00
+ATOM     29  C1' UNK     1      -2.148  -5.554  22.440  1.00  0.00
+ATOM     30  C2' UNK     1      -1.931  -6.884  21.730  1.00  0.00
+ATOM     31  C3' UNK     1      -2.714  -7.845  22.610  1.00  0.00
+ATOM     32  O3' UNK     1      -4.085  -7.826  22.220  1.00  0.00
+ATOM     33  N1  UNK     1      -0.969  -4.648  22.390  1.00  0.00
+ATOM     34  C6  UNK     1       0.296  -5.130  22.490  1.00  0.00
+ATOM     35  C5  UNK     1       1.376  -4.304  22.440  1.00  0.00
+ATOM     36  N4  UNK     1       2.109  -2.040  22.240  1.00  0.00
+ATOM     37  C4  UNK     1       1.110  -2.907  22.290  1.00  0.00
+ATOM     38  N3  UNK     1       0.060  -2.454  22.190  1.00  0.00
+ATOM     39  O2  UNK     1      -2.370  -2.882  22.150  1.00  0.00
+ATOM     40  C2  UNK     1      -1.203  -3.283  22.240  1.00  0.00
+ATOM     41  P   UNK     1      -4.458  -7.797  20.670  1.00  0.00
+ATOM     42  O1P UNK     1      -5.732  -8.510  20.450  1.00  0.00
+ATOM     43  O2P UNK     1      -3.300  -8.281  19.880  1.00  0.00
+ATOM     44  O5' UNK     1      -4.655  -6.235  20.420  1.00  0.00
+ATOM     45  C5' UNK     1      -5.454  -5.474  21.360  1.00  0.00
+ATOM     46  C4' UNK     1      -6.196  -4.377  20.630  1.00  0.00
+ATOM     47  O4' UNK     1      -5.283  -3.254  20.420  1.00  0.00
+ATOM     48  C1' UNK     1      -4.898  -3.197  19.060  1.00  0.00
+ATOM     49  C2' UNK     1      -5.504  -4.400  18.350  1.00  0.00
+ATOM     50  C3' UNK     1      -6.702  -4.718  19.230  1.00  0.00
+ATOM     51  O3' UNK     1      -7.800  -3.897  18.840  1.00  0.00
+ATOM     52  N9  UNK     1      -3.411  -3.157  19.010  1.00  0.00
+ATOM     53  C8  UNK     1      -2.507  -4.196  19.090  1.00  0.00
+ATOM     54  N7  UNK     1      -1.256  -3.821  19.010  1.00  0.00
+ATOM     55  C5  UNK     1      -1.327  -2.434  18.870  1.00  0.00
+ATOM     56  O6  UNK     1       0.923  -1.645  18.720  1.00  0.00
+ATOM     57  C6  UNK     1      -0.295  -1.475  18.740  1.00  0.00
+ATOM     58  N1  UNK     1      -0.815  -0.175  18.620  1.00  0.00
+ATOM     59  N2  UNK     1      -2.454   1.442  18.490  1.00  0.00
+ATOM     60  C2  UNK     1      -2.158   0.146  18.620  1.00  0.00
+ATOM     61  N3  UNK     1      -3.129  -0.755  18.750  1.00  0.00
+ATOM     62  C4  UNK     1      -2.647  -2.021  18.870  1.00  0.00
+ATOM     63  P   UNK     1      -8.085  -3.654  17.290  1.00  0.00
+ATOM     64  O1P UNK     1      -9.534  -3.482  17.070  1.00  0.00
+ATOM     65  O2P UNK     1      -7.432  -4.726  16.500  1.00  0.00
+ATOM     66  O5' UNK     1      -7.327  -2.275  17.040  1.00  0.00
+ATOM     67  C5' UNK     1      -7.525  -1.189  17.980  1.00  0.00
+ATOM     68  C4' UNK     1      -7.481   0.134  17.250  1.00  0.00
+ATOM     69  O4' UNK     1      -6.082   0.506  17.040  1.00  0.00
+ATOM     70  C1' UNK     1      -5.737   0.326  15.680  1.00  0.00
+ATOM     71  C2' UNK     1      -6.934  -0.291  14.970  1.00  0.00
+ATOM     72  C3' UNK     1      -8.090   0.156  15.850  1.00  0.00
+ATOM     73  O3' UNK     1      -8.497   1.466  15.460  1.00  0.00
+ATOM     74  N1  UNK     1      -4.511  -0.515  15.630  1.00  0.00
+ATOM     75  C6  UNK     1      -4.595  -1.882  15.730  1.00  0.00
+ATOM     76  C5M UNK     1      -3.523  -4.144  15.790  1.00  0.00
+ATOM     77  C5  UNK     1      -3.488  -2.650  15.680  1.00  0.00
+ATOM     78  O4  UNK     1      -1.119  -2.685  15.480  1.00  0.00
+ATOM     79  C4  UNK     1      -2.182  -2.056  15.530  1.00  0.00
+ATOM     80  N3  UNK     1      -2.194  -0.683  15.440  1.00  0.00
+ATOM     81  O2  UNK     1      -3.216   1.348  15.390  1.00  0.00
+ATOM     82  C2  UNK     1      -3.304   0.136  15.480  1.00  0.00
+ATOM     83  P   UNK     1      -8.585   1.830  13.910  1.00  0.00
+ATOM     84  O1P UNK     1      -9.655   2.821  13.690  1.00  0.00
+ATOM     85  O2P UNK     1      -8.686   0.579  13.120  1.00  0.00
+ATOM     86  O5' UNK     1      -7.160   2.500  13.660  1.00  0.00
+ATOM     87  C5' UNK     1      -6.682   3.495  14.600  1.00  0.00
+ATOM     88  C4' UNK     1      -5.869   4.539  13.870  1.00  0.00
+ATOM     89  O4' UNK     1      -4.518   4.018  13.660  1.00  0.00
+ATOM     90  C1' UNK     1      -4.345   3.670  12.300  1.00  0.00
+ATOM     91  C2' UNK     1      -5.677   3.874  11.590  1.00  0.00
+ATOM     92  C3' UNK     1      -6.349   4.916  12.470  1.00  0.00
+ATOM     93  O3' UNK     1      -5.908   6.214  12.080  1.00  0.00
+ATOM     94  N9  UNK     1      -3.848   2.268  12.250  1.00  0.00
+ATOM     95  C8  UNK     1      -4.571   1.109  12.330  1.00  0.00
+ATOM     96  N7  UNK     1      -3.842   0.035  12.260  1.00  0.00
+ATOM     97  C5  UNK     1      -2.555   0.519  12.110  1.00  0.00
+ATOM     98  N6  UNK     1      -1.153  -1.446  11.970  1.00  0.00
+ATOM     99  C6  UNK     1      -1.306  -0.124  11.980  1.00  0.00
+ATOM    100  N1  UNK     1      -0.220   0.653  11.860  1.00  0.00
+ATOM    101  C2  UNK     1      -0.369   1.974  11.870  1.00  0.00
+ATOM    102  N3  UNK     1      -1.467   2.690  11.990  1.00  0.00
+ATOM    103  C4  UNK     1      -2.538   1.883  12.110  1.00  0.00
+ATOM    104  P   UNK     1      -5.765   6.560  10.530  1.00  0.00
+ATOM    105  O1P UNK     1      -6.049   7.991  10.310  1.00  0.00
+ATOM    106  O2P UNK     1      -6.583   5.608   9.740  1.00  0.00
+ATOM    107  O5' UNK     1      -4.219   6.264  10.280  1.00  0.00
+ATOM    108  C5' UNK     1      -3.247   6.788  11.220  1.00  0.00
+ATOM    109  C4' UNK     1      -1.975   7.156  10.490  1.00  0.00
+ATOM    110  O4' UNK     1      -1.189   5.940  10.280  1.00  0.00
+ATOM    111  C1' UNK     1      -1.254   5.556   8.920  1.00  0.00
+ATOM    112  C2' UNK     1      -2.211   6.504   8.210  1.00  0.00
+ATOM    113  C3' UNK     1      -2.142   7.742   9.090  1.00  0.00
+ATOM    114  O3' UNK     1      -1.023   8.533   8.700  1.00  0.00
+ATOM    115  N1  UNK     1      -1.675   4.130   8.870  1.00  0.00
+ATOM    116  C6  UNK     1      -2.982   3.777   8.970  1.00  0.00
+ATOM    117  C5  UNK     1      -3.370   2.474   8.920  1.00  0.00
+ATOM    118  N4  UNK     1      -2.633   0.211   8.720  1.00  0.00
+ATOM    119  C4  UNK     1      -2.333   1.500   8.770  1.00  0.00
+ATOM    120  N3  UNK     1      -1.218   1.751   8.670  1.00  0.00
+ATOM    121  O2  UNK     1       0.496   3.525   8.630  1.00  0.00
+ATOM    122  C2  UNK     1      -0.684   3.163   8.720  1.00  0.00
+ATOM    123  P   UNK     1      -0.704   8.729   7.150  1.00  0.00
+ATOM    124  O1P UNK     1      -0.092  10.054   6.930  1.00  0.00
+ATOM    125  O2P UNK     1      -1.925   8.439   6.360  1.00  0.00
+ATOM    126  O5' UNK     1       0.374   7.581   6.900  1.00  0.00
+ATOM    127  C5' UNK     1       1.468   7.434   7.840  1.00  0.00
+ATOM    128  C4' UNK     1       2.712   6.984   7.110  1.00  0.00
+ATOM    129  O4' UNK     1       2.634   5.538   6.900  1.00  0.00
+ATOM    130  C1' UNK     1       2.356   5.266   5.540  1.00  0.00
+ATOM    131  C2' UNK     1       2.139   6.596   4.830  1.00  0.00
+ATOM    132  C3' UNK     1       2.922   7.557   5.710  1.00  0.00
+ATOM    133  O3' UNK     1       4.293   7.538   5.320  1.00  0.00
+ATOM    134  N9  UNK     1       1.177   4.360   5.490  1.00  0.00
+ATOM    135  C8  UNK     1      -0.165   4.668   5.570  1.00  0.00
+ATOM    136  N7  UNK     1      -0.957   3.630   5.490  1.00  0.00
+ATOM    137  C5  UNK     1      -0.084   2.549   5.350  1.00  0.00
+ATOM    138  O6  UNK     1      -1.441   0.589   5.200  1.00  0.00
+ATOM    139  C6  UNK     1      -0.355   1.168   5.220  1.00  0.00
+ATOM    140  N1  UNK     1       0.830   0.421   5.100  1.00  0.00
+ATOM    141  N2  UNK     1       3.106   0.076   4.970  1.00  0.00
+ATOM    142  C2  UNK     1       2.104   0.951   5.100  1.00  0.00
+ATOM    143  N3  UNK     1       2.360   2.250   5.230  1.00  0.00
+ATOM    144  C4  UNK     1       1.227   2.991   5.350  1.00  0.00
+ATOM    145  P   UNK     1       4.666   7.509   3.770  1.00  0.00
+ATOM    146  O1P UNK     1       5.940   8.222   3.550  1.00  0.00
+ATOM    147  O2P UNK     1       3.508   7.993   2.980  1.00  0.00
+ATOM    148  O5' UNK     1       4.863   5.947   3.520  1.00  0.00
+ATOM    149  C5' UNK     1       5.662   5.186   4.460  1.00  0.00
+ATOM    150  C4' UNK     1       6.404   4.089   3.730  1.00  0.00
+ATOM    151  O4' UNK     1       5.491   2.966   3.520  1.00  0.00
+ATOM    152  C1' UNK     1       5.106   2.909   2.160  1.00  0.00
+ATOM    153  C2' UNK     1       5.712   4.112   1.450  1.00  0.00
+ATOM    154  C3' UNK     1       6.910   4.430   2.330  1.00  0.00
+ATOM    155  O3' UNK     1       8.008   3.609   1.940  1.00  0.00
+ATOM    156  N1  UNK     1       3.619   2.869   2.110  1.00  0.00
+ATOM    157  C6  UNK     1       2.884   4.024   2.210  1.00  0.00
+ATOM    158  C5M UNK     1       0.687   5.224   2.270  1.00  0.00
+ATOM    159  C5  UNK     1       1.537   3.995   2.160  1.00  0.00
+ATOM    160  O4  UNK     1      -0.400   2.631   1.960  1.00  0.00
+ATOM    161  C4  UNK     1       0.830   2.746   2.010  1.00  0.00
+ATOM    162  N3  UNK     1       1.646   1.643   1.920  1.00  0.00
+ATOM    163  O2  UNK     1       3.667   0.600   1.870  1.00  0.00
+ATOM    164  C2  UNK     1       3.026   1.633   1.960  1.00  0.00
+ATOM    165  P   UNK     1       8.293   3.366   0.390  1.00  0.00
+ATOM    166  O1P UNK     1       9.742   3.194   0.170  1.00  0.00
+ATOM    167  O2P UNK     1       7.640   4.438  -0.400  1.00  0.00
+ATOM    168  O5' UNK     1       7.535   1.987   0.140  1.00  0.00
+ATOM    169  C5' UNK     1       7.733   0.901   1.080  1.00  0.00
+ATOM    170  C4' UNK     1       7.689  -0.422   0.350  1.00  0.00
+ATOM    171  O4' UNK     1       6.290  -0.794   0.140  1.00  0.00
+ATOM    172  C1' UNK     1       5.945  -0.614  -1.220  1.00  0.00
+ATOM    173  C2' UNK     1       7.142   0.003  -1.930  1.00  0.00
+ATOM    174  C3' UNK     1       8.298  -0.444  -1.050  1.00  0.00
+ATOM    175  O3' UNK     1       8.705  -1.754  -1.440  1.00  0.00
+ATOM    176  N9  UNK     1       4.719   0.227  -1.270  1.00  0.00
+ATOM    177  C8  UNK     1       4.623   1.591  -1.190  1.00  0.00
+ATOM    178  N7  UNK     1       3.402   2.030  -1.260  1.00  0.00
+ATOM    179  C5  UNK     1       2.644   0.882  -1.410  1.00  0.00
+ATOM    180  N6  UNK     1       0.356   1.648  -1.550  1.00  0.00
+ATOM    181  C6  UNK     1       1.256   0.669  -1.540  1.00  0.00
+ATOM    182  N1  UNK     1       0.834  -0.598  -1.660  1.00  0.00
+ATOM    183  C2  UNK     1       1.732  -1.579  -1.650  1.00  0.00
+ATOM    184  N3  UNK     1       3.040  -1.513  -1.530  1.00  0.00
+ATOM    185  C4  UNK     1       3.433  -0.231  -1.410  1.00  0.00
+ATOM    186  P   UNK     1       8.793  -2.118  -2.990  1.00  0.00
+ATOM    187  O1P UNK     1       9.863  -3.109  -3.210  1.00  0.00
+ATOM    188  O2P UNK     1       8.894  -0.867  -3.780  1.00  0.00
+ATOM    189  O5' UNK     1       7.368  -2.788  -3.240  1.00  0.00
+ATOM    190  C5' UNK     1       6.890  -3.783  -2.300  1.00  0.00
+ATOM    191  C4' UNK     1       6.077  -4.827  -3.030  1.00  0.00
+ATOM    192  O4' UNK     1       4.726  -4.306  -3.240  1.00  0.00
+ATOM    193  C1' UNK     1       4.553  -3.958  -4.600  1.00  0.00
+ATOM    194  C2' UNK     1       5.885  -4.162  -5.310  1.00  0.00
+ATOM    195  C3' UNK     1       6.557  -5.204  -4.430  1.00  0.00
+ATOM    196  O3' UNK     1       6.116  -6.502  -4.820  1.00  0.00
+ATOM    197  N1  UNK     1       4.056  -2.556  -4.650  1.00  0.00
+ATOM    198  C6  UNK     1       4.905  -1.503  -4.550  1.00  0.00
+ATOM    199  C5  UNK     1       4.453  -0.220  -4.600  1.00  0.00
+ATOM    200  N4  UNK     1       2.527   1.177  -4.800  1.00  0.00
+ATOM    201  C4  UNK     1       3.042  -0.041  -4.750  1.00  0.00
+ATOM    202  N3  UNK     1       2.287  -0.900  -4.850  1.00  0.00
+ATOM    203  O2  UNK     1       1.943  -3.343  -4.890  1.00  0.00
+ATOM    204  C2  UNK     1       2.686  -2.357  -4.800  1.00  0.00
+ATOM    205  P   UNK     1       5.973  -6.848  -6.370  1.00  0.00
+ATOM    206  O1P UNK     1       6.257  -8.279  -6.590  1.00  0.00
+ATOM    207  O2P UNK     1       6.791  -5.896  -7.160  1.00  0.00
+ATOM    208  O5' UNK     1       4.427  -6.552  -6.620  1.00  0.00
+ATOM    209  C5' UNK     1       3.455  -7.076  -5.680  1.00  0.00
+ATOM    210  C4' UNK     1       2.183  -7.444  -6.410  1.00  0.00
+ATOM    211  O4' UNK     1       1.397  -6.228  -6.620  1.00  0.00
+ATOM    212  C1' UNK     1       1.462  -5.844  -7.980  1.00  0.00
+ATOM    213  C2' UNK     1       2.419  -6.792  -8.690  1.00  0.00
+ATOM    214  C3' UNK     1       2.350  -8.030  -7.810  1.00  0.00
+ATOM    215  O3' UNK     1       1.231  -8.821  -8.200  1.00  0.00
+ATOM    216  N9  UNK     1       1.883  -4.418  -8.030  1.00  0.00
+ATOM    217  C8  UNK     1       3.150  -3.879  -7.950  1.00  0.00
+ATOM    218  N7  UNK     1       3.180  -2.574  -8.030  1.00  0.00
+ATOM    219  C5  UNK     1       1.840  -2.212  -8.170  1.00  0.00
+ATOM    220  O6  UNK     1       1.785   0.171  -8.320  1.00  0.00
+ATOM    221  C6  UNK     1       1.247  -0.935  -8.300  1.00  0.00
+ATOM    222  N1  UNK     1      -0.151  -1.028  -8.420  1.00  0.00
+ATOM    223  N2  UNK     1      -2.195  -2.087  -8.550  1.00  0.00
+ATOM    224  C2  UNK     1      -0.870  -2.205  -8.420  1.00  0.00
+ATOM    225  N3  UNK     1      -0.314  -3.407  -8.290  1.00  0.00
+ATOM    226  C4  UNK     1       1.039  -3.340  -8.170  1.00  0.00
+ATOM    227  P   UNK     1       0.912  -9.017  -9.750  1.00  0.00
+ATOM    228  O1P UNK     1       0.300 -10.342  -9.970  1.00  0.00
+ATOM    229  O2P UNK     1       2.133  -8.727 -10.540  1.00  0.00
+ATOM    230  O5' UNK     1      -0.166  -7.869 -10.000  1.00  0.00
+ATOM    231  C5' UNK     1      -1.260  -7.722  -9.060  1.00  0.00
+ATOM    232  C4' UNK     1      -2.504  -7.272  -9.790  1.00  0.00
+ATOM    233  O4' UNK     1      -2.426  -5.826 -10.000  1.00  0.00
+ATOM    234  C1' UNK     1      -2.148  -5.554 -11.360  1.00  0.00
+ATOM    235  C2' UNK     1      -1.931  -6.884 -12.070  1.00  0.00
+ATOM    236  C3' UNK     1      -2.714  -7.845 -11.190  1.00  0.00
+ATOM    237  O3' UNK     1      -4.085  -7.826 -11.580  1.00  0.00
+ATOM    238  N1  UNK     1      -0.969  -4.648 -11.410  1.00  0.00
+ATOM    239  C6  UNK     1       0.305  -5.150 -11.310  1.00  0.00
+ATOM    240  C5M UNK     1       2.788  -4.830 -11.250  1.00  0.00
+ATOM    241  C5  UNK     1       1.377  -4.335 -11.360  1.00  0.00
+ATOM    242  O4  UNK     1       2.143  -2.092 -11.560  1.00  0.00
+ATOM    243  C4  UNK     1       1.216  -2.909 -11.510  1.00  0.00
+ATOM    244  N3  UNK     1      -0.093  -2.496 -11.600  1.00  0.00
+ATOM    245  O2  UNK     1      -2.341  -2.841 -11.650  1.00  0.00
+ATOM    246  C2  UNK     1      -1.216  -3.299 -11.560  1.00  0.00
+ATOM    247  P   UNK     1      -4.458  -7.797 -13.130  1.00  0.00
+ATOM    248  O1P UNK     1      -5.732  -8.510 -13.350  1.00  0.00
+ATOM    249  O2P UNK     1      -3.300  -8.281 -13.920  1.00  0.00
+ATOM    250  O5' UNK     1      -4.655  -6.235 -13.380  1.00  0.00
+ATOM    251  C5' UNK     1      -5.454  -5.474 -12.440  1.00  0.00
+ATOM    252  C4' UNK     1      -6.196  -4.377 -13.170  1.00  0.00
+ATOM    253  O4' UNK     1      -5.283  -3.254 -13.380  1.00  0.00
+ATOM    254  C1' UNK     1      -4.898  -3.197 -14.740  1.00  0.00
+ATOM    255  C2' UNK     1      -5.504  -4.400 -15.450  1.00  0.00
+ATOM    256  C3' UNK     1      -6.702  -4.718 -14.570  1.00  0.00
+ATOM    257  O3' UNK     1      -7.800  -3.897 -14.960  1.00  0.00
+ATOM    258  N9  UNK     1      -3.411  -3.157 -14.790  1.00  0.00
+ATOM    259  C8  UNK     1      -2.532  -4.203 -14.710  1.00  0.00
+ATOM    260  N7  UNK     1      -1.286  -3.841 -14.780  1.00  0.00
+ATOM    261  C5  UNK     1      -1.348  -2.468 -14.930  1.00  0.00
+ATOM    262  N6  UNK     1       0.954  -1.742 -15.070  1.00  0.00
+ATOM    263  C6  UNK     1      -0.350  -1.479 -15.060  1.00  0.00
+ATOM    264  N1  UNK     1      -0.754  -0.205 -15.180  1.00  0.00
+ATOM    265  C2  UNK     1      -2.056   0.060 -15.170  1.00  0.00
+ATOM    266  N3  UNK     1      -3.076  -0.762 -15.050  1.00  0.00
+ATOM    267  C4  UNK     1      -2.640  -2.030 -14.930  1.00  0.00
+ATOM    268  P   UNK     1      -8.085  -3.654 -16.510  1.00  0.00
+ATOM    269  O1P UNK     1      -9.534  -3.482 -16.730  1.00  0.00
+ATOM    270  O2P UNK     1      -7.432  -4.726 -17.300  1.00  0.00
+ATOM    271  O5' UNK     1      -7.327  -2.275 -16.760  1.00  0.00
+ATOM    272  C5' UNK     1      -7.525  -1.189 -15.820  1.00  0.00
+ATOM    273  C4' UNK     1      -7.481   0.134 -16.550  1.00  0.00
+ATOM    274  O4' UNK     1      -6.082   0.506 -16.760  1.00  0.00
+ATOM    275  C1' UNK     1      -5.737   0.326 -18.120  1.00  0.00
+ATOM    276  C2' UNK     1      -6.934  -0.291 -18.830  1.00  0.00
+ATOM    277  C3' UNK     1      -8.090   0.156 -17.950  1.00  0.00
+ATOM    278  O3' UNK     1      -8.497   1.466 -18.340  1.00  0.00
+ATOM    279  N1  UNK     1      -4.511  -0.515 -18.170  1.00  0.00
+ATOM    280  C6  UNK     1      -4.579  -1.867 -18.070  1.00  0.00
+ATOM    281  C5  UNK     1      -3.459  -2.639 -18.120  1.00  0.00
+ATOM    282  N4  UNK     1      -1.080  -2.637 -18.320  1.00  0.00
+ATOM    283  C4  UNK     1      -2.213  -1.954 -18.270  1.00  0.00
+ATOM    284  N3  UNK     1      -2.106  -0.816 -18.370  1.00  0.00
+ATOM    285  O2  UNK     1      -3.264   1.363 -18.410  1.00  0.00
+ATOM    286  C2  UNK     1      -3.285   0.129 -18.320  1.00  0.00
+ATOM    287  P   UNK     1      -8.585   1.830 -19.890  1.00  0.00
+ATOM    288  O1P UNK     1      -9.655   2.821 -20.110  1.00  0.00
+ATOM    289  O2P UNK     1      -8.686   0.579 -20.680  1.00  0.00
+ATOM    290  O5' UNK     1      -7.160   2.500 -20.140  1.00  0.00
+ATOM    291  C5' UNK     1      -6.682   3.495 -19.200  1.00  0.00
+ATOM    292  C4' UNK     1      -5.869   4.539 -19.930  1.00  0.00
+ATOM    293  O4' UNK     1      -4.518   4.018 -20.140  1.00  0.00
+ATOM    294  C1' UNK     1      -4.345   3.670 -21.500  1.00  0.00
+ATOM    295  C2' UNK     1      -5.677   3.874 -22.210  1.00  0.00
+ATOM    296  C3' UNK     1      -6.349   4.916 -21.330  1.00  0.00
+ATOM    297  O3' UNK     1      -5.908   6.214 -21.720  1.00  0.00
+ATOM    298  N9  UNK     1      -3.848   2.268 -21.550  1.00  0.00
+ATOM    299  C8  UNK     1      -4.556   1.087 -21.470  1.00  0.00
+ATOM    300  N7  UNK     1      -3.813   0.013 -21.550  1.00  0.00
+ATOM    301  C5  UNK     1      -2.516   0.509 -21.690  1.00  0.00
+ATOM    302  O6  UNK     1      -1.071  -1.386 -21.840  1.00  0.00
+ATOM    303  C6  UNK     1      -1.286  -0.176 -21.820  1.00  0.00
+ATOM    304  N1  UNK     1      -0.209   0.721 -21.940  1.00  0.00
+ATOM    305  N2  UNK     1       0.822   2.779 -22.070  1.00  0.00
+ATOM    306  C2  UNK     1      -0.319   2.096 -21.940  1.00  0.00
+ATOM    307  N3  UNK     1      -1.476   2.742 -21.810  1.00  0.00
+ATOM    308  C4  UNK     1      -2.531   1.892 -21.690  1.00  0.00
+ATOM    309  P   UNK     1      -5.765   6.560 -23.270  1.00  0.00
+ATOM    310  O1P UNK     1      -6.049   7.991 -23.490  1.00  0.00
+ATOM    311  O2P UNK     1      -6.583   5.608 -24.060  1.00  0.00
+ATOM    312  O5' UNK     1      -4.219   6.264 -23.520  1.00  0.00
+ATOM    313  C5' UNK     1      -3.247   6.788 -22.580  1.00  0.00
+ATOM    314  C4' UNK     1      -1.975   7.156 -23.310  1.00  0.00
+ATOM    315  O4' UNK     1      -1.189   5.940 -23.520  1.00  0.00
+ATOM    316  C1' UNK     1      -1.254   5.556 -24.880  1.00  0.00
+ATOM    317  C2' UNK     1      -2.211   6.504 -25.590  1.00  0.00
+ATOM    318  C3' UNK     1      -2.142   7.742 -24.710  1.00  0.00
+ATOM    319  O3' UNK     1      -1.023   8.533 -25.100  1.00  0.00
+ATOM    320  N1  UNK     1      -1.675   4.130 -24.930  1.00  0.00
+ATOM    321  C6  UNK     1      -3.001   3.788 -24.830  1.00  0.00
+ATOM    322  C5M UNK     1      -4.821   2.070 -24.770  1.00  0.00
+ATOM    323  C5  UNK     1      -3.389   2.498 -24.880  1.00  0.00
+ATOM    324  O4  UNK     1      -2.691   0.234 -25.080  1.00  0.00
+ATOM    325  C4  UNK     1      -2.420   1.440 -25.030  1.00  0.00
+ATOM    326  N3  UNK     1      -1.119   1.875 -25.120  1.00  0.00
+ATOM    327  O2  UNK     1       0.497   3.475 -25.170  1.00  0.00
+ATOM    328  C2  UNK     1      -0.683   3.184 -25.080  1.00  0.00
+ATOM    329  P   UNK     1       4.666  -7.797 -27.430  1.00  0.00
+ATOM    330  O1P UNK     1       5.940  -8.510 -27.210  1.00  0.00
+ATOM    331  O2P UNK     1       3.508  -8.281 -26.640  1.00  0.00
+ATOM    332  O5' UNK     1       4.863  -6.235 -27.180  1.00  0.00
+ATOM    333  C5' UNK     1       5.662  -5.474 -28.120  1.00  0.00
+ATOM    334  C4' UNK     1       6.404  -4.377 -27.390  1.00  0.00
+ATOM    335  O4' UNK     1       5.491  -3.254 -27.180  1.00  0.00
+ATOM    336  C1' UNK     1       5.106  -3.197 -25.820  1.00  0.00
+ATOM    337  C2' UNK     1       5.712  -4.400 -25.110  1.00  0.00
+ATOM    338  C3' UNK     1       6.910  -4.718 -25.990  1.00  0.00
+ATOM    339  O3' UNK     1       8.008  -3.897 -25.600  1.00  0.00
+ATOM    340  N9  UNK     1       3.619  -3.157 -25.770  1.00  0.00
+ATOM    341  C8  UNK     1       2.740  -4.203 -25.850  1.00  0.00
+ATOM    342  N7  UNK     1       1.494  -3.841 -25.780  1.00  0.00
+ATOM    343  C5  UNK     1       1.556  -2.468 -25.630  1.00  0.00
+ATOM    344  N6  UNK     1      -0.746  -1.742 -25.490  1.00  0.00
+ATOM    345  C6  UNK     1       0.558  -1.479 -25.500  1.00  0.00
+ATOM    346  N1  UNK     1       0.962  -0.205 -25.380  1.00  0.00
+ATOM    347  C2  UNK     1       2.264   0.060 -25.390  1.00  0.00
+ATOM    348  N3  UNK     1       3.284  -0.762 -25.510  1.00  0.00
+ATOM    349  C4  UNK     1       2.848  -2.030 -25.630  1.00  0.00
+ATOM    350  P   UNK     1       8.293  -3.654 -24.050  1.00  0.00
+ATOM    351  O1P UNK     1       9.742  -3.482 -23.830  1.00  0.00
+ATOM    352  O2P UNK     1       7.640  -4.726 -23.260  1.00  0.00
+ATOM    353  O5' UNK     1       7.535  -2.275 -23.800  1.00  0.00
+ATOM    354  C5' UNK     1       7.733  -1.189 -24.740  1.00  0.00
+ATOM    355  C4' UNK     1       7.689   0.134 -24.010  1.00  0.00
+ATOM    356  O4' UNK     1       6.290   0.506 -23.800  1.00  0.00
+ATOM    357  C1' UNK     1       5.945   0.326 -22.440  1.00  0.00
+ATOM    358  C2' UNK     1       7.142  -0.291 -21.730  1.00  0.00
+ATOM    359  C3' UNK     1       8.298   0.156 -22.610  1.00  0.00
+ATOM    360  O3' UNK     1       8.705   1.466 -22.220  1.00  0.00
+ATOM    361  N1  UNK     1       4.719  -0.515 -22.390  1.00  0.00
+ATOM    362  C6  UNK     1       4.787  -1.867 -22.490  1.00  0.00
+ATOM    363  C5  UNK     1       3.667  -2.639 -22.440  1.00  0.00
+ATOM    364  N4  UNK     1       1.288  -2.637 -22.240  1.00  0.00
+ATOM    365  C4  UNK     1       2.421  -1.954 -22.290  1.00  0.00
+ATOM    366  N3  UNK     1       2.314  -0.816 -22.190  1.00  0.00
+ATOM    367  O2  UNK     1       3.472   1.363 -22.150  1.00  0.00
+ATOM    368  C2  UNK     1       3.493   0.129 -22.240  1.00  0.00
+ATOM    369  P   UNK     1       8.793   1.830 -20.670  1.00  0.00
+ATOM    370  O1P UNK     1       9.863   2.821 -20.450  1.00  0.00
+ATOM    371  O2P UNK     1       8.894   0.579 -19.880  1.00  0.00
+ATOM    372  O5' UNK     1       7.368   2.500 -20.420  1.00  0.00
+ATOM    373  C5' UNK     1       6.890   3.495 -21.360  1.00  0.00
+ATOM    374  C4' UNK     1       6.077   4.539 -20.630  1.00  0.00
+ATOM    375  O4' UNK     1       4.726   4.018 -20.420  1.00  0.00
+ATOM    376  C1' UNK     1       4.553   3.670 -19.060  1.00  0.00
+ATOM    377  C2' UNK     1       5.885   3.874 -18.350  1.00  0.00
+ATOM    378  C3' UNK     1       6.557   4.916 -19.230  1.00  0.00
+ATOM    379  O3' UNK     1       6.116   6.214 -18.840  1.00  0.00
+ATOM    380  N9  UNK     1       4.056   2.268 -19.010  1.00  0.00
+ATOM    381  C8  UNK     1       4.764   1.087 -19.090  1.00  0.00
+ATOM    382  N7  UNK     1       4.021   0.013 -19.010  1.00  0.00
+ATOM    383  C5  UNK     1       2.724   0.509 -18.870  1.00  0.00
+ATOM    384  O6  UNK     1       1.279  -1.386 -18.720  1.00  0.00
+ATOM    385  C6  UNK     1       1.494  -0.176 -18.740  1.00  0.00
+ATOM    386  N1  UNK     1       0.417   0.721 -18.620  1.00  0.00
+ATOM    387  N2  UNK     1      -0.614   2.779 -18.490  1.00  0.00
+ATOM    388  C2  UNK     1       0.527   2.096 -18.620  1.00  0.00
+ATOM    389  N3  UNK     1       1.684   2.742 -18.750  1.00  0.00
+ATOM    390  C4  UNK     1       2.739   1.892 -18.870  1.00  0.00
+ATOM    391  P   UNK     1       5.973   6.560 -17.290  1.00  0.00
+ATOM    392  O1P UNK     1       6.257   7.991 -17.070  1.00  0.00
+ATOM    393  O2P UNK     1       6.791   5.608 -16.500  1.00  0.00
+ATOM    394  O5' UNK     1       4.427   6.264 -17.040  1.00  0.00
+ATOM    395  C5' UNK     1       3.455   6.788 -17.980  1.00  0.00
+ATOM    396  C4' UNK     1       2.183   7.156 -17.250  1.00  0.00
+ATOM    397  O4' UNK     1       1.397   5.940 -17.040  1.00  0.00
+ATOM    398  C1' UNK     1       1.462   5.556 -15.680  1.00  0.00
+ATOM    399  C2' UNK     1       2.419   6.504 -14.970  1.00  0.00
+ATOM    400  C3' UNK     1       2.350   7.742 -15.850  1.00  0.00
+ATOM    401  O3' UNK     1       1.231   8.533 -15.460  1.00  0.00
+ATOM    402  N1  UNK     1       1.883   4.130 -15.630  1.00  0.00
+ATOM    403  C6  UNK     1       3.209   3.788 -15.730  1.00  0.00
+ATOM    404  C5M UNK     1       5.029   2.070 -15.790  1.00  0.00
+ATOM    405  C5  UNK     1       3.597   2.498 -15.680  1.00  0.00
+ATOM    406  O4  UNK     1       2.899   0.234 -15.480  1.00  0.00
+ATOM    407  C4  UNK     1       2.628   1.440 -15.530  1.00  0.00
+ATOM    408  N3  UNK     1       1.327   1.875 -15.440  1.00  0.00
+ATOM    409  O2  UNK     1      -0.289   3.475 -15.390  1.00  0.00
+ATOM    410  C2  UNK     1       0.891   3.184 -15.480  1.00  0.00
+ATOM    411  P   UNK     1       0.912   8.729 -13.910  1.00  0.00
+ATOM    412  O1P UNK     1       0.300  10.054 -13.690  1.00  0.00
+ATOM    413  O2P UNK     1       2.133   8.439 -13.120  1.00  0.00
+ATOM    414  O5' UNK     1      -0.166   7.581 -13.660  1.00  0.00
+ATOM    415  C5' UNK     1      -1.260   7.434 -14.600  1.00  0.00
+ATOM    416  C4' UNK     1      -2.504   6.984 -13.870  1.00  0.00
+ATOM    417  O4' UNK     1      -2.426   5.538 -13.660  1.00  0.00
+ATOM    418  C1' UNK     1      -2.148   5.266 -12.300  1.00  0.00
+ATOM    419  C2' UNK     1      -1.931   6.596 -11.590  1.00  0.00
+ATOM    420  C3' UNK     1      -2.714   7.557 -12.470  1.00  0.00
+ATOM    421  O3' UNK     1      -4.085   7.538 -12.080  1.00  0.00
+ATOM    422  N9  UNK     1      -0.969   4.360 -12.250  1.00  0.00
+ATOM    423  C8  UNK     1       0.357   4.689 -12.330  1.00  0.00
+ATOM    424  N7  UNK     1       1.153   3.664 -12.260  1.00  0.00
+ATOM    425  C5  UNK     1       0.295   2.589 -12.110  1.00  0.00
+ATOM    426  N6  UNK     1       1.731   0.649 -11.970  1.00  0.00
+ATOM    427  C6  UNK     1       0.521   1.203 -11.980  1.00  0.00
+ATOM    428  N1  UNK     1      -0.554   0.410 -11.860  1.00  0.00
+ATOM    429  C2  UNK     1      -1.764   0.961 -11.870  1.00  0.00
+ATOM    430  N3  UNK     1      -2.106   2.226 -11.990  1.00  0.00
+ATOM    431  C4  UNK     1      -1.008   2.995 -12.110  1.00  0.00
+ATOM    432  P   UNK     1      -4.458   7.509 -10.530  1.00  0.00
+ATOM    433  O1P UNK     1      -5.732   8.222 -10.310  1.00  0.00
+ATOM    434  O2P UNK     1      -3.300   7.993  -9.740  1.00  0.00
+ATOM    435  O5' UNK     1      -4.655   5.947 -10.280  1.00  0.00
+ATOM    436  C5' UNK     1      -5.454   5.186 -11.220  1.00  0.00
+ATOM    437  C4' UNK     1      -6.196   4.089 -10.490  1.00  0.00
+ATOM    438  O4' UNK     1      -5.283   2.966 -10.280  1.00  0.00
+ATOM    439  C1' UNK     1      -4.898   2.909  -8.920  1.00  0.00
+ATOM    440  C2' UNK     1      -5.504   4.112  -8.210  1.00  0.00
+ATOM    441  C3' UNK     1      -6.702   4.430  -9.090  1.00  0.00
+ATOM    442  O3' UNK     1      -7.800   3.609  -8.700  1.00  0.00
+ATOM    443  N1  UNK     1      -3.411   2.869  -8.870  1.00  0.00
+ATOM    444  C6  UNK     1      -2.672   4.003  -8.970  1.00  0.00
+ATOM    445  C5  UNK     1      -1.312   3.969  -8.920  1.00  0.00
+ATOM    446  N4  UNK     1       0.612   2.569  -8.720  1.00  0.00
+ATOM    447  C4  UNK     1      -0.706   2.682  -8.770  1.00  0.00
+ATOM    448  N3  UNK     1      -1.289   1.698  -8.670  1.00  0.00
+ATOM    449  O2  UNK     1      -3.507   0.617  -8.630  1.00  0.00
+ATOM    450  C2  UNK     1      -2.798   1.627  -8.720  1.00  0.00
+ATOM    451  P   UNK     1      -8.085   3.366  -7.150  1.00  0.00
+ATOM    452  O1P UNK     1      -9.534   3.194  -6.930  1.00  0.00
+ATOM    453  O2P UNK     1      -7.432   4.438  -6.360  1.00  0.00
+ATOM    454  O5' UNK     1      -7.327   1.987  -6.900  1.00  0.00
+ATOM    455  C5' UNK     1      -7.525   0.901  -7.840  1.00  0.00
+ATOM    456  C4' UNK     1      -7.481  -0.422  -7.110  1.00  0.00
+ATOM    457  O4' UNK     1      -6.082  -0.794  -6.900  1.00  0.00
+ATOM    458  C1' UNK     1      -5.737  -0.614  -5.540  1.00  0.00
+ATOM    459  C2' UNK     1      -6.934   0.003  -4.830  1.00  0.00
+ATOM    460  C3' UNK     1      -8.090  -0.444  -5.710  1.00  0.00
+ATOM    461  O3' UNK     1      -8.497  -1.754  -5.320  1.00  0.00
+ATOM    462  N9  UNK     1      -4.511   0.227  -5.490  1.00  0.00
+ATOM    463  C8  UNK     1      -4.390   1.599  -5.570  1.00  0.00
+ATOM    464  N7  UNK     1      -3.157   2.031  -5.490  1.00  0.00
+ATOM    465  C5  UNK     1      -2.399   0.867  -5.350  1.00  0.00
+ATOM    466  O6  UNK     1      -0.116   1.552  -5.200  1.00  0.00
+ATOM    467  C6  UNK     1      -1.002   0.698  -5.220  1.00  0.00
+ATOM    468  N1  UNK     1      -0.658  -0.660  -5.100  1.00  0.00
+ATOM    469  N2  UNK     1      -1.033  -2.931  -4.970  1.00  0.00
+ATOM    470  C2  UNK     1      -1.555  -1.708  -5.100  1.00  0.00
+ATOM    471  N3  UNK     1      -2.870  -1.550  -5.230  1.00  0.00
+ATOM    472  C4  UNK     1      -3.225  -0.243  -5.350  1.00  0.00
+ATOM    473  P   UNK     1      -8.585  -2.118  -3.770  1.00  0.00
+ATOM    474  O1P UNK     1      -9.655  -3.109  -3.550  1.00  0.00
+ATOM    475  O2P UNK     1      -8.686  -0.867  -2.980  1.00  0.00
+ATOM    476  O5' UNK     1      -7.160  -2.788  -3.520  1.00  0.00
+ATOM    477  C5' UNK     1      -6.682  -3.783  -4.460  1.00  0.00
+ATOM    478  C4' UNK     1      -5.869  -4.827  -3.730  1.00  0.00
+ATOM    479  O4' UNK     1      -4.518  -4.306  -3.520  1.00  0.00
+ATOM    480  C1' UNK     1      -4.345  -3.958  -2.160  1.00  0.00
+ATOM    481  C2' UNK     1      -5.677  -4.162  -1.450  1.00  0.00
+ATOM    482  C3' UNK     1      -6.349  -5.204  -2.330  1.00  0.00
+ATOM    483  O3' UNK     1      -5.908  -6.502  -1.940  1.00  0.00
+ATOM    484  N1  UNK     1      -3.848  -2.556  -2.110  1.00  0.00
+ATOM    485  C6  UNK     1      -4.719  -1.500  -2.210  1.00  0.00
+ATOM    486  C5M UNK     1      -5.182   0.960  -2.270  1.00  0.00
+ATOM    487  C5  UNK     1      -4.275  -0.228  -2.160  1.00  0.00
+ATOM    488  O4  UNK     1      -2.379   1.193  -1.960  1.00  0.00
+ATOM    489  C4  UNK     1      -2.869   0.059  -2.010  1.00  0.00
+ATOM    490  N3  UNK     1      -2.072  -1.059  -1.920  1.00  0.00
+ATOM    491  O2  UNK     1      -1.705  -3.303  -1.870  1.00  0.00
+ATOM    492  C2  UNK     1      -2.489  -2.374  -1.960  1.00  0.00
+ATOM    493  P   UNK     1      -5.765  -6.848  -0.390  1.00  0.00
+ATOM    494  O1P UNK     1      -6.049  -8.279  -0.170  1.00  0.00
+ATOM    495  O2P UNK     1      -6.583  -5.896   0.400  1.00  0.00
+ATOM    496  O5' UNK     1      -4.219  -6.552  -0.140  1.00  0.00
+ATOM    497  C5' UNK     1      -3.247  -7.076  -1.080  1.00  0.00
+ATOM    498  C4' UNK     1      -1.975  -7.444  -0.350  1.00  0.00
+ATOM    499  O4' UNK     1      -1.189  -6.228  -0.140  1.00  0.00
+ATOM    500  C1' UNK     1      -1.254  -5.844   1.220  1.00  0.00
+ATOM    501  C2' UNK     1      -2.211  -6.792   1.930  1.00  0.00
+ATOM    502  C3' UNK     1      -2.142  -8.030   1.050  1.00  0.00
+ATOM    503  O3' UNK     1      -1.023  -8.821   1.440  1.00  0.00
+ATOM    504  N9  UNK     1      -1.675  -4.418   1.270  1.00  0.00
+ATOM    505  C8  UNK     1      -2.942  -3.905   1.190  1.00  0.00
+ATOM    506  N7  UNK     1      -2.983  -2.608   1.260  1.00  0.00
+ATOM    507  C5  UNK     1      -1.657  -2.243   1.410  1.00  0.00
+ATOM    508  N6  UNK     1      -1.679   0.170   1.550  1.00  0.00
+ATOM    509  C6  UNK     1      -1.025  -0.989   1.540  1.00  0.00
+ATOM    510  N1  UNK     1       0.311  -0.979   1.660  1.00  0.00
+ATOM    511  C2  UNK     1       0.966  -2.136   1.650  1.00  0.00
+ATOM    512  N3  UNK     1       0.499  -3.360   1.530  1.00  0.00
+ATOM    513  C4  UNK     1      -0.842  -3.337   1.410  1.00  0.00
+ATOM    514  P   UNK     1      -0.704  -9.017   2.990  1.00  0.00
+ATOM    515  O1P UNK     1      -0.092 -10.342   3.210  1.00  0.00
+ATOM    516  O2P UNK     1      -1.925  -8.727   3.780  1.00  0.00
+ATOM    517  O5' UNK     1       0.374  -7.869   3.240  1.00  0.00
+ATOM    518  C5' UNK     1       1.468  -7.722   2.300  1.00  0.00
+ATOM    519  C4' UNK     1       2.712  -7.272   3.030  1.00  0.00
+ATOM    520  O4' UNK     1       2.634  -5.826   3.240  1.00  0.00
+ATOM    521  C1' UNK     1       2.356  -5.554   4.600  1.00  0.00
+ATOM    522  C2' UNK     1       2.139  -6.884   5.310  1.00  0.00
+ATOM    523  C3' UNK     1       2.922  -7.845   4.430  1.00  0.00
+ATOM    524  O3' UNK     1       4.293  -7.826   4.820  1.00  0.00
+ATOM    525  N1  UNK     1       1.177  -4.648   4.650  1.00  0.00
+ATOM    526  C6  UNK     1      -0.088  -5.130   4.550  1.00  0.00
+ATOM    527  C5  UNK     1      -1.168  -4.304   4.600  1.00  0.00
+ATOM    528  N4  UNK     1      -1.901  -2.040   4.800  1.00  0.00
+ATOM    529  C4  UNK     1      -0.902  -2.907   4.750  1.00  0.00
+ATOM    530  N3  UNK     1       0.148  -2.454   4.850  1.00  0.00
+ATOM    531  O2  UNK     1       2.578  -2.882   4.890  1.00  0.00
+ATOM    532  C2  UNK     1       1.411  -3.283   4.800  1.00  0.00
+ATOM    533  P   UNK     1       4.666  -7.797   6.370  1.00  0.00
+ATOM    534  O1P UNK     1       5.940  -8.510   6.590  1.00  0.00
+ATOM    535  O2P UNK     1       3.508  -8.281   7.160  1.00  0.00
+ATOM    536  O5' UNK     1       4.863  -6.235   6.620  1.00  0.00
+ATOM    537  C5' UNK     1       5.662  -5.474   5.680  1.00  0.00
+ATOM    538  C4' UNK     1       6.404  -4.377   6.410  1.00  0.00
+ATOM    539  O4' UNK     1       5.491  -3.254   6.620  1.00  0.00
+ATOM    540  C1' UNK     1       5.106  -3.197   7.980  1.00  0.00
+ATOM    541  C2' UNK     1       5.712  -4.400   8.690  1.00  0.00
+ATOM    542  C3' UNK     1       6.910  -4.718   7.810  1.00  0.00
+ATOM    543  O3' UNK     1       8.008  -3.897   8.200  1.00  0.00
+ATOM    544  N9  UNK     1       3.619  -3.157   8.030  1.00  0.00
+ATOM    545  C8  UNK     1       2.715  -4.196   7.950  1.00  0.00
+ATOM    546  N7  UNK     1       1.464  -3.821   8.030  1.00  0.00
+ATOM    547  C5  UNK     1       1.535  -2.434   8.170  1.00  0.00
+ATOM    548  O6  UNK     1      -0.715  -1.645   8.320  1.00  0.00
+ATOM    549  C6  UNK     1       0.503  -1.475   8.300  1.00  0.00
+ATOM    550  N1  UNK     1       1.023  -0.175   8.420  1.00  0.00
+ATOM    551  N2  UNK     1       2.662   1.442   8.550  1.00  0.00
+ATOM    552  C2  UNK     1       2.366   0.146   8.420  1.00  0.00
+ATOM    553  N3  UNK     1       3.337  -0.755   8.290  1.00  0.00
+ATOM    554  C4  UNK     1       2.855  -2.021   8.170  1.00  0.00
+ATOM    555  P   UNK     1       8.293  -3.654   9.750  1.00  0.00
+ATOM    556  O1P UNK     1       9.742  -3.482   9.970  1.00  0.00
+ATOM    557  O2P UNK     1       7.640  -4.726  10.540  1.00  0.00
+ATOM    558  O5' UNK     1       7.535  -2.275  10.000  1.00  0.00
+ATOM    559  C5' UNK     1       7.733  -1.189   9.060  1.00  0.00
+ATOM    560  C4' UNK     1       7.689   0.134   9.790  1.00  0.00
+ATOM    561  O4' UNK     1       6.290   0.506  10.000  1.00  0.00
+ATOM    562  C1' UNK     1       5.945   0.326  11.360  1.00  0.00
+ATOM    563  C2' UNK     1       7.142  -0.291  12.070  1.00  0.00
+ATOM    564  C3' UNK     1       8.298   0.156  11.190  1.00  0.00
+ATOM    565  O3' UNK     1       8.705   1.466  11.580  1.00  0.00
+ATOM    566  N1  UNK     1       4.719  -0.515  11.410  1.00  0.00
+ATOM    567  C6  UNK     1       4.803  -1.882  11.310  1.00  0.00
+ATOM    568  C5M UNK     1       3.731  -4.144  11.250  1.00  0.00
+ATOM    569  C5  UNK     1       3.696  -2.650  11.360  1.00  0.00
+ATOM    570  O4  UNK     1       1.327  -2.685  11.560  1.00  0.00
+ATOM    571  C4  UNK     1       2.390  -2.056  11.510  1.00  0.00
+ATOM    572  N3  UNK     1       2.402  -0.683  11.600  1.00  0.00
+ATOM    573  O2  UNK     1       3.424   1.348  11.650  1.00  0.00
+ATOM    574  C2  UNK     1       3.512   0.136  11.560  1.00  0.00
+ATOM    575  P   UNK     1       8.793   1.830  13.130  1.00  0.00
+ATOM    576  O1P UNK     1       9.863   2.821  13.350  1.00  0.00
+ATOM    577  O2P UNK     1       8.894   0.579  13.920  1.00  0.00
+ATOM    578  O5' UNK     1       7.368   2.500  13.380  1.00  0.00
+ATOM    579  C5' UNK     1       6.890   3.495  12.440  1.00  0.00
+ATOM    580  C4' UNK     1       6.077   4.539  13.170  1.00  0.00
+ATOM    581  O4' UNK     1       4.726   4.018  13.380  1.00  0.00
+ATOM    582  C1' UNK     1       4.553   3.670  14.740  1.00  0.00
+ATOM    583  C2' UNK     1       5.885   3.874  15.450  1.00  0.00
+ATOM    584  C3' UNK     1       6.557   4.916  14.570  1.00  0.00
+ATOM    585  O3' UNK     1       6.116   6.214  14.960  1.00  0.00
+ATOM    586  N9  UNK     1       4.056   2.268  14.790  1.00  0.00
+ATOM    587  C8  UNK     1       4.779   1.109  14.710  1.00  0.00
+ATOM    588  N7  UNK     1       4.050   0.035  14.780  1.00  0.00
+ATOM    589  C5  UNK     1       2.763   0.519  14.930  1.00  0.00
+ATOM    590  N6  UNK     1       1.361  -1.446  15.070  1.00  0.00
+ATOM    591  C6  UNK     1       1.514  -0.124  15.060  1.00  0.00
+ATOM    592  N1  UNK     1       0.428   0.653  15.180  1.00  0.00
+ATOM    593  C2  UNK     1       0.577   1.974  15.170  1.00  0.00
+ATOM    594  N3  UNK     1       1.675   2.690  15.050  1.00  0.00
+ATOM    595  C4  UNK     1       2.746   1.883  14.930  1.00  0.00
+ATOM    596  P   UNK     1       5.973   6.560  16.510  1.00  0.00
+ATOM    597  O1P UNK     1       6.257   7.991  16.730  1.00  0.00
+ATOM    598  O2P UNK     1       6.791   5.608  17.300  1.00  0.00
+ATOM    599  O5' UNK     1       4.427   6.264  16.760  1.00  0.00
+ATOM    600  C5' UNK     1       3.455   6.788  15.820  1.00  0.00
+ATOM    601  C4' UNK     1       2.183   7.156  16.550  1.00  0.00
+ATOM    602  O4' UNK     1       1.397   5.940  16.760  1.00  0.00
+ATOM    603  C1' UNK     1       1.462   5.556  18.120  1.00  0.00
+ATOM    604  C2' UNK     1       2.419   6.504  18.830  1.00  0.00
+ATOM    605  C3' UNK     1       2.350   7.742  17.950  1.00  0.00
+ATOM    606  O3' UNK     1       1.231   8.533  18.340  1.00  0.00
+ATOM    607  N1  UNK     1       1.883   4.130  18.170  1.00  0.00
+ATOM    608  C6  UNK     1       3.190   3.777  18.070  1.00  0.00
+ATOM    609  C5  UNK     1       3.578   2.474  18.120  1.00  0.00
+ATOM    610  N4  UNK     1       2.841   0.211  18.320  1.00  0.00
+ATOM    611  C4  UNK     1       2.541   1.500  18.270  1.00  0.00
+ATOM    612  N3  UNK     1       1.426   1.751  18.370  1.00  0.00
+ATOM    613  O2  UNK     1      -0.288   3.525  18.410  1.00  0.00
+ATOM    614  C2  UNK     1       0.892   3.163  18.320  1.00  0.00
+ATOM    615  P   UNK     1       0.912   8.729  19.890  1.00  0.00
+ATOM    616  O1P UNK     1       0.300  10.054  20.110  1.00  0.00
+ATOM    617  O2P UNK     1       2.133   8.439  20.680  1.00  0.00
+ATOM    618  O5' UNK     1      -0.166   7.581  20.140  1.00  0.00
+ATOM    619  C5' UNK     1      -1.260   7.434  19.200  1.00  0.00
+ATOM    620  C4' UNK     1      -2.504   6.984  19.930  1.00  0.00
+ATOM    621  O4' UNK     1      -2.426   5.538  20.140  1.00  0.00
+ATOM    622  C1' UNK     1      -2.148   5.266  21.500  1.00  0.00
+ATOM    623  C2' UNK     1      -1.931   6.596  22.210  1.00  0.00
+ATOM    624  C3' UNK     1      -2.714   7.557  21.330  1.00  0.00
+ATOM    625  O3' UNK     1      -4.085   7.538  21.720  1.00  0.00
+ATOM    626  N9  UNK     1      -0.969   4.360  21.550  1.00  0.00
+ATOM    627  C8  UNK     1       0.373   4.668  21.470  1.00  0.00
+ATOM    628  N7  UNK     1       1.165   3.630  21.550  1.00  0.00
+ATOM    629  C5  UNK     1       0.292   2.549  21.690  1.00  0.00
+ATOM    630  O6  UNK     1       1.649   0.589  21.840  1.00  0.00
+ATOM    631  C6  UNK     1       0.563   1.168  21.820  1.00  0.00
+ATOM    632  N1  UNK     1      -0.622   0.421  21.940  1.00  0.00
+ATOM    633  N2  UNK     1      -2.898   0.076  22.070  1.00  0.00
+ATOM    634  C2  UNK     1      -1.896   0.951  21.940  1.00  0.00
+ATOM    635  N3  UNK     1      -2.152   2.250  21.810  1.00  0.00
+ATOM    636  C4  UNK     1      -1.019   2.991  21.690  1.00  0.00
+ATOM    637  P   UNK     1      -4.458   7.509  23.270  1.00  0.00
+ATOM    638  O1P UNK     1      -5.732   8.222  23.490  1.00  0.00
+ATOM    639  O2P UNK     1      -3.300   7.993  24.060  1.00  0.00
+ATOM    640  O5' UNK     1      -4.655   5.947  23.520  1.00  0.00
+ATOM    641  C5' UNK     1      -5.454   5.186  22.580  1.00  0.00
+ATOM    642  C4' UNK     1      -6.196   4.089  23.310  1.00  0.00
+ATOM    643  O4' UNK     1      -5.283   2.966  23.520  1.00  0.00
+ATOM    644  C1' UNK     1      -4.898   2.909  24.880  1.00  0.00
+ATOM    645  C2' UNK     1      -5.504   4.112  25.590  1.00  0.00
+ATOM    646  C3' UNK     1      -6.702   4.430  24.710  1.00  0.00
+ATOM    647  O3' UNK     1      -7.800   3.609  25.100  1.00  0.00
+ATOM    648  N1  UNK     1      -3.411   2.869  24.930  1.00  0.00
+ATOM    649  C6  UNK     1      -2.676   4.024  24.830  1.00  0.00
+ATOM    650  C5M UNK     1      -0.479   5.224  24.770  1.00  0.00
+ATOM    651  C5  UNK     1      -1.329   3.995  24.880  1.00  0.00
+ATOM    652  O4  UNK     1       0.608   2.631  25.080  1.00  0.00
+ATOM    653  C4  UNK     1      -0.622   2.746  25.030  1.00  0.00
+ATOM    654  N3  UNK     1      -1.438   1.643  25.120  1.00  0.00
+ATOM    655  O2  UNK     1      -3.459   0.600  25.170  1.00  0.00
+ATOM    656  C2  UNK     1      -2.818   1.633  25.080  1.00  0.00
+
diff --git a/visad/python/fft_test.py b/visad/python/fft_test.py
new file mode 100644
index 0000000..6364255
--- /dev/null
+++ b/visad/python/fft_test.py
@@ -0,0 +1,18 @@
+from visad.python.JPythonMethods import *
+# load a netCDF file containg a NAST-I spectrum
+data = load("../examples/b2rlc.nc")
+# extract the spectrum
+data2 = data[2][0]
+spectrum = data2[data2.length-1]
+
+# print the VisAD MathType of the spectrum
+print spectrum.getType()
+
+# compute the Fourier transform of the spectrum
+ft = fft(spectrum)
+
+# plot the Fourier transform in red = 1, green = 0, blue = 0
+# i.e., red
+clearplot()
+plot(ft, 1, 0, 0)
+
diff --git a/visad/python/field1d.py b/visad/python/field1d.py
new file mode 100644
index 0000000..e31127e
--- /dev/null
+++ b/visad/python/field1d.py
@@ -0,0 +1,19 @@
+from visad.python.JPythonMethods import *
+
+# create an expandable list to put data in
+values = []
+
+# number of values to create
+n = 16
+
+# loop from 0 to n-1
+for i in range(n):
+
+  # compute the value at "i"
+  values.append((n - 1) * i - i * i)
+
+# create a VisAD field with the values
+data = field(values)
+
+# plot the field
+plot(data)
diff --git a/visad/python/field2d.py b/visad/python/field2d.py
new file mode 100644
index 0000000..51db8f3
--- /dev/null
+++ b/visad/python/field2d.py
@@ -0,0 +1,27 @@
+from visad.python.JPythonMethods import *
+import math
+
+# make an n x n grid of values
+n = 16
+
+# make the type (i.e. schema) for the 2 x 2 grid domain
+type = makeType("(x, y)");
+
+# make the 2 x 2 grid domain
+set = makeDomain(type, 1, n, n, 1, n, n)
+
+# create an array to hold the grid values
+values = []
+
+# nested loops for the 2-D grid
+for y in range(n):
+  for x in range(n):
+
+    # compute the grid value at (x, y)
+    values.append( math.sin(4 * x * y * 0.017) )
+
+# create a VisAD field with the grid values
+data = field(set, "value", values)
+
+# plot the field
+plot(data)
diff --git a/visad/python/gd.py b/visad/python/gd.py
new file mode 100644
index 0000000..660d2f4
--- /dev/null
+++ b/visad/python/gd.py
@@ -0,0 +1,106 @@
+from visad import *
+from visad import ScalarMap, FlowControl, Display, ConstantMap
+from visad.bom import BarbRendererJ3D,ImageRendererJ3D
+from visad.data.mcidas import PointDataAdapter, BaseMapAdapter
+from visad.python.JPythonMethods import *
+from subs import *
+from visad.util import SelectRangeWidget
+from visad.util import AnimationWidget
+
+#select conditions
+position=2
+imgDate = "2001-01-26"
+imgTime = ["04:52:00","05:52:00","06:52:00","07:52:00","08:52:00",
+           "09:52:00","10:52:00","11:52:00","12:52:00","13:52:00"]
+
+ptdata=[]
+date=[]
+img=[]
+
+for t in range(10):
+# get winds
+  request= "adde://suomi.ssec.wisc.edu/pointdata?group=cimssp&descr=wndpointhur&parm=lat lon dir spd pw &select='LAT 15 65;LON 80 180'&num=all&pos="+str(t+1)
+  print request
+  ptdata.append( PointDataAdapter(request).getData() )
+  date.append( DateTime.createDateTime(imgDate+" "+imgTime[t]+"Z") )
+
+# get images
+  request = "adde://suomi.ssec.wisc.edu/imagedata?group=cimssp&descr=wndimagehur&latlon=34 -144&size=425 1100&day="+imgDate+"&time="+imgTime[t]+"&mag=-2 -2&band=4&unit=BRIT&version=1"
+  print request
+  img.append( load(request) )
+
+# get the types of the data for everything
+lat = getRealType('LAT')
+lon = getRealType('LON')
+dir = getRealType('DIR')
+spd = getRealType('SPD')
+pw = getRealType('PW')
+
+maps = makeMaps(lat,"y",
+                lon, "x",
+                pw, "z",
+                dir, "flow1azimuth",
+                spd, "flow1radial",
+                spd, "rgb",
+                pw, "selectrange",
+                RealType.Time, "animation")
+
+# set the ranges along each axis
+maps[0].setRange(15, 65)
+maps[1].setRange(-180, -80)
+maps[2].setRange(1000, 100)
+maps[3].setRange(0,360)
+maps[4].setRange(0,1)
+
+disp = makeDisplay3D(maps)
+setBoxSize(disp,.6)
+makeCube(disp)
+showAxesScales(disp, 1)
+
+# set up the size of the barbs and stuff
+control = maps[4].getControl()
+control.setFlowScale(.03)
+control.setBarbOrientation(FlowControl.NH_ORIENTATION)
+
+
+# define the underlying basemap and put it at z=1000 in gray
+bma=BaseMapAdapter("OUTLUSAM")
+bma.setLatLonLimits(15.,65.,-180.,-80.)
+a=bma.getData()
+
+constBaseMap = (ConstantMap(.0, Display.Red), 
+            ConstantMap(.0, Display.Green), 
+            ConstantMap(.0, Display.Blue), 
+            ConstantMap(-.98, Display.ZAxis) )
+
+#domain = makeDomain(RealType.Time, date[0].getValue(), date[2].getValue(),3)
+domain = makeDomain(RealType.Time, date[0].getValue(), date[9].getValue(),10)
+ftype = FunctionType(RealType.Time, ptdata[0].getType())
+field = FieldImpl(ftype, domain)
+ftype2 = FunctionType(RealType.Time, img[0].getType())
+field2 = FieldImpl(ftype2, domain)
+
+for t in range(10):
+  field.setSample(t,ptdata[t])
+  field2.setSample(t,img[t])
+
+constImageMap = (ConstantMap(-1.0, Display.ZAxis), )
+imgRange = rangeType(img[0])
+rngMap = ScalarMap(imgRange[0], Display.RGB)
+disp.addMap(rngMap)
+
+gray=[]
+for i in xrange(255):
+  gray.append(float(i)/255.)
+colorTable = (gray, gray, gray)
+rngMap.getControl().setTable(colorTable)
+
+addData("winds", field, disp, renderer=BarbRendererJ3D() )
+addData("basemap", a, disp, constBaseMap)
+addData("image", field2, disp, constImageMap, renderer=ImageRendererJ3D() )
+
+rw = SelectRangeWidget(maps[6])
+aw = AnimationWidget(maps[7], 200)
+
+showDisplay(disp, 400, 400, "GWINDEX Winds", bottom=rw, top=aw)
+
diff --git a/visad/python/graph.py b/visad/python/graph.py
new file mode 100644
index 0000000..e776e36
--- /dev/null
+++ b/visad/python/graph.py
@@ -0,0 +1,494 @@
+"""
+Graph.py provides a collection of methods for making quick-plots of a
+data object in a variety of ways.
+
+"""
+try:  # try/except done to remove these lines from documentation
+  from org.python.core import PyTuple, PyList
+  from visad import RealType, RealTupleType, FunctionType, FieldImpl, ScalarMap, Display, LinearNDSet
+  from visad.util import AnimationWidget
+  from visad.python.JPythonMethods import *
+  from javax.swing import JFrame, JPanel
+  from java.awt import BorderLayout, FlowLayout, Font
+  __mapname='outlsupu'
+  import subs
+except:
+  pass
+
+
+def image(data, panel=None, colortable=None, width=400, height=400, title="VisAD Image"):
+  """
+  Display an image with a gray scale color mapping.  <data> contains
+  the image, <panel> is the name of a panel to put this into (default=
+  make a new one), <colortable> is a color table to use (def = gray
+  scale), <width> and <height> are the dimensions.  <title> is the
+  phrase for the title bar.  Returns a reference to the display.
+  """
+  if isinstance(data,PyList) or isinstance(data,PyTuple):
+    data = field(data)
+
+  dom_1 = RealType.getRealType(domainType(data,0) )
+  dom_2 = RealType.getRealType(domainType(data,1)) 
+  rng = RealType.getRealType(rangeType(data,0))
+  rngMap = ScalarMap(rng, Display.RGB)
+  xMap = ScalarMap(dom_1, Display.XAxis)
+  yMap = ScalarMap(dom_2, Display.YAxis)
+  maps = (xMap, yMap, rngMap)
+
+  #disp = subs.makeDisplay2D(maps)
+  disp = subs.makeDisplay(maps)
+
+  if colortable is None:
+    # make a gray-scale table
+    gray = []
+    for i in range(0,255):
+      gray.append( float(i)/255.)
+    colortable = (gray, gray, gray)
+
+  rngMap.getControl().setTable(colortable)
+
+  dr=subs.addData("brightness", data, disp)
+  subs.setBoxSize(disp, .80)
+  subs.setAspectRatio(disp, float(width)/float(height))
+  subs.showDisplay(disp,width,height,title,None,None,panel)
+  return disp
+
+def colorimage(red_data, green_data, blue_data, panel=None, colortable=None, width=400, height=400, title="VisAD Color Image"):
+  """
+  Display a color image, from three images <red_data>, <green_data>
+  and <blue_data>.  <panel> is the name of a panel to put this into
+  (default= make a new one), <colortable> is a color table to use (def
+  = gray scale), <width> and <height> are the dimensions.  <title> is
+  the phrase for the title bar.  Returns a reference to the display.
+
+  """
+
+
+  _comb_image = FieldImpl.combine( [red_data, green_data, blue_data])
+  dom_1 = RealType.getRealType(domainType(_comb_image,0))
+  dom_2 = RealType.getRealType(domainType(_comb_image,1))
+  rng = rangeType(_comb_image)
+  maps = subs.makeMaps(dom_1,'x', dom_2, 'y', rng[0], 'red',
+     rng[1], 'green', rng[2], 'blue')
+
+  disp = subs.makeDisplay(maps)
+  subs.addData('comb',_comb_image,disp)
+  subs.setBoxSize(disp, .80)
+  subs.setAspectRatio(disp, float(width)/float(height))
+  subs.showDisplay(disp,width,height,title,None,None,panel)
+  return disp
+
+def resampleImage(imagedata, maxpoints=1024):
+  """
+  Resample an image to a domain that will display as a texture
+  map.  This is only a stop-gap for now.  Default is 1024,
+  but needs to be adjusted for your card.  Return the resampled
+  image.
+  """
+  dom = getDomain(imagedata)
+  if isinstance(dom,LinearNDSet):
+    xl = dom.getLengths()[0]
+    yl = dom.getLengths()[1]
+    x0 = dom.getFirsts()[0]
+    y0 = dom.getFirsts()[1]
+    xN = dom.getLasts()[0]
+    yN = dom.getLasts()[1]
+
+  else:
+    xc = dom.getX()
+    yc = dom.getY()
+    xl = len(xc)
+    yl = len(yc)
+    x0 = xc.getFirst()
+    y0 = yc.getFirst()
+    xN = xc.getLast()
+    yN = yc.getLast()
+
+  if xl > maxpoints or yl > maxpoints:
+    print "Resampling image from",yl,"x",xl,"to",min(yl,maxpoints),"x",min(xl,maxpoints)
+    imagedata = resample(imagedata, makeDomain(dom.getType(),
+              x0, xN, min(xl, maxpoints), y0, yN, min(yl, maxpoints) ) )
+  
+  return imagedata
+
+
+#----------------------------------------------------------------------
+# mapimage displays a navigatedimage with a basemap on top
+def mapimage(imagedata, mapfile="outlsupw", panel=None, colortable=None, width=400, height=400, lat=None, lon=None, title="VisAD Image and Map"):
+  """
+  Display an image with a basemap.  <imagedata> is the image object,
+  <mapfile> is the name of the map file to use (def = outlsupw).
+  <panel> is the name of a panel to put this into (default= make a new
+  one), <colortable> is a color table to use (def = gray scale),
+  <width> and <height> are the dimensions.  <lat> and <lon> are
+  lists/tuples of the range (min->max) of the domain (def = compute
+  them). <title> is the phrase for the title bar.  Returns a reference
+  to the display.
+  """
+
+  rng = RealType.getRealType(rangeType(imagedata,0))
+  rngMap = ScalarMap(rng, Display.RGB)
+  xMap = ScalarMap(RealType.Longitude, Display.XAxis)
+  yMap = ScalarMap(RealType.Latitude, Display.YAxis)
+  maps = (xMap, yMap, rngMap)
+  dom = getDomain(imagedata)
+  xc = dom.getX()
+  yc = dom.getY()
+  xl = len(xc)
+  yl = len(yc)
+  if xl > 1024 or yl > 1024:
+    print "Resampling image from",yl,"x",xl,"to",min(yl,1024),"x",min(xl,1024)
+    imagedata = resample(imagedata, makeDomain(dom.getType(),
+                         xc.getFirst(), xc.getLast(), min(xl, 1024),
+                         yc.getFirst(), yc.getLast(), min(yl, 1024) ) )
+
+  if lat is None or lon is None:
+    c=dom.getCoordinateSystem()
+    ll = c.toReference( ( (0,0,xl,xl),(0,yl,0,yl) ) )
+    import java.lang.Double.NaN as missing
+
+    if (min(ll[0]) == missing) or (min(ll[1]) == missing) or (min(ll[1]) == max(ll[1])) or (min(ll[0]) == max(ll[0])):
+      # compute delta from mid-point...as an estimate
+      xl2 = xl/2.0
+      yl2 = yl/2.0
+      ll2 = c.toReference( ( 
+                (xl2,xl2,xl2,xl2-10, xl2+10),(yl2,yl2-10,yl2+10,yl2,yl2)))
+      dlon = abs((ll2[1][4] - ll2[1][3])*xl/40.) + abs((ll2[0][4] - ll2[0][3])*yl/40.)
+
+      dlat = abs((ll2[0][2] - ll2[0][1])*yl/40.) + abs((ll2[1][2] - ll2[1][1])*xl/40.)
+
+      lonmin = max( -180., min(ll2[1][0] - dlon, min(ll[1])))
+      lonmax = min( 360., max(ll2[1][0] + dlon, max(ll[1])))
+
+      latmin = max(-90., min(ll2[0][0] - dlat, min(ll[0])))
+      latmax = min(90., max(ll2[0][0] + dlat, min(ll[0])))
+
+      xMap.setRange(lonmin, lonmax)
+      yMap.setRange(latmin, latmax)
+      print "computed lat/lon bounds=",latmin,latmax,lonmin,lonmax
+
+    else:
+      xMap.setRange(min(ll[1]), max(ll[1]))
+      yMap.setRange(min(ll[0]), max(ll[0]))
+
+  else:
+    yMap.setRange(lat[0], lat[1])
+    xMap.setRange(lon[0], lon[1])
+
+  disp = subs.makeDisplay(maps)
+
+  if colortable is None:
+    # make a gray-scale table
+    gray = []
+    for i in range(0,255):
+      gray.append( float(i)/255.)
+    colortable = (gray, gray, gray)
+
+  rngMap.getControl().setTable(colortable)
+  mapdata = load(mapfile)
+  drm = subs.addData("basemap", mapdata, disp)
+  dr=subs.addData("addeimage", imagedata, disp)
+  subs.setBoxSize(disp, .80, clip=1)
+  subs.setAspectRatio(disp, float(width)/float(height))
+  subs.showDisplay(disp,width,height,title,None,None,panel)
+  return disp
+
+  
+#----------------------------------------------------------------------------
+# use GetAreaGUI to repeatedly show an image
+try:  # try/except done only to supress documentation
+  def __showaddeimage(event):
+    mapimage(load(event.getActionCommand()),__mapname)
+
+except:
+  pass
+
+def addeimage(mapname='outlsupu'):
+  """
+  Pop up the GUI for fetching an image via ADDE and then display it.
+  <mapname> is the filename of the basemap file to use (def=outlsupu)
+  """
+  global __mapname
+  __mapname = mapname
+  from edu.wisc.ssec.mcidas.adde import GetAreaGUI
+  __gag = GetAreaGUI("Show",0,0,actionPerformed=__showaddeimage)
+  __gag.show()
+
+#----------------------------------------------------------------------------
+# basic scatter plot between two fields.
+def scatter(data_1, data_2, panel=None, pointsize=None, width=400, height=400, xlabel=None, ylabel=None, title="VisAD Scatter", bottom=None, top=None, color=None):
+  """
+  Quick plot of a scatter diagram between <data_1> and <data_2>.
+  <panel> is the name of a panel to put this into (default= make a new
+  one), <pointsize> is the size of the scatter points (def = 1),
+  <width> and <height> are the dimensions, <xlabel> and <ylabel> are
+  the axes labels to use (def = names of data objects).  <title> is
+  the phrase for the title bar.  Returns a reference to the display.
+  """
+
+  if isinstance(data_1,PyList) or isinstance(data_1,PyTuple):
+    data_1 = field('data_1',data_1)
+
+  if isinstance(data_2,PyList) or isinstance(data_2,PyTuple):
+    data_2 = field('data_2',data_2)
+
+  rng_1 = data_1.getType().getRange().toString()
+  rng_2 = data_2.getType().getRange().toString()
+  data = FieldImpl.combine((data_1,data_2))
+  maps = subs.makeMaps(getRealType(rng_1),"x", getRealType(rng_2),"y")
+  disp = subs.makeDisplay(maps)
+  subs.addData("data", data, disp, constantMaps=subs.makeColorMap(color))
+  subs.setBoxSize(disp, .70)
+  showAxesScales(disp,1)
+  #setAxesScalesFont(maps, Font("Monospaced", Font.PLAIN, 18))
+  if pointsize is not None: subs.setPointSize(disp, pointsize)
+
+  subs.setAspectRatio(disp, float(width)/float(height))
+  subs.showDisplay(disp,width,height,title,bottom,top,panel)
+  setAxesScalesLabel(maps, [xlabel, ylabel])
+  return disp
+
+#----------------------------------------------------------------------------
+# quick look histogram - only first range component is used.
+def histogram(data, bins=20, width=400, height=400, title="VisAD Histogram", color=None, bottom=None, top=None, panel=None, clip=1):
+
+  """
+  Quick plot of a histogram from <data>.  <bins> is the number of bins
+  to use (def = 20), <panel> is the name of a panel to put this into
+  (default= make a new one), <color> is the color to use, <width> and
+  <height> are the dimensions, <title> is the phrase for the title
+  bar.  Returns a reference to the display.
+  """
+  if isinstance(data,PyList) or isinstance(data,PyTuple):
+    data = field(data)
+
+  from java.lang.Math import abs
+
+  x=[]
+  y=[]
+
+  h = hist(data, [0], [bins])
+  dom = getDomain(h)
+  d = dom.getSamples()
+  step2 = dom.getStep()/2
+
+  hmin = h[0].getValue()
+  hmax = hmin
+
+  for i in range(0,len(h)):
+    hval = h[i].getValue()
+    if hval < hmin: hmin = hval
+    if hval > hmax: hmax = hval
+
+  for i in range(0,len(h)):
+    xm = d[0][i]-step2
+    xp = d[0][i]+step2
+    x.append(xm)
+    y.append(hmin)
+    x.append(xm)
+    hval = h[i].getValue()
+    y.append(hval)
+    x.append(xp)
+    y.append(hval)
+    x.append(xp)
+    y.append(hmin)
+  
+  domt = domainType(h)
+  rngt = rangeType(h)
+
+  xaxis = ScalarMap(domt[0], Display.XAxis)
+  yaxis = ScalarMap(rngt, Display.YAxis)
+
+  yaxis.setRange(hmin, hmax + abs(hmax * .05))
+
+  disp = subs.makeDisplay( (xaxis, yaxis) )
+  subs.drawLine(disp, (x,y), mathtype=(domt[0],rngt), color=color)
+  showAxesScales(disp,1)
+  subs.setBoxSize(disp,.65,clip)
+  subs.setAspectRatio(disp, float(width)/float(height))
+  subs.showDisplay(disp,width,height,title,bottom,top,panel)
+
+  return disp
+
+
+#----------------------------------------------------------------------------
+# a simple line plot for one parameter
+def lineplot(data, panel=None, color=None, width=400, height=400, title="Line Plot"):
+  """
+  Quick plot of a line plot from <data>.  <panel> is the name of a
+  panel to put this into (default= make a new one), <color> is the
+  color to use, <width> and <height> are the dimensions, <title> is
+  the phrase for the title bar.  Returns a reference to the display.
+  """
+
+  if isinstance(data,PyTuple) or isinstance(data,PyList):
+    data = field(data)
+
+  domt = domainType(data)
+  rngt = rangeType(data)
+  xaxis = ScalarMap(domt[0], Display.XAxis)
+  if isinstance(rngt,RealTupleType):
+    yaxis = ScalarMap(rngt[0], Display.YAxis)
+  else:
+    yaxis = ScalarMap(rngt, Display.YAxis)
+
+  axes = (xaxis, yaxis)
+
+  disp = subs.makeDisplay( axes )
+  constmap = subs.makeColorMap(color)
+
+  dr=subs.addData("Lineplot", data, disp, constmap)
+  subs.setBoxSize(disp, .70)
+  showAxesScales(disp, 1)
+  setAxesScalesFont(axes, Font("Monospaced", Font.PLAIN, 18))
+
+  subs.setAspectRatio(disp, float(width)/float(height))
+  subs.showDisplay(disp,width,height,title,None,None,panel)
+  
+  return disp
+
+#----------------------------------------------------------------------------
+# a contour plot of a 2D field
+# interval = [ interval, lowvalue, highvalue, basevalue ]
+
+def contour(data, panel=None, enableLabels=1, interval=None, width=400, height=400, title="VisAD Contour Plot"):
+
+  """
+  Quick plot of a contour (isopleth) from <data>.  <panel> is the name
+  of a panel to put this into (default= make a new one),
+  <enableLabels> controls whether the contour lines will be labelled,
+  <interval[4]> is a list containing the contour interval info
+  (interval, minimum, maximum, base), <width> and <height> are the
+  dimensions, <title> is the phrase for the title bar.  Returns a
+  reference to the display.
+
+  """
+  if isinstance(data,PyList) or isinstance(data,PyTuple):
+    data = field(data)
+
+  ndom = domainDimension(data)
+  if ndom != 2:
+    print "domain dimension must be 2!"
+    return None
+
+  dom_1 = RealType.getRealType(domainType(data,0) )
+  dom_2 = RealType.getRealType(domainType(data,1)) 
+  rng = RealType.getRealType(rangeType(data,0))
+  rngMap = ScalarMap(rng, Display.IsoContour)
+  xMap = ScalarMap(dom_1, Display.XAxis)
+  yMap = ScalarMap(dom_2, Display.YAxis)
+  maps = (xMap, yMap, rngMap)
+
+  disp = subs.makeDisplay(maps)
+  ci = rngMap.getControl()
+  ci.enableLabels(enableLabels)
+  if interval is not None:
+    ci.setContourInterval(interval[0], interval[1], interval[2], interval[3])
+
+  dr=subs.addData("contours", data, disp)
+  subs.setBoxSize(disp, .80)
+  subs.setAspectRatio(disp, float(width)/float(height))
+  subs.showDisplay(disp,width,height,title,None,None,panel)
+
+  return disp
+
+
+def animation(data, width=400, height=500, title="VisAD Animation"):
+  """
+  Quick plot of an animation.  <data> is a list/tuple of iamges to
+  animate.  <width> and <height> are the size, and <title> is the
+  label for the titlebar.  Returns a reference to the display.
+  """
+
+  num_frames = len(data)
+
+  frames = RealType.getRealType("frames")
+  frames_type = RealTupleType( frames )
+
+  image_type = data[0].getType()
+  ndom = domainDimension(data[0])
+
+  if ndom != 2:
+    print "domain dimension must be 2!"
+    return None
+
+  dom_1 = RealType.getRealType(domainType(data[0],0) )
+  dom_2 = RealType.getRealType(domainType(data[0],1)) 
+
+  nrng = rangeDimension(data[0])
+  if (nrng != 3) and (nrng != 1):
+    print "range dimension must be 1 or 3"
+    return None
+
+  # now create display scalar maps
+  maps = None
+  rng_1 = rangeType(data[0],0)
+  if nrng == 3:
+    rng_2 = rangeType(data[0],1)
+    rng_3 = rangeType(data[0],2)
+    rng_red = None
+    if (rng_1 == "Red"): rng_red = rng_1
+    if (rng_2 == "Red"): rng_red = rng_2
+    if (rng_3 == "Red"): rng_red = rng_3
+    rng_green = None
+    if (rng_1 == "Green"): rng_green = rng_1
+    if (rng_2 == "Green"): rng_green = rng_2
+    if (rng_3 == "Green"): rng_green = rng_3
+    rng_blue = None
+    if (rng_1 == "Blue"): rng_blue = rng_1
+    if (rng_2 == "Blue"): rng_blue = rng_2
+    if (rng_3 == "Blue"): rng_blue = rng_3
+
+    if (rng_red is None) or (rng_green is None) or (rng_blue is None):
+      print "3 Range components must be Red, Green and Blue"
+
+    else:
+      maps = subs.makeMaps(dom_1,"x", dom_2,"y", RealType.getRealType(rng_red), "red", RealType.getRealType(rng_green), "green", RealType.getRealType(rng_blue), "blue")
+
+  else:
+    maps = subs.makeMaps(dom_1,"x", dom_2, "y", RealType.getRealType(rng_1), "rgb")
+
+  frame_images = FunctionType(frames_type, image_type)
+  frame_set = makeDomain(frames, 0, num_frames-1, num_frames)
+  frame_seq = FieldImpl(frame_images, frame_set)
+
+  for i in range(0,num_frames):
+    frame_seq.setSample(i, data[i])
+
+  disp = subs.makeDisplay(maps)
+  animap = ScalarMap(frames, Display.Animation)
+  disp.addMap(animap)
+  refimg = subs.addData("VisAD_Animation", frame_seq, disp)
+  widget = AnimationWidget(animap, 500) 
+  subs.setAspectRatio(disp, float(width)/float(height))
+  myAnimFrame(disp, widget, width, height, "Animation")
+
+  return disp
+
+
+class myAnimFrame:
+  """
+  Supporting class for animations.
+  """
+  def desty(self, event):
+    self.display.destroy()
+    self.frame.dispose()
+
+  def __init__(self, display, widget, width, height, title):
+    from javax.swing import JFrame, JPanel
+    from java.awt import BorderLayout, FlowLayout
+    self.display = display
+    self.panel = JPanel(BorderLayout())
+    self.panel2 = JPanel(FlowLayout())
+    self.panel2.add(widget)
+    self.panel.add("North", self.panel2)
+    self.panel.add("Center",self.display.getComponent())
+
+    self.frame = JFrame(title, windowClosing=self.desty)
+    self.pane = self.frame.getContentPane()
+    self.pane.add(self.panel)
+
+    self.frame.setSize(width,height)
+    self.frame.pack()
+    self.frame.show()
+
diff --git a/visad/python/hist_test.py b/visad/python/hist_test.py
new file mode 100644
index 0000000..536ed02
--- /dev/null
+++ b/visad/python/hist_test.py
@@ -0,0 +1,11 @@
+from visad.python.JPythonMethods import *
+# load a McIDAS area file
+area = load("../examples/AREA0008")
+
+# compute a 2-D histogram of the first two bands of the area
+histogram = hist(area, [0, 1])
+
+# plot the histogram
+clearplot()
+plot(histogram)
+
diff --git a/visad/python/image_line.py b/visad/python/image_line.py
new file mode 100644
index 0000000..a747d06
--- /dev/null
+++ b/visad/python/image_line.py
@@ -0,0 +1,74 @@
+from visad.python.JPythonMethods import *
+import subs
+from visad.java2d import *
+from visad import DataReferenceImpl, CellImpl, AxisScale
+from visad.util import VisADSlider
+from javax.swing import JFrame, JPanel
+from java.awt import BorderLayout, GridLayout, Font
+
+image = load("AREA0007")
+print "Done reading data..."
+
+dom = getDomain(image)
+d = domainType(image)
+r = rangeType(image)
+
+# max lines & elements of image
+NELE = dom.getX().getLength()
+LINES = dom.getY().getLength()
+
+# subs for image in display-1
+m = subs.makeMaps(d[0],"x",d[1],"y",r[0],"rgb")
+d1 = subs.makeDisplay(m)
+subs.setBoxSize(d1,.80)
+
+# add the image to the display
+refimg = subs.addData("image", image, d1)
+
+# now the second panel
+m2 = subs.makeMaps(d[0],"x",r[0],"y")
+m2[1].setRange(0, 255)
+d2 = subs.makeDisplay(m2)
+subs.setBoxSize(d2,.80)
+
+# get the desired format of the Data (line->(element->value))
+# factoring works because image has a 2-D rectangular sampling
+byline = domainFactor(image,d[1])
+ref2 = subs.addData("imageline", byline[0], d2)
+
+# set up a dummy reference so we can put the line onto the display
+usref = subs.addData("line", None, d1)
+
+# define an inner-type CellImpl class to handle changes
+class MyCell(CellImpl):
+ def doAction(this):
+  line = (userline.getData()).getValue()
+  iline = (LINES-1) - line
+  pts = subs.makeLine( (d[1], d[0]), ((iline,iline),(0,NELE)))
+  usref.setData(pts)
+  ff = byline[int(line)]
+  ref2.setData(ff)
+
+# make a DataReference that we can use to change the value of "line"
+userline = DataReferenceImpl("userline")
+slide = VisADSlider("imgline",0,LINES,0,1.0,userline,d[1])
+
+cell = MyCell();
+cell.addReference(userline)
+
+# change the scale label on x axis
+showAxesScales(d2,1)
+
+# display everything...
+frame = JFrame("Test T7")
+pane = frame.getContentPane()
+pane.setLayout(BorderLayout())
+# GridLayout with 1 row, 2 columns, 5 pixel horiz and vert gaps
+panel = JPanel(GridLayout(1,2,5,5))
+panel.add(d1.getComponent())
+panel.add(d2.getComponent())
+pane.add("Center",panel)
+pane.add("North",slide)
+frame.setSize(800,500)
+frame.setVisible(1)
+
diff --git a/visad/python/image_line_client.py b/visad/python/image_line_client.py
new file mode 100644
index 0000000..08b16d7
--- /dev/null
+++ b/visad/python/image_line_client.py
@@ -0,0 +1,39 @@
+from visad.python.JPythonMethods import *
+from visad.util import VisADSlider, ClientServer
+from javax.swing import JFrame, JPanel
+from java.awt import BorderLayout, GridLayout, Font
+
+# connect to server - modify "localhost" to actual server name
+client = ClientServer.connectToServer("localhost", "Jython")
+
+# fetch displays and data from the server
+d1 = ClientServer.getClientDisplay(client, 0)
+d2 = ClientServer.getClientDisplay(client, 1)
+refimg = client.getDataReference(0)
+userline = client.getDataReference(1)
+
+# get image in order to get its size and type schema
+image = refimg.getData()
+dom = getDomain(image)
+LINES = dom.getY().getLength()
+d = domainType(image)
+
+# create line slider for client user
+slide = VisADSlider("imgline",0,LINES,0,1.0,userline,d[1])
+
+showAxesScales(d2,1)
+
+# display everything...
+frame = JFrame("Test T8 Client")
+pane = frame.getContentPane()
+pane.setLayout(BorderLayout())
+
+# GridLayout with 1 row, 2 columns, 5 pixel horiz and vert gaps
+panel = JPanel(GridLayout(1,2,5,5))
+panel.add(d1.getComponent())
+panel.add(d2.getComponent())
+pane.add("Center",panel)
+pane.add("North",slide)
+frame.setSize(800,500)
+frame.setVisible(1)
+
diff --git a/visad/python/image_line_server.py b/visad/python/image_line_server.py
new file mode 100644
index 0000000..eca014d
--- /dev/null
+++ b/visad/python/image_line_server.py
@@ -0,0 +1,82 @@
+from visad.python.JPythonMethods import *
+import subs
+from visad.java2d import *
+from visad import DataReferenceImpl, CellImpl, AxisScale
+from visad.util import VisADSlider
+from javax.swing import JFrame, JPanel
+from java.awt import BorderLayout, GridLayout, Font
+
+image = load("AREA0007")
+print "Done reading data..."
+
+dom = getDomain(image)
+d = domainType(image)
+r = rangeType(image)
+
+# max lines & elements of image
+NELE = dom.getX().getLength()
+LINES = dom.getY().getLength()
+
+# subs for image in display-1
+m = subs.makeMaps(d[0],"x",d[1],"y",r[0],"rgb")
+d1 = subs.makeDisplay(m)
+subs.setBoxSize(d1,.80)
+
+# add the image to the display
+refimg = subs.addData("image", image, d1)
+
+# now the second panel
+m2 = subs.makeMaps(d[0],"x",r[0],"y")
+m2[1].setRange(0, 255)
+d2 = subs.makeDisplay(m2)
+subs.setBoxSize(d2,.80)
+
+# get the desired format of the Data (line->(element->value))
+# factoring works because image has a 2-D rectangular sampling
+byline = domainFactor(image,d[1])
+ref2 = subs.addData("imageline", byline[0], d2)
+
+# set up a dummy reference so we can put the line onto the display
+usref = subs.addData("line", None, d1)
+
+# define an inner-type CellImpl class to handle changes
+class MyCell(CellImpl):
+ def doAction(this):
+  line = (userline.getData()).getValue()
+  iline = (LINES-1) - line
+  pts = subs.makeLine( (d[1], d[0]), ((iline,iline),(0,NELE)))
+  usref.setData(pts)
+  ff = byline[int(line)]
+  ref2.setData(ff)
+
+# make a DataReference that we can use to change the value of "line"
+userline = DataReferenceImpl("userline")
+slide = VisADSlider("imgline",0,LINES,0,1.0,userline,d[1])
+
+cell = MyCell();
+cell.addReference(userline)
+
+# change the scale label on x axis
+showAxesScales(d2,1)
+
+# display everything...
+frame = JFrame("Test T8 Server")
+pane = frame.getContentPane()
+pane.setLayout(BorderLayout())
+# GridLayout with 1 row, 2 columns, 5 pixel horiz and vert gaps
+panel = JPanel(GridLayout(1,2,5,5))
+panel.add(d1.getComponent())
+panel.add(d2.getComponent())
+pane.add("Center",panel)
+pane.add("North",slide)
+frame.setSize(800,500)
+frame.setVisible(1)
+
+from visad.util import ClientServer
+# add displays and data to the server
+server = ClientServer.startServer("Jython")
+server.addDisplay(d1)
+server.addDisplay(d2)
+server.addDataReference(refimg)
+server.addDataReference(userline)
+
diff --git a/visad/python/image_text_test.py b/visad/python/image_text_test.py
new file mode 100644
index 0000000..e5858a2
--- /dev/null
+++ b/visad/python/image_text_test.py
@@ -0,0 +1,46 @@
+from visad.python.JPythonMethods import *
+import subs, graph
+from visad import Real, RealTuple, RealType, DataReferenceImpl
+from visad.java3d import DefaultRendererJ3D
+from visad.bom import DiscoverableZoom
+
+a=load("../data/mcidas/AREA0007")
+ds = a.getDomainSet()
+xdim = ds.getX().getLength()
+ydim = ds.getY().getLength()
+print "Image size = ",ydim," by",xdim
+
+cs = ds.getCoordinateSystem()
+
+rangetype = a.getType().getRange()
+maps=subs.makeMaps(RealType.Latitude, "y", RealType.Longitude, "x",
+                   rangetype[0], "text")
+disp=subs.makeDisplay3D(maps)
+
+pcontrol = disp.getProjectionControl()
+dzoom = DiscoverableZoom()
+pcontrol.addControlListener(dzoom)
+
+tcontrol = maps[2].getControl()
+tcontrol.setAutoSize(1)
+
+rends = []
+
+for i in range (ydim/2, ydim/2 + 20):
+  for j in range (xdim/2, xdim/2 + 20):
+
+    ref = DataReferenceImpl("data")
+    latlon = cs.toReference( ( (j,), (i,) ))
+    tuple = RealTuple( ( 
+           a[i * xdim + j][0], 
+           Real( RealType.Latitude, latlon[0][0]), 
+           Real( RealType.Longitude, latlon[1][0]) ) )
+
+    ref.setData(tuple)
+    rend = DefaultRendererJ3D()
+    rends.append(rend)
+    disp.addReferences(rend, ref, None)
+
+dzoom.setRenderers(rends, .1)
+subs.showDisplay(disp)
+
diff --git a/visad/python/make_data.py b/visad/python/make_data.py
new file mode 100644
index 0000000..8a58432
--- /dev/null
+++ b/visad/python/make_data.py
@@ -0,0 +1,34 @@
+from visad.python.JPythonMethods import *
+from visad import *
+from visad.data.netcdf import Plain
+
+# construct file interface for writing data
+file = Plain()
+
+# create generic real file
+a = Real(1.0)
+file.save("generic_real.nc", a, 1)
+
+# create typed real file
+t = RealType("temp")
+rt0 = Real(t, 0.0)
+file.save("real0.nc", rt0, 1)
+rt1 = Real(t, 1.0)
+file.save("real1.nc", rt1, 1)
+rt2 = Real(t, 2.0)
+file.save("real2.nc", rt2, 1)
+rt3 = Real(t, 3.0)
+file.save("real3.nc", rt3, 1)
+
+# create typed real vector file
+p = RealType("pres")
+h = RealType("hum")
+rp = Real(p, 2.0)
+rh = Real(h, -1.0)
+reals = [rt1, rp, rh]
+v = RealTuple(reals)
+file.save("vector.nc", v, 1)
+
+values = [-2.0, 0.0, 1.0, 0.0, -2.0]
+f = field("temp", values)
+file.save("field.nc", f, 1)
\ No newline at end of file
diff --git a/visad/python/matrix_test.py b/visad/python/matrix_test.py
new file mode 100644
index 0000000..3d04c79
--- /dev/null
+++ b/visad/python/matrix_test.py
@@ -0,0 +1,19 @@
+from visad.python.JPythonMethods import *
+# construct a 2 x 2 matrix in a VisAD Field
+matrix = field([[1, 2], [1, 3]])
+# construct a 2 vector in a VisAD Field
+vector = field([2, 1])
+
+# solve the linear system
+solution = solve(matrix, vector)
+
+# print the solution
+print solution[0], solution[1]
+
+# prints 4.0 -1.0
+#
+# note
+# 1  2       4       2
+#        *       =
+# 1  3      -1       1
+
diff --git a/visad/python/mcidas_test.py b/visad/python/mcidas_test.py
new file mode 100644
index 0000000..d749c5f
--- /dev/null
+++ b/visad/python/mcidas_test.py
@@ -0,0 +1,23 @@
+from visad.python.JPythonMethods import *
+# load a McIDAS area file
+area = load("../examples/AREA2001")
+print area.length
+
+# make a scratch across the top of the area
+for i in range(20000, 21000):
+	area[i] = 0
+
+# load a McIDAS map file
+map = load("../examples/OUTLSUPW")
+print map.length
+
+# print area pixel values at some map locations
+for j in range(map.length/200):
+	i = 200 * j
+	print "area = ", area[map[i]], " at ", map[i]
+
+# plot the area overlaid with the map
+clearplot()
+plot(area)
+plot(map)
+
diff --git a/visad/python/mike.py b/visad/python/mike.py
new file mode 100644
index 0000000..04f85c9
--- /dev/null
+++ b/visad/python/mike.py
@@ -0,0 +1,39 @@
+from visad.python.JPythonMethods import *
+from visad import *
+import math
+
+# n time steps of an n x n x n grid
+n=16
+
+# make the type (i.e., schema) for the time sequence of grids
+ftype = makeType(" (time -> ( (x, y, z) -> value) )")
+
+# make the (1,...,n) sampling for the time domain
+fdom = makeDomain("time", 1, n, n)
+
+# make the (1,...,n) x (1,...,n) x (1,...,n) sampling for the
+#   grid domain
+gdom = makeDomain("(x, y, z)", 1, n, n,  1, n, n,  1, n, n)
+
+# create the time sequence data object (a VisAD FieldImpl)
+seq = FieldImpl(ftype, fdom)
+
+# loop for each time step in the sequence
+for i in range(0, n):
+
+  # create an array to hold the grid values for time step i
+  v = []
+
+  # nested loops for 3-D grid of values
+  for z in range(0, n):
+    for y in range(0, n):
+      for x in range(0, n):
+
+        # compute the grid value at (x, y, z)
+        v.append( math.sin(i*x*y*z*0.0174533/n) )
+
+  # create a grid field as the i-th sample of the time sequence
+  seq.setSample(i, field(gdom, "value", v) )
+
+# plot the time sequence of grids
+plot(seq)
diff --git a/visad/python/resample_test.py b/visad/python/resample_test.py
new file mode 100644
index 0000000..ef75878
--- /dev/null
+++ b/visad/python/resample_test.py
@@ -0,0 +1,23 @@
+from visad.python.JPythonMethods import *
+# load two McIDAS area files
+area7 = load("../examples/AREA0007")
+area8 = load("../examples/AREA0008")
+
+# extract one band from area8
+area8 = area8.extract(0)
+
+# get set of area8 pixel locations
+set = area8.getDomainSet()
+
+# resample area7 to area8 locations
+area9 = area7.resample(set)
+
+# resample area7 to area8 locations one pixel at a time
+# and compute difference with all at once resample
+#   NOTE - this is slow
+for i in range(set.length):
+	area9[i] = area9[i] - area7[set[i]]
+
+clearplot()
+plot(area9)
+
diff --git a/visad/python/slice.py b/visad/python/slice.py
new file mode 100644
index 0000000..2e6d136
--- /dev/null
+++ b/visad/python/slice.py
@@ -0,0 +1,91 @@
+from visad.python.JPythonMethods import *
+import subs
+from visad import *
+from visad.util import VisADSlider
+
+# load our neep602_mystery data set, available from
+# ftp://www.ssec.wisc.edu/pub/visad-2.0/course/neep602_mystery
+a = load("neep602_mystery")
+
+# get one 3-D grid from the data set, in this case the 8-th
+grid = a[7]
+
+# get the type (i.e., schema) information for the 3-D grid
+d = domainType(grid)
+r = rangeType(grid)
+
+# get the spatial sampling of the 3-D grid
+set = getDomain(grid)
+
+# assuming the spatial sampling is rectangular, get its factors
+xset = set.getX()
+yset = set.getY()
+zset = set.getZ()
+
+# get units, coordinate system and errors for the sampling
+units = set.getSetUnits()
+cs = set.getCoordinateSystem()
+errors = set.getSetErrors()
+
+# get the actual x, y and z values of the spatial sampling
+xv = xset.getSamples()[0]
+yv = yset.getSamples()[0]
+zv = zset.getSamples()[0]
+
+# create a display with mappings for our grid
+maps = subs.makeMaps(d[0], "y", d[1], "x", d[2], "z", r, "rgb")
+maps[2].setRange(zv[0], zv[-1])
+display = subs.makeDisplay(maps)
+
+# create an interactive slider for choosing a grid level
+level = DataReferenceImpl("height")
+slider = VisADSlider("level", int(1000.0 * zv[0]),
+                     int(1000.0 * zv[-1]),
+                     int(1000.0 * zv[0]), 0.001,
+                     level, d[2])
+
+# define a function for extracting a grid level at height 'z'
+def makeSlice(z):
+  # initialize arrays for a 2-D grid at height 'z'
+  xs = []
+  ys = []
+  zs = []
+  # loops for the x and y values from the original 3-D grid
+  for y in yv:
+    for x in xv:
+      # set x and y locations for the 2-D grid
+      xs.append(x)
+      ys.append(y)
+      # set constant z heights in the 2-D grid
+      zs.append(z)
+      # or, for a curved slice, use this instead:
+      # zs.append(z + 0.04 * ((x-xv[9])*(x-xv[9])+(y-yv[9])*(y-yv[9])))
+  # create a 2-D grid embedded at height 'z' in 3-D space
+  slice_set = Gridded3DSet(d, [xs, ys, zs], len(xv), len(yv),
+                           cs, units, errors)
+  # resample our original 3-D grid to the 2-D grid
+  return grid.resample(slice_set)
+
+# add an initial slice to the display
+slice = subs.addData("slice", makeSlice(zv[0]), display)
+
+# a little program to run whenever the user moves the slider
+#   it displays a 2-D grid at the height defined by the slider
+class MyCell(CellImpl):
+  def doAction(this):
+    z = level.getData().getValue()
+    slice.setData(makeSlice(z))
+
+# connect the slider to the little program
+cell = MyCell();
+cell.addReference(level)
+
+# turn on axis scales in the display
+showAxesScales(display, 1)
+
+# show the display on the screen, along with the slider
+subs.showDisplay(display, top=slider)
+
+# ordinary plot of the 3-D grid for comparison
+plot(grid)
+
diff --git a/visad/python/slice_contour.py b/visad/python/slice_contour.py
new file mode 100644
index 0000000..cf66645
--- /dev/null
+++ b/visad/python/slice_contour.py
@@ -0,0 +1,93 @@
+from visad.python.JPythonMethods import *
+import subs
+from visad import *
+from visad.util import VisADSlider
+from visad.util import ContourWidget
+
+# load our neep602_mystery data set, available from
+# ftp://www.ssec.wisc.edu/pub/visad-2.0/course/neep602_mystery
+a = load("neep602_mystery")
+
+# get one 3-D grid from the data set, in this case the 8-th
+grid = a[7]
+
+# get the type (i.e., schema) information for the 3-D grid
+d = domainType(grid)
+r = rangeType(grid)
+
+# get the spatial sampling of the 3-D grid
+set = getDomain(grid)
+
+# assuming the spatial sampling is rectangular, get its factors
+xset = set.getX()
+yset = set.getY()
+zset = set.getZ()
+
+# get units, coordinate system and errors for the sampling
+units = set.getSetUnits()
+cs = set.getCoordinateSystem()
+errors = set.getSetErrors()
+
+# get the actual x, y and z values of the spatial sampling
+xv = xset.getSamples()[0]
+yv = yset.getSamples()[0]
+zv = zset.getSamples()[0]
+
+# create a display with mappings for our grid
+maps = subs.makeMaps(d[0], "y", d[1], "x", d[2], "z", r, "contour")
+maps[2].setRange(zv[0], zv[-1])
+display = subs.makeDisplay(maps)
+contourwidget = ContourWidget(maps[3])
+
+# create an interactive slider for choosing a grid level
+level = DataReferenceImpl("height")
+slider = VisADSlider("level", int(1000.0 * zv[0]),
+                     int(1000.0 * zv[-1]),
+                     int(1000.0 * zv[0]), 0.001,
+                     level, d[2])
+
+# define a function for extracting a grid level at height 'z'
+def makeSlice(z):
+  # initialize arrays for a 2-D grid at height 'z'
+  xs = []
+  ys = []
+  zs = []
+  # loops for the x and y values from the original 3-D grid
+  for y in yv:
+    for x in xv:
+      # set x and y locations for the 2-D grid
+      xs.append(x)
+      ys.append(y)
+      # set constant z heights in the 2-D grid
+      zs.append(z)
+      # or, for a curved slice, use this instead:
+      # zs.append(z + 0.04 * ((x-xv[9])*(x-xv[9])+(y-yv[9])*(y-yv[9])))
+  # create a 2-D grid embedded at height 'z' in 3-D space
+  slice_set = Gridded3DSet(d, [xs, ys, zs], len(xv), len(yv),
+                           cs, units, errors)
+  # resample our original 3-D grid to the 2-D grid
+  return grid.resample(slice_set)
+
+# add an initial slice to the display
+slice = subs.addData("slice", makeSlice(zv[0]), display)
+
+# a little program to run whenever the user moves the slider
+#   it displays a 2-D grid at the height defined by the slider
+class MyCell(CellImpl):
+  def doAction(this):
+    z = level.getData().getValue()
+    slice.setData(makeSlice(z))
+
+# connect the slider to the little program
+cell = MyCell();
+cell.addReference(level)
+
+# turn on axis scales in the display
+showAxesScales(display, 1)
+
+# show the display on the screen, along with the slider
+subs.showDisplay(display, top=slider, bottom=contourwidget)
+
+# ordinary plot of the 3-D grid for comparison
+plot(grid)
+
diff --git a/visad/python/slice_rgb.py b/visad/python/slice_rgb.py
new file mode 100644
index 0000000..fd62f0e
--- /dev/null
+++ b/visad/python/slice_rgb.py
@@ -0,0 +1,93 @@
+from visad.python.JPythonMethods import *
+import subs
+from visad import *
+from visad.util import VisADSlider
+from visad.util import LabeledColorWidget
+
+# load our neep602_mystery data set, available from
+# ftp://www.ssec.wisc.edu/pub/visad-2.0/course/neep602_mystery
+a = load("neep602_mystery")
+
+# get one 3-D grid from the data set, in this case the 8-th
+grid = a[7]
+
+# get the type (i.e., schema) information for the 3-D grid
+d = domainType(grid)
+r = rangeType(grid)
+
+# get the spatial sampling of the 3-D grid
+set = getDomain(grid)
+
+# assuming the spatial sampling is rectangular, get its factors
+xset = set.getX()
+yset = set.getY()
+zset = set.getZ()
+
+# get units, coordinate system and errors for the sampling
+units = set.getSetUnits()
+cs = set.getCoordinateSystem()
+errors = set.getSetErrors()
+
+# get the actual x, y and z values of the spatial sampling
+xv = xset.getSamples()[0]
+yv = yset.getSamples()[0]
+zv = zset.getSamples()[0]
+
+# create a display with mappings for our grid
+maps = subs.makeMaps(d[0], "y", d[1], "x", d[2], "z", r, "rgb")
+maps[2].setRange(zv[0], zv[-1])
+display = subs.makeDisplay(maps)
+rgbwidget = LabeledColorWidget(maps[3])
+
+# create an interactive slider for choosing a grid level
+level = DataReferenceImpl("height")
+slider = VisADSlider("level", int(1000.0 * zv[0]),
+                     int(1000.0 * zv[-1]),
+                     int(1000.0 * zv[0]), 0.001,
+                     level, d[2])
+
+# define a function for extracting a grid level at height 'z'
+def makeSlice(z):
+  # initialize arrays for a 2-D grid at height 'z'
+  xs = []
+  ys = []
+  zs = []
+  # loops for the x and y values from the original 3-D grid
+  for y in yv:
+    for x in xv:
+      # set x and y locations for the 2-D grid
+      xs.append(x)
+      ys.append(y)
+      # set constant z heights in the 2-D grid
+      zs.append(z)
+      # or, for a curved slice, use this instead:
+      # zs.append(z + 0.04 * ((x-xv[9])*(x-xv[9])+(y-yv[9])*(y-yv[9])))
+  # create a 2-D grid embedded at height 'z' in 3-D space
+  slice_set = Gridded3DSet(d, [xs, ys, zs], len(xv), len(yv),
+                           cs, units, errors)
+  # resample our original 3-D grid to the 2-D grid
+  return grid.resample(slice_set)
+
+# add an initial slice to the display
+slice = subs.addData("slice", makeSlice(zv[0]), display)
+
+# a little program to run whenever the user moves the slider
+#   it displays a 2-D grid at the height defined by the slider
+class MyCell(CellImpl):
+  def doAction(this):
+    z = level.getData().getValue()
+    slice.setData(makeSlice(z))
+
+# connect the slider to the little program
+cell = MyCell();
+cell.addReference(level)
+
+# turn on axis scales in the display
+showAxesScales(display, 1)
+
+# show the display on the screen, along with the slider
+subs.showDisplay(display, top=slider, bottom=rgbwidget)
+
+# ordinary plot of the 3-D grid for comparison
+plot(grid)
+
diff --git a/visad/python/stroud.py b/visad/python/stroud.py
new file mode 100644
index 0000000..6006efc
--- /dev/null
+++ b/visad/python/stroud.py
@@ -0,0 +1,154 @@
+from visad.python.JPythonMethods import *
+from visad import *
+from visad.util import Delay
+from subs import *
+import math
+
+# make the type (i.e., schema) for the shape
+type = makeType("(x, y, z)");
+
+# initialize the location of the shape
+loc = [0, 2, 0]
+
+# initialize the "up" direction for the shape
+up = [0, 1, 0]
+
+# initialize the direction for the shape is facing
+face = [0, 0, 1]
+
+# initialize the direction to the shape's right
+right = [1, 0, 0]
+
+# initialize the arm angle for the shape
+arm = 0
+
+# define a function for normalizing a vector
+def normalize(a):
+  norm = math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2])
+  a[0] = a[0] / norm
+  a[1] = a[1] / norm
+  a[2] = a[2] / norm
+
+# define a function for a cross product of vectors
+def cross(a, b):
+  return [a[1]*b[2]-a[2]*b[1],
+          a[2]*b[0]-a[0]*b[2],
+          a[0]*b[1]-a[1]*b[0]]
+
+# define a function for rotating vector "a" around
+#   vector "b" by amount "s"
+def rotate(a, b, s):
+  r = cross(a, b)
+  normalize(r)
+  a[0] = a[0] + s * r[0]
+  a[1] = a[1] + s * r[1]
+  a[2] = a[2] + s * r[2]
+  normalize(a)
+
+# define a function for creating a simple human shape
+#   as a VisAD UnionSet data object
+def makeBody():
+  c = cross(up, face)
+  right[0] = c[0]
+  right[1] = c[1]
+  right[2] = c[2]
+  sets = []
+
+  # make torso and head
+  samples = []
+  for i in range(3):
+    samples.append(
+      [loc[i]-2*up[i],
+       loc[i],
+       loc[i]+up[i],
+       loc[i]+2*up[i]+right[i],
+       loc[i]+2*up[i]-right[i],
+       loc[i]+up[i]])
+  sets.append(Gridded3DSet(type, samples, len(samples[0])))
+
+  # make arms
+  samples = []
+  for i in range(3):
+    samples.append(
+      [loc[i]+up[i]-2*right[i]+2*arm*face[i],
+       loc[i]-right[i]+arm*face[i],
+       loc[i],
+       loc[i]+right[i]+arm*face[i],
+       loc[i]+up[i]+2*right[i]+2*arm*face[i]])
+  sets.append(Gridded3DSet(type, samples, len(samples[0])))
+
+  # make legs
+  samples = []
+  for i in range(3):
+    samples.append(
+      [loc[i]-4*up[i]-right[i],
+       loc[i]-3*up[i]-right[i],
+       loc[i]-2*up[i],
+       loc[i]-3*up[i]+right[i],
+       loc[i]-4*up[i]+right[i]])
+  sets.append(Gridded3DSet(type, samples, len(samples[0])))
+
+  set = UnionSet(type, sets)
+  return set
+
+# create the initial shape
+set = makeBody()
+
+# connect the shape to a VisAD DataReference
+ref = DataReferenceImpl("set")
+ref.setData(set)
+
+# make the display mapings for the shape
+maps = makeMaps(type[0], "x", type[1], "y", type[2], "z")
+maps[0].setRange(-8, 8)
+maps[1].setRange(-8, 8)
+maps[2].setRange(-8, 8)
+
+# make a VisAD display with those mappings
+display = makeDisplay3D(maps)
+
+# connect the DataReference to the display
+display.addReference(ref)
+
+# show the display on the screen
+showDisplay(display)
+
+# initialize some vectors use for rotating the shape
+rot = [0.2, -0.6, 0.7]
+normalize(rot)
+rot2 = [-0.5, 0.3, 0.4]
+normalize(rot2)
+funny = [1, 0, 0]
+arminc = 0
+
+while 1:
+  rotate(rot2, rot, 0.043)
+
+  # rotate the shape "up" direction
+  rotate(up, rot2, 0.1)
+  rotate(funny, rot, 0.08)
+
+  # compute new "face" direction
+  face = cross(up, funny)
+  normalize(face)
+
+  # compute new location of shape
+  rotate(loc, face, 0.9)
+  loc[0] = 4*loc[0]
+  loc[1] = 4*loc[1]
+  loc[2] = 4*loc[2]
+
+  # compute new arm angle
+  arm = 1.0 * math.sin(arminc)
+  arminc = arminc + 0.4
+
+  # make the new shape
+  set = makeBody()
+
+  # set the new shape in the DataReference connected
+  #   to the display
+  ref.setData(set)
+
+  # wait 50 milleseconds
+  Delay(50)
+
diff --git a/visad/python/stroud2.py b/visad/python/stroud2.py
new file mode 100644
index 0000000..525629d
--- /dev/null
+++ b/visad/python/stroud2.py
@@ -0,0 +1,161 @@
+from visad.python.JPythonMethods import *
+from visad import *
+from visad.util import Delay
+from subs import *
+import math
+
+# make the type (i.e., schema) for the shape
+type = makeType("(x, y, z)")
+ftype = makeType("(index->Set(x, y, z))")
+itype = makeType("index")
+index_set = Integer1DSet(itype, 100)
+function = FieldImpl(ftype, index_set)
+index = 0
+
+# initialize the location of the shape
+loc = [0, 2, 0]
+
+# initialize the "up" direction for the shape
+up = [0, 1, 0]
+
+# initialize the direction for the shape is facing
+face = [0, 0, 1]
+
+# initialize the direction to the shape's right
+right = [1, 0, 0]
+
+# initialize the arm angle for the shape
+arm = 0
+
+# define a function for normalizing a vector
+def normalize(a):
+  norm = math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2])
+  a[0] = a[0] / norm
+  a[1] = a[1] / norm
+  a[2] = a[2] / norm
+
+# define a function for a cross product of vectors
+def cross(a, b):
+  return [a[1]*b[2]-a[2]*b[1],
+          a[2]*b[0]-a[0]*b[2],
+          a[0]*b[1]-a[1]*b[0]]
+
+# define a function for rotating vector "a" around
+#   vector "b" by amount "s"
+def rotate(a, b, s):
+  r = cross(a, b)
+  normalize(r)
+  a[0] = a[0] + s * r[0]
+  a[1] = a[1] + s * r[1]
+  a[2] = a[2] + s * r[2]
+  normalize(a)
+
+# define a function for creating a simple human shape
+#   as a VisAD UnionSet data object
+def makeBody():
+  c = cross(up, face)
+  right[0] = c[0]
+  right[1] = c[1]
+  right[2] = c[2]
+  sets = []
+
+  # make torso and head
+  samples = []
+  for i in range(3):
+    samples.append(
+      [loc[i]-2*up[i],
+       loc[i],
+       loc[i]+up[i],
+       loc[i]+2*up[i]+right[i],
+       loc[i]+2*up[i]-right[i],
+       loc[i]+up[i]])
+  sets.append(Gridded3DSet(type, samples, len(samples[0])))
+
+  # make arms
+  samples = []
+  for i in range(3):
+    samples.append(
+      [loc[i]+up[i]-2*right[i]+2*arm*face[i],
+       loc[i]-right[i]+arm*face[i],
+       loc[i],
+       loc[i]+right[i]+arm*face[i],
+       loc[i]+up[i]+2*right[i]+2*arm*face[i]])
+  sets.append(Gridded3DSet(type, samples, len(samples[0])))
+
+  # make legs
+  samples = []
+  for i in range(3):
+    samples.append(
+      [loc[i]-4*up[i]-right[i],
+       loc[i]-3*up[i]-right[i],
+       loc[i]-2*up[i],
+       loc[i]-3*up[i]+right[i],
+       loc[i]-4*up[i]+right[i]])
+  sets.append(Gridded3DSet(type, samples, len(samples[0])))
+
+  set = UnionSet(type, sets)
+  return set
+
+# create the initial shape
+set = makeBody()
+
+# connect the shape to a VisAD DataReference
+ref = DataReferenceImpl("set")
+function.setSample(index, set)
+index = index + 1
+ref.setData(function)
+
+# make the display mapings for the shape
+maps = makeMaps(type[0], "x", type[1], "y", type[2], "z")
+maps[0].setRange(-8, 8)
+maps[1].setRange(-8, 8)
+maps[2].setRange(-8, 8)
+
+# make a VisAD display with those mappings
+display = makeDisplay3D(maps)
+
+# connect the DataReference to the display
+display.addReference(ref)
+
+# show the display on the screen
+showDisplay(display)
+
+# initialize some vectors use for rotating the shape
+rot = [0.2, -0.6, 0.7]
+normalize(rot)
+rot2 = [-0.5, 0.3, 0.4]
+normalize(rot2)
+funny = [1, 0, 0]
+arminc = 0
+
+for i in range(99):
+  # wait 1000 milleseconds
+  Delay(1000)
+
+  rotate(rot2, rot, 0.043)
+
+  # rotate the shape "up" direction
+  rotate(up, rot2, 0.1)
+  rotate(funny, rot, 0.08)
+
+  # compute new "face" direction
+  face = cross(up, funny)
+  normalize(face)
+
+  # compute new location of shape
+  rotate(loc, face, 0.9)
+  loc[0] = 4*loc[0]
+  loc[1] = 4*loc[1]
+  loc[2] = 4*loc[2]
+
+  # compute new arm angle
+  arm = 1.0 * math.sin(arminc)
+  arminc = arminc + 0.4
+
+  # make the new shape
+  set = makeBody()
+
+  # set the new shape in the DataReference connected
+  #   to the display
+  function.setSample(index, set)
+  index = index + 1
diff --git a/visad/python/subs.py b/visad/python/subs.py
new file mode 100644
index 0000000..4c428e0
--- /dev/null
+++ b/visad/python/subs.py
@@ -0,0 +1,1572 @@
+"""
+A collection of support methods for connecting VisAD to Jython.  The
+emphasis is on display-side methods and classes.  All display-dependent
+functions (methods) are available as either static functions 
+(where one of the parameter is the 'display') or as an instance
+function (for a previously-created 'display').  See the '_vdisp' class.
+
+"""
+
+try:  #try/except done to remove these lines from documentation only
+
+  from visad import ScalarMap, Display, DataReferenceImpl, RealTupleType,\
+          Gridded2DSet, Gridded3DSet, DisplayImpl, RealType, RealTuple, \
+          VisADLineArray, VisADQuadArray, VisADTriangleArray, SetType, \
+          VisADGeometryArray, ConstantMap, Integer1DSet, FunctionType, \
+          ScalarMap, Display, Integer1DSet, FieldImpl, CellImpl, \
+          DisplayListener, DisplayEvent, GraphicsModeControl, \
+          MouseHelper, UnionSet
+          
+
+  from types import StringType
+  from visad.ss import BasicSSCell
+  from visad.java2d import DisplayImplJ2D, DisplayRendererJ2D
+  from java.lang import Double
+
+  # define private fields for certains types and scalar mappings
+  __py_shapes = None 
+except:
+  pass
+
+# Super class for displays - this contains all the
+# useful instance methods for the displays.  (Note
+# the 'static' methods usually use these.)  This
+# class should _only_ be instantiated indirectly using
+# the makeDisplay(), makeDisplay2D(), or makeDisplay3D()
+# methods!!
+class _vdisp:
+  instance = 0 # make sure we use unique names for each instance
+
+  def __init__(self):
+      instance =+ 1
+      self.text_type = RealType.getRealType("py_text_type_"+str(instance))
+      self.shape_type = RealType.getRealType("py_shape_type_"+str(instance))
+      self.pcMatrix = None
+      self.text_map = ScalarMap(self.text_type, Display.Text)
+      self.addMap(self.text_map)
+      self.shape_map = ScalarMap(self.shape_type, Display.Shape)
+      self.addMap(self.shape_map)
+      self.my_frame = None
+
+  def addMaps(self, maps):
+    """
+    Add list/tuple <maps> mappings to the <display>.  These determine what
+    scalars will appear along what axes. See makeMaps, below.
+    """
+
+    for map in maps:
+      self.addMap(map)
+
+
+  def addShape(self, type, scale=.1, color=None, index=None, autoScale=1):
+    """
+    Add a shape for this display.  
+    Parameters: <type> of shape ('cross','triangle','square',
+    'solid_square','solid_triangle', or a VisADGeometryArray.  <scale> = 
+    relative size for the shape.  <color> = color name (e.g., "green") 
+    <index> = the index of the shape to replace with this one.  If
+    autoScale is true, the shape will be scaled.
+
+    Returns an index to this shape. (For use with moveShape.)
+
+    """
+    return (self.shapes.addShape(type, scale, color, index, autoScale))
+
+
+  def moveShape(self, index, coord):
+    """
+    Move the shape pointed to by <inx>, to the <coordinates> which
+    is a list of values in the same order as returned by
+    getDisplayMaps.
+    """
+    self.shapes.moveShape(index, coord)
+
+
+  def drawString(self, string, point, color=None, center=0, font="futural",
+                   start=[0.,0.,0.], base=[.1,0.,0.], up=[0.,.1,0.],size=None ):
+
+    """
+    Draw a string of characters on this display.  <string> is the
+    string of characters.  <point> is the starting location for the
+    string drawing, expressed as a list/tuple of 2 or 3 values (x,y,z).
+    <color> is the color (e.g., "red" or java.awt.Color; default =
+    white).  <center> if true will center the string.  <font> defiles
+    the HersheyFont name to use.  <start> defines the relative location
+    of the starting point (x,y,z; default = 0,0,0).  <base> defines the
+    direction that the base of the string should point (x,y,z; default=
+    along x-axis).  <up> defines the direction of the vertical stroke
+    for each character (default = along y-axis).  <size> is the relative
+    size.  Returns the index to the shape of the text (useful for
+    moving).
+    """
+
+    textfs = textShape(string, center, font, start, base, up, size)
+    i = self.shapes.addShape(textfs, color=color, index=None)
+    self.shapes.moveShape(i, point)
+    return i
+
+
+  def addTitle(self, string, center=1, font="timesrb", color=None, size=None, offset=.1, index=None):
+    """
+    Put a title onto this display.  The title is centered just 
+    above the VisAD 'box'.  Use 'offset' to move it up/down.
+    Set the text color with 'color', and if this is a replacement
+    for a previous title, then 'index' is the value previously
+    returned.
+
+    Return the shape index for later adjustments...
+    """
+    ts = textShape(string,center,font,start=[1.,2.+offset,0], size=size)
+    return self.shapes.addShape(ts, color=color, index=index, autoScale=0)
+
+
+  def saveDisplay(self,filename, display_wait=1, file_write_wait=1):
+    """
+    Save the display <disp> as a JPEG, given the filename to use. This
+    will wait for the display to be 'done'.
+    """
+    from visad.util import Util
+    Util.captureDisplay(self, filename, display_wait, file_write_wait)
+
+
+  def addData(self, name, data, constantMaps=None, renderer=None, ref=None, zlayer=None):
+    """
+    Add VisAD <data> to the display <disp>.  Use a reference <name>.
+    If there are ConstantMaps, also add them.  If there is a non-default
+    Renderer, use it as well.  You can supply a pre-defined
+    DataReference as <ref>.  Finally, if nothing is mapped to Display.ZAxis,
+    data objects with the highest <zlayer: (0,1)> will appear on top in the display.
+
+
+    Returns the DataReference
+    """
+
+    if zlayer is not None:
+      zmap = [ConstantMap(zlayer, Display.ZAxis)]
+      if constantMaps is not None:
+        constantMaps = constantMaps + zmap
+      else:
+        constantMaps = zmap
+
+    if ref is None: 
+      ref = DataReferenceImpl(name)
+    if renderer is None:
+      self.addReference(ref, constantMaps)
+    else:
+      self.addReferences(renderer, ref, constantMaps)
+
+    if data is not None: 
+      ref.setData(data)
+    else:
+      print "added Data is None"
+
+    return ref
+
+  def toggle(self, ref, on):
+    rendVector = self.getRendererVector()
+    for i in range(rendVector.size()):
+      ren = rendVector.elementAt(i)
+      links = ren.getLinks()
+      for j in range(len(links)):
+        if (links[j].getThingReference() == ref):
+          links[j].getRenderer().toggle(on)
+    
+  def setPointSize(self, size):
+    """
+    Set the size of points (1,2,3...) to use in this display.
+    """
+    self.getGraphicsModeControl().setPointSize(size)
+    
+  def setAspectRatio(self, ratio):
+    """
+    Set the aspect <ratio> for this display.  The ratio is
+    expressed as the fractional value for: width/height.
+    """
+    x = 1.
+    y = 1.
+    if ratio > 1:
+      x = 1.
+      y = 1. / ratio
+    else:
+      y = 1.
+      x = 1. * ratio
+    self.setAspects(x, y, 1.)
+
+
+  def setAspects(self, x, y, z):
+    """
+    Set the relative sizes of each axis in this display.
+    """
+    self.getProjectionControl().setAspectCartesian( (x, y, z))
+
+  def rotateBox(self, azimuth, declination):
+    """
+    Rotate the 3D display box to the azimuth angle (0-360) and
+    declination angle (all in degrees).  Code from Unidata.
+    """
+    zangle = 180 - azimuth
+    aziMat = self.make_matrix(0, 0, zangle, 1, 0, 0, 0)
+    pc = self.getProjectionControl()
+    comb = self.multiply_matrix(aziMat, pc.getMatrix())
+    decMat = self.make_matrix(declination, 0, 0, 1, 0, 0, 0)
+    comb2 = self.multiply_matrix(decMat,comb)
+    pc.setMatrix(comb2)
+
+  def maximizeBox(self, clip=1):
+    """
+    Set the size of the VisAD 'box' for the <display> to 95%.  If
+    <clip> is true, the display will be clipped at the border of the
+    box; otherwise, data displays may spill over.
+    """
+    self.setBoxSize(.95, clip)
+
+  def setBoxSize(self, percent=.70, clip=1, showBox=1, snap=0):
+    """
+    Set the size of the VisAD 'box' for the <display> as a percentage
+    (fraction). The default is .70 (70%).   If <clip> is true, the
+    display will be clipped at the border of the box; otherwise, data
+    displays may spill over.  If <showBox> is true, the wire-frame will
+    be shown; otherwise, it will be turned off.  If <snap> is true, the
+    box will be reoriented to an upright position.
+    """
+    pc=self.getProjectionControl()
+    if (not snap) or (self.pcMatrix == None): 
+      self.pcMatrix=pc.getMatrix()
+      
+    if len(self.pcMatrix) > 10:
+      self.pcMatrix[0]=percent
+      self.pcMatrix[5]=percent
+      self.pcMatrix[10]=percent
+    else:
+      self.pcMatrix[0]=percent/.64
+      self.pcMatrix[3]=-percent/.64
+      
+    pc.setMatrix(self.pcMatrix)
+    dr = self.getDisplayRenderer();
+
+    if __ok3d:
+      try:
+         if isinstance(dr, DisplayRendererJ3D):
+           dr.setClip(0, clip,  1.,  0.0,  0.0, -1.);
+           dr.setClip(1, clip, -1.,  0.0,  0.0, -1.);
+           dr.setClip(2, clip,  0.0,  1.,  0.0, -1.);
+           dr.setClip(3, clip,  0.0, -1.,  0.0, -1.);
+           #dr.setClip(4, 1,  0.0,  0.0,  1., -1.);
+           #dr.setClip(5, 1,  0.0,  0.0, -1., -1.);
+         elif isinstance(dr, DisplayRendererJ2D):
+           if clip: 
+             dr.setClip(-1., 1., -1., 1.)
+           else:
+             dr.unsetClip()
+      except:
+         pass
+
+    dr.setBoxOn(showBox)
+
+  def makeCube(self):
+    """
+    Turn the VisAD box for this display into a cube with no
+    perspective (no 'vanishing point'.  Useful for aligning
+    vertically-stacked data.  
+    """
+    self.getGraphicsModeControl().setProjectionPolicy(0)
+
+  def setBackgroundColor(self,c):
+    """
+    Set the background color to 'color' (which may be a
+    java.awt.Color, or a string with the name in it (like 'green')
+    """
+    r,g,b = _color2rgb(c)
+    self.getDisplayRenderer().getRendererControl().setBackgroundColor(r,g,b)
+
+  def setForegroundColor(self,c):
+    """
+    Set the foreground color to 'color' (which may be a
+    java.awt.Color, or a string with the name in it (like 'green')
+    """
+    r,g,b = _color2rgb(c)
+    self.getDisplayRenderer().getRendererControl().setForegroundColor(r,g,b)
+
+  def setBoxOn(self,on):
+    """
+    Turn the wire frame box on or off.
+    """
+    self.getDisplayRenderer().getRendererControl().setBoxOn(on)
+     
+  def setCursorColor(self,c):
+    """
+    Set the cursor color to 'color' (which may be a
+    java.awt.Color, or a string with the name in it (like 'green')
+    """
+    r,g,b = _color2rgb(c)
+    self.getDisplayRenderer().getRendererControl().setCursorColor(r,g,b)
+
+  def setBoxColor(self,c):
+    """
+    Set the box color to 'color' (which may be a
+    java.awt.Color, or a string with the name in it (like 'green')
+    """
+    r,g,b = _color2rgb(c)
+    self.getDisplayRenderer().getRendererControl().setBoxColor(r,g,b)
+
+  def zoomBox(self, factor):
+    """
+    Zoom the display by 'factor' (1.0 does nothing...). Related:
+    setBoxSize().
+    """
+    mouseBehavior = self.getMouseBehavior()
+    pc = self.getProjectionControl()
+    currentMatrix = pc.getMatrix()
+    scaleMatrix = mouseBehavior.make_matrix( 0, 0, 0, factor, 0, 0, 0)
+    scaleMatrix = mouseBehavior.multiply_matrix( scaleMatrix, currentMatrix);
+    try:
+      pc.setMatrix(scaleMatrix)
+    except:
+      pass
+
+  def setPointMode(size, on):
+    """
+    Turn on (on=true) point mode for some renderings; otherwise
+    these objects may be shown as lines or texture maps.
+    """
+    self.getGraphicsModControl().setPointSize(size)
+    self.getGraphicsModControl().setPointMode(on)
+
+  def showAxesScales(self, on):
+    """
+    Turn on the axes labels for this display if 'on' is true
+    """
+    self.getGraphicsModeControl().setScaleEnable(on)
+
+  def enableRubberBandBoxZoomer(self,useKey,callback=None,color=None,x=None,y=None):
+    """
+    Method to attach a Rubber Band Box zoomer to this
+    display.  Once attached, it is there foreever!  The useKey
+    parameter can be 0 (no key), 1(CTRL), 2(SHIFT)
+    """
+    rrbz = RubberBandZoomer(self,useKey,1,callback,color,x,y)
+    return rrbz.ref
+
+  def enableRubberBandBox(self,useKey,callback=None,color=None,x=None,y=None):
+    """
+    Method to attach a Rubber Band Box to this display.  Once
+    attached, it is there forever!  The useKey parameter can 
+    be 0 (no key), 1(CTRL), 2(SHIFT)
+    """
+    rrbz = RubberBandZoomer(self,useKey,0,callback,color,x,y)
+    return rrbz.ref
+
+  def getDisplayScalarMaps(self, includeShapes=0):
+    """
+    Return a list of the scalarmaps mappings for this display. The list
+    elements are ordered: x,y,z,display.  If <includeShapes> is
+    true, then mappings for Shape will be appended.  The <display>
+    may be a Display, or the name of a 'plot()' window.
+    """
+    return (getDisplayScalarMaps(self,includeShapes))
+
+  def getDisplayScalarMapLists(self, includeShapes=0):
+    """
+    Return a list of the scalarmaps mappings for this display. The list
+    elements are ordered: x,y,z,display.  If <includeShapes> is
+    true, then mappings for Shape will be appended.  The <display>
+    may be a Display, or the name of a 'plot()' window.
+    Note: this is identical to getDisplayScalarMaps except that
+    the return type is a list of lists since there may more than
+    one ScalarMap to x, y or z.
+    """
+    return (getDisplayScalarMapLists(self,includeShapes))
+
+  def getDisplayMaps(self, includeShapes=0):
+    """
+    Return a list of the type mappings for the <display>. The list
+    elements are ordered: x,y,z,display.  If <includeShapes> is
+    true, then mappings for Shape will be appended.  The <display>
+    may be a Display, or the name of a 'plot()' window.
+    """
+    return (getDisplayMaps(self,includeShapes))
+
+
+  def moveLine(self, lref, points):
+    """ 
+    move the referenced line to the new points
+    """
+    type = lref.getType()
+    lref.setData(makeLine(type,points))
+
+
+  def drawLine(self, points, color=None, mathtype=None, style=None, width=None):
+    """
+    Draw lines on this display.  <points> is a 2 or 3 dimensional,
+    list/tuple of points to connect as a line, ordered as given in the
+    <mathtype>.  Default <mathtype> is whatever is mapped to the x,y
+    (and maybe z) axis.  <color> is the line color ("red" or
+    java.awt.Color; default=white), <style> is the line style (e.g.,
+    "dash"), and <width> is the line width in pixels.
+
+    Return a reference to this line.
+    """
+
+    constmap = makeColorMap(color)
+    constyle = makeLineStyleMap(style, width)
+    if constyle is not None:
+      constmap.append(constyle)
+      
+    # drawLine(display, domainType, points[])
+    maps = None
+    if mathtype is not None:
+      if len(points) == 2:
+        lineseg = Gridded2DSet(RealTupleType(mathtype), points, len(points[0]))
+      else:
+        lineseg = Gridded3DSet(RealTupleType(mathtype), points, len(points[0]))
+
+      linref = self.addData("linesegment", lineseg, constmap)
+      return linref
+
+    # drawLine(name|display, points[])
+    else:
+      x , y , z , disp = self.getDisplayMaps()
+
+      if len(points) == 2:
+        dom = RealTupleType(x,y)
+        lineseg = Gridded2DSet(dom, points, len(points[0]))
+      else:
+        dom = RealTupleType(x,y,z)
+        lineseg = Gridded3DSet(dom, points, len(points[0]))
+
+      linref = self.addData("linesegment", lineseg, constmap)
+      return linref
+
+
+  def drawBox(self, points, color=None, mathtype=None, style=None, width=None, boxref=None):
+    """
+    Draw a box on this display.  <points> is a 2 dimensional, list/tuple
+    of points to connect a box diagonal, ordered as given in the
+    <mathtype>.  Default <mathtype> is whatever is mapped to the x,y
+    (and maybe z) axis.  <color> is the line color ("red" or
+    java.awt.Color; default=white), <style> is the line style (e.g.,
+    "dash"), and <width> is the line width in pixels.
+
+    Return a reference to this box.
+    """
+
+    constmap = makeColorMap(color)
+    constyle = makeLineStyleMap(style, width)
+    if constyle is not None:
+      constmap.append(constyle)
+
+    maps = None
+    lineseg_s = []
+    if mathtype is not None:
+      dom = RealTupleType(mathtype)
+      if len(points) == 2:
+        aa = [[points[0][0], points[0][1]], [points[1][0], points[1][0]]]
+        lineseg_s.append(Gridded2DSet(dom, aa, len(aa[0])))
+        bb = [[aa[0][1], points[0][1]], [aa[1][1], points[1][1]]]
+        lineseg_s.append(Gridded2DSet(dom, bb, len(bb[0])))
+        cc = [[bb[0][1], aa[0][0]], [bb[1][1], bb[1][1]]]
+        lineseg_s.append(Gridded2DSet(dom, cc, len(cc[0])))
+        dd = [[cc[0][1], aa[0][0]], [cc[1][1], aa[1][0]]]
+        lineseg_s.append(Gridded2DSet(dom, dd, len(dd[0])))
+      else:
+        lineseg = Gridded3DSet(RealTupleType(mathtype), points, len(points[0]))
+
+      uset = UnionSet(lineseg_s)
+      if boxref is not None:
+        boxref.setData(uset)
+      else:
+        boxref = self.addData("box", uset, constmap)
+      return boxref
+
+    else:
+      x , y , z , disp = self.getDisplayMaps()
+
+      if len(points) == 2:
+        dom = RealTupleType(x,y)
+        aa = [[points[0][0], points[0][1]], [points[1][0], points[1][0]]]
+        lineseg_s.append(Gridded2DSet(dom, aa, len(aa[0])))
+        bb = [[aa[0][1], points[0][1]], [aa[1][1], points[1][1]]]
+        lineseg_s.append(Gridded2DSet(dom, bb, len(bb[0])))
+        cc = [[bb[0][1], aa[0][0]], [bb[1][1], bb[1][1]]]
+        lineseg_s.append(Gridded2DSet(dom, cc, len(cc[0])))
+        dd = [[cc[0][1], aa[0][0]], [cc[1][1], aa[1][0]]]
+        lineseg_s.append(Gridded2DSet(dom, dd, len(dd[0])))
+      else:
+        dom = RealTupleType(x,y,z)
+        lineseg = Gridded3DSet(dom, points, len(points[0]))
+
+      uset = UnionSet(lineseg_s)
+      if boxref is not None:
+        boxref.setData(uset)
+      else:
+        boxref = self.addData("box", uset, constmap)
+      return boxref
+
+
+  def showDisplay(self, width=300, height=300, 
+                  title="VisAD Display", bottom=None, top=None,
+                  panel=None, right=None, left=None):
+    """
+    Quick display of this display in a separate frame. <width> and
+    <height> give the dimensions of the window; <title> is the
+    text string for the titlebar, <bottom> is a panel to put
+    below the <display>, <top> is a panel to put above the
+    <display>, and <panel> is the panel to put everything into (default
+    is to make a new one, and display it).  Additionally, you may put
+    panels on the <right> and <left>.  
+    """
+
+    return myFrame(self, width, height, title, bottom, top, right, left, panel)
+
+
+
+#-----------------------------------------------------------------
+# try to get 3D and make display class for 3D
+__ok3d = 1
+try:
+  from visad.java3d import DisplayImplJ3D, TwoDDisplayRendererJ3D, DisplayRendererJ3D,MouseBehaviorJ3D
+  from visad.bom import RubberBandBoxRendererJ3D
+
+  # helper class for 3D displays -- need to subclass DisplayImplJ3D
+  # and the _vdisp helper
+  class _vdisp3D(_vdisp, DisplayImplJ3D):
+      
+    def __init__(self,maps,renderer):
+      global __py_shapes
+      DisplayImplJ3D.__init__(self,"Jython3D",renderer)
+      _vdisp.__init__(self)
+      if maps is not None:  addMaps(self, maps)
+      self.shapes = Shapes(self, self.shape_map)
+      __py_shapes = self.shapes
+
+except:
+  __ok3d = 0
+
+try: # this keeps the doc from being generated 
+
+  # helper class for 2D displays -- need to subclass DisplayImplJ2D
+  # and the _vdisp helper
+  class _vdisp2D(_vdisp, DisplayImplJ2D):
+
+    def __init__(self,maps):
+      global __py_shapes
+      DisplayImplJ2D.__init__(self,"Jython2D")
+      _vdisp.__init__(self)
+      if maps is not None:  addMaps(self, maps)
+      self.shapes = Shapes(self, self.shape_map)
+      __py_shapes = self.shapes
+except:
+  pass
+
+
+#----------------------------------------------------------------
+# static methods start here
+def makeDisplay3D(maps):
+  """
+  Create (and return) a VisAD DisplayImplJ3D and add the ScalarMaps
+  <maps>, if any.  The VisAD box is resized to about 95% of the window.
+  This returns the Display.
+  """
+  return _vdisp3D(maps,None)
+
+def makeDisplay2D(maps):
+  """
+  Create (and return) a VisAD DisplayImplJ2D and add the ScalarMaps
+  <maps>, if any.  The VisAD box is resized to about 95% of the window.
+  This returns the Display.
+  """
+  return _vdisp2D(maps)
+
+# create (and return) a VisAD DisplayImpl and add the ScalarMaps, if any
+# the VisAD box is resized to about 95% of the window
+def makeDisplay(maps):
+  """
+  Create (and return) a VisAD DisplayImpl and add the ScalarMaps
+  <maps>, if any.  The VisAD box is resized to about 95% of the window.  
+  Use 3D if availble, otherwise use 2D.  This returns the Display.
+  """
+
+  is3d = 0
+  if maps == None:
+    is3d = 1
+  else:
+    for m in maps:
+      if m.getDisplayScalar().toString() == "DisplayZAxis": is3d = 1
+
+  if is3d == 1 and __ok3d == 1:
+    disp = makeDisplay3D(maps)
+  else:
+    if __ok3d:
+      #tdr = TwoDDisplayRendererJ3D()
+      #disp = _vdisp3D(maps, tdr)
+      disp = makeDisplay3D(maps)
+      mode = disp.getGraphicsModeControl()
+      mode.setProjectionPolicy(DisplayImplJ3D.PARALLEL_PROJECTION)
+      mousehelper = disp.getDisplayRenderer().getMouseBehavior().getMouseHelper()
+      mousehelper.setFunctionMap([[[MouseHelper.NONE, MouseHelper.ZOOM],
+                                   [MouseHelper.TRANSLATE, MouseHelper.NONE]],
+                                  [[MouseHelper.CURSOR_TRANSLATE, MouseHelper.CURSOR_ZOOM],
+                                   [MouseHelper.NONE, MouseHelper.NONE]],
+                                  [[MouseHelper.DIRECT, MouseHelper.DIRECT],
+                                   [MouseHelper.DIRECT, MouseHelper.DIRECT]]])
+    else:
+      disp =  _vdisp2D(maps)
+  
+  return disp
+
+
+def saveDisplay(disp, filename):
+  """
+  Save the display <disp> as a JPEG, given the filename to use.
+  """
+  disp.saveDisplay(filename)
+
+
+def addData(name, data, disp, constantMaps=None, renderer=None, ref=None, zlayer=None):
+  """
+  Add <data> to the display <disp>.  Use a reference <name>.
+  If there are ConstantMaps, also add them.  If there is a non-default
+  Renderer, use it as well.  You can supply a pre-defined
+  DataReference as <ref>.  Finally, if nothing is mapped to Display.ZAxis,
+  data objects with the highest <zlayer: (0,1)> will appear on top in the display.  
+
+  Returns the DataReference
+  """
+  return disp.addData(name, data, constantMaps, renderer, ref, zlayer)
+
+
+def setPointSize(display, size):
+  """
+  Set the size of points (1,2,3...) to use in the <display>.
+  """
+  display.setPointSize(size)
+
+  
+# define the aspects of width and height, as a ratio: width/height
+def setAspectRatio(display, ratio):
+  """
+  Set the aspect <ratio> for the <display>.  The ratio is
+  expressed as the fractional value for: width/height.
+  """
+  display.setAspectRatio(ratio)
+
+
+def setAspects(display, x, y, z):
+  """
+  Set the relative sizes of each axis in the <display>.
+  """
+  display.setAspects(x, y, z)
+
+def rotateBox(display, azimuth, declination):
+  """
+  Rotate the 3D display box to the azimuth angle (0-360) and
+  declination angle (all in degrees).  Code from Unidata.
+  """
+  display.rotateBox(azimuth, declination)
+
+def maximizeBox(display, clip=1):
+  """
+  Set the size of the VisAD 'box' for the <display> to 95%.  If
+  <clip> is true, the display will be clipped at the border of the
+  box; otherwise, data displays may spill over.
+  """
+  display.maximizeBox(clip)
+
+def setBoxSize(display, percent=.70, clip=1, showBox=1, snap=0):
+  """
+  Set the size of the VisAD 'box' for the <display> as a percentage
+  (fraction). The default is .70 (70%).   If <clip> is true, the
+  display will be clipped at the border of the box; otherwise, data
+  displays may spill over.  If <showBox> is true, the wire-frame will
+  be shown; otherwise, it will be turned off.  If <snap> is true, the
+  box will be reoriented to an upright position.
+  """
+  display.setBoxSize(percent, clip, showBox, snap)
+
+
+def makeCube(display):
+  """
+  Turn the VisAD box for this <display> into a cube with no
+  perspective (no 'vanishing point'.  Useful for aligning
+  vertically-stacked data.  
+  """
+  display.makeCube()
+
+def setBackgroundColor(display,color):
+  """
+  Set the background color to 'color' (which may be a
+  java.awt.Color, or a string with the name in it (like 'green')
+  """
+  display.setBackgroundColor(color)
+
+def setForegroundColor(display,color):
+  """
+  Set the foreground color to 'color' (which may be a
+  java.awt.Color, or a string with the name in it (like 'green')
+  """
+  display.setForegroundColor(color)
+
+def setBoxOn(display,on):
+  """
+  Turn the wire frame box on or off.
+  """
+  display.setBoxOn(on)
+   
+def setCursorColor(display,color):
+  """
+  Set the cursor color to 'color' (which may be a
+  java.awt.Color, or a string with the name in it (like 'green')
+  """
+  display.setCursorColor(color)
+
+def setBoxColor(display,color):
+  """
+  Set the box color to 'color' (which may be a
+  java.awt.Color, or a string with the name in it (like 'green')
+  """
+  display.setBoxColor(color)
+
+def zoomBox(display, factor):
+  """
+  Zoom the display by 'factor' (1.0 does nothing...). Related:
+  setBoxSize().
+  """
+  display.zoomBox(factor)
+
+def enableRubberBandBoxZoomer(display,useKey,callback=None,color=None,x=None,y=None):
+  """
+  Method to attach a Rubber Band Box zoomer to this
+  display.  Once attached, it is there foreever!  The useKey
+  parameter can be 0 (no key), 1(CTRL), 2(SHIFT)
+  """
+  display.enableRubberBandBoxZoomer(useKey,callback,color,x,y)
+
+def enableRubberBandBox(display,useKey,callback=None,color=None,x=None,y=None):
+  """
+  Method to attach a Rubber Band Box to this display.
+  Once attached, it is there foreever!  The useKey
+  parameter can be 0 (no key), 1(CTRL), 2(SHIFT)
+  """
+  display.enableRubberBandBox(useKey,callback,color,x,y)
+
+def getDisplayScalarMaps(display, includeShapes=0):
+  """
+  Return a list of the scalarmaps mappings for this display. The list
+  elements are ordered: x,y,z,display.  If <includeShapes> is
+  true, then mappings for Shape will be appended.  The <display>
+  may be a Display, or the name of a 'plot()' window.
+  """
+
+  if type(display) == StringType:
+    d = BasicSSCell.getSSCellByName(display)
+    disp = d.getDisplay()
+    maps = d.getMaps()
+
+  elif isinstance(display, DisplayImpl):
+    maps = display.getMapVector()
+    disp = display
+
+  else:
+    maps = None
+    disp = None
+
+  x = y = z = shape = None
+  if maps != None:
+    for m in maps:
+      if m.getDisplayScalar().toString() == "DisplayXAxis":
+        x = m
+      if m.getDisplayScalar().toString() == "DisplayYAxis":
+        y = m
+      if m.getDisplayScalar().toString() == "DisplayZAxis":
+        z = m
+      if m.getDisplayScalar().toString() == "DisplayShape":
+        shape = m
+  
+  if includeShapes:
+    return [x,y,z,disp,shape]
+  else:
+    return [x,y,z,disp]
+
+def getDisplayScalarMapLists(display, includeShapes=0):
+  """
+  Return a list of the scalarmaps mappings for this display. The list
+  elements are ordered: x,y,z,display.  If <includeShapes> is
+  true, then mappings for Shape will be appended.  The <display>
+  may be a Display, or the name of a 'plot()' window.
+  Note: this is identical to getDisplayScalarMaps except that
+  the return type is a list of lists since there may more than
+  one ScalarMap to x, y or z.
+  """
+                                                                                                                                   
+  if type(display) == StringType:
+    d = BasicSSCell.getSSCellByName(display)
+    disp = d.getDisplay()
+    maps = d.getMaps()
+                                                                                                                                   
+  elif isinstance(display, DisplayImpl):
+    maps = display.getMapVector()
+    disp = display
+                                                                                                                                   
+  else:
+    maps = None
+    disp = None
+                                                                                                                                   
+  x = []
+  y = []
+  z = []
+  shape = []
+                                                                                                                                   
+  if maps != None:
+    for m in maps:
+      if m.getDisplayScalar().toString() == "DisplayXAxis":
+        x.append(m)
+      if m.getDisplayScalar().toString() == "DisplayYAxis":
+        y.append(m)
+      if m.getDisplayScalar().toString() == "DisplayZAxis":
+        z.append(m)
+      if m.getDisplayScalar().toString() == "DisplayShape":
+        shape.append(m)
+                                                                                                                                   
+  if includeShapes:
+    return [x,y,z,disp,shape]
+  else:
+    return [x,y,z,disp]
+
+def getDisplayMaps(display, includeShapes=0):
+  """
+  Return a list of the type mappings for the <display>. The list
+  elements are ordered: x,y,z,display.  If <includeShapes> is
+  true, then mappings for Shape will be appended.  The <display>
+  may be a Display, or the name of a 'plot()' window.
+  """
+
+  if type(display) == StringType:
+    d = BasicSSCell.getSSCellByName(display)
+    disp = d.getDisplay()
+    maps = d.getMaps()
+
+  elif isinstance(display, DisplayImpl):
+    maps = display.getMapVector()
+    disp = display
+
+  else:
+    maps = None
+    disp = None
+
+  x = y = z = shape = None
+  if maps == None:
+    x = RealType.getRealTypeByName("x")
+    y = RealType.getRealTypeByName("y")
+    z = RealType.getRealTypeByName("z")
+    shape = RealType.getRealTypeByName("py_shape_type")
+  # if no maps, make them...
+  else:
+    for m in maps:
+      if m.getDisplayScalar().toString() == "DisplayXAxis":
+        x = m.getScalar()
+      if m.getDisplayScalar().toString() == "DisplayYAxis":
+        y = m.getScalar()
+      if m.getDisplayScalar().toString() == "DisplayZAxis":
+        z = m.getScalar()
+      if m.getDisplayScalar().toString() == "DisplayShape":
+        shape = m.getScalar()
+  
+  if includeShapes:
+    return [x,y,z,disp,shape]
+  else:
+    return [x,y,z,disp]
+
+def drawLine(display, points, color=None, mathtype=None, style=None, width=None):
+  """
+  Deprecated.
+
+  Draw lines on the <display>.  <points> is a 2 or 3 dimensional,
+  list/tuple of points to connect as a line, ordered as given in the
+  <mathtype>.  Default <mathtype> is whatever is mapped to the x,y
+  (and maybe z) axis.  <color> is the line color ("red" or
+  java.awt.Color; default=white), <style> is the line style (e.g.,
+  "dash"), and <width> is the line width in pixels.
+
+  Return a reference to this line.
+  """
+  return display.drawLine(points, color, mathtype, style, width)
+
+# draw a string on the display
+def drawString(display, string, point, color=None, center=0, font="futural",
+                 start=[0.,0.,0.], base=[.1,0.,0.], up=[0.,.1,0.],size=None ):
+
+  """
+  Deprecated.
+
+  Draw a string of characters on the <display>.  <string> is the
+  string of characters.  <point> is the starting location for the
+  string drawing, expressed as a list/tuple of 2 or 3 values (x,y,z).
+  <color> is the color (e.g., "red" or java.awt.Color; default =
+  white).  <center> if true will center the string.  <font> defiles
+  the HersheyFont name to use.  <start> defines the relative location
+  of the starting point (x,y,z; default = 0,0,0).  <base> defines the
+  direction that the base of the string should point (x,y,z; default=
+  along x-axis).  <up> defines the direction of the vertical stroke
+  for each character (default = along y-axis).  <size> is the relative
+  size.  This returns the Shapes object.  Note that the drawString
+  method in the display returns the shape index for this, so is
+  the preferred method.
+  """
+
+  display.drawString(string, point, color, center,font, start, base, up, size)
+  return display.shapes
+
+def addMaps(display, maps):
+  """
+  Add list/tuple <maps> mappings to the <display>.  These determine what
+  scalars will appear along what axes. See makeMaps, below.
+  """
+  display.addMaps(maps)
+
+
+
+#-----------------------------------------------------------------
+# non-display type of methods to do useful things
+# make a 2D or 3D line, return a reference so it can be changed
+def makeLine(domainType, points):
+  """
+  returns a set of <points>, as defined in the <domainType>. For
+  example, if <domaintType> defines a (Latitude,Longitude), then
+  the <points> are in Latitude,Longitude.
+  """
+  if isinstance(domainType, SetType):
+    dt = domainType
+  elif isinstance(domainType, RealTupleType):
+    dt = domainType
+  else:
+    dt = RealTupleType(domainType)
+
+  if len(points) == 2:
+    return Gridded2DSet(dt, points, len(points[0]))
+  else:
+    return Gridded3DSet(dt, points, len(points[0]))
+
+
+def makeLineStyleMap(style, width):
+  """
+  Make a ConstantMap for the indicated line <style>, which
+  may be: "dash", "dot", "dashdot", or "solid" (default). The
+  <width> is the line width in pixels.  Used by drawLine.
+  """
+  constmap = None
+  constyle = None
+  if style is not None:
+
+    if style == "dash":
+      constyle = ConstantMap(GraphicsModeControl.DASH_STYLE,Display.LineStyle)
+    elif style == "dot":
+      constyle = ConstantMap(GraphicsModeControl.DOT_STYLE,Display.LineStyle)
+    elif style == "dashdot":
+      constyle = ConstantMap(GraphicsModeControl.DASH_DOT_STYLE,Display.LineStyle)
+    else:
+      constyle = ConstantMap(GraphicsModeControl.SOLID_STYLE, Display.LineStyle)
+
+    constmap = constyle
+
+  if width is not None:
+    constwid = ConstantMap(width, Display.LineWidth)
+    if constyle is not None:
+      constmap = [constyle, constwid]
+    else:
+      constmap = constwid
+    
+  return constmap
+
+
+def _color2rgb(color):
+  """
+  Return a triplet of Red,Green,Blue values (0-1)
+  for the given color or color name
+  """
+  # if it's a real color or a name
+  if color is not None:
+    from java.awt import Color
+    if isinstance(color,Color):
+      awtColor = color
+    else:
+      exec 'awtColor=Color.'+color
+
+    red = float(awtColor.getRed())/255.
+    green = float(awtColor.getGreen())/255.
+    blue = float(awtColor.getBlue())/255.
+
+  # or just make it white
+  else:
+    red=1.0
+    green=1.0
+    blue=1.0
+
+  return red,green,blue
+
+  
+def makeColorMap(color):
+  """
+  Return a ConstantMap of <color>, given by name (e.g., "red")
+  or a java.awt.Color object.  Default is white. Used by numerous methods.
+  """
+
+  red,green,blue = _color2rgb(color)
+  constmap = [ ConstantMap(red,Display.Red), ConstantMap(green,Display.Green),
+         ConstantMap(blue,Display.Blue) ] 
+
+  return constmap
+
+
+def makeMaps(*a):
+  """
+  Define a list of scalar mappings for each axis and any
+  other one needed.  The parameter list is in pairs:
+  Type, Name.  For example: makeMaps("lat","y", "lon","x")
+  returns a list that maps variable "lat" to the y-axis, and
+  variable "lon" to the x-axis.
+
+  Here is a complete list of available names:
+
+  "x","y","z","lat","lon","rad","list","red","green",
+  "blue","rgb","rgba","hue","saturation","value","hsv","cyan",
+  "magenta","yellow","cmy","alpha","animation","selectvalue",
+  "selectrange","contour","flow1x","flow1y","flow1z",
+  "flow2x","flow2y","flow2z","xoffset","yoffset","zoffset",
+  "shape","text","shapescale","linewidth","pointsize",
+  "cylradius","cylazimuth","cylzaxis",
+  "flow1elev","flow1azimuth","flow1radial",
+  "flow2elev","flow2azimuth","flow2radial","linestyle",
+  "textureenable"
+
+  """
+
+  dis = ("x","y","z","lat","lon","rad","list","red","green",
+  "blue","rgb","rgba","hue","saturation","value","hsv","cyan",
+  "magenta","yellow","cmy","alpha","animation","selectvalue",
+  "selectrange","contour","flow1x","flow1y","flow1z",
+  "flow2x","flow2y","flow2z","xoffset","yoffset","zoffset",
+  "shape","text","shapescale","linewidth","pointsize",
+  "cylradius","cylazimuth","cylzaxis",
+  "flow1elev","flow1azimuth","flow1radial",
+  "flow2elev","flow2azimuth","flow2radial","linestyle",
+  "textureenable")
+
+# note this list is in the same order as Display.DisplayRealArray! 
+
+  maps=[]
+  for i in xrange(0,len(a),2):
+    got = -1 
+
+    for k in xrange(len(dis)):
+      if dis[k] == a[i+1] : got=k
+
+    if got != -1:
+      if type(a[i]) == StringType:
+        rt = RealType.getRealType(a[i])
+        maps.append(ScalarMap(RealType.getRealType(a[i]),
+                                    Display.DisplayRealArray[got]))
+      else:
+        maps.append(ScalarMap(a[i], Display.DisplayRealArray[got]))
+    else:
+      print "While making mappings, cannot match: ",a[i+1]
+
+  return maps
+
+def textShape(string, center=0, font="futural",
+                 start=[0.,0.,0.], base=[.1,0.,0.], up=[0.,.1,0.],
+                 size=None ):
+    """
+    Creates a Shape for the text string given.  For use with the Shape
+    class.  See comments on drawString, above.
+    """
+
+    from visad import PlotText
+    from visad.util import HersheyFont
+    if size != None:
+      start = [0., 0., size]
+      base = [size, 0., 0.]
+      up = [0., size, 0.]
+
+    return (PlotText.render_font(string, HersheyFont(font), 
+                                          start, base, up, center))
+
+
+# local shadow methods for addShape and moveShape
+def addShape(type, scale=.1, color=None, index=None, autoScale=1):
+  """
+  Deprecated.  (Please use the instance method from the 'display'
+  you intend to use this Shape within.)
+  
+  Simply a shadow method for addShape in case the user has not
+  made their own.  You cannot use this method with more than
+  one display (see Shapes class, or use the addShapes() method
+  for the 'display')
+  """
+  return (__py_shapes.addShape(type, scale, color, index, autoScale))
+
+
+def moveShape(index, coord):
+  """
+  Deprecated.  (Please use the instance method from the 'display'
+  you intend to use this Shape within.)
+
+  Simply a shadow method for moveShape in case the user has not
+  made their own.
+  """
+  __py_shapes.moveShape(index, coord)
+
+
+# quick display of a Display object in a separate JFrame
+# you can set the size and title, if you want...
+def showDisplay(display, width=300, height=300, 
+                title="VisAD Display", bottom=None, top=None,
+                panel=None, right=None, left=None):
+  """
+  Quick display of <display> in a separate frame. <width> and
+  <height> give the dimensions of the window; <title> is the
+  text string for the titlebar, <bottom> is a panel to put
+  below the <display>, <top> is a panel to put above the
+  <display>, and <panel> is the panel to put everything into (default
+  is to make a new one, and display it).  Additionally, you may put
+  panels on the <right> and <left>.  
+  """
+
+  myf = myFrame(display, width, height, title, bottom, top, right, left, panel)
+  return myf
+
+
+def changeRangeName(data, new_name):
+  """
+  Change the name of the (single) range component of the Data 
+  object...which really needs to be a Field of some kind.  <data>
+  is the Data object, <new_name> is the new name -- it will
+  inherit the Units of the original.
+  """
+  _at = data.getType()
+  _au = _at.getRange()[0].getDefaultUnit()
+  _nft = FunctionType(_at.getDomain(), RealType.getRealType(new_name,_au))
+  return data.changeMathType(_nft)
+
+#--------------------------------------------------------------------
+# other classes
+class myFrame:
+  """
+  Creates a frame out of the display, with possible optional panels
+  (JPanels) to add to the "top", "bottom", "left", "right". The
+  'display' will be added to the 'center' of this panel.  If 'panel'
+  is None, a new JFrame is created, and will be shown; otherwise,
+  the contents are just put into the JPanel 'panel'.
+  """
+
+  def destroy(self, event):
+    self.desty(event)
+
+  def desty(self, event):
+    try:
+      self.display.destroy()
+    except:
+      pass
+    self.frame.dispose()
+
+  def __init__(self, display, width, height, title, 
+                         bottom, top, right, left, panel):
+
+    from javax.swing import JFrame, JPanel
+    from java.awt import BorderLayout, Dimension
+    self.display = display
+
+    autoShow = 0
+    if panel==None:
+      self.frame = JFrame(title, windowClosing=self.desty)
+      self.pane = self.frame.getContentPane()
+      autoShow = 1
+    elif isinstance(panel, JFrame):
+      self.pane = panel.getContentPane()
+    else:
+      self.pane = panel
+      self.pane.setLayout(BorderLayout())
+
+    self.display.getComponent().setPreferredSize(Dimension(width,height))
+    self.pane.add(self.display.getComponent(), BorderLayout.CENTER)
+    if bottom != None: 
+      self.pb = JPanel(BorderLayout())
+      self.pb.add(bottom)
+      self.pane.add(self.pb, BorderLayout.SOUTH)
+    if top != None: 
+      self.pt = JPanel(BorderLayout())
+      self.pt.add(top)
+      self.pane.add(self.pt, BorderLayout.NORTH)
+    if right != None: 
+      self.pt = JPanel(BorderLayout())
+      self.pt.add(right)
+      self.pane.add(self.pt, BorderLayout.EAST)
+    if left != None: 
+      self.pt = JPanel(BorderLayout())
+      self.pt.add(left)
+      self.pane.add(self.pt, BorderLayout.WEST)
+
+    if autoShow:
+      self.frame.pack()
+      self.frame.show()
+
+    self.display.my_frame = self.frame
+
+
+
+class Shapes:
+  """
+  Helper class for handling Shapes within a display.
+  """
+
+  def __init__(self, display, shapemap):
+    """
+    <display> is the display this is working with; <shapemap>
+    is the mapping parameter for shapes. Prior to creating
+    this instance, the <display> should have all its
+    ScalarMaps set up.
+    """
+
+    self.x, self.y, self.z, self.disp = getDisplayMaps(display)
+    self.doing3D = 1
+    if self.z == None:
+      self.doing3D = 0
+
+    self.count = -1 
+    self.shapeList = []
+    self.shapeRef = []
+    self.shapeMap = shapemap
+    self.shapeType = shapemap.getScalar()
+
+  def addShape(self, type, scale=.1, color=None, index=None, autoScale=1):
+    """
+    Add a shape.  Parameters: <type> of shape ('cross','triangle','square',
+    'solid_square','solid_triangle', or a VisADGeometryArray.  <scale> = 
+    relative size for the shape.  <color> = color name (e.g., "green") 
+    <index> = the index of the shape to replace with this one.  If
+    autoScale is true, the shape will be scaled.
+
+    Returns an index to this shape. (For use with moveShape.)
+
+    """
+
+    if isinstance(type,VisADGeometryArray): 
+
+      self.shape = type
+
+    else:
+
+      if type == "cross":
+        self.shape = VisADLineArray()
+        self.shape.coordinates = ( scale,scale,0,  -scale, -scale, 0,
+                                   scale, -scale, 0,  -scale, scale, 0 )
+      elif type == "triangle":
+        self.shape = VisADLineArray()
+        self.shape.coordinates = ( -scale, -scale/2, 0,  scale, -scale/2, 0,  
+                                    scale, -scale/2, 0,  0, scale,0,
+                                    0, scale, 0, -scale, -scale/2, 0 )
+      elif type == "solid_square":
+        self.shape = VisADQuadArray()
+        self.shape.coordinates = (scale, scale, 0, scale, -scale, 0,
+                                  -scale, -scale, 0, -scale, scale, 0)
+      elif type == "cube":
+        self.shape = VisADQuadArray()
+        self.shape.coordinates = (scale, scale, -scale,  scale, -scale, -scale,
+                   scale, -scale, -scale,    -scale, -scale, -scale,
+                  -scale, -scale, -scale,    -scale,  scale, -scale,
+                  -scale,  scale, -scale,     scale,  scale, -scale,
+
+                   scale,  scale,  scale,     scale, -scale,  scale,
+                   scale, -scale,  scale,    -scale, -scale,  scale,
+                  -scale, -scale,  scale,    -scale,  scale,  scale,
+                  -scale,  scale,  scale,     scale,  scale,  scale,
+
+                   scale,  scale,  scale,     scale,  scale, -scale,
+                   scale,  scale, -scale,     scale, -scale, -scale,
+                   scale, -scale, -scale,     scale, -scale,  scale,
+                   scale, -scale,  scale,     scale,  scale,  scale,
+
+                  -scale,  scale,  scale,    -scale,  scale, -scale,
+                  -scale,  scale, -scale,    -scale, -scale, -scale,
+                  -scale, -scale, -scale,    -scale, -scale,  scale,
+                  -scale, -scale,  scale,    -scale,  scale,  scale,
+
+                   scale,  scale,  scale,     scale,  scale, -scale,
+                   scale,  scale, -scale,    -scale,  scale, -scale,
+                  -scale,  scale, -scale,    -scale,  scale,  scale,
+                  -scale,  scale,  scale,     scale,  scale,  scale,
+
+                   scale, -scale,  scale,     scale, -scale, -scale,
+                   scale, -scale, -scale,    -scale, -scale, -scale,
+                  -scale, -scale, -scale,    -scale, -scale,  scale,
+                  -scale, -scale,  scale,     scale, -scale,  scale)
+
+      elif type == "solid_triangle":
+        self.shape = VisADTriangleArray()
+        self.shape.coordinates = ( -scale, -scale/2, 0,  
+                                   scale, -scale/2, 0,  0, scale, 0 )
+      else:
+        self.shape = VisADLineArray()
+        self.shape.coordinates = ( scale, scale, 0,  scale, -scale, 0,
+                                   scale, -scale, 0,  -scale, -scale, 0,
+                                    -scale, -scale,0,  -scale, scale, 0,
+                                    -scale, scale, 0,  scale, scale, 0)
+
+    self.shape.vertexCount = len(self.shape.coordinates)/3
+    shape_control = self.shapeMap.getControl()
+
+    if index != None:
+      shape_control.setShape(index, self.shape)
+      return (index)
+
+    self.count = self.count + 1
+    self.shapeList.append(self.shape)
+
+    shape_control.setShapeSet(Integer1DSet(self.count+1))
+    shape_control.setShapes( self.shapeList )
+    if autoScale:  
+      shape_control.setAutoScale(1)
+
+    if self.doing3D:
+      self.shape_coord = RealTupleType(self.x,self.y,self.z,self.shapeType)
+      shapeLoc = RealTuple(self.shape_coord, (0., 0., 0., self.count))
+    else:
+      self.shape_coord = RealTupleType(self.x,self.y,self.shapeType)
+      shapeLoc = RealTuple(self.shape_coord, (0., 0., self.count))
+
+    constmap = makeColorMap(color)
+    ad=addData("shape",shapeLoc, self.disp, constmap)
+    self.shapeRef.append(ad)
+
+    return (self.count)
+
+  def moveShape(self, inx, coordinates):
+    """
+    Move the shape pointed to by <inx>, to the <coordinates> which
+    is a list of values in the same order as returned by
+    getDisplayMaps.
+    """
+    coord = list(coordinates)
+    coord.append(inx)
+    shapeLoc = RealTuple(self.shape_coord, coord)
+    self.shapeRef[inx].setData(shapeLoc)
+
+# SelectField aids in showing a series of data objects
+# using the Display.SelectValue.  'data' should be
+# an array of data with same MathTypes
+class SelectField:
+  """
+  Aids in showing a series of data objects using Display.SelectValue.
+  """
+
+  def __init__(this, selectMapName, data):
+    """
+    <selectMapName> is the name of the type to be used for this
+    select map.  <data> is an array/list of data with identical
+    types (MathTypes).
+    """
+    selectMap = RealType.getRealType(selectMapName)
+    this.selectScalarMap = ScalarMap(selectMap, Display.SelectValue)
+    selectType = FunctionType(selectMap, data[0].getType())
+    selectSet = Integer1DSet(selectMap, len(data))
+    this.selectField = FieldImpl(selectType, selectSet)
+    for i in xrange(len(data)):
+      this.selectField.setSample(i, data[i])
+    this.selectedIndex = -1
+
+  def showIt(this, index):
+    """
+    Show only field <index> from this group.
+    """
+    if this.selectedIndex == -1:
+      this.control = this.selectScalarMap.getControl()
+    if index != this.selectedIndex:
+      this.control.setValue(index)
+      this.selectedIndex = index
+
+  def getScalarMap(this):
+    """
+    Return the ScalarMap to add to the display for this Select
+    """
+    return this.selectScalarMap
+
+  def getSelectField(this):
+    """
+    Return the Field to add() to a display.
+    """
+    return this.selectField
+
+class RubberBandZoomer:
+  """
+  Class to define a Rubber Band Box zoom capability
+  for a display.  Once invoked, a drag with right mouse
+  button creates a RubberBandBox.  When released, the
+  image is moved and zoomed to fill the window.
+  """
+
+  def __init__(self, display, requireKey, zoom, callback, color, x, y):
+    """
+    display is the display object.  requireKey = 0 (no key),
+    = 1 (CTRL), = 2 (SHIFT)
+    """
+    if x != None and y != None:
+      self.x = x
+      self.y = y
+      self.display = display
+    else:
+      self.x, self.y, self.z, self.display = getDisplayMaps(display)
+    self.xy = RealTupleType(self.x, self.y)
+    self.dummy_set = Gridded2DSet(self.xy,None,1)
+    mask = 0
+    from java.awt.event import InputEvent
+    if requireKey == 1: 
+      mask = InputEvent.CTRL_MASK
+    elif requireKey == 2:
+      mask = InputEvent.SHIFT_MASK
+
+    self.rbb = RubberBandBoxRendererJ3D(self.x, self.y, mask, mask)
+    constMap = None
+    if color != None:
+      constMap = makeColorMap(color)
+    self.ref = addData('rbb',self.dummy_set,self.display, constMap, renderer=self.rbb)
+    self.callback = callback
+    self.zoom = zoom
+    
+    class MyCell(CellImpl):
+      def __init__(self, ref, disp, zoom, callback):
+        self.ref = ref
+        self.display = disp
+        self.xm, self.ym, self.zm, self.d = getDisplayScalarMaps(disp)
+        self.dr = self.display.getDisplayRenderer()
+        self.can = self.dr.getCanvas()
+        self.mb = MouseBehaviorJ3D(self.dr)
+        self.pc = self.display.getProjectionControl()
+        self.callback = callback
+        self.zoom     = zoom
+
+      def doAction(self):
+
+        # get the 'box' coordinates of the RB box
+        set = self.ref.getData()
+        samples = set.getSamples()
+        if samples is None: return  # no sense in going further
+        if self.callback is not None:  self.callback(samples)
+        if self.zoom == 1: self.zoomIt(samples)
+
+      def zoomIt(self, samples):
+        xsv = self.xm.scaleValues((samples[0][0],samples[0][1]))
+        ysv = self.ym.scaleValues((samples[1][0],samples[1][1]))
+
+        xsc = (xsv[0] + xsv[1]) / 2.0
+        ysc = (ysv[0] + ysv[1]) / 2.0
+
+        # compute the 'box' coordinates of the corners of Canvas
+        canvasDim = self.can.getSize()
+        sc = self.mb.findRay( 0, 0 )
+        a = sc.position[2]/sc.vector[2]
+        x0 = sc.position[0] - a*sc.vector[0]
+        y0 = sc.position[1] - a*sc.vector[1]
+
+        sc = self.mb.findRay( canvasDim.width, canvasDim.height )
+        a = sc.position[2]/sc.vector[2]
+        x1 = sc.position[0] - a*sc.vector[0]
+        y1 = sc.position[1] - a*sc.vector[1]
+
+        # center point of the canvas
+        xc = (x0 + x1)/2
+        yc = (y0 + y1)/2
+
+        # do the matrix changes.....
+        mat = self.pc.getMatrix()
+
+        # compute matrix to translate 
+        tm = self.mb.make_matrix(0,0,0,1,(xc - xsc),(yc - ysc),0)
+        tsm = self.mb.multiply_matrix( mat, tm)
+
+        try:
+          # compute the ratio if possible
+          ratio = min(abs((x1 - x0)/(xsv[0]-xsv[1])) , 
+                      abs((y1 - y0)/(ysv[0]-ysv[1])))
+
+          ts = self.mb.make_matrix(0,0,0,ratio,0,0,0)
+          newm = self.mb.multiply_matrix( ts, tsm)
+
+          # apply the stuff
+          self.pc.setMatrix(newm)
+
+        except:
+          pass
+
+
+    self.cell = MyCell(self.ref,self.display,self.zoom, self.callback)
+    self.cell.addReference(self.ref)
+
+  def zoomit(self, samples):
+    """
+    To force a zoom with an array of samples, call this...
+    samples[2][] contains values for the corner points, in
+    the proper scalar type.  samples[0][] is x,y of upperleft.
+    samples[1][] is x,y is lower right.
+    
+    """
+    self.cell.zoomIt(samples)
+
+
+class HandlePickEvent(DisplayListener):
+  """
+  Helper class for interfacing to the VisAD Display when
+  the user drags the mouse around with both buttons
+  pressed (which causes a cursor to appear and the domain
+  readout values to be shown).  When the mouse buttons
+  are released, the applications 'handler' will be called.
+  """
+
+  def __init__(self, display, handler):
+    """
+    The 'display' is the display.  The 'handler' is the
+    method in the caller's program that will be called
+    when the user releases the buttons.  It must have
+    two parameters: x,y that will get the values of
+    the domain values for the x- and y-axis, respectively.
+    """
+    self.x, self.y, self.z, self.display=getDisplayMaps(display)
+    self.dr = self.display.getDisplayRenderer()
+    self.display.addDisplayListener(self)
+    self.handler = handler
+
+  def displayChanged(self, event):
+    """
+    Handle event
+    """
+    # first, confirm this is only a drag, with no other key down
+    try:
+      ie = event.getInputEvent()
+      if ie.isControlDown(): return
+      if ie.isShiftDown(): return
+    except:
+      pass
+
+    # multiple MOUSE_RELEASE type events will happen,
+    # but only one has values...
+
+    if event.getId() == DisplayEvent.MOUSE_RELEASED:
+      self.xx = self.dr.getDirectAxisValue(self.x)
+      self.yy = self.dr.getDirectAxisValue(self.y)
+      if not Double(self.xx).isNaN():  # there will be NaN values
+        self.display.disableAction()
+        self.handler(self.xx, self.yy)  # to get a new image
+        self.display.enableAction()
+
+from visad import ControlListener
+class LinkBoxControl(ControlListener):
+  def __init__(self, mydisplay, otherdisplay):
+    self.me = mydisplay
+    self.other = otherdisplay
+    self.control = self.me.getProjectionControl()
+    self.control.addControlListener(self)
+    self.mat = self.control.getMatrix()
+  
+  def controlChanged(self,e):
+    self.otherpc = self.other.getProjectionControl()
+    if not self.control.equals(self.otherpc):
+      self.mat = self.control.getMatrix()
+      self.otherpc.setMatrix(self.mat)
diff --git a/visad/python/vis_test.py b/visad/python/vis_test.py
new file mode 100644
index 0000000..828f51a
--- /dev/null
+++ b/visad/python/vis_test.py
@@ -0,0 +1,23 @@
+from visad.python.JPythonMethods import *
+"""\
+vis_test.py
+
+An example of a JPython script that utilizes
+VisAD functionality.
+
+To execute at the command prompt, type:
+  jpython vis_test.py
+
+To execute within the JPython editor, launch
+the editor with:
+  java visad.python.JPythonFrame
+Then open this file and choose "Command", "Run"
+"""
+
+# load a GIF image file
+data = load("../ss/cut.gif")
+
+# plot the GIF image
+clearplot()
+plot(data)
+
diff --git a/visad/rabin/Rain.java b/visad/rabin/Rain.java
new file mode 100644
index 0000000..547c76d
--- /dev/null
+++ b/visad/rabin/Rain.java
@@ -0,0 +1,1049 @@
+
+//
+// Rain.java
+//
+
+package visad.rabin;
+
+// import needed classes
+import visad.*;
+import visad.java2d.DisplayImplJ2D;
+import visad.java2d.DirectManipulationRendererJ2D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.TwoDDisplayRendererJ3D;
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.util.VisADSlider;
+import visad.util.LabeledColorWidget;
+import visad.util.Delay;
+import visad.data.Form;
+import visad.data.vis5d.Vis5DForm;
+import visad.data.netcdf.Plain;
+import visad.formula.*;
+import java.rmi.RemoteException;
+import java.rmi.NotBoundException;
+import java.rmi.AccessException;
+import java.rmi.Naming;
+import java.net.MalformedURLException;
+import java.io.IOException;
+import java.util.Vector;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.border.*;
+
+public class Rain implements ActionListener, ControlListener {
+
+  /** RemoteServerImpl for server
+      this Rain is a server if server_server != null
+      this Rain is stand-alone if server_server == null
+        and client_server == null */
+  RemoteServerImpl server_server = null;
+
+  /** RemoteServer for client
+      this Rain is a client if client_server != null */
+  RemoteServer client_server = null;
+
+  /** whether to use Java2D or Java3D */
+  boolean twod = false;
+
+  static final int N_COLUMNS = 3;
+  static final int N_ROWS = 4;
+  final JPanel[] column_panels =
+    new JPanel[N_COLUMNS];
+  final JPanel[][] cell_panels =
+    new JPanel[N_ROWS][N_COLUMNS];
+  final DataReference[][] cell_refs =
+    new DataReferenceImpl[N_ROWS][N_COLUMNS];
+  final CellImpl[][] cells =
+    new CellImpl[N_ROWS][N_COLUMNS];
+  final DisplayImpl[][] displays =
+    new DisplayImpl[N_ROWS][N_COLUMNS];
+  final RemoteDisplayImpl[][] remote_displays =
+    new RemoteDisplayImpl[N_ROWS][N_COLUMNS];
+  final CellImpl[][] formula_update =
+    new CellImpl[N_ROWS][N_COLUMNS];
+  final boolean[][] display_done =
+    new boolean[N_ROWS][N_COLUMNS];
+
+  static final String[][] cell_names =
+    {{"A1", "B1", "C1"}, {"A2", "B2", "C2"},
+     {"A3", "B3", "C3"}, {"A4", "B4", "C4"}};
+
+  static final String[][] cell_formulas =
+    {{"", "A1[0]",
+      "(10^(extract(B1, 0)/10)/num300) ^ (1/num1_4)"},
+     {"(10^(extract(B1, 1)/10)/num300) ^ (1/num1_4)",
+      "(10^(extract(B1, 2)/10)/num300) ^ (1/num1_4)",
+      "(10^(extract(B1, 3)/10)/num300) ^ (1/num1_4)"},
+     {"(10^(extract(B1, 4)/10)/num300) ^ (1/num1_4)",
+      "(10^(extract(B1, 5)/10)/num300) ^ (1/num1_4)",
+      "(10*C1 + 10*A2 + 10*B2 + 10*C2 + 10*A3 + 3*B3)/53"},
+     {"extract(B1, 6)", "extract(B1, 7)", "extract(B1, 8)"}};
+
+  JLabel[][] cell_fields = new JLabel[N_ROWS][N_COLUMNS];
+
+  /** width and height of the UI frame */
+  static final int WIDTH = 1100;
+  static final int HEIGHT = 900;
+
+  static final double MIN = 0.0;
+  static final double MAX = 300.0;
+  static final double MAXH2 = 10.0;
+
+  /** remoted DataReferences */
+  DataReference ref300 = null;
+  DataReference ref1_4 = null;
+  DataReference refMAX = null;
+  DataReference ref_cursor = null;
+  DataReference ref_vis5d = null;
+  DataReference ref_projection = null;
+  DataReference ref_colorH1 = null;
+  DataReference ref_colorH2 = null;
+  DataReference[][] cell_text = new DataReference[N_ROWS][N_COLUMNS];
+
+  /** widgets and controls */
+  VisADSlider slider300;
+  LabeledColorWidget color_widgetH1 = null;
+  LabeledColorWidget color_widgetH2 = null;
+  ColorControl color_controlH1 = null;
+  ColorControl color_controlH2 = null;
+  ColorControl[][] color_controls = new ColorControl[N_ROWS][N_COLUMNS];
+  ProjectionControl[][] projection_controls =
+    new ProjectionControl[N_ROWS][N_COLUMNS];
+  ScalarMap[][] color_maps = new ScalarMap[N_ROWS][N_COLUMNS];
+  ScalarMap color_mapH1 = null;
+  ScalarMap color_mapH2 = null;
+
+  /** seventh band, used for point mode detection during band changes */
+  RealType band7 = null;
+
+  /** cursor */
+  RealTupleType cursor_type = null;
+
+  /** formula-related objects */
+  FormulaManager f_manager = null;
+  JTextField[][] jtfield = new JTextField[N_ROWS][N_COLUMNS];
+
+  // type 'java Rain' to run this application
+  public static void main(String args[])
+         throws VisADException, RemoteException, IOException {
+    if (args == null || args.length < 1) {
+      System.out.println("run 'java visad.rabin.Rain file.v5d'\n or");
+      System.out.println("    'java visad.rabin.Rain file.nc'\n or");
+      System.out.println("    'java visad.rabin.Rain server.ip.name'");
+      System.exit(1);
+    }
+    Rain rain = new Rain(args);
+
+    rain.makeRain();
+  }
+
+  private Rain(String args[])
+          throws VisADException, RemoteException {
+    if (args[0].endsWith(".v5d") || args[0].endsWith(".nc")) {
+      // this is server
+      // try to set up a RemoteServer
+      server_server = new RemoteServerImpl();
+      try {
+        Naming.rebind("///Rain", server_server);
+      }
+      catch (MalformedURLException e) {
+        System.out.println("Cannot set up server - running as stand-alone");
+        server_server = null;
+      }
+      catch (AccessException e) {
+        System.out.println("Cannot set up server - running as stand-alone");
+        server_server = null;
+      }
+      catch (RemoteException e) {
+        System.out.println("Cannot set up server - running as stand-alone");
+        server_server = null;
+      }
+
+      Form form = null;
+      if (args[0].endsWith(".v5d")) {
+        try {
+          form = new Vis5DForm();
+        } catch (UnsatisfiedLinkError e) {
+          System.out.println("Cannot find vis5d library: " + e.getMessage());
+          System.exit(1);
+        }
+      }
+      else {
+        form = new Plain();
+      }
+      FieldImpl vis5d = null;
+      try {
+        vis5d = (FieldImpl) form.open(args[0]);
+      }
+      catch (Exception e) {
+        System.out.println(e.getMessage());
+        System.exit(0);
+      }
+      if (vis5d == null) {
+        System.out.println("bad Vis5D file read");
+        System.exit(0);
+      }
+
+      ref300 = new DataReferenceImpl("num300");
+      ref1_4 = new DataReferenceImpl("num1_4");
+      refMAX = new DataReferenceImpl("colorMAX");
+      ref_cursor = new DataReferenceImpl("cursor");
+      ref_vis5d = new DataReferenceImpl("vis5d");
+      ref_projection = new DataReferenceImpl("projection");
+      ref_colorH1 = new DataReferenceImpl("colorH1");
+      ref_colorH2 = new DataReferenceImpl("colorH2");
+      for (int i=0; i<N_ROWS; i++) {
+        for (int j=0; j<N_COLUMNS; j++) {
+          cell_text[i][j] = new DataReferenceImpl("text_" + i + "_" + j);
+        }
+      }
+
+      ref_vis5d.setData(vis5d);
+      if (server_server != null) {
+        // set RemoteDataReferenceImpls in RemoteServer
+        RemoteDataReferenceImpl[] refs =
+          new RemoteDataReferenceImpl[8 + N_ROWS*N_COLUMNS];
+        refs[0] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) ref300);
+        refs[1] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) ref1_4);
+        refs[2] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) refMAX);
+        refs[3] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) ref_cursor);
+        refs[4] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) ref_vis5d);
+        refs[5] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) ref_projection);
+        refs[6] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) ref_colorH1);
+        refs[7] =
+          new RemoteDataReferenceImpl((DataReferenceImpl) ref_colorH2);
+        for (int i=0; i<N_ROWS; i++) {
+          for (int j=0; j<N_COLUMNS; j++) {
+            refs[8 + N_COLUMNS*i + j] =
+              new RemoteDataReferenceImpl((DataReferenceImpl) cell_text[i][j]);
+          }
+        }
+        server_server.setDataReferences(refs);
+      }
+    }
+    else { // if (!(args[0].endsWith(".v5d") || args[0].endsWith(".nc")))
+      // this is client
+      // try to connect to RemoteServer
+      String domain = "//" + args[0] + "/Rain";
+      try {
+        client_server = (RemoteServer) Naming.lookup(domain);
+      }
+      catch (MalformedURLException e) {
+        System.out.println("Cannot connect to server");
+        System.exit(0);
+      }
+      catch (NotBoundException e) {
+        System.out.println("Cannot connect to server");
+        System.exit(0);
+      }
+      catch (AccessException e) {
+        System.out.println("Cannot connect to server");
+        System.exit(0);
+      }
+      catch (RemoteException e) {
+        System.out.println("Cannot connect to server");
+        System.exit(0);
+      }
+      RemoteDataReference[] refs = client_server.getDataReferences();
+      if (refs == null) {
+        System.out.println("Cannot connect to server");
+        System.exit(0);
+      }
+      ref300 = refs[0];
+      ref1_4 = refs[1];
+      refMAX = refs[2];
+      ref_cursor = refs[3];
+      // localize the big data set once at the start
+      ref_vis5d = new DataReferenceImpl("vis5d");
+      ref_vis5d.setData(refs[4].getData().local());
+      ref_projection = refs[5];
+      ref_colorH1 = refs[6];
+      ref_colorH2 = refs[7];
+      for (int i=0; i<N_ROWS; i++) {
+        for (int j=0; j<N_COLUMNS; j++) {
+          cell_text[i][j] = refs[8 + N_COLUMNS*i + j];
+        }
+      }
+    }
+  }
+
+  private void makeRain()
+         throws VisADException, RemoteException, IOException {
+    FieldImpl vis5d = (FieldImpl) ref_vis5d.getData();
+
+    FunctionType vis5d_type = (FunctionType) vis5d.getType();
+    RealType time = (RealType) vis5d_type.getDomain().getComponent(0);
+    FunctionType grid_type = (FunctionType) vis5d_type.getRange();
+    RealTupleType domain = grid_type.getDomain();
+    RealType x_domain = (RealType) domain.getComponent(0);
+    RealType y_domain = (RealType) domain.getComponent(1);
+    RealTupleType range = (RealTupleType) grid_type.getRange();
+    RealType rangeH1 = (RealType) range.getComponent(0);
+    RealType rangeH2 = (RealType) range.getComponent(8);
+    int dim = range.getDimension();
+    RealType[] range_types = new RealType[dim];
+    for (int i=0; i<dim; i++) {
+      range_types[i] = (RealType) range.getComponent(i);
+    }
+
+    // create cursor
+    final RealType shape = RealType.getRealType("shape");
+    RealTupleType cursor_type = new RealTupleType(x_domain, y_domain, shape);
+    SampledSet grid_set =
+      (SampledSet) ((FlatField) vis5d.getSample(0)).getDomainSet();
+    float[] lows = grid_set.getLow();
+    float[] his = grid_set.getHi();
+    double cursorx = 0.5 * (lows[0] + his[0]);
+    double cursory = 0.5 * (lows[1] + his[1]);
+    RealTuple cursor =
+      new RealTuple(cursor_type, new double[] {cursorx, cursory, 0.0});
+    ref_cursor.setData(cursor);
+    final Gridded1DSet shape_count_set =
+      new Gridded1DSet(shape, new float[][] {{0.0f}}, 1);
+    VisADLineArray cross = new VisADLineArray();
+    cross.coordinates = new float[]
+      {0.1f,  0.0f, 0.0f,    -0.1f,  0.0f, 0.0f,
+       0.0f, -0.1f, 0.0f,     0.0f,  0.1f, 0.0f};
+    cross.colors = new byte[]
+      {-1,  -1, -1,     -1,  -1, -1,
+       -1,  -1, -1,     -1,  -1, -1};
+    cross.vertexCount = cross.coordinates.length / 3;
+    final VisADGeometryArray[] shapes = {cross};
+
+    // create formula manager object with standard options
+    f_manager = FormulaUtil.createStandardManager();
+    f_manager.createVar("num300", ref300);
+    f_manager.createVar("num1_4", ref1_4);
+
+    //
+    // construct JFC user interface
+    //
+
+    // create a JFrame
+    JFrame frame = new JFrame("Vis5D");
+    WindowListener l = new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    };
+    frame.addWindowListener(l);
+    frame.setSize(WIDTH, HEIGHT);
+    frame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    frame.setLocation(screenSize.width/2 - WIDTH/2,
+                      screenSize.height/2 - HEIGHT/2);
+
+    // create big_panel JPanel in frame
+    JPanel big_panel = new JPanel();
+    big_panel.setLayout(new BoxLayout(big_panel, BoxLayout.X_AXIS));
+    big_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    big_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.getContentPane().add(big_panel);
+
+    final JPanel left_panel = new JPanel();
+    left_panel.setLayout(new BoxLayout(left_panel, BoxLayout.Y_AXIS));
+    left_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    left_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    big_panel.add(left_panel);
+
+    JPanel display_panel = new JPanel();
+    display_panel.setLayout(new BoxLayout(display_panel, BoxLayout.X_AXIS));
+    display_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    display_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    big_panel.add(display_panel);
+
+    // create column JPanels
+    for (int j=0; j<N_COLUMNS; j++) {
+      column_panels[j] = new JPanel();
+      column_panels[j].setLayout(new BoxLayout(column_panels[j],
+                                            BoxLayout.Y_AXIS));
+      column_panels[j].setAlignmentY(JPanel.TOP_ALIGNMENT);
+      column_panels[j].setAlignmentX(JPanel.LEFT_ALIGNMENT);
+      display_panel.add(column_panels[j]);
+    }
+    // create row JPanels
+    for (int i=0; i<N_ROWS; i++) {
+
+      // create cell JPanels
+      for (int j=0; j<N_COLUMNS; j++) {
+        cell_panels[i][j] = new JPanel();
+        cell_panels[i][j].setLayout(new BoxLayout(cell_panels[i][j],
+                                                 BoxLayout.Y_AXIS));
+        cell_panels[i][j].setAlignmentY(JPanel.TOP_ALIGNMENT);
+        cell_panels[i][j].setAlignmentX(JPanel.LEFT_ALIGNMENT);
+        column_panels[j].add(cell_panels[i][j]);
+        if (i == 0 && j ==0) {
+          cell_refs[i][j] = ref_vis5d;
+        }
+        else {
+          cell_refs[i][j] = new DataReferenceImpl("cell_" + i + "_" + j);
+        }
+        displays[i][j] = newDisplay("display_" + i + "_" + j);
+        if (client_server != null) {
+          remote_displays[i][j] = new RemoteDisplayImpl(displays[i][j]);
+        }
+        displays[i][j].addMap(new ScalarMap(y_domain, Display.XAxis));
+        displays[i][j].addMap(new ScalarMap(x_domain, Display.YAxis));
+
+        // add cursor to cell
+        ScalarMap shape_map = new ScalarMap(shape, Display.Shape);
+        displays[i][j].addMap(shape_map);
+        ShapeControl shape_control = (ShapeControl) shape_map.getControl();
+        shape_control.setShapeSet(shape_count_set);
+        shape_control.setShapes(shapes);
+
+        projection_controls[i][j] = displays[i][j].getProjectionControl();
+        projection_controls[i][j].addControlListener(this);
+
+        display_done[i][j] = false;
+
+        // add cell to FormulaManager database
+        f_manager.createVar(cell_names[i][j], cell_refs[i][j]);
+        f_manager.setTextRef(cell_names[i][j], cell_text[i][j]);
+
+        // construct cell's formula text field
+        JPanel fpanel = new JPanel();
+        fpanel.setLayout(new BoxLayout(fpanel, BoxLayout.X_AXIS));
+        jtfield[i][j] = new JTextField(cell_formulas[i][j]);
+        Dimension psize = jtfield[i][j].getPreferredSize();
+        Dimension msize = jtfield[i][j].getMaximumSize();
+        msize.height = psize.height;
+        jtfield[i][j].setMaximumSize(msize);
+        jtfield[i][j].addActionListener(this);
+        jtfield[i][j].setActionCommand("fc_" + cell_names[i][j]);
+        fpanel.add(new JLabel(cell_names[i][j] + ": "));
+        fpanel.add(jtfield[i][j]);
+        cell_panels[i][j].add(fpanel);
+
+        // construct cell's CellImpl for detecting changes in the cell
+        final int fi = i;
+        final int fj = j;
+        final Rain rain = this;
+
+        formula_update[i][j] = new CellImpl() {
+          public void doAction() {
+            // save old mappings
+            ScalarMap[] maps = null;
+            Vector v = displays[fi][fj].getMapVector();
+            maps = new ScalarMap[v.size()];
+            for (int k=0; k<maps.length; k++) {
+              maps[k] = (ScalarMap) v.elementAt(k);
+            }
+
+            // identify whether the mappings need to be replaced
+            boolean change = false;
+            for (int k=0; k<maps.length && !change; k++) {
+              RealType ort = (RealType) maps[k].getScalar();
+              DisplayRealType drt = maps[k].getDisplayScalar();
+              if (drt.equals(Display.RGB)) {
+                try {
+                  Data d = cell_refs[fi][fj].getData();
+                  if (d != null) {
+                    FunctionType f = (FunctionType) d.getType();
+                    RealType rt = (RealType) f.getRange();
+                    if (!rt.equals(ort)) change = true;
+                  }
+                }
+                catch (ClassCastException exc) { }
+                catch (VisADException exc) { }
+                catch (RemoteException exc) { }
+              }
+            }
+
+            if (change) {
+              // clear old mappings
+              try {
+                displays[fi][fj].removeReference(cell_refs[fi][fj]);
+                if (color_controls[fi][fj] != null || (fi == 0 && fj == 2)) {
+                  removeCursor(fi, fj);
+                }
+                displays[fi][fj].clearMaps();
+              }
+              catch (VisADException exc) { }
+              catch (RemoteException exc) { }
+
+              // reapply mappings
+              for (int k=0; k<maps.length; k++) {
+                boolean done = false;
+                RealType ort = (RealType) maps[k].getScalar();
+                DisplayRealType drt = maps[k].getDisplayScalar();
+                if (drt.equals(Display.RGB)) {
+                  // fix ScalarMap and color table to use new range RealType
+                  try {
+                    Data d = cell_refs[fi][fj].getData();
+                    FunctionType f = (FunctionType) d.getType();
+                    RealType rt = (RealType) f.getRange();
+                    if (!rt.equals(ort)) {
+                      // range RealType has changed
+                      ScalarMap sm = new ScalarMap(rt, Display.RGB);
+                      maps[k] = sm;
+                      ColorControl cc = null;
+                      double max;
+                      if (fi == 3 && fj == 2) {
+                        cc = color_controlH2;
+                        max = MAXH2;
+                      }
+                      else {
+                        cc = color_controlH1;
+                        max = ((Real) refMAX.getData()).getValue();
+                      }
+                      if (cc != null) {
+                        float[][] table = cc.getTable();
+                        color_maps[fi][fj] = sm;
+                        color_maps[fi][fj].setRange(MIN, max);
+                        displays[fi][fj].addMap(sm);
+                        done = true;
+                        color_controls[fi][fj] = (ColorControl)
+                          color_maps[fi][fj].getControl();
+                        if (table != null) {
+                          color_controls[fi][fj].setTable(table);
+                        }
+                      }
+                      // if cell displays band #7, enable point mode
+                      boolean isBand7 = rt.equals(band7);
+                      GraphicsModeControl mode =
+                        displays[fi][fj].getGraphicsModeControl();
+                      mode.setTextureEnable(!isBand7);
+                      mode.setPointMode(isBand7);
+                      mode.setPointSize(5.0f);
+                    }
+                  }
+                  catch (ClassCastException exc) { }
+                  catch (VisADException exc) { }
+                  catch (RemoteException exc) { }
+                }
+                else if (drt.equals(Display.Shape)) {
+                  try {
+                    ScalarMap shape_mapx = new ScalarMap(shape, Display.Shape);
+                    displays[fi][fj].addMap(shape_mapx);
+                    ShapeControl shape_controlx =
+                      (ShapeControl) shape_mapx.getControl();
+                    shape_controlx.setShapeSet(shape_count_set);
+                    shape_controlx.setShapes(shapes);
+                    done = true;
+                  }
+                  catch (VisADException exc) { }
+                  catch (RemoteException exc) { }
+                }
+                if (!done) {
+                  try {
+                    displays[fi][fj].addMap(maps[k]);
+                  }
+                  catch (VisADException exc) { }
+                  catch (RemoteException exc) { }
+                }
+              }
+              try {
+                displays[fi][fj].addReference(cell_refs[fi][fj]);
+                if (color_controls[fi][fj] != null) addCursor(fi, fj);
+              }
+              catch (VisADException exc) { }
+              catch (RemoteException exc) { }
+            }
+          }
+        };
+
+        // construct cell's display
+        JPanel d_panel = (JPanel) displays[i][j].getComponent();
+        d_panel.setAlignmentX(JPanel.CENTER_ALIGNMENT);
+        Border etchedBorder5 =
+          new CompoundBorder(new EtchedBorder(),
+                             new EmptyBorder(5, 5, 5, 5));
+        d_panel.setBorder(etchedBorder5);
+        cell_panels[i][j].add(d_panel);
+
+        // construct cell's current value label
+        cell_fields[i][j] = new JLabel("---");
+        cell_fields[i][j].setAlignmentX(JLabel.CENTER_ALIGNMENT);
+        cell_fields[i][j].setMinimumSize(jtfield[i][j].getMinimumSize());
+        cell_fields[i][j].setPreferredSize(jtfield[i][j].getPreferredSize());
+        cell_fields[i][j].setMaximumSize(jtfield[i][j].getMaximumSize());
+        cell_panels[i][j].add(cell_fields[i][j]);
+
+      } // end for (int j=0; j<N_ROWS; j++)
+    } // end for (int i=0; i<N_COLUMNS; i++)
+
+    slider300 = new VisADSlider("num300", 0, 600, 300, 1.0,
+                                ref300, RealType.Generic);
+    VisADSlider slider1_4 = new VisADSlider("num1_4", 0, 280, 140, 0.01,
+                                            ref1_4, RealType.Generic);
+    VisADSlider sliderMAX = new VisADSlider("colorMAX", 0, 1000, ((int) MAX),
+                                            1.0, refMAX, RealType.Generic);
+
+    left_panel.add(slider300);
+    left_panel.add(new JLabel("  "));
+    left_panel.add(slider1_4);
+    left_panel.add(new JLabel("  "));
+    left_panel.add(sliderMAX);
+    left_panel.add(new JLabel("  "));
+
+    // set up cells' formulas
+    for (int i=0; i<N_ROWS; i++) {
+      for (int j=0; j<N_COLUMNS; j++) {
+        if (i != 0 || j != 0) {
+          f_manager.assignFormula(cell_names[i][j], cell_formulas[i][j]);
+        }
+      }
+    }
+
+    // set up cells' displays
+
+    // set up hidden cell 1, for use with the first ColorControl (cells C1-B4)
+    color_mapH1 = new ScalarMap(rangeH1, Display.RGB);
+    DisplayImpl displayH1 = newDisplay("display_hidden_1");
+    displayH1.addMap(color_mapH1);
+    color_widgetH1 = new LabeledColorWidget(color_mapH1);
+    Dimension d = new Dimension(500, 170);
+    color_widgetH1.setMaximumSize(d);
+    color_mapH1.setRange(MIN, MAX);
+
+    left_panel.add(color_widgetH1);
+    left_panel.add(new JLabel("  "));
+
+    color_controlH1 = (ColorControl) color_mapH1.getControl();
+    // listener sets all non-null color_controls[i][j]
+    // for ControlEvents from color_control
+    color_controlH1.addControlListener(this);
+    if (server_server != null) {
+      float[][] table = color_controlH1.getTable();
+      Integer1DSet set = new Integer1DSet(table[0].length);
+      FlatField color_fieldH1 =
+        new FlatField(FunctionType.REAL_1TO3_FUNCTION, set);
+      color_fieldH1.setSamples(table);
+      ref_colorH1.setData(color_fieldH1);
+    }
+
+    // set up cell for detecting color table changes and updating all displays
+    if (server_server != null || client_server != null) {
+      CellImpl color_cellH1 = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+          FlatField field = (FlatField) ref_colorH1.getData().local();
+          float[][] table = field.getFloats();
+          float[][] old_table = color_controlH1.getTable();
+          boolean identical = true;
+          for (int i=0; i<3; i++) {
+            if (identical) {
+              for (int j=0; j<table[i].length; j++) {
+                if (Math.abs(table[i][j] - old_table[i][j]) > 0.00001) {
+                  identical = false;
+                  break;
+                }
+              }
+            }
+          }
+          if (!identical) {
+            color_controlH1.setTable(table);
+          }
+        }
+      };
+      if (client_server != null) {
+        RemoteCellImpl remote_cell = new RemoteCellImpl(color_cellH1);
+        remote_cell.addReference(ref_colorH1);
+      }
+      else {
+        color_cellH1.addReference(ref_colorH1);
+      }
+    }
+
+    // set up hidden cell 2, for use with the second ColorControl (cell C4)
+    color_mapH2 = new ScalarMap(rangeH2, Display.RGB);
+    DisplayImpl displayH2 = newDisplay("display_hidden_2");
+    displayH2.addMap(color_mapH2);
+    color_widgetH2 = new LabeledColorWidget(color_mapH2);
+
+    Dimension dH2 = new Dimension(500, 170);
+    color_widgetH2.setMaximumSize(dH2);
+    color_mapH2.setRange(MIN, MAXH2);
+    color_controlH2 = (ColorControl) color_mapH2.getControl();
+    color_controlH2.addControlListener(this);
+
+    if (server_server != null) {
+      float[][] table = color_controlH2.getTable();
+      Integer1DSet set = new Integer1DSet(table[0].length);
+      FlatField color_fieldH2 =
+        new FlatField(FunctionType.REAL_1TO3_FUNCTION, set);
+      color_fieldH2.setSamples(table);
+      ref_colorH2.setData(color_fieldH2);
+    }
+
+    if (server_server != null || client_server != null) {
+      CellImpl color_cellH2 = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+          FlatField field = (FlatField) ref_colorH2.getData().local();
+          float[][] table = field.getFloats();
+          float[][] old_table = color_controlH2.getTable();
+          boolean identical = true;
+          for (int i=0; i<3; i++) {
+            if (identical) {
+              for (int j=0; j<table[i].length; j++) {
+                if (Math.abs(table[i][j] - old_table[i][j]) > 0.00001) {
+                  identical = false;
+                  break;
+                }
+              }
+            }
+          }
+          if (!identical) {
+            color_controlH2.setTable(table);
+          }
+        }
+      };
+      if (client_server != null) {
+        RemoteCellImpl remote_cell = new RemoteCellImpl(color_cellH2);
+        remote_cell.addReference(ref_colorH2);
+      }
+      else {
+        color_cellH2.addReference(ref_colorH2);
+      }
+    }
+
+    left_panel.add(color_widgetH2);
+    left_panel.add(new JLabel("  "));
+
+    // set up cell A1
+    displays[0][0].addMap(new ScalarMap(range_types[0], Display.Red));
+    displays[0][0].addMap(new ScalarMap(range_types[1], Display.Green));
+    displays[0][0].addMap(new ScalarMap(range_types[2], Display.Blue));
+    displays[0][0].addMap(new ScalarMap(time, Display.Animation));
+    displays[0][0].addReference(cell_refs[0][0]);
+    display_done[0][0] = true;
+
+    if (server_server != null) {
+      double[] matrix = projection_controls[0][0].getMatrix();
+      if (matrix.length != 6) {
+        matrix = ProjectionControl.matrix3DTo2D(matrix);
+      }
+      Integer1DSet set = new Integer1DSet(6);
+      FlatField projection_field =
+        new FlatField(FunctionType.REAL_1TO1_FUNCTION, set);
+      projection_field.setSamples(new double[][] {matrix});
+      ref_projection.setData(projection_field);
+    }
+
+    if (server_server != null || client_server != null) {
+      CellImpl projection_cell = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+          FlatField field = (FlatField) ref_projection.getData().local();
+          double[] matrix = field.getValues()[0];
+          double[] old = projection_controls[0][0].getMatrix();
+          double[] old_matrix = null;
+          if (old.length == 6) {
+            old_matrix = old;
+          }
+          else {
+            old_matrix = ProjectionControl.matrix3DTo2D(old);
+          }
+          boolean identical = true;
+          for (int j=0; j<matrix.length; j++) {
+            if (Math.abs(matrix[j] - old_matrix[j]) > 0.00001) {
+              identical = false;
+              break;
+            }
+          }
+          if (!identical) {
+            if (old.length == 6) {
+              projection_controls[0][0].setMatrix(matrix);
+            }
+            else {
+              double[] mat = ProjectionControl.matrix2DTo3D(matrix);
+              projection_controls[0][0].setMatrix(mat);
+            }
+
+          }
+        }
+      };
+      if (client_server != null) {
+        RemoteCellImpl remote_cell = new RemoteCellImpl(projection_cell);
+        remote_cell.addReference(ref_projection);
+      }
+      else {
+        projection_cell.addReference(ref_projection);
+      }
+    }
+
+    // cell B1
+    displays[0][1].addMap(new ScalarMap(range_types[0], Display.Red));
+    displays[0][1].addMap(new ScalarMap(range_types[1], Display.Green));
+    displays[0][1].addMap(new ScalarMap(range_types[2], Display.Blue));
+    displays[0][1].addReference(cell_refs[0][1]);
+    display_done[0][1] = true;
+
+    // cell C1
+    finishDisplay(client_server, (RealType) range.getComponent(0), 0, 2);
+
+    // cell A2
+    finishDisplay(client_server, (RealType) range.getComponent(1), 1, 0);
+
+    // cell B2
+    finishDisplay(client_server, (RealType) range.getComponent(2), 1, 1);
+
+    // cell C2
+    finishDisplay(client_server, (RealType) range.getComponent(3), 1, 2);
+
+    // cell A3
+    finishDisplay(client_server, (RealType) range.getComponent(4), 2, 0);
+
+    // cell B3
+    finishDisplay(client_server, (RealType) range.getComponent(5), 2, 1);
+
+    // cell C3
+    finishDisplay(client_server, rangeH1, 2, 2);
+
+    // cell A4
+    finishDisplay(client_server, (RealType) range.getComponent(6), 3, 0);
+
+    // cell B4
+    band7 = (RealType) range.getComponent(7);
+    finishDisplay(client_server, band7, 3, 1);
+
+    // enable point mode for cell B4
+    GraphicsModeControl mode = displays[3][1].getGraphicsModeControl();
+    mode.setTextureEnable(false);
+    mode.setPointMode(true);
+    mode.setPointSize(5.0f);
+
+    // cell C4
+    finishDisplay(client_server, (RealType) range.getComponent(8), 3, 2);
+
+    // cell for updating formula text fields when formulas change
+    CellImpl cell_formulas = new CellImpl() {
+      public void doAction() {
+        for (int i=0; i<N_ROWS; i++) {
+          for (int j=0; j<N_COLUMNS; j++) {
+            try {
+              Text t = (Text) cell_text[i][j].getThing();
+              if (t != null) {
+                String s = t.getValue();
+                if (s == null) s = "";
+                if (!s.equals(jtfield[i][j].getText())) {
+                  final JTextField jtf = jtfield[i][j];
+                  final String str = s;
+                  SwingUtilities.invokeLater(new Runnable() {
+                    public void run() {
+                      jtf.setText(str);
+                    }
+                  });
+                }
+              }
+            }
+            catch (VisADException exc) { }
+            catch (RemoteException exc) { }
+          }
+        }
+      }
+    };
+    if (client_server != null) {
+      RemoteCellImpl remote_cell = new RemoteCellImpl(cell_formulas);
+      for (int i=0; i<N_ROWS; i++) {
+        for (int j=0; j<N_COLUMNS; j++) {
+          remote_cell.addReference(cell_text[i][j]);
+        }
+      }
+    }
+    else {
+      for (int i=0; i<N_ROWS; i++) {
+        for (int j=0; j<N_COLUMNS; j++) {
+          cell_formulas.addReference(cell_text[i][j]);
+        }
+      }
+    }
+
+    CellImpl cellMAX = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        double max = ((Real) refMAX.getData()).getValue();
+        color_mapH1.setRange(MIN, max);
+        for (int i=0; i<N_ROWS; i++) {
+          for (int j=0; j<N_COLUMNS; j++) {
+            if (color_maps[i][j] != null && !(i == 3 && j == 2)) {
+              color_maps[i][j].setRange(MIN, max);
+            }
+          }
+        }
+      }
+    };
+    if (client_server != null) {
+      RemoteCellImpl remote_cell = new RemoteCellImpl(cellMAX);
+      remote_cell.addReference(refMAX);
+    }
+    else {
+      cellMAX.addReference(refMAX);
+    }
+
+    CellImpl cell_cursor = new CellImpl() {
+      public void doAction() throws VisADException, RemoteException {
+        RealTuple c = (RealTuple) ref_cursor.getData();
+        RealTuple dom = new RealTuple(new Real[]
+                        {(Real) c.getComponent(0), (Real) c.getComponent(1)});
+        for (int i=0; i<N_ROWS; i++) {
+          for (int j=0; j<N_COLUMNS; j++) {
+            try {
+              FlatField field = (FlatField) cell_refs[i][j].getData();
+              double val = ((Real) field.evaluate(dom)).getValue();
+              cell_fields[i][j].setText("" + val);
+            }
+            catch (Exception e) {}
+          }
+        }
+      }
+    };
+    if (client_server != null) {
+      RemoteCellImpl remote_cell = new RemoteCellImpl(cell_cursor);
+      remote_cell.addReference(ref_cursor);
+    }
+    else {
+      cell_cursor.addReference(ref_cursor);
+    }
+
+    // add formula update cell references
+    new Delay(1000);  // give time for cell C3 to finish computing
+    for (int i=0; i<N_ROWS; i++) {
+      for (int j=0; j<N_COLUMNS; j++) {
+        formula_update[i][j].addReference(cell_refs[i][j]);
+      }
+    }
+
+    // make the JFrame visible
+    frame.setVisible(true);
+  }
+
+  /** creates a new Java3D or Java2D display */
+  public DisplayImpl newDisplay(String name) throws VisADException,
+                                                    RemoteException {
+    DisplayImpl display = null;
+    if (!twod) {
+      try {
+        display = new DisplayImplJ3D(name, new TwoDDisplayRendererJ3D());
+      }
+      catch (UnsatisfiedLinkError e) {
+        twod = true;
+      }
+    }
+    if (twod) display = new DisplayImplJ2D(name);
+    return display;
+  }
+
+  /** adds a cursor to display (i, j) */
+  public void addCursor(int i, int j) throws VisADException, RemoteException {
+    DataRenderer dr = null;
+    if (twod) {
+      dr = new DirectManipulationRendererJ2D();
+    }
+    else {
+      dr = new DirectManipulationRendererJ3D();
+    }
+    if (client_server != null) {
+      remote_displays[i][j].addReferences(dr, ref_cursor);
+    }
+    else {
+      displays[i][j].addReferences(dr, ref_cursor);
+    }
+  }
+
+  /** removes a cursor from display (i, j) */
+  public void removeCursor(int i, int j) throws VisADException,
+                                                RemoteException {
+    if (client_server != null) {
+      remote_displays[i][j].removeReference(ref_cursor);
+    }
+    else {
+      displays[i][j].removeReference(ref_cursor);
+    }
+  }
+
+  public void finishDisplay(RemoteServer cs, RealType rt, int i, int j)
+         throws VisADException, RemoteException {
+    color_maps[i][j] = new ScalarMap(rt, Display.RGB);
+    displays[i][j].addMap(color_maps[i][j]);
+    color_maps[i][j].setRange(MIN, MAX);
+    color_controls[i][j] = (ColorControl) color_maps[i][j].getControl();
+    ColorControl cc = null;
+    if (i == 3 && j == 4) {
+      // cell C4 uses ColorControl #2
+      cc = color_controlH2;
+    }
+    else {
+      // other cells all share ColorControl #1
+      cc = color_controlH1;
+    }
+    if (cc != null) {
+      float[][] table = cc.getTable();
+      if (table != null) color_controls[i][j].setTable(table);
+    }
+    displays[i][j].addReference(cell_refs[i][j]);
+    addCursor(i, j);
+    display_done[i][j] = true;
+  }
+
+  /** Handle changes to formula text fields */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.startsWith("fc_")) {
+      slider300.requestFocus();
+      JTextField f_field = (JTextField) e.getSource();
+      String formula = f_field.getText();
+      String cell_name = cmd.substring(3, cmd.length());
+      try {
+        f_manager.assignFormula(cell_name, formula);
+      }
+      catch (FormulaException exc) { }
+      catch (VisADException exc) { }
+    }
+  }
+
+  boolean in_proj = false;
+
+  /** Handle changes to controls */
+  public void controlChanged(ControlEvent e)
+         throws VisADException, RemoteException {
+    Control control = e.getControl();
+    if (control.equals(color_controlH1)) {
+      float[][] table = color_controlH1.getTable();
+      if (table != null) {
+        for (int i=0; i<N_ROWS; i++) {
+          for (int j=0; j<N_COLUMNS; j++) {
+            if (color_controls[i][j] != null && !(i == 3 && j == 2)) {
+              color_controls[i][j].setTable(table);
+            }
+          }
+        }
+      }
+      Field field = (Field) ref_colorH1.getData();
+      if (field != null) field.setSamples(table);
+    }
+    else if (control.equals(color_controlH2)) {
+      float[][] table = color_controlH2.getTable();
+      if (table != null) color_controls[3][2].setTable(table);
+    }
+    else if (!in_proj && control != null &&
+             control instanceof ProjectionControl) {
+      in_proj = true; // don't allow setMatrix below to re-trigger
+      double[] matrix = ((ProjectionControl) control).getMatrix();
+      for (int i=0; i<N_ROWS; i++) {
+        for (int j=0; j<N_COLUMNS; j++) {
+          if (control != projection_controls[i][j]) {
+            if (projection_controls[i][j] != null) {
+              projection_controls[i][j].setMatrix(matrix);
+            }
+          }
+        }
+      }
+      Field field = (Field) ref_projection.getData();
+      if (matrix.length == 6) {
+        if (field != null) field.setSamples(new double[][] {matrix});
+      }
+      else {
+        double[] mat = ProjectionControl.matrix3DTo2D(matrix);
+        if (field != null) field.setSamples(new double[][] {mat});
+      }
+      in_proj = false;
+    }
+  }
+
+}
+
diff --git a/visad/rabin/RainSheet.java b/visad/rabin/RainSheet.java
new file mode 100644
index 0000000..b4542b9
--- /dev/null
+++ b/visad/rabin/RainSheet.java
@@ -0,0 +1,318 @@
+
+//
+// RainSheet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.rabin;
+
+// import JFC packages
+import javax.swing.*;
+import javax.swing.border.*;
+
+// import AWT package
+import java.awt.*;
+import java.awt.event.*;
+
+// import needed Spread Sheet classes
+import visad.ss.BasicSSCell;
+import visad.ss.FancySSCell;
+
+// import other needed classes
+import visad.*;
+import visad.util.VisADSlider;
+import java.rmi.RemoteException;
+
+
+/** RainSheet is a "toy" version of visad.ss.SpreadSheet,
+    with only two cells.  It demonstrates how the developer can use
+    the classes in the visad.ss package as GUI components for their
+    own applications.<P> */
+public class RainSheet extends JFrame implements ActionListener {
+
+  // type 'java RainSheet' to run this application
+
+
+
+
+  static final int N_COLUMNS = 3;
+  static final int N_ROWS = 4;
+  static final JPanel[] row_panels = new JPanel[N_ROWS];
+  static final FancySSCell[] cells = new FancySSCell[N_ROWS * N_COLUMNS];
+  static final JTextField[] formulas = new JTextField[N_ROWS * N_COLUMNS];
+  static final JButton[] maps = new JButton[N_ROWS * N_COLUMNS];
+
+
+  /** the width and height of the UI frame */
+  static final int WIDTH = 1100;
+  static final int HEIGHT = 900;
+  static final int CELL_WIDTH = 200;
+  static final int CELL_HEIGHT = 200;
+
+  static String[] formula_name =
+    {"A1", "B1", "C1", "A2", "B2", "C2", "A3", "B3", "C3", "A4", "B4", "C4"};
+
+  static String[] formula_array =
+    {"file:dallas_2.5km_v5d.v5d",
+     "A1(0)",
+     "((10^((extract(B1,0))/10))/SLIDER300)^(1/SLIDER1_4)",
+     "((10^((extract(B1,1))/10))/SLIDER300)^(1/SLIDER1_4)",
+     "((10^((extract(B1,2))/10))/SLIDER300)^(1/SLIDER1_4)",
+     "((10^((extract(B1,3))/10))/SLIDER300)^(1/SLIDER1_4)",
+     "((10^((extract(B1,4))/10))/SLIDER300)^(1/SLIDER1_4)",
+     "((10^((extract(B1,5))/10))/SLIDER300)^(1/SLIDER1_4)",
+     "(10*C1+10*A2+10*B2+10*C2+10*A3+3*B3)/53",
+     "extract(B1,6)",
+     "extract(B1,7)",
+     "extract(B1,8)"};
+
+
+  /** The main method just constructs a RainSheet, displays it, and exits */
+  public static void main(String[] argv)
+         throws VisADException, RemoteException {
+    RainSheet ms = new RainSheet();
+    ms.pack();
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    ms.setSize(WIDTH, HEIGHT);
+    Dimension appSize = ms.getSize();
+    ms.setLocation(screenSize.width/2 - appSize.width/2,
+                   screenSize.height/2 - appSize.height/2);
+    ms.setVisible(true);
+  }
+
+  /** Creates a label with black text s and adds it to p */
+  private static void createLabel(JPanel p, String s) {
+    JLabel l = new JLabel(s);
+    l.setForeground(Color.black);
+    p.add(l);
+  }
+
+
+  /** Two text fields */
+  private JTextField Formula1, Formula2;
+
+  /** Constructs the RainSheet frame */
+  public RainSheet() throws VisADException, RemoteException {
+    // construct a frame with appropriate title
+    super("RainSheet");
+
+    // mapping dialog is the wrong color without this line
+    setBackground(Color.white);
+
+    // end program when this frame is closed
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        quitProgram();
+      }
+    });
+
+    // construct main panel
+    JPanel main = new JPanel();
+    setContentPane(main);
+    main.setLayout(new BoxLayout(main, BoxLayout.X_AXIS));
+
+    // construct left panel
+    JPanel left = new JPanel();
+    left.setBorder(new CompoundBorder(new EtchedBorder(),
+                                      new EmptyBorder(5, 10, 5, 10)));
+    left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
+    main.add(left);
+
+    JPanel display_panel = new JPanel();
+    display_panel.setLayout(new BoxLayout(display_panel, BoxLayout.Y_AXIS));
+    display_panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    display_panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    main.add(display_panel);
+
+
+    // add JLabels to left panel
+    createLabel(left, "RainSheet -- a custom spreadsheet");
+    createLabel(left, "for rain estimation");
+
+    DataReference ref300 = new DataReferenceImpl("num300");
+    DataReference ref1_4 = new DataReferenceImpl("num1_4");
+    VisADSlider slider300 = new VisADSlider("num300", 0, 600, 300, 1.0,
+                                            ref300, RealType.Generic);
+    VisADSlider slider1_4 = new VisADSlider("num1_4", 0, 280, 140, 0.01,
+                                            ref1_4, RealType.Generic);
+    left.add(slider300);
+    left.add(slider1_4);
+    BasicSSCell.createVar("SLIDER300", ref300);
+    BasicSSCell.createVar("SLIDER1_4", ref1_4);
+
+    // create panel for Quit button so the button can be centered
+    JPanel qpanel = new JPanel();
+    qpanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    qpanel.setLayout(new BoxLayout(qpanel, BoxLayout.X_AXIS));
+    left.add(Box.createRigidArea(new Dimension(0, 15)));
+    left.add(qpanel);
+
+    // add Quit button to its panel
+    JButton quit = new JButton("Quit");
+    quit.addActionListener(this);
+    quit.setActionCommand("quit");
+    qpanel.add(Box.createHorizontalGlue());
+    qpanel.add(quit);
+    qpanel.add(Box.createHorizontalGlue());
+
+    // don't let left panel shrink or grow in size
+    Dimension lps = left.getPreferredSize();
+    left.setMinimumSize(lps);
+    left.setMaximumSize(lps);
+
+    // construct two spreadsheet cells and related GUI components
+    int i = 0;
+    // create row JPanels
+    for (int k=0; k<N_ROWS; k++) {
+      row_panels[k] = new JPanel();
+      row_panels[k].setLayout(new BoxLayout(row_panels[k],
+                                            BoxLayout.X_AXIS));
+      row_panels[k].setAlignmentY(JPanel.TOP_ALIGNMENT);
+      row_panels[k].setAlignmentX(JPanel.LEFT_ALIGNMENT);
+      display_panel.add(row_panels[k]);
+
+      // create cell JPanels
+      for (int j=0; j<N_COLUMNS; j++) {
+        JPanel cellPanel = new JPanel();
+        cellPanel.setLayout(new BoxLayout(cellPanel, BoxLayout.Y_AXIS));
+        row_panels[k].add(cellPanel);
+        FancySSCell fCell = null;
+        try {
+          fCell = new FancySSCell(formula_name[i], this);
+          fCell.setDimension(true, false);  // set twoD = true, java2d = false
+          // fCell.setFormula(formula_array[i]);
+        }
+        catch (Exception exc) {
+          System.out.println("Could not create the first spreadsheet cell!");
+          System.out.println("Received the following exception:");
+          System.out.println(exc.toString());
+          System.exit(i);
+        }
+
+        fCell.setPreferredSize(new Dimension(CELL_WIDTH, CELL_HEIGHT));
+        fCell.setMaximumSize(new Dimension(CELL_WIDTH, CELL_HEIGHT));
+
+        JPanel bPanel = new JPanel();
+        bPanel.setLayout(new BoxLayout(bPanel, BoxLayout.X_AXIS));
+        JButton load = new JButton("Load");
+        load.addActionListener(this);
+        load.setActionCommand("load" + i);
+        bPanel.add(load);
+        JButton save = new JButton("Save");
+        save.addActionListener(this);
+        save.setActionCommand("save" + i);
+        bPanel.add(save);
+        JButton map = new JButton("Maps");
+        map.addActionListener(this);
+        map.setActionCommand("map" + i);
+        bPanel.add(map);
+        JButton show = new JButton("Show");
+        show.addActionListener(this);
+        show.setActionCommand("show" + i);
+        bPanel.add(show);
+        JTextField tf = new JTextField();
+
+        // WLH 2 Dec 98
+        Dimension msize = tf.getMaximumSize();
+        Dimension psize = tf.getPreferredSize();
+        msize.height = psize.height;
+        tf.setMaximumSize(msize);
+
+        JPanel lPanel = new JPanel();
+        lPanel.setLayout(new BoxLayout(lPanel, BoxLayout.X_AXIS));
+        lPanel.add(Box.createHorizontalGlue());
+        JLabel l = new JLabel(formula_name[i]);
+        l.setForeground(Color.blue);
+        lPanel.add(l);
+        lPanel.add(Box.createHorizontalGlue());
+        JPanel fPanel = new JPanel();
+        fPanel.setLayout(new BoxLayout(fPanel, BoxLayout.X_AXIS));
+        createLabel(fPanel, "Formula:  ");
+        JTextField textf = new JTextField(formula_array[i]);
+
+        // WLH 2 Dec 98
+        msize = textf.getMaximumSize();
+        psize = textf.getPreferredSize();
+        msize.height = psize.height;
+        textf.setMaximumSize(msize);
+
+        textf.addActionListener(this);
+        textf.setActionCommand("formula" + i);
+        fPanel.add(textf);
+        cellPanel.add(lPanel);
+        cellPanel.add(fPanel);
+        cellPanel.add(fCell);
+        cellPanel.add(bPanel);
+        cells[i] = fCell;
+        maps[i] = map;
+        formulas[i] = textf;
+        i++;
+      } // end for (int j=0; j<N_COLUMNS; j++)
+    } // end for (int k=0; k<N_ROWS; k++)
+    for (i=0; i<N_ROWS * N_COLUMNS; i++) {
+      // cells[i].setFormula(formula_array[i]);
+    }
+  }
+
+  /** This method handles button presses */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("quit")) {
+      quitProgram();
+      return;
+    }
+    for (int i=0; i<N_ROWS * N_COLUMNS; i++) {
+      if (cmd.equals("load" + i)) cells[i].loadDataDialog();
+      else if (cmd.equals("save" + i)) cells[i].saveDataDialog(true);
+      else if (cmd.equals("map" + i)) cells[i].addMapDialog();
+      else if (cmd.equals("show" + i)) cells[i].showWidgetFrame();
+      else if (cmd.equals("formula" + i)) {
+        maps[i].requestFocus();
+        try {
+          cells[i].setFormula(formulas[i].getText());
+        }
+        catch (Exception exc) { }
+      }
+    }
+  }
+
+  /** Waits for files to finish saving before quitting */
+  void quitProgram() {
+    Thread t = new Thread() {
+      public void run() {
+        if (BasicSSCell.isSaving()) {
+          System.out.println("Please wait for RainSheet to finish " +
+                             "saving files...");
+        }
+        while (BasicSSCell.isSaving()) {
+          try {
+            sleep(500);
+          }
+          catch (InterruptedException exc) { }
+        }
+        System.exit(0);
+      }
+    };
+    t.start();
+  }
+}
+
diff --git a/visad/rmic_script b/visad/rmic_script
new file mode 100644
index 0000000..32f71da
--- /dev/null
+++ b/visad/rmic_script
@@ -0,0 +1 @@
+rmic visad.RemoteCellImpl visad.RemoteActionImpl visad.RemoteDisplayImpl visad.RemoteThingReferenceImpl visad.RemoteDataReferenceImpl visad.RemoteThingImpl visad.RemoteDataImpl visad.RemoteFunctionImpl visad.RemoteFieldImpl visad.RemoteServerImpl
diff --git a/visad/sounder/NastiInstrument.java b/visad/sounder/NastiInstrument.java
new file mode 100644
index 0000000..f3f5db2
--- /dev/null
+++ b/visad/sounder/NastiInstrument.java
@@ -0,0 +1,151 @@
+//
+// NastiInstrument.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.sounder;
+
+import visad.*;
+import java.rmi.RemoteException;
+
+
+/**
+   NastiInstrument is the VisAD class for the NAST-I sounding instrument.<p>
+*/
+public class NastiInstrument extends SounderInstrument
+{
+  static double[][] pressures = {
+    { 
+      50.,60.,70.,75.,80.,85.,90.,100.,125.,150.,175.,200.,
+      250.,300.,350.,400.,450.,500.,550.,600.,620.,640.,660.,680.,
+      700.,720.,740.,760.,780.,800.,820.,840.,860.,880.,900.,920.,
+      940.,960.,980.,1000. 
+    }
+  };
+
+  static float[] wavelengths = new float[9127];
+  static 
+  { 
+    for ( int i = 0; i < wavelengths.length; i++ ) {
+      wavelengths[i] = i;
+    }
+  }
+
+  static String[] scalar_names =
+  {
+    "gamma_t",
+    "gamma_w",
+    "emissivity"
+  };
+
+  static String[] default_units = new String[3];
+  static double[] default_model_parms = new double[3];
+
+
+  //-- use default model parameters
+  //
+  public NastiInstrument()
+         throws VisADException, RemoteException
+  {
+    super(scalar_names, default_units, default_model_parms);
+  }
+
+  //-- trusted, supply model paramters in correct order/units
+  //
+  public NastiInstrument( double[] model_parms )
+         throws VisADException, RemoteException
+  {
+    super(scalar_names, default_units, model_parms);
+  }
+
+
+  float[][] computeRetrieval(float[][] radiances, double[][] model_parms)
+  {
+
+
+
+
+
+
+    //-nasti_retrvl_c(  );
+    return null;
+  }
+
+  float[][] computeFoward(float[][] rtvl, double[][] model_parms)
+  {
+
+
+
+
+
+
+    //-nastirte_c(   );
+    return null;
+  }
+
+  Sounding makeSounding()
+           throws VisADException, RemoteException
+  {
+    return new Sounding((Set.doubleToFloat(pressures))[0], null, null);
+  }
+
+  Sounding makeSounding(Sounding sounding)
+  {
+    try {
+      return (Sounding) sounding.resample((makeSounding()).getDomainSet());
+    }
+    catch (VisADException e) {
+      throw new VisADError(e.getMessage());
+    }
+    catch (RemoteException e) {
+      throw new VisADError(e.getMessage());
+    }
+  }
+
+  Spectrum makeSpectrum()
+           throws VisADException, RemoteException
+  {
+    return new Spectrum(wavelengths, null, null, null);
+  }
+
+  public void setGamma_t( double gamma_t ) {
+    model_parms[0][0] = gamma_t; 
+  }
+
+  public void setGamma_w( double gamma_w ) {
+    model_parms[0][1] = gamma_w;
+  }
+
+  public void setEmissivity( double emiss ) {
+    model_parms[0][2] = emiss;
+  }
+
+  private native void nastirte_c( float a, float b, int c, float d,
+                                  float[] p, float[] t, float[] wv, float[] o,
+                                  int[] u, double[] vn, double[] tb, double[] rr );
+
+  private native void nasti_retrvl_c( int opt, int opt2, int rec,
+                                      float gamt, float gamw, float gamts, float emis,
+                                      float[] tair, float[] rr, float[] pout );
+}
diff --git a/visad/sounder/PCA.java b/visad/sounder/PCA.java
new file mode 100644
index 0000000..22a8c46
--- /dev/null
+++ b/visad/sounder/PCA.java
@@ -0,0 +1,102 @@
+//
+// PCA.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.sounder;
+
+import java.lang.reflect.InvocationTargetException;
+import visad.*;
+import visad.matrix.*;
+import java.rmi.RemoteException;
+
+public class PCA 
+{
+  JamaMatrix eigenvectors;
+  JamaMatrix mean_vector;
+
+  public PCA(JamaMatrix eigenvectors, JamaMatrix mean_vector)
+  {
+    this.eigenvectors = eigenvectors;
+    this.mean_vector = mean_vector;
+  }
+
+  public Spectrum compressReconstruct( Spectrum spectrum )
+         throws VisADException, RemoteException,IllegalAccessException, InstantiationException, InvocationTargetException
+  {
+    Spectrum new_spectrum = (Spectrum) spectrum.clone();
+    JamaMatrix tmp = uncompress(compress(spectrum));
+    new_spectrum.setSamples(tmp.getValues());
+    return new_spectrum;
+  } 
+
+  public JamaMatrix compress( Spectrum spectrum ) 
+         throws VisADException, RemoteException, IllegalAccessException, InstantiationException, InvocationTargetException
+  {
+    JamaMatrix data_vector = new JamaMatrix(spectrum.getValues());
+    JamaMatrix tmp_vector = data_vector.minus(mean_vector);
+    JamaMatrix trans_data_vector = eigenvectors.times(tmp_vector.transpose());
+    return trans_data_vector;
+  }
+
+  public JamaMatrix uncompress( JamaMatrix trans_data_vector )
+         throws VisADException, IllegalAccessException, InstantiationException, InvocationTargetException
+  {
+     JamaMatrix r_data_vector =
+       (eigenvectors.transpose()).times(trans_data_vector);
+     r_data_vector = r_data_vector.plusEquals(mean_vector.transpose());
+     return r_data_vector;
+  }
+
+  public static JamaMatrix makeCovarianceMatrix( double[][] data_vectors )
+         throws VisADException, IllegalAccessException, InstantiationException, InvocationTargetException
+  {
+    int dim = data_vectors[0].length;
+    int n_vectors = data_vectors.length;
+    double[] mean_vector = new double[dim];
+
+    for ( int jj = 0; jj < dim; jj++ ) {
+      double sum = 0;
+      for ( int kk = 0; kk < n_vectors; kk++ ) {
+         sum += data_vectors[kk][jj];
+      }
+      mean_vector[jj] = sum/n_vectors;
+    }
+
+    double[][] cv = new double[dim][dim];
+
+    for ( int jj = 0; jj < dim; jj++ ) {
+      for ( int ii = jj; ii < dim; ii++ ) {
+        double sum = 0;
+        for ( int kk = 0; kk < n_vectors; kk++ ) {
+          sum += (data_vectors[kk][jj] - mean_vector[jj])*
+                 (data_vectors[kk][ii] - mean_vector[ii]);
+        }
+        cv[jj][ii] = sum/n_vectors;
+        cv[ii][jj] = cv[jj][ii];
+      }
+    }
+    return new JamaMatrix(cv);
+  }
+}
diff --git a/visad/sounder/SounderDisplay.java b/visad/sounder/SounderDisplay.java
new file mode 100644
index 0000000..15e4a63
--- /dev/null
+++ b/visad/sounder/SounderDisplay.java
@@ -0,0 +1,44 @@
+//
+// SounderDisplay.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.sounder;
+
+/**
+   SoundingInstrument is the VisAD abstract class for atmospheric
+   sounding instruments.<p>
+*/
+public class SounderDisplay
+{
+
+  public SounderDisplay( SounderInstrument instrument )
+  {
+
+
+
+
+  }
+
+}
diff --git a/visad/sounder/SounderInstrument.java b/visad/sounder/SounderInstrument.java
new file mode 100644
index 0000000..e75965f
--- /dev/null
+++ b/visad/sounder/SounderInstrument.java
@@ -0,0 +1,127 @@
+//
+// SounderInstrument.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.sounder;
+
+import visad.*;
+import visad.data.netcdf.units.Parser;
+import visad.data.netcdf.units.ParseException;
+import visad.data.netcdf.units.NoSuchUnitException;
+
+import java.rmi.RemoteException;
+
+/**
+   SoundingInstrument is the VisAD abstract class for atmospheric
+   sounding instruments.<p>
+*/
+public abstract class SounderInstrument
+{
+  double[][] model_parms;
+  RealType[] model_parm_types;
+  DataReferenceImpl[] parm_refs;
+  int n_parms;
+
+
+  public SounderInstrument(String[] names, String[] units, double[] parms)
+         throws VisADException, RemoteException
+  {
+    n_parms = parms.length;
+
+    if ( names.length != n_parms || units.length != n_parms ) {
+      throw new VisADException("# of names, units, parms must be equal");
+    }
+
+    model_parm_types = new RealType[n_parms];
+    model_parms = new double[1][n_parms];
+    parm_refs = new DataReferenceImpl[n_parms];
+
+    CellImpl update_cell = new CellImpl()
+    {
+      public void doAction() throws VisADException, RemoteException
+      {
+        for ( int ii = 0; ii < n_parms; ii++ ) {
+          model_parms[0][ii] = ((Real)parm_refs[ii].getData()).getValue();
+        }
+      }
+    };
+
+    for ( int ii = 0; ii < n_parms; ii++ ) {
+      Unit u = null;
+      try {
+        u = Parser.parse(units[ii]);
+      }
+      catch ( NoSuchUnitException e ) {
+        e.printStackTrace();
+      }
+      catch ( ParseException e ) {
+        e.printStackTrace();
+      }
+
+      model_parm_types[ii] = new RealType(names[ii], u, null);
+      model_parms[0][ii] = parms[ii];
+      parm_refs[ii] = new DataReferenceImpl(names[ii]);
+      parm_refs[ii].setData(new Real(model_parm_types[ii], model_parms[0][ii]));
+    }
+
+    for ( int ii = 0; ii < n_parms; ii++ ) {
+      update_cell.addReference(parm_refs[ii]);
+    }
+  }
+
+  public DataReferenceImpl[] getParamReferences()
+  {
+    return parm_refs;
+  }
+
+  public Sounding retrieval(Spectrum spectrum, Sounding sounding)
+  {
+    float[][] radiances = null;
+
+    float[][] rtvl = computeRetrieval(radiances, model_parms);
+    return null;
+  }
+
+  public Sounding retrieval(Spectrum spectrum)
+  {
+    return this.retrieval(spectrum, null);
+  }
+
+  public Spectrum foward(Sounding sounding)
+  {
+    float[][] rtvl = null;
+
+
+    float[][] spec = computeFoward(rtvl, model_parms);
+    return null;
+  }
+
+  abstract Sounding makeSounding() throws VisADException, RemoteException;
+  abstract Sounding makeSounding(Sounding sounding); 
+  abstract Spectrum makeSpectrum() throws VisADException, RemoteException;
+
+  abstract float[][] computeRetrieval(float[][] radiances, double[][] model_parms);
+  abstract float[][] computeFoward(float[][] rtvl, double[][] model_parms);
+}
diff --git a/visad/sounder/Sounding.java b/visad/sounder/Sounding.java
new file mode 100644
index 0000000..2e5638e
--- /dev/null
+++ b/visad/sounder/Sounding.java
@@ -0,0 +1,202 @@
+//
+// Sounding.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.sounder;
+
+import visad.*;
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.data.netcdf.units.UnitsDB;
+import visad.data.netcdf.units.DefaultUnitsDB;
+import visad.data.netcdf.StandardQuantityDB;
+
+import java.util.Vector;
+
+import java.rmi.RemoteException;
+
+/**
+   Sounding is the VisAD class for atmospheric soundings.
+   It extends FlatField.<p>
+*/
+public class Sounding extends FlatField {
+
+  static private UnitsDB udb;
+  static private StandardQuantityDB qdb;
+
+  static private RealType Pressure;
+  static private RealType Temperature;
+  static private RealType DewPoint;
+
+  static private FunctionType soundingType;
+
+  static {
+    try {
+      udb = DefaultUnitsDB.instance();
+      qdb = StandardQuantityDB.instance();
+      Pressure = qdb.get("Pressure");
+      Temperature = qdb.get("Temperature");
+      DewPoint = new RealType("DewPoint", udb.get("K"), null);
+      RealTupleType ttd = new RealTupleType(Temperature, DewPoint);
+      soundingType = new FunctionType(Pressure, ttd);
+    }
+    catch (VisADException e) {
+      e.printStackTrace();
+    }
+  }
+
+  //- this sounding's display_s
+  //
+  private Vector soundingDisplay_s = new Vector();
+
+  //- this sounding's DataReference
+  //
+  private DataReferenceImpl sounding_ref;
+
+  /** pressures in hPa, temperatures and dewpoints in K */
+  public Sounding(float[] pressures, float[] temperatures, float[] dewpoints)
+         throws VisADException, RemoteException {
+    super(soundingType, makePressureSet(pressures));
+    if (temperatures == null && dewpoints == null) {
+      throw new VisADException("temperatures and dewpoints cannot both be null");
+    }
+    int length = getLength();
+    if ((temperatures != null && temperatures.length != length) ||
+        (dewpoints != null && dewpoints.length != length)) {
+      throw new VisADException("temperatures and dewpoints cannot have " +
+                               "different lengths");
+    }
+    if (temperatures == null) {
+      temperatures = new float[length];
+      for (int i=0; i<length; i++) {
+        temperatures[i] = Float.NaN;
+      }
+    }
+    if (dewpoints == null) {
+      dewpoints = new float[length];
+      for (int i=0; i<length; i++) {
+        dewpoints[i] = Float.NaN;
+      }
+    }
+    setSamples(new float[][] {temperatures, dewpoints});
+    sounding_ref = new DataReferenceImpl("sounding reference");
+    sounding_ref.setData(this);
+  }
+
+  static private Gridded1DSet makePressureSet(float[] pressures)
+         throws VisADException {
+    if (pressures == null) {
+      throw new SetException("pressures cannot be null");
+    }
+    return new Gridded1DSet(Pressure, new float[][] {pressures}, pressures.length,
+                            null, new Unit[] {udb.get("hPa")}, null);
+  }
+
+  public void addToDisplay( DisplayImpl display ) 
+         throws VisADException, RemoteException
+  {
+     addToDisplay(display, null);
+  }
+
+  public void addToDisplayWithDirectManipulation( DisplayImpl display )
+         throws VisADException, RemoteException
+  {
+     addToDisplay(display, new DirectManipulationRendererJ3D());
+  }
+
+  private void addToDisplay(DisplayImpl display, DataRenderer renderer)
+          throws VisADException, RemoteException
+  {
+    if ( soundingDisplay_s.contains(display) ) {
+      return;
+    }
+    else { 
+      soundingDisplay_s.add(display);
+      Vector mapVector = display.getMapVector();
+
+      boolean presToYAxis = false;
+      boolean tempToXAxis = false;
+      boolean dewpToXAxis = false;
+
+      for (int kk = 0; kk < mapVector.size(); kk++) { 
+        ScalarMap smap = (ScalarMap) mapVector.elementAt(kk);
+        ScalarType s_type = smap.getScalar();
+        DisplayRealType d_type = smap.getDisplayScalar();
+  
+        if (Pressure.equals(smap) && d_type.equals(Display.YAxis)) {
+          presToYAxis = true;
+        }
+        if (Temperature.equals(smap) && d_type.equals(Display.XAxis)) {
+          tempToXAxis = true;
+        }
+        if (DewPoint.equals(smap) && d_type.equals(Display.XAxis)) {
+          dewpToXAxis = true;
+        }
+      }
+      
+      if (presToYAxis && tempToXAxis && dewpToXAxis) {
+        display.removeAllReferences();
+        display.addReferences(renderer, sounding_ref);
+      }
+      else {
+        display.removeAllReferences();
+        display.clearMaps();
+
+        display.addMap(new ScalarMap(Pressure, Display.YAxis));
+        display.addMap(new ScalarMap(Temperature, Display.XAxis));
+        display.addMap(new ScalarMap(DewPoint, Display.XAxis));
+        display.addReferences(renderer, sounding_ref);
+      }
+      return;
+    }
+  }
+
+  public void remove()
+         throws VisADException, RemoteException
+  {
+    for ( int kk = 0; kk < soundingDisplay_s.size(); kk++ ) {
+      DisplayImpl display = 
+        (DisplayImpl) soundingDisplay_s.elementAt(kk); 
+      display.removeReference(sounding_ref);
+    }
+    soundingDisplay_s.removeAllElements();
+    return;
+  }
+
+  public boolean restore()
+  {
+    //- ?
+    return false;
+  }
+
+  public static void main(String args[])
+         throws VisADException, RemoteException {
+    float[] p = {1000.0f, 500.0f, 100.0f};
+    float[] t = {283.0f, 200.0f, 150.0f};
+    float[] td = {273.0f, 180.0f, 100.0f};
+    Sounding sounding = new Sounding(p, t, td);
+    System.out.println("sounding = " + sounding);
+  }
+
+}
diff --git a/visad/sounder/Spectrum.java b/visad/sounder/Spectrum.java
new file mode 100644
index 0000000..5da8a43
--- /dev/null
+++ b/visad/sounder/Spectrum.java
@@ -0,0 +1,188 @@
+//
+// Spectrum.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.sounder;
+
+import visad.*;
+import visad.java3d.DirectManipulationRendererJ3D;
+import visad.data.netcdf.units.UnitsDB;
+import visad.data.netcdf.units.DefaultUnitsDB;
+import visad.data.netcdf.StandardQuantityDB;
+
+import java.util.Vector;
+
+import java.rmi.RemoteException;
+
+/**
+   Sounding is the VisAD class for atmospheric soundings.
+   It extends FlatField.<p>
+*/
+public class Spectrum extends FlatField {
+
+  static private UnitsDB udb;
+  static private StandardQuantityDB qdb;
+
+  static private RealType Wavelength;
+  static private RealType Radiance;
+
+  static private FunctionType spectrumType;
+
+  static {
+    try {
+      udb = DefaultUnitsDB.instance();
+      qdb = StandardQuantityDB.instance();
+      Wavelength = qdb.get("Wavelength");
+      Radiance = qdb.get("Radiance");
+      spectrumType = new FunctionType(Wavelength, Radiance);
+    }
+    catch (VisADException e) {
+      e.printStackTrace();
+    }
+  }
+
+  //- this spectrum's display_s
+  //
+  private Vector spectrumDisplay_s = new Vector();
+
+  //- this spectrum's DataReference
+  //
+  DataReferenceImpl spectrum_ref;
+
+  Unit domain_unit;
+  Unit range_unit;
+
+  public Spectrum(float[] wavelength_domain,
+                  Unit domain_unit,
+                  float[] radiance_range,
+                  Unit range_unit
+                                            )
+         throws VisADException, RemoteException
+  {
+    super(spectrumType, makeSet(wavelength_domain));
+
+    int length = getLength();
+    if (radiance_range == null) {
+      radiance_range = new float[length];
+      for (int i=0; i<length; i++) {
+        radiance_range[i] = Float.NaN;
+      }
+    }
+    else if ( radiance_range.length != length ) {
+      throw new VisADException("radiance_range should have same length as wavelenghts"); 
+    }
+    setSamples(new float[][] {radiance_range});
+    this.domain_unit = domain_unit;
+    this.range_unit = range_unit;
+    spectrum_ref = new DataReferenceImpl("Spectrum reference");
+    spectrum_ref.setData(this);
+  }
+
+
+  static private Gridded1DSet makeSet(float[] wavelength_domain)
+         throws VisADException {
+    if (wavelength_domain == null) {
+      throw new SetException("wavelengths cannot be null");
+    }
+    return new Gridded1DSet(Wavelength, new float[][] {wavelength_domain},
+                            wavelength_domain.length,
+                            null, new Unit[] {udb.get("Radiance")}, null);
+  }
+
+  public void addToDisplay( DisplayImpl display )
+         throws VisADException, RemoteException
+  {
+     addToDisplay(display, null);
+  }
+
+  public void addToDisplayWithDirectManipulation( DisplayImpl display )
+         throws VisADException, RemoteException
+  {
+     addToDisplay(display, new DirectManipulationRendererJ3D());
+  }
+
+  private void addToDisplay( DisplayImpl display, DataRenderer renderer )
+          throws VisADException, RemoteException
+  {
+    if ( spectrumDisplay_s.contains(display) ) {
+      return;
+    }
+    else {
+      spectrumDisplay_s.add(display);
+      Vector mapVector = display.getMapVector();
+  
+      boolean radianceToYAxis = false;
+      boolean wavelengthToXAxis = false;
+  
+      for (int kk = 0; kk < mapVector.size(); kk++) {
+        ScalarMap smap = (ScalarMap) mapVector.elementAt(kk);
+        ScalarType s_type = smap.getScalar();
+        DisplayRealType d_type = smap.getDisplayScalar();
+
+        if (Radiance.equals(smap) && d_type.equals(Display.YAxis)) {
+          radianceToYAxis = true;
+        }
+        if (Wavelength.equals(smap) && d_type.equals(Display.XAxis)) {
+          wavelengthToXAxis = true;
+        }
+      }
+
+      if (radianceToYAxis && wavelengthToXAxis) {
+        display.removeAllReferences();
+        display.addReferences(renderer, spectrum_ref);
+      }
+      else {
+        display.removeAllReferences();
+        display.clearMaps();
+        display.addMap(new ScalarMap(Radiance, Display.YAxis));
+        display.addMap(new ScalarMap(Wavelength, Display.XAxis));
+        display.addReferences(renderer, spectrum_ref);
+      }
+
+      return;
+    }
+  }
+
+  public void remove()
+         throws VisADException, RemoteException
+  {
+    for ( int kk = 0; kk < spectrumDisplay_s.size(); kk++ ) {
+      DisplayImpl display = 
+        (DisplayImpl) spectrumDisplay_s.elementAt(kk);
+      display.removeReference(spectrum_ref);
+    }
+    spectrumDisplay_s.removeAllElements();
+    return;
+  }
+
+  public boolean restore()
+  {
+    //- ?
+    return false;
+  }
+
+  public static void main(String args[]) {
+  }
+}
diff --git a/visad/ss/2d.gif b/visad/ss/2d.gif
new file mode 100644
index 0000000..827e742
Binary files /dev/null and b/visad/ss/2d.gif differ
diff --git a/visad/ss/3d.gif b/visad/ss/3d.gif
new file mode 100644
index 0000000..91c5c9c
Binary files /dev/null and b/visad/ss/3d.gif differ
diff --git a/visad/ss/BasicSSCell.java b/visad/ss/BasicSSCell.java
new file mode 100644
index 0000000..a787e7b
--- /dev/null
+++ b/visad/ss/BasicSSCell.java
@@ -0,0 +1,3497 @@
+//
+// BasicSSCell.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+import javax.imageio.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.*;
+import java.rmi.*;
+import java.util.*;
+import javax.swing.*;
+import visad.*;
+import visad.bom.ImageRendererJ3D;
+import visad.collab.DisplayMonitor;
+import visad.data.*;
+import visad.formula.*;
+import visad.java2d.*;
+import visad.java3d.*;
+import visad.util.*;
+
+/**
+ * BasicSSCell represents a single spreadsheet display cell.
+ * BasicSSCells can be added to a VisAD user interface to provide some of the
+ * capabilities presented in the VisAD SpreadSheet. Other capabilities, like
+ * the file loader and data mapping dialog boxes, are available only with a
+ * FancySSCell.
+ */
+public class BasicSSCell extends JPanel
+  implements DisplayListener, MessageListener
+{
+
+  /**
+   * Debugging flag.
+   */
+  public static boolean DEBUG = false;
+
+  /**
+   * Debugging level.
+   *
+   * <li>1 = Normal.
+   * <li>2 = Collaboration messages.
+   * <li>3 = All exceptions.
+   */
+  public static int DEBUG_LEVEL = 2;
+
+
+  // --- STATIC UTILITY METHODS ---
+
+  /**
+   * List of SSCells on this JVM.
+   */
+  protected static final Vector SSCellVector = new Vector();
+
+  /**
+   * The number of SSCells currently saving data.
+   */
+  protected static int Saving = 0;
+
+  /**
+   * Whether Java3D is possible for this JVM.
+   */
+  protected static boolean Possible3D;
+
+  /**
+   * Whether Java3D is enabled for this JVM.
+   */
+  protected static boolean CanDo3D = enable3D();
+
+
+  /**
+   * Gets the SSCell with the specified name.
+   */
+  public static BasicSSCell getSSCellByName(String name) {
+    synchronized (SSCellVector) {
+      int len = SSCellVector.size();
+      for (int i=0; i<len; i++) {
+        BasicSSCell cell = (BasicSSCell) SSCellVector.elementAt(i);
+        if (name.equalsIgnoreCase(cell.Name)) return cell;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns true if any SSCell is currently saving data.
+   */
+  public static boolean isSaving() {
+    return Saving > 0;
+  }
+
+  // -- Detect, enable & disable Java3D --
+
+  /**
+   * Returns true if Java3D is possible for this JVM.
+   */
+  public static boolean possible3D() {
+    return Possible3D;
+  }
+
+  /**
+   * Returns true if Java3D is enabled for this JVM.
+   */
+  public static boolean canDo3D() {
+    return CanDo3D;
+  }
+
+  /**
+   * Attempts to enable Java3D for this JVM, returning true if successful.
+   */
+  public static boolean enable3D() {
+    if (Possible3D) {
+      // Java3D test has already succeeded
+      CanDo3D = true;
+    }
+    else {
+      // test for Java3D availability
+      Possible3D = CanDo3D = Util.canDoJava3D();
+      if (DEBUG && !Possible3D) {
+        if (DEBUG) System.err.println("Warning: Java3D library not found");
+      }
+    }
+    return CanDo3D;
+  }
+
+  /**
+   * Disables Java3D for this JVM.
+   */
+  public static void disable3D() {
+    CanDo3D = false;
+  }
+
+
+  // --- CONSTRUCTORS ---
+
+  /**
+   * Default FormulaManager object used by SSCells.
+   */
+  protected static final FormulaManager defaultFM =
+    FormulaUtil.createStandardManager();
+
+  /**
+   * Name of this cell.
+   */
+  protected String Name;
+
+  /**
+   * Formula manager for this cell.
+   */
+  protected FormulaManager fm;
+
+
+  /**
+   * Constructs a new BasicSSCell with the given name.
+   */
+  public BasicSSCell(String name) throws VisADException, RemoteException {
+    this(name, null, null, false, null);
+  }
+
+  /**
+   * Constructs a new BasicSSCell with the given name and non-default
+   * formula manager, to allow for custom formulas.
+   */
+  public BasicSSCell(String name, FormulaManager fman)
+    throws VisADException, RemoteException
+  {
+    this(name, fman, null, false, null);
+  }
+
+  /**
+   * Constructs a new BasicSSCell with the given name, that gets its
+   * information from the given RemoteServer. The associated SSCell on the
+   * server end must have already invoked its addToRemoteServer method.
+   */
+  public BasicSSCell(String name, RemoteServer rs)
+    throws VisADException, RemoteException
+  {
+    this(name, null, rs, false, null);
+  }
+
+  /**
+   * Constructs a new BasicSSCell with the given name and save string,
+   * used to reconstruct this cell's configuration.
+   */
+  public BasicSSCell(String name, String save)
+    throws VisADException, RemoteException
+  {
+    this(name, null, null, false, save);
+  }
+
+  /**
+   * Constructs a new BasicSSCell with the given name, formula manager,
+   * and remote server.
+   */
+  public BasicSSCell(String name, FormulaManager fman, RemoteServer rs,
+    String save) throws VisADException, RemoteException
+  {
+    this(name, fman, rs, false, save);
+  }
+
+  /**
+   * Constructs a new, possibly slaved, BasicSSCell with the given name,
+   * formula manager, and remote server.
+   */
+  public BasicSSCell(String name, FormulaManager fman, RemoteServer rs,
+    boolean slave, String save) throws VisADException, RemoteException
+  {
+    // set name
+    if (name == null) {
+      throw new VisADException("BasicSSCell: name cannot be null");
+    }
+    synchronized (SSCellVector) {
+      int len = SSCellVector.size();
+      for (int i=0; i<len; i++) {
+        BasicSSCell cell = (BasicSSCell) SSCellVector.elementAt(i);
+        if (name.equalsIgnoreCase(cell.Name)) {
+          throw new VisADException("BasicSSCell: name already used");
+        }
+      }
+      Name = name;
+      SSCellVector.add(this);
+    }
+
+    // set formula manager
+    fm = (fman == null ? defaultFM : fman);
+
+    // set remote server
+    if (rs != null) {
+      RemoteVServer = rs;
+      RemoteVDisplay = rs.getDisplay(Name);
+    }
+    IsRemote = (RemoteVDisplay != null);
+    IsSlave = slave;
+
+    // collaboration setup
+    if (IsRemote) {
+      // CLIENT: initialize
+      setupClient();
+    }
+    else {
+      // SERVER: initialize
+      setupServer();
+    }
+
+    // set save string
+    if (save != null) setSaveString(save);
+
+    // finish GUI setup
+    initDisplayPanel();
+    setPreferredSize(new Dimension(0, 0));
+    setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
+    setBackground(IsSlave ? Color.darkGray : Color.black);
+    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+  }
+
+
+  // --- COLLABORATION ---
+
+  // Command              Message ID           Message Str  Message Data
+  // -------              ----------           -----------  ------------
+  // Add file             ADD_DATA             src str      loaded data
+  // Add URL              ADD_DATA             src str      loaded data
+  // Add RMI addr         ADD_SOURCE           src str      Real(src type)
+  // Add formula          ADD_SOURCE           src str      Real(src type)
+  // Add direct           ADD_DATA             src str("")  direct data
+  // Remove data          REMOVE_DATA          varName      null
+  // Set maps             SET_MAPS             map str      null
+  // Clear maps           SET_MAPS             null         null
+  // Request dim switch   SET_DIM              ""           Real(newDim)
+  // Switch dim           SET_DIM              null         Real(newDim)
+  // Set errors for cell  SET_ERRORS           null         Tuple(Text[])
+  // Set errors for data  SET_ERRORS           varName      Tuple(Text[])
+  // Update data          UPDATE_DATA          varName      new data
+  // Update dependencies  UPDATE_DEPENDENCIES  varName      true or false
+  // Request status       STATUS               ""           null
+  // Update status        STATUS               null         status info
+
+  // Relevant methods: sendMessage(), receiveMessage()
+
+  // Most collaboration messages are handled by calling the appropriate
+  // methods with the notify bit set to false, so that additional messages
+  // are not sent in response, thus avoiding feedback loops.
+
+  // However, there are four exceptions to this model:
+
+  // 1) Clients send SET_MAPS commands but do not alter maps themselves.
+  // Servers receive SET_MAPS commands and alter mappings, but do not
+  // send SET_MAPS commands, thus avoiding feedback loops.
+
+  // 2) Both servers and clients send SET_DIM commands when setDimension()
+  // is called. However, only servers actually alter the dimension.
+  // Clients switch dimensions using setDimClone() when a SET_DIM command
+  // from the server is received. A server receiving a SET_DIM command
+  // will switch the dimension using setDimension(), generating another
+  // SET_DIM command to which the clients respond. Thus, whenever a client
+  // changes the dimension, two SET_DIM commands actually get sent: the
+  // first by that client (msg="") and the second by the server (msg=null).
+
+  // 3) Only servers send UPDATE_DEPENDENCIES commands. Both servers and
+  // clients receive them and set their dependencies to match the message.
+  // The reason for this behavior is that only the server computes formulas,
+  // and consequently only servers can compute whether a given data object
+  // is dependent on other data objects.
+
+  // 4) New clients call STATUS to request the current status of the cell.
+  // Servers answer with a cell status report by calling STATUS with the
+  // current status of the cell as the message data.
+
+  // CTR: TODO: If a cell has a formula depending on another data object in
+  // that same cell, and user wants to clear the whole cell, he'll get a
+  // message to confirm saying "other data objects depend"... This weird
+  // message should be avoided somehow, but it's not easy without extending
+  // the current implementation of formulas.
+
+
+  /**
+   * Message ID indicating a data object has been added.
+   */
+  public static final int ADD_DATA = 0;
+
+  /**
+   * Message ID indicating a source has been added.
+   */
+  public static final int ADD_SOURCE = 1;
+
+  /**
+   * Message ID indicating a data object has been removed.
+   */
+  public static final int REMOVE_DATA = 2;
+
+  /**
+   * Message ID indicating mappings have changed.
+   */
+  public static final int SET_MAPS = 3;
+
+  /**
+   * Message ID indicating dimension has changed.
+   */
+  public static final int SET_DIM = 4;
+
+  /**
+   * Message ID indicating errors have changed.
+   */
+  public static final int SET_ERRORS = 5;
+
+  /**
+   * Message ID indicating a data object has changed.
+   */
+  public static final int UPDATE_DATA = 6;
+
+  /**
+   * Message ID indicating a data object's dependencies have changed.
+   */
+  public static final int UPDATE_DEPENDENCIES = 7;
+
+  /**
+   * Message ID indicating a cell's status information
+   * is being requested or reported.
+   */
+  public static final int STATUS = 8;
+
+  /**
+   * No message ID should have a value greater than or equal to this number.
+   */
+  public static final int MAX_ID = 9;
+
+  /**
+   * Message ID strings, for debugging.
+   */
+  public static final String[] messages = {"ADD_DATA", "ADD_SOURCE",
+    "REMOVE_DATA", "SET_MAPS", "SET_DIM", "SET_ERRORS", "UPDATE_DATA",
+    "UPDATE_DEPENDENCIES", "STATUS"};
+
+
+  /**
+   * List of servers to which this cell has been added.
+   */
+  protected Vector Servers = new Vector();
+
+  /**
+   * Associated DisplayImpl for sending and receiving messages.
+   */
+  protected DisplayImpl MDisplay = null;
+
+  /**
+   * Associated RemoteDisplay for sending and receiving messages.
+   */
+  protected RemoteDisplay RemoteMDisplay = null;
+
+  /**
+   * Associated VisAD RemoteDisplay.
+   */
+  protected RemoteDisplay RemoteVDisplay = null;
+
+  /**
+   * Associated VisAD RemoteSlaveDisplay, if any.
+   */
+  protected RemoteSlaveDisplayImpl RemoteVSlave = null;
+
+  /**
+   * Associated VisAD RemoteServer, if any.
+   */
+  protected RemoteServer RemoteVServer = null;
+
+  /**
+   * ID number for this collaborative cell.
+   */
+  protected int CollabID = DisplayMonitor.UNKNOWN_LISTENER_ID;
+
+  /**
+   * Whether this display is remote.
+   */
+  protected boolean IsRemote;
+
+  /**
+   * Whether this display is slaved.
+   */
+  protected boolean IsSlave;
+
+  /**
+   * Whether this display is still a new client (hasn't been initialized).
+   */
+  protected boolean NewClient;
+
+
+  // -- Add & remove cells to remote servers --
+
+  /**
+   * Adds this cell to the given RemoteServer. SSCell servers must call this
+   * method for each cell before clients can clone the cells with the
+   * BasicSSCell(String name, RemoteServer rs) constructor, and before the
+   * cells can be exported as RMI addresses.
+   */
+  public void addToRemoteServer(RemoteServerImpl rs) throws RemoteException {
+    if (rs == null) return;
+    if (IsRemote) {
+      // CLIENT: illegal operation
+      throw new RemoteException("Cannot add a cloned cell to a server");
+    }
+
+    synchronized (Servers) {
+      if (!Servers.contains(rs)) {
+        rs.addDisplay((RemoteDisplayImpl) RemoteMDisplay);
+        rs.addDisplay((RemoteDisplayImpl) RemoteVDisplay);
+        synchronized (CellData) {
+          int len = CellData.size();
+          for (int i=0; i<len; i++) {
+            SSCellData cellData = (SSCellData) CellData.elementAt(i);
+            rs.addDataReference(cellData.getRemoteReference());
+          }
+        }
+        Servers.add(rs);
+      }
+    }
+  }
+
+  /**
+   * Removes this cell from the given RemoteServer.
+   */
+  public void removeFromRemoteServer(RemoteServerImpl rs)
+    throws RemoteException
+  {
+    if (rs == null) return;
+    if (IsRemote) {
+      // CLIENT: illegal operation
+      throw new RemoteException("Cannot remove a cloned cell from a server");
+    }
+
+    synchronized (Servers) {
+      if (Servers.contains(rs)) {
+        rs.removeDisplay((RemoteDisplayImpl) RemoteMDisplay);
+        rs.removeDisplay((RemoteDisplayImpl) RemoteVDisplay);
+        synchronized (CellData) {
+          int len = CellData.size();
+          for (int i=0; i<len; i++) {
+            SSCellData cellData = (SSCellData) CellData.elementAt(i);
+            rs.removeDataReference(cellData.getRemoteReference());
+          }
+        }
+        Servers.remove(rs);
+      }
+    }
+  }
+
+  // -- Send & receive cell update messages --
+
+  /**
+   * Sends a message of type <tt>id</tt> to server and all clients
+   * of this cell.
+   */
+  void sendMessage(int id, String msg, Data data)
+    throws RemoteException
+  {
+    // convert data to remote data
+    RemoteData d;
+    if (data instanceof RemoteData) d = (RemoteData) data;
+    else d = new RemoteDataImpl((DataImpl) data);
+
+    MDisplay.sendMessage(new MessageEvent(MAX_ID * CollabID + id, msg, d));
+    if (DEBUG && DEBUG_LEVEL >= 2) {
+      System.out.println(Name + "[" + CollabID + "]: sent " +
+        messages[id] + ": msg=" + msg + ", data=" +
+        (data == null ? "null" : data.getClass().getName()));
+    }
+  }
+
+  /**
+   * Handles VisAD messages. This method is the heart of BasicSSCell's
+   * collaboration support.
+   */
+  public void receiveMessage(MessageEvent msg) throws RemoteException {
+    int id = msg.getId();
+    int mid = id % MAX_ID;
+    int oid = id / MAX_ID;
+    if (oid == CollabID && mid != UPDATE_DEPENDENCIES) {
+      // cells ignore their own updates, except for UPDATE_DEPENDENCIES
+      return;
+    }
+    String m = msg.getString();
+    RemoteData data = msg.getData();
+    if (DEBUG && DEBUG_LEVEL >= 2) {
+      DataImpl ld = DataUtility.makeLocal(data, DEBUG);
+      System.out.println(Name + "[" + CollabID + "]: received " +
+        messages[mid] + " from " + oid + ": msg=" + m + ", data=" +
+        (ld == null ? "null" : ld.getClass().getName()));
+    }
+
+    try {
+      if (mid == ADD_DATA) {
+        // add data from remote URL_SOURCE or DIRECT_SOURCE
+        synchronized (CellData) {
+          SSCellData cellData = addReferenceImpl(0, null, null, m,
+            m.equals("") ? DIRECT_SOURCE : URL_SOURCE, false, false);
+          cellData.setData(data, false);
+        }
+      }
+
+      else if (mid == ADD_SOURCE) {
+        // add data from remote RMI_SOURCE or FORMULA_SOURCE
+        synchronized (CellData) {
+          int type = (int)
+	    ((Real) DataUtility.makeLocal(data, DEBUG)).getValue();
+          addDataSource(0, m, type, false);
+        }
+      }
+
+      else if (mid == REMOVE_DATA) {
+        // remove data
+        synchronized (CellData) {
+          SSCellData cellData = getCellDataByName(m);
+          removeDataImpl(cellData, false, true);
+        }
+      }
+
+      else if (mid == SET_MAPS) {
+        // set maps
+        if (!IsRemote) {
+          // SERVER: respond to client's SET_MAPS command
+          if (m == null) clearMaps();
+          else setMaps(DataUtility.convertStringToMaps(m, getData(), true));
+        }
+      }
+
+      else if (mid == SET_DIM) {
+        // set dimension
+        int dim = (int)
+	  ((Real) DataUtility.makeLocal(data, DEBUG)).getValue();
+        if (m != null) {
+          // SET_DIM command originates from client
+          if (IsRemote) {
+            // CLIENT: turn on wait dialog
+            beginWait(true);
+          }
+          else {
+            // SERVER: respond to client's SET_DIM command
+            setDimension(dim);
+          }
+        }
+        else if (IsRemote) {
+          // CLIENT: respond to server's SET_DIM command
+          endWait(false);
+          setDimClone();
+        }
+      }
+
+      else if (mid == SET_ERRORS) {
+        // set errors
+        Tuple tuple = (Tuple) DataUtility.makeLocal(data, DEBUG);
+        String[] errors = DataUtility.tupleToStrings(tuple, DEBUG);
+        SSCellData cellData;
+        synchronized (CellData) {
+          cellData = getCellDataByName(m);
+        }
+        if (cellData != null) cellData.setErrors(errors, false);
+      }
+
+      else if (mid == UPDATE_DATA) {
+        // update local data to match data from message
+        SSCellData cellData;
+        synchronized (CellData) {
+          cellData = getCellDataByName(m);
+        }
+        cellData.cell.skipNextErrors();
+        cellData.setData(data, false);
+      }
+
+      else if (mid == UPDATE_DEPENDENCIES) {
+        // update local data dependencies to match dependencies from message
+        SSCellData cellData;
+        synchronized (CellData) {
+          cellData = getCellDataByName(m);
+        }
+        if (cellData != null) {
+          cellData.setDependencies(
+	    (Real) DataUtility.makeLocal(data, DEBUG));
+        }
+      }
+
+      else if (mid == STATUS) {
+        if (m == null) {
+          // status report from server
+          if (IsRemote && NewClient) {
+            Tuple tuple = (Tuple) DataUtility.makeLocal(data, DEBUG);
+            if (tuple != null) {
+              synchronized (CellData) {
+                // add Data objects to cell
+                try {
+                  int len = tuple.getDimension();
+                  for (int i=0; i<len; i++) {
+                    Tuple t = (Tuple)
+		      DataUtility.makeLocal(tuple.getComponent(i), DEBUG);
+                    Real rid = (Real)
+		      DataUtility.makeLocal(t.getComponent(0), DEBUG);
+                    Data d = t.getComponent(1);
+                    DataReferenceImpl ref = new DataReferenceImpl(Name);
+                    ref.setData(d);
+                    Text source = (Text)
+		      DataUtility.makeLocal(t.getComponent(2), DEBUG);
+                    Real type = (Real)
+		      DataUtility.makeLocal(t.getComponent(3), DEBUG);
+                    addReferenceImpl((int) rid.getValue(), ref, null,
+                      source.getValue(), (int) type.getValue(), false, false);
+                  }
+                }
+                catch (VisADException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                }
+                catch (RemoteException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                }
+              }
+            }
+            NewClient = false;
+          }
+        }
+        else {
+          // status request from new client
+          if (!IsRemote) {
+            // SERVER: send out status report
+            synchronized (CellData) {
+              try {
+                int len = CellData.size();
+                Data[] d = new Data[len];
+                for (int i=0; i<len; i++) {
+                  SSCellData cellData = (SSCellData) CellData.elementAt(i);
+                  Data[] status = new Data[4];
+                  status[0] = new Real(cellData.getId());
+                  status[1] = cellData.getData();
+                  status[2] = new Text(cellData.getSource());
+                  status[3] = new Real(cellData.getSourceType());
+                  d[i] = new Tuple(status);
+                }
+                sendMessage(STATUS, null, len == 0 ? null : new Tuple(d));
+              }
+              catch (VisADException exc) {
+                if (DEBUG) exc.printStackTrace();
+              }
+              catch (RemoteException exc) {
+                if (DEBUG) exc.printStackTrace();
+              }
+            }
+          }
+        }
+      }
+
+      else if (DEBUG) {
+        warn("unknown message id (" + mid + ") received.");
+      }
+    }
+    catch (VisADException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+  }
+
+  // -- Set up server & client --
+
+  /**
+   * Sets up data needed for this cell to be a server.
+   */
+  protected void setupServer() throws VisADException, RemoteException {
+    MDisplay = new DisplayImplJ2D(Name + "_Messenger", null);
+    RemoteMDisplay = new RemoteDisplayImpl(MDisplay);
+    MDisplay.addMessageListener(this);
+    CollabID = 0;
+    setDimension(JAVA2D_2D);
+  }
+
+  /**
+   * Sets up data needed for this cell to be a client.
+   */
+  protected void setupClient() throws VisADException, RemoteException {
+    RemoteMDisplay = RemoteVServer.getDisplay(Name + "_Messenger");
+    MDisplay = new DisplayImplJ2D(RemoteMDisplay, null);
+    MDisplay.addMessageListener(this);
+    try {
+      CollabID = MDisplay.getConnectionID(RemoteMDisplay);
+    }
+    catch (RemoteException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+    setDimClone();
+    addDisplayListener(this);
+    NewClient = true;
+    sendMessage(STATUS, "", null);
+  }
+
+  // -- Manage dependencies --
+
+  /**
+   * Broadcasts updated cell dependencies to all clients.
+   */
+  static void updateDependencies() {
+    // check every data object of each cell for changes in dependency status
+    synchronized (SSCellVector) {
+      int len = SSCellVector.size();
+      for (int i=0; i<len; i++) {
+        BasicSSCell cell = (BasicSSCell) SSCellVector.elementAt(i);
+        if (!cell.isRemote()) {
+          synchronized (cell.CellData) {
+            int len2 = cell.CellData.size();
+            for (int j=0; j<len2; j++) {
+              SSCellData cellData = (SSCellData) cell.CellData.elementAt(j);
+              if (!cellData.ssCell.isRemote()) {
+                String varName = cellData.getVariableName();
+                boolean canBeRemoved = true;
+                try {
+                  canBeRemoved = cell.fm.canBeRemoved(varName);
+                }
+                catch (FormulaException exc) {
+                  if (DEBUG) exc.printStackTrace();
+                }
+                if (canBeRemoved == cellData.othersDepend) {
+                  try {
+                    // notify linked cells of dependency status change
+                    cellData.ssCell.sendMessage(UPDATE_DEPENDENCIES, varName,
+                      canBeRemoved ? SSCellImpl.FALSE : SSCellImpl.TRUE);
+                  }
+                  catch (RemoteException exc) {
+                    if (DEBUG) exc.printStackTrace();
+                  }
+                } // if canBeRemoved == cellData.othersDepend
+              } // if cellData.ssCell.isRemote
+            } // for j
+          } // synchronized cell.CellData
+        } // if cell.isRemote
+      } // for i
+    } // synchronized SSCellVector
+  }
+
+
+  // --- DATA MANAGEMENT ---
+
+  /**
+   * Interval at which to check for status changes while waiting.
+   */
+  protected static final int POLLING_INTERVAL = 100;
+
+  /**
+   * Indicates that the source of the data is unknown.
+   */
+  public static final int UNKNOWN_SOURCE = -1;
+
+  /**
+   * Indicates that the data was added to this cell directly
+   * using addData() or addReference().
+   */
+  public static final int DIRECT_SOURCE = 0;
+
+  /**
+   * Indicates that the data came from a file or URL.
+   */
+  public static final int URL_SOURCE = 1;
+
+  /**
+   * Indicates that the data was computed from a formula.
+   */
+  public static final int FORMULA_SOURCE = 2;
+
+  /**
+   * Indicates that the data came from an RMI server.
+   */
+  public static final int RMI_SOURCE = 3;
+
+  /**
+   * Indicates that the data came from a remotely linked cell.
+   */
+  public static final int REMOTE_SOURCE = 4;
+
+  /**
+   * The number of data objects this cell is currently loading.
+   */
+  protected int Loading = 0;
+
+  /**
+   * List of this cell's data.
+   */
+  protected Vector CellData = new Vector();
+
+
+  // -- Add data --
+
+  /**
+   * Adds a Data object to this cell, creating
+   * an associated DataReference for it.
+   *
+   * @return Variable name of the newly added data.
+   */
+  public String addData(Data data) throws VisADException, RemoteException {
+    return addData(0, data, null, "", DIRECT_SOURCE, true);
+  }
+
+  /**
+   * Adds a Data object to this cell, creating an associated
+   * DataReference with the specified ConstantMaps for it.
+   *
+   * @return Variable name of the newly added data.
+   */
+  public String addData(Data data, ConstantMap[] cmaps)
+    throws VisADException, RemoteException
+  {
+    return addData(0, data, cmaps, "", DIRECT_SOURCE, true);
+  }
+
+  /**
+   * Adds a Data object to this cell from the given source of the
+   * specified type, creating an associated DataReference for it.
+   *
+   * @return Variable name of the newly added data.
+   */
+  protected String addData(int id, Data data, ConstantMap[] cmaps,
+    String source, int type, boolean notify)
+    throws VisADException, RemoteException
+  {
+    // add Data object to cell
+    DataReferenceImpl ref = new DataReferenceImpl(Name);
+    ref.setData(data);
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = addReferenceImpl(id, ref, cmaps, source, type, notify, true);
+    }
+    return cellData.getVariableName();
+  }
+
+  /**
+   * Adds the given DataReference to this cell.
+   *
+   * @return Variable name of the newly added reference.
+   */
+  public String addReference(DataReferenceImpl ref)
+    throws VisADException, RemoteException
+  {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = addReferenceImpl(0, ref, null, "", DIRECT_SOURCE, true, true);
+    }
+    return cellData.getVariableName();
+  }
+
+  /**
+   * Adds the given DataReference to this cell with the specified ConstantMaps.
+   *
+   * @return Variable name of the newly added reference.
+   */
+  public String addReference(DataReferenceImpl ref, ConstantMap[] cmaps)
+    throws VisADException, RemoteException
+  {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData =
+        addReferenceImpl(0, ref, cmaps, "", DIRECT_SOURCE, true, true);
+    }
+    return cellData.getVariableName();
+  }
+
+  /**
+   * Obtains a Data object from the given source of unknown type,
+   * and adds it to this cell.
+   *
+   * @return Variable name of the newly added data.
+   */
+  public String addDataSource(String source)
+    throws VisADException, RemoteException
+  {
+    return addDataSource(0, source, UNKNOWN_SOURCE, true);
+  }
+
+  /**
+   * Obtains a Data object from the given source of the specified type,
+   * and adds it to this cell.
+   *
+   * @return Variable name of the newly added data.
+   */
+  public String addDataSource(String source, int type)
+    throws VisADException, RemoteException
+  {
+    return addDataSource(0, source, type, true);
+  }
+
+  /**
+   * Obtains a Data object from the given source of the specified type,
+   * and adds it to this cell, assigning it the specified id.
+   *
+   * @return Variable name of the newly added data.
+   */
+  String addDataSource(int id, String source, int type, boolean notify)
+    throws VisADException, RemoteException
+  {
+    String varName = null;
+
+    if (type == UNKNOWN_SOURCE) {
+      // determine source type
+      if (source.startsWith("rmi://")) type = RMI_SOURCE;
+      else if (source.startsWith("adde://")) type = URL_SOURCE;
+      else {
+        File f = new File(source);
+        if (f.exists()) type = URL_SOURCE;
+        else {
+          URL url = null;
+          try {
+            url = new URL(source);
+          }
+          catch (MalformedURLException exc) {
+            if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+          }
+          if (url != null) type = URL_SOURCE;
+          else type = FORMULA_SOURCE;
+        }
+      }
+    }
+
+    if (type == DIRECT_SOURCE || type == REMOTE_SOURCE) {
+      // direct and remote source types are invalid
+      throw new VisADException("Invalid source type");
+    }
+
+    if (type == URL_SOURCE) {
+      // obtain data from filename or URL
+      beginWait(true);
+
+      Data data = null;
+      boolean success = true;
+      try {
+        // load file or URL
+        DefaultFamily loader = new DefaultFamily("loader", true);
+        if (source.startsWith("file:")) {
+          source = source.substring(5);
+          // if source looks like it starts with a Windows drive spec...
+          if (source.length() > 2 && source.charAt(2) == ':' &&
+            source.charAt(0) == '/')
+          {
+            source = source.substring(1);
+          }
+        }
+        data = loader.open(source);
+
+        // check if source is a local file
+        File file = new File(source);
+        if (file.exists()) {
+          String path = file.getAbsolutePath();
+          char[] p = path.toCharArray();
+          for (int i=0; i<p.length; i++) if (p[i] == '\\') p[i] = '/';
+          path = new String(p);
+          source = "file:" + (path.startsWith("/") ? "" : "/") + path;
+        }
+      }
+      catch (OutOfMemoryError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+        throw new VisADException("Not enough memory to import the data.");
+      }
+      catch (BadFormException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+        throw new VisADException(
+          "The source could not be converted to VisAD data.");
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+        throw exc;
+      }
+      finally {
+        endWait(!success);
+      }
+      if (data == null) {
+        throw new VisADException("Could not load data from source " + source);
+      }
+      varName = addData(id, data, null, source, URL_SOURCE, notify);
+    }
+
+    else if (type == FORMULA_SOURCE) {
+      synchronized (CellData) {
+        SSCellData cellData = addReferenceImpl(id, null, null,
+          source, FORMULA_SOURCE, false, false);
+        varName = cellData.getVariableName();
+        if (!IsRemote) {
+          // SERVER: link data to formula computation
+          fm.assignFormula(varName, source);
+        }
+      }
+
+      if (notify) {
+        // notify linked cells of source addition
+        sendMessage(ADD_SOURCE, source, new Real(type));
+      }
+    }
+
+    else if (type == RMI_SOURCE) {
+      // obtain data from an RMI server
+      // example of RMI address: rmi://www.ssec.wisc.edu/MyServer/A1
+      if (!source.startsWith("rmi://")) {
+        throw new VisADException("RMI address must begin with rmi://");
+      }
+      final DataReferenceImpl lref = new DataReferenceImpl(Name);
+
+      SSCellData cellData;
+      synchronized (CellData) {
+        cellData =
+          addReferenceImpl(id, lref, null, source, RMI_SOURCE, false, false);
+        varName = cellData.getVariableName();
+      }
+
+      if (!IsRemote) {
+        // SERVER: obtain data from RMI address
+        beginWait(true);
+        boolean success = true;
+        try {
+          // attempt to obtain data from RMI server
+          int len = source.length();
+          int end = source.lastIndexOf("/");
+          if (end < 6) end = len;
+          String server = source.substring(4, end);
+          String object = (end < len - 1) ? source.substring(end + 1) : "";
+          RemoteServer rs = null;
+          rs = (RemoteServer) Naming.lookup(server);
+          RemoteDataReference ref = rs.getDataReference(object);
+          if (ref == null) throw new VisADException("The remote object " +
+            "called \"" + object + "\" does not exist");
+
+          // set up cell to update local data when remote data changes
+          final SSCellData fcd = cellData;
+          final RemoteDataReference rref = ref;
+          final BasicSSCell cell = this;
+          CellImpl lcell = new CellImpl() {
+            public void doAction() {
+              try {
+                lref.setData(DataUtility.makeLocal(rref.getData(), DEBUG));
+              }
+              catch (NullPointerException exc) {
+                if (DEBUG) exc.printStackTrace();
+                fcd.setError("Remote data is null");
+              }
+              catch (VisADException exc) {
+                if (DEBUG) exc.printStackTrace();
+                fcd.setError("Could not update remote data");
+              }
+              catch (RemoteException exc) {
+                if (DEBUG) exc.printStackTrace();
+                fcd.setError("Unable to import updated remote data");
+              }
+            }
+          };
+          RemoteCellImpl rcell = new RemoteCellImpl(lcell);
+          rcell.addReference(ref);
+        }
+        catch (ClassCastException exc) {
+          if (DEBUG) exc.printStackTrace();
+          success = false;
+          throw new VisADException("The name of the RMI server is not valid.");
+        }
+        catch (MalformedURLException exc) {
+          if (DEBUG) exc.printStackTrace();
+          success = false;
+          throw new VisADException("The name of the RMI server is not valid.");
+        }
+        catch (NotBoundException exc) {
+          if (DEBUG) exc.printStackTrace();
+          success = false;
+          throw new VisADException(
+            "The remote data specified does not exist.");
+        }
+        catch (AccessException exc) {
+          if (DEBUG) exc.printStackTrace();
+          success = false;
+          throw new VisADException(
+            "Could not gain access to the remote data.");
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+          success = false;
+          throw new VisADException("Could not connect to the RMI server.");
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+          success = false;
+          throw exc;
+        }
+        finally {
+          endWait(!success);
+        }
+      }
+
+      if (notify) {
+        // notify linked cells of source addition
+        sendMessage(ADD_SOURCE, source, new Real(type));
+      }
+    }
+
+    return varName;
+  }
+
+  /**
+   * Does the work of adding the given DataReference,
+   * from the given source of the specified type.
+   *
+   * @return The newly created SSCellData object.
+   */
+  protected SSCellData addReferenceImpl(int id, DataReferenceImpl ref,
+    ConstantMap[] cmaps, String source, int type, boolean notify,
+    boolean checkErrors) throws VisADException, RemoteException
+  {
+    // ensure that id is valid
+    if (id == 0) id = getFirstFreeId();
+
+    // ensure that ref is valid
+    if (ref == null) ref = new DataReferenceImpl(Name);
+
+    // notify linked cells of data addition (ADD_DATA message must come first)
+    if (notify) sendMessage(ADD_DATA, source, ref.getData());
+
+    // add data reference to cell
+    SSCellData cellData =
+      new SSCellData(id, this, ref, cmaps, source, type, checkErrors);
+    CellData.add(cellData);
+
+    if (!IsRemote) {
+      // SERVER: add data reference to display
+      if (HasMappings) VDisplay.addReference(ref, cmaps);
+
+      // add remote data reference to servers
+      synchronized (Servers) {
+        RemoteDataReferenceImpl remoteRef =
+          (RemoteDataReferenceImpl) cellData.getRemoteReference();
+        int len = Servers.size();
+        for (int i=0; i<len; i++) {
+          RemoteServerImpl rs = (RemoteServerImpl) Servers.elementAt(i);
+          rs.addDataReference(remoteRef);
+        }
+      }
+    }
+
+    return cellData;
+  }
+
+
+  // -- Remove data --
+
+  /**
+   * Removes the given Data object from this cell.
+   */
+  public void removeData(Data data) throws VisADException, RemoteException {
+    boolean found = false;
+    synchronized (CellData) {
+      int len = CellData.size();
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        if (cellData.getData() == data) {
+          removeDataImpl(cellData, true, true);
+          found = true;
+          break;
+        }
+      }
+    }
+    if (!found) {
+      throw new VisADException("The given Data object does not exist");
+    }
+  }
+
+  /**
+   * Removes the Data object corresponding to the
+   * given variable name from this cell.
+   */
+  public void removeData(String varName)
+    throws VisADException, RemoteException
+  {
+    synchronized (CellData) {
+      SSCellData cellData = getCellDataByName(varName);
+      if (cellData == null) {
+        throw new VisADException(
+          "Data object called " + varName + " does not exist");
+      }
+      removeDataImpl(cellData, true, true);
+    }
+  }
+
+  /**
+   * Removes the given DataReference's associated Data object from this cell.
+   */
+  public void removeReference(DataReferenceImpl ref)
+    throws VisADException, RemoteException
+  {
+    boolean found = false;
+    synchronized (CellData) {
+      int len = CellData.size();
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        if (cellData.getReference() == ref) {
+          removeDataImpl(cellData, true, true);
+          found = true;
+          break;
+        }
+      }
+    }
+    if (!found) {
+      throw new VisADException("The given DataReference does not exist");
+    }
+  }
+
+  /**
+   * Removes all Data objects from this cell.
+   */
+  public void removeAllReferences() throws VisADException, RemoteException {
+    removeAllReferences(true, true);
+  }
+
+  /**
+   * Removes all Data objects from this cell, notifying listeners if the
+   * notify flag is set.
+   */
+  protected void removeAllReferences(boolean notify)
+    throws VisADException, RemoteException
+  {
+    removeAllReferences(notify, true);
+  }
+
+  /**
+   * Removes all Data objects from this cell, notifying listeners if the
+   * notify flag is set, and updating the display if the display flag is set.
+   */
+  protected void removeAllReferences(boolean notify, boolean display)
+    throws VisADException, RemoteException
+  {
+    synchronized (CellData) {
+      int len = CellData.size();
+      for (int i=0; i<len; i++) {
+        removeDataImpl((SSCellData) CellData.firstElement(), notify, display);
+      }
+    }
+  }
+
+  /**
+   * Does the work of removing the Data object at the specified index.
+   */
+  protected void removeDataImpl(SSCellData cellData, boolean notify,
+    boolean display) throws VisADException, RemoteException
+  {
+    String varName = cellData.getVariableName();
+
+    // notify linked cells of data removal
+    if (notify) sendMessage(REMOVE_DATA, varName, null);
+
+    if (!IsRemote) {
+      // remove data reference from display
+      if (HasMappings) VDisplay.removeReference(cellData.getReference());
+    
+      // remove data reference from all servers
+      synchronized (Servers) {
+        RemoteDataReferenceImpl ref =
+          (RemoteDataReferenceImpl) cellData.getRemoteReference();
+        int len = Servers.size();
+        for (int i=0; i<len; i++) {
+          RemoteServerImpl rs = (RemoteServerImpl) Servers.elementAt(i);
+          rs.removeDataReference(ref);
+        }
+      }
+    }
+
+    // purge cell data
+    CellData.remove(cellData);
+    cellData.destroy();
+    cellData = null;
+
+    // clear cell if no data objects are left
+    if (display) {
+      if (hasData()) updateDisplay();
+      else clearDisplay();
+    }
+  }
+
+  // -- Wait for data to load --
+
+  /**
+   * Blocks until the Data object with the given variable name
+   * finishes loading.
+   */
+  public void waitForData(String varName) throws VisADException {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = getCellDataByName(varName);
+    }
+
+    // wait for formula computation to finish
+    fm.waitForFormula(varName);
+
+    // wait for cell data to initialize
+    while (!cellData.isInited()) {
+      try {
+        Thread.sleep(POLLING_INTERVAL);
+      }
+      catch (InterruptedException exc) {
+        if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * Blocks until all of this cell's Data objects finish loading.
+   */
+  public void waitForData() throws VisADException {
+    // compile list of data variable names
+    String[] varNames;
+    int len;
+    synchronized (CellData) {
+      len = CellData.size();
+      varNames = new String[len];
+      for (int i=0; i<len; i++) {
+        // get data's variable name
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        varNames[i] = cellData.getVariableName();
+      }
+    }
+
+    // wait for file, URL and RMI loads to finish
+    while (Loading > 0) {
+      try {
+        Thread.sleep(POLLING_INTERVAL);
+      }
+      catch (InterruptedException exc) {
+        if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+      }
+    }
+
+    // wait for each data object
+    for (int i=0; i<len; i++) waitForData(varNames[i]);
+  }
+
+  // -- Save data --
+
+  /**
+   * Exports a Data object to the given location, using the given Data form.
+   */
+  public void saveData(String varName, String location, Form form)
+    throws BadFormException, IOException, VisADException, RemoteException
+  {
+    if (IsSlave) {
+      // SLAVE: saveData not supported
+      throw new VisADException("Cannot saveData on a slaved cell");
+    }
+    Data data = getData(varName);
+    Saving++;
+    try {
+      form.save(location, data, true);
+    }
+    finally {
+      Saving--;
+    }
+  }
+
+  // -- Utility --
+
+  /**
+   * Obtains the cell data entry corresponding to the given variable name.
+   */
+  protected SSCellData getCellDataByName(String varName) {
+    int len = CellData.size();
+    SSCellData cellData = null;
+    for (int i=0; i<len; i++) {
+      SSCellData cd = (SSCellData) CellData.elementAt(i);
+      if (cd.getVariableName().equals(varName)) {
+        cellData = cd;
+        break;
+      }
+    }
+    return cellData;
+  }
+
+  /**
+   * Gets the first free cell data ID number.
+   */
+  protected int getFirstFreeId() {
+    synchronized (CellData) {
+      if (CellData.size() == 0) return 1;
+      SSCellData cellData = (SSCellData) CellData.lastElement();
+      return cellData.getId() + 1;
+    }
+  }
+
+
+  // --- DISPLAY MANAGEMENT ---
+
+  /**
+   * Constant for 3-D (Java3D) dimensionality.
+   */
+  public static final int JAVA3D_3D = 1;
+
+  /**
+   * Constant for 2-D (Java2D) dimensionality.
+   */
+  public static final int JAVA2D_2D = 2;
+
+  /**
+   * Constant for 2-D (Java3D) dimensionality.
+   */
+  public static final int JAVA3D_2D = 3;
+
+  /**
+   * Class name of the 3-D (Java3D) display renderer.
+   */
+  private static final String j33 = "visad.java3d.DefaultDisplayRendererJ3D";
+
+  /**
+   * Class name of the 2-D (Java2D) display renderer.
+   */
+  private static final String j22 = "visad.java2d.DefaultDisplayRendererJ2D";
+
+  /**
+   * Class name of the 2-D (Java3D) display renderer.
+   */
+  private static final String j32 = "visad.java3d.TwoDDisplayRendererJ3D";
+
+  /**
+   * Class name of the image 3-D (Java3D) display renderer.
+   */
+  private static final String jir = "visad.bom.ImageRendererJ3D";
+
+  /**
+   * Associated VisAD Display.
+   */
+  protected DisplayImpl VDisplay;
+
+  /**
+   * The dimensionality of the display: JAVA3D_3D, JAVA2D_2D, or JAVA3D_2D.
+   */
+  protected int Dim = -1;
+
+  /**
+   * Whether this cell has mappings from Data to Display.
+   */
+  protected boolean HasMappings = false;
+
+
+  // -- Build display --
+
+  /**
+   * Reconstructs this cell's display.
+   */
+  public synchronized boolean constructDisplay() {
+    boolean success = true;
+    DisplayImpl newDisplay = VDisplay;
+    RemoteDisplay rmtDisplay = RemoteVDisplay;
+    if (IsSlave) {
+      // SLAVE: construct dummy 2-D display
+      try {
+        newDisplay = new DisplayImplJ2D("DUMMY");
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    else if (!CanDo3D && Dim != JAVA2D_2D) {
+      // dimension requires Java3D, but Java3D is disabled for this JVM
+      success = false;
+    }
+    else {
+      // construct display of the proper dimension
+      try {
+        if (IsRemote) {
+          // CLIENT: construct new display from server's remote copy
+          if (Dim == JAVA3D_3D) newDisplay = new DisplayImplJ3D(rmtDisplay);
+          else if (Dim == JAVA2D_2D) {
+            newDisplay = new DisplayImplJ2D(rmtDisplay);
+          }
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(rmtDisplay, tdr);
+          }
+        }
+        else {
+          // SERVER: construct new display and make a remote copy
+          if (Dim == JAVA3D_3D) newDisplay = new DisplayImplJ3D(Name);
+          else if (Dim == JAVA2D_2D) newDisplay = new DisplayImplJ2D(Name);
+          else { // Dim == JAVA3D_2D
+            TwoDDisplayRendererJ3D tdr = new TwoDDisplayRendererJ3D();
+            newDisplay = new DisplayImplJ3D(Name, tdr);
+          }
+          rmtDisplay = new RemoteDisplayImpl(newDisplay);
+        }
+      }
+      catch (NoClassDefFoundError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (UnsatisfiedLinkError err) {
+        if (DEBUG) err.printStackTrace();
+        success = false;
+      }
+      catch (Exception exc) {
+        if (DEBUG) exc.printStackTrace();
+        success = false;
+      }
+    }
+    if (success) {
+      if (VDisplay != null) {
+        try {
+          VDisplay.destroy();
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+      VDisplay = newDisplay;
+      RemoteVDisplay = rmtDisplay;
+    }
+    return success;
+  }
+
+  // -- Handle display changes --
+
+  /**
+   * Handles display changes.
+   */
+  public void displayChanged(DisplayEvent e) {
+    int id = e.getId();
+    if (id == DisplayEvent.TRANSFORM_DONE ||
+      (id == DisplayEvent.FRAME_DONE && IsSlave && !hasDisplay()))
+    {
+      if (!hasDisplay()) {
+        initDisplayPanel();
+        updateDisplay(true);
+      }
+      // display has changed; notify listeners
+      notifySSCellListeners(SSCellChangeEvent.DISPLAY_CHANGE);
+    }
+    else if (id == DisplayEvent.MAPS_CLEARED) updateDisplay(false);
+  }
+
+  // -- Set & clear mappings --
+
+  /**
+   * Maps RealTypes to the display according to the specified ScalarMaps.
+   */
+  public synchronized void setMaps(ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    if (maps == null) return;
+
+    VisADException vexc = null;
+    RemoteException rexc = null;
+
+    if (IsRemote) {
+      // CLIENT: send new mappings to server
+      sendMessage(SET_MAPS, DataUtility.convertMapsToString(maps), null);
+    }
+    else {
+      // SERVER: set up mappings
+      DataReference[] dr;
+      ConstantMap[][] cmaps;
+      synchronized (CellData) {
+        int len = CellData.size();
+        dr = new DataReference[len];
+        cmaps = new ConstantMap[len][];
+        for (int i=0; i<len; i++) {
+          SSCellData cellData = (SSCellData) CellData.elementAt(i);
+          dr[i] = cellData.getReference();
+          cmaps[i] = cellData.getConstantMaps();
+        }
+      }
+      String save = getPartialSaveString();
+      VDisplay.disableAction();
+      clearMaps();
+      for (int i=0; i<maps.length; i++) {
+        if (maps[i] != null) {
+          try {
+            VDisplay.addMap(maps[i]);
+          }
+          catch (VisADException exc) {
+            vexc = exc;
+          }
+          catch (RemoteException exc) {
+            rexc = exc;
+          }
+        }
+      }
+      for (int i=0; i<dr.length; i++) {
+        // determine if ImageRendererJ3D can be used
+        boolean ok = false;
+        Data data = dr[i].getData();
+        if (data == null) {
+          if (DEBUG) warn("data #" + i + " is null; cannot analyze MathType");
+        }
+        else if (Possible3D) {
+          if (data instanceof FieldImpl) {
+            visad.Set set = ((FieldImpl) data).getDomainSet();
+            if (set instanceof GriddedSet && set.getManifoldDimension() == 2) {
+              MathType type = data.getType();
+              try {
+                ok = ImageRendererJ3D.isRendererUsable(type, maps);
+              }
+              catch (VisADException exc) {
+                if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+              }
+            }
+          }
+        }
+        // add reference
+        if (ok && Dim != JAVA2D_2D) {
+          VDisplay.addReferences(new ImageRendererJ3D(), dr[i], cmaps[i]);
+        }
+        else {
+          if (DEBUG) warn("data #" + i + " cannot use ImageRendererJ3D");
+          VDisplay.addReference(dr[i], cmaps[i]);
+        }
+      }
+      setPartialSaveString(save, true);
+      VDisplay.enableAction();
+    }
+    HasMappings = true;
+    if (vexc != null) throw vexc;
+    if (rexc != null) throw rexc;
+  }
+
+  /**
+   * Clears this cell's mappings.
+   */
+  public void clearMaps() throws VisADException, RemoteException {
+    if (IsRemote) clearMapsClone(true);
+    else if (hasMappings()) {
+      VDisplay.removeAllReferences();
+      VDisplay.clearMaps();
+      HasMappings = false;
+    }
+  }
+
+  /**
+   * Clears this cloned cell's mappings.
+   */
+  private void clearMapsClone(boolean display)
+    throws VisADException, RemoteException
+  {
+    if (hasMappings()) {
+      RemoteVDisplay.removeAllReferences();
+      RemoteVDisplay.clearMaps();
+      if (display) {
+        clearDisplay();
+        constructDisplay();
+        initDisplayPanel();
+        updateDisplay(true);
+      }
+      HasMappings = false;
+    }
+  }
+
+  // -- Clear cell --
+
+  /**
+   * Clears this cell's display.
+   */
+  public void clearDisplay() throws VisADException, RemoteException {
+    if (!DisplayEnabled) return;
+    HasDisplay = false;
+    Util.invoke(false, DEBUG, new Runnable() {
+      public void run() {
+        removeAll();
+        refresh();
+      }
+    });
+  }
+
+  /**
+   * Clears this cell completely.
+   */
+  public void clearCell() throws VisADException, RemoteException {
+    removeAllReferences();
+  }
+
+  /**
+   * Clears this cell completely and destroys it,
+   * removing it from the list of created cells.
+   */
+  public void destroyCell() throws VisADException, RemoteException {
+    RemoteException problem = null;
+    setDisplayEnabled(false);
+
+    // remove all data objects from this cell
+    removeAllReferences(false, !IsRemote);
+
+    if (!IsRemote) {
+      // SERVER: stop serving this cell
+      clearCell();
+      int slen = Servers.size();
+      for (int i=0; i<slen; i++) {
+        RemoteServerImpl rs = (RemoteServerImpl) Servers.elementAt(i);
+        try {
+          removeFromRemoteServer(rs);
+        }
+        catch (RemoteException exc) {
+          problem = exc;
+        }
+      }
+    }
+    else if (IsSlave && RemoteVSlave != null) {
+      // SLAVE: disconnect cleanly
+      try {
+        RemoteVSlave.unlink();
+      }
+      catch (RemoteException exc) {
+        problem = exc;
+      }
+    }
+
+    // remove cell from static list
+    synchronized (SSCellVector) {
+      SSCellVector.remove(this);
+    }
+
+    if (problem != null) throw problem;
+  }
+
+  // -- Set dimension --
+
+  /**
+   * Sets this cell's dimensionality.
+   */
+  public void setDimension(int dim) throws VisADException, RemoteException {
+    if (dim == Dim) return;
+    if (dim != JAVA3D_3D && dim != JAVA2D_2D && dim != JAVA3D_2D) {
+      throw new VisADException("Invalid dimension");
+    }
+
+    if (!IsRemote) {
+      // SERVER: do dimension switch
+      Dim = dim;
+      synchronized (DListen) {
+        // remove listeners temporarily
+        detachListeners();
+
+        // save current mappings for restoration after dimension switch
+        ScalarMap[] maps = null;
+        if (VDisplay != null) {
+          Vector mapVector = VDisplay.getMapVector();
+          int mvs = mapVector.size();
+          if (mvs > 0) {
+            maps = new ScalarMap[mvs];
+            for (int i=0; i<mvs; i++) {
+              maps[i] = (ScalarMap) mapVector.elementAt(i);
+            }
+          }
+        }
+
+        synchronized (Servers) {
+          // remove old display from all RemoteServers
+          int slen = Servers.size();
+          for (int i=0; i<slen; i++) {
+            RemoteServerImpl rsi = (RemoteServerImpl) Servers.elementAt(i);
+            rsi.removeDisplay((RemoteDisplayImpl) RemoteVDisplay);
+          }
+
+          // switch display dimension
+          constructDisplay();
+
+          for (int i=0; i<slen; i++) {
+            RemoteServerImpl rsi = (RemoteServerImpl) Servers.elementAt(i);
+            rsi.addDisplay((RemoteDisplayImpl) RemoteVDisplay);
+          }
+        }
+
+        // put mappings back
+        if (maps != null) {
+          try {
+            setMaps(maps);
+          }
+          catch (VisADException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+        }
+
+        // reinitialize display
+        initDisplayPanel();
+        updateDisplay(hasData());
+
+        // put listeners back
+        attachListeners();
+      }
+
+      // broadcast dimension change event
+      notifySSCellListeners(SSCellChangeEvent.DIMENSION_CHANGE);
+    }
+
+    // notify linked cells of dimension change
+    sendMessage(SET_DIM, IsRemote ? "" : null, new Real(dim));
+  }
+
+  /**
+   * Updates the dimension of this cloned cell to match that of the server.
+   */
+  private void setDimClone() throws VisADException, RemoteException {
+    synchronized (DListen) {
+      // remove listeners temporarily
+      detachListeners();
+
+      // remove old display panel from cell
+      clearDisplay();
+
+      // get updated display from server
+      RemoteVDisplay = RemoteVServer.getDisplay(Name);
+
+      // update remote slave display
+      if (IsSlave) {
+        if (RemoteVSlave != null) RemoteVSlave.unlink();
+        RemoteVSlave = new RemoteSlaveDisplayImpl(RemoteVDisplay);
+      }
+
+      // autodetect new dimension
+      String s = RemoteVDisplay.getDisplayRendererClassName();
+      if (s.equals(j33) || s.equals(jir)) Dim = JAVA3D_3D;
+      else if (s.equals(j22)) Dim = JAVA2D_2D;
+      else if (s.equals(j32)) Dim = JAVA3D_2D;
+
+      // construct new display from server's display
+      boolean success = constructDisplay();
+
+      if (!success) {
+        // set up error message canvas
+        JComponent errorCanvas;
+        if (Dim == JAVA2D_2D) {
+          errorCanvas = new JComponent() {
+            public void paint(Graphics g) {
+              g.setColor(Color.white);
+              g.drawString("A serious error occurred while " +
+                "constructing this display.", 8, 20);
+            }
+          };
+        }
+        else {
+          errorCanvas = new JComponent() {
+            public void paint(Graphics g) {
+              g.setColor(Color.white);
+              g.drawString("This machine does not support Java3D.", 8, 20);
+              g.drawString("Switch the dimension to 2-D (Java2D) to " +
+                "view this display.", 8, 35);
+            }
+          };
+        }
+
+        // set up dummy display
+        VDisplay = new DisplayImplJ2D("DUMMY");
+
+        // redraw cell
+        final JComponent ec = errorCanvas;
+        Util.invoke(false, DEBUG, new Runnable() {
+          public void run() {
+            removeAll();
+            add(ec);
+            refresh();
+          }
+        });
+      }
+
+      // reinitialize display
+      initDisplayPanel();
+      if (success && hasData()) updateDisplay(true);
+
+      // put all listeners back
+      attachListeners();
+    }
+
+    // broadcast dimension change event
+    notifySSCellListeners(SSCellChangeEvent.DIMENSION_CHANGE);
+  }
+
+  // -- Capture display image --
+
+  /**
+   * Captures an image and saves it to a given file name, in JPEG format.
+   */
+  public void captureImage(File f) throws VisADException, IOException {
+    BufferedImage image =
+      IsSlave ? RemoteVSlave.getImage() : VDisplay.getImage();
+    try {
+      Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
+      ImageWriter writer = iter.next();
+      ImageWriteParam param = writer.getDefaultWriteParam();
+      param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+      param.setCompressionQuality(1.0f);
+      FileOutputStream fout = new FileOutputStream(f);
+      writer.setOutput(fout);
+      IIOImage iio = new IIOImage(image, null, null);
+      writer.write(null, iio, param);
+      ImageIO.write(image, "image/jpeg", fout);
+      fout.close();
+    }
+    catch (NoClassDefFoundError err) {
+      throw new VisADException("JPEG codec not found");
+    }
+  }
+
+
+  // --- SAVE STRINGS ---
+
+  /**
+   * Gets the save string necessary to reconstruct this cell.
+   */
+  public String getSaveString() {
+    StringBuffer sb = new StringBuffer();
+
+    // append data information
+    synchronized (CellData) {
+      int len = CellData.size();
+      sb.append("# ");
+      sb.append(Name);
+      sb.append(": data information\n");
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        sb.append("id = ");
+        sb.append(cellData.getId());
+        sb.append('\n');
+        sb.append("source = ");
+        sb.append(cellData.getSource());
+        sb.append('\n');
+        sb.append("source type = ");
+        sb.append(cellData.getSourceType());
+        sb.append('\n');
+      }
+    }
+
+    // append display information
+    sb.append("\n# ");
+    sb.append(Name);
+    sb.append(": display information\n");
+
+    // add dimension to save string
+    sb.append("dim = ");
+    sb.append(Dim);
+    sb.append('\n');
+
+    // add mapping and control information to save string
+    sb.append(getPartialSaveString());
+
+    return sb.toString();
+  }
+
+  /**
+   * Reconstructs this cell using the specified save string.
+   */
+  public void setSaveString(String save)
+    throws VisADException, RemoteException
+  {
+    setPartialSaveString(save, false);
+  }
+
+  /**
+   * Gets a string for reconstructing ScalarMap range
+   * and Control information.
+   */
+  public String getPartialSaveString() {
+    StringBuffer sb = new StringBuffer();
+    Vector mapVector = null;
+    if (hasMappings()) {
+      mapVector = VDisplay.getMapVector();
+      int mvs = mapVector.size();
+      if (mvs > 0) {
+        // add mappings to save string
+        sb.append("maps =");
+        sb.append(DataUtility.convertMapsToString(mapVector));
+        sb.append('\n');
+
+        // add map ranges to save string
+        sb.append("map ranges =");
+        for (int i=0; i<mvs; i++) {
+          ScalarMap m = (ScalarMap) mapVector.elementAt(i);
+          double[] range = new double[2];
+          boolean scale = m.getScale(new double[2], range, new double[2]);
+          if (scale) {
+            sb.append(' ');
+            sb.append(range[0]);
+            sb.append(' ');
+            sb.append(range[1]);
+          }
+        }
+        sb.append('\n');
+      }
+    }
+
+    if (hasDisplay()) {
+      // add projection control state to save string
+      ProjectionControl pc = VDisplay.getProjectionControl();
+      if (pc != null) {
+        sb.append("projection = ");
+        sb.append(pc.getSaveString());
+      }
+
+      // add graphics mode control settings to save string
+      GraphicsModeControl gmc = VDisplay.getGraphicsModeControl();
+      if (gmc != null) {
+        sb.append("graphics mode = ");
+        sb.append(gmc.getSaveString());
+        sb.append('\n');
+      }
+
+      // add color control state(s) to save string
+      Vector cv = VDisplay.getControls(ColorControl.class);
+      if (cv != null) {
+        int cvlen = cv.size();
+        for (int i=0; i<cvlen; i++) {
+          ColorControl cc = (ColorControl) cv.elementAt(i);
+          if (cc != null) {
+            sb.append("color = ");
+            sb.append(cc.getSaveString());
+          }
+        }
+      }
+
+      // add contour control state(s) to save string
+      cv = VDisplay.getControls(ContourControl.class);
+      if (cv != null) {
+        int cvlen = cv.size();
+        for (int i=0; i<cvlen; i++) {
+          ContourControl cc = (ContourControl) cv.elementAt(i);
+          if (cc != null) {
+            sb.append("contour = ");
+            sb.append(cc.getSaveString());
+            sb.append('\n');
+          }
+        }
+      }
+
+      // add range control state(s) to save string
+      cv = VDisplay.getControls(RangeControl.class);
+      if (cv != null) {
+        int cvlen = cv.size();
+        for (int i=0; i<cvlen; i++) {
+          RangeControl rc = (RangeControl) cv.elementAt(i);
+          if (rc != null) {
+            sb.append("range = ");
+            sb.append(rc.getSaveString());
+            sb.append('\n');
+          }
+        }
+      }
+
+      // add animation control state(s) to save string
+      cv = VDisplay.getControls(AnimationControl.class);
+      if (cv != null) {
+        int cvlen = cv.size();
+        for (int i=0; i<cvlen; i++) {
+          AnimationControl ac = (AnimationControl) cv.elementAt(i);
+          if (ac != null) {
+            sb.append("anim = ");
+            sb.append(ac.getSaveString());
+            sb.append('\n');
+          }
+        }
+      }
+
+      // add value control state(s) to save string
+      cv = VDisplay.getControls(ValueControl.class);
+      if (cv != null) {
+        int cvlen = cv.size();
+        for (int i=0; i<cvlen; i++) {
+          ValueControl vc = (ValueControl) cv.elementAt(i);
+          if (vc != null) {
+            sb.append("value = ");
+            sb.append(vc.getSaveString());
+            sb.append('\n');
+          }
+        }
+      }
+    }
+
+    return sb.toString();
+  }
+
+  /**
+   * Reconstructs parts of this cell using the specified save string.
+   */
+  public void setPartialSaveString(String save, boolean preserveMaps)
+    throws VisADException, RemoteException
+  {
+    // make sure cell is not remote
+    if (IsRemote) {
+      throw new VisADException("Cannot setSaveString on a remote cell");
+    }
+
+    // data variables
+    Vector ids = new Vector();
+    Vector sources = new Vector();
+    Vector types = new Vector();
+
+    // display variables
+    int dim = -1;
+    String mapString = null;
+    ScalarMap[] maps = null;
+    Vector mapMins = null;
+    Vector mapMaxs = null;
+    String proj = null;
+    String mode = null;
+    Vector color = new Vector();
+    Vector contour = new Vector();
+    Vector range = new Vector();
+    Vector anim = new Vector();
+    Vector selectVal = new Vector();
+
+    // parse the save string into "keyword = value" tokens
+    SaveStringTokenizer sst = new SaveStringTokenizer(save);
+    for (int i=0; i<sst.keywords.length; i++) {
+      String keyword = sst.keywords[i];
+      String value = sst.values[i];
+
+      // id
+      if (keyword.equalsIgnoreCase("id") ||
+        keyword.equalsIgnoreCase("data id") ||
+        keyword.equalsIgnoreCase("data_id") ||
+        keyword.equalsIgnoreCase("dataid"))
+      {
+        try {
+          ids.add(new Integer(value));
+        }
+        catch (NumberFormatException exc) {
+          // invalid id value
+          if (DEBUG) exc.printStackTrace();
+          warn("data id value " + value + " is not valid and will be ignored");
+        }
+      }
+
+      // source
+      else if (keyword.equalsIgnoreCase("source") ||
+        keyword.equalsIgnoreCase("data source") ||
+        keyword.equalsIgnoreCase("data_source") ||
+        keyword.equalsIgnoreCase("datasource"))
+      {
+        sources.add(value);
+      }
+
+      // source type
+      else if (keyword.equalsIgnoreCase("source type") ||
+        keyword.equalsIgnoreCase("source_type") ||
+        keyword.equalsIgnoreCase("sourcetype") ||
+        keyword.equalsIgnoreCase("data source type") ||
+        keyword.equalsIgnoreCase("data_source_type") ||
+        keyword.equalsIgnoreCase("datasourcetype"))
+      {
+        try {
+          types.add(new Integer(value));
+        }
+        catch (NumberFormatException exc) {
+          // invalid source type value
+          if (DEBUG) exc.printStackTrace();
+          warn("source type value " + value +
+            " is not valid and will be ignored");
+        }
+      }
+
+      // filename (old keyword)
+      else if (keyword.equalsIgnoreCase("filename") ||
+        keyword.equalsIgnoreCase("file name") ||
+        keyword.equalsIgnoreCase("file_name") ||
+        keyword.equalsIgnoreCase("file"))
+      {
+        ids.add(new Integer(0));
+        sources.add(value);
+        types.add(new Integer(URL_SOURCE));
+      }
+
+      // rmi address (old keyword)
+      else if (keyword.equalsIgnoreCase("rmi") ||
+        keyword.equalsIgnoreCase("rmi address") ||
+        keyword.equalsIgnoreCase("rmi_address") ||
+        keyword.equalsIgnoreCase("rmiaddress"))
+      {
+        ids.add(new Integer(0));
+        sources.add(value);
+        types.add(new Integer(RMI_SOURCE));
+      }
+
+      // formula (old keyword)
+      else if (keyword.equalsIgnoreCase("formula") ||
+        keyword.equalsIgnoreCase("equation"))
+      {
+        ids.add(new Integer(0));
+        sources.add(value);
+        types.add(new Integer(FORMULA_SOURCE));
+      }
+
+      // dimension
+      else if (keyword.equalsIgnoreCase("dim") ||
+        keyword.equalsIgnoreCase("dimension"))
+      {
+        int d = -1;
+        try {
+          d = Integer.parseInt(value);
+        }
+        catch (NumberFormatException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        if (d > 0 && d < 4) dim = d;
+        else {
+          // invalid dimension value
+          warn("dimension value " + value +
+            " is not valid and will be ignored");
+        }
+      }
+
+      // mappings
+      else if (keyword.equalsIgnoreCase("maps") ||
+        keyword.equalsIgnoreCase("mappings"))
+      {
+        mapString = value;
+      }
+
+      // mapping ranges
+      else if (keyword.equalsIgnoreCase("map ranges") ||
+        keyword.equalsIgnoreCase("map_ranges") ||
+        keyword.equalsIgnoreCase("mapranges"))
+      {
+        StringTokenizer st = new StringTokenizer(value);
+        mapMins = new Vector();
+        mapMaxs = new Vector();
+        while (true) {
+          if (!st.hasMoreTokens()) break;
+          String s1 = st.nextToken();
+          if (!st.hasMoreTokens()) {
+            warn("trailing map range min value " + s1 +
+              " has no corresponding max value and will be ignored");
+            break;
+          }
+          String s2 = st.nextToken();
+          Double d1 = null, d2 = null;
+          try {
+            d1 = new Double(s1.equals("NaN") ?
+              Double.NaN : Double.parseDouble(s1));
+            d2 = new Double(s2.equals("NaN") ?
+              Double.NaN : Double.parseDouble(s2));
+          }
+          catch (NumberFormatException exc) {
+            if (DEBUG) exc.printStackTrace();
+          }
+          if (d1 == null || d2 == null) {
+            warn("map range min/max pair (" + s1 + ", " + s2 +
+              ") is not valid and will be ignored");
+          }
+          else {
+            mapMins.add(d1);
+            mapMaxs.add(d2);
+          }
+        }
+      }
+
+      // projection matrix
+      else if (keyword.equalsIgnoreCase("projection") ||
+        keyword.equalsIgnoreCase("proj"))
+      {
+        proj = value;
+      }
+
+      // graphics mode settings
+      else if (keyword.equalsIgnoreCase("graphics mode") ||
+        keyword.equalsIgnoreCase("graphics_mode") ||
+        keyword.equalsIgnoreCase("graphicsmode") ||
+        keyword.equalsIgnoreCase("graphics") ||
+        keyword.equalsIgnoreCase("mode"))
+      {
+        mode = value;
+      }
+
+      // color table
+      else if (keyword.equalsIgnoreCase("color") ||
+        keyword.equalsIgnoreCase("color table") ||
+        keyword.equalsIgnoreCase("color_table") ||
+        keyword.equalsIgnoreCase("colortable"))
+      {
+        color.add(value);
+      }
+
+      // contour data
+      else if (keyword.equalsIgnoreCase("contour") ||
+        keyword.equalsIgnoreCase("contours") ||
+        keyword.equalsIgnoreCase("iso contour") ||
+        keyword.equalsIgnoreCase("iso_contour") ||
+        keyword.equalsIgnoreCase("isocontour") ||
+        keyword.equalsIgnoreCase("iso contours") ||
+        keyword.equalsIgnoreCase("iso_contours") ||
+        keyword.equalsIgnoreCase("isocontours"))
+      {
+        contour.add(value);
+      }
+
+      // range
+      else if (keyword.equalsIgnoreCase("range") ||
+        keyword.equalsIgnoreCase("select range") ||
+        keyword.equalsIgnoreCase("select_range") ||
+        keyword.equalsIgnoreCase("selectrange"))
+      {
+        range.add(value);
+      }
+
+      // animation
+      else if (keyword.equalsIgnoreCase("anim") ||
+        keyword.equalsIgnoreCase("animation"))
+      {
+        anim.add(value);
+      }
+
+      // select value
+      else if (keyword.equalsIgnoreCase("value") ||
+        keyword.equalsIgnoreCase("select value") ||
+        keyword.equalsIgnoreCase("select_value") ||
+        keyword.equalsIgnoreCase("selectvalue"))
+      {
+        selectVal.add(value);
+      }
+
+      // unknown keyword
+      else {
+        warn("keyword " + keyword + " is unknown and will be ignored");
+      }
+    }
+
+    if (preserveMaps) {
+      // detect which maps are the same and set appropriate ranges
+      maps = DataUtility.convertStringToMaps(mapString, getData(), true);
+      if (maps != null) {
+        int lmin = mapMins == null ? -1 : mapMins.size();
+        int lmax = mapMaxs == null ? -1 : mapMaxs.size();
+        int cmin = 0, cmax = 0;
+        Vector mapVector = VDisplay.getMapVector();
+        for (int j=0; j<maps.length; j++) {
+          if (maps[j] != null) {
+            // detect whether map needs a range
+            boolean scale = maps[j].getScale(
+              new double[2], new double[2], new double[2]);
+            if (scale && cmin < lmin && cmax < lmax) {
+              // find map in current display vector
+              int mapIndex = mapVector.indexOf(maps[j]);
+              if (mapIndex >= 0) {
+                // set map's minimum and maximum range values
+                ScalarMap map = (ScalarMap) mapVector.elementAt(mapIndex);
+                map.setRange(
+                  ((Double) mapMins.elementAt(cmin++)).doubleValue(),
+                  ((Double) mapMaxs.elementAt(cmax++)).doubleValue());
+              }
+              else {
+                // skip current minimum and maximum range values
+                cmin++;
+                cmax++;
+              }
+            }
+          }
+        }
+      }
+    }
+    else {
+      // clear old stuff from cell
+      clearCell();
+
+      // set up dimension
+      setDimension(dim);
+
+      // set up data objects
+      int ilen = ids.size();
+      int slen = sources.size();
+      int tlen = types.size();
+      if (ilen != slen || ilen != tlen) {
+        warn("some data object entries are corrupt and will be ignored");
+      }
+      int len =
+        ilen < slen && ilen < tlen ? ilen : (slen < tlen ? slen : tlen);
+      setDisplayEnabled(false);
+      for (int i=0; i<len; i++) {
+        int id = ((Integer) ids.elementAt(i)).intValue();
+        String source = (String) sources.elementAt(i);
+        int type = ((Integer) types.elementAt(i)).intValue();
+        addDataSource(id, source, type, true);
+      }
+      waitForData();
+
+      // set up map ranges; then set maps
+      maps = DataUtility.convertStringToMaps(mapString, getData(), true);
+      if (maps != null) {
+        int lmin = mapMins == null ? -1 : mapMins.size();
+        int lmax = mapMaxs == null ? -1 : mapMaxs.size();
+        int cmin = 0, cmax = 0;
+        for (int j=0; j<maps.length; j++) {
+          if (maps[j] != null) {
+            // set map's minimum and maximum range value, if applicable
+            ScalarMap sm = maps[j];
+            boolean scale = sm.getScale(
+              new double[2], new double[2], new double[2]);
+            if (scale && cmin < lmin && cmax < lmax) {
+              sm.setRange(((Double) mapMins.elementAt(cmin++)).doubleValue(),
+                ((Double) mapMaxs.elementAt(cmax++)).doubleValue());
+            }
+          }
+        }
+        setMaps(maps);
+      }
+      setDisplayEnabled(true);
+    }
+
+    // set up projection control
+    if (proj != null) {
+      ProjectionControl pc = VDisplay.getProjectionControl();
+      if (pc != null) pc.setSaveString(proj);
+      else if (!preserveMaps) warn("display has no ProjectionControl; " +
+        "the provided projection matrix will be ignored");
+    }
+
+    // set up graphics mode control
+    if (mode != null) {
+      GraphicsModeControl gmc = VDisplay.getGraphicsModeControl();
+      if (gmc != null) {
+        try {
+          gmc.setSaveString(mode);
+        }
+        catch (VisADException exc) {
+          if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+        }
+      }
+      else if (!preserveMaps) warn("display has no GraphicsModeControl; " +
+        "the provided graphics mode settings will be ignored");
+    }
+
+    // set up color control(s)
+    int len = color.size();
+    if (len > 0) {
+      for (int i=0; i<len; i++) {
+        String s = (String) color.elementAt(i);
+        ColorControl cc = (ColorControl)
+          VDisplay.getControl(ColorControl.class, i);
+        if (cc != null) cc.setSaveString(s);
+        else if (!preserveMaps) warn("display has no ColorControl #" +
+          (i + 1) + "; " + "the provided color table will be ignored");
+      }
+    }
+
+    // set up contour control(s)
+    len = contour.size();
+    if (len > 0) {
+      for (int i=0; i<len; i++) {
+        String s = (String) contour.elementAt(i);
+        ContourControl cc = (ContourControl)
+          VDisplay.getControl(ContourControl.class, i);
+        if (cc != null) cc.setSaveString(s);
+        else if (!preserveMaps) warn("display has no ContourControl #" +
+          (i + 1) + "; " + "the provided contour settings will be ignored");
+      }
+    }
+
+    // set up range control(s)
+    len = range.size();
+    if (len > 0) {
+      for (int i=0; i<len; i++) {
+        String s = (String) range.elementAt(i);
+        RangeControl rc = (RangeControl)
+          VDisplay.getControl(RangeControl.class, i);
+        if (rc != null) rc.setSaveString(s);
+        else if (!preserveMaps) warn("display has no RangeControl #" +
+          (i + 1) + "; " + "the provided range will be ignored");
+      }
+    }
+
+    // set up animation control(s)
+    len = anim.size();
+    if (len > 0) {
+      for (int i=0; i<len; i++) {
+        String s = (String) anim.elementAt(i);
+        AnimationControl ac = (AnimationControl)
+          VDisplay.getControl(AnimationControl.class, i);
+        if (ac != null) {
+          // Note: There is a race condition that prevents the AnimationControl
+          // from correctly setting the current step and step delays.
+          // The AnimationControl gets its parameters reset back to default
+          // values when its ScalarMap's range is set above.
+          // The one-second delay here should solve the problem in most cases.
+          try {
+            Thread.sleep(1000);
+          }
+          catch (InterruptedException exc) {
+            if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+          }
+          ac.setSaveString(s);
+        }
+        else if (!preserveMaps) warn("display has no AnimationControl #" +
+          (i + 1) + "; " + "the provided animation settings will be ignored");
+      }
+    }
+
+    // set up value control(s)
+    len = selectVal.size();
+    if (len > 0) {
+      for (int i=0; i<len; i++) {
+        String s = (String) selectVal.elementAt(i);
+        ValueControl vc = (ValueControl)
+          VDisplay.getControl(ValueControl.class, i);
+        if (vc != null) vc.setSaveString(s);
+        else if (!preserveMaps) warn("display has no ValueControl #" +
+          (i + 1) + "; " + "the provided value will be ignored");
+      }
+    }
+  }
+
+
+  // --- UTILITY ---
+
+  /**
+   * Adds a variable to this cell's formula manager.
+   */
+  public void addVar(String name, ThingReference tr) throws VisADException {
+    fm.createVar(name, tr);
+  }
+
+  /**
+   * Prints a warning message.
+   */
+  private void warn(String s) {
+    System.err.println(Name + ": Warning: " + s);
+  }
+
+
+  // --- GUI MANAGEMENT ---
+
+  /**
+   * Prevents simultaneous GUI manipulation.
+   */
+  protected Object Lock = new Object();
+
+  /**
+   * Associated VisAD Display component.
+   */
+  protected Component VDPanel;
+
+  /**
+   * Global errors currently being displayed in this cell, if any.
+   */
+  protected String[] Errors;
+
+  /**
+   * Whether a valid VisAD display currently exists.
+   */
+  protected boolean HasDisplay = false;
+
+  /**
+   * Whether display updates are enabled.
+   */
+  protected boolean DisplayEnabled = true;
+
+  /**
+   * A panel that displays the words "Please wait."
+   */
+  private JPanel WaitPanel = null;
+
+
+  // -- GUI refresh --
+
+  /**
+   * Refreshes this cell's display.
+   */
+  void refresh() {
+    validate();
+    repaint();
+  }
+
+  // -- Set errors --
+
+  /**
+   * Displays global errors in this cell, notifying
+   * linked cells if notify flag is set.
+   */
+  protected void setErrors(String[] errors, boolean notify) {
+    if (Util.arraysEqual(Errors, errors)) return;
+    Errors = errors;
+    updateDisplay();
+    if (notify) {
+      try {
+        sendMessage(SET_ERRORS, null, stringsToTuple(errors));
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+    }
+  }
+
+  // -- Init & toggle display panel --
+
+  /**
+   * Initializes this cell's display panel.
+   */
+  private void initDisplayPanel() {
+    if (IsSlave) VDPanel = RemoteVSlave.getComponent();
+    else VDPanel = VDisplay.getComponent();
+  }
+
+  /**
+   * Display the data for this cell, or all relevant errors if there are any.
+   */
+  void updateDisplay(boolean hasDisplay) {
+    HasDisplay = hasDisplay;
+    updateDisplay();
+  }
+
+  /**
+   * Display the data for this cell if hasDisplay flag is set.
+   */
+  void updateDisplay() {
+    if (!DisplayEnabled) return;
+
+    if (WaitPanel == null) {
+      // initialize "Please wait" panel
+      WaitPanel = new JPanel();
+      WaitPanel.setBackground(Color.black);
+      WaitPanel.setLayout(new BoxLayout(WaitPanel, BoxLayout.X_AXIS));
+      WaitPanel.add(Box.createHorizontalGlue());
+      WaitPanel.add(new JLabel("Please wait..."));
+      WaitPanel.add(Box.createHorizontalGlue());
+    }
+
+    // compile list of errors
+    final Vector e = new Vector();
+    if (Errors == null) {
+      // no global errors; compile list of data-related errors
+      synchronized (CellData) {
+        int len = CellData.size();
+        for (int i=0; i<len; i++) {
+          SSCellData cellData = (SSCellData) CellData.elementAt(i);
+          String varName = cellData.getVariableName();
+          String[] errors = cellData.getErrors();
+          if (errors != null) {
+            for (int j=0; j<errors.length; j++) {
+              e.add(varName + ": " + errors[j]);
+            }
+          }
+        }
+      }
+    }
+    else {
+      // global errors exist; they take precedence
+      for (int i=0; i<Errors.length; i++) e.add(Errors[i]);
+    }
+
+    // set up error canvas
+    final int len = e.size();
+    JComponent errorCanvas;
+    if (len == 0) errorCanvas = null;
+    else {
+      errorCanvas = new JComponent() {
+        public void paint(Graphics g) {
+          g.setColor(Color.white);
+          String s = (len == 1 ? "An error" : "Errors") +
+            " occurred while computing this cell:";
+          g.drawString(s, 8, 20);
+          for (int i=0; i<len; i++) {
+            s = (String) e.elementAt(i);
+            g.drawString(s, 8, 15*i + 50);
+          }
+        }
+      };
+    }
+
+    final JComponent ec = errorCanvas;
+    Util.invoke(true, DEBUG, new Runnable() {
+      public void run() {
+        // determine whether VDPanel is already present onscreen
+        Component[] c = getComponents();
+        boolean hasPanel = c.length > 0 && c[0] == VDPanel;
+
+        // redraw cell
+        if (Loading > 0) {
+          removeAll();
+          add(WaitPanel);
+        }
+        else if (ec != null) {
+          removeAll();
+          add(ec);
+        }
+        else if (HasDisplay) {
+          if (!hasPanel) {
+            // no need to re-add VDPanel if already present
+            removeAll();
+            add(VDPanel);
+          }
+        }
+        else removeAll();
+        refresh();
+      }
+    });
+  }
+
+  /**
+   * Enables or disables display updates.
+   */
+  private void setDisplayEnabled(boolean value) {
+    if (value == DisplayEnabled) return;
+    DisplayEnabled = value;
+    if (DisplayEnabled) updateDisplay();
+  }
+
+  // -- Toggle waiting mode --
+
+  /**
+   * Increments the loading counter.
+   */
+  private void beginWait(boolean update) {
+    Loading++;
+    if (update) updateDisplay();
+  }
+
+  /**
+   * Decrements the loading counter.
+   */
+  private void endWait(boolean update) {
+    Loading--;
+    if (update) updateDisplay();
+  }
+
+
+  // --- EVENT HANDLING ---
+
+  /**
+   * List of SSCellListeners.
+   */
+  protected Vector SListen = new Vector();
+
+  /**
+   * List of DisplayListeners.
+   */
+  protected Vector DListen = new Vector();
+
+
+  // -- Add & remove DisplayListeners --
+
+  /**
+   * Adds a DisplayListener.
+   */
+  public void addDisplayListener(DisplayListener d) {
+    synchronized (DListen) {
+      if (!DListen.contains(d)) {
+        if (IsSlave) RemoteVSlave.addDisplayListener(d);
+        else VDisplay.addDisplayListener(d);
+        DListen.add(d);
+      }
+    }
+  }
+
+  /**
+   * Removes a DisplayListener from this cell.
+   */
+  public void removeDisplayListener(DisplayListener d) {
+    synchronized (DListen) {
+      if (DListen.contains(d)) {
+        if (IsSlave) RemoteVSlave.removeDisplayListener(d);
+        else VDisplay.removeDisplayListener(d);
+        DListen.remove(d);
+      }
+    }
+  }
+
+  /**
+   * Re-attaches all display listeners after they have been detached.
+   */
+  private void attachListeners() {
+    int len = DListen.size();
+    if (IsSlave) {
+      for (int i=0; i<len; i++) {
+        DisplayListener l = (DisplayListener) DListen.elementAt(i);
+        RemoteVSlave.addDisplayListener(l);
+      }
+    }
+    else {
+      for (int i=0; i<len; i++) {
+        DisplayListener l = (DisplayListener) DListen.elementAt(i);
+        VDisplay.addDisplayListener(l);
+      }
+    }
+  }
+
+  /**
+   * Temporarily detaches all display listeners.
+   */
+  private void detachListeners() {
+    int len = DListen.size();
+    if (IsSlave) {
+      for (int i=0; i<len; i++) {
+        DisplayListener l = (DisplayListener) DListen.elementAt(i);
+        RemoteVSlave.removeDisplayListener(l);
+      }
+    }
+    else {
+      for (int i=0; i<len; i++) {
+        DisplayListener l = (DisplayListener) DListen.elementAt(i);
+        VDisplay.removeDisplayListener(l);
+      }
+    }
+  }
+
+  // -- Add, remove & notify SSCellListeners --
+
+  /**
+   * Adds an SSCellListener.
+   */
+  public void addSSCellListener(SSCellListener l) {
+    synchronized (SListen) {
+      if (!SListen.contains(l)) SListen.add(l);
+    }
+  }
+
+  /**
+   * Removes an SSCellListener.
+   */
+  public void removeSSCellListener(SSCellListener l) {
+    synchronized (SListen) {
+      SListen.remove(l);
+    }
+  }
+
+  /**
+   * Removes all SSCellListeners.
+   */
+  public void removeAllSSCellListeners() {
+    synchronized (SListen) {
+      SListen.removeAllElements();
+    }
+  }
+
+  /**
+   * Informs all SSCellListeners of cell change.
+   */
+  void notifySSCellListeners(int changeType) {
+    notifySSCellListeners(changeType, null);
+  }
+
+  /**
+   * Informs all SSCellListeners of a cell change.
+   */
+  void notifySSCellListeners(int changeType, String varName) {
+    SSCellChangeEvent e = new SSCellChangeEvent(this, changeType, varName);
+    int len;
+    SSCellListener[] l;
+    synchronized (SListen) {
+      len = SListen.size();
+      l = new SSCellListener[len];
+      for (int i=0; i<len; i++) l[i] = (SSCellListener) SListen.elementAt(i);
+    }
+    for (int i=0; i<len; i++) l[i].ssCellChanged(e);
+  }
+
+
+  // --- ACCESSORS ---
+
+  /**
+   * Gets this cell's name.
+   */
+  public String getName() {
+    return Name;
+  }
+
+  /**
+   * Gets whether this cell is a cloned display cell.
+   */
+  public boolean isRemote() {
+    return IsRemote;
+  }
+
+  /**
+   * Gets whether this cell is a slaved display cell.
+   */
+  public boolean isSlave() {
+    return IsSlave;
+  }
+
+  /**
+   * Gets the id number this cell uses for remote collaboration.
+   */
+  public int getRemoteId() {
+    return CollabID;
+  }
+
+  /**
+   * Gets this cell's formula manager.
+   */
+  public FormulaManager getFormulaManager() {
+    return fm;
+  }
+
+  /**
+   * Gets this cell's VisAD Display.
+   */
+  public DisplayImpl getDisplay() {
+    return VDisplay;
+  }
+
+  /**
+   * Gets this cell's VisAD RemoteDisplay.
+   */
+  public RemoteDisplay getRemoteDisplay() {
+    return RemoteVDisplay;
+  }
+
+  /**
+   * Gets this cell's mappings.
+   */
+  public ScalarMap[] getMaps() {
+    Vector mapVector = null;
+    if (IsSlave) {
+      // SLAVE: get mappings from remote display
+      try {
+        mapVector = RemoteVDisplay.getMapVector();
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+    }
+    else if (VDisplay != null) {
+      // get mappings from local display
+      mapVector = VDisplay.getMapVector();
+    }
+
+    int len = (mapVector == null ? 0 : mapVector.size());
+    ScalarMap[] maps = (len > 0 ? new ScalarMap[len] : null);
+    for (int i=0; i<len; i++) maps[i] = (ScalarMap) mapVector.elementAt(i);
+    return maps;
+  }
+
+  /**
+   * Gets this cell's dimension.
+   * @return  Dimension type. Valid types are:
+   *          <UL>
+   *          <LI>BasicSSCell.JAVA3D_3D
+   *          <LI>BasicSSCell.JAVA2D_2D
+   *          <LI>BasicSSCell.JAVA3D_2D
+   *          </UL>
+   */
+  public int getDimension() {
+    return Dim;
+  }
+
+  /**
+   * Gets this cell's Data object with the specified variable name.
+   */
+  public Data getData(String varName) {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = getCellDataByName(varName);
+    }
+    return cellData == null ? null : cellData.getData();
+  }
+
+  /**
+   * Gets this cell's Data objects.
+   */
+  public Data[] getData() {
+    synchronized (CellData) {
+      int len = CellData.size();
+      Data[] data = new Data[len];
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        data[i] = cellData.getData();
+      }
+      return data;
+    }
+  }
+
+  /**
+   * Gets this cell's DataReference with the specified variable name.
+   */
+  public DataReference getReference(String varName) {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = getCellDataByName(varName);
+    }
+    return cellData == null ? null : cellData.getReference();
+  }
+
+  /**
+   * Gets this cell's DataReferences.
+   */
+  public DataReferenceImpl[] getReferences() {
+    synchronized (CellData) {
+      int len = CellData.size();
+      DataReferenceImpl[] refs = new DataReferenceImpl[len];
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        refs[i] = cellData.getReference();
+      }
+      return refs;
+    }
+  }
+
+  /**
+   * Gets this cell's remote DataReference for data
+   * with the specified variable name.
+   */
+  public RemoteDataReference getRemoteReference(String varName) {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = getCellDataByName(varName);
+    }
+    return cellData == null ? null : cellData.getRemoteReference();
+  }
+
+  /**
+   * Gets this cell's remote DataReferences.
+   */
+  public RemoteDataReference[] getRemoteReferences() {
+    synchronized (CellData) {
+      int len = CellData.size();
+      RemoteDataReference[] remoteRefs = new RemoteDataReference[len];
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        remoteRefs[i] = cellData.getRemoteReference();
+      }
+      return remoteRefs;
+    }
+  }
+
+  /**
+   * Gets this cell's data source type for data
+   * with the specified variable name.
+   */
+  public int getDataSourceType(String varName) {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = getCellDataByName(varName);
+    }
+    return cellData == null ? UNKNOWN_SOURCE : cellData.getSourceType();
+  }
+
+  /**
+   * Gets this cell's data source types.
+   */
+  public int[] getDataSourceTypes() {
+    synchronized (CellData) {
+      int len = CellData.size();
+      int[] types = new int[len];
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        types[i] = cellData.getSourceType();
+      }
+      return types;
+    }
+  }
+
+  /**
+   * Gets this cell's data source string for data
+   * with the specified variable name.
+   */
+  public String getDataSource(String varName) {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = getCellDataByName(varName);
+    }
+    return cellData == null ? null : cellData.getSource();
+  }
+
+  /**
+   * Gets this cell's data source strings.
+   */
+  public String[] getDataSources() {
+    synchronized (CellData) {
+      int len = CellData.size();
+      String[] sources = new String[len];
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        sources[i] = cellData.getSource();
+      }
+      return sources;
+    }
+  }
+
+  /**
+   * Gets the variable name of this cell's first Data object.
+   */
+  public String getFirstVariableName() {
+    String varName = null;
+    synchronized (CellData) {
+      if (CellData.size() > 0) {
+        SSCellData cellData = (SSCellData) CellData.firstElement();
+        varName = cellData.getVariableName();
+      }
+    }
+    return varName;
+  }
+
+  /**
+   * Gets the variable name of this cell's last Data object.
+   */
+  public String getLastVariableName() {
+    String varName = null;
+    synchronized (CellData) {
+      if (CellData.size() > 0) {
+        SSCellData cellData = (SSCellData) CellData.lastElement();
+        varName = cellData.getVariableName();
+      }
+    }
+    return varName;
+  }
+
+  /**
+   * Gets the variable names of this cell's Data objects.
+   */
+  public String[] getVariableNames() {
+    synchronized (CellData) {
+      int len = CellData.size();
+      String[] varNames = new String[len];
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        varNames[i] = cellData.getVariableName();
+      }
+      return varNames;
+    }
+  }
+
+  /**
+   * Gets the number of Data object this cell has.
+   */
+  public int getDataCount() {
+    return CellData.size();
+  }
+
+  /**
+   * Whether this cell has any data.
+   */
+  public boolean hasData() {
+    return CellData.size() > 0;
+  }
+
+  /**
+   * Whether this cell has a valid display on-screen.
+   */
+  public boolean hasDisplay() {
+    return HasDisplay;
+  }
+
+  /**
+   * Whether this cell has any mappings.
+   */
+  public boolean hasMappings() {
+    if (IsRemote) {
+      // CLIENT: check mappings from remote display
+      Vector v = null;
+      try {
+        v = RemoteVDisplay.getMapVector();
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+      }
+      return v != null && !v.isEmpty();
+    }
+    else return HasMappings;
+  }
+
+  /**
+   * Whether other cells are dependent on this cell's Data object
+   * with the specified variable name.
+   */
+  public boolean othersDepend(String varName) {
+    SSCellData cellData;
+    synchronized (CellData) {
+      cellData = getCellDataByName(varName);
+    }
+    return cellData.othersDepend();
+  }
+
+  /**
+   * Whether other cells are dependent on any of this cell's Data objects.
+   */
+  public boolean othersDepend() {
+    synchronized (CellData) {
+      int len = CellData.size();
+      for (int i=0; i<len; i++) {
+        SSCellData cellData = (SSCellData) CellData.elementAt(i);
+        if (cellData.othersDepend()) return true;
+      }
+      return false;
+    }
+  }
+
+
+  // --- DEPRECATED METHODS ---
+
+  /**
+   * @deprecated Use addVar(String, ThingReference) instead.
+   */
+  public static void createVar(String name, ThingReference tr)
+    throws VisADException
+  {
+    defaultFM.createVar(name, tr);
+  }
+
+  /**
+   * @deprecated Use addSSCellListener(SSCellListener) instead.
+   */
+  public void addSSCellChangeListener(SSCellListener l) {
+    addSSCellListener(l);
+  }
+
+  /**
+   * @deprecated Use removeSSCellListener(SSCellListener) instead.
+   */
+  public void removeListener(SSCellListener l) {
+    removeSSCellListener(l);
+  }
+
+  /**
+   * @deprecated Use removeAllSSCellListeners() instead.
+   */
+  public void removeAllListeners() {
+    removeAllSSCellListeners();
+  }
+
+  /**
+   * @deprecated Use setSaveString(String) instead.
+   */
+  public void setSSCellString(String save)
+    throws VisADException, RemoteException
+  {
+    setSaveString(save);
+  }
+
+  /**
+   * @deprecated Use getSaveString() instead.
+   */
+  public String getSSCellString() {
+    return getSaveString();
+  }
+
+  /**
+   * @deprecated Use saveData(String, Form) instead.
+   */
+  public void saveData(File f, boolean netcdf)
+    throws BadFormException, IOException, VisADException, RemoteException
+  {
+    Form form;
+    if (netcdf) form = new visad.data.netcdf.Plain();
+    else form = new visad.data.visad.VisADForm();
+    saveData(getFirstVariableName(), f.getPath(), form);
+  }
+
+  /**
+   * @deprecated Use saveData(String, Form) instead.
+   */
+  public void saveData(File f, Form form)
+    throws BadFormException, IOException, VisADException, RemoteException
+  {
+    saveData(getFirstVariableName(), f.getPath(), form);
+  }
+
+  /**
+   * @deprecated Use addData(Data) instead.
+   */
+  public void setData(Data data) throws VisADException, RemoteException {
+    removeAllReferences();
+    addData(data);
+  }
+
+  /**
+   * @deprecated Use addDataSource(String, FORMULA_SOURCE) instead.
+   */
+  public void setFormula(String f)
+    throws VisADException, RemoteException
+  {
+    removeAllReferences();
+    addDataSource(f, FORMULA_SOURCE);
+  }
+
+  /**
+   * @deprecated Use waitForData(String) instead.
+   */
+  public void waitForFormula() throws VisADException, RemoteException {
+    waitForData();
+  }
+
+  /**
+   * @deprecated Use getReference(String) instead.
+   */
+  public DataReferenceImpl getDataRef() {
+    return (getDataCount() > 0 ? (DataReferenceImpl)
+      getReference(getFirstVariableName()) : null);
+  }
+
+  /**
+   * @deprecated Use getRemoteReference(String) instead.
+   */
+  public RemoteDataReferenceImpl getRemoteDataRef() {
+    return (getDataCount() > 0 ?  (RemoteDataReferenceImpl)
+      getRemoteReference(getFirstVariableName()) : null);
+  }
+
+  /**
+   * @deprecated Use getDataSource(String) instead.
+   */
+  public URL getFileURL() {
+    URL url = null;
+    String varName = getFirstVariableName();
+    if (getDataCount() > 0 && getDataSourceType(varName) == URL_SOURCE) {
+      try {
+        url = new URL(getDataSource(varName));
+      }
+      catch (MalformedURLException exc) {
+        if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+      }
+    }
+    return url;
+  }
+
+  /**
+   * @deprecated Use getDataSource(String) instead.
+   */
+  public String getFilename() {
+    String filename = "";
+    String varName = getFirstVariableName();
+    if (getDataCount() > 0 && getDataSourceType(varName) == URL_SOURCE) {
+      filename = getDataSource(varName);
+    }
+    return filename;
+  }
+
+  /**
+   * @deprecated Use getDataSource(String) instead.
+   */
+  public String getRMIAddress() {
+    String rmi = null;
+    String varName = getFirstVariableName();
+    if (getDataCount() > 0  && getDataSourceType(varName) == RMI_SOURCE) {
+      rmi = getDataSource(varName);
+    }
+    return rmi;
+  }
+
+  /**
+   * @deprecated Use getDataSource(String) instead.
+   */
+  public String getFormula() {
+    String formula = "";
+    String varName = getFirstVariableName();
+    if (getDataCount() > 0 && getDataSourceType(varName) == FORMULA_SOURCE) {
+      formula = getDataSource(varName);
+    }
+    return formula;
+  }
+
+  /**
+   * @deprecated Use addDataSource(String, URL_SOURCE) instead.
+   */
+  public void loadData(URL u) throws VisADException, RemoteException {
+    addDataSource(u.toString(), URL_SOURCE);
+  }
+
+  /**
+   * @deprecated Use addDataSource(String, URL_SOURCE) instead.
+   */
+  public void loadData(String s)
+    throws VisADException, RemoteException
+  {
+    addDataSource(s, URL_SOURCE);
+  }
+
+  /**
+   * @deprecated Use addDataSource(String, RMI_SOURCE) instead.
+   */
+  public void loadRMI(String s)
+    throws VisADException, RemoteException
+  {
+    addDataSource(s, RMI_SOURCE);
+  }
+
+  /**
+   * @deprecated Use setDimension(int) instead.
+   */
+  public void setDimension(boolean twoD, boolean java2d)
+    throws VisADException, RemoteException
+  {
+    int dim;
+    if (!twoD && java2d) return;
+    if (!twoD && !java2d) dim = JAVA3D_3D;
+    else if (twoD && java2d) dim = JAVA2D_2D;
+    else dim = JAVA3D_2D; // twoD && !java2d
+    setDimension(dim);
+  }
+
+  /**
+   * @deprecated Use getDataSourceType(String) instead.
+   */
+  public boolean hasFormula() {
+    return (getDataCount() > 0 &&
+      getDataSourceType(getFirstVariableName()) == FORMULA_SOURCE);
+  }
+
+  /**
+   * @deprecated Use getReference(String) instead.
+   */
+  public DataReference getReference() {
+    return (getDataCount() > 0 ? getReference(getFirstVariableName()) : null);
+  }
+
+  /**
+   * @deprecated Use visad.DataUtility.stringsToTuple(String[]) instead.
+   */
+  public static Tuple stringsToTuple(String[] s) {
+    return DataUtility.stringsToTuple(s, DEBUG);
+  }
+
+  /**
+   * @deprecated Use visad.DataUtility.tupleToStrings(Tuple) instead.
+   */
+  public static String[] tupleToStrings(Tuple t) {
+    return DataUtility.tupleToStrings(t, DEBUG);
+  }
+
+  /**
+   * @deprecated Use visad.DataUtility.makeLocal(data) instead.
+   */
+  public static DataImpl makeLocal(Data data) {
+    return DataUtility.makeLocal(data, DEBUG && DEBUG_LEVEL >= 3);
+  }
+
+  /**
+   * @deprecated Use visad.Util.arraysEqual(Object[], Object[]) instead.
+   */
+  public static boolean arraysEqual(Object[] o1, Object[] o2) {
+    return Util.arraysEqual(o1, o2);
+  }
+
+  /**
+   * @deprecated Use visad.Util.invoke(boolean, Runnable) instead.
+   */
+  public static void invoke(boolean wait, Runnable r) {
+    Util.invoke(wait, DEBUG, r);
+  }
+
+}
diff --git a/visad/ss/FancySSCell.java b/visad/ss/FancySSCell.java
new file mode 100644
index 0000000..4948c0f
--- /dev/null
+++ b/visad/ss/FancySSCell.java
@@ -0,0 +1,862 @@
+//
+// FancySSCell.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+import java.awt.*;
+import java.io.*;
+import java.net.*;
+import java.rmi.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import visad.*;
+import visad.data.*;
+import visad.formula.FormulaManager;
+import visad.util.*;
+
+/**
+ * FancySSCell is an extension of BasicSSCell with extra options, such
+ * as a file loader dialog and a dialog to set up ScalarMaps.
+ * It provides an example of GUI extensions to BasicSSCell.
+ */
+public class FancySSCell extends BasicSSCell implements SSCellListener {
+
+  // --- CONSTANTS ---
+
+  /**
+   * Dark red.
+   */
+  public static final Color DARK_RED = new Color(0.5f, 0f, 0f);
+
+  /**
+   * Dark green.
+   */
+  public static final Color DARK_GREEN = new Color(0f, 0.5f, 0f);
+
+  /**
+   * Dark blue.
+   */
+  public static final Color DARK_BLUE = new Color(0f, 0f, 0.5f);
+
+  /**
+   * Dark yellow.
+   */
+  public static final Color DARK_YELLOW = new Color(0.5f, 0.5f, 0f);
+
+  /**
+   * Dark purple.
+   */
+  public static final Color DARK_PURPLE = new Color(0.5f, 0f, 0.5f);
+
+  /**
+   * Dark cyan.
+   */
+  public static final Color DARK_CYAN = new Color(0f, 0.5f, 0.5f);
+
+  /**
+   * Border for cell with no data.
+   */
+  public static final Border B_EMPTY = new LineBorder(Color.gray, 3);
+
+  /**
+   * Border for selected cell.
+   */
+  public static final Border B_HIGHLIGHT = new LineBorder(Color.yellow, 3);
+
+  /**
+   * Border for cell with data from an unknown source.
+   */
+  public static final Border B_UNKNOWN = new LineBorder(DARK_PURPLE, 3);
+
+  /**
+   * Border for cell with data set directly.
+   */
+  public static final Border B_DIRECT = new LineBorder(DARK_CYAN, 3);
+
+  /**
+   * Border for cell with file or URL.
+   */
+  public static final Border B_URL = new LineBorder(DARK_GREEN, 3);
+
+  /**
+   * Border for cell with formula.
+   */
+  public static final Border B_FORMULA = new LineBorder(DARK_RED, 3);
+
+  /**
+   * Border for cell with RMI address.
+   */
+  public static final Border B_RMI = new LineBorder(DARK_BLUE, 3);
+
+  /**
+   * Border for cell with data from a remote source.
+   */
+  public static final Border B_REMOTE = new LineBorder(DARK_YELLOW, 3);
+
+  /**
+   * Border for cell with multiple data objects.
+   */
+  public static final Border B_MULTI = new CompoundBorder(
+    new CompoundBorder(new LineBorder(DARK_RED), new LineBorder(DARK_GREEN)),
+    new LineBorder(DARK_BLUE));
+
+
+  // --- FIELDS ---
+
+  /**
+   * File chooser for loading and saving data. This variable is static so
+   * that the directory is remembered between each load or save command.
+   */
+  protected static JFileChooser FileBox = Util.getVisADFileChooser();
+
+  /**
+   * Parent frame.
+   */
+  protected Frame Parent;
+
+  /**
+   * Associated JFrame, for use with VisAD Controls.
+   */
+  protected JFrame WidgetFrame;
+
+  /**
+   * Whether this cell is selected.
+   */
+  protected boolean Selected = false;
+
+  /**
+   * Whether this cell should auto-switch to 3-D.
+   */
+  protected boolean AutoSwitch = true;
+
+  /**
+   * Whether this cell should auto-detect mappings for data.
+   */
+  protected boolean AutoDetect = true;
+
+  /**
+   * Whether this cell should auto-display its widget frame.
+   */
+  protected boolean AutoShowControls = true;
+
+  /**
+   * Lock object for mapping auto-detection notification.
+   */
+  private Object MapLock = new Object();
+
+  /**
+   * Counter for mapping auto-detection notification.
+   */
+  private int MapCount = 0;
+
+
+  // --- CONSTRUCTORS ---
+
+  /**
+   * Constructs a new FancySSCell with the given name.
+   */
+  public FancySSCell(String name) throws VisADException, RemoteException {
+    this(name, null, null, false, null, null);
+  }
+
+  /**
+   * Constructs a new FancySSCell with the given name and parent Frame.
+   */
+  public FancySSCell(String name, Frame parent)
+    throws VisADException, RemoteException
+  {
+    this(name, null, null, false, null, parent);
+  }
+
+  /**
+   * Constructs a new FancySSCell with the given name, formula manager,
+   * and parent Frame.
+   */
+  public FancySSCell(String name, FormulaManager fman, Frame parent)
+    throws VisADException, RemoteException
+  {
+    this(name, fman, null, false, null, parent);
+  }
+
+  /**
+   * Constructs a new FancySSCell with the given name, remote server,
+   * and parent Frame.
+   */
+  public FancySSCell(String name, RemoteServer rs, Frame parent)
+    throws VisADException, RemoteException
+  {
+    this(name, null, rs, false, null, parent);
+  }
+
+  /**
+   * Constructs a new FancySSCell with the given name, save string, and
+   * parent Frame.
+   */
+  public FancySSCell(String name, String save, Frame parent)
+    throws VisADException, RemoteException
+  {
+    this(name, null, null, false, save, parent);
+  }
+
+  /**
+   * Constructs a new FancySSCell with the given name, formula manager,
+   * remote server, save string, and parent Frame.
+   */
+  public FancySSCell(String name, FormulaManager fman, RemoteServer rs,
+    String save, Frame parent) throws VisADException, RemoteException
+  {
+    this(name, fman, rs, false, save, parent);
+  }
+
+  /**
+   * Constructs a new, possibly slaved, FancySSCell with the given name,
+   * formula manager, remote server, save string, and parent Frame.
+   */
+  public FancySSCell(String name, FormulaManager fman, RemoteServer rs,
+    boolean slave, String save, Frame parent) throws VisADException,
+    RemoteException
+  {
+    super(name, fman, rs, slave, save);
+    Parent = parent;
+    WidgetFrame = new JFrame("Controls (" + Name + ")");
+    Util.invoke(false, DEBUG, new Runnable() {
+      public void run() {
+        setHighlighted(false);
+      }
+    });
+    addSSCellListener(this);
+  }
+
+
+  // --- DATA MANAGEMENT ---
+
+  /**
+   * Removes the Data object corresponding to the
+   * given variable name from this cell.
+   */
+  public void removeData(String varName)
+    throws VisADException, RemoteException
+  {
+    super.removeData(varName);
+    if (CellData.size() == 0) clearWidgetFrame();
+  }
+
+  /**
+   * Imports a data object from the given source of unknown type,
+   * in a separate thread.
+   */
+  public void loadDataSource(String source) {
+    loadDataSource(source, UNKNOWN_SOURCE);
+  }
+
+  /**
+   * Imports a data object from the given source of the specified type,
+   * in a separate thread.
+   */
+  public void loadDataSource(String source, int type) {
+    final String fsource = source;
+    final int ftype = type;
+    final BasicSSCell cell = this;
+    Runnable load = new Runnable() {
+      public void run() {
+        try {
+          cell.addDataSource(fsource, ftype);
+          if (!cell.hasData()) {
+            JOptionPane.showMessageDialog(Parent, "Unable to import data",
+              "Error importing data", JOptionPane.ERROR_MESSAGE);
+          }
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+          JOptionPane.showMessageDialog(Parent, exc.getMessage(),
+            "Error importing data", JOptionPane.ERROR_MESSAGE);
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+          JOptionPane.showMessageDialog(Parent, exc.getMessage(),
+            "Error importing data", JOptionPane.ERROR_MESSAGE);
+        }
+      }
+    };
+    Thread t = new Thread(load);
+    t.start();
+  }
+
+  /**
+   * Imports data from a file selected by the user.
+   */
+  public void loadDataDialog() {
+    // get file name from file dialog
+    FileBox.setDialogType(JFileChooser.OPEN_DIALOG);
+    if (FileBox.showOpenDialog(Parent) != JFileChooser.APPROVE_OPTION) return;
+
+    // make sure file exists
+    File f = FileBox.getSelectedFile();
+    if (!f.exists()) {
+      JOptionPane.showMessageDialog(Parent, f.getName() + " does not exist",
+        "Cannot load file", JOptionPane.ERROR_MESSAGE);
+      return;
+    }
+
+    // load file
+    loadDataSource(f.getAbsolutePath(), URL_SOURCE);
+  }
+
+  /**
+   * Pops up a dialog box for user to select file where data will be saved.
+   */
+  private File getSaveFile() {
+    if (!hasData()) {
+      JOptionPane.showMessageDialog(Parent, "This cell is empty.",
+        "Nothing to save", JOptionPane.ERROR_MESSAGE);
+      return null;
+    }
+
+    // get file name from file dialog
+    FileBox.setDialogType(JFileChooser.SAVE_DIALOG);
+    if (FileBox.showSaveDialog(Parent) != JFileChooser.APPROVE_OPTION) {
+      return null;
+    }
+    return FileBox.getSelectedFile();
+  }
+
+  /**
+   * Saves a Data object to a file selected by the user,
+   * using the given data form.
+   */
+  public void saveDataDialog(String varName, Form saveForm) {
+    // get file where data should be saved
+    final File file = getSaveFile();
+    if (file == null) return;
+
+    // start new thread to save the file
+    final BasicSSCell cell = this;
+    final String fname = varName;
+    final Form form = saveForm;
+    Runnable saveFile = new Runnable() {
+      public void run() {
+        String msg = "Could not save the dataset to the file " +
+          "\"" + file.getName() + "\" in " + form.getName() + " format. ";
+        try {
+          cell.saveData(fname, file.getAbsolutePath(), form);
+        }
+        catch (BadFormException exc) {
+          if (DEBUG) exc.printStackTrace();
+          msg = msg + "An error occurred: " + exc.getMessage();
+          JOptionPane.showMessageDialog(Parent, msg, "Error saving data",
+            JOptionPane.ERROR_MESSAGE);
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+          msg = msg + "A remote error occurred: " + exc.getMessage();
+          JOptionPane.showMessageDialog(Parent, msg, "Error saving data",
+            JOptionPane.ERROR_MESSAGE);
+        }
+        catch (IOException exc) {
+          if (DEBUG) exc.printStackTrace();
+          msg = msg + "An I/O error occurred: " + exc.getMessage();
+          JOptionPane.showMessageDialog(Parent, msg, "Error saving data",
+            JOptionPane.ERROR_MESSAGE);
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+          msg = msg + "An error occurred: " + exc.getMessage();
+          JOptionPane.showMessageDialog(Parent, msg, "Error saving data",
+            JOptionPane.ERROR_MESSAGE);
+        }
+      }
+    };
+    Thread t = new Thread(saveFile);
+    t.start();
+  }
+
+  /**
+   * Blocks until mapping auto-detection is complete.
+   */
+  public void waitForMaps() {
+    synchronized (MapLock) {
+      while (MapCount > 0) {
+        try { MapLock.wait(); }
+        catch (InterruptedException exc) {
+          if (DEBUG && DEBUG_LEVEL >= 3) exc.printStackTrace();
+        }
+      }
+    }
+  }
+
+  /**
+   * Does the work of adding the given DataReference,
+   * from the given source of the specified type.
+   *
+   * @return The newly created SSCellData object.
+   */
+  protected SSCellData addReferenceImpl(int id, DataReferenceImpl ref,
+    ConstantMap[] cmaps, String source, int type, boolean notify,
+    boolean checkErrors) throws VisADException, RemoteException
+  {
+    synchronized (MapLock) { MapCount++; }
+    return super.addReferenceImpl(id, ref, cmaps,
+      source, type, notify, checkErrors);
+  }
+
+  // --- DISPLAY MANAGEMENT ---
+
+  /**
+   * Whether the mapping dialog is currently being displayed.
+   */
+  private boolean mapDialogUp = false;
+
+  /**
+   * Asks user to confirm clearing the cell if any other cell depends on it.
+   */
+  public boolean confirmClear() {
+    if (othersDepend()) {
+      int ans = JOptionPane.showConfirmDialog(null, "Other cells depend on " +
+        "this cell. Are you sure you want to clear it?", "Warning",
+        JOptionPane.YES_NO_OPTION);
+      if (ans != JOptionPane.YES_OPTION) return false;
+    }
+    return true;
+  }
+
+  /**
+   * Clears the cell if no other cell depends on it; otherwise, ask the
+   * user "Are you sure?" return true if the cell was cleared.
+   */
+  public boolean smartClear() throws VisADException, RemoteException {
+    if (confirmClear()) {
+      clearWidgetFrame();
+      clearCell();
+      return true;
+    }
+    else return false;
+  }
+
+  /**
+   * Permanently destroy this cell, asking user for confirmation first
+   * if other cells depend on it; return true if the cell was destroyed.
+   */
+  public boolean smartDestroy() throws VisADException, RemoteException {
+    if (confirmClear()) {
+      clearWidgetFrame();
+      destroyCell();
+      return true;
+    }
+    else return false;
+  }
+
+  /**
+   * Switches to 3-D mode if necessary and available.
+   */
+  public void setMapsAuto(ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    if (AutoSwitch && maps != null) {
+      int need = 0;
+      for (int i=0; i<maps.length; i++) {
+        DisplayRealType drt = maps[i].getDisplayScalar();
+        if (drt.equals(Display.ZAxis) || drt.equals(Display.Latitude)) {
+          need = 2;
+        }
+        if (drt.equals(Display.Alpha) || drt.equals(Display.RGBA)) {
+          if (need < 1) need = 1;
+        }
+      }
+      // switch to Java3D mode if needed
+      if (need == 2) setDimension(JAVA3D_3D);
+      else if (need == 1 && Dim != JAVA3D_3D) setDimension(JAVA3D_2D);
+    }
+    setMaps(maps);
+  }
+
+  /**
+   * Sets the ScalarMaps for this cell and creates needed control widgets.
+   */
+  public void setMaps(ScalarMap[] maps)
+    throws VisADException, RemoteException
+  {
+    super.setMaps(maps);
+    if (WidgetFrame.isVisible() || AutoShowControls) showWidgetFrame();
+  }
+
+  /**
+   * Lets the user specify mappings between this cell's data and display.
+   */
+  public void addMapDialog() {
+    if (mapDialogUp) return;
+    mapDialogUp = true;
+
+    try {
+      // check whether this cell has data
+      if (getDataCount() == 0) {
+        JOptionPane.showMessageDialog(Parent, "This cell has no data",
+          "FancySSCell error", JOptionPane.ERROR_MESSAGE);
+        return;
+      }
+
+      // get mappings from mapping dialog
+      MappingDialog mapDialog = new MappingDialog(Parent, getData(), getMaps(),
+        Dim != JAVA2D_2D || AutoSwitch, Dim == JAVA3D_3D || AutoSwitch);
+      mapDialog.display();
+
+      // make sure user did not cancel the operation
+      if (!mapDialog.okPressed()) return;
+
+      // set up new mappings
+      try {
+        setMapsAuto(mapDialog.getMaps());
+      }
+      catch (VisADException exc) {
+        if (DEBUG) exc.printStackTrace();
+        JOptionPane.showMessageDialog(Parent,
+          "This combination of mappings is not valid: " + exc.getMessage(),
+          "Cannot assign mappings", JOptionPane.ERROR_MESSAGE);
+      }
+      catch (RemoteException exc) {
+        if (DEBUG) exc.printStackTrace();
+        JOptionPane.showMessageDialog(Parent,
+          "This combination of mappings is not valid: " + exc.getMessage(),
+          "Cannot assign mappings", JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    finally {
+      mapDialogUp = false;
+    }
+  }
+
+  /**
+   * Guesses a good set of mappings for this cell's data and applies them.
+   */
+  protected void autoDetectMappings() throws VisADException, RemoteException {
+    if (AutoDetect) {
+      boolean allow3d = (Dim != JAVA2D_2D || AutoSwitch);
+
+      // guess mappings for Data objects
+      int len = getDataCount();
+      Data[] data = getData();
+      MathType[] types = new MathType[len];
+      for (int i=0; i<len; i++) {
+        Data d = data[i];
+        types[i] = (d == null ? null : d.getType());
+      }
+      ScalarMap[] maps = DataUtility.guessMaps(types, allow3d);
+
+      // apply the mappings
+      setMapsAuto(maps);
+    }
+
+    // notify waitForMaps() method
+    synchronized (MapLock) {
+      MapCount--;
+      MapLock.notifyAll();
+    }
+  }
+
+
+  // --- GUI MANAGEMENT ---
+
+  /**
+   * Shows the widgets for altering controls (if there are any).
+   */
+  public synchronized void showWidgetFrame() {
+    if (VDisplay == null || CellData.size() == 0) return;
+    Util.invoke(false, DEBUG, new Runnable() {
+      public void run() {
+        Container jc = VDisplay.getWidgetPanel();
+        if (jc != null && jc.getComponentCount() > 0) {
+          WidgetFrame.setContentPane(jc);
+          WidgetFrame.pack();
+          WidgetFrame.setVisible(true);
+        }
+      }
+    });
+  }
+
+  /**
+   * Hides the widgets for altering controls.
+   */
+  public void hideWidgetFrame() {
+    Util.invoke(false, DEBUG, new Runnable() {
+      public void run() {
+        WidgetFrame.setVisible(false);
+      }
+    });
+  }
+
+  /**
+   * Specifies whether the FancySSCell has a border.
+   */
+  public void setBorderEnabled(boolean value) {
+    if (value) setSelected(Selected);
+    else setBorder(null);
+  }
+
+  /**
+   * Specifies whether the FancySSCell has a highlighted border.
+   */
+  public void setSelected(boolean value) {
+    if (Selected == value) return;
+    Selected = value;
+    Util.invoke(false, DEBUG, new Runnable() {
+      public void run() {
+        setHighlighted(Selected);
+        if (!Selected) hideWidgetFrame();
+        else if (AutoShowControls) showWidgetFrame();
+        refresh();
+      }
+    });
+  }
+
+  /**
+   * Specifies whether this FancySSCell should auto-switch to 3-D.
+   */
+  public synchronized void setAutoSwitch(boolean value) {
+    AutoSwitch = value;
+  }
+
+  /**
+   * Specifies whether this FancySSCell should auto-detect its mappings.
+   */
+  public synchronized void setAutoDetect(boolean value) {
+    AutoDetect = value;
+  }
+
+  /**
+   * Specifies whether this FancySSCell should auto-display its widget frame.
+   */
+  public synchronized void setAutoShowControls(boolean value) {
+    AutoShowControls = value;
+  }
+
+  /**
+   * Removes all widgets for altering controls and hide widget frame.
+   */
+  private void clearWidgetFrame() {
+    Util.invoke(false, DEBUG, new Runnable() {
+      public void run() {
+        WidgetFrame.setVisible(false);
+        JPanel pane = new JPanel();
+        pane.add(new JLabel("No controls"), "CENTER");
+        WidgetFrame.setContentPane(pane);
+      }
+    });
+  }
+
+  /**
+   * Sets whether this cell is highlighted.
+   */
+  private void setHighlighted(boolean hl) {
+    if (hl) setBorder(B_HIGHLIGHT);
+    else {
+      int dataCount = getDataCount();
+      if (dataCount == 0) {
+        // no datasets
+        setBorder(B_EMPTY);
+      }
+      else if (dataCount == 1) {
+        // single dataset
+        int type = getDataSourceType(getFirstVariableName());
+        if (type == DIRECT_SOURCE) setBorder(B_DIRECT);
+        else if (type == URL_SOURCE) setBorder(B_URL);
+        else if (type == FORMULA_SOURCE) setBorder(B_FORMULA);
+        else if (type == RMI_SOURCE) setBorder(B_RMI);
+        else if (type == REMOTE_SOURCE) setBorder(B_REMOTE);
+        else setBorder(B_UNKNOWN);
+      }
+      else {
+        // multiple datasets
+        setBorder(B_MULTI);
+      }
+    }
+  }
+
+
+  // --- EVENT HANDLING ---
+
+  /**
+   * Re-detects mappings when this cell's data changes.
+   */
+  public void ssCellChanged(SSCellChangeEvent e) {
+    int type = e.getChangeType();
+    if (type == SSCellChangeEvent.DATA_CHANGE) {
+      // refresh border color
+      Util.invoke(false, DEBUG, new Runnable() {
+        public void run() {
+          setHighlighted(Selected);
+        }
+      });
+
+      if (!IsRemote) {
+        // attempt to auto-detect mappings for new data
+        Data value = null;
+        try {
+          value = (Data) fm.getThing(e.getVariableName());
+        }
+        catch (ClassCastException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        try {
+          if (value != null) autoDetectMappings();
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (DEBUG) exc.printStackTrace();
+        }
+      }
+    }
+    else if (type == SSCellChangeEvent.DISPLAY_CHANGE) {
+      if (IsRemote) {
+        // reconstruct controls for cloned display
+        if (AutoShowControls) showWidgetFrame();
+      }
+    }
+  }
+
+
+  // --- MISCELLANEOUS ---
+
+  /**
+   * Captures display image and saves to a file selected by the user,
+   * in JPEG format.
+   */
+  public void captureDialog() {
+    // get file where captured image should be saved
+    final File f = getSaveFile();
+    if (f == null) return;
+
+    // start new thread to capture image and save it to the file
+    final BasicSSCell cell = this;
+    Runnable captureImage = new Runnable() {
+      public void run() {
+        String msg = "Could not save image snapshot to file \"" + f.getName() +
+          "\" in JPEG format. ";
+        try {
+          cell.captureImage(f);
+        }
+        catch (VisADException exc) {
+          if (DEBUG) exc.printStackTrace();
+          msg = msg + "An error occurred: " + exc.getMessage();
+          JOptionPane.showMessageDialog(Parent, msg, "Error saving data",
+            JOptionPane.ERROR_MESSAGE);
+        }
+        catch (IOException exc) {
+          if (DEBUG) exc.printStackTrace();
+          msg = msg + "An I/O error occurred: " + exc.getMessage();
+          JOptionPane.showMessageDialog(Parent, msg, "Error saving data",
+            JOptionPane.ERROR_MESSAGE);
+        }
+      }
+    };
+    Thread t = new Thread(captureImage);
+    t.start();
+  }
+
+
+  // --- ACCESSORS ---
+
+  /**
+   * Returns whether this FancySSCell auto-switches to 3-D.
+   */
+  public boolean getAutoSwitch() {
+    return AutoSwitch;
+  }
+
+  /**
+   * Returns whether this FancySSCell auto-detects its mappings.
+   */
+  public boolean getAutoDetect() {
+    return AutoDetect;
+  }
+
+  /**
+   * Returns whether this FancySSCell auto-displays its widget frame.
+   */
+  public boolean getAutoShowControls() {
+    return AutoShowControls;
+  }
+
+  /**
+   * Returns whether the cell has any associated controls.
+   */
+  public boolean hasControls() {
+    if (VDisplay == null || CellData.size() == 0) return false;
+    Container jc = VDisplay.getWidgetPanel();
+    if (jc == null) return false;
+    return (jc.getComponentCount() > 0);
+  }
+
+
+  // --- DEPRECATED ---
+
+  /**
+   * @deprecated Use loadDataSource(String, RMI_SOURCE) instead.
+   */
+  public void loadDataRMI(String s) {
+    loadDataSource(s, RMI_SOURCE);
+  }
+
+  /**
+   * @deprecated Use loadDataSource(String, URL_SOURCE) instead.
+   */
+  public synchronized void loadDataString(String s) {
+    loadDataSource(s, URL_SOURCE);
+  }
+
+  /**
+   * @deprecated Use loadDataSource(String, URL_SOURCE) instead.
+   */
+  public void loadDataURL(URL u) {
+    loadDataSource(u.toString(), URL_SOURCE);
+  }
+
+  /**
+   * @deprecated Use saveDataDialog(String, Form) instead.
+   */
+  public void saveDataDialog(boolean netcdf) {
+    try {
+      Form f;
+      if (netcdf) f = new visad.data.netcdf.Plain();
+      else f = new visad.data.visad.VisADForm();
+      saveDataDialog(getFirstVariableName(), f);
+    }
+    catch (VisADException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+  }
+
+  /**
+   * @deprecated Use saveDataDialog(String, Form) instead.
+   */
+  public void saveDataDialog(Form saveForm) {
+    saveDataDialog(getFirstVariableName(), saveForm);
+  }
+
+}
diff --git a/visad/ss/MappingDialog.java b/visad/ss/MappingDialog.java
new file mode 100644
index 0000000..0b71ac1
--- /dev/null
+++ b/visad/ss/MappingDialog.java
@@ -0,0 +1,1278 @@
+//
+// MappingDialog.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.event.*;
+import java.net.URL;
+import java.rmi.RemoteException;
+import java.util.Hashtable;
+import java.util.Vector;
+import javax.swing.*;
+import javax.swing.event.*;
+import visad.*;
+import visad.util.*;
+
+/**
+ * MappingDialog is a dialog that lets the user create ScalarMaps.
+ */
+public class MappingDialog extends JDialog
+  implements ActionListener, ListSelectionListener, MouseListener
+{
+  /**
+   * Flag whether user hit Done or Cancel button.
+   */
+  private boolean Confirm = false;
+
+  /**
+   * ScalarMaps selected by the user.
+   */
+  private ScalarMap[] ScalarMaps;
+
+
+  // -- GUI components --
+
+  private JComponent MathCanvas;
+  private JScrollPane MathCanvasView;
+  private JComponent CoordCanvas;
+  private JScrollPane CoordCanvasView;
+  private boolean CoordRefs;
+  private JList MathList;
+  private JComponent DisplayCanvas;
+  private DefaultListModel CurMaps;
+  private JList CurrentMaps;
+  private JScrollPane CurrentMapsView;
+  private JLabel description;
+
+
+  // -- State info --
+
+  /**
+   * Array of ScalarTypes.
+   */
+  private ScalarType[] MathTypes;
+
+  /**
+   * Array of ScalarType names.
+   */
+  private String[] Scalars;
+
+  /**
+   * Array of ScalarType widths.
+   */
+  private int[] ScW;
+
+  /**
+   * Vector of MDTuples indicating ScalarType locations.
+   */
+  private Vector[] ScP;
+
+  /**
+   * ScalarType height.
+   */
+  private int ScH;
+
+  /**
+   * Width and height of MathType string.
+   */
+  private Dimension StrSize;
+
+  /**
+   * Width and height of CoordinateSystem string.
+   */
+  private Dimension CoordSize;
+
+  /**
+   * Flags marking whether each possible ScalarMap has been assigned.
+   */
+  private boolean[][][] Maps;
+
+  /**
+   * String representation of each possible ScalarMap.
+   */
+  private String[][][] CurMapLabel;
+
+  /**
+   * 11 pt monospaced font.
+   */
+  private static final Font Mono = new Font("Monospaced", Font.PLAIN, 11);
+
+  /**
+   * Names of system intrinsic DisplayRealTypes.
+   */
+  private static final String[][] MapNames = {
+    {"X Axis", "Y Axis", "Z Axis", "X Offset", "Y Offset", "Z Offset"},
+    {"Latitude", "Longitude", "Radius",
+      "Cyl Radius", "Cyl Azimuth", "Cyl Z Axis"},
+    {"Flow1 X", "Flow1 Y", "Flow1 Z", "Flow2 X", "Flow2 Y", "Flow2 Z"},
+    {"Flow1 Elevation", "Flow1 Azimuth", "Flow1 Radial",
+      "Flow2 Elevation", "Flow2 Azimuth", "Flow2 Radial"},
+    {"Red", "Green", "Blue", "RGB", "RGBA", "Alpha"},
+    {"Cyan", "Magenta", "Yellow", "CMY", "Animation", "Iso-contour"},
+    {"Hue", "Saturation", "Value", "HSV", "Select Value", "Select Range"},
+    {"", "Text", "", "", "Shape", ""}
+  };
+
+  /**
+   * List of system intrinsic DisplayRealTypes.
+   */
+  private static final DisplayRealType[][] MapTypes = {
+    {Display.XAxis, Display.YAxis, Display.ZAxis,
+      Display.XAxisOffset, Display.YAxisOffset, Display.ZAxisOffset},
+    {Display.Latitude, Display.Longitude, Display.Radius,
+      Display.CylRadius, Display.CylAzimuth, Display.CylZAxis},
+    {Display.Flow1X, Display.Flow1Y, Display.Flow1Z,
+      Display.Flow2X, Display.Flow2Y, Display.Flow2Z},
+    {Display.Flow1Elevation, Display.Flow1Azimuth, Display.Flow1Radial,
+      Display.Flow2Elevation, Display.Flow2Azimuth, Display.Flow2Radial},
+    {Display.Red, Display.Green, Display.Blue,
+      Display.RGB, Display.RGBA, Display.Alpha},
+    {Display.Cyan, Display.Magenta, Display.Yellow,
+      Display.CMY, Display.Animation, Display.IsoContour},
+    {Display.Hue, Display.Saturation, Display.Value,
+      Display.HSV, Display.SelectValue, Display.SelectRange},
+    {null, Display.Text, null, null, Display.Shape, null}
+  };
+
+  /**
+   * Indices into MapTypes of alpha-related DisplayRealTypes.
+   */
+  private static final Point[] AlphaMaps = {
+    new Point(4, 4), // RGBA
+    new Point(4, 5)  // Alpha
+  };
+
+  /**
+   * Indices into MapTypes of 3D-related DisplayRealTypes.
+   */
+  private static final Point[] ThreeDMaps = {
+    new Point(0, 2), // ZAxis
+    new Point(0, 5), // ZAxisOffset
+    new Point(1, 0), // Latitude
+    new Point(1, 5), // CylZAxis
+    new Point(2, 2), // Flow1Z
+    new Point(2, 5), // Flow2Z
+    new Point(3, 0), // Flow1Elevation
+    new Point(3, 3)  // Flow2Elevation
+  };
+  
+  /**
+   * Width of mapping arrays.
+   */
+  private static final int MapWidth = MapTypes[0].length;
+
+  /**
+   * Height of mapping arrays.
+   */
+  private static final int MapHeight = MapTypes.length;
+
+  /**
+   * display.gif image.
+   */
+  private static Image DRT = null;
+
+  /**
+   * Whether DRT image has been initialized.
+   */
+  private static boolean Inited = false;
+
+  /**
+   * Pre-loads the display.gif file, so it's ready
+   * when mapping dialog is requested.
+   */
+  public static void initDialog() {
+    if (DRT == null) {
+      URL url = MappingDialog.class.getResource("display.gif");
+      DRT = Toolkit.getDefaultToolkit().getImage(url);
+    }
+    Inited = true;
+  }
+
+  /**
+   * Returns a human-readable list of CoordinateSystem dependencies.
+   */
+  private static String prettyCoordSys(MathType type) {
+    String s = "";
+    if (type instanceof FunctionType) s = s + pcsFunction((FunctionType) type);
+    else if (type instanceof SetType) s = s + pcsSet((SetType) type);
+    else if (type instanceof TupleType) s = s + pcsTuple((TupleType) type);
+    return s;
+  }
+
+  /**
+   * prettyCoordSys helper method.
+   */
+  private static String pcsFunction(FunctionType mathType) {
+    String s = "";
+
+    // extract domain
+    RealTupleType domain = mathType.getDomain();
+    s = s + pcsTuple((TupleType) domain);
+
+    // extract range
+    MathType range = mathType.getRange();
+    s = s + prettyCoordSys(range);
+
+    return s;
+  }
+
+  /**
+   * prettyCoordSys helper method.
+   */
+  private static String pcsSet(SetType mathType) {
+    // extract domain
+    RealTupleType domain = mathType.getDomain();
+    return pcsTuple((TupleType) domain);
+  }
+
+  /**
+   * prettyCoordSys helper method.
+   */
+  private static String pcsTuple(TupleType mathType) {
+    String s = "";
+    if (mathType instanceof RealTupleType) {
+      RealTupleType rtt = (RealTupleType) mathType;
+      CoordinateSystem cs = rtt.getCoordinateSystem();
+      if (cs != null) {
+        RealTupleType ref = cs.getReference();
+        if (ref != null) {
+          s = s + rtt.prettyString() + " ==> " + ref.prettyString() + "\n";
+        }
+      }
+    }
+    else {
+      // extract components
+      for (int j=0; j<mathType.getDimension(); j++) {
+        MathType cType = null;
+        try {
+          cType = mathType.getComponent(j);
+        }
+        catch (VisADException exc) { }
+        if (cType != null) s = s + prettyCoordSys(cType);
+      }
+    }
+    return s;
+  }
+
+  /**
+   * Converts a string index into a graphical position.
+   */
+  private static Point indexToPoint(int ndx, int[] lines, int w, int h) {
+    int i = 0;
+    while (i < lines.length && lines[i] <= ndx) i++;
+    int surplus = ndx - lines[i - 1];
+    return new Point(w * surplus + 5, (h + 2) * (i - 1) + 6);
+  }
+
+  /**
+   * For synchronization.
+   */
+  private Object Lock = new Object();
+
+  /**
+   * Flags marking whether each DisplayRealType is illegal.
+   */
+  private boolean[][] Illegal = new boolean[MapHeight][MapWidth];
+
+  /**
+   * This MappingDialog's copy of DRT with certain
+   * DisplayRealTypes blacked out as necessary.
+   */
+  private Image MapTo;
+
+  /**
+   * The MathType with which this mapping dialog works.
+   */
+  private MathType[] Types = null;
+
+  /**
+   * Whether this mapping dialog allows mappings to Alpha and RGBA.
+   */
+  private boolean AllowAlpha;
+
+  /**
+   * Whether this mapping dialog allows mappings to Z-Axis,
+   * Latitude, and Z-Offset.
+   */
+  private boolean Allow3D;
+
+  /**
+   * Constructs a MappingDialog from a single Data object.
+   */
+  public MappingDialog(Frame parent, Data data, ScalarMap[] startMaps,
+    boolean allowAlpha, boolean allow3D)
+  {
+    this(parent, new Data[] {data}, startMaps, allowAlpha, allow3D);
+  }
+
+  /**
+   * Constructs a MappingDialog from multiple Data objects.
+   */
+  public MappingDialog(Frame parent, Data[] data, ScalarMap[] startMaps,
+    boolean allowAlpha, boolean allow3D)
+  {
+    super(parent, "Set up data mappings", true);
+    AllowAlpha = allowAlpha;
+    Allow3D = allow3D;
+
+    // set up content pane
+    setBackground(Color.white);
+    JPanel contentPane = new JPanel();
+    setContentPane(contentPane);
+    contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
+    contentPane.add(Box.createRigidArea(new Dimension(0, 5)));
+
+    // parse MathTypes
+    int numData = data.length;
+    Types = new MathType[numData];
+    String typeString = "";
+    String coordString = "";
+    for (int i=0; i<numData; i++) {
+      try {
+        Data d = data[i];
+        if (d == null) Types[i] = null;
+        else {
+          Types[i] = d.getType();
+          typeString = typeString + Types[i].prettyString() + "\n";
+          coordString = coordString + prettyCoordSys(Types[i]);
+        }
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+    }
+    Vector v = new Vector();
+    int dupl = 0;
+    try {
+      dupl = DataUtility.getScalarTypes(data, v, true, true);
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+
+    // determine width and height of MathType string
+    FontMetrics fm = getFontMetrics(Mono);
+    ScH = fm.getHeight();
+    int charWidth = fm.stringWidth(" ");
+    int width = 1;
+    int pos = -1;
+    int numLines = 0;
+    do {
+      int nextPos = typeString.indexOf("\n", pos + 1);
+      if (nextPos < 0) nextPos = typeString.length();
+      int w = charWidth * (nextPos - pos);
+      if (w > width) width = w;
+      pos = nextPos;
+      numLines++;
+    }
+    while (pos < typeString.length());
+    StrSize = new Dimension(width, (ScH + 2) * numLines + 10);
+
+    // get starting line position for each line of MathType string
+    int[] mathLines = new int[numLines];
+    pos = -1;
+    for (int i=0; i<numLines; i++) {
+      mathLines[i] = pos + 1;
+      pos = typeString.indexOf("\n", pos + 1);
+    }
+
+    // determine width and height of CoordinateSystem string
+    width = 1;
+    pos = -1;
+    numLines = 0;
+    do {
+      int nextPos = coordString.indexOf("\n", pos + 1);
+      int w = charWidth * (nextPos - pos);
+      if (w > width) width = w;
+      pos = nextPos;
+      numLines++;
+    }
+    while (pos < coordString.length() - 1);
+    CoordSize = new Dimension(width, (ScH + 2) * numLines + 10);
+
+    // get starting line position for each line of CoordinateSystem string
+    int[] coordLines = new int[numLines];
+    pos = -1;
+    for (int i=0; i<numLines; i++) {
+      coordLines[i] = pos + 1;
+      pos = coordString.indexOf("\n", pos + 1);
+    }
+
+    // extract information about ScalarType locations within MathType strings
+    int len = v.size();
+    int unique = len - dupl;
+    MathTypes = new ScalarType[unique];
+    Scalars = new String[unique];
+    ScW = new int[unique];
+    ScP = new Vector[unique];
+    Hashtable nameToIndex = new Hashtable();
+    int s = 0;
+    boolean inCoord = false;
+    pos = -1;
+    for (int i=0; i<len; i++) {
+      ScalarType type = (ScalarType) v.elementAt(i);
+      String name = type.getName();
+      if (v.indexOf(type) == i) {
+        // first occurrence of this ScalarType
+        MathTypes[s] = type;
+        Scalars[s] = name;
+        ScW[s] = charWidth * name.length();
+        ScP[s] = new Vector();
+        nameToIndex.put(name, new Integer(s));
+        s++;
+      }
+      // determine position of ScalarType and add to position vector
+      int index = ((Integer) nameToIndex.get(name)).intValue();
+      MDTuple tuple = null;
+      if (!inCoord) {
+        pos = typeString.indexOf(name, pos + 1);
+        if (pos == -1) inCoord = true;
+        else {
+          Point p = indexToPoint(pos, mathLines, charWidth, ScH);
+          tuple = new MDTuple(p.x, p.y, false);
+        }
+      }
+      if (inCoord) {
+        pos = coordString.indexOf(name, pos + 1);
+        Point p = indexToPoint(pos, coordLines, charWidth, ScH);
+        tuple = new MDTuple(p.x, p.y, true);
+      }
+      ScP[index].add(tuple);
+    }
+
+    // alphabetize Scalars list
+    sort(0, unique - 1);
+
+    // mark whether there are CoordinateSystem references
+    CoordRefs = !coordString.equals("");
+
+    // set up "MathType" label
+    JLabel l0 = new JLabel("MathType:");
+    l0.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    contentPane.add(l0);
+
+    // set up top panel
+    JPanel topPanel = new JPanel();
+    topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS));
+    contentPane.add(topPanel);
+    contentPane.add(Box.createRigidArea(new Dimension(0, 5)));
+
+    // set up "CoordinateSystem references" label
+    if (CoordRefs) {
+      JLabel l1 = new JLabel("CoordinateSystem references:");
+      l1.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+      contentPane.add(l1);
+    }
+
+    // set up second top panel
+    JPanel topPanel2 = new JPanel();
+    topPanel2.setLayout(new BoxLayout(topPanel2, BoxLayout.X_AXIS));
+    contentPane.add(topPanel2);
+
+    // draw the pretty-print MathType sequence to an Image (slow!)
+    final BufferedImage ppImg = new BufferedImage(
+      StrSize.width, StrSize.height, BufferedImage.TYPE_INT_RGB);
+    Graphics g = ppImg.getGraphics();
+    g.setFont(Mono);
+    g.setColor(Color.white);
+    g.fillRect(0, 0, StrSize.width, StrSize.height);
+    g.setColor(Color.black);
+    for (int i=0; i<mathLines.length; i++) {
+      int start = mathLines[i];
+      int end = (i < mathLines.length - 1 ?
+        mathLines[i + 1] - 1 : typeString.length());
+      String line = typeString.substring(start, end);
+      g.drawString(line, 5, (ScH + 2) * (i + 1));
+    }
+    g.dispose();
+
+    // set up MathType canvas
+    MathCanvas = new JComponent() {
+      public void paint(Graphics g2) {
+        // draw pretty-print MathType using its Image
+        g2.drawImage(ppImg, 0, 0, this);
+        int ndx = MathList.getSelectedIndex();
+        if (ndx >= 0) {
+          for (int i=0; i<ScP[ndx].size(); i++) {
+            MDTuple tuple = (MDTuple) ScP[ndx].elementAt(i);
+            if (!tuple.b) {
+              g2.setFont(Mono);
+              String ss = Scalars[ndx];
+              int x = tuple.x;
+              int y = tuple.y;
+              g2.setColor(Color.blue);
+              g2.fillRect(tuple.x, tuple.y, ScW[ndx], ScH);
+              g2.setColor(Color.white);
+              g2.drawString(ss, tuple.x, tuple.y + ScH - 4);
+            }
+          }
+        }
+        // work-around for JDK 1.3 bug
+        Dimension d = MathCanvas.getSize();
+        g2.setColor(Color.white);
+        g2.fillRect(StrSize.width, 0, d.width, d.height);
+        g2.fillRect(0, StrSize.height, StrSize.width, d.height);
+      }
+    };
+    MathCanvas.setMinimumSize(StrSize);
+    MathCanvas.setPreferredSize(StrSize);
+    MathCanvas.addMouseListener(this);
+    MathCanvas.setBackground(Color.white);
+
+    // set up MathCanvas's ScrollPane
+    MathCanvasView = new JScrollPane(MathCanvas);
+    MathCanvasView.setMinimumSize(new Dimension(0, 0));
+    int prefMCHeight = StrSize.height + 10;
+    if (prefMCHeight < 70) prefMCHeight = 70;
+    int maxMCHeight = Toolkit.getDefaultToolkit().getScreenSize().height / 2;
+    if (prefMCHeight > maxMCHeight) prefMCHeight = maxMCHeight;
+    MathCanvasView.setPreferredSize(new Dimension(0, prefMCHeight));
+    MathCanvasView.setBackground(Color.white);
+    JScrollBar horiz = MathCanvasView.getHorizontalScrollBar();
+    JScrollBar verti = MathCanvasView.getVerticalScrollBar();
+    horiz.setBlockIncrement(5 * ScH + 10);
+    horiz.setUnitIncrement(ScH+2);
+    verti.setBlockIncrement(5 * ScH + 10);
+    verti.setUnitIncrement(ScH+2);
+    topPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+    JPanel whitePanel = new JPanel();
+    whitePanel.setBackground(Color.white);
+    whitePanel.setLayout(new BoxLayout(whitePanel, BoxLayout.X_AXIS));
+    whitePanel.add(MathCanvasView);
+    topPanel.add(whitePanel);
+    topPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+    // set up description label
+    description = new JLabel(" ");
+    JPanel descPanel = new JPanel();
+    descPanel.setLayout(new BoxLayout(descPanel, BoxLayout.X_AXIS));
+    contentPane.add(descPanel);
+    contentPane.add(Box.createRigidArea(new Dimension(0, 15)));
+    descPanel.add(description);
+
+    // set up lower panel
+    JPanel lowerPanel = new JPanel();
+    lowerPanel.setLayout(new BoxLayout(lowerPanel, BoxLayout.X_AXIS));
+    contentPane.add(lowerPanel);
+    contentPane.add(Box.createRigidArea(new Dimension(0, 5)));
+
+    // set up far-left-side panel
+    JPanel flsPanel = new JPanel();
+    flsPanel.setLayout(new BoxLayout(flsPanel, BoxLayout.Y_AXIS));
+    lowerPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+    lowerPanel.add(flsPanel);
+
+    // only do CoordSys stuff if there are CoordinateSystem references
+    if (CoordRefs) {
+      // draw the pretty-print CoordinateSystem references to an Image (slow!)
+      final BufferedImage csImg = new BufferedImage(
+        CoordSize.width, CoordSize.height, BufferedImage.TYPE_INT_RGB);
+      g = csImg.getGraphics();
+      g.setFont(Mono);
+      g.setColor(Color.white);
+      g.fillRect(0, 0, CoordSize.width, CoordSize.height);
+      g.setColor(Color.black);
+      for (int i=0; i<coordLines.length; i++) {
+        int start = coordLines[i];
+        int end = (i < coordLines.length - 1 ?
+          coordLines[i + 1] - 1 : coordString.length() - 1);
+        String line = coordString.substring(start, end);
+        g.drawString(line, 5, (ScH + 2) * (i + 1));
+      }
+      g.dispose();
+
+      // set up CoordinateSystem references canvas
+      CoordCanvas = new JComponent() {
+        public void paint(Graphics g2) {
+          // draw pretty-print CoordinateSystem reference list using its Image
+          g2.drawImage(csImg, 0, 0, this);
+          int ndx = MathList.getSelectedIndex();
+          if (ndx >= 0) {
+            for (int i=0; i<ScP[ndx].size(); i++) {
+              MDTuple tuple = (MDTuple) ScP[ndx].elementAt(i);
+              if (tuple.b) {
+                g2.setFont(Mono);
+                String ss = Scalars[ndx];
+                int x = tuple.x;
+                int y = tuple.y;
+                g2.setColor(Color.blue);
+                g2.fillRect(tuple.x, tuple.y, ScW[ndx], ScH);
+                g2.setColor(Color.white);
+                g2.drawString(ss, tuple.x, tuple.y + ScH - 4);
+              }
+            }
+          }
+          // work-around for JDK 1.3 bug
+          Dimension d = CoordCanvas.getSize();
+          g2.setColor(Color.white);
+          g2.fillRect(CoordSize.width, 0, d.width, d.height);
+          g2.fillRect(0, CoordSize.height, CoordSize.width, d.height);
+        }
+      };
+      CoordCanvas.setMinimumSize(CoordSize);
+      CoordCanvas.setPreferredSize(CoordSize);
+      CoordCanvas.addMouseListener(this);
+      CoordCanvas.setBackground(Color.white);
+
+      // set up CoordCanvas's ScrollPane
+      CoordCanvasView = new JScrollPane(CoordCanvas);
+      CoordCanvasView.setMinimumSize(new Dimension(0, 0));
+      CoordCanvasView.setPreferredSize(new Dimension(0, CoordSize.height));
+      int prefCCHeight = CoordSize.height + 10;
+      if (prefCCHeight < 70) prefCCHeight = 70;
+      int maxCCHeight = Toolkit.getDefaultToolkit().getScreenSize().height / 2;
+      if (prefCCHeight > maxCCHeight) prefCCHeight = maxCCHeight;
+      CoordCanvasView.setPreferredSize(new Dimension(0, prefCCHeight));
+      CoordCanvasView.setBackground(Color.white);
+      horiz = CoordCanvasView.getHorizontalScrollBar();
+      verti = CoordCanvasView.getVerticalScrollBar();
+      horiz.setBlockIncrement(5 * ScH + 10);
+      horiz.setUnitIncrement(ScH + 2);
+      verti.setBlockIncrement(5 * ScH + 10);
+      verti.setUnitIncrement(ScH + 2);
+      topPanel2.add(Box.createRigidArea(new Dimension(5, 0)));
+      JPanel whitePanel2 = new JPanel();
+      whitePanel2.setBackground(Color.white);
+      whitePanel2.setLayout(new BoxLayout(whitePanel2, BoxLayout.X_AXIS));
+      whitePanel2.add(CoordCanvasView);
+      topPanel2.add(whitePanel2);
+      topPanel2.add(Box.createRigidArea(new Dimension(5, 0)));
+    }
+
+    // set up left-side panel
+    JPanel lsPanel = new JPanel();
+    lsPanel.setLayout(new BoxLayout(lsPanel, BoxLayout.Y_AXIS));
+    lowerPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+    lowerPanel.add(lsPanel);
+
+    // begin set up "current mappings" list
+    CurMaps = new DefaultListModel();
+    int num = MathTypes.length;
+    CurMaps.ensureCapacity(num * MapWidth * MapHeight);
+    CurrentMaps = new JList(CurMaps);
+
+    // set up "map from" list
+    Maps = new boolean[num][MapHeight][MapWidth];
+    CurMapLabel = new String[num][MapHeight][MapWidth];
+    for (int i=0; i<num; i++) {
+      for (int j=0; j<MapHeight; j++) {
+        for (int k=0; k<MapWidth; k++) {
+          Maps[i][j][k] = false;
+          CurMapLabel[i][j][k] = Scalars[i] + " -> " + MapNames[j][k];
+          if (startMaps != null) {
+            for (int m=0; m<startMaps.length; m++) {
+              if (startMaps[m].getScalar().equals(MathTypes[i]) &&
+                  startMaps[m].getDisplayScalar().equals(MapTypes[j][k])) {
+                Maps[i][j][k] = true;
+                CurMaps.addElement(CurMapLabel[i][j][k]);
+              }
+            }
+          }
+        }
+      }
+    }
+    MathList = new JList(Scalars);
+    MathList.addListSelectionListener(this);
+    MathList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+    JScrollPane mathListView = new JScrollPane(MathList) {
+      public Dimension getMinimumSize() {
+        return new Dimension(0, 40 * MapHeight + 64);
+      }
+      public Dimension getPreferredSize() {
+        return new Dimension(200, 40 * MapHeight + 64);
+      }
+      public Dimension getMaximumSize() {
+        return new Dimension(Integer.MAX_VALUE, 40 * MapHeight + 64);
+      }
+    };
+    JLabel l2 = new JLabel("Map from:");
+    l2.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    lsPanel.add(l2);
+    lsPanel.add(mathListView);
+    lowerPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+    // set up center panel
+    JPanel centerPanel = new JPanel();
+    centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS));
+    lowerPanel.add(centerPanel);
+    lowerPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+    JLabel l3 = new JLabel("Map to:");
+    l3.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    centerPanel.add(l3);
+
+    // finish loading DRT if necessary
+    if (!Inited) initDialog();
+    MediaTracker mtracker = new MediaTracker(this);
+    mtracker.addImage(DRT, 0);
+    try {
+      mtracker.waitForID(0);
+    }
+    catch (InterruptedException exc) { }
+
+    // flag illegal DisplayRealTypes
+    for (int i=0; i<MapHeight; i++) {
+      for (int j=0; j<MapWidth; j++) Illegal[i][j] = MapTypes[i][j] == null;
+    }
+    if (!AllowAlpha) {
+      for (int i=0; i<AlphaMaps.length; i++) {
+        Point p = AlphaMaps[i];
+        Illegal[p.x][p.y] = true;
+      }
+    }
+    if (!Allow3D) {
+      for (int i=0; i<ThreeDMaps.length; i++) {
+        Point p = ThreeDMaps[i];
+        Illegal[p.x][p.y] = true;
+      }
+    }
+
+    // copy DRT into MapTo and black out icons of illegal DisplayRealTypes
+    MapTo = new BufferedImage(40 * MapWidth, 40 * MapHeight,
+      BufferedImage.TYPE_INT_RGB);
+    Graphics gr = MapTo.getGraphics();
+    gr.drawImage(DRT, 0, 0, this);
+    for (int i=0; i<MapHeight; i++) {
+      for (int j=0; j<MapWidth; j++) {
+        if (Illegal[i][j]) eraseBox(j, i, gr);
+      }
+    }
+    gr.dispose();
+
+    // set up "map to" canvas
+    DisplayCanvas = new JComponent() {
+      public void paint(Graphics g2) {
+        g2.drawImage(MapTo, 0, 0, this);
+        int ndx = MathList.getSelectedIndex();
+        if (ndx >= 0) {
+          for (int col=0; col<MapWidth; col++) {
+            for (int row=0; row<MapHeight; row++) {
+              if (Maps[ndx][row][col]) highlightBox(col, row, g2);
+            }
+          }
+        }
+      }
+
+      public Dimension getMinimumSize() {
+        return new Dimension(40 * MapWidth, 40 * MapHeight);
+      }
+
+      public Dimension getPreferredSize() {
+        return new Dimension(40 * MapWidth, 40 * MapHeight);
+      }
+
+      public Dimension getMaximumSize() {
+        return new Dimension(40 * MapWidth, 40 * MapHeight);
+      }
+    };
+    DisplayCanvas.addMouseListener(this);
+    centerPanel.add(DisplayCanvas);
+    centerPanel.add(Box.createRigidArea(new Dimension(0, 5)));
+
+    // set up button panel 1
+    JPanel b1Panel = new JPanel();
+    b1Panel.setLayout(new BoxLayout(b1Panel, BoxLayout.X_AXIS));
+    centerPanel.add(b1Panel);
+    centerPanel.add(Box.createRigidArea(new Dimension(0, 5)));
+    b1Panel.add(Box.createHorizontalGlue());
+
+    // set up "clear all" button
+    JButton clearAll = new JButton("Clear all");
+    clearAll.setAlignmentX(JButton.CENTER_ALIGNMENT);
+    clearAll.setToolTipText("Clear all mappings from mappings box");
+    clearAll.setActionCommand("all");
+    clearAll.addActionListener(this);
+    b1Panel.add(clearAll);
+    b1Panel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+    // set up "clear selected" button
+    JButton clearSel = new JButton("Clear selected");
+    clearSel.setAlignmentX(JButton.CENTER_ALIGNMENT);
+    clearSel.setToolTipText("Clear selected mappings from mappings box");
+    clearSel.setActionCommand("sel");
+    clearSel.addActionListener(this);
+    b1Panel.add(clearSel);
+    b1Panel.add(Box.createHorizontalGlue());
+
+    // set up button panel 2
+    JPanel b2Panel = new JPanel();
+    b2Panel.setLayout(new BoxLayout(b2Panel, BoxLayout.X_AXIS));
+    centerPanel.add(b2Panel);
+    b2Panel.add(Box.createHorizontalGlue());
+
+    // set up done button
+    JButton done = new JButton("Done");
+    done.setAlignmentX(JButton.CENTER_ALIGNMENT);
+    done.setToolTipText("Apply selected mappings");
+    done.setActionCommand("done");
+    done.addActionListener(this);
+    b2Panel.add(done);
+    b2Panel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+    // set up cancel button
+    JButton cancel = new JButton("Cancel");
+    cancel.setAlignmentX(JButton.CENTER_ALIGNMENT);
+    cancel.setToolTipText("Close dialog box without applying mappings");
+    cancel.setActionCommand("cancel");
+    cancel.addActionListener(this);
+    b2Panel.add(cancel);
+    b2Panel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+    // set up detect button
+    JButton detect = new JButton("Detect");
+    detect.setAlignmentX(JButton.CENTER_ALIGNMENT);
+    detect.setToolTipText("Automatically identify some good mappings");
+    detect.setActionCommand("detect");
+    detect.addActionListener(this);
+    b2Panel.add(detect);
+    b2Panel.add(Box.createHorizontalGlue());
+
+    // set up right-side panel
+    JPanel rsPanel = new JPanel();
+    rsPanel.setLayout(new BoxLayout(rsPanel, BoxLayout.Y_AXIS));
+    lowerPanel.add(rsPanel);
+    lowerPanel.add(Box.createRigidArea(new Dimension(5, 0)));
+
+    // finish set up "current mappings" list
+    CurrentMaps.addListSelectionListener(this);
+    CurrentMapsView = new JScrollPane(CurrentMaps) {
+      public Dimension getMinimumSize() {
+        return new Dimension(0, 40 * MapHeight + 64);
+      }
+
+      public Dimension getPreferredSize() {
+        return new Dimension(200, 40 * MapHeight + 64);
+      }
+
+      public Dimension getMaximumSize() {
+        return new Dimension(Integer.MAX_VALUE, 40 * MapHeight + 64);
+      }
+    };
+    JLabel l4 = new JLabel("Current maps:");
+    l4.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+    rsPanel.add(l4);
+    rsPanel.add(CurrentMapsView);
+  }
+
+  /**
+   * Ensure mapping dialog is not larger than the screen size.
+   */
+  public Dimension getPreferredSize() {
+    Dimension max = super.getPreferredSize();
+
+    // take Windows Start bar into account
+    String os = System.getProperty("os.name");
+    final int pad = os.startsWith("Windows") ? 60 : 20;
+
+    // ensure dialog size does not exceed screen size
+    int w = max.width;
+    int h = max.height;
+    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+    if (w > screen.width - pad) w = screen.width - pad;
+    if (h > screen.height - pad) h = screen.height - pad;
+    return new Dimension(w, h);
+  }
+
+  /**
+   * Displays the dialog in the center of the screen.
+   */
+  public void display() {
+    pack();
+    Util.centerWindow(this);
+    setVisible(true);
+  }
+
+  /**
+   * Gets the mappings selected by the user.
+   */
+  public ScalarMap[] getMaps() {
+    return ScalarMaps;
+  }
+
+  /**
+   * Gets whether the user pressed the Ok button.
+   */
+  public boolean okPressed() {
+    return Confirm;
+  }
+
+  /**
+   * Recursive quick-sort routine for alphabetizing scalars.
+   */
+  private void sort(int lo0, int hi0) {
+    if (hi0 < lo0) return;
+    int lo = lo0;
+    int hi = hi0;
+    String mid = Scalars[(lo0 + hi0) / 2].toLowerCase();
+    while (lo <= hi) {
+      while (lo < hi0 && Scalars[lo].toLowerCase().compareTo(mid) < 0) ++lo;
+      while (hi > lo0 && Scalars[hi].toLowerCase().compareTo(mid) > 0) --hi;
+      if (lo <= hi) {
+        // MathTypes[lo] <-> MathTypes[hi]
+        ScalarType st = MathTypes[lo];
+        MathTypes[lo] = MathTypes[hi];
+        MathTypes[hi] = st;
+        // Scalars[lo] <-> Scalars[hi]
+        String s = Scalars[lo];
+        Scalars[lo] = Scalars[hi];
+        Scalars[hi] = s;
+        // ScW[lo] <-> ScW[hi]
+        int i = ScW[lo];
+        ScW[lo] = ScW[hi];
+        ScW[hi] = i;
+        // ScP[lo] <-> ScP[hi]
+        Vector v = ScP[lo];
+        ScP[lo] = ScP[hi];
+        ScP[hi] = v;
+
+        lo++;
+        hi--;
+      }
+    }
+    if (lo0 < hi) sort(lo0, hi);
+    if (lo < hi0) sort(lo, hi0);
+  }
+
+  /**
+   * Clears a box in the "map to" canvas.
+   */
+  void eraseBox(int col, int row, Graphics g) {
+    int x = 40 * col;
+    int y = 40 * row;
+    g.setColor(Color.black);
+    for (int i=0; i<40; i+=2) {
+      g.drawLine(x, y + i, x + i, y);
+      g.drawLine(x + i, y + 38, x + 38, y + i);
+    }
+  }
+
+  /**
+   * Highlights a box in the "map to" canvas.
+   */
+  void highlightBox(int col, int row, Graphics g) {
+    int x = 40 * col;
+    int y = 40 * row;
+    final int n1 = 11;
+    final int n2 = 29;
+    g.setColor(Color.blue);
+    g.drawRect(x, y, 39, 39);
+    g.drawLine(x, y + n1, x + n1, y);
+    g.drawLine(x, y + n2, x + n2, y);
+    g.drawLine(x + n1, y + 39, x + 39, y + n1);
+    g.drawLine(x + n2, y + 39, x + 39, y + n2);
+    g.drawLine(x + n2, y, x + 39, y + n1);
+    g.drawLine(x + n1, y, x + 39, y + n2);
+    g.drawLine(x, y + n1, x + n2, y + 39);
+    g.drawLine(x, y + n2, x + n1, y + 39);
+  }
+
+  /**
+   * Clears all maps from the current mappings list.
+   */
+  private void clearAll() {
+    CurrentMaps.clearSelection(); // work-around for nasty swing bug
+    CurMaps.removeAllElements();
+    for (int i=0; i<CurMapLabel.length; i++) {
+      for (int j=0; j<MapHeight; j++) {
+        for (int k=0; k<MapWidth; k++) Maps[i][j][k] = false;
+      }
+    }
+  }
+
+  /**
+   * Updates the description label to match the currently selected ScalarType.
+   */
+  private void updateDescriptionLabel(int i) {
+    ScalarType st = MathTypes[i];
+    String desc = "     " + Scalars[i];
+    if (st instanceof RealType) {
+      RealType r = (RealType) st;
+      Unit unit = r.getDefaultUnit();
+      Set set = r.getDefaultSet();
+      String u = unit == null ? "none" : unit.toString();
+      String s;
+      if (set == null) s = "none";
+      else {
+        String setType = set.getClass().getName();
+        int index = setType.lastIndexOf(".");
+        if (index >= 0) setType = setType.substring(index + 1);
+        int dim = set.getDimension();
+        s = setType + "(" + dim + ")";
+      }
+      desc = desc + ": Unit=" + u + "; Set=" + s;
+    }
+    else {
+      desc = desc + " (text)";
+    }
+    description.setText(desc);
+  }
+
+  /**
+   * Handles button press events.
+   */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+
+    synchronized (Lock) {
+      if (cmd.equals("all")) { // clear all
+        if (CurMaps.getSize() > 0) {
+          clearAll();
+          // update components
+          Graphics g = DisplayCanvas.getGraphics();
+          DisplayCanvas.paint(g);
+          g.dispose();
+          CurrentMaps.repaint();
+          CurrentMapsView.validate();
+        }
+      }
+
+      else if (cmd.equals("sel")) { // clear selected
+        int[] ndx = CurrentMaps.getSelectedIndices();
+        int len = ndx.length;
+        for (int x=len-1; x>=0; x--) {
+          String s = (String) CurMaps.getElementAt(ndx[x]);
+          boolean looking = true;
+          for (int i=0; i<CurMapLabel.length && looking; i++) {
+            for (int j=0; j<MapHeight && looking; j++) {
+              for (int k=0; k<MapWidth && looking; k++) {
+                if (CurMapLabel[i][j][k] == s) {
+                  Maps[i][j][k] = false;
+                  looking = false;
+                }
+              }
+            }
+          }
+          // take map off list
+          CurMaps.removeElementAt(ndx[x]);
+        }
+        if (len > 0) {
+          // update components
+          Graphics g = DisplayCanvas.getGraphics();
+          DisplayCanvas.paint(g);
+          g.dispose();
+          CurrentMaps.repaint();
+          CurrentMapsView.validate();
+        }
+      }
+
+      else if (cmd.equals("detect")) {
+        ScalarMap[] maps = DataUtility.guessMaps(Types, Allow3D);
+        if (CurMaps.getSize() > 0) clearAll();
+        if (maps != null) {
+          for (int i=0; i<MathTypes.length; i++) {
+            for (int j=0; j<MapHeight; j++) {
+              for (int k=0; k<MapWidth; k++) {
+                for (int m=0; m<maps.length; m++) {
+                  if (maps[m].getScalar() == MathTypes[i] &&
+                    maps[m].getDisplayScalar() == MapTypes[j][k])
+                  {
+                    Maps[i][j][k] = true;
+                    CurMaps.addElement(CurMapLabel[i][j][k]);
+                  }
+                }
+              }
+            }
+          }
+        }
+        // update components
+        Graphics g = DisplayCanvas.getGraphics();
+        DisplayCanvas.paint(g);
+        g.dispose();
+        CurrentMaps.repaint();
+        CurrentMapsView.validate();
+      }
+
+      else if (cmd.equals("done")) {
+        boolean okay = true;
+        int size = CurMaps.getSize();
+        ScalarMaps = new ScalarMap[size];
+        int s = 0;
+        for (int i=0; i<CurMapLabel.length; i++) {
+          for (int j=0; j<MapHeight; j++) {
+            for (int k=0; k<MapWidth; k++) {
+              if (Maps[i][j][k]) {
+                try {
+                  ScalarMaps[s++] =
+                    new ScalarMap(MathTypes[i], MapTypes[j][k]);
+                }
+                catch (VisADException exc) {
+                  okay = false;
+                  JOptionPane.showMessageDialog(this, "The mapping (" +
+                    Scalars[i] + " -> " + MapNames[j][k] + ") is not valid.",
+                    "Illegal mapping", JOptionPane.ERROR_MESSAGE);
+                }
+              }
+            }
+          }
+        }
+        if (okay) {
+          Confirm = true;
+          setVisible(false);
+        }
+      }
+
+      else if (cmd.equals("cancel")) setVisible(false);
+    }
+  }
+
+  /**
+   * Handles list selection change events.
+   */
+  public void valueChanged(ListSelectionEvent e) {
+    synchronized (Lock) {
+      if (!e.getValueIsAdjusting()) {
+        if ((JList) e.getSource() == MathList) {
+          Graphics g = DisplayCanvas.getGraphics();
+          DisplayCanvas.paint(g);
+          g.dispose();
+          int i = MathList.getSelectedIndex();
+          MDTuple tuple = (MDTuple) ScP[i].elementAt(0);
+          Rectangle r = new Rectangle(tuple.x, tuple.y, ScW[i], ScH);
+          MathList.ensureIndexIsVisible(i);
+          if (tuple.b) CoordCanvas.scrollRectToVisible(r);
+          else MathCanvas.scrollRectToVisible(r);
+          MathCanvas.repaint();
+          if (CoordRefs) CoordCanvas.repaint();
+          updateDescriptionLabel(i);
+        }
+      }
+    }
+  }
+
+  /**
+   * Handles mouse clicks in the MathType window and "map to" canvas.
+   */
+  public void mousePressed(MouseEvent e) {
+    synchronized (Lock) {
+      Component c = e.getComponent();
+      if (c == MathCanvas) {
+        Point p = e.getPoint();
+        for (int i=0; i<Scalars.length; i++) {
+          for (int j=0; j<ScP[i].size(); j++) {
+            MDTuple tuple = (MDTuple) ScP[i].elementAt(j);
+            if (!tuple.b) {
+              Rectangle r = new Rectangle(tuple.x, tuple.y, ScW[i], ScH);
+              if (r.contains(p)) {
+                // highlight clicked ScalarType
+                MathList.setSelectedIndex(i);
+                MathList.ensureIndexIsVisible(i);
+                MathCanvas.scrollRectToVisible(r);
+                updateDescriptionLabel(i);
+                return;
+              }
+            }
+          }
+        }
+      }
+      else if (c == DisplayCanvas) {
+        int col = e.getX() / 40;
+        int row = e.getY() / 40;
+        int ndx = MathList.getSelectedIndex();
+        if (ndx >= 0 && row >= 0 && col >= 0 &&
+          row < MapHeight && col < MapWidth && !Illegal[row][col])
+        {
+          Maps[ndx][row][col] = !Maps[ndx][row][col];
+          if (Maps[ndx][row][col]) {
+            CurMaps.addElement(CurMapLabel[ndx][row][col]);
+          }
+          else {
+            CurrentMaps.clearSelection();
+            CurMaps.removeElement(CurMapLabel[ndx][row][col]);
+          }
+          // redraw DisplayCanvas
+          Graphics g = DisplayCanvas.getGraphics();
+          g.setClip(40 * col, 40 * row, 41, 41);
+          DisplayCanvas.paint(g);
+          g.dispose();
+
+          // redraw CurrentMaps
+          CurrentMaps.repaint();
+          CurrentMapsView.validate();
+        }
+      }
+      else if (c == CoordCanvas) {
+        Point p = e.getPoint();
+        for (int i=0; i<Scalars.length; i++) {
+          for (int j=0; j<ScP[i].size(); j++) {
+            MDTuple tuple = (MDTuple) ScP[i].elementAt(j);
+            if (tuple.b) {
+              Rectangle r = new Rectangle(tuple.x, tuple.y, ScW[i], ScH);
+              if (r.contains(p)) {
+                MathList.setSelectedIndex(i);
+                MathList.ensureIndexIsVisible(i);
+                CoordCanvas.scrollRectToVisible(r);
+                return;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Unused MouseListener method.
+   */
+  public void mouseEntered(MouseEvent e) { }
+
+  /**
+   * Unused MouseListener method.
+   */
+  public void mouseExited(MouseEvent e) { }
+
+  /**
+   * Unused MouseListener method.
+   */
+  public void mouseClicked(MouseEvent e) { }
+
+  /** 
+   * Unused MouseListener method.
+   */
+  public void mouseReleased(MouseEvent e) { }
+
+
+  /**
+   * Helper class representing an (int, int, boolean) tuple.
+   */
+  class MDTuple {
+    int x, y;
+    boolean b;
+
+    MDTuple(int x, int y, boolean b) {
+      this.x = x;
+      this.y = y;
+      this.b = b;
+    }
+  }
+
+}
diff --git a/visad/ss/README.ss b/visad/ss/README.ss
new file mode 100644
index 0000000..3f586a8
--- /dev/null
+++ b/visad/ss/README.ss
@@ -0,0 +1,665 @@
+                    VisAD SpreadSheet User Interface README file
+                                  24 January 2001
+ 
+                                 Table of Contents
+
+1. The visad.ss Package
+  1.1 Description
+  1.2 Compiling and Running
+  1.3 Source Files
+    1.3.1 BasicSSCell
+    1.3.2 FancySSCell
+    1.3.3 MappingDialog
+    1.3.4 SpreadSheet
+    1.3.5 SSCellChangeEvent
+    1.3.6 SSCellData
+    1.3.7 SSCellImpl
+    1.3.8 SSCellListener
+    1.3.9 SSLayout
+2. Features of the SpreadSheet User Interface
+  2.1 Basic Commands
+  2.2 Menu Commands
+    2.2.1 File Menu
+    2.2.2 Edit Menu
+    2.2.3 Setup Menu
+    2.2.4 Cell Menu
+    2.2.5 Layout Menu
+    2.2.6 Options Menu
+  2.3 Toolbars
+    2.3.1 Main Toolbar
+    2.3.2 Formula Toolbar
+      2.3.2.1 Description and Usage
+      2.3.2.2 How To Enter Formulas
+      2.3.2.3 Formula Syntax
+      2.3.2.4 Linking to External Java Code
+      2.3.2.5 Examples of Valid Formulas
+  2.4 Remote Collaboration
+    2.4.1 Creating a SpreadSheet RMI server
+    2.4.2 Sharing individual SpreadSheet cells
+    2.4.3 Cloning entire SpreadSheets
+    2.4.4 Creating a SpreadSheet slave
+  2.5 Undocumented Features
+3. Known Bugs
+
+
+1. The visad.ss Package
+
+1.1 Description
+
+This README file explains what the visad.ss package is, what it does,
+and how to use it.
+
+The visad.ss package is a "generic" spreadsheet user interface for VisAD.
+It is intended to be powerful and flexible, and it can be used to visualize
+many types of data, without any programming.  It supports many features of a
+traditional spreadsheet, such as formulas.  The package also provides a class
+structure such that developers can easily create their own user interfaces
+using SpreadSheet cells from the visad.ss package.
+
+Each VisAD SpreadSheet cell can display an arbitrary number of VisAD Data
+objects, mapped to the display in any way you choose.  Data can be imported
+from a file or URL, an RMI address for a server SpreadSheet running on
+another machine, or computed from a formula, similar to a traditional
+spreadsheet application.
+
+For up-to-date information about the VisAD SpreadSheet, see the VisAD
+SpreadSheet web page at http://www.ssec.wisc.edu/~curtis/ss.html
+
+For up-to-date information about VisAD in general, see the VisAD web page
+at http://www.ssec.wisc.edu/~billh/visad.html
+
+1.2 Compiling and Running
+
+To compile the package, type the following from the visad/ss directory:
+
+    javac -J-mx32m *.java
+
+To run the SpreadSheet user interface, type:
+
+    java -mx64m visad.ss.SpreadSheet
+
+You can optionally specify the number of spreadsheet cells with:
+
+    java -mx64m visad.ss.SpreadSheet (cols) (rows)
+
+where (cols) is the number of columns, and (rows) is the number of rows.
+The default is four cells (two columns, two rows).  Note that rows and
+columns can be added or deleted at run time using the commands from the
+Layout menu.
+
+The SpreadSheet user interface requires a lot of memory (at least 32 MB),
+especially if you want to work with large data sets.  If you receive an
+OutOfMemoryError, you should increase the amount of memory allocated to
+the program (increase the # in "-mx#m").
+
+To load a spreadsheet file or data file into the SpreadSheet automatically
+upon launch, use:
+
+    java -mx64m visad.ss.SpreadSheet -file (filename)
+
+where (filename) is the name of the spreadsheet file or data file to be loaded.
+
+Other useful command line parameters include:
+  -debug   Runs the SpreadSheet in "debug mode" (all errors will print stack
+           traces to the console, and some additional warnings will also be
+           outputted where appropriate).
+  -no3d    Disables Java3D.  The SpreadSheet will run as though Java3D is not
+           present on the system, allowing only "2-D (Java2D)" displays.
+  -gui     Causes a dialog box to pop up, allowing you to configure the
+           SpreadSheet.  Options include setting up a SpreadSheet server (clone
+           or slave), specifying numbers of rows and columns, and toggling
+           debug mode or Java3D support.  This flag is useful, for example, if
+           you are running Windows and want to create a shortcut to the
+           SpreadSheet, but still wish to have the full power of the command
+           line arguments.
+
+1.3 Source Files
+
+The following source files are part of the visad.ss package:
+    - BasicSSCell.java
+    - FancySSCell.java
+    - MappingDialog.java
+    - SpreadSheet.java
+    - SSCellChangeEvent.java
+    - SSCellData.java
+    - SSCellImpl.java
+    - SSCellListener.java
+    - SSLayout.java
+
+The following included GIF files are needed by the package:
+    - 2d.gif
+    - 3d.gif
+    - add.gif
+    - copy.gif
+    - cut.gif
+    - del.gif
+    - display.gif
+    - j2d.gif
+    - mappings.gif
+    - open.gif
+    - paste.gif
+    - reset.gif
+    - save.gif
+    - show.gif
+    - tile.gif
+
+1.3.1 BasicSSCell
+
+This class can be instantiated and added to a JFC user interface.  It
+represents a single spreadsheet cell with some basic capabilities.  It is
+designed to be "quiet" (i.e., it throws exceptions rather than displaying
+errors in error message dialog boxes).
+
+1.3.2 FancySSCell
+
+This class is an extension of BasicSSCell that can be instantiated and
+added to a JFC user interface to provide all of the capabilities of a
+BasicSSCell, plus some additional, "fancy" capabilities.  It is designed to
+be "loud" (i.e., it displays errors in error message dialog boxes rather
+than throwing exceptions).
+
+1.3.3 MappingDialog
+
+This class is a dialog box allowing the user to specify ScalarMaps for
+the current data sets.
+
+1.3.4 SpreadSheet
+
+This is the main SpreadSheet user interface class.  It manages
+multiple FancySSCells.
+
+1.3.5 SSCellChangeEvent
+
+An event signifying a data, display or dimension change in an SSCell.
+
+1.3.6 SSCellData
+
+This class encapsulates a VisAD Data object and important associated
+information, such as the data's variable name (e.g., A1d1).
+
+1.3.7 SSCellImpl
+
+Each VisAD Data object present in an SSCell is monitored by an instance of this
+class, which takes care of updating the SSCell display and notifying remote
+cells of the data changes that occur.
+
+1.3.8 SSCellListener
+
+An interface for classes that wish to be informed when an SSCell changes.
+
+1.3.9 SSLayout
+
+This is the layout manager for the spreadsheet cells and their labels.
+
+2. Features of the SpreadSheet User Interface
+
+2.1 Basic Commands
+
+The spreadsheet cell with the yellow border is the current, highlighted
+cell.  Any operation you perform (such as importing a data set), will affect
+the highlighted cell.  Cells that are not highlighted will be colored according
+to the following scheme:
+  - Gray - no data
+  - Red - a formula
+  - Blue - an RMI address
+  - Green - a filename or URL
+  - Rainbow - multiple data objects
+
+In addition, the following border colors are supported, although they will not
+appear during normal SpreadSheet operation:
+  - Purple - data from an unknown source
+  - Yellow - data from a remote source
+  - Cyan - data that was set directly
+
+To change which cell is highlighted, click inside the desired cell with a mouse
+button.  You can also resize the spreadsheet cells, to allow some cells to be
+larger than others, by dragging the yellow blocks between cell labels.
+
+2.2 Menu Commands
+
+2.2.1 File Menu
+
+Here are the commands from the File menu:
+
+Import data - Brings up a dialog box that allows the user to select a file for
+the SpreadSheet to import to the current cell.  Currently, VisAD supports the
+following file types:
+    GIF, JPEG, PNG, netCDF, HDF-5, HDF-EOS, FITS,
+    Vis5D, McIDAS area, and serialized data.
+-------------------------------------------------------------------------------
+Note: You must have the HDF-EOS and HDF-5 file adapter native C code compiled
+      in order to import data sets of those types.  See the SpreadSheet web
+      page for information on how to compile this native code.
+-------------------------------------------------------------------------------
+
+Export data to netCDF - Exports the selected dataset from the current cell to a
+file in netCDF format.  A dialog box will appear to let you select the name and
+location of the netCDF file.  If the file exists, it will be overwritten.
+
+Export serialized data - Exports the selected dataset from the current cell to
+a file in serialized data format (the "VisAD" form).  A dialog box will appear
+to let you select the name and location of the serialized data file.  If the
+file exists, it will be overwritten.
+-------------------------------------------------------------------------------
+WARNING: Exporting a cell as serialized data is a handy and portable way to
+         store data, but each time the VisAD Data class hierarchy changes, old
+         serialized data files become obsolete and will no longer load
+         properly.  For long term storage of your data, use the "Export data to
+         netCDF" command.
+-------------------------------------------------------------------------------
+
+Export data to HDF5 - Exports the selected dataset from the current cell to a
+file in HDF-5 format.  A dialog box will appear to let you select the name and
+location of the HDF-5 file.  If the file exists, it will be overwritten.
+-------------------------------------------------------------------------------
+Note: You must have the HDF-5 file adapter native C code compiled in order to
+export data sets of this type.  See the SpreadSheet web page for information on
+how to compile this native code.
+-------------------------------------------------------------------------------
+
+Take JPEG snapshot - Takes a snapshot of the current cell and saves it to a
+file in JPEG format.  A dialog box will appear to let you select the name and
+location of the JPEG file.  If the file exists, it will be overwritten.
+
+Exit - Quits the VisAD SpreadSheet User Interface.
+
+2.2.2 Edit Menu
+
+Here are the commands from the Edit menu:
+
+Cut - Moves the current cell to the clipboard.
+
+Copy - Copies the current cell to the clipboard.
+
+Paste - Copies the cell in the clipboard to the current cell.
+
+Clear - Clears the current cell.
+
+2.2.3 Setup Menu
+
+Here are the commands from the Setup menu:
+
+New - Clears all spreadsheet cells; starts from scratch.
+
+Open - Opens a "spreadsheet file".  Spreadsheet files are small, containing
+only the instructions needed to recreate a spreadsheet.  They do not contain
+any actual data, but rather the file names, URLs, RMI addresses, formulas,
+dimensionality information, mappings, and control information of the cells.
+
+Save - Saves a spreadsheet file under the current name.  
+
+Save as - Saves a spreadsheet file under a new name.
+
+2.2.4 Cell Menu
+
+3-D (Java3D) - Sets the current cell's display dimension to 3-D.  This setting
+requires Java3D.  If you do not have Java3D installed, this option will be
+grayed out.
+
+2-D (Java2D) - Sets the current cell's display dimension to 2-D.  This uses
+Java2D, which comes with the JDK.  However, in this mode, nothing can be mapped
+to ZAxis, Latitude, or Alpha.  For computers without 3-D acceleration, this
+mode will provide much better performance, but the display quality will not be
+as good as 2-D (Java3D).  If you do not have Java3D installed, this is the only
+available mode.
+
+2-D (Java3D) - Sets the current cell's display dimension to 2-D.  This requires
+Java3D.  In this mode, nothing can be mapped to ZAxis or Latitude (but things
+can be mapped to Alpha).  On computers with 3-D acceleration, this mode will
+probably provide better performance than 2-D (Java2D).  It also has better
+display quality than 2-D (Java2D).  If you do not have Java3D installed, this
+option will be grayed out.
+
+Add data object - Blanks out the formula bar, allowing you to type in a new
+data source (e.g., filename, URL or formula).
+
+Remove data object - Removes the data object currently selected (use the
+drop-down formula bar list to specify a data object).
+
+Print cell - Prints the current cell to the printer.  Choosing this option
+causes a dialog box to appear that lets you specify your printer settings
+before the cell is actually printed.
+
+Edit Mappings - Brings up a dialog box which lets you change how the current
+cell's Data objects are mapped to the Display.  Click a RealType object on the
+left (or from the MathType display at the top), then click a display icon from
+the display panel in the center of the dialog.  The "Current Mappings" box on
+the lower right will change to reflect which mappings you've currently set up.
+When you've set up all the mappings to your liking, click the Done button and
+the SpreadSheet will try to display the data objects.  To close the dialog box
+without applying any of the changes you made to the mappings, click the Cancel
+button.  You can also highlight items from the "Current Mappings" box, then
+click "Clear selected" to remove those mappings from the list, or click "Clear
+all" to clear all mappings from the list and start from scratch.
+
+Reset orientation - Resets the current cell's display projection to the
+original orientation, size and location.
+
+Show controls - Displays the set of controls relevant to the current cell
+(these controls are displayed by default, but could become hidden at a later
+time).  This option is not a checkbox, but rather just redisplays the controls
+for the current cell if they have been closed by the user.
+
+2.2.5 Layout Menu
+
+Here are the commands from the Layout menu:
+
+Add column - Creates a new column and places it at the right edge of the
+spreadsheet.
+
+Add row - Creates a new row and places it at the bottom edge of the
+spreadsheet.
+
+Delete column - Deletes the column to which the currently selected cell
+belongs.  If a cell depends on any of the cells in the column, the delete
+column operation will fail.
+
+Delete row - Deletes the row to which the currently selected cell belongs.
+If a cell depends on any of the cells in the row, the delete row operation will
+fail.
+
+Tile cells - Resizes the cells to equal sizes, so that they fit exactly within
+the visible frame, if possible (if there are a lot of cells, they may not all
+fit within the visible frame).
+
+2.2.6 Options Menu
+
+Here are the commands from the Options menu:
+
+Auto-switch to 3-D - If this option is checked, cells will automatically switch
+to 3-D display mode when mappings are used that require 3-D display mode.  In
+addition, it will switch to mode 2-D (Java3D) from mode 2-D (Java2D) if
+anything is mapped to Alpha or RGBA.  If you do not have Java3D installed, this
+option is grayed out.  Otherwise, this option is checked by default.
+
+Auto-detect mappings - If this option is checked, the SpreadSheet will attempt
+to detect a good set of mappings for a newly loaded data set and automatically
+apply them.  This option is checked by default.
+
+Auto-display controls - If this option is checked, the SpreadSheet will
+automatically display the controls relevant to a cell's data whenever that
+cell's mappings change, or the cell becomes highlighted.  If this option is
+unchecked, use the "Show VisAD controls" menu item or toolbar button to display
+the controls.  This option is checked by default.
+
+2.3 Toolbars
+
+2.3.1 Main Toolbar
+
+The main toolbar provides shortcuts to the following menu items:
+    File Import and File Export (netCDF),
+    Edit Cut, Edit Copy and Edit Paste,
+    Cell 3-D (Java3D), Cell 2-D (Java3D) and Cell 2-D (Java2D),
+    Cell Edit mappings, Cell Reset orientation and Cell Show controls,
+    Layout Tile cells.
+
+The main toolbar has tool tips so each button can be easily identified.
+
+2.3.2 Formula Toolbar
+
+2.3.2.1 Description and Usage
+
+The formula toolbar is used for entering file names, URLs, RMI addresses,
+and formulas for the current cell.  If you enter the name of a file in the
+formula text box, the SpreadSheet will attempt to import the data from that
+file.  If you enter a URL, the SpreadSheet will try to download and import the
+data from that URL (however, VisAD only supports loading GIF, JPEG and PNG
+files from URLs right now).  If you enter an RMI address, the SpreadSheet will
+try to import the data from that RMI address (see section 2.4).  If you enter a
+formula, it will attempt to parse and evaluate that formula.  If a formula
+entered is invalid for some reason, the answer cannot be computed, or the file,
+URL, or RMI address entered does not exist, the cell will have an explanation
+(i.e., a list of error messages) displayed inside instead of the normal data
+box.  If the data box appears, the cell was computed successfully and mappings
+can be set up.
+
+The down arrow to the right of the formula text box brings up a drop-down list
+of all data objects currently loaded in this cell.  The data object you choose
+from this list becomes the current dataset for the cell (e.g., when you choose
+to export data from the File menu, this is the dataset that will be exported).
+
+You can remove the current dataset by clicking the formula bar's delete button
+("Del"), located just to the left of the formula text box.
+
+Clicking the formula bar's add button ("Add") will blank out the formula text
+box, allowing you to type in a new filename, URL, RMI address or formula.
+
+2.3.2.2 How To Enter Formulas
+
+To reference the data objects of a cell, keep in mind that each column is a
+letter (the first column is 'A', the second is 'B', and so on), and each row is
+a number (the first row is '1', the second is '2', and so on).  So, the cell on
+the top-left is A1, the cell on A1's right is B1, and the cell directly below
+A1 is A2, etc.  Each dataset of a cell has an associated variable name, which
+you can determine by examining the formula bar's drop-down list of datasets for
+that cell.  For example, the first data object you load into cell A1 will be
+called "A1d1", the second will be called "A1d2", etc.
+
+Type your formula in the formula text field.  Once you've typed in a formula,
+press Enter to apply the formula.  The new dataset will be added to the formula
+bar's drop-down list, and the dataset will appear in the cell.
+
+2.3.2.3 Formula Syntax
+
+Formulas are case insensitive.
+
+Any of the following can be used in formula construction:
+
+1) Formulas can use any of the basic operators:
+       + add,  - subtract,  * multiply,  / divide,  % remainder,  ^ power
+
+2) Formulas can use any of the following binary functions:
+       max, min, atan2, atan2Degrees
+
+3) Formulas can use any of the following unary functions:
+       abs, acos, acosDegrees, asin, asinDegrees, atan, atanDegrees, ceil,
+       cos, cosDegrees, domainMultiply, exp, floor, log, rint, round, sin,
+       sinDegrees, sqrt, tan, tanDegrees, negate
+
+4) Unary minus syntax (e.g., B2d1 * -A1d1) is supported.
+
+5) Derivatives are supported with the syntax:
+       d(DATA)/d(TYPE)
+            OR
+       derive(DATA, TYPE)
+   where DATA is a Function, and TYPE is the name of a RealType present in
+   the Function's domain.  This syntax calls Function's derivative() method
+   with an error_type of Data.NO_ERRORS.
+
+6) Function evaluation is supported with the syntax:
+       DATA1(DATA2)
+            OR
+       (DATA1)(DATA2)
+   where DATA1 is a Function and DATA2 is a Real or a RealTuple.
+   This syntax calls Function's evaluate() method.
+
+7) You can obtain an individual sample from a Field with the syntax:
+       DATA[N]
+   where DATA is the Field, and N is a literal integer.
+   Use DATA[0] for the first sample of DATA.
+   This syntax calls Field's getSample() method.
+
+8) You can obtain one component of a Tuple with the syntax:
+       DATA.N
+   where DATA is a Tuple and N is a literal integer.
+   Use DATA.0 for the first Tuple component of DATA.
+   This syntax calls Tuple's getComponent() method.
+
+9) You can extract part of a field with the syntax:
+      extract(DATA, N)
+   where DATA is a Field and N is a literal integer.
+   This syntax calls Field's extract() method.
+
+10) You can combine multiple fields with the syntax:
+       combine(DATA1, DATA2, ..., DATAN)
+    where DATA1 through DATAN are Fields.
+    This syntax calls FieldImpl's combine() method.
+
+11) You can perform a domain factoring with the syntax:
+       domainFactor(DATA, TYPE)
+    where DATA is a FieldImpl, and TYPE is the name of a RealType present
+    in the FieldImpl's domain.
+    This syntax calls FieldImpl's domainFactor() method.
+
+2.3.2.4 Linking to External Java Code
+
+You can link to an external Java method with the syntax:
+
+    link(package.Class.Method(DATA1, DATA2, ..., DATAN))
+
+where package.Class.Method is the fully qualified method name and DATA1
+through DATAN are each Data objects or RealType objects.
+
+Keep the following points in mind when writing an external Java method
+that you wish to link to the SpreadSheet:
+
+1) The signature of the linked method must be public and static and must return
+   a Data object.  In addition, the class to which the method belongs must be
+   public.  The method must have only Data and RealType parameters (if any).
+
+2) The method can contain one array argument (Data[] or RealType[]).  In this
+   way, a linked method can support a variable number of arguments.  For
+   example, a method with the signature "public static Data max(Data[] d)"
+   that is part of a class called Util could be linked into a SpreadSheet cell
+   with any number of arguments; e.g.,
+       link(Util.max(A1d1, A2d1))
+       link(Util.max(A2d1, C3d2, B1d4, A1d2))
+   would both be correct references to the max method.
+
+2.3.2.5 Examples of Valid Formulas
+
+Here are some examples of valid formulas for cell A1:
+    sqrt(A2d1 + B2d2^5 - min(B1d1, -C1d1))
+    d(A2d1 + A2d2)/d(ImageElement)
+    A2d1(A3d1)
+    C2d2.6[0]
+    (B1d2 * C1d1)(A3d4).1
+    C2d10 - 5*link(com.happyjava.vis.Linked.crunch(A6d1, C3d11, B5d15))
+
+2.4 Remote Collaboration
+
+2.4.1 Creating a SpreadSheet RMI server
+
+The first step in collaboration is to create a SpreadSheet RMI server.
+To launch the SpreadSheet in collaborative mode, type:
+
+    java -mx64m visad.ss.SpreadSheet -server name
+
+where "name" is the desired name for the RMI server.  If the server is created
+successfully, the title bar will contain the server name in parentheses.
+
+Once your SpreadSheet is operating as an RMI server, other SpreadSheets can
+work with it collaboratively.
+
+2.4.2 Sharing individual SpreadSheet cells
+
+Any VisAD SpreadSheet has the capability to import data objects from an RMI
+server.  Simply type the RMI address into the SpreadSheet's formula bar.
+The format of the RMI address is:
+
+    rmi://rmi.address/name/data
+
+where "rmi.address" is the IP address of the RMI server, "name" is the name of
+the RMI server, and "data" is the name of the data object desired.
+
+For example, suppose that the machine at address www.ssec.wisc.edu is running
+an RMI server called "VisADServ" using a SpreadSheet with two cells, A1 and B1.
+A SpreadSheet on another machine could import data from cell B1 of VisADServ
+by typing the following RMI address in the formula bar:
+
+    rmi://www.ssec.wisc.edu/VisADServ/B1d1
+
+Just like file names, URLs, and formulas, the SpreadSheet will load the data,
+showing the data box if the import is successful, or displaying error messages
+within the cell if there is a problem.
+
+2.4.3 Cloning entire SpreadSheets
+
+The VisAD SpreadSheet also allows for a more powerful form of collaboration:
+the cloning of entire SpreadSheets from a SpreadSheet RMI server.  To clone a
+SpreadSheet RMI server, type:
+
+    java -mx64m visad.ss.SpreadSheet -client rmi.address/name
+
+Where "rmi.address" is the IP address of the RMI server and "name" is the RMI
+server's name.  The resulting SpreadSheet will have the same cell layout as
+the SpreadSheet RMI server and the same data with the same mappings.  In
+addition, it will be linked so that any changes to the SpreadSheet will be
+propagated to the server and all its clones.
+
+Note that if a SpreadSheet RMI server does not support Java3D, none of its
+clones will be able to either.  Thus, for maximum functionality, it is best
+to make sure that the machine chosen to be the RMI server supports Java3D.
+
+2.4.4 Creating a SpreadSheet slave
+
+SpreadSheet clones need not re-render the server's data locally.
+Alternatively, the clone can be a slave, receiving image data from the server
+and sending mouse events back to the server so that users can still interact
+with the displays.  To launch a SpreadSheet clone as a slave, type:
+
+    java -mx64m visad.ss.SpreadSheet -slave rmi.address/name
+
+Note that the command line is the same as a regular clone except that the
+parameter is "-slave" instead of "-client".  Some features that are available
+to a regular clone are disabled for a slave, since only images of the data are
+sent to the slave, not the data itself.
+
+Slaves have a very slow update rate (taking a snapshot of a Java3D display on
+the server is very slow, and each snapshot must then be sent across the
+network). However, slaves are very useful for visualizing 3-D displays on a
+machine that does not have Java3D, and they also conserve large amounts of
+memory, since large datasets are not sent to the slave.
+
+2.5 Undocumented Features
+
+Obviously, if they're undocumented, you won't find them in this README!
+However, creating the javadoc for the visad.ss package should help in
+deciphering it, since the source is liberally commented.  You may also wish to
+create the javadoc for visad.formula, a package that is heavily used by the
+SpreadSheet.
+
+In addition, you can obtain help with the SpreadSheet's command line options by
+using the "-help" command line option.
+
+3. Known Bugs
+
+The following bugs have been discovered:
+
+1) On certain machine configurations, the SpreadSheet may sometimes lock up
+   on startup (or when a toolbar button first becomes grayed out) due to a
+   MediaTracker bug (#4332685). Try running the SpreadSheet with a different
+   number of rows and columns on startup. If you still have trouble, you can
+   use the "-bugfix" command line flag to disable the SpreadSheet's toolbar.
+   This workaround will keep the SpreadSheet from locking up on startup, but
+   you will not have the convenience of the toolbar. Of course, all
+   functionality is still accessible from the menus.
+
+2) Due to a workaround to improve the functionality of the formula bar, the
+   backspace key sometimes causes two characters to be deleted from the formula
+   instead of one.
+
+3) When importing certain netCDF data sets, a series of errors beginning with
+   "Couldn't decode attribute" may be displayed.  These are warnings the netCDF
+   loader prints about unit types.  The SpreadSheet will still import the
+   netCDF data set correctly (i.e., these warnings can be safely ignored).
+
+4) With JDK 1.2 under Windows, the first time a data set is imported, an error
+   beginning with "A nonfatal internal JIT (3.00.078(x)) error 'regvar' has
+   occurred" is displayed.  This error occurs whenever a VisAD application
+   makes use of the visad.data.DefaultFamily.open() method, and is a problem
+   with the Symantec JIT compiler for Windows.  This error is harmless and data
+   sets are still imported correctly (i.e., ignore this error message).
+
+   This error no longer appears in JDK 1.3, since the JVM no longer uses the
+   Symantec JIT compiler, but instead uses Sun's Hotspot compiler.
+
+5) The SpreadSheet may not import certain data sets correctly, due to
+   incomplete implementations in VisAD file adapter forms.
+
+If you find a bug in the SpreadSheet user interface not listed above,
+please send e-mail to visad-list at ssec.wisc.edu describing the problem,
+preferably with a detailed description of how to recreate the problem.
+
+If you have any suggestions for features that you would find useful,
+please send e-mail to visad-list at ssec.wisc.edu describing the feature.
diff --git a/visad/ss/SSCellChangeEvent.java b/visad/ss/SSCellChangeEvent.java
new file mode 100644
index 0000000..f70ace7
--- /dev/null
+++ b/visad/ss/SSCellChangeEvent.java
@@ -0,0 +1,100 @@
+//
+// SSCellChangeEvent.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+/**
+ * Event class for SSCell changes.
+ */
+public class SSCellChangeEvent {
+
+  /**
+   * Indicates that the cell's data has changed.
+   */
+  public static int DATA_CHANGE = 0;
+
+  /**
+   * Indicates that the cell's display has changed.
+   */
+  public static int DISPLAY_CHANGE = 1;
+
+  /**
+   * Indicates that the cell's dimension has changed.
+   */
+  public static int DIMENSION_CHANGE = 2;
+
+  /**
+   * The cell that changed.
+   */
+  private BasicSSCell SSCell;
+
+  /**
+   * The type of change that occurred.
+   */
+  private int ChangeType;
+
+  /**
+   * If data changed, the variable name of that data.
+   */
+  private String VarName;
+
+  /**
+   * Constructs an SSCellChangeEvent.
+   */
+  public SSCellChangeEvent(BasicSSCell ssCell, int changeType) {
+    this(ssCell, changeType, null);
+  }
+
+  public SSCellChangeEvent(BasicSSCell ssCell, int changeType,
+    String varName)
+  {
+    SSCell = ssCell;
+    ChangeType = changeType;
+    VarName = varName;
+  }
+
+  /**
+   * Gets the cell that changed.
+   */
+  public BasicSSCell getSSCell() {
+    return SSCell;
+  }
+
+  /**
+   * Gets the type of change that occurred.
+   */
+  public int getChangeType() {
+    return ChangeType;
+  }
+
+  /**
+   * Gets the variable name for the data that has changed.
+   */
+  public String getVariableName() {
+    return VarName;
+  }
+
+}
diff --git a/visad/ss/SSCellData.java b/visad/ss/SSCellData.java
new file mode 100644
index 0000000..68d5986
--- /dev/null
+++ b/visad/ss/SSCellData.java
@@ -0,0 +1,333 @@
+//
+// SSCellData.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+import java.rmi.RemoteException;
+import visad.*;
+import visad.formula.*;
+import visad.util.*;
+
+/**
+ * Class for encapsulating all needed information
+ * about a Data object present in a BasicSSCell.
+ */
+public class SSCellData {
+
+  // --- FIELDS ---
+
+  /**
+   * Associated spreadsheet cell for the data.
+   */
+  BasicSSCell ssCell;
+
+  /**
+   * The id number the Data object.
+   */
+  private int id;
+
+  /**
+   * The DataReference that points to the actual Data.
+   */
+  private DataReferenceImpl ref;
+
+  /**
+   * The ConstantMaps associated with the reference.
+   */
+  private ConstantMap[] cmaps;
+
+  /**
+   * Remote copy of the DataReference.
+   */
+  private RemoteDataReferenceImpl remoteRef;
+
+  /**
+   * The Data object's source, in string form.
+   */
+  private String source;
+
+  /**
+   * The Data object's source type.
+   */
+  private int type;
+
+  /**
+   * The name of the Data object's associated spreadsheet cell.
+   */
+  private String cellName;
+
+  /**
+   * The variable name of the Data object.
+   */
+  private String varName;
+
+  /**
+   * Errors encountered when computing the Data object.
+   */
+  private String[] errors;
+
+  /**
+   * The formula manager of the data's spreadsheet cell.
+   */
+  private FormulaManager fm;
+
+  /**
+   * Whether other data depends on this data.
+   */
+  boolean othersDepend;
+
+  /**
+   * VisAD Cell for monitoring local data changes.
+   */
+  SSCellImpl cell;
+
+
+  // --- CONSTRUCTORS ---
+
+  /**
+   * Constructs a new SSCellData object, for encapsulating
+   * a Data object and related information.
+   */
+  public SSCellData(int id, BasicSSCell ssCell, DataReferenceImpl ref,
+    ConstantMap[] cmaps, String source, int type, boolean checkErrors)
+    throws VisADException, RemoteException
+  {
+    this.ssCell = ssCell;
+    this.id = id;
+    this.ref = (DataReferenceImpl) ref;
+    this.remoteRef = new RemoteDataReferenceImpl(ref);
+    this.source = source;
+    this.type = type;
+    this.cellName = ssCell.getName();
+    this.varName = cellName + "d" + id;
+    this.errors = new String[0];
+    this.othersDepend = false;
+    this.fm = ssCell.getFormulaManager();
+
+    // set variable name's reference in formula manager database
+    fm.setReference(varName, ref);
+    if (this.id == 1) {
+      // make CELL default to CELLd1
+      fm.setReference(cellName, ref);
+    }
+
+    // detect changes to the data
+    this.cell = new SSCellImpl(this, ref, varName, checkErrors);
+  }
+
+
+  // --- ACCESSORS ---
+
+  /**
+   * Gets the ID number.
+   */
+  public int getId() {
+    return id;
+  }
+
+  /**
+   * Gets the Data object.
+   */
+  public Data getData() {
+    return ref.getData();
+  }
+
+  /**
+   * Gets the DataReference pointing to the data. Changes to the
+   * reference's data automatically propagate to all linked cells.
+   */
+  public DataReferenceImpl getReference() {
+    return ref;
+  }
+
+  /**
+   * Gets the ConstantMaps associated with the reference.
+   */
+  public ConstantMap[] getConstantMaps() {
+    return cmaps;
+  }
+
+  /**
+   * Gets the remote copy of the DataReference.
+   */
+  public RemoteDataReferenceImpl getRemoteReference() {
+    return remoteRef;
+  }
+
+  /**
+   * Gets the source of the data, in String form.
+   */
+  public String getSource() {
+    return source;
+  }
+
+  /**
+   * Gets the source type of the data.
+   * @return Source type. Valid types are:
+   *         <UL>
+   *         <LI>BasicSSCell.UNKNOWN_SOURCE
+   *         <LI>BasicSSCell.DIRECT_SOURCE
+   *         <LI>BasicSSCell.URL_SOURCE
+   *         <LI>BasicSSCell.FORMULA_SOURCE
+   *         <LI>BasicSSCell.RMI_SOURCE
+   *         <LI>BasicSSCell.REMOTE_SOURCE
+   *         </UL>
+   */
+  public int getSourceType() {
+    return type;
+  }
+
+  /**
+   * Gets the variable name used for the data in the formula manager.
+   */
+  public String getVariableName() {
+    return varName;
+  }
+
+  /**
+   * Gets the errors encountered when generating the Data object.
+   */
+  public String[] getErrors() {
+    return errors;
+  }
+
+  /**
+   * Gets whether other data depends on this data.
+   */
+  public boolean othersDepend() {
+    return othersDepend;
+  }
+
+  /**
+   * Returns whether this data's cell has finished initializing.
+   */
+  public boolean isInited() {
+    return cell.isInited();
+  }
+
+
+  // --- MODIFIERS ---
+
+  /**
+   * Sets the data.
+   */
+  public void setData(Data data) throws VisADException, RemoteException {
+    setData(data, true);
+  }
+  
+  /**
+   * Sets the data, broadcasting data change notification if flag is set.
+   */
+  void setData(Data data, boolean notify)
+    throws VisADException, RemoteException
+  {
+    DataImpl d = data.local();
+    if (!notify) cell.skipNextNotify();
+    ref.setData(d);
+  }
+
+  /**
+   * Sets a single error for the Data object.
+   */
+  public void setError(String error) {
+    setErrors(new String[] {error}, true, true);
+  }
+
+  /**
+   * Sets the errors for the Data object.
+   */
+  public void setErrors(String[] errors) {
+    setErrors(errors, true, true);
+  }
+
+  /**
+   * Sets the errors for the Data object, notifying
+   * linked cells if notify flag is set.
+   */
+  void setErrors(String[] errors, boolean notify) {
+    setErrors(errors, notify, true);
+  }
+
+  /**
+   * Sets the errors for the Data object, notifying linked cells if
+   * notify flag is set, and updating display if update flag is set.
+   */
+  void setErrors(String[] errors, boolean notify, boolean update) {
+    if (Util.arraysEqual(this.errors, errors)) return;
+    this.errors = errors;
+    if (update) ssCell.updateDisplay();
+    if (notify) {
+      try {
+        ssCell.sendMessage(BasicSSCell.SET_ERRORS, varName,
+          DataUtility.stringsToTuple(errors, BasicSSCell.DEBUG));
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+    }
+  }
+
+  /**
+   * Sets whether others depend on this data.
+   */
+  public void setDependencies(Real real) {
+    othersDepend = real.equals(SSCellImpl.TRUE);
+  }
+
+  /**
+   * Stops monitoring the data for changes.
+   */
+  public void destroy() {
+    // set data's variable to null in formula manager database
+    try {
+      fm.setThing(varName, null);
+      if (id == 1) fm.setThing(cellName, null);
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+
+    // stop local data change monitoring
+    try {
+      cell.removeAllReferences();
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    cell.stop();
+    cell = null;
+
+    // broadcast data change event
+    ssCell.notifySSCellListeners(SSCellChangeEvent.DATA_CHANGE, varName);
+  }
+
+}
diff --git a/visad/ss/SSCellImpl.java b/visad/ss/SSCellImpl.java
new file mode 100644
index 0000000..305638b
--- /dev/null
+++ b/visad/ss/SSCellImpl.java
@@ -0,0 +1,208 @@
+//
+// SSCellImpl.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+import java.rmi.RemoteException;
+import java.util.Vector;
+import visad.*;
+import visad.formula.*;
+import visad.util.DataUtility;
+
+/**
+ * A VisAD Cell which updates an SSCell
+ * when one of its Data objects change.
+ */
+public class SSCellImpl extends CellImpl {
+
+  /**
+   * VisAD Data object representing boolean true.
+   */
+  public static final Real TRUE = new Real(1.0);
+
+  /**
+   * VisAD Data object representing boolean false.
+   */
+  public static final Real FALSE = new Real(0.0);
+
+  /**
+   * SSCellData that this cell updates when its data changes.
+   */
+  private SSCellData cellData;
+
+  /**
+   * Data reference that this cell monitors for changes.
+   */
+  private DataReferenceImpl ref;
+
+  /**
+   * Name of the linked SSCell.
+   */
+  private String cellName;
+
+  /**
+   * Variable name of the linked data.
+   */
+  private String varName;
+
+  /**
+   * Errors generated from computing the linked data.
+   */
+  private String[] errors;
+
+  /**
+   * Formula manager for the linked SSCell.
+   */
+  private FormulaManager fm;
+
+  /**
+   * Flag marking whether this cell has finished initializing.
+   */
+  private boolean inited = false;
+
+  /**
+   * Whether this cell should broadcast data changes.
+   */
+  private int skipNotify = 1; // skip notify from initial doAction() call
+
+  /**
+   * Whether this cell should check for updated data computation errors.
+   */
+  private int skipErrors = 0;
+
+  /**
+   * Constructs an SSCellImpl.
+   */
+  public SSCellImpl(SSCellData cellData, DataReferenceImpl ref, String varName,
+    boolean checkErrors) throws VisADException, RemoteException
+  {
+    this.cellData = cellData;
+    this.ref = ref;
+    cellName = cellData.ssCell.getName();
+    this.varName = varName;
+    fm = cellData.ssCell.getFormulaManager();
+    inited = false;
+    if (!checkErrors) skipNextErrors();
+    addReference(ref);
+  }
+
+  /**
+   * Returns whether this cell has finished initializing.
+   */
+  public boolean isInited() {
+    return inited;
+  }
+
+  /**
+   * Returns the errors relevant to the linked data.
+   */
+  String[] getErrors() {
+    return errors;
+  }
+
+  /**
+   * Disables broadcasting of data changes during next data update.
+   */
+  void skipNextNotify() {
+    skipNotify++;
+  }
+
+  /**
+   * Disables detection of errors during next data update.
+   */
+  void skipNextErrors() {
+    skipErrors++;
+  }
+
+  /**
+   * Invoked when linked data changes.
+   */
+  public synchronized void doAction() {
+    // get new data
+    Data data = ref.getData();
+
+    // broadcast new errors, if any
+    if (skipErrors == 0) {
+      cellData.setErrors(fm.getErrors(varName), true, false);
+    }
+    else skipErrors--;
+
+    if (data != null) {
+      // update cell display
+      cellData.ssCell.updateDisplay(true);
+
+      // add data's ScalarTypes to FormulaManager variable registry
+      Vector v = new Vector();
+      try {
+        DataUtility.getScalarTypes(new Data[] {data}, v, false, true);
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      int len = v.size();
+      for (int i=0; i<len; i++) {
+        ScalarType st = (ScalarType) v.elementAt(i);
+        if (st instanceof RealType) {
+          RealType rt = (RealType) st;
+          try {
+            fm.setThing(rt.getName(), VRealType.get(rt));
+          }
+          catch (VisADException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+          }
+          catch (RemoteException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+          }
+        }
+      }
+
+      // notify linked cells of data change
+      if (skipNotify == 0) {
+        try {
+          cellData.ssCell.sendMessage(BasicSSCell.UPDATE_DATA, varName, data);
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+        }
+      }
+      else skipNotify--;
+    }
+    else cellData.ssCell.updateDisplay();
+
+    // update dependencies for all cells
+    cellData.ssCell.updateDependencies();
+
+    // broadcast data change event
+    cellData.ssCell.notifySSCellListeners(
+      SSCellChangeEvent.DATA_CHANGE, varName);
+
+    inited = true;
+  }
+
+}
diff --git a/visad/ss/SSCellListener.java b/visad/ss/SSCellListener.java
new file mode 100644
index 0000000..51b4676
--- /dev/null
+++ b/visad/ss/SSCellListener.java
@@ -0,0 +1,39 @@
+//
+// SSCellListener.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+/**
+ * Interface for listeners of SSCell changes.
+ */
+public interface SSCellListener {
+
+  /**
+   * Handles an SSCell data, display or dimension change.
+   */
+  void ssCellChanged(SSCellChangeEvent e);
+
+}
diff --git a/visad/ss/SSLayout.java b/visad/ss/SSLayout.java
new file mode 100644
index 0000000..477fb52
--- /dev/null
+++ b/visad/ss/SSLayout.java
@@ -0,0 +1,160 @@
+//
+// SSLayout.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+import java.awt.*;
+import javax.swing.*;
+
+/**
+ * SSLayout is the layout manager for the SpreadSheet's cells and their labels.
+ * It sets up components in a rectangular grid similar to GridLayout, but uses
+ * the components' preferred sizes to allow for variable-sized cells.
+ */
+public class SSLayout implements LayoutManager {
+
+  /**
+   * Number of columns components should form.
+   */
+  private int NumCols;
+
+  /**
+   * Number of rows components should form.
+   */
+  private int NumRows;
+
+  /**
+   * Space between columns.
+   */
+  private int ColSpace;
+
+  /**
+   * Space between rows.
+   */
+  private int RowSpace;
+
+  /**
+   * Constructs an SSLayout.
+   */
+  public SSLayout(int ncol, int nrow, int wspace, int hspace) {
+    NumCols = ncol;
+    NumRows = nrow;
+    ColSpace = wspace;
+    RowSpace = hspace;
+  }
+
+  /**
+   * Adds the necessary number of elements to the Component array.
+   */
+  private Component[] fillOut(Component[] c) {
+    // warn the user
+    System.err.println("Warning: spreadsheet cell layout is corrupted");
+
+    // add blank components to the layout
+    Component[] nc = new Component[NumCols * NumRows];
+    System.arraycopy(c, 0, nc, 0, c.length);
+    for (int i=c.length; i<nc.length; i++) {
+      nc[i] = new JComponent() {
+        public void paint(Graphics g) { }
+      };
+    }
+    return nc;
+  }
+
+  /**
+   * Lays out the components.
+   */
+  public void layoutContainer(Container parent) {
+    // get parent's components
+    Component[] c = parent.getComponents();
+    if (c.length < NumCols * NumRows) c = fillOut(c);
+
+    // get preferred widths
+    int[] pw = new int[NumCols];
+    for (int i=0; i<NumCols; i++) {
+      pw[i] = c[i].getPreferredSize().width;
+    }
+
+    // get preferred heights
+    int[] ph = new int[NumRows];
+    for (int j=0; j<NumRows; j++) {
+      ph[j] = c[NumCols * j].getPreferredSize().height;
+    }
+
+    // lay out all components
+    int sy = 0;
+    for (int j=0; j<NumRows; j++) {
+      int sx = 0;
+      for (int i=0; i<NumCols; i++) {
+        c[NumCols * j + i].setBounds(sx, sy, pw[i], ph[j]);
+        sx += pw[i] + ColSpace;
+      }
+      sy += ph[j] + RowSpace;
+    }
+  }
+
+  /**
+   * Gets minimum layout size.
+   */
+  public Dimension minimumLayoutSize(Container parent) {
+    return preferredLayoutSize(parent);
+  }
+
+  /**
+   * Gets preferred layout size.
+   */
+  public Dimension preferredLayoutSize(Container parent) {
+    // get parent's components
+    Component[] c = parent.getComponents();
+    if (c.length < NumCols * NumRows) c = fillOut(c);
+
+    // get preferred widths total
+    int pwt = -ColSpace;
+    for (int i=0; i<NumCols; i++) {
+      pwt += c[i].getPreferredSize().width + ColSpace;
+    }
+
+    // get preferred heights total
+    int pht = -RowSpace;
+    for (int j=0; j<NumRows; j++) {
+      pht += c[NumCols * j].getPreferredSize().height + RowSpace;
+    }
+
+    // return final layout size
+    return new Dimension(pwt, pht);
+  }
+
+  /**
+   * Not used by SSLayout.
+   */
+  public void addLayoutComponent(String name, Component comp) { }
+
+  /**
+   * Not used by SSLayout.
+   */
+  public void removeLayoutComponent(Component comp) { }
+
+}
diff --git a/visad/ss/SpreadSheet.java b/visad/ss/SpreadSheet.java
new file mode 100644
index 0000000..aef5634
--- /dev/null
+++ b/visad/ss/SpreadSheet.java
@@ -0,0 +1,3914 @@
+//
+// SpreadSheet.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.ss;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.print.*;
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.rmi.*;
+import java.rmi.registry.*;
+import java.util.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import visad.*;
+import visad.data.Form;
+import visad.data.netcdf.Plain;
+import visad.data.tiff.TiffForm;
+import visad.data.visad.VisADForm;
+import visad.formula.*;
+import visad.util.*;
+
+/**
+ * SpreadSheet is a user interface for VisAD that supports
+ * multiple 3-D displays (FancySSCells).
+ */
+public class SpreadSheet extends GUIFrame implements AdjustmentListener,
+  DisplayListener, KeyListener, ItemListener, MouseListener,
+  MouseMotionListener, SSCellListener
+{
+
+  /**
+   * Starting width of the application, in percentage of screen size.
+   */
+  protected static final int WIDTH_PERCENT = 60;
+
+  /**
+   * Starting width of the application, in percentage of screen size.
+   */
+  protected static final int HEIGHT_PERCENT = 80;
+
+  /**
+   * Minimum VisAD display width, including display border.
+   */
+  protected static final int MIN_VIS_WIDTH = 120;
+
+  /**
+   * Minimum VisAD display height, including display border.
+   */
+  protected static final int MIN_VIS_HEIGHT = 120;
+
+  /**
+   * Default VisAD display width.
+   */
+  protected static final int DEFAULT_VIS_WIDTH = 250;
+
+  /**
+   * Default VisAD display height.
+   */
+  protected static final int DEFAULT_VIS_HEIGHT = 250;
+
+  /**
+   * Spreadsheet cell letter order.
+   */
+  protected static final String Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+  /**
+   * Vertical cell label's width.
+   */
+  protected static final int LABEL_WIDTH = 30;
+
+  /**
+   * Horizontal cell label's height.
+   */
+  protected static final int LABEL_HEIGHT = 20;
+
+  /**
+   * Whether connection status messages are printed about clones.
+   */
+  protected static final boolean SHOW_CONNECT_MESSAGES = true;
+
+  /**
+   * Header for first line of spreadsheet files.
+   */
+  protected static final String SSFileHeader =
+    "# VisAD Visualization SpreadSheet spreadsheet file";
+
+  /**
+   * Argument classes for constructing an SSCell.
+   */
+  protected static final Class[] cellArgs = {
+    String.class, FormulaManager.class, RemoteServer.class,
+    boolean.class, String.class, Frame.class
+  };
+
+
+  /**
+   * Constructor used to create SSCells for SpreadSheets.
+   */
+  protected static Constructor cellConstructor;
+
+  /**
+   * Whether Java3D is possible on this JVM.
+   */
+  protected static boolean Possible3D;
+
+  /**
+   * Whether Java3D is enabled on this JVM.
+   */
+  protected static boolean CanDo3D;
+
+  /**
+   * Whether the HDF-5 native library is present on this JVM.
+   */
+  protected static boolean CanDoHDF5;
+
+  /**
+   * Whether this JVM supports saving JPEG images with JPEGImageEncoder.
+   */
+  protected static boolean CanDoJPEG;
+
+  /**
+   * Whether this JVM supports JPython scripting.
+   */
+  protected static boolean CanDoPython;
+
+  /**
+   * Whether spreadsheet should have toolbar buttons.
+   */
+  protected static boolean BugFix;
+
+
+  /**
+   * File dialog.
+   */
+  protected JFileChooser SSFileDialog;
+
+  /**
+   * Base title.
+   */
+  protected String bTitle;
+
+  /**
+   * Number of display columns.
+   */
+  protected int NumVisX;
+
+  /**
+   * Number of display rows.
+   */
+  protected int NumVisY;
+
+  /**
+   * Formula manager.
+   */
+  protected FormulaManager fm;
+
+
+  /**
+   * Server name, if any.
+   */
+  protected String serverName;
+
+  /**
+   * Server address for a cloned sheet, if any.
+   */
+  protected String cloneAddress;
+
+  /**
+   * Server for spreadsheet cells, if any.
+   */
+  protected RemoteServerImpl rsi = null;
+
+  /**
+   * Whether spreadsheet is a clone of another spreadsheet.
+   */
+  protected boolean IsRemote = false;
+
+  /**
+   * Whether spreadsheet is a slaved clone of another spreadsheet.
+   */
+  protected boolean IsSlave = false;
+
+  /**
+   * ID number for this collaborative spreadsheet.
+   */
+  protected double CollabID = 0;
+
+  /**
+   * Row and column information needed for spreadsheet cloning.
+   */
+  protected RemoteDataReference RemoteColRow;
+
+  /**
+   * Remote clone's copy of CanDo3D.
+   */
+  protected RemoteDataReference RemoteCanDo3D;
+
+
+  /**
+   * Flag marking whether spreadsheet's cells
+   * automatically switch dimensions when needed.
+   */
+  protected boolean AutoSwitch = true;
+
+  /**
+   * Flag marking whether spreadsheet's cells
+   * automatically detect mappings.
+   */
+  protected boolean AutoDetect = true;
+
+  /**
+   * Flag marking whether spreadsheet's cells
+   * automatically show controls.
+   */
+  protected boolean AutoShowControls = true;
+
+
+  /**
+   * Panel that contains actual VisAD displays.
+   */
+  protected Panel DisplayPanel;
+
+  /**
+   * Panel containing the scrolling pane.
+   */
+  protected JPanel ScrollPanel;
+
+  /**
+   * Scrolling pane, in case sheet gets too small.
+   */
+  protected ScrollPane SCPane;
+
+  /**
+   * View port for horizontal cell labels.
+   */
+  protected JViewport HorizLabels;
+
+  /**
+   * View port for vertical cell labels.
+   */
+  protected JViewport VertLabels;
+
+  /**
+   * Array of panels for horizontal labels.
+   */
+  protected JPanel[] HorizLabel;
+
+  /**
+   * Array of panels for vertical labels.
+   */
+  protected JPanel[] VertLabel;
+
+  /**
+   * Array of horizontal yellow sizing boxes.
+   */
+  protected JComponent[] HorizDrag;
+
+  /**
+   * Array of vertical yellow sizing boxes.
+   */
+  protected JComponent[] VertDrag;
+
+  /**
+   * Panel containing horizontal labels and sizing boxes.
+   */
+  protected JPanel HorizPanel;
+
+  /**
+   * Panel containing vertical labels and sizing boxes.
+   */
+  protected JPanel VertPanel;
+
+  /**
+   * Array of spreadsheet cells.
+   */
+  protected FancySSCell[][] DisplayCells = null;
+
+  /**
+   * Formula bar.
+   */
+  protected JComboBox FormulaBox;
+
+  /**
+   * Formula editor.
+   */
+  protected ComboBoxEditor FormulaEditor;
+
+  /**
+   * Formula text field.
+   */
+  protected JTextField FormulaText;
+
+  /**
+   * Formula action listener.
+   */
+  protected ActionListener FormulaListener;
+
+
+  /**
+   * Tool bar.
+   */
+  protected JToolBar Toolbar;
+
+  /**
+   * Submenus.
+   */
+  protected JMenu FileExport;
+   
+  /**
+   * Menu items.
+   */
+  protected JMenuItem FileSave1, FileSave2, FileSave3, FileSave4, FileSave5,
+    FileSnap, EditPaste, EditClear, CellDel, CellPrint, CellEdit, CellReset,
+    CellShow, LayAddCol, LayDelCol, LayDelRow;
+
+  /**
+   * Checkbox menu items.
+   */
+  protected JCheckBoxMenuItem CellDim3D3D, CellDim2D2D, CellDim2D3D,
+    AutoSwitchBox, AutoDetectBox, AutoShowBox;
+
+  /**
+   * Toolbar buttons.
+   */
+  protected JButton ToolSave, ToolPaste, Tool3D, Tool2D, ToolJ2D, ToolMap,
+    ToolShow, ToolReset, FormulaAdd, FormulaDel;
+
+
+  /**
+   * Column of currently selected cell.
+   */
+  protected int CurX = 0;
+
+  /**
+   * Row of currently selected cell.
+   */
+  protected int CurY = 0;
+
+  /**
+   * Contents of clipboard.
+   */
+  protected String Clipboard = null;
+
+  /**
+   * Current spreadsheet file.
+   */
+  protected File CurrentFile = null;
+
+  /**
+   * Object for preventing simultaneous GUI manipulation.
+   */
+  protected Object Lock = new Object();
+
+
+  /**
+   * Waits the specified number of milliseconds.
+   */
+  public static void snooze(long ms) {
+    try {
+      Thread.sleep(ms);
+    }
+    catch (InterruptedException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+  }
+
+  /**
+   * Gateway into VisAD Visualization SpreadSheet user interface.
+   */
+  public static void main(String[] argv) {
+    String usage = "\n" +
+      "Usage: java [-mx###m] visad.ss.SpreadSheet [cols rows]\n" +
+      "       [-file filename] [-gui] [-no3d] [-debug] [-bugfix]\n" +
+      "       [-server name] [-client address] [-slave address]\n\n" +
+      "###\n" +
+      "     Maximum megabytes of memory to use.\n" +
+      "cols\n" +
+      "     Number of columns in this SpreadSheet.\n" +
+      "rows\n" +
+      "     Number of rows in this SpreadSheet.\n" +
+      "-file filename\n" +
+      "     Load the given filename at launch. If file is a\n" +
+      "     spreadsheet file, the layout is configured accordingly.\n" +
+      "     If file is data, it is loaded into cell A1.\n" +
+      "-gui\n" +
+      "     Pop up an options window so that the user can\n" +
+      "     select SpreadSheet settings graphically.\n" +
+      "-no3d\n" +
+      "     Disable Java3D.\n" +
+      "-debug\n" +
+      "     Print stack traces for all errors.\n" +
+      "-bugfix\n" +
+      "     Disable toolbar. For some systems, will prevent\n" +
+      "     lockups on spreadsheet start.\n" +
+      "-server name\n" +
+      "     Initialize this SpreadSheet as an RMI server\n" +
+      "     with the given name.\n" +
+      "-client address\n" +
+      "     Initialize this SpreadSheet as a clone of\n" +
+      "     the server at the given RMI address.\n" +
+      "-slave address\n" +
+      "     Initialize this SpreadSheet as a slaved clone\n" +
+      "     of the server at the given RMI address.";
+    int cols = 2;
+    int rows = 2;
+    String dfile = null;
+    String servname = null;
+    String clonename = null;
+    boolean guiOptions = false;
+    int len = argv.length;
+    if (len > 0) {
+      int ix = 0;
+
+      // parse command line flags
+      while (ix < len) {
+        if (argv[ix].charAt(0) == '-') {
+          if (argv[ix].equals("-file")) {
+            if (ix < len - 1) dfile = argv[++ix];
+          }
+          else if (argv[ix].equals("-server")) {
+            if (clonename != null) {
+              System.out.println("A spreadsheet cannot be both a server " +
+                "and a clone!");
+              System.out.println(usage);
+              System.exit(3);
+            }
+            else if (ix < len - 1) servname = argv[++ix];
+            else {
+              System.out.println("You must specify a server name after " +
+                "the '-server' flag!");
+              System.out.println(usage);
+              System.exit(4);
+            }
+          }
+          else if (argv[ix].equals("-client") || argv[ix].equals("-slave")) {
+            if (servname != null) {
+              System.out.println("A spreadsheet cannot be both a server " +
+                "and a clone!");
+              System.out.println(usage);
+              System.exit(3);
+            }
+            else if (ix < len - 1) {
+              clonename = argv[ix + 1];
+              if (argv[ix].equals("-slave")) clonename = "slave:" + clonename;
+              ix++;
+            }
+            else {
+              System.out.println("You must specify a server after " +
+                "the '" + argv[ix] + "' flag!");
+              System.out.println(usage);
+              System.exit(5);
+            }
+          }
+          else if (argv[ix].equals("-gui")) guiOptions = true;
+          else if (argv[ix].equals("-bugfix")) BugFix = true;
+          else if (argv[ix].equals("-no3d")) BasicSSCell.disable3D();
+          else if (argv[ix].equals("-debug")) {
+            BasicSSCell.DEBUG = true;
+            FormulaVar.DEBUG = true;
+          }
+          else {
+            // unknown flag
+            if (!argv[ix].equals("-help")) {
+              System.out.println("Unknown option: " + argv[ix]);
+            }
+            System.out.println(usage);
+            System.exit(1);
+          }
+        }
+        else {
+          // parse number of rows and columns
+          boolean success = true;
+          if (ix < len - 1) {
+            try {
+              cols = Integer.parseInt(argv[ix]);
+              rows = Integer.parseInt(argv[ix + 1]);
+              ix++;
+              if (rows < 1 || cols < 1 || cols > Letters.length()) {
+                success = false;
+              }
+            }
+            catch (NumberFormatException exc) {
+              success = false;
+            }
+            if (!success) {
+              System.out.println("Invalid number of columns and rows: " +
+                argv[ix] + " x " + argv[ix + 1]);
+              System.out.println(usage);
+              System.exit(2);
+            }
+          }
+          else {
+            System.out.println("Unknown option: " + argv[ix]);
+            System.out.println(usage);
+            System.exit(1);
+          }
+        }
+        ix++;
+      }
+    }
+    final SpreadSheet ss = new SpreadSheet(WIDTH_PERCENT, HEIGHT_PERCENT,
+      cols, rows, servname, clonename, "VisAD SpreadSheet", null, guiOptions);
+    if (dfile != null) {
+      File f = new File(dfile);
+      String line = null;
+      try {
+        BufferedReader fin = new BufferedReader(new FileReader(f));
+        line = fin.readLine();
+        fin.close();
+      }
+      catch (IOException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+        System.out.println("Could not read file " + dfile);
+      }
+      if (line != null) {
+        final boolean ssfile = line.equals(SSFileHeader);
+        final String filename = dfile;
+        Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+          public void run() {
+            try {
+              if (ssfile) {
+                // file is a spreadsheet file
+                ss.openFile(filename);
+              }
+              else {
+                // file is a data file
+                ss.DisplayCells[0][0].addDataSource(filename);
+              }
+            }
+            catch (Exception exc) {
+              if (BasicSSCell.DEBUG) exc.printStackTrace();
+              System.out.println("Could not load file " + filename +
+                " into the SpreadSheet");
+            }
+          }
+        });
+      }
+    }
+  }
+
+
+  // --- CONSTRUCTORS ---
+
+  /**
+   * Constructor with option selection dialog at default values.
+   */
+  public SpreadSheet() {
+    this(WIDTH_PERCENT, HEIGHT_PERCENT, 2, 2, null, null,
+      "VisAD SpreadSheet", null, true);
+  }
+
+  /**
+   * Constructor with default formula manager and no option selection dialog.
+   */
+  public SpreadSheet(int sWidth, int sHeight, int cols, int rows,
+    String server, String clone, String sTitle)
+  {
+    this(sWidth, sHeight, cols, rows, server, clone, sTitle, null, false);
+  }
+
+  /**
+   * Constructor with no option selection dialog.
+   */
+  public SpreadSheet(int sWidth, int sHeight, int cols, int rows,
+    String server, String clone, String sTitle, FormulaManager fm)
+  {
+    this(sWidth, sHeight, cols, rows, server, clone, sTitle, fm, false);
+  }
+
+  /**
+   * Main constructor.
+   */
+  public SpreadSheet(int sWidth, int sHeight, int cols, int rows,
+    String server, String clone, String sTitle, FormulaManager fm,
+    boolean chooseOptions)
+  {
+    super(true);
+    bTitle = sTitle;
+    NumVisX = cols;
+    NumVisY = rows;
+    this.fm = fm;
+    Possible3D = BasicSSCell.possible3D();
+    CanDo3D = BasicSSCell.canDo3D();
+    MappingDialog.initDialog();
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        quitProgram();
+      }
+    });
+    setBackground(Color.white);
+
+    // parse clone address
+    boolean slave = clone != null && clone.startsWith("slave:");
+    if (slave) clone = clone.substring(6);
+    if (clone != null) {
+      int slash = clone.lastIndexOf("/");
+      if (slash < 0) slash = clone.lastIndexOf(":");
+      server = clone.substring(slash + 1);
+      clone = clone.substring(0, slash);
+    }
+    serverName = server;
+    cloneAddress = clone;
+    IsSlave = slave;
+
+    // test whether HDF-5 native library is present
+    CanDoHDF5 = Util.canDoHDF5();
+    if (!CanDoHDF5 && BasicSSCell.DEBUG) {
+      System.err.println("Warning: HDF-5 library not found");
+    }
+
+    // test whether JPEG codec is present
+    CanDoJPEG = Util.canDoJPEG();
+    if (!CanDoJPEG && BasicSSCell.DEBUG) {
+      System.err.println("Warning: JPEG codec not found");
+    }
+
+    // test whether JPython is present
+    CanDoPython = Util.canDoPython();
+    if (!CanDoPython && BasicSSCell.DEBUG) {
+      System.err.println("Warning: JPython not found");
+    }
+
+    // create file chooser dialog
+    SSFileDialog = new JFileChooser(System.getProperty("user.dir"));
+    SSFileDialog.addChoosableFileFilter(
+      new ExtensionFileFilter("ss", "SpreadSheet files"));
+
+    if (chooseOptions) {
+      // get settings from option selection dialog
+      getOptions(NumVisX, NumVisY, serverName, cloneAddress, IsSlave);
+    }
+    clone = cloneAddress == null ? null : cloneAddress + "/" + serverName;
+    server = cloneAddress == null ? serverName : null;
+
+    // determine information for spreadsheet cloning
+    RemoteServer rs = null;
+    String[][] cellNames = null;
+    if (clone != null) {
+      // CLIENT: initialize
+
+      // connect to server
+      boolean success = true;
+      if (SHOW_CONNECT_MESSAGES) {
+        System.out.print("Connecting to " + clone + " ");
+      }
+
+      // connection loop
+      while (cellNames == null && success) {
+        // wait a second before trying to connect
+        snooze(1000);
+        if (SHOW_CONNECT_MESSAGES) System.out.print(".");
+
+        try {
+          // look up server
+          rs = (RemoteServer) Naming.lookup("//" + clone);
+
+          // determine whether server supports Java3D
+          RemoteCanDo3D = rs.getDataReference("CanDo3D");
+          Real bit = (Real) RemoteCanDo3D.getData();
+          if (bit.getValue() == 0) {
+            CanDo3D = false;
+            BasicSSCell.disable3D();
+          }
+
+          // extract cell name information
+          RemoteColRow = rs.getDataReference("ColRow");
+          cellNames = getNewCellNames();
+        }
+        catch (UnmarshalException exc) {
+          // fatal RMI error, probably a version difference; display error box
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Unable to clone the spreadsheet at " + clone +
+            ". The server is using an incompatible version of Java", null,
+            "Failed to clone spreadsheet");
+          success = false;
+        }
+        catch (MalformedURLException exc) {
+          // server name is invalid; display error box
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Unable to clone the spreadsheet at " + clone +
+            ". The server name is not valid", null,
+            "Failed to clone spreadsheet");
+          success = false;
+        }
+        catch (VisADException exc) {
+          // fatal error of some other type; display error box
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Unable to clone the spreadsheet at " + clone +
+            ". An error occurred while downloading the necessary data", exc,
+            "Failed to clone spreadsheet");
+          success = false;
+        }
+        catch (NotBoundException exc) {
+          // server is not ready yet; try again
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+        }
+        catch (NullPointerException exc) {
+          // server is not ready yet; try again
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          // server is not ready yet; try again
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+        }
+      }
+
+      if (success) {
+        if (SHOW_CONNECT_MESSAGES) System.out.println(" done");
+        bTitle = bTitle + " [" + (IsSlave ? "slaved" : "collaborative") +
+          " mode: " + clone + "]";
+        IsRemote = true;
+      }
+      else {
+        if (SHOW_CONNECT_MESSAGES) System.out.println(" failed");
+        IsSlave = false;
+        rs = null;
+      }
+    }
+
+    // set up the content pane
+    JPanel pane = new JPanel();
+    pane.setBackground(Color.white);
+    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+    setContentPane(pane);
+
+    // set up file menu
+    addMenuItem("File", "Import data...", "loadDataSet", 'i');
+    FileExport = addSubMenu("File", "Export data", 'e', false);
+    FileSave1 = addMenuItem("Export data", "netCDF...",
+      "exportDataSetNetcdf", 'n', true);
+    FileSave3 = addMenuItem("Export data", "HDF-5...",
+      "exportDataSetHDF5", 'h', CanDoHDF5);
+    FileSave4 = addMenuItem("Export data", "TIFF...",
+      "exportDataSetTIFF", 't', true);
+    FileSave2 = addMenuItem("Export data", "Serialized...",
+      "exportDataSetSerial", 's', true);
+    FileSave5 = addMenuItem("Export data", "Binary...",
+      "exportDataSetBinary", 'b', true);
+    addMenuSeparator("File");
+    FileSnap = addMenuItem("File", "Take JPEG snapshot...",
+      "captureImageJPEG", 'j', false);
+    addMenuSeparator("File");
+    addMenuItem("File", "Exit", "quitProgram", 'x');
+
+    // set up edit menu
+    addMenuItem("Edit", "Cut", "cutCell", 't', !IsRemote);
+    addMenuItem("Edit", "Copy", "copyCell", 'c', !IsRemote);
+    EditPaste = addMenuItem("Edit", "Paste", "pasteCell", 'p', false);
+    EditClear = addMenuItem("Edit", "Clear", "clearCell", 'l', false);
+
+    // set up setup menu
+    addMenuItem("Setup", "New spreadsheet file", "newFile", 'n');
+    addMenuItem("Setup", "Open spreadsheet file...", "openFile", 'o',
+      !IsRemote);
+    addMenuItem("Setup", "Save spreadsheet file", "saveFile", 's', !IsRemote);
+    addMenuItem("Setup", "Save spreadsheet file as...", "saveAsFile", 'a',
+      !IsRemote);
+
+    // set up cell menu
+    CellDim3D3D = new JCheckBoxMenuItem("3-D (Java3D)", CanDo3D);
+    addMenuItem("Cell", CellDim3D3D, "setDim3D", '3', CanDo3D);
+    CellDim2D2D = new JCheckBoxMenuItem("2-D (Java2D)", !CanDo3D);
+    addMenuItem("Cell", CellDim2D2D, "setDimJ2D", 'j', true);
+    CellDim2D3D = new JCheckBoxMenuItem("2-D (Java3D)", false);
+    addMenuItem("Cell", CellDim2D3D, "setDim2D", '2', CanDo3D);
+    addMenuSeparator("Cell");
+    addMenuItem("Cell", "Add data object", "formulaAdd", 'a');
+    CellDel = addMenuItem("Cell", "Delete data object",
+      "formulaDel", 'd', false);
+    addMenuSeparator("Cell");
+    CellPrint = addMenuItem("Cell", "Print cell...",
+      "printCurrentCell", 'p', false);
+    addMenuSeparator("Cell");
+    CellEdit = addMenuItem("Cell", "Edit mappings...",
+      "createMappings", 'e', false);
+    CellReset = addMenuItem("Cell", "Reset orientation",
+      "resetOrientation", 'r', false);
+    CellShow = addMenuItem("Cell", "Show controls",
+      "showControls", 's', false);
+
+    // set up layout menu
+    LayAddCol = addMenuItem("Layout", "Add column", "addColumn", 'c');
+    addMenuItem("Layout", "Add row", "addRow", 'r');
+    LayDelCol = addMenuItem("Layout", "Delete column",
+      "deleteColumn", 'l', NumVisX > 1);
+    LayDelRow = addMenuItem("Layout", "Delete row",
+      "deleteRow", 'w', NumVisY > 1);
+    addMenuSeparator("Layout");
+    addMenuItem("Layout", "Tile cells", "tileCells", 't');
+
+    // set up options menu
+    if (!CanDo3D) AutoSwitch = false;
+    AutoSwitchBox = new JCheckBoxMenuItem("Auto-switch to 3-D",
+      AutoSwitch && !IsRemote);
+    addMenuItem("Options", AutoSwitchBox,
+      "optionsSwitch", '3', CanDo3D && !IsRemote);
+    AutoDetectBox = new JCheckBoxMenuItem("Auto-detect mappings",
+      AutoDetect && !IsRemote);
+    addMenuItem("Options", AutoDetectBox, "optionsDetect", 'm', !IsRemote);
+    AutoShowBox = new JCheckBoxMenuItem("Auto-display controls",
+      AutoShowControls && !IsSlave);
+    addMenuItem("Options", AutoShowBox, "optionsDisplay", 'c', !IsSlave);
+
+    // set up toolbar
+    if (!BugFix) {
+      Toolbar = new JToolBar();
+      Toolbar.setBackground(Color.lightGray);
+      Toolbar.setBorder(new EtchedBorder());
+      Toolbar.setFloatable(false);
+      pane.add(Toolbar);
+
+      // file menu toolbar icons
+      addToolbarButton("open", "Import data", "loadDataSet", true, Toolbar);
+      ToolSave = addToolbarButton("save", "Export data to netCDF",
+        "exportDataSetNetcdf", false, Toolbar);
+      Toolbar.addSeparator();
+
+      // edit menu toolbar icons
+      addToolbarButton("cut", "Cut", "cutCell", !IsRemote, Toolbar);
+      addToolbarButton("copy", "Copy", "copyCell", !IsRemote, Toolbar);
+      ToolPaste = addToolbarButton("paste", "Paste",
+        "pasteCell", false, Toolbar);
+      Toolbar.addSeparator();
+
+      // cell menu toolbar icons
+      Tool3D = addToolbarButton("3d", "3-D (Java3D)",
+        "setDim3D", false, Toolbar);
+      ToolJ2D = addToolbarButton("j2d", "2-D (Java2D)",
+        "setDimJ2D", CanDo3D, Toolbar);
+      Tool2D = addToolbarButton("2d", "2-D (Java3D)",
+        "setDim2D", CanDo3D, Toolbar);
+      Toolbar.addSeparator();
+      ToolMap = addToolbarButton("mappings", "Edit mappings",
+        "createMappings", false, Toolbar);
+      ToolReset = addToolbarButton("reset", "Reset orientation",
+        "resetOrientation", false, Toolbar);
+      ToolShow = addToolbarButton("show", "Show controls",
+        "showControls", false, Toolbar);
+      Toolbar.addSeparator();
+
+      // layout menu toolbar icon
+      addToolbarButton("tile", "Tile cells", "tileCells", true, Toolbar);
+      Toolbar.add(Box.createHorizontalGlue());
+
+      // HACK - limit toolbar height
+      Toolbar.setMaximumSize(new Dimension(Toolbar.getMaximumSize().width,
+        Toolbar.getPreferredSize().height));
+    }
+
+    // set up formula bar
+    JPanel formulaPanel = new JPanel();
+    formulaPanel.setPreferredSize(new Dimension(Integer.MAX_VALUE, 25));
+    formulaPanel.setBackground(Color.lightGray);
+    formulaPanel.setLayout(new BoxLayout(formulaPanel, BoxLayout.X_AXIS));
+    formulaPanel.setBorder(new EtchedBorder());
+    pane.add(formulaPanel);
+    pane.add(Box.createRigidArea(new Dimension(0, 6)));
+    if (!BugFix) {
+      FormulaAdd = addToolbarButton("add", "Add data",
+        "formulaAdd", true, formulaPanel);
+      FormulaDel = addToolbarButton("del", "Remove data",
+        "formulaDel", true, formulaPanel);
+    }
+    FormulaBox = new JComboBox();
+    formulaPanel.add(FormulaBox);
+    FormulaBox.setLightWeightPopupEnabled(false);
+    FormulaBox.setEditable(true);
+    FormulaEditor = FormulaBox.getEditor();
+    FormulaListener = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        String newItem = ((String) FormulaEditor.getItem()).trim();
+        try {
+          int id = 0;
+          int type = BasicSSCell.UNKNOWN_SOURCE;
+          boolean notify = true;
+
+          int index1 = newItem.indexOf("d", 2);
+          int index2 = newItem.indexOf(":");
+          if (index1 > 0 && index2 > 0 && index1 < index2) {
+            String cellName = newItem.substring(0, index1);
+            BasicSSCell ssCell = BasicSSCell.getSSCellByName(cellName);
+            int dataId = 0;
+            try {
+              dataId = Integer.parseInt(newItem.substring(index1 + 1, index2));
+            }
+            catch (NumberFormatException exc) {
+              if (BasicSSCell.DEBUG && BasicSSCell.DEBUG_LEVEL >= 3) {
+                exc.printStackTrace();
+              }
+            }
+            if (ssCell == DisplayCells[CurX][CurY] && dataId > 0) {
+              // user is 'editing' the data object
+              String varName = newItem.substring(0, index2);
+              String source = ssCell.getDataSource(varName);
+              if (source != null) {
+                ssCell.removeData(varName);
+                String oldItem = null;
+                for (int i=0; i<FormulaBox.getItemCount(); i++) {
+                  String item = (String) FormulaBox.getItemAt(i);
+                  if (item.startsWith(varName + ":")) {
+                    oldItem = item;
+                    break;
+                  }
+                }
+                if (oldItem != null) {
+                  // remove old item from FormulaBox
+                  FormulaBox.removeItem(oldItem);
+                }
+              }
+              id = dataId;
+              newItem = newItem.substring(index2 + 1).trim();
+            }
+          }
+          String varName = DisplayCells[CurX][CurY].addDataSource(
+            id, newItem, type, notify);
+          String itemString = varName + ": " + newItem;
+          FormulaBox.addItem(itemString);
+          FormulaBox.setSelectedItem(itemString);
+          FormulaText.getCaret().setVisible(true); // BIG HAMMER HACK
+        }
+        catch (VisADException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage(
+            "Unable to compute data object from \"" + newItem + "\"", exc,
+            "VisAD SpreadSheet error");
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage(
+            "Unable to compute data object from \"" + newItem + "\"", exc,
+            "VisAD SpreadSheet error");
+        }
+      }
+    };
+    FormulaEditor.addActionListener(FormulaListener);
+    FormulaText = (JTextField) FormulaEditor.getEditorComponent();
+
+    // set up horizontal spreadsheet cell labels
+    JPanel horizShell = new JPanel();
+    horizShell.setBackground(Color.white);
+    horizShell.setLayout(new BoxLayout(horizShell, BoxLayout.X_AXIS));
+    horizShell.add(Box.createRigidArea(new Dimension(LABEL_WIDTH + 6, 0)));
+    pane.add(horizShell);
+
+    HorizPanel = new JPanel() {
+      public Dimension getPreferredSize() {
+        Dimension d = super.getPreferredSize();
+        return new Dimension(d.width, LABEL_HEIGHT);
+      }
+    };
+    HorizPanel.setBackground(Color.white);
+    constructHorizontalLabels();
+    JViewport hl = new JViewport() {
+      public Dimension getMinimumSize() {
+        return new Dimension(0, LABEL_HEIGHT);
+      }
+      public Dimension getPreferredSize() {
+        return new Dimension(0, LABEL_HEIGHT);
+      }
+      public Dimension getMaximumSize() {
+        return new Dimension(Integer.MAX_VALUE, LABEL_HEIGHT);
+      }
+    };
+    HorizLabels = hl;
+    HorizLabels.setView(HorizPanel);
+    horizShell.add(HorizLabels);
+    horizShell.add(new JComponent() {
+      public Dimension getMinimumSize() {
+        return new Dimension(6 + SCPane.getVScrollbarWidth(), 0);
+      }
+      public Dimension getPreferredSize() {
+        return new Dimension(6 + SCPane.getVScrollbarWidth(), 0);
+      }
+      public Dimension getMaximumSize() {
+        return new Dimension(6 + SCPane.getVScrollbarWidth(), 0);
+      }
+    });
+
+    // set up window's main panel
+    JPanel mainPanel = new JPanel();
+    mainPanel.setBackground(Color.white);
+    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));
+    pane.add(mainPanel);
+    pane.add(Box.createRigidArea(new Dimension(0, 6)));
+
+    // set up vertical spreadsheet cell labels
+    JPanel vertShell = new JPanel();
+    vertShell.setBackground(Color.white);
+    vertShell.setLayout(new BoxLayout(vertShell, BoxLayout.Y_AXIS));
+    mainPanel.add(Box.createRigidArea(new Dimension(6, 0)));
+    mainPanel.add(vertShell);
+
+    VertPanel = new JPanel() {
+      public Dimension getPreferredSize() {
+        Dimension d = super.getPreferredSize();
+        return new Dimension(LABEL_WIDTH, d.height);
+      }
+    };
+    VertPanel.setBackground(Color.white);
+    constructVerticalLabels();
+    JViewport vl = new JViewport() {
+      public Dimension getMinimumSize() {
+        return new Dimension(LABEL_WIDTH, 0);
+      }
+      public Dimension getPreferredSize() {
+        return new Dimension(LABEL_WIDTH, 0);
+      }
+      public Dimension getMaximumSize() {
+        return new Dimension(LABEL_WIDTH, Integer.MAX_VALUE);
+      }
+    };
+    VertLabels = vl;
+    VertLabels.setView(VertPanel);
+    vertShell.add(VertLabels);
+    vertShell.add(new JComponent() {
+      public Dimension getMinimumSize() {
+        return new Dimension(0, SCPane.getHScrollbarHeight());
+      }
+      public Dimension getPreferredSize() {
+        return new Dimension(0, SCPane.getHScrollbarHeight());
+      }
+      public Dimension getMaximumSize() {
+        return new Dimension(0, SCPane.getHScrollbarHeight());
+      }
+    });
+
+    // set up scroll pane's panel
+    ScrollPanel = new JPanel();
+    ScrollPanel.setBackground(Color.white);
+    ScrollPanel.setLayout(new BoxLayout(ScrollPanel, BoxLayout.X_AXIS));
+    mainPanel.add(ScrollPanel);
+    mainPanel.add(Box.createRigidArea(new Dimension(6, 0)));
+
+    // set up scroll pane for VisAD Displays
+    SCPane = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS) {
+      public Dimension getPreferredSize() {
+        return new Dimension(0, 0);
+      }
+    };
+    Adjustable hadj = SCPane.getHAdjustable();
+    Adjustable vadj = SCPane.getVAdjustable();
+    hadj.setBlockIncrement(MIN_VIS_WIDTH);
+    hadj.setUnitIncrement(MIN_VIS_WIDTH/4);
+    hadj.addAdjustmentListener(this);
+    vadj.setBlockIncrement(MIN_VIS_HEIGHT);
+    vadj.setUnitIncrement(MIN_VIS_HEIGHT/4);
+    vadj.addAdjustmentListener(this);
+    ScrollPanel.add(SCPane);
+
+    // set up display panel
+    DisplayPanel = new Panel();
+    DisplayPanel.setBackground(Color.darkGray);
+    SCPane.add(DisplayPanel);
+
+    // BIG HAMMER HACK
+    String os = System.getProperty("os.name");
+    if (!os.startsWith("Windows")) { // Windows does not need the hack
+      addKeyListener(this);
+      SCPane.addKeyListener(this);
+      ScrollPanel.addKeyListener(this);
+      DisplayPanel.addKeyListener(this);
+      FormulaBox.addFocusListener(new FocusAdapter() {
+        public void focusGained(FocusEvent e) { SCPane.requestFocus(); }
+      });
+    }
+
+    DataReferenceImpl lColRow = null;
+    if (server != null) {
+      // SERVER: initialize
+      boolean success = true;
+      boolean registryStarted = false;
+      while (true) {
+        try {
+          rsi = new RemoteServerImpl();
+          Naming.rebind("///" + server, rsi);
+          break;
+        }
+        catch (java.rmi.ConnectException exc) {
+          if (!registryStarted) {
+            try {
+              LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
+              registryStarted = true;
+            }
+            catch (RemoteException rexc) {
+              if (BasicSSCell.DEBUG) exc.printStackTrace();
+              displayErrorMessage("Unable to autostart rmiregistry. " +
+                "Please start rmiregistry before launching the " +
+                "SpreadSheet in server mode", null,
+                "Failed to initialize RemoteServer");
+              success = false;
+            }
+          }
+          else {
+            displayErrorMessage("Unable to export cells as RMI addresses. " +
+              "Make sure you are running rmiregistry before launching the " +
+              "SpreadSheet in server mode", null,
+              "Failed to initialize RemoteServer");
+            success = false;
+          }
+        }
+        catch (MalformedURLException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Unable to export cells as RMI addresses. " +
+            "The name \"" + server + "\" is not valid", null,
+            "Failed to initialize RemoteServer");
+          success = false;
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Unable to export cells as RMI addresses", exc,
+            "Failed to initialize RemoteServer");
+          success = false;
+        }
+
+        if (!success) break;
+      }
+
+      // set up info for spreadsheet cloning
+      try {
+        // set flag for whether server has Java3D enabled
+        DataReferenceImpl lCanDo3D = new DataReferenceImpl("CanDo3D");
+        RemoteCanDo3D = new RemoteDataReferenceImpl(lCanDo3D);
+        RemoteCanDo3D.setData(new Real(CanDo3D ? 1 : 0));
+        rsi.addDataReference((RemoteDataReferenceImpl) RemoteCanDo3D);
+        // set up remote reference for conveying cell layout information
+        lColRow = new DataReferenceImpl("ColRow");
+        RemoteColRow = new RemoteDataReferenceImpl(lColRow);
+        rsi.addDataReference((RemoteDataReferenceImpl) RemoteColRow);
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+        displayErrorMessage("Unable to export cells as RMI addresses. " +
+          "An error occurred setting up the necessary data", exc,
+          "Failed to initialize RemoteServer");
+        success = false;
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+        displayErrorMessage("Unable to export cells as RMI addresses. " +
+          "A remote error occurred setting up the necessary data", exc,
+          "Failed to initialize RemoteServer");
+        success = false;
+      }
+
+      if (success) bTitle = bTitle + " (" + server + ")";
+      else rsi = null;
+    }
+
+    // construct spreadsheet cells
+    if (rs == null) constructSpreadsheetCells(null);
+    else {
+      NumVisX = cellNames.length;
+      NumVisY = cellNames[0].length;
+      reconstructLabels(cellNames, null, null);
+      constructSpreadsheetCells(cellNames, rs);
+    }
+    if (rsi != null) synchColRow();
+    CollabID = DisplayCells[0][0].getRemoteId();
+
+    if (rsi != null || IsRemote) {
+      // update spreadsheet when remote row and column information changes
+      final RemoteServer frs = rs;
+      CellImpl lColRowCell = new CellImpl() {
+        public void doAction() {
+          // extract new cell information
+          if (getColRowID() != CollabID) {
+            Util.invoke(true, BasicSSCell.DEBUG, new Runnable() {
+              public void run() {
+                // update is coming from a different sheet
+                String[][] cellNamesx = getNewCellNames();
+                if (cellNamesx == null) {
+                  if (BasicSSCell.DEBUG) System.out.println("Warning: " +
+                    "could not obtain new spreadsheet dimensions!");
+                  return;
+                }
+                int oldNVX = NumVisX;
+                int oldNVY = NumVisY;
+                NumVisX = cellNamesx.length;
+                NumVisY = cellNamesx[0].length;
+                if (NumVisX != oldNVX || NumVisY != oldNVY) {
+                  // reconstruct spreadsheet cells and labels
+                  reconstructSpreadsheet(cellNamesx, null, null, frs);
+                  if (!IsRemote) synchColRow();
+                }
+              }
+            });
+          }
+        }
+      };
+      try {
+        RemoteCellImpl rColRowCell = new RemoteCellImpl(lColRowCell);
+        rColRowCell.addReference(RemoteColRow);
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+        displayErrorMessage("Remote cell error (1)",
+          exc, "VisAD SpreadSheet error");
+      }
+      catch (RemoteException exc) {
+        try {
+          lColRowCell.addReference(lColRow);
+        }
+        catch (VisADException exc2) {
+          if (BasicSSCell.DEBUG) exc2.printStackTrace();
+          displayErrorMessage("Remote cell error (2)",
+            exc2, "VisAD SpreadSheet error");
+        }
+        catch (RemoteException exc2) {
+          if (BasicSSCell.DEBUG) exc2.printStackTrace();
+          displayErrorMessage("Remote cell error (3)",
+            exc2, "VisAD SpreadSheet error");
+        }
+      }
+    }
+
+    // display window on screen
+    setTitle(bTitle);
+    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+    int appWidth = (int) (0.01 * sWidth * screenSize.width);
+    int appHeight = (int) (0.01 * sHeight * screenSize.height);
+    setSize(appWidth, appHeight);
+    Util.centerWindow(this);
+    setVisible(true);
+
+    // wait for frame to lay itself out, then tile cells
+    snooze(500);
+    FormulaText.getCaret().setVisible(true); // BIG HAMMER HACK
+    tileCells();
+  }
+
+
+  // --- FILE MENU ---
+
+  /**
+   * Imports a data set.
+   */
+  public void loadDataSet() {
+    DisplayCells[CurX][CurY].loadDataDialog();
+  }
+
+  /**
+   * Exports a data set to netCDF format.
+   */
+  public void exportDataSetNetcdf() {
+    try {
+      exportDataSet(new Plain());
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Error initializing netCDF export", exc,
+        "VisAD SpreadSheet error");
+    }
+  }
+
+  /**
+   * Exports a data set to serialized data format.
+   */
+  public void exportDataSetSerial() {
+    exportDataSet(new VisADForm());
+  }
+
+  /**
+   * Exports a data set to HDF-5 format.
+   */
+  public void exportDataSetHDF5() {
+    Form hdf5form = null;
+    try {
+      ClassLoader cl = ClassLoader.getSystemClassLoader();
+      Class hdf5form_class = cl.loadClass("visad.data.hdf5.HDF5Form");
+      hdf5form = (Form) hdf5form_class.newInstance();
+    }
+    catch (Exception exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Error initializing HDF-5 export", exc,
+        "VisAD SpreadSheet error");
+    }
+    if (hdf5form != null) exportDataSet(hdf5form);
+  }
+
+  /**
+   * Exports a data set to TIFF format.
+   */
+  public void exportDataSetTIFF() {
+    exportDataSet(new TiffForm());
+  }
+
+  /**
+   * Exports a data set to VisAD binary data format.
+   */
+  public void exportDataSetBinary() {
+    exportDataSet(new VisADForm(true));
+  }
+
+  /**
+   * Exports a data set using the given form.
+   */
+  public void exportDataSet(Form form) {
+    String item = (String) FormulaBox.getSelectedItem();
+    String varName = item.substring(0, item.indexOf(":"));
+    DisplayCells[CurX][CurY].saveDataDialog(varName, form);
+  }
+
+  /**
+   * Captures the display of the current cell and saves it as a JPEG image.
+   */
+  public void captureImageJPEG() {
+    DisplayCells[CurX][CurY].captureDialog();
+  }
+
+  /**
+   * Does any necessary clean-up, then quits the program.
+   */
+  public void quitProgram() {
+    // hide frames
+    try {
+      DisplayCells[CurX][CurY].hideWidgetFrame();
+    }
+    catch (NullPointerException exc) { }
+    setVisible(false);
+
+    // wait for files to finish saving
+    Thread t = new Thread() {
+      public void run() {
+        boolean b = BasicSSCell.isSaving();
+        JFrame f = null;
+        if (b) {
+          // display "please wait" message in new frame
+          f = new JFrame("Please wait");
+          f.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+          JPanel p = new JPanel();
+          f.setContentPane(p);
+          p.setBorder(new EmptyBorder(10, 20, 10, 20));
+          p.setLayout(new BorderLayout());
+          p.add("Center", new JLabel("Please wait while the VisAD " +
+                          "SpreadSheet finishes saving files..."));
+          f.setResizable(false);
+          f.pack();
+          Dimension sSize = Toolkit.getDefaultToolkit().getScreenSize();
+          Dimension fSize = f.getSize();
+          f.setLocation(sSize.width/2 - fSize.width/2,
+                        sSize.height/2 - fSize.height/2);
+          f.setVisible(true);
+        }
+        while (BasicSSCell.isSaving()) snooze(200);
+        if (b) {
+          f.setCursor(Cursor.getDefaultCursor());
+          f.setVisible(false);
+        }
+
+        // destroy all spreadsheet cells intelligently and cleanly
+        boolean[][] alive = new boolean[NumVisX][NumVisY];
+        for (int j=0; j<NumVisY; j++) {
+          for (int i=0; i<NumVisX; i++) alive[i][j] = true;
+        }
+        int aliveCount = NumVisX * NumVisY;
+        while (aliveCount > 0) {
+          for (int j=0; j<NumVisY; j++) {
+            for (int i=0; i<NumVisX; i++) {
+              if (alive[i][j] && !DisplayCells[i][j].othersDepend()) {
+                try {
+                  DisplayCells[i][j].destroyCell();
+                  alive[i][j] = false;
+                  aliveCount--;
+                }
+                catch (VisADException exc) {
+                  if (BasicSSCell.DEBUG) exc.printStackTrace();
+                  displayErrorMessage("Cannot destroy cell (1)", exc,
+                    "VisAD SpreadSheet error");
+                }
+                catch (RemoteException exc) {
+                  if (BasicSSCell.DEBUG) exc.printStackTrace();
+                  displayErrorMessage("Cannot destroy cell (2)", exc,
+                    "VisAD SpreadSheet error");
+                }
+              }
+            }
+          }
+        }
+        System.exit(0);
+      }
+    };
+    t.start();
+  }
+
+
+  // --- EDIT MENU ---
+
+  /**
+   * Moves a cell from the screen to the clipboard.
+   */
+  public void cutCell() {
+    if (DisplayCells[CurX][CurY].confirmClear()) {
+      copyCell();
+      clearCell(false);
+    }
+  }
+
+  /**
+   * Copies a cell from the screen to the clipboard.
+   */
+  public void copyCell() {
+    Clipboard = DisplayCells[CurX][CurY].getSaveString();
+    EditPaste.setEnabled(true);
+    if (!BugFix) ToolPaste.setEnabled(true);
+  }
+
+  /**
+   * Copies a cell from the clipboard to the screen.
+   */
+  public void pasteCell() {
+    if (Clipboard != null) {
+      try {
+        boolean b = DisplayCells[CurX][CurY].getAutoDetect();
+        DisplayCells[CurX][CurY].setAutoDetect(false);
+        DisplayCells[CurX][CurY].setSaveString(Clipboard);
+        DisplayCells[CurX][CurY].setAutoDetect(b);
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+        displayErrorMessage("Cannot paste cell", exc,
+          "VisAD SpreadSheet error");
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+        displayErrorMessage("Cannot paste cell", exc,
+          "VisAD SpreadSheet error");
+      }
+    }
+  }
+
+  /**
+   * Clears the mappings and formula of the current cell
+   * if it is safe to do so, or if the user confirms the clear.
+   */
+  public void clearCell() {
+    clearCell(true);
+  }
+
+  /**
+   * Clears the mappings and formula of the current cell.
+   */
+  protected void clearCell(boolean checkSafe) {
+    try {
+      if (checkSafe) DisplayCells[CurX][CurY].smartClear();
+      else DisplayCells[CurX][CurY].clearCell();
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Cannot clear display mappings", exc,
+        "VisAD SpreadSheet error");
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Cannot clear display mappings", exc,
+        "VisAD SpreadSheet error");
+    }
+    refreshFormulaBar();
+    refreshMenuCommands();
+  }
+
+
+  // --- SETUP MENU ---
+
+  /**
+   * Creates a new spreadsheet file, asking user to confirm first.
+   * @return true if successful.
+   */
+  public boolean newFile() {
+    return newFile(true);
+  }
+
+  /**
+   * Creates a new spreadsheet file.
+   * @return true if successful.
+   */
+  protected boolean newFile(boolean safe) {
+    if (safe) {
+      int ans = JOptionPane.showConfirmDialog(this,
+                "Clear all spreadsheet cells?", "Are you sure?",
+                JOptionPane.YES_NO_OPTION);
+      if (ans != JOptionPane.YES_OPTION) return false;
+    }
+
+    // hide control widgets
+    DisplayCells[CurX][CurY].hideWidgetFrame();
+
+    // clear all cells (in smart order to prevent errors)
+    boolean[][] dirty = new boolean[NumVisX][NumVisY];
+    for (int j=0; j<NumVisY; j++) {
+      for (int i=0; i<NumVisX; i++) dirty[i][j] = true;
+    }
+    int dirtyCount = NumVisX * NumVisY;
+    while (dirtyCount > 0) {
+      for (int j=0; j<NumVisY; j++) {
+        for (int i=0; i<NumVisX; i++) {
+          if (dirty[i][j] && !DisplayCells[i][j].othersDepend()) {
+            try {
+              DisplayCells[i][j].clearCell();
+              dirty[i][j] = false;
+              dirtyCount--;
+            }
+            catch (VisADException exc) {
+              if (BasicSSCell.DEBUG) exc.printStackTrace();
+              displayErrorMessage("Cannot clear cell (1)", exc,
+                "VisAD SpreadSheet error");
+            }
+            catch (RemoteException exc) {
+              if (BasicSSCell.DEBUG) exc.printStackTrace();
+              displayErrorMessage("Cannot clear cell (2)", exc,
+                "VisAD SpreadSheet error");
+            }
+          }
+        }
+      }
+    }
+    CurrentFile = null;
+    setTitle(bTitle);
+    return true;
+  }
+
+  /**
+   * Opens an existing spreadsheet file chosen by the user.
+   */
+  public void openFile() {
+    SSFileDialog.setDialogType(JFileChooser.OPEN_DIALOG);
+    if (SSFileDialog.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
+      // user has canceled request
+      return;
+    }
+
+    // make sure file exists
+    File f = SSFileDialog.getSelectedFile();
+    if (!f.exists()) {
+      displayErrorMessage("The file " + f.getName() + " does not exist", null,
+        "VisAD SpreadSheet error");
+      return;
+    }
+    openFile(f.getPath());
+  }
+
+  /**
+   * Opens the specified spreadsheet file.
+   */
+  public void openFile(String file) {
+    File f = new File(file);
+
+    // disable auto-switch, auto-detect and auto-show
+    boolean origSwitch = AutoSwitch;
+    boolean origDetect = AutoDetect;
+    boolean origShow = AutoShowControls;
+    setAutoSwitch(false);
+    setAutoDetect(false);
+    setAutoShowControls(false);
+
+    // clear all cells
+    newFile(false);
+
+    // load entire file into buffer
+    int fLen = (int) f.length();
+    char[] buff = new char[fLen];
+    try {
+      FileReader fr = new FileReader(f);
+      fr.read(buff, 0, fLen);
+      fr.close();
+    }
+    catch (IOException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Unable to read the file " + file + " from disk",
+        null, "VisAD SpreadSheet error");
+      // reset auto-switch, auto-detect and auto-show
+      setAutoSwitch(origSwitch);
+      setAutoDetect(origDetect);
+      setAutoShowControls(origShow);
+      return;
+    }
+
+    // cut buffer up into lines
+    StringTokenizer st = new StringTokenizer(new String(buff), "\n\r");
+    int numTokens = st.countTokens();
+    String[] tokens = new String[numTokens + 1];
+    for (int i=0; i<numTokens; i++) tokens[i] = st.nextToken();
+    tokens[numTokens] = null;
+    st = null;
+
+    // get global information
+    int sizeX = -1, sizeY = -1;
+    int dimX = -1, dimY = -1;
+    Vector colWidths = new Vector(), rowHeights = new Vector();
+    boolean autoSwitch = true, autoDetect = true, autoShow = true;
+    int tokenNum = 0;
+    String line;
+    int len;
+    int eq;
+    boolean gotGlobal = false;
+    while (!gotGlobal) {
+      line = tokens[tokenNum++];
+      if (line == null) {
+        displayErrorMessage("The file " + file + " does not contain " +
+          "the required [Global] tag", null, "VisAD SpreadSheet error");
+        // reset auto-switch, auto-detect and auto-show
+        setAutoSwitch(origSwitch);
+        setAutoDetect(origDetect);
+        setAutoShowControls(origShow);
+        return;
+      }
+      len = line.length();
+      String trimLine = line.trim();
+      int trimLen = trimLine.length();
+      if (trimLine.charAt(0) == '[' && trimLine.charAt(trimLen - 1) == ']') {
+        String sub = trimLine.substring(1, trimLen - 1).trim();
+        if (!sub.equalsIgnoreCase("global")) {
+          displayErrorMessage("The file " + file + " does not contain the " +
+            "[Global] tag as its first entry", null,
+            "VisAD SpreadSheet error");
+          // reset auto-switch, auto-detect and auto-show
+          setAutoSwitch(origSwitch);
+          setAutoDetect(origDetect);
+          setAutoShowControls(origShow);
+          return;
+        }
+
+        // parse global information
+        int endToken = tokenNum;
+        while (tokens[endToken] != null &&
+          tokens[endToken].trim().indexOf("[") != 0)
+        {
+          endToken++;
+        }
+        for (int i=tokenNum; i<endToken; i++) {
+          line = tokens[i].trim();
+          if (line == null) continue;
+          if (line.charAt(0) == '#') {
+            // ignore comments
+            continue;
+          }
+          eq = line.indexOf("=");
+          if (eq < 0) {
+            // ignore worthless lines
+            continue;
+          }
+          String keyword = line.substring(0, eq).trim();
+
+          // sheet size
+          if (keyword.equalsIgnoreCase("sheet size") ||
+            keyword.equalsIgnoreCase("sheet_size") ||
+            keyword.equalsIgnoreCase("sheetsize") ||
+            keyword.equalsIgnoreCase("size"))
+          {
+            int x = line.indexOf("x", eq);
+            if (x >= 0) {
+              String sX = line.substring(eq + 1, x).trim();
+              String sY = line.substring(x + 1).trim();
+              try {
+                sizeX = Integer.parseInt(sX);
+                sizeY = Integer.parseInt(sY);
+              }
+              catch (NumberFormatException exc) {
+                if (BasicSSCell.DEBUG) exc.printStackTrace();
+              }
+            }
+          }
+
+          // cell dimension
+          if (keyword.equalsIgnoreCase("dimension") ||
+            keyword.equalsIgnoreCase("dimensions") ||
+            keyword.equalsIgnoreCase("dim"))
+          {
+            int x = line.indexOf("x", eq);
+            if (x >= 0) {
+              String sX = line.substring(eq + 1, x).trim();
+              String sY = line.substring(x + 1).trim();
+              try {
+                dimX = Integer.parseInt(sX);
+                dimY = Integer.parseInt(sY);
+              }
+              catch (NumberFormatException exc) {
+                if (BasicSSCell.DEBUG) exc.printStackTrace();
+              }
+            }
+          }
+
+          // column widths
+          if (keyword.equalsIgnoreCase("columns") ||
+            keyword.equalsIgnoreCase("column") ||
+            keyword.equalsIgnoreCase("column widths") ||
+            keyword.equalsIgnoreCase("column_widths") ||
+            keyword.equalsIgnoreCase("columnwidths") ||
+            keyword.equalsIgnoreCase("column width") ||
+            keyword.equalsIgnoreCase("column_width") ||
+            keyword.equalsIgnoreCase("columnwidth"))
+          {
+            StringTokenizer ln = new StringTokenizer(line.substring(eq + 1));
+            int nt = ln.countTokens();
+            for (int z=0; z<nt; z++) {
+              int cw = 0;
+              try {
+                cw = Integer.parseInt(ln.nextToken());
+              }
+              catch (NumberFormatException exc) {
+                if (BasicSSCell.DEBUG) exc.printStackTrace();
+              }
+              if (cw >= MIN_VIS_WIDTH) colWidths.add(new Integer(cw));
+            }
+          }
+
+          // rows heights
+          if (keyword.equalsIgnoreCase("rows") ||
+            keyword.equalsIgnoreCase("row") ||
+            keyword.equalsIgnoreCase("row heights") ||
+            keyword.equalsIgnoreCase("row_heights") ||
+            keyword.equalsIgnoreCase("rowheights") ||
+            keyword.equalsIgnoreCase("row height") ||
+            keyword.equalsIgnoreCase("row_height") ||
+            keyword.equalsIgnoreCase("rowheight"))
+          {
+            StringTokenizer ln = new StringTokenizer(line.substring(eq + 1));
+            int nt = ln.countTokens();
+            for (int z=0; z<nt; z++) {
+              int rh = 0;
+              try {
+                rh = Integer.parseInt(ln.nextToken());
+              }
+              catch (NumberFormatException exc) {
+                if (BasicSSCell.DEBUG) exc.printStackTrace();
+              }
+              if (rh >= MIN_VIS_WIDTH) rowHeights.add(new Integer(rh));
+            }
+          }
+
+          // auto switch
+          if (keyword.equalsIgnoreCase("auto switch") ||
+            keyword.equalsIgnoreCase("auto_switch") ||
+            keyword.equalsIgnoreCase("auto-switch") ||
+            keyword.equalsIgnoreCase("autoswitch"))
+          {
+            String val = line.substring(eq + 1).trim();
+            if (val.equalsIgnoreCase("false") ||
+              val.equalsIgnoreCase("F"))
+            {
+              autoSwitch = false;
+            }
+          }
+
+          // auto detect
+          if (keyword.equalsIgnoreCase("auto detect") ||
+            keyword.equalsIgnoreCase("auto_detect") ||
+            keyword.equalsIgnoreCase("auto-detect") ||
+            keyword.equalsIgnoreCase("autodetect"))
+          {
+            String val = line.substring(eq + 1).trim();
+            if (val.equalsIgnoreCase("false") ||
+              val.equalsIgnoreCase("F"))
+            {
+              autoDetect = false;
+            }
+          }
+
+          // auto show
+          if (keyword.equalsIgnoreCase("auto show") ||
+            keyword.equalsIgnoreCase("auto_show") ||
+            keyword.equalsIgnoreCase("auto-show") ||
+            keyword.equalsIgnoreCase("autoshow"))
+          {
+            String val = line.substring(eq + 1).trim();
+            if (val.equalsIgnoreCase("false") ||
+              val.equalsIgnoreCase("F"))
+            {
+              autoShow = false;
+            }
+          }
+        }
+        gotGlobal = true;
+      }
+    }
+
+    // make sure cell dimensions are valid
+    if (dimX < 1 || dimY < 1) {
+      displayErrorMessage("The file " + file + " has an invalid " +
+        "global dimension entry", null, "VisAD SpreadSheet error");
+      // reset auto-switch, auto-detect and auto-show
+      setAutoSwitch(origSwitch);
+      setAutoDetect(origDetect);
+      setAutoShowControls(origShow);
+      return;
+    }
+
+    // create label width and height arrays
+    len = colWidths.size();
+    int[] cw = new int[dimX];
+    for (int i=0; i<dimX; i++) {
+      if (i < len) cw[i] = ((Integer) colWidths.elementAt(i)).intValue();
+      else cw[i] = DEFAULT_VIS_WIDTH;
+    }
+    len = rowHeights.size();
+    int[] rh = new int[dimY];
+    for (int i=0; i<dimY; i++) {
+      if (i < len) rh[i] = ((Integer) rowHeights.elementAt(i)).intValue();
+      else rh[i] = DEFAULT_VIS_HEIGHT;
+    }
+
+    // examine each cell entry
+    String[][] cellNames = new String[dimX][dimY];
+    String[][] fileStrings = new String[dimX][dimY];
+    for (int j=0; j<dimY; j++) {
+      for (int i=0; i<dimX; i++) {
+        // find next cell name
+        cellNames[i][j] = null;
+        do {
+          line = tokens[tokenNum++];
+          if (line == null) {
+            displayErrorMessage("The file " + file + " is incomplete", null,
+              "VisAD SpreadSheet error");
+            // reset auto-switch, auto-detect and auto-show
+            setAutoSwitch(origSwitch);
+            setAutoDetect(origDetect);
+            setAutoShowControls(origShow);
+            return;
+          }
+          String trimLine = line.trim();
+          int trimLen = trimLine.length();
+          if (trimLine.charAt(0) == '[' &&
+            trimLine.charAt(trimLen - 1) == ']')
+          {
+            // this line identifies a cell name
+            cellNames[i][j] = trimLine.substring(1, trimLen - 1).trim();
+          }
+        }
+        while (cellNames[i][j] == null);
+
+        // find last line of this cell's save string
+        int last = tokenNum + 1;
+        while (tokens[last] != null &&
+          tokens[last].trim().indexOf("[") != 0)
+        {
+          last++;
+        }
+
+        // build this cell's save string
+        String s = "";
+        for (int l=tokenNum; l<last; l++) s = s + tokens[l] + "\n";
+        fileStrings[i][j] = s;
+      }
+    }
+
+    // resize the sheet to the correct size
+    if (sizeX > 0 && sizeY > 0) setSize(sizeX, sizeY);
+
+    // reconstruct spreadsheet cells and labels
+    NumVisX = dimX;
+    NumVisY = dimY;
+    reconstructSpreadsheet(cellNames, cw, rh, null);
+    synchColRow();
+
+    // set each cell's string
+    for (int j=0; j<NumVisY; j++) {
+      for (int i=0; i<NumVisX; i++) {
+        try {
+          DisplayCells[i][j].setSaveString(fileStrings[i][j]);
+        }
+        catch (VisADException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Invalid save string", exc,
+            "VisAD SpreadSheet error");
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Invalid save string", exc,
+            "VisAD SpreadSheet error");
+        }
+      }
+    }
+
+    // set auto-switch, auto-detect and auto-show
+    setAutoSwitch(autoSwitch);
+    setAutoDetect(autoDetect);
+    setAutoShowControls(autoShow);
+
+    // update current file and title
+    CurrentFile = f;
+    setTitle(bTitle + " - " + f.getPath());
+
+    // refresh GUI components
+    refreshDisplayMenuItems();
+    refreshFormulaBar();
+    refreshMenuCommands();
+    refreshOptions();
+    validate();
+    repaint();
+  }
+
+  /**
+   * Saves a spreadsheet file under its current name.
+   */
+  public void saveFile() {
+    if (CurrentFile == null) saveAsFile();
+    else {
+      // construct file header
+      StringBuffer sb = new StringBuffer(1024 * NumVisX * NumVisY + 1024);
+      sb.append(SSFileHeader);
+      sb.append("\n");
+      sb.append("# File ");
+      sb.append(CurrentFile.getName());
+      sb.append(" written at ");
+      sb.append(Util.getTimestamp());
+      sb.append("\n\n");
+
+      // compile global information
+      sb.append("[Global]\n");
+      sb.append("sheet size = ");
+      sb.append(getWidth());
+      sb.append(" x ");
+      sb.append(getHeight());
+      sb.append("\n");
+      sb.append("dimension = ");
+      sb.append(NumVisX);
+      sb.append(" x ");
+      sb.append(NumVisY);
+      sb.append("\n");
+      sb.append("columns =");
+      for (int j=0; j<NumVisX; j++) {
+        sb.append(" ");
+        sb.append(HorizLabel[j].getSize().width);
+      }
+      sb.append("\n");
+      sb.append("rows =");
+      for (int i=0; i<NumVisY; i++) {
+        sb.append(" ");
+        sb.append(VertLabel[i].getSize().height);
+      }
+      sb.append("\n");
+      sb.append("auto switch = ");
+      sb.append(AutoSwitch);
+      sb.append("\n");
+      sb.append("auto detect = ");
+      sb.append(AutoDetect);
+      sb.append("\n");
+      sb.append("auto show = ");
+      sb.append(AutoShowControls);
+      sb.append("\n\n");
+
+      // compile cell information
+      for (int j=0; j<NumVisY; j++) {
+        for (int i=0; i<NumVisX; i++) {
+          sb.append("[");
+          sb.append(DisplayCells[i][j].getName());
+          sb.append("]\n");
+          sb.append(DisplayCells[i][j].getSaveString());
+          sb.append("\n");
+        }
+      }
+
+      // convert information to a character array
+      char[] sc = sb.toString().toCharArray();
+
+      try {
+        // write file to disk
+        FileWriter fw = new FileWriter(CurrentFile);
+        fw.write(sc, 0, sc.length);
+        fw.close();
+      }
+      catch (IOException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+        displayErrorMessage("Could not save file " + CurrentFile.getName() +
+          ". Make sure there is enough disk space", null,
+          "VisAD SpreadSheet error");
+      }
+    }
+  }
+
+  /**
+   * Saves a spreadsheet file under a new name.
+   */
+  public void saveAsFile() {
+    SSFileDialog.setDialogType(JFileChooser.SAVE_DIALOG);
+    if (SSFileDialog.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
+      // user has canceled request
+      return;
+    }
+
+    // get file and make sure it is valid
+    File f = SSFileDialog.getSelectedFile();
+    CurrentFile = f;
+    setTitle(bTitle + " - " + f.getPath());
+    saveFile();
+  }
+
+
+  // --- CELL MENU ---
+
+  /**
+   * Sets the dimension of the current cell to 3-D (Java3D).
+   */
+  public void setDim3D() {
+    setDim(BasicSSCell.JAVA3D_3D);
+  }
+
+  /**
+   * Sets the dimension of the current cell to 2-D (Java2D).
+   */
+  public void setDimJ2D() {
+    setDim(BasicSSCell.JAVA2D_2D);
+  }
+
+  /**
+   * Sets the dimension of the current cell to 2-D (Java3D).
+   */
+  public void setDim2D() {
+    setDim(BasicSSCell.JAVA3D_2D);
+  }
+
+  /**
+   * Sets the dimension of the current cell.
+   */
+  protected void setDim(int dim) {
+    try {
+      DisplayCells[CurX][CurY].setDimension(dim);
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Cannot alter display dimension", exc,
+        "VisAD SpreadSheet error");
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Cannot alter display dimension", exc,
+        "VisAD SpreadSheet error");
+    }
+    refreshDisplayMenuItems();
+  }
+
+  /**
+   * Creates a hardcopy of the current spreadsheet cell.
+   */
+  public void printCurrentCell() {
+    if (!DisplayCells[CurX][CurY].hasData()) {
+      displayErrorMessage("The current cell contains no data to be printed",
+        null, "VisAD SpreadSheet error");
+      return;
+    }
+    final PrinterJob printJob = PrinterJob.getPrinterJob();
+    DisplayImpl display = DisplayCells[CurX][CurY].getDisplay();
+    Printable p = display.getPrintable();
+    printJob.setPrintable(p);
+    if (printJob.printDialog()) {
+      Runnable printImage = new Runnable() {
+        public void run() {
+          try {
+            printJob.print();
+          }
+          catch (Exception exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+            displayErrorMessage("Cannot print the current cell", exc,
+              "VisAD SpreadSheet error");
+          }
+        }
+      };
+      Thread t = new Thread(printImage);
+      t.start();
+
+    }
+  }
+
+  /**
+   * Specifies mappings from Data to Display.
+   */
+  public void createMappings() {
+    DisplayCells[CurX][CurY].addMapDialog();
+    refreshMenuCommands();
+  }
+
+  private static double[] matrix3D =
+    {0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 0.5, 0, 0, 0, 0, 1};
+
+  private static double[] matrix2D =
+    {0.65, 0, 0, 0, 0, 0.65, 0, 0, 0, 0, 0.65, 0, 0, 0, 0, 1};
+
+  private static double[] matrixJ2D =
+    {1, 0, 0, -1, 0, 0};
+
+
+  /**
+   * Resets the display projection to its original value.
+   */
+  public void resetOrientation() {
+    DisplayImpl display = DisplayCells[CurX][CurY].getDisplay();
+    if (display != null) {
+      ProjectionControl pc = display.getProjectionControl();
+      if (pc != null) {
+        int dim = DisplayCells[CurX][CurY].getDimension();
+        double[] matrix;
+        if (dim == 1) {
+          // 3-D (Java3D)
+          matrix = matrix3D;
+        }
+        else if (dim == 2) {
+          // 2-D (Java2D)
+          matrix = matrixJ2D;
+        }
+        else {
+          // 2-D (Java3D)
+          matrix = matrix2D;
+        }
+        try {
+          pc.setMatrix(matrix);
+        }
+        catch (VisADException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot reset orientation", exc,
+            "VisAD SpreadSheet error");
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot reset orientation", exc,
+            "VisAD SpreadSheet error");
+        }
+      }
+    }
+  }
+
+  /**
+   * Displays the controls for the currently selected cell.
+   */
+  public void showControls() {
+    DisplayCells[CurX][CurY].showWidgetFrame();
+  }
+
+
+  // --- DISPLAY MENU ---
+
+  /**
+   * Adds a column to the spreadsheet.
+   */
+  public synchronized void addColumn() {
+    JLabel l = (JLabel) HorizLabel[NumVisX - 1].getComponent(0);
+    int maxVisX = Letters.indexOf(l.getText()) + 1;
+    int diffX = Letters.length() - maxVisX;
+    if (diffX > 0) {
+      // re-layout horizontal spreadsheet labels
+      JPanel[] newLabels = new JPanel[NumVisX + 1];
+      for (int i=0; i<NumVisX; i++) newLabels[i] = HorizLabel[i];
+      newLabels[NumVisX] = new JPanel();
+      newLabels[NumVisX].setBorder(new LineBorder(Color.black, 1));
+      newLabels[NumVisX].setLayout(new BorderLayout());
+      newLabels[NumVisX].setPreferredSize(
+        new Dimension(DEFAULT_VIS_WIDTH, LABEL_HEIGHT));
+      String s = String.valueOf(Letters.charAt(maxVisX));
+      newLabels[NumVisX].add("Center", new JLabel(s, SwingConstants.CENTER));
+
+      if (IsRemote) {
+        // let the server handle the actual cell layout
+        HorizLabel = newLabels;
+        synchColRow();
+      }
+      else {
+        // re-layout horizontal label separators
+        JComponent[] newDrag = new JComponent[NumVisX + 1];
+        for (int i=0; i<NumVisX; i++) newDrag[i] = HorizDrag[i];
+        newDrag[NumVisX] = new JComponent() {
+          public void paint(Graphics g) {
+            Dimension d = this.getSize();
+            g.setColor(Color.black);
+            g.drawRect(0, 0, d.width - 1, d.height - 1);
+            g.setColor(Color.yellow);
+            g.fillRect(1, 1, d.width - 2, d.height - 2);
+          }
+        };
+        newDrag[NumVisX].setPreferredSize(new Dimension(5, 0));
+        newDrag[NumVisX].addMouseListener(this);
+        newDrag[NumVisX].addMouseMotionListener(this);
+        HorizPanel.removeAll();
+
+        // re-layout spreadsheet cells
+        FancySSCell[][] fcells = new FancySSCell[NumVisX + 1][NumVisY];
+        DisplayPanel.removeAll();
+        for (int j=0; j<NumVisY; j++) {
+          for (int i=0; i<NumVisX; i++) fcells[i][j] = DisplayCells[i][j];
+          try {
+            String name = String.valueOf(Letters.charAt(maxVisX)) +
+              String.valueOf(j + 1);
+            FancySSCell f = createCell(name, null);
+            f.addSSCellListener(this);
+            f.addMouseListener(this);
+            f.setAutoSwitch(AutoSwitch);
+            f.setAutoDetect(AutoDetect);
+            f.setAutoShowControls(AutoShowControls);
+            f.setDimension(CanDo3D ?
+              BasicSSCell.JAVA3D_3D : BasicSSCell.JAVA2D_2D);
+            f.addDisplayListener(this);
+            f.setPreferredSize(
+              new Dimension(DEFAULT_VIS_WIDTH, DEFAULT_VIS_HEIGHT));
+            fcells[NumVisX][j] = f;
+            if (rsi != null) {
+              // add new cell to server
+              fcells[NumVisX][j].addToRemoteServer(rsi);
+            }
+          }
+          catch (VisADException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+            displayErrorMessage("Cannot add the column. Unable to create " +
+              "new displays", exc, "VisAD SpreadSheet error");
+          }
+          catch (RemoteException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+            displayErrorMessage("Cannot add the column. A remote error " +
+              "occurred", exc, "VisAD SpreadSheet error");
+          }
+        }
+
+        NumVisX++;
+        reconstructHoriz(newLabels, newDrag, fcells);
+        if (diffX == 1) LayAddCol.setEnabled(false);
+        LayDelCol.setEnabled(true);
+      }
+    }
+  }
+
+  /**
+   * Adds a row to the spreadsheet.
+   */
+  public void addRow() {
+    JLabel l = (JLabel) VertLabel[NumVisY - 1].getComponent(0);
+    int maxVisY = Integer.parseInt(l.getText());
+
+    // re-layout vertical spreadsheet labels
+    JPanel[] newLabels = new JPanel[NumVisY + 1];
+    JComponent[] newDrag = new JComponent[NumVisY + 1];
+    for (int i=0; i<NumVisY; i++) newLabels[i] = VertLabel[i];
+    newLabels[NumVisY] = new JPanel();
+    newLabels[NumVisY].setBorder(new LineBorder(Color.black, 1));
+    newLabels[NumVisY].setLayout(new BorderLayout());
+    newLabels[NumVisY].setPreferredSize(
+      new Dimension(LABEL_WIDTH, DEFAULT_VIS_HEIGHT));
+    String s = String.valueOf(maxVisY + 1);
+    newLabels[NumVisY].add("Center", new JLabel(s, SwingConstants.CENTER));
+
+    if (IsRemote) {
+      // let server handle the actual cell layout
+      VertLabel = newLabels;
+      synchColRow();
+    }
+    else {
+      // re-layout vertical label separators
+      for (int i=0; i<NumVisY; i++) newDrag[i] = VertDrag[i];
+      newDrag[NumVisY] = new JComponent() {
+        public void paint(Graphics g) {
+          Dimension d = this.getSize();
+          g.setColor(Color.black);
+          g.drawRect(0, 0, d.width - 1, d.height - 1);
+          g.setColor(Color.yellow);
+          g.fillRect(1, 1, d.width - 2, d.height - 2);
+        }
+      };
+      newDrag[NumVisY].setPreferredSize(new Dimension(0, 5));
+      newDrag[NumVisY].addMouseListener(this);
+      newDrag[NumVisY].addMouseMotionListener(this);
+      VertPanel.removeAll();
+
+      // re-layout spreadsheet cells
+      FancySSCell[][] fcells = new FancySSCell[NumVisX][NumVisY + 1];
+      DisplayPanel.removeAll();
+      for (int i=0; i<NumVisX; i++) {
+        for (int j=0; j<NumVisY; j++) fcells[i][j] = DisplayCells[i][j];
+        try {
+          String name = String.valueOf(Letters.charAt(i)) +
+            String.valueOf(maxVisY + 1);
+          FancySSCell f = createCell(name, null);
+          f.addSSCellListener(this);
+          f.addMouseListener(this);
+          f.setAutoSwitch(AutoSwitch);
+          f.setAutoDetect(AutoDetect);
+          f.setAutoShowControls(AutoShowControls);
+          f.setDimension(CanDo3D ?
+            BasicSSCell.JAVA3D_3D : BasicSSCell.JAVA2D_2D);
+          f.addDisplayListener(this);
+          f.setPreferredSize(
+            new Dimension(DEFAULT_VIS_WIDTH, DEFAULT_VIS_HEIGHT));
+          fcells[i][NumVisY] = f;
+          if (rsi != null) {
+            // add new cell to server
+            fcells[i][NumVisY].addToRemoteServer(rsi);
+          }
+        }
+        catch (VisADException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot add the row. Unable to create new " +
+            "displays", exc, "VisAD SpreadSheet error");
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot add the row. A remote error occurred",
+            exc, "VisAD SpreadSheet error");
+        }
+      }
+
+      NumVisY++;
+      reconstructVert(newLabels, newDrag, fcells);
+      LayDelRow.setEnabled(true);
+    }
+  }
+
+  /**
+   * Deletes a column from the spreadsheet.
+   */
+  public synchronized boolean deleteColumn() {
+    // make sure at least one column will be left
+    if (NumVisX == 1) {
+      displayErrorMessage("This is the last column", null,
+        "Cannot delete column");
+      return false;
+    }
+    // make sure no cells are dependent on columns about to be deleted
+    for (int j=0; j<NumVisY; j++) {
+      if (DisplayCells[CurX][j].othersDepend()) {
+        displayErrorMessage("Other cells depend on cells from this " +
+          "column. Make sure that no cells depend on this column before " +
+          "attempting to delete it", null, "Cannot delete column");
+        return false;
+      }
+    }
+
+    // get column letter to be deleted
+    JLabel label = (JLabel) HorizLabel[CurX].getComponent(0);
+    char letter = label.getText().charAt(0);
+    char last = Letters.charAt(Letters.length() - 1);
+
+    // re-layout horizontal spreadsheet labels
+    JPanel[] newLabels = new JPanel[NumVisX - 1];
+    for (int i=0; i<CurX; i++) newLabels[i] = HorizLabel[i];
+    for (int i=CurX+1; i<NumVisX; i++) newLabels[i - 1] = HorizLabel[i];
+
+    if (IsRemote) {
+      // let server handle the actual cell layout
+      HorizLabel = newLabels;
+      synchColRow();
+    }
+    else {
+      // re-layout horizontal label separators
+      JComponent[] newDrag = new JComponent[NumVisX - 1];
+      for (int i=0; i<NumVisX-1; i++) newDrag[i] = HorizDrag[i];
+      HorizPanel.removeAll();
+
+      // re-layout spreadsheet cells
+      FancySSCell[][] fcells = new FancySSCell[NumVisX - 1][NumVisY];
+      DisplayPanel.removeAll();
+      for (int j=0; j<NumVisY; j++) {
+        for (int i=0; i<CurX; i++) fcells[i][j] = DisplayCells[i][j];
+        for (int i=CurX+1; i<NumVisX; i++) {
+          fcells[i - 1][j] = DisplayCells[i][j];
+        }
+        try {
+          DisplayCells[CurX][j].destroyCell();
+        }
+        catch (VisADException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot destroy cell (3)", exc,
+            "VisAD SpreadSheet error");
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot destroy cell (4)", exc,
+            "VisAD SpreadSheet error");
+        }
+        DisplayCells[CurX][j] = null;
+      }
+      NumVisX--;
+      if (CurX > NumVisX - 1) selectCell(NumVisX - 1, CurY);
+      reconstructHoriz(newLabels, newDrag, fcells);
+      if (letter == last) LayAddCol.setEnabled(true);
+      if (NumVisX == 1) LayDelCol.setEnabled(false);
+    }
+    return true;
+  }
+
+  /**
+   * Deletes a row from the spreadsheet.
+   */
+  public synchronized boolean deleteRow() {
+    // make sure at least one row will be left
+    if (NumVisY == 1) {
+      displayErrorMessage("This is the last row", null, "Cannot delete row");
+      return false;
+    }
+
+    // make sure no cells are dependent on rows about to be deleted
+    for (int i=0; i<NumVisX; i++) {
+      if (DisplayCells[i][CurY].othersDepend()) {
+        displayErrorMessage("Other cells depend on cells from this row. " +
+          "Make sure that no cells depend on this row before attempting " +
+          "to delete it", null, "Cannot delete row");
+        return false;
+      }
+    }
+
+    // re-layout vertical spreadsheet labels
+    JPanel[] newLabels = new JPanel[NumVisY - 1];
+    for (int i=0; i<CurY; i++) newLabels[i] = VertLabel[i];
+    for (int i=CurY+1; i<NumVisY; i++) newLabels[i - 1] = VertLabel[i];
+
+    if (IsRemote) {
+      // let server handle the actual cell layout
+      VertLabel = newLabels;
+      synchColRow();
+    }
+    else {
+      // re-layout horizontal label separators
+      JComponent[] newDrag = new JComponent[NumVisY];
+      for (int i=0; i<NumVisY; i++) newDrag[i] = VertDrag[i];
+      VertPanel.removeAll();
+
+      // re-layout spreadsheet cells
+      FancySSCell[][] fcells = new FancySSCell[NumVisX][NumVisY - 1];
+      DisplayPanel.removeAll();
+      for (int i=0; i<NumVisX; i++) {
+        for (int j=0; j<CurY; j++) fcells[i][j] = DisplayCells[i][j];
+        for (int j=CurY+1; j<NumVisY; j++) {
+          fcells[i][j - 1] = DisplayCells[i][j];
+        }
+        try {
+          DisplayCells[i][CurY].destroyCell();
+        }
+        catch (VisADException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot destroy cell (5)", exc,
+            "VisAD SpreadSheet error");
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+          displayErrorMessage("Cannot destroy cell (6)", exc,
+            "VisAD SpreadSheet error");
+        }
+        DisplayCells[i][CurY] = null;
+      }
+      NumVisY--;
+      if (CurY > NumVisY - 1) selectCell(CurX, NumVisY - 1);
+      reconstructVert(newLabels, newDrag, fcells);
+      if (NumVisY == 1) LayDelRow.setEnabled(false);
+    }
+    return true;
+  }
+
+  /**
+   * Resizes all cells to exactly fill the entire pane, if possible.
+   */
+  public void tileCells() {
+    Dimension paneSize = SCPane.getSize();
+    int w = paneSize.width - SCPane.getVScrollbarWidth() - 5 * NumVisX;
+    int h = paneSize.height - SCPane.getHScrollbarHeight() - 5 * NumVisY;
+    int wx = w / NumVisX;
+    int hy = h / NumVisY;
+    if (wx < MIN_VIS_WIDTH) wx = MIN_VIS_WIDTH;
+    if (hy < MIN_VIS_HEIGHT) hy = MIN_VIS_HEIGHT;
+    Dimension hSize = new Dimension(wx, LABEL_HEIGHT);
+    Dimension vSize = new Dimension(LABEL_WIDTH, hy);
+    for (int i=0; i<NumVisX; i++) {
+      HorizLabel[i].setSize(hSize);
+      HorizLabel[i].setPreferredSize(hSize);
+    }
+    for (int j=0; j<NumVisY; j++) {
+      VertLabel[j].setSize(vSize);
+      VertLabel[j].setPreferredSize(vSize);
+    }
+    synchLabelAndCellSizes();
+  }
+
+
+  // --- OPTIONS MENU ---
+
+  /**
+   * Sets auto-dimension switching to match Auto-switch menu item state.
+   */
+  public void optionsSwitch() {
+    setAutoSwitch(AutoSwitchBox.getState());
+  }
+
+  /**
+   * Sets mapping auto-detection to match Auto-detect menu item state.
+   */
+  public void optionsDetect() {
+    setAutoDetect(AutoDetectBox.getState());
+  }
+
+  /**
+   * Sets auto-display of controls to match Auto-display menu item state.
+   */
+  public void optionsDisplay() {
+    setAutoShowControls(AutoShowBox.getState());
+  }
+
+  /**
+   * Toggles auto-dimension switching.
+   */
+  protected void setAutoSwitch(boolean b) {
+    for (int j=0; j<NumVisY; j++) {
+      for (int i=0; i<NumVisX; i++) DisplayCells[i][j].setAutoSwitch(b);
+    }
+    AutoSwitch = b;
+  }
+
+  /**
+   * Toggles mapping auto-detection.
+   */
+  protected void setAutoDetect(boolean b) {
+    for (int j=0; j<NumVisY; j++) {
+      for (int i=0; i<NumVisX; i++) DisplayCells[i][j].setAutoDetect(b);
+    }
+    AutoDetect = b;
+  }
+
+  /**
+   * Toggles auto-display of controls.
+   */
+  protected void setAutoShowControls(boolean b) {
+    for (int j=0; j<NumVisY; j++) {
+      for (int i=0; i<NumVisX; i++) DisplayCells[i][j].setAutoShowControls(b);
+    }
+    AutoShowControls = b;
+  }
+
+
+  // --- FORMULA BAR BUTTONS ---
+
+  /**
+   * Prompts the user to type a source for a new data object
+   * for the current cell.
+   */
+  public void formulaAdd() {
+    FormulaEditor.setItem("");
+    FormulaEditor.selectAll();
+  }
+
+  /**
+   * Deletes the selected data object from the current cell.
+   */
+  public void formulaDel() {
+    String item = (String) FormulaBox.getSelectedItem();
+    if (item == null) return;
+    int index = item.indexOf(":");
+    if (index < 0) return;
+    String varName = item.substring(0, index);
+    try {
+      DisplayCells[CurX][CurY].removeData(varName);
+      FormulaBox.removeItem(item);
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Cannot delete data " + varName, exc,
+        "VisAD SpreadSheet error");
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+      displayErrorMessage("Cannot delete data " + varName, exc,
+        "VisAD SpreadSheet error");
+    }
+  }
+
+
+  // --- GUI MANAGEMENT ---
+
+  /**
+   * Refreshes spreadsheet cells.
+   */
+  protected void refreshCells() {
+    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+      public void run() {
+        for (int i=0; i<NumVisX; i++) {
+          for (int j=0; j<NumVisY; j++) DisplayCells[i][j].refresh();
+        }
+      }
+    });
+  }
+
+  /**
+   * Refreshes check box items in the Options menu.
+   */
+  protected void refreshOptions() {
+    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+      public void run() {
+        AutoSwitchBox.setState(AutoSwitch);
+        AutoDetectBox.setState(AutoDetect);
+        AutoShowBox.setState(AutoShowControls);
+      }
+    });
+  }
+
+  /**
+   * Refreshes the "Show controls" menu option and toolbar button.
+   */
+  protected void refreshShowControls() {
+    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+      public void run() {
+        boolean b = DisplayCells[CurX][CurY].hasControls();
+        CellShow.setEnabled(b);
+        if (!BugFix) ToolShow.setEnabled(b);
+      }
+    });
+  }
+
+  /**
+   * Enables or disables certain menu items
+   * depending on whether this cell has data.
+   */
+  protected void refreshMenuCommands() {
+    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+      public void run() {
+        boolean b = DisplayCells[CurX][CurY].hasData();
+        FileExport.setEnabled(b);
+        FileSnap.setEnabled(b && CanDoJPEG);
+        EditClear.setEnabled(b);
+        CellPrint.setEnabled(b);
+        CellEdit.setEnabled(b);
+        CellReset.setEnabled(b && !IsSlave);
+        if (!BugFix) {
+          ToolSave.setEnabled(b);
+          ToolMap.setEnabled(b);
+          ToolReset.setEnabled(b && !IsSlave);
+        }
+        refreshShowControls();
+      }
+    });
+  }
+
+  /**
+   * Makes sure the formula bar is displaying up-to-date info.
+   */
+  protected void refreshFormulaBar() {
+    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+      public void run() {
+        if (FormulaBox.getItemCount() > 0) FormulaBox.removeAllItems();
+        String[] varNames = DisplayCells[CurX][CurY].getVariableNames();
+        int len = varNames.length;
+        for (int i=0; i<len; i++) {
+          String varName = varNames[i];
+          String source = DisplayCells[CurX][CurY].getDataSource(varName);
+          FormulaBox.addItem(varName + ": " + source);
+        }
+        boolean b = len > 0;
+        CellDel.setEnabled(b);
+        if (!BugFix) FormulaDel.setEnabled(b);
+      }
+    });
+  }
+
+  /**
+   * Updates dimension checkbox menu items and toolbar buttons.
+   */
+  protected void refreshDisplayMenuItems() {
+    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+      public void run() {
+        // update dimension check marks
+        int dim = DisplayCells[CurX][CurY].getDimension();
+        boolean j3d3d = (dim == BasicSSCell.JAVA3D_3D);
+        boolean j2d2d = (dim == BasicSSCell.JAVA2D_2D);
+        boolean j3d2d = (dim == BasicSSCell.JAVA3D_2D);
+        CellDim3D3D.setState(j3d3d);
+        CellDim2D2D.setState(j2d2d);
+        CellDim2D3D.setState(j3d2d);
+        if (!BugFix) {
+          Tool3D.setEnabled(!j3d3d && CanDo3D);
+          Tool2D.setEnabled(!j3d2d && CanDo3D);
+          ToolJ2D.setEnabled(!j2d2d);
+        }
+      }
+    });
+  }
+
+
+  // --- COLLABORATION ---
+
+  /**
+   * Determines whether or not the last remote event was from the server.
+   */
+  private double getColRowID() {
+    TupleIface t = null;
+    Real id = null;
+    try {
+      t = (TupleIface) RemoteColRow.getData();
+      id = (Real) t.getComponent(0);
+      return id.getValue();
+    }
+    catch (NullPointerException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    return Float.NaN;
+  }
+
+  /**
+   * Gets the latest remote row and column information.
+   */
+  private String[][] getNewCellNames() {
+    // extract new row and column information
+    TupleIface t = null;
+    TupleIface tc = null;
+    RealTuple tr = null;
+    try {
+      t = (TupleIface) RemoteColRow.getData();
+      tc = (TupleIface) t.getComponent(1);
+      tr = (RealTuple) t.getComponent(2);
+    }
+    catch (NullPointerException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    catch (VisADException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    if (tc == null || tr == null) return null;
+    int collen = -1;
+    int rowlen = -1;
+    try {
+      collen = tc.getDimension();
+      rowlen = tr.getDimension();
+    }
+    catch (RemoteException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+    if (rowlen < 1 || collen < 1) return null;
+    String[] colNames = new String[collen];
+    int[] rowNames = new int[rowlen];
+    String[][] cellNames = new String[collen][rowlen];
+    for (int i=0; i<collen; i++) {
+      Text txt = null;
+      try {
+        txt = (Text) tc.getComponent(i);
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      if (txt == null) return null;
+      colNames[i] = txt.getValue();
+    }
+    for (int j=0; j<rowlen; j++) {
+      Real r = null;
+      try {
+        r = (Real) tr.getComponent(j);
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      if (r == null) return null;
+      rowNames[j] = (int) r.getValue();
+    }
+    for (int i=0; i<collen; i++) {
+      for (int j=0; j<rowlen; j++) {
+        cellNames[i][j] = colNames[i] + rowNames[j];
+      }
+    }
+
+    return cellNames;
+  }
+
+  /**
+   * Updates the remote row and column information.
+   */
+  private void synchColRow() {
+    if (RemoteColRow != null) {
+      synchronized (RemoteColRow) {
+        int xlen = HorizLabel.length;
+        int ylen = VertLabel.length;
+        try {
+          MathType[] m = new MathType[3];
+
+          Real id = new Real(CollabID);
+          m[0] = id.getType();
+          Text[] txt = new Text[xlen];
+          TextType[] tt = new TextType[xlen];
+          for (int i=0; i<xlen; i++) {
+            String s = ((JLabel) HorizLabel[i].getComponent(0)).getText();
+            txt[i] = new Text(s);
+            tt[i] = (TextType) txt[i].getType();
+          }
+          m[1] = new TupleType(tt);
+          TupleIface tc = new Tuple((TupleType) m[1], txt);
+
+          Real[] r = new Real[ylen];
+          for (int j=0; j<ylen; j++) {
+            String s = ((JLabel) VertLabel[j].getComponent(0)).getText();
+            r[j] = new Real(Integer.parseInt(s));
+          }
+          RealTuple tr = new RealTuple(r);
+          m[2] = tr.getType();
+
+          TupleIface t = new Tuple(new TupleType(m), new Data[] {id, tc, tr});
+          RemoteColRow.setData(t);
+        }
+        catch (VisADException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+        }
+        catch (RemoteException exc) {
+          if (BasicSSCell.DEBUG) exc.printStackTrace();
+        }
+      }
+    }
+  }
+
+
+  // --- CELL & LABEL CONSTRUCTION ---
+
+  /**
+   * Ensures that the cells' preferred sizes match those of the labels.
+   */
+  private void synchLabelAndCellSizes() {
+    // resize spreadsheet cells
+    for (int j=0; j<NumVisY; j++) {
+      int h = VertLabel[j].getSize().height;
+      for (int i=0; i<NumVisX; i++) {
+        int w = HorizLabel[i].getSize().width;
+        DisplayCells[i][j].setPreferredSize(new Dimension(w, h));
+      }
+    }
+
+    // refresh display
+    HorizLabels.validate();
+    VertLabels.validate();
+    DisplayPanel.doLayout();
+    SCPane.validate();
+    refreshCells();
+  }
+
+  private void constructSpreadsheetCells(RemoteServer rs) {
+    String[][] labels = new String[NumVisX][NumVisY];
+    for (int i=0; i<NumVisX; i++) {
+      for (int j=0; j<NumVisY; j++) {
+        labels[i][j] = "" + Letters.charAt(i) + (j + 1);
+      }
+    }
+    constructSpreadsheetCells(labels, rs);
+  }
+
+  private void constructSpreadsheetCells(String[][] l, RemoteServer rs) {
+    synchronized (Lock) {
+      DisplayPanel.setLayout(new SSLayout(NumVisX, NumVisY, 5, 5));
+      DisplayCells = new FancySSCell[NumVisX][NumVisY];
+      for (int j=0; j<NumVisY; j++) {
+        int ph = VertLabel[j].getPreferredSize().height;
+        for (int i=0; i<NumVisX; i++) {
+          int pw = HorizLabel[i].getPreferredSize().width;
+          try {
+            FancySSCell f = (FancySSCell) BasicSSCell.getSSCellByName(l[i][j]);
+            if (f == null) {
+              f = createCell(l[i][j], rs);
+              f.addSSCellListener(this);
+              f.addMouseListener(this);
+              f.setAutoSwitch(AutoSwitch);
+              f.setAutoDetect(AutoDetect);
+              f.setAutoShowControls(AutoShowControls);
+              if (rs == null) f.setDimension(CanDo3D ?
+                BasicSSCell.JAVA3D_3D : BasicSSCell.JAVA2D_2D);
+              f.addDisplayListener(this);
+              if (rsi != null) {
+                // add new cell to server
+                f.addToRemoteServer(rsi);
+              }
+            }
+            f.setPreferredSize(new Dimension(pw, ph));
+            DisplayCells[i][j] = f;
+
+            DisplayPanel.add(DisplayCells[i][j]);
+          }
+          catch (VisADException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+            displayErrorMessage("Cannot construct spreadsheet cells. " +
+              "An error occurred", exc, "VisAD SpreadSheet error");
+          }
+          catch (RemoteException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+            displayErrorMessage("Cannot construct spreadsheet cells. " +
+              "A remote error occurred", exc, "VisAD SpreadSheet error");
+          }
+        }
+      }
+      selectCell(0, 0);
+    }
+  }
+
+  private void constructHorizontalLabels() {
+    constructHorizontalLabels(null, null);
+  }
+
+  private void constructHorizontalLabels(String[] l, int[] widths) {
+    if (l == null) {
+      l = new String[NumVisX];
+      for (int i=0; i<NumVisX; i++) l[i] = "" + Letters.charAt(i);
+    }
+    if (widths == null) {
+      widths = new int[NumVisX];
+      for (int i=0; i<NumVisX; i++) widths[i] = DEFAULT_VIS_WIDTH;
+    }
+    synchronized (Lock) {
+      HorizPanel.setLayout(new SSLayout(2 * NumVisX, 1, 0, 0));
+      HorizLabel = new JPanel[NumVisX];
+      HorizDrag = new JComponent[NumVisX];
+      for (int i=0; i<NumVisX; i++) {
+        HorizLabel[i] = new JPanel();
+        HorizLabel[i].setBorder(new LineBorder(Color.black, 1));
+        HorizLabel[i].setLayout(new BorderLayout());
+        HorizLabel[i].setPreferredSize(new Dimension(widths[i], LABEL_HEIGHT));
+        HorizLabel[i].add("Center", new JLabel(l[i], SwingConstants.CENTER));
+        HorizPanel.add(HorizLabel[i]);
+        HorizDrag[i] = new JComponent() {
+          public void paint(Graphics g) {
+            Dimension d = this.getSize();
+            g.setColor(Color.black);
+            g.drawRect(0, 0, d.width - 1, d.height - 1);
+            g.setColor(Color.yellow);
+            g.fillRect(1, 1, d.width - 2, d.height - 2);
+          }
+          public Dimension getPreferredSize() {
+            return new Dimension(5, LABEL_HEIGHT);
+          }
+        };
+        HorizDrag[i].setPreferredSize(new Dimension(5, 0));
+        HorizDrag[i].addMouseListener(this);
+        HorizDrag[i].addMouseMotionListener(this);
+        HorizPanel.add(HorizDrag[i]);
+      }
+    }
+  }
+
+  private void constructVerticalLabels() {
+    constructVerticalLabels(null, null);
+  }
+
+  private void constructVerticalLabels(String[] l, int[] heights) {
+    if (l == null) {
+      l = new String[NumVisY];
+      for (int i=0; i<NumVisY; i++) l[i] = "" + (i+1);
+    }
+    if (heights == null) {
+      heights = new int[NumVisY];
+      for (int i=0; i<NumVisY; i++) heights[i] = DEFAULT_VIS_HEIGHT;
+    }
+    synchronized (Lock) {
+      VertPanel.setLayout(new SSLayout(1, 2 * NumVisY, 0, 0));
+      VertLabel = new JPanel[NumVisY];
+      VertDrag = new JComponent[NumVisY];
+      for (int i=0; i<NumVisY; i++) {
+        VertLabel[i] = new JPanel();
+        VertLabel[i].setBorder(new LineBorder(Color.black, 1));
+        VertLabel[i].setLayout(new BorderLayout());
+        VertLabel[i].setPreferredSize(new Dimension(LABEL_WIDTH, heights[i]));
+        VertLabel[i].add("Center", new JLabel(l[i], SwingConstants.CENTER));
+        VertPanel.add(VertLabel[i]);
+        VertDrag[i] = new JComponent() {
+          public void paint(Graphics g) {
+            Dimension d = this.getSize();
+            g.setColor(Color.black);
+            g.drawRect(0, 0, d.width - 1, d.height - 1);
+            g.setColor(Color.yellow);
+            g.fillRect(1, 1, d.width - 2, d.height - 2);
+          }
+          public Dimension getPreferredSize() {
+            return new Dimension(LABEL_WIDTH, 5);
+          }
+        };
+        VertDrag[i].setBackground(Color.white);
+        VertDrag[i].setPreferredSize(new Dimension(0, 5));
+        VertDrag[i].addMouseListener(this);
+        VertDrag[i].addMouseMotionListener(this);
+        VertPanel.add(VertDrag[i]);
+      }
+    }
+  }
+
+  private void reconstructLabels(String[][] cellNames, int[] w, int[] h) {
+    // reconstruct horizontal labels
+    String[] hLabels = new String[NumVisX];
+    synchronized (Lock) {
+      HorizPanel.removeAll();
+      for (int i=0; i<NumVisX; i++) {
+        hLabels[i] = "" + cellNames[i][0].charAt(0);
+      }
+    }
+    constructHorizontalLabels(hLabels, w);
+
+    // reconstruct vertical labels
+    String[] vLabels = new String[NumVisY];
+    synchronized (Lock) {
+      VertPanel.removeAll();
+      for (int j=0; j<NumVisY; j++) vLabels[j] = cellNames[0][j].substring(1);
+    }
+    constructVerticalLabels(vLabels, h);
+  }
+
+  protected void reconstructSpreadsheet(String[][] cellNames, int[] w, int[] h,
+    RemoteServer rs)
+  {
+    // reconstruct labels
+    reconstructLabels(cellNames, w, h);
+
+    // reconstruct spreadsheet cells
+    synchronized (Lock) {
+      DisplayPanel.removeAll();
+      int ox, oy;
+      if (DisplayCells == null) {
+        ox = 0;
+        oy = 0;
+      }
+      else {
+        ox = DisplayCells.length;
+        oy = DisplayCells[0].length;
+      }
+      for (int i=0; i<ox; i++) {
+        for (int j=0; j<oy; j++) {
+          try {
+            String s = DisplayCells[i][j].getName();
+            boolean kill = true;
+            // only delete cells that are truly gone
+            for (int ii=0; ii<cellNames.length; ii++) {
+              for (int jj=0; jj<cellNames[ii].length; jj++) {
+                if (s.equals(cellNames[ii][jj])) kill = false;
+              }
+            }
+            if (kill) DisplayCells[i][j].destroyCell();
+          }
+          catch (VisADException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+            displayErrorMessage("Cannot destroy cell (7)", exc,
+              "VisAD SpreadSheet error");
+          }
+          catch (RemoteException exc) {
+            if (BasicSSCell.DEBUG) exc.printStackTrace();
+            displayErrorMessage("Cannot destroy cell (8)", exc,
+              "VisAD SpreadSheet error");
+          }
+          DisplayCells[i][j] = null;
+        }
+      }
+    }
+    constructSpreadsheetCells(cellNames, rs);
+
+    synchronized (Lock) {
+      // refresh display
+      HorizPanel.doLayout();
+      for (int i=0; i<NumVisX; i++) HorizLabel[i].doLayout();
+      VertPanel.doLayout();
+      for (int j=0; j<NumVisY; j++) VertLabel[j].doLayout();
+      SCPane.doLayout();
+      DisplayPanel.doLayout();
+      refreshCells();
+    }
+  }
+
+  private void reconstructHoriz(JPanel[] newLabels, JComponent[] newDrag,
+    FancySSCell[][] fcells)
+  {
+    synchronized (Lock) {
+      // reconstruct horizontal spreadsheet label layout
+      HorizLabel = newLabels;
+      HorizDrag = newDrag;
+      HorizPanel.setLayout(new SSLayout(2*NumVisX, 1, 0, 0));
+      for (int i=0; i<NumVisX; i++) {
+        HorizPanel.add(HorizLabel[i]);
+        HorizPanel.add(HorizDrag[i]);
+      }
+
+      // reconstruct spreadsheet cell layout
+      DisplayCells = fcells;
+      DisplayPanel.setLayout(new SSLayout(NumVisX, NumVisY, 5, 5));
+      for (int j=0; j<NumVisY; j++) {
+        for (int i=0; i<NumVisX; i++) DisplayPanel.add(DisplayCells[i][j]);
+      }
+
+      // refresh display
+      HorizPanel.validate();
+      HorizLabels.validate();
+      HorizLabels.repaint();
+      for (int i=0; i<NumVisX; i++) HorizLabel[i].validate();
+      SCPane.validate();
+      DisplayPanel.repaint();
+      refreshCells();
+    }
+
+    synchColRow();
+  }
+
+  private void reconstructVert(JPanel[] newLabels, JComponent[] newDrag,
+    FancySSCell[][] fcells)
+  {
+    synchronized (Lock) {
+      // reconstruct vertical spreadsheet label layout
+      VertLabel = newLabels;
+      VertDrag = newDrag;
+      VertPanel.setLayout(new SSLayout(1, 2*NumVisY, 0, 0));
+      for (int i=0; i<NumVisY; i++) {
+        VertPanel.add(VertLabel[i]);
+        VertPanel.add(VertDrag[i]);
+      }
+
+      // reconstruct spreadsheet cell layout
+      DisplayCells = fcells;
+      DisplayPanel.setLayout(new SSLayout(NumVisX, NumVisY, 5, 5));
+      for (int j=0; j<NumVisY; j++) {
+        for (int i=0; i<NumVisX; i++) DisplayPanel.add(DisplayCells[i][j]);
+      }
+
+      // refresh display
+      VertPanel.validate();
+      VertLabels.validate();
+      VertLabels.repaint();
+      for (int j=0; j<NumVisY; j++) VertLabel[j].validate();
+      SCPane.validate();
+      DisplayPanel.repaint();
+      refreshCells();
+    }
+
+    synchColRow();
+  }
+
+
+  // --- EVENT HANDLING ---
+
+  /**
+   * Handles checkbox menu item changes (dimension checkboxes).
+   */
+  public void itemStateChanged(ItemEvent e) {
+    String item = (String) e.getItem();
+    if (item.equals("3-D (Java3D)")) setDim(BasicSSCell.JAVA3D_3D);
+    else if (item.equals("2-D (Java2D)")) setDim(BasicSSCell.JAVA2D_2D);
+    else if (item.equals("2-D (Java3D)")) setDim(BasicSSCell.JAVA3D_2D);
+    else if (item.equals("Auto-switch to 3-D")) {
+      boolean b = e.getStateChange() == ItemEvent.SELECTED;
+      setAutoSwitch(b);
+    }
+    else if (item.equals("Auto-detect mappings")) {
+      boolean b = e.getStateChange() == ItemEvent.SELECTED;
+      setAutoDetect(b);
+    }
+    else if (item.equals("Auto-display controls")) {
+      boolean b = e.getStateChange() == ItemEvent.SELECTED;
+      setAutoShowControls(b);
+    }
+  }
+
+  /**
+   * Handles scrollbar changes.
+   */
+  public void adjustmentValueChanged(AdjustmentEvent e) {
+    Adjustable a = e.getAdjustable();
+    int value = a.getValue();
+
+    if (a.getOrientation() == Adjustable.HORIZONTAL) {
+      HorizLabels.setViewPosition(new Point(value, 0));
+    }
+    else {  // a.getOrientation() == Adjustable.VERTICAL
+      VertLabels.setViewPosition(new Point(0, value));
+    }
+  }
+
+  /**
+   * Handles display changes.
+   */
+  public void displayChanged(DisplayEvent e) {
+    if (e.getId() == DisplayEvent.MOUSE_PRESSED && !e.isRemote()) {
+      // highlight cell if it is the source of a local mouse click
+      String name = null;
+      try {
+        Display d = e.getDisplay();
+        name = d.getName();
+        if (name.endsWith(".remote")) {
+          // cloned cells need ".remote" stripped from their names
+          name = name.substring(0, name.length() - 7);
+        }
+      }
+      catch (VisADException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      catch (RemoteException exc) {
+        if (BasicSSCell.DEBUG) exc.printStackTrace();
+      }
+      FancySSCell fcell = (FancySSCell) BasicSSCell.getSSCellByName(name);
+      int ci = -1;
+      int cj = -1;
+      for (int j=0; j<NumVisY; j++) {
+        for (int i=0; i<NumVisX; i++) {
+          if (fcell == DisplayCells[i][j]) {
+            ci = i;
+            cj = j;
+          }
+        }
+      }
+      if (BasicSSCell.DEBUG && (ci < 0 || cj < 0)) {
+        System.err.println("Warning: an unknown display change occurred: " +
+          "display (" + name + ") has changed, but there is no " +
+          "corresponding SSCell with that name!");
+      }
+      selectCell(ci, cj);
+    }
+  }
+
+  /**
+   * BIG HAMMER HACK.
+   */
+  private boolean commandKey;
+
+  /**
+   * BIG HAMMER HACK.
+   */
+  private boolean shiftHeld;
+
+  /**
+   * BIG HAMMER HACK.
+   */
+  public void keyPressed(KeyEvent e) {
+    commandKey = true;
+    int keyCode = e.getKeyCode();
+    if (keyCode == KeyEvent.VK_ENTER) {
+      // enter pressed; notify action listener
+      FormulaListener.actionPerformed(new ActionEvent(FormulaEditor, 0, ""));
+    }
+    else if (keyCode == KeyEvent.VK_BACK_SPACE) {
+      // backspace pressed; delete text
+      String text = FormulaText.getText();
+      int start = FormulaText.getSelectionStart();
+      int end = FormulaText.getSelectionEnd();
+      if (start != end || start != 0) {
+        int pos = start == end ? start - 1 : start;
+        String pre = text.substring(0, pos);
+        String post = text.substring(end);
+        FormulaText.setText(pre + post);
+        FormulaText.getCaret().setDot(pos);
+      }
+    }
+    else if (keyCode == KeyEvent.VK_SHIFT) {
+      // shift pressed
+      shiftHeld = true;
+    }
+    else if (keyCode == KeyEvent.VK_LEFT) {
+      int pos = FormulaText.getCaretPosition();
+      if (shiftHeld) {
+        // shift + left arrow pressed; alter selection left
+        if (pos > 0) FormulaText.getCaret().moveDot(pos - 1);
+      }
+      else {
+        // left arrow pressed; move caret left
+        if (pos > 0) FormulaText.getCaret().setDot(pos - 1);
+      }
+    }
+    else if (keyCode == KeyEvent.VK_RIGHT) {
+      int pos = FormulaText.getCaretPosition();
+      if (shiftHeld) {
+        // shift + right arrow pressed; alter selection right
+        if (pos < FormulaText.getText().length()) {
+          FormulaText.getCaret().moveDot(pos + 1);
+        }
+      }
+      else {
+        // right arrow pressed; move caret right
+        if (pos < FormulaText.getText().length()) {
+          FormulaText.getCaret().setDot(pos + 1);
+        }
+      }
+    }
+    else if (keyCode == KeyEvent.VK_HOME) {
+      if (shiftHeld) {
+        // shift + home pressed; select to beginning of text
+        FormulaText.getCaret().moveDot(0);
+      }
+      else {
+        // home pressed; move to beginning of text
+        FormulaText.getCaret().setDot(0);
+      }
+    }
+    else if (keyCode == KeyEvent.VK_END) {
+      if (shiftHeld) {
+        // shift + end pressed; select to end of text
+        FormulaText.getCaret().moveDot(FormulaText.getText().length());
+      }
+      else {
+        // end pressed; move to end of text
+        FormulaText.getCaret().setDot(FormulaText.getText().length());
+      }
+    }
+    else commandKey = false;
+  }
+
+  /**
+   * BIG HAMMER HACK.
+   */
+  public void keyReleased(KeyEvent e) {
+    int keyCode = e.getKeyCode();
+    if (keyCode == KeyEvent.VK_SHIFT) shiftHeld = false;
+  }
+
+  /**
+   * BIG HAMMER HACK.
+   */
+  public void keyTyped(KeyEvent e) {
+    char key = e.getKeyChar();
+    if (!commandKey && !e.isActionKey() && key >= 32) {
+      int start = FormulaText.getSelectionStart();
+      int end = FormulaText.getSelectionEnd();
+      String text = FormulaText.getText();
+      FormulaText.setText(text.substring(0, start) +
+        key + text.substring(end));
+      FormulaText.getCaret().setDot(start + 1);
+    }
+    e.consume();
+  }
+
+  /**
+   * Old x value used with cell resizing logic.
+   */
+  private int oldX;
+
+  /**
+   * Old y value used with cell resizing logic.
+   */
+  private int oldY;
+
+  /**
+   * Handles mouse presses.
+   */
+  public void mousePressed(MouseEvent e) {
+    Component c = e.getComponent();
+    for (int j=0; j<NumVisY; j++) {
+      for (int i=0; i<NumVisX; i++) {
+        if (c == DisplayCells[i][j]) {
+          selectCell(i, j);
+          return;
+        }
+      }
+    }
+    oldX = e.getX();
+    oldY = e.getY();
+  }
+
+  /**
+   * Handles cell resizing.
+   */
+  public void mouseReleased(MouseEvent e) {
+    int x = e.getX();
+    int y = e.getY();
+    Component c = e.getComponent();
+    boolean change = false;
+    for (int j=0; j<NumVisX; j++) {
+      if (c == HorizDrag[j]) {
+        change = true;
+        break;
+      }
+    }
+    for (int j=0; j<NumVisY; j++) {
+      if (c == VertDrag[j]) {
+        change = true;
+        break;
+      }
+    }
+    if (change) synchLabelAndCellSizes();
+  }
+
+  /**
+   * Handles cell label resizing.
+   */
+  public void mouseDragged(MouseEvent e) {
+    Component c = e.getComponent();
+    int x = e.getX();
+    int y = e.getY();
+    for (int j=0; j<NumVisX; j++) {
+      if (c == HorizDrag[j]) {
+        // resize columns (labels)
+        Dimension s = HorizLabel[j].getSize();
+        int oldW = s.width;
+        s.width += x - oldX;
+        if (s.width < MIN_VIS_WIDTH) s.width = MIN_VIS_WIDTH;
+        HorizLabel[j].setSize(s);
+        HorizLabel[j].setPreferredSize(s);
+        HorizLabels.validate();
+        return;
+      }
+    }
+    for (int j=0; j<NumVisY; j++) {
+      if (c == VertDrag[j]) {
+        // resize rows (labels)
+        Dimension s = VertLabel[j].getSize();
+        int oldH = s.height;
+        s.height += y - oldY;
+        if (s.height < MIN_VIS_HEIGHT) s.height = MIN_VIS_HEIGHT;
+        VertLabel[j].setSize(s);
+        VertLabel[j].setPreferredSize(s);
+        VertLabels.validate();
+        return;
+      }
+    }
+  }
+
+  /**
+   * Unused MouseListener method.
+   */
+  public void mouseClicked(MouseEvent e) { }
+
+  /**
+   * Unused MouseListener method.
+   */
+  public void mouseEntered(MouseEvent e) { }
+
+  /**
+   * Unused MouseListener method.
+   */
+  public void mouseExited(MouseEvent e) { }
+
+  /**
+   * Unused MouseMotionListener method.
+   */
+  public void mouseMoved(MouseEvent e) { }
+
+  /**
+   * Handles changes in a cell's data.
+   */
+  public void ssCellChanged(SSCellChangeEvent e) {
+    FancySSCell f = (FancySSCell) e.getSSCell();
+    if (CurX < NumVisX && CurY < NumVisY && DisplayCells[CurX][CurY] == f) {
+      int ct = e.getChangeType();
+      if (ct == SSCellChangeEvent.DATA_CHANGE) {
+        refreshFormulaBar();
+        refreshMenuCommands();
+      }
+      else if (ct == SSCellChangeEvent.DISPLAY_CHANGE) {
+        refreshShowControls();
+        if (IsSlave) {
+          // slaves cannot send DATA_CHANGE notification
+          refreshFormulaBar();
+          refreshMenuCommands();
+        }
+      }
+      else if (ct == SSCellChangeEvent.DIMENSION_CHANGE) {
+        refreshDisplayMenuItems();
+      }
+    }
+  }
+
+
+  // --- SPREADSHEET API ---
+
+  /**
+   * Sets the SpreadSheet cell class to the given class (which must extend
+   * FancySSCell), used for creating SpreadSheet cells.
+   */
+  public static void setSSCellClass(Class c) {
+    try {
+      cellConstructor = c.getConstructor(cellArgs);
+    }
+    catch (NoSuchMethodException exc) {
+      if (BasicSSCell.DEBUG) exc.printStackTrace();
+    }
+  }
+
+  /**
+   * Selects the specified cell and updates screen info.
+   */
+  public void selectCell(int x, int y) {
+    if (x < 0) x = 0;
+    if (y < 0) y = 0;
+    if (x >= NumVisX) x = NumVisX - 1;
+    if (y >= NumVisY) y = NumVisY - 1;
+
+    // update borders of all cells
+    for (int j=0; j<NumVisY; j++) {
+      for (int i=0; i<NumVisX; i++) {
+        boolean selected = x == i && y == j;
+        DisplayCells[i][j].setSelected(selected);
+        DisplayCells[i][j].setAutoShowControls(selected && AutoShowControls);
+      }
+    }
+
+    // update spreadsheet info
+    CurX = x;
+    CurY = y;
+    FormulaText.getCaret().setVisible(true); // BIG HAMMER HACK
+    refreshFormulaBar();
+    refreshMenuCommands();
+    refreshDisplayMenuItems();
+  }
+
+  /**
+   * Pops up an option selection dialog for choosing SpreadSheet options.
+   */
+  protected boolean getOptions(int cols, int rows,
+    String server, String clone, boolean slave)
+  {
+    // Note: When the "Ok" button of this option dialog is pressed, the values
+    // of SpreadSheet fields are altered directly. After calling this method,
+    // another method like constructSpreadsheet() should be called to implement
+    // the user-chosen settings.
+
+    // set up the initial settings
+    final SSOptions options = new SSOptions(cols, rows, CanDo3D,
+      BugFix, BasicSSCell.DEBUG, server, clone, slave);
+
+    // set up main content pane
+    final boolean[] success = new boolean[1];
+    success[0] = false;
+    final JDialog dialog =
+      new JDialog((JFrame) null, "VisAD SpreadSheet Options", true);
+    JPanel pane = new JPanel();
+    dialog.setContentPane(pane);
+    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+
+    // set up first row of options
+    JPanel row1 = new JPanel();
+    row1.setLayout(new BoxLayout(row1, BoxLayout.X_AXIS));
+    JPanel row1Cols = new JPanel();
+    row1Cols.setLayout(new BoxLayout(row1Cols, BoxLayout.Y_AXIS));
+    row1Cols.add(new JLabel("Columns"));
+    final JTextField colField = new JTextField("" + cols);
+    Util.adjustTextField(colField);
+    colField.setEnabled(clone == null);
+    row1Cols.add(colField);
+    row1.add(row1Cols);
+    JPanel row1Xs = new JPanel();
+    row1Xs.setLayout(new BoxLayout(row1Xs, BoxLayout.Y_AXIS));
+    row1Xs.add(new JLabel(" x "));
+    row1Xs.add(new JLabel(" x "));
+    row1.add(row1Xs);
+    JPanel row1Rows = new JPanel();
+    row1Rows.setLayout(new BoxLayout(row1Rows, BoxLayout.Y_AXIS));
+    row1Rows.add(new JLabel("Rows"));
+    final JTextField rowField = new JTextField("" + rows);
+    Util.adjustTextField(rowField);
+    rowField.setEnabled(clone == null);
+    row1Rows.add(rowField);
+    row1.add(row1Rows);
+    JPanel row1Boxes = new JPanel();
+    row1Boxes.setLayout(new BoxLayout(row1Boxes, BoxLayout.Y_AXIS));
+    final JCheckBox java3d = new JCheckBox("Enable Java3D", CanDo3D);
+    java3d.setEnabled(Possible3D);
+    row1Boxes.add(java3d);
+    final JCheckBox toolBox = new JCheckBox("Enable toolbar", !BugFix);
+    row1Boxes.add(toolBox);
+    final JCheckBox debugBox = new JCheckBox("Debug mode", BasicSSCell.DEBUG);
+    row1Boxes.add(debugBox);
+    row1.add(row1Boxes);
+    pane.add(row1);
+
+    // set up second row of options
+    JPanel row2 = new JPanel();
+    row2.setLayout(new BoxLayout(row2, BoxLayout.X_AXIS));
+    ButtonGroup collabGroup = new ButtonGroup();
+    final JRadioButton serverChoice = new JRadioButton("Server");
+    serverChoice.setSelected(server != null && clone == null);
+    collabGroup.add(serverChoice);
+    row2.add(serverChoice);
+    final JRadioButton cloneChoice = new JRadioButton("Clone");
+    cloneChoice.setSelected(clone != null && !slave);
+    collabGroup.add(cloneChoice);
+    row2.add(cloneChoice);
+    final JRadioButton slaveChoice = new JRadioButton("Slave");
+    slaveChoice.setSelected(slave);
+    collabGroup.add(slaveChoice);
+    row2.add(slaveChoice);
+    final JRadioButton aloneChoice = new JRadioButton("Stand-alone");
+    aloneChoice.setSelected(server == null && clone == null);
+    collabGroup.add(aloneChoice);
+    row2.add(aloneChoice);
+    pane.add(row2);
+
+    // set up third row of options
+    JPanel row3 = new JPanel();
+    row3.setLayout(new BoxLayout(row3, BoxLayout.X_AXIS));
+    row3.add(new JLabel("Server name "));
+    final JTextField name = new JTextField(server == null ? "" : server);
+    Util.adjustTextField(name);
+    name.setEnabled(server != null);
+    row3.add(name);
+    pane.add(row3);
+
+    // set up fourth row of options
+    JPanel row4 = new JPanel();
+    row4.setLayout(new BoxLayout(row4, BoxLayout.X_AXIS));
+    row4.add(new JLabel("Server address "));
+    final JTextField host = new JTextField(clone == null ? "" : clone);
+    Util.adjustTextField(host);
+    host.setEnabled(clone != null);
+    row4.add(host);
+    pane.add(row4);
+
+    // set up fifth row of options
+    JPanel row5 = new JPanel();
+    row5.setLayout(new BoxLayout(row5, BoxLayout.X_AXIS));
+    boolean first = true;
+    String extras = "Extras: ";
+    if (Possible3D) {
+      extras = extras + (first ? "" : ", ") + "Java3D";
+      first = false;
+    }
+    if (CanDoHDF5) {
+      extras = extras + (first ? "" : ", ") + "HDF-5";
+      first = false;
+    }
+    if (CanDoJPEG) {
+      extras = extras + (first ? "" : ", ") + "JPEG snapshot";
+      first = false;
+    }
+    if (CanDoPython) {
+      extras = extras + (first ? "" : ", ") + "JPython scripting";
+      first = false;
+    }
+    if (first) extras = extras + "None";
+    extras = extras + ".";
+    row5.add(new JLabel(extras));
+    pane.add(row5);
+
+    // set up button row
+    JPanel buttons = new JPanel();
+    buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
+    final JButton ok = new JButton("Ok");
+    dialog.getRootPane().setDefaultButton(ok);
+    buttons.add(ok);
+    final JButton cancel = new JButton("Cancel");
+    buttons.add(cancel);
+    final JButton quit = new JButton("Quit");
+    buttons.add(quit);
+    pane.add(buttons);
+
+    // handle important events
+    ActionListener handler = new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        Object o = e.getSource();
+        if (o == serverChoice) {
+          colField.setEnabled(true);
+          rowField.setEnabled(true);
+          name.setEnabled(true);
+          host.setEnabled(false);
+        }
+        else if (o == cloneChoice || o == slaveChoice) {
+          colField.setEnabled(false);
+          rowField.setEnabled(false);
+          name.setEnabled(true);
+          host.setEnabled(true);
+        }
+        else if (o == aloneChoice) {
+          colField.setEnabled(true);
+          rowField.setEnabled(true);
+          name.setEnabled(false);
+          host.setEnabled(false);
+        }
+        else if (o == ok) {
+          // determine server type
+          boolean serv = serverChoice.isSelected();
+          boolean clon = cloneChoice.isSelected();
+          boolean slav = slaveChoice.isSelected();
+          boolean alon = aloneChoice.isSelected();
+
+          if (clon || slav) {
+            // column and row values are irrelevant for client sheets
+            options.cols = 2;
+            options.rows = 2;
+          }
+          else {
+            // get number of columns
+            options.cols = 0;
+            try {
+              options.cols = Integer.parseInt(colField.getText());
+            }
+            catch (NumberFormatException exc) { }
+            if (options.cols <= 0) {
+              displayErrorMessage(dialog, "The columns field must contain a " +
+                "number greater than zero", null, "Invalid value");
+              return;
+            }
+
+            // get number of rows
+            options.rows = 0;
+            try {
+              options.rows = Integer.parseInt(rowField.getText());
+            }
+            catch (NumberFormatException exc) { }
+            if (options.rows <= 0) {
+              displayErrorMessage(dialog, "The rows field must contain a " +
+                "number greater than zero", null, "Invalid value");
+              return;
+            }
+          }
+
+          // get Java3D toggle value
+          options.enable3d = java3d.isSelected();
+
+          // get toolbar toggle value
+          options.bugfix = !toolBox.isSelected();
+
+          // get debug toggle value
+          options.debug = debugBox.isSelected();
+
+          // get server name
+          options.name = alon ? null : name.getText();
+
+          // get server address
+          options.address = clon || slav ? host.getText() : null;
+
+          // get slave toggle value
+          options.slave = slav;
+
+          // everything ok, assign variables
+          NumVisX = options.cols;
+          NumVisY = options.rows;
+          CanDo3D = options.enable3d;
+          BasicSSCell.DEBUG = options.debug;
+          BugFix = options.bugfix;
+          serverName = options.name;
+          cloneAddress = options.address;
+          IsSlave = options.slave;
+
+          // return successfully
+          success[0] = true;
+          dialog.setVisible(false);
+        }
+        else if (o == cancel) {
+          success[0] = false;
+          dialog.setVisible(false);
+        }
+        else if (o == quit) {
+          // wow, just up and quit
+          System.exit(0);
+        }
+      }
+    };
+    serverChoice.addActionListener(handler);
+    cloneChoice.addActionListener(handler);
+    slaveChoice.addActionListener(handler);
+    aloneChoice.addActionListener(handler);
+    ok.addActionListener(handler);
+    cancel.addActionListener(handler);
+    quit.addActionListener(handler);
+
+    // display dialog
+    dialog.pack();
+    Util.centerWindow(dialog);
+    dialog.setVisible(true);
+
+    return success[0];
+  }
+
+  /**
+   * Inner class for use with getOptions().
+   */
+  public class SSOptions {
+    public int cols;
+    public int rows;
+    public boolean enable3d;
+    public boolean bugfix;
+    public boolean debug;
+    public String name;
+    public String address;
+    public boolean slave;
+
+    public SSOptions(int c, int r, boolean e, boolean b,
+      boolean d, String n, String a, boolean s)
+    {
+      cols = c;
+      rows = r;
+      enable3d = e;
+      bugfix = b;
+      debug = d;
+      name = n;
+      address = a;
+      slave = s;
+    }
+  }
+
+  /**
+   * Returns the JToolBar object for other programs to use (e.g., add buttons).
+   */
+  public JToolBar getToolbar() {
+    return Toolbar;
+  }
+
+  /**
+   * Returns a new instance of a spreadsheet cell (which must extend
+   * FancySSCell), used when a spreadsheet row or column is added.
+   */
+  protected FancySSCell createCell(String name, RemoteServer rs)
+    throws VisADException, RemoteException
+  {
+    Object[] args = {name, fm, rs, new Boolean(IsSlave), null, this};
+    if (cellConstructor == null) setSSCellClass(FancySSCell.class);
+    Object cell = null;
+    try {
+      cell = cellConstructor.newInstance(args);
+    }
+    catch (IllegalAccessException exc) {
+      exc.printStackTrace();
+    }
+    catch (InstantiationException exc) {
+      exc.printStackTrace();
+    }
+    catch (InvocationTargetException exc) {
+      if (exc.getTargetException() instanceof java.rmi.StubNotFoundException) {
+        System.err.println(
+          "Your VisAD installation has not properly executed the RMIC\n" +
+          "compiler on the appropriate source files. Please re-run\n" +
+          "\"make compile\" in the VisAD directory. If you are using\n" +
+          "Makefile.WinNT and running JDK 1.2, please double-check that\n" +
+          "you have uncommented the RMIC-related environment variables,\n" +
+          "or else the RMIC-related classes will be placed in the wrong\n" +
+          "directories. A full stack dump follows:\n");
+      }
+      exc.getTargetException().printStackTrace();
+    }
+    if (!(cell instanceof FancySSCell)) {
+      System.err.print("Cell constructor failed to " +
+        "produce a FancySSCell, but instead produced: ");
+      if (cell == null) System.err.println("null");
+      else System.err.println(cell.getClass().getName());
+      System.exit(3);
+    }
+    return (FancySSCell) cell;
+  }
+
+  /**
+   * Displays an error in a message dialog.
+   */
+  protected void displayErrorMessage(String msg, Exception exc, String title) {
+    displayErrorMessage(this, msg, exc, title);
+  }
+
+  /**
+   * Displays an error in a message dialog.
+   */
+  protected void displayErrorMessage(Component parent, String msg,
+    Exception exc, String title)
+  {
+    String s = (exc == null ? null : exc.getMessage());
+    final Component c = parent;
+    final String m = msg + (s == null ? "." : (": " + s));
+    final String t = title;
+    Util.invoke(false, BasicSSCell.DEBUG, new Runnable() {
+      public void run() {
+        JOptionPane.showMessageDialog(c, m, t, JOptionPane.ERROR_MESSAGE);
+      }
+    });
+  }
+
+  /**
+   * Adds a button to a toolbar.
+   */
+  protected JButton addToolbarButton(String file, String tooltip,
+    String command, boolean enabled, JComponent parent)
+  {
+    URL url = SpreadSheet.class.getResource(file + ".gif");
+    ImageIcon icon = new ImageIcon(url);
+    if (icon != null) {
+      JButton b = new JButton(icon);
+      b.setAlignmentY(JButton.CENTER_ALIGNMENT);
+      b.setToolTipText(tooltip);
+      b.addActionListener(this);
+      b.setActionCommand(command);
+      b.setEnabled(enabled);
+      if (parent instanceof JPanel) {
+        int w = icon.getIconWidth() + 4;
+        int h = icon.getIconHeight() + 4;
+        b.setPreferredSize(new Dimension(w, h));
+      }
+      parent.add(b);
+      return b;
+    }
+    else return null;
+  }
+
+
+  // --- DEPRECATED METHODS ---
+
+  /**
+   * @deprecated Use Util.adjustTextField(JTextField) instead.
+   */
+  public static void adjustTextField(JTextField field) {
+    Util.adjustTextField(field);
+  }
+
+  /**
+   * @deprecated Use Util.centerWindow(Window) instead.
+   */
+  public static void centerWindow(Window window) {
+    Util.centerWindow(window);
+  }
+
+}
diff --git a/visad/ss/add.gif b/visad/ss/add.gif
new file mode 100644
index 0000000..007b67c
Binary files /dev/null and b/visad/ss/add.gif differ
diff --git a/visad/ss/copy.gif b/visad/ss/copy.gif
new file mode 100644
index 0000000..36b0e86
Binary files /dev/null and b/visad/ss/copy.gif differ
diff --git a/visad/ss/cut.gif b/visad/ss/cut.gif
new file mode 100644
index 0000000..a96808a
Binary files /dev/null and b/visad/ss/cut.gif differ
diff --git a/visad/ss/del.gif b/visad/ss/del.gif
new file mode 100644
index 0000000..7e7d40d
Binary files /dev/null and b/visad/ss/del.gif differ
diff --git a/visad/ss/display.gif b/visad/ss/display.gif
new file mode 100644
index 0000000..0a088fb
Binary files /dev/null and b/visad/ss/display.gif differ
diff --git a/visad/ss/j2d.gif b/visad/ss/j2d.gif
new file mode 100644
index 0000000..acb91ad
Binary files /dev/null and b/visad/ss/j2d.gif differ
diff --git a/visad/ss/mappings.gif b/visad/ss/mappings.gif
new file mode 100644
index 0000000..220202a
Binary files /dev/null and b/visad/ss/mappings.gif differ
diff --git a/visad/ss/open.gif b/visad/ss/open.gif
new file mode 100644
index 0000000..19a7d96
Binary files /dev/null and b/visad/ss/open.gif differ
diff --git a/visad/ss/package.html b/visad/ss/package.html
new file mode 100644
index 0000000..237bab5
--- /dev/null
+++ b/visad/ss/package.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides a spreadsheet user interface for VisAD that can import data from
+any form VisAD supports and compute new data objects using formulas by
+utilizing the visad.formula package.
+
+</body>
+</html>
+
diff --git a/visad/ss/paste.gif b/visad/ss/paste.gif
new file mode 100644
index 0000000..326c348
Binary files /dev/null and b/visad/ss/paste.gif differ
diff --git a/visad/ss/reset.gif b/visad/ss/reset.gif
new file mode 100644
index 0000000..1aeb35c
Binary files /dev/null and b/visad/ss/reset.gif differ
diff --git a/visad/ss/save.gif b/visad/ss/save.gif
new file mode 100644
index 0000000..30be928
Binary files /dev/null and b/visad/ss/save.gif differ
diff --git a/visad/ss/show.gif b/visad/ss/show.gif
new file mode 100644
index 0000000..53f6a48
Binary files /dev/null and b/visad/ss/show.gif differ
diff --git a/visad/ss/tile.gif b/visad/ss/tile.gif
new file mode 100644
index 0000000..6cf0e55
Binary files /dev/null and b/visad/ss/tile.gif differ
diff --git a/visad/test/ContourHardTest.java b/visad/test/ContourHardTest.java
new file mode 100644
index 0000000..e021a1c
--- /dev/null
+++ b/visad/test/ContourHardTest.java
@@ -0,0 +1,82 @@
+package visad.test;
+
+import java.awt.BorderLayout;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.ContourControl;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.Field;
+import visad.FunctionType;
+import visad.GraphicsModeControl;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.data.netcdf.Plain;
+import visad.java3d.DisplayImplJ3D;
+import visad.util.ContourWidget;
+
+public class ContourHardTest {
+
+	public static void main(String[] args) throws Exception {
+
+	  String fn = "";
+	  try {
+	    fn = args[0];
+	  } catch (Exception e) {
+	    System.err.println("Must provide a netcdf file readable by visad.data.netcdf.Plain");
+	    System.exit(1);
+	  }
+	  
+	  System.err.println("Reading data...");
+	  Plain plain = new Plain();
+	  Field field = (Field) plain.open(args[0]);
+		
+		DisplayImpl display = new DisplayImplJ3D("display");
+		GraphicsModeControl gmc = display.getGraphicsModeControl();
+		gmc.setTextureEnable(false);
+		//gmc.setAdjustProjectionSeam(true);
+		
+		DataReference ref = new DataReferenceImpl("ref");
+		ref.setData(field);
+		display.addReference(ref);
+
+		FunctionType fcn = (FunctionType) field.getType();
+		RealType xType = (RealType) fcn.getDomain().getComponent(0);
+		RealType yType = (RealType) fcn.getDomain().getComponent(1);
+		
+		display.addMap(new ScalarMap(xType, Display.XAxis));
+		display.addMap(new ScalarMap(yType, Display.YAxis));
+		//display.addMap(new ScalarMap(RealType.Altitude, Display.RGB));
+		display.addMap(new ScalarMap(RealType.Altitude, Display.IsoContour));
+		
+		System.err.println("Setting up GUI...");
+		
+		JFrame jframe = new JFrame();
+		jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		jframe.add(display.getComponent());
+		jframe.pack();
+		jframe.setVisible(true);
+
+		ScalarMap map = (ScalarMap) display.getMapVector().lastElement();
+		ContourControl ctl = (ContourControl) map.getControl();
+		ctl.enableLabels(true);
+		ctl.setDashedStyle(GraphicsModeControl.DASH_STYLE);
+		ContourWidget cw = new ContourWidget(map);
+
+		JPanel big_panel = new JPanel();
+		big_panel.setLayout(new BorderLayout());
+		big_panel.add("Center", cw);
+
+		JFrame jframe2 = new JFrame();
+		jframe2.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+		jframe2.setContentPane(big_panel);
+		jframe2.pack();
+		jframe2.setVisible(true);
+
+		System.err.println("Init done");
+	}
+}
diff --git a/visad/test/FlatFieldCacheTest.java b/visad/test/FlatFieldCacheTest.java
new file mode 100644
index 0000000..6999a93
--- /dev/null
+++ b/visad/test/FlatFieldCacheTest.java
@@ -0,0 +1,172 @@
+package visad.test;
+
+import java.awt.BorderLayout;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.AnimationControl;
+import visad.ColorControl;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.DateTime;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.FieldImpl;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.Integer2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.bom.ImageRendererJ3D;
+import visad.data.FlatFieldCache;
+import visad.data.AreaImageAccessor;
+import visad.data.AreaImageCacheAdapter;
+import visad.java3d.DisplayImplJ3D;
+import visad.meteorology.SatelliteImage;
+
+/**
+ * Simple test for <code>AnimationControl</code> and
+ * <code>ImageRendererJ3D</code>.
+ */
+public class FlatFieldCacheTest extends JPanel {
+
+  private static Logger log = Logger.getLogger(FlatFieldCacheTest.class.getName());
+  
+  // array used to load AREA data into
+  private static int[][][] readCache;
+
+  private static int parseInt(String s) {
+    try {
+      return Integer.parseInt(s);
+    } catch (Exception e) {}
+    return -1;
+  }
+  
+  public static void main(String[] args) throws Exception {
+    
+    try {
+    Logger logger = Logger.getLogger("visad");
+    logger.setLevel(Level.FINE);
+    logger.setUseParentHandlers(false);
+    Handler console = new ConsoleHandler();
+    console.setLevel(Level.FINE);
+    console.setFormatter(new Formatter() {
+      public String format(LogRecord r) {
+        if (r.getThrown() != null) {
+          ByteArrayOutputStream buf = new ByteArrayOutputStream();
+          r.getThrown().printStackTrace(new PrintStream(buf));
+          return String.format("[%s] %s\n%s", r.getLevel().getName(), r.getMessage(), buf.toString());
+        }
+        return String.format("[%s] %s\n", r.getLevel().getName(), r.getMessage());
+      }
+    });
+    logger.addHandler(console);
+    
+    RealType elementType = RealType.getRealType("ImageElement");
+    RealType lineType = RealType.getRealType("ImageLine");
+    RealType bandType = RealType.getRealType("Band1");
+    
+    FunctionType imgType = new FunctionType(new RealTupleType(elementType, lineType), bandType);
+    FunctionType timePxlVal = new FunctionType(RealType.Time, imgType);
+    log.fine("Image type: " + imgType.toString());
+    log.fine("Animation type: " + timePxlVal.toString());
+    
+    FlatFieldCache cache = new FlatFieldCache(Integer.parseInt(args[1]));
+    
+    //
+    // load AREA files
+    //
+    File dir = 
+      new File(args[0]);
+    log.info("File location:"+dir.getPath());
+    File[] files = dir.listFiles(new FileFilter(){
+      public boolean accept(File pathname) {
+        if (pathname.getName().endsWith("area") && pathname.isFile()) {
+          return true;
+        }
+        return false;
+      }
+    });
+    
+    //
+    // Create and sort accessors
+    //
+    List<AreaImageAccessor> accessors = new ArrayList<AreaImageAccessor>();
+    int dwell = parseInt(args[2]);
+    int band = parseInt(args[3]);
+    int startLine = parseInt(args[4]);
+    int numLines = parseInt(args[5]);
+    int startElement = parseInt(args[6]);
+    int numElements = parseInt(args[7]);
+    int mag = parseInt(args[8]);
+    readCache = new int[1][numLines][numElements];
+    for (File file : files) {
+      AreaImageAccessor accessor = new AreaImageAccessor(file.getPath(), band, readCache);
+      accessor.setAreaParams(startLine, numLines, mag, startElement, numElements, mag);
+      accessors.add(accessor);
+    }
+    Collections.sort(accessors);
+
+    // Domain
+    FieldImpl timeFld = new FieldImpl(timePxlVal, new Integer1DSet(RealType.Time, accessors.size()));
+    for (int i = 0; i < accessors.size(); i++) {
+      SatelliteImage template = new SatelliteImage(imgType, new Integer2DSet(numElements, numLines),
+          new DateTime(i), "MET9 Satellite Image" + i, "MET9");
+      AreaImageCacheAdapter acc = new AreaImageCacheAdapter(template, accessors.get(i), cache);
+      timeFld.setSample(i, acc, false);
+    }
+
+    DataReference ref = new DataReferenceImpl("image");
+    ref.setData(timeFld);
+    
+    //
+    // Display related 
+    //
+    
+    DisplayImpl display = new DisplayImplJ3D("image display");
+    display.addMap(new ScalarMap(elementType, Display.XAxis));
+    display.addMap(new ScalarMap(lineType, Display.YAxis));
+    ScalarMap map = new ScalarMap(bandType, Display.RGB);
+    display.addMap(map);
+//    ColorControl ctrl = (ColorControl) map.getControl();
+//    ctrl.initGreyWedge();
+    
+    
+    ScalarMap aniMap = new ScalarMap(RealType.Time, Display.Animation);
+    display.addMap(aniMap);
+    AnimationControl aniCtrl = (AnimationControl) aniMap.getControl();
+    aniCtrl.setStep(dwell);
+    aniCtrl.setOn(true);
+    
+    ImageRendererJ3D renderer = new ImageRendererJ3D();
+    renderer.suggestBufImageType(BufferedImage.TYPE_BYTE_GRAY);
+    display.addReferences(renderer, ref);
+    JFrame frame = new JFrame("Image Animation Test");
+    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    frame.setLayout(new BorderLayout());
+    frame.add(display.getComponent());
+    frame.setSize(600, 600);
+    frame.setVisible(true);
+
+    } catch (Exception e) {
+      System.out.println("FlatFieldCacheTest <dir with .area files> <cache size> <dwell (ms)> <band> <startline> <numLines> <startelem> <numElems> <mag>");
+      e.printStackTrace();
+    }
+  }
+}
diff --git a/visad/test/ImageAnimationTest.java b/visad/test/ImageAnimationTest.java
new file mode 100644
index 0000000..b9add42
--- /dev/null
+++ b/visad/test/ImageAnimationTest.java
@@ -0,0 +1,151 @@
+package visad.test;
+
+import java.awt.BorderLayout;
+import java.rmi.RemoteException;
+
+import javax.media.j3d.BranchGroup;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import visad.AnimationControl;
+import visad.CoordinateSystem;
+import visad.GridCoordinateSystem;
+import visad.DataReference;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.Field;
+import visad.FieldImpl;
+import visad.FlatField;
+import visad.FunctionType;
+import visad.Integer1DSet;
+import visad.Linear2DSet;
+import visad.Gridded2DSet;
+import visad.RealTupleType;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.VisADException;
+import visad.bom.ImageRendererJ3D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.DisplayRendererJ3D;
+import visad.util.Util;
+
+/**
+ * Simple test for <code>AnimationControl</code> and <code>ImageRendererJ3D</code>.
+ */
+public class ImageAnimationTest extends JPanel {
+
+  ImageRendererJ3D imgRend;
+
+  public static final float[] makeSinusoidalSamples(int lenX, int lenY, int waveNum) {
+    float[] samples = new float[lenX*lenY];
+    int index = 0;
+    double PI = Math.PI;
+    for (int ii = 0; ii < lenX; ii++) {
+      for (int jj = 0; jj < lenY; jj++) {
+        samples[index] = (float) (
+            Math.sin((waveNum*PI) / 100 * jj) *
+            Math.sin((waveNum*PI) / 100 * ii));
+        index++;
+      }
+    }
+    return samples;
+  }
+  
+  protected float[][] makeSamples(int size, int num) {
+    float[][] samples = new float[num][size^2];
+    for (int i=0; i<num; i++)
+      samples[i] = makeSinusoidalSamples(size, size, i);
+    return samples;
+  }
+  
+  public ImageAnimationTest(int num, int size) throws VisADException, RemoteException {
+    
+    // (Time -> ((x, y) -> val))
+    RealTupleType pxlA = new RealTupleType(RealType.getRealType("x_a"), RealType.getRealType("y_a"));
+    //-Linear2DSet imgSet = new Linear2DSet(pxl, 0, size-1, size, 0, size-1, size); 
+    Linear2DSet set = new Linear2DSet(RealTupleType.Generic2D, 0, size-1, size, 0, size-1, size); 
+    float[][] locs = set.getSamples();
+    Gridded2DSet locs_set = new Gridded2DSet(pxlA, locs, size, size); 
+    GridCoordinateSystem gcs = new GridCoordinateSystem(locs_set);
+
+
+    //-RealTupleType pxl = new RealTupleType(RealType.getRealType("x"), RealType.getRealType("y"));
+    RealTupleType pxl = new RealTupleType(RealType.getRealType("x"), RealType.getRealType("y"), gcs, null);
+    Linear2DSet imgSet = new Linear2DSet(pxl, 0, size-1, size, 0, size-1, size); 
+    FunctionType pxlVal = new FunctionType(pxl, RealType.getRealType("val"));
+    FunctionType timePxlVal = new FunctionType(RealType.Time, pxlVal);
+    
+
+
+    Field timeFld = new FieldImpl(timePxlVal, new Integer1DSet(RealType.Time, num));
+    
+    float[][] samples = makeSamples(size, num);
+    
+    for (int i = 0; i < samples.length; i++) {
+      FlatField imgFld = new FlatField(pxlVal, imgSet);
+
+      imgFld.setSamples(new float[][]{samples[i]}, false);
+      
+      // set image for time
+      timeFld.setSample(i, imgFld, false);
+    }
+    System.out.println("makeSamples done");
+    
+    DataReference ref = new DataReferenceImpl("image");
+    ref.setData(timeFld);
+    
+    DisplayImpl display = new DisplayImplJ3D("image display");
+    imgRend = new ImageRendererJ3D();
+    System.err.println("Renderer:"+imgRend.toString());
+
+    //-display.addMap(new ScalarMap(RealType.getRealType("x"), Display.XAxis));
+    //-display.addMap(new ScalarMap(RealType.getRealType("y"), Display.YAxis));
+    display.addMap(new ScalarMap(RealType.getRealType("x_a"), Display.XAxis));
+    display.addMap(new ScalarMap(RealType.getRealType("y_a"), Display.YAxis));
+    display.addMap(new ScalarMap(RealType.getRealType("val"), Display.RGB));
+    
+    ScalarMap aniMap = new ScalarMap(RealType.Time, Display.Animation);
+    display.addMap(aniMap);
+    AnimationControl aniCtrl = (AnimationControl) aniMap.getControl();
+    aniCtrl.setStep(500);
+    aniCtrl.setOn(true);
+    display.addReferences(imgRend, ref);
+    
+    setLayout(new BorderLayout());
+    add(display.getComponent());
+    
+    try {
+      Thread.sleep(1000);
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    }
+
+    //-BranchGroup scene = ((DisplayRendererJ3D) imgRend.getDisplayRenderer()).getRoot();
+    //-Util.printSceneGraph(scene);
+  }
+
+  public static void main(String[] args) throws RemoteException, VisADException {
+    
+    int num = 0;
+    int size = 0;
+    try {
+      num = Integer.parseInt(args[0]);
+      size = Integer.parseInt(args[1]);
+    } catch (Exception e) {
+      System.err.println("USAGE: ImageAnimationTest <numImgs> <size>");
+      System.exit(1);
+    }
+    
+    Util.printJ3DProperties(null);
+    
+    JFrame frame = new JFrame("Image Animation Test");
+    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    frame.setLayout(new BorderLayout());
+    final ImageAnimationTest aniTest = new ImageAnimationTest(num, size);
+    frame.add(aniTest);
+    frame.setSize(600, 600);
+    frame.setVisible(true);
+
+  }
+}
diff --git a/visad/test/J3DTextureTest.java b/visad/test/J3DTextureTest.java
new file mode 100644
index 0000000..62bad59
--- /dev/null
+++ b/visad/test/J3DTextureTest.java
@@ -0,0 +1,270 @@
+package visad.test;
+
+import java.awt.BorderLayout;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.media.j3d.Appearance;
+import javax.media.j3d.BoundingSphere;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Canvas3D;
+import javax.media.j3d.Geometry;
+import javax.media.j3d.GeometryArray;
+import javax.media.j3d.Group;
+import javax.media.j3d.ImageComponent;
+import javax.media.j3d.ImageComponent2D;
+import javax.media.j3d.LineAttributes;
+import javax.media.j3d.PointAttributes;
+import javax.media.j3d.PolygonAttributes;
+import javax.media.j3d.QuadArray;
+import javax.media.j3d.RenderingAttributes;
+import javax.media.j3d.Shape3D;
+import javax.media.j3d.Switch;
+import javax.media.j3d.Texture;
+import javax.media.j3d.Texture2D;
+import javax.media.j3d.TextureAttributes;
+import javax.swing.JFrame;
+import javax.vecmath.Point3d;
+
+import visad.util.Util;
+
+import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
+import com.sun.j3d.utils.universe.SimpleUniverse;
+import com.sun.j3d.utils.universe.ViewingPlatform;
+
+/**
+ * Creates 2 textures, adds them to a switch, and changes the switch's active
+ * child on a timer. This is a Java3D implementation without any VisAD classes.
+ */
+public class J3DTextureTest extends Canvas3D {
+
+  public static final float BACK2D = -0.01f;
+  
+  private BranchGroup scene;
+  private Switch swit;
+  
+  public J3DTextureTest(int size) throws IOException {
+    
+    super(SimpleUniverse.getPreferredConfiguration());
+    createSceneGraph(size);
+    SimpleUniverse uverse = new SimpleUniverse(this);
+    ViewingPlatform vp = uverse.getViewingPlatform();
+    vp.setNominalViewingTransform();
+    OrbitBehavior orbit = new OrbitBehavior(uverse.getCanvas());
+    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 1.0);
+    orbit.setSchedulingBounds(bounds);
+    vp.setViewPlatformBehavior(orbit);
+    uverse.addBranchGraph(scene);
+    
+    Timer timer = new Timer();
+    timer.scheduleAtFixedRate(new TimerTask() {
+      int i = 0;
+      public void run() {
+        int idx = i++ % 3;
+        if (idx < 2) {
+          swit.setWhichChild(idx);
+          System.err.println("Switched to child " + idx);
+        } else {
+          swit.setWhichChild(Switch.CHILD_NONE);
+          System.err.println("Switched to NONE");
+        }
+        
+      }
+    }, 0, 2000);
+  }
+  
+  private void setGeometryCapabilities(GeometryArray array) {
+    array.setCapability(GeometryArray.ALLOW_COLOR_READ);
+    array.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
+    array.setCapability(GeometryArray.ALLOW_COUNT_READ);
+    array.setCapability(GeometryArray.ALLOW_FORMAT_READ);
+    array.setCapability(GeometryArray.ALLOW_NORMAL_READ);
+    array.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
+  }
+  
+  private Appearance makeAppearance(Geometry geometry) {
+
+    Appearance appearance = new Appearance();
+    appearance.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_MATERIAL_READ);
+    appearance.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_TEXGEN_READ);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+    // appearance.setCapability(Appearance.ALLOW_TEXTURE_UNIT_STATE_READ);
+    appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
+
+    LineAttributes line = new LineAttributes();
+    line.setCapability(LineAttributes.ALLOW_ANTIALIASING_READ);
+    line.setCapability(LineAttributes.ALLOW_PATTERN_READ);
+    line.setCapability(LineAttributes.ALLOW_WIDTH_READ);
+    int pattern = LineAttributes.PATTERN_SOLID;
+    line.setLinePattern(pattern);
+    appearance.setLineAttributes(line);
+
+    PointAttributes point = new PointAttributes();
+    point.setCapability(PointAttributes.ALLOW_ANTIALIASING_READ);
+    point.setCapability(PointAttributes.ALLOW_SIZE_READ);
+    appearance.setPointAttributes(point);
+
+    PolygonAttributes polygon = new PolygonAttributes();
+    polygon.setCapability(PolygonAttributes.ALLOW_CULL_FACE_READ);
+    polygon.setCapability(PolygonAttributes.ALLOW_MODE_READ);
+    polygon.setCapability(PolygonAttributes.ALLOW_NORMAL_FLIP_READ);
+    polygon.setCapability(PolygonAttributes.ALLOW_OFFSET_READ);
+    polygon.setCullFace(PolygonAttributes.CULL_NONE);
+
+    appearance.setPolygonAttributes(polygon);
+
+    RenderingAttributes rendering = new RenderingAttributes();
+    rendering.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_FUNCTION_READ);
+    rendering.setCapability(RenderingAttributes.ALLOW_ALPHA_TEST_VALUE_READ);
+    rendering.setCapability(RenderingAttributes.ALLOW_DEPTH_ENABLE_READ);
+
+    rendering.setDepthBufferEnable(true);
+    appearance.setRenderingAttributes(rendering);
+
+    return appearance;
+  }
+  
+  private void createSceneGraph(int size) throws IOException {
+    
+    scene = new BranchGroup();
+    scene.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    
+    swit = new Switch();
+    swit.setCapability(Switch.ALLOW_SWITCH_READ);
+    swit.setCapability(Switch.ALLOW_SWITCH_WRITE);
+    swit.setCapability(BranchGroup.ALLOW_DETACH);
+    swit.setCapability(Group.ALLOW_CHILDREN_READ);
+    swit.setCapability(Group.ALLOW_CHILDREN_WRITE);
+    swit.setCapability(Switch.ALLOW_CHILDREN_WRITE);
+    
+    textureToGroup(swit, makeGeometryArray(), makeImage(255,0,0,255, size, size));
+    textureToGroup(swit, makeGeometryArray(), makeImage(0,0,255,255, size, size));
+
+    scene.addChild(swit);
+    scene.compile();
+    
+    Util.printSceneGraph(scene);
+  }
+  
+  private GeometryArray makeGeometryArray() {
+
+    float width = .5f;
+    float height = .5f;
+    
+    float[] grid = new float[]{
+      -width, -height, 0f,
+       width, -height, 0f,
+       width,  height, 0f,
+      -width,  height, 0f
+    };
+    
+    float[] texCoord = new float[] {
+       -width, -height,
+        width, -height,
+        width,  height,
+       -width,  height
+    };
+    
+    QuadArray array = new QuadArray(4, 
+        GeometryArray.BY_REFERENCE | 
+        GeometryArray.COORDINATES | 
+        GeometryArray.TEXTURE_COORDINATE_2);
+    setGeometryCapabilities(array);
+    array.setCoordRefFloat(grid);
+    array.setTexCoordRefFloat(0, texCoord);
+
+    return array;
+  }
+  
+  private BufferedImage makeImage(int r, int b, int g, int a, int width, int height) {
+    System.err.println(String.format("Image color: RGBA(%s,%s,%s,%s)", r,g,b,a));
+    
+    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
+    
+    int pxl = 0;
+    pxl = 255 << 24;
+    pxl += r << 16;
+    pxl += g << 8;
+    pxl += b;
+    
+    int[] pxls = new int[image.getWidth()*image.getHeight()];
+    for (int i=0; i<pxls.length; i++) {
+      pxls[i] = pxl;
+    }
+    
+    image.setRGB(0, 0, image.getWidth(), image.getHeight(), pxls, 0, width);
+    
+    return image;
+  }
+  
+  public void textureToGroup(Group group, GeometryArray geometry, BufferedImage image) {
+    
+    Appearance appearance = makeAppearance(geometry);
+    TextureAttributes texture_attributes = new TextureAttributes();
+
+    texture_attributes.setTextureMode(TextureAttributes.REPLACE);
+
+    texture_attributes.setPerspectiveCorrectionMode(TextureAttributes.NICEST);
+    appearance.setTextureAttributes(texture_attributes);
+
+    Texture2D texture = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, image.getWidth(), image.getHeight());
+    
+    System.err.println(String.format("Image width:%s height:%s", image.getWidth(), image.getHeight()));
+    System.err.println(String.format("Texture width:%s height:%s", texture.getWidth(), texture.getHeight()));
+    System.err.println();
+    
+    texture.setCapability(Texture.ALLOW_IMAGE_READ);
+    ImageComponent2D image2d = new ImageComponent2D(ImageComponent.FORMAT_RGBA, image);
+    image2d.setCapability(ImageComponent.ALLOW_IMAGE_READ);
+    texture.setImage(0, image2d);
+
+    texture.setMinFilter(Texture.BASE_LEVEL_POINT);
+    texture.setMagFilter(Texture.BASE_LEVEL_POINT);
+    texture.setEnable(true);
+
+    Shape3D shape = new Shape3D(geometry, appearance);
+    shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
+    shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
+    appearance.setTexture(texture);
+    appearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
+
+    BranchGroup branch = new BranchGroup();
+    branch.setCapability(BranchGroup.ALLOW_DETACH);
+    branch.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
+    branch.addChild(shape);
+    if (group.numChildren() > 0) {
+      group.insertChild(branch, 0);
+    } else {
+      group.addChild(branch);
+    }
+  }
+  
+  public static void main(String[] args) throws IOException {
+    
+    int size = 0;
+    try {
+      size = Integer.parseInt(args[0]);
+    } catch (Exception e) {
+      System.err.println("You must provide an image size");
+      System.exit(1);
+    }
+    
+    JFrame frame = new JFrame("Texture Test Canvas");
+    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    frame.setLayout(new BorderLayout());
+    
+    J3DTextureTest canvas = new J3DTextureTest(size);
+    Util.printJ3DProperties(canvas);
+    frame.add(canvas);
+    frame.setSize(800, 600);
+    frame.setVisible(true);
+  }
+}
diff --git a/visad/test/TriangleStripBuilderTest.java b/visad/test/TriangleStripBuilderTest.java
new file mode 100644
index 0000000..d3f1462
--- /dev/null
+++ b/visad/test/TriangleStripBuilderTest.java
@@ -0,0 +1,179 @@
+package visad.test;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.rmi.RemoteException;
+import java.util.logging.Logger;
+
+import javax.media.j3d.View;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import ucar.netcdf.Netcdf;
+import ucar.netcdf.NetcdfFile;
+import visad.ContourControl;
+import visad.DataReferenceImpl;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.FieldImpl;
+import visad.GraphicsModeControl;
+import visad.RealType;
+import visad.ScalarMap;
+import visad.VisADException;
+import visad.data.netcdf.QuantityDB;
+import visad.data.netcdf.QuantityDBManager;
+import visad.data.netcdf.in.NetcdfAdapter;
+import visad.java3d.DisplayImplJ3D;
+import visad.util.CmdlineConsumer;
+import visad.util.CmdlineParser;
+
+public class TriangleStripBuilderTest implements CmdlineConsumer {
+
+  private static Logger log = Logger.getLogger(TriangleStripBuilderTest.class.getName());
+
+  private int verbosity = 0;
+  private String filename;
+
+  private CmdlineParser parser;
+
+  public TriangleStripBuilderTest() {
+    parser = new CmdlineParser(this);
+  }
+
+  public static void main(String[] args) throws Exception {
+
+//    Logger logger = Logger.getLogger("visad");
+//    logger.setLevel(Level.FINEST);
+//    logger.setUseParentHandlers(false);
+//    Handler console = new ConsoleHandler();
+//    console.setLevel(Level.FINEST);
+//    console.setFormatter(new Formatter() {
+//      public String format(LogRecord r) {
+//        if (r.getThrown() != null) {
+//          ByteArrayOutputStream buf = new ByteArrayOutputStream();
+//          r.getThrown().printStackTrace(new PrintStream(buf));
+//          return String.format("[%s] %s\n%s", r.getLevel().getName(), r.getMessage(), buf.toString());
+//        }
+//        return String.format("[%s] %s\n", r.getLevel().getName(), r.getMessage());
+//      }
+//    });
+//    logger.addHandler(console);
+
+    TriangleStripBuilderTest test = new TriangleStripBuilderTest();
+    test.parser.processArgs(args);
+
+    Netcdf netcdf = new NetcdfFile(test.filename, true);
+    QuantityDB db = QuantityDBManager.instance();
+    NetcdfAdapter adapter = new NetcdfAdapter(netcdf, db);
+    final FieldImpl data = (FieldImpl) adapter.getData();
+    log.info("loaded " + data.getType().toString());
+
+    final DisplayImpl display = new DisplayImplJ3D("display");
+
+    ScalarMap xmap = new ScalarMap(RealType.Longitude, Display.XAxis);
+    display.addMap(xmap);
+    ScalarMap ymap = new ScalarMap(RealType.Latitude, Display.YAxis);
+    display.addMap(ymap);
+    // ScalarMap zmap = new ScalarMap(RealType.Altitude, Display.ZAxis);
+    // display.addMap(zmap);
+    // zmap.setRange(-20, 20);
+    ScalarMap rgbaMap = new ScalarMap(RealType.Altitude, Display.RGBA);
+    display.addMap(rgbaMap);
+
+    ScalarMap map1contour = new ScalarMap(RealType.Altitude, Display.IsoContour);
+    display.addMap(map1contour);
+    ContourControl ctrl = (ContourControl) map1contour.getControl();
+    ctrl.setDashedStyle(GraphicsModeControl.SOLID_STYLE);
+    ctrl.setContourFill(true);
+
+    GraphicsModeControl mode = display.getGraphicsModeControl();
+    mode.setProjectionPolicy(View.PARALLEL_PROJECTION);
+    mode.setScaleEnable(true);
+
+    final DataReferenceImpl ref = new DataReferenceImpl("r");
+    ref.setData(data);
+//    display.addReference(ref, null);
+
+    JFrame jframe = new JFrame();
+    jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+    jframe.setLayout(new BorderLayout());
+    jframe.add((JPanel) display.getComponent(), BorderLayout.CENTER);
+    JPanel panel = new JPanel();
+    JButton start = new JButton("Start");
+    start.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        try {
+          display.addReference(ref, null);
+        } catch (RemoteException e) {
+          e.printStackTrace();
+        } catch (VisADException e) {
+          e.printStackTrace();
+        }
+      }
+    });
+    panel.add(start);
+    jframe.add(panel, BorderLayout.SOUTH);
+    jframe.pack();
+    jframe.setVisible(true);
+//
+//    ContourWidget cw = new ContourWidget(map1contour);
+//
+//    JPanel big_panel = new JPanel();
+//    big_panel.setLayout(new BorderLayout());
+//    big_panel.add("Center", cw);
+//
+//    JFrame jframe2 = new JFrame();
+//    jframe2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+//
+//    jframe2.setContentPane(big_panel);
+//    jframe2.pack();
+//    jframe2.setVisible(true);
+//
+////    LabeledColorWidget caw = new LabeledColorWidget(rgbaMap);
+//    JPanel big_panel2 = new JPanel();
+//    big_panel2.setLayout(new BorderLayout());
+//    big_panel2.add(caw, BorderLayout.CENTER);
+//    JFrame jframe3 = new JFrame();
+//    jframe3.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+//    jframe3.setContentPane(big_panel2);
+//    jframe3.pack();
+//    jframe3.setVisible(true);
+  }
+
+  public int checkKeyword(String mainName, int thisArg, String[] args) {
+    filename = args[thisArg];
+    if (!(new File(filename).exists())) {
+      return -1;
+    }
+    return 1;
+  }
+
+  public int checkOption(String mainName, char ch, String arg) {
+    switch (ch) {
+    case 'v':
+      verbosity++;
+      return 1;
+    }
+    return 0;
+  }
+
+  public boolean finalizeArgs(String mainName) {
+     visad.util.Util.configureLogging(verbosity);
+    return true;
+  }
+
+  public void initializeArgs() {
+  }
+
+  public String keywordUsage() {
+    return "";
+  }
+
+  public String optionUsage() {
+    return "[-v ...] <nc file>";
+  }
+
+}
diff --git a/visad/util/AnimationWidget.java b/visad/util/AnimationWidget.java
new file mode 100644
index 0000000..c4622cb
--- /dev/null
+++ b/visad/util/AnimationWidget.java
@@ -0,0 +1,437 @@
+//
+// AnimationWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+/* AWT packages */
+import java.awt.Color;
+import java.awt.Dimension;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/* JFC packages */
+import javax.swing.*;
+import javax.swing.event.*;
+
+/* RMI classes */
+import java.rmi.RemoteException;
+
+/* VisAD packages */
+import visad.*;
+
+/**
+ * A widget that allows users to control aspects of animation (stop/start,
+ * step, animation speed and direction).  It is initialized with the state
+ * of the AnimationControl for the ScalarMap used in the constructor. Once this
+ * widget is constructed, it should be used to control animation instead
+ * of using methods in AnimationControl.  Once constructed, changes made
+ * using AnimationControl methods will not be reflected in this widget.
+ */
+public class AnimationWidget
+  extends JPanel
+  implements ActionListener, ChangeListener, ControlListener,
+             ScalarMapListener
+{
+  private static final boolean DEBUG = false;
+
+  private boolean aDir;
+  private boolean aAnim;
+  private int aMs;
+
+  private JRadioButton forward;
+  private JRadioButton reverse;
+  private JButton onOff;
+  private JButton step;
+  private JTextField ms;
+  private JSlider TimeSlider;
+
+  private AnimationControl control;
+
+  /**
+   * construct an AnimationWidget linked to the Control in smap
+   * (which must be to Display.Animation) with auto-detecting ms/frame
+   *
+   * @param	smap	Display.Animation ScalarMap
+   */
+  public AnimationWidget(ScalarMap smap) throws VisADException,
+                                                RemoteException {
+    this(smap, -1);
+  }
+
+  /**
+   * construct an AnimationWidget linked to the Control in smap
+   * (which must be to Display.Animation) with specified ms/frame
+   *
+   * @param     smap    Display.Animation ScalarMap
+   * @param     st      animation speed (ms/frame).  If value is negative,
+   *                    the default speed set in the Control is used.
+   */
+  public AnimationWidget(ScalarMap smap, int st) throws VisADException,
+                                                        RemoteException {
+    // verify scalar map
+    if (!Display.Animation.equals(smap.getDisplayScalar())) {
+      throw new DisplayException("AnimationWidget: ScalarMap must " +
+                                 "be to Display.Animation");
+    }
+
+    // initialize control settings (these will change later)
+    aAnim = false;
+    aDir = true;
+    aMs = st;
+
+    // create JPanels
+    JPanel top = new JPanel();
+    JPanel bottom = new JPanel();
+    JPanel left = new JPanel();
+    JPanel right = new JPanel();
+
+    // set up layouts
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    top.setLayout(new BoxLayout(top, BoxLayout.X_AXIS));
+    bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS));
+    left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
+    right.setLayout(new BoxLayout(right, BoxLayout.Y_AXIS));
+
+    // create JComponents
+    forward = new JRadioButton("Forward", aDir);
+    reverse = new JRadioButton("Reverse", !aDir);
+    onOff = new JButton("Stop");
+    step = new JButton("Step");
+    ms = new JTextField("????????");
+
+    // WLH 2 Dec 98
+    Dimension msize = ms.getMaximumSize();
+    Dimension psize = ms.getPreferredSize();
+    msize.height = psize.height;
+    ms.setMaximumSize(msize);
+
+    JLabel msLabel = new JLabel("ms/frame");
+    TimeSlider = new JSlider(1, 1, 1);   /* DRM 1999-05-19 */
+
+    // set up JComponents
+    Color fore = msLabel.getForeground();
+    forward.setForeground(fore);
+    reverse.setForeground(fore);
+    onOff.setForeground(fore);
+    step.setForeground(fore);
+    ms.setForeground(fore);
+    TimeSlider.setPaintTicks(true);
+
+    // make sure onOff button stays the same size to avoid ...'s
+    // use step maximum size since "Step" has same size as "Stop"
+    onOff.setMaximumSize(step.getMaximumSize());
+
+    // group JRadioButtons
+    ButtonGroup group = new ButtonGroup();
+    group.add(forward);
+    group.add(reverse);
+
+    // align JComponents
+    left.setAlignmentX(JPanel.CENTER_ALIGNMENT);
+    left.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    right.setAlignmentX(JPanel.CENTER_ALIGNMENT);
+    right.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    onOff.setAlignmentX(JButton.CENTER_ALIGNMENT);
+    step.setAlignmentX(JButton.CENTER_ALIGNMENT);
+    ms.setAlignmentY(JTextField.CENTER_ALIGNMENT);
+    msLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+
+    // lay out JComponents
+    left.add(forward);
+    left.add(reverse);
+    right.add(onOff);
+    right.add(step);
+    top.add(left);
+    top.add(right);
+    add(top);
+    bottom.add(ms);
+    bottom.add(msLabel);
+    add(bottom);
+    add(TimeSlider);
+
+    // get control startup values.
+    getControlSettings((AnimationControl )smap.getControl());
+    if (st > 0) {
+      aMs = st;
+      if (control != null) control.setStep(aMs);
+    }
+    fixControlUI();
+
+    // add listeners
+    if (control != null) control.addControlListener(this);
+    smap.addScalarMapListener(this);
+    forward.addActionListener(this);
+    forward.setActionCommand("forward");
+    reverse.addActionListener(this);
+    reverse.setActionCommand("reverse");
+    onOff.addActionListener(this);
+    onOff.setActionCommand("go");
+    step.addActionListener(this);
+    step.setActionCommand("step");
+    ms.addActionListener(this);
+    ms.setActionCommand("ms");
+    TimeSlider.addChangeListener(this);
+  }
+
+  private void getControlSettings(AnimationControl ctl)
+  {
+    control = ctl;
+    if (control != null) {
+      aDir = control.getDirection();
+      aAnim = control.getOn();
+      aMs = (int )control.getStep();
+    }
+  }
+
+  private void fixAnimUI()
+  {
+    if (aAnim) {
+      onOff.setText("Stop");
+      step.setEnabled(false);
+    } else {
+      onOff.setText("Go");
+      step.setEnabled(true);
+    }
+  }
+
+  private void fixDirUI()
+  {
+    if (aDir) {
+      forward.setSelected(true);
+    } else {
+      reverse.setSelected(true);
+    }
+  }
+
+  private void fixSpeedUI()
+  {
+    ms.setText(Integer.toString(aMs));
+  }
+
+  private void fixSliderUI()
+  {
+    int max = 1;
+    int cur = 1;
+    Set set = null;
+    if(control != null) {
+      try {
+        set = control.getSet();
+        if (set != null) {
+          max = set.getLength();
+        } 
+      }
+      catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+      cur = control.getCurrent() + 1;
+      if (cur < 1) {
+        cur = 1;
+      } else if (cur > max) {
+        cur = max;
+      }
+    }
+
+    TimeSlider.setMaximum(max);
+    TimeSlider.setMinimum(1);
+    TimeSlider.setValue(cur);
+
+    int maj;
+    if (max < 20) {
+      maj = max/4;
+    } else if (max < 30) {
+      maj = max/6;
+    } else {
+      maj = max/8;
+    }
+
+    TimeSlider.setMajorTickSpacing(maj);
+    TimeSlider.setMinorTickSpacing(maj/4);
+    TimeSlider.setPaintLabels(set != null);
+    TimeSlider.repaint();
+  }
+
+  private void fixControlUI()
+  {
+    // update Stop/Go buttons
+    fixAnimUI();
+
+    // update direction radiobuttons
+    fixDirUI();
+
+    // update speed text
+    fixSpeedUI();
+
+    // update slider ticks
+    fixSliderUI();
+
+  }
+
+  /**
+   * ActionListener method used with JTextField and JButtons
+   */
+  public void actionPerformed(ActionEvent e) {
+    // WLH 28 March 2000
+    if (control == null) {
+      System.out.println("control == null in AnimationWidget.actionPerformed");
+      return;
+    }
+    String cmd = e.getActionCommand();
+    if (cmd.equals("forward")) {
+      try {
+        control.setDirection(true);
+        aDir = true;
+      }
+      catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+      catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); }
+    }
+    if (cmd.equals("reverse")) {
+      try {
+        control.setDirection(false);
+        aDir = false;
+      }
+      catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+      catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); }
+    }
+    if (cmd.equals("ms") || (cmd.equals("go") && !aAnim)) {
+      int fr = -1;
+      try {
+        fr = Integer.parseInt(ms.getText());
+      }
+      catch (NumberFormatException exc) { }
+      if (fr > 0) {
+        try {
+          control.setStep(fr);
+          aMs = fr;
+          if (aDir) forward.requestFocus();
+          else reverse.requestFocus();
+        }
+        catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+        catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); }
+      }
+      fixSpeedUI();
+    }
+    if (cmd.equals("go")) {
+      try {
+        control.setOn(!aAnim);
+        aAnim = !aAnim;
+        fixAnimUI();
+      }
+      catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+      catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); }
+    }
+    if (cmd.equals("step")) {
+      try {
+        // slider will adjust automatically with ControlListener
+        control.takeStep();
+      }
+      catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+      catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); }
+    }
+  }
+
+  /**
+   * ChangeListener method used with JSlider.
+   */
+  public void stateChanged(ChangeEvent e) {
+    // CTR 2 June 2000 - There is a bug in JDK 1.2.2+ where
+    // JSlider.getValueIsAdjusting() always seems to return true
+    // on Solaris platforms (but not on Windows platforms).
+    // if (!TimeSlider.getValueIsAdjusting())
+    try {
+      /* DRM 1999-05-19 */
+      if (control != null) control.setCurrent(TimeSlider.getValue()-1);
+    }
+    catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+    catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); }
+  }
+
+  /**
+   * ControlListener method used for programmatically moving JSlider
+   */
+  public void controlChanged(ControlEvent e) {
+    if (control != null) {
+      boolean newDir = control.getDirection();
+      if (newDir != aDir) {
+        aDir = newDir;
+        fixDirUI();
+      }
+
+      boolean newAnim = control.getOn();
+      if (newAnim != aAnim) {
+        aAnim = newAnim;
+        fixAnimUI();
+      }
+
+      int newMs = (int )control.getStep();
+      if (newMs != aMs) {
+        aMs = newMs;
+        fixSpeedUI();
+      }
+
+      fixSliderUI();
+    }
+  }
+
+  /**
+   * ScalarMapListener method used to recompute JSlider bounds
+   */
+  public void mapChanged(ScalarMapEvent e) {
+    fixSliderUI();
+  }
+
+  /**
+   * ScalarMapListener method used to detect new AnimationControl
+   */
+  public void controlChanged(ScalarMapControlEvent evt)
+  {
+    int id = evt.getId();
+    if (id == ScalarMapEvent.CONTROL_REMOVED ||
+        id == ScalarMapEvent.CONTROL_REPLACED)
+    {
+      evt.getControl().removeControlListener(this);
+      if (id == ScalarMapEvent.CONTROL_REMOVED) {
+        control = null;
+      }
+    }
+
+    if (id == ScalarMapEvent.CONTROL_REPLACED ||
+        id == ScalarMapEvent.CONTROL_ADDED)
+    {
+      control = (AnimationControl )(evt.getScalarMap().getControl());
+      getControlSettings(control);
+      fixControlUI();
+      if (control != null) control.addControlListener(this);
+    }
+  }
+
+  /**
+   * Work-around for Swing bug where pack() doesn't display slider labels;
+   * actually, it still won't, but window will be the right size
+   */
+  public Dimension getPreferredSize() {
+    Dimension d = super.getPreferredSize();
+    return new Dimension(d.width, d.height+18);
+  }
+}
diff --git a/visad/util/ArrowSlider.java b/visad/util/ArrowSlider.java
new file mode 100644
index 0000000..cc16d02
--- /dev/null
+++ b/visad/util/ArrowSlider.java
@@ -0,0 +1,344 @@
+/*
+
+@(#) $Id: ArrowSlider.java,v 1.17 2000-10-09 19:12:11 donm Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+/**
+ * A pointer slider for visad .
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.17 $, $Date: 2000-10-09 19:12:11 $
+ * @since Visad Utility Library v0.7.1
+ */
+
+public class ArrowSlider extends Slider implements MouseListener, MouseMotionListener {
+
+  /** The upper bound */
+  private float upper;
+
+  /** The lower bound */
+  private float lower;
+
+  /** The current value */
+  private float val;
+
+  /** widget sizes */
+  Dimension minSize = null;
+  Dimension prefSize = null;
+  Dimension maxSize = null;
+
+  private Object lock = new Object();
+
+  /** Construct a new arrow slider with the default values */
+  public ArrowSlider() {
+    this(-1, 1, 0);
+  }
+
+  /**
+   * Construct a new arrow slider with the givden lower, upper and initial values
+   * @throws IllegalArgumenentException if lower is not less than initial or initial
+   * is not less than upper
+   */
+  public ArrowSlider(float lower, float upper, float initial) {
+    this(lower, upper, initial, "value");
+  }
+
+  /**
+   * Construct a new arrow slider with the given lower, upper and initial values
+   * @throws IllegalArgumenentException if lower is not less than initial or initial
+   * is not less than upper
+   */
+  public ArrowSlider(float lower, float upper, float initial, String name) {
+
+    if (lower > initial) {
+      throw new IllegalArgumentException("ArrowSlider: lower bound is greater than initial value");
+    }
+
+    if (initial > upper) {
+      throw new IllegalArgumentException("ArrowSlider: initial value is greater than the upper bound");
+    }
+
+    this.upper = upper;
+    this.lower = lower;
+    this.val = initial;
+
+    this.name = name;
+
+    this.addMouseListener(this);
+    this.addMouseMotionListener(this);
+  }
+
+  /** For testing purposes */
+  public static void main(String[] argv) {
+
+    javax.swing.JFrame frame;
+    frame = new javax.swing.JFrame("Visad Arrow Slider");
+    frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+
+    ArrowSlider a = new ArrowSlider();
+
+    frame.getContentPane().add(a);
+
+    frame.setSize(a.getPreferredSize());
+    frame.setVisible(true);
+  }
+
+  /* CTR: 29 Jul 1998: added setBounds method */
+  /** Sets new minimum, maximum, and initial values for this slider */
+  // public synchronized void setBounds(float min, float max, float init) {
+  public void setBounds(float min, float max, float init) {
+    synchronized (lock) {
+      if (min > max) {
+        throw new IllegalArgumentException("ArrowSlider: min cannot be "
+                                           +"greater than max");
+      }
+      if (init < min || init > max) {
+        throw new IllegalArgumentException("ArrowSlider: initial value "
+                                           +"must be between min and max");
+      }
+      lower = min;
+      upper = max;
+      val = init;
+    }
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.LOWER_CHANGE, min));
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.UPPER_CHANGE, max));
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.VALUE_CHANGE, init));
+
+    // redraw
+    validate();
+    repaint();
+  }
+
+  /** Return the minimum value of this slider */
+  public float getMinimum() {
+    return lower;
+  }
+
+  /** Sets the minimum value for this slider */
+  // public synchronized void setMinimum(float value) {
+  public void setMinimum(float value) {
+    synchronized (lock) {
+      if (value > val || (value == val && value == upper)) {
+        throw new IllegalArgumentException("ArrowSlider: Attemped to set new minimum value greater than the current value");
+      }
+
+      lower = value;
+    }
+
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.LOWER_CHANGE, value));
+
+    // redraw
+    validate();
+    repaint();
+  }
+
+  /** Return the maximum value of this slider */
+  public float getMaximum() {
+    return upper;
+  }
+
+  /** Sets the maximum value of this scrolbar */
+  // public synchronized void setMaximum(float value){
+  public void setMaximum(float value){
+    synchronized (lock) {
+      if (value < val || (value == val && value == lower)) {
+        throw new IllegalArgumentException("ArrowSlider: Attemped to set new maximum value less than the current value");
+      }
+
+      upper = value;
+    }
+
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.UPPER_CHANGE, value));
+
+    // redraw
+    validate();
+    repaint();
+  }
+
+  /** Returns the current value of the slider */
+  public float getValue() {
+    return val;
+  }
+
+  /**
+   * Sets the current value of the slider
+   * @throws IllegalArgumentException if the new value is out of bounds for the slider
+   */
+  // public synchronized void setValue(float value) {
+  public void setValue(float value) {
+    synchronized (lock) {
+
+      if (value > upper || value < lower) {
+        throw new IllegalArgumentException("ArrowSlider: Attemped to set new value out of slider range");
+      }
+
+      val = value;
+    }
+
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.VALUE_CHANGE, value));
+
+    // redraw
+    validate();
+    repaint();
+  }
+
+  /** Return the preferred sise of the arrow slider */
+  public Dimension getPreferredSize() {
+    if (prefSize == null) {
+      prefSize = new Dimension(256, 16);
+    }
+    return prefSize;
+  }
+
+  /** Set the preferred size of the arrow slider */
+  public void setPreferredSize(Dimension dim) { prefSize = dim; }
+
+  /** Return the maximum size of the arrow slider */
+  public Dimension getMaximumSize() {
+    if (maxSize == null) {
+      maxSize = new Dimension(Integer.MAX_VALUE, 16);
+    }
+    return maxSize;
+  }
+
+  /** Set the preferred size of the arrow slider */
+  public void setMaximumSize(Dimension dim) { maxSize = dim; }
+
+  /** Return the minimum size of the arrow slider */
+  public Dimension getMinimumSize() {
+    if (minSize == null) {
+      minSize = new Dimension(40, 16);
+    }
+    return minSize;
+  }
+
+  /** Set the preferred size of the arrow slider */
+  public void setMinimumSize(Dimension dim) { minSize = dim; }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseClicked(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseEntered(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseExited(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Moves the slider to the clicked position */
+  public void mousePressed(MouseEvent e) {
+    //System.out.println(e.paramString());
+    updatePosition(e);
+  }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseReleased(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Updates the slider position */
+  public void mouseDragged(MouseEvent e) {
+    //System.out.println(e.paramString());
+    updatePosition(e);
+  }
+
+  /** Present to implement MouseMovementListener, currently ignored */
+  public void mouseMoved(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Recalculate the position and value of the slider given the new mouse position */
+  private void updatePosition(MouseEvent e) {
+    int x = e.getX();
+
+    if (x < 0) x = 0;
+    if (x >= getBounds().width) x = getBounds().width - 1;
+
+    float dist = (float) x / (float) (getBounds().width - 1);
+
+    setValue(lower + dist*(upper - lower));
+  }
+
+  /** the last position where the arrow was drawn */
+  private int oldxval;
+
+  /** update the slider */
+  public void update(Graphics g) {
+    g.setColor(Color.black);
+    g.drawLine(oldxval, 0, oldxval, getBounds().height - 1);
+    g.drawLine(oldxval, 0, oldxval - 4, 4);
+    g.drawLine(oldxval, 0, oldxval + 4, 4);
+
+    g.setColor(Color.white);
+
+    int xval = (int) Math.floor((val - lower) * (getBounds().width - 1) / (upper - lower));
+    g.drawLine(xval, 0, xval, getBounds().height - 1);
+    g.drawLine(xval, 0, xval - 4, 4);
+    g.drawLine(xval, 0, xval + 4, 4);
+
+    oldxval = xval;
+  }
+
+  /** Redraw the slider */
+  public void paint(Graphics g) {
+// System.out.println("ArrowSlider.paint in");
+// Test66 hangs between ArrowSlider.paint in and out
+// but not with DebugGraphics in constructor
+// the reason was JDC bug # 4252578 and the fact that
+// lower, upper and val were NaNs
+
+    if (val != val || lower != lower || upper != upper) return;
+
+    g.setColor(Color.black);
+    g.fillRect(0, 0, getBounds().width, getBounds().height);
+
+    g.setColor(Color.white);
+
+    int xval = (int) Math.floor((val - lower) * (getBounds().width - 1) / (upper - lower));
+    g.drawLine(xval, 0, xval, getBounds().height - 1);
+    g.drawLine(xval, 0, xval - 4, 4);
+    g.drawLine(xval, 0, xval + 4, 4);
+
+    oldxval = xval;
+// System.out.println("ArrowSlider.paint out");
+  }
+}
diff --git a/visad/util/BarGraph.java b/visad/util/BarGraph.java
new file mode 100644
index 0000000..e150997
--- /dev/null
+++ b/visad/util/BarGraph.java
@@ -0,0 +1,428 @@
+//
+// BarGraph.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import javax.swing.*;
+import java.awt.*;
+import java.rmi.RemoteException;
+import visad.*;
+import visad.java3d.*;
+
+/** BarGraph provides methods for plotting colored bar graphs in 2D or 3D. */
+public class BarGraph {
+
+  // -- Constants --
+
+  protected static final RealType BAR_X = RealType.getRealType("Bar_X");
+  protected static final RealType BAR_Y = RealType.getRealType("Bar_Y");
+  protected static final RealType BAR_Z = RealType.getRealType("Bar_Z");
+  protected static final RealType BAR_R = RealType.getRealType("Bar_Red");
+  protected static final RealType BAR_G = RealType.getRealType("Bar_Green");
+  protected static final RealType BAR_B = RealType.getRealType("Bar_Blue");
+  protected static final FunctionType BOX_2D;
+  protected static final FunctionType BOX_3D;
+  static {
+    FunctionType func2d = null, func3d = null;
+    try {
+      RealTupleType rgb = new RealTupleType(BAR_R, BAR_G, BAR_B);
+      func2d = new FunctionType(new RealTupleType(BAR_X, BAR_Y), rgb);
+      func3d = new FunctionType(new RealTupleType(BAR_X, BAR_Y, BAR_Z), rgb);
+    }
+    catch (VisADException exc) { exc.printStackTrace(); }
+    BOX_2D = func2d;
+    BOX_3D = func3d;
+  }
+
+  // -- 2D bar graph method signatures --
+
+  /**
+   * Constructs a 2D bar graph.
+   * @param heights Height of each bar
+   * @param spacing Spacing between bars (at least 0, and less than 1)
+   * @param colors Color of each bar
+   */
+  
+  public static FlatField makeBarGraph2D(float[] heights, float spacing,
+    Color[] colors) throws VisADException, RemoteException
+  {
+    return makeBarGraph2D(BOX_2D, heights, spacing, colors);
+  }
+
+  /**
+   * Constructs a 2D bar graph.
+   * @param type MathType to use, of the form ((X, Y) -> (R, G, B))
+   * @param heights Height of each bar
+   * @param spacing Spacing between bars (at least 0, and less than 1)
+   * @param colors Color of each bar
+   */
+  
+  public static FlatField makeBarGraph2D(FunctionType type, float[] heights,
+    float spacing, Color[] colors) throws VisADException, RemoteException
+  {
+    if (heights == null) throw new VisADException("Heights is null");
+    int len = heights.length;
+    float[] x1 = new float[len], y1 = new float[len];
+    float[] x2 = new float[len], y2 = new float[len];
+    float s = spacing / 2;
+    for (int i=0; i<len; i++) {
+      x1[i] = i + s;
+      y1[i] = 0;
+      x2[i] = i + 1 - s;
+      y2[i] = heights[i];
+    }
+    return makeBoxes2D(type, x1, y1, x2, y2, colors);
+  }
+
+  public static FlatField makeBoxes2D(float[] x1, float[] y1,
+    float[] x2, float[] y2, Color[] c) throws VisADException, RemoteException
+  {
+    return makeBoxes2D(BOX_2D, x1, y1, x2, y2, c);
+  }
+
+  public static FlatField makeBoxes2D(float[] x1, float[] y1,
+    float[] x2, float[] y2, float[] r, float[] g, float[] b)
+    throws VisADException, RemoteException
+  {
+    return makeBoxes2D(BOX_2D, x1, y1, x2, y2, r, g, b);
+  }
+
+  public static FlatField makeBoxes2D(FunctionType type,
+    float[] x1, float[] y1, float[] x2, float[] y2, Color[] c)
+    throws VisADException, RemoteException
+  {
+    float[][] rgb = extractColors(c);
+    return makeBoxes2D(BOX_2D, x1, y1, x2, y2, rgb[0], rgb[1], rgb[2]);
+  }
+
+  public static FlatField makeBoxes2D(FunctionType type,
+    float[] x1, float[] y1, float[] x2, float[] y2,
+    float[] r, float[] g, float[] b) throws VisADException, RemoteException
+  {
+    if (type == null) throw new VisADException("Type is null");
+    if (x1 == null || y1 == null || x2 == null || y2 == null) {
+      throw new VisADException("Coordinates are null");
+    }
+    if (r == null || g == null || b == null) {
+      throw new VisADException("Color values are null");
+    }
+    int len = x1.length;
+    if (len != y1.length || len != x2.length || len != y2.length ||
+      len != r.length || len != g.length || len != b.length)
+    {
+      throw new VisADException("Lengths do not match");
+    }
+    RealTupleType domain = type.getDomain();
+    Gridded2DSet[] sets = new Gridded2DSet[len];
+    float[][] colors = new float[3][4 * len];
+    for (int i=0; i<len; i++) {
+      float[][] samples = {
+        {x1[i], x2[i], x1[i], x2[i]},
+        {y1[i], y1[i], y2[i], y2[i]}
+      };
+      sets[i] = new Gridded2DSet(domain,
+        samples, 2, 2, null, null, null, false);
+      for (int j=0; j<4; j++) {
+        int ndx = 4 * i + j;
+        colors[0][ndx] = r[i];
+        colors[1][ndx] = g[i];
+        colors[2][ndx] = b[i];
+      }
+    }
+    UnionSet uset = new UnionSet(domain, sets);
+    FlatField ff = new FlatField(type, uset);
+    ff.setSamples(colors, false);
+    return ff;
+  }
+
+  // -- 3D bar graph method signatures --
+
+  /**
+   * Constructs a 3D bar graph.
+   * @param heights Height of each bar (dimensioned cols X rows)
+   * @param spacing Spacing between bars (at least 0, and less than 1)
+   * @param colors Color of each bar (dimensioned cols X rows)
+   */
+  
+  public static FlatField makeBarGraph3D(float[][] heights, float spacing,
+    Color[][] colors) throws VisADException, RemoteException
+  {
+    return makeBarGraph3D(BOX_2D, heights, spacing, colors);
+  }
+
+  /**
+   * Constructs a 3D bar graph.
+   * @param type MathType to use, of the form ((X, Y, Z) -> (R, G, B))
+   * @param heights Height of each bar (dimensioned cols X rows)
+   * @param spacing Spacing between bars (at least 0, and less than 1)
+   * @param colors Color of each bar (dimensioned cols X rows)
+   */
+  
+  public static FlatField makeBarGraph3D(FunctionType type, float[][] heights,
+    float spacing, Color[][] colors) throws VisADException, RemoteException
+  {
+    if (heights == null) throw new VisADException("Heights is null");
+    if (colors == null) throw new VisADException("Colors are null");
+    int lenX = heights.length;
+    if (lenX < 1) throw new VisADException("Not enough bars");
+    if (lenX != colors.length) {
+      throw new VisADException("Lengths do not match");
+    }
+    int lenY = heights[0].length;
+    for (int c=0; c<lenX; c++) {
+      if (heights[c] == null) {
+        throw new VisADException("Heights[" + c + "] is null");
+      }
+      if (colors[c] == null) {
+        throw new VisADException("Colors[" + c + "] is null");
+      }
+      if (lenY != heights[c].length || lenY != colors[c].length) {
+        throw new VisADException("Lengths do not match");
+      }
+    }
+    int len = lenX * lenY;
+    float[] x1 = new float[len], y1 = new float[len], z1 = new float[len];
+    float[] x2 = new float[len], y2 = new float[len], z2 = new float[len];
+    Color[] cols = new Color[len];
+    float s = spacing / 2;
+    for (int r=0; r<lenY; r++) {
+      for (int c=0; c<lenX; c++) {
+        int i = lenX * r + c;
+        x1[i] = c + s;
+        y1[i] = r + s;
+        z1[i] = 0;
+        x2[i] = c + 1 - s;
+        y2[i] = r + 1 - s;
+        z2[i] = heights[c][r];
+        cols[i] = colors[c][r];
+      }
+    }
+    return makeBoxes3D(type, x1, y1, z1, x2, y2, z2, cols);
+  }
+
+  public static FlatField makeBoxes3D(float[] x1, float[] y1, float[] z1,
+    float[] x2, float[] y2, float[] z2, Color[] c)
+    throws VisADException, RemoteException
+  {
+    return makeBoxes3D(BOX_3D, x1, y1, z1, x2, y2, z2, c);
+  }
+
+  public static FlatField makeBoxes3D(float[] x1, float[] y1, float[] z1,
+    float[] x2, float[] y2, float[] z2, float[] r, float[] g, float[] b)
+    throws VisADException, RemoteException
+  {
+    return makeBoxes3D(BOX_3D, x1, y1, z1, x2, y2, z2, r, g, b);
+  }
+
+  public static FlatField makeBoxes3D(FunctionType type,
+    float[] x1, float[] y1, float[] z1, float[] x2, float[] y2, float[] z2,
+    Color[] c) throws VisADException, RemoteException
+  {
+    float[][] rgb = extractColors(c);
+    return makeBoxes3D(BOX_3D,
+      x1, y1, z1, x2, y2, z2, rgb[0], rgb[1], rgb[2]);
+  }
+
+  public static FlatField makeBoxes3D(FunctionType type,
+    float[] x1, float[] y1, float[] z1, float[] x2, float[] y2, float[] z2,
+    float[] r, float[] g, float[] b) throws VisADException, RemoteException
+  {
+    if (type == null) throw new VisADException("Type is null");
+    if (x1 == null || y1 == null || z1 == null ||
+      x2 == null || y2 == null || z2 == null)
+    {
+      throw new VisADException("Coordinates are null");
+    }
+    if (r == null || g == null || b == null) {
+      throw new VisADException("Color values are null");
+    }
+    int len = x1.length;
+    if (len != y1.length || len != z1.length ||
+      len != x2.length || len != y2.length || len != z2.length ||
+      len != r.length || len != g.length || len != b.length)
+    {
+      throw new VisADException("Lengths do not match");
+    }
+
+    RealTupleType domain = type.getDomain();
+    UnionSet[] sets = new UnionSet[len];
+    float[][] colors = new float[3][4 * 6 * len];
+    for (int i=0; i<len; i++) {
+      float[][] bottomSamples = {
+        {x1[i], x2[i], x1[i], x2[i]},
+        {y1[i], y1[i], y2[i], y2[i]},
+        {z1[i], z1[i], z1[i], z1[i]}
+      };
+      Gridded3DSet bottom = new Gridded3DSet(domain,
+        bottomSamples, 2, 2, null, null, null, false);
+
+      float[][] topSamples = {
+        {x1[i], x2[i], x1[i], x2[i]},
+        {y1[i], y1[i], y2[i], y2[i]},
+        {z2[i], z2[i], z2[i], z2[i]}
+      };
+      Gridded3DSet top = new Gridded3DSet(domain,
+        topSamples, 2, 2, null, null, null, false);
+
+      float[][] frontSamples = {
+        {x1[i], x2[i], x1[i], x2[i]},
+        {y1[i], y1[i], y1[i], y1[i]},
+        {z1[i], z1[i], z2[i], z2[i]}
+      };
+      Gridded3DSet front = new Gridded3DSet(domain,
+        frontSamples, 2, 2, null, null, null, false);
+
+      float[][] backSamples = {
+        {x1[i], x2[i], x1[i], x2[i]},
+        {y2[i], y2[i], y2[i], y2[i]},
+        {z1[i], z1[i], z2[i], z2[i]}
+      };
+      Gridded3DSet back = new Gridded3DSet(domain,
+        backSamples, 2, 2, null, null, null, false);
+
+      float[][] leftSamples = {
+        {x1[i], x1[i], x1[i], x1[i]},
+        {y1[i], y2[i], y1[i], y2[i]},
+        {z1[i], z1[i], z2[i], z2[i]}
+      };
+      Gridded3DSet left = new Gridded3DSet(domain,
+        leftSamples, 2, 2, null, null, null, false);
+
+      float[][] rightSamples = {
+        {x2[i], x2[i], x2[i], x2[i]},
+        {y1[i], y2[i], y1[i], y2[i]},
+        {z1[i], z1[i], z2[i], z2[i]}
+      };
+      Gridded3DSet right = new Gridded3DSet(domain,
+        rightSamples, 2, 2, null, null, null, false);
+
+      sets[i] = new UnionSet(domain,
+        new SampledSet[] {bottom, top, front, back, left, right});
+
+      for (int j=0; j<24; j++) {
+        int ndx = 24 * i + j;
+        colors[0][ndx] = r[i];
+        colors[1][ndx] = g[i];
+        colors[2][ndx] = b[i];
+      }
+    }
+    UnionSet uset = new UnionSet(domain, sets);
+    FlatField ff = new FlatField(type, uset);
+    ff.setSamples(colors, false);
+    return ff;
+  }
+
+  // -- Helper methods --
+
+  /** Converts java.awt.Color objects to floating point RGB values. */
+  public static float[][] extractColors(Color[] c) {
+    if (c == null) return new float[][] {null, null, null};
+    float[][] rgb = new float[3][c.length];
+    for (int i=0; i<c.length; i++) {
+      rgb[0][i] = c[i].getRed() / 255f;
+      rgb[1][i] = c[i].getGreen() / 255f;
+      rgb[2][i] = c[i].getBlue() / 255f;
+    }
+    return rgb;
+  }
+
+  // -- Main method --
+
+  /** Run 'java visad.util.BarGraph' to test bar graphing. */
+  public static void main(String[] argv)
+    throws VisADException, RemoteException
+  {
+    JFrame frame = new JFrame("Bar Graphs in VisAD");
+    JPanel pane = new JPanel();
+    pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
+    frame.setContentPane(pane);
+
+    // 2D bar graph
+    float[] heights2 = {4, 7, 5, 11, 9};
+    float max2 = 12; // top of graph
+    Color[] colors2 = {Color.red, Color.yellow,
+      Color.green, Color.gray, Color.magenta};
+    FlatField barGraph2 = makeBarGraph2D(heights2, 0.2f, colors2);
+
+    DisplayImplJ3D d2 = new DisplayImplJ3D("d2", new TwoDDisplayRendererJ3D());
+    ScalarMap xMap2 = new ScalarMap(BAR_X, Display.XAxis);
+    xMap2.setRange(0, heights2.length);
+    d2.addMap(xMap2);
+    ScalarMap yMap2 = new ScalarMap(BAR_Y, Display.YAxis);
+    yMap2.setRange(0, max2);
+    d2.addMap(yMap2);
+    d2.addMap(new ScalarMap(BAR_R, Display.Red));
+    d2.addMap(new ScalarMap(BAR_G, Display.Green));
+    d2.addMap(new ScalarMap(BAR_B, Display.Blue));
+    DataReferenceImpl ref2 = new DataReferenceImpl("ref2");
+    ref2.setData(barGraph2);
+    d2.addReference(ref2);
+    pane.add(d2.getComponent());
+    d2.getGraphicsModeControl().setScaleEnable(true);
+
+    // 3D bar graph
+    float[][] heights3 = {
+      {8, 7, 5, 14, 9},
+      {13, 1, 19, 7, 16},
+      {6, 11, 12, 13, 4}
+    };
+    float max3 = 20; // top of graph
+    Color darkPink = Color.pink.darker();
+    Color darkYellow = Color.yellow.darker();
+    Color darkMagenta = Color.magenta.darker();
+    Color[][] colors3 = {
+      {Color.red, Color.yellow, Color.green, Color.gray, Color.magenta},
+      {Color.blue, Color.white, Color.orange, Color.pink, Color.lightGray},
+      {Color.cyan, Color.darkGray, darkYellow, darkPink, darkMagenta}
+    };
+    FlatField barGraph3 = makeBarGraph3D(heights3, 0.2f, colors3);
+
+    DisplayImplJ3D d3 = new DisplayImplJ3D("d3");
+    ScalarMap xMap3 = new ScalarMap(BAR_X, Display.XAxis);
+    xMap3.setRange(0, heights3.length);
+    d3.addMap(xMap3);
+    ScalarMap yMap3 = new ScalarMap(BAR_Y, Display.YAxis);
+    yMap3.setRange(0, heights3[0].length);
+    d3.addMap(yMap3);
+    ScalarMap zMap3 = new ScalarMap(BAR_Z, Display.ZAxis);
+    zMap3.setRange(0, max3);
+    d3.addMap(zMap3);
+    d3.addMap(new ScalarMap(BAR_R, Display.Red));
+    d3.addMap(new ScalarMap(BAR_G, Display.Green));
+    d3.addMap(new ScalarMap(BAR_B, Display.Blue));
+    DataReferenceImpl ref3 = new DataReferenceImpl("ref3");
+    ref3.setData(barGraph3);
+    d3.addReference(ref3);
+    pane.add(d3.getComponent());
+    d3.getGraphicsModeControl().setScaleEnable(true);
+
+    Dimension ss = Toolkit.getDefaultToolkit().getScreenSize();
+    int w = ss.width - 100;
+    frame.setBounds(50, 50, w, w / 2);
+    frame.show();
+  }
+
+}
diff --git a/visad/util/BarSlider.java b/visad/util/BarSlider.java
new file mode 100644
index 0000000..412843c
--- /dev/null
+++ b/visad/util/BarSlider.java
@@ -0,0 +1,280 @@
+/*
+
+@(#) $Id: BarSlider.java,v 1.8 2000-03-14 17:18:38 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+/**
+ * The visad utillity sliding bar
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.8 $, $Date: 2000-03-14 17:18:38 $
+ * @since Visad Utility Library v0.7.1
+ */
+
+public class BarSlider extends Slider implements MouseListener, MouseMotionListener {
+
+  /** The upper bound */
+  private float upper;
+
+  /** The lower bound */
+  private float lower;
+
+  /** The current value */
+  private float val;
+
+  /** widget sizes */
+  Dimension minSize = null;
+  Dimension prefSize = null;
+  Dimension maxSize = null;
+
+  /** Construct a new bar slider with the default values */
+  public BarSlider() {
+    this(-1, 1, 0);
+  }
+
+  /**
+   * Construct a new bar slider with the givden lower, upper and initial values
+   * @throws IllegalArgumenentException if lower is not less than initial or initial
+   * is not less than upper
+   */
+  public BarSlider(float lower, float upper, float initial) {
+
+    if (lower > initial) {
+      throw new IllegalArgumentException("BarSlider: lower bound is greater than initial value");
+    }
+
+    if (initial > upper) {
+      throw new IllegalArgumentException("BarSlider: initial value is greater than the upper bound");
+    }
+
+    this.upper = upper;
+    this.lower = lower;
+    this.val = initial;
+
+    this.addMouseListener(this);
+    this.addMouseMotionListener(this);
+
+  }
+
+  /** For testing puropses */
+  public static void main(String[] argv) {
+
+    javax.swing.JFrame frame;
+    frame = new javax.swing.JFrame("Visad Bar Slider");
+    frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+
+    BarSlider b = new BarSlider();
+
+    frame.add(b);
+
+    frame.setSize(b.getPreferredSize());
+    frame.setVisible(true);
+  }
+
+  /** Return the minimum value of this slider */
+  public float getMinimum() {
+    return lower;
+  }
+
+  /** Sets the minimum value for this slider */
+  public synchronized void setMinimum(float value) {
+
+    if (value > val || (value == val && value == upper)) {
+      throw new IllegalArgumentException("BarSlider: Attemped to set new minimum value greater than the current value");
+    }
+
+    lower = value;
+
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.LOWER_CHANGE, value));
+
+    repaint();
+  }
+
+  /** Return the maximum value of this slider */
+  public float getMaximum() {
+    return upper;
+  }
+
+  /** Sets the maximum value of this scrolbar */
+  public synchronized void setMaximum(float value){
+
+    if (value < val || (value == val && value == lower)) {
+      throw new IllegalArgumentException("BarSlider: Attemped to set new maximum value less than the current value");
+    }
+
+    upper = value;
+
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.UPPER_CHANGE, value));
+
+    repaint();
+  }
+
+  /** Returns the current value of the slider */
+  public float getValue() {
+    return val;
+  }
+
+  /**
+   * Sets the current value of the slider
+   * @throws IllegalArgumentException if the new value is out of bounds for the slider
+   */
+  public synchronized void setValue(float value){
+
+    if (value > upper || value < lower) {
+      throw new IllegalArgumentException("BarSlider: Attemped to set new value out of slider range");
+    }
+
+    val = value;
+
+    notifyListeners(new SliderChangeEvent(SliderChangeEvent.VALUE_CHANGE, value));
+
+    repaint();
+  }
+
+  /** Return the preferred sise of the bar slider */
+  public Dimension getPreferredSize() {
+    if (prefSize == null) {
+      prefSize = new Dimension(256, 16);
+    }
+    return prefSize;
+  }
+
+  /** Set the preferred size of the bar slider */
+  public void setPreferredSize(Dimension dim) { prefSize = dim; }
+
+  /** Return the maximum size of the bar slider */
+  public Dimension getMaximumSize() {
+    if (maxSize == null) {
+      maxSize = new Dimension(Integer.MAX_VALUE, 16);
+    }
+    return maxSize;
+  }
+
+  /** Set the preferred size of the bar slider */
+  public void setMaximumSize(Dimension dim) { maxSize = dim; }
+
+  /** Return the minimum size of the bar slider */
+  public Dimension getMinimumSize() {
+    if (minSize == null) {
+      minSize = new Dimension(40, 16);
+    }
+    return minSize;
+  }
+
+  /** Set the preferred size of the bar slider */
+  public void setMinimumSize(Dimension dim) { minSize = dim; }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseClicked(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseEntered(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseExited(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Moves the slider to the clicked position */
+  public void mousePressed(MouseEvent e) {
+    //System.out.println(e.paramString());
+    updatePosition(e);
+  }
+
+  /** Present to implement MouseListener, currently ignored */
+  public void mouseReleased(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Updates the slider position */
+  public void mouseDragged(MouseEvent e) {
+    //System.out.println(e.paramString());
+    updatePosition(e);
+  }
+
+  /** Present to implement MouseMovementListener, currently ignored */
+  public void mouseMoved(MouseEvent e) {
+    //System.out.println(e.paramString());
+  }
+
+  /** Recalculate the position and value of the slider given the new mouse position */
+  private void updatePosition(MouseEvent e) {
+    int x = e.getX();
+
+    if (x < 0) x = 0;
+    if (x >= getBounds().width) x = getBounds().width - 1;
+
+    float dist = (float) x / (float) (getBounds().width - 1);
+
+    setValue(lower + dist*(upper - lower));
+  }
+
+  /** the last position where the bar was drawn */
+  private int oldxval;
+
+  /** update the slider */
+  public void update(Graphics g) {
+    g.setColor(Color.black);
+    g.drawRect(oldxval - 2, 0, 5, getBounds().height - 1);
+    g.setColor(Color.gray);
+    g.fillRect(oldxval - 2, getBounds().height / 2 - 1, 6, 3);
+    g.setColor(Color.white);
+
+    int xval = (int) Math.floor((val - lower) * (getBounds().width - 1) / (upper - lower));
+    g.drawRect(xval - 2, 0, 5, getBounds().height - 1);
+
+    oldxval = xval;
+  }
+
+  /** Redraw the slider */
+  public void paint(Graphics g) {
+    g.setColor(Color.black);
+    g.fillRect(0, 0, getBounds().width, getBounds().height);
+    g.setColor(Color.gray);
+    g.fillRect(0, getBounds().height / 2 - 1, getBounds().width, 3);
+
+    g.setColor(Color.white);
+
+    int xval = (int) Math.floor((val - lower) * (getBounds().width - 1) / (upper - lower));
+    g.drawRect(xval - 2, 0, 5, getBounds().height - 1);
+    oldxval = xval;
+  }
+}
diff --git a/visad/util/BaseRGBMap.java b/visad/util/BaseRGBMap.java
new file mode 100644
index 0000000..4917b92
--- /dev/null
+++ b/visad/util/BaseRGBMap.java
@@ -0,0 +1,949 @@
+/*
+
+@(#) $Id: BaseRGBMap.java,v 1.22 2002-09-19 21:08:42 curtis Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Toolkit;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+import java.rmi.RemoteException;
+
+import visad.BaseColorControl;
+import visad.ControlEvent;
+import visad.ControlListener;
+import visad.VisADException;
+
+/**
+ * An extensible RGB colormap with no interpolation between the
+ * internally stored values.  Click and drag with the left mouse
+ * button to draw the color curves. Click with the right or middle
+ * mouse button to alternate between the color curves.
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.22 $, $Date: 2002-09-19 21:08:42 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public class BaseRGBMap
+  extends ColorMap
+  implements ControlListener, MouseListener, MouseMotionListener
+{
+  /** change this to <TT>true</TT> to use color cursors */
+  public static boolean USE_COLOR_CURSORS = false;
+
+  /** default resolution */
+  public static final int DEFAULT_RESOLUTION =
+    BaseColorControl.DEFAULT_NUMBER_OF_COLORS;
+
+  /** The color control */
+  private BaseColorControl ctl;
+
+  /** The left modified value */
+  private int valLeft = 0;
+  /** The right modified value */
+  private int valRight = 0;
+
+  /** A lock to synchronize against when modifying the modified area */
+  private Object mutex = new Object();
+
+  /** The index of the color red */
+  private static final int RED = BaseColorControl.RED;
+  /** The index of the color green */
+  private static final int GREEN = BaseColorControl.GREEN;
+  /** The index of the color blue */
+  private static final int BLUE = BaseColorControl.BLUE;
+  /** The index of the alpha channel */
+  private static final int ALPHA = BaseColorControl.ALPHA;
+  /** The current color for the mouse to draw on */
+  private int state = RED;
+
+  /** The resolution of the map */
+  private int resolution = 256;
+  /** 'true' if this map has an alpha component */
+  private boolean hasAlpha;
+
+  private static Cursor[] cursor = null;
+  /** a slightly brighter blue */
+  private static final Color bluish = new Color(80, 80, 255);
+
+  /** the preferred size of this widget */
+  private Dimension preferredSize = null;
+
+  /**
+   * Construct a BaseRGBMap with the default resolution
+   *
+   * @param hasAlpha set to <TT>true</TT> is this map has
+   *                 an alpha component
+   */
+  public BaseRGBMap(boolean hasAlpha)
+    throws RemoteException, VisADException
+  {
+    this(defaultTable(DEFAULT_RESOLUTION, hasAlpha));
+  }
+
+  /**
+   * Construct a colormap with the specified resolution
+   *
+   * @param resolution the length of the array
+   * @param hasAlpha set to <TT>true</TT> is this map has
+   *                 an alpha component
+   */
+  public BaseRGBMap(int resolution, boolean hasAlpha)
+    throws RemoteException, VisADException
+  {
+    this(defaultTable(resolution, hasAlpha));
+  }
+
+  /**
+   * Construct a colormap initialized with the supplied tuples
+   *
+   * @param vals the tuples used to initialize the colormap
+   * @param hasAlpha <TT>true</TT> if the colormap should
+   *                 have an ALPHA component.
+   *
+   * @deprecated <TT>hasAlpha</TT> isn't really necessary.
+   */
+  public BaseRGBMap(float[][] vals, boolean hasAlpha)
+    throws RemoteException, VisADException
+  {
+    this(vals != null ? vals : defaultTable(DEFAULT_RESOLUTION, hasAlpha));
+  }
+
+  /**
+   * Construct a colormap initialized with the supplied tuples
+   *
+   * @param vals the tuples used to initialize the colormap
+   *             See setValues() for constraints on the <TT>vals</TT> array.
+   */
+  public BaseRGBMap(float[][] vals)
+    throws RemoteException, VisADException
+  {
+    if (vals == null) {
+      vals = defaultTable(DEFAULT_RESOLUTION, true);
+    }
+
+    setValues(vals);
+
+    if (USE_COLOR_CURSORS) buildCursors();
+
+    addMouseListener(this);
+    addMouseMotionListener(this);
+    ctl.addControlListener(this);
+  }
+
+  /**
+   * Construct a colormap from the specified control.
+   *
+   * @param ctl control to use as data source
+   */
+  public BaseRGBMap(BaseColorControl ctl)
+  {
+    this.ctl = ctl;
+
+    hasAlpha = (ctl.getNumberOfComponents() == 4);
+    resolution = ctl.getNumberOfColors();
+
+    if (USE_COLOR_CURSORS) buildCursors();
+
+    addMouseListener(this);
+    addMouseMotionListener(this);
+    ctl.addControlListener(this);
+  }
+
+  /**
+   * Build a table with the given number of components and colors
+   *
+   * @param resolution Number of colors.
+   * @param hasAlpha <TT>true</TT> if this colormap has an alpha component.
+   *
+   * @return The new table.
+   */
+  static float[][] defaultTable(int resolution, boolean hasAlpha)
+  {
+    final int components = hasAlpha ? 4 : 3;
+    float[][] tbl = new float[components][resolution];
+    return BaseColorControl.initTableVis5D(tbl);
+  }
+
+  /**
+   * Build one of the red, green, blue and alpha cursors
+   *
+   * @param rgba cursor to build (RED, GREEN, BLUE or ALPHA)
+   *
+   * @return the new <TT>Cursor</TT>
+   */
+  static Cursor buildRGBACursor(int rgba)
+  {
+    if (rgba < 0 || rgba > 3) rgba = 0;
+
+    final int lines = 15;
+    final int elements = 15;
+
+    int[] pixel = new int[lines*elements];
+
+    for (int i = 0; i < pixel.length; i++) {
+      pixel[i] = 0;
+    }
+
+    final int color;
+    switch (rgba) {
+    case RED: color = Color.red.getRGB(); break;
+    case GREEN: color = Color.green.getRGB(); break;
+    case BLUE: color = bluish.getRGB(); break;
+    default:
+    case ALPHA: color = Color.gray.getRGB(); break;
+    }
+
+    final int midLine = (lines / 2) * elements;
+    for (int i = midLine + elements - 1; i >= midLine; i--) {
+      pixel[i] = color;
+    }
+
+    final int midElement = (elements / 2);
+    for (int i = 0; i < lines; i++) {
+      pixel[i*elements + midElement] = color;
+    }
+    java.awt.image.ImageProducer ip;
+    ip = new java.awt.image.MemoryImageSource(elements, lines, pixel,
+                                              0, elements);
+
+    java.awt.Image img = Toolkit.getDefaultToolkit().createImage(ip);
+
+    Point pt = new Point(img.getWidth(null) / 2, img.getHeight(null) / 2);
+    String name;
+    switch (rgba) {
+    case RED: name = "crossRed"; break;
+    case GREEN: name = "crossGreen"; break;
+    case BLUE: name = "crossBlue"; break;
+    default:
+    case ALPHA: name = "crossAlpha"; break;
+    }
+
+    return Toolkit.getDefaultToolkit().createCustomCursor(img, pt, name);
+  }
+
+  /**
+   * Used internally to initialize the red, green, blue and alpha cursors
+   */
+  private void buildCursors()
+  {
+    if (cursor != null) return;
+
+    // only try to change the cursor if we're running under JDK 1.3 or greater
+    String jVersion = System.getProperty("java.version");
+    if (jVersion == null) return;
+    if (jVersion.length() < 3) return;
+    if (jVersion.charAt(0) < '1') return;
+    if (jVersion.charAt(1) != '.') return;
+    if (jVersion.charAt(0) == '1' && jVersion.charAt(2) < '3') return;
+
+    cursor = new Cursor[4];
+    cursor[RED] = buildRGBACursor(RED);
+    cursor[GREEN] = buildRGBACursor(GREEN);
+    cursor[BLUE] = buildRGBACursor(BLUE);
+    cursor[ALPHA] = buildRGBACursor(ALPHA);
+
+    setCursor(cursor[state]);
+  }
+
+  /**
+   * Sets the values of the internal array after the map
+   * has been created.
+   *
+   * The table should be <TT>float[resolution][dimension]</TT>
+   * where <TT>dimension</TT> is either 3 (for an RGB table)
+   * or 4 (if the table also has an alpha component) and
+   * <TT>resolution</TT> is the number of colors in the table,
+   * which must be greater than 4.
+   *
+   * @param newVal the color tuples used to initialize the map.
+   */
+  public void setValues(float[][] newVal)
+    throws RemoteException, VisADException
+  {
+    if (newVal == null) {
+      throw new VisADException("Can't set table to null");
+    }
+
+    if (newVal.length >= 3 && newVal.length <= 4 && newVal[0].length > 4) {
+      hasAlpha = newVal.length > 3;
+      resolution = newVal[0].length;
+    } else if (newVal[0].length >= 3 && newVal[0].length <= 4 &&
+               newVal.length > 4)
+    {
+      // table is inverted
+
+      hasAlpha = newVal[0].length > 3;
+      resolution = newVal.length;
+
+      float[][] tmpVal = new float[hasAlpha ? 4 : 3][resolution];
+      for (int i = 0; i < resolution; i++) {
+        tmpVal[RED][i] = newVal[i][RED];
+        tmpVal[GREEN][i] = newVal[i][GREEN];
+        tmpVal[BLUE][i] = newVal[i][BLUE];
+        if (hasAlpha) {
+          tmpVal[ALPHA][i] = newVal[i][ALPHA];
+        }
+      }
+      newVal = tmpVal;
+    } else {
+      throw new VisADException("Cannot set table with dimensions [" +
+                               newVal.length + "][" + newVal[0].length + "]");
+    }
+
+    if (ctl == null) {
+      ctl = new BaseColorControl(null, hasAlpha ? 4 : 3);
+    }
+
+    ctl.setTable(newVal);
+
+    sendUpdate(0, resolution-1);
+  }
+
+  /**
+   * Get the resolution of the map
+   *
+   * @return the number of colors in the map.
+   */
+  public int getMapResolution() {
+    return resolution;
+  }
+
+  /**
+   * Get the dimension of the map
+   *
+   * @return either 3 or 4
+   */
+  public int getMapDimension() {
+    return ctl.getNumberOfComponents();
+  }
+
+  /**
+   * Get the color map (as an array of <TT>float</TT> tuples.
+   *
+   * @return a copy of the color map
+   */
+  public float[][] getColorMap() {
+    return ctl.getTable();
+  }
+
+  /**
+   * Returns the tuple at a floating point value val
+   *
+   * @param firstVal the location to start.
+   * @param lastVal the location to finish.
+   * @param num the number of tuples to return.
+   *
+   * @return The array of 3 or 4 element arrays.
+   */
+  public float[][] getTuples(float firstVal, float lastVal, int num) {
+    if (num <= 0 || firstVal > lastVal ||
+        (num == 1 && !Util.isApproximatelyEqual(firstVal, lastVal)))
+    {
+      return null;
+    }
+
+    float floatIdx;
+
+    floatIdx = firstVal * (resolution - 1);
+    int startIndex = (int )Math.floor(floatIdx);
+
+    floatIdx = lastVal * (resolution - 1);
+    int endIndex = (int )Math.floor(floatIdx);
+
+    float partialEnd = floatIdx - endIndex;
+    boolean isPartialEnd = (partialEnd != 0);
+
+    float[][] tuples = new float[num][hasAlpha ? 4 : 3];
+
+    int rStart = startIndex;
+    if (rStart < 0) {
+      rStart = 0;
+    } else if (rStart >= resolution) {
+      rStart = resolution - 1;
+    }
+
+    int rEnd = (isPartialEnd ? endIndex+1 : endIndex);
+    if (rEnd >= resolution) {
+      rEnd = resolution - 1;
+    } else if (rEnd < 0) {
+      rEnd = 0;
+    }
+
+    final int rLen = (rEnd - rStart) + 1;
+
+    float[][] colors;
+    try {
+      colors = ctl.lookupRange(rStart, rEnd);
+    } catch (Exception e) {
+      System.err.println("Error in " + getClass().getName() + ": " +
+                         e.getClass().getName() + ": " + e.getMessage());
+      return null;
+    }
+
+    float stepVal = (lastVal - firstVal) / (float )num;
+
+    for (int i = 0; i < num; i++) {
+      float thisVal = firstVal + (float )i * stepVal;
+
+      floatIdx = thisVal * (resolution - 1);
+      int index = (int )Math.floor(floatIdx);
+
+      float partial = floatIdx - index;
+      boolean isPartial = (partial != 0);
+
+      index -= rStart;
+
+      if (index < 0 || index >= rLen ||
+          (index == (rLen - 1) && isPartial))
+      {
+        tuples[i][RED] = tuples[i][GREEN] = tuples[i][BLUE] = 0;
+        if (hasAlpha) {
+          tuples[i][ALPHA] = 0;
+        }
+        continue;
+      }
+
+      if (isPartial) {
+        tuples[i][RED] = colors[RED][index] * (1 - partial) +
+          colors[RED][index+1] * partial;
+        tuples[i][GREEN] = colors[GREEN][index] * (1 - partial) +
+          colors[GREEN][index+1] * partial;
+        tuples[i][BLUE] = colors[BLUE][index] * (1 - partial) +
+          colors[BLUE][index+1] * partial;
+        if (hasAlpha) {
+          tuples[i][ALPHA] = colors[ALPHA][index] * (1 - partial) +
+            colors[ALPHA][index+1] * partial;
+        }
+      } else {
+        tuples[i][RED] = colors[RED][index];
+        tuples[i][GREEN] = colors[GREEN][index];
+        tuples[i][BLUE] = colors[BLUE][index];
+        if (hasAlpha) {
+          tuples[i][ALPHA] = colors[ALPHA][index];
+        }
+      }
+    }
+
+    return tuples;
+  }
+
+  /**
+   * Returns the tuple at a floating point value val
+   *
+   * <B>WARNING</B>: This is a <I>really</I> slow way to
+   * get a color, so don't use it inside a loop.
+   *
+   * @param value the location to return.
+   *
+   * @return The 3 or 4 element array.
+   */
+  public float[] getTuple(float value) {
+    float[][] v = getTuples(value, value, 1);
+    return (v == null ? null : v[0]);
+  }
+
+  /**
+   * Implementation of the abstract function in ColorMap
+   *
+   * <B>WARNING</B>: This is a <I>really</I> slow way to
+   * get a color, so don't use it inside a loop.
+   */
+  public float[][] getRGBTuples(float startVal, float endVal, int num) {
+    float[][] t = getTuples(startVal, endVal, num);
+    if (t[0].length > 3) {
+      float[][] f = new float[t.length][3];
+      for (int i = t.length - 1; i >= 0; i--) {
+        f[i][RED] = t[i][RED];
+        f[i][GREEN] = t[i][GREEN];
+        f[i][BLUE] = t[i][BLUE];
+      }
+      t = f;
+    }
+    return t;
+  }
+
+  /**
+   * Implementation of the abstract function in ColorMap
+   *
+   * <B>WARNING</B>: This is a <I>really</I> slow way to
+   * get a color, so don't use it inside a loop.
+   *
+   * @param value a floating point number between 0 and 1
+   * @return an RGB tuple of floating point numbers in the
+   * range 0 to 1
+   */
+  public float[] getRGBTuple(float value) {
+    float[] t = getTuple(value);
+    if (t.length > 3) {
+      float[] f = new float[3];
+      f[RED] = t[RED];
+      f[GREEN] = t[GREEN];
+      f[BLUE] = t[BLUE];
+      t = f;
+    }
+    return t;
+  }
+
+  /**
+   * Redraw the between the <TT>left</TT> and
+   * <TT>right</TT> colors
+   *
+   * @param left the left edge of the changed area (in the range 0.0-1.0)
+   * @param right the right edge of the changed area
+   */
+  protected void sendUpdate(int left, int right) {
+
+    notifyListeners(new ColorChangeEvent(this, left, right));
+
+    if (left != 0) {
+      left--;
+    }
+    if (right != resolution - 1) {
+      right++;
+    }
+
+    synchronized (mutex) {
+      if (left < valLeft)
+        valLeft = left;
+      if (right > valRight)
+        valRight = right;
+    }
+
+    // redraw
+    validate();
+    repaint();
+  }
+
+  /**
+   * Present to implement MouseListener, currently ignored
+   *
+   * @param evt ignored
+   */
+  public void mouseClicked(MouseEvent evt) {
+  }
+
+  /**
+   * MouseListener, currently ignored
+   *
+   * @param evt ignored
+   */
+  public void mouseEntered(MouseEvent evt) {
+  }
+
+  /**
+   * MouseListener method, currently ignored
+   *
+   * @param evt ignored
+   */
+  public void mouseExited(MouseEvent evt) {
+  }
+
+  /** The last mouse event's x value */
+  private int oldX;
+  /** The last mouse event's y value */
+  private int oldY;
+
+  /** A synchronization primitive for the mouse movements */
+  private Object mouseMutex = new Object();
+
+  /**
+   * Updates the associated Control
+   *
+   * @param evt the mouse press event
+   */
+  public void mousePressed(MouseEvent evt) {
+    if ((evt.getModifiers() & evt.BUTTON1_MASK) == 0 &&
+        evt.getModifiers() != 0)
+    {
+      return;
+    }
+
+    int width = getBounds().width;
+    int height = getBounds().height;
+    int x = evt.getX();
+    int y = evt.getY();
+
+    if (x < 0)
+      x = 0;
+    else if (x >= width)
+      x = width - 1;
+    if (y < 0)
+      y = 0;
+    else if (y >= height)
+      y = height - 1;
+
+    float step = (float )(resolution - 1) / (float )width;
+    int pos = (int )Math.floor((float )x * step + 0.5);
+
+    float[][] colors;
+    try {
+      colors = ctl.lookupRange(pos, pos);
+    } catch (Exception e) {
+      System.err.println("Error in " + getClass().getName() + ": " +
+                         e.getClass().getName() + ": " + e.getMessage());
+      return;
+    }
+
+    colors[state][0] = 1 - (float )y / (float )height;
+
+    try {
+      ctl.setRange(pos, pos, colors);
+    } catch (Exception e) {
+      System.err.println("Error in " + getClass().getName() + ": " +
+                         e.getClass().getName() + ": " + e.getMessage());
+      return;
+    }
+
+    oldX = x;
+    oldY = y;
+
+    sendUpdate(pos, pos);
+  }
+
+  /**
+   * Listens for releases of the right mouse button,
+   * and changes the active color
+   *
+   * @param evt the release event
+   */
+  public void mouseReleased(MouseEvent evt) {
+    if ((evt.getModifiers() & (evt.BUTTON2_MASK|evt.BUTTON3_MASK)) == 0) {
+      return;
+    }
+    state = (state + 1) % (hasAlpha ? 4 : 3);
+    if (cursor != null) {
+      setCursor(cursor[state]);
+    }
+  }
+
+  /**
+   * Updates the associated Control
+   *
+   * @param evt the drag event
+   */
+  public void mouseDragged(MouseEvent evt) {
+    if ((evt.getModifiers() & evt.BUTTON1_MASK) == 0 &&
+        evt.getModifiers() != 0)
+    {
+      return;
+    }
+
+    drag(evt.getX(), evt.getY(), oldX, oldY);
+
+    oldX = evt.getX();
+    oldY = evt.getY();
+  }
+
+  /**
+   * Internal mouse dragging function
+   *
+   * @param x the current x coordinate
+   * @param y the current y coordinate
+   * @param oldx the starting x coordinate
+   * @param oldy the starting y coordinate
+   */
+  private void drag(int x, int y, int oldx, int oldy) {
+
+    int width = getBounds().width;
+    int height = getBounds().height;
+
+    // make sure x, y, oldx and oldy are all inside the window
+    if (x < 0)
+      x = 0;
+    else if (x >= width)
+      x = width - 1;
+    if (y < 0)
+      y = 0;
+    else if (y >= height)
+      y = height - 1;
+    if (oldx < 0)
+      oldx = 0;
+    else if (oldx >= width)
+      oldx = width - 1;
+    if (oldy < 0)
+      oldy = 0;
+    else if (oldy >= height)
+      oldy = height - 1;
+
+    float step = (float )(resolution - 1) / (float )width;
+
+    int oldPos = (int )Math.floor((float )oldx * step + 0.5);
+    int newPos = (int )Math.floor((float )x * step + 0.5);
+
+    float oldVal = 1 - (float )oldy / (float )height;
+    float newVal = 1 - (float )y / (float )height;
+
+    final int start, finish;
+    final int len = ctl.getNumberOfColors() - 1;
+    if (newPos > oldPos) {
+      start = oldx < width - 1 ? oldPos : len;
+      finish = x < width - 1 ? newPos : len;
+    } else {
+      start = x < width - 1 ? newPos : len;
+      finish = oldx < width - 1 ? oldPos : len;
+    }
+
+    float[][] colors;
+    try {
+      colors = ctl.lookupRange(start, finish);
+    } catch (Exception e) {
+      System.err.println("Error in " + getClass().getName() + ": " +
+                         e.getClass().getName() + ": " + e.getMessage());
+      return;
+    }
+
+    final float loVal, hiVal;
+
+    if (newPos > oldPos) {
+      loVal = newVal;
+      hiVal = oldVal;
+    } else {
+      loVal = oldVal;
+      hiVal = newVal;
+    }
+
+    final int total = finish - start + 1;
+    for (int i = 0; i < total; i++) {
+      float v = ((hiVal * (float )(total - i) + loVal * (float )i) /
+                 (float )total);
+      colors[state][i] = v;
+    }
+
+    try {
+      ctl.setRange(start, finish, colors);
+    } catch (Exception e) {
+      System.err.println("Error in " + getClass().getName() + ": " +
+                         e.getClass().getName() + ": " + e.getMessage());
+      return;
+    }
+
+    sendUpdate(start, finish);
+  }
+
+  /**
+   * MouseMovementListener method, currently ignored
+   *
+   * @param evt ignored
+   */
+  public void mouseMoved(MouseEvent evt) {
+  }
+
+  /**
+   * Repaints the entire JPanel
+   *
+   * @param g The <TT>Graphics</TT> to update.
+   */
+  public void paint(Graphics g) {
+
+    synchronized (mutex) {
+
+      valLeft = 0;
+      valRight = resolution - 1;
+    }
+
+    update(g);
+  }
+
+  /** The left bound for updating the JPanel */
+  private float updateLeft = 0;
+
+  /** The right bound for updating the JPanel */
+  private float updateRight = 1;
+
+  /**
+   * Repaints the modified areas of the JPanel
+   *
+   * @param g The <TT>Graphics</TT> to update.
+   */
+  public void update(Graphics g) {
+
+    final int maxRight = resolution - 1;
+
+    int left = 0;
+    int right = maxRight;
+
+    synchronized (mutex) {
+      if (valLeft > valRight) {
+        return;
+      }
+
+      left = valLeft;
+      right = valRight;
+
+      valLeft = maxRight;
+      valRight = 0;
+    }
+
+    final int numColors = ctl.getNumberOfColors() - 1;
+
+    if (left < 0) {
+      left = 0;
+    } else if (left > numColors) {
+      left = numColors;
+    }
+    if (right < 0) {
+      right = 0;
+    } else if (right > numColors) {
+      right = numColors;
+    }
+
+    if (left > 0) {
+      left--;
+    }
+    if (right < maxRight) {
+      right++;
+    }
+
+    final int maxWidth = getBounds().width - 1;
+    final int maxHeight = getBounds().height - 1;
+
+    int leftPixel = (left * maxWidth) / maxRight;
+    int rightPixel = (right * maxWidth) / maxRight;
+
+    g.setColor(Color.black);
+    g.fillRect(leftPixel,0,rightPixel - leftPixel + 1, maxHeight + 1);
+
+    if (left > 0) {
+      left--;
+    }
+    if (right < maxRight) {
+      right++;
+    }
+
+    leftPixel = (left * maxWidth) / maxRight;
+    rightPixel = (right * maxWidth) / maxRight;
+
+    float[][] colors;
+    try {
+      colors = ctl.lookupRange(left, right < maxRight ? right + 1 : maxRight);
+    } catch (Exception e) {
+      System.err.println("Error in " + getClass().getName() + ": " +
+                         e.getClass().getName() + ": " + e.getMessage());
+      colors = null;
+    }
+
+    if (colors == null) {
+      return;
+    }
+
+    int prevEnd = leftPixel;
+
+    int prevRed = (int )Math.floor((1 - colors[RED][0]) * maxHeight);
+    int prevGreen = (int )Math.floor((1 - colors[GREEN][0]) * maxHeight);
+    int prevBlue = (int )Math.floor((1 - colors[BLUE][0]) * maxHeight);
+    int prevAlpha;
+    if (hasAlpha) {
+      prevAlpha = (int )Math.floor((1 - colors[ALPHA][0]) * maxHeight);
+    } else {
+      prevAlpha = 0;
+    }
+
+    int alpha = 0;
+    for (int i = 1; i < colors[0].length; i++) {
+      int lineEnd = ((left + i) * maxWidth) / maxRight;
+
+      int red = (int )Math.floor((1 - colors[RED][i]) * maxHeight);
+      int green = (int )Math.floor((1 - colors[GREEN][i]) * maxHeight);
+      int blue = (int )Math.floor((1 - colors[BLUE][i]) * maxHeight);
+      if (hasAlpha) {
+        alpha = (int )Math.floor((1 - colors[ALPHA][i]) * maxHeight);
+      }
+
+      g.setColor(Color.red);
+      g.drawLine(prevEnd, prevRed, lineEnd, red);
+
+      g.setColor(Color.green);
+      g.drawLine(prevEnd, prevGreen, lineEnd, green);
+
+      g.setColor(bluish);
+      g.drawLine(prevEnd, prevBlue, lineEnd, blue);
+
+      if (hasAlpha) {
+        g.setColor(Color.gray);
+        g.drawLine(prevEnd, prevAlpha, lineEnd, alpha);
+      }
+
+      prevEnd = lineEnd;
+
+      prevRed = red;
+      prevGreen = green;
+      prevBlue = blue;
+      if (hasAlpha) {
+        prevAlpha = alpha;
+      }
+    }
+  }
+
+  /**
+   * Return the preferred size of this map, taking into account
+   * the resolution.
+   *
+   * @return preferred size.
+   */
+  public Dimension getPreferredSize()
+  {
+    if (preferredSize == null) {
+      final int height = 170;
+      preferredSize = new Dimension(resolution, height);
+    }
+    return preferredSize;
+  }
+
+  /**
+   * Override the widget's calculation for the preferred size
+   * of this map.
+   *
+   * @param pref preferred size.
+   */
+  public void setPreferredSize(Dimension pref)
+  {
+    preferredSize = pref;
+  }
+
+  /**
+   * If the color data in the <CODE>Control</CODE> associated with this
+   * widget's <CODE>Control</CODE> has changed, update the data in
+   * the <CODE>ColorMap</CODE>.
+   *
+   * @param evt Data from the changed <CODE>Control</CODE>.
+   */
+  public void controlChanged(ControlEvent evt)
+    throws RemoteException, VisADException
+  {
+    hasAlpha = (ctl.getNumberOfComponents() == 4);
+    resolution = ctl.getNumberOfColors();
+
+    sendUpdate(0, getMapResolution()-1);
+  }
+}
diff --git a/visad/util/ChosenColorWidget.java b/visad/util/ChosenColorWidget.java
new file mode 100644
index 0000000..c566f48
--- /dev/null
+++ b/visad/util/ChosenColorWidget.java
@@ -0,0 +1,405 @@
+/*
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Component;
+import java.awt.Dimension;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.rmi.RemoteException;
+
+import javax.swing.BoxLayout;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+
+import visad.ScalarMap;
+import visad.VisADException;
+
+/**
+ * A color widget that allows users to interactively map numeric data to
+ * RGB/RGBA tuples in a <CODE>ScalarMap</CODE>, and to choose from
+ * a drop-down list of canned colormap combinations.
+ */
+public class ChosenColorWidget
+  extends JPanel
+  implements ActionListener
+{
+  ColorMapWidget wrappedWidget;
+  private float[][] original;
+  private JComboBox choice;
+
+  /**
+   * This class is a glorified data structure used to link
+   * an item's name and color values.
+   */
+  class ComboItem
+  {
+    private String name;
+    private float[][] table;
+
+    /**
+     * Link together the specified name and color values.
+     *
+     * @param name Name of this table.
+     * @param table Color values.
+     */
+    private ComboItem(String name, float[][] table)
+    {
+      this.name = name;
+      this.table = table;
+    }
+
+    /**
+     * Return the name for the benefit of the <CODE>JComboBox</CODE>.
+     *
+     * @return Name of this table.
+     */
+    public String toString() { return name; }
+  }
+
+  /**
+   * Construct a <CODE>ChosenColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public ChosenColorWidget(ScalarMap smap)
+    throws VisADException, RemoteException
+  {
+    this(new ColorMapWidget(smap));
+  }
+
+  /**
+   * Wrap a <CODE>ChosenColorWidget</CODE> around the specified
+   * <CODE>ColorMapWidget</CODE>.
+   *
+   * @param w The <CODE>ColorMapWidget</CODE>.
+   */
+  public ChosenColorWidget(ColorMapWidget w)
+  {
+    wrappedWidget = w;
+
+    // save the original table
+    original = wrappedWidget.copy_table(wrappedWidget.control.getTable());
+
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    add(wrappedWidget);
+    add(buildChoices());
+  }
+
+  /**
+   * Build color choice box.
+   *
+   * @return JPanel containing the choices or <CODE>null</CODE> if
+   *         the wrapped widget's button panel was used.
+   */
+  private Component buildChoices()
+  {
+    choice = new JComboBox();
+    choice.addItem(new ComboItem("Original", original));
+    choice.addActionListener(this);
+
+    boolean usedPanel = false;
+    JPanel panel = wrappedWidget.getButtonPanel();
+    if (panel != null) {
+      panel.add(choice);
+      usedPanel = true;
+    }
+
+    return (usedPanel ? (Component )panel : (Component )choice);
+  }
+
+  /**
+   * Return the number of rows in the table (3 for an RGB-based table,
+   * 4 for an RGBA-based table.)
+   *
+   * @return The number of rows.
+   *
+   * @exception VisADException If the size of the current table cannot
+   *                           be determined.
+   */
+  public int getNumberOfRows()
+    throws VisADException
+  {
+    if (original == null) {
+      throw new VisADException("Unknown original table size");
+    }
+
+    return original.length;
+  }
+
+  /**
+   * Return the table's "resolution" (aka the length of its rows.)
+   *
+   * @return The row length.
+   *
+   * @exception VisADException If the size of the current table cannot
+   *                           be determined.
+   */
+  public int getRowLength()
+    throws VisADException
+  {
+    if (original == null || original[0] == null) {
+      throw new VisADException("Unknown original table size");
+    }
+
+    return original[0].length;
+  }
+
+  /**
+   * Add the standard "Grey Wedge" item to the list of choices.
+   *
+   * @exception VisADException If the size of the current table cannot
+   *                           be determined.
+   */
+  public void addGreyWedgeItem()
+    throws VisADException
+  {
+    if (original == null || original[0] == null) {
+      throw new VisADException("Unknown original table size");
+    }
+
+    final int num = original.length;
+    final int len = original[0].length;
+    float[][] table = new float[num][len];
+    final float step = 1.0f / (len - 1.0f);
+    float total = 0.0f;
+    for (int j=0; j<len; j++) {
+      table[0][j] = table[1][j] = table[2][j] = total;
+      if (num > 3) {
+        table[3][j] = 1.0f;
+      }
+      total += step;
+    }
+
+    addItem("Grey Wedge", table);
+  }
+
+  /**
+   * Add a color lookup table to the list of choices.
+   *
+   * @param name Name of this table.
+   * @param table Table of colors.
+   *
+   * @exception VisADException If there is a problem with the table.
+   */
+  public void addItem(String name, float[][] table)
+    throws VisADException
+  {
+    if (original == null || original[0] == null) {
+      throw new VisADException("Unknown original table size");
+    }
+    if (table == null) {
+      throw new VisADException("Null table");
+    }
+    for (int i = table.length - 1; i >= 0; i--) {
+      if (table[i] == null || table[i].length != original[i].length) {
+        throw new VisADException("Table row " + i + " should have " +
+                                 original[i].length + " elements");
+      }
+    }
+
+    choice.addItem(new ComboItem(name, table));
+  }
+
+  /**
+   * Handle selections from the <CODE>JComboBox</CODE>.
+   *
+   * @param evt Data from the selected choice.
+   */
+  public void actionPerformed(ActionEvent evt)
+  {
+    if (evt.getActionCommand().equals("comboBoxChanged")) {
+      ComboItem item = (ComboItem )choice.getSelectedItem();
+      // reset color table to chosen values
+      try {
+        wrappedWidget.setTable(item.table);
+      } catch (RemoteException re) {
+      } catch (VisADException ve) {
+      }
+    }
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.getMaximumSize()</CODE>.
+   *
+   * @return Maximum size in <CODE>Dimension</CODE>.
+   */
+  public Dimension getMaximumSize()
+  {
+    return wrappedWidget.getMaximumSize();
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.setMaximumSize()</CODE>.
+   *
+   * @param size Maximum size.
+   */
+  public void setMaximumSize(Dimension size)
+  {
+    wrappedWidget.setMaximumSize(size);
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.getMinimumSize()</CODE>.
+   *
+   * @return Minimum size in <CODE>Dimension</CODE>.
+   */
+  public Dimension getMinimumSize()
+  {
+    return wrappedWidget.getMinimumSize();
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.setMinimumSize()</CODE>.
+   *
+   * @param size Minimum size.
+   */
+  public void setMinimumSize(Dimension size)
+  {
+    wrappedWidget.setMinimumSize(size);
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.getPreferredSize()</CODE>.
+   *
+   * @return Preferred size in <CODE>Dimension</CODE>.
+   */
+  public Dimension getPreferredSize()
+  {
+    return wrappedWidget.getPreferredSize();
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.setPreferredSize()</CODE>.
+   *
+   * @param size Preferred size.
+   */
+  public void setPreferredSize(Dimension size)
+  {
+    wrappedWidget.setPreferredSize(size);
+  }
+
+  public static void main(String[] args)
+  {
+    try {
+      visad.RealType vis = visad.RealType.getRealType("vis");
+      ScalarMap map = new ScalarMap(vis, visad.Display.RGBA);
+      map.setRange(0.0f, 1.0f);
+
+      visad.DisplayImpl dpy = new visad.java2d.DisplayImplJ2D("2d");
+      dpy.addMap(map);
+
+      javax.swing.JFrame f;
+      ChosenColorWidget chosen;
+
+      // build first widget
+      chosen = new ChosenColorWidget(map);
+      chosen.addGreyWedgeItem();
+
+      // wrap frame around first widget
+      f = new javax.swing.JFrame("0");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(chosen);
+      f.pack();
+      f.setVisible(true);
+
+      // build second widget
+      chosen = new ChosenColorWidget(new ColorMapWidget(map, null));
+      final int num = chosen.getNumberOfRows();
+      final int len = chosen.getRowLength();
+      float[][] table = new float[num][len];
+      final float step = 1.0f / (len - 1.0f);
+      float total = 1.0f;
+      for (int j=0; j<len; j++) {
+        table[0][j] = table[1][j] = table[2][j] = total;
+        if (num > 3) {
+          table[3][j] = 1.0f;
+        }
+        total -= step;
+      }
+      chosen.addItem("Reverse Wedge", table);
+
+      // wrap frame around second widget
+      f = new javax.swing.JFrame("1");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(chosen);
+      f.pack();
+      f.setVisible(true);
+
+      // wrap frame around third widget
+      f = new javax.swing.JFrame("!Updated");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      ColorMapWidget cmw3 = new ColorMapWidget(map, null, false);
+      f.getContentPane().add(new ChosenColorWidget(cmw3));
+      f.pack();
+      f.setVisible(true);
+
+      f = new javax.swing.JFrame("!Immediate");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      ColorMapWidget cmw = new ColorMapWidget(map, null, true, false);
+      f.getContentPane().add(new ChosenColorWidget(cmw));
+      f.pack();
+      f.setVisible(true);
+
+      try { Thread.sleep(5000); } catch (InterruptedException ie) { }
+
+      map.setRange(-10.0f, 10.0f);
+    } catch (RemoteException re) {
+      re.printStackTrace();
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+  }
+}
diff --git a/visad/util/ClientServer.java b/visad/util/ClientServer.java
new file mode 100644
index 0000000..e233406
--- /dev/null
+++ b/visad/util/ClientServer.java
@@ -0,0 +1,320 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.lang.reflect.InvocationTargetException;
+
+import java.rmi.ConnectException;
+import java.rmi.Naming;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+
+import visad.DataReference;
+import visad.DisplayImpl;
+import visad.LocalDisplay;
+import visad.RemoteDisplay;
+import visad.RemoteServer;
+import visad.RemoteServerImpl;
+import visad.VisADException;
+
+public class ClientServer
+{
+  private static final int maximumWaitTime = 60;
+
+  public static RemoteServer connectToServer(String hostName,
+                                             String serviceName)
+    throws RemoteException, VisADException
+  {
+    return connectToServer(hostName, serviceName, false);
+  }
+
+  public static RemoteServer connectToServer(String hostName,
+                                             String serviceName,
+                                             boolean verbose)
+    throws RemoteException, VisADException
+  {
+    RemoteServer client = null;
+    String domain = "//" + hostName + "/" + serviceName;
+
+    int loops = 0;
+    while (client == null && loops < maximumWaitTime) {
+
+      // try to reconnect to the server after the first loop
+      if (loops > 0) {
+        try {
+          client = (RemoteServer )Naming.lookup(domain);
+        } catch (NotBoundException nbe) {
+          client = null;
+        } catch (ConnectException ce) {
+          client = null;
+        } catch (Exception e) {
+          throw new VisADException ("Cannot connect to server on \"" +
+                                    hostName + "\" (" +
+                                    e.getClass().getName() + ": " +
+                                    e.getMessage() + ")");
+        }
+      }
+
+      // try to get first display from remote server
+      RemoteDisplay rmtDpy;
+      try {
+        if (client != null) {
+          rmtDpy = client.getDisplay(0);
+        }
+      } catch (java.rmi.ConnectException ce) {
+        client = null;
+      }
+
+      // if we didn't get the display, print a message and wait a bit
+      if (client == null) {
+        if (verbose) {
+          if (loops == 0) {
+            System.err.print("Client waiting for server ");
+          } else {
+            System.err.print(".");
+          }
+        }
+
+        try { Thread.sleep(1000); } catch (InterruptedException ie) { }
+
+        loops++;
+      }
+    }
+
+    if (loops == maximumWaitTime) {
+      if (verbose) {
+        System.err.println(" giving up!");
+      }
+      throw new VisADException("Cannot connect to " + hostName +
+                               ":" + serviceName);
+    } else if (loops > 0) {
+      if (verbose) {
+        System.err.println(" connected");
+      }
+    }
+
+    return client;
+  }
+
+  private static LocalDisplay wrapRemoteDisplay(RemoteDisplay rmtDpy)
+    throws RemoteException, VisADException
+  {
+    String className = rmtDpy.getDisplayClassName();
+    Class dpyClass;
+    try {
+      dpyClass = Class.forName(className);
+    } catch (ClassNotFoundException e) {
+      throw new VisADException("Couldn't create " + className);
+    }
+
+    java.lang.reflect.Constructor cons;
+    try {
+      cons = dpyClass.getConstructor(new Class[] { RemoteDisplay.class });
+    } catch (NoSuchMethodException e) {
+      throw new VisADException(className + " has no RemoteDisplay" +
+                               " constructor");
+    }
+
+    DisplayImpl dpy;
+
+    Object[] cargs = new Object[1];
+    cargs[0] = rmtDpy;
+    try {
+      dpy = (DisplayImpl )cons.newInstance(cargs);
+    } catch (InvocationTargetException ite) {
+      Throwable t = ite.getTargetException();
+      if (t instanceof VisADException) {
+        throw (VisADException )t;
+      } else if (t instanceof ConnectException) {
+        throw new VisADException("Couldn't create local shadow for " +
+                                 rmtDpy + ": Connection refused");
+      } else {
+        throw new VisADException("Couldn't create local shadow for " +
+                                 rmtDpy + ": " + t.getClass().getName() +
+                                 ": " + t.getMessage());
+      }
+    } catch (Exception e) {
+      throw new VisADException("Couldn't create local shadow for " +
+                               rmtDpy + ": " + e.getClass().getName() +
+                               ": " + e.getMessage());
+    }
+
+    return dpy;
+  }
+
+  public static LocalDisplay getClientDisplay(RemoteServer client, int index)
+    throws RemoteException, VisADException
+  {
+    return getClientDisplay(client, index, null);
+  }
+
+  public static LocalDisplay getClientDisplay(RemoteServer client, int index,
+                                              DataReference[] refs)
+    throws RemoteException, VisADException
+  {
+    // fail if there's no remote server
+    if (client == null) {
+      return null;
+    }
+
+    RemoteDisplay rmtDpy = null;
+
+    int loops = 0;
+    while (rmtDpy == null && loops < maximumWaitTime) {
+
+      try {
+        rmtDpy = client.getDisplay(index);
+      } catch (java.rmi.ConnectException ce) {
+        ce.printStackTrace();
+      }
+
+      // if we didn't get the display, print a message and wait a bit
+      if (rmtDpy == null) {
+        if (loops == 0) {
+          System.err.print("Client waiting for server display #" + index +
+                           " ");
+        } else {
+          System.err.print(".");
+        }
+
+        try { Thread.sleep(1000); } catch (InterruptedException ie) { }
+
+        loops++;
+      }
+    }
+
+    if (rmtDpy == null && loops == maximumWaitTime) {
+      System.err.println(" giving up!");
+      System.exit(1);
+    } else if (loops > 0) {
+      System.err.println(". ready");
+    }
+
+    if (rmtDpy == null) {
+      return null;
+    }
+
+    LocalDisplay dpy = wrapRemoteDisplay(rmtDpy);
+    if (dpy != null && refs != null) {
+      dpy.replaceReferences(rmtDpy, null, refs, null);
+    }
+
+    return dpy;
+  }
+
+  public static LocalDisplay[] getClientDisplays(RemoteServer client)
+    throws RemoteException, VisADException
+  {
+    return getClientDisplays(client, null);
+  }
+
+  public static LocalDisplay[] getClientDisplays(RemoteServer client,
+                                                 DataReference[] refs)
+    throws RemoteException, VisADException
+  {
+    // fail if there's no remote server
+    if (client == null) {
+      return null;
+    }
+
+    RemoteDisplay[] rmtDpys = null;
+
+    int loops = 0;
+    while (rmtDpys == null && loops < maximumWaitTime) {
+
+      try {
+        rmtDpys = client.getDisplays();
+      } catch (java.rmi.ConnectException ce) {
+      }
+
+      // if we didn't get the display, print a message and wait a bit
+      if (rmtDpys == null) {
+        if (loops == 0) {
+          System.err.print("Client waiting for server displays ");
+        } else {
+          System.err.print(".");
+        }
+
+        try { Thread.sleep(1000); } catch (InterruptedException ie) { }
+
+        loops++;
+      }
+    }
+
+    if (rmtDpys == null && loops == maximumWaitTime) {
+      System.err.println(" giving up!");
+      System.exit(1);
+    } else if (loops > 0) {
+      System.err.println(". ready");
+    }
+
+    if (rmtDpys == null) {
+      return null;
+    }
+
+    LocalDisplay[] dpys = new LocalDisplay[rmtDpys.length];
+    for (int i = 0; i < dpys.length; i++) {
+      dpys[i] = wrapRemoteDisplay(rmtDpys[i]);
+      if (dpys[i] != null && refs != null) {
+        dpys[i].replaceReferences(rmtDpys[i], null, refs, null);
+      }
+    }
+
+    return dpys;
+  }
+
+  public static RemoteServerImpl startServer(String serviceName)
+    throws RemoteException, VisADException
+  {
+    // create new server
+    RemoteServerImpl server;
+    boolean registryStarted = false;
+    while (true) {
+      boolean success = true;
+      try {
+        server = new RemoteServerImpl();
+        String domain = "///" + serviceName;
+        Naming.rebind(domain, server);
+        break;
+      } catch (java.rmi.ConnectException ce) {
+        if (!registryStarted) {
+          LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
+          registryStarted = true;
+        } else {
+          success = false;
+        }
+      } catch (Exception e) {
+        success = false;
+      }
+      if (!success) {
+        throw new VisADException("Cannot set up server" +
+                                 " (rmiregistry may not be running)");
+      }
+    }
+
+    return server;
+  }
+}
diff --git a/visad/util/CmdlineConsumer.java b/visad/util/CmdlineConsumer.java
new file mode 100644
index 0000000..b9c47cb
--- /dev/null
+++ b/visad/util/CmdlineConsumer.java
@@ -0,0 +1,129 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+public interface CmdlineConsumer
+{
+  /**
+   * Method used to initialize any instance variables which may be
+   * changed by a cmdline option.<br>
+   * <br>
+   * This is needed when arguments are processed inside the
+   * constructor.  If the first line in the constructor in a class
+   * which extends this class is <tt>super(args)</tt>,
+   * {@link visad.util.CmdlineParser CmdlineParser} will be run
+   * <em>before</em> any instance variables for the extending
+   * class are initialized.<br>
+   * <br>
+   * To ensure all instance variables are properly initialized,
+   * place the initialization code in the initializeArgs() method.
+   */
+  void initializeArgs();
+
+  /**
+   * Handle subclass-specific command line options and their arguments.<br>
+   * <br>
+   * If <tt>-abc -d efg -h -1 -i</tt> is specified, this
+   * method will be called a maximum of 5 times:<ul>
+   * <li><tt>checkOption(mainName, 'a', "bc");</tt>
+   * <li><tt>checkOption(mainName, 'd', "efg");</tt>
+   * <li><tt>checkOption(mainName, 'h', "-1");</tt>
+   * <li><tt>checkOption(mainName, '1', "-i");</tt>
+   * <li><tt>checkOption(mainName, 'i', null);</tt>
+   * </ul>
+   * <br>
+   * Note that either of the last two method calls may not
+   * happen if the preceeding method call claims to have used
+   * the following argument (by returning <tt>2</tt>.<br>
+   * <br>
+   * For example,
+   * if the third call (where <tt>ch</tt> is set to <tt>'h'</tt>)
+   * returns <tt>0</tt> or <tt>1</tt>, the next call will contain
+   * <tt>'1'</tt> and <tt>"-i"</tt>.  If, however, the third call
+   * returns <tt>2</tt>, the next call will contain <tt>'i'</tt>
+   * and <tt>null</tt>.
+   *
+   * @param mainName The name of the main class (useful for
+   *                 error messages.)
+   * @param ch Option character.  If <tt>-a</tt> is specified
+   *           on the command line, <tt>'a'</tt> would be passed to
+   *           this method.)
+   * @param arg The argument associated with this option.
+   *
+   * @return less than <tt>0</tt> to indicate an error<br>
+   *         <tt>0</tt> to indicate that this option is not used by this
+   *         class<br>
+   *         <tt>1</tt> to indicate that only the option was used<br>
+   *         <tt>2</tt> or greater to indicate that both the option and the
+   *         argument were used
+   */
+  int checkOption(String mainName, char ch, String arg);
+
+  /**
+   * A short string included in the usage message to indicate
+   * valid options.  An example might be <tt>"[-t type]"</tt>.
+   *
+   * @return A <em>very</em> terse description string.
+   */
+  String optionUsage();
+
+  /**
+   * Handle subclass-specific command line options and their arguments.
+   *
+   * @param mainName The name of the main class (useful for
+   *                 error messages.)
+   * @param thisArg The index of the current keyword.
+   * @param args The full list of arguments.
+   *
+   * @return less than <tt>0</tt> to indicate an error<br>
+   *         <tt>0</tt> to indicate that this argument is not used by this
+   *         class<br>
+   *         <tt>1 or more</tt> to indicate the number of arguments used<br>
+   */
+  int checkKeyword(String mainName, int thisArg, String[] args);
+
+  /**
+   * A short string included in the usage message to indicate
+   * valid keywords.<br>
+   * <br>
+   * An example might be <tt>"[username] [password]"</tt>.
+   *
+   * @return A <em>very</em> terse description string.
+   */
+  String keywordUsage();
+
+  /**
+   * Validate arguments after argument parsing has finished.<br>
+   * <br>
+   * This is useful for verifying that all required keywords and
+   * options have been specified, that options don't conflict
+   * with one another, etc.
+   *
+   * @param mainName The name of the main class (useful for
+   *                 error messages.)
+   *
+   * @return <tt>true</tt> if everything is correct, <tt>false</tt>
+   *         if there is a problem.
+   */
+  boolean finalizeArgs(String mainName);
+}
diff --git a/visad/util/CmdlineGenericConsumer.java b/visad/util/CmdlineGenericConsumer.java
new file mode 100644
index 0000000..7b0f92e
--- /dev/null
+++ b/visad/util/CmdlineGenericConsumer.java
@@ -0,0 +1,144 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+/**
+ * Simple implementation of CmdlineConsumer interface which
+ * can be <tt>extend</tt>ed to easily add command-line parsing
+ * to an application.
+ */
+public class CmdlineGenericConsumer
+  implements CmdlineConsumer
+{
+  /**
+   * Method used to initialize any instance variables which may be
+   * changed by a cmdline option.<br>
+   * <br>
+   * This is needed when arguments are processed inside the
+   * constructor.  If the first line in the constructor in a class
+   * which extends this class is <tt>super(args)</tt>,
+   * {@link visad.util.CmdlineParser CmdlineParser} will be run
+   * <em>before</em> any instance variables for the extending
+   * class are initialized.<br>
+   * <br>
+   * To ensure all instance variables are properly initialized,
+   * place the initialization code in the initializeArgs() method.
+   */
+  public void initializeArgs() { }
+
+  /**
+   * Handle subclass-specific command line options and their arguments.<br>
+   * <br>
+   * If <tt>-abc -d efg -h -1 -i</tt> is specified, this
+   * method will be called a maximum of 5 times:<ul>
+   * <li><tt>checkOption(mainName, 'a', "bc");</tt>
+   * <li><tt>checkOption(mainName, 'd', "efg");</tt>
+   * <li><tt>checkOption(mainName, 'h', "-1");</tt>
+   * <li><tt>checkOption(mainName, '1', "-i");</tt>
+   * <li><tt>checkOption(mainName, 'i', null);</tt>
+   * </ul>
+   * <br>
+   * Note that either of the last two method calls may not
+   * happen if the preceeding method call claims to have used
+   * the following argument (by returning <tt>2</tt>.<br>
+   * <br>
+   * For example,
+   * if the third call (where <tt>ch</tt> is set to <tt>'h'</tt>)
+   * returns <tt>0</tt> or <tt>1</tt>, the next call will contain
+   * <tt>'1'</tt> and <tt>"-i"</tt>.  If, however, the third call
+   * returns <tt>2</tt>, the next call will contain <tt>'i'</tt>
+   * and <tt>null</tt>.
+   *
+   * @param mainName The name of the main class (useful for
+   *                 error messages.)
+   * @param ch Option character.  If <tt>-a</tt> is specified
+   *           on the command line, <tt>'a'</tt> would be passed to
+   *           this method.)
+   * @param arg The argument associated with this option.
+   *
+   * @return less than <tt>0</tt> to indicate an error<br>
+   *         <tt>0</tt> to indicate that this option is not used by this
+   *         class<br>
+   *         <tt>1</tt> to indicate that only the option was used<br>
+   *         <tt>2</tt> or greater to indicate that both the option and the
+   *         argument were used
+   */
+  public int checkOption(String mainName, char ch, String arg)
+  {
+    return 0;
+  }
+
+  /**
+   * A short string included in the usage message to indicate
+   * valid options.  An example might be <tt>"[-t type]"</tt>.
+   *
+   * @return A <em>very</em> terse description string.
+   */
+  public String optionUsage() { return ""; }
+
+  /**
+   * Handle subclass-specific command line options and their arguments.
+   *
+   * @param mainName The name of the main class (useful for
+   *                 error messages.)
+   * @param thisArg The index of the current keyword.
+   * @param args The full list of arguments.
+   *
+   * @return less than <tt>0</tt> to indicate an error<br>
+   *         <tt>0</tt> to indicate that this argument is not used by this
+   *         class<br>
+   *         <tt>1 or more</tt> to indicate the number of arguments used<br>
+   */
+  public int checkKeyword(String mainName, int thisArg, String[] args)
+  {
+    return 0;
+  }
+
+  /**
+   * A short string included in the usage message to indicate
+   * valid keywords.<br>
+   * <br>
+   * An example might be <tt>"[username] [password]"</tt>.
+   *
+   * @return A <em>very</em> terse description string.
+   */
+  public String keywordUsage() { return ""; }
+
+  /**
+   * Validate arguments after argument parsing has finished.<br>
+   * <br>
+   * This is useful for verifying that all required keywords and
+   * options have been specified, that options don't conflict
+   * with one another, etc.
+   *
+   * @param mainName The name of the main class (useful for
+   *                 error messages.)
+   *
+   * @return <tt>true</tt> if everything is correct, <tt>false</tt>
+   *         if there is a problem.
+   */
+  public boolean finalizeArgs(String mainName)
+  {
+    return true;
+  }
+}
diff --git a/visad/util/CmdlineParser.java b/visad/util/CmdlineParser.java
new file mode 100644
index 0000000..42e07a5
--- /dev/null
+++ b/visad/util/CmdlineParser.java
@@ -0,0 +1,195 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.util.ArrayList;
+
+/**
+ * Parse command-line arguments passed to the initial main() method
+ * of an application.<br>
+ * <br>
+ * To use this, a class would implement <tt>CmdlineConsumer</tt>,
+ * then add the following code snippet to the constructor
+ * (assuming the constructor is supplied a list of arguments
+ *  named <tt>'args'</tt>):<br>
+ * <pre>
+ * <code>
+ *    CmdlineParser cmdline = new CmdlineParser(this);
+ *    if (!cmdline.processArgs(args)) {
+ * </code>
+ * <i>
+ *      complain about errors, exit, etc.
+ * </i>
+ * <code>
+ *    }
+ * </code>
+ *
+ */
+public class CmdlineParser
+{
+  private String mainName;
+  private ArrayList list;
+
+  /**
+   * Create a command-line parser.
+   *
+   * @param mainClass The class in which the main() method lives.
+   */
+  public CmdlineParser(Object mainClass)
+  {
+    String className = mainClass.getClass().getName();
+    int pt = className.lastIndexOf('.');
+    final int ds = className.lastIndexOf('$');
+    if (ds > pt) {
+      pt = ds;
+    }
+
+    mainName = className.substring(pt == -1 ? 0 : pt + 1);
+    list = null;
+
+    if (mainClass instanceof CmdlineConsumer) {
+      addConsumer((CmdlineConsumer )mainClass);
+    }
+  }
+
+  /**
+   * Add a command-line argument/keyword consumer.
+   *
+   * consumer Class which implements <tt>CmdlineConsumer</tt>.
+   */
+  public void addConsumer(CmdlineConsumer consumer)
+  {
+    if (list == null) {
+      list = new ArrayList();
+    }
+
+    list.add(consumer);
+  }
+
+  /**
+   * Get the name of the main class.
+   *
+   * @return main class name
+   */
+  public String getMainClassName() { return mainName; }
+
+  /**
+   * Pass all options/keywords on to all
+   * {@link CmdlineConsumer CmdlineConsumer}s.
+   *
+   * @param args Array of command-line arguments passed to main() method.
+   */
+  public boolean processArgs(String[] args)
+  {
+    boolean usage = false;
+
+    // if the are no consumers or arguments, we're done
+    if (list == null || args == null) {
+      return true;
+    }
+
+    // add consumers from newest to oldest
+    CmdlineConsumer[] consumers = new CmdlineConsumer[list.size()];
+    for (int c = 0; c < consumers.length; c++) {
+      consumers[c] = (CmdlineConsumer )list.get(consumers.length - (c + 1));
+    }
+
+    for (int c = 0; c < consumers.length; c++) {
+      consumers[c].initializeArgs();
+    }
+
+    for (int i = 0; !usage && i < args.length; i++) {
+      if (args[i].length() > 0 && args[i].charAt(0) == '-') {
+        char ch = args[i].charAt(1);
+
+        String str, result;
+
+        boolean strInOption = false;
+        if (args[i].length() > 2) {
+          str = args[i].substring(2);
+          strInOption = true;
+        } else if ((i + 1) < args.length) {
+          str = args[i+1];
+        } else {
+          str = null;
+        }
+
+        int handled;
+        for (int c = 0; c < consumers.length; c++) {
+          handled = consumers[c].checkOption(mainName, ch, str);
+          if (handled > 0) {
+            if (handled > 1) {
+              if (strInOption) {
+                handled = 1;
+              } else {
+                handled = 2;
+              }
+            }
+            i += (handled - 1);
+            break;
+          } else {
+            if (handled == 0) {
+              System.err.println(mainName + ": Unknown option \"-" + ch +
+                                 "\"");
+            }
+
+            usage = true;
+          }
+        }
+      } else {
+        int handled;
+        for (int c = 0; c < consumers.length; c++) {
+          handled = consumers[c].checkKeyword(mainName, i, args);
+          if (handled > 0) {
+            i += (handled - 1);
+            break;
+          } else {
+            if (handled == 0) {
+              System.err.println(mainName + ": Unknown keyword \"" +
+                                 args[i] + "\"");
+            }
+
+            usage = true;
+          }
+        }
+      }
+    }
+
+    for (int c = 0; !usage && c < consumers.length; c++) {
+      usage |= !consumers[c].finalizeArgs(mainName);
+    }
+
+    if (usage) {
+      StringBuffer buf = new StringBuffer("Usage: " + mainName);
+      for (int c = 0; c < consumers.length; c++) {
+        buf.append(consumers[c].optionUsage());
+      }
+      for (int c = 0; c < consumers.length; c++) {
+        buf.append(consumers[c].keywordUsage());
+      }
+      System.err.println(buf.toString());
+    }
+
+    return !usage;
+  }
+}
diff --git a/visad/util/CodeEditor.java b/visad/util/CodeEditor.java
new file mode 100644
index 0000000..227b5ac
--- /dev/null
+++ b/visad/util/CodeEditor.java
@@ -0,0 +1,180 @@
+//
+// CodeEditor.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.undo.*;
+import visad.VisADException;
+
+/** An editor for writing and executing source code in Java runtime. */
+public abstract class CodeEditor extends TextEditor {
+
+  /** text pane containing line numbers */
+  protected JTextArea lineNumbers;
+
+  /** number of lines of code in the document */
+  protected int numLines;
+
+  /** number of digits in lines of code in the document */
+  protected int numDigits;
+
+  /** constructs a CodeEditor */
+  public CodeEditor() {
+    this(null);
+  }
+
+  /** constructs a CodeEditor containing text from the given filename */
+  public CodeEditor(String filename) {
+    super(filename);
+
+    // set up line numbers
+    final int fontWidth = getFontMetrics(MONO).stringWidth(" ");
+    lineNumbers = new JTextArea("1") {
+      public Dimension getPreferredSize() {
+        Dimension d = super.getPreferredSize();
+        d.width = numDigits * fontWidth;
+        return d;
+      }
+    };
+    lineNumbers.setEditable(false);
+    lineNumbers.setFont(MONO);
+    numLines = 1;
+    numDigits = 1;
+    JPanel p = new JPanel() {
+      public void paint(Graphics g) {
+        // paint a thin gray line down right-hand side of line numbering
+        super.paint(g);
+        Dimension d = getSize();
+        int w = d.width - 5;
+        int h = d.height - 1;
+        g.setColor(Color.gray);
+        g.drawLine(w, 0, w, h);
+      }
+
+      public Dimension getPreferredSize() {
+        Dimension d = lineNumbers.getPreferredSize();
+        d.width += 10;
+        return d;
+      }
+    };
+    p.setBackground(Color.white);
+    p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
+    p.add(lineNumbers);
+    setRowHeaderView(p);
+  }
+
+  /** highlights the given line of code in the document */
+  public void highlightLine(int line) {
+    int start = 0;
+    String s = text.getText() + "\n";
+    for (int i=0; i<line-1; i++) start = s.indexOf("\n", start) + 1;
+    int end = s.indexOf("\n", start);
+    text.requestFocus();
+    text.setCaretPosition(start);
+    text.moveCaretPosition(end);
+  }
+
+  /** executes the source code in Java runtime */
+  public abstract void run() throws VisADException;
+
+  /** compiles the source code to a Java class */
+  public abstract void compile() throws VisADException;
+
+  /** executes the given line of code immediately in Java runtime */
+  public abstract void exec(String line) throws VisADException;
+
+  /** updates line numbers to match document text */
+  protected void updateLineNumbers() {
+    String s = text.getText();
+    int len = s.length();
+    int count = 1;
+
+    // count number of lines in document
+    int index = s.indexOf("\n");
+    while (index >= 0) {
+      count++;
+      index = s.indexOf("\n", index + 1);
+    }
+    if (count == numLines) return;
+
+    // compute index into line numbers text string
+    String l = lineNumbers.getText() + "\n";
+    int digits = ("" + count).length();
+    int spot = 0;
+    int nine = 9;
+    for (int i=2; i<=digits; i++) {
+      spot += i * nine;
+      nine *= 10;
+    }
+    int ten = nine / 9;
+    spot += (digits + 1) * (count - ten + 1);
+
+    // update line numbers text string
+    int maxSpot = l.length();
+    String newL;
+    if (spot < maxSpot) {
+      // eliminate extra line numbers
+      newL = l.substring(0, spot - 1);
+    }
+    else {
+      // append additional line numbers
+      StringBuffer sb = new StringBuffer(spot);
+      sb.append(l);
+      for (int i=numLines+1; i<count; i++) {
+        sb.append(i);
+        sb.append("\n");
+      }
+      sb.append(count);
+      newL = sb.toString();
+    }
+    numLines = count;
+    numDigits = digits;
+    lineNumbers.setText(newL);
+  }
+
+  /** undoes the last edit */
+  public void undo() throws CannotUndoException {
+    super.undo();
+    updateLineNumbers();
+  }
+
+  /** redoes the last undone edit */
+  public void redo() throws CannotRedoException {
+    super.redo();
+    updateLineNumbers();
+  }
+
+  /** updates line numbers when undoable action occurs */
+  public void undoableEditHappened(UndoableEditEvent e) {
+    super.undoableEditHappened(e);
+    if (!e.getEdit().isSignificant()) return;
+    updateLineNumbers();
+  }
+
+}
diff --git a/visad/util/CodeFrame.java b/visad/util/CodeFrame.java
new file mode 100644
index 0000000..3e35f7b
--- /dev/null
+++ b/visad/util/CodeFrame.java
@@ -0,0 +1,135 @@
+//
+// CodeFrame.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.event.*;
+import javax.swing.*;
+import visad.VisADException;
+import java.util.Vector;
+
+/** A GUI frame for editing source code in Java runtime. */
+public class CodeFrame extends TextFrame {
+
+  /** constructs a CodeFrame from the given CodeEditor object */
+  public CodeFrame(CodeEditor editor) {
+    super(editor);
+
+    // setup menu bar
+    addMenuItem("Command", "Run", "commandRun", 'r');
+    addMenuItem("Command", "Compile", "commandCompile", 'c');
+  }
+
+  /** sets up the GUI */
+  protected void layoutGUI() {
+    // set up content pane
+    JPanel pane = new JPanel();
+    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+    setContentPane(pane);
+
+    // set up immediate text field
+    JPanel immediate = new JPanel();
+    immediate.setLayout(new BoxLayout(immediate, BoxLayout.X_AXIS));
+    final JTextField textLine = new JTextField();
+    Util.adjustTextField(textLine);
+    textLine.setToolTipText(
+      "Enter a command and press enter to execute it immediately; up/down arrows recall commands");
+    final CodeEditor fTextPane = (CodeEditor) textPane;
+    final Vector stack = new Vector();
+    stack.addElement(new Integer(-1)); // first element is pointer
+    textLine.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        try {
+          String cmd = textLine.getText();
+          fTextPane.exec(cmd);
+          int sp = ((Integer)(stack.elementAt(0))).intValue();
+          if (sp < 0 || sp >= stack.size() ||
+            !cmd.equals( (String)stack.elementAt(sp) ) ) {
+            stack.addElement(textLine.getText());
+            stack.setElementAt(new Integer(stack.size()),0);
+          }
+          textLine.setText("");
+        }
+        catch (VisADException exc) {
+          showError(exc.getMessage());
+        }
+      }
+    });
+
+    textLine.addKeyListener(new KeyListener() {
+      public void keyReleased(KeyEvent e) {;}
+      public void keyTyped(KeyEvent e) {;}
+      public void keyPressed(KeyEvent e) {
+        int spoint = ((Integer)(stack.elementAt(0))).intValue();
+        if (spoint < 0 ) return;
+
+        int kc = e.getKeyCode();
+        if (kc == KeyEvent.VK_UP) {
+          spoint = spoint - 1;
+          if (spoint < 1) spoint = stack.size() - 1;
+          textLine.setText( (String)stack.elementAt(spoint));
+          stack.setElementAt(new Integer(spoint), 0);
+
+        } else if (kc == KeyEvent.VK_DOWN) {
+          spoint = spoint + 1;
+          if (spoint >= stack.size()) spoint = 1;
+          textLine.setText( (String)stack.elementAt(spoint));
+          stack.setElementAt(new Integer(spoint), 0);
+        }
+        
+      }
+    });
+
+    immediate.add(new JLabel("Immediate: "));
+    immediate.add(textLine);
+
+    // finish GUI setup
+    pane.add(textPane);
+    pane.add(immediate);
+    setTitle("VisAD Source Code Editor");
+  }
+
+  final
+
+  public void commandRun() {
+    try {
+      ((CodeEditor) textPane).run();
+    }
+    catch (VisADException exc) {
+      showError(exc.getMessage());
+    }
+  }
+
+  public void commandCompile() {
+    try {
+      ((CodeEditor) textPane).compile();
+    }
+    catch (VisADException exc) {
+      showError(exc.getMessage());
+    }
+  }
+
+}
diff --git a/visad/util/ColorChangeEvent.java b/visad/util/ColorChangeEvent.java
new file mode 100644
index 0000000..c96e074
--- /dev/null
+++ b/visad/util/ColorChangeEvent.java
@@ -0,0 +1,71 @@
+/*
+
+@(#) $Id: ColorChangeEvent.java,v 1.6 2000-08-22 18:17:09 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.util.*;
+import java.io.Serializable;
+
+/**
+ * An event to be dispatched when the color widget has been changed
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.6 $, $Date: 2000-08-22 18:17:09 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public class ColorChangeEvent extends EventObject implements Serializable {
+
+  /** The starting location where the ColorMap has changed */
+  private float start;
+  /** The ending location where the ColorMap has changed */
+  private float end;
+
+  /** Construct a colorChangeEvent object to notify any ColorChangeListeners
+   * @param source the map that generated the event
+   * @param start the start of the region of the map that has been modified
+   * @param end the end of the region of the map that has been modified
+   */
+  public ColorChangeEvent(Object source, float start, float end) {
+    super(source);
+    this.start = start;
+    this.end = end;
+  }
+
+  /** Get the start of the modified region of the map */
+  public float getStart() {
+    return start;
+  }
+
+  /** Get the end of the modified region of the map */
+  public float getEnd() {
+    return end;
+  }
+
+  /** Return a string representation of this object */
+  public String toString() {
+    return new String("Change: from " + start + " to " + end);
+  }
+}
diff --git a/visad/util/ColorChangeListener.java b/visad/util/ColorChangeListener.java
new file mode 100644
index 0000000..496adbd
--- /dev/null
+++ b/visad/util/ColorChangeListener.java
@@ -0,0 +1,43 @@
+/*
+
+@(#) $Id: ColorChangeListener.java,v 1.6 2000-03-14 16:56:47 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.util.EventListener;
+
+/**
+ * The interface that all objects must implement to recieve color change
+ * events from the color widget
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.6 $, $Date: 2000-03-14 16:56:47 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public interface ColorChangeListener extends EventListener {
+
+  /** The function called when the color widget has changed */
+  void colorChanged(ColorChangeEvent e);
+}
diff --git a/visad/util/ColorMap.java b/visad/util/ColorMap.java
new file mode 100644
index 0000000..2a15faa
--- /dev/null
+++ b/visad/util/ColorMap.java
@@ -0,0 +1,197 @@
+/*
+
+@(#) $Id: ColorMap.java,v 1.11 2000-08-22 18:17:09 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Color;
+
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * The abstract class that all color-mapping widgets must extend.  This
+ * class manages all of the listener notification for the ColorMaps.
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.11 $, $Date: 2000-08-22 18:17:09 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public abstract class ColorMap extends JPanel {
+
+  /** Maps a floating point value (in the range 0 to 1) onto a Color,
+   * returns Color.black if the number is out of range
+   *
+   * If you're getting more than 1 or 2 colors,
+   * use getColors() instead.
+   */
+  public Color getColor(float value) {
+    if (value < 0 || value > 1) {
+      return Color.black;
+    }
+
+    float[] rgb = getRGBTuple(value);
+
+    if (rgb[0] < 0) rgb[0] = 0;
+    if (rgb[1] < 0) rgb[1] = 0;
+    if (rgb[2] < 0) rgb[2] = 0;
+    if (rgb[0] > 1) rgb[0] = 1;
+    if (rgb[1] > 1) rgb[1] = 1;
+    if (rgb[2] > 1) rgb[2] = 1;
+
+    return new Color(rgb[0], rgb[1], rgb[2]);
+  }
+
+  /**
+   * Map a range of floating point values (in the range 0 to 1) onto
+   * a range of Colors.
+   */
+  public Color[] getColors(float start, float end, int num) {
+    float[][] rgb;
+    if (getMapDimension() < 3) {
+      rgb = getRGBTuples(start, end, num);
+    } else {
+      rgb = getTuples(start, end, num);
+    }
+
+    if (rgb == null) {
+      return null;
+    }
+
+    Color[] colors = new Color[rgb.length];
+
+    for (int i = rgb.length - 1; i >= 0; i--) {
+      if (rgb[i][0] < 0.0f)
+        rgb[i][0] = 0.0f;
+      else if (rgb[i][0] > 1.0f)
+        rgb[i][0] = 1.0f;
+      if (rgb[i][1] < 0.0f)
+        rgb[i][1] = 0.0f;
+      else if (rgb[i][1] > 1.0f)
+        rgb[i][1] = 1.0f;
+      if (rgb[i][2] < 0.0f)
+        rgb[i][2] = 0.0f;
+      else if (rgb[i][2] > 1.0f)
+        rgb[i][2] = 1.0f;
+
+      colors[i] = new Color(rgb[i][0], rgb[i][1], rgb[i][2]);
+    }
+
+    return colors;
+  }
+
+  /**
+   * Maps a floating point value (in the range 0 to 1) onto an RGB
+   * triplet of floating point numbers in the range 0 to 1)
+   *
+   * If you're getting more than 1 or 2 tuples,
+   * use getRGBTuples() instead.
+   */
+  public abstract float[] getRGBTuple(float value);
+
+  /**
+   * Maps the specified floating point values (in the range 0 to 1)
+   * onto a group of RGB triplets of floating point numbers in the
+   * range 0 to 1.  The endpoints are included in <TT>count</TT>
+   * so if <TT>count=1</TT>, only the RGBTuple for <TT>start</TT>
+   * is returned; if <TT>count=3</TT>, then RGBTuples are returned
+   * for <TT>start</TT>, the midpoint between <TT>start</TT> and
+   * <TT>end, and for <TT>end</TT>.
+   *
+   * @param start the first value to translate
+   * @param end the last value to translate
+   * @param count the number of values (including the two endpoints)
+   *              to be returned.
+   */
+  public abstract float[][] getRGBTuples(float start, float end, int count);
+
+  /**
+   * Maps a floating point value (in the range 0 to 1) into a tuple
+   * with dimension of the map
+   *
+   * If you're getting more than 1 or 2 tuples,
+   * use getTuples() instead.
+   */
+  public abstract float[] getTuple(float value);
+
+  /**
+   * Maps the specified floating point values (in the range 0 to 1)
+   * onto a group of floating point tuples with the dimension of
+   * the map.  The endpoints are included in <TT>count</TT>
+   * so if <TT>count=1</TT>, only the tuple for <TT>start</TT>
+   * is returned; if <TT>count=3</TT>, then tuples are returned
+   * for <TT>start</TT>, the midpoint between <TT>start</TT> and
+   * <TT>end, and for <TT>end</TT>.
+   *
+   * @param start The first value to translate
+   * @param end The last value to translate
+   * @param count The number of values (including the two endpoints)
+   *              to be returned.
+   */
+  public abstract float[][] getTuples(float start, float end, int count);
+
+  /** Returns the current map resolution */
+  public abstract int getMapResolution();
+
+  /** Returns the dimension of the map */
+  public abstract int getMapDimension();
+
+  /** Returns a copy of the ColorMap */
+  public abstract float[][] getColorMap();
+
+  /** The vector containing the ColorChangeListeners */
+  private Vector listeners = new Vector();
+
+  /** Add a ColorChangeListener to the listeners list */
+  public void addColorChangeListener(ColorChangeListener c) {
+    synchronized (listeners) {
+      if (!listeners.contains(c)) {
+        listeners.addElement(c);
+      }
+    }
+  }
+
+  /** Remove a ColorChangeListener from the listeners list */
+  public void removeColorChangeListener(ColorChangeListener c) {
+    synchronized (listeners) {
+      if (listeners.contains(c)) {
+        listeners.removeElement(c);
+      }
+    }
+  }
+
+  /** Notify the ColorChangeListerers that the color widget has changed */
+  protected void notifyListeners(ColorChangeEvent e) {
+    Vector cl = null;
+    synchronized (listeners) {
+      cl = (Vector) listeners.clone();
+    }
+    for (int i = 0; i < cl.size(); i++) {
+      ColorChangeListener c = (ColorChangeListener) cl.elementAt(i);
+      c.colorChanged(e);
+    }
+  }
+}
diff --git a/visad/util/ColorMapWidget.java b/visad/util/ColorMapWidget.java
new file mode 100644
index 0000000..fd06ac0
--- /dev/null
+++ b/visad/util/ColorMapWidget.java
@@ -0,0 +1,463 @@
+/*
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.FlowLayout;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JPanel;
+
+import java.rmi.RemoteException;
+
+import visad.BaseColorControl;
+import visad.Control;
+import visad.ControlEvent;
+import visad.ControlListener;
+import visad.DisplayException;
+import visad.ScalarMap;
+import visad.ScalarMapEvent;
+import visad.ScalarMapControlEvent;
+import visad.ScalarMapListener;
+import visad.VisADException;
+
+/**
+ * A color widget that allows users to interactively map numeric data to
+ * RGB/RGBA tuples in a <CODE>ScalarMap</CODE>.
+ */
+public class ColorMapWidget
+  extends SimpleColorMapWidget
+  implements ActionListener, ControlListener, ScalarMapListener
+{
+  private JPanel buttonPanel = null;
+  private float[][] undoTable = null;
+
+  BaseColorControl control, realControl;
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public ColorMapWidget(ScalarMap smap)
+    throws VisADException, RemoteException
+  {
+    this(smap, null, true, true);
+  }
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param immediate <CODE>true</CODE> if changes are immediately
+   *                  propagated to the associated <CODE>Control</CODE>.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public ColorMapWidget(ScalarMap smap, boolean immediate)
+    throws VisADException, RemoteException
+  {
+    this(smap, null, true, immediate);
+  }
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * The initial color table (if non-null)
+   * should be a <CODE>float[resolution][dimension]</CODE>, where
+   * <CODE>dimension</CODE> is either
+   * <CODE>3</CODE> for <CODE>Display.RGB</CODE> or
+   * <CODE>4</CODE> for <CODE>Display.RGB</CODE>) with values
+   * between <CODE>0.0f</CODE> and <CODE>1.0f</CODE>.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param table Initial color lookup table.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public ColorMapWidget(ScalarMap smap, float[][] table)
+    throws VisADException, RemoteException
+  {
+    this(smap, table, true, true);
+  }
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * The initial color table (if non-null)
+   * should be a <CODE>float[resolution][dimension]</CODE>, where
+   * <CODE>dimension</CODE> is either
+   * <CODE>3</CODE> for <CODE>Display.RGB</CODE> or
+   * <CODE>4</CODE> for <CODE>Display.RGB</CODE>) with values
+   * between <CODE>0.0f</CODE> and <CODE>1.0f</CODE>.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param table Initial color lookup table.
+   * @param update <CODE>true</CODE> if the slider should follow the
+   *               <CODE>ScalarMap</CODE>'s range.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public ColorMapWidget(ScalarMap smap, float[][] table, boolean update)
+    throws VisADException, RemoteException
+  {
+    this(smap, table, update, true);
+  }
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * The initial color table (if non-null)
+   * should be a <CODE>float[resolution][dimension]</CODE>, where
+   * <CODE>dimension</CODE> is either
+   * <CODE>3</CODE> for <CODE>Display.RGB</CODE> or
+   * <CODE>4</CODE> for <CODE>Display.RGB</CODE>) with values
+   * between <CODE>0.0f</CODE> and <CODE>1.0f</CODE>.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param table Initial color lookup table.
+   * @param update <CODE>true</CODE> if the slider should follow the
+   *               <CODE>ScalarMap</CODE>'s range.
+   * @param immediate <CODE>true</CODE> if changes are immediately
+   *                  propagated to the associated <CODE>Control</CODE>.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public ColorMapWidget(ScalarMap smap, float[][] table, boolean update,
+                        boolean immediate)
+    throws VisADException, RemoteException
+  {
+    super(smap.getScalarName(), smap.getControl(),
+          (float )smap.getRange()[0], (float )smap.getRange()[1] + 1.0f);
+
+    // make sure we're using a valid scalarmap
+    Control ctl = smap.getControl();
+    if (!(ctl instanceof BaseColorControl)) {
+      throw new DisplayException(getClass().getName() + ": ScalarMap must " +
+                                 "be Display.RGB or Display.RGBA");
+    }
+
+    // save the control
+    if (immediate) {
+      control = (BaseColorControl )ctl;
+      realControl = null;
+    } else {
+      realControl = (BaseColorControl )ctl;
+      control = new BaseColorControl(realControl.getDisplay(),
+                                     realControl.getNumberOfComponents());
+      control.syncControl(realControl);
+
+      // use the shadow control, not the real control
+      baseMap = new BaseRGBMap(control);
+      preview.setMap(baseMap);
+      rebuildGUI();
+    }
+
+    // set up color table
+    if (table != null) {
+      control.setTable(table);
+    }
+
+    // if not in immediate mode, build "Apply" and "Undo" buttons
+    if (!immediate) {
+      buttonPanel = buildButtons();
+      add(buttonPanel);
+    }
+
+    // listen for changes
+    if (realControl != null) {
+      realControl.addControlListener(this);
+    } else {
+      control.addControlListener(this);
+    }
+    if (update) {
+      smap.addScalarMapListener(this);
+    }
+  }
+
+  /**
+   * Use a new table of color values.
+   * If immediate mode is off, changes to the associated color
+   * control are not applied until the Apply button is clicked.
+   *
+   * @param table New color values.
+   */
+  public void setTableView(float[][] table) {
+    try { control.setTable(table); }
+    catch (VisADException exc) { exc.printStackTrace(); }
+    catch (RemoteException exc) { exc.printStackTrace(); }
+  }
+
+  /**
+   * Gets the widget's current table. If immediate mode is
+   * off, it may not match the linked color control's table.
+   */
+  public float[][] getTableView() { return control.getTable(); }
+
+  /**
+   * Build "Apply" and "Undo" button panel.
+   *
+   * @return JPanel containing the buttons.
+   */
+  private JPanel buildButtons()
+  {
+    JButton apply = new JButton("Apply");
+    apply.setActionCommand("apply");
+    apply.addActionListener(this);
+
+    JButton undo = new JButton("Undo");
+    undo.setActionCommand("undo");
+    undo.addActionListener(this);
+
+    JPanel panel = new JPanel();
+    panel.setLayout(new FlowLayout(FlowLayout.CENTER,5,5));
+    panel.add(apply);
+    panel.add(undo);
+
+    // save initial table in case they immediately hit "undo"
+    undoTable = control.getTable();
+
+    return panel;
+  }
+
+  /**
+   * Return the panel in which the buttons are stored.
+   */
+  public JPanel getButtonPanel() { return buttonPanel; }
+
+  /**
+   * Handle button presses.
+   *
+   * @param evt Data from the changed <CODE>JButton</CODE>.
+   */
+  public void actionPerformed(ActionEvent evt)
+  {
+    if (evt.getActionCommand().equals("apply")) {
+      undoTable = realControl.getTable();
+      try {
+        realControl.syncControl(control);
+      } catch (VisADException ve) {
+      }
+    } else if (evt.getActionCommand().equals("undo")) {
+      try {
+        realControl.setTable(undoTable);
+      } catch (VisADException ve) {
+      } catch (RemoteException re) {
+      }
+    }
+  }
+
+  /**
+   * Forward changes from the <CODE>Control</CODE> associated with
+   * this widget's <CODE>ScalarMap</CODE> to the internal
+   * shadow <CODE>Control</CODE>.
+   *
+   * @param evt Data from the changed <CODE>Control</CODE>.
+   */
+  public void controlChanged(ControlEvent evt)
+    throws RemoteException, VisADException
+  {
+    if (realControl == evt.getControl()) {
+      control.syncControl(realControl);
+    }
+  }
+
+  /**
+   * If the <CODE>ScalarMap</CODE> changes, update the slider with
+   * the new range.
+   *
+   * @param evt Data from the changed <CODE>ScalarMap</CODE>.
+   */
+  public void mapChanged(ScalarMapEvent evt)
+  {
+    double[] range = evt.getScalarMap().getRange();
+    updateSlider((float) range[0], (float) range[1]);
+  }
+
+  /**
+   * ScalarMapListener method used to detect new control.
+   */
+  public void controlChanged(ScalarMapControlEvent evt)
+    throws RemoteException, VisADException
+  {
+    int id = evt.getId();
+    if (id == ScalarMapEvent.CONTROL_REMOVED ||
+        id == ScalarMapEvent.CONTROL_REPLACED)
+    {
+      if (realControl != null) {
+        evt.getControl().removeControlListener(this);
+      }
+    }
+
+    if (id == ScalarMapEvent.CONTROL_REPLACED ||
+        id == ScalarMapEvent.CONTROL_ADDED)
+    {
+      BaseColorControl ctl;
+      ctl = (BaseColorControl )(evt.getScalarMap().getControl());
+      if (realControl != null) {
+        realControl = ctl;
+        realControl.addControlListener(this);
+      }
+    }
+  }
+
+  public static void main(String[] args)
+  {
+    try {
+      visad.RealType vis = visad.RealType.getRealType("vis");
+      ScalarMap visMap = new ScalarMap(vis, visad.Display.RGBA);
+      visMap.setRange(0.0f, 1.0f);
+
+      visad.RealType ir = visad.RealType.getRealType("ir");
+      ScalarMap irMap = new ScalarMap(vis, visad.Display.RGB);
+      irMap.setRange(0.0f, 1.0f);
+
+      visad.DisplayImpl dpy = new visad.java2d.DisplayImplJ2D("2d");
+      dpy.addMap(visMap);
+      dpy.addMap(irMap);
+
+      javax.swing.JFrame f;
+
+      f = new javax.swing.JFrame("0");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new ColorMapWidget(visMap));
+      f.pack();
+      f.setVisible(true);
+
+      f = new javax.swing.JFrame("1");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new ColorMapWidget(visMap, false));
+      f.pack();
+      f.setVisible(true);
+
+      f = new javax.swing.JFrame("!Updated");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new ColorMapWidget(visMap, null, false, true));
+      f.pack();
+      f.setVisible(true);
+
+      final int num = 3;
+      final int len = 256;
+      float[][] table = new float[num][len];
+      final float step = 1.0f / (len - 1.0f);
+      float total = 1.0f;
+      for (int j=0; j<len; j++) {
+        table[0][j] = table[1][j] = table[2][j] = total;
+        if (num > 3) {
+          table[3][j] = 1.0f;
+        }
+        total -= step;
+      }
+
+      f = new javax.swing.JFrame("Table");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new ColorMapWidget(irMap, table));
+      f.pack();
+      f.setVisible(true);
+
+      try { Thread.sleep(5000); } catch (InterruptedException ie) { }
+
+      visMap.setRange(-10.0f, 10.0f);
+    } catch (RemoteException re) {
+      re.printStackTrace();
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+  }
+}
diff --git a/visad/util/ColorPreview.java b/visad/util/ColorPreview.java
new file mode 100644
index 0000000..8a294f8
--- /dev/null
+++ b/visad/util/ColorPreview.java
@@ -0,0 +1,186 @@
+/*
+
+@(#) $Id: ColorPreview.java,v 1.14 2001-12-20 13:00:27 billh Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+
+import javax.swing.JPanel;
+
+/**
+ * A small preview bar generated for a color widget
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.14 $, $Date: 2001-12-20 13:00:27 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public class ColorPreview extends JPanel implements ColorChangeListener {
+
+  /** The ColorWidget that this is attached to */
+  private ColorMap map;
+
+  /** The height of the ColorPreview */
+  private int height = 15;
+
+  /**
+   * Constructs a ColorPreview that Listens to the specified
+   * widget and has the default height
+   *
+   * @deprecated Specify the ColorMap instead.
+   */
+  ColorPreview(ColorWidget widget) {
+    this(widget, 15);
+  }
+
+  /**
+   * Constructs a ColorPreview that listens to the specified
+   * ColorWidget and has the specified height
+   *
+   * @deprecated Specify the ColorMap instead.
+   */
+  public ColorPreview(ColorWidget widget, int height) {
+    this(widget.getColorMap(), height);
+  }
+
+  /**
+   * Constructs a ColorPreview that Listens to the specified
+   * ColorMap and has the default height
+   */
+  ColorPreview(ColorMap map) {
+    this(map, 15);
+  }
+
+  /**
+   * Constructs a ColorPreview that listens to the specified
+   * ColorMap and has the specified height
+   */
+  public ColorPreview(ColorMap map, int height) {
+    this.map = map;
+    this.height = height;
+    map.addColorChangeListener(this);
+  }
+
+  /** Overridden to maintain the preview at the specified height */
+  public Dimension getMaximumSize() {
+    return new Dimension(Integer.MAX_VALUE, height);
+  }
+
+  /** Redraw the entire panel */
+  public void paint(Graphics g) {
+    updateLeft = 0;
+    updateRight = 1;
+    update(g);
+  }
+
+  /** The location to begin an update */
+  private float updateLeft;
+  /** The location to end an update */
+  private float updateRight;
+
+  /** Updates the nessecary areas of the panel after ColorChangeEvents and paint()
+   * @see ColorChangeEvent
+   */
+  public void update(Graphics g) {
+
+    int leftIndex;
+    int rightIndex;
+
+    synchronized(this) {
+      leftIndex = (int) Math.floor(updateLeft * getBounds().width);
+      rightIndex = (int) Math.floor(updateRight * getBounds().width);
+      updateLeft = 1;
+      updateRight = 0;
+    }
+
+    if (leftIndex > rightIndex) {
+      int tmp = leftIndex;
+      leftIndex = rightIndex;
+      rightIndex = tmp;
+    }
+
+    if (leftIndex < 0) {
+      leftIndex = 0;
+    }
+    if (leftIndex >= getBounds().width) {
+      leftIndex = getBounds().width - 1;
+    }
+    if (rightIndex < 0) {
+      rightIndex = 0;
+    }
+    if (rightIndex >= getBounds().width) {
+      rightIndex = getBounds().width - 1;
+    }
+
+    final float fWidth = (float )getBounds().width;
+    int num = (rightIndex - leftIndex) + 1;
+
+    Color[] c = map.getColors((float )leftIndex / fWidth,
+                              (float )rightIndex / fWidth,
+                              num);
+
+    if (num > c.length) num = c.length; // WLH 20 Dec 2001
+
+    for (int i = 0; i < num; i++) {
+      g.setColor(c[i]);
+      g.drawLine(i+leftIndex,0,i+leftIndex,getBounds().height - 1);
+    }
+  }
+
+  /** Implementation of the ColorChangeListener interface
+   * @see ColorChangeListener
+   */
+  public void colorChanged(ColorChangeEvent e) {
+    synchronized(this) {
+      if (e.getStart() < updateLeft) {
+        updateLeft = e.getStart();
+      }
+      if (e.getEnd() > updateRight) {
+        updateRight = e.getEnd();
+      }
+    }
+
+    // redraw
+    validate();
+    repaint();
+  }
+
+  /** Finds the preferred width of the ColorMap, and returns it with the specified
+   * height
+   */
+  public Dimension getPreferredSize() {
+    Dimension d = map.getPreferredSize();
+    return new Dimension(d.width, height);
+  }
+
+  public void setMap(ColorMap newMap)
+  {
+    map.removeColorChangeListener(this);
+    newMap.addColorChangeListener(this);
+    map = newMap;
+  }
+}
diff --git a/visad/util/ColorWidget.java b/visad/util/ColorWidget.java
new file mode 100644
index 0000000..42ed8e4
--- /dev/null
+++ b/visad/util/ColorWidget.java
@@ -0,0 +1,225 @@
+/*
+@(#) $Id: ColorWidget.java,v 1.15 2002-06-28 15:59:02 tomw Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Dimension;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.applet.Applet;
+
+import java.rmi.RemoteException;
+
+import java.util.Vector;
+
+import javax.swing.*;
+
+import visad.VisADException;
+
+/**
+ * A color widget that allows users to interactively map numeric data to
+ * RGBA tuples based on the Vis5D color widget
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.15 $, $Date: 2002-06-28 15:59:02 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public class ColorWidget extends Applet implements ColorChangeListener {
+
+  /** The visibility of the preview panel at the botom of the widget */
+  private boolean previewVisible;
+
+  /** The ColorMap associsted with this color widget */
+  private ColorMap map;
+
+  /** The ColorPreview associated with this color widget */
+  private ColorPreview colorPreview;
+
+  /* / * * The Event Queue for mouse events */
+
+  /** Construct a color widget with a ColorPreview and the default ColorMap */
+  public ColorWidget()
+    throws RemoteException, VisADException
+  {
+    this(true);
+  }
+
+  /** Construct a color widget with the default ColorMap
+   * @param preview indicates wether or not the preview bar at the
+   * bottom of the widget should be present
+   */
+  public ColorWidget(boolean preview)
+    throws RemoteException, VisADException
+  {
+    this(new RGBMap(), preview);
+  }
+
+  /** Construct a color widget with a ColorPreview and the specified ColorMap
+   * @param map the ColorMap for the widget to use
+   */
+  public ColorWidget(ColorMap map) {
+    this(map, true);
+  }
+
+  /** Construct a color widget with the desired ColorMap and ColorPreview visibility
+   * @param map the ColorMap for the widget to use
+   * @param preview indicates wether or not the preview bar at the
+   * bottom of the widget should be present
+   */
+  public ColorWidget(ColorMap map, boolean preview) {
+    this.map = map;
+
+    previewVisible = preview;
+    if (preview) {
+      colorPreview = new ColorPreview(map);
+    }
+    //setLayout(new WidgetLayout(this));
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    setColorMap(map);
+  }
+
+  /** main method for standalone testing */
+  public static void main(String[] argv)
+    throws RemoteException, VisADException
+  {
+
+    JFrame frame;
+    frame = new JFrame("VisAD Color Widget");
+    frame.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+
+    ColorWidget w = new ColorWidget(new RGBAMap());
+
+    frame.getContentPane().add(w);
+    frame.setSize(w.getPreferredSize());
+    frame.setVisible(true);
+
+  }
+
+  /** The vector containing the ColorChangeListeners */
+  private Vector listeners = new Vector();
+  private Object listeners_lock = new Object();
+
+  /** Add a ColorChangeListener to the listeners list */
+  // public synchronized void addColorChangeListener(ColorChangeListener c) {
+  public void addColorChangeListener(ColorChangeListener c) {
+    synchronized (listeners_lock) {
+      if (!listeners.contains(c)) {
+        listeners.addElement(c);
+      }
+    }
+  }
+
+  /** Remove a ColorChangeListener from the listeners list */
+  // public synchronized void removeColorChangeListener(ColorChangeListener c) {
+  public void removeColorChangeListener(ColorChangeListener c) {
+    synchronized (listeners_lock) {
+      if (listeners.contains(c)) {
+        listeners.removeElement(c);
+      }
+    }
+  }
+
+  /** Notify the ColorChangeListerers that the color widget has changed */
+  // protected synchronized void notifyListeners(ColorChangeEvent e) {
+  protected void notifyListeners(ColorChangeEvent e) {
+    Vector listeners_clone = null;
+    synchronized (listeners_lock) {
+      listeners_clone = (Vector) listeners.clone();
+    }
+    for (int i = 0; i < listeners_clone.size(); i++) {
+      ColorChangeListener c =
+        (ColorChangeListener) listeners_clone.elementAt(i);
+      c.colorChanged(e);
+    }
+  }
+
+  /** Listen to the ColorMap and re-dispatch the ColorChangeEvents to
+   * the ColorChangeListeners listening to the widget
+   */
+  public void colorChanged(ColorChangeEvent e) {
+    notifyListeners(e);
+  }
+
+  /** Set the ColorWidget to listen to a specific ColorMap */
+  public void setColorMap(ColorMap map) {
+    if (this.map != null) {
+      this.map.removeColorChangeListener(this);
+    }
+
+    this.map = map;
+
+    map.addColorChangeListener(this);
+
+    removeAll();
+    add(map);
+    if (previewVisible) {
+      if (colorPreview == null) {
+        colorPreview = new ColorPreview(map);
+      }
+      add(colorPreview);
+    }
+    validate();
+
+    int res = map.getMapResolution();
+    notifyListeners(new ColorChangeEvent(map, 0, res));
+  }
+
+  /** Make the preview bar at the bottom of the widget visible */
+  public void showPreview() {
+
+    if (previewVisible) return;
+    previewVisible = true;
+    this.setColorMap(map);
+  }
+
+  /** Hide the preview bar at the bottom of the widget */
+  public void hidePreview() {
+
+    if (!previewVisible) return;
+
+    previewVisible = false;
+    this.setColorMap(map);
+  }
+
+  /** Returns the ColorMap that the color wdget is curently pointing to */
+  public ColorMap getColorMap() {
+    return map;
+  }
+
+  /** Analyses the visible components and determines the preferred size */
+  public Dimension getPreferredSize() {
+    Dimension d = map.getPreferredSize();
+    if (previewVisible) {
+      Dimension p = colorPreview.getPreferredSize();
+      Dimension n = new Dimension(d.width, d.height + p.height);
+      d = n;
+    }
+    return d;
+  }
+}
diff --git a/visad/util/ComboFileFilter.java b/visad/util/ComboFileFilter.java
new file mode 100644
index 0000000..75a19c5
--- /dev/null
+++ b/visad/util/ComboFileFilter.java
@@ -0,0 +1,144 @@
+//
+// ComboFileFilter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.io.File;
+import java.util.*;
+import javax.swing.filechooser.FileFilter;
+
+/** A file filter that recognizes files from a union of other filters. */
+public class ComboFileFilter extends FileFilter
+  implements java.io.FileFilter, Comparable
+{
+
+  // -- Fields --
+
+  /** List of filters to be combined. */
+  private FileFilter[] filts;
+
+  /** Description. */
+  private String desc;
+
+  // -- Constructor --
+
+  /** Constructs a new filter from a list of other filters. */
+  public ComboFileFilter(FileFilter[] filters, String description) {
+    filts = new FileFilter[filters.length];
+    System.arraycopy(filters, 0, filts, 0, filters.length);
+    desc = description;
+  }
+
+  // -- ComboFileFilter API methods --
+
+  /** Gets the list of file filters forming this filter combination. */
+  public FileFilter[] getFilters() {
+    FileFilter[] ff = new FileFilter[filts.length];
+    System.arraycopy(filts, 0, ff, 0, filts.length);
+    return ff;
+  }
+
+  // -- Static ComboFileFilter API methods --
+
+  /**
+   * Sorts the given list of file filters, and combines filters with identical
+   * descriptions into a combination filter that accepts anything any of its
+   * constituant filters do.
+   */
+  public static FileFilter[] sortFilters(FileFilter[] filters) {
+    return sortFilters(new Vector(Arrays.asList(filters)));
+  }
+
+  /**
+   * Sorts the given list of file filters, and combines filters with identical
+   * descriptions into a combination filter that accepts anything any of its
+   * constituant filters do.
+   */
+  public static FileFilter[] sortFilters(Vector filters) {
+    // sort filters alphanumerically
+    Collections.sort(filters);
+
+    // combine matching filters
+    int len = filters.size();
+    Vector v = new Vector(len);
+    for (int i=0; i<len; i++) {
+      FileFilter ffi = (FileFilter) filters.elementAt(i);
+      int ndx = i + 1;
+      while (ndx < len) {
+        FileFilter ff = (FileFilter) filters.elementAt(ndx);
+        if (!ffi.getDescription().equals(ff.getDescription())) break;
+        ndx++;
+      }
+      if (ndx > i + 1) {
+        // create combination filter for matching filters
+        FileFilter[] temp = new FileFilter[ndx - i];
+        for (int j=0; j<temp.length; j++) {
+          temp[j] = (FileFilter) filters.elementAt(i + j);
+        }
+        v.add(new ComboFileFilter(temp, temp[0].getDescription()));
+        i += temp.length - 1; // skip next temp-1 filters
+      }
+      else v.add(ffi);
+    }
+    FileFilter[] result = new FileFilter[v.size()];
+    v.copyInto(result);
+    return result;
+  }
+
+  // -- FileFilter API methods --
+
+  /** Accepts files with the proper filename prefix. */
+  public boolean accept(File f) {
+    for (int i=0; i<filts.length; i++) {
+      if (filts[i].accept(f)) return true;
+    }
+    return false;
+  }
+
+  /** Returns the filter's description. */
+  public String getDescription() { return desc; }
+
+  // -- Object API methods --
+
+  /** Gets a string representation of this file filter. */
+  public String toString() {
+    StringBuffer sb = new StringBuffer("ComboFileFilter: ");
+    sb.append(desc);
+    for (int i=0; i<filts.length; i++) {
+      sb.append("\n\t");
+      sb.append(filts[i].toString());
+    }
+    return sb.toString();
+  }
+
+  // -- Comparable API methods --
+
+  /** Compares two FileFilter objects alphanumerically. */
+  public int compareTo(Object o) {
+    return desc.compareToIgnoreCase(((FileFilter) o).getDescription());
+  }
+
+}
diff --git a/visad/util/ContourWidget.java b/visad/util/ContourWidget.java
new file mode 100644
index 0000000..3a4add0
--- /dev/null
+++ b/visad/util/ContourWidget.java
@@ -0,0 +1,609 @@
+//
+// ContourWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+/* AWT packages */
+import java.awt.Color;
+import java.awt.Dimension;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+/* JFC packages */
+import javax.swing.*;
+import javax.swing.event.*;
+
+/* RMI classes */
+import java.rmi.RemoteException;
+
+/* VisAD packages */
+import visad.*;
+
+/** A widget that allows users to control iso-contours.<P> */
+public class ContourWidget
+  extends JPanel
+  implements ActionListener, ChangeListener, ItemListener, ControlListener,
+             ScalarMapListener
+{
+
+  /** This ContourRangeSlider's associated Control. */
+  private ContourControl control;
+
+  private float cInterval;
+  private float cBase;
+  private float cSurface;
+  private float cLo;
+  private float cHi;
+
+  private String name;
+
+  private JTextField Interval;
+  private JTextField Base;
+  private JLabel SurfaceLabel;
+  private JSlider Surface;
+  private JCheckBox Labels;
+  private JCheckBox Contours;
+  private JCheckBox Dashed;
+  private ContourRangeSlider ContourRange;
+
+  private JCheckBox Fill;
+
+  /** construct a ContourWidget linked to the Control in the map
+      (which must be to Display.IsoContour), with default interval,
+      base, min, max, and surface value, and auto-scaling min and max. */
+  public ContourWidget(ScalarMap smap) throws VisADException, RemoteException {
+    this(smap, Float.NaN, Float.NaN, Float.NaN, Float.NaN, Float.NaN, true);
+  }
+
+  /** construct a ContourWidget linked to the Control in the map
+      (which must be to Display.IsoContour), with specified surface
+      value, and default interval, min, max, and base, and auto-scaling
+      min and max. */
+  public ContourWidget(ScalarMap smap, float surf) throws VisADException,
+                                                          RemoteException {
+    this(smap, Float.NaN, Float.NaN, Float.NaN, Float.NaN, surf, true);
+  }
+
+  /** construct a ContourWidget linked to the Control in the map
+      (which must be to Display.IsoContour), with specified interval
+      and base, default surface value, min, and max, and auto-scaling
+      min and max. */
+  public ContourWidget(ScalarMap smap, float interv, float min, float max,
+                       float ba) throws VisADException, RemoteException {
+    this(smap, interv, min, max, ba, Float.NaN, true);
+  }
+
+  /** construct a ContourWidget linked to the Control in the map
+      (which must be to Display.IsoContour), with specified interval,
+      minimum, maximum, base, surface value, and auto-scale behavior. */
+  public ContourWidget(ScalarMap smap, float interv, float min, float max,
+                       float ba, float surf, boolean update)
+                       throws VisADException, RemoteException {
+    // verify scalar map
+    if (!Display.IsoContour.equals(smap.getDisplayScalar())) {
+      throw new DisplayException("ContourWidget: ScalarMap must " +
+                                 "be to Display.IsoContour");
+    }
+    name = smap.getScalarName();
+
+    // get control settings
+    control = (ContourControl) smap.getControl();
+
+    boolean[] flags = new boolean[2];
+    float[] values = new float[5];
+    control.getMainContours(flags, values);
+
+    // initialize flags from control settings
+    boolean contourFlag, labelFlag, dashFlag, fillFlag;
+    contourFlag = flags[0];
+    labelFlag = flags[1];
+    dashFlag = (values[1] < 0.0f);
+
+    fillFlag = control.contourFilled();
+
+    // use either parameter value or (if param val is NaN) control value
+    boolean setSurface = false;
+    boolean setInterval = false;
+    if (surf == surf) {
+      cSurface = surf;
+      setSurface = true;
+    } else {
+      cSurface = values[0];
+    }
+    if (interv == interv) {
+      cInterval = interv;
+      setInterval = true;
+    } else {
+      cInterval = values[1];
+    }
+    if (min == min) {
+      cLo = min;
+      setInterval = true;
+    } else {
+      cLo = values[2];
+    }
+    if (max == max) {
+      cHi = max;
+      setInterval = true;
+    } else {
+      cHi = values[3];
+    }
+    if (ba == ba) {
+      cBase = ba;
+      setInterval = true;
+    } else {
+      cBase = values[4];
+    }
+
+    // create JPanels
+    JPanel top = new JPanel();
+    JPanel mid = new JPanel();
+    JPanel low = new JPanel();
+
+    // set up layouts
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    top.setLayout(new BoxLayout(top, BoxLayout.X_AXIS));
+    mid.setLayout(new BoxLayout(mid, BoxLayout.X_AXIS));
+    low.setLayout(new BoxLayout(low, BoxLayout.X_AXIS));
+
+    // create JComponents
+    Contours = new JCheckBox("contours", contourFlag);
+    Labels = new JCheckBox("labels", labelFlag);
+    Dashed = new JCheckBox("dashed lines below base", dashFlag);
+    JLabel intLabel = new JLabel("interval:");
+    Interval = new JTextField("---");
+    Fill  = new JCheckBox("fill", fillFlag);
+
+    // WLH 2 Dec 98
+    Dimension msize = Interval.getMaximumSize();
+    Dimension psize = Interval.getPreferredSize();
+    msize.height = psize.height;
+    Interval.setMaximumSize(msize);
+
+    JLabel baseLabel = new JLabel("base:");
+    Base = new JTextField("---");
+
+    // WLH 2 Dec 98
+    msize = Base.getMaximumSize();
+    psize = Base.getPreferredSize();
+    msize.height = psize.height;
+    Base.setMaximumSize(msize);
+
+    SurfaceLabel = new JLabel(name + " = ---");
+    Surface = new JSlider();
+    ContourRange = new ContourRangeSlider(smap, cLo, cHi, this, update);
+    if (!update) {
+      if (setSurface) {
+        control.setSurfaceValue(cSurface);
+      }
+      if (setInterval) {
+        control.setContourInterval(cInterval, cLo, cHi, cBase);
+      }
+      updateWidgetSurface();
+      updateWidgetRange();
+    }
+
+    // set label foregrounds
+    intLabel.setForeground(Color.black);
+    baseLabel.setForeground(Color.black);
+    SurfaceLabel.setForeground(Color.black);
+
+    // align JComponents
+    Dashed.setAlignmentX(JCheckBox.CENTER_ALIGNMENT);
+    SurfaceLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+
+    // add listeners
+    Interval.addActionListener(this);
+    Interval.setActionCommand("interval");
+    Base.addActionListener(this);
+    Base.setActionCommand("base");
+    Dimension d = new Dimension(Integer.MAX_VALUE,
+                                SurfaceLabel.getMaximumSize().height);
+    SurfaceLabel.setMaximumSize(d);
+    SurfaceLabel.setPreferredSize(d);
+    Surface.addChangeListener(this);
+    Labels.addItemListener(this);
+    Contours.addItemListener(this);
+    Fill.addItemListener(this);
+    Dashed.addItemListener(this);
+    control.addControlListener(this);
+    smap.addScalarMapListener(this);
+
+    // set up JComponents' tool tips
+    Contours.setToolTipText("Toggle contours");
+    Labels.setToolTipText("Toggle iso-contour labels (2-D only)");
+    Fill.setToolTipText("Solid filled contours (2-D only)");
+    Dashed.setToolTipText("Toggle dashed lines below base value (2-D only)");
+    String s = "Specify the iso-contouring interval (2-D only)";
+    intLabel.setToolTipText(s);
+    Interval.setToolTipText(s);
+    String t = "Specify the iso-contouring base value (2-D only)";
+    baseLabel.setToolTipText(t);
+    Base.setToolTipText(t);
+    String u = "Specify the iso-level value (3-D only)";
+    SurfaceLabel.setToolTipText(u);
+    Surface.setToolTipText(u);
+
+    // lay out JComponents
+    top.add(Contours);
+    top.add(Labels);
+    top.add(Fill);
+    mid.add(intLabel);
+    mid.add(Interval);
+    mid.add(Box.createRigidArea(new Dimension(10, 0)));
+    mid.add(baseLabel);
+    mid.add(Base);
+    low.add(Box.createRigidArea(new Dimension(10, 0)));
+    low.add(SurfaceLabel);
+    add(top);
+    add(Dashed);
+    add(mid);
+    add(low);
+    add(Surface);
+    add(ContourRange);
+  }
+
+  private double sliderScale;
+
+  void setSliderBounds(float min, float max) {
+    sliderScale = 1000/(max-min);
+    Surface.setMinimum((int) (sliderScale*min));
+    Surface.setMaximum((int) (sliderScale*max));
+  }
+
+  void setMinMax(float min, float max) throws VisADException,
+                                              RemoteException {
+    if (!Util.isApproximatelyEqual(cLo, min) ||
+        !Util.isApproximatelyEqual(cHi, max))
+    {
+      cLo = min;
+      cHi = max;
+      control.setContourLimits(cLo, cHi);
+    }
+  }
+
+  private void detectValues(double[] range) throws VisADException,
+                                              RemoteException {
+    boolean[] bval = new boolean[2];
+    float[] fval = new float[5];
+    control.getMainContours(bval, fval);
+
+    boolean setSurface = false;
+    if (fval[0] == fval[0] && !Util.isApproximatelyEqual(cSurface, fval[0])) {
+      cSurface = fval[0];
+      setSurface = true;
+    } else if (!Util.isApproximatelyEqual(cSurface, (float )range[0])) {
+      cSurface = (float )range[0];
+      setSurface = true;
+    }
+    if (setSurface) {
+      control.setSurfaceValue(cSurface);
+    }
+
+    if (!Util.isApproximatelyEqual(cInterval, fval[1]) ||
+        !Util.isApproximatelyEqual(cLo, fval[2]) ||
+        !Util.isApproximatelyEqual(cHi, fval[3]) ||
+        !Util.isApproximatelyEqual(cBase, fval[4]))
+    {
+      cInterval = fval[1];
+      cLo = fval[2];
+      cHi = fval[3];
+      cBase = fval[4];
+      control.setContourInterval(cInterval, cLo, cHi, cBase);
+    }
+
+    updateWidgetSurface();
+    updateWidgetRange();
+  }
+
+  synchronized private void updateWidgetSurface()
+          throws VisADException, RemoteException
+  {
+    if (cSurface == cSurface) {
+      Surface.setEnabled(true);
+
+      int val;
+      double tmp = sliderScale * cSurface;
+      if (tmp < 0) {
+        val = (int )(tmp - 0.5);
+      } else {
+        val = (int )(tmp + 0.5);
+      }
+      Surface.setValue(val);
+      SurfaceLabel.setText(name + " = " + PlotText.shortString(cSurface));
+    }
+    else {
+      Surface.setEnabled(false);
+      SurfaceLabel.setText(name + " = ---");
+    }
+  }
+
+  synchronized private void updateWidgetRange()
+          throws VisADException, RemoteException
+  {
+    if (cInterval == cInterval && cBase == cBase) {
+      Interval.setEnabled(true);
+      Interval.setText(PlotText.shortString(Math.abs(cInterval)));
+      Base.setEnabled(true);
+      Base.setText("" + PlotText.shortString(cBase));
+    }
+    else {
+      Interval.setEnabled(false);
+      Interval.setText("---");
+      Base.setEnabled(false);
+      Base.setText("---");
+    }
+  }
+
+  /** ActionListener method for JTextFields. */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("interval")) {
+      float interv = Float.NaN;
+      try {
+        interv = Float.valueOf(Interval.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        Interval.setText(PlotText.shortString(Math.abs(cInterval)));
+      }
+      if (interv == interv && interv >= 0.0f) {
+        if (cInterval < 0.0f) interv = -interv;
+        try {
+          control.setContourInterval(interv, cLo, cHi, cBase);
+          cInterval = interv;
+        }
+        catch (VisADException exc) {
+          Interval.setText(PlotText.shortString(Math.abs(cInterval)));
+        }
+        catch (RemoteException exc) {
+          Interval.setText(PlotText.shortString(Math.abs(cInterval)));
+        }
+      }
+      else Interval.setText(PlotText.shortString(Math.abs(cInterval)));
+    }
+    if (cmd.equals("base")) {
+      float ba = Float.NaN;
+      try {
+        ba = Float.valueOf(Base.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        Base.setText(PlotText.shortString(cBase));
+      }
+      if (ba == ba) {
+        try {
+          control.setContourInterval(cInterval, cLo, cHi, ba);
+          cBase = ba;
+        }
+        catch (VisADException exc) {
+          Base.setText(PlotText.shortString(cBase));
+        }
+        catch (RemoteException exc) {
+          Base.setText(PlotText.shortString(cBase));
+        }
+      }
+    }
+  }
+
+  /** ChangeListener method for JSlider. */
+  public void stateChanged(ChangeEvent e) {
+    float newVal = (float) (Surface.getValue()/sliderScale);
+    if (!Util.isApproximatelyEqual(cSurface, newVal)) {
+      cSurface = newVal;
+      SurfaceLabel.setText(name + " = " + PlotText.shortString(cSurface));
+
+      try {
+        control.setSurfaceValue(cSurface);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+  }
+
+  /** ItemListener method for JCheckBoxes. */
+  public void itemStateChanged(ItemEvent e) {
+    Object o = e.getItemSelectable();
+    boolean on = (e.getStateChange() == ItemEvent.SELECTED);
+    if (o == Labels) {
+      try {
+        control.enableLabels(on);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+    if (o == Contours) {
+      try {
+        control.enableContours(on);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+    if (o == Dashed) {
+      cInterval = -cInterval;
+      try {
+        control.setContourInterval(cInterval, cLo, cHi, cBase);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+    if (o == Fill) {
+      try {
+        control.setContourFill(on);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+  }
+
+  /** ControlListener method for ContourControl. */
+  public void controlChanged(ControlEvent e)
+        throws VisADException, RemoteException {
+    boolean[] bvals = new boolean[2];
+    float[] fvals = new float[5];
+    control.getMainContours(bvals, fvals);
+
+    if (Contours.isSelected() != bvals[0]) {
+      Contours.setSelected(bvals[0]);
+    }
+    if (Labels.isSelected() != bvals[1]) {
+      Labels.setSelected(bvals[1]);
+    }
+
+    float interval = fvals[1];
+    boolean dashedState = (interval < 0.0f);
+
+    if (Dashed.isSelected() != dashedState) {
+      Dashed.setSelected(dashedState);
+    }
+
+    float cInt;
+    if (dashedState != (cInterval < 0.0f)) {
+      cInt = -cInterval;
+    } else {
+      cInt = cInterval;
+    }
+    if (!Util.isApproximatelyEqual(interval, cInt)) {
+      if (interval < 0.0f) interval = -interval;
+      Interval.setText(PlotText.shortString(interval));
+      cInterval = fvals[1];
+    }
+    if (!Util.isApproximatelyEqual(fvals[4], cBase)) {
+      Base.setText(PlotText.shortString(fvals[4]));
+      cBase = fvals[4];
+    }
+
+    if (!Util.isApproximatelyEqual(fvals[0], cSurface)) {
+      cSurface = fvals[0];
+      updateWidgetSurface();
+    }
+    if (!Util.isApproximatelyEqual(fvals[2], cLo) ||
+        !Util.isApproximatelyEqual(fvals[3], cHi)) {
+      cLo = fvals[2];
+      cHi = fvals[3];
+      updateWidgetRange();
+      ContourRange.setValues(cLo, cHi);
+    }
+  }
+
+  private Dimension prefSize = null;
+
+  /** Make ContourWidget appear decent-sized */
+  public Dimension getPreferredSize() {
+    if (prefSize == null) {
+      prefSize = new Dimension(300, super.getPreferredSize().height);
+    }
+    return prefSize;
+  }
+
+  /** Set ContourWidget size */
+  public void setPreferredSize(Dimension dim) { prefSize = dim; }
+
+  /** Do-nothing method; <CODE>ContourRangeSlider</CODE> handles map data */
+  public void mapChanged(ScalarMapEvent e) { }
+
+  /** Deal with changes to the <CODE>ScalarMap</CODE>  control */
+  public void controlChanged(ScalarMapControlEvent evt)
+    throws RemoteException, VisADException
+  {
+    int id = evt.getId();
+    if (control != null && (id == ScalarMapEvent.CONTROL_REMOVED ||
+                            id == ScalarMapEvent.CONTROL_REPLACED))
+    {
+      evt.getControl().removeControlListener(this);
+    }
+
+    if (id == ScalarMapEvent.CONTROL_REPLACED ||
+        id == ScalarMapEvent.CONTROL_ADDED)
+    {
+      control = (ContourControl )(evt.getScalarMap().getControl());
+      controlChanged(new ControlEvent(control));
+      control.addControlListener(this);
+    }
+  }
+
+  /** Subclass of RangeSlider for selecting min and max values.<P> */
+  class ContourRangeSlider extends RangeSlider implements ScalarMapListener {
+
+    ContourWidget pappy;
+
+    ContourRangeSlider(ScalarMap smap, float min, float max, ContourWidget dad,
+                       boolean update) throws VisADException, RemoteException {
+      super(RangeSlider.nameOf(smap), min, max);
+      pappy = dad;
+
+      // set auto-scaling enabled (listen for new min and max)
+      if (update) smap.addScalarMapListener(this);
+    }
+
+    /** ScalarMapListener method used with delayed auto-scaling. */
+    public void mapChanged(ScalarMapEvent e) {
+      ScalarMap s = e.getScalarMap();
+      ContourControl cc = (ContourControl )s.getControl();
+      double[] range = s.getRange();
+
+      try {
+
+        minLimit = (float) range[0];
+        maxLimit = (float) range[1];
+
+        if (!Util.isApproximatelyEqual(cLo, minLimit) ||
+            !Util.isApproximatelyEqual(cHi, maxLimit))
+        {
+          cLo = minLimit;
+          cHi = maxLimit;
+        }
+
+        pappy.setSliderBounds(minLimit, maxLimit);
+
+        pappy.detectValues(range);
+
+        float[] lhb = new float[3];
+        boolean[] dashes = new boolean[1];
+        float[] lvls = cc.getLevels(lhb, dashes);
+        setValues(lhb[0], lhb[1]);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+
+    /** Do-nothing method; <CODE>ContourWidget</CODE> handles map control */
+    public void controlChanged(ScalarMapControlEvent evt) { }
+
+    /** tell parent when the value changes */
+    public void valuesUpdated() {
+      try {
+        pappy.setMinMax(minValue, maxValue);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+
+  }
+
+}
+
diff --git a/visad/util/CursorUtil.java b/visad/util/CursorUtil.java
new file mode 100644
index 0000000..886dd78
--- /dev/null
+++ b/visad/util/CursorUtil.java
@@ -0,0 +1,202 @@
+//
+// CursorUtil.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.rmi.RemoteException;
+import java.util.Vector;
+import visad.*;
+
+/**
+ * Utility methods for cursor-related functions, including converting between
+ * pixel, cursor and domain coordinates, and evaluating functions at a given
+ * cursor location.
+ */
+public class CursorUtil {
+
+  /** Converts the given cursor coordinates to domain coordinates. */
+  public static double[] cursorToDomain(DisplayImpl d,
+    RealType[] types, double[] cursor)
+  {
+    if (d == null) return null;
+    double[][] scale = getScaleValues(d, types);
+    double[] domain = new double[3];
+    for (int i=0; i<3; i++) {
+      domain[i] = scale[i] == null ? 0 :
+        (cursor[i] - scale[i][1]) / scale[i][0];
+    }
+    return domain;
+  }
+
+  /** Converts the given domain coordinates to cursor coordinates. */
+  public static double[] domainToCursor(DisplayImpl d,
+    RealType[] types, double[] domain)
+  {
+    if (d == null) return null;
+    double[][] scale = getScaleValues(d, types);
+    double[] cursor = new double[3];
+    for (int i=0; i<3; i++) {
+      cursor[i] = scale[i] == null ? 0 :
+        scale[i][0] * domain[i] + scale[i][1];
+    }
+    return cursor;
+  }
+
+  /** Converts the given cursor coordinates to domain coordinates. */
+  public static double[] cursorToDomain(DisplayImpl d, double[] cursor) {
+    return cursorToDomain(d, null, cursor);
+  }
+
+  /** Converts the given domain coordinates to cursor coordinates. */
+  public static double[] domainToCursor(DisplayImpl d, double[] domain) {
+    return domainToCursor(d, null, domain);
+  }
+
+  /** Converts the given pixel coordinates to cursor coordinates. */
+  public static double[] pixelToCursor(DisplayImpl d, int x, int y) {
+    if (d == null) return null;
+    MouseBehavior mb = d.getDisplayRenderer().getMouseBehavior();
+    VisADRay ray = mb.findRay(x, y);
+    return ray.position;
+  }
+
+  /** Converts the given cursor coordinates to pixel coordinates. */
+  public static int[] cursorToPixel(DisplayImpl d, double[] cursor) {
+    if (d == null) return null;
+    MouseBehavior mb = d.getDisplayRenderer().getMouseBehavior();
+    return mb.getScreenCoords(cursor);
+  }
+
+  /** Converts the given pixel coordinates to domain coordinates. */
+  public static double[] pixelToDomain(DisplayImpl d, int x, int y) {
+    return cursorToDomain(d, pixelToCursor(d, x, y));
+  }
+
+  /** Converts the given domain coordinates to pixel coordinates. */
+  public static int[] domainToPixel(DisplayImpl d, double[] domain) {
+    return cursorToPixel(d, domainToCursor(d, domain));
+  }
+
+  /** Evaluates the given function at the specified domain coordinates. */
+  public static double[] evaluate(FunctionImpl data, double[] domain)
+    throws VisADException, RemoteException
+  {
+    // build data objects
+    FunctionType functionType = (FunctionType) data.getType();
+    RealTupleType domainType = functionType.getDomain();
+    RealType[] domainTypes = domainType.getRealComponents();
+    int len = domainTypes.length < domain.length ?
+      domainTypes.length : domain.length;
+    Real[] v = new Real[len];
+    for (int i=0; i<len; i++) v[i] = new Real(domainTypes[i], domain[i]);
+    RealTuple tuple = new RealTuple(v);
+
+    // evaluate function
+    Data result = data.evaluate(tuple, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS);
+
+    // extract range values
+    double[] range = null;
+    if (result instanceof Real) {
+      Real r = (Real) result;
+      range = new double[] {r.getValue()};
+    }
+    else if (result instanceof RealTuple) {
+      RealTuple rt = (RealTuple) result;
+      int dim = rt.getDimension();
+      range = new double[dim];
+      for (int j=0; j<dim; j++) {
+        Real r = (Real) rt.getComponent(j);
+        range[j] = r.getValue();
+      }
+    }
+    return range;
+  }
+
+  /**
+   * Gets scale values (multiplier and offset) for the X, Y and Z maps
+   * corresponding to the given RealTypes (or the first ScalarMaps to
+   * X, Y and Z if types is null). If no mapping to a spatial axis is
+   * found, that component of the array will be null.
+   *
+   * @return Scale array of size [3][2], with the first dimension
+   * corresponding to X, Y or Z, and the second giving multiplier and offset.
+   * For example, cursor_x = scale[0][0] * domain_x + scale[0][1].
+   */
+  public static double[][] getScaleValues(DisplayImpl d, RealType[] types) {
+    // locate x, y and z mappings
+    ScalarMap[] maps = getXYZMaps(d, types);
+    ScalarMap mapX = maps[0], mapY = maps[1], mapZ = maps[2];
+
+    // get scale values
+    double[][] scale = new double[3][];
+    double[] dummy = new double[2];
+    if (mapX == null) scale[0] = null;
+    else {
+      scale[0] = new double[2];
+      mapX.getScale(scale[0], dummy, dummy);
+    }
+    if (mapY == null) scale[1] = null;
+    else {
+      scale[1] = new double[2];
+      mapY.getScale(scale[1], dummy, dummy);
+    }
+    if (mapZ == null) scale[2] = null;
+    else {
+      scale[2] = new double[2];
+      mapZ.getScale(scale[2], dummy, dummy);
+    }
+    return scale;
+  }
+
+  /**
+   * Gets X, Y and Z maps for the given display, corresponding to the specified
+   * RealTypes, or the first ScalarMaps to X, Y and Z if types is null.
+   * @return RealType array of size [3], for X, Y and Z map, respectively.
+   */
+  public static ScalarMap[] getXYZMaps(DisplayImpl d, RealType[] types) {
+    Vector maps = d.getMapVector();
+    int numMaps = maps.size();
+    ScalarMap mapX = null, mapY = null, mapZ = null;
+    for (int i=0; i<numMaps; i++) {
+      if (mapX != null && mapY != null && mapZ != null) break;
+      ScalarMap map = (ScalarMap) maps.elementAt(i);
+      if (types == null) {
+        DisplayRealType drt = map.getDisplayScalar();
+        if (drt.equals(Display.XAxis) && mapX == null) mapX = map;
+        else if (drt.equals(Display.YAxis) && mapY == null) mapY = map;
+        else if (drt.equals(Display.ZAxis) && mapZ == null) mapZ = map;
+      }
+      else {
+        ScalarType st = map.getScalar();
+        if (st.equals(types[0])) mapX = map;
+        if (st.equals(types[1])) mapY = map;
+        if (st.equals(types[2])) mapZ = map;
+      }
+    }
+    return new ScalarMap[] {mapX, mapY, mapZ};
+  }
+
+}
diff --git a/visad/util/DataConverter.java b/visad/util/DataConverter.java
new file mode 100644
index 0000000..d8bab04
--- /dev/null
+++ b/visad/util/DataConverter.java
@@ -0,0 +1,308 @@
+//
+// DataConverter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import visad.*;
+import visad.data.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.Vector;
+import javax.swing.*;
+
+/**
+ * DataConverter provides a simple GUI for converting data
+ * between VisAD-supported formats, using the data form of choice.
+ */
+public class DataConverter {
+
+  // -- CONSTANTS --
+
+  protected static final ExtensionFileFilter FILTER =
+    new ExtensionFileFilter("class", "Java classes");
+
+
+  // -- FIELDS --
+
+  protected Vector forms;
+  protected DataImpl data;
+  protected File lastDir;
+
+  protected JFrame frame;
+  protected JTextArea area;
+  protected JScrollPane scroll;
+  protected JButton read, write;
+
+
+  // -- CONSTRUCTORS --
+
+  public DataConverter() { this(new File("../data"), "visad"); }
+
+  public DataConverter(File dir, String prefix) {
+    forms = new Vector();
+    findForms(dir, prefix);
+    doGUI();
+  }
+
+
+  // -- HELPER METHODS --
+
+  protected void doGUI() {
+    frame = new JFrame("VisAD Data Converter");
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) { System.exit(0); }
+    });
+    JPanel pane = new JPanel();
+    pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
+    frame.setContentPane(pane);
+
+    area = new JTextArea("VisAD Data Converter\n\n");
+    area.setEditable(false);
+    scroll = new JScrollPane(area);
+    pane.add(scroll);
+
+    final JPopupMenu readMenu = new JPopupMenu();
+    read = new JButton("Import");
+    read.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        popup(read, readMenu);
+      }
+    });
+
+    final JPopupMenu writeMenu = new JPopupMenu();
+    write = new JButton("Export");
+    write.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        popup(write, writeMenu);
+      }
+    });
+
+    for (int i=0; i<forms.size(); i++) {
+      final FormNode form = (FormNode) forms.elementAt(i);
+      StringBuffer label = new StringBuffer(form.getClass().getName());
+      String[] suffixes = getSuffixes(form);
+      if (suffixes != null) {
+        label.append(" (*.");
+        label.append(suffixes[0]);
+        for (int j=1; j<suffixes.length; j++) {
+          if (j == 2 && j <= suffixes.length - 3) {
+            // too many suffixes; abbreviate them
+            label.append(", ...");
+            j = suffixes.length - 3;
+            continue;
+          }
+          label.append(", *.");
+          label.append(suffixes[j]);
+        }
+        label.append(")");
+      }
+      String s = label.toString();
+
+      JMenuItem readItem = new JMenuItem(s);
+      readItem.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) { importData(form); }
+      });
+      readMenu.add(readItem);
+
+      JMenuItem writeItem = new JMenuItem(s);
+      writeItem.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) { exportData(form); }
+      });
+      writeMenu.add(writeItem);
+    }
+
+    JPanel p = new JPanel();
+    p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
+    p.add(read);
+    p.add(write);
+    pane.add(p);
+
+    frame.setSize(500, 300);
+    Util.centerWindow(frame);
+    frame.show();
+  }
+
+  protected void popup(JButton button, JPopupMenu menu) {
+    Dimension b = read.getSize();
+    Point loc = button.getLocationOnScreen();
+    Dimension s = Toolkit.getDefaultToolkit().getScreenSize();
+    Dimension m = menu.getPreferredSize();
+    int xpos = s.width - loc.x - m.width - b.width - 2;
+    int ypos = s.height - loc.y - m.height - 30;
+    if (xpos > 0) xpos = 0;
+    if (ypos > 0) ypos = 0;
+    menu.show(button, b.width + xpos, ypos);
+  }
+
+  protected void importData(FormNode form) {
+    final FormNode loader = form;
+    final File file = doDialog(form, false);
+    if (file == null) return;
+    Thread t = new Thread(new Runnable() {
+      public void run() {
+        setBusy(true);
+        print("Importing " + file.getPath() + ": ");
+        try {
+          data = loader.open(file.getPath());
+          if (data == null) println("Data is null");
+          else {
+            println("OK");
+            println(data.getType().prettyString());
+          }
+        }
+        catch (Throwable tt) {
+          println("Error");
+          println(tt);
+        }
+        setBusy(false);
+      }
+    });
+    t.start();
+  }
+
+  protected void exportData(FormNode form) {
+    final FormNode saver = form;
+    final File file = doDialog(form, true);
+    if (file == null) return;
+    Thread t = new Thread(new Runnable() {
+      public void run() {
+        setBusy(true);
+        print("Exporting " + file.getPath() + ": ");
+        try {
+          saver.save(file.getPath(), data, true);
+          println("OK");
+        }
+        catch (Throwable tt) {
+          println("Error");
+          println(tt);
+        }
+        setBusy(false);
+      }
+    });
+    t.start();
+  }
+
+  protected File doDialog(FormNode form, boolean save) {
+    JFileChooser chooser = lastDir == null ?
+      new JFileChooser() : new JFileChooser(lastDir);
+    String[] suffixes = getSuffixes(form);
+    if (suffixes != null) {
+      chooser.setFileFilter(new ExtensionFileFilter(
+        suffixes, form.getClass().getName()));
+    }
+    int retVal = save ? chooser.showSaveDialog(frame) :
+      chooser.showOpenDialog(frame);
+    lastDir = chooser.getCurrentDirectory();
+    return retVal == JFileChooser.APPROVE_OPTION ?
+      chooser.getSelectedFile() : null;
+  }
+
+  protected String[] getSuffixes(FormNode form) {
+    if (!(form instanceof FormFileInformer)) return null;
+    FormFileInformer ffi = (FormFileInformer) form;
+    String[] suffixes = ffi.getDefaultSuffixes();
+    return suffixes == null || suffixes.length == 0 ||
+      suffixes[0].trim().equals("") ? null : suffixes;
+  }
+
+  protected void setBusy(boolean busy) {
+    read.setEnabled(!busy);
+    write.setEnabled(!busy);
+    frame.setCursor(busy ? Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR) :
+      Cursor.getDefaultCursor());
+  }
+
+  protected void print(String text) {
+    area.append(text);
+    JViewport view = scroll.getViewport();
+    Dimension size = view.getViewSize();
+    view.setViewPosition(new Point(0, size.height));
+  }
+
+  protected void println(String text) { print(text + "\n\n"); }
+  protected void println() { print("\n\n"); }
+  protected void println(Throwable t) {
+    StringWriter sw = new StringWriter();
+    PrintWriter pw = new PrintWriter(sw);
+    t.printStackTrace(pw);
+    pw.close();
+    print(sw.toString() + "\n\n");
+  }
+
+  protected void findForms(File f, String prefix) {
+    if (f.isDirectory()) {
+      File[] list = f.listFiles(FILTER);
+      String name = f.getName();
+      String pre = "".equals(prefix) ? name : prefix + "." + name;
+      for (int i=0; i<list.length; i++) findForms(list[i], pre);
+    }
+    else {
+      try {
+        String name = f.getName();
+        String qual = "".equals(prefix) ? name : prefix + "." + name;
+        qual = qual.substring(0, qual.length() - 6);
+        Class c = Class.forName(qual);
+        boolean isForm = false;
+        for (Class q=c; q!=Object.class && q!=null; q=q.getSuperclass()) {
+          if (q == FormNode.class) {
+            isForm = true;
+            break;
+          }
+        }
+        if (isForm) {
+          FormNode form = null;
+          try { form = (FormNode) c.newInstance(); }
+          catch (InstantiationException exc) { }
+          catch (IllegalAccessException exc) { }
+          catch (IllegalArgumentException exc) { }
+          try {
+            Constructor con = c.getConstructor(new Class[] {String.class});
+            form = (FormNode) con.newInstance(new Object[] {name});
+          }
+          catch (NoSuchMethodException exc) { }
+          catch (SecurityException exc) { }
+          catch (InstantiationException exc) { }
+          catch (IllegalAccessException exc) { }
+          catch (IllegalArgumentException exc) { }
+          catch (InvocationTargetException exc) { }
+          if (form != null) forms.add(form);
+        }
+      }
+      catch (ClassNotFoundException exc) { }
+      catch (NoClassDefFoundError err) { }
+      catch (UnsatisfiedLinkError err) { }
+    }
+  }
+
+
+  // -- MAIN METHOD --
+
+  /** Run 'java visad.util.DataConverter' to convert some data. */
+  public static void main(String[] args) { new DataConverter(); }
+
+}
diff --git a/visad/util/DataUtility.java b/visad/util/DataUtility.java
new file mode 100644
index 0000000..cb64c35
--- /dev/null
+++ b/visad/util/DataUtility.java
@@ -0,0 +1,1693 @@
+//
+// DataUtility.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+// General Java
+import java.awt.Image;
+import java.awt.Toolkit;
+import java.awt.image.ColorModel;
+import java.awt.image.DirectColorModel;
+import java.awt.image.MemoryImageSource;
+import java.awt.image.PixelGrabber;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.TreeSet;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Comparator;
+import java.util.Vector;
+import java.util.StringTokenizer;
+
+// RMI classes
+import java.rmi.RemoteException;
+
+import visad.java2d.DisplayImplJ2D;
+import visad.java3d.DisplayImplJ3D;
+import visad.java3d.TwoDDisplayRendererJ3D;
+
+// GUI handling
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.*;
+
+// VisAD packages
+import visad.*;
+
+/** A general utility class for VisAD Data objects */
+public class DataUtility {
+
+  private static boolean init = false;
+  private static FunctionType simpleImageType;
+  private static RealType radiance;
+  private static RealTupleType imageDomain;
+  private static RealType line;
+  private static RealType element;
+
+  private int num_lines, num_elements;
+
+  private static synchronized void makeTypes()
+          throws VisADException {
+    if (!init) {
+      init = true;
+      simpleImageType = (FunctionType)
+        MathType.stringToType("((ImageElement, ImageLine) -> ImageRadiance)");
+      imageDomain = simpleImageType.getDomain();
+      line = (RealType) imageDomain.getComponent(1);
+      element = (RealType) imageDomain.getComponent(0);
+      MathType range = simpleImageType.getRange();
+      if (range instanceof RealType) {
+        radiance = (RealType) range;
+      }
+      else {
+        radiance = (RealType) ((RealTupleType) range).getComponent(0);
+      }
+    }
+  }
+
+  /** return a FlatField for a simple image from
+      values[nlines][nelements] */
+  public static FlatField makeImage(float[][] values)
+         throws VisADException, RemoteException {
+    if (values == null) return null;
+    int nlines = values.length;
+    int nelements = 0;
+    for (int l=0; l<nlines; l++) {
+      if (values[l] != null) {
+        if (values[l].length > nelements) nelements = values[l].length;
+      }
+    }
+    if (!init) makeTypes();
+    FlatField image = new FlatField(simpleImageType,
+                new Integer2DSet(imageDomain, nelements, nlines));
+    setPixels(image, values);
+    return image;
+  }
+
+  /** set pixel values in a simple image,
+      indexed as values[line][element] */
+  public static void setPixels(FlatField image, float[][] values)
+         throws VisADException, RemoteException {
+    if (values == null) return;
+    Integer2DSet set = (Integer2DSet) image.getDomainSet();
+    int num_elements = set.getLength(0);
+    int num_lines = set.getLength(1);
+    float[][] vals = new float[1][num_lines * num_elements];
+    for (int i=0; i<num_lines * num_elements; i++) {
+      vals[0][i] = Float.NaN;
+    }
+    int nl = values.length;
+    if (num_lines < nl) nl = num_lines;
+    int base = 0;
+    for (int l=0; l<nl; l++) {
+      if (values[l] != null) {
+        int ne = values[l].length;
+        if (num_elements < ne) ne = num_elements;
+        for (int e=0; e<ne; e++) {
+          vals[0][base + e] = values[l][e];
+        }
+      }
+      base += num_elements;
+    }
+    image.setSamples(vals, false); // no need to copy
+  }
+
+  public static float[][] getPixels(FlatField image)
+         throws VisADException, RemoteException {
+    Integer2DSet set = (Integer2DSet) image.getDomainSet();
+    int num_elements = set.getLength(0);
+    int num_lines = set.getLength(1);
+    float[][] values = new float[num_lines][num_elements];
+    double[][] vals = image.getValues();
+    int base = 0;
+    for (int l=0; l<num_lines; l++) {
+      for (int e=0; e<num_elements; e++) {
+        values[l][e] = (float) vals[0][base + e];
+      }
+      base += num_elements;
+    }
+    return values;
+  }
+
+  /** 
+   * Create a VisAD Data object from the given Image 
+   * @param  image   image to use
+   * @return a FlatField representation of the image
+   */
+  public static FlatField makeField(Image image)
+    throws IOException, VisADException
+  {
+    return makeField(image, false);
+  }
+
+  /** 
+   * Create a VisAD Data object from the given Image 
+   * @param  image   image to use
+   * @param  withAlpha   include Alpha in the field if the image ColorModel
+   *                     supports it and the image is not opaque.
+   * @return a FlatField representation of the image
+   */
+  public static FlatField makeField(Image image, boolean withAlpha)
+    throws IOException, VisADException
+  {
+    if (image == null) {
+      throw new VisADException("image cannot be null");
+    }
+    ImageHelper ih = new ImageHelper();
+
+    // determine image height and width
+    int width = -1;
+    int height = -1;
+    do {
+      if (width < 0) width = image.getWidth(ih);
+      if (height < 0) height = image.getHeight(ih);
+      if (ih.badImage || (width >= 0 && height >= 0)) break;
+      try { Thread.sleep(100); } catch (InterruptedException e) { }
+    }
+    while (true);
+    if (ih.badImage) throw new IOException("Not an image");
+
+    // extract image pixels
+    int numPixels = width * height;
+    int[] words = new int[numPixels];
+
+    PixelGrabber grabber = new PixelGrabber(
+      image.getSource(), 0, 0, width, height, words, 0, width);
+
+    try { grabber.grabPixels(); }
+    catch (InterruptedException e) { }
+
+    ColorModel cm = grabber.getColorModel();
+    boolean doAlpha = cm.hasAlpha() && withAlpha;
+    //System.out.println("do alpha: " + doAlpha);
+
+    float[] red_pix = new float[numPixels];
+    float[] green_pix = new float[numPixels];
+    float[] blue_pix = new float[numPixels];
+    float[] alpha_pix = null;
+
+    for (int i=0; i<numPixels; i++) {
+      red_pix[i] = cm.getRed(words[i]);
+      green_pix[i] = cm.getGreen(words[i]);
+      blue_pix[i] = cm.getBlue(words[i]);
+    }
+    boolean opaque = true;
+    if (doAlpha) {
+      alpha_pix = new float[numPixels];
+      for (int i=0; i<numPixels; i++) {
+        alpha_pix[i] = cm.getAlpha(words[i]);
+        if (alpha_pix[i] != 255.0f) opaque = false;
+      }
+    }
+
+    if (opaque && doAlpha) {  // if opaque, don't include alpha
+      doAlpha = false;
+      alpha_pix = null;
+    }
+    //System.out.println("opaque = " + opaque);
+
+    // build FlatField
+    RealType line = RealType.getRealType("ImageLine");
+    RealType element = RealType.getRealType("ImageElement");
+    RealType c_red = RealType.getRealType("Red");
+    RealType c_green = RealType.getRealType("Green");
+    RealType c_blue = RealType.getRealType("Blue");
+    RealType c_alpha = RealType.getRealType("Alpha");
+
+    RealType[] c_all = 
+       (doAlpha) ? new RealType[] {c_red, c_green, c_blue, c_alpha}
+                  : new RealType[] {c_red, c_green, c_blue};
+    RealTupleType radiance = new RealTupleType(c_all);
+
+    RealType[] domain_components = {element, line};
+    RealTupleType image_domain = new RealTupleType(domain_components);
+    Linear2DSet domain_set = new Linear2DSet(image_domain, 0.0,
+      (float) (width - 1.0), width, (float) (height - 1.0), 0.0, height);
+    FunctionType image_type = new FunctionType(image_domain, radiance);
+
+    FlatField field = new FlatField(image_type, domain_set);
+
+    float[][] samples = new float[doAlpha?4:3][];
+    samples[0] = red_pix;
+    samples[1] = green_pix;
+    samples[2] = blue_pix;
+    if (doAlpha) samples[3] = alpha_pix;
+    try { field.setSamples(samples, false); }
+    catch (RemoteException e) {
+      throw new VisADException("Couldn't finish image initialization");
+    }
+
+    return field;
+  }
+
+  /**
+   * Converts a flat field of the form <tt>((x, y) -> (r, g, b))</tt>
+   * to an AWT Image. If reverse flag is set, image will be upside-down.
+   */
+  public static Image extractImage(FlatField field, boolean reverse) {
+    try {
+      GriddedSet set = (GriddedSet) field.getDomainSet();
+      int[] wh = set.getLengths();
+      int w = wh[0];
+      int h = wh[1];
+      double[][] samples = field.getValues();
+      int[] pixels = new int[samples[0].length];
+      if (samples.length == 3) {
+        int len = samples[0].length;
+        for (int i=0; i<len; i++) {
+          int r = (int) samples[0][i] & 0x000000ff;
+          int g = (int) samples[1][i] & 0x000000ff;
+          int b = (int) samples[2][i] & 0x000000ff;
+          int index = reverse ? len - i - 1 : i;
+          pixels[index] = r << 16 | g << 8 | b;
+        }
+      }
+      else {
+        int len = samples[0].length;
+        for (int i=0; i<len; i++) {
+          int v = (int) samples[0][i] & 0x000000ff;
+          int index = reverse ? len - i - 1 : i;
+          pixels[index] = v << 16 | v << 8 | v;
+        }
+      }
+      MemoryImageSource source = new MemoryImageSource(w, h,
+        new DirectColorModel(24, 0xff0000, 0xff00, 0xff), pixels, 0, w);
+      source.setFullBufferUpdates(true);
+      return Toolkit.getDefaultToolkit().createImage(source);
+    }
+    catch (VisADException exc) {
+      return null;
+    }
+  }
+
+  public static FlatField[] getImageFields(Data data) {
+    FlatField[] fields = null;
+    String pcImageType = "((e, l) -> v)";
+    String rgbImageType = "((e, l) -> (r, g, b))";
+    String pcTimeType = "(t -> " + pcImageType + ")";
+    String rgbTimeType = "(t -> " + rgbImageType + ")";
+    try {
+      MathType type = data.getType();
+      if (type.equalsExceptName(MathType.stringToType(pcTimeType)) ||
+        type.equalsExceptName(MathType.stringToType(rgbTimeType)))
+      {
+        FieldImpl fi = (FieldImpl) data;
+        int len = fi.getLength();
+        fields = new FlatField[len];
+        for (int i=0; i<len; i++) fields[i] = (FlatField) fi.getSample(i);
+      }
+      else if (type.equalsExceptName(MathType.stringToType(pcImageType)) ||
+        type.equalsExceptName(MathType.stringToType(rgbImageType)))
+      {
+        fields = new FlatField[1];
+        fields[0] = (FlatField) data;
+      }
+    }
+    catch (VisADException exc) { }
+    catch (RemoteException exc) { }
+    return fields;
+  }
+
+  public static DisplayImpl makeSimpleDisplay(DataImpl data)
+         throws VisADException, RemoteException {
+    boolean three_d = true;
+    DisplayImpl display = null;
+    try {
+      display = new DisplayImplJ3D("simple data display");
+    }
+    catch (UnsatisfiedLinkError e) {
+      display = new DisplayImplJ2D("simple data display");
+      three_d = false;
+    }
+    MathType type = data.getType();
+    ScalarMap[] maps = type.guessMaps(three_d);
+    if (maps == null) {
+      display.stop();
+      return null;
+    }
+    if (three_d) {
+      boolean only_2d = true;
+      for (int i=0; i<maps.length; i++) {
+        DisplayRealType dtype = maps[i].getDisplayScalar();
+        if (Display.ZAxis.equals(maps[i]) ||
+            Display.Latitude.equals(maps[i])) {
+          only_2d = false;
+          break;
+        }
+      }
+      if (only_2d) {
+        display.destroy();
+        display = new DisplayImplJ3D("simple data display",
+                                     new TwoDDisplayRendererJ3D());
+      }
+    }
+    for (int i=0; i<maps.length; i++) {
+      display.addMap(maps[i]);
+    }
+
+    DataReferenceImpl ref = new DataReferenceImpl("simple data display");
+    ref.setData(data);
+    display.addReference(ref);
+    return display;
+  }
+
+  public static void main(String[] argv)
+         throws VisADException, RemoteException {
+    float[][] pixels = new float[64][64];
+    for (int i=0; i<64; i++) {
+      for (int j=0; j<64; j++) {
+        pixels[i][j] = i * (i - 32) * (i - 64) *
+                       j * (j - 32) * (j - 64) + 100000;
+      }
+    }
+
+    FlatField image = DataUtility.makeImage(pixels);
+    DisplayImpl display = DataUtility.makeSimpleDisplay(image);
+
+    JFrame jframe = new JFrame("SimplImage.main");
+    jframe.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {System.exit(0);}
+    });
+
+    jframe.setContentPane((JPanel) display.getComponent());
+    jframe.pack();
+    jframe.setVisible(true);
+  }
+
+  /**
+   * Ensures that a MathType is a RealTupleType.  Converts if necessary.
+   * @param type		The math type to be "converted" to a
+   *				RealTupleType.  It shall be either a RealType,
+   *				a RealTupleType, or a SetType.
+   * @return                    The RealTupleType version of <code>type</code>.
+   *                            If <code>type</code> is a RealTupleType, then
+   *                            it is returned; otherwise, if <code>type</code>
+   *                            is a RealType, then a RealTupleType
+   *                            containing <code>type</code> as the
+   *                            only component is returned; otherwise,
+   *                            if <code>type</code> is a SetType, then
+   *                            <code>((SetType)type).getDomain()</code> is
+   *                            returned.
+   * @throws TypeException	<code>type</code> is the wrong type: it can't
+   *				be converted into a RealTupleType.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   */
+  public static RealTupleType
+  ensureRealTupleType(MathType type)
+    throws TypeException, VisADException
+  {
+    RealTupleType	result;
+    if (type instanceof RealTupleType)
+      result = (RealTupleType)type;
+    else if (type instanceof RealType)
+      result = new RealTupleType((RealType)type);
+    else if (type instanceof SetType)
+      result = ((SetType)type).getDomain();
+    else
+      throw new TypeException(
+	DataUtility.class.getName() +
+	".ensureRealTupleType(MathType): Can't convert MathType \"" +
+	type + "\" into a RealTupleType");
+    return result;
+  }
+
+  /**
+   * Ensures that a MathType is a TupleType.  Converts if necessary.
+   * @param type		The math type to be "converted" to a TupleType.
+   * @return                    The TupleType version of <code>type</code>.
+   *                            If <code>type</code> is a TupleType,
+   *                            then it is returned; otherwise, if
+   *                            <code>type</code> is a SetType, then
+   *                            <code>((SetType)type).getDomain()</code> is
+   *                            returned; otherwise, a TupleType containing
+   *                            <code>type</code> as the only component is
+   *                            returned (if <code>type</code> is a RealType,
+   *                            then the returned TupleType is a RealTupleType);
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   */
+  public static TupleType
+  ensureTupleType(MathType type)
+    throws VisADException
+  {
+    return
+      type instanceof TupleType
+	? (TupleType)type
+	: type instanceof SetType
+	  ? ((SetType)type).getDomain()		// actually a RealTupleType
+	  : type instanceof RealType
+	    ? new RealTupleType((RealType)type)
+	    : new TupleType(new MathType[] {type});
+  }
+
+  /**
+   * Ensures that a Data is a Tuple.  Creates a Tuple if necessary.
+   * @param datum		The math type to be "converted" to a Tuple.
+   * @return                    The Tuple version of <code>datum</code>.  If
+   *                            <code>datum</code> is a Tuple, then it is
+   *                            returned; otherwise, if <code>datum</code> is
+   *                            a Real, then a RealTuple containing <code>
+   *                            datum</code> as the only component is returned;
+   *                            otherwise, a Tuple containing <code>datum</code>
+   *                            as the only component is returned.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static TupleIface
+  ensureTuple(Data datum)
+    throws VisADException, RemoteException
+  {
+    return
+      datum instanceof TupleIface
+	? (TupleIface)datum
+	: datum instanceof Real
+	    ? new RealTuple(new Real[] {(Real)datum})
+	    : new Tuple(new Data[] {datum});
+  }
+
+  /**
+   * Gets the MathType of the domain of a Set.
+   * @param set			A set.
+   * @return			The MathType of the domain of the Set.
+   */
+  public static RealTupleType
+  getDomainType(Set set)
+  {
+    return ((SetType)set.getType()).getDomain();
+  }
+
+  /**
+   * Gets the MathType of the domain of a Function.
+   * @param function		A function.
+   * @return			The MathType of the domain of the function.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static RealTupleType
+  getDomainType(Function function)
+    throws VisADException, RemoteException
+  {
+    return ((FunctionType)function.getType()).getDomain();
+  }
+
+  /**
+   * Gets the MathType of the range of a Function.
+   * @param function		A function.
+   * @return			The MathType of the range of the function.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static MathType
+  getRangeType(Function function)
+    throws VisADException, RemoteException
+  {
+    return ((FunctionType)function.getType()).getRange();
+  }
+
+  /**
+   * Gets the TupleType of the range of a Function.
+   * @param function		A function.
+   * @return			The TupleType of the range of the function.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static TupleType
+  getRangeTupleType(Function function)
+    throws VisADException, RemoteException
+  {
+    return ensureTupleType(getRangeType(function));
+  }
+
+  /**
+   * Gets the MathType of the flat-range of a Function.
+   * @param function		A function.
+   * @return			The MathType of the flat-range of the function.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static RealTupleType
+  getFlatRangeType(Function function)
+    throws VisADException, RemoteException
+  {
+    return ((FunctionType)function.getType()).getFlatRange();
+  }
+
+  /**
+   * Gets the number of components in the range of a Function.  NB: This differs
+   * from visad.FlatField.getRangeDimension() in that it returns the number of
+   * components in the actual range rather than the number of components in the
+   * flat range.
+   * @param function		A function.
+   * @return			The number of components in the range of the
+   *				function.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static int
+  getRangeDimension(Function function)
+    throws VisADException, RemoteException
+  {
+    return getRangeTupleType(function).getDimension();
+  }
+
+  /**
+   * Gets the index of a component in a TupleType.  If the TupleType
+   * contains multiple instances of the component, then it is unspecified
+   * which component index is returned.  This method first looks for an
+   * exact match via the <code>equals(Object)</code> method.  If none is
+   * found, then this method looks for an approximate match based on the
+   * <code>equalsExceptNameButUnits(MathType)</code> method.
+   * @param tupleType		The type of the tuple.
+   * @param componentType	The MathType of the component.
+   * @return                    The index of the component in the tuple
+   *                            or -1 if the component is not in the tuple.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static int
+  getComponentIndex(TupleType tupleType, MathType componentType)
+    throws VisADException, RemoteException
+  {
+    /*
+     * Return first exact match if found.
+     */
+    for (int i = tupleType.getDimension(); --i >= 0; )
+      if (componentType.equals(tupleType.getComponent(i)))
+	return i;
+    /*
+     * Return the first convertible-units match if found.
+     */
+    for (int i = tupleType.getDimension(); --i >= 0; )
+      if (componentType.equalsExceptNameButUnits(tupleType.getComponent(i)))
+	return i;
+    return -1;
+  }
+
+  /**
+   * Gets the index of a component in a Set.  If the set contains multiple
+   * instances of the component, then it is unspecified which component index is
+   * returned.
+   * @param set			The Set.
+   * @param componentType	The MathType of the component.
+   * @return			The index of the component in the set or -1 if
+   *				the component is not in the set.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static int
+  getComponentIndex(Set set, MathType componentType)
+    throws VisADException, RemoteException
+  {
+    return
+      getComponentIndex(((SetType)set.getType()).getDomain(), componentType);
+  }
+
+  /**
+   * Gets the index of a component in the range of a Function.  If the range
+   * contains multiple instances of the component, then it is unspecified
+   * which component index is returned.
+   * @param function		The Function.
+   * @param componentType	The MathType of the component.
+   * @return                    The index of the component in the range of the
+   *                            field or -1 if the component is not in the range
+   *                            of the field (NB: this is not the flat-range
+   *                            index).
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static int
+  getComponentIndex(Function function, MathType componentType)
+    throws VisADException, RemoteException
+  {
+    return getComponentIndex(getRangeTupleType(function), componentType);
+  }
+
+  /**
+   * Ensures that the range of a Field is a given type.  Extracts from
+   * the input field only if necessary.
+   * @param field		The input field.
+   * @param newRangeType	The desired type of range for the resulting
+   *				field.
+   * @return			A field with the desired range type.  The range
+   *				data will be missing, however, if <em>all</em>
+   *				of it couldn't be extracted from the input
+   *				Field (i.e.
+   *				RETURN_VALUE<code>.isMissing()</code> will be
+   *				true.
+   * @throws UnimplementedException
+   *				The desired range type is a TupleType and not
+   *				a RealTupleType.
+   * @throws TypeException	The new range type cannot be the range of a
+   *				field.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static Field
+  ensureRange(Field field, MathType newRangeType)
+    throws UnimplementedException, TypeException, VisADException,
+      RemoteException
+  {
+    Field	result;
+    if (newRangeType.equals(getRangeType(field)))
+    {
+      result = field;
+    }
+    else if (newRangeType instanceof RealType)
+    {
+      int	componentIndex = getComponentIndex(field, newRangeType);
+      if (componentIndex >= 0)
+      {
+	result = field.extract(componentIndex);
+      }
+      else
+      {
+	result =
+	  new FlatField(
+	    new FunctionType(getDomainType(field), newRangeType),
+	    field.getDomainSet());
+      }
+    }
+    else if (newRangeType instanceof RealTupleType)
+    {
+      int	realTupleIndex = getComponentIndex(field, newRangeType);
+      if (realTupleIndex >= 0)
+      {
+	/*
+	 * The desired RealTuple range is a component of the input Field.
+	 */
+	result = (FlatField)field.extract(realTupleIndex);
+      }
+      else
+      {
+	/*
+	 * The desired RealTuple range is not a component of the input Field.
+	 * Extract each component of the desired RealTuple into a separate
+	 * Field and then combine the Field-s.
+	 */
+	RealTupleType	newRangeRealTupleType = (RealTupleType)newRangeType;
+	int		componentCount = newRangeRealTupleType.getDimension();
+	ArrayList	flatFields = new ArrayList(componentCount);
+	for (int i = 0; i < componentCount; ++i)
+	{
+	  int	componentIndex =
+	    getComponentIndex(field, newRangeRealTupleType.getComponent(i));
+	  if (componentIndex >= 0)
+	    flatFields.add(field.extract(componentIndex));
+	}
+	if (flatFields.size() != componentCount)
+	{
+	  /* Not all desired components exist in the range of the input Field */
+	  result =
+	    new FlatField(
+	      new FunctionType(getDomainType(field), newRangeType),
+	      field.getDomainSet());
+	}
+	else
+	{
+	  /* All desired components exist in the range of the input Field */
+	  result =
+	    (FlatField)FieldImpl.combine(
+	      (FlatField[])flatFields.toArray(new FlatField[componentCount]));
+	}
+      }
+    }
+    else if (newRangeType instanceof TupleType)
+    {
+      throw new UnimplementedException(
+	"Can't yet create Field with range " + newRangeType +
+	" from existing Field");
+    }
+    else
+    {
+      throw new TypeException("Can't create Field with range " + newRangeType);
+    }
+    return result;
+  }
+
+  /**
+   * Provides support for comparing RealTuple-s of the same RealTupleType.
+   *
+   * @author Steven R. Emmerson
+   */
+  public static final class
+  RealTupleComparator
+    implements Comparator
+  {
+    /**
+     * The single instance.
+     */
+    public static final RealTupleComparator	INSTANCE =
+      new RealTupleComparator();
+
+    /**
+     * Constructs from nothing.
+     */
+    private
+    RealTupleComparator()
+    {}
+
+    /**
+     * Compares two RealTuple-s of the same RealTupleType.
+     *
+     * @param obj1		The first RealTuple.
+     * @param obj2		The second RealTuple.  It shall have the same
+     *				RealTupleType as the first RealTuple.
+     * @return			A negative integer, zero, or a positive integer
+     *				as the first RealTuple is less than, equal to,
+     *				or greater than the second RealTuple.
+     * @throws ClassCastException
+     *                          The types of the arguments prevent this
+     *                          comparator from comparing them.
+     */
+    public int
+    compare(Object obj1, Object obj2)
+      throws ClassCastException
+    {
+      RealTuple	realTuple1 = (RealTuple)obj1;
+      RealTuple	realTuple2 = (RealTuple)obj2;
+      try
+      {
+	int	rank = realTuple1.getDimension();
+	int	comp = 0;
+	/*
+	 * Because Set rasterization has the last component as the outermost
+	 * dimension, the last tuple component is treated as the grossest one
+	 * for the purpose of comparison.  Hence, components are compared in
+	 * decreasing order.
+	 */
+	for (int i = rank; --i >= 0 && comp == 0; )
+	  comp = ((Real)realTuple1.getComponent(i)).compareTo(
+	    ((Real)realTuple2.getComponent(i)));
+	return comp;
+      }
+      catch (Exception e)
+      {
+	/*
+	 * This is the only checked exception a Comparator is allowed to throw.
+	 * The original exception could be either a visad.VisADException or a
+	 * java.rmi.RemoteException.
+	 */
+	String	reason = e.getMessage();
+	throw new ClassCastException("Can't compare RealTuple-s" +
+	  (reason == null ? "" : ": " + reason));
+      }
+    }
+  }
+
+  /**
+   * Provides support for comparing domain points that are reference to a
+   * particular Field.
+   */
+  private static class
+  ReferencedDomainPoint
+    implements Comparable
+  {
+    protected final RealTuple	sample;
+    protected final Field	field;
+    public ReferencedDomainPoint(RealTuple sample, Field field)
+    {
+      this.sample = sample;
+      this.field = field;
+    }
+    public int compareTo(Object obj)
+    {
+      return
+	RealTupleComparator.INSTANCE.compare(
+	  sample, ((ReferencedDomainPoint)obj).sample);
+    }
+  }
+
+  /**
+   * Consolidates fields.
+   * @param fields		The fields to consolidate.  Each field shall
+   *				have the same FunctionType.
+   * @return                    The input Fields consolidated into one Field.
+   *                            The domain shall be a GriddedSet comprising the
+   *                            union of the sample points of the fields.  The
+   *                            FunctionType shall be the same as that of the
+   *                            input Field-s.  When more than one input Field
+   *                            has valid range data for the same domain point,
+   *                            it is unspecified which range datum is used for
+   *                            the output Field.
+   * @throws FieldException	The Field array has zero length.
+   * @throws TypeException	Input Field-s not all same type.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static Field
+  consolidate(Field[] fields)
+    throws FieldException, TypeException, VisADException, RemoteException
+  {
+    /*
+     * Identify valid fields.
+     */
+    ArrayList	validFields = new ArrayList(fields.length);
+    for (int i = 0; i < fields.length; ++i)
+    {
+      Field	field = fields[i];
+      if (!field.isMissing())
+	validFields.add(field);
+    }
+    if (validFields.size() == 0)
+      throw new FieldException(
+	DataUtility.class.getName() + "(Field[]): Zero fields to consolidate");
+
+    /*
+     * Determine the consolidated domain.
+     */
+    FunctionType		funcType = (FunctionType)fields[0].getType();
+    TreeSet	consolidatedDomainTuples = new TreeSet();
+    for (Iterator iter = validFields.iterator(); iter.hasNext(); )
+    {
+      Field	field = (Field)iter.next();
+      if (!field.getType().equals(funcType))
+	throw new TypeException("Field type mismatch");
+      for (Enumeration en = field.domainEnumeration();
+	  en.hasMoreElements(); )
+      {
+	consolidatedDomainTuples.add(
+	  new ReferencedDomainPoint((RealTuple)en.nextElement(), field));
+      }
+    }
+
+    /*
+     * Create the consolidated field (with no range data).
+     */
+    Field	field = fields[0];
+    float[][]	domainFloats =
+      new float[field.getDomainDimension()][consolidatedDomainTuples.size()];
+    Unit[]	domainUnits = field.getDomainUnits();
+    int		sampleIndex = 0;
+    for (Iterator iter = consolidatedDomainTuples.iterator();
+	iter.hasNext();
+	++sampleIndex)
+    {
+      RealTuple	domainTuple = ((ReferencedDomainPoint)iter.next()).sample;
+      for (int i = domainFloats.length; --i >= 0; )
+	domainFloats[i][sampleIndex] =
+	  (float)((Real)domainTuple.getComponent(i)).getValue(domainUnits[i]);
+    }
+    SampledSet	consolidatedDomain =
+      domainFloats.length == 1
+	? (SampledSet)new Gridded1DSet(
+	    getDomainType(field), domainFloats, domainFloats[0].length,
+	    (CoordinateSystem)null, field.getDomainUnits(),
+	    (ErrorEstimate[])null)
+	: domainFloats.length == 2
+	  ? (SampledSet)new Irregular2DSet(
+	      getDomainType(field), domainFloats, (CoordinateSystem)null,
+	      field.getDomainUnits(), (ErrorEstimate[])null, (Delaunay)null)
+	  : domainFloats.length == 3
+	    ? (SampledSet)new Irregular3DSet(
+		getDomainType(field), domainFloats, (CoordinateSystem)null,
+		field.getDomainUnits(), (ErrorEstimate[])null, (Delaunay)null)
+	    : (SampledSet)new IrregularSet(
+		getDomainType(field), domainFloats, (CoordinateSystem)null,
+		field.getDomainUnits(), (ErrorEstimate[])null);
+    Field	consolidatedField =
+      field instanceof FlatField
+	? new FlatField(funcType, consolidatedDomain)
+	: new FieldImpl(funcType, consolidatedDomain);
+
+    /*
+     * Set the range of the consolidated field.
+     */
+    for (Iterator iter = consolidatedDomainTuples.iterator(); iter.hasNext(); )
+    {
+      ReferencedDomainPoint	point = (ReferencedDomainPoint)iter.next();
+      RealTuple			domainTuple = point.sample;
+      consolidatedField.setSample(
+	domainTuple, point.field.evaluate(domainTuple));
+    }
+
+    return consolidatedField;
+  }
+
+  /**
+   * Creates a GriddedSet from a FlatField.  The GriddedSet will be created from
+   * the domain and flat-range of the FlatField.  The first components in the
+   * tuples of the GriddedSet will come from the domain of the FlatField and the
+   * remaining components will come from the range of the FlatField.  Note that,
+   * because a GriddedSet doesn't have the ability to contain values in small
+   * primitives (e.g. byte, short), the returned set may be significantly larger
+   * than the input field.
+   * @param field		The FlatField from which to create a GriddedSet.
+   * @param copy		Whether or not to copy the range values from
+   *				the field (i.e. <code>field.getFloats(copy)
+   *				</code>).
+   * @return			The GriddedSet corresponding to the input
+   *				FlatField.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   * @throws RemoteException	Java RMI failure.
+   */
+  public static GriddedSet
+  createGriddedSet(FlatField field, boolean copy)
+    throws VisADException, RemoteException
+  {
+    int			domainRank = field.getDomainDimension();
+    int			flatRangeRank = field.getRangeDimension();
+    RealType[]		realTypes = new RealType[domainRank + flatRangeRank];
+    RealTupleType	tupleType = getDomainType(field);
+    for (int i = 0; i < domainRank; i++)
+      realTypes[i] = (RealType)tupleType.getComponent(i);
+    tupleType = getFlatRangeType(field);
+    for (int i = 0; i < flatRangeRank; i++)
+      realTypes[domainRank+i] = (RealType)tupleType.getComponent(i);
+    float[][]	samples = new float[realTypes.length][field.getLength()];
+    /*
+     * The following gets the domain values in their actual units.
+     */
+    System.arraycopy(
+      field.getDomainSet().getSamples(), 0, samples, 0, domainRank);
+    /*
+     * The following gets the range values in their default units.
+     */
+    System.arraycopy(
+      field.getFloats(copy), 0, samples, domainRank, flatRangeRank);
+    int[]		lengths = new int[samples.length];
+    for (int i = samples.length; --i >= 0; )
+      lengths[i] = samples[i].length;
+    Unit[]		units = new Unit[realTypes.length];
+    System.arraycopy(field.getDomainUnits(), 0, units, 0, domainRank);
+    System.arraycopy(
+      field.getDefaultRangeUnits(), 0, units, domainRank, flatRangeRank);
+    return
+      GriddedSet.create(
+	new RealTupleType(realTypes),
+	samples,
+	lengths,
+	(CoordinateSystem)null,
+	units,
+	(ErrorEstimate[])null);
+  }
+
+  /**
+   * Simplifies a MathType.  Removes all enclosing, single-component TupleType-s
+   * until the "core" is revealed (e.g. ScalarType, multi-component TupleType).
+   * @param type		The MathType to be simplified.
+   * @return			The simplest form corresponding to
+   *				<code>type</code>.
+   * @throws VisADException	Couldn't create necessary VisAD object.
+   */
+  public static MathType simplify(MathType type)
+    throws VisADException
+  {
+    while (type instanceof TupleType && ((TupleType)type).getDimension() == 1)
+	type = ((TupleType)type).getComponent(0);
+    return type;
+  }
+
+  /**
+   * @deprecated Use getScalarTypes(Data, Vector) instead.
+   */
+  public static int getRealTypes(Data data, Vector v)
+    throws VisADException, RemoteException
+  {
+    return getRealTypes(new Data[] {data}, v, true, false);
+  }
+
+  /**
+   * @deprecated Use getScalarTypes(Data[], Vector, boolean, boolean) instead.
+   */
+  public static int getRealTypes(Data[] data, Vector v, boolean keepDupl,
+    boolean doCoordSys) throws VisADException, RemoteException
+  {
+    int dupl = getScalarTypes(data, v, keepDupl, doCoordSys);
+    int i = 0;
+    while (i < v.size()) {
+      ScalarType st = (ScalarType) v.elementAt(i);
+      if (!(st instanceof RealType)) v.remove(i);
+      else i++;
+    }
+    return dupl;
+  }
+
+  /**
+   * Obtains a Vector consisting of all ScalarTypes present in a Data object's
+   * MathType.
+   * @param data                The Data from which to extract the ScalarTypes.
+   * @param v                   The Vector in which to store the ScalarTypes.
+   * @throws VisADException     Couldn't parse the Data's MathType.
+   * @throws RemoteException    Couldn't obtain the remote Data's MathType.
+   * @return                    The number of duplicate ScalarTypes found.
+   */
+  public static int getScalarTypes(Data data, Vector v)
+    throws VisADException, RemoteException
+  {
+    return getScalarTypes(new Data[] {data}, v, true, false);
+  }
+
+  /**
+   * Obtains a Vector consisting of all ScalarTypes present in an array of
+   * Data objects' MathTypes.
+   * @param data                The array of Data from which to extract the
+   *                            ScalarTypes.
+   * @param v                   The Vector in which to store the ScalarTypes.
+   * @param keepDupl            Whether to add a RealType to the Vector when
+   *                            it is already present there.
+   * @param doCoordSys          Whether to include ScalarTypes from
+   *                            CoordinateSystem references.
+   * @throws VisADException     Couldn't parse a Data's MathType.
+   * @throws RemoteException    Couldn't obtain a remote Data's MathType.
+   * @return                    The number of duplicate ScalarTypes found.
+   */
+  public static int getScalarTypes(Data[] data, Vector v, boolean keepDupl,
+    boolean doCoordSys) throws VisADException, RemoteException
+  {
+    Vector coord = (doCoordSys ? new Vector() : null);
+    int[] dupl = new int[1];
+    dupl[0] = 0;
+    for (int i=0; i<data.length; i++) {
+      Data d = data[i];
+      if (d != null) {
+        MathType type = d.getType();
+        parse(type, v, dupl, keepDupl, coord);
+      }
+    }
+    if (coord != null) {
+      // append coordinate system reference ScalarTypes to vector
+      for (int i=0; i<coord.size(); i++) {
+        Object o = coord.elementAt(i);
+        boolean c = v.contains(o);
+        if (c) dupl[0]++;
+        if (keepDupl || !c) v.add(o);
+      }
+    }
+    return dupl[0];
+  }
+
+  /**
+   * getScalarTypes helper method.
+   */
+  private static void parse(MathType mathType, Vector v, int[] i,
+    boolean keepDupl, Vector coord) throws VisADException
+  {
+    if (mathType instanceof FunctionType) {
+      parseFunction((FunctionType) mathType, v, i, keepDupl, coord);
+    }
+    else if (mathType instanceof SetType) {
+      parseSet((SetType) mathType, v, i, keepDupl, coord);
+    }
+    else if (mathType instanceof TupleType) {
+      parseTuple((TupleType) mathType, v, i, keepDupl, coord);
+    }
+    else parseScalar((ScalarType) mathType, v, i, keepDupl);
+  }
+
+  /**
+   * getScalarTypes helper method.
+   */
+  private static void parseFunction(FunctionType mathType, Vector v, int[] i,
+    boolean keepDupl, Vector coord) throws VisADException
+  {
+    // extract domain
+    RealTupleType domain = mathType.getDomain();
+    parseTuple((TupleType) domain, v, i, keepDupl, coord);
+
+    // extract range
+    MathType range = mathType.getRange();
+    parse(range, v, i, keepDupl, coord);
+  }
+
+  /**
+   * getScalarTypes helper method.
+   */
+  private static void parseSet(SetType mathType, Vector v, int[] i,
+    boolean keepDupl, Vector coord) throws VisADException
+  {
+    // extract domain
+    RealTupleType domain = mathType.getDomain();
+    parseTuple((TupleType) domain, v, i, keepDupl, coord);
+  }
+
+  /**
+   * getScalarTypes helper method.
+   */
+  private static void parseTuple(TupleType mathType, Vector v, int[] i,
+    boolean keepDupl, Vector coord) throws VisADException
+  {
+    // extract components
+    for (int j=0; j<mathType.getDimension(); j++) {
+      MathType cType = mathType.getComponent(j);
+      if (cType != null) parse(cType, v, i, keepDupl, coord);
+    }
+    if (mathType instanceof RealTupleType && coord != null) {
+      // add coordinate system references
+      RealTupleType realTupleType = (RealTupleType) mathType;
+      CoordinateSystem coordSys = realTupleType.getCoordinateSystem();
+      if (coordSys != null) {
+        RealTupleType ref = coordSys.getReference();
+        for (int j=0; j<realTupleType.getDimension(); j++) {
+          RealType rType = (RealType) realTupleType.getComponent(j);
+          parseScalar(rType, coord, new int[1], keepDupl);
+        }
+        for (int j=0; j<ref.getDimension(); j++) {
+          RealType rType = (RealType) ref.getComponent(j);
+          parseScalar(rType, coord, new int[1], keepDupl);
+        }
+      }
+    }
+  }
+
+  /**
+   * getScalarTypes helper method.
+   */
+  private static void parseScalar(ScalarType mathType, Vector v, int[] i,
+    boolean keepDupl)
+  {
+    if (v.contains(mathType)) {
+      if (keepDupl) v.add(mathType);
+      i[0]++;
+    }
+    else v.add(mathType);
+  }
+
+  /**
+   * Attempts to guess a good set of mappings for a display containing
+   * Data objects of the given types. The algorithm simply returns mappings
+   * for the first successfully guessed dataset.
+   */
+  public static ScalarMap[] guessMaps(MathType[] types, boolean allow3d) {
+    int len = types.length;
+    ScalarMap[] maps = null;
+    for (int i=0; i<len; i++) {
+      MathType t = types[i];
+      if (t != null) maps = t.guessMaps(allow3d);
+      if (maps != null) break;
+    }
+    return maps;
+  }
+
+  /**
+   * Converts the given vector of mappings to an easy-to-read String form.
+   */
+  public static String convertMapsToString(Vector v) {
+    int len = v.size();
+    ScalarMap[] sm = new ScalarMap[len];
+    for (int i=0; i<len; i++) sm[i] = (ScalarMap) v.elementAt(i);
+    return convertMapsToString(sm);
+  }
+
+  /**
+   * Converts the given array of mappings to an easy-to-read String form.
+   */
+  public static String convertMapsToString(ScalarMap[] sm) {
+    StringBuffer sb = new StringBuffer(128);
+    for (int i=0; i<sm.length; i++) {
+      ScalarMap m = sm[i];
+      ScalarType domain = m.getScalar();
+      DisplayRealType range = m.getDisplayScalar();
+      int q = -1;
+      for (int j=0; j<Display.DisplayRealArray.length; j++) {
+        if (range.equals(Display.DisplayRealArray[j])) q = j;
+      }
+      sb.append(' ');
+      sb.append(domain.getName());
+      sb.append(' ');
+      sb.append(q);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Converts the given map string to its corresponding array of mappings.
+   * @param mapString      The String from which to extract the ScalarMaps.
+   * @param data           The Data object to search for valid ScalarTypes.
+   * @param showErrors     Whether to output errors to stdout.
+   */
+  public static ScalarMap[] convertStringToMaps(
+    String mapString, Data data, boolean showErrors)
+  {
+    return convertStringToMaps(mapString, new Data[] {data}, showErrors);
+  }
+
+  /**
+   * Converts the given map string to its corresponding array of mappings.
+   * @param mapString      The String from which to extract the ScalarMaps.
+   * @param data           The Data objects to search for valid ScalarTypes.
+   * @param showErrors     Whether to output errors to stdout.
+   */
+  public static ScalarMap[] convertStringToMaps(
+    String mapString, Data[] data, boolean showErrors)
+  {
+    Vector types = new Vector();
+    for (int i=0; i<data.length; i++) {
+      try {
+        getScalarTypes(data[i], types);
+      }
+      catch (VisADException exc) {
+        if (showErrors) {
+          System.out.println("Warning: " +
+            "could not extract ScalarTypes from Data object.");
+        }
+      }
+      catch (RemoteException exc) {
+        if (showErrors) {
+          System.out.println("Warning: " +
+            "could not extract ScalarTypes from Data object.");
+        }
+      }
+    }
+    return convertStringToMaps(mapString, types, showErrors);
+  }
+
+  /**
+   * Converts the given map string to its corresponding array of mappings.
+   * @param mapString      The String from which to extract the ScalarMaps.
+   * @param types          List of valid ScalarTypes.
+   * @param showErrors     Whether to output errors to stdout.
+   */
+  public static ScalarMap[] convertStringToMaps(
+    String mapString, Vector types, boolean showErrors)
+  {
+    // extract mapping information from string
+    if (mapString == null) return null;
+    StringTokenizer st = new StringTokenizer(mapString);
+    Vector dnames = new Vector();
+    Vector rnames = new Vector();
+    while (true) {
+      if (!st.hasMoreTokens()) break;
+      String s = st.nextToken();
+      if (!st.hasMoreTokens()) {
+        if (showErrors) {
+          System.err.println("Warning: trailing maps value " + s +
+            " has no corresponding number and will be ignored");
+        }
+        continue;
+      }
+      String si = st.nextToken();
+      Integer i = null;
+      try {
+        i = new Integer(Integer.parseInt(si));
+      }
+      catch (NumberFormatException exc) { }
+      if (i == null) {
+        if (showErrors) {
+          System.err.println("Warning: maps value " + si + " is not a " +
+            "valid integer and the maps pair (" + s + ", " + si + ") " +
+            "will be ignored");
+        }
+      }
+      else {
+        dnames.add(s);
+        rnames.add(i);
+      }
+    }
+
+    // set up mappings
+    if (dnames != null) {
+      int len = dnames.size();
+      if (len > 0) {
+        int vLen = types.size();
+        int dLen = Display.DisplayRealArray.length;
+
+        // construct ScalarMaps
+        ScalarMap[] maps = new ScalarMap[len];
+        for (int j=0; j<len; j++) {
+          // find appropriate ScalarType
+          ScalarType mapDomain = null;
+          String name = (String) dnames.elementAt(j);
+          for (int k=0; k<vLen; k++) {
+            ScalarType type = (ScalarType) types.elementAt(k);
+            if (name.equals(type.getName())) {
+              mapDomain = type;
+              break;
+            }
+          }
+          if (mapDomain == null) {
+            // still haven't found type; look in static Vector for it
+            mapDomain = ScalarType.getScalarTypeByName(name);
+          }
+
+          // find appropriate DisplayRealType
+          int q = ((Integer) rnames.elementAt(j)).intValue();
+          DisplayRealType mapRange = null;
+          if (q >= 0 && q < dLen) mapRange = Display.DisplayRealArray[q];
+
+          // construct mapping
+          if (mapDomain == null || mapRange == null) {
+            if (showErrors) {
+              System.err.print("Warning: maps pair (" + name + ", " +
+                q + ") has an invalid ");
+              if (mapDomain == null && mapRange == null) {
+                System.err.print("domain and range");
+              }
+              else if (mapDomain == null) System.err.print("domain");
+              else System.err.print("range");
+              System.err.println(" and will be ignored");
+            }
+            maps[j] = null;
+          }
+          else {
+            try {
+              maps[j] = new ScalarMap(mapDomain, mapRange);
+            }
+            catch (VisADException exc) {
+              if (showErrors) {
+                System.err.println("Warning: maps pair (" + name + ", " +
+                  q + ") cannot be converted to a ScalarMap");
+              }
+              maps[j] = null;
+            }
+          }
+        }
+        return maps;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Converts an array of strings into a VisAD Tuple.
+   *
+   * @param s The array of strings to be converted to a VisAD Tuple.
+   *
+   * @return VisAD Tuple, or null if Tuple could not be created.
+   */
+  public static Tuple stringsToTuple(String[] s) {
+    return stringsToTuple(s, false);
+  }
+
+  /**
+   * Converts an array of strings into a VisAD Tuple.
+   *
+   * @param s The array of strings to be converted to a VisAD Tuple.
+   * @param printStackTraces <tt>true</tt> if the stack trace for
+   *                         any exception should be printed.
+   *
+   * @return VisAD Tuple, or null if Tuple could not be created.
+   */
+  public static Tuple stringsToTuple(String[] s, boolean printStackTraces) {
+    try {
+      if (s == null) return null;
+      int len = s.length;
+      if (len == 0) return null;
+      Text[] text = new Text[len];
+      for (int i=0; i<len; i++) text[i] = new Text(s[i]);
+      Tuple tuple = new Tuple(text);
+      return tuple;
+    }
+    catch (VisADException exc) {
+      if (printStackTraces) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (printStackTraces) exc.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Converts a VisAD tuple into an array of strings.
+   *
+   * @param t The VisAD Tuple to be converted to an array of strings.
+   *
+   * @return Array of Strings, or null if array could not be created.
+   */
+  public static String[] tupleToStrings(Tuple t) {
+    return tupleToStrings(t, false);
+  }
+
+  /**
+   * Converts a VisAD tuple into an array of strings.
+   *
+   * @param t The VisAD Tuple to be converted to an array of strings.
+   * @param printStackTraces <tt>true</tt> if the stack trace for
+   *                         any exception should be printed.
+   *
+   * @return Array of Strings, or null if array could not be created.
+   */
+  public static String[] tupleToStrings(Tuple t, boolean printStackTraces) {
+    if (t == null) return null;
+    int len = t.getDimension();
+    try {
+      String[] errors = new String[len];
+      for (int i=0; i<len; i++) {
+        Text text = (Text) t.getComponent(i);
+        errors[i] = text.getValue();
+      }
+      return errors;
+    }
+    catch (VisADException exc) {
+      if (printStackTraces) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (printStackTraces) exc.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Verify that an object is Serializable by attempting to
+   * serialize it.
+   *
+   * @param obj An object which needs to be serialized
+   *
+   * @return <tt>true</tt> if the object is Serializable, false otherwise.
+   */
+  public static boolean isSerializable(Object obj) {
+    return isSerializable(obj, false);
+  }
+
+  /**
+   * Verify that an object is Serializable by attempting to
+   * serialize it.
+   *
+   * @param obj An object which needs to be serialized
+   * @param printStackTraces <tt>true</tt> if the stack trace for
+   *                         any exception should be printed.
+   *
+   * @return <tt>true</tt> if the object is Serializable, false otherwise.
+   */
+  public static boolean isSerializable(Object obj, boolean printStackTraces)
+  {
+    java.io.ByteArrayOutputStream outBytes;
+    outBytes = new java.io.ByteArrayOutputStream();
+
+    java.io.ObjectOutputStream outStream;
+    try {
+      outStream = new java.io.ObjectOutputStream(outBytes);
+    } catch (java.io.IOException ioe) {
+      if (printStackTraces) ioe.printStackTrace();
+      return false;
+    }
+
+    try {
+      outStream.writeObject(obj);
+      outStream.flush();
+    } catch (java.io.IOException ioe) {
+      if (printStackTraces) ioe.printStackTrace();
+      return false;
+    }
+
+    java.io.ByteArrayInputStream inBytes;
+    inBytes = new java.io.ByteArrayInputStream(outBytes.toByteArray());
+
+    try {
+      outStream.close();
+      outBytes.close();
+    } catch (java.io.IOException ioe) {
+      if (printStackTraces) ioe.printStackTrace();
+      return false;
+    }
+
+    java.io.ObjectInputStream inStream;
+    try {
+      inStream = new java.io.ObjectInputStream(inBytes);
+    } catch (java.io.IOException ioe) {
+      if (printStackTraces) ioe.printStackTrace();
+      return false;
+    }
+
+    Object obj2;
+    try {
+      obj2 = inStream.readObject();
+    } catch (java.io.IOException ioe) {
+      if (printStackTraces) ioe.printStackTrace();
+      return false;
+    } catch (ClassNotFoundException cnfe) {
+      if (printStackTraces) cnfe.printStackTrace();
+      return false;
+    }
+
+    try {
+      inStream.close();
+      inBytes.close();
+    } catch (java.io.IOException ioe) {
+      if (printStackTraces) ioe.printStackTrace();
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Converts a remote Data object to a local Data object.
+   *
+   * @param data The Data object to be made local.
+   *
+   * @return Local Data object, or null if Data could not be converted.
+   */
+  public static DataImpl makeLocal(Data data) {
+    return makeLocal(data, false);
+  }
+
+  /**
+   * Converts a remote Data object to a local Data object.
+   *
+   * @param data The Data object to be made local.
+   * @param printStackTraces <tt>true</tt> if the stack trace for
+   *                         any exception should be printed.
+   *
+   * @return Local Data object, or null if Data could not be converted.
+   */
+  public static DataImpl makeLocal(Data data, boolean printStackTraces) {
+    try {
+      if (data != null) return data.local();
+    }
+    catch (VisADException exc) {
+      if (printStackTraces) exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      if (printStackTraces) exc.printStackTrace();
+    }
+    return null;
+  }
+
+  /**
+   * Gets the specified sample of a VisAD Set.
+   *
+   * @param set               The set to have a sample returned.
+   * @param index             The index of the sample to be returned.
+   * @return                  The sample at the specified index.  Will
+   *                          be empty if the index is out-of-bounds.
+   *                          Will have a single component if the set is
+   *                          one-dimensional.
+   * @throws VisADException   Couldn't create necessary VisAD object.
+   * @throws RemoteException  Java RMI failure.
+   */
+  public static RealTuple getSample(Set set, int index)
+          throws VisADException, RemoteException {
+
+    RealTuple     sample;
+    RealTupleType realTupleType = ((SetType) set.getType()).getDomain();
+    double[][]    values        =
+      Unit.convertTuple(set.indexToDouble(new int[]{ index }),
+                        set.getSetUnits(),
+                        realTupleType.getDefaultUnits());
+
+    if ((index < 0) || (index >= set.getLength())) {
+      sample = new RealTuple(realTupleType);
+    } else {
+      double[] doubles = new double[values.length];
+
+      for (int i = doubles.length; --i >= 0; ) {
+        doubles[i] = values[i][0];
+      }
+
+      sample = new RealTuple(realTupleType, doubles);
+    }
+
+    return sample;
+  }
+
+  /**
+   * Gets the units of the (flat) components of the range of a FlatField.
+   *
+   * @param field             The FlatField.
+   * @return                  The units of the (flat) components of the
+   *                          range of the FlatField.  Won't be
+   *                          <code>null</code>.
+   */
+  public static Unit[] getRangeUnits(FlatField field) {
+
+      Unit[][] units  = field.getRangeUnits();
+      Unit[]   result = new Unit[units.length];
+
+      for (int i = result.length; --i >= 0; ) {
+          result[i] = units[i][0];
+      }
+
+      return result;
+  }
+
+  /**
+   * Make a RealType with a unique name for the given unit
+   *
+   * @param name name of the RealType
+   * @param unit  the default unit
+   *
+   * @return  the RealType 
+   *
+   * @throws VisADException  problem creating the RealType
+   */
+  public static RealType getUniqueRealType(String name, Unit unit) {
+      return getUniqueRealType(name, unit, null, 0);
+  }
+
+  /**
+   * Make a RealType with a unique name for the given unit
+   *
+   * @param name name of the RealType
+   * @param unit  the default unit
+   * @param set  the default set
+   * @param attrMask  the attribute mask
+   *
+   * @return  the RealType  or null if it still can't create a unique name
+   */
+  public static RealType getUniqueRealType(String name, Unit unit, 
+                                           Set set, int attrMask)  {
+    RealType type    = RealType.getRealType(name, unit, set, attrMask);
+    if (type == null) {
+      String  newname = cleanTypeName(name) + "[unit:" + ((unit == null)
+              ? "null"
+              : cleanTypeName(unit.toString())) + "]";
+
+      type = RealType.getRealType(newname, unit, set, attrMask);
+    }
+    return type;
+  }  
+
+  /**
+   * Make a valid VisAD RealType name from the string.  Remove
+   * spaces, "." and parens.
+   * @param name name to clean
+   * @return cleaned up name
+   */
+  public static String cleanTypeName(String name) {
+    name = name.replace('.', '_');
+    name = name.replace(",","_");
+    name = name.replace("-","_");  // so doesn't get confused with ->
+    name = name.replace("*","_");  // so it doesn't confuse regexes
+    name = name.replace(' ', '_');
+    name = name.replace('(', '[');
+    name = name.replace(')', ']');
+    while(name.indexOf("__")>=0) {
+      name = name.replace("__","_");
+    }
+    return name;
+  }
+}
diff --git a/visad/util/Delay.java b/visad/util/Delay.java
new file mode 100644
index 0000000..7510ae2
--- /dev/null
+++ b/visad/util/Delay.java
@@ -0,0 +1,50 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+public class Delay {
+
+  public static final int DEFAULT_DELAY = 50;
+
+  public Delay() {
+    this(DEFAULT_DELAY);
+  }
+
+  /** wait for millis milliseconds */
+  public Delay(int millis) {
+    wait(millis);
+  }
+
+  /** wait for millis milliseconds */
+  public void wait(int millis) {
+    try {
+      synchronized(this) {
+        super.wait(millis);
+      }
+    }
+    catch (InterruptedException e) {
+    }
+  }
+
+  public static void main(String[] args) { new Delay(10000); }
+}
diff --git a/visad/util/DualRes.java b/visad/util/DualRes.java
new file mode 100644
index 0000000..0583b83
--- /dev/null
+++ b/visad/util/DualRes.java
@@ -0,0 +1,192 @@
+//
+// DualRes.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.rmi.RemoteException;
+import visad.*;
+
+/**
+ * Maintains two representations for a given data reference: one at
+ * high (normal) resolution, and one at low (scaled-down) resolution.
+ * When greater rendering speed is necessary, programs can utilize the
+ * computed low-resolution data. When more detail is required, programs
+ * can switch back to the hi-resolution data.
+ */
+public class DualRes {
+
+  /** Debugging flag. */
+  public static boolean DEBUG = true;
+
+  /** High-resolution data reference. */
+  protected DataReferenceImpl hi_ref;
+  
+  /** Low-resolution data reference. */
+  protected DataReferenceImpl lo_ref;
+
+  /** Computational cell that scales down high-resolution data. */
+  private CellImpl cell;
+
+  /** Scale factor for low-resolution data. */
+  private double scale = 0.5;
+
+  /** Rescales a field by the given scale factor. */
+  public static FieldImpl rescale(FieldImpl field, double scale)
+    throws VisADException, RemoteException
+  {
+    Set set = field.getDomainSet();
+    if (!(set instanceof LinearSet)) return null;
+    LinearSet lset = (LinearSet) set;
+    int dim = set.getDimension();
+
+    // compute new lengths
+    int[] lengths = new int[dim];
+    for (int i=0; i<dim; i++) {
+      Linear1DSet lin1set = lset.getLinear1DComponent(i);
+      lengths[i] = (int) (lin1set.getLength() * scale);
+      if (lengths[i] < 1) lengths[i] = 1;
+    }
+
+    return rescale(field, lengths);
+  }
+
+  /** Rescales a field by the given scale factor. */
+  public static FieldImpl rescale(FieldImpl field, int[] lengths)
+    throws VisADException, RemoteException
+  {
+    Set set = field.getDomainSet();
+    if (!(set instanceof LinearSet)) return null;
+    LinearSet lset = (LinearSet) set;
+    int dim = set.getDimension();
+    if (lengths.length != dim) {
+      throw new VisADException("bad lengths dimension");
+    }
+
+    // scale set to new resolution
+    Linear1DSet[] lin_sets = new Linear1DSet[dim];
+    for (int i=0; i<dim; i++) {
+      Linear1DSet lin1set = lset.getLinear1DComponent(i);
+      MathType type = lin1set.getType();
+      double first = lin1set.getFirst();
+      double last = lin1set.getLast();
+      CoordinateSystem coord_sys = lin1set.getCoordinateSystem();
+      Unit[] units = lin1set.getSetUnits();
+
+      lin_sets[i] = new Linear1DSet(type,
+        first, last, lengths[i], coord_sys, units, null);
+    }
+
+    // compute new linear set at new resolution
+    MathType type = set.getType();
+    CoordinateSystem coord_sys = set.getCoordinateSystem();
+    Unit[] units = set.getSetUnits();
+    Set nset;
+    if (dim == 1) {
+      nset = lin_sets[0];
+    }
+    else if (dim == 2) {
+      nset = new Linear2DSet(type, lin_sets, coord_sys, units, null);
+    }
+    else if (dim == 3) {
+      nset = new Linear3DSet(type, lin_sets, coord_sys, units, null);
+    }
+    else {
+      nset = new LinearNDSet(type, lin_sets, coord_sys, units, null);
+    }
+
+    // rescale data
+    return (FieldImpl)
+      field.resample(nset, Data.WEIGHTED_AVERAGE, Data.NO_ERRORS);
+  }
+
+  /**
+   * Constructs an object to maintain both high- and low-resolution
+   * representations for the referenced data.
+   */
+  public DualRes(DataReferenceImpl ref)
+    throws VisADException, RemoteException
+  {
+    hi_ref = ref;
+    lo_ref = new DataReferenceImpl("DualRes_ref");
+
+    cell = new CellImpl() {
+      public void doAction() {
+        try {
+          // compute low-resolution data representation
+          Data data = hi_ref.getData();
+          if (data == null || !(data instanceof FieldImpl)) return;
+          FieldImpl field = (FieldImpl) data;
+
+          // check if data is a timestack
+          FunctionType ftype = (FunctionType) data.getType();
+          RealTupleType domain = ftype.getDomain();
+          MathType range = ftype.getRange();
+          FieldImpl downfield;
+          if (domain.getDimension() == 1 && range instanceof FunctionType) {
+            // timestack; downsample each range component
+            downfield = new FieldImpl(ftype, field.getDomainSet());
+            int len = field.getLength();
+            for (int i=0; i<len; i++) {
+              Data sample = field.getSample(i);
+              if (!(sample instanceof FieldImpl)) return;
+              downfield.setSample(i, rescale((FieldImpl) sample, scale));
+            }
+          }
+          else downfield = rescale(field, scale);
+          lo_ref.setData(downfield);
+        }
+        catch (VisADException exc) { if (DEBUG) exc.printStackTrace(); }
+        catch (RemoteException exc) { if (DEBUG) exc.printStackTrace(); }
+      }
+    };
+    cell.addReference(hi_ref);
+  }
+
+  /**
+   * Sets the factor by which the low-resolution representation is
+   * scaled down from the high-resolution one.
+   */
+  public void setResolutionScale(double scale) throws VisADException {
+    if (scale > 1) this.scale = 1.0 / scale;
+    else {
+      throw new VisADException(
+        "DualRes: scale factor must be greater than 1");
+    }
+  }
+
+  /**
+   * Gets the factor by which the low-resolution representation is
+   * scaled down from the high-resolution one.
+   */
+  public double getResolutionScale() { return 1.0 / scale; }
+
+  /** Gets the DataReference corresponding to the full-resolution data. */
+  public DataReferenceImpl getHighResReference() { return hi_ref; }
+
+  /** Gets the DataReference corresponding to the scaled-down data. */
+  public DataReferenceImpl getLowResReference() { return lo_ref; }
+
+}
diff --git a/visad/util/ExtensionFileFilter.java b/visad/util/ExtensionFileFilter.java
new file mode 100644
index 0000000..da2702b
--- /dev/null
+++ b/visad/util/ExtensionFileFilter.java
@@ -0,0 +1,113 @@
+//
+// ExtensionFileFilter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+
+/** A file filter based on file extensions, for use with a JFileChooser. */
+public class ExtensionFileFilter extends FileFilter
+  implements java.io.FileFilter, Comparable
+{
+
+  // -- Fields --
+
+  /** List of valid extensions. */
+  private String[] exts;
+
+  /** Description. */
+  private String desc;
+
+  // -- Constructors --
+
+  /** Constructs a new filter that accepts the given extension. */
+  public ExtensionFileFilter(String extension, String description) {
+    this(new String[] {extension}, description);
+  }
+
+  /** Constructs a new filter that accepts the given extensions. */
+  public ExtensionFileFilter(String[] extensions, String description) {
+    exts = new String[extensions.length];
+    System.arraycopy(extensions, 0, exts, 0, extensions.length);
+    StringBuffer sb = new StringBuffer(description);
+    boolean first = true;
+    for (int i=0; i<exts.length; i++) {
+      if (exts[i] == null) exts[i] = "";
+      if (exts[i].equals("")) continue;
+      if (first) {
+        sb.append(" (");
+        first = false;
+      }
+      else sb.append(", ");
+      sb.append("*.");
+      sb.append(exts[i]);
+    }
+    sb.append(")");
+    desc = sb.toString();
+  }
+
+  // -- ExtensionFileFilter API methods --
+
+  /** Gets the filter's first valid extension. */
+  public String getExtension() { return exts[0]; }
+
+  /** Gets the filter's valid extensions. */
+  public String[] getExtensions() { return exts; }
+
+  // -- FileFilter API methods --
+
+  /** Accepts files with the proper extensions. */
+  public boolean accept(File f) {
+    if (f.isDirectory()) return true;
+
+    String name = f.getName();
+    int index = name.lastIndexOf('.');
+    String ext = index < 0 ? "" : name.substring(index + 1);
+
+    for (int i=0; i<exts.length; i++) {
+      if (ext.equalsIgnoreCase(exts[i])) return true;
+    }
+
+    return false;
+  }
+
+  /** Gets the filter's description. */
+  public String getDescription() { return desc; }
+
+  // -- Object API methods --
+
+  /** Gets a string representation of this file filter. */
+  public String toString() { return "ExtensionFileFilter: " + desc; }
+
+  // -- Comparable API methods --
+
+  /** Compares two FileFilter objects alphanumerically. */
+  public int compareTo(Object o) {
+    return desc.compareToIgnoreCase(((FileFilter) o).getDescription());
+  }
+
+}
diff --git a/visad/util/FloatTupleArray.java b/visad/util/FloatTupleArray.java
new file mode 100644
index 0000000..fca7dbd
--- /dev/null
+++ b/visad/util/FloatTupleArray.java
@@ -0,0 +1,88 @@
+package visad.util;
+
+import java.util.logging.Logger;
+
+/**
+ * Growable data structure for float tuples.   
+ */
+
+public interface FloatTupleArray {
+
+  public static class Factory {
+	  
+    private static Logger logger = Logger.getLogger(Factory.class.getName());
+
+    /**
+     * Create a new instance. 
+     * 
+     * TODO: Currently only returns the default implementation 
+     * but in the future may return the most efficient implementation 
+     * available in the runtime.
+     * 
+     * @param dim Tuple dimension.
+     * @param size Initial size of data structure.
+     * @return An instance initialized to contain tuples of size <code>dim</code>
+     *  and have size <code>size</code>.
+     */
+    public static FloatTupleArray newInstance(int dim, int size) {
+      return new FloatTupleArrayImpl(dim, size);
+    }
+    private Factory() {}
+  }
+
+  /**
+   * Add tuples to array. 
+   * @param values Values to add to array where dimension 2 == <code>dim()</code>.
+   * @param start Index in input array where to start taking tuples.
+   * @param num The number of tuples to take.
+   */
+  public void add(float[][] values, int start, int num); 
+
+  /**
+   * Get the elements of this array.  
+   * @return This may return a reference or copy depending
+   * on the implementation.
+   */
+  public float[][] elements();
+
+  /**
+   * Add tuples to the array. The array will grow as necessary to accommodate the new data.
+   * @param elements Values to add to array where dimension 2 == <code>dim()</code>.
+   */
+  public void add(float[][] elements);
+
+  /**
+   * Set an array value.
+   * @param i Tuple row index.
+   * @param j Tuple col index.
+   * @param val Value to set.
+   */
+  public void set(int i, int j, float val);
+
+  /**
+   * Get a value. 
+   * @param i Tuple row index.
+   * @param j Tuple col index.
+   * @return The value for tuple <code>i</code> at tuple index <code>j</code>.
+   */
+  public float get(int i, int j);
+
+  /**
+   * Get array data. This is potentially expensive.
+   * @return A copy of the array data.
+   */
+  public float[][] toArray();
+
+  /**
+   * Get array size.
+   * @return size of array
+   */
+  
+  public int size();
+
+  /**
+   * Get array tuple dimension.
+   * @return The array tuple dimension
+   */
+  public int dim();
+}
diff --git a/visad/util/FloatTupleArrayImpl.java b/visad/util/FloatTupleArrayImpl.java
new file mode 100644
index 0000000..ae10f06
--- /dev/null
+++ b/visad/util/FloatTupleArrayImpl.java
@@ -0,0 +1,177 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.util.logging.Logger;
+
+/**
+ * Growable array of float tuples.
+ */
+public class FloatTupleArrayImpl implements FloatTupleArray {
+
+  private static Logger log = Logger.getLogger(FloatTupleArrayImpl.class.getName());
+
+  /** Factor by which the arrays are grown when they run out of space. */
+  public static final float DEF_GROW_FACTOR = 1.5f;
+
+  protected float[][] elements;
+  private int size; // last used element
+  private final int dim;
+  private final float growFactor;
+
+  public FloatTupleArrayImpl(int dim, int initialSize) {
+    this(dim, initialSize, DEF_GROW_FACTOR);
+  }
+
+  /**
+   * Construct an instance of <code>initialSize</code>.
+   * @param dim The tuple dimension.
+   * @param initialSize The initial size of the internal array.
+   * @param growFactor Factor to grow by when resizing is necessary.
+   */
+  public FloatTupleArrayImpl(int dim, int initialSize, float growFactor) {
+    assert initialSize > 1;
+    this.dim = dim;
+    elements = new float[dim][initialSize];
+    this.growFactor = growFactor;
+  }
+
+  /**
+   * Grow the internal arrays. <code>System.arraycopy</code> is used for array
+   * expansion.
+   */
+  protected void grow() {
+    int newSize = (int)(elements[0].length * growFactor);
+    log.fine("growing from " + elements[0].length + " to "+newSize);
+    float[][] newArr = new float[elements.length][newSize];
+    for (int i = 0; i < elements.length; i++) {
+      System.arraycopy(elements[i], 0, newArr[i], 0, elements[0].length);
+    }
+    elements = newArr;
+    newArr = null;
+  }
+
+  /** 
+   * Add values. If there is not enough space for the number of values to be 
+   * added the array is continually grown until there is room.
+   * @param values Source array.
+   * @param start index in the source array to start at
+   * @param num number of values to add.
+   */
+  
+  public void add(float[][] values, int start, int num) {
+
+    int spaceLeft = this.elements[0].length - size;
+    while (spaceLeft < num) {
+      grow();
+      spaceLeft = this.elements[0].length - size;
+    }
+
+    for (int i = 0; i < this.dim; i++) {
+      System.arraycopy(values[i], start, this.elements[i], size, num);
+    }
+    size += num;
+  }
+
+  /**
+   * Reference to the backing data array. Any changes made to this array will be 
+   * reflected in the actual data.
+   */
+  public float[][] elements() {
+    return elements;
+  }
+
+  /**
+   * Add values. If there is not enough space for the number of values to be 
+   * added the array is continually grown until there is room.
+   * @param values Source array.
+   */
+  public void add(float[][] values) {
+    add(values, 0, values[0].length);
+  }
+
+  /** 
+   * Set a value.
+   * @param i dimension index of the values to set
+   * @param j tuple index of the value to set
+   * @param val the value
+   */
+  public void set(int i, int j, float val) {
+    assert i < dim;
+    assert j < size;
+    elements[i][j] = val;
+  }
+
+  /**
+   * Get a tuple. 
+   * @param idx The index of the tuple
+   * @return an array of the tuple values at the provided index.
+   */
+  public float[] get(int idx) {
+    assert idx >= 0 && idx < size;
+    float[] point = new float[dim()];
+    for (int i = 0; i < dim(); i++) {
+      point[i] = elements[i][idx];
+    }
+    return point;
+  }
+
+  /**
+   * Get a value.
+   * @param i dimension index
+   * @param j tuple index.
+   */
+  public float get(int i, int j) {
+    assert i < elements.length;
+    assert i < size;
+    return elements[i][j];
+  }
+
+  /**
+   * Get contained data as array. The size of the array is exactly the size of the
+   * data. 
+   * <p>
+   * NOTE: This is an expensive operation.
+   */
+  public float[][] toArray() {
+    float[][] ret = new float[dim][size];
+    for (int i = 0; i < dim; i++) {
+      System.arraycopy(elements[i], 0, ret[i], 0, size);
+    }
+    return ret;
+  }
+
+  /**
+   * Get the size of the valid data.
+   */
+  public int size() {
+    return size;
+  }
+
+  /**
+   * Get the dimension of the tuple.
+   */
+  public int dim() {
+    return dim;
+  }
+}
diff --git a/visad/util/FormFileFilter.java b/visad/util/FormFileFilter.java
new file mode 100644
index 0000000..f7b4e33
--- /dev/null
+++ b/visad/util/FormFileFilter.java
@@ -0,0 +1,80 @@
+//
+// FormFileFilter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+import visad.data.FormFileInformer;
+
+/**
+ * A file filter based on a file form adapter's isThisType(String) method,
+ * for use with a JFileChooser.
+ */
+public class FormFileFilter extends FileFilter
+  implements java.io.FileFilter, Comparable
+{
+
+  // -- Fields --
+
+  /** Associated file form implementing the FormFileAdapter interface. */
+  private FormFileInformer form;
+
+  /** Description. */
+  private String desc;
+
+
+  // -- Constructors --
+
+  /** Constructs a new filter that accepts the given extension. */
+  public FormFileFilter(FormFileInformer form, String description) {
+    this.form = form;
+    desc = description;
+  }
+
+
+  // -- FileFilter API methods --
+
+  /** Accepts files with the proper extensions. */
+  public boolean accept(File f) {
+    if (f.isDirectory()) return true;
+    return form.isThisType(f.getPath());
+  }
+
+  /** return the filter's description */
+  public String getDescription() {
+    return desc;
+  }
+
+
+  // -- Comparable API methods --
+
+  /** Compares two FileFilter objects alphanumerically. */
+  public int compareTo(Object o) {
+    return desc.compareTo(((FileFilter) o).getDescription());
+  }
+
+}
diff --git a/visad/util/GMCWidget.java b/visad/util/GMCWidget.java
new file mode 100644
index 0000000..69f5ccf
--- /dev/null
+++ b/visad/util/GMCWidget.java
@@ -0,0 +1,236 @@
+//
+// GMCWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+/* AWT packages */
+import java.awt.Color;
+import java.awt.Dimension;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+/* JFC packages */
+import javax.swing.*;
+
+/* RMI classes */
+import java.rmi.RemoteException;
+
+/* VisAD packages */
+import visad.*;
+
+/** A widget that allows users to control graphics mode parameters.<P> */
+public class GMCWidget extends JPanel implements ActionListener,
+                                                 ItemListener,
+                                                 ControlListener {
+
+  /** This GMCWidget's associated control */
+  GraphicsModeControl control;
+
+  JCheckBox scale;
+  JCheckBox point;
+  JCheckBox texture;
+  JTextField lineWidth;
+  JTextField pointSize;
+
+  float gmcLineWidth;
+  float gmcPointSize;
+
+  /** Constructs a GMCWidget linked to the GraphicsModeControl gmc */
+  public GMCWidget(GraphicsModeControl gmc) {
+    control = gmc;
+
+    // create JPanels
+    JPanel top = new JPanel();
+    JPanel bot = new JPanel();
+
+    // set up layouts
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    top.setLayout(new BoxLayout(top, BoxLayout.X_AXIS));
+    bot.setLayout(new BoxLayout(bot, BoxLayout.X_AXIS));
+
+    // auto-detect values from control if possible
+    boolean s = false;
+    boolean p = false;
+    boolean t = false;
+    if (control != null) {
+      s = control.getScaleEnable();
+      p = control.getPointMode();
+      t = control.getTextureEnable();
+    }
+    gmcLineWidth = 1.0f;
+    gmcPointSize = 1.0f;
+    if (control != null) {
+      gmcLineWidth = control.getLineWidth();
+      gmcPointSize = control.getPointSize();
+    }
+
+    // construct JComponents
+    scale = new JCheckBox("Enable scale", s);
+    point = new JCheckBox("Point mode", p);
+    texture = new JCheckBox("Texture mapping", t);
+    lineWidth = new JTextField(PlotText.shortString(gmcLineWidth));
+
+    // WLH 2 Dec 98
+    Dimension msize = lineWidth.getMaximumSize();
+    Dimension psize = lineWidth.getPreferredSize();
+    msize.height = psize.height;
+    lineWidth.setMaximumSize(msize);
+
+    pointSize = new JTextField(PlotText.shortString(gmcPointSize));
+
+    // WLH 2 Dec 98
+    msize = pointSize.getMaximumSize();
+    psize = pointSize.getPreferredSize();
+    msize.height = psize.height;
+    pointSize.setMaximumSize(msize);
+
+    JLabel lwLabel = new JLabel("Line width:");
+    JLabel psLabel = new JLabel("Point size:");
+
+    // set label colors
+    lwLabel.setForeground(Color.black);
+    psLabel.setForeground(Color.black);
+
+    // add listeners
+    scale.addItemListener(this);
+    point.addItemListener(this);
+    texture.addItemListener(this);
+    lineWidth.addActionListener(this);
+    lineWidth.setActionCommand("line");
+    pointSize.addActionListener(this);
+    pointSize.setActionCommand("point");
+    control.addControlListener(this);
+
+    // lay out JComponents
+    top.add(scale);
+    top.add(point);
+    top.add(texture);
+    bot.add(lwLabel);
+    bot.add(lineWidth);
+    bot.add(psLabel);
+    bot.add(pointSize);
+    add(top);
+    add(bot);
+  }
+
+  /** Handles JTextField changes */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    if (cmd.equals("line")) {
+      float lw = Float.NaN;
+      try {
+        lw = Float.valueOf(lineWidth.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        lineWidth.setText(PlotText.shortString(gmcLineWidth));
+      }
+      if (lw == lw && control != null) {
+        try {
+          control.setLineWidth(lw);
+          gmcLineWidth = lw;
+          scale.requestFocus();
+        }
+        catch (VisADException exc) {
+          lineWidth.setText(PlotText.shortString(gmcLineWidth));
+        }
+        catch (RemoteException exc) {
+          lineWidth.setText(PlotText.shortString(gmcLineWidth));
+        }
+      }
+    }
+    else if (cmd.equals("point")) {
+      float ps = Float.NaN;
+      try {
+        ps = Float.valueOf(pointSize.getText()).floatValue();
+      }
+      catch (NumberFormatException exc) {
+        pointSize.setText(PlotText.shortString(gmcPointSize));
+      }
+      if (ps == ps && control != null) {
+        try {
+          control.setPointSize(ps);
+          gmcPointSize = ps;
+          scale.requestFocus();
+        }
+        catch (VisADException exc) {
+          pointSize.setText(PlotText.shortString(gmcPointSize));
+        }
+        catch (RemoteException exc) {
+          pointSize.setText(PlotText.shortString(gmcPointSize));
+        }
+      }
+    }
+  }
+
+  /** Handles JCheckBox changes */
+  public void itemStateChanged(ItemEvent e) {
+    if (control != null) {
+      Object o = e.getItemSelectable();
+      boolean on = (e.getStateChange() == ItemEvent.SELECTED);
+      try {
+        if (o == scale) control.setScaleEnable(on);
+        else if (o == point) control.setPointMode(on);
+        else if (o == texture) control.setTextureEnable(on);
+      }
+      catch (VisADException exc) { }
+      catch (RemoteException exc) { }
+    }
+  }
+
+  /** Handles GraphicsModeControl changes */
+  public void controlChanged(ControlEvent e) {
+    if (control == null) {
+      return;
+    }
+
+    if (control.getScaleEnable() != scale.isSelected()) {
+      scale.setSelected(control.getScaleEnable());
+    }
+    if (control.getPointMode() != point.isSelected()) {
+      point.setSelected(control.getPointMode());
+    }
+    if (control.getTextureEnable() != texture.isSelected()) {
+      texture.setSelected(control.getTextureEnable());
+    }
+
+    float val;
+
+    val = control.getLineWidth();
+    if (!Util.isApproximatelyEqual(val, gmcLineWidth)) {
+      lineWidth.setText(PlotText.shortString(val));
+      gmcLineWidth = val;
+    }
+    val = control.getPointSize();
+    if (!Util.isApproximatelyEqual(val, gmcPointSize)) {
+      pointSize.setText(PlotText.shortString(val));
+      gmcPointSize = val;
+    }
+  }
+}
+
diff --git a/visad/util/GUIFrame.java b/visad/util/GUIFrame.java
new file mode 100644
index 0000000..4b4b70e
--- /dev/null
+++ b/visad/util/GUIFrame.java
@@ -0,0 +1,181 @@
+//
+// GUIFrame.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.Toolkit;
+import java.awt.event.*;
+import java.lang.reflect.*;
+import java.util.Hashtable;
+import javax.swing.*;
+
+/** A general-purpose frame for simplifing GUI construction and management. */
+public class GUIFrame extends JFrame implements ActionListener {
+
+  /** key mask for use with keyboard shortcuts on this operating system */
+  public static final int MENU_MASK =
+    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
+
+  /** menu bar */
+  protected JMenuBar menubar;
+
+  /** hashtable */
+  protected Hashtable hash;
+
+  /** heavyweight flag */
+  protected boolean heavy;
+
+  /** constructs a GUIFrame */
+  public GUIFrame() {
+    this(false);
+  }
+
+  /** constructs a GUIFrame with light- or heavy-weight menus as specified */
+  public GUIFrame(boolean heavyweight) {
+    menubar = new JMenuBar();
+    setJMenuBar(menubar);
+    hash = new Hashtable();
+    heavy = heavyweight;
+  }
+
+  /** gets the JMenu corresponding to the given menu name */
+  public JMenu getMenu(String menu) {
+    // get menu from hashtable
+    JMenu m = (JMenu) hash.get(menu);
+    if (m == null) {
+      m = new JMenu(menu);
+      m.setMnemonic(menu.charAt(0));
+      m.getPopupMenu().setLightWeightPopupEnabled(!heavy);
+      menubar.add(m);
+      hash.put(menu, m);
+    }
+    return m;
+  }
+
+  /** gets the JMenuItem corresponding to the given menu and item name */
+  public JMenuItem getMenuItem(String menu, String item) {
+    // get menu item from hashtable
+    JMenuItem x = (JMenuItem) hash.get(menu + "\n" + item);
+    return x;
+  }
+
+  /** adds the given menu item to the specified menu */
+  public JMenuItem addMenuItem(String menu, String item, String command,
+    char mnemonic)
+  {
+    JMenuItem x = new JMenuItem(item);
+    addMenuItem(menu, x, command, mnemonic, true);
+    return x;
+  }
+
+  /** adds the given menu item to the specified menu */
+  public JMenuItem addMenuItem(String menu, String item, String command,
+    char mnemonic, boolean enabled)
+  {
+    JMenuItem x = new JMenuItem(item);
+    addMenuItem(menu, x, command, mnemonic, enabled);
+    return x;
+  }
+
+  /** adds the given menu item to the specified menu */
+  public void addMenuItem(String menu, JMenuItem item, String command,
+    char mnemonic, boolean enabled)
+  {
+    // add menu item to menu
+    JMenu m = getMenu(menu);
+    item.setMnemonic(mnemonic);
+    item.setActionCommand(command);
+    item.addActionListener(this);
+    item.setEnabled(enabled);
+    m.add(item);
+    hash.put(menu + "\n" + item.getText(), item);
+  }
+
+  /** adds the given sub-menu to the specified menu */
+  public JMenu addSubMenu(String menu, String sub, char mnemonic) {
+    JMenu x = new JMenu(sub);
+    addSubMenu(menu, x, mnemonic, true);
+    return x;
+  }
+
+  /** adds the given sub-menu to the specified menu */
+  public JMenu addSubMenu(String menu, String sub, char mnemonic,
+    boolean enabled)
+  {
+    JMenu x = new JMenu(sub);
+    addSubMenu(menu, x, mnemonic, enabled);
+    return x;
+  }
+
+  /** adds the given sub-menu to the specified menu */
+  public void addSubMenu(String menu, JMenu sub, char mnemonic,
+    boolean enabled)
+  {
+    // add sub-menu to menu
+    JMenu m = getMenu(menu);
+    sub.setMnemonic(mnemonic);
+    sub.getPopupMenu().setLightWeightPopupEnabled(!heavy);
+    sub.setEnabled(enabled);
+    m.add(sub);
+    hash.put(sub.getText(), sub);
+  }
+
+  /** adds a separator to the specified menu */
+  public void addMenuSeparator(String menu) {
+    JMenu m = getMenu(menu);
+    m.addSeparator();
+  }
+
+  /** sets the keyboard shortcut for the given menu item */
+  public void setMenuShortcut(String menu, String item, int keycode) {
+    JMenuItem jmi = getMenuItem(menu, item);
+    if (jmi == null) return;
+    jmi.setAccelerator(KeyStroke.getKeyStroke(keycode, MENU_MASK));
+  }
+
+  /** handles menu item actions */
+  public void actionPerformed(ActionEvent e) {
+    // convert command name to method
+    String command = e.getActionCommand();
+    Method method = null;
+    try {
+      method = getClass().getMethod(command, (Class[]) null);
+    }
+    catch (NoSuchMethodException exc) { exc.printStackTrace(); }
+    catch (SecurityException exc) { exc.printStackTrace(); }
+
+    // execute the method
+    if (method != null) {
+      try {
+        method.invoke(this, (Object[]) null);
+      }
+      catch (IllegalAccessException exc) { exc.printStackTrace(); }
+      catch (IllegalArgumentException exc) { exc.printStackTrace(); }
+      catch (InvocationTargetException exc) { exc.printStackTrace(); }
+    }
+  }
+
+}
diff --git a/visad/util/HersheyFont.java b/visad/util/HersheyFont.java
new file mode 100644
index 0000000..7607a6d
--- /dev/null
+++ b/visad/util/HersheyFont.java
@@ -0,0 +1,522 @@
+//
+// HersheyFont.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*****************************************************************************/
+//
+//  Copyright (c) James P. Buzbee 1996
+//  House Blend Software
+//
+//  jbuzbee at nyx.net
+//  Version 1.1 Dec 11 1996
+//  Version 1.2 Sep 18 1997
+//  Version 1.3 Feb 28 1998
+//  Version 1.4 Aug 13 2000 : J++ bug workaround by  Paul Emory Sullivan
+//  Version 1.x Sep 20 2001 - adapted to visad/PlotText usage
+//
+// Permission to use, copy, modify, and distribute this software
+// for any use is hereby granted provided
+// this notice is kept intact within the source file
+// This is freeware, use it as desired !
+//
+// Very loosly based on code with authors listed as :
+// Alan Richardson, Pete Holzmann, James Hurt
+/*****************************************************************************/
+
+package visad.util;
+
+import java.io. *;
+import java.net.URL;
+
+/**
+HersheyFont supports the Hershey Fonts for VisAD.
+Adapted from the original code by Buzbee (see source)
+*/
+public class HersheyFont {
+
+   private final static int MAX_CHARACTERS = 256;
+   private final static int MAX_POINTS = 300;
+   protected final static int X = 0;
+   protected final static int Y = 1;
+   private String copyright = "Copyright (c) James P. Buzbee Mar 30, 1996";
+   protected String name;
+   protected char characterVectors[][][] = new char[MAX_CHARACTERS][2][MAX_POINTS];
+   protected int numberOfPoints[] = new int[MAX_CHARACTERS];
+   protected int characterMinX[];
+   protected int characterMaxX[];
+   protected int characterSetMinY;
+   protected int characterSetMaxY;
+   protected int characterSetMinX;
+   protected int characterSetMaxX;
+   protected int charactersInSet;
+   protected boolean fixedWidth = false;
+
+   /** Default constructor.  Use the font 'futural'
+    *
+    * Supplied fonts are:
+    *   futural, timesr, timesrb, cursive, futuram, rowmans,
+    *   rowmant, meteorology
+    *
+    * All other fonts supplied by Bizbee are available at the
+    * VisAD web site.
+    *
+    * More info at: http://batbox.org/font.html
+    *
+    */
+   public HersheyFont() {
+     this("futural");
+   }
+
+   /**
+    * Get a Hershey Font by name.  This constructor form will
+    * add a .jhf extension to the name given and read it as a
+    * Resourse from the visad/util directory.  
+    *
+    * Supplied fonts are:
+    *   futural, timesr, timesrb, cursive, futuram, rowmans,
+    *   rowmant, meteorology
+    *
+    * All other fonts supplied by Bizbee are available at the
+    * VisAD web site.
+    *
+    * @param fontName name of the Hershey font to use
+    * 
+    */
+   public HersheyFont (String fontName)
+   {
+      name = fontName;
+      try
+      {
+         // open the font file
+         //InputStream fontStream = new FileInputStream (fontName);
+         String fn = fontName+".jhf";
+
+         InputStream fontStream = HersheyFont.class.getResourceAsStream(fn);
+         // load the font file
+         LoadHersheyFont (fontName, fontStream);
+         // close the font file
+         fontStream.close ();
+      }
+      catch (Exception e)
+      {
+         System.out.println ("Error processing HersheyFont named "+fontName+": "+e);
+      }
+      return;
+   }
+
+
+   /**
+    * Get a Hershey Font by URL and name.  You must give the
+    * complete filename (e.g., futural.jhf) as well as the URL
+    *
+    * @param base is the base URL of the file
+    * @param fontName the name of the fontfile (include .jhf extension)
+    * 
+    */
+   public HersheyFont (URL base, String fontName)
+   {
+      name = fontName;
+      try
+      {
+         // open the font file
+         InputStream fontStream = new URL (base, fontName).openStream ();
+         // load the font file
+         LoadHersheyFont (fontName, fontStream);
+         // close the font file
+         fontStream.close ();
+      }
+      catch (Exception e)
+      {
+         System.out.println ("Error processing font "+fontName+": "+e);
+      }
+      return;
+   }
+
+
+   /**
+    * Get a Hershey Font by URL.  You must give the
+    * complete filename (e.g., futural.jhf) as part of the URL
+    *
+    * @param base is the URL of the file (e.g.,
+    * http://www.ssec.wisc.edu/visad/futural.jhf)
+    * 
+    */
+   public HersheyFont (URL base)
+   {
+      name = base.toString ();
+      try
+      {
+         // open the font file
+         InputStream fontStream = base.openStream ();
+         // load the font file
+         LoadHersheyFont (name, fontStream);
+         // close the font file
+         fontStream.close ();
+      }
+      catch (Exception e)
+      {
+         System.out.println ("Error processing font "+name+": "+e);
+      }
+      return;
+   }
+   
+   /**
+    * See if o is equal to this
+    * @return true if they have the same name
+    */
+   public boolean equals(Object o) {
+	   if (!(o instanceof HersheyFont)) {
+		   return false;
+	   }
+	   HersheyFont that = (HersheyFont) o;
+	   return this.name.equals(that.name);
+   }
+   
+   /**
+    * Get the hashcode
+    * @return the hashcode
+    */
+   public int hasCode() {
+	   return name.hashCode();
+   }
+
+
+   /** get the maximum number of points (segments) allowed
+   *
+   * @return value of max number of segments allowed.
+   *
+   */
+   public int getMaxPoints() {
+     return MAX_POINTS;
+   }
+   /** get the minimum X coordinate values for all characters
+   *
+   * @return array of minimum X coordinates
+   *
+   */
+   public int[] getCharacterMinX () { return characterMinX; }
+
+   /** get the maximum X coordinate values for all characters
+   *
+   * @return array of maximum X coordinates
+   *
+   */
+   public int[] getCharacterMaxX () { return characterMaxX; }
+
+   /** get the minimum Y coordiante value for all characters
+   *
+   * @return minimum Y coordinate value for all characters in thie font
+   *
+   */
+   public int getCharacterSetMinY() { return characterSetMinY; }
+
+
+   /** get the maximum Y coordiante value for all characters
+   *
+   * @return maximum Y coordinate value for all characters in thie font
+   *
+   */
+   public int getCharacterSetMaxY() { return characterSetMaxY; }
+
+   /** get the minimum X coordiante value for all characters
+   *
+   * @return minimum X coordinate value for all characters in thie font
+   *
+   */
+   public int getCharacterSetMinX() { return characterSetMinX; }
+
+   /** get the maximum X coordiante value for all characters
+   *
+   * @return maximum X coordinate value for all characters in thie font
+   *
+   */
+   public int getCharacterSetMaxX() { return characterSetMaxX; }
+
+   /** get the minimum Y coordiante value for all characters
+   *
+   * @return minimum Y coordinate value for all characters in thie font
+   *
+   */
+   public int getCharactersInSet() { return charactersInSet; }
+
+   /** get the number of points for the i-th character
+   *
+   * @param i the index of the character
+   *
+   * @return the number of data points for this character
+   *
+   */
+   public int getNumberOfPoints(int i) {
+     if (i < characterVectors.length) {
+       return (numberOfPoints[i]); 
+     } else {
+       return 0;
+     }
+
+   }
+
+   /** get the vector of X and Y coordinates for the i-th character
+   *
+   * @param i the index of the character
+
+   * @return array[2][number_points] for this character
+   *
+   */
+   public char[][] getCharacterVector(int i) {
+     if (i < characterVectors.length) {
+       return (characterVectors[i]); 
+     } else {
+       return null;
+     }
+
+   }
+
+   /** set whether this font is 'fixed width' or not (not =
+   *   proportional spacing)
+   *
+   *   Right now, only the 'wmo' font is defaulted to fixed.
+   */
+   public void setFixedWidth(boolean fw) {
+     fixedWidth = fw;
+     return;
+   }
+   /** indicate whether this is a fixed-width font
+   *
+   * @return true if name indicates fixed with (wmo...)
+   *
+   */
+   public boolean getFixedWidth() {
+     fixedWidth = false;
+     if (name.toLowerCase().startsWith("wmo")) fixedWidth = true;
+      // others?
+     return fixedWidth;
+   }
+
+   /** indicate whether this is a cursive font
+   *
+   * @return true if name contains "cursive"
+   *
+   */
+   public boolean getIsCursive() {
+     return ( (name.toLowerCase().indexOf("cursive") != -1) );
+   }
+
+   private int getInt (InputStream file, int n) throws IOException
+   {
+      if (file == null) return (-1);
+
+      char[] buf;
+      int c;
+      int j = 0;
+        buf = new char[n];
+      // for the specified number of characters
+      for (int i = 0; i < n; i++)
+      {
+         c = file.read ();
+         // get character and discard spare newlines
+         while ((c == '\n') || (c == '\r'))
+         {
+            c = file.read ();
+         }
+         // if we hit end of file
+         if (c == -1)
+         {
+            // return an error
+            return (c);
+         }
+         // if this is not a blank
+         if ((char) c != ' ')
+         {
+            // save the character
+            buf[j++] = (char) c;
+         }
+      }
+      // return the decimal equivilent of the string
+      return (Integer.parseInt (String.copyValueOf (buf, 0, j)));
+   }
+/******************************************************************************/
+/***********************************************************/
+   private int fontAdjustment (String fontname)
+   {
+      int xadjust = 0;
+      // if we do not have a script type font
+      if (fontname.indexOf ("scri") < 0 )
+      {
+         // if we have a gothic font
+         if (fontname.indexOf ("goth") >= 0)
+         {
+            xadjust = 2;
+         }
+         else
+         {
+            xadjust = 3;
+         }
+      }
+      return (xadjust);
+   }
+
+/******************************************************************************/
+   private void LoadHersheyFont (String fontname, InputStream fontStream)
+   {
+      if (fontStream == null) return;
+      int character, n;
+      int c;
+      int xadjust = fontAdjustment (fontname);
+        try
+      {
+         // loop through the characters in the file ...
+         character = 0;
+         // while we have not processed all of the characters
+         while (true)
+         {
+            // if we cannot read the next field
+            if (getInt (fontStream, 5) < 1)
+            {
+               // we are done, set the font specification for num chars
+               charactersInSet = character;
+               // break the read loop
+               break;
+            }
+            else
+            {
+               // get the number of vertices in this character
+               n = getInt (fontStream, 3);
+               // save it
+               numberOfPoints[character] = n;
+               // read in the vertice coordinates ...
+               for (int i = 0; i < n; i++)
+               {
+                  // if we are at the end of the line
+                  if ((i == 32) || (i == 68) || (i == 104) || (i == 140))
+                  {
+                     // skip the carriage return
+                     fontStream.read ();
+                  }
+                  // get the next character
+                  c = fontStream.read ();
+                  // if this is a return ( we have a DOS style file )
+                  if (c == '\n')
+                  {
+                     // throw it away and get another
+                     c = fontStream.read ();
+                  }
+                  // get the x coordinate
+                  characterVectors[character][X][i] = (char) c;
+                  // read the y coordinate
+                  characterVectors[character][Y][i] = (char) fontStream.read ();
+               }
+               // skip the carriage return
+               fontStream.read ();
+               // increment the character counter
+               character++;
+            }
+         }
+         // determine the size of each character ...
+         characterMinX = new int[charactersInSet];
+         characterMaxX = new int[charactersInSet];
+         // initialize ...
+         characterSetMinY = 1000;
+         characterSetMaxY = -1000;
+         characterSetMinX = 1000;
+         characterSetMaxX = -1000;
+         // loop through each character ( except the space character )
+         for (int j = 1; j < charactersInSet; j++)
+         {
+            // calculate the size
+            calculateCharacterSize (j, xadjust);
+         }
+         // handle the space character - if the 'a' character is defined
+         if (((int) 'a' - (int) ' ') <= charactersInSet)
+         {
+            // make the space character the same size as the 'a'
+            characterMinX[0] = characterMinX[(int) 'a' - (int) ' '];
+            characterMaxX[0] = characterMaxX[(int) 'a' - (int) ' '];
+         }
+         else
+         {
+            // make the space char the same size as the last char
+            characterMinX[0] = characterMinX[charactersInSet - 1];
+            characterMaxX[0] = characterMaxX[charactersInSet - 1];
+         }
+      }
+      catch (IOException e)
+      {
+         System.out.println (e);
+      }
+      return;
+   }
+/*************************************************************************************/
+   protected void calculateCharacterSize (int j, int xadj)
+   {
+      int cx,cy;
+      characterMinX[j] = 1000;
+      characterMaxX[j] = -1000;
+      // for all the vertices in the character
+      for (int i = 1; i < numberOfPoints[j]; i++)
+      {
+        cx = (int) characterVectors[j][X][i];   
+        cy = (int) characterVectors[j][Y][i]; 
+         // if this is not a "skip"
+         if (cx != ' ')
+         {
+            // if this is less than our current minimum
+            if (cx < characterMinX[j])
+            {
+               // save it
+               characterMinX[j] = cx;
+            }
+            if (cx < characterSetMinX) characterSetMinX = cx;
+
+            // if this is greater than our current maximum
+            if ( cx > characterMaxX[j])
+            {
+               // save it
+               characterMaxX[j] = cx;
+            }
+            if (cx > characterSetMaxX) characterSetMaxX = cx;
+
+            // if this is less than our current minimum
+            if (cy < characterSetMinY)
+            {
+               // save it
+               characterSetMinY = cy;
+            }
+            // if this is greater than our current maximum
+            if (cy > characterSetMaxY)
+            {
+               // save it
+               characterSetMaxY = cy;
+            }
+         }
+      }
+      characterMinX[j] -= xadj;
+      characterMaxX[j] += xadj;
+   }
+/******************************************************************************/
+   public String toString()
+   {
+       return( "HersheyFont: "+name );
+   }   
+}
diff --git a/visad/util/ImageHelper.java b/visad/util/ImageHelper.java
new file mode 100644
index 0000000..782c5c7
--- /dev/null
+++ b/visad/util/ImageHelper.java
@@ -0,0 +1,55 @@
+//
+// ImageHelper.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.Image;
+import java.awt.image.ImageObserver;
+
+/** Helper class for monitoring loading images. */
+public class ImageHelper implements ImageObserver {
+
+  /** flags whether image is bad */
+  public boolean badImage = false;
+
+  /** monitor image load */
+  public boolean imageUpdate(Image i, int f, int x, int y, int w, int h) {
+    boolean rtnval = true;
+    if ((f & ABORT) != 0) {
+      badImage = true;
+      rtnval = false;
+    }
+    if ((f & ALLBITS) != 0) {
+      rtnval = false;
+    }
+    if ((f & ERROR) != 0) {
+      badImage = true;
+      rtnval = false;
+    }
+    return rtnval;
+  }
+
+}
diff --git a/visad/util/LabeledColorWidget.java b/visad/util/LabeledColorWidget.java
new file mode 100644
index 0000000..a48908f
--- /dev/null
+++ b/visad/util/LabeledColorWidget.java
@@ -0,0 +1,443 @@
+/*
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.Dimension;
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.rmi.RemoteException;
+
+import visad.ScalarMap;
+import visad.VisADException;
+
+/**
+ * A color widget that allows users to interactively map numeric data to
+ * RGB/RGBA tuples in a <CODE>ScalarMap</CODE>.
+ */
+public class LabeledColorWidget
+  extends JPanel
+  implements ActionListener
+{
+  ColorMapWidget wrappedWidget;
+  private float[][] original;
+  private float[][] grey = null;
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public LabeledColorWidget(ScalarMap smap)
+    throws VisADException, RemoteException
+  {
+    this(smap, null, true);
+  }
+
+  /**
+   * This method is deprecated, since <CODE>min</CODE> and <CODE>max</CODE>
+   * are ignored.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param min Ignored value.
+   * @param max Ignored value.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   *
+   * @deprecated - 'min' and 'max' are ignored
+   */
+  public LabeledColorWidget(ScalarMap smap, float min, float max)
+    throws VisADException, RemoteException
+  {
+    this(smap, null, true);
+  }
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * The initial color table (if non-null)
+   * should be a <CODE>float[resolution][dimension]</CODE>, where
+   * <CODE>dimension</CODE> is either
+   * <CODE>3</CODE> for <CODE>Display.RGB</CODE> or
+   * <CODE>4</CODE> for <CODE>Display.RGB</CODE>) with values
+   * between <CODE>0.0f</CODE> and <CODE>1.0f</CODE>.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param table Initial color lookup table.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public LabeledColorWidget(ScalarMap smap, float[][] table)
+    throws VisADException, RemoteException
+  {
+    this(smap, table, true);
+  }
+
+  /**
+   * This method is deprecated, since <CODE>min</CODE> and <CODE>max</CODE>
+   * are ignored.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param min Ignored value.
+   * @param max Ignored value.
+   * @param table Initial color lookup table.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   *
+   * @deprecated - 'min' and 'max' are ignored
+   */
+  public LabeledColorWidget(ScalarMap smap, float min, float max,
+                            float[][] table)
+    throws VisADException, RemoteException
+  {
+    this(smap, table, true);
+  }
+
+  /**
+   * Construct a <CODE>LabeledColorWidget</CODE> linked to the
+   * color control in the <CODE>ScalarMap</CODE> (which must be to either
+   * <CODE>Display.RGB</CODE> or </CODE>Display.RGBA</CODE> and already
+   * have been added to a <CODE>Display</CODE>).
+   * It will be labeled with the name of the <CODE>ScalarMap</CODE>'s
+   * RealType and linked to the <CODE>ScalarMap</CODE>'s color control.
+   * The range of <CODE>RealType</CODE> values mapped to color is taken
+   * from the <CODE>ScalarMap's</CODE> range - this allows a color widget
+   * to be used with a range of values defined by auto-scaling from
+   * displayed data.
+   *
+   * The initial color table (if non-null)
+   * should be a <CODE>float[resolution][dimension]</CODE>, where
+   * <CODE>dimension</CODE> is either
+   * <CODE>3</CODE> for <CODE>Display.RGB</CODE> or
+   * <CODE>4</CODE> for <CODE>Display.RGB</CODE>) with values
+   * between <CODE>0.0f</CODE> and <CODE>1.0f</CODE>.
+   *
+   * @param smap <CODE>ScalarMap</CODE> to which this widget is bound.
+   * @param in_table Initial color lookup table.
+   * @param update <CODE>true</CODE> if the slider should follow the
+   *               <CODE>ScalarMap</CODE>'s range.
+   *
+   * @exception RemoteException If there is an RMI-related problem.
+   * @exception VisADException If there is a problem initializing the
+   *                           widget.
+   */
+  public LabeledColorWidget(ScalarMap smap, float[][] in_table, boolean update)
+    throws VisADException, RemoteException
+  {
+    this(new ColorMapWidget(smap, in_table, update));
+  }
+
+  /**
+   *
+   */
+  public LabeledColorWidget(ColorMapWidget w)
+  {
+    wrappedWidget = w;
+
+    // save the initial table
+    original = wrappedWidget.copy_table(wrappedWidget.control.getTable());
+
+    setLayout( new BorderLayout(5,5));
+    add("Center",wrappedWidget);
+
+    JPanel buttons = buildButtons();
+    if (buttons != null) {
+      add("South",buttons);
+    }
+  }
+
+  /**
+   * Build "Reset" and "Grey Scale" button panel.
+   *
+   * @return JPanel containing the buttons or <CODE>null</CODE> if
+   *         the wrapped widget's button panel was used.
+   */
+  private JPanel buildButtons()
+  {
+    JButton reset = new JButton("Reset");
+    reset.setActionCommand("reset");
+    reset.addActionListener(this);
+
+    JButton grey = new JButton("Grey Scale");
+    grey.setActionCommand("grey");
+    grey.addActionListener(this);
+
+    boolean newPanel = false;
+    JPanel panel = wrappedWidget.getButtonPanel();
+    if (panel == null) {
+      panel = new JPanel();
+      panel.setLayout(new FlowLayout(FlowLayout.CENTER,5,5));
+      newPanel = true;
+    }
+
+    panel.add(reset, 0);
+    panel.add(grey, 1);
+
+    return (newPanel ? panel : null);
+  }
+
+  /**
+   * Handle button presses.
+   *
+   * @param evt Data from the changed <CODE>JButton</CODE>.
+   */
+  public void actionPerformed(ActionEvent evt)
+  {
+    if (evt.getActionCommand().equals("reset")) {
+      // reset color table to original values
+      try {
+        wrappedWidget.setTable(original);
+      } catch (RemoteException re) {
+      } catch (VisADException ve) {
+      }
+    } else if (evt.getActionCommand().equals("grey")) {
+      // reset color table to grey wedge
+      if (grey == null) {
+        if (original != null && original[0] != null) {
+          final int num = original.length;
+          final int len = original[0].length;
+          grey = new float[num][len];
+          float a = 1.0f / (len - 1.0f);
+          for (int j=0; j<len; j++) {
+            grey[0][j] = grey[1][j] = grey[2][j] = j * a;
+            if (num > 3) {
+              grey[3][j] = 1.0f;
+            }
+          }
+        }
+      }
+
+      if (grey != null) {
+        try {
+          wrappedWidget.setTable(grey);
+        } catch (RemoteException re) {
+        } catch (VisADException ve) {
+        }
+      }
+    }
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.getMaximumSize()</CODE>.
+   *
+   * @return Maximum size in <CODE>Dimension</CODE>.
+   */
+  public Dimension getMaximumSize()
+  {
+    return wrappedWidget.getMaximumSize();
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.setMaximumSize()</CODE>.
+   *
+   * @param size Maximum size.
+   */
+  public void setMaximumSize(Dimension size)
+  {
+    wrappedWidget.setMaximumSize(size);
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.getMinimumSize()</CODE>.
+   *
+   * @return Minimum size in <CODE>Dimension</CODE>.
+   */
+  public Dimension getMinimumSize()
+  {
+    return wrappedWidget.getMinimumSize();
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.setMinimumSize()</CODE>.
+   *
+   * @param size Minimum size.
+   */
+  public void setMinimumSize(Dimension size)
+  {
+    wrappedWidget.setMinimumSize(size);
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.getPreferredSize()</CODE>.
+   *
+   * @return Preferred size in <CODE>Dimension</CODE>.
+   */
+  public Dimension getPreferredSize()
+  {
+    return wrappedWidget.getPreferredSize();
+  }
+
+  /**
+   * Stub routine which calls <CODE>ColorMapWidget.setPreferredSize()</CODE>.
+   *
+   * @param size Preferred size.
+   */
+  public void setPreferredSize(Dimension size)
+  {
+    wrappedWidget.setPreferredSize(size);
+  }
+
+  public BaseRGBMap getBaseMap() { return wrappedWidget.baseMap; }
+  public ColorPreview getPreview() { return wrappedWidget.preview; }
+  public ArrowSlider getSlider() { return wrappedWidget.slider; }
+
+  /**
+   * Use a new table of color values.
+   * If immediate mode is off, changes to the associated color
+   * control are not applied until the Apply button is clicked.
+   *
+   * @param table New color values.
+   */
+  public void setTable(float[][] table) { wrappedWidget.setTableView(table); }
+
+  /**
+   * Gets the widget's current table. If immediate mode is
+   * off, it may not match the linked color control's table.
+   */
+  public float[][] getTable() { return wrappedWidget.getTableView(); }
+
+  public static void main(String[] args)
+  {
+    try {
+      visad.RealType vis = visad.RealType.getRealType("vis");
+      ScalarMap visMap = new ScalarMap(vis, visad.Display.RGBA);
+      visMap.setRange(0.0f, 1.0f);
+
+      visad.RealType ir = visad.RealType.getRealType("ir");
+      ScalarMap irMap = new ScalarMap(vis, visad.Display.RGB);
+      irMap.setRange(0.0f, 1.0f);
+
+      visad.DisplayImpl dpy = new visad.java2d.DisplayImplJ2D("2d");
+      dpy.addMap(visMap);
+      dpy.addMap(irMap);
+
+      javax.swing.JFrame f;
+
+      f = new javax.swing.JFrame("0");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new LabeledColorWidget(visMap));
+      f.pack();
+      f.setVisible(true);
+
+      f = new javax.swing.JFrame("1");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new LabeledColorWidget(visMap, null));
+      f.pack();
+      f.setVisible(true);
+
+      f = new javax.swing.JFrame("!Updated");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new LabeledColorWidget(visMap, null, false));
+      f.pack();
+      f.setVisible(true);
+
+      f = new javax.swing.JFrame("!Immediate");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      ColorMapWidget cmw = new ColorMapWidget(visMap, null, false, false);
+      f.getContentPane().add(new LabeledColorWidget(cmw));
+      f.pack();
+      f.setVisible(true);
+
+      final int num = 3;
+      final int len = 256;
+      float[][] table = new float[num][len];
+      final float step = 1.0f / (len - 1.0f);
+      float total = 1.0f;
+      for (int j=0; j<len; j++) {
+        table[0][j] = table[1][j] = table[2][j] = total;
+        if (num > 3) {
+          table[3][j] = 1.0f;
+        }
+        total -= step;
+      }
+
+      f = new javax.swing.JFrame("Table");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent we) {
+            System.exit(0);
+          }
+        });
+      f.getContentPane().add(new ColorMapWidget(irMap, table));
+      f.pack();
+      f.setVisible(true);
+
+      try { Thread.sleep(5000); } catch (InterruptedException ie) { }
+
+      visMap.setRange(-10.0f, 10.0f);
+    } catch (RemoteException re) {
+      re.printStackTrace();
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+  }
+}
diff --git a/visad/util/LabeledRGBAWidget.java b/visad/util/LabeledRGBAWidget.java
new file mode 100644
index 0000000..1c638dd
--- /dev/null
+++ b/visad/util/LabeledRGBAWidget.java
@@ -0,0 +1,107 @@
+/*
+
+@(#) $Id: LabeledRGBAWidget.java,v 1.18 2001-11-27 22:30:27 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.rmi.RemoteException;
+
+import visad.ScalarMap;
+import visad.VisADException;
+
+/**
+ * A color widget that allows users to interactively map numeric data to
+ * RGBA tuples based on the Vis5D color widget
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.18 $, $Date: 2001-11-27 22:30:27 $
+ * @since Visad Utility Library v0.7.1
+ * @deprecated - use LabeledColorWidget
+ */
+public class LabeledRGBAWidget
+  extends LabeledColorWidget
+{
+  /** this will be labeled with the name of smap's RealType and
+      linked to the ColorAlphaControl in smap;
+      the range of RealType values mapped to color is taken from
+      smap.getRange() - this allows a color widget to be used with
+      a range of values defined by auto-scaling from displayed Data;
+      if smap's range values are not available at the time this
+      constructor is invoked, the LabeledRGBAWidget becomes a
+      ScalarMapListener and sets its range when smap's range is set;
+      the DisplayRealType of smap must be Display.RGBA and should
+      already be added to a Display
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBAWidget(ScalarMap smap)
+    throws VisADException, RemoteException
+  {
+    super(smap);
+  }
+
+  /** this will be labeled with the name of smap's RealType and
+      linked to the ColorAlphaControl in smap;
+      the range of RealType values (min, max) is mapped to color
+      as defined by an interactive color widget;
+      the DisplayRealType of smap must be Display.RGBA and should
+      already be added to a Display
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBAWidget(ScalarMap smap, float min, float max)
+    throws VisADException, RemoteException
+  {
+    super(smap);
+  }
+
+  /** this will be labeled with the name of smap's RealType and
+      linked to the ColorAlphaControl in smap;
+      the range of RealType values (min, max) is mapped to color
+      as defined by an interactive color widget; table initializes
+      the color lookup table, organized as float[TABLE_SIZE][4]
+      with values between 0.0f and 1.0f;
+      the DisplayRealType of smap must be Display.RGBA and should
+      already be added to a Display
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBAWidget(ScalarMap smap, float min, float max,
+                           float[][] table)
+    throws VisADException, RemoteException
+  {
+    super(smap, table);
+  }
+
+  /** construct a LabeledRGBAWidget linked to the ColorAlphaControl
+      in smap (which must be to Display.RGBA), with range of
+      values (min, max), initial color table in format
+      float[TABLE_SIZE][4] with values between 0.0f and 1.0f, and
+      specified auto-scaling min and max behavior
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBAWidget(ScalarMap smap, float min, float max,
+                           float[][] table, boolean update)
+    throws VisADException, RemoteException
+  {
+    super(smap, table, update);
+  }
+}
diff --git a/visad/util/LabeledRGBWidget.java b/visad/util/LabeledRGBWidget.java
new file mode 100644
index 0000000..42f9ed9
--- /dev/null
+++ b/visad/util/LabeledRGBWidget.java
@@ -0,0 +1,107 @@
+/*
+
+@(#) $Id: LabeledRGBWidget.java,v 1.24 2001-11-27 22:30:27 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.rmi.RemoteException;
+
+import visad.ScalarMap;
+import visad.VisADException;
+
+/**
+ * A color widget that allows users to interactively map numeric data to
+ * RGB tuples based on the Vis5D color widget
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.24 $, $Date: 2001-11-27 22:30:27 $
+ * @since Visad Utility Library v0.7.1
+ * @deprecated - use LabeledColorWidget
+ */
+public class LabeledRGBWidget
+  extends LabeledColorWidget
+{
+  /** this will be labeled with the name of smap's RealType and
+      linked to the ColorControl in smap;
+      the range of RealType values mapped to color is taken from
+      smap.getRange() - this allows a color widget to be used with
+      a range of values defined by auto-scaling from displayed Data;
+      if smap's range values are not available at the time this
+      constructor is invoked, the LabeledRGBWidget becomes a
+      ScalarMapListener and sets its range when smap's range is set;
+      the DisplayRealType of smap must be Display.RGB and should
+      already be added to a Display
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBWidget(ScalarMap smap)
+    throws VisADException, RemoteException
+  {
+    super(smap);
+  }
+
+  /** this will be labeled with the name of smap's RealType and
+      linked to the ColorControl in smap;
+      the range of RealType values (min, max) is mapped to color
+      as defined by an interactive color widget;
+      the DisplayRealType of smap must be Display.RGB and should
+      already be added to a Display
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBWidget(ScalarMap smap, float min, float max)
+    throws VisADException, RemoteException
+  {
+    super(smap);
+  }
+
+  /** this will be labeled with the name of smap's RealType and
+      linked to the ColorControl in smap;
+      the range of RealType values (min, max) is mapped to color
+      as defined by an interactive color widget; table initializes
+      the color lookup table, organized as float[TABLE_SIZE][3]
+      with values between 0.0f and 1.0f;
+      the DisplayRealType of smap must be Display.RGB and should
+      already be added to a Display
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBWidget(ScalarMap smap, float min, float max,
+                          float[][] table)
+    throws VisADException, RemoteException
+  {
+    super(smap, table);
+  }
+
+  /** construct a LabeledRGBWidget linked to the ColorControl
+      in map (which must be to Display.RGB), with range of
+      values (min, max), initial color table in format
+      float[TABLE_SIZE][3] with values between 0.0f and 1.0f, and
+      specified auto-scaling min and max behavior
+      @deprecated - use LabeledColorWidget instead
+   */
+  public LabeledRGBWidget(ScalarMap smap, float min, float max,
+                          float[][] table, boolean update)
+    throws VisADException, RemoteException
+  {
+    super(smap, table, update);
+  }
+}
diff --git a/visad/util/McIDASFileFilter.java b/visad/util/McIDASFileFilter.java
new file mode 100644
index 0000000..d64eee3
--- /dev/null
+++ b/visad/util/McIDASFileFilter.java
@@ -0,0 +1,54 @@
+//
+// McIDASFileFilter.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+
+/**
+ * A file filter for McIDAS area and map files, for use with a JFileChooser.
+ *
+ * @deprecated use FormFileFilter (see Util.getVisADFileChooser)
+ */
+public class McIDASFileFilter extends FileFilter {
+  
+  /** construct a new filter for McIDAS AREA files */
+  public McIDASFileFilter() { }
+
+  /** accept files with the proper filename prefix */
+  public boolean accept(File f) {
+    if (f.isDirectory()) return true;
+    String name = f.getName();
+    return name.startsWith("AREA") || name.endsWith("area") ||
+      name.startsWith("OUTL");
+  }
+    
+  /** return the filter's description */
+  public String getDescription() {
+    return "McIDAS area and map files (AREA*, *area, OUTL*)";
+  }
+}
diff --git a/visad/util/PrintActionListener.java b/visad/util/PrintActionListener.java
new file mode 100644
index 0000000..799d6ad
--- /dev/null
+++ b/visad/util/PrintActionListener.java
@@ -0,0 +1,132 @@
+//
+// PrintActionListener.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import visad.*;
+import java.awt.print.*;
+import java.awt.event.*;
+
+/** PrintActionListener is an ActionListener which is used to
+* print the contents of the VisAD DisplayImpl.  A
+* simple way to use this is:
+*
+*<pre>
+*  DisplayImpl di = new DisplayImpl(...);
+*  ...
+*  JButton pb = new JButton("Print Me");
+*  pb.addActionListener(new PrintActionListener(di));
+*</pre>
+*
+*/
+public class PrintActionListener
+     implements ActionListener {
+
+  DisplayImpl display = null;
+  private boolean showDialog=true;
+
+  /** ActionListener for printing the contents of the VisAD display
+  *
+  * @param dim the DisplayImpl to print when the actionPerformed is
+  * called
+  *
+  */
+  public PrintActionListener(DisplayImpl dim) {
+    display = dim;
+  }
+
+  /** set whether the PrintDialog should be used or not
+  *
+  * @param value set to 'true' to use PrintDialog or to
+  *  'false' to supress the dialog and just print.  The
+  *  default is 'true'.
+  *
+  */
+  public void setShowDialog(boolean value) {
+    showDialog = value;
+  }
+
+  /** get the value of the showDialog state
+  *
+  * @return 'true' is PrintDialog will be used; 'false' if
+  *  supressed.
+  *
+  */
+  public boolean getShowDialog() {
+    return showDialog;
+  }
+
+  /**
+   * Set the display to which this action will listen
+   *
+   * @param dim  DisplayImpl to be printed
+   */
+  public void setDisplay(DisplayImpl dim)
+  {
+      display = dim;
+  }
+
+ /**
+  * Return the display
+  *
+  * @return the display associated with this ActionListener
+  */
+ public DisplayImpl getDisplay()
+ {
+     return display;
+ }
+
+ /**
+  *cause the printout of the DisplayImpl; if the dialog is
+  * enabled, it will pop up to solicit information from the
+  * user.  If the dialog is disabled, just print...
+  *
+  * @param e the ActionEvent which is ignored...
+  *
+  */
+  public void actionPerformed(ActionEvent e) {
+
+    Thread t = new Thread() {
+      public void run() {
+
+        PrinterJob printJob = PrinterJob.getPrinterJob();
+        printJob.setPrintable(display.getPrintable());
+        if (!showDialog || (showDialog && printJob.printDialog()) ) {
+          try {
+            printJob.print();
+          }
+          catch (Exception pe) {
+            pe.printStackTrace();
+          }
+        }
+      }
+
+    };
+
+    t.start();
+  }
+
+}
diff --git a/visad/util/ProjWidget.java b/visad/util/ProjWidget.java
new file mode 100644
index 0000000..5fc1e01
--- /dev/null
+++ b/visad/util/ProjWidget.java
@@ -0,0 +1,96 @@
+//
+// ProjWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.Dimension;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.*;
+import java.rmi.RemoteException;
+import visad.*;
+
+/** A widget that allows users to save and restore different projections.<P> */
+public class ProjWidget extends JPanel {
+
+  /** This ProjWidget's associated control */
+  ProjectionControl control;
+
+  JComboBox savedViewList;
+  JButton save;
+
+  double[][] savedViews;
+
+  /** Constructs a ProjWidget linked to the ProjectionControl pc */
+  public ProjWidget(ProjectionControl pc) {
+    control = pc;
+
+    // get initial view from control
+    savedViews = new double[1][];
+    savedViews[0] = control.getMatrix();
+
+    // set up layouts
+    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+
+    // construct JComboBox
+    savedViewList = new JComboBox();
+    savedViewList.setEditable(false);
+    savedViewList.addItem("Position 1");
+    savedViewList.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        int index = savedViewList.getSelectedIndex();
+        try {
+          control.setMatrix(savedViews[index]);
+        }
+        catch (VisADException exc) { }
+        catch (RemoteException exc) { }
+      }
+    });
+
+    // construct JButton
+    save = new JButton("Save");
+    save.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        int num = savedViews.length;
+        double[][] d = new double[num+1][];
+        System.arraycopy(savedViews, 0, d, 0, num);
+        d[num] = control.getMatrix();
+        savedViews = d;
+        savedViewList.addItem("Position " + (num + 1));
+        savedViewList.setSelectedIndex(num);
+      }
+    });
+
+    // lay out JComponents
+    add(savedViewList);
+    add(Box.createRigidArea(new Dimension(5, 0)));
+    add(save);
+  }
+
+}
+
diff --git a/visad/util/RGBAMap.java b/visad/util/RGBAMap.java
new file mode 100644
index 0000000..cd69152
--- /dev/null
+++ b/visad/util/RGBAMap.java
@@ -0,0 +1,68 @@
+/*
+
+@(#) $Id: RGBAMap.java,v 1.11 2000-02-18 20:44:03 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.rmi.RemoteException;
+
+import visad.VisADException;
+
+/**
+ * A simple RGBA colormap with no interpolation between the internally
+ * stored values.  Click and drag with the left mouse button to draw
+ * the color curves. Click with the middle or right mouse button to
+ * alternate between the red, green, blue and alpha curves.
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.11 $, $Date: 2000-02-18 20:44:03 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public class RGBAMap
+  extends BaseRGBMap
+{
+  /** Construct an RGBAMap with the default resolution of 256 */
+  public RGBAMap()
+    throws RemoteException, VisADException
+  {
+    super(true);
+  }
+
+  /** The RGBAMap map is represented internally by an array of
+   * floats
+   * @param resolution the length of the array
+   */
+  public RGBAMap(int resolution)
+    throws RemoteException, VisADException
+  {
+    super(resolution, true);
+  }
+
+  public RGBAMap(float[][] vals)
+    throws RemoteException, VisADException
+  {
+    super(vals != null ? vals : defaultTable(DEFAULT_RESOLUTION, true));
+  }
+}
diff --git a/visad/util/RGBMap.java b/visad/util/RGBMap.java
new file mode 100644
index 0000000..c8dc054
--- /dev/null
+++ b/visad/util/RGBMap.java
@@ -0,0 +1,68 @@
+/*
+
+@(#) $Id: RGBMap.java,v 1.13 2000-02-18 20:44:03 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.rmi.RemoteException;
+
+import visad.VisADException;
+
+/**
+ * A simple RGB colormap with no interpolation between the internally
+ * stored values.  Click and drag with the left mouse button to draw
+ * the color curves. Click with the middle or right mouse button to
+ * alternate between the red, green and blue curves.
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.13 $, $Date: 2000-02-18 20:44:03 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public class RGBMap
+  extends BaseRGBMap
+{
+  /** Construct an RGBMap with the default resolution of 256 */
+  public RGBMap()
+    throws RemoteException, VisADException
+  {
+    super(false);
+  }
+
+  /** The RGBMap map is represented internally by an array of
+   * floats
+   * @param resolution the length of the array
+   */
+  public RGBMap(int resolution)
+    throws RemoteException, VisADException
+  {
+    super(resolution, false);
+  }
+
+  public RGBMap(float[][] vals)
+    throws RemoteException, VisADException
+  {
+    super(vals != null ? vals : defaultTable(DEFAULT_RESOLUTION, false));
+  }
+}
diff --git a/visad/util/RangeSlider.java b/visad/util/RangeSlider.java
new file mode 100644
index 0000000..9203cba
--- /dev/null
+++ b/visad/util/RangeSlider.java
@@ -0,0 +1,49 @@
+//
+// RangeSlider.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import visad.ScalarMap;
+
+/** A slider widget that allows users to select a lower and upper bound. */
+public class RangeSlider extends visad.browser.RangeSlider {
+
+  /** constructs a RangeSlider with the specified range of values */
+  public RangeSlider(String n, float min, float max) {
+    super(n, min, max);
+  }
+
+  /** obtains the name of the specified ScalarMap */
+  static String nameOf(ScalarMap smap) {
+    String n = DEFAULT_NAME;
+    try {
+      n = smap.getScalarName();
+    }
+    catch (Exception exc) { }
+    return n;
+  }
+
+}
diff --git a/visad/util/RangeWidget.java b/visad/util/RangeWidget.java
new file mode 100644
index 0000000..1c49f22
--- /dev/null
+++ b/visad/util/RangeWidget.java
@@ -0,0 +1,150 @@
+//
+// RangeWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.Dimension;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.rmi.RemoteException;
+
+import javax.swing.*;
+
+import visad.*;
+import visad.browser.Convert;
+
+/** A widget that allows users to specify a ScalarMap's range scaling */
+public class RangeWidget extends JPanel implements ActionListener,
+                                                   ScalarMapListener {
+
+  private JTextField dataLow, dataHi;
+
+  private ScalarMap map;
+
+  /** construct a RangeWidget linked to the ScalarMap smap */
+  public RangeWidget(ScalarMap smap) throws VisADException {
+    // verify scalar map
+    map = smap;
+    if (map == null) {
+      throw new VisADException("RangeWidget: ScalarMap cannot be null");
+    }
+    double[] so = new double[2];
+    double[] data = new double[2];
+    double[] display = new double[2];
+    boolean scale = map.getScale(so, data, display);
+    if (!scale) {
+      throw new VisADException("RangeWidget: ScalarMap must have " +
+                               "linearly scalable range");
+    }
+
+    // create JTextFields
+    dataLow = new JTextField();
+    dataHi = new JTextField();
+    updateTextFields(data);
+
+    // limit JTextField heights
+    Dimension msize = dataLow.getMaximumSize();
+    Dimension psize = dataLow.getPreferredSize();
+    msize.height = psize.height;
+    dataLow.setMaximumSize(msize);
+    dataHi.setMaximumSize(msize);
+
+    // create JPanel
+    JPanel p = new JPanel();
+    p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
+    p.add(new JLabel("low: "));
+    p.add(dataLow);
+    p.add(new JLabel(" hi: "));
+    p.add(dataHi);
+
+    // lay out GUI
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    add(new JLabel("Range of " + map.getScalarName() + " mapped to " +
+                   map.getDisplayScalar().getName().substring(7)));
+    add(p);
+
+    // add listeners
+    map.addScalarMapListener(this);
+    dataLow.addActionListener(this);
+    dataHi.addActionListener(this);
+  }
+
+  private void updateTextFields(double[] data) {
+    // do not update range with truncated values
+    dataLow.removeActionListener(this);
+    dataHi.removeActionListener(this);
+
+    if (data[0] < data[1]) {
+      dataLow.setText(Convert.shortString(data[0], Convert.ROUND_DOWN));
+      dataHi.setText(Convert.shortString(data[1], Convert.ROUND_UP));
+    }
+    else {
+      dataLow.setText(Convert.shortString(data[0], Convert.ROUND_UP));
+      dataHi.setText(Convert.shortString(data[1], Convert.ROUND_DOWN));
+    }
+
+    dataLow.addActionListener(this);
+    dataHi.addActionListener(this);
+  }
+
+  private void updateScalarMap(double[] data) {
+    try {
+      map.setRange(data[0], data[1]);
+    }
+    catch (VisADException exc) {
+// exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+// exc.printStackTrace();
+    }
+  }
+
+  /** handle JTextField changes */
+  public void actionPerformed(ActionEvent e) {
+    String cmd = e.getActionCommand();
+    double[] data = new double[2];
+    data[0] = Double.parseDouble(dataLow.getText());
+    data[1] = Double.parseDouble(dataHi.getText());
+// System.out.println("actionPerformed " + data[0] + " " + data[1]);
+    updateScalarMap(data);
+  }
+
+  /** handle ScalarMap changes */
+  public void mapChanged(ScalarMapEvent e) {
+    double[] so = new double[2];
+    double[] data = new double[2];
+    double[] display = new double[2];
+    map.getScale(so, data, display);
+    updateTextFields(data);
+  }
+
+  /**
+   * Don't care about <CODE>ScalarMap</CODE> control changes.
+   */
+  public void controlChanged(ScalarMapControlEvent evt) { }
+}
diff --git a/visad/util/ReflectedUniverse.java b/visad/util/ReflectedUniverse.java
new file mode 100644
index 0000000..20fb49d
--- /dev/null
+++ b/visad/util/ReflectedUniverse.java
@@ -0,0 +1,352 @@
+//
+// ReflectedUniverse.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import visad.VisADException;
+import java.lang.reflect.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * A general-purpose reflection wrapper class.
+ * See visad.data.jai.JAIForm for examples of usage.
+ */
+public class ReflectedUniverse {
+
+  // -- Fields --
+
+  /** Hashtable containing all variables present in the universe. */
+  protected Hashtable variables;
+
+  /** Class loader for imported classes. */
+  protected ClassLoader loader;
+
+  /** Debugging flag. */
+  protected boolean debug;
+
+
+  // -- Constructors --
+
+  /** Constructs a new reflected universe. */
+  public ReflectedUniverse() {
+    this((ClassLoader) null);
+  }
+
+  /**
+   * Constructs a new reflected universe, with the given URLs
+   * representing additional search paths for imported classes
+   * (in addition to the CLASSPATH).
+   */
+  public ReflectedUniverse(URL[] urls) {
+    this(urls == null ? null : new URLClassLoader(urls));
+  }
+
+  /** Constructs a new reflected universe that uses the given class loader. */
+  public ReflectedUniverse(ClassLoader loader) {
+    variables = new Hashtable();
+    this.loader = loader == null ? getClass().getClassLoader() : loader;
+    debug = false;
+  }
+
+
+  // -- Utility methods --
+
+  /**
+   * Returns whether the given object is compatible with the
+   * specified class for the purposes of reflection.
+   */
+  public static boolean isInstance(Class c, Object o) {
+    return (o == null || c.isInstance(o) ||
+      (c == byte.class && o instanceof Byte) ||
+      (c == short.class && o instanceof Short) ||
+      (c == int.class && o instanceof Integer) ||
+      (c == long.class && o instanceof Long) ||
+      (c == float.class && o instanceof Float) ||
+      (c == double.class && o instanceof Double) ||
+      (c == boolean.class && o instanceof Boolean) ||
+      (c == char.class && o instanceof Character));
+  }
+
+
+  // -- API methods --
+
+  /**
+   * Executes a command in the universe. The following syntaxes are valid:
+   * <li>import fully.qualified.package.ClassName
+   * <li>var = new ClassName(param1, ..., paramN)
+   * <li>var.method(param1, ..., paramN)
+   * <li>var2 = var1.method(param1, ..., paramN)
+   * <li>ClassName.method(param1, ..., paramN)
+   * <p>
+   * Important guidelines:
+   * <li>Any referenced class must be imported first using "import".
+   * <li>Variables can be exported from the universe with getVar().
+   * <li>Variables can be imported to the universe with setVar().
+   * <li>Each parameter must be either a variable in the universe
+   *     or a static or instance field (i.e., no nested methods).
+   */
+  public Object exec(String command) throws VisADException {
+    command = command.trim();
+    if (command.startsWith("import ")) {
+      // command is an import statement
+      command = command.substring(7).trim();
+      int dot = command.lastIndexOf(".");
+      String varName = dot < 0 ? command : command.substring(dot + 1);
+      Class c;
+      try {
+        c = Class.forName(command, true, loader);
+      }
+      catch (ClassNotFoundException exc) {
+        if (debug) exc.printStackTrace();
+        throw new VisADException("No such class: " + command);
+      }
+      setVar(varName, c);
+      return null;
+    }
+
+    // get variable where results of command should be stored
+    int eqIndex = command.indexOf("=");
+    String target = null;
+    if (eqIndex >= 0) {
+      target = command.substring(0, eqIndex).trim();
+      command = command.substring(eqIndex + 1).trim();
+    }
+
+    // parse parentheses
+    int leftParen = command.indexOf("(");
+    if (leftParen < 0 || leftParen != command.lastIndexOf("(") ||
+      command.indexOf(")") != command.length() - 1)
+    {
+      throw new VisADException("invalid parentheses");
+    }
+
+    // parse arguments
+    String arglist = command.substring(leftParen + 1);
+    StringTokenizer st = new StringTokenizer(arglist, "(,)");
+    int len = st.countTokens();
+    Object[] args = new Object[len];
+    for (int i=0; i<len; i++) {
+      String arg = st.nextToken().trim();
+      args[i] = getVar(arg);
+    }
+    command = command.substring(0, leftParen);
+
+    Object result;
+    if (command.startsWith("new ")) {
+      // command is a constructor call
+      String className = command.substring(4).trim();
+      Object var = getVar(className);
+      if (!(var instanceof Class)) {
+        throw new VisADException("not a class: " + className);
+      }
+      Class cl = (Class) var;
+
+      // Search for a constructor that matches the arguments. Unfortunately,
+      // calling cl.getConstructor(argClasses) does not work, because
+      // getConstructor() is not flexible enough to detect when the arguments
+      // are subclasses of the constructor argument classes, making a brute
+      // force search through all public constructors necessary.
+      Constructor constructor = null;
+      Constructor[] c = cl.getConstructors();
+      for (int i=0; i<c.length; i++) {
+        Class[] params = c[i].getParameterTypes();
+        if (params.length == args.length) {
+          boolean match = true;
+          for (int j=0; j<params.length; j++) {
+            if (!isInstance(params[j], args[j])) {
+              match = false;
+              break;
+            }
+          }
+          if (match) {
+            constructor = c[i];
+            break;
+          }
+        }
+      }
+      if (constructor == null) {
+        throw new VisADException("No such constructor");
+      }
+
+      // invoke constructor
+      try {
+        result = constructor.newInstance(args);
+      }
+      catch (Exception exc) {
+        if (debug) exc.printStackTrace();
+        throw new VisADException("Cannot instantiate object");
+      }
+    }
+    else {
+      // command is a method call
+      int dot = command.indexOf(".");
+      if (dot < 0) throw new VisADException("syntax error");
+      String varName = command.substring(0, dot).trim();
+      String methodName = command.substring(dot + 1).trim();
+      Object var = getVar(varName);
+      if (var == null) {
+        throw new VisADException("No such variable: " + varName);
+      }
+      Class varClass = var instanceof Class ? (Class) var : var.getClass();
+
+      // Search for a method that matches the arguments. Unfortunately,
+      // calling varClass.getMethod(methodName, argClasses) does not work,
+      // because getMethod() is not flexible enough to detect when the
+      // arguments are subclasses of the method argument classes, making a
+      // brute force search through all public methods necessary.
+      Method method = null;
+      Method[] m = varClass.getMethods();
+      for (int i=0; i<m.length; i++) {
+        if (methodName.equals(m[i].getName())) {
+          Class[] params = m[i].getParameterTypes();
+          if (params.length == args.length) {
+            boolean match = true;
+            for (int j=0; j<params.length; j++) {
+              if (!isInstance(params[j], args[j])) {
+                match = false;
+                break;
+              }
+            }
+            if (match) {
+              method = m[i];
+              break;
+            }
+          }
+        }
+      }
+      if (method == null) {
+        throw new VisADException("No such method: " + methodName);
+      }
+
+      // invoke method
+      try {
+        result = method.invoke(var, args);
+      }
+      catch (Exception exc) {
+        if (debug) exc.printStackTrace();
+        throw new VisADException("Cannot execute method: " + methodName);
+      }
+    }
+
+    // assign result to proper variable
+    if (target != null) setVar(target, result);
+    return result;
+  }
+
+  /** Registers a variable in the universe. */
+  public void setVar(String varName, Object obj) {
+    if (obj == null) variables.remove(varName);
+    else variables.put(varName, obj);
+  }
+
+  /** Registers a variable of primitive type boolean in the universe. */
+  public void setVar(String varName, boolean b) {
+    setVar(varName, new Boolean(b));
+  }
+
+  /** Registers a variable of primitive type byte in the universe. */
+  public void setVar(String varName, byte b) {
+    setVar(varName, new Byte(b));
+  }
+
+  /** Registers a variable of primitive type char in the universe. */
+  public void setVar(String varName, char c) {
+    setVar(varName, new Character(c));
+  }
+
+  /** Registers a variable of primitive type double in the universe. */
+  public void setVar(String varName, double d) {
+    setVar(varName, new Double(d));
+  }
+
+  /** Registers a variable of primitive type float in the universe. */
+  public void setVar(String varName, float f) {
+    setVar(varName, new Float(f));
+  }
+
+  /** Registers a variable of primitive type int in the universe. */
+  public void setVar(String varName, int i) {
+    setVar(varName, new Integer(i));
+  }
+
+  /** Registers a variable of primitive type long in the universe. */
+  public void setVar(String varName, long l) {
+    setVar(varName, new Long(l));
+  }
+
+  /** Registers a variable of primitive type short in the universe. */
+  public void setVar(String varName, short s) {
+    setVar(varName, new Short(s));
+  }
+
+  /**
+   * Returns the value of a variable or field in the universe.
+   * Primitive types will be wrapped in their Java Object wrapper classes.
+   */
+  public Object getVar(String varName) throws VisADException {
+    int dot = varName.indexOf(".");
+    if (dot >= 0) {
+      // get field value of variable
+      Object var = variables.get(varName.substring(0, dot).trim());
+      Class varClass = var instanceof Class ? (Class) var : var.getClass();
+      String fieldName = varName.substring(dot + 1).trim();
+      Field field;
+      try {
+        field = varClass.getField(fieldName);
+      }
+      catch (NoSuchFieldException exc) {
+        if (debug) exc.printStackTrace();
+        throw new VisADException("No such field: " + varName);
+      }
+      Object fieldVal;
+      try {
+        fieldVal = field.get(var);
+      }
+      catch (Exception exc) {
+        if (debug) exc.printStackTrace();
+        throw new VisADException("Cannot get field value: " + varName);
+      }
+      return fieldVal;
+    }
+    else {
+      // get variable
+      Object var = variables.get(varName);
+      return var;
+    }
+  }
+
+  /** Enables or disables extended debugging output. */
+  public void setDebug(boolean debug) {
+    this.debug = debug;
+  }
+
+  /** Gets whether extended debugging output is enabled. */
+  public boolean isDebug() {
+    return debug;
+  }
+
+}
diff --git a/visad/util/ResSwitcher.java b/visad/util/ResSwitcher.java
new file mode 100644
index 0000000..8c6adc1
--- /dev/null
+++ b/visad/util/ResSwitcher.java
@@ -0,0 +1,170 @@
+//
+// ResSwitcher.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.rmi.RemoteException;
+import java.util.Vector;
+import visad.*;
+import visad.data.DefaultFamily;
+import visad.java3d.DisplayImplJ3D;
+
+/**
+ * Handles automatic toggling between a high-resolution representation
+ * and a low-resolution one for a data object.  When the display is
+ * busy, the low-resolution representation is used for faster response
+ * time.  When the display is idle, the high-resolution representation
+ * is used for greater detail.
+ */
+public class ResSwitcher extends DualRes implements ActivityHandler {
+
+  /** Display affected by resolution switcher. */
+  private LocalDisplay display;
+
+  /** High-resolution data renderer toggled by resolution switcher. */
+  private DataRenderer hi_rend;
+  
+  /** Low-resolution data renderer toggled by resolution switcher. */
+  private DataRenderer lo_rend;
+
+  /**
+   * Constructs a resolution switcher for swapping between high- and low-
+   * resolution representations for the referenced data on the given display.
+   */
+  public ResSwitcher(LocalDisplay d, DataReferenceImpl ref)
+    throws VisADException, RemoteException
+  {
+    this(d, ref, null, null);
+  }
+
+  /**
+   * Constructs a resolution switcher for swapping between high- and low-
+   * resolution representations for the referenced data on the given display.
+   *
+   * @param renderer The data renderer to be used for the low-res rendering.
+   * @param cmaps    The ConstantMaps to be used for the low-res rendering.
+   */
+  public ResSwitcher(LocalDisplay d, DataReferenceImpl ref,
+    DataRenderer renderer, ConstantMap[] cmaps)
+    throws VisADException, RemoteException
+  {
+    super(ref);
+    display = d;
+    if (renderer == null) display.addReference(lo_ref, cmaps);
+    else display.addReferences(renderer, lo_ref, cmaps);
+
+    // get data renderers
+    Vector dataRenderers = display.getRendererVector();
+    int len = dataRenderers == null ? 0 : dataRenderers.size();
+    int flags = 0;
+    for (int i=0; i<len && flags!=3; i++) {
+      DataRenderer rend = (DataRenderer) dataRenderers.elementAt(i);
+      DataDisplayLink[] links = rend.getLinks();
+      for (int j=0; j<links.length && flags!=3; j++) {
+        DataReference jref = links[j].getDataReference();
+        if (jref == hi_ref) {
+          hi_rend = rend;
+          flags &= 1;
+        }
+        else if (jref == lo_ref) {
+          lo_rend = rend;
+          flags &= 2;
+        }
+      }
+    }
+
+    display.addActivityHandler(this);
+  }
+
+  /** Unlinks the resolution switcher from its display. */
+  public void unlink() throws VisADException {
+    display.removeActivityHandler(this);
+  }
+
+  /** Swaps in the low-resolution data when the display is busy. */
+  public void busyDisplay(LocalDisplay d) {
+    // switch on low-res data
+    if (lo_rend != null && hi_rend != null) {
+      lo_rend.toggle(true);
+      hi_rend.toggle(false);
+    }
+  }
+
+  /** Swaps in the high-resolution data when the display is idle. */
+  public void idleDisplay(LocalDisplay d) {
+    // switch on hi-res data
+    if (lo_rend != null && hi_rend != null) {
+      hi_rend.toggle(true);
+      lo_rend.toggle(false);
+    }
+  }
+
+  /** Run 'java visad.util.ResSwitcher data_file' to test ResSwitcher. */
+  public static void main(String[] args) throws Exception {
+    if (args.length < 1) {
+      System.out.println("Please specify a datafile on the command line.");
+      return;
+    }
+    DefaultFamily loader = new DefaultFamily("loader");
+    Data d = loader.open(args[0]);
+
+    DisplayImplJ3D display = new DisplayImplJ3D("display");
+    ScalarMap[] maps = d.getType().guessMaps(true);
+    if (maps != null) {
+      for (int i=0; i<maps.length; i++) display.addMap(maps[i]);
+    }
+
+    DataReferenceImpl ref = new DataReferenceImpl("ref");
+    ref.setData(d);
+    display.addReference(ref);
+
+    ResSwitcher rs = new ResSwitcher(display, ref);
+    if (args.length > 1) {
+      try { rs.setResolutionScale(Double.parseDouble(args[1])); }
+      catch (NumberFormatException exc) { }
+    }
+
+    JFrame frame = new JFrame("ResSwitcher test");
+    JPanel p = new JPanel();
+    p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+    p.setAlignmentY(JPanel.TOP_ALIGNMENT);
+    p.setAlignmentX(JPanel.LEFT_ALIGNMENT);
+    frame.setContentPane(p);
+    p.add(display.getComponent());
+
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        System.exit(0);
+      }
+    });
+
+    frame.pack();
+    frame.show();
+  }
+
+}
diff --git a/visad/util/SaveStringTokenizer.java b/visad/util/SaveStringTokenizer.java
new file mode 100644
index 0000000..c71c2a4
--- /dev/null
+++ b/visad/util/SaveStringTokenizer.java
@@ -0,0 +1,102 @@
+//
+// SaveStringTokenizer.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.util.*;
+
+/**
+ * Parses a save string containing a series of lines of the form:
+ * <dd><tt>keyword = value</tt>
+ */
+public class SaveStringTokenizer {
+
+  /**
+   * List of keywords found in the save string.
+   */
+  public String[] keywords;
+
+  /**
+   * List of values corresponding to the keywords in the save string.
+   */
+  public String[] values;
+
+  /**
+   * Constructs a new save string tokenizer from the given save string.
+   */
+  public SaveStringTokenizer(String save) {
+    StringTokenizer st = new StringTokenizer(save, "\n\r");
+    Vector v = new Vector();
+    while (st.hasMoreTokens()) v.add(st.nextToken().trim());
+
+    // parse keywords and values from save string
+    Vector keys = new Vector();
+    Vector vals = new Vector();
+    int numLines = v.size();
+    int t = 0;
+    boolean done = false;
+    while (true) {
+      // get next meaningful line
+      String line = null;
+      int eq = -1;
+      while (eq < 0) {
+        // search for a non-comment line that contains an equals sign
+        if (t == numLines) {
+          done = true;
+          break;
+        }
+        line = (String) v.elementAt(t++);
+        if (line.charAt(0) != '#') eq = line.indexOf('=');
+      }
+      if (done) break;
+      keys.add(line.substring(0, eq).trim());
+
+      // get remainder of information after the equals sign
+      String value = line.substring(eq + 1).trim();
+      String nextLine = (t < numLines ? (String) v.elementAt(t) : null);
+      boolean moreLines = false;
+      while (nextLine != null && nextLine.indexOf('=') < 0) {
+        if (nextLine.length() > 0 && !nextLine.startsWith("#")) {
+          value = value + '\n';
+          value = value + '\n' + nextLine;
+          moreLines = true;
+        }
+        nextLine = (t < numLines - 1 ? (String) v.elementAt(++t) : null);
+      }
+      if (moreLines) value = value + '\n';
+      vals.add(value);
+    }
+
+    int len = keys.size();
+    keywords = new String[len];
+    values = new String[len];
+    for (int i=0; i<len; i++) {
+      keywords[i] = (String) keys.elementAt(i);
+      values[i] = (String) vals.elementAt(i);
+    }
+  }
+
+}
diff --git a/visad/util/SceneGraphInspector.java b/visad/util/SceneGraphInspector.java
new file mode 100644
index 0000000..cc650e2
--- /dev/null
+++ b/visad/util/SceneGraphInspector.java
@@ -0,0 +1,386 @@
+package visad.util;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Arrays;
+import java.util.Enumeration;
+
+import javax.media.j3d.Bounds;
+import javax.media.j3d.BranchGroup;
+import javax.media.j3d.Canvas3D;
+import javax.media.j3d.Geometry;
+import javax.media.j3d.GeometryArray;
+import javax.media.j3d.Group;
+import javax.media.j3d.Node;
+import javax.media.j3d.OrderedGroup;
+import javax.media.j3d.Shape3D;
+import javax.media.j3d.Switch;
+import javax.media.j3d.Text3D;
+import javax.media.j3d.Transform3D;
+import javax.media.j3d.TransformGroup;
+import javax.media.j3d.View;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JToggleButton;
+import javax.swing.JTree;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.TreeSelectionModel;
+
+import visad.java3d.DisplayRendererJ3D;
+
+public class SceneGraphInspector extends JPanel implements TreeSelectionListener {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = 1L;
+
+  public static void show(DisplayRendererJ3D renderer) {
+    
+    JFrame frame = new JFrame("VisAD SceneGraph Inspector");
+    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+    frame.add(new SceneGraphInspector(renderer));
+    frame.setSize(800, 480);
+    frame.setVisible(true);
+    
+  }
+  
+  private static String makeName(Object obj) {
+    String name = obj.getClass().getSimpleName();
+
+    if (obj instanceof Node) {
+      Node node = (Node) obj;
+      String nodeName = Util.getName(node);
+      if (nodeName != null && nodeName.length() > 0) {
+        name = nodeName;
+      }
+    }
+    else if (obj instanceof String) {
+      return (String) obj;
+    }
+      
+    return name + "@" + obj.hashCode();
+  }
+
+  class MyNode<T> extends DefaultMutableTreeNode {
+    
+    private static final long serialVersionUID = 1L;
+    private T node;
+    
+    public MyNode(T node) {
+      this.node = node;
+    }
+    
+    public T getNode() {
+      return node;
+    }
+    
+    public String getName() {
+      return makeName(node);
+    }
+    
+    public String toString() {
+      return getName();
+    }
+    
+  }
+  
+  private JTree tree;
+  private JPanel cards;
+  
+  public SceneGraphInspector(DisplayRendererJ3D renderer) {
+    
+    cards = new JPanel();
+    cards.setLayout(new CardLayout());
+    
+    MyNode<String> top = new MyNode<String>("ROOT");
+
+    View view = renderer.getView();
+    MyNode<View> viewNode = new MyNode<View>(view);
+    createViewNodes(viewNode);
+    top.add(viewNode);
+    
+    MyNode<Node> scene = new MyNode<Node>(renderer.getRoot());
+    createSceneNodes(scene);
+    top.add(scene);
+  
+    tree = new JTree(top);
+    tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+    tree.addTreeSelectionListener(this);
+    
+    Dimension minimumSize = new Dimension(300, 200);
+    JScrollPane treeView = new JScrollPane(tree);
+    treeView.setMinimumSize(minimumSize);
+    
+    
+    JSplitPane split = new JSplitPane(
+        JSplitPane.HORIZONTAL_SPLIT,
+        treeView, 
+        cards
+    );
+    setLayout(new BorderLayout());
+    add(split, BorderLayout.CENTER);
+    
+    for (int i = 0; i < tree.getRowCount(); i++) {
+      tree.expandRow(i);
+    }
+  }
+  
+  private void createViewNodes(MyNode<View> viewNode) {
+    
+    cards.add(makeViewComponent(viewNode.getNode()), viewNode.getName());
+    Enumeration<Canvas3D> canvases = viewNode.getNode().getAllCanvas3Ds();
+    while (canvases.hasMoreElements()) {
+      Canvas3D canvas = canvases.nextElement();
+      MyNode<Canvas3D> canvasNode = new MyNode<Canvas3D>(canvas);
+      viewNode.add(canvasNode);
+      cards.add(makeCanvasComponent(canvas), canvasNode.getName());
+    }
+  }
+  
+  private void createSceneNodes(MyNode<Node> scene) {
+    
+    Node node = scene.getNode();
+    cards.add(makeNodeComponent(node), scene.getName());
+    if (node instanceof Group) {
+      Group group = (Group) node;
+      Enumeration<Node> children = group.getAllChildren();
+      while (children.hasMoreElements()) {
+        MyNode<Node> tnode = new MyNode<Node>(children.nextElement());
+        scene.add(tnode);
+        createSceneNodes(tnode);
+      }
+    }
+    
+  }
+  
+  private JPanel makeViewComponent(final View view) {
+    
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.add(new JLabel("Class: " + view.getClass().getName()));
+    panel.add(new JLabel("AntiAliasing: " + view.getSceneAntialiasingEnable()));
+    int x = view.getViewPolicy();
+    panel.add(new JLabel("ViewPolicy: " + (x == View.HMD_VIEW ? "HMD_VIEW" : "SCREEN_VIEW")));
+    String visPolicy = "";
+    switch (view.getVisibilityPolicy()) {
+    case View.VISIBILITY_DRAW_ALL: 
+      visPolicy = "VISIBILITY_DRAW_ALL";
+      break;
+    case View.VISIBILITY_DRAW_INVISIBLE:
+      visPolicy = "VISIBILITY_DRAW_INVISIBLE";
+      break;
+    case View.VISIBILITY_DRAW_VISIBLE:
+      visPolicy = "VISIBILITY_DRAW_VISIBLE";
+      break;
+    }
+    panel.add(new JLabel("VisibilityPolicy: " + visPolicy));
+    panel.add(new JLabel("FrameNumber: " + view.getFrameNumber()));
+    panel.add(new JLabel("ViewRunning: " + view.isViewRunning()));
+    panel.add(new JLabel("BehaviorSchedulerRunning: " + view.isBehaviorSchedulerRunning()));
+    
+    final JButton startView = new JButton("Start View");
+    startView.setEnabled(!view.isViewRunning());
+    final JButton stopView = new JButton("Stop View");
+    startView.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        view.startView();
+        startView.setEnabled(!view.isViewRunning());
+        stopView.setEnabled(view.isViewRunning());
+      }
+    });
+    stopView.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        view.stopView();
+        startView.setEnabled(!view.isViewRunning());
+        stopView.setEnabled(view.isViewRunning());
+      }
+    });
+    JPanel subPanel = new JPanel();
+    subPanel.add(startView);
+    subPanel.add(stopView);
+    panel.add(subPanel);
+    
+    final JButton startBehav = new JButton("Start BehaviorScheduler");
+    startView.setEnabled(!view.isViewRunning());
+    final JButton stopBehav = new JButton("Stop BehaviorScheduler");
+    startView.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        view.startBehaviorScheduler();
+        stopBehav.setEnabled(!view.isViewRunning());
+        startBehav.setEnabled(view.isViewRunning());
+      }
+    });
+    stopView.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        view.stopBehaviorScheduler();
+        stopBehav.setEnabled(!view.isViewRunning());
+        startBehav.setEnabled(view.isViewRunning());
+      }
+    });
+    subPanel = new JPanel();
+    subPanel.add(startView);
+    subPanel.add(stopView);
+    panel.add(subPanel);
+    
+    return panel;
+  }
+  
+  private JPanel makeCanvasComponent(final Canvas3D canvas) {
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    panel.add(new JLabel("Class: " + canvas.getClass().getName()));
+    panel.add(new JLabel("Name: " + canvas.getName()));
+   
+    panel.add(new JLabel("DoubleBufferEnabled: " + canvas.getDoubleBufferEnable()));
+    panel.add(new JLabel("Height: " + canvas.getHeight()));
+    panel.add(new JLabel("Width: " + canvas.getWidth()));
+    panel.add(new JLabel("IgnoreRepaint: " + canvas.getIgnoreRepaint()));
+    panel.add(new JLabel("MousePosition: " + canvas.getMousePosition()));
+   
+    final JButton startRend = new JButton("Start Renderer");
+    startRend.setEnabled(!canvas.isRendererRunning());
+    final JButton stopRend = new JButton("Stop Renderer");
+    startRend.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        canvas.startRenderer();
+        startRend.setEnabled(!canvas.isRendererRunning());
+        stopRend.setEnabled(canvas.isRendererRunning());
+      }
+    });
+    stopRend.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent evt) {
+        canvas.stopRenderer();
+        startRend.setEnabled(!canvas.isRendererRunning());
+        stopRend.setEnabled(canvas.isRendererRunning());
+      }
+    });
+    JPanel subPanel = new JPanel();
+    subPanel.add(startRend);
+    subPanel.add(stopRend);
+    panel.add(subPanel);
+    
+    return panel;
+  }
+  
+  private JPanel makeNodeComponent(final Node node) {
+    
+    final Group parent = (Group) node.getParent();
+    JPanel panel = new JPanel();
+    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+    
+    panel.add(new JLabel("Name: " + makeName(node)));
+    panel.add(new JLabel("Class: " + node.getClass().getName()));
+    panel.add(new JLabel("Live: " + node.isLive()));
+    panel.add(new JLabel("Compiled: " + node.isCompiled()));
+    panel.add(new JLabel("UserData: " + node.getUserData()));
+    panel.add(new JLabel("Pickable: " + node.getPickable()));
+    panel.add(new JLabel("BoundsAutoCompute: " + node.getBoundsAutoCompute()));
+    Bounds bounds = node.getBounds();
+    if (bounds != null) {
+      panel.add(new JLabel("Bounds.isEmpty: " + bounds.isEmpty()));
+    }
+    
+    if (node instanceof Group) {
+      Group group = (Group) node;
+      panel.add(new JLabel("NumChildren: " + group.numChildren()));
+      
+      if ((node instanceof BranchGroup) && parent != null &&
+          group.getCapability(BranchGroup.ALLOW_DETACH)) {
+        final JToggleButton button = new JToggleButton("Detach");
+        button.setSelected(true);
+        button.addItemListener(new ItemListener(){
+          public void itemStateChanged(ItemEvent e) {
+            if (e.getStateChange() == ItemEvent.SELECTED) {
+              parent.addChild(node);
+              button.setText("Detach");
+            } else if (e.getStateChange() == ItemEvent.DESELECTED) {
+              ((BranchGroup) node).detach(); 
+              button.setText("Attach");
+            }
+          }
+        });
+        panel.add(button);
+      } else if (node instanceof OrderedGroup) {
+        OrderedGroup ogroup = (OrderedGroup) node;
+        panel.add(new JLabel("Order: " + Arrays.toString(ogroup.getChildIndexOrder())));
+        
+      } else if (node instanceof TransformGroup) {
+        TransformGroup tgroup = (TransformGroup) node;
+        Transform3D trans = new Transform3D();
+        tgroup.getTransform(trans);
+        panel.add(new JLabel("TransformMatrix: " + trans.toString()));
+      }
+    
+      if (node instanceof Switch) {
+        final Switch swich = (Switch) node;
+        String[] indexes = new String[swich.numChildren() + 1];
+        indexes[0] = "None";
+        for (int i = 1; i < indexes.length; i++) {
+          indexes[i] = "" + (i - 1);
+        }
+        JComboBox comboBox = new JComboBox(indexes);
+        int selected = swich.getWhichChild();
+        if (selected == Switch.CHILD_NONE) {
+          selected = 0;
+        }
+        comboBox.setSelectedIndex(swich.getWhichChild() + 1);
+        comboBox.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+             JComboBox cBox = (JComboBox) e.getSource();
+             int selected = cBox.getSelectedIndex();
+             if (selected == 0) {
+               selected = Switch.CHILD_NONE;
+             } else {
+               selected -= 1;
+             }
+             swich.setWhichChild(selected);
+          }
+        });
+        panel.add(comboBox);
+      }
+      
+    } else {
+      
+      if (node instanceof Shape3D) {
+        Shape3D shape = (Shape3D) node;
+        Geometry geo = shape.getGeometry();
+        if (geo != null) {
+          panel.add(new JLabel("Geometry: " + geo.getClass().getSimpleName()));
+          if (geo instanceof GeometryArray) {
+            GeometryArray arr = (GeometryArray) geo;
+            panel.add(new JLabel("VertexCount: " + arr.getVertexCount()));
+          } else if (geo instanceof Text3D) {
+            Text3D txt = (Text3D) geo;
+            panel.add(new JLabel("String: \"" + txt.getString()+ "\""));
+          }
+        }
+      }
+    }
+    
+    return panel;
+  }
+
+  @Override
+  public void valueChanged(TreeSelectionEvent evt) {
+    
+    MyNode tnode = (MyNode) evt.getPath().getLastPathComponent();
+    Object node = tnode.getNode();
+    CardLayout cl = (CardLayout) cards.getLayout();
+    cl.show(cards, tnode.getName());
+  }
+  
+}
diff --git a/visad/util/SelectRangeWidget.java b/visad/util/SelectRangeWidget.java
new file mode 100644
index 0000000..63a02b7
--- /dev/null
+++ b/visad/util/SelectRangeWidget.java
@@ -0,0 +1,226 @@
+//
+// SelectRangeWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+/* RMI classes */
+import java.rmi.RemoteException;
+
+/* VisAD packages */
+import visad.*;
+
+/** A slider widget that allows users to select a lower and upper bound.<P> */
+public class SelectRangeWidget extends RangeSlider
+                               implements ScalarMapListener,
+                                          ControlListener
+{
+
+  /** This SelectRangeWidget's associated Control. */
+  private RangeControl rangeControl;
+
+  /** this will be labeled with the name of smap's RealType, and
+      the range of RealType values defining the bounds of the
+      selectable range is taken from smap.getRange(). This allows
+      a SelectRangeWidget to be used with a range of values defined
+      by auto-scaling from displayed Data. If smap's range values
+      are not available at the time this constructor is invoked,
+      the SelectRangeWidget becomes a ScalarMapListener and sets
+      its range when smap's range is set.
+      The DisplayRealType of smap must be Display.SelectRange and
+      should already be added to a Display. */
+  public SelectRangeWidget(ScalarMap smap) throws VisADException,
+                                                  RemoteException {
+    this(smap, true);
+  }
+
+  /** this will be labeled with the name of smap's RealType, and
+      the range of RealType values (min, max) defines the
+      bounds of the selectable range.
+      The DisplayRealType of smap must be Display.SelectRange and
+      should already be added to a Display.
+      @deprecated - set range in map instead
+  */
+  public SelectRangeWidget(ScalarMap smap, float min, float max)
+                           throws VisADException, RemoteException {
+    this(smap, true);
+    if (min == min || max == max) {
+      System.err.println("Warning:  SelectRangeWidget initial range " +
+                         " values ignored");
+    }
+  }
+
+  /** construct a SelectRangeWidget linked to the Control
+      in the map (which must be to Display.SelectRange), with
+      range of values (min, max) and specified auto-scaling behavior.
+      @deprecated - set range in map instead
+  */
+  public SelectRangeWidget(ScalarMap smap, float min, float max,
+         boolean update) throws VisADException, RemoteException {
+    this(smap, true);
+    if (min == min || max == max) {
+      System.err.println("Warning:  SelectRangeWidget initial range " +
+                         " values ignored");
+    }
+  }
+
+  /** this will be labeled with the name of smap's RealType, and
+      the range of RealType values defining the bounds of the
+      selectable range is taken from smap.getRange(). This allows
+      a SelectRangeWidget to be used with a range of values defined
+      by auto-scaling from displayed Data. If smap's range values
+      are not available at the time this constructor is invoked,
+      the SelectRangeWidget becomes a ScalarMapListener and sets
+      its range when smap's range is set.
+      The DisplayRealType of smap must be Display.SelectRange and
+      should already be added to a Display. */
+  public SelectRangeWidget(ScalarMap smap, boolean update)
+    throws VisADException, RemoteException {
+    super(RangeSlider.nameOf(smap), 0.0f, 1.0f);
+
+    // verify scalar map
+    if (!Display.SelectRange.equals(smap.getDisplayScalar())) {
+      throw new DisplayException("SelectRangeWidget: ScalarMap must " +
+                                 "be to Display.SelectRange");
+    }
+
+    // copy range from ScalarMap
+    double[] smapRange = smap.getRange();
+    float[] wr = widenRange((float) smapRange[0], (float) smapRange[1]);
+    resetValues(wr[0], wr[1]);
+    // resetValues((float )smapRange[0], (float )smapRange[1]); // HERE
+
+    // get range control
+    rangeControl = (RangeControl) smap.getControl();
+
+    // enable auto-scaling
+    if (update) smap.addScalarMapListener(this);
+    else setBounds(minLimit, maxLimit);
+
+    // listen for changes to the control
+    rangeControl.addControlListener(this);
+
+    // set slider values to match control's values
+    float[] range = rangeControl.getRange();
+    if (range == null) {
+      range = new float[2];
+      range[0] = minLimit;
+      range[1] = maxLimit;
+    }
+    if (range[0] != range[0] || range[1] != range[1]) {
+      range[0] = minLimit;
+      range[1] = maxLimit;
+    }
+    setValues(range[0], range[1]);
+  }
+
+  /** Update control and graphical widget components. */
+  private void updateWidget(float min, float max) throws VisADException,
+                                                 RemoteException {
+    rangeControl.setRange(new float[] {min, max});
+    float[] wr = widenRange(min, max);
+    setBounds(wr[0], wr[1]);
+    // setBounds(min, max); // HERE
+  }
+
+  private float[] widenRange(float lo, float hi) {
+    float newLo = lo;
+    float newHi = hi;
+
+    float widen = 0.001f * (hi - lo);
+    if (Math.abs(widen) < 0.0001f) {
+      return new float[] {lo - widen, hi + widen};
+    }
+
+    if( ( hi - lo ) > 0. ) {
+      newLo = (float)Math.floor( lo );
+      newHi = (float)Math.ceil( hi );
+    }
+    else {
+      newLo = (float)Math.ceil( lo );
+      newHi = (float)Math.floor( hi );
+    }
+    return new float[] { newLo, newHi };
+  }
+
+  /** ScalarMapListener method used with delayed auto-scaling. */
+  public void mapChanged(ScalarMapEvent e) {
+    ScalarMap s = e.getScalarMap();
+    double[] range = s.getRange();
+    try {
+      float r0 = (float) range[0];
+      float r1 = (float) range[1];
+
+      if (minValue != minValue || !Util.isApproximatelyEqual(r0, minValue) ||
+          maxValue != maxValue || !Util.isApproximatelyEqual(r1, maxValue))
+      {
+        updateWidget(r0, r1);
+      }
+    }
+    catch (VisADException exc) { }
+    catch (RemoteException exc) { }
+  }
+
+  /**
+   * ScalarMapListener method used to detect new control.
+   */
+  public void controlChanged(ScalarMapControlEvent evt)
+    throws RemoteException, VisADException
+  {
+    int id = evt.getId();
+    if (rangeControl != null && (id == ScalarMapEvent.CONTROL_REMOVED ||
+                                 id == ScalarMapEvent.CONTROL_REPLACED))
+    {
+      rangeControl.removeControlListener(this);
+    }
+
+    if (id == ScalarMapEvent.CONTROL_REPLACED ||
+        id == ScalarMapEvent.CONTROL_ADDED)
+    {
+      rangeControl = (RangeControl )(evt.getScalarMap().getControl());
+      controlChanged(new ControlEvent(rangeControl));
+      rangeControl.addControlListener(this);
+    }
+  }
+
+  /** tell parent when the value changes */
+  public void valuesUpdated() {
+    try {
+      rangeControl.setRange(new float[] {minValue, maxValue});
+    }
+    catch (VisADException exc) { }
+    catch (RemoteException exc ) { }
+  }
+
+
+  /** ControlListener method for RangeControl */
+  public void controlChanged(ControlEvent e)
+    throws VisADException, RemoteException
+  {
+    float[] range = rangeControl.getRange();
+    setValues(range[0], range[1]);
+  }
+}
+
diff --git a/visad/util/SimpleColorMapWidget.java b/visad/util/SimpleColorMapWidget.java
new file mode 100644
index 0000000..c6be005
--- /dev/null
+++ b/visad/util/SimpleColorMapWidget.java
@@ -0,0 +1,290 @@
+/*
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.rmi.RemoteException;
+
+import javax.swing.BoxLayout;
+import javax.swing.JPanel;
+
+import visad.BaseColorControl;
+import visad.Control;
+import visad.DisplayException;
+import visad.VisADException;
+
+/**
+ * A "simple" color widget that allows users to interactively map numeric
+ * data to RGB/RGBA color maps.
+ */
+public class SimpleColorMapWidget
+  extends JPanel
+{
+  BaseRGBMap baseMap;
+  ColorPreview preview;
+  ArrowSlider slider;
+
+  private SliderLabel label;
+
+  /**
+   * Construct a <CODE>SimpleColorMapWidget</CODE>.
+   *
+   * The initial color table (if non-null)
+   * should be a <CODE>float[resolution][dimension]</CODE>, where
+   * <CODE>dimension</CODE> is either
+   * <CODE>3</CODE> for <CODE>Display.RGB</CODE> or
+   * <CODE>4</CODE> for <CODE>Display.RGB</CODE>) with values
+   * between <CODE>0.0f</CODE> and <CODE>1.0f</CODE>.
+   *
+   * @param name Name used for slider label.
+   * @param in_table Initial table of color values.
+   * @param min Minimum value for slider.
+   * @param max Maximum value for slider.
+   *
+   * @deprecated Use SimpleColorMapWidget(String, BaseColorControl, float, float)
+   */
+  public SimpleColorMapWidget(String name, float[][] in_table,
+                              float min, float max)
+    throws RemoteException, VisADException
+  {
+    float[][] table;
+    if (in_table != null && in_table[0] != null &&
+        in_table.length >= 3 && in_table.length <= 4 &&
+        in_table[0].length > 4)
+    {
+      table = table_reorg(in_table);
+    } else {
+      table = in_table;
+    }
+
+    if (table != null && (table[0] == null || table[0].length < 3 ||
+                          table[0].length > 4))
+    {
+      throw new VisADException("Bad initial table");
+    }
+
+    if (table == null) {
+      baseMap = new BaseRGBMap(false);
+    } else {
+      baseMap = new BaseRGBMap(table);
+    }
+
+    finishInit(name, min, max);
+  }
+
+  /**
+   * Construct a <CODE>SimpleColorMapWidget</CODE>.
+   *
+   * The initial color table (if non-null)
+   * should be a <CODE>float[resolution][dimension]</CODE>, where
+   * <CODE>dimension</CODE> is either
+   * <CODE>3</CODE> for <CODE>Display.RGB</CODE> or
+   * <CODE>4</CODE> for <CODE>Display.RGB</CODE>) with values
+   * between <CODE>0.0f</CODE> and <CODE>1.0f</CODE>.
+   *
+   * @param name Name used for slider label.
+   * @param ctl Control to which this widget is attached.
+   * @param min Minimum value for slider.
+   * @param max Maximum value for slider.
+   */
+  public SimpleColorMapWidget(String name, Control ctl,
+                              float min, float max)
+    throws RemoteException, VisADException
+  {
+    if (ctl == null) {
+      throw new DisplayException(getClass().getName() + ": Null control");
+    }
+
+    if (!(ctl instanceof BaseColorControl)) {
+      throw new DisplayException(getClass().getName() + ": Control must " +
+                                 "be BaseColorControl, not " +
+                                 ctl.getClass().getName());
+    }
+
+    baseMap = new BaseRGBMap((BaseColorControl )ctl);
+
+    finishInit(name, min, max);
+  }
+
+  private void finishInit(String name, float min, float max)
+  {
+    // set up user interface
+    preview = new ColorPreview(baseMap);
+    slider = new ArrowSlider(min, max, (min + max) / 2, name);
+    label = new SliderLabel(slider);
+
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+
+    // set min/max bounds for slider
+    updateSlider(min, max);
+
+    rebuildGUI();
+  }
+
+  void rebuildGUI()
+  {
+    removeAll();
+    add(baseMap);
+    add(preview);
+    add(slider);
+    add(label);
+  }
+
+  /**
+   * Internal convenience routine used to update the slider.
+   *
+   * @param min Minimum value for slider.
+   * @param max Maximum value for slider.
+   */
+  void updateSlider(float min, float max)
+  {
+    float val = slider.getValue();
+    if (val != val || val <= min || val >= max) {
+      val = (min + max) / 2;
+    }
+    slider.setBounds(min, max, val);
+  }
+
+  public BaseRGBMap getBaseMap() { return baseMap; }
+  public ColorPreview getPreview() { return preview; }
+  public ArrowSlider getSlider() { return slider; }
+
+  /**
+   * Use a new table of color values.
+   *
+   * @param table New color values.
+   */
+  public void setTable(float[][] table)
+    throws RemoteException, VisADException
+  {
+    float[][] newTable = copy_table(table);
+    baseMap.setValues(newTable);
+  }
+
+  /**
+   * Utility routine used to make a copy of a 2D <CODE>float</CODE> array.
+   *
+   * @param table Table to copy.
+   *
+   * @return The new copy.
+   */
+  static float[][] copy_table(float[][] table)
+  {
+    if (table == null || table[0] == null) {
+      return null;
+    }
+
+    final int dim = table.length;
+    int len = table[0].length;
+
+    float[][] new_table = new float[dim][len];
+    try {
+      for (int i=0; i<dim; i++) {
+        System.arraycopy(table[i], 0, new_table[i], 0, len);
+      }
+      return new_table;
+    } catch (ArrayIndexOutOfBoundsException obe) {
+      return null;
+    }
+  }
+
+  /**
+   * Utility routine used convert a table with dimensions <CODE>[X][Y]</CODE>
+   * to a table with dimensions <CODE>[Y][X]</CODE>.
+   *
+   * @param table Table to reorganize.
+   *
+   * @return The reorganized table.
+   */
+  static float[][] table_reorg(float[][] table)
+  {
+    if (table == null || table[0] == null) {
+      return null;
+    }
+
+    final int dim = table.length;
+    int len = table[0].length;
+
+    float[][] out = new float[len][dim];
+    try {
+      for (int i=0; i<len; i++) {
+        out[i][0] = table[0][i];
+        out[i][1] = table[1][i];
+        out[i][2] = table[2][i];
+        if (dim > 3) {
+          out[i][3] = table[3][i];
+        }
+      }
+    } catch (ArrayIndexOutOfBoundsException obe) {
+      out = null;
+    }
+
+    return out;
+  }
+
+  public static void main(String[] args)
+    throws RemoteException, VisADException
+  {
+    try {
+      javax.swing.JFrame f;
+      SimpleColorMapWidget simple;
+
+      f = new javax.swing.JFrame("Empty SimpleColorMapWidget");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent e) {
+            System.exit(0);
+          }
+        });
+      simple = new SimpleColorMapWidget("Foo", (float [][])null, 0.0f, 1.0f);
+      f.getContentPane().add(simple);
+      f.pack();
+      f.setVisible(true);
+
+      final int num = 4;
+      final int len = 256;
+      float[][] table = new float[num][len];
+      final float step = 1.0f / (len - 1.0f);
+      float total = 1.0f;
+      for (int j=0; j<len; j++) {
+        table[0][j] = table[1][j] = table[2][j] = total;
+        if (num > 3) {
+          table[3][j] = 1.0f;
+        }
+        total -= step;
+      }
+
+      f = new javax.swing.JFrame("Full SimpleColorMapWidget");
+      f.addWindowListener(new java.awt.event.WindowAdapter() {
+          public void windowClosing(java.awt.event.WindowEvent e) {
+            System.exit(0);
+          }
+        });
+      simple = new SimpleColorMapWidget("Foo", table, 0.0f, 1.0f);
+      f.getContentPane().add(simple);
+      f.pack();
+      f.setVisible(true);
+    } catch (VisADException ve) {
+      ve.printStackTrace();
+    }
+  }
+}
diff --git a/visad/util/Slider.java b/visad/util/Slider.java
new file mode 100644
index 0000000..ddb556c
--- /dev/null
+++ b/visad/util/Slider.java
@@ -0,0 +1,114 @@
+/*
+
+@(#) $Id: Slider.java,v 1.7 2000-04-19 18:37:20 billh Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+/**
+ * An abstract class which is very similar to java.awt.ScrollBar, except using
+ * all floating point values and having an internal name.
+ *
+ * Although the interface has nothing to do with orientation, a horizontal
+ * orientation will be assumed by several other classes
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.7 $, $Date: 2000-04-19 18:37:20 $
+ * @since Visad Utility Library v0.7.1
+ */
+
+public abstract class Slider extends JPanel {
+
+  /**
+   * The internal name of the slider, accessed through getName()
+   * @see #getName
+   */
+  protected String name;
+
+  /** Get the internal name for this slider */
+  public String getName() {
+    return name;
+  }
+
+
+  /** Return the minimum value of this slider */
+  public abstract float getMinimum();
+
+  /** Sets the minimum value for this slider */
+  public abstract void setMinimum(float value);
+
+  /** Return the maximum value of this slider */
+  public abstract float getMaximum();
+
+  /** Sets the maximum value of this scrolbar */
+  public abstract void setMaximum(float value);
+
+  /** Returns the current value of the slider */
+  public abstract float getValue();
+
+  /** Sets the current value of the slider */
+  public abstract void setValue(float value);
+
+
+  /** The vector containing the SliderChangeListeners */
+  protected Vector listeners = new Vector();
+  private Object listeners_lock = new Object();
+
+  /** Add a SliderChangeListener to the listeners list */
+  // public synchronized void addSliderChangeListener(SliderChangeListener s) {
+  public void addSliderChangeListener(SliderChangeListener s) {
+    synchronized (listeners_lock) {
+      if (!listeners.contains(s)) {
+        listeners.addElement(s);
+      }
+    }
+  }
+
+  /** Remove a SliderChangeListener from the listeners list */
+  // public synchronized void removeSliderChangeListener(SliderChangeListener s) {
+  public void removeSliderChangeListener(SliderChangeListener s) {
+    synchronized (listeners_lock) {
+      if (listeners.contains(s)) {
+        listeners.removeElement(s);
+      }
+    }
+  }
+
+  /** Notify the ColorChangeListerers that the color widget has changed */
+  // protected synchronized void notifyListeners(SliderChangeEvent e) {
+  protected void notifyListeners(SliderChangeEvent e) {
+    Vector listeners_clone = null;
+    synchronized (listeners_lock) {
+      listeners_clone = (Vector) listeners.clone();
+    }
+    for (int i = 0; i < listeners_clone.size(); i++) {
+      SliderChangeListener s =
+        (SliderChangeListener) listeners_clone.elementAt(i);
+      s.sliderChanged(e);
+    }
+  }
+}
diff --git a/visad/util/SliderChangeEvent.java b/visad/util/SliderChangeEvent.java
new file mode 100644
index 0000000..6f87ef5
--- /dev/null
+++ b/visad/util/SliderChangeEvent.java
@@ -0,0 +1,98 @@
+/*
+
+@(#) $Id: SliderChangeEvent.java,v 1.4 2000-03-14 16:56:48 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+/**
+ * The event that occurs when a slider is changed
+ *
+ * @author Nick Rasmussen
+ * @version $Revision: 1.4 $, $Date: 2000-03-14 16:56:48 $
+ * @since Visad Utility Library v0.7.1
+ */
+
+public class SliderChangeEvent {
+
+  /** The string representation of this event */
+  private String string;
+
+  /** The type of this event */
+  public int type;
+
+  /** The value of this event.  Typically the new value of the field
+   * by mods */
+  private float val;
+
+  /** The constant indicating a value change */
+  public static final int VALUE_CHANGE = 1;
+
+  /** The constant indicating a lower bound change */
+  public static final int LOWER_CHANGE = 2;
+
+  /** The constant indicating an upper bound change */
+  public static final int UPPER_CHANGE = 3;
+
+  /** Construct a new event with the given type and value */
+  SliderChangeEvent(int type, float val) {
+    this(getTypeString(type), type, val);
+    this.type = type;
+    string = new String("SliderChangeEvent: " + string + " value=" + val);
+  }
+
+  /** Construct a new event with the given type, value, and description string */
+  SliderChangeEvent(String string, int type, float val) {
+    this.string = string;
+    this.type = type;
+    this.val = val;
+  }
+
+  /** Return the string representation of a specific type */
+  public static String getTypeString(int type) {
+
+    String result = null;
+
+    switch (type) {
+    case VALUE_CHANGE:
+      result = "VALUE_CHANGE";
+      break;
+    case LOWER_CHANGE:
+      result = "LOWER_CHANGE";
+      break;
+    case UPPER_CHANGE:
+      result = "UPPER_CHANGE";
+      break;
+    default:
+      result = "(unknown type)";
+      break;
+    }
+
+    return result;
+  }
+
+  /** Return a string description of this object */
+  public String toString() {
+    return string;
+  }
+}
diff --git a/visad/util/SliderChangeListener.java b/visad/util/SliderChangeListener.java
new file mode 100644
index 0000000..db59ed4
--- /dev/null
+++ b/visad/util/SliderChangeListener.java
@@ -0,0 +1,43 @@
+/*
+
+@(#) $Id: SliderChangeListener.java,v 1.5 2000-03-14 16:56:48 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.util.EventListener;
+
+/**
+ * The interface that any object that wishes to listen to SliderChangeEvents
+ * must implement
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.5 $, $Date: 2000-03-14 16:56:48 $
+ * @since Visad Utility Library v0.7.1
+ */
+
+public interface SliderChangeListener extends EventListener {
+
+  /** The nessecary function to implement */
+  void sliderChanged(SliderChangeEvent e);
+}
diff --git a/visad/util/SliderLabel.java b/visad/util/SliderLabel.java
new file mode 100644
index 0000000..02c499d
--- /dev/null
+++ b/visad/util/SliderLabel.java
@@ -0,0 +1,250 @@
+/*
+
+@(#) $Id: SliderLabel.java,v 1.12 2000-03-14 17:18:40 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import javax.swing.JPanel;
+
+/**
+ * A label that can be attached to any slider showing the current value,
+ * and optionally, the bounds.
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.12 $, $Date: 2000-03-14 17:18:40 $
+ * @since Visad Utility Library v0.7.1
+ */
+
+public class SliderLabel extends JPanel implements SliderChangeListener {
+
+  /** The slider to which the label is attached */
+  private Slider slider;
+
+  /** The label representing the slider's variable */
+  private String label;
+
+  /** Whether or not the range values are visible */
+  private boolean rangeVisible;
+
+  /** The backround color of the panel */
+  private Color background;
+
+  /** The text color of the panel */
+  private Color text;
+
+  /** widget sizes */
+  Dimension minSize = null;
+  Dimension prefSize = null;
+  Dimension maxSize = null;
+
+  /** Construct a SliderLabel from the given slider */
+  public SliderLabel(Slider slider) {
+    this(slider, slider.getName());
+  }
+
+  /** Construct a SliderLabel with the given background and text colors */
+  public SliderLabel(Slider slider, Color background, Color text) {
+    this(slider, slider.getName(), background, text);
+  }
+
+  /** Construct a SliderLabel with the given label, background and text colors */
+  public SliderLabel(Slider slider, String label, Color background, Color text) {
+    this(slider, label, true, background, text);
+  }
+
+  /** Construct a slider label with the given slider and label */
+  public SliderLabel(Slider slider, String label) {
+    this(slider, label, true);
+  }
+
+  /** Construct a slider label with the given slider, label and range visibility */
+  public SliderLabel(Slider slider, String label, boolean rangeVisible) {
+    this(slider, label, rangeVisible, Color.black, Color.white);
+  }
+
+  /** Construct a slider label with the given slider, label and range visibility */
+  public SliderLabel(Slider slider, String label, boolean rangeVisible,
+                     Color background, Color text) {
+
+    this.slider = slider;
+    this.label = label;
+    this.rangeVisible = rangeVisible;
+    this.background = background;
+    this.text = text;
+
+    slider.addSliderChangeListener(this);
+
+  }
+
+  /** Listen for slider change events */
+  public void sliderChanged(SliderChangeEvent e) {
+    if (e.type != e.VALUE_CHANGE) {
+      rangeChanged = true;
+    }
+    // redraw
+    validate();
+    repaint();
+  }
+
+  private boolean rangeChanged;
+  private String drawmin;
+  private String drawmax;
+  private String drawval;
+
+  /** Update the panel */
+  public void update(Graphics g) {
+
+    FontMetrics fm = g.getFontMetrics();
+
+    if (rangeVisible) {
+      if (rangeChanged) {
+        g.setColor(background);
+        if (drawmin == null) drawmin = "null";
+        if (drawmax == null) drawmax = "null";
+        g.drawString(drawmin, 3, getBounds().height - 1 - fm.getDescent());
+        g.drawString(drawmax, getBounds().width - 4 - fm.stringWidth(drawmax),
+                     getBounds().height - 1 - fm.getDescent());
+        rangeChanged = false;
+      }
+      g.setColor(text);
+
+      String min = Float.toString(slider.getMinimum());
+      g.drawString(min, 3, getBounds().height - 1 - fm.getDescent());
+      drawmin = min;
+
+      String max = Float.toString(slider.getMaximum());
+      g.drawString(max, getBounds().width - 4 - fm.stringWidth(max),
+                   getBounds().height - 1 - fm.getDescent());
+      drawmax = max;
+    }
+
+    g.setColor(background);
+    if (drawval == null) drawval = "null";
+    g.drawString(drawval, getBounds().width / 2 - fm.stringWidth(drawval) / 2 + 3,
+                 getBounds().height - 1 - fm.getDescent());
+
+    g.setColor(text);
+    //String val = new String(label + " = " + (slider.getValue() - (slider.getValue() % 0.01)));
+    String val = new String(label + " = " + (slider.getValue()));
+    g.drawString(val, getBounds().width / 2 - fm.stringWidth(val) / 2 + 3,
+                 getBounds().height - 1 - fm.getDescent());
+
+    drawval = val;
+  }
+
+  /** Draw the panel */
+  public void paint(Graphics g) {
+    g.setColor(background);
+    g.fillRect(0, 0, getBounds().width, getBounds().height);
+
+    g.setColor(text);
+
+    FontMetrics fm = g.getFontMetrics();
+
+    if (rangeVisible) {
+
+      String min = Float.toString(slider.getMinimum());
+      g.drawString(min, 3, getBounds().height - 1 - fm.getDescent());
+      drawmin = min;
+
+      String max = Float.toString(slider.getMaximum());
+      g.drawString(max, getBounds().width - 4 - fm.stringWidth(max),
+                   getBounds().height - 1 - fm.getDescent());
+      drawmax = max;
+    }
+
+    //String val = new String(label + " = " + (slider.getValue() - (slider.getValue() % 0.01)));
+    String val = new String(label + " = " + (slider.getValue()));
+    g.drawString(val, getBounds().width / 2 - fm.stringWidth(val) / 2 + 3,
+                 getBounds().height - 1 - fm.getDescent());
+
+    drawval = val;
+  }
+
+  /** Return the preferred sise of the SliderLabel */
+  public Dimension getPreferredSize() {
+    if (prefSize == null) {
+      prefSize = new Dimension(256, 18);
+    }
+    return prefSize;
+  }
+
+  /** Set the preferred size of the SliderLabel */
+  public void setPreferredSize(Dimension dim) { prefSize = dim; }
+
+  /** Return the maximum size of the SliderLabel */
+  public Dimension getMaximumSize() {
+    if (maxSize == null) {
+      maxSize = new Dimension(Integer.MAX_VALUE, 18);
+    }
+    return maxSize;
+  }
+
+  /** Set the preferred size of the SliderLabel */
+  public void setMaximumSize(Dimension dim) { maxSize = dim; }
+
+  /** Return the minimum size of the SliderLabel */
+  public Dimension getMinimumSize() {
+    if (minSize == null) {
+      minSize = new Dimension(100, 18);
+    }
+    return minSize;
+  }
+
+  /** Set the preferred size of the SliderLabel */
+  public void setMinimumSize(Dimension dim) { minSize = dim; }
+
+  /** for debugging purposes */
+  public static void main(String[] argc) {
+
+    Slider slider = new ArrowSlider();
+    SliderLabel label = new SliderLabel(slider, "test");
+
+    javax.swing.JFrame f;
+    f = new javax.swing.JFrame("Visad Slider Label");
+    f.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+
+    f.setLayout(new BorderLayout());
+    f.add(label, "South");
+    f.add(slider, "North");
+
+    int height = slider.getPreferredSize().height + label.getPreferredSize().height;
+    int width = Math.max(slider.getPreferredSize().width, label.getPreferredSize().height);
+
+    f.setSize(new Dimension(width, height + 27));
+    f.setVisible(true);
+
+  }
+}
diff --git a/visad/util/StepWidget.java b/visad/util/StepWidget.java
new file mode 100644
index 0000000..6cbd304
--- /dev/null
+++ b/visad/util/StepWidget.java
@@ -0,0 +1,231 @@
+//
+// StepWidget.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.plaf.basic.BasicArrowButton;
+
+/**
+ * StepWidget is a slider GUI component with
+ * directional step arrows at either end.
+ */
+public class StepWidget extends JPanel
+  implements ActionListener, ChangeListener
+{
+
+  protected static final boolean DEBUG = true;
+  private static final int BUTTON_WIDTH = 25;
+  private static final int BUTTON_HEIGHT = 25;
+  private static final int GAP = 5;
+  private static final Dimension buttonSize =
+    new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT);
+
+  protected JSlider step;
+  private boolean horiz;
+  protected JButton forward;
+  protected JButton back;
+
+  protected int min = 1;
+  protected int max = 1;
+  protected int cur = 1;
+
+  /** Constructs a StepWidget. */
+  public StepWidget(boolean horizontal) {
+    // determine orientation
+    horiz = horizontal;
+    int mainLayout, subLayout;
+    int backDir, stepDir, forwardDir;
+    Dimension gap;
+    Component glue1, glue2, glue3, glue4;
+    if (horizontal) {
+      mainLayout = BoxLayout.X_AXIS;
+      subLayout = BoxLayout.Y_AXIS;
+      backDir = BasicArrowButton.WEST;
+      stepDir = JSlider.HORIZONTAL;
+      forwardDir = BasicArrowButton.EAST;
+      gap = new Dimension(GAP, 0);
+      glue1 = Box.createVerticalGlue();
+      glue2 = Box.createVerticalGlue();
+      glue3 = Box.createVerticalGlue();
+      glue4 = Box.createVerticalGlue();
+    }
+    else {
+      mainLayout = BoxLayout.Y_AXIS;
+      subLayout = BoxLayout.X_AXIS;
+      backDir = BasicArrowButton.NORTH;
+      stepDir = JSlider.VERTICAL;
+      forwardDir = BasicArrowButton.SOUTH;
+      gap = new Dimension(0, GAP);
+      glue1 = Box.createHorizontalGlue();
+      glue2 = Box.createHorizontalGlue();
+      glue3 = Box.createHorizontalGlue();
+      glue4 = Box.createHorizontalGlue();
+    }
+
+    // create panels
+    JPanel before = new JPanel();
+    JPanel after = new JPanel();
+    setLayout(new BoxLayout(this, mainLayout));
+    before.setLayout(new BoxLayout(before, subLayout));
+    after.setLayout(new BoxLayout(after, subLayout));
+
+    // create components
+    back = new BasicArrowButton(backDir) {
+      public Dimension getPreferredSize() { return buttonSize; }
+      public Dimension getMaximumSize() { return buttonSize; }
+    };
+    step = new JSlider(stepDir, min, max, cur);
+    forward = new BasicArrowButton(forwardDir) {
+      public Dimension getPreferredSize() { return buttonSize; }
+      public Dimension getMaximumSize() { return buttonSize; }
+    };
+    step.setPaintTicks(true);
+    step.setAlignmentX(JButton.LEFT_ALIGNMENT);
+
+    // lay out components
+    add(before);
+    add(Box.createRigidArea(gap));
+    add(step);
+    add(Box.createRigidArea(gap));
+    add(after);
+    before.add(glue1);
+    before.add(back);
+    before.add(glue2);
+    after.add(glue3);
+    after.add(forward);
+    after.add(glue4);
+
+    // listen for GUI events
+    back.addActionListener(this);
+    forward.addActionListener(this);
+    step.addChangeListener(this);
+
+    // disable controls
+    setEnabled(false);
+  }
+
+  /** Returns the minimum size of the widget. */
+  public Dimension getMinimumSize() {
+    Dimension min = getPreferredSize();
+    return horiz ? new Dimension(0, min.height + 4 * GAP) :
+      new Dimension(min.width, 0);
+  }
+
+  /** Returns the maximum size of the widget. */
+  public Dimension getMaximumSize() {
+    Dimension max = getPreferredSize();
+    return horiz ? new Dimension(Integer.MAX_VALUE, max.height + 4 * GAP) :
+      new Dimension(max.width, Integer.MAX_VALUE);
+  }
+
+  /** Gets the current value of the widget. */
+  public int getValue() { return step.getValue(); }
+
+  /** Gets the minimum value of the widget. */
+  public int getMinimum() { return step.getMinimum(); }
+
+  /** Gets the maximum value of the widget. */
+  public int getMaximum() { return step.getMaximum(); }
+
+  /** Sets the current value of the widget. */
+  public void setValue(int cur) {
+    this.cur = cur;
+    step.setValue(cur);
+  }
+
+  /** Enables or disables the widget. */
+  public void setEnabled(boolean enabled) {
+    step.setEnabled(enabled);
+    back.setEnabled(enabled);
+    forward.setEnabled(enabled);
+  }
+
+  /** Sets the minimum, maximum and current values of the slider. */
+  public void setBounds(int min, int max, int cur) {
+    this.min = min;
+    this.max = max;
+    this.cur = cur;
+
+    step.setMinimum(min);
+    step.setMaximum(max);
+    step.setValue(cur);
+
+    int maj;
+    if (max < 4) maj = 1;
+    else if (max < 20) maj = max / 4;
+    else if (max < 30) maj = max / 6;
+    else maj = max / 8;
+
+    step.setMajorTickSpacing(maj);
+    step.setMinorTickSpacing(maj / 4);
+    step.setPaintLabels(true);
+  }
+
+  /** Adds a ChangeListener to the widget. */
+  public void addChangeListener(ChangeListener l) {
+    step.addChangeListener(l);
+  }
+
+  /** Removes a ChangeListener from the widget. */
+  public void removeChangeListener(ChangeListener l) {
+    step.removeChangeListener(l);
+  }
+
+  /** ActionListener method used with JButtons. */
+  public void actionPerformed(ActionEvent e) {
+    boolean direction = (e.getSource() == back);
+    if (horiz == direction) {
+      // move back
+      cur--;
+      if (cur < min) cur = max;
+    }
+    else {
+      // move forward
+      cur++;
+      if (cur > max) cur = min;
+    }
+    step.setValue(cur);
+    updateStep();
+  }
+
+  /** ChangeListener method used with JSlider. */
+  public void stateChanged(ChangeEvent e) {
+    cur = step.getValue();
+    updateStep();
+  }
+
+  /**
+   * Takes action when the slider's current value changes.
+   *
+   * This method is a stub that can be overridden to define behavior.
+   */
+  protected void updateStep() { }
+
+}
diff --git a/visad/util/TextControlWidget.java b/visad/util/TextControlWidget.java
new file mode 100644
index 0000000..ab35092
--- /dev/null
+++ b/visad/util/TextControlWidget.java
@@ -0,0 +1,1094 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+/*
+ * TextControlWidget.java
+ *
+ * Created on July 14, 2003, 3:15 PM
+ */
+
+package visad.util;
+
+import java.awt.*;
+import java.util.Vector;
+import java.rmi.*;
+import java.io.*;
+import java.net.*;
+import java.util.zip.*;
+import java.util.Enumeration;
+
+import visad.*;
+import visad.util.HersheyFont;
+import visad.java2d.DisplayImplJ2D;
+
+
+/**
+ *
+ * @author  Sylvain Letourneau (Sylvain.letourneau at nrc.ca)
+ * This is a VisAD widget for interactive settings of text controls.
+ *
+ */
+public class TextControlWidget extends javax.swing.JPanel {
+  private String[] javaFontNames = null;
+  private String[] hersheyFontNames = null;
+  private String[] visadFontNames = null;
+  private String textToPreview = "VisAD";
+  //private String textToPreview = "Mf.abci-_='bp";
+  private TextControl previewDisplayTC = null;
+  private TextControl textControl = null;
+
+  private DisplayImpl display = null;
+  private ScalarMap textMap = null;
+  private FieldImpl textField = null;
+  private TextType textType = null;
+
+  /** Creates new form TextControlWidget */
+  public TextControlWidget(TextControl aTextControl)
+    throws RemoteException, VisADException {
+    initComponents();
+
+    javaFontNames = getJavaFontNames();
+    hersheyFontNames = getHersheyFontNames();
+    visadFontNames = new String[]{"VisAD Default"};
+    fontNamesJList.setListData(javaFontNames);
+    fontNamesJList.setSelectedIndex(0);
+
+    String[] sizes = { "8", "9", "10", "11", "12", "14", "16", "18", "24",
+                       "36", "48", "72"};
+    fontSizesJList.setListData(sizes);
+    fontSizesJList.setSelectedIndex(7);
+
+    String[] styles = {
+      "Plain",
+      "Bold",
+      "Italic",
+      "BoldItalic"
+    };
+    fontStylesJList.setListData(styles);
+    fontStylesJList.setSelectedIndex(0);
+
+    textToPreviewField.setText(textToPreview);
+
+    // Save initial controls
+    textControl = aTextControl;
+    if (textControl == null) {
+      textControl = new TextControl(null);
+    }
+    setGUIControls(textControl);
+
+    setupPreviewDisplay();
+    if (display == null) {
+      System.out.println("Display is null!");
+    }
+
+    previewDisplayTC = (TextControl) textMap.getControl();
+    updateTextControl();
+    displayTextToPreview.add(display.getComponent());
+  }
+
+  /** This method is called from within the constructor to
+   * initialize the form.
+   * WARNING: Do NOT modify this code. The content of this method is
+   * always regenerated by the Form Editor.
+   */
+  private void initComponents() {//GEN-BEGIN:initComponents
+    java.awt.GridBagConstraints gridBagConstraints;
+
+    fontTypeButtonGroup = new javax.swing.ButtonGroup();
+    fontTypeJPanel = new javax.swing.JPanel();
+    javaFontJRadioButton = new javax.swing.JRadioButton();
+    hersheyFontjRadioButton = new javax.swing.JRadioButton();
+    labelFontJRadioButton = new javax.swing.JRadioButton();
+    fontSelectionJPanel = new javax.swing.JPanel();
+    jLabel1 = new javax.swing.JLabel();
+    jLabel2 = new javax.swing.JLabel();
+    jLabel3 = new javax.swing.JLabel();
+    jScrollPane1 = new javax.swing.JScrollPane();
+    fontNamesJList = new javax.swing.JList();
+    jScrollPane2 = new javax.swing.JScrollPane();
+    fontStylesJList = new javax.swing.JList();
+    jScrollPane3 = new javax.swing.JScrollPane();
+    fontSizesJList = new javax.swing.JList();
+    previewPanel = new javax.swing.JPanel();
+    textToPreviewField = new javax.swing.JTextField();
+    displayTextToPreview = new javax.swing.JPanel();
+    jScrollPane4 = new javax.swing.JScrollPane();
+    textPanelAttributes = new javax.swing.JPanel();
+    leftPanel = new javax.swing.JPanel();
+    jLabel4 = new javax.swing.JLabel();
+    textOrientationField = new javax.swing.JTextField();
+    jLabel5 = new javax.swing.JLabel();
+    characterRotationField = new javax.swing.JTextField();
+    jLabel8 = new javax.swing.JLabel();
+    scaleTextField = new javax.swing.JTextField();
+    autoSizeJCheckBox = new javax.swing.JCheckBox();
+    rightPanel = new javax.swing.JPanel();
+    horizJustificationJComboBox = new javax.swing.JComboBox();
+    jLabel7 = new javax.swing.JLabel();
+    jLabel6 = new javax.swing.JLabel();
+    vertJustificationJComboBox = new javax.swing.JComboBox();
+    bottomPanel = new javax.swing.JPanel();
+    jLabel9 = new javax.swing.JLabel();
+    offsetXField = new javax.swing.JTextField();
+    offsetYField = new javax.swing.JTextField();
+    offsetZField = new javax.swing.JTextField();
+
+    setLayout(new java.awt.GridBagLayout());
+
+    fontTypeJPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));
+
+    javaFontJRadioButton.setSelected(true);
+    javaFontJRadioButton.setText("Java font");
+    fontTypeButtonGroup.add(javaFontJRadioButton);
+    javaFontJRadioButton.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          javaFontJRadioButtonActionPerformed(evt);
+        }
+      });
+
+    fontTypeJPanel.add(javaFontJRadioButton);
+
+    hersheyFontjRadioButton.setText("Hershery font");
+    fontTypeButtonGroup.add(hersheyFontjRadioButton);
+    hersheyFontjRadioButton.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          hersheyFontjRadioButtonActionPerformed(evt);
+        }
+      });
+
+    fontTypeJPanel.add(hersheyFontjRadioButton);
+
+    labelFontJRadioButton.setText("VisAD font");
+    fontTypeButtonGroup.add(labelFontJRadioButton);
+    labelFontJRadioButton.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          labelFontJRadioButtonActionPerformed(evt);
+        }
+      });
+
+    fontTypeJPanel.add(labelFontJRadioButton);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridwidth = 3;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    add(fontTypeJPanel, gridBagConstraints);
+
+    fontSelectionJPanel.setLayout(new java.awt.GridBagLayout());
+
+    fontSelectionJPanel.setMinimumSize(new java.awt.Dimension(200, 70));
+    fontSelectionJPanel.setPreferredSize(new java.awt.Dimension(360, 100));
+    jLabel1.setText("Font Name");
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+    fontSelectionJPanel.add(jLabel1, gridBagConstraints);
+
+    jLabel2.setText("Style");
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+    fontSelectionJPanel.add(jLabel2, gridBagConstraints);
+
+    jLabel3.setText("Size");
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 2;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
+    fontSelectionJPanel.add(jLabel3, gridBagConstraints);
+
+    jScrollPane1.setMinimumSize(new java.awt.Dimension(160, 40));
+    jScrollPane1.setPreferredSize(new java.awt.Dimension(160, 40));
+    fontNamesJList.setBorder(new javax.swing.border.CompoundBorder());
+    fontNamesJList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+    fontNamesJList.setSelectedIndex(1);
+    fontNamesJList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
+        public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
+          fontNamesJListValueChanged(evt);
+        }
+      });
+
+    jScrollPane1.setViewportView(fontNamesJList);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weightx = 0.5;
+    gridBagConstraints.weighty = 0.5;
+    fontSelectionJPanel.add(jScrollPane1, gridBagConstraints);
+
+    jScrollPane2.setMaximumSize(new java.awt.Dimension(0, 0));
+    jScrollPane2.setMinimumSize(new java.awt.Dimension(90, 40));
+    jScrollPane2.setPreferredSize(new java.awt.Dimension(100, 40));
+    fontStylesJList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+    fontStylesJList.setSelectedIndex(0);
+    fontStylesJList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
+        public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
+          fontStylesJListValueChanged(evt);
+        }
+      });
+
+    jScrollPane2.setViewportView(fontStylesJList);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weighty = 0.5;
+    fontSelectionJPanel.add(jScrollPane2, gridBagConstraints);
+
+    jScrollPane3.setMinimumSize(new java.awt.Dimension(100, 40));
+    jScrollPane3.setPreferredSize(new java.awt.Dimension(60, 40));
+    fontSizesJList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+    fontSizesJList.setSelectedIndex(3);
+    fontSizesJList.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
+        public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
+          fontSizesJListValueChanged(evt);
+        }
+      });
+
+    jScrollPane3.setViewportView(fontSizesJList);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 2;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weighty = 0.5;
+    fontSelectionJPanel.add(jScrollPane3, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weightx = 0.5;
+    gridBagConstraints.weighty = 0.3;
+    gridBagConstraints.insets = new java.awt.Insets(7, 7, 7, 7);
+    add(fontSelectionJPanel, gridBagConstraints);
+
+    previewPanel.setLayout(new java.awt.GridBagLayout());
+
+    previewPanel.setBorder(new javax.swing.border.TitledBorder("Preview"));
+    previewPanel.setMinimumSize(new java.awt.Dimension(56, 192));
+    textToPreviewField.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          textToPreviewFieldActionPerformed(evt);
+        }
+      });
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+    gridBagConstraints.weightx = 0.5;
+    previewPanel.add(textToPreviewField, gridBagConstraints);
+
+    displayTextToPreview.setLayout(new java.awt.BorderLayout());
+
+    displayTextToPreview.setBorder(new javax.swing.border.EtchedBorder());
+    displayTextToPreview.setPreferredSize(new java.awt.Dimension(300, 300));
+    displayTextToPreview.add(jScrollPane4, java.awt.BorderLayout.SOUTH);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weightx = 0.5;
+    gridBagConstraints.weighty = 0.9;
+    gridBagConstraints.insets = new java.awt.Insets(10, 10, 10, 10);
+    previewPanel.add(displayTextToPreview, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 3;
+    gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weightx = 0.5;
+    gridBagConstraints.weighty = 0.8;
+    gridBagConstraints.insets = new java.awt.Insets(7, 7, 7, 7);
+    add(previewPanel, gridBagConstraints);
+
+    textPanelAttributes.setLayout(new java.awt.GridBagLayout());
+
+    leftPanel.setLayout(new java.awt.GridBagLayout());
+
+    jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
+    jLabel4.setText("Text Orientation:");
+    jLabel4.setPreferredSize(new java.awt.Dimension(120, 16));
+    jLabel4.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT);
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    leftPanel.add(jLabel4, gridBagConstraints);
+
+    textOrientationField.setText("0");
+    textOrientationField.setPreferredSize(new java.awt.Dimension(50, 20));
+    textOrientationField.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          textOrientationFieldActionPerformed(evt);
+        }
+      });
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    leftPanel.add(textOrientationField, gridBagConstraints);
+
+    jLabel5.setText("Character Rotation:");
+    jLabel5.setPreferredSize(new java.awt.Dimension(40, 16));
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    leftPanel.add(jLabel5, gridBagConstraints);
+
+    characterRotationField.setText("0");
+    characterRotationField.setPreferredSize(new java.awt.Dimension(50, 20));
+    characterRotationField.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          characterRotationFieldActionPerformed(evt);
+        }
+      });
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    leftPanel.add(characterRotationField, gridBagConstraints);
+
+    jLabel8.setText("Scale:");
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    leftPanel.add(jLabel8, gridBagConstraints);
+
+    scaleTextField.setText("1.0");
+    scaleTextField.setPreferredSize(new java.awt.Dimension(50, 20));
+    scaleTextField.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          scaleTextFieldActionPerformed(evt);
+        }
+      });
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    leftPanel.add(scaleTextField, gridBagConstraints);
+
+    autoSizeJCheckBox.setText("auto size");
+    autoSizeJCheckBox.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          autoSizeJCheckBoxActionPerformed(evt);
+        }
+      });
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 3;
+    gridBagConstraints.gridwidth = 2;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    leftPanel.add(autoSizeJCheckBox, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+    gridBagConstraints.weightx = 0.5;
+    gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
+    textPanelAttributes.add(leftPanel, gridBagConstraints);
+
+    rightPanel.setLayout(new java.awt.GridBagLayout());
+
+    rightPanel.setMinimumSize(new java.awt.Dimension(144, 80));
+    rightPanel.setPreferredSize(new java.awt.Dimension(220, 56));
+    horizJustificationJComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Left", "Center", "Right" }));
+    horizJustificationJComboBox.setPreferredSize(new java.awt.Dimension(90, 25));
+    horizJustificationJComboBox.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          horizJustificationJComboBoxActionPerformed(evt);
+        }
+      });
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    gridBagConstraints.insets = new java.awt.Insets(3, 5, 3, 3);
+    rightPanel.add(horizJustificationJComboBox, gridBagConstraints);
+
+    jLabel7.setText("Horizontal justification:");
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    rightPanel.add(jLabel7, gridBagConstraints);
+
+    jLabel6.setText("Vertical justification:");
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 2;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    rightPanel.add(jLabel6, gridBagConstraints);
+
+    vertJustificationJComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Bottom", "Center", "Top" }));
+    vertJustificationJComboBox.setPreferredSize(new java.awt.Dimension(90, 25));
+    vertJustificationJComboBox.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          vertJustificationJComboBoxActionPerformed(evt);
+        }
+      });
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 2;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    gridBagConstraints.insets = new java.awt.Insets(3, 5, 3, 3);
+    rightPanel.add(vertJustificationJComboBox, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+    gridBagConstraints.weightx = 0.5;
+    gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
+    textPanelAttributes.add(rightPanel, gridBagConstraints);
+
+    bottomPanel.setLayout(new java.awt.GridBagLayout());
+
+    jLabel9.setText("Offsets ([x, y, z]):");
+    bottomPanel.add(jLabel9, new java.awt.GridBagConstraints());
+
+    offsetXField.setText("0");
+    offsetXField.setPreferredSize(new java.awt.Dimension(50, 20));
+    offsetXField.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          offsetXFieldActionPerformed(evt);
+        }
+      });
+
+    bottomPanel.add(offsetXField, new java.awt.GridBagConstraints());
+
+    offsetYField.setText("0");
+    offsetYField.setPreferredSize(new java.awt.Dimension(50, 20));
+    offsetYField.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          offsetYFieldActionPerformed(evt);
+        }
+      });
+
+    bottomPanel.add(offsetYField, new java.awt.GridBagConstraints());
+
+    offsetZField.setText("0");
+    offsetZField.setPreferredSize(new java.awt.Dimension(50, 20));
+    offsetZField.addActionListener(new java.awt.event.ActionListener() {
+        public void actionPerformed(java.awt.event.ActionEvent evt) {
+          offsetZFieldActionPerformed(evt);
+        }
+      });
+
+    bottomPanel.add(offsetZField, new java.awt.GridBagConstraints());
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.gridwidth = 2;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTH;
+    gridBagConstraints.weightx = 2.0;
+    gridBagConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
+    textPanelAttributes.add(bottomPanel, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 0;
+    gridBagConstraints.gridy = 2;
+    gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
+    gridBagConstraints.weightx = 0.5;
+    gridBagConstraints.insets = new java.awt.Insets(7, 7, 7, 7);
+    add(textPanelAttributes, gridBagConstraints);
+
+  }//GEN-END:initComponents
+
+
+  private void vertJustificationJComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_vertJustificationJComboBoxActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_vertJustificationJComboBoxActionPerformed
+
+  private void autoSizeJCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_autoSizeJCheckBoxActionPerformed
+    updateTextControl();
+  }//GEN-LAST:event_autoSizeJCheckBoxActionPerformed
+
+  private void labelFontJRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_labelFontJRadioButtonActionPerformed
+    fontNamesJList.setListData(visadFontNames);
+    fontNamesJList.setSelectedIndex(0);
+    fontSelectionJPanel.remove(jLabel2);
+    fontSelectionJPanel.remove(jScrollPane2);
+    fontSelectionJPanel.updateUI();
+  }//GEN-LAST:event_labelFontJRadioButtonActionPerformed
+
+  private void offsetZFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_offsetZFieldActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_offsetZFieldActionPerformed
+
+  private void offsetYFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_offsetYFieldActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_offsetYFieldActionPerformed
+
+  private void offsetXFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_offsetXFieldActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_offsetXFieldActionPerformed
+
+  private void javaFontJRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_javaFontJRadioButtonActionPerformed
+    fontNamesJList.setListData(javaFontNames);
+    fontNamesJList.setSelectedIndex(0);
+    jLabel1.setVisible(true);
+    jLabel2.setVisible(true);
+    jLabel3.setVisible(true);
+
+    java.awt.GridBagConstraints gridBagConstraints;
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
+    fontSelectionJPanel.add(jLabel2, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 2;
+    gridBagConstraints.gridy = 0;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
+    fontSelectionJPanel.add(jLabel3, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 1;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weighty = 0.5;
+    fontSelectionJPanel.add(jScrollPane2, gridBagConstraints);
+
+    gridBagConstraints = new java.awt.GridBagConstraints();
+    gridBagConstraints.gridx = 2;
+    gridBagConstraints.gridy = 1;
+    gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
+    gridBagConstraints.weighty = 0.5;
+    fontSelectionJPanel.add(jScrollPane3, gridBagConstraints);
+
+    fontSelectionJPanel.updateUI();
+
+  }//GEN-LAST:event_javaFontJRadioButtonActionPerformed
+
+  private void textToPreviewFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_textToPreviewFieldActionPerformed
+    // Add your handling code here:
+    textToPreview = textToPreviewField.getText();
+    try {
+      Data datum = new Text(textType, textToPreview);
+      textField.setSample(0, datum);
+    } catch (RemoteException e) {
+      System.out.println("Exception: " + e);
+    } catch (VisADException e1) {
+      System.out.println("Exception: " + e1);
+    }
+  }//GEN-LAST:event_textToPreviewFieldActionPerformed
+
+  private void scaleTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_scaleTextFieldActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_scaleTextFieldActionPerformed
+
+  private void characterRotationFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_characterRotationFieldActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_characterRotationFieldActionPerformed
+
+  private void horizJustificationJComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_horizJustificationJComboBoxActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_horizJustificationJComboBoxActionPerformed
+
+  private void textOrientationFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_textOrientationFieldActionPerformed
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_textOrientationFieldActionPerformed
+
+  private void fontSizesJListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_fontSizesJListValueChanged
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_fontSizesJListValueChanged
+
+  private void fontStylesJListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_fontStylesJListValueChanged
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_fontStylesJListValueChanged
+
+  private void fontNamesJListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_fontNamesJListValueChanged
+    // Add your handling code here:
+    updateTextControl();
+  }//GEN-LAST:event_fontNamesJListValueChanged
+
+  private void hersheyFontjRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hersheyFontjRadioButtonActionPerformed
+    // Add your handling code here:
+    fontNamesJList.setListData(hersheyFontNames);
+    fontNamesJList.setSelectedIndex(0);
+
+    fontSelectionJPanel.remove(jLabel2);
+    fontSelectionJPanel.remove(jScrollPane2);
+    fontSelectionJPanel.updateUI();
+  }//GEN-LAST:event_hersheyFontjRadioButtonActionPerformed
+
+  private Object getSelectedFont() {
+    int selectedIndex = fontNamesJList.getSelectedIndex();
+    if (selectedIndex == -1) {
+      selectedIndex = 0;
+      fontNamesJList.setSelectedIndex(selectedIndex);
+    }
+
+    if (javaFontJRadioButton.isSelected()) {
+      int size;
+      try {
+        size = Integer.parseInt((String)fontSizesJList.getSelectedValue());
+      }
+      catch(Exception e)  {
+        size = 12;
+      }
+      return new Font((String)fontNamesJList.getSelectedValue(),
+                      fontStylesJList.getSelectedIndex(),size);
+    }
+    else if (hersheyFontjRadioButton.isSelected()) {
+      return new HersheyFont ((String)fontNamesJList.getSelectedValue());
+    }
+    else {
+      // VisAD font
+      return null;
+    }
+  }
+
+  private String[] getJavaFontNames() {
+    String[] nameArray = GraphicsEnvironment
+      .getLocalGraphicsEnvironment()
+      .getAvailableFontFamilyNames();
+    Vector nameVector = new Vector(nameArray.length);
+
+    /*    for(int i = 0, j; i < nameArray.length; i++) {
+          System.out.println(i + ".  " + nameArray[i]);
+          }
+    */
+
+    String[] _array = new String[nameVector.size()];
+    nameVector.copyInto(_array);
+    return nameArray;
+  }
+
+  public static String[] getHersheyFontNames() {
+    String[] fontNames = null;
+    URL url = HersheyFont.class.
+      getResource("futural.jhf");
+    String protocol = url.getProtocol();
+    //System.out.println(url.getFile());
+    //System.out.println(url.getProtocol());
+    if (protocol.equals("file")) {
+      // We are running from the file system (i.e., visad has beeen extracted)
+      String file;
+      try {
+        file = URLDecoder.decode(url.getFile(),"UTF-8");
+      } catch (UnsupportedEncodingException uee) { // shouldn't happen
+        file = url.getFile();
+      }
+      File f = new File(file);
+      f = f.getParentFile();
+      FilenameFilter filter = new FilenameFilter() {
+          public boolean accept(File dir, String name) {
+            return name.endsWith(".jhf");
+          }
+        };
+      String[] children = f.list(filter);
+      if (children == null) {
+        return null;
+      }
+      fontNames = new String[children.length];
+      for (int i=0; i<children.length; i++) {
+        fontNames[i] =  children[i].substring(0, children[i].length()-4);
+      }
+    }
+    else if (protocol.equals("jar")) {
+      // We are running from a jar file
+      try {
+        JarURLConnection jarURL = (JarURLConnection)url.openConnection();
+        //System.out.println(jarURL.getJarEntry());
+        //System.out.println(jarURL.getJarFile().getName());
+        //System.out.println(jarURL.getEntryName());
+        ZipFile zf = new ZipFile(jarURL.getJarFile().getName());
+        Enumeration e = zf.entries();
+        Vector namesVector = new Vector();
+        while (e.hasMoreElements()) {
+          ZipEntry ze = (ZipEntry)e.nextElement();
+          String name = ze.getName();
+          if (name.startsWith("visad/util/") &&
+              name.endsWith(".jhf")) {
+            namesVector.add(name.substring(11));
+          }
+        }
+        zf.close();
+        if (namesVector.size() == 0) {
+          return null;
+        }
+        fontNames = new String[namesVector.size()];
+        for (int i=0; i<fontNames.length; i++) {
+          String name = (String)namesVector.elementAt(i);
+          fontNames[i] =  name.substring(0, name.length()-4);
+        }
+      }
+      catch (Exception  e) {
+        System.out.println(e);
+      }
+    }
+    else {
+      System.out.println("Cannot locate the HersheyFonts.");
+    }
+    return fontNames;
+  }
+
+   /**
+    * Dumps a zip entry into a string.
+    * @param ze a ZipEntry
+    */
+   private String dumpZipEntry(ZipEntry ze) {
+       StringBuffer sb=new StringBuffer();
+       if (ze.isDirectory()) {
+          sb.append("d ");
+       } else {
+          sb.append("f ");
+       }
+       if (ze.getMethod()==ZipEntry.STORED) {
+          sb.append("stored   ");
+       } else {
+          sb.append("defalted ");
+       }
+       sb.append(ze.getName());
+       sb.append("\t");
+       sb.append(""+ze.getSize());
+       if (ze.getMethod()==ZipEntry.DEFLATED) {
+          sb.append("/"+ze.getCompressedSize());
+       }
+       return (sb.toString());
+   }
+
+
+  private void setupPreviewDisplay()
+    throws RemoteException, VisADException
+  {
+    display = new DisplayImplJ2D("Preview_Display_in_Text_Control_Widget");
+
+    textType = TextType.getTextType("Preview_text_type_for_display_in_Text_Control_Widget");
+    RealTupleType coord_tuple_type = new RealTupleType(new RealType[]
+      {RealType.Latitude, RealType.Longitude});
+    FunctionType text_function = new FunctionType(coord_tuple_type, textType);
+
+    Linear2DSet domain = new Linear2DSet(coord_tuple_type, -120.0, 120.0, 1,
+                                         -120.0, 120.0, 1);
+    textField = new FieldImpl(text_function, domain);
+    Data datum = new Text(textType, textToPreview);
+    textField.setSample(0, datum);
+
+    display.addMap(new ScalarMap(RealType.Latitude, Display.YAxis));
+    display.addMap(new ScalarMap(RealType.Longitude, Display.XAxis));
+    textMap = new ScalarMap(textType, Display.Text);
+    display.addMap(textMap);
+
+    DataReferenceImpl ref_text_field =
+      new DataReferenceImpl("ref_text_field");
+    ref_text_field.setData(textField);
+    display.addReference(ref_text_field, null);
+
+    // re-sizing the box around the display
+    ProjectionControl pc=display.getProjectionControl();
+    double[] pcMatrix=pc.getMatrix();
+    double percent = .90;
+    if (pcMatrix.length > 10) {
+      pcMatrix[0]=percent;
+      pcMatrix[5]=percent;
+      pcMatrix[10]=percent;
+    }
+    else {
+      pcMatrix[0]=percent/.64;
+      pcMatrix[3]=-percent/.64;
+      pc.setMatrix(pcMatrix);
+    }
+
+    // remove the box around the display
+    //    display.getDisplayRenderer().setBoxOn(false);
+
+  }
+
+  private void setGUIControls(TextControl tc) {
+    if (tc == null) return;
+
+    String str = String.valueOf(tc.getScale());
+    scaleTextField.setText(str);
+
+    if (tc.getJustification() == TextControl.Justification.LEFT) {
+      horizJustificationJComboBox.setSelectedItem("Left");
+    }
+    else if (tc.getJustification() == TextControl.Justification.RIGHT) {
+      horizJustificationJComboBox.setSelectedItem("Right");
+    }
+    else {
+      horizJustificationJComboBox.setSelectedItem("Center");
+    }
+
+    if (tc.getVerticalJustification() == TextControl.Justification.BOTTOM) {
+      vertJustificationJComboBox.setSelectedItem("Bottom");
+    }
+    else if (tc.getVerticalJustification() == TextControl.Justification.TOP) {
+      vertJustificationJComboBox.setSelectedItem("Top");
+    }
+    else {
+      vertJustificationJComboBox.setSelectedItem("Center");
+    }
+
+    str = String.valueOf((int)(tc.getSize() * 12.0));
+    fontSizesJList.setSelectedValue(str, true);
+    Object font = tc.getFont();
+    if (font instanceof java.awt.Font) {
+      javaFontJRadioButton.setSelected(true);
+      switch (((java.awt.Font) font).getStyle()) {
+      case java.awt.Font.PLAIN:
+        fontStylesJList.setSelectedIndex(0);
+        break;
+      case java.awt.Font.BOLD:
+        fontStylesJList.setSelectedIndex(1);
+        break;
+      case java.awt.Font.ITALIC:
+        fontStylesJList.setSelectedIndex(2);
+        break;
+      case java.awt.Font.BOLD+java.awt.Font.ITALIC:
+        fontStylesJList.setSelectedIndex(3);
+        break;
+      default:
+        fontStylesJList.setSelectedIndex(0);
+        break;
+      }
+      javaFontJRadioButtonActionPerformed(null);
+    }
+    else if (font instanceof visad.util.HersheyFont) {
+      hersheyFontjRadioButton.setSelected(true);
+      hersheyFontjRadioButtonActionPerformed(null);
+    }
+    else {
+      labelFontJRadioButton.setSelected(true);
+      labelFontJRadioButtonActionPerformed(null);
+    }
+
+    str = String.valueOf(tc.getRotation());
+    textOrientationField.setText(str);
+    str = String.valueOf(tc.getCharacterRotation());
+    characterRotationField.setText(str);
+
+    double[] offsets = tc.getOffset();
+    str = String.valueOf(offsets[0]);
+    offsetXField.setText(str);
+    str = String.valueOf(offsets[1]);
+    offsetYField.setText(str);
+    str = String.valueOf(offsets[2]);
+    offsetZField.setText(str);
+
+    if (tc.getAutoSize()) {
+      autoSizeJCheckBox.setSelected(true);
+    }
+  }
+
+  private void updateTextControl() {
+    try {
+      if (textMap == null) return;
+
+      previewDisplayTC = (TextControl) textMap.getControl();
+      if (previewDisplayTC != null) {
+        String str = scaleTextField.getText();
+        double val;
+        try {
+          val = Double.valueOf(str).doubleValue();
+          if (val <= 0.0) {
+            val = 1.0;
+            scaleTextField.setText("1.0");
+          }
+        } catch (NumberFormatException e) {
+          val = 1.0;
+          scaleTextField.setText("1.0");
+        }
+        previewDisplayTC.setScale(val);
+        textControl.setScale(val);
+
+        String justStr = (String) horizJustificationJComboBox.getSelectedItem();
+        if (justStr.equals("Left")) {
+          previewDisplayTC.setJustification(TextControl.Justification.LEFT);
+          textControl.setJustification(TextControl.Justification.LEFT);
+        }
+        else if (justStr.equals("Right")) {
+          previewDisplayTC.setJustification(TextControl.Justification.RIGHT);
+          textControl.setJustification(TextControl.Justification.RIGHT);
+        }
+        else {
+          previewDisplayTC.setJustification(TextControl.Justification.CENTER);
+          textControl.setJustification(TextControl.Justification.CENTER);
+        }
+
+        justStr = (String) vertJustificationJComboBox.getSelectedItem();
+        if (justStr.equals("Bottom")) {
+          previewDisplayTC.setVerticalJustification(TextControl.Justification.BOTTOM);
+          textControl.setVerticalJustification(TextControl.Justification.BOTTOM);
+        }
+        else if (justStr.equals("Top")) {
+          previewDisplayTC.setVerticalJustification(TextControl.Justification.TOP);
+          textControl.setVerticalJustification(TextControl.Justification.TOP);
+        }
+        else {
+          previewDisplayTC.setVerticalJustification(TextControl.Justification.CENTER);
+          textControl.setVerticalJustification(TextControl.Justification.CENTER);
+        }
+
+        previewDisplayTC.setFont(getSelectedFont());
+        textControl.setFont(getSelectedFont());
+        // Set initial size
+        double size;
+        try {
+          size = (double) Integer.parseInt((String)fontSizesJList.getSelectedValue());
+        }
+        catch(Exception e)  {
+          size = 12.0;
+        }
+        previewDisplayTC.setSize(size/12.0);
+        textControl.setSize(size/12.0);
+
+        str = textOrientationField.getText();
+        try {
+          val = Double.valueOf(str).doubleValue();
+        } catch (NumberFormatException e) {
+          val = 0.0;
+          textOrientationField.setText("0.0");
+        }
+        previewDisplayTC.setRotation(val);
+        textControl.setRotation(val);
+        str = characterRotationField.getText();
+        try {
+          val = Double.valueOf(str).doubleValue();
+        } catch (NumberFormatException e) {
+          val = 0.0;
+          characterRotationField.setText("0.0");
+        }
+        previewDisplayTC.setCharacterRotation(val);
+        textControl.setCharacterRotation(val);
+
+        double offsetX = 0.0;
+        str = offsetXField.getText();
+        try {
+          offsetX = Double.valueOf(str).doubleValue();
+        } catch (NumberFormatException e) {
+          offsetXField.setText("0.0");
+        }
+        double offsetY = 0.0;
+        str = offsetYField.getText();
+        try {
+          offsetY = Double.valueOf(str).doubleValue();
+        } catch (NumberFormatException e) {
+          offsetYField.setText("0.0");
+        }
+        double offsetZ = 0.0;
+        str = offsetZField.getText();
+        try {
+          offsetZ = Double.valueOf(str).doubleValue();
+        } catch (NumberFormatException e) {
+          offsetZField.setText("0.0");
+        }
+        double[] offsets = new double[]{offsetX, offsetY, offsetZ};
+        previewDisplayTC.setOffset(offsets);
+        textControl.setOffset(offsets);
+
+        previewDisplayTC.setAutoSize(autoSizeJCheckBox.isSelected());
+        textControl.setAutoSize(autoSizeJCheckBox.isSelected());
+
+      } else {
+        System.out.println("TextControl is null?");
+      }
+
+    } catch (RemoteException e) {
+      System.out.println("Exception: " + e);
+    } catch (VisADException e1) {
+      System.out.println("Exception: " + e1);
+    }
+  }
+
+  // Variables declaration - do not modify//GEN-BEGIN:variables
+  private javax.swing.JLabel jLabel8;
+  private javax.swing.JLabel jLabel4;
+  private javax.swing.JComboBox vertJustificationJComboBox;
+  private javax.swing.JTextField offsetYField;
+  private javax.swing.JTextField offsetZField;
+  private javax.swing.JScrollPane jScrollPane1;
+  private javax.swing.JLabel jLabel1;
+  private javax.swing.JLabel jLabel3;
+  private javax.swing.JRadioButton labelFontJRadioButton;
+  private javax.swing.JTextField textOrientationField;
+  private javax.swing.JLabel jLabel2;
+  private javax.swing.JScrollPane jScrollPane4;
+  private javax.swing.JLabel jLabel9;
+  private javax.swing.JScrollPane jScrollPane2;
+  private javax.swing.JPanel fontTypeJPanel;
+  private javax.swing.JRadioButton hersheyFontjRadioButton;
+  private javax.swing.JPanel displayTextToPreview;
+  private javax.swing.JPanel leftPanel;
+  private javax.swing.JList fontNamesJList;
+  private javax.swing.JComboBox horizJustificationJComboBox;
+  private javax.swing.JList fontStylesJList;
+  private javax.swing.JCheckBox autoSizeJCheckBox;
+  private javax.swing.JPanel previewPanel;
+  private javax.swing.JPanel fontSelectionJPanel;
+  private javax.swing.JRadioButton javaFontJRadioButton;
+  private javax.swing.JLabel jLabel7;
+  private javax.swing.JTextField offsetXField;
+  private javax.swing.JPanel textPanelAttributes;
+  private javax.swing.JList fontSizesJList;
+  private javax.swing.JTextField characterRotationField;
+  private javax.swing.JLabel jLabel6;
+  private javax.swing.JTextField scaleTextField;
+  private javax.swing.ButtonGroup fontTypeButtonGroup;
+  private javax.swing.JTextField textToPreviewField;
+  private javax.swing.JScrollPane jScrollPane3;
+  private javax.swing.JPanel rightPanel;
+  private javax.swing.JPanel bottomPanel;
+  private javax.swing.JLabel jLabel5;
+  // End of variables declaration//GEN-END:variables
+
+}
diff --git a/visad/util/TextEditor.java b/visad/util/TextEditor.java
new file mode 100644
index 0000000..ac6aafb
--- /dev/null
+++ b/visad/util/TextEditor.java
@@ -0,0 +1,318 @@
+//
+// TextEditor.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.Font;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.text.JTextComponent;
+import javax.swing.event.*;
+import javax.swing.undo.*;
+
+/** A general-purpose editor for reading and writing text files. */
+public class TextEditor extends JScrollPane implements UndoableEditListener {
+
+  /** monospaced font */
+  public static final Font MONO = new Font("monospaced", Font.PLAIN, 12);
+
+  /** debugging flag */
+  public static final boolean DEBUG = false;
+
+  /** main text area */
+  protected JTextArea text;
+
+  /** file chooser dialog box */
+  private JFileChooser fileChooser;
+
+  /** undo manager */
+  protected UndoManager undo = new UndoManager();
+
+  /** file being edited */
+  protected File currentFile;
+
+  /** whether the text has changed since last save */
+  protected boolean changed = false;
+
+
+  /** constructs a TextEditor */
+  public TextEditor() {
+    this(null);
+  }
+
+  /** constructs a TextEditor containing text from the given filename */
+  public TextEditor(String filename) {
+    super();
+    text = new JTextArea();
+
+    // load file, if any
+    try {
+      openFile(filename);
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+    }
+
+    // setup text area
+    text.setFont(MONO);
+    setViewportView(text);
+
+    // provide support for undo and redo
+    addUndoableEditListener(this);
+
+    // setup file chooser dialog box
+  }
+
+  /** Create and initialize the the file chooser */
+  protected JFileChooser doMakeFileChooser() {
+       JFileChooser tmpChooser = new JFileChooser(System.getProperty("user.dir"));
+       tmpChooser.addChoosableFileFilter(
+         new ExtensionFileFilter("txt", "Text files"));
+       return tmpChooser;
+  }
+
+
+  /** Create if needed and return the file chooser */
+  protected JFileChooser getFileChooser() {
+    if(fileChooser == null) {
+       fileChooser = doMakeFileChooser();
+    }
+    return fileChooser;
+  }
+
+  /** starts from scratch with a blank document */
+  public void newFile() {
+    currentFile = null;
+    setText("");
+    undo.discardAllEdits();
+    changed = false;
+  }
+
+  /** opens the given file */
+  public void openFile(String filename) throws IOException {
+    File file = (filename == null ? null : new File(filename));
+    openFile(file);
+  }
+
+  /** opens the given file */
+  public void openFile(File file) throws IOException {
+    String fileText;
+    if (file == null) fileText = "";
+    else {
+      int len = (int) file.length();
+      byte[] bytes = new byte[len];
+      FileInputStream in = new FileInputStream(file);
+      in.read(bytes);
+      in.close();
+      fileText = new String(bytes);
+    }
+    currentFile = file;
+    setText(fileText);
+    changed = false;
+  }
+
+  /** saves the given file */
+  public void saveFile(String filename) throws IOException {
+    File file = (filename == null ? null : new File(filename));
+    saveFile(file);
+  }
+
+  /** saves the given file */
+  public void saveFile(File file) throws IOException {
+    byte[] bytes = getText().getBytes();
+    FileOutputStream out = new FileOutputStream(file);
+    out.write(bytes);
+    out.close();
+    currentFile = file;
+    changed = false;
+  }
+ 
+  /** pops up a dialog box for the user to select a file to open */
+  public boolean openDialog() {
+    getFileChooser().setDialogType(JFileChooser.OPEN_DIALOG);
+    if (getFileChooser().showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
+      // user has canceled request
+      return false;
+    }
+    try {
+      openFile(getFileChooser().getSelectedFile());
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+      return false;
+    }
+    return true;
+  }
+
+  /** pops up a dialog box for the user to select a file to save */
+  public boolean saveDialog() {
+    getFileChooser().setDialogType(JFileChooser.SAVE_DIALOG);
+    if (getFileChooser().showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
+      // user has canceled request
+      return false;
+    }
+    try {
+      saveFile(getFileChooser().getSelectedFile());
+    }
+    catch (IOException exc) {
+      if (DEBUG) exc.printStackTrace();
+      return false;
+    }
+    return true;
+  }
+
+  /** saves the file under its current name */
+  public boolean saveFile() {
+    boolean success = false;
+    if (currentFile == null) success = saveDialog();
+    else {
+      try {
+        saveFile(currentFile);
+        success = true;
+      }
+      catch (IOException exc) {
+        // display error box
+        JOptionPane.showMessageDialog(this, "Could not save the file.",
+          "VisAD Text Editor", JOptionPane.ERROR_MESSAGE);
+      }
+    }
+    return success;
+  }
+
+  /** undoes the last edit */
+  public void undo() throws CannotUndoException {
+    undo.undo();
+    changed = true;
+  }
+
+  /** redoes the last undone edit */
+  public void redo() throws CannotRedoException {
+    undo.redo();
+    changed = true;
+  }
+
+  /** cuts the selected text to the clipboard */
+  public void cut() {
+    text.cut();
+  }
+
+  /** copies the selected text to the clipboard */
+  public void copy() {
+    text.copy();
+  }
+
+  /** pastes the clipboard into the text document */
+  public void paste() {
+    text.paste();
+  }
+
+ /**
+  * Provide direct access to the text component
+  *
+  * @return the text component
+  */
+ public JTextComponent getTextComponent() {
+     return text;
+ }
+
+
+  /** returns a string containing the text of the document */
+  public String getText() {
+    return text.getText();
+  }
+
+  /** sets the text of this document to the current string */
+  public void setText(String text) {
+    this.text.setText(text);
+  }
+  
+  /**
+   * Insert the given text at the caret
+   *
+   * @param textToInsert the text
+   */
+  public void insertText(String textToInsert) {
+    int    pos = this.text.getCaretPosition();
+    String t   = this.text.getText();
+    t = t.substring(0, pos) + textToInsert + t.substring(pos);
+    this.text.setText(t);
+    this.text.setCaretPosition(pos + textToInsert.length());
+  }
+
+
+  /** returns the filename being edited */
+  public String getFilename() {
+    return (currentFile == null ? null : currentFile.getPath());
+  }
+  
+  /** returns the file being edited */
+  public File getFile() {
+    return currentFile;
+  }
+
+  /** returns whether an undo command is possible */
+  public boolean canUndo() {
+    return undo.canUndo();
+  }
+
+  /** returns whether a redo command is possible */
+  public boolean canRedo() {
+    return undo.canRedo();
+  }
+  
+  /** returns the name of the undo command */
+  public String getUndoName() {
+    return undo.getUndoPresentationName();
+  }
+
+  /** returns the name of the redo command */
+  public String getRedoName() {
+    return undo.getRedoPresentationName();
+  }
+  
+  /** returns whether the document has changed since the last save */
+  public boolean hasChanged() {
+    return changed;
+  }
+
+  /** handle undoable edits */
+  public void undoableEditHappened(UndoableEditEvent e) {
+    if (!e.getEdit().isSignificant()) return;
+    undo.addEdit(e.getEdit());
+    changed = true;
+  }
+
+  /** add an undoable edit listener */
+  public void addUndoableEditListener(UndoableEditListener l) {
+    text.getDocument().addUndoableEditListener(l);
+  }
+
+  /** remove an undoable edit listener */
+  public void removeUndoableEditListener(UndoableEditListener l) {
+    text.getDocument().removeUndoableEditListener(l);
+  }
+
+}
diff --git a/visad/util/TextFrame.java b/visad/util/TextFrame.java
new file mode 100644
index 0000000..b656bc7
--- /dev/null
+++ b/visad/util/TextFrame.java
@@ -0,0 +1,235 @@
+//
+// TextFrame.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+/** A GUI frame for editing text files. */
+public class TextFrame extends GUIFrame implements UndoableEditListener {
+
+  /** main frame component */
+  protected TextEditor textPane;
+
+  /** frame title */
+  private String title = "VisAD Text Editor";
+
+  /** constructs a TextFrame */
+  public TextFrame() {
+    this((String) null);
+  }
+
+  /** constructs a TextFrame containing text from the given filename */
+  public TextFrame(String filename) {
+    this(new TextEditor(filename));
+  }
+
+  /** constructs a TextFrame from the given TextEditor object */
+  public TextFrame(TextEditor textEdit) {
+    textPane = textEdit;
+    textPane.addUndoableEditListener(this);
+
+    // setup menu bar
+    addMenuItem("File", "New", "fileNew", 'n');
+    addMenuItem("File", "Open...", "fileOpen", 'o');
+    addMenuItem("File", "Save", "fileSave", 's', false);
+    addMenuItem("File", "Save as...", "fileSaveAs", 'a');
+    addMenuItem("File", "Exit", "fileExit", 'x');
+    addMenuItem("Edit", "Undo", "editUndo", 'u', false);
+    addMenuItem("Edit", "Redo", "editRedo", 'r', false);
+    addMenuItem("Edit", "Cut", "editCut", 't');
+    addMenuItem("Edit", "Copy", "editCopy", 'c');
+    addMenuItem("Edit", "Paste", "editPaste", 'p');
+
+    // finish UI setup
+    layoutGUI();
+  }
+
+  /** sets up the GUI */
+  protected void layoutGUI() {
+    setContentPane(textPane);
+    setTitle(title);
+  }
+
+  /** sets the text editor's title bar text */
+  public void setTitle(String title) {
+    this.title = title;
+    refreshTitleBar();
+  }
+
+  /** gets the text editor's title bar text */
+  public String getTitle() {
+    return title;
+  }
+
+  /** refreshes the Edit Undo and Edit Redo menu items */
+  private void refreshUndoMenuItems() {
+    JMenuItem editUndo = getMenuItem("Edit", "Undo");
+    JMenuItem editRedo = getMenuItem("Edit", "Redo");
+    editUndo.setEnabled(textPane.canUndo());
+    editUndo.setText(textPane.getUndoName());
+    editRedo.setEnabled(textPane.canRedo());
+    editRedo.setText(textPane.getRedoName());
+  }
+
+  /** refreshes the File Save menu item */
+  private void refreshSaveMenuItem(boolean dirty) {
+    JMenuItem fileSave = getMenuItem("File", "Save");
+    fileSave.setEnabled(dirty);
+  }
+
+  /** refreshes the frame's title bar */
+  private void refreshTitleBar() {
+    String filename = textPane.getFilename();
+    super.setTitle(title + (filename == null ? "" : " - " + filename));
+  }
+
+  /** refreshes Edit Undo, Edit Redo and File Save */
+  private void refreshMenuItems(boolean dirty) {
+    refreshUndoMenuItems();
+    refreshSaveMenuItem(dirty);
+  }
+
+  /** prompts user to save the document before it is discarded */
+  protected boolean askSaveChanges(boolean allowCancel) {
+    int ans = JOptionPane.showConfirmDialog(this,
+      "Save changes to the file?", getTitle(), allowCancel ?
+      JOptionPane.YES_NO_CANCEL_OPTION : JOptionPane.YES_NO_OPTION);
+    if (ans == JOptionPane.YES_OPTION) fileSave();
+    return (ans != JOptionPane.CANCEL_OPTION);
+  }
+
+  /** displays an error message in an error box */
+  public void showError(String msg) {
+    // strip off carriage returns
+    int i = 0;
+    int len = msg.length();
+    StringBuffer sb = new StringBuffer(len);
+    while (true) {
+      int index = msg.indexOf((char) 13, i);
+      if (index < 0) break;
+      sb.append(msg.substring(i, index));
+      i = index + 1;
+    }
+    sb.append(msg.substring(i));
+    msg = sb.toString();
+    JOptionPane.showMessageDialog(this, msg, getTitle(),
+      JOptionPane.ERROR_MESSAGE);
+  }
+
+  public void fileNew() {
+    if (textPane.hasChanged() && !askSaveChanges(true)) return;
+    textPane.newFile();
+    refreshMenuItems(false);
+    refreshTitleBar();
+  }
+
+  public void fileOpen() {
+    if (textPane.hasChanged() && !askSaveChanges(true)) return;
+    textPane.openDialog();
+    refreshMenuItems(false);
+    refreshTitleBar();
+  }
+
+  /** @return true if save was successful */
+  public boolean fileSave() {
+    boolean success = textPane.saveFile();
+    if (success) refreshSaveMenuItem(false);
+    return success;
+  }
+
+  /** @return true if save was successful */
+  public boolean fileSaveAs() {
+    boolean success = textPane.saveDialog();
+    if (success) {
+      refreshSaveMenuItem(false);
+      refreshTitleBar();
+    }
+    return success;
+  }
+
+  public void fileExit() {
+    if (textPane.hasChanged()) askSaveChanges(false);
+    System.exit(0);
+  }
+
+  public void editUndo() {
+    textPane.undo();
+    refreshMenuItems(true);
+  }
+
+  public void editRedo() {
+    textPane.redo();
+    refreshMenuItems(true);
+  }
+
+  public void editCut() {
+    textPane.cut();
+    refreshMenuItems(true);
+  }
+
+  public void editCopy() {
+    textPane.copy();
+    refreshUndoMenuItems();
+  }
+
+  public void editPaste() {
+    textPane.paste();
+    refreshMenuItems(true);
+  }
+
+  /** updates menu items when undoable action occurs */
+  public void undoableEditHappened(UndoableEditEvent e) {
+    if (!e.getEdit().isSignificant()) return;
+
+    // refresh menu items when an undoable event occurs
+    refreshMenuItems(true);
+  }
+
+  /** returns the main text editor pane */
+  public TextEditor getTextPane() {
+    return textPane;
+  }
+
+  /** tests the TextFrame class */
+  public static void main(String[] args) {
+    final TextFrame frame = new TextFrame();
+
+    // close program if frame is closed
+    frame.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        frame.fileExit();
+      }
+    });
+
+    // display frame onscreen
+    frame.setBounds(100, 100, 500, 800);
+    frame.setVisible(true);
+  }
+
+}
diff --git a/visad/util/ThreadManager.java b/visad/util/ThreadManager.java
new file mode 100644
index 0000000..b92da1a
--- /dev/null
+++ b/visad/util/ThreadManager.java
@@ -0,0 +1,444 @@
+//
+// ThreadUtil.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import visad.VisADException;
+
+import java.util.ArrayList;
+
+import java.rmi.RemoteException;
+
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * This class provides support for running a collection of Runnables
+ * concurrently. It will collect and then throw any exceptions that
+ * are thrown. It uses the static maxThreads as the number of threads
+ * to run. The default is 1, resulting in sequential execution.
+ */
+
+public class ThreadManager {
+
+  /**           */
+  private String name = "TreadManager";
+
+  /**           */
+  private static int maxThreads = 1;
+
+  /**           */
+  private List<VisADException> visadExceptions =
+    new ArrayList<VisADException>();
+
+  /**           */
+  private List<RemoteException> remoteExceptions =
+    new ArrayList<RemoteException>();
+
+  /**           */
+  private List<RuntimeException> runtimeExceptions =
+    new ArrayList<RuntimeException>();
+
+  /**           */
+  private int numThreadsRunning = 0;
+
+  /**           */
+  private Object MUTEX = new Object();
+
+  /**           */
+  private boolean running = false;
+
+  /**           */
+  private List<MyRunnable> runnables = new ArrayList<MyRunnable>();
+
+  /**           */
+  private int myMaxThreads;
+
+  /**           */
+  private static Hashtable<Integer, Integer[]> times = new Hashtable<Integer,
+                                                         Integer[]>();
+
+
+ public boolean debug = false;
+
+  /**
+   * 
+   */
+  public ThreadManager() {
+    this("ThreadManager");
+  }
+
+  /**
+   * Constructor with name specified
+   *
+   * @param theName 
+   */
+  
+  public ThreadManager(String theName) {
+    this.name = theName;
+    if (maxThreads <= 0) {
+      maxThreads = Runtime.getRuntime().availableProcessors();
+    }
+    myMaxThreads = maxThreads;
+  }
+
+
+  /**
+   * 
+   *
+   * @param maxThreads 
+   */
+  public ThreadManager(int maxThreads) {
+    myMaxThreads = maxThreads;
+  }
+
+  /**
+   * 
+   *
+   * @param max 
+   */
+  public static void setGlobalMaxThreads(int max) {
+    ThreadManager.maxThreads = max;
+  }
+
+
+    public void debug(String msg) {
+
+    }
+
+
+  /**
+   * 
+   */
+  public static void clearTimes() {
+    times = new Hashtable<Integer, Integer[]>();
+  }
+
+
+  /**
+   * 
+   *
+   * @param runnable 
+   */
+  public void addRunnable(MyRunnable runnable) {
+    runnables.add(runnable);
+  }
+
+  /**
+   * 
+   *
+   * @param exc 
+   */
+  public void handleException(Exception exc) {
+    if (exc instanceof VisADException) {
+      visadExceptions.add((VisADException)exc);
+    }
+    else if (exc instanceof RemoteException) {
+      remoteExceptions.add((RemoteException)exc);
+    }
+    else if (exc instanceof RuntimeException) {
+      runtimeExceptions.add((RuntimeException)exc);
+    }
+    else {
+      runtimeExceptions.add(new RuntimeException(exc));
+    }
+  }
+
+  /**
+   * 
+   */
+  public void runnableStopped() {
+    synchronized (MUTEX) {
+      numThreadsRunning--;
+    }
+  }
+
+  /**
+   * 
+   */
+  public void runnableStarted() {
+    synchronized (MUTEX) {
+      numThreadsRunning++;
+    }
+  }
+
+
+  /**
+   * 
+   *
+   * @return count of Runnables
+   */
+  
+  public int getNumRunnables() {
+    return runnables.size();
+  }
+
+
+  /**
+   * 
+   *
+   * @param maxThreads 
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public void runInParallel(int maxThreads)
+          throws VisADException, RemoteException {
+    myMaxThreads = maxThreads;
+    runInParallel();
+  }
+
+  /**
+   * 
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public void runSequentially() throws VisADException, RemoteException {
+    myMaxThreads = 1;
+    runInParallel();
+  }
+
+
+  /**
+   * 
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public void runAllParallel() throws VisADException, RemoteException {
+    runInParallel(runnables.size());
+  }
+
+
+  /**
+   * 
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  public void runInParallel() throws VisADException, RemoteException {
+    runInParallel(true);
+  }
+
+
+  /**           */
+  public static final int MAX_THREADS = 32;
+
+  /**
+   * 
+   *
+   * @param doAverage 
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+    public void runInParallel(boolean doAverage)
+        throws VisADException, RemoteException {
+        myMaxThreads = Math.max(myMaxThreads, 1);
+        myMaxThreads = Math.min(myMaxThreads, MAX_THREADS);
+        int max = Math.min(myMaxThreads, runnables.size());
+        int min = max;
+        running = true;
+        //If we are not running in parallel then just run in this thread
+        //so we minimize any side effects
+        long t1 = System.currentTimeMillis();
+        if(max<2) {
+            for (MyRunnable myRunnable : runnables) {
+                try {
+                    myRunnable.run();
+                } catch (Exception exc) {
+                    handleException(exc);
+                } 
+                checkErrors();
+            }
+        } else {
+            ThreadPool pool;
+            try {
+                pool = new ThreadPool("thread util", min, max);
+            } catch (Exception exc) {
+                throw new RuntimeException(exc);
+            }
+            for (MyRunnable myRunnable : runnables) {
+                runnableStarted();
+                final MyRunnable theRunnable = myRunnable;
+                Runnable runnable = new Runnable() {
+                        public void run() {
+                            try {
+                                theRunnable.run();
+                            } catch (Exception exc) {
+                                handleException(exc);
+                            } finally {
+                                runnableStopped();
+                            }
+                        }
+                    };
+                pool.queue(runnable);
+            }
+
+            try {
+                pool.waitForTasks();
+                checkErrors();
+            }  finally {
+                try {
+                    pool.stopThreads();
+                } catch (Exception ignoreThis) {}
+            }
+
+        }
+
+
+        running = false;
+
+        long t2 = System.currentTimeMillis();
+        if(debug) {
+            System.err.println(
+                               name + " time:" + (t2 - t1) + " max threads:" + myMaxThreads);
+        }
+        if (doAverage && false) {
+            Integer[] tuple = times.get(new Integer(myMaxThreads));
+            if (tuple == null) {
+                times.put(new Integer(myMaxThreads), tuple = new Integer[] {0, 0});
+            }
+            tuple[0] = new Integer(tuple[0].intValue() + 1);
+            tuple[1] = new Integer(tuple[1].intValue() + (int)(t2 - t1));
+            System.err.print("   times:");
+            for (Enumeration keys = times.keys(); keys.hasMoreElements(); ) {
+                Integer maxThreads = (Integer)keys.nextElement();
+                Integer[] values = times.get(maxThreads);
+                System.err.print(
+                                 "  #:" + maxThreads + " avg:" +
+                                 (int)(values[1].intValue() / (double)values[0].intValue()));
+            }
+            System.err.println("");
+        }
+    }
+
+
+
+  /**
+   * 
+   *
+   * @throws RemoteException 
+   * @throws VisADException 
+   */
+  private void checkErrors() throws VisADException, RemoteException {
+    try {
+      if (visadExceptions.size() > 0) throw visadExceptions.get(0);
+      if (remoteExceptions.size() > 0) throw remoteExceptions.get(0);
+      if (runtimeExceptions.size() > 0) throw runtimeExceptions.get(0);
+    }
+    finally {
+      running = false;
+    }
+  }
+
+
+  /**
+   * Return the list of any exceptions that were thrown when running the threads
+   * 
+   * @return The exceptions that were thrown
+   */
+  public List<Exception> getExceptions() {
+      List<Exception> exceptions = new ArrayList<Exception>();
+      exceptions.addAll(visadExceptions);
+      exceptions.addAll(remoteExceptions);
+      exceptions.addAll(runtimeExceptions);
+      return exceptions;
+   } 
+
+
+  /**
+   * MyRunnable 
+   */
+  public interface MyRunnable {
+
+    /**
+     * 
+     *
+     * @throws Exception 
+     */
+    public void run() throws Exception;
+
+  }
+
+
+
+
+    public static final void doWork(int amt,int[]A) {
+        long x=0;
+        long y=0;
+        long work = ((long)amt)*2000000L;
+        /*
+        for(int j=0;j<amt;j++) {
+            for(int i=0;i<A.length;i++) {
+                A[i] = 0;
+            }
+            }*/
+        for(long i=0;i<work;i++) {
+            x++;
+        }
+    }
+
+
+    /**
+     * Main method for testing
+     *
+     * @param args  args
+     */
+    public static void main(String[] args) throws Exception {
+        int numberOfProcessors = Runtime.getRuntime().availableProcessors();
+        int myCnt = (args.length>0?new Integer(args[0]).intValue():2);
+        //        for(int j=0;j<1000;j++) {
+        for(myCnt=1;myCnt<20;myCnt+=1) {
+            visad.util.ThreadManager threadManager  = new visad.util.ThreadManager();
+            final int amt = 2000;
+            //           final int cnt = (args.length>0?new Integer(args[0]).intValue():2);
+            final int cnt = myCnt;
+            final int [] A = new int[10000000];
+            for(int i=0;i<cnt;i++) {
+                threadManager.addRunnable(new visad.util.ThreadManager.MyRunnable() {
+                        public void run() throws Exception {
+                            doWork(amt/cnt,A);
+                        }
+                    });
+            }
+            long t1  = System.currentTimeMillis();
+            threadManager.runInParallel(cnt);
+            //        threadManager.runSequentially();
+            long t2  = System.currentTimeMillis();
+            long time = t2-t1;
+            //            System.err.println (cnt +" time:" + time);
+        }
+            //        }
+    }
+
+
+
+
+}
+
diff --git a/visad/util/ThreadPool.java b/visad/util/ThreadPool.java
new file mode 100644
index 0000000..3b45630
--- /dev/null
+++ b/visad/util/ThreadPool.java
@@ -0,0 +1,258 @@
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2013 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+ */
+
+package visad.util;
+
+import java.util.Collection;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * A pool of threads which can be used to execute any Runnable tasks. Internally
+ * this class uses a java.util.concurrent.ThreadPoolExecutor, but originally it
+ * did not because the original class predates java.util.concurrent. As the
+ * internals of this class have evolved, an effort has been made to preserve the
+ * original API. Note that a java.util.concurrent.ThreadPoolExecutor does not
+ * support the notion of minimum and maximum threads so minimum threads is
+ * ignored, and maximum threads is simply the size of the thread pool.
+ */
+public class ThreadPool {
+
+	/** Default prefix */
+	private static final String DEFAULT_PREFIX = ThreadPool.class.toString();
+
+	/** Thread pool from core Java */
+	private final ThreadPoolExecutor exec;
+
+	/**
+	 * We just need a thread-safe, lock-free, high-performance bag. It does not
+	 * matter that it is a queue.
+	 */
+	private final Collection<Future<?>> bagOfFutures = new ConcurrentLinkedQueue<Future<?>>();
+
+	/** waitForTasks requires that we lock this class for a moment */
+	private final Object mutex = new Object();
+
+	/**
+	 * The available processors on the system. Java Concurrency in Practice, 8.2
+	 * has more information on tuning this number
+	 */
+	private static final int PROCESSORS = Runtime.getRuntime()
+			.availableProcessors() + 1;
+
+	/** The prefix for this thread pool. */
+	private final String prefix;
+
+	/**
+	 * Build a thread pool with the default thread name prefix and the default
+	 * minimum and maximum numbers of threads.
+	 * 
+	 * @throws Exception
+	 */
+	public ThreadPool() throws Exception {
+		this(DEFAULT_PREFIX, 0, PROCESSORS);
+	}
+
+	/**
+	 * Build a thread pool with the specified thread name prefix, and the default
+	 * minimum and maximum numbers of threads
+	 * 
+	 * @param prefix
+	 * 
+	 * @throws Exception
+	 */
+	public ThreadPool(String prefix) throws Exception {
+		this(prefix, 0, PROCESSORS);
+	}
+
+	/**
+	 * Build a thread pool with the specified maximum number of threads, and the
+	 * default thread name prefix and minimum number of threads
+	 * 
+	 * @param max
+	 * 
+	 * @throws Exception
+	 */
+	public ThreadPool(int max) throws Exception {
+		this(DEFAULT_PREFIX, 0, max);
+	}
+
+	/**
+	 * Build a thread pool with the specified minimum and maximum numbers of
+	 * threads, and the default thread name prefix.
+	 * 
+	 * @param min
+	 * @param max
+	 * 
+	 * @throws Exception
+	 */
+	public ThreadPool(int min, int max) throws Exception {
+		this(DEFAULT_PREFIX, min, max);
+	}
+
+	/**
+	 * Build a thread pool with the specified thread name prefix and minimum and
+	 * maximum numbers of threads
+	 * 
+	 * @param prefix
+	 * @param min
+	 * @param max
+	 * 
+	 * @throws Exception
+	 */
+	public ThreadPool(String prefix, int min, int max) throws Exception {
+		this.prefix = prefix;
+
+		// Tom & Don TODO:
+		// I would say it is never advisable to go below PROCESSORS constant. The
+		// test I ran assume this. But I also want to keep the API as it stands now.
+		// I'll let you make the final call here.
+
+		// Could check for this: exec = (ThreadPoolExecutor) Executors.newFixedThreadPool(max < PROCESSORS ? PROCESSORS : max);
+		exec = (ThreadPoolExecutor) Executors.newFixedThreadPool(max);
+	}
+
+	/**
+	 * Return the number of tasks in the queue that are running. Note this
+	 * number is constantly changing so check than act is not really recommended.
+	 * 
+	 * @return a rough number of queued and active tasks
+	 */
+	public int getTaskCount() {
+		synchronized (mutex) {
+			for (Future<?> f : bagOfFutures) {
+				if (f.isDone()) {
+					bagOfFutures.remove(f);
+				}
+			}
+			return bagOfFutures.size();
+		}
+	}
+
+	// WLH 17 Dec 2001
+
+	/**
+	 * Remove this task from the tread pool.
+	 * 
+	 * @param r
+	 *          the runnable to remove from the queue
+	 */
+	public void remove(Runnable r) {
+		exec.remove(r);
+	}
+
+	/**
+	 * Has the thread pool been closed?
+	 * 
+	 * @return <tt>true</tt> if the pool has been terminated.
+	 */
+	public boolean isTerminated() {
+		return exec.isTerminated();
+	}
+
+	/**
+	 * Utility method to print out the tasks
+	 */
+
+	public void printPool() {
+		System.err.println("Busy Tasks:");
+		for (Future<?> f : bagOfFutures) {
+			if (!f.isDone()) {
+				System.out.println(f.toString());
+			}
+		}
+		System.err.println("Completed Tasks:");
+		for (Future<?> f : bagOfFutures) {
+			if (f.isDone()) {
+				System.out.println(f.toString());
+			}
+		}
+	}
+
+	/**
+	 * Add a task to the queue; tasks are executed as soon as a thread is
+	 * available, in the order in which they are submitted
+	 * 
+	 * @param r
+	 *          the runnable that will be executed by this thread pool.
+	 */
+	public void queue(Runnable r) {
+		Future<?> submit = exec.submit(r);
+		bagOfFutures.add(submit);
+
+		// While we are at it, clean out the bag of completed tasks.
+		for (Future<?> f : bagOfFutures) {
+			if (f.isDone()) {
+				bagOfFutures.remove(f);
+			}
+		}
+	}
+
+	/**
+	 * Wait for currently-running tasks to finish. Blocks while the internal queue
+	 * of tasks is completed.
+	 * 
+	 * @return true
+	 */
+	public boolean waitForTasks() {
+		// Lock the thread pool and drain all pending task.
+		synchronized (mutex) {
+			for (Future<?> f : bagOfFutures) {
+				try {
+					f.get();
+					// Tom & Don TODO: handle the exception in whatever way you think is
+					// best. The TheadPool previously just swallowed exceptions so that is 
+					// how I am leaving it, but in principle, this is not advisable.
+				} catch (InterruptedException e) {
+					// http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html
+					Thread.currentThread().interrupt();
+				} catch (ExecutionException e) {
+					// The runnable threw exception.
+					//e.printStackTrace();
+				}
+			}
+			// We are now clear of futures.
+			bagOfFutures.clear();
+		}
+		return true;
+	}
+
+	/**
+	 * Set the maximum number of pooled threads
+	 * 
+	 * @param num
+	 *          the number of threads
+	 * 
+	 * @throws Exception
+	 */
+	public void setThreadMaximum(int num) throws Exception {
+		exec.setCorePoolSize(num);
+	}
+
+	/** Shut down this thread pool. */
+	public void stopThreads() {
+		exec.shutdown();
+	}
+}
diff --git a/visad/util/Trace.java b/visad/util/Trace.java
new file mode 100644
index 0000000..e7c7696
--- /dev/null
+++ b/visad/util/Trace.java
@@ -0,0 +1,176 @@
+//
+// Trace.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.lang.reflect.Method;
+
+/**
+ * This class provides a hook into the IDV Trace facility. It uses reflection to call
+ * the  corresponding ucar.unidata.util.Trace methods if that class can be found. 
+ * Else, nothing happens.
+ * To use this first call: Trace.startTrace();
+ * <p>
+ * You can bracket a code segment with:<pre>
+ *  Trace.call1("some label");
+ *  ... code
+ *  Trace.call2("some label");
+ * </pre>
+ * This matches the "some label". It will indent subsequent Trace calls to show nesting.
+ * You can also just print something out with:<pre>
+ * Trace.msg("some message");
+ * </pre>
+ * The output looks like:<pre>
+ * 196  996   >ImageRenderer type.doTransform
+ * 11   99      >ImageRenderer build texture length: 4503200
+ * 2    15        >ImageRenderer new byte
+ * 691  15683     <ImageRenderer new byte ms: 690
+ * 1    1         >ImageRenderer color_bytes
+ * 345  99        <ImageRenderer color_bytes ms: 344
+ * 0    2       <ImageRenderer build texture ms: 1037
+ * </pre>
+ * The first column is the elapsed time since the last print line. The second column is the 
+ * memory delta (note: GC can make this negative). For the call1/call2 pairs the ms:... shows
+ * the time spent in the block.
+ *
+ */
+
+public  class Trace
+{
+
+    private static boolean doneReflectionLookup = false;
+
+    private static Method call1Method;
+    private static Method call2Method;
+    private static Method msgMethod;
+    private static Method startMethod;
+    private static Method stopMethod;
+
+
+    public static void main(String[]args) {
+        //        Trace.startTrace();
+        Trace.call1("hello");
+        Trace.msg("hello there");
+        Trace.call2("hello");
+    }
+
+    /** Try to load the ucar Trace facility via reflection
+     *
+     * @return Was successful
+     */
+    private static boolean checkReflection() {
+        if(!doneReflectionLookup) {
+            try {
+                Class c = Class.forName("ucar.unidata.util.Trace");
+                call1Method = c.getDeclaredMethod("call1", new Class[]{String.class,String.class});
+                call2Method = c.getDeclaredMethod("call2", new Class[]{String.class,String.class});
+                msgMethod = c.getDeclaredMethod("msg", new Class[]{String.class});
+                startMethod = c.getDeclaredMethod("startTrace", new Class[]{});
+                stopMethod = c.getDeclaredMethod("stopTrace", new Class[]{});
+            } catch(Exception exc){
+            }
+            doneReflectionLookup = true;
+        }
+        return msgMethod!=null;
+    }
+
+
+    /** 
+     * Bracket a block of code with call1("some unique msg"); ...code...; call2("some unique msg"); 
+     * Where "some unique msg" is used to match up the call1/call2 pairs
+     */
+    public static void call1(String msg) {
+        call1(msg,"");
+    }
+
+    /** Bracket a block of code with call1("some unique msg"); ...code...; call2("some unique msg");
+     *  Append extra to the end of the line
+     */
+    public static void call1(String msg, String extra) {
+        if(!checkReflection()) return;
+        try {
+            call1Method.invoke(null, new Object[]{msg,extra});
+        } catch(Exception iae){
+            System.err.println("Trace.call1:" + iae);
+        }
+    }
+
+    /** Call this to start tracing */
+    public static void startTrace() {
+        if(!checkReflection()) {
+            System.err.println("Could not start tracing");
+            return;
+        }
+        try {
+            startMethod.invoke(null, new Object[]{});
+        } catch(Exception iae){
+            System.err.println("Trace.startTrace:" + iae);
+        }
+    }
+
+    /** Call this to start tracing */
+    public static void stopTrace() {
+        if(!checkReflection()) {
+            System.err.println("Could not stop tracing");
+            return;
+        }
+        try {
+            stopMethod.invoke(null, new Object[]{});
+        } catch(Exception iae){
+            System.err.println("Trace.stopTrace:" + iae);
+        }
+    }
+
+
+
+    /** Bracket a block of code with call1("some unique msg"); ...code...; call2("some unique msg"); */
+    public static void call2(String msg) {
+        call2(msg,"");
+    }
+
+    /** Close the call */
+    public static void call2(String msg, String extra) {
+        if(!checkReflection()) return;
+        try {
+            call2Method.invoke(null, new Object[]{msg,extra});
+        } catch(Exception iae){
+            System.err.println("Trace.call2:" + iae);
+        }
+    }
+
+
+    /** Print out a line */
+    public static void msg(String msg) {
+        if(!checkReflection()) return;
+        try {
+            msgMethod.invoke(null, new Object[]{msg});
+        } catch(Exception iae){
+            System.err.println("Trace.msg:" + iae);
+        }
+    }
+
+}
+
diff --git a/visad/util/Util.java b/visad/util/Util.java
new file mode 100644
index 0000000..92f17de
--- /dev/null
+++ b/visad/util/Util.java
@@ -0,0 +1,1097 @@
+//
+// Util.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.image.BufferedImage;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.media.j3d.Canvas3D;
+import javax.media.j3d.Group;
+import javax.media.j3d.Node;
+import javax.media.j3d.SceneGraphObject;
+import javax.media.j3d.VirtualUniverse;
+
+import javax.swing.JComboBox;
+import javax.swing.JFileChooser;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.filechooser.FileFilter;
+
+import ncsa.hdf.hdf5lib.H5;
+
+import visad.ConstantMap;
+import visad.Display;
+import visad.DisplayImpl;
+import visad.VisADException;
+
+import visad.data.bio.LociForm;
+import visad.data.mcidas.AreaForm;
+import visad.data.mcidas.MapForm;
+
+import com.sun.j3d.utils.universe.SimpleUniverse;
+
+import java.util.logging.Formatter;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+
+/**
+ * A hodge-podge of general utility methods.
+ */
+public class Util {
+
+  /**
+   * Determine whether two numbers are roughly the same.
+   *
+   * @param a First number
+   * @param b Second number
+   * @param epsilon Absolute amount by which they can differ.
+   *
+   * @return <TT>true</TT> if they're approximately equal.
+   */
+  public static boolean isApproximatelyEqual(float a, float b,
+                                             float epsilon) {
+    // deal with NaNs first
+    if (Float.isNaN(a)) {
+      return Float.isNaN(b);
+    }
+    else if (Float.isNaN(b)) {
+      return false;
+    }
+
+    if (Float.isInfinite(a)) {
+      if (Float.isInfinite(b)) {
+        return ((a < 0.0 && b < 0.0) || (a > 0.0 && b > 0.0));
+      }
+
+      return false;
+    }
+    else if (Float.isInfinite(b)) {
+      return false;
+    }
+
+    if (Math.abs(a - b) < epsilon) {
+      return true;
+    }
+
+    if (a == 0.0 || b == 0.0) {
+      return false;
+    }
+
+    final float FLT_EPSILON = 1.192092896E-07F;
+
+    return Math.abs(1 - a / b) < FLT_EPSILON;
+  }
+
+  /**
+   * Determine whether two numbers are roughly the same.
+   *
+   * @param a First number
+   * @param b Second number
+   *
+   * @return <TT>true</TT> if they're approximately equal.
+   */
+  public static boolean isApproximatelyEqual(float a, float b) {
+    return isApproximatelyEqual(a, b, 0.00001);
+  }
+
+  /**
+   * Determine whether two numbers are roughly the same.
+   *
+   * @param a First number
+   * @param b Second number
+   * @param epsilon Absolute amount by which they can differ.
+   *
+   * @return <TT>true</TT> if they're approximately equal.
+   */
+  public static boolean isApproximatelyEqual(double a, double b,
+                                             double epsilon) {
+    // deal with NaNs first
+    if (Double.isNaN(a)) {
+      return Double.isNaN(b);
+    }
+    else if (Double.isNaN(b)) {
+      return false;
+    }
+
+    if (Double.isInfinite(a)) {
+      if (Double.isInfinite(b)) {
+        return ((a < 0.0 && b < 0.0) || (a > 0.0 && b > 0.0));
+      }
+
+      return false;
+    }
+    else if (Double.isInfinite(b)) {
+      return false;
+    }
+
+    if (Math.abs(a - b) < epsilon) {
+      return true;
+    }
+
+    if (a == 0.0 || b == 0.0) {
+      return false;
+    }
+
+    final double DBL_EPSILON = 2.2204460492503131E-16;
+
+    return Math.abs(1 - a / b) < DBL_EPSILON;
+  }
+
+  /**
+   * Determine whether two numbers are roughly the same.
+   *
+   * @param a First number
+   * @param b Second number
+   *
+   * @return <TT>true</TT> if they're approximately equal.
+   */
+  public static boolean isApproximatelyEqual(double a, double b) {
+    return isApproximatelyEqual(a, b, 0.000000001);
+  }
+
+  /**
+   * Return a string representation of VisAD's build date and time.
+   *
+   * @return VisAD build date and time
+   */
+  
+  public static String getVersionDate() {
+    try {
+      InputStream is = Util.class.getResourceAsStream("/DATE");
+      BufferedReader in = new BufferedReader(new InputStreamReader(is));
+      String date = in.readLine();
+      in.close();
+      return date;
+    }
+    catch (IOException exc) {
+      return null;
+    }
+  }
+
+  /**
+   * Print Java3D properties.
+   * @param str Where to print properties.
+   * @param canvas The Canvas3D to get properties from. If null Canvas3D
+   *  properties are skipped.
+   *
+   * @see #printJ3DProperties(Canvas3D)
+   */
+  public static void printJ3DProperties(PrintStream str, Canvas3D canvas) {
+    Map map = VirtualUniverse.getProperties();
+    for (Object key : map.keySet()) {
+      str.println(String.format("%s=%s", key, map.get(key)));
+    }
+
+    if (canvas == null) {
+      canvas = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
+    }
+    str.println("== Canvas3D properties ==");
+    map = canvas.queryProperties();
+    for (Object key : map.keySet()) {
+      str.println(String.format("%s=%s", key, map.get(key).toString()));
+    }
+  }
+
+  /**
+   * Print Java3D global and Canvas3D properties to <code>System.err</code>.
+   * @param canvas The Canvas3D to get properties from. If null Canvas3D
+   *  properties are skipped.
+   *
+   * @see javax.media.j3d.VirtualUniverse#getProperties()
+   * @see javax.media.j3d.Canvas3D#queryProperties()
+   */
+  public static void printJ3DProperties(Canvas3D canvas) {
+    printJ3DProperties(System.err, canvas);
+  }
+
+  /**
+   * 
+   *
+   * @param node 
+   */
+  public static void printSceneGraph(Node node) {
+    if (node == null) return;
+    printSceneGraph(node, 0);
+  }
+
+  /**
+   * 
+   *
+   * @param node 
+   * @param lvl 
+   */
+  private static void printSceneGraph(Node node, int lvl) {
+    StringBuffer buf = new StringBuffer();
+    for (int i = 0; i < lvl; i++) {
+      buf.append("    ");
+    }
+    lvl++;
+    System.err.println(buf.toString() + node.toString());
+    if (node instanceof Group) {
+      Group group = (Group)node;
+      Enumeration children = group.getAllChildren();
+      while(children.hasMoreElements()) {
+        printSceneGraph((Node)children.nextElement(), lvl);
+      }
+    }
+  }
+
+  // Map<SceneGraphObject, String>
+  //private static final Map SGO_NAMES = new HashMap();
+  private static final Method SGO_GET_NAME;
+  private static final Method SGO_SET_NAME;
+  static {
+    Method sgoGetName = null, sgoSetName = null;
+    try {
+      sgoGetName = SceneGraphObject.class.getDeclaredMethod("getName",
+        new Class[0]);
+      sgoSetName = SceneGraphObject.class.getDeclaredMethod("setName",
+        new Class[] {String.class});
+    }
+    catch (SecurityException e) {
+    }
+    catch (NoSuchMethodException e) {
+    }
+    SGO_GET_NAME = sgoGetName;
+    SGO_SET_NAME = sgoSetName;
+  }
+
+  /**
+   * Gets the name of the given {@link SceneGraphObject}.
+   *
+   * This method exists to avoid a compile-time
+   * dependency on Java3D 1.4+.
+   */
+  public static String getName(SceneGraphObject obj) {
+    if (SGO_GET_NAME != null) {
+      try {
+        return (String) SGO_GET_NAME.invoke(obj);
+      }
+      catch (IllegalAccessException exc) {
+      }
+      catch (InvocationTargetException exc) {
+      }
+    }
+    else {
+      // no SceneGraphObject.getName method; retrieve name from Map instead
+      //return (String) SGO_NAMES.get(obj);
+    }
+    return null;
+  }
+
+  /**
+   * Sets the name of the given {@link SceneGraphObject}.
+   *
+   * This method exists to avoid a compile-time
+   * dependency on Java3D 1.4+.
+   */
+  public static void setName(SceneGraphObject obj, String name) {
+    if (SGO_SET_NAME != null) {
+      try {
+        SGO_SET_NAME.invoke(obj, name);
+      }
+      catch (IllegalAccessException exc) {
+      }
+      catch (InvocationTargetException exc) {
+      }
+    }
+    else {
+      // no SceneGraphObject.setName method; save name to Map instead
+      //SGO_NAMES.put(obj, name);
+    }
+  }
+
+  /**
+   * Convert 10-bit data to 2-byte data
+   * @param input
+   * @param output
+   * @return 0 if no errors
+   */
+  public static int tenBitToTwoByte(byte [] input, short [] output) {
+
+	  int total = output.length;
+	  int index = 0;
+	  int temp = 0;
+	  int skip = 0;
+	  int outputIndex = 0;
+
+	  index = skip / 8;
+	  skip = skip % 8;
+	  //input = (unsigned char *) inp;
+
+	  while (total > 0)
+	  {
+		  total--;
+
+		  /*
+		   * enumerated to avoid the more general need
+		   * to always access 3 bytes
+		   * which in reality is needed only for case 7
+		   */
+		  switch (skip)
+		  {
+		  case 0:
+			  temp = 4 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 64;
+			  break;
+		  case 1:
+			  temp = 8 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 32;
+			  break;
+		  case 2:
+			  temp = 16 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 16;
+			  break;
+		  case 3:
+			  temp = 32 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 8;
+			  break;
+		  case 4:
+			  temp = 64 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 4;
+			  break;
+		  case 5:
+			  temp = 128 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF) / 2;
+			  break;
+		  case 6:
+			  temp = 256 * (int) (input[index] & 0xFF) +  (int) (input[index + 1] & 0xFF);
+			  break;
+		  case 7:
+			  temp = 512 *(1& (int) (input[index] & 0xFF)) + 2 * (int) (input[index + 1] & 0xFF)
+			                                        + ( (int) (input[index + 2] & 0xFF) > 127 ? 1 : 0);
+			  break;
+		  }
+
+		  output[outputIndex] = (short) (temp & 0x3ff);
+		  outputIndex++;
+
+		  /*
+		   * these two statements together increment 10 bits on the input
+		   */
+		  index++;
+		  skip += 2;
+
+		  /*
+		   * now normalize skip
+		   */
+		  if (skip > 7)
+		  {
+			  index++;
+			  skip -= 8;
+		  }
+	  }
+
+	  return 0;
+  }
+  
+  /**
+   * Return a string representation of the current date and time.
+   *
+   * @return string timestamp for current time
+   */
+  
+  public static String getTimestamp() {
+    StringBuffer sb = new StringBuffer();
+    Calendar cal = Calendar.getInstance();
+    int year = cal.get(Calendar.YEAR);
+    int month = cal.get(Calendar.MONTH);
+    int day = cal.get(Calendar.DAY_OF_MONTH);
+    int hour = cal.get(Calendar.HOUR_OF_DAY);
+    int min = cal.get(Calendar.MINUTE);
+    int sec = cal.get(Calendar.SECOND);
+    int milli = cal.get(Calendar.MILLISECOND);
+    sb.append(year);
+    sb.append("/");
+    if (month < 9) sb.append("0");
+    sb.append(month + 1);
+    sb.append("/");
+    if (day < 10) sb.append("0");
+    sb.append(day);
+    sb.append(", ");
+    if (hour < 10) sb.append("0");
+    sb.append(hour);
+    sb.append(":");
+    if (min < 10) sb.append("0");
+    sb.append(min);
+    sb.append(":");
+    if (sec < 10) sb.append("0");
+    sb.append(sec);
+    sb.append(".");
+    if (milli < 100) sb.append("0");
+    if (milli < 10) sb.append("0");
+    sb.append(milli);
+    return sb.toString();
+  }
+
+  /**
+   * Return a JFileChooser that recognizes supported VisAD file types.
+   *
+   * @return file chooser which recognizes supported VisAD file types
+   */
+  
+  public static JFileChooser getVisADFileChooser() {
+    JFileChooser dialog = new JFileChooser(System.getProperty("user.dir"));
+    Vector filters = new Vector();
+    boolean jai = canDoJAI();
+
+    // Amanda F2000 - amanda/F2000Form
+    FileFilter f2000 = new ExtensionFileFilter("r", "Amanda F2000");
+    filters.add(f2000);
+
+    // ASCII text - text/TextForm
+    FileFilter text = new ExtensionFileFilter(new String[] {"csv", "tsv",
+            "bsv", "txt"}, "ASCII text");
+    filters.add(text);
+
+    // DEM - gis/DemFamily
+    FileFilter dem = new ExtensionFileFilter("dem",
+                                             "Digital Elevation Model");
+    filters.add(dem);
+
+    // FITS - fits/FitsForm
+    FileFilter fits = new ExtensionFileFilter("fits",
+                        "Flexible Image Transport System");
+    filters.add(fits);
+
+    // FlashPix - jai/JAIForm
+    if (jai) {
+      FileFilter flashpix = new ExtensionFileFilter("flashpix", "FlashPix");
+      filters.add(flashpix);
+    }
+
+    // HDF-5 - hdf5/HDF5Form
+    if (canDoHDF5()) {
+      FileFilter hdf5 = new ExtensionFileFilter(new String[] {"hdf", "hdf5"},
+                          "HDF-5");
+      filters.add(hdf5);
+    }
+
+    // HDF-EOS - hdfeos/HdfeosForm
+    FileFilter hdfeos = new ExtensionFileFilter(new String[] {"hdf",
+            "hdfeos"}, "HDF-EOS");
+    filters.add(hdfeos);
+
+    // McIDAS area - mcidas/AreaForm
+    FormFileFilter mcidasArea = new FormFileFilter(new AreaForm(),
+                                  "McIDAS area (AREA*, *area)");
+    filters.add(mcidasArea);
+
+    // McIDAS map - mcidas/MapForm
+    FormFileFilter mcidasMap = new FormFileFilter(new MapForm(),
+                                 "McIDAS map (OUTL*)");
+    filters.add(mcidasMap);
+
+    // netCDF - netcdf/Plain
+    FileFilter netcdf = new ExtensionFileFilter("nc", "NetCDF");
+    filters.add(netcdf);
+
+    // PNM - jai/JAIForm
+    if (jai) {
+      FileFilter pnm = new ExtensionFileFilter("pnm", "PNM");
+      filters.add(pnm);
+    }
+
+    // VisAD binary/serialized - visad/VisADForm
+    FileFilter visad = new ExtensionFileFilter("vad",
+                         "Binary or serialized VisAD");
+    filters.add(visad);
+
+    // Vis5D - vis5d/Vis5DForm
+    FileFilter vis5d = new ExtensionFileFilter("v5d", "Vis5D");
+    filters.add(vis5d);
+
+    // biology-related formats - LociForm
+    FileFilter[] lociFilters = new LociForm().getReaderFilters();
+    for (int i = 0; i < lociFilters.length; i++)
+      filters.add(lociFilters[i]);
+
+    // sort and combine filters alphanumerically
+    FileFilter[] ff = ComboFileFilter.sortFilters(filters);
+
+    // combination filter
+    FileFilter combo = new ComboFileFilter(ff, "All VisAD file types");
+
+    // add filters to chooser
+    dialog.addChoosableFileFilter(combo);
+    for (int i = 0; i < ff.length; i++)
+      dialog.addChoosableFileFilter(ff[i]);
+    dialog.setFileFilter(combo);
+
+    return dialog;
+  }
+
+  /**
+   * Limit the given text field to one line in height.
+   *
+   * @param field 
+   */
+  public static void adjustTextField(JTextField field) {
+    Dimension msize = field.getMaximumSize();
+    Dimension psize = field.getPreferredSize();
+    msize.height = psize.height;
+    field.setMaximumSize(msize);
+  }
+
+  /**
+   * Limit the given combo box to one line in height.
+   *
+   * @param combo 
+   */
+  public static void adjustComboBox(JComboBox combo) {
+    Dimension msize = combo.getMaximumSize();
+    Dimension psize = combo.getPreferredSize();
+    msize.height = psize.height;
+    combo.setMaximumSize(msize);
+  }
+
+  /**
+   * Center the given window on the screen.
+   *
+   * @param window 
+   */
+  public static void centerWindow(Window window) {
+    Dimension s = Toolkit.getDefaultToolkit().getScreenSize();
+    Dimension w = window.getSize();
+    window.setLocation((s.width - w.width) / 2, (s.height - w.height) / 2);
+    int x = (s.width - w.width) / 2;
+    int y = (s.height - w.height) / 2;
+    if (x < 0) x = 0;
+    if (y < 0) y = 0;
+    window.setLocation(x, y);
+  }
+
+  /**
+   * Center the given window within the specified parent window.
+   *
+   * @param parent 
+   * @param window 
+   */
+  public static void centerWindow(Window parent, Window window) {
+    Point loc = parent.getLocation();
+    Dimension p = parent.getSize();
+    Dimension w = window.getSize();
+    int x = loc.x + (p.width - w.width) / 2;
+    int y = loc.y + (p.height - w.height) / 2;
+    if (x < 0) x = 0;
+    if (y < 0) y = 0;
+    window.setLocation(x, y);
+  }
+
+  /**
+   * Test whether HDF-5 native code is present in this JVM.
+   * @return true if found, otherwise false
+   */
+  public static boolean canDoHDF5() {
+    boolean success = false;
+    try {
+      H5.J2C(0); // HDF-5 call initializes HDF-5 native library
+      success = true;
+    }
+    catch (NoClassDefFoundError err) {
+    }
+    catch (UnsatisfiedLinkError err) {
+    }
+    catch (Exception exc) {
+    }
+    return success;
+  }
+
+  /**
+   * Test whether ImageJ is present in this JVM.
+   * @return true if found, otherwise false
+   */
+  public static boolean canDoImageJ() {
+    return canDoClass("ij.IJ") != null;
+  }
+
+  /**
+   * Test whether <code>javax.imageio</code> can write JPEGs.
+   * @return true if found, otherwise false
+   */
+  public static boolean canDoJPEG() {
+    Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
+    return iter.hasNext();
+  }
+
+  /**
+   * Test whether Java Advanced Imaging is present in this JVM.
+   * @return true if found, otherwise false
+   */
+  public static boolean canDoJAI() {
+    return canDoClass("javax.media.jai.JAI") != null;
+  }
+
+  /**
+   * Test whether Jython is present in this JVM.
+   * @return true if found, otherwise false
+   */
+  public static boolean canDoPython() {
+    return canDoClass("org.python.util.PythonInterpreter") != null;
+  }
+
+  /**
+   * Test whether QuickTime for Java is present in this JVM.
+   * @return true if found, otherwise false
+   */
+  public static boolean canDoQuickTime() {
+    return canDoClass("quicktime.QTSession") != null;
+  }
+
+  /**
+   * Test whether Java3D is present in this JVM.
+   * @return true if found, otherwise false
+   */
+  public static boolean canDoJava3D() {
+    return canDoJava3D("1.0");
+  }
+
+  /**
+   * Check to see if the version of Java3D being used is compatible
+   * with the desired specification version.
+   * @param version   version to check.  Needs to conform to the dotted format
+   *                  of specification version numbers (e.g., 1.2)
+   * @return true if the Java3D version being used is greater than or
+   *         equal to the desired version number
+   */
+  public static boolean canDoJava3D(String version) {
+    Class testClass = canDoClass("javax.vecmath.Point3d");
+    boolean b = (testClass != null)
+                ? (canDoClass("javax.media.j3d.SceneGraphObject") != null)
+                : false;
+    if (b) {
+      Package p = testClass.getPackage();
+      if (p != null) {
+        try {
+          b = p.isCompatibleWith(version);
+        }
+        catch (NumberFormatException nfe) {
+          b = false;
+        }
+      }
+    }
+    return b;
+  }
+
+  /**
+   * General classloader tester.
+   * @param classname  name of class to test
+   * @return  the class or null if class can't be loaded.
+   */
+  private static Class canDoClass(String classname) {
+    Class c = null;
+    try {
+      c = Class.forName(classname);
+    }
+    catch (ClassNotFoundException exc) {
+    }
+    return c;
+  }
+
+  /**
+   * Capture a DisplayImpl into a JPEG file
+   *
+   * @param display the DisplayImpl to capture
+   * @param filename the name of the file to write into
+   *
+   */
+  public static void captureDisplay(DisplayImpl display, String filename) {
+    captureDisplay(display, filename, false);
+  }
+
+  /**
+   * Capture a DisplayImpl into a JPEG file
+   *
+   * @param display the DisplayImpl to capture
+   * @param filename the name of the file to write into
+   * @param sync ensure the display is "done" if true
+   *
+   */
+  public static void captureDisplay(DisplayImpl display, String filename,
+                                    boolean sync) {
+    final DisplayImpl disp = display;
+    final File fn = new File(filename);
+    final boolean wait = sync;
+
+    Runnable savedisp = new Runnable() {
+      public void run() {
+        BufferedImage image = disp.getImage(wait);
+        try {
+            Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpeg");
+            ImageWriter writer = iter.next();
+            ImageWriteParam param = writer.getDefaultWriteParam();
+            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            param.setCompressionQuality(1.0f);
+            FileOutputStream fout = new FileOutputStream(fn);
+            writer.setOutput(fout);
+            IIOImage iio = new IIOImage(image, null, null);
+            writer.write(null, iio, param);
+          fout.close();
+        }
+        catch (Exception err) {
+          System.err.println("Error whilst saving JPEG: " + err);
+        }
+      }
+    };
+    Thread t = new Thread(savedisp);
+    t.start();
+  }
+
+  /**
+   * Tests whether two arrays are component-wise equal.
+   *
+   * @param o1 
+   * @param o2 
+   *
+   * @return true if the arrays are equal
+   */
+  
+  public static boolean arraysEqual(Object[] o1, Object[] o2) {
+    // test for null
+    if (o1 == null && o2 == null) return true;
+    if (o1 == null || o2 == null) return false;
+
+    // test for differing lengths
+    if (o1.length != o2.length) return false;
+
+    // test each component
+    for (int i = 0; i < o1.length; i++) {
+      //if (!o1[i].equals(o2[i])) return false;
+      Object a1 = o1[i];
+      Object a2 = o2[i];
+      if (!(a1 == null
+            ? a2 == null
+            : a1.equals(a2))) return false;
+    }
+    return true;
+  }
+
+
+  /**
+   * Create a string representation of the given array
+   *
+   * @param prefix 
+   * @param array  array to print
+   *
+   */
+  
+  public static void printArray(String prefix, Object[] array) {
+    StringBuffer buf = new StringBuffer();
+    buf.append(prefix);
+    buf.append(": ");
+    if (array == null) {
+      buf.append(" null ");
+    }
+    else {
+      for (int i = 0; i < array.length; i++) {
+        buf.append("[");
+        buf.append(i);
+        buf.append("]: ");
+        buf.append((array[i] == null)
+                   ? "null"
+                   : array[i]);
+        buf.append(" ");
+      }
+    }
+    System.out.println(buf.toString());
+  }
+
+  /**
+   * Print out the values in a double array.
+   *
+   * @param prefix  prefix string
+   * @param array  array to print
+   */
+  public static void printArray(String prefix, double[] array) {
+    StringBuffer buf = new StringBuffer();
+    buf.append(prefix);
+    buf.append(": ");
+    if (array == null) {
+      buf.append(" null ");
+    }
+    else {
+      for (int i = 0; i < array.length; i++) {
+        buf.append("[");
+        buf.append(i);
+        buf.append("]: ");
+        buf.append(array[i]);
+        buf.append(" ");
+      }
+    }
+    System.out.println(buf.toString());
+  }
+
+  /**
+   * Executes the given Runnable object with the Swing event handling thread.
+   *
+   * @param wait <tt>true</tt> if method should block until Runnable code
+   *             finishes execution.
+   * @param r Runnable object to execute using the event handling thread.
+   */
+  public static void invoke(boolean wait, Runnable r) {
+    invoke(wait, false, r);
+  }
+
+  /**
+   * Executes the given Runnable object with the Swing event handling thread.
+   *
+   * @param wait <tt>true</tt> if method should block until Runnable code
+   *             finishes execution.
+   * @param printStackTraces <tt>true</tt> if the stack trace for
+   *                         any exception should be printed.
+   * @param r Runnable object to execute using the event handling thread.
+   */
+  public static void invoke(boolean wait, boolean printStackTraces,
+                            Runnable r) {
+    if (wait) {
+      // use invokeAndWait
+      if (Thread.currentThread().getName().startsWith("AWT-EventQueue")) {
+        // current thread is the AWT event queue thread; just execute the code
+        r.run();
+      }
+      else {
+        // execute the code with the AWT event thread
+        try {
+          SwingUtilities.invokeAndWait(r);
+        }
+        catch (InterruptedException exc) {
+          if (printStackTraces) exc.printStackTrace();
+        }
+        catch (InvocationTargetException exc) {
+          if (printStackTraces) exc.getTargetException().printStackTrace();
+        }
+      }
+    }
+    else {
+      // use invokeLater
+      SwingUtilities.invokeLater(r);
+    }
+  }
+
+  /**
+   * Create a ConstantMap array of colors for use with
+   * {@link Display#addReference(DataReference, ConstantMap[])}
+   *
+   * @param color color to encode
+   *
+   * @return an array containing either 3 colors or, if the <tt>color</tt>
+   *         parameter included an alpha component, a 4 element array
+   *         with 3 colors and an @{link Display.Alpha alpha} component.
+   *
+   * @throws VisADException 
+   */
+  public static ConstantMap[] getColorMaps(Color color)
+          throws VisADException {
+    final int alpha = color.getAlpha();
+
+    ConstantMap[] maps = new ConstantMap[alpha == 255
+                                         ? 3
+                                         : 4];
+
+    maps[0] = new ConstantMap((float)color.getRed() / 255.0f, Display.Red);
+    maps[1] = new ConstantMap((float)color.getGreen() / 255.0f,
+                              Display.Green);
+    maps[2] = new ConstantMap((float)color.getBlue() / 255.0f, Display.Blue);
+    if (alpha != 255) {
+      maps[3] = new ConstantMap((float)color.getAlpha() / 255f,
+                                Display.Alpha);
+    }
+
+    return maps;
+  }
+
+  /**
+   * Configure basic logging for the visad package. In a production
+   * environment the preferred way to configure logging is using the
+   * logging.properties file. This is intended only as a convenience method
+   * for configuring console logging for the purposes of testing.
+   *
+   * @param verbosity 0 is <code>Level.WARNING</code> and progresses to a
+   *  maximum of <code>Level.ALL</code>.
+   * @param pkg Name of the java package to configure logging for.
+   * @return The <code>Level</code> logging was set to.
+   */
+  public static Level configureLogging(int verbosity, String pkg) {
+    Level lvl = Level.WARNING;
+    switch (verbosity) {
+      case 1:
+        lvl = Level.INFO;
+        break;
+      case 2:
+        lvl = Level.FINE;
+        break;
+      case 3:
+        lvl = Level.FINER;
+        break;
+      case 4:
+        lvl = Level.FINEST;
+        break;
+      case 5:
+        lvl = Level.ALL;
+        break;
+      default:
+        lvl = Level.WARNING;
+    }
+    Logger logger = Logger.getLogger(pkg);
+    logger.setLevel(lvl);
+    logger.setUseParentHandlers(false);
+    Handler console = new ConsoleHandler();
+    console.setLevel(lvl);
+    console.setFormatter(new Formatter() {
+      public String format(LogRecord r) {
+        return String.format(
+                 "[%s] %s\n", r.getLevel().getName(), r.getMessage());
+      }
+    });
+    logger.addHandler(console);
+    return lvl;
+  }
+
+  /**
+   * @see #configureLogging(int, java.lang.String)
+   *
+   * @param verbosity 
+   *
+   * @return logging level
+   */
+  
+  public static Level configureLogging(int verbosity) {
+    return configureLogging(verbosity, "visad");
+  }
+
+
+  /**
+   * Utility method to return the stack trace
+   *
+   * @return The stack trace
+   */
+  public static String getStackTrace() {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    (new IllegalArgumentException("stack trace")).printStackTrace(
+      new PrintStream(baos));
+    return baos.toString();
+  }
+
+
+  /**
+   * do a deep clone 
+   *
+   * @param input  the array
+   *
+   * @return  cloned array
+   */
+  public static float[][] clone(float[][] input) {
+    float[][] output = (float[][])input.clone();
+    for (int i = 0; i < input.length; i++) {
+      output[i] = (float[])input[i].clone();
+    }
+    return output;
+  }
+
+
+  /**
+   * do a deep clone 
+   *
+   * @param input  the array
+   *
+   * @return  cloned array
+   */
+  public static double[][] clone(double[][] input) {
+    double[][] output = (double[][])input.clone();
+    for (int i = 0; i < input.length; i++) {
+      output[i] = (double[])input[i].clone();
+    }
+    return output;
+  }
+
+	/**
+	 * Converts an (unsigned) byte to an unsigned int. 
+	 * Since Java doesn't have an unsigned
+	 * byte type, this requires some foolery.
+	 * This solution based on information and code from
+	 * http://www.rgagnon.com/javadetails/java-0026.html
+	 * @param s The unsigned short to convert
+	 * @return the unsigned int equivalent
+	 */
+	
+	public static int unsignedShortToInt(short s) {
+		return (int) s & 0xFFFF;
+	}
+
+	/**
+	 * Converts an (unsigned) byte to an unsigned int. 
+	 * Since Java doesn't have an unsigned
+	 * byte type, this requires some foolery.
+	 * This solution based on information and code from
+	 * http://www.rgagnon.com/javadetails/java-0026.html
+	 * @param b The unsigned byte to convert
+	 * @return the unsigned int equivalent
+	 */
+	
+	public static int unsignedByteToInt(byte b) {
+		return (int) b & 0xFF;
+	}
+	
+	/**
+	 * Converts an (unsigned) byte to an unsigned long.
+	 * Since Java doesn't have an unsigned
+	 * byte type, this requires some foolery.
+	 * This solution based on information and code from
+	 * http://www.rgagnon.com/javadetails/java-0026.html
+	 * @param b The unsigned byte to convert
+	 * @return the unsigned long equivalent
+	 */
+	public static long unsignedByteToLong(byte b) {
+		return (long) b & 0xFF;
+	}
+
+}
+
diff --git a/visad/util/VisADSlider.java b/visad/util/VisADSlider.java
new file mode 100644
index 0000000..13ab8ae
--- /dev/null
+++ b/visad/util/VisADSlider.java
@@ -0,0 +1,515 @@
+//
+// VisADSlider.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+// JFC packages
+import javax.swing.*;
+import javax.swing.event.*;
+
+// AWT class
+import java.awt.Dimension;
+
+// RMI class
+import java.rmi.RemoteException;
+
+// VisAD package
+import visad.*;
+
+/** VisADSlider combines a JSlider and a JLabel and links them to either a
+ *  Real (via a DataReference) or a ScalarMap that maps to
+ *  Display.SelectValue.  Changes in the slider will reflect the Real or
+ *  ScalarMap linked to it.  If no bounds are specified, they will be
+ *  detected from the ScalarMap and the slider will auto-scale.  Note that
+ *  a slider linked to a Real cannot auto-scale, because it has no way to
+ *  detect the bounds.<br>
+ * <br>
+ * {@link javax.swing.BoxLayout BoxLayout} doesn't handle a mixture
+ * of the standard center-aligned widgets and VisADSliders, which
+ * are left-aligned by default.  If you have problems with widgets
+ * being too wide, you may want to change the other widgets in
+ * the {@link javax.swing.JPanel JPanel} to align on the left
+ * (e.g. <tt>widget.setAlignmentX(BoxLayout.LEFT_ALIGNMENT)</tt>)
+ */
+public class VisADSlider extends JPanel implements ChangeListener,
+  ControlListener, ScalarMapListener
+{
+
+  /** The default number of ticks the slider should have */
+  private static final int D_TICKS = 1000;
+
+  /** Default width of the slider in pixels */
+  private static final int SLIDER_WIDTH = 150;
+
+  /** Default width of the label in pixels */
+  private static final int LABEL_WIDTH = 200;
+
+  /** The JSlider that forms part of the VisADSlider's UI */
+  private JSlider slider;
+
+  /** The JLabel that forms part of the VisADSlider's UI */
+  private JLabel label;
+
+  /** The ScalarMap that is linked to this VisADSlider (null if none) */
+  private ScalarMap map;
+
+  /** The ValueControl that this VisADSlider utilizes (null if none) */
+  private ValueControl control;
+
+  /** The DataReference that is linked to this VisADSlider (null if none) */
+  private DataReference sRef;
+
+  /** The type of the linked Real (null if none) */
+  private RealType realType;
+
+  /** The name of the variable being modified by this VisADSlider */
+  private String sName;
+
+  /** The minimum allowed slider value */
+  private double sMinimum;
+
+  /** The maximum allowed slider value */
+  private double sMaximum;
+
+  /** The current slider value */
+  private double sCurrent;
+
+  /** The number of ticks in the slider */
+  private int sTicks;
+
+  /** <CODE>true</CODE> if the widget will auto-scale */
+  private boolean autoScale;
+
+  /** <CODE>true</CODE> if the slider ticks should be integers */
+  private boolean integralValues;
+
+  /** <CODE>true</CODE> if the label width should be dynamically scaled */
+  private boolean dynamicLabelWidth;
+
+  /** JSlider values range between <tt>low</tt> and <tt>hi</tt>
+   *  (with initial value <tt>st</tt>) and are multiplied by
+   *  <tt>scale</tt> to create Real values of RealType <tt>rt</tt>
+   *  referenced by <tt>ref</tt>.
+   */
+  public VisADSlider(String n, int lo, int hi, int st, double scale,
+                     DataReference ref, RealType rt) throws VisADException,
+                                                            RemoteException {
+    this(ref, null, (float) (lo * scale), (float) (hi * scale),
+                    (float) (st * scale), hi - lo,
+                    (ref == null || ref.getData() instanceof Real) ? null : rt,
+                    n, false, false);
+  }
+
+  /** JSlider values range between <tt>low</tt> and <tt>hi</tt>
+   *  (with initial value <tt>st</tt>) and are multiplied by
+   *  <tt>scale</tt> to create Real values of RealType <tt>rt</tt>
+   *  referenced by <tt>ref</tt>.  The slider label has a
+   *  dynamically sized width if <tt>dynamicLabelWidth</tt> is <tt>true</tt>.
+   */
+  public VisADSlider(String n, int lo, int hi, int st, double scale,
+                    DataReference ref, RealType rt,
+                    boolean dynamicLabelWidth)
+    throws VisADException, RemoteException
+  {
+    this(ref, null, (float) (lo * scale), (float) (hi * scale),
+                    (float) (st * scale), hi - lo,
+                    (ref == null || ref.getData() instanceof Real) ? null : rt,
+                    n, false, dynamicLabelWidth);
+  }
+
+  /** construct a VisADSlider from a ScalarMap that maps to
+   *  Display.SelectValue, with auto-scaling minimum and maximum bounds,
+   *  non-integral values, and a statically sized label.
+   */
+  public VisADSlider(ScalarMap smap) throws VisADException, RemoteException {
+    // CASE ONE
+    this(null, smap, Float.NaN, Float.NaN, Float.NaN, D_TICKS, null, null,
+         false, false);
+  }
+
+  /** construct a VisADSlider from a ScalarMap that maps to
+   *  Display.SelectValue, with auto-scaling minimum and maximum bounds,
+   *  either integer or floating-point values, depending on the setting
+   *  of <tt>integralTicks</tt>, and a statically sized label.
+   */
+  public VisADSlider(ScalarMap smap, boolean integralTicks)
+                     throws VisADException, RemoteException {
+    // CASE ONE
+    this(null, smap, Float.NaN, Float.NaN, Float.NaN, D_TICKS, null, null,
+         integralTicks, false);
+  }
+
+  /** construct a VisADSlider from a ScalarMap that maps to
+   *  Display.SelectValue, with auto-scaling minimum and maximum bounds,
+   *  either integer or floating-point values (depending on the setting
+   *  of <tt>integralTicks</tt>, and either a static or dynamically
+   *  sized label (depending on the setting of <tt>dynamicLabelWidth</tt>.
+   */
+  public VisADSlider(ScalarMap smap, boolean integralTicks,
+                    boolean dynamicLabelWidth)
+                     throws VisADException, RemoteException {
+    // CASE ONE
+    this(null, smap, Float.NaN, Float.NaN, Float.NaN, D_TICKS, null, null,
+         integralTicks, dynamicLabelWidth);
+  }
+
+  /** construct a VisADSlider from a ScalarMap that maps to
+   *  Display.SelectValue, with minimum and maximum bounds min and max,
+   *  no auto-scaling, non-integer values, and a static label width.
+   */
+  public VisADSlider(ScalarMap smap, float min, float max)
+                     throws VisADException, RemoteException {
+    // CASE TWO
+    this(null, smap, min, max, Float.NaN, D_TICKS, null, null, false, false);
+  }
+
+  /** construct a VisADSlider by creating a Real and linking it to r,
+      using RealType rt and name n, with minimum and maximum bounds
+      min and max, and starting value start */
+  public VisADSlider(DataReference ref, float min, float max, float start,
+                     RealType rt, String n) throws VisADException,
+                                                   RemoteException {
+    // CASE THREE
+    this(ref, null, min, max, start, D_TICKS, rt, n, false, false);
+  }
+
+  /** construct a VisADSlider from an existing Real pointed to by r,
+      with minimum and maximum bounds min and max */
+  public VisADSlider(DataReference ref, float min, float max)
+                     throws VisADException, RemoteException {
+    // CASE FOUR
+    this(ref, null, min, max, Float.NaN, D_TICKS, null, null, false, false);
+  }
+
+  /** complete constructor */
+  private VisADSlider(DataReference ref, ScalarMap smap, float min, float max,
+                      float start, int sliderTicks, RealType rt, String n,
+                      boolean integralValues, boolean dynamicLabelWidth)
+                      throws VisADException, RemoteException {
+    this.integralValues = integralValues;
+    this.dynamicLabelWidth = dynamicLabelWidth;
+
+    // set up some UI components
+    setAlignmentX(LEFT_ALIGNMENT);   // VisADSliders default to LEFT_ALIGNMENT
+    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+    sTicks = sliderTicks;
+    Dimension d;
+    slider = new JSlider(0, sTicks, sTicks / 2);
+    d = slider.getMinimumSize();
+    slider.setMinimumSize(new Dimension(SLIDER_WIDTH, d.height));
+    d = slider.getPreferredSize();
+    slider.setPreferredSize(new Dimension(SLIDER_WIDTH, d.height));
+    d = slider.getMaximumSize();
+    slider.setMaximumSize(new Dimension(SLIDER_WIDTH, d.height));
+
+    // by default, don't auto-scale
+    autoScale = false;
+
+    // set up internal components
+    if (ref == null) {
+      // this VisADSlider should link to a ScalarMap
+      if (smap == null) {
+        throw new VisADException("VisADSlider: must specify either a " +
+                                 "DataReference or a ScalarMap!");
+      }
+      if (smap.getDisplayScalar() != Display.SelectValue) {
+        throw new VisADException("VisADSlider: ScalarMap must be to " +
+                                 "Display.SelectValue!");
+      }
+      if (!(smap.getScalar() instanceof RealType)) {
+        throw new VisADException("VisADSlider: ScalarMap must be from " +
+                                 "a RealType!");
+      }
+      map = smap;
+      control = (ValueControl) smap.getControl();
+      if (control == null) {
+        throw new VisADException("VisADSlider: ScalarMap must be addMap'ed " +
+                                 "to a Display");
+      }
+      sRef = null;
+      sName = smap.getScalarName();
+      start = (float) control.getValue();
+
+      if (min == min && max == max && start == start) {
+        // do not use auto-scaling (CASE TWO)
+        sMinimum = min;
+        sMaximum = max;
+
+        if (integralValues) {
+          int tmp = (int )(sMaximum - sMinimum);
+          if (tmp != sTicks) {
+            sTicks = tmp;
+            slider.setMaximum(sTicks);
+          }
+        }
+
+        sCurrent = start;
+        initLabel();
+        smap.setRange(min, max);
+        if (start < min || start > max) {
+          start = (min + max) / 2;
+          control.setValue(start);
+        }
+      }
+      else {
+        // enable auto-scaling (CASE ONE)
+        autoScale = true;
+        initLabel();
+      }
+      control.addControlListener(this);
+      smap.addScalarMapListener(this);
+    }
+    else {
+      // this VisADSlider should link to a Real
+      map = null;
+      control = null;
+      if (ref == null) {
+        throw new VisADException("VisADSlider: DataReference " +
+                                 "cannot be null!");
+      }
+      sRef = ref;
+      Data data = ref.getData();
+      if (data == null) {
+        // the Real must be created (CASE THREE)
+        if (rt == null) {
+          throw new VisADException("VisADSlider: RealType cannot be null!");
+        }
+        if (n == null) {
+          throw new VisADException("VisADSlider: name cannot be null!");
+        }
+        realType = rt;
+        if (min != min || max != max || start != start) {
+          throw new VisADException("VisADSlider: min, max, and start " +
+                                   "cannot be NaN!");
+        }
+        sMinimum = min;
+        sMaximum = max;
+        sCurrent = (start < min || start > max) ? (min + max) / 2 : start;
+        sRef.setData(new Real(realType, sCurrent));
+      }
+      else {
+        // the Real already exists (CASE FOUR)
+        if (!(data instanceof Real)) {
+          throw new VisADException("VisADSlider: DataReference " +
+                                   "must point to a Real!");
+        }
+        Real real = (Real) data;
+        realType = (RealType) real.getType();
+        sCurrent = (float) real.getValue();
+        if (min != min || max != max) {
+          throw new VisADException("VisADSlider: minimum and maximum " +
+                                   "cannot be NaN!");
+        }
+        sMinimum = min;
+        sMaximum = max;
+        if (sCurrent < min || sCurrent > max) sCurrent = (min + max) / 2;
+      }
+      sName = (n != null) ? n : realType.getName();
+      initLabel();
+
+      // watch for changes in Real, and update slider when necessary
+      CellImpl cell = new CellImpl() {
+        public void doAction() throws VisADException, RemoteException {
+          // update slider when value of linked Real changes
+          if (sRef != null) {
+            double val;
+            try {
+              val = ((Real) sRef.getData()).getValue();
+              if (!Util.isApproximatelyEqual(sCurrent, val)) updateSlider(val);
+            } catch (RemoteException re) {
+              if (visad.collab.CollabUtil.isDisconnectException(re)) {
+                // Remote data server went away
+                sRef = null;
+              }
+              throw re;
+            }
+          }
+        }
+      };
+
+      if (ref instanceof RemoteDataReference) {
+        RemoteCell remoteCell = new RemoteCellImpl(cell);
+        remoteCell.addReference(ref);
+      }
+      else cell.addReference(ref);
+    }
+
+    // add UI components
+    add(slider);
+    add(label);
+
+    // add listeners
+    slider.addChangeListener(this);
+
+    // do initial update of the slider
+    updateSlider(start);
+  }
+
+  /** sets up the JLabel */
+  private void initLabel() {
+    String str = sName + " = " + PlotText.shortString(sCurrent);
+
+    Dimension d;
+    if (dynamicLabelWidth) {
+      label = new JLabel(str);
+    } else {
+      // add a bit of whitespace to the string to avoid value truncation
+      label = new JLabel(str + "         ");
+      d = label.getMinimumSize();
+      label.setMinimumSize(new Dimension(LABEL_WIDTH, d.height));
+      d = label.getPreferredSize();
+      label.setPreferredSize(new Dimension(LABEL_WIDTH, d.height));
+      d = label.getMaximumSize();
+      label.setMaximumSize(new Dimension(LABEL_WIDTH, d.height));
+    }
+    label.setAlignmentX(JLabel.CENTER_ALIGNMENT);
+  }
+
+  /**
+   * Hardcode the preferred size of the slider after increasing
+   * the current width by the specified percentage (or decreasing
+   * it if <tt>percent</tt> is negative.)<br>
+   * <br>
+   * This method is primarily useful to keep changes in the
+   * label (of a VisADSlider with <tt>dynamicLabelWidth</tt>
+   * set to <tt>true</t>)
+   * from causing the rest of the window to be redrawn.
+   *
+   * @param percent percent of current size to use for hardcoded
+   *                size.  (e.g. to keep the current size, specify
+   *                <tt>100</tt>; to increase the size a bit,
+   *                specify <tt>115</tt>, to decrease it a bit,
+   *                specify <tt>85</tt>, etc.)
+   */
+  public void hardcodeSizePercent(int percent)
+  {
+    Dimension d = getPreferredSize();
+    int newWidth = d.width + (d.width * (percent - 100)) / 100;
+    setPreferredSize(new Dimension(newWidth, d.height));
+  }
+
+  /** called when slider is adjusted */
+  public synchronized void stateChanged(ChangeEvent e) {
+    try {
+      double val = slider.getValue();
+      double cur = (sMaximum - sMinimum) * (val / sTicks) + sMinimum;
+      if (integralValues) {
+        cur = Math.floor(cur + 0.5);
+      }
+
+      if (!Util.isApproximatelyEqual(sCurrent, cur)) {
+        if (control != null) control.setValue(cur);
+        else if (sRef != null) {
+          try {
+            sRef.setData(new Real(realType, cur));
+          } catch (RemoteException re) {
+            if (visad.collab.CollabUtil.isDisconnectException(re)) {
+              // Remote data server went away
+              sRef = null;
+            }
+            throw re;
+          }
+        }
+      }
+    }
+    catch (VisADException exc) {
+      exc.printStackTrace();
+    }
+    catch (RemoteException exc) {
+      exc.printStackTrace();
+    }
+  }
+
+  /** used for auto-scaling the minimum and maximum */
+  public void mapChanged(ScalarMapEvent e) {
+    if (!autoScale) return;
+    double[] range = map.getRange();
+    sMinimum = (float) range[0];
+    sMaximum = (float) range[1];
+
+    if (integralValues) {
+      int tmp = (int )(sMaximum - sMinimum);
+      if (tmp != sTicks) {
+        sTicks = tmp;
+        slider.setMaximum(sTicks);
+      }
+    }
+
+    sCurrent = (float) control.getValue();
+    if (sCurrent < sMinimum || sCurrent > sMaximum) {
+      sCurrent = (sMinimum + sMaximum) / 2;
+    }
+    updateSlider(sCurrent);
+  }
+
+  /**
+   * ScalarMapListener method used to detect new control.
+   */
+  public void controlChanged(ScalarMapControlEvent evt) {
+    int id = evt.getId();
+    if (id == ScalarMapEvent.CONTROL_REMOVED ||
+        id == ScalarMapEvent.CONTROL_REPLACED)
+    {
+      control = null;
+    }
+
+    if (id == ScalarMapEvent.CONTROL_REPLACED ||
+        id == ScalarMapEvent.CONTROL_ADDED)
+    {
+      control = (ValueControl) evt.getScalarMap().getControl();
+    }
+  }
+
+  /** Update slider when value of linked ValueControl changes */
+  public void controlChanged(ControlEvent e)
+    throws VisADException, RemoteException
+  {
+    double cur = control.getValue();
+    if (!Util.isApproximatelyEqual(sCurrent, cur)) {
+      updateSlider(control.getValue());
+    }
+  }
+
+  /** Update the slider's value */
+  private synchronized void updateSlider(double value) {
+    if (integralValues) {
+      value = Math.floor(value + 0.5);
+    }
+
+    int ival = (int) (sTicks * ((value - sMinimum) / (sMaximum - sMinimum)));
+    if (Math.abs(slider.getValue() - ival) > 1) {
+      slider.removeChangeListener(this);
+      slider.setValue(ival);
+      slider.addChangeListener(this);
+    }
+    sCurrent = value;
+
+    label.setText(sName + " = " + PlotText.shortString(sCurrent));
+    invalidate();
+  }
+
+}
+
diff --git a/visad/util/WeakMapValue.java b/visad/util/WeakMapValue.java
new file mode 100644
index 0000000..6d4587f
--- /dev/null
+++ b/visad/util/WeakMapValue.java
@@ -0,0 +1,108 @@
+//
+// WeakMapValue.java
+//
+
+/*
+VisAD system for interactive analysis and visualization of numerical
+data.  Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
+Tommy Jasmin.
+
+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., 59 Temple Place - Suite 330, Boston,
+MA 02111-1307, USA
+*/
+
+package visad.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+
+/**
+ * A weakly-referenced {@link java.util.Map} value.  The purpose of this class
+ * is to aid the creation of memory-sensitive, canonical mappings.
+ */
+public final class WeakMapValue extends WeakReference {
+
+  private final Object        key;
+
+  /**
+   * Constructs from a key and value.  The key and value are not copied.
+   *
+   * @param key                   The key for the map.
+   * @param value                 The value for the map.
+   * @param queue                 The queue to receive garbaged-collected
+   *                              instances of this class.
+   * @throws NullPointerException if the queue is <code>null</code>.
+   */
+  public WeakMapValue(Object key, Object value, ReferenceQueue queue) {
+    super(value, queue);
+    this.key = key;
+  }
+
+  /**
+   * Returns the key associated with this value.  The returned object is not a
+   * copy.
+   *
+   * @return                        The associated key.
+   */
+  public final Object getKey() {
+    return key;
+  }
+
+  /**
+   * Returns the value associated with this value.  The returned object is not
+   * a copy.  It may be <code>null</code> if this reference object has been
+   * cleared by the program or garbage collector.
+   *
+   * @return                        The associated value or <code>null</code>.
+   */
+  public final Object getValue() {
+    return get();
+  }
+
+  /**
+   * Indicates if this instance equals another object.  This method returns true
+   * if and only if the other object is a WeakMapValue and the values of both
+   * instances are either <code>null</code> or equal.
+   *
+   * @param obj                     The other object.
+   * @return                        True if and only if this instance equals the
+   *                                other object.
+   */
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (!(obj instanceof WeakMapValue))
+      return false;
+    WeakMapValue that = (WeakMapValue)obj;
+    Object thisVal = get();
+    Object thatVal = that.get();
+    return
+      thisVal == null
+        ? thatVal == null
+        : thisVal.equals(thatVal);
+  }
+
+  /**
+   * Returns the hash code of this instance.  This is the same as the hash code
+   * of the associated value.
+   *
+   * @return                         The hash code of this instance.
+   */
+  public int hashCode() {
+    Object val = get();
+    return val == null ? 0 : val.hashCode();
+  }
+}
diff --git a/visad/util/WidgetLayout.java b/visad/util/WidgetLayout.java
new file mode 100644
index 0000000..842b067
--- /dev/null
+++ b/visad/util/WidgetLayout.java
@@ -0,0 +1,217 @@
+/*
+
+@(#) $Id: WidgetLayout.java,v 1.6 2000-03-14 16:56:49 dglo Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.AWTError;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.LayoutManager;
+
+/**
+ * A simple layout manager for use in the visad ColorWidget.  Stacks the first two
+ * components vertically, and stretches them to fit the panel.
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision: 1.6 $, $Date: 2000-03-14 16:56:49 $
+ * @since Visad Utility Library, 0.5
+ */
+
+public class WidgetLayout implements LayoutManager
+{
+
+  /** The container that this layout manager is responsible for */
+  private Container container;
+
+  /** The current preferred X size of the container */
+  private int preferredX;
+  /** The current preferred Y size of the container */
+  private int preferredY;
+
+  /** The minimum layout size X component */
+  private int minX;
+  /** The minimum layout size Y component */
+  private int minY;
+
+  /** The maximum layout size X component */
+  private int maxX;
+  /** The maximum layout size Y component */
+  private int maxY;
+
+  /** Component widths (all windows) */
+  private int width;
+  /** Component0 height */
+  private int height0;
+  /** Component1 height */
+  private int height1;
+
+
+  /** Make a new WidgetLayout for the specified ColorWidget */
+  public WidgetLayout(ColorWidget colorWidget) {
+    container = colorWidget;
+    calcDimensions();
+  }
+
+  /** Not used, no effect */
+  public void addLayoutComponent(String name, Component component) {
+  }
+
+  /** Not used, no effect */
+  public void removeLayoutComponent(Component component) {
+  }
+
+
+  /** Lay out the container */
+  public void layoutContainer(Container parent) {
+
+    if (parent != container) {
+      throw new AWTError("WidgetLayout: got layoutContainer() with incorrect parent");
+    }
+
+    calcDimensions();
+
+    int i = container.getComponentCount();
+
+    switch (i) {
+    case 0:
+      break;
+
+    case 2:
+      container.getComponent(1).setBounds(container.getInsets().left,
+                                          (int) Math.min((long) height0 + container.getInsets().top, Integer.MAX_VALUE),
+                                          width, height1);
+      //fall through
+    case 1:
+      container.getComponent(0).setBounds(container.getInsets().left,
+                                          container.getInsets().top, width, height0);
+      break;
+    }
+
+    return;
+  }
+
+  /** Return the minimum size for this layout */
+  public Dimension minimumLayoutSize(Container parent) {
+
+    if (parent != container) {
+      throw new AWTError("WidgetLayout: got layoutContainer() with incorrect parent");
+    }
+
+    calcDimensions();
+
+    return new Dimension(minX, minY);
+  }
+
+  /** Return the preferred size for this layout */
+  public Dimension preferredLayoutSize(Container parent) {
+
+    if (parent != container) {
+      throw new AWTError("WidgetLayout: got layoutContainer() with incorrect parent");
+    }
+
+    calcDimensions();
+
+    return new Dimension(preferredX, preferredY);
+  }
+
+  /** Return the maximum size for this layout */
+  public Dimension maximumLayoutSize(Container parent) {
+
+    if (parent != container) {
+      throw new AWTError("WidgetLayout: got layoutContainer() with incorrect parent");
+    }
+
+    calcDimensions();
+
+    return new Dimension(maxX, maxY);
+  }
+
+
+  /** Calculate the desired and required dimensions of all the components in this container */
+  private void calcDimensions() {
+
+    int i = container.getComponentCount();
+
+    switch (i) {
+    case 0:
+      minX = 0;
+      minY = 0;
+      maxX = 0;
+      maxY = 0;
+      preferredX = 0;
+      preferredY = 0;
+      break;
+
+    case 1:
+      Component c = container.getComponent(0);
+      minX = (int)Math.min((long)c.getMinimumSize().width + container.getInsets().right +
+                           container.getInsets().left, (long) Integer.MAX_VALUE);
+      minY = (int)Math.min((long)c.getMinimumSize().height + container.getInsets().top +
+                           container.getInsets().bottom, (long) Integer.MAX_VALUE);
+
+      minX = (int)Math.min((long)c.getMaximumSize().width + container.getInsets().right +
+                           container.getInsets().left, (long) Integer.MAX_VALUE);
+      minY = (int)Math.min((long)c.getMaximumSize().height + container.getInsets().top +
+                           container.getInsets().bottom, (long) Integer.MAX_VALUE);
+
+      preferredX = (int)Math.min((long)c.getPreferredSize().width + container.getInsets().right +
+                                 container.getInsets().left, (long) Integer.MAX_VALUE);
+      preferredY = (int)Math.min((long)c.getPreferredSize().height + container.getInsets().top +
+                                 container.getInsets().bottom, (long) Integer.MAX_VALUE);
+
+      width = container.getBounds().width;
+      height0 = container.getBounds().height;
+      height1 = 0;
+      break;
+
+    default:
+      Component c0 = container.getComponent(0);
+      Component c1 = container.getComponent(1);
+
+      minX = (int)Math.min((long)c0.getMinimumSize().width + c1.getMinimumSize().width +
+                           container.getInsets().right + container.getInsets().left, (long) Integer.MAX_VALUE);
+      minY = (int)Math.min((long)c0.getMinimumSize().height + c1.getMinimumSize().height +
+                           container.getInsets().top + container.getInsets().bottom, (long) Integer.MAX_VALUE);
+
+      minX = (int)Math.min((long)c0.getMaximumSize().width + c1.getMaximumSize().width +
+                           container.getInsets().right + container.getInsets().left, (long) Integer.MAX_VALUE);
+      minY = (int)Math.min((long)c0.getMaximumSize().height + c1.getMaximumSize().height +
+                           container.getInsets().top + container.getInsets().bottom, (long) Integer.MAX_VALUE);
+
+      preferredX = (int)Math.min((long)c0.getPreferredSize().width + c1.getPreferredSize().width +
+                                 container.getInsets().right + container.getInsets().left, (long) Integer.MAX_VALUE);
+      preferredY = (int)Math.min((long)c0.getPreferredSize().height + c1.getPreferredSize().height +
+                                 container.getInsets().top + container.getInsets().bottom, (long) Integer.MAX_VALUE);
+
+      width = container.getBounds().width;
+      height1 = Math.min(container.getBounds().height, c1.getPreferredSize().height);
+      height0 = container.getBounds().height - height1;
+      break;
+    }
+
+    return;
+  }
+}
diff --git a/visad/util/WidgetTest.java b/visad/util/WidgetTest.java
new file mode 100644
index 0000000..69b6250
--- /dev/null
+++ b/visad/util/WidgetTest.java
@@ -0,0 +1,71 @@
+/*
+
+@(#) $Id: WidgetTest.java,v 1.12 2002-02-12 19:53:15 curtis Exp $
+
+VisAD Utility Library: Widgets for use in building applications with
+the VisAD interactive analysis and visualization library
+Copyright (C) 2011 Nick Rasmussen
+VisAD is Copyright (C) 1996 - 2011 Bill Hibbard, Curtis Rueden, Tom
+Rink and Dave Glowacki.
+
+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 1, or (at your option)
+any later version.
+
+This program is distributed in the hope that 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 in file NOTICE 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+package visad.util;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.applet.Applet;
+
+import javax.swing.*;
+
+/**
+ * A program for testing the VisAD ColorWidget.
+ *
+ * @author Nick Rasmussen nick at cae.wisc.edu
+ * @version $Revision 1.2 $, $Date: 2002-02-12 19:53:15 $
+ * @since Visad Utility Library v0.7.1
+ */
+
+public class WidgetTest extends Applet {
+
+  /** for debugging purposes */
+  public static void main(String[] argc) throws Exception {
+
+    Slider slider = new BarSlider();
+    //Slider slider = new ArrowSlider();
+    SliderLabel label = new SliderLabel(slider, "value");
+
+    ColorWidget widget = new ColorWidget();
+
+    JFrame f;
+    f = new JFrame("Visad Widget Test");
+    f.addWindowListener(new WindowAdapter() {
+        public void windowClosing(WindowEvent e) {System.exit(0);}
+      });
+
+    JPanel p = new JPanel();
+    p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
+    p.add(widget);
+    p.add(slider);
+    p.add(label);
+    f.setContentPane(p);
+
+    f.setSize(f.getPreferredSize());
+    f.setVisible(true);
+
+  }
+}
diff --git a/visad/util/cursive.jhf b/visad/util/cursive.jhf
new file mode 100644
index 0000000..68aab72
--- /dev/null
+++ b/visad/util/cursive.jhf
@@ -0,0 +1,113 @@
+12345  1JZ
+12345  9MWRFRT RRYQZR[SZRY
+12345  6JZNFNM RVFVM
+12345 12H]SBLb RYBRb RLOZO RKUYU
+12345 27H\PBP_ RTBT_ RYIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX
+12345 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+12345 35E_\O\N[MZMYNXPVUTXRZP[L[JZIYHWHUISJRQNRMSKSIRGPFNGMIMKNNPQUXWZY[
+[[\Z\Y
+12345  3PTRMRQ
+12345 11KYVBTDRGPKOPOTPYR]T`Vb
+12345 11KYNBPDRGTKUPUTTYR]P`Nb
+12345  9JZRLRX RMOWU RWOMU
+12345  6E_RIR[ RIR[R
+12345  8NVSWRXQWRVSWSYQ[
+12345  3E_IR[R
+12345  6NVRVQWRXSWRV
+12345  3G][BIb
+12345 18H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF
+12345  5H\NJPISFS[
+12345 15H\LKLJMHNGPFTFVGWHXJXLWNUQK[Y[
+12345 16H\MFXFRNUNWOXPYSYUXXVZS[P[MZLYKW
+12345  7H\UFKTZT RUFU[
+12345 18H\WFMFLOMNPMSMVNXPYSYUXXVZS[P[MZLYKW
+12345 24H\XIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQLT
+12345  6H\YFO[ RKFYF
+12345 30H\PFMGLILKMMONSOVPXRYTYWXYWZT[P[MZLYKWKTLRNPQOUNWMXKXIWGTFPF
+12345 24H\XMWPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLX
+12345 12NVROQPRQSPRO RRVQWRXSWRV
+12345 14NVROQPRQSPRO RSWRXQWRVSWSYQ[
+12345  4F^ZIJRZ[
+12345  6E_IO[O RIU[U
+12345  4F^JIZRJ[
+12345 21I[LKLJMHNGPFTFVGWHXJXLWNVORQRT RRYQZR[SZRY
+12345 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+12345 20G[G[IZLWOSSLVFV[UXSUQSNQLQKRKTLVNXQZT[Y[
+12345 41F]SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFXFZG[I[KZMYNWOTP RSPTPWQ
+XRYTYWXYWZU[R[PZOX
+12345 24H\TLTMUNWNYMZKZIYGWFTFQGOIMLLNKRKVLYMZO[Q[TZVXWV
+12345 35G^TFRGQIPMOSNVMXKZI[G[FZFXGWIWKXMZP[S[VZXXZT[O[KZHYGWFTFRHRJSMUP
+WRZT\U
+12345 28H\VJVKWLYLZKZIYGVFRFOGNINLONPOSPPPMQLRKTKWLYMZP[S[VZXXYV
+12345 28H\RLPLNKMINGQFTFXG[G]F RXGVNTTRXPZN[L[JZIXIVJULUNV RQPZP
+12345 29G^G[IZMVPQQNRJRGQFPFOGNINLONQOUOXNYMZKZQYVXXVZS[O[LZJXIVIT
+12345 38F^MMKLJJJIKGMFNFPGQIQKPONULYJ[H[GZGX RMRVOXN[L]J^H^G]F\FZHXLVRUW
+UZV[W[YZZY\V
+12345 25IZWVUTSQROQLQIRGSFUFVGWIWLVQTVSXQZO[M[KZJXJVKUMUOV
+12345 25JYT^R[PVOPOJPGRFTFUGVJVMURR[PaOdNfLgKfKdLaN^P\SZWX
+12345 39F^MMKLJJJIKGMFNFPGQIQKPONULYJ[H[GZGX R^I^G]F\FZGXIVLTNROPO RROSQ
+SXTZU[V[XZYY[V
+12345 29I\MRORSQVOXMYKYHXFVFUGTISNRSQVPXNZL[J[IZIXJWLWNXQZT[V[YZ[X
+12345 45 at aEMCLBJBICGEFFFHGIIIKHPGTE[ RGTJLLHMGOFPFRGSISKRPQTO[ RQTTLVHWG
+YFZF\G]I]K\PZWZZ[[\[^Z_YaV
+12345 32E]JMHLGJGIHGJFKFMGNINKMPLTJ[ RLTOLQHRGTFVFXGYIYKXPVWVZW[X[ZZ[Y]V
+12345 29H]TFQGOIMLLNKRKVLYMZO[Q[TZVXXUYSZOZKYHXGVFTFRHRKSNUQWSZU\V
+12345 31F_SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFZF\G]H^J^M]O\PZQWQUPTO
+12345 32H^ULTNSOQPOPNNNLOIQGTFWFYGZIZMYPWSSWPYNZK[I[HZHXIWKWMXPZS[V[YZ[X
+12345 38F_SHTITLSPRSQUOXMZK[J[IZIWJRKOLMNJPHRGUFYF[G\H]J]M\O[PYQVQSPTQUS
+UXVZX[ZZ[Y]V
+12345 28H\H[JZLXOTQQSMTJTGSFRFQGPIPKQMSOVQXSYUYWXYWZT[P[MZKXJVJT
+12345 25H[RLPLNKMINGQFTFXG[G]F RXGVNTTRXPZN[L[JZIXIVJULUNV
+12345 33E]JMHLGJGIHGJFKFMGNINKMOLRKVKXLZN[P[RZSYUUXMZF RXMWQVWVZW[X[ZZ[Y
+]V
+12345 32F]KMILHJHIIGKFLFNGOIOKNOMRLVLYM[O[QZTWVTXPYMZIZGYFXFWGVIVKWNYP[Q
+12345 25C_HMFLEJEIFGHFIFKGLILLK[ RUFK[ RUFS[ RaF_G\JYNVTS[
+12345 36F^NLLLKKKILGNFPFRGSISLQUQXRZT[V[XZYXYVXUVU R]I]G\FZFXGVITLPUNXLZ
+J[H[GZGX
+12345 38F]KMILHJHIIGKFLFNGOIOKNOMRLVLXMZN[P[RZTXVUWSYM R[FYMVWT]RbPfNgMf
+MdNaP^S[VY[V
+12345 40H]ULTNSOQPOPNNNLOIQGTFWFYGZIZMYPWTTWPZN[K[JZJXKWNWPXQYR[R^QaPcNf
+LgKfKdLaN^Q[TYZV
+12345 12KYOBOb RPBPb ROBVB RObVb
+12345  3KYKFY^
+12345 12KYTBTb RUBUb RNBUB RNbUb
+12345  6JZRDJR RRDZR
+12345  3G[Gb[b
+12345  8NVSKQMQORPSORNQO
+12345 22L\UUTSRRPRNSMTLVLXMZO[Q[SZTXVRUWUZV[W[YZZY\V
+12345 23M[MVOSRNSLTITGSFQGPIOMNTNZO[P[RZTXUUURVVWWYW[V
+12345 14MXTTTSSRQROSNTMVMXNZP[S[VYXV
+12345 24L\UUTSRRPRNSMTLVLXMZO[Q[SZTXZF RVRUWUZV[W[YZZY\V
+12345 17NXOYQXRWSUSSRRQROSNUNXOZQ[S[UZVYXV
+12345 24OWOVSQUNVLWIWGVFTGSIQQNZKaJdJfKgMfNcOZP[R[TZUYWV
+12345 28L[UUTSRRPRNSMTLVLXMZO[Q[SZTY RVRTYPdOfMgLfLdMaP^S\U[XY[V
+12345 29M\MVOSRNSLTITGSFQGPIOMNSM[ RM[NXOVQSSRURVSVUUXUZV[W[YZZY\V
+12345 16PWSMSNTNTMSM RPVRRPXPZQ[R[TZUYWV
+12345 20PWSMSNTNTMSM RPVRRLdKfIgHfHdIaL^O\Q[TYWV
+12345 33M[MVOSRNSLTITGSFQGPIOMNSM[ RM[NXOVQSSRURVSVUTVQV RQVSWTZU[V[XZYY
+[V
+12345 18OWOVQSTNULVIVGUFSGRIQMPTPZQ[R[TZUYWV
+12345 33E^EVGSIRJSJTIXH[ RIXJVLSNRPRQSQTPXO[ RPXQVSSURWRXSXUWXWZX[Y[[Z\Y
+^V
+12345 23J\JVLSNROSOTNXM[ RNXOVQSSRURVSVUUXUZV[W[YZZY\V
+12345 23LZRRPRNSMTLVLXMZO[Q[SZTYUWUUTSRRQSQURWTXWXYWZV
+12345 24KZKVMSNQMUGg RMUNSPRRRTSUUUWTYSZQ[ RMZO[R[UZWYZV
+12345 27L[UUTSRRPRNSMTLVLXMZO[Q[SZ RVRUUSZPaOdOfPgRfScS\U[XY[V
+12345 15MZMVOSPQPSSSTTTVSYSZT[U[WZXYZV
+12345 16NYNVPSQQQSSVTXTZR[ RNZP[T[VZWYYV
+12345 16OXOVQSSO RVFPXPZQ[S[UZVYXV RPNWN
+12345 19L[LVNRLXLZM[O[QZSXUU RVRTXTZU[V[XZYY[V
+12345 17L[LVNRMWMZN[O[RZTXUUUR RURVVWWYW[V
+12345 25I^LRJTIWIYJ[L[NZPX RRRPXPZQ[S[UZWXXUXR RXRYVZW\W^V
+12345 20JZJVLSNRPRQSQZR[U[XYZV RWSVRTRSSOZN[L[KZ
+12345 23L[LVNRLXLZM[O[QZSXUU RVRPdOfMgLfLdMaP^S\U[XY[V
+12345 23LZLVNSPRRRTTTVSXQZN[P\Q^QaPdOfMgLfLdMaP^S\WYZV
+12345 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSU
+SWRYQZP\P^Q`RaTb
+12345  3NVRBRb
+12345 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQU
+QWRYSZT\T^S`RaPb
+12345 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
+12345 35JZJFJ[K[KFLFL[M[MFNFN[O[OFPFP[Q[QFRFR[S[SFTFT[U[UFVFV[W[WFXFX[Y[
+YFZFZ[
diff --git a/visad/util/futural.jhf b/visad/util/futural.jhf
new file mode 100644
index 0000000..7445439
--- /dev/null
+++ b/visad/util/futural.jhf
@@ -0,0 +1,101 @@
+12345  1JZ
+12345  9MWRFRT RRYQZR[SZRY
+12345  6JZNFNM RVFVM
+12345 12H]SBLb RYBRb RLOZO RKUYU
+12345 27H\PBP_ RTBT_ RYIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX
+12345 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+12345 35E_\O\N[MZMYNXPVUTXRZP[L[JZIYHWHUISJRQNRMSKSIRGPFNGMIMKNNPQUXWZY[
+[[\Z\Y
+12345  8MWRHQGRFSGSIRKQL
+12345 11KYVBTDRGPKOPOTPYR]T`Vb
+12345 11KYNBPDRGTKUPUTTYR]P`Nb
+12345  9JZRLRX RMOWU RWOMU
+12345  6E_RIR[ RIR[R
+12345  8NVSWRXQWRVSWSYQ[
+12345  3E_IR[R
+12345  6NVRVQWRXSWRV
+12345  3G][BIb
+12345 18H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF
+12345  4H\PISFS[
+12345 15H\LKLJMHNGPFTFVGWHXJXLWNUQK[Y[
+12345 16H\MFXFRNUNWOXPYSYUXXVZS[P[MZLYKW
+12345  7H\UFKTZT RUFU[
+12345 18H\WFMFLOMNPMSMVNXPYSYUXXVZS[P[MZLYKW
+12345 24H\XIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQLT
+12345  6H\YFO[ RKFYF
+12345 30H\PFMGLILKMMONSOVPXRYTYWXYWZT[P[MZLYKWKTLRNPQOUNWMXKXIWGTFPF
+12345 24H\XMWPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLX
+12345 12NVROQPRQSPRO RRVQWRXSWRV
+12345 14NVROQPRQSPRO RSWRXQWRVSWSYQ[
+12345  4F^ZIJRZ[
+12345  6E_IO[O RIU[U
+12345  4F^JIZRJ[
+12345 21I[LKLJMHNGPFTFVGWHXJXLWNVORQRT RRYQZR[SZRY
+12345 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+12345  9I[RFJ[ RRFZ[ RMTWT
+12345 24G\KFK[ RKFTFWGXHYJYLXNWOTP RKPTPWQXRYTYWXYWZT[K[
+12345 19H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV
+12345 16G\KFK[ RKFRFUGWIXKYNYSXVWXUZR[K[
+12345 12H[LFL[ RLFYF RLPTP RL[Y[
+12345  9HZLFL[ RLFYF RLPTP
+12345 23H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZVZS RUSZS
+12345  9G]KFK[ RYFY[ RKPYP
+12345  3NVRFR[
+12345 11JZVFVVUYTZR[P[NZMYLVLT
+12345  9G\KFK[ RYFKT RPOY[
+12345  6HYLFL[ RL[X[
+12345 12F^JFJ[ RJFR[ RZFR[ RZFZ[
+12345  9G]KFK[ RKFY[ RYFY[
+12345 22G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF
+12345 14G\KFK[ RKFTFWGXHYJYMXOWPTQKQ
+12345 25G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF RSWY]
+12345 17G\KFK[ RKFTFWGXHYJYLXNWOTPKP RRPY[
+12345 21H\YIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX
+12345  6JZRFR[ RKFYF
+12345 11G]KFKULXNZQ[S[VZXXYUYF
+12345  6I[JFR[ RZFR[
+12345 12F^HFM[ RRFM[ RRFW[ R\FW[
+12345  6H\KFY[ RYFK[
+12345  7I[JFRPR[ RZFRP
+12345  9H\YFK[ RKFYF RK[Y[
+12345 12KYOBOb RPBPb ROBVB RObVb
+12345  3KYKFY^
+12345 12KYTBTb RUBUb RNBUB RNbUb
+12345  6JZRDJR RRDZR
+12345  3I[Ib[b
+12345  8NVSKQMQORPSORNQO
+12345 18I\XMX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 18H[LFL[ RLPNNPMSMUNWPXSXUWXUZS[P[NZLX
+12345 15I[XPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 18I\XFX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 18I[LSXSXQWOVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345  9MYWFUFSGRJR[ ROMVM
+12345 23I\XMX]W`VaTbQbOa RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 11I\MFM[ RMQPNRMUMWNXQX[
+12345  9NVQFRGSFREQF RRMR[
+12345 12MWRFSGTFSERF RSMS^RaPbNb
+12345  9IZMFM[ RWMMW RQSX[
+12345  3NVRFR[
+12345 19CaGMG[ RGQJNLMOMQNRQR[ RRQUNWMZM\N]Q][
+12345 11I\MMM[ RMQPNRMUMWNXQX[
+12345 18I\QMONMPLSLUMXOZQ[T[VZXXYUYSXPVNTMQM
+12345 18H[LMLb RLPNNPMSMUNWPXSXUWXUZS[P[NZLX
+12345 18I\XMXb RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345  9KXOMO[ ROSPPRNTMWM
+12345 18J[XPWNTMQMNNMPNRPSUTWUXWXXWZT[Q[NZMX
+12345  9MYRFRWSZU[W[ ROMVM
+12345 11I\MMMWNZP[S[UZXW RXMX[
+12345  6JZLMR[ RXMR[
+12345 12G]JMN[ RRMN[ RRMV[ RZMV[
+12345  6J[MMX[ RXMM[
+12345 10JZLMR[ RXMR[P_NaLbKb
+12345  9J[XMM[ RMMXM RM[X[
+12345 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSU
+SWRYQZP\P^Q`RaTb
+12345  3NVRBRb
+12345 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQU
+QWRYSZT\T^S`RaPb
+12345 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
+12345 35JZJFJ[K[KFLFL[M[MFNFN[O[OFPFP[Q[QFRFR[S[SFTFT[U[UFVFV[W[WFXFX[Y[
+YFZFZ[
diff --git a/visad/util/futuram.jhf b/visad/util/futuram.jhf
new file mode 100644
index 0000000..3d94ca2
--- /dev/null
+++ b/visad/util/futuram.jhf
@@ -0,0 +1,127 @@
+12345  1JZ
+12345 24MXRFRTST RRFSFST RRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+12345 22I[NFMGMM RNGMM RNFOGMM RWFVGVM RWGVM RWFXGVM
+12345 12H]SBLb RYBRb RLOZO RKUYU
+12345 51I\RBR_S_ RRBSBS_ RWIYIWGTFQFNGLILKMMNNVRWSXUXWWYTZQZOYNX RWIVHTG
+QGNHMIMKNMVQXSYUYWXYWZT[Q[NZLXNX RXXUZ
+12345 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+12345 49F_[NZO[P\O\N[MZMYNXPVUTXRZP[M[JZIXIUJSPORMSKSIRGPFNGMIMKNNPQUXWZ
+Z[[[\Z\Y RM[KZJXJUKSMQ RMKNMVXXZZ[
+12345 11NWSFRGRM RSGRM RSFTGRM
+12345 20KYVBTDRGPKOPOTPYR]T`Vb RTDRHQKPPPTQYR\T`
+12345 20KYNBPDRGTKUPUTTYR]P`Nb RPDRHSKTPTTSYR\P`
+12345 39JZRFQGSQRR RRFRR RRFSGQQRR RMINIVOWO RMIWO RMIMJWNWO RWIVINOMO R
+WIMO RWIWJMNMO
+12345 16F_RIRZSZ RRISISZ RJQ[Q[R RJQJR[R
+12345 24MXTZS[R[QZQYRXSXTYT\S^Q_ RRYRZSZSYRY RS[T\ RTZS^
+12345  3E_IR[R
+12345 16MXRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+12345  8G^[BIbJb R[B\BJb
+12345 42H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF ROGMJLOLRMWOZ RNYQZSZVY RUZ
+WWXRXOWJUG RVHSGQGNH
+12345 12H\NJPISFS[ RNJNKPJRHR[S[
+12345 34H\LKLJMHNGPFTFVGWHXJXLWNUQL[ RLKMKMJNHPGTGVHWJWLVNTQK[ RLZYZY[ R
+K[Y[
+12345 48H\MFXFQO RMFMGWG RWFPO RQNSNVOXQYTYUXXVZS[P[MZLYKWLW RPOSOVPXS R
+TOWQXTXUWXTZ RXVVYSZPZMYLW ROZLX
+12345 18H\UIU[V[ RVFV[ RVFKVZV RUILV RLUZUZV
+12345 53H\MFLO RNGMN RMFWFWG RNGWG RMNPMSMVNXPYSYUXXVZS[P[MZLYKWLW RLOMO
+ONSNVOXR RTNWPXSXUWXTZ RXVVYSZPZMYLW ROZLX
+12345 62H\VGWIXIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQ RWHTGRGOH RPG
+NJMOMTNXQZ RMVOYRZSZVYXV RTZWXXUXTWQTO RXSVPSOROOPMS RQONQMT
+12345 12H\KFYFO[ RKFKGXG RXFN[O[
+12345 68H\PFMGLILKMMNNPOTPVQWRXTXWWYTZPZMYLWLTMRNQPPTOVNWMXKXIWGTFPF RNG
+MIMKNMPNTOVPXRYTYWXYWZT[P[MZLYKWKTLRNPPOTNVMWKWIVG RWHTGPGMH RLXOZ RUZXX
+12345 62H\WPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLXMXNZ RWMVPSR RWNUQ
+RRQRNQLN RPRMPLMLLMIPG RLKNHQGRGUHWK RSGVIWMWRVWTZ RUYRZPZMY
+12345 32MXRMQNQORPSPTOTNSMRM RRNROSOSNRN RRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+12345 40MXRMQNQORPSPTOTNSMRM RRNROSOSNRN RTZS[R[QZQYRXSXTYT\S^Q_ RRYRZSZ
+SYRY RS[T\ RTZS^
+12345  4F^ZIJRZ[
+12345 16F_JM[M[N RJMJN[N RJU[U[V RJUJV[V
+12345  4F^JIZRJ[
+12345 58I\LKLJMHNGQFTFWGXHYJYLXNWOUPRQ RLKMKMJNHQGTGWHXJXLWNUORP RMIPG R
+UGXI RXMTP RRPRTSTSP RRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+12345 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+12345 20H\RFJ[ RRIK[J[ RRIY[Z[ RRFZ[ RMUWU RLVXV
+12345 44H\LFL[ RMGMZ RLFTFWGXHYJYMXOWPTQ RMGTGWHXJXMWOTP RMPTPWQXRYTYWXY
+WZT[L[ RMQTQWRXTXWWYTZMZ
+12345 38H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV RZKYKXIWHUGQGOHMKLNLSMVOY
+QZUZWYXXYVZV
+12345 32H]LFL[ RMGMZ RLFSFVGXIYKZNZSYVXXVZS[L[ RMGSGVHWIXKYNYSXVWXVYSZMZ
+12345 27I\MFM[ RNGNZ RMFYF RNGYGYF RNPTPTQ RNQTQ RNZYZY[ RM[Y[
+12345 21I[MFM[ RNGN[M[ RMFYF RNGYGYF RNPTPTQ RNQTQ
+12345 44H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZVZRUR RZKYKXIWHUGQGOHNIMKLN
+LSMVNXOYQZUZWYXXYVYSUSUR
+12345 22G]KFK[ RKFLFL[K[ RYFXFX[Y[ RYFY[ RLPXP RLQXQ
+12345  8NWRFR[S[ RRFSFS[
+12345 20J[VFVVUYSZQZOYNVMV RVFWFWVVYUZS[Q[OZNYMV
+12345 22H]LFL[M[ RLFMFM[ RZFYFMR RZFMS RPOY[Z[ RQOZ[
+12345 14IZMFM[ RMFNFNZ RNZYZY[ RM[Y[
+12345 26F^JFJ[ RKKK[J[ RKKR[ RJFRX RZFRX RYKR[ RYKY[Z[ RZFZ[
+12345 20G]KFK[ RLIL[K[ RLIY[ RKFXX RXFXX RXFYFY[
+12345 40G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF RQGNHLKKNKSLVNYQZSZ
+VYXVYSYNXKVHSGQG
+12345 27H\LFL[ RMGM[L[ RLFUFWGXHYJYMXOWPUQMQ RMGUGWHXJXMWOUPMP
+12345 48G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF RQGNHLKKNKSLVNYQZSZ
+VYXVYSYNXKVHSGQG RSXX]Y] RSXTXY]
+12345 34H\LFL[ RMGM[L[ RLFTFWGXHYJYMXOWPTQMQ RMGTGWHXJXMWOTPMP RRQX[Y[ R
+SQY[
+12345 43H\YIWGTFPFMGKIKKLMMNOOTQVRWSXUXXWYTZPZNYMXKX RYIWIVHTGPGMHLILKMM
+ONTPVQXSYUYXWZT[P[MZKX
+12345 15J[RGR[ RSGS[R[ RLFYFYG RLFLGYG
+12345 24G]KFKULXNZQ[S[VZXXYUYF RKFLFLUMXNYQZSZVYWXXUXFYF
+12345 14H\JFR[ RJFKFRX RZFYFRX RZFR[
+12345 26E_GFM[ RGFHFMX RRFMX RRIM[ RRIW[ RRFWX R]F\FWX R]FW[
+12345 16H\KFX[Y[ RKFLFY[ RYFXFK[ RYFL[K[
+12345 17I\KFRPR[S[ RKFLFSP RZFYFRP RZFSPS[
+12345 20H\XFK[ RYFL[ RKFYF RKFKGXG RLZYZY[ RK[Y[
+12345 12KYOBOb RPBPb ROBVB RObVb
+12345  3KYKFY^
+12345 12KYTBTb RUBUb RNBUB RNbUb
+12345  8G]JTROZT RJTRPZT
+12345  3H\Hb\b
+12345  7LXPFUL RPFOGUL
+12345 36H\WMW[X[ RWMXMX[ RWPUNSMPMNNLPKSKULXNZP[S[UZWX RWPSNPNNOMPLSLUMX
+NYPZSZWX
+12345 36H\LFL[M[ RLFMFM[ RMPONQMTMVNXPYSYUXXVZT[Q[OZMX RMPQNTNVOWPXSXUWX
+VYTZQZMX
+12345 32I[XPVNTMQMONMPLSLUMXOZQ[T[VZXX RXPWQVOTNQNOONPMSMUNXOYQZTZVYWWXX
+12345 36H\WFW[X[ RWFXFX[ RWPUNSMPMNNLPKSKULXNZP[S[UZWX RWPSNPNNOMPLSLUMX
+NYPZSZWX
+12345 36I[MTXTXQWOVNTMQMONMPLSLUMXOZQ[T[VZXX RMSWSWQVOTNQNOONPMSMUNXOYQZ
+TZVYWWXX
+12345 24LZWFUFSGRJR[S[ RWFWGUGSH RTGSJS[ ROMVMVN ROMONVN
+12345 48H\XMWMW\V_U`SaQaO`N_L_ RXMX\W_UaSbPbNaL_ RWPUNSMPMNNLPKSKULXNZP[
+S[UZWX RWPSNPNNOMPLSLUMXNYPZSZWX
+12345 25H\LFL[M[ RLFMFM[ RMQPNRMUMWNXQX[ RMQPORNTNVOWQW[X[
+12345 24NWRFQGQHRISITHTGSFRF RRGRHSHSGRG RRMR[S[ RRMSMS[
+12345 24NWRFQGQHRISITHTGSFRF RRGRHSHSGRG RRMRbSb RRMSMSb
+12345 22H[LFL[M[ RLFMFM[ RXMWMMW RXMMX RPTV[X[ RQSX[
+12345  8NWRFR[S[ RRFSFS[
+12345 42CbGMG[H[ RGMHMH[ RHQKNMMPMRNSQS[ RHQKOMNONQORQR[S[ RSQVNXM[M]N^Q
+^[ RSQVOXNZN\O]Q][^[
+12345 25H\LML[M[ RLMMMM[ RMQPNRMUMWNXQX[ RMQPORNTNVOWQW[X[
+12345 36I\QMONMPLSLUMXOZQ[T[VZXXYUYSXPVNTMQM RQNOONPMSMUNXOYQZTZVYWXXUXS
+WPVOTNQN
+12345 36H\LMLbMb RLMMMMb RMPONQMTMVNXPYSYUXXVZT[Q[OZMX RMPQNTNVOWPXSXUWX
+VYTZQZMX
+12345 36H\WMWbXb RWMXMXb RWPUNSMPMNNLPKSKULXNZP[S[UZWX RWPSNPNNOMPLSLUMX
+NYPZSZWX
+12345 21KYOMO[P[ ROMPMP[ RPSQPSNUMXM RPSQQSOUNXNXM
+12345 50J[XPWNTMQMNNMPNRPSUUWV RVUWWWXVZ RWYTZQZNY ROZNXMX RXPWPVN RWOTN
+QNNO RONNPOR RNQPRUTWUXWXXWZT[Q[NZMX
+12345 16MXRFR[S[ RRFSFS[ ROMVMVN ROMONVN
+12345 25H\LMLWMZO[R[TZWW RLMMMMWNYPZRZTYWW RWMW[X[ RWMXMX[
+12345 14JZLMR[ RLMMMRY RXMWMRY RXMR[
+12345 26F^IMN[ RIMJMNX RRMNX RRPN[ RRPV[ RRMVX R[MZMVX R[MV[
+12345 16I[LMW[X[ RLMMMX[ RXMWML[ RXMM[L[
+12345 17JZLMR[ RLMMMRY RXMWMRYNb RXMR[ObNb
+12345 20I[VNL[ RXMNZ RLMXM RLMLNVN RNZXZX[ RL[X[
+12345  4KYUBNRUb
+12345  3NVRBRb
+12345  4KYOBVROb
+12345 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
+12345 35JZJFJ[K[KFLFL[M[MFNFN[O[OFPFP[Q[QFRFR[S[SFTFT[U[UFVFV[W[WFXFX[Y[
+YFZFZ[
diff --git a/visad/util/meteorology.jhf b/visad/util/meteorology.jhf
new file mode 100644
index 0000000..a85f97d
--- /dev/null
+++ b/visad/util/meteorology.jhf
@@ -0,0 +1,99 @@
+12345  1JZ
+12345 18PSSRRSQSPRPQQPRPSQSSRUQV RQQQRRRRQQQ
+12345 16PTQPPQPSQTSTTSTQSPQP RRQQRRSSRRQ
+12345  9NVPOTU RTOPU RNRVR
+12345 28MWRKQMOPMR RRKSMUPWR RRMOQ RRMUQ RROPQ RROTQ RQQSQ RMRWR
+12345 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+12345 26MWMRMQNOONQMSMUNVOWQWR RPNTN ROOUO RNPVP RNQVQ RMRWR
+12345 14LRLFLRRRLF RLIPQ RLLOR RLOMQ
+12345 10MWRKQMOPMR RRKSMUPWR
+12345 11MWWRWQVOUNSMQMONNOMQMR
+12345  9JZRLRX RMOWU RWOMU
+12345 13G]]R]P\MZJWHTGPGMHJJHMGPGR
+12345  8NVSWRXQWRVSWSYQ[
+12345  3E_IR[R
+12345  6NVRVQWRXSWRV
+12345  3G][BIb
+12345 18H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF
+12345  4H\PISFS[
+12345 15H\LKLJMHNGPFTFVGWHXJXLWNUQK[Y[
+12345 16H\MFXFRNUNWOXPYSYUXXVZS[P[MZLYKW
+12345  7H\UFKTZT RUFU[
+12345 18H\WFMFLOMNPMSMVNXPYSYUXXVZS[P[MZLYKW
+12345 24H\XIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQLT
+12345  6H\YFO[ RKFYF
+12345 30H\PFMGLILKMMONSOVPXRYTYWXYWZT[P[MZLYKWKTLRNPQOUNWMXKXIWGTFPF
+12345 24H\XMWPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLX
+12345 11MWMRMSNUOVQWSWUVVUWSWR
+12345  7LXLPNRQSSSVRXP
+12345  6RURUTTURTPRO
+12345  7RVRRUPVNVLUKTK
+12345  7NRRROPNNNLOKPK
+12345 32I[MJNKMLLKLJMHNGPFSFVGWHXJXLWNVORQRT RSFUGVHWJWLVNTP RRYQZR[SZRY
+12345 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+12345  9I[RFJ[ RRFZ[ RMTWT
+12345 24G\KFK[ RKFTFWGXHYJYLXNWOTP RKPTPWQXRYTYWXYWZT[K[
+12345 19H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV
+12345 16G\KFK[ RKFRFUGWIXKYNYSXVWXUZR[K[
+12345 12H[LFL[ RLFYF RLPTP RL[Y[
+12345  9HZLFL[ RLFYF RLPTP
+12345 23H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZVZS RUSZS
+12345  9G]KFK[ RYFY[ RKPYP
+12345  3NVRFR[
+12345 11JZVFVVUYTZR[P[NZMYLVLT
+12345  9G\KFK[ RYFKT RPOY[
+12345  6HYLFL[ RL[X[
+12345 12F^JFJ[ RJFR[ RZFR[ RZFZ[
+12345  9G]KFK[ RKFY[ RYFY[
+12345 22G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF
+12345 14G\KFK[ RKFTFWGXHYJYMXOWPTQKQ
+12345 25G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF RSWY]
+12345 17G\KFK[ RKFTFWGXHYJYLXNWOTPKP RRPY[
+12345 21H\YIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX
+12345  6JZRFR[ RKFYF
+12345 11G]KFKULXNZQ[S[VZXXYUYF
+12345  6I[JFR[ RZFR[
+12345 12F^HFM[ RRFM[ RRFW[ R\FW[
+12345  6H\KFY[ RYFK[
+12345  7I[JFRPR[ RZFRP
+12345  9H\YFK[ RKFYF RK[Y[
+12345 12KYOBOb RPBPb ROBVB RObVb
+12345  3KYKFY^
+12345 12KYTBTb RUBUb RNBUB RNbUb
+12345 21H\YIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX
+12345 31G]RRTUUVWWYW[V\U]S]Q\O[NYMWMUNTOPUOVMWKWIVHUGSGQHOINKMMMONPORR
+12345 21G]IWHVGTGQHOINKMMMONPOTUUVWWYW[V\U]S]P\N[M
+12345 18I\XMX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 18H[LFL[ RLPNNPMSMUNWPXSXUWXUZS[P[NZLX
+12345 15I[XPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 18I\XFX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 18I[LSXSXQWOVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345  9MYWFUFSGRJR[ ROMVM
+12345 23I\XMX]W`VaTbQbOa RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345 11I\MFM[ RMQPNRMUMWNXQX[
+12345  9NVQFRGSFREQF RRMR[
+12345 12MWRFSGTFSERF RSMS^RaPbNb
+12345  9IZMFM[ RWMMW RQSX[
+12345  3NVRFR[
+12345 19CaGMG[ RGQJNLMOMQNRQR[ RRQUNWMZM\N]Q][
+12345 11I\MMM[ RMQPNRMUMWNXQX[
+12345 18I\QMONMPLSLUMXOZQ[T[VZXXYUYSXPVNTMQM
+12345 18H[LMLb RLPNNPMSMUNWPXSXUWXUZS[P[NZLX
+12345 18I\XMXb RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+12345  9KXOMO[ ROSPPRNTMWM
+12345 18J[XPWNTMQMNNMPNRPSUTWUXWXXWZT[Q[NZMX
+12345  9MYRFRWSZU[W[ ROMVM
+12345 11I\MMMWNZP[S[UZXW RXMX[
+12345  6JZLMR[ RXMR[
+12345 12G]JMN[ RRMN[ RRMV[ RZMV[
+12345  6J[MMX[ RXMM[
+12345 10JZLMR[ RXMR[P_NaLbKb
+12345  9J[XMM[ RMMXM RM[X[
+12345 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSU
+SWRYQZP\P^Q`RaTb
+12345 22H\KFK[ RHF[FQP[Z RZV[Y\[ RZVZY RWYZY RWYZZ\[
+12345 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQU
+QWRYSZT\T^S`RaPb
+12345 30KYUARBPCNELHKLKRLUNWQXSXVWXUYR RKPLMNKQJSJVKXMYPYVXZV]T_R`Oa
+12345 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
diff --git a/visad/util/package.html b/visad/util/package.html
new file mode 100644
index 0000000..41be6bc
--- /dev/null
+++ b/visad/util/package.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+</head>
+<body bgcolor="ffffff">
+
+Provides a collection of useful utilities, many of them
+GUI widgets, to aid in VisAD application design.
+
+</body>
+</html>
+
diff --git a/visad/util/rowmans.jhf b/visad/util/rowmans.jhf
new file mode 100644
index 0000000..602d96b
--- /dev/null
+++ b/visad/util/rowmans.jhf
@@ -0,0 +1,100 @@
+  699  1JZ
+  714  9MWRFRT RRYQZR[SZRY
+  717  6JZNFNM RVFVM
+  733 12H]SBLb RYBRb RLOZO RKUYU
+  719 27H\PBP_ RTBT_ RYIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX
+ 2271 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+  734 35E_\O\N[MZMYNXPVUTXRZP[L[JZIYHWHUISJRQNRMSKSIRGPFNGMIMKNNPQUXWZY[
+[[\Z\Y
+  731  8MWRHQGRFSGSIRKQL
+  721 11KYVBTDRGPKOPOTPYR]T`Vb
+  722 11KYNBPDRGTKUPUTTYR]P`Nb
+ 2219  9JZRFRR RMIWO RWIMO
+  725  6E_RIR[ RIR[R
+  711  9MWSZR[QZRYSZS\R^Q_
+  724  3E_IR[R
+  710  6MWRYQZR[SZRY
+  720  3G][BIb
+  700 18H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF
+  701  5H\NJPISFS[
+  702 15H\LKLJMHNGPFTFVGWHXJXLWNUQK[Y[
+  703 16H\MFXFRNUNWOXPYSYUXXVZS[P[MZLYKW
+  704  7H\UFKTZT RUFU[
+  705 18H\WFMFLOMNPMSMVNXPYSYUXXVZS[P[MZLYKW
+  706 24H\XIWGTFRFOGMJLOLTMXOZR[S[VZXXYUYTXQVOSNRNOOMQLT
+  707  6H\YFO[ RKFYF
+  708 30H\PFMGLILKMMONSOVPXRYTYWXYWZT[P[MZLYKWKTLRNPQOUNWMXKXIWGTFPF
+  709 24H\XMWPURRSQSNRLPKMKLLINGQFRFUGWIXMXRWWUZR[P[MZLX
+  712 12MWRMQNROSNRM RRYQZR[SZRY
+  713 15MWRMQNROSNRM RSZR[QZRYSZS\R^Q_
+ 2241  4F^ZIJRZ[
+  726  6E_IO[O RIU[U
+ 2242  4F^JIZRJ[
+  715 21I[LKLJMHNGPFTFVGWHXJXLWNVORQRT RRYQZR[SZRY
+ 2273 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+  501  9I[RFJ[ RRFZ[ RMTWT
+  502 24G\KFK[ RKFTFWGXHYJYLXNWOTP RKPTPWQXRYTYWXYWZT[K[
+  503 19H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZV
+  504 16G\KFK[ RKFRFUGWIXKYNYSXVWXUZR[K[
+  505 12H[LFL[ RLFYF RLPTP RL[Y[
+  506  9HZLFL[ RLFYF RLPTP
+  507 23H]ZKYIWGUFQFOGMILKKNKSLVMXOZQ[U[WZYXZVZS RUSZS
+  508  9G]KFK[ RYFY[ RKPYP
+  509  3NVRFR[
+  510 11JZVFVVUYTZR[P[NZMYLVLT
+  511  9G\KFK[ RYFKT RPOY[
+  512  6HYLFL[ RL[X[
+  513 12F^JFJ[ RJFR[ RZFR[ RZFZ[
+  514  9G]KFK[ RKFY[ RYFY[
+  515 22G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF
+  516 14G\KFK[ RKFTFWGXHYJYMXOWPTQKQ
+  517 25G]PFNGLIKKJNJSKVLXNZP[T[VZXXYVZSZNYKXIVGTFPF RSWY]
+  518 17G\KFK[ RKFTFWGXHYJYLXNWOTPKP RRPY[
+  519 21H\YIWGTFPFMGKIKKLMMNOOUQWRXSYUYXWZT[P[MZKX
+  520  6JZRFR[ RKFYF
+  521 11G]KFKULXNZQ[S[VZXXYUYF
+  522  6I[JFR[ RZFR[
+  523 12F^HFM[ RRFM[ RRFW[ R\FW[
+  524  6H\KFY[ RYFK[
+  525  7I[JFRPR[ RZFRP
+  526  9H\YFK[ RKFYF RK[Y[
+ 2223 12KYOBOb RPBPb ROBVB RObVb
+  804  3KYKFY^
+ 2224 12KYTBTb RUBUb RNBUB RNbUb
+ 2262 11JZPLRITL RMORJWO RRJR[
+  999  3JZJ]Z]
+  730  8MWSFRGQIQKRLSKRJ
+  601 18I\XMX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+  602 18H[LFL[ RLPNNPMSMUNWPXSXUWXUZS[P[NZLX
+  603 15I[XPVNTMQMONMPLSLUMXOZQ[T[VZXX
+  604 18I\XFX[ RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+  605 18I[LSXSXQWOVNTMQMONMPLSLUMXOZQ[T[VZXX
+  606  9MYWFUFSGRJR[ ROMVM
+  607 23I\XMX]W`VaTbQbOa RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+  608 11I\MFM[ RMQPNRMUMWNXQX[
+  609  9NVQFRGSFREQF RRMR[
+  610 12MWRFSGTFSERF RSMS^RaPbNb
+  611  9IZMFM[ RWMMW RQSX[
+  612  3NVRFR[
+  613 19CaGMG[ RGQJNLMOMQNRQR[ RRQUNWMZM\N]Q][
+  614 11I\MMM[ RMQPNRMUMWNXQX[
+  615 18I\QMONMPLSLUMXOZQ[T[VZXXYUYSXPVNTMQM
+  616 18H[LMLb RLPNNPMSMUNWPXSXUWXUZS[P[NZLX
+  617 18I\XMXb RXPVNTMQMONMPLSLUMXOZQ[T[VZXX
+  618  9KXOMO[ ROSPPRNTMWM
+  619 18J[XPWNTMQMNNMPNRPSUTWUXWXXWZT[Q[NZMX
+  620  9MYRFRWSZU[W[ ROMVM
+  621 11I\MMMWNZP[S[UZXW RXMX[
+  622  6JZLMR[ RXMR[
+  623 12G]JMN[ RRMN[ RRMV[ RZMV[
+  624  6J[MMX[ RXMM[
+  625 10JZLMR[ RXMR[P_NaLbKb
+  626  9J[XMM[ RMMXM RM[X[
+ 2225 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSU
+SWRYQZP\P^Q`RaTb
+  723  3NVRBRb
+ 2226 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQU
+QWRYSZT\T^S`RaPb
+ 2246 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
+  718 14KYQFOGNINKOMQNSNUMVKVIUGSFQF
diff --git a/visad/util/rowmant.jhf b/visad/util/rowmant.jhf
new file mode 100644
index 0000000..5b096bf
--- /dev/null
+++ b/visad/util/rowmant.jhf
@@ -0,0 +1,176 @@
+ 3199  1JZ
+ 3214 34MXRFQGQIRQ RRFRTST RRFSFST RSFTGTISQ RRXQYQZR[S[TZTYSXRX RRYRZSZ
+SYRY
+ 3228 22I[NFMGMM RNGMM RNFOGMM RWFVGVM RWGVM RWFXGVM
+ 2275 12H]SFLb RYFRb RLQZQ RKWYW
+ 3219 56H\PBP_ RTBT_ RXKXJWJWLYLYJXHWGTFPFMGKIKLLNOPURWSXUXXWZ RLLMNOOUQ
+WRXT RMGLILKMMONUPXRYTYWXYWZT[P[MZLYKWKUMUMWLWLV
+ 2271 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+ 3218 74E_[O[NZNZP\P\N[MZMYNXPVUTXRZP[L[JZIXIUJSPORMSKSIRGPFNGMIMLNOPRTW
+WZY[[[\Y\X RKZJXJUKSLR RRMSI RSKRG RNGMK RNNPQTVWYYZ RN[LZKXKULSPO RMINM
+QQUVXYZZ[Z\Y
+ 3217 24MXTHSIRIQHQGRFSFTGTJSLQM RRGRHSHSGRG RSITJ RTHSL
+ 3221 27KYUBSDQGOKNPNTOYQ]S`Ub RQHPKOOOUPYQ\ RSDRFQIPOPUQ[R^S`
+ 3222 27KYOBQDSGUKVPVTUYS]Q`Ob RSHTKUOUUTYS\ RQDRFSITOTUS[R^Q`
+ 3223 39JZRFQGSQRR RRFRR RRFSGQQRR RMINIVOWO RMIWO RMIMJWNWO RWIVINOMO R
+WIMO RWIWJMNMO
+ 3225 16F_RIRZSZ RRISISZ RJQ[Q[R RJQJR[R
+ 3211 24MXTZS[R[QZQYRXSXTYT\S^Q_ RRYRZSZSYRY RS[T\ RTZS^
+ 3224  8F_JQ[Q[R RJQJR[R
+ 3210 16MXRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+ 3220  8G^[BIbJb R[B\BJb
+ 3200 50H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF RNHMJLNLSMWNY RVYWWXSXNWJVH
+ RQFOGNIMNMSNXOZQ[ RS[UZVXWSWNVIUGSF
+ 3201 28H\QHQ[ RRHRZ RSFS[ RSFPINJ RM[W[ RQZO[ RQYP[ RSYT[ RSZU[
+ 3202 62H\LJLKMKMJLJ RLIMINJNKMLLLKKKJLHMGPFTFWGXHYJYLXNUPPRNSLUKXK[ RWH
+XJXLWN RTFVGWJWLVNTPPR RKYLXNXSYWYYX RNXSZWZXY RNXS[W[XZYXYV
+ 3203 76H\LJLKMKMJLJ RLIMINJNKMLLLKKKJLHMGPFTFWGXIXLWNTO RVGWIWLVN RSFUG
+VIVLUNSO RQOTOVPXRYTYWXYWZT[P[MZLYKWKVLUMUNVNWMXLX RWRXTXWWY RSOUPVQWTWW
+VZT[ RLVLWMWMVLV
+ 3204 28H\SIS[ RTHTZ RUFU[ RUFJUZU RP[X[ RSZQ[ RSYR[ RUYV[ RUZW[
+ 3205 55H\MFKPMNPMSMVNXPYSYUXXVZS[P[MZLYKWKVLUMUNVNWMXLX RWPXRXVWX RSMUN
+VOWRWVVYUZS[ RLVLWMWMVLV RMFWF RMGUG RMHQHUGWF
+ 3206 69H\VIVJWJWIVI RWHVHUIUJVKWKXJXIWGUFRFOGMILKKOKULXNZQ[S[VZXXYUYTXQ
+VOSNQNOONPMR RNIMKLOLUMXNY RWXXVXSWQ RRFPGOHNJMNMUNXOZQ[ RS[UZVYWVWSVPUO
+SN
+ 3207 43H\KFKL RYFYIXLTQSSRWR[ RSRRTQWQ[ RXLSQQTPWP[R[ RKJLHNFPFUIWIXHYF
+ RMHNGPGRH RKJLINHPHUI
+ 3208 79H\PFMGLILLMNPOTOWNXLXIWGTFPF RNGMIMLNN RVNWLWIVG RPFOGNINLONPO R
+TOUNVLVIUGTF RPOMPLQKSKWLYMZP[T[WZXYYWYSXQWPTO RMQLSLWMY RWYXWXSWQ RPONP
+MSMWNZP[ RT[VZWWWSVPTO
+ 3209 69H\MWMXNXNWMW RWOVQURSSQSNRLPKMKLLINGQFSFVGXIYLYRXVWXUZR[O[MZLXLW
+MVNVOWOXNYMY RMPLNLKMI RVHWIXLXRWVVX RQSORNQMNMKNHOGQF RSFUGVIWLWSVWUYTZ
+R[
+ 3212 32MXRMQNQORPSPTOTNSMRM RRNROSOSNRN RRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+ 3213 40MXRMQNQORPSPTOTNSMRM RRNROSOSNRN RTZS[R[QZQYRXSXTYT\S^Q_ RRYRZSZ
+SYRY RS[T\ RTZS^
+ 2241  4F^ZIJRZ[
+ 3226 16F_JM[M[N RJMJN[N RJU[U[V RJUJV[V
+ 2242  4F^JIZRJ[
+ 3215 52I\MKMJNJNLLLLJMHNGPFTFWGXHYJYLXNWOSQ RWHXIXMWN RTFVGWIWMVOUP RRQ
+RTSTSQRQ RRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+ 2273 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+ 3001 36H\RFKZ RQIW[ RRIX[ RRFY[ RMUVU RI[O[ RT[[[ RKZJ[ RKZM[ RWZU[ RWY
+V[ RXYZ[
+ 3002 78G]LFL[ RMGMZ RNFN[ RIFUFXGYHZJZLYNXOUP RXHYJYLXN RUFWGXIXMWOUP R
+NPUPXQYRZTZWYYXZU[I[ RXRYTYWXY RUPWQXSXXWZU[ RJFLG RKFLH ROFNH RPFNG RLZ
+J[ RLYK[ RNYO[ RNZP[
+ 3003 37G\XIYFYLXIVGTFQFNGLIKKJNJSKVLXNZQ[T[VZXXYV RMILKKNKSLVMX RQFOGMJ
+LNLSMWOZQ[
+ 3004 62G]LFL[ RMGMZ RNFN[ RIFSFVGXIYKZNZSYVXXVZS[I[ RWIXKYNYSXVWX RSFUG
+WJXNXSWWUZS[ RJFLG RKFLH ROFNH RPFNG RLZJ[ RLYK[ RNYO[ RNZP[
+ 3005 83G\LFL[ RMGMZ RNFN[ RIFYFYL RNPTP RTLTT RI[Y[YU RJFLG RKFLH ROFNH
+ RPFNG RTFYG RVFYH RWFYI RXFYL RTLSPTT RTNRPTR RTOPPTQ RLZJ[ RLYK[ RNYO[
+ RNZP[ RT[YZ RV[YY RW[YX RX[YU
+ 3006 70G[LFL[ RMGMZ RNFN[ RIFYFYL RNPTP RTLTT RI[Q[ RJFLG RKFLH ROFNH R
+PFNG RTFYG RVFYH RWFYI RXFYL RTLSPTT RTNRPTR RTOPPTQ RLZJ[ RLYK[ RNYO[ R
+NZP[
+ 3007 60G^XIYFYLXIVGTFQFNGLIKKJNJSKVLXNZQ[T[VZXZY[YS RMILKKNKSLVMX RQFOG
+MJLNLSMWOZQ[ RXTXY RWSWYVZ RTS\S RUSWT RVSWU RZSYU R[SYT
+ 3008 81F^KFK[ RLGLZ RMFM[ RWFW[ RXGXZ RYFY[ RHFPF RTF\F RMPWP RH[P[ RT[
+\[ RIFKG RJFKH RNFMH ROFMG RUFWG RVFWH RZFYH R[FYG RKZI[ RKYJ[ RMYN[ RMZ
+O[ RWZU[ RWYV[ RYYZ[ RYZ[[
+ 3009 39LXQFQ[ RRGRZ RSFS[ RNFVF RN[V[ ROFQG RPFQH RTFSH RUFSG RQZO[ RQY
+P[ RSYT[ RSZU[
+ 3010 45JJSFSWRZQ[ RTGTWSZ RUFUWTZQ[O[MZLXLVMUNUOVOWNXMX RMVMWNWNVMV RPF
+XF RQFSG RRFSH RVFUH RWFUG
+ 3011 69F\KFK[ RLGLZ RMFM[ RXGMR RPPW[ RQPX[ RQNY[ RHFPF RUF[F RH[P[ RT[
+[[ RIFKG RJFKH RNFMH ROFMG RWFXG RZFXG RKZI[ RKYJ[ RMYN[ RMZO[ RWYU[ RWY
+Z[
+ 3012 52I[NFN[ ROGOZ RPFP[ RKFSF RK[Z[ZU RLFNG RMFNH RQFPH RRFPG RNZL[ R
+NYM[ RPYQ[ RPZR[ RU[ZZ RW[ZY RX[ZX RY[ZU
+ 3013 63E_JFJZ RJFQ[ RKFQX RLFRX RXFQ[ RXFX[ RYGYZ RZFZ[ RGFLF RXF]F RG[
+M[ RU[][ RHFJG R[FZH R\FZG RJZH[ RJZL[ RXZV[ RXYW[ RZY[[ RZZ\[
+ 3014 39F^KFKZ RKFY[ RLFXX RMFYX RYGY[ RHFMF RVF\F RH[N[ RIFKG RWFYG R[F
+YG RKZI[ RKZM[
+ 3015 54G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RMILKKNKSLVMX RWXXV
+YSYNXKWI RQFOGMJLNLSMWOZQ[ RS[UZWWXSXNWJUGSF
+ 3016 59G]LFL[ RMGMZ RNFN[ RIFUFXGYHZJZMYOXPUQNQ RXHYJYMXO RUFWGXIXNWPUQ
+ RI[Q[ RJFLG RKFLH ROFNH RPFNG RLZJ[ RLYK[ RNYO[ RNZP[
+ 3017 77G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RMILKKNKSLVMX RWXXV
+YSYNXKWI RQFOGMJLNLSMWOZQ[ RS[UZWWXSXNWJUGSF RNXOVQURUTVUXV^W`Y`Z^Z\ RV\
+W^X_Y_ RUXW]X^Y^Z]
+ 3018 80G]LFL[ RMGMZ RNFN[ RIFUFXGYHZJZLYNXOUPNP RXHYJYLXN RUFWGXIXMWOUP
+ RRPTQUSWYX[Z[[Y[W RWWXYYZZZ RTQURXXYYZY[X RI[Q[ RJFLG RKFLH ROFNH RPFNG
+ RLZJ[ RLYK[ RNYO[ RNZP[
+ 3019 44H\XIYFYLXIVGSFPFMGKIKLLNOPURWSXUXXWZ RLLMNOOUQWRXT RMGLILKMMONUP
+XRYTYWXYWZT[Q[NZLXKUK[LX
+ 3020 57H\JFJL RQFQ[ RRGRZ RSFS[ RZFZL RJFZF RN[V[ RKFJL RLFJI RMFJH ROF
+JG RUFZG RWFZH RXFZI RYFZL RQZO[ RQYP[ RSYT[ RSZU[
+ 3021 45F^KFKULXNZQ[S[VZXXYUYG RLGLVMX RMFMVNYOZQ[ RHFPF RVF\F RIFKG RJF
+KH RNFMH ROFMG RWFYG R[FYG
+ 3022 34H\KFR[ RLFRXR[ RMFSX RYGR[ RIFPF RUF[F RJFLH RNFMH ROFMG RWFYG R
+ZFYG
+ 3023 55F^JFN[ RKFNVN[ RLFOV RRFOVN[ RRFV[ RSFVVV[ RTFWV RZGWVV[ RGFOF R
+RFTF RWF]F RHFKG RIFKH RMFLH RNFLG RXFZG R\FZG
+ 3024 54H\KFW[ RLFX[ RMFY[ RXGLZ RIFPF RUF[F RI[O[ RT[[[ RJFMH RNFMH ROF
+MG RVFXG RZFXG RLZJ[ RLZN[ RWZU[ RWYV[ RWYZ[
+ 3025 48G]JFQQQ[ RKFRQRZ RLFSQS[ RYGSQ RHFOF RVF\F RN[V[ RIFKG RNFLG RWF
+YG R[FYG RQZO[ RQYP[ RSYT[ RSZU[
+ 3026 41H\YFKFKL RWFK[ RXFL[ RYFM[ RK[Y[YU RLFKL RMFKI RNFKH RPFKG RT[YZ
+ RV[YY RW[YX RX[YU
+ 2223 12KYOBOb RPBPb ROBVB RObVb
+  804  3KYKFY^
+ 2224 12KYTBTb RUBUb RNBUB RNbUb
+ 2262 11JZPLRITL RMORJWO RRJR[
+  999  3JZJ]Z]
+ 3216 24MXTFRGQIQLRMSMTLTKSJRJQK RRKRLSLSKRK RRGQK RQIRJ
+ 3101 54I]NPNOOOOQMQMONNPMTMVNWOXQXXYZZ[ RVOWQWXXZ RTMUNVPVXWZZ[[[ RVRUS
+PTMULWLXMZP[S[UZVX RNUMWMXNZ RUSQTOUNWNXOZP[
+ 3102 47G\LFL[MZOZ RMGMY RIFNFNZ RNPONQMSMVNXPYSYUXXVZS[Q[OZNX RWPXRXVWX
+ RSMUNVOWRWVVYUZS[ RJFLG RKFLH
+ 3103 34H[WQWPVPVRXRXPVNTMQMNNLPKSKULXNZQ[S[VZXX RMPLRLVMX RQMONNOMRMVNY
+OZQ[
+ 3104 52H]VFV[[[ RWGWZ RSFXFX[ RVPUNSMQMNNLPKSKULXNZQ[S[UZVX RMPLRLVMX R
+QMONNOMRMVNYOZQ[ RTFVG RUFVH RXYY[ RXZZ[
+ 3105 41H[MSXSXQWOVNSMQMNNLPKSKULXNZQ[S[VZXX RWRWQVO RMPLRLVMX RVSVPUNSM
+ RQMONNOMRMVNYOZQ[
+ 3106 40KYWHWGVGVIXIXGWFTFRGQHPKP[ RRHQKQZ RTFSGRIR[ RMMVM RM[U[ RPZN[ R
+PYO[ RRYS[ RRZT[
+ 3107 89I\XNYOZNYMXMVNUO RQMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM ROONQNSOU R
+UUVSVQUO RQMPNOPOTPVQW RSWTVUTUPTNSM RNUMVLXLYM[N\Q]U]X^Y_ RN[Q\U\X] RLY
+MZP[U[X\Y^Y_XaUbObLaK_K^L\O[ RObMaL_L^M\O[
+ 3108 65G^LFL[ RMGMZ RIFNFN[ RNQOOPNRMUMWNXOYRY[ RWOXRXZ RUMVNWQW[ RI[Q[
+ RT[\[ RJFLG RKFLH RLZJ[ RLYK[ RNYO[ RNZP[ RWZU[ RWYV[ RYYZ[ RYZ[[
+ 3109 43LXQFQHSHSFQF RRFRH RQGSG RQMQ[ RRNRZ RNMSMS[ RN[V[ ROMQN RPMQO R
+QZO[ RQYP[ RSYT[ RSZU[
+ 3110 41KXRFRHTHTFRF RSFSH RRGTG RRMR^QaPb RSNS]R` ROMTMT]S`RaPbMbLaL_N_
+NaMaM` RPMRN RQMRO
+ 3111 61G]LFL[ RMGMZ RIFNFN[ RWNNW RRSY[ RRTX[ RQTW[ RTM[M RI[Q[ RT[[[ R
+JFLG RKFLH RUMWN RZMWN RLZJ[ RLYK[ RNYO[ RNZP[ RWYU[ RVYZ[
+ 3112 31LXQFQ[ RRGRZ RNFSFS[ RN[V[ ROFQG RPFQH RQZO[ RQYP[ RSYT[ RSZU[
+ 3113 99AcFMF[ RGNGZ RCMHMH[ RHQIOJNLMOMQNROSRS[ RQORRRZ ROMPNQQQ[ RSQTO
+UNWMZM\N]O^R^[ R\O]R]Z RZM[N\Q\[ RC[K[ RN[V[ RY[a[ RDMFN REMFO RFZD[ RFY
+E[ RHYI[ RHZJ[ RQZO[ RQYP[ RSYT[ RSZU[ R\ZZ[ R\Y[[ R^Y_[ R^Z`[
+ 3114 65G^LML[ RMNMZ RIMNMN[ RNQOOPNRMUMWNXOYRY[ RWOXRXZ RUMVNWQW[ RI[Q[
+ RT[\[ RJMLN RKMLO RLZJ[ RLYK[ RNYO[ RNZP[ RWZU[ RWYV[ RYYZ[ RYZ[[
+ 3115 46H\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM RMPLRLVMX RWXXVXRWP RQMONNO
+MRMVNYOZQ[ RS[UZVYWVWRVOUNSM
+ 3116 60G\LMLb RMNMa RIMNMNb RNPONQMSMVNXPYSYUXXVZS[Q[OZNX RWPXRXVWX RSM
+UNVOWRWVVYUZS[ RIbQb RJMLN RKMLO RLaJb RL`Kb RN`Ob RNaPb
+ 3117 55H\VNVb RWOWa RUNWNXMXb RVPUNSMQMNNLPKSKULXNZQ[S[UZVX RMPLRLVMX R
+QMONNOMRMVNYOZQ[ RSb[b RVaTb RV`Ub RX`Yb RXaZb
+ 3118 43IZNMN[ RONOZ RKMPMP[ RWOWNVNVPXPXNWMUMSNQPPS RK[S[ RLMNN RMMNO R
+NZL[ RNYM[ RPYQ[ RPZR[
+ 3119 43J[WOXMXQWOVNTMPMNNMOMQNSPTUUWVXY RNNMQ RNRPSUTWU RXVWZ RMONQPRUS
+WTXVXYWZU[Q[OZNYMWM[NY
+ 3120 22KZPHPVQYRZT[V[XZYX RQHQWRY RPHRFRWSZT[ RMMVM
+ 3121 43G^LMLVMYNZP[S[UZVYWW RMNMWNY RIMNMNWOZP[ RWMW[\[ RXNXZ RTMYMY[ R
+JMLN RKMLO RYYZ[ RYZ[[
+ 3122 31I[LMR[ RMMRY RNMSY RXNSYR[ RJMQM RTMZM RKMNO RPMNN RVMXN RYMXN
+ 3123 45F^JMN[ RKMNX RLMOX RRMOXN[ RRMV[ RSMVX RRMTMWX RZNWXV[ RGMOM RWM
+]M RHMKN RNMLN RXMZN R\MZN
+ 3124 48H\LMV[ RMMW[ RNMX[ RWNMZ RJMQM RTMZM RJ[P[ RS[Z[ RKMMN RPMNN RUM
+WN RYMWN RMZK[ RMZO[ RVZT[ RWZY[
+ 3125 40H[LMR[ RMMRY RNMSY RXNSYP_NaLbJbIaI_K_KaJaJ` RJMQM RTMZM RKMNO R
+PMNN RVMXN RYMXN
+ 3126 41I[VML[ RWMM[ RXMN[ RXMLMLQ RL[X[XW RMMLQ RNMLP ROMLO RQMLN RS[XZ
+ RU[XY RV[XX RW[XW
+ 2225 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSU
+SWRYQZP\P^Q`RaTb
+ 2229  3NVRBRb
+ 2226 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQU
+QWRYSZT\T^S`RaPb
+ 2246 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
+ 3229 30KYQFOGNINKOMQNSNUMVKVIUGSFQF RQFNIOMSNVKUGQF RSFOGNKQNUMVISF
diff --git a/visad/util/timesr.jhf b/visad/util/timesr.jhf
new file mode 100644
index 0000000..b0826a4
--- /dev/null
+++ b/visad/util/timesr.jhf
@@ -0,0 +1,123 @@
+12345  1JZ
+12345 15MWRFQHRTSHRF RRHRN RRYQZR[SZRY
+12345 22I[NFMGMM RNGMM RNFOGMM RWFVGVM RWGVM RWFXGVM
+12345 12H]SBLb RYBRb RLOZO RKUYU
+12345 42H\PBP_ RTBT_ RXIWJXKYJYIWGTFPFMGKIKKLMMNOOUQWRYT RKKMMONUPWQXRYT
+YXWZT[P[MZKXKWLVMWLX
+12345 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+12345 49F_[NZO[P\O\N[MZMYNXPVUTXRZP[M[JZIXIUJSPORMSKSIRGPFNGMIMKNNPQUXWZ
+Z[[[\Z\Y RM[KZJXJUKSMQ RMKNMVXXZZ[
+12345  6NVRFQM RSFQM
+12345 20KYVBTDRGPKOPOTPYR]T`Vb RTDRHQKPPPTQYR\T`
+12345 20KYNBPDRGTKUPUTTYR]P`Nb RPDRHSKTPTTSYR\P`
+12345  9JZRLRX RMOWU RWOMU
+12345  6E_RIR[ RIR[R
+12345  8NVSWRXQWRVSWSYQ[
+12345  3E_IR[R
+12345  6NVRVQWRXSWRV
+12345  3G][BIb
+12345 40H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF RQFOGNHMJLOLRMWNYOZQ[ RS[UZ
+VYWWXRXOWJVHUGSF
+12345 11H\NJPISFS[ RRGR[ RN[W[
+12345 45H\LJMKLLKKKJLHMGPFTFWGXHYJYLXNUPPRNSLUKXK[ RTFVGWHXJXLWNTPPR RKY
+LXNXSZVZXYYX RNXS[W[XZYXYV
+12345 47H\LJMKLLKKKJLHMGPFTFWGXIXLWNTOQO RTFVGWIWLVNTO RTOVPXRYTYWXYWZT[
+P[MZLYKWKVLUMVLW RWQXTXWWYVZT[
+12345 13H\THT[ RUFU[ RUFJUZU RQ[X[
+12345 39H\MFKP RKPMNPMSMVNXPYSYUXXVZS[P[MZLYKWKVLUMVLW RSMUNWPXSXUWXUZS[
+ RMFWF RMGRGWF
+12345 48H\WIVJWKXJXIWGUFRFOGMILKKOKULXNZQ[S[VZXXYUYTXQVOSNRNOOMQLT RRFPG
+NIMKLOLUMXOZQ[ RS[UZWXXUXTWQUOSN
+12345 31H\KFKL RKJLHNFPFUIWIXHYF RLHNGPGUI RYFYIXLTQSSRVR[ RXLSQRSQVQ[
+12345 63H\PFMGLILLMNPOTOWNXLXIWGTFPF RPFNGMIMLNNPO RTOVNWLWIVGTF RPOMPLQ
+KSKWLYMZP[T[WZXYYWYSXQWPTO RPONPMQLSLWMYNZP[ RT[VZWYXWXSWQVPTO
+12345 48H\XMWPURRSQSNRLPKMKLLINGQFSFVGXIYLYRXVWXUZR[O[MZLXLWMVNWMX RQSOR
+MPLMLLMIOGQF RSFUGWIXLXRWVVXTZR[
+12345 12NVROQPRQSPRO RRVQWRXSWRV
+12345 14NVROQPRQSPRO RSWRXQWRVSWSYQ[
+12345  4F^ZIJRZ[
+12345  6E_IO[O RIU[U
+12345  4F^JIZRJ[
+12345 32I[MJNKMLLKLJMHNGPFSFVGWHXJXLWNVORQRT RSFUGVHWJWLVNTP RRYQZR[SZRY
+12345 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+12345 18H\RFK[ RRFY[ RRIX[ RMUVU RI[O[ RU[[[
+12345 45G]LFL[ RMFM[ RIFUFXGYHZJZLYNXOUP RUFWGXHYJYLXNWOUP RMPUPXQYRZTZW
+YYXZU[I[ RUPWQXRYTYWXYWZU[
+12345 32G\XIYLYFXIVGSFQFNGLIKKJNJSKVLXNZQ[S[VZXXYV RQFOGMILKKNKSLVMXOZQ[
+12345 30G]LFL[ RMFM[ RIFSFVGXIYKZNZSYVXXVZS[I[ RSFUGWIXKYNYSXVWXUZS[
+12345 22G\LFL[ RMFM[ RSLST RIFYFYLXF RMPSP RI[Y[YUX[
+12345 20G[LFL[ RMFM[ RSLST RIFYFYLXF RMPSP RI[P[
+12345 40G^XIYLYFXIVGSFQFNGLIKKJNJSKVLXNZQ[S[VZXX RQFOGMILKKNKSLVMXOZQ[ R
+XSX[ RYSY[ RUS\S
+12345 27F^KFK[ RLFL[ RXFX[ RYFY[ RHFOF RUF\F RLPXP RH[O[ RU[\[
+12345 12MXRFR[ RSFS[ ROFVF RO[V[
+12345 20KZUFUWTZR[P[NZMXMVNUOVNW RTFTWSZR[ RQFXF
+12345 27F\KFK[ RLFL[ RYFLS RQOY[ RPOX[ RHFOF RUF[F RH[O[ RU[[[
+12345 14I[NFN[ ROFO[ RKFRF RK[Z[ZUY[
+12345 30F_KFK[ RLFRX RKFR[ RYFR[ RYFY[ RZFZ[ RHFLF RYF]F RH[N[ RV[][
+12345 21G^LFL[ RMFYY RMHY[ RYFY[ RIFMF RVF\F RI[O[
+12345 44G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RQFOGMILKKOKRLVMXOZ
+Q[ RS[UZWXXVYRYOXKWIUGSF
+12345 29G]LFL[ RMFM[ RIFUFXGYHZJZMYOXPUQMQ RUFWGXHYJYMXOWPUQ RI[P[
+12345 64G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RQFOGMILKKOKRLVMXOZ
+Q[ RS[UZWXXVYRYOXKWIUGSF RNYNXOVQURUTVUXV_W`Y`Z^Z] RUXV\W^X_Y_Z^
+12345 45G]LFL[ RMFM[ RIFUFXGYHZJZLYNXOUPMP RUFWGXHYJYLXNWOUP RI[P[ RRPTQ
+URXYYZZZ[Y RTQUSWZX[Z[[Y[X
+12345 34H\XIYFYLXIVGSFPFMGKIKKLMMNOOUQWRYT RKKMMONUPWQXRYTYXWZT[Q[NZLXKU
+K[LX
+12345 16I\RFR[ RSFS[ RLFKLKFZFZLYF RO[V[
+12345 23F^KFKULXNZQ[S[VZXXYUYF RLFLUMXOZQ[ RHFOF RVF\F
+12345 15H\KFR[ RLFRX RYFR[ RIFOF RUF[F
+12345 24F^JFN[ RKFNV RRFN[ RRFV[ RSFVV RZFV[ RGFNF RWF]F
+12345 21H\KFX[ RLFY[ RYFK[ RIFOF RUF[F RI[O[ RU[[[
+12345 20H]KFRQR[ RLFSQS[ RZFSQ RIFOF RVF\F RO[V[
+12345 16H\XFK[ RYFL[ RLFKLKFYF RK[Y[YUX[
+12345 12KYOBOb RPBPb ROBVB RObVb
+12345  3KYKFY^
+12345 12KYTBTb RUBUb RNBUB RNbUb
+12345  8G]JTROZT RJTRPZT
+12345  3H\Hb\b
+12345  7LXPFUL RPFOGUL
+12345 39I]NONPMPMONNPMTMVNWOXQXXYZZ[ RWOWXXZZ[[[ RWQVRPSMTLVLXMZP[S[UZWX
+ RPSNTMVMXNZP[
+12345 33G\LFL[ RMFM[ RMPONQMSMVNXPYSYUXXVZS[Q[OZMX RSMUNWPXSXUWXUZS[ RIF
+MF
+12345 28H[WPVQWRXQXPVNTMQMNNLPKSKULXNZQ[S[VZXX RQMONMPLSLUMXOZQ[
+12345 36H]WFW[ RXFX[ RWPUNSMQMNNLPKSKULXNZQ[S[UZWX RQMONMPLSLUMXOZQ[ RTF
+XF RW[[[
+12345 31H[LSXSXQWOVNTMQMNNLPKSKULXNZQ[S[VZXX RWSWPVN RQMONMPLSLUMXOZQ[
+12345 22KXUGTHUIVHVGUFSFQGPIP[ RSFRGQIQ[ RMMUM RM[T[
+12345 60I\QMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM RONNPNTOV RUVVTVPUN RVOWNYM
+YNWN RNUMVLXLYM[P\U\X]Y^ RLYMZP[U[X\Y^Y_XaUbObLaK_K^L\O[
+12345 28G]LFL[ RMFM[ RMPONRMTMWNXPX[ RTMVNWPW[ RIFMF RI[P[ RT[[[
+12345 18MXRFQGRHSGRF RRMR[ RSMS[ ROMSM RO[V[
+12345 25MXSFRGSHTGSF RTMT_SaQbObNaN`O_P`Oa RSMS_RaQb RPMTM
+12345 27G\LFL[ RMFM[ RWMMW RRSX[ RQSW[ RIFMF RTMZM RI[P[ RT[Z[
+12345 12MXRFR[ RSFS[ ROFSF RO[V[
+12345 44BcGMG[ RHMH[ RHPJNMMOMRNSPS[ ROMQNRPR[ RSPUNXMZM]N^P^[ RZM\N]P][
+ RDMHM RD[K[ RO[V[ RZ[a[
+12345 28G]LML[ RMMM[ RMPONRMTMWNXPX[ RTMVNWPW[ RIMMM RI[P[ RT[[[
+12345 36H\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM RQMONMPLSLUMXOZQ[ RS[UZWXXU
+XSWPUNSM
+12345 36G\LMLb RMMMb RMPONQMSMVNXPYSYUXXVZS[Q[OZMX RSMUNWPXSXUWXUZS[ RIM
+MM RIbPb
+12345 33H\WMWb RXMXb RWPUNSMQMNNLPKSKULXNZQ[S[UZWX RQMONMPLSLUMXOZQ[ RTb
+[b
+12345 23IZNMN[ ROMO[ ROSPPRNTMWMXNXOWPVOWN RKMOM RK[R[
+12345 32J[WOXMXQWOVNTMPMNNMOMQNRPSUUWVXW RMPNQPRUTWUXVXYWZU[Q[OZNYMWM[NY
+12345 16KZPFPWQZS[U[WZXX RQFQWRZS[ RMMUM
+12345 28G]LMLXMZP[R[UZWX RMMMXNZP[ RWMW[ RXMX[ RIMMM RTMXM RW[[[
+12345 15I[LMR[ RMMRY RXMR[ RJMPM RTMZM
+12345 24F^JMN[ RKMNX RRMN[ RRMV[ RSMVX RZMV[ RGMNM RWM]M
+12345 21H\LMW[ RMMX[ RXML[ RJMPM RTMZM RJ[P[ RT[Z[
+12345 22H[LMR[ RMMRY RXMR[P_NaLbKbJaK`La RJMPM RTMZM
+12345 16I[WML[ RXMM[ RMMLQLMXM RL[X[XWW[
+12345 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSU
+SWRYQZP\P^Q`RaTb
+12345  3NVRBRb
+12345 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQU
+QWRYSZT\T^S`RaPb
+12345 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
+12345 35JZJFJ[K[KFLFL[M[MFNFN[O[OFPFP[Q[QFRFR[S[SFTFT[U[UFVFV[W[WFXFX[Y[
+YFZFZ[
diff --git a/visad/util/timesrb.jhf b/visad/util/timesrb.jhf
new file mode 100644
index 0000000..6572639
--- /dev/null
+++ b/visad/util/timesrb.jhf
@@ -0,0 +1,177 @@
+12345  1JZ
+12345 34MXRFQGQIRQ RRFRTST RRFSFST RSFTGTISQ RRXQYQZR[S[TZTYSXRX RRYRZSZ
+SYRY
+12345 22I[NFMGMM RNGMM RNFOGMM RWFVGVM RWGVM RWFXGVM
+12345 12H]SBLb RYBRb RLOZO RKUYU
+12345 56H\PBP_ RTBT_ RXKXJWJWLYLYJXHWGTFPFMGKIKLLNOPURWSXUXXWZ RLLMNOOUQ
+WRXT RMGLILKMMONUPXRYTYWXYWZT[P[MZLYKWKUMUMWLWLV
+12345 32F^[FI[ RNFPHPJOLMMKMIKIIJGLFNFPGSHVHYG[F RWTUUTWTYV[X[ZZ[X[VYTWT
+12345 74E_[O[NZNZP\P\N[MZMYNXPVUTXRZP[L[JZIXIUJSPORMSKSIRGPFNGMIMLNOPRTW
+WZY[[[\Y\X RKZJXJUKSLR RRMSI RSKRG RNGMK RNNPQTVWYYZ RN[LZKXKULSPO RMINM
+QQUVXYZZ[Z\Y
+12345 11NWSFRGRM RSGRM RSFTGRM
+12345 27KYUBSDQGOKNPNTOYQ]S`Ub RQHPKOOOUPYQ\ RSDRFQIPOPUQ[R^S`
+12345 27KYOBQDSGUKVPVTUYS]Q`Ob RSHTKUOUUTYS\ RQDRFSITOTUS[R^Q`
+12345 39JZRFQGSQRR RRFRR RRFSGQQRR RMINIVOWO RMIWO RMIMJWNWO RWIVINOMO R
+WIMO RWIWJMNMO
+12345 16F_RIRZSZ RRISISZ RJQ[Q[R RJQJR[R
+12345 24MXTZS[R[QZQYRXSXTYT\S^Q_ RRYRZSZSYRY RS[T\ RTZS^
+12345  3E_IR[R
+12345 16MXRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+12345  8G^[BIbJb R[B\BJb
+12345 50H\QFNGLJKOKRLWNZQ[S[VZXWYRYOXJVGSFQF RNHMJLNLSMWNY RVYWWXSXNWJVH
+ RQFOGNIMNMSNXOZQ[ RS[UZVXWSWNVIUGSF
+12345 28H\QHQ[ RRHRZ RSFS[ RSFPINJ RM[W[ RQZO[ RQYP[ RSYT[ RSZU[
+12345 62H\LJLKMKMJLJ RLIMINJNKMLLLKKKJLHMGPFTFWGXHYJYLXNUPPRNSLUKXK[ RWH
+XJXLWN RTFVGWJWLVNTPPR RKYLXNXSYWYYX RNXSZWZXY RNXS[W[XZYXYV
+12345 76H\LJLKMKMJLJ RLIMINJNKMLLLKKKJLHMGPFTFWGXIXLWNTO RVGWIWLVN RSFUG
+VIVLUNSO RQOTOVPXRYTYWXYWZT[P[MZLYKWKVLUMUNVNWMXLX RWRXTXWWY RSOUPVQWTWW
+VZT[ RLVLWMWMVLV
+12345 28H\SIS[ RTHTZ RUFU[ RUFJUZU RP[X[ RSZQ[ RSYR[ RUYV[ RUZW[
+12345 55H\MFKPMNPMSMVNXPYSYUXXVZS[P[MZLYKWKVLUMUNVNWMXLX RWPXRXVWX RSMUN
+VOWRWVVYUZS[ RLVLWMWMVLV RMFWF RMGUG RMHQHUGWF
+12345 69H\VIVJWJWIVI RWHVHUIUJVKWKXJXIWGUFRFOGMILKKOKULXNZQ[S[VZXXYUYTXQ
+VOSNQNOONPMR RNIMKLOLUMXNY RWXXVXSWQ RRFPGOHNJMNMUNXOZQ[ RS[UZVYWVWSVPUO
+SN
+12345 43H\KFKL RYFYIXLTQSSRWR[ RSRRTQWQ[ RXLSQQTPWP[R[ RKJLHNFPFUIWIXHYF
+ RMHNGPGRH RKJLINHPHUI
+12345 79H\PFMGLILLMNPOTOWNXLXIWGTFPF RNGMIMLNN RVNWLWIVG RPFOGNINLONPO R
+TOUNVLVIUGTF RPOMPLQKSKWLYMZP[T[WZXYYWYSXQWPTO RMQLSLWMY RWYXWXSWQ RPONP
+MSMWNZP[ RT[VZWWWSVPTO
+12345 69H\MWMXNXNWMW RWOVQURSSQSNRLPKMKLLINGQFSFVGXIYLYRXVWXUZR[O[MZLXLW
+MVNVOWOXNYMY RMPLNLKMI RVHWIXLXRWVVX RQSORNQMNMKNHOGQF RSFUGVIWLWSVWUYTZ
+R[
+12345 32MXRMQNQORPSPTOTNSMRM RRNROSOSNRN RRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+12345 40MXRMQNQORPSPTOTNSMRM RRNROSOSNRN RTZS[R[QZQYRXSXTYT\S^Q_ RRYRZSZ
+SYRY RS[T\ RTZS^
+12345  4F^ZIJRZ[
+12345 16F_JM[M[N RJMJN[N RJU[U[V RJUJV[V
+12345  4F^JIZRJ[
+12345 52I\MKMJNJNLLLLJMHNGPFTFWGXHYJYLXNWOSQ RWHXIXMWN RTFVGWIWMVOUP RRQ
+RTSTSQRQ RRXQYQZR[S[TZTYSXRX RRYRZSZSYRY
+12345 56E`WNVLTKQKOLNMMPMSNUPVSVUUVS RQKOMNPNSOUPV RWKVSVUXVZV\T]Q]O\L[J
+YHWGTFQFNGLHJJILHOHRIUJWLYNZQ[T[WZYYZX RXKWSWUXV
+12345 36H\RFKZ RQIW[ RRIX[ RRFY[ RMUVU RI[O[ RT[[[ RKZJ[ RKZM[ RWZU[ RWY
+V[ RXYZ[
+12345 78G]LFL[ RMGMZ RNFN[ RIFUFXGYHZJZLYNXOUP RXHYJYLXN RUFWGXIXMWOUP R
+NPUPXQYRZTZWYYXZU[I[ RXRYTYWXY RUPWQXSXXWZU[ RJFLG RKFLH ROFNH RPFNG RLZ
+J[ RLYK[ RNYO[ RNZP[
+12345 37G\XIYFYLXIVGTFQFNGLIKKJNJSKVLXNZQ[T[VZXXYV RMILKKNKSLVMX RQFOGMJ
+LNLSMWOZQ[
+12345 62G]LFL[ RMGMZ RNFN[ RIFSFVGXIYKZNZSYVXXVZS[I[ RWIXKYNYSXVWX RSFUG
+WJXNXSWWUZS[ RJFLG RKFLH ROFNH RPFNG RLZJ[ RLYK[ RNYO[ RNZP[
+12345 83G\LFL[ RMGMZ RNFN[ RIFYFYL RNPTP RTLTT RI[Y[YU RJFLG RKFLH ROFNH
+ RPFNG RTFYG RVFYH RWFYI RXFYL RTLSPTT RTNRPTR RTOPPTQ RLZJ[ RLYK[ RNYO[
+ RNZP[ RT[YZ RV[YY RW[YX RX[YU
+12345 70G[LFL[ RMGMZ RNFN[ RIFYFYL RNPTP RTLTT RI[Q[ RJFLG RKFLH ROFNH R
+PFNG RTFYG RVFYH RWFYI RXFYL RTLSPTT RTNRPTR RTOPPTQ RLZJ[ RLYK[ RNYO[ R
+NZP[
+12345 60G^XIYFYLXIVGTFQFNGLIKKJNJSKVLXNZQ[T[VZXZY[YS RMILKKNKSLVMX RQFOG
+MJLNLSMWOZQ[ RXTXY RWSWYVZ RTS\S RUSWT RVSWU RZSYU R[SYT
+12345 81F^KFK[ RLGLZ RMFM[ RWFW[ RXGXZ RYFY[ RHFPF RTF\F RMPWP RH[P[ RT[
+\[ RIFKG RJFKH RNFMH ROFMG RUFWG RVFWH RZFYH R[FYG RKZI[ RKYJ[ RMYN[ RMZ
+O[ RWZU[ RWYV[ RYYZ[ RYZ[[
+12345 39LXQFQ[ RRGRZ RSFS[ RNFVF RN[V[ ROFQG RPFQH RTFSH RUFSG RQZO[ RQY
+P[ RSYT[ RSZU[
+12345 45JZSFSWRZQ[ RTGTWSZ RUFUWTZQ[O[MZLXLVMUNUOVOWNXMX RMVMWNWNVMV RPF
+XF RQFSG RRFSH RVFUH RWFUG
+12345 69F\KFK[ RLGLZ RMFM[ RXGMR RPPW[ RQPX[ RQNY[ RHFPF RUF[F RH[P[ RT[
+[[ RIFKG RJFKH RNFMH ROFMG RWFXG RZFXG RKZI[ RKYJ[ RMYN[ RMZO[ RWYU[ RWY
+Z[
+12345 52I[NFN[ ROGOZ RPFP[ RKFSF RK[Z[ZU RLFNG RMFNH RQFPH RRFPG RNZL[ R
+NYM[ RPYQ[ RPZR[ RU[ZZ RW[ZY RX[ZX RY[ZU
+12345 63E_JFJZ RJFQ[ RKFQX RLFRX RXFQ[ RXFX[ RYGYZ RZFZ[ RGFLF RXF]F RG[
+M[ RU[][ RHFJG R[FZH R\FZG RJZH[ RJZL[ RXZV[ RXYW[ RZY[[ RZZ\[
+12345 39F^KFKZ RKFY[ RLFXX RMFYX RYGY[ RHFMF RVF\F RH[N[ RIFKG RWFYG R[F
+YG RKZI[ RKZM[
+12345 54G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RMILKKNKSLVMX RWXXV
+YSYNXKWI RQFOGMJLNLSMWOZQ[ RS[UZWWXSXNWJUGSF
+12345 59G]LFL[ RMGMZ RNFN[ RIFUFXGYHZJZMYOXPUQNQ RXHYJYMXO RUFWGXIXNWPUQ
+ RI[Q[ RJFLG RKFLH ROFNH RPFNG RLZJ[ RLYK[ RNYO[ RNZP[
+12345 77G]QFNGLIKKJOJRKVLXNZQ[S[VZXXYVZRZOYKXIVGSFQF RMILKKNKSLVMX RWXXV
+YSYNXKWI RQFOGMJLNLSMWOZQ[ RS[UZWWXSXNWJUGSF RNXOVQURUTVUXV^W`Y`Z^Z\ RV\
+W^X_Y_ RUXW]X^Y^Z]
+12345 80G]LFL[ RMGMZ RNFN[ RIFUFXGYHZJZLYNXOUPNP RXHYJYLXN RUFWGXIXMWOUP
+ RRPTQUSWYX[Z[[Y[W RWWXYYZZZ RTQURXXYYZY[X RI[Q[ RJFLG RKFLH ROFNH RPFNG
+ RLZJ[ RLYK[ RNYO[ RNZP[
+12345 44H\XIYFYLXIVGSFPFMGKIKLLNOPURWSXUXXWZ RLLMNOOUQWRXT RMGLILKMMONUP
+XRYTYWXYWZT[Q[NZLXKUK[LX
+12345 57H\JFJL RQFQ[ RRGRZ RSFS[ RZFZL RJFZF RN[V[ RKFJL RLFJI RMFJH ROF
+JG RUFZG RWFZH RXFZI RYFZL RQZO[ RQYP[ RSYT[ RSZU[
+12345 45F^KFKULXNZQ[S[VZXXYUYG RLGLVMX RMFMVNYOZQ[ RHFPF RVF\F RIFKG RJF
+KH RNFMH ROFMG RWFYG R[FYG
+12345 34H\KFR[ RLFRXR[ RMFSX RYGR[ RIFPF RUF[F RJFLH RNFMH ROFMG RWFYG R
+ZFYG
+12345 55F^JFN[ RKFNVN[ RLFOV RRFOVN[ RRFV[ RSFVVV[ RTFWV RZGWVV[ RGFOF R
+RFTF RWF]F RHFKG RIFKH RMFLH RNFLG RXFZG R\FZG
+12345 54H\KFW[ RLFX[ RMFY[ RXGLZ RIFPF RUF[F RI[O[ RT[[[ RJFMH RNFMH ROF
+MG RVFXG RZFXG RLZJ[ RLZN[ RWZU[ RWYV[ RWYZ[
+12345 48G]JFQQQ[ RKFRQRZ RLFSQS[ RYGSQ RHFOF RVF\F RN[V[ RIFKG RNFLG RWF
+YG R[FYG RQZO[ RQYP[ RSYT[ RSZU[
+12345 41H\YFKFKL RWFK[ RXFL[ RYFM[ RK[Y[YU RLFKL RMFKI RNFKH RPFKG RT[YZ
+ RV[YY RW[YX RX[YU
+12345 12KYOBOb RPBPb ROBVB RObVb
+12345  3KYKFY^
+12345 12KYTBTb RUBUb RNBUB RNbUb
+12345  8G]JTROZT RJTRPZT
+12345  3H\Hb\b
+12345  7LXPFUL RPFOGUL
+12345 54I]NPNOOOOQMQMONNPMTMVNWOXQXXYZZ[ RVOWQWXXZ RTMUNVPVXWZZ[[[ RVRUS
+PTMULWLXMZP[S[UZVX RNUMWMXNZ RUSQTOUNWNXOZP[
+12345 47G\LFL[MZOZ RMGMY RIFNFNZ RNPONQMSMVNXPYSYUXXVZS[Q[OZNX RWPXRXVWX
+ RSMUNVOWRWVVYUZS[ RJFLG RKFLH
+12345 34H[WQWPVPVRXRXPVNTMQMNNLPKSKULXNZQ[S[VZXX RMPLRLVMX RQMONNOMRMVNY
+OZQ[
+12345 52H]VFV[[[ RWGWZ RSFXFX[ RVPUNSMQMNNLPKSKULXNZQ[S[UZVX RMPLRLVMX R
+QMONNOMRMVNYOZQ[ RTFVG RUFVH RXYY[ RXZZ[
+12345 41H[MSXSXQWOVNSMQMNNLPKSKULXNZQ[S[VZXX RWRWQVO RMPLRLVMX RVSVPUNSM
+ RQMONNOMRMVNYOZQ[
+12345 40KYWHWGVGVIXIXGWFTFRGQHPKP[ RRHQKQZ RTFSGRIR[ RMMVM RM[U[ RPZN[ R
+PYO[ RRYS[ RRZT[
+12345 89I\XNYOZNYMXMVNUO RQMONNOMQMSNUOVQWSWUVVUWSWQVOUNSMQM ROONQNSOU R
+UUVSVQUO RQMPNOPOTPVQW RSWTVUTUPTNSM RNUMVLXLYM[N\Q]U]X^Y_ RN[Q\U\X] RLY
+MZP[U[X\Y^Y_XaUbObLaK_K^L\O[ RObMaL_L^M\O[
+12345 65G^LFL[ RMGMZ RIFNFN[ RNQOOPNRMUMWNXOYRY[ RWOXRXZ RUMVNWQW[ RI[Q[
+ RT[\[ RJFLG RKFLH RLZJ[ RLYK[ RNYO[ RNZP[ RWZU[ RWYV[ RYYZ[ RYZ[[
+12345 43LXQFQHSHSFQF RRFRH RQGSG RQMQ[ RRNRZ RNMSMS[ RN[V[ ROMQN RPMQO R
+QZO[ RQYP[ RSYT[ RSZU[
+12345 41KXRFRHTHTFRF RSFSH RRGTG RRMR^QaPb RSNS]R` ROMTMT]S`RaPbMbLaL_N_
+NaMaM` RPMRN RQMRO
+12345 61G]LFL[ RMGMZ RIFNFN[ RWNNW RRSY[ RRTX[ RQTW[ RTM[M RI[Q[ RT[[[ R
+JFLG RKFLH RUMWN RZMWN RLZJ[ RLYK[ RNYO[ RNZP[ RWYU[ RVYZ[
+12345 31LXQFQ[ RRGRZ RNFSFS[ RN[V[ ROFQG RPFQH RQZO[ RQYP[ RSYT[ RSZU[
+12345 99AcFMF[ RGNGZ RCMHMH[ RHQIOJNLMOMQNROSRS[ RQORRRZ ROMPNQQQ[ RSQTO
+UNWMZM\N]O^R^[ R\O]R]Z RZM[N\Q\[ RC[K[ RN[V[ RY[a[ RDMFN REMFO RFZD[ RFY
+E[ RHYI[ RHZJ[ RQZO[ RQYP[ RSYT[ RSZU[ R\ZZ[ R\Y[[ R^Y_[ R^Z`[
+12345 65G^LML[ RMNMZ RIMNMN[ RNQOOPNRMUMWNXOYRY[ RWOXRXZ RUMVNWQW[ RI[Q[
+ RT[\[ RJMLN RKMLO RLZJ[ RLYK[ RNYO[ RNZP[ RWZU[ RWYV[ RYYZ[ RYZ[[
+12345 46H\QMNNLPKSKULXNZQ[S[VZXXYUYSXPVNSMQM RMPLRLVMX RWXXVXRWP RQMONNO
+MRMVNYOZQ[ RS[UZVYWVWRVOUNSM
+12345 60G\LMLb RMNMa RIMNMNb RNPONQMSMVNXPYSYUXXVZS[Q[OZNX RWPXRXVWX RSM
+UNVOWRWVVYUZS[ RIbQb RJMLN RKMLO RLaJb RL`Kb RN`Ob RNaPb
+12345 55H\VNVb RWOWa RUNWNXMXb RVPUNSMQMNNLPKSKULXNZQ[S[UZVX RMPLRLVMX R
+QMONNOMRMVNYOZQ[ RSb[b RVaTb RV`Ub RX`Yb RXaZb
+12345 43IZNMN[ RONOZ RKMPMP[ RWOWNVNVPXPXNWMUMSNQPPS RK[S[ RLMNN RMMNO R
+NZL[ RNYM[ RPYQ[ RPZR[
+12345 43J[WOXMXQWOVNTMPMNNMOMQNSPTUUWVXY RNNMQ RNRPSUTWU RXVWZ RMONQPRUS
+WTXVXYWZU[Q[OZNYMWM[NY
+12345 22KZPHPVQYRZT[V[XZYX RQHQWRY RPHRFRWSZT[ RMMVM
+12345 43G^LMLVMYNZP[S[UZVYWW RMNMWNY RIMNMNWOZP[ RWMW[\[ RXNXZ RTMYMY[ R
+JMLN RKMLO RYYZ[ RYZ[[
+12345 31I[LMR[ RMMRY RNMSY RXNSYR[ RJMQM RTMZM RKMNO RPMNN RVMXN RYMXN
+12345 45F^JMN[ RKMNX RLMOX RRMOXN[ RRMV[ RSMVX RRMTMWX RZNWXV[ RGMOM RWM
+]M RHMKN RNMLN RXMZN R\MZN
+12345 48H\LMV[ RMMW[ RNMX[ RWNMZ RJMQM RTMZM RJ[P[ RS[Z[ RKMMN RPMNN RUM
+WN RYMWN RMZK[ RMZO[ RVZT[ RWZY[
+12345 40H[LMR[ RMMRY RNMSY RXNSYP_NaLbJbIaI_K_KaJaJ` RJMQM RTMZM RKMNO R
+PMNN RVMXN RYMXN
+12345 41I[VML[ RWMM[ RXMN[ RXMLMLQ RL[X[XW RMMLQ RNMLP ROMLO RQMLN RS[XZ
+ RU[XY RV[XX RW[XW
+12345 40KYTBRCQDPFPHQJRKSMSOQQ RRCQEQGRISJTLTNSPORSTTVTXSZR[Q]Q_Ra RQSSU
+SWRYQZP\P^Q`RaTb
+12345  3NVRBRb
+12345 40KYPBRCSDTFTHSJRKQMQOSQ RRCSESGRIQJPLPNQPURQTPVPXQZR[S]S_Ra RSSQU
+QWRYSZT\T^S`RaPb
+12345 24F^IUISJPLONOPPTSVTXTZS[Q RISJQLPNPPQTTVUXUZT[Q[O
+12345 35JZJFJ[K[KFLFL[M[MFNFN[O[OFPFP[Q[QFRFR[S[SFTFT[U[UFVFV[W[WFXFX[Y[
+YFZFZ[
diff --git a/visad/util/wmo.jhf b/visad/util/wmo.jhf
new file mode 100644
index 0000000..292d07a
--- /dev/null
+++ b/visad/util/wmo.jhf
@@ -0,0 +1,308 @@
+1234  10B^VBNBFJFRNZVZ^R^JVB
+1234  13B^VBNBFJFRNZVZ^R^JVB RRZR^
+1234  16BbVBNBFJFRNZVZ^R^JVB RFNBN R^NbN
+1234  13>^VBNBFJFRNZVZ^R^JVB RRBR>
+1234  12>fB^B>F>JBNBR>V>ZB^Bb>f>
+1234  14FbFRFNJJNJZV^VbRbN^JZJNVJVFR
+1234  11BZZFVBNBJFJJZRZVVZNZJV
+1234  17:^ZFVBNBJFJJZRZVVZNZJV RR^R:N>R:V>
+1234  19>ZV>R>NBNFRJVJVFRFNJNNRRVRVNRNNRNVRZVZ
+1234  27>fZFVBNBJFJJZRZVVZNZJV RFNbN^JbN^R RF^BZBBF> Rb^fZfBb>
+1234   6F^FR^R RFJ^J
+1234  12F^FRNR RVR^R RFJNJ RVJ^J
+1234   9F^FR^R RFJNJ RVJ^J
+1234   7FVVFNNVVRVVVVR
+1234  49F^RJSJTKUKULVMVNVOUPUQTQSRRRQRPQOQOPNONNNMOLOKPKQJRJQJSJOKUKOLUL
+NMVMNNVNNOVOOPUPOQUQQRSR RFRJVZV^R
+1234  54>^RJSJTKUKULVMVNVOUPUQTQSRRRQRPQOQOPNONNNMOLOKPKQJRJQJSJOKUKOLUL
+NMVMNNVNNOVOOPUPOQUQQRSR RF>JBJZF^ R^>ZBZZ^^
+1234  54>^RJSJTKUKULVMVNVOUPUQTQSRRRQRPQOQOPNONNNMOLOKPKQJRJQJSJOKUKOLUL
+NMVMNNVNNOVOOPUPOQUQQRSR RN>JBJZN^ RV>ZBZZV^
+1234  21>bNVNB RJBZBRJ^VZV^V^R RJZFVFBJ> R^ZbVbB^>
+1234   6FZJFRZZFRJJF
+1234  10>^J>NBNZJ^ RV>RBRZV^
+1234  51!!NGOGPGPHQIQJQKQLPLOMNMMMLMLLKKKJKIKHLHMGNGLGOGLHQHKIQIKJQJKKQK
+KLPLMMPM ?NJNNJR ?RZVZVBRB ?JRPNQLMPPM
+1234  49BZNJOJPKQKQLRMRNROQPQQPQORNRMRLQKQKPJOJNJMKLKKLKMJNJMJOJKKQKKLQL
+JMRMJNRNJOROKPQPKQQQMROR RRZVZVBRB
+1234  14BZVZZZZBVB RJRRJ RJJRR RFNVN
+1234  58BZVZZZZBVB RJVRN RJNRV RFRVR RNBOBPCQCQDRERFRGQHQIPIOJNJMJLIKIKH
+JGJFJEKDKCLCMBNBMBOBKCQCKDQDJEREJFRFJGRGKHQHKIQIMJOJ
+1234  16Bb^ZbZbB^B RJRFNFJJFNFVRZR^N^JZF
+1234  54BZVZZZZBVB RNZJNRNNZ RNBOBPCQCQDRERFRGQHQIPIOJNJMJLIKIKHJGJFJEKD
+KCLCMBNBMBOBKCQCKDQDJEREJFRFJGRGKHQHKIQIMJOJ
+1234  19BZVZZZZBVB RNZJNRNNZ RJJRB RJBRJ RFFVF
+1234  15BZVZZZZBVB RNZJNRNNZ RJJNBRJJJ
+1234  14BbFV^V RFN^N RFF^F R^ZbZbB^B
+1234  16>^BZBF R>FNFFNRZRVRZNZ RN^V^VBNB
+1234  20>bVFRBJBFFFJVRVVRZJZFV RBN^NZJ^NZR Rb^b>
+1234  17BbZFVBNBJFJJZRZVVZNZJV RFNbN^JbN^R
+1234  20>bZFVBNBJFJJZRZVVZNZJV RB^B> RFNbN^JbN^R
+1234  23:fVBR>J>FBFJVRVZR^J^FZ RBR^RbNZVbNZF^JBJ Rfbf:
+1234  20>bVBR>J>FBFJVRVZR^J^FZ RBR^RbNZVbNZF^JBJ
+1234  23:bVBR>J>FBFJVRVZR^J^FZ RBR^RbNZVbNZF^JBJ R>b>:
+1234  12B^FN^NZJ^NZR RRBRZNVRZVV
+1234  14BbFR^RZVbNZF^JFJ RRBRZNVRZVV
+1234  12B^FN^NZJ^NZR RNFRBVFRBRZ
+1234  14BbFR^RZVbNZF^JFJ RNFRBVFRBRZ
+1234  19>bFV^V RFN^N RFF^F RF^BVBFF> R^^bVbF^>
+1234  15F^FVNV RVV^V RFN^N RFFNF RVF^F
+1234  15BbFV^V RFN^N RFFNF RVF^F RbZbB
+1234  12BbFV^V RFN^N RFF^F RbZbB
+1234  12F^FV^V RFN^N RFFNF RVF^F
+1234   9F^FV^V RFN^N RFF^F
+1234  15B^FV^V RFN^N RFFNF RVF^F RBZBB
+1234  12B^FV^V RFN^N RFF^F RBZBB
+1234  17F^NNRVVNNN RFV^V RFN^N RFFJFRVZF^F
+1234  18F^NNRVVNNN RFV^V RFN^N RFF^F RJFRVZF
+1234  46!!RGSGTGTHUIUJUKULTLSMRMQMPMPLOKOJOIOHPHQGRGPGSGPHUHOIUIOJUJOKUK
+OLTLQMTM ?RJRNNR ?NRTNULQPSM
+1234  94!!JGKGLGLHMIMJMKMLLLKMJMIMHMHLGKGJGIGHHHIGJGHGKGHHMHGIMIGJMJGKMK
+GLLLIMLM ?JJJNFR ?ZG[G\G\H]I]J]K]L\L[MZMYMXMXLWKWJWIWHXHYGZGXG[GXH]HWI]I
+WJ]JWK]KWL\LYM\M ?ZJZNVR ?FRLNMLIOLNJM ?VR\N]LYO[NZM
+1234  93!!RCSCTCTDUEUFUGUHTHSIRIQIPIPHOGOFOEODPDQCRCPCSCPDUDOEUEOFUFOGUG
+OHTHQITI ?RFRJNN ?ROSOTOTPUQURUSUTTTSURUQUPUPTOSOROQOPPPQOROPOSOPPUPOQUQ
+ORUROSUSOTTTQUTU ?RRRVNZ ?NNTJUHQKSJ ?NZTVUTQWTVRU
+1234 141!!FGGGHGHHIIIJIKILHLGMFMEMDMDLCKCJCICHDHEGFGDGGGDHIHCIIICJIJCKIK
+CLHLEMHM ?FJFNBR ?^G_G`G`HaIaJaKaL`L_M^M]M\M\L[K[J[I[H\H]G^G\G_G\HaH[IaI
+[JaJ[KaK[L`L]M`M ?^J^NZR ?R?S?T?T at UAUBUCUDTDSEREQEPEPDOCOBOAO@P at Q?R?P?S?
+P at U@OAUAOBUBOCUCODTDQETE ?RBRFNJ ?BRHNHLEOGNGM ?NJTFUDQGTFSE ?ZR`NaL]O_N
+_M
+1234 141!!R;S;T;T<U=U>U?U at T@SARAQAPAP at O?O>O=O<P<Q;R;P;S;P<U<O=U=O>U>O?U?
+O at T@QATA ?R>RBNF ?RGSGTGTHUIUJUKULTLSMRMQMPMPLOKOJOIOHPHQGRGPGSGPHUHOIUI
+OJUJOKUKOLTLQMTM ?RJRNNR ?RSSSTSTTUUUVUWUXTXSYRYQYPYPXOWOVOUOTPTQSRSPSSS
+PTUTOUUUOVUVOWUWOXTXQYTY ?RVRZN^ ?NFTBU at QCTBSA ?NRTNULQOTNRM ?N^TZUXQ[TZ
+RY
+1234 188!!FGGGHGHHIIIJIKILHLGMFMEMDMDLCKCJCICHDHEGFGDGGGDHIHCIIICJIJCKIK
+CLHLEMHM ?FJFNBR ?^G_G`G`HaIaJaKaL`L_M^M]M\M\L[K[J[I[H\H]G^G\G_G\HaH[IaI
+[JaJ[KaK[L`L]M`M ?^J^NZR ?R?S?T?T at UAUBUCUDTDSEREQEPEPDOCOBOAO@P at Q?R?P?S?
+P at U@OAUAOBUBOCUCODTDQETE ?RBRFNJ ?ROSOTOTPUQURUSUTTTSURUQUPUPTOSOROQOPPP
+QOROPOSOPPUPOQUQORUROSUSOTTTQUTU ?RRRVNZ ?BRHNILEOHNFM ?NJTFUDQGTFRE ?ZR`NaL]O`N_M ?NZTVUTQWTVRU
+1234  58!!FGGGHGHHIIIJIKILHLGMFMEMDMDLCKCJCICHDHEGFGDGGGDHIHCIIICJIJCKIK
+CLHLEMHM ?FJFNBR ?BV>R>N>JBFJFZVbVfRfJbF ?BRHNILEPGM
+1234 108!!FGGGHGHHIIIJIKILHLGMFMEMDMDLCKCJCICHDHEGFGDGGGDHIHCIIICJIJCKIK
+CLHLEMHM ?FJFNBR ?^G_G`G`HaIaJaKaL`L_M^M]M\M\L[K[J[I[H\H]G^G\G_G\HaH[IaI
+[JaJ[KaK[L`L]M`M ?^J^NZR ?BV>R>N>JBFJFZVbVfRfJbF ?ZR`NaL]O`N^M ?BRHNIL ?
+HLEOHNFM
+1234  91!!RBSBTCUCUDVEVFVGUHUITISJRJQJPIOIOHNGNFNEODOCPCQBRBQBSBOCUCODUD
+NEVENFVFNGVGOHUHOIUIQJSJ ?ROSOTOTPUQURUSUTTTSURUQUPUPTOSOROQOPPPQOROPOSO
+PPUPOQUQORUROSUSOTTTQUTU ?RRRVNZ ?NZTVUTRVTVSU
+1234 138!!R;S;T;T<U=U>U?U at T@SARAQAPAP at O?O>O=O<P<Q;R;P;S;P<U<O=U=O>U>O?U?
+O at T@QATA ?R>RBNF ?RJSJTKUKULVMVNVOUPUQTQSRRRQRPQOQOPNONNNMOLOKPKQJRJQJSJ
+OKUKOLULNMVMNNVNNOVOOPUPOQUQQRSR ?RWSWTWTXUYUZU[U\T\S]R]Q]P]P\O[OZOYOXPX
+QWRWPWSWPXUXOYUYOZUZO[U[O\T\Q]T] ?RZR^Nb ?NFTBU at RBTBSA ?NbT^U\R^T^S]
+1234  44JVRJSJTKUKULVMVNVOUPUQTQSRRRQRPQOQOPNONNNMOLOKPKQJRJQJSJOKUKOLUL
+NMVMNNVNNOVOOPUPOQUQQRSR
+1234  88F^JJKJLKMKMLNMNNNOMPMQLQKRJRIRHQGQGPFOFNFMGLGKHKIJJJIJKJGKMKGLML
+FMNMFNNNFONOGPMPGQMQIRKR RZJ[J\K]K]L^M^N^O]P]Q\Q[RZRYRXQWQWPVOVNVMWLWKXK
+YJZJYJ[JWK]KWL]LVM^MVN^NVO^OWP]PWQ]QYR[R
+1234  88BZRRSRTSUSUTVUVVVWUXUYTYSZRZQZPYOYOXNWNVNUOTOSPSQRRRQRSROSUSOTUT
+NUVUNVVVNWVWOXUXOYUYQZSZ RRBSBTCUCUDVEVFVGUHUITISJRJQJPIOIOHNGNFNEODOCPC
+QBRBQBSBOCUCODUDNEVENFVFNGVGOHUHOIUIQJSJ
+1234 132B^JNKNLOMOMPNQNRNSMTMULUKVJVIVHUGUGTFSFRFQGPGOHOINJNINKNGOMOGPMP
+FQNQFRNRFSNSGTMTGUMUIVKV RZN[N\O]O]P^Q^R^S]T]U\U[VZVYVXUWUWTVSVRVQWPWOXO
+YNZNYN[NWO]OWP]PVQ^QVR^RVS^SWT]TWU]UYV[V RRBSBTCUCUDVEVFVGUHUITISJRJQJPI
+OIOHNGNFNEODOCPCQBRBQBSBOCUCODUDNEVENFVFNGVGOHUHOIUIQJSJ
+1234 132>^RVSVTWUWUXVYVZV[U\U]T]S^R^Q^P]O]O\N[NZNYOXOWPWQVRVQVSVOWUWOXUX
+NYVYNZVZN[V[O\U\O]U]Q^S^ RRJSJTKUKULVMVNVOUPUQTQSRRRQRPQOQOPNONNNMOLOKPK
+QJRJQJSJOKUKOLULNMVMNNVNNOVOOPUPOQUQQRSR RR>S>T?U?U at VAVBVCUDUETESFRFQFPE
+OEODNCNBNAO at O?P?Q>R>Q>S>O?U?O at U@NAVANBVBNCVCODUDOEUEQFSF
+1234 176>^RVSVTWUWUXVYVZV[U\U]T]S^R^Q^P]O]O\N[NZNYOXOWPWQVRVQVSVOWUWOXUX
+NYVYNZVZN[V[O\U\O]U]Q^S^ RJJKJLKMKMLNMNNNOMPMQLQKRJRIRHQGQGPFOFNFMGLGKHK
+IJJJIJKJGKMKGLMLFMNMFNNNFONOGPMPGQMQIRKR RZJ[J\K]K]L^M^N^O]P]Q\Q[RZRYRXQ
+WQWPVOVNVMWLWKXKYJZJYJ[JWK]KWL]LVM^MVN^NVO^OWP]PWQ]QYR[R RR>S>T?U?U at VAVB
+VCUDUETESFRFQFPEOEODNCNBNAO at O?P?Q>R>Q>S>O?U?O at U@NAVANBVBNCVCODUDOEUEQFSF
+1234  56>fFJGJHKIKILJMJNJOIPIQHQGRFRERDQCQCPBOBNBMCLCKDKEJFJEJGJCKIKCLIL
+BMJMBNJNBOJOCPIPCQIQERGR RBV>R>N>JBFJFZVbVfRfJbF
+1234 100>fFJGJHKIKILJMJNJOIPIQHQGRFRERDQCQCPBOBNBMCLCKDKEJFJEJGJCKIKCLIL
+BMJMBNJNBOJOCPIPCQIQERGR R^J_J`KaKaLbMbNbOaPaQ`Q_R^R]R\Q[Q[PZOZNZM[L[K\K
+]J^J]J_J[KaK[LaLZMbMZNbNZObO[PaP[QaQ]R_R RBV>R>N>JBFJFZVbVfRfJbF
+1234  53BZRBSBTCUCUDVEVFVGUHUITISJRJQJPIOIOHNGNFNEODOCPCQBRBQBSBOCUCODUD
+NEVENFVFNGVGOHUHOIUIQJSJ RNVVN RNNVV RJRZR
+1234  62>^N^VV RNVV^ RJZZZ RRJSJTKUKULVMVNVOUPUQTQSRRRQRPQOQOPNONNNMOLOK
+PKQJRJQJSJOKUKOLULNMVMNNVNNOVOOPUPOQUQQRSR RNFV> RN>VF RJBZB
+1234   9JZJNZN RNRVJ RNJVR
+1234  18>fBRJJ RBJJR R>NNN RZRbJ RZJbR RVNfN
+1234  18BZNVVN RNNVV RJRZR RNJVB RNBVJ RJFZF
+1234  27>fBNJV RBVJN R>RNR RZNbV RZVbN RVRfR RNBVJ RNJVB RJFZF
+1234  27>^N^VV RNVV^ RJZZZ RNRVJ RNJVR RJNZN RNFV> RN>VF RJBZB
+1234  36>fN>VF RNFV> RJBZB RBJJR RBRJJ R>NNN RZJbR RZRbJ RVNfN RNVV^ RN^
+VV RJZZZ
+1234   9F^JRFNJJFN^NZJ^NZR
+1234   8F^FN^N RNRRJVRNR
+1234   9BbBNbN RNRVJ RNJVR
+1234  41B^FVRB^VFV RRKSKTKTLUMUNUOUPTPSQRQQQPQPPOOONOMOLPLQKRKPKSKPLULOM
+UMONUNOOUOOPTPQQTQ
+1234  49>bFJRb^JFJ RR>S>T?U?U at VAVBVCUDUETESFRFQFPEOEODNCNBNAO@O?P?Q>R>Q>
+S>O?U?O at U@NAVANBVBNCVCODUDOEUEQFSF
+1234  52>bFJRb^JFJ RR>S>T?U?U at VAVBVCUDUETESFRFQFPEOEODNCNBNAO@O?P?Q>R>Q>
+S>O?U?O at U@NAVANBVBNCVCODUDOEUEQFSF RJRZR
+1234  936bFJRb^JFJ RR>S>T?U?U at VAVBVCUDUETESFRFQFPEOEODNCNBNAO@O?P?Q>R>Q>
+S>O?U?O at U@NAVANBVBNCVCODUDOEUEQFSF RR6S6T7U7U8V9V:V;U<U=T=S>R>Q>P=O=O<N;
+N:N9O8O7P7Q6R6Q6S6O7U7O8U8N9V9N:V:N;V;O<U<O=U=Q>S>
+1234  586bFJRb^JFJ RNFV> RN>VF RJBZB RR6S6T7U7U8V9V:V;U<U=T=S>R>Q>P=O=O<
+N;N:N9O8O7P7Q6R6Q6S6O7U7O8U8N9V9N:V:N;V;O<U<O=U=Q>S>
+1234  616bFJRb^JFJ RNFV> RN>VF RJBZB RR6S6T7U7U8V9V:V;U<U=T=S>R>Q>P=O=O<
+N;N:N9O8O7P7Q6R6Q6S6O7U7O8U8N9V9N:V:N;V;O<U<O=U=Q>S> RJRZR
+1234  14:bFJRb^JFJ RNBV: RN:VB RJ>Z>
+1234  17:bFJRb^JFJ RJRZR RNBV: RN:VB RJ>Z>
+1234  10>bFJRb^JFJ RFFR>^FFF
+1234  13>bFJRb^JFJ RJRZR RFFR>^FFF
+1234  10>bFFR>^FFF RFJRb^JFJ
+1234  13>bFFR>^FFF RFJRb^JFJ RJRZR
+1234  60>bBZBF R>FNFFNRZRVRZNZ RN^V^VBNB R^J_J`KaKaLbMbNbOaPaQ`Q_R^R]R\Q
+[Q[PZOZNZM[L[K\K]J^J]J_J[KaK[LaLZMbMZNbNZObO[PaP[QaQ]R_R
+1234 104>bBZBF R>FNFFNRZRVRZNZ RN^V^VBNB R^R_R`SaSaTbUbVbWaXaY`Y_Z^Z]Z\Y
+[Y[XZWZVZU[T[S\S]R^R]R_R[SaS[TaTZUbUZVbVZWbW[XaX[YaY]Z_Z R^F_F`GaGaHbIbJ
+bKaLaM`M_N^N]N\M[M[LZKZJZI[H[G\G]F^F]F_F[GaG[HaHZIbIZJbJZKbK[LaL[MaM]N_N
+1234  21>bBZBF R>FNFFNRZRVRZNZ RN^V^VBNB R^JZRbR^J
+1234  25>bBZBF R>FNFFNRZRVRZNZ RN^V^VBNB RZRbJ RZJbR RZNbN
+1234  26>bBZBF R>FNFFNRZRVRZNZ RN^V^VBNB R^FZJbJ^F R^RZVbV^R
+1234  34>bBZBF R>FNFFNRZRVRZNZ RN^V^VBNB RZFbN RZNbF RZJbJ RZRbZ RZZbR R
+ZVbV
+1234  55>^R>S>T?U?U at VAVBVCUDUETESFRFQFPEOEODNCNBNAO@O?P?Q>R>Q>S>O?U?O at U@
+NAVANBVBNCVCODUDOEUEQFSF RJJZJRR^^^Z^^Z^ RN^NJ
+1234  20>^JJZJRR^^^Z^^Z^ RN^NJ RNFV> RN>VF RJBZB
+1234  16>^JJZJRR^^^Z^^Z^ RN^NJ RR>NFVFR>
+1234  56>^FJZJRRZVR^RZR^V^ RJ^JJ RR>S>T?U?U at VAVBVCUDUETESFRFQFPEOEODNCNB
+NAO at O?P?Q>R>Q>S>O?U?O at U@NAVANBVBNCVCODUDOEUEQFSF
+1234  21>^FJZJRRZVR^RZR^V^ RJ^JJ RNFV> RN>VF RJBZB
+1234  24>^JJZJRR^^^Z^^Z^ RN^NJ RJBZBVFZBV> RR>N>NBRBRFNF
+1234  17>^FJZJRRZVR^RZR^V^ RJ^JJ RR>NFVFR>
+1234  10F^GVGSJOOMTMYO[S[VGV
+1234  18F^GVGSJOOMTMYO[S[VGV RJOJMLIQFVIYMYO
+1234  21F^GVGSJOOMTMYO[S[VGV RJOJMLIQFVIYMYO RQFQO
+1234  17F^GMLMLOOQTQVOVM[M RJMJKLFQDVFYKYM
+1234   9F^GMLMLOOQTQVOVM[M
+1234   3F^GM[M
+1234   9F^EMJM ROMTM RYM^M
+1234  19F^GVGSJOOMTMYO[S[VGV RGDLDLFOITIVFVD[D
+1234  15F^GVGSJOOMTMYO[S[VGV RJOGI[IYO
+1234   4F^[VGV[D
+1234   7F^[VGV[D R[KOV
+1234  12F^GKGMJOOOQMQKQMTOYO[M[K
+1234   8F^[DGQGSJVOVQSQQ
+1234  13F^[DGQGSJVOVQSQQQSTVYV[S[Q
+1234  21F^GVGSJOOMTMYO[S[V RGIGKJMOMQKQIQKTMYM[K[I
+1234  14F^[DGQGSJVOVQSQQQSTVYV[S[QGQ
+1234   9F^LVLDLFOITIVFVDVV
+1234  13F^[DGOGQJSOSQQQO RQSOVJVGS
+1234   6F^GOYO[M[KYI
+1234  11F^GOYO[M[KYI RVIYKYMVO
+1234   7F^GKYK[M[OYQYS
+1234   6F^TDVDYFYIJV
+1234   6F^JILKLMGO[O
+1234   7F^TDVDYFYIJVQV
+1234   9F^JILKLMGO[OVMVKYI
+1234   6F^GO[OVMVKYI
+1234  16F^VDYD[F[IGQGSJVOVQSQQQSTVYV[S[Q
+1234  18B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB
+1234  21B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?RGRU
+1234  61B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?RBRN^N ?RCWCRDXDREZERF[FRG
+\GRH\HRI]IRJ]J^JRK^KRL^LRM^MRNSNSBRNTBTNUBWDXEYFZG[H\I]J]N
+1234  49B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?RBRZ ?^NRN ?VBRCWCRDYDREZE
+RF[FRG\GRH]HRI]IRJ^JRK^KRL^LRM^MRN
+1234  75B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?RBRZ ?VBRCWCRDXDREZERF[FRG
+\GRH\HRI]IRJ^J ?^JRK^KRL^LRM^MRN^NRO\O^ORP^PRQ^QRR^R ?^RRS]SRT\TRU[URV[V
+RWYWRXYXRYWYRZ
+1234  78B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?RBRZ ?VBRCWCRDXDREZERF[FRG
+\GRH\HRI]IRJ^J ?^JRK^KRL^LRM^MRN^NRO\O^ORP^PRQ^QRR^R ?^RRS]SRT\TRU[URV[V
+RWYWRXYXRYWYRZ ?FNRN
+1234 103B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?RBRZ ?VBRCWCRDXDREZERF[FRG
+\GRH\HRI]IRJ^J ?^JRK^KRL^LRM^MRN^NRO\O^ORP^PRQ^QRR^R ?^RRS]SRT\TRU[URV[V
+RWYWRXYXRYWYRZ ?FNRNFOROFPRPFQRQFRRR ?RRGSRSHTRTIURUIVRVJWRWLXRXMYRYNZ
+1234 128B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?ODUDUXOXOD ?NBWCMCXDUDYEUE
+[FUF\GUG]HUH]IUI^JUJ ?UJ^KUK^LUL^MUM^NUN^OUO^PVPUP^QUQ^RUR ?UR]SUS\TUT\U
+UU[VUVZWUWYXUXWYOX ?NZWYMYPXKXOWJWOVIVOUHUOTGTOSGSORFR ?FROQFQOPFPOOFOON
+FNOMFMOLFLOKFKOJFJ ?FJOIGIOHHHOGHGOFIFOEJEODLDPD
+1234 128B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?RBRZ ?VBRCWCRDXDREZERF[FRG
+\GRH\HRI]IRJ^J ?^JRK^KRL^LRM^MRN^NRO\O^ORP^PRQ^QRR^R ?^RRS]SRT\TRU[URV[V
+RWYWRXYXRYWYRZ ?RBMCRCLDRDJEREIFRFHGRGGHRHFIRIFJRJ ?RJFKRKFLRLFMRMFNRNFO
+ROFPRPFQRQFRRR ?RRGSRSGTRTHURUIVRVJWRWLXRXMYRYNZ
+1234  30B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?IV ?IV ?IV ?ZFJW ?IF[V
+1234  24B^VBNBJEHGFJFRHUJWNZVZZW\U^R^J\GZEVB ?LTLGRNXGXT
+1234   4B^FZXH^N
+1234   4B^FZXH^H
+1234   3B^FZ^B
+1234   4B^FNLT^B
+1234   3B^FN^N
+1234   4B^FBXT^N
+1234   4B^FBXT^T
+1234   3B^FB^Z
+1234   4B^FNLH^Z
+1234  13F^UDPDMHMMQPUPXMXGUD RYCMQ
+1234   9F^OKTK RGKLK RWK\K
+1234   4F^LKQBVK
+1234  11F^EQLQQIVQ^Q RPQ RPQSQ
+1234   6F^EQLQQIVQ^Q
+1234  13F^EQLQQIVQ^Q RLKQBVK RPQSQ
+1234  10F^EQLQQIVQ^Q RLKQBVK
+1234  57F^LKQBVK RLQQIVQ RLQ RLQVQMPUPMOUONNTNNMTMOLSLPKRKQJQJQIMQQJNQRJ
+OQRKPQSLQQSMRQTNSQTOTQUPUQQJMQNONNPQMONQMP RFQGQ\Q
+1234  60F^EQLQQIVQ^Q RLKQBVK RLQVQMPUPMOUONNTNNMTMOLSLPKRKPJRJPKUQOLSQOM
+RQNNQQNOOQMONQMPMQQJNQRKOQSLPQSMQQTNRQUOSQUPUQQIQJPKQIRK
+1234  13F^GM[M ROQTQVOVKTIOILKLOOQ
+1234   7F^VIVOTQOQLOLI
+1234  10F^VFVMTOOOLMLF RQMQV
+1234  10F^VFVMTOOOLMLF RQIQV
+1234  13F^YFYMVOLOJMJF ROMOV RTMTV
+1234  13F^YFYMVOLOJMJF ROIOV RTITV
+1234  15F^[FYFYMVOLOJMJFGF ROIOV RTITV
+1234  16F^[F[MYOJOGMGF RLMLV RQMQV RVMVV
+1234  16F^[F[MYOJOGMGF RLILV RQIQV RVIVV
+1234  11F^VIVMTOOOLMLIVI RQDQV
+1234  14F^YIYMVOLOJMJIYI RODOV RTDTV
+1234   6F^EXEB^B^XEX
+1234  53!!EXEB^B^XEX ?EB^B^BEC^CED^DEE^EEF^FEG^GEH^HEI^IEJ^JEK^KEL^LEM^M
+EN^NEO^OEP^PEQ^QER^RES^SET^TEU^UEV^VEW^WEX
+1234  18F^EKEOGSJVOXTXYV[S^O^K[FYDTBOBJDGFEK
+1234  63!!EKEOGSJVOXTXYV[S^O^K[FYDTBOBJDGFEK ?OXVWMWXVKVZUIUZTHT[SGS[RGR
+\QFQ]PFP^OEO^NEN^MEM^LEL^KEK]JEJ]IFI\HFH[GGG[FGFZEIEYDKDVCMCTB
+1234   5F^EX^XQBEX
+1234  50!!EX^XQBEX ?EX]WFW]VFV\UGU[TGT[SHSZRHRZQIQYPJPYOJOXNKNWMKMWLLLVK
+LKVJMJUIMIUHNHTGNGSFOFSEOERDPDQCQDQC
+1234   6F^QBEMQX^MQB
+1234  52!!QBEMQX^MQB ?EM^MFN\NGO[OHPZPIQYQJRXRKSWSLTVTNUTUOVSVPWRW ?EM]L
+FL\KGKZJHJYIIIXHJHWGKGVFMFUEMETDODRCPCRD
+1234  12F^GVQQ[VVM^IVIQBLIEILMGV
+1234  69!!GVQQ[VVM^IVIQBLIEILMGV ?LMVMKNVNKOWOJPXPJQQQIRORISMSHTKTHUHU ?
+XPQQXQRRYRTSYSVTYTYUZUWS ?LMWLKLYKIK\JGJ^ILIUHMHTGNGTFNFSEOESDPDRCPC ?EI
+UJ
+1234  14F^GDLDLKVKVD[D[XVXVOLOLXGXGD
+1234   8F^GDLDLS[S[XGXGD
+1234  45!!GDLDLKVKVD[D[XVXVOLOLXGXGD ?GDHXHDIXIDJXJDKXKDLXLKVKLLVLLMVMLN
+VNLOVOVDWXWDXXXDYXYDZXZD[X
+1234  28!!GDLDLS[S[XGXGD ?GDHXHDIXIDJXJDKXKDLX[WLW[VLV[ULU[TLT[S
+1234   6F^EOYO ROXOF
+1234   3F^EOYO
+1234  66!!VBTBODLILMOIQDVB ?LXOXTVVQVMTQQVLX ?OILKLMOQTQVOVMTIOI ?VBODOI
+LIQDTBNFPFNINFPDPFMINF ?LXTVTQVQQVOXUTSSUQUSRTTUQVTVVMVQUO ?LIMLMINJ
+1234  72!!VBTBODLILMOQTQQVLXOXTVVQVMTIOIQDVB ?VBODQDTB ?LXTVQVOX ?ODOILI
+QDPDNIPFNFLMNIMNOINOPIOPQIOQRIPQSIQQTIRQUJSQULTQVMRVVQUOUTSSTV ?QIULSIVM
+ ?LMQQNO
+1234  71!!TXQXLVJQJMLQOVTX ?TMQIODJBLBQDTITMQQLQJOJMLIQITKQQLQ ?JBQDODLB
+TIQIQDRFPGRIPDSIQDODRI ?TXLVLQJQOVQXRXLUKQMVLQNWNT ?TIRJTJSKTKRI ?JQKPJP
+KOJNLQ
+1234  73!!TXQXLVJQJMLIQIODJBLBQDTITMQQLQOVTX ?TXLVOVQX ?LVLQJQOVMVJMLQKL
+MQKJNQLIOQMIPQNIQQOIRPPISOQISNTISMSHRLSFQIRFQHQDODTIPFTM ?JBQDODLB ?SNLQ
+ ?RKJMQIKJ
+1234  14F^GVYVQKGV RQKQB RLFVF RLDVD
+1234  21!!EIEKGMJMLKLILKOMQMTKTITKVMYM[K[I ?JVPFVV
+1234   3F^EX^B
+1234  10F^EXEB^B^XEX^B R^XEB
+1234  24F^EKEOGSJVOXTXYV[S^O^K[FYDTBOBJDGFEK RJSYF RYSJF
+1234  15F^EB^B RQBQX R^V[XYXVVVQYO[O^Q
+1234 149!!QXQVTXVS[Q^O^M[I[KVOVKTBQFODOFLKLMOQOOTMQOQSJQGOEIEMGQEOEQGSES
+QX ?GIJMJILFLDJFJBGIGMJOGI ?GMHKHNIMIN ?GIJDHIJFLFKEJIIDHKJGIKJJGIJL ?OF
+PEOOOFNONHMNMJOQLKQFPNRDQNTBRMUFSMVITMVKQSVOQO ?[K\JVSSMTXQS^MVS^O[K[QXM
+XQVOQVVSQSOWORMVMRKUKQITIPHTGQTTGSQV ?EOFRGQ ?EMGOEKGQ ?ZL]LXM^MYN^OVO\P
+UP[QQQVSSWKQ ?QWGS
+1234   6F^EX^B R^XEB
+1234  14!!EBJBJSVSVXEXEB ?LQVI ?LIVQ
+1234  34!!EBJBJSVSVXEXEB ?LQVI ?LIVQ ?EBFXFBGXGBHXHBIXIBJXVWJWVVJVVUJUVT
+JTVS
+1234  13F^EQJSOQQMYM[O^S RQMOKODQB
+1234  13F^^QYSTQQMJMGOES RQMTKTDQB
+1234  47!!OVQXSVQSOVGVLKVK[VSV ?QKLD ?QKQD ?QKVD ?ZN ?OVOTQSSTSVSWQXOWOV
+ ?OTSTOUSUOVSVOWSWOVSVOTQXQSSW
+1234  14F^EQGOLKQFVFQKQOTSYS^O RYF[F

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/visad.git



More information about the debian-med-commit mailing list